From 39ed87570bdb2f86969d4be821c94b722dc71179 Mon Sep 17 00:00:00 2001 From: Joe Ludwig Date: Wed, 26 Jun 2013 15:22:04 -0700 Subject: First version of the SOurce SDK 2013 --- .../captioncompiler/captioncompiler-2010.vcxproj | 261 + .../captioncompiler-2010.vcxproj.filters | 110 + mp/src/utils/captioncompiler/captioncompiler.cpp | 588 +++ mp/src/utils/captioncompiler/cbase.h | 19 + mp/src/utils/common/ISQLDBReplyTarget.h | 29 + mp/src/utils/common/MySqlDatabase.cpp | 192 + mp/src/utils/common/MySqlDatabase.h | 104 + mp/src/utils/common/bsplib.cpp | 5064 ++++++++++++++++++++ mp/src/utils/common/bsplib.h | 404 ++ mp/src/utils/common/cmdlib.cpp | 1007 ++++ mp/src/utils/common/cmdlib.h | 178 + mp/src/utils/common/consolewnd.cpp | 333 ++ mp/src/utils/common/consolewnd.h | 45 + mp/src/utils/common/filesystem_tools.cpp | 209 + mp/src/utils/common/filesystem_tools.h | 59 + mp/src/utils/common/map_shared.cpp | 136 + mp/src/utils/common/map_shared.h | 91 + mp/src/utils/common/movie.h | 34 + mp/src/utils/common/mpi_stats.cpp | 839 ++++ mp/src/utils/common/mpi_stats.h | 59 + mp/src/utils/common/mstristrip.cpp | 930 ++++ mp/src/utils/common/mstristrip.h | 43 + mp/src/utils/common/pacifier.cpp | 63 + mp/src/utils/common/pacifier.h | 23 + mp/src/utils/common/physdll.cpp | 31 + mp/src/utils/common/physdll.h | 30 + mp/src/utils/common/polylib.cpp | 915 ++++ mp/src/utils/common/polylib.h | 78 + mp/src/utils/common/qfiles.h | 42 + mp/src/utils/common/scratchpad_helpers.cpp | 103 + mp/src/utils/common/scratchpad_helpers.h | 25 + mp/src/utils/common/scriplib.cpp | 1349 ++++++ mp/src/utils/common/scriplib.h | 96 + mp/src/utils/common/threads.cpp | 257 + mp/src/utils/common/threads.h | 65 + mp/src/utils/common/tools_minidump.cpp | 61 + mp/src/utils/common/tools_minidump.h | 35 + mp/src/utils/common/utilmatlib.cpp | 184 + mp/src/utils/common/utilmatlib.h | 41 + mp/src/utils/common/vmpi_tools_shared.cpp | 374 ++ mp/src/utils/common/vmpi_tools_shared.h | 45 + mp/src/utils/common/wadlib.c | 334 ++ mp/src/utils/common/wadlib.h | 46 + mp/src/utils/glview/glos.h | 21 + mp/src/utils/glview/glview-2010.vcxproj | 250 + mp/src/utils/glview/glview-2010.vcxproj.filters | 77 + mp/src/utils/glview/glview.cpp | 1427 ++++++ .../utils/height2normal/height2normal-2010.vcxproj | 241 + .../height2normal-2010.vcxproj.filters | 56 + mp/src/utils/height2normal/height2normal.cpp | 343 ++ .../utils/motionmapper/motionmapper-2010.vcxproj | 288 ++ .../motionmapper/motionmapper-2010.vcxproj.filters | 188 + mp/src/utils/motionmapper/motionmapper.cpp | 3272 +++++++++++++ mp/src/utils/motionmapper/motionmapper.h | 274 ++ mp/src/utils/nvtristriplib/nvtristrip.h | 124 + mp/src/utils/phonemeextractor/extractor_utils.cpp | 28 + .../phonemeextractor/phonemeextractor-2010.vcxproj | 268 ++ .../phonemeextractor-2010.vcxproj.filters | 146 + mp/src/utils/phonemeextractor/phonemeextractor.cpp | 1425 ++++++ .../phonemeextractor/phonemeextractor_ims.cpp | 1075 +++++ mp/src/utils/phonemeextractor/talkback.doc | Bin 0 -> 8114176 bytes mp/src/utils/phonemeextractor/talkback.h | 732 +++ mp/src/utils/qc_eyes/QC_Eyes.cpp | 73 + mp/src/utils/qc_eyes/QC_Eyes.h | 50 + mp/src/utils/qc_eyes/QC_Eyes.rc | 276 ++ mp/src/utils/qc_eyes/QC_EyesDlg.cpp | 705 +++ mp/src/utils/qc_eyes/QC_EyesDlg.h | 134 + mp/src/utils/qc_eyes/StdAfx.cpp | 9 + mp/src/utils/qc_eyes/StdAfx.h | 28 + mp/src/utils/qc_eyes/qc_eyes-2010.vcxproj | 261 + mp/src/utils/qc_eyes/qc_eyes-2010.vcxproj.filters | 104 + mp/src/utils/qc_eyes/res/QC_Eyes.ico | Bin 0 -> 1078 bytes mp/src/utils/qc_eyes/res/QC_Eyes.rc2 | 13 + mp/src/utils/qc_eyes/res/eye_XY_L.bmp | Bin 0 -> 101956 bytes mp/src/utils/qc_eyes/res/eye_XY_R.bmp | Bin 0 -> 94228 bytes mp/src/utils/qc_eyes/res/eye_Z_L.bmp | Bin 0 -> 95864 bytes mp/src/utils/qc_eyes/res/eye_Z_R.bmp | Bin 0 -> 100212 bytes mp/src/utils/qc_eyes/res/eye_default.bmp | Bin 0 -> 94264 bytes mp/src/utils/qc_eyes/res/eye_lower_hi.bmp | Bin 0 -> 92984 bytes mp/src/utils/qc_eyes/res/eye_lower_lo.bmp | Bin 0 -> 94576 bytes mp/src/utils/qc_eyes/res/eye_lower_mid.bmp | Bin 0 -> 94316 bytes mp/src/utils/qc_eyes/res/eye_upper_hi.bmp | Bin 0 -> 95056 bytes mp/src/utils/qc_eyes/res/eye_upper_lo.bmp | Bin 0 -> 91216 bytes mp/src/utils/qc_eyes/res/eye_upper_mid.bmp | Bin 0 -> 94300 bytes mp/src/utils/qc_eyes/resource.h | 78 + .../utils/serverplugin_sample/serverplugin_bot.cpp | 383 ++ .../serverplugin_empty-2010.vcxproj | 258 + .../serverplugin_empty-2010.vcxproj.filters | 110 + .../serverplugin_sample/serverplugin_empty.cpp | 922 ++++ mp/src/utils/smdlexp/smdlexp.cpp | 1096 +++++ mp/src/utils/smdlexp/smdlexp.def | 8 + mp/src/utils/smdlexp/smdlexp.mak | 325 ++ mp/src/utils/smdlexp/smdlexp.rc | 147 + mp/src/utils/smdlexp/smedefs.h | 178 + mp/src/utils/smdlexp/smexprc.h | 28 + mp/src/utils/tgadiff/tgadiff-2010.vcxproj | 243 + mp/src/utils/tgadiff/tgadiff-2010.vcxproj.filters | 50 + mp/src/utils/tgadiff/tgadiff.cpp | 184 + mp/src/utils/vbsp/boundbox.cpp | 285 ++ mp/src/utils/vbsp/boundbox.h | 79 + mp/src/utils/vbsp/brushbsp.cpp | 1469 ++++++ mp/src/utils/vbsp/csg.cpp | 784 +++ mp/src/utils/vbsp/csg.h | 32 + mp/src/utils/vbsp/cubemap.cpp | 995 ++++ mp/src/utils/vbsp/detail.cpp | 693 +++ mp/src/utils/vbsp/detail.h | 18 + mp/src/utils/vbsp/detailobjects.cpp | 966 ++++ mp/src/utils/vbsp/disp_ivp.cpp | 359 ++ mp/src/utils/vbsp/disp_ivp.h | 49 + mp/src/utils/vbsp/disp_vbsp.cpp | 675 +++ mp/src/utils/vbsp/disp_vbsp.h | 46 + mp/src/utils/vbsp/faces.cpp | 1810 +++++++ mp/src/utils/vbsp/faces.h | 20 + mp/src/utils/vbsp/glfile.cpp | 219 + mp/src/utils/vbsp/ivp.cpp | 1656 +++++++ mp/src/utils/vbsp/ivp.h | 75 + mp/src/utils/vbsp/leakfile.cpp | 168 + mp/src/utils/vbsp/manifest.cpp | 568 +++ mp/src/utils/vbsp/manifest.h | 73 + mp/src/utils/vbsp/map.cpp | 3304 +++++++++++++ mp/src/utils/vbsp/map.h | 18 + mp/src/utils/vbsp/materialpatch.cpp | 440 ++ mp/src/utils/vbsp/materialpatch.h | 60 + mp/src/utils/vbsp/materialsub.cpp | 90 + mp/src/utils/vbsp/materialsub.h | 25 + mp/src/utils/vbsp/nodraw.cpp | 32 + mp/src/utils/vbsp/normals.cpp | 50 + mp/src/utils/vbsp/notes.txt | 66 + mp/src/utils/vbsp/overlay.cpp | 487 ++ mp/src/utils/vbsp/portals.cpp | 1684 +++++++ mp/src/utils/vbsp/portals.h | 19 + mp/src/utils/vbsp/prtfile.cpp | 374 ++ mp/src/utils/vbsp/staticprop.cpp | 741 +++ mp/src/utils/vbsp/textures.cpp | 737 +++ mp/src/utils/vbsp/tree.cpp | 207 + mp/src/utils/vbsp/vbsp-2010.vcxproj | 371 ++ mp/src/utils/vbsp/vbsp-2010.vcxproj.filters | 446 ++ mp/src/utils/vbsp/vbsp.cpp | 1404 ++++++ mp/src/utils/vbsp/vbsp.h | 657 +++ mp/src/utils/vbsp/worldvertextransitionfixup.cpp | 212 + mp/src/utils/vbsp/worldvertextransitionfixup.h | 15 + mp/src/utils/vbsp/writebsp.cpp | 1552 ++++++ mp/src/utils/vbsp/writebsp.h | 34 + mp/src/utils/vice/vice-2010.vcxproj | 247 + mp/src/utils/vice/vice-2010.vcxproj.filters | 65 + mp/src/utils/vice/vice.cpp | 277 ++ mp/src/utils/vmpi/ichannel.h | 49 + mp/src/utils/vmpi/imysqlwrapper.h | 115 + mp/src/utils/vmpi/iphelpers.h | 162 + mp/src/utils/vmpi/messbuf.h | 52 + mp/src/utils/vmpi/threadhelpers.h | 110 + mp/src/utils/vmpi/vmpi.h | 217 + mp/src/utils/vmpi/vmpi_defs.h | 147 + mp/src/utils/vmpi/vmpi_dispatch.h | 15 + mp/src/utils/vmpi/vmpi_distribute_work.h | 89 + mp/src/utils/vmpi/vmpi_filesystem.h | 53 + mp/src/utils/vmpi/vmpi_parameters.h | 31 + mp/src/utils/vrad/disp_vrad.cpp | 332 ++ mp/src/utils/vrad/disp_vrad.h | 22 + mp/src/utils/vrad/iincremental.h | 71 + mp/src/utils/vrad/imagepacker.cpp | 141 + mp/src/utils/vrad/imagepacker.h | 51 + mp/src/utils/vrad/incremental.cpp | 766 +++ mp/src/utils/vrad/incremental.h | 175 + mp/src/utils/vrad/leaf_ambient_lighting.cpp | 708 +++ mp/src/utils/vrad/leaf_ambient_lighting.h | 17 + mp/src/utils/vrad/lightmap.cpp | 3576 ++++++++++++++ mp/src/utils/vrad/lightmap.h | 141 + mp/src/utils/vrad/macro_texture.cpp | 166 + mp/src/utils/vrad/macro_texture.h | 24 + mp/src/utils/vrad/mpivrad.cpp | 496 ++ mp/src/utils/vrad/mpivrad.h | 36 + mp/src/utils/vrad/notes.txt | 22 + mp/src/utils/vrad/origface.cpp | 51 + mp/src/utils/vrad/radial.cpp | 882 ++++ mp/src/utils/vrad/radial.h | 76 + mp/src/utils/vrad/samplehash.cpp | 230 + mp/src/utils/vrad/trace.cpp | 644 +++ mp/src/utils/vrad/vismat.cpp | 483 ++ mp/src/utils/vrad/vismat.h | 34 + mp/src/utils/vrad/vrad.cpp | 2936 ++++++++++++ mp/src/utils/vrad/vrad.h | 610 +++ mp/src/utils/vrad/vrad_dispcoll.cpp | 1080 +++++ mp/src/utils/vrad/vrad_dispcoll.h | 80 + mp/src/utils/vrad/vrad_dll-2010.vcxproj | 405 ++ mp/src/utils/vrad/vrad_dll-2010.vcxproj.filters | 563 +++ mp/src/utils/vrad/vraddetailprops.cpp | 1034 ++++ mp/src/utils/vrad/vraddetailprops.h | 34 + mp/src/utils/vrad/vraddisps.cpp | 1783 +++++++ mp/src/utils/vrad/vraddll.cpp | 243 + mp/src/utils/vrad/vraddll.h | 34 + mp/src/utils/vrad/vradstaticprops.cpp | 1915 ++++++++ mp/src/utils/vrad_launcher/stdafx.cpp | 15 + mp/src/utils/vrad_launcher/stdafx.h | 32 + .../utils/vrad_launcher/vrad_launcher-2010.vcxproj | 245 + .../vrad_launcher-2010.vcxproj.filters | 53 + mp/src/utils/vrad_launcher/vrad_launcher.cpp | 145 + mp/src/utils/vtf2tga/vtf2tga-2010.vcxproj | 261 + mp/src/utils/vtf2tga/vtf2tga-2010.vcxproj.filters | 107 + mp/src/utils/vtf2tga/vtf2tga.cpp | 316 ++ mp/src/utils/vtfdiff/vtfdiff-2010.vcxproj | 244 + mp/src/utils/vtfdiff/vtfdiff-2010.vcxproj.filters | 53 + mp/src/utils/vtfdiff/vtfdiff.cpp | 438 ++ mp/src/utils/vvis/WaterDist.cpp | 30 + mp/src/utils/vvis/flow.cpp | 881 ++++ mp/src/utils/vvis/mpivis.cpp | 640 +++ mp/src/utils/vvis/mpivis.h | 21 + mp/src/utils/vvis/vis.h | 125 + mp/src/utils/vvis/vvis.cpp | 1240 +++++ mp/src/utils/vvis/vvis_dll-2010.vcxproj | 291 ++ mp/src/utils/vvis/vvis_dll-2010.vcxproj.filters | 218 + mp/src/utils/vvis_launcher/StdAfx.cpp | 15 + mp/src/utils/vvis_launcher/StdAfx.h | 31 + .../utils/vvis_launcher/vvis_launcher-2010.vcxproj | 248 + .../vvis_launcher-2010.vcxproj.filters | 53 + mp/src/utils/vvis_launcher/vvis_launcher.cpp | 79 + 216 files changed, 82164 insertions(+) create mode 100644 mp/src/utils/captioncompiler/captioncompiler-2010.vcxproj create mode 100644 mp/src/utils/captioncompiler/captioncompiler-2010.vcxproj.filters create mode 100644 mp/src/utils/captioncompiler/captioncompiler.cpp create mode 100644 mp/src/utils/captioncompiler/cbase.h create mode 100644 mp/src/utils/common/ISQLDBReplyTarget.h create mode 100644 mp/src/utils/common/MySqlDatabase.cpp create mode 100644 mp/src/utils/common/MySqlDatabase.h create mode 100644 mp/src/utils/common/bsplib.cpp create mode 100644 mp/src/utils/common/bsplib.h create mode 100644 mp/src/utils/common/cmdlib.cpp create mode 100644 mp/src/utils/common/cmdlib.h create mode 100644 mp/src/utils/common/consolewnd.cpp create mode 100644 mp/src/utils/common/consolewnd.h create mode 100644 mp/src/utils/common/filesystem_tools.cpp create mode 100644 mp/src/utils/common/filesystem_tools.h create mode 100644 mp/src/utils/common/map_shared.cpp create mode 100644 mp/src/utils/common/map_shared.h create mode 100644 mp/src/utils/common/movie.h create mode 100644 mp/src/utils/common/mpi_stats.cpp create mode 100644 mp/src/utils/common/mpi_stats.h create mode 100644 mp/src/utils/common/mstristrip.cpp create mode 100644 mp/src/utils/common/mstristrip.h create mode 100644 mp/src/utils/common/pacifier.cpp create mode 100644 mp/src/utils/common/pacifier.h create mode 100644 mp/src/utils/common/physdll.cpp create mode 100644 mp/src/utils/common/physdll.h create mode 100644 mp/src/utils/common/polylib.cpp create mode 100644 mp/src/utils/common/polylib.h create mode 100644 mp/src/utils/common/qfiles.h create mode 100644 mp/src/utils/common/scratchpad_helpers.cpp create mode 100644 mp/src/utils/common/scratchpad_helpers.h create mode 100644 mp/src/utils/common/scriplib.cpp create mode 100644 mp/src/utils/common/scriplib.h create mode 100644 mp/src/utils/common/threads.cpp create mode 100644 mp/src/utils/common/threads.h create mode 100644 mp/src/utils/common/tools_minidump.cpp create mode 100644 mp/src/utils/common/tools_minidump.h create mode 100644 mp/src/utils/common/utilmatlib.cpp create mode 100644 mp/src/utils/common/utilmatlib.h create mode 100644 mp/src/utils/common/vmpi_tools_shared.cpp create mode 100644 mp/src/utils/common/vmpi_tools_shared.h create mode 100644 mp/src/utils/common/wadlib.c create mode 100644 mp/src/utils/common/wadlib.h create mode 100644 mp/src/utils/glview/glos.h create mode 100644 mp/src/utils/glview/glview-2010.vcxproj create mode 100644 mp/src/utils/glview/glview-2010.vcxproj.filters create mode 100644 mp/src/utils/glview/glview.cpp create mode 100644 mp/src/utils/height2normal/height2normal-2010.vcxproj create mode 100644 mp/src/utils/height2normal/height2normal-2010.vcxproj.filters create mode 100644 mp/src/utils/height2normal/height2normal.cpp create mode 100644 mp/src/utils/motionmapper/motionmapper-2010.vcxproj create mode 100644 mp/src/utils/motionmapper/motionmapper-2010.vcxproj.filters create mode 100644 mp/src/utils/motionmapper/motionmapper.cpp create mode 100644 mp/src/utils/motionmapper/motionmapper.h create mode 100644 mp/src/utils/nvtristriplib/nvtristrip.h create mode 100644 mp/src/utils/phonemeextractor/extractor_utils.cpp create mode 100644 mp/src/utils/phonemeextractor/phonemeextractor-2010.vcxproj create mode 100644 mp/src/utils/phonemeextractor/phonemeextractor-2010.vcxproj.filters create mode 100644 mp/src/utils/phonemeextractor/phonemeextractor.cpp create mode 100644 mp/src/utils/phonemeextractor/phonemeextractor_ims.cpp create mode 100644 mp/src/utils/phonemeextractor/talkback.doc create mode 100644 mp/src/utils/phonemeextractor/talkback.h create mode 100644 mp/src/utils/qc_eyes/QC_Eyes.cpp create mode 100644 mp/src/utils/qc_eyes/QC_Eyes.h create mode 100644 mp/src/utils/qc_eyes/QC_Eyes.rc create mode 100644 mp/src/utils/qc_eyes/QC_EyesDlg.cpp create mode 100644 mp/src/utils/qc_eyes/QC_EyesDlg.h create mode 100644 mp/src/utils/qc_eyes/StdAfx.cpp create mode 100644 mp/src/utils/qc_eyes/StdAfx.h create mode 100644 mp/src/utils/qc_eyes/qc_eyes-2010.vcxproj create mode 100644 mp/src/utils/qc_eyes/qc_eyes-2010.vcxproj.filters create mode 100644 mp/src/utils/qc_eyes/res/QC_Eyes.ico create mode 100644 mp/src/utils/qc_eyes/res/QC_Eyes.rc2 create mode 100644 mp/src/utils/qc_eyes/res/eye_XY_L.bmp create mode 100644 mp/src/utils/qc_eyes/res/eye_XY_R.bmp create mode 100644 mp/src/utils/qc_eyes/res/eye_Z_L.bmp create mode 100644 mp/src/utils/qc_eyes/res/eye_Z_R.bmp create mode 100644 mp/src/utils/qc_eyes/res/eye_default.bmp create mode 100644 mp/src/utils/qc_eyes/res/eye_lower_hi.bmp create mode 100644 mp/src/utils/qc_eyes/res/eye_lower_lo.bmp create mode 100644 mp/src/utils/qc_eyes/res/eye_lower_mid.bmp create mode 100644 mp/src/utils/qc_eyes/res/eye_upper_hi.bmp create mode 100644 mp/src/utils/qc_eyes/res/eye_upper_lo.bmp create mode 100644 mp/src/utils/qc_eyes/res/eye_upper_mid.bmp create mode 100644 mp/src/utils/qc_eyes/resource.h create mode 100644 mp/src/utils/serverplugin_sample/serverplugin_bot.cpp create mode 100644 mp/src/utils/serverplugin_sample/serverplugin_empty-2010.vcxproj create mode 100644 mp/src/utils/serverplugin_sample/serverplugin_empty-2010.vcxproj.filters create mode 100644 mp/src/utils/serverplugin_sample/serverplugin_empty.cpp create mode 100644 mp/src/utils/smdlexp/smdlexp.cpp create mode 100644 mp/src/utils/smdlexp/smdlexp.def create mode 100644 mp/src/utils/smdlexp/smdlexp.mak create mode 100644 mp/src/utils/smdlexp/smdlexp.rc create mode 100644 mp/src/utils/smdlexp/smedefs.h create mode 100644 mp/src/utils/smdlexp/smexprc.h create mode 100644 mp/src/utils/tgadiff/tgadiff-2010.vcxproj create mode 100644 mp/src/utils/tgadiff/tgadiff-2010.vcxproj.filters create mode 100644 mp/src/utils/tgadiff/tgadiff.cpp create mode 100644 mp/src/utils/vbsp/boundbox.cpp create mode 100644 mp/src/utils/vbsp/boundbox.h create mode 100644 mp/src/utils/vbsp/brushbsp.cpp create mode 100644 mp/src/utils/vbsp/csg.cpp create mode 100644 mp/src/utils/vbsp/csg.h create mode 100644 mp/src/utils/vbsp/cubemap.cpp create mode 100644 mp/src/utils/vbsp/detail.cpp create mode 100644 mp/src/utils/vbsp/detail.h create mode 100644 mp/src/utils/vbsp/detailobjects.cpp create mode 100644 mp/src/utils/vbsp/disp_ivp.cpp create mode 100644 mp/src/utils/vbsp/disp_ivp.h create mode 100644 mp/src/utils/vbsp/disp_vbsp.cpp create mode 100644 mp/src/utils/vbsp/disp_vbsp.h create mode 100644 mp/src/utils/vbsp/faces.cpp create mode 100644 mp/src/utils/vbsp/faces.h create mode 100644 mp/src/utils/vbsp/glfile.cpp create mode 100644 mp/src/utils/vbsp/ivp.cpp create mode 100644 mp/src/utils/vbsp/ivp.h create mode 100644 mp/src/utils/vbsp/leakfile.cpp create mode 100644 mp/src/utils/vbsp/manifest.cpp create mode 100644 mp/src/utils/vbsp/manifest.h create mode 100644 mp/src/utils/vbsp/map.cpp create mode 100644 mp/src/utils/vbsp/map.h create mode 100644 mp/src/utils/vbsp/materialpatch.cpp create mode 100644 mp/src/utils/vbsp/materialpatch.h create mode 100644 mp/src/utils/vbsp/materialsub.cpp create mode 100644 mp/src/utils/vbsp/materialsub.h create mode 100644 mp/src/utils/vbsp/nodraw.cpp create mode 100644 mp/src/utils/vbsp/normals.cpp create mode 100644 mp/src/utils/vbsp/notes.txt create mode 100644 mp/src/utils/vbsp/overlay.cpp create mode 100644 mp/src/utils/vbsp/portals.cpp create mode 100644 mp/src/utils/vbsp/portals.h create mode 100644 mp/src/utils/vbsp/prtfile.cpp create mode 100644 mp/src/utils/vbsp/staticprop.cpp create mode 100644 mp/src/utils/vbsp/textures.cpp create mode 100644 mp/src/utils/vbsp/tree.cpp create mode 100644 mp/src/utils/vbsp/vbsp-2010.vcxproj create mode 100644 mp/src/utils/vbsp/vbsp-2010.vcxproj.filters create mode 100644 mp/src/utils/vbsp/vbsp.cpp create mode 100644 mp/src/utils/vbsp/vbsp.h create mode 100644 mp/src/utils/vbsp/worldvertextransitionfixup.cpp create mode 100644 mp/src/utils/vbsp/worldvertextransitionfixup.h create mode 100644 mp/src/utils/vbsp/writebsp.cpp create mode 100644 mp/src/utils/vbsp/writebsp.h create mode 100644 mp/src/utils/vice/vice-2010.vcxproj create mode 100644 mp/src/utils/vice/vice-2010.vcxproj.filters create mode 100644 mp/src/utils/vice/vice.cpp create mode 100644 mp/src/utils/vmpi/ichannel.h create mode 100644 mp/src/utils/vmpi/imysqlwrapper.h create mode 100644 mp/src/utils/vmpi/iphelpers.h create mode 100644 mp/src/utils/vmpi/messbuf.h create mode 100644 mp/src/utils/vmpi/threadhelpers.h create mode 100644 mp/src/utils/vmpi/vmpi.h create mode 100644 mp/src/utils/vmpi/vmpi_defs.h create mode 100644 mp/src/utils/vmpi/vmpi_dispatch.h create mode 100644 mp/src/utils/vmpi/vmpi_distribute_work.h create mode 100644 mp/src/utils/vmpi/vmpi_filesystem.h create mode 100644 mp/src/utils/vmpi/vmpi_parameters.h create mode 100644 mp/src/utils/vrad/disp_vrad.cpp create mode 100644 mp/src/utils/vrad/disp_vrad.h create mode 100644 mp/src/utils/vrad/iincremental.h create mode 100644 mp/src/utils/vrad/imagepacker.cpp create mode 100644 mp/src/utils/vrad/imagepacker.h create mode 100644 mp/src/utils/vrad/incremental.cpp create mode 100644 mp/src/utils/vrad/incremental.h create mode 100644 mp/src/utils/vrad/leaf_ambient_lighting.cpp create mode 100644 mp/src/utils/vrad/leaf_ambient_lighting.h create mode 100644 mp/src/utils/vrad/lightmap.cpp create mode 100644 mp/src/utils/vrad/lightmap.h create mode 100644 mp/src/utils/vrad/macro_texture.cpp create mode 100644 mp/src/utils/vrad/macro_texture.h create mode 100644 mp/src/utils/vrad/mpivrad.cpp create mode 100644 mp/src/utils/vrad/mpivrad.h create mode 100644 mp/src/utils/vrad/notes.txt create mode 100644 mp/src/utils/vrad/origface.cpp create mode 100644 mp/src/utils/vrad/radial.cpp create mode 100644 mp/src/utils/vrad/radial.h create mode 100644 mp/src/utils/vrad/samplehash.cpp create mode 100644 mp/src/utils/vrad/trace.cpp create mode 100644 mp/src/utils/vrad/vismat.cpp create mode 100644 mp/src/utils/vrad/vismat.h create mode 100644 mp/src/utils/vrad/vrad.cpp create mode 100644 mp/src/utils/vrad/vrad.h create mode 100644 mp/src/utils/vrad/vrad_dispcoll.cpp create mode 100644 mp/src/utils/vrad/vrad_dispcoll.h create mode 100644 mp/src/utils/vrad/vrad_dll-2010.vcxproj create mode 100644 mp/src/utils/vrad/vrad_dll-2010.vcxproj.filters create mode 100644 mp/src/utils/vrad/vraddetailprops.cpp create mode 100644 mp/src/utils/vrad/vraddetailprops.h create mode 100644 mp/src/utils/vrad/vraddisps.cpp create mode 100644 mp/src/utils/vrad/vraddll.cpp create mode 100644 mp/src/utils/vrad/vraddll.h create mode 100644 mp/src/utils/vrad/vradstaticprops.cpp create mode 100644 mp/src/utils/vrad_launcher/stdafx.cpp create mode 100644 mp/src/utils/vrad_launcher/stdafx.h create mode 100644 mp/src/utils/vrad_launcher/vrad_launcher-2010.vcxproj create mode 100644 mp/src/utils/vrad_launcher/vrad_launcher-2010.vcxproj.filters create mode 100644 mp/src/utils/vrad_launcher/vrad_launcher.cpp create mode 100644 mp/src/utils/vtf2tga/vtf2tga-2010.vcxproj create mode 100644 mp/src/utils/vtf2tga/vtf2tga-2010.vcxproj.filters create mode 100644 mp/src/utils/vtf2tga/vtf2tga.cpp create mode 100644 mp/src/utils/vtfdiff/vtfdiff-2010.vcxproj create mode 100644 mp/src/utils/vtfdiff/vtfdiff-2010.vcxproj.filters create mode 100644 mp/src/utils/vtfdiff/vtfdiff.cpp create mode 100644 mp/src/utils/vvis/WaterDist.cpp create mode 100644 mp/src/utils/vvis/flow.cpp create mode 100644 mp/src/utils/vvis/mpivis.cpp create mode 100644 mp/src/utils/vvis/mpivis.h create mode 100644 mp/src/utils/vvis/vis.h create mode 100644 mp/src/utils/vvis/vvis.cpp create mode 100644 mp/src/utils/vvis/vvis_dll-2010.vcxproj create mode 100644 mp/src/utils/vvis/vvis_dll-2010.vcxproj.filters create mode 100644 mp/src/utils/vvis_launcher/StdAfx.cpp create mode 100644 mp/src/utils/vvis_launcher/StdAfx.h create mode 100644 mp/src/utils/vvis_launcher/vvis_launcher-2010.vcxproj create mode 100644 mp/src/utils/vvis_launcher/vvis_launcher-2010.vcxproj.filters create mode 100644 mp/src/utils/vvis_launcher/vvis_launcher.cpp (limited to 'mp/src/utils') diff --git a/mp/src/utils/captioncompiler/captioncompiler-2010.vcxproj b/mp/src/utils/captioncompiler/captioncompiler-2010.vcxproj new file mode 100644 index 00000000..47767523 --- /dev/null +++ b/mp/src/utils/captioncompiler/captioncompiler-2010.vcxproj @@ -0,0 +1,261 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + Captioncompiler + {E85D01E5-DA1B-00A2-5D72-A9B6DEA9A995} + + + + Application + MultiByte + captioncompiler + + + Application + MultiByte + captioncompiler + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30319.1 + .\Debug\win32\ + .\Debug\win32\ + ..\..\devtools\vstools;$(ExecutablePath);$(Path) + true + true + true + true + true + .\Release\win32\ + .\Release\win32\ + ..\..\devtools\vstools;$(ExecutablePath);$(Path) + true + true + false + true + true + + + + if EXIST ..\..\..\game\bin\$(TargetFileName) for /f "delims=" %%A in ('attrib "..\..\..\game\bin\$(TargetFileName)"') do set valveTmpIsReadOnly="%%A" set valveTmpIsReadOnlyLetter=%valveTmpIsReadOnly:~6,1% if "%valveTmpIsReadOnlyLetter%"=="R" del /q "$(TargetDir)"$(TargetFileName) if exist ..\..\devtools\bin\vpc.exe ..\..\devtools\bin\vpc.exe -crc2 captioncompiler.vcxproj if ERRORLEVEL 1 exit 1 + + + /MP + Disabled + ..\..\common;..\..\public;..\..\public\tier0;..\..\public\tier1;..\common;..\..\game\shared;.\ + _HAS_ITERATOR_DEBUGGING=0;WIN32;_WIN32;_DEBUG;DEBUG;_WINDOWS;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;RAD_TELEMETRY_DISABLED;COMPILER_MSVC32;captioncompiler;VPCGAMECAPS=VALVE;PROJECTDIR=D:\dev\games\rel\hl2\src\utils\captioncompiler;_DLL_EXT=.dll;VPCGAME=valve + true + false + Default + MultiThreadedDebug + true + StreamingSIMDExtensions + Fast + true + true + true + false + NotUsing + false + NoListing + $(IntDir)/ + $(IntDir)/ + $(IntDir)/ + false + Level4 + true + EditAndContinue + CompileAsCpp + $(IntDir)/ + Prompt + + + _DEBUG;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE + 1033 + + + + + /NXCOMPAT /ignore:4221 + %(AdditionalDependencies) + NotSet + $(OutDir)\captioncompiler.exe + true + libc;libcd;libcmt + true + $(IntDir)/$(TargetName).pdb + false + $(IntDir)/$(TargetName).map + Console + + MachineX86 + PromptImmediately + false + + + true + + + true + + + true + $(OutDir)/captioncompiler.bsc + + + Publishing to ..\..\..\game\bin + if not exist "..\..\..\game\bin" mkdir "..\..\..\game\bin" copy "$(TargetDir)"$(TargetFileName) ..\..\..\game\bin\$(TargetFileName) if ERRORLEVEL 1 goto BuildEventFailed if exist "$(TargetDir)"$(TargetName).map copy "$(TargetDir)"$(TargetName).map ..\..\..\game\bin\$(TargetName).map copy "$(TargetDir)"$(TargetName).pdb ..\..\..\game\bin\$(TargetName).pdb if ERRORLEVEL 1 goto BuildEventFailed goto BuildEventOK :BuildEventFailed echo *** ERROR! PostBuildStep FAILED for $(ProjectName)! EXE or DLL is probably running. *** del /q "$(TargetDir)"$(TargetFileName) exit 1 :BuildEventOK + + + + + + + if EXIST ..\..\..\game\bin\$(TargetFileName) for /f "delims=" %%A in ('attrib "..\..\..\game\bin\$(TargetFileName)"') do set valveTmpIsReadOnly="%%A" set valveTmpIsReadOnlyLetter=%valveTmpIsReadOnly:~6,1% if "%valveTmpIsReadOnlyLetter%"=="R" del /q "$(TargetDir)"$(TargetFileName) if exist ..\..\devtools\bin\vpc.exe ..\..\devtools\bin\vpc.exe -crc2 captioncompiler.vcxproj if ERRORLEVEL 1 exit 1 + + + /MP /d2Zi+ + MaxSpeed + AnySuitable + true + Speed + ..\..\common;..\..\public;..\..\public\tier0;..\..\public\tier1;..\common;..\..\game\shared;.\ + WIN32;_WIN32;NDEBUG;_WINDOWS;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;RAD_TELEMETRY_DISABLED;COMPILER_MSVC32;captioncompiler;VPCGAMECAPS=VALVE;PROJECTDIR=D:\dev\games\rel\hl2\src\utils\captioncompiler;_DLL_EXT=.dll;VPCGAME=valve + true + false + MultiThreaded + false + true + StreamingSIMDExtensions + Fast + true + true + true + false + NotUsing + false + NoListing + $(IntDir)/ + $(IntDir)/ + $(IntDir)/ + false + Level4 + true + ProgramDatabase + CompileAsCpp + $(IntDir)/ + Prompt + + + NDEBUG;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE + 1033 + + + + + /DYNAMICBASE /NXCOMPAT /ignore:4221 + %(AdditionalDependencies) + NotSet + $(OutDir)\captioncompiler.exe + true + libc;libcd;libcmtd + true + $(IntDir)/$(TargetName).pdb + false + $(IntDir)/$(TargetName).map + Console + true + true + + MachineX86 + PromptImmediately + + + true + + + true + + + true + $(OutDir)/captioncompiler.bsc + + + Publishing to ..\..\..\game\bin + if not exist "..\..\..\game\bin" mkdir "..\..\..\game\bin" copy "$(TargetDir)"$(TargetFileName) ..\..\..\game\bin\$(TargetFileName) if ERRORLEVEL 1 goto BuildEventFailed if exist "$(TargetDir)"$(TargetName).map copy "$(TargetDir)"$(TargetName).map ..\..\..\game\bin\$(TargetName).map copy "$(TargetDir)"$(TargetName).pdb ..\..\..\game\bin\$(TargetName).pdb if ERRORLEVEL 1 goto BuildEventFailed goto BuildEventOK :BuildEventFailed echo *** ERROR! PostBuildStep FAILED for $(ProjectName)! EXE or DLL is probably running. *** del /q "$(TargetDir)"$(TargetFileName) exit 1 :BuildEventOK + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + NotUsing + NotUsing + + + + + + + Compiling pointeroverride.asm + "$(VCInstallDir)bin\ml.exe" /c /Cp /Zi /Fo"$(IntDir)\%(Filename).obj" "%(FullPath)" + $(IntDir)\%(Filename).obj + Compiling pointeroverride.asm + "$(VCInstallDir)bin\ml.exe" /c /Cp /Zi /Fo"$(IntDir)\%(Filename).obj" "%(FullPath)" + $(IntDir)\%(Filename).obj + + + + + + + + diff --git a/mp/src/utils/captioncompiler/captioncompiler-2010.vcxproj.filters b/mp/src/utils/captioncompiler/captioncompiler-2010.vcxproj.filters new file mode 100644 index 00000000..3e16f2b4 --- /dev/null +++ b/mp/src/utils/captioncompiler/captioncompiler-2010.vcxproj.filters @@ -0,0 +1,110 @@ + + + + + {1680C80B-FF1E-EA4D-9817-CC12254F2E40} + + + {C5D73B3A-C648-896C-B7CE-F174808E5BA5} + + + {C0A65B3F-3B05-094C-44FA-B1624BAD7BAC} + + + {BA03E055-4FA2-FCE3-8A1C-D348547D379C} + + + + + Link Libraries + + + Link Libraries + + + Link Libraries + + + Link Libraries + + + Link Libraries + + + Link Libraries + + + Link Libraries + + + + + Header Files + + + Header Files + + + Shared Code + + + Shared Code + + + Shared Code + + + Shared Code + + + Shared Code + + + Shared Code + + + Shared Code + + + + + Shared Code + + + Shared Code + + + Shared Code + + + Shared Code + + + Shared Code + + + Shared Code + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + + + Source Files + + + + + diff --git a/mp/src/utils/captioncompiler/captioncompiler.cpp b/mp/src/utils/captioncompiler/captioncompiler.cpp new file mode 100644 index 00000000..44e1578f --- /dev/null +++ b/mp/src/utils/captioncompiler/captioncompiler.cpp @@ -0,0 +1,588 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: vcd_sound_check.cpp : Defines the entry point for the console application. +// +//===========================================================================// +#include +#include +#include "tier0/dbg.h" +#include "tier1/utldict.h" +#include "filesystem.h" +#include "cmdlib.h" +#include "scriplib.h" +#include "vstdlib/random.h" +#include "tier1/UtlBuffer.h" +#include "pacifier.h" +#include "appframework/tier3app.h" +#include "tier0/icommandline.h" +#include "vgui/IVGui.h" +#include "vgui_controls/controls.h" +#include "vgui/ILocalize.h" +#include "tier1/checksum_crc.h" +#include "tier1/UtlSortVector.h" +#include "tier1/utlmap.h" +#include "captioncompiler.h" + +#include "tier0/fasttimer.h" + +using namespace vgui; + +// #define TESTING 1 + + +bool uselogfile = false; +bool bX360 = false; + +struct AnalysisData +{ + CUtlSymbolTable symbols; +}; + +static AnalysisData g_Analysis; + +IBaseFileSystem *filesystem = NULL; + +static bool spewed = false; + +SpewRetval_t SpewFunc( SpewType_t type, char const *pMsg ) +{ + spewed = true; + + printf( "%s", pMsg ); + OutputDebugString( pMsg ); + + if ( type == SPEW_ERROR ) + { + printf( "\n" ); + OutputDebugString( "\n" ); + } + + return SPEW_CONTINUE; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : depth - +// *fmt - +// ... - +//----------------------------------------------------------------------------- +void vprint( int depth, const char *fmt, ... ) +{ + char string[ 8192 ]; + va_list va; + va_start( va, fmt ); + vsprintf( string, fmt, va ); + va_end( va ); + + FILE *fp = NULL; + + if ( uselogfile ) + { + fp = fopen( "log.txt", "ab" ); + } + + while ( depth-- > 0 ) + { + printf( " " ); + OutputDebugString( " " ); + if ( fp ) + { + fprintf( fp, " " ); + } + } + + ::printf( string ); + OutputDebugString( string ); + + if ( fp ) + { + char *p = string; + while ( *p ) + { + if ( *p == '\n' ) + { + fputc( '\r', fp ); + } + fputc( *p, fp ); + p++; + } + fclose( fp ); + } +} + +void logprint( char const *logfile, const char *fmt, ... ) +{ + char string[ 8192 ]; + va_list va; + va_start( va, fmt ); + vsprintf( string, fmt, va ); + va_end( va ); + + FILE *fp = NULL; + static bool first = true; + if ( first ) + { + first = false; + fp = fopen( logfile, "wb" ); + } + else + { + fp = fopen( logfile, "ab" ); + } + if ( fp ) + { + char *p = string; + while ( *p ) + { + if ( *p == '\n' ) + { + fputc( '\r', fp ); + } + fputc( *p, fp ); + p++; + } + fclose( fp ); + } +} + + +void Con_Printf( const char *fmt, ... ) +{ + va_list args; + static char output[1024]; + + va_start( args, fmt ); + vprintf( fmt, args ); + vsprintf( output, fmt, args ); + + vprint( 0, output ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void printusage( void ) +{ + vprint( 0, "usage: captioncompiler closecaptionfile.txt\n\ + \t-v = verbose output\n\ + \t-l = log to file log.txt\n\ + \ne.g.: kvc -l u:/xbox/game/hl2x/resource/closecaption_english.txt" ); + + // Exit app + exit( 1 ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CheckLogFile( void ) +{ + if ( uselogfile ) + { + _unlink( "log.txt" ); + vprint( 0, " Outputting to log.txt\n" ); + } +} + +void PrintHeader() +{ + vprint( 0, "Valve Software - captioncompiler.exe (%s)\n", __DATE__ ); + vprint( 0, "--- Close Caption File compiler ---\n" ); +} + +//----------------------------------------------------------------------------- +// The application object +//----------------------------------------------------------------------------- +class CCompileCaptionsApp : public CTier3SteamApp +{ + typedef CTier3SteamApp BaseClass; + +public: + // Methods of IApplication + virtual bool Create(); + virtual bool PreInit(); + virtual int Main(); + virtual void PostShutdown(); + virtual void Destroy(); + +private: + // Sets up the search paths + bool SetupSearchPaths(); + + void CompileCaptionFile( char const *infile, char const *outfile ); + void DescribeCaptions( char const *file ); +}; + + +bool CCompileCaptionsApp::Create() +{ + SpewOutputFunc( SpewFunc ); + SpewActivate( "kvc", 2 ); + + AppSystemInfo_t appSystems[] = + { + { "vgui2.dll", VGUI_IVGUI_INTERFACE_VERSION }, + { "", "" } // Required to terminate the list + }; + + return AddSystems( appSystems ); +} + +void CCompileCaptionsApp::Destroy() +{ +} + + +//----------------------------------------------------------------------------- +// Sets up the game path +//----------------------------------------------------------------------------- +bool CCompileCaptionsApp::SetupSearchPaths() +{ + if ( !BaseClass::SetupSearchPaths( NULL, false, true ) ) + return false; + + // Set gamedir. + Q_MakeAbsolutePath( gamedir, sizeof( gamedir ), GetGameInfoPath() ); + Q_AppendSlash( gamedir, sizeof( gamedir ) ); + + return true; +} + + +//----------------------------------------------------------------------------- +// Init, shutdown +//----------------------------------------------------------------------------- +bool CCompileCaptionsApp::PreInit( ) +{ + if ( !BaseClass::PreInit() ) + return false; + + g_pFileSystem = g_pFullFileSystem; + if ( !g_pFileSystem || !g_pVGui || !g_pVGuiLocalize ) + { + Error( "Unable to load required library interface!\n" ); + return false; + } + +// MathLib_Init( 2.2f, 2.2f, 0.0f, 2.0f, false, false, false, false ); + g_pFullFileSystem->SetWarningFunc( Warning ); + + // Add paths... + if ( !SetupSearchPaths() ) + return false; + + return true; +} + +void CCompileCaptionsApp::PostShutdown() +{ + g_pFileSystem = NULL; + BaseClass::PostShutdown(); +} + +void CCompileCaptionsApp::CompileCaptionFile( char const *infile, char const *outfile ) +{ + StringIndex_t maxindex = (StringIndex_t)-1; + int maxunicodesize = 0; + int totalsize = 0; + + int c = 0; + + int curblock = 0; + int usedBytes = 0; + int blockSize = MAX_BLOCK_SIZE; + + int freeSpace = 0; + + CUtlVector< CaptionLookup_t > directory; + CUtlBuffer data; + + CUtlRBTree< unsigned int > hashcollision( 0, 0, DefLessFunc( unsigned int ) ); + + for ( StringIndex_t i = g_pVGuiLocalize->GetFirstStringIndex(); i != INVALID_LOCALIZE_STRING_INDEX; i = g_pVGuiLocalize->GetNextStringIndex( i ), ++c ) + { + char const *entryName = g_pVGuiLocalize->GetNameByIndex( i ); + CaptionLookup_t entry; + entry.SetHash( entryName ); + + // vprint( 0, "%d / %d: %s == %u\n", c, i, g_pVGuiLocalize->GetNameByIndex( i ), entry.hash ); + + if ( hashcollision.Find( entry.hash ) != hashcollision.InvalidIndex() ) + { + Error( "Hash name collision on %s!!!\n", g_pVGuiLocalize->GetNameByIndex( i ) ); + } + + hashcollision.Insert( entry.hash ); + + const wchar_t *text = g_pVGuiLocalize->GetValueByIndex( i ); + if ( verbose ) + { + vprint( 0, "Processing: '%30.30s' = '%S'\n", entryName, text ); + } + int len = text ? ( wcslen( text ) + 1 ) * sizeof( short ) : 0; + if ( len > maxunicodesize ) + { + maxindex = i; + maxunicodesize = len; + } + + if ( len > blockSize ) + { + Error( "Caption text file '%s' contains a single caption '%s' of %d bytes (%d is max), change MAX_BLOCK_SIZE in captioncompiler.h to fix!!!\n", g_pVGuiLocalize->GetNameByIndex( i ), + entryName, len, blockSize ); + } + totalsize += len; + + if ( usedBytes + len >= blockSize ) + { + ++curblock; + + int leftover = ( blockSize - usedBytes ); + + totalsize += leftover; + + freeSpace += leftover; + + while ( --leftover >= 0 ) + { + data.PutChar( 0 ); + } + + usedBytes = len; + entry.offset = 0; + + data.Put( (const void *)text, len ); + } + else + { + entry.offset = usedBytes; + usedBytes += len; + data.Put( (const void *)text, len ); + } + + entry.length = len; + entry.blockNum = curblock; + + directory.AddToTail( entry ); + } + + int leftover = ( blockSize - usedBytes ); + totalsize += leftover; + freeSpace += leftover; + while ( --leftover >= 0 ) + { + data.PutChar( 0 ); + } + + vprint( 0, "Found %i strings in '%s'\n", c, infile ); + + if ( maxindex != INVALID_LOCALIZE_STRING_INDEX ) + { + vprint( 0, "Longest string '%s' = (%i) bytes average(%.3f)\n%", + g_pVGuiLocalize->GetNameByIndex( maxindex ), maxunicodesize, (float)totalsize/(float)c ); + } + + vprint( 0, "%d blocks (%d bytes each), %d bytes wasted (%.3f per block average), total bytes %d\n", + curblock + 1, blockSize, freeSpace, (float)freeSpace/(float)( curblock + 1 ), totalsize ); + + vprint( 0, "directory size %d entries, %d bytes, data size %d bytes\n", + directory.Count(), directory.Count() * sizeof( CaptionLookup_t ), data.TellPut() ); + + CompiledCaptionHeader_t header; + header.magic = COMPILED_CAPTION_FILEID; + header.version = COMPILED_CAPTION_VERSION; + header.numblocks = curblock + 1; + header.blocksize = blockSize; + header.directorysize = directory.Count(); + header.dataoffset = 0; + + // Now write the outfile + CUtlBuffer out; + out.Put( &header, sizeof( header ) ); + out.Put( directory.Base(), directory.Count() * sizeof( CaptionLookup_t ) ); + int curOffset = out.TellPut(); + // Round it up to the next 512 byte boundary + int nBytesDestBuffer = AlignValue( curOffset, 512 ); // align to HD sector + int nPadding = nBytesDestBuffer - curOffset; + while ( --nPadding >= 0 ) + { + out.PutChar( 0 ); + } + out.Put( data.Base(), data.TellPut() ); + + // Write out a corrected header + header.dataoffset = nBytesDestBuffer; + int savePos = out.TellPut(); + out.SeekPut( CUtlBuffer::SEEK_HEAD, 0 ); + out.Put( &header, sizeof( header ) ); + out.SeekPut( CUtlBuffer::SEEK_HEAD, savePos ); + + g_pFullFileSystem->WriteFile( outfile, NULL, out ); + + // Jeep: this function no longer exisits + /*if ( bX360 ) + { + UpdateOrCreateCaptionFile_X360( g_pFullFileSystem, outfile, NULL, true ); + }*/ +} + +void CCompileCaptionsApp::DescribeCaptions( char const *file ) +{ + CUtlBuffer buf; + if ( !g_pFullFileSystem->ReadFile( file, NULL, buf ) ) + { + Error( "Unable to read '%s' into buffer\n", file ); + } + + CompiledCaptionHeader_t header; + buf.Get( &header, sizeof( header ) ); + if ( header.magic != COMPILED_CAPTION_FILEID ) + Error( "Invalid file id for %s\n", file ); + if ( header.version != COMPILED_CAPTION_VERSION ) + Error( "Invalid file version for %s\n", file ); + + // Read the directory + CUtlSortVector< CaptionLookup_t, CCaptionLookupLess > directory; + directory.EnsureCapacity( header.directorysize ); + directory.CopyArray( (const CaptionLookup_t *)buf.PeekGet(), header.directorysize ); + directory.RedoSort( true ); + buf.SeekGet( CUtlBuffer::SEEK_HEAD, header.dataoffset ); + + int i; + CUtlVector< CaptionBlock_t > blocks; + for ( i = 0; i < header.numblocks; ++i ) + { + CaptionBlock_t& newBlock = blocks[ blocks.AddToTail() ]; + Q_memset( newBlock.data, 0, sizeof( newBlock.data ) ); + buf.Get( newBlock.data, header.blocksize ); + } + + CUtlMap< unsigned int, StringIndex_t > inverseMap( 0, 0, DefLessFunc( unsigned int ) ); + for ( StringIndex_t idx = g_pVGuiLocalize->GetFirstStringIndex(); idx != INVALID_LOCALIZE_STRING_INDEX; idx = g_pVGuiLocalize->GetNextStringIndex( idx ) ) + { + const char *name = g_pVGuiLocalize->GetNameByIndex( idx ); + CaptionLookup_t dummy; + dummy.SetHash( name ); + + inverseMap.Insert( dummy.hash, idx ); + } + + // Now print everything out... + for ( i = 0; i < header.directorysize; ++i ) + { + const CaptionLookup_t& entry = directory[ i ]; + char const *name = g_pVGuiLocalize->GetNameByIndex( inverseMap.Element( inverseMap.Find( entry.hash ) ) ); + const CaptionBlock_t& block = blocks[ entry.blockNum ]; + const wchar_t *data = (const wchar_t *)&block.data[ entry.offset ]; + wchar_t *temp = ( wchar_t * )_alloca( entry.length * sizeof( short ) ); + wcsncpy( temp, data, ( entry.length / sizeof( short ) ) - 1 ); + + vprint( 0, "%3.3d: (%40.40s) hash(%15.15u), block(%4.4d), offset(%4.4d), len(%4.4d) %S\n", + i, name, entry.hash, entry.blockNum, entry.offset, entry.length, temp ); + } +} + +//----------------------------------------------------------------------------- +// main application +//----------------------------------------------------------------------------- +int CCompileCaptionsApp::Main() +{ + CUtlVector< CUtlSymbol > worklist; + + int i = 1; + for ( i ; iParmCount() ; i++) + { + if ( CommandLine()->GetParm( i )[ 0 ] == '-' ) + { + switch( CommandLine()->GetParm( i )[ 1 ] ) + { + case 'l': + uselogfile = true; + break; + case 'v': + verbose = true; + break; + case 'x': + bX360 = true; + break; + case 'g': // -game + ++i; + break; + default: + printusage(); + break; + } + } + else if ( i != 0 ) + { + char fn[ 512 ]; + Q_strncpy( fn, CommandLine()->GetParm( i ), sizeof( fn ) ); + Q_FixSlashes( fn ); + Q_strlower( fn ); + + CUtlSymbol sym; + sym = fn; + worklist.AddToTail( sym ); + } + } + + if ( CommandLine()->ParmCount() < 2 || ( i != CommandLine()->ParmCount() ) || worklist.Count() != 1 ) + { + PrintHeader(); + printusage(); + } + + CheckLogFile(); + + PrintHeader(); + + char binaries[MAX_PATH]; + Q_strncpy( binaries, gamedir, MAX_PATH ); + Q_StripTrailingSlash( binaries ); + Q_strncat( binaries, "/../bin", MAX_PATH, MAX_PATH ); + + char outfile[ 512 ]; + if ( Q_stristr( worklist[ worklist.Count() - 1 ].String(), gamedir ) ) + { + Q_strncpy( outfile, &worklist[ worklist.Count() - 1 ].String()[ Q_strlen( gamedir ) ] , sizeof( outfile ) ); + } + else + { + Q_snprintf( outfile, sizeof( outfile ), "resource\\%s", worklist[ worklist.Count() - 1 ].String() ); + } + + char infile[ 512 ]; + Q_strncpy( infile, outfile, sizeof( infile ) ); + + Q_SetExtension( outfile, ".dat", sizeof( outfile ) ); + + vprint( 0, "gamedir[ %s ]\n", gamedir ); + vprint( 0, "infile[ %s ]\n", infile ); + vprint( 0, "outfile[ %s ]\n", outfile ); + + g_pFullFileSystem->AddSearchPath( binaries, "EXECUTABLE_PATH" ); + + if ( !g_pVGuiLocalize->AddFile( infile, "MOD", false ) ) + { + Error( "Unable to add localization file '%s'\n", infile ); + } + + vprint( 0, " Compiling Captions for '%s'...\n", infile ); + + CompileCaptionFile( infile, outfile ); + + if ( verbose ) + { + DescribeCaptions( outfile ); + } + + g_pVGuiLocalize->RemoveAll(); + + return 0; +} + + +//----------------------------------------------------------------------------- +// Purpose: Main entry point +//----------------------------------------------------------------------------- +DEFINE_CONSOLE_STEAM_APPLICATION_OBJECT( CCompileCaptionsApp ) diff --git a/mp/src/utils/captioncompiler/cbase.h b/mp/src/utils/captioncompiler/cbase.h new file mode 100644 index 00000000..378d503f --- /dev/null +++ b/mp/src/utils/captioncompiler/cbase.h @@ -0,0 +1,19 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#ifndef CBASE_H +#define CBASE_H +#ifdef _WIN32 +#pragma once +#endif + +#include "basetypes.h" + +// This is just a dummy file to make this tool compile +#include "ai_activity.h" +#include "utlvector.h" + +#endif // CBASE_H diff --git a/mp/src/utils/common/ISQLDBReplyTarget.h b/mp/src/utils/common/ISQLDBReplyTarget.h new file mode 100644 index 00000000..31406368 --- /dev/null +++ b/mp/src/utils/common/ISQLDBReplyTarget.h @@ -0,0 +1,29 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef ISQLDLREPLYTARGET_H +#define ISQLDLREPLYTARGET_H +#ifdef _WIN32 +#pragma once +#endif + +//----------------------------------------------------------------------------- +// Purpose: Interface to handle results of SQL queries +//----------------------------------------------------------------------------- +class ISQLDBReplyTarget +{ +public: + // handles a response from the database + virtual void SQLDBResponse(int cmdID, int returnState, int returnVal, void *data) = 0; + + // called from a seperate thread; tells the reply target that a message is waiting for it + virtual void WakeUp() = 0; + +}; + + +#endif // ISQLDLREPLYTARGET_H diff --git a/mp/src/utils/common/MySqlDatabase.cpp b/mp/src/utils/common/MySqlDatabase.cpp new file mode 100644 index 00000000..46d8a4b9 --- /dev/null +++ b/mp/src/utils/common/MySqlDatabase.cpp @@ -0,0 +1,192 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "MySqlDatabase.h" + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +CMySqlDatabase::CMySqlDatabase() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: Destructor +// blocks until db process thread has stopped +//----------------------------------------------------------------------------- +CMySqlDatabase::~CMySqlDatabase() +{ + // flag the thread to stop + m_bRunThread = false; + + // pulse the thread to make it run + ::SetEvent(m_hEvent); + + // make sure it's done + ::EnterCriticalSection(&m_csThread); + ::LeaveCriticalSection(&m_csThread); +} + +//----------------------------------------------------------------------------- +// Purpose: Thread access function +//----------------------------------------------------------------------------- +static DWORD WINAPI staticThreadFunc(void *param) +{ + ((CMySqlDatabase *)param)->RunThread(); + return 0; +} + +//----------------------------------------------------------------------------- +// Purpose: Establishes connection to the database and sets up this object to handle db command +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CMySqlDatabase::Initialize() +{ + // prepare critical sections + //!! need to download SDK and replace these with InitializeCriticalSectionAndSpinCount() calls + ::InitializeCriticalSection(&m_csThread); + ::InitializeCriticalSection(&m_csInQueue); + ::InitializeCriticalSection(&m_csOutQueue); + ::InitializeCriticalSection(&m_csDBAccess); + + // initialize wait calls + m_hEvent = ::CreateEvent(NULL, false, true, NULL); + + // start the DB-access thread + m_bRunThread = true; + + unsigned long threadID; + ::CreateThread(NULL, 0, staticThreadFunc, this, 0, &threadID); + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Main thread loop +//----------------------------------------------------------------------------- +void CMySqlDatabase::RunThread() +{ + ::EnterCriticalSection(&m_csThread); + while (m_bRunThread) + { + if (m_InQueue.Count() > 0) + { + // get a dispatched DB request + ::EnterCriticalSection(&m_csInQueue); + + // pop the front of the queue + int headIndex = m_InQueue.Head(); + msg_t msg = m_InQueue[headIndex]; + m_InQueue.Remove(headIndex); + + ::LeaveCriticalSection(&m_csInQueue); + + ::EnterCriticalSection(&m_csDBAccess); + + // run sqldb command + msg.result = msg.cmd->RunCommand(); + + ::LeaveCriticalSection(&m_csDBAccess); + + if (msg.replyTarget) + { + // put the results in the outgoing queue + ::EnterCriticalSection(&m_csOutQueue); + m_OutQueue.AddToTail(msg); + ::LeaveCriticalSection(&m_csOutQueue); + + // wake up out queue + msg.replyTarget->WakeUp(); + } + else + { + // there is no return data from the call, so kill the object now + msg.cmd->deleteThis(); + } + } + else + { + // nothing in incoming queue, so wait until we get the signal + ::WaitForSingleObject(m_hEvent, INFINITE); + } + + // check the size of the outqueue; if it's getting too big, sleep to let the main thread catch up + if (m_OutQueue.Count() > 50) + { + ::Sleep(2); + } + } + ::LeaveCriticalSection(&m_csThread); +} + +//----------------------------------------------------------------------------- +// Purpose: Adds a database command to the queue, and wakes the db thread +//----------------------------------------------------------------------------- +void CMySqlDatabase::AddCommandToQueue(ISQLDBCommand *cmd, ISQLDBReplyTarget *replyTarget, int returnState) +{ + ::EnterCriticalSection(&m_csInQueue); + + // add to the queue + msg_t msg = { cmd, replyTarget, 0, returnState }; + m_InQueue.AddToTail(msg); + + ::LeaveCriticalSection(&m_csInQueue); + + // signal the thread to start running + ::SetEvent(m_hEvent); +} + +//----------------------------------------------------------------------------- +// Purpose: Dispatches responses to SQLDB queries +//----------------------------------------------------------------------------- +bool CMySqlDatabase::RunFrame() +{ + bool doneWork = false; + + while (m_OutQueue.Count() > 0) + { + ::EnterCriticalSection(&m_csOutQueue); + + // pop the first item in the queue + int headIndex = m_OutQueue.Head(); + msg_t msg = m_OutQueue[headIndex]; + m_OutQueue.Remove(headIndex); + + ::LeaveCriticalSection(&m_csOutQueue); + + // run result + if (msg.replyTarget) + { + msg.replyTarget->SQLDBResponse(msg.cmd->GetID(), msg.returnState, msg.result, msg.cmd->GetReturnData()); + + // kill command + // it would be a good optimization to be able to reuse these + msg.cmd->deleteThis(); + } + + doneWork = true; + } + + return doneWork; +} + +//----------------------------------------------------------------------------- +// Purpose: load info - returns the number of sql db queries waiting to be processed +//----------------------------------------------------------------------------- +int CMySqlDatabase::QueriesInOutQueue() +{ + // the queue names are from the DB point of view, not the server - thus the reversal + return m_InQueue.Count(); +} + +//----------------------------------------------------------------------------- +// Purpose: number of queries finished processing, waiting to be responded to +//----------------------------------------------------------------------------- +int CMySqlDatabase::QueriesInFinishedQueue() +{ + return m_OutQueue.Count(); +} diff --git a/mp/src/utils/common/MySqlDatabase.h b/mp/src/utils/common/MySqlDatabase.h new file mode 100644 index 00000000..caa5855c --- /dev/null +++ b/mp/src/utils/common/MySqlDatabase.h @@ -0,0 +1,104 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef MYSQLDATABASE_H +#define MYSQLDATABASE_H +#ifdef _WIN32 +#pragma once +#endif + +#include +#include "ISQLDBReplyTarget.h" +#include "utlvector.h" +#include "UtlLinkedList.h" + +class ISQLDBCommand; + +//----------------------------------------------------------------------------- +// Purpose: Generic MySQL accessing database +// Provides threaded I/O queue functionality for accessing a mysql db +//----------------------------------------------------------------------------- +class CMySqlDatabase +{ +public: + // constructor + CMySqlDatabase(); + ~CMySqlDatabase(); + + // initialization - must be called before this object can be used + bool Initialize(); + + // Dispatches responses to SQLDB queries + bool RunFrame(); + + // load info - returns the number of sql db queries waiting to be processed + virtual int QueriesInOutQueue(); + + // number of queries finished processing, waiting to be responded to + virtual int QueriesInFinishedQueue(); + + // activates the thread + void RunThread(); + + // command queues + void AddCommandToQueue(ISQLDBCommand *cmd, ISQLDBReplyTarget *replyTarget, int returnState = 0); + +private: + + // threading data + bool m_bRunThread; + CRITICAL_SECTION m_csThread; + CRITICAL_SECTION m_csInQueue; + CRITICAL_SECTION m_csOutQueue; + CRITICAL_SECTION m_csDBAccess; + + // wait event + HANDLE m_hEvent; + + struct msg_t + { + ISQLDBCommand *cmd; + ISQLDBReplyTarget *replyTarget; + int result; + int returnState; + }; + + // command queues + CUtlLinkedList m_InQueue; + CUtlLinkedList m_OutQueue; +}; + +class Connection; + +//----------------------------------------------------------------------------- +// Purpose: Interface to a command +//----------------------------------------------------------------------------- +class ISQLDBCommand +{ +public: + // makes the command run (blocking), returning the success code + virtual int RunCommand() = 0; + + // return data + virtual void *GetReturnData() { return NULL; } + + // returns the command ID + virtual int GetID() { return 0; } + + // gets information about the command for if it failed + virtual void GetDebugInfo(char *buf, int bufSize) { buf[0] = 0; } + + // use to delete + virtual void deleteThis() = 0; + +protected: + // protected destructor, so that it has to be deleted through deleteThis() + virtual ~ISQLDBCommand() {} +}; + + +#endif // MYSQLDATABASE_H diff --git a/mp/src/utils/common/bsplib.cpp b/mp/src/utils/common/bsplib.cpp new file mode 100644 index 00000000..84d1a1d0 --- /dev/null +++ b/mp/src/utils/common/bsplib.cpp @@ -0,0 +1,5064 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Revision: $ +// $NoKeywords: $ +//=============================================================================// + +#include "cmdlib.h" +#include "mathlib/mathlib.h" +#include "bsplib.h" +#include "zip_utils.h" +#include "scriplib.h" +#include "utllinkedlist.h" +#include "bsptreedata.h" +#include "cmodel.h" +#include "gamebspfile.h" +#include "materialsystem/imaterial.h" +#include "materialsystem/hardwareverts.h" +#include "utlbuffer.h" +#include "utlrbtree.h" +#include "utlsymbol.h" +#include "utlstring.h" +#include "checksum_crc.h" +#include "physdll.h" +#include "tier0/dbg.h" +#include "lumpfiles.h" +#include "vtf/vtf.h" + +//============================================================================= + +// Boundary each lump should be aligned to +#define LUMP_ALIGNMENT 4 + +// Data descriptions for byte swapping - only needed +// for structures that are written to file for use by the game. +BEGIN_BYTESWAP_DATADESC( dheader_t ) + DEFINE_FIELD( ident, FIELD_INTEGER ), + DEFINE_FIELD( version, FIELD_INTEGER ), + DEFINE_EMBEDDED_ARRAY( lumps, HEADER_LUMPS ), + DEFINE_FIELD( mapRevision, FIELD_INTEGER ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( lump_t ) + DEFINE_FIELD( fileofs, FIELD_INTEGER ), + DEFINE_FIELD( filelen, FIELD_INTEGER ), + DEFINE_FIELD( version, FIELD_INTEGER ), + DEFINE_ARRAY( fourCC, FIELD_CHARACTER, 4 ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( dflagslump_t ) + DEFINE_FIELD( m_LevelFlags, FIELD_INTEGER ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( dplane_t ) + DEFINE_FIELD( normal, FIELD_VECTOR ), + DEFINE_FIELD( dist, FIELD_FLOAT ), + DEFINE_FIELD( type, FIELD_INTEGER ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( dleaf_version_0_t ) + DEFINE_FIELD( contents, FIELD_INTEGER ), + DEFINE_FIELD( cluster, FIELD_SHORT ), + DEFINE_BITFIELD( bf, FIELD_SHORT, 16 ), + DEFINE_ARRAY( mins, FIELD_SHORT, 3 ), + DEFINE_ARRAY( maxs, FIELD_SHORT, 3 ), + DEFINE_FIELD( firstleafface, FIELD_SHORT ), + DEFINE_FIELD( numleaffaces, FIELD_SHORT ), + DEFINE_FIELD( firstleafbrush, FIELD_SHORT ), + DEFINE_FIELD( numleafbrushes, FIELD_SHORT ), + DEFINE_FIELD( leafWaterDataID, FIELD_SHORT ), + DEFINE_EMBEDDED( m_AmbientLighting ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( dleaf_t ) + DEFINE_FIELD( contents, FIELD_INTEGER ), + DEFINE_FIELD( cluster, FIELD_SHORT ), + DEFINE_BITFIELD( bf, FIELD_SHORT, 16 ), + DEFINE_ARRAY( mins, FIELD_SHORT, 3 ), + DEFINE_ARRAY( maxs, FIELD_SHORT, 3 ), + DEFINE_FIELD( firstleafface, FIELD_SHORT ), + DEFINE_FIELD( numleaffaces, FIELD_SHORT ), + DEFINE_FIELD( firstleafbrush, FIELD_SHORT ), + DEFINE_FIELD( numleafbrushes, FIELD_SHORT ), + DEFINE_FIELD( leafWaterDataID, FIELD_SHORT ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( CompressedLightCube ) // array of 6 ColorRGBExp32 (3 bytes and 1 char) + DEFINE_ARRAY( m_Color, FIELD_CHARACTER, 6 * sizeof(ColorRGBExp32) ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( dleafambientindex_t ) + DEFINE_FIELD( ambientSampleCount, FIELD_SHORT ), + DEFINE_FIELD( firstAmbientSample, FIELD_SHORT ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( dleafambientlighting_t ) // array of 6 ColorRGBExp32 (3 bytes and 1 char) + DEFINE_EMBEDDED( cube ), + DEFINE_FIELD( x, FIELD_CHARACTER ), + DEFINE_FIELD( y, FIELD_CHARACTER ), + DEFINE_FIELD( z, FIELD_CHARACTER ), + DEFINE_FIELD( pad, FIELD_CHARACTER ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( dvertex_t ) + DEFINE_FIELD( point, FIELD_VECTOR ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( dnode_t ) + DEFINE_FIELD( planenum, FIELD_INTEGER ), + DEFINE_ARRAY( children, FIELD_INTEGER, 2 ), + DEFINE_ARRAY( mins, FIELD_SHORT, 3 ), + DEFINE_ARRAY( maxs, FIELD_SHORT, 3 ), + DEFINE_FIELD( firstface, FIELD_SHORT ), + DEFINE_FIELD( numfaces, FIELD_SHORT ), + DEFINE_FIELD( area, FIELD_SHORT ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( texinfo_t ) + DEFINE_ARRAY( textureVecsTexelsPerWorldUnits, FIELD_FLOAT, 2 * 4 ), + DEFINE_ARRAY( lightmapVecsLuxelsPerWorldUnits, FIELD_FLOAT, 2 * 4 ), + DEFINE_FIELD( flags, FIELD_INTEGER ), + DEFINE_FIELD( texdata, FIELD_INTEGER ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( dtexdata_t ) + DEFINE_FIELD( reflectivity, FIELD_VECTOR ), + DEFINE_FIELD( nameStringTableID, FIELD_INTEGER ), + DEFINE_FIELD( width, FIELD_INTEGER ), + DEFINE_FIELD( height, FIELD_INTEGER ), + DEFINE_FIELD( view_width, FIELD_INTEGER ), + DEFINE_FIELD( view_height, FIELD_INTEGER ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( ddispinfo_t ) + DEFINE_FIELD( startPosition, FIELD_VECTOR ), + DEFINE_FIELD( m_iDispVertStart, FIELD_INTEGER ), + DEFINE_FIELD( m_iDispTriStart, FIELD_INTEGER ), + DEFINE_FIELD( power, FIELD_INTEGER ), + DEFINE_FIELD( minTess, FIELD_INTEGER ), + DEFINE_FIELD( smoothingAngle, FIELD_FLOAT ), + DEFINE_FIELD( contents, FIELD_INTEGER ), + DEFINE_FIELD( m_iMapFace, FIELD_SHORT ), + DEFINE_FIELD( m_iLightmapAlphaStart, FIELD_INTEGER ), + DEFINE_FIELD( m_iLightmapSamplePositionStart, FIELD_INTEGER ), + DEFINE_EMBEDDED_ARRAY( m_EdgeNeighbors, 4 ), + DEFINE_EMBEDDED_ARRAY( m_CornerNeighbors, 4 ), + DEFINE_ARRAY( m_AllowedVerts, FIELD_INTEGER, ddispinfo_t::ALLOWEDVERTS_SIZE ), // unsigned long +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( CDispNeighbor ) + DEFINE_EMBEDDED_ARRAY( m_SubNeighbors, 2 ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( CDispCornerNeighbors ) + DEFINE_ARRAY( m_Neighbors, FIELD_SHORT, MAX_DISP_CORNER_NEIGHBORS ), + DEFINE_FIELD( m_nNeighbors, FIELD_CHARACTER ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( CDispSubNeighbor ) + DEFINE_FIELD( m_iNeighbor, FIELD_SHORT ), + DEFINE_FIELD( m_NeighborOrientation, FIELD_CHARACTER ), + DEFINE_FIELD( m_Span, FIELD_CHARACTER ), + DEFINE_FIELD( m_NeighborSpan, FIELD_CHARACTER ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( CDispVert ) + DEFINE_FIELD( m_vVector, FIELD_VECTOR ), + DEFINE_FIELD( m_flDist, FIELD_FLOAT ), + DEFINE_FIELD( m_flAlpha, FIELD_FLOAT ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( CDispTri ) + DEFINE_FIELD( m_uiTags, FIELD_SHORT ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( CFaceMacroTextureInfo ) + DEFINE_FIELD( m_MacroTextureNameID, FIELD_SHORT ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( dprimitive_t ) + DEFINE_FIELD( type, FIELD_CHARACTER ), + DEFINE_FIELD( firstIndex, FIELD_SHORT ), + DEFINE_FIELD( indexCount, FIELD_SHORT ), + DEFINE_FIELD( firstVert, FIELD_SHORT ), + DEFINE_FIELD( vertCount, FIELD_SHORT ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( dprimvert_t ) + DEFINE_FIELD( pos, FIELD_VECTOR ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( dface_t ) + DEFINE_FIELD( planenum, FIELD_SHORT ), + DEFINE_FIELD( side, FIELD_CHARACTER ), + DEFINE_FIELD( onNode, FIELD_CHARACTER ), + DEFINE_FIELD( firstedge, FIELD_INTEGER ), + DEFINE_FIELD( numedges, FIELD_SHORT ), + DEFINE_FIELD( texinfo, FIELD_SHORT ), + DEFINE_FIELD( dispinfo, FIELD_SHORT ), + DEFINE_FIELD( surfaceFogVolumeID, FIELD_SHORT ), + DEFINE_ARRAY( styles, FIELD_CHARACTER, MAXLIGHTMAPS ), + DEFINE_FIELD( lightofs, FIELD_INTEGER ), + DEFINE_FIELD( area, FIELD_FLOAT ), + DEFINE_ARRAY( m_LightmapTextureMinsInLuxels, FIELD_INTEGER, 2 ), + DEFINE_ARRAY( m_LightmapTextureSizeInLuxels, FIELD_INTEGER, 2 ), + DEFINE_FIELD( origFace, FIELD_INTEGER ), + DEFINE_FIELD( m_NumPrims, FIELD_SHORT ), + DEFINE_FIELD( firstPrimID, FIELD_SHORT ), + DEFINE_FIELD( smoothingGroups, FIELD_INTEGER ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( dfaceid_t ) + DEFINE_FIELD( hammerfaceid, FIELD_SHORT ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( dbrush_t ) + DEFINE_FIELD( firstside, FIELD_INTEGER ), + DEFINE_FIELD( numsides, FIELD_INTEGER ), + DEFINE_FIELD( contents, FIELD_INTEGER ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( dbrushside_t ) + DEFINE_FIELD( planenum, FIELD_SHORT ), + DEFINE_FIELD( texinfo, FIELD_SHORT ), + DEFINE_FIELD( dispinfo, FIELD_SHORT ), + DEFINE_FIELD( bevel, FIELD_SHORT ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( dedge_t ) + DEFINE_ARRAY( v, FIELD_SHORT, 2 ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( dmodel_t ) + DEFINE_FIELD( mins, FIELD_VECTOR ), + DEFINE_FIELD( maxs, FIELD_VECTOR ), + DEFINE_FIELD( origin, FIELD_VECTOR ), + DEFINE_FIELD( headnode, FIELD_INTEGER ), + DEFINE_FIELD( firstface, FIELD_INTEGER ), + DEFINE_FIELD( numfaces, FIELD_INTEGER ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( dphysmodel_t ) + DEFINE_FIELD( modelIndex, FIELD_INTEGER ), + DEFINE_FIELD( dataSize, FIELD_INTEGER ), + DEFINE_FIELD( keydataSize, FIELD_INTEGER ), + DEFINE_FIELD( solidCount, FIELD_INTEGER ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( dphysdisp_t ) + DEFINE_FIELD( numDisplacements, FIELD_SHORT ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( darea_t ) + DEFINE_FIELD( numareaportals, FIELD_INTEGER ), + DEFINE_FIELD( firstareaportal, FIELD_INTEGER ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( dareaportal_t ) + DEFINE_FIELD( m_PortalKey, FIELD_SHORT ), + DEFINE_FIELD( otherarea, FIELD_SHORT ), + DEFINE_FIELD( m_FirstClipPortalVert, FIELD_SHORT ), + DEFINE_FIELD( m_nClipPortalVerts, FIELD_SHORT ), + DEFINE_FIELD( planenum, FIELD_INTEGER ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( dworldlight_t ) + DEFINE_FIELD( origin, FIELD_VECTOR ), + DEFINE_FIELD( intensity, FIELD_VECTOR ), + DEFINE_FIELD( normal, FIELD_VECTOR ), + DEFINE_FIELD( cluster, FIELD_INTEGER ), + DEFINE_FIELD( type, FIELD_INTEGER ), // enumeration + DEFINE_FIELD( style, FIELD_INTEGER ), + DEFINE_FIELD( stopdot, FIELD_FLOAT ), + DEFINE_FIELD( stopdot2, FIELD_FLOAT ), + DEFINE_FIELD( exponent, FIELD_FLOAT ), + DEFINE_FIELD( radius, FIELD_FLOAT ), + DEFINE_FIELD( constant_attn, FIELD_FLOAT ), + DEFINE_FIELD( linear_attn, FIELD_FLOAT ), + DEFINE_FIELD( quadratic_attn, FIELD_FLOAT ), + DEFINE_FIELD( flags, FIELD_INTEGER ), + DEFINE_FIELD( texinfo, FIELD_INTEGER ), + DEFINE_FIELD( owner, FIELD_INTEGER ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( dleafwaterdata_t ) + DEFINE_FIELD( surfaceZ, FIELD_FLOAT ), + DEFINE_FIELD( minZ, FIELD_FLOAT ), + DEFINE_FIELD( surfaceTexInfoID, FIELD_SHORT ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( doccluderdata_t ) + DEFINE_FIELD( flags, FIELD_INTEGER ), + DEFINE_FIELD( firstpoly, FIELD_INTEGER ), + DEFINE_FIELD( polycount, FIELD_INTEGER ), + DEFINE_FIELD( mins, FIELD_VECTOR ), + DEFINE_FIELD( maxs, FIELD_VECTOR ), + DEFINE_FIELD( area, FIELD_INTEGER ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( doccluderpolydata_t ) + DEFINE_FIELD( firstvertexindex, FIELD_INTEGER ), + DEFINE_FIELD( vertexcount, FIELD_INTEGER ), + DEFINE_FIELD( planenum, FIELD_INTEGER ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( dcubemapsample_t ) + DEFINE_ARRAY( origin, FIELD_INTEGER, 3 ), + DEFINE_FIELD( size, FIELD_CHARACTER ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( doverlay_t ) + DEFINE_FIELD( nId, FIELD_INTEGER ), + DEFINE_FIELD( nTexInfo, FIELD_SHORT ), + DEFINE_FIELD( m_nFaceCountAndRenderOrder, FIELD_SHORT ), + DEFINE_ARRAY( aFaces, FIELD_INTEGER, OVERLAY_BSP_FACE_COUNT ), + DEFINE_ARRAY( flU, FIELD_FLOAT, 2 ), + DEFINE_ARRAY( flV, FIELD_FLOAT, 2 ), + DEFINE_ARRAY( vecUVPoints, FIELD_VECTOR, 4 ), + DEFINE_FIELD( vecOrigin, FIELD_VECTOR ), + DEFINE_FIELD( vecBasisNormal, FIELD_VECTOR ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( dwateroverlay_t ) + DEFINE_FIELD( nId, FIELD_INTEGER ), + DEFINE_FIELD( nTexInfo, FIELD_SHORT ), + DEFINE_FIELD( m_nFaceCountAndRenderOrder, FIELD_SHORT ), + DEFINE_ARRAY( aFaces, FIELD_INTEGER, WATEROVERLAY_BSP_FACE_COUNT ), + DEFINE_ARRAY( flU, FIELD_FLOAT, 2 ), + DEFINE_ARRAY( flV, FIELD_FLOAT, 2 ), + DEFINE_ARRAY( vecUVPoints, FIELD_VECTOR, 4 ), + DEFINE_FIELD( vecOrigin, FIELD_VECTOR ), + DEFINE_FIELD( vecBasisNormal, FIELD_VECTOR ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( doverlayfade_t ) + DEFINE_FIELD( flFadeDistMinSq, FIELD_FLOAT ), + DEFINE_FIELD( flFadeDistMaxSq, FIELD_FLOAT ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( dgamelumpheader_t ) + DEFINE_FIELD( lumpCount, FIELD_INTEGER ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( dgamelump_t ) + DEFINE_FIELD( id, FIELD_INTEGER ), // GameLumpId_t + DEFINE_FIELD( flags, FIELD_SHORT ), + DEFINE_FIELD( version, FIELD_SHORT ), + DEFINE_FIELD( fileofs, FIELD_INTEGER ), + DEFINE_FIELD( filelen, FIELD_INTEGER ), +END_BYTESWAP_DATADESC() + +// From gamebspfile.h +BEGIN_BYTESWAP_DATADESC( StaticPropDictLump_t ) + DEFINE_ARRAY( m_Name, FIELD_CHARACTER, STATIC_PROP_NAME_LENGTH ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( StaticPropLump_t ) + DEFINE_FIELD( m_Origin, FIELD_VECTOR ), + DEFINE_FIELD( m_Angles, FIELD_VECTOR ), // QAngle + DEFINE_FIELD( m_PropType, FIELD_SHORT ), + DEFINE_FIELD( m_FirstLeaf, FIELD_SHORT ), + DEFINE_FIELD( m_LeafCount, FIELD_SHORT ), + DEFINE_FIELD( m_Solid, FIELD_CHARACTER ), + DEFINE_FIELD( m_Flags, FIELD_CHARACTER ), + DEFINE_FIELD( m_Skin, FIELD_INTEGER ), + DEFINE_FIELD( m_FadeMinDist, FIELD_FLOAT ), + DEFINE_FIELD( m_FadeMaxDist, FIELD_FLOAT ), + DEFINE_FIELD( m_LightingOrigin, FIELD_VECTOR ), + DEFINE_FIELD( m_flForcedFadeScale, FIELD_FLOAT ), + DEFINE_FIELD( m_nMinDXLevel, FIELD_SHORT ), + DEFINE_FIELD( m_nMaxDXLevel, FIELD_SHORT ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( StaticPropLumpV4_t ) + DEFINE_FIELD( m_Origin, FIELD_VECTOR ), + DEFINE_FIELD( m_Angles, FIELD_VECTOR ), // QAngle + DEFINE_FIELD( m_PropType, FIELD_SHORT ), + DEFINE_FIELD( m_FirstLeaf, FIELD_SHORT ), + DEFINE_FIELD( m_LeafCount, FIELD_SHORT ), + DEFINE_FIELD( m_Solid, FIELD_CHARACTER ), + DEFINE_FIELD( m_Flags, FIELD_CHARACTER ), + DEFINE_FIELD( m_Skin, FIELD_INTEGER ), + DEFINE_FIELD( m_FadeMinDist, FIELD_FLOAT ), + DEFINE_FIELD( m_FadeMaxDist, FIELD_FLOAT ), + DEFINE_FIELD( m_LightingOrigin, FIELD_VECTOR ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( StaticPropLumpV5_t ) + DEFINE_FIELD( m_Origin, FIELD_VECTOR ), + DEFINE_FIELD( m_Angles, FIELD_VECTOR ), // QAngle + DEFINE_FIELD( m_PropType, FIELD_SHORT ), + DEFINE_FIELD( m_FirstLeaf, FIELD_SHORT ), + DEFINE_FIELD( m_LeafCount, FIELD_SHORT ), + DEFINE_FIELD( m_Solid, FIELD_CHARACTER ), + DEFINE_FIELD( m_Flags, FIELD_CHARACTER ), + DEFINE_FIELD( m_Skin, FIELD_INTEGER ), + DEFINE_FIELD( m_FadeMinDist, FIELD_FLOAT ), + DEFINE_FIELD( m_FadeMaxDist, FIELD_FLOAT ), + DEFINE_FIELD( m_LightingOrigin, FIELD_VECTOR ), + DEFINE_FIELD( m_flForcedFadeScale, FIELD_FLOAT ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( StaticPropLeafLump_t ) + DEFINE_FIELD( m_Leaf, FIELD_SHORT ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( DetailObjectDictLump_t ) + DEFINE_ARRAY( m_Name, FIELD_CHARACTER, DETAIL_NAME_LENGTH ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( DetailObjectLump_t ) + DEFINE_FIELD( m_Origin, FIELD_VECTOR ), + DEFINE_FIELD( m_Angles, FIELD_VECTOR ), // QAngle + DEFINE_FIELD( m_DetailModel, FIELD_SHORT ), + DEFINE_FIELD( m_Leaf, FIELD_SHORT ), + DEFINE_ARRAY( m_Lighting, FIELD_CHARACTER, 4 ), // ColorRGBExp32 + DEFINE_FIELD( m_LightStyles, FIELD_INTEGER ), + DEFINE_FIELD( m_LightStyleCount, FIELD_CHARACTER ), + DEFINE_FIELD( m_SwayAmount, FIELD_CHARACTER ), + DEFINE_FIELD( m_ShapeAngle, FIELD_CHARACTER ), + DEFINE_FIELD( m_ShapeSize, FIELD_CHARACTER ), + DEFINE_FIELD( m_Orientation, FIELD_CHARACTER ), + DEFINE_ARRAY( m_Padding2, FIELD_CHARACTER, 3 ), + DEFINE_FIELD( m_Type, FIELD_CHARACTER ), + DEFINE_ARRAY( m_Padding3, FIELD_CHARACTER, 3 ), + DEFINE_FIELD( m_flScale, FIELD_FLOAT ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( DetailSpriteDictLump_t ) + DEFINE_FIELD( m_UL, FIELD_VECTOR2D ), + DEFINE_FIELD( m_LR, FIELD_VECTOR2D ), + DEFINE_FIELD( m_TexUL, FIELD_VECTOR2D ), + DEFINE_FIELD( m_TexLR, FIELD_VECTOR2D ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( DetailPropLightstylesLump_t ) + DEFINE_ARRAY( m_Lighting, FIELD_CHARACTER, 4 ), // ColorRGBExp32 + DEFINE_FIELD( m_Style, FIELD_CHARACTER ), +END_BYTESWAP_DATADESC() + +// From vradstaticprops.h +namespace HardwareVerts +{ +BEGIN_BYTESWAP_DATADESC( MeshHeader_t ) + DEFINE_FIELD( m_nLod, FIELD_INTEGER ), + DEFINE_FIELD( m_nVertexes, FIELD_INTEGER ), + DEFINE_FIELD( m_nOffset, FIELD_INTEGER ), + DEFINE_ARRAY( m_nUnused, FIELD_INTEGER, 4 ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( FileHeader_t ) + DEFINE_FIELD( m_nVersion, FIELD_INTEGER ), + DEFINE_FIELD( m_nChecksum, FIELD_INTEGER ), + DEFINE_FIELD( m_nVertexFlags, FIELD_INTEGER ), + DEFINE_FIELD( m_nVertexSize, FIELD_INTEGER ), + DEFINE_FIELD( m_nVertexes, FIELD_INTEGER ), + DEFINE_FIELD( m_nMeshes, FIELD_INTEGER ), + DEFINE_ARRAY( m_nUnused, FIELD_INTEGER, 4 ), +END_BYTESWAP_DATADESC() +} // end namespace + +static const char *s_LumpNames[] = { + "LUMP_ENTITIES", // 0 + "LUMP_PLANES", // 1 + "LUMP_TEXDATA", // 2 + "LUMP_VERTEXES", // 3 + "LUMP_VISIBILITY", // 4 + "LUMP_NODES", // 5 + "LUMP_TEXINFO", // 6 + "LUMP_FACES", // 7 + "LUMP_LIGHTING", // 8 + "LUMP_OCCLUSION", // 9 + "LUMP_LEAFS", // 10 + "LUMP_FACEIDS", // 11 + "LUMP_EDGES", // 12 + "LUMP_SURFEDGES", // 13 + "LUMP_MODELS", // 14 + "LUMP_WORLDLIGHTS", // 15 + "LUMP_LEAFFACES", // 16 + "LUMP_LEAFBRUSHES", // 17 + "LUMP_BRUSHES", // 18 + "LUMP_BRUSHSIDES", // 19 + "LUMP_AREAS", // 20 + "LUMP_AREAPORTALS", // 21 + "LUMP_UNUSED0", // 22 + "LUMP_UNUSED1", // 23 + "LUMP_UNUSED2", // 24 + "LUMP_UNUSED3", // 25 + "LUMP_DISPINFO", // 26 + "LUMP_ORIGINALFACES", // 27 + "LUMP_PHYSDISP", // 28 + "LUMP_PHYSCOLLIDE", // 29 + "LUMP_VERTNORMALS", // 30 + "LUMP_VERTNORMALINDICES", // 31 + "LUMP_DISP_LIGHTMAP_ALPHAS", // 32 + "LUMP_DISP_VERTS", // 33 + "LUMP_DISP_LIGHTMAP_SAMPLE_POSITIONS", // 34 + "LUMP_GAME_LUMP", // 35 + "LUMP_LEAFWATERDATA", // 36 + "LUMP_PRIMITIVES", // 37 + "LUMP_PRIMVERTS", // 38 + "LUMP_PRIMINDICES", // 39 + "LUMP_PAKFILE", // 40 + "LUMP_CLIPPORTALVERTS", // 41 + "LUMP_CUBEMAPS", // 42 + "LUMP_TEXDATA_STRING_DATA", // 43 + "LUMP_TEXDATA_STRING_TABLE", // 44 + "LUMP_OVERLAYS", // 45 + "LUMP_LEAFMINDISTTOWATER", // 46 + "LUMP_FACE_MACRO_TEXTURE_INFO", // 47 + "LUMP_DISP_TRIS", // 48 + "LUMP_PHYSCOLLIDESURFACE", // 49 + "LUMP_WATEROVERLAYS", // 50 + "LUMP_LEAF_AMBIENT_INDEX_HDR", // 51 + "LUMP_LEAF_AMBIENT_INDEX", // 52 + "LUMP_LIGHTING_HDR", // 53 + "LUMP_WORLDLIGHTS_HDR", // 54 + "LUMP_LEAF_AMBIENT_LIGHTING_HDR", // 55 + "LUMP_LEAF_AMBIENT_LIGHTING", // 56 + "LUMP_XZIPPAKFILE", // 57 + "LUMP_FACES_HDR", // 58 + "LUMP_MAP_FLAGS", // 59 + "LUMP_OVERLAY_FADES", // 60 +}; + +const char *GetLumpName( unsigned int lumpnum ) +{ + if ( lumpnum >= ARRAYSIZE( s_LumpNames ) ) + { + return "UNKNOWN"; + } + return s_LumpNames[lumpnum]; +} + +// "-hdr" tells us to use the HDR fields (if present) on the light sources. Also, tells us to write +// out the HDR lumps for lightmaps, ambient leaves, and lights sources. +bool g_bHDR = false; + +// Set to true to generate Xbox360 native output files +static bool g_bSwapOnLoad = false; +static bool g_bSwapOnWrite = false; + +VTFConvertFunc_t g_pVTFConvertFunc; +VHVFixupFunc_t g_pVHVFixupFunc; +CompressFunc_t g_pCompressFunc; + +CUtlVector< CUtlString > g_StaticPropNames; +CUtlVector< int > g_StaticPropInstances; + +CByteswap g_Swap; + +uint32 g_LevelFlags = 0; + +int nummodels; +dmodel_t dmodels[MAX_MAP_MODELS]; + +int visdatasize; +byte dvisdata[MAX_MAP_VISIBILITY]; +dvis_t *dvis = (dvis_t *)dvisdata; + +CUtlVector dlightdataHDR; +CUtlVector dlightdataLDR; +CUtlVector *pdlightdata = &dlightdataLDR; + +CUtlVector dentdata; + +int numleafs; +#if !defined( BSP_USE_LESS_MEMORY ) +dleaf_t dleafs[MAX_MAP_LEAFS]; +#else +dleaf_t *dleafs; +#endif + +CUtlVector g_LeafAmbientIndexLDR; +CUtlVector g_LeafAmbientIndexHDR; +CUtlVector *g_pLeafAmbientIndex = NULL; +CUtlVector g_LeafAmbientLightingLDR; +CUtlVector g_LeafAmbientLightingHDR; +CUtlVector *g_pLeafAmbientLighting = NULL; + +unsigned short g_LeafMinDistToWater[MAX_MAP_LEAFS]; + +int numplanes; +dplane_t dplanes[MAX_MAP_PLANES]; + +int numvertexes; +dvertex_t dvertexes[MAX_MAP_VERTS]; + +int g_numvertnormalindices; // dfaces reference these. These index g_vertnormals. +unsigned short g_vertnormalindices[MAX_MAP_VERTNORMALS]; + +int g_numvertnormals; +Vector g_vertnormals[MAX_MAP_VERTNORMALS]; + +int numnodes; +dnode_t dnodes[MAX_MAP_NODES]; + +CUtlVector texinfo( MAX_MAP_TEXINFO ); + +int numtexdata; +dtexdata_t dtexdata[MAX_MAP_TEXDATA]; + +// +// displacement map bsp file info: dispinfo +// +CUtlVector g_dispinfo; +CUtlVector g_DispVerts; +CUtlVector g_DispTris; +CUtlVector g_DispLightmapSamplePositions; // LUMP_DISP_LIGHTMAP_SAMPLE_POSITIONS + +int numorigfaces; +dface_t dorigfaces[MAX_MAP_FACES]; + +int g_numprimitives = 0; +dprimitive_t g_primitives[MAX_MAP_PRIMITIVES]; + +int g_numprimverts = 0; +dprimvert_t g_primverts[MAX_MAP_PRIMVERTS]; + +int g_numprimindices = 0; +unsigned short g_primindices[MAX_MAP_PRIMINDICES]; + +int numfaces; +dface_t dfaces[MAX_MAP_FACES]; + +int numfaceids; +CUtlVector dfaceids; + +int numfaces_hdr; +dface_t dfaces_hdr[MAX_MAP_FACES]; + +int numedges; +dedge_t dedges[MAX_MAP_EDGES]; + +int numleaffaces; +unsigned short dleaffaces[MAX_MAP_LEAFFACES]; + +int numleafbrushes; +unsigned short dleafbrushes[MAX_MAP_LEAFBRUSHES]; + +int numsurfedges; +int dsurfedges[MAX_MAP_SURFEDGES]; + +int numbrushes; +dbrush_t dbrushes[MAX_MAP_BRUSHES]; + +int numbrushsides; +dbrushside_t dbrushsides[MAX_MAP_BRUSHSIDES]; + +int numareas; +darea_t dareas[MAX_MAP_AREAS]; + +int numareaportals; +dareaportal_t dareaportals[MAX_MAP_AREAPORTALS]; + +int numworldlightsLDR; +dworldlight_t dworldlightsLDR[MAX_MAP_WORLDLIGHTS]; + +int numworldlightsHDR; +dworldlight_t dworldlightsHDR[MAX_MAP_WORLDLIGHTS]; + +int *pNumworldlights = &numworldlightsLDR; +dworldlight_t *dworldlights = dworldlightsLDR; + +int numleafwaterdata = 0; +dleafwaterdata_t dleafwaterdata[MAX_MAP_LEAFWATERDATA]; + +CUtlVector g_FaceMacroTextureInfos; + +Vector g_ClipPortalVerts[MAX_MAP_PORTALVERTS]; +int g_nClipPortalVerts; + +dcubemapsample_t g_CubemapSamples[MAX_MAP_CUBEMAPSAMPLES]; +int g_nCubemapSamples = 0; + +int g_nOverlayCount; +doverlay_t g_Overlays[MAX_MAP_OVERLAYS]; +doverlayfade_t g_OverlayFades[MAX_MAP_OVERLAYS]; + +int g_nWaterOverlayCount; +dwateroverlay_t g_WaterOverlays[MAX_MAP_WATEROVERLAYS]; + +CUtlVector g_TexDataStringData; +CUtlVector g_TexDataStringTable; + +byte *g_pPhysCollide = NULL; +int g_PhysCollideSize = 0; +int g_MapRevision = 0; + +byte *g_pPhysDisp = NULL; +int g_PhysDispSize = 0; + +CUtlVector g_OccluderData( 256, 256 ); +CUtlVector g_OccluderPolyData( 1024, 1024 ); +CUtlVector g_OccluderVertexIndices( 2048, 2048 ); + +template static void WriteData( T *pData, int count = 1 ); +template static void WriteData( int fieldType, T *pData, int count = 1 ); +template< class T > static void AddLump( int lumpnum, T *pData, int count, int version = 0 ); +template< class T > static void AddLump( int lumpnum, CUtlVector &data, int version = 0 ); + +dheader_t *g_pBSPHeader; +FileHandle_t g_hBSPFile; + +struct Lump_t +{ + void *pLumps[HEADER_LUMPS]; + int size[HEADER_LUMPS]; + bool bLumpParsed[HEADER_LUMPS]; +} g_Lumps; + +CGameLump g_GameLumps; + +static IZip *s_pakFile = 0; + +//----------------------------------------------------------------------------- +// Keep the file position aligned to an arbitrary boundary. +// Returns updated file position. +//----------------------------------------------------------------------------- +static unsigned int AlignFilePosition( FileHandle_t hFile, int alignment ) +{ + unsigned int currPosition = g_pFileSystem->Tell( hFile ); + + if ( alignment >= 2 ) + { + unsigned int newPosition = AlignValue( currPosition, alignment ); + unsigned int count = newPosition - currPosition; + if ( count ) + { + char *pBuffer; + char smallBuffer[4096]; + if ( count > sizeof( smallBuffer ) ) + { + pBuffer = (char *)malloc( count ); + } + else + { + pBuffer = smallBuffer; + } + + memset( pBuffer, 0, count ); + SafeWrite( hFile, pBuffer, count ); + + if ( pBuffer != smallBuffer ) + { + free( pBuffer ); + } + + currPosition = newPosition; + } + } + + return currPosition; +} + +//----------------------------------------------------------------------------- +// Purpose: // Get a pakfile instance +// Output : IZip* +//----------------------------------------------------------------------------- +IZip* GetPakFile( void ) +{ + if ( !s_pakFile ) + { + s_pakFile = IZip::CreateZip(); + } + return s_pakFile; +} + +//----------------------------------------------------------------------------- +// Purpose: Free the pak files +//----------------------------------------------------------------------------- +void ReleasePakFileLumps( void ) +{ + // Release the pak files + IZip::ReleaseZip( s_pakFile ); + s_pakFile = NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: Set the sector alignment for all subsequent zip operations +//----------------------------------------------------------------------------- +void ForceAlignment( IZip *pak, bool bAlign, bool bCompatibleFormat, unsigned int alignmentSize ) +{ + pak->ForceAlignment( bAlign, bCompatibleFormat, alignmentSize ); +} + +//----------------------------------------------------------------------------- +// Purpose: Store data back out to .bsp file +//----------------------------------------------------------------------------- +static void WritePakFileLump( void ) +{ + CUtlBuffer buf( 0, 0 ); + GetPakFile()->ActivateByteSwapping( IsX360() ); + GetPakFile()->SaveToBuffer( buf ); + + // must respect pak file alignment + // pad up and ensure lump starts on same aligned boundary + AlignFilePosition( g_hBSPFile, GetPakFile()->GetAlignment() ); + + // Now store final buffers out to file + AddLump( LUMP_PAKFILE, (byte*)buf.Base(), buf.TellPut() ); +} + +//----------------------------------------------------------------------------- +// Purpose: Remove all entries +//----------------------------------------------------------------------------- +void ClearPakFile( IZip *pak ) +{ + pak->Reset(); +} + +//----------------------------------------------------------------------------- +// Purpose: Add file from disk to .bsp PAK lump +// Input : *relativename - +// *fullpath - +//----------------------------------------------------------------------------- +void AddFileToPak( IZip *pak, const char *relativename, const char *fullpath ) +{ + pak->AddFileToZip( relativename, fullpath ); +} + +//----------------------------------------------------------------------------- +// Purpose: Add buffer to .bsp PAK lump as named file +// Input : *relativename - +// *data - +// length - +//----------------------------------------------------------------------------- +void AddBufferToPak( IZip *pak, const char *pRelativeName, void *data, int length, bool bTextMode ) +{ + pak->AddBufferToZip( pRelativeName, data, length, bTextMode ); +} + +//----------------------------------------------------------------------------- +// Purpose: Check if a file already exists in the pack file. +// Input : *relativename - +//----------------------------------------------------------------------------- +bool FileExistsInPak( IZip *pak, const char *pRelativeName ) +{ + return pak->FileExistsInZip( pRelativeName ); +} + + +//----------------------------------------------------------------------------- +// Read a file from the pack file +//----------------------------------------------------------------------------- +bool ReadFileFromPak( IZip *pak, const char *pRelativeName, bool bTextMode, CUtlBuffer &buf ) +{ + return pak->ReadFileFromZip( pRelativeName, bTextMode, buf ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Remove file from .bsp PAK lump +// Input : *relativename - +//----------------------------------------------------------------------------- +void RemoveFileFromPak( IZip *pak, const char *relativename ) +{ + pak->RemoveFileFromZip( relativename ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Get next filename in directory +// Input : id, -1 to start, returns next id, or -1 at list conclusion +//----------------------------------------------------------------------------- +int GetNextFilename( IZip *pak, int id, char *pBuffer, int bufferSize, int &fileSize ) +{ + return pak->GetNextFilename( id, pBuffer, bufferSize, fileSize ); +} + +//----------------------------------------------------------------------------- +// Convert four-CC code to a handle + back +//----------------------------------------------------------------------------- +GameLumpHandle_t CGameLump::GetGameLumpHandle( GameLumpId_t id ) +{ + // NOTE: I'm also expecting game lump id's to be four-CC codes + Assert( id > HEADER_LUMPS ); + + FOR_EACH_LL(m_GameLumps, i) + { + if (m_GameLumps[i].m_Id == id) + return i; + } + + return InvalidGameLump(); +} + +GameLumpId_t CGameLump::GetGameLumpId( GameLumpHandle_t handle ) +{ + return m_GameLumps[handle].m_Id; +} + +int CGameLump::GetGameLumpFlags( GameLumpHandle_t handle ) +{ + return m_GameLumps[handle].m_Flags; +} + +int CGameLump::GetGameLumpVersion( GameLumpHandle_t handle ) +{ + return m_GameLumps[handle].m_Version; +} + + +//----------------------------------------------------------------------------- +// Game lump accessor methods +//----------------------------------------------------------------------------- + +void* CGameLump::GetGameLump( GameLumpHandle_t id ) +{ + return m_GameLumps[id].m_Memory.Base(); +} + +int CGameLump::GameLumpSize( GameLumpHandle_t id ) +{ + return m_GameLumps[id].m_Memory.NumAllocated(); +} + + +//----------------------------------------------------------------------------- +// Game lump iteration methods +//----------------------------------------------------------------------------- + +GameLumpHandle_t CGameLump::FirstGameLump() +{ + return (m_GameLumps.Count()) ? m_GameLumps.Head() : InvalidGameLump(); +} + +GameLumpHandle_t CGameLump::NextGameLump( GameLumpHandle_t handle ) +{ + + return (m_GameLumps.IsValidIndex(handle)) ? m_GameLumps.Next(handle) : InvalidGameLump(); +} + +GameLumpHandle_t CGameLump::InvalidGameLump() +{ + return 0xFFFF; +} + + +//----------------------------------------------------------------------------- +// Game lump creation/destruction method +//----------------------------------------------------------------------------- + +GameLumpHandle_t CGameLump::CreateGameLump( GameLumpId_t id, int size, int flags, int version ) +{ + Assert( GetGameLumpHandle(id) == InvalidGameLump() ); + GameLumpHandle_t handle = m_GameLumps.AddToTail(); + m_GameLumps[handle].m_Id = id; + m_GameLumps[handle].m_Flags = flags; + m_GameLumps[handle].m_Version = version; + m_GameLumps[handle].m_Memory.EnsureCapacity( size ); + return handle; +} + +void CGameLump::DestroyGameLump( GameLumpHandle_t handle ) +{ + m_GameLumps.Remove( handle ); +} + +void CGameLump::DestroyAllGameLumps() +{ + m_GameLumps.RemoveAll(); +} + +//----------------------------------------------------------------------------- +// Compute file size and clump count +//----------------------------------------------------------------------------- + +void CGameLump::ComputeGameLumpSizeAndCount( int& size, int& clumpCount ) +{ + // Figure out total size of the client lumps + size = 0; + clumpCount = 0; + GameLumpHandle_t h; + for( h = FirstGameLump(); h != InvalidGameLump(); h = NextGameLump( h ) ) + { + ++clumpCount; + size += GameLumpSize( h ); + } + + // Add on headers + size += sizeof( dgamelumpheader_t ) + clumpCount * sizeof( dgamelump_t ); +} + + +void CGameLump::SwapGameLump( GameLumpId_t id, int version, byte *dest, byte *src, int length ) +{ + int count = 0; + switch( id ) + { + case GAMELUMP_STATIC_PROPS: + // Swap the static prop model dict + count = *(int*)src; + g_Swap.SwapBufferToTargetEndian( (int*)dest, (int*)src ); + count = g_bSwapOnLoad ? *(int*)dest : count; + src += sizeof(int); + dest += sizeof(int); + + g_Swap.SwapFieldsToTargetEndian( (StaticPropDictLump_t*)dest, (StaticPropDictLump_t*)src, count ); + src += sizeof(StaticPropDictLump_t) * count; + dest += sizeof(StaticPropDictLump_t) * count; + + // Swap the leaf list + count = *(int*)src; + g_Swap.SwapBufferToTargetEndian( (int*)dest, (int*)src ); + count = g_bSwapOnLoad ? *(int*)dest : count; + src += sizeof(int); + dest += sizeof(int); + + g_Swap.SwapFieldsToTargetEndian( (StaticPropLeafLump_t*)dest, (StaticPropLeafLump_t*)src, count ); + src += sizeof(StaticPropLeafLump_t) * count; + dest += sizeof(StaticPropLeafLump_t) * count; + + // Swap the models + count = *(int*)src; + g_Swap.SwapBufferToTargetEndian( (int*)dest, (int*)src ); + count = g_bSwapOnLoad ? *(int*)dest : count; + src += sizeof(int); + dest += sizeof(int); + + // The one-at-a-time swap is to compensate for these structures + // possibly being misaligned, which crashes the Xbox 360. + if ( version == 4 ) + { + StaticPropLumpV4_t lump; + for ( int i = 0; i < count; ++i ) + { + Q_memcpy( &lump, src, sizeof(StaticPropLumpV4_t) ); + g_Swap.SwapFieldsToTargetEndian( &lump, &lump ); + Q_memcpy( dest, &lump, sizeof(StaticPropLumpV4_t) ); + src += sizeof( StaticPropLumpV4_t ); + dest += sizeof( StaticPropLumpV4_t ); + } + } + else if ( version == 5 ) + { + StaticPropLumpV5_t lump; + for ( int i = 0; i < count; ++i ) + { + Q_memcpy( &lump, src, sizeof(StaticPropLumpV5_t) ); + g_Swap.SwapFieldsToTargetEndian( &lump, &lump ); + Q_memcpy( dest, &lump, sizeof(StaticPropLumpV5_t) ); + src += sizeof( StaticPropLumpV5_t ); + dest += sizeof( StaticPropLumpV5_t ); + } + } + else + { + if ( version != 6 ) + { + Error( "Unknown Static Prop Lump version %d didn't get swapped!\n", version ); + } + + StaticPropLump_t lump; + for ( int i = 0; i < count; ++i ) + { + Q_memcpy( &lump, src, sizeof(StaticPropLump_t) ); + g_Swap.SwapFieldsToTargetEndian( &lump, &lump ); + Q_memcpy( dest, &lump, sizeof(StaticPropLump_t) ); + src += sizeof( StaticPropLump_t ); + dest += sizeof( StaticPropLump_t ); + } + } + break; + + case GAMELUMP_DETAIL_PROPS: + // Swap the detail prop model dict + count = *(int*)src; + g_Swap.SwapBufferToTargetEndian( (int*)dest, (int*)src ); + count = g_bSwapOnLoad ? *(int*)dest : count; + src += sizeof(int); + dest += sizeof(int); + + g_Swap.SwapFieldsToTargetEndian( (DetailObjectDictLump_t*)dest, (DetailObjectDictLump_t*)src, count ); + src += sizeof(DetailObjectDictLump_t) * count; + dest += sizeof(DetailObjectDictLump_t) * count; + + if ( version == 4 ) + { + // Swap the detail sprite dict + count = *(int*)src; + g_Swap.SwapBufferToTargetEndian( (int*)dest, (int*)src ); + count = g_bSwapOnLoad ? *(int*)dest : count; + src += sizeof(int); + dest += sizeof(int); + + DetailSpriteDictLump_t spritelump; + for ( int i = 0; i < count; ++i ) + { + Q_memcpy( &spritelump, src, sizeof(DetailSpriteDictLump_t) ); + g_Swap.SwapFieldsToTargetEndian( &spritelump, &spritelump ); + Q_memcpy( dest, &spritelump, sizeof(DetailSpriteDictLump_t) ); + src += sizeof(DetailSpriteDictLump_t); + dest += sizeof(DetailSpriteDictLump_t); + } + + // Swap the models + count = *(int*)src; + g_Swap.SwapBufferToTargetEndian( (int*)dest, (int*)src ); + count = g_bSwapOnLoad ? *(int*)dest : count; + src += sizeof(int); + dest += sizeof(int); + + DetailObjectLump_t objectlump; + for ( int i = 0; i < count; ++i ) + { + Q_memcpy( &objectlump, src, sizeof(DetailObjectLump_t) ); + g_Swap.SwapFieldsToTargetEndian( &objectlump, &objectlump ); + Q_memcpy( dest, &objectlump, sizeof(DetailObjectLump_t) ); + src += sizeof(DetailObjectLump_t); + dest += sizeof(DetailObjectLump_t); + } + } + break; + + case GAMELUMP_DETAIL_PROP_LIGHTING: + // Swap the LDR light styles + count = *(int*)src; + g_Swap.SwapBufferToTargetEndian( (int*)dest, (int*)src ); + count = g_bSwapOnLoad ? *(int*)dest : count; + src += sizeof(int); + dest += sizeof(int); + + g_Swap.SwapFieldsToTargetEndian( (DetailPropLightstylesLump_t*)dest, (DetailPropLightstylesLump_t*)src, count ); + src += sizeof(DetailObjectDictLump_t) * count; + dest += sizeof(DetailObjectDictLump_t) * count; + break; + + case GAMELUMP_DETAIL_PROP_LIGHTING_HDR: + // Swap the HDR light styles + count = *(int*)src; + g_Swap.SwapBufferToTargetEndian( (int*)dest, (int*)src ); + count = g_bSwapOnLoad ? *(int*)dest : count; + src += sizeof(int); + dest += sizeof(int); + + g_Swap.SwapFieldsToTargetEndian( (DetailPropLightstylesLump_t*)dest, (DetailPropLightstylesLump_t*)src, count ); + src += sizeof(DetailObjectDictLump_t) * count; + dest += sizeof(DetailObjectDictLump_t) * count; + break; + + default: + char idchars[5] = {0}; + Q_memcpy( idchars, &id, 4 ); + Warning( "Unknown game lump '%s' didn't get swapped!\n", idchars ); + memcpy ( dest, src, length); + break; + } +} + +//----------------------------------------------------------------------------- +// Game lump file I/O +//----------------------------------------------------------------------------- +void CGameLump::ParseGameLump( dheader_t* pHeader ) +{ + g_GameLumps.DestroyAllGameLumps(); + + g_Lumps.bLumpParsed[LUMP_GAME_LUMP] = true; + + int length = pHeader->lumps[LUMP_GAME_LUMP].filelen; + int ofs = pHeader->lumps[LUMP_GAME_LUMP].fileofs; + + if (length > 0) + { + // Read dictionary... + dgamelumpheader_t* pGameLumpHeader = (dgamelumpheader_t*)((byte *)pHeader + ofs); + if ( g_bSwapOnLoad ) + { + g_Swap.SwapFieldsToTargetEndian( pGameLumpHeader ); + } + dgamelump_t* pGameLump = (dgamelump_t*)(pGameLumpHeader + 1); + for (int i = 0; i < pGameLumpHeader->lumpCount; ++i ) + { + if ( g_bSwapOnLoad ) + { + g_Swap.SwapFieldsToTargetEndian( &pGameLump[i] ); + } + + int length = pGameLump[i].filelen; + GameLumpHandle_t lump = g_GameLumps.CreateGameLump( pGameLump[i].id, length, pGameLump[i].flags, pGameLump[i].version ); + if ( g_bSwapOnLoad ) + { + SwapGameLump( pGameLump[i].id, pGameLump[i].version, (byte*)g_GameLumps.GetGameLump(lump), (byte *)pHeader + pGameLump[i].fileofs, length ); + } + else + { + memcpy( g_GameLumps.GetGameLump(lump), (byte *)pHeader + pGameLump[i].fileofs, length ); + } + } + } +} + + +//----------------------------------------------------------------------------- +// String table methods +//----------------------------------------------------------------------------- +const char *TexDataStringTable_GetString( int stringID ) +{ + return &g_TexDataStringData[g_TexDataStringTable[stringID]]; +} + +int TexDataStringTable_AddOrFindString( const char *pString ) +{ + int i; + // garymcthack: Make this use an RBTree! + for( i = 0; i < g_TexDataStringTable.Count(); i++ ) + { + if( stricmp( pString, &g_TexDataStringData[g_TexDataStringTable[i]] ) == 0 ) + { + return i; + } + } + + int len = strlen( pString ); + int outOffset = g_TexDataStringData.AddMultipleToTail( len+1, pString ); + int outIndex = g_TexDataStringTable.AddToTail( outOffset ); + return outIndex; +} + +//----------------------------------------------------------------------------- +// Adds all game lumps into one big block +//----------------------------------------------------------------------------- + +static void AddGameLumps( ) +{ + // Figure out total size of the client lumps + int size, clumpCount; + g_GameLumps.ComputeGameLumpSizeAndCount( size, clumpCount ); + + // Set up the main lump dictionary entry + g_Lumps.size[LUMP_GAME_LUMP] = 0; // mark it written + + lump_t* lump = &g_pBSPHeader->lumps[LUMP_GAME_LUMP]; + + lump->fileofs = g_pFileSystem->Tell( g_hBSPFile ); + lump->filelen = size; + + // write header + dgamelumpheader_t header; + header.lumpCount = clumpCount; + WriteData( &header ); + + // write dictionary + dgamelump_t dict; + int offset = lump->fileofs + sizeof(header) + clumpCount * sizeof(dgamelump_t); + GameLumpHandle_t h; + for( h = g_GameLumps.FirstGameLump(); h != g_GameLumps.InvalidGameLump(); h = g_GameLumps.NextGameLump( h ) ) + { + dict.id = g_GameLumps.GetGameLumpId(h); + dict.version = g_GameLumps.GetGameLumpVersion(h); + dict.flags = g_GameLumps.GetGameLumpFlags(h); + dict.fileofs = offset; + dict.filelen = g_GameLumps.GameLumpSize( h ); + offset += dict.filelen; + + WriteData( &dict ); + } + + // write lumps.. + for( h = g_GameLumps.FirstGameLump(); h != g_GameLumps.InvalidGameLump(); h = g_GameLumps.NextGameLump( h ) ) + { + unsigned int lumpsize = g_GameLumps.GameLumpSize(h); + if ( g_bSwapOnWrite ) + { + g_GameLumps.SwapGameLump( g_GameLumps.GetGameLumpId(h), g_GameLumps.GetGameLumpVersion(h), (byte*)g_GameLumps.GetGameLump(h), (byte*)g_GameLumps.GetGameLump(h), lumpsize ); + } + SafeWrite( g_hBSPFile, g_GameLumps.GetGameLump(h), lumpsize ); + } + + // align to doubleword + AlignFilePosition( g_hBSPFile, 4 ); +} + + +//----------------------------------------------------------------------------- +// Adds the occluder lump... +//----------------------------------------------------------------------------- +static void AddOcclusionLump( ) +{ + g_Lumps.size[LUMP_OCCLUSION] = 0; // mark it written + + int nOccluderCount = g_OccluderData.Count(); + int nOccluderPolyDataCount = g_OccluderPolyData.Count(); + int nOccluderVertexIndices = g_OccluderVertexIndices.Count(); + + int nLumpLength = nOccluderCount * sizeof(doccluderdata_t) + + nOccluderPolyDataCount * sizeof(doccluderpolydata_t) + + nOccluderVertexIndices * sizeof(int) + + 3 * sizeof(int); + + lump_t *lump = &g_pBSPHeader->lumps[LUMP_OCCLUSION]; + + lump->fileofs = g_pFileSystem->Tell( g_hBSPFile ); + lump->filelen = nLumpLength; + lump->version = LUMP_OCCLUSION_VERSION; + lump->fourCC[0] = ( char )0; + lump->fourCC[1] = ( char )0; + lump->fourCC[2] = ( char )0; + lump->fourCC[3] = ( char )0; + + // Data is swapped in place, so the 'Count' variables aren't safe to use after they're written + WriteData( FIELD_INTEGER, &nOccluderCount ); + WriteData( (doccluderdata_t*)g_OccluderData.Base(), g_OccluderData.Count() ); + WriteData( FIELD_INTEGER, &nOccluderPolyDataCount ); + WriteData( (doccluderpolydata_t*)g_OccluderPolyData.Base(), g_OccluderPolyData.Count() ); + WriteData( FIELD_INTEGER, &nOccluderVertexIndices ); + WriteData( FIELD_INTEGER, (int*)g_OccluderVertexIndices.Base(), g_OccluderVertexIndices.Count() ); +} + + +//----------------------------------------------------------------------------- +// Loads the occluder lump... +//----------------------------------------------------------------------------- +static void UnserializeOcclusionLumpV2( CUtlBuffer &buf ) +{ + int nCount = buf.GetInt(); + if ( nCount ) + { + g_OccluderData.SetCount( nCount ); + buf.GetObjects( g_OccluderData.Base(), nCount ); + } + + nCount = buf.GetInt(); + if ( nCount ) + { + g_OccluderPolyData.SetCount( nCount ); + buf.GetObjects( g_OccluderPolyData.Base(), nCount ); + } + + nCount = buf.GetInt(); + if ( nCount ) + { + if ( g_bSwapOnLoad ) + { + g_Swap.SwapBufferToTargetEndian( (int*)buf.PeekGet(), (int*)buf.PeekGet(), nCount ); + } + g_OccluderVertexIndices.SetCount( nCount ); + buf.Get( g_OccluderVertexIndices.Base(), nCount * sizeof(g_OccluderVertexIndices[0]) ); + } +} + + +static void LoadOcclusionLump() +{ + g_OccluderData.RemoveAll(); + g_OccluderPolyData.RemoveAll(); + g_OccluderVertexIndices.RemoveAll(); + + int length, ofs; + + g_Lumps.bLumpParsed[LUMP_OCCLUSION] = true; + + length = g_pBSPHeader->lumps[LUMP_OCCLUSION].filelen; + ofs = g_pBSPHeader->lumps[LUMP_OCCLUSION].fileofs; + + CUtlBuffer buf( (byte *)g_pBSPHeader + ofs, length, CUtlBuffer::READ_ONLY ); + buf.ActivateByteSwapping( g_bSwapOnLoad ); + switch ( g_pBSPHeader->lumps[LUMP_OCCLUSION].version ) + { + case 2: + UnserializeOcclusionLumpV2( buf ); + break; + + case 0: + break; + + default: + Error("Unknown occlusion lump version!\n"); + break; + } +} + + +/* +=============== +CompressVis + +=============== +*/ +int CompressVis (byte *vis, byte *dest) +{ + int j; + int rep; + int visrow; + byte *dest_p; + + dest_p = dest; +// visrow = (r_numvisleafs + 7)>>3; + visrow = (dvis->numclusters + 7)>>3; + + for (j=0 ; j>3; + row = (dvis->numclusters+7)>>3; + out = decompressed; + + do + { + if (*in) + { + *out++ = *in++; + continue; + } + + c = in[1]; + if (!c) + Error ("DecompressVis: 0 repeat"); + in += 2; + if ((out - decompressed) + c > row) + { + c = row - (out - decompressed); + Warning( "warning: Vis decompression overrun\n" ); + } + while (c) + { + *out++ = 0; + c--; + } + } while (out - decompressed < row); +} + +//----------------------------------------------------------------------------- +// Lump-specific swap functions +//----------------------------------------------------------------------------- +struct swapcollideheader_t +{ + DECLARE_BYTESWAP_DATADESC(); + int size; + int vphysicsID; + short version; + short modelType; +}; + +struct swapcompactsurfaceheader_t : swapcollideheader_t +{ + DECLARE_BYTESWAP_DATADESC(); + int surfaceSize; + Vector dragAxisAreas; + int axisMapSize; +}; + +struct swapmoppsurfaceheader_t : swapcollideheader_t +{ + DECLARE_BYTESWAP_DATADESC(); + int moppSize; +}; + +BEGIN_BYTESWAP_DATADESC( swapcollideheader_t ) + DEFINE_FIELD( size, FIELD_INTEGER ), + DEFINE_FIELD( vphysicsID, FIELD_INTEGER ), + DEFINE_FIELD( version, FIELD_SHORT ), + DEFINE_FIELD( modelType, FIELD_SHORT ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC_( swapcompactsurfaceheader_t, swapcollideheader_t ) + DEFINE_FIELD( surfaceSize, FIELD_INTEGER ), + DEFINE_FIELD( dragAxisAreas, FIELD_VECTOR ), + DEFINE_FIELD( axisMapSize, FIELD_INTEGER ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC_( swapmoppsurfaceheader_t, swapcollideheader_t ) + DEFINE_FIELD( moppSize, FIELD_INTEGER ), +END_BYTESWAP_DATADESC() + + +static void SwapPhyscollideLump( byte *pDestBase, byte *pSrcBase, unsigned int &count ) +{ + IPhysicsCollision *physcollision = NULL; + CSysModule *pPhysicsModule = g_pFullFileSystem->LoadModule( "vphysics.dll" ); + if ( pPhysicsModule ) + { + CreateInterfaceFn physicsFactory = Sys_GetFactory( pPhysicsModule ); + if ( physicsFactory ) + { + physcollision = (IPhysicsCollision *)physicsFactory( VPHYSICS_COLLISION_INTERFACE_VERSION, NULL ); + } + } + + if ( !physcollision ) + { + Warning("!!! WARNING: Can't swap the physcollide lump!\n" ); + return; + } + + // physics data is variable length. The last physmodel is a NULL pointer + // with modelIndex -1, dataSize -1 + dphysmodel_t *pPhysModel; + byte *pSrc = pSrcBase; + + // first the src chunks have to be aligned properly + // swap increases size, allocate enough expansion room + byte *pSrcAlignedBase = (byte*)malloc( 2*count ); + byte *basePtr = pSrcAlignedBase; + byte *pSrcAligned = pSrcAlignedBase; + + do + { + if ( g_bSwapOnLoad ) + { + g_Swap.SwapFieldsToTargetEndian( (dphysmodel_t*)pSrcAligned, (dphysmodel_t*)pSrc ); + } + else + { + Q_memcpy( pSrcAligned, pSrc, sizeof(dphysmodel_t) ); + } + pPhysModel = (dphysmodel_t*)pSrcAligned; + + pSrc += sizeof(dphysmodel_t); + pSrcAligned += sizeof(dphysmodel_t); + + if ( pPhysModel->dataSize > 0 ) + { + // Align the collide headers + for ( int i = 0; i < pPhysModel->solidCount; ++i ) + { + // Get data size + int size; + Q_memcpy( &size, pSrc, sizeof(int) ); + if ( g_bSwapOnLoad ) + size = SwapLong( size ); + + // Fixup size + int padBytes = 0; + if ( size % 4 != 0 ) + { + padBytes = ( 4 - size % 4 ); + count += padBytes; + pPhysModel->dataSize += padBytes; + } + + // Copy data and size into alligned buffer + int newsize = size + padBytes; + if ( g_bSwapOnLoad ) + newsize = SwapLong( newsize ); + + Q_memcpy( pSrcAligned, &newsize, sizeof(int) ); + Q_memcpy( pSrcAligned + sizeof(int), pSrc + sizeof(int), size ); + pSrcAligned += size + padBytes + sizeof(int); + pSrc += size + sizeof(int); + } + + int padBytes = 0; + int dataSize = pPhysModel->dataSize + pPhysModel->keydataSize; + Q_memcpy( pSrcAligned, pSrc, pPhysModel->keydataSize ); + pSrc += pPhysModel->keydataSize; + pSrcAligned += pPhysModel->keydataSize; + if ( dataSize % 4 != 0 ) + { + // Next chunk will be unaligned + padBytes = ( 4 - dataSize % 4 ); + pPhysModel->keydataSize += padBytes; + count += padBytes; + Q_memset( pSrcAligned, 0, padBytes ); + pSrcAligned += padBytes; + } + } + } while ( pPhysModel->dataSize > 0 ); + + // Now the data can be swapped properly + pSrcBase = pSrcAlignedBase; + pSrc = pSrcBase; + byte *pDest = pDestBase; + + do + { + // src headers are in native format + pPhysModel = (dphysmodel_t*)pSrc; + if ( g_bSwapOnWrite ) + { + g_Swap.SwapFieldsToTargetEndian( (dphysmodel_t*)pDest, (dphysmodel_t*)pSrc ); + } + else + { + Q_memcpy( pDest, pSrc, sizeof(dphysmodel_t) ); + } + + pSrc += sizeof(dphysmodel_t); + pDest += sizeof(dphysmodel_t); + + pSrcBase = pSrc; + pDestBase = pDest; + + if ( pPhysModel->dataSize > 0 ) + { + vcollide_t collide = {0}; + int dataSize = pPhysModel->dataSize + pPhysModel->keydataSize; + + if ( g_bSwapOnWrite ) + { + // Load the collide data + physcollision->VCollideLoad( &collide, pPhysModel->solidCount, (const char *)pSrc, dataSize, false ); + } + + int *offsets = new int[ pPhysModel->solidCount ]; + + // Swap the collision data headers + for ( int i = 0; i < pPhysModel->solidCount; ++i ) + { + int headerSize = 0; + swapcollideheader_t *baseHdr = (swapcollideheader_t*)pSrc; + short modelType = baseHdr->modelType; + if ( g_bSwapOnLoad ) + { + g_Swap.SwapBufferToTargetEndian( &modelType ); + } + + if ( modelType == 0 ) // COLLIDE_POLY + { + headerSize = sizeof(swapcompactsurfaceheader_t); + swapcompactsurfaceheader_t swapHdr; + Q_memcpy( &swapHdr, pSrc, headerSize ); + g_Swap.SwapFieldsToTargetEndian( &swapHdr, &swapHdr ); + Q_memcpy( pDest, &swapHdr, headerSize ); + } + else if ( modelType == 1 ) // COLLIDE_MOPP + { + // The PC still unserializes these, but we don't support them + if ( g_bSwapOnWrite ) + { + collide.solids[i] = NULL; + } + + headerSize = sizeof(swapmoppsurfaceheader_t); + swapmoppsurfaceheader_t swapHdr; + Q_memcpy( &swapHdr, pSrc, headerSize ); + g_Swap.SwapFieldsToTargetEndian( &swapHdr, &swapHdr ); + Q_memcpy( pDest, &swapHdr, headerSize ); + + } + else + { + // Shouldn't happen + Assert( 0 ); + } + + if ( g_bSwapOnLoad ) + { + // src needs the native header data to load the vcollides + Q_memcpy( pSrc, pDest, headerSize ); + } + // HACK: Need either surfaceSize or moppSize - both sit at the same offset in the structure + swapmoppsurfaceheader_t *hdr = (swapmoppsurfaceheader_t*)pSrc; + pSrc += hdr->size + sizeof(int); + pDest += hdr->size + sizeof(int); + offsets[i] = hdr->size; + } + + pSrc = pSrcBase; + pDest = pDestBase; + if ( g_bSwapOnLoad ) + { + physcollision->VCollideLoad( &collide, pPhysModel->solidCount, (const char *)pSrc, dataSize, true ); + } + + // Write out the ledge tree data + for ( int i = 0; i < pPhysModel->solidCount; ++i ) + { + if ( collide.solids[i] ) + { + // skip over the size member + pSrc += sizeof(int); + pDest += sizeof(int); + int offset = physcollision->CollideWrite( (char*)pDest, collide.solids[i], g_bSwapOnWrite ); + pSrc += offset; + pDest += offset; + } + else + { + pSrc += offsets[i] + sizeof(int); + pDest += offsets[i] + sizeof(int); + } + } + + // copy the keyvalues data + Q_memcpy( pDest, pSrc, pPhysModel->keydataSize ); + pDest += pPhysModel->keydataSize; + pSrc += pPhysModel->keydataSize; + + // Free the memory + physcollision->VCollideUnload( &collide ); + delete [] offsets; + } + + // avoid infinite loop on badly formed file + if ( (pSrc - basePtr) > count ) + break; + + } while ( pPhysModel->dataSize > 0 ); + + free( pSrcAlignedBase ); +} + + +// UNDONE: This code is not yet tested. +static void SwapPhysdispLump( byte *pDest, byte *pSrc, int count ) +{ + // the format of this lump is one unsigned short dispCount, then dispCount unsigned shorts of sizes + // followed by an array of variable length (each element is the length of the corresponding entry in the + // previous table) byte-stream data structure of the displacement collision models + // these byte-stream structs are endian-neutral because each element is byte-sized + unsigned short dispCount = *(unsigned short*)pSrc; + if ( g_bSwapOnLoad ) + { + g_Swap.SwapBufferToTargetEndian( &dispCount ); + } + g_Swap.SwapBufferToTargetEndian( (unsigned short*)pDest, (unsigned short*)pSrc, dispCount + 1 ); + + const int nBytes = (dispCount + 1) * sizeof( unsigned short ); + pSrc += nBytes; + pDest += nBytes; + count -= nBytes; + + g_Swap.SwapBufferToTargetEndian( pDest, pSrc, count ); +} + + +static void SwapVisibilityLump( byte *pDest, byte *pSrc, int count ) +{ + int firstInt = *(int*)pSrc; + if ( g_bSwapOnLoad ) + { + g_Swap.SwapBufferToTargetEndian( &firstInt ); + } + int intCt = firstInt * 2 + 1; + const int hdrSize = intCt * sizeof(int); + g_Swap.SwapBufferToTargetEndian( (int*)pDest, (int*)pSrc, intCt ); + g_Swap.SwapBufferToTargetEndian( pDest + hdrSize, pSrc + hdrSize, count - hdrSize ); +} + +//============================================================================= +void Lumps_Init( void ) +{ + memset( &g_Lumps, 0, sizeof(g_Lumps) ); +} + +int LumpVersion( int lump ) +{ + return g_pBSPHeader->lumps[lump].version; +} + +bool HasLump( int lump ) +{ + return g_pBSPHeader->lumps[lump].filelen > 0; +} + +void ValidateLump( int lump, int length, int size, int forceVersion ) +{ + if ( length % size ) + { + Error( "ValidateLump: odd size for lump %d", lump ); + } + + if ( forceVersion >= 0 && forceVersion != g_pBSPHeader->lumps[lump].version ) + { + Error( "ValidateLump: old version for lump %d in map!", lump ); + } +} + +//----------------------------------------------------------------------------- +// Add Lumps of integral types without datadescs +//----------------------------------------------------------------------------- +template< class T > +int CopyLumpInternal( int fieldType, int lump, T *dest, int forceVersion ) +{ + g_Lumps.bLumpParsed[lump] = true; + + // Vectors are passed in as floats + int fieldSize = ( fieldType == FIELD_VECTOR ) ? sizeof(Vector) : sizeof(T); + unsigned int length = g_pBSPHeader->lumps[lump].filelen; + unsigned int ofs = g_pBSPHeader->lumps[lump].fileofs; + + // count must be of the integral type + unsigned int count = length / sizeof(T); + + ValidateLump( lump, length, fieldSize, forceVersion ); + + if ( g_bSwapOnLoad ) + { + switch( lump ) + { + case LUMP_VISIBILITY: + SwapVisibilityLump( (byte*)dest, ((byte*)g_pBSPHeader + ofs), count ); + break; + + case LUMP_PHYSCOLLIDE: + // SwapPhyscollideLump may change size + SwapPhyscollideLump( (byte*)dest, ((byte*)g_pBSPHeader + ofs), count ); + length = count; + break; + + case LUMP_PHYSDISP: + SwapPhysdispLump( (byte*)dest, ((byte*)g_pBSPHeader + ofs), count ); + break; + + default: + g_Swap.SwapBufferToTargetEndian( dest, (T*)((byte*)g_pBSPHeader + ofs), count ); + break; + } + } + else + { + memcpy( dest, (byte*)g_pBSPHeader + ofs, length ); + } + + // Return actual count of elements + return length / fieldSize; +} + +template< class T > +int CopyLump( int fieldType, int lump, T *dest, int forceVersion = -1 ) +{ + return CopyLumpInternal( fieldType, lump, dest, forceVersion ); +} + +template< class T > +void CopyLump( int fieldType, int lump, CUtlVector &dest, int forceVersion = -1 ) +{ + Assert( fieldType != FIELD_VECTOR ); // TODO: Support this if necessary + dest.SetSize( g_pBSPHeader->lumps[lump].filelen / sizeof(T) ); + CopyLumpInternal( fieldType, lump, dest.Base(), forceVersion ); +} + +template< class T > +void CopyOptionalLump( int fieldType, int lump, CUtlVector &dest, int forceVersion = -1 ) +{ + // not fatal if not present + if ( !HasLump( lump ) ) + return; + + dest.SetSize( g_pBSPHeader->lumps[lump].filelen / sizeof(T) ); + CopyLumpInternal( fieldType, lump, dest.Base(), forceVersion ); +} + +template< class T > +int CopyVariableLump( int fieldType, int lump, void **dest, int forceVersion = -1 ) +{ + int length = g_pBSPHeader->lumps[lump].filelen; + *dest = malloc( length ); + + return CopyLumpInternal( fieldType, lump, (T*)*dest, forceVersion ); +} + +//----------------------------------------------------------------------------- +// Add Lumps of object types with datadescs +//----------------------------------------------------------------------------- +template< class T > +int CopyLumpInternal( int lump, T *dest, int forceVersion ) +{ + g_Lumps.bLumpParsed[lump] = true; + + unsigned int length = g_pBSPHeader->lumps[lump].filelen; + unsigned int ofs = g_pBSPHeader->lumps[lump].fileofs; + unsigned int count = length / sizeof(T); + + ValidateLump( lump, length, sizeof(T), forceVersion ); + + if ( g_bSwapOnLoad ) + { + g_Swap.SwapFieldsToTargetEndian( dest, (T*)((byte*)g_pBSPHeader + ofs), count ); + } + else + { + memcpy( dest, (byte*)g_pBSPHeader + ofs, length ); + } + + return count; +} + +template< class T > +int CopyLump( int lump, T *dest, int forceVersion = -1 ) +{ + return CopyLumpInternal( lump, dest, forceVersion ); +} + +template< class T > +void CopyLump( int lump, CUtlVector &dest, int forceVersion = -1 ) +{ + dest.SetSize( g_pBSPHeader->lumps[lump].filelen / sizeof(T) ); + CopyLumpInternal( lump, dest.Base(), forceVersion ); +} + +template< class T > +void CopyOptionalLump( int lump, CUtlVector &dest, int forceVersion = -1 ) +{ + // not fatal if not present + if ( !HasLump( lump ) ) + return; + + dest.SetSize( g_pBSPHeader->lumps[lump].filelen / sizeof(T) ); + CopyLumpInternal( lump, dest.Base(), forceVersion ); +} + +template< class T > +int CopyVariableLump( int lump, void **dest, int forceVersion = -1 ) +{ + int length = g_pBSPHeader->lumps[lump].filelen; + *dest = malloc( length ); + + return CopyLumpInternal( lump, (T*)*dest, forceVersion ); +} + +//----------------------------------------------------------------------------- +// Add/Write unknown lumps +//----------------------------------------------------------------------------- +void Lumps_Parse( void ) +{ + int i; + + for ( i = 0; i < HEADER_LUMPS; i++ ) + { + if ( !g_Lumps.bLumpParsed[i] && g_pBSPHeader->lumps[i].filelen ) + { + g_Lumps.size[i] = CopyVariableLump( FIELD_CHARACTER, i, &g_Lumps.pLumps[i], -1 ); + Msg( "Reading unknown lump #%d (%d bytes)\n", i, g_Lumps.size[i] ); + } + } +} + +void Lumps_Write( void ) +{ + int i; + + for ( i = 0; i < HEADER_LUMPS; i++ ) + { + if ( g_Lumps.size[i] ) + { + Msg( "Writing unknown lump #%d (%d bytes)\n", i, g_Lumps.size[i] ); + AddLump( i, (byte*)g_Lumps.pLumps[i], g_Lumps.size[i] ); + } + if ( g_Lumps.pLumps[i] ) + { + free( g_Lumps.pLumps[i] ); + g_Lumps.pLumps[i] = NULL; + } + } +} + +int LoadLeafs( void ) +{ +#if defined( BSP_USE_LESS_MEMORY ) + dleafs = (dleaf_t*)malloc( g_pBSPHeader->lumps[LUMP_LEAFS].filelen ); +#endif + + switch ( LumpVersion( LUMP_LEAFS ) ) + { + case 0: + { + g_Lumps.bLumpParsed[LUMP_LEAFS] = true; + int length = g_pBSPHeader->lumps[LUMP_LEAFS].filelen; + int size = sizeof( dleaf_version_0_t ); + if ( length % size ) + { + Error( "odd size for LUMP_LEAFS\n" ); + } + int count = length / size; + + void *pSrcBase = ( ( byte * )g_pBSPHeader + g_pBSPHeader->lumps[LUMP_LEAFS].fileofs ); + dleaf_version_0_t *pSrc = (dleaf_version_0_t *)pSrcBase; + dleaf_t *pDst = dleafs; + + // version 0 predates HDR, build the LDR + g_LeafAmbientLightingLDR.SetCount( count ); + g_LeafAmbientIndexLDR.SetCount( count ); + + dleafambientlighting_t *pDstLeafAmbientLighting = &g_LeafAmbientLightingLDR[0]; + for ( int i = 0; i < count; i++ ) + { + g_LeafAmbientIndexLDR[i].ambientSampleCount = 1; + g_LeafAmbientIndexLDR[i].firstAmbientSample = i; + + if ( g_bSwapOnLoad ) + { + g_Swap.SwapFieldsToTargetEndian( pSrc ); + } + // pDst is a subset of pSrc; + *pDst = *( ( dleaf_t * )( void * )pSrc ); + pDstLeafAmbientLighting->cube = pSrc->m_AmbientLighting; + pDstLeafAmbientLighting->x = pDstLeafAmbientLighting->y = pDstLeafAmbientLighting->z = pDstLeafAmbientLighting->pad = 0; + pDst++; + pSrc++; + pDstLeafAmbientLighting++; + } + return count; + } + + case 1: + return CopyLump( LUMP_LEAFS, dleafs ); + + default: + Assert( 0 ); + Error( "Unknown LUMP_LEAFS version\n" ); + return 0; + } +} + +void LoadLeafAmbientLighting( int numLeafs ) +{ + if ( LumpVersion( LUMP_LEAFS ) == 0 ) + { + // an older leaf version already built the LDR ambient lighting on load + return; + } + + // old BSP with ambient, or new BSP with no lighting, convert ambient light to new format or create dummy ambient + if ( !HasLump( LUMP_LEAF_AMBIENT_INDEX ) ) + { + // a bunch of legacy maps, have these lumps with garbage versions + // expect them to be NOT the current version + if ( HasLump(LUMP_LEAF_AMBIENT_LIGHTING) ) + { + Assert( LumpVersion( LUMP_LEAF_AMBIENT_LIGHTING ) != LUMP_LEAF_AMBIENT_LIGHTING_VERSION ); + } + if ( HasLump(LUMP_LEAF_AMBIENT_LIGHTING_HDR) ) + { + Assert( LumpVersion( LUMP_LEAF_AMBIENT_LIGHTING_HDR ) != LUMP_LEAF_AMBIENT_LIGHTING_VERSION ); + } + + void *pSrcBase = ( ( byte * )g_pBSPHeader + g_pBSPHeader->lumps[LUMP_LEAF_AMBIENT_LIGHTING].fileofs ); + CompressedLightCube *pSrc = NULL; + if ( HasLump( LUMP_LEAF_AMBIENT_LIGHTING ) ) + { + pSrc = (CompressedLightCube*)pSrcBase; + } + g_LeafAmbientIndexLDR.SetCount( numLeafs ); + g_LeafAmbientLightingLDR.SetCount( numLeafs ); + + void *pSrcBaseHDR = ( ( byte * )g_pBSPHeader + g_pBSPHeader->lumps[LUMP_LEAF_AMBIENT_LIGHTING_HDR].fileofs ); + CompressedLightCube *pSrcHDR = NULL; + if ( HasLump( LUMP_LEAF_AMBIENT_LIGHTING_HDR ) ) + { + pSrcHDR = (CompressedLightCube*)pSrcBaseHDR; + } + g_LeafAmbientIndexHDR.SetCount( numLeafs ); + g_LeafAmbientLightingHDR.SetCount( numLeafs ); + + for ( int i = 0; i < numLeafs; i++ ) + { + g_LeafAmbientIndexLDR[i].ambientSampleCount = 1; + g_LeafAmbientIndexLDR[i].firstAmbientSample = i; + g_LeafAmbientIndexHDR[i].ambientSampleCount = 1; + g_LeafAmbientIndexHDR[i].firstAmbientSample = i; + + Q_memset( &g_LeafAmbientLightingLDR[i], 0, sizeof(g_LeafAmbientLightingLDR[i]) ); + Q_memset( &g_LeafAmbientLightingHDR[i], 0, sizeof(g_LeafAmbientLightingHDR[i]) ); + + if ( pSrc ) + { + if ( g_bSwapOnLoad ) + { + g_Swap.SwapFieldsToTargetEndian( &pSrc[i] ); + } + g_LeafAmbientLightingLDR[i].cube = pSrc[i]; + } + if ( pSrcHDR ) + { + if ( g_bSwapOnLoad ) + { + g_Swap.SwapFieldsToTargetEndian( &pSrcHDR[i] ); + } + g_LeafAmbientLightingHDR[i].cube = pSrcHDR[i]; + } + } + + g_Lumps.bLumpParsed[LUMP_LEAF_AMBIENT_LIGHTING] = true; + g_Lumps.bLumpParsed[LUMP_LEAF_AMBIENT_INDEX] = true; + g_Lumps.bLumpParsed[LUMP_LEAF_AMBIENT_LIGHTING_HDR] = true; + g_Lumps.bLumpParsed[LUMP_LEAF_AMBIENT_INDEX_HDR] = true; + } + else + { + CopyOptionalLump( LUMP_LEAF_AMBIENT_LIGHTING, g_LeafAmbientLightingLDR ); + CopyOptionalLump( LUMP_LEAF_AMBIENT_INDEX, g_LeafAmbientIndexLDR ); + CopyOptionalLump( LUMP_LEAF_AMBIENT_LIGHTING_HDR, g_LeafAmbientLightingHDR ); + CopyOptionalLump( LUMP_LEAF_AMBIENT_INDEX_HDR, g_LeafAmbientIndexHDR ); + } +} + +void ValidateHeader( const char *filename, const dheader_t *pHeader ) +{ + if ( pHeader->ident != IDBSPHEADER ) + { + Error ("%s is not a IBSP file", filename); + } + if ( pHeader->version < MINBSPVERSION || pHeader->version > BSPVERSION ) + { + Error ("%s is version %i, not %i", filename, pHeader->version, BSPVERSION); + } +} + +//----------------------------------------------------------------------------- +// Low level BSP opener for external parsing. Parses headers, but nothing else. +// You must close the BSP, via CloseBSPFile(). +//----------------------------------------------------------------------------- +void OpenBSPFile( const char *filename ) +{ + Lumps_Init(); + + // load the file header + LoadFile( filename, (void **)&g_pBSPHeader ); + + if ( g_bSwapOnLoad ) + { + g_Swap.ActivateByteSwapping( true ); + g_Swap.SwapFieldsToTargetEndian( g_pBSPHeader ); + } + + ValidateHeader( filename, g_pBSPHeader ); + + g_MapRevision = g_pBSPHeader->mapRevision; +} + +//----------------------------------------------------------------------------- +// CloseBSPFile +//----------------------------------------------------------------------------- +void CloseBSPFile( void ) +{ + free( g_pBSPHeader ); + g_pBSPHeader = NULL; +} + +//----------------------------------------------------------------------------- +// LoadBSPFile +//----------------------------------------------------------------------------- +void LoadBSPFile( const char *filename ) +{ + OpenBSPFile( filename ); + + nummodels = CopyLump( LUMP_MODELS, dmodels ); + numvertexes = CopyLump( LUMP_VERTEXES, dvertexes ); + numplanes = CopyLump( LUMP_PLANES, dplanes ); + numleafs = LoadLeafs(); + numnodes = CopyLump( LUMP_NODES, dnodes ); + CopyLump( LUMP_TEXINFO, texinfo ); + numtexdata = CopyLump( LUMP_TEXDATA, dtexdata ); + + CopyLump( LUMP_DISPINFO, g_dispinfo ); + CopyLump( LUMP_DISP_VERTS, g_DispVerts ); + CopyLump( LUMP_DISP_TRIS, g_DispTris ); + CopyLump( FIELD_CHARACTER, LUMP_DISP_LIGHTMAP_SAMPLE_POSITIONS, g_DispLightmapSamplePositions ); + CopyLump( LUMP_FACE_MACRO_TEXTURE_INFO, g_FaceMacroTextureInfos ); + + numfaces = CopyLump(LUMP_FACES, dfaces, LUMP_FACES_VERSION); + if ( HasLump( LUMP_FACES_HDR ) ) + numfaces_hdr = CopyLump( LUMP_FACES_HDR, dfaces_hdr, LUMP_FACES_VERSION ); + else + numfaces_hdr = 0; + + CopyOptionalLump( LUMP_FACEIDS, dfaceids ); + + g_numprimitives = CopyLump( LUMP_PRIMITIVES, g_primitives ); + g_numprimverts = CopyLump( LUMP_PRIMVERTS, g_primverts ); + g_numprimindices = CopyLump( FIELD_SHORT, LUMP_PRIMINDICES, g_primindices ); + numorigfaces = CopyLump( LUMP_ORIGINALFACES, dorigfaces ); // original faces + numleaffaces = CopyLump( FIELD_SHORT, LUMP_LEAFFACES, dleaffaces ); + numleafbrushes = CopyLump( FIELD_SHORT, LUMP_LEAFBRUSHES, dleafbrushes ); + numsurfedges = CopyLump( FIELD_INTEGER, LUMP_SURFEDGES, dsurfedges ); + numedges = CopyLump( LUMP_EDGES, dedges ); + numbrushes = CopyLump( LUMP_BRUSHES, dbrushes ); + numbrushsides = CopyLump( LUMP_BRUSHSIDES, dbrushsides ); + numareas = CopyLump( LUMP_AREAS, dareas ); + numareaportals = CopyLump( LUMP_AREAPORTALS, dareaportals ); + + visdatasize = CopyLump ( FIELD_CHARACTER, LUMP_VISIBILITY, dvisdata ); + CopyOptionalLump( FIELD_CHARACTER, LUMP_LIGHTING, dlightdataLDR, LUMP_LIGHTING_VERSION ); + CopyOptionalLump( FIELD_CHARACTER, LUMP_LIGHTING_HDR, dlightdataHDR, LUMP_LIGHTING_VERSION ); + + LoadLeafAmbientLighting( numleafs ); + + CopyLump( FIELD_CHARACTER, LUMP_ENTITIES, dentdata ); + numworldlightsLDR = CopyLump( LUMP_WORLDLIGHTS, dworldlightsLDR ); + numworldlightsHDR = CopyLump( LUMP_WORLDLIGHTS_HDR, dworldlightsHDR ); + + numleafwaterdata = CopyLump( LUMP_LEAFWATERDATA, dleafwaterdata ); + g_PhysCollideSize = CopyVariableLump( FIELD_CHARACTER, LUMP_PHYSCOLLIDE, (void**)&g_pPhysCollide ); + g_PhysDispSize = CopyVariableLump( FIELD_CHARACTER, LUMP_PHYSDISP, (void**)&g_pPhysDisp ); + + g_numvertnormals = CopyLump( FIELD_VECTOR, LUMP_VERTNORMALS, (float*)g_vertnormals ); + g_numvertnormalindices = CopyLump( FIELD_SHORT, LUMP_VERTNORMALINDICES, g_vertnormalindices ); + + g_nClipPortalVerts = CopyLump( FIELD_VECTOR, LUMP_CLIPPORTALVERTS, (float*)g_ClipPortalVerts ); + g_nCubemapSamples = CopyLump( LUMP_CUBEMAPS, g_CubemapSamples ); + + CopyLump( FIELD_CHARACTER, LUMP_TEXDATA_STRING_DATA, g_TexDataStringData ); + CopyLump( FIELD_INTEGER, LUMP_TEXDATA_STRING_TABLE, g_TexDataStringTable ); + + g_nOverlayCount = CopyLump( LUMP_OVERLAYS, g_Overlays ); + g_nWaterOverlayCount = CopyLump( LUMP_WATEROVERLAYS, g_WaterOverlays ); + CopyLump( LUMP_OVERLAY_FADES, g_OverlayFades ); + + dflagslump_t flags_lump; + + if ( HasLump( LUMP_MAP_FLAGS ) ) + CopyLump ( LUMP_MAP_FLAGS, &flags_lump ); + else + memset( &flags_lump, 0, sizeof( flags_lump ) ); // default flags to 0 + + g_LevelFlags = flags_lump.m_LevelFlags; + + LoadOcclusionLump(); + + CopyLump( FIELD_SHORT, LUMP_LEAFMINDISTTOWATER, g_LeafMinDistToWater ); + + /* + int crap; + for( crap = 0; crap < g_nBSPStringTable; crap++ ) + { + Msg( "stringtable %d", ( int )crap ); + Msg( " %d:", ( int )g_BSPStringTable[crap] ); + puts( &g_BSPStringData[g_BSPStringTable[crap]] ); + puts( "\n" ); + } + */ + + // Load PAK file lump into appropriate data structure + byte *pakbuffer = NULL; + int paksize = CopyVariableLump( FIELD_CHARACTER, LUMP_PAKFILE, ( void ** )&pakbuffer ); + if ( paksize > 0 ) + { + GetPakFile()->ActivateByteSwapping( IsX360() ); + GetPakFile()->ParseFromBuffer( pakbuffer, paksize ); + } + else + { + GetPakFile()->Reset(); + } + + free( pakbuffer ); + + g_GameLumps.ParseGameLump( g_pBSPHeader ); + + // NOTE: Do NOT call CopyLump after Lumps_Parse() it parses all un-Copied lumps + // parse any additional lumps + Lumps_Parse(); + + // everything has been copied out + CloseBSPFile(); + + g_Swap.ActivateByteSwapping( false ); +} + +//----------------------------------------------------------------------------- +// Reset any state. +//----------------------------------------------------------------------------- +void UnloadBSPFile() +{ + nummodels = 0; + numvertexes = 0; + numplanes = 0; + + numleafs = 0; +#if defined( BSP_USE_LESS_MEMORY ) + if ( dleafs ) + { + free( dleafs ); + dleafs = NULL; + } +#endif + + numnodes = 0; + texinfo.Purge(); + numtexdata = 0; + + g_dispinfo.Purge(); + g_DispVerts.Purge(); + g_DispTris.Purge(); + + g_DispLightmapSamplePositions.Purge(); + g_FaceMacroTextureInfos.Purge(); + + numfaces = 0; + numfaces_hdr = 0; + + dfaceids.Purge(); + + g_numprimitives = 0; + g_numprimverts = 0; + g_numprimindices = 0; + numorigfaces = 0; + numleaffaces = 0; + numleafbrushes = 0; + numsurfedges = 0; + numedges = 0; + numbrushes = 0; + numbrushsides = 0; + numareas = 0; + numareaportals = 0; + + visdatasize = 0; + dlightdataLDR.Purge(); + dlightdataHDR.Purge(); + + g_LeafAmbientLightingLDR.Purge(); + g_LeafAmbientLightingHDR.Purge(); + g_LeafAmbientIndexHDR.Purge(); + g_LeafAmbientIndexLDR.Purge(); + + dentdata.Purge(); + numworldlightsLDR = 0; + numworldlightsHDR = 0; + + numleafwaterdata = 0; + + if ( g_pPhysCollide ) + { + free( g_pPhysCollide ); + g_pPhysCollide = NULL; + } + g_PhysCollideSize = 0; + + if ( g_pPhysDisp ) + { + free( g_pPhysDisp ); + g_pPhysDisp = NULL; + } + g_PhysDispSize = 0; + + g_numvertnormals = 0; + g_numvertnormalindices = 0; + + g_nClipPortalVerts = 0; + g_nCubemapSamples = 0; + + g_TexDataStringData.Purge(); + g_TexDataStringTable.Purge(); + + g_nOverlayCount = 0; + g_nWaterOverlayCount = 0; + + g_LevelFlags = 0; + + g_OccluderData.Purge(); + g_OccluderPolyData.Purge(); + g_OccluderVertexIndices.Purge(); + + g_GameLumps.DestroyAllGameLumps(); + + for ( int i = 0; i < HEADER_LUMPS; i++ ) + { + if ( g_Lumps.pLumps[i] ) + { + free( g_Lumps.pLumps[i] ); + g_Lumps.pLumps[i] = NULL; + } + } + + ReleasePakFileLumps(); +} + +//----------------------------------------------------------------------------- +// LoadBSPFileFilesystemOnly +//----------------------------------------------------------------------------- +void LoadBSPFile_FileSystemOnly( const char *filename ) +{ + Lumps_Init(); + + // + // load the file header + // + LoadFile( filename, (void **)&g_pBSPHeader ); + + ValidateHeader( filename, g_pBSPHeader ); + + // Load PAK file lump into appropriate data structure + byte *pakbuffer = NULL; + int paksize = CopyVariableLump( FIELD_CHARACTER, LUMP_PAKFILE, ( void ** )&pakbuffer, 1 ); + if ( paksize > 0 ) + { + GetPakFile()->ParseFromBuffer( pakbuffer, paksize ); + } + else + { + GetPakFile()->Reset(); + } + + free( pakbuffer ); + + // everything has been copied out + free( g_pBSPHeader ); + g_pBSPHeader = NULL; +} + +void ExtractZipFileFromBSP( char *pBSPFileName, char *pZipFileName ) +{ + Lumps_Init(); + + // + // load the file header + // + LoadFile( pBSPFileName, (void **)&g_pBSPHeader); + + ValidateHeader( pBSPFileName, g_pBSPHeader ); + + byte *pakbuffer = NULL; + int paksize = CopyVariableLump( FIELD_CHARACTER, LUMP_PAKFILE, ( void ** )&pakbuffer ); + if ( paksize > 0 ) + { + FILE *fp; + fp = fopen( pZipFileName, "wb" ); + if( !fp ) + { + fprintf( stderr, "can't open %s\n", pZipFileName ); + return; + } + + fwrite( pakbuffer, paksize, 1, fp ); + fclose( fp ); + } + else + { + fprintf( stderr, "zip file is zero length!\n" ); + } +} + +/* +============= +LoadBSPFileTexinfo + +Only loads the texinfo lump, so qdata can scan for textures +============= +*/ +void LoadBSPFileTexinfo( const char *filename ) +{ + FILE *f; + int length, ofs; + + g_pBSPHeader = (dheader_t*)malloc( sizeof(dheader_t) ); + + f = fopen( filename, "rb" ); + fread( g_pBSPHeader, sizeof(dheader_t), 1, f); + + ValidateHeader( filename, g_pBSPHeader ); + + length = g_pBSPHeader->lumps[LUMP_TEXINFO].filelen; + ofs = g_pBSPHeader->lumps[LUMP_TEXINFO].fileofs; + + int nCount = length / sizeof(texinfo_t); + + texinfo.Purge(); + texinfo.AddMultipleToTail( nCount ); + + fseek( f, ofs, SEEK_SET ); + fread( texinfo.Base(), length, 1, f ); + fclose( f ); + + // everything has been copied out + free( g_pBSPHeader ); + g_pBSPHeader = NULL; +} + +static void AddLumpInternal( int lumpnum, void *data, int len, int version ) +{ + lump_t *lump; + + g_Lumps.size[lumpnum] = 0; // mark it written + + lump = &g_pBSPHeader->lumps[lumpnum]; + + lump->fileofs = g_pFileSystem->Tell( g_hBSPFile ); + lump->filelen = len; + lump->version = version; + lump->fourCC[0] = ( char )0; + lump->fourCC[1] = ( char )0; + lump->fourCC[2] = ( char )0; + lump->fourCC[3] = ( char )0; + + SafeWrite( g_hBSPFile, data, len ); + + // pad out to the next dword + AlignFilePosition( g_hBSPFile, 4 ); +} + +template< class T > +static void SwapInPlace( T *pData, int count ) +{ + if ( !pData ) + return; + + // use the datadesc to swap the fields in place + g_Swap.SwapFieldsToTargetEndian( (T*)pData, pData, count ); +} + +template< class T > +static void SwapInPlace( int fieldType, T *pData, int count ) +{ + if ( !pData ) + return; + + // swap the data in place + g_Swap.SwapBufferToTargetEndian( (T*)pData, (T*)pData, count ); +} + +//----------------------------------------------------------------------------- +// Add raw data chunk to file (not a lump) +//----------------------------------------------------------------------------- +template< class T > +static void WriteData( int fieldType, T *pData, int count ) +{ + if ( g_bSwapOnWrite ) + { + SwapInPlace( fieldType, pData, count ); + } + SafeWrite( g_hBSPFile, pData, count * sizeof(T) ); +} + +template< class T > +static void WriteData( T *pData, int count ) +{ + if ( g_bSwapOnWrite ) + { + SwapInPlace( pData, count ); + } + SafeWrite( g_hBSPFile, pData, count * sizeof(T) ); +} + +//----------------------------------------------------------------------------- +// Add Lump of object types with datadescs +//----------------------------------------------------------------------------- +template< class T > +static void AddLump( int lumpnum, T *pData, int count, int version ) +{ + AddLumpInternal( lumpnum, pData, count * sizeof(T), version ); +} + +template< class T > +static void AddLump( int lumpnum, CUtlVector &data, int version ) +{ + AddLumpInternal( lumpnum, data.Base(), data.Count() * sizeof(T), version ); +} + +/* +============= +WriteBSPFile + +Swaps the bsp file in place, so it should not be referenced again +============= +*/ +void WriteBSPFile( const char *filename, char *pUnused ) +{ + if ( texinfo.Count() > MAX_MAP_TEXINFO ) + { + Error( "Map has too many texinfos (has %d, can have at most %d)\n", texinfo.Count(), MAX_MAP_TEXINFO ); + return; + } + + dheader_t outHeader; + g_pBSPHeader = &outHeader; + memset( g_pBSPHeader, 0, sizeof( dheader_t ) ); + + g_pBSPHeader->ident = IDBSPHEADER; + g_pBSPHeader->version = BSPVERSION; + g_pBSPHeader->mapRevision = g_MapRevision; + + g_hBSPFile = SafeOpenWrite( filename ); + WriteData( g_pBSPHeader ); // overwritten later + + AddLump( LUMP_PLANES, dplanes, numplanes ); + AddLump( LUMP_LEAFS, dleafs, numleafs, LUMP_LEAFS_VERSION ); + AddLump( LUMP_LEAF_AMBIENT_LIGHTING, g_LeafAmbientLightingLDR, LUMP_LEAF_AMBIENT_LIGHTING_VERSION ); + AddLump( LUMP_LEAF_AMBIENT_INDEX, g_LeafAmbientIndexLDR ); + AddLump( LUMP_LEAF_AMBIENT_INDEX_HDR, g_LeafAmbientIndexHDR ); + AddLump( LUMP_LEAF_AMBIENT_LIGHTING_HDR, g_LeafAmbientLightingHDR, LUMP_LEAF_AMBIENT_LIGHTING_VERSION ); + + AddLump( LUMP_VERTEXES, dvertexes, numvertexes ); + AddLump( LUMP_NODES, dnodes, numnodes ); + AddLump( LUMP_TEXINFO, texinfo ); + AddLump( LUMP_TEXDATA, dtexdata, numtexdata ); + + AddLump( LUMP_DISPINFO, g_dispinfo ); + AddLump( LUMP_DISP_VERTS, g_DispVerts ); + AddLump( LUMP_DISP_TRIS, g_DispTris ); + AddLump( LUMP_DISP_LIGHTMAP_SAMPLE_POSITIONS, g_DispLightmapSamplePositions ); + AddLump( LUMP_FACE_MACRO_TEXTURE_INFO, g_FaceMacroTextureInfos ); + + AddLump( LUMP_PRIMITIVES, g_primitives, g_numprimitives ); + AddLump( LUMP_PRIMVERTS, g_primverts, g_numprimverts ); + AddLump( LUMP_PRIMINDICES, g_primindices, g_numprimindices ); + AddLump( LUMP_FACES, dfaces, numfaces, LUMP_FACES_VERSION ); + if (numfaces_hdr) + AddLump( LUMP_FACES_HDR, dfaces_hdr, numfaces_hdr, LUMP_FACES_VERSION ); + AddLump ( LUMP_FACEIDS, dfaceids, numfaceids ); + + AddLump( LUMP_ORIGINALFACES, dorigfaces, numorigfaces ); // original faces lump + AddLump( LUMP_BRUSHES, dbrushes, numbrushes ); + AddLump( LUMP_BRUSHSIDES, dbrushsides, numbrushsides ); + AddLump( LUMP_LEAFFACES, dleaffaces, numleaffaces ); + AddLump( LUMP_LEAFBRUSHES, dleafbrushes, numleafbrushes ); + AddLump( LUMP_SURFEDGES, dsurfedges, numsurfedges ); + AddLump( LUMP_EDGES, dedges, numedges ); + AddLump( LUMP_MODELS, dmodels, nummodels ); + AddLump( LUMP_AREAS, dareas, numareas ); + AddLump( LUMP_AREAPORTALS, dareaportals, numareaportals ); + + AddLump( LUMP_LIGHTING, dlightdataLDR, LUMP_LIGHTING_VERSION ); + AddLump( LUMP_LIGHTING_HDR, dlightdataHDR, LUMP_LIGHTING_VERSION ); + AddLump( LUMP_VISIBILITY, dvisdata, visdatasize ); + AddLump( LUMP_ENTITIES, dentdata ); + AddLump( LUMP_WORLDLIGHTS, dworldlightsLDR, numworldlightsLDR ); + AddLump( LUMP_WORLDLIGHTS_HDR, dworldlightsHDR, numworldlightsHDR ); + AddLump( LUMP_LEAFWATERDATA, dleafwaterdata, numleafwaterdata ); + + AddOcclusionLump(); + + dflagslump_t flags_lump; + flags_lump.m_LevelFlags = g_LevelFlags; + AddLump( LUMP_MAP_FLAGS, &flags_lump, 1 ); + + // NOTE: This is just for debugging, so it is disabled in release maps +#if 0 + // add the vis portals to the BSP for visualization + AddLump( LUMP_PORTALS, dportals, numportals ); + AddLump( LUMP_CLUSTERS, dclusters, numclusters ); + AddLump( LUMP_PORTALVERTS, dportalverts, numportalverts ); + AddLump( LUMP_CLUSTERPORTALS, dclusterportals, numclusterportals ); +#endif + + AddLump( LUMP_CLIPPORTALVERTS, (float*)g_ClipPortalVerts, g_nClipPortalVerts * 3 ); + AddLump( LUMP_CUBEMAPS, g_CubemapSamples, g_nCubemapSamples ); + AddLump( LUMP_TEXDATA_STRING_DATA, g_TexDataStringData ); + AddLump( LUMP_TEXDATA_STRING_TABLE, g_TexDataStringTable ); + AddLump( LUMP_OVERLAYS, g_Overlays, g_nOverlayCount ); + AddLump( LUMP_WATEROVERLAYS, g_WaterOverlays, g_nWaterOverlayCount ); + AddLump( LUMP_OVERLAY_FADES, g_OverlayFades, g_nOverlayCount ); + + if ( g_pPhysCollide ) + { + AddLump( LUMP_PHYSCOLLIDE, g_pPhysCollide, g_PhysCollideSize ); + } + + if ( g_pPhysDisp ) + { + AddLump ( LUMP_PHYSDISP, g_pPhysDisp, g_PhysDispSize ); + } + + AddLump( LUMP_VERTNORMALS, (float*)g_vertnormals, g_numvertnormals * 3 ); + AddLump( LUMP_VERTNORMALINDICES, g_vertnormalindices, g_numvertnormalindices ); + + AddLump( LUMP_LEAFMINDISTTOWATER, g_LeafMinDistToWater, numleafs ); + + AddGameLumps(); + + // Write pakfile lump to disk + WritePakFileLump(); + + // NOTE: Do NOT call AddLump after Lumps_Write() it writes all un-Added lumps + // write any additional lumps + Lumps_Write(); + + g_pFileSystem->Seek( g_hBSPFile, 0, FILESYSTEM_SEEK_HEAD ); + WriteData( g_pBSPHeader ); + g_pFileSystem->Close( g_hBSPFile ); +} + +// Generate the next clear lump filename for the bsp file +bool GenerateNextLumpFileName( const char *bspfilename, char *lumpfilename, int buffsize ) +{ + for (int i = 0; i < MAX_LUMPFILES; i++) + { + GenerateLumpFileName( bspfilename, lumpfilename, buffsize, i ); + + if ( !g_pFileSystem->FileExists( lumpfilename ) ) + return true; + } + + return false; +} + +void WriteLumpToFile( char *filename, int lump ) +{ + if ( !HasLump(lump) ) + return; + + char lumppre[MAX_PATH]; + if ( !GenerateNextLumpFileName( filename, lumppre, MAX_PATH ) ) + { + Warning( "Failed to find valid lump filename for bsp %s.\n", filename ); + return; + } + + // Open the file + FileHandle_t lumpfile = g_pFileSystem->Open(lumppre, "wb"); + if ( !lumpfile ) + { + Error ("Error opening %s! (Check for write enable)\n",filename); + return; + } + + int ofs = g_pBSPHeader->lumps[lump].fileofs; + int length = g_pBSPHeader->lumps[lump].filelen; + + // Write the header + lumpfileheader_t lumpHeader; + lumpHeader.lumpID = lump; + lumpHeader.lumpVersion = LumpVersion(lump); + lumpHeader.lumpLength = length; + lumpHeader.mapRevision = LittleLong( g_MapRevision ); + lumpHeader.lumpOffset = sizeof(lumpfileheader_t); // Lump starts after the header + SafeWrite (lumpfile, &lumpHeader, sizeof(lumpfileheader_t)); + + // Write the lump + SafeWrite (lumpfile, (byte *)g_pBSPHeader + ofs, length); +} + +void WriteLumpToFile( char *filename, int lump, int nLumpVersion, void *pBuffer, size_t nBufLen ) +{ + char lumppre[MAX_PATH]; + if ( !GenerateNextLumpFileName( filename, lumppre, MAX_PATH ) ) + { + Warning( "Failed to find valid lump filename for bsp %s.\n", filename ); + return; + } + + // Open the file + FileHandle_t lumpfile = g_pFileSystem->Open(lumppre, "wb"); + if ( !lumpfile ) + { + Error ("Error opening %s! (Check for write enable)\n",filename); + return; + } + + // Write the header + lumpfileheader_t lumpHeader; + lumpHeader.lumpID = lump; + lumpHeader.lumpVersion = nLumpVersion; + lumpHeader.lumpLength = nBufLen; + lumpHeader.mapRevision = LittleLong( g_MapRevision ); + lumpHeader.lumpOffset = sizeof(lumpfileheader_t); // Lump starts after the header + SafeWrite( lumpfile, &lumpHeader, sizeof(lumpfileheader_t)); + + // Write the lump + SafeWrite( lumpfile, pBuffer, nBufLen ); + + g_pFileSystem->Close( lumpfile ); +} + + +//============================================================================ +#define ENTRIES(a) (sizeof(a)/sizeof(*(a))) +#define ENTRYSIZE(a) (sizeof(*(a))) + +int ArrayUsage( const char *szItem, int items, int maxitems, int itemsize ) +{ + float percentage = maxitems ? items * 100.0 / maxitems : 0.0; + + Msg("%-17.17s %8i/%-8i %8i/%-8i (%4.1f%%) ", + szItem, items, maxitems, items * itemsize, maxitems * itemsize, percentage ); + if ( percentage > 80.0 ) + Msg( "VERY FULL!\n" ); + else if ( percentage > 95.0 ) + Msg( "SIZE DANGER!\n" ); + else if ( percentage > 99.9 ) + Msg( "SIZE OVERFLOW!!!\n" ); + else + Msg( "\n" ); + return items * itemsize; +} + +int GlobUsage( const char *szItem, int itemstorage, int maxstorage ) +{ + float percentage = maxstorage ? itemstorage * 100.0 / maxstorage : 0.0; + Msg("%-17.17s [variable] %8i/%-8i (%4.1f%%) ", + szItem, itemstorage, maxstorage, percentage ); + if ( percentage > 80.0 ) + Msg( "VERY FULL!\n" ); + else if ( percentage > 95.0 ) + Msg( "SIZE DANGER!\n" ); + else if ( percentage > 99.9 ) + Msg( "SIZE OVERFLOW!!!\n" ); + else + Msg( "\n" ); + return itemstorage; +} + +/* +============= +PrintBSPFileSizes + +Dumps info about current file +============= +*/ +void PrintBSPFileSizes (void) +{ + int totalmemory = 0; + +// if (!num_entities) +// ParseEntities (); + + Msg("\n"); + Msg( "%-17s %16s %16s %9s \n", "Object names", "Objects/Maxobjs", "Memory / Maxmem", "Fullness" ); + Msg( "%-17s %16s %16s %9s \n", "------------", "---------------", "---------------", "--------" ); + + totalmemory += ArrayUsage( "models", nummodels, ENTRIES(dmodels), ENTRYSIZE(dmodels) ); + totalmemory += ArrayUsage( "brushes", numbrushes, ENTRIES(dbrushes), ENTRYSIZE(dbrushes) ); + totalmemory += ArrayUsage( "brushsides", numbrushsides, ENTRIES(dbrushsides), ENTRYSIZE(dbrushsides) ); + totalmemory += ArrayUsage( "planes", numplanes, ENTRIES(dplanes), ENTRYSIZE(dplanes) ); + totalmemory += ArrayUsage( "vertexes", numvertexes, ENTRIES(dvertexes), ENTRYSIZE(dvertexes) ); + totalmemory += ArrayUsage( "nodes", numnodes, ENTRIES(dnodes), ENTRYSIZE(dnodes) ); + totalmemory += ArrayUsage( "texinfos", texinfo.Count(),MAX_MAP_TEXINFO, sizeof(texinfo_t) ); + totalmemory += ArrayUsage( "texdata", numtexdata, ENTRIES(dtexdata), ENTRYSIZE(dtexdata) ); + + totalmemory += ArrayUsage( "dispinfos", g_dispinfo.Count(), 0, sizeof( ddispinfo_t ) ); + totalmemory += ArrayUsage( "disp_verts", g_DispVerts.Count(), 0, sizeof( g_DispVerts[0] ) ); + totalmemory += ArrayUsage( "disp_tris", g_DispTris.Count(), 0, sizeof( g_DispTris[0] ) ); + totalmemory += ArrayUsage( "disp_lmsamples",g_DispLightmapSamplePositions.Count(),0,sizeof( g_DispLightmapSamplePositions[0] ) ); + + totalmemory += ArrayUsage( "faces", numfaces, ENTRIES(dfaces), ENTRYSIZE(dfaces) ); + totalmemory += ArrayUsage( "hdr faces", numfaces_hdr, ENTRIES(dfaces_hdr), ENTRYSIZE(dfaces_hdr) ); + totalmemory += ArrayUsage( "origfaces", numorigfaces, ENTRIES(dorigfaces), ENTRYSIZE(dorigfaces) ); // original faces + totalmemory += ArrayUsage( "leaves", numleafs, ENTRIES(dleafs), ENTRYSIZE(dleafs) ); + totalmemory += ArrayUsage( "leaffaces", numleaffaces, ENTRIES(dleaffaces), ENTRYSIZE(dleaffaces) ); + totalmemory += ArrayUsage( "leafbrushes", numleafbrushes, ENTRIES(dleafbrushes), ENTRYSIZE(dleafbrushes) ); + totalmemory += ArrayUsage( "areas", numareas, ENTRIES(dareas), ENTRYSIZE(dareas) ); + totalmemory += ArrayUsage( "surfedges", numsurfedges, ENTRIES(dsurfedges), ENTRYSIZE(dsurfedges) ); + totalmemory += ArrayUsage( "edges", numedges, ENTRIES(dedges), ENTRYSIZE(dedges) ); + totalmemory += ArrayUsage( "LDR worldlights", numworldlightsLDR, ENTRIES(dworldlightsLDR), ENTRYSIZE(dworldlightsLDR) ); + totalmemory += ArrayUsage( "HDR worldlights", numworldlightsHDR, ENTRIES(dworldlightsHDR), ENTRYSIZE(dworldlightsHDR) ); + + totalmemory += ArrayUsage( "leafwaterdata", numleafwaterdata,ENTRIES(dleafwaterdata), ENTRYSIZE(dleafwaterdata) ); + totalmemory += ArrayUsage( "waterstrips", g_numprimitives,ENTRIES(g_primitives), ENTRYSIZE(g_primitives) ); + totalmemory += ArrayUsage( "waterverts", g_numprimverts, ENTRIES(g_primverts), ENTRYSIZE(g_primverts) ); + totalmemory += ArrayUsage( "waterindices", g_numprimindices,ENTRIES(g_primindices),ENTRYSIZE(g_primindices) ); + totalmemory += ArrayUsage( "cubemapsamples", g_nCubemapSamples,ENTRIES(g_CubemapSamples),ENTRYSIZE(g_CubemapSamples) ); + totalmemory += ArrayUsage( "overlays", g_nOverlayCount, ENTRIES(g_Overlays), ENTRYSIZE(g_Overlays) ); + + totalmemory += GlobUsage( "LDR lightdata", dlightdataLDR.Count(), 0 ); + totalmemory += GlobUsage( "HDR lightdata", dlightdataHDR.Count(), 0 ); + totalmemory += GlobUsage( "visdata", visdatasize, sizeof(dvisdata) ); + totalmemory += GlobUsage( "entdata", dentdata.Count(), 384*1024 ); // goal is <384K + + totalmemory += ArrayUsage( "LDR ambient table", g_LeafAmbientIndexLDR.Count(), MAX_MAP_LEAFS, sizeof( g_LeafAmbientIndexLDR[0] ) ); + totalmemory += ArrayUsage( "HDR ambient table", g_LeafAmbientIndexHDR.Count(), MAX_MAP_LEAFS, sizeof( g_LeafAmbientIndexHDR[0] ) ); + totalmemory += ArrayUsage( "LDR leaf ambient lighting", g_LeafAmbientLightingLDR.Count(), MAX_MAP_LEAFS, sizeof( g_LeafAmbientLightingLDR[0] ) ); + totalmemory += ArrayUsage( "HDR leaf ambient lighting", g_LeafAmbientLightingHDR.Count(), MAX_MAP_LEAFS, sizeof( g_LeafAmbientLightingHDR[0] ) ); + + totalmemory += ArrayUsage( "occluders", g_OccluderData.Count(), 0, sizeof( g_OccluderData[0] ) ); + totalmemory += ArrayUsage( "occluder polygons", g_OccluderPolyData.Count(), 0, sizeof( g_OccluderPolyData[0] ) ); + totalmemory += ArrayUsage( "occluder vert ind",g_OccluderVertexIndices.Count(),0, sizeof( g_OccluderVertexIndices[0] ) ); + + GameLumpHandle_t h = g_GameLumps.GetGameLumpHandle( GAMELUMP_DETAIL_PROPS ); + if (h != g_GameLumps.InvalidGameLump()) + totalmemory += GlobUsage( "detail props", 1, g_GameLumps.GameLumpSize(h) ); + h = g_GameLumps.GetGameLumpHandle( GAMELUMP_DETAIL_PROP_LIGHTING ); + if (h != g_GameLumps.InvalidGameLump()) + totalmemory += GlobUsage( "dtl prp lght", 1, g_GameLumps.GameLumpSize(h) ); + h = g_GameLumps.GetGameLumpHandle( GAMELUMP_DETAIL_PROP_LIGHTING_HDR ); + if (h != g_GameLumps.InvalidGameLump()) + totalmemory += GlobUsage( "HDR dtl prp lght", 1, g_GameLumps.GameLumpSize(h) ); + h = g_GameLumps.GetGameLumpHandle( GAMELUMP_STATIC_PROPS ); + if (h != g_GameLumps.InvalidGameLump()) + totalmemory += GlobUsage( "static props", 1, g_GameLumps.GameLumpSize(h) ); + + totalmemory += GlobUsage( "pakfile", GetPakFile()->EstimateSize(), 0 ); + // HACKHACK: Set physics limit at 4MB, in reality this is totally dynamic + totalmemory += GlobUsage( "physics", g_PhysCollideSize, 4*1024*1024 ); + totalmemory += GlobUsage( "physics terrain", g_PhysDispSize, 1*1024*1024 ); + + Msg( "\nLevel flags = %x\n", g_LevelFlags ); + + Msg( "\n" ); + + int triangleCount = 0; + + for ( int i = 0; i < numfaces; i++ ) + { + // face tris = numedges - 2 + triangleCount += dfaces[i].numedges - 2; + } + Msg("Total triangle count: %d\n", triangleCount ); + + // UNDONE: + // areaportals, portals, texdata, clusters, worldlights, portalverts +} + +/* +============= +PrintBSPPackDirectory + +Dumps a list of files stored in the bsp pack. +============= +*/ +void PrintBSPPackDirectory( void ) +{ + GetPakFile()->PrintDirectory(); +} + + +//============================================ + +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 = (epair_t*)malloc (sizeof(epair_t)); + memset (e, 0, sizeof(epair_t)); + + if (strlen(token) >= MAX_KEY-1) + Error ("ParseEpar: token too long"); + e->key = copystring(token); + + GetToken (false); + if (strlen(token) >= MAX_VALUE-1) + Error ("ParseEpar: token too long"); + e->value = copystring(token); + + // strip trailing spaces + StripTrailing (e->key); + StripTrailing (e->value); + + return e; +} + + +/* +================ +ParseEntity +================ +*/ +qboolean ParseEntity (void) +{ + epair_t *e; + entity_t *mapent; + + if (!GetToken (true)) + return false; + + if (Q_stricmp (token, "{") ) + Error ("ParseEntity: { not found"); + + if (num_entities == MAX_MAP_ENTITIES) + Error ("num_entities == MAX_MAP_ENTITIES"); + + mapent = &entities[num_entities]; + num_entities++; + + do + { + if (!GetToken (true)) + Error ("ParseEntity: EOF without closing brace"); + if (!Q_stricmp (token, "}") ) + break; + e = ParseEpair (); + e->next = mapent->epairs; + mapent->epairs = e; + } while (1); + + return true; +} + +/* +================ +ParseEntities + +Parses the dentdata string into entities +================ +*/ +void ParseEntities (void) +{ + num_entities = 0; + ParseFromMemory (dentdata.Base(), dentdata.Count()); + + while (ParseEntity ()) + { + } +} + + +/* +================ +UnparseEntities + +Generates the dentdata string from all the entities +================ +*/ +void UnparseEntities (void) +{ + epair_t *ep; + char line[2048]; + int i; + char key[1024], value[1024]; + + CUtlBuffer buffer( 0, 0, CUtlBuffer::TEXT_BUFFER ); + buffer.EnsureCapacity( 256 * 1024 ); + + for (i=0 ; inext) + { + strcpy (key, ep->key); + StripTrailing (key); + strcpy (value, ep->value); + StripTrailing (value); + + sprintf(line, "\"%s\" \"%s\"\n", key, value); + buffer.PutString( line ); + } + buffer.PutString("}\n"); + } + int entdatasize = buffer.TellPut()+1; + + dentdata.SetSize( entdatasize ); + memcpy( dentdata.Base(), buffer.Base(), entdatasize-1 ); + dentdata[entdatasize-1] = 0; +} + +void PrintEntity (entity_t *ent) +{ + epair_t *ep; + + Msg ("------- entity %p -------\n", ent); + for (ep=ent->epairs ; ep ; ep=ep->next) + { + Msg ("%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 (!Q_stricmp (ep->key, key) ) + { + free (ep->value); + ep->value = copystring(value); + return; + } + ep = (epair_t*)malloc (sizeof(*ep)); + ep->next = ent->epairs; + ent->epairs = ep; + ep->key = copystring(key); + ep->value = copystring(value); +} + +char *ValueForKey (entity_t *ent, char *key) +{ + for (epair_t *ep=ent->epairs ; ep ; ep=ep->next) + if (!Q_stricmp (ep->key, key) ) + return ep->value; + return ""; +} + +vec_t FloatForKey (entity_t *ent, char *key) +{ + char *k = ValueForKey (ent, key); + return atof(k); +} + +vec_t FloatForKeyWithDefault (entity_t *ent, char *key, float default_value) +{ + for (epair_t *ep=ent->epairs ; ep ; ep=ep->next) + if (!Q_stricmp (ep->key, key) ) + return atof( ep->value ); + return default_value; +} + + + +int IntForKey (entity_t *ent, char *key) +{ + char *k = ValueForKey (ent, key); + return atol(k); +} + +int IntForKeyWithDefault(entity_t *ent, char *key, int nDefault ) +{ + char *k = ValueForKey (ent, key); + if ( !k[0] ) + return nDefault; + return atol(k); +} + +void GetVectorForKey (entity_t *ent, char *key, Vector& vec) +{ + + char *k = ValueForKey (ent, key); +// scanf into doubles, then assign, so it is vec_t size independent + double v1, v2, v3; + v1 = v2 = v3 = 0; + sscanf (k, "%lf %lf %lf", &v1, &v2, &v3); + vec[0] = v1; + vec[1] = v2; + vec[2] = v3; +} + +void GetVector2DForKey (entity_t *ent, char *key, Vector2D& vec) +{ + double v1, v2; + + char *k = ValueForKey (ent, key); +// scanf into doubles, then assign, so it is vec_t size independent + v1 = v2 = 0; + sscanf (k, "%lf %lf", &v1, &v2); + vec[0] = v1; + vec[1] = v2; +} + +void GetAnglesForKey (entity_t *ent, char *key, QAngle& angle) +{ + 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); + angle[0] = v1; + angle[1] = v2; + angle[2] = v3; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void BuildFaceCalcWindingData( dface_t *pFace, int *points ) +{ + for( int i = 0; i < pFace->numedges; i++ ) + { + int eIndex = dsurfedges[pFace->firstedge+i]; + if( eIndex < 0 ) + { + points[i] = dedges[-eIndex].v[1]; + } + else + { + points[i] = dedges[eIndex].v[0]; + } + } +} + + +void TriStripToTriList( + unsigned short const *pTriStripIndices, + int nTriStripIndices, + unsigned short **pTriListIndices, + int *pnTriListIndices ) +{ + int nMaxTriListIndices = (nTriStripIndices - 2) * 3; + *pTriListIndices = new unsigned short[ nMaxTriListIndices ]; + *pnTriListIndices = 0; + + for( int i=0; i < nTriStripIndices - 2; i++ ) + { + if( pTriStripIndices[i] == pTriStripIndices[i+1] || + pTriStripIndices[i] == pTriStripIndices[i+2] || + pTriStripIndices[i+1] == pTriStripIndices[i+2] ) + { + } + else + { + // Flip odd numbered tris.. + if( i & 1 ) + { + (*pTriListIndices)[(*pnTriListIndices)++] = pTriStripIndices[i+2]; + (*pTriListIndices)[(*pnTriListIndices)++] = pTriStripIndices[i+1]; + (*pTriListIndices)[(*pnTriListIndices)++] = pTriStripIndices[i]; + } + else + { + (*pTriListIndices)[(*pnTriListIndices)++] = pTriStripIndices[i]; + (*pTriListIndices)[(*pnTriListIndices)++] = pTriStripIndices[i+1]; + (*pTriListIndices)[(*pnTriListIndices)++] = pTriStripIndices[i+2]; + } + } + } +} + + +void CalcTextureCoordsAtPoints( + float const texelsPerWorldUnits[2][4], + int const subtractOffset[2], + Vector const *pPoints, + int const nPoints, + Vector2D *pCoords ) +{ + for( int i=0; i < nPoints; i++ ) + { + for( int iCoord=0; iCoord < 2; iCoord++ ) + { + float *pDestCoord = &pCoords[i][iCoord]; + + *pDestCoord = 0; + for( int iDot=0; iDot < 3; iDot++ ) + *pDestCoord += pPoints[i][iDot] * texelsPerWorldUnits[iCoord][iDot]; + + *pDestCoord += texelsPerWorldUnits[iCoord][3]; + *pDestCoord -= subtractOffset[iCoord]; + } + } +} + + +/* +================ +CalcFaceExtents + +Fills in s->texmins[] and s->texsize[] +================ +*/ +void CalcFaceExtents(dface_t *s, int lightmapTextureMinsInLuxels[2], int lightmapTextureSizeInLuxels[2]) +{ + vec_t mins[2], maxs[2], val=0; + int i,j, e=0; + dvertex_t *v=NULL; + texinfo_t *tex=NULL; + + mins[0] = mins[1] = 1e24; + maxs[0] = maxs[1] = -1e24; + + tex = &texinfo[s->texinfo]; + + for (i=0 ; inumedges ; i++) + { + e = dsurfedges[s->firstedge+i]; + if (e >= 0) + v = dvertexes + dedges[e].v[0]; + else + v = dvertexes + dedges[-e].v[1]; + + for (j=0 ; j<2 ; j++) + { + val = v->point[0] * tex->lightmapVecsLuxelsPerWorldUnits[j][0] + + v->point[1] * tex->lightmapVecsLuxelsPerWorldUnits[j][1] + + v->point[2] * tex->lightmapVecsLuxelsPerWorldUnits[j][2] + + tex->lightmapVecsLuxelsPerWorldUnits[j][3]; + if (val < mins[j]) + mins[j] = val; + if (val > maxs[j]) + maxs[j] = val; + } + } + + int nMaxLightmapDim = (s->dispinfo == -1) ? MAX_LIGHTMAP_DIM_WITHOUT_BORDER : MAX_DISP_LIGHTMAP_DIM_WITHOUT_BORDER; + for (i=0 ; i<2 ; i++) + { + mins[i] = ( float )floor( mins[i] ); + maxs[i] = ( float )ceil( maxs[i] ); + + lightmapTextureMinsInLuxels[i] = ( int )mins[i]; + lightmapTextureSizeInLuxels[i] = ( int )( maxs[i] - mins[i] ); + if( lightmapTextureSizeInLuxels[i] > nMaxLightmapDim + 1 ) + { + Vector point = vec3_origin; + for (int j=0 ; jnumedges ; j++) + { + e = dsurfedges[s->firstedge+j]; + v = (e<0)?dvertexes + dedges[-e].v[1] : dvertexes + dedges[e].v[0]; + point += v->point; + Warning( "Bad surface extents point: %f %f %f\n", v->point.x, v->point.y, v->point.z ); + } + point *= 1.0f/s->numedges; + Error( "Bad surface extents - surface is too big to have a lightmap\n\tmaterial %s around point (%.1f %.1f %.1f)\n\t(dimension: %d, %d>%d)\n", + TexDataStringTable_GetString( dtexdata[texinfo[s->texinfo].texdata].nameStringTableID ), + point.x, point.y, point.z, + ( int )i, + ( int )lightmapTextureSizeInLuxels[i], + ( int )( nMaxLightmapDim + 1 ) + ); + } + } +} + + +void UpdateAllFaceLightmapExtents() +{ + for( int i=0; i < numfaces; i++ ) + { + dface_t *pFace = &dfaces[i]; + + if ( texinfo[pFace->texinfo].flags & (SURF_SKY|SURF_NOLIGHT) ) + continue; // non-lit texture + + CalcFaceExtents( pFace, pFace->m_LightmapTextureMinsInLuxels, pFace->m_LightmapTextureSizeInLuxels ); + } +} + + +//----------------------------------------------------------------------------- +// +// Helper class to iterate over leaves, used by tools +// +//----------------------------------------------------------------------------- + +#define TEST_EPSILON (0.03125) + + +class CToolBSPTree : public ISpatialQuery +{ +public: + // Returns the number of leaves + int LeafCount() const; + + // Enumerates the leaves along a ray, box, etc. + bool EnumerateLeavesAtPoint( Vector const& pt, ISpatialLeafEnumerator* pEnum, int context ); + bool EnumerateLeavesInBox( Vector const& mins, Vector const& maxs, ISpatialLeafEnumerator* pEnum, int context ); + bool EnumerateLeavesInSphere( Vector const& center, float radius, ISpatialLeafEnumerator* pEnum, int context ); + bool EnumerateLeavesAlongRay( Ray_t const& ray, ISpatialLeafEnumerator* pEnum, int context ); +}; + + +//----------------------------------------------------------------------------- +// Returns the number of leaves +//----------------------------------------------------------------------------- + +int CToolBSPTree::LeafCount() const +{ + return numleafs; +} + + +//----------------------------------------------------------------------------- +// Enumerates the leaves at a point +//----------------------------------------------------------------------------- + +bool CToolBSPTree::EnumerateLeavesAtPoint( Vector const& pt, + ISpatialLeafEnumerator* pEnum, int context ) +{ + int node = 0; + while( node >= 0 ) + { + dnode_t* pNode = &dnodes[node]; + dplane_t* pPlane = &dplanes[pNode->planenum]; + + if (DotProduct( pPlane->normal, pt ) <= pPlane->dist) + { + node = pNode->children[1]; + } + else + { + node = pNode->children[0]; + } + } + + return pEnum->EnumerateLeaf( - node - 1, context ); +} + + +//----------------------------------------------------------------------------- +// Enumerates the leaves in a box +//----------------------------------------------------------------------------- + +static bool EnumerateLeavesInBox_R( int node, Vector const& mins, + Vector const& maxs, ISpatialLeafEnumerator* pEnum, int context ) +{ + Vector cornermin, cornermax; + + while( node >= 0 ) + { + dnode_t* pNode = &dnodes[node]; + dplane_t* pPlane = &dplanes[pNode->planenum]; + + // Arbitrary split plane here + for (int i = 0; i < 3; ++i) + { + if (pPlane->normal[i] >= 0) + { + cornermin[i] = mins[i]; + cornermax[i] = maxs[i]; + } + else + { + cornermin[i] = maxs[i]; + cornermax[i] = mins[i]; + } + } + + if ( (DotProduct( pPlane->normal, cornermax ) - pPlane->dist) <= -TEST_EPSILON ) + { + node = pNode->children[1]; + } + else if ( (DotProduct( pPlane->normal, cornermin ) - pPlane->dist) >= TEST_EPSILON ) + { + node = pNode->children[0]; + } + else + { + if (!EnumerateLeavesInBox_R( pNode->children[0], mins, maxs, pEnum, context )) + { + return false; + } + + return EnumerateLeavesInBox_R( pNode->children[1], mins, maxs, pEnum, context ); + } + } + + return pEnum->EnumerateLeaf( - node - 1, context ); +} + +bool CToolBSPTree::EnumerateLeavesInBox( Vector const& mins, Vector const& maxs, + ISpatialLeafEnumerator* pEnum, int context ) +{ + return EnumerateLeavesInBox_R( 0, mins, maxs, pEnum, context ); +} + +//----------------------------------------------------------------------------- +// Enumerate leaves within a sphere +//----------------------------------------------------------------------------- + +static bool EnumerateLeavesInSphere_R( int node, Vector const& origin, + float radius, ISpatialLeafEnumerator* pEnum, int context ) +{ + while( node >= 0 ) + { + dnode_t* pNode = &dnodes[node]; + dplane_t* pPlane = &dplanes[pNode->planenum]; + + if (DotProduct( pPlane->normal, origin ) + radius - pPlane->dist <= -TEST_EPSILON ) + { + node = pNode->children[1]; + } + else if (DotProduct( pPlane->normal, origin ) - radius - pPlane->dist >= TEST_EPSILON ) + { + node = pNode->children[0]; + } + else + { + if (!EnumerateLeavesInSphere_R( pNode->children[0], + origin, radius, pEnum, context )) + { + return false; + } + + return EnumerateLeavesInSphere_R( pNode->children[1], + origin, radius, pEnum, context ); + } + } + + return pEnum->EnumerateLeaf( - node - 1, context ); +} + +bool CToolBSPTree::EnumerateLeavesInSphere( Vector const& center, float radius, ISpatialLeafEnumerator* pEnum, int context ) +{ + return EnumerateLeavesInSphere_R( 0, center, radius, pEnum, context ); +} + + +//----------------------------------------------------------------------------- +// Enumerate leaves along a ray +//----------------------------------------------------------------------------- + +static bool EnumerateLeavesAlongRay_R( int node, Ray_t const& ray, + Vector const& start, Vector const& end, ISpatialLeafEnumerator* pEnum, int context ) +{ + float front,back; + + while (node >= 0) + { + dnode_t* pNode = &dnodes[node]; + dplane_t* pPlane = &dplanes[pNode->planenum]; + + if ( pPlane->type <= PLANE_Z ) + { + front = start[pPlane->type] - pPlane->dist; + back = end[pPlane->type] - pPlane->dist; + } + else + { + front = DotProduct(start, pPlane->normal) - pPlane->dist; + back = DotProduct(end, pPlane->normal) - pPlane->dist; + } + + if (front <= -TEST_EPSILON && back <= -TEST_EPSILON) + { + node = pNode->children[1]; + } + else if (front >= TEST_EPSILON && back >= TEST_EPSILON) + { + node = pNode->children[0]; + } + else + { + // test the front side first + bool side = front < 0; + + // Compute intersection point based on the original ray + float splitfrac; + float denom = DotProduct( ray.m_Delta, pPlane->normal ); + if ( denom == 0.0f ) + { + splitfrac = 1.0f; + } + else + { + splitfrac = ( pPlane->dist - DotProduct( ray.m_Start, pPlane->normal ) ) / denom; + if (splitfrac < 0) + splitfrac = 0; + else if (splitfrac > 1) + splitfrac = 1; + } + + // Compute the split point + Vector split; + VectorMA( ray.m_Start, splitfrac, ray.m_Delta, split ); + + bool r = EnumerateLeavesAlongRay_R (pNode->children[side], ray, start, split, pEnum, context ); + if (!r) + return r; + return EnumerateLeavesAlongRay_R (pNode->children[!side], ray, split, end, pEnum, context); + } + } + + return pEnum->EnumerateLeaf( - node - 1, context ); +} + +bool CToolBSPTree::EnumerateLeavesAlongRay( Ray_t const& ray, ISpatialLeafEnumerator* pEnum, int context ) +{ + if (!ray.m_IsSwept) + { + Vector mins, maxs; + VectorAdd( ray.m_Start, ray.m_Extents, maxs ); + VectorSubtract( ray.m_Start, ray.m_Extents, mins ); + + return EnumerateLeavesInBox_R( 0, mins, maxs, pEnum, context ); + } + + // FIXME: Extruded ray not implemented yet + Assert( ray.m_IsRay ); + + Vector end; + VectorAdd( ray.m_Start, ray.m_Delta, end ); + return EnumerateLeavesAlongRay_R( 0, ray, ray.m_Start, end, pEnum, context ); +} + + +//----------------------------------------------------------------------------- +// Singleton accessor +//----------------------------------------------------------------------------- + +ISpatialQuery* ToolBSPTree() +{ + static CToolBSPTree s_ToolBSPTree; + return &s_ToolBSPTree; +} + + + +//----------------------------------------------------------------------------- +// Enumerates nodes in front to back order... +//----------------------------------------------------------------------------- + +// FIXME: Do we want this in the IBSPTree interface? + +static bool EnumerateNodesAlongRay_R( int node, Ray_t const& ray, float start, float end, + IBSPNodeEnumerator* pEnum, int context ) +{ + float front, back; + float startDotN, deltaDotN; + + while (node >= 0) + { + dnode_t* pNode = &dnodes[node]; + dplane_t* pPlane = &dplanes[pNode->planenum]; + + if ( pPlane->type <= PLANE_Z ) + { + startDotN = ray.m_Start[pPlane->type]; + deltaDotN = ray.m_Delta[pPlane->type]; + } + else + { + startDotN = DotProduct( ray.m_Start, pPlane->normal ); + deltaDotN = DotProduct( ray.m_Delta, pPlane->normal ); + } + + front = startDotN + start * deltaDotN - pPlane->dist; + back = startDotN + end * deltaDotN - pPlane->dist; + + if (front <= -TEST_EPSILON && back <= -TEST_EPSILON) + { + node = pNode->children[1]; + } + else if (front >= TEST_EPSILON && back >= TEST_EPSILON) + { + node = pNode->children[0]; + } + else + { + // test the front side first + bool side = front < 0; + + // Compute intersection point based on the original ray + float splitfrac; + if ( deltaDotN == 0.0f ) + { + splitfrac = 1.0f; + } + else + { + splitfrac = ( pPlane->dist - startDotN ) / deltaDotN; + if (splitfrac < 0.0f) + splitfrac = 0.0f; + else if (splitfrac > 1.0f) + splitfrac = 1.0f; + } + + bool r = EnumerateNodesAlongRay_R (pNode->children[side], ray, start, splitfrac, pEnum, context ); + if (!r) + return r; + + // Visit the node... + if (!pEnum->EnumerateNode( node, ray, splitfrac, context )) + return false; + + return EnumerateNodesAlongRay_R (pNode->children[!side], ray, splitfrac, end, pEnum, context); + } + } + + // Visit the leaf... + return pEnum->EnumerateLeaf( - node - 1, ray, start, end, context ); +} + + +bool EnumerateNodesAlongRay( Ray_t const& ray, IBSPNodeEnumerator* pEnum, int context ) +{ + Vector end; + VectorAdd( ray.m_Start, ray.m_Delta, end ); + return EnumerateNodesAlongRay_R( 0, ray, 0.0f, 1.0f, pEnum, context ); +} + + +//----------------------------------------------------------------------------- +// Helps us find all leaves associated with a particular cluster +//----------------------------------------------------------------------------- +CUtlVector g_ClusterLeaves; + +void BuildClusterTable( void ) +{ + int i, j; + int leafCount; + int leafList[MAX_MAP_LEAFS]; + + g_ClusterLeaves.SetCount( dvis->numclusters ); + for ( i = 0; i < dvis->numclusters; i++ ) + { + leafCount = 0; + for ( j = 0; j < numleafs; j++ ) + { + if ( dleafs[j].cluster == i ) + { + leafList[ leafCount ] = j; + leafCount++; + } + } + + g_ClusterLeaves[i].leafCount = leafCount; + if ( leafCount ) + { + g_ClusterLeaves[i].leafs.SetCount( leafCount ); + memcpy( g_ClusterLeaves[i].leafs.Base(), leafList, sizeof(int) * leafCount ); + } + } +} + +// There's a version of this in host.cpp!!! Make sure that they match. +void GetPlatformMapPath( const char *pMapPath, char *pPlatformMapPath, int dxlevel, int maxLength ) +{ + Q_StripExtension( pMapPath, pPlatformMapPath, maxLength ); + +// if( dxlevel <= 60 ) +// { +// Q_strncat( pPlatformMapPath, "_dx60", maxLength, COPY_ALL_CHARACTERS ); +// } + + Q_strncat( pPlatformMapPath, ".bsp", maxLength, COPY_ALL_CHARACTERS ); +} + +// There's a version of this in checksum_engine.cpp!!! Make sure that they match. +static bool CRC_MapFile(CRC32_t *crcvalue, const char *pszFileName) +{ + byte chunk[1024]; + lump_t *curLump; + + FileHandle_t fp = g_pFileSystem->Open( pszFileName, "rb" ); + if ( !fp ) + return false; + + // CRC across all lumps except for the Entities lump + for ( int l = 0; l < HEADER_LUMPS; ++l ) + { + if (l == LUMP_ENTITIES) + continue; + + curLump = &g_pBSPHeader->lumps[l]; + unsigned int nSize = curLump->filelen; + + g_pFileSystem->Seek( fp, curLump->fileofs, FILESYSTEM_SEEK_HEAD ); + + // Now read in 1K chunks + while ( nSize > 0 ) + { + int nBytesRead = 0; + + if ( nSize > 1024 ) + nBytesRead = g_pFileSystem->Read( chunk, 1024, fp ); + else + nBytesRead = g_pFileSystem->Read( chunk, nSize, fp ); + + // If any data was received, CRC it. + if ( nBytesRead > 0 ) + { + nSize -= nBytesRead; + CRC32_ProcessBuffer( crcvalue, chunk, nBytesRead ); + } + else + { + g_pFileSystem->Close( fp ); + return false; + } + } + } + + g_pFileSystem->Close( fp ); + return true; +} + + +void SetHDRMode( bool bHDR ) +{ + g_bHDR = bHDR; + if ( bHDR ) + { + pdlightdata = &dlightdataHDR; + g_pLeafAmbientLighting = &g_LeafAmbientLightingHDR; + g_pLeafAmbientIndex = &g_LeafAmbientIndexHDR; + pNumworldlights = &numworldlightsHDR; + dworldlights = dworldlightsHDR; +#ifdef VRAD + extern void VRadDetailProps_SetHDRMode( bool bHDR ); + VRadDetailProps_SetHDRMode( bHDR ); +#endif + } + else + { + pdlightdata = &dlightdataLDR; + g_pLeafAmbientLighting = &g_LeafAmbientLightingLDR; + g_pLeafAmbientIndex = &g_LeafAmbientIndexLDR; + pNumworldlights = &numworldlightsLDR; + dworldlights = dworldlightsLDR; +#ifdef VRAD + extern void VRadDetailProps_SetHDRMode( bool bHDR ); + VRadDetailProps_SetHDRMode( bHDR ); +#endif + } +} + +bool SwapVHV( void *pDestBase, void *pSrcBase ) +{ + byte *pDest = (byte*)pDestBase; + byte *pSrc = (byte*)pSrcBase; + + HardwareVerts::FileHeader_t *pHdr = (HardwareVerts::FileHeader_t*)( g_bSwapOnLoad ? pDest : pSrc ); + g_Swap.SwapFieldsToTargetEndian( (HardwareVerts::FileHeader_t*)pDest, (HardwareVerts::FileHeader_t*)pSrc ); + pSrc += sizeof(HardwareVerts::FileHeader_t); + pDest += sizeof(HardwareVerts::FileHeader_t); + + // This swap is pretty format specific + Assert( pHdr->m_nVersion == VHV_VERSION ); + if ( pHdr->m_nVersion != VHV_VERSION ) + return false; + + HardwareVerts::MeshHeader_t *pSrcMesh = (HardwareVerts::MeshHeader_t*)pSrc; + HardwareVerts::MeshHeader_t *pDestMesh = (HardwareVerts::MeshHeader_t*)pDest; + HardwareVerts::MeshHeader_t *pMesh = (HardwareVerts::MeshHeader_t*)( g_bSwapOnLoad ? pDest : pSrc ); + for ( int i = 0; i < pHdr->m_nMeshes; ++i, ++pMesh, ++pSrcMesh, ++pDestMesh ) + { + g_Swap.SwapFieldsToTargetEndian( pDestMesh, pSrcMesh ); + + pSrc = (byte*)pSrcBase + pMesh->m_nOffset; + pDest = (byte*)pDestBase + pMesh->m_nOffset; + + // Swap as a buffer of integers + // (source is bgra for an Intel swap to argb. PowerPC won't swap, so we need argb source. + g_Swap.SwapBufferToTargetEndian( (int*)pDest, (int*)pSrc, pMesh->m_nVertexes ); + } + return true; +} + +const char *ResolveStaticPropToModel( const char *pPropName ) +{ + // resolve back to static prop + int iProp = -1; + + // filename should be sp_???.vhv or sp_hdr_???.vhv + if ( V_strnicmp( pPropName, "sp_", 3 ) ) + { + return NULL; + } + const char *pPropNumber = V_strrchr( pPropName, '_' ); + if ( pPropNumber ) + { + sscanf( pPropNumber+1, "%d.vhv", &iProp ); + } + else + { + return NULL; + } + + // look up the prop to get to the actual model + if ( iProp < 0 || iProp >= g_StaticPropInstances.Count() ) + { + // prop out of range + return NULL; + } + int iModel = g_StaticPropInstances[iProp]; + if ( iModel < 0 || iModel >= g_StaticPropNames.Count() ) + { + // model out of range + return NULL; + } + + return g_StaticPropNames[iModel].String(); +} + +//----------------------------------------------------------------------------- +// Iterate files in pak file, distribute to converters +// pak file will be ready for serialization upon completion +//----------------------------------------------------------------------------- +void ConvertPakFileContents( const char *pInFilename ) +{ + IZip *newPakFile = IZip::CreateZip( NULL ); + + CUtlBuffer sourceBuf; + CUtlBuffer targetBuf; + bool bConverted; + CUtlVector< CUtlString > hdrFiles; + + int id = -1; + int fileSize; + while ( 1 ) + { + char relativeName[MAX_PATH]; + id = GetNextFilename( GetPakFile(), id, relativeName, sizeof( relativeName ), fileSize ); + if ( id == -1) + break; + + bConverted = false; + sourceBuf.Purge(); + targetBuf.Purge(); + + const char* pExtension = V_GetFileExtension( relativeName ); + const char* pExt = 0; + + bool bOK = ReadFileFromPak( GetPakFile(), relativeName, false, sourceBuf ); + if ( !bOK ) + { + Warning( "Failed to load '%s' from lump pak for conversion or copy in '%s'.\n", relativeName, pInFilename ); + continue; + } + + if ( pExtension && !V_stricmp( pExtension, "vtf" ) ) + { + bOK = g_pVTFConvertFunc( relativeName, sourceBuf, targetBuf, g_pCompressFunc ); + if ( !bOK ) + { + Warning( "Failed to convert '%s' in '%s'.\n", relativeName, pInFilename ); + continue; + } + + bConverted = true; + pExt = ".vtf"; + } + else if ( pExtension && !V_stricmp( pExtension, "vhv" ) ) + { + CUtlBuffer tempBuffer; + if ( g_pVHVFixupFunc ) + { + // caller supplied a fixup + const char *pModelName = ResolveStaticPropToModel( relativeName ); + if ( !pModelName ) + { + Warning( "Static Prop '%s' failed to resolve actual model in '%s'.\n", relativeName, pInFilename ); + continue; + } + + // output temp buffer may shrink, must use TellPut() to determine size + bOK = g_pVHVFixupFunc( relativeName, pModelName, sourceBuf, tempBuffer ); + if ( !bOK ) + { + Warning( "Failed to convert '%s' in '%s'.\n", relativeName, pInFilename ); + continue; + } + } + else + { + // use the source buffer as-is + tempBuffer.EnsureCapacity( sourceBuf.TellMaxPut() ); + tempBuffer.Put( sourceBuf.Base(), sourceBuf.TellMaxPut() ); + } + + // swap the VHV + targetBuf.EnsureCapacity( tempBuffer.TellPut() ); + bOK = SwapVHV( targetBuf.Base(), tempBuffer.Base() ); + if ( !bOK ) + { + Warning( "Failed to swap '%s' in '%s'.\n", relativeName, pInFilename ); + continue; + } + targetBuf.SeekPut( CUtlBuffer::SEEK_HEAD, tempBuffer.TellPut() ); + + if ( g_pCompressFunc ) + { + CUtlBuffer compressedBuffer; + targetBuf.SeekGet( CUtlBuffer::SEEK_HEAD, sizeof( HardwareVerts::FileHeader_t ) ); + bool bCompressed = g_pCompressFunc( targetBuf, compressedBuffer ); + if ( bCompressed ) + { + // copy all the header data off + CUtlBuffer headerBuffer; + headerBuffer.EnsureCapacity( sizeof( HardwareVerts::FileHeader_t ) ); + headerBuffer.Put( targetBuf.Base(), sizeof( HardwareVerts::FileHeader_t ) ); + + // reform the target with the header and then the compressed data + targetBuf.Clear(); + targetBuf.Put( headerBuffer.Base(), sizeof( HardwareVerts::FileHeader_t ) ); + targetBuf.Put( compressedBuffer.Base(), compressedBuffer.TellPut() ); + } + + targetBuf.SeekGet( CUtlBuffer::SEEK_HEAD, 0 ); + } + + bConverted = true; + pExt = ".vhv"; + } + + if ( !bConverted ) + { + // straight copy + AddBufferToPak( newPakFile, relativeName, sourceBuf.Base(), sourceBuf.TellMaxPut(), false ); + } + else + { + // converted filename + V_StripExtension( relativeName, relativeName, sizeof( relativeName ) ); + V_strcat( relativeName, ".360", sizeof( relativeName ) ); + V_strcat( relativeName, pExt, sizeof( relativeName ) ); + AddBufferToPak( newPakFile, relativeName, targetBuf.Base(), targetBuf.TellMaxPut(), false ); + } + + if ( V_stristr( relativeName, ".hdr" ) || V_stristr( relativeName, "_hdr" ) ) + { + hdrFiles.AddToTail( relativeName ); + } + + DevMsg( "Created '%s' in lump pak in '%s'.\n", relativeName, pInFilename ); + } + + // strip ldr version of hdr files + for ( int i=0; iRemoveFileFromZip( ldrFileName ); + } + } + + // discard old pak in favor of new pak + IZip::ReleaseZip( s_pakFile ); + s_pakFile = newPakFile; +} + +void SetAlignedLumpPosition( int lumpnum, int alignment = LUMP_ALIGNMENT ) +{ + g_pBSPHeader->lumps[lumpnum].fileofs = AlignFilePosition( g_hBSPFile, alignment ); +} + +template< class T > +int SwapLumpToDisk( int fieldType, int lumpnum ) +{ + if ( g_pBSPHeader->lumps[lumpnum].filelen == 0 ) + return 0; + + DevMsg( "Swapping %s\n", GetLumpName( lumpnum ) ); + + // lump swap may expand, allocate enough expansion room + void *pBuffer = malloc( 2*g_pBSPHeader->lumps[lumpnum].filelen ); + + // CopyLumpInternal will handle the swap on load case + unsigned int fieldSize = ( fieldType == FIELD_VECTOR ) ? sizeof(Vector) : sizeof(T); + unsigned int count = CopyLumpInternal( fieldType, lumpnum, (T*)pBuffer, g_pBSPHeader->lumps[lumpnum].version ); + g_pBSPHeader->lumps[lumpnum].filelen = count * fieldSize; + + if ( g_bSwapOnWrite ) + { + // Swap the lump in place before writing + switch( lumpnum ) + { + case LUMP_VISIBILITY: + SwapVisibilityLump( (byte*)pBuffer, (byte*)pBuffer, count ); + break; + + case LUMP_PHYSCOLLIDE: + // SwapPhyscollideLump may change size + SwapPhyscollideLump( (byte*)pBuffer, (byte*)pBuffer, count ); + g_pBSPHeader->lumps[lumpnum].filelen = count; + break; + + case LUMP_PHYSDISP: + SwapPhysdispLump( (byte*)pBuffer, (byte*)pBuffer, count ); + break; + + default: + g_Swap.SwapBufferToTargetEndian( (T*)pBuffer, (T*)pBuffer, g_pBSPHeader->lumps[lumpnum].filelen / sizeof(T) ); + break; + } + } + + SetAlignedLumpPosition( lumpnum ); + SafeWrite( g_hBSPFile, pBuffer, g_pBSPHeader->lumps[lumpnum].filelen ); + + free( pBuffer ); + + return g_pBSPHeader->lumps[lumpnum].filelen; +} + +template< class T > +int SwapLumpToDisk( int lumpnum ) +{ + if ( g_pBSPHeader->lumps[lumpnum].filelen == 0 || g_Lumps.bLumpParsed[lumpnum] ) + return 0; + + DevMsg( "Swapping %s\n", GetLumpName( lumpnum ) ); + + // lump swap may expand, allocate enough room + void *pBuffer = malloc( 2*g_pBSPHeader->lumps[lumpnum].filelen ); + + // CopyLumpInternal will handle the swap on load case + int count = CopyLumpInternal( lumpnum, (T*)pBuffer, g_pBSPHeader->lumps[lumpnum].version ); + g_pBSPHeader->lumps[lumpnum].filelen = count * sizeof(T); + + if ( g_bSwapOnWrite ) + { + // Swap the lump in place before writing + g_Swap.SwapFieldsToTargetEndian( (T*)pBuffer, (T*)pBuffer, count ); + } + + SetAlignedLumpPosition( lumpnum ); + SafeWrite( g_hBSPFile, pBuffer, g_pBSPHeader->lumps[lumpnum].filelen ); + free( pBuffer ); + + return g_pBSPHeader->lumps[lumpnum].filelen; +} + +void SwapLeafAmbientLightingLumpToDisk() +{ + if ( HasLump( LUMP_LEAF_AMBIENT_INDEX ) || HasLump( LUMP_LEAF_AMBIENT_INDEX_HDR ) ) + { + // current version, swap in place + if ( HasLump( LUMP_LEAF_AMBIENT_INDEX_HDR ) ) + { + // write HDR + SwapLumpToDisk< dleafambientlighting_t >( LUMP_LEAF_AMBIENT_LIGHTING_HDR ); + SwapLumpToDisk< dleafambientindex_t >( LUMP_LEAF_AMBIENT_INDEX_HDR ); + + // cull LDR + g_pBSPHeader->lumps[LUMP_LEAF_AMBIENT_LIGHTING].filelen = 0; + g_pBSPHeader->lumps[LUMP_LEAF_AMBIENT_INDEX].filelen = 0; + } + else + { + // no HDR, keep LDR version + SwapLumpToDisk< dleafambientlighting_t >( LUMP_LEAF_AMBIENT_LIGHTING ); + SwapLumpToDisk< dleafambientindex_t >( LUMP_LEAF_AMBIENT_INDEX ); + } + } + else + { + // older ambient lighting version (before index) + // load older ambient lighting into memory and build ambient/index + // an older leaf version would have already built the new LDR leaf ambient/index + int numLeafs = g_pBSPHeader->lumps[LUMP_LEAFS].filelen / sizeof( dleaf_t ); + LoadLeafAmbientLighting( numLeafs ); + + if ( HasLump( LUMP_LEAF_AMBIENT_LIGHTING_HDR ) ) + { + DevMsg( "Swapping %s\n", GetLumpName( LUMP_LEAF_AMBIENT_LIGHTING_HDR ) ); + DevMsg( "Swapping %s\n", GetLumpName( LUMP_LEAF_AMBIENT_INDEX_HDR ) ); + + // write HDR + if ( g_bSwapOnWrite ) + { + g_Swap.SwapFieldsToTargetEndian( g_LeafAmbientLightingHDR.Base(), g_LeafAmbientLightingHDR.Count() ); + g_Swap.SwapFieldsToTargetEndian( g_LeafAmbientIndexHDR.Base(), g_LeafAmbientIndexHDR.Count() ); + } + + SetAlignedLumpPosition( LUMP_LEAF_AMBIENT_LIGHTING_HDR ); + g_pBSPHeader->lumps[LUMP_LEAF_AMBIENT_LIGHTING_HDR].version = LUMP_LEAF_AMBIENT_LIGHTING_VERSION; + g_pBSPHeader->lumps[LUMP_LEAF_AMBIENT_LIGHTING_HDR].filelen = g_LeafAmbientLightingHDR.Count() * sizeof( dleafambientlighting_t ); + SafeWrite( g_hBSPFile, g_LeafAmbientLightingHDR.Base(), g_pBSPHeader->lumps[LUMP_LEAF_AMBIENT_LIGHTING_HDR].filelen ); + + SetAlignedLumpPosition( LUMP_LEAF_AMBIENT_INDEX_HDR ); + g_pBSPHeader->lumps[LUMP_LEAF_AMBIENT_INDEX_HDR].filelen = g_LeafAmbientIndexHDR.Count() * sizeof( dleafambientindex_t ); + SafeWrite( g_hBSPFile, g_LeafAmbientIndexHDR.Base(), g_pBSPHeader->lumps[LUMP_LEAF_AMBIENT_INDEX_HDR].filelen ); + + // mark as processed + g_Lumps.bLumpParsed[LUMP_LEAF_AMBIENT_LIGHTING_HDR] = true; + g_Lumps.bLumpParsed[LUMP_LEAF_AMBIENT_INDEX_HDR] = true; + + // cull LDR + g_pBSPHeader->lumps[LUMP_LEAF_AMBIENT_LIGHTING].filelen = 0; + g_pBSPHeader->lumps[LUMP_LEAF_AMBIENT_INDEX].filelen = 0; + } + else + { + // no HDR, keep LDR version + DevMsg( "Swapping %s\n", GetLumpName( LUMP_LEAF_AMBIENT_LIGHTING ) ); + DevMsg( "Swapping %s\n", GetLumpName( LUMP_LEAF_AMBIENT_INDEX ) ); + + if ( g_bSwapOnWrite ) + { + g_Swap.SwapFieldsToTargetEndian( g_LeafAmbientLightingLDR.Base(), g_LeafAmbientLightingLDR.Count() ); + g_Swap.SwapFieldsToTargetEndian( g_LeafAmbientIndexLDR.Base(), g_LeafAmbientIndexLDR.Count() ); + } + + SetAlignedLumpPosition( LUMP_LEAF_AMBIENT_LIGHTING ); + g_pBSPHeader->lumps[LUMP_LEAF_AMBIENT_LIGHTING].version = LUMP_LEAF_AMBIENT_LIGHTING_VERSION; + g_pBSPHeader->lumps[LUMP_LEAF_AMBIENT_LIGHTING].filelen = g_LeafAmbientLightingLDR.Count() * sizeof( dleafambientlighting_t ); + SafeWrite( g_hBSPFile, g_LeafAmbientLightingLDR.Base(), g_pBSPHeader->lumps[LUMP_LEAF_AMBIENT_LIGHTING].filelen ); + + SetAlignedLumpPosition( LUMP_LEAF_AMBIENT_INDEX ); + g_pBSPHeader->lumps[LUMP_LEAF_AMBIENT_INDEX].filelen = g_LeafAmbientIndexLDR.Count() * sizeof( dleafambientindex_t ); + SafeWrite( g_hBSPFile, g_LeafAmbientIndexLDR.Base(), g_pBSPHeader->lumps[LUMP_LEAF_AMBIENT_INDEX].filelen ); + + // mark as processed + g_Lumps.bLumpParsed[LUMP_LEAF_AMBIENT_LIGHTING] = true; + g_Lumps.bLumpParsed[LUMP_LEAF_AMBIENT_INDEX] = true; + } + + g_LeafAmbientLightingLDR.Purge(); + g_LeafAmbientIndexLDR.Purge(); + g_LeafAmbientLightingHDR.Purge(); + g_LeafAmbientIndexHDR.Purge(); + } +} + +void SwapLeafLumpToDisk( void ) +{ + DevMsg( "Swapping %s\n", GetLumpName( LUMP_LEAFS ) ); + + // load the leafs + int count = LoadLeafs(); + if ( g_bSwapOnWrite ) + { + g_Swap.SwapFieldsToTargetEndian( dleafs, count ); + } + + bool bOldLeafVersion = ( LumpVersion( LUMP_LEAFS ) == 0 ); + if ( bOldLeafVersion ) + { + // version has been converted in the load process + // not updating the version ye, SwapLeafAmbientLightingLumpToDisk() can detect + g_pBSPHeader->lumps[LUMP_LEAFS].filelen = count * sizeof( dleaf_t ); + } + + SetAlignedLumpPosition( LUMP_LEAFS ); + SafeWrite( g_hBSPFile, dleafs, g_pBSPHeader->lumps[LUMP_LEAFS].filelen ); + + SwapLeafAmbientLightingLumpToDisk(); + + if ( bOldLeafVersion ) + { + // version has been converted in the load process + // can now safely change + g_pBSPHeader->lumps[LUMP_LEAFS].version = 1; + } + +#if defined( BSP_USE_LESS_MEMORY ) + if ( dleafs ) + { + free( dleafs ); + dleafs = NULL; + } +#endif +} + +void SwapOcclusionLumpToDisk( void ) +{ + DevMsg( "Swapping %s\n", GetLumpName( LUMP_OCCLUSION ) ); + + LoadOcclusionLump(); + SetAlignedLumpPosition( LUMP_OCCLUSION ); + AddOcclusionLump(); +} + +void SwapPakfileLumpToDisk( const char *pInFilename ) +{ + DevMsg( "Swapping %s\n", GetLumpName( LUMP_PAKFILE ) ); + + byte *pakbuffer = NULL; + int paksize = CopyVariableLump( FIELD_CHARACTER, LUMP_PAKFILE, ( void ** )&pakbuffer ); + if ( paksize > 0 ) + { + GetPakFile()->ActivateByteSwapping( IsX360() ); + GetPakFile()->ParseFromBuffer( pakbuffer, paksize ); + + ConvertPakFileContents( pInFilename ); + } + free( pakbuffer ); + + SetAlignedLumpPosition( LUMP_PAKFILE, XBOX_DVD_SECTORSIZE ); + WritePakFileLump(); + + ReleasePakFileLumps(); +} + +void SwapGameLumpsToDisk( void ) +{ + DevMsg( "Swapping %s\n", GetLumpName( LUMP_GAME_LUMP ) ); + + g_GameLumps.ParseGameLump( g_pBSPHeader ); + SetAlignedLumpPosition( LUMP_GAME_LUMP ); + AddGameLumps(); +} + +//----------------------------------------------------------------------------- +// Generate a table of all static props, used for resolving static prop lighting +// files back to their actual mdl. +//----------------------------------------------------------------------------- +void BuildStaticPropNameTable() +{ + g_StaticPropNames.Purge(); + g_StaticPropInstances.Purge(); + + g_GameLumps.ParseGameLump( g_pBSPHeader ); + + GameLumpHandle_t hGameLump = g_GameLumps.GetGameLumpHandle( GAMELUMP_STATIC_PROPS ); + if ( hGameLump != g_GameLumps.InvalidGameLump() ) + { + int nVersion = g_GameLumps.GetGameLumpVersion( hGameLump ); + if ( nVersion < 4 ) + { + // old unsupported version + return; + } + + if ( nVersion != 4 && nVersion != 5 && nVersion != 6 ) + { + Error( "Unknown Static Prop Lump version %d!\n", nVersion ); + } + + byte *pGameLumpData = (byte *)g_GameLumps.GetGameLump( hGameLump ); + if ( pGameLumpData && g_GameLumps.GameLumpSize( hGameLump ) ) + { + // get the model dictionary + int count = ((int *)pGameLumpData)[0]; + pGameLumpData += sizeof( int ); + StaticPropDictLump_t *pStaticPropDictLump = (StaticPropDictLump_t *)pGameLumpData; + for ( int i = 0; i < count; i++ ) + { + g_StaticPropNames.AddToTail( pStaticPropDictLump[i].m_Name ); + } + pGameLumpData += count * sizeof( StaticPropDictLump_t ); + + // skip the leaf list + count = ((int *)pGameLumpData)[0]; + pGameLumpData += sizeof( int ); + pGameLumpData += count * sizeof( StaticPropLeafLump_t ); + + // get the instances + count = ((int *)pGameLumpData)[0]; + pGameLumpData += sizeof( int ); + for ( int i = 0; i < count; i++ ) + { + int propType; + if ( nVersion == 4 ) + { + propType = ((StaticPropLumpV4_t *)pGameLumpData)->m_PropType; + pGameLumpData += sizeof( StaticPropLumpV4_t ); + } + else if ( nVersion == 5 ) + { + propType = ((StaticPropLumpV5_t *)pGameLumpData)->m_PropType; + pGameLumpData += sizeof( StaticPropLumpV5_t ); + } + else + { + propType = ((StaticPropLump_t *)pGameLumpData)->m_PropType; + pGameLumpData += sizeof( StaticPropLump_t ); + } + g_StaticPropInstances.AddToTail( propType ); + } + } + } + + g_GameLumps.DestroyAllGameLumps(); +} + +int AlignBuffer( CUtlBuffer &buffer, int alignment ) +{ + unsigned int newPosition = AlignValue( buffer.TellPut(), alignment ); + int padLength = newPosition - buffer.TellPut(); + for ( int i = 0; ipLump->fileofs; + int fileOffsetB = pSortedLumpB->pLump->fileofs; + + int fileSizeA = pSortedLumpA->pLump->filelen; + int fileSizeB = pSortedLumpB->pLump->filelen; + + // invalid or empty lumps get sorted together + if ( !fileSizeA ) + { + fileOffsetA = 0; + } + if ( !fileSizeB ) + { + fileOffsetB = 0; + } + + // compare by offset, want ascending + if ( fileOffsetA < fileOffsetB ) + { + return -1; + } + else if ( fileOffsetA > fileOffsetB ) + { + return 1; + } + + return 0; +} + +bool CompressGameLump( dheader_t *pInBSPHeader, dheader_t *pOutBSPHeader, CUtlBuffer &outputBuffer, CompressFunc_t pCompressFunc ) +{ + CByteswap byteSwap; + + dgamelumpheader_t* pInGameLumpHeader = (dgamelumpheader_t*)(((byte *)pInBSPHeader) + pInBSPHeader->lumps[LUMP_GAME_LUMP].fileofs); + dgamelump_t* pInGameLump = (dgamelump_t*)(pInGameLumpHeader + 1); + + byteSwap.ActivateByteSwapping( true ); + byteSwap.SwapFieldsToTargetEndian( pInGameLumpHeader ); + byteSwap.SwapFieldsToTargetEndian( pInGameLump, pInGameLumpHeader->lumpCount ); + + unsigned int newOffset = outputBuffer.TellPut(); + outputBuffer.Put( pInGameLumpHeader, sizeof( dgamelumpheader_t ) ); + outputBuffer.Put( pInGameLump, pInGameLumpHeader->lumpCount * sizeof( dgamelump_t ) ); + + dgamelumpheader_t* pOutGameLumpHeader = (dgamelumpheader_t*)((byte *)outputBuffer.Base() + newOffset); + dgamelump_t* pOutGameLump = (dgamelump_t*)(pOutGameLumpHeader + 1); + + // add a dummy terminal gamelump + // purposely NOT updating the .filelen to reflect the compressed size, but leaving as original size + // callers use the next entry offset to determine compressed size + pOutGameLumpHeader->lumpCount++; + dgamelump_t dummyLump = { 0 }; + outputBuffer.Put( &dummyLump, sizeof( dgamelump_t ) ); + + for ( int i = 0; i < pInGameLumpHeader->lumpCount; i++ ) + { + CUtlBuffer inputBuffer; + CUtlBuffer compressedBuffer; + + pOutGameLump[i].fileofs = AlignBuffer( outputBuffer, 4 ); + + if ( pInGameLump[i].filelen ) + { + inputBuffer.SetExternalBuffer( ((byte *)pInBSPHeader) + pInGameLump[i].fileofs, pInGameLump[i].filelen, pInGameLump[i].filelen ); + + bool bCompressed = pCompressFunc( inputBuffer, compressedBuffer ); + if ( bCompressed ) + { + pOutGameLump[i].flags |= GAMELUMPFLAG_COMPRESSED; + + outputBuffer.Put( compressedBuffer.Base(), compressedBuffer.TellPut() ); + compressedBuffer.Purge(); + } + else + { + // as is + outputBuffer.Put( inputBuffer.Base(), inputBuffer.TellPut() ); + } + } + } + + // fix the dummy terminal lump + int lastLump = pOutGameLumpHeader->lumpCount-1; + pOutGameLump[lastLump].fileofs = outputBuffer.TellPut(); + + // fix the output for 360, swapping it back + byteSwap.SwapFieldsToTargetEndian( pOutGameLump, pOutGameLumpHeader->lumpCount ); + byteSwap.SwapFieldsToTargetEndian( pOutGameLumpHeader ); + + pOutBSPHeader->lumps[LUMP_GAME_LUMP].fileofs = newOffset; + pOutBSPHeader->lumps[LUMP_GAME_LUMP].filelen = outputBuffer.TellPut() - newOffset; + + return true; +} + +bool CompressBSP( CUtlBuffer &inputBuffer, CUtlBuffer &outputBuffer, CompressFunc_t pCompressFunc ) +{ + CByteswap byteSwap; + + dheader_t *pInBSPHeader = (dheader_t *)inputBuffer.Base(); + if ( pInBSPHeader->ident != BigLong( IDBSPHEADER ) || !pCompressFunc ) + { + // only compress 360 bsp's + return false; + } + + // bsp is 360, swap the header back + byteSwap.ActivateByteSwapping( true ); + byteSwap.SwapFieldsToTargetEndian( pInBSPHeader ); + + // output will be smaller, use input size as upper bound + outputBuffer.EnsureCapacity( inputBuffer.TellMaxPut() ); + outputBuffer.Put( pInBSPHeader, sizeof( dheader_t ) ); + + dheader_t *pOutBSPHeader = (dheader_t *)outputBuffer.Base(); + + // must adhere to input lump's offset order and process according to that, NOT lump num + // sort by offset order + CUtlVector< SortedLump_t > sortedLumps; + for ( int i = 0; i < HEADER_LUMPS; i++ ) + { + int iIndex = sortedLumps.AddToTail(); + sortedLumps[iIndex].lumpNum = i; + sortedLumps[iIndex].pLump = &pInBSPHeader->lumps[i]; + } + sortedLumps.Sort( SortLumpsByOffset ); + + // iterate in sorted order + for ( int i = 0; i < HEADER_LUMPS; ++i ) + { + SortedLump_t *pSortedLump = &sortedLumps[i]; + int lumpNum = pSortedLump->lumpNum; + + if ( !pSortedLump->pLump->filelen ) + { + // degenerate + pOutBSPHeader->lumps[lumpNum].fileofs = 0; + } + else + { + int alignment = 4; + if ( lumpNum == LUMP_PAKFILE ) + { + alignment = 2048; + } + unsigned int newOffset = AlignBuffer( outputBuffer, alignment ); + + // only set by compressed lumps, hides the uncompressed size + *((unsigned int *)pOutBSPHeader->lumps[lumpNum].fourCC) = 0; + + CUtlBuffer inputBuffer; + inputBuffer.SetExternalBuffer( ((byte *)pInBSPHeader) + pSortedLump->pLump->fileofs, pSortedLump->pLump->filelen, pSortedLump->pLump->filelen ); + + if ( lumpNum == LUMP_GAME_LUMP ) + { + // the game lump has to have each of its components individually compressed + CompressGameLump( pInBSPHeader, pOutBSPHeader, outputBuffer, pCompressFunc ); + } + else if ( lumpNum == LUMP_PAKFILE ) + { + // add as is + pOutBSPHeader->lumps[lumpNum].fileofs = newOffset; + outputBuffer.Put( inputBuffer.Base(), inputBuffer.TellPut() ); + } + else + { + CUtlBuffer compressedBuffer; + bool bCompressed = pCompressFunc( inputBuffer, compressedBuffer ); + if ( bCompressed ) + { + // placing the uncompressed size in the unused fourCC, will decode at runtime + *((unsigned int *)pOutBSPHeader->lumps[lumpNum].fourCC) = BigLong( inputBuffer.TellPut() ); + pOutBSPHeader->lumps[lumpNum].filelen = compressedBuffer.TellPut(); + pOutBSPHeader->lumps[lumpNum].fileofs = newOffset; + outputBuffer.Put( compressedBuffer.Base(), compressedBuffer.TellPut() ); + compressedBuffer.Purge(); + } + else + { + // add as is + pOutBSPHeader->lumps[lumpNum].fileofs = newOffset; + outputBuffer.Put( inputBuffer.Base(), inputBuffer.TellPut() ); + } + } + } + } + + // fix the output for 360, swapping it back + byteSwap.SetTargetBigEndian( true ); + byteSwap.SwapFieldsToTargetEndian( pOutBSPHeader ); + + return true; +} + +//----------------------------------------------------------------------------- +// For all lumps in a bsp: Loads the lump from file A, swaps it, writes it to file B. +// This limits the memory used for the swap process which helps the Xbox 360. +// +// NOTE: These lumps will be written to the file in exactly the order they appear here, +// so they can be shifted around if desired for file access optimization. +//----------------------------------------------------------------------------- +bool SwapBSPFile( const char *pInFilename, const char *pOutFilename, bool bSwapOnLoad, VTFConvertFunc_t pVTFConvertFunc, VHVFixupFunc_t pVHVFixupFunc, CompressFunc_t pCompressFunc ) +{ + DevMsg( "Creating %s\n", pOutFilename ); + + if ( !g_pFileSystem->FileExists( pInFilename ) ) + { + Warning( "Error! Couldn't open input file %s - BSP swap failed!\n", pInFilename ); + return false; + } + + g_hBSPFile = SafeOpenWrite( pOutFilename ); + if ( !g_hBSPFile ) + { + Warning( "Error! Couldn't open output file %s - BSP swap failed!\n", pOutFilename ); + return false; + } + + if ( !pVTFConvertFunc ) + { + Warning( "Error! Missing VTF Conversion function\n" ); + return false; + } + g_pVTFConvertFunc = pVTFConvertFunc; + + // optional VHV fixup + g_pVHVFixupFunc = pVHVFixupFunc; + + // optional compression callback + g_pCompressFunc = pCompressFunc; + + // These must be mutually exclusive + g_bSwapOnLoad = bSwapOnLoad; + g_bSwapOnWrite = !bSwapOnLoad; + + g_Swap.ActivateByteSwapping( true ); + + OpenBSPFile( pInFilename ); + + // CRC the bsp first + CRC32_t mapCRC; + CRC32_Init(&mapCRC); + if ( !CRC_MapFile( &mapCRC, pInFilename ) ) + { + Warning( "Failed to CRC the bsp\n" ); + return false; + } + + // hold a dictionary of all the static prop names + // this is needed to properly convert any VHV files inside the pak lump + BuildStaticPropNameTable(); + + // Set the output file pointer after the header + dheader_t dummyHeader = { 0 }; + SafeWrite( g_hBSPFile, &dummyHeader, sizeof( dheader_t ) ); + + // To allow for alignment fixups, the lumps will be written to the + // output file in the order they appear in this function. + + // NOTE: Flags for 360 !!!MUST!!! be first + SwapLumpToDisk< dflagslump_t >( LUMP_MAP_FLAGS ); + + // complex lump swaps first or for later contingent data + SwapLeafLumpToDisk(); + SwapOcclusionLumpToDisk(); + SwapGameLumpsToDisk(); + + // Strip dead or non relevant lumps + g_pBSPHeader->lumps[LUMP_DISP_LIGHTMAP_ALPHAS].filelen = 0; + g_pBSPHeader->lumps[LUMP_FACEIDS].filelen = 0; + + // Strip obsolete LDR in favor of HDR + if ( SwapLumpToDisk( LUMP_FACES_HDR ) ) + { + g_pBSPHeader->lumps[LUMP_FACES].filelen = 0; + } + else + { + // no HDR, keep LDR version + SwapLumpToDisk( LUMP_FACES ); + } + + if ( SwapLumpToDisk( LUMP_WORLDLIGHTS_HDR ) ) + { + g_pBSPHeader->lumps[LUMP_WORLDLIGHTS].filelen = 0; + } + else + { + // no HDR, keep LDR version + SwapLumpToDisk( LUMP_WORLDLIGHTS ); + } + + // Simple lump swaps + SwapLumpToDisk( FIELD_CHARACTER, LUMP_PHYSDISP ); + SwapLumpToDisk( FIELD_CHARACTER, LUMP_PHYSCOLLIDE ); + SwapLumpToDisk( FIELD_CHARACTER, LUMP_VISIBILITY ); + SwapLumpToDisk( LUMP_MODELS ); + SwapLumpToDisk( LUMP_VERTEXES ); + SwapLumpToDisk( LUMP_PLANES ); + SwapLumpToDisk( LUMP_NODES ); + SwapLumpToDisk( LUMP_TEXINFO ); + SwapLumpToDisk( LUMP_TEXDATA ); + SwapLumpToDisk( LUMP_DISPINFO ); + SwapLumpToDisk( LUMP_DISP_VERTS ); + SwapLumpToDisk( LUMP_DISP_TRIS ); + SwapLumpToDisk( FIELD_CHARACTER, LUMP_DISP_LIGHTMAP_SAMPLE_POSITIONS ); + SwapLumpToDisk( LUMP_FACE_MACRO_TEXTURE_INFO ); + SwapLumpToDisk( LUMP_PRIMITIVES ); + SwapLumpToDisk( LUMP_PRIMVERTS ); + SwapLumpToDisk( FIELD_SHORT, LUMP_PRIMINDICES ); + SwapLumpToDisk( LUMP_ORIGINALFACES ); + SwapLumpToDisk( FIELD_SHORT, LUMP_LEAFFACES ); + SwapLumpToDisk( FIELD_SHORT, LUMP_LEAFBRUSHES ); + SwapLumpToDisk( FIELD_INTEGER, LUMP_SURFEDGES ); + SwapLumpToDisk( LUMP_EDGES ); + SwapLumpToDisk( LUMP_BRUSHES ); + SwapLumpToDisk( LUMP_BRUSHSIDES ); + SwapLumpToDisk( LUMP_AREAS ); + SwapLumpToDisk( LUMP_AREAPORTALS ); + SwapLumpToDisk( FIELD_CHARACTER, LUMP_ENTITIES ); + SwapLumpToDisk( LUMP_LEAFWATERDATA ); + SwapLumpToDisk( FIELD_VECTOR, LUMP_VERTNORMALS ); + SwapLumpToDisk( FIELD_SHORT, LUMP_VERTNORMALINDICES ); + SwapLumpToDisk( FIELD_VECTOR, LUMP_CLIPPORTALVERTS ); + SwapLumpToDisk( LUMP_CUBEMAPS ); + SwapLumpToDisk( FIELD_CHARACTER, LUMP_TEXDATA_STRING_DATA ); + SwapLumpToDisk( FIELD_INTEGER, LUMP_TEXDATA_STRING_TABLE ); + SwapLumpToDisk( LUMP_OVERLAYS ); + SwapLumpToDisk( LUMP_WATEROVERLAYS ); + SwapLumpToDisk( FIELD_SHORT, LUMP_LEAFMINDISTTOWATER ); + SwapLumpToDisk( LUMP_OVERLAY_FADES ); + + + // NOTE: this data placed at the end for the sake of 360: + { + // NOTE: lighting must be the penultimate lump + // (allows 360 to free this memory part-way through map loading) + if ( SwapLumpToDisk( FIELD_CHARACTER, LUMP_LIGHTING_HDR ) ) + { + g_pBSPHeader->lumps[LUMP_LIGHTING].filelen = 0; + } + else + { + // no HDR, keep LDR version + SwapLumpToDisk( FIELD_CHARACTER, LUMP_LIGHTING ); + } + // NOTE: Pakfile for 360 !!!MUST!!! be last + SwapPakfileLumpToDisk( pInFilename ); + } + + + // Store the crc in the flags lump version field + g_pBSPHeader->lumps[LUMP_MAP_FLAGS].version = mapCRC; + + // Pad out the end of the file to a sector boundary for optimal IO + AlignFilePosition( g_hBSPFile, XBOX_DVD_SECTORSIZE ); + + // Warn of any lumps that didn't get swapped + for ( int i = 0; i < HEADER_LUMPS; ++i ) + { + if ( HasLump( i ) && !g_Lumps.bLumpParsed[i] ) + { + // a new lump got added that needs to have a swap function + Warning( "BSP: '%s', %s has no swap or copy function. Discarding!\n", pInFilename, GetLumpName(i) ); + + // the data didn't get copied, so don't reference garbage + g_pBSPHeader->lumps[i].filelen = 0; + } + } + + // Write the updated header + g_pFileSystem->Seek( g_hBSPFile, 0, FILESYSTEM_SEEK_HEAD ); + WriteData( g_pBSPHeader ); + g_pFileSystem->Close( g_hBSPFile ); + g_hBSPFile = 0; + + // Cleanup + g_Swap.ActivateByteSwapping( false ); + + CloseBSPFile(); + + g_StaticPropNames.Purge(); + g_StaticPropInstances.Purge(); + + DevMsg( "Finished BSP Swap\n" ); + + // caller provided compress func will further compress compatible lumps + if ( pCompressFunc ) + { + CUtlBuffer inputBuffer; + if ( !g_pFileSystem->ReadFile( pOutFilename, NULL, inputBuffer ) ) + { + Warning( "Error! Couldn't read file %s - final BSP compression failed!\n", pOutFilename ); + return false; + } + + CUtlBuffer outputBuffer; + if ( !CompressBSP( inputBuffer, outputBuffer, pCompressFunc ) ) + { + Warning( "Error! Failed to compress BSP '%s'!\n", pOutFilename ); + return false; + } + + g_hBSPFile = SafeOpenWrite( pOutFilename ); + if ( !g_hBSPFile ) + { + Warning( "Error! Couldn't open output file %s - BSP swap failed!\n", pOutFilename ); + return false; + } + SafeWrite( g_hBSPFile, outputBuffer.Base(), outputBuffer.TellPut() ); + g_pFileSystem->Close( g_hBSPFile ); + g_hBSPFile = 0; + } + + return true; +} + +//----------------------------------------------------------------------------- +// Get the pak lump from a BSP +//----------------------------------------------------------------------------- +bool GetPakFileLump( const char *pBSPFilename, void **pPakData, int *pPakSize ) +{ + *pPakData = NULL; + *pPakSize = 0; + + if ( !g_pFileSystem->FileExists( pBSPFilename ) ) + { + Warning( "Error! Couldn't open file %s!\n", pBSPFilename ); + return false; + } + + // determine endian nature + dheader_t *pHeader; + LoadFile( pBSPFilename, (void **)&pHeader ); + bool bSwap = ( pHeader->ident == BigLong( IDBSPHEADER ) ); + free( pHeader ); + + g_bSwapOnLoad = bSwap; + g_bSwapOnWrite = !bSwap; + + OpenBSPFile( pBSPFilename ); + + if ( g_pBSPHeader->lumps[LUMP_PAKFILE].filelen ) + { + *pPakSize = CopyVariableLump( FIELD_CHARACTER, LUMP_PAKFILE, pPakData ); + } + + CloseBSPFile(); + + return true; +} + +// compare function for qsort below +static int LumpOffsetCompare( const void *pElem1, const void *pElem2 ) +{ + int lump1 = *(byte *)pElem1; + int lump2 = *(byte *)pElem2; + + if ( lump1 != lump2 ) + { + // force LUMP_MAP_FLAGS to be first, always + if ( lump1 == LUMP_MAP_FLAGS ) + { + return -1; + } + else if ( lump2 == LUMP_MAP_FLAGS ) + { + return 1; + } + + // force LUMP_PAKFILE to be last, always + if ( lump1 == LUMP_PAKFILE ) + { + return 1; + } + else if ( lump2 == LUMP_PAKFILE ) + { + return -1; + } + } + + int fileOffset1 = g_pBSPHeader->lumps[lump1].fileofs; + int fileOffset2 = g_pBSPHeader->lumps[lump2].fileofs; + + // invalid or empty lumps will get sorted together + if ( !g_pBSPHeader->lumps[lump1].filelen ) + { + fileOffset1 = 0; + } + + if ( !g_pBSPHeader->lumps[lump2].filelen ) + { + fileOffset2 = 0; + } + + // compare by offset + if ( fileOffset1 < fileOffset2 ) + { + return -1; + } + else if ( fileOffset1 > fileOffset2 ) + { + return 1; + } + return 0; +} + +//----------------------------------------------------------------------------- +// Replace the pak lump in a BSP +//----------------------------------------------------------------------------- +bool SetPakFileLump( const char *pBSPFilename, const char *pNewFilename, void *pPakData, int pakSize ) +{ + if ( !g_pFileSystem->FileExists( pBSPFilename ) ) + { + Warning( "Error! Couldn't open file %s!\n", pBSPFilename ); + return false; + } + + // determine endian nature + dheader_t *pHeader; + LoadFile( pBSPFilename, (void **)&pHeader ); + bool bSwap = ( pHeader->ident == BigLong( IDBSPHEADER ) ); + free( pHeader ); + + g_bSwapOnLoad = bSwap; + g_bSwapOnWrite = bSwap; + + OpenBSPFile( pBSPFilename ); + + // save a copy of the old header + // generating a new bsp is a destructive operation + dheader_t oldHeader; + oldHeader = *g_pBSPHeader; + + g_hBSPFile = SafeOpenWrite( pNewFilename ); + if ( !g_hBSPFile ) + { + return false; + } + + // placeholder only, reset at conclusion + WriteData( &oldHeader ); + + // lumps must be reserialized in same relative offset order + // build sorted order table + int readOrder[HEADER_LUMPS]; + for ( int i=0; ilumps[lump].filelen; + if ( length ) + { + // save the lump data + int offset = g_pBSPHeader->lumps[lump].fileofs; + SetAlignedLumpPosition( lump ); + SafeWrite( g_hBSPFile, (byte *)g_pBSPHeader + offset, length ); + } + else + { + g_pBSPHeader->lumps[lump].fileofs = 0; + } + } + + // Always write the pak file at the end + // Pad out the end of the file to a sector boundary for optimal IO + g_pBSPHeader->lumps[LUMP_PAKFILE].fileofs = AlignFilePosition( g_hBSPFile, XBOX_DVD_SECTORSIZE ); + g_pBSPHeader->lumps[LUMP_PAKFILE].filelen = pakSize; + SafeWrite( g_hBSPFile, pPakData, pakSize ); + + // Pad out the end of the file to a sector boundary for optimal IO + AlignFilePosition( g_hBSPFile, XBOX_DVD_SECTORSIZE ); + + // Write the updated header + g_pFileSystem->Seek( g_hBSPFile, 0, FILESYSTEM_SEEK_HEAD ); + WriteData( g_pBSPHeader ); + g_pFileSystem->Close( g_hBSPFile ); + + CloseBSPFile(); + + return true; +} + +//----------------------------------------------------------------------------- +// Build a list of files that BSP owns, world/cubemap materials, static props, etc. +//----------------------------------------------------------------------------- +bool GetBSPDependants( const char *pBSPFilename, CUtlVector< CUtlString > *pList ) +{ + if ( !g_pFileSystem->FileExists( pBSPFilename ) ) + { + Warning( "Error! Couldn't open file %s!\n", pBSPFilename ); + return false; + } + + // must be set, but exact hdr not critical for dependant traversal + SetHDRMode( false ); + + LoadBSPFile( pBSPFilename ); + + char szBspName[MAX_PATH]; + V_FileBase( pBSPFilename, szBspName, sizeof( szBspName ) ); + V_SetExtension( szBspName, ".bsp", sizeof( szBspName ) ); + + // get embedded pak files, and internals + char szFilename[MAX_PATH]; + int fileSize; + int fileId = -1; + for ( ;; ) + { + fileId = GetPakFile()->GetNextFilename( fileId, szFilename, sizeof( szFilename ), fileSize ); + if ( fileId == -1 ) + { + break; + } + pList->AddToTail( szFilename ); + } + + // get all the world materials + for ( int i=0; iAddToTail( szFilename ); + } + + // get all the static props + GameLumpHandle_t hGameLump = g_GameLumps.GetGameLumpHandle( GAMELUMP_STATIC_PROPS ); + if ( hGameLump != g_GameLumps.InvalidGameLump() ) + { + byte *pGameLumpData = (byte *)g_GameLumps.GetGameLump( hGameLump ); + if ( pGameLumpData && g_GameLumps.GameLumpSize( hGameLump ) ) + { + int count = ((int *)pGameLumpData)[0]; + pGameLumpData += sizeof( int ); + + StaticPropDictLump_t *pStaticPropDictLump = (StaticPropDictLump_t *)pGameLumpData; + for ( int i=0; iAddToTail( pStaticPropDictLump[i].m_Name ); + } + } + } + + // get all the detail props + hGameLump = g_GameLumps.GetGameLumpHandle( GAMELUMP_DETAIL_PROPS ); + if ( hGameLump != g_GameLumps.InvalidGameLump() ) + { + byte *pGameLumpData = (byte *)g_GameLumps.GetGameLump( hGameLump ); + if ( pGameLumpData && g_GameLumps.GameLumpSize( hGameLump ) ) + { + int count = ((int *)pGameLumpData)[0]; + pGameLumpData += sizeof( int ); + + DetailObjectDictLump_t *pDetailObjectDictLump = (DetailObjectDictLump_t *)pGameLumpData; + for ( int i=0; iAddToTail( pDetailObjectDictLump[i].m_Name ); + } + pGameLumpData += count * sizeof( DetailObjectDictLump_t ); + + if ( g_GameLumps.GetGameLumpVersion( hGameLump ) == 4 ) + { + count = ((int *)pGameLumpData)[0]; + pGameLumpData += sizeof( int ); + if ( count ) + { + // All detail prop sprites must lie in the material detail/detailsprites + pList->AddToTail( "materials/detail/detailsprites.vmt" ); + } + } + } + } + + UnloadBSPFile(); + + return true; +} + diff --git a/mp/src/utils/common/bsplib.h b/mp/src/utils/common/bsplib.h new file mode 100644 index 00000000..83486e8b --- /dev/null +++ b/mp/src/utils/common/bsplib.h @@ -0,0 +1,404 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// $NoKeywords: $ +//=============================================================================// + +#ifndef BSPLIB_H +#define BSPLIB_H + +#ifdef _WIN32 +#pragma once +#endif + + +#include "bspfile.h" +#include "utlvector.h" +#include "utlstring.h" +#include "utllinkedlist.h" +#include "byteswap.h" +#ifdef ENGINE_DLL +#include "zone.h" +#endif + +#ifdef ENGINE_DLL +typedef CUtlVector > CDispLightmapSamplePositions; +#else +typedef CUtlVector CDispLightmapSamplePositions; +#endif + +class ISpatialQuery; +struct Ray_t; +class Vector2D; +struct portal_t; +class CUtlBuffer; +class IZip; + +// this is only true in vrad +extern bool g_bHDR; + +// default width/height of luxels in world units. +#define DEFAULT_LUXEL_SIZE ( 16.0f ) + +#define SINGLE_BRUSH_MAP (MAX_BRUSH_LIGHTMAP_DIM_INCLUDING_BORDER*MAX_BRUSH_LIGHTMAP_DIM_INCLUDING_BORDER) +#define SINGLEMAP (MAX_LIGHTMAP_DIM_INCLUDING_BORDER*MAX_LIGHTMAP_DIM_INCLUDING_BORDER) + +struct entity_t +{ + Vector origin; + int firstbrush; + int numbrushes; + epair_t *epairs; + + // only valid for func_areaportals + int areaportalnum; + int portalareas[2]; + portal_t *m_pPortalsLeadingIntoAreas[2]; // portals leading into portalareas +}; + +extern int num_entities; +extern entity_t entities[MAX_MAP_ENTITIES]; + +extern int nummodels; +extern dmodel_t dmodels[MAX_MAP_MODELS]; + +extern int visdatasize; +extern byte dvisdata[MAX_MAP_VISIBILITY]; +extern dvis_t *dvis; + +extern CUtlVector dlightdataHDR; +extern CUtlVector dlightdataLDR; +extern CUtlVector *pdlightdata; +extern CUtlVector dentdata; + +extern int numleafs; +#if !defined( _X360 ) +extern dleaf_t dleafs[MAX_MAP_LEAFS]; +#else +extern dleaf_t *dleafs; +#endif +extern CUtlVector *g_pLeafAmbientLighting; +extern CUtlVector *g_pLeafAmbientIndex; +extern unsigned short g_LeafMinDistToWater[MAX_MAP_LEAFS]; + +extern int numplanes; +extern dplane_t dplanes[MAX_MAP_PLANES]; + +extern int numvertexes; +extern dvertex_t dvertexes[MAX_MAP_VERTS]; + +extern int g_numvertnormalindices; // dfaces reference these. These index g_vertnormals. +extern unsigned short g_vertnormalindices[MAX_MAP_VERTNORMALS]; + +extern int g_numvertnormals; +extern Vector g_vertnormals[MAX_MAP_VERTNORMALS]; + +extern int numnodes; +extern dnode_t dnodes[MAX_MAP_NODES]; + +extern CUtlVector texinfo; + +extern int numtexdata; +extern dtexdata_t dtexdata[MAX_MAP_TEXDATA]; + +// displacement map .bsp file info +extern CUtlVector g_dispinfo; +extern CUtlVector g_DispVerts; +extern CUtlVector g_DispTris; +extern CDispLightmapSamplePositions g_DispLightmapSamplePositions; // LUMP_DISP_LIGHTMAP_SAMPLE_POSITIONS + +extern int numorigfaces; +extern dface_t dorigfaces[MAX_MAP_FACES]; + +extern int g_numprimitives; +extern dprimitive_t g_primitives[MAX_MAP_PRIMITIVES]; + +extern int g_numprimverts; +extern dprimvert_t g_primverts[MAX_MAP_PRIMVERTS]; + +extern int g_numprimindices; +extern unsigned short g_primindices[MAX_MAP_PRIMINDICES]; + +extern int numfaces; +extern dface_t dfaces[MAX_MAP_FACES]; + +extern int numfaceids; +extern CUtlVector dfaceids; + +extern int numfaces_hdr; +extern dface_t dfaces_hdr[MAX_MAP_FACES]; + +extern int numedges; +extern dedge_t dedges[MAX_MAP_EDGES]; + +extern int numleaffaces; +extern unsigned short dleaffaces[MAX_MAP_LEAFFACES]; + +extern int numleafbrushes; +extern unsigned short dleafbrushes[MAX_MAP_LEAFBRUSHES]; + +extern int numsurfedges; +extern int dsurfedges[MAX_MAP_SURFEDGES]; + +extern int numareas; +extern darea_t dareas[MAX_MAP_AREAS]; + +extern int numareaportals; +extern dareaportal_t dareaportals[MAX_MAP_AREAPORTALS]; + +extern int numbrushes; +extern dbrush_t dbrushes[MAX_MAP_BRUSHES]; + +extern int numbrushsides; +extern dbrushside_t dbrushsides[MAX_MAP_BRUSHSIDES]; + +extern int *pNumworldlights; +extern dworldlight_t *dworldlights; + +extern Vector g_ClipPortalVerts[MAX_MAP_PORTALVERTS]; +extern int g_nClipPortalVerts; + +extern dcubemapsample_t g_CubemapSamples[MAX_MAP_CUBEMAPSAMPLES]; +extern int g_nCubemapSamples; + +extern int g_nOverlayCount; +extern doverlay_t g_Overlays[MAX_MAP_OVERLAYS]; +extern doverlayfade_t g_OverlayFades[MAX_MAP_OVERLAYS]; // Parallel array of fade info in a separate lump to avoid breaking backwards compat + +extern int g_nWaterOverlayCount; +extern dwateroverlay_t g_WaterOverlays[MAX_MAP_WATEROVERLAYS]; + +extern CUtlVector g_TexDataStringData; +extern CUtlVector g_TexDataStringTable; + +extern int numleafwaterdata; +extern dleafwaterdata_t dleafwaterdata[MAX_MAP_LEAFWATERDATA]; + +extern CUtlVector g_FaceMacroTextureInfos; + +extern CUtlVector g_OccluderData; +extern CUtlVector g_OccluderPolyData; +extern CUtlVector g_OccluderVertexIndices; + +// level flags - see LVLFLAGS_xxx in bspfile.h +extern uint32 g_LevelFlags; + +// physics collision data +extern byte *g_pPhysCollide; +extern int g_PhysCollideSize; +extern byte *g_pPhysDisp; +extern int g_PhysDispSize; + +// Embedded pack/pak file +IZip *GetPakFile( void ); +IZip *GetSwapPakFile( void ); +void ClearPakFile( IZip *pak ); +void AddFileToPak( IZip *pak, const char *pRelativeName, const char *fullpath ); +void AddBufferToPak( IZip *pak, const char *pRelativeName, void *data, int length, bool bTextMode ); +bool FileExistsInPak( IZip *pak, const char *pRelativeName ); +bool ReadFileFromPak( IZip *pak, const char *pRelativeName, bool bTextMode, CUtlBuffer &buf ); +void RemoveFileFromPak( IZip *pak, const char *pRelativeName ); +int GetNextFilename( IZip *pak, int id, char *pBuffer, int bufferSize, int &fileSize ); +void ForceAlignment( IZip *pak, bool bAlign, bool bCompatibleFormat, unsigned int alignmentSize ); + +typedef bool (*CompressFunc_t)( CUtlBuffer &inputBuffer, CUtlBuffer &outputBuffer ); +typedef bool (*VTFConvertFunc_t)( const char *pDebugName, CUtlBuffer &sourceBuf, CUtlBuffer &targetBuf, CompressFunc_t pCompressFunc ); +typedef bool (*VHVFixupFunc_t)( const char *pVhvFilename, const char *pModelName, CUtlBuffer &sourceBuf, CUtlBuffer &targetBuf ); + +//----------------------------------------------------------------------------- +// Game lump memory storage +//----------------------------------------------------------------------------- +// NOTE: This is not optimal at all; since I expect client lumps to +// not be accessed all that often. + +struct GameLump_t +{ + GameLumpId_t m_Id; + unsigned short m_Flags; + unsigned short m_Version; + CUtlMemory< unsigned char > m_Memory; +}; + +//----------------------------------------------------------------------------- +// Handle to a game lump +//----------------------------------------------------------------------------- +typedef unsigned short GameLumpHandle_t; + +class CGameLump +{ +public: + //----------------------------------------------------------------------------- + // Convert four-CC code to a handle + back + //----------------------------------------------------------------------------- + GameLumpHandle_t GetGameLumpHandle( GameLumpId_t id ); + GameLumpId_t GetGameLumpId( GameLumpHandle_t handle ); + int GetGameLumpFlags( GameLumpHandle_t handle ); + int GetGameLumpVersion( GameLumpHandle_t handle ); + void ComputeGameLumpSizeAndCount( int& size, int& clumpCount ); + void ParseGameLump( dheader_t* pHeader ); + void SwapGameLump( GameLumpId_t id, int version, byte *dest, byte *src, int size ); + + + //----------------------------------------------------------------------------- + // Game lump accessor methods + //----------------------------------------------------------------------------- + void* GetGameLump( GameLumpHandle_t handle ); + int GameLumpSize( GameLumpHandle_t handle ); + + + //----------------------------------------------------------------------------- + // Game lump iteration methods + //----------------------------------------------------------------------------- + GameLumpHandle_t FirstGameLump(); + GameLumpHandle_t NextGameLump( GameLumpHandle_t handle ); + GameLumpHandle_t InvalidGameLump(); + + + //----------------------------------------------------------------------------- + // Game lump creation/destruction method + //----------------------------------------------------------------------------- + GameLumpHandle_t CreateGameLump( GameLumpId_t id, int size, int flags, int version ); + void DestroyGameLump( GameLumpHandle_t handle ); + void DestroyAllGameLumps(); + +private: + CUtlLinkedList< GameLump_t, GameLumpHandle_t > m_GameLumps; +}; + +extern CGameLump g_GameLumps; +extern CByteswap g_Swap; + +//----------------------------------------------------------------------------- +// Helper for the bspzip tool +//----------------------------------------------------------------------------- +void ExtractZipFileFromBSP( char *pBSPFileName, char *pZipFileName ); + + +//----------------------------------------------------------------------------- +// String table methods +//----------------------------------------------------------------------------- +const char * TexDataStringTable_GetString( int stringID ); +int TexDataStringTable_AddOrFindString( const char *pString ); + +void DecompressVis (byte *in, byte *decompressed); +int CompressVis (byte *vis, byte *dest); + +void OpenBSPFile( const char *filename ); +void CloseBSPFile(void); +void LoadBSPFile( const char *filename ); +void LoadBSPFile_FileSystemOnly( const char *filename ); +void LoadBSPFileTexinfo( const char *filename ); +void WriteBSPFile( const char *filename, char *pUnused = NULL ); +void PrintBSPFileSizes(void); +void PrintBSPPackDirectory(void); +void ReleasePakFileLumps(void); +bool SwapBSPFile( const char *filename, const char *swapFilename, bool bSwapOnLoad, VTFConvertFunc_t pVTFConvertFunc, VHVFixupFunc_t pVHVFixupFunc, CompressFunc_t pCompressFunc ); +bool GetPakFileLump( const char *pBSPFilename, void **pPakData, int *pPakSize ); +bool SetPakFileLump( const char *pBSPFilename, const char *pNewFilename, void *pPakData, int pakSize ); +void WriteLumpToFile( char *filename, int lump ); +void WriteLumpToFile( char *filename, int lump, int nLumpVersion, void *pBuffer, size_t nBufLen ); +bool GetBSPDependants( const char *pBSPFilename, CUtlVector< CUtlString > *pList ); +void UnloadBSPFile(); + +void ParseEntities (void); +void UnparseEntities (void); +void PrintEntity (entity_t *ent); + +void SetKeyValue (entity_t *ent, const char *key, const char *value); +char *ValueForKey (entity_t *ent, char *key); +// will return "" if not present +int IntForKey (entity_t *ent, char *key); +int IntForKeyWithDefault(entity_t *ent, char *key, int nDefault ); +vec_t FloatForKey (entity_t *ent, char *key); +vec_t FloatForKeyWithDefault (entity_t *ent, char *key, float default_value); +void GetVectorForKey (entity_t *ent, char *key, Vector& vec); +void GetVector2DForKey (entity_t *ent, char *key, Vector2D& vec); +void GetAnglesForKey (entity_t *ent, char *key, QAngle& vec); +epair_t *ParseEpair (void); +void StripTrailing (char *e); + +// Build a list of the face's vertices (index into dvertexes). +// points must be able to hold pFace->numedges indices. +void BuildFaceCalcWindingData( dface_t *pFace, int *points ); + +// Convert a tristrip to a trilist. +// Removes degenerates. +// Fills in pTriListIndices and pnTriListIndices. +// You must free pTriListIndices with delete[]. +void TriStripToTriList( + unsigned short const *pTriStripIndices, + int nTriStripIndices, + unsigned short **pTriListIndices, + int *pnTriListIndices ); + +// Calculates the lightmap coordinates at a given set of positions given the +// lightmap basis information. +void CalcTextureCoordsAtPoints( + float const texelsPerWorldUnits[2][4], + int const subtractOffset[2], + Vector const *pPoints, + int const nPoints, + Vector2D *pCoords ); + +// Figure out lightmap extents on all (lit) faces. +void UpdateAllFaceLightmapExtents(); + + +//----------------------------------------------------------------------------- +// Gets at an interface for the tree for enumeration of leaves in volumes. +//----------------------------------------------------------------------------- +ISpatialQuery* ToolBSPTree(); + +class IBSPNodeEnumerator +{ +public: + // call back with a node and a context + virtual bool EnumerateNode( int node, Ray_t const& ray, float f, int context ) = 0; + + // call back with a leaf and a context + virtual bool EnumerateLeaf( int leaf, Ray_t const& ray, float start, float end, int context ) = 0; +}; + +//----------------------------------------------------------------------------- +// Enumerates nodes + leafs in front to back order... +//----------------------------------------------------------------------------- +bool EnumerateNodesAlongRay( Ray_t const& ray, IBSPNodeEnumerator* pEnum, int context ); + + +//----------------------------------------------------------------------------- +// Helps us find all leaves associated with a particular cluster +//----------------------------------------------------------------------------- +struct clusterlist_t +{ + int leafCount; + CUtlVector leafs; +}; + +extern CUtlVector g_ClusterLeaves; + +// Call this to build the mapping from cluster to leaves +void BuildClusterTable( ); + +void GetPlatformMapPath( const char *pMapPath, char *pPlatformMapPath, int dxlevel, int maxLength ); + +void SetHDRMode( bool bHDR ); + +// ----------------------------------------------------------------------------- // +// Helper accessors for the various structures. +// ----------------------------------------------------------------------------- // + +inline ColorRGBExp32* dface_AvgLightColor( dface_t *pFace, int nLightStyleIndex ) +{ + return (ColorRGBExp32*)&(*pdlightdata)[pFace->lightofs - (nLightStyleIndex+1) * 4]; +} + +inline const char* TexInfo_TexName( int iTexInfo ) +{ + return TexDataStringTable_GetString( dtexdata[texinfo[iTexInfo].texdata].nameStringTableID ); +} + + +#endif // BSPLIB_H diff --git a/mp/src/utils/common/cmdlib.cpp b/mp/src/utils/common/cmdlib.cpp new file mode 100644 index 00000000..a6962380 --- /dev/null +++ b/mp/src/utils/common/cmdlib.cpp @@ -0,0 +1,1007 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// ----------------------- +// cmdlib.c +// ----------------------- +#include "tier0/platform.h" +#ifdef IS_WINDOWS_PC +#include +#endif +#include "cmdlib.h" +#include +#include +#include "tier1/strtools.h" +#ifdef _WIN32 +#include +#endif +#include "utlvector.h" +#include "filesystem_helpers.h" +#include "utllinkedlist.h" +#include "tier0/icommandline.h" +#include "KeyValues.h" +#include "filesystem_tools.h" + +#if defined( MPI ) + + #include "vmpi.h" + #include "vmpi_tools_shared.h" + +#endif + + +#if defined( _WIN32 ) || defined( WIN32 ) +#include +#endif + +#if defined( _X360 ) +#include "xbox/xbox_win32stubs.h" +#endif + +// set these before calling CheckParm +int myargc; +char **myargv; + +char com_token[1024]; + +qboolean archive; +char archivedir[1024]; + +FileHandle_t g_pLogFile = 0; + +CUtlLinkedList g_CleanupFunctions; +CUtlLinkedList g_ExtraSpewHooks; + +bool g_bStopOnExit = false; +void (*g_ExtraSpewHook)(const char*) = NULL; + +#if defined( _WIN32 ) || defined( WIN32 ) + +void CmdLib_FPrintf( FileHandle_t hFile, const char *pFormat, ... ) +{ + static CUtlVector buf; + if ( buf.Count() == 0 ) + buf.SetCount( 1024 ); + + va_list marker; + va_start( marker, pFormat ); + + while ( 1 ) + { + int ret = Q_vsnprintf( buf.Base(), buf.Count(), pFormat, marker ); + if ( ret >= 0 ) + { + // Write the string. + g_pFileSystem->Write( buf.Base(), ret, hFile ); + + break; + } + else + { + // Make the buffer larger. + int newSize = buf.Count() * 2; + buf.SetCount( newSize ); + if ( buf.Count() != newSize ) + { + Error( "CmdLib_FPrintf: can't allocate space for text." ); + } + } + } + + va_end( marker ); +} + +char* CmdLib_FGets( char *pOut, int outSize, FileHandle_t hFile ) +{ + int iCur=0; + for ( ; iCur < (outSize-1); iCur++ ) + { + char c; + if ( !g_pFileSystem->Read( &c, 1, hFile ) ) + { + if ( iCur == 0 ) + return NULL; + else + break; + } + + pOut[iCur] = c; + if ( c == '\n' ) + break; + + if ( c == EOF ) + { + if ( iCur == 0 ) + return NULL; + else + break; + } + } + + pOut[iCur] = 0; + return pOut; +} + +#if !defined( _X360 ) +#include +#endif + +// This pauses before exiting if they use -StopOnExit. Useful for debugging. +class CExitStopper +{ +public: + ~CExitStopper() + { + if ( g_bStopOnExit ) + { + Warning( "\nPress any key to quit.\n" ); + getch(); + } + } +} g_ExitStopper; + + +static unsigned short g_InitialColor = 0xFFFF; +static unsigned short g_LastColor = 0xFFFF; +static unsigned short g_BadColor = 0xFFFF; +static WORD g_BackgroundFlags = 0xFFFF; +static void GetInitialColors( ) +{ +#if !defined( _X360 ) + // Get the old background attributes. + CONSOLE_SCREEN_BUFFER_INFO oldInfo; + GetConsoleScreenBufferInfo( GetStdHandle( STD_OUTPUT_HANDLE ), &oldInfo ); + g_InitialColor = g_LastColor = oldInfo.wAttributes & (FOREGROUND_RED|FOREGROUND_GREEN|FOREGROUND_BLUE|FOREGROUND_INTENSITY); + g_BackgroundFlags = oldInfo.wAttributes & (BACKGROUND_RED|BACKGROUND_GREEN|BACKGROUND_BLUE|BACKGROUND_INTENSITY); + + g_BadColor = 0; + if (g_BackgroundFlags & BACKGROUND_RED) + g_BadColor |= FOREGROUND_RED; + if (g_BackgroundFlags & BACKGROUND_GREEN) + g_BadColor |= FOREGROUND_GREEN; + if (g_BackgroundFlags & BACKGROUND_BLUE) + g_BadColor |= FOREGROUND_BLUE; + if (g_BackgroundFlags & BACKGROUND_INTENSITY) + g_BadColor |= FOREGROUND_INTENSITY; +#endif +} + +WORD SetConsoleTextColor( int red, int green, int blue, int intensity ) +{ + WORD ret = g_LastColor; +#if !defined( _X360 ) + + g_LastColor = 0; + if( red ) g_LastColor |= FOREGROUND_RED; + if( green ) g_LastColor |= FOREGROUND_GREEN; + if( blue ) g_LastColor |= FOREGROUND_BLUE; + if( intensity ) g_LastColor |= FOREGROUND_INTENSITY; + + // Just use the initial color if there's a match... + if (g_LastColor == g_BadColor) + g_LastColor = g_InitialColor; + + SetConsoleTextAttribute( GetStdHandle( STD_OUTPUT_HANDLE ), g_LastColor | g_BackgroundFlags ); +#endif + return ret; +} + +void RestoreConsoleTextColor( WORD color ) +{ +#if !defined( _X360 ) + SetConsoleTextAttribute( GetStdHandle( STD_OUTPUT_HANDLE ), color | g_BackgroundFlags ); + g_LastColor = color; +#endif +} + + +#if defined( CMDLIB_NODBGLIB ) + +// This can go away when everything is in bin. +void Error( char const *pMsg, ... ) +{ + va_list marker; + va_start( marker, pMsg ); + vprintf( pMsg, marker ); + va_end( marker ); + + exit( -1 ); +} + +#else + +CRITICAL_SECTION g_SpewCS; +bool g_bSpewCSInitted = false; +bool g_bSuppressPrintfOutput = false; + +SpewRetval_t CmdLib_SpewOutputFunc( SpewType_t type, char const *pMsg ) +{ + // Hopefully two threads won't call this simultaneously right at the start! + if ( !g_bSpewCSInitted ) + { + InitializeCriticalSection( &g_SpewCS ); + g_bSpewCSInitted = true; + } + + WORD old; + SpewRetval_t retVal; + + EnterCriticalSection( &g_SpewCS ); + { + if (( type == SPEW_MESSAGE ) || (type == SPEW_LOG )) + { + Color c = *GetSpewOutputColor(); + if ( c.r() != 255 || c.g() != 255 || c.b() != 255 ) + { + // custom color + old = SetConsoleTextColor( c.r(), c.g(), c.b(), c.a() ); + } + else + { + old = SetConsoleTextColor( 1, 1, 1, 0 ); + } + retVal = SPEW_CONTINUE; + } + else if( type == SPEW_WARNING ) + { + old = SetConsoleTextColor( 1, 1, 0, 1 ); + retVal = SPEW_CONTINUE; + } + else if( type == SPEW_ASSERT ) + { + old = SetConsoleTextColor( 1, 0, 0, 1 ); + retVal = SPEW_DEBUGGER; + +#ifdef MPI + // VMPI workers don't want to bring up dialogs and suchlike. + // They need to have a special function installed to handle + // the exceptions and write the minidumps. + // Install the function after VMPI_Init with a call: + // SetupToolsMinidumpHandler( VMPI_ExceptionFilter ); + if ( g_bUseMPI && !g_bMPIMaster && !Plat_IsInDebugSession() ) + { + // Generating an exception and letting the + // installed handler handle it + ::RaiseException + ( + 0, // dwExceptionCode + EXCEPTION_NONCONTINUABLE, // dwExceptionFlags + 0, // nNumberOfArguments, + NULL // const ULONG_PTR* lpArguments + ); + + // Never get here (non-continuable exception) + + VMPI_HandleCrash( pMsg, NULL, true ); + exit( 0 ); + } +#endif + } + else if( type == SPEW_ERROR ) + { + old = SetConsoleTextColor( 1, 0, 0, 1 ); + retVal = SPEW_ABORT; // doesn't matter.. we exit below so we can return an errorlevel (which dbg.dll doesn't do). + } + else + { + old = SetConsoleTextColor( 1, 1, 1, 1 ); + retVal = SPEW_CONTINUE; + } + + if ( !g_bSuppressPrintfOutput || type == SPEW_ERROR ) + printf( "%s", pMsg ); + + OutputDebugString( pMsg ); + + if ( type == SPEW_ERROR ) + { + printf( "\n" ); + OutputDebugString( "\n" ); + } + + if( g_pLogFile ) + { + CmdLib_FPrintf( g_pLogFile, "%s", pMsg ); + g_pFileSystem->Flush( g_pLogFile ); + } + + // Dispatch to other spew hooks. + FOR_EACH_LL( g_ExtraSpewHooks, i ) + g_ExtraSpewHooks[i]( pMsg ); + + RestoreConsoleTextColor( old ); + } + LeaveCriticalSection( &g_SpewCS ); + + if ( type == SPEW_ERROR ) + { + CmdLib_Exit( 1 ); + } + + return retVal; +} + + +void InstallSpewFunction() +{ + setvbuf( stdout, NULL, _IONBF, 0 ); + setvbuf( stderr, NULL, _IONBF, 0 ); + + SpewOutputFunc( CmdLib_SpewOutputFunc ); + GetInitialColors(); +} + + +void InstallExtraSpewHook( SpewHookFn pFn ) +{ + g_ExtraSpewHooks.AddToTail( pFn ); +} + +#if 0 +void CmdLib_AllocError( unsigned long size ) +{ + Error( "Error trying to allocate %d bytes.\n", size ); +} + + +int CmdLib_NewHandler( size_t size ) +{ + CmdLib_AllocError( size ); + return 0; +} +#endif + +void InstallAllocationFunctions() +{ +// _set_new_mode( 1 ); // so if malloc() fails, we exit. +// _set_new_handler( CmdLib_NewHandler ); +} + +void SetSpewFunctionLogFile( char const *pFilename ) +{ + Assert( (!g_pLogFile) ); + g_pLogFile = g_pFileSystem->Open( pFilename, "a" ); + + Assert( g_pLogFile ); + if (!g_pLogFile) + Error("Can't create LogFile:\"%s\"\n", pFilename ); + + CmdLib_FPrintf( g_pLogFile, "\n\n\n" ); +} + + +void CloseSpewFunctionLogFile() +{ + if ( g_pFileSystem && g_pLogFile ) + { + g_pFileSystem->Close( g_pLogFile ); + g_pLogFile = FILESYSTEM_INVALID_HANDLE; + } +} + + +void CmdLib_AtCleanup( CleanupFn pFn ) +{ + g_CleanupFunctions.AddToTail( pFn ); +} + + +void CmdLib_Cleanup() +{ + CloseSpewFunctionLogFile(); + + CmdLib_TermFileSystem(); + + FOR_EACH_LL( g_CleanupFunctions, i ) + g_CleanupFunctions[i](); + +#if defined( MPI ) + // Unfortunately, when you call exit(), even if you have things registered with atexit(), + // threads go into a seemingly undefined state where GetExitCodeThread gives STILL_ACTIVE + // and WaitForSingleObject will stall forever on the thread. Because of this, we must cleanup + // everything that uses threads before exiting. + VMPI_Finalize(); +#endif +} + + +void CmdLib_Exit( int exitCode ) +{ + TerminateProcess( GetCurrentProcess(), 1 ); +} + + + +#endif + +#endif + + + + +/* +=================== +ExpandWildcards + +Mimic unix command line expansion +=================== +*/ +#define MAX_EX_ARGC 1024 +int ex_argc; +char *ex_argv[MAX_EX_ARGC]; +#if defined( _WIN32 ) && !defined( _X360 ) +#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; + + Q_ExtractFilePath (path, filebase, sizeof( 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 + + +// only printf if in verbose mode +qboolean verbose = false; +void qprintf (const char *format, ...) +{ + if (!verbose) + return; + + va_list argptr; + va_start (argptr,format); + + char str[2048]; + Q_vsnprintf( str, sizeof(str), format, argptr ); + +#if defined( CMDLIB_NODBGLIB ) + printf( "%s", str ); +#else + Msg( "%s", str ); +#endif + + va_end (argptr); +} + + +// ---------------------------------------------------------------------------------------------------- // +// Helpers. +// ---------------------------------------------------------------------------------------------------- // + +static void CmdLib_getwd( char *out, int outSize ) +{ +#if defined( _WIN32 ) || defined( WIN32 ) + _getcwd( out, outSize ); + Q_strncat( out, "\\", outSize, COPY_ALL_CHARACTERS ); +#else + getcwd(out, outSize); + strcat(out, "/"); +#endif + Q_FixSlashes( out ); +} + +char *ExpandArg (char *path) +{ + static char full[1024]; + + if (path[0] != '/' && path[0] != '\\' && path[1] != ':') + { + CmdLib_getwd (full, sizeof( full )); + Q_strncat (full, path, sizeof( full ), COPY_ALL_CHARACTERS); + } + else + Q_strncpy (full, path, sizeof( full )); + return full; +} + + +char *ExpandPath (char *path) +{ + static char full[1024]; + if (path[0] == '/' || path[0] == '\\' || path[1] == ':') + return path; + sprintf (full, "%s%s", qdir, path); + return full; +} + + + +char *copystring(const char *s) +{ + char *b; + b = (char *)malloc(strlen(s)+1); + strcpy (b, s); + return b; +} + + +void GetHourMinuteSeconds( int nInputSeconds, int &nHours, int &nMinutes, int &nSeconds ) +{ +} + + +void GetHourMinuteSecondsString( int nInputSeconds, char *pOut, int outLen ) +{ + int nMinutes = nInputSeconds / 60; + int nSeconds = nInputSeconds - nMinutes * 60; + int nHours = nMinutes / 60; + nMinutes -= nHours * 60; + + const char *extra[2] = { "", "s" }; + + if ( nHours > 0 ) + Q_snprintf( pOut, outLen, "%d hour%s, %d minute%s, %d second%s", nHours, extra[nHours != 1], nMinutes, extra[nMinutes != 1], nSeconds, extra[nSeconds != 1] ); + else if ( nMinutes > 0 ) + Q_snprintf( pOut, outLen, "%d minute%s, %d second%s", nMinutes, extra[nMinutes != 1], nSeconds, extra[nSeconds != 1] ); + else + Q_snprintf( pOut, outLen, "%d second%s", nSeconds, extra[nSeconds != 1] ); +} + + +void Q_mkdir (char *path) +{ +#if defined( _WIN32 ) || defined( WIN32 ) + if (_mkdir (path) != -1) + return; +#else + if (mkdir (path, 0777) != -1) + return; +#endif +// if (errno != EEXIST) + Error ("mkdir failed %s\n", path ); +} + +void CmdLib_InitFileSystem( const char *pFilename, int maxMemoryUsage ) +{ + FileSystem_Init( pFilename, maxMemoryUsage ); + if ( !g_pFileSystem ) + Error( "CmdLib_InitFileSystem failed." ); +} + +void CmdLib_TermFileSystem() +{ + FileSystem_Term(); +} + +CreateInterfaceFn CmdLib_GetFileSystemFactory() +{ + return FileSystem_GetFactory(); +} + + +/* +============ +FileTime + +returns -1 if not present +============ +*/ +int FileTime (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) +{ + return (char*)ParseFile( data, com_token, NULL ); +} + + +/* +============================================================================= + + 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 (char *check) +{ + int i; + + for (i = 1;iSize( f ); +} + + +FileHandle_t SafeOpenWrite ( const char *filename ) +{ + FileHandle_t f = g_pFileSystem->Open(filename, "wb"); + + if (!f) + { + //Error ("Error opening %s: %s",filename,strerror(errno)); + // BUGBUG: No way to get equivalent of errno from IFileSystem! + Error ("Error opening %s! (Check for write enable)\n",filename); + } + + return f; +} + +#define MAX_CMDLIB_BASE_PATHS 10 +static char g_pBasePaths[MAX_CMDLIB_BASE_PATHS][MAX_PATH]; +static int g_NumBasePaths = 0; + +void CmdLib_AddBasePath( const char *pPath ) +{ +// printf( "CmdLib_AddBasePath( \"%s\" )\n", pPath ); + if( g_NumBasePaths < MAX_CMDLIB_BASE_PATHS ) + { + Q_strncpy( g_pBasePaths[g_NumBasePaths], pPath, MAX_PATH ); + Q_FixSlashes( g_pBasePaths[g_NumBasePaths] ); + g_NumBasePaths++; + } + else + { + Assert( 0 ); + } +} + +bool CmdLib_HasBasePath( const char *pFileName_, int &pathLength ) +{ + char *pFileName = ( char * )_alloca( strlen( pFileName_ ) + 1 ); + strcpy( pFileName, pFileName_ ); + Q_FixSlashes( pFileName ); + pathLength = 0; + int i; + for( i = 0; i < g_NumBasePaths; i++ ) + { + // see if we can rip the base off of the filename. + if( Q_strncasecmp( g_pBasePaths[i], pFileName, strlen( g_pBasePaths[i] ) ) == 0 ) + { + pathLength = strlen( g_pBasePaths[i] ); + return true; + } + } + return false; +} + +int CmdLib_GetNumBasePaths( void ) +{ + return g_NumBasePaths; +} + +const char *CmdLib_GetBasePath( int i ) +{ + Assert( i >= 0 && i < g_NumBasePaths ); + return g_pBasePaths[i]; +} + + +//----------------------------------------------------------------------------- +// Like ExpandPath but expands the path for each base path like SafeOpenRead +//----------------------------------------------------------------------------- +int CmdLib_ExpandWithBasePaths( CUtlVector< CUtlString > &expandedPathList, const char *pszPath ) +{ + int nPathLength = 0; + + pszPath = ExpandPath( const_cast< char * >( pszPath ) ); // Kind of redundant but it's how CmdLib_HasBasePath needs things + + if ( CmdLib_HasBasePath( pszPath, nPathLength ) ) + { + pszPath = pszPath + nPathLength; + for ( int i = 0; i < CmdLib_GetNumBasePaths(); ++i ) + { + CUtlString &expandedPath = expandedPathList[ expandedPathList.AddToTail( CmdLib_GetBasePath( i ) ) ]; + expandedPath += pszPath; + } + } + else + { + expandedPathList.AddToTail( pszPath ); + } + + return expandedPathList.Count(); +} + + +FileHandle_t SafeOpenRead( const char *filename ) +{ + int pathLength; + FileHandle_t f = 0; + if( CmdLib_HasBasePath( filename, pathLength ) ) + { + filename = filename + pathLength; + int i; + for( i = 0; i < g_NumBasePaths; i++ ) + { + char tmp[MAX_PATH]; + strcpy( tmp, g_pBasePaths[i] ); + strcat( tmp, filename ); + f = g_pFileSystem->Open( tmp, "rb" ); + if( f ) + { + return f; + } + } + Error ("Error opening %s\n",filename ); + return f; + } + else + { + f = g_pFileSystem->Open( filename, "rb" ); + if ( !f ) + Error ("Error opening %s",filename ); + + return f; + } +} + +void SafeRead( FileHandle_t f, void *buffer, int count) +{ + if ( g_pFileSystem->Read (buffer, count, f) != (size_t)count) + Error ("File read failure"); +} + + +void SafeWrite ( FileHandle_t f, void *buffer, int count) +{ + if (g_pFileSystem->Write (buffer, count, f) != (size_t)count) + Error ("File write failure"); +} + + +/* +============== +FileExists +============== +*/ +qboolean FileExists ( const char *filename ) +{ + FileHandle_t hFile = g_pFileSystem->Open( filename, "rb" ); + if ( hFile == FILESYSTEM_INVALID_HANDLE ) + { + return false; + } + else + { + g_pFileSystem->Close( hFile ); + return true; + } +} + +/* +============== +LoadFile +============== +*/ +int LoadFile ( const char *filename, void **bufferptr ) +{ + int length = 0; + void *buffer; + + FileHandle_t f = SafeOpenRead (filename); + if ( FILESYSTEM_INVALID_HANDLE != f ) + { + length = Q_filelength (f); + buffer = malloc (length+1); + ((char *)buffer)[length] = 0; + SafeRead (f, buffer, length); + g_pFileSystem->Close (f); + *bufferptr = buffer; + } + else + { + *bufferptr = NULL; + } + return length; +} + + + +/* +============== +SaveFile +============== +*/ +void SaveFile ( const char *filename, void *buffer, int count ) +{ + FileHandle_t f = SafeOpenWrite (filename); + SafeWrite (f, buffer, count); + g_pFileSystem->Close (f); +} + +/* +==================== +Extract file parts +==================== +*/ +// FIXME: should include the slash, otherwise +// backing to an empty path will be wrong when appending a slash + + + +/* +============== +ParseNum / ParseHex +============== +*/ +int ParseHex (char *hex) +{ + 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 (char *str) +{ + if (str[0] == '$') + return ParseHex (str+1); + if (str[0] == '0' && str[1] == 'x') + return ParseHex (str+2); + return atol (str); +} + +/* +============ +CreatePath +============ +*/ +void CreatePath (char *path) +{ + char *ofs, c; + + // strip the drive + if (path[1] == ':') + path += 2; + + for (ofs = path+1 ; *ofs ; ofs++) + { + c = *ofs; + if (c == '/' || c == '\\') + { // create the directory + *ofs = 0; + Q_mkdir (path); + *ofs = c; + } + } +} + +//----------------------------------------------------------------------------- +// Creates a path, path may already exist +//----------------------------------------------------------------------------- +#if defined( _WIN32 ) || defined( WIN32 ) +void SafeCreatePath( char *path ) +{ + char *ptr; + + // skip past the drive path, but don't strip + if ( path[1] == ':' ) + { + ptr = strchr( path, '\\' ); + } + else + { + ptr = path; + } + while ( ptr ) + { + ptr = strchr( ptr+1, '\\' ); + if ( ptr ) + { + *ptr = '\0'; + _mkdir( path ); + *ptr = '\\'; + } + } +} +#endif + +/* +============ +QCopyFile + + Used to archive source files +============ +*/ +void QCopyFile (char *from, char *to) +{ + void *buffer; + int length; + + length = LoadFile (from, &buffer); + CreatePath (to); + SaveFile (to, buffer, length); + free (buffer); +} + + + diff --git a/mp/src/utils/common/cmdlib.h b/mp/src/utils/common/cmdlib.h new file mode 100644 index 00000000..50fa9d20 --- /dev/null +++ b/mp/src/utils/common/cmdlib.h @@ -0,0 +1,178 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// $NoKeywords: $ +//=============================================================================// + +#ifndef CMDLIB_H +#define CMDLIB_H + +#ifdef _WIN32 +#pragma once +#endif + +// cmdlib.h + +#include "basetypes.h" + +// This can go away when everything is in bin. +#if defined( CMDLIB_NODBGLIB ) + void Error( PRINTF_FORMAT_STRING char const *pMsg, ... ); +#else + #include "tier0/dbg.h" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include "filesystem.h" +#include "filesystem_tools.h" +#include "tier1/utlstring.h" + + +// Tools should use this as the read path ID. It'll look into the paths specified by gameinfo.txt +#define TOOLS_READ_PATH_ID "GAME" + + +// Tools should use this to fprintf data to files. +void CmdLib_FPrintf( FileHandle_t hFile, PRINTF_FORMAT_STRING const char *pFormat, ... ); +char* CmdLib_FGets( char *pOut, int outSize, FileHandle_t hFile ); + + +// This can be set so Msg() sends output to hook functions (like the VMPI MySQL database), +// but doesn't actually printf the output. +extern bool g_bSuppressPrintfOutput; + +extern IBaseFileSystem *g_pFileSystem; + +// These call right into the functions in filesystem_tools.h +void CmdLib_InitFileSystem( const char *pFilename, int maxMemoryUsage = 0 ); +void CmdLib_TermFileSystem(); // GracefulExit calls this. +CreateInterfaceFn CmdLib_GetFileSystemFactory(); + + +#ifdef _WIN32 +#pragma warning(disable : 4244) // MIPS +#pragma warning(disable : 4136) // X86 +#pragma warning(disable : 4051) // ALPHA + +#pragma warning(disable : 4018) // signed/unsigned mismatch +#pragma warning(disable : 4305) // truncate from double to float + +#pragma warning(disable : 4389) // singned/unsigned mismatch in == +#pragma warning(disable: 4512) // assignment operator could not be generated +#endif + + +// the dec offsetof macro doesnt work very well... +#define myoffsetof(type,identifier) offsetof( type, identifier ) + + +// set these before calling CheckParm +extern int myargc; +extern char **myargv; + +int Q_filelength (FileHandle_t f); +int FileTime (char *path); + +void Q_mkdir( char *path ); + +char *ExpandArg (char *path); // expand relative to CWD +char *ExpandPath (char *path); // expand relative to gamedir + +char *ExpandPathAndArchive (char *path); + +// Fills in pOut with "X hours, Y minutes, Z seconds". Leaves out hours or minutes if they're zero. +void GetHourMinuteSecondsString( int nInputSeconds, char *pOut, int outLen ); + + + +int CheckParm (char *check); + +FileHandle_t SafeOpenWrite ( const char *filename ); +FileHandle_t SafeOpenRead ( const char *filename ); +void SafeRead( FileHandle_t f, void *buffer, int count); +void SafeWrite( FileHandle_t f, void *buffer, int count); + +int LoadFile ( const char *filename, void **bufferptr ); +void SaveFile ( const char *filename, void *buffer, int count ); +qboolean FileExists ( const char *filename ); + +int ParseNum (char *str); + +// Do a printf in the specified color. +#define CP_ERROR stderr, 1, 0, 0, 1 // default colors.. +#define CP_WARNING stderr, 1, 1, 0, 1 +#define CP_STARTUP stdout, 0, 1, 1, 1 +#define CP_NOTIFY stdout, 1, 1, 1, 1 +void ColorPrintf( FILE *pFile, bool red, bool green, bool blue, bool intensity, PRINTF_FORMAT_STRING char const *pFormat, ... ); + +// Initialize spew output. +void InstallSpewFunction(); + +// This registers an extra callback for spew output. +typedef void (*SpewHookFn)( const char * ); +void InstallExtraSpewHook( SpewHookFn pFn ); + +// Install allocation hooks so we error out if an allocation can't happen. +void InstallAllocationFunctions(); + +// This shuts down mgrs that use threads gracefully. If you just call exit(), the threads can +// get in a state where you can't tell if they are shutdown or not, and it can stall forever. +typedef void (*CleanupFn)(); +void CmdLib_AtCleanup( CleanupFn pFn ); // register a callback when Cleanup() is called. +void CmdLib_Cleanup(); +void CmdLib_Exit( int exitCode ); // Use this to cleanup and call exit(). + +// entrypoint if chaining spew functions +SpewRetval_t CmdLib_SpewOutputFunc( SpewType_t type, char const *pMsg ); +unsigned short SetConsoleTextColor( int red, int green, int blue, int intensity ); +void RestoreConsoleTextColor( unsigned short color ); + +// Append all spew output to the specified file. +void SetSpewFunctionLogFile( char const *pFilename ); + +char *COM_Parse (char *data); + +extern char com_token[1024]; + +char *copystring(const char *s); + +void CreatePath( char *path ); +void QCopyFile( char *from, char *to ); +void SafeCreatePath( char *path ); + +extern qboolean archive; +extern char archivedir[1024]; + +extern qboolean verbose; + +void qprintf( PRINTF_FORMAT_STRING const char *format, ... ); + +void ExpandWildcards (int *argc, char ***argv); + +void CmdLib_AddBasePath( const char *pBasePath ); +bool CmdLib_HasBasePath( const char *pFileName, int &pathLength ); +int CmdLib_GetNumBasePaths( void ); +const char *CmdLib_GetBasePath( int i ); +// Like ExpandPath but expands the path for each base path like SafeOpenRead +int CmdLib_ExpandWithBasePaths( CUtlVector< CUtlString > &expandedPathList, const char *pszPath ); + +extern bool g_bStopOnExit; + +// for compression routines +typedef struct +{ + byte *data; + int count; +} cblock_t; + + +#endif // CMDLIB_H \ No newline at end of file diff --git a/mp/src/utils/common/consolewnd.cpp b/mp/src/utils/common/consolewnd.cpp new file mode 100644 index 00000000..8802e39e --- /dev/null +++ b/mp/src/utils/common/consolewnd.cpp @@ -0,0 +1,333 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include +#include "consolewnd.h" + + +#pragma warning( disable : 4311 ) // warning C4311: 'reinterpret_cast' : pointer truncation from 'CConsoleWnd *const ' to 'LONG' +#pragma warning( disable : 4312 ) // warning C4312: 'type cast' : conversion from 'LONG' to 'CConsoleWnd *' of greater size + +#define EDITCONTROL_BORDER_SIZE 5 + + +// ------------------------------------------------------------------------------------------------ // +// Functions to manage the console window. +// ------------------------------------------------------------------------------------------------ // + +class CConsoleWnd : public IConsoleWnd +{ +public: + CConsoleWnd(); + ~CConsoleWnd(); + + bool Init( void *hInstance, int dialogResourceID, int editControlID, bool bVisible ); + void Term(); + + virtual void Release(); + + virtual void SetVisible( bool bVisible ); + virtual bool IsVisible() const; + + virtual void PrintToConsole( const char *pMsg ); + virtual void SetTitle( const char *pTitle ); + + virtual void SetDeleteOnClose( bool bDelete ); + + +private: + + int WindowProc( + HWND hwndDlg, // handle to dialog box + UINT uMsg, // message + WPARAM wParam, // first message parameter + LPARAM lParam // second message parameter + ); + + static int CALLBACK StaticWindowProc( + HWND hwndDlg, // handle to dialog box + UINT uMsg, // message + WPARAM wParam, // first message parameter + LPARAM lParam // second message parameter + ); + + void RepositionEditControl(); + + +private: + + HWND m_hWnd; + HWND m_hEditControl; + bool m_bVisible; + bool m_bDeleteOnClose; + int m_nCurrentChars; +}; + + +CConsoleWnd::CConsoleWnd() +{ + m_hWnd = m_hEditControl = NULL; + m_bVisible = false; + m_bDeleteOnClose = false; + m_nCurrentChars = 0; +} + + +CConsoleWnd::~CConsoleWnd() +{ + Term(); +} + +bool CConsoleWnd::Init( void *hInstance, int dialogResourceID, int editControlID, bool bVisible ) +{ + // Create the window. + m_hWnd = CreateDialog( + (HINSTANCE)hInstance, + MAKEINTRESOURCE( dialogResourceID ), + NULL, + &CConsoleWnd::StaticWindowProc ); + + if ( !m_hWnd ) + return false; + + SetWindowLong( m_hWnd, GWL_USERDATA, reinterpret_cast< LONG >( this ) ); + if ( bVisible ) + ShowWindow( m_hWnd, SW_SHOW ); + + // Get a handle to the edit control. + m_hEditControl = GetDlgItem( m_hWnd, editControlID ); + if ( !m_hEditControl ) + return false; + + RepositionEditControl(); + + m_bVisible = bVisible; + return true; +} + + +void CConsoleWnd::Term() +{ + if ( m_hWnd ) + { + DestroyWindow( m_hWnd ); + m_hWnd = NULL; + } +} + + +void CConsoleWnd::Release() +{ + delete this; +} + + +void CConsoleWnd::SetVisible( bool bVisible ) +{ + ShowWindow( m_hWnd, bVisible ? SW_RESTORE : SW_HIDE ); + + if ( bVisible ) + { + ShowWindow( m_hWnd, SW_SHOW ); + SetWindowPos( m_hWnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE ); + UpdateWindow( m_hWnd ); + + int nLen = (int)SendMessage( m_hEditControl, EM_GETLIMITTEXT, 0, 0 ); + SendMessage( m_hEditControl, EM_SETSEL, nLen, nLen ); + } + else + { + SetWindowPos( m_hWnd, 0, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_HIDEWINDOW | SWP_NOOWNERZORDER ); + } + + m_bVisible = bVisible; +} + + +bool CConsoleWnd::IsVisible() const +{ + return m_bVisible; +} + + +void CConsoleWnd::PrintToConsole( const char *pMsg ) +{ + if ( m_nCurrentChars >= 16*1024 ) + { + // Clear the edit control otherwise it'll stop outputting anything. + m_nCurrentChars = 0; + + int nLen = (int)SendMessage( m_hEditControl, EM_GETLIMITTEXT, 0, 0 ); + SendMessage( m_hEditControl, EM_SETSEL, 0, nLen ); + SendMessage( m_hEditControl, EM_REPLACESEL, FALSE, (LPARAM)"" ); + } + + FormatAndSendToEditControl( m_hEditControl, pMsg ); + m_nCurrentChars += (int)strlen( pMsg ); +} + + +void CConsoleWnd::SetTitle( const char *pTitle ) +{ + SetWindowText( m_hWnd, pTitle ); +} + + +int CConsoleWnd::WindowProc( + HWND hwndDlg, // handle to dialog box + UINT uMsg, // message + WPARAM wParam, // first message parameter + LPARAM lParam // second message parameter + ) +{ + lParam = lParam; // avoid compiler warning + + if ( hwndDlg != m_hWnd ) + return false; + + switch ( uMsg ) + { + case WM_SYSCOMMAND: + { + if ( wParam == SC_CLOSE ) + { + if ( m_bDeleteOnClose ) + { + Release(); + } + else + { + SetVisible( false ); + return true; + } + } + } + break; + + case WM_SHOWWINDOW: + { + m_bVisible = (wParam != 0); + } + break; + + case WM_SIZE: + case WM_INITDIALOG: + { + RepositionEditControl(); + } + break; + } + + return false; +} + + +int CConsoleWnd::StaticWindowProc( + HWND hwndDlg, // handle to dialog box + UINT uMsg, // message + WPARAM wParam, // first message parameter + LPARAM lParam // second message parameter + ) +{ + CConsoleWnd *pDlg = (CConsoleWnd*)GetWindowLong( hwndDlg, GWL_USERDATA ); + if ( pDlg ) + return pDlg->WindowProc( hwndDlg, uMsg, wParam, lParam ); + else + return false; +} + + +void CConsoleWnd::RepositionEditControl() +{ + RECT rcMain; + GetClientRect( m_hWnd, &rcMain ); + + RECT rcNew; + rcNew.left = rcMain.left + EDITCONTROL_BORDER_SIZE; + rcNew.right = rcMain.right - EDITCONTROL_BORDER_SIZE; + rcNew.top = rcMain.top + EDITCONTROL_BORDER_SIZE; + rcNew.bottom = rcMain.bottom - EDITCONTROL_BORDER_SIZE; + + SetWindowPos( + m_hEditControl, + NULL, + rcNew.left, + rcNew.top, + rcNew.right - rcNew.left, + rcNew.bottom - rcNew.top, + SWP_NOZORDER ); +} + + +void CConsoleWnd::SetDeleteOnClose( bool bDelete ) +{ + m_bDeleteOnClose = bDelete; +} + + +// ------------------------------------------------------------------------------------ // +// Module interface. +// ------------------------------------------------------------------------------------ // + +void SendToEditControl( HWND hEditControl, const char *pText ) +{ + int nLen = (int)SendMessage( hEditControl, EM_GETLIMITTEXT, 0, 0 ); + SendMessage( hEditControl, EM_SETSEL, nLen, nLen ); + SendMessage( hEditControl, EM_REPLACESEL, FALSE, (LPARAM)pText ); +} + + +void FormatAndSendToEditControl( void *hWnd, const char *pText ) +{ + HWND hEditControl = (HWND)hWnd; + + // Translate \n to \r\n. + char outMsg[1024]; + const char *pIn = pText; + char *pOut = outMsg; + while ( *pIn ) + { + if ( *pIn == '\n' ) + { + *pOut = '\r'; + pOut++; + } + *pOut = *pIn; + + ++pIn; + ++pOut; + + if ( pOut - outMsg >= 1020 ) + { + *pOut = 0; + SendToEditControl( hEditControl, outMsg ); + pOut = outMsg; + } + } + *pOut = 0; + SendToEditControl( hEditControl, outMsg ); +} + + +IConsoleWnd* CreateConsoleWnd( void *hInstance, int dialogResourceID, int editControlID, bool bVisible ) +{ + CConsoleWnd *pWnd = new CConsoleWnd; + + if ( pWnd->Init( hInstance, dialogResourceID, editControlID, bVisible ) ) + { + return pWnd; + } + else + { + pWnd->Release(); + return NULL; + } +} + + + + diff --git a/mp/src/utils/common/consolewnd.h b/mp/src/utils/common/consolewnd.h new file mode 100644 index 00000000..4572ff57 --- /dev/null +++ b/mp/src/utils/common/consolewnd.h @@ -0,0 +1,45 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef CONSOLEWND_H +#define CONSOLEWND_H +#ifdef _WIN32 +#pragma once +#endif + + +class IConsoleWnd +{ +public: + virtual void Release() = 0; + + // Print a message to the console. + virtual void PrintToConsole( const char *pMsg ) = 0; + + // Set the window title. + virtual void SetTitle( const char *pTitle ) = 0; + + // Show and hide the console window. + virtual void SetVisible( bool bVisible ) = 0; + virtual bool IsVisible() const = 0; + + // Normally, the window just hides itself when closed. You can use this to make the window + // automatically go away when they close it. + virtual void SetDeleteOnClose( bool bDelete ) = 0; +}; + + +// Utility functions. + +// This converts adds \r's where necessary and sends the text to the edit control. +void FormatAndSendToEditControl( void *hWnd, const char *pText ); + + +IConsoleWnd* CreateConsoleWnd( void *hInstance, int dialogResourceID, int editControlID, bool bVisible ); + + +#endif // CONSOLEWND_H diff --git a/mp/src/utils/common/filesystem_tools.cpp b/mp/src/utils/common/filesystem_tools.cpp new file mode 100644 index 00000000..9714c57a --- /dev/null +++ b/mp/src/utils/common/filesystem_tools.cpp @@ -0,0 +1,209 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//===========================================================================// + +#if defined( _WIN32 ) && !defined( _X360 ) +#include +#include +#include // _chmod +#elif _LINUX +#include +#endif + +#include +#include +#include "tier1/strtools.h" +#include "filesystem_tools.h" +#include "tier0/icommandline.h" +#include "KeyValues.h" +#include "tier2/tier2.h" + +#ifdef MPI + #include "vmpi.h" + #include "vmpi_tools_shared.h" + #include "vmpi_filesystem.h" +#endif + +// memdbgon must be the last include file in a .cpp file!!! +#include + + +// ---------------------------------------------------------------------------------------------------- // +// Module interface. +// ---------------------------------------------------------------------------------------------------- // + +IBaseFileSystem *g_pFileSystem = NULL; + +// These are only used for tools that need the search paths that the engine's file system provides. +CSysModule *g_pFullFileSystemModule = NULL; + +// --------------------------------------------------------------------------- +// +// These are the base paths that everything will be referenced relative to (textures especially) +// All of these directories include the trailing slash +// +// --------------------------------------------------------------------------- + +// This is the the path of the initial source file (relative to the cwd) +char qdir[1024]; + +// This is the base engine + mod-specific game dir (e.g. "c:\tf2\mytfmod\") +char gamedir[1024]; + +void FileSystem_SetupStandardDirectories( const char *pFilename, const char *pGameInfoPath ) +{ + // Set qdir. + if ( !pFilename ) + { + pFilename = "."; + } + + Q_MakeAbsolutePath( qdir, sizeof( qdir ), pFilename, NULL ); + Q_StripFilename( qdir ); + Q_strlower( qdir ); + if ( qdir[0] != 0 ) + { + Q_AppendSlash( qdir, sizeof( qdir ) ); + } + + // Set gamedir. + Q_MakeAbsolutePath( gamedir, sizeof( gamedir ), pGameInfoPath ); + Q_AppendSlash( gamedir, sizeof( gamedir ) ); +} + + +bool FileSystem_Init_Normal( const char *pFilename, FSInitType_t initType, bool bOnlyUseDirectoryName ) +{ + if ( initType == FS_INIT_FULL ) + { + // First, get the name of the module + char fileSystemDLLName[MAX_PATH]; + bool bSteam; + if ( FileSystem_GetFileSystemDLLName( fileSystemDLLName, MAX_PATH, bSteam ) != FS_OK ) + return false; + + // If we're under Steam we need extra setup to let us find the proper modules + FileSystem_SetupSteamInstallPath(); + + // Next, load the module, call Connect/Init. + CFSLoadModuleInfo loadModuleInfo; + loadModuleInfo.m_pFileSystemDLLName = fileSystemDLLName; + loadModuleInfo.m_pDirectoryName = pFilename; + loadModuleInfo.m_bOnlyUseDirectoryName = bOnlyUseDirectoryName; + loadModuleInfo.m_ConnectFactory = Sys_GetFactoryThis(); + loadModuleInfo.m_bSteam = bSteam; + loadModuleInfo.m_bToolsMode = true; + if ( FileSystem_LoadFileSystemModule( loadModuleInfo ) != FS_OK ) + return false; + + // Next, mount the content + CFSMountContentInfo mountContentInfo; + mountContentInfo.m_pDirectoryName= loadModuleInfo.m_GameInfoPath; + mountContentInfo.m_pFileSystem = loadModuleInfo.m_pFileSystem; + mountContentInfo.m_bToolsMode = true; + if ( FileSystem_MountContent( mountContentInfo ) != FS_OK ) + return false; + + // Finally, load the search paths. + CFSSearchPathsInit searchPathsInit; + searchPathsInit.m_pDirectoryName = loadModuleInfo.m_GameInfoPath; + searchPathsInit.m_pFileSystem = loadModuleInfo.m_pFileSystem; + if ( FileSystem_LoadSearchPaths( searchPathsInit ) != FS_OK ) + return false; + + // Store the data we got from filesystem_init. + g_pFileSystem = g_pFullFileSystem = loadModuleInfo.m_pFileSystem; + g_pFullFileSystemModule = loadModuleInfo.m_pModule; + + FileSystem_AddSearchPath_Platform( g_pFullFileSystem, loadModuleInfo.m_GameInfoPath ); + + FileSystem_SetupStandardDirectories( pFilename, loadModuleInfo.m_GameInfoPath ); + } + else + { + if ( !Sys_LoadInterface( + "filesystem_stdio", + FILESYSTEM_INTERFACE_VERSION, + &g_pFullFileSystemModule, + (void**)&g_pFullFileSystem ) ) + { + return false; + } + + if ( g_pFullFileSystem->Init() != INIT_OK ) + return false; + + g_pFullFileSystem->RemoveAllSearchPaths(); + g_pFullFileSystem->AddSearchPath( "../platform", "PLATFORM" ); + g_pFullFileSystem->AddSearchPath( ".", "GAME" ); + + g_pFileSystem = g_pFullFileSystem; + } + + return true; +} + + +bool FileSystem_Init( const char *pBSPFilename, int maxMemoryUsage, FSInitType_t initType, bool bOnlyUseFilename ) +{ + Assert( CommandLine()->GetCmdLine() != NULL ); // Should have called CreateCmdLine by now. + + // If this app uses VMPI, then let VMPI intercept all filesystem calls. +#if defined( MPI ) + if ( g_bUseMPI ) + { + if ( g_bMPIMaster ) + { + if ( !FileSystem_Init_Normal( pBSPFilename, initType, bOnlyUseFilename ) ) + return false; + + g_pFileSystem = g_pFullFileSystem = VMPI_FileSystem_Init( maxMemoryUsage, g_pFullFileSystem ); + SendQDirInfo(); + } + else + { + g_pFileSystem = g_pFullFileSystem = VMPI_FileSystem_Init( maxMemoryUsage, NULL ); + RecvQDirInfo(); + } + return true; + } +#endif + + return FileSystem_Init_Normal( pBSPFilename, initType, bOnlyUseFilename ); +} + + +void FileSystem_Term() +{ +#if defined( MPI ) + if ( g_bUseMPI ) + { + g_pFileSystem = g_pFullFileSystem = VMPI_FileSystem_Term(); + } +#endif + + if ( g_pFullFileSystem ) + { + g_pFullFileSystem->Shutdown(); + g_pFullFileSystem = NULL; + g_pFileSystem = NULL; + } + + if ( g_pFullFileSystemModule ) + { + Sys_UnloadModule( g_pFullFileSystemModule ); + g_pFullFileSystemModule = NULL; + } +} + + +CreateInterfaceFn FileSystem_GetFactory() +{ +#if defined( MPI ) + if ( g_bUseMPI ) + return VMPI_FileSystem_GetFactory(); +#endif + return Sys_GetFactory( g_pFullFileSystemModule ); +} diff --git a/mp/src/utils/common/filesystem_tools.h b/mp/src/utils/common/filesystem_tools.h new file mode 100644 index 00000000..09db7b3e --- /dev/null +++ b/mp/src/utils/common/filesystem_tools.h @@ -0,0 +1,59 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//===========================================================================// + +#ifndef FILESYSTEM_TOOLS_H +#define FILESYSTEM_TOOLS_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "filesystem.h" +#include "filesystem_init.h" + + +// This is the the path of the initial source file +extern char qdir[1024]; + +// This is the base engine + mod-specific game dir (e.g. "d:\tf2\mytfmod\") +extern char gamedir[1024]; + + +// ---------------------------------------------------------------------------------------- // +// Filesystem initialization. +// ---------------------------------------------------------------------------------------- // + +enum FSInitType_t +{ + FS_INIT_FULL, // Load gameinfo.txt, maybe use filesystem_steam, and setup search paths. + FS_INIT_COMPATIBILITY_MODE // Load filesystem_stdio and that's it. +}; + +// +// Initializes qdir, and gamedir. Also initializes the VMPI filesystem if MPI is defined. +// +// pFilename can be NULL if you want to rely on vproject and qproject. If it's specified, FileSystem_Init +// will go up directories from pFilename looking for gameinfo.txt (if vproject isn't specified). +// +// If bOnlyUseFilename is true, then it won't use any alternative methods of finding the vproject dir +// (ie: it won't use -game or -vproject or the vproject env var or qproject). +// +bool FileSystem_Init( const char *pFilename, int maxMemoryUsage=0, FSInitType_t initType=FS_INIT_FULL, bool bOnlyUseFilename=false ); +void FileSystem_Term(); + +// Used to connect app-framework based console apps to the filesystem tools +void FileSystem_SetupStandardDirectories( const char *pFilename, const char *pGameInfoPath ); + +CreateInterfaceFn FileSystem_GetFactory( void ); + + +extern IBaseFileSystem *g_pFileSystem; +extern IFileSystem *g_pFullFileSystem; // NOTE: this is here when VMPI is being used, but a VMPI app can + // ONLY use LoadModule/UnloadModule. + + +#endif // FILESYSTEM_TOOLS_H diff --git a/mp/src/utils/common/map_shared.cpp b/mp/src/utils/common/map_shared.cpp new file mode 100644 index 00000000..f1e970c0 --- /dev/null +++ b/mp/src/utils/common/map_shared.cpp @@ -0,0 +1,136 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "map_shared.h" +#include "bsplib.h" +#include "cmdlib.h" + + +CMapError g_MapError; +int g_nMapFileVersion; + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *szKey - +// *szValue - +// *pLoadEntity - +// Output : ChunkFileResult_t +//----------------------------------------------------------------------------- +ChunkFileResult_t LoadEntityKeyCallback(const char *szKey, const char *szValue, LoadEntity_t *pLoadEntity) +{ + if (!stricmp(szKey, "classname")) + { + if (!stricmp(szValue, "func_detail")) + { + pLoadEntity->nBaseContents = CONTENTS_DETAIL; + } + else if (!stricmp(szValue, "func_ladder")) + { + pLoadEntity->nBaseContents = CONTENTS_LADDER; + } + else if (!stricmp(szValue, "func_water")) + { + pLoadEntity->nBaseContents = CONTENTS_WATER; + } + } + else if (!stricmp(szKey, "id")) + { + // UNDONE: flag entity errors by ID instead of index + //g_MapError.EntityState( atoi( szValue ) ); + // rename this field since DME code uses this name + SetKeyValue( pLoadEntity->pEntity, "hammerid", szValue ); + return(ChunkFile_Ok); + } + else if( !stricmp( szKey, "mapversion" ) ) + { + // .vmf map revision number + g_MapRevision = atoi( szValue ); + SetKeyValue( pLoadEntity->pEntity, szKey, szValue ); + return ( ChunkFile_Ok ); + } + + SetKeyValue( pLoadEntity->pEntity, szKey, szValue ); + + return(ChunkFile_Ok); +} + + +static ChunkFileResult_t LoadEntityCallback( CChunkFile *pFile, int nParam ) +{ + if (num_entities == MAX_MAP_ENTITIES) + { + // Exits. + g_MapError.ReportError ("num_entities == MAX_MAP_ENTITIES"); + } + + entity_t *mapent = &entities[num_entities]; + num_entities++; + memset(mapent, 0, sizeof(*mapent)); + mapent->numbrushes = 0; + + LoadEntity_t LoadEntity; + LoadEntity.pEntity = mapent; + + // No default flags/contents + LoadEntity.nBaseFlags = 0; + LoadEntity.nBaseContents = 0; + + // + // Read the entity chunk. + // + ChunkFileResult_t eResult = pFile->ReadChunk((KeyHandler_t)LoadEntityKeyCallback, &LoadEntity); + + return eResult; +} + + +bool LoadEntsFromMapFile( char const *pFilename ) +{ + // + // Dummy this up for the texture handling. This can be removed when old .MAP file + // support is removed. + // + g_nMapFileVersion = 400; + + // + // Open the file. + // + CChunkFile File; + ChunkFileResult_t eResult = File.Open( pFilename, ChunkFile_Read ); + + if(eResult == ChunkFile_Ok) + { + num_entities = 0; + + // + // Set up handlers for the subchunks that we are interested in. + // + CChunkHandlerMap Handlers; + Handlers.AddHandler("entity", (ChunkHandler_t)LoadEntityCallback, 0); + + File.PushHandlers(&Handlers); + + // + // Read the sub-chunks. We ignore keys in the root of the file. + // + while (eResult == ChunkFile_Ok) + { + eResult = File.ReadChunk(); + } + + File.PopHandlers(); + return true; + } + else + { + Error("Error in LoadEntsFromMapFile (in-memory file): %s.\n", File.GetErrorText(eResult)); + return false; + } +} + + diff --git a/mp/src/utils/common/map_shared.h b/mp/src/utils/common/map_shared.h new file mode 100644 index 00000000..08e443a0 --- /dev/null +++ b/mp/src/utils/common/map_shared.h @@ -0,0 +1,91 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef MAP_SHARED_H +#define MAP_SHARED_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "ChunkFile.h" +#include "bsplib.h" +#include "cmdlib.h" + + +struct LoadEntity_t +{ + entity_t *pEntity; + int nID; + int nBaseFlags; + int nBaseContents; +}; + + +class CMapError +{ +public: + + void BrushState( int brushID ) + { + m_brushID = brushID; + } + + void BrushSide( int side ) + { + m_sideIndex = side; + } + + void TextureState( const char *pTextureName ) + { + Q_strncpy( m_textureName, pTextureName, sizeof( m_textureName ) ); + } + + void ClearState( void ) + { + BrushState( 0 ); + BrushSide( 0 ); + TextureState( "Not a Parse error!" ); + } + + //----------------------------------------------------------------------------- + // Purpose: Hook the map parse errors and report brush/ent/texture state + // Input : *pErrorString - + //----------------------------------------------------------------------------- + void ReportError( const char *pErrorString ) + { + Error( "Brush %i: %s\nSide %i\nTexture: %s\n", m_brushID, pErrorString, m_sideIndex, m_textureName ); + } + + //----------------------------------------------------------------------------- + // Purpose: Hook the map parse errors and report brush/ent/texture state without exiting. + // Input : pWarningString - + //----------------------------------------------------------------------------- + void ReportWarning( const char *pWarningString ) + { + printf( "Brush %i, Side %i: %s\n", m_brushID, m_sideIndex, pWarningString ); + } + +private: + + int m_brushID; + int m_sideIndex; + char m_textureName[80]; +}; + + +extern CMapError g_MapError; +extern int g_nMapFileVersion; + + +// Shared mapload code. +ChunkFileResult_t LoadEntityKeyCallback( const char *szKey, const char *szValue, LoadEntity_t *pLoadEntity ); + +// Used by VRAD incremental lighting - only load ents from the file and +// fill in the global entities/num_entities array. +bool LoadEntsFromMapFile( char const *pFilename ); + +#endif // MAP_SHARED_H diff --git a/mp/src/utils/common/movie.h b/mp/src/utils/common/movie.h new file mode 100644 index 00000000..78ba92fb --- /dev/null +++ b/mp/src/utils/common/movie.h @@ -0,0 +1,34 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +#ifndef _MOVIE_H_ +#define _MOVIE_H_ + +/* + movie.h + + definitions and such for dumping screen shots to make a movie +*/ + +typedef struct +{ + unsigned long tag; + unsigned long size; +} movieblockheader_t; + + +typedef struct +{ + short width; + short height; + short depth; +} movieframe_t; + + + +#endif _MOVIE_H_ \ No newline at end of file diff --git a/mp/src/utils/common/mpi_stats.cpp b/mp/src/utils/common/mpi_stats.cpp new file mode 100644 index 00000000..8d9cc5e7 --- /dev/null +++ b/mp/src/utils/common/mpi_stats.cpp @@ -0,0 +1,839 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +// Nasty headers! +#include "MySqlDatabase.h" +#include "tier1/strtools.h" +#include "vmpi.h" +#include "vmpi_dispatch.h" +#include "mpi_stats.h" +#include "cmdlib.h" +#include "imysqlwrapper.h" +#include "threadhelpers.h" +#include "vmpi_tools_shared.h" +#include "tier0/icommandline.h" + +/* + +-- MySQL code to create the databases, create the users, and set access privileges. +-- You only need to ever run this once. + +create database vrad; + +use mysql; + +create user vrad_worker; +create user vmpi_browser; + +-- This updates the "user" table, which is checked when someone tries to connect to the database. +grant select,insert,update on vrad.* to vrad_worker; +grant select on vrad.* to vmpi_browser; +flush privileges; + +/* + +-- SQL code to (re)create the tables. + +-- Master generates a unique job ID (in job_master_start) and sends it to workers. +-- Each worker (and the master) make a job_worker_start, link it to the primary job ID, +-- get their own unique ID, which represents that process in that job. +-- All JobWorkerID fields link to the JobWorkerID field in job_worker_start. + +-- NOTE: do a "use vrad" or "use vvis" first, depending on the DB you want to create. + + +use vrad; + + +drop table job_master_start; +create table job_master_start ( + JobID INTEGER UNSIGNED NOT NULL AUTO_INCREMENT, index id( JobID, MachineName(5) ), + BSPFilename TINYTEXT NOT NULL, + StartTime TIMESTAMP NOT NULL, + MachineName TEXT NOT NULL, + RunningTimeMS INTEGER UNSIGNED NOT NULL, + NumWorkers INTEGER UNSIGNED NOT NULL default 0 + ); + +drop table job_master_end; +create table job_master_end ( + JobID INTEGER UNSIGNED NOT NULL, PRIMARY KEY ( JobID ), + NumWorkersConnected SMALLINT UNSIGNED NOT NULL, + NumWorkersDisconnected SMALLINT UNSIGNED NOT NULL, + ErrorText TEXT NOT NULL + ); + +drop table job_worker_start; +create table job_worker_start ( + JobWorkerID INTEGER UNSIGNED NOT NULL AUTO_INCREMENT, + index index_jobid( JobID ), + index index_jobworkerid( JobWorkerID ), + + JobID INTEGER UNSIGNED NOT NULL, -- links to job_master_start::JobID + IsMaster BOOL NOT NULL, -- Set to 1 if this "worker" is the master process. + RunningTimeMS INTEGER UNSIGNED NOT NULL default 0, + MachineName TEXT NOT NULL, + WorkerState SMALLINT UNSIGNED NOT NULL default 0, -- 0 = disconnected, 1 = connected + NumWorkUnits INTEGER UNSIGNED NOT NULL default 0, -- how many work units this worker has completed + CurrentStage TINYTEXT NOT NULL, -- which compile stage is it on + Thread0WU INTEGER NOT NULL default 0, -- which WU thread 0 is on + Thread1WU INTEGER NOT NULL default 0, -- which WU thread 1 is on + Thread2WU INTEGER NOT NULL default 0, -- which WU thread 2 is on + Thread3WU INTEGER NOT NULL default 0 -- which WU thread 3 is on + ); + +drop table text_messages; +create table text_messages ( + JobWorkerID INTEGER UNSIGNED NOT NULL, index id( JobWorkerID, MessageIndex ), + MessageIndex INTEGER UNSIGNED NOT NULL, + Text TEXT NOT NULL + ); + +drop table graph_entry; +create table graph_entry ( + JobWorkerID INTEGER UNSIGNED NOT NULL, index id( JobWorkerID ), + MSSinceJobStart INTEGER UNSIGNED NOT NULL, + BytesSent INTEGER UNSIGNED NOT NULL, + BytesReceived INTEGER UNSIGNED NOT NULL + ); + +drop table events; +create table events ( + JobWorkerID INTEGER UNSIGNED NOT NULL, index id( JobWorkerID ), + Text TEXT NOT NULL + ); +*/ + + + +// Stats set by the app. +int g_nWorkersConnected = 0; +int g_nWorkersDisconnected = 0; + + +DWORD g_StatsStartTime; + +CMySqlDatabase *g_pDB = NULL; + +IMySQL *g_pSQL = NULL; +CSysModule *g_hMySQLDLL = NULL; + +char g_BSPFilename[256]; + +bool g_bMaster = false; +unsigned long g_JobPrimaryID = 0; // This represents this job, but doesn't link to a particular machine. +unsigned long g_JobWorkerID = 0; // A unique key in the DB that represents this machine in this job. +char g_MachineName[MAX_COMPUTERNAME_LENGTH+1] = {0}; + +unsigned long g_CurrentMessageIndex = 0; + + +HANDLE g_hPerfThread = NULL; +DWORD g_PerfThreadID = 0xFEFEFEFE; +HANDLE g_hPerfThreadExitEvent = NULL; + +// These are set by the app and they go into the database. +extern uint64 g_ThreadWUs[4]; + +extern uint64 VMPI_GetNumWorkUnitsCompleted( int iProc ); + + +// ---------------------------------------------------------------------------------------------------- // +// This is a helper class to build queries like the stream IO. +// ---------------------------------------------------------------------------------------------------- // + +class CMySQLQuery +{ +friend class CMySQL; + +public: + // This is like a sprintf, but it will grow the string as necessary. + void Format( const char *pFormat, ... ); + + int Execute( IMySQL *pDB ); + +private: + CUtlVector m_QueryText; +}; + + +void CMySQLQuery::Format( const char *pFormat, ... ) +{ + #define QUERYTEXT_GROWSIZE 1024 + + // This keeps growing the buffer and calling _vsnprintf until the buffer is + // large enough to hold all the data. + m_QueryText.SetSize( QUERYTEXT_GROWSIZE ); + while ( 1 ) + { + va_list marker; + va_start( marker, pFormat ); + int ret = _vsnprintf( m_QueryText.Base(), m_QueryText.Count(), pFormat, marker ); + va_end( marker ); + + if ( ret < 0 ) + { + m_QueryText.SetSize( m_QueryText.Count() + QUERYTEXT_GROWSIZE ); + } + else + { + m_QueryText[ m_QueryText.Count() - 1 ] = 0; + break; + } + } +} + + +int CMySQLQuery::Execute( IMySQL *pDB ) +{ + int ret = pDB->Execute( m_QueryText.Base() ); + m_QueryText.Purge(); + return ret; +} + + + +// ---------------------------------------------------------------------------------------------------- // +// This inserts the necessary backslashes in front of backslashes or quote characters. +// ---------------------------------------------------------------------------------------------------- // + +char* FormatStringForSQL( const char *pText ) +{ + // First, count the quotes in the string. We need to put a backslash in front of each one. + int nChars = 0; + const char *pCur = pText; + while ( *pCur != 0 ) + { + if ( *pCur == '\"' || *pCur == '\\' ) + ++nChars; + + ++pCur; + ++nChars; + } + + pCur = pText; + char *pRetVal = new char[nChars+1]; + for ( int i=0; i < nChars; ) + { + if ( *pCur == '\"' || *pCur == '\\' ) + pRetVal[i++] = '\\'; + + pRetVal[i++] = *pCur; + ++pCur; + } + pRetVal[nChars] = 0; + + return pRetVal; +} + + + +// -------------------------------------------------------------------------------- // +// Commands to add data to the database. +// -------------------------------------------------------------------------------- // +class CSQLDBCommandBase : public ISQLDBCommand +{ +public: + virtual ~CSQLDBCommandBase() + { + } + + virtual void deleteThis() + { + delete this; + } +}; + +class CSQLDBCommand_WorkerStats : public CSQLDBCommandBase +{ +public: + virtual int RunCommand() + { + int nCurConnections = VMPI_GetCurrentNumberOfConnections(); + + + // Update the NumWorkers entry. + char query[2048]; + Q_snprintf( query, sizeof( query ), "update job_master_start set NumWorkers=%d where JobID=%lu", + nCurConnections, + g_JobPrimaryID ); + g_pSQL->Execute( query ); + + + // Update the job_master_worker_stats stuff. + for ( int i=1; i < nCurConnections; i++ ) + { + unsigned long jobWorkerID = VMPI_GetJobWorkerID( i ); + + if ( jobWorkerID != 0xFFFFFFFF ) + { + Q_snprintf( query, sizeof( query ), "update " + "job_worker_start set WorkerState=%d, NumWorkUnits=%d where JobWorkerID=%lu", + VMPI_IsProcConnected( i ), + (int) VMPI_GetNumWorkUnitsCompleted( i ), + VMPI_GetJobWorkerID( i ) + ); + g_pSQL->Execute( query ); + } + } + return 1; + } +}; + +class CSQLDBCommand_JobMasterEnd : public CSQLDBCommandBase +{ +public: + + virtual int RunCommand() + { + CMySQLQuery query; + query.Format( "insert into job_master_end values ( %lu, %d, %d, \"no errors\" )", g_JobPrimaryID, g_nWorkersConnected, g_nWorkersDisconnected ); + query.Execute( g_pSQL ); + + // Now set RunningTimeMS. + unsigned long runningTimeMS = GetTickCount() - g_StatsStartTime; + query.Format( "update job_master_start set RunningTimeMS=%lu where JobID=%lu", runningTimeMS, g_JobPrimaryID ); + query.Execute( g_pSQL ); + return 1; + } +}; + + +void UpdateJobWorkerRunningTime() +{ + unsigned long runningTimeMS = GetTickCount() - g_StatsStartTime; + + char curStage[256]; + VMPI_GetCurrentStage( curStage, sizeof( curStage ) ); + + CMySQLQuery query; + query.Format( "update job_worker_start set RunningTimeMS=%lu, CurrentStage=\"%s\", " + "Thread0WU=%d, Thread1WU=%d, Thread2WU=%d, Thread3WU=%d where JobWorkerID=%lu", + runningTimeMS, + curStage, + (int) g_ThreadWUs[0], + (int) g_ThreadWUs[1], + (int) g_ThreadWUs[2], + (int) g_ThreadWUs[3], + g_JobWorkerID ); + query.Execute( g_pSQL ); +} + + +class CSQLDBCommand_GraphEntry : public CSQLDBCommandBase +{ +public: + + CSQLDBCommand_GraphEntry( DWORD msTime, DWORD nBytesSent, DWORD nBytesReceived ) + { + m_msTime = msTime; + m_nBytesSent = nBytesSent; + m_nBytesReceived = nBytesReceived; + } + + virtual int RunCommand() + { + CMySQLQuery query; + query.Format( "insert into graph_entry (JobWorkerID, MSSinceJobStart, BytesSent, BytesReceived) " + "values ( %lu, %lu, %lu, %lu )", + g_JobWorkerID, + m_msTime, + m_nBytesSent, + m_nBytesReceived ); + + query.Execute( g_pSQL ); + + UpdateJobWorkerRunningTime(); + + ++g_CurrentMessageIndex; + return 1; + } + + DWORD m_nBytesSent; + DWORD m_nBytesReceived; + DWORD m_msTime; +}; + + + +class CSQLDBCommand_TextMessage : public CSQLDBCommandBase +{ +public: + + CSQLDBCommand_TextMessage( const char *pText ) + { + m_pText = FormatStringForSQL( pText ); + } + + virtual ~CSQLDBCommand_TextMessage() + { + delete [] m_pText; + } + + virtual int RunCommand() + { + CMySQLQuery query; + query.Format( "insert into text_messages (JobWorkerID, MessageIndex, Text) values ( %lu, %lu, \"%s\" )", g_JobWorkerID, g_CurrentMessageIndex, m_pText ); + query.Execute( g_pSQL ); + + ++g_CurrentMessageIndex; + return 1; + } + + char *m_pText; +}; + + +// -------------------------------------------------------------------------------- // +// Internal helpers. +// -------------------------------------------------------------------------------- // + +// This is the spew output before it has connected to the MySQL database. +CCriticalSection g_SpewTextCS; +CUtlVector g_SpewText( 1024 ); + + +void VMPI_Stats_SpewHook( const char *pMsg ) +{ + CCriticalSectionLock csLock( &g_SpewTextCS ); + csLock.Lock(); + + // Queue the text up so we can send it to the DB right away when we connect. + g_SpewText.AddMultipleToTail( strlen( pMsg ), pMsg ); +} + + +void PerfThread_SendSpewText() +{ + // Send the spew text to the database. + CCriticalSectionLock csLock( &g_SpewTextCS ); + csLock.Lock(); + + if ( g_SpewText.Count() > 0 ) + { + g_SpewText.AddToTail( 0 ); + + if ( g_bMPI_StatsTextOutput ) + { + g_pDB->AddCommandToQueue( new CSQLDBCommand_TextMessage( g_SpewText.Base() ), NULL ); + } + else + { + // Just show one message in the vmpi_job_watch window to let them know that they need + // to use a command line option to get the output. + static bool bFirst = true; + if ( bFirst ) + { + char msg[512]; + V_snprintf( msg, sizeof( msg ), "%s not enabled", VMPI_GetParamString( mpi_Stats_TextOutput ) ); + bFirst = false; + g_pDB->AddCommandToQueue( new CSQLDBCommand_TextMessage( msg ), NULL ); + } + } + + g_SpewText.RemoveAll(); + } + + csLock.Unlock(); +} + + +void PerfThread_AddGraphEntry( DWORD startTicks, DWORD &lastSent, DWORD &lastReceived ) +{ + // Send the graph entry with data transmission info. + DWORD curSent = g_nBytesSent + g_nMulticastBytesSent; + DWORD curReceived = g_nBytesReceived + g_nMulticastBytesReceived; + + g_pDB->AddCommandToQueue( + new CSQLDBCommand_GraphEntry( + GetTickCount() - startTicks, + curSent - lastSent, + curReceived - lastReceived ), + NULL ); + + lastSent = curSent; + lastReceived = curReceived; +} + + +// This function adds a graph_entry into the database periodically. +DWORD WINAPI PerfThreadFn( LPVOID pParameter ) +{ + DWORD lastSent = 0; + DWORD lastReceived = 0; + DWORD startTicks = GetTickCount(); + + while ( WaitForSingleObject( g_hPerfThreadExitEvent, 1000 ) != WAIT_OBJECT_0 ) + { + PerfThread_AddGraphEntry( startTicks, lastSent, lastReceived ); + + // Send updates for text output. + PerfThread_SendSpewText(); + + // If we're the master, update all the worker stats. + if ( g_bMaster ) + { + g_pDB->AddCommandToQueue( + new CSQLDBCommand_WorkerStats, + NULL ); + } + } + + // Add the remaining text and one last graph entry (which will include the current stage info). + PerfThread_SendSpewText(); + PerfThread_AddGraphEntry( startTicks, lastSent, lastReceived ); + + SetEvent( g_hPerfThreadExitEvent ); + return 0; +} + + +// -------------------------------------------------------------------------------- // +// VMPI_Stats interface. +// -------------------------------------------------------------------------------- // + +void VMPI_Stats_InstallSpewHook() +{ + InstallExtraSpewHook( VMPI_Stats_SpewHook ); +} + + +void UnloadMySQLWrapper() +{ + if ( g_hMySQLDLL ) + { + if ( g_pSQL ) + { + g_pSQL->Release(); + g_pSQL = NULL; + } + + Sys_UnloadModule( g_hMySQLDLL ); + g_hMySQLDLL = NULL; + } +} + + +bool LoadMySQLWrapper( + const char *pHostName, + const char *pDBName, + const char *pUserName + ) +{ + UnloadMySQLWrapper(); + + // Load the DLL and the interface. + if ( !Sys_LoadInterface( "mysql_wrapper", MYSQL_WRAPPER_VERSION_NAME, &g_hMySQLDLL, (void**)&g_pSQL ) ) + return false; + + // Try to init the database. + if ( !g_pSQL->InitMySQL( pDBName, pHostName, pUserName ) ) + { + UnloadMySQLWrapper(); + return false; + } + + return true; +} + + +bool VMPI_Stats_Init_Master( + const char *pHostName, + const char *pDBName, + const char *pUserName, + const char *pBSPFilename, + unsigned long *pDBJobID ) +{ + Assert( !g_pDB ); + + g_bMaster = true; + + // Connect the database. + g_pDB = new CMySqlDatabase; + if ( !g_pDB || !g_pDB->Initialize() || !LoadMySQLWrapper( pHostName, pDBName, pUserName ) ) + { + delete g_pDB; + g_pDB = NULL; + return false; + } + + DWORD size = sizeof( g_MachineName ); + GetComputerName( g_MachineName, &size ); + + // Create the job_master_start row. + Q_FileBase( pBSPFilename, g_BSPFilename, sizeof( g_BSPFilename ) ); + + g_JobPrimaryID = 0; + CMySQLQuery query; + query.Format( "insert into job_master_start ( BSPFilename, StartTime, MachineName, RunningTimeMS ) values ( \"%s\", null, \"%s\", %lu )", g_BSPFilename, g_MachineName, RUNNINGTIME_MS_SENTINEL ); + query.Execute( g_pSQL ); + + g_JobPrimaryID = g_pSQL->InsertID(); + if ( g_JobPrimaryID == 0 ) + { + delete g_pDB; + g_pDB = NULL; + return false; + } + + + // Now init the worker portion. + *pDBJobID = g_JobPrimaryID; + return VMPI_Stats_Init_Worker( NULL, NULL, NULL, g_JobPrimaryID ); +} + + + +bool VMPI_Stats_Init_Worker( const char *pHostName, const char *pDBName, const char *pUserName, unsigned long DBJobID ) +{ + g_StatsStartTime = GetTickCount(); + + // If pDBServerName is null, then we're the master and we just want to make the job_worker_start entry. + if ( pHostName ) + { + Assert( !g_pDB ); + + // Connect the database. + g_pDB = new CMySqlDatabase; + if ( !g_pDB || !g_pDB->Initialize() || !LoadMySQLWrapper( pHostName, pDBName, pUserName ) ) + { + delete g_pDB; + g_pDB = NULL; + return false; + } + + // Get our machine name to store in the database. + DWORD size = sizeof( g_MachineName ); + GetComputerName( g_MachineName, &size ); + } + + + g_JobPrimaryID = DBJobID; + g_JobWorkerID = 0; + + CMySQLQuery query; + query.Format( "insert into job_worker_start ( JobID, CurrentStage, IsMaster, MachineName ) values ( %lu, \"none\", %d, \"%s\" )", + g_JobPrimaryID, g_bMaster, g_MachineName ); + query.Execute( g_pSQL ); + + g_JobWorkerID = g_pSQL->InsertID(); + if ( g_JobWorkerID == 0 ) + { + delete g_pDB; + g_pDB = NULL; + return false; + } + + // Now create a thread that samples perf data and stores it in the database. + g_hPerfThreadExitEvent = CreateEvent( NULL, FALSE, FALSE, NULL ); + g_hPerfThread = CreateThread( + NULL, + 0, + PerfThreadFn, + NULL, + 0, + &g_PerfThreadID ); + + return true; +} + + +void VMPI_Stats_Term() +{ + if ( !g_pDB ) + return; + + // Stop the thread. + SetEvent( g_hPerfThreadExitEvent ); + WaitForSingleObject( g_hPerfThread, INFINITE ); + + CloseHandle( g_hPerfThreadExitEvent ); + g_hPerfThreadExitEvent = NULL; + + CloseHandle( g_hPerfThread ); + g_hPerfThread = NULL; + + if ( g_bMaster ) + { + // (Write a job_master_end entry here). + g_pDB->AddCommandToQueue( new CSQLDBCommand_JobMasterEnd, NULL ); + } + + // Wait for up to a second for the DB to finish writing its data. + DWORD startTime = GetTickCount(); + while ( GetTickCount() - startTime < 1000 ) + { + if ( g_pDB->QueriesInOutQueue() == 0 ) + break; + } + + delete g_pDB; + g_pDB = NULL; + + UnloadMySQLWrapper(); +} + + +static bool ReadStringFromFile( FILE *fp, char *pStr, int strSize ) +{ + int i=0; + for ( i; i < strSize-2; i++ ) + { + if ( fread( &pStr[i], 1, 1, fp ) != 1 || + pStr[i] == '\n' ) + { + break; + } + } + + pStr[i] = 0; + return i != 0; +} + + +// This looks for pDBInfoFilename in the same path as pBaseExeFilename. +// The file has 3 lines: machine name (with database), database name, username +void GetDBInfo( const char *pDBInfoFilename, CDBInfo *pInfo ) +{ + char baseExeFilename[512]; + if ( !GetModuleFileName( GetModuleHandle( NULL ), baseExeFilename, sizeof( baseExeFilename ) ) ) + Error( "GetModuleFileName failed." ); + + // Look for the info file in the same directory as the exe. + char dbInfoFilename[512]; + Q_strncpy( dbInfoFilename, baseExeFilename, sizeof( dbInfoFilename ) ); + Q_StripFilename( dbInfoFilename ); + + if ( dbInfoFilename[0] == 0 ) + Q_strncpy( dbInfoFilename, ".", sizeof( dbInfoFilename ) ); + + Q_strncat( dbInfoFilename, "/", sizeof( dbInfoFilename ), COPY_ALL_CHARACTERS ); + Q_strncat( dbInfoFilename, pDBInfoFilename, sizeof( dbInfoFilename ), COPY_ALL_CHARACTERS ); + + FILE *fp = fopen( dbInfoFilename, "rt" ); + if ( !fp ) + { + Error( "Can't open %s for database info.\n", dbInfoFilename ); + } + + if ( !ReadStringFromFile( fp, pInfo->m_HostName, sizeof( pInfo->m_HostName ) ) || + !ReadStringFromFile( fp, pInfo->m_DBName, sizeof( pInfo->m_DBName ) ) || + !ReadStringFromFile( fp, pInfo->m_UserName, sizeof( pInfo->m_UserName ) ) + ) + { + Error( "%s is not a valid database info file.\n", dbInfoFilename ); + } + + fclose( fp ); +} + + +void RunJobWatchApp( char *pCmdLine ) +{ + STARTUPINFO si; + memset( &si, 0, sizeof( si ) ); + si.cb = sizeof( si ); + + PROCESS_INFORMATION pi; + memset( &pi, 0, sizeof( pi ) ); + + // Working directory should be the same as our exe's directory. + char dirName[512]; + if ( GetModuleFileName( NULL, dirName, sizeof( dirName ) ) != 0 ) + { + char *s1 = V_strrchr( dirName, '\\' ); + char *s2 = V_strrchr( dirName, '/' ); + if ( s1 || s2 ) + { + // Get rid of the last slash. + s1 = max( s1, s2 ); + s1[0] = 0; + + if ( !CreateProcess( + NULL, + pCmdLine, + NULL, // security + NULL, + TRUE, + 0, // flags + NULL, // environment + dirName, // current directory + &si, + &pi ) ) + { + Warning( "%s - error launching '%s'\n", VMPI_GetParamString( mpi_Job_Watch ), pCmdLine ); + } + } + } +} + + +void StatsDB_InitStatsDatabase( + int argc, + char **argv, + const char *pDBInfoFilename ) +{ + // Did they disable the stats database? + if ( !g_bMPI_Stats && !VMPI_IsParamUsed( mpi_Job_Watch ) ) + return; + + unsigned long jobPrimaryID; + + // Now open the DB. + if ( g_bMPIMaster ) + { + CDBInfo dbInfo; + GetDBInfo( pDBInfoFilename, &dbInfo ); + + if ( !VMPI_Stats_Init_Master( dbInfo.m_HostName, dbInfo.m_DBName, dbInfo.m_UserName, argv[argc-1], &jobPrimaryID ) ) + { + Warning( "VMPI_Stats_Init_Master( %s, %s, %s ) failed.\n", dbInfo.m_HostName, dbInfo.m_DBName, dbInfo.m_UserName ); + + // Tell the workers not to use stats. + dbInfo.m_HostName[0] = 0; + } + + char cmdLine[2048]; + Q_snprintf( cmdLine, sizeof( cmdLine ), "vmpi_job_watch -JobID %d", jobPrimaryID ); + + Msg( "\nTo watch this job, run this command line:\n%s\n\n", cmdLine ); + + if ( VMPI_IsParamUsed( mpi_Job_Watch ) ) + { + // Convenience thing to automatically launch the job watch for this job. + RunJobWatchApp( cmdLine ); + } + + // Send the database info to all the workers. + SendDBInfo( &dbInfo, jobPrimaryID ); + } + else + { + // Wait to get DB info so we can connect to the MySQL database. + CDBInfo dbInfo; + unsigned long jobPrimaryID; + RecvDBInfo( &dbInfo, &jobPrimaryID ); + + if ( dbInfo.m_HostName[0] != 0 ) + { + if ( !VMPI_Stats_Init_Worker( dbInfo.m_HostName, dbInfo.m_DBName, dbInfo.m_UserName, jobPrimaryID ) ) + Error( "VMPI_Stats_Init_Worker( %s, %s, %s, %d ) failed.\n", dbInfo.m_HostName, dbInfo.m_DBName, dbInfo.m_UserName, jobPrimaryID ); + } + } +} + + +unsigned long StatsDB_GetUniqueJobID() +{ + return g_JobPrimaryID; +} + + +unsigned long VMPI_Stats_GetJobWorkerID() +{ + return g_JobWorkerID; +} \ No newline at end of file diff --git a/mp/src/utils/common/mpi_stats.h b/mp/src/utils/common/mpi_stats.h new file mode 100644 index 00000000..10aa6162 --- /dev/null +++ b/mp/src/utils/common/mpi_stats.h @@ -0,0 +1,59 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef MPI_STATS_H +#define MPI_STATS_H +#ifdef _WIN32 +#pragma once +#endif + + +// The VMPI stats module reports a bunch of statistics to a MySQL server, and the +// stats can be used to trace and graph a compile session. +// + +// Call this as soon as possible to initialize spew hooks. +void VMPI_Stats_InstallSpewHook(); + +// +// pDBServerName is the hostname (or dotted IP address) of the MySQL server to connect to. +// pBSPFilename is the last argument on the command line. +// pMachineIP is the dotted IP address of this machine. +// jobID is an 8-byte unique identifier for this job. +// +bool VMPI_Stats_Init_Master( const char *pHostName, const char *pDBName, const char *pUserName, const char *pBSPFilename, unsigned long *pDBJobID ); +bool VMPI_Stats_Init_Worker( const char *pHostName, const char *pDBName, const char *pUserName, unsigned long DBJobID ); +void VMPI_Stats_Term(); + +// Add a generic text event to the database. +void VMPI_Stats_AddEventText( const char *pText ); + +class CDBInfo +{ +public: + char m_HostName[128]; + char m_DBName[128]; + char m_UserName[128]; +}; + +// If you're the master, this loads pDBInfoFilename, sends that info to the workers, and +// connects to the database. +// +// If you're a worker, this waits for the DB info, then connects to the database. +void StatsDB_InitStatsDatabase( + int argc, + char **argv, + const char *pDBInfoFilename ); + +// The database gives back a unique ID for the job. +unsigned long StatsDB_GetUniqueJobID(); + +// Get the worker ID (used for the JobWorkerID fields in the database). +unsigned long VMPI_Stats_GetJobWorkerID(); + + +#endif // MPI_STATS_H diff --git a/mp/src/utils/common/mstristrip.cpp b/mp/src/utils/common/mstristrip.cpp new file mode 100644 index 00000000..6a66de2f --- /dev/null +++ b/mp/src/utils/common/mstristrip.cpp @@ -0,0 +1,930 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +//----------------------------------------------------------------------------- +// FILE: TRISTRIP.CPP +// +// Desc: Xbox tristripper +// +// Copyright (c) 1999-2000 Microsoft Corporation. All rights reserved. +//----------------------------------------------------------------------------- + +// identifier was truncated to '255' characters in the debug information +#pragma warning(disable: 4786) +// conversion from 'double' to 'float' +#pragma warning(disable: 4244) +#pragma warning(disable: 4530) + +#include +#include +#include +#include +#include + +#include +#ifdef _DEBUG +#include +#endif + +#include "mstristrip.h" + +using namespace std; + +//========================================================================= +// structs +//========================================================================= +typedef vector STRIPVERTS; +typedef list STRIPLIST; +typedef WORD (*TRIANGLELIST)[3]; + +struct TRIANGLEINFO +{ + int neighbortri[3]; + int neighboredge[3]; +}; + +// return true if strip starts clockwise +inline bool FIsStripCW(const STRIPVERTS &stripvertices) +{ + // last index should have cw/ccw bool + return !!stripvertices[stripvertices.size() - 1]; +} + +// return length of strip +inline int StripLen(const STRIPVERTS &stripvertices) +{ + return (int)stripvertices.size() - 1; +} + +// free all stripverts and clear the striplist +inline void FreeStripListVerts(STRIPLIST *pstriplist) +{ + STRIPLIST::iterator istriplist = pstriplist->begin(); + while(istriplist != pstriplist->end()) + { + STRIPVERTS *pstripverts = *istriplist; + delete pstripverts; + pstriplist->erase(istriplist++); + } +} + +//========================================================================= +// main stripper class +//========================================================================= +class CStripper +{ +public: + // ctors/dtors + CStripper(int numtris, TRIANGLELIST ptriangles); + ~CStripper(); + + // initialize tri info + void InitTriangleInfo(int tri, int vert); + + // get maximum length strip from tri/vert + int CreateStrip(int tri, int vert, int maxlen, int *pswaps, + bool flookahead, bool fstartcw, int *pstriptris, int *pstripverts); + + // stripify entire mesh + void BuildStrips(STRIPLIST *pstriplist, int maxlen, bool flookahead); + + // blast strip indices to ppstripindices + int CreateManyStrips(STRIPLIST *pstriplist, WORD **ppstripindices); + int CreateLongStrip(STRIPLIST *pstriplist, WORD **ppstripindices); + + inline int GetNeighborCount(int tri) + { + int count = 0; + for(int vert = 0; vert < 3; vert++) + { + int neighbortri = m_ptriinfo[tri].neighbortri[vert]; + count += (neighbortri != -1) && !m_pused[neighbortri]; + } + return count; + } + + // from callee + int m_numtris; // # tris + TRIANGLELIST m_ptriangles; // trilist + + TRIANGLEINFO *m_ptriinfo; // tri edge, neighbor info + int *m_pused; // tri used flag +}; + +//========================================================================= +// vertex cache class +//========================================================================= +class CVertCache +{ +public: + CVertCache() + { Reset(); } + ~CVertCache() + {}; + + // reset cache + void Reset() + { + m_iCachePtr = 0; + m_cachehits = 0; + memset(m_rgCache, 0xff, sizeof(m_rgCache)); + } + + // add vertindex to cache + bool Add(int strip, int vertindex); + + int NumCacheHits() const + { return m_cachehits; } + +// enum { CACHE_SIZE = 10 }; + enum { CACHE_SIZE = 18 }; + +private: + int m_cachehits; // current # of cache hits + WORD m_rgCache[CACHE_SIZE]; // vertex cache + int m_rgCacheStrip[CACHE_SIZE]; // strip # which added vert + int m_iCachePtr; // fifo ptr +}; + +//========================================================================= +// Get maximum length of strip starting at tri/vert +//========================================================================= +int CStripper::CreateStrip(int tri, int vert, int maxlen, int *pswaps, + bool flookahead, bool fstartcw, int *pstriptris, int *pstripverts) +{ + *pswaps = 0; + + // this guy has already been used? + if(m_pused[tri]) + return 0; + + // mark tri as used + m_pused[tri] = 1; + + int swaps = 0; + + // add first tri info + pstriptris[0] = tri; + pstriptris[1] = tri; + pstriptris[2] = tri; + + if(fstartcw) + { + pstripverts[0] = (vert) % 3; + pstripverts[1] = (vert + 1) % 3; + pstripverts[2] = (vert + 2) % 3; + } + else + { + pstripverts[0] = (vert + 1) % 3; + pstripverts[1] = (vert + 0) % 3; + pstripverts[2] = (vert + 2) % 3; + } + fstartcw = !fstartcw; + + // get next tri information + int edge = (fstartcw ? vert + 2 : vert + 1) % 3; + int nexttri = m_ptriinfo[tri].neighbortri[edge]; + int nextvert = m_ptriinfo[tri].neighboredge[edge]; + + // start building the strip until we run out of room or indices + int stripcount; + for( stripcount = 3; stripcount < maxlen; stripcount++) + { + // dead end? + if(nexttri == -1 || m_pused[nexttri]) + break; + + // move to next tri + tri = nexttri; + vert = nextvert; + + // toggle orientation + fstartcw = !fstartcw; + + // find the next natural edge + int edge = (fstartcw ? vert + 2 : vert + 1) % 3; + nexttri = m_ptriinfo[tri].neighbortri[edge]; + nextvert = m_ptriinfo[tri].neighboredge[edge]; + + bool fswap = false; + if(nexttri == -1 || m_pused[nexttri]) + { + // if the next tri is a dead end - try swapping orientation + fswap = true; + } + else if(flookahead) + { + // try a swap and see who our new neighbor would be + int edgeswap = (fstartcw ? vert + 1 : vert + 2) % 3; + int nexttriswap = m_ptriinfo[tri].neighbortri[edgeswap]; + int nextvertswap = m_ptriinfo[tri].neighboredge[edgeswap]; + + if(nexttriswap != -1 && !m_pused[nexttriswap]) + { + assert(nexttri != -1); + + // if the swap neighbor has a lower count, change directions + if(GetNeighborCount(nexttriswap) < GetNeighborCount(nexttri)) + { + fswap = true; + } + else if(GetNeighborCount(nexttriswap) == GetNeighborCount(nexttri)) + { + // if they have the same number of neighbors - check their neighbors + edgeswap = (fstartcw ? nextvertswap + 2 : nextvertswap + 1) % 3; + nexttriswap = m_ptriinfo[nexttriswap].neighbortri[edgeswap]; + + int edge1 = (fstartcw ? nextvert + 1 : nextvert + 2) % 3; + int nexttri1 = m_ptriinfo[nexttri].neighbortri[edge1]; + + if(nexttri1 == -1 || m_pused[nexttri1]) + { + // natural winding order leads us to a dead end so turn + fswap = true; + } + else if(nexttriswap != -1 && !m_pused[nexttriswap]) + { + // check neighbor counts on both directions and swap if it's better + if(GetNeighborCount(nexttriswap) < GetNeighborCount(nexttri1)) + fswap = true; + } + } + } + } + + if(fswap) + { + // we've been told to change directions so make sure we actually can + // and then add the swap vertex + int edgeswap = (fstartcw ? vert + 1 : vert + 2) % 3; + nexttri = m_ptriinfo[tri].neighbortri[edgeswap]; + nextvert = m_ptriinfo[tri].neighboredge[edgeswap]; + + if(nexttri != -1 && !m_pused[nexttri]) + { + pstriptris[stripcount] = pstriptris[stripcount - 2]; + pstripverts[stripcount] = pstripverts[stripcount - 2]; + stripcount++; + swaps++; + fstartcw = !fstartcw; + } + } + + // record index information + pstriptris[stripcount] = tri; + pstripverts[stripcount] = (vert + 2) % 3; + + // mark triangle as used + m_pused[tri] = 1; + } + + // clear the used flags + for(int j = 2; j < stripcount; j++) + m_pused[pstriptris[j]] = 0; + + // return swap count and striplen + *pswaps = swaps; + return stripcount; +} + +//========================================================================= +// Given a striplist and current cache state, pick the best next strip +//========================================================================= +STRIPLIST::iterator FindBestCachedStrip(STRIPLIST *pstriplist, + const CVertCache &vertcachestate) +{ + if(pstriplist->empty()) + return pstriplist->end(); + + bool fFlipStrip = false; + int maxcachehits = -1; + STRIPLIST::iterator istriplistbest = pstriplist->begin(); + + int striplen = StripLen(**istriplistbest); + bool fstartcw = FIsStripCW(**istriplistbest); + + // go through all the other strips looking for the best caching + for(STRIPLIST::iterator istriplist = pstriplist->begin(); + istriplist != pstriplist->end(); + ++istriplist) + { + bool fFlip = false; + const STRIPVERTS &stripverts = **istriplist; + int striplennew = StripLen(stripverts); + + // check cache if this strip is the same type as us (ie: cw/odd) + if((FIsStripCW(stripverts) == fstartcw) && + ((striplen & 0x1) == (striplennew & 0x1))) + { + // copy current state of cache + CVertCache vertcachenew = vertcachestate; + + // figure out what this guy would do to our cache + for(int ivert = 0; ivert < striplennew; ivert++) + vertcachenew.Add(2, stripverts[ivert]); + + // even length strip - see if better cache hits reversed + if(!(striplennew & 0x1)) + { + CVertCache vertcacheflipped = vertcachestate; + + for(int ivert = StripLen(stripverts) - 1; ivert >= 0; ivert--) + vertcacheflipped.Add(2, stripverts[ivert]); + + if(vertcacheflipped.NumCacheHits() > vertcachenew.NumCacheHits()) + { + vertcachenew = vertcacheflipped; + fFlip = true; + } + } + + // record the best number of cache hits to date + int numcachehits = vertcachenew.NumCacheHits() - vertcachestate.NumCacheHits(); + if(numcachehits > maxcachehits) + { + maxcachehits = numcachehits; + istriplistbest = istriplist; + fFlipStrip = fFlip; + } + } + } + + if(fFlipStrip) + { + STRIPVERTS &stripverts = **istriplistbest; + STRIPVERTS::iterator vend = stripverts.end(); + + reverse(stripverts.begin(), --vend); + } + + // make sure we keep the list in order and always pull off + // the first dude. + if(istriplistbest != pstriplist->begin()) + swap(*istriplistbest, *pstriplist->begin()); + + return pstriplist->begin(); +} + + +//========================================================================= +// Don't merge the strips - just blast em into the stripbuffer one by one +// (useful for debugging) +//========================================================================= +int CStripper::CreateManyStrips(STRIPLIST *pstriplist, WORD **ppstripindices) +{ + // allow room for each of the strips size plus the final 0 + int indexcount = (int)pstriplist->size() + 1; + + // we're storing the strips in [size1 i1 i2 i3][size2 i4 i5 i6][0] format + STRIPLIST::iterator istriplist; + for( istriplist = pstriplist->begin(); istriplist != pstriplist->end(); ++istriplist) + { + // add striplength plus potential degenerate to swap ccw --> cw + indexcount += StripLen(**istriplist) + 1; + } + + // alloc the space for all this stuff + WORD *pstripindices = new WORD [indexcount]; + assert(pstripindices); + + CVertCache vertcache; + int numstripindices = 0; + + for(istriplist = pstriplist->begin(); + !pstriplist->empty(); + istriplist = FindBestCachedStrip(pstriplist, vertcache)) + { + const STRIPVERTS &stripverts = **istriplist; + + if(!FIsStripCW(stripverts)) + { + // add an extra index if it's ccw + pstripindices[numstripindices++] = StripLen(stripverts) + 1; + pstripindices[numstripindices++] = stripverts[0]; + } + else + { + // add the strip length + pstripindices[numstripindices++] = StripLen(stripverts); + } + + // add all the strip indices + for(int i = 0; i < StripLen(stripverts); i++) + { + pstripindices[numstripindices++] = stripverts[i]; + vertcache.Add(1, stripverts[i]); + } + + // free this guy and pop him off the list + delete &stripverts; + pstriplist->pop_front(); + } + + // add terminating zero + pstripindices[numstripindices++] = 0; + *ppstripindices = pstripindices; + + return numstripindices; +} + +//========================================================================= +// Merge striplist into one big uberlist with (hopefully) optimal caching +//========================================================================= +int CStripper::CreateLongStrip(STRIPLIST *pstriplist, WORD **ppstripindices) +{ + // allow room for one strip length plus a possible 3 extra indices per + // concatenated strip list plus the final 0 + int indexcount = ((int)pstriplist->size() * 3) + 2; + + // we're storing the strips in [size1 i1 i2 i3][size2 i4 i5 i6][0] format + STRIPLIST::iterator istriplist; + for( istriplist = pstriplist->begin(); istriplist != pstriplist->end(); ++istriplist) + { + indexcount += StripLen(**istriplist); + } + + // alloc the space for all this stuff + WORD *pstripindices = new WORD [indexcount]; + assert(pstripindices); + + CVertCache vertcache; + int numstripindices = 0; + + // add first strip + istriplist = pstriplist->begin(); + const STRIPVERTS &stripverts = **istriplist; + + // first strip should be cw + assert(FIsStripCW(stripverts)); + + for(int ivert = 0; ivert < StripLen(stripverts); ivert++) + { + pstripindices[numstripindices++] = stripverts[ivert]; + vertcache.Add(1, stripverts[ivert]); + } + + // kill first dude + delete &stripverts; + pstriplist->erase(istriplist); + + // add all the others + while(pstriplist->size()) + { + istriplist = FindBestCachedStrip(pstriplist, vertcache); + STRIPVERTS &stripverts = **istriplist; + short lastvert = pstripindices[numstripindices - 1]; + short firstvert = stripverts[0]; + + if(firstvert != lastvert) + { + // add degenerate from last strip + pstripindices[numstripindices++] = lastvert; + + // add degenerate from our strip + pstripindices[numstripindices++] = firstvert; + } + + // if we're not orientated correctly, we need to add a degenerate + if(FIsStripCW(stripverts) != !(numstripindices & 0x1)) + { + // This shouldn't happen - we're currently trying very hard + // to keep everything oriented correctly. + assert(false); + pstripindices[numstripindices++] = firstvert; + } + + // add these verts + for(int ivert = 0; ivert < StripLen(stripverts); ivert++) + { + pstripindices[numstripindices++] = stripverts[ivert]; + vertcache.Add(1, stripverts[ivert]); + } + + // free these guys + delete &stripverts; + pstriplist->erase(istriplist); + } + + *ppstripindices = pstripindices; + return numstripindices; +} + +//========================================================================= +// Build a (hopefully) optimal set of strips from a trilist +//========================================================================= +void CStripper::BuildStrips(STRIPLIST *pstriplist, int maxlen, bool flookahead) +{ + // temp indices storage + const int ctmpverts = 1024; + int pstripverts[ctmpverts + 1]; + int pstriptris[ctmpverts + 1]; + + assert(maxlen <= ctmpverts); + + // clear all the used flags for the tris + memset(m_pused, 0, sizeof(m_pused[0]) * m_numtris); + + bool fstartcw = true; + for(;;) + { + int besttri = 0; + int bestvert = 0; + float bestratio = 2.0f; + int bestneighborcount = INT_MAX; + + int tri; + for( tri = 0; tri < m_numtris; tri++) + { + // if used the continue + if(m_pused[tri]) + continue; + + // get the neighbor count + int curneightborcount = GetNeighborCount(tri); + assert(curneightborcount >= 0 && curneightborcount <= 3); + + // push all the singletons to the very end + if(!curneightborcount) + curneightborcount = 4; + + // if this guy has more neighbors than the current best - bail + if(curneightborcount > bestneighborcount) + continue; + + // try starting the strip with each of this tris verts + for(int vert = 0; vert < 3; vert++) + { + int swaps; + int len = CreateStrip(tri, vert, maxlen, &swaps, flookahead, + fstartcw, pstriptris, pstripverts); + assert(len); + + float ratio = (len == 3) ? 1.0f : (float)swaps / len; + + // check if this ratio is better than what we've already got for + // this neighborcount + if((curneightborcount < bestneighborcount) || + ((curneightborcount == bestneighborcount) && (ratio < bestratio))) + { + bestneighborcount = curneightborcount; + + besttri = tri; + bestvert = vert; + bestratio = ratio; + } + + } + } + + // no strips found? + if(bestneighborcount == INT_MAX) + break; + + // recreate this strip + int swaps; + int len = CreateStrip(besttri, bestvert, maxlen, + &swaps, flookahead, fstartcw, pstriptris, pstripverts); + assert(len); + + // mark the tris on the best strip as used + for(tri = 0; tri < len; tri++) + m_pused[pstriptris[tri]] = 1; + + // create a new STRIPVERTS and stuff in the indices + STRIPVERTS *pstripvertices = new STRIPVERTS(len + 1); + assert(pstripvertices); + + // store orientation in first entry + for(tri = 0; tri < len; tri++) + (*pstripvertices)[tri] = m_ptriangles[pstriptris[tri]][pstripverts[tri]]; + (*pstripvertices)[len] = fstartcw; + + // store the STRIPVERTS + pstriplist->push_back(pstripvertices); + + // if strip was odd - swap orientation + if((len & 0x1)) + fstartcw = !fstartcw; + } + +#ifdef _DEBUG + // make sure all tris are used + for(int t = 0; t < m_numtris; t++) + assert(m_pused[t]); +#endif +} + +//========================================================================= +// Guesstimate on the total index count for this list of strips +//========================================================================= +int EstimateStripCost(STRIPLIST *pstriplist) +{ + int count = 0; + + for(STRIPLIST::iterator istriplist = pstriplist->begin(); + istriplist != pstriplist->end(); + ++istriplist) + { + // add count of indices + count += StripLen(**istriplist); + } + + // assume 2 indices per strip to tack all these guys together + return count + ((int)pstriplist->size() - 1) * 2; +} + +//========================================================================= +// Initialize triangle information (edges, #neighbors, etc.) +//========================================================================= +void CStripper::InitTriangleInfo(int tri, int vert) +{ + WORD *ptriverts = &m_ptriangles[tri + 1][0]; + int vert1 = m_ptriangles[tri][(vert + 1) % 3]; + int vert2 = m_ptriangles[tri][vert]; + + for(int itri = tri + 1; itri < m_numtris; itri++, ptriverts += 3) + { + if(m_pused[itri] != 0x7) + { + for(int ivert = 0; ivert < 3; ivert++) + { + if((ptriverts[ivert] == vert1) && + (ptriverts[(ivert + 1) % 3] == vert2)) + { + // add the triangle info + m_ptriinfo[tri].neighbortri[vert] = itri; + m_ptriinfo[tri].neighboredge[vert] = ivert; + m_pused[tri] |= (1 << vert); + + m_ptriinfo[itri].neighbortri[ivert] = tri; + m_ptriinfo[itri].neighboredge[ivert] = vert; + m_pused[itri] |= (1 << ivert); + return; + } + } + } + } +} + +//========================================================================= +// CStripper ctor +//========================================================================= +CStripper::CStripper(int numtris, TRIANGLELIST ptriangles) +{ + // store trilist info + m_numtris = numtris; + m_ptriangles = ptriangles; + + m_pused = new int[numtris]; + assert(m_pused); + m_ptriinfo = new TRIANGLEINFO[numtris]; + assert(m_ptriinfo); + + // init triinfo + int itri; + for( itri = 0; itri < numtris; itri++) + { + m_ptriinfo[itri].neighbortri[0] = -1; + m_ptriinfo[itri].neighbortri[1] = -1; + m_ptriinfo[itri].neighbortri[2] = -1; + } + + // clear the used flag + memset(m_pused, 0, sizeof(m_pused[0]) * m_numtris); + + // go through all the triangles and find edges, neighbor counts + for(itri = 0; itri < numtris; itri++) + { + for(int ivert = 0; ivert < 3; ivert++) + { + if(!(m_pused[itri] & (1 << ivert))) + InitTriangleInfo(itri, ivert); + } + } + + // clear the used flags from InitTriangleInfo + memset(m_pused, 0, sizeof(m_pused[0]) * m_numtris); +} + +//========================================================================= +// CStripper dtor +//========================================================================= +CStripper::~CStripper() +{ + // free stuff + delete [] m_pused; + m_pused = NULL; + + delete [] m_ptriinfo; + m_ptriinfo = NULL; +} + +//========================================================================= +// Add an index to the cache - returns true if it was added, false otherwise +//========================================================================= +bool CVertCache::Add(int strip, int vertindex) +{ + // find index in cache + for(int iCache = 0; iCache < CACHE_SIZE; iCache++) + { + if(vertindex == m_rgCache[iCache]) + { + // if it's in the cache and it's from a different strip + // change the strip to the new one and count the cache hit + if(strip != m_rgCacheStrip[iCache]) + { + m_cachehits++; + m_rgCacheStrip[iCache] = strip; + return true; + } + + // we added this item to the cache earlier - carry on + return false; + } + } + + // not in cache, add vert and strip + m_rgCache[m_iCachePtr] = vertindex; + m_rgCacheStrip[m_iCachePtr] = strip; + m_iCachePtr = (m_iCachePtr + 1) % CACHE_SIZE; + return true; +} + +#ifdef _DEBUG +//========================================================================= +// Turn on c runtime leak checking, etc. +//========================================================================= +void EnableLeakChecking() +{ + int flCrtDbgFlags = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG); + + flCrtDbgFlags &= + ~(_CRTDBG_LEAK_CHECK_DF | + _CRTDBG_CHECK_ALWAYS_DF | + _CRTDBG_DELAY_FREE_MEM_DF); + + // always check for memory leaks + flCrtDbgFlags |= _CRTDBG_LEAK_CHECK_DF; + + // others you may / may not want to set + flCrtDbgFlags |= _CRTDBG_CHECK_ALWAYS_DF; + flCrtDbgFlags |= _CRTDBG_DELAY_FREE_MEM_DF; + + _CrtSetDbgFlag(flCrtDbgFlags); + + // all types of reports go via OutputDebugString + _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_DEBUG); + _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_DEBUG); + _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_DEBUG); + + // big errors and asserts get their own assert window + _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_WNDW); + _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_WNDW); + + // _CrtSetBreakAlloc(0); +} +#endif + +//========================================================================= +// Main Stripify routine +//========================================================================= +int Stripify(int numtris, WORD *ptriangles, int *pnumindices, WORD **ppstripindices) +{ + if(!numtris || !ptriangles) + return 0; + +#ifdef _DEBUG +// EnableLeakChecking(); +#endif + + CStripper stripper(numtris, (TRIANGLELIST)ptriangles); + + // map of various args to try stripifying mesh with + struct ARGMAP + { + int maxlen; // maximum length of strips + bool flookahead; // use sgi greedy lookahead (or not) + } rgargmap[] = + { + { 1024, true }, + { 1024, false }, + }; + static const int cargmaps = sizeof(rgargmap) / sizeof(rgargmap[0]); + STRIPLIST striplistbest; + int bestlistcost = 0; + + for(int imap = 0; imap < cargmaps; imap++) + { + STRIPLIST striplist; + + // build the strip with the various args + stripper.BuildStrips(&striplist, rgargmap[imap].maxlen, + rgargmap[imap].flookahead); + + // guesstimate the list cost and store it if it's good + int listcost = EstimateStripCost(&striplist); + if(!bestlistcost || (listcost < bestlistcost)) + { + // free the old best list + FreeStripListVerts(&striplistbest); + + // store the new best list + striplistbest = striplist; + bestlistcost = listcost; + assert(bestlistcost > 0); + } + else + { + FreeStripListVerts(&striplist); + } + } + +#ifdef NEVER + // Return the strips in [size1 i1 i2 i3][size2 i4 i5 i6]...[0] format + // Very useful for debugging... + return stripper.CreateManyStrips(&striplistbest, ppstripindices); +#endif // NEVER + + // return one big long strip + int numindices = stripper.CreateLongStrip(&striplistbest, ppstripindices); + + if(pnumindices) + *pnumindices = numindices; + return numindices; +} + +//========================================================================= +// Class used to vertices for locality of access. +//========================================================================= +struct SortEntry +{ +public: + int iFirstUsed; + int iOrigIndex; + + bool operator<(const SortEntry& rhs) + { + return iFirstUsed < rhs.iFirstUsed; + } +}; + +//========================================================================= +// Reorder the vertices +//========================================================================= +void ComputeVertexPermutation(int numstripindices, WORD* pstripindices, + int* pnumverts, WORD** ppvertexpermutation) +{ + // Sort verts to maximize locality. + SortEntry* pSortTable = new SortEntry[*pnumverts]; + + // Fill in original index. + int i; + for( i = 0; i < *pnumverts; i++) + { + pSortTable[i].iOrigIndex = i; + pSortTable[i].iFirstUsed = -1; + } + + // Fill in first used flag. + for(i = 0; i < numstripindices; i++) + { + int index = pstripindices[i]; + + if(pSortTable[index].iFirstUsed == -1) + pSortTable[index].iFirstUsed = i; + } + + // Sort the table. + sort(pSortTable, pSortTable + *pnumverts); + + // Copy re-mapped to orignal vertex permutaion into output array. + *ppvertexpermutation = new WORD[*pnumverts]; + + for(i = 0; i < *pnumverts; i++) + { + (*ppvertexpermutation)[i] = pSortTable[i].iOrigIndex; + } + + // Build original to re-mapped permutation. + WORD* pInversePermutation = new WORD[numstripindices]; + + for(i = 0; i < *pnumverts; i++) + { + pInversePermutation[pSortTable[i].iOrigIndex] = i; + } + + // We need to remap indices as well. + for(i = 0; i < numstripindices; i++) + { + pstripindices[i] = pInversePermutation[pstripindices[i]]; + } + + delete[] pSortTable; + delete[] pInversePermutation; +} + diff --git a/mp/src/utils/common/mstristrip.h b/mp/src/utils/common/mstristrip.h new file mode 100644 index 00000000..626a0062 --- /dev/null +++ b/mp/src/utils/common/mstristrip.h @@ -0,0 +1,43 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +//----------------------------------------------------------------------------- +// FILE: TRISTRIP.H +// +// Desc: tristrip header file +// +// Copyright (c) 1999-2000 Microsoft Corporation. All rights reserved. +//----------------------------------------------------------------------------- + +typedef unsigned short WORD; + +// +// Main Stripify routine. Returns number of strip indices contained +// in ppstripindices. Caller must delete [] ppstripindices. +// +int Stripify( + int numtris, // Number of triangles + WORD *ptriangles, // triangle indices pointer + int *pnumindices, // number of indices in ppstripindices (out) + WORD **ppstripindices // triangle strip indices +); + +// +// Re-arrange vertices so that they occur in the order that they are first +// used. This function doesn't actually move vertex data around, it returns +// an array that specifies where in the new vertex array each old vertex +// should go. It also re-maps the strip indices to use the new vertex +// locations. Caller must delete [] pVertexPermutation. +// +void ComputeVertexPermutation +( + int numstripindices, // Number of strip indices + WORD *pstripindices, // Strip indices + int *pnumverts, // Number of verts (in and out) + WORD **ppvertexpermutation // Map from orignal index to remapped index +); + diff --git a/mp/src/utils/common/pacifier.cpp b/mp/src/utils/common/pacifier.cpp new file mode 100644 index 00000000..6d9c73f2 --- /dev/null +++ b/mp/src/utils/common/pacifier.cpp @@ -0,0 +1,63 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include +#include "basetypes.h" +#include "pacifier.h" +#include "tier0/dbg.h" + + +static int g_LastPacifierDrawn = -1; +static bool g_bPacifierSuppressed = false; + +#define clamp(a,b,c) ( (a) > (c) ? (c) : ( (a) < (b) ? (b) : (a) ) ) + +void StartPacifier( char const *pPrefix ) +{ + Msg( "%s", pPrefix ); + g_LastPacifierDrawn = -1; + UpdatePacifier( 0.001f ); +} + +void UpdatePacifier( float flPercent ) +{ + int iCur = (int)(flPercent * 40.0f); + iCur = clamp( iCur, g_LastPacifierDrawn, 40 ); + + if( iCur != g_LastPacifierDrawn && !g_bPacifierSuppressed ) + { + for( int i=g_LastPacifierDrawn+1; i <= iCur; i++ ) + { + if ( !( i % 4 ) ) + { + Msg("%d", i/4); + } + else + { + if( i != 40 ) + { + Msg("."); + } + } + } + + g_LastPacifierDrawn = iCur; + } +} + +void EndPacifier( bool bCarriageReturn ) +{ + UpdatePacifier(1); + + if( bCarriageReturn && !g_bPacifierSuppressed ) + Msg("\n"); +} + +void SuppressPacifier( bool bSuppress ) +{ + g_bPacifierSuppressed = bSuppress; +} diff --git a/mp/src/utils/common/pacifier.h b/mp/src/utils/common/pacifier.h new file mode 100644 index 00000000..42ecd6ec --- /dev/null +++ b/mp/src/utils/common/pacifier.h @@ -0,0 +1,23 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef PACIFIER_H +#define PACIFIER_H +#ifdef _WIN32 +#pragma once +#endif + + +// Use these to display a pacifier like: +// ProcessBlock_Thread: 0...1...2...3...4...5...6...7...8...9... (0) +void StartPacifier( char const *pPrefix ); // Prints the prefix and resets the pacifier +void UpdatePacifier( float flPercent ); // percent value between 0 and 1. +void EndPacifier( bool bCarriageReturn = true ); // Completes pacifier as if 100% was done +void SuppressPacifier( bool bSuppress = true ); // Suppresses pacifier updates if another thread might still be firing them + + +#endif // PACIFIER_H diff --git a/mp/src/utils/common/physdll.cpp b/mp/src/utils/common/physdll.cpp new file mode 100644 index 00000000..fae9810a --- /dev/null +++ b/mp/src/utils/common/physdll.cpp @@ -0,0 +1,31 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +#include +#include "physdll.h" +#include "filesystem_tools.h" + +static CSysModule *pPhysicsModule = NULL; +CreateInterfaceFn GetPhysicsFactory( void ) +{ + if ( !pPhysicsModule ) + { + pPhysicsModule = g_pFullFileSystem->LoadModule( "VPHYSICS.DLL" ); + if ( !pPhysicsModule ) + return NULL; + } + + return Sys_GetFactory( pPhysicsModule ); +} + +void PhysicsDLLPath( const char *pPathname ) +{ + if ( !pPhysicsModule ) + { + pPhysicsModule = g_pFullFileSystem->LoadModule( pPathname ); + } +} diff --git a/mp/src/utils/common/physdll.h b/mp/src/utils/common/physdll.h new file mode 100644 index 00000000..151a9e50 --- /dev/null +++ b/mp/src/utils/common/physdll.h @@ -0,0 +1,30 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef PHYSDLL_H +#define PHYSDLL_H +#pragma once + + +#ifdef __cplusplus +#include "vphysics_interface.h" +class IPhysics; +class IPhysicsCollision; + +extern CreateInterfaceFn GetPhysicsFactory( void ); + +extern "C" { +#endif + +// tools need to force the path +void PhysicsDLLPath( const char *pPathname ); + +#ifdef __cplusplus +} +#endif + +#endif // PHYSDLL_H diff --git a/mp/src/utils/common/polylib.cpp b/mp/src/utils/common/polylib.cpp new file mode 100644 index 00000000..36690a27 --- /dev/null +++ b/mp/src/utils/common/polylib.cpp @@ -0,0 +1,915 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// $NoKeywords: $ +//=============================================================================// + +#include "cmdlib.h" +#include "mathlib/mathlib.h" +#include "polylib.h" +#include "worldsize.h" +#include "threads.h" +#include "tier0/dbg.h" + +// doesn't seem to need to be here? -- in threads.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; + +void pw(winding_t *w) +{ + int i; + for (i=0 ; inumpoints ; i++) + printf ("(%5.1f, %5.1f, %5.1f)\n",w->p[i][0], w->p[i][1],w->p[i][2]); +} + +winding_t *winding_pool[MAX_POINTS_ON_WINDING+4]; + +/* +============= +AllocWinding +============= +*/ +winding_t *AllocWinding (int points) +{ + winding_t *w; + + 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; + } + ThreadLock(); + if (winding_pool[points]) + { + w = winding_pool[points]; + winding_pool[points] = w->next; + } + else + { + w = (winding_t *)malloc(sizeof(*w)); + w->p = (Vector *)calloc( points, sizeof(Vector) ); + } + ThreadUnlock(); + w->numpoints = 0; // None are occupied yet even though allocated. + w->maxpoints = points; + w->next = NULL; + return w; +} + +void FreeWinding (winding_t *w) +{ + if (w->numpoints == 0xdeaddead) + Error ("FreeWinding: freed a freed winding"); + + ThreadLock(); + w->numpoints = 0xdeaddead; // flag as freed + w->next = winding_pool[w->maxpoints]; + winding_pool[w->maxpoints] = w; + ThreadUnlock(); +} + +/* +============ +RemoveColinearPoints +============ +*/ +int c_removed; + +void RemoveColinearPoints (winding_t *w) +{ + int i, j, k; + Vector v1, v2; + int nump; + Vector p[MAX_POINTS_ON_WINDING]; + + nump = 0; + for (i=0 ; inumpoints ; i++) + { + j = (i+1)%w->numpoints; + k = (i+w->numpoints-1)%w->numpoints; + VectorSubtract (w->p[j], w->p[i], v1); + VectorSubtract (w->p[i], w->p[k], v2); + VectorNormalize(v1); + VectorNormalize(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, Vector &normal, vec_t *dist) +{ + Vector v1, v2; + + VectorSubtract (w->p[1], w->p[0], v1); + + // HACKHACK: Avoid potentially collinear verts + if ( w->numpoints > 3 ) + { + VectorSubtract (w->p[3], w->p[0], v2); + } + else + { + VectorSubtract (w->p[2], w->p[0], v2); + } + CrossProduct (v2, v1, normal); + VectorNormalize (normal); + *dist = DotProduct (w->p[0], normal); + +} + + +/* +============= +WindingArea +============= +*/ +vec_t WindingArea(winding_t *w) +{ + int i; + Vector d1, d2, cross; + vec_t total; + + total = 0; + for (i=2 ; inumpoints ; i++) + { + VectorSubtract (w->p[i-1], w->p[0], d1); + VectorSubtract (w->p[i], w->p[0], d2); + CrossProduct (d1, d2, cross); + total += VectorLength ( cross ); + } + return total * 0.5; +} + +void WindingBounds (winding_t *w, Vector &mins, Vector &maxs) +{ + vec_t v; + int i,j; + + mins[0] = mins[1] = mins[2] = 99999; + maxs[0] = maxs[1] = maxs[2] = -99999; + + for (i=0 ; inumpoints ; i++) + { + for (j=0 ; j<3 ; j++) + { + v = w->p[i][j]; + if (v < mins[j]) + mins[j] = v; + if (v > maxs[j]) + maxs[j] = v; + } + } +} + +/* +============= +WindingCenter +============= +*/ +void WindingCenter (winding_t *w, Vector ¢er) +{ + int i; + float scale; + + VectorCopy (vec3_origin, center); + for (i=0 ; inumpoints ; i++) + VectorAdd (w->p[i], center, center); + + scale = 1.0/w->numpoints; + VectorScale (center, scale, center); +} + + + +/* +============= +WindingCenter +============= +*/ +vec_t WindingAreaAndBalancePoint( winding_t *w, Vector ¢er ) +{ + int i; + Vector d1, d2, cross; + vec_t total; + + VectorCopy (vec3_origin, center); + if ( !w ) + return 0.0f; + + total = 0; + for (i=2 ; inumpoints ; i++) + { + VectorSubtract (w->p[i-1], w->p[0], d1); + VectorSubtract (w->p[i], w->p[0], d2); + CrossProduct (d1, d2, cross); + float area = VectorLength ( cross ); + total += area; + + // center of triangle, weighed by area + VectorMA( center, area / 3.0, w->p[i-1], center ); + VectorMA( center, area / 3.0, w->p[i], center ); + VectorMA( center, area / 3.0, w->p[0], center ); + } + if (total) + { + VectorScale( center, 1.0 / total, center ); + } + return total * 0.5; +} + +/* +================= +BaseWindingForPlane +================= +*/ +winding_t *BaseWindingForPlane (const Vector &normal, vec_t dist) +{ + int i, x; + vec_t max, v; + Vector org, vright, vup; + winding_t *w; + +// find the major axis + + max = -1; + 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); + + VectorScale (normal, dist, org); + + CrossProduct (vup, normal, vright); + + VectorScale (vup, (MAX_COORD_INTEGER*4), vup); + VectorScale (vright, (MAX_COORD_INTEGER*4), vright); + +// project a really big axis aligned box onto the plane + w = AllocWinding (4); + + VectorSubtract (org, vright, w->p[0]); + VectorAdd (w->p[0], vup, w->p[0]); + + VectorAdd (org, vright, w->p[1]); + VectorAdd (w->p[1], vup, w->p[1]); + + VectorAdd (org, vright, w->p[2]); + VectorSubtract (w->p[2], vup, w->p[2]); + + VectorSubtract (org, vright, w->p[3]); + VectorSubtract (w->p[3], vup, w->p[3]); + + w->numpoints = 4; + + return w; +} + +/* +================== +CopyWinding +================== +*/ +winding_t *CopyWinding (winding_t *w) +{ + int size; + winding_t *c; + + c = AllocWinding (w->numpoints); + c->numpoints = w->numpoints; + size = w->numpoints*sizeof(w->p[0]); + memcpy (c->p, w->p, size); + return c; +} + +/* +================== +ReverseWinding +================== +*/ +winding_t *ReverseWinding (winding_t *w) +{ + int i; + winding_t *c; + + c = AllocWinding (w->numpoints); + for (i=0 ; inumpoints ; i++) + { + VectorCopy (w->p[w->numpoints-1-i], c->p[i]); + } + c->numpoints = w->numpoints; + return c; +} + + +// BUGBUG: Hunt this down - it's causing CSG errors +#pragma optimize("g", off) +/* +============= +ClipWindingEpsilon +============= +*/ + +void ClipWindingEpsilon (winding_t *in, const Vector &normal, vec_t dist, + vec_t epsilon, winding_t **front, winding_t **back) +{ + vec_t dists[MAX_POINTS_ON_WINDING+4]; + int sides[MAX_POINTS_ON_WINDING+4]; + int counts[3]; + vec_t dot; + int i, j; + Vector mid = vec3_origin; + winding_t *f, *b; + int maxpts; + + counts[0] = counts[1] = counts[2] = 0; + +// determine sides for each point + for (i=0 ; inumpoints ; i++) + { + dot = DotProduct (in->p[i], normal); + dot -= dist; + dists[i] = dot; + if (dot > epsilon) + sides[i] = SIDE_FRONT; + else if (dot < -epsilon) + sides[i] = SIDE_BACK; + else + { + sides[i] = SIDE_ON; + } + counts[sides[i]]++; + } + sides[i] = sides[0]; + dists[i] = dists[0]; + + *front = *back = NULL; + + if (!counts[0]) + { + *back = CopyWinding (in); + return; + } + if (!counts[1]) + { + *front = CopyWinding (in); + return; + } + + maxpts = in->numpoints+4; // cant use counts[0]+2 because + // of fp grouping errors + + *front = f = AllocWinding (maxpts); + *back = b = AllocWinding (maxpts); + + for (i=0 ; inumpoints ; i++) + { + Vector& 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 + Vector& 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"); +} +#pragma optimize("", on) + + +// NOTE: This is identical to ClipWindingEpsilon, but it does a pre/post translation to improve precision +void ClipWindingEpsilon_Offset( winding_t *in, const Vector &normal, vec_t dist, vec_t epsilon, winding_t **front, winding_t **back, const Vector &offset ) +{ + TranslateWinding( in, offset ); + ClipWindingEpsilon( in, normal, dist+DotProduct(offset,normal), epsilon, front, back ); + TranslateWinding( in, -offset ); + if ( front && *front ) + { + TranslateWinding( *front, -offset ); + } + if ( back && *back ) + { + TranslateWinding( *back, -offset ); + } +} + +void ClassifyWindingEpsilon_Offset( winding_t *in, const Vector &normal, vec_t dist, vec_t epsilon, winding_t **front, winding_t **back, winding_t **on, const Vector &offset) +{ + TranslateWinding( in, offset ); + ClassifyWindingEpsilon( in, normal, dist+DotProduct(offset,normal), epsilon, front, back, on ); + TranslateWinding( in, -offset ); + if ( front && *front ) + { + TranslateWinding( *front, -offset ); + } + if ( back && *back ) + { + TranslateWinding( *back, -offset ); + } + if ( on && *on ) + { + TranslateWinding( *on, -offset ); + } +} + +/* +============= +ClassifyWindingEpsilon +============= +*/ +// This version returns the winding as "on" if all verts lie in the plane +void ClassifyWindingEpsilon( winding_t *in, const Vector &normal, vec_t dist, + vec_t epsilon, winding_t **front, winding_t **back, winding_t **on) +{ + vec_t dists[MAX_POINTS_ON_WINDING+4]; + int sides[MAX_POINTS_ON_WINDING+4]; + int counts[3]; + vec_t dot; + int i, j; + Vector mid = vec3_origin; + winding_t *f, *b; + int maxpts; + + counts[0] = counts[1] = counts[2] = 0; + +// determine sides for each point + for (i=0 ; inumpoints ; i++) + { + dot = DotProduct (in->p[i], normal); + dot -= dist; + dists[i] = dot; + if (dot > epsilon) + sides[i] = SIDE_FRONT; + else if (dot < -epsilon) + sides[i] = SIDE_BACK; + else + { + sides[i] = SIDE_ON; + } + counts[sides[i]]++; + } + sides[i] = sides[0]; + dists[i] = dists[0]; + + *front = *back = *on = NULL; + + if ( !counts[0] && !counts[1] ) + { + *on = CopyWinding(in); + 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 ; inumpoints ; i++) + { + Vector& 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 + Vector& p2 = in->p[(i+1)%in->numpoints]; + + dot = dists[i] / (dists[i]-dists[i+1]); + for (j=0 ; j<3 ; j++) + { // avoid round off error when possible + if (normal[j] == 1) + mid[j] = dist; + else if (normal[j] == -1) + mid[j] = -dist; + else + mid[j] = p1[j] + dot*(p2[j]-p1[j]); + } + + VectorCopy (mid, f->p[f->numpoints]); + f->numpoints++; + VectorCopy (mid, b->p[b->numpoints]); + b->numpoints++; + } + + if (f->numpoints > maxpts || b->numpoints > maxpts) + Error ("ClipWinding: points exceeded estimate"); + if (f->numpoints > MAX_POINTS_ON_WINDING || b->numpoints > MAX_POINTS_ON_WINDING) + Error ("ClipWinding: MAX_POINTS_ON_WINDING"); +} + +/* +============= +ChopWindingInPlace +============= +*/ +void ChopWindingInPlace (winding_t **inout, const Vector &normal, vec_t dist, vec_t epsilon) +{ + winding_t *in; + vec_t dists[MAX_POINTS_ON_WINDING+4]; + int sides[MAX_POINTS_ON_WINDING+4]; + int counts[3]; + vec_t dot; + int i, j; + Vector mid = vec3_origin; + winding_t *f; + int maxpts; + + in = *inout; + counts[0] = counts[1] = counts[2] = 0; +// determine sides for each point + for (i=0 ; inumpoints ; i++) + { + dot = DotProduct (in->p[i], normal); + dot -= dist; + dists[i] = dot; + if (dot > epsilon) + { + sides[i] = SIDE_FRONT; + } + else if (dot < -epsilon) + { + sides[i] = SIDE_BACK; + } + else + { + sides[i] = SIDE_ON; + } + counts[sides[i]]++; + } + sides[i] = sides[0]; + dists[i] = dists[0]; + + if (!counts[0]) + { + FreeWinding (in); + *inout = NULL; + return; + } + if (!counts[1]) + return; // inout stays the same + + maxpts = in->numpoints+4; // cant use counts[0]+2 because + // of fp grouping errors + + f = AllocWinding (maxpts); + + for (i=0 ; inumpoints ; i++) + { + Vector& 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 + Vector& 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, const Vector &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 d, edgedist; + Vector dir, edgenormal, facenormal; + vec_t area; + vec_t facedist; + + if (w->numpoints < 3) + Error ("CheckWinding: %i points",w->numpoints); + + area = WindingArea(w); + if (area < 1) + Error ("CheckWinding: %f area", area); + + WindingPlane (w, facenormal, &facedist); + + for (i=0 ; inumpoints ; i++) + { + Vector& p1 = w->p[i]; + + for (j=0 ; j<3 ; j++) + { + if (p1[j] > MAX_COORD_INTEGER || p1[j] < MIN_COORD_INTEGER) + Error ("CheckFace: out of range: %f",p1[j]); + } + + j = i+1 == w->numpoints ? 0 : i+1; + + // check the point is on the face plane + d = DotProduct (p1, facenormal) - facedist; + if (d < -ON_EPSILON || d > ON_EPSILON) + Error ("CheckWinding: point off plane"); + + // check the edge isnt degenerate + Vector& p2 = w->p[j]; + VectorSubtract (p2, p1, dir); + + if (VectorLength (dir) < ON_EPSILON) + Error ("CheckWinding: degenerate edge"); + + CrossProduct (facenormal, dir, edgenormal); + VectorNormalize (edgenormal); + edgedist = DotProduct (p1, edgenormal); + edgedist += ON_EPSILON; + + // all other points must be on front side + for (j=0 ; jnumpoints ; j++) + { + if (j == i) + continue; + d = DotProduct (w->p[j], edgenormal); + if (d > edgedist) + Error ("CheckWinding: non-convex"); + } + } +} + + +/* +============ +WindingOnPlaneSide +============ +*/ +int WindingOnPlaneSide (winding_t *w, const Vector &normal, vec_t dist) +{ + qboolean front, back; + int i; + vec_t d; + + front = false; + back = false; + for (i=0 ; inumpoints ; i++) + { + d = DotProduct (w->p[i], normal) - dist; + if (d < -ON_EPSILON) + { + if (front) + return SIDE_CROSS; + back = true; + continue; + } + if (d > ON_EPSILON) + { + if (back) + return SIDE_CROSS; + front = true; + continue; + } + } + + if (back) + return SIDE_BACK; + if (front) + return SIDE_FRONT; + return SIDE_ON; +} + + +//----------------------------------------------------------------------------- +// Purpose: 2d point inside of winding test (assumes the point resides in the +// winding plane) +//----------------------------------------------------------------------------- +bool PointInWinding( const Vector &pt, winding_t *pWinding ) +{ + if( !pWinding ) + return false; + +#if 0 + // + // NOTE: this will be a quicker way to calculate this, however I don't + // know the trick off hand (post dot product tests??) + // TODO: look in graphics gems!!!! (cab) + // + + Vector edge1, edge2; + for( int ndxPt = 0; ndxPt < pWinding->numpoints; ndxPt++ ) + { + edge1 = pWinding->p[ndxPt] - pt; + edge2 = pWinding->p[(ndxPt+1)%pWinding->numpoints] - pt; + + VectorNormalize( edge1 ); + VectorNormalize( edge2 ); + + if( edge2.Dot( edge1 ) < 0.0f ) + return false; + } + + return true; + +#else + Vector edge, toPt, cross, testCross; + + // + // get the first normal to test + // + toPt = pt - pWinding->p[0]; + edge = pWinding->p[1] - pWinding->p[0]; + testCross = edge.Cross( toPt ); + VectorNormalize( testCross ); + + for( int ndxPt = 1; ndxPt < pWinding->numpoints; ndxPt++ ) + { + toPt = pt - pWinding->p[ndxPt]; + edge = pWinding->p[(ndxPt+1)%pWinding->numpoints] - pWinding->p[ndxPt]; + cross = edge.Cross( toPt ); + VectorNormalize( cross ); + + if( cross.Dot( testCross ) < 0.0f ) + return false; + } + + return true; +#endif +} + +void TranslateWinding( winding_t *pWinding, const Vector &offset ) +{ + for ( int i = 0; i < pWinding->numpoints; i++ ) + { + pWinding->p[i] += offset; + } +} diff --git a/mp/src/utils/common/polylib.h b/mp/src/utils/common/polylib.h new file mode 100644 index 00000000..1750a82a --- /dev/null +++ b/mp/src/utils/common/polylib.h @@ -0,0 +1,78 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef POLYLIB_H +#define POLYLIB_H +#pragma once + +#ifndef MATHLIB_H +#include "mathlib/mathlib.h" +#endif + +struct winding_t +{ + int numpoints; + Vector *p; // variable sized + int maxpoints; + winding_t *next; +}; + +#define MAX_POINTS_ON_WINDING 64 + +// you can define on_epsilon in the makefile as tighter +// point on plane side epsilon +// todo: need a world-space epsilon, a lightmap-space epsilon, and a texture space epsilon +// or at least convert from a world-space epsilon to lightmap and texture space epsilons +#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, Vector ¢er); +vec_t WindingAreaAndBalancePoint( winding_t *w, Vector ¢er ); +void ClipWindingEpsilon (winding_t *in, const Vector &normal, vec_t dist, + vec_t epsilon, winding_t **front, winding_t **back); + +// translates everything by offset, then does the clip, then translates back (to keep precision) +void ClipWindingEpsilon_Offset( winding_t *in, const Vector &normal, vec_t dist, vec_t epsilon, winding_t **front, winding_t **back, const Vector &offset ); + +void ClassifyWindingEpsilon( winding_t *in, const Vector &normal, vec_t dist, + vec_t epsilon, winding_t **front, winding_t **back, winding_t **on); +void ClassifyWindingEpsilon_Offset( winding_t *in, const Vector &normal, vec_t dist, + vec_t epsilon, winding_t **front, winding_t **back, winding_t **on, const Vector &offset); + +winding_t *ChopWinding (winding_t *in, const Vector &normal, vec_t dist); +winding_t *CopyWinding (winding_t *w); +winding_t *ReverseWinding (winding_t *w); +winding_t *BaseWindingForPlane (const Vector &normal, vec_t dist); +void CheckWinding (winding_t *w); +void WindingPlane (winding_t *w, Vector &normal, vec_t *dist); +void RemoveColinearPoints (winding_t *w); +int WindingOnPlaneSide (winding_t *w, const Vector &normal, vec_t dist); +void FreeWinding (winding_t *w); +void WindingBounds (winding_t *w, Vector &mins, Vector &maxs); + +void ChopWindingInPlace (winding_t **w, const Vector &normal, vec_t dist, vec_t epsilon); +// frees the original if clipped + +bool PointInWinding( Vector const &pt, winding_t *pWinding ); + +// translates a winding by offset +void TranslateWinding( winding_t *pWinding, const Vector &offset ); + +void pw(winding_t *w); + + +#endif // POLYLIB_H diff --git a/mp/src/utils/common/qfiles.h b/mp/src/utils/common/qfiles.h new file mode 100644 index 00000000..b1d2232f --- /dev/null +++ b/mp/src/utils/common/qfiles.h @@ -0,0 +1,42 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef QFILES_H +#define QFILES_H +#pragma once + + +// +// qfiles.h: quake file formats +// This file must be identical in the quake and utils directories +// + +#include "basetypes.h" +#include "commonmacros.h" +#include "worldsize.h" +#include "bspfile.h" + +#define MAX_OSPATH 260 +#define MAX_QPATH 64 + +/* +======================================================================== + +The .pak files are just a linear collapse of a directory tree + +======================================================================== +*/ + +#define IDPAKHEADER (('K'<<24)+('C'<<16)+('A'<<8)+'P') + +#endif // QFILES_H diff --git a/mp/src/utils/common/scratchpad_helpers.cpp b/mp/src/utils/common/scratchpad_helpers.cpp new file mode 100644 index 00000000..9a3c2d74 --- /dev/null +++ b/mp/src/utils/common/scratchpad_helpers.cpp @@ -0,0 +1,103 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "scratchpad_helpers.h" +#include "bspfile.h" +#include "bsplib.h" + + +void ScratchPad_DrawWinding( + IScratchPad3D *pPad, + int nPoints, + Vector *pPoints, + Vector vColor, + Vector vOffset ) +{ + for ( int i=0; i < nPoints; i++ ) + { + pPad->DrawLine( CSPVert( pPoints[i]+vOffset, vColor ), CSPVert( pPoints[(i+1)%nPoints]+vOffset, vColor ) ); + } +} + + +void ScratchPad_DrawFace( IScratchPad3D *pPad, dface_t *f, int iFaceNumber, const CSPColor &faceColor, const Vector &vOffset ) +{ + // Draw the face's outline, then put text for its face index on it too. + CUtlVector points; + for ( int iEdge = 0; iEdge < f->numedges; iEdge++ ) + { + int v; + int se = dsurfedges[f->firstedge + iEdge]; + if ( se < 0 ) + v = dedges[-se].v[1]; + else + v = dedges[se].v[0]; + + dvertex_t *dv = &dvertexes[v]; + points.AddToTail( dv->point ); + } + + // Draw the outline. + Vector vCenter( 0, 0, 0 ); + for ( int iEdge=0; iEdge < points.Count(); iEdge++ ) + { + pPad->DrawLine( CSPVert( points[iEdge]+vOffset, faceColor ), CSPVert( points[(iEdge+1)%points.Count()]+vOffset, faceColor ) ); + vCenter += points[iEdge]; + } + vCenter /= points.Count(); + vCenter += vOffset; + + // Draw the text. + if ( iFaceNumber != -1 ) + { + char str[64]; + Q_snprintf( str, sizeof( str ), "%d", iFaceNumber ); + + CTextParams params; + + params.m_bCentered = true; + params.m_bOutline = true; + params.m_flLetterWidth = 2; + params.m_vColor.Init( 1, 0, 0 ); + + VectorAngles( dplanes[f->planenum].normal, params.m_vAngles ); + params.m_bTwoSided = true; + + params.m_vPos = vCenter; + + pPad->DrawText( str, params ); + } +} + + +void ScratchPad_DrawWorld( IScratchPad3D *pPad, bool bDrawFaceNumbers, const CSPColor &faceColor ) +{ + bool bAutoFlush = pPad->GetAutoFlush(); + pPad->SetAutoFlush( false ); + + for ( int i=0; i < numleafs; i++ ) + { + dleaf_t *l = &dleafs[i]; + if ( l->contents & CONTENTS_DETAIL ) + continue; + + for ( int z=0; z < l->numleaffaces; z++ ) + { + int iFace = dleaffaces[l->firstleafface+z]; + dface_t *f = &dfaces[iFace]; + ScratchPad_DrawFace( pPad, f, bDrawFaceNumbers ? i : -1 ); + } + } + + pPad->SetAutoFlush( bAutoFlush ); +} + + +void ScratchPad_DrawWorld( bool bDrawFaceNumbers, const CSPColor &faceColor ) +{ + IScratchPad3D *pPad = ScratchPad3D_Create(); + ScratchPad_DrawWorld( pPad, bDrawFaceNumbers ); +} diff --git a/mp/src/utils/common/scratchpad_helpers.h b/mp/src/utils/common/scratchpad_helpers.h new file mode 100644 index 00000000..8f409fca --- /dev/null +++ b/mp/src/utils/common/scratchpad_helpers.h @@ -0,0 +1,25 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef SCRATCHPAD_HELPERS_H +#define SCRATCHPAD_HELPERS_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "iscratchpad3d.h" +#include "bspfile.h" + + +void ScratchPad_DrawWinding( IScratchPad3D *pPad, int nPoints, Vector *pPoints, Vector vColor, Vector vOffset = Vector(0,0,0) ); + +void ScratchPad_DrawFace( IScratchPad3D *pPad, dface_t *f, int iFaceNumber = -1, const CSPColor &faceColor=CSPColor(1,1,1,1), const Vector &vOffset=Vector(0,0,0) ); +void ScratchPad_DrawWorld( IScratchPad3D *pPad, bool bDrawFaceNumbers, const CSPColor &faceColor=CSPColor(1,1,1,1) ); +void ScratchPad_DrawWorld( bool bDrawFaceNumbers, const CSPColor &faceColor=CSPColor(1,1,1,1) ); + + +#endif // SCRATCHPAD_HELPERS_H diff --git a/mp/src/utils/common/scriplib.cpp b/mp/src/utils/common/scriplib.cpp new file mode 100644 index 00000000..469c7885 --- /dev/null +++ b/mp/src/utils/common/scriplib.cpp @@ -0,0 +1,1349 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//===========================================================================// + +// scriplib.c + +#include "tier1/strtools.h" +#include "tier2/tier2.h" +#include "cmdlib.h" +#include "scriplib.h" +#if defined( _X360 ) +#include "xbox\xbox_win32stubs.h" +#endif +#if defined(POSIX) +#include "../../filesystem/linux_support.h" +#include +#endif +/* +============================================================================= + + PARSING STUFF + +============================================================================= +*/ + +typedef struct +{ + char filename[1024]; + char *buffer,*script_p,*end_p; + int line; + + char macrobuffer[4096]; + char *macroparam[64]; + char *macrovalue[64]; + int nummacroparams; + +} script_t; + +#define MAX_INCLUDES 64 +script_t scriptstack[MAX_INCLUDES]; +script_t *script = NULL; +int scriptline; + +char token[MAXTOKEN]; +qboolean endofscript; +qboolean tokenready; // only true if UnGetToken was just called + +typedef struct +{ + char *param; + char *value; +} variable_t; + +CUtlVector g_definevariable; + +/* +Callback stuff +*/ + +void DefaultScriptLoadedCallback( char const *pFilenameLoaded, char const *pIncludedFromFileName, int nIncludeLineNumber ) +{ + NULL; +} + +SCRIPT_LOADED_CALLBACK g_pfnCallback = DefaultScriptLoadedCallback; + +SCRIPT_LOADED_CALLBACK SetScriptLoadedCallback( SCRIPT_LOADED_CALLBACK pfnNewScriptLoadedCallback ) +{ + SCRIPT_LOADED_CALLBACK pfnCallback = g_pfnCallback; + g_pfnCallback = pfnNewScriptLoadedCallback; + return pfnCallback; +} + +/* +============== +AddScriptToStack +============== +*/ +void AddScriptToStack (char *filename, ScriptPathMode_t pathMode = SCRIPT_USE_ABSOLUTE_PATH) +{ + int size; + + script++; + if (script == &scriptstack[MAX_INCLUDES]) + Error ("script file exceeded MAX_INCLUDES"); + + if ( pathMode == SCRIPT_USE_RELATIVE_PATH ) + Q_strncpy( script->filename, filename, sizeof( script->filename ) ); + else + Q_strncpy (script->filename, ExpandPath (filename), sizeof( script->filename ) ); + + size = LoadFile (script->filename, (void **)&script->buffer); + + // printf ("entering %s\n", script->filename); + if ( g_pfnCallback ) + { + if ( script == scriptstack + 1 ) + g_pfnCallback( script->filename, NULL, 0 ); + else + g_pfnCallback( script->filename, script[-1].filename, script[-1].line ); + } + + script->line = 1; + + script->script_p = script->buffer; + script->end_p = script->buffer + size; +} + + +/* +============== +LoadScriptFile +============== +*/ +void LoadScriptFile (char *filename, ScriptPathMode_t pathMode) +{ + script = scriptstack; + AddScriptToStack (filename, pathMode); + + endofscript = false; + tokenready = false; +} + + +/* +============== +============== +*/ + +script_t *macrolist[256]; +int nummacros; + +void DefineMacro( char *macroname ) +{ + script_t *pmacro = (script_t *)malloc( sizeof( script_t ) ); + + strcpy( pmacro->filename, macroname ); + pmacro->line = script->line; + pmacro->nummacroparams = 0; + + char *mp = pmacro->macrobuffer; + char *cp = script->script_p; + + while (TokenAvailable( )) + { + GetToken( false ); + + if (token[0] == '\\' && token[1] == '\\') + { + break; + } + cp = script->script_p; + + pmacro->macroparam[pmacro->nummacroparams++] = mp; + + strcpy( mp, token ); + mp += strlen( token ) + 1; + + if (mp >= pmacro->macrobuffer + sizeof( pmacro->macrobuffer )) + Error("Macro buffer overflow\n"); + } + // roll back script_p to previous valid location + script->script_p = cp; + + // find end of macro def + while (*cp && *cp != '\n') + { + //Msg("%d ", *cp ); + if (*cp == '\\' && *(cp+1) == '\\') + { + // skip till end of line + while (*cp && *cp != '\n') + { + *cp = ' '; // replace with spaces + cp++; + } + + if (*cp) + { + cp++; + } + } + else + { + cp++; + } + } + + int size = (cp - script->script_p); + + pmacro->buffer = (char *)malloc( size + 1); + memcpy( pmacro->buffer, script->script_p, size ); + pmacro->buffer[size] = '\0'; + pmacro->end_p = &pmacro->buffer[size]; + + macrolist[nummacros++] = pmacro; + + script->script_p = cp; +} + + +void DefineVariable( char *variablename ) +{ + variable_t v; + + v.param = strdup( variablename ); + + GetToken( false ); + + v.value = strdup( token ); + + g_definevariable.AddToTail( v ); +} + + + +/* +============== +============== +*/ +bool AddMacroToStack( char *macroname ) +{ + // lookup macro + if (macroname[0] != '$') + return false; + + int i; + for (i = 0; i < nummacros; i++) + { + if (strcmpi( macrolist[i]->filename, ¯oname[1] ) == 0) + { + break; + } + } + if (i == nummacros) + return false; + + script_t *pmacro = macrolist[i]; + + // get tokens + script_t *pnext = script + 1; + + pnext++; + if (pnext == &scriptstack[MAX_INCLUDES]) + Error ("script file exceeded MAX_INCLUDES"); + + // get tokens + char *cp = pnext->macrobuffer; + + pnext->nummacroparams = pmacro->nummacroparams; + + for (i = 0; i < pnext->nummacroparams; i++) + { + GetToken(false); + + strcpy( cp, token ); + pnext->macroparam[i] = pmacro->macroparam[i]; + pnext->macrovalue[i] = cp; + + cp += strlen( token ) + 1; + + if (cp >= pnext->macrobuffer + sizeof( pnext->macrobuffer )) + Error("Macro buffer overflow\n"); + } + + script = pnext; + strcpy( script->filename, pmacro->filename ); + + int size = pmacro->end_p - pmacro->buffer; + script->buffer = (char *)malloc( size + 1 ); + memcpy( script->buffer, pmacro->buffer, size ); + pmacro->buffer[size] = '\0'; + script->script_p = script->buffer; + script->end_p = script->buffer + size; + script->line = pmacro->line; + + return true; +} + + + +bool ExpandMacroToken( char *&token_p ) +{ + if ( script->nummacroparams && *script->script_p == '$' ) + { + char *cp = script->script_p + 1; + + while ( *cp > 32 && *cp != '$' ) + { + cp++; + } + + // found a word with $'s on either end? + if (*cp != '$') + return false; + + // get token pointer + char *tp = script->script_p + 1; + int len = (cp - tp); + *(tp + len) = '\0'; + + // lookup macro parameter + int index = 0; + for (index = 0; index < script->nummacroparams; index++) + { + if (stricmp( script->macroparam[index], tp ) == 0) + break; + } + if (index >= script->nummacroparams) + { + Error("unknown macro token \"%s\" in %s\n", tp, script->filename ); + } + + // paste token into + len = strlen( script->macrovalue[index] ); + strcpy( token_p, script->macrovalue[index] ); + token_p += len; + + script->script_p = cp + 1; + + if (script->script_p >= script->end_p) + Error ("Macro expand overflow\n"); + + if (token_p >= &token[MAXTOKEN]) + Error ("Token too large on line %i\n",scriptline); + + return true; + } + return false; +} + + + +/* +============== +============== +*/ +// FIXME: this should create a new script context so the individual tokens in the variable can be parsed +bool ExpandVariableToken( char *&token_p ) +{ + if ( *script->script_p == '$' ) + { + char *cp = script->script_p + 1; + + while ( *cp > 32 && *cp != '$' ) + { + cp++; + } + + // found a word with $'s on either end? + if (*cp != '$') + return false; + + // get token pointer + char *tp = script->script_p + 1; + int len = (cp - tp); + *(tp + len) = '\0'; + + // lookup macro parameter + + int index; + for (index = 0; index < g_definevariable.Count(); index++) + { + if (Q_strnicmp( g_definevariable[index].param, tp, len ) == 0) + break; + } + + if (index >= g_definevariable.Count() ) + { + Error("unknown variable token \"%s\" in %s\n", tp, script->filename ); + } + + // paste token into + len = strlen( g_definevariable[index].value ); + strcpy( token_p, g_definevariable[index].value ); + token_p += len; + + script->script_p = cp + 1; + + if (script->script_p >= script->end_p) + Error ("Macro expand overflow\n"); + + if (token_p >= &token[MAXTOKEN]) + Error ("Token too large on line %i\n",scriptline); + + return true; + } + return false; +} + + + +/* +============== +ParseFromMemory +============== +*/ +void ParseFromMemory (char *buffer, int size) +{ + script = scriptstack; + script++; + if (script == &scriptstack[MAX_INCLUDES]) + Error ("script file exceeded MAX_INCLUDES"); + strcpy (script->filename, "memory buffer" ); + + script->buffer = buffer; + script->line = 1; + script->script_p = script->buffer; + script->end_p = script->buffer + size; + + endofscript = false; + tokenready = false; +} + + +//----------------------------------------------------------------------------- +// Used instead of ParseFromMemory to temporarily add a memory buffer +// to the script stack. ParseFromMemory just blows away the stack. +//----------------------------------------------------------------------------- +void PushMemoryScript( char *pszBuffer, const int nSize ) +{ + if ( script == NULL ) + { + script = scriptstack; + } + script++; + if ( script == &scriptstack[MAX_INCLUDES] ) + { + Error ( "script file exceeded MAX_INCLUDES" ); + } + strcpy (script->filename, "memory buffer" ); + + script->buffer = pszBuffer; + script->line = 1; + script->script_p = script->buffer; + script->end_p = script->buffer + nSize; + + endofscript = false; + tokenready = false; +} + + +//----------------------------------------------------------------------------- +// Used after calling PushMemoryScript to clean up the memory buffer +// added to the script stack. The normal end of script terminates +// all parsing at the end of a memory buffer even if there are more scripts +// remaining on the script stack +//----------------------------------------------------------------------------- +bool PopMemoryScript() +{ + if ( V_stricmp( script->filename, "memory buffer" ) ) + return false; + + if ( script == scriptstack ) + { + endofscript = true; + return false; + } + script--; + scriptline = script->line; + + endofscript = false; + + return true; +} + + +/* +============== +UnGetToken + +Signals that the current token was not used, and should be reported +for the next GetToken. Note that + +GetToken (true); +UnGetToken (); +GetToken (false); + +could cross a line boundary. +============== +*/ +void UnGetToken (void) +{ + tokenready = true; +} + + +qboolean EndOfScript (qboolean crossline) +{ + if (!crossline) + Error ("Line %i is incomplete\n",scriptline); + + if (!strcmp (script->filename, "memory buffer")) + { + endofscript = true; + return false; + } + + free (script->buffer); + script->buffer = NULL; + if (script == scriptstack+1) + { + endofscript = true; + return false; + } + script--; + scriptline = script->line; + // printf ("returning to %s\n", script->filename); + return GetToken (crossline); +} + + +//----------------------------------------------------------------------------- +// Purpose: Given an absolute path, do a find first find next on it and build +// a list of files. Physical file system only +//----------------------------------------------------------------------------- +static void FindFileAbsoluteList( CUtlVector< CUtlString > &outAbsolutePathNames, const char *pszFindName ) +{ + char szPath[MAX_PATH]; + V_strncpy( szPath, pszFindName, sizeof( szPath ) ); + V_StripFilename( szPath ); + + char szResult[MAX_PATH]; + FileFindHandle_t hFile = FILESYSTEM_INVALID_FIND_HANDLE; + + for ( const char *pszFoundFile = g_pFullFileSystem->FindFirst( pszFindName, &hFile ); pszFoundFile && hFile != FILESYSTEM_INVALID_FIND_HANDLE; pszFoundFile = g_pFullFileSystem->FindNext( hFile ) ) + { + V_ComposeFileName( szPath, pszFoundFile, szResult, sizeof( szResult ) ); + outAbsolutePathNames.AddToTail( szResult ); + } + + g_pFullFileSystem->FindClose( hFile ); +} + + +//----------------------------------------------------------------------------- +// Data for checking for single character tokens while parsing +//----------------------------------------------------------------------------- +bool g_bCheckSingleCharTokens = false; +CUtlString g_sSingleCharTokens; + + +//----------------------------------------------------------------------------- +// Sets whether the scriplib parser will do a special check for single +// character tokens. Returns previous state of whether single character +// tokens will be checked. +//----------------------------------------------------------------------------- +bool SetCheckSingleCharTokens( bool bCheck ) +{ + const bool bRetVal = g_bCheckSingleCharTokens; + + g_bCheckSingleCharTokens = bCheck; + + return bRetVal; +} + + +//----------------------------------------------------------------------------- +// Sets the list of single character tokens to check if SetCheckSingleCharTokens +// is turned on. +//----------------------------------------------------------------------------- +CUtlString SetSingleCharTokenList( const char *pszSingleCharTokenList ) +{ + const CUtlString sRetVal = g_sSingleCharTokens; + + if ( pszSingleCharTokenList ) + { + g_sSingleCharTokens = pszSingleCharTokenList; + } + + return sRetVal; +} + + +/* +============== +GetToken +============== +*/ +qboolean GetToken (qboolean crossline) +{ + char *token_p; + + if (tokenready) // is a token allready waiting? + { + tokenready = false; + return true; + } + + // printf("script_p %x (%x)\n", script->script_p, script->end_p ); fflush( stdout ); + + if (script->script_p >= script->end_p) + { + return EndOfScript (crossline); + } + + tokenready = false; + + // skip space, ctrl chars +skipspace: + while (*script->script_p <= 32) + { + if (script->script_p >= script->end_p) + { + return EndOfScript (crossline); + } + if (*(script->script_p++) == '\n') + { + if (!crossline) + { + Error ("Line %i is incomplete\n",scriptline); + } + scriptline = ++script->line; + } + } + + if (script->script_p >= script->end_p) + { + return EndOfScript (crossline); + } + + // strip single line comments + if (*script->script_p == ';' || *script->script_p == '#' || // semicolon and # is comment field + (*script->script_p == '/' && *((script->script_p)+1) == '/')) // also make // a comment field + { + if (!crossline) + Error ("Line %i is incomplete\n",scriptline); + while (*script->script_p++ != '\n') + { + if (script->script_p >= script->end_p) + { + return EndOfScript (crossline); + } + } + scriptline = ++script->line; + goto skipspace; + } + + // strip out matching /* */ comments + if (*script->script_p == '/' && *((script->script_p)+1) == '*') + { + script->script_p += 2; + while (*script->script_p != '*' || *((script->script_p)+1) != '/') + { + if (*script->script_p++ != '\n') + { + if (script->script_p >= script->end_p) + { + return EndOfScript (crossline); + } + + scriptline = ++script->line; + } + } + script->script_p += 2; + goto skipspace; + } + + // copy token to buffer + 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 if ( g_bCheckSingleCharTokens && !g_sSingleCharTokens.IsEmpty() && strchr( g_sSingleCharTokens.String(), *script->script_p ) != NULL ) + { + *token_p++ = *script->script_p++; + } + else // regular token + while ( *script->script_p > 32 && *script->script_p != ';') + { + if ( !ExpandMacroToken( token_p ) ) + { + if ( !ExpandVariableToken( token_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); + + } + } + } + + // add null to end of token + *token_p = 0; + + // check for other commands + if ( !stricmp( token, "$include" ) ) + { + GetToken( false ); + + bool bFallbackToToken = true; + + CUtlVector< CUtlString > expandedPathList; + + if ( CmdLib_ExpandWithBasePaths( expandedPathList, token ) > 0 ) + { + for ( int i = 0; i < expandedPathList.Count(); ++i ) + { + CUtlVector< CUtlString > findFileList; + FindFileAbsoluteList( findFileList, expandedPathList[i].String() ); + + if ( findFileList.Count() > 0 ) + { + bFallbackToToken = false; + + // Only add the first set of glob matches from the first base path + for ( int j = 0; j < findFileList.Count(); ++j ) + { + AddScriptToStack( const_cast< char * >( findFileList[j].String() ) ); + } + + break; + } + } + } + + if ( bFallbackToToken ) + { + AddScriptToStack( token ); + } + + return GetToken( crossline ); + } + else if (!stricmp (token, "$definemacro")) + { + GetToken (false); + DefineMacro(token); + return GetToken (crossline); + } + else if (!stricmp (token, "$definevariable")) + { + GetToken (false); + DefineVariable(token); + return GetToken (crossline); + } + else if (AddMacroToStack( token )) + { + return GetToken (crossline); + } + + return true; +} + + +/* +============== +GetExprToken - use C mathematical operator parsing rules to split tokens instead of whitespace +============== +*/ +qboolean GetExprToken (qboolean crossline) +{ + char *token_p; + + if (tokenready) // is a token allready waiting? + { + tokenready = false; + return true; + } + + if (script->script_p >= script->end_p) + return EndOfScript (crossline); + + tokenready = false; + +// +// skip space +// +skipspace: + while (*script->script_p <= 32) + { + if (script->script_p >= script->end_p) + return EndOfScript (crossline); + if (*script->script_p++ == '\n') + { + if (!crossline) + Error ("Line %i is incomplete\n",scriptline); + scriptline = ++script->line; + } + } + + if (script->script_p >= script->end_p) + return EndOfScript (crossline); + + if (*script->script_p == ';' || *script->script_p == '#' || // semicolon and # is comment field + (*script->script_p == '/' && *((script->script_p)+1) == '/')) // also make // a comment field + { + if (!crossline) + Error ("Line %i is incomplete\n",scriptline); + while (*script->script_p++ != '\n') + if (script->script_p >= script->end_p) + return EndOfScript (crossline); + goto skipspace; + } + +// +// 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 + { + if ( V_isalpha( *script->script_p ) || *script->script_p == '_' ) + { + // regular token + while ( V_isalnum( *script->script_p ) || *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); + } + } + else if ( V_isdigit( *script->script_p ) || *script->script_p == '.' ) + { + // regular token + while ( V_isdigit( *script->script_p ) || *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); + } + } + else + { + // single char + *token_p++ = *script->script_p++; + } + } + + *token_p = 0; + + if (!stricmp (token, "$include")) + { + GetToken (false); + AddScriptToStack (token); + return GetToken (crossline); + } + + return true; +} + + +/* +============== +TokenAvailable + +Returns true if there is another token on the line +============== +*/ +qboolean TokenAvailable (void) +{ + char *search_p; + + if (tokenready) // is a token allready waiting? + { + return true; + } + + search_p = script->script_p; + + if (search_p >= script->end_p) + return false; + + while ( *search_p <= 32) + { + if (*search_p == '\n') + return false; + search_p++; + if (search_p == script->end_p) + return false; + + } + + if (*search_p == ';' || *search_p == '#' || // semicolon and # is comment field + (*search_p == '/' && *((search_p)+1) == '/')) // also make // a comment field + return false; + + return true; +} + +qboolean GetTokenizerStatus( char **pFilename, int *pLine ) +{ + // is this the default state? + if (!script) + return false; + + if (script->script_p >= script->end_p) + return false; + + if (pFilename) + { + *pFilename = script->filename; + } + if (pLine) + { + *pLine = script->line; + } + return true; +} + + +#include +#include +#ifdef WIN32 +#include +#include +#include +#endif +#include +#include +#include +#include +#include "tier1/utlbuffer.h" + +class CScriptLib : public IScriptLib +{ +public: + virtual bool ReadFileToBuffer( const char *pSourceName, CUtlBuffer &buffer, bool bText = false, bool bNoOpenFailureWarning = false ); + virtual bool WriteBufferToFile( const char *pTargetName, CUtlBuffer &buffer, DiskWriteMode_t writeMode ); + virtual int FindFiles( char* pFileMask, bool bRecurse, CUtlVector &fileList ); + virtual char *MakeTemporaryFilename( char const *pchModPath, char *pPath, int pathSize ); + virtual void DeleteTemporaryFiles( const char *pFileMask ); + virtual int CompareFileTime( const char *pFilenameA, const char *pFilenameB ); + virtual bool DoesFileExist( const char *pFilename ); + +private: + + int GetFileList( const char* pDirPath, const char* pPattern, CUtlVector< fileList_t > &fileList ); + void RecurseFileTree_r( const char* pDirPath, int depth, CUtlVector< CUtlString > &dirList ); +}; + +static CScriptLib g_ScriptLib; +IScriptLib *scriptlib = &g_ScriptLib; +IScriptLib *g_pScriptLib = &g_ScriptLib; + +//----------------------------------------------------------------------------- +// Existence check +//----------------------------------------------------------------------------- +bool CScriptLib::DoesFileExist( const char *pFilename ) +{ + return g_pFullFileSystem->FileExists( pFilename ); +} + +//----------------------------------------------------------------------------- +// Purpose: Helper utility, read file into buffer +//----------------------------------------------------------------------------- +bool CScriptLib::ReadFileToBuffer( const char *pSourceName, CUtlBuffer &buffer, bool bText, bool bNoOpenFailureWarning ) +{ + bool bSuccess = true; + + if ( !g_pFullFileSystem->ReadFile( pSourceName, NULL, buffer ) ) + { + if ( !bNoOpenFailureWarning ) + { + Msg( "ReadFileToBuffer(): Error opening %s: %s\n", pSourceName, strerror( errno ) ); + } + return false; + } + + if ( bText ) + { + // force it into text mode + buffer.SetBufferType( true, true ); + } + else + { + buffer.SetBufferType( false, false ); + } + + return bSuccess; +} + +//----------------------------------------------------------------------------- +// Purpose: Helper utility, Write buffer to file +//----------------------------------------------------------------------------- +bool CScriptLib::WriteBufferToFile( const char *pTargetName, CUtlBuffer &buffer, DiskWriteMode_t writeMode ) +{ + char* ptr; + char dirPath[MAX_PATH]; + + bool bSuccess = true; + + // create path + // prime and skip to first seperator + strcpy( dirPath, pTargetName ); + ptr = strchr( dirPath, '\\' ); + while ( ptr ) + { + ptr = strchr( ptr+1, '\\' ); + if ( ptr ) + { + *ptr = '\0'; + _mkdir( dirPath ); + *ptr = '\\'; + } + } + + bool bDoWrite = false; + if ( writeMode == WRITE_TO_DISK_ALWAYS ) + { + bDoWrite = true; + } + else if ( writeMode == WRITE_TO_DISK_UPDATE ) + { + if ( DoesFileExist( pTargetName ) ) + { + bDoWrite = true; + } + } + + if ( bDoWrite ) + { + bSuccess = g_pFullFileSystem->WriteFile( pTargetName, NULL, buffer ); + } + + return bSuccess; +} + +//----------------------------------------------------------------------------- +// Returns -1, 0, or 1. +//----------------------------------------------------------------------------- +int CScriptLib::CompareFileTime( const char *pFilenameA, const char *pFilenameB ) +{ + int timeA = g_pFullFileSystem->GetFileTime( (char *)pFilenameA ); + int timeB = g_pFullFileSystem->GetFileTime( (char *)pFilenameB ); + + if ( timeA == -1) + { + // file a not exist + timeA = 0; + } + if ( timeB == -1 ) + { + // file b not exist + timeB = 0; + } + + if ( (unsigned int)timeA < (unsigned int)timeB ) + { + return -1; + } + else if ( (unsigned int)timeA > (unsigned int)timeB ) + { + return 1; + } + + return 0; +} + +//----------------------------------------------------------------------------- +// Make a temporary filename +//----------------------------------------------------------------------------- +char *CScriptLib::MakeTemporaryFilename( char const *pchModPath, char *pPath, int pathSize ) +{ + char *pBuffer = _tempnam( pchModPath, "mgd_" ); + if ( pBuffer[0] == '\\' ) + { + pBuffer++; + } + if ( pBuffer[strlen( pBuffer )-1] == '.' ) + { + pBuffer[strlen( pBuffer )-1] = '\0'; + } + V_snprintf( pPath, pathSize, "%s.tmp", pBuffer ); + + free( pBuffer ); + + return pPath; +} + +//----------------------------------------------------------------------------- +// Delete temporary files +//----------------------------------------------------------------------------- +void CScriptLib::DeleteTemporaryFiles( const char *pFileMask ) +{ +#if !defined( _X360 ) + const char *pEnv = getenv( "temp" ); + if ( !pEnv ) + { + pEnv = getenv( "tmp" ); + } + + if ( pEnv ) + { + char tempPath[MAX_PATH]; + strcpy( tempPath, pEnv ); + V_AppendSlash( tempPath, sizeof( tempPath ) ); + strcat( tempPath, pFileMask ); + + CUtlVector fileList; + FindFiles( tempPath, false, fileList ); + for ( int i=0; i &fileList ) +{ + char sourcePath[MAX_PATH]; + char fullPath[MAX_PATH]; + bool bFindDirs; + + fileList.Purge(); + + strcpy( sourcePath, pDirPath ); + int len = (int)strlen( sourcePath ); + if ( !len ) + { + strcpy( sourcePath, ".\\" ); + } + else if ( sourcePath[len-1] != '\\' ) + { + sourcePath[len] = '\\'; + sourcePath[len+1] = '\0'; + } + + strcpy( fullPath, sourcePath ); + if ( pPattern[0] == '\\' && pPattern[1] == '\0' ) + { + // find directories only + bFindDirs = true; + strcat( fullPath, "*" ); + } + else + { + // find files, use provided pattern + bFindDirs = false; + strcat( fullPath, pPattern ); + } + +#ifdef WIN32 + struct _finddata_t findData; + intptr_t h = _findfirst( fullPath, &findData ); + if ( h == -1 ) + { + return 0; + } + + do + { + // dos attribute complexities i.e. _A_NORMAL is 0 + if ( bFindDirs ) + { + // skip non dirs + if ( !( findData.attrib & _A_SUBDIR ) ) + continue; + } + else + { + // skip dirs + if ( findData.attrib & _A_SUBDIR ) + continue; + } + + if ( !stricmp( findData.name, "." ) ) + continue; + + if ( !stricmp( findData.name, ".." ) ) + continue; + + char fileName[MAX_PATH]; + strcpy( fileName, sourcePath ); + strcat( fileName, findData.name ); + + int j = fileList.AddToTail(); + fileList[j].fileName.Set( fileName ); + fileList[j].timeWrite = findData.time_write; + } + while ( !_findnext( h, &findData ) ); + + _findclose( h ); +#elif defined(POSIX) + FIND_DATA findData; + Q_FixSlashes( fullPath ); + void *h = FindFirstFile( fullPath, &findData ); + if ( (int)h == -1 ) + { + return 0; + } + + do + { + // dos attribute complexities i.e. _A_NORMAL is 0 + if ( bFindDirs ) + { + // skip non dirs + if ( !( findData.dwFileAttributes & S_IFDIR ) ) + continue; + } + else + { + // skip dirs + if ( findData.dwFileAttributes & S_IFDIR ) + continue; + } + + if ( !stricmp( findData.cFileName, "." ) ) + continue; + + if ( !stricmp( findData.cFileName, ".." ) ) + continue; + + char fileName[MAX_PATH]; + strcpy( fileName, sourcePath ); + strcat( fileName, findData.cFileName ); + + int j = fileList.AddToTail(); + fileList[j].fileName.Set( fileName ); + struct stat statbuf; + if ( stat( fileName, &statbuf ) ) +#ifdef OSX + fileList[j].timeWrite = statbuf.st_mtimespec.tv_sec; +#else + fileList[j].timeWrite = statbuf.st_mtime; +#endif + else + fileList[j].timeWrite = 0; + } + while ( !FindNextFile( h, &findData ) ); + + FindClose( h ); + +#else +#error +#endif + + + return fileList.Count(); +} + +//----------------------------------------------------------------------------- +// Purpose: Recursively determine directory tree +//----------------------------------------------------------------------------- +void CScriptLib::RecurseFileTree_r( const char* pDirPath, int depth, CUtlVector< CUtlString > &dirList ) +{ + // recurse from source directory, get directories only + CUtlVector< fileList_t > fileList; + int dirCount = GetFileList( pDirPath, "\\", fileList ); + if ( !dirCount ) + { + // add directory name to search tree + int j = dirList.AddToTail(); + dirList[j].Set( pDirPath ); + return; + } + + for ( int i=0; i &fileList ) +{ + char dirPath[MAX_PATH]; + char pattern[MAX_PATH]; + char extension[MAX_PATH]; + + // get path only + strcpy( dirPath, pFileMask ); + V_StripFilename( dirPath ); + + // get pattern only + V_FileBase( pFileMask, pattern, sizeof( pattern ) ); + V_ExtractFileExtension( pFileMask, extension, sizeof( extension ) ); + if ( extension[0] ) + { + strcat( pattern, "." ); + strcat( pattern, extension ); + } + + if ( !bRecurse ) + { + GetFileList( dirPath, pattern, fileList ); + } + else + { + // recurse and get the tree + CUtlVector< fileList_t > tempList; + CUtlVector< CUtlString > dirList; + RecurseFileTree_r( dirPath, 0, dirList ); + for ( int i=0; i &fileList ) = 0; + virtual char *MakeTemporaryFilename( char const *pchModPath, char *pPath, int pathSize ) = 0; + virtual void DeleteTemporaryFiles( const char *pFileMask ) = 0; + virtual int CompareFileTime( const char *pFilenameA, const char *pFilenameB ) = 0; + virtual bool DoesFileExist( const char *pFilename ) = 0; +}; + +extern IScriptLib *scriptlib; + + +#endif // SCRIPLIB_H diff --git a/mp/src/utils/common/threads.cpp b/mp/src/utils/common/threads.cpp new file mode 100644 index 00000000..344943c9 --- /dev/null +++ b/mp/src/utils/common/threads.cpp @@ -0,0 +1,257 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//=============================================================================// + +#define USED + +#include +#include "cmdlib.h" +#define NO_THREAD_NAMES +#include "threads.h" +#include "pacifier.h" + +#define MAX_THREADS 16 + + +class CRunThreadsData +{ +public: + int m_iThread; + void *m_pUserData; + RunThreadsFn m_Fn; +}; + +CRunThreadsData g_RunThreadsData[MAX_THREADS]; + + +int dispatch; +int workcount; +qboolean pacifier; + +qboolean threaded; +bool g_bLowPriorityThreads = false; + +HANDLE g_ThreadHandles[MAX_THREADS]; + + + +/* +============= +GetThreadWork + +============= +*/ +int GetThreadWork (void) +{ + int r; + + ThreadLock (); + + if (dispatch == workcount) + { + ThreadUnlock (); + return -1; + } + + UpdatePacifier( (float)dispatch / workcount ); + + r = dispatch; + dispatch++; + ThreadUnlock (); + + return r; +} + + +ThreadWorkerFn workfunction; + +void ThreadWorkerFunction( int iThread, void *pUserData ) +{ + int work; + + while (1) + { + work = GetThreadWork (); + if (work == -1) + break; + + workfunction( iThread, work ); + } +} + +void RunThreadsOnIndividual (int workcnt, qboolean showpacifier, ThreadWorkerFn func) +{ + if (numthreads == -1) + ThreadSetDefault (); + + workfunction = func; + RunThreadsOn (workcnt, showpacifier, ThreadWorkerFunction); +} + + +/* +=================================================================== + +WIN32 + +=================================================================== +*/ + +int numthreads = -1; +CRITICAL_SECTION crit; +static int enter; + + +class CCritInit +{ +public: + CCritInit() + { + InitializeCriticalSection (&crit); + } +} g_CritInit; + + + +void SetLowPriority() +{ + SetPriorityClass( GetCurrentProcess(), IDLE_PRIORITY_CLASS ); +} + + +void ThreadSetDefault (void) +{ + SYSTEM_INFO info; + + if (numthreads == -1) // not set manually + { + GetSystemInfo (&info); + numthreads = info.dwNumberOfProcessors; + if (numthreads < 1 || numthreads > 32) + numthreads = 1; + } + + Msg ("%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); +} + + +// This runs in the thread and dispatches a RunThreadsFn call. +DWORD WINAPI InternalRunThreadsFn( LPVOID pParameter ) +{ + CRunThreadsData *pData = (CRunThreadsData*)pParameter; + pData->m_Fn( pData->m_iThread, pData->m_pUserData ); + return 0; +} + + +void RunThreads_Start( RunThreadsFn fn, void *pUserData, ERunThreadsPriority ePriority ) +{ + Assert( numthreads > 0 ); + threaded = true; + + if ( numthreads > MAX_TOOL_THREADS ) + numthreads = MAX_TOOL_THREADS; + + for ( int i=0; i < numthreads ;i++ ) + { + g_RunThreadsData[i].m_iThread = i; + g_RunThreadsData[i].m_pUserData = pUserData; + g_RunThreadsData[i].m_Fn = fn; + + DWORD dwDummy; + g_ThreadHandles[i] = CreateThread( + NULL, // LPSECURITY_ATTRIBUTES lpsa, + 0, // DWORD cbStack, + InternalRunThreadsFn, // LPTHREAD_START_ROUTINE lpStartAddr, + &g_RunThreadsData[i], // LPVOID lpvThreadParm, + 0, // DWORD fdwCreate, + &dwDummy ); + + if ( ePriority == k_eRunThreadsPriority_UseGlobalState ) + { + if( g_bLowPriorityThreads ) + SetThreadPriority( g_ThreadHandles[i], THREAD_PRIORITY_LOWEST ); + } + else if ( ePriority == k_eRunThreadsPriority_Idle ) + { + SetThreadPriority( g_ThreadHandles[i], THREAD_PRIORITY_IDLE ); + } + } +} + + +void RunThreads_End() +{ + WaitForMultipleObjects( numthreads, g_ThreadHandles, TRUE, INFINITE ); + for ( int i=0; i < numthreads; i++ ) + CloseHandle( g_ThreadHandles[i] ); + + threaded = false; +} + + +/* +============= +RunThreadsOn +============= +*/ +void RunThreadsOn( int workcnt, qboolean showpacifier, RunThreadsFn fn, void *pUserData ) +{ + int start, end; + + start = Plat_FloatTime(); + dispatch = 0; + workcount = workcnt; + StartPacifier(""); + pacifier = showpacifier; + +#ifdef _PROFILE + threaded = false; + (*func)( 0 ); + return; +#endif + + + RunThreads_Start( fn, pUserData ); + RunThreads_End(); + + + end = Plat_FloatTime(); + if (pacifier) + { + EndPacifier(false); + printf (" (%i)\n", end-start); + } +} + + diff --git a/mp/src/utils/common/threads.h b/mp/src/utils/common/threads.h new file mode 100644 index 00000000..e29e9aab --- /dev/null +++ b/mp/src/utils/common/threads.h @@ -0,0 +1,65 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef THREADS_H +#define THREADS_H +#pragma once + + +// Arrays that are indexed by thread should always be MAX_TOOL_THREADS+1 +// large so THREADINDEX_MAIN can be used from the main thread. +#define MAX_TOOL_THREADS 16 +#define THREADINDEX_MAIN (MAX_TOOL_THREADS) + + +extern int numthreads; + +// If set to true, then all the threads that are created are low priority. +extern bool g_bLowPriorityThreads; + +typedef void (*ThreadWorkerFn)( int iThread, int iWorkItem ); +typedef void (*RunThreadsFn)( int iThread, void *pUserData ); + + +enum ERunThreadsPriority +{ + k_eRunThreadsPriority_UseGlobalState=0, // Default.. uses g_bLowPriorityThreads to decide what to set the priority to. + k_eRunThreadsPriority_Normal, // Doesn't touch thread priorities. + k_eRunThreadsPriority_Idle // Sets threads to idle priority. +}; + + +// Put the process into an idle priority class so it doesn't hog the UI. +void SetLowPriority(); + +void ThreadSetDefault (void); +int GetThreadWork (void); + +void RunThreadsOnIndividual ( int workcnt, qboolean showpacifier, ThreadWorkerFn fn ); + +void RunThreadsOn ( int workcnt, qboolean showpacifier, RunThreadsFn fn, void *pUserData=NULL ); + +// This version doesn't track work items - it just runs your function and waits for it to finish. +void RunThreads_Start( RunThreadsFn fn, void *pUserData, ERunThreadsPriority ePriority=k_eRunThreadsPriority_UseGlobalState ); +void RunThreads_End(); + +void ThreadLock (void); +void ThreadUnlock (void); + + +#ifndef NO_THREAD_NAMES +#define RunThreadsOn(n,p,f) { if (p) printf("%-20s ", #f ":"); RunThreadsOn(n,p,f); } +#define RunThreadsOnIndividual(n,p,f) { if (p) printf("%-20s ", #f ":"); RunThreadsOnIndividual(n,p,f); } +#endif + +#endif // THREADS_H diff --git a/mp/src/utils/common/tools_minidump.cpp b/mp/src/utils/common/tools_minidump.cpp new file mode 100644 index 00000000..a0c84209 --- /dev/null +++ b/mp/src/utils/common/tools_minidump.cpp @@ -0,0 +1,61 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include +#include +#include "tier0/minidump.h" +#include "tools_minidump.h" + +static bool g_bToolsWriteFullMinidumps = false; +static ToolsExceptionHandler g_pCustomExceptionHandler = NULL; + + +// --------------------------------------------------------------------------------- // +// Internal helpers. +// --------------------------------------------------------------------------------- // + +static LONG __stdcall ToolsExceptionFilter( struct _EXCEPTION_POINTERS *ExceptionInfo ) +{ + // Non VMPI workers write a minidump and show a crash dialog like normal. + int iType = MiniDumpNormal; + if ( g_bToolsWriteFullMinidumps ) + iType = MiniDumpWithDataSegs | MiniDumpWithIndirectlyReferencedMemory; + + WriteMiniDumpUsingExceptionInfo( ExceptionInfo->ExceptionRecord->ExceptionCode, ExceptionInfo, (MINIDUMP_TYPE)iType ); + return EXCEPTION_CONTINUE_SEARCH; +} + + +static LONG __stdcall ToolsExceptionFilter_Custom( struct _EXCEPTION_POINTERS *ExceptionInfo ) +{ + // Run their custom handler. + g_pCustomExceptionHandler( ExceptionInfo->ExceptionRecord->ExceptionCode, ExceptionInfo ); + return EXCEPTION_EXECUTE_HANDLER; // (never gets here anyway) +} + + +// --------------------------------------------------------------------------------- // +// Interface functions. +// --------------------------------------------------------------------------------- // + +void EnableFullMinidumps( bool bFull ) +{ + g_bToolsWriteFullMinidumps = bFull; +} + + +void SetupDefaultToolsMinidumpHandler() +{ + SetUnhandledExceptionFilter( ToolsExceptionFilter ); +} + + +void SetupToolsMinidumpHandler( ToolsExceptionHandler fn ) +{ + g_pCustomExceptionHandler = fn; + SetUnhandledExceptionFilter( ToolsExceptionFilter_Custom ); +} diff --git a/mp/src/utils/common/tools_minidump.h b/mp/src/utils/common/tools_minidump.h new file mode 100644 index 00000000..dfb44a9b --- /dev/null +++ b/mp/src/utils/common/tools_minidump.h @@ -0,0 +1,35 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef TOOLS_MINIDUMP_H +#define TOOLS_MINIDUMP_H +#ifdef _WIN32 +#pragma once +#endif + + + +// Defaults to false. If true, it'll write larger minidump files with the contents +// of global variables and following pointers from where the crash occurred. +void EnableFullMinidumps( bool bFull ); + + +// This handler catches any crash, writes a minidump, and runs the default system +// crash handler (which usually shows a dialog). +void SetupDefaultToolsMinidumpHandler(); + + +// (Used by VMPI) - you specify your own crash handler. +// Arguments passed to ToolsExceptionHandler +// exceptionCode - exception code +// pvExceptionInfo - on Win32 platform points to "struct _EXCEPTION_POINTERS" +// otherwise NULL +// +typedef void (*ToolsExceptionHandler)( unsigned long exceptionCode, void *pvExceptionInfo ); +void SetupToolsMinidumpHandler( ToolsExceptionHandler fn ); + + +#endif // MINIDUMP_H diff --git a/mp/src/utils/common/utilmatlib.cpp b/mp/src/utils/common/utilmatlib.cpp new file mode 100644 index 00000000..962bb3f5 --- /dev/null +++ b/mp/src/utils/common/utilmatlib.cpp @@ -0,0 +1,184 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// $NoKeywords: $ +//=============================================================================// + +// C callable material system interface for the utils. + +#include "materialsystem/imaterialsystem.h" +#include "materialsystem/imaterial.h" +#include "materialsystem/imaterialvar.h" +#include +#include "utilmatlib.h" +#include "tier0/dbg.h" +#include +#include "filesystem.h" +#include "materialsystem/materialsystem_config.h" +#include "mathlib/Mathlib.h" + +void LoadMaterialSystemInterface( CreateInterfaceFn fileSystemFactory ) +{ + if( g_pMaterialSystem ) + return; + + // materialsystem.dll should be in the path, it's in bin along with vbsp. + const char *pDllName = "materialsystem.dll"; + CSysModule *materialSystemDLLHInst; + materialSystemDLLHInst = g_pFullFileSystem->LoadModule( pDllName ); + if( !materialSystemDLLHInst ) + { + Error( "Can't load MaterialSystem.dll\n" ); + } + + CreateInterfaceFn clientFactory = Sys_GetFactory( materialSystemDLLHInst ); + if ( clientFactory ) + { + g_pMaterialSystem = (IMaterialSystem *)clientFactory( MATERIAL_SYSTEM_INTERFACE_VERSION, NULL ); + if ( !g_pMaterialSystem ) + { + Error( "Could not get the material system interface from materialsystem.dll (" __FILE__ ")" ); + } + } + else + { + Error( "Could not find factory interface in library MaterialSystem.dll" ); + } + + if (!g_pMaterialSystem->Init( "shaderapiempty.dll", 0, fileSystemFactory )) + { + Error( "Could not start the empty shader (shaderapiempty.dll)!" ); + } +} + +void InitMaterialSystem( const char *materialBaseDirPath, CreateInterfaceFn fileSystemFactory ) +{ + LoadMaterialSystemInterface( fileSystemFactory ); + MaterialSystem_Config_t config; + g_pMaterialSystem->OverrideConfig( config, false ); +} + +void ShutdownMaterialSystem( ) +{ + if ( g_pMaterialSystem ) + { + g_pMaterialSystem->Shutdown(); + g_pMaterialSystem = NULL; + } +} + +MaterialSystemMaterial_t FindMaterial( const char *materialName, bool *pFound, bool bComplain ) +{ + IMaterial *pMat = g_pMaterialSystem->FindMaterial( materialName, TEXTURE_GROUP_OTHER, bComplain ); + MaterialSystemMaterial_t matHandle = pMat; + + if ( pFound ) + { + *pFound = true; + if ( IsErrorMaterial( pMat ) ) + *pFound = false; + } + + return matHandle; +} + +void GetMaterialDimensions( MaterialSystemMaterial_t materialHandle, int *width, int *height ) +{ + PreviewImageRetVal_t retVal; + ImageFormat dummyImageFormat; + IMaterial *material = ( IMaterial * )materialHandle; + bool translucent; + retVal = material->GetPreviewImageProperties( width, height, &dummyImageFormat, &translucent ); + if (retVal != MATERIAL_PREVIEW_IMAGE_OK ) + { +#if 0 + if (retVal == MATERIAL_PREVIEW_IMAGE_BAD ) + { + Error( "problem getting preview image for %s", + g_pMaterialSystem->GetMaterialName( materialInfo[matID].materialHandle ) ); + } +#else + *width = 128; + *height = 128; +#endif + } +} + +void GetMaterialReflectivity( MaterialSystemMaterial_t materialHandle, float *reflectivityVect ) +{ + IMaterial *material = ( IMaterial * )materialHandle; + const IMaterialVar *reflectivityVar; + + bool found; + reflectivityVar = material->FindVar( "$reflectivity", &found, false ); + if( !found ) + { + Vector tmp; + material->GetReflectivity( tmp ); + VectorCopy( tmp.Base(), reflectivityVect ); + } + else + { + reflectivityVar->GetVecValue( reflectivityVect, 3 ); + } +} + +int GetMaterialShaderPropertyBool( MaterialSystemMaterial_t materialHandle, int propID ) +{ + IMaterial *material = ( IMaterial * )materialHandle; + switch( propID ) + { + case UTILMATLIB_NEEDS_BUMPED_LIGHTMAPS: + return material->GetPropertyFlag( MATERIAL_PROPERTY_NEEDS_BUMPED_LIGHTMAPS ); + + case UTILMATLIB_NEEDS_LIGHTMAP: + return material->GetPropertyFlag( MATERIAL_PROPERTY_NEEDS_LIGHTMAP ); + + default: + Assert( 0 ); + return 0; + } +} + +int GetMaterialShaderPropertyInt( MaterialSystemMaterial_t materialHandle, int propID ) +{ + IMaterial *material = ( IMaterial * )materialHandle; + switch( propID ) + { + case UTILMATLIB_OPACITY: + if (material->IsTranslucent()) + return UTILMATLIB_TRANSLUCENT; + if (material->IsAlphaTested()) + return UTILMATLIB_ALPHATEST; + return UTILMATLIB_OPAQUE; + + default: + Assert( 0 ); + return 0; + } +} + +const char *GetMaterialVar( MaterialSystemMaterial_t materialHandle, const char *propertyName ) +{ + IMaterial *material = ( IMaterial * )materialHandle; + IMaterialVar *var; + bool found; + var = material->FindVar( propertyName, &found, false ); + if( found ) + { + return var->GetStringValue(); + } + else + { + return NULL; + } +} + +const char *GetMaterialShaderName( MaterialSystemMaterial_t materialHandle ) +{ + IMaterial *material = ( IMaterial * )materialHandle; + return material->GetShaderName(); +} diff --git a/mp/src/utils/common/utilmatlib.h b/mp/src/utils/common/utilmatlib.h new file mode 100644 index 00000000..f73a73d0 --- /dev/null +++ b/mp/src/utils/common/utilmatlib.h @@ -0,0 +1,41 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// $NoKeywords: $ +//=============================================================================// + +#ifndef UTILMATLIB_H +#define UTILMATLIB_H + +#ifdef _WIN32 +#pragma once +#endif + +#define MATERIAL_NOT_FOUND NULL + +class IMaterialSystem; +extern IMaterialSystem *g_pMaterialSystem; + +typedef void *MaterialSystemMaterial_t; + +#define UTILMATLIB_NEEDS_BUMPED_LIGHTMAPS 0 +#define UTILMATLIB_NEEDS_LIGHTMAP 1 +#define UTILMATLIB_OPACITY 2 + +enum { UTILMATLIB_ALPHATEST = 0, UTILMATLIB_OPAQUE, UTILMATLIB_TRANSLUCENT }; + +void InitMaterialSystem( const char *materialBaseDirPath, CreateInterfaceFn fileSystemFactory ); +void ShutdownMaterialSystem( ); +MaterialSystemMaterial_t FindMaterial( const char *materialName, bool *pFound, bool bComplain = true ); +void GetMaterialDimensions( MaterialSystemMaterial_t materialHandle, int *width, int *height ); +int GetMaterialShaderPropertyBool( MaterialSystemMaterial_t materialHandle, int propID ); +int GetMaterialShaderPropertyInt( MaterialSystemMaterial_t materialHandle, int propID ); +const char *GetMaterialVar( MaterialSystemMaterial_t materialHandle, const char *propertyName ); +void GetMaterialReflectivity( MaterialSystemMaterial_t materialHandle, float *reflectivityVect ); +const char *GetMaterialShaderName( MaterialSystemMaterial_t materialHandle ); + + +#endif // UTILMATLIB_H diff --git a/mp/src/utils/common/vmpi_tools_shared.cpp b/mp/src/utils/common/vmpi_tools_shared.cpp new file mode 100644 index 00000000..c753ce11 --- /dev/null +++ b/mp/src/utils/common/vmpi_tools_shared.cpp @@ -0,0 +1,374 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include +#include +#include "vmpi.h" +#include "cmdlib.h" +#include "vmpi_tools_shared.h" +#include "tier1/strtools.h" +#include "mpi_stats.h" +#include "iphelpers.h" +#include "tier0/minidump.h" + + +// ----------------------------------------------------------------------------- // +// Globals. +// ----------------------------------------------------------------------------- // + +static bool g_bReceivedDirectoryInfo = false; // Have we gotten the qdir info yet? + +static bool g_bReceivedDBInfo = false; +static CDBInfo g_DBInfo; +static unsigned long g_JobPrimaryID; + +static int g_nDisconnects = 0; // Tracks how many remote processes have disconnected ungracefully. + + +// ----------------------------------------------------------------------------- // +// Shared dispatch code. +// ----------------------------------------------------------------------------- // + +bool SharedDispatch( MessageBuffer *pBuf, int iSource, int iPacketID ) +{ + char *pInPos = &pBuf->data[2]; + + switch ( pBuf->data[1] ) + { + case VMPI_SUBPACKETID_DIRECTORIES: + { + Q_strncpy( gamedir, pInPos, sizeof( gamedir ) ); + pInPos += strlen( pInPos ) + 1; + + Q_strncpy( qdir, pInPos, sizeof( qdir ) ); + + g_bReceivedDirectoryInfo = true; + } + return true; + + case VMPI_SUBPACKETID_DBINFO: + { + g_DBInfo = *((CDBInfo*)pInPos); + pInPos += sizeof( CDBInfo ); + g_JobPrimaryID = *((unsigned long*)pInPos); + + g_bReceivedDBInfo = true; + } + return true; + + case VMPI_SUBPACKETID_CRASH: + { + char const chCrashInfoType = *pInPos; + pInPos += 2; + switch ( chCrashInfoType ) + { + case 't': + Warning( "\nWorker '%s' dead: %s\n", VMPI_GetMachineName( iSource ), pInPos ); + break; + case 'f': + { + int iFileSize = * reinterpret_cast< int const * >( pInPos ); + pInPos += sizeof( iFileSize ); + + // Temp folder + char const *szFolder = NULL; + if ( !szFolder ) szFolder = getenv( "TEMP" ); + if ( !szFolder ) szFolder = getenv( "TMP" ); + if ( !szFolder ) szFolder = "c:"; + + // Base module name + char chModuleName[_MAX_PATH], *pModuleName = chModuleName; + ::GetModuleFileName( NULL, chModuleName, sizeof( chModuleName ) / sizeof( chModuleName[0] ) ); + + if ( char *pch = strrchr( chModuleName, '.' ) ) + *pch = 0; + if ( char *pch = strrchr( chModuleName, '\\' ) ) + *pch = 0, pModuleName = pch + 1; + + // Current time + time_t currTime = ::time( NULL ); + struct tm * pTime = ::localtime( &currTime ); + + // Number of minidumps this run + static int s_numMiniDumps = 0; + ++ s_numMiniDumps; + + // Prepare the filename + char chSaveFileName[ 2 * _MAX_PATH ] = { 0 }; + sprintf( chSaveFileName, "%s\\vmpi_%s_on_%s_%d%.2d%2d%.2d%.2d%.2d_%d.mdmp", + szFolder, + pModuleName, + VMPI_GetMachineName( iSource ), + pTime->tm_year + 1900, /* Year less 2000 */ + pTime->tm_mon + 1, /* month (0 - 11 : 0 = January) */ + pTime->tm_mday, /* day of month (1 - 31) */ + pTime->tm_hour, /* hour (0 - 23) */ + pTime->tm_min, /* minutes (0 - 59) */ + pTime->tm_sec, /* seconds (0 - 59) */ + s_numMiniDumps + ); + + if ( FILE *fDump = fopen( chSaveFileName, "wb" ) ) + { + fwrite( pInPos, 1, iFileSize, fDump ); + fclose( fDump ); + + Warning( "\nSaved worker crash minidump '%s', size %d byte(s).\n", + chSaveFileName, iFileSize ); + } + else + { + Warning( "\nReceived worker crash minidump size %d byte(s), failed to save.\n", iFileSize ); + } + } + break; + } + } + return true; + } + + return false; +} + +CDispatchReg g_SharedDispatchReg( VMPI_SHARED_PACKET_ID, SharedDispatch ); + + + +// ----------------------------------------------------------------------------- // +// Module interfaces. +// ----------------------------------------------------------------------------- // + +void SendQDirInfo() +{ + char cPacketID[2] = { VMPI_SHARED_PACKET_ID, VMPI_SUBPACKETID_DIRECTORIES }; + + MessageBuffer mb; + mb.write( cPacketID, 2 ); + mb.write( gamedir, strlen( gamedir ) + 1 ); + mb.write( qdir, strlen( qdir ) + 1 ); + + VMPI_SendData( mb.data, mb.getLen(), VMPI_PERSISTENT ); +} + + +void RecvQDirInfo() +{ + while ( !g_bReceivedDirectoryInfo ) + VMPI_DispatchNextMessage(); +} + + +void SendDBInfo( const CDBInfo *pInfo, unsigned long jobPrimaryID ) +{ + char cPacketInfo[2] = { VMPI_SHARED_PACKET_ID, VMPI_SUBPACKETID_DBINFO }; + const void *pChunks[] = { cPacketInfo, pInfo, &jobPrimaryID }; + int chunkLengths[] = { 2, sizeof( CDBInfo ), sizeof( jobPrimaryID ) }; + + VMPI_SendChunks( pChunks, chunkLengths, ARRAYSIZE( pChunks ), VMPI_PERSISTENT ); +} + + +void RecvDBInfo( CDBInfo *pInfo, unsigned long *pJobPrimaryID ) +{ + while ( !g_bReceivedDBInfo ) + VMPI_DispatchNextMessage(); + + *pInfo = g_DBInfo; + *pJobPrimaryID = g_JobPrimaryID; +} + +// If the file is successfully opened, read and sent returns the size of the file in bytes +// otherwise returns 0 and nothing is sent +int VMPI_SendFileChunk( const void *pvChunkPrefix, int lenPrefix, tchar const *ptchFileName ) +{ + HANDLE hFile = NULL; + HANDLE hMapping = NULL; + void const *pvMappedData = NULL; + int iResult = 0; + + hFile = ::CreateFile( ptchFileName, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL ); + if ( !hFile || ( hFile == INVALID_HANDLE_VALUE ) ) + goto done; + + hMapping = ::CreateFileMapping( hFile, NULL, PAGE_READONLY, 0, 0, NULL ); + if ( !hMapping || ( hMapping == INVALID_HANDLE_VALUE ) ) + goto done; + + pvMappedData = ::MapViewOfFile( hMapping, FILE_MAP_READ, 0, 0, 0 ); + if ( !pvMappedData ) + goto done; + + int iMappedFileSize = ::GetFileSize( hFile, NULL ); + if ( INVALID_FILE_SIZE == iMappedFileSize ) + goto done; + + // Send the data over VMPI + if ( VMPI_Send3Chunks( + pvChunkPrefix, lenPrefix, + &iMappedFileSize, sizeof( iMappedFileSize ), + pvMappedData, iMappedFileSize, + VMPI_MASTER_ID ) ) + iResult = iMappedFileSize; + + // Fall-through for cleanup code to execute +done: + if ( pvMappedData ) + ::UnmapViewOfFile( pvMappedData ); + + if ( hMapping && ( hMapping != INVALID_HANDLE_VALUE ) ) + ::CloseHandle( hMapping ); + + if ( hFile && ( hFile != INVALID_HANDLE_VALUE ) ) + ::CloseHandle( hFile ); + + return iResult; +} + +void VMPI_HandleCrash( const char *pMessage, void *pvExceptionInfo, bool bAssert ) +{ + static LONG crashHandlerCount = 0; + if ( InterlockedIncrement( &crashHandlerCount ) == 1 ) + { + Msg( "\nFAILURE: '%s' (assert: %d)\n", pMessage, bAssert ); + + // Send a message to the master. + char crashMsg[4] = { VMPI_SHARED_PACKET_ID, VMPI_SUBPACKETID_CRASH, 't', ':' }; + + VMPI_Send2Chunks( + crashMsg, + sizeof( crashMsg ), + pMessage, + strlen( pMessage ) + 1, + VMPI_MASTER_ID ); + + // Now attempt to create a minidump with the given exception information + if ( pvExceptionInfo ) + { + struct _EXCEPTION_POINTERS *pvExPointers = ( struct _EXCEPTION_POINTERS * ) pvExceptionInfo; + tchar tchMinidumpFileName[_MAX_PATH] = { 0 }; + bool bSucceededWritingMinidump = WriteMiniDumpUsingExceptionInfo( + pvExPointers->ExceptionRecord->ExceptionCode, + pvExPointers, + ( MINIDUMP_TYPE )( MiniDumpWithDataSegs | MiniDumpWithIndirectlyReferencedMemory | MiniDumpWithProcessThreadData ), + // ( MINIDUMP_TYPE )( MiniDumpWithDataSegs | MiniDumpWithFullMemory | MiniDumpWithHandleData | MiniDumpWithUnloadedModules | MiniDumpWithIndirectlyReferencedMemory | MiniDumpWithProcessThreadData | MiniDumpWithPrivateReadWriteMemory ), + // ( MINIDUMP_TYPE )( MiniDumpNormal ), + tchMinidumpFileName ); + if ( bSucceededWritingMinidump ) + { + crashMsg[2] = 'f'; + VMPI_SendFileChunk( crashMsg, sizeof( crashMsg ), tchMinidumpFileName ); + ::DeleteFile( tchMinidumpFileName ); + } + } + + // Let the messages go out. + Sleep( 500 ); + } + + InterlockedDecrement( &crashHandlerCount ); +} + + +// This is called if we crash inside our crash handler. It just terminates the process immediately. +LONG __stdcall VMPI_SecondExceptionFilter( struct _EXCEPTION_POINTERS *ExceptionInfo ) +{ + TerminateProcess( GetCurrentProcess(), 2 ); + return EXCEPTION_EXECUTE_HANDLER; // (never gets here anyway) +} + + +void VMPI_ExceptionFilter( unsigned long uCode, void *pvExceptionInfo ) +{ + // This is called if we crash inside our crash handler. It just terminates the process immediately. + SetUnhandledExceptionFilter( VMPI_SecondExceptionFilter ); + + //DWORD code = ExceptionInfo->ExceptionRecord->ExceptionCode; + + #define ERR_RECORD( name ) { name, #name } + struct + { + int code; + char *pReason; + } errors[] = + { + ERR_RECORD( EXCEPTION_ACCESS_VIOLATION ), + ERR_RECORD( EXCEPTION_ARRAY_BOUNDS_EXCEEDED ), + ERR_RECORD( EXCEPTION_BREAKPOINT ), + ERR_RECORD( EXCEPTION_DATATYPE_MISALIGNMENT ), + ERR_RECORD( EXCEPTION_FLT_DENORMAL_OPERAND ), + ERR_RECORD( EXCEPTION_FLT_DIVIDE_BY_ZERO ), + ERR_RECORD( EXCEPTION_FLT_INEXACT_RESULT ), + ERR_RECORD( EXCEPTION_FLT_INVALID_OPERATION ), + ERR_RECORD( EXCEPTION_FLT_OVERFLOW ), + ERR_RECORD( EXCEPTION_FLT_STACK_CHECK ), + ERR_RECORD( EXCEPTION_FLT_UNDERFLOW ), + ERR_RECORD( EXCEPTION_ILLEGAL_INSTRUCTION ), + ERR_RECORD( EXCEPTION_IN_PAGE_ERROR ), + ERR_RECORD( EXCEPTION_INT_DIVIDE_BY_ZERO ), + ERR_RECORD( EXCEPTION_INT_OVERFLOW ), + ERR_RECORD( EXCEPTION_INVALID_DISPOSITION ), + ERR_RECORD( EXCEPTION_NONCONTINUABLE_EXCEPTION ), + ERR_RECORD( EXCEPTION_PRIV_INSTRUCTION ), + ERR_RECORD( EXCEPTION_SINGLE_STEP ), + ERR_RECORD( EXCEPTION_STACK_OVERFLOW ), + ERR_RECORD( EXCEPTION_ACCESS_VIOLATION ), + }; + + int nErrors = sizeof( errors ) / sizeof( errors[0] ); + int i=0; + char *pchReason = NULL; + char chUnknownBuffer[32]; + for ( i; ( i < nErrors ) && !pchReason; i++ ) + { + if ( errors[i].code == uCode ) + pchReason = errors[i].pReason; + } + + if ( i == nErrors ) + { + sprintf( chUnknownBuffer, "Error code 0x%08X", uCode ); + pchReason = chUnknownBuffer; + } + + VMPI_HandleCrash( pchReason, pvExceptionInfo, true ); + + TerminateProcess( GetCurrentProcess(), 1 ); +} + + +void HandleMPIDisconnect( int procID, const char *pReason ) +{ + int nLiveWorkers = VMPI_GetCurrentNumberOfConnections() - g_nDisconnects - 1; + + // We ran into the size limit before and it wasn't readily apparent that the size limit had + // been breached, so make sure to show errors about invalid packet sizes.. + bool bOldSuppress = g_bSuppressPrintfOutput; + g_bSuppressPrintfOutput = ( Q_stristr( pReason, "invalid packet size" ) == 0 ); + + Warning( "\n\n--- WARNING: lost connection to '%s' (%s).\n", VMPI_GetMachineName( procID ), pReason ); + + if ( g_bMPIMaster ) + { + Warning( "%d workers remain.\n\n", nLiveWorkers ); + + ++g_nDisconnects; + /* + if ( VMPI_GetCurrentNumberOfConnections() - g_nDisconnects <= 1 ) + { + Error( "All machines disconnected!" ); + } + */ + } + else + { + VMPI_HandleAutoRestart(); + Error( "Worker quitting." ); + } + + g_bSuppressPrintfOutput = bOldSuppress; +} + + diff --git a/mp/src/utils/common/vmpi_tools_shared.h b/mp/src/utils/common/vmpi_tools_shared.h new file mode 100644 index 00000000..7c22201f --- /dev/null +++ b/mp/src/utils/common/vmpi_tools_shared.h @@ -0,0 +1,45 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef VMPI_TOOLS_SHARED_H +#define VMPI_TOOLS_SHARED_H +#ifdef _WIN32 +#pragma once +#endif + + +// Packet IDs. + #define VMPI_SUBPACKETID_DIRECTORIES 0 // qdir directories. + #define VMPI_SUBPACKETID_DBINFO 1 // MySQL database info. + #define VMPI_SUBPACKETID_CRASH 3 // A worker saying it crashed. + #define VMPI_SUBPACKETID_MULTICAST_ADDR 4 // Filesystem multicast address. + + +class CDBInfo; +class CIPAddr; + + +// Send/receive the qdir info. +void SendQDirInfo(); +void RecvQDirInfo(); + +void SendDBInfo( const CDBInfo *pInfo, unsigned long jobPrimaryID ); +void RecvDBInfo( CDBInfo *pInfo, unsigned long *pJobPrimaryID ); + +void SendMulticastIP( const CIPAddr *pAddr ); +void RecvMulticastIP( CIPAddr *pAddr ); + +void VMPI_HandleCrash( const char *pMessage, void *pvExceptionInfo, bool bAssert ); + +// Call this from an exception handler (set by SetUnhandledExceptionHandler). +// uCode = ExceptionInfo->ExceptionRecord->ExceptionCode. +// pvExceptionInfo = ExceptionInfo +void VMPI_ExceptionFilter( unsigned long uCode, void *pvExceptionInfo ); + +void HandleMPIDisconnect( int procID, const char *pReason ); + + +#endif // VMPI_TOOLS_SHARED_H diff --git a/mp/src/utils/common/wadlib.c b/mp/src/utils/common/wadlib.c new file mode 100644 index 00000000..2b5bb6b1 --- /dev/null +++ b/mp/src/utils/common/wadlib.c @@ -0,0 +1,334 @@ +//========= Copyright © 1996-2005, Valve LLC, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// wad2lib.c + +#include +#include +#include +#include +#include +#include +#include +//#include +#include + +#ifdef NeXT +#include +#endif +#include "cmdlib.h" +#include "wadlib.h" +#include "commonmacros.h" + +/* +============================================================================ + + WAD READING + +============================================================================ +*/ + + +lumpinfo_t *lumpinfo; // location of each lump on disk +int numlumps; + +wadinfo_t header; +FILE *wadhandle; + + +/* +==================== +W_OpenWad +==================== +*/ +void W_OpenWad (char *filename) +{ + lumpinfo_t *lump_p; + unsigned i; + int length; + +// +// open the file and add to directory +// + wadhandle = SafeOpenRead (filename); + SafeRead (wadhandle, &header, sizeof(header)); + + if (!STRING_MATCHES_ID(header.identification,WAD_ID)) + Error ("Wad file %s doesn't have %s identifier\n",filename, WAD_IDNAME); + + header.numlumps = LittleLong(header.numlumps); + header.infotableofs = LittleLong(header.infotableofs); + + numlumps = header.numlumps; + + length = numlumps*sizeof(lumpinfo_t); + lumpinfo = malloc (length); + lump_p = lumpinfo; + + fseek (wadhandle, header.infotableofs, SEEK_SET); + SafeRead (wadhandle, lumpinfo, length); + +// +// Fill in lumpinfo +// + + for (i=0 ; ifilepos = LittleLong(lump_p->filepos); + lump_p->size = LittleLong(lump_p->size); + } +} + + + +void CleanupName (char *in, char *out) +{ + int i; + + for (i=0 ; iname ) ; i++ ) + { + if (!in[i]) + break; + + out[i] = toupper(in[i]); + } + + for ( ; iname ); i++ ) + out[i] = 0; +} + + +/* +==================== +W_CheckNumForName + +Returns -1 if name not found +==================== +*/ +int W_CheckNumForName (char *name) +{ + char cleanname[TEXTURE_NAME_LENGTH]; + int v1,v2, v3, v4; + int i; + lumpinfo_t *lump_p; + + CleanupName (name, cleanname); + +// make the name into four integers for easy compares + + v1 = *(int *)cleanname; + v2 = *(int *)&cleanname[4]; + v3 = *(int *)&cleanname[8]; + v4 = *(int *)&cleanname[12]; + +// find it + + lump_p = lumpinfo; + for (i=0 ; iname == v1 + && *(int *)&lump_p->name[4] == v2 + && *(int *)&lump_p->name[8] == v3 + && *(int *)&lump_p->name[12] == v4 + && !strcmp( lump_p->name, cleanname ) ) + return i; + } + + return -1; +} + + +/* +==================== +W_GetNumForName + +Calls W_CheckNumForName, but bombs out if not found +==================== +*/ +int W_GetNumForName (char *name) +{ + int i; + + i = W_CheckNumForName (name); + if (i != -1) + return i; + + Error ("W_GetNumForName: %s not found!",name); + return -1; +} + + +/* +==================== +W_LumpLength + +Returns the buffer size needed to load the given lump +==================== +*/ +int W_LumpLength (int lump) +{ + if (lump >= numlumps) + Error ("W_LumpLength: %i >= numlumps",lump); + return lumpinfo[lump].size; +} + + +/* +==================== +W_ReadLumpNum + +Loads the lump into the given buffer, which must be >= W_LumpLength() +==================== +*/ +void W_ReadLumpNum (int lump, void *dest) +{ + lumpinfo_t *l; + + if (lump >= numlumps) + Error ("W_ReadLump: %i >= numlumps",lump); + l = lumpinfo+lump; + + fseek (wadhandle, l->filepos, SEEK_SET); + SafeRead (wadhandle, dest, l->size); +} + + + +/* +==================== +W_LoadLumpNum +==================== +*/ +void *W_LoadLumpNum (int lump) +{ + void *buf; + + if ((unsigned)lump >= numlumps) + Error ("W_CacheLumpNum: %i >= numlumps",lump); + + buf = malloc (W_LumpLength (lump)); + W_ReadLumpNum (lump, buf); + + return buf; +} + + +/* +==================== +W_LoadLumpName +==================== +*/ +void *W_LoadLumpName (char *name) +{ + return W_LoadLumpNum (W_GetNumForName(name)); +} + + +/* +=============================================================================== + + WAD CREATION + +=============================================================================== +*/ + +FILE *outwad; + +lumpinfo_t outinfo[4096]; +int outlumps; + +short (*wadshort) (short l); +int (*wadlong) (int l); + +/* +=============== +NewWad +=============== +*/ + +void NewWad (char *pathname, qboolean bigendien) +{ + outwad = SafeOpenWrite (pathname); + fseek (outwad, sizeof(wadinfo_t), SEEK_SET); + memset (outinfo, 0, sizeof(outinfo)); + + if (bigendien) + { + wadshort = BigShort; + wadlong = BigLong; + } + else + { + wadshort = LittleShort; + wadlong = LittleLong; + } + + outlumps = 0; +} + + +/* +=============== +AddLump +=============== +*/ + +void AddLump (char *name, void *buffer, int length, int type, int compress) +{ + lumpinfo_t *info; + int ofs; + + info = &outinfo[outlumps]; + outlumps++; + + memset (info,0,sizeof(info)); + + strcpy (info->name, name); + Q_strupr (info->name); + + ofs = ftell(outwad); + info->filepos = wadlong(ofs); + info->size = info->disksize = wadlong(length); + info->type = type; + info->compression = compress; + +// FIXME: do compression + + SafeWrite (outwad, buffer, length); +} + + +/* +=============== +WriteWad +=============== +*/ + +void WriteWad (int wad3) +{ + wadinfo_t header; + int ofs; + +// write the lumpingo + ofs = ftell(outwad); + + SafeWrite (outwad, outinfo, outlumps*sizeof(lumpinfo_t) ); + +// write the header + +// a program will be able to tell the ednieness of a wad by the id + ID_TO_STRING( WAD_ID, header.identification ); + + header.numlumps = wadlong(outlumps); + header.infotableofs = wadlong(ofs); + + fseek (outwad, 0, SEEK_SET); + SafeWrite (outwad, &header, sizeof(header)); + fclose (outwad); +} + + diff --git a/mp/src/utils/common/wadlib.h b/mp/src/utils/common/wadlib.h new file mode 100644 index 00000000..a8e4e09a --- /dev/null +++ b/mp/src/utils/common/wadlib.h @@ -0,0 +1,46 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +// wadlib.h + +// +// wad reading +// + +#define CMP_NONE 0 +#define CMP_LZSS 1 + +#define TYP_NONE 0 +#define TYP_LABEL 1 +#define TYP_LUMPY 64 // 64 + grab command number + +#ifndef WADTYPES_H +#include "wadtypes.h" +#endif + +extern lumpinfo_t *lumpinfo; // location of each lump on disk +extern int numlumps; +extern wadinfo_t header; + +void W_OpenWad (char *filename); +int W_CheckNumForName (char *name); +int W_GetNumForName (char *name); +int W_LumpLength (int lump); +void W_ReadLumpNum (int lump, void *dest); +void *W_LoadLumpNum (int lump); +void *W_LoadLumpName (char *name); + +void CleanupName (char *in, char *out); + +// +// wad creation +// +void NewWad (char *pathname, qboolean bigendien); +void AddLump (char *name, void *buffer, int length, int type, int compress); +void WriteWad (int wad3); + diff --git a/mp/src/utils/glview/glos.h b/mp/src/utils/glview/glos.h new file mode 100644 index 00000000..bc36bb1a --- /dev/null +++ b/mp/src/utils/glview/glos.h @@ -0,0 +1,21 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// GLOS.H +// +// This is an OS specific header file + +#include + +// disable data conversion warnings + +#pragma warning(disable : 4244) // MIPS +#pragma warning(disable : 4136) // X86 +#pragma warning(disable : 4051) // ALPHA + + + diff --git a/mp/src/utils/glview/glview-2010.vcxproj b/mp/src/utils/glview/glview-2010.vcxproj new file mode 100644 index 00000000..32b29c92 --- /dev/null +++ b/mp/src/utils/glview/glview-2010.vcxproj @@ -0,0 +1,250 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + Glview + {DC76828F-1DD4-7E83-371E-EA4058FEE050} + + + + Application + MultiByte + glview + + + Application + MultiByte + glview + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30319.1 + .\Debug\win32\ + .\Debug\win32\ + ..\..\devtools\vstools;$(ExecutablePath);$(Path) + true + true + true + true + true + .\Release\win32\ + .\Release\win32\ + ..\..\devtools\vstools;$(ExecutablePath);$(Path) + true + true + false + true + true + + + + if EXIST ..\..\..\game\bin\$(TargetFileName) for /f "delims=" %%A in ('attrib "..\..\..\game\bin\$(TargetFileName)"') do set valveTmpIsReadOnly="%%A" set valveTmpIsReadOnlyLetter=%valveTmpIsReadOnly:~6,1% if "%valveTmpIsReadOnlyLetter%"=="R" del /q "$(TargetDir)"$(TargetFileName) if exist ..\..\devtools\bin\vpc.exe ..\..\devtools\bin\vpc.exe -crc2 glview.vcxproj if ERRORLEVEL 1 exit 1 + + + /MP + Disabled + ..\..\common;..\..\public;..\..\public\tier0;..\..\public\tier1;..\common + _HAS_ITERATOR_DEBUGGING=0;WIN32;_WIN32;_DEBUG;DEBUG;_WINDOWS;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;RAD_TELEMETRY_DISABLED;COMPILER_MSVC32;PROTECTED_THINGS_DISABLE;VPCGAMECAPS=VALVE;PROJECTDIR=D:\dev\games\rel\hl2\src\utils\glview;_DLL_EXT=.dll;VPCGAME=valve + true + false + Default + MultiThreadedDebug + true + StreamingSIMDExtensions + Fast + true + true + true + false + NotUsing + false + NoListing + $(IntDir)/ + $(IntDir)/ + $(IntDir)/ + false + Level4 + true + EditAndContinue + CompileAsCpp + $(IntDir)/ + Prompt + + + _DEBUG;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE + 1033 + + + + + /NXCOMPAT /ignore:4221 + %(AdditionalDependencies);glu32.lib;opengl32.lib;odbc32.lib;odbccp32.lib;winmm.lib + NotSet + $(OutDir)\glview.exe + true + libc;libcd;libcmt + true + $(IntDir)/$(TargetName).pdb + false + $(IntDir)/$(TargetName).map + Windows + + MachineX86 + PromptImmediately + false + + + true + + + true + + + true + $(OutDir)/glview.bsc + + + Publishing to ..\..\..\game\bin + if not exist "..\..\..\game\bin" mkdir "..\..\..\game\bin" copy "$(TargetDir)"$(TargetFileName) ..\..\..\game\bin\$(TargetFileName) if ERRORLEVEL 1 goto BuildEventFailed if exist "$(TargetDir)"$(TargetName).map copy "$(TargetDir)"$(TargetName).map ..\..\..\game\bin\$(TargetName).map copy "$(TargetDir)"$(TargetName).pdb ..\..\..\game\bin\$(TargetName).pdb if ERRORLEVEL 1 goto BuildEventFailed goto BuildEventOK :BuildEventFailed echo *** ERROR! PostBuildStep FAILED for $(ProjectName)! EXE or DLL is probably running. *** del /q "$(TargetDir)"$(TargetFileName) exit 1 :BuildEventOK + + + + + + + if EXIST ..\..\..\game\bin\$(TargetFileName) for /f "delims=" %%A in ('attrib "..\..\..\game\bin\$(TargetFileName)"') do set valveTmpIsReadOnly="%%A" set valveTmpIsReadOnlyLetter=%valveTmpIsReadOnly:~6,1% if "%valveTmpIsReadOnlyLetter%"=="R" del /q "$(TargetDir)"$(TargetFileName) if exist ..\..\devtools\bin\vpc.exe ..\..\devtools\bin\vpc.exe -crc2 glview.vcxproj if ERRORLEVEL 1 exit 1 + + + /MP /d2Zi+ + MaxSpeed + AnySuitable + true + Speed + ..\..\common;..\..\public;..\..\public\tier0;..\..\public\tier1;..\common + WIN32;_WIN32;NDEBUG;_WINDOWS;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;RAD_TELEMETRY_DISABLED;COMPILER_MSVC32;PROTECTED_THINGS_DISABLE;VPCGAMECAPS=VALVE;PROJECTDIR=D:\dev\games\rel\hl2\src\utils\glview;_DLL_EXT=.dll;VPCGAME=valve + true + false + MultiThreaded + false + true + StreamingSIMDExtensions + Fast + true + true + true + false + NotUsing + false + NoListing + $(IntDir)/ + $(IntDir)/ + $(IntDir)/ + false + Level4 + true + ProgramDatabase + CompileAsCpp + $(IntDir)/ + Prompt + + + NDEBUG;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE + 1033 + + + + + /DYNAMICBASE /NXCOMPAT /ignore:4221 + %(AdditionalDependencies);glu32.lib;opengl32.lib;odbc32.lib;odbccp32.lib;winmm.lib + NotSet + $(OutDir)\glview.exe + true + libc;libcd;libcmtd + true + $(IntDir)/$(TargetName).pdb + false + $(IntDir)/$(TargetName).map + Windows + true + true + + MachineX86 + PromptImmediately + + + true + + + true + + + true + $(OutDir)/glview.bsc + + + Publishing to ..\..\..\game\bin + if not exist "..\..\..\game\bin" mkdir "..\..\..\game\bin" copy "$(TargetDir)"$(TargetFileName) ..\..\..\game\bin\$(TargetFileName) if ERRORLEVEL 1 goto BuildEventFailed if exist "$(TargetDir)"$(TargetName).map copy "$(TargetDir)"$(TargetName).map ..\..\..\game\bin\$(TargetName).map copy "$(TargetDir)"$(TargetName).pdb ..\..\..\game\bin\$(TargetName).pdb if ERRORLEVEL 1 goto BuildEventFailed goto BuildEventOK :BuildEventFailed echo *** ERROR! PostBuildStep FAILED for $(ProjectName)! EXE or DLL is probably running. *** del /q "$(TargetDir)"$(TargetFileName) exit 1 :BuildEventOK + + + + + + + + + + + + + + + + + + + + NotUsing + NotUsing + + + + + + + + + + + + Compiling pointeroverride.asm + "$(VCInstallDir)bin\ml.exe" /c /Cp /Zi /Fo"$(IntDir)\%(Filename).obj" "%(FullPath)" + $(IntDir)\%(Filename).obj + Compiling pointeroverride.asm + "$(VCInstallDir)bin\ml.exe" /c /Cp /Zi /Fo"$(IntDir)\%(Filename).obj" "%(FullPath)" + $(IntDir)\%(Filename).obj + + + + + + + + diff --git a/mp/src/utils/glview/glview-2010.vcxproj.filters b/mp/src/utils/glview/glview-2010.vcxproj.filters new file mode 100644 index 00000000..4d5ea2a6 --- /dev/null +++ b/mp/src/utils/glview/glview-2010.vcxproj.filters @@ -0,0 +1,77 @@ + + + + + {1680C80B-FF1E-EA4D-9817-CC12254F2E40} + + + {C5D73B3A-C648-896C-B7CE-F174808E5BA5} + + + {BA03E055-4FA2-FCE3-8A1C-D348547D379C} + + + {66CEFED7-D9FA-AC06-065F-BBA238DD0568} + + + + + Link Libraries + + + Link Libraries + + + Link Libraries + + + Link Libraries + + + Link Libraries + + + + + Header Files + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + Source Files\common files + + + Source Files\common files + + + Source Files\common files + + + Source Files\common files + + + Source Files\common files + + + + + + + Source Files + + + + + diff --git a/mp/src/utils/glview/glview.cpp b/mp/src/utils/glview/glview.cpp new file mode 100644 index 00000000..d60a6556 --- /dev/null +++ b/mp/src/utils/glview/glview.cpp @@ -0,0 +1,1427 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +#include "glos.h" +#include +#if _MSC_VER < 1600 +#include +#endif +#include +#include +#include +#include +#include +#include "cmdlib.h" +#include "mathlib/mathlib.h" +#include "cmodel.h" +#include "tier1/strtools.h" +#include "physdll.h" +#include "phyfile.h" +#include "vphysics_interface.h" +#include "tier0/icommandline.h" +#include "tier0/vprof.h" + +HDC camdc; +HGLRC baseRC; +HWND camerawindow; +HANDLE main_instance; + +/* YWB: 3/13/98 + You run the program like normal with any file. If you want to read portals for the + file type, you type: glview -portal filename.gl0 (or whatever). glview will then + try to read in the .prt file filename.prt. + + The portals are shown as white lines superimposed over your image. You can toggle the + view between showing portals or not by hitting the '2' key. The '1' key toggles + world polygons. + + The 'b' key toggles blending modes. + + If you don't want to depth buffer the portals, hit 'p'. + + The command line parsing is inelegant but functional. + + I sped up the KB movement and turn speed, too. + */ + +// Vars added by YWB +Vector g_Center; // Center of all read points, so camera is in a sensible place +int g_nTotalPoints = 0; // Total points read, for calculating center +int g_UseBlending = 0; // Toggle to use blending mode or not +BOOL g_bReadPortals = 0; // Did we read in a portal file? +BOOL g_bNoDepthPortals = 0; // Do we zbuffer the lines of the portals? +int g_nPortalHighlight = -1; // The leaf we're viewing +int g_nLeafHighlight = -1; // The leaf we're viewing +BOOL g_bShowList1 = 1; // Show regular polygons? +BOOL g_bShowList2 = 1; // Show portals? +BOOL g_bShowLines = 0; // Show outlines of faces +BOOL g_Active = TRUE; +BOOL g_Update = TRUE; +BOOL g_bDisp = FALSE; +IPhysicsCollision *physcollision = NULL; +// ----------- +static int g_Keys[255]; +void AppKeyDown( int key ); +void AppKeyUp( int key ); + + +BOOL ReadDisplacementFile( const char *filename ); +void DrawDisplacementData( void ); + +#define BENCHMARK_PHY 0 + +/* +================= +Error + +For abnormal program terminations +================= +*/ +void Error (char *error, ...) +{ + va_list argptr; + char text[1024]; + + va_start (argptr,error); + vsprintf (text, error,argptr); + va_end (argptr); + + MessageBox(NULL, text, "Error", 0 /* MB_OK */ ); + + exit (1); +} + +float origin[3] = {32, 32, 48}; +float angles[3]; +float forward[3], right[3], vup[3], vpn[3], vright[3]; +float width = 1024; +float height = 768; + +float g_flMovementSpeed = 320.f; // Units / second (run speed of HL) +#define SPEED_TURN 90 // Degrees / second + +#define VK_COMMA 188 +#define VK_PERIOD 190 + + +void KeyDown (int key) +{ + switch (key) + { + case VK_ESCAPE: + g_Active = FALSE; + break; + + case VK_F1: + glEnable (GL_CULL_FACE); + glCullFace (GL_FRONT); + break; + case 'B': + g_UseBlending ^= 1; + if (g_UseBlending) + glEnable(GL_BLEND);// YWB TESTING + else + glDisable(GL_BLEND); + break; + + case '1': + g_bShowList1 ^= 1; + break; + case '2': + g_bShowList2 ^= 1; + break; + case 'P': + g_bNoDepthPortals ^= 1; + break; + case 'L': + g_bShowLines ^= 1; + break; + } + g_Update = TRUE; +} + +static BOOL g_Capture = FALSE; + +#define MOUSE_SENSITIVITY 0.2f +#define MOUSE_SENSITIVITY_X (MOUSE_SENSITIVITY*1) +#define MOUSE_SENSITIVITY_Y (MOUSE_SENSITIVITY*1) + +void Cam_MouseMoved( void ) +{ + if ( g_Capture ) + { + RECT rect; + int centerx, centery; + float deltax, deltay; + POINT cursorPoint; + + GetWindowRect( camerawindow, &rect ); + + if ( rect.top < 0) + rect.top = 0; + if ( rect.left < 0) + rect.left = 0; + + centerx = ( rect.left + rect.right ) / 2; + centery = ( rect.top + rect.bottom ) / 2; + + GetCursorPos( &cursorPoint ); + SetCursorPos( centerx, centery ); + + deltax = (cursorPoint.x - centerx) * MOUSE_SENSITIVITY_X; + deltay = (cursorPoint.y - centery) * MOUSE_SENSITIVITY_Y; + + angles[1] -= deltax; + angles[0] -= deltay; + + g_Update = TRUE; + } +} + +int Test_Key( int key ) +{ + int r = (g_Keys[ key ] != 0); + + g_Keys[ key ] &= 0x01; // clear out debounce bit + + if (r) + g_Update = TRUE; + + return r; +} + +// UNDONE: Probably should change the controls to match the game - but I don't know who relies on them +// as of now. +void Cam_Update( float frametime ) +{ + if ( Test_Key( 'W' ) ) + { + VectorMA (origin, g_flMovementSpeed*frametime, vpn, origin); + } + if ( Test_Key( 'S' ) ) + { + VectorMA (origin, -g_flMovementSpeed*frametime, vpn, origin); + } + if ( Test_Key( 'A' ) ) + { + VectorMA (origin, -g_flMovementSpeed*frametime, vright, origin); + } + if ( Test_Key( 'D' ) ) + { + VectorMA (origin, g_flMovementSpeed*frametime, vright, origin); + } + + if ( Test_Key( VK_UP ) ) + { + VectorMA (origin, g_flMovementSpeed*frametime, forward, origin); + } + if ( Test_Key( VK_DOWN ) ) + { + VectorMA (origin, -g_flMovementSpeed*frametime, forward, origin); + } + + if ( Test_Key( VK_LEFT ) ) + { + angles[1] += SPEED_TURN * frametime; + } + if ( Test_Key( VK_RIGHT ) ) + { + angles[1] -= SPEED_TURN * frametime; + } + if ( Test_Key( 'F' ) ) + { + origin[2] += g_flMovementSpeed*frametime; + } + if ( Test_Key( 'C' ) ) + { + origin[2] -= g_flMovementSpeed*frametime; + } + if ( Test_Key( VK_INSERT ) ) + { + angles[0] += SPEED_TURN * frametime; + if (angles[0] > 85) + angles[0] = 85; + } + if ( Test_Key( VK_DELETE ) ) + { + angles[0] -= SPEED_TURN * frametime; + if (angles[0] < -85) + angles[0] = -85; + } + Cam_MouseMoved(); +} + +void Cam_BuildMatrix (void) +{ + float xa, ya; + float matrix[4][4]; + int i; + + xa = angles[0]/180*M_PI; + ya = angles[1]/180*M_PI; + + // the movement matrix is kept 2d ?? do we want this? + + forward[0] = cos(ya); + forward[1] = sin(ya); + right[0] = forward[1]; + right[1] = -forward[0]; + + glGetFloatv (GL_PROJECTION_MATRIX, &matrix[0][0]); + + for (i=0 ; i<3 ; i++) + { + vright[i] = matrix[i][0]; + vup[i] = matrix[i][1]; + vpn[i] = matrix[i][2]; + } + + VectorNormalize (vright); + VectorNormalize (vup); + VectorNormalize (vpn); +} + +void Draw (void) +{ + float screenaspect; + float yfov; + + //glClearColor (0.5, 0.5, 0.5, 0); + glClearColor(0.0, 0.0, 0.0, 0); // Black Clearing YWB + glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + // + // set up viewpoint + // + glMatrixMode(GL_PROJECTION); + glLoadIdentity (); + + screenaspect = (float)width/height; + yfov = 2*atan((float)height/width)*180/M_PI; + gluPerspective (yfov, screenaspect, 6, 20000); + + glRotatef (-90, 1, 0, 0); // put Z going up + glRotatef (90, 0, 0, 1); // put Z going up + glRotatef (angles[0], 0, 1, 0); + glRotatef (-angles[1], 0, 0, 1); + glTranslatef (-origin[0], -origin[1], -origin[2]); + + Cam_BuildMatrix (); + + // + // set drawing parms + // + glShadeModel (GL_SMOOTH); + + glPolygonMode (GL_FRONT_AND_BACK, GL_FILL); + glFrontFace(GL_CW); // YWB Carmack goes backward + glCullFace(GL_BACK); // Cull backfaces (qcsg used to spit out two sides, doesn't for -glview now) + glEnable(GL_CULL_FACE); // Enable face culling, just in case... + glDisable(GL_TEXTURE_2D); + + // Blending function if enabled.. + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + if (g_UseBlending) + { + glEnable(GL_BLEND);// YWB TESTING + glDisable(GL_DEPTH_TEST); + glDisable(GL_CULL_FACE); // Enable face culling, just in case... + } + else + { + glDisable(GL_BLEND); + glEnable(GL_DEPTH_TEST); + } + glDepthFunc (GL_LEQUAL); + + if( g_bDisp ) + { + DrawDisplacementData(); + } + else + { + // + // draw the list + // + if (g_bShowList1) + glCallList (1); + + if (g_bReadPortals) + { + if (g_bNoDepthPortals) + glDisable(GL_DEPTH_TEST); + glDisable(GL_CULL_FACE); // Disable face culling + if (g_bShowList2) + glCallList(2); + }; + + if (g_bShowLines) + glCallList(3); + } +} + +void ReadPolyFileType(const char *name, int nList, BOOL drawLines) +{ + FILE *f; + int i, j, numverts; + float v[8]; + int c; + int r; + float divisor; + + f = fopen (name, "rt"); + if (!f) + Error ("Couldn't open %s", name); + + if (g_bReadPortals) + divisor = 2.0f; + else + divisor = 1.0f; + + c = 0; + glNewList (nList, GL_COMPILE); + + for (i = 0; i < 3; i++) // Find the center point so we can put the viewer there by default + g_Center[i] = 0.0f; + + if (drawLines) // Slight hilite + glLineWidth(1.5); + + while (1) + { + r = fscanf( f, "%i\n", &numverts); + if (!r || r == EOF) + break; + + if ( c > 65534*8) + break; + + if (drawLines || numverts == 2) + glBegin(GL_LINE_LOOP); + else + glBegin (GL_POLYGON); + + for (i=0 ; i 0) // Avoid division by zero + { + for (i = 0; i < 3; i++) + { + g_Center[i] = g_Center[i]/(float)g_nTotalPoints; // Calculate center... + origin[i] = g_Center[i]; + } + } +} + +#if BENCHMARK_PHY +#define NUM_COLLISION_TESTS 2500 +#include "gametrace.h" +#include "fmtstr.h" + + +struct testlist_t +{ + Vector start; + Vector end; + Vector normal; + bool hit; +}; + +const float baselineTotal = 120.16f; +const float baselineRay = 28.25f; +const float baselineBox = 91.91f; +#define IMPROVEMENT_FACTOR(x,baseline) (baseline/(x)) +#define IMPROVEMENT_PERCENT(x,baseline) (((baseline-(x)) / baseline) * 100.0f) + +testlist_t g_Traces[NUM_COLLISION_TESTS]; +void Benchmark_PHY( const CPhysCollide *pCollide ) +{ + int i; + Msg( "Testing collision system\n" ); + Vector start = vec3_origin; + static Vector *targets = NULL; + static bool first = true; + static float test[2] = {1,1}; + if ( first ) + { + float radius = 0; + float theta = 0; + float phi = 0; + for ( int i = 0; i < NUM_COLLISION_TESTS; i++ ) + { + radius += NUM_COLLISION_TESTS * 123.123f; + radius = fabs(fmod(radius, 128)); + theta += NUM_COLLISION_TESTS * 0.76f; + theta = fabs(fmod(theta, DEG2RAD(360))); + phi += NUM_COLLISION_TESTS * 0.16666666f; + phi = fabs(fmod(phi, DEG2RAD(180))); + + float st, ct, sp, cp; + SinCos( theta, &st, &ct ); + SinCos( phi, &sp, &cp ); + st = sin(theta); + ct = cos(theta); + sp = sin(phi); + cp = cos(phi); + + g_Traces[i].start.x = radius * ct * sp; + g_Traces[i].start.y = radius * st * sp; + g_Traces[i].start.z = radius * cp; + } + first = false; + } + + float duration = 0; + Vector size[2]; + size[0].Init(0,0,0); + size[1].Init(16,16,16); + unsigned int dots = 0; + +#if VPROF_LEVEL > 0 + g_VProfCurrentProfile.Reset(); + g_VProfCurrentProfile.ResetPeaks(); + g_VProfCurrentProfile.Start(); +#endif + unsigned int hitCount = 0; + double startTime = Plat_FloatTime(); + trace_t tr; + for ( i = 0; i < NUM_COLLISION_TESTS; i++ ) + { + physcollision->TraceBox( g_Traces[i].start, start, -size[0], size[0], pCollide, vec3_origin, vec3_angle, &tr ); + if ( tr.DidHit() ) + { + g_Traces[i].end = tr.endpos; + g_Traces[i].normal = tr.plane.normal; + g_Traces[i].hit = true; + hitCount++; + } + else + { + g_Traces[i].hit = false; + } + } + for ( i = 0; i < NUM_COLLISION_TESTS; i++ ) + { + physcollision->TraceBox( g_Traces[i].start, start, -size[1], size[1], pCollide, vec3_origin, vec3_angle, &tr ); + } + duration = Plat_FloatTime() - startTime; + { + unsigned int msSupp = physcollision->ReadStat( 100 ); + unsigned int msGJK = physcollision->ReadStat( 101 ); + unsigned int msMesh = physcollision->ReadStat( 102 ); + CFmtStr str("%d ms total %d ms gjk %d mesh solve\n", msSupp, msGJK, msMesh ); + OutputDebugStr( str.Access() ); + } + +#if VPROF_LEVEL > 0 + g_VProfCurrentProfile.MarkFrame(); + g_VProfCurrentProfile.Stop(); + g_VProfCurrentProfile.Reset(); + g_VProfCurrentProfile.ResetPeaks(); + g_VProfCurrentProfile.Start(); +#endif + hitCount = 0; + startTime = Plat_FloatTime(); + for ( i = 0; i < NUM_COLLISION_TESTS; i++ ) + { + physcollision->TraceBox( g_Traces[i].start, start, -size[0], size[0], pCollide, vec3_origin, vec3_angle, &tr ); + if ( tr.DidHit() ) + { + g_Traces[i].end = tr.endpos; + g_Traces[i].normal = tr.plane.normal; + g_Traces[i].hit = true; + hitCount++; + } + else + { + g_Traces[i].hit = false; + } +#if VPROF_LEVEL > 0 + g_VProfCurrentProfile.MarkFrame(); +#endif + } + double midTime = Plat_FloatTime(); + for ( i = 0; i < NUM_COLLISION_TESTS; i++ ) + { + physcollision->TraceBox( g_Traces[i].start, start, -size[1], size[1], pCollide, vec3_origin, vec3_angle, &tr ); +#if VPROF_LEVEL > 0 + g_VProfCurrentProfile.MarkFrame(); +#endif + } + double endTime = Plat_FloatTime(); + duration = endTime - startTime; + { + CFmtStr str("%d collisions in %.2f ms [%.2f X] %d hits\n", NUM_COLLISION_TESTS, duration*1000, IMPROVEMENT_FACTOR(duration*1000.0f, baselineTotal), hitCount ); + OutputDebugStr( str.Access() ); + } + { + float rayTime = (midTime - startTime) * 1000.0f; + float boxTime = (endTime - midTime)*1000.0f; + CFmtStr str("%.2f ms rays [%.2f X] %.2f ms boxes [%.2f X]\n", rayTime, IMPROVEMENT_FACTOR(rayTime, baselineRay), boxTime, IMPROVEMENT_FACTOR(boxTime, baselineBox)); + OutputDebugStr( str.Access() ); + } + + { + unsigned int msSupp = physcollision->ReadStat( 100 ); + unsigned int msGJK = physcollision->ReadStat( 101 ); + unsigned int msMesh = physcollision->ReadStat( 102 ); + CFmtStr str("%d ms total %d ms gjk %d mesh solve\n", msSupp, msGJK, msMesh ); + OutputDebugStr( str.Access() ); + } +#if VPROF_LEVEL > 0 + g_VProfCurrentProfile.Stop(); + g_VProfCurrentProfile.OutputReport( VPRT_FULL & ~VPRT_HIERARCHY, NULL ); +#endif + + // draw the traces in yellow + glColor3f( 1.0f, 1.0f, 0.0f ); + glBegin( GL_LINES ); + for ( int i = 0; i < NUM_COLLISION_TESTS; i++ ) + { + if ( !g_Traces[i].hit ) + continue; + glVertex3fv( g_Traces[i].end.Base() ); + Vector tmp = g_Traces[i].end + g_Traces[i].normal * 10.0f; + glVertex3fv( tmp.Base() ); + } + glEnd(); +} +#endif + +struct phyviewparams_t +{ + Vector mins; + Vector maxs; + Vector offset; + QAngle angles; + int outputType; + + void Defaults() + { + ClearBounds(mins, maxs); + offset.Init(); + outputType = GL_POLYGON; + angles.Init(); + } +}; + + +void AddVCollideToList( phyheader_t &header, vcollide_t &collide, phyviewparams_t ¶ms ) +{ + matrix3x4_t xform; + AngleMatrix( params.angles, params.offset, xform ); + ClearBounds( params.mins, params.maxs ); + for ( int i = 0; i < header.solidCount; i++ ) + { + ICollisionQuery *pQuery = physcollision->CreateQueryModel( collide.solids[i] ); + for ( int j = 0; j < pQuery->ConvexCount(); j++ ) + { + for ( int k = 0; k < pQuery->TriangleCount(j); k++ ) + { + Vector verts[3]; + pQuery->GetTriangleVerts( j, k, verts ); + Vector v0,v1,v2; + VectorTransform( verts[0], xform, v0 ); + VectorTransform( verts[1], xform, v1 ); + VectorTransform( verts[2], xform, v2 ); + AddPointToBounds( v0, params.mins, params.maxs ); + AddPointToBounds( v1, params.mins, params.maxs ); + AddPointToBounds( v2, params.mins, params.maxs ); + + glBegin(params.outputType); + glColor3ub( 255, 0, 0 ); + glVertex3fv( v0.Base() ); + glColor3ub( 0, 255, 0 ); + glVertex3fv( v1.Base() ); + glColor3ub( 0, 0, 255 ); + glVertex3fv( v2.Base() ); + glEnd(); + } + } + physcollision->DestroyQueryModel( pQuery ); + } +} + +void GL_DrawLine( const Vector &start, const Vector &dir, float length, int r, int g, int b ) +{ + Vector end = start + (dir*length); + glBegin( GL_LINES ); + glColor3ub(r,g,b); + glVertex3fv( start.Base() ); + glVertex3fv( end.Base() ); + glEnd(); +} + +void GL_DrawBox( Vector origin, float size, int r, int g, int b ) +{ + Vector mins = origin - Vector(size,size,size); + Vector maxs = origin + Vector(size,size,size); + const float *v[2] = {mins.Base(), maxs.Base()}; + + Vector start, end; + { + for ( int i = 0; i < 3; i++ ) + { + int a0 = i; + int a1 = (i+1)%3; + int a2 = (i+2)%3; + for ( int j = 0; j < 2; j++ ) + { + for ( int k = 0; k < 2; k++ ) + { + start[a0] = v[0][a0]; + end[a0] = v[1][a0]; + start[a1] = v[j][a1]; + end[a1] = v[j][a1]; + start[a2] = v[k][a2]; + end[a2] = v[k][a2]; + GL_DrawLine( start, end-start, 1, r, g, b ); + } + } + } + } + for ( int axis = 0; axis < 3; axis++ ) + { + int a0 = axis; + int a1 = (axis+1)%3; + int a2 = (axis+2)%3; + start[a0] = v[0][a0]; + end[a0] = v[1][a0]; + start[a1] = 0.5f *(v[0][a1]+v[1][a1]); + end[a1] = 0.5f *(v[0][a1]+v[1][a1]); + start[a2] = 0.5f *(v[0][a2]+v[1][a2]); + end[a2] = 0.5f *(v[0][a2]+v[1][a2]); + GL_DrawLine( start, end-start, 1, r, g, b ); + } +} + + +void ReadPHYFile(const char *name, phyviewparams_t ¶ms ) +{ + FILE *fp = fopen (name, "rb"); + if (!fp) + Error ("Couldn't open %s", name); + + phyheader_t header; + + fread( &header, sizeof(header), 1, fp ); + if ( header.size != sizeof(header) || header.solidCount <= 0 ) + return; + + int pos = ftell( fp ); + fseek( fp, 0, SEEK_END ); + int fileSize = ftell(fp) - pos; + fseek( fp, pos, SEEK_SET ); + + char *buf = (char *)_alloca( fileSize ); + fread( buf, fileSize, 1, fp ); + fclose( fp ); + + vcollide_t collide; + physcollision->VCollideLoad( &collide, header.solidCount, (const char *)buf, fileSize ); +#if 0 + Vector start0( -3859.1199, -2050.8674, 64.031250 ); + Vector end0(-3859.2246, -2051.2817, 64.031250 ); + Vector modelPosition(-3840,-2068.0000, 82.889099); + QAngle modelAngles(0,90,0); + + { + Ray_t ray; + ray.Init( start0, end0, Vector(-16,-16,0), Vector(16,16,72)); + trace_t tr; + physcollision->TraceBox( ray, collide.solids[0], modelPosition, modelAngles, &tr ); + Assert(!tr.startsolid); + if ( tr.DidHit() ) + { + Ray_t ray2; + ray2.Init( tr.endpos, tr.endpos, Vector(-16,-16,0), Vector(16,16,72)); + trace_t tr2; + physcollision->TraceBox( ray2, collide.solids[0], modelPosition, modelAngles, &tr2 ); + Assert(!tr2.startsolid); + } + } +#endif +#if BENCHMARK_PHY + Benchmark_PHY( collide.solids[0] ); +#endif + AddVCollideToList( header, collide, params ); +} + +void ReadPolyFile (const char *name) +{ + char ext[4]; + Q_ExtractFileExtension( name, ext, 4 ); + + bool isPHY = !Q_stricmp( ext, "phy" ); + if ( isPHY ) + { + CreateInterfaceFn physicsFactory = GetPhysicsFactory(); + physcollision = (IPhysicsCollision *)physicsFactory( VPHYSICS_COLLISION_INTERFACE_VERSION, NULL ); + if ( physcollision ) + { + phyviewparams_t params; + params.Defaults(); + glNewList (1, GL_COMPILE); + ReadPHYFile( name, params ); + Vector tmp = (params.mins + params.maxs) * 0.5; + tmp.CopyToArray(origin); + glEndList (); + } + } + else + { + // Read in polys... + ReadPolyFileType(name, 1, false); + + // Make list 3 just the lines... so we can draw outlines + ReadPolyFileType(name, 3, true); + } +} + +void ReadPortalFile (char *name) +{ + FILE *f; + int i, numverts; + float v[8]; + int c; + int r; + + // For Portal type reading... + char szDummy[80]; + int nNumLeafs; + int nNumPortals; + int nLeafIndex[2]; + + f = fopen (name, "r"); + if (!f) + Error ("Couldn't open %s", name); + + c = 0; + + glNewList (2, GL_COMPILE); + + // Read in header + fscanf(f, "%79s\n", szDummy); + fscanf(f, "%i\n", &nNumLeafs); + fscanf(f, "%i\n", &nNumPortals); + + glLineWidth(1.5); + + while (1) + { + r = fscanf(f, "%i %i %i ", &numverts, &nLeafIndex[0], &nLeafIndex[1]); + if (!r || r == EOF) + break; + + glBegin(GL_LINE_LOOP); + for (i=0 ; i= MAX_DISP_COUNT ) + break; + + fileCount = fscanf( pFile, "%f %f %f %f %f %f", + &dispPoints[dispPointCount][0], &dispPoints[dispPointCount][1], &dispPoints[dispPointCount][2], + &dispNormals[dispPointCount][0], &dispNormals[dispPointCount][1], &dispNormals[dispPointCount][2] ); + dispPointCount++; + + // end of file check + if( !fileCount || ( fileCount == EOF ) ) + break; + } + + fclose( pFile ); + + return TRUE; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void DrawDisplacementData( void ) +{ + int i, j; + int width, halfCount; + + GLUquadricObj *pObject = gluNewQuadric(); + + glEnable( GL_DEPTH_TEST ); + + for( i = 0; i < dispPointCount; i++ ) + { + // draw a sphere where the point is (in red) + glColor3f( 1.0f, 0.0f, 0.0f ); + glPushMatrix(); + glTranslatef( dispPoints[i][0], dispPoints[i][1], dispPoints[i][2] ); + gluSphere( pObject, 5, 5, 5 ); + glPopMatrix(); + + // draw the normal (in yellow) + glColor3f( 1.0f, 1.0f, 0.0f ); + glBegin( GL_LINES ); + glVertex3f( dispPoints[i][0], dispPoints[i][1], dispPoints[i][2] ); + glVertex3f( dispPoints[i][0] + ( dispNormals[i][0] * 50.0f ), dispPoints[i][1] + ( dispNormals[i][1] * 50.0f ), dispPoints[i][2] + ( dispNormals[i][2] * 50.0f ) ); + glEnd(); + } + + halfCount = dispPointCount / 2; + + width = sqrt( (float)halfCount ); + + glDisable( GL_CULL_FACE ); + + glColor3f( 0.0f, 0.0f, 1.0f ); + for( i = 0; i < width - 1; i++ ) + { + for( j = 0; j < width - 1; j++ ) + { + glBegin( GL_POLYGON ); + glVertex3f( dispPoints[i*width+j][0], dispPoints[i*width+j][1], dispPoints[i*width+j][2] ); + glVertex3f( dispPoints[(i+1)*width+j][0], dispPoints[(i+1)*width+j][1], dispPoints[(i+1)*width+j][2] ); + glVertex3f( dispPoints[(i+1)*width+(j+1)][0], dispPoints[(i+1)*width+(j+1)][1], dispPoints[(i+1)*width+(j+1)][2] ); + glVertex3f( dispPoints[i*width+(j+1)][0], dispPoints[i*width+(j+1)][1], dispPoints[i*width+(j+1)][2] ); + glEnd(); + } + } + +#if 0 + for( i = 0; i < width - 1; i++ ) + { + for( j = 0; j < width - 1; j++ ) + { + glBegin( GL_POLYGON ); + glVertex3f( dispPoints[halfCount+(i*width+j)][0], dispPoints[halfCount+(i*width+j)][1], dispPoints[halfCount+(i*width+j)][2] ); + glVertex3f( dispPoints[halfCount+((i+1)*width+j)][0], dispPoints[halfCount+(i+1)*width+j][1], dispPoints[halfCount+((i+1)*width+j)][2] ); + glVertex3f( dispPoints[halfCount+((i+1)*width+(j+1))][0], dispPoints[halfCount+(i+1)*width+(j+1)][1], dispPoints[halfCount+((i+1)*width+(j+1))][2] ); + glVertex3f( dispPoints[halfCount+(i*width+(j+1))][0], dispPoints[halfCount+(i*width+(j+1))][1], dispPoints[halfCount+(i*width+(j+1))][2] ); + glEnd(); + } + } +#endif + + glColor3f( 0.0f, 1.0f, 0.0f ); + for( i = 0; i < width - 1; i++ ) + { + for( j = 0; j < width - 1; j++ ) + { + glBegin( GL_POLYGON ); + glVertex3f( dispPoints[i*width+j][0] + ( dispNormals[i*width+j][0] * 150.0f ), + dispPoints[i*width+j][1] + ( dispNormals[i*width+j][1] * 150.0f ), + dispPoints[i*width+j][2] + ( dispNormals[i*width+j][2] * 150.0f ) ); + + glVertex3f( dispPoints[(i+1)*width+j][0] + ( dispNormals[(i+1)*width+j][0] * 150.0f ), + dispPoints[(i+1)*width+j][1] + ( dispNormals[(i+1)*width+j][1] * 150.0f ), + dispPoints[(i+1)*width+j][2] + ( dispNormals[(i+1)*width+j][2] * 150.0f ) ); + + glVertex3f( dispPoints[(i+1)*width+(j+1)][0] + ( dispNormals[(i+1)*width+(j+1)][0] * 150.0f ), + dispPoints[(i+1)*width+(j+1)][1] + ( dispNormals[(i+1)*width+(j+1)][1] * 150.0f ), + dispPoints[(i+1)*width+(j+1)][2] + ( dispNormals[(i+1)*width+(j+1)][2] * 150.0f ) ); + + glVertex3f( dispPoints[i*width+(j+1)][0] + ( dispNormals[i*width+(j+1)][0] * 150.0f ), + dispPoints[i*width+(j+1)][1] + ( dispNormals[i*width+(j+1)][1] * 150.0f ), + dispPoints[i*width+(j+1)][2] + ( dispNormals[i*width+(j+1)][2] * 150.0f ) ); + glEnd(); + } + } + + glDisable( GL_DEPTH_TEST ); + + glColor3f( 0.0f, 0.0f, 1.0f ); + for( i = 0; i < width - 1; i++ ) + { + for( j = 0; j < width - 1; j++ ) + { + glBegin( GL_LINE_LOOP ); + glVertex3f( dispPoints[i*width+j][0] + ( dispNormals[i*width+j][0] * 150.0f ), + dispPoints[i*width+j][1] + ( dispNormals[i*width+j][1] * 150.0f ), + dispPoints[i*width+j][2] + ( dispNormals[i*width+j][2] * 150.0f ) ); + + glVertex3f( dispPoints[(i+1)*width+j][0] + ( dispNormals[(i+1)*width+j][0] * 150.0f ), + dispPoints[(i+1)*width+j][1] + ( dispNormals[(i+1)*width+j][1] * 150.0f ), + dispPoints[(i+1)*width+j][2] + ( dispNormals[(i+1)*width+j][2] * 150.0f ) ); + + glVertex3f( dispPoints[(i+1)*width+(j+1)][0] + ( dispNormals[(i+1)*width+(j+1)][0] * 150.0f ), + dispPoints[(i+1)*width+(j+1)][1] + ( dispNormals[(i+1)*width+(j+1)][1] * 150.0f ), + dispPoints[(i+1)*width+(j+1)][2] + ( dispNormals[(i+1)*width+(j+1)][2] * 150.0f ) ); + + glVertex3f( dispPoints[i*width+(j+1)][0] + ( dispNormals[i*width+(j+1)][0] * 150.0f ), + dispPoints[i*width+(j+1)][1] + ( dispNormals[i*width+(j+1)][1] * 150.0f ), + dispPoints[i*width+(j+1)][2] + ( dispNormals[i*width+(j+1)][2] * 150.0f ) ); + glEnd(); + } + } + + + gluDeleteQuadric( pObject ); +} + + +//===================================================================== + +BOOL bSetupPixelFormat(HDC hDC) +{ + static PIXELFORMATDESCRIPTOR pfd = { + sizeof(PIXELFORMATDESCRIPTOR), // size of this pfd + 1, // version number + PFD_DRAW_TO_WINDOW | // support window + PFD_SUPPORT_OPENGL | // support OpenGL + PFD_DOUBLEBUFFER, // double buffered + PFD_TYPE_RGBA, // RGBA type + 24, // 24-bit color depth + 0, 0, 0, 0, 0, 0, // color bits ignored + 0, // no alpha buffer + 0, // shift bit ignored + 0, // no accumulation buffer + 0, 0, 0, 0, // accum bits ignored + 32, // 32-bit z-buffer + 0, // no stencil buffer + 0, // no auxiliary buffer + PFD_MAIN_PLANE, // main layer + 0, // reserved + 0, 0, 0 // layer masks ignored + }; + + int pixelformat = 0; + + if ( (pixelformat = ChoosePixelFormat(hDC, &pfd)) == 0 ) + Error ("ChoosePixelFormat failed"); + + if (!SetPixelFormat(hDC, pixelformat, &pfd)) + Error ("SetPixelFormat failed"); + + return TRUE; +} + +/* +============ +CameraWndProc +============ +*/ +LONG WINAPI WCam_WndProc ( + HWND hWnd, + UINT uMsg, + WPARAM wParam, + LPARAM lParam) +{ + LONG lRet = 1; + RECT rect; + + GetClientRect(hWnd, &rect); + + switch (uMsg) + { + case WM_CREATE: + { + camdc = GetDC(hWnd); + bSetupPixelFormat(camdc); + + baseRC = wglCreateContext( camdc ); + if (!baseRC) + Error ("wglCreateContext failed"); + if (!wglMakeCurrent( camdc, baseRC )) + Error ("wglMakeCurrent failed"); + glCullFace(GL_FRONT); + glEnable(GL_CULL_FACE); + } + break; + case WM_PAINT: + { + PAINTSTRUCT ps; + + BeginPaint(hWnd, &ps); + if (!wglMakeCurrent( camdc, baseRC )) + Error ("wglMakeCurrent failed"); + Draw (); + SwapBuffers(camdc); + EndPaint(hWnd, &ps); + } + break; + + case WM_KEYDOWN: + KeyDown (wParam); + AppKeyDown( wParam ); + break; + + case WM_KEYUP: + AppKeyUp( wParam ); + break; + + case WM_MBUTTONDOWN: + case WM_RBUTTONDOWN: + case WM_LBUTTONDOWN: + SetCapture (camerawindow); + ShowCursor( FALSE ); + g_Capture = TRUE; + break; + + case WM_MBUTTONUP: + case WM_RBUTTONUP: + case WM_LBUTTONUP: + if (! (wParam & (MK_LBUTTON|MK_RBUTTON|MK_MBUTTON))) + { + g_Capture = FALSE; + ReleaseCapture (); + ShowCursor( TRUE ); + } + break; + + case WM_SIZE: + InvalidateRect(camerawindow, NULL, false); + break; + case WM_NCCALCSIZE:// don't let windows copy pixels + lRet = DefWindowProc (hWnd, uMsg, wParam, lParam); + return WVR_REDRAW; + case WM_CLOSE: + /* call destroy window to cleanup and go away */ + DestroyWindow (hWnd); + break; + + case WM_DESTROY: + { + HGLRC hRC; + HDC hDC; + + /* release and free the device context and rendering context */ + hRC = wglGetCurrentContext(); + hDC = wglGetCurrentDC(); + + wglMakeCurrent(NULL, NULL); + + if (hRC) + wglDeleteContext(hRC); + if (hDC) + ReleaseDC(hWnd, hDC); + + PostQuitMessage (0); + } + break; + + default: + /* pass all unhandled messages to DefWindowProc */ + lRet = DefWindowProc (hWnd, uMsg, wParam, lParam); + break; + } + + /* return 1 if handled message, 0 if not */ + return lRet; +} + + +/* +============== +WCam_Register +============== +*/ +void WCam_Register (HINSTANCE hInstance) +{ + WNDCLASS wc; + + /* Register the camera class */ + memset (&wc, 0, sizeof(wc)); + + wc.style = 0; + wc.lpfnWndProc = (WNDPROC)WCam_WndProc; + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + wc.hInstance = hInstance; + wc.hIcon = 0; + wc.hCursor = LoadCursor (NULL,IDC_ARROW); + wc.hbrBackground = NULL; + wc.lpszMenuName = 0; + wc.lpszClassName = "camera"; + + if (!RegisterClass (&wc) ) + Error ("WCam_Register: failed"); +} + + +void WCam_Create (HINSTANCE hInstance) +{ + // Center it + int nScx, nScy; + int w, h; + int x, y; + + WCam_Register (hInstance); + + w = ::width; + h = ::height; + + nScx = GetSystemMetrics(SM_CXSCREEN); + nScy = GetSystemMetrics(SM_CYSCREEN); + + + x = (nScx - w)/2; + y = (nScy - h)/2; + + camerawindow = CreateWindow ("camera" , + "Camera View", + WS_OVERLAPPED | + WS_CAPTION | + WS_SYSMENU | + WS_THICKFRAME | + WS_MAXIMIZEBOX | + WS_CLIPSIBLINGS | + WS_CLIPCHILDREN, + + x, + y, + w, + h, // size + + NULL, // parent window + 0, // no menu + hInstance, + 0); + if (!camerawindow) + Error ("Couldn't create camerawindow"); + + ShowWindow (camerawindow, SW_SHOWDEFAULT); +} + + +void AppKeyDown( int key ) +{ + key &= 0xFF; + + g_Keys[key] = 0x03; // add debounce bit +} + +void AppKeyUp( int key ) +{ + key &= 0xFF; + + g_Keys[key] &= 0x02; +} + +void AppRender( void ) +{ + static double lastTime = 0; + double time = timeGetTime() * 0.001f; + double frametime = time - lastTime; + + // clamp too large frames (like first frame) + if ( frametime > 0.2 ) + frametime = 0.2; + lastTime = time; + + if (!wglMakeCurrent( camdc, baseRC )) + Error ("wglMakeCurrent failed"); + + Cam_Update( frametime ); + + if (g_Update) + { + Draw (); + SwapBuffers(camdc); + g_Update = FALSE; + } + else + { + Sleep( 1.0 ); + } +} + +SpewRetval_t Sys_SpewFunc( SpewType_t type, const char *pMsg ) +{ + OutputDebugString( pMsg ); + if( type == SPEW_ASSERT ) + return SPEW_DEBUGGER; + else if( type == SPEW_ERROR ) + return SPEW_ABORT; + else + return SPEW_CONTINUE; +} + + +/* +================== +WinMain + +================== +*/ +int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance + ,LPSTR lpCmdLine, int nCmdShow) +{ + CommandLine()->CreateCmdLine( Plat_GetCommandLine() ); + + MathLib_Init( 2.2f, 2.2f, 0.0f, 2.0f ); + MSG msg; + + if (!lpCmdLine || !lpCmdLine[0]) + Error ("No file specified"); + + main_instance = hInstance; + + WCam_Create (hInstance); + + // Last argument is the file name + const char *pFileName = CommandLine()->GetParm( CommandLine()->ParmCount() - 1 ); + CmdLib_InitFileSystem( pFileName ); + + if ( CommandLine()->CheckParm( "-portal") ) + { + g_bReadPortals = 1; + g_nPortalHighlight = CommandLine()->ParmValue( "-portalhighlight", -1 ); + g_nLeafHighlight = CommandLine()->ParmValue( "-leafhighlight", -1 ); + } + g_flMovementSpeed = CommandLine()->ParmValue( "-speed", 320 ); + + if( CommandLine()->CheckParm( "-disp") ) + { + ReadDisplacementFile( pFileName ); + g_bDisp = TRUE; + } + SpewOutputFunc( Sys_SpewFunc ); + + // Any chunk of original left is the filename. + if (pFileName && pFileName[0] && !g_bDisp ) + { + ReadPolyFile( pFileName ); + } + + if (g_bReadPortals) + { + // Copy file again and this time look for the . from .gl? so we can concatenate .prt + // and open the portal file. + char szTempCmd[MAX_PATH]; + strcpy(szTempCmd, pFileName); + char *pTmp = szTempCmd; + while (pTmp && *pTmp && *pTmp != '.') + { + pTmp++; + } + + *pTmp = '\0'; + strcat(szTempCmd, ".prt"); + + ReadPortalFile(szTempCmd); + }; + + /* main window message loop */ + while (g_Active) + { + while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) + { + TranslateMessage (&msg); + DispatchMessage (&msg); + } + AppRender(); + } + + /* return success of application */ + return TRUE; +} + diff --git a/mp/src/utils/height2normal/height2normal-2010.vcxproj b/mp/src/utils/height2normal/height2normal-2010.vcxproj new file mode 100644 index 00000000..40f3288c --- /dev/null +++ b/mp/src/utils/height2normal/height2normal-2010.vcxproj @@ -0,0 +1,241 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + Height2normal + {129A563E-9F48-79D9-E0C5-EE2DAF7FEAB7} + + + + Application + MultiByte + height2normal + + + Application + MultiByte + height2normal + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30319.1 + .\Debug\win32\ + .\Debug\win32\ + ..\..\devtools\vstools;$(ExecutablePath);$(Path) + true + true + true + true + true + .\Release\win32\ + .\Release\win32\ + ..\..\devtools\vstools;$(ExecutablePath);$(Path) + true + true + false + true + true + + + + if EXIST ..\..\..\game\bin\$(TargetFileName) for /f "delims=" %%A in ('attrib "..\..\..\game\bin\$(TargetFileName)"') do set valveTmpIsReadOnly="%%A" set valveTmpIsReadOnlyLetter=%valveTmpIsReadOnly:~6,1% if "%valveTmpIsReadOnlyLetter%"=="R" del /q "$(TargetDir)"$(TargetFileName) if exist ..\..\devtools\bin\vpc.exe ..\..\devtools\bin\vpc.exe -crc2 height2normal.vcxproj if ERRORLEVEL 1 exit 1 + + + /MP + Disabled + ..\..\common;..\..\public;..\..\public\tier0;..\..\public\tier1;..\common + _HAS_ITERATOR_DEBUGGING=0;_DEBUG;_WIN32;_CONSOLE;VPCGAMECAPS=VALVE;PROJECTDIR=D:\dev\games\rel\hl2\src\utils\height2normal;_DLL_EXT=.dll;VPCGAME=valve + true + false + Default + MultiThreadedDebug + true + StreamingSIMDExtensions + Fast + true + true + true + false + NotUsing + false + NoListing + $(IntDir)/ + $(IntDir)/ + $(IntDir)/ + false + Level4 + true + EditAndContinue + CompileAsCpp + $(IntDir)/ + Prompt + + + _DEBUG;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE + 1033 + + + + + /NXCOMPAT /ignore:4221 + %(AdditionalDependencies) + NotSet + $(OutDir)\height2normal.exe + true + libc;libcd;libcmt + true + $(IntDir)/$(TargetName).pdb + false + $(IntDir)/$(TargetName).map + Console + + MachineX86 + PromptImmediately + false + + + true + + + true + + + true + $(OutDir)/height2normal.bsc + + + Publishing to ..\..\..\game\bin + if not exist "..\..\..\game\bin" mkdir "..\..\..\game\bin" copy "$(TargetDir)"$(TargetFileName) ..\..\..\game\bin\$(TargetFileName) if ERRORLEVEL 1 goto BuildEventFailed if exist "$(TargetDir)"$(TargetName).map copy "$(TargetDir)"$(TargetName).map ..\..\..\game\bin\$(TargetName).map copy "$(TargetDir)"$(TargetName).pdb ..\..\..\game\bin\$(TargetName).pdb if ERRORLEVEL 1 goto BuildEventFailed goto BuildEventOK :BuildEventFailed echo *** ERROR! PostBuildStep FAILED for $(ProjectName)! EXE or DLL is probably running. *** del /q "$(TargetDir)"$(TargetFileName) exit 1 :BuildEventOK + + + + + + + if EXIST ..\..\..\game\bin\$(TargetFileName) for /f "delims=" %%A in ('attrib "..\..\..\game\bin\$(TargetFileName)"') do set valveTmpIsReadOnly="%%A" set valveTmpIsReadOnlyLetter=%valveTmpIsReadOnly:~6,1% if "%valveTmpIsReadOnlyLetter%"=="R" del /q "$(TargetDir)"$(TargetFileName) if exist ..\..\devtools\bin\vpc.exe ..\..\devtools\bin\vpc.exe -crc2 height2normal.vcxproj if ERRORLEVEL 1 exit 1 + + + /MP /d2Zi+ + MaxSpeed + AnySuitable + true + Speed + ..\..\common;..\..\public;..\..\public\tier0;..\..\public\tier1;..\common + _HAS_ITERATOR_DEBUGGING=0;_DEBUG;_WIN32;_CONSOLE;VPCGAMECAPS=VALVE;PROJECTDIR=D:\dev\games\rel\hl2\src\utils\height2normal;_DLL_EXT=.dll;VPCGAME=valve + true + false + MultiThreaded + false + true + StreamingSIMDExtensions + Fast + true + true + true + false + NotUsing + false + NoListing + $(IntDir)/ + $(IntDir)/ + $(IntDir)/ + false + Level4 + true + ProgramDatabase + CompileAsCpp + $(IntDir)/ + Prompt + + + NDEBUG;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE + 1033 + + + + + /DYNAMICBASE /NXCOMPAT /ignore:4221 + %(AdditionalDependencies) + NotSet + $(OutDir)\height2normal.exe + true + libc;libcd;libcmtd + true + $(IntDir)/$(TargetName).pdb + false + $(IntDir)/$(TargetName).map + Console + true + true + + MachineX86 + PromptImmediately + + + true + + + true + + + true + $(OutDir)/height2normal.bsc + + + Publishing to ..\..\..\game\bin + if not exist "..\..\..\game\bin" mkdir "..\..\..\game\bin" copy "$(TargetDir)"$(TargetFileName) ..\..\..\game\bin\$(TargetFileName) if ERRORLEVEL 1 goto BuildEventFailed if exist "$(TargetDir)"$(TargetName).map copy "$(TargetDir)"$(TargetName).map ..\..\..\game\bin\$(TargetName).map copy "$(TargetDir)"$(TargetName).pdb ..\..\..\game\bin\$(TargetName).pdb if ERRORLEVEL 1 goto BuildEventFailed goto BuildEventOK :BuildEventFailed echo *** ERROR! PostBuildStep FAILED for $(ProjectName)! EXE or DLL is probably running. *** del /q "$(TargetDir)"$(TargetFileName) exit 1 :BuildEventOK + + + + + + + + + + + + + + + + + + + + + + + + Compiling pointeroverride.asm + "$(VCInstallDir)bin\ml.exe" /c /Cp /Zi /Fo"$(IntDir)\%(Filename).obj" "%(FullPath)" + $(IntDir)\%(Filename).obj + Compiling pointeroverride.asm + "$(VCInstallDir)bin\ml.exe" /c /Cp /Zi /Fo"$(IntDir)\%(Filename).obj" "%(FullPath)" + $(IntDir)\%(Filename).obj + + + + + + + + diff --git a/mp/src/utils/height2normal/height2normal-2010.vcxproj.filters b/mp/src/utils/height2normal/height2normal-2010.vcxproj.filters new file mode 100644 index 00000000..192274bd --- /dev/null +++ b/mp/src/utils/height2normal/height2normal-2010.vcxproj.filters @@ -0,0 +1,56 @@ + + + + + {1680C80B-FF1E-EA4D-9817-CC12254F2E40} + + + {C5D73B3A-C648-896C-B7CE-F174808E5BA5} + + + {BA03E055-4FA2-FCE3-8A1C-D348547D379C} + + + + + Link Libraries + + + Link Libraries + + + Link Libraries + + + Link Libraries + + + Link Libraries + + + Link Libraries + + + + + Header Files + + + Header Files + + + + + Source Files + + + + + + + Source Files + + + + + diff --git a/mp/src/utils/height2normal/height2normal.cpp b/mp/src/utils/height2normal/height2normal.cpp new file mode 100644 index 00000000..f0dff189 --- /dev/null +++ b/mp/src/utils/height2normal/height2normal.cpp @@ -0,0 +1,343 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//===========================================================================// +#include +#include +#include +#include "bitmap/imageformat.h" +#include "tier1/strtools.h" +#include "mathlib/mathlib.h" +#include "bitmap/TGAWriter.h" +#include "bitmap/TGALoader.h" +#include +#include +#include "tier1/utlbuffer.h" +#include "tier2/tier2.h" +#include "filesystem.h" + +static bool g_NoPause = false; +static bool g_Quiet = false; + +static void Pause( void ) +{ + if( !g_NoPause ) + { + printf( "Hit a key to continue\n" ); + getch(); + } +} + +static bool ImageRGBA8888HasAlpha( unsigned char *pImage, int numTexels ) +{ + int i; + for( i = 0; i < numTexels; i++ ) + { + if( pImage[i*4+3] != 255 ) + { + return true; + } + } + return false; +} + +static bool GetKeyValueFromBuffer( CUtlBuffer &buf, char **key, char **val ) +{ + char stringBuf[2048]; + while( buf.IsValid() ) + { + buf.GetLine( stringBuf, sizeof(stringBuf) ); + char *scan = stringBuf; + // search for the first quote for the key. + while( 1 ) + { + if( *scan == '\"' ) + { + *key = ++scan; + break; + } + if( *scan == '#' ) + { + goto next_line; // comment + } + if( *scan == '\0' ) + { + goto next_line; // end of line. + } + scan++; + } + // read the key until another quote. + while( 1 ) + { + if( *scan == '\"' ) + { + *scan = '\0'; + scan++; + break; + } + if( *scan == '\0' ) + { + goto next_line; + } + scan++; + } + // search for the first quote for the value. + while( 1 ) + { + if( *scan == '\"' ) + { + *val = ++scan; + break; + } + if( *scan == '#' ) + { + goto next_line; // comment + } + if( *scan == '\0' ) + { + goto next_line; // end of line. + } + scan++; + } + // read the value until another quote. + while( 1 ) + { + if( *scan == '\"' ) + { + *scan = '\0'; + scan++; + // got a key and a value, so get the hell out of here. + return true; + } + if( *scan == '\0' ) + { + goto next_line; + } + scan++; + } +next_line: + ; + } + return false; +} + +static void LoadConfigFile( const char *pFileName, float *bumpScale, int *startFrame, int *endFrame ) +{ + CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER ); + if ( !g_pFullFileSystem->ReadFile( pFileName, NULL, buf ) ) + { + fprintf( stderr, "Can't open: %s\n", pFileName ); + Pause(); + exit( -1 ); + } + + char *key = NULL; + char *val = NULL; + while( GetKeyValueFromBuffer( buf, &key, &val ) ) + { + if( stricmp( key, "bumpscale" ) == 0 ) + { + *bumpScale = atof( val ); + } + if( stricmp( key, "startframe" ) == 0 ) + { + *startFrame = atoi( val ); + } + else if( stricmp( key, "endframe" ) == 0 ) + { + *endFrame = atoi( val ); + } + } +} + +static void Usage() +{ + fprintf( stderr, "Usage: height2normal [-nopause] [-quiet] tex1_normal.txt tex2_normal.txt . . .\n" ); + fprintf( stderr, "-quiet : don't print anything out, don't pause for input\n" ); + fprintf( stderr, "-nopause : don't pause for input\n" ); + Pause(); + exit( -1 ); +} + +void ProcessFiles( const char *pNormalFileNameWithoutExtension, + int startFrame, int endFrame, + float bumpScale ) +{ + static char heightTGAFileName[1024]; + static char normalTGAFileName[1024]; + static char buf[1024]; + bool animated = !( startFrame == -1 || endFrame == -1 ); + int numFrames = endFrame - startFrame + 1; + int frameID; + + for( frameID = 0; frameID < numFrames; frameID++ ) + { + if( animated ) + { + sprintf( normalTGAFileName, "%s%03d.tga", pNormalFileNameWithoutExtension, frameID + startFrame ); + } + else + { + sprintf( normalTGAFileName, "%s.tga", pNormalFileNameWithoutExtension ); + } + if( !Q_stristr( pNormalFileNameWithoutExtension, "_normal" ) ) + { + fprintf( stderr, "ERROR: config file name must end in _normal.txt\n" ); + return; + } + + strcpy( buf, pNormalFileNameWithoutExtension ); + + // Strip '_normal' off the end because we're looking for '_height' + char *pcUnderscore = Q_stristr( buf, "_normal" ); + *pcUnderscore = NULL; + + if( animated ) + { + sprintf( heightTGAFileName, "%s_height%03d.tga", buf, frameID + startFrame ); + } + else + { + sprintf( heightTGAFileName, "%s_height.tga", buf ); + } + + enum ImageFormat imageFormat; + int width, height; + float sourceGamma; + CUtlBuffer buf; + if ( !g_pFullFileSystem->ReadFile( heightTGAFileName, NULL, buf ) ) + { + fprintf( stderr, "%s not found\n", heightTGAFileName ); + return; + } + + if ( !TGALoader::GetInfo( buf, &width, &height, &imageFormat, &sourceGamma ) ) + { + fprintf( stderr, "error in %s\n", heightTGAFileName ); + return; + } + + int memRequired = ImageLoader::GetMemRequired( width, height, 1, IMAGE_FORMAT_IA88, false ); + unsigned char *pImageIA88 = new unsigned char[memRequired]; + + buf.SeekGet( CUtlBuffer::SEEK_HEAD, 0 ); + TGALoader::Load( pImageIA88, buf, width, height, IMAGE_FORMAT_IA88, sourceGamma, false ); + + memRequired = ImageLoader::GetMemRequired( width, height, 1, IMAGE_FORMAT_RGBA8888, false ); + unsigned char *pImageRGBA8888 = new unsigned char[memRequired]; + ImageLoader::ConvertIA88ImageToNormalMapRGBA8888( pImageIA88, width, height, pImageRGBA8888, bumpScale ); + + CUtlBuffer normalBuf; + ImageLoader::NormalizeNormalMapRGBA8888( pImageRGBA8888, width * height ); + if( ImageRGBA8888HasAlpha( pImageRGBA8888, width * height ) ) + { + TGAWriter::WriteToBuffer( pImageRGBA8888, normalBuf, width, height, IMAGE_FORMAT_RGBA8888, IMAGE_FORMAT_RGBA8888 ); + } + else + { + memRequired = ImageLoader::GetMemRequired( width, height, 1, IMAGE_FORMAT_RGB888, false ); + unsigned char *pImageRGB888 = new unsigned char[memRequired]; + ImageLoader::ConvertImageFormat( pImageRGBA8888, IMAGE_FORMAT_RGBA8888, + pImageRGB888, IMAGE_FORMAT_RGB888, width, height, 0, 0 ); + TGAWriter::WriteToBuffer( pImageRGB888, normalBuf, width, height, IMAGE_FORMAT_RGB888, IMAGE_FORMAT_RGB888 ); + delete [] pImageRGB888; + } + if ( !g_pFullFileSystem->WriteFile( normalTGAFileName, NULL, normalBuf ) ) + { + fprintf( stderr, "unable to write %s\n", normalTGAFileName ); + return; + } + delete [] pImageIA88; + delete [] pImageRGBA8888; + } +} + +int main( int argc, char **argv ) +{ + if( argc < 2 ) + { + Usage(); + } + MathLib_Init( 2.2f, 2.2f, 0.0f, 2.0f ); + InitDefaultFileSystem(); + + int i = 1; + while( i < argc ) + { + if( stricmp( argv[i], "-quiet" ) == 0 ) + { + i++; + g_Quiet = true; + g_NoPause = true; // no point in pausing if we aren't going to print anything out. + } + if( stricmp( argv[i], "-nopause" ) == 0 ) + { + i++; + g_NoPause = true; + } + else + { + break; + } + } + + char pCurrentDirectory[MAX_PATH]; + if ( _getcwd( pCurrentDirectory, sizeof(pCurrentDirectory) ) == NULL ) + { + fprintf( stderr, "Unable to get the current directory\n" ); + return -1; + } + + Q_FixSlashes( pCurrentDirectory ); + Q_StripTrailingSlash( pCurrentDirectory ); + + for( ; i < argc; i++ ) + { + static char normalFileNameWithoutExtension[1024]; + char *pFileName; + if ( !Q_IsAbsolutePath( argv[i] ) ) + { + Q_snprintf( normalFileNameWithoutExtension, sizeof(normalFileNameWithoutExtension), "%s\\%s", pCurrentDirectory, argv[i] ); + pFileName = normalFileNameWithoutExtension; + } + else + { + pFileName = argv[i]; + } + + if( !g_Quiet ) + { + printf( "file: %s\n", pFileName ); + } + float bumpScale = -1.0f; + int startFrame = -1; + int endFrame = -1; + LoadConfigFile( pFileName, &bumpScale, &startFrame, &endFrame ); + if( bumpScale == -1.0f ) + { + fprintf( stderr, "Must specify \"bumpscale\" in config file\n" ); + Pause(); + continue; + } + if( ( startFrame == -1 && endFrame != -1 ) || + ( startFrame != -1 && endFrame == -1 ) ) + { + fprintf( stderr, "ERROR: If you use startframe, you must use endframe, and vice versa.\n" ); + Pause(); + continue; + } + if( !g_Quiet ) + { + printf( "\tbumpscale: %f\n", bumpScale ); + } + + Q_StripExtension( pFileName, normalFileNameWithoutExtension, sizeof( normalFileNameWithoutExtension ) ); + ProcessFiles( normalFileNameWithoutExtension, + startFrame, endFrame, + bumpScale ); + } + return 0; +} diff --git a/mp/src/utils/motionmapper/motionmapper-2010.vcxproj b/mp/src/utils/motionmapper/motionmapper-2010.vcxproj new file mode 100644 index 00000000..ba84e933 --- /dev/null +++ b/mp/src/utils/motionmapper/motionmapper-2010.vcxproj @@ -0,0 +1,288 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + Motionmapper + {C805838C-256D-6672-3417-589B6AF7D95E} + + + + Application + MultiByte + motionmapper + + + Application + MultiByte + motionmapper + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30319.1 + .\Debug\win32\ + .\Debug\win32\ + ..\..\devtools\vstools;$(ExecutablePath);$(Path) + true + true + true + true + true + .\Release\win32\ + .\Release\win32\ + ..\..\devtools\vstools;$(ExecutablePath);$(Path) + true + true + false + true + true + + + + if EXIST ..\..\..\game\bin\$(TargetFileName) for /f "delims=" %%A in ('attrib "..\..\..\game\bin\$(TargetFileName)"') do set valveTmpIsReadOnly="%%A" set valveTmpIsReadOnlyLetter=%valveTmpIsReadOnly:~6,1% if "%valveTmpIsReadOnlyLetter%"=="R" del /q "$(TargetDir)"$(TargetFileName) if exist ..\..\devtools\bin\vpc.exe ..\..\devtools\bin\vpc.exe -crc2 motionmapper.vcxproj if ERRORLEVEL 1 exit 1 + + + /MP + Disabled + ..\..\common;..\..\public;..\..\public\tier0;..\..\public\tier1;..\common;..\nvtristriplib;..\..\Game_Shared + _HAS_ITERATOR_DEBUGGING=0;WIN32;_WIN32;_DEBUG;DEBUG;_WINDOWS;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;RAD_TELEMETRY_DISABLED;COMPILER_MSVC32;PROTECTED_THINGS_DISABLE;VPCGAMECAPS=VALVE;PROJECTDIR=D:\dev\games\rel\hl2\src\utils\motionmapper;_DLL_EXT=.dll;VPCGAME=valve + true + false + Default + MultiThreadedDebug + true + StreamingSIMDExtensions + Fast + true + true + true + false + NotUsing + false + NoListing + $(IntDir)/ + $(IntDir)/ + $(IntDir)/ + false + Level4 + true + EditAndContinue + CompileAsCpp + $(IntDir)/ + Prompt + + + _DEBUG;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE + 1033 + + + + + /NXCOMPAT /ignore:4221 + %(AdditionalDependencies);winmm.lib + NotSet + $(OutDir)\motionmapper.exe + true + libc;libcd;libcmt + true + $(IntDir)/$(TargetName).pdb + false + $(IntDir)/$(TargetName).map + Console + + MachineX86 + PromptImmediately + false + + + true + + + true + + + true + $(OutDir)/motionmapper.bsc + + + Publishing to ..\..\..\game\bin + if not exist "..\..\..\game\bin" mkdir "..\..\..\game\bin" copy "$(TargetDir)"$(TargetFileName) ..\..\..\game\bin\$(TargetFileName) if ERRORLEVEL 1 goto BuildEventFailed if exist "$(TargetDir)"$(TargetName).map copy "$(TargetDir)"$(TargetName).map ..\..\..\game\bin\$(TargetName).map copy "$(TargetDir)"$(TargetName).pdb ..\..\..\game\bin\$(TargetName).pdb if ERRORLEVEL 1 goto BuildEventFailed goto BuildEventOK :BuildEventFailed echo *** ERROR! PostBuildStep FAILED for $(ProjectName)! EXE or DLL is probably running. *** del /q "$(TargetDir)"$(TargetFileName) exit 1 :BuildEventOK + + + + + + + if EXIST ..\..\..\game\bin\$(TargetFileName) for /f "delims=" %%A in ('attrib "..\..\..\game\bin\$(TargetFileName)"') do set valveTmpIsReadOnly="%%A" set valveTmpIsReadOnlyLetter=%valveTmpIsReadOnly:~6,1% if "%valveTmpIsReadOnlyLetter%"=="R" del /q "$(TargetDir)"$(TargetFileName) if exist ..\..\devtools\bin\vpc.exe ..\..\devtools\bin\vpc.exe -crc2 motionmapper.vcxproj if ERRORLEVEL 1 exit 1 + + + /MP /d2Zi+ + MaxSpeed + AnySuitable + true + Speed + ..\..\common;..\..\public;..\..\public\tier0;..\..\public\tier1;..\common;..\nvtristriplib;..\..\Game_Shared + WIN32;_WIN32;NDEBUG;_WINDOWS;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;RAD_TELEMETRY_DISABLED;COMPILER_MSVC32;PROTECTED_THINGS_DISABLE;VPCGAMECAPS=VALVE;PROJECTDIR=D:\dev\games\rel\hl2\src\utils\motionmapper;_DLL_EXT=.dll;VPCGAME=valve + true + false + MultiThreaded + false + true + StreamingSIMDExtensions + Fast + true + true + true + false + NotUsing + false + NoListing + $(IntDir)/ + $(IntDir)/ + $(IntDir)/ + false + Level4 + true + ProgramDatabase + CompileAsCpp + $(IntDir)/ + Prompt + + + NDEBUG;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE + 1033 + + + + + /DYNAMICBASE /NXCOMPAT /ignore:4221 + %(AdditionalDependencies);winmm.lib + NotSet + $(OutDir)\motionmapper.exe + true + libc;libcd;libcmtd + true + $(IntDir)/$(TargetName).pdb + false + $(IntDir)/$(TargetName).map + Console + true + true + + MachineX86 + PromptImmediately + + + true + + + true + + + true + $(OutDir)/motionmapper.bsc + + + Publishing to ..\..\..\game\bin + if not exist "..\..\..\game\bin" mkdir "..\..\..\game\bin" copy "$(TargetDir)"$(TargetFileName) ..\..\..\game\bin\$(TargetFileName) if ERRORLEVEL 1 goto BuildEventFailed if exist "$(TargetDir)"$(TargetName).map copy "$(TargetDir)"$(TargetName).map ..\..\..\game\bin\$(TargetName).map copy "$(TargetDir)"$(TargetName).pdb ..\..\..\game\bin\$(TargetName).pdb if ERRORLEVEL 1 goto BuildEventFailed goto BuildEventOK :BuildEventFailed echo *** ERROR! PostBuildStep FAILED for $(ProjectName)! EXE or DLL is probably running. *** del /q "$(TargetDir)"$(TargetFileName) exit 1 :BuildEventOK + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + NotUsing + NotUsing + + + + + + + + + Compiling pointeroverride.asm + "$(VCInstallDir)bin\ml.exe" /c /Cp /Zi /Fo"$(IntDir)\%(Filename).obj" "%(FullPath)" + $(IntDir)\%(Filename).obj + Compiling pointeroverride.asm + "$(VCInstallDir)bin\ml.exe" /c /Cp /Zi /Fo"$(IntDir)\%(Filename).obj" "%(FullPath)" + $(IntDir)\%(Filename).obj + + + + + + + + diff --git a/mp/src/utils/motionmapper/motionmapper-2010.vcxproj.filters b/mp/src/utils/motionmapper/motionmapper-2010.vcxproj.filters new file mode 100644 index 00000000..15d71252 --- /dev/null +++ b/mp/src/utils/motionmapper/motionmapper-2010.vcxproj.filters @@ -0,0 +1,188 @@ + + + + + {1680C80B-FF1E-EA4D-9817-CC12254F2E40} + + + {C5D73B3A-C648-896C-B7CE-F174808E5BA5} + + + {BA03E055-4FA2-FCE3-8A1C-D348547D379C} + + + + + Link Libraries + + + Link Libraries + + + Link Libraries + + + Link Libraries + + + Link Libraries + + + Link Libraries + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + + + Source Files + + + + + diff --git a/mp/src/utils/motionmapper/motionmapper.cpp b/mp/src/utils/motionmapper/motionmapper.cpp new file mode 100644 index 00000000..6961e8ec --- /dev/null +++ b/mp/src/utils/motionmapper/motionmapper.cpp @@ -0,0 +1,3272 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +#include +#include +#include +#include +#include "filesystem_tools.h" +#include "cmdlib.h" +#include "scriplib.h" +#include "mathlib/mathlib.h" +#define EXTERN +#include "studio.h" +#include "motionmapper.h" +#include "tier1/strtools.h" +#include "tier0/icommandline.h" +#include "utldict.h" +#include +#include "UtlBuffer.h" +#include "utlsymbol.h" + +bool g_quiet = false; +bool g_verbose = false; +char g_outfile[1024]; +bool uselogfile = false; + +char g_szFilename[1024]; +FILE *g_fpInput; +char g_szLine[4096]; +int g_iLinecount; + +bool g_bZBrush = false; +bool g_bGaveMissingBoneWarning = false; + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : depth - +// *fmt - +// ... - +//----------------------------------------------------------------------------- +void vprint( int depth, const char *fmt, ... ) +{ + char string[ 8192 ]; + va_list va; + va_start( va, fmt ); + V_vsprintf_safe( string, fmt, va ); + va_end( va ); + + FILE *fp = NULL; + + if ( uselogfile ) + { + fp = fopen( "log.txt", "ab" ); + } + + while ( depth-- > 0 ) + { + vprint( 0, " " ); + OutputDebugString( " " ); + if ( fp ) + { + fprintf( fp, " " ); + } + } + + ::printf( "%s", string ); + OutputDebugString( string ); + + if ( fp ) + { + char *p = string; + while ( *p ) + { + if ( *p == '\n' ) + { + fputc( '\r', fp ); + } + fputc( *p, fp ); + p++; + } + fclose( fp ); + } +} + + +int k_memtotal; +void *kalloc( int num, int size ) +{ + // vprint( 0, "calloc( %d, %d )\n", num, size ); + // vprint( 0, "%d ", num * size ); + k_memtotal += num * size; + return calloc( num, size ); +} + +void kmemset( void *ptr, int value, int size ) +{ + // vprint( 0, "kmemset( %x, %d, %d )\n", ptr, value, size ); + memset( ptr, value, size ); + return; +} + +static bool g_bFirstWarning = true; + +void MdlWarning( const char *fmt, ... ) +{ + va_list args; + static char output[1024]; + + if (g_quiet) + { + if (g_bFirstWarning) + { + vprint( 0, "%s :\n", fullpath ); + g_bFirstWarning = false; + } + vprint( 0, "\t"); + } + + vprint( 0, "WARNING: "); + va_start( args, fmt ); + vprint( 0, fmt, args ); +} + + +void MdlError( char const *fmt, ... ) +{ + va_list args; + + if (g_quiet) + { + if (g_bFirstWarning) + { + vprint( 0, "%s :\n", fullpath ); + g_bFirstWarning = false; + } + vprint( 0, "\t"); + } + + vprint( 0, "ERROR: "); + va_start( args, fmt ); + vprint( 0, fmt, args ); + + exit( -1 ); +} + +int OpenGlobalFile( char *src ) +{ + int time1; + char filename[1024]; + + // local copy of string + strcpy( filename, ExpandPath( src ) ); + + // Ummm, path sanity checking + int pathLength; + int numBasePaths = CmdLib_GetNumBasePaths(); + // This is kinda gross. . . doing the same work in cmdlib on SafeOpenRead. + if( CmdLib_HasBasePath( filename, pathLength ) ) + { + char tmp[1024]; + int i; + for( i = 0; i < numBasePaths; i++ ) + { + strcpy( tmp, CmdLib_GetBasePath( i ) ); + strcat( tmp, filename + pathLength ); + + time1 = FileTime( tmp ); + if( time1 != -1 ) + { + if ((g_fpInput = fopen(tmp, "r")) == 0) + { + MdlWarning( "reader: could not open file '%s'\n", src ); + return 0; + } + else + { + return 1; + } + } + } + return 0; + } + else + { + time1 = FileTime (filename); + if (time1 == -1) + return 0; + + // Whoohooo, FOPEN! + if ((g_fpInput = fopen(filename, "r")) == 0) + { + MdlWarning( "reader: could not open file '%s'\n", src ); + return 0; + } + + return 1; + } +} + +bool IsEnd( char const* pLine ) +{ + if (strncmp( "end", pLine, 3 ) != 0) + return false; + return (pLine[3] == '\0') || (pLine[3] == '\n'); +} + + +//Wrong name for the use of it. +void scale_vertex( Vector &org ) +{ + org[0] = org[0] * g_currentscale; + org[1] = org[1] * g_currentscale; + org[2] = org[2] * g_currentscale; +} + + +void clip_rotations( RadianEuler& rot ) +{ + int j; + // clip everything to : -M_PI <= x < M_PI + + for (j = 0; j < 3; j++) { + while (rot[j] >= M_PI) + rot[j] -= M_PI*2; + while (rot[j] < -M_PI) + rot[j] += M_PI*2; + } +} + + +void clip_rotations( Vector& rot ) +{ + int j; + // clip everything to : -180 <= x < 180 + + for (j = 0; j < 3; j++) { + while (rot[j] >= 180) + rot[j] -= 180*2; + while (rot[j] < -180) + rot[j] += 180*2; + } +} + + +void Build_Reference( s_source_t *psource) +{ + int i, parent; + Vector angle; + + for (i = 0; i < psource->numbones; i++) + { + matrix3x4_t m; + AngleMatrix( psource->rawanim[0][i].rot, m ); + m[0][3] = psource->rawanim[0][i].pos[0]; + m[1][3] = psource->rawanim[0][i].pos[1]; + m[2][3] = psource->rawanim[0][i].pos[2]; + + parent = psource->localBone[i].parent; + if (parent == -1) + { + // scale the done pos. + // calc rotational matrices + MatrixCopy( m, psource->boneToPose[i] ); + } + else + { + // calc compound rotational matrices + // FIXME : Hey, it's orthogical so inv(A) == transpose(A) + ConcatTransforms( psource->boneToPose[parent], m, psource->boneToPose[i] ); + } + // vprint( 0, "%3d %f %f %f\n", i, psource->bonefixup[i].worldorg[0], psource->bonefixup[i].worldorg[1], psource->bonefixup[i].worldorg[2] ); + /* + AngleMatrix( angle, m ); + vprint( 0, "%8.4f %8.4f %8.4f\n", m[0][0], m[1][0], m[2][0] ); + vprint( 0, "%8.4f %8.4f %8.4f\n", m[0][1], m[1][1], m[2][1] ); + vprint( 0, "%8.4f %8.4f %8.4f\n", m[0][2], m[1][2], m[2][2] ); + */ + } +} + +int Grab_Nodes( s_node_t *pnodes ) +{ + // + // s_node_t structure: index is index!! + // + int index; + char name[1024]; + int parent; + int numbones = 0; + + // Init parent to none + for (index = 0; index < MAXSTUDIOSRCBONES; index++) + { + pnodes[index].parent = -1; + } + + // March through nodes lines + while (fgets( g_szLine, sizeof( g_szLine ), g_fpInput ) != NULL) + { + g_iLinecount++; + // get tokens + if (sscanf( g_szLine, "%d \"%[^\"]\" %d", &index, name, &parent ) == 3) + { + // check for duplicated bones + /* + if (strlen(pnodes[index].name) != 0) + { + MdlError( "bone \"%s\" exists more than once\n", name ); + } + */ + // copy name to struct array + V_strcpy_safe( pnodes[index].name, name ); + // set parent into struct array + pnodes[index].parent = parent; + // increment numbones + if (index > numbones) + { + numbones = index; + } + } + else + { + return numbones + 1; + } + } + MdlError( "Unexpected EOF at line %d\n", g_iLinecount ); + return 0; +} + +void Grab_Vertexanimation( s_source_t *psource ) +{ + char cmd[1024]; + int index; + Vector pos; + Vector normal; + int t = -1; + int count = 0; + static s_vertanim_t tmpvanim[MAXSTUDIOVERTS*4]; + + while (fgets( g_szLine, sizeof( g_szLine ), g_fpInput ) != NULL) + { + g_iLinecount++; + if (sscanf( g_szLine, "%d %f %f %f %f %f %f", &index, &pos[0], &pos[1], &pos[2], &normal[0], &normal[1], &normal[2] ) == 7) + { + if (psource->startframe < 0) + { + MdlError( "Missing frame start(%d) : %s", g_iLinecount, g_szLine ); + } + + if (t < 0) + { + MdlError( "VTA Frame Sync (%d) : %s", g_iLinecount, g_szLine ); + } + + tmpvanim[count].vertex = index; + VectorCopy( pos, tmpvanim[count].pos ); + VectorCopy( normal, tmpvanim[count].normal ); + count++; + + if (index >= psource->numvertices) + psource->numvertices = index + 1; + } + else + { + // flush data + + if (count) + { + psource->numvanims[t] = count; + + psource->vanim[t] = (s_vertanim_t *)kalloc( count, sizeof( s_vertanim_t ) ); + + memcpy( psource->vanim[t], tmpvanim, count * sizeof( s_vertanim_t ) ); + } + else if (t > 0) + { + psource->numvanims[t] = 0; + } + + // next command + if (sscanf( g_szLine, "%1023s %d", cmd, &index )) + { + if (strcmp( cmd, "time" ) == 0) + { + t = index; + count = 0; + + if (t < psource->startframe) + { + MdlError( "Frame MdlError(%d) : %s", g_iLinecount, g_szLine ); + } + if (t > psource->endframe) + { + MdlError( "Frame MdlError(%d) : %s", g_iLinecount, g_szLine ); + } + + t -= psource->startframe; + } + else if (strcmp( cmd, "end") == 0) + { + psource->numframes = psource->endframe - psource->startframe + 1; + return; + } + else + { + MdlError( "MdlError(%d) : %s", g_iLinecount, g_szLine ); + } + + } + else + { + MdlError( "MdlError(%d) : %s", g_iLinecount, g_szLine ); + } + } + } + MdlError( "unexpected EOF: %s\n", psource->filename ); +} + +void Grab_Animation( s_source_t *psource ) +{ + Vector pos; + RadianEuler rot; + char cmd[1024]; + int index; + int t = -99999999; + int size; + + // Init startframe + psource->startframe = -1; + + // size per frame + size = psource->numbones * sizeof( s_bone_t ); + + // march through animation + while (fgets( g_szLine, sizeof( g_szLine ), g_fpInput ) != NULL) + { + // linecount + g_iLinecount++; + // split if big enoough + if (sscanf( g_szLine, "%d %f %f %f %f %f %f", &index, &pos[0], &pos[1], &pos[2], &rot[0], &rot[1], &rot[2] ) == 7) + { + // startframe is sanity check for having determined time + if (psource->startframe < 0) + { + MdlError( "Missing frame start(%d) : %s", g_iLinecount, g_szLine ); + } + + // scale if pertinent + scale_vertex( pos ); + VectorCopy( pos, psource->rawanim[t][index].pos ); + VectorCopy( rot, psource->rawanim[t][index].rot ); + + clip_rotations( rot ); // !!! + } + else if (sscanf( g_szLine, "%1023s %d", cmd, &index )) + { + // get time + if (strcmp( cmd, "time" ) == 0) + { + // again time IS an index + t = index; + if (psource->startframe == -1) + { + psource->startframe = t; + } + // sanity check time (little funny logic here, see previous IF) + if (t < psource->startframe) + { + MdlError( "Frame MdlError(%d) : %s", g_iLinecount, g_szLine ); + } + // bump up endframe? + if (t > psource->endframe) + { + psource->endframe = t; + } + // make t into pure index + t -= psource->startframe; + + // check for memory allocation + if (psource->rawanim[t] == NULL) + { + // Allocate 1 frame of full bonecount + psource->rawanim[t] = (s_bone_t *)kalloc( 1, size ); + + // duplicate previous frames keys?? preventative sanity? + if (t > 0 && psource->rawanim[t-1]) + { + for (int j = 0; j < psource->numbones; j++) + { + VectorCopy( psource->rawanim[t-1][j].pos, psource->rawanim[t][j].pos ); + VectorCopy( psource->rawanim[t-1][j].rot, psource->rawanim[t][j].rot ); + } + } + } + else + { + // MdlError( "%s has duplicated frame %d\n", psource->filename, t ); + } + } + else if (strcmp( cmd, "end") == 0) + { + psource->numframes = psource->endframe - psource->startframe + 1; + + for (t = 0; t < psource->numframes; t++) + { + if (psource->rawanim[t] == NULL) + { + MdlError( "%s is missing frame %d\n", psource->filename, t + psource->startframe ); + } + } + + Build_Reference( psource ); + return; + } + else + { + MdlError( "MdlError(%d) : %s", g_iLinecount, g_szLine ); + } + } + else + { + MdlError( "MdlError(%d) : %s", g_iLinecount, g_szLine ); + } + } + + MdlError( "unexpected EOF: %s\n", psource->filename ); +} + +int lookup_index( s_source_t *psource, int material, Vector& vertex, Vector& normal, Vector2D texcoord ) +{ + int i; + + for (i = 0; i < numvlist; i++) + { + if (v_listdata[i].m == material + && DotProduct( g_normal[i], normal ) > normal_blend + && VectorCompare( g_vertex[i], vertex ) + && g_texcoord[i][0] == texcoord[0] + && g_texcoord[i][1] == texcoord[1]) + { + v_listdata[i].lastref = numvlist; + return i; + } + } + if (i >= MAXSTUDIOVERTS) { + MdlError( "too many indices in source: \"%s\"\n", psource->filename); + } + + VectorCopy( vertex, g_vertex[i] ); + VectorCopy( normal, g_normal[i] ); + Vector2Copy( texcoord, g_texcoord[i] ); + + v_listdata[i].v = i; + v_listdata[i].m = material; + v_listdata[i].n = i; + v_listdata[i].t = i; + + v_listdata[i].firstref = numvlist; + v_listdata[i].lastref = numvlist; + + numvlist = i + 1; + return i; +} + + +void ParseFaceData( s_source_t *psource, int material, s_face_t *pFace ) +{ + int index[3]; + int i, j; + Vector p; + Vector normal; + Vector2D t; + int iCount, bones[MAXSTUDIOSRCBONES]; + float weights[MAXSTUDIOSRCBONES]; + int bone; + + for (j = 0; j < 3; j++) + { + memset( g_szLine, 0, sizeof( g_szLine ) ); + + if (fgets( g_szLine, sizeof( g_szLine ), g_fpInput ) == NULL) + { + MdlError("%s: error on g_szLine %d: %s", g_szFilename, g_iLinecount, g_szLine ); + } + + iCount = 0; + + g_iLinecount++; + i = sscanf( g_szLine, "%d %f %f %f %f %f %f %f %f %d %d %f %d %f %d %f %d %f", + &bone, + &p[0], &p[1], &p[2], + &normal[0], &normal[1], &normal[2], + &t[0], &t[1], + &iCount, + &bones[0], &weights[0], &bones[1], &weights[1], &bones[2], &weights[2], &bones[3], &weights[3] ); + + if (i < 9) + continue; + + if (bone < 0 || bone >= psource->numbones) + { + MdlError("bogus bone index\n%d %s :\n%s", g_iLinecount, g_szFilename, g_szLine ); + } + + //Scale face pos + scale_vertex( p ); + + // continue parsing more bones. + // FIXME: don't we have a built in parser that'll do this? + if (iCount > 4) + { + int k; + int ctr = 0; + char *token; + for (k = 0; k < 18; k++) + { + while (g_szLine[ctr] == ' ') + { + ctr++; + } + token = strtok( &g_szLine[ctr], " " ); + ctr += strlen( token ) + 1; + } + for (k = 4; k < iCount && k < MAXSTUDIOSRCBONES; k++) + { + while (g_szLine[ctr] == ' ') + { + ctr++; + } + token = strtok( &g_szLine[ctr], " " ); + ctr += strlen( token ) + 1; + + bones[k] = atoi(token); + + token = strtok( &g_szLine[ctr], " " ); + ctr += strlen( token ) + 1; + + weights[k] = atof(token); + } + // vprint( 0, "%d ", iCount ); + + //vprint( 0, "\n"); + //exit(1); + } + + // adjust_vertex( p ); + // scale_vertex( p ); + + // move vertex position to object space. + // VectorSubtract( p, psource->bonefixup[bone].worldorg, tmp ); + // VectorTransform(tmp, psource->bonefixup[bone].im, p ); + + // move normal to object space. + // VectorCopy( normal, tmp ); + // VectorTransform(tmp, psource->bonefixup[bone].im, normal ); + // VectorNormalize( normal ); + + // invert v + t[1] = 1.0 - t[1]; + + index[j] = lookup_index( psource, material, p, normal, t ); + + if (i == 9 || iCount == 0) + { + g_bone[index[j]].numbones = 1; + g_bone[index[j]].bone[0] = bone; + g_bone[index[j]].weight[0] = 1.0; + } + else + { + iCount = SortAndBalanceBones( iCount, MAXSTUDIOBONEWEIGHTS, bones, weights ); + + g_bone[index[j]].numbones = iCount; + for (i = 0; i < iCount; i++) + { + g_bone[index[j]].bone[i] = bones[i]; + g_bone[index[j]].weight[i] = weights[i]; + } + } + } + + // pFace->material = material; // BUG + pFace->a = index[0]; + pFace->b = index[1]; + pFace->c = index[2]; + Assert( ((pFace->a & 0xF0000000) == 0) && ((pFace->b & 0xF0000000) == 0) && + ((pFace->c & 0xF0000000) == 0) ); + + if (flip_triangles) + { + j = pFace->b; pFace->b = pFace->c; pFace->c = j; + } +} + +int use_texture_as_material( int textureindex ) +{ + if (g_texture[textureindex].material == -1) + { + // vprint( 0, "%d %d %s\n", textureindex, g_nummaterials, g_texture[textureindex].name ); + g_material[g_nummaterials] = textureindex; + g_texture[textureindex].material = g_nummaterials++; + } + + return g_texture[textureindex].material; +} + +int material_to_texture( int material ) +{ + int i; + for (i = 0; i < g_numtextures; i++) + { + if (g_texture[i].material == material) + { + return i; + } + } + return -1; +} + +int lookup_texture( char *texturename, int maxlen ) +{ + int i; + + Q_StripExtension( texturename, texturename, maxlen ); + + for (i = 0; i < g_numtextures; i++) + { + if (stricmp( g_texture[i].name, texturename ) == 0) + { + return i; + } + } + + if (i >= MAXSTUDIOSKINS) + MdlError("Too many materials used, max %d\n", ( int )MAXSTUDIOSKINS ); + +// vprint( 0, "texture %d = %s\n", i, texturename ); + V_strcpy_safe( g_texture[i].name, texturename ); + + g_texture[i].material = -1; + /* + if (stristr( texturename, "chrome" ) != NULL) { + texture[i].flags = STUDIO_NF_FLATSHADE | STUDIO_NF_CHROME; + } + else { + texture[i].flags = 0; + } + */ + g_numtextures++; + return i; +} + +int SortAndBalanceBones( int iCount, int iMaxCount, int bones[], float weights[] ) +{ + int i; + + // collapse duplicate bone weights + for (i = 0; i < iCount-1; i++) + { + int j; + for (j = i + 1; j < iCount; j++) + { + if (bones[i] == bones[j]) + { + weights[i] += weights[j]; + weights[j] = 0.0; + } + } + } + + // do sleazy bubble sort + int bShouldSort; + do { + bShouldSort = false; + for (i = 0; i < iCount-1; i++) + { + if (weights[i+1] > weights[i]) + { + int j = bones[i+1]; bones[i+1] = bones[i]; bones[i] = j; + float w = weights[i+1]; weights[i+1] = weights[i]; weights[i] = w; + bShouldSort = true; + } + } + } while (bShouldSort); + + // throw away all weights less than 1/20th + while (iCount > 1 && weights[iCount-1] < 0.05) + { + iCount--; + } + + // clip to the top iMaxCount bones + if (iCount > iMaxCount) + { + iCount = iMaxCount; + } + + float t = 0; + for (i = 0; i < iCount; i++) + { + t += weights[i]; + } + + if (t <= 0.0) + { + // missing weights?, go ahead and evenly share? + // FIXME: shouldn't this error out? + t = 1.0 / iCount; + + for (i = 0; i < iCount; i++) + { + weights[i] = t; + } + } + else + { + // scale to sum to 1.0 + t = 1.0 / t; + + for (i = 0; i < iCount; i++) + { + weights[i] = weights[i] * t; + } + } + + return iCount; +} + +int vlistCompare( const void *elem1, const void *elem2 ) +{ + v_unify_t *u1 = &v_listdata[*(int *)elem1]; + v_unify_t *u2 = &v_listdata[*(int *)elem2]; + + // sort by material + if (u1->m < u2->m) + return -1; + if (u1->m > u2->m) + return 1; + + // sort by last used + if (u1->lastref < u2->lastref) + return -1; + if (u1->lastref > u2->lastref) + return 1; + + return 0; +} + +int faceCompare( const void *elem1, const void *elem2 ) +{ + int i1 = *(int *)elem1; + int i2 = *(int *)elem2; + + // sort by material + if (g_face[i1].material < g_face[i2].material) + return -1; + if (g_face[i1].material > g_face[i2].material) + return 1; + + // sort by original usage + if (i1 < i2) + return -1; + if (i1 > i2) + return 1; + + return 0; +} + +#define SMALL_FLOAT 1e-12 + +// NOTE: This routine was taken (and modified) from NVidia's BlinnReflection demo +// Creates basis vectors, based on a vertex and index list. +// See the NVidia white paper 'GDC2K PerPixel Lighting' for a description +// of how this computation works +static void CalcTriangleTangentSpace( s_source_t *pSrc, int v1, int v2, int v3, + Vector &sVect, Vector &tVect ) +{ +/* + static bool firstTime = true; + static FILE *fp = NULL; + if( firstTime ) + { + firstTime = false; + fp = fopen( "crap.out", "w" ); + } +*/ + + /* Compute the partial derivatives of X, Y, and Z with respect to S and T. */ + Vector2D t0( pSrc->texcoord[v1][0], pSrc->texcoord[v1][1] ); + Vector2D t1( pSrc->texcoord[v2][0], pSrc->texcoord[v2][1] ); + Vector2D t2( pSrc->texcoord[v3][0], pSrc->texcoord[v3][1] ); + Vector p0( pSrc->vertex[v1][0], pSrc->vertex[v1][1], pSrc->vertex[v1][2] ); + Vector p1( pSrc->vertex[v2][0], pSrc->vertex[v2][1], pSrc->vertex[v2][2] ); + Vector p2( pSrc->vertex[v3][0], pSrc->vertex[v3][1], pSrc->vertex[v3][2] ); + + sVect.Init( 0.0f, 0.0f, 0.0f ); + tVect.Init( 0.0f, 0.0f, 0.0f ); + + // x, s, t + Vector edge01 = Vector( p1.x - p0.x, t1.x - t0.x, t1.y - t0.y ); + Vector edge02 = Vector( p2.x - p0.x, t2.x - t0.x, t2.y - t0.y ); + + Vector cross; + CrossProduct( edge01, edge02, cross ); + if( fabs( cross.x ) > SMALL_FLOAT ) + { + sVect.x += -cross.y / cross.x; + tVect.x += -cross.z / cross.x; + } + + // y, s, t + edge01 = Vector( p1.y - p0.y, t1.x - t0.x, t1.y - t0.y ); + edge02 = Vector( p2.y - p0.y, t2.x - t0.x, t2.y - t0.y ); + + CrossProduct( edge01, edge02, cross ); + if( fabs( cross.x ) > SMALL_FLOAT ) + { + sVect.y += -cross.y / cross.x; + tVect.y += -cross.z / cross.x; + } + + // z, s, t + edge01 = Vector( p1.z - p0.z, t1.x - t0.x, t1.y - t0.y ); + edge02 = Vector( p2.z - p0.z, t2.x - t0.x, t2.y - t0.y ); + + CrossProduct( edge01, edge02, cross ); + if( fabs( cross.x ) > SMALL_FLOAT ) + { + sVect.z += -cross.y / cross.x; + tVect.z += -cross.z / cross.x; + } + + // Normalize sVect and tVect + VectorNormalize( sVect ); + VectorNormalize( tVect ); + +/* + // Calculate flat normal + Vector flatNormal; + edge01 = p1 - p0; + edge02 = p2 - p0; + CrossProduct( edge02, edge01, flatNormal ); + VectorNormalize( flatNormal ); + + // Get the average position + Vector avgPos = ( p0 + p1 + p2 ) / 3.0f; + + // Draw the svect + Vector endS = avgPos + sVect * .2f; + fvprint( 0, fp, "2\n" ); + fvprint( 0, fp, "%f %f %f 1.0 0.0 0.0\n", endS[0], endS[1], endS[2] ); + fvprint( 0, fp, "%f %f %f 1.0 0.0 0.0\n", avgPos[0], avgPos[1], avgPos[2] ); + + // Draw the tvect + Vector endT = avgPos + tVect * .2f; + fvprint( 0, fp, "2\n" ); + fvprint( 0, fp, "%f %f %f 0.0 1.0 0.0\n", endT[0], endT[1], endT[2] ); + fvprint( 0, fp, "%f %f %f 0.0 1.0 0.0\n", avgPos[0], avgPos[1], avgPos[2] ); + + // Draw the normal + Vector endN = avgPos + flatNormal * .2f; + fvprint( 0, fp, "2\n" ); + fvprint( 0, fp, "%f %f %f 0.0 0.0 1.0\n", endN[0], endN[1], endN[2] ); + fvprint( 0, fp, "%f %f %f 0.0 0.0 1.0\n", avgPos[0], avgPos[1], avgPos[2] ); + + // Draw the wireframe of the triangle in white. + fvprint( 0, fp, "2\n" ); + fvprint( 0, fp, "%f %f %f 1.0 1.0 1.0\n", p0[0], p0[1], p0[2] ); + fvprint( 0, fp, "%f %f %f 1.0 1.0 1.0\n", p1[0], p1[1], p1[2] ); + fvprint( 0, fp, "2\n" ); + fvprint( 0, fp, "%f %f %f 1.0 1.0 1.0\n", p1[0], p1[1], p1[2] ); + fvprint( 0, fp, "%f %f %f 1.0 1.0 1.0\n", p2[0], p2[1], p2[2] ); + fvprint( 0, fp, "2\n" ); + fvprint( 0, fp, "%f %f %f 1.0 1.0 1.0\n", p2[0], p2[1], p2[2] ); + fvprint( 0, fp, "%f %f %f 1.0 1.0 1.0\n", p0[0], p0[1], p0[2] ); + + // Draw a slightly shrunken version of the geometry to hide surfaces + Vector tmp0 = p0 - flatNormal * .1f; + Vector tmp1 = p1 - flatNormal * .1f; + Vector tmp2 = p2 - flatNormal * .1f; + fvprint( 0, fp, "3\n" ); + fvprint( 0, fp, "%f %f %f 0.1 0.1 0.1\n", tmp0[0], tmp0[1], tmp0[2] ); + fvprint( 0, fp, "%f %f %f 0.1 0.1 0.1\n", tmp1[0], tmp1[1], tmp1[2] ); + fvprint( 0, fp, "%f %f %f 0.1 0.1 0.1\n", tmp2[0], tmp2[1], tmp2[2] ); + + fflush( fp ); +*/ +} + +typedef CUtlVector CIntVector; + +void CalcModelTangentSpaces( s_source_t *pSrc ) +{ + // Build a map from vertex to a list of triangles that share the vert. + int meshID; + for( meshID = 0; meshID < pSrc->nummeshes; meshID++ ) + { + s_mesh_t *pMesh = &pSrc->mesh[pSrc->meshindex[meshID]]; + CUtlVector vertToTriMap; + vertToTriMap.AddMultipleToTail( pMesh->numvertices ); + int triID; + for( triID = 0; triID < pMesh->numfaces; triID++ ) + { + s_face_t *pFace = &pSrc->face[triID + pMesh->faceoffset]; + vertToTriMap[pFace->a].AddToTail( triID ); + vertToTriMap[pFace->b].AddToTail( triID ); + vertToTriMap[pFace->c].AddToTail( triID ); + } + + // Calculate the tangent space for each triangle. + CUtlVector triSVect; + CUtlVector triTVect; + triSVect.AddMultipleToTail( pMesh->numfaces ); + triTVect.AddMultipleToTail( pMesh->numfaces ); + for( triID = 0; triID < pMesh->numfaces; triID++ ) + { + s_face_t *pFace = &pSrc->face[triID + pMesh->faceoffset]; + CalcTriangleTangentSpace( pSrc, + pMesh->vertexoffset + pFace->a, + pMesh->vertexoffset + pFace->b, + pMesh->vertexoffset + pFace->c, + triSVect[triID], triTVect[triID] ); + } + + // calculate an average tangent space for each vertex. + int vertID; + for( vertID = 0; vertID < pMesh->numvertices; vertID++ ) + { + const Vector &normal = pSrc->normal[vertID+pMesh->vertexoffset]; + Vector4D &finalSVect = pSrc->tangentS[vertID+pMesh->vertexoffset]; + Vector sVect, tVect; + + sVect.Init( 0.0f, 0.0f, 0.0f ); + tVect.Init( 0.0f, 0.0f, 0.0f ); + for( triID = 0; triID < vertToTriMap[vertID].Size(); triID++ ) + { + sVect += triSVect[vertToTriMap[vertID][triID]]; + tVect += triTVect[vertToTriMap[vertID][triID]]; + } + + // In the case of zbrush, everything needs to be treated as smooth. + if( g_bZBrush ) + { + int vertID2; + Vector vertPos1( pSrc->vertex[vertID][0], pSrc->vertex[vertID][1], pSrc->vertex[vertID][2] ); + for( vertID2 = 0; vertID2 < pMesh->numvertices; vertID2++ ) + { + if( vertID2 == vertID ) + { + continue; + } + Vector vertPos2( pSrc->vertex[vertID2][0], pSrc->vertex[vertID2][1], pSrc->vertex[vertID2][2] ); + if( vertPos1 == vertPos2 ) + { + int triID2; + for( triID2 = 0; triID2 < vertToTriMap[vertID2].Size(); triID2++ ) + { + sVect += triSVect[vertToTriMap[vertID2][triID2]]; + tVect += triTVect[vertToTriMap[vertID2][triID2]]; + } + } + } + } + + // make an orthonormal system. + // need to check if we are left or right handed. + Vector tmpVect; + CrossProduct( sVect, tVect, tmpVect ); + bool leftHanded = DotProduct( tmpVect, normal ) < 0.0f; + if( !leftHanded ) + { + CrossProduct( normal, sVect, tVect ); + CrossProduct( tVect, normal, sVect ); + VectorNormalize( sVect ); + VectorNormalize( tVect ); + finalSVect[0] = sVect[0]; + finalSVect[1] = sVect[1]; + finalSVect[2] = sVect[2]; + finalSVect[3] = 1.0f; + } + else + { + CrossProduct( sVect, normal, tVect ); + CrossProduct( normal, tVect, sVect ); + VectorNormalize( sVect ); + VectorNormalize( tVect ); + finalSVect[0] = sVect[0]; + finalSVect[1] = sVect[1]; + finalSVect[2] = sVect[2]; + finalSVect[3] = -1.0f; + } + } + } +} + +void BuildIndividualMeshes( s_source_t *psource ) +{ + int i, j, k; + + // sort new vertices by materials, last used + static int v_listsort[MAXSTUDIOVERTS]; // map desired order to vlist entry + static int v_ilistsort[MAXSTUDIOVERTS]; // map vlist entry to desired order + + for (i = 0; i < numvlist; i++) + { + v_listsort[i] = i; + } + qsort( v_listsort, numvlist, sizeof( int ), vlistCompare ); + for (i = 0; i < numvlist; i++) + { + v_ilistsort[v_listsort[i]] = i; + } + + + // allocate memory + psource->numvertices = numvlist; + psource->localBoneweight = (s_boneweight_t *)kalloc( psource->numvertices, sizeof( s_boneweight_t ) ); + psource->globalBoneweight = NULL; + psource->vertexInfo = (s_vertexinfo_t *)kalloc( psource->numvertices, sizeof( s_vertexinfo_t ) ); + psource->vertex = new Vector[psource->numvertices]; + psource->normal = new Vector[psource->numvertices]; + psource->tangentS = new Vector4D[psource->numvertices]; + psource->texcoord = (Vector2D *)kalloc( psource->numvertices, sizeof( Vector2D ) ); + + // create arrays of unique vertexes, normals, texcoords. + for (i = 0; i < psource->numvertices; i++) + { + j = v_listsort[i]; + + VectorCopy( g_vertex[v_listdata[j].v], psource->vertex[i] ); + VectorCopy( g_normal[v_listdata[j].n], psource->normal[i] ); + Vector2Copy( g_texcoord[v_listdata[j].t], psource->texcoord[i] ); + + psource->localBoneweight[i].numbones = g_bone[v_listdata[j].v].numbones; + int k; + for( k = 0; k < MAXSTUDIOBONEWEIGHTS; k++ ) + { + psource->localBoneweight[i].bone[k] = g_bone[v_listdata[j].v].bone[k]; + psource->localBoneweight[i].weight[k] = g_bone[v_listdata[j].v].weight[k]; + } + + // store a bunch of other info + psource->vertexInfo[i].material = v_listdata[j].m; + + psource->vertexInfo[i].firstref = v_listdata[j].firstref; + psource->vertexInfo[i].lastref = v_listdata[j].lastref; + // vprint( 0, "%4d : %2d : %6.2f %6.2f %6.2f\n", i, psource->boneweight[i].bone[0], psource->vertex[i][0], psource->vertex[i][1], psource->vertex[i][2] ); + } + + // sort faces by materials, last used. + static int facesort[MAXSTUDIOTRIANGLES]; // map desired order to src_face entry + static int ifacesort[MAXSTUDIOTRIANGLES]; // map src_face entry to desired order + + for (i = 0; i < g_numfaces; i++) + { + facesort[i] = i; + } + qsort( facesort, g_numfaces, sizeof( int ), faceCompare ); + for (i = 0; i < g_numfaces; i++) + { + ifacesort[facesort[i]] = i; + } + + psource->numfaces = g_numfaces; + // find first occurance for each material + for (k = 0; k < MAXSTUDIOSKINS; k++) + { + psource->mesh[k].numvertices = 0; + psource->mesh[k].vertexoffset = psource->numvertices; + + psource->mesh[k].numfaces = 0; + psource->mesh[k].faceoffset = g_numfaces; + } + + // find first and count of indices per material + for (i = 0; i < psource->numvertices; i++) + { + k = psource->vertexInfo[i].material; + psource->mesh[k].numvertices++; + if (psource->mesh[k].vertexoffset > i) + psource->mesh[k].vertexoffset = i; + } + + // find first and count of faces per material + for (i = 0; i < psource->numfaces; i++) + { + k = g_face[facesort[i]].material; + + psource->mesh[k].numfaces++; + if (psource->mesh[k].faceoffset > i) + psource->mesh[k].faceoffset = i; + } + + /* + for (k = 0; k < MAXSTUDIOSKINS; k++) + { + vprint( 0, "%d : %d:%d %d:%d\n", k, psource->mesh[k].numvertices, psource->mesh[k].vertexoffset, psource->mesh[k].numfaces, psource->mesh[k].faceoffset ); + } + */ + + // create remapped faces + psource->face = (s_face_t *)kalloc( psource->numfaces, sizeof( s_face_t )); + for (k = 0; k < MAXSTUDIOSKINS; k++) + { + if (psource->mesh[k].numfaces) + { + psource->meshindex[psource->nummeshes] = k; + + for (i = psource->mesh[k].faceoffset; i < psource->mesh[k].numfaces + psource->mesh[k].faceoffset; i++) + { + j = facesort[i]; + + psource->face[i].a = v_ilistsort[g_src_uface[j].a] - psource->mesh[k].vertexoffset; + psource->face[i].b = v_ilistsort[g_src_uface[j].b] - psource->mesh[k].vertexoffset; + psource->face[i].c = v_ilistsort[g_src_uface[j].c] - psource->mesh[k].vertexoffset; + Assert( ((psource->face[i].a & 0xF0000000) == 0) && ((psource->face[i].b & 0xF0000000) == 0) && + ((psource->face[i].c & 0xF0000000) == 0) ); + // vprint( 0, "%3d : %4d %4d %4d\n", i, psource->face[i].a, psource->face[i].b, psource->face[i].c ); + } + + psource->nummeshes++; + } + } + + CalcModelTangentSpaces( psource ); +} + +void Grab_Triangles( s_source_t *psource ) +{ + int i; + Vector vmin, vmax; + + vmin[0] = vmin[1] = vmin[2] = 99999; + vmax[0] = vmax[1] = vmax[2] = -99999; + + g_numfaces = 0; + numvlist = 0; + + // + // load the base triangles + // + int texture; + int material; + char texturename[64]; + + while (1) + { + if (fgets( g_szLine, sizeof( g_szLine ), g_fpInput ) == NULL) + break; + + g_iLinecount++; + + // check for end + if (IsEnd( g_szLine )) + break; + + // Look for extra junk that we may want to avoid... + int nLineLength = strlen( g_szLine ); + if (nLineLength >= 64) + { + MdlWarning("Unexpected data at line %d, (need a texture name) ignoring...\n", g_iLinecount ); + continue; + } + + // strip off trailing smag + V_strcpy_safe( texturename, g_szLine ); + for (i = strlen( texturename ) - 1; i >= 0 && ! isgraph( texturename[i] ); i--) + { + } + texturename[i + 1] = '\0'; + + // funky texture overrides + for (i = 0; i < numrep; i++) + { + if (sourcetexture[i][0] == '\0') + { + strcpy( texturename, defaulttexture[i] ); + break; + } + if (stricmp( texturename, sourcetexture[i]) == 0) + { + strcpy( texturename, defaulttexture[i] ); + break; + } + } + + if (texturename[0] == '\0') + { + // weird source problem, skip them + fgets( g_szLine, sizeof( g_szLine ), g_fpInput ); + fgets( g_szLine, sizeof( g_szLine ), g_fpInput ); + fgets( g_szLine, sizeof( g_szLine ), g_fpInput ); + g_iLinecount += 3; + continue; + } + + if (stricmp( texturename, "null.bmp") == 0 || stricmp( texturename, "null.tga") == 0) + { + // skip all faces with the null texture on them. + fgets( g_szLine, sizeof( g_szLine ), g_fpInput ); + fgets( g_szLine, sizeof( g_szLine ), g_fpInput ); + fgets( g_szLine, sizeof( g_szLine ), g_fpInput ); + g_iLinecount += 3; + continue; + } + + texture = lookup_texture( texturename, sizeof( texturename ) ); + psource->texmap[texture] = texture; // hack, make it 1:1 + material = use_texture_as_material( texture ); + + s_face_t f; + ParseFaceData( psource, material, &f ); + + g_src_uface[g_numfaces] = f; + g_face[g_numfaces].material = material; + g_numfaces++; + } + + BuildIndividualMeshes( psource ); +} + +//-------------------------------------------------------------------- +// Load a SMD file +//-------------------------------------------------------------------- +int Load_SMD ( s_source_t *psource ) +{ + char cmd[1024]; + int option; + + // Open file + if (!OpenGlobalFile( psource->filename )) + return 0; + + // verbose + if( !g_quiet ) + { + printf ("SMD MODEL %s\n", psource->filename); + } + + //March through lines + g_iLinecount = 0; + while (fgets( g_szLine, sizeof( g_szLine ), g_fpInput ) != NULL) + { + g_iLinecount++; + int numRead = sscanf( g_szLine, "%s %d", cmd, &option ); + + // Blank line + if ((numRead == EOF) || (numRead == 0)) + continue; + + if (strcmp( cmd, "version" ) == 0) + { + if (option != 1) + { + MdlError("bad version\n"); + } + } + // Get hierarchy? + else if (strcmp( cmd, "nodes" ) == 0) + { + psource->numbones = Grab_Nodes( psource->localBone ); + } + // Get animation?? + else if (strcmp( cmd, "skeleton" ) == 0) + { + Grab_Animation( psource ); + } + // Geo? + else if (strcmp( cmd, "triangles" ) == 0) + { + Grab_Triangles( psource ); + } + // Geo animation + else if (strcmp( cmd, "vertexanimation" ) == 0) + { + Grab_Vertexanimation( psource ); + } + else + { + MdlWarning("unknown studio command\n" ); + } + } + fclose( g_fpInput ); + + is_v1support = true; + + return 1; +} + +//----------------------------------------------------------------------------- +// Checks to see if the model source was already loaded +//----------------------------------------------------------------------------- +static s_source_t *FindCachedSource( char const* name, char const* xext ) +{ + int i; + + if( xext[0] ) + { + // we know what extension is necessary. . look for it. + sprintf (g_szFilename, "%s%s.%s", cddir[numdirs], name, xext ); + for (i = 0; i < g_numsources; i++) + { + if (stricmp( g_szFilename, g_source[i]->filename ) == 0) + return g_source[i]; + } + } + else + { + // we don't know what extension to use, so look for all of 'em. + sprintf (g_szFilename, "%s%s.vrm", cddir[numdirs], name ); + for (i = 0; i < g_numsources; i++) + { + if (stricmp( g_szFilename, g_source[i]->filename ) == 0) + return g_source[i]; + } + sprintf (g_szFilename, "%s%s.smd", cddir[numdirs], name ); + for (i = 0; i < g_numsources; i++) + { + if (stricmp( g_szFilename, g_source[i]->filename ) == 0) + return g_source[i]; + } + /* + sprintf (g_szFilename, "%s%s.vta", cddir[numdirs], name ); + for (i = 0; i < g_numsources; i++) + { + if (stricmp( g_szFilename, g_source[i]->filename ) == 0) + return g_source[i]; + } + */ + } + + // Not found + return 0; +} + +static void FlipFacing( s_source_t *pSrc ) +{ + unsigned short tmp; + + int i, j; + for( i = 0; i < pSrc->nummeshes; i++ ) + { + s_mesh_t *pMesh = &pSrc->mesh[i]; + for( j = 0; j < pMesh->numfaces; j++ ) + { + s_face_t &f = pSrc->face[pMesh->faceoffset + j]; + tmp = f.b; f.b = f.c; f.c = tmp; + } + } +} + +//----------------------------------------------------------------------------- +// Loads an animation source +//----------------------------------------------------------------------------- + +s_source_t *Load_Source( char const *name, const char *ext, bool reverse, bool isActiveModel ) +{ + // Sanity check number of source files + if ( g_numsources >= MAXSTUDIOSEQUENCES ) + MdlError( "Load_Source( %s ) - overflowed g_numsources.", name ); + + // Sanity check file and init + Assert(name); + int namelen = strlen(name) + 1; + char* pTempName = (char*)_alloca( namelen ); + char xext[32]; + int result = false; + + // Local copy of filename + strcpy( pTempName, name ); + + // Sanity check file extension? + Q_ExtractFileExtension( pTempName, xext, sizeof( xext ) ); + if (xext[0] == '\0') + { + V_strcpy_safe( xext, ext ); + } + else + { + Q_StripExtension( pTempName, pTempName, namelen ); + } + + // Cached source, ie: already loaded model, legacy + // s_source_t* pSource = FindCachedSource( pTempName, xext ); + // if (pSource) + // { + // if (isActiveModel) + // pSource->isActiveModel = true; + // return pSource; + // } + + // allocate space and whatnot + g_source[g_numsources] = (s_source_t *)kalloc( 1, sizeof( s_source_t ) ); + V_strcpy_safe( g_source[g_numsources]->filename, g_szFilename ); + + // legacy stuff + if (isActiveModel) + { + g_source[g_numsources]->isActiveModel = true; + } + + // more ext sanity check + if ( ( !result && xext[0] == '\0' ) || stricmp( xext, "smd" ) == 0) + { + Q_snprintf( g_szFilename, sizeof(g_szFilename), "%s%s.smd", cddir[numdirs], pTempName ); + V_strcpy_safe( g_source[g_numsources]->filename, g_szFilename ); + + // Import part, load smd file + result = Load_SMD( g_source[g_numsources] ); + } + + /* + if ( ( !result && xext[0] == '\0' ) || stricmp( xext, "dmx" ) == 0) + { + Q_snprintf( g_szFilename, sizeof(g_szFilename), "%s%s.dmx", cddir[numdirs], pTempName ); + V_strcpy_safe( g_source[g_numsources]->filename, g_szFilename ); + + // Import part, load smd file + result = Load_DMX( g_source[g_numsources] ); + } + */ + + // Oops + if ( !result) + { + MdlError( "could not load file '%s'\n", g_source[g_numsources]->filename ); + } + + // bump up number of sources + g_numsources++; + if( reverse ) + { + FlipFacing( g_source[g_numsources-1] ); + } + return g_source[g_numsources-1]; +} + +void SaveNodes( s_source_t *source, CUtlBuffer& buf ) +{ + if ( source->numbones <= 0 ) + return; + + buf.Printf( "nodes\n" ); + + for ( int i = 0; i < source->numbones; ++i ) + { + s_node_t *bone = &source->localBone[ i ]; + + buf.Printf( "%d \"%s\" %d\n", i, bone->name, bone->parent ); + } + + buf.Printf( "end\n" ); +} + +// FIXME: since we don't us a .qc, we could have problems with scaling, etc.??? +void descale_vertex( Vector &org ) +{ + float invscale = 1.0f / g_currentscale; + + org[0] = org[0] * invscale; + org[1] = org[1] * invscale; + org[2] = org[2] * invscale; +} + +void SaveAnimation( s_source_t *source, CUtlBuffer& buf ) +{ + if ( source->numbones <= 0 ) + return; + + buf.Printf( "skeleton\n" ); + + for ( int frame = 0; frame < source->numframes; ++frame ) + { + buf.Printf( "time %i\n", frame + source->startframe ); + + for ( int i = 0; i < source->numbones; ++i ) + { + s_bone_t *prev = NULL; + if ( frame > 0 ) + { + if ( source->rawanim[ frame - 1 ] ) + { + prev = &source->rawanim[ frame - 1 ][ i ]; + } + } + + Vector pos = source->rawanim[ frame ][ i ].pos; + descale_vertex( pos ); + RadianEuler rot = source->rawanim[ frame ][ i ].rot; + +// If this is enabled, then we delta this pos vs the prev frame and don't write out a sample if it's the same value... +#if 0 + if ( prev ) + { + Vector ppos = source->rawanim[ frame -1 ][ i ].pos; + descale_vertex( pos ); + RadianEuler prot = source->rawanim[ frame -1 ][ i ].rot; + + // Only output it if there's a delta + if ( ( ppos != pos ) || + Q_memcmp( &prot, &rot, sizeof( prot ) ) ) + { + buf.Printf + ( "%d %f %f %f %f %f %f\n", + i, // bone index + pos[ 0 ], + pos[ 1 ], + pos[ 2 ], + rot[ 0 ], + rot[ 1 ], + rot[ 2 ] + ); + } + } + else +#endif + { + buf.Printf + ( "%d %f %f %f %f %f %f\n", + i, // bone index + pos[ 0 ], + pos[ 1 ], + pos[ 2 ], + rot[ 0 ], + rot[ 1 ], + rot[ 2 ] + ); + } + } + } + + buf.Printf( "end\n" ); +} + +void Save_SMD( char const *filename, s_source_t *source ) +{ + // Text buffer + CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER ); + + buf.Printf( "version 1\n" ); + + SaveNodes( source, buf ); + SaveAnimation( source, buf ); + + FileHandle_t fh = g_pFileSystem->Open( filename, "wb" ); + if ( FILESYSTEM_INVALID_HANDLE != fh ) + { + g_pFileSystem->Write( buf.Base(), buf.TellPut(), fh ); + g_pFileSystem->Close( fh ); + } +} + +//-------------------------------------------------------------------- +// mikes right handed row based linear algebra +//-------------------------------------------------------------------- +struct M_matrix4x4_t +{ + M_matrix4x4_t() { + + m_flMatVal[0][0] = 1.0; m_flMatVal[0][1] = 0.0; m_flMatVal[0][2] = 0.0; m_flMatVal[0][3] = 0.0; + m_flMatVal[1][0] = 0.0; m_flMatVal[1][1] = 1.0; m_flMatVal[1][2] = 0.0; m_flMatVal[1][3] = 0.0; + m_flMatVal[2][0] = 0.0; m_flMatVal[2][1] = 0.0; m_flMatVal[2][2] = 1.0; m_flMatVal[2][3] = 0.0; + m_flMatVal[3][0] = 0.0; m_flMatVal[3][1] = 0.0; m_flMatVal[3][2] = 0.0; m_flMatVal[3][3] = 1.0; + + } + // M_matrix3x4_t( + // float m00, float m01, float m02, + // float m10, float m11, float m12, + // float m20, float m21, float m22, + // float m30, float m31, float m32) + // { + // m_flMatVal[0][0] = m00; m_flMatVal[0][1] = m01; m_flMatVal[0][2] = m02; + // m_flMatVal[1][0] = m10; m_flMatVal[1][1] = m11; m_flMatVal[1][2] = m12; + // m_flMatVal[2][0] = m20; m_flMatVal[2][1] = m21; m_flMatVal[2][2] = m22; + // m_flMatVal[3][0] = m30; m_flMatVal[3][1] = m31; m_flMatVal[3][2] = m32; + + // } + + float *operator[]( int i ) { Assert(( i >= 0 ) && ( i < 4 )); return m_flMatVal[i]; } + const float *operator[]( int i ) const { Assert(( i >= 0 ) && ( i < 4 )); return m_flMatVal[i]; } + float *Base() { return &m_flMatVal[0][0]; } + const float *Base() const { return &m_flMatVal[0][0]; } + + float m_flMatVal[4][4]; +}; + +void M_MatrixAngles( const M_matrix4x4_t& matrix, RadianEuler &angles, Vector &position) +{ + float cX, sX, cY, sY, cZ, sZ; + + sY = -matrix[0][2]; + cY = sqrtf(1.0-(sY*sY)); + + if (cY != 0.0) + { + sX = matrix[1][2]; + cX = matrix[2][2]; + sZ = matrix[0][1]; + cZ = matrix[0][0]; + } + else + { + sX = -matrix[2][1]; + cX = matrix[1][1]; + sZ = 0.0; + cZ = 1.0; + } + + angles[0] = atan2f( sX, cX ); + angles[2] = atan2f( sZ, cZ ); + + sX = sinf(angles[0]); + cX = cosf(angles[0]); + + if (sX > cX) + cY = matrix[1][2] / sX; + else + cY = matrix[2][2] / cX; + + angles[1] = atan2f( sY, cY ); + + + position.x = matrix[3][0]; + position.y = matrix[3][1]; + position.z = matrix[3][2]; + +} + +// void M_MatrixAngles( const M_matrix4x4_t& matrix, RadianEuler &angles, Vector &position) +// { + + // float cX, sX, cY, sY, cZ, sZ; + + // sY = matrix[2][0]; + // cY = sqrtf(1.0-(sY*sY)); + + // if (cY != 0.0) + // { + // sX = -matrix[2][1]; + // cX = matrix[2][2]; + // sZ = -matrix[1][0]; + // cZ = matrix[0][0]; + // } + // else + // { + // sX = matrix[0][1]; + // cX = matrix[1][1]; + // sZ = 0.0; + // cZ = 1.0; + // } + + // angles[0] = atan2f( sX, cX ); + // angles[2] = atan2f( sZ, cZ ); + + // sX = sinf(angles[0]); + // cX = cosf(angles[0]); + + // if (sX > cX) + // cY = -matrix[2][1] / sX; + // else + // cY = matrix[2][2] / cX; + + // angles[1] = atan2f( sY, cY ); + + // angles[0] = angles[0]; + // angles[1] = angles[1]; + // angles[2] = angles[2]; + + // position.x = matrix[3][0]; + // position.y = matrix[3][1]; + // position.z = matrix[3][2]; +// } + +void M_MatrixCopy( const M_matrix4x4_t& in, M_matrix4x4_t& out ) +{ + // Assert( s_bMathlibInitialized ); + memcpy( out.Base(), in.Base(), sizeof( float ) * 4 * 4 ); +} +void M_RotateZMatrix(float radian, M_matrix4x4_t &resultMatrix) +{ + + resultMatrix[0][0] = cosf(radian); + resultMatrix[0][1] = sin(radian); + resultMatrix[0][2] = 0.0; + resultMatrix[1][0] =-sin(radian); + resultMatrix[1][1] = cos(radian); + resultMatrix[1][2] = 0.0; + resultMatrix[2][0] = 0.0; + resultMatrix[2][1] = 0.0; + resultMatrix[2][2] = 1.0; +} + +// !!! THIS SHIT DOESN'T WORK!! WHY? HAS I EVER? +void M_AngleAboutAxis(Vector &axis, float radianAngle, M_matrix4x4_t &result) +{ + float c = cosf(radianAngle); + float s = sinf(radianAngle); + float t = 1.0 - c; + // axis.normalize(); + + result[0][0] = t * axis[0] * axis[0] + c; + result[0][1] = t * axis[0] * axis[1] - s * axis[2]; + result[0][2] = t * axis[0] * axis[2] + s * axis[1]; + result[1][0] = t * axis[0] * axis[1] + s * axis[2]; + result[1][1] = t * axis[1] * axis[1] + c; + result[1][2] = t * axis[1] * axis[2] - s * axis[0]; + result[2][0] = t * axis[1] * axis[2] - s; + result[2][1] = t * axis[1] * axis[2] + s * axis[1]; + result[2][2] = t * axis[2] * axis[2] + c * axis[0]; + +} + + +void M_MatrixInvert( const M_matrix4x4_t& in, M_matrix4x4_t& out ) +{ + // Assert( s_bMathlibInitialized ); + if ( &in == &out ) + { + M_matrix4x4_t in2; + M_MatrixCopy( in, in2 ); + M_MatrixInvert( in2, out ); + return; + } + float tmp[3]; + + // I'm guessing this only works on a 3x4 orthonormal matrix + out[0][0] = in[0][0]; + out[1][0] = in[0][1]; + out[2][0] = in[0][2]; + + out[0][1] = in[1][0]; + out[1][1] = in[1][1]; + out[2][1] = in[1][2]; + + out[0][2] = in[2][0]; + out[1][2] = in[2][1]; + out[2][2] = in[2][2]; + + tmp[0] = in[3][0]; + tmp[1] = in[3][1]; + tmp[2] = in[3][2]; + + float v1[3], v2[3], v3[3]; + v1[0] = out[0][0]; + v1[1] = out[1][0]; + v1[2] = out[2][0]; + v2[0] = out[0][1]; + v2[1] = out[1][1]; + v2[2] = out[2][1]; + v3[0] = out[0][2]; + v3[1] = out[1][2]; + v3[2] = out[2][2]; + + out[3][0] = -DotProduct( tmp, v1 ); + out[3][1] = -DotProduct( tmp, v2 ); + out[3][2] = -DotProduct( tmp, v3 ); + + // Trivial case + // if (IS_IDENTITY(matrix)) + // return SbMatrix::identity(); + + // // Affine case... + // // SbMatrix affineAnswer; + // // if ( affine_inverse( SbMatrix(matrix), affineAnswer ) ) + // // return affineAnswer; + + // int index[4]; + // float d, invmat[4][4], temp; + // SbMatrix inverse = *this; + + // if(inverse.LUDecomposition(index, d)) { + + // invmat[0][0] = 1.0; + // invmat[0][1] = 0.0; + // invmat[0][2] = 0.0; + // invmat[0][3] = 0.0; + // inverse.LUBackSubstitution(index, invmat[0]); + // invmat[1][0] = 0.0; + // invmat[1][1] = 1.0; + // invmat[1][2] = 0.0; + // invmat[1][3] = 0.0; + // inverse.LUBackSubstitution(index, invmat[1]); + // invmat[2][0] = 0.0; + // invmat[2][1] = 0.0; + // invmat[2][2] = 1.0; + // invmat[2][3] = 0.0; + // inverse.LUBackSubstitution(index, invmat[2]); + // invmat[3][0] = 0.0; + // invmat[3][1] = 0.0; + // invmat[3][2] = 0.0; + // invmat[3][3] = 1.0; + // inverse.LUBackSubstitution(index, invmat[3]); + +// #define SWAP(i,j) \ + // temp = invmat[i][j]; \ + // invmat[i][j] = invmat[j][i]; \ + // invmat[j][i] = temp; + + // SWAP(1,0); + + // SWAP(2,0); + // SWAP(2,1); + + // SWAP(3,0); + // SWAP(3,1); + // SWAP(3,2); +// #undef SWAP + // } +} + +/* +================ +M_ConcatTransforms +================ +*/ +void M_ConcatTransforms (const M_matrix4x4_t &in1, const M_matrix4x4_t &in2, M_matrix4x4_t &out) +{ + + // Assert( s_bMathlibInitialized ); + // if ( &in1 == &out ) + // { + // matrix3x4_t in1b; + // MatrixCopy( in1, in1b ); + // ConcatTransforms( in1b, in2, out ); + // return; + // } + // if ( &in2 == &out ) + // { + // matrix3x4_t in2b; + // MatrixCopy( in2, in2b ); + // ConcatTransforms( in1, in2b, out ); + // return; + // } + +#define MULT(i,j) (in1[i][0]*in2[0][j] + \ + in1[i][1]*in2[1][j] + \ + in1[i][2]*in2[2][j] + \ + in1[i][3]*in2[3][j]) + + out[0][0] = MULT(0,0); + out[0][1] = MULT(0,1); + out[0][2] = MULT(0,2); + out[0][3] = MULT(0,3); + out[1][0] = MULT(1,0); + out[1][1] = MULT(1,1); + out[1][2] = MULT(1,2); + out[1][3] = MULT(1,3); + out[2][0] = MULT(2,0); + out[2][1] = MULT(2,1); + out[2][2] = MULT(2,2); + out[2][3] = MULT(2,3); + out[3][0] = MULT(3,0); + out[3][1] = MULT(3,1); + out[3][2] = MULT(3,2); + out[3][3] = MULT(3,3); + +#undef MULT + +} + +void M_AngleMatrix( RadianEuler const &angles, const Vector &position, M_matrix4x4_t& matrix ) +{ + // Assert( s_bMathlibInitialized ); + float sx, sy, sz, cx, cy, cz; + + + sx = sinf(angles[0]); + cx = cosf(angles[0]); + sy = sinf(angles[1]); + cy = cosf(angles[1]); + sz = sinf(angles[2]); + cz = cosf(angles[2]); + + // SinCos( angles[0], &sx, &cx ); // 2 + // SinCos( angles[1], &sy, &cy ); // 1 + // SinCos( angles[2], &sz, &cz ); // 0 + + M_matrix4x4_t mx, my, mz, temp1; + + // rotation about x + mx[1][1] = cx; + mx[1][2] = sx; + mx[2][1] = -sx; + mx[2][2] = cx; + + // rotation about y + my[0][0] = cy; + my[0][2] = -sy; + my[2][0] = sy; + my[2][2] = cy; + + // rotation about z + mz[0][0] = cz; + mz[0][1] = sz; + mz[1][0] = -sz; + mz[1][1] = cz; + + // z * y * x + M_ConcatTransforms(mx, my, temp1); + M_ConcatTransforms(temp1, mz, matrix); + + // put position in + matrix[3][0] = position.x; + matrix[3][1] = position.y; + matrix[3][2] = position.z; + +} + + +//----------------------------------------------------------------------------- +// Motion mapper functions +//----------------------------------------------------------------------------- +#define BONEAXIS 0 +#define BONEDIR 0 +#define BONESIDE 1 +#define BONEUP 2 +#define WORLDUP 2 +#define PRINTMAT(m) \ + printf("\n%f %f %f %f\n", m[0][0], m[0][1], m[0][2], m[0][3]); \ + printf("%f %f %f %f\n", m[1][0], m[1][1], m[1][2], m[1][3]); \ + printf("%f %f %f %f\n", m[2][0], m[2][1], m[2][2], m[2][3]); \ + printf("%f %f %f %f\n", m[3][0], m[3][1], m[3][2], m[3][3]); + +struct s_planeConstraint_t +{ + char jointNameString[1024]; + float floor; + int axis; + +}; + +struct s_iksolve_t +{ + char jointNameString[1024]; + int reverseSolve; + float extremityScale; + Vector limbRootOffsetScale; + int doRelativeLock; + char relativeLockNameString[1024]; + float relativeLockScale; + +}; + +struct s_jointScale_t +{ + char jointNameString[1024]; + float scale; +}; + +struct s_template_t +{ + char rootScaleJoint[1024]; + float rootScaleAmount; + int numIKSolves; + s_iksolve_t *ikSolves[128]; + int numJointScales; + s_jointScale_t *jointScales[128]; + int numPlaneConstraints; + s_planeConstraint_t *planeConstraints[128]; + float toeFloorZ; + int doSkeletonScale; + float skeletonScale; + +}; + + +//----------------------------------------------------------------------------- +// Load a template file into structure +//----------------------------------------------------------------------------- +s_template_t *New_Template() +{ + s_template_t *pTemplate = (s_template_t *)kalloc(1, sizeof(s_template_t)); + pTemplate->rootScaleAmount = 1.0; + pTemplate->numIKSolves = 0; + pTemplate->numJointScales = 0; + pTemplate->toeFloorZ = 2.802277; + pTemplate->numPlaneConstraints = 0; + pTemplate->doSkeletonScale = 0; + pTemplate->skeletonScale = 1.0; + return pTemplate; +} +s_iksolve_t *New_IKSolve() +{ + s_iksolve_t *pIKSolve = (s_iksolve_t *)kalloc(1, sizeof(s_iksolve_t)); + pIKSolve->reverseSolve = 0; + pIKSolve->extremityScale = 1.0; + pIKSolve->limbRootOffsetScale[0] = pIKSolve->limbRootOffsetScale[1] = pIKSolve->limbRootOffsetScale[2] = 0.0; + pIKSolve->doRelativeLock = 0; + pIKSolve->relativeLockScale = 1.0; + return pIKSolve; +} + +s_planeConstraint_t *New_planeConstraint(float floor) +{ + s_planeConstraint_t *pConstraint = (s_planeConstraint_t *)kalloc(1, sizeof(s_planeConstraint_t)); + pConstraint->floor = floor; + pConstraint->axis = 2; + + return pConstraint; +} + +void Set_DefaultTemplate(s_template_t *pTemplate) +{ + pTemplate->numJointScales = 0; + + strcpy(pTemplate->rootScaleJoint, "ValveBiped.Bip01_L_Foot"); + pTemplate->rootScaleAmount = 1.0; + + pTemplate->numIKSolves = 4; + pTemplate->ikSolves[0] = New_IKSolve(); + pTemplate->ikSolves[1] = New_IKSolve(); + pTemplate->ikSolves[2] = New_IKSolve(); + pTemplate->ikSolves[3] = New_IKSolve(); + + + pTemplate->numPlaneConstraints = 2; + pTemplate->planeConstraints[0] = New_planeConstraint(pTemplate->toeFloorZ); + strcpy(pTemplate->planeConstraints[0]->jointNameString, "ValveBiped.Bip01_L_Toe0"); + pTemplate->planeConstraints[1] = New_planeConstraint(pTemplate->toeFloorZ); + strcpy(pTemplate->planeConstraints[1]->jointNameString, "ValveBiped.Bip01_R_Toe0"); + + strcpy(pTemplate->ikSolves[0]->jointNameString, "ValveBiped.Bip01_L_Foot"); + pTemplate->ikSolves[0]->reverseSolve = 0; + pTemplate->ikSolves[0]->extremityScale = 1.0; + pTemplate->ikSolves[0]->limbRootOffsetScale[0] = 1.0; + pTemplate->ikSolves[0]->limbRootOffsetScale[1] = 1.0; + pTemplate->ikSolves[0]->limbRootOffsetScale[2] = 0.0; + + strcpy(pTemplate->ikSolves[1]->jointNameString, "ValveBiped.Bip01_R_Foot"); + pTemplate->ikSolves[1]->reverseSolve = 0; + pTemplate->ikSolves[1]->extremityScale = 1.0; + pTemplate->ikSolves[1]->limbRootOffsetScale[0] = 1.0; + pTemplate->ikSolves[1]->limbRootOffsetScale[1] = 1.0; + pTemplate->ikSolves[1]->limbRootOffsetScale[2] = 0.0; + + strcpy(pTemplate->ikSolves[2]->jointNameString, "ValveBiped.Bip01_R_Hand"); + pTemplate->ikSolves[2]->reverseSolve = 1; + pTemplate->ikSolves[2]->extremityScale = 1.0; + pTemplate->ikSolves[2]->limbRootOffsetScale[0] = 0.0; + pTemplate->ikSolves[2]->limbRootOffsetScale[1] = 0.0; + pTemplate->ikSolves[2]->limbRootOffsetScale[2] = 1.0; + + strcpy(pTemplate->ikSolves[3]->jointNameString, "ValveBiped.Bip01_L_Hand"); + pTemplate->ikSolves[3]->reverseSolve = 1; + pTemplate->ikSolves[3]->extremityScale = 1.0; + pTemplate->ikSolves[3]->limbRootOffsetScale[0] = 0.0; + pTemplate->ikSolves[3]->limbRootOffsetScale[1] = 0.0; + pTemplate->ikSolves[3]->limbRootOffsetScale[2] = 1.0; + // pTemplate->ikSolves[3]->doRelativeLock = 1; + // strcpy(pTemplate->ikSolves[3]->relativeLockNameString, "ValveBiped.Bip01_R_Hand"); + // pTemplate->ikSolves[3]->relativeLockScale = 1.0; + +} + +void split(char *str, char *sep, char **sp) +{ + char *r = strtok(str, sep); + while(r != NULL) + { + *sp = r; + sp++; + r = strtok(NULL, sep); + } + *sp = NULL; +} + + +int checkCommand(char *str, char *cmd, int numOptions, int numSplit) +{ + if(strcmp(str, cmd) == 0) + { + if(numOptions <= numSplit) + return 1; + else + { + printf("Error: Number or argument mismatch in template file cmd %s, requires %i, found %i\n", cmd, numOptions, numSplit); + return 0; + } + } + return 0; +} + +s_template_t *Load_Template(char *name ) +{ + + // Sanity check file and init + Assert(name); + + s_template_t *pTemplate = New_Template(); + + + // Open file + if (!OpenGlobalFile( name )) + return 0; + + + //March through lines + g_iLinecount = 0; + while(fgets( g_szLine, sizeof( g_szLine ), g_fpInput ) != NULL) + { + g_iLinecount++; + if(g_szLine[0] == '#') + continue; + + char *endP = strrchr(g_szLine, '\n'); + if(endP != NULL) + *endP = '\0'; + + + char *sp[128]; + char **spp = sp; + + char sep[] = " "; + split(g_szLine, sep, sp); + int numSplit = 0; + + while(*spp != NULL) + { + spp++; + numSplit++; + + } + if(numSplit < 1 || + *sp[0] == '\n') + continue; + + + // int numRead = sscanf( g_szLine, "%s %s %s", cmd, &option, &option2 ); + + // // Blank line + // if ((numRead == EOF) || (numRead == 0)) + // continue; + + // commands + char *cmd; + int numOptions = numSplit - 1; + + cmd = sp[0]; + if(checkCommand(cmd, "twoJointIKSolve", 1, numOptions)) + { + printf("\nCreating two joint IK solve %s\n", sp[1]); + pTemplate->ikSolves[pTemplate->numIKSolves] = New_IKSolve(); + strcpy(pTemplate->ikSolves[pTemplate->numIKSolves]->jointNameString, sp[1]); + pTemplate->numIKSolves++; + + } + else if(checkCommand(cmd, "oneJointPlaneConstraint", 1, numOptions)) + { + printf("\nCreating one joint plane constraint %s\n", sp[1]); + pTemplate->planeConstraints[pTemplate->numPlaneConstraints] = New_planeConstraint(pTemplate->toeFloorZ); + strcpy(pTemplate->planeConstraints[pTemplate->numPlaneConstraints]->jointNameString, sp[1]); + pTemplate->numPlaneConstraints++; + + } + else if(checkCommand(cmd, "reverseSolve", 1, numOptions)) + { + printf("reverseSolve: %s\n", sp[1]); + pTemplate->ikSolves[pTemplate->numIKSolves - 1]->reverseSolve = atoi(sp[1]); + } + else if(checkCommand(cmd, "extremityScale", 1, numOptions)) + { + printf("extremityScale: %s\n", sp[1]); + pTemplate->ikSolves[pTemplate->numIKSolves - 1]->extremityScale = atof(sp[1]); + } + else if(checkCommand(cmd, "limbRootOffsetScale", 3, numOptions)) + { + printf("limbRootOffsetScale: %s %s %s\n", sp[1], sp[2], sp[3]); + pTemplate->ikSolves[pTemplate->numIKSolves - 1]->limbRootOffsetScale[0] = atof(sp[1]); + pTemplate->ikSolves[pTemplate->numIKSolves - 1]->limbRootOffsetScale[1] = atof(sp[2]); + pTemplate->ikSolves[pTemplate->numIKSolves - 1]->limbRootOffsetScale[2] = atof(sp[3]); + } + else if(checkCommand(cmd, "toeFloorZ", 1, numOptions)) + { + printf("toeFloorZ: %s\n", sp[1]); + pTemplate->toeFloorZ = atof(sp[1]); + } + else if(checkCommand(cmd, "relativeLock", 2, numOptions)) + { + printf("relativeLock: %s\n", sp[1]); + pTemplate->ikSolves[pTemplate->numIKSolves - 1]->doRelativeLock = 1; + strcpy(pTemplate->ikSolves[pTemplate->numIKSolves - 1]->relativeLockNameString, sp[1]); + pTemplate->ikSolves[pTemplate->numIKSolves - 1]->relativeLockScale = atof(sp[2]); + + } + else if(checkCommand(cmd, "rootScaleJoint", 1, numOptions)) + { + printf("\nrootScaleJoint: %s\n", sp[1]); + strcpy(pTemplate->rootScaleJoint, sp[1]); + } + else if(checkCommand(cmd, "rootScaleAmount", 1, numOptions)) + { + printf("rootScaleAmount: %s\n", sp[1]); + pTemplate->rootScaleAmount = atof(sp[1]); + } + else if(checkCommand(cmd, "jointScale", 2, numOptions)) + { + printf("\nCreating joint scale %s of %s\n", sp[1], sp[2]); + pTemplate->jointScales[pTemplate->numJointScales] = (s_jointScale_t *)kalloc(1, sizeof(s_jointScale_t)); + strcpy(pTemplate->jointScales[pTemplate->numJointScales]->jointNameString, sp[1]); + pTemplate->jointScales[pTemplate->numJointScales]->scale = atof(sp[2]); + pTemplate->numJointScales++; + } + else if(checkCommand(cmd, "skeletonScale", 2, numOptions)) + { + printf("\nCreating skeleton scale of %s\n", sp[1]); + pTemplate->doSkeletonScale = 1; + pTemplate->skeletonScale = atof(sp[1]); + } + else + { + MdlWarning("unknown studio command\n" ); + } + } + fclose( g_fpInput ); + return pTemplate; +} + +//----------------------------------------------------------------------------- +// get node index from node string name +//----------------------------------------------------------------------------- +int GetNodeIndex(s_source_t *psource, char *nodeName) +{ + for(int i = 0; i < psource->numbones; i++) + { + if(strcmp(nodeName, psource->localBone[i].name) == 0) + { + return i; + } + } + return -1; +} + +//----------------------------------------------------------------------------- +// get node index from node string name +//----------------------------------------------------------------------------- +void GetNodePath(s_source_t *psource, int startIndex, int endIndex, int *path) +{ + *path = endIndex; + + s_node_t *nodes; + nodes = psource->localBone; + while(*path != startIndex) + { + int parent = nodes[*path].parent; + path++; + *path = parent; + } + path++; + *path = -1; +} + +void SumBonePathTranslations(int *indexPath, s_bone_t *boneArray, Vector &resultVector, int rootOffset = 0) +{ + + // walk the path + int *pathPtr = indexPath; + // M_matrix4x4_t matrixCum; + + // find length of path + int length = 0; + while(*pathPtr != -1) + { + length++; + pathPtr++; + } + + int l = length - (1 + rootOffset); + + resultVector[0] = 0.0; + resultVector[1] = 0.0; + resultVector[2] = 0.0; + + for(int i = l; i > -1; i--) + { + s_bone_t *thisBone = boneArray + indexPath[i]; + resultVector += thisBone->pos; + } +} + +void CatBonePath(int *indexPath, s_bone_t *boneArray, M_matrix4x4_t &resultMatrix, int rootOffset = 0) +{ + + // walk the path + int *pathPtr = indexPath; + // M_matrix4x4_t matrixCum; + + // find length of path + int length = 0; + while(*pathPtr != -1) + { + length++; + pathPtr++; + } + + int l = length - (1 + rootOffset); + + for(int i = l; i > -1; i--) + { + s_bone_t *thisBone = boneArray + indexPath[i]; + // printf("bone index: %i %i\n", i, indexPath[i]); + // printf("pos: %f %f %f, rot: %f %f %f\n", thisBone->pos.x, thisBone->pos.y, thisBone->pos.z, thisBone->rot.x, thisBone->rot.y, thisBone->rot.z); + M_matrix4x4_t thisMatrix; + M_AngleMatrix(thisBone->rot, thisBone->pos, thisMatrix); + // PRINTMAT(thisMatrix) + M_matrix4x4_t tempCum; + M_MatrixCopy(resultMatrix, tempCum); + M_ConcatTransforms(thisMatrix, tempCum, resultMatrix); + } + // PRINTMAT(matrixCum); + // M_MatrixAngles(matrixCum, resultBone.rot, resultBone.pos); + + // printf("pos: %f %f %f, rot: %f %f %f\n", resultBone.pos.x,resultBone.pos.y, resultBone.pos.z, RAD2DEG(resultBone.rot.x),RAD2DEG(resultBone.rot.y),RAD2DEG(resultBone.rot.z)); + +} +// int ConformSources(s_source_t *pSource, s_source_t *pTarget) +// { + // if(pSource->numbones != *pTarget->numbones) + // { + // printf("ERROR: The number of bones in the target file must match the source file."); + // return 1; + // } + // if(pSource->numframes != pTarget->numframes) + // { + // printf("Note: Source and target frame lengths do not match"); + // for(int t = 0; t < pTarget->numframes; t++) + // { + // free(pTarget->rawanim[t]); + // } + // pTarget->numframes = pSource->numframes; + // int size = pTarget->numbones * sizeof( s_bone_t ); + // for(t = 0; t < pTarget->numframes; t++) + // { + // pTarget->rawanim[t] = (s_bone_t *) kalloc(1, size); + // memcpy((void *) pSource->rawanim[t], (void *) pTarget->rawanim[t], size + // } + // } + // pTarget->startframe = pSource->startframe; + // pTarget->endframe = pSource->endframe; + + + + +void ScaleJointsFrame(s_source_t *pSkeleton, s_jointScale_t *jointScale, int t) +{ + int numBones = pSkeleton->numbones; + + for(int i = 0; i < numBones; i++) + { + s_node_t pNode = pSkeleton->localBone[i]; + s_bone_t *pSkelBone = &pSkeleton->rawanim[t][i]; + if(strcmp(jointScale->jointNameString, pNode.name) == 0) + { + // printf("Scaling joint %s\n", pNode.name); + pSkelBone->pos = pSkelBone->pos * jointScale->scale; + } + + } +} +void ScaleJoints(s_source_t *pSkeleton, s_jointScale_t *jointScale) +{ + int numFrames = pSkeleton->numframes; + for(int t = 0; t < numFrames; t++) + { + ScaleJointsFrame(pSkeleton, jointScale, t); + } +} + +void ScaleSkeletonFrame(s_source_t *pSkeleton, float scale, int t) +{ + int numBones = pSkeleton->numbones; + + for(int i = 0; i < numBones; i++) + { + s_bone_t *pSkelBone = &pSkeleton->rawanim[t][i]; + pSkelBone->pos = pSkelBone->pos * scale; + + } +} +void ScaleSkeleton(s_source_t *pSkeleton, float scale) +{ + int numFrames = pSkeleton->numframes; + for(int t = 0; t < numFrames; t++) + { + ScaleSkeletonFrame(pSkeleton, scale, t); + } +} + +void CombineSkeletonAnimationFrame(s_source_t *pSkeleton, s_source_t *pAnimation, s_bone_t **ppAnim, int t) +{ + int numBones = pAnimation->numbones; + int size = numBones * sizeof( s_bone_t ); + ppAnim[t] = (s_bone_t *) kalloc(1, size); + for(int i = 0; i < numBones; i++) + { + s_node_t pNode = pAnimation->localBone[i]; + s_bone_t pAnimBone = pAnimation->rawanim[t][i]; + + if(pNode.parent > -1) + { + if ( i < pSkeleton->numbones ) + { + s_bone_t pSkelBone = pSkeleton->rawanim[0][i]; + ppAnim[t][i].pos = pSkelBone.pos; + } + else + { + if ( !g_bGaveMissingBoneWarning ) + { + g_bGaveMissingBoneWarning = true; + Warning( "Warning: Target skeleton has less bones than source animation. Reverting to source data for extra bones.\n" ); + } + + ppAnim[t][i].pos = pAnimBone.pos; + } + } + else + { + ppAnim[t][i].pos = pAnimBone.pos; + } + + ppAnim[t][i].rot = pAnimBone.rot; + } +} +void CombineSkeletonAnimation(s_source_t *pSkeleton, s_source_t *pAnimation, s_bone_t **ppAnim) +{ + int numFrames = pAnimation->numframes; + for(int t = 0; t < numFrames; t++) + { + CombineSkeletonAnimationFrame(pSkeleton, pAnimation, ppAnim, t); + } +} + + +//-------------------------------------------------------------------- +// MotionMap +//-------------------------------------------------------------------- +s_source_t *MotionMap( s_source_t *pSource, s_source_t *pTarget, s_template_t *pTemplate ) +{ + + // scale skeleton + if(pTemplate->doSkeletonScale) + { + ScaleSkeleton(pTarget, pTemplate->skeletonScale); + } + + // scale joints + for(int j = 0; j < pTemplate->numJointScales; j++) + { + s_jointScale_t *pJointScale = pTemplate->jointScales[j]; + ScaleJoints(pTarget, pJointScale); + } + + + // root stuff + char rootString[128] = "ValveBiped.Bip01"; + + // !!! PARAMETER + int rootIndex = GetNodeIndex(pSource, rootString); + int rootScaleIndex = GetNodeIndex(pSource, pTemplate->rootScaleJoint); + int rootScalePath[512]; + if(rootScaleIndex > -1) + { + GetNodePath(pSource, rootIndex, rootScaleIndex, rootScalePath); + } + else + { + printf("Error: Can't find node\n"); + exit(0); + } + float rootScaleLengthSrc = pSource->rawanim[0][rootScaleIndex].pos[BONEDIR]; + float rootScaleParentLengthSrc = pSource->rawanim[0][rootScalePath[1]].pos[BONEDIR]; + float rootScaleSrc = rootScaleLengthSrc + rootScaleParentLengthSrc; + float rootScaleLengthTgt = pTarget->rawanim[0][rootScaleIndex].pos[BONEDIR]; + float rootScaleParentLengthTgt = pTarget->rawanim[0][rootScalePath[1]].pos[BONEDIR]; + float rootScaleTgt = rootScaleLengthTgt + rootScaleParentLengthTgt; + float rootScaleFactor = rootScaleTgt / rootScaleSrc; + + if(g_verbose) + printf("Root Scale Factor: %f\n", rootScaleFactor); + + + // root scale origin + float toeFloorZ = pTemplate->toeFloorZ; + Vector rootScaleOrigin = pSource->rawanim[0][rootIndex].pos; + rootScaleOrigin[2] = toeFloorZ; + + + // setup workspace + s_bone_t *combinedRefAnimation[MAXSTUDIOANIMFRAMES]; + s_bone_t *combinedAnimation[MAXSTUDIOANIMFRAMES]; + s_bone_t *sourceAnimation[MAXSTUDIOANIMFRAMES]; + CombineSkeletonAnimation(pTarget, pSource, combinedAnimation); + CombineSkeletonAnimation(pTarget, pSource, combinedRefAnimation); + + + // do source and target sanity checking + int sourceNumFrames = pSource->numframes; + + + // iterate through limb solves + for(int t = 0; t < sourceNumFrames; t++) + { + // setup pTarget for skeleton comparison + pTarget->rawanim[t] = combinedRefAnimation[t]; + + printf("Note: Processing frame: %i\n", t); + for(int ii = 0; ii < pTemplate->numIKSolves; ii++) + { + s_iksolve_t *thisSolve = pTemplate->ikSolves[ii]; + + char *thisJointNameString = thisSolve->jointNameString; + int thisJointIndex = GetNodeIndex(pSource, thisJointNameString); + + // init paths to feet + int thisJointPathInRoot[512]; + + // get paths to feet + if(thisJointIndex > -1) + { + GetNodePath(pSource, rootIndex, thisJointIndex, thisJointPathInRoot); + } + else + { + printf("Error: Can't find node: %s\n" , thisJointNameString); + exit(0); + } + + // leg "root" or thigh pointers + //int gParentIndex = thisJointPathInRoot[2]; + int *gParentPath = thisJointPathInRoot + 2; + + //---------------------------------------------------------------- + // get limb lengths + //---------------------------------------------------------------- + float thisJointLengthSrc = pSource->rawanim[0][thisJointIndex].pos[BONEDIR]; + float parentJointLengthSrc = pSource->rawanim[0][thisJointPathInRoot[1]].pos[BONEDIR]; + + float thisLimbLengthSrc = thisJointLengthSrc + parentJointLengthSrc; + + float thisJointLengthTgt = pTarget->rawanim[0][thisJointIndex].pos[BONEDIR]; + float parentJointLengthTgt = pTarget->rawanim[0][thisJointPathInRoot[1]].pos[BONEDIR]; + + float thisLimbLengthTgt = thisJointLengthTgt + parentJointLengthTgt; + + // Factor leg length delta + float thisLimbLength = thisLimbLengthSrc - thisLimbLengthTgt; + float thisLimbLengthFactor = thisLimbLengthTgt / thisLimbLengthSrc; + + if(g_verbose) + printf("limb length %s: %i: %f, factor %f\n", thisJointNameString, thisJointIndex, thisLimbLength, thisLimbLengthFactor); + + // calculate joint grandparent offset + // Note: because there's no reference pose this doesn't take rotation into account. + // This only works because of the assumption that joint translations aren't animated. + M_matrix4x4_t gParentGlobalMatSrc, gParentGlobalMatTgt; + Vector gParentGlobalSrc, gParentGlobalTgt; + + // SumBonePathTranslations(gParentPath, pSource->rawanim[t], gParentGlobalSrc, 1); + // SumBonePathTranslations(gParentPath, pTarget->rawanim[t], gParentGlobalTgt, 1); + + // get root path to source parent + CatBonePath(gParentPath, pSource->rawanim[t], gParentGlobalMatSrc, 1); + // check against reference animation + CatBonePath(gParentPath, pTarget->rawanim[t], gParentGlobalMatTgt, 1); + + gParentGlobalSrc[0] = gParentGlobalMatSrc[3][0]; + gParentGlobalSrc[1] = gParentGlobalMatSrc[3][1]; + gParentGlobalSrc[2] = gParentGlobalMatSrc[3][2]; + + gParentGlobalTgt[0] = gParentGlobalMatTgt[3][0]; + gParentGlobalTgt[1] = gParentGlobalMatTgt[3][1]; + gParentGlobalTgt[2] = gParentGlobalMatTgt[3][2]; + + + Vector gParentDelta(gParentGlobalTgt - gParentGlobalSrc); + + if(g_verbose) + printf("Grand parent delta: %f %f %f\n", gParentDelta[0], gParentDelta[1], gParentDelta[2]); + + gParentDelta *= thisSolve->limbRootOffsetScale; + + + //---------------------------------------------------------------- + // time takes effect here + // above waste is unavoidable? + //---------------------------------------------------------------- + M_matrix4x4_t rootMat; + M_AngleMatrix(pSource->rawanim[t][rootIndex].rot, pSource->rawanim[t][rootIndex].pos, rootMat); + + + // OK, time to get it together + // 1) scale foot by legLengthFactor in the non-translated thigh space + // 2) translate foot by legRootDelta in the space of the root + // do we leave everything in the space of the root then? PROBABLY!! + + M_matrix4x4_t thisJointMat, parentJointMat, thisJointInGParentMat; + M_AngleMatrix(pSource->rawanim[t][thisJointPathInRoot[0]].rot, pSource->rawanim[t][thisJointPathInRoot[0]].pos, thisJointMat); + M_AngleMatrix(pSource->rawanim[t][thisJointPathInRoot[1]].rot, pSource->rawanim[t][thisJointPathInRoot[1]].pos, parentJointMat); + M_ConcatTransforms(thisJointMat, parentJointMat, thisJointInGParentMat); + + if(!thisSolve->doRelativeLock) + { + // scale around grand parent + float effectiveScaleFactor = ((thisLimbLengthFactor - 1.0) * thisSolve->extremityScale ) + 1.0; + thisJointInGParentMat[3][0] *= effectiveScaleFactor; + thisJointInGParentMat[3][1] *= effectiveScaleFactor; + thisJointInGParentMat[3][2] *= effectiveScaleFactor; + } + + // adjust into source root space + M_matrix4x4_t gParentInRootMat, thisJointInRootMat; + CatBonePath(gParentPath, pSource->rawanim[t], gParentInRootMat, 1); + M_ConcatTransforms(thisJointInGParentMat, gParentInRootMat, thisJointInRootMat); + + if(!thisSolve->doRelativeLock) + { + // adjust by difference of local root + thisJointInRootMat[3][0] += gParentDelta[0]; + thisJointInRootMat[3][1] += gParentDelta[1]; + thisJointInRootMat[3][2] += gParentDelta[2]; + } + else + { + char *relativeJointNameString = thisSolve->relativeLockNameString; + int relativeJointIndex = GetNodeIndex(pSource, relativeJointNameString); + + // init paths to feet + int relativeJointPathInRoot[512]; + + // get paths to feet + if(relativeJointIndex > -1) + { + GetNodePath(pSource, rootIndex, relativeJointIndex, relativeJointPathInRoot); + } + else + { + printf("Error: Can't find node: %s\n" , relativeJointNameString); + exit(0); + } + // get the source relative joint + M_matrix4x4_t relativeJointInRootMatSrc, relativeJointInRootMatSrcInverse, thisJointInRelativeSrcMat; + CatBonePath(relativeJointPathInRoot, pSource->rawanim[t], relativeJointInRootMatSrc, 1); + M_MatrixInvert(relativeJointInRootMatSrc, relativeJointInRootMatSrcInverse); + M_ConcatTransforms(thisJointInRootMat, relativeJointInRootMatSrcInverse, thisJointInRelativeSrcMat); + if(thisSolve->relativeLockScale != 1.0) + { + thisJointInRelativeSrcMat[3][0] *= thisSolve->relativeLockScale; + thisJointInRelativeSrcMat[3][1] *= thisSolve->relativeLockScale; + thisJointInRelativeSrcMat[3][2] *= thisSolve->relativeLockScale; + } + + // swap momentarily to get new destination + // NOTE: the relative lock must have already been solved + sourceAnimation[t] = pSource->rawanim[t]; + pSource->rawanim[t] = combinedAnimation[t]; + + // get new relative location + M_matrix4x4_t relativeJointInRootMatTgt; + CatBonePath(relativeJointPathInRoot, pSource->rawanim[t], relativeJointInRootMatTgt, 1); + M_ConcatTransforms(thisJointInRelativeSrcMat, relativeJointInRootMatTgt, thisJointInRootMat); + + // swap back just for cleanliness + // a little overkill as it's just swapped + // just leaving it here for clarity + combinedAnimation[t] = pSource->rawanim[t]; + pSource->rawanim[t] = sourceAnimation[t]; + + } + + //---------------------------------------------------------------- + // swap animation + //---------------------------------------------------------------- + sourceAnimation[t] = pSource->rawanim[t]; + pSource->rawanim[t] = combinedAnimation[t]; + + //---------------------------------------------------------------- + // make thigh data global based on new skeleton + //---------------------------------------------------------------- + // get thigh in global space + M_matrix4x4_t gParentInTgtRootMat, ggParentInTgtRootMat; + // int *gParentPath = thisJointPathInRoot + 2; + CatBonePath(gParentPath, pSource->rawanim[t], gParentInTgtRootMat, 1); + CatBonePath(gParentPath+1, pSource->rawanim[t], ggParentInTgtRootMat, 1); + + + //---------------------------------------------------------------- + // Calculate IK for legs + //---------------------------------------------------------------- + float parentJointLength = pSource->rawanim[t][*(thisJointPathInRoot + 1)].pos[BONEDIR]; + float thisJointLength = pSource->rawanim[t][thisJointIndex].pos[BONEDIR]; + + Vector thisLimbHypot; + thisLimbHypot[0] = thisJointInRootMat[3][0] - gParentInTgtRootMat[3][0]; + thisLimbHypot[1] = thisJointInRootMat[3][1] - gParentInTgtRootMat[3][1]; + thisLimbHypot[2] = thisJointInRootMat[3][2] - gParentInTgtRootMat[3][2]; + + float thisLimbHypotLength = thisLimbHypot.Length(); + + // law of cosines! + float gParentCos = (thisLimbHypotLength*thisLimbHypotLength + parentJointLength*parentJointLength - thisJointLength*thisJointLength) / (2*parentJointLength*thisLimbHypotLength); + float parentCos = (parentJointLength*parentJointLength + thisJointLength*thisJointLength - thisLimbHypotLength*thisLimbHypotLength) / (2*parentJointLength*thisJointLength); + + VectorNormalize(thisLimbHypot); + + Vector thisLimbHypotUnit = thisLimbHypot; + + M_matrix4x4_t gParentJointIKMat; + Vector gParentJointIKRot, gParentJointIKOrth; + + gParentJointIKRot[0] = gParentInTgtRootMat[BONEUP][0]; + gParentJointIKRot[1] = gParentInTgtRootMat[BONEUP][1]; + gParentJointIKRot[2] = gParentInTgtRootMat[BONEUP][2]; + + VectorNormalize(gParentJointIKRot); + gParentJointIKOrth = gParentJointIKRot.Cross(thisLimbHypotUnit); + VectorNormalize(gParentJointIKOrth); + gParentJointIKRot = thisLimbHypotUnit.Cross(gParentJointIKOrth); + VectorNormalize(gParentJointIKRot); + + M_MatrixCopy(gParentInTgtRootMat, gParentJointIKMat); + + gParentJointIKMat[0][0] = thisLimbHypotUnit[0]; + gParentJointIKMat[0][1] = thisLimbHypotUnit[1]; + gParentJointIKMat[0][2] = thisLimbHypotUnit[2]; + + gParentJointIKMat[1][0] = gParentJointIKOrth[0]; + gParentJointIKMat[1][1] = gParentJointIKOrth[1]; + gParentJointIKMat[1][2] = gParentJointIKOrth[2]; + + gParentJointIKMat[2][0] = gParentJointIKRot[0]; + gParentJointIKMat[2][1] = gParentJointIKRot[1]; + gParentJointIKMat[2][2] = gParentJointIKRot[2]; + + + M_matrix4x4_t gParentJointIKRotMat, gParentJointResultMat; + float gParentDeg; + if(thisSolve->reverseSolve) + { + gParentDeg = acos(gParentCos); + } + else + { + gParentDeg = -acos(gParentCos); + } + + // sanity check limb length + if(thisLimbHypotLength < thisLimbLengthTgt) + { + M_RotateZMatrix(gParentDeg, gParentJointIKRotMat); + } + + M_ConcatTransforms(gParentJointIKRotMat, gParentJointIKMat, gParentJointResultMat); + + M_matrix4x4_t parentJointIKRotMat; + //!!! shouldn't need the 180 degree addition, something in the law of cosines!!! + float parentDeg; + if(thisSolve->reverseSolve) + { + parentDeg = acos(parentCos)+M_PI; + } + else + { + parentDeg = -acos(parentCos)+M_PI; + } + + // sanity check limb length + if(thisLimbHypotLength < thisLimbLengthTgt) + { + M_RotateZMatrix(parentDeg, parentJointIKRotMat); + } + + + // Thighs + M_matrix4x4_t ggParentInTgtRootMatInverse, gParentJointLocalMat; + M_MatrixInvert(ggParentInTgtRootMat, ggParentInTgtRootMatInverse); + M_ConcatTransforms(gParentJointResultMat, ggParentInTgtRootMatInverse, gParentJointLocalMat); + + s_bone_t resultBone; + + // temp test stuff + // M_MatrixAngles(thisJointInRootMat, resultBone.rot, resultBone.pos); + // pSource->rawanim[t][thisJointIndex].rot = resultBone.rot; + // pSource->rawanim[t][thisJointIndex].pos = resultBone.pos; + + // M_MatrixAngles(gParentInTgtRootMat, resultBone.rot, resultBone.pos); + // pSource->rawanim[t][gParentIndex].rot = resultBone.rot; + // pSource->rawanim[t][gParentIndex].pos = resultBone.pos; + + + M_MatrixAngles(gParentJointLocalMat, resultBone.rot, resultBone.pos); + pSource->rawanim[t][*gParentPath].pos = resultBone.pos; + pSource->rawanim[t][*gParentPath].rot = resultBone.rot; + + M_MatrixAngles(parentJointIKRotMat, resultBone.rot, resultBone.pos); + pSource->rawanim[t][*(thisJointPathInRoot+1)].rot = resultBone.rot; + + M_matrix4x4_t parentJointGlobalMat, parentJointGlobalMatInverse, thisJointLocalMat; + CatBonePath(thisJointPathInRoot+1, pSource->rawanim[t], parentJointGlobalMat, 1); + + + M_MatrixInvert(parentJointGlobalMat, parentJointGlobalMatInverse); + M_ConcatTransforms(thisJointInRootMat, parentJointGlobalMatInverse, thisJointLocalMat); + + M_MatrixAngles(thisJointLocalMat, resultBone.rot, resultBone.pos); + pSource->rawanim[t][thisJointIndex].rot = resultBone.rot; + + + // swap animation back for next solve + combinedAnimation[t] = pSource->rawanim[t]; + pSource->rawanim[t] = sourceAnimation[t]; + + } + // swap animation + sourceAnimation[t] = pSource->rawanim[t]; + pSource->rawanim[t] = combinedAnimation[t]; + + //---------------------------------------------------------------- + // adjust root + //---------------------------------------------------------------- + Vector originBonePos = pSource->rawanim[t][rootIndex].pos; + Vector rootInScaleOrigin = originBonePos - rootScaleOrigin; + float effectiveRootScale = ((rootScaleFactor - 1.0) * pTemplate->rootScaleAmount) + 1.0; + Vector scaledRoot = rootInScaleOrigin * effectiveRootScale; + pSource->rawanim[t][rootIndex].pos = rootScaleOrigin + scaledRoot; + + //------------------------------------------------------------ + // plane constraints + //------------------------------------------------------------ + for(int ii = 0; ii < pTemplate->numPlaneConstraints; ii++) + { + s_planeConstraint_t *thisSolve = pTemplate->planeConstraints[ii]; + + char *thisJointNameString = thisSolve->jointNameString; + if(g_verbose) + printf("Executing plane constraint: %s\n", thisJointNameString); + + int thisJointIndex = GetNodeIndex(pSource, thisJointNameString); + + // init paths to feet + int thisJointPath[512]; + + // get paths to feet + if(thisJointIndex > -1) + { + GetNodePath(pSource, -1, thisJointIndex, thisJointPath); + } + else + { + printf("Error: Can't find node: %s\n" , thisJointNameString); + exit(0); + } + int parentIndex = thisJointPath[1]; + int *parentPath = thisJointPath + 1; + + M_matrix4x4_t thisJointGlobalMat, parentJointGlobalMat, gParentJointGlobalMat, gParentJointGlobalMatInverse; + CatBonePath(thisJointPath, pSource->rawanim[t], thisJointGlobalMat, 0); + CatBonePath(parentPath, pSource->rawanim[t], parentJointGlobalMat, 0); + CatBonePath(parentPath+1, pSource->rawanim[t], gParentJointGlobalMat, 0); + M_MatrixInvert(gParentJointGlobalMat, gParentJointGlobalMatInverse); + + if(thisJointGlobalMat[3][thisSolve->axis] < thisSolve->floor) + { + // printf("-- broken plane: %f\n", thisJointGlobalMat[3][thisSolve->axis]); + if(parentJointGlobalMat[3][thisSolve->axis] < thisSolve->floor) + { + printf("Error: Constraint parent has broken the plane, this frame's plane constraint unsolvable!\n"); + } + else + { + Vector parentJointAtPlane(parentJointGlobalMat[3][0], parentJointGlobalMat[3][1], parentJointGlobalMat[3][2]); + Vector parentPos(parentJointGlobalMat[3][0], parentJointGlobalMat[3][1], parentJointGlobalMat[3][2]); + Vector thisJointAtPlane(thisJointGlobalMat[3][0], thisJointGlobalMat[3][1], thisJointGlobalMat[3][2]); + Vector thisJointPos(thisJointGlobalMat[3][0], thisJointGlobalMat[3][1], thisJointGlobalMat[3][2]); + + thisJointAtPlane[thisSolve->axis] = thisSolve->floor; + parentJointAtPlane[thisSolve->axis] = thisSolve->floor; + + float thisJointLength = pSource->rawanim[t][thisJointIndex].pos[BONEAXIS]; + float parentLengthToPlane = parentPos[thisSolve->axis] - thisSolve->floor; + float adjacent = sqrtf((thisJointLength * thisJointLength) - (parentLengthToPlane * parentLengthToPlane)); + Vector parentDirection = thisJointAtPlane - parentJointAtPlane; + VectorNormalize(parentDirection); + + Vector newJointPos = parentJointAtPlane + (parentDirection * adjacent); + + Vector newParentDir = newJointPos - parentPos; + Vector parentUp(parentJointGlobalMat[BONEUP][0], parentJointGlobalMat[BONEUP][1], parentJointGlobalMat[BONEUP][2]); + + VectorNormalize(newParentDir); + VectorNormalize(parentUp); + // Vector parentSide = newParentDir.Cross(parentUp); + Vector parentSide = parentUp.Cross(newParentDir); + VectorNormalize(parentSide); + parentUp = newParentDir.Cross(parentSide); + // parentUp = parentSide.Cross(newParentDir); + VectorNormalize(parentUp); + parentJointGlobalMat[BONEDIR][0] = newParentDir[0]; + parentJointGlobalMat[BONEDIR][1] = newParentDir[1]; + parentJointGlobalMat[BONEDIR][2] = newParentDir[2]; + parentJointGlobalMat[BONEUP][0] = parentUp[0]; + parentJointGlobalMat[BONEUP][1] = parentUp[1]; + parentJointGlobalMat[BONEUP][2] = parentUp[2]; + parentJointGlobalMat[BONESIDE][0] = parentSide[0]; + parentJointGlobalMat[BONESIDE][1] = parentSide[1]; + parentJointGlobalMat[BONESIDE][2] = parentSide[2]; + + + M_matrix4x4_t newParentJointMat; + + M_ConcatTransforms(parentJointGlobalMat, gParentJointGlobalMatInverse, newParentJointMat); + + s_bone_t resultBone; + M_MatrixAngles(newParentJointMat, resultBone.rot, resultBone.pos); + pSource->rawanim[t][parentIndex].rot = resultBone.rot; + } + } + } + + // swap animation back for next solve + combinedAnimation[t] = pSource->rawanim[t]; + pSource->rawanim[t] = sourceAnimation[t]; + } + for(int t = 0; t < sourceNumFrames; t++) + { + pTarget->rawanim[t] = combinedAnimation[t]; + } + pTarget->numframes = sourceNumFrames; + + + + + +#if 0 + // Process motion mapping into out and return that + s_source_t *out = new s_source_t; + + return out; +#else + // Just returns the start animation, to test the Save_SMD API. + return pTarget; +#endif +} + +char templates[] = +"\n\ +#\n\ +# default template file is analogus to not specifying a template file at all\n\ +#\n\ +\n\ +rootScaleJoint ValveBiped.Bip01_L_Foot\n\ +rootScaleAmount 1.0\n\ +toeFloorZ 2.7777\n\ +\n\ +twoJointIKSolve ValveBiped.Bip01_L_Foot\n\ +reverseSolve 0\n\ +extremityScale 1.0\n\ +limbRootOffsetScale 1.0 1.0 0.0\n\ +\n\ +twoJointIKSolve ValveBiped.Bip01_R_Foot\n\ +reverseSolve 0\n\ +extremityScale 1.0\n\ +limbRootOffsetScale 1.0 1.0 0.0\n\ +\n\ +oneJointPlaneConstraint ValveBiped.Bip01_L_Toe0\n\ +\n\ +oneJointPlaneConstraint ValveBiped.Bip01_R_Toe0\n\ +\n\ +twoJointIKSolve ValveBiped.Bip01_R_Hand\n\ +reverseSolve 1\n\ +extremityScale 1.0\n\ +limbRootOffsetScale 0.0 0.0 1.0\n\ +\n\ +twoJointIKSolve ValveBiped.Bip01_L_Hand\n\ +reverseSolve 1\n\ +extremityScale 1.0\n\ +limbRootOffsetScale 0.0 0.0 1.0\n\ +\n\ +"; + + +void UsageAndExit() +{ + MdlError( "usage: motionmapper [-quiet] [-verbose] [-templateFile filename] [-printTemplates] sourceanim.smd targetskeleton.smd output.smd\n\ +\tsourceanim: should contain ref pose and animation data\n\ +\ttargetsekeleton: should contain new ref pose, animation data ignored/can be absent\n\ +\toutput: animation from source mapped onto target skeleton (contains new ref pose)\n\ +\t-templateFile filename : specifies a template file for guiding the mapping of motion\n\ +\t-printTemplate: Causes motionmapper to output the contents of an example template file, which can be used in conjunction with the -templateFile argument to create various motion effects.\n\ +\n"); +} + +void PrintHeader() +{ + vprint( 0, "Valve Software - motionmapper.exe ((c) Valve Coroporation %s)\n", __DATE__ ); + vprint( 0, "--- Maps motion from one animation/skeleton onto another skeleton ---\n" ); +} + + + +/* +============== +main +============== +*/ +int main (int argc, char **argv) +{ + int i; + + int useTemplate = 0; + char templateFileName[1024]; + + // Header + PrintHeader(); + + // Init command line stuff + CommandLine()->CreateCmdLine( argc, argv ); + InstallSpewFunction(); + + // init math stuff + MathLib_Init( 2.2f, 2.2f, 0.0f, 2.0f, false, false, false, false ); + g_currentscale = g_defaultscale = 1.0; + g_defaultrotation = RadianEuler( 0, 0, M_PI / 2 ); + + // No args? + if (argc == 1) + { + UsageAndExit(); + } + + // Init variable + g_quiet = false; + + // list template hooey + CUtlVector< CUtlSymbol > filenames; + + // Get args + for (i = 1; i < argc; i++) + { + // Switches + if (argv[i][0] == '-') + { + if (!stricmp(argv[i], "-allowdebug")) + { + // Ignore, used by interface system to catch debug builds checked into release tree + continue; + } + + if (!stricmp(argv[i], "-quiet")) + { + g_quiet = true; + g_verbose = false; + continue; + } + + if (!stricmp(argv[i], "-verbose")) + { + g_quiet = false; + g_verbose = true; + continue; + } + if (!stricmp(argv[i], "-printTemplate")) + { + printf("%s\n", templates); + exit(0); + + } + if (!stricmp(argv[i], "-templateFile")) + { + if(i + 1 < argc) + { + strcpy( templateFileName, argv[i+1]); + useTemplate = 1; + printf("Note: %s passed as template file", templateFileName); + } + else + { + printf("Error: -templateFile requires an argument, none found!"); + UsageAndExit(); + + } + i++; + continue; + } + } + else + { + // more template stuff + CUtlSymbol sym = argv[ i ]; + filenames.AddToTail( sym ); + } + } + + // Enough file args? + if ( filenames.Count() != 3 ) + { + // misformed arguments + // otherwise generating unintended results + printf("Error: 3 file arguments required, %i found!", filenames.Count()); + UsageAndExit(); + } + + // Filename arg indexes + int sourceanim = 0; + int targetskel = 1; + int outputanim = 2; + + // Copy arg string to global variable + strcpy( g_outfile, filenames[ outputanim ].String() ); + + // Init filesystem hooey + CmdLib_InitFileSystem( g_outfile ); + // ?? + Q_FileBase( g_outfile, g_outfile, sizeof( g_outfile ) ); + + // Verbose stuff + if (!g_quiet) + { + vprint( 0, "%s, %s, %s, path %s\n", qdir, gamedir, g_outfile ); + } + // ?? + Q_DefaultExtension(g_outfile, ".smd", sizeof( g_outfile ) ); + + // Verbose stuff + if (!g_quiet) + { + vprint( 0, "Source animation: %s\n", filenames[ sourceanim ].String() ); + vprint( 0, "Target skeleton: %s\n", filenames[ targetskel ].String() ); + + vprint( 0, "Creating on \"%s\"\n", g_outfile); + } + // fullpath = EXTERNAL GLOBAL!!!??? + strcpy( fullpath, g_outfile ); + strcpy( fullpath, ExpandPath( fullpath ) ); + strcpy( fullpath, ExpandArg( fullpath ) ); + + // Load source and target data + s_source_t *pSource = Load_Source( filenames[sourceanim].String(), "smd", false, false ); + s_source_t *pTarget = Load_Source( filenames[targetskel].String(), "smd", false, false ); + + + // + s_template_t *pTemplate = NULL; + if(useTemplate) + { + pTemplate = Load_Template(templateFileName); + } + else + { + printf("Note: No template file specified, using defaults settings.\n"); + + pTemplate = New_Template(); + Set_DefaultTemplate(pTemplate); + } + + + // Process skeleton + s_source_t *pMappedAnimation = MotionMap( pSource, pTarget, pTemplate ); + + + // Save output (ref skeleton & animation data); + Save_SMD( fullpath, pMappedAnimation ); + + Q_StripExtension( filenames[outputanim].String(), outname, sizeof( outname ) ); + + // Verbose stuff + if (!g_quiet) + { + vprint( 0, "\nCompleted \"%s\"\n", g_outfile); + } + + return 0; +} + diff --git a/mp/src/utils/motionmapper/motionmapper.h b/mp/src/utils/motionmapper/motionmapper.h new file mode 100644 index 00000000..0df3f8ee --- /dev/null +++ b/mp/src/utils/motionmapper/motionmapper.h @@ -0,0 +1,274 @@ +/*** +* +//========= Copyright Valve Corporation, All rights reserved. ============// +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + +#include +#include "basetypes.h" +#include "utlvector.h" +#include "utlsymbol.h" +#include "mathlib/vector.h" +#include "studio.h" + +struct LodScriptData_t; + +#define IDSTUDIOHEADER (('T'<<24)+('S'<<16)+('D'<<8)+'I') + // little-endian "IDST" +#define IDSTUDIOANIMGROUPHEADER (('G'<<24)+('A'<<16)+('D'<<8)+'I') + // little-endian "IDAG" + + +#define STUDIO_QUADRATIC_MOTION 0x00002000 + +#define MAXSTUDIOANIMFRAMES 2000 // max frames per animation +#define MAXSTUDIOSEQUENCES 1524 // total sequences +#define MAXSTUDIOSRCBONES 512 // bones allowed at source movement +#define MAXSTUDIOBONEWEIGHTS 3 +#define MAXSTUDIONAME 128 + +#ifndef EXTERN +#define EXTERN extern +#endif + +EXTERN char outname[1024]; +EXTERN int numdirs; +EXTERN char cddir[32][MAX_PATH]; +EXTERN char fullpath[1024]; + +EXTERN float g_defaultscale; +EXTERN float g_currentscale; +EXTERN RadianEuler g_defaultrotation; + + +EXTERN char defaulttexture[16][MAX_PATH]; +EXTERN char sourcetexture[16][MAX_PATH]; + +EXTERN int numrep; + +EXTERN int flip_triangles; +EXTERN float normal_blend; + + +void *kalloc( int num, int size ); + +struct s_trianglevert_t +{ + int vertindex; + int normindex; // index into normal array + int s,t; + float u,v; +}; + +struct s_boneweight_t +{ + int numbones; + + int bone[MAXSTUDIOBONEWEIGHTS]; + float weight[MAXSTUDIOBONEWEIGHTS]; +}; + + +struct s_vertexinfo_t +{ + // wtf is this doing here? + int material; + + int firstref; + int lastref; + + int flexmask; + int numflex; + int flexoffset; +}; + +struct s_tmpface_t +{ + int material; + unsigned long a, b, c; + unsigned long ta, tb, tc; + unsigned long na, nb, nc; +}; + +struct s_face_t +{ + unsigned long a, b, c; +}; + +struct s_node_t +{ + char name[MAXSTUDIONAME]; + int parent; +}; + + +struct s_bone_t +{ + Vector pos; + RadianEuler rot; +}; + +struct s_texture_t +{ + char name[MAX_PATH]; + int flags; + int parent; + int material; + float width; + float height; + float dPdu; + float dPdv; +}; +EXTERN s_texture_t g_texture[MAXSTUDIOSKINS]; +EXTERN int g_numtextures; +EXTERN int g_material[MAXSTUDIOSKINS]; // link into texture array +EXTERN int g_nummaterials; + +EXTERN float g_gamma; +EXTERN int g_numskinref; +EXTERN int g_numskinfamilies; +EXTERN int g_skinref[256][MAXSTUDIOSKINS]; // [skin][skinref], returns texture index +EXTERN int g_numtexturegroups; +EXTERN int g_numtexturelayers[32]; +EXTERN int g_numtexturereps[32]; +EXTERN int g_texturegroup[32][32][32]; + +struct s_mesh_t +{ + int numvertices; + int vertexoffset; + + int numfaces; + int faceoffset; +}; + + +struct s_vertanim_t +{ + int vertex; + float speed; + float side; + Vector pos; + Vector normal; +}; + +// processed aggregate lod pools +struct s_loddata_t +{ + int numvertices; + s_boneweight_t *globalBoneweight; + s_vertexinfo_t *vertexInfo; + Vector *vertex; + Vector *normal; + Vector4D *tangentS; + Vector2D *texcoord; + + int numfaces; + s_face_t *face; + + s_mesh_t mesh[MAXSTUDIOSKINS]; + + // remaps verts from an lod's source mesh to this all-lod processed aggregate pool + int *pMeshVertIndexMaps[MAX_NUM_LODS]; +}; + +// raw off-disk source files. Raw data should be not processed. +struct s_source_t +{ + char filename[MAX_PATH]; + int time; // time stamp + + bool isActiveModel; + + // local skeleton hierarchy + int numbones; + s_node_t localBone[MAXSTUDIOSRCBONES]; + matrix3x4_t boneToPose[MAXSTUDIOSRCBONES]; // converts bone local data into initial pose data + + // bone remapping + int boneflags[MAXSTUDIOSRCBONES]; // attachment, vertex, etc flags for this bone + int boneref[MAXSTUDIOSRCBONES]; // flags for this and child bones + int boneLocalToGlobal[MAXSTUDIOSRCBONES]; // bonemap : local bone to world bone mapping + int boneGlobalToLocal[MAXSTUDIOSRCBONES]; // boneimap : world bone to local bone mapping + + int texmap[MAXSTUDIOSKINS*4]; // map local MAX materials to unique textures + + // per material mesh + int nummeshes; + int meshindex[MAXSTUDIOSKINS]; // mesh to skin index + s_mesh_t mesh[MAXSTUDIOSKINS]; + + // model global copy of vertices + int numvertices; + s_boneweight_t *localBoneweight; // vertex info about local bone weighting + s_boneweight_t *globalBoneweight; // vertex info about global bone weighting + s_vertexinfo_t *vertexInfo; // generic vertex info + Vector *vertex; + Vector *normal; + Vector4D *tangentS; + Vector2D *texcoord; + + int numfaces; + s_face_t *face; // vertex indexs per face + + // raw skeletal animation + int numframes; + int startframe; + int endframe; + s_bone_t *rawanim[MAXSTUDIOANIMFRAMES]; // [frame][bones]; + + // vertex animation + int *vanim_mapcount; // local verts map to N target verts + int **vanim_map; // local vertices to target vertices mapping list + int *vanim_flag; // local vert does animate + + int numvanims[MAXSTUDIOANIMFRAMES]; + s_vertanim_t *vanim[MAXSTUDIOANIMFRAMES]; // [frame][vertex] + + // processed aggregate lod data + s_loddata_t *pLodData; +}; + + +EXTERN int g_numsources; +EXTERN s_source_t *g_source[MAXSTUDIOSEQUENCES]; + +EXTERN int is_v1support; + +EXTERN int g_numverts; +EXTERN Vector g_vertex[MAXSTUDIOVERTS]; +EXTERN s_boneweight_t g_bone[MAXSTUDIOVERTS]; + +EXTERN int g_numnormals; +EXTERN Vector g_normal[MAXSTUDIOVERTS]; + +EXTERN int g_numtexcoords; +EXTERN Vector2D g_texcoord[MAXSTUDIOVERTS]; + +EXTERN int g_numfaces; +EXTERN s_tmpface_t g_face[MAXSTUDIOTRIANGLES]; +EXTERN s_face_t g_src_uface[MAXSTUDIOTRIANGLES]; // max res unified faces + +struct v_unify_t +{ + int refcount; + int lastref; + int firstref; + int v; + int m; + int n; + int t; + v_unify_t *next; +}; + +EXTERN v_unify_t *v_list[MAXSTUDIOVERTS]; +EXTERN v_unify_t v_listdata[MAXSTUDIOVERTS]; +EXTERN int numvlist; + +int SortAndBalanceBones( int iCount, int iMaxCount, int bones[], float weights[] ); +void Grab_Vertexanimation( s_source_t *psource ); +extern void BuildIndividualMeshes( s_source_t *psource ); diff --git a/mp/src/utils/nvtristriplib/nvtristrip.h b/mp/src/utils/nvtristriplib/nvtristrip.h new file mode 100644 index 00000000..65ad5ef8 --- /dev/null +++ b/mp/src/utils/nvtristriplib/nvtristrip.h @@ -0,0 +1,124 @@ +#ifndef NVTRISTRIP_H +#define NVTRISTRIP_H + +#ifndef NULL +#define NULL 0 +#endif + +#pragma comment(lib, "nvtristrip") + +//////////////////////////////////////////////////////////////////////////////////////// +// Public interface for stripifier +//////////////////////////////////////////////////////////////////////////////////////// + +//GeForce1 and 2 cache size +#define CACHESIZE_GEFORCE1_2 16 + +//GeForce3 cache size +#define CACHESIZE_GEFORCE3 24 + +enum PrimType +{ + PT_LIST, + PT_STRIP, + PT_FAN +}; + +struct PrimitiveGroup +{ + PrimType type; + unsigned int numIndices; + unsigned short* indices; + +//////////////////////////////////////////////////////////////////////////////////////// + + PrimitiveGroup() : type(PT_STRIP), numIndices(0), indices(NULL) {} + ~PrimitiveGroup() + { + if(indices) + delete[] indices; + indices = NULL; + } +}; + +//////////////////////////////////////////////////////////////////////////////////////// +// SetCacheSize() +// +// Sets the cache size which the stripfier uses to optimize the data. +// Controls the length of the generated individual strips. +// This is the "actual" cache size, so 24 for GeForce3 and 16 for GeForce1/2 +// You may want to play around with this number to tweak performance. +// +// Default value: 16 +// +void SetCacheSize(const unsigned int cacheSize); + + +//////////////////////////////////////////////////////////////////////////////////////// +// SetStitchStrips() +// +// bool to indicate whether to stitch together strips into one huge strip or not. +// If set to true, you'll get back one huge strip stitched together using degenerate +// triangles. +// If set to false, you'll get back a large number of separate strips. +// +// Default value: true +// +void SetStitchStrips(const bool bStitchStrips); + + +//////////////////////////////////////////////////////////////////////////////////////// +// SetMinStripSize() +// +// Sets the minimum acceptable size for a strip, in triangles. +// All strips generated which are shorter than this will be thrown into one big, separate list. +// +// Default value: 0 +// +void SetMinStripSize(const unsigned int minSize); + + +//////////////////////////////////////////////////////////////////////////////////////// +// SetListsOnly() +// +// If set to true, will return an optimized list, with no strips at all. +// +// Default value: false +// +void SetListsOnly(const bool bListsOnly); + + +//////////////////////////////////////////////////////////////////////////////////////// +// GenerateStrips() +// +// in_indices: input index list, the indices you would use to render +// in_numIndices: number of entries in in_indices +// primGroups: array of optimized/stripified PrimitiveGroups +// numGroups: number of groups returned +// +// Be sure to call delete[] on the returned primGroups to avoid leaking mem +// +void GenerateStrips(const unsigned short* in_indices, const unsigned int in_numIndices, + PrimitiveGroup** primGroups, unsigned short* numGroups); + + +//////////////////////////////////////////////////////////////////////////////////////// +// RemapIndices() +// +// Function to remap your indices to improve spatial locality in your vertex buffer. +// +// in_primGroups: array of PrimitiveGroups you want remapped +// numGroups: number of entries in in_primGroups +// numVerts: number of vertices in your vertex buffer, also can be thought of as the range +// of acceptable values for indices in your primitive groups. +// remappedGroups: array of remapped PrimitiveGroups +// +// Note that, according to the remapping handed back to you, you must reorder your +// vertex buffer. +// +// Credit goes to the MS Xbox crew for the idea for this interface. +// +void RemapIndices(const PrimitiveGroup* in_primGroups, const unsigned short numGroups, + const unsigned short numVerts, PrimitiveGroup** remappedGroups); + +#endif \ No newline at end of file diff --git a/mp/src/utils/phonemeextractor/extractor_utils.cpp b/mp/src/utils/phonemeextractor/extractor_utils.cpp new file mode 100644 index 00000000..ba927f04 --- /dev/null +++ b/mp/src/utils/phonemeextractor/extractor_utils.cpp @@ -0,0 +1,28 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// +#include +#include + +//----------------------------------------------------------------------------- +// Purpose: converts an english string to unicode +//----------------------------------------------------------------------------- +int ConvertANSIToUnicode(const char *ansi, wchar_t *unicode, int unicodeBufferSize) +{ + return ::MultiByteToWideChar(CP_ACP, 0, ansi, -1, unicode, unicodeBufferSize); +} + +char *va( const char *fmt, ... ) +{ + va_list args; + static char output[4][1024]; + static int outbuffer = 0; + + outbuffer++; + va_start( args, fmt ); + vprintf( fmt, args ); + vsprintf( output[ outbuffer & 3 ], fmt, args ); + return output[ outbuffer & 3 ]; +} \ No newline at end of file diff --git a/mp/src/utils/phonemeextractor/phonemeextractor-2010.vcxproj b/mp/src/utils/phonemeextractor/phonemeextractor-2010.vcxproj new file mode 100644 index 00000000..4f444916 --- /dev/null +++ b/mp/src/utils/phonemeextractor/phonemeextractor-2010.vcxproj @@ -0,0 +1,268 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + Phonemeextractor + {079933D6-F849-3176-49FC-D50E4B461AC4} + + + + DynamicLibrary + MultiByte + phonemeextractor + + + DynamicLibrary + MultiByte + phonemeextractor + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30319.1 + .\Debug\win32\ + .\Debug\win32\ + ..\..\devtools\vstools;$(ExecutablePath);$(Path) + true + true + true + false + true + .\Release\win32\ + .\Release\win32\ + ..\..\devtools\vstools;$(ExecutablePath);$(Path) + true + true + false + false + true + + + + + + /MP /wd4995 + Disabled + ..\..\common;..\..\public;..\..\public\tier0;..\..\public\tier1;../common;../hlfaceposer;../sapi51/include + _HAS_ITERATOR_DEBUGGING=0;WIN32;_WIN32;_DEBUG;DEBUG;_WINDOWS;_USRDLL;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;DLLNAME=phonemeextractor;RAD_TELEMETRY_DISABLED;COMPILER_MSVC32;PHONEMEEXTRACTOR_EXPORTS;VPCGAMECAPS=VALVE;PROJECTDIR=D:\dev\games\rel\hl2\src\utils\phonemeextractor;_DLL_EXT=.dll;VPCGAME=valve + true + false + Default + MultiThreadedDebug + true + StreamingSIMDExtensions + Fast + true + true + NotUsing + $(IntDir)/ + $(IntDir)/ + $(IntDir)/ + false + false + Level4 + true + EditAndContinue + CompileAsCpp + $(IntDir)/ + Prompt + + + _DEBUG;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE + 1033 + + + + + /ignore:4221 + %(AdditionalDependencies);odbc32.lib;odbccp32.lib + NotSet + $(OutDir)\phonemeextractor.dll + true + ..\..\lib\common;..\..\lib\public + libc;libcd;libcmt + true + $(IntDir)/$(TargetName).pdb + false + $(IntDir)/$(TargetName).map + Windows + + MachineX86 + PromptImmediately + false + false + + + true + + + true + + + true + $(OutDir)/phonemeextractor.bsc + + + Publishing to ..\..\..\game\bin\phonemeextractors + if not exist "..\..\..\game\bin\phonemeextractors" mkdir "..\..\..\game\bin\phonemeextractors" copy "$(TargetDir)"$(TargetFileName) "..\..\..\game\bin\phonemeextractors\$(TargetFileName)" if ERRORLEVEL 1 goto BuildEventFailed if exist "$(TargetDir)"$(TargetName).map copy "$(TargetDir)"$(TargetName).map ..\..\..\game\bin\phonemeextractors\$(TargetName).map copy "$(TargetDir)"$(TargetName).pdb ..\..\..\game\bin\phonemeextractors\$(TargetName).pdb if ERRORLEVEL 1 goto BuildEventFailed goto BuildEventOK :BuildEventFailed echo *** ERROR! PostBuildStep FAILED for $(ProjectName)! EXE or DLL is probably running. *** del /q "$(TargetDir)"$(TargetFileName) exit 1 :BuildEventOK + + + + + + + + + /MP /d2Zi+ /wd4995 + MaxSpeed + AnySuitable + true + Speed + ..\..\common;..\..\public;..\..\public\tier0;..\..\public\tier1;../common;../hlfaceposer;../sapi51/include + WIN32;_WIN32;NDEBUG;_WINDOWS;_USRDLL;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;DLLNAME=phonemeextractor;RAD_TELEMETRY_DISABLED;COMPILER_MSVC32;PHONEMEEXTRACTOR_EXPORTS;VPCGAMECAPS=VALVE;PROJECTDIR=D:\dev\games\rel\hl2\src\utils\phonemeextractor;_DLL_EXT=.dll;VPCGAME=valve + true + false + MultiThreaded + false + true + StreamingSIMDExtensions + Fast + true + true + NotUsing + $(IntDir)/ + $(IntDir)/ + $(IntDir)/ + false + false + Level4 + true + ProgramDatabase + CompileAsCpp + $(IntDir)/ + Prompt + + + NDEBUG;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE + 1033 + + + + + /DYNAMICBASE /ignore:4221 + %(AdditionalDependencies);odbc32.lib;odbccp32.lib + NotSet + $(OutDir)\phonemeextractor.dll + true + ..\..\lib\common;..\..\lib\public + libc;libcd;libcmtd + true + $(IntDir)/$(TargetName).pdb + false + $(IntDir)/$(TargetName).map + Windows + true + true + + MachineX86 + PromptImmediately + false + + + true + + + true + + + true + $(OutDir)/phonemeextractor.bsc + + + Publishing to ..\..\..\game\bin\phonemeextractors + if not exist "..\..\..\game\bin\phonemeextractors" mkdir "..\..\..\game\bin\phonemeextractors" copy "$(TargetDir)"$(TargetFileName) "..\..\..\game\bin\phonemeextractors\$(TargetFileName)" if ERRORLEVEL 1 goto BuildEventFailed if exist "$(TargetDir)"$(TargetName).map copy "$(TargetDir)"$(TargetName).map ..\..\..\game\bin\phonemeextractors\$(TargetName).map copy "$(TargetDir)"$(TargetName).pdb ..\..\..\game\bin\phonemeextractors\$(TargetName).pdb if ERRORLEVEL 1 goto BuildEventFailed goto BuildEventOK :BuildEventFailed echo *** ERROR! PostBuildStep FAILED for $(ProjectName)! EXE or DLL is probably running. *** del /q "$(TargetDir)"$(TargetFileName) exit 1 :BuildEventOK + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + NotUsing + NotUsing + + + + + + + + + + Compiling pointeroverride.asm + "$(VCInstallDir)bin\ml.exe" /c /Cp /Zi /Fo"$(IntDir)\%(Filename).obj" "%(FullPath)" + $(IntDir)\%(Filename).obj + Compiling pointeroverride.asm + "$(VCInstallDir)bin\ml.exe" /c /Cp /Zi /Fo"$(IntDir)\%(Filename).obj" "%(FullPath)" + $(IntDir)\%(Filename).obj + + + + + + + + diff --git a/mp/src/utils/phonemeextractor/phonemeextractor-2010.vcxproj.filters b/mp/src/utils/phonemeextractor/phonemeextractor-2010.vcxproj.filters new file mode 100644 index 00000000..22dba2f7 --- /dev/null +++ b/mp/src/utils/phonemeextractor/phonemeextractor-2010.vcxproj.filters @@ -0,0 +1,146 @@ + + + + + {1680C80B-FF1E-EA4D-9817-CC12254F2E40} + + + {C5D73B3A-C648-896C-B7CE-F174808E5BA5} + + + {680EF60A-F852-B6F6-8E56-5693F8167FE5} + + + {0339A88D-F26D-9E55-2AC4-F2CED0F7745A} + + + {BA03E055-4FA2-FCE3-8A1C-D348547D379C} + + + + + Link Libraries + + + Link Libraries + + + Link Libraries + + + Link Libraries + + + Link Libraries + + + + + Header Files + + + Public Header Files + + + Public Header Files + + + Public Header Files + + + Public Header Files + + + Public Header Files + + + Public Header Files + + + Public Header Files + + + Public Header Files + + + Public Header Files + + + Public Header Files + + + Public Header Files + + + Public Header Files + + + Public Header Files + + + Public Header Files + + + Public Header Files + + + Public Header Files + + + Public Header Files + + + Public Header Files + + + Public Header Files + + + Public Header Files + + + SAPI Header Files + + + SAPI Header Files + + + SAPI Header Files + + + SAPI Header Files + + + SAPI Header Files + + + SAPI Header Files + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + + + Source Files + + + + + diff --git a/mp/src/utils/phonemeextractor/phonemeextractor.cpp b/mp/src/utils/phonemeextractor/phonemeextractor.cpp new file mode 100644 index 00000000..8dfc8439 --- /dev/null +++ b/mp/src/utils/phonemeextractor/phonemeextractor.cpp @@ -0,0 +1,1425 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// extracephonemes.cpp : Defines the entry point for the console application. +// +#define PROTECTED_THINGS_DISABLE + +#include "tier0/wchartypes.h" +#include +#include +#include +#include "sphelper.h" +#include "spddkhlp.h" +// ATL Header Files +#include +// Face poser and util includes +#include "utlvector.h" +#include "phonemeextractor/PhonemeExtractor.h" +#include "PhonemeConverter.h" +#include "sentence.h" +#include "tier0/dbg.h" +#include "tier0/icommandline.h" +#include "filesystem.h" + +// Extract phoneme grammar id +#define EP_GRAM_ID 101 +// First rule of dynamic sentence rule set +#define DYN_SENTENCERULE 102 +// # of milliseconds to allow for processing before timeout +#define SR_WAVTIMEOUT 4000 +// Weight tag for rule to rule word/rule transitions +#define CONFIDENCE_WEIGHT 0.0f + +//#define LOGGING 1 +#define LOGFILE "c:\\fp.log" + +void LogReset( void ) +{ +#if LOGGING + FILE *fp = fopen( LOGFILE, "w" ); + if ( fp ) + fclose( fp ); +#endif +} + +char *va( const char *fmt, ... ); + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *words - +//----------------------------------------------------------------------------- +void LogWords( CSentence& sentence ) +{ + Log( "Wordcount == %i\n", sentence.m_Words.Size() ); + + for ( int i = 0; i < sentence.m_Words.Size(); i++ ) + { + const CWordTag *w = sentence.m_Words[ i ]; + Log( "Word %s %u to %u\n", w->GetWord(), w->m_uiStartByte, w->m_uiEndByte ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *phonemes - +//----------------------------------------------------------------------------- +void LogPhonemes( CSentence& sentence ) +{ + return; + + Log( "Phonemecount == %i\n", sentence.CountPhonemes() ); + + for ( int i = 0; i < sentence.m_Words.Size(); i++ ) + { + const CWordTag *w = sentence.m_Words[ i ]; + + for ( int j = 0; j < w->m_Phonemes.Size(); j++ ) + { + const CPhonemeTag *p = w->m_Phonemes[ j ]; + Log( "Phoneme %s %u to %u\n", p->GetTag(), p->m_uiStartByte, p->m_uiEndByte ); + } + } +} + +#define NANO_CONVERT 10000000.0f; + +//----------------------------------------------------------------------------- +// Purpose: Walk list of words and phonemes and create phoneme tags in CSentence object +// FIXME: Right now, phonemes are assumed to evenly space out across a word. +// Input : *converter - +// result - +// sentence - +//----------------------------------------------------------------------------- +void EnumeratePhonemes( ISpPhoneConverter *converter, const ISpRecoResult* result, CSentence& sentence ) +{ + USES_CONVERSION; + + // Grab access to element container + ISpPhrase *phrase = ( ISpPhrase * )result; + if ( !phrase ) + return; + + SPPHRASE *pElements; + if ( !SUCCEEDED( phrase->GetPhrase( &pElements ) ) ) + return; + + // Only use it if it's better/same size as what we already had on-hand + if ( pElements->Rule.ulCountOfElements > 0 ) + //(unsigned int)( sentence.m_Words.Size() - sentence.GetWordBase() ) ) + { + sentence.ResetToBase(); + + // Walk list of words + for ( ULONG i = 0; i < pElements->Rule.ulCountOfElements; i++ ) + { + unsigned int wordstart, wordend; + + // Get start/end sample index + wordstart = pElements->pElements[i].ulAudioStreamOffset + (unsigned int)pElements->ullAudioStreamPosition; + wordend = wordstart + pElements->pElements[i].ulAudioSizeBytes; + + // Create word tag + CWordTag *w = new CWordTag( W2T( pElements->pElements[i].pszDisplayText ) ); + Assert( w ); + w->m_uiStartByte = wordstart; + w->m_uiEndByte = wordend; + + sentence.AddWordTag( w ); + + // Count # of phonemes in this word + SPPHONEID pstr[ 2 ]; + pstr[ 1 ] = 0; + WCHAR wszPhoneme[ SP_MAX_PRON_LENGTH ]; + + const SPPHONEID *current; + SPPHONEID phoneme; + current = pElements->pElements[i].pszPronunciation; + float total_weight = 0.0f; + while ( 1 ) + { + phoneme = *current++; + if ( !phoneme ) + break; + + pstr[ 0 ] = phoneme; + wszPhoneme[ 0 ] = L'\0'; + + converter->IdToPhone( pstr, wszPhoneme ); + + total_weight += WeightForPhoneme( W2A( wszPhoneme ) ); + } + + current = pElements->pElements[i].pszPronunciation; + + // Decide # of bytes/phoneme weight + float psize = 0; + if ( total_weight ) + { + psize = ( wordend - wordstart ) / total_weight; + } + + int number = 0; + + // Re-walk the phoneme list and create true phoneme tags + float startWeight = 0.0f; + while ( 1 ) + { + phoneme = *current++; + if ( !phoneme ) + break; + + pstr[ 0 ] = phoneme; + wszPhoneme[ 0 ] = L'\0'; + + converter->IdToPhone( pstr, wszPhoneme ); + + CPhonemeTag *p = new CPhonemeTag( W2A( wszPhoneme ) ); + Assert( p ); + + float weight = WeightForPhoneme( W2A( wszPhoneme ) ); + + p->m_uiStartByte = wordstart + (int)( startWeight * psize ); + p->m_uiEndByte = p->m_uiStartByte + (int)( psize * weight ); + + startWeight += weight; + + // Convert to IPA phoneme code + p->SetPhonemeCode( TextToPhoneme( p->GetTag() ) ); + + sentence.AddPhonemeTag( w, p ); + + number++; + } + } + } + + // Free memory + ::CoTaskMemFree(pElements); +} + +//----------------------------------------------------------------------------- +// Purpose: Create rules for each word in the reference sentence +//----------------------------------------------------------------------------- +typedef struct +{ + int ruleId; + SPSTATEHANDLE hRule; + CSpDynamicString word; + char plaintext[ 256 ]; +} WORDRULETYPE; + +//----------------------------------------------------------------------------- +// Purpose: Creates start for word of sentence +// Input : cpRecoGrammar - +// *root - +// *rules - +// word - +//----------------------------------------------------------------------------- +void AddWordRule( ISpRecoGrammar* cpRecoGrammar, SPSTATEHANDLE *root, CUtlVector< WORDRULETYPE > *rules, CSpDynamicString& word ) +{ + USES_CONVERSION; + HRESULT hr; + WORDRULETYPE *newrule; + + int idx = (*rules).AddToTail(); + + newrule = &(*rules)[ idx ]; + + newrule->ruleId = DYN_SENTENCERULE + idx + 1; + newrule->word = word; + + strcpy( newrule->plaintext, W2T( word ) ); + + // Create empty rule + hr = cpRecoGrammar->CreateNewState( *root, &newrule->hRule ); + Assert( !FAILED( hr ) ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : cpRecoGrammar - +// *from - +// *to - +//----------------------------------------------------------------------------- +void AddWordTransitionRule( ISpRecoGrammar* cpRecoGrammar, WORDRULETYPE *from, WORDRULETYPE *to ) +{ + USES_CONVERSION; + + HRESULT hr; + Assert( from ); + + if ( from && !to ) + { + OutputDebugString( va( "Transition from %s to TERM\r\n", from->plaintext ) ); + } + else + { + OutputDebugString( va( "Transition from %s to %s\r\n", from->plaintext, to->plaintext ) ); + } + + hr = cpRecoGrammar->AddWordTransition( from->hRule, to ? to->hRule : NULL, (WCHAR *)from->word, NULL, SPWT_LEXICAL, CONFIDENCE_WEIGHT, NULL ); + Assert( !FAILED( hr ) ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : cpRecoGrammar - +// *from - +// *to - +//----------------------------------------------------------------------------- +void AddOptionalTransitionRule( ISpRecoGrammar* cpRecoGrammar, WORDRULETYPE *from, WORDRULETYPE *to ) +{ + USES_CONVERSION; + + HRESULT hr; + Assert( from ); + + if ( from && !to ) + { + OutputDebugString( va( "Opt transition from %s to TERM\r\n", from->plaintext ) ); + } + else + { + OutputDebugString( va( "Opt transition from %s to %s\r\n", from->plaintext, to->plaintext ) ); + } + + hr = cpRecoGrammar->AddWordTransition( from->hRule, to ? to->hRule : NULL, NULL, NULL, SPWT_LEXICAL, CONFIDENCE_WEIGHT, NULL ); + Assert( !FAILED( hr ) ); +} + +#define MAX_WORD_SKIP 1 +//----------------------------------------------------------------------------- +// Purpose: Links together all word rule states into a sentence rule CFG +// Input : singleword - +// cpRecoGrammar - +// *root - +// *rules - +//----------------------------------------------------------------------------- +bool BuildRules( ISpRecoGrammar* cpRecoGrammar, SPSTATEHANDLE *root, CUtlVector< WORDRULETYPE > *rules ) +{ + HRESULT hr; + WORDRULETYPE *rule, *next; + + int numrules = (*rules).Size(); + + rule = &(*rules)[ 0 ]; + + // Add transition + hr = cpRecoGrammar->AddWordTransition( *root, rule->hRule, NULL, NULL, SPWT_LEXICAL, CONFIDENCE_WEIGHT, NULL ); + Assert( !FAILED( hr ) ); + + for ( int i = 0; i < numrules; i++ ) + { + rule = &(*rules)[ i ]; + if ( i < numrules - 1 ) + { + next = &(*rules)[ i + 1 ]; + } + else + { + next = NULL; + } + + AddWordTransitionRule( cpRecoGrammar, rule, next ); + } + + if ( numrules > 1 ) + { + for ( int skip = 1; skip <= min( MAX_WORD_SKIP, numrules ); skip++ ) + { + OutputDebugString( va( "Opt transition from Root to %s\r\n", (*rules)[ 0 ].plaintext ) ); + + hr = cpRecoGrammar->AddWordTransition( *root, (*rules)[ 0 ].hRule, NULL, NULL, SPWT_LEXICAL, CONFIDENCE_WEIGHT, NULL ); + + // Now build rules where you can skip 1 to N intervening words + for ( int i = 1; i < numrules; i++ ) + { + // Start at the beginning? + rule = &(*rules)[ i ]; + if ( i < numrules - skip ) + { + next = &(*rules)[ i + skip ]; + } + else + { + continue; + } + + // Add transition + AddOptionalTransitionRule( cpRecoGrammar, rule, next ); + } + + // Go from final rule to end point + AddOptionalTransitionRule( cpRecoGrammar, rule, NULL ); + } + } + + // Store it + hr = cpRecoGrammar->Commit(NULL); + if ( FAILED( hr ) ) + return false; + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Debugging, prints alternate list if one is created +// Input : cpResult - +// (*pfnPrint - +//----------------------------------------------------------------------------- +void PrintAlternates( ISpRecoResult* cpResult, void (*pfnPrint)( const char *fmt, ... ) ) +{ + ISpPhraseAlt *rgPhraseAlt[ 32 ]; + memset( rgPhraseAlt, 0, sizeof( rgPhraseAlt ) ); + + ULONG ulCount; + + ISpPhrase *phrase = ( ISpPhrase * )cpResult; + if ( phrase ) + { + SPPHRASE *pElements; + if ( SUCCEEDED( phrase->GetPhrase( &pElements ) ) ) + { + if ( pElements->Rule.ulCountOfElements > 0 ) + { + HRESULT hr = cpResult->GetAlternates( + pElements->Rule.ulFirstElement, + pElements->Rule.ulCountOfElements, + 32, + rgPhraseAlt, + &ulCount); + + Assert( !FAILED( hr ) ); + + for ( ULONG r = 0 ; r < ulCount; r++ ) + { + CSpDynamicString dstrText; + hr = rgPhraseAlt[ r ]->GetText( (ULONG)SP_GETWHOLEPHRASE, (ULONG)SP_GETWHOLEPHRASE, TRUE, &dstrText, NULL); + Assert( !FAILED( hr ) ); + + pfnPrint( "[ ALT ]" ); + pfnPrint( dstrText.CopyToChar() ); + pfnPrint( "\r\n" ); + } + } + } + + } + + for ( int i = 0; i < 32; i++ ) + { + if ( rgPhraseAlt[ i ] ) + { + rgPhraseAlt[ i ]->Release(); + rgPhraseAlt[ i ] = NULL; + } + } +} + +void PrintWordsAndPhonemes( CSentence& sentence, void (*pfnPrint)( const char *fmt, ... ) ) +{ + char sz[ 256 ]; + int i; + + pfnPrint( "WORDS\r\n\r\n" ); + + for ( i = 0 ; i < sentence.m_Words.Size(); i++ ) + { + CWordTag *word = sentence.m_Words[ i ]; + if ( !word ) + continue; + + sprintf( sz, "<%u - %u> %s\r\n", + word->m_uiStartByte, word->m_uiEndByte, word->GetWord() ); + + pfnPrint( sz ); + + for ( int j = 0 ; j < word->m_Phonemes.Size(); j++ ) + { + CPhonemeTag *phoneme = word->m_Phonemes[ j ]; + if ( !phoneme ) + continue; + + sprintf( sz, " <%u - %u> %s\r\n", + phoneme->m_uiStartByte, phoneme->m_uiEndByte, phoneme->GetTag() ); + + pfnPrint( sz ); + } + } + + pfnPrint( "\r\n" ); +} + +//----------------------------------------------------------------------------- +// Purpose: Given a wave file and a string of words "text", creates a CFG from the +// sentence and stores the resulting words/phonemes in CSentence +// Input : *wavname - +// text - +// sentence - +// (*pfnPrint - +// Output : SR_RESULT +//----------------------------------------------------------------------------- +SR_RESULT ExtractPhonemes( const char *wavname, CSpDynamicString& text, CSentence& sentence, void (*pfnPrint)( const char *fmt, ...) ) +{ + // Assume failure + SR_RESULT result = SR_RESULT_ERROR; + + if ( text.Length() <= 0 ) + { + pfnPrint( "Error: no rule / text specified\n" ); + return result; + } + + USES_CONVERSION; + HRESULT hr; + + CUtlVector < WORDRULETYPE > wordRules; + + CComPtr cpInputStream; + CComPtr cpRecognizer; + CComPtr cpRecoContext; + CComPtr cpRecoGrammar; + CComPtr cpPhoneConv; + + // Create basic SAPI stream object + // NOTE: The helper SpBindToFile can be used to perform the following operations + hr = cpInputStream.CoCreateInstance(CLSID_SpStream); + if ( FAILED( hr ) ) + { + pfnPrint( "Error: SAPI 5.1 Stream object not installed?\n" ); + return result; + } + + CSpStreamFormat sInputFormat; + + // setup stream object with wav file MY_WAVE_AUDIO_FILENAME + // for read-only access, since it will only be access by the SR engine + hr = cpInputStream->BindToFile( + T2W(wavname), + SPFM_OPEN_READONLY, + NULL, + sInputFormat.WaveFormatExPtr(), + SPFEI_ALL_EVENTS ); + + if ( FAILED( hr ) ) + { + pfnPrint( "Error: couldn't open wav file %s\n", wavname ); + return result; + } + + // Create in-process speech recognition engine + hr = cpRecognizer.CoCreateInstance(CLSID_SpInprocRecognizer); + if ( FAILED( hr ) ) + { + pfnPrint( "Error: SAPI 5.1 In process recognizer object not installed?\n" ); + return result; + } + + // Create recognition context to receive events + hr = cpRecognizer->CreateRecoContext(&cpRecoContext); + if ( FAILED( hr ) ) + { + pfnPrint( "Error: SAPI 5.1 Unable to create recognizer context\n" ); + return result; + } + + // Create a grammar + hr = cpRecoContext->CreateGrammar( EP_GRAM_ID, &cpRecoGrammar ); + if ( FAILED( hr ) ) + { + pfnPrint( "Error: SAPI 5.1 Unable to create recognizer grammar\n" ); + return result; + } + + LANGID englishID = 0x409; // 1033 decimal + + bool userSpecified = false; + LANGID langID = SpGetUserDefaultUILanguage(); + + // Allow commandline override + if ( CommandLine()->FindParm( "-languageid" ) != 0 ) + { + userSpecified = true; + langID = CommandLine()->ParmValue( "-languageid", langID ); + } + + // Create a phoneme converter ( so we can convert to IPA codes ) + hr = SpCreatePhoneConverter( langID, NULL, NULL, &cpPhoneConv ); + if ( FAILED( hr ) ) + { + if ( langID != englishID ) + { + if ( userSpecified ) + { + pfnPrint( "Warning: SAPI 5.1 Unable to create phoneme converter for command line override -languageid %i\n", langID ); + } + else + { + pfnPrint( "Warning: SAPI 5.1 Unable to create phoneme converter for default UI language %i\n",langID ); + } + + // Try english!!! + langID = englishID; + hr = SpCreatePhoneConverter( langID, NULL, NULL, &cpPhoneConv ); + } + + if ( FAILED( hr ) ) + { + pfnPrint( "Error: SAPI 5.1 Unable to create phoneme converter for English language id %i\n", langID ); + return result; + } + else + { + pfnPrint( "Note: SAPI 5.1 Falling back to use english -languageid %i\n", langID ); + } + } + else if ( userSpecified ) + { + pfnPrint( "Note: SAPI 5.1 Using user specified -languageid %i\n",langID ); + } + + SPSTATEHANDLE hStateRoot; + // create/re-create Root level rule of grammar + hr = cpRecoGrammar->GetRule(L"Root", 0, SPRAF_TopLevel | SPRAF_Active, TRUE, &hStateRoot); + if ( FAILED( hr ) ) + { + pfnPrint( "Error: SAPI 5.1 Unable to create root rule\n" ); + return result; + } + + // Inactivate it so we can alter it + hr = cpRecoGrammar->SetRuleState( NULL, NULL, SPRS_INACTIVE ); + if ( FAILED( hr ) ) + { + pfnPrint( "Error: SAPI 5.1 Unable to deactivate grammar rules\n" ); + return result; + } + + // Create the rule set from the words in text + { + CSpDynamicString currentWord; + WCHAR *pos = ( WCHAR * )text; + WCHAR str[ 2 ]; + str[1]= 0; + + while ( *pos ) + { + if ( *pos == L' ' /*|| *pos == L'.' || *pos == L'-'*/ ) + { + // Add word to rule set + if ( currentWord.Length() > 0 ) + { + AddWordRule( cpRecoGrammar, &hStateRoot, &wordRules, currentWord ); + currentWord.Clear(); + } + pos++; + continue; + } + + // Skip anything that's inside a [ xxx ] pair. + if ( *pos == L'[' ) + { + while ( *pos && *pos != L']' ) + { + pos++; + } + + if ( *pos ) + { + pos++; + } + continue; + } + + str[ 0 ] = *pos; + + currentWord.Append( str ); + pos++; + } + + if ( currentWord.Length() > 0 ) + { + AddWordRule( cpRecoGrammar, &hStateRoot, &wordRules, currentWord ); + } + + if ( wordRules.Size() <= 0 ) + { + pfnPrint( "Error: Text %s contained no usable words\n", text ); + return result; + } + + // Build all word to word transitions in the grammar + if ( !BuildRules( cpRecoGrammar, &hStateRoot, &wordRules ) ) + { + pfnPrint( "Error: Rule set for %s could not be generated\n", text ); + return result; + } + } + + // check for recognitions and end of stream event + const ULONGLONG ullInterest = + SPFEI(SPEI_RECOGNITION) | SPFEI(SPEI_END_SR_STREAM) | SPFEI(SPEI_FALSE_RECOGNITION) | + SPFEI(SPEI_PHRASE_START ) | SPFEI(SPEI_HYPOTHESIS ) | SPFEI(SPEI_INTERFERENCE) ; + hr = cpRecoContext->SetInterest( ullInterest, ullInterest ); + if ( FAILED( hr ) ) + { + pfnPrint( "Error: SAPI 5.1 Unable to set interest level\n" ); + return result; + } + // use Win32 events for command-line style application + hr = cpRecoContext->SetNotifyWin32Event(); + if ( FAILED( hr ) ) + { + pfnPrint( "Error: SAPI 5.1 Unable to set win32 notify event\n" ); + return result; + } + // connect wav input to recognizer + // SAPI will negotiate mismatched engine/input audio formats using system audio codecs, so second parameter is not important - use default of TRUE + hr = cpRecognizer->SetInput(cpInputStream, TRUE); + if ( FAILED( hr ) ) + { + pfnPrint( "Error: SAPI 5.1 Unable to associate input stream\n" ); + return result; + } + + // Activate the CFG ( rather than using dictation ) + hr = cpRecoGrammar->SetRuleState( NULL, NULL, SPRS_ACTIVE ); + if ( FAILED( hr ) ) + { + switch ( hr ) + { + case E_INVALIDARG: + pfnPrint( "pszName is invalid or bad. Alternatively, pReserved is non-NULL\n" ); + break; + case SP_STREAM_UNINITIALIZED: + pfnPrint( "ISpRecognizer::SetInput has not been called with the InProc recognizer\n" ); + break; + case SPERR_UNINITIALIZED: + pfnPrint( "The object has not been properly initialized.\n"); + break; + case SPERR_UNSUPPORTED_FORMAT: + pfnPrint( "Audio format is bad or is not recognized. Alternatively, the device driver may be busy by another application and cannot be accessed.\n" ); + break; + case SPERR_NOT_TOPLEVEL_RULE: + pfnPrint( "The rule pszName exists, but is not a top-level rule.\n" ); + break; + default: + pfnPrint( "Unknown error\n" ); + break; + } + pfnPrint( "Error: SAPI 5.1 Unable to activate rule set\n" ); + return result; + } + + // while events occur, continue processing + // timeout should be greater than the audio stream length, or a reasonable amount of time expected to pass before no more recognitions are expected in an audio stream + BOOL fEndStreamReached = FALSE; + while (!fEndStreamReached && S_OK == cpRecoContext->WaitForNotifyEvent( SR_WAVTIMEOUT )) + { + CSpEvent spEvent; + // pull all queued events from the reco context's event queue + + while (!fEndStreamReached && S_OK == spEvent.GetFrom(cpRecoContext)) + { + // Check event type + switch (spEvent.eEventId) + { + case SPEI_INTERFERENCE: + { + SPINTERFERENCE interference = spEvent.Interference(); + + switch ( interference ) + { + case SPINTERFERENCE_NONE: + pfnPrint( "[ I None ]\r\n" ); + break; + case SPINTERFERENCE_NOISE: + pfnPrint( "[ I Noise ]\r\n" ); + break; + case SPINTERFERENCE_NOSIGNAL: + pfnPrint( "[ I No Signal ]\r\n" ); + break; + case SPINTERFERENCE_TOOLOUD: + pfnPrint( "[ I Too Loud ]\r\n" ); + break; + case SPINTERFERENCE_TOOQUIET: + pfnPrint( "[ I Too Quiet ]\r\n" ); + break; + case SPINTERFERENCE_TOOFAST: + pfnPrint( "[ I Too Fast ]\r\n" ); + break; + case SPINTERFERENCE_TOOSLOW: + pfnPrint( "[ I Too Slow ]\r\n" ); + break; + default: + break; + } + } + break; + case SPEI_PHRASE_START: + pfnPrint( "Phrase Start\r\n" ); + sentence.MarkNewPhraseBase(); + break; + + case SPEI_HYPOTHESIS: + case SPEI_RECOGNITION: + case SPEI_FALSE_RECOGNITION: + { + CComPtr cpResult; + cpResult = spEvent.RecoResult(); + + CSpDynamicString dstrText; + if (spEvent.eEventId == SPEI_FALSE_RECOGNITION) + { + dstrText = L"(Unrecognized)"; + + result = SR_RESULT_FAILED; + + // It's possible that the failed recog might have more words, so see if that's the case + EnumeratePhonemes( cpPhoneConv, cpResult, sentence ); + } + else + { + // Hypothesis or recognition success + cpResult->GetText( (ULONG)SP_GETWHOLEPHRASE, (ULONG)SP_GETWHOLEPHRASE, TRUE, &dstrText, NULL); + + EnumeratePhonemes( cpPhoneConv, cpResult, sentence ); + + if ( spEvent.eEventId == SPEI_RECOGNITION ) + { + result = SR_RESULT_SUCCESS; + } + + pfnPrint( va( "%s%s\r\n", spEvent.eEventId == SPEI_HYPOTHESIS ? "[ Hypothesis ] " : "", dstrText.CopyToChar() ) ); + } + + cpResult.Release(); + } + break; + // end of the wav file was reached by the speech recognition engine + case SPEI_END_SR_STREAM: + fEndStreamReached = TRUE; + break; + } + + // clear any event data/object references + spEvent.Clear(); + }// END event pulling loop - break on empty event queue OR end stream + }// END event polling loop - break on event timeout OR end stream + + // Deactivate rule + hr = cpRecoGrammar->SetRuleState( NULL, NULL, SPRS_INACTIVE ); + if ( FAILED( hr ) ) + { + pfnPrint( "Error: SAPI 5.1 Unable to deactivate rule set\n" ); + return result; + } + + // close the input stream, since we're done with it + // NOTE: smart pointer will call SpStream's destructor, and consequently ::Close, but code may want to check for errors on ::Close operation + hr = cpInputStream->Close(); + if ( FAILED( hr ) ) + { + pfnPrint( "Error: SAPI 5.1 Unable to close input stream\n" ); + return result; + } + + return result; +} + +//----------------------------------------------------------------------------- +// Purpose: HACK HACK: We have to delete the RecoContext key or sapi starts to train +// itself on each iteration which was causing some problems. +// Input : hKey - +//----------------------------------------------------------------------------- +void RecursiveRegDelKey(HKEY hKey) +{ + char keyname[256]={0}; + DWORD namesize=256; + + //base case: no subkeys when RegEnumKeyEx returns error on index 0 + LONG lResult=RegEnumKeyEx(hKey,0,keyname,&namesize,NULL,NULL,NULL,NULL); + if (lResult!=ERROR_SUCCESS) + { + return; + } + + do + { + HKEY subkey; + LONG lResult2; + LONG lDelResult; + lResult2=RegOpenKeyEx(hKey,keyname,0,KEY_ALL_ACCESS,&subkey); + + if (lResult2==ERROR_SUCCESS) + { + RecursiveRegDelKey(subkey); + + RegCloseKey(subkey); + lDelResult=RegDeleteKey(hKey,keyname); + namesize=256; + //use 0 in the next function call because when you delete one, the rest shift down! + lResult=RegEnumKeyEx(hKey,0,keyname,&namesize,NULL,NULL,NULL,NULL); + } + + else + { + break; + } + + } while (lResult!=ERROR_NO_MORE_ITEMS); +} + +bool IsUseable( CWordTag *word ) +{ + if ( word->m_uiStartByte || word->m_uiEndByte ) + return true; + + return false; +} + +int FindLastUsableWord( CSentence& outwords ) +{ + int numwords = outwords.m_Words.Size(); + if ( numwords < 1 ) + { + Assert( 0 ); + return -1; + } + + for ( int i = numwords-1; i >= 0; i-- ) + { + CWordTag *check = outwords.m_Words[ i ]; + if ( IsUseable( check ) ) + { + return i; + } + } + + return -1; +} + + +int FindFirstUsableWord( CSentence& outwords ) +{ + int numwords = outwords.m_Words.Size(); + if ( numwords < 1 ) + { + Assert( 0 ); + return -1; + } + + for ( int i = 0; i < numwords; i++ ) + { + CWordTag *check = outwords.m_Words[ i ]; + if ( IsUseable( check ) ) + { + return i; + } + } + + return -1; +} + +//----------------------------------------------------------------------------- +// Purpose: Counts words which have either a valid start or end byte +// Input : *outwords - +// Output : int +//----------------------------------------------------------------------------- +int CountUsableWords( CSentence& outwords ) +{ + int count = 0; + int numwords = outwords.m_Words.Size(); + // Nothing to do + if ( numwords <= 0 ) + return count; + + for ( int i = 0; i < numwords; i++ ) + { + CWordTag *word = outwords.m_Words[ i ]; + if ( !IsUseable( word ) ) + continue; + + count++; + } + + return count; +} + + +//----------------------------------------------------------------------------- +// Purpose: Counts words which have either a valid start or end byte +// Input : *outwords - +// Output : int +//----------------------------------------------------------------------------- +int CountUnuseableWords( CSentence& outwords ) +{ + int count = 0; + int numwords = outwords.m_Words.Size(); + // Nothing to do + if ( numwords <= 0 ) + return count; + + for ( int i = 0; i < numwords; i++ ) + { + CWordTag *word = outwords.m_Words[ i ]; + if ( IsUseable( word ) ) + continue; + + count++; + } + + return count; +} + +// Keeps same relative spacing, but rebases list +void RepartitionPhonemes( CWordTag *word, unsigned int oldStart, unsigned int oldEnd ) +{ + // Repartition phonemes based on old range + float oldRange = ( float )( oldEnd - oldStart ); + float newRange = ( float )( word->m_uiEndByte - word->m_uiStartByte ); + + for ( int i = 0; i < word->m_Phonemes.Size(); i++ ) + { + CPhonemeTag *tag = word->m_Phonemes[ i ]; + Assert( tag ); + + float frac1 = 0.0f, frac2 = 0.0f; + float delta1, delta2; + + delta1 = ( float ) ( tag->m_uiStartByte - oldStart ); + delta2 = ( float ) ( tag->m_uiEndByte - oldStart ); + if ( oldRange > 0.0f ) + { + frac1 = delta1 / oldRange; + frac2 = delta2 / oldRange; + } + + tag->m_uiStartByte = word->m_uiStartByte + ( unsigned int ) ( frac1 * newRange ); + tag->m_uiEndByte = word->m_uiStartByte + ( unsigned int ) ( frac2 * newRange ); + } +} + +void PartitionWords( CSentence& outwords, int start, int end, int sampleStart, int sampleEnd ) +{ + int wordCount = end - start + 1; + Assert( wordCount >= 1 ); + int stepSize = ( sampleEnd - sampleStart ) / wordCount; + + int currentStart = sampleStart; + + for ( int i = start; i <= end; i++ ) + { + CWordTag *word = outwords.m_Words[ i ]; + Assert( word ); + + unsigned int oldStart = word->m_uiStartByte; + unsigned int oldEnd = word->m_uiEndByte; + + word->m_uiStartByte = currentStart; + word->m_uiEndByte = currentStart + stepSize; + + RepartitionPhonemes( word, oldStart, oldEnd ); + + currentStart += stepSize; + } +} + +void MergeWords( CWordTag *w1, CWordTag *w2 ) +{ + unsigned int start, end; + + start = min( w1->m_uiStartByte, w2->m_uiStartByte ); + end = max( w1->m_uiEndByte, w2->m_uiEndByte ); + + unsigned int mid = ( start + end ) / 2; + + unsigned int oldw1start, oldw2start, oldw1end, oldw2end; + + oldw1start = w1->m_uiStartByte; + oldw2start = w2->m_uiStartByte; + oldw1end = w1->m_uiEndByte; + oldw2end = w2->m_uiEndByte; + + w1->m_uiStartByte = start; + w1->m_uiEndByte = mid; + w2->m_uiStartByte = mid; + w2->m_uiEndByte = end; + + RepartitionPhonemes( w1, oldw1start, oldw1end ); + RepartitionPhonemes( w2, oldw2start, oldw2end ); +} + +void FixupZeroLengthWords( CSentence& outwords ) +{ + while ( 1 ) + { + int i; + for ( i = 0 ; i < outwords.m_Words.Size() - 1; i++ ) + { + CWordTag *current, *next; + + current = outwords.m_Words[ i ]; + next = outwords.m_Words[ i + 1 ]; + + if ( current->m_uiEndByte - current->m_uiStartByte <= 0 ) + { + MergeWords( current, next ); + break; + } + + if ( next->m_uiEndByte - next->m_uiStartByte <= 0 ) + { + MergeWords( current, next ); + break; + } + } + + if ( i >= outwords.m_Words.Size() - 1 ) + { + break; + } + } +} + +void ComputeMissingByteSpans( int numsamples, CSentence& outwords ) +{ + int numwords = outwords.m_Words.Size(); + // Nothing to do + if ( numwords <= 0 ) + return; + + int interationcount = 1; + + while( 1 ) + { + Log( "\nCompute %i\n", interationcount++ ); + LogWords( outwords ); + + int wordNumber; + + // Done! + if ( !CountUnuseableWords( outwords ) ) + { + FixupZeroLengthWords( outwords ); + break; + } + + if ( !CountUsableWords( outwords ) ) + { + // Evenly space words across full sample time + PartitionWords( outwords, 0, numwords - 1, 0, numsamples ); + break; + } + + wordNumber = FindFirstUsableWord( outwords ); + // Not the first word + if ( wordNumber > 0 ) + { + // Repartition all of the unusables and the first one starting at zero over the range + CWordTag *firstUsable = outwords.m_Words[ wordNumber ]; + Assert( firstUsable ); + + if ( firstUsable->m_uiStartByte != 0 ) + { + PartitionWords( outwords, 0, wordNumber - 1, 0, firstUsable->m_uiStartByte ); + } + else + { + PartitionWords( outwords, 0, wordNumber, 0, firstUsable->m_uiEndByte ); + } + + // Start over + continue; + } + + wordNumber = FindLastUsableWord( outwords ); + // Not the last word + if ( wordNumber >= 0 && wordNumber < numwords - 1 ) + { + // Repartition all of the unusables and the first one starting at zero over the range + CWordTag *lastUsable = outwords.m_Words[ wordNumber ]; + Assert( lastUsable ); + + if ( lastUsable->m_uiEndByte != (unsigned int)numsamples ) + { + PartitionWords( outwords, wordNumber + 1, numwords-1, lastUsable->m_uiEndByte, numsamples ); + } + else + { + PartitionWords( outwords, wordNumber, numwords-1, lastUsable->m_uiStartByte, numsamples ); + } + + // Start over + continue; + } + + // If we get here it means that the start and end of the list are okay and we just have to + // iterate across the list and fix things in the middle + int startByte = 0; + int endByte = 0; + for ( int i = 0; i < numwords ; i++ ) + { + CWordTag *word = outwords.m_Words[ i ]; + if ( IsUseable( word ) ) + { + startByte = word->m_uiEndByte; + continue; + } + + // Found the start of a chain of 1 or more unusable words + // Find the startbyte of the next usable word and count how many words we check + int wordCount = 1; + for ( int j = i + 1; j < numwords; j++ ) + { + CWordTag *next = outwords.m_Words[ j ]; + if ( IsUseable( next ) ) + { + endByte = next->m_uiStartByte; + break; + } + + wordCount++; + } + + // Now partition words across the gap and go to start again + PartitionWords( outwords, i, i + wordCount - 1, startByte, endByte ); + break; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Given a wavfile and a list of inwords, determines the word/phonene +// sample counts for the sentce +// Input : *wavfile - +// *inwords - +// *outphonemes{ text.Clear( - +// Output : SR_RESULT +//----------------------------------------------------------------------------- +static SR_RESULT SAPI_ExtractPhonemes( + const char *wavfile, + int numsamples, + void (*pfnPrint)( const char *fmt, ... ), + CSentence& inwords, + CSentence& outwords ) +{ + LogReset(); + + USES_CONVERSION; + + CSpDynamicString text; + text.Clear(); + + HKEY hkwipe; + LONG lResult = RegOpenKeyEx( HKEY_CURRENT_USER, "Software\\Microsoft\\Speech\\RecoProfiles", 0, KEY_ALL_ACCESS, &hkwipe ); + if ( lResult == ERROR_SUCCESS ) + { + RecursiveRegDelKey( hkwipe ); + RegCloseKey( hkwipe ); + } + + if ( strlen( inwords.GetText() ) <= 0 ) + { + inwords.SetTextFromWords(); + } + + // Construct a string from the inwords array + text.Append( T2W( inwords.GetText() ) ); + + // Assume failure + SR_RESULT result = SR_RESULT_ERROR; + + if ( text.Length() > 0 ) + { + CSentence sentence; + + pfnPrint( "Processing...\r\n" ); + + // Give it a try + result = ExtractPhonemes( wavfile, text, sentence, pfnPrint ); + + pfnPrint( "Finished.\r\n" ); + // PrintWordsAndPhonemes( sentence, pfnPrint ); + + // Copy results to outputs + outwords.Reset(); + + outwords.SetText( inwords.GetText() ); + + Log( "Starting\n" ); + LogWords( inwords ); + + if ( SR_RESULT_ERROR != result ) + { + int i; + + Log( "Hypothesized\n" ); + LogWords( sentence ); + + for( i = 0 ; i < sentence.m_Words.Size(); i++ ) + { + CWordTag *tag = sentence.m_Words[ i ]; + if ( tag ) + { + // Skip '...' tag + if ( stricmp( tag->GetWord(), "..." ) ) + { + CWordTag *newTag = new CWordTag( *tag ); + + outwords.m_Words.AddToTail( newTag ); + } + } + } + + // Now insert unrecognized/skipped words from original list + // + int frompos = 0, topos = 0; + + while( 1 ) + { + // End of source list + if ( frompos >= inwords.m_Words.Size() ) + break; + + const CWordTag *fromTag = inwords.m_Words[ frompos ]; + + // Reached end of destination list, just copy words over from from source list until + // we run out of source words + if ( topos >= outwords.m_Words.Size() ) + { + // Just copy words over + CWordTag *newWord = new CWordTag( *fromTag ); + + // Remove phonemes + while ( newWord->m_Phonemes.Size() > 0 ) + { + CPhonemeTag *kill = newWord->m_Phonemes[ 0 ]; + newWord->m_Phonemes.Remove( 0 ); + delete kill; + } + + outwords.m_Words.AddToTail( newWord ); + frompos++; + topos++; + continue; + } + + // Destination word + const CWordTag *toTag = outwords.m_Words[ topos ]; + + // Words match, just skip ahead + if ( !stricmp( fromTag->GetWord(), toTag->GetWord() ) ) + { + frompos++; + topos++; + continue; + } + + // The only case we handle is that something in the source wasn't in the destination + + // Find the next source word that appears in the destination + int skipAhead = frompos + 1; + bool found = false; + while ( skipAhead < inwords.m_Words.Size() ) + { + const CWordTag *sourceWord = inwords.m_Words[ skipAhead ]; + if ( !stricmp( sourceWord->GetWord(), toTag->GetWord() ) ) + { + found = true; + break; + } + + skipAhead++; + } + + // Uh oh destination has words that are not in source, just skip to next destination word? + if ( !found ) + { + topos++; + } + else + { + // Copy words from from source list into destination + // + int skipCount = skipAhead - frompos; + + while ( --skipCount>= 0 ) + { + const CWordTag *sourceWord = inwords.m_Words[ frompos++ ]; + CWordTag *newWord = new CWordTag( *sourceWord ); + + // Remove phonemes + while ( newWord->m_Phonemes.Size() > 0 ) + { + CPhonemeTag *kill = newWord->m_Phonemes[ 0 ]; + newWord->m_Phonemes.Remove( 0 ); + delete kill; + } + + outwords.m_Words.InsertBefore( topos, newWord ); + topos++; + } + + frompos++; + topos++; + } + } + + Log( "\nDone simple check\n" ); + + LogWords( outwords ); + LogPhonemes( outwords ); + + ComputeMissingByteSpans( numsamples, outwords ); + + Log( "\nFinal check\n" ); + + LogWords( outwords ); + LogPhonemes( outwords ); + } + } + else + { + pfnPrint( "Input sentence is empty!\n" ); + } + + // Return results + return result; +} + + +//----------------------------------------------------------------------------- +// Purpose: Expose the interface +//----------------------------------------------------------------------------- +class CPhonemeExtractorSAPI : public IPhonemeExtractor +{ +public: + virtual PE_APITYPE GetAPIType() const + { + return SPEECH_API_SAPI; + } + + // Used for menus, etc + virtual char const *GetName() const + { + return "MS SAPI 5.1"; + } + + SR_RESULT Extract( + const char *wavfile, + int numsamples, + void (*pfnPrint)( const char *fmt, ... ), + CSentence& inwords, + CSentence& outwords ) + { + return SAPI_ExtractPhonemes( wavfile, numsamples, pfnPrint, inwords, outwords ); + } +}; + +EXPOSE_SINGLE_INTERFACE( CPhonemeExtractorSAPI, IPhonemeExtractor, VPHONEME_EXTRACTOR_INTERFACE ); \ No newline at end of file diff --git a/mp/src/utils/phonemeextractor/phonemeextractor_ims.cpp b/mp/src/utils/phonemeextractor/phonemeextractor_ims.cpp new file mode 100644 index 00000000..70819f8e --- /dev/null +++ b/mp/src/utils/phonemeextractor/phonemeextractor_ims.cpp @@ -0,0 +1,1075 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include +#include +#include +#include +#include +#include +#include +#include + +#include "phonemeextractor/PhonemeExtractor.h" +#include "ims_helper/ims_helper.h" + +#include "tier0/dbg.h" +#include "sentence.h" +#include "PhonemeConverter.h" +#include "tier1/strtools.h" + +#define TEXTLESS_WORDNAME "[Textless]" + +static IImsHelper *talkback = NULL; + +//----------------------------------------------------------------------------- +// Purpose: Expose the interface +//----------------------------------------------------------------------------- +class CPhonemeExtractorLipSinc : public IPhonemeExtractor +{ +public: + virtual PE_APITYPE GetAPIType() const + { + return SPEECH_API_LIPSINC; + } + + // Used for menus, etc + virtual char const *GetName() const + { + return "IMS (LipSinc)"; + } + + SR_RESULT Extract( + const char *wavfile, + int numsamples, + void (*pfnPrint)( const char *fmt, ... ), + CSentence& inwords, + CSentence& outwords ); + + + CPhonemeExtractorLipSinc( void ); + ~CPhonemeExtractorLipSinc( void ); + + enum + { + MAX_WORD_LENGTH = 128, + }; +private: + + + class CAnalyzedWord + { + public: + char buffer[ MAX_WORD_LENGTH ]; + double starttime; + double endtime; + }; + + class CAnalyzedPhoneme + { + public: + char phoneme[ 32 ]; + double starttime; + double endtime; + }; + + bool InitLipSinc( void ); + void ShutdownLipSinc( void ); + + void DescribeError( TALKBACK_ERR err ); + void Printf( char const *fmt, ... ); + + bool CheckSoundFile( char const *filename ); + bool GetInitialized( void ); + void SetInitialized( bool init ); + + void (*m_pfnPrint)( const char *fmt, ... ); + + char const *ConstructInputSentence( CSentence& inwords ); + bool AttemptAnalysis( TALKBACK_ANALYSIS **ppAnalysis, char const *wavfile, CSentence& inwords ); + + char const *ApplyTBWordRules( char const *word ); + + void ProcessWords( TALKBACK_ANALYSIS *analysis, CSentence& inwords, CSentence& outwords ); + void ProcessWordsTextless( TALKBACK_ANALYSIS *analysis, CSentence& outwords ); + + int GetPhonemeIndexAtWord( TALKBACK_ANALYSIS *analysis, double time, bool checkstart ); + + int GetPhonemeIndexAtWordStart( TALKBACK_ANALYSIS *analysis, double starttime ); + int GetPhonemeIndexAtWordEnd( TALKBACK_ANALYSIS *analysis, double endtime ); + + CAnalyzedWord *GetAnalyzedWord( TALKBACK_ANALYSIS *analysis, int index ); + CAnalyzedPhoneme *GetAnalyzedPhoneme( TALKBACK_ANALYSIS *analysis, int index ); + + int ComputeByteFromTime( float time ); + + bool m_bInitialized; + + float m_flSampleCount; + float m_flDuration; + + float m_flSamplesPerSecond; + + int m_nBytesPerSample; + + HMODULE m_hHelper; +}; + +CPhonemeExtractorLipSinc::CPhonemeExtractorLipSinc( void ) +{ + m_hHelper = (HMODULE)0; + m_pfnPrint = NULL; + + m_bInitialized = false; + + m_flSampleCount = 0.0f; + m_flDuration = 0.0f; + + m_flSamplesPerSecond = 0.0f; + + m_nBytesPerSample = 0; +} + +CPhonemeExtractorLipSinc::~CPhonemeExtractorLipSinc( void ) +{ + if ( GetInitialized() ) + { + ShutdownLipSinc(); + } +} + +bool CPhonemeExtractorLipSinc::GetInitialized( void ) +{ + return m_bInitialized; +} + +void CPhonemeExtractorLipSinc::SetInitialized( bool init ) +{ + m_bInitialized = init; +} + +int CPhonemeExtractorLipSinc::ComputeByteFromTime( float time ) +{ + if ( !m_flDuration ) + return 0; + + float frac = time / m_flDuration; + + float sampleNumber = frac * m_flSampleCount; + + int bytenumber = sampleNumber * m_nBytesPerSample; + + return bytenumber; +} + +void CPhonemeExtractorLipSinc::DescribeError( TALKBACK_ERR err ) +{ + Assert( m_pfnPrint ); + + // Get the error description. + char errorDesc[256] = ""; + if ( err != TALKBACK_NOERR ) + { + talkback->TalkBackGetErrorString( err, sizeof(errorDesc), errorDesc ); + } + + // Report or log the error... + (*m_pfnPrint)( "LIPSINC ERROR: %s\n", errorDesc ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *fmt - +// .. - +//----------------------------------------------------------------------------- +void CPhonemeExtractorLipSinc::Printf( char const *fmt, ... ) +{ + Assert( m_pfnPrint ); + + char string[ 4096 ]; + + va_list argptr; + va_start( argptr, fmt ); + vsprintf( string, fmt, argptr ); + va_end( argptr ); + + (*m_pfnPrint)( "%s", string ); +} + +bool CPhonemeExtractorLipSinc::CheckSoundFile( char const *filename ) +{ + TALKBACK_SOUND_FILE_METRICS fm; + memset( &fm, 0, sizeof( fm ) ); + fm.m_size = sizeof( fm ); + + TALKBACK_ERR err = talkback->TalkBackGetSoundFileMetrics( filename, &fm ); + if ( err != TALKBACK_NOERR ) + { + DescribeError( err ); + return false; + } + + if ( fm.m_canBeAnalyzed ) + { + Printf( "%s: %.2f s, rate %i, bits %i, channels %i\n", + filename, + fm.m_duration, + fm.m_sampleRate, + fm.m_bitsPerSample, + fm.m_channelCount ); + } + + m_flDuration = fm.m_duration; + if ( m_flDuration > 0 ) + { + m_flSamplesPerSecond = m_flSampleCount / m_flDuration; + } + else + { + m_flSamplesPerSecond = 0.0f; + } + + m_nBytesPerSample = ( fm.m_bitsPerSample >> 3 ); + + m_flSampleCount /= m_nBytesPerSample; + + m_nBytesPerSample /= fm.m_channelCount; + + return fm.m_canBeAnalyzed ? true : false; +} + +typedef IImsHelper *(*pfnImsHelper)(void); + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CPhonemeExtractorLipSinc::InitLipSinc( void ) +{ + if ( GetInitialized() ) + { + return true; + } + + m_hHelper = LoadLibrary( "ims_helper.dll" ); + if ( !m_hHelper ) + { + return false; + } + + pfnImsHelper factory = (pfnImsHelper)::GetProcAddress( m_hHelper, "GetImsHelper" ); + if ( !factory ) + { + FreeLibrary( m_hHelper ); + return false; + } + + talkback = reinterpret_cast< IImsHelper * >( (*factory)() ); + if ( !talkback ) + { + FreeLibrary( m_hHelper ); + return false; + } + + char szExeName[ MAX_PATH ]; + szExeName[0] = 0; + GetModuleFileName( (HMODULE)0, szExeName, sizeof( szExeName ) ); + + char szBaseDir[ MAX_PATH ]; + Q_strncpy( szBaseDir, szExeName, sizeof( szBaseDir ) ); + + Q_StripLastDir( szBaseDir, sizeof( szBaseDir ) ); + Q_StripTrailingSlash( szBaseDir ); + Q_strlower( szBaseDir ); + + char coreDataDir[ 512 ]; + Q_snprintf( coreDataDir, sizeof( coreDataDir ), "%s\\lipsinc_data\\", + szBaseDir ); + Q_FixSlashes( coreDataDir ); + + char szCheck[ 512 ]; + Q_snprintf( szCheck, sizeof( szCheck ), "%sDtC6dal.dat", coreDataDir ); + struct __stat64 buf; + + if ( _stat64( szCheck, &buf ) != 0 ) + { + Q_snprintf( coreDataDir, sizeof( coreDataDir ), "%s\\bin\\lipsinc_data\\", + szBaseDir ); + Q_FixSlashes( coreDataDir ); + Q_snprintf( szCheck, sizeof( szCheck ), "%sDtC6dal.dat", coreDataDir ); + + if ( _stat64( szCheck, &buf ) != 0 ) + { + Error( "Unable to find talkback data files in %s.", coreDataDir ); + } + } + + TALKBACK_ERR err; + + err = talkback->TalkBackStartupLibrary( coreDataDir ); + if ( err != TALKBACK_NOERR ) + { + DescribeError( err ); + FreeLibrary( m_hHelper ); + return false; + } + + long verMajor = 0; + long verMinor = 0; + long verRevision = 0; + + err = talkback->TalkBackGetVersion( + &verMajor, + &verMinor, + &verRevision); + if ( err != TALKBACK_NOERR ) + { + DescribeError( err ); + FreeLibrary( m_hHelper ); + return false; + } + + Printf( "Lipsinc TalkBack Version %i.%i.%i\n", verMajor, verMinor, verRevision ); + + m_bInitialized = true; + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPhonemeExtractorLipSinc::ShutdownLipSinc( void ) +{ + // HACK HACK: This seems to crash on exit sometimes + __try + { + talkback->TalkBackShutdownLibrary(); + + FreeLibrary( m_hHelper ); + } + __except(EXCEPTION_EXECUTE_HANDLER ) + { + OutputDebugString( "----> Crash shutting down TALKBACK sdk, exception caught and ignored\n" ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : inwords - +// Output : char const +//----------------------------------------------------------------------------- +char const *CPhonemeExtractorLipSinc::ConstructInputSentence( CSentence& inwords ) +{ + static char sentence[ 16384 ]; + + sentence[ 0 ] = 0; + + int last = inwords.m_Words.Size() - 1; + + for ( int i = 0 ; i <= last; i++ ) + { + CWordTag *w = inwords.m_Words[ i ]; + + strcat( sentence, w->GetWord() ); + if ( i != last ) + { + strcat( sentence, " " ); + } + } + + if ( inwords.m_Words.Count() == 1 && + !Q_strnicmp( inwords.GetText(), TEXTLESS_WORDNAME, Q_strlen( TEXTLESS_WORDNAME ) ) ) + { + sentence[ 0 ] = 0; + } + + return sentence; +} + +bool CPhonemeExtractorLipSinc::AttemptAnalysis( TALKBACK_ANALYSIS **ppAnalysis, char const *wavfile, CSentence& inwords ) +{ + *ppAnalysis = NULL; + + TALKBACK_ANALYSIS_SETTINGS settings; + memset( &settings, 0, sizeof( settings ) ); + + // Set this field to sizeof(TALKBACK_ANALYSIS_SETTINGS) before using the + // structure. + settings.fSize = sizeof( TALKBACK_ANALYSIS_SETTINGS ); + + + // Default value: 30 (frames per second). + settings.fFrameRate = 100; + // Set this to 1 to optimize for flipbook output, 0 to do analysis normally. + // + // Default value: 0 (normal analysis). + settings.fOptimizeForFlipbook = 0; + // Set this to -1 to seed the random number generator with the current time. + // Any other number will be used directly for the random number seed, which + // is useful if you want repeatable speech gestures. This value does not + // influence lip-synching at all. + // + // Default value: -1 (use current time). + settings.fRandomSeed = -1; + // Path to the configuration (.INI) file with phoneme-to-speech-target + // mapping. Set this to NULL to use the default mapping. + // + // Default value: NULL (use default mapping). + settings.fConfigFile = NULL; + + char const *text = ConstructInputSentence( inwords ); + + Printf( "Analyzing: \"%s\"\n", text[ 0 ] ? text : TEXTLESS_WORDNAME ); + + TALKBACK_ERR err = talkback->TalkBackGetAnalysis( + ppAnalysis, + wavfile, + text, + &settings ); + + if ( err != TALKBACK_NOERR ) + { + DescribeError( err ); + return false; + } + + Printf( "Analysis successful...\n" ); + + return true; +} + +typedef struct +{ + TALKBACK_PHONEME phoneme; + char const *string; +} TBPHONEMES_t; + +static TBPHONEMES_t g_TBPhonemeList[]= +{ + { TALKBACK_PHONEME_IY, "iy" }, + { TALKBACK_PHONEME_IH, "ih" }, + { TALKBACK_PHONEME_EH, "eh" }, + { TALKBACK_PHONEME_EY, "ey" }, + { TALKBACK_PHONEME_AE, "ae" }, + { TALKBACK_PHONEME_AA, "aa" }, + { TALKBACK_PHONEME_AW, "aw" }, + { TALKBACK_PHONEME_AY, "ay" }, + { TALKBACK_PHONEME_AH, "ah" }, + { TALKBACK_PHONEME_AO, "ao" }, + { TALKBACK_PHONEME_OY, "oy" }, + { TALKBACK_PHONEME_OW, "ow" }, + { TALKBACK_PHONEME_UH, "uh" }, + { TALKBACK_PHONEME_UW, "uw" }, + { TALKBACK_PHONEME_ER, "er" }, + { TALKBACK_PHONEME_AX, "ax" }, + { TALKBACK_PHONEME_S, "s" }, + { TALKBACK_PHONEME_SH, "sh" }, + { TALKBACK_PHONEME_Z, "z" }, + { TALKBACK_PHONEME_ZH, "zh" }, + { TALKBACK_PHONEME_F, "f" }, + { TALKBACK_PHONEME_TH, "th" }, + { TALKBACK_PHONEME_V, "v" }, + { TALKBACK_PHONEME_DH, "dh" }, + { TALKBACK_PHONEME_M, "m" }, + { TALKBACK_PHONEME_N, "n" }, + { TALKBACK_PHONEME_NG, "ng" }, + { TALKBACK_PHONEME_L, "l" }, + { TALKBACK_PHONEME_R, "r" }, + { TALKBACK_PHONEME_W, "w" }, + { TALKBACK_PHONEME_Y, "y" }, + { TALKBACK_PHONEME_HH, "hh" }, + { TALKBACK_PHONEME_B, "b" }, + { TALKBACK_PHONEME_D, "d" }, + { TALKBACK_PHONEME_JH, "jh" }, + { TALKBACK_PHONEME_G, "g" }, + { TALKBACK_PHONEME_P, "p" }, + { TALKBACK_PHONEME_T, "t" }, + { TALKBACK_PHONEME_K, "k" }, + { TALKBACK_PHONEME_CH, "ch" }, + { TALKBACK_PHONEME_SIL, "" }, + { -1, NULL } +}; + +char const *TBPhonemeToString( TALKBACK_PHONEME phoneme ) +{ + if ( phoneme < TALKBACK_PHONEME_FIRST || phoneme > TALKBACK_PHONEME_LAST ) + { + return "Bogus"; + } + + TBPHONEMES_t *item = &g_TBPhonemeList[ phoneme ]; + return item->string; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *analysis - +// time - +// start - +// Output : int +//----------------------------------------------------------------------------- +int CPhonemeExtractorLipSinc::GetPhonemeIndexAtWord( TALKBACK_ANALYSIS *analysis, double time, bool start ) +{ + long count; + + TALKBACK_ERR err = talkback->TalkBackGetNumPhonemes( analysis, &count ); + if ( err != TALKBACK_NOERR ) + { + DescribeError( err ); + return -1; + } + + if ( count <= 0L ) + return -1; + + // Bogus + if ( count >= 100000L ) + return -1; + + for ( int i = 0; i < (int)count; i++ ) + { + TALKBACK_PHONEME tbPhoneme = TALKBACK_PHONEME_INVALID; + err = talkback->TalkBackGetPhonemeEnum( analysis, i, &tbPhoneme ); + if ( err != TALKBACK_NOERR ) + { + DescribeError( err ); + continue; + } + + double t; + + if ( start ) + { + err = talkback->TalkBackGetPhonemeStartTime( analysis, i, &t ); + } + else + { + err = talkback->TalkBackGetPhonemeEndTime( analysis, i, &t ); + } + + if ( err != TALKBACK_NOERR ) + { + DescribeError( err ); + continue; + } + + if ( t == time ) + { + return i; + } + } + + return -1; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *analysis - +// starttime - +// Output : int +//----------------------------------------------------------------------------- +int CPhonemeExtractorLipSinc::GetPhonemeIndexAtWordStart( TALKBACK_ANALYSIS *analysis, double starttime ) +{ + return GetPhonemeIndexAtWord( analysis, starttime, true ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *analysis - +// endtime - +// Output : int +//----------------------------------------------------------------------------- +int CPhonemeExtractorLipSinc::GetPhonemeIndexAtWordEnd( TALKBACK_ANALYSIS *analysis, double endtime ) +{ + return GetPhonemeIndexAtWord( analysis, endtime, false ); +} + +CPhonemeExtractorLipSinc::CAnalyzedPhoneme *CPhonemeExtractorLipSinc::GetAnalyzedPhoneme( TALKBACK_ANALYSIS *analysis, int index ) +{ + static CAnalyzedPhoneme p; + + memset( &p, 0, sizeof( p ) ); + + TALKBACK_PHONEME tb; + + TALKBACK_ERR err = talkback->TalkBackGetPhonemeEnum( analysis, index, &tb ); + if ( err != TALKBACK_NOERR ) + { + DescribeError( err ); + return NULL; + } + + strcpy( p.phoneme, TBPhonemeToString( tb ) ); + + err = talkback->TalkBackGetPhonemeStartTime( analysis, index, &p.starttime ); + if ( err != TALKBACK_NOERR ) + { + DescribeError( err ); + return NULL; + } + err = talkback->TalkBackGetPhonemeEndTime( analysis, index, &p.endtime ); + if ( err != TALKBACK_NOERR ) + { + DescribeError( err ); + return NULL; + } + + return &p; +} + +CPhonemeExtractorLipSinc::CAnalyzedWord *CPhonemeExtractorLipSinc::GetAnalyzedWord( TALKBACK_ANALYSIS *analysis, int index ) +{ + static CAnalyzedWord w; + + memset( &w, 0, sizeof( w ) ); + + long chars = sizeof( w.buffer ); + + TALKBACK_ERR err = talkback->TalkBackGetWord( analysis, index, chars, w.buffer ); + if ( err != TALKBACK_NOERR ) + { + DescribeError( err ); + return NULL; + } + + err = talkback->TalkBackGetWordStartTime( analysis, index, &w.starttime ); + if ( err != TALKBACK_NOERR ) + { + DescribeError( err ); + return NULL; + } + err = talkback->TalkBackGetWordEndTime( analysis, index, &w.endtime ); + if ( err != TALKBACK_NOERR ) + { + DescribeError( err ); + return NULL; + } + + return &w; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *w1 - +// *w2 - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool FuzzyWordMatch( char const *w1, char const *w2 ) +{ + int len1 = strlen( w1 ); + int len2 = strlen( w2 ); + + int minlen = min( len1, len2 ); + + // Found a match + if ( !strnicmp( w1, w2, minlen ) ) + return true; + + int letterdiff = abs( len1 - len2 ); + // More than three letters different, don't bother + if ( letterdiff > 5 ) + return false; + + // Compute a "delta" + char *p1 = (char *)w1; + char *p2 = (char *)w2; + + CUtlVector word1; + CUtlVector word2; + + while ( *p1 ) + { + if ( V_isalpha( *p1 ) ) + { + word1.AddToTail( *p1 ); + } + p1++; + } + + while ( *p2 ) + { + if ( V_isalpha( *p2 ) ) + { + word2.AddToTail( *p2 ); + } + p2++; + } + + int i; + for ( i = 0; i < word1.Size(); i++ ) + { + char c = word1[ i ]; + + // See if c is in word 2, if so subtract it out + int idx = word2.Find( c ); + + if ( idx != word2.InvalidIndex() ) + { + word2.Remove( idx ); + } + } + + if ( word2.Size() <= letterdiff ) + return true; + + word2.RemoveAll(); + + while ( *p2 ) + { + if ( V_isalpha( *p2 ) ) + { + word2.AddToTail( *p2 ); + } + p2++; + } + + for ( i = 0; i < word2.Size(); i++ ) + { + char c = word2[ i ]; + + // See if c is in word 2, if so subtract it out + int idx = word1.Find( c ); + + if ( idx != word1.InvalidIndex() ) + { + word1.Remove( idx ); + } + } + + if ( word1.Size() <= letterdiff ) + return true; + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: For foreign language stuff, if inwords is empty, process anyway... +// Input : *analysis - +// outwords - +//----------------------------------------------------------------------------- +void CPhonemeExtractorLipSinc::ProcessWordsTextless( TALKBACK_ANALYSIS *analysis, CSentence& outwords ) +{ + long count; + + TALKBACK_ERR err = talkback->TalkBackGetNumPhonemes( analysis, &count ); + if ( err != TALKBACK_NOERR ) + { + DescribeError( err ); + return; + } + + CWordTag *newWord = new CWordTag; + + newWord->SetWord( TEXTLESS_WORDNAME ); + + float starttime = 0.0f; + float endtime = 1.0f; + + + for ( int i = 0; i < count; ++i ) + { + // Get phoneme and timing info + CAnalyzedPhoneme *ph = GetAnalyzedPhoneme( analysis, i ); + if ( !ph ) + continue; + + CPhonemeTag *ptag = new CPhonemeTag; + + if ( i == 0 || ( ph->starttime < starttime ) ) + { + starttime = ph->starttime; + } + + if ( i == 0 || ( ph->endtime > endtime ) ) + { + endtime = ph->endtime; + } + + ptag->SetStartTime( ph->starttime ); + ptag->SetEndTime( ph->endtime ); + + ptag->m_uiStartByte = ComputeByteFromTime( ph->starttime ); + ptag->m_uiEndByte = ComputeByteFromTime( ph->endtime ); + + ptag->SetTag( ph->phoneme ); + ptag->SetPhonemeCode( TextToPhoneme( ptag->GetTag() ) ); + + newWord->m_Phonemes.AddToTail( ptag ); + } + + newWord->m_flStartTime = starttime; + newWord->m_flEndTime = endtime; + + newWord->m_uiStartByte = ComputeByteFromTime( starttime ); + newWord->m_uiEndByte = ComputeByteFromTime( endtime ); + + outwords.Reset(); + outwords.AddWordTag( newWord ); + outwords.SetTextFromWords(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *analysis - +// inwords - +// outwords - +//----------------------------------------------------------------------------- +void CPhonemeExtractorLipSinc::ProcessWords( TALKBACK_ANALYSIS *analysis, CSentence& inwords, CSentence& outwords ) +{ + long count; + + TALKBACK_ERR err = talkback->TalkBackGetNumWords( analysis, &count ); + if ( err != TALKBACK_NOERR ) + { + DescribeError( err ); + return; + } + + if ( count <= 0L ) + { + if ( inwords.m_Words.Count() == 0 || + !Q_strnicmp( inwords.GetText(), TEXTLESS_WORDNAME, Q_strlen( TEXTLESS_WORDNAME ) ) ) + { + ProcessWordsTextless( analysis, outwords ); + } + return; + } + + // Bogus + if ( count >= 100000L ) + return; + + int inwordpos = 0; + int awordpos = 0; + + outwords.Reset(); + + char previous[ 256 ]; + previous[ 0 ] = 0; + + while ( inwordpos < inwords.m_Words.Size() ) + { + CWordTag *in = inwords.m_Words[ inwordpos ]; + + if ( awordpos >= count ) + { + // Just copy the rest over without phonemes + CWordTag *copy = new CWordTag( *in ); + + outwords.AddWordTag( copy ); + + inwordpos++; + continue; + } + + // Should never fail + CAnalyzedWord *w = GetAnalyzedWord( analysis, awordpos ); + if ( !w ) + { + return; + } + + if ( !stricmp( w->buffer, "" ) ) + { + awordpos++; + continue; + } + + char const *check = ApplyTBWordRules( in->GetWord() ); + if ( !FuzzyWordMatch( check, w->buffer ) ) + { + bool advance_input = true; + if ( previous[ 0 ] ) + { + if ( FuzzyWordMatch( previous, w->buffer ) ) + { + advance_input = false; + } + } + + if ( advance_input ) + { + inwordpos++; + } + awordpos++; + continue; + } + strcpy( previous, check ); + + CWordTag *newWord = new CWordTag; + + newWord->SetWord( in->GetWord() ); + + newWord->m_flStartTime = w->starttime; + newWord->m_flEndTime = w->endtime; + + newWord->m_uiStartByte = ComputeByteFromTime( w->starttime ); + newWord->m_uiEndByte = ComputeByteFromTime( w->endtime ); + + int phonemestart, phonemeend; + + phonemestart = GetPhonemeIndexAtWordStart( analysis, w->starttime ); + phonemeend = GetPhonemeIndexAtWordEnd( analysis, w->endtime ); + + if ( phonemestart >= 0 && phonemeend >= 0 ) + { + for ( ; phonemestart <= phonemeend; phonemestart++ ) + { + // Get phoneme and timing info + CAnalyzedPhoneme *ph = GetAnalyzedPhoneme( analysis, phonemestart ); + if ( !ph ) + continue; + + CPhonemeTag *ptag = new CPhonemeTag; + ptag->SetStartTime( ph->starttime ); + ptag->SetEndTime( ph->endtime ); + + ptag->m_uiStartByte = ComputeByteFromTime( ph->starttime ); + ptag->m_uiEndByte = ComputeByteFromTime( ph->endtime ); + + ptag->SetTag( ph->phoneme ); + ptag->SetPhonemeCode( TextToPhoneme( ptag->GetTag() ) ); + + newWord->m_Phonemes.AddToTail( ptag ); + } + } + + outwords.AddWordTag( newWord ); + inwordpos++; + awordpos++; + } +} + +char const *CPhonemeExtractorLipSinc::ApplyTBWordRules( char const *word ) +{ + static char outword[ 256 ]; + + char const *in = word; + char *out = outword; + + while ( *in && ( ( out - outword ) <= 255 ) ) + { + if ( *in == '\t' || + *in == ' ' || + *in == '\n' || + *in == '-' || + *in == '.' || + *in == ',' || + *in == ';' || + *in == '?' || + *in == '"' || + *in == ':' || + *in == '(' || + *in == ')' ) + { + in++; + *out++ = ' '; + continue; + } + + if ( !V_isprint( *in ) ) + { + in++; + continue; + } + + if ( *in >= 128 ) + { + in++; + continue; + } + + // Skip numbers + if ( *in >= '0' && *in <= '9' ) + { + in++; + continue; + } + + // Convert all letters to upper case + if ( *in >= 'a' && *in <= 'z' ) + { + *out++ = ( *in++ ) - 'a' + 'A'; + continue; + } + + if ( *in >= 'A' && *in <= 'Z' ) + { + *out++ = *in++; + continue; + } + + if ( *in == '\'' ) + { + *out++ = *in++; + continue; + } + + in++; + } + + *out = 0; + + return outword; +} + +//----------------------------------------------------------------------------- +// Purpose: Given a wavfile and a list of inwords, determines the word/phonene +// sample counts for the sentce +// Output : SR_RESULT +//----------------------------------------------------------------------------- +SR_RESULT CPhonemeExtractorLipSinc::Extract( + const char *wavfile, + int numsamples, + void (*pfnPrint)( const char *fmt, ... ), + CSentence& inwords, + CSentence& outwords ) +{ + // g_enableTalkBackDebuggingOutput = 1; + + m_pfnPrint = pfnPrint; + + if ( !InitLipSinc() ) + { + return SR_RESULT_ERROR; + } + + m_flSampleCount = numsamples; + + if ( !CheckSoundFile( wavfile ) ) + { + FreeLibrary( m_hHelper ); + return SR_RESULT_ERROR; + } + + TALKBACK_ANALYSIS *analysis = NULL; + + if ( !AttemptAnalysis( &analysis, wavfile, inwords ) ) + { + FreeLibrary( m_hHelper ); + return SR_RESULT_FAILED; + } + + if ( strlen( inwords.GetText() ) <= 0 ) + { + inwords.SetTextFromWords(); + } + + outwords = inwords; + + // Examine data + ProcessWords( analysis, inwords, outwords ); + + if ( analysis ) + { + talkback->TalkBackFreeAnalysis( &analysis ); + } + + return SR_RESULT_SUCCESS; +} + +EXPOSE_SINGLE_INTERFACE( CPhonemeExtractorLipSinc, IPhonemeExtractor, VPHONEME_EXTRACTOR_INTERFACE ); \ No newline at end of file diff --git a/mp/src/utils/phonemeextractor/talkback.doc b/mp/src/utils/phonemeextractor/talkback.doc new file mode 100644 index 00000000..8217b79a Binary files /dev/null and b/mp/src/utils/phonemeextractor/talkback.doc differ diff --git a/mp/src/utils/phonemeextractor/talkback.h b/mp/src/utils/phonemeextractor/talkback.h new file mode 100644 index 00000000..3a1b179a --- /dev/null +++ b/mp/src/utils/phonemeextractor/talkback.h @@ -0,0 +1,732 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// ============================================================================= +// Interface to the LIPSinc TalkBack 1.1 library (TalkBack_*.lib). +// +// Copyright © 1998-2002 LIPSinc. All rights reserved. + +#if !defined(TalkBack_h) +#define TalkBack_h + +#include // size_t. + +// Enforce a C API. +#if defined(__cplusplus) +extern "C" +{ +#endif + +// ----------------------------------------------------------------------------- +// Use the preprocessor to make the new API compatible with the old one. + +#define TalkbackStartupLibrary TalkBackStartupLibrary +#define TalkbackShutdownLibrary TalkBackShutdownLibrary +#define TalkbackGetVersion TalkBackGetVersion +#define TalkbackGetVersionString TalkBackGetVersionString +#define TalkbackCheckSoundFile TalkBackCheckSoundFile +#define TalkbackCheckSpokenText TalkBackCheckSpokenText +#define TalkbackGetErrorString TalkBackGetErrorString +#define TalkbackGetAnalysis TalkBackGetAnalysis +#define TalkbackFreeAnalysis TalkBackFreeAnalysis +#define TalkbackGetFirstFrameNum TalkBackGetFirstFrameNum +#define TalkbackGetLastFrameNum TalkBackGetLastFrameNum +#define TalkbackGetFrameStartTime TalkBackGetFrameStartTime +#define TalkbackGetFrameEndTime TalkBackGetFrameEndTime +#define TalkbackGetNumPhonemes TalkBackGetNumPhonemes +#define TalkbackGetPhonemeEnum TalkBackGetPhonemeEnum +#define TalkbackGetPhonemeStartTime TalkBackGetPhonemeStartTime +#define TalkbackGetPhonemeEndTime TalkBackGetPhonemeEndTime +#define TalkbackInsertPhoneme TalkBackInsertPhoneme +#define TalkbackDeletePhoneme TalkBackDeletePhoneme +#define TalkbackChangePhonemeStart TalkBackChangePhonemeStart +#define TalkbackChangePhonemeEnd TalkBackChangePhonemeEnd +#define TalkbackChangePhonemeEnum TalkBackChangePhonemeEnum +#define TalkbackGetNumWords TalkBackGetNumWords +#define TalkbackGetWord TalkBackGetWord +#define TalkbackGetWordStartTime TalkBackGetWordStartTime +#define TalkbackGetWordEndTime TalkBackGetWordEndTime +#define TalkbackGetNumSpeechTargetTracks TalkBackGetNumSpeechTargetTracks +#define TalkbackGetNumSpeechTargetKeys TalkBackGetNumSpeechTargetKeys +#define TalkbackGetSpeechTargetKeyInfo TalkBackGetSpeechTargetKeyInfo +#define TalkbackGetSpeechTargetValueAtFrame TalkBackGetSpeechTargetValueAtFrame +#define TalkbackGetDominantSpeechTargetAtFrame TalkBackGetDominantSpeechTargetAtFrame +#define TalkbackGetSpeechTargetValueAtTime TalkBackGetSpeechTargetValueAtTime +#define TalkbackGetSpeechTargetDerivativesAtTime TalkBackGetSpeechTargetDerivativesAtTime +#define TalkbackGetNumGestureTracks TalkBackGetNumGestureTracks +#define TalkbackGetNumGestureKeys TalkBackGetNumGestureKeys +#define TalkbackGetGestureKeyInfo TalkBackGetGestureKeyInfo +#define TalkbackGetGestureValueAtFrame TalkBackGetGestureValueAtFrame +#define TalkbackGetGestureValueAtTime TalkBackGetGestureValueAtTime +#define TalkbackGetGestureDerivativesAtTime TalkBackGetGestureDerivativesAtTime + +// ----------------------------------------------------------------------------- +// For debug builds, set this to a non-zero value to get verbose debugging +// output from TalkBack. + +extern int g_enableTalkBackDebuggingOutput; + +// ----------------------------------------------------------------------------- +// Miscellaneous constants. + +// For calling TalkBackGetAnalysis() with all defaults. +#define TALKBACK_DEFAULT_SETTINGS NULL + +// For setting the iSoundText parameter in TalkBackGetAnalysis() to "no text." +#define TALKBACK_NO_TEXT NULL + +// Handy constants for TALKBACK_ANALYSIS_SETTINGS fields: + + // For setting fSize. +#define TALKBACK_SETTINGS_SIZE sizeof(TALKBACK_ANALYSIS_SETTINGS) + // For setting fFrameRate to the + // default. +#define TALKBACK_DEFAULT_FRAME_RATE 30 + // For setting fOptimizeForFlipbook + // to *not* optimize for flipbook. +#define TALKBACK_OPTIMIZE_FOR_FLIPBOOK_OFF 0 + // For setting fOptimizeForFlipbook + // to optimize for flipbook. +#define TALKBACK_OPTIMIZE_FOR_FLIPBOOK_ON 1 + // For setting fRandomSeed to use the + // current time to seed the random + // number generator and thereby get + // non-deterministic speech gestures. +#define TALKBACK_RANDOM_SEED -1 + // For setting fConfigFile to "no + // config file." +#define TALKBACK_NO_CONFIG_FILE NULL + +// ----------------------------------------------------------------------------- +// Data types. + +// TALKBACK_NOERR if successful, TalkBack error code if not. +typedef long TALKBACK_ERR; + +// Opaque analysis results. +typedef void TALKBACK_ANALYSIS; + +// Speech target. +typedef long TALKBACK_SPEECH_TARGET; + +// Speech gesture. +typedef long TALKBACK_GESTURE; + +// Phoneme. +typedef long TALKBACK_PHONEME; + +// ----------------------------------------------------------------------------- +// Data structures. + +#pragma pack(push, 1) + +// Optional analysis settings passed to TalkBackGetAnalysis(). +typedef struct +{ + // Set this field to sizeof(TALKBACK_ANALYSIS_SETTINGS) before using the + // structure. + long fSize; + // Frame rate for analysis. This only matters if you will be using *AtFrame + // functions. + // + // Default value: 30 (frames per second). + long fFrameRate; + // Set this to 1 to optimize for flipbook output, 0 to do analysis normally. + // + // Default value: 0 (normal analysis). + long fOptimizeForFlipbook; + // Set this to -1 to seed the random number generator with the current time. + // Any other number will be used directly for the random number seed, which + // is useful if you want repeatable speech gestures. This value does not + // influence lip-synching at all. + // + // Default value: -1 (use current time). + long fRandomSeed; + // Path to the configuration (.INI) file with phoneme-to-speech-target + // mapping. Set this to NULL to use the default mapping. + // + // Default value: NULL (use default mapping). + char const *fConfigFile; +} TALKBACK_ANALYSIS_SETTINGS; + +typedef struct +{ + // Set this field to sizeof(TALKBACK_SOUND_FILE_METRICS) before using the + // structure. This will allow the structure to evolve if necessary. + size_t m_size; + // Bits per sample. + long m_bitsPerSample; + // Sample rate in Hertz. + long m_sampleRate; + // Duration of the audio in seconds. + double m_duration; + // 1 if the sound file can be analyzed, 0 if not. + long m_canBeAnalyzed; + // 1 if the sound file is clipped, 0 if not. + long m_isClipped; + // The decibel range of the sound file. + double m_decibelRange; + // A quality value for the sound file: the nominal range is 0 to 100. Try + // to keep it above 45 for good results. + int m_quality; + + // Added for version 2 of the metrics structure: + // --------------------------------------------- + // The number of channels in the sound file: 1 for mono, 2 for stereo, etc. + long m_channelCount; +} TALKBACK_SOUND_FILE_METRICS; + +#pragma pack(pop) + +// ----------------------------------------------------------------------------- +// Constants. + +// TalkBack error codes. Use TalkBackGetErrorString() to return text +// descriptions for these codes. +enum +{ + // Windows convention: set this bit to indicate an application-defined error + // code. + BIT29 = (1 << 29), + // Success (not an error). + TALKBACK_NOERR = 0, + // The first error code: useful for iterating through the error codes. + TALKBACK_ERROR_FIRST = 4201 | BIT29, + // Generic error. + TALKBACK_ERROR = TALKBACK_ERROR_FIRST, + // TalkBackStartupLibrary() failed [internal error] or was never called. + TALKBACK_STARTUP_FAILED_ERR, + // TalkBackShutdownLibrary() failed, either because + // TalkBackStartupLibrary() was never called or because + // TalkBackShutdownLibrary() has already been called. + TALKBACK_SHUTDOWN_FAILED_ERR, + // The TalkBack data files could not be found [invalid path or missing + // files]. + TALKBACK_CORE_DATA_NOT_FOUND_ERR, + // One or more of the parameters are NULL. + TALKBACK_NULL_PARAMETER_ERR, + // One or more of the parameters is invalid. + TALKBACK_INVALID_PARAMETER_ERR, + // The analysis object pointer is invalid. + TALKBACK_INVALID_ANALYSIS_ERR, + // Analysis failed [the sound file cannot be analyzed or an internal error + // occurred]. + TALKBACK_ANALYSIS_FAILED_ERR, + // One or more of the indices (track, key, frame, word, phoneme) are + // invalid (out of range). + TALKBACK_INVALID_INDEX_ERR, + // The time parameter is invalid (out of range). + TALKBACK_INVALID_TIME_ERR, + // A serious internal error occurred in TalkBack; please alert LIPSinc by + // sending mail with a description of how the error was triggered to + // talkback-support@LIPSinc.com. + TALKBACK_INTERNAL_ERR, + // Could not open the specified sound file. + TALKBACK_COULD_NOT_LOAD_SOUND_ERR, + // TalkBackStartupLibrary() has not been called. + TALKBACK_STARTUP_NOT_CALLED, + // The configuration file specified in the TALKBACK_ANALYSIS_SETTINGS + // structure is invalid. + TALKBACK_CONFIG_PARSE_ERROR, + // The last error code: useful for iterating through the error codes. + TALKBACK_ERROR_LAST = TALKBACK_CONFIG_PARSE_ERROR +}; + +// Default lip-synching track identifiers. +// +// NOTE: these track identifiers apply *only* to the default phoneme-to-track +// mapping! Consult the TalkBack Reference Guide for more details. +// +// NOTE: these values are valid *only* if you use the default mapping and are +// provided as a convenience. If you use your own mapping, these values +// are invalid and should not be used. + +enum +{ + TALKBACK_SPEECH_TARGET_INVALID = -1, + TALKBACK_SPEECH_TARGET_FIRST = 0, + TALKBACK_SPEECH_TARGET_EAT = TALKBACK_SPEECH_TARGET_FIRST, // 0 + TALKBACK_SPEECH_TARGET_EARTH, // 1 + TALKBACK_SPEECH_TARGET_IF, // 2 + TALKBACK_SPEECH_TARGET_OX, // 3 + TALKBACK_SPEECH_TARGET_OAT, // 4 + TALKBACK_SPEECH_TARGET_WET, // 5 + TALKBACK_SPEECH_TARGET_SIZE, // 6 + TALKBACK_SPEECH_TARGET_CHURCH, // 7 + TALKBACK_SPEECH_TARGET_FAVE, // 8 + TALKBACK_SPEECH_TARGET_THOUGH, // 9 + TALKBACK_SPEECH_TARGET_TOLD, // 10 + TALKBACK_SPEECH_TARGET_BUMP, // 11 + TALKBACK_SPEECH_TARGET_NEW, // 12 + TALKBACK_SPEECH_TARGET_ROAR, // 13 + TALKBACK_SPEECH_TARGET_CAGE, // 14 + TALKBACK_SPEECH_TARGET_LAST = TALKBACK_SPEECH_TARGET_CAGE, // 14 + TALKBACK_NUM_SPEECH_TARGETS // 15 (0..14) +}; + +// Speech gesture track identifiers. + +enum +{ + TALKBACK_GESTURE_INVALID = -1, + TALKBACK_GESTURE_FIRST = 0, + TALKBACK_GESTURE_EYEBROW_RAISE_LEFT = TALKBACK_GESTURE_FIRST, // 0 + TALKBACK_GESTURE_EYEBROW_RAISE_RIGHT, // 1 + TALKBACK_GESTURE_BLINK_LEFT, // 2 + TALKBACK_GESTURE_BLINK_RIGHT, // 3 + TALKBACK_GESTURE_HEAD_BEND, // 4 + TALKBACK_GESTURE_HEAD_SIDE_SIDE, // 5 + TALKBACK_GESTURE_HEAD_TWIST, // 6 + TALKBACK_GESTURE_EYE_SIDE_SIDE_LEFT, // 7 + TALKBACK_GESTURE_EYE_SIDE_SIDE_RIGHT, // 8 + TALKBACK_GESTURE_EYE_UP_DOWN_LEFT, // 9 + TALKBACK_GESTURE_EYE_UP_DOWN_RIGHT, // 10 + TALKBACK_GESTURE_LAST = TALKBACK_GESTURE_EYE_UP_DOWN_RIGHT, // 10 + TALKBACK_NUM_GESTURES // 11 (0..10) +}; + +// Phoneme identifiers. + +enum +{ + TALKBACK_PHONEME_INVALID = -1, + TALKBACK_PHONEME_FIRST = 0, + TALKBACK_PHONEME_IY = TALKBACK_PHONEME_FIRST, // 0 + TALKBACK_PHONEME_IH, // 1 + TALKBACK_PHONEME_EH, // 2 + TALKBACK_PHONEME_EY, // 3 + TALKBACK_PHONEME_AE, // 4 + TALKBACK_PHONEME_AA, // 5 + TALKBACK_PHONEME_AW, // 6 + TALKBACK_PHONEME_AY, // 7 + TALKBACK_PHONEME_AH, // 8 + TALKBACK_PHONEME_AO, // 9 + TALKBACK_PHONEME_OY, // 10 + TALKBACK_PHONEME_OW, // 11 + TALKBACK_PHONEME_UH, // 12 + TALKBACK_PHONEME_UW, // 13 + TALKBACK_PHONEME_ER, // 14 + TALKBACK_PHONEME_AX, // 15 + TALKBACK_PHONEME_S, // 16 + TALKBACK_PHONEME_SH, // 17 + TALKBACK_PHONEME_Z, // 18 + TALKBACK_PHONEME_ZH, // 19 + TALKBACK_PHONEME_F, // 20 + TALKBACK_PHONEME_TH, // 21 + TALKBACK_PHONEME_V, // 22 + TALKBACK_PHONEME_DH, // 23 + TALKBACK_PHONEME_M, // 24 + TALKBACK_PHONEME_N, // 25 + TALKBACK_PHONEME_NG, // 26 + TALKBACK_PHONEME_L, // 27 + TALKBACK_PHONEME_R, // 28 + TALKBACK_PHONEME_W, // 29 + TALKBACK_PHONEME_Y, // 30 + TALKBACK_PHONEME_HH, // 31 + TALKBACK_PHONEME_B, // 32 + TALKBACK_PHONEME_D, // 33 + TALKBACK_PHONEME_JH, // 34 + TALKBACK_PHONEME_G, // 35 + TALKBACK_PHONEME_P, // 36 + TALKBACK_PHONEME_T, // 37 + TALKBACK_PHONEME_K, // 38 + TALKBACK_PHONEME_CH, // 39 + TALKBACK_PHONEME_SIL, // 40 + TALKBACK_PHONEME_LAST = TALKBACK_PHONEME_SIL, // 40 + TALKBACK_NUM_PHONEMES // 41 (0..40) +}; + +// ----------------------------------------------------------------------------- +// Function declarations. + +// --------------------------- +// Startup/shutdown functions. +// --------------------------- + +// Must be the first function called when using TalkBack. +TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. +TalkBackStartupLibrary( + char const *iCoreDataDir); // IN: full path of folder containing TalkBack data files. + +// Should be the last function called when using TalkBack. +TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. +TalkBackShutdownLibrary(); // IN: nothing. + +// ------------------ +// Version functions. +// ------------------ + +// Gets the TalkBack version number. +TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. +TalkBackGetVersion( + long *oMajor, // OUT: major version number. + long *oMinor, // OUT: minor version number. + long *oRevision); // OUT: revision version number. + +// Gets the TalkBack version number as a string. +TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. +TalkBackGetVersionString( + long iMaxChars, // IN: size of version string buffer. + char *oVersion); // OUT: version string buffer. + +// ------------------ +// Utility functions. +// ------------------ + +// Checks whether a sound file can be analyzed and returns some quality metrics. +// +// NOTE: this function is deprecated and has been supplanted by +// TalkBackGetSoundFileMetrics(). +TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. +TalkBackCheckSoundFile( + char const *iSoundFileName, // IN: name of sound file to be checked. + long *oCanBeAnalyzed, // OUT: 1 if sound can be analyzed, 0 if not. + long *oIsClipped, // OUT: 1 if sound is clipped, 0 if not. + double *oDecibelRange); // OUT: used decibel range of sound. + +// Returns metrics for the specified sound file. +TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. +TalkBackGetSoundFileMetrics( + char const *iSoundFileName, // IN: name of sound file to be checked. + TALKBACK_SOUND_FILE_METRICS *ioMetrics); // IN/OUT: address of a structure where the metrics will be stored. + +// Checks whether text can be used for text-based analysis, returning the text +// as it will be analyzed. +TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. +TalkBackCheckSpokenText( + char const *iSpokenText, // IN: text to check. + long iMaxChars, // IN: size of analyzed text buffer. + char *oAnalyzedText); // OUT: buffer for text as it will be analyzed. + +// Convert a TalkBack error code to a description string. +TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. +TalkBackGetErrorString( + TALKBACK_ERR iErrorCode, // IN: TalkBack error code to convert. + long iMaxChars, // IN: size of the buffer. + char *oErrorString); // OUT: buffer for the description string. + +// Gets the error code and text for the most recent TalkBack error. +TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. +TalkBackGetLastError( + long iMaxChars, // IN: size of the buffer. + char *oErrorString, // OUT: buffer for the description string. + TALKBACK_ERR *oErrorCode); // OUT: most recent TalkBack error code. + +// ------------------- +// Analysis functions. +// ------------------- + +// Gets an opaque TALKBACK_ANALYSIS object. This object is then queried with the +// TalkBackGet* functions below. +TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. +TalkBackGetAnalysis( + TALKBACK_ANALYSIS **ioAnalysis, // IN/OUT: address of a TALKBACK_ANALYSIS *variable where analysis will be stored. + char const *iSoundFileName, // IN: name of the sound file to analyze. + char const *iSoundText, // IN: text spoken in sound file (can be NULL to use textless analysis). + TALKBACK_ANALYSIS_SETTINGS *iSettings); // IN: pointer to a TALKBACK_ANALYSIS_SETTINGS structure (can be NULL for defaults). + +// Frees an opaque TALKBACK_ANALYSIS object. This releases all memory used by +// the analysis. +TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. +TalkBackFreeAnalysis( + TALKBACK_ANALYSIS **ioAnalysis); // IN/OUT: analysis to free. + +// ####################################################################### +// NOTE: all functions from this point on require a valid analysis object. +// ####################################################################### + +// ------------------------ +// Speech target functions. +// ------------------------ + +// Gets the number of speech target tracks. +TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. +TalkBackGetNumSpeechTargetTracks( + TALKBACK_ANALYSIS *iAnalysis, // IN: opaque analysis object returned by TalkBackGetAnalysis(). + long *oResult); // OUT: number of speech target tracks. + +// Gets the number of keys in the specified speech target track. +TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. +TalkBackGetNumSpeechTargetKeys( + TALKBACK_ANALYSIS *iAnalysis, // IN: opaque analysis object returned by TalkBackGetAnalysis(). + long iTrackNum, // IN: speech target track. + long *oResult); // OUT: number of keys in the speech target track. + +// Gets key information (time, value, derivative in, and derivative out) for the +// specified key in the specified speech target track. +TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. +TalkBackGetSpeechTargetKeyInfo( + TALKBACK_ANALYSIS *iAnalysis, // IN: opaque analysis object returned by TalkBackGetAnalysis(). + long iTrackNum, // IN: speech target track. + long iKeyNum, // IN: speech target key. + double *oTime, // OUT: time of key. + double *oValue, // OUT: value of key. + double *oDerivativeIn, // OUT: incoming derivative of key. + double *oDerivativeOut); // OUT: outgoing derivative of key. + +// Gets the value of the function curve for the specified speech target track at +// the specified time. +TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. +TalkBackGetSpeechTargetValueAtTime( + TALKBACK_ANALYSIS *iAnalysis, // IN: opaque analysis object returned by TalkBackGetAnalysis(). + long iTrackNum, // IN: speech target track. + double iTime, // IN: time in seconds. + double *oResult); // OUT: value of the function curve. + +// Gets the derivatives of the function curve for the specified speech target +// track at the specified time. +TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. +TalkBackGetSpeechTargetDerivativesAtTime( + TALKBACK_ANALYSIS *iAnalysis, // IN: opaque analysis object returned by TalkBackGetAnalysis(). + long iTrackNum, // IN: speech target track. + double iTime, // IN: time in seconds. + double *oDerivativeIn, // OUT: value of the incoming derivative of the function curve. + double *oDerivativeOut); // OUT: value of the outgoing derivative of the function curve. + +// ------------------------- +// Speech gesture functions. +// ------------------------- + +// Gets the number of speech gesture tracks. +TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. +TalkBackGetNumGestureTracks( + TALKBACK_ANALYSIS *iAnalysis, // IN: opaque analysis object returned by TalkBackGetAnalysis(). + long *oResult); // OUT: number of speech gesture tracks + +// Gets the number of keys in the specified speech gesture track. +TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. +TalkBackGetNumGestureKeys( + TALKBACK_ANALYSIS *iAnalysis, // IN: opaque analysis object returned by TalkBackGetAnalysis(). + long iTrackNum, // IN: speech gesture track. + long *oResult); // OUT: number of keys in the speech gesture track. + +// Gets key information (time, value, derivative in, and derivative out) for the +// specified key in the specified speech gesture track. +TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. +TalkBackGetGestureKeyInfo( + TALKBACK_ANALYSIS *iAnalysis, // IN: opaque analysis object returned by TalkBackGetAnalysis(). + long iTrackNum, // IN: speech gesture track. + long iKeyNum, // IN: speech gesture key. + double *oTime, // OUT: time of key. + double *oValue, // OUT: value of key. + double *oDerivativeIn, // OUT: incoming derivative of key. + double *oDerivativeOut); // OUT: outgoing derivative of key. + +// Gets the value of the function curve for the specified speech gesture track +// at the specified time. +TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. +TalkBackGetGestureValueAtTime( + TALKBACK_ANALYSIS *iAnalysis, // IN: opaque analysis object returned by TalkBackGetAnalysis(). + long iTrackNum, // IN: speech gesture track. + double iTime, // IN: time in seconds. + double *oResult); // OUT: value of the function curve. + +// Gets the derivatives of the function curve for the specified speech gesture +// track at the specified time. +TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. +TalkBackGetGestureDerivativesAtTime( + TALKBACK_ANALYSIS *iAnalysis, // IN: opaque analysis object returned by TalkBackGetAnalysis(). + long iTrackNum, // IN: speech gesture track. + double iTime, // IN: time in seconds. + double *oDerivativeIn, // OUT: value of the incoming derivative of the function curve. + double *oDerivativeOut); // OUT: value of the outgoing derivative of the function curve. + +// ---------------- +// Frame functions. +// ---------------- + +// NOTE: these functions use the frame rate specified in the +// TALKBACK_ANALYSIS_SETTINGS structure passed to TalkBackGetAnalysis() and +// default to 30 fps (TALKBACK_DEFAULT_FRAME_RATE) if the structure pointer was +// NULL. + +// Gets the first frame number. +TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. +TalkBackGetFirstFrameNum( + TALKBACK_ANALYSIS *iAnalysis, // IN: opaque analysis object returned by TalkBackGetAnalysis(). + long *oResult); // OUT: number of the first frame. + +// Gets the last frame number. +TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. +TalkBackGetLastFrameNum( + TALKBACK_ANALYSIS *iAnalysis, // IN: opaque analysis object returned by TalkBackGetAnalysis(). + long *oResult); // OUT: number of the last frame. + +// Gets the start time of the specified frame. +TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. +TalkBackGetFrameStartTime( + TALKBACK_ANALYSIS *iAnalysis, // IN: opaque analysis object returned by TalkBackGetAnalysis(). + long iFrameNum, // IN: frame. + double *oResult); // OUT: start time of the frame in seconds. + +// Gets the end time of the specified frame. +TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. +TalkBackGetFrameEndTime( + TALKBACK_ANALYSIS *iAnalysis, // IN: opaque analysis object returned by TalkBackGetAnalysis(). + long iFrameNum, // IN: frame. + double *oResult); // OUT: end time of the frame in seconds. + +// Gets the value of the function curve for a speech target integrated over the +// specified frame. +TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. +TalkBackGetSpeechTargetValueAtFrame( + TALKBACK_ANALYSIS *iAnalysis, // IN: opaque analysis object returned by TalkBackGetAnalysis(). + long iTrackNum, // IN: speech target track. + long iFrameNum, // IN: frame number. + double *oResult); // OUT: value of the function curve integrated over the frame. + +// Gets the dominant speech target at the specified frame. +// +// NOTE: this function is meant to be used in flipbook mode only. +TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. +TalkBackGetDominantSpeechTargetAtFrame( + TALKBACK_ANALYSIS *iAnalysis, // IN: opaque analysis object returned by TalkBackGetAnalysis(). + long iFrameNum, // IN: frame number. + TALKBACK_SPEECH_TARGET *oSpeechTarget); // OUT: dominant speech target. + +// Gets the value of the function curve for a speech gesture integrated over the +// specified frame. +TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. +TalkBackGetGestureValueAtFrame( + TALKBACK_ANALYSIS *iAnalysis, // IN: opaque analysis object returned by TalkBackGetAnalysis(). + long iTrackNum, // IN: speech gesture track. + long iFrameNum, // IN: frame number. + double *oResult); // OUT: value of the function curve integrated over the frame. + +// ------------------ +// Phoneme functions. +// ------------------ + +// Gets the number of phonemes. +TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. +TalkBackGetNumPhonemes( + TALKBACK_ANALYSIS *iAnalysis, // IN: opaque analysis object returned by TalkBackGetAnalysis(). + long *oResult); // OUT: number of phonemes. + +// Gets the enumeration of the specified phoneme. +TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. +TalkBackGetPhonemeEnum( + TALKBACK_ANALYSIS *iAnalysis, // IN: opaque analysis object returned by TalkBackGetAnalysis(). + long iPhonemeNum, // IN: phoneme. + TALKBACK_PHONEME *oResult); // OUT: enumeration of the specified phoneme. + +// Gets the start time of the specified phoneme. +TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. +TalkBackGetPhonemeStartTime( + TALKBACK_ANALYSIS *iAnalysis, // IN: opaque analysis object returned by TalkBackGetAnalysis(). + long iPhonemeNum, // IN: phoneme. + double *oResult); // OUT: start time of the phoneme in seconds. + +// Gets the end time of the specified phoneme. +TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. +TalkBackGetPhonemeEndTime( + TALKBACK_ANALYSIS *iAnalysis, // IN: opaque analysis object returned by TalkBackGetAnalysis(). + long iPhonemeNum, // IN: phoneme. + double *oResult); // OUT: end time of the phoneme in seconds. + +// --------------- +// Word functions. +// --------------- + +// NOTE: these functions only yield data for text-based analysis. + +// Gets the number of words. +TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. +TalkBackGetNumWords( + TALKBACK_ANALYSIS *iAnalysis, // IN: opaque analysis object returned by TalkBackGetAnalysis(). + long *oResult); // OUT: number of words. + +// Gets the text of the specified word. +TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. +TalkBackGetWord( + TALKBACK_ANALYSIS *iAnalysis, // IN: opaque analysis object returned by TalkBackGetAnalysis(). + long iWordNum, // IN: word. + long iMaxChars, // IN: size of word buffer. + char *oWord); // OUT: word buffer. + +// Gets the start time of the specified word. +TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. +TalkBackGetWordStartTime( + TALKBACK_ANALYSIS *iAnalysis, // IN: opaque analysis object returned by TalkBackGetAnalysis(). + long iWordNum, // IN: word. + double *oResult); // OUT: start time of the word in seconds. + +// Gets the end time of the specified word. +TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. +TalkBackGetWordEndTime( + TALKBACK_ANALYSIS *iAnalysis, // IN: opaque analysis object returned by TalkBackGetAnalysis(). + long iWordNum, // IN: word. + double *oResult); // OUT: end time of the word in seconds. + +// -------------------------- +// Phoneme editing functions. +// -------------------------- + +// Use these functions to modify the phoneme list after you get an opaque +// analysis object from TalkBackGetAnalysis(). After modifying the phoneme list +// in the opaque analysis object, subsequent TalkBackGet* calls on that opaque +// analysis object for speech target (lip-synching) data will return values +// based on the modified phoneme list. However, speech gesture data is not +// affected by phoneme editing. +// +// NOTE: phoneme editing is only provided in order to support Ventriloquist-like +// applications where tweaking of the phoneme segmenation (and subsequent +// recalculation of the animation data) is required. Most customers probably +// won't need this functionality. + +// Inserts a phoneme at the specified position in the specified manner. +TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. +TalkBackInsertPhoneme( + TALKBACK_ANALYSIS *iAnalysis, // IN: opaque analysis object returned by TalkBackGetAnalysis(). + TALKBACK_PHONEME iPhoneme, // IN: enumeration of phoneme to insert. + long iInsertPosition, // IN: position (phoneme number) at which to insert. + int iInsertBefore); // IN: manner of insertion: + // 0 means put phoneme after insert position; + // 1 means put phoneme before insert position. + +// Deletes the specified phoneme. +TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. +TalkBackDeletePhoneme( + TALKBACK_ANALYSIS *iAnalysis, // IN: opaque analysis object returned by TalkBackGetAnalysis(). + long iPhonemeToDelete); // IN: phoneme to delete. + +// Changes the start time of the specified phoneme. +// +// NOTE: the start time specified may not be the actual start time for a number +// of reasons, most notably if the specified start time will make the phoneme +// too short. This function returns the actual start time so the caller can +// check the result without having to query the phoneme. +TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. +TalkBackChangePhonemeStart( + TALKBACK_ANALYSIS *iAnalysis, // IN: opaque analysis object returned by TalkBackGetAnalysis(). + long iPhonemeToChange, // IN: phoneme to change. + double *ioNewTime); // IN/OUT: new start time value in seconds (in); actual start time (out). + +// Changes the end time of the specified phoneme. +// +// NOTE: the end time specified may not be the actual end time for a number of +// reasons, most notably if the specified end time will make the phoneme too +// short. This function returns the actual end time so the caller can check the +// result without having to query the phoneme. +TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. +TalkBackChangePhonemeEnd( + TALKBACK_ANALYSIS *iAnalysis, // IN: opaque analysis object returned by TalkBackGetAnalysis(). + long iPhonemeToChange, // IN: phoneme to change. + double *ioNewTime); // IN/OUT: new end time value in seconds (in); actual end time (out). + +// Changes the enumeration of the specified phoneme. +TALKBACK_ERR // RETURNS: TALKBACK_NOERR if successful, TalkBack error code if not. +TalkBackChangePhonemeEnum( + TALKBACK_ANALYSIS *iAnalysis, // IN: opaque analysis object returned by TalkBackGetAnalysis(). + long iPhonemeToChange, // IN: phoneme to change. + TALKBACK_PHONEME iNewPhoneme); // IN: new phoneme enumeration. + +#if defined(__cplusplus) +} +#endif + +#endif diff --git a/mp/src/utils/qc_eyes/QC_Eyes.cpp b/mp/src/utils/qc_eyes/QC_Eyes.cpp new file mode 100644 index 00000000..d62fe124 --- /dev/null +++ b/mp/src/utils/qc_eyes/QC_Eyes.cpp @@ -0,0 +1,73 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// QC_Eyes.cpp : Defines the class behaviors for the application. +// + +#include "stdafx.h" +#include "QC_Eyes.h" +#include "QC_EyesDlg.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// CQC_EyesApp + +BEGIN_MESSAGE_MAP(CQC_EyesApp, CWinApp) + //{{AFX_MSG_MAP(CQC_EyesApp) + // NOTE - the ClassWizard will add and remove mapping macros here. + // DO NOT EDIT what you see in these blocks of generated code! + //}}AFX_MSG + ON_COMMAND(ID_HELP, CWinApp::OnHelp) +END_MESSAGE_MAP() + +///////////////////////////////////////////////////////////////////////////// +// CQC_EyesApp construction + +CQC_EyesApp::CQC_EyesApp() +{ + // TODO: add construction code here, + // Place all significant initialization in InitInstance +} + +///////////////////////////////////////////////////////////////////////////// +// The one and only CQC_EyesApp object + +CQC_EyesApp theApp; + +///////////////////////////////////////////////////////////////////////////// +// CQC_EyesApp initialization + +BOOL CQC_EyesApp::InitInstance() +{ + AfxEnableControlContainer(); + + // Standard initialization + // If you are not using these features and wish to reduce the size + // of your final executable, you should remove from the following + // the specific initialization routines you do not need. + +#ifdef _AFXDLL + Enable3dControls(); // Call this when using MFC in a shared DLL +#endif + + CQC_EyesDlg dlg; + m_pMainWnd = &dlg; + INT_PTR nResponse = dlg.DoModal(); + if (nResponse == IDOK) + { + // TODO: Place code here to handle when the dialog is + // dismissed with OK + } + else if (nResponse == IDCANCEL) + { + // TODO: Place code here to handle when the dialog is + // dismissed with Cancel + } + + // Since the dialog has been closed, return FALSE so that we exit the + // application, rather than start the application's message pump. + return FALSE; +} diff --git a/mp/src/utils/qc_eyes/QC_Eyes.h b/mp/src/utils/qc_eyes/QC_Eyes.h new file mode 100644 index 00000000..240cb3d1 --- /dev/null +++ b/mp/src/utils/qc_eyes/QC_Eyes.h @@ -0,0 +1,50 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// QC_Eyes.h : main header file for the QC_EYES application +// + +#if !defined(AFX_QC_EYES_H__398BAF8D_D3C0_4326_BEF0_5129884EE1A3__INCLUDED_) +#define AFX_QC_EYES_H__398BAF8D_D3C0_4326_BEF0_5129884EE1A3__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#ifndef __AFXWIN_H__ + #error include 'stdafx.h' before including this file for PCH +#endif + +#include "resource.h" // main symbols + +///////////////////////////////////////////////////////////////////////////// +// CQC_EyesApp: +// See QC_Eyes.cpp for the implementation of this class +// + +class CQC_EyesApp : public CWinApp +{ +public: + CQC_EyesApp(); + +// Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CQC_EyesApp) + public: + virtual BOOL InitInstance(); + //}}AFX_VIRTUAL + +// Implementation + + //{{AFX_MSG(CQC_EyesApp) + // NOTE - the ClassWizard will add and remove member functions here. + // DO NOT EDIT what you see in these blocks of generated code ! + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + + +///////////////////////////////////////////////////////////////////////////// + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_QC_EYES_H__398BAF8D_D3C0_4326_BEF0_5129884EE1A3__INCLUDED_) diff --git a/mp/src/utils/qc_eyes/QC_Eyes.rc b/mp/src/utils/qc_eyes/QC_Eyes.rc new file mode 100644 index 00000000..c0cafafc --- /dev/null +++ b/mp/src/utils/qc_eyes/QC_Eyes.rc @@ -0,0 +1,276 @@ +// Microsoft Visual C++ generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "afxres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""afxres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "#define _AFX_NO_SPLITTER_RESOURCES\r\n" + "#define _AFX_NO_OLE_RESOURCES\r\n" + "#define _AFX_NO_TRACKER_RESOURCES\r\n" + "#define _AFX_NO_PROPERTY_RESOURCES\r\n" + "\r\n" + "#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)\r\n" + "#ifdef _WIN32\r\n" + "LANGUAGE 9, 1\r\n" + "#pragma code_page(1252)\r\n" + "#endif //_WIN32\r\n" + "#include ""res\\QC_Eyes.rc2"" // non-Microsoft Visual C++ edited resources\r\n" + "#include ""afxres.rc"" // Standard components\r\n" + "#endif\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDR_MAINFRAME ICON "res\\QC_Eyes.ico" + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_QC_EYES_DIALOG DIALOGEX 0, 0, 695, 510 +STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | + WS_SYSMENU +EXSTYLE WS_EX_APPWINDOW +CAPTION "QC Eyes" +FONT 8, "MS Sans Serif", 0, 0, 0x1 +BEGIN + EDITTEXT IDC_REFERENCE_FILENAME,87,24,93,14,ES_AUTOHSCROLL + EDITTEXT IDC_EXPRESSIONS_FILENAME,87,42,93,14,ES_AUTOHSCROLL + EDITTEXT IDC_MODEL_FILENAME,87,60,93,14,ES_AUTOHSCROLL + EDITTEXT IDC_RIGHT_EYE_X,19,140,40,14,ES_AUTOHSCROLL + EDITTEXT IDC_RIGHT_EYE_Y,83,140,40,14,ES_AUTOHSCROLL + EDITTEXT IDC_RIGHT_EYE_Z,147,140,40,14,ES_AUTOHSCROLL + EDITTEXT IDC_LEFT_EYE_X,19,205,40,14,ES_AUTOHSCROLL + EDITTEXT IDC_LEFT_EYE_Y,83,205,40,14,ES_AUTOHSCROLL + EDITTEXT IDC_LEFT_EYE_Z,147,205,40,14,ES_AUTOHSCROLL + GROUPBOX "3D Platform",IDC_STATIC,219,7,126,47 + GROUPBOX "Options",IDC_STATIC,219,58,126,47 + GROUPBOX "Upper Lid Positions",IDC_STATIC,219,109,126,60 + EDITTEXT IDC_UPPER_LID_RAISED,264,118,74,14,ES_AUTOHSCROLL + EDITTEXT IDC_UPPER_LID_NEUTRAL,264,134,74,14,ES_AUTOHSCROLL + EDITTEXT IDC_UPPER_LID_LOWERED,264,150,74,14,ES_AUTOHSCROLL + EDITTEXT IDC_LOWER_LID_RAISED,264,184,74,14,ES_AUTOHSCROLL + EDITTEXT IDC_LOWER_LID_NEUTRAL,264,200,74,14,ES_AUTOHSCROLL + EDITTEXT IDC_LOWER_LID_LOWERED,264,216,74,14,ES_AUTOHSCROLL + GROUPBOX "Iris Color",IDC_STATIC,351,175,75,60 + GROUPBOX "Eye White Color",IDC_STATIC,431,175,126,60 + EDITTEXT IDC_IRIS_SIZE,469,21,59,14,ES_AUTOHSCROLL + EDITTEXT IDC_EYEBALL_SIZE,469,43,59,14,ES_AUTOHSCROLL + PUSHBUTTON "Create QC Text",IDC_CREATE_QC_TEXT,7,243,93,19 + PUSHBUTTON "Copy Text To Clipboard",IDC_COPY_TEXT_TO_CLIPBOARD,109, + 243,93,19 + EDITTEXT IDC_OUTPUT_TEXT,7,271,681,232,ES_MULTILINE | + ES_AUTOHSCROLL + CONTROL "Y Axis Up (XSI, Maya)",IDC_Y_AXIS_UP,"Button", + BS_AUTORADIOBUTTON | WS_GROUP,228,21,92,10 + CONTROL "Z Axis Up (MAX)",IDC_Z_AXIS_UP,"Button", + BS_AUTORADIOBUTTON,228,36,94,10 + CONTROL "Default Controls",IDC_DEFAULT_CONTROLS,"Button", + BS_AUTORADIOBUTTON | WS_GROUP,228,72,92,10 + CONTROL "Advanced Controls",IDC_ADVANCED_CONTROLS,"Button", + BS_AUTORADIOBUTTON,228,88,94,10 + RADIOBUTTON "Brown Irises",IDC_IRIS_COLOR_BROWN,360,188,55,10, + WS_GROUP + RADIOBUTTON "Green Irises",IDC_IRIS_COLOR_GREEN,360,203,54,10 + RADIOBUTTON "Blue Irises",IDC_IRIS_COLOR_BLUE,360,218,54,10 + RADIOBUTTON "Light Whites (paler skin tones)",IDC_EYE_COLOR_LIGHT, + 438,193,108,10,WS_GROUP + RADIOBUTTON "Dark Whites (darker skin tones)",IDC_EYE_COLOR_DARK,438, + 214,112,10 + RTEXT "Reference Filename:",IDC_STATIC,13,27,70,8 + RTEXT "Expressions Filename:",IDC_STATIC,13,45,70,8 + RTEXT "Model Filename:",IDC_STATIC,13,64,70,8 + LTEXT ".SMD",IDC_STATIC,184,29,19,8 + LTEXT ".VTA",IDC_STATIC,184,47,17,8 + LTEXT ".MDL",IDC_STATIC,184,65,18,8 + GROUPBOX "Eye Positions",IDC_STATIC,7,95,200,140 + GROUPBOX "Right Eye",IDC_STATIC,14,108,185,56 + LTEXT "Y Position",IDC_STATIC,85,126,32,8 + LTEXT "X Position",IDC_STATIC,21,126,32,8 + LTEXT "Z Position",IDC_STATIC,149,126,32,8 + GROUPBOX "Left Eye",IDC_STATIC,14,173,185,56 + LTEXT "Y Position",IDC_STATIC,85,191,32,8 + LTEXT "X Position",IDC_STATIC,21,191,32,8 + LTEXT "Z Position",IDC_STATIC,149,191,32,8 + GROUPBOX "Filenames",IDC_STATIC,7,7,200,78 + CONTROL "",IDC_PICTURES,"Static",SS_BITMAP,352,7,336,162 + RTEXT "Raised:",IDC_STATIC,228,122,32,8 + RTEXT "Neutral:",IDC_STATIC,227,137,32,8 + RTEXT "Lowered:",IDC_STATIC,227,153,32,8 + GROUPBOX "Lowered Lid Positions",IDC_STATIC,219,175,126,60 + RTEXT "Raised:",IDC_STATIC,228,188,32,8 + RTEXT "Neutral:",IDC_STATIC,227,204,32,8 + RTEXT "Lowered:",IDC_STATIC,227,220,32,8 + GROUPBOX "Eye Detail Control",IDC_EYE_DETAIL_CONTROL_FRAME,414,7, + 140,73 + RTEXT "Iris Size:",IDC_IRIS_SIZE_LABEL,417,25,48,8 + RTEXT "Eyeball Size:",IDC_EYEBALL_SIZE_LABEL,417,47,48,8 + CTEXT "",IDC_PICTURE_LABEL,375,162,285,8 + CONTROL "Enable Independent Left Lid Control", + IDC_LEFT_LID_CONTROL,"Button",BS_AUTOCHECKBOX | + WS_TABSTOP,421,62,128,10 + GROUPBOX "Upper Left Lid Positions",IDC_UPPER_LEFT_LID_PANEL,562, + 7,126,60 + EDITTEXT IDC_UPPER_LEFT_LID_RAISED,607,16,74,14,ES_AUTOHSCROLL + EDITTEXT IDC_UPPER_LEFT_LID_NEUTRAL,607,32,74,14,ES_AUTOHSCROLL + EDITTEXT IDC_UPPER_LEFT_LID_LOWERED,607,48,74,14,ES_AUTOHSCROLL + EDITTEXT IDC_LOWER_LEFT_LID_RAISED,607,82,74,14,ES_AUTOHSCROLL + EDITTEXT IDC_LOWER_LEFT_LID_NEUTRAL,607,98,74,14,ES_AUTOHSCROLL + EDITTEXT IDC_LOWER_LEFT_LID_LOWERED,607,114,74,14,ES_AUTOHSCROLL + RTEXT "Raised:",IDC_UPPER_LEFT_LID_RAISED_LABEL,571,20,32,8 + RTEXT "Neutral:",IDC_UPPER_LEFT_LID_NEUTRAL_LABEL,570,35,32,8 + RTEXT "Lowered:",IDC_UPPER_LEFT_LID_LOWERED_LABEL,570,51,32,8 + GROUPBOX "Lowered Left Lid Positions",IDC_LOWER_LEFT_LID_PANEL, + 562,73,126,60 + RTEXT "Raised:",IDC_LOWER_LEFT_LID_RAISED_LABEL,571,86,32,8 + RTEXT "Neutral:",IDC_LOWER_LEFT_LID_NEUTRAL_LABEL,570,102,32,8 + RTEXT "Lowered:",IDC_LOWER_LEFT_LID_LOWERED_LABEL,570,118,32,8 +END + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,0,0,1 + PRODUCTVERSION 1,0,0,1 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x4L + FILETYPE 0x1L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904B0" + BEGIN + VALUE "FileDescription", "QC_Eyes MFC Application" + VALUE "FileVersion", "1, 0, 0, 1" + VALUE "InternalName", "QC_Eyes" + VALUE "LegalCopyright", "Copyright (C) 2005" + VALUE "OriginalFilename", "QC_Eyes.EXE" + VALUE "ProductName", "QC_Eyes Application" + VALUE "ProductVersion", "1, 0, 0, 1" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO +BEGIN + IDD_QC_EYES_DIALOG, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 688 + TOPMARGIN, 7 + BOTTOMMARGIN, 503 + END +END +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Bitmap +// + +IDB_EYE_DEFAULT BITMAP "res\\eye_default.bmp" +IDB_EYE_LOWER_HI BITMAP "res\\eye_lower_hi.bmp" +IDB_EYE_LOWER_LO BITMAP "res\\eye_lower_lo.bmp" +IDB_EYE_LOWER_MID BITMAP "res\\eye_lower_mid.bmp" +IDB_EYE_UPPER_HI BITMAP "res\\eye_upper_hi.bmp" +IDB_EYE_UPPER_LO BITMAP "res\\eye_upper_lo.bmp" +IDB_EYE_UPPER_MID BITMAP "res\\eye_upper_mid.bmp" +IDB_EYE_XY_L BITMAP "res\\eye_xy_l.bmp" +IDB_EYE_XY_R BITMAP "res\\eye_xy_r.bmp" +IDB_EYE_Z_L BITMAP "res\\eye_z_l.bmp" +IDB_EYE_Z_R BITMAP "res\\eye_z_r.bmp" +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// +#define _AFX_NO_SPLITTER_RESOURCES +#define _AFX_NO_OLE_RESOURCES +#define _AFX_NO_TRACKER_RESOURCES +#define _AFX_NO_PROPERTY_RESOURCES + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE 9, 1 +#pragma code_page(1252) +#endif //_WIN32 +#include "res\QC_Eyes.rc2" // non-Microsoft Visual C++ edited resources +#include "afxres.rc" // Standard components +#endif + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/mp/src/utils/qc_eyes/QC_EyesDlg.cpp b/mp/src/utils/qc_eyes/QC_EyesDlg.cpp new file mode 100644 index 00000000..863e556a --- /dev/null +++ b/mp/src/utils/qc_eyes/QC_EyesDlg.cpp @@ -0,0 +1,705 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// QC_EyesDlg.cpp : implementation file +// + +#include "stdafx.h" +#include +#include "QC_Eyes.h" +#include "QC_EyesDlg.h" + +#ifdef _DEBUG +#define new DEBUG_NEW +#undef THIS_FILE +static char THIS_FILE[] = __FILE__; +#endif + +///////////////////////////////////////////////////////////////////////////// +// CQC_EyesDlg dialog + +CQC_EyesDlg::CQC_EyesDlg(CWnd* pParent /*=NULL*/) + : CDialog(CQC_EyesDlg::IDD, pParent) +{ + //{{AFX_DATA_INIT(CQC_EyesDlg) + // NOTE: the ClassWizard will add member initialization here + //}}AFX_DATA_INIT + // Note that LoadIcon does not require a subsequent DestroyIcon in Win32 + m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME); + m_pBitmapHead = NULL; +} + +void CQC_EyesDlg::DoDataExchange(CDataExchange* pDX) +{ + CDialog::DoDataExchange(pDX); + //{{AFX_DATA_MAP(CQC_EyesDlg) + DDX_Control(pDX, IDC_LEFT_LID_CONTROL, m_IndependentLeftLidControl); + DDX_Control(pDX, IDC_PICTURES, m_PictureControl); + //}}AFX_DATA_MAP +} + +BEGIN_MESSAGE_MAP(CQC_EyesDlg, CDialog) + //{{AFX_MSG_MAP(CQC_EyesDlg) + ON_WM_PAINT() + ON_WM_QUERYDRAGICON() + ON_BN_CLICKED(IDC_CREATE_QC_TEXT, OnCreateQcText) + ON_BN_CLICKED(IDC_IRIS_COLOR_BROWN, OnIrisColorBrown) + ON_BN_CLICKED(IDC_IRIS_COLOR_GREEN, OnIrisColorGreen) + ON_BN_CLICKED(IDC_IRIS_COLOR_BLUE, OnIrisColorBlue) + ON_BN_CLICKED(IDC_EYE_COLOR_DARK, OnEyeColorDark) + ON_BN_CLICKED(IDC_EYE_COLOR_LIGHT, OnEyeColorLight) + ON_EN_SETFOCUS(IDC_RIGHT_EYE_X, OnSetfocusRightEyeX) + ON_EN_SETFOCUS(IDC_RIGHT_EYE_Y, OnSetfocusRightEyeY) + ON_EN_SETFOCUS(IDC_RIGHT_EYE_Z, OnSetfocusRightEyeZ) + ON_EN_SETFOCUS(IDC_LEFT_EYE_X, OnSetfocusLeftEyeX) + ON_EN_SETFOCUS(IDC_LEFT_EYE_Y, OnSetfocusLeftEyeY) + ON_EN_SETFOCUS(IDC_LEFT_EYE_Z, OnSetfocusLeftEyeZ) + ON_EN_SETFOCUS(IDC_UPPER_LID_LOWERED, OnSetfocusUpperLidLowered) + ON_EN_SETFOCUS(IDC_UPPER_LID_NEUTRAL, OnSetfocusUpperLidNeutral) + ON_EN_SETFOCUS(IDC_UPPER_LID_RAISED, OnSetfocusUpperLidRaised) + ON_EN_SETFOCUS(IDC_LOWER_LID_LOWERED, OnSetfocusLowerLidLowered) + ON_EN_SETFOCUS(IDC_LOWER_LID_NEUTRAL, OnSetfocusLowerLidNeutral) + ON_EN_SETFOCUS(IDC_LOWER_LID_RAISED, OnSetfocusLowerLidRaised) + ON_BN_CLICKED(IDC_COPY_TEXT_TO_CLIPBOARD, OnCopyTextToClipboard) + ON_BN_CLICKED(IDC_DEFAULT_CONTROLS, OnDefaultControls) + ON_BN_CLICKED(IDC_ADVANCED_CONTROLS, OnAdvancedControls) + ON_BN_CLICKED(IDC_LEFT_LID_CONTROL, OnLeftLidControl) + //}}AFX_MSG_MAP +END_MESSAGE_MAP() + + +void CQC_EyesDlg::AddText( const char *pFormat, ... ) +{ + char tempMsg[4096]; + va_list marker; + va_start( marker, pFormat ); + _vsnprintf( tempMsg, sizeof( tempMsg ), pFormat, marker ); + tempMsg[ ARRAYSIZE(tempMsg) - 1 ] = 0; + va_end( marker ); + + size_t nCharsInBuf = strlen( m_Buf ); + size_t nCharsInMsg = strlen( tempMsg ); + + if ( nCharsInBuf + nCharsInMsg + 1 > m_BufSize ) + { + m_BufSize = nCharsInBuf + nCharsInMsg + 4096; + char *newbuf = new char[m_BufSize]; + memcpy( newbuf, m_Buf, nCharsInBuf + 1 ); + delete [] m_Buf; + m_Buf = newbuf; + } + + strcat( m_Buf, tempMsg ); +} + + +void SendToEditControl( HWND hEditControl, const char *pText ) +{ + LRESULT nLen = SendMessage( hEditControl, EM_GETLIMITTEXT, 0, 0 ); + SendMessage( hEditControl, EM_SETSEL, nLen, nLen ); + SendMessage( hEditControl, EM_REPLACESEL, FALSE, (LPARAM)pText ); +} + + +void FormatAndSendToEditControl( void *hWnd, const char *pText ) +{ + HWND hEditControl = (HWND)hWnd; + + // Translate \n to \r\n. + char outMsg[1024]; + const char *pIn = pText; + char *pOut = outMsg; + while ( *pIn ) + { + if ( *pIn == '\n' ) + { + *pOut = '\r'; + pOut++; + } + *pOut = *pIn; + + ++pIn; + ++pOut; + + if ( pOut - outMsg >= 1020 ) + { + *pOut = 0; + SendToEditControl( hEditControl, outMsg ); + pOut = outMsg; + } + } + *pOut = 0; + SendToEditControl( hEditControl, outMsg ); +} + + +HBITMAP CQC_EyesDlg::GetCachedBitmap( UINT id ) +{ + for ( CBitmapRef *pCur=m_pBitmapHead; pCur; pCur=pCur->m_pNext ) + { + if ( pCur->m_iResource == id ) + return pCur->m_hBitmap; + } + + CBitmapRef *pNew = new CBitmapRef; + pNew->m_iResource = id; + pNew->m_hBitmap = ::LoadBitmap( AfxGetInstanceHandle(), MAKEINTRESOURCE(id) ); + pNew->m_pNext = m_pBitmapHead; + m_pBitmapHead = pNew; + + return pNew->m_hBitmap; +} + + +///////////////////////////////////////////////////////////////////////////// +// CQC_EyesDlg message handlers + +BOOL CQC_EyesDlg::OnInitDialog() +{ + CDialog::OnInitDialog(); + + // Set the icon for this dialog. The framework does this automatically + // when the application's main window is not a dialog + SetIcon(m_hIcon, TRUE); // Set big icon + SetIcon(m_hIcon, FALSE); // Set small icon + + // TODO: Add extra initialization here + GetDlgItem( IDC_REFERENCE_FILENAME )->SetWindowText( "filename_reference" ); + GetDlgItem( IDC_EXPRESSIONS_FILENAME )->SetWindowText( "filename_expressions" ); + GetDlgItem( IDC_MODEL_FILENAME )->SetWindowText( "filename_model" ); + GetDlgItem( IDC_IRIS_SIZE )->SetWindowText( "0.63" ); + GetDlgItem( IDC_EYEBALL_SIZE )->SetWindowText( "1.0" ); + + ::SendMessage( ::GetDlgItem( m_hWnd, IDC_Y_AXIS_UP ), BM_SETCHECK, BST_CHECKED, 0 ); + ::SendMessage( ::GetDlgItem( m_hWnd, IDC_DEFAULT_CONTROLS ), BM_SETCHECK, BST_CHECKED, 0 ); + ::SendMessage( ::GetDlgItem( m_hWnd, IDC_IRIS_COLOR_BROWN ), BM_SETCHECK, BST_CHECKED, 0 ); + ::SendMessage( ::GetDlgItem( m_hWnd, IDC_EYE_COLOR_LIGHT ), BM_SETCHECK, BST_CHECKED, 0 ); + + m_hOutputText = ::GetDlgItem( m_hWnd, IDC_OUTPUT_TEXT ); + + m_PictureControl.SetBitmap( GetCachedBitmap( IDB_EYE_DEFAULT ) ); + OnDefaultControls(); // Hide the advanced controls. + + return TRUE; // return TRUE unless you set the focus to a control +} + +// If you add a minimize button to your dialog, you will need the code below +// to draw the icon. For MFC applications using the document/view model, +// this is automatically done for you by the framework. + +void CQC_EyesDlg::OnPaint() +{ + if (IsIconic()) + { + CPaintDC dc(this); // device context for painting + + SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0); + + // Center icon in client rectangle + int cxIcon = GetSystemMetrics(SM_CXICON); + int cyIcon = GetSystemMetrics(SM_CYICON); + CRect rect; + GetClientRect(&rect); + int x = (rect.Width() - cxIcon + 1) / 2; + int y = (rect.Height() - cyIcon + 1) / 2; + + // Draw the icon + dc.DrawIcon(x, y, m_hIcon); + } + else + { + CDialog::OnPaint(); + } +} + +// The system calls this to obtain the cursor to display while the user drags +// the minimized window. +HCURSOR CQC_EyesDlg::OnQueryDragIcon() +{ + return (HCURSOR) m_hIcon; +} + + +void HandleYAxisUp( float &yVal, float &zVal ) +{ + float flTemp = yVal; + yVal = -zVal; + zVal = flTemp; +} + + +float CQC_EyesDlg::GetDlgItemFloat( UINT id ) +{ + char text[4096]; + GetDlgItemText( id, text, sizeof( text ) ); + return (float)atof( text ); +} + + +bool CQC_EyesDlg::IsOptionChecked( UINT option ) +{ + return (::SendMessage( ::GetDlgItem( m_hWnd, option ), BM_GETCHECK, 0, 0 ) == BST_CHECKED); +} + + +void CQC_EyesDlg::GetDialogParams( CDialogParams &p ) +{ + p.m_flLeftEye[0] = GetDlgItemFloat( IDC_LEFT_EYE_X ); + p.m_flLeftEye[1] = GetDlgItemFloat( IDC_LEFT_EYE_Y ); + p.m_flLeftEye[2] = GetDlgItemFloat( IDC_LEFT_EYE_Z ); + + p.m_flRightEye[0] = GetDlgItemFloat( IDC_RIGHT_EYE_X ); + p.m_flRightEye[1] = GetDlgItemFloat( IDC_RIGHT_EYE_Y ); + p.m_flRightEye[2] = GetDlgItemFloat( IDC_RIGHT_EYE_Z ); + + bool bYAxisUp = IsOptionChecked( IDC_Y_AXIS_UP ); + if ( bYAxisUp ) + { + HandleYAxisUp( p.m_flLeftEye[1], p.m_flLeftEye[2] ); + HandleYAxisUp( p.m_flRightEye[1], p.m_flRightEye[2] ); + } + + GetDlgItemText( IDC_REFERENCE_FILENAME, p.m_ReferenceFilename, sizeof( p.m_ReferenceFilename ) ); + GetDlgItemText( IDC_EXPRESSIONS_FILENAME, p.m_ExpressionsFilename, sizeof( p.m_ExpressionsFilename ) ); + GetDlgItemText( IDC_MODEL_FILENAME, p.m_ModelFilename, sizeof( p.m_ModelFilename ) ); + + p.m_flIrisSize = GetDlgItemFloat( IDC_IRIS_SIZE ); + p.m_flEyeballSize = GetDlgItemFloat( IDC_EYEBALL_SIZE ); + + p.m_flRightUpperLidRaised = GetDlgItemFloat( IDC_UPPER_LID_RAISED ); + p.m_flRightUpperLidNeutral = GetDlgItemFloat( IDC_UPPER_LID_NEUTRAL ); + p.m_flRightUpperLidLowered = GetDlgItemFloat( IDC_UPPER_LID_LOWERED ); + + p.m_flRightLowerLidRaised = GetDlgItemFloat( IDC_LOWER_LID_RAISED ); + p.m_flRightLowerLidNeutral = GetDlgItemFloat( IDC_LOWER_LID_NEUTRAL ); + p.m_flRightLowerLidLowered = GetDlgItemFloat( IDC_LOWER_LID_LOWERED ); + + if ( IsIndependentLeftLidControlEnabled() ) + { + p.m_flLeftUpperLidRaised = GetDlgItemFloat( IDC_UPPER_LEFT_LID_RAISED ); + p.m_flLeftUpperLidNeutral = GetDlgItemFloat( IDC_UPPER_LEFT_LID_NEUTRAL ); + p.m_flLeftUpperLidLowered = GetDlgItemFloat( IDC_UPPER_LEFT_LID_LOWERED ); + + p.m_flLeftLowerLidRaised = GetDlgItemFloat( IDC_LOWER_LEFT_LID_RAISED ); + p.m_flLeftLowerLidNeutral = GetDlgItemFloat( IDC_LOWER_LEFT_LID_NEUTRAL ); + p.m_flLeftLowerLidLowered = GetDlgItemFloat( IDC_LOWER_LEFT_LID_LOWERED ); + } + else + { + // Left lids follow the right lids. + p.m_flLeftUpperLidRaised = p.m_flRightUpperLidRaised; + p.m_flLeftUpperLidNeutral = p.m_flRightUpperLidNeutral; + p.m_flLeftUpperLidLowered = p.m_flRightUpperLidLowered; + + p.m_flLeftLowerLidRaised = p.m_flRightLowerLidRaised; + p.m_flLeftLowerLidNeutral = p.m_flRightLowerLidNeutral; + p.m_flLeftLowerLidLowered = p.m_flRightLowerLidLowered; + } + + // Figure out the eyeball prefix. + if ( IsOptionChecked( IDC_EYE_COLOR_LIGHT ) ) + strcpy( p.m_EyeballPrefix, "eyeball" ); + else + strcpy( p.m_EyeballPrefix, "dark_eyeball" ); + + // Figure out the pupil prefix. + if ( IsOptionChecked( IDC_IRIS_COLOR_BROWN ) ) + strcpy( p.m_PupilPrefix, "pupil" ); + else if ( IsOptionChecked( IDC_IRIS_COLOR_GREEN ) ) + strcpy( p.m_PupilPrefix, "grn_pupil" ); + else + strcpy( p.m_PupilPrefix, "bl_pupil" ); +} + + +void CQC_EyesDlg::GenerateQCText() +{ + CDialogParams p; + GetDialogParams( p ); + + + m_BufSize = 16 * 1024; + m_Buf = new char[m_BufSize]; + m_Buf[0] = 0; + + AddText( "//start eye/face data\n" ); + AddText( "$eyeposition 0 0 70\n\n" ); + + AddText( "//head controllers\n" ); + AddText( "$attachment \"eyes\" \"ValveBiped.Bip01_Head1\" %.2f %.2f %.2f absolute\n", + p.m_flLeftEye[0] - ((fabs( p.m_flRightEye[0] ) + p.m_flLeftEye[0]) * 0.5), + (p.m_flLeftEye[1] + p.m_flRightEye[1]) * 0.5, + (p.m_flLeftEye[2] + p.m_flRightEye[2]) * 0.5 ); + + AddText( "$attachment \"mouth\" \"ValveBiped.Bip01_Head1\" 0.80 -5.80 -0.15 rotate 0 -80 -90\n\n" ); + + AddText( "$model %s \"%s.smd\" {\n", + p.m_ModelFilename, p.m_ReferenceFilename ); + + AddText( "\teyeball righteye \"ValveBiped.Bip01_Head1\" %.2f %.2f %.2f \"%s_r\" %.2f 4 \"%s_r\" %.2f\n", + p.m_flRightEye[0], + p.m_flRightEye[1], + p.m_flRightEye[2], + p.m_EyeballPrefix, + p.m_flEyeballSize, + p.m_PupilPrefix, + p.m_flIrisSize ); + + AddText( "\teyeball lefteye \"ValveBiped.Bip01_Head1\" %.2f %.2f %.2f \"%s_l\" %.2f -4 \"%s_l\" %.2f\n\n", + p.m_flLeftEye[0], + p.m_flLeftEye[1], + p.m_flLeftEye[2], + p.m_EyeballPrefix, + p.m_flEyeballSize, + p.m_PupilPrefix, + p.m_flIrisSize ); + + AddText( "\teyelid upper_right \"%s\" lowerer 1 %.2f neutral 0 %.2f raiser 2 %.2f split 0.1 eyeball righteye\n", + p.m_ExpressionsFilename, + p.m_flRightUpperLidLowered - p.m_flRightEye[2], + p.m_flRightUpperLidNeutral - p.m_flRightEye[2], + p.m_flRightUpperLidRaised - p.m_flRightEye[2] ); + + AddText( "\teyelid lower_right \"%s\" lowerer 3 %.2f neutral 0 %.2f raiser 4 %.2f split 0.1 eyeball righteye\n", + p.m_ExpressionsFilename, + p.m_flRightLowerLidLowered - p.m_flRightEye[2], + p.m_flRightLowerLidNeutral - p.m_flRightEye[2], + p.m_flRightLowerLidRaised - p.m_flRightEye[2] ); + + AddText( "\teyelid upper_left \"%s\" lowerer 1 %.2f neutral 0 %.2f raiser 2 %.2f split -0.1 eyeball lefteye\n", + p.m_ExpressionsFilename, + p.m_flLeftUpperLidLowered - p.m_flLeftEye[2], + p.m_flLeftUpperLidNeutral - p.m_flLeftEye[2], + p.m_flLeftUpperLidRaised - p.m_flLeftEye[2] ); + + AddText( "\teyelid lower_left \"%s\" lowerer 3 %.2f neutral 0 %.2f raiser 4 %.2f split -0.1 eyeball lefteye\n\n", + p.m_ExpressionsFilename, + p.m_flLeftLowerLidLowered - p.m_flLeftEye[2], + p.m_flLeftLowerLidNeutral - p.m_flLeftEye[2], + p.m_flLeftLowerLidRaised - p.m_flLeftEye[2] ); + + AddText( "\tmouth 0 \"mouth\" \"ValveBiped.Bip01_Head1\" 0 1 0 // mouth illumination\n" ); + AddText( "\tflexfile \"%s\" {\n", p.m_ExpressionsFilename ); + AddText( "\t\t$include \"../standardflex_xsi.qci\"\n" ); + AddText( "\t}\n" ); + AddText( "\t$include \"../facerules_xsi.qci\"\n" ); + AddText( "\t$include \"../bodyrules_xsi.qci\"\n" ); + AddText( "}\n" ); + AddText( "//end eye/face data\n" ); +} + + +bool CQC_EyesDlg::CheckNumericInputs() +{ + struct + { + const char *pControlName; + UINT controlID; + } + controls[] = + { + {"Right Eye X", IDC_RIGHT_EYE_X}, + {"Right Eye Y", IDC_RIGHT_EYE_Y}, + {"Right Eye Z", IDC_RIGHT_EYE_Z}, + + {"Left Eye X", IDC_LEFT_EYE_X}, + {"Left Eye Y", IDC_LEFT_EYE_Y}, + {"Left Eye Z", IDC_LEFT_EYE_Z}, + + {"Upper Lid Raised", IDC_UPPER_LID_RAISED}, + {"Upper Lid Neutral", IDC_UPPER_LID_NEUTRAL}, + {"Upper Lid Lowered", IDC_UPPER_LID_LOWERED}, + + {"Lower Lid Raised", IDC_LOWER_LID_RAISED}, + {"Lower Lid Neutral", IDC_LOWER_LID_NEUTRAL}, + {"Lower Lid Lowered", IDC_LOWER_LID_LOWERED}, + + {"Upper Left Lid Raised", IDC_UPPER_LEFT_LID_RAISED}, + {"Upper Left Lid Neutral", IDC_UPPER_LEFT_LID_NEUTRAL}, + {"Upper Left Lid Lowered", IDC_UPPER_LEFT_LID_LOWERED}, + + {"Lower Left Lid Raised", IDC_LOWER_LEFT_LID_RAISED}, + {"Lower Left Lid Neutral", IDC_LOWER_LEFT_LID_NEUTRAL}, + {"Lower Left Lid Lowered", IDC_LOWER_LEFT_LID_LOWERED}, + + {"Iris Size", IDC_IRIS_SIZE}, + {"Eyeball Size", IDC_EYEBALL_SIZE} + }; + + for ( int i=0; i < sizeof( controls ) / sizeof( controls[0] ); i++ ) + { + char text[512]; + GetDlgItem( controls[i].controlID )->GetWindowText( text, sizeof( text ) ); + + for ( int z=0; z < (int)strlen( text ); z++ ) + { + if ( text[z] < '0' || text[z] > '9' ) + { + if ( text[z] != '.' && text[z] != '-' ) + { + char errMsg[512]; + _snprintf( errMsg, sizeof( errMsg ), "The '%s' control must have a numeric value.", controls[i].pControlName ); + AfxMessageBox( errMsg, MB_OK ); + return false; + } + } + } + } + + return true; +} + + +void CQC_EyesDlg::OnCreateQcText() +{ + if ( !CheckNumericInputs() ) + return; + + GenerateQCText(); + + // Clear the edit control. + LRESULT nLen = ::SendMessage( m_hOutputText, EM_GETLIMITTEXT, 0, 0 ); + ::SendMessage( m_hOutputText, EM_SETSEL, 0, nLen ); + ::SendMessage( m_hOutputText, EM_REPLACESEL, FALSE, (LPARAM)"" ); + + FormatAndSendToEditControl( m_hOutputText, m_Buf ); + + delete [] m_Buf; +} + +void CQC_EyesDlg::OnIrisColorBrown() +{ + ::SendMessage( ::GetDlgItem( m_hWnd, IDC_IRIS_COLOR_BROWN ), BM_SETCHECK, BST_CHECKED, 0 ); + ::SendMessage( ::GetDlgItem( m_hWnd, IDC_IRIS_COLOR_GREEN ), BM_SETCHECK, BST_UNCHECKED, 0 ); + ::SendMessage( ::GetDlgItem( m_hWnd, IDC_IRIS_COLOR_BLUE ), BM_SETCHECK, BST_UNCHECKED, 0 ); + SetupBitmapLabel( IDB_EYE_DEFAULT, "" ); +} + +void CQC_EyesDlg::OnIrisColorGreen() +{ + ::SendMessage( ::GetDlgItem( m_hWnd, IDC_IRIS_COLOR_BROWN ), BM_SETCHECK, BST_UNCHECKED, 0 ); + ::SendMessage( ::GetDlgItem( m_hWnd, IDC_IRIS_COLOR_GREEN ), BM_SETCHECK, BST_CHECKED, 0 ); + ::SendMessage( ::GetDlgItem( m_hWnd, IDC_IRIS_COLOR_BLUE ), BM_SETCHECK, BST_UNCHECKED, 0 ); + SetupBitmapLabel( IDB_EYE_DEFAULT, "" ); +} + +void CQC_EyesDlg::OnIrisColorBlue() +{ + ::SendMessage( ::GetDlgItem( m_hWnd, IDC_IRIS_COLOR_BROWN ), BM_SETCHECK, BST_UNCHECKED, 0 ); + ::SendMessage( ::GetDlgItem( m_hWnd, IDC_IRIS_COLOR_GREEN ), BM_SETCHECK, BST_UNCHECKED, 0 ); + ::SendMessage( ::GetDlgItem( m_hWnd, IDC_IRIS_COLOR_BLUE ), BM_SETCHECK, BST_CHECKED, 0 ); + SetupBitmapLabel( IDB_EYE_DEFAULT, "" ); +} + +void CQC_EyesDlg::OnEyeColorDark() +{ + ::SendMessage( ::GetDlgItem( m_hWnd, IDC_EYE_COLOR_LIGHT ), BM_SETCHECK, BST_UNCHECKED, 0 ); + ::SendMessage( ::GetDlgItem( m_hWnd, IDC_EYE_COLOR_DARK ), BM_SETCHECK, BST_CHECKED, 0 ); + SetupBitmapLabel( IDB_EYE_DEFAULT, "" ); +} + +void CQC_EyesDlg::OnEyeColorLight() +{ + ::SendMessage( ::GetDlgItem( m_hWnd, IDC_EYE_COLOR_LIGHT ), BM_SETCHECK, BST_CHECKED, 0 ); + ::SendMessage( ::GetDlgItem( m_hWnd, IDC_EYE_COLOR_DARK ), BM_SETCHECK, BST_UNCHECKED, 0 ); + SetupBitmapLabel( IDB_EYE_DEFAULT, "" ); +} + +void CQC_EyesDlg::SetupBitmapLabel( UINT iBitmapResourceID, const char *pString, ... ) +{ + char msg[4096]; + va_list marker; + va_start( marker, pString ); + _vsnprintf( msg, sizeof( msg ), pString, marker ); + msg[ ARRAYSIZE(msg) - 1 ] = 0; + va_end( marker ); + + m_PictureControl.SetBitmap( GetCachedBitmap( iBitmapResourceID ) ); + GetDlgItem( IDC_PICTURE_LABEL )->SetWindowText( msg ); +} + +void CQC_EyesDlg::OnSetfocusRightEyeX() +{ + SetupBitmapLabel( IDB_EYE_XY_R, "Enter the X position of the center vertex of the right eye" ); +} + +void CQC_EyesDlg::OnSetfocusRightEyeY() +{ + SetupBitmapLabel( IsOptionChecked( IDC_Y_AXIS_UP ) ? IDB_EYE_XY_R : IDB_EYE_Z_R, "Enter the Y position of the center vertex of the right eye" ); +} + +void CQC_EyesDlg::OnSetfocusRightEyeZ() +{ + SetupBitmapLabel( IsOptionChecked( IDC_Y_AXIS_UP ) ? IDB_EYE_Z_R : IDB_EYE_XY_R, "Enter the Z position of the center vertex of the right eye" ); +} + +void CQC_EyesDlg::OnSetfocusLeftEyeX() +{ + SetupBitmapLabel( IDB_EYE_XY_L, "Enter the X position of the center vertex of the right eye" ); +} + +void CQC_EyesDlg::OnSetfocusLeftEyeY() +{ + SetupBitmapLabel( IsOptionChecked( IDC_Y_AXIS_UP ) ? IDB_EYE_XY_L : IDB_EYE_Z_L, "Enter the Y position of the center vertex of the right eye" ); +} + +void CQC_EyesDlg::OnSetfocusLeftEyeZ() +{ + SetupBitmapLabel( IsOptionChecked( IDC_Y_AXIS_UP ) ? IDB_EYE_Z_L : IDB_EYE_XY_L, "Enter the Z position of the center vertex of the right eye" ); +} + +void CQC_EyesDlg::OnSetfocusUpperLidLowered() +{ + const char *pCoord = IsOptionChecked( IDC_Y_AXIS_UP ) ? "Y" : "Z"; + SetupBitmapLabel( IDB_EYE_UPPER_LO, "At Frame 1, enter the %s position of the center vertex of the right upper eye lid", pCoord ); +} + +void CQC_EyesDlg::OnSetfocusUpperLidNeutral() +{ + const char *pCoord = IsOptionChecked( IDC_Y_AXIS_UP ) ? "Y" : "Z"; + SetupBitmapLabel( IDB_EYE_UPPER_MID, "At Frame 0, enter the %s position of the center vertex of the right upper eye lid", pCoord ); +} + +void CQC_EyesDlg::OnSetfocusUpperLidRaised() +{ + const char *pCoord = IsOptionChecked( IDC_Y_AXIS_UP ) ? "Y" : "Z"; + SetupBitmapLabel( IDB_EYE_UPPER_HI, "At Frame 2, enter the %s position of the center vertex of the right upper eye lid", pCoord ); +} + + +void CQC_EyesDlg::OnSetfocusLowerLidLowered() +{ + const char *pCoord = IsOptionChecked( IDC_Y_AXIS_UP ) ? "Y" : "Z"; + SetupBitmapLabel( IDB_EYE_LOWER_LO, "At Frame 3, enter the %s position of the center vertex of the right lower eye lid", pCoord ); +} + +void CQC_EyesDlg::OnSetfocusLowerLidNeutral() +{ + const char *pCoord = IsOptionChecked( IDC_Y_AXIS_UP ) ? "Y" : "Z"; + SetupBitmapLabel( IDB_EYE_LOWER_MID, "At Frame 0, enter the %s position of the center vertex of the right lower eye lid", pCoord ); +} + +void CQC_EyesDlg::OnSetfocusLowerLidRaised() +{ + const char *pCoord = IsOptionChecked( IDC_Y_AXIS_UP ) ? "Y" : "Z"; + SetupBitmapLabel( IDB_EYE_LOWER_HI, "At Frame 4, enter the %s position of the center vertex of the right lower eye lid", pCoord ); +} + +void CQC_EyesDlg::OnCopyTextToClipboard() +{ + if ( !CheckNumericInputs() ) + return; + + GenerateQCText(); + + if ( !OpenClipboard() ) + return; + + size_t textLen = strlen( m_Buf ); + HANDLE hmem = GlobalAlloc( GMEM_MOVEABLE | GMEM_DDESHARE, textLen + 1 ); + if ( hmem ) + { + void *ptr = GlobalLock( hmem ); + if ( ptr ) + { + memcpy( ptr, m_Buf, textLen+1 ); + GlobalUnlock( hmem ); + + SetClipboardData( CF_TEXT, hmem ); + } + } + + CloseClipboard(); + + delete [] m_Buf; +} + + +int g_AdvancedControls[] = +{ + IDC_EYE_DETAIL_CONTROL_FRAME, + IDC_IRIS_SIZE, + IDC_IRIS_SIZE_LABEL, + IDC_EYEBALL_SIZE, + IDC_EYEBALL_SIZE_LABEL, + IDC_LEFT_LID_CONTROL +}; +#define NUM_ADVANCED_CONTROLS ( sizeof( g_AdvancedControls ) / sizeof( g_AdvancedControls[0] ) ) + +int g_LeftLidPositionControls[] = +{ + IDC_UPPER_LEFT_LID_PANEL, + IDC_UPPER_LEFT_LID_RAISED, + IDC_UPPER_LEFT_LID_RAISED_LABEL, + IDC_UPPER_LEFT_LID_NEUTRAL, + IDC_UPPER_LEFT_LID_NEUTRAL_LABEL, + IDC_UPPER_LEFT_LID_LOWERED, + IDC_UPPER_LEFT_LID_LOWERED_LABEL, + IDC_LOWER_LEFT_LID_PANEL, + IDC_LOWER_LEFT_LID_RAISED, + IDC_LOWER_LEFT_LID_RAISED_LABEL, + IDC_LOWER_LEFT_LID_NEUTRAL, + IDC_LOWER_LEFT_LID_NEUTRAL_LABEL, + IDC_LOWER_LEFT_LID_LOWERED, + IDC_LOWER_LEFT_LID_LOWERED_LABEL +}; +#define NUM_LEFT_LID_POSITION_CONTROLS ( sizeof( g_LeftLidPositionControls ) / sizeof( g_LeftLidPositionControls[0] ) ) + + +void CQC_EyesDlg::OnDefaultControls() +{ + GetDlgItem( IDC_PICTURES )->ShowWindow( SW_SHOW ); + + // Hide all the advanced controls. + for ( int i=0; i < NUM_ADVANCED_CONTROLS; i++ ) + { + GetDlgItem( g_AdvancedControls[i] )->ShowWindow( SW_HIDE ); + } + + for ( int i=0; i < NUM_LEFT_LID_POSITION_CONTROLS; i++ ) + { + GetDlgItem( g_LeftLidPositionControls[i] )->ShowWindow( SW_HIDE ); + } + +} + +void CQC_EyesDlg::OnAdvancedControls() +{ + GetDlgItem( IDC_PICTURES )->ShowWindow( SW_HIDE ); + + // Show the advanced controls. + for ( int i=0; i < NUM_ADVANCED_CONTROLS; i++ ) + { + GetDlgItem( g_AdvancedControls[i] )->ShowWindow( SW_SHOW ); + GetDlgItem( g_AdvancedControls[i] )->InvalidateRect( NULL ); + } + + if ( IsIndependentLeftLidControlEnabled() ) + { + OnLeftLidControl(); + } +} + + +bool CQC_EyesDlg::IsIndependentLeftLidControlEnabled() +{ + return m_IndependentLeftLidControl.GetCheck() == 1; +} + +void CQC_EyesDlg::OnLeftLidControl() +{ + if ( IsIndependentLeftLidControlEnabled() ) + { + for ( int i=0; i < NUM_LEFT_LID_POSITION_CONTROLS; i++ ) + { + GetDlgItem( g_LeftLidPositionControls[i] )->ShowWindow( SW_SHOW ); + GetDlgItem( g_LeftLidPositionControls[i] )->InvalidateRect( NULL ); + } + } + else + { + for ( int i=0; i < NUM_LEFT_LID_POSITION_CONTROLS; i++ ) + { + GetDlgItem( g_LeftLidPositionControls[i] )->ShowWindow( SW_HIDE ); + GetDlgItem( g_LeftLidPositionControls[i] )->InvalidateRect( NULL ); + } + } +} diff --git a/mp/src/utils/qc_eyes/QC_EyesDlg.h b/mp/src/utils/qc_eyes/QC_EyesDlg.h new file mode 100644 index 00000000..62b42b1a --- /dev/null +++ b/mp/src/utils/qc_eyes/QC_EyesDlg.h @@ -0,0 +1,134 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// QC_EyesDlg.h : header file +// + +#if !defined(AFX_QC_EYESDLG_H__9130E22D_05ED_4851_960C_38D90DA94967__INCLUDED_) +#define AFX_QC_EYESDLG_H__9130E22D_05ED_4851_960C_38D90DA94967__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +class CDialogParams +{ +public: + float m_flLeftEye[3]; + float m_flRightEye[3]; + + float m_flIrisSize; + float m_flEyeballSize; + + float m_flLeftUpperLidRaised; + float m_flLeftUpperLidNeutral; + float m_flLeftUpperLidLowered; + + float m_flLeftLowerLidRaised; + float m_flLeftLowerLidNeutral; + float m_flLeftLowerLidLowered; + + float m_flRightUpperLidRaised; + float m_flRightUpperLidNeutral; + float m_flRightUpperLidLowered; + + float m_flRightLowerLidRaised; + float m_flRightLowerLidNeutral; + float m_flRightLowerLidLowered; + + char m_ReferenceFilename[1024]; + char m_ExpressionsFilename[1024]; + char m_ModelFilename[1024]; + + char m_EyeballPrefix[1024]; // eyeball_ or dark_eyeball_ + char m_PupilPrefix[1024]; // pupil_ or grn_pupil_ or bl_pupil_ +}; + +///////////////////////////////////////////////////////////////////////////// +// CQC_EyesDlg dialog + +class CQC_EyesDlg : public CDialog +{ +// Construction +public: + CQC_EyesDlg(CWnd* pParent = NULL); // standard constructor + +// Dialog Data + //{{AFX_DATA(CQC_EyesDlg) + enum { IDD = IDD_QC_EYES_DIALOG }; + CButton m_IndependentLeftLidControl; + CStatic m_PictureControl; + //}}AFX_DATA + + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CQC_EyesDlg) + protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + //}}AFX_VIRTUAL + +// Implementation +protected: + HICON m_hIcon; + + void GenerateQCText(); + void AddText( const char *pFormat, ... ); + bool IsOptionChecked( UINT option ); + float GetDlgItemFloat( UINT id ); + void GetDialogParams( CDialogParams &p ); + void SetupBitmapLabel( UINT iBitmapResourceID, const char *pString, ... ); + + HWND m_hOutputText; + + + // Cached list of bitmaps. + class CBitmapRef + { + public: + UINT m_iResource; + HBITMAP m_hBitmap; + CBitmapRef *m_pNext; + }; + CBitmapRef *m_pBitmapHead; + HBITMAP GetCachedBitmap( UINT id ); + + + size_t m_BufSize; + char *m_Buf; + bool IsIndependentLeftLidControlEnabled(); + + bool CheckNumericInputs(); + + + // Generated message map functions + //{{AFX_MSG(CQC_EyesDlg) + virtual BOOL OnInitDialog(); + afx_msg void OnPaint(); + afx_msg HCURSOR OnQueryDragIcon(); + afx_msg void OnCreateQcText(); + afx_msg void OnIrisColorBrown(); + afx_msg void OnIrisColorGreen(); + afx_msg void OnIrisColorBlue(); + afx_msg void OnEyeColorDark(); + afx_msg void OnEyeColorLight(); + afx_msg void OnSetfocusRightEyeX(); + afx_msg void OnSetfocusRightEyeY(); + afx_msg void OnSetfocusRightEyeZ(); + afx_msg void OnSetfocusLeftEyeX(); + afx_msg void OnSetfocusLeftEyeY(); + afx_msg void OnSetfocusLeftEyeZ(); + afx_msg void OnSetfocusUpperLidLowered(); + afx_msg void OnSetfocusUpperLidNeutral(); + afx_msg void OnSetfocusUpperLidRaised(); + afx_msg void OnSetfocusLowerLidLowered(); + afx_msg void OnSetfocusLowerLidNeutral(); + afx_msg void OnSetfocusLowerLidRaised(); + afx_msg void OnCopyTextToClipboard(); + afx_msg void OnDefaultControls(); + afx_msg void OnAdvancedControls(); + afx_msg void OnLeftLidControl(); + //}}AFX_MSG + DECLARE_MESSAGE_MAP() +}; + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_QC_EYESDLG_H__9130E22D_05ED_4851_960C_38D90DA94967__INCLUDED_) diff --git a/mp/src/utils/qc_eyes/StdAfx.cpp b/mp/src/utils/qc_eyes/StdAfx.cpp new file mode 100644 index 00000000..8fdb97ce --- /dev/null +++ b/mp/src/utils/qc_eyes/StdAfx.cpp @@ -0,0 +1,9 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// stdafx.cpp : source file that includes just the standard includes +// QC_Eyes.pch will be the pre-compiled header +// stdafx.obj will contain the pre-compiled type information + +#include "stdafx.h" + + + diff --git a/mp/src/utils/qc_eyes/StdAfx.h b/mp/src/utils/qc_eyes/StdAfx.h new file mode 100644 index 00000000..c65813d7 --- /dev/null +++ b/mp/src/utils/qc_eyes/StdAfx.h @@ -0,0 +1,28 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// stdafx.h : include file for standard system include files, +// or project specific include files that are used frequently, but +// are changed infrequently +// + +#if !defined(AFX_STDAFX_H__88FA48B3_A92C_49CA_8A82_50D120A84756__INCLUDED_) +#define AFX_STDAFX_H__88FA48B3_A92C_49CA_8A82_50D120A84756__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#define VC_EXTRALEAN // Exclude rarely-used stuff from Windows headers + +#include // MFC core and standard components +#include // MFC extensions +#include // MFC Automation classes +#include // MFC support for Internet Explorer 4 Common Controls +#ifndef _AFX_NO_AFXCMN_SUPPORT +#include // MFC support for Windows Common Controls +#endif // _AFX_NO_AFXCMN_SUPPORT + + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_STDAFX_H__88FA48B3_A92C_49CA_8A82_50D120A84756__INCLUDED_) diff --git a/mp/src/utils/qc_eyes/qc_eyes-2010.vcxproj b/mp/src/utils/qc_eyes/qc_eyes-2010.vcxproj new file mode 100644 index 00000000..0de210fb --- /dev/null +++ b/mp/src/utils/qc_eyes/qc_eyes-2010.vcxproj @@ -0,0 +1,261 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + QC_Eyes + {EA02FAE0-2A4F-C7C8-6176-5DEDA8E139E9} + + + + Application + MultiByte + qc_eyes + + + Application + MultiByte + qc_eyes + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30319.1 + .\Debug\win32\ + .\Debug\win32\ + ..\..\devtools\vstools;$(ExecutablePath);$(Path) + true + true + true + true + true + .\Release\win32\ + .\Release\win32\ + ..\..\devtools\vstools;$(ExecutablePath);$(Path) + true + true + false + true + true + + + + if EXIST ..\..\..\game\bin\$(TargetFileName) for /f "delims=" %%A in ('attrib "..\..\..\game\bin\$(TargetFileName)"') do set valveTmpIsReadOnly="%%A" set valveTmpIsReadOnlyLetter=%valveTmpIsReadOnly:~6,1% if "%valveTmpIsReadOnlyLetter%"=="R" del /q "$(TargetDir)"$(TargetFileName) if exist ..\..\devtools\bin\vpc.exe ..\..\devtools\bin\vpc.exe -crc2 qc_eyes.vcxproj if ERRORLEVEL 1 exit 1 + + + /MP + Disabled + ..\..\common;..\..\public;..\..\public\tier0;..\..\public\tier1 + _HAS_ITERATOR_DEBUGGING=0;WIN32;_WIN32;_DEBUG;DEBUG;_WINDOWS;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;RAD_TELEMETRY_DISABLED;COMPILER_MSVC32;VPCGAMECAPS=VALVE;PROJECTDIR=D:\dev\games\rel\hl2\src\utils\qc_eyes;_DLL_EXT=.dll;VPCGAME=valve + true + Sync + Default + MultiThreadedDebug + true + StreamingSIMDExtensions + Fast + true + true + true + false + Use + Debug/QC_Eyes.pch + false + NoListing + $(IntDir)/ + $(IntDir)/ + $(IntDir)/ + false + Level4 + true + EditAndContinue + CompileAsCpp + $(IntDir)/ + Prompt + + + _DEBUG;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE + 1033 + + + + + /NXCOMPAT /ignore:4221 + %(AdditionalDependencies) + NotSet + $(OutDir)\qc_eyes.exe + true + libc;libcd;libcmt + true + $(IntDir)/$(TargetName).pdb + false + $(IntDir)/$(TargetName).map + Windows + + MachineX86 + PromptImmediately + false + + + true + + + true + + + true + $(OutDir)/qc_eyes.bsc + + + Publishing to ..\..\..\game\bin + if not exist "..\..\..\game\bin" mkdir "..\..\..\game\bin" copy "$(TargetDir)"$(TargetFileName) ..\..\..\game\bin\$(TargetFileName) if ERRORLEVEL 1 goto BuildEventFailed if exist "$(TargetDir)"$(TargetName).map copy "$(TargetDir)"$(TargetName).map ..\..\..\game\bin\$(TargetName).map copy "$(TargetDir)"$(TargetName).pdb ..\..\..\game\bin\$(TargetName).pdb if ERRORLEVEL 1 goto BuildEventFailed goto BuildEventOK :BuildEventFailed echo *** ERROR! PostBuildStep FAILED for $(ProjectName)! EXE or DLL is probably running. *** del /q "$(TargetDir)"$(TargetFileName) exit 1 :BuildEventOK + + + + + + + if EXIST ..\..\..\game\bin\$(TargetFileName) for /f "delims=" %%A in ('attrib "..\..\..\game\bin\$(TargetFileName)"') do set valveTmpIsReadOnly="%%A" set valveTmpIsReadOnlyLetter=%valveTmpIsReadOnly:~6,1% if "%valveTmpIsReadOnlyLetter%"=="R" del /q "$(TargetDir)"$(TargetFileName) if exist ..\..\devtools\bin\vpc.exe ..\..\devtools\bin\vpc.exe -crc2 qc_eyes.vcxproj if ERRORLEVEL 1 exit 1 + + + /MP /d2Zi+ + MaxSpeed + AnySuitable + true + Speed + ..\..\common;..\..\public;..\..\public\tier0;..\..\public\tier1 + WIN32;_WIN32;NDEBUG;_WINDOWS;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;RAD_TELEMETRY_DISABLED;COMPILER_MSVC32;VPCGAMECAPS=VALVE;PROJECTDIR=D:\dev\games\rel\hl2\src\utils\qc_eyes;_DLL_EXT=.dll;VPCGAME=valve + true + Sync + MultiThreaded + false + true + StreamingSIMDExtensions + Fast + true + true + true + false + Use + Debug/QC_Eyes.pch + false + NoListing + $(IntDir)/ + $(IntDir)/ + $(IntDir)/ + false + Level4 + true + ProgramDatabase + CompileAsCpp + $(IntDir)/ + Prompt + + + NDEBUG;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE + 1033 + + + + + /DYNAMICBASE /NXCOMPAT /ignore:4221 + %(AdditionalDependencies) + NotSet + $(OutDir)\qc_eyes.exe + true + libc;libcd;libcmtd + true + $(IntDir)/$(TargetName).pdb + false + $(IntDir)/$(TargetName).map + Windows + true + true + + MachineX86 + PromptImmediately + + + true + + + true + + + true + $(OutDir)/qc_eyes.bsc + + + Publishing to ..\..\..\game\bin + if not exist "..\..\..\game\bin" mkdir "..\..\..\game\bin" copy "$(TargetDir)"$(TargetFileName) ..\..\..\game\bin\$(TargetFileName) if ERRORLEVEL 1 goto BuildEventFailed if exist "$(TargetDir)"$(TargetName).map copy "$(TargetDir)"$(TargetName).map ..\..\..\game\bin\$(TargetName).map copy "$(TargetDir)"$(TargetName).pdb ..\..\..\game\bin\$(TargetName).pdb if ERRORLEVEL 1 goto BuildEventFailed goto BuildEventOK :BuildEventFailed echo *** ERROR! PostBuildStep FAILED for $(ProjectName)! EXE or DLL is probably running. *** del /q "$(TargetDir)"$(TargetFileName) exit 1 :BuildEventOK + + + + + + + + + + + + + + + + + + + + Create + Create + + + + + + + + Compiling pointeroverride.asm + "$(VCInstallDir)bin\ml.exe" /c /Cp /Zi /Fo"$(IntDir)\%(Filename).obj" "%(FullPath)" + $(IntDir)\%(Filename).obj + Compiling pointeroverride.asm + "$(VCInstallDir)bin\ml.exe" /c /Cp /Zi /Fo"$(IntDir)\%(Filename).obj" "%(FullPath)" + $(IntDir)\%(Filename).obj + + + + + + + + + + + + + + + + + + + + + diff --git a/mp/src/utils/qc_eyes/qc_eyes-2010.vcxproj.filters b/mp/src/utils/qc_eyes/qc_eyes-2010.vcxproj.filters new file mode 100644 index 00000000..cdbb0778 --- /dev/null +++ b/mp/src/utils/qc_eyes/qc_eyes-2010.vcxproj.filters @@ -0,0 +1,104 @@ + + + + + {1680C80B-FF1E-EA4D-9817-CC12254F2E40} + + + {C5D73B3A-C648-896C-B7CE-F174808E5BA5} + + + {DDCF50C2-9294-D441-4F3F-7C1BBC892CB5} + + + {BA03E055-4FA2-FCE3-8A1C-D348547D379C} + + + + + Link Libraries + + + Link Libraries + + + Link Libraries + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + Source Files + + + + + Source Files + + + + + Source Files + + + + + Resources + + + Resources + + + Resources + + + Resources + + + Resources + + + Resources + + + Resources + + + Resources + + + Resources + + + Resources + + + Resources + + + Resources + + + Resources + + + diff --git a/mp/src/utils/qc_eyes/res/QC_Eyes.ico b/mp/src/utils/qc_eyes/res/QC_Eyes.ico new file mode 100644 index 00000000..7eef0bcb Binary files /dev/null and b/mp/src/utils/qc_eyes/res/QC_Eyes.ico differ diff --git a/mp/src/utils/qc_eyes/res/QC_Eyes.rc2 b/mp/src/utils/qc_eyes/res/QC_Eyes.rc2 new file mode 100644 index 00000000..b57420f8 --- /dev/null +++ b/mp/src/utils/qc_eyes/res/QC_Eyes.rc2 @@ -0,0 +1,13 @@ +// +// QC_EYES.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/mp/src/utils/qc_eyes/res/eye_XY_L.bmp b/mp/src/utils/qc_eyes/res/eye_XY_L.bmp new file mode 100644 index 00000000..3350ed2f Binary files /dev/null and b/mp/src/utils/qc_eyes/res/eye_XY_L.bmp differ diff --git a/mp/src/utils/qc_eyes/res/eye_XY_R.bmp b/mp/src/utils/qc_eyes/res/eye_XY_R.bmp new file mode 100644 index 00000000..9bfbc91f Binary files /dev/null and b/mp/src/utils/qc_eyes/res/eye_XY_R.bmp differ diff --git a/mp/src/utils/qc_eyes/res/eye_Z_L.bmp b/mp/src/utils/qc_eyes/res/eye_Z_L.bmp new file mode 100644 index 00000000..949f9ac1 Binary files /dev/null and b/mp/src/utils/qc_eyes/res/eye_Z_L.bmp differ diff --git a/mp/src/utils/qc_eyes/res/eye_Z_R.bmp b/mp/src/utils/qc_eyes/res/eye_Z_R.bmp new file mode 100644 index 00000000..72042e1d Binary files /dev/null and b/mp/src/utils/qc_eyes/res/eye_Z_R.bmp differ diff --git a/mp/src/utils/qc_eyes/res/eye_default.bmp b/mp/src/utils/qc_eyes/res/eye_default.bmp new file mode 100644 index 00000000..705ac95b Binary files /dev/null and b/mp/src/utils/qc_eyes/res/eye_default.bmp differ diff --git a/mp/src/utils/qc_eyes/res/eye_lower_hi.bmp b/mp/src/utils/qc_eyes/res/eye_lower_hi.bmp new file mode 100644 index 00000000..9a88d59e Binary files /dev/null and b/mp/src/utils/qc_eyes/res/eye_lower_hi.bmp differ diff --git a/mp/src/utils/qc_eyes/res/eye_lower_lo.bmp b/mp/src/utils/qc_eyes/res/eye_lower_lo.bmp new file mode 100644 index 00000000..90b42b60 Binary files /dev/null and b/mp/src/utils/qc_eyes/res/eye_lower_lo.bmp differ diff --git a/mp/src/utils/qc_eyes/res/eye_lower_mid.bmp b/mp/src/utils/qc_eyes/res/eye_lower_mid.bmp new file mode 100644 index 00000000..9affa705 Binary files /dev/null and b/mp/src/utils/qc_eyes/res/eye_lower_mid.bmp differ diff --git a/mp/src/utils/qc_eyes/res/eye_upper_hi.bmp b/mp/src/utils/qc_eyes/res/eye_upper_hi.bmp new file mode 100644 index 00000000..fdc8b38e Binary files /dev/null and b/mp/src/utils/qc_eyes/res/eye_upper_hi.bmp differ diff --git a/mp/src/utils/qc_eyes/res/eye_upper_lo.bmp b/mp/src/utils/qc_eyes/res/eye_upper_lo.bmp new file mode 100644 index 00000000..16313d58 Binary files /dev/null and b/mp/src/utils/qc_eyes/res/eye_upper_lo.bmp differ diff --git a/mp/src/utils/qc_eyes/res/eye_upper_mid.bmp b/mp/src/utils/qc_eyes/res/eye_upper_mid.bmp new file mode 100644 index 00000000..724ede4a Binary files /dev/null and b/mp/src/utils/qc_eyes/res/eye_upper_mid.bmp differ diff --git a/mp/src/utils/qc_eyes/resource.h b/mp/src/utils/qc_eyes/resource.h new file mode 100644 index 00000000..d16935a9 --- /dev/null +++ b/mp/src/utils/qc_eyes/resource.h @@ -0,0 +1,78 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +//{{NO_DEPENDENCIES}} +// Microsoft Developer Studio generated include file. +// Used by QC_Eyes.rc +// +#define IDD_QC_EYES_DIALOG 102 +#define IDR_MAINFRAME 128 +#define IDB_EYE_DEFAULT 150 +#define IDB_EYE_LOWER_HI 151 +#define IDB_EYE_LOWER_LO 152 +#define IDB_EYE_LOWER_MID 153 +#define IDB_EYE_UPPER_HI 154 +#define IDB_EYE_UPPER_LO 155 +#define IDB_EYE_UPPER_MID 156 +#define IDB_EYE_XY_L 157 +#define IDB_EYE_XY_R 158 +#define IDB_EYE_Z_L 159 +#define IDB_EYE_Z_R 160 +#define IDC_REFERENCE_FILENAME 1000 +#define IDC_EXPRESSIONS_FILENAME 1001 +#define IDC_MODEL_FILENAME 1002 +#define IDC_RIGHT_EYE_X 1003 +#define IDC_RIGHT_EYE_Y 1004 +#define IDC_RIGHT_EYE_Z 1005 +#define IDC_LEFT_EYE_X 1006 +#define IDC_LEFT_EYE_Y 1007 +#define IDC_LEFT_EYE_Z 1008 +#define IDC_Y_AXIS_UP 1009 +#define IDC_Z_AXIS_UP 1010 +#define IDC_DEFAULT_CONTROLS 1011 +#define IDC_ADVANCED_CONTROLS 1012 +#define IDC_UPPER_LID_RAISED 1013 +#define IDC_OUTPUT_TEXT 1014 +#define IDC_UPPER_LEFT_LID_RAISED 1015 +#define IDC_UPPER_LID_LOWERED 1016 +#define IDC_UPPER_LID_NEUTRAL 1017 +#define IDC_LOWER_LID_RAISED 1018 +#define IDC_LOWER_LID_LOWERED 1019 +#define IDC_LOWER_LID_NEUTRAL 1020 +#define IDC_IRIS_COLOR_GREEN 1021 +#define IDC_IRIS_COLOR_BLUE 1022 +#define IDC_IRIS_COLOR_BROWN 1023 +#define IDC_EYE_COLOR_LIGHT 1024 +#define IDC_EYE_COLOR_DARK 1025 +#define IDC_IRIS_SIZE 1026 +#define IDC_CREATE_QC_TEXT 1027 +#define IDC_EYEBALL_SIZE 1028 +#define IDC_COPY_TEXT_TO_CLIPBOARD 1029 +#define IDC_PICTURES 1030 +#define IDC_PICTURE_LABEL 1031 +#define IDC_LEFT_LID_CONTROL 1032 +#define IDC_UPPER_LEFT_LID_NEUTRAL 1033 +#define IDC_UPPER_LEFT_LID_LOWERED 1034 +#define IDC_LOWER_LEFT_LID_RAISED 1035 +#define IDC_LOWER_LEFT_LID_NEUTRAL 1036 +#define IDC_LOWER_LEFT_LID_LOWERED 1037 +#define IDC_EYE_DETAIL_CONTROL_FRAME 1038 +#define IDC_IRIS_SIZE_LABEL 1039 +#define IDC_EYEBALL_SIZE_LABEL 1040 +#define IDC_UPPER_LEFT_LID_PANEL 1041 +#define IDC_UPPER_LEFT_LID_RAISED_LABEL 1042 +#define IDC_UPPER_LEFT_LID_NEUTRAL_LABEL 1043 +#define IDC_UPPER_LEFT_LID_LOWERED_LABEL 1044 +#define IDC_LOWER_LEFT_LID_PANEL 1045 +#define IDC_LOWER_LEFT_LID_RAISED_LABEL 1046 +#define IDC_LOWER_LEFT_LID_NEUTRAL_LABEL 1047 +#define IDC_LOWER_LEFT_LID_LOWERED_LABEL 1048 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 130 +#define _APS_NEXT_COMMAND_VALUE 32771 +#define _APS_NEXT_CONTROL_VALUE 1049 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/mp/src/utils/serverplugin_sample/serverplugin_bot.cpp b/mp/src/utils/serverplugin_sample/serverplugin_bot.cpp new file mode 100644 index 00000000..250bf149 --- /dev/null +++ b/mp/src/utils/serverplugin_sample/serverplugin_bot.cpp @@ -0,0 +1,383 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Basic BOT handling. +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//=============================================================================// + +#include "interface.h" +#include "filesystem.h" +#undef VECTOR_NO_SLOW_OPERATIONS +#include "mathlib/vector.h" + +#include "eiface.h" +#include "edict.h" +#include "game/server/iplayerinfo.h" +#include "igameevents.h" +#include "convar.h" +#include "vstdlib/random.h" +#include "../../game/shared/in_buttons.h" +#include "../../game/shared/shareddefs.h" +//#include "../../game_shared/util_shared.h" +#include "engine/IEngineTrace.h" + +extern IBotManager *botmanager; +extern IUniformRandomStream *randomStr; +extern IPlayerInfoManager *playerinfomanager; +extern IVEngineServer *engine; +extern IEngineTrace *enginetrace; +extern IPlayerInfoManager *playerinfomanager; // game dll interface to interact with players +extern IServerPluginHelpers *helpers; // special 3rd party plugin helpers from the engine + +extern CGlobalVars *gpGlobals; + +ConVar bot_forcefireweapon( "plugin_bot_forcefireweapon", "", 0, "Force bots with the specified weapon to fire." ); +ConVar bot_forceattack2( "plugin_bot_forceattack2", "0", 0, "When firing, use attack2." ); +ConVar bot_forceattackon( "plugin_bot_forceattackon", "0", 0, "When firing, don't tap fire, hold it down." ); +ConVar bot_flipout( "plugin_bot_flipout", "0", 0, "When on, all bots fire their guns." ); +ConVar bot_changeclass( "plugin_bot_changeclass", "0", 0, "Force all bots to change to the specified class." ); +static ConVar bot_mimic( "plugin_bot_mimic", "0", 0, "Bot uses usercmd of player by index." ); +static ConVar bot_mimic_yaw_offset( "plugin_bot_mimic_yaw_offset", "0", 0, "Offsets the bot yaw." ); + +ConVar bot_sendcmd( "plugin_bot_sendcmd", "", 0, "Forces bots to send the specified command." ); +ConVar bot_crouch( "plugin_bot_crouch", "0", 0, "Bot crouches" ); + + +// This is our bot class. +class CPluginBot +{ +public: + CPluginBot() : + m_bBackwards(0), + m_flNextTurnTime(0), + m_bLastTurnToRight(0), + m_flNextStrafeTime(0), + m_flSideMove(0), + m_ForwardAngle(), + m_LastAngles() + { + } + + bool m_bBackwards; + + float m_flNextTurnTime; + bool m_bLastTurnToRight; + + float m_flNextStrafeTime; + float m_flSideMove; + + QAngle m_ForwardAngle; + QAngle m_LastAngles; + + IBotController *m_BotInterface; + IPlayerInfo *m_PlayerInfo; + edict_t *m_BotEdict; +}; + +CUtlVector s_Bots; + +void Bot_Think( CPluginBot *pBot ); + +// Handler for the "bot" command. +void BotAdd_f() +{ + if ( !botmanager ) + return; + + static int s_BotNum = 0; + char botName[64]; + Q_snprintf( botName, sizeof(botName), "Bot_%i", s_BotNum ); + s_BotNum++; + + edict_t *botEdict = botmanager->CreateBot( botName ); + if ( botEdict ) + { + int botIndex = s_Bots.AddToTail(); + CPluginBot & bot = s_Bots[ botIndex ]; + bot.m_BotInterface = botmanager->GetBotController( botEdict ); + bot.m_PlayerInfo = playerinfomanager->GetPlayerInfo( botEdict ); + bot.m_BotEdict = botEdict; + Assert( bot.m_BotInterface ); + } +} + +ConCommand cc_Bot( "plugin_bot_add", BotAdd_f, "Add a bot." ); + + +//----------------------------------------------------------------------------- +// Purpose: Run through all the Bots in the game and let them think. +//----------------------------------------------------------------------------- +void Bot_RunAll( void ) +{ + if ( !botmanager ) + return; + + for ( int i = 0; i < s_Bots.Count(); i++ ) + { + CPluginBot & bot = s_Bots[i]; + if ( bot.m_BotEdict->IsFree() || !bot.m_BotEdict->GetUnknown()|| !bot.m_PlayerInfo->IsConnected() ) + { + s_Bots.Remove(i); + --i; + } + else + { + Bot_Think( &bot ); + } + } +} + +bool Bot_RunMimicCommand( CBotCmd& cmd ) +{ + if ( bot_mimic.GetInt() <= 0 ) + return false; + + if ( bot_mimic.GetInt() > gpGlobals->maxClients ) + return false; + + IPlayerInfo *playerInfo = playerinfomanager->GetPlayerInfo( engine->PEntityOfEntIndex( bot_mimic.GetInt() ) ); + if ( !playerInfo ) + return false; + + cmd = playerInfo->GetLastUserCommand(); + cmd.viewangles[YAW] += bot_mimic_yaw_offset.GetFloat(); + + if( bot_crouch.GetInt() ) + cmd.buttons |= IN_DUCK; + + return true; +} + +void Bot_UpdateStrafing( CPluginBot *pBot, CBotCmd &cmd ) +{ + if ( gpGlobals->curtime >= pBot->m_flNextStrafeTime ) + { + pBot->m_flNextStrafeTime = gpGlobals->curtime + 1.0f; + + if ( randomStr->RandomInt( 0, 5 ) == 0 ) + { + pBot->m_flSideMove = -600.0f + 1200.0f * randomStr->RandomFloat( 0, 2 ); + } + else + { + pBot->m_flSideMove = 0; + } + cmd.sidemove = pBot->m_flSideMove; + + if ( randomStr->RandomInt( 0, 20 ) == 0 ) + { + pBot->m_bBackwards = true; + } + else + { + pBot->m_bBackwards = false; + } + } +} + +void Bot_UpdateDirection( CPluginBot *pBot ) +{ + float angledelta = 15.0; + + int maxtries = (int)360.0/angledelta; + + if ( pBot->m_bLastTurnToRight ) + { + angledelta = -angledelta; + } + + QAngle angle( pBot->m_BotInterface->GetLocalAngles() ); + + trace_t trace; + Vector vecSrc, vecEnd, forward; + while ( --maxtries >= 0 ) + { + AngleVectors( angle, &forward ); + + vecSrc = pBot->m_BotInterface->GetLocalOrigin() + Vector( 0, 0, 36 ); + vecEnd = vecSrc + forward * 10; + + Ray_t ray; + ray.Init( vecSrc, vecEnd, Vector(-16, -16, 0 ), Vector( 16, 16, 72 ) ); + CTraceFilterWorldAndPropsOnly traceFilter; + enginetrace->TraceRay( ray, MASK_PLAYERSOLID, &traceFilter, &trace ); + + if ( trace.fraction == 1.0 ) + { + if ( gpGlobals->curtime < pBot->m_flNextTurnTime ) + { + break; + } + } + + angle.y += angledelta; + + if ( angle.y > 180 ) + angle.y -= 360; + else if ( angle.y < -180 ) + angle.y += 360; + + pBot->m_flNextTurnTime = gpGlobals->curtime + 2.0; + pBot->m_bLastTurnToRight = randomStr->RandomInt( 0, 1 ) == 0 ? true : false; + + pBot->m_ForwardAngle = angle; + pBot->m_LastAngles = angle; + } + + pBot->m_BotInterface->SetLocalAngles( angle ); +} + + +void Bot_FlipOut( CPluginBot *pBot, CBotCmd &cmd ) +{ + if ( bot_flipout.GetInt() > 0 && !pBot->m_PlayerInfo->IsDead() ) + { + if ( bot_forceattackon.GetBool() || (RandomFloat(0.0,1.0) > 0.5) ) + { + cmd.buttons |= bot_forceattack2.GetBool() ? IN_ATTACK2 : IN_ATTACK; + } + + if ( bot_flipout.GetInt() >= 2 ) + { + QAngle angOffset = RandomAngle( -1, 1 ); + + pBot->m_LastAngles += angOffset; + + for ( int i = 0 ; i < 2; i++ ) + { + if ( fabs( pBot->m_LastAngles[ i ] - pBot->m_ForwardAngle[ i ] ) > 15.0f ) + { + if ( pBot->m_LastAngles[ i ] > pBot->m_ForwardAngle[ i ] ) + { + pBot->m_LastAngles[ i ] = pBot->m_ForwardAngle[ i ] + 15; + } + else + { + pBot->m_LastAngles[ i ] = pBot->m_ForwardAngle[ i ] - 15; + } + } + } + + pBot->m_LastAngles[ 2 ] = 0; + + pBot->m_BotInterface->SetLocalAngles( pBot->m_LastAngles ); + } + } +} + + +void Bot_HandleSendCmd( CPluginBot *pBot ) +{ + if ( strlen( bot_sendcmd.GetString() ) > 0 ) + { + //send the cmd from this bot + helpers->ClientCommand( pBot->m_BotEdict, bot_sendcmd.GetString() ); + + bot_sendcmd.SetValue(""); + } +} + + +// If bots are being forced to fire a weapon, see if I have it +void Bot_ForceFireWeapon( CPluginBot *pBot, CBotCmd &cmd ) +{ + if ( Q_strlen( bot_forcefireweapon.GetString() ) > 0 ) + { + pBot->m_BotInterface->SetActiveWeapon( bot_forcefireweapon.GetString() ); + bot_forcefireweapon.SetValue( "" ); + // Start firing + // Some weapons require releases, so randomise firing + if ( bot_forceattackon.GetBool() || (RandomFloat(0.0,1.0) > 0.5) ) + { + cmd.buttons |= bot_forceattack2.GetBool() ? IN_ATTACK2 : IN_ATTACK; + } + } +} + + +void Bot_SetForwardMovement( CPluginBot *pBot, CBotCmd &cmd ) +{ + if ( !pBot->m_BotInterface->IsEFlagSet(EFL_BOT_FROZEN) ) + { + if ( pBot->m_PlayerInfo->GetHealth() == 100 ) + { + cmd.forwardmove = 600 * ( pBot->m_bBackwards ? -1 : 1 ); + if ( pBot->m_flSideMove != 0.0f ) + { + cmd.forwardmove *= randomStr->RandomFloat( 0.1, 1.0f ); + } + } + else + { + // Stop when shot + cmd.forwardmove = 0; + } + } +} + + +void Bot_HandleRespawn( CPluginBot *pBot, CBotCmd &cmd ) +{ + // Wait for Reinforcement wave + if ( pBot->m_PlayerInfo->IsDead() ) + { + if ( pBot->m_PlayerInfo->GetTeamIndex() == 0 ) + { + helpers->ClientCommand( pBot->m_BotEdict, "joingame" ); + helpers->ClientCommand( pBot->m_BotEdict, "jointeam 3" ); + helpers->ClientCommand( pBot->m_BotEdict, "joinclass 0" ); + } + } +} + + +//----------------------------------------------------------------------------- +// Run this Bot's AI for one frame. +//----------------------------------------------------------------------------- +void Bot_Think( CPluginBot *pBot ) +{ + CBotCmd cmd; + Q_memset( &cmd, 0, sizeof( cmd ) ); + + // Finally, override all this stuff if the bot is being forced to mimic a player. + if ( !Bot_RunMimicCommand( cmd ) ) + { + cmd.sidemove = pBot->m_flSideMove; + + if ( !pBot->m_PlayerInfo->IsDead() ) + { + Bot_SetForwardMovement( pBot, cmd ); + + // Only turn if I haven't been hurt + if ( !pBot->m_BotInterface->IsEFlagSet(EFL_BOT_FROZEN) && pBot->m_PlayerInfo->GetHealth() == 100 ) + { + Bot_UpdateDirection( pBot ); + Bot_UpdateStrafing( pBot, cmd ); + } + + // Handle console settings. + Bot_ForceFireWeapon( pBot, cmd ); + Bot_HandleSendCmd( pBot ); + } + else + { + Bot_HandleRespawn( pBot, cmd ); + } + + Bot_FlipOut( pBot, cmd ); + + cmd.viewangles = pBot->m_BotInterface->GetLocalAngles(); + cmd.upmove = 0; + cmd.impulse = 0; + } + + pBot->m_BotInterface->RunPlayerMove( &cmd ); +} + + diff --git a/mp/src/utils/serverplugin_sample/serverplugin_empty-2010.vcxproj b/mp/src/utils/serverplugin_sample/serverplugin_empty-2010.vcxproj new file mode 100644 index 00000000..14a96c78 --- /dev/null +++ b/mp/src/utils/serverplugin_sample/serverplugin_empty-2010.vcxproj @@ -0,0 +1,258 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + Serverplugin_empty + {394B82B6-3999-E576-5458-2D2EB4229509} + + + + DynamicLibrary + MultiByte + serverplugin_empty + + + DynamicLibrary + MultiByte + serverplugin_empty + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30319.1 + .\Debug\win32\ + .\Debug\win32\ + ..\..\devtools\vstools;$(ExecutablePath);$(Path) + true + true + true + false + true + .\Release\win32\ + .\Release\win32\ + ..\..\devtools\vstools;$(ExecutablePath);$(Path) + true + true + false + false + true + + + + + + /MP + Disabled + ..\..\common;..\..\public;..\..\public\tier0;..\..\public\tier1;..\..\game\server;..\..\game\shared + _HAS_ITERATOR_DEBUGGING=0;WIN32;_WIN32;_DEBUG;DEBUG;_WINDOWS;_USRDLL;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;DLLNAME=serverplugin_empty;RAD_TELEMETRY_DISABLED;COMPILER_MSVC32;serverplugin_emptyONLY;_MBCS;VPCGAMECAPS=VALVE;PROJECTDIR=D:\dev\games\rel\hl2\src\utils\serverplugin_sample;_DLL_EXT=.dll;VPCGAME=valve + true + false + Default + MultiThreadedDebug + true + StreamingSIMDExtensions + Fast + true + true + NotUsing + $(IntDir)/ + $(IntDir)/ + $(IntDir)/ + false + false + Level4 + true + EditAndContinue + CompileAsCpp + $(IntDir)/ + Prompt + + + _DEBUG;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE + 1033 + + + + + /ignore:4221 + %(AdditionalDependencies);odbc32.lib;odbccp32.lib + NotSet + $(OutDir)\serverplugin_empty.dll + true + ..\..\lib\common;..\..\lib\public + libc;libcd;libcmt + true + $(IntDir)/$(TargetName).pdb + false + $(IntDir)/$(TargetName).map + Windows + + MachineX86 + PromptImmediately + false + false + + + true + + + true + + + true + $(OutDir)/serverplugin_empty.bsc + + + Publishing to ..\..\..\game\bin + if not exist "..\..\..\game\bin" mkdir "..\..\..\game\bin" copy "$(TargetDir)"$(TargetFileName) "..\..\..\game\bin\$(TargetFileName)" if ERRORLEVEL 1 goto BuildEventFailed if exist "$(TargetDir)"$(TargetName).map copy "$(TargetDir)"$(TargetName).map ..\..\..\game\bin\$(TargetName).map copy "$(TargetDir)"$(TargetName).pdb ..\..\..\game\bin\$(TargetName).pdb if ERRORLEVEL 1 goto BuildEventFailed goto BuildEventOK :BuildEventFailed echo *** ERROR! PostBuildStep FAILED for $(ProjectName)! EXE or DLL is probably running. *** del /q "$(TargetDir)"$(TargetFileName) exit 1 :BuildEventOK + + + + + + + + + /MP /d2Zi+ + MaxSpeed + AnySuitable + true + Speed + ..\..\common;..\..\public;..\..\public\tier0;..\..\public\tier1;..\..\game\server;..\..\game\shared + WIN32;_WIN32;NDEBUG;_WINDOWS;_USRDLL;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;DLLNAME=serverplugin_empty;RAD_TELEMETRY_DISABLED;COMPILER_MSVC32;serverplugin_emptyONLY;_MBCS;VPCGAMECAPS=VALVE;PROJECTDIR=D:\dev\games\rel\hl2\src\utils\serverplugin_sample;_DLL_EXT=.dll;VPCGAME=valve + true + false + MultiThreaded + false + true + StreamingSIMDExtensions + Fast + true + true + NotUsing + $(IntDir)/ + $(IntDir)/ + $(IntDir)/ + false + false + Level4 + true + ProgramDatabase + CompileAsCpp + $(IntDir)/ + Prompt + + + NDEBUG;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE + 1033 + + + + + /DYNAMICBASE /ignore:4221 + %(AdditionalDependencies);odbc32.lib;odbccp32.lib + NotSet + $(OutDir)\serverplugin_empty.dll + true + ..\..\lib\common;..\..\lib\public + libc;libcd;libcmtd + true + $(IntDir)/$(TargetName).pdb + false + $(IntDir)/$(TargetName).map + Windows + true + true + + MachineX86 + PromptImmediately + false + + + true + + + true + + + true + $(OutDir)/serverplugin_empty.bsc + + + Publishing to ..\..\..\game\bin + if not exist "..\..\..\game\bin" mkdir "..\..\..\game\bin" copy "$(TargetDir)"$(TargetFileName) "..\..\..\game\bin\$(TargetFileName)" if ERRORLEVEL 1 goto BuildEventFailed if exist "$(TargetDir)"$(TargetName).map copy "$(TargetDir)"$(TargetName).map ..\..\..\game\bin\$(TargetName).map copy "$(TargetDir)"$(TargetName).pdb ..\..\..\game\bin\$(TargetName).pdb if ERRORLEVEL 1 goto BuildEventFailed goto BuildEventOK :BuildEventFailed echo *** ERROR! PostBuildStep FAILED for $(ProjectName)! EXE or DLL is probably running. *** del /q "$(TargetDir)"$(TargetFileName) exit 1 :BuildEventOK + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + NotUsing + NotUsing + + + + + + + + + Compiling pointeroverride.asm + "$(VCInstallDir)bin\ml.exe" /c /Cp /Zi /Fo"$(IntDir)\%(Filename).obj" "%(FullPath)" + $(IntDir)\%(Filename).obj + Compiling pointeroverride.asm + "$(VCInstallDir)bin\ml.exe" /c /Cp /Zi /Fo"$(IntDir)\%(Filename).obj" "%(FullPath)" + $(IntDir)\%(Filename).obj + + + + + + + + diff --git a/mp/src/utils/serverplugin_sample/serverplugin_empty-2010.vcxproj.filters b/mp/src/utils/serverplugin_sample/serverplugin_empty-2010.vcxproj.filters new file mode 100644 index 00000000..7f979a47 --- /dev/null +++ b/mp/src/utils/serverplugin_sample/serverplugin_empty-2010.vcxproj.filters @@ -0,0 +1,110 @@ + + + + + {1680C80B-FF1E-EA4D-9817-CC12254F2E40} + + + {C5D73B3A-C648-896C-B7CE-F174808E5BA5} + + + {BA03E055-4FA2-FCE3-8A1C-D348547D379C} + + + + + Link Libraries + + + Link Libraries + + + Link Libraries + + + Link Libraries + + + Link Libraries + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + Source Files + + + + + + + Source Files + + + + + diff --git a/mp/src/utils/serverplugin_sample/serverplugin_empty.cpp b/mp/src/utils/serverplugin_sample/serverplugin_empty.cpp new file mode 100644 index 00000000..ff82b5f6 --- /dev/null +++ b/mp/src/utils/serverplugin_sample/serverplugin_empty.cpp @@ -0,0 +1,922 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//===========================================================================// + +#include + +//#define GAME_DLL +#ifdef GAME_DLL +#include "cbase.h" +#endif + +#include +#include "interface.h" +#include "filesystem.h" +#include "engine/iserverplugin.h" +#include "eiface.h" +#include "igameevents.h" +#include "convar.h" +#include "Color.h" +#include "vstdlib/random.h" +#include "engine/IEngineTrace.h" +#include "tier2/tier2.h" +#include "game/server/pluginvariant.h" +#include "game/server/iplayerinfo.h" +#include "game/server/ientityinfo.h" +#include "game/server/igameinfo.h" + +//#define SAMPLE_TF2_PLUGIN +#ifdef SAMPLE_TF2_PLUGIN +#include "tf/tf_shareddefs.h" +#endif +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +// Interfaces from the engine +IVEngineServer *engine = NULL; // helper functions (messaging clients, loading content, making entities, running commands, etc) +IGameEventManager *gameeventmanager_ = NULL; // game events interface +#ifndef GAME_DLL +#define gameeventmanager gameeventmanager_ +#endif +IPlayerInfoManager *playerinfomanager = NULL; // game dll interface to interact with players +IEntityInfoManager *entityinfomanager = NULL; // game dll interface to interact with all entities (like IPlayerInfo) +IGameInfoManager *gameinfomanager = NULL; // game dll interface to get data from game rules directly +IBotManager *botmanager = NULL; // game dll interface to interact with bots +IServerPluginHelpers *helpers = NULL; // special 3rd party plugin helpers from the engine +IUniformRandomStream *randomStr = NULL; +IEngineTrace *enginetrace = NULL; + + +CGlobalVars *gpGlobals = NULL; + +// function to initialize any cvars/command in this plugin +void Bot_RunAll( void ); + +// useful helper func +#ifndef GAME_DLL +inline bool FStrEq(const char *sz1, const char *sz2) +{ + return(Q_stricmp(sz1, sz2) == 0); +} +#endif +//--------------------------------------------------------------------------------- +// Purpose: a sample 3rd party plugin class +//--------------------------------------------------------------------------------- +class CEmptyServerPlugin: public IServerPluginCallbacks, public IGameEventListener +{ +public: + CEmptyServerPlugin(); + ~CEmptyServerPlugin(); + + // IServerPluginCallbacks methods + virtual bool Load( CreateInterfaceFn interfaceFactory, CreateInterfaceFn gameServerFactory ); + virtual void Unload( void ); + virtual void Pause( void ); + virtual void UnPause( void ); + virtual const char *GetPluginDescription( void ); + virtual void LevelInit( char const *pMapName ); + virtual void ServerActivate( edict_t *pEdictList, int edictCount, int clientMax ); + virtual void GameFrame( bool simulating ); + virtual void LevelShutdown( void ); + virtual void ClientActive( edict_t *pEntity ); + virtual void ClientDisconnect( edict_t *pEntity ); + virtual void ClientPutInServer( edict_t *pEntity, char const *playername ); + virtual void SetCommandClient( int index ); + virtual void ClientSettingsChanged( edict_t *pEdict ); + virtual PLUGIN_RESULT ClientConnect( bool *bAllowConnect, edict_t *pEntity, const char *pszName, const char *pszAddress, char *reject, int maxrejectlen ); + virtual PLUGIN_RESULT ClientCommand( edict_t *pEntity, const CCommand &args ); + virtual PLUGIN_RESULT NetworkIDValidated( const char *pszUserName, const char *pszNetworkID ); + virtual void OnQueryCvarValueFinished( QueryCvarCookie_t iCookie, edict_t *pPlayerEntity, EQueryCvarValueStatus eStatus, const char *pCvarName, const char *pCvarValue ); + virtual void OnEdictAllocated( edict_t *edict ); + virtual void OnEdictFreed( const edict_t *edict ); + + // IGameEventListener Interface + virtual void FireGameEvent( KeyValues * event ); + + virtual int GetCommandIndex() { return m_iClientCommandIndex; } +private: + int m_iClientCommandIndex; +}; + + +// +// The plugin is a static singleton that is exported as an interface +// +CEmptyServerPlugin g_EmtpyServerPlugin; +EXPOSE_SINGLE_INTERFACE_GLOBALVAR(CEmptyServerPlugin, IServerPluginCallbacks, INTERFACEVERSION_ISERVERPLUGINCALLBACKS, g_EmtpyServerPlugin ); + +//--------------------------------------------------------------------------------- +// Purpose: constructor/destructor +//--------------------------------------------------------------------------------- +CEmptyServerPlugin::CEmptyServerPlugin() +{ + m_iClientCommandIndex = 0; +} + +CEmptyServerPlugin::~CEmptyServerPlugin() +{ +} + +//--------------------------------------------------------------------------------- +// Purpose: called when the plugin is loaded, load the interface we need from the engine +//--------------------------------------------------------------------------------- +bool CEmptyServerPlugin::Load( CreateInterfaceFn interfaceFactory, CreateInterfaceFn gameServerFactory ) +{ + ConnectTier1Libraries( &interfaceFactory, 1 ); + ConnectTier2Libraries( &interfaceFactory, 1 ); + + entityinfomanager = (IEntityInfoManager *)gameServerFactory(INTERFACEVERSION_ENTITYINFOMANAGER,NULL); + if ( !entityinfomanager ) + { + Warning( "Unable to load entityinfomanager, ignoring\n" ); // this isn't fatal, we just won't be able to access entity data + } + + playerinfomanager = (IPlayerInfoManager *)gameServerFactory(INTERFACEVERSION_PLAYERINFOMANAGER,NULL); + if ( !playerinfomanager ) + { + Warning( "Unable to load playerinfomanager, ignoring\n" ); // this isn't fatal, we just won't be able to access specific player data + } + + botmanager = (IBotManager *)gameServerFactory(INTERFACEVERSION_PLAYERBOTMANAGER, NULL); + if ( !botmanager ) + { + Warning( "Unable to load botcontroller, ignoring\n" ); // this isn't fatal, we just won't be able to access specific bot functions + } + gameinfomanager = (IGameInfoManager *)gameServerFactory(INTERFACEVERSION_GAMEINFOMANAGER, NULL); + if (!gameinfomanager) + { + Warning( "Unable to load gameinfomanager, ignoring\n" ); + } + + engine = (IVEngineServer*)interfaceFactory(INTERFACEVERSION_VENGINESERVER, NULL); + gameeventmanager = (IGameEventManager *)interfaceFactory(INTERFACEVERSION_GAMEEVENTSMANAGER,NULL); + helpers = (IServerPluginHelpers*)interfaceFactory(INTERFACEVERSION_ISERVERPLUGINHELPERS, NULL); + enginetrace = (IEngineTrace *)interfaceFactory(INTERFACEVERSION_ENGINETRACE_SERVER,NULL); + randomStr = (IUniformRandomStream *)interfaceFactory(VENGINE_SERVER_RANDOM_INTERFACE_VERSION, NULL); + + // get the interfaces we want to use + if( ! ( engine && gameeventmanager && g_pFullFileSystem && helpers && enginetrace && randomStr ) ) + { + return false; // we require all these interface to function + } + + if ( playerinfomanager ) + { + gpGlobals = playerinfomanager->GetGlobalVars(); + } + + MathLib_Init( 2.2f, 2.2f, 0.0f, 2.0f ); + ConVar_Register( 0 ); + return true; +} + +//--------------------------------------------------------------------------------- +// Purpose: called when the plugin is unloaded (turned off) +//--------------------------------------------------------------------------------- +void CEmptyServerPlugin::Unload( void ) +{ + gameeventmanager->RemoveListener( this ); // make sure we are unloaded from the event system + + ConVar_Unregister( ); + DisconnectTier2Libraries( ); + DisconnectTier1Libraries( ); +} + +//--------------------------------------------------------------------------------- +// Purpose: called when the plugin is paused (i.e should stop running but isn't unloaded) +//--------------------------------------------------------------------------------- +void CEmptyServerPlugin::Pause( void ) +{ +} + +//--------------------------------------------------------------------------------- +// Purpose: called when the plugin is unpaused (i.e should start executing again) +//--------------------------------------------------------------------------------- +void CEmptyServerPlugin::UnPause( void ) +{ +} + +//--------------------------------------------------------------------------------- +// Purpose: the name of this plugin, returned in "plugin_print" command +//--------------------------------------------------------------------------------- +const char *CEmptyServerPlugin::GetPluginDescription( void ) +{ + return "Emtpy-Plugin V2, Valve"; +} + +//--------------------------------------------------------------------------------- +// Purpose: called on level start +//--------------------------------------------------------------------------------- +void CEmptyServerPlugin::LevelInit( char const *pMapName ) +{ + Msg( "Level \"%s\" has been loaded\n", pMapName ); + gameeventmanager->AddListener( this, true ); +} + +//--------------------------------------------------------------------------------- +// Purpose: called on level start, when the server is ready to accept client connections +// edictCount is the number of entities in the level, clientMax is the max client count +//--------------------------------------------------------------------------------- +void CEmptyServerPlugin::ServerActivate( edict_t *pEdictList, int edictCount, int clientMax ) +{ +} + +//--------------------------------------------------------------------------------- +// Purpose: called once per server frame, do recurring work here (like checking for timeouts) +//--------------------------------------------------------------------------------- +void CEmptyServerPlugin::GameFrame( bool simulating ) +{ + if ( simulating ) + { + Bot_RunAll(); + } +} + +//--------------------------------------------------------------------------------- +// Purpose: called on level end (as the server is shutting down or going to a new map) +//--------------------------------------------------------------------------------- +void CEmptyServerPlugin::LevelShutdown( void ) // !!!!this can get called multiple times per map change +{ + gameeventmanager->RemoveListener( this ); +} + +//--------------------------------------------------------------------------------- +// Purpose: called when a client spawns into a server (i.e as they begin to play) +//--------------------------------------------------------------------------------- +void CEmptyServerPlugin::ClientActive( edict_t *pEntity ) +{ +} + +//--------------------------------------------------------------------------------- +// Purpose: called when a client leaves a server (or is timed out) +//--------------------------------------------------------------------------------- +void CEmptyServerPlugin::ClientDisconnect( edict_t *pEntity ) +{ +} + +//--------------------------------------------------------------------------------- +// Purpose: called on +//--------------------------------------------------------------------------------- +void CEmptyServerPlugin::ClientPutInServer( edict_t *pEntity, char const *playername ) +{ + KeyValues *kv = new KeyValues( "msg" ); + kv->SetString( "title", "Hello" ); + kv->SetString( "msg", "Hello there" ); + kv->SetColor( "color", Color( 255, 0, 0, 255 )); + kv->SetInt( "level", 5); + kv->SetInt( "time", 10); + helpers->CreateMessage( pEntity, DIALOG_MSG, kv, this ); + kv->deleteThis(); +} + +//--------------------------------------------------------------------------------- +// Purpose: called on level start +//--------------------------------------------------------------------------------- +void CEmptyServerPlugin::SetCommandClient( int index ) +{ + m_iClientCommandIndex = index; +} + +void ClientPrint( edict_t *pEdict, char *format, ... ) +{ + va_list argptr; + static char string[1024]; + + va_start (argptr, format); + Q_vsnprintf(string, sizeof(string), format,argptr); + va_end (argptr); + + engine->ClientPrintf( pEdict, string ); +} +//--------------------------------------------------------------------------------- +// Purpose: called on level start +//--------------------------------------------------------------------------------- +void CEmptyServerPlugin::ClientSettingsChanged( edict_t *pEdict ) +{ + if ( playerinfomanager ) + { + IPlayerInfo *playerinfo = playerinfomanager->GetPlayerInfo( pEdict ); + + const char * name = engine->GetClientConVarValue( engine->IndexOfEdict(pEdict), "name" ); + + // CAN'T use Q_stricmp here, this dll is made by 3rd parties and may not link to tier0/vstdlib + if ( playerinfo && name && playerinfo->GetName() && + stricmp( name, playerinfo->GetName()) ) // playerinfo may be NULL if the MOD doesn't support access to player data + // OR if you are accessing the player before they are fully connected + { + ClientPrint( pEdict, "Your name changed to \"%s\" (from \"%s\"\n", name, playerinfo->GetName() ); + // this is the bad way to check this, the better option it to listen for the "player_changename" event in FireGameEvent() + // this is here to give a real example of how to use the playerinfo interface + } + } +} + +//--------------------------------------------------------------------------------- +// Purpose: called when a client joins a server +//--------------------------------------------------------------------------------- +PLUGIN_RESULT CEmptyServerPlugin::ClientConnect( bool *bAllowConnect, edict_t *pEntity, const char *pszName, const char *pszAddress, char *reject, int maxrejectlen ) +{ + return PLUGIN_CONTINUE; +} + +CON_COMMAND( DoAskConnect, "Server plugin example of using the ask connect dialog" ) +{ + if ( args.ArgC() < 2 ) + { + Warning ( "DoAskConnect \n" ); + } + else + { + const char *pServerIP = args.Arg( 1 ); + + KeyValues *kv = new KeyValues( "menu" ); + kv->SetString( "title", pServerIP ); // The IP address of the server to connect to goes in the "title" field. + kv->SetInt( "time", 3 ); + + for ( int i=1; i < gpGlobals->maxClients; i++ ) + { + edict_t *pEdict = engine->PEntityOfEntIndex( i ); + if ( pEdict ) + { + helpers->CreateMessage( pEdict, DIALOG_ASKCONNECT, kv, &g_EmtpyServerPlugin ); + } + } + + kv->deleteThis(); + } +} + +#ifdef SAMPLE_TF2_PLUGIN +const char *classNames[] = +{ + "unknown", + "scout", + "sniper", + "soldier", + "demoman", + "medic", + "heavy weapons guy", + "pyro", + "spy", + "engineer", +}; + +bool TFPlayerHasCondition( int inBits, int condition ) +{ + Assert( condition >= 0 && condition < TF_COND_LAST ); + + return ( ( inBits & (1<GetPlayerInfo( pEntity ); + if (!playerinfo) + { + Msg("couldn't get playerinfo\n"); + return; + } + + Msg("Sentry Status:\n"); + pluginvariant value; + pluginvariant emptyVariant; + edict_t *pSentry = NULL; + if (playerinfo->GetCustomInfo(TFPLAYERINFO_ENTINDEX_SENTRY, value, emptyVariant)) + { + pSentry = engine->PEntityOfEntIndex( value.Int() ); + if (!pSentry) + { + Warning("couldn't attain sentry gun entity\n"); + return; + } + } + else + { + Msg("No Sentrygun built.\n"); + return; + + } + IEntityInfo *entinfo = entityinfomanager->GetEntityInfo( pSentry ); + if (!entinfo) + { + Warning("couldn't get entinfo for sentry gun\n"); + return; + } + + if (playerinfo->GetCustomInfo(TFPLAYERINFO_BUILDING_SENTRY, value, emptyVariant)) + { + if (value.Bool()) + Msg("Sentry Under Construction...\n"); + } + if (playerinfo->GetCustomInfo(TFPLAYERINFO_UPGRADING_SENTRY, value, emptyVariant)) + { + if (value.Bool()) + Msg("Sentry Upgrading...\n"); + } + + int sentryLevel = 0; + if (playerinfo->GetCustomInfo(TFPLAYERINFO_SENTRY_LEVEL, value, emptyVariant)) + { + sentryLevel = value.Int(); + Msg("Sentry Level: %i\n", sentryLevel ); + } + else + Msg("Unable to retrive sentry level\n"); + + if (playerinfo->GetCustomInfo(TFPLAYERINFO_SENTRY_PROGRESS, value, emptyVariant)) + { + if (sentryLevel < 3) + { + int iMetal, iRequiredMetal; + iRequiredMetal = value.Int() & 0xFF; + iMetal = (value.Int()>>8) & 0xFF; + Msg("%i / %i Metal Required for Sentry Level %i\n", iMetal, iRequiredMetal, sentryLevel+1); + } + else + Msg("Sentry cannot be upgraded further.\n"); + } + + Msg("Health: %i\n", entinfo->GetHealth() ); + + if (playerinfo->GetCustomInfo(TFPLAYERINFO_SENTRY_KILLS, value, emptyVariant)) + Msg("Kills: %i\n", value.Int() ); + else + Msg("Unable to retrieve sentry kills\n"); + + if (playerinfo->GetCustomInfo(TFPLAYERINFO_SENTRY_AMMO_SHELLS, value, emptyVariant)) + { + int iShells, iMaxShells; + iMaxShells = value.Int() & 0xFF; + iShells = (value.Int()>>8) & 0xFF; + Msg("Shells: %i / %i\n", iShells, iMaxShells); + } + if (sentryLevel > 2) + { + if (playerinfo->GetCustomInfo(TFPLAYERINFO_SENTRY_AMMO_ROCKETS, value, emptyVariant)) + { + int iRockets, iMaxRockets; + iMaxRockets = value.Int() & 0xFF; + iRockets = (value.Int()>>8) & 0xFF; + Msg("Rockets: %i / %i\n", iRockets, iMaxRockets); + } + } + +} +void DispenserStatus( edict_t *pEntity ) +{ + IPlayerInfo *playerinfo = playerinfomanager->GetPlayerInfo( pEntity ); + if (!playerinfo) + { + Msg("couldn't get playerinfo\n"); + return; + } + + Msg("Dispenser Status:\n"); + pluginvariant value; + pluginvariant emptyVariant; + edict_t *pDispenser = NULL; + if (playerinfo->GetCustomInfo(TFPLAYERINFO_ENTINDEX_DISPENSER, value, emptyVariant)) + { + pDispenser = engine->PEntityOfEntIndex( value.Int() ); + if (!pDispenser) + { + Warning("couldn't attain dispenser entity\n"); + return; + } + } + else + { + Msg("No dispenser built.\n"); + return; + } + IEntityInfo *entinfo = entityinfomanager->GetEntityInfo( pDispenser ); + if (!entinfo) + { + Warning("couldn't get entinfo for dispenser\n"); + return; + } + if (playerinfo->GetCustomInfo(TFPLAYERINFO_BUILDING_DISPENSER, value, emptyVariant)) + { + if (value.Bool()) + Msg("Dispenser Under Construction...\n"); + } + Msg("Health: %i\n", entinfo->GetHealth() ); + if (playerinfo->GetCustomInfo(TFPLAYERINFO_DISPENSER_METAL, value, emptyVariant)) + Msg("Metal: %i\n", value.Int() ); +} +void TeleporterStatus( edict_t *pEntity ) +{ + IPlayerInfo *playerinfo = playerinfomanager->GetPlayerInfo( pEntity ); + if (!playerinfo) + { + Msg("couldn't get playerinfo\n"); + return; + } + + Msg("Teleporter Status:\n"); + + pluginvariant value; + pluginvariant emptyVariant; + edict_t *pEntrance = NULL; + edict_t *pExit = NULL; + if (playerinfo->GetCustomInfo(TFPLAYERINFO_ENTINDEX_TELEPORTER_ENTRANCE, value, emptyVariant)) + { + pEntrance = engine->PEntityOfEntIndex( value.Int() ); + if (!pEntrance) + { + Warning("couldn't attain entrance entity\n"); + } + } + else + { + Msg("No Teleporter Entrance built.\n"); + } + if (playerinfo->GetCustomInfo(TFPLAYERINFO_ENTINDEX_TELEPORTER_EXIT, value, emptyVariant)) + { + pExit = engine->PEntityOfEntIndex( value.Int() ); + if (!pExit) + { + Warning("couldn't attain exit entity\n"); + } + } + else + { + Msg("No Teleporter Entrance built.\n"); + } + IEntityInfo *entranceInfo = entityinfomanager->GetEntityInfo( pEntrance ); + if (!entranceInfo) + { + Warning("couldn't get entinfo for teleporter entrance\n"); + } + IEntityInfo *exitInfo = entityinfomanager->GetEntityInfo( pExit ); + if (!exitInfo) + { + Warning("couldn't get entinfo for teleporter exit\n"); + } + + if (pEntrance && entranceInfo) + { + if (playerinfo->GetCustomInfo(TFPLAYERINFO_BUILDING_TELEPORTER_ENTRANCE, value, emptyVariant)) + { + if (value.Bool()) + Msg("Entrance Under Construction...\n"); + } + Msg("Entrance Health: %i\n", entranceInfo->GetHealth() ); + if (playerinfo->GetCustomInfo(TFPLAYERINFO_TELEPORTER_USES, value, emptyVariant)) + Msg("Entrance Used %i Times.\n", value.Int() ); + + } + if (pExit && exitInfo) + { + if (playerinfo->GetCustomInfo(TFPLAYERINFO_BUILDING_TELEPORTER_EXIT, value, emptyVariant)) + { + if (value.Bool()) + Msg("Exit Under Construction...\n"); + } + Msg("Exit Health: %i\n", exitInfo->GetHealth() ); + } +} +void ClassStatus( edict_t *pEntity ) +{ + IPlayerInfo *playerinfo = playerinfomanager->GetPlayerInfo( pEntity ); + if (!playerinfo) + { + Msg("couldn't get playerinfo\n"); + return; + } + int playerClassId = playerinfo->GetPlayerClassId(); + + Msg("Player Class: %s\n", playerinfo->GetPlayerClassName()); + pluginvariant conditionValue; + pluginvariant emptyVariant; + if (!playerinfo->GetCustomInfo(TFPLAYERINFO_CONDITIONS, conditionValue, emptyVariant)) + { + Warning("unable to retrieve conditions!\n"); + } + if (TFPlayerHasCondition(conditionValue.Int(), TF_COND_INVULNERABLE )) + Msg("You are Invulnerable!\n"); + if (TFPlayerHasCondition(conditionValue.Int(), TF_COND_SELECTED_TO_TELEPORT )) + Msg("You are about to Teleport.\n"); + if (TFPlayerHasCondition(conditionValue.Int(), TF_COND_TELEPORTED )) + Msg("You have recently been teleported.\n"); + + switch(playerClassId) + { + default: + case TF_CLASS_MEDIC: + break; + case TF_CLASS_ENGINEER: + Msg("Building Information:\n"); + SentryStatus( pEntity ); + DispenserStatus( pEntity ); + TeleporterStatus( pEntity ); + break; + case TF_CLASS_SPY: + { + int disguiseClass = 0; + pluginvariant value; + + if (playerinfo->GetCustomInfo(TFPLAYERINFO_SPY_DISGUISEDAS, value, emptyVariant)) + disguiseClass = value.Int(); + + if ( TFPlayerHasCondition(conditionValue.Int(), TF_COND_DISGUISING ) ) + Msg("Disguising..\n"); + else if (TFPlayerHasCondition(conditionValue.Int(), TF_COND_DISGUISED ) ) + Msg("Disguised as: %s\n", classNames[disguiseClass] ); + + if (TFPlayerHasCondition(conditionValue.Int(), TF_COND_STEALTHED )) + Msg("Cloaked!\n"); + if (playerinfo->GetCustomInfo(TFPLAYERINFO_SPY_CLOAKCHARGELEVEL, value, emptyVariant)) + Msg("Cloak Charge Percent: %d\n", value.Float() ); + + break; + } + case TF_CLASS_DEMOMAN: + break; + } +} +const char *ctf_flagtype[] = +{ + "ctf", //TF_FLAGTYPE_CTF = 0, + "attack / defend", //TF_FLAGTYPE_ATTACK_DEFEND, + "territory control", //TF_FLAGTYPE_TERRITORY_CONTROL, + "invade", //TF_FLAGTYPE_INVADE, + "king of the hill", //TF_FLAGTYPE_KINGOFTHEHILL, +}; +const char *ctf_flagstatus[] = +{ + "unknown", + "At Home", + "Dropped", + "Stolen", +}; +void FlagStatus( edict_t *pPlayer ) +{ + IPlayerInfo *pInfo = playerinfomanager->GetPlayerInfo( pPlayer ); + if (!pInfo) + { + Msg( "couldn't get playerinfo\n" ); + return; + } + IGameInfo *gameInfo = gameinfomanager->GetGameInfo(); + if (!gameInfo) + { + Msg( "couldn't get gameinfo\n" ); + } + + int gameType = gameInfo->GetInfo_GameType(); + + if (gameType != 1) + { + Msg( "Game is not CTF.\n" ); + return; + } + Msg( "===============================\n" ); + Msg( "Capture The Flag -- Flag Status\n" ); + Msg( "===============================\n" ); + pluginvariant value, options; + + edict_t *pFlag = NULL; + while ( (pFlag = entityinfomanager->FindEntityByClassname(pFlag, "item_teamflag")) != NULL ) + { + IEntityInfo *pFlagInfo = entityinfomanager->GetEntityInfo( pFlag ); + if (!pFlagInfo) + continue; + + Msg( "\nTeam %s's Flag\n", gameInfo->GetInfo_GetTeamName( pFlagInfo->GetTeamIndex() ) ); + options.SetInt(engine->IndexOfEdict(pFlag)); + if ( gameInfo->GetInfo_Custom( TFGAMEINFO_CTF_FLAG_TYPE, value, options) ) + Msg( "Type: %s\n", ctf_flagtype[value.Int()] ); + if ( gameInfo->GetInfo_Custom( TFGAMEINFO_CTF_FLAG_STATUS, value, options) ) + { + Msg( "Status: %s\n", ctf_flagstatus[value.Int()] ); + //Tony; if we're carried, find out who has us. + if (value.Int() == 3) + { + edict_t *pPlayer = pFlagInfo->GetOwner(); + if (pPlayer) + { + IPlayerInfo *pPlayerInfo = playerinfomanager->GetPlayerInfo( pPlayer ); + if (pPlayerInfo) + Msg( "Carried by: %s\n", pPlayerInfo->GetName() ); + } + } + } + } + + + Msg( "===============================\n" ); +} +#endif + +//--------------------------------------------------------------------------------- +// Purpose: called when a client types in a command (only a subset of commands however, not CON_COMMAND's) +//--------------------------------------------------------------------------------- +PLUGIN_RESULT CEmptyServerPlugin::ClientCommand( edict_t *pEntity, const CCommand &args ) +{ + const char *pcmd = args[0]; + + if ( !pEntity || pEntity->IsFree() ) + { + return PLUGIN_CONTINUE; + } + + if ( FStrEq( pcmd, "menu" ) ) + { + KeyValues *kv = new KeyValues( "menu" ); + kv->SetString( "title", "You've got options, hit ESC" ); + kv->SetInt( "level", 1 ); + kv->SetColor( "color", Color( 255, 0, 0, 255 )); + kv->SetInt( "time", 20 ); + kv->SetString( "msg", "Pick an option\nOr don't." ); + + for( int i = 1; i < 9; i++ ) + { + char num[10], msg[10], cmd[10]; + Q_snprintf( num, sizeof(num), "%i", i ); + Q_snprintf( msg, sizeof(msg), "Option %i", i ); + Q_snprintf( cmd, sizeof(cmd), "option%i", i ); + + KeyValues *item1 = kv->FindKey( num, true ); + item1->SetString( "msg", msg ); + item1->SetString( "command", cmd ); + } + + helpers->CreateMessage( pEntity, DIALOG_MENU, kv, this ); + kv->deleteThis(); + return PLUGIN_STOP; // we handled this function + } + else if ( FStrEq( pcmd, "rich" ) ) + { + KeyValues *kv = new KeyValues( "menu" ); + kv->SetString( "title", "A rich message" ); + kv->SetInt( "level", 1 ); + kv->SetInt( "time", 20 ); + kv->SetString( "msg", "This is a long long long text string.\n\nIt also has line breaks." ); + + helpers->CreateMessage( pEntity, DIALOG_TEXT, kv, this ); + kv->deleteThis(); + return PLUGIN_STOP; // we handled this function + } + else if ( FStrEq( pcmd, "msg" ) ) + { + KeyValues *kv = new KeyValues( "menu" ); + kv->SetString( "title", "Just a simple hello" ); + kv->SetInt( "level", 1 ); + kv->SetInt( "time", 20 ); + + helpers->CreateMessage( pEntity, DIALOG_MSG, kv, this ); + kv->deleteThis(); + return PLUGIN_STOP; // we handled this function + } + else if ( FStrEq( pcmd, "entry" ) ) + { + KeyValues *kv = new KeyValues( "entry" ); + kv->SetString( "title", "Stuff" ); + kv->SetString( "msg", "Enter something" ); + kv->SetString( "command", "say" ); // anything they enter into the dialog turns into a say command + kv->SetInt( "level", 1 ); + kv->SetInt( "time", 20 ); + + helpers->CreateMessage( pEntity, DIALOG_ENTRY, kv, this ); + kv->deleteThis(); + return PLUGIN_STOP; // we handled this function + } +#ifdef SAMPLE_TF2_PLUGIN + else if ( FStrEq( pcmd, "gameinfo" ) ) + { + IGameInfo *gameInfo = gameinfomanager->GetGameInfo(); + if (!gameInfo) + return PLUGIN_STOP; + + Msg("=== Game Information ===\n"); + Msg("Game Type: %i / %s\n", gameInfo->GetInfo_GameType(), gameInfo->GetInfo_GameTypeName() ); + int teamCount = gameInfo->GetInfo_GetTeamCount(); + Msg("Num Teams: %i\n", teamCount ); + + Msg("Player Counts:\n"); + for (int i = 0;iGetInfo_GetTeamName(i) ) + continue; + Msg("Team: %s, Players: %i\n", gameInfo->GetInfo_GetTeamName(i), gameInfo->GetInfo_NumPlayersOnTeam(i) ); + } + return PLUGIN_STOP; + + } + // Sample to use the new CustomInfo added to TF2 for plugins + else if ( FStrEq( pcmd, "tfcond" ) ) + { + IPlayerInfo *playerinfo = playerinfomanager->GetPlayerInfo( pEntity ); + if (!playerinfo) + return PLUGIN_STOP; + + pluginvariant conditionValue; + pluginvariant emptyVariant; + if (!playerinfo->GetCustomInfo(TFPLAYERINFO_CONDITIONS, conditionValue, emptyVariant)) + { + Msg("unable to retrieve conditions!\n"); + return PLUGIN_STOP; + } + + Msg("Disguising?: %s\n", TFPlayerHasCondition(conditionValue.Int(), TF_COND_DISGUISING ) ? "yes" : "no" ); + Msg("Disguised?: %s\n", TFPlayerHasCondition(conditionValue.Int(), TF_COND_DISGUISED ) ? "yes" : "no" ); + Msg("Stealthed?: %s\n", TFPlayerHasCondition(conditionValue.Int(), TF_COND_STEALTHED ) ? "yes" : "no" ); + Msg("Invulnerable?: %s\n", TFPlayerHasCondition(conditionValue.Int(), TF_COND_INVULNERABLE ) ? "yes" : "no" ); + Msg("Teleported Recently?: %s\n", TFPlayerHasCondition(conditionValue.Int(), TF_COND_TELEPORTED ) ? "yes" : "no" ); + Msg("Selected for Teleportation?: %s\n", TFPlayerHasCondition(conditionValue.Int(), TF_COND_SELECTED_TO_TELEPORT ) ? "yes" : "no" ); + Msg("On Fire?: %s\n", TFPlayerHasCondition(conditionValue.Int(), TF_COND_BURNING ) ? "yes" : "no" ); + + return PLUGIN_STOP; + } + else if ( FStrEq( pcmd, "sentry_status" ) ) + { + SentryStatus(pEntity); + return PLUGIN_STOP; + } + else if ( FStrEq( pcmd, "class_status" ) ) + { + ClassStatus(pEntity); + return PLUGIN_STOP; + } + else if ( FStrEq( pcmd, "flag_status" ) ) + { + FlagStatus(pEntity); + return PLUGIN_STOP; + } + #ifdef GAME_DLL + else if ( FStrEq( pcmd, "cbe_test" ) ) + { + IPlayerInfo *playerinfo = playerinfomanager->GetPlayerInfo( pEntity ); + if (!playerinfo) + return PLUGIN_STOP; + + CBaseEntity *pEnt = static_cast< CBaseEntity* >(entityinfomanager->GetEntity( pEntity )); + if (pEnt) + Msg("got a pointer to CBaseEntity..\n"); + Msg("attempting to print this entities modelname directly..\n"); + + Msg("ModelName: %s\n", STRING(pEnt->GetModelName()) ); + + return PLUGIN_STOP; + } + #endif +#endif + + + return PLUGIN_CONTINUE; +} + +//--------------------------------------------------------------------------------- +// Purpose: called when a client is authenticated +//--------------------------------------------------------------------------------- +PLUGIN_RESULT CEmptyServerPlugin::NetworkIDValidated( const char *pszUserName, const char *pszNetworkID ) +{ + return PLUGIN_CONTINUE; +} + +//--------------------------------------------------------------------------------- +// Purpose: called when a cvar value query is finished +//--------------------------------------------------------------------------------- +void CEmptyServerPlugin::OnQueryCvarValueFinished( QueryCvarCookie_t iCookie, edict_t *pPlayerEntity, EQueryCvarValueStatus eStatus, const char *pCvarName, const char *pCvarValue ) +{ + Msg( "Cvar query (cookie: %d, status: %d) - name: %s, value: %s\n", iCookie, eStatus, pCvarName, pCvarValue ); +} +void CEmptyServerPlugin::OnEdictAllocated( edict_t *edict ) +{ +} +void CEmptyServerPlugin::OnEdictFreed( const edict_t *edict ) +{ +} + +//--------------------------------------------------------------------------------- +// Purpose: called when an event is fired +//--------------------------------------------------------------------------------- +void CEmptyServerPlugin::FireGameEvent( KeyValues * event ) +{ + const char * name = event->GetName(); + Msg( "CEmptyServerPlugin::FireGameEvent: Got event \"%s\"\n", name ); +} + +//--------------------------------------------------------------------------------- +// Purpose: an example of how to implement a new command +//--------------------------------------------------------------------------------- +CON_COMMAND( empty_version, "prints the version of the empty plugin" ) +{ + Msg( "Version:2.0.0.0\n" ); +} + +CON_COMMAND( empty_log, "logs the version of the empty plugin" ) +{ + engine->LogPrint( "Version:2.0.0.0\n" ); +} + +//--------------------------------------------------------------------------------- +// Purpose: an example cvar +//--------------------------------------------------------------------------------- +static ConVar empty_cvar("plugin_empty", "0", FCVAR_NOTIFY, "Example plugin cvar"); diff --git a/mp/src/utils/smdlexp/smdlexp.cpp b/mp/src/utils/smdlexp/smdlexp.cpp new file mode 100644 index 00000000..fafb5da6 --- /dev/null +++ b/mp/src/utils/smdlexp/smdlexp.cpp @@ -0,0 +1,1096 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +#include "MAX.H" +#include "DECOMP.H" +#include "STDMAT.H" +#include "ANIMTBL.H" +#include "istdplug.h" +#include "phyexp.h" +#include "BonesPro.h" + +#include "smexprc.h" +#include "smedefs.h" + +//=================================================================== +// Prototype declarations +// +int GetIndexOfINode(INode *pnode,BOOL fAssertPropExists = TRUE); +void SetIndexOfINode(INode *pnode, int inode); +BOOL FUndesirableNode(INode *pnode); +BOOL FNodeMarkedToSkip(INode *pnode); +float FlReduceRotation(float fl); + + +//=================================================================== +// Global variable definitions +// + +// Save for use with dialogs +static HINSTANCE hInstance; + +// We just need one of these to hand off to 3DSMAX. +static SmdExportClassDesc SmdExportCD; + +// For OutputDebugString and misc sprintf's +static char st_szDBG[300]; + +// INode mapping table +static int g_inmMac = 0; + +//=================================================================== +// Utility functions +// + +static int AssertFailedFunc(char *sz) +{ + MessageBox(GetActiveWindow(), sz, "Assert failure", MB_OK); + int Set_Your_Breakpoint_Here = 1; + return 1; +} +#define ASSERT_MBOX(f, sz) ((f) ? 1 : AssertFailedFunc(sz)) + + +//=================================================================== +// Required plug-in export functions +// +BOOL WINAPI DllMain( HINSTANCE hinstDLL, ULONG fdwReason, LPVOID lpvReserved) +{ + static int fFirstTimeHere = TRUE; + if (fFirstTimeHere) + { + fFirstTimeHere = FALSE; + hInstance = hinstDLL; + } + return TRUE; +} + + +EXPORT_THIS int LibNumberClasses(void) +{ + return 1; +} + + +EXPORT_THIS ClassDesc *LibClassDesc(int iWhichClass) +{ + switch(iWhichClass) + { + case 0: return &SmdExportCD; + default: return 0; + } +} + + +EXPORT_THIS const TCHAR *LibDescription() +{ + return _T("Valve SMD Plug-in."); +} + + +EXPORT_THIS ULONG LibVersion() +{ + return VERSION_3DSMAX; +} + + +//===================================================================== +// Methods for SmdExportClass +// + +CONSTRUCTOR SmdExportClass::SmdExportClass(void) +{ + m_rgmaxnode = NULL; +} + + +DESTRUCTOR SmdExportClass::~SmdExportClass(void) +{ + if (m_rgmaxnode) + delete[] m_rgmaxnode; +} + + +int SmdExportClass::DoExport(const TCHAR *name,ExpInterface *ei,Interface *i, BOOL suppressPrompts, DWORD options) +{ + ExpInterface *pexpiface = ei; // Hungarian + Interface *piface = i; // Hungarian + + // Reset the name-map property manager + g_inmMac = 0; + + if (!suppressPrompts) + { + // Present the user with the Export Options dialog + if (DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_EXPORTOPTIONS), GetActiveWindow(), + ExportOptionsDlgProc, (LPARAM)this) <= 0) + return 0; // error or cancel + } + else + { + m_fReferenceFrame = 0; + } + + // Break up filename, re-assemble longer versions + TSTR strPath, strFile, strExt; + TCHAR szFile[MAX_PATH]; + SplitFilename(TSTR(name), &strPath, &strFile, &strExt); + sprintf(szFile, "%s\\%s.%s", (char*)strPath, (char*)strFile, DEFAULT_EXT); + + /* + if (m_fReferenceFrame) + sprintf(szFile, "%s\\%s_model.%s", (char*)strPath, (char*)strFile, DEFAULT_EXT); + */ + + FILE *pFile; + if ((pFile = fopen(szFile, "w")) == NULL) + return FALSE/*failure*/; + + fprintf( pFile, "version %d\n", 1 ); + + // Get animation metrics + m_intervalOfAnimation = piface->GetAnimRange(); + m_tvStart = m_intervalOfAnimation.Start(); + m_tvEnd = m_intervalOfAnimation.End(); + m_tpf = ::GetTicksPerFrame(); + + // Count nodes, label them, collect into array + if (!CollectNodes(pexpiface)) + return 0; /*fail*/ + + // Output nodes + if (!DumpBones(pFile, pexpiface)) + { + fclose( pFile ); + return 0; /*fail*/ + } + + // Output bone rotations, for each frame. Do only first frame if this is the reference frame MAX file + DumpRotations(pFile, pexpiface); + + // Output triangle meshes (first frame/all frames), if this is the reference frame MAX file + if (m_fReferenceFrame) + { + DumpModel(pFile, pexpiface); + } + + if (!suppressPrompts) + { + // Tell user that exporting is finished (it can take a while with no feedback) + char szExportComplete[300]; + sprintf(szExportComplete, "Exported %s.", szFile); + MessageBox(GetActiveWindow(), szExportComplete, "Status", MB_OK); + } + + fclose( pFile ); + + return 1/*success*/; +} + + +BOOL SmdExportClass::CollectNodes( ExpInterface *pexpiface) +{ + // Count total nodes in the model, so I can alloc array + // Also "brands" each node with node index, or with "skip me" marker. + CountNodesTEP procCountNodes; + procCountNodes.m_cNodes = 0; + (void) pexpiface->theScene->EnumTree(&procCountNodes); + ASSERT_MBOX(procCountNodes.m_cNodes > 0, "No nodes!"); + + // Alloc and fill array + m_imaxnodeMac = procCountNodes.m_cNodes; + m_rgmaxnode = new MaxNode[m_imaxnodeMac]; + ASSERT_MBOX(m_rgmaxnode != NULL, "new failed"); + + + CollectNodesTEP procCollectNodes; + procCollectNodes.m_phec = this; + (void) pexpiface->theScene->EnumTree(&procCollectNodes); + + return TRUE; +} + + +BOOL SmdExportClass::DumpBones(FILE *pFile, ExpInterface *pexpiface) +{ + // Dump bone names + DumpNodesTEP procDumpNodes; + procDumpNodes.m_pfile = pFile; + procDumpNodes.m_phec = this; + fprintf(pFile, "nodes\n" ); + (void) pexpiface->theScene->EnumTree(&procDumpNodes); + fprintf(pFile, "end\n" ); + + return TRUE; +} + + +BOOL SmdExportClass::DumpRotations(FILE *pFile, ExpInterface *pexpiface) +{ + // Dump bone-rotation info, for each frame + // Also dumps root-node translation info (the model's world-position at each frame) + DumpFrameRotationsTEP procDumpFrameRotations; + procDumpFrameRotations.m_pfile = pFile; + procDumpFrameRotations.m_phec = this; + + TimeValue m_tvTill = (m_fReferenceFrame) ? m_tvStart : m_tvEnd; + + fprintf(pFile, "skeleton\n" ); + for (TimeValue tv = m_tvStart; tv <= m_tvTill; tv += m_tpf) + { + fprintf(pFile, "time %d\n", tv / GetTicksPerFrame() ); + procDumpFrameRotations.m_tvToDump = tv; + (void) pexpiface->theScene->EnumTree(&procDumpFrameRotations); + } + fprintf(pFile, "end\n" ); + + return TRUE; +} + + +BOOL SmdExportClass::DumpModel( FILE *pFile, ExpInterface *pexpiface) +{ + // Dump mesh info: vertices, normals, UV texture map coords, bone assignments + DumpModelTEP procDumpModel; + procDumpModel.m_pfile = pFile; + procDumpModel.m_phec = this; + fprintf(pFile, "triangles\n" ); + procDumpModel.m_tvToDump = m_tvStart; + (void) pexpiface->theScene->EnumTree(&procDumpModel); + fprintf(pFile, "end\n" ); + return TRUE; +} + + + + +//============================================================================= +// TREE-ENUMERATION PROCEDURES +//============================================================================= + +#define ASSERT_AND_ABORT(f, sz) \ + if (!(f)) \ + { \ + ASSERT_MBOX(FALSE, sz); \ + cleanup( ); \ + return TREE_ABORT; \ + } + + +//================================================================= +// Methods for CountNodesTEP +// +int CountNodesTEP::callback( INode *node) +{ + INode *pnode = node; // Hungarian + + ASSERT_MBOX(!(pnode)->IsRootNode(), "Encountered a root node!"); + + if (::FUndesirableNode(pnode)) + { + // Mark as skippable + ::SetIndexOfINode(pnode, SmdExportClass::UNDESIRABLE_NODE_MARKER); + return TREE_CONTINUE; + } + + // Establish "node index"--just ascending ints + ::SetIndexOfINode(pnode, m_cNodes); + + m_cNodes++; + + return TREE_CONTINUE; +} + + +//================================================================= +// Methods for CollectNodesTEP +// +int CollectNodesTEP::callback(INode *node) +{ + INode *pnode = node; // Hungarian + + ASSERT_MBOX(!(pnode)->IsRootNode(), "Encountered a root node!"); + + if (::FNodeMarkedToSkip(pnode)) + return TREE_CONTINUE; + + // Get pre-stored "index" + int iNode = ::GetIndexOfINode(pnode); + ASSERT_MBOX(iNode >= 0 && iNode <= m_phec->m_imaxnodeMac-1, "Bogus iNode"); + + // Get name, store name in array + TSTR strNodeName(pnode->GetName()); + strcpy(m_phec->m_rgmaxnode[iNode].szNodeName, (char*)strNodeName); + + // Get Node's time-zero Transformation Matrices + m_phec->m_rgmaxnode[iNode].mat3NodeTM = pnode->GetNodeTM(0/*TimeValue*/); + m_phec->m_rgmaxnode[iNode].mat3ObjectTM = pnode->GetObjectTM(0/*TimeValue*/); + + // I'll calculate this later + m_phec->m_rgmaxnode[iNode].imaxnodeParent = SmdExportClass::UNDESIRABLE_NODE_MARKER; + + return TREE_CONTINUE; +} + + + + + + +//================================================================= +// Methods for DumpNodesTEP +// +int DumpNodesTEP::callback(INode *pnode) +{ + ASSERT_MBOX(!(pnode)->IsRootNode(), "Encountered a root node!"); + + if (::FNodeMarkedToSkip(pnode)) + return TREE_CONTINUE; + + // Get node's parent + INode *pnodeParent; + pnodeParent = pnode->GetParentNode(); + + // The model's root is a child of the real "scene root" + TSTR strNodeName(pnode->GetName()); + BOOL fNodeIsRoot = pnodeParent->IsRootNode( ); + + int iNode = ::GetIndexOfINode(pnode); + int iNodeParent = ::GetIndexOfINode(pnodeParent, !fNodeIsRoot/*fAssertPropExists*/); + + // Convenient time to cache this + m_phec->m_rgmaxnode[iNode].imaxnodeParent = fNodeIsRoot ? SmdExportClass::UNDESIRABLE_NODE_MARKER : iNodeParent; + + // Root node has no parent, thus no translation + if (fNodeIsRoot) + iNodeParent = -1; + + // check to see if the matrix isn't right handed + m_phec->m_rgmaxnode[iNode].isMirrored = DotProd( CrossProd( m_phec->m_rgmaxnode[iNode].mat3ObjectTM.GetRow(0).Normalize(), m_phec->m_rgmaxnode[iNode].mat3ObjectTM.GetRow(1).Normalize() ).Normalize(), m_phec->m_rgmaxnode[iNode].mat3ObjectTM.GetRow(2).Normalize() ) < 0; + + // Dump node description + fprintf(m_pfile, "%3d \"%s\" %3d\n", + iNode, + strNodeName, + iNodeParent ); + + return TREE_CONTINUE; +} + + + +//================================================================= +// Methods for DumpFrameRotationsTEP +// +int DumpFrameRotationsTEP::callback(INode *pnode) +{ + ASSERT_MBOX(!(pnode)->IsRootNode(), "Encountered a root node!"); + + if (::FNodeMarkedToSkip(pnode)) + return TREE_CONTINUE; + + int iNode = ::GetIndexOfINode(pnode); + + TSTR strNodeName(pnode->GetName()); + + // The model's root is a child of the real "scene root" + INode *pnodeParent = pnode->GetParentNode(); + BOOL fNodeIsRoot = pnodeParent->IsRootNode( ); + + // Get Node's "Local" Transformation Matrix + Matrix3 mat3NodeTM = pnode->GetNodeTM(m_tvToDump); + Matrix3 mat3ParentTM = pnodeParent->GetNodeTM(m_tvToDump); + mat3NodeTM.NoScale(); // Clear these out because they apparently + mat3ParentTM.NoScale(); // screw up the following calculation. + Matrix3 mat3NodeLocalTM = mat3NodeTM * Inverse(mat3ParentTM); + Point3 rowTrans = mat3NodeLocalTM.GetTrans(); + + // check to see if the parent bone was mirrored. If so, mirror invert this bones position + if (m_phec->m_rgmaxnode[iNode].imaxnodeParent >= 0 && m_phec->m_rgmaxnode[m_phec->m_rgmaxnode[iNode].imaxnodeParent].isMirrored) + { + rowTrans = rowTrans * -1.0f; + } + + // Get the rotation (via decomposition into "affine parts", then quaternion-to-Euler) + // Apparently the order of rotations returned by QuatToEuler() is X, then Y, then Z. + AffineParts affparts; + float rgflXYZRotations[3]; + + decomp_affine(mat3NodeLocalTM, &affparts); + QuatToEuler(affparts.q, rgflXYZRotations); + + float xRot = rgflXYZRotations[0]; // in radians + float yRot = rgflXYZRotations[1]; // in radians + float zRot = rgflXYZRotations[2]; // in radians + + // Get rotations in the -2pi...2pi range + xRot = ::FlReduceRotation(xRot); + yRot = ::FlReduceRotation(yRot); + zRot = ::FlReduceRotation(zRot); + + // Print rotations + fprintf(m_pfile, "%3d %f %f %f %f %f %f\n", + // Node:%-15s Rotation (x,y,z)\n", + iNode, rowTrans.x, rowTrans.y, rowTrans.z, xRot, yRot, zRot); + + return TREE_CONTINUE; +} + + + +//================================================================= +// Methods for DumpModelTEP +// +Modifier *FindPhysiqueModifier (INode *nodePtr) +{ + // Get object from node. Abort if no object. + Object *ObjectPtr = nodePtr->GetObjectRef(); + if (!ObjectPtr) return NULL; + + // Is derived object ? + if (ObjectPtr->SuperClassID() == GEN_DERIVOB_CLASS_ID) + { + // Yes -> Cast. + IDerivedObject *DerivedObjectPtr = static_cast(ObjectPtr); + + // Iterate over all entries of the modifier stack. + int ModStackIndex = 0; + while (ModStackIndex < DerivedObjectPtr->NumModifiers()) + { + // Get current modifier. + Modifier *ModifierPtr = DerivedObjectPtr->GetModifier(ModStackIndex); + + // Is this Physique ? + if (ModifierPtr->ClassID() == Class_ID( PHYSIQUE_CLASS_ID_A, PHYSIQUE_CLASS_ID_B) ) + { + // Yes -> Exit. + return ModifierPtr; + } + // Next modifier stack entry. + ModStackIndex++; + } + } + // Not found. + return NULL; +} + +Modifier *FindBonesProModifier (INode *nodePtr) +{ + // Get object from node. Abort if no object. + Object *ObjectPtr = nodePtr->GetObjectRef(); + if (!ObjectPtr) return NULL; + + // Is derived object ? + if (ObjectPtr->SuperClassID() == GEN_DERIVOB_CLASS_ID) + { + // Yes -> Cast. + IDerivedObject *DerivedObjectPtr = static_cast(ObjectPtr); + + // Iterate over all entries of the modifier stack. + int ModStackIndex = 0; + while (ModStackIndex < DerivedObjectPtr->NumModifiers()) + { + // Get current modifier. + Modifier *ModifierPtr = DerivedObjectPtr->GetModifier(ModStackIndex); + + // Is this Bones Pro OSM? + if (ModifierPtr->ClassID() == BP_CLASS_ID_OSM ) + { + // Yes -> Exit. + return ModifierPtr; + } + // Is this Bones Pro WSM? + if (ModifierPtr->ClassID() == BP_CLASS_ID_WSM ) + { + // Yes -> Exit. + return ModifierPtr; + } + // Next modifier stack entry. + ModStackIndex++; + } + } + // Not found. + return NULL; +} + +// #define DEBUG_MESH_DUMP + +//================================================================= +// Methods for DumpModelTEP +// +int DumpModelTEP::callback(INode *pnode) +{ + Object* pobj; + int fHasMat = TRUE; + + // clear physique export parameters + m_mcExport = NULL; + m_phyExport = NULL; + m_phyMod = NULL; + m_bonesProMod = NULL; + + ASSERT_MBOX(!(pnode)->IsRootNode(), "Encountered a root node!"); + + if (::FNodeMarkedToSkip(pnode)) + return TREE_CONTINUE; + + // Actually, if it's not selected, skip it! + //if (!pnode->Selected()) + // return TRUE; + + int iNode = ::GetIndexOfINode(pnode); + TSTR strNodeName(pnode->GetName()); + + // The Footsteps node apparently MUST have a dummy mesh attached! Ignore it explicitly. + if (FStrEq((char*)strNodeName, "Bip01 Footsteps")) + return TREE_CONTINUE; + + // Helper nodes don't have meshes + pobj = pnode->GetObjectRef(); + if (pobj->SuperClassID() == HELPER_CLASS_ID) + return TREE_CONTINUE; + + // The model's root is a child of the real "scene root" + INode *pnodeParent = pnode->GetParentNode(); + BOOL fNodeIsRoot = pnodeParent->IsRootNode( ); + + // Get node's material: should be a multi/sub (if it has a material at all) + Mtl *pmtlNode = pnode->GetMtl(); + if (pmtlNode == NULL) + { + return TREE_CONTINUE; + fHasMat = FALSE; + } + else if (!(pmtlNode->ClassID() == Class_ID(MULTI_CLASS_ID, 0) && pmtlNode->IsMultiMtl())) + { + // sprintf(st_szDBG, "ERROR--Material on node %s isn't a Multi/Sub-Object", (char*)strNodeName); + // ASSERT_AND_ABORT(FALSE, st_szDBG); + // fHasMat = FALSE; + } + + // Get Node's object, convert to a triangle-mesh object, so I can access the Faces + ObjectState os = pnode->EvalWorldState(m_tvToDump); + pobj = os.obj; + TriObject *ptriobj; + BOOL fConvertedToTriObject = + pobj->CanConvertToType(triObjectClassID) && + (ptriobj = (TriObject*)pobj->ConvertToType(m_tvToDump, triObjectClassID)) != NULL; + if (!fConvertedToTriObject) + return TREE_CONTINUE; + Mesh *pmesh = &ptriobj->mesh; + + // Shouldn't have gotten this far if it's a helper object + if (pobj->SuperClassID() == HELPER_CLASS_ID) + { + sprintf(st_szDBG, "ERROR--Helper node %s has an attached mesh, and it shouldn't.", (char*)strNodeName); + ASSERT_AND_ABORT(FALSE, st_szDBG); + } + + // Ensure that the vertex normals are up-to-date + pmesh->buildNormals(); + + // We want the vertex coordinates in World-space, not object-space + Matrix3 mat3ObjectTM = pnode->GetObjectTM(m_tvToDump); + + + // initialize physique export parameters + m_phyMod = FindPhysiqueModifier(pnode); + if (m_phyMod) + { + // Physique Modifier exists for given Node + m_phyExport = (IPhysiqueExport *)m_phyMod->GetInterface(I_PHYINTERFACE); + + if (m_phyExport) + { + // create a ModContext Export Interface for the specific node of the Physique Modifier + m_mcExport = (IPhyContextExport *)m_phyExport->GetContextInterface(pnode); + + if (m_mcExport) + { + // convert all vertices to Rigid + m_mcExport->ConvertToRigid(TRUE); + } + } + } + + // initialize bones pro export parameters + m_wa = NULL; + m_bonesProMod = FindBonesProModifier(pnode); + if (m_bonesProMod) + { + m_bonesProMod->SetProperty( BP_PROPID_GET_WEIGHTS, &m_wa ); + } + + // Dump the triangle face info + int cFaces = pmesh->getNumFaces(); + for (int iFace = 0; iFace < cFaces; iFace++) + { + Face* pface = &pmesh->faces[iFace]; + TVFace* ptvface = (pmesh->tvFace) ? &pmesh->tvFace[iFace] : NULL; + DWORD smGroupFace = pface->getSmGroup(); + + // Get face's 3 indexes into the Mesh's vertex array(s). + DWORD iVertex0 = pface->getVert(0); + DWORD iVertex1 = pface->getVert(1); + DWORD iVertex2 = pface->getVert(2); + ASSERT_AND_ABORT((int)iVertex0 < pmesh->getNumVerts(), "Bogus Vertex 0 index"); + ASSERT_AND_ABORT((int)iVertex1 < pmesh->getNumVerts(), "Bogus Vertex 1 index"); + ASSERT_AND_ABORT((int)iVertex2 < pmesh->getNumVerts(), "Bogus Vertex 2 index"); + + // Get the 3 Vertex's for this face + Point3 pt3Vertex0 = pmesh->getVert(iVertex0); + Point3 pt3Vertex1 = pmesh->getVert(iVertex1); + Point3 pt3Vertex2 = pmesh->getVert(iVertex2); + + // Get the 3 RVertex's for this face + // NOTE: I'm using getRVertPtr instead of getRVert to work around a 3DSMax bug + RVertex *prvertex0 = pmesh->getRVertPtr(iVertex0); + RVertex *prvertex1 = pmesh->getRVertPtr(iVertex1); + RVertex *prvertex2 = pmesh->getRVertPtr(iVertex2); + + // Find appropriate normals for each RVertex + // A vertex can be part of multiple faces, so the "smoothing group" + // is used to locate the normal for this face's use of the vertex. + Point3 pt3Vertex0Normal; + Point3 pt3Vertex1Normal; + Point3 pt3Vertex2Normal; + if (smGroupFace) + { + pt3Vertex0Normal = Pt3GetRVertexNormal(prvertex0, smGroupFace); + pt3Vertex1Normal = Pt3GetRVertexNormal(prvertex1, smGroupFace); + pt3Vertex2Normal = Pt3GetRVertexNormal(prvertex2, smGroupFace); + } + else + { + pt3Vertex0Normal = pmesh->getFaceNormal( iFace ); + pt3Vertex1Normal = pmesh->getFaceNormal( iFace ); + pt3Vertex2Normal = pmesh->getFaceNormal( iFace ); + } + ASSERT_AND_ABORT( Length( pt3Vertex0Normal ) <= 1.1, "bogus orig normal 0" ); + ASSERT_AND_ABORT( Length( pt3Vertex1Normal ) <= 1.1, "bogus orig normal 1" ); + ASSERT_AND_ABORT( Length( pt3Vertex2Normal ) <= 1.1, "bogus orig normal 2" ); + + // Get Face's sub-material from node's material, to get the bitmap name. + // And no, there isn't a simpler way to get the bitmap name, you have to + // dig down through all these levels. + TCHAR szBitmapName[256] = "null.bmp"; + if (fHasMat) + { + Texmap *ptexmap = NULL; + MtlID mtlidFace = pface->getMatID(); + if (pmtlNode->IsMultiMtl()) + { + if (mtlidFace >= pmtlNode->NumSubMtls()) + { + sprintf(st_szDBG, "ERROR--Bogus sub-material index %d in node %s; highest valid index is %d", + mtlidFace, (char*)strNodeName, pmtlNode->NumSubMtls()-1); + // ASSERT_AND_ABORT(FALSE, st_szDBG); + mtlidFace = 0; + } + Mtl *pmtlFace = pmtlNode->GetSubMtl(mtlidFace); + ASSERT_AND_ABORT(pmtlFace != NULL, "NULL Sub-material returned"); + + /* + if ((pmtlFace->ClassID() == Class_ID(MULTI_CLASS_ID, 0) && pmtlFace->IsMultiMtl())) + { + // it's a sub-sub material. Gads. + pmtlFace = pmtlFace->GetSubMtl(mtlidFace); + ASSERT_AND_ABORT(pmtlFace != NULL, "NULL Sub-material returned"); + } + */ + + if (!(pmtlFace->ClassID() == Class_ID(DMTL_CLASS_ID, 0))) + { + + sprintf(st_szDBG, + "ERROR--Sub-material with index %d (used in node %s) isn't a 'default/standard' material [%x].", + mtlidFace, (char*)strNodeName, pmtlFace->ClassID()); + ASSERT_AND_ABORT(FALSE, st_szDBG); + } + StdMat *pstdmtlFace = (StdMat*)pmtlFace; + ptexmap = pstdmtlFace->GetSubTexmap(ID_DI); + } + else + { + ptexmap = pmtlNode->GetActiveTexmap(); + } + + // ASSERT_AND_ABORT(ptexmap != NULL, "NULL diffuse texture") + if (ptexmap != NULL) + { + if (!(ptexmap->ClassID() == Class_ID(BMTEX_CLASS_ID, 0))) + { + sprintf(st_szDBG, + "ERROR--Sub-material with index %d (used in node %s) doesn't have a bitmap as its diffuse texture.", + mtlidFace, (char*)strNodeName); + ASSERT_AND_ABORT(FALSE, st_szDBG); + } + BitmapTex *pbmptex = (BitmapTex*)ptexmap; + strcpy(szBitmapName, pbmptex->GetMapName()); + TSTR strPath, strFile; + SplitPathFile(TSTR(szBitmapName), &strPath, &strFile); + strcpy(szBitmapName,strFile); + } + } + + UVVert UVvertex0( 0, 0, 0 ); + UVVert UVvertex1( 1, 0, 0 ); + UVVert UVvertex2( 0, 1, 0 ); + + // All faces must have textures assigned to them + if (ptvface && (pface->flags & HAS_TVERTS)) + { + // Get TVface's 3 indexes into the Mesh's TVertex array(s). + DWORD iTVertex0 = ptvface->getTVert(0); + DWORD iTVertex1 = ptvface->getTVert(1); + DWORD iTVertex2 = ptvface->getTVert(2); + ASSERT_AND_ABORT((int)iTVertex0 < pmesh->getNumTVerts(), "Bogus TVertex 0 index"); + ASSERT_AND_ABORT((int)iTVertex1 < pmesh->getNumTVerts(), "Bogus TVertex 1 index"); + ASSERT_AND_ABORT((int)iTVertex2 < pmesh->getNumTVerts(), "Bogus TVertex 2 index"); + + // Get the 3 TVertex's for this TVFace + // NOTE: I'm using getRVertPtr instead of getRVert to work around a 3DSMax bug + UVvertex0 = pmesh->getTVert(iTVertex0); + UVvertex1 = pmesh->getTVert(iTVertex1); + UVvertex2 = pmesh->getTVert(iTVertex2); + } + else + { + //sprintf(st_szDBG, "ERROR--Node %s has a textureless face. All faces must have an applied texture.", (char*)strNodeName); + //ASSERT_AND_ABORT(FALSE, st_szDBG); + } + + /* + const char *szExpectedExtension = ".bmp"; + if (stricmp(szBitmapName+strlen(szBitmapName)-strlen(szExpectedExtension), szExpectedExtension) != 0) + { + sprintf(st_szDBG, "Node %s uses %s, which is not a %s file", (char*)strNodeName, szBitmapName, szExpectedExtension); + ASSERT_AND_ABORT(FALSE, st_szDBG); + } + */ + + // Determine owning bones for the vertices. + int iNodeV0, iNodeV1, iNodeV2; + // Simple 3dsMax model: the vertices are owned by the object, and hence the node + iNodeV0 = iNode; + iNodeV1 = iNode; + iNodeV2 = iNode; + + // Rotate the face vertices out of object-space, and into world-space space + Point3 v0 = pt3Vertex0 * mat3ObjectTM; + Point3 v1 = pt3Vertex1 * mat3ObjectTM; + Point3 v2 = pt3Vertex2 * mat3ObjectTM; + + + Matrix3 mat3ObjectNTM = mat3ObjectTM; + mat3ObjectNTM.NoScale( ); + ASSERT_AND_ABORT( Length( pt3Vertex0Normal ) <= 1.1, "bogus pre normal 0" ); + pt3Vertex0Normal = VectorTransform(mat3ObjectNTM, pt3Vertex0Normal); + ASSERT_AND_ABORT( Length( pt3Vertex0Normal ) <= 1.1, "bogus post normal 0" ); + ASSERT_AND_ABORT( Length( pt3Vertex1Normal ) <= 1.1, "bogus pre normal 1" ); + pt3Vertex1Normal = VectorTransform(mat3ObjectNTM, pt3Vertex1Normal); + ASSERT_AND_ABORT( Length( pt3Vertex1Normal ) <= 1.1, "bogus post normal 1" ); + ASSERT_AND_ABORT( Length( pt3Vertex2Normal ) <= 1.1, "bogus pre normal 2" ); + pt3Vertex2Normal = VectorTransform(mat3ObjectNTM, pt3Vertex2Normal); + ASSERT_AND_ABORT( Length( pt3Vertex2Normal ) <= 1.1, "bogus post normal 2" ); + + // Finally dump the bitmap name and 3 lines of face info + fprintf(m_pfile, "%s\n", szBitmapName); + fprintf(m_pfile, "%3d %8.4f %8.4f %8.4f %8.4f %8.4f %8.4f %8.4f %8.4f", + iNodeV0, v0.x, v0.y, v0.z, + pt3Vertex0Normal.x, pt3Vertex0Normal.y, pt3Vertex0Normal.z, + UVvertex0.x, UVvertex0.y); + DumpWeights( iVertex0 ); + fprintf(m_pfile, "%3d %8.4f %8.4f %8.4f %8.4f %8.4f %8.4f %8.4f %8.4f", + iNodeV1, v1.x, v1.y, v1.z, + pt3Vertex1Normal.x, pt3Vertex1Normal.y, pt3Vertex1Normal.z, + UVvertex1.x, UVvertex1.y); + DumpWeights( iVertex1 ); + fprintf(m_pfile, "%3d %8.4f %8.4f %8.4f %8.4f %8.4f %8.4f %8.4f %8.4f", + iNodeV2, v2.x, v2.y, v2.z, + pt3Vertex2Normal.x, pt3Vertex2Normal.y, pt3Vertex2Normal.z, + UVvertex2.x, UVvertex2.y); + DumpWeights( iVertex2 ); + } + + cleanup( ); + return TREE_CONTINUE; +} + + +#define MAX_BLEND_WEIGHTS 8 + +static struct { + int iNode; + float flWeight; +} aWeights[MAX_BLEND_WEIGHTS+1]; + +int AddWeight( int iCount, int iNode, float flWeight ) +{ + if (flWeight < 0.001) + return iCount; + + for (int i = 0; i < iCount; i++) + { + if (aWeights[i].flWeight < flWeight) + { + for (int j = iCount; j > i; j--) + { + aWeights[j] = aWeights[j-1]; + } + break; + } + } + aWeights[i].iNode = iNode; + aWeights[i].flWeight = flWeight; + + iCount++; + if (iCount > MAX_BLEND_WEIGHTS) + iCount = MAX_BLEND_WEIGHTS; + + return iCount; +} + + +void DumpModelTEP::DumpWeights(int iVertex) +{ + if (m_mcExport) + { + IPhyVertexExport *vtxExport = m_mcExport->GetVertexInterface(iVertex); + + if (vtxExport) + { + if (vtxExport->GetVertexType() & BLENDED_TYPE) + { + IPhyBlendedRigidVertex *pBlend = ((IPhyBlendedRigidVertex *)vtxExport); + int iCount = 0; + + for (int i = 0; i < pBlend->GetNumberNodes(); i++) + { + iCount = AddWeight( iCount, GetIndexOfINode( pBlend->GetNode( i ) ), pBlend->GetWeight( i ) ); + } + + fprintf(m_pfile, " %2d ", iCount ); + for (i = 0; i < iCount; i++) + { + fprintf(m_pfile, " %2d %5.3f ", aWeights[i].iNode, aWeights[i].flWeight ); + } + } + else + { + INode *Bone = ((IPhyRigidVertex *)vtxExport)->GetNode(); + + fprintf(m_pfile, " 1 %2d 1.000", GetIndexOfINode(Bone) ); + } + m_mcExport->ReleaseVertexInterface(vtxExport); + } + } + else if (m_wa != NULL) + { + int iCount = 0; + + for ( int iBone = 0; iBone < m_wa->nb; iBone++) + { + if (m_wa->w[iVertex * m_wa->nb + iBone] > 0.0) + { + BonesPro_Bone bone; + bone.t = BP_TIME_ATTACHED; + bone.index = iBone; + m_bonesProMod->SetProperty( BP_PROPID_GET_BONE_STAT, &bone ); + if (bone.node != NULL) + { + iCount = AddWeight( iCount, GetIndexOfINode( bone.node ), m_wa->w[iVertex * m_wa->nb + iBone] ); + } + } + } + + fprintf(m_pfile, " %2d ", iCount ); + for (int i = 0; i < iCount; i++) + { + fprintf(m_pfile, " %2d %5.3f ", aWeights[i].iNode, aWeights[i].flWeight ); + } + } + + fprintf(m_pfile, "\n" ); + fflush( m_pfile ); +} + +void DumpModelTEP::cleanup(void) +{ + if (m_phyMod && m_phyExport) + { + if (m_mcExport) + { + m_phyExport->ReleaseContextInterface(m_mcExport); + m_mcExport = NULL; + } + m_phyMod->ReleaseInterface(I_PHYINTERFACE, m_phyExport); + m_phyExport = NULL; + m_phyMod = NULL; + } +} + + +Point3 DumpModelTEP::Pt3GetRVertexNormal(RVertex *prvertex, DWORD smGroupFace) +{ + // Lookup the appropriate vertex normal, based on smoothing group. + int cNormals = prvertex->rFlags & NORCT_MASK; + + ASSERT_MBOX((cNormals == 1 && prvertex->ern == NULL) || + (cNormals > 1 && prvertex->ern != NULL), "BOGUS RVERTEX"); + + if (cNormals == 1) + return prvertex->rn.getNormal(); + else + { + for (int irn = 0; irn < cNormals; irn++) + if (prvertex->ern[irn].getSmGroup() & smGroupFace) + break; + + if (irn >= cNormals) + { + irn = 0; + // ASSERT_MBOX(irn < cNormals, "unknown smoothing group\n"); + } + return prvertex->ern[irn].getNormal(); + } +} + + + + + +//=========================================================== +// Dialog proc for export options +// +static BOOL CALLBACK ExportOptionsDlgProc( + HWND hDlg, + UINT message, + WPARAM wParam, + LPARAM lParam) +{ + static SmdExportClass *pexp; + switch (message) + { + case WM_INITDIALOG: + pexp = (SmdExportClass*) lParam; + CheckRadioButton(hDlg, IDC_CHECK_SKELETAL, IDC_CHECK_REFFRAME, IDC_CHECK_SKELETAL); + SetFocus(GetDlgItem(hDlg,IDOK)); + return FALSE; + case WM_DESTROY: + return FALSE; + case WM_COMMAND: + switch (LOWORD(wParam)) + { + case IDOK: + pexp->m_fReferenceFrame = IsDlgButtonChecked(hDlg, IDC_CHECK_REFFRAME); + EndDialog(hDlg, 1); // 1 indicates "ok to export" + return TRUE; + case IDCANCEL: // 0 indicates "cancel export" + EndDialog(hDlg, 0); + return TRUE; + case IDC_CHECK_SKELETAL: + case IDC_CHECK_REFFRAME: + CheckRadioButton(hDlg, IDC_CHECK_SKELETAL, IDC_CHECK_REFFRAME, LOWORD(wParam)); + break; + } + } + return FALSE; +} + + + +//======================================================================== +// Utility functions for getting/setting the personal "node index" property. +// NOTE: I'm storing a string-property because I hit a 3DSMax bug in v1.2 when I +// NOTE: tried using an integer property. +// FURTHER NOTE: those properties seem to change randomly sometimes, so I'm +// implementing my own. + +typedef struct +{ + char szNodeName[SmdExportClass::MAX_NAME_CHARS]; + int iNode; +} NAMEMAP; +const int MAX_NAMEMAP = 512; +static NAMEMAP g_rgnm[MAX_NAMEMAP]; + +int GetIndexOfINode(INode *pnode, BOOL fAssertPropExists) +{ + TSTR strNodeName(pnode->GetName()); + for (int inm = 0; inm < g_inmMac; inm++) + { + if (FStrEq(g_rgnm[inm].szNodeName, (char*)strNodeName)) + { + return g_rgnm[inm].iNode; + } + } + + if (fAssertPropExists) + ASSERT_MBOX(FALSE, "No NODEINDEXSTR property"); + return -7777; +} + + +void SetIndexOfINode(INode *pnode, int inode) +{ + TSTR strNodeName(pnode->GetName()); + NAMEMAP *pnm; + for (int inm = 0; inm < g_inmMac; inm++) + if (FStrEq(g_rgnm[inm].szNodeName, (char*)strNodeName)) + break; + if (inm < g_inmMac) + pnm = &g_rgnm[inm]; + else + { + ASSERT_MBOX(g_inmMac < MAX_NAMEMAP, "NAMEMAP is full"); + pnm = &g_rgnm[g_inmMac++]; + strcpy(pnm->szNodeName, (char*)strNodeName); + } + pnm->iNode = inode; +} + + +//============================================================= +// Returns TRUE if a node should be ignored during tree traversal. +// +BOOL FUndesirableNode(INode *pnode) +{ + // Get Node's underlying object, and object class name + Object *pobj = pnode->GetObjectRef(); + + // Don't care about lights, dummies, and cameras + if (pobj->SuperClassID() == CAMERA_CLASS_ID) + return TRUE; + if (pobj->SuperClassID() == LIGHT_CLASS_ID) + return TRUE; + + return FALSE; +} + + +//============================================================= +// Returns TRUE if a node has been marked as skippable +// +BOOL FNodeMarkedToSkip(INode *pnode) +{ + return (::GetIndexOfINode(pnode) == SmdExportClass::UNDESIRABLE_NODE_MARKER); +} + + +//============================================================= +// Reduces a rotation to within the -2PI..2PI range. +// +static float FlReduceRotation(float fl) +{ + while (fl >= TWOPI) + fl -= TWOPI; + while (fl <= -TWOPI) + fl += TWOPI; + return fl; +} diff --git a/mp/src/utils/smdlexp/smdlexp.def b/mp/src/utils/smdlexp/smdlexp.def new file mode 100644 index 00000000..d06167f3 --- /dev/null +++ b/mp/src/utils/smdlexp/smdlexp.def @@ -0,0 +1,8 @@ +LIBRARY smdlexp +EXPORTS + LibDescription @1 + LibNumberClasses @2 + LibClassDesc @3 + LibVersion @4 +SECTIONS + .data READ WRITE diff --git a/mp/src/utils/smdlexp/smdlexp.mak b/mp/src/utils/smdlexp/smdlexp.mak new file mode 100644 index 00000000..f0f7ecc0 --- /dev/null +++ b/mp/src/utils/smdlexp/smdlexp.mak @@ -0,0 +1,325 @@ +# Microsoft Developer Studio Generated NMAKE File, Format Version 4.20 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102 + +!IF "$(CFG)" == "" +CFG=smdlexp - Win32 Debug +!MESSAGE No configuration specified. Defaulting to smdlexp - Win32 Debug. +!ENDIF + +!IF "$(CFG)" != "smdlexp - Win32 Release" && "$(CFG)" !=\ + "smdlexp - Win32 Debug" +!MESSAGE Invalid configuration "$(CFG)" specified. +!MESSAGE You can specify a configuration when running NMAKE on this makefile +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "smdlexp.mak" CFG="smdlexp - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "smdlexp - Win32 Release" (based on\ + "Win32 (x86) Dynamic-Link Library") +!MESSAGE "smdlexp - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE +!ERROR An invalid configuration is specified. +!ENDIF + +!IF "$(OS)" == "Windows_NT" +NULL= +!ELSE +NULL=nul +!ENDIF +################################################################################ +# Begin Project +# PROP Target_Last_Scanned "smdlexp - Win32 Debug" +RSC=rc.exe +MTL=mktyplib.exe +CPP=cl.exe + +!IF "$(CFG)" == "smdlexp - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Target_Dir "" +OUTDIR=.\Release +INTDIR=.\Release + +ALL : "..\..\..\3DSMAX\STDPLUGS\SMDLEXP.DLE" + +CLEAN : + -@erase "$(INTDIR)\smdlexp.obj" + -@erase "$(INTDIR)\smdlexp.res" + -@erase "$(OUTDIR)\SMDLEXP.exp" + -@erase "$(OUTDIR)\SMDLEXP.lib" + -@erase "..\..\..\3DSMAX\STDPLUGS\SMDLEXP.DLE" + +"$(OUTDIR)" : + if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)" + +# ADD BASE CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /YX /c +# ADD CPP /nologo /MT /W3 /GX /O2 /I "\3DSMAX2.5\MAXSDK\INCLUDE" /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /YX /c +CPP_PROJ=/nologo /MT /W3 /GX /O2 /I "\3DSMAX2.5\MAXSDK\INCLUDE" /D "WIN32" /D\ + "NDEBUG" /D "_WINDOWS" /Fp"$(INTDIR)/smdlexp.pch" /YX /Fo"$(INTDIR)/" /c +CPP_OBJS=.\Release/ +CPP_SBRS=.\. +# ADD BASE MTL /nologo /D "NDEBUG" /win32 +# ADD MTL /nologo /D "NDEBUG" /win32 +MTL_PROJ=/nologo /D "NDEBUG" /win32 +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +RSC_PROJ=/l 0x409 /fo"$(INTDIR)/smdlexp.res" /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +BSC32_FLAGS=/nologo /o"$(OUTDIR)/smdlexp.bsc" +BSC32_SBRS= \ + +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib /nologo /subsystem:windows /dll /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib COMCTL32.LIB /nologo /subsystem:windows /dll /machine:I386 /out:"\3DSMAX\STDPLUGS\SMDLEXP.DLE" +LINK32_FLAGS=kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib\ + advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib COMCTL32.LIB /nologo\ + /subsystem:windows /dll /incremental:no /pdb:"$(OUTDIR)/SMDLEXP.pdb"\ + /machine:I386 /def:".\smdlexp.def" /out:"\3DSMAX\STDPLUGS\SMDLEXP.DLE"\ + /implib:"$(OUTDIR)/SMDLEXP.lib" +DEF_FILE= \ + ".\smdlexp.def" +LINK32_OBJS= \ + "$(INTDIR)\smdlexp.obj" \ + "$(INTDIR)\smdlexp.res" \ + "..\..\..\quiver\src\utils\3dsmax\CORE.LIB" \ + "..\..\..\quiver\src\utils\3dsmax\GEOM.LIB" \ + "..\..\..\quiver\src\utils\3dsmax\MESH.LIB" \ + "..\..\..\quiver\src\utils\3dsmax\UTIL.LIB" + +"..\..\..\3DSMAX\STDPLUGS\SMDLEXP.DLE" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS) + $(LINK32) @<< + $(LINK32_FLAGS) $(LINK32_OBJS) +<< + +!ELSEIF "$(CFG)" == "smdlexp - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Target_Dir "" +OUTDIR=.\Debug +INTDIR=.\Debug + +ALL : "..\..\..\3DSMAX\STDPLUGS\SMDLEXP.DLE" "$(OUTDIR)\smdlexp.bsc" + +CLEAN : + -@erase "$(INTDIR)\smdlexp.obj" + -@erase "$(INTDIR)\smdlexp.res" + -@erase "$(INTDIR)\smdlexp.sbr" + -@erase "$(INTDIR)\vc40.idb" + -@erase "$(INTDIR)\vc40.pdb" + -@erase "$(OUTDIR)\smdlexp.bsc" + -@erase "$(OUTDIR)\SMDLEXP.exp" + -@erase "$(OUTDIR)\SMDLEXP.lib" + -@erase "$(OUTDIR)\SMDLEXP.pdb" + -@erase "..\..\..\3DSMAX\STDPLUGS\SMDLEXP.DLE" + -@erase "..\..\..\3DSMAX\STDPLUGS\SMDLEXP.ILK" + +"$(OUTDIR)" : + if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)" + +# ADD BASE CPP /nologo /MTd /W3 /Gm /GX /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /YX /c +# ADD CPP /nologo /MD /W3 /Gm /GX /Zi /Od /I "\3DSMAX2.5\MAXSDK\INCLUDE" /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FR /YX /c +CPP_PROJ=/nologo /MD /W3 /Gm /GX /Zi /Od /I "\3DSMAX2.5\MAXSDK\INCLUDE" /D\ + "WIN32" /D "_DEBUG" /D "_WINDOWS" /FR"$(INTDIR)/" /Fp"$(INTDIR)/smdlexp.pch"\ + /YX /Fo"$(INTDIR)/" /Fd"$(INTDIR)/" /c +CPP_OBJS=.\Debug/ +CPP_SBRS=.\Debug/ +# ADD BASE MTL /nologo /D "_DEBUG" /win32 +# ADD MTL /nologo /D "_DEBUG" /win32 +MTL_PROJ=/nologo /D "_DEBUG" /win32 +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +RSC_PROJ=/l 0x409 /fo"$(INTDIR)/smdlexp.res" /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +BSC32_FLAGS=/nologo /o"$(OUTDIR)/smdlexp.bsc" +BSC32_SBRS= \ + "$(INTDIR)\smdlexp.sbr" + +"$(OUTDIR)\smdlexp.bsc" : "$(OUTDIR)" $(BSC32_SBRS) + $(BSC32) @<< + $(BSC32_FLAGS) $(BSC32_SBRS) +<< + +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib /nologo /subsystem:windows /dll /debug /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib COMCTL32.LIB /nologo /subsystem:windows /dll /debug /machine:I386 /out:"\3DSMAX\STDPLUGS\SMDLEXP.DLE" +LINK32_FLAGS=kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib\ + advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib COMCTL32.LIB /nologo\ + /subsystem:windows /dll /incremental:yes /pdb:"$(OUTDIR)/SMDLEXP.pdb" /debug\ + /machine:I386 /def:".\smdlexp.def" /out:"\3DSMAX\STDPLUGS\SMDLEXP.DLE"\ + /implib:"$(OUTDIR)/SMDLEXP.lib" +DEF_FILE= \ + ".\smdlexp.def" +LINK32_OBJS= \ + "$(INTDIR)\smdlexp.obj" \ + "$(INTDIR)\smdlexp.res" \ + "..\..\..\quiver\src\utils\3dsmax\CORE.LIB" \ + "..\..\..\quiver\src\utils\3dsmax\GEOM.LIB" \ + "..\..\..\quiver\src\utils\3dsmax\MESH.LIB" \ + "..\..\..\quiver\src\utils\3dsmax\UTIL.LIB" + +"..\..\..\3DSMAX\STDPLUGS\SMDLEXP.DLE" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS) + $(LINK32) @<< + $(LINK32_FLAGS) $(LINK32_OBJS) +<< + +!ENDIF + +.c{$(CPP_OBJS)}.obj: + $(CPP) $(CPP_PROJ) $< + +.cpp{$(CPP_OBJS)}.obj: + $(CPP) $(CPP_PROJ) $< + +.cxx{$(CPP_OBJS)}.obj: + $(CPP) $(CPP_PROJ) $< + +.c{$(CPP_SBRS)}.sbr: + $(CPP) $(CPP_PROJ) $< + +.cpp{$(CPP_SBRS)}.sbr: + $(CPP) $(CPP_PROJ) $< + +.cxx{$(CPP_SBRS)}.sbr: + $(CPP) $(CPP_PROJ) $< + +################################################################################ +# Begin Target + +# Name "smdlexp - Win32 Release" +# Name "smdlexp - Win32 Debug" + +!IF "$(CFG)" == "smdlexp - Win32 Release" + +!ELSEIF "$(CFG)" == "smdlexp - Win32 Debug" + +!ENDIF + +################################################################################ +# Begin Source File + +SOURCE=.\smdlexp.cpp +DEP_CPP_SMDLE=\ + ".\smedefs.h"\ + +NODEP_CPP_SMDLE=\ + ".\ANIMTBL.H"\ + ".\DECOMP.H"\ + ".\istdplug.h"\ + ".\MAX.H"\ + ".\STDMAT.H"\ + + +!IF "$(CFG)" == "smdlexp - Win32 Release" + + +"$(INTDIR)\smdlexp.obj" : $(SOURCE) $(DEP_CPP_SMDLE) "$(INTDIR)" + + +!ELSEIF "$(CFG)" == "smdlexp - Win32 Debug" + + +"$(INTDIR)\smdlexp.obj" : $(SOURCE) $(DEP_CPP_SMDLE) "$(INTDIR)" + +"$(INTDIR)\smdlexp.sbr" : $(SOURCE) $(DEP_CPP_SMDLE) "$(INTDIR)" + + +!ENDIF + +# End Source File +################################################################################ +# Begin Source File + +SOURCE=.\smdlexp.def + +!IF "$(CFG)" == "smdlexp - Win32 Release" + +!ELSEIF "$(CFG)" == "smdlexp - Win32 Debug" + +!ENDIF + +# End Source File +################################################################################ +# Begin Source File + +SOURCE=.\smdlexp.rc + +"$(INTDIR)\smdlexp.res" : $(SOURCE) "$(INTDIR)" + $(RSC) $(RSC_PROJ) $(SOURCE) + + +# End Source File +################################################################################ +# Begin Source File + +SOURCE=\quiver\src\utils\3dsmax\UTIL.LIB + +!IF "$(CFG)" == "smdlexp - Win32 Release" + +!ELSEIF "$(CFG)" == "smdlexp - Win32 Debug" + +!ENDIF + +# End Source File +################################################################################ +# Begin Source File + +SOURCE=\quiver\src\utils\3dsmax\GEOM.LIB + +!IF "$(CFG)" == "smdlexp - Win32 Release" + +!ELSEIF "$(CFG)" == "smdlexp - Win32 Debug" + +!ENDIF + +# End Source File +################################################################################ +# Begin Source File + +SOURCE=\quiver\src\utils\3dsmax\MESH.LIB + +!IF "$(CFG)" == "smdlexp - Win32 Release" + +!ELSEIF "$(CFG)" == "smdlexp - Win32 Debug" + +!ENDIF + +# End Source File +################################################################################ +# Begin Source File + +SOURCE=\quiver\src\utils\3dsmax\CORE.LIB + +!IF "$(CFG)" == "smdlexp - Win32 Release" + +!ELSEIF "$(CFG)" == "smdlexp - Win32 Debug" + +!ENDIF + +# End Source File +# End Target +# End Project +################################################################################ diff --git a/mp/src/utils/smdlexp/smdlexp.rc b/mp/src/utils/smdlexp/smdlexp.rc new file mode 100644 index 00000000..4a20ba4f --- /dev/null +++ b/mp/src/utils/smdlexp/smdlexp.rc @@ -0,0 +1,147 @@ +//Microsoft Developer Studio generated resource script. +// +#include "smexprc.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "afxres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_EXPORTOPTIONS DIALOG DISCARDABLE 0, 0, 186, 42 +STYLE DS_MODALFRAME | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "SMD Exporter" +FONT 8, "MS Sans Serif" +BEGIN + DEFPUSHBUTTON "OK",IDOK,129,7,50,14 + PUSHBUTTON "Cancel",IDCANCEL,129,22,50,14 + CONTROL "Skeletal Animation",IDC_CHECK_SKELETAL,"Button", + BS_AUTORADIOBUTTON | WS_GROUP,15,10,74,10 + CONTROL "Reference Frame",IDC_CHECK_REFFRAME,"Button", + BS_AUTORADIOBUTTON | WS_GROUP,15,24,71,10 +END + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO DISCARDABLE +BEGIN + IDD_EXPORTOPTIONS, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 179 + TOPMARGIN, 7 + BOTTOMMARGIN, 36 + END +END +#endif // APSTUDIO_INVOKED + + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE DISCARDABLE +BEGIN + "smexprc.h\0" +END + +2 TEXTINCLUDE DISCARDABLE +BEGIN + "#include ""afxres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE DISCARDABLE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +#ifndef _MAC +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 2,0,0,1 + PRODUCTVERSION 2,0,0,1 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x40004L + FILETYPE 0x2L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "Comments", "\0" + VALUE "CompanyName", "Valve LLC\0" + VALUE "FileDescription", "SMD file exporter (3D Studio Max plugin)\0" + VALUE "FileVersion", "2, 0, 0, 1\0" + VALUE "InternalName", "SMDLEXP\0" + VALUE "LegalCopyright", "Copyright © 1998, Valve LLC\0" + VALUE "LegalTrademarks", "The following are registered trademarks of Autodesk, Inc.: 3D Studio MAX. The following are trademarks of Autodesk, Inc.: Kinetix, Kinetix(logo), BIPED, Physique, Character Studio, MAX DWG, DWG Unplugged, Heidi, FLI, FLC, DXF.\0" + VALUE "OriginalFilename", "SMDLEXP.DLE\0" + VALUE "PrivateBuild", "\0" + VALUE "ProductName", "Valve LLC SMDLEXP\0" + VALUE "ProductVersion", "2, 0, 0, 1\0" + VALUE "SpecialBuild", "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END + +#endif // !_MAC + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/mp/src/utils/smdlexp/smedefs.h b/mp/src/utils/smdlexp/smedefs.h new file mode 100644 index 00000000..7211e1e1 --- /dev/null +++ b/mp/src/utils/smdlexp/smedefs.h @@ -0,0 +1,178 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +//=================================================================== +// Useful macros +// +#define CONSTRUCTOR +#define DESTRUCTOR + +#define EXPORT_THIS __declspec(dllexport) + +#define DEFAULT_EXT _T("smd") + +#define FStrEq(sz1, sz2) (strcmp((sz1), (sz2)) == 0) + + +//=================================================================== +// Class that implements the scene-export. +// +class SmdExportClass : public SceneExport +{ + friend BOOL CALLBACK ExportOptionsDlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam); + friend class DumpModelTEP; + friend class DumpDeformsTEP; + +public: + CONSTRUCTOR SmdExportClass (void); + DESTRUCTOR ~SmdExportClass (void); + + // Required by classes derived from SceneExport + virtual int ExtCount (void) { return 1; } + virtual const TCHAR* Ext (int i) { return DEFAULT_EXT; } + virtual const TCHAR* LongDesc (void) { return _T("Valve Skeletal Model Exporter for 3D Studio Max"); } + virtual const TCHAR* ShortDesc (void) { return _T("Valve SMD"); } + virtual const TCHAR* AuthorName (void) { return _T("Valve, LLC"); } + virtual const TCHAR* CopyrightMessage(void) { return _T("Copyright (c) 1998, Valve LLC"); } + virtual const TCHAR* OtherMessage1 (void) { return _T(""); } + virtual const TCHAR* OtherMessage2 (void) { return _T(""); } + virtual unsigned int Version (void) { return 201; } + virtual void ShowAbout (HWND hWnd) { return; } + // virtual int DoExport (const TCHAR *name, ExpInterface *ei, Interface *i); + virtual int DoExport(const TCHAR *name,ExpInterface *ei,Interface *i, BOOL suppressPrompts=FALSE,DWORD options=0); // Export file + + // Integer constants for this class + enum + { + MAX_NAME_CHARS = 70, + UNDESIRABLE_NODE_MARKER = -7777 + }; + + // For keeping info about each (non-ignored) 3dsMax node in the tree + typedef struct + { + char szNodeName[MAX_NAME_CHARS]; // usefull for lookups + Matrix3 mat3NodeTM; // node's transformation matrix (at time zero) + Matrix3 mat3ObjectTM; // object-offset transformation matrix (at time zero) + int imaxnodeParent; // cached index of parent node + float xRotFirstFrame; // 1st frame's X rotation + float yRotFirstFrame; // 1st frame's Y rotation + float zRotFirstFrame; // 1st frame's Z rotation + bool isMirrored; + } MaxNode; + MaxNode *m_rgmaxnode; // array of nodes + long m_imaxnodeMac; // # of nodes + + // Animation metrics (gleaned from 3dsMax and cached for convenience) + Interval m_intervalOfAnimation; + TimeValue m_tvStart; + TimeValue m_tvEnd; + int m_tpf; // ticks-per-frame + +private: + BOOL CollectNodes (ExpInterface *expiface); + BOOL DumpBones (FILE *pFile, ExpInterface *pexpiface); + BOOL DumpRotations (FILE *pFile, ExpInterface *pexpiface); + BOOL DumpModel (FILE *pFile, ExpInterface *pexpiface); + BOOL DumpDeforms (FILE *pFile, ExpInterface *pexpiface); + + // Is this MAX file just the reference frame, or an animation? + // If TRUE, the "bones" and "mesh" files will be created. + // If FALSE, the "rots" file will be created. + BOOL m_fReferenceFrame; +}; + + +//=================================================================== +// Basically just a ClassFactory for communicating with 3DSMAX. +// +class SmdExportClassDesc : public ClassDesc +{ +public: + int IsPublic (void) { return TRUE; } + void * Create (BOOL loading=FALSE) { return new SmdExportClass; } + const TCHAR * ClassName (void) { return _T("SmdExport"); } + SClass_ID SuperClassID (void) { return SCENE_EXPORT_CLASS_ID; } + Class_ID ClassID (void) { return Class_ID(0x774a43fd, 0x794d2210); } + const TCHAR * Category (void) { return _T(""); } +}; + + +//=================================================================== +// Tree Enumeration Callback +// Just counts the nodes in the node tree +// +class CountNodesTEP : public ITreeEnumProc +{ +public: + virtual int callback(INode *node); + int m_cNodes; // running count of nodes +}; + + +//=================================================================== +// Tree Enumeration Callback +// Collects the nodes in the tree into the global array +// +class CollectNodesTEP : public ITreeEnumProc +{ +public: + virtual int callback(INode *node); + SmdExportClass *m_phec; +}; + + +//=================================================================== +// Tree Enumeration Callback +// Dumps the bone offsets to a file. +// +class DumpNodesTEP : public ITreeEnumProc +{ +public: + virtual int callback(INode *node); + FILE *m_pfile; // write to this file + SmdExportClass *m_phec; +}; + + +//=================================================================== +// Tree Enumeration Callback +// Dumps the per-frame bone rotations to a file. +// +class DumpFrameRotationsTEP : public ITreeEnumProc +{ +public: + virtual int callback(INode *node); + void cleanup(void); + FILE *m_pfile; // write to this file + TimeValue m_tvToDump; // dump snapshot at this frame time + SmdExportClass *m_phec; +}; + +//=================================================================== +// Tree Enumeration Callback +// Dumps the triangle meshes to a file. +// +class DumpModelTEP : public ITreeEnumProc +{ +public: + virtual int callback(INode *node); + void cleanup(void); + FILE *m_pfile; // write to this file + TimeValue m_tvToDump; // dump snapshot at this frame time + SmdExportClass *m_phec; + IPhyContextExport *m_mcExport; + IPhysiqueExport *m_phyExport; + Modifier *m_phyMod; + Modifier *m_bonesProMod; + BonesPro_WeightArray *m_wa; +private: + Point3 Pt3GetRVertexNormal(RVertex *prvertex, DWORD smGroupFace); + void DumpWeights( int iVertex ); +}; + diff --git a/mp/src/utils/smdlexp/smexprc.h b/mp/src/utils/smdlexp/smexprc.h new file mode 100644 index 00000000..d783fc67 --- /dev/null +++ b/mp/src/utils/smdlexp/smexprc.h @@ -0,0 +1,28 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +//{{NO_DEPENDENCIES}} +// Microsoft Developer Studio generated include file. +// Used by smdlexp.rc +// +#define IDD_SMDLEXP_UI 101 +#define IDD_EXPORTOPTIONS 101 +#define IDC_CHECK_SKELETAL 1000 +#define IDC_CHECK_DEFORM 1001 +#define IDC_CHECK_REFFRAME 1002 +#define IDC_CHECK_PHYSIQUE 1003 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1006 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/mp/src/utils/tgadiff/tgadiff-2010.vcxproj b/mp/src/utils/tgadiff/tgadiff-2010.vcxproj new file mode 100644 index 00000000..85a5c581 --- /dev/null +++ b/mp/src/utils/tgadiff/tgadiff-2010.vcxproj @@ -0,0 +1,243 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + Tgadiff + {C6A1B4E3-DFD8-CD7B-5CBF-D3267A96FF21} + + + + Application + MultiByte + tgadiff + + + Application + MultiByte + tgadiff + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30319.1 + .\Debug\win32\ + .\Debug\win32\ + ..\..\devtools\vstools;$(ExecutablePath);$(Path) + true + true + true + true + true + .\Release\win32\ + .\Release\win32\ + ..\..\devtools\vstools;$(ExecutablePath);$(Path) + true + true + false + true + true + + + + if EXIST ..\..\..\game\bin\$(TargetFileName) for /f "delims=" %%A in ('attrib "..\..\..\game\bin\$(TargetFileName)"') do set valveTmpIsReadOnly="%%A" set valveTmpIsReadOnlyLetter=%valveTmpIsReadOnly:~6,1% if "%valveTmpIsReadOnlyLetter%"=="R" del /q "$(TargetDir)"$(TargetFileName) if exist ..\..\devtools\bin\vpc.exe ..\..\devtools\bin\vpc.exe -crc2 tgadiff.vcxproj if ERRORLEVEL 1 exit 1 + + + /MP + Disabled + ..\..\common;..\..\public;..\..\public\tier0;..\..\public\tier1 + _HAS_ITERATOR_DEBUGGING=0;WIN32;_WIN32;_DEBUG;DEBUG;_WINDOWS;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;RAD_TELEMETRY_DISABLED;COMPILER_MSVC32;VPCGAMECAPS=VALVE;PROJECTDIR=D:\dev\games\rel\hl2\src\utils\tgadiff;_DLL_EXT=.dll;VPCGAME=valve + true + false + Default + MultiThreadedDebug + true + StreamingSIMDExtensions + Fast + true + true + true + false + NotUsing + false + NoListing + $(IntDir)/ + $(IntDir)/ + $(IntDir)/ + false + Level4 + true + EditAndContinue + CompileAsCpp + $(IntDir)/ + Prompt + + + _DEBUG;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE + 1033 + + + + + /NXCOMPAT /ignore:4221 + %(AdditionalDependencies) + NotSet + $(OutDir)\tgadiff.exe + true + libc;libcd;libcmt + true + $(IntDir)/$(TargetName).pdb + false + $(IntDir)/$(TargetName).map + Console + + MachineX86 + PromptImmediately + false + + + true + + + true + + + true + $(OutDir)/tgadiff.bsc + + + Publishing to ..\..\..\game\bin + if not exist "..\..\..\game\bin" mkdir "..\..\..\game\bin" copy "$(TargetDir)"$(TargetFileName) ..\..\..\game\bin\$(TargetFileName) if ERRORLEVEL 1 goto BuildEventFailed if exist "$(TargetDir)"$(TargetName).map copy "$(TargetDir)"$(TargetName).map ..\..\..\game\bin\$(TargetName).map copy "$(TargetDir)"$(TargetName).pdb ..\..\..\game\bin\$(TargetName).pdb if ERRORLEVEL 1 goto BuildEventFailed goto BuildEventOK :BuildEventFailed echo *** ERROR! PostBuildStep FAILED for $(ProjectName)! EXE or DLL is probably running. *** del /q "$(TargetDir)"$(TargetFileName) exit 1 :BuildEventOK + + + + + + + if EXIST ..\..\..\game\bin\$(TargetFileName) for /f "delims=" %%A in ('attrib "..\..\..\game\bin\$(TargetFileName)"') do set valveTmpIsReadOnly="%%A" set valveTmpIsReadOnlyLetter=%valveTmpIsReadOnly:~6,1% if "%valveTmpIsReadOnlyLetter%"=="R" del /q "$(TargetDir)"$(TargetFileName) if exist ..\..\devtools\bin\vpc.exe ..\..\devtools\bin\vpc.exe -crc2 tgadiff.vcxproj if ERRORLEVEL 1 exit 1 + + + /MP /d2Zi+ + MaxSpeed + AnySuitable + true + Speed + ..\..\common;..\..\public;..\..\public\tier0;..\..\public\tier1 + WIN32;_WIN32;NDEBUG;_WINDOWS;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;RAD_TELEMETRY_DISABLED;COMPILER_MSVC32;VPCGAMECAPS=VALVE;PROJECTDIR=D:\dev\games\rel\hl2\src\utils\tgadiff;_DLL_EXT=.dll;VPCGAME=valve + true + false + MultiThreaded + false + true + StreamingSIMDExtensions + Fast + true + true + true + false + NotUsing + false + NoListing + $(IntDir)/ + $(IntDir)/ + $(IntDir)/ + false + Level4 + true + ProgramDatabase + CompileAsCpp + $(IntDir)/ + Prompt + + + NDEBUG;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE + 1033 + + + + + /DYNAMICBASE /NXCOMPAT /ignore:4221 + %(AdditionalDependencies) + NotSet + $(OutDir)\tgadiff.exe + true + libc;libcd;libcmtd + true + $(IntDir)/$(TargetName).pdb + false + $(IntDir)/$(TargetName).map + Console + true + true + + MachineX86 + PromptImmediately + + + true + + + true + + + true + $(OutDir)/tgadiff.bsc + + + Publishing to ..\..\..\game\bin + if not exist "..\..\..\game\bin" mkdir "..\..\..\game\bin" copy "$(TargetDir)"$(TargetFileName) ..\..\..\game\bin\$(TargetFileName) if ERRORLEVEL 1 goto BuildEventFailed if exist "$(TargetDir)"$(TargetName).map copy "$(TargetDir)"$(TargetName).map ..\..\..\game\bin\$(TargetName).map copy "$(TargetDir)"$(TargetName).pdb ..\..\..\game\bin\$(TargetName).pdb if ERRORLEVEL 1 goto BuildEventFailed goto BuildEventOK :BuildEventFailed echo *** ERROR! PostBuildStep FAILED for $(ProjectName)! EXE or DLL is probably running. *** del /q "$(TargetDir)"$(TargetFileName) exit 1 :BuildEventOK + + + + + + + + + + + + + + + + + NotUsing + NotUsing + + + + + + + + Compiling pointeroverride.asm + "$(VCInstallDir)bin\ml.exe" /c /Cp /Zi /Fo"$(IntDir)\%(Filename).obj" "%(FullPath)" + $(IntDir)\%(Filename).obj + Compiling pointeroverride.asm + "$(VCInstallDir)bin\ml.exe" /c /Cp /Zi /Fo"$(IntDir)\%(Filename).obj" "%(FullPath)" + $(IntDir)\%(Filename).obj + + + + + + + + diff --git a/mp/src/utils/tgadiff/tgadiff-2010.vcxproj.filters b/mp/src/utils/tgadiff/tgadiff-2010.vcxproj.filters new file mode 100644 index 00000000..f55777eb --- /dev/null +++ b/mp/src/utils/tgadiff/tgadiff-2010.vcxproj.filters @@ -0,0 +1,50 @@ + + + + + {C5D73B3A-C648-896C-B7CE-F174808E5BA5} + + + {BA03E055-4FA2-FCE3-8A1C-D348547D379C} + + + + + Link Libraries + + + Link Libraries + + + Link Libraries + + + Link Libraries + + + Link Libraries + + + Link Libraries + + + + + + + Source Files + + + Source Files + + + + + + + Source Files + + + + + diff --git a/mp/src/utils/tgadiff/tgadiff.cpp b/mp/src/utils/tgadiff/tgadiff.cpp new file mode 100644 index 00000000..6dbc3e3a --- /dev/null +++ b/mp/src/utils/tgadiff/tgadiff.cpp @@ -0,0 +1,184 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//===========================================================================// +#include +#include +#include +#include "bitmap/tgaloader.h" +#include "bitmap/tgawriter.h" +#include "tier1/utlbuffer.h" +#include "tier2/tier2.h" +#include "mathlib/mathlib.h" +#include "filesystem.h" + +void Usage( void ) +{ + printf( "Usage: tgadiff src1.tga src2.tga diff.tga\n" ); + exit( -1 ); +} + +int main( int argc, char **argv ) +{ + if( argc != 4 ) + { + Usage(); + } + + MathLib_Init( 2.2f, 2.2f, 0.0f, 2.0f ); + InitDefaultFileSystem(); + + char pCurrentDirectory[MAX_PATH]; + if ( _getcwd( pCurrentDirectory, sizeof(pCurrentDirectory) ) == NULL ) + { + fprintf( stderr, "Unable to get the current directory\n" ); + return -1; + } + Q_FixSlashes( pCurrentDirectory ); + Q_StripTrailingSlash( pCurrentDirectory ); + + char pBuf[3][MAX_PATH]; + const char *pFileName[3]; + for ( int i = 0; i < 3; ++i ) + { + if ( !Q_IsAbsolutePath( argv[i+1] ) ) + { + Q_snprintf( pBuf[i], sizeof(pBuf[i]), "%s\\%s", pCurrentDirectory, argv[i+1] ); + pFileName[i] = pBuf[i]; + } + else + { + pFileName[i] = argv[i+1]; + } + } + + int width1, height1; + ImageFormat imageFormat1; + float gamma1; + + CUtlBuffer buf1; + if ( !g_pFullFileSystem->ReadFile( pFileName[0], NULL, buf1 ) ) + { + fprintf( stderr, "%s not found\n", pFileName[0] ); + return -1; + } + + if( !TGALoader::GetInfo( buf1, &width1, &height1, &imageFormat1, &gamma1 ) ) + { + printf( "error loading %s\n", pFileName[0] ); + exit( -1 ); + } + + int width2, height2; + ImageFormat imageFormat2; + float gamma2; + + CUtlBuffer buf2; + if ( !g_pFullFileSystem->ReadFile( pFileName[1], NULL, buf2 ) ) + { + fprintf( stderr, "%s not found\n", pFileName[1] ); + return -1; + } + + if( !TGALoader::GetInfo( buf2, &width2, &height2, &imageFormat2, &gamma2 ) ) + { + printf( "error loading %s\n", pFileName[1] ); + exit( -1 ); + } + + if( width1 != width2 || height1 != height2 ) + { + printf( "image dimensions different (%dx%d!=%dx%d): can't do diff for %s\n", + width1, height1, width2, height2, pFileName[2] ); + exit( -1 ); + } +#if 0 + // have to allow for different formats for now due to *.txt file screwup. + if( imageFormat1 != imageFormat2 ) + { + printf( "image format different (%s!=%s). . can't do diff for %s\n", + ImageLoader::GetName( imageFormat1 ), ImageLoader::GetName( imageFormat2 ), pFileName[2] ); + exit( -1 ); + } +#endif + if( gamma1 != gamma2 ) + { + printf( "image gamma different (%f!=%f). . can't do diff for %s\n", gamma1, gamma2, pFileName[2] ); + exit( -1 ); + } + + unsigned char *pImage1Tmp = new unsigned char[ImageLoader::GetMemRequired( width1, height1, 1, imageFormat1, false )]; + unsigned char *pImage2Tmp = new unsigned char[ImageLoader::GetMemRequired( width2, height2, 1, imageFormat2, false )]; + + buf1.SeekGet( CUtlBuffer::SEEK_HEAD, 0 ); + if( !TGALoader::Load( pImage1Tmp, buf1, width1, height1, imageFormat1, 2.2f, false ) ) + { + printf( "error loading %s\n", pFileName[0] ); + exit( -1 ); + } + + buf2.SeekGet( CUtlBuffer::SEEK_HEAD, 0 ); + if( !TGALoader::Load( pImage2Tmp, buf2, width2, height2, imageFormat2, 2.2f, false ) ) + { + printf( "error loading %s\n", pFileName[1] ); + exit( -1 ); + } + + unsigned char *pImage1 = new unsigned char[ImageLoader::GetMemRequired( width1, height1, 1, IMAGE_FORMAT_ABGR8888, false )]; + unsigned char *pImage2 = new unsigned char[ImageLoader::GetMemRequired( width2, height2, 1, IMAGE_FORMAT_ABGR8888, false )]; + unsigned char *pDiff = new unsigned char[ImageLoader::GetMemRequired( width2, height2, 1, IMAGE_FORMAT_ABGR8888, false )]; + ImageLoader::ConvertImageFormat( pImage1Tmp, imageFormat1, pImage1, IMAGE_FORMAT_ABGR8888, width1, height1, 0, 0 ); + ImageLoader::ConvertImageFormat( pImage2Tmp, imageFormat2, pImage2, IMAGE_FORMAT_ABGR8888, width2, height2, 0, 0 ); + + int sizeInBytes = ImageLoader::SizeInBytes( IMAGE_FORMAT_ABGR8888 ); + bool isDifferent = false; + for( int i = 0; i < width1 * height1 * sizeInBytes; i++ ) + { + int d; + d = pImage2[i] - pImage1[i]; + pDiff[i] = d > 0 ? d : -d; + if( d != 0 ) + { + isDifferent = true; + } + } + + if( !isDifferent ) + { + printf( "Files are the same %s %s : not generating %s\n", pFileName[0], pFileName[1], pFileName[2] ); + exit( -1 ); + } + else + { + printf( "Generating diff: %s!\n", pFileName[2] ); + } + + ImageFormat dstImageFormat; + // get rid of this until we get the formats matching +// if( sizeInBytes == 3 ) +// { +// dstImageFormat = IMAGE_FORMAT_RGB888; +// } +// else + { + dstImageFormat = IMAGE_FORMAT_RGBA8888; + } + + CUtlBuffer outBuffer; + if ( !TGAWriter::WriteToBuffer( pDiff, outBuffer, width1, height1, dstImageFormat, dstImageFormat ) ) + { + printf( "error writing %s to buffer\n", pFileName[2] ); + exit( -1 ); + } + + if ( !g_pFullFileSystem->WriteFile( pFileName[2], NULL, outBuffer ) ) + { + fprintf( stderr, "unable to write %s\n", pFileName[2] ); + return -1; + } + + return 0; +} diff --git a/mp/src/utils/vbsp/boundbox.cpp b/mp/src/utils/vbsp/boundbox.cpp new file mode 100644 index 00000000..d0366cfc --- /dev/null +++ b/mp/src/utils/vbsp/boundbox.cpp @@ -0,0 +1,285 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "vbsp.h" +#include "BoundBox.h" +//#include "hammer_mathlib.h" +//#include "MapDefs.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +float rint(float f) +{ + if (f > 0.0f) { + return (float) floor(f + 0.5f); + } else if (f < 0.0f) { + return (float) ceil(f - 0.5f); + } else + return 0.0f; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +BoundBox::BoundBox(void) +{ + ResetBounds(); +} + +BoundBox::BoundBox(const Vector &mins, const Vector &maxs) +{ + bmins = mins; + bmaxs = maxs; +} + +//----------------------------------------------------------------------------- +// Purpose: Sets the box to an uninitialized state, so that calls to UpdateBounds +// will properly set the mins and maxs. +//----------------------------------------------------------------------------- +void BoundBox::ResetBounds(void) +{ + bmins[0] = bmins[1] = bmins[2] = COORD_NOTINIT; + bmaxs[0] = bmaxs[1] = bmaxs[2] = -COORD_NOTINIT; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : pt - +//----------------------------------------------------------------------------- +void BoundBox::UpdateBounds(const Vector& pt) +{ + if(pt[0] < bmins[0]) + bmins[0] = pt[0]; + if(pt[1] < bmins[1]) + bmins[1] = pt[1]; + if(pt[2] < bmins[2]) + bmins[2] = pt[2]; + + if(pt[0] > bmaxs[0]) + bmaxs[0] = pt[0]; + if(pt[1] > bmaxs[1]) + bmaxs[1] = pt[1]; + if(pt[2] > bmaxs[2]) + bmaxs[2] = pt[2]; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : bmins - +// bmaxs - +//----------------------------------------------------------------------------- +void BoundBox::UpdateBounds(const Vector& mins, const Vector& maxs) +{ + if(mins[0] < bmins[0]) + bmins[0] = mins[0]; + if(mins[1] < bmins[1]) + bmins[1] = mins[1]; + if(mins[2] < bmins[2]) + bmins[2] = mins[2]; + + if(maxs[0] > bmaxs[0]) + bmaxs[0] = maxs[0]; + if(maxs[1] > bmaxs[1]) + bmaxs[1] = maxs[1]; + if(maxs[2] > bmaxs[2]) + bmaxs[2] = maxs[2]; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : pBox - +//----------------------------------------------------------------------------- +void BoundBox::UpdateBounds(const BoundBox *pBox) +{ + UpdateBounds(pBox->bmins, pBox->bmaxs); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : ptdest - +//----------------------------------------------------------------------------- +void BoundBox::GetBoundsCenter(Vector& ptdest) +{ + ptdest = (bmins + bmaxs)/2.0f; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : pt - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool BoundBox::ContainsPoint(const Vector& pt) const +{ + for (int i = 0; i < 3; i++) + { + if (pt[i] < bmins[i] || pt[i] > bmaxs[i]) + { + return(false); + } + } + return(true); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : pfMins - +// pfMaxs - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool BoundBox::IsIntersectingBox(const Vector& pfMins, const Vector& pfMaxs) const +{ + if ((bmins[0] >= pfMaxs[0]) || (bmaxs[0] <= pfMins[0])) + { + return(false); + + } + if ((bmins[1] >= pfMaxs[1]) || (bmaxs[1] <= pfMins[1])) + { + return(false); + } + + if ((bmins[2] >= pfMaxs[2]) || (bmaxs[2] <= pfMins[2])) + { + return(false); + } + + return(true); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : pfMins - +// pfMaxs - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool BoundBox::IsInsideBox(const Vector& pfMins, const Vector& pfMaxs) const +{ + if ((bmins[0] < pfMins[0]) || (bmaxs[0] > pfMaxs[0])) + { + return(false); + } + + if ((bmins[1] < pfMins[1]) || (bmaxs[1] > pfMaxs[1])) + { + return(false); + } + + if ((bmins[2] < pfMins[2]) || (bmaxs[2] > pfMaxs[2])) + { + return(false); + } + + return(true); +} + + +//----------------------------------------------------------------------------- +// Purpose: Returns whether this bounding box is valid, ie maxs >= mins. +//----------------------------------------------------------------------------- +bool BoundBox::IsValidBox(void) const +{ + for (int i = 0; i < 3; i++) + { + if (bmins[i] > bmaxs[i]) + { + return(false); + } + } + + return(true); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : size - +//----------------------------------------------------------------------------- +void BoundBox::GetBoundsSize(Vector& size) +{ + size[0] = bmaxs[0] - bmins[0]; + size[1] = bmaxs[1] - bmins[1]; + size[2] = bmaxs[2] - bmins[2]; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : iValue - +// iGridSize - +// Output : +//----------------------------------------------------------------------------- +static int Snap(/*int*/ float iValue, int iGridSize) +{ + return (int)(rint(iValue/iGridSize) * iGridSize); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : iGridSize - +//----------------------------------------------------------------------------- +void BoundBox::SnapToGrid(int iGridSize) +{ + // does not alter the size of the box .. snaps its minimal coordinates + // to the grid size specified in iGridSize + Vector size; + GetBoundsSize(size); + + for(int i = 0; i < 3; i++) + { + bmins[i] = (float)Snap(/* YWB (int)*/bmins[i], iGridSize); + bmaxs[i] = bmins[i] + size[i]; + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : axis - +//----------------------------------------------------------------------------- +void BoundBox::Rotate90(int axis) +{ + int e1 = AXIS_X, e2 = AXIS_Y; + + // get bounds center first + Vector center; + GetBoundsCenter(center); + + switch(axis) + { + case AXIS_Z: + e1 = AXIS_X; + e2 = AXIS_Y; + break; + case AXIS_X: + e1 = AXIS_Y; + e2 = AXIS_Z; + break; + case AXIS_Y: + e1 = AXIS_X; + e2 = AXIS_Z; + break; + } + + float tmp1, tmp2; + tmp1 = bmins[e1] - center[e1] + center[e2]; + tmp2 = bmaxs[e1] - center[e1] + center[e2]; + bmins[e1] = bmins[e2] - center[e2] + center[e1]; + bmaxs[e1] = bmaxs[e2] - center[e2] + center[e1]; + bmins[e2] = tmp1; + bmaxs[e2] = tmp2; +} + diff --git a/mp/src/utils/vbsp/boundbox.h b/mp/src/utils/vbsp/boundbox.h new file mode 100644 index 00000000..c9838fe6 --- /dev/null +++ b/mp/src/utils/vbsp/boundbox.h @@ -0,0 +1,79 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: An axis aligned bounding box class. +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef BOUNDBOX_H +#define BOUNDBOX_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "mathlib/vector.h" + +#define COORD_NOTINIT ((float)(99999.0)) + +enum +{ + AXIS_X = 0, + AXIS_Y, + AXIS_Z +}; + +class BoundBox +{ + public: + + BoundBox(void); + BoundBox(const Vector &mins, const Vector &maxs); + + void ResetBounds(void); + inline void SetBounds(const Vector &mins, const Vector &maxs); + + void UpdateBounds(const Vector& bmins, const Vector& bmaxs); + void UpdateBounds(const Vector& pt); + void UpdateBounds(const BoundBox *pBox); + void GetBoundsCenter(Vector& ptdest); + inline void GetBounds(Vector& Mins, Vector& Maxs); + + virtual bool IsIntersectingBox(const Vector& pfMins, const Vector& pfMaxs) const; + bool IsInsideBox(const Vector& pfMins, const Vector& pfMaxs) const; + bool ContainsPoint(const Vector& pt) const; + bool IsValidBox(void) const; + void GetBoundsSize(Vector& size); + void SnapToGrid(int iGridSize); + void Rotate90(int axis); + + Vector bmins; + Vector bmaxs; +}; + + +//----------------------------------------------------------------------------- +// Purpose: Gets the bounding box as two vectors, a min and a max. +// Input : Mins - Receives the box's minima. +// Maxs - Receives the box's maxima. +//----------------------------------------------------------------------------- +void BoundBox::GetBounds(Vector &Mins, Vector &Maxs) +{ + Mins = bmins; + Maxs = bmaxs; +} + + +//----------------------------------------------------------------------------- +// Purpose: Sets the box outright, equivalent to ResetBounds + UpdateBounds. +// Input : mins - Minima to set. +// maxs - Maxima to set. +//----------------------------------------------------------------------------- +void BoundBox::SetBounds(const Vector &mins, const Vector &maxs) +{ + bmins = mins; + bmaxs = maxs; +} + + +#endif // BOUNDBOX_H diff --git a/mp/src/utils/vbsp/brushbsp.cpp b/mp/src/utils/vbsp/brushbsp.cpp new file mode 100644 index 00000000..17323c58 --- /dev/null +++ b/mp/src/utils/vbsp/brushbsp.cpp @@ -0,0 +1,1469 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +#include "vbsp.h" + + +int c_nodes; +int c_nonvis; +int c_active_brushes; + +// if a brush just barely pokes onto the other side, +// let it slide by without chopping +#define PLANESIDE_EPSILON 0.001 +//0.1 + + +void FindBrushInTree (node_t *node, int brushnum) +{ + bspbrush_t *b; + + if (node->planenum == PLANENUM_LEAF) + { + for (b=node->brushlist ; b ; b=b->next) + if (b->original->brushnum == brushnum) + Msg("here\n"); + return; + } + FindBrushInTree (node->children[0], brushnum); + FindBrushInTree (node->children[1], brushnum); +} + +//================================================== + +/* +================ +DrawBrushList +================ +*/ +void DrawBrushList (bspbrush_t *brush, node_t *node) +{ + int i; + side_t *s; + + GLS_BeginScene (); + for ( ; brush ; brush=brush->next) + { + for (i=0 ; inumsides ; i++) + { + s = &brush->sides[i]; + if (!s->winding) + continue; + if (s->texinfo == TEXINFO_NODE) + GLS_Winding (s->winding, 1); + else if (!s->visible) + GLS_Winding (s->winding, 2); + else + GLS_Winding (s->winding, 0); + } + } + GLS_EndScene (); +} + +/* +================ +WriteBrushList +================ +*/ +void WriteBrushList (char *name, bspbrush_t *brush, qboolean onlyvis) +{ + int i; + side_t *s; + + qprintf ("writing %s\n", name); + FileHandle_t f = g_pFileSystem->Open(name, "w"); + + for ( ; brush ; brush=brush->next) + { + for (i=0 ; inumsides ; i++) + { + s = &brush->sides[i]; + if (!s->winding) + continue; + if (onlyvis && !s->visible) + continue; + OutputWinding (brush->sides[i].winding, f); + } + } + + g_pFileSystem->Close (f); +} + +void PrintBrush (bspbrush_t *brush) +{ + int i; + + Msg("brush: %p\n", brush); + for (i=0;inumsides ; i++) + { + pw(brush->sides[i].winding); + Msg("\n"); + } +} + +/* +================== +BoundBrush + +Sets the mins/maxs based on the windings +================== +*/ +void BoundBrush (bspbrush_t *brush) +{ + int i, j; + winding_t *w; + + ClearBounds (brush->mins, brush->maxs); + for (i=0 ; inumsides ; i++) + { + w = brush->sides[i].winding; + if (!w) + continue; + for (j=0 ; jnumpoints ; j++) + AddPointToBounds (w->p[j], brush->mins, brush->maxs); + } +} + +Vector PointInsideBrush( bspbrush_t *brush ) +{ + Vector insidePoint = vec3_origin; + + bool bInside = false; + for ( int k = 0; k < 4 && !bInside; k++ ) + { + bInside = true; + for (int i = 0; i < brush->numsides; i++) + { + side_t *side = &brush->sides[i]; + plane_t *plane = &g_MainMap->mapplanes[side->planenum]; + float d = DotProduct( plane->normal, insidePoint ) - plane->dist; + if ( d < 0 ) + { + bInside = false; + insidePoint -= d * plane->normal; + } + } + } + return insidePoint; +} + +/* +================== +CreateBrushWindings + +================== +*/ +void CreateBrushWindings (bspbrush_t *brush) +{ + int i, j; + winding_t *w; + side_t *side; + plane_t *plane; + + // translate the CSG problem to improve precision + Vector insidePoint = PointInsideBrush( brush ); + Vector offset = -insidePoint; + + for (i=0 ; inumsides ; i++) + { + side = &brush->sides[i]; + plane = &g_MainMap->mapplanes[side->planenum]; + w = BaseWindingForPlane (plane->normal, plane->dist + DotProduct(plane->normal, offset)); + for (j=0 ; jnumsides && w; j++) + { + if (i == j) + continue; + if (brush->sides[j].bevel) + continue; + plane = &g_MainMap->mapplanes[brush->sides[j].planenum^1]; + ChopWindingInPlace (&w, plane->normal, plane->dist + DotProduct(plane->normal, offset), 0); //CLIP_EPSILON); + } + + TranslateWinding( w, -offset ); + side->winding = w; + } + + BoundBrush (brush); +} + +/* +================== +BrushFromBounds + +Creates a new axial brush +================== +*/ +bspbrush_t *BrushFromBounds (Vector& mins, Vector& maxs) +{ + bspbrush_t *b; + int i; + Vector 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 = g_MainMap->FindFloatPlane (normal, dist); + + normal[i] = -1; + dist = -mins[i]; + b->sides[3+i].planenum = g_MainMap->FindFloatPlane (normal, dist); + } + + CreateBrushWindings (b); + + return b; +} + +/* +================== +BrushVolume + +================== +*/ +vec_t BrushVolume (bspbrush_t *brush) +{ + int i; + winding_t *w; + Vector corner; + vec_t d, area, volume; + plane_t *plane; + + if (!brush) + return 0; + + // grab the first valid point as the corner + + w = NULL; + for (i=0 ; inumsides ; i++) + { + w = brush->sides[i].winding; + if (w) + break; + } + if (!w) + return 0; + VectorCopy (w->p[0], corner); + + // make tetrahedrons to all other faces + + volume = 0; + for ( ; inumsides ; i++) + { + w = brush->sides[i].winding; + if (!w) + continue; + plane = &g_MainMap->mapplanes[brush->sides[i].planenum]; + d = -(DotProduct (corner, plane->normal) - plane->dist); + area = WindingArea (w); + volume += d*area; + } + + volume /= 3; + return volume; +} + +/* +================ +CountBrushList +================ +*/ +int CountBrushList (bspbrush_t *brushes) +{ + int c; + + c = 0; + for ( ; brushes ; brushes = brushes->next) + c++; + return c; +} + +/* +================ +AllocTree +================ +*/ +tree_t *AllocTree (void) +{ + tree_t *tree; + + tree = (tree_t*)malloc(sizeof(*tree)); + memset (tree, 0, sizeof(*tree)); + ClearBounds (tree->mins, tree->maxs); + + return tree; +} + +/* +================ +AllocNode +================ +*/ +node_t *AllocNode (void) +{ + static int s_NodeCount = 0; + + node_t *node; + + node = (node_t*)malloc(sizeof(*node)); + memset (node, 0, sizeof(*node)); + node->id = s_NodeCount; + node->diskId = -1; + + s_NodeCount++; + + return node; +} + + +/* +================ +AllocBrush +================ +*/ +bspbrush_t *AllocBrush (int numsides) +{ + static int s_BrushId = 0; + + bspbrush_t *bb; + int c; + + c = (int)&(((bspbrush_t *)0)->sides[numsides]); + bb = (bspbrush_t*)malloc(c); + memset (bb, 0, c); + bb->id = s_BrushId++; + if (numthreads == 1) + c_active_brushes++; + return bb; +} + +/* +================ +FreeBrush +================ +*/ +void FreeBrush (bspbrush_t *brushes) +{ + int i; + + for (i=0 ; inumsides ; i++) + if (brushes->sides[i].winding) + FreeWinding(brushes->sides[i].winding); + free (brushes); + if (numthreads == 1) + c_active_brushes--; +} + + +/* +================ +FreeBrushList +================ +*/ +void FreeBrushList (bspbrush_t *brushes) +{ + bspbrush_t *next; + + for ( ; brushes ; brushes = next) + { + next = brushes->next; + + FreeBrush (brushes); + } +} + +/* +================== +CopyBrush + +Duplicates the brush, the sides, and the windings +================== +*/ +bspbrush_t *CopyBrush (bspbrush_t *brush) +{ + bspbrush_t *newbrush; + int size; + int i; + + size = (int)&(((bspbrush_t *)0)->sides[brush->numsides]); + + newbrush = AllocBrush (brush->numsides); + memcpy (newbrush, brush, size); + + for (i=0 ; inumsides ; i++) + { + if (brush->sides[i].winding) + newbrush->sides[i].winding = CopyWinding (brush->sides[i].winding); + } + + return newbrush; +} + + +/* +================== +PointInLeaf + +================== +*/ +node_t *PointInLeaf (node_t *node, Vector& point) +{ + vec_t d; + plane_t *plane; + + while (node->planenum != PLANENUM_LEAF) + { + plane = &g_MainMap->mapplanes[node->planenum]; + if (plane->type < 3) + { + d = point[plane->type] - plane->dist; + } + else + { + d = DotProduct (point, plane->normal) - plane->dist; + } + + if (d >= 0) + node = node->children[0]; + else + node = node->children[1]; + } + + return node; +} + +//======================================================== + +/* +============== +BoxOnPlaneSide + +Returns PSIDE_FRONT, PSIDE_BACK, or PSIDE_BOTH +============== +*/ +int BrushBspBoxOnPlaneSide (const Vector& mins, const Vector& maxs, dplane_t *plane) +{ + int side; + int i; + Vector corners[2]; + vec_t dist1, dist2; + + // axial planes are easy + if (plane->type < 3) + { + side = 0; + if (maxs[plane->type] > plane->dist+PLANESIDE_EPSILON) + side |= PSIDE_FRONT; + if (mins[plane->type] < plane->dist-PLANESIDE_EPSILON) + side |= PSIDE_BACK; + return side; + } + + // create the proper leading and trailing verts for the box + + for (i=0 ; i<3 ; i++) + { + if (plane->normal[i] < 0) + { + corners[0][i] = mins[i]; + corners[1][i] = maxs[i]; + } + else + { + corners[1][i] = mins[i]; + corners[0][i] = maxs[i]; + } + } + + dist1 = DotProduct (plane->normal, corners[0]) - plane->dist; + dist2 = DotProduct (plane->normal, corners[1]) - plane->dist; + side = 0; + if (dist1 >= PLANESIDE_EPSILON) + side = PSIDE_FRONT; + if (dist2 < PLANESIDE_EPSILON) + side |= PSIDE_BACK; + + return side; +} + +/* +============ +QuickTestBrushToPlanenum + +============ +*/ +int QuickTestBrushToPlanenum (bspbrush_t *brush, int planenum, int *numsplits) +{ + int i, num; + plane_t *plane; + int s; + + *numsplits = 0; + + // if the brush actually uses the planenum, + // we can tell the side for sure + for (i=0 ; inumsides ; i++) + { + num = brush->sides[i].planenum; + if (num >= 0x10000) + Error ("bad planenum"); + if (num == planenum) + return PSIDE_BACK|PSIDE_FACING; + if (num == (planenum ^ 1) ) + return PSIDE_FRONT|PSIDE_FACING; + } + + // box on plane side + plane = &g_MainMap->mapplanes[planenum]; + s = BrushBspBoxOnPlaneSide (brush->mins, brush->maxs, plane); + + // if both sides, count the visible faces split + if (s == PSIDE_BOTH) + { + *numsplits += 3; + } + + return s; +} + +/* +============ +TestBrushToPlanenum + +============ +*/ +int TestBrushToPlanenum (bspbrush_t *brush, int planenum, + int *numsplits, qboolean *hintsplit, int *epsilonbrush) +{ + int i, j, num; + plane_t *plane; + int s; + winding_t *w; + vec_t d, d_front, d_back; + int front, back; + + *numsplits = 0; + *hintsplit = false; + + // if the brush actually uses the planenum, + // we can tell the side for sure + for (i=0 ; inumsides ; i++) + { + num = brush->sides[i].planenum; + if (num >= 0x10000) + Error ("bad planenum"); + if (num == planenum) + return PSIDE_BACK|PSIDE_FACING; + if (num == (planenum ^ 1) ) + return PSIDE_FRONT|PSIDE_FACING; + } + + // box on plane side + plane = &g_MainMap->mapplanes[planenum]; + s = BrushBspBoxOnPlaneSide (brush->mins, brush->maxs, plane); + + if (s != PSIDE_BOTH) + return s; + +// if both sides, count the visible faces split + d_front = d_back = 0; + + for (i=0 ; inumsides ; i++) + { + if (brush->sides[i].texinfo == TEXINFO_NODE) + continue; // on node, don't worry about splits + if (!brush->sides[i].visible) + continue; // we don't care about non-visible + w = brush->sides[i].winding; + if (!w) + continue; + + front = back = 0; + for (j=0 ; jnumpoints; j++) + { + d = DotProduct (w->p[j], plane->normal) - plane->dist; + + if (d > d_front) + d_front = d; + if (d < d_back) + d_back = d; + + if (d > 0.1) // PLANESIDE_EPSILON) + front = 1; + if (d < -0.1) // PLANESIDE_EPSILON) + back = 1; + } + + if (front && back) + { + if ( !(brush->sides[i].surf & SURF_SKIP) ) + { + (*numsplits)++; + if (brush->sides[i].surf & SURF_HINT) + *hintsplit = true; + } + } + } + + if ( (d_front > 0.0 && d_front < 1.0) + || (d_back < 0.0 && d_back > -1.0) ) + (*epsilonbrush)++; + +#if 0 + if (*numsplits == 0) + { // didn't really need to be split + if (front) + s = PSIDE_FRONT; + else if (back) + s = PSIDE_BACK; + else + s = 0; + } +#endif + + return s; +} + +//======================================================== + +/* +================ +WindingIsTiny + +Returns true if the winding would be crunched out of +existance by the vertex snapping. +================ +*/ +#define EDGE_LENGTH 0.2 +qboolean WindingIsTiny (winding_t *w) +{ + int i, j; + vec_t len; + Vector delta; + int edges; + + edges = 0; + for (i=0 ; inumpoints ; i++) + { + j = i == w->numpoints - 1 ? 0 : i+1; + VectorSubtract (w->p[j], w->p[i], delta); + len = VectorLength (delta); + if (len > EDGE_LENGTH) + { + if (++edges == 3) + return false; + } + } + return true; +} + + +// UNDONE: JAY: This should be a slightly better heuristic - it builds an OBB +// around the winding and tests planar dimensions. NOTE: This can fail when a +// winding normal cannot be constructed (or is degenerate), but that is probably +// desired in this case. +// UNDONE: Test & use this instead. +#if 0 +qboolean WindingIsTiny2 (winding_t *w) +{ + int i, j; + vec_t len; + Vector delta; + int edges; + + vec_t maxLen = 0; + Vector maxEdge = vec3_origin; + + edges = 0; + for (i=0 ; inumpoints ; i++) + { + j = i == w->numpoints - 1 ? 0 : i+1; + VectorSubtract (w->p[j], w->p[i], delta); + len = VectorLength (delta); + if (len > maxLen) + { + maxEdge = delta; + maxLen = len; + } + } + Vector normal; + vec_t dist; + WindingPlane (w, normal, &dist); // normal can come back vec3_origin in some cases + VectorNormalize(maxEdge); + Vector cross = CrossProduct(normal, maxEdge); + VectorNormalize(cross); + Vector mins, maxs; + ClearBounds( mins, maxs ); + for (i=0 ; inumpoints ; i++) + { + Vector point; + point.x = DotProduct( w->p[i], maxEdge ); + point.y = DotProduct( w->p[i], cross ); + point.z = DotProduct( w->p[i], normal ); + AddPointToBounds( point, mins, maxs ); + } + + // check to see if the size in the plane is too small in either dimension + Vector size = maxs - mins; + for ( i = 0; i < 2; i++ ) + { + if ( size[i] < EDGE_LENGTH ) + return true; + } + return false; +} +#endif + + +/* +================ +WindingIsHuge + +Returns true if the winding still has one of the points +from basewinding for plane +================ +*/ +qboolean WindingIsHuge (winding_t *w) +{ + int i, j; + + for (i=0 ; inumpoints ; i++) + { + for (j=0 ; j<3 ; j++) + if (w->p[i][j] < MIN_COORD_INTEGER || w->p[i][j] > MAX_COORD_INTEGER) + return true; + } + return false; +} + +//============================================================ + +/* +================ +Leafnode +================ +*/ +void LeafNode (node_t *node, bspbrush_t *brushes) +{ + bspbrush_t *b; + int i; + + node->planenum = PLANENUM_LEAF; + node->contents = 0; + + for (b=brushes ; b ; b=b->next) + { + // if the brush is solid and all of its sides are on nodes, + // it eats everything + if (b->original->contents & CONTENTS_SOLID) + { + for (i=0 ; inumsides ; i++) + if (b->sides[i].texinfo != TEXINFO_NODE) + break; + if (i == b->numsides) + { + node->contents = CONTENTS_SOLID; + break; + } + } + node->contents |= b->original->contents; + } + + node->brushlist = brushes; +} + + +void RemoveAreaPortalBrushes_R( node_t *node ) +{ + if( node->planenum == PLANENUM_LEAF ) + { + // Remove any CONTENTS_AREAPORTAL brushes we added. We don't want them in the engine + // at runtime but we do want their flags in the leaves. + bspbrush_t **pPrev = &node->brushlist; + for( bspbrush_t *b=node->brushlist; b; b=b->next ) + { + if( b->original->contents == CONTENTS_AREAPORTAL ) + { + *pPrev = b->next; + } + else + { + pPrev = &b->next; + } + } + } + else + { + RemoveAreaPortalBrushes_R( node->children[0] ); + RemoveAreaPortalBrushes_R( node->children[1] ); + } +} + + +//============================================================ + +void CheckPlaneAgainstParents (int pnum, node_t *node) +{ + node_t *p; + + for (p=node->parent ; p ; p=p->parent) + { + if (p->planenum == pnum) + Error ("Tried parent"); + } +} + +qboolean CheckPlaneAgainstVolume (int pnum, node_t *node) +{ + bspbrush_t *front, *back; + qboolean good; + + SplitBrush (node->volume, pnum, &front, &back); + + good = (front && back); + + if (front) + FreeBrush (front); + if (back) + FreeBrush (back); + + return good; +} + +/* +================ +SelectSplitSide + +Using a hueristic, choses one of the sides out of the brushlist +to partition the brushes with. +Returns NULL if there are no valid planes to split with.. +================ +*/ + +side_t *SelectSplitSide (bspbrush_t *brushes, node_t *node) +{ + int value, bestvalue; + bspbrush_t *brush, *test; + side_t *side, *bestside; + int i, j, pass, numpasses; + int pnum; + int s; + int front, back, both, facing, splits; + int bsplits; + int bestsplits; + int epsilonbrush; + qboolean hintsplit = false; + + bestside = NULL; + bestvalue = -99999; + bestsplits = 0; + + // the search order goes: visible-structural, nonvisible-structural + // If any valid plane is available in a pass, no further + // passes will be tried. + numpasses = 2; + for (pass = 0 ; pass < numpasses ; pass++) + { + for (brush = brushes ; brush ; brush=brush->next) + { + for (i=0 ; inumsides ; i++) + { + side = brush->sides + i; + + if (side->bevel) + continue; // never use a bevel as a spliter + if (!side->winding) + continue; // nothing visible, so it can't split + if (side->texinfo == TEXINFO_NODE) + continue; // allready a node splitter + if (side->tested) + continue; // we allready have metrics for this plane + if (side->surf & SURF_SKIP) + continue; // skip surfaces are never chosen + if ( side->visible ^ (pass<1) ) + continue; // only check visible faces on first pass + + pnum = side->planenum; + pnum &= ~1; // allways use positive facing plane + + CheckPlaneAgainstParents (pnum, node); + + if (!CheckPlaneAgainstVolume (pnum, node)) + continue; // would produce a tiny volume + + front = 0; + back = 0; + both = 0; + facing = 0; + splits = 0; + epsilonbrush = 0; + + for (test = brushes ; test ; test=test->next) + { + s = TestBrushToPlanenum (test, pnum, &bsplits, &hintsplit, &epsilonbrush); + + splits += bsplits; + if (bsplits && (s&PSIDE_FACING) ) + Error ("PSIDE_FACING with splits"); + + test->testside = s; + // if the brush shares this face, don't bother + // testing that facenum as a splitter again + if (s & PSIDE_FACING) + { + facing++; + for (j=0 ; jnumsides ; j++) + { + if ( (test->sides[j].planenum&~1) == pnum) + test->sides[j].tested = true; + } + } + if (s & PSIDE_FRONT) + front++; + if (s & PSIDE_BACK) + back++; + if (s == PSIDE_BOTH) + both++; + } + + // give a value estimate for using this plane + value = 5*facing - 5*splits - abs(front-back); +// value = -5*splits; +// value = 5*facing - 5*splits; + if (g_MainMap->mapplanes[pnum].type < 3) + value+=5; // axial is better + value -= epsilonbrush*1000; // avoid! + + // trans should split last + if ( side->surf & SURF_TRANS ) + { + value -= 500; + } + + // never split a hint side except with another hint + if (hintsplit && !(side->surf & SURF_HINT) ) + value = -9999999; + + // water should split first + if (side->contents & (CONTENTS_WATER | CONTENTS_SLIME)) + value = 9999999; + + // save off the side test so we don't need + // to recalculate it when we actually seperate + // the brushes + if (value > bestvalue) + { + bestvalue = value; + bestside = side; + bestsplits = splits; + for (test = brushes ; test ; test=test->next) + test->side = test->testside; + } + } + } + + // if we found a good plane, don't bother trying any + // other passes + if (bestside) + { + if (pass > 0) + { + if (numthreads == 1) + c_nonvis++; + } + break; + } + } + + // + // clear all the tested flags we set + // + for (brush = brushes ; brush ; brush=brush->next) + { + for (i=0 ; inumsides ; i++) + brush->sides[i].tested = false; + } + + return bestside; +} + + +/* +================== +BrushMostlyOnSide + +================== +*/ +int BrushMostlyOnSide (bspbrush_t *brush, plane_t *plane) +{ + int i, j; + winding_t *w; + vec_t d, max; + int side; + + max = 0; + side = PSIDE_FRONT; + for (i=0 ; inumsides ; i++) + { + w = brush->sides[i].winding; + if (!w) + continue; + for (j=0 ; jnumpoints ; j++) + { + d = DotProduct (w->p[j], plane->normal) - plane->dist; + if (d > max) + { + max = d; + side = PSIDE_FRONT; + } + if (-d > max) + { + max = -d; + side = PSIDE_BACK; + } + } + } + return side; +} + +/* +================ +SplitBrush + +Generates two new brushes, leaving the original +unchanged +================ +*/ + + +void SplitBrush( bspbrush_t *brush, int planenum, bspbrush_t **front, bspbrush_t **back ) +{ + bspbrush_t *b[2]; + int i, j; + winding_t *w, *cw[2], *midwinding; + plane_t *plane, *plane2; + side_t *s, *cs; + float d, d_front, d_back; + + *front = *back = NULL; + plane = &g_MainMap->mapplanes[planenum]; + + // check all points + d_front = d_back = 0; + for (i=0 ; inumsides ; i++) + { + w = brush->sides[i].winding; + if (!w) + continue; + for (j=0 ; jnumpoints ; j++) + { + d = DotProduct (w->p[j], plane->normal) - plane->dist; + if (d > 0 && d > d_front) + d_front = d; + if (d < 0 && d < d_back) + d_back = d; + } + } + + if (d_front < 0.1) // PLANESIDE_EPSILON) + { // only on back + *back = CopyBrush (brush); + return; + } + if (d_back > -0.1) // PLANESIDE_EPSILON) + { // only on front + *front = CopyBrush (brush); + return; + } + + + // Move the CSG problem so that offset is at the origin + // This gives us much better floating point precision in the clipping operations + Vector offset = -0.5f * (brush->mins + brush->maxs); + // create a new winding from the split plane + + w = BaseWindingForPlane (plane->normal, plane->dist + DotProduct(plane->normal,offset)); + for (i=0 ; inumsides && w ; i++) + { + plane2 = &g_MainMap->mapplanes[brush->sides[i].planenum ^ 1]; + ChopWindingInPlace (&w, plane2->normal, plane2->dist+DotProduct(plane2->normal,offset), 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)) + { + qprintf ("WARNING: huge winding\n"); + } + + TranslateWinding( w, -offset ); + midwinding = w; + + // + // + // split it for real + // + // + + // + // allocate two new brushes referencing the original + // + for( i = 0; i < 2; i++ ) + { + b[i] = AllocBrush( brush->numsides + 1 ); + b[i]->original = brush->original; + } + + // + // split all the current windings + // + for( i = 0; i < brush->numsides; i++ ) + { + // get the current side + s = &brush->sides[i]; + + // get the sides winding + w = s->winding; + if( !w ) + continue; + + // clip the winding + ClipWindingEpsilon_Offset( w, plane->normal, plane->dist, 0 /*PLANESIDE_EPSILON*/, &cw[0], &cw[1], offset ); + + for( j = 0; j < 2; j++ ) + { + // does winding exist? + if( !cw[j] ) + continue; +#if 0 + if (WindingIsTiny (cw[j])) + { + FreeWinding (cw[j]); + continue; + } +#endif + + // + // create a clipped "side" with the new winding + // + cs = &b[j]->sides[b[j]->numsides]; + b[j]->numsides++; + *cs = *s; + cs->winding = cw[j]; + cs->tested = false; + // save the original side information + //cs->original = s->original; + } + } + + + // see if we have valid polygons on both sides + + for (i=0 ; i<2 ; i++) + { + BoundBrush (b[i]); + for (j=0 ; j<3 ; j++) + { + if (b[i]->mins[j] < MIN_COORD_INTEGER || b[i]->maxs[j] > MAX_COORD_INTEGER) + { + qprintf ("bogus brush after clip\n"); + break; + } + } + + if (b[i]->numsides < 3 || j < 3) + { + FreeBrush (b[i]); + b[i] = NULL; + } + } + + if ( !(b[0] && b[1]) ) + { + if (!b[0] && !b[1]) + qprintf ("split removed brush\n"); + else + qprintf ("split not on both sides\n"); + if (b[0]) + { + FreeBrush (b[0]); + *front = CopyBrush (brush); + } + if (b[1]) + { + FreeBrush (b[1]); + *back = CopyBrush (brush); + } + return; + } + + // add the midwinding to both sides + for (i=0 ; i<2 ; i++) + { + cs = &b[i]->sides[b[i]->numsides]; + b[i]->numsides++; + + cs->planenum = planenum^i^1; + cs->texinfo = TEXINFO_NODE; + + // initialize the displacement map index + cs->pMapDisp = NULL; + + cs->visible = false; + cs->tested = false; + if (i==0) + cs->winding = CopyWinding (midwinding); + else + cs->winding = midwinding; + } + +{ + vec_t v1; + int i; + + for (i=0 ; i<2 ; i++) + { + v1 = BrushVolume (b[i]); + if (v1 < 1.0) + { + FreeBrush (b[i]); + b[i] = NULL; +// qprintf ("tiny volume after clip\n"); + } + } +} + + *front = b[0]; + *back = b[1]; +} + + +/* +================ +SplitBrushList +================ +*/ +void SplitBrushList (bspbrush_t *brushes, + node_t *node, bspbrush_t **front, bspbrush_t **back) +{ + bspbrush_t *brush, *newbrush, *newbrush2; + side_t *side; + int sides; + int i; + + *front = *back = NULL; + + for (brush = brushes ; brush ; brush=brush->next) + { + sides = brush->side; + + if (sides == PSIDE_BOTH) + { // split into two brushes + SplitBrush (brush, node->planenum, &newbrush, &newbrush2); + if (newbrush) + { + newbrush->next = *front; + *front = newbrush; + } + if (newbrush2) + { + newbrush2->next = *back; + *back = newbrush2; + } + continue; + } + + newbrush = CopyBrush (brush); + + // if the planenum is actualy a part of the brush + // find the plane and flag it as used so it won't be tried + // as a splitter again + if (sides & PSIDE_FACING) + { + for (i=0 ; inumsides ; i++) + { + side = newbrush->sides + i; + if ( (side->planenum& ~1) == node->planenum) + side->texinfo = TEXINFO_NODE; + } + } + + + if (sides & PSIDE_FRONT) + { + newbrush->next = *front; + *front = newbrush; + continue; + } + if (sides & PSIDE_BACK) + { + newbrush->next = *back; + *back = newbrush; + continue; + } + } +} + + +/* +================ +BuildTree_r +================ +*/ + + +node_t *BuildTree_r (node_t *node, bspbrush_t *brushes) +{ + node_t *newnode; + side_t *bestside; + int i; + bspbrush_t *children[2]; + + if (numthreads == 1) + c_nodes++; + + // find the best plane to use as a splitter + bestside = SelectSplitSide (brushes, node); + + if (!bestside) + { + // leaf node + node->side = NULL; + node->planenum = -1; + LeafNode (node, brushes); + return node; + } + + // this is a splitplane node + node->side = bestside; + node->planenum = bestside->planenum & ~1; // always use front facing + + SplitBrushList (brushes, node, &children[0], &children[1]); + FreeBrushList (brushes); + + // allocate children before recursing + for (i=0 ; i<2 ; i++) + { + newnode = AllocNode (); + newnode->parent = node; + node->children[i] = newnode; + } + + SplitBrush (node->volume, node->planenum, &node->children[0]->volume, + &node->children[1]->volume); + + // recursively process children + for (i=0 ; i<2 ; i++) + { + node->children[i] = BuildTree_r (node->children[i], children[i]); + } + + return node; +} + + +//=========================================================== + +/* +================= +BrushBSP + +The incoming list will be freed before exiting +================= +*/ +tree_t *BrushBSP (bspbrush_t *brushlist, Vector& mins, Vector& maxs) +{ + node_t *node; + bspbrush_t *b; + int c_faces, c_nonvisfaces; + int c_brushes; + tree_t *tree; + int i; + vec_t volume; + + qprintf ("--- BrushBSP ---\n"); + + tree = AllocTree (); + + c_faces = 0; + c_nonvisfaces = 0; + c_brushes = 0; + for (b=brushlist ; b ; b=b->next) + { + c_brushes++; + + volume = BrushVolume (b); + if (volume < microvolume) + { + Warning("Brush %i: WARNING, microbrush\n", b->original->id); + } + + for (i=0 ; inumsides ; i++) + { + if (b->sides[i].bevel) + continue; + if (!b->sides[i].winding) + continue; + if (b->sides[i].texinfo == TEXINFO_NODE) + continue; + if (b->sides[i].visible) + c_faces++; + else + c_nonvisfaces++; + } + + AddPointToBounds (b->mins, tree->mins, tree->maxs); + AddPointToBounds (b->maxs, tree->mins, tree->maxs); + } + + qprintf ("%5i brushes\n", c_brushes); + qprintf ("%5i visible faces\n", c_faces); + qprintf ("%5i nonvisible faces\n", c_nonvisfaces); + + c_nodes = 0; + c_nonvis = 0; + node = AllocNode (); + + node->volume = BrushFromBounds (mins, maxs); + + tree->headnode = node; + + node = BuildTree_r (node, brushlist); + qprintf ("%5i visible nodes\n", c_nodes/2 - c_nonvis); + qprintf ("%5i nonvis nodes\n", c_nonvis); + qprintf ("%5i leafs\n", (c_nodes+1)/2); +#if 0 +{ // debug code +static node_t *tnode; +Vector p; + +p[0] = -1469; +p[1] = -118; +p[2] = 119; +tnode = PointInLeaf (tree->headnode, p); +Msg("contents: %i\n", tnode->contents); +p[0] = 0; +} +#endif + return tree; +} + diff --git a/mp/src/utils/vbsp/csg.cpp b/mp/src/utils/vbsp/csg.cpp new file mode 100644 index 00000000..c4b89bd9 --- /dev/null +++ b/mp/src/utils/vbsp/csg.cpp @@ -0,0 +1,784 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +#include "vbsp.h" + +/* + +tag all brushes with original contents +brushes may contain multiple contents +there will be no brush overlap after csg phase + + + + +each side has a count of the other sides it splits + +the best split will be the one that minimizes the total split counts +of all remaining sides + +precalc side on plane table + +evaluate split side +{ +cost = 0 +for all sides + for all sides + get + if side splits side and splitside is on same child + cost++; +} + + + */ + +void SplitBrush2( bspbrush_t *brush, int planenum, bspbrush_t **front, bspbrush_t **back ) +{ + SplitBrush( brush, planenum, front, back ); +#if 0 + if (*front && (*front)->sides[(*front)->numsides-1].texinfo == -1) + (*front)->sides[(*front)->numsides-1].texinfo = (*front)->sides[0].texinfo; // not -1 + if (*back && (*back)->sides[(*back)->numsides-1].texinfo == -1) + (*back)->sides[(*back)->numsides-1].texinfo = (*back)->sides[0].texinfo; // not -1 +#endif +} + +/* +=============== +SubtractBrush + +Returns a list of brushes that remain after B is subtracted from A. +May by empty if A is contained inside B. + +The originals are undisturbed. +=============== +*/ +bspbrush_t *SubtractBrush (bspbrush_t *a, bspbrush_t *b) +{ // a - b = out (list) + int i; + bspbrush_t *front, *back; + bspbrush_t *out, *in; + + in = a; + out = NULL; + for (i=0 ; inumsides && in ; i++) + { + SplitBrush2 (in, b->sides[i].planenum, &front, &back); + if (in != a) + FreeBrush (in); + if (front) + { // add to list + front->next = out; + out = front; + } + in = back; + } + if (in) + FreeBrush (in); + else + { // didn't really intersect + FreeBrushList (out); + return a; + } + return out; +} + +/* +=============== +IntersectBrush + +Returns a single brush made up by the intersection of the +two provided brushes, or NULL if they are disjoint. + +The originals are undisturbed. +=============== +*/ +bspbrush_t *IntersectBrush (bspbrush_t *a, bspbrush_t *b) +{ + int i; + bspbrush_t *front, *back; + bspbrush_t *in; + + in = a; + for (i=0 ; inumsides && in ; i++) + { + SplitBrush2 (in, b->sides[i].planenum, &front, &back); + if (in != a) + FreeBrush (in); + if (front) + FreeBrush (front); + in = back; + } + + if (in == a || !in) + return NULL; + + in->next = NULL; + return in; +} + + +/* +=============== +BrushesDisjoint + +Returns true if the two brushes definately do not intersect. +There will be false negatives for some non-axial combinations. +=============== +*/ +qboolean BrushesDisjoint (bspbrush_t *a, bspbrush_t *b) +{ + int i, j; + + // check bounding boxes + for (i=0 ; i<3 ; i++) + if (a->mins[i] >= b->maxs[i] + || a->maxs[i] <= b->mins[i]) + return true; // bounding boxes don't overlap + + // check for opposing planes + for (i=0 ; inumsides ; i++) + { + for (j=0 ; jnumsides ; j++) + { + if (a->sides[i].planenum == + (b->sides[j].planenum^1) ) + return true; // opposite planes, so not touching + } + } + + return false; // might intersect +} + + +int minplanenums[3]; +int maxplanenums[3]; + +/* +=============== +ClipBrushToBox + +Any planes shared with the box edge will be set to no texinfo +=============== +*/ +bspbrush_t *ClipBrushToBox (bspbrush_t *brush, const Vector& clipmins, const Vector& clipmaxs) +{ + int i, j; + bspbrush_t *front, *back; + int p; + + for (j=0 ; j<2 ; j++) + { + if (brush->maxs[j] > clipmaxs[j]) + { + SplitBrush (brush, maxplanenums[j], &front, &back); + if (front) + FreeBrush (front); + brush = back; + if (!brush) + return NULL; + } + if (brush->mins[j] < clipmins[j]) + { + SplitBrush (brush, minplanenums[j], &front, &back); + if (back) + FreeBrush (back); + brush = front; + if (!brush) + return NULL; + } + } + + // remove any colinear faces + + for (i=0 ; inumsides ; i++) + { + p = brush->sides[i].planenum & ~1; + if (p == maxplanenums[0] || p == maxplanenums[1] + || p == minplanenums[0] || p == minplanenums[1]) + { + brush->sides[i].texinfo = TEXINFO_NODE; + brush->sides[i].visible = false; + } + } + return brush; +} + + +//----------------------------------------------------------------------------- +// Creates a clipped brush from a map brush +//----------------------------------------------------------------------------- +static bspbrush_t *CreateClippedBrush( mapbrush_t *mb, const Vector& clipmins, const Vector& clipmaxs ) +{ + int nNumSides = mb->numsides; + if (!nNumSides) + return NULL; + + // if the brush is outside the clip area, skip it + for (int j=0 ; j<3 ; j++) + { + if (mb->mins[j] >= clipmaxs[j] || mb->maxs[j] <= clipmins[j]) + { + return NULL; + } + } + + // make a copy of the brush + bspbrush_t *newbrush = AllocBrush( nNumSides ); + newbrush->original = mb; + newbrush->numsides = nNumSides; + memcpy (newbrush->sides, mb->original_sides, nNumSides*sizeof(side_t)); + + for (int j=0 ; jsides[j].winding) + { + newbrush->sides[j].winding = CopyWinding (newbrush->sides[j].winding); + } + + if (newbrush->sides[j].surf & SURF_HINT) + { + newbrush->sides[j].visible = true; // hints are always visible + } + + // keep a pointer to the original map brush side -- use to create the original face later!! + //newbrush->sides[j].original = &mb->original_sides[j]; + } + + VectorCopy (mb->mins, newbrush->mins); + VectorCopy (mb->maxs, newbrush->maxs); + + // carve off anything outside the clip box + newbrush = ClipBrushToBox (newbrush, clipmins, clipmaxs); + return newbrush; +} + + +//----------------------------------------------------------------------------- +// Creates a clipped brush from a map brush +//----------------------------------------------------------------------------- +static void ComputeBoundingPlanes( const Vector& clipmins, const Vector& clipmaxs ) +{ + Vector normal; + float dist; + for (int i=0 ; i<2 ; i++) + { + VectorClear (normal); + normal[i] = 1; + dist = clipmaxs[i]; + maxplanenums[i] = g_MainMap->FindFloatPlane (normal, dist); + dist = clipmins[i]; + minplanenums[i] = g_MainMap->FindFloatPlane (normal, dist); + } +} + + +//----------------------------------------------------------------------------- +// This forces copies of texinfo data for matching sides of a brush +//----------------------------------------------------------------------------- +void CopyMatchingTexinfos( side_t *pDestSides, int numDestSides, const bspbrush_t *pSource ) +{ + for ( int i = 0; i < numDestSides; i++ ) + { + side_t *pSide = &pDestSides[i]; + plane_t *pPlane = &g_MainMap->mapplanes[pSide->planenum]; + + // We have to use the *original sides* because MapBSPBrushList could have generated + // splits when cutting the original brush to the block being processed. This + // will generate faces that use TEXINFO_NODE, which is definitely *not* what we want. + // If we end up with faces using TEXINFO_NODE here, the area portal will flood into + // the entire water volume intersecting the areaportal. + + mapbrush_t *pSourceBrush = pSource->original; + Assert( pSourceBrush ); + + const side_t *pSourceSide = pSourceBrush->original_sides; + const side_t *pBestSide = NULL; + float flBestDot = -1.0f; + for ( int j = 0; j < pSourceBrush->numsides; ++j, ++pSourceSide ) + { + if ( pSourceSide->texinfo == TEXINFO_NODE ) + continue; + + plane_t *pSourcePlane = &g_MainMap->mapplanes[pSourceSide->planenum]; + float flDot = DotProduct( pPlane->normal, pSourcePlane->normal ); + if ( flDot == 1.0f || pSide->planenum == pSourceSide->planenum ) + { + pBestSide = pSourceSide; + break; + } + else if ( flDot > flBestDot ) + { + pBestSide = pSourceSide; + flBestDot = flDot; + } + } + + if ( pBestSide ) + { + pSide->texinfo = pBestSide->texinfo; + if ( pSide->original ) + { + pSide->original->texinfo = pSide->texinfo; + } + } + else + { + texinfo_t *pTexInfo = &texinfo[pSide->texinfo]; + dtexdata_t *pTexData = GetTexData( pTexInfo->texdata ); + Msg("Found no matching plane for %s\n", TexDataStringTable_GetString( pTexData->nameStringTableID ) ); + } + } +} + +// This is a hack to allow areaportals to work in water +// It was done this way for ease of implementation. +// This searches a brush list to find intersecting areaportals and water +// If an areaportal is found inside water, then the water contents and +// texture information is copied over to the areaportal so that the +// resulting space has the same properties as the water (normal areaportals assume "empty" surroundings) +void FixupAreaportalWaterBrushes( bspbrush_t *pList ) +{ + for ( bspbrush_t *pAreaportal = pList; pAreaportal; pAreaportal = pAreaportal->next ) + { + if ( !(pAreaportal->original->contents & CONTENTS_AREAPORTAL) ) + continue; + + for ( bspbrush_t *pWater = pList; pWater; pWater = pWater->next ) + { + // avoid using areaportal/water combo brushes that have already been fixed up + if ( pWater->original->contents & CONTENTS_AREAPORTAL ) + continue; + + if ( !(pWater->original->contents & MASK_SPLITAREAPORTAL) ) + continue; + + if ( BrushesDisjoint( pAreaportal, pWater ) ) + continue; + + bspbrush_t *pIntersect = IntersectBrush( pAreaportal, pWater ); + if ( !pIntersect ) + continue; + FreeBrush( pIntersect ); + pAreaportal->original->contents |= pWater->original->contents; + + // HACKHACK: Ideally, this should have been done before the bspbrush_t was + // created from the map brush. But since it hasn't been, retexture the original map + // brush's sides + CopyMatchingTexinfos( pAreaportal->sides, pAreaportal->numsides, pWater ); + CopyMatchingTexinfos( pAreaportal->original->original_sides, pAreaportal->original->numsides, pWater ); + } + } +} + + +//----------------------------------------------------------------------------- +// MakeBspBrushList +//----------------------------------------------------------------------------- +// UNDONE: Put detail brushes in a separate brush array and pass that instead of "onlyDetail" ? +bspbrush_t *MakeBspBrushList (int startbrush, int endbrush, const Vector& clipmins, const Vector& clipmaxs, int detailScreen) +{ + ComputeBoundingPlanes( clipmins, clipmaxs ); + + bspbrush_t *pBrushList = NULL; + + int i; + for (i=startbrush ; imapbrushes[i]; + if ( detailScreen != FULL_DETAIL ) + { + bool onlyDetail = (detailScreen == ONLY_DETAIL); + bool detail = (mb->contents & CONTENTS_DETAIL) != 0; + if ( onlyDetail ^ detail ) + { + // both of these must have the same value or we're not interested in this brush + continue; + } + } + + bspbrush_t *pNewBrush = CreateClippedBrush( mb, clipmins, clipmaxs ); + if ( pNewBrush ) + { + pNewBrush->next = pBrushList; + pBrushList = pNewBrush; + } + } + + return pBrushList; +} + + +//----------------------------------------------------------------------------- +// A version which uses a passed-in list of brushes +//----------------------------------------------------------------------------- +bspbrush_t *MakeBspBrushList (mapbrush_t **pBrushes, int nBrushCount, const Vector& clipmins, const Vector& clipmaxs) +{ + ComputeBoundingPlanes( clipmins, clipmaxs ); + + bspbrush_t *pBrushList = NULL; + for ( int i=0; i < nBrushCount; ++i ) + { + bspbrush_t *pNewBrush = CreateClippedBrush( pBrushes[i], clipmins, clipmaxs ); + if ( pNewBrush ) + { + pNewBrush->next = pBrushList; + pBrushList = pNewBrush; + } + } + + return pBrushList; +} + + +/* +=============== +AddBspBrushListToTail +=============== +*/ +bspbrush_t *AddBrushListToTail (bspbrush_t *list, bspbrush_t *tail) +{ + bspbrush_t *walk, *next; + + for (walk=list ; walk ; walk=next) + { // add to end of list + next = walk->next; + walk->next = NULL; + tail->next = walk; + tail = walk; + } + + return tail; +} + +/* +=========== +CullList + +Builds a new list that doesn't hold the given brush +=========== +*/ +bspbrush_t *CullList (bspbrush_t *list, bspbrush_t *skip1) +{ + bspbrush_t *newlist; + bspbrush_t *next; + + newlist = NULL; + + for ( ; list ; list = next) + { + next = list->next; + if (list == skip1) + { + FreeBrush (list); + continue; + } + list->next = newlist; + newlist = list; + } + return newlist; +} + + +/* +================== +WriteBrushMap +================== +*/ +void WriteBrushMap (char *name, bspbrush_t *list) +{ + FILE *f; + side_t *s; + int i; + winding_t *w; + + Msg("writing %s\n", name); + f = fopen (name, "w"); + if (!f) + Error ("Can't write %s\b", name); + + fprintf (f, "{\n\"classname\" \"worldspawn\"\n"); + + for ( ; list ; list=list->next ) + { + fprintf (f, "{\n"); + for (i=0,s=list->sides ; inumsides ; i++,s++) + { + w = BaseWindingForPlane (g_MainMap->mapplanes[s->planenum].normal, g_MainMap->mapplanes[s->planenum].dist); + + fprintf (f,"( %i %i %i ) ", (int)w->p[0][0], (int)w->p[0][1], (int)w->p[0][2]); + fprintf (f,"( %i %i %i ) ", (int)w->p[1][0], (int)w->p[1][1], (int)w->p[1][2]); + fprintf (f,"( %i %i %i ) ", (int)w->p[2][0], (int)w->p[2][1], (int)w->p[2][2]); + + fprintf (f, "%s 0 0 0 1 1\n", TexDataStringTable_GetString( GetTexData( texinfo[s->texinfo].texdata )->nameStringTableID ) ); + FreeWinding (w); + } + fprintf (f, "}\n"); + } + fprintf (f, "}\n"); + + fclose (f); + +} + +// UNDONE: This isn't quite working yet +#if 0 +void WriteBrushVMF(char *name, bspbrush_t *list) +{ + FILE *f; + side_t *s; + int i; + winding_t *w; + Vector u, v; + + Msg("writing %s\n", name); + f = fopen (name, "w"); + if (!f) + Error ("Can't write %s\b", name); + + fprintf (f, "world\n{\n\"classname\" \"worldspawn\"\n"); + + for ( ; list ; list=list->next ) + { + fprintf (f, "\tsolid\n\t{\n"); + for (i=0,s=list->sides ; inumsides ; i++,s++) + { + fprintf( f, "\t\tside\n\t\t{\n" ); + fprintf( f, "\t\t\t\"plane\" \"" ); + 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, "\"\n" ); + fprintf( f, "\t\t\t\"material\" \"%s\"\n", GetTexData( texinfo[s->texinfo].texdata )->name ); + // UNDONE: recreate correct texture axes + BasisForPlane( mapplanes[s->planenum].normal, u, v ); + fprintf( f, "\t\t\t\"uaxis\" \"[%.3f %.3f %.3f 0] 1.0\"\n", u[0], u[1], u[2] ); + fprintf( f, "\t\t\t\"vaxis\" \"[%.3f %.3f %.3f 0] 1.0\"\n", v[0], v[1], v[2] ); + + fprintf( f, "\t\t\t\"rotation\" \"0.0\"\n" ); + fprintf( f, "\t\t\t\"lightmapscale\" \"16.0\"\n" ); + + FreeWinding (w); + fprintf (f, "\t\t}\n"); + } + fprintf (f, "\t}\n"); + } + fprintf (f, "}\n"); + + fclose (f); + +} +#endif + +void PrintBrushContentsToString( int contents, char *pOut, int nMaxChars ) +{ + #define ADD_CONTENTS( flag ) \ + if ( contents & flag ) \ + Q_strncat( pOut, #flag " ", nMaxChars, COPY_ALL_CHARACTERS ); + + pOut[0] = 0; + + ADD_CONTENTS(CONTENTS_SOLID) + ADD_CONTENTS(CONTENTS_WINDOW) + ADD_CONTENTS(CONTENTS_AUX) + ADD_CONTENTS(CONTENTS_GRATE) + ADD_CONTENTS(CONTENTS_SLIME) + ADD_CONTENTS(CONTENTS_WATER) + ADD_CONTENTS(CONTENTS_BLOCKLOS) + ADD_CONTENTS(CONTENTS_OPAQUE) + ADD_CONTENTS(CONTENTS_TESTFOGVOLUME) + ADD_CONTENTS(CONTENTS_MOVEABLE) + ADD_CONTENTS(CONTENTS_AREAPORTAL) + ADD_CONTENTS(CONTENTS_PLAYERCLIP) + ADD_CONTENTS(CONTENTS_MONSTERCLIP) + ADD_CONTENTS(CONTENTS_CURRENT_0) + ADD_CONTENTS(CONTENTS_CURRENT_90) + ADD_CONTENTS(CONTENTS_CURRENT_180) + ADD_CONTENTS(CONTENTS_CURRENT_270) + ADD_CONTENTS(CONTENTS_CURRENT_UP) + ADD_CONTENTS(CONTENTS_CURRENT_DOWN) + ADD_CONTENTS(CONTENTS_ORIGIN) + ADD_CONTENTS(CONTENTS_MONSTER) + ADD_CONTENTS(CONTENTS_DEBRIS) + ADD_CONTENTS(CONTENTS_DETAIL) + ADD_CONTENTS(CONTENTS_TRANSLUCENT) + ADD_CONTENTS(CONTENTS_LADDER) + ADD_CONTENTS(CONTENTS_HITBOX) +} + +void PrintBrushContents( int contents ) +{ + char str[1024]; + PrintBrushContentsToString( contents, str, sizeof( str ) ); + Msg( "%s", str ); +} + +/* +================== +BrushGE + +Returns true if b1 is allowed to bite b2 +================== +*/ +qboolean BrushGE (bspbrush_t *b1, bspbrush_t *b2) +{ + // Areaportals are allowed to bite water + slime + // NOTE: This brush combo should have been fixed up + // in a first pass (FixupAreaportalWaterBrushes) + if( (b2->original->contents & MASK_SPLITAREAPORTAL) && + (b1->original->contents & CONTENTS_AREAPORTAL) ) + { + return true; + } + + // detail brushes never bite structural brushes + if ( (b1->original->contents & CONTENTS_DETAIL) + && !(b2->original->contents & CONTENTS_DETAIL) ) + return false; + if (b1->original->contents & CONTENTS_SOLID) + return true; + // Transparent brushes are not marked as detail anymore, so let them cut each other. + if ( (b1->original->contents & TRANSPARENT_CONTENTS) && (b2->original->contents & TRANSPARENT_CONTENTS) ) + return true; + + return false; +} + +/* +================= +ChopBrushes + +Carves any intersecting solid brushes into the minimum number +of non-intersecting brushes. +================= +*/ +bspbrush_t *ChopBrushes (bspbrush_t *head) +{ + bspbrush_t *b1, *b2, *next; + bspbrush_t *tail; + bspbrush_t *keep; + bspbrush_t *sub, *sub2; + int c1, c2; + + qprintf ("---- ChopBrushes ----\n"); + qprintf ("original brushes: %i\n", CountBrushList (head)); + +#if DEBUG_BRUSHMODEL + if (entity_num == DEBUG_BRUSHMODEL) + WriteBrushList ("before.gl", head, false); +#endif + keep = NULL; + +newlist: + // find tail + if (!head) + return NULL; + for (tail=head ; tail->next ; tail=tail->next) + ; + + for (b1=head ; b1 ; b1=next) + { + next = b1->next; + for (b2=b1->next ; b2 ; b2 = b2->next) + { + if (BrushesDisjoint (b1, b2)) + continue; + + sub = NULL; + sub2 = NULL; + c1 = 999999; + c2 = 999999; + + if ( BrushGE (b2, b1) ) + { +// printf( "b2 bites b1\n" ); + sub = SubtractBrush (b1, b2); + if (sub == b1) + continue; // didn't really intersect + if (!sub) + { // b1 is swallowed by b2 + head = CullList (b1, b1); + goto newlist; + } + c1 = CountBrushList (sub); + } + + if ( BrushGE (b1, b2) ) + { +// printf( "b1 bites b2\n" ); + sub2 = SubtractBrush (b2, b1); + if (sub2 == b2) + continue; // didn't really intersect + if (!sub2) + { // b2 is swallowed by b1 + FreeBrushList (sub); + head = CullList (b1, b2); + goto newlist; + } + c2 = CountBrushList (sub2); + } + + if (!sub && !sub2) + continue; // neither one can bite + + // only accept if it didn't fragment + // (commening this out allows full fragmentation) + if (c1 > 1 && c2 > 1) + { + const int contents1 = b1->original->contents; + const int contents2 = b2->original->contents; + // if both detail, allow fragmentation + if ( !((contents1&contents2) & CONTENTS_DETAIL) && !((contents1|contents2) & CONTENTS_AREAPORTAL) ) + { + if (sub2) + FreeBrushList (sub2); + if (sub) + FreeBrushList (sub); + continue; + } + } + + if (c1 < c2) + { + if (sub2) + FreeBrushList (sub2); + tail = AddBrushListToTail (sub, tail); + head = CullList (b1, b1); + goto newlist; + } + else + { + if (sub) + FreeBrushList (sub); + tail = AddBrushListToTail (sub2, tail); + head = CullList (b1, b2); + goto newlist; + } + } + + if (!b2) + { // b1 is no longer intersecting anything, so keep it + b1->next = keep; + keep = b1; + } + } + + qprintf ("output brushes: %i\n", CountBrushList (keep)); +#if DEBUG_BRUSHMODEL + if ( entity_num == DEBUG_BRUSHMODEL ) + { + WriteBrushList ("after.gl", keep, false); + WriteBrushMap ("after.map", keep); + } +#endif + return keep; +} + + diff --git a/mp/src/utils/vbsp/csg.h b/mp/src/utils/vbsp/csg.h new file mode 100644 index 00000000..30436dd9 --- /dev/null +++ b/mp/src/utils/vbsp/csg.h @@ -0,0 +1,32 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef CSG_H +#define CSG_H +#ifdef _WIN32 +#pragma once +#endif + + +// Print a CONTENTS_ mask to a string. +void PrintBrushContentsToString( int contents, char *pOut, int nMaxChars ); + +// Print a CONTENTS_ mask with Msg(). +void PrintBrushContents( int contents ); + +void FixupAreaportalWaterBrushes( bspbrush_t *pList ); + +bspbrush_t *MakeBspBrushList (int startbrush, int endbrush, + const Vector& clipmins, const Vector& clipmaxs, int detailScreen); +bspbrush_t *MakeBspBrushList (mapbrush_t **pBrushes, int nBrushCount, const Vector& clipmins, const Vector& clipmaxs); + +void WriteBrushMap (char *name, bspbrush_t *list); + +bspbrush_t *ChopBrushes (bspbrush_t *head); +bspbrush_t *IntersectBrush (bspbrush_t *a, bspbrush_t *b); +qboolean BrushesDisjoint (bspbrush_t *a, bspbrush_t *b); + +#endif // CSG_H diff --git a/mp/src/utils/vbsp/cubemap.cpp b/mp/src/utils/vbsp/cubemap.cpp new file mode 100644 index 00000000..fda41703 --- /dev/null +++ b/mp/src/utils/vbsp/cubemap.cpp @@ -0,0 +1,995 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "vbsp.h" +#include "bsplib.h" +#include "tier1/UtlBuffer.h" +#include "tier1/utlvector.h" +#include "bitmap/imageformat.h" +#include +#include "tier1/strtools.h" +#include "tier1/utlsymbol.h" +#include "vtf/vtf.h" +#include "materialpatch.h" +#include "materialsystem/imaterialsystem.h" +#include "materialsystem/imaterial.h" +#include "materialsystem/imaterialvar.h" + + +/* + Meager documentation for how the cubemaps are assigned. + + + While loading the map, it calls: + *** Cubemap_SaveBrushSides + Builds a list of what cubemaps manually were assigned to what faces + in s_EnvCubemapToBrushSides. + + Immediately after loading the map, it calls: + *** Cubemap_FixupBrushSidesMaterials + Goes through s_EnvCubemapToBrushSides and does Cubemap_CreateTexInfo for each + side referenced by an env_cubemap manually. + + Then it calls Cubemap_AttachDefaultCubemapToSpecularSides: + *** Cubemap_InitCubemapSideData: + Setup s_aCubemapSideData.bHasEnvMapInMaterial and bManuallyPickedByAnEnvCubemap for each side. + bHasEnvMapInMaterial is set if the side's material has $envmap. + bManuallyPickedByAnEnvCubemap is true if the side was in s_EnvCubemapToBrushSides. + + Then, for each bHasEnvMapInMaterial and !bManuallyPickedByAnEnvCubemap (ie: every specular surface that wasn't + referenced by some env_cubemap), it does Cubemap_CreateTexInfo. +*/ + +struct PatchInfo_t +{ + char *m_pMapName; + int m_pOrigin[3]; +}; + +struct CubemapInfo_t +{ + int m_nTableId; + bool m_bSpecular; +}; + +static bool CubemapLessFunc( const CubemapInfo_t &lhs, const CubemapInfo_t &rhs ) +{ + return ( lhs.m_nTableId < rhs.m_nTableId ); +} + + +typedef CUtlVector IntVector_t; +static CUtlVector s_EnvCubemapToBrushSides; + +static CUtlVector s_DefaultCubemapNames; +static char g_IsCubemapTexData[MAX_MAP_TEXDATA]; + + +struct CubemapSideData_t +{ + bool bHasEnvMapInMaterial; + bool bManuallyPickedByAnEnvCubemap; +}; + +static CubemapSideData_t s_aCubemapSideData[MAX_MAP_BRUSHSIDES]; + + + +inline bool SideHasCubemapAndWasntManuallyReferenced( int iSide ) +{ + return s_aCubemapSideData[iSide].bHasEnvMapInMaterial && !s_aCubemapSideData[iSide].bManuallyPickedByAnEnvCubemap; +} + + +void Cubemap_InsertSample( const Vector& origin, int size ) +{ + dcubemapsample_t *pSample = &g_CubemapSamples[g_nCubemapSamples]; + pSample->origin[0] = ( int )origin[0]; + pSample->origin[1] = ( int )origin[1]; + pSample->origin[2] = ( int )origin[2]; + pSample->size = size; + g_nCubemapSamples++; +} + +static const char *FindSkyboxMaterialName( void ) +{ + for( int i = 0; i < g_MainMap->num_entities; i++ ) + { + char* pEntity = ValueForKey(&g_MainMap->entities[i], "classname"); + if (!strcmp(pEntity, "worldspawn")) + { + return ValueForKey( &g_MainMap->entities[i], "skyname" ); + } + } + return NULL; +} + +static void BackSlashToForwardSlash( char *pname ) +{ + while ( *pname ) { + if ( *pname == '\\' ) + *pname = '/'; + pname++; + } +} + +static void ForwardSlashToBackSlash( char *pname ) +{ + while ( *pname ) { + if ( *pname == '/' ) + *pname = '\\'; + pname++; + } +} + + +//----------------------------------------------------------------------------- +// Finds materials that are used by a particular material +//----------------------------------------------------------------------------- +#define MAX_MATERIAL_NAME 512 + +// This is the list of materialvars which are used in our codebase to look up dependent materials +static const char *s_pDependentMaterialVar[] = +{ + "$bottommaterial", // Used by water materials + "$crackmaterial", // Used by shattered glass materials + "$fallbackmaterial", // Used by all materials + + "", // Always must be last +}; + +static const char *FindDependentMaterial( const char *pMaterialName, const char **ppMaterialVar = NULL ) +{ + // FIXME: This is a terrible way of doing this! It creates a dependency + // between vbsp and *all* code which reads dependent materials from materialvars + // At the time of writing this function, that means the engine + studiorender. + // We need a better way of figuring out how to do this, but for now I'm trying to do + // the fastest solution possible since it's close to ship + + static char pDependentMaterialName[MAX_MATERIAL_NAME]; + for( int i = 0; s_pDependentMaterialVar[i][0]; ++i ) + { + if ( !GetValueFromMaterial( pMaterialName, s_pDependentMaterialVar[i], pDependentMaterialName, MAX_MATERIAL_NAME - 1 ) ) + continue; + + if ( !Q_stricmp( pDependentMaterialName, pMaterialName ) ) + { + Warning( "Material %s is depending on itself through materialvar %s! Ignoring...\n", pMaterialName, s_pDependentMaterialVar[i] ); + continue; + } + + // Return the material var that caused the dependency + if ( ppMaterialVar ) + { + *ppMaterialVar = s_pDependentMaterialVar[i]; + } + +#ifdef _DEBUG + // FIXME: Note that this code breaks if a material has more than 1 dependent material + ++i; + static char pDependentMaterialName2[MAX_MATERIAL_NAME]; + while( s_pDependentMaterialVar[i][0] ) + { + Assert( !GetValueFromMaterial( pMaterialName, s_pDependentMaterialVar[i], pDependentMaterialName2, MAX_MATERIAL_NAME - 1 ) ); + ++i; + } +#endif + + return pDependentMaterialName; + } + + return NULL; +} + + +//----------------------------------------------------------------------------- +// Loads VTF files +//----------------------------------------------------------------------------- +static bool LoadSrcVTFFiles( IVTFTexture *pSrcVTFTextures[6], const char *pSkyboxMaterialBaseName, + int *pUnionTextureFlags, bool bHDR ) +{ + const char *facingName[6] = { "rt", "lf", "bk", "ft", "up", "dn" }; + int i; + for( i = 0; i < 6; i++ ) + { + char srcMaterialName[1024]; + sprintf( srcMaterialName, "%s%s", pSkyboxMaterialBaseName, facingName[i] ); + + IMaterial *pSkyboxMaterial = g_pMaterialSystem->FindMaterial( srcMaterialName, "skybox" ); + //IMaterialVar *pSkyTextureVar = pSkyboxMaterial->FindVar( bHDR ? "$hdrbasetexture" : "$basetexture", NULL ); //, bHDR ? false : true ); + IMaterialVar *pSkyTextureVar = pSkyboxMaterial->FindVar( "$basetexture", NULL ); // Since we're setting it to black anyway, just use $basetexture for HDR + const char *vtfName = pSkyTextureVar->GetStringValue(); + char srcVTFFileName[MAX_PATH]; + Q_snprintf( srcVTFFileName, MAX_PATH, "materials/%s.vtf", vtfName ); + + CUtlBuffer buf; + if ( !g_pFullFileSystem->ReadFile( srcVTFFileName, NULL, buf ) ) + { + // Try looking for a compressed HDR texture + if ( bHDR ) + { + /* // FIXME: We need a way to uncompress this format! + bool bHDRCompressed = true; + + pSkyTextureVar = pSkyboxMaterial->FindVar( "$hdrcompressedTexture", NULL ); + vtfName = pSkyTextureVar->GetStringValue(); + Q_snprintf( srcVTFFileName, MAX_PATH, "materials/%s.vtf", vtfName ); + + if ( !g_pFullFileSystem->ReadFile( srcVTFFileName, NULL, buf ) ) + */ + { + return false; + } + } + else + { + return false; + } + } + + pSrcVTFTextures[i] = CreateVTFTexture(); + if (!pSrcVTFTextures[i]->Unserialize(buf)) + { + Warning("*** Error unserializing skybox texture: %s\n", pSkyboxMaterialBaseName ); + return false; + } + + *pUnionTextureFlags |= pSrcVTFTextures[i]->Flags(); + int flagsNoAlpha = pSrcVTFTextures[i]->Flags() & ~( TEXTUREFLAGS_EIGHTBITALPHA | TEXTUREFLAGS_ONEBITALPHA ); + int flagsFirstNoAlpha = pSrcVTFTextures[0]->Flags() & ~( TEXTUREFLAGS_EIGHTBITALPHA | TEXTUREFLAGS_ONEBITALPHA ); + + // NOTE: texture[0] is a side texture that could be 1/2 height, so allow this and also allow 4x4 faces + if ( ( ( pSrcVTFTextures[i]->Width() != pSrcVTFTextures[0]->Width() ) && ( pSrcVTFTextures[i]->Width() != 4 ) ) || + ( ( pSrcVTFTextures[i]->Height() != pSrcVTFTextures[0]->Height() ) && ( pSrcVTFTextures[i]->Height() != pSrcVTFTextures[0]->Height()*2 ) && ( pSrcVTFTextures[i]->Height() != 4 ) ) || + ( flagsNoAlpha != flagsFirstNoAlpha ) ) + { + Warning("*** Error: Skybox vtf files for %s weren't compiled with the same size texture and/or same flags!\n", pSkyboxMaterialBaseName ); + return false; + } + + if ( bHDR ) + { + pSrcVTFTextures[i]->ConvertImageFormat( IMAGE_FORMAT_RGB323232F, false ); + pSrcVTFTextures[i]->GenerateMipmaps(); + pSrcVTFTextures[i]->ConvertImageFormat( IMAGE_FORMAT_RGBA16161616F, false ); + } + } + + return true; +} + +void VTFNameToHDRVTFName( const char *pSrcName, char *pDest, int maxLen, bool bHDR ) +{ + Q_strncpy( pDest, pSrcName, maxLen ); + if( !bHDR ) + { + return; + } + char *pDot = Q_stristr( pDest, ".vtf" ); + if( !pDot ) + { + return; + } + Q_strncpy( pDot, ".hdr.vtf", maxLen - ( pDot - pDest ) ); +} + +#define DEFAULT_CUBEMAP_SIZE 32 + +void CreateDefaultCubemaps( bool bHDR ) +{ + memset( g_IsCubemapTexData, 0, sizeof(g_IsCubemapTexData) ); + + // NOTE: This implementation depends on the fact that all VTF files contain + // all mipmap levels + const char *pSkyboxBaseName = FindSkyboxMaterialName(); + char skyboxMaterialName[MAX_PATH]; + Q_snprintf( skyboxMaterialName, MAX_PATH, "skybox/%s", pSkyboxBaseName ); + + IVTFTexture *pSrcVTFTextures[6]; + + if( !skyboxMaterialName ) + { + if( s_DefaultCubemapNames.Count() ) + { + Warning( "This map uses env_cubemap, and you don't have a skybox, so no default env_cubemaps will be generated.\n" ); + } + return; + } + + int unionTextureFlags = 0; + if( !LoadSrcVTFFiles( pSrcVTFTextures, skyboxMaterialName, &unionTextureFlags, bHDR ) ) + { + Warning( "Can't load skybox file %s to build the default cubemap!\n", skyboxMaterialName ); + return; + } + Msg( "Creating default %scubemaps for env_cubemap using skybox materials:\n %s*.vmt\n" + " ! Run buildcubemaps in the engine to get the correct cube maps.\n", bHDR ? "HDR " : "LDR ", skyboxMaterialName ); + + // Figure out the mip differences between the two textures + int iMipLevelOffset = 0; + int tmp = pSrcVTFTextures[0]->Width(); + while( tmp > DEFAULT_CUBEMAP_SIZE ) + { + iMipLevelOffset++; + tmp >>= 1; + } + + // Create the destination cubemap + IVTFTexture *pDstCubemap = CreateVTFTexture(); + pDstCubemap->Init( DEFAULT_CUBEMAP_SIZE, DEFAULT_CUBEMAP_SIZE, 1, + pSrcVTFTextures[0]->Format(), unionTextureFlags | TEXTUREFLAGS_ENVMAP, + pSrcVTFTextures[0]->FrameCount() ); + + // First iterate over all frames + for (int iFrame = 0; iFrame < pDstCubemap->FrameCount(); ++iFrame) + { + // Next iterate over all normal cube faces (we know there's 6 cause it's an envmap) + for (int iFace = 0; iFace < 6; ++iFace ) + { + // Finally, iterate over all mip levels in the *destination* + for (int iMip = 0; iMip < pDstCubemap->MipCount(); ++iMip ) + { + // Copy the bits from the source images into the cube faces + unsigned char *pSrcBits = pSrcVTFTextures[iFace]->ImageData( iFrame, 0, iMip + iMipLevelOffset ); + unsigned char *pDstBits = pDstCubemap->ImageData( iFrame, iFace, iMip ); + int iSize = pDstCubemap->ComputeMipSize( iMip ); + int iSrcMipSize = pSrcVTFTextures[iFace]->ComputeMipSize( iMip + iMipLevelOffset ); + + // !!! FIXME: Set this to black until HDR cubemaps are built properly! + memset( pDstBits, 0, iSize ); + continue; + + if ( ( pSrcVTFTextures[iFace]->Width() == 4 ) && ( pSrcVTFTextures[iFace]->Height() == 4 ) ) // If texture is 4x4 square + { + // Force mip level 2 to get the 1x1 face + unsigned char *pSrcBits = pSrcVTFTextures[iFace]->ImageData( iFrame, 0, 2 ); + int iSrcMipSize = pSrcVTFTextures[iFace]->ComputeMipSize( 2 ); + + // Replicate 1x1 mip level across entire face + //memset( pDstBits, 0, iSize ); + for ( int i = 0; i < ( iSize / iSrcMipSize ); i++ ) + { + memcpy( pDstBits + ( i * iSrcMipSize ), pSrcBits, iSrcMipSize ); + } + } + else if ( pSrcVTFTextures[iFace]->Width() == pSrcVTFTextures[iFace]->Height() ) // If texture is square + { + if ( iSrcMipSize != iSize ) + { + Warning( "%s - ERROR! Cannot copy square face for default cubemap! iSrcMipSize(%d) != iSize(%d)\n", skyboxMaterialName, iSrcMipSize, iSize ); + memset( pDstBits, 0, iSize ); + } + else + { + // Just copy the mip level + memcpy( pDstBits, pSrcBits, iSize ); + } + } + else if ( pSrcVTFTextures[iFace]->Width() == pSrcVTFTextures[iFace]->Height()*2 ) // If texture is rectangle 2x wide + { + int iMipWidth, iMipHeight, iMipDepth; + pDstCubemap->ComputeMipLevelDimensions( iMip, &iMipWidth, &iMipHeight, &iMipDepth ); + if ( ( iMipHeight > 1 ) && ( iSrcMipSize*2 != iSize ) ) + { + Warning( "%s - ERROR building default cube map! %d*2 != %d\n", skyboxMaterialName, iSrcMipSize, iSize ); + memset( pDstBits, 0, iSize ); + } + else + { + // Copy row at a time and repeat last row + memcpy( pDstBits, pSrcBits, iSize/2 ); + //memcpy( pDstBits + iSize/2, pSrcBits, iSize/2 ); + int nSrcRowSize = pSrcVTFTextures[iFace]->RowSizeInBytes( iMip + iMipLevelOffset ); + int nDstRowSize = pDstCubemap->RowSizeInBytes( iMip ); + if ( nSrcRowSize != nDstRowSize ) + { + Warning( "%s - ERROR building default cube map! nSrcRowSize(%d) != nDstRowSize(%d)!\n", skyboxMaterialName, nSrcRowSize, nDstRowSize ); + memset( pDstBits, 0, iSize ); + } + else + { + for ( int i = 0; i < ( iSize/2 / nSrcRowSize ); i++ ) + { + memcpy( pDstBits + iSize/2 + i*nSrcRowSize, pSrcBits + iSrcMipSize - nSrcRowSize, nSrcRowSize ); + } + } + } + } + else + { + // ERROR! This code only supports square and rectangluar 2x wide + Warning( "%s - Couldn't create default cubemap because texture res is %dx%d\n", skyboxMaterialName, pSrcVTFTextures[iFace]->Width(), pSrcVTFTextures[iFace]->Height() ); + memset( pDstBits, 0, iSize ); + return; + } + } + } + } + + ImageFormat originalFormat = pDstCubemap->Format(); + if( !bHDR ) + { + // Convert the cube to format that we can apply tools to it... + pDstCubemap->ConvertImageFormat( IMAGE_FORMAT_DEFAULT, false ); + } + + // Fixup the cubemap facing + pDstCubemap->FixCubemapFaceOrientation(); + + // Now that the bits are in place, compute the spheremaps... + pDstCubemap->GenerateSpheremap(); + + if( !bHDR ) + { + // Convert the cubemap to the final format + pDstCubemap->ConvertImageFormat( originalFormat, false ); + } + + // Write the puppy out! + char dstVTFFileName[1024]; + if( bHDR ) + { + sprintf( dstVTFFileName, "materials/maps/%s/cubemapdefault.hdr.vtf", mapbase ); + } + else + { + sprintf( dstVTFFileName, "materials/maps/%s/cubemapdefault.vtf", mapbase ); + } + + CUtlBuffer outputBuf; + if (!pDstCubemap->Serialize( outputBuf )) + { + Warning( "Error serializing default cubemap %s\n", dstVTFFileName ); + return; + } + + IZip *pak = GetPakFile(); + + // spit out the default one. + AddBufferToPak( pak, dstVTFFileName, outputBuf.Base(), outputBuf.TellPut(), false ); + + // spit out all of the ones that are attached to world geometry. + int i; + for( i = 0; i < s_DefaultCubemapNames.Count(); i++ ) + { + char vtfName[MAX_PATH]; + VTFNameToHDRVTFName( s_DefaultCubemapNames[i], vtfName, MAX_PATH, bHDR ); + if( FileExistsInPak( pak, vtfName ) ) + { + continue; + } + AddBufferToPak( pak, vtfName, outputBuf.Base(),outputBuf.TellPut(), false ); + } + + // Clean up the textures + for( i = 0; i < 6; i++ ) + { + DestroyVTFTexture( pSrcVTFTextures[i] ); + } + DestroyVTFTexture( pDstCubemap ); +} + +void Cubemap_CreateDefaultCubemaps( void ) +{ + CreateDefaultCubemaps( false ); + CreateDefaultCubemaps( true ); +} + +// Builds a list of what cubemaps manually were assigned to what faces +// in s_EnvCubemapToBrushSides. +void Cubemap_SaveBrushSides( const char *pSideListStr ) +{ + IntVector_t &brushSidesVector = s_EnvCubemapToBrushSides[s_EnvCubemapToBrushSides.AddToTail()]; + char *pTmp = ( char * )_alloca( strlen( pSideListStr ) + 1 ); + strcpy( pTmp, pSideListStr ); + const char *pScan = strtok( pTmp, " " ); + if( !pScan ) + { + return; + } + do + { + int brushSideID; + if( sscanf( pScan, "%d", &brushSideID ) == 1 ) + { + brushSidesVector.AddToTail( brushSideID ); + } + } while( ( pScan = strtok( NULL, " " ) ) ); +} + + +//----------------------------------------------------------------------------- +// Generate patched material name +//----------------------------------------------------------------------------- +static void GeneratePatchedName( const char *pMaterialName, const PatchInfo_t &info, bool bMaterialName, char *pBuffer, int nMaxLen ) +{ + const char *pSeparator = bMaterialName ? "_" : ""; + int nLen = Q_snprintf( pBuffer, nMaxLen, "maps/%s/%s%s%d_%d_%d", info.m_pMapName, + pMaterialName, pSeparator, info.m_pOrigin[0], info.m_pOrigin[1], info.m_pOrigin[2] ); + + if ( bMaterialName ) + { + Assert( nLen < TEXTURE_NAME_LENGTH - 1 ); + if ( nLen >= TEXTURE_NAME_LENGTH - 1 ) + { + Error( "Generated env_cubemap patch name : %s too long! (max = %d)\n", pBuffer, TEXTURE_NAME_LENGTH ); + } + } + + BackSlashToForwardSlash( pBuffer ); + Q_strlower( pBuffer ); +} + + +//----------------------------------------------------------------------------- +// Patches the $envmap for a material and all its dependents, returns true if any patching happened +//----------------------------------------------------------------------------- +static bool PatchEnvmapForMaterialAndDependents( const char *pMaterialName, const PatchInfo_t &info, const char *pCubemapTexture ) +{ + // Do *NOT* patch the material if there is an $envmap specified and it's not 'env_cubemap' + + // FIXME: It's theoretically ok to patch the material if $envmap is not specified, + // because we're using the 'replace' block, which will only add the env_cubemap if + // $envmap is specified in the source material. But it will fail if someone adds + // a specific non-env_cubemap $envmap to the source material at a later point. Bleah + + // See if we have an $envmap to patch + bool bShouldPatchEnvCubemap = DoesMaterialHaveKeyValuePair( pMaterialName, "$envmap", "env_cubemap" ); + + // See if we have a dependent material to patch + bool bDependentMaterialPatched = false; + const char *pDependentMaterialVar = NULL; + const char *pDependentMaterial = FindDependentMaterial( pMaterialName, &pDependentMaterialVar ); + if ( pDependentMaterial ) + { + bDependentMaterialPatched = PatchEnvmapForMaterialAndDependents( pDependentMaterial, info, pCubemapTexture ); + } + + // If we have neither to patch, we're done + if ( !bShouldPatchEnvCubemap && !bDependentMaterialPatched ) + return false; + + // Otherwise we have to make a patched version of ourselves + char pPatchedMaterialName[1024]; + GeneratePatchedName( pMaterialName, info, true, pPatchedMaterialName, 1024 ); + + MaterialPatchInfo_t pPatchInfo[2]; + int nPatchCount = 0; + if ( bShouldPatchEnvCubemap ) + { + pPatchInfo[nPatchCount].m_pKey = "$envmap"; + pPatchInfo[nPatchCount].m_pRequiredOriginalValue = "env_cubemap"; + pPatchInfo[nPatchCount].m_pValue = pCubemapTexture; + ++nPatchCount; + } + + char pDependentPatchedMaterialName[1024]; + if ( bDependentMaterialPatched ) + { + // FIXME: Annoying! I either have to pass back the patched dependent material name + // or reconstruct it. Both are sucky. + GeneratePatchedName( pDependentMaterial, info, true, pDependentPatchedMaterialName, 1024 ); + pPatchInfo[nPatchCount].m_pKey = pDependentMaterialVar; + pPatchInfo[nPatchCount].m_pValue = pDependentPatchedMaterialName; + ++nPatchCount; + } + + CreateMaterialPatch( pMaterialName, pPatchedMaterialName, nPatchCount, pPatchInfo, PATCH_REPLACE ); + + return true; +} + + +//----------------------------------------------------------------------------- +// Finds a texinfo that has a particular +//----------------------------------------------------------------------------- + + +//----------------------------------------------------------------------------- +// Create a VMT to override the specified texinfo which references the cubemap entity at the specified origin. +// Returns the index of the new (or preexisting) texinfo referencing that VMT. +// +// Also adds the new cubemap VTF filename to s_DefaultCubemapNames so it can copy the +// default (skybox) cubemap into this file so the cubemap doesn't have the pink checkerboard at +// runtime before they run buildcubemaps. +//----------------------------------------------------------------------------- +static int Cubemap_CreateTexInfo( int originalTexInfo, int origin[3] ) +{ + // Don't make cubemap tex infos for nodes + if ( originalTexInfo == TEXINFO_NODE ) + return originalTexInfo; + + texinfo_t *pTexInfo = &texinfo[originalTexInfo]; + dtexdata_t *pTexData = GetTexData( pTexInfo->texdata ); + const char *pMaterialName = TexDataStringTable_GetString( pTexData->nameStringTableID ); + if ( g_IsCubemapTexData[pTexInfo->texdata] ) + { + Warning("Multiple references for cubemap on texture %s!!!\n", pMaterialName ); + return originalTexInfo; + } + + // Get out of here if the originalTexInfo is already a generated material for this position. + char pStringToSearchFor[512]; + Q_snprintf( pStringToSearchFor, 512, "_%d_%d_%d", origin[0], origin[1], origin[2] ); + if ( Q_stristr( pMaterialName, pStringToSearchFor ) ) + return originalTexInfo; + + // Package up information needed to generate patch names + PatchInfo_t info; + info.m_pMapName = mapbase; + info.m_pOrigin[0] = origin[0]; + info.m_pOrigin[1] = origin[1]; + info.m_pOrigin[2] = origin[2]; + + // Generate the name of the patched material + char pGeneratedTexDataName[1024]; + GeneratePatchedName( pMaterialName, info, true, pGeneratedTexDataName, 1024 ); + + // Make sure the texdata doesn't already exist. + int nTexDataID = FindTexData( pGeneratedTexDataName ); + bool bHasTexData = (nTexDataID != -1); + if( !bHasTexData ) + { + // Generate the new "$envmap" texture name. + char pTextureName[1024]; + GeneratePatchedName( "c", info, false, pTextureName, 1024 ); + + // Hook the texture into the material and all dependent materials + // but if no hooking was necessary, exit out + if ( !PatchEnvmapForMaterialAndDependents( pMaterialName, info, pTextureName ) ) + return originalTexInfo; + + // Store off the name of the cubemap that we need to create since we successfully patched + char pFileName[1024]; + int nLen = Q_snprintf( pFileName, 1024, "materials/%s.vtf", pTextureName ); + int id = s_DefaultCubemapNames.AddToTail(); + s_DefaultCubemapNames[id] = new char[ nLen + 1 ]; + strcpy( s_DefaultCubemapNames[id], pFileName ); + + // Make a new texdata + nTexDataID = AddCloneTexData( pTexData, pGeneratedTexDataName ); + g_IsCubemapTexData[nTexDataID] = true; + } + + Assert( nTexDataID != -1 ); + + texinfo_t newTexInfo; + newTexInfo = *pTexInfo; + newTexInfo.texdata = nTexDataID; + + int nTexInfoID = -1; + + // See if we need to make a new texinfo + bool bHasTexInfo = false; + if( bHasTexData ) + { + nTexInfoID = FindTexInfo( newTexInfo ); + bHasTexInfo = (nTexInfoID != -1); + } + + // Make a new texinfo if we need to. + if( !bHasTexInfo ) + { + nTexInfoID = texinfo.AddToTail( newTexInfo ); + } + + Assert( nTexInfoID != -1 ); + return nTexInfoID; +} + +static int SideIDToIndex( int brushSideID ) +{ + int i; + for( i = 0; i < g_MainMap->nummapbrushsides; i++ ) + { + if( g_MainMap->brushsides[i].id == brushSideID ) + { + return i; + } + } + return -1; +} + + +//----------------------------------------------------------------------------- +// Goes through s_EnvCubemapToBrushSides and does Cubemap_CreateTexInfo for each +// side referenced by an env_cubemap manually. +//----------------------------------------------------------------------------- +void Cubemap_FixupBrushSidesMaterials( void ) +{ + Msg( "fixing up env_cubemap materials on brush sides...\n" ); + Assert( s_EnvCubemapToBrushSides.Count() == g_nCubemapSamples ); + + int cubemapID; + for( cubemapID = 0; cubemapID < g_nCubemapSamples; cubemapID++ ) + { + IntVector_t &brushSidesVector = s_EnvCubemapToBrushSides[cubemapID]; + int i; + for( i = 0; i < brushSidesVector.Count(); i++ ) + { + int brushSideID = brushSidesVector[i]; + int sideIndex = SideIDToIndex( brushSideID ); + if( sideIndex < 0 ) + { + Warning("env_cubemap pointing at deleted brushside near (%d, %d, %d)\n", + g_CubemapSamples[cubemapID].origin[0], g_CubemapSamples[cubemapID].origin[1], g_CubemapSamples[cubemapID].origin[2] ); + + continue; + } + + side_t *pSide = &g_MainMap->brushsides[sideIndex]; + +#ifdef DEBUG + if ( pSide->pMapDisp ) + { + Assert( pSide->texinfo == pSide->pMapDisp->face.texinfo ); + } +#endif + + pSide->texinfo = Cubemap_CreateTexInfo( pSide->texinfo, g_CubemapSamples[cubemapID].origin ); + if ( pSide->pMapDisp ) + { + pSide->pMapDisp->face.texinfo = pSide->texinfo; + } + } + } +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void Cubemap_ResetCubemapSideData( void ) +{ + for ( int iSide = 0; iSide < MAX_MAP_BRUSHSIDES; ++iSide ) + { + s_aCubemapSideData[iSide].bHasEnvMapInMaterial = false; + s_aCubemapSideData[iSide].bManuallyPickedByAnEnvCubemap = false; + } +} + + +//----------------------------------------------------------------------------- +// Returns true if the material or any of its dependents use an $envmap +//----------------------------------------------------------------------------- +bool DoesMaterialOrDependentsUseEnvmap( const char *pPatchedMaterialName ) +{ + const char *pOriginalMaterialName = GetOriginalMaterialNameForPatchedMaterial( pPatchedMaterialName ); + if( DoesMaterialHaveKey( pOriginalMaterialName, "$envmap" ) ) + return true; + + const char *pDependentMaterial = FindDependentMaterial( pOriginalMaterialName ); + if ( !pDependentMaterial ) + return false; + + return DoesMaterialOrDependentsUseEnvmap( pDependentMaterial ); +} + + +//----------------------------------------------------------------------------- +// Builds a list of all texdatas which need fixing up +//----------------------------------------------------------------------------- +void Cubemap_InitCubemapSideData( void ) +{ + // This tree is used to prevent re-parsing material vars multiple times + CUtlRBTree lookup( 0, g_MainMap->nummapbrushsides, CubemapLessFunc ); + + // Fill in specular data. + for ( int iSide = 0; iSide < g_MainMap->nummapbrushsides; ++iSide ) + { + side_t *pSide = &g_MainMap->brushsides[iSide]; + if ( !pSide ) + continue; + + if ( pSide->texinfo == TEXINFO_NODE ) + continue; + + texinfo_t *pTex = &texinfo[pSide->texinfo]; + if ( !pTex ) + continue; + + dtexdata_t *pTexData = GetTexData( pTex->texdata ); + if ( !pTexData ) + continue; + + CubemapInfo_t info; + info.m_nTableId = pTexData->nameStringTableID; + + // Have we encountered this materal? If so, then copy the data we cached off before + int i = lookup.Find( info ); + if ( i != lookup.InvalidIndex() ) + { + s_aCubemapSideData[iSide].bHasEnvMapInMaterial = lookup[i].m_bSpecular; + continue; + } + + // First time we've seen this material. Figure out if it uses env_cubemap + const char *pPatchedMaterialName = TexDataStringTable_GetString( pTexData->nameStringTableID ); + info.m_bSpecular = DoesMaterialOrDependentsUseEnvmap( pPatchedMaterialName ); + s_aCubemapSideData[ iSide ].bHasEnvMapInMaterial = info.m_bSpecular; + lookup.Insert( info ); + } + + // Fill in cube map data. + for ( int iCubemap = 0; iCubemap < g_nCubemapSamples; ++iCubemap ) + { + IntVector_t &sideList = s_EnvCubemapToBrushSides[iCubemap]; + int nSideCount = sideList.Count(); + for ( int iSide = 0; iSide < nSideCount; ++iSide ) + { + int nSideID = sideList[iSide]; + int nIndex = SideIDToIndex( nSideID ); + if ( nIndex < 0 ) + continue; + + s_aCubemapSideData[nIndex].bManuallyPickedByAnEnvCubemap = true; + } + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +int Cubemap_FindClosestCubemap( const Vector &entityOrigin, side_t *pSide ) +{ + if ( !pSide ) + return -1; + + // Return a valid (if random) cubemap if there's no winding + if ( !pSide->winding ) + return 0; + + // Calculate the center point. + Vector vecCenter; + vecCenter.Init(); + + for ( int iPoint = 0; iPoint < pSide->winding->numpoints; ++iPoint ) + { + VectorAdd( vecCenter, pSide->winding->p[iPoint], vecCenter ); + } + VectorScale( vecCenter, 1.0f / pSide->winding->numpoints, vecCenter ); + vecCenter += entityOrigin; + plane_t *pPlane = &g_MainMap->mapplanes[pSide->planenum]; + + // Find the closest cubemap. + int iMinCubemap = -1; + float flMinDist = FLT_MAX; + + // Look for cubemaps in front of the surface first. + for ( int iCubemap = 0; iCubemap < g_nCubemapSamples; ++iCubemap ) + { + dcubemapsample_t *pSample = &g_CubemapSamples[iCubemap]; + Vector vecSampleOrigin( static_cast( pSample->origin[0] ), + static_cast( pSample->origin[1] ), + static_cast( pSample->origin[2] ) ); + Vector vecDelta; + VectorSubtract( vecSampleOrigin, vecCenter, vecDelta ); + float flDist = vecDelta.NormalizeInPlace(); + float flDot = DotProduct( vecDelta, pPlane->normal ); + if ( ( flDot >= 0.0f ) && ( flDist < flMinDist ) ) + { + flMinDist = flDist; + iMinCubemap = iCubemap; + } + } + + // Didn't find anything in front search for closest. + if( iMinCubemap == -1 ) + { + for ( int iCubemap = 0; iCubemap < g_nCubemapSamples; ++iCubemap ) + { + dcubemapsample_t *pSample = &g_CubemapSamples[iCubemap]; + Vector vecSampleOrigin( static_cast( pSample->origin[0] ), + static_cast( pSample->origin[1] ), + static_cast( pSample->origin[2] ) ); + Vector vecDelta; + VectorSubtract( vecSampleOrigin, vecCenter, vecDelta ); + float flDist = vecDelta.Length(); + if ( flDist < flMinDist ) + { + flMinDist = flDist; + iMinCubemap = iCubemap; + } + } + } + + return iMinCubemap; +} + + +//----------------------------------------------------------------------------- +// For every specular surface that wasn't referenced by some env_cubemap, call Cubemap_CreateTexInfo. +//----------------------------------------------------------------------------- +void Cubemap_AttachDefaultCubemapToSpecularSides( void ) +{ + Cubemap_ResetCubemapSideData(); + Cubemap_InitCubemapSideData(); + + // build a mapping from side to entity id so that we can get the entity origin + CUtlVector sideToEntityIndex; + sideToEntityIndex.SetCount(g_MainMap->nummapbrushsides); + int i; + for ( i = 0; i < g_MainMap->nummapbrushsides; i++ ) + { + sideToEntityIndex[i] = -1; + } + + for ( i = 0; i < g_MainMap->nummapbrushes; i++ ) + { + int entityIndex = g_MainMap->mapbrushes[i].entitynum; + for ( int j = 0; j < g_MainMap->mapbrushes[i].numsides; j++ ) + { + side_t *side = &g_MainMap->mapbrushes[i].original_sides[j]; + int sideIndex = side - g_MainMap->brushsides; + sideToEntityIndex[sideIndex] = entityIndex; + } + } + + for ( int iSide = 0; iSide < g_MainMap->nummapbrushsides; ++iSide ) + { + side_t *pSide = &g_MainMap->brushsides[iSide]; + if ( !SideHasCubemapAndWasntManuallyReferenced( iSide ) ) + continue; + + + int currentEntity = sideToEntityIndex[iSide]; + + int iCubemap = Cubemap_FindClosestCubemap( g_MainMap->entities[currentEntity].origin, pSide ); + if ( iCubemap == -1 ) + continue; + +#ifdef DEBUG + if ( pSide->pMapDisp ) + { + Assert( pSide->texinfo == pSide->pMapDisp->face.texinfo ); + } +#endif + pSide->texinfo = Cubemap_CreateTexInfo( pSide->texinfo, g_CubemapSamples[iCubemap].origin ); + if ( pSide->pMapDisp ) + { + pSide->pMapDisp->face.texinfo = pSide->texinfo; + } + } +} + +// Populate with cubemaps that were skipped +void Cubemap_AddUnreferencedCubemaps() +{ + char pTextureName[1024]; + char pFileName[1024]; + PatchInfo_t info; + dcubemapsample_t *pSample; + int i,j; + + for ( i=0; iorigin[0]; + info.m_pOrigin[1] = pSample->origin[1]; + info.m_pOrigin[2] = pSample->origin[2]; + GeneratePatchedName( "c", info, false, pTextureName, 1024 ); + + // find or add + for ( j=0; j + +face_t *NewFaceFromFace (face_t *f); +face_t *ComputeVisibleBrushSides( bspbrush_t *list ); + +//----------------------------------------------------------------------------- +// Purpose: Copies a face and its winding +// Input : *pFace - +// Output : face_t +//----------------------------------------------------------------------------- +face_t *CopyFace( face_t *pFace ) +{ + face_t *f = NewFaceFromFace( pFace ); + f->w = CopyWinding( pFace->w ); + + return f; +} + +//----------------------------------------------------------------------------- +// Purpose: Link this brush into the list for this leaf +// Input : *node - +// *brush - +//----------------------------------------------------------------------------- +void AddBrushToLeaf( node_t *node, bspbrush_t *brush ) +{ + brush->next = node->brushlist; + node->brushlist = brush; +} + +//----------------------------------------------------------------------------- +// Purpose: Recursively filter a brush through the tree +// Input : *node - +// *brush - +//----------------------------------------------------------------------------- +void MergeBrush_r( node_t *node, bspbrush_t *brush ) +{ + if ( node->planenum == PLANENUM_LEAF ) + { + if ( node->contents & CONTENTS_SOLID ) + { + FreeBrush( brush ); + } + else + { + AddBrushToLeaf( node, brush ); + } + return; + } + + bspbrush_t *front, *back; + SplitBrush( brush, node->planenum, &front, &back ); + FreeBrush( brush ); + + if ( front ) + { + MergeBrush_r( node->children[0], front ); + } + if ( back ) + { + MergeBrush_r( node->children[1], back ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Recursively filter a face into the tree leaving references to the +// original face in any visible leaves that a clipped fragment falls +// into. +// Input : *node - current head of tree +// *face - clipped face fragment +// *original - unclipped original face +// Output : Returns true if any references were left +//----------------------------------------------------------------------------- +bool MergeFace_r( node_t *node, face_t *face, face_t *original ) +{ + bool referenced = false; + + if ( node->planenum == PLANENUM_LEAF ) + { + if ( node->contents & CONTENTS_SOLID ) + { + FreeFace( face ); + return false; + } + + leafface_t *plist = new leafface_t; + plist->pFace = original; + plist->pNext = node->leaffacelist; + node->leaffacelist = plist; + + referenced = true; + } + else + { + // UNDONE: Don't copy the faces each time unless it's necessary!?!?! + plane_t *plane = &g_MainMap->mapplanes[node->planenum]; + winding_t *frontwinding, *backwinding, *onwinding; + + Vector offset; + WindingCenter( face->w, offset ); + + // UNDONE: Export epsilon from original face clipping code + ClassifyWindingEpsilon_Offset(face->w, plane->normal, plane->dist, 0.001, &frontwinding, &backwinding, &onwinding, -offset); + + if ( onwinding ) + { + // face is in the split plane, go down the appropriate side according to the facing direction + assert( frontwinding == NULL ); + assert( backwinding == NULL ); + + if ( DotProduct( g_MainMap->mapplanes[face->planenum].normal, g_MainMap->mapplanes[node->planenum].normal ) > 0 ) + { + frontwinding = onwinding; + } + else + { + backwinding = onwinding; + } + } + + if ( frontwinding ) + { + face_t *tmp = NewFaceFromFace( face ); + tmp->w = frontwinding; + referenced = MergeFace_r( node->children[0], tmp, original ); + } + if ( backwinding ) + { + face_t *tmp = NewFaceFromFace( face ); + tmp->w = backwinding; + bool test = MergeFace_r( node->children[1], tmp, original ); + referenced = referenced || test; + } + } + FreeFace( face ); + + return referenced; +} + +//----------------------------------------------------------------------------- +// Purpose: Loop through each face and filter it into the tree +// Input : *out - +// *pFaces - +//----------------------------------------------------------------------------- +face_t *FilterFacesIntoTree( tree_t *out, face_t *pFaces ) +{ + face_t *pLeafFaceList = NULL; + for ( face_t *f = pFaces; f; f = f->next ) + { + if( f->merged || f->split[0] || f->split[1] ) + continue; + + face_t *tmp = CopyFace( f ); + face_t *original = CopyFace( f ); + + if ( MergeFace_r( out->headnode, tmp, original ) ) + { + // clear out portal (comes from a different tree) + original->portal = NULL; + original->next = pLeafFaceList; + pLeafFaceList = original; + } + else + { + FreeFace( original ); + } + } + + return pLeafFaceList; +} + + +//----------------------------------------------------------------------------- +// Purpose: Splits the face list into faces from the same plane and tries to merge +// them if possible +// Input : **pFaceList - +//----------------------------------------------------------------------------- +void TryMergeFaceList( face_t **pFaceList ) +{ + face_t **pPlaneList = NULL; + + // divide the list into buckets by plane number + pPlaneList = new face_t *[g_MainMap->nummapplanes]; + memset( pPlaneList, 0, sizeof(face_t *) * g_MainMap->nummapplanes ); + + face_t *pFaces = *pFaceList; + face_t *pOutput = NULL; + + while ( pFaces ) + { + face_t *next = pFaces->next; + + // go ahead and delete the old split/merged faces + if ( pFaces->merged || pFaces->split[0] || pFaces->split[1] ) + { + Error("Split face in merge list!"); + } + else + { + // add to the list for this plane + pFaces->next = pPlaneList[pFaces->planenum]; + pPlaneList[pFaces->planenum] = pFaces; + } + + pFaces = next; + } + + // now merge each plane's list of faces + int merged = 0; + for ( int i = 0; i < g_MainMap->nummapplanes; i++ ) + { + if ( pPlaneList[i] ) + { + MergeFaceList( &pPlaneList[i] ); + } + + // move these over to the output face list + face_t *list = pPlaneList[i]; + while ( list ) + { + face_t *next = list->next; + + if ( list->merged ) + merged++; + + list->next = pOutput; + pOutput = list; + list = next; + } + } + + if ( merged ) + { + Msg("\nMerged %d detail faces...", merged ); + } + delete[] pPlaneList; + + *pFaceList = pOutput; +} + + +//----------------------------------------------------------------------------- +// Purpose: filter each brush in the list into the tree +// Input : *out - +// *brushes - +//----------------------------------------------------------------------------- +void FilterBrushesIntoTree( tree_t *out, bspbrush_t *brushes ) +{ + // Merge all of the brushes into the world tree + for ( bspbrush_t *plist = brushes; plist; plist = plist->next ) + { + MergeBrush_r( out->headnode, CopyBrush(plist) ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Build faces for the detail brushes and merge them into the BSP +// Input : *worldtree - +// brush_start - +// brush_end - +//----------------------------------------------------------------------------- +face_t *MergeDetailTree( tree_t *worldtree, int brush_start, int brush_end ) +{ + int start; + bspbrush_t *detailbrushes = NULL; + face_t *pFaces = NULL; + face_t *pLeafFaceList = NULL; + + // Grab the list of detail brushes + detailbrushes = MakeBspBrushList (brush_start, brush_end, g_MainMap->map_mins, g_MainMap->map_maxs, ONLY_DETAIL ); + if (detailbrushes) + { + start = Plat_FloatTime(); + Msg("Chop Details..."); + // if there are detail brushes, chop them against each other + if (!nocsg) + detailbrushes = ChopBrushes (detailbrushes); + + Msg("done (%d)\n", (int)(Plat_FloatTime() - start) ); + // Now mark the visible sides so we can eliminate all detail brush sides + // that are covered by other detail brush sides + // NOTE: This still leaves detail brush sides that are covered by the world. (these are removed in the merge operation) + Msg("Find Visible Detail Sides..."); + pFaces = ComputeVisibleBrushSides( detailbrushes ); + TryMergeFaceList( &pFaces ); + SubdivideFaceList( &pFaces ); + Msg("done (%d)\n", (int)(Plat_FloatTime() - start) ); + + start = Plat_FloatTime(); + Msg("Merging details..."); + // Merge the detail solids and faces into the world tree + // Merge all of the faces into the world tree + pLeafFaceList = FilterFacesIntoTree( worldtree, pFaces ); + FilterBrushesIntoTree( worldtree, detailbrushes ); + + FreeFaceList( pFaces ); + FreeBrushList(detailbrushes); + + Msg("done (%d)\n", (int)(Plat_FloatTime() - start) ); + } + + return pLeafFaceList; +} + + +//----------------------------------------------------------------------------- +// Purpose: Quick overlap test for brushes +// Input : *p1 - +// *p2 - +// Output : Returns false if the brushes cannot intersect +//----------------------------------------------------------------------------- +bool BrushBoxOverlap( bspbrush_t *p1, bspbrush_t *p2 ) +{ + if ( p1 == p2 ) + return false; + + for ( int i = 0; i < 3; i++ ) + { + if ( p1->mins[i] > p2->maxs[i] || p1->maxs[i] < p2->mins[i] ) + return false; + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pFace - input face to test +// *pbrush - brush to clip face against +// **pOutputList - list of faces clipped from pFace +// Output : Returns true if the brush completely clips the face +//----------------------------------------------------------------------------- +// NOTE: This assumes the brushes have already been chopped so that no solid space +// is enclosed by more than one brush!! +bool ClipFaceToBrush( face_t *pFace, bspbrush_t *pbrush, face_t **pOutputList ) +{ + int planenum = pFace->planenum & (~1); + int foundSide = -1; + + CUtlVector sortedSides; + + int i; + for ( i = 0; i < pbrush->numsides && foundSide < 0; i++ ) + { + int bplane = pbrush->sides[i].planenum & (~1); + if ( bplane == planenum ) + foundSide = i; + } + + Vector offset = -0.5f * (pbrush->maxs + pbrush->mins); + face_t *currentface = CopyFace( pFace ); + + if ( foundSide >= 0 ) + { + sortedSides.RemoveAll(); + for ( i = 0; i < pbrush->numsides; i++ ) + { + // don't clip to bevels + if ( pbrush->sides[i].bevel ) + continue; + + if ( g_MainMap->mapplanes[pbrush->sides[i].planenum].type <= PLANE_Z ) + { + sortedSides.AddToHead( i ); + } + else + { + sortedSides.AddToTail( i ); + } + } + + for ( i = 0; i < sortedSides.Size(); i++ ) + { + int index = sortedSides[i]; + if ( index == foundSide ) + continue; + + plane_t *plane = &g_MainMap->mapplanes[pbrush->sides[index].planenum]; + winding_t *frontwinding, *backwinding; + ClipWindingEpsilon_Offset(currentface->w, plane->normal, plane->dist, 0.001, &frontwinding, &backwinding, offset); + + // only clip if some part of this face is on the back side of all brush sides + if ( !backwinding || WindingIsTiny(backwinding)) + { + FreeFaceList( *pOutputList ); + *pOutputList = NULL; + break; + } + if ( frontwinding && !WindingIsTiny(frontwinding) ) + { + // add this fragment to the return list + // make a face for the fragment + face_t *f = NewFaceFromFace( pFace ); + f->w = frontwinding; + + // link the fragment in + f->next = *pOutputList; + *pOutputList = f; + } + + // update the current winding to be the part behind each plane + FreeWinding( currentface->w ); + currentface->w = backwinding; + } + + // free the bit that is left in solid or not clipped (if we broke out early) + FreeFace( currentface ); + + // if we made it all the way through and didn't produce any fragments then the whole face was clipped away + if ( !*pOutputList && i == sortedSides.Size() ) + { + return true; + } + } + return false; +} + + +//----------------------------------------------------------------------------- +// Purpose: Given an original side and chopped winding, make a face_t +// Input : *side - side of the original brush +// *winding - winding for this face (portion of the side) +// Output : face_t +//----------------------------------------------------------------------------- +face_t *MakeBrushFace( side_t *originalSide, winding_t *winding ) +{ + face_t *f = AllocFace(); + f->merged = NULL; + f->split[0] = f->split[1] = NULL; + f->w = CopyWinding( winding ); + f->originalface = originalSide; + // + // save material info + // + f->texinfo = originalSide->texinfo; + f->dispinfo = -1; + + // save plane info + f->planenum = originalSide->planenum; + f->contents = originalSide->contents; + + return f; +} + + +//----------------------------------------------------------------------------- +// Purpose: Chop away sides that are inside other brushes. +// Brushes have already been chopped up so that they do not overlap, +// they merely touch. +// Input : *list - list of brushes +// Output : face_t * - list of visible faces (some marked bad/split) +//----------------------------------------------------------------------------- +// assumes brushes were chopped! + + +side_t *FindOriginalSide( mapbrush_t *mb, side_t *pBspSide ) +{ + side_t *bestside = NULL; + float bestdot = 0; + + plane_t *p1 = g_MainMap->mapplanes + pBspSide->planenum; + + for (int i=0 ; inumsides ; i++) + { + side_t *side = &mb->original_sides[i]; + if (side->bevel) + continue; + if (side->texinfo == TEXINFO_NODE) + continue; // non-visible + if ((side->planenum&~1) == (pBspSide->planenum&~1)) + { // exact match + return mb->original_sides + i; + } + // see how close the match is + plane_t *p2 = &g_MainMap->mapplanes[side->planenum&~1]; + float dot = DotProduct (p1->normal, p2->normal); + if (dot > bestdot) + { + bestdot = dot; + bestside = side; + } + } + + if ( !bestside ) + { + Error( "Bad detail brush side\n" ); + } + return bestside; +} + +// Get a list of brushes from pBrushList that could cut faces on the source brush +int GetListOfCutBrushes( CUtlVector &out, bspbrush_t *pSourceBrush, bspbrush_t *pBrushList ) +{ + mapbrush_t *mb = pSourceBrush->original; + for ( bspbrush_t *walk = pBrushList; walk; walk = walk->next ) + { + if ( walk == pSourceBrush ) + continue; + + // only clip to transparent brushes if the original brush is transparent + if ( walk->original->contents & TRANSPARENT_CONTENTS ) + { + if ( !(mb->contents & TRANSPARENT_CONTENTS) ) + continue; + } + + // don't clip to clip brushes, etc. + if ( !(walk->original->contents & ALL_VISIBLE_CONTENTS) ) + continue; + + // brushes overlap, test faces + if ( !BrushBoxOverlap( pSourceBrush, walk ) ) + continue; + + out.AddToTail( walk ); + } + return out.Count(); +} + +// Count the number of real (unsplit) faces in the list +static int CountFaceList( face_t *f ) +{ + int count = 0; + for ( ; f; f = f->next ) + { + if ( f->split[0] ) + continue; + count++; + } + + return count; +} + +// Clips f to a list of potential cutting brushes +// If f clips into new faces, returns the list of new faces in pOutputList +static void ClipFaceToBrushList( face_t *f, const CUtlVector &cutBrushes, face_t **pOutputList ) +{ + *pOutputList = NULL; + + if ( f->split[0] ) + return; + + face_t *pClipList = CopyFace( f ); + pClipList->next = NULL; + bool clipped = false; + for ( int i = 0; i < cutBrushes.Count(); i++ ) + { + bspbrush_t *cut = cutBrushes[i]; + for ( face_t *pCutFace = pClipList; pCutFace; pCutFace = pCutFace->next ) + { + face_t *pClip = NULL; + // already split, no need to clip + if ( pCutFace->split[0] ) + continue; + + if ( ClipFaceToBrush( pCutFace, cut, &pClip ) ) + { + clipped = true; + // mark face bad, the brush clipped it away + pCutFace->split[0] = pCutFace; + } + else if ( pClip ) + { + clipped = true; + // mark this face as split + pCutFace->split[0] = pCutFace; + + // insert face fragments at head of list (UNDONE: reverses order, do we care?) + while ( pClip ) + { + face_t *next = pClip->next; + pClip->next = pClipList; + pClipList = pClip; + pClip = next; + } + } + } + } + if ( clipped ) + { + *pOutputList = pClipList; + } + else + { + // didn't do any clipping, go ahead and free the copy of the face here. + FreeFaceList( pClipList ); + } +} + +// Compute a list of faces that are visible on the detail brush sides +face_t *ComputeVisibleBrushSides( bspbrush_t *list ) +{ + face_t *pTotalFaces = NULL; + CUtlVector cutBrushes; + + // Go through the whole brush list + for ( bspbrush_t *pbrush = list; pbrush; pbrush = pbrush->next ) + { + face_t *pFaces = NULL; + mapbrush_t *mb = pbrush->original; + + if ( !(mb->contents & ALL_VISIBLE_CONTENTS) ) + continue; + + // Make a face for each brush side, then clip it by the other + // details to see if any fragments are visible + for ( int i = 0; i < pbrush->numsides; i++ ) + { + winding_t *winding = pbrush->sides[i].winding; + if ( !winding ) + continue; + + if (! (pbrush->sides[i].contents & ALL_VISIBLE_CONTENTS) ) + continue; + + side_t *side = FindOriginalSide( mb, pbrush->sides + i ); + face_t *f = MakeBrushFace( side, winding ); + + // link to head of face list + f->next = pFaces; + pFaces = f; + } + + // Make a list of brushes that can cut the face list for this brush + cutBrushes.RemoveAll(); + if ( GetListOfCutBrushes( cutBrushes, pbrush, list ) ) + { + // now cut each face to find visible fragments + for ( face_t *f = pFaces; f; f = f->next ) + { + // this will be a new list of faces that this face cuts into + face_t *pClip = NULL; + ClipFaceToBrushList( f, cutBrushes, &pClip ); + if ( pClip ) + { + int outCount = CountFaceList(pClip); + // it cut into more faces (or it was completely cut away) + if ( outCount <= 1 ) + { + // was removed or cut down, mark as split + f->split[0] = f; + // insert face fragments at head of list (UNDONE: reverses order, do we care?) + while ( pClip ) + { + face_t *next = pClip->next; + pClip->next = pFaces; + pFaces = pClip; + pClip = next; + } + } + else + { + // it cut into more than one visible fragment + // Don't fragment details + // UNDONE: Build 2d convex hull of this list and swap face winding + // with that polygon? That would fix the remaining issues. + FreeFaceList( pClip ); + pClip = NULL; + } + } + } + } + + // move visible fragments to global face list + while ( pFaces ) + { + face_t *next = pFaces->next; + if ( pFaces->split[0] ) + { + FreeFace( pFaces ); + } + else + { + pFaces->next = pTotalFaces; + pTotalFaces = pFaces; + } + pFaces = next; + } + } + + return pTotalFaces; +} diff --git a/mp/src/utils/vbsp/detail.h b/mp/src/utils/vbsp/detail.h new file mode 100644 index 00000000..aa0147d1 --- /dev/null +++ b/mp/src/utils/vbsp/detail.h @@ -0,0 +1,18 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef DETAIL_H +#define DETAIL_H + +#ifdef _WIN32 +#pragma once +#endif + +face_t *MergeDetailTree( tree_t *worldtree, int brush_start, int brush_end ); + + +#endif // DETAIL_H diff --git a/mp/src/utils/vbsp/detailobjects.cpp b/mp/src/utils/vbsp/detailobjects.cpp new file mode 100644 index 00000000..22595781 --- /dev/null +++ b/mp/src/utils/vbsp/detailobjects.cpp @@ -0,0 +1,966 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Places "detail" objects which are client-only renderable things +// +// $Revision: $ +// $NoKeywords: $ +//=============================================================================// + +#include +#include "vbsp.h" +#include "bsplib.h" +#include "KeyValues.h" +#include "utlsymbol.h" +#include "utlvector.h" +#include +#include "bspfile.h" +#include "utilmatlib.h" +#include "gamebspfile.h" +#include "mathlib/VMatrix.h" +#include "materialpatch.h" +#include "pacifier.h" +#include "vstdlib/random.h" +#include "builddisp.h" +#include "disp_vbsp.h" +#include "UtlBuffer.h" +#include "CollisionUtils.h" +#include +#include "UtlLinkedList.h" +#include "byteswap.h" +#include "writebsp.h" + +//----------------------------------------------------------------------------- +// Information about particular detail object types +//----------------------------------------------------------------------------- +enum +{ + MODELFLAG_UPRIGHT = 0x1, +}; + +struct DetailModel_t +{ + CUtlSymbol m_ModelName; + float m_Amount; + float m_MinCosAngle; + float m_MaxCosAngle; + int m_Flags; + int m_Orientation; + int m_Type; + Vector2D m_Pos[2]; + Vector2D m_Tex[2]; + float m_flRandomScaleStdDev; + unsigned char m_ShapeSize; + unsigned char m_ShapeAngle; + unsigned char m_SwayAmount; +}; + +struct DetailObjectGroup_t +{ + float m_Alpha; + CUtlVector< DetailModel_t > m_Models; +}; + +struct DetailObject_t +{ + CUtlSymbol m_Name; + float m_Density; + CUtlVector< DetailObjectGroup_t > m_Groups; + + bool operator==(const DetailObject_t& src ) const + { + return src.m_Name == m_Name; + } +}; + +static CUtlVector s_DetailObjectDict; + + +//----------------------------------------------------------------------------- +// Error checking.. make sure the model is valid + is a static prop +//----------------------------------------------------------------------------- +struct StaticPropLookup_t +{ + CUtlSymbol m_ModelName; + bool m_IsValid; +}; + +static bool StaticLess( StaticPropLookup_t const& src1, StaticPropLookup_t const& src2 ) +{ + return src1.m_ModelName < src2.m_ModelName; +} + +static CUtlRBTree< StaticPropLookup_t, unsigned short > s_StaticPropLookup( 0, 32, StaticLess ); + + +//----------------------------------------------------------------------------- +// These puppies are used to construct the game lumps +//----------------------------------------------------------------------------- +static CUtlVector s_DetailObjectDictLump; +static CUtlVector s_DetailObjectLump; +static CUtlVector s_DetailSpriteDictLump; + + +//----------------------------------------------------------------------------- +// Parses the key-value pairs in the detail.rad file +//----------------------------------------------------------------------------- +static void ParseDetailGroup( int detailId, KeyValues* pGroupKeyValues ) +{ + // Sort the group by alpha + float alpha = pGroupKeyValues->GetFloat( "alpha", 1.0f ); + + int i = s_DetailObjectDict[detailId].m_Groups.Count(); + while ( --i >= 0 ) + { + if (alpha > s_DetailObjectDict[detailId].m_Groups[i].m_Alpha) + break; + } + + // Insert after the first guy who's more transparent that we are! + i = s_DetailObjectDict[detailId].m_Groups.InsertAfter(i); + DetailObjectGroup_t& group = s_DetailObjectDict[detailId].m_Groups[i]; + + group.m_Alpha = alpha; + + // Add in all the model groups + KeyValues* pIter = pGroupKeyValues->GetFirstSubKey(); + float totalAmount = 0.0f; + while( pIter ) + { + if (pIter->GetFirstSubKey()) + { + int i = group.m_Models.AddToTail(); + + DetailModel_t &model = group.m_Models[i]; + + model.m_ModelName = pIter->GetString( "model", 0 ); + if (model.m_ModelName != UTL_INVAL_SYMBOL) + { + model.m_Type = DETAIL_PROP_TYPE_MODEL; + } + else + { + const char *pSpriteData = pIter->GetString( "sprite", 0 ); + if (pSpriteData) + { + const char *pProcModelType = pIter->GetString( "sprite_shape", 0 ); + + if ( pProcModelType ) + { + if ( !Q_stricmp( pProcModelType, "cross" ) ) + { + model.m_Type = DETAIL_PROP_TYPE_SHAPE_CROSS; + } + else if ( !Q_stricmp( pProcModelType, "tri" ) ) + { + model.m_Type = DETAIL_PROP_TYPE_SHAPE_TRI; + } + else + model.m_Type = DETAIL_PROP_TYPE_SPRITE; + } + else + { + // card sprite + model.m_Type = DETAIL_PROP_TYPE_SPRITE; + } + + model.m_Tex[0].Init(); + model.m_Tex[1].Init(); + + float x = 0, y = 0, flWidth = 64, flHeight = 64, flTextureSize = 512; + int nValid = sscanf( pSpriteData, "%f %f %f %f %f", &x, &y, &flWidth, &flHeight, &flTextureSize ); + if ( (nValid != 5) || (flTextureSize == 0) ) + { + Error( "Invalid arguments to \"sprite\" in detail.vbsp (model %s)!\n", model.m_ModelName.String() ); + } + + model.m_Tex[0].x = ( x + 0.5f ) / flTextureSize; + model.m_Tex[0].y = ( y + 0.5f ) / flTextureSize; + model.m_Tex[1].x = ( x + flWidth - 0.5f ) / flTextureSize; + model.m_Tex[1].y = ( y + flHeight - 0.5f ) / flTextureSize; + + model.m_Pos[0].Init( -10, 20 ); + model.m_Pos[1].Init( 10, 0 ); + + pSpriteData = pIter->GetString( "spritesize", 0 ); + if (pSpriteData) + { + sscanf( pSpriteData, "%f %f %f %f", &x, &y, &flWidth, &flHeight ); + + float ox = flWidth * x; + float oy = flHeight * y; + + model.m_Pos[0].x = -ox; + model.m_Pos[0].y = flHeight - oy; + model.m_Pos[1].x = flWidth - ox; + model.m_Pos[1].y = -oy; + } + + model.m_flRandomScaleStdDev = pIter->GetFloat( "spriterandomscale", 0.0f ); + + // sway is a percent of max sway, cl_detail_max_sway + float flSway = clamp( pIter->GetFloat( "sway", 0.0f ), 0.0, 1.0 ); + model.m_SwayAmount = (unsigned char)( 255.0 * flSway ); + + // shape angle + // for the tri shape, this is the angle each side is fanned out + model.m_ShapeAngle = pIter->GetInt( "shape_angle", 0 ); + + // shape size + // for the tri shape, this is the distance from the origin to the center of a side + float flShapeSize = clamp( pIter->GetFloat( "shape_size", 0.0f ), 0.0, 1.0 ); + model.m_ShapeSize = (unsigned char)( 255.0 * flShapeSize ); + } + } + + model.m_Amount = pIter->GetFloat( "amount", 1.0 ) + totalAmount; + totalAmount = model.m_Amount; + + model.m_Flags = 0; + if (pIter->GetInt( "upright", 0 )) + { + model.m_Flags |= MODELFLAG_UPRIGHT; + } + + // These are used to prevent emission on steep surfaces + float minAngle = pIter->GetFloat( "minAngle", 180 ); + float maxAngle = pIter->GetFloat( "maxAngle", 180 ); + model.m_MinCosAngle = cos(minAngle * M_PI / 180.f); + model.m_MaxCosAngle = cos(maxAngle * M_PI / 180.f); + model.m_Orientation = pIter->GetInt( "detailOrientation", 0 ); + + // Make sure minAngle < maxAngle + if ( model.m_MinCosAngle < model.m_MaxCosAngle) + { + model.m_MinCosAngle = model.m_MaxCosAngle; + } + } + pIter = pIter->GetNextKey(); + } + + // renormalize the amount if the total > 1 + if (totalAmount > 1.0f) + { + for (i = 0; i < group.m_Models.Count(); ++i) + { + group.m_Models[i].m_Amount /= totalAmount; + } + } +} + + +//----------------------------------------------------------------------------- +// Parses the key-value pairs in the detail.vbsp file +//----------------------------------------------------------------------------- +static void ParseDetailObjectFile( KeyValues& keyValues ) +{ + // Iterate over all detail object groups... + KeyValues* pIter; + for( pIter = keyValues.GetFirstSubKey(); pIter; pIter = pIter->GetNextKey() ) + { + if (!pIter->GetFirstSubKey()) + continue; + + int i = s_DetailObjectDict.AddToTail( ); + s_DetailObjectDict[i].m_Name = pIter->GetName() ; + s_DetailObjectDict[i].m_Density = pIter->GetFloat( "density", 0.0f ); + + // Iterate over all detail object groups... + KeyValues* pIterGroups = pIter->GetFirstSubKey(); + while( pIterGroups ) + { + if (pIterGroups->GetFirstSubKey()) + { + ParseDetailGroup( i, pIterGroups ); + } + pIterGroups = pIterGroups->GetNextKey(); + } + } +} + + +//----------------------------------------------------------------------------- +// Finds the name of the detail.vbsp file to use +//----------------------------------------------------------------------------- +static const char *FindDetailVBSPName( void ) +{ + for( int i = 0; i < num_entities; i++ ) + { + char* pEntity = ValueForKey( &entities[i], "classname" ); + if ( !strcmp( pEntity, "worldspawn" ) ) + { + const char *pDetailVBSP = ValueForKey( &entities[i], "detailvbsp" ); + if ( !pDetailVBSP || !pDetailVBSP[0] ) + { + pDetailVBSP = "detail.vbsp"; + } + return pDetailVBSP; + } + } + return "detail.vbsp"; +} + + +//----------------------------------------------------------------------------- +// Loads up the detail object dictionary +//----------------------------------------------------------------------------- +void LoadEmitDetailObjectDictionary( const char* pGameDir ) +{ + // Set the required global lights filename and try looking in qproject + const char *pDetailVBSP = FindDetailVBSPName(); + KeyValues * values = new KeyValues( pDetailVBSP ); + if ( values->LoadFromFile( g_pFileSystem, pDetailVBSP ) ) + { + ParseDetailObjectFile( *values ); + } + values->deleteThis(); +} + + +//----------------------------------------------------------------------------- +// Selects a detail group +//----------------------------------------------------------------------------- +static int SelectGroup( const DetailObject_t& detail, float alpha ) +{ + // Find the two groups whose alpha we're between... + int start, end; + for ( start = 0; start < detail.m_Groups.Count() - 1; ++start ) + { + if (alpha < detail.m_Groups[start+1].m_Alpha) + break; + } + + end = start + 1; + if (end >= detail.m_Groups.Count()) + --end; + + if (start == end) + return start; + + // Figure out how far we are between start and end... + float dist = 0.0f; + float dAlpha = (detail.m_Groups[end].m_Alpha - detail.m_Groups[start].m_Alpha); + if (dAlpha != 0.0f) + { + dist = (alpha - detail.m_Groups[start].m_Alpha) / dAlpha; + } + + // Pick a number, any number... + float r = rand() / (float)VALVE_RAND_MAX; + + // When dist == 0, we *always* want start. + // When dist == 1, we *always* want end + // That's why this logic looks a little reversed + return (r > dist) ? start : end; +} + + +//----------------------------------------------------------------------------- +// Selects a detail object +//----------------------------------------------------------------------------- +static int SelectDetail( DetailObjectGroup_t const& group ) +{ + // Pick a number, any number... + float r = rand() / (float)VALVE_RAND_MAX; + + // Look through the list of models + pick the one associated with this number + for ( int i = 0; i < group.m_Models.Count(); ++i ) + { + if (r <= group.m_Models[i].m_Amount) + return i; + } + + return -1; +} + + +//----------------------------------------------------------------------------- +// Adds a detail dictionary element (expected to oftentimes be shared) +//----------------------------------------------------------------------------- +static int AddDetailDictLump( const char* pModelName ) +{ + DetailObjectDictLump_t dictLump; + strncpy( dictLump.m_Name, pModelName, DETAIL_NAME_LENGTH ); + + for (int i = s_DetailObjectDictLump.Count(); --i >= 0; ) + { + if (!memcmp(&s_DetailObjectDictLump[i], &dictLump, sizeof(dictLump) )) + return i; + } + + return s_DetailObjectDictLump.AddToTail( dictLump ); +} + +static int AddDetailSpriteDictLump( const Vector2D *pPos, const Vector2D *pTex ) +{ + DetailSpriteDictLump_t dictLump; + dictLump.m_UL = pPos[0]; + dictLump.m_LR = pPos[1]; + dictLump.m_TexUL = pTex[0]; + dictLump.m_TexLR = pTex[1]; + + for (int i = s_DetailSpriteDictLump.Count(); --i >= 0; ) + { + if (!memcmp(&s_DetailSpriteDictLump[i], &dictLump, sizeof(dictLump) )) + return i; + } + + return s_DetailSpriteDictLump.AddToTail( dictLump ); +} + + +//----------------------------------------------------------------------------- +// Computes the leaf that the detail lies in +//----------------------------------------------------------------------------- +static int ComputeDetailLeaf( const Vector& pt ) +{ + int node = 0; + while( node >= 0 ) + { + dnode_t* pNode = &dnodes[node]; + dplane_t* pPlane = &dplanes[pNode->planenum]; + + if (DotProduct(pt, pPlane->normal) < pPlane->dist) + node = pNode->children[1]; + else + node = pNode->children[0]; + } + + return - node - 1; +} + + +//----------------------------------------------------------------------------- +// Make sure the details are compiled with static prop +//----------------------------------------------------------------------------- +static bool IsModelValid( const char* pModelName ) +{ + StaticPropLookup_t lookup; + lookup.m_ModelName = pModelName; + + int i = s_StaticPropLookup.Find( lookup ); + if (i != s_StaticPropLookup.InvalidIndex() ) + return s_StaticPropLookup[i].m_IsValid; + + CUtlBuffer buf; + lookup.m_IsValid = LoadStudioModel( pModelName, "detail_prop", buf ); + if (!lookup.m_IsValid) + { + Warning("Error loading studio model \"%s\"!\n", pModelName ); + } + + s_StaticPropLookup.Insert( lookup ); + return lookup.m_IsValid; +} + + +//----------------------------------------------------------------------------- +// Add a detail to the lump. +//----------------------------------------------------------------------------- +static int s_nDetailOverflow = 0; +static void AddDetailToLump( const char* pModelName, const Vector& pt, const QAngle& angles, int nOrientation ) +{ + Assert( pt.IsValid() && angles.IsValid() ); + + // Make sure the model is valid... + if (!IsModelValid(pModelName)) + return; + + if (s_DetailObjectLump.Count() == 65535) + { + ++s_nDetailOverflow; + return; + } + + // Insert an element into the object dictionary if it aint there... + int i = s_DetailObjectLump.AddToTail( ); + + DetailObjectLump_t& objectLump = s_DetailObjectLump[i]; + objectLump.m_DetailModel = AddDetailDictLump( pModelName ); + VectorCopy( angles, objectLump.m_Angles ); + VectorCopy( pt, objectLump.m_Origin ); + objectLump.m_Leaf = ComputeDetailLeaf(pt); + objectLump.m_Lighting.r = 255; + objectLump.m_Lighting.g = 255; + objectLump.m_Lighting.b = 255; + objectLump.m_Lighting.exponent = 0; + objectLump.m_LightStyles = 0; + objectLump.m_LightStyleCount = 0; + objectLump.m_Orientation = nOrientation; + objectLump.m_Type = DETAIL_PROP_TYPE_MODEL; +} + + +//----------------------------------------------------------------------------- +// Add a detail sprite to the lump. +//----------------------------------------------------------------------------- +static void AddDetailSpriteToLump( const Vector &vecOrigin, const QAngle &vecAngles, int nOrientation, + const Vector2D *pPos, const Vector2D *pTex, float flScale, int iType, + int iShapeAngle = 0, int iShapeSize = 0, int iSwayAmount = 0 ) +{ + // Insert an element into the object dictionary if it aint there... + int i = s_DetailObjectLump.AddToTail( ); + + if (i >= 65535) + { + Error( "Error! Too many detail props emitted on this map! (64K max!)n" ); + } + + DetailObjectLump_t& objectLump = s_DetailObjectLump[i]; + objectLump.m_DetailModel = AddDetailSpriteDictLump( pPos, pTex ); + VectorCopy( vecAngles, objectLump.m_Angles ); + VectorCopy( vecOrigin, objectLump.m_Origin ); + objectLump.m_Leaf = ComputeDetailLeaf(vecOrigin); + objectLump.m_Lighting.r = 255; + objectLump.m_Lighting.g = 255; + objectLump.m_Lighting.b = 255; + objectLump.m_Lighting.exponent = 0; + objectLump.m_LightStyles = 0; + objectLump.m_LightStyleCount = 0; + objectLump.m_Orientation = nOrientation; + objectLump.m_Type = iType; + objectLump.m_flScale = flScale; + objectLump.m_ShapeAngle = iShapeAngle; + objectLump.m_ShapeSize = iShapeSize; + objectLump.m_SwayAmount = iSwayAmount; +} + +static void AddDetailSpriteToLump( const Vector &vecOrigin, const QAngle &vecAngles, DetailModel_t const& model, float flScale ) +{ + AddDetailSpriteToLump( vecOrigin, + vecAngles, + model.m_Orientation, + model.m_Pos, + model.m_Tex, + flScale, + model.m_Type, + model.m_ShapeAngle, + model.m_ShapeSize, + model.m_SwayAmount ); +} + +//----------------------------------------------------------------------------- +// Got a detail! Place it on the surface... +//----------------------------------------------------------------------------- +// BUGBUG: When the global optimizer is on, "normal" gets trashed in this function +// (only when not in the debugger?) +// Printing the values of normal at the bottom of the function fixes it as does +// disabling global optimizations. +static void PlaceDetail( DetailModel_t const& model, const Vector& pt, const Vector& normal ) +{ + // But only place it on the surface if it meets the angle constraints... + float cosAngle = normal.z; + + // Never emit if the angle's too steep + if (cosAngle < model.m_MaxCosAngle) + return; + + // If it's between min + max, flip a coin... + if (cosAngle < model.m_MinCosAngle) + { + float probability = (cosAngle - model.m_MaxCosAngle) / + (model.m_MinCosAngle - model.m_MaxCosAngle); + + float t = rand() / (float)VALVE_RAND_MAX; + if (t > probability) + return; + } + + // Compute the orientation of the detail + QAngle angles; + if (model.m_Flags & MODELFLAG_UPRIGHT) + { + // If it's upright, we just select a random yaw + angles.Init( 0, 360.0f * rand() / (float)VALVE_RAND_MAX, 0.0f ); + } + else + { + // It's not upright, so it must conform to the ground. Choose + // a random orientation based on the surface normal + + Vector zaxis; + VectorCopy( normal, zaxis ); + VectorNormalize( zaxis ); + + // Choose any two arbitrary axes which are perpendicular to the normal + Vector xaxis( 1, 0, 0 ); + if (fabs(xaxis.Dot(zaxis)) - 1.0 > -1e-3) + xaxis.Init( 0, 1, 0 ); + Vector yaxis; + CrossProduct( zaxis, xaxis, yaxis ); + VectorNormalize( yaxis ); + CrossProduct( yaxis, zaxis, xaxis ); + VectorNormalize( xaxis ); + VMatrix matrix; + matrix.SetBasisVectors( xaxis, yaxis, zaxis ); + matrix.SetTranslation( vec3_origin ); + + float rotAngle = 360.0f * rand() / (float)VALVE_RAND_MAX; + VMatrix rot = SetupMatrixAxisRot( Vector( 0, 0, 1 ), rotAngle ); + matrix = matrix * rot; + + MatrixToAngles( matrix, angles ); + } + + // FIXME: We may also want a purely random rotation too + + // Insert an element into the object dictionary if it aint there... + switch ( model.m_Type ) + { + case DETAIL_PROP_TYPE_MODEL: + AddDetailToLump( model.m_ModelName.String(), pt, angles, model.m_Orientation ); + break; + + // Sprites and procedural models made from sprites + case DETAIL_PROP_TYPE_SPRITE: + default: + { + float flScale = 1.0f; + if ( model.m_flRandomScaleStdDev != 0.0f ) + { + flScale = fabs( RandomGaussianFloat( 1.0f, model.m_flRandomScaleStdDev ) ); + } + + AddDetailSpriteToLump( pt, angles, model, flScale ); + } + break; + } +} + + +//----------------------------------------------------------------------------- +// Places Detail Objects on a face +//----------------------------------------------------------------------------- +static void EmitDetailObjectsOnFace( dface_t* pFace, DetailObject_t& detail ) +{ + if (pFace->numedges < 3) + return; + + // We're going to pick a bunch of random points, and then probabilistically + // decide whether or not to plant a detail object there. + + // Turn the face into a bunch of polygons, and compute the area of each + int* pSurfEdges = &dsurfedges[pFace->firstedge]; + int vertexIdx = (pSurfEdges[0] < 0); + int firstVertexIndex = dedges[abs(pSurfEdges[0])].v[vertexIdx]; + dvertex_t* pFirstVertex = &dvertexes[firstVertexIndex]; + for (int i = 1; i < pFace->numedges - 1; ++i ) + { + int vertexIdx = (pSurfEdges[i] < 0); + dedge_t* pEdge = &dedges[abs(pSurfEdges[i])]; + + // Compute two triangle edges + Vector e1, e2; + VectorSubtract( dvertexes[pEdge->v[vertexIdx]].point, pFirstVertex->point, e1 ); + VectorSubtract( dvertexes[pEdge->v[1 - vertexIdx]].point, pFirstVertex->point, e2 ); + + // Compute the triangle area + Vector areaVec; + CrossProduct( e1, e2, areaVec ); + float normalLength = areaVec.Length(); + float area = 0.5f * normalLength; + + // Compute the number of samples to take + int numSamples = area * detail.m_Density * 0.000001; + + // Now take a sample, and randomly place an object there + for (int i = 0; i < numSamples; ++i ) + { + // Create a random sample... + float u = rand() / (float)VALVE_RAND_MAX; + float v = rand() / (float)VALVE_RAND_MAX; + if (v > 1.0f - u) + { + u = 1.0f - u; + v = 1.0f - v; + assert( u + v <= 1.0f ); + } + + // Compute alpha + float alpha = 1.0f; + + // Select a group based on the alpha value + int group = SelectGroup( detail, alpha ); + + // Now that we've got a group, choose a detail + int model = SelectDetail( detail.m_Groups[group] ); + if (model < 0) + continue; + + // Got a detail! Place it on the surface... + Vector pt, normal; + VectorMA( pFirstVertex->point, u, e1, pt ); + VectorMA( pt, v, e2, pt ); + VectorDivide( areaVec, -normalLength, normal ); + + PlaceDetail( detail.m_Groups[group].m_Models[model], pt, normal ); + } + } +} + + +//----------------------------------------------------------------------------- +// Places Detail Objects on a face +//----------------------------------------------------------------------------- +static float ComputeDisplacementFaceArea( dface_t* pFace ) +{ + float area = 0.0f; + + // Compute the area of the base face + int* pSurfEdges = &dsurfedges[pFace->firstedge]; + int vertexIdx = (pSurfEdges[0] < 0); + int firstVertexIndex = dedges[abs(pSurfEdges[0])].v[vertexIdx]; + dvertex_t* pFirstVertex = &dvertexes[firstVertexIndex]; + for (int i = 1; i <= 2; ++i ) + { + int vertexIdx = (pSurfEdges[i] < 0); + dedge_t* pEdge = &dedges[abs(pSurfEdges[i])]; + + // Compute two triangle edges + Vector e1, e2; + VectorSubtract( dvertexes[pEdge->v[vertexIdx]].point, pFirstVertex->point, e1 ); + VectorSubtract( dvertexes[pEdge->v[1 - vertexIdx]].point, pFirstVertex->point, e2 ); + + // Compute the triangle area + Vector areaVec; + CrossProduct( e1, e2, areaVec ); + float normalLength = areaVec.Length(); + area += 0.5f * normalLength; + } + + return area; +} + + +//----------------------------------------------------------------------------- +// Places Detail Objects on a face +//----------------------------------------------------------------------------- +static void EmitDetailObjectsOnDisplacementFace( dface_t* pFace, + DetailObject_t& detail, CCoreDispInfo& coreDispInfo ) +{ + assert(pFace->numedges == 4); + + // We're going to pick a bunch of random points, and then probabilistically + // decide whether or not to plant a detail object there. + + // Compute the area of the base face + float area = ComputeDisplacementFaceArea( pFace ); + + // Compute the number of samples to take + int numSamples = area * detail.m_Density * 0.000001; + + // Now take a sample, and randomly place an object there + for (int i = 0; i < numSamples; ++i ) + { + // Create a random sample... + float u = rand() / (float)VALVE_RAND_MAX; + float v = rand() / (float)VALVE_RAND_MAX; + + // Compute alpha + float alpha; + Vector pt, normal; + coreDispInfo.GetPositionOnSurface( u, v, pt, &normal, &alpha ); + alpha /= 255.0f; + + // Select a group based on the alpha value + int group = SelectGroup( detail, alpha ); + + // Now that we've got a group, choose a detail + int model = SelectDetail( detail.m_Groups[group] ); + if (model < 0) + continue; + + // Got a detail! Place it on the surface... + PlaceDetail( detail.m_Groups[group].m_Models[model], pt, normal ); + } +} + + +//----------------------------------------------------------------------------- +// Sort detail objects by leaf +//----------------------------------------------------------------------------- +static int SortFunc( const void *arg1, const void *arg2 ) +{ + int nDelta = ((DetailObjectLump_t*)arg1)->m_Leaf - ((DetailObjectLump_t*)arg2)->m_Leaf; + if ( nDelta < 0 ) + return -1; + if ( nDelta > 0 ) + return 1; + return 0; +} + + +//----------------------------------------------------------------------------- +// Places Detail Objects in the lump +//----------------------------------------------------------------------------- +static void SetLumpData( ) +{ + // Sort detail props by leaf + qsort( s_DetailObjectLump.Base(), s_DetailObjectLump.Count(), sizeof(DetailObjectLump_t), SortFunc ); + + GameLumpHandle_t handle = g_GameLumps.GetGameLumpHandle(GAMELUMP_DETAIL_PROPS); + if (handle != g_GameLumps.InvalidGameLump()) + { + g_GameLumps.DestroyGameLump(handle); + } + int nDictSize = s_DetailObjectDictLump.Count() * sizeof(DetailObjectDictLump_t); + int nSpriteDictSize = s_DetailSpriteDictLump.Count() * sizeof(DetailSpriteDictLump_t); + int nObjSize = s_DetailObjectLump.Count() * sizeof(DetailObjectLump_t); + int nSize = nDictSize + nSpriteDictSize + nObjSize + (3 * sizeof(int)); + + handle = g_GameLumps.CreateGameLump( GAMELUMP_DETAIL_PROPS, nSize, 0, GAMELUMP_DETAIL_PROPS_VERSION ); + + // Serialize the data + CUtlBuffer buf( g_GameLumps.GetGameLump(handle), nSize ); + buf.PutInt( s_DetailObjectDictLump.Count() ); + if (nDictSize) + { + buf.Put( s_DetailObjectDictLump.Base(), nDictSize ); + } + buf.PutInt( s_DetailSpriteDictLump.Count() ); + if (nSpriteDictSize) + { + buf.Put( s_DetailSpriteDictLump.Base(), nSpriteDictSize ); + } + buf.PutInt( s_DetailObjectLump.Count() ); + if (nObjSize) + { + buf.Put( s_DetailObjectLump.Base(), nObjSize ); + } +} + + +//----------------------------------------------------------------------------- +// Places Detail Objects in the level +//----------------------------------------------------------------------------- +void EmitDetailModels() +{ + StartPacifier("Placing detail props : "); + + // Place stuff on each face + dface_t* pFace = dfaces; + for (int j = 0; j < numfaces; ++j) + { + UpdatePacifier( (float)j / (float)numfaces ); + + // Get at the material associated with this face + texinfo_t* pTexInfo = &texinfo[pFace[j].texinfo]; + dtexdata_t* pTexData = GetTexData( pTexInfo->texdata ); + + // Try to get at the material + bool found; + MaterialSystemMaterial_t handle = + FindOriginalMaterial( TexDataStringTable_GetString( pTexData->nameStringTableID ), + &found, false ); + if (!found) + continue; + + // See if its got any detail objects on it + const char* pDetailType = GetMaterialVar( handle, "%detailtype" ); + if (!pDetailType) + continue; + + // Get the detail type... + DetailObject_t search; + search.m_Name = pDetailType; + int objectType = s_DetailObjectDict.Find(search); + if (objectType < 0) + { + Warning("Material %s uses unknown detail object type %s!\n", + TexDataStringTable_GetString( pTexData->nameStringTableID ), + pDetailType); + continue; + } + + // Emit objects on a particular face + DetailObject_t& detail = s_DetailObjectDict[objectType]; + + // Initialize the Random Number generators for detail prop placement based on the hammer Face num. + int detailpropseed = dfaceids[j].hammerfaceid; +#ifdef WARNSEEDNUMBER + Warning( "[%d]\n",detailpropseed ); +#endif + srand( detailpropseed ); + RandomSeed( detailpropseed ); + + if (pFace[j].dispinfo < 0) + { + EmitDetailObjectsOnFace( &pFace[j], detail ); + } + else + { + // Get a CCoreDispInfo. All we need is the triangles and lightmap texture coordinates. + mapdispinfo_t *pMapDisp = &mapdispinfo[pFace[j].dispinfo]; + CCoreDispInfo coreDispInfo; + DispMapToCoreDispInfo( pMapDisp, &coreDispInfo, NULL, NULL ); + + EmitDetailObjectsOnDisplacementFace( &pFace[j], detail, coreDispInfo ); + } + } + + // Emit specifically specified detail props + Vector origin; + QAngle angles; + Vector2D pos[2]; + Vector2D tex[2]; + for (int i = 0; i < num_entities; ++i) + { + char* pEntity = ValueForKey(&entities[i], "classname"); + if (!strcmp(pEntity, "detail_prop") || !strcmp(pEntity, "prop_detail")) + { + GetVectorForKey( &entities[i], "origin", origin ); + GetAnglesForKey( &entities[i], "angles", angles ); + char* pModelName = ValueForKey( &entities[i], "model" ); + int nOrientation = IntForKey( &entities[i], "detailOrientation" ); + + AddDetailToLump( pModelName, origin, angles, nOrientation ); + + // strip this ent from the .bsp file + entities[i].epairs = 0; + continue; + } + + if (!strcmp(pEntity, "prop_detail_sprite")) + { + GetVectorForKey( &entities[i], "origin", origin ); + GetAnglesForKey( &entities[i], "angles", angles ); + int nOrientation = IntForKey( &entities[i], "detailOrientation" ); + GetVector2DForKey( &entities[i], "position_ul", pos[0] ); + GetVector2DForKey( &entities[i], "position_lr", pos[1] ); + GetVector2DForKey( &entities[i], "tex_ul", tex[0] ); + GetVector2DForKey( &entities[i], "tex_size", tex[1] ); + float flTextureSize = FloatForKey( &entities[i], "tex_total_size" ); + + tex[1].x += tex[0].x - 0.5f; + tex[1].y += tex[0].y - 0.5f; + tex[0].x += 0.5f; + tex[0].y += 0.5f; + tex[0] /= flTextureSize; + tex[1] /= flTextureSize; + + AddDetailSpriteToLump( origin, angles, nOrientation, pos, tex, 1.0f, DETAIL_PROP_TYPE_SPRITE ); + + // strip this ent from the .bsp file + entities[i].epairs = 0; + continue; + } + } + + EndPacifier( true ); +} + + +//----------------------------------------------------------------------------- +// Places Detail Objects in the level +//----------------------------------------------------------------------------- +void EmitDetailObjects() +{ + EmitDetailModels(); + + // Done! Now lets add the lumps (destroy previous ones) + SetLumpData( ); + + if ( s_nDetailOverflow != 0 ) + { + Warning( "Error! Too many detail props on this map. %d were not emitted!\n", s_nDetailOverflow ); + } +} diff --git a/mp/src/utils/vbsp/disp_ivp.cpp b/mp/src/utils/vbsp/disp_ivp.cpp new file mode 100644 index 00000000..3fe50799 --- /dev/null +++ b/mp/src/utils/vbsp/disp_ivp.cpp @@ -0,0 +1,359 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// +#include "vbsp.h" +#include "disp_vbsp.h" +#include "builddisp.h" +#include "disp_common.h" +#include "ivp.h" +#include "disp_ivp.h" +#include "vphysics_interface.h" +#include "vphysics/virtualmesh.h" +#include "utlrbtree.h" +#include "tier1/utlbuffer.h" +#include "materialpatch.h" + +struct disp_grid_t +{ + int gridIndex; + CUtlVector dispList; +}; + +static CUtlVector gDispGridList; + + + +disp_grid_t &FindOrInsertGrid( int gridIndex ) +{ + // linear search is slow, but only a few grids will be present + for ( int i = gDispGridList.Count()-1; i >= 0; i-- ) + { + if ( gDispGridList[i].gridIndex == gridIndex ) + { + return gDispGridList[i]; + } + } + int index = gDispGridList.AddToTail(); + gDispGridList[index].gridIndex = gridIndex; + + // must be empty + Assert( gDispGridList[index].dispList.Count() == 0 ); + + return gDispGridList[index]; +} + +// UNDONE: Tune these or adapt them to map size or triangle count? +#define DISP_GRID_SIZEX 4096 +#define DISP_GRID_SIZEY 4096 +#define DISP_GRID_SIZEZ 8192 + +int Disp_GridIndex( CCoreDispInfo *pDispInfo ) +{ + // quick hash the center into the grid and put the whole terrain in that grid + Vector mins, maxs; + pDispInfo->GetNode(0)->GetBoundingBox( mins, maxs ); + Vector center; + center = 0.5 * (mins + maxs); + // make sure it's positive + center += Vector(MAX_COORD_INTEGER,MAX_COORD_INTEGER,MAX_COORD_INTEGER); + int gridX = center.x / DISP_GRID_SIZEX; + int gridY = center.y / DISP_GRID_SIZEY; + int gridZ = center.z / DISP_GRID_SIZEZ; + + gridX &= 0xFF; + gridY &= 0xFF; + gridZ &= 0xFF; + return MAKEID( gridX, gridY, gridZ, 0 ); +} + +void AddToGrid( int gridIndex, int dispIndex ) +{ + disp_grid_t &grid = FindOrInsertGrid( gridIndex ); + grid.dispList.AddToTail( dispIndex ); +} + +MaterialSystemMaterial_t GetMatIDFromDisp( mapdispinfo_t *pMapDisp ) +{ + texinfo_t *pTexInfo = &texinfo[pMapDisp->face.texinfo]; + dtexdata_t *pTexData = GetTexData( pTexInfo->texdata ); + MaterialSystemMaterial_t matID = FindOriginalMaterial( TexDataStringTable_GetString( pTexData->nameStringTableID ), NULL, true ); + return matID; +} + +// check this and disable virtual mesh if in use +bool Disp_HasPower4Displacements() +{ + for ( int i = 0; i < g_CoreDispInfos.Count(); i++ ) + { + if ( g_CoreDispInfos[i]->GetPower() > 3 ) + { + return true; + } + } + return false; +} + +// adds all displacement faces as a series of convex objects +// UNDONE: Only add the displacements for this model? +void Disp_AddCollisionModels( CUtlVector &collisionList, dmodel_t *pModel, int contentsMask) +{ + int dispIndex; + + // Add each displacement to the grid hash + for ( dispIndex = 0; dispIndex < g_CoreDispInfos.Count(); dispIndex++ ) + { + CCoreDispInfo *pDispInfo = g_CoreDispInfos[ dispIndex ]; + mapdispinfo_t *pMapDisp = &mapdispinfo[ dispIndex ]; + + // not solid for this pass + if ( !(pMapDisp->contents & contentsMask) ) + continue; + + int gridIndex = Disp_GridIndex( pDispInfo ); + AddToGrid( gridIndex, dispIndex ); + } + + // now make a polysoup for the terrain in each grid + for ( int grid = 0; grid < gDispGridList.Count(); grid++ ) + { + int triCount = 0; + CPhysPolysoup *pTerrainPhysics = physcollision->PolysoupCreate(); + + // iterate the displacements in this grid + for ( int listIndex = 0; listIndex < gDispGridList[grid].dispList.Count(); listIndex++ ) + { + dispIndex = gDispGridList[grid].dispList[listIndex]; + CCoreDispInfo *pDispInfo = g_CoreDispInfos[ dispIndex ]; + mapdispinfo_t *pMapDisp = &mapdispinfo[ dispIndex ]; + + // Get the material id. + MaterialSystemMaterial_t matID = GetMatIDFromDisp( pMapDisp ); + + // Build a triangle list. This shares the tesselation code with the engine. + CUtlVector indices; + CVBSPTesselateHelper helper; + helper.m_pIndices = &indices; + helper.m_pActiveVerts = pDispInfo->GetAllowedVerts().Base(); + helper.m_pPowerInfo = pDispInfo->GetPowerInfo(); + + ::TesselateDisplacement( &helper ); + + Assert( indices.Count() > 0 ); + Assert( indices.Count() % 3 == 0 ); // Make sure indices are a multiple of 3. + int nTriCount = indices.Count() / 3; + triCount += nTriCount; + if ( triCount >= 65536 ) + { + // don't put more than 64K tris in any single collision model + CPhysCollide *pCollide = physcollision->ConvertPolysoupToCollide( pTerrainPhysics, false ); + if ( pCollide ) + { + collisionList.AddToTail( new CPhysCollisionEntryStaticMesh( pCollide, NULL ) ); + } + // Throw this polysoup away and start over for the remaining triangles + physcollision->PolysoupDestroy( pTerrainPhysics ); + pTerrainPhysics = physcollision->PolysoupCreate(); + triCount = nTriCount; + } + Vector tmpVerts[3]; + for ( int iTri = 0; iTri < nTriCount; ++iTri ) + { + float flAlphaTotal = 0.0f; + for ( int iTriVert = 0; iTriVert < 3; ++iTriVert ) + { + pDispInfo->GetVert( indices[iTri*3+iTriVert], tmpVerts[iTriVert] ); + flAlphaTotal += pDispInfo->GetAlpha( indices[iTri*3+iTriVert] ); + } + + int nProp = g_SurfaceProperties[texinfo[pMapDisp->face.texinfo].texdata]; + if ( flAlphaTotal > DISP_ALPHA_PROP_DELTA ) + { + int nProp2 = GetSurfaceProperties2( matID, "surfaceprop2" ); + if ( nProp2 != -1 ) + { + nProp = nProp2; + } + } + int nMaterialIndex = RemapWorldMaterial( nProp ); + physcollision->PolysoupAddTriangle( pTerrainPhysics, tmpVerts[0], tmpVerts[1], tmpVerts[2], nMaterialIndex ); + } + } + + // convert the whole grid's polysoup to a collide and store in the collision list + CPhysCollide *pCollide = physcollision->ConvertPolysoupToCollide( pTerrainPhysics, false ); + if ( pCollide ) + { + collisionList.AddToTail( new CPhysCollisionEntryStaticMesh( pCollide, NULL ) ); + } + // now that we have the collide, we're done with the soup + physcollision->PolysoupDestroy( pTerrainPhysics ); + } +} + + +class CDispMeshEvent : public IVirtualMeshEvent +{ +public: + CDispMeshEvent( unsigned short *pIndices, int indexCount, CCoreDispInfo *pDispInfo ); + virtual void GetVirtualMesh( void *userData, virtualmeshlist_t *pList ); + virtual void GetWorldspaceBounds( void *userData, Vector *pMins, Vector *pMaxs ); + virtual void GetTrianglesInSphere( void *userData, const Vector ¢er, float radius, virtualmeshtrianglelist_t *pList ); + + CUtlVector m_verts; + unsigned short *m_pIndices; + int m_indexCount; +}; + +CDispMeshEvent::CDispMeshEvent( unsigned short *pIndices, int indexCount, CCoreDispInfo *pDispInfo ) +{ + m_pIndices = pIndices; + m_indexCount = indexCount; + int maxIndex = 0; + for ( int i = 0; i < indexCount; i++ ) + { + if ( pIndices[i] > maxIndex ) + { + maxIndex = pIndices[i]; + } + } + for ( int i = 0; i < indexCount/2; i++ ) + { + V_swap( pIndices[i], pIndices[(indexCount-i)-1] ); + } + int count = maxIndex + 1; + m_verts.SetCount( count ); + for ( int i = 0; i < count; i++ ) + { + m_verts[i] = pDispInfo->GetVert(i); + } +} + +void CDispMeshEvent::GetVirtualMesh( void *userData, virtualmeshlist_t *pList ) +{ + Assert(userData==((void *)this)); + pList->pVerts = m_verts.Base(); + pList->indexCount = m_indexCount; + pList->triangleCount = m_indexCount/3; + pList->vertexCount = m_verts.Count(); + pList->surfacePropsIndex = 0; // doesn't matter here, reset at runtime + pList->pHull = NULL; + int indexMax = ARRAYSIZE(pList->indices); + int indexCount = min(m_indexCount, indexMax); + Assert(m_indexCount < indexMax); + Q_memcpy( pList->indices, m_pIndices, sizeof(*m_pIndices) * indexCount ); +} + +void CDispMeshEvent::GetWorldspaceBounds( void *userData, Vector *pMins, Vector *pMaxs ) +{ + Assert(userData==((void *)this)); + ClearBounds( *pMins, *pMaxs ); + for ( int i = 0; i < m_verts.Count(); i++ ) + { + AddPointToBounds( m_verts[i], *pMins, *pMaxs ); + } +} + +void CDispMeshEvent::GetTrianglesInSphere( void *userData, const Vector ¢er, float radius, virtualmeshtrianglelist_t *pList ) +{ + Assert(userData==((void *)this)); + pList->triangleCount = m_indexCount/3; + int indexMax = ARRAYSIZE(pList->triangleIndices); + int indexCount = min(m_indexCount, indexMax); + Assert(m_indexCount < MAX_VIRTUAL_TRIANGLES*3); + Q_memcpy( pList->triangleIndices, m_pIndices, sizeof(*m_pIndices) * indexCount ); +} + +void Disp_BuildVirtualMesh( int contentsMask ) +{ + CUtlVector virtualMeshes; + virtualMeshes.EnsureCount( g_CoreDispInfos.Count() ); + for ( int i = 0; i < g_CoreDispInfos.Count(); i++ ) + { + CCoreDispInfo *pDispInfo = g_CoreDispInfos[ i ]; + mapdispinfo_t *pMapDisp = &mapdispinfo[ i ]; + + virtualMeshes[i] = NULL; + // not solid for this pass + if ( !(pMapDisp->contents & contentsMask) ) + continue; + + // Build a triangle list. This shares the tesselation code with the engine. + CUtlVector indices; + CVBSPTesselateHelper helper; + helper.m_pIndices = &indices; + helper.m_pActiveVerts = pDispInfo->GetAllowedVerts().Base(); + helper.m_pPowerInfo = pDispInfo->GetPowerInfo(); + + ::TesselateDisplacement( &helper ); + + // validate the collision data + if ( 1 ) + { + int triCount = indices.Count() / 3; + for ( int j = 0; j < triCount; j++ ) + { + int index = j * 3; + Vector v0 = pDispInfo->GetVert( indices[index+0] ); + Vector v1 = pDispInfo->GetVert( indices[index+1] ); + Vector v2 = pDispInfo->GetVert( indices[index+2] ); + if ( v0 == v1 || v1 == v2 || v2 == v0 ) + { + Warning( "Displacement %d has bad geometry near %.2f %.2f %.2f\n", i, v0.x, v0.y, v0.z ); + texinfo_t *pTexInfo = &texinfo[pMapDisp->face.texinfo]; + dtexdata_t *pTexData = GetTexData( pTexInfo->texdata ); + const char *pMatName = TexDataStringTable_GetString( pTexData->nameStringTableID ); + + Error( "Can't compile displacement physics, exiting. Texture is %s\n", pMatName ); + } + } + + } + CDispMeshEvent meshHandler( indices.Base(), indices.Count(), pDispInfo ); + virtualmeshparams_t params; + params.buildOuterHull = true; + params.pMeshEventHandler = &meshHandler; + params.userData = &meshHandler; + virtualMeshes[i] = physcollision->CreateVirtualMesh( params ); + } + unsigned int totalSize = 0; + CUtlBuffer buf; + dphysdisp_t header; + header.numDisplacements = g_CoreDispInfos.Count(); + buf.PutObjects( &header ); + + CUtlVector dispBuf; + for ( int i = 0; i < header.numDisplacements; i++ ) + { + if ( virtualMeshes[i] ) + { + unsigned int testSize = physcollision->CollideSize( virtualMeshes[i] ); + totalSize += testSize; + buf.PutShort( testSize ); + } + else + { + buf.PutShort( -1 ); + } + } + for ( int i = 0; i < header.numDisplacements; i++ ) + { + if ( virtualMeshes[i] ) + { + unsigned int testSize = physcollision->CollideSize( virtualMeshes[i] ); + dispBuf.RemoveAll(); + dispBuf.EnsureCount(testSize); + + unsigned int outSize = physcollision->CollideWrite( dispBuf.Base(), virtualMeshes[i], false ); + Assert( outSize == testSize ); + buf.Put( dispBuf.Base(), outSize ); + } + } + g_PhysDispSize = totalSize + sizeof(dphysdisp_t) + (sizeof(unsigned short) * header.numDisplacements); + Assert( buf.TellMaxPut() == g_PhysDispSize ); + g_PhysDispSize = buf.TellMaxPut(); + g_pPhysDisp = new byte[g_PhysDispSize]; + Q_memcpy( g_pPhysDisp, buf.Base(), g_PhysDispSize ); +} + diff --git a/mp/src/utils/vbsp/disp_ivp.h b/mp/src/utils/vbsp/disp_ivp.h new file mode 100644 index 00000000..2c473f9a --- /dev/null +++ b/mp/src/utils/vbsp/disp_ivp.h @@ -0,0 +1,49 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef DISP_IVP_H +#define DISP_IVP_H +#ifdef _WIN32 +#pragma once +#endif + +#include "utlvector.h" +#include "../../public/disp_tesselate.h" + + +class CPhysCollisionEntry; +struct dmodel_t; + + +// This provides the template functions that the engine's tesselation code needs +// so we can share the code in VBSP. +class CVBSPTesselateHelper : public CBaseTesselateHelper +{ +public: + void EndTriangle() + { + m_pIndices->AddToTail( m_TempIndices[0] ); + m_pIndices->AddToTail( m_TempIndices[1] ); + m_pIndices->AddToTail( m_TempIndices[2] ); + } + + DispNodeInfo_t& GetNodeInfo( int iNodeBit ) + { + // VBSP doesn't care about these. Give it back something to play with. + static DispNodeInfo_t dummy; + return dummy; + } + +public: + CUtlVector *m_pIndices; +}; + + +extern void Disp_AddCollisionModels( CUtlVector &collisionList, dmodel_t *pModel, int contentsMask ); +extern void Disp_BuildVirtualMesh( int contentsMask ); +extern bool Disp_HasPower4Displacements(); + +#endif // DISP_IVP_H diff --git a/mp/src/utils/vbsp/disp_vbsp.cpp b/mp/src/utils/vbsp/disp_vbsp.cpp new file mode 100644 index 00000000..82934f67 --- /dev/null +++ b/mp/src/utils/vbsp/disp_vbsp.cpp @@ -0,0 +1,675 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// $NoKeywords: $ +//=============================================================================// + +#include "disp_vbsp.h" +#include "tier0/dbg.h" +#include "vbsp.h" +#include "mstristrip.h" +#include "writebsp.h" +#include "pacifier.h" +#include "disp_ivp.h" +#include "builddisp.h" +#include "mathlib/vector.h" + +// map displacement info -- runs parallel to the dispinfos struct +int nummapdispinfo = 0; +mapdispinfo_t mapdispinfo[MAX_MAP_DISPINFO]; + +CUtlVector g_CoreDispInfos; + +//----------------------------------------------------------------------------- +// Computes the bounds for a disp info +//----------------------------------------------------------------------------- +void ComputeDispInfoBounds( int dispinfo, Vector& mins, Vector& maxs ) +{ + CDispBox box; + + // Get a CCoreDispInfo. All we need is the triangles and lightmap texture coordinates. + mapdispinfo_t *pMapDisp = &mapdispinfo[dispinfo]; + + CCoreDispInfo coreDispInfo; + DispMapToCoreDispInfo( pMapDisp, &coreDispInfo, NULL, NULL ); + + GetDispBox( &coreDispInfo, box ); + mins = box.m_Min; + maxs = box.m_Max; +} + +// Gets the barycentric coordinates of the position on the triangle where the lightmap +// coordinates are equal to lmCoords. This always generates the coordinates but it +// returns false if the point containing them does not lie inside the triangle. +bool GetBarycentricCoordsFromLightmapCoords( Vector2D tri[3], Vector2D const &lmCoords, float bcCoords[3] ) +{ + GetBarycentricCoords2D( tri[0], tri[1], tri[2], lmCoords, bcCoords ); + + return + (bcCoords[0] >= 0.0f && bcCoords[0] <= 1.0f) && + (bcCoords[1] >= 0.0f && bcCoords[1] <= 1.0f) && + (bcCoords[2] >= 0.0f && bcCoords[2] <= 1.0f); +} + + +bool FindTriIndexMapByUV( CCoreDispInfo *pCoreDisp, Vector2D const &lmCoords, + int &iTriangle, float flBarycentric[3] ) +{ + const CPowerInfo *pPowerInfo = GetPowerInfo( pCoreDisp->GetPower() ); + + // Search all the triangles.. + int nTriCount= pCoreDisp->GetTriCount(); + for ( int iTri = 0; iTri < nTriCount; ++iTri ) + { + unsigned short iVerts[3]; +// pCoreDisp->GetTriIndices( iTri, iVerts[0], iVerts[1], iVerts[2] ); + CTriInfo *pTri = &pPowerInfo->m_pTriInfos[iTri]; + iVerts[0] = pTri->m_Indices[0]; + iVerts[1] = pTri->m_Indices[1]; + iVerts[2] = pTri->m_Indices[2]; + + // Get this triangle's UVs. + Vector2D vecUV[3]; + for ( int iCoord = 0; iCoord < 3; ++iCoord ) + { + pCoreDisp->GetLuxelCoord( 0, iVerts[iCoord], vecUV[iCoord] ); + } + + // See if the passed-in UVs are in this triangle's UVs. + if( GetBarycentricCoordsFromLightmapCoords( vecUV, lmCoords, flBarycentric ) ) + { + iTriangle = iTri; + return true; + } + } + + return false; +} + + +void CalculateLightmapSamplePositions( CCoreDispInfo *pCoreDispInfo, const dface_t *pFace, CUtlVector &out ) +{ + int width = pFace->m_LightmapTextureSizeInLuxels[0] + 1; + int height = pFace->m_LightmapTextureSizeInLuxels[1] + 1; + + // For each lightmap sample, find the triangle it sits in. + Vector2D lmCoords; + for( int y=0; y < height; y++ ) + { + lmCoords.y = y + 0.5f; + + for( int x=0; x < width; x++ ) + { + lmCoords.x = x + 0.5f; + + float flBarycentric[3]; + int iTri; + + if( FindTriIndexMapByUV( pCoreDispInfo, lmCoords, iTri, flBarycentric ) ) + { + if( iTri < 255 ) + { + out.AddToTail( iTri ); + } + else + { + out.AddToTail( 255 ); + out.AddToTail( iTri - 255 ); + } + + out.AddToTail( (unsigned char)( flBarycentric[0] * 255.9f ) ); + out.AddToTail( (unsigned char)( flBarycentric[1] * 255.9f ) ); + out.AddToTail( (unsigned char)( flBarycentric[2] * 255.9f ) ); + } + else + { + out.AddToTail( 0 ); + out.AddToTail( 0 ); + out.AddToTail( 0 ); + out.AddToTail( 0 ); + } + } + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +int GetDispInfoEntityNum( mapdispinfo_t *pDisp ) +{ + return pDisp->entitynum; +} + +// Setup a CCoreDispInfo given a mapdispinfo_t. +// If pFace is non-NULL, then lightmap texture coordinates will be generated. +void DispMapToCoreDispInfo( mapdispinfo_t *pMapDisp, CCoreDispInfo *pCoreDispInfo, dface_t *pFace, int *pSwappedTexInfos ) +{ + winding_t *pWinding = pMapDisp->face.originalface->winding; + + Assert( pWinding->numpoints == 4 ); + + // + // set initial surface data + // + CCoreDispSurface *pSurf = pCoreDispInfo->GetSurface(); + + texinfo_t *pTexInfo = &texinfo[ pMapDisp->face.texinfo ]; + Assert( pTexInfo != NULL ); + + // init material contents + pMapDisp->contents = pMapDisp->face.contents; + if (!(pMapDisp->contents & (ALL_VISIBLE_CONTENTS | CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP) ) ) + { + pMapDisp->contents |= CONTENTS_SOLID; + } + + pSurf->SetContents( pMapDisp->contents ); + + // Calculate the lightmap coordinates. + Vector2D tCoords[4] = {Vector2D(0,0),Vector2D(0,1),Vector2D(1,0),Vector2D(1,1)}; + if( pFace ) + { + Assert( pFace->numedges == 4 ); + + Vector pt[4]; + for( int i=0; i < 4; i++ ) + pt[i] = pWinding->p[i]; + + int zeroOffset[2] = {0,0}; + CalcTextureCoordsAtPoints( + pTexInfo->textureVecsTexelsPerWorldUnits, + zeroOffset, + pt, + 4, + tCoords ); + } + + // + // set face point data ... + // + pSurf->SetPointCount( 4 ); + for( int i = 0; i < 4; i++ ) + { + // position + pSurf->SetPoint( i, pWinding->p[i] ); + pSurf->SetTexCoord( i, tCoords[i] ); + } + + // reset surface given start info + pSurf->SetPointStart( pMapDisp->startPosition ); + pSurf->FindSurfPointStartIndex(); + pSurf->AdjustSurfPointData(); + + // Set the luxel coordinates on the base displacement surface. + Vector vecTmp( pTexInfo->lightmapVecsLuxelsPerWorldUnits[0][0], + pTexInfo->lightmapVecsLuxelsPerWorldUnits[0][1], + pTexInfo->lightmapVecsLuxelsPerWorldUnits[0][2] ); + int nLuxelsPerWorldUnit = static_cast( 1.0f / VectorLength( vecTmp ) ); + Vector vecU( pTexInfo->lightmapVecsLuxelsPerWorldUnits[0][0], + pTexInfo->lightmapVecsLuxelsPerWorldUnits[0][1], + pTexInfo->lightmapVecsLuxelsPerWorldUnits[0][2] ); + Vector vecV( pTexInfo->lightmapVecsLuxelsPerWorldUnits[1][0], + pTexInfo->lightmapVecsLuxelsPerWorldUnits[1][1], + pTexInfo->lightmapVecsLuxelsPerWorldUnits[1][2] ); + bool bSwap = pSurf->CalcLuxelCoords( nLuxelsPerWorldUnit, false, vecU, vecV ); + + // Set the face m_LightmapExtents + if ( pFace ) + { + pFace->m_LightmapTextureSizeInLuxels[0] = pSurf->GetLuxelU(); + pFace->m_LightmapTextureSizeInLuxels[1] = pSurf->GetLuxelV(); + if ( bSwap ) + { + if ( pSwappedTexInfos[ pMapDisp->face.texinfo ] < 0 ) + { + // Create a new texinfo to hold the swapped data. + // We must do this because other surfaces may want the non-swapped data + // This fixes a lighting bug in d2_prison_08 where many non-displacement surfaces + // were pitch black, in addition to bugs in other maps I bet. + + // NOTE: Copy here because adding a texinfo could realloc. + texinfo_t temp = *pTexInfo; + memcpy( temp.lightmapVecsLuxelsPerWorldUnits[0], pTexInfo->lightmapVecsLuxelsPerWorldUnits[1], 4 * sizeof(float) ); + memcpy( temp.lightmapVecsLuxelsPerWorldUnits[1], pTexInfo->lightmapVecsLuxelsPerWorldUnits[0], 4 * sizeof(float) ); + temp.lightmapVecsLuxelsPerWorldUnits[1][0] *= -1.0f; + temp.lightmapVecsLuxelsPerWorldUnits[1][1] *= -1.0f; + temp.lightmapVecsLuxelsPerWorldUnits[1][2] *= -1.0f; + temp.lightmapVecsLuxelsPerWorldUnits[1][3] *= -1.0f; + pSwappedTexInfos[ pMapDisp->face.texinfo ] = texinfo.AddToTail( temp ); + } + pMapDisp->face.texinfo = pSwappedTexInfos[ pMapDisp->face.texinfo ]; + } + + // NOTE: This is here to help future-proof code, since there are codepaths where + // pTexInfo can be made invalid (texinfo.AddToTail above). + pTexInfo = NULL; + } + + // Setup the displacement vectors and offsets. + int size = ( ( ( 1 << pMapDisp->power ) + 1 ) * ( ( 1 << pMapDisp->power ) + 1 ) ); + + Vector vectorDisps[2048]; + float dispDists[2048]; + Assert( size < sizeof(vectorDisps)/sizeof(vectorDisps[0]) ); + + for( int j = 0; j < size; j++ ) + { + Vector v; + float dist; + + VectorScale( pMapDisp->vectorDisps[j], pMapDisp->dispDists[j], v ); + VectorAdd( v, pMapDisp->vectorOffsets[j], v ); + + dist = VectorLength( v ); + VectorNormalize( v ); + + vectorDisps[j] = v; + dispDists[j] = dist; + } + + + // Use CCoreDispInfo to setup the actual vertex positions. + pCoreDispInfo->InitDispInfo( pMapDisp->power, pMapDisp->minTess, pMapDisp->smoothingAngle, + pMapDisp->alphaValues, vectorDisps, dispDists ); + pCoreDispInfo->Create(); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void EmitInitialDispInfos( void ) +{ + int i; + mapdispinfo_t *pMapDisp; + ddispinfo_t *pDisp; + Vector v; + + // Calculate the total number of verts. + int nTotalVerts = 0; + int nTotalTris = 0; + for ( i=0; i < nummapdispinfo; i++ ) + { + nTotalVerts += NUM_DISP_POWER_VERTS( mapdispinfo[i].power ); + nTotalTris += NUM_DISP_POWER_TRIS( mapdispinfo[i].power ); + } + + // Clear the output arrays.. + g_dispinfo.Purge(); + g_dispinfo.SetSize( nummapdispinfo ); + g_DispVerts.SetSize( nTotalVerts ); + g_DispTris.SetSize( nTotalTris ); + + int iCurVert = 0; + int iCurTri = 0; + for( i = 0; i < nummapdispinfo; i++ ) + { + pDisp = &g_dispinfo[i]; + pMapDisp = &mapdispinfo[i]; + + CDispVert *pOutVerts = &g_DispVerts[iCurVert]; + CDispTri *pOutTris = &g_DispTris[iCurTri]; + + // Setup the vert pointers. + pDisp->m_iDispVertStart = iCurVert; + pDisp->m_iDispTriStart = iCurTri; + iCurVert += NUM_DISP_POWER_VERTS( pMapDisp->power ); + iCurTri += NUM_DISP_POWER_TRIS( pMapDisp->power ); + + // + // save power, minimum tesselation, and smoothing angle + // + pDisp->power = pMapDisp->power; + + // If the high bit is set - this is FLAGS! + pDisp->minTess = pMapDisp->flags; + pDisp->minTess |= 0x80000000; +// pDisp->minTess = pMapDisp->minTess; + pDisp->smoothingAngle = pMapDisp->smoothingAngle; + pDisp->m_iMapFace = (unsigned short)-2; + + // get surface contents + pDisp->contents = pMapDisp->face.contents; + + pDisp->startPosition = pMapDisp->startPosition; + // + // add up the vectorOffsets and displacements, save alphas (per vertex) + // + int size = ( ( ( 1 << pDisp->power ) + 1 ) * ( ( 1 << pDisp->power ) + 1 ) ); + for( int j = 0; j < size; j++ ) + { + VectorScale( pMapDisp->vectorDisps[j], pMapDisp->dispDists[j], v ); + VectorAdd( v, pMapDisp->vectorOffsets[j], v ); + + float dist = VectorLength( v ); + VectorNormalize( v ); + + VectorCopy( v, pOutVerts[j].m_vVector ); + pOutVerts[j].m_flDist = dist; + + pOutVerts[j].m_flAlpha = pMapDisp->alphaValues[j]; + } + + int nTriCount = ( (1 << (pDisp->power)) * (1 << (pDisp->power)) * 2 ); + for ( int iTri = 0; iTri< nTriCount; ++iTri ) + { + pOutTris[iTri].m_uiTags = pMapDisp->triTags[iTri]; + } + //=================================================================== + //=================================================================== + + // save the index for face data reference + pMapDisp->face.dispinfo = i; + } +} + + +void ExportCoreDispNeighborData( const CCoreDispInfo *pIn, ddispinfo_t *pOut ) +{ + for ( int i=0; i < 4; i++ ) + { + pOut->m_EdgeNeighbors[i] = *pIn->GetEdgeNeighbor( i ); + pOut->m_CornerNeighbors[i] = *pIn->GetCornerNeighbors( i ); + } +} + +void ExportNeighborData( CCoreDispInfo **ppListBase, ddispinfo_t *pBSPDispInfos, int listSize ) +{ + FindNeighboringDispSurfs( ppListBase, listSize ); + + // Export the neighbor data. + for ( int i=0; i < nummapdispinfo; i++ ) + { + ExportCoreDispNeighborData( g_CoreDispInfos[i], &pBSPDispInfos[i] ); + } +} + + +void ExportCoreDispAllowedVertList( const CCoreDispInfo *pIn, ddispinfo_t *pOut ) +{ + ErrorIfNot( + pIn->GetAllowedVerts().GetNumDWords() == sizeof( pOut->m_AllowedVerts ) / 4, + ("ExportCoreDispAllowedVertList: size mismatch") + ); + for ( int i=0; i < pIn->GetAllowedVerts().GetNumDWords(); i++ ) + pOut->m_AllowedVerts[i] = pIn->GetAllowedVerts().GetDWord( i ); +} + + +void ExportAllowedVertLists( CCoreDispInfo **ppListBase, ddispinfo_t *pBSPDispInfos, int listSize ) +{ + SetupAllowedVerts( ppListBase, listSize ); + + for ( int i=0; i < listSize; i++ ) + { + ExportCoreDispAllowedVertList( ppListBase[i], &pBSPDispInfos[i] ); + } +} + +bool FindEnclosingTri( + const Vector2D &vert, + CUtlVector &vertCoords, + CUtlVector &indices, + int *pStartVert, + float bcCoords[3] ) +{ + for ( int i=0; i < indices.Count(); i += 3 ) + { + GetBarycentricCoords2D( + vertCoords[indices[i+0]], + vertCoords[indices[i+1]], + vertCoords[indices[i+2]], + vert, + bcCoords ); + + if ( bcCoords[0] >= 0 && bcCoords[0] <= 1 && + bcCoords[1] >= 0 && bcCoords[1] <= 1 && + bcCoords[2] >= 0 && bcCoords[2] <= 1 ) + { + *pStartVert = i; + return true; + } + } + + return false; +} + +void SnapRemainingVertsToSurface( CCoreDispInfo *pCoreDisp, ddispinfo_t *pDispInfo ) +{ + // First, tesselate the displacement. + CUtlVector indices; + CVBSPTesselateHelper helper; + helper.m_pIndices = &indices; + helper.m_pActiveVerts = pCoreDisp->GetAllowedVerts().Base(); + helper.m_pPowerInfo = pCoreDisp->GetPowerInfo(); + ::TesselateDisplacement( &helper ); + + // Figure out which verts are actually referenced in the tesselation. + CUtlVector vertsTouched; + vertsTouched.SetSize( pCoreDisp->GetSize() ); + memset( vertsTouched.Base(), 0, sizeof( bool ) * vertsTouched.Count() ); + + for ( int i=0; i < indices.Count(); i++ ) + vertsTouched[ indices[i] ] = true; + + // Generate 2D floating point coordinates for each vertex. We use these to generate + // barycentric coordinates, and the scale doesn't matter. + CUtlVector vertCoords; + vertCoords.SetSize( pCoreDisp->GetSize() ); + for ( int y=0; y < pCoreDisp->GetHeight(); y++ ) + { + for ( int x=0; x < pCoreDisp->GetWidth(); x++ ) + vertCoords[y*pCoreDisp->GetWidth()+x].Init( x, y ); + } + + // Now, for each vert not touched, snap its position to the main surface. + for ( int y=0; y < pCoreDisp->GetHeight(); y++ ) + { + for ( int x=0; x < pCoreDisp->GetWidth(); x++ ) + { + int index = y * pCoreDisp->GetWidth() + x; + if ( !( vertsTouched[index] ) ) + { + float bcCoords[3]; + int iStartVert = -1; + if ( FindEnclosingTri( vertCoords[index], vertCoords, indices, &iStartVert, bcCoords ) ) + { + const Vector &A = pCoreDisp->GetVert( indices[iStartVert+0] ); + const Vector &B = pCoreDisp->GetVert( indices[iStartVert+1] ); + const Vector &C = pCoreDisp->GetVert( indices[iStartVert+2] ); + Vector vNewPos = A*bcCoords[0] + B*bcCoords[1] + C*bcCoords[2]; + + // This is kind of cheesy, but it gets the job done. Since the CDispVerts store the + // verts relative to some other offset, we'll just offset their position instead + // of setting it directly. + Vector vOffset = vNewPos - pCoreDisp->GetVert( index ); + + // Modify the mapfile vert. + CDispVert *pVert = &g_DispVerts[pDispInfo->m_iDispVertStart + index]; + pVert->m_vVector = (pVert->m_vVector * pVert->m_flDist) + vOffset; + pVert->m_flDist = 1; + + // Modify the CCoreDispInfo vert (although it probably won't be used later). + pCoreDisp->SetVert( index, vNewPos ); + } + else + { + // This shouldn't happen because it would mean that the triangulation that + // disp_tesselation.h produced was missing a chunk of the space that the + // displacement covers. + // It also could indicate a floating-point epsilon error.. check to see if + // FindEnclosingTri finds a triangle that -almost- encloses the vert. + Assert( false ); + } + } + } + } +} + +void SnapRemainingVertsToSurface( CCoreDispInfo **ppListBase, ddispinfo_t *pBSPDispInfos, int listSize ) +{ +//g_pPad = ScratchPad3D_Create(); + for ( int i=0; i < listSize; i++ ) + { + SnapRemainingVertsToSurface( ppListBase[i], &pBSPDispInfos[i] ); + } +} + +void EmitDispLMAlphaAndNeighbors() +{ + int i; + + Msg( "Finding displacement neighbors...\n" ); + + // Build the CCoreDispInfos. + CUtlVector faces; + + // Create the core dispinfos and init them for use as CDispUtilsHelpers. + for ( int iDisp = 0; iDisp < nummapdispinfo; ++iDisp ) + { + CCoreDispInfo *pDisp = new CCoreDispInfo; + if ( !pDisp ) + { + g_CoreDispInfos.Purge(); + return; + } + + int nIndex = g_CoreDispInfos.AddToTail(); + pDisp->SetListIndex( nIndex ); + g_CoreDispInfos[nIndex] = pDisp; + } + + for ( i=0; i < nummapdispinfo; i++ ) + { + g_CoreDispInfos[i]->SetDispUtilsHelperInfo( g_CoreDispInfos.Base(), nummapdispinfo ); + } + + faces.SetSize( nummapdispinfo ); + + int nMemSize = texinfo.Count() * sizeof(int); + int *pSwappedTexInfos = (int*)stackalloc( nMemSize ); + memset( pSwappedTexInfos, 0xFF, nMemSize ); + for( i = 0; i < numfaces; i++ ) + { + dface_t *pFace = &dfaces[i]; + + if( pFace->dispinfo == -1 ) + continue; + + mapdispinfo_t *pMapDisp = &mapdispinfo[pFace->dispinfo]; + + // Set the displacement's face index. + ddispinfo_t *pDisp = &g_dispinfo[pFace->dispinfo]; + pDisp->m_iMapFace = i; + + // Get a CCoreDispInfo. All we need is the triangles and lightmap texture coordinates. + CCoreDispInfo *pCoreDispInfo = g_CoreDispInfos[pFace->dispinfo]; + DispMapToCoreDispInfo( pMapDisp, pCoreDispInfo, pFace, pSwappedTexInfos ); + + faces[pFace->dispinfo] = pFace; + } + stackfree( pSwappedTexInfos ); + + // Generate and export neighbor data. + ExportNeighborData( g_CoreDispInfos.Base(), g_dispinfo.Base(), nummapdispinfo ); + + // Generate and export the active vert lists. + ExportAllowedVertLists( g_CoreDispInfos.Base(), g_dispinfo.Base(), nummapdispinfo ); + + + // Now that we know which vertices are actually going to be around, snap the ones that won't + // be around onto the slightly-reduced mesh. This is so the engine's ray test code and + // overlay code works right. + SnapRemainingVertsToSurface( g_CoreDispInfos.Base(), g_dispinfo.Base(), nummapdispinfo ); + + Msg( "Finding lightmap sample positions...\n" ); + for ( i=0; i < nummapdispinfo; i++ ) + { + dface_t *pFace = faces[i]; + ddispinfo_t *pDisp = &g_dispinfo[pFace->dispinfo]; + CCoreDispInfo *pCoreDispInfo = g_CoreDispInfos[i]; + + pDisp->m_iLightmapSamplePositionStart = g_DispLightmapSamplePositions.Count(); + + CalculateLightmapSamplePositions( pCoreDispInfo, pFace, g_DispLightmapSamplePositions ); + } + + StartPacifier( "Displacement Alpha : "); + + // Build lightmap alphas. + int dispCount = 0; // How many we've processed. + for( i = 0; i < nummapdispinfo; i++ ) + { + dface_t *pFace = faces[i]; + + Assert( pFace->dispinfo == i ); + ddispinfo_t *pDisp = &g_dispinfo[pFace->dispinfo]; + + // Allocate space for the alpha values. + pDisp->m_iLightmapAlphaStart = 0; // not used anymore + + ++dispCount; + } + + EndPacifier(); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void DispGetFaceInfo( mapbrush_t *pBrush ) +{ + int i; + side_t *pSide; + + // we don't support displacement on entities at the moment!! + if( pBrush->entitynum != 0 ) + { + char* pszEntityName = ValueForKey( &g_LoadingMap->entities[pBrush->entitynum], "classname" ); + Error( "Error: displacement found on a(n) %s entity - not supported (entity %d, brush %d)\n", pszEntityName, pBrush->entitynum, pBrush->brushnum ); + } + + for( i = 0; i < pBrush->numsides; i++ ) + { + pSide = &pBrush->original_sides[i]; + if( pSide->pMapDisp ) + { + // error checking!! + if( pSide->winding->numpoints != 4 ) + Error( "Trying to create a non-quad displacement! (entity %d, brush %d)\n", pBrush->entitynum, pBrush->brushnum ); + pSide->pMapDisp->face.originalface = pSide; + pSide->pMapDisp->face.texinfo = pSide->texinfo; + pSide->pMapDisp->face.dispinfo = -1; + pSide->pMapDisp->face.planenum = pSide->planenum; + pSide->pMapDisp->face.numpoints = pSide->winding->numpoints; + pSide->pMapDisp->face.w = CopyWinding( pSide->winding ); + pSide->pMapDisp->face.contents = pBrush->contents; + + pSide->pMapDisp->face.merged = FALSE; + pSide->pMapDisp->face.split[0] = FALSE; + pSide->pMapDisp->face.split[1] = FALSE; + + pSide->pMapDisp->entitynum = pBrush->entitynum; + pSide->pMapDisp->brushSideID = pSide->id; + } + } +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool HasDispInfo( mapbrush_t *pBrush ) +{ + int i; + side_t *pSide; + + for( i = 0; i < pBrush->numsides; i++ ) + { + pSide = &pBrush->original_sides[i]; + if( pSide->pMapDisp ) + return true; + } + + return false; +} diff --git a/mp/src/utils/vbsp/disp_vbsp.h b/mp/src/utils/vbsp/disp_vbsp.h new file mode 100644 index 00000000..1c26775e --- /dev/null +++ b/mp/src/utils/vbsp/disp_vbsp.h @@ -0,0 +1,46 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef VBSP_DISPINFO_H +#define VBSP_DISPINFO_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "vbsp.h" + + + +class CCoreDispInfo; + + +extern CUtlVector g_CoreDispInfos; + + +// Setup initial entries in g_dispinfo with some of the vertex data from the mapdisps. +void EmitInitialDispInfos(); + +// Resample vertex alpha into lightmap alpha for displacement surfaces so LOD popping artifacts are +// less noticeable on the mid-to-high end. +// +// Also builds neighbor data. +void EmitDispLMAlphaAndNeighbors(); + +// Setup a CCoreDispInfo given a mapdispinfo_t. +// If pFace is non-NULL, then lightmap texture coordinates will be generated. +void DispMapToCoreDispInfo( mapdispinfo_t *pMapDisp, + CCoreDispInfo *pCoreDispInfo, dface_t *pFace, int *pSwappedTexInfos ); + + +void DispGetFaceInfo( mapbrush_t *pBrush ); +bool HasDispInfo( mapbrush_t *pBrush ); + +// Computes the bounds for a disp info +void ComputeDispInfoBounds( int dispinfo, Vector& mins, Vector& maxs ); + +#endif // VBSP_DISPINFO_H diff --git a/mp/src/utils/vbsp/faces.cpp b/mp/src/utils/vbsp/faces.cpp new file mode 100644 index 00000000..13ed9d61 --- /dev/null +++ b/mp/src/utils/vbsp/faces.cpp @@ -0,0 +1,1810 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// faces.c + +#include "vbsp.h" +#include "utlvector.h" +#include "utilmatlib.h" +#include +#include "mstristrip.h" +#include "tier1/strtools.h" +#include "materialpatch.h" +/* + + some faces will be removed before saving, but still form nodes: + + the insides of sky volumes + meeting planes of different water current volumes + +*/ + +// undefine for dumb linear searches +#define USE_HASHING + +#define INTEGRAL_EPSILON 0.01 +#define POINT_EPSILON 0.1 +#define OFF_EPSILON 0.25 + +int c_merge; +int c_subdivide; + +int c_totalverts; +int c_uniqueverts; +int c_degenerate; +int c_tjunctions; +int c_faceoverflows; +int c_facecollapse; +int c_badstartverts; + +#define MAX_SUPERVERTS 512 +int superverts[MAX_SUPERVERTS]; +int numsuperverts; + +face_t *edgefaces[MAX_MAP_EDGES][2]; +int firstmodeledge = 1; +int firstmodelface; + +int c_tryedges; + +Vector edge_dir; +Vector edge_start; +vec_t edge_len; + +int num_edge_verts; +int edge_verts[MAX_MAP_VERTS]; + + +float g_maxLightmapDimension = 32; + + +face_t *NewFaceFromFace (face_t *f); + +// Used to speed up GetEdge2(). Holds a list of edges connected to each vert. +CUtlVector g_VertEdgeList[MAX_MAP_VERTS]; + + +//=========================================================================== + +typedef struct hashvert_s +{ + struct hashvert_s *next; + int num; +} hashvert_t; + +#define HASH_BITS 7 +#define HASH_SIZE (COORD_EXTENT>>HASH_BITS) + + +int vertexchain[MAX_MAP_VERTS]; // the next vertex in a hash chain +int hashverts[HASH_SIZE*HASH_SIZE]; // a vertex number, or 0 for no verts + +//face_t *edgefaces[MAX_MAP_EDGES][2]; + +//============================================================================ + + +unsigned HashVec (Vector& vec) +{ + int x, y; + + x = (MAX_COORD_INTEGER + (int)(vec[0]+0.5)) >> HASH_BITS; + y = (MAX_COORD_INTEGER + (int)(vec[1]+0.5)) >> HASH_BITS; + + if ( x < 0 || x >= HASH_SIZE || y < 0 || y >= HASH_SIZE ) + Error ("HashVec: point outside valid range"); + + return y*HASH_SIZE + x; +} + +#ifdef USE_HASHING +/* +============= +GetVertex + +Uses hashing +============= +*/ +int GetVertexnum (Vector& in) +{ + int h; + int i; + Vector vert; + int vnum; + + c_totalverts++; + + for (i=0 ; i<3 ; i++) + { + if ( fabs(in[i] - (int)(in[i]+0.5)) < INTEGRAL_EPSILON) + vert[i] = (int)(in[i]+0.5); + else + vert[i] = in[i]; + } + + h = HashVec (vert); + + for (vnum=hashverts[h] ; vnum ; vnum=vertexchain[vnum]) + { + Vector& p = dvertexes[vnum].point; + if ( fabs(p[0]-vert[0]) MAX_COORD_INTEGER) + Error ("GetVertexnum: outside world, vertex %.1f %.1f %.1f", v.x, v.y, v.z); + } + + // search for an existing vertex match + for (i=0, dv=dvertexes ; ipoint[j]; + if ( d > POINT_EPSILON || d < -POINT_EPSILON) + break; + } + if (j == 3) + return i; // a match + } + + // new point + if (numvertexes == MAX_MAP_VERTS) + Error ("Too many unique verts, max = %d (map has too much brush geometry)\n", MAX_MAP_VERTS); + VectorCopy (v, dv->point); + numvertexes++; + c_uniqueverts++; + + return numvertexes-1; +} +#endif + + +/* +================== +FaceFromSuperverts + +The faces vertexes have beeb added to the superverts[] array, +and there may be more there than can be held in a face (MAXEDGES). + +If less, the faces vertexnums[] will be filled in, otherwise +face will reference a tree of split[] faces until all of the +vertexnums can be added. + +superverts[base] will become face->vertexnums[0], and the others +will be circularly filled in. +================== +*/ +void FaceFromSuperverts (face_t **pListHead, face_t *f, int base) +{ + face_t *newf; + int remaining; + int i; + + remaining = numsuperverts; + while (remaining > MAXEDGES) + { // must split into two faces, because of vertex overload + c_faceoverflows++; + + newf = NewFaceFromFace (f); + f->split[0] = newf; + + newf->next = *pListHead; + *pListHead = newf; + + newf->numpoints = MAXEDGES; + for (i=0 ; ivertexnums[i] = superverts[(i+base)%numsuperverts]; + + f->split[1] = NewFaceFromFace (f); + f = f->split[1]; + + f->next = *pListHead; + *pListHead = f; + + remaining -= (MAXEDGES-2); + base = (base+MAXEDGES-1)%numsuperverts; + } + + // copy the vertexes back to the face + f->numpoints = remaining; + for (i=0 ; ivertexnums[i] = superverts[(i+base)%numsuperverts]; +} + + +/* +================== +EmitFaceVertexes +================== +*/ +void EmitFaceVertexes (face_t **pListHead, face_t *f) +{ + winding_t *w; + int i; + + if (f->merged || f->split[0] || f->split[1]) + return; + + w = f->w; + for (i=0 ; inumpoints ; i++) + { + if (noweld) + { // make every point unique + if (numvertexes == MAX_MAP_VERTS) + Error ("Too many unique verts, max = %d (map has too much brush geometry)\n", MAX_MAP_VERTS); + superverts[i] = numvertexes; + VectorCopy (w->p[i], dvertexes[numvertexes].point); + numvertexes++; + c_uniqueverts++; + c_totalverts++; + } + else + superverts[i] = GetVertexnum (w->p[i]); + } + numsuperverts = w->numpoints; + + // this may fragment the face if > MAXEDGES + FaceFromSuperverts (pListHead, f, 0); +} + +/* +================== +EmitNodeFaceVertexes_r +================== +*/ +void EmitNodeFaceVertexes_r (node_t *node) +{ + int i; + face_t *f; + + if (node->planenum == PLANENUM_LEAF) + { + // leaf faces are emitted in second pass + return; + } + + for (f=node->faces ; f ; f=f->next) + { + EmitFaceVertexes (&node->faces, f); + } + + for (i=0 ; i<2 ; i++) + { + EmitNodeFaceVertexes_r (node->children[i]); + } +} + +void EmitLeafFaceVertexes( face_t **ppLeafFaceList ) +{ + face_t *f = *ppLeafFaceList; + + while ( f ) + { + EmitFaceVertexes( ppLeafFaceList, f ); + f = f->next; + } +} + + +#ifdef USE_HASHING +/* +========== +FindEdgeVerts + +Uses the hash tables to cut down to a small number +========== +*/ +void FindEdgeVerts (Vector& v1, Vector& v2) +{ + int x1, x2, y1, y2, t; + int x, y; + int vnum; + +#if 0 +{ + int i; + num_edge_verts = numvertexes-1; + for (i=0 ; i> HASH_BITS; + y1 = (MAX_COORD_INTEGER + (int)(v1[1]+0.5)) >> HASH_BITS; + x2 = (MAX_COORD_INTEGER + (int)(v2[0]+0.5)) >> HASH_BITS; + y2 = (MAX_COORD_INTEGER + (int)(v2[1]+0.5)) >> HASH_BITS; + + if (x1 > x2) + { + t = x1; + x1 = x2; + x2 = t; + } + if (y1 > y2) + { + t = y1; + y1 = y2; + y2 = t; + } +#if 0 + x1--; + x2++; + y1--; + y2++; + if (x1 < 0) + x1 = 0; + if (x2 >= HASH_SIZE) + x2 = HASH_SIZE; + if (y1 < 0) + y1 = 0; + if (y2 >= HASH_SIZE) + y2 = HASH_SIZE; +#endif + num_edge_verts = 0; + for (x=x1 ; x <= x2 ; x++) + { + for (y=y1 ; y <= y2 ; y++) + { + for (vnum=hashverts[y*HASH_SIZE+x] ; vnum ; vnum=vertexchain[vnum]) + { + edge_verts[num_edge_verts++] = vnum; + } + } + } +} + +#else +/* +========== +FindEdgeVerts + +Forced a dumb check of everything +========== +*/ +void FindEdgeVerts (Vector& v1, Vector& v2) +{ + int i; + + num_edge_verts = numvertexes-1; + for (i=0 ; i= end) + continue; // off an end + VectorMA (edge_start, dist, edge_dir, exact); + VectorSubtract (p, exact, off); + error = off.Length(); + + if (error > OFF_EPSILON) + continue; // not on the edge + + // break the edge + c_tjunctions++; + TestEdge (start, dist, p1, j, k+1); + TestEdge (dist, end, j, p2, k+1); + return; + } + + // the edge p1 to p2 is now free of tjunctions + if (numsuperverts >= MAX_SUPERVERTS) + Error ("Edge with too many vertices due to t-junctions. Max %d verts along an edge!\n", MAX_SUPERVERTS); + superverts[numsuperverts] = p1; + numsuperverts++; +} + + +// stores the edges that each vert is part of +struct face_vert_table_t +{ + face_vert_table_t() + { + edge0 = -1; + edge1 = -1; + } + + void AddEdge( int edge ) + { + if ( edge0 == -1 ) + { + edge0 = edge; + } + else + { + // can only have two edges + Assert(edge1==-1); + edge1 = edge; + } + } + + bool HasEdge( int edge ) const + { + if ( edge >= 0 ) + { + if ( edge0 == edge || edge1 == edge ) + return true; + } + return false; + } + + int edge0; + int edge1; +}; + +// if these two verts share an edge, they must be collinear +bool IsDiagonal( const face_vert_table_t &v0, const face_vert_table_t &v1 ) +{ + if ( v1.HasEdge(v0.edge0) || v1.HasEdge(v0.edge1) ) + return false; + + return true; +} + + +void Triangulate_r( CUtlVector &out, const CUtlVector &inIndices, const CUtlVector &poly ) +{ + Assert( inIndices.Count() > 2 ); + + // one triangle left, return + if ( inIndices.Count() == 3 ) + { + for ( int i = 0; i < inIndices.Count(); i++ ) + { + out.AddToTail( inIndices[i] ); + } + return; + } + + // check each pair of verts and see if they are diagonal (not on a shared edge) + // if so, split & recurse + for ( int i = 0; i < inIndices.Count(); i++ ) + { + int count = inIndices.Count(); + + // i + count is myself, i + count-1 is previous, so we need to stop at i+count-2 + for ( int j = 2; j < count-1; j++ ) + { + // if these two form a diagonal, split the poly along + // the diagonal and triangulate the two sub-polys + int index = inIndices[i]; + int nextArray = (i+j)%count; + int nextIndex = inIndices[nextArray]; + if ( IsDiagonal(poly[index], poly[nextIndex]) ) + { + // add the poly up to the diagonal + CUtlVector in1; + for ( int k = i; k != nextArray; k = (k+1)%count ) + { + in1.AddToTail(inIndices[k]); + } + in1.AddToTail(nextIndex); + + // add the rest of the poly starting with the diagonal + CUtlVector in2; + in2.AddToTail(index); + for ( int l = nextArray; l != i; l = (l+1)%count ) + { + in2.AddToTail(inIndices[l]); + } + + // triangulate the sub-polys + Triangulate_r( out, in1, poly ); + Triangulate_r( out, in2, poly ); + return; + } + } + } + + // didn't find a diagonal + Assert(0); +} + +/* +================== +FixFaceEdges + +================== +*/ +void FixFaceEdges (face_t **pList, face_t *f) +{ + int p1, p2; + int i; + Vector e2; + vec_t len; + int count[MAX_SUPERVERTS], start[MAX_SUPERVERTS]; + int base; + + if (f->merged || f->split[0] || f->split[1]) + return; + + numsuperverts = 0; + + int originalPoints = f->numpoints; + for (i=0 ; inumpoints ; i++) + { + p1 = f->vertexnums[i]; + p2 = f->vertexnums[(i+1)%f->numpoints]; + + VectorCopy (dvertexes[p1].point, edge_start); + VectorCopy (dvertexes[p2].point, e2); + + FindEdgeVerts (edge_start, e2); + + VectorSubtract (e2, edge_start, edge_dir); + len = VectorNormalize (edge_dir); + + start[i] = numsuperverts; + TestEdge (0, len, p1, p2, 0); + + count[i] = numsuperverts - start[i]; + } + + if (numsuperverts < 3) + { // entire face collapsed + f->numpoints = 0; + c_facecollapse++; + return; + } + + // we want to pick a vertex that doesn't have tjunctions + // on either side, which can cause artifacts on trifans, + // especially underwater + for (i=0 ; inumpoints ; i++) + { + if (count[i] == 1 && count[(i+f->numpoints-1)%f->numpoints] == 1) + break; + } + if (i == f->numpoints) + { + f->badstartvert = true; + c_badstartverts++; + base = 0; + + } + else + { // rotate the vertex order + base = start[i]; + } + + // this may fragment the face if > MAXEDGES + FaceFromSuperverts (pList, f, base); + + // if this is the world, then re-triangulate to sew cracks + if ( f->badstartvert && entity_num == 0 ) + { + CUtlVector poly; + CUtlVector inIndices; + CUtlVector outIndices; + poly.AddMultipleToTail( numsuperverts ); + for ( i = 0; i < originalPoints; i++ ) + { + // edge may not have output any points. Don't mark + if ( !count[i] ) + continue; + // mark each edge the point is a member of + // we'll use this as a fast "is collinear" test + for ( int j = 0; j <= count[i]; j++ ) + { + int polyIndex = (start[i] + j) % numsuperverts; + poly[polyIndex].AddEdge( i ); + } + } + for ( i = 0; i < numsuperverts; i++ ) + { + inIndices.AddToTail( i ); + } + Triangulate_r( outIndices, inIndices, poly ); + dprimitive_t &newPrim = g_primitives[g_numprimitives]; + f->firstPrimID = g_numprimitives; + g_numprimitives++; + f->numPrims = 1; + newPrim.firstIndex = g_numprimindices; + newPrim.firstVert = g_numprimverts; + newPrim.indexCount = outIndices.Count(); + newPrim.vertCount = 0; + newPrim.type = PRIM_TRILIST; + g_numprimindices += newPrim.indexCount; + if ( g_numprimitives > MAX_MAP_PRIMITIVES || g_numprimindices > MAX_MAP_PRIMINDICES ) + { + Error("Too many t-junctions to fix up! (%d prims, max %d :: %d indices, max %d)\n", g_numprimitives, MAX_MAP_PRIMITIVES, g_numprimindices, MAX_MAP_PRIMINDICES ); + } + for ( i = 0; i < outIndices.Count(); i++ ) + { + g_primindices[newPrim.firstIndex + i] = outIndices[i]; + } + } +} + +/* +================== +FixEdges_r +================== +*/ +void FixEdges_r (node_t *node) +{ + int i; + face_t *f; + + if (node->planenum == PLANENUM_LEAF) + { + return; + } + + for (f=node->faces ; f ; f=f->next) + FixFaceEdges (&node->faces, f); + + for (i=0 ; i<2 ; i++) + FixEdges_r (node->children[i]); +} + + +//----------------------------------------------------------------------------- +// Purpose: Fix the t-junctions on detail faces +//----------------------------------------------------------------------------- +void FixLeafFaceEdges( face_t **ppLeafFaceList ) +{ + face_t *f; + + for ( f = *ppLeafFaceList; f; f = f->next ) + { + FixFaceEdges( ppLeafFaceList, f ); + } +} + +/* +=========== +FixTjuncs + +=========== +*/ + +face_t *FixTjuncs (node_t *headnode, face_t *pLeafFaceList) +{ + // snap and merge all vertexes + qprintf ("---- snap verts ----\n"); + memset (hashverts, 0, sizeof(hashverts)); + memset (vertexchain, 0, sizeof(vertexchain)); + c_totalverts = 0; + c_uniqueverts = 0; + c_faceoverflows = 0; + EmitNodeFaceVertexes_r (headnode); + + // UNDONE: This count is wrong with tjuncs off on details - since + + // break edges on tjunctions + qprintf ("---- tjunc ----\n"); + c_tryedges = 0; + c_degenerate = 0; + c_facecollapse = 0; + c_tjunctions = 0; + + if ( g_bAllowDetailCracks ) + { + FixEdges_r (headnode); + EmitLeafFaceVertexes( &pLeafFaceList ); + FixLeafFaceEdges( &pLeafFaceList ); + } + else + { + EmitLeafFaceVertexes( &pLeafFaceList ); + if (!notjunc) + { + FixEdges_r (headnode); + FixLeafFaceEdges( &pLeafFaceList ); + } + } + + + qprintf ("%i unique from %i\n", c_uniqueverts, c_totalverts); + qprintf ("%5i edges degenerated\n", c_degenerate); + qprintf ("%5i faces degenerated\n", c_facecollapse); + qprintf ("%5i edges added by tjunctions\n", c_tjunctions); + qprintf ("%5i faces added by tjunctions\n", c_faceoverflows); + qprintf ("%5i bad start verts\n", c_badstartverts); + + return pLeafFaceList; +} + + +//======================================================== + +int c_faces; + +face_t *AllocFace (void) +{ + static int s_FaceId = 0; + + face_t *f; + + f = (face_t*)malloc(sizeof(*f)); + memset (f, 0, sizeof(*f)); + f->id = s_FaceId; + ++s_FaceId; + + c_faces++; + + return f; +} + +face_t *NewFaceFromFace (face_t *f) +{ + face_t *newf; + + newf = AllocFace (); + *newf = *f; + newf->merged = NULL; + newf->split[0] = newf->split[1] = NULL; + newf->w = NULL; + return newf; +} + +void FreeFace (face_t *f) +{ + if (f->w) + FreeWinding (f->w); + free (f); + c_faces--; +} + + +void FreeFaceList( face_t *pFaces ) +{ + while ( pFaces ) + { + face_t *next = pFaces->next; + + FreeFace( pFaces ); + pFaces = next; + } +} + +//======================================================== + +void GetEdge2_InitOptimizedList() +{ + for( int i=0; i < MAX_MAP_VERTS; i++ ) + g_VertEdgeList[i].RemoveAll(); +} + + +void IntSort( CUtlVector &theList ) +{ + for( int i=0; i < theList.Size()-1; i++ ) + { + if( theList[i] > theList[i+1] ) + { + int temp = theList[i]; + theList[i] = theList[i+1]; + theList[i+1] = temp; + if( i > 0 ) + i -= 2; + else + i = -1; + } + } +} + + +int AddEdge( int v1, int v2, face_t *f ) +{ + if (numedges >= MAX_MAP_EDGES) + Error ("Too many edges in map, max == %d", MAX_MAP_EDGES); + + g_VertEdgeList[v1].AddToTail( numedges ); + g_VertEdgeList[v2].AddToTail( numedges ); + IntSort( g_VertEdgeList[v1] ); + IntSort( g_VertEdgeList[v2] ); + + dedge_t *edge = &dedges[numedges]; + numedges++; + + edge->v[0] = v1; + edge->v[1] = v2; + edgefaces[numedges-1][0] = f; + return numedges - 1; +} + + +/* +================== +GetEdge + +Called by writebsp. +Don't allow four way edges +================== +*/ +int GetEdge2 (int v1, int v2, face_t *f) +{ + dedge_t *edge; + + c_tryedges++; + + if (!noshare) + { + // Check all edges connected to v1. + CUtlVector &theList = g_VertEdgeList[v1]; + for( int i=0; i < theList.Size(); i++ ) + { + int iEdge = theList[i]; + edge = &dedges[iEdge]; + if (v1 == edge->v[1] && v2 == edge->v[0] && edgefaces[iEdge][0]->contents == f->contents) + { + if (edgefaces[iEdge][1]) + continue; + + edgefaces[iEdge][1] = f; + return -iEdge; + } + } + } + + return AddEdge( v1, v2, f ); +} + +/* +=========================================================================== + +FACE MERGING + +=========================================================================== +*/ + +#define CONTINUOUS_EPSILON 0.001 + +/* +============= +TryMergeWinding + +If two polygons share a common edge and the edges that meet at the +common points are both inside the other polygons, merge them + +Returns NULL if the faces couldn't be merged, or the new face. +The originals will NOT be freed. +============= +*/ +winding_t *TryMergeWinding (winding_t *f1, winding_t *f2, Vector& planenormal) +{ + Vector *p1, *p2, *p3, *p4, *back; + winding_t *newf; + int i, j, k, l; + Vector normal, delta; + vec_t dot; + qboolean keep1, keep2; + + + // + // find a common edge + // + p1 = p2 = NULL; // stop compiler warning + j = 0; // + + for (i=0 ; inumpoints ; i++) + { + p1 = &f1->p[i]; + p2 = &f1->p[(i+1)%f1->numpoints]; + for (j=0 ; jnumpoints ; j++) + { + p3 = &f2->p[j]; + p4 = &f2->p[(j+1)%f2->numpoints]; + for (k=0 ; k<3 ; k++) + { + if (fabs((*p1)[k] - (*p4)[k]) > EQUAL_EPSILON) + break; + if (fabs((*p2)[k] - (*p3)[k]) > EQUAL_EPSILON) + break; + } + if (k==3) + break; + } + if (j < f2->numpoints) + break; + } + + if (i == f1->numpoints) + return NULL; // no matching edges + + // + // check slope of connected lines + // if the slopes are colinear, the point can be removed + // + back = &f1->p[(i+f1->numpoints-1)%f1->numpoints]; + VectorSubtract (*p1, *back, delta); + CrossProduct (planenormal, delta, normal); + VectorNormalize (normal); + + back = &f2->p[(j+2)%f2->numpoints]; + VectorSubtract (*back, *p1, delta); + dot = DotProduct (delta, normal); + if (dot > CONTINUOUS_EPSILON) + return NULL; // not a convex polygon + keep1 = (qboolean)(dot < -CONTINUOUS_EPSILON); + + back = &f1->p[(i+2)%f1->numpoints]; + VectorSubtract (*back, *p2, delta); + CrossProduct (planenormal, delta, normal); + VectorNormalize (normal); + + back = &f2->p[(j+f2->numpoints-1)%f2->numpoints]; + VectorSubtract (*back, *p2, delta); + dot = DotProduct (delta, normal); + if (dot > CONTINUOUS_EPSILON) + return NULL; // not a convex polygon + keep2 = (qboolean)(dot < -CONTINUOUS_EPSILON); + + // + // build the new polygon + // + newf = AllocWinding (f1->numpoints + f2->numpoints); + + // copy first polygon + for (k=(i+1)%f1->numpoints ; k != i ; k=(k+1)%f1->numpoints) + { + if (k==(i+1)%f1->numpoints && !keep2) + continue; + + VectorCopy (f1->p[k], newf->p[newf->numpoints]); + newf->numpoints++; + } + + // copy second polygon + for (l= (j+1)%f2->numpoints ; l != j ; l=(l+1)%f2->numpoints) + { + if (l==(j+1)%f2->numpoints && !keep1) + continue; + VectorCopy (f2->p[l], newf->p[newf->numpoints]); + newf->numpoints++; + } + + return newf; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool OverlaysAreEqual( face_t *f1, face_t *f2 ) +{ + // Check the overlay ids - see if they are the same. + if ( f1->originalface->aOverlayIds.Count() != f2->originalface->aOverlayIds.Count() ) + return false; + + int nOverlayCount = f1->originalface->aOverlayIds.Count(); + for ( int iOverlay = 0; iOverlay < nOverlayCount; ++iOverlay ) + { + int nOverlayId = f1->originalface->aOverlayIds[iOverlay]; + if ( f2->originalface->aOverlayIds.Find( nOverlayId ) == -1 ) + return false; + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool FaceOnWaterBrush( face_t *face ) +{ + side_t *pSide = face->originalface; + if ( !pSide ) + return false; + + if ( pSide->contents & ( CONTENTS_WATER | CONTENTS_SLIME ) ) + return true; + + return false; +} + +/* +============= +TryMerge + +If two polygons share a common edge and the edges that meet at the +common points are both inside the other polygons, merge them + +Returns NULL if the faces couldn't be merged, or the new face. +The originals will NOT be freed. +============= +*/ +face_t *TryMerge (face_t *f1, face_t *f2, Vector& planenormal) +{ + face_t *newf; + winding_t *nw; + + if (!f1->w || !f2->w) + return NULL; + if (f1->texinfo != f2->texinfo) + return NULL; + if (f1->planenum != f2->planenum) // on front and back sides + return NULL; + if (f1->contents != f2->contents) + return NULL; + if ( f1->originalface->smoothingGroups != f2->originalface->smoothingGroups ) + return NULL; + if ( !OverlaysAreEqual( f1, f2 ) ) + return NULL; + if ( nomergewater && ( FaceOnWaterBrush( f1 ) || FaceOnWaterBrush( f2 ) ) ) + return NULL; + + nw = TryMergeWinding (f1->w, f2->w, planenormal); + if (!nw) + return NULL; + + c_merge++; + newf = NewFaceFromFace (f1); + newf->w = nw; + + f1->merged = newf; + f2->merged = newf; + + return newf; +} + +/* +=============== +MergeFaceList +=============== +*/ +void MergeFaceList(face_t **pList) +{ + face_t *f1, *f2, *end; + face_t *merged; + plane_t *plane; + + merged = NULL; + + for (f1 = *pList; f1 ; f1 = f1->next) + { + if (f1->merged || f1->split[0] || f1->split[1]) + continue; + for (f2 = *pList; f2 != f1 ; f2=f2->next) + { + if (f2->merged || f2->split[0] || f2->split[1]) + continue; + + plane = &g_MainMap->mapplanes[f1->planenum]; + merged = TryMerge (f1, f2, plane->normal); + if (!merged) + continue; + + // add merged to the end of the face list + // so it will be checked against all the faces again + for (end = *pList; end->next ; end = end->next) + ; + merged->next = NULL; + end->next = merged; + break; + } + } +} + +//===================================================================== + +/* +=============== +SubdivideFace + +Chop up faces that are larger than we want in the surface cache +=============== +*/ +void SubdivideFace (face_t **pFaceList, face_t *f) +{ + float mins, maxs; + vec_t v; + vec_t luxelsPerWorldUnit; + int axis, i; + texinfo_t *tex; + Vector temp; + vec_t dist; + winding_t *w, *frontw, *backw; + + if ( f->merged || f->split[0] || f->split[1] ) + return; + +// special (non-surface cached) faces don't need subdivision + tex = &texinfo[f->texinfo]; + + if( tex->flags & SURF_NOLIGHT ) + { + return; + } + + for (axis = 0 ; axis < 2 ; axis++) + { + while (1) + { + mins = 999999; + maxs = -999999; + + VECTOR_COPY (tex->lightmapVecsLuxelsPerWorldUnits[axis], temp); + w = f->w; + for (i=0 ; inumpoints ; i++) + { + v = DotProduct (w->p[i], temp); + if (v < mins) + mins = v; + if (v > maxs) + maxs = v; + } +#if 0 + if (maxs - mins <= 0) + Error ("zero extents"); +#endif + if (maxs - mins <= g_maxLightmapDimension) + break; + + // split it + c_subdivide++; + + luxelsPerWorldUnit = VectorNormalize (temp); + + dist = ( mins + g_maxLightmapDimension - 1 ) / luxelsPerWorldUnit; + + ClipWindingEpsilon (w, temp, dist, ON_EPSILON, &frontw, &backw); + if (!frontw || !backw) + Error ("SubdivideFace: didn't split the polygon"); + + f->split[0] = NewFaceFromFace (f); + f->split[0]->w = frontw; + f->split[0]->next = *pFaceList; + *pFaceList = f->split[0]; + + f->split[1] = NewFaceFromFace (f); + f->split[1]->w = backw; + f->split[1]->next = *pFaceList; + *pFaceList = f->split[1]; + + SubdivideFace (pFaceList, f->split[0]); + SubdivideFace (pFaceList, f->split[1]); + return; + } + } +} + +void SubdivideFaceList(face_t **pFaceList) +{ + face_t *f; + + for (f = *pFaceList ; f ; f=f->next) + { + SubdivideFace (pFaceList, f); + } +} + + +//----------------------------------------------------------------------------- +// Assigns the bottom material to the bottom face +//----------------------------------------------------------------------------- +static bool AssignBottomWaterMaterialToFace( face_t *f ) +{ + // NOTE: This happens *after* cubemap fixup occurs, so we need to get the + // fixed-up bottom material for this + texinfo_t *pTexInfo = &texinfo[f->texinfo]; + dtexdata_t *pTexData = GetTexData( pTexInfo->texdata ); + const char *pMaterialName = TexDataStringTable_GetString( pTexData->nameStringTableID ); + + char pBottomMatName[512]; + if ( !GetValueFromPatchedMaterial( pMaterialName, "$bottommaterial", pBottomMatName, 512 ) ) + { + if( !Q_stristr( pMaterialName, "nodraw" ) && !Q_stristr( pMaterialName, "toolsskip" ) ) + { + Warning("error: material %s doesn't have a $bottommaterial\n", pMaterialName ); + } + return false; + } + + //Assert( mapplanes[f->planenum].normal.z < 0 ); + texinfo_t newTexInfo; + newTexInfo.flags = pTexInfo->flags; + int j, k; + for (j=0 ; j<2 ; j++) + { + for (k=0 ; k<4 ; k++) + { + newTexInfo.textureVecsTexelsPerWorldUnits[j][k] = pTexInfo->textureVecsTexelsPerWorldUnits[j][k]; + newTexInfo.lightmapVecsLuxelsPerWorldUnits[j][k] = pTexInfo->lightmapVecsLuxelsPerWorldUnits[j][k]; + } + } + newTexInfo.texdata = FindOrCreateTexData( pBottomMatName ); + f->texinfo = FindOrCreateTexInfo( newTexInfo ); + + return true; +} + + +//=========================================================================== + +int c_nodefaces; + +static void SubdivideFaceBySubdivSize( face_t *f, float subdivsize ); +void SubdivideFaceBySubdivSize( face_t *f ); + +/* +============ +FaceFromPortal + +============ +*/ +extern int FindOrCreateTexInfo( const texinfo_t &searchTexInfo ); + +face_t *FaceFromPortal (portal_t *p, int pside) +{ + face_t *f; + side_t *side; + int deltaContents; + + // portal does not bridge different visible contents + side = p->side; + if (!side) + return NULL; + + // allocate a new face + f = AllocFace(); + + // save the original "side" from the map brush -- portal->side + // see FindPortalSide(...) + f->originalface = side; + + // + // save material info + // + f->texinfo = side->texinfo; + f->dispinfo = -1; // all faces with displacement info are created elsewhere + f->smoothingGroups = side->smoothingGroups; + + // save plane info + f->planenum = (side->planenum & ~1) | pside; + if ( entity_num != 0 ) + { + // the brush model renderer doesn't use PLANEBACK, so write the real plane + // inside water faces can be flipped because they are generated on the inside of the brush + if ( p->nodes[pside]->contents & (CONTENTS_WATER|CONTENTS_SLIME) ) + { + f->planenum = (side->planenum & ~1) | pside; + } + else + { + f->planenum = side->planenum; + } + } + + // save portal info + f->portal = p; + f->fogVolumeLeaf = NULL; + + deltaContents = VisibleContents(p->nodes[!pside]->contents^p->nodes[pside]->contents); + + // don't show insides of windows or grates + if ( ((p->nodes[pside]->contents & CONTENTS_WINDOW) && deltaContents == CONTENTS_WINDOW) || + ((p->nodes[pside]->contents & CONTENTS_GRATE) && deltaContents == CONTENTS_GRATE) ) + { + FreeFace( f ); + return NULL; + } + + if ( p->nodes[pside]->contents & MASK_WATER ) + { + f->fogVolumeLeaf = p->nodes[pside]; + } + else if ( p->nodes[!pside]->contents & MASK_WATER ) + { + f->fogVolumeLeaf = p->nodes[!pside]; + } + + // If it's the underside of water, we need to figure out what material to use, etc. + if( ( p->nodes[pside]->contents & CONTENTS_WATER ) && deltaContents == CONTENTS_WATER ) + { + if ( !AssignBottomWaterMaterialToFace( f ) ) + { + FreeFace( f ); + return NULL; + } + } + + // + // generate the winding for the face and save face contents + // + if( pside ) + { + f->w = ReverseWinding(p->winding); + f->contents = p->nodes[1]->contents; + } + else + { + f->w = CopyWinding(p->winding); + f->contents = p->nodes[0]->contents; + } + + f->numPrims = 0; + f->firstPrimID = 0; + + // return the created face + return f; +} + +/* +=============== +MakeFaces_r + +If a portal will make a visible face, +mark the side that originally created it + + solid / empty : solid + solid / water : solid + water / empty : water + water / water : none +=============== +*/ +void MakeFaces_r (node_t *node) +{ + portal_t *p; + int s; + + // recurse down to leafs + if (node->planenum != PLANENUM_LEAF) + { + MakeFaces_r (node->children[0]); + MakeFaces_r (node->children[1]); + + // merge together all visible faces on the node + if (!nomerge) + MergeFaceList(&node->faces); + if (!nosubdiv) + SubdivideFaceList(&node->faces); + + return; + } + + // solid leafs never have visible faces + if (node->contents & CONTENTS_SOLID) + return; + + // see which portals are valid + for (p=node->portals ; p ; p = p->next[s]) + { + s = (p->nodes[1] == node); + + p->face[s] = FaceFromPortal (p, s); + if (p->face[s]) + { + c_nodefaces++; + p->face[s]->next = p->onnode->faces; + p->onnode->faces = p->face[s]; + } + } +} + +typedef winding_t *pwinding_t; + +static void PrintWinding( winding_t *w ) +{ + int i; + Msg( "\t---\n" ); + for( i = 0; i < w->numpoints; i++ ) + { + Msg( "\t%f %f %f\n", w->p[i].x, w->p[i].y, w->p[i].z ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Adds a winding to the current list of primverts +// Input : *w - the winding +// *pIndices - The output indices +// vertStart - the starting vert index +// vertCount - current count +// Output : int - output count including new verts from this winding +//----------------------------------------------------------------------------- +int AddWindingToPrimverts( const winding_t *w, unsigned short *pIndices, int vertStart, int vertCount ) +{ + for( int i = 0; i < w->numpoints; i++ ) + { + int j; + for( j = vertStart; j < vertStart + vertCount; j++ ) + { + Vector tmp = g_primverts[j].pos - w->p[i]; + + if( tmp.LengthSqr() < POINT_EPSILON*POINT_EPSILON ) + { + pIndices[i] = j; + break; + } + } + if ( j >= vertStart + vertCount ) + { + pIndices[i] = j; + g_primverts[j].pos = w->p[i]; + vertCount++; + g_numprimverts++; + if ( g_numprimverts > MAX_MAP_PRIMVERTS ) + { + Error( "Exceeded max water verts.\nIncrease surface subdivision size or lower your subdivision size in vmt files! (%d>%d)\n", + ( int )g_numprimverts, ( int )MAX_MAP_PRIMVERTS ); + } + } + } + + return vertCount; +} + + + +#pragma optimize( "g", off ) +#define USE_TRISTRIPS + +// UNDONE: Should split this function into subdivide and primitive building parts +// UNDONE: We should try building strips of shared verts for all water faces in a leaf +// since those will be drawn concurrently anyway. It should be more efficient. +static void SubdivideFaceBySubdivSize( face_t *f, float subdivsize ) +{ + // garymcthack - REFACTOR ME!!! + + vec_t dummy; + Vector hackNormal; + WindingPlane( f->w, hackNormal, &dummy ); + + // HACK - only subdivide stuff that is facing up or down (for water) + if( fabs(hackNormal[2]) < .9f ) + { + return; + } + + // Get the extents of the surface. + // garymcthack - this assumes a surface of constant z for now (for water). . can generalize later. + subdivsize = ( int )subdivsize; + winding_t *w; + w = CopyWinding( f->w ); + + Vector min, max; + WindingBounds( w, min, max ); + +#if 0 + Msg( "START WINDING: \n" ); + PrintWinding( w ); +#endif + int xStart, yStart, xEnd, yEnd, xSteps, ySteps; + xStart = ( int )subdivsize * ( int )( ( min[0] - subdivsize ) / subdivsize ); + xEnd = ( int )subdivsize * ( int )( ( max[0] + subdivsize ) / subdivsize ); + yStart = ( int )subdivsize * ( int )( ( min[1] - subdivsize ) / subdivsize ); + yEnd = ( int )subdivsize * ( int )( ( max[1] + subdivsize ) / subdivsize ); + xSteps = ( xEnd - xStart ) / subdivsize; + ySteps = ( yEnd - yStart ) / subdivsize; + int x, y; + int xi, yi; + winding_t **windings = ( winding_t ** )new pwinding_t[xSteps * ySteps]; + memset( windings, 0, sizeof( winding_t * ) * xSteps * ySteps ); + + for( yi = 0, y = yStart; y < yEnd; y += ( int )subdivsize, yi++ ) + { + for( xi = 0, x = xStart; x < xEnd; x += ( int )subdivsize, xi++ ) + { + winding_t *tempWinding, *frontWinding, *backWinding; + float planeDist; + Vector normal; + normal.Init( 1.0f, 0.0f, 0.0f ); + planeDist = ( float )x; + tempWinding = CopyWinding( w ); + ClipWindingEpsilon( tempWinding, normal, planeDist, ON_EPSILON, + &frontWinding, &backWinding ); + if( tempWinding ) + { + FreeWinding( tempWinding ); + } + if( backWinding ) + { + FreeWinding( backWinding ); + } + if( !frontWinding ) + { + continue; + } + tempWinding = frontWinding; + + normal.Init( -1.0f, 0.0f, 0.0f ); + planeDist = -( float )( x + subdivsize ); + ClipWindingEpsilon( tempWinding, normal, planeDist, ON_EPSILON, + &frontWinding, &backWinding ); + if( tempWinding ) + { + FreeWinding( tempWinding ); + } + if( backWinding ) + { + FreeWinding( backWinding ); + } + if( !frontWinding ) + { + continue; + } + tempWinding = frontWinding; + + normal.Init( 0.0f, 1.0f, 0.0f ); + planeDist = ( float )y; + ClipWindingEpsilon( tempWinding, normal, planeDist, ON_EPSILON, + &frontWinding, &backWinding ); + if( tempWinding ) + { + FreeWinding( tempWinding ); + } + if( backWinding ) + { + FreeWinding( backWinding ); + } + if( !frontWinding ) + { + continue; + } + tempWinding = frontWinding; + + normal.Init( 0.0f, -1.0f, 0.0f ); + planeDist = -( float )( y + subdivsize ); + ClipWindingEpsilon( tempWinding, normal, planeDist, ON_EPSILON, + &frontWinding, &backWinding ); + if( tempWinding ) + { + FreeWinding( tempWinding ); + } + if( backWinding ) + { + FreeWinding( backWinding ); + } + if( !frontWinding ) + { + continue; + } + +#if 0 + Msg( "output winding:\n" ); + PrintWinding( frontWinding ); +#endif + + if( frontWinding ) + { + windings[xi + yi * xSteps] = frontWinding; + } + } + } + FreeWinding( w ); + dprimitive_t &newPrim = g_primitives[g_numprimitives]; + f->firstPrimID = g_numprimitives; + f->numPrims = 1; + newPrim.firstIndex = g_numprimindices; + newPrim.firstVert = g_numprimverts; + newPrim.indexCount = 0; + newPrim.vertCount = 0; +#ifdef USE_TRISTRIPS + newPrim.type = PRIM_TRISTRIP; +#else + newPrim.type = PRIM_TRILIST; +#endif + + CUtlVector triListIndices; + int i; + for( i = 0; i < xSteps * ySteps; i++ ) + { + if( !windings[i] ) + { + continue; + } + unsigned short *pIndices = + ( unsigned short * )_alloca( windings[i]->numpoints * sizeof( unsigned short ) ); + // find indices for the verts. + newPrim.vertCount = AddWindingToPrimverts( windings[i], pIndices, newPrim.firstVert, newPrim.vertCount ); + + // Now that we have indices for the verts, fan-tesselate the polygon and spit out tris. + for( int j = 0; j < windings[i]->numpoints - 2; j++ ) + { + triListIndices.AddToTail( pIndices[0] ); + triListIndices.AddToTail( pIndices[j+1] ); + triListIndices.AddToTail( pIndices[j+2] ); + } + } + + delete [] windings; + // We've already updated the verts and have a trilist. . let's strip it! + if( !triListIndices.Size() ) + { + return; + } + +#ifdef USE_TRISTRIPS + int numTristripIndices; + WORD *pStripIndices = NULL; + Stripify( triListIndices.Size() / 3, triListIndices.Base(), &numTristripIndices, + &pStripIndices ); + Assert( pStripIndices ); + + // FIXME: Should also call ComputeVertexPermutation and reorder the verts. + + for( i = 0; i < numTristripIndices; i++ ) + { + Assert( pStripIndices[i] >= newPrim.firstVert && + pStripIndices[i] < newPrim.firstVert + newPrim.vertCount ); + g_primindices[newPrim.firstIndex + newPrim.indexCount] = pStripIndices[i]; + newPrim.indexCount++; + g_numprimindices++; + if( g_numprimindices > MAX_MAP_PRIMINDICES ) + { + Error( "Exceeded max water indicies.\nIncrease surface subdivision size! (%d>%d)\n", g_numprimindices, MAX_MAP_PRIMINDICES ); + } + } + delete [] pStripIndices; +#else + for( i = 0; i < triListIndices.Size(); i++ ) + { + g_primindices[newPrim.firstIndex + newPrim.indexCount] = triListIndices[i]; + newPrim.indexCount++; + g_numprimindices++; + if( g_numprimindices > MAX_MAP_PRIMINDICES ) + { + Error( "Exceeded max water indicies.\nIncrease surface subdivision size! (%d>%d)\n", g_numprimindices, MAX_MAP_PRIMINDICES ); + } + } +#endif + g_numprimitives++; // don't increment until we get here and are sure that we have a primitive. + if( g_numprimitives > MAX_MAP_PRIMITIVES ) + { + Error( "Exceeded max water primitives.\nIncrease surface subdivision size! (%d>%d)\n", ( int )g_numprimitives, ( int )MAX_MAP_PRIMITIVES ); + } +} + +void SubdivideFaceBySubdivSize( face_t *f ) +{ + if( f->numpoints == 0 || f->split[0] || f->split[1] || f->merged || !f->w ) + { + return; + } + // see if the face needs to be subdivided. + texinfo_t *pTexInfo = &texinfo[f->texinfo]; + dtexdata_t *pTexData = GetTexData( pTexInfo->texdata ); + bool bFound; + const char *pMaterialName = TexDataStringTable_GetString( pTexData->nameStringTableID ); + MaterialSystemMaterial_t matID = + FindOriginalMaterial( pMaterialName, &bFound, false ); + + if( !bFound ) + { + return; + } + const char *subdivsizeString = GetMaterialVar( matID, "$subdivsize" ); + if( subdivsizeString ) + { + float subdivSize = atof( subdivsizeString ); + if( subdivSize > 0.0f ) + { + // NOTE: Subdivision is unsupported and should be phased out + Warning("Using subdivision on %s\n", pMaterialName ); + SubdivideFaceBySubdivSize( f, subdivSize ); + } + } +} + +void SplitSubdividedFaces_Node_r( node_t *node ) +{ + if (node->planenum == PLANENUM_LEAF) + { + return; + } + face_t *f; + for( f = node->faces; f ;f = f->next ) + { + SubdivideFaceBySubdivSize( f ); + } + + // + // recursively output the other nodes + // + SplitSubdividedFaces_Node_r( node->children[0] ); + SplitSubdividedFaces_Node_r( node->children[1] ); +} + +void SplitSubdividedFaces( face_t *pLeafFaceList, node_t *headnode ) +{ + // deal with leaf faces. + face_t *f = pLeafFaceList; + while ( f ) + { + SubdivideFaceBySubdivSize( f ); + f = f->next; + } + + // deal with node faces. + SplitSubdividedFaces_Node_r( headnode ); +} + +#pragma optimize( "", on ) + +/* +============ +MakeFaces +============ +*/ +void MakeFaces (node_t *node) +{ + qprintf ("--- MakeFaces ---\n"); + c_merge = 0; + c_subdivide = 0; + c_nodefaces = 0; + + MakeFaces_r (node); + + qprintf ("%5i makefaces\n", c_nodefaces); + qprintf ("%5i merged\n", c_merge); + qprintf ("%5i subdivided\n", c_subdivide); +} \ No newline at end of file diff --git a/mp/src/utils/vbsp/faces.h b/mp/src/utils/vbsp/faces.h new file mode 100644 index 00000000..cfebc24f --- /dev/null +++ b/mp/src/utils/vbsp/faces.h @@ -0,0 +1,20 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef FACES_H +#define FACES_H +#ifdef _WIN32 +#pragma once +#endif + + +void GetEdge2_InitOptimizedList(); // Call this before calling GetEdge2() on a bunch of edges. +int AddEdge( int v1, int v2, face_t *f ); +int GetEdge2(int v1, int v2, face_t *f); + + +#endif // FACES_H diff --git a/mp/src/utils/vbsp/glfile.cpp b/mp/src/utils/vbsp/glfile.cpp new file mode 100644 index 00000000..236845bc --- /dev/null +++ b/mp/src/utils/vbsp/glfile.cpp @@ -0,0 +1,219 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +#include "vbsp.h" + +int c_glfaces; + +int PortalVisibleSides (portal_t *p) +{ + int fcon, bcon; + + if (!p->onnode) + return 0; // outside + + fcon = p->nodes[0]->contents; + bcon = p->nodes[1]->contents; + + // same contents never create a face + if (fcon == bcon) + return 0; + + // FIXME: is this correct now? + if (!fcon) + return 1; + if (!bcon) + return 2; + return 0; +} + +void OutputWinding (winding_t *w, FileHandle_t glview) +{ + static int level = 128; + vec_t light; + int i; + + CmdLib_FPrintf( glview, "%i\n", w->numpoints); + level+=28; + light = (level&255)/255.0; + for (i=0 ; inumpoints ; i++) + { + CmdLib_FPrintf(glview, "%6.3f %6.3f %6.3f %6.3f %6.3f %6.3f\n", + w->p[i][0], + w->p[i][1], + w->p[i][2], + light, + light, + light); + } + //CmdLib_FPrintf(glview, "\n"); +} + +void OutputWindingColor (winding_t *w, FileHandle_t glview, int r, int g, int b) +{ + int i; + + CmdLib_FPrintf( glview, "%i\n", w->numpoints); + float lr = r * (1.0f/255.0f); + float lg = g * (1.0f/255.0f); + float lb = b * (1.0f/255.0f); + for (i=0 ; inumpoints ; i++) + { + CmdLib_FPrintf(glview, "%6.3f %6.3f %6.3f %6.3f %6.3f %6.3f\n", + w->p[i][0], + w->p[i][1], + w->p[i][2], + lr, + lg, + lb); + } + //CmdLib_FPrintf(glview, "\n"); +} + +/* +============= +OutputPortal +============= +*/ +void OutputPortal (portal_t *p, FileHandle_t glview) +{ + winding_t *w; + int sides; + + sides = PortalVisibleSides (p); + if (!sides) + return; + + c_glfaces++; + + w = p->winding; + + if (sides == 2) // back side + w = ReverseWinding (w); + + OutputWinding (w, glview); + + if (sides == 2) + FreeWinding(w); +} + +/* +============= +WriteGLView_r +============= +*/ +void WriteGLView_r (node_t *node, FileHandle_t glview) +{ + portal_t *p, *nextp; + + if (node->planenum != PLANENUM_LEAF) + { + WriteGLView_r (node->children[0], glview); + WriteGLView_r (node->children[1], glview); + return; + } + + // write all the portals + for (p=node->portals ; p ; p=nextp) + { + if (p->nodes[0] == node) + { + OutputPortal (p, glview); + nextp = p->next[0]; + } + else + nextp = p->next[1]; + } +} + + +void WriteGLViewFaces_r( node_t *node, FileHandle_t glview ) +{ + portal_t *p, *nextp; + + if (node->planenum != PLANENUM_LEAF) + { + WriteGLViewFaces_r (node->children[0], glview); + WriteGLViewFaces_r (node->children[1], glview); + return; + } + + // write all the portals + for (p=node->portals ; p ; p=nextp) + { + int s = (p->nodes[1] == node); + + if ( p->face[s] ) + { + OutputWinding( p->face[s]->w, glview ); + } + nextp = p->next[s]; + } +} + +/* +============= +WriteGLView +============= +*/ +void WriteGLView (tree_t *tree, char *source) +{ + char name[1024]; + FileHandle_t glview; + + c_glfaces = 0; + sprintf (name, "%s%s.gl",outbase, source); + Msg("Writing %s\n", name); + + glview = g_pFileSystem->Open( name, "w" ); + if (!glview) + Error ("Couldn't open %s", name); + WriteGLView_r (tree->headnode, glview); + g_pFileSystem->Close( glview ); + + Msg("%5i c_glfaces\n", c_glfaces); +} + + +void WriteGLViewFaces( tree_t *tree, const char *pName ) +{ + char name[1024]; + FileHandle_t glview; + + c_glfaces = 0; + sprintf (name, "%s%s.gl", outbase, pName); + Msg("Writing %s\n", name); + + glview = g_pFileSystem->Open( name, "w" ); + if (!glview) + Error ("Couldn't open %s", name); + WriteGLViewFaces_r (tree->headnode, glview); + g_pFileSystem->Close( glview ); + + Msg("%5i c_glfaces\n", c_glfaces); +} + + +void WriteGLViewBrushList( bspbrush_t *pList, const char *pName ) +{ + char name[1024]; + FileHandle_t glview; + + sprintf (name, "%s%s.gl", outbase, pName ); + Msg("Writing %s\n", name); + + glview = g_pFileSystem->Open( name, "w" ); + if (!glview) + Error ("Couldn't open %s", name); + for ( bspbrush_t *pBrush = pList; pBrush; pBrush = pBrush->next ) + { + for (int i = 0; i < pBrush->numsides; i++ ) + OutputWinding( pBrush->sides[i].winding, glview ); + } + g_pFileSystem->Close( glview ); +} diff --git a/mp/src/utils/vbsp/ivp.cpp b/mp/src/utils/vbsp/ivp.cpp new file mode 100644 index 00000000..421b1b2e --- /dev/null +++ b/mp/src/utils/vbsp/ivp.cpp @@ -0,0 +1,1656 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include +#include "mathlib/vector.h" +#include "bspfile.h" +#include "bsplib.h" +#include "cmdlib.h" +#include "physdll.h" +#include "utlvector.h" +#include "vbsp.h" +#include "phyfile.h" +#include +#include "KeyValues.h" +#include "UtlBuffer.h" +#include "utlsymbol.h" +#include "utlrbtree.h" +#include "ivp.h" +#include "disp_ivp.h" +#include "materialpatch.h" +#include "bitvec.h" + +// bit per leaf +typedef CBitVec leafbitarray_t; + +// parameters for conversion to vphysics +#define NO_SHRINK 0.0f +// NOTE: vphysics maintains a minimum separation radius between objects +// This radius is set to 0.25, but it's symmetric. So shrinking potentially moveable +// brushes by 0.5 in every direction ensures that these brushes can be constructed +// touching the world, and constrained in place without collisions or friction +// UNDONE: Add a key to disable this shrinking if necessary +#define VPHYSICS_SHRINK (0.5f) // shrink BSP brushes by this much for collision +#define VPHYSICS_MERGE 0.01f // merge verts closer than this + +void EmitPhysCollision(); + +IPhysicsCollision *physcollision = NULL; +extern IPhysicsSurfaceProps *physprops; + +// a list of all of the materials in the world model +static CUtlVector s_WorldPropList; + +//----------------------------------------------------------------------------- +// Purpose: Write key/value pairs out to a memory buffer +//----------------------------------------------------------------------------- +CTextBuffer::CTextBuffer( void ) +{ +} +CTextBuffer::~CTextBuffer( void ) +{ +} + +void CTextBuffer::WriteText( const char *pText ) +{ + int len = strlen( pText ); + CopyData( pText, len ); +} + +void CTextBuffer::WriteIntKey( const char *pKeyName, int outputData ) +{ + char tmp[1024]; + + // FAIL! + if ( strlen(pKeyName) > 1000 ) + { + Msg("Error writing collision data %s\n", pKeyName ); + return; + } + sprintf( tmp, "\"%s\" \"%d\"\n", pKeyName, outputData ); + CopyData( tmp, strlen(tmp) ); +} + +void CTextBuffer::WriteStringKey( const char *pKeyName, const char *outputData ) +{ + CopyStringQuotes( pKeyName ); + CopyData( " ", 1 ); + CopyStringQuotes( outputData ); + CopyData( "\n", 1 ); +} + +void CTextBuffer::WriteFloatKey( const char *pKeyName, float outputData ) +{ + char tmp[1024]; + + // FAIL! + if ( strlen(pKeyName) > 1000 ) + { + Msg("Error writing collision data %s\n", pKeyName ); + return; + } + sprintf( tmp, "\"%s\" \"%f\"\n", pKeyName, outputData ); + CopyData( tmp, strlen(tmp) ); +} + +void CTextBuffer::WriteFloatArrayKey( const char *pKeyName, const float *outputData, int count ) +{ + char tmp[1024]; + + // FAIL! + if ( strlen(pKeyName) > 1000 ) + { + Msg("Error writing collision data %s\n", pKeyName ); + return; + } + sprintf( tmp, "\"%s\" \"", pKeyName ); + for ( int i = 0; i < count; i++ ) + { + char buf[80]; + + sprintf( buf, "%f ", outputData[i] ); + strcat( tmp, buf ); + } + strcat( tmp, "\"\n" ); + + CopyData( tmp, strlen(tmp) ); +} + +void CTextBuffer::CopyStringQuotes( const char *pString ) +{ + CopyData( "\"", 1 ); + CopyData( pString, strlen(pString) ); + CopyData( "\"", 1 ); +} + +void CTextBuffer::Terminate( void ) +{ + CopyData( "\0", 1 ); +} + +void CTextBuffer::CopyData( const char *pData, int len ) +{ + int offset = m_buffer.AddMultipleToTail( len ); + memcpy( m_buffer.Base() + offset, pData, len ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Writes a glview text file containing the collision surface in question +// Input : *pCollide - +// *pFilename - +//----------------------------------------------------------------------------- +void DumpCollideToGlView( CPhysCollide *pCollide, const char *pFilename ) +{ + if ( !pCollide ) + return; + + Msg("Writing %s...\n", pFilename ); + Vector *outVerts; + int vertCount = physcollision->CreateDebugMesh( pCollide, &outVerts ); + FILE *fp = fopen( pFilename, "w" ); + int triCount = vertCount / 3; + int vert = 0; + for ( int i = 0; i < triCount; i++ ) + { + fprintf( fp, "3\n" ); + fprintf( fp, "%6.3f %6.3f %6.3f 1 0 0\n", outVerts[vert].x, outVerts[vert].y, outVerts[vert].z ); + vert++; + fprintf( fp, "%6.3f %6.3f %6.3f 0 1 0\n", outVerts[vert].x, outVerts[vert].y, outVerts[vert].z ); + vert++; + fprintf( fp, "%6.3f %6.3f %6.3f 0 0 1\n", outVerts[vert].x, outVerts[vert].y, outVerts[vert].z ); + vert++; + } + fclose( fp ); + physcollision->DestroyDebugMesh( vertCount, outVerts ); +} + + +void DumpCollideToPHY( CPhysCollide *pCollide, CTextBuffer *text, const char *pFilename ) +{ + Msg("Writing %s...\n", pFilename ); + FILE *fp = fopen( pFilename, "wb" ); + phyheader_t header; + header.size = sizeof(header); + header.id = 0; + header.checkSum = 0; + header.solidCount = 1; + fwrite( &header, sizeof(header), 1, fp ); + int size = physcollision->CollideSize( pCollide ); + fwrite( &size, sizeof(int), 1, fp ); + + char *buf = (char *)malloc( size ); + physcollision->CollideWrite( buf, pCollide ); + fwrite( buf, size, 1, fp ); + + fwrite( text->GetData(), text->GetSize(), 1, fp ); + fclose( fp ); + free( buf ); +} + +CPhysCollisionEntry::CPhysCollisionEntry( CPhysCollide *pCollide ) +{ + m_pCollide = pCollide; +} + +unsigned int CPhysCollisionEntry::GetCollisionBinarySize() +{ + return physcollision->CollideSize( m_pCollide ); +} + +unsigned int CPhysCollisionEntry::WriteCollisionBinary( char *pDest ) +{ + return physcollision->CollideWrite( pDest, m_pCollide ); +} + +void CPhysCollisionEntry::DumpCollideFileName( const char *pName, int modelIndex, CTextBuffer *pTextBuffer ) +{ + char tmp[128]; + sprintf( tmp, "%s%03d.phy", pName, modelIndex ); + DumpCollideToPHY( m_pCollide, pTextBuffer, tmp ); + sprintf( tmp, "%s%03d.txt", pName, modelIndex ); + DumpCollideToGlView( m_pCollide, tmp ); +} + + +class CPhysCollisionEntrySolid : public CPhysCollisionEntry +{ +public: + CPhysCollisionEntrySolid( CPhysCollide *pCollide, const char *pMaterialName, float mass ); + + virtual void WriteToTextBuffer( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex ); + virtual void DumpCollide( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex ); + +private: + float m_volume; + float m_mass; + const char *m_pMaterial; +}; + + +CPhysCollisionEntrySolid::CPhysCollisionEntrySolid( CPhysCollide *pCollide, const char *pMaterialName, float mass ) + : CPhysCollisionEntry( pCollide ) +{ + m_volume = physcollision->CollideVolume( m_pCollide ); + m_mass = mass; + m_pMaterial = pMaterialName; +} + +void CPhysCollisionEntrySolid::DumpCollide( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex ) +{ + DumpCollideFileName( "collide", modelIndex, pTextBuffer ); +} + +void CPhysCollisionEntrySolid::WriteToTextBuffer( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex ) +{ + pTextBuffer->WriteText( "solid {\n" ); + pTextBuffer->WriteIntKey( "index", collideIndex ); + pTextBuffer->WriteFloatKey( "mass", m_mass ); + if ( m_pMaterial ) + { + pTextBuffer->WriteStringKey( "surfaceprop", m_pMaterial ); + } + if ( m_volume != 0.f ) + { + pTextBuffer->WriteFloatKey( "volume", m_volume ); + } + pTextBuffer->WriteText( "}\n" ); +} + + +class CPhysCollisionEntryStaticSolid : public CPhysCollisionEntry +{ +public: + CPhysCollisionEntryStaticSolid ( CPhysCollide *pCollide, int contentsMask ); + + virtual void WriteToTextBuffer( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex ); + virtual void DumpCollide( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex ); + +private: + int m_contentsMask; +}; + + +CPhysCollisionEntryStaticSolid ::CPhysCollisionEntryStaticSolid ( CPhysCollide *pCollide, int contentsMask ) + : CPhysCollisionEntry( pCollide ), m_contentsMask(contentsMask) +{ +} + +void CPhysCollisionEntryStaticSolid::DumpCollide( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex ) +{ + char tmp[128]; + sprintf( tmp, "static%02d", modelIndex ); + DumpCollideFileName( tmp, collideIndex, pTextBuffer ); +} + +void CPhysCollisionEntryStaticSolid::WriteToTextBuffer( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex ) +{ + pTextBuffer->WriteText( "staticsolid {\n" ); + pTextBuffer->WriteIntKey( "index", collideIndex ); + pTextBuffer->WriteIntKey( "contents", m_contentsMask ); + pTextBuffer->WriteText( "}\n" ); +} + +CPhysCollisionEntryStaticMesh::CPhysCollisionEntryStaticMesh( CPhysCollide *pCollide, const char *pMaterialName ) + : CPhysCollisionEntry( pCollide ) +{ + m_pMaterial = pMaterialName; +} + +void CPhysCollisionEntryStaticMesh::DumpCollide( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex ) +{ + char tmp[128]; + sprintf( tmp, "mesh%02d", modelIndex ); + DumpCollideFileName( tmp, collideIndex, pTextBuffer ); +} + +void CPhysCollisionEntryStaticMesh::WriteToTextBuffer( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex ) +{ + pTextBuffer->WriteText( "staticsolid {\n" ); + pTextBuffer->WriteIntKey( "index", collideIndex ); + pTextBuffer->WriteText( "}\n" ); +} + +class CPhysCollisionEntryFluid : public CPhysCollisionEntry +{ +public: + ~CPhysCollisionEntryFluid(); + CPhysCollisionEntryFluid( CPhysCollide *pCollide, const char *pSurfaceProp, float damping, const Vector &normal, float dist, int nContents ); + + virtual void WriteToTextBuffer( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex ); + virtual void DumpCollide( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex ); + +private: + char *m_pSurfaceProp; + float m_damping; + Vector m_surfaceNormal; + float m_surfaceDist; + int m_contentsMask; +}; + + +CPhysCollisionEntryFluid::CPhysCollisionEntryFluid( CPhysCollide *pCollide, const char *pSurfaceProp, float damping, const Vector &normal, float dist, int nContents ) + : CPhysCollisionEntry( pCollide ) +{ + m_surfaceNormal = normal; + m_surfaceDist = dist; + m_pSurfaceProp = new char[strlen(pSurfaceProp)+1]; + strcpy( m_pSurfaceProp, pSurfaceProp ); + m_damping = damping; + m_contentsMask = nContents; +} + +CPhysCollisionEntryFluid::~CPhysCollisionEntryFluid() +{ + delete[] m_pSurfaceProp; +} + +void CPhysCollisionEntryFluid::DumpCollide( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex ) +{ + char tmp[128]; + sprintf( tmp, "water%02d", modelIndex ); + DumpCollideFileName( tmp, collideIndex, pTextBuffer ); +} + +void CPhysCollisionEntryFluid::WriteToTextBuffer( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex ) +{ + pTextBuffer->WriteText( "fluid {\n" ); + pTextBuffer->WriteIntKey( "index", collideIndex ); + pTextBuffer->WriteStringKey( "surfaceprop", m_pSurfaceProp ); // write out water material + pTextBuffer->WriteFloatKey( "damping", m_damping ); // write out water damping + pTextBuffer->WriteIntKey( "contents", m_contentsMask ); // write out water contents + float array[4]; + m_surfaceNormal.CopyToArray( array ); + array[3] = m_surfaceDist; + pTextBuffer->WriteFloatArrayKey( "surfaceplane", array, 4 ); // write out water surface plane + pTextBuffer->WriteFloatArrayKey( "currentvelocity", vec3_origin.Base(), 3 ); // write out water velocity + pTextBuffer->WriteText( "}\n" ); +} + +// Get an index into the prop list of this prop (add it if necessary) +static int PropIndex( CUtlVector &propList, int propIndex ) +{ + for ( int i = 0; i < propList.Count(); i++ ) + { + if ( propList[i] == propIndex ) + return i+1; + } + + if ( propList.Count() < 126 ) + { + return propList.AddToTail( propIndex )+1; + } + + return 0; +} + +int RemapWorldMaterial( int materialIndexIn ) +{ + return PropIndex( s_WorldPropList, materialIndexIn ); +} + +typedef struct +{ + float normal[3]; + float dist; +} listplane_t; + +static void AddListPlane( CUtlVector *list, float x, float y, float z, float d ) +{ + listplane_t plane; + plane.normal[0] = x; + plane.normal[1] = y; + plane.normal[2] = z; + plane.dist = d; + + list->AddToTail( plane ); +} + +class CPlaneList +{ +public: + + CPlaneList( float shrink, float merge ); + ~CPlaneList( void ); + + void AddConvex( CPhysConvex *pConvex ); + + // add the brushes to the model + int AddBrushes( void ); + + // Adds a single brush as a convex object + void ReferenceBrush( int brushnumber ); + bool IsBrushReferenced( int brushnumber ); + + void ReferenceLeaf( int leafIndex ); + bool IsLeafReferenced( int leafIndex ); + int GetFirstBrushSide(); + +private: + + CPhysConvex *CPlaneList::BuildConvexForBrush( int brushnumber, float shrink, CPhysCollide *pCollideTest, float shrinkMinimum ); + +public: + CUtlVector m_convex; + + CUtlVector m_leafList; + int m_contentsMask; + + float m_shrink; + float m_merge; + bool *m_brushAdded; + float m_totalVolume; +}; + +CPlaneList::CPlaneList( float shrink, float merge ) +{ + m_shrink = shrink; + m_merge = merge; + m_contentsMask = MASK_SOLID; + m_brushAdded = new bool[numbrushes]; + memset( m_brushAdded, 0, sizeof(bool) * numbrushes ); + m_totalVolume = 0; + m_leafList.Purge(); +} + + +CPlaneList::~CPlaneList( void ) +{ + delete[] m_brushAdded; +} + + +void CPlaneList::AddConvex( CPhysConvex *pConvex ) +{ + if ( pConvex ) + { + m_totalVolume += physcollision->ConvexVolume( pConvex ); + m_convex.AddToTail( pConvex ); + } +} + +// Adds a single brush as a convex object +void CPlaneList::ReferenceBrush( int brushnumber ) +{ + if ( !(dbrushes[brushnumber].contents & m_contentsMask) ) + return; + + m_brushAdded[brushnumber] = true; + +} + + +bool CPlaneList::IsBrushReferenced( int brushnumber ) +{ + return m_brushAdded[brushnumber]; +} + +CPhysConvex *CPlaneList::BuildConvexForBrush( int brushnumber, float shrink, CPhysCollide *pCollideTest, float shrinkMinimum ) +{ + CUtlVector temp( 0, 32 ); + + for ( int i = 0; i < dbrushes[brushnumber].numsides; i++ ) + { + dbrushside_t *pside = dbrushsides + i + dbrushes[brushnumber].firstside; + if ( pside->bevel ) + continue; + + dplane_t *pplane = dplanes + pside->planenum; + float shrinkThisPlane = shrink; + + if ( i < g_MainMap->mapbrushes[brushnumber].numsides ) + { + if ( !g_MainMap->mapbrushes[brushnumber].original_sides[i].visible ) + { + // don't shrink brush sides with no visible components. + // this produces something closer to the ideal shrink than simply shrinking all planes + shrinkThisPlane = 0; + } + } + // Make sure shrinking won't swallow geometry along this axis. + if ( pCollideTest && shrinkThisPlane != 0 ) + { + Vector start = physcollision->CollideGetExtent( pCollideTest, vec3_origin, vec3_angle, pplane->normal ); + Vector end = physcollision->CollideGetExtent( pCollideTest, vec3_origin, vec3_angle, -pplane->normal ); + float thick = DotProduct( (end-start), pplane->normal ); + // NOTE: The object must be at least "shrinkMinimum" inches wide on each axis + if ( fabs(thick) < shrinkMinimum ) + { +#if _DEBUG + Warning("Can't shrink brush %d, plane %d (%.2f, %.2f, %.2f)\n", brushnumber, pside->planenum, pplane->normal[0], pplane->normal[1], pplane->normal[2] ); +#endif + shrinkThisPlane = 0; + } + } + AddListPlane( &temp, pplane->normal[0], pplane->normal[1], pplane->normal[2], pplane->dist - shrinkThisPlane ); + } + return physcollision->ConvexFromPlanes( (float *)temp.Base(), temp.Count(), m_merge ); +} + +int CPlaneList::AddBrushes( void ) +{ + int count = 0; + for ( int brushnumber = 0; brushnumber < numbrushes; brushnumber++ ) + { + if ( IsBrushReferenced(brushnumber) ) + { + CPhysConvex *pBrushConvex = NULL; + if ( m_shrink != 0 ) + { + // Make sure shrinking won't swallow this brush. + CPhysConvex *pConvex = BuildConvexForBrush( brushnumber, 0, NULL, 0 ); + CPhysCollide *pUnshrunkCollide = physcollision->ConvertConvexToCollide( &pConvex, 1 ); + pBrushConvex = BuildConvexForBrush( brushnumber, m_shrink, pUnshrunkCollide, m_shrink * 3 ); + physcollision->DestroyCollide( pUnshrunkCollide ); + } + else + { + pBrushConvex = BuildConvexForBrush( brushnumber, m_shrink, NULL, 1.0 ); + } + + if ( pBrushConvex ) + { + count++; + physcollision->SetConvexGameData( pBrushConvex, brushnumber ); + AddConvex( pBrushConvex ); + } + } + } + return count; +} + + +int CPlaneList::GetFirstBrushSide() +{ + for ( int brushnumber = 0; brushnumber < numbrushes; brushnumber++ ) + { + if ( IsBrushReferenced(brushnumber) ) + { + for ( int i = 0; i < dbrushes[brushnumber].numsides; i++ ) + { + int sideIndex = i + dbrushes[brushnumber].firstside; + dbrushside_t *pside = dbrushsides + sideIndex; + if ( pside->bevel ) + continue; + return sideIndex; + } + } + } + return 0; +} + +// UNDONE: Try using this kind of algorithm if we run into precision problems. +// NOTE: ConvexFromPlanes will be doing a bunch of matrix inversions that can suffer +// if plane normals are too close to each other... +#if 0 +void CPlaneList::AddBrushes( void ) +{ + CUtlVector temp; + for ( int brushnumber = 0; brushnumber < numbrushes; brushnumber++ ) + { + if ( IsBrushReferenced(brushnumber) ) + { + CUtlVector windings; + + for ( int i = 0; i < dbrushes[brushnumber].numsides; i++ ) + { + dbrushside_t *pside = dbrushsides + i + dbrushes[brushnumber].firstside; + if (pside->bevel) + continue; + dplane_t *pplane = dplanes + pside->planenum; + winding_t *w = BaseWindingForPlane( pplane->normal, pplane->dist - m_shrink ); + for ( int j = 0; j < dbrushes[brushnumber].numsides && w; j++ ) + { + if (i == j) + continue; + dbrushside_t *pClipSide = dbrushsides + j + dbrushes[brushnumber].firstside; + if (pClipSide->bevel) + continue; + dplane_t *pClipPlane = dplanes + pClipSide->planenum; + ChopWindingInPlace (&w, -pClipPlane->normal, -pClipPlane->dist+m_shrink, 0); //CLIP_EPSILON); + } + if ( w ) + { + windings.AddToTail( w ); + } + } + + CUtlVector vertList; + for ( int p = 0; p < windings.Count(); p++ ) + { + for ( int v = 0; v < windings[p]->numpoints; v++ ) + { + vertList.AddToTail( windings[p]->p + v ); + } + } + CPhysConvex *pConvex = physcollision->ConvexFromVerts( vertList.Base(), vertList.Count() ); + if ( pConvex ) + { + physcollision->SetConvexGameData( pConvex, brushnumber ); + AddConvex( pConvex ); + } + temp.RemoveAll(); + } + } +} +#endif + +// If I have a list of leaves, make sure this leaf is in it. +// Otherwise, process all leaves +bool CPlaneList::IsLeafReferenced( int leafIndex ) +{ + if ( !m_leafList.Count() ) + return true; + + for ( int i = 0; i < m_leafList.Count(); i++ ) + { + if ( m_leafList[i] == leafIndex ) + return true; + } + + return false; +} + +// Add a leaf to my list of interesting leaves +void CPlaneList::ReferenceLeaf( int leafIndex ) +{ + m_leafList.AddToTail( leafIndex ); +} + +static void VisitLeaves_r( CPlaneList &planes, int node ) +{ + if ( node < 0 ) + { + int leafIndex = -1 - node; + if ( planes.IsLeafReferenced(leafIndex) ) + { + int i; + + // Add the solids in the "empty" leaf + for ( i = 0; i < dleafs[leafIndex].numleafbrushes; i++ ) + { + int brushIndex = dleafbrushes[dleafs[leafIndex].firstleafbrush + i]; + planes.ReferenceBrush( brushIndex ); + } + } + } + else + { + dnode_t *pnode = dnodes + node; + + VisitLeaves_r( planes, pnode->children[0] ); + VisitLeaves_r( planes, pnode->children[1] ); + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +struct waterleaf_t +{ + Vector surfaceNormal; + float surfaceDist; + float minZ; + bool hasSurface; + int waterLeafIndex;// this is the submerged leaf + int planenum; //UNDONE: REMOVE + int surfaceTexInfo; // if hasSurface == true, this is the texinfo index for the water material + int outsideLeafIndex;// this is the leaf on the other side of the water surface + node_t *pNode; +}; + + + +// returns true if newleaf should appear before currentleaf in the list +static bool IsLowerLeaf( const waterleaf_t &newleaf, const waterleaf_t ¤tleaf ) +{ + if ( newleaf.hasSurface && currentleaf.hasSurface ) + { + // the one with the upmost pointing z goes first + if ( currentleaf.surfaceNormal.z > newleaf.surfaceNormal.z ) + return false; + + if ( fabs(currentleaf.surfaceNormal.z - newleaf.surfaceNormal.z) < 0.01 ) + { + if ( newleaf.surfaceDist < currentleaf.surfaceDist ) + return true; + } + return true; + } + else if ( newleaf.hasSurface ) // the leaf with a surface always goes first + return true; + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Water surfaces are stored in an RB tree and the tree is used to +// create one-off .vmt files embedded in the .bsp for each surface so that the +// water depth effect occurs on a per-water surface level. +//----------------------------------------------------------------------------- +struct WaterTexInfo +{ + // The mangled new .vmt name ( materials/levelename/oldmaterial_depth_xxx ) where xxx is + // the water depth (as an integer ) + CUtlSymbol m_FullName; + + // The original .vmt name + CUtlSymbol m_MaterialName; + + // The depth of the water this texinfo refers to + int m_nWaterDepth; + + // The texinfo id + int m_nTexInfo; + + // The subdivision size for the water surface +// float m_SubdivSize; +}; + +//----------------------------------------------------------------------------- +// Purpose: Helper for RB tree operations ( we compare full mangled names ) +// Input : src1 - +// src2 - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool WaterLessFunc( WaterTexInfo const& src1, WaterTexInfo const& src2 ) +{ + return src1.m_FullName < src2.m_FullName; +} + +//----------------------------------------------------------------------------- +// Purpose: A growable RB tree of water surfaces +//----------------------------------------------------------------------------- +static CUtlRBTree< WaterTexInfo, int > g_WaterTexInfos( 0, 32, WaterLessFunc ); + +#if 0 +float GetSubdivSizeForFogVolume( int fogVolumeID ) +{ + Assert( fogVolumeID >= 0 && fogVolumeID < g_WaterTexInfos.Count() ); + return g_WaterTexInfos[fogVolumeID].m_SubdivSize; +} +#endif + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *mapname - +// *materialname - +// waterdepth - +// *fullname - +//----------------------------------------------------------------------------- +void GetWaterTextureName( char const *mapname, char const *materialname, int waterdepth, char *fullname ) +{ + char temp[ 512 ]; + + // Construct the full name (prepend mapname to reduce name collisions) + sprintf( temp, "maps/%s/%s_depth_%i", mapname, materialname, (int)waterdepth ); + + // Make sure it's lower case + strlwr( temp ); + + strcpy( fullname, temp ); +} + +//----------------------------------------------------------------------------- +// Purpose: Called to write procedural materials in the rb tree to the embedded +// pak file for this .bsp +//----------------------------------------------------------------------------- +void EmitWaterMaterialFile( WaterTexInfo *wti ) +{ + char waterTextureName[512]; + if ( !wti ) + { + return; + } + + GetWaterTextureName( mapbase, wti->m_MaterialName.String(), ( int )wti->m_nWaterDepth, waterTextureName ); + + // Convert to string + char szDepth[ 32 ]; + sprintf( szDepth, "%i", wti->m_nWaterDepth ); + CreateMaterialPatch( wti->m_MaterialName.String(), waterTextureName, "$waterdepth", szDepth, PATCH_INSERT ); +} + +//----------------------------------------------------------------------------- +// Purpose: Takes the texinfo_t referenced by the .vmt and the computed depth for the +// surface and looks up or creates a texdata/texinfo for the mangled one-off water .vmt file +// Input : *pBaseInfo - +// depth - +// Output : int +//----------------------------------------------------------------------------- +int FindOrCreateWaterTexInfo( texinfo_t *pBaseInfo, float depth ) +{ + char fullname[ 512 ]; + char materialname[ 512 ]; + + // Get the base texture/material name + char const *name = TexDataStringTable_GetString( GetTexData( pBaseInfo->texdata )->nameStringTableID ); + + GetWaterTextureName( mapbase, name, (int)depth, fullname ); + + // See if we already have an entry for this depth + WaterTexInfo lookup; + lookup.m_FullName = fullname; + int idx = g_WaterTexInfos.Find( lookup ); + + // If so, return the existing entry texinfo index + if ( idx != g_WaterTexInfos.InvalidIndex() ) + { + return g_WaterTexInfos[ idx ].m_nTexInfo; + } + + // Otherwise, fill in the rest of the data + lookup.m_nWaterDepth = (int)depth; + // Remember the current material name + sprintf( materialname, "%s", name ); + strlwr( materialname ); + lookup.m_MaterialName = materialname; + + texinfo_t ti; + // Make a copy + ti = *pBaseInfo; + // Create a texdata that is based on the underlying existing entry + ti.texdata = FindAliasedTexData( fullname, GetTexData( pBaseInfo->texdata ) ); + + // Find or create a new index + lookup.m_nTexInfo = FindOrCreateTexInfo( ti ); + + // Add the new texinfo to the RB tree + idx = g_WaterTexInfos.Insert( lookup ); + + // Msg( "created texinfo for %s\n", lookup.m_FullName.String() ); + + // Go ahead and create the new vmt file. + EmitWaterMaterialFile( &g_WaterTexInfos[idx] ); + + // Return the new texinfo + return g_WaterTexInfos[ idx ].m_nTexInfo; +} + +extern node_t *dfacenodes[MAX_MAP_FACES]; +static void WriteFogVolumeIDs( dmodel_t *pModel ) +{ + int i; + + // write fog volume ID to each face in this model + for( i = pModel->firstface; i < pModel->firstface + pModel->numfaces; i++ ) + { + dface_t *pFace = &dfaces[i]; + node_t *pFaceNode = dfacenodes[i]; + texinfo_t *pTexInfo = &texinfo[pFace->texinfo]; + pFace->surfaceFogVolumeID = -1; + if ( pFaceNode ) + { + if ( (pTexInfo->flags & SURF_WARP ) && pFaceNode->planenum == PLANENUM_LEAF && pFaceNode->diskId >= 0 ) + { + pFace->surfaceFogVolumeID = dleafs[pFaceNode->diskId].leafWaterDataID; + dleafwaterdata_t *pLeafWaterData = &dleafwaterdata[pFace->surfaceFogVolumeID]; + + // HACKHACK: Should probably mark these faces as water bottom or "bottommaterial" faces. + // HACKHACK: Use a heuristic, if it points up, it's the water top. + if ( dplanes[pFace->planenum].normal.z > 0 ) + { + pFace->texinfo = pLeafWaterData->surfaceTexInfoID; + } + } + else + { + // missed this face somehow? + Assert( !(pTexInfo->flags & SURF_WARP ) ); + } + + } + } +} + + +static bool PortalCrossesWater( waterleaf_t &baseleaf, portal_t *portal ) +{ + if ( baseleaf.hasSurface ) + { + int side = WindingOnPlaneSide( portal->winding, baseleaf.surfaceNormal, baseleaf.surfaceDist ); + if ( side == SIDE_CROSS || side == SIDE_FRONT ) + return true; + } + + return false; +} + + +static int FindOrCreateLeafWaterData( float surfaceZ, float minZ, int surfaceTexInfoID ) +{ + int i; + for( i = 0; i < numleafwaterdata; i++ ) + { + dleafwaterdata_t *pLeafWaterData = &dleafwaterdata[i]; + if( pLeafWaterData->surfaceZ == surfaceZ && + pLeafWaterData->minZ == minZ && + pLeafWaterData->surfaceTexInfoID == surfaceTexInfoID ) + { + return i; + } + } + dleafwaterdata_t *pLeafWaterData = &dleafwaterdata[numleafwaterdata]; + pLeafWaterData->surfaceZ = surfaceZ; + pLeafWaterData->minZ = minZ; + pLeafWaterData->surfaceTexInfoID = surfaceTexInfoID; + numleafwaterdata++; + return numleafwaterdata - 1; +} + + +// Enumerate all leaves under node with contents in contentsMask and add them to list +void EnumLeaves_r( CUtlVector &list, node_t *node, int contentsMask ) +{ + if ( node->planenum != PLANENUM_LEAF ) + { + EnumLeaves_r( list, node->children[0], contentsMask ); + EnumLeaves_r( list, node->children[1], contentsMask ); + return; + } + + if ( !(node->contents & contentsMask) ) + return; + + + // has the contents, put it in the list + list.AddToTail( node ); +} + + +// Builds a waterleaf_t for the given leaf +static void BuildWaterLeaf( node_t *pLeafIn, waterleaf_t &waterLeafOut ) +{ + waterLeafOut.pNode = pLeafIn; + waterLeafOut.waterLeafIndex = pLeafIn->diskId; + waterLeafOut.outsideLeafIndex = -1; + waterLeafOut.hasSurface = false; + waterLeafOut.surfaceDist = MAX_COORD_INTEGER; + waterLeafOut.surfaceNormal.Init( 0.f, 0.f, 1.f ); + waterLeafOut.planenum = -1; + waterLeafOut.surfaceTexInfo = -1; + waterLeafOut.minZ = MAX_COORD_INTEGER; + + // search the list of portals out of this leaf for one that leaves water + // If you find one, this leaf has a surface, so fill out the surface data + int oppositeNodeIndex = 0; + for (portal_t *p = pLeafIn->portals ; p ; p = p->next[!oppositeNodeIndex]) + { + oppositeNodeIndex = (p->nodes[0] == pLeafIn) ? 1 : 0; + + // not visible, can't be the portals we're looking for... + if ( !p->side ) + continue; + + // See if this portal crosses into air + node_t *pOpposite = p->nodes[oppositeNodeIndex]; + if ( !(pOpposite->contents & MASK_WATER) && !(pOpposite->contents & MASK_SOLID) ) + { + // it does, there must be a surface here + plane_t *plane = &g_MainMap->mapplanes[p->side->planenum]; + if ( waterLeafOut.hasSurface ) + { + // Sort to find the most upward facing normal (skips sides) + if ( waterLeafOut.surfaceNormal.z > plane->normal.z ) + continue; + if ( (waterLeafOut.surfaceNormal.z == plane->normal.z) && waterLeafOut.surfaceDist >= plane->dist ) + continue; + } + // water surface needs to point at least somewhat up, this is + // probably a map error + if ( plane->normal.z <= 0 ) + continue; + waterLeafOut.surfaceDist = plane->dist; + waterLeafOut.surfaceNormal = plane->normal; + waterLeafOut.hasSurface = true; + waterLeafOut.outsideLeafIndex = p->nodes[oppositeNodeIndex]->diskId; + waterLeafOut.surfaceTexInfo = p->side->texinfo; + } + } +} + + +static void InsertSortWaterLeaf( CUtlVector &list, const waterleaf_t &leafInsert ) +{ + // insertion sort the leaf (lowest leaves go first) + // leaves that aren't actually on the surface of the water will have leaf.hasSurface == false. + for ( int i = 0; i < list.Count(); i++ ) + { + if ( IsLowerLeaf( leafInsert, list[i] ) ) + { + list.InsertBefore( i, leafInsert ); + return; + } + } + + // must the highest one, so stick it at the end. + list.AddToTail( leafInsert ); +} + + +// Flood fill the tree, finding neighboring water volumes and connecting them to this list +// Cut groups that try to cross the surface. +// Mark leaves that are in a group as "visited" so they won't be chosen by subsequent fills +static void Flood_FindConnectedWaterVolumes_r( CUtlVector &list, node_t *pLeaf, waterleaf_t &baseleaf, leafbitarray_t &visited ) +{ + // already visited, or not the same water contents + if ( pLeaf->diskId < 0 || visited.Get(pLeaf->diskId) || !(pLeaf->contents & (baseleaf.pNode->contents & MASK_WATER) ) ) + return; + + int oppositeNodeIndex = 0; + for (portal_t *p = pLeaf->portals ; p ; p = p->next[!oppositeNodeIndex]) + { + oppositeNodeIndex = (p->nodes[0] == pLeaf) ? 1 : 0; + + // If any portal crosses the water surface, don't flow through this leaf + if ( PortalCrossesWater( baseleaf, p ) ) + return; + } + + visited.Set( pLeaf->diskId ); + list.AddToTail( pLeaf ); + + baseleaf.minZ = min( pLeaf->mins.z, baseleaf.minZ ); + + for (portal_t *p = pLeaf->portals ; p ; p = p->next[!oppositeNodeIndex]) + { + oppositeNodeIndex = (p->nodes[0] == pLeaf) ? 1 : 0; + + Flood_FindConnectedWaterVolumes_r( list, p->nodes[oppositeNodeIndex], baseleaf, visited ); + } +} + +// UNDONE: This is a bit of a hack to avoid crashing when we can't find an +// appropriate texinfo for a water model (to get physics properties) +int FirstWaterTexinfo( bspbrush_t *brushlist, int contents ) +{ + while (brushlist) + { + if ( brushlist->original->contents & contents ) + { + for ( int i = 0; i < brushlist->original->numsides; i++ ) + { + if ( brushlist->original->original_sides[i].contents & contents ) + { + return brushlist->original->original_sides[i].texinfo; + } + } + } + brushlist = brushlist->next; + } + + Assert(0); + return 0; +} + +// This is a list of water data that will be turned into physics models +struct watermodel_t +{ + int modelIndex; + int contents; + waterleaf_t waterLeafData; + int depthTexinfo; + int firstWaterLeafIndex; + int waterLeafCount; + int fogVolumeIndex; +}; + +static CUtlVector g_WaterModels; +static CUtlVector g_WaterLeafList; + +// Creates a list of watermodel_t for later processing by EmitPhysCollision +void EmitWaterVolumesForBSP( dmodel_t *pModel, node_t *node ) +{ + CUtlVector leafListAnyWater; + // build the list of all leaves containing water + EnumLeaves_r( leafListAnyWater, node, MASK_WATER ); + + // make a sorted list to flood fill + CUtlVector list; + + int i; + for ( i = 0; i < leafListAnyWater.Count(); i++ ) + { + waterleaf_t waterLeaf; + BuildWaterLeaf( leafListAnyWater[i], waterLeaf ); + InsertSortWaterLeaf( list, waterLeaf ); + } + + leafbitarray_t visited; + CUtlVector waterAreaList; + for ( i = 0; i < list.Count(); i++ ) + { + Flood_FindConnectedWaterVolumes_r( waterAreaList, list[i].pNode, list[i], visited ); + + // did we find a list of leaves connected to this one? + // remember the list is sorted, so this one may have been attached to a previous + // leaf. So it could have nothing hanging off of it. + if ( waterAreaList.Count() ) + { + // yes, emit a watermodel + watermodel_t tmp; + tmp.modelIndex = nummodels; + tmp.contents = list[i].pNode->contents; + tmp.waterLeafData = list[i]; + tmp.firstWaterLeafIndex = g_WaterLeafList.Count(); + tmp.waterLeafCount = waterAreaList.Count(); + + float waterDepth = tmp.waterLeafData.surfaceDist - tmp.waterLeafData.minZ; + if ( tmp.waterLeafData.surfaceTexInfo < 0 ) + { + // the map has probably leaked in this case, but output something anyway. + Assert(list[i].pNode->planenum == PLANENUM_LEAF); + tmp.waterLeafData.surfaceTexInfo = FirstWaterTexinfo( list[i].pNode->brushlist, tmp.contents ); + } + tmp.depthTexinfo = FindOrCreateWaterTexInfo( &texinfo[ tmp.waterLeafData.surfaceTexInfo ], waterDepth ); + tmp.fogVolumeIndex = FindOrCreateLeafWaterData( tmp.waterLeafData.surfaceDist, tmp.waterLeafData.minZ, tmp.waterLeafData.surfaceTexInfo ); + + for ( int j = 0; j < waterAreaList.Count(); j++ ) + { + g_WaterLeafList.AddToTail( waterAreaList[j]->diskId ); + } + waterAreaList.RemoveAll(); + g_WaterModels.AddToTail( tmp ); + } + } + + WriteFogVolumeIDs( pModel ); +} + + +static void ConvertWaterModelToPhysCollide( CUtlVector &collisionList, int modelIndex, + float shrinkSize, float mergeTolerance ) +{ + dmodel_t *pModel = dmodels + modelIndex; + + for ( int i = 0; i < g_WaterModels.Count(); i++ ) + { + watermodel_t &waterModel = g_WaterModels[i]; + if ( waterModel.modelIndex != modelIndex ) + continue; + + CPlaneList planes( shrinkSize, mergeTolerance ); + int firstLeaf = waterModel.firstWaterLeafIndex; + planes.m_contentsMask = waterModel.contents; + + // push all of the leaves into the collision list + for ( int j = 0; j < waterModel.waterLeafCount; j++ ) + { + int leafIndex = g_WaterLeafList[firstLeaf + j]; + + dleaf_t *pLeaf = dleafs + leafIndex; + // fixup waterdata + pLeaf->leafWaterDataID = waterModel.fogVolumeIndex; + planes.ReferenceLeaf( leafIndex ); + } + + // visit the referenced leaves that belong to this model + VisitLeaves_r( planes, pModel->headnode ); + + // Now add the brushes from those leaves as convex + + // BUGBUG: NOTE: If your map has a brush that crosses the surface, it will be added to two water + // volumes. This only happens with connected water volumes with multiple surface heights + // UNDONE: Right now map makers must cut such brushes. It could be automatically cut by adding the + // surface plane to the list for each brush before calling ConvexFromPlanes() + planes.AddBrushes(); + + int count = planes.m_convex.Count(); + if ( !count ) + continue; + + // Save off the plane of the surface for this group as well as the collision model + // for all convex objects in the group. + CPhysCollide *pCollide = physcollision->ConvertConvexToCollide( planes.m_convex.Base(), count ); + if ( pCollide ) + { + int waterSurfaceTexInfoID = -1; + // use defaults + const char *pSurfaceProp = "water"; + float damping = 0.01; + if ( waterSurfaceTexInfoID >= 0 ) + { + // material override + int texdata = texinfo[waterSurfaceTexInfoID].texdata; + int prop = g_SurfaceProperties[texdata]; + pSurfaceProp = physprops->GetPropName( prop ); + } + + if ( !waterModel.waterLeafData.hasSurface ) + { + waterModel.waterLeafData.surfaceNormal.Init( 0,0,1 ); + Vector top = physcollision->CollideGetExtent( pCollide, vec3_origin, vec3_angle, waterModel.waterLeafData.surfaceNormal ); + waterModel.waterLeafData.surfaceDist = top.z; + } + CPhysCollisionEntryFluid *pCollisionEntryFuild = new CPhysCollisionEntryFluid( pCollide, + pSurfaceProp, damping, waterModel.waterLeafData.surfaceNormal, waterModel.waterLeafData.surfaceDist, waterModel.contents ); + collisionList.AddToTail( pCollisionEntryFuild ); + } + } +} + +// compute a normal for a triangle of the given three points (points are clockwise, normal points out) +static Vector TriangleNormal( const Vector &p0, const Vector &p1, const Vector &p2 ) +{ + Vector e0 = p1 - p0; + Vector e1 = p2 - p0; + Vector normal = CrossProduct( e1, e0 ); + VectorNormalize( normal ); + + return normal; +} + + +// find the side of the brush with the normal closest to the given normal +static dbrushside_t *FindBrushSide( int brushIndex, const Vector &normal ) +{ + dbrush_t *pbrush = &dbrushes[brushIndex]; + dbrushside_t *out = NULL; + float best = -1.f; + + for ( int i = 0; i < pbrush->numsides; i++ ) + { + dbrushside_t *pside = dbrushsides + i + pbrush->firstside; + dplane_t *pplane = dplanes + pside->planenum; + float dot = DotProduct( normal, pplane->normal ); + if ( dot > best ) + { + best = dot; + out = pside; + } + } + + return out; +} + + + +static void ConvertWorldBrushesToPhysCollide( CUtlVector &collisionList, float shrinkSize, float mergeTolerance, int contentsMask ) +{ + CPlaneList planes( shrinkSize, mergeTolerance ); + + planes.m_contentsMask = contentsMask; + + VisitLeaves_r( planes, dmodels[0].headnode ); + planes.AddBrushes(); + + int count = planes.m_convex.Count(); + if ( count ) + { + CPhysCollide *pCollide = physcollision->ConvertConvexToCollide( planes.m_convex.Base(), count ); + + ICollisionQuery *pQuery = physcollision->CreateQueryModel( pCollide ); + int convex = pQuery->ConvexCount(); + for ( int i = 0; i < convex; i++ ) + { + int triCount = pQuery->TriangleCount( i ); + int brushIndex = pQuery->GetGameData( i ); + + Vector points[3]; + for ( int j = 0; j < triCount; j++ ) + { + pQuery->GetTriangleVerts( i, j, points ); + Vector normal = TriangleNormal( points[0], points[1], points[2] ); + dbrushside_t *pside = FindBrushSide( brushIndex, normal ); + if ( pside->texinfo != TEXINFO_NODE ) + { + int prop = g_SurfaceProperties[texinfo[pside->texinfo].texdata]; + pQuery->SetTriangleMaterialIndex( i, j, RemapWorldMaterial( prop ) ); + } + } + } + physcollision->DestroyQueryModel( pQuery ); + pQuery = NULL; + + collisionList.AddToTail( new CPhysCollisionEntryStaticSolid( pCollide, contentsMask ) ); + } +} + +// adds any world, terrain, and water collision models to the collision list +static void BuildWorldPhysModel( CUtlVector &collisionList, float shrinkSize, float mergeTolerance ) +{ + ConvertWorldBrushesToPhysCollide( collisionList, shrinkSize, mergeTolerance, MASK_SOLID ); + ConvertWorldBrushesToPhysCollide( collisionList, shrinkSize, mergeTolerance, CONTENTS_PLAYERCLIP ); + ConvertWorldBrushesToPhysCollide( collisionList, shrinkSize, mergeTolerance, CONTENTS_MONSTERCLIP ); + + if ( !g_bNoVirtualMesh && Disp_HasPower4Displacements() ) + { + Warning("WARNING: Map using power 4 displacements, terrain physics cannot be compressed, map will need additional memory and CPU.\n"); + g_bNoVirtualMesh = true; + } + + // if there's terrain, save it off as a static mesh/polysoup + if ( g_bNoVirtualMesh || !physcollision->SupportsVirtualMesh() ) + { + Disp_AddCollisionModels( collisionList, &dmodels[0], MASK_SOLID ); + } + else + { + Disp_BuildVirtualMesh( MASK_SOLID ); + } + ConvertWaterModelToPhysCollide( collisionList, 0, shrinkSize, mergeTolerance ); +} + + +// adds a collision entry for this brush model +static void ConvertModelToPhysCollide( CUtlVector &collisionList, int modelIndex, int contents, float shrinkSize, float mergeTolerance ) +{ + int i; + CPlaneList planes( shrinkSize, mergeTolerance ); + + planes.m_contentsMask = contents; + + dmodel_t *pModel = dmodels + modelIndex; + VisitLeaves_r( planes, pModel->headnode ); + planes.AddBrushes(); + int count = planes.m_convex.Count(); + convertconvexparams_t params; + params.Defaults(); + params.buildOuterConvexHull = count > 1 ? true : false; + params.buildDragAxisAreas = true; + Vector size = pModel->maxs - pModel->mins; + + float minSurfaceArea = -1.0f; + for ( i = 0; i < 3; i++ ) + { + int other = (i+1)%3; + int cross = (i+2)%3; + float surfaceArea = size[other] * size[cross]; + if ( minSurfaceArea < 0 || surfaceArea < minSurfaceArea ) + { + minSurfaceArea = surfaceArea; + } + } + // this can be really slow with super-large models and a low error tolerance + // Basically you get a ray cast through each square of epsilon surface area on each OBB side + // So compute it for 1% error (on the smallest side, less on larger sides) + params.dragAreaEpsilon = clamp( minSurfaceArea * 1e-2f, 1.0f, 1024.0f ); + CPhysCollide *pCollide = physcollision->ConvertConvexToCollideParams( planes.m_convex.Base(), count, params ); + + if ( !pCollide ) + return; + + struct + { + int prop; + float area; + } proplist[256]; + int numprops = 1; + + proplist[0].prop = -1; + proplist[0].area = 1; + // compute the array of props on the surface of this model + + // NODRAW brushes no longer have any faces + if ( !dmodels[modelIndex].numfaces ) + { + int sideIndex = planes.GetFirstBrushSide(); + int texdata = texinfo[dbrushsides[sideIndex].texinfo].texdata; + int prop = g_SurfaceProperties[texdata]; + proplist[numprops].prop = prop; + proplist[numprops].area = 2; + numprops++; + } + + for ( i = 0; i < dmodels[modelIndex].numfaces; i++ ) + { + dface_t *face = dfaces + i + dmodels[modelIndex].firstface; + int texdata = texinfo[face->texinfo].texdata; + int prop = g_SurfaceProperties[texdata]; + int j; + for ( j = 0; j < numprops; j++ ) + { + if ( proplist[j].prop == prop ) + { + proplist[j].area += face->area; + break; + } + } + + if ( (!numprops || j >= numprops) && numprops < ARRAYSIZE(proplist) ) + { + proplist[numprops].prop = prop; + proplist[numprops].area = face->area; + numprops++; + } + } + + + // choose the prop with the most surface area + int maxIndex = -1; + float maxArea = 0; + float totalArea = 0; + + for ( i = 0; i < numprops; i++ ) + { + if ( proplist[i].area > maxArea ) + { + maxIndex = i; + maxArea = proplist[i].area; + } + // add up the total surface area + totalArea += proplist[i].area; + } + + float mass = 1.0f; + const char *pMaterial = "default"; + if ( maxIndex >= 0 ) + { + int prop = proplist[maxIndex].prop; + + // use default if this material has no prop + if ( prop < 0 ) + prop = 0; + + pMaterial = physprops->GetPropName( prop ); + float density, thickness; + physprops->GetPhysicsProperties( prop, &density, &thickness, NULL, NULL ); + + // if this is a "shell" material (it is hollow and encloses some empty space) + // compute the mass with a constant surface thickness + if ( thickness != 0 ) + { + mass = totalArea * thickness * density * CUBIC_METERS_PER_CUBIC_INCH; + } + else + { + // material is completely solid, compute total mass as if constant density throughout. + mass = planes.m_totalVolume * density * CUBIC_METERS_PER_CUBIC_INCH; + } + } + + // Clamp mass to 100,000 kg + if ( mass > VPHYSICS_MAX_MASS ) + { + mass = VPHYSICS_MAX_MASS; + } + + collisionList.AddToTail( new CPhysCollisionEntrySolid( pCollide, pMaterial, mass ) ); +} + +static void ClearLeafWaterData( void ) +{ + int i; + + for( i = 0; i < numleafs; i++ ) + { + dleafs[i].leafWaterDataID = -1; + dleafs[i].contents &= ~CONTENTS_TESTFOGVOLUME; + } +} + + +// This is the only public entry to this file. +// The global data touched in the file is: +// from bsplib.h: +// g_pPhysCollide : This is an output from this file. +// g_PhysCollideSize : This is set in this file. +// g_numdispinfo : This is an input to this file. +// g_dispinfo : This is an input to this file. +// numnodewaterdata : This is an output from this file. +// dleafwaterdata : This is an output from this file. +// from vbsp.h: +// g_SurfaceProperties : This is an input to this file. +void EmitPhysCollision() +{ + ClearLeafWaterData(); + + CreateInterfaceFn physicsFactory = GetPhysicsFactory(); + if ( physicsFactory ) + { + physcollision = (IPhysicsCollision *)physicsFactory( VPHYSICS_COLLISION_INTERFACE_VERSION, NULL ); + } + + if ( !physcollision ) + { + Warning("!!! WARNING: Can't build collision data!\n" ); + return; + } + + CUtlVector collisionList[MAX_MAP_MODELS]; + CTextBuffer *pTextBuffer[MAX_MAP_MODELS]; + + int physModelCount = 0, totalSize = 0; + + int start = Plat_FloatTime(); + + Msg("Building Physics collision data...\n" ); + + int i, j; + for ( i = 0; i < nummodels; i++ ) + { + // Build a list of collision models for this brush model section + if ( i == 0 ) + { + // world is the only model that processes water separately. + // other brushes are assumed to be completely solid or completely liquid + BuildWorldPhysModel( collisionList[i], NO_SHRINK, VPHYSICS_MERGE); + } + else + { + ConvertModelToPhysCollide( collisionList[i], i, MASK_SOLID|CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP|MASK_WATER, VPHYSICS_SHRINK, VPHYSICS_MERGE ); + } + + pTextBuffer[i] = NULL; + if ( !collisionList[i].Count() ) + continue; + + // if we've got collision models, write their script for processing in the game + pTextBuffer[i] = new CTextBuffer; + for ( j = 0; j < collisionList[i].Count(); j++ ) + { + // dump a text file for visualization + if ( dumpcollide ) + { + collisionList[i][j]->DumpCollide( pTextBuffer[i], i, j ); + } + // each model knows how to write its script + collisionList[i][j]->WriteToTextBuffer( pTextBuffer[i], i, j ); + // total up the binary section's size + totalSize += collisionList[i][j]->GetCollisionBinarySize() + sizeof(int); + } + + // These sections only appear in the world's collision text + if ( i == 0 ) + { + if ( !g_bNoVirtualMesh && physcollision->SupportsVirtualMesh() ) + { + pTextBuffer[i]->WriteText("virtualterrain {}\n"); + } + if ( s_WorldPropList.Count() ) + { + pTextBuffer[i]->WriteText( "materialtable {\n" ); + for ( j = 0; j < s_WorldPropList.Count(); j++ ) + { + int propIndex = s_WorldPropList[j]; + if ( propIndex < 0 ) + { + pTextBuffer[i]->WriteIntKey( "default", j+1 ); + } + else + { + pTextBuffer[i]->WriteIntKey( physprops->GetPropName( propIndex ), j+1 ); + } + } + pTextBuffer[i]->WriteText( "}\n" ); + } + } + + pTextBuffer[i]->Terminate(); + + // total lump size includes the text buffers (scripts) + totalSize += pTextBuffer[i]->GetSize(); + + physModelCount++; + } + + // add one for tail of list marker + physModelCount++; + + // DWORD align the lump because AddLump assumes that it is DWORD aligned. + byte *ptr ; + g_PhysCollideSize = totalSize + (physModelCount * sizeof(dphysmodel_t)); + g_pPhysCollide = (byte *)malloc(( g_PhysCollideSize + 3 ) & ~3 ); + memset( g_pPhysCollide, 0, g_PhysCollideSize ); + ptr = g_pPhysCollide; + + for ( i = 0; i < nummodels; i++ ) + { + if ( pTextBuffer[i] ) + { + int j; + + dphysmodel_t model; + + model.modelIndex = i; + model.solidCount = collisionList[i].Count(); + model.dataSize = sizeof(int) * model.solidCount; + + for ( j = 0; j < model.solidCount; j++ ) + { + model.dataSize += collisionList[i][j]->GetCollisionBinarySize(); + } + model.keydataSize = pTextBuffer[i]->GetSize(); + + // store the header + memcpy( ptr, &model, sizeof(model) ); + ptr += sizeof(model); + + for ( j = 0; j < model.solidCount; j++ ) + { + int collideSize = collisionList[i][j]->GetCollisionBinarySize(); + + // write size + memcpy( ptr, &collideSize, sizeof(int) ); + ptr += sizeof(int); + + // now write the collision model + collisionList[i][j]->WriteCollisionBinary( reinterpret_cast(ptr) ); + ptr += collideSize; + } + + memcpy( ptr, pTextBuffer[i]->GetData(), pTextBuffer[i]->GetSize() ); + ptr += pTextBuffer[i]->GetSize(); + } + + delete pTextBuffer[i]; + } + + dphysmodel_t model; + + // Mark end of list + model.modelIndex = -1; + model.dataSize = -1; + model.keydataSize = 0; + model.solidCount = 0; + memcpy( ptr, &model, sizeof(model) ); + ptr += sizeof(model); + Assert( (ptr-g_pPhysCollide) == g_PhysCollideSize); + Msg("done (%d) (%d bytes)\n", (int)(Plat_FloatTime() - start), g_PhysCollideSize ); + + // UNDONE: Collision models (collisionList) memory leak! +} diff --git a/mp/src/utils/vbsp/ivp.h b/mp/src/utils/vbsp/ivp.h new file mode 100644 index 00000000..d3c310dc --- /dev/null +++ b/mp/src/utils/vbsp/ivp.h @@ -0,0 +1,75 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef IVP_H +#define IVP_H +#ifdef _WIN32 +#pragma once +#endif + +#include "utlvector.h" + +class CPhysCollide; +class CTextBuffer; +class IPhysicsCollision; +extern IPhysicsCollision *physcollision; + +// a list of all of the materials in the world model +extern int RemapWorldMaterial( int materialIndexIn ); + +class CPhysCollisionEntry +{ +public: + CPhysCollisionEntry( CPhysCollide *pCollide ); + + virtual void WriteToTextBuffer( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex ) = 0; + virtual void DumpCollide( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex ) = 0; + + unsigned int GetCollisionBinarySize(); + unsigned int WriteCollisionBinary( char *pDest ); + +protected: + void DumpCollideFileName( const char *pName, int modelIndex, CTextBuffer *pTextBuffer ); + +protected: + CPhysCollide *m_pCollide; +}; + +class CPhysCollisionEntryStaticMesh : public CPhysCollisionEntry +{ +public: + CPhysCollisionEntryStaticMesh( CPhysCollide *pCollide, const char *pMaterialName ); + + virtual void WriteToTextBuffer( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex ); + virtual void DumpCollide( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex ); + +private: + const char *m_pMaterial; +}; + + +class CTextBuffer +{ +public: + CTextBuffer( void ); + ~CTextBuffer( void ); + inline int GetSize( void ) { return m_buffer.Count(); } + inline char *GetData( void ) { return m_buffer.Base(); } + + void WriteText( const char *pText ); + void WriteIntKey( const char *pKeyName, int outputData ); + void WriteStringKey( const char *pKeyName, const char *outputData ); + void WriteFloatKey( const char *pKeyName, float outputData ); + void WriteFloatArrayKey( const char *pKeyName, const float *outputData, int count ); + + void CopyStringQuotes( const char *pString ); + void Terminate( void ); +private: + void CopyData( const char *pData, int len ); + CUtlVector m_buffer; +}; + +#endif // IVP_H diff --git a/mp/src/utils/vbsp/leakfile.cpp b/mp/src/utils/vbsp/leakfile.cpp new file mode 100644 index 00000000..bd8a9b28 --- /dev/null +++ b/mp/src/utils/vbsp/leakfile.cpp @@ -0,0 +1,168 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +#include "vbsp.h" +#include "color.h" + +/* +============================================================================== + +LEAF FILE GENERATION + +Save out name.line for qe3 to read +============================================================================== +*/ + + +/* +============= +LeakFile + +Finds the shortest possible chain of portals +that leads from the outside leaf to a specifically +occupied leaf +============= +*/ +void LeakFile (tree_t *tree) +{ + Vector mid; + FILE *linefile; + char filename[1024]; + node_t *node; + int count; + + if (!tree->outside_node.occupied) + return; + + tree->leaked = true; + qprintf ("--- LeakFile ---\n"); + + // + // write the points to the file + // + sprintf (filename, "%s.lin", source); + linefile = fopen (filename, "w"); + if (!linefile) + Error ("Couldn't open %s\n", filename); + + count = 0; + node = &tree->outside_node; + while (node->occupied > 1) + { + portal_t *nextportal = NULL; + node_t *nextnode = NULL; + int s = 0; + + // find the best portal exit + int next = node->occupied; + for (portal_t *p=node->portals ; p ; p = p->next[!s]) + { + s = (p->nodes[0] == node); + if (p->nodes[s]->occupied + && p->nodes[s]->occupied < next) + { + nextportal = p; + nextnode = p->nodes[s]; + next = nextnode->occupied; + } + } + node = nextnode; + WindingCenter (nextportal->winding, mid); + fprintf (linefile, "%f %f %f\n", mid[0], mid[1], mid[2]); + count++; + } + + // Add the occupant's origin to the leakfile. + Vector origin; + GetVectorForKey (node->occupant, "origin", origin); + + fprintf (linefile, "%f %f %f\n", origin[0], origin[1], origin[2]); + qprintf ("%5i point linefile\n", count+1); + + fclose (linefile); + + // Emit a leak warning. + const char *cl = ValueForKey (node->occupant, "classname"); + Color red(255,0,0,255); + ColorSpewMessage( SPEW_MESSAGE, &red, "Entity %s (%.2f %.2f %.2f) leaked!\n", cl, origin[0], origin[1], origin[2] ); +} + +void AreaportalLeakFile( tree_t *tree, portal_t *pStartPortal, portal_t *pEndPortal, node_t *pStart ) +{ + Vector mid; + FILE *linefile; + char filename[1024]; + node_t *node; + int count; + + // wrote a leak line file already, don't overwrite it with the areaportal leak file + if ( tree->leaked ) + return; + + tree->leaked = true; + qprintf ("--- LeakFile ---\n"); + + // + // write the points to the file + // + sprintf (filename, "%s.lin", source); + linefile = fopen (filename, "w"); + if (!linefile) + Error ("Couldn't open %s\n", filename); + + count = 2; + WindingCenter (pEndPortal->winding, mid); + fprintf (linefile, "%f %f %f\n", mid[0], mid[1], mid[2]); + mid = 0.5 * (pStart->mins + pStart->maxs); + fprintf (linefile, "%f %f %f\n", mid[0], mid[1], mid[2]); + + node = pStart; + while (node->occupied >= 1) + { + portal_t *nextportal = NULL; + node_t *nextnode = NULL; + int s = 0; + + // find the best portal exit + int next = node->occupied; + for (portal_t *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; + } + } + if ( !nextnode ) + break; + node = nextnode; + WindingCenter (nextportal->winding, mid); + fprintf (linefile, "%f %f %f\n", mid[0], mid[1], mid[2]); + count++; + } + // add the occupant center + if ( node ) + { + mid = 0.5 * (node->mins + node->maxs); + fprintf (linefile, "%f %f %f\n", mid[0], mid[1], mid[2]); + count++; + } + WindingCenter (pStartPortal->winding, mid); + count++; + fprintf (linefile, "%f %f %f\n", mid[0], mid[1], mid[2]); + + qprintf ("%5i point linefile\n", count); + + fclose (linefile); + Warning( "Wrote %s\n", filename ); + Color red(255,0,0,255); + ColorSpewMessage( SPEW_MESSAGE, &red, "Areaportal leak ! File: %s ", filename ); +} \ No newline at end of file diff --git a/mp/src/utils/vbsp/manifest.cpp b/mp/src/utils/vbsp/manifest.cpp new file mode 100644 index 00000000..44dd07b8 --- /dev/null +++ b/mp/src/utils/vbsp/manifest.cpp @@ -0,0 +1,568 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +#include "vbsp.h" +#include "map_shared.h" +#include "fgdlib/fgdlib.h" +#include "manifest.h" +#include "windows.h" + +//----------------------------------------------------------------------------- +// Purpose: default constructor +//----------------------------------------------------------------------------- +CManifestMap::CManifestMap( void ) +{ + m_RelativeMapFileName[ 0 ] = 0; + m_bTopLevelMap = false; +} + + +//----------------------------------------------------------------------------- +// Purpose: default constructor +//----------------------------------------------------------------------------- +CManifest::CManifest( void ) +{ + m_InstancePath[ 0 ] = 0; + m_bIsCordoning = false; + m_CordoningMapEnt = NULL; +} + + +//----------------------------------------------------------------------------- +// Purpose: this function will parse through the known keys for the manifest map entry +// Input : szKey - the key name +// szValue - the value +// pManifestMap - the manifest map this belongs to +// Output : ChunkFileResult_t - result of the parsing +//----------------------------------------------------------------------------- +ChunkFileResult_t CManifest::LoadManifestMapKeyCallback( const char *szKey, const char *szValue, CManifestMap *pManifestMap ) +{ + if ( !stricmp( szKey, "Name" ) ) + { + // pManifestMap->m_FriendlyName = szValue; + } + else if ( !stricmp( szKey, "File" ) ) + { + strcpy( pManifestMap->m_RelativeMapFileName, szValue ); + } + else if ( !stricmp( szKey, "IsPrimary" ) ) + { + // pManifestMap->m_bPrimaryMap = ( atoi( szValue ) == 1 ); + } + else if ( !stricmp( szKey, "IsProtected" ) ) + { + // pManifestMap->m_bCanBeModified = ( atoi( szValue ) != 1 ); + } + else if ( !stricmp( szKey, "TopLevel" ) ) + { + pManifestMap->m_bTopLevelMap = ( atoi( szValue ) == 1 ); + } + + return ChunkFile_Ok; +} + + +//----------------------------------------------------------------------------- +// Purpose: this function is responsible for setting up the manifest map about to be read in +// Input : pFile - the chunk file being read +// pDoc - the owning manifest document +// Output : ChunkFileResult_t - result of the parsing +//----------------------------------------------------------------------------- +ChunkFileResult_t CManifest::LoadManifestVMFCallback( CChunkFile *pFile, CManifest *pManifest ) +{ + CManifestMap *pManifestMap = new CManifestMap(); + + pManifest->m_Maps.AddToTail( pManifestMap ); + + ChunkFileResult_t eResult = pFile->ReadChunk( ( KeyHandler_t )LoadManifestMapKeyCallback, pManifestMap ); + + return( eResult ); +} + + +//----------------------------------------------------------------------------- +// Purpose: this function will load the VMF chunk +// Input : pFile - the chunk file being read +// pDoc - the owning manifest document +// Output : ChunkFileResult_t - result of the parsing +//----------------------------------------------------------------------------- +ChunkFileResult_t CManifest::LoadManifestMapsCallback( CChunkFile *pFile, CManifest *pManifest ) +{ + CChunkHandlerMap Handlers; + Handlers.AddHandler( "VMF", ( ChunkHandler_t )LoadManifestVMFCallback, pManifest ); + pFile->PushHandlers(&Handlers); + + ChunkFileResult_t eResult = ChunkFile_Ok; + + eResult = pFile->ReadChunk(); + + pFile->PopHandlers(); + + return( eResult ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : +// Output : +//----------------------------------------------------------------------------- +ChunkFileResult_t CManifest::LoadCordonBoxCallback( CChunkFile *pFile, Cordon_t *pCordon ) +{ + // Add a box to this cordon. + pCordon->m_Boxes.AddToTail(); + BoundBox &box = pCordon->m_Boxes.Tail(); + + // Fill it in with the data from the VMF. + return pFile->ReadChunk( (KeyHandler_t)LoadCordonBoxKeyCallback, (void *)&box ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : +// Output : +//----------------------------------------------------------------------------- +ChunkFileResult_t CManifest::LoadCordonBoxKeyCallback( const char *szKey, const char *szValue, BoundBox *pBox ) +{ + if (!stricmp(szKey, "mins")) + { + CChunkFile::ReadKeyValuePoint(szValue, pBox->bmins); + } + else if (!stricmp(szKey, "maxs")) + { + CChunkFile::ReadKeyValuePoint(szValue, pBox->bmaxs); + } + + return ChunkFile_Ok; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : +// Output : +//----------------------------------------------------------------------------- +ChunkFileResult_t CManifest::LoadCordonKeyCallback( const char *szKey, const char *szValue, Cordon_t *pCordon ) +{ + if (!stricmp(szKey, "name")) + { + pCordon->m_szName.Set( szValue ); + } + // Whether this particular cordon volume is active. + else if (!stricmp(szKey, "active")) + { + CChunkFile::ReadKeyValueBool(szValue, pCordon->m_bActive); + } + + return ChunkFile_Ok; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : +// Output : +//----------------------------------------------------------------------------- +ChunkFileResult_t CManifest::LoadCordonCallback( CChunkFile *pFile, CManifest *pManifest ) +{ + // Add a new cordon which will be filled in by the key callback + pManifest->m_Cordons.AddToTail(); + Cordon_t &cordon = pManifest->m_Cordons.Tail(); + + CChunkHandlerMap Handlers; + Handlers.AddHandler( "box", (ChunkHandler_t)CManifest::LoadCordonBoxCallback, (void *)&cordon ); + + pFile->PushHandlers(&Handlers); + ChunkFileResult_t eResult = pFile->ReadChunk( (KeyHandler_t)LoadCordonKeyCallback, (void *)&cordon ); + pFile->PopHandlers(); + + return(eResult); +} + + +//----------------------------------------------------------------------------------------------------------- +// Parses keys that are applicable to all cordons in the map. +//----------------------------------------------------------------------------- +ChunkFileResult_t CManifest::LoadCordonsKeyCallback( const char *szKey, const char *szValue, CManifest *pManifest ) +{ + // Whether the cordoning system is enabled or disabled. + if ( !stricmp( szKey, "active" ) ) + { + CChunkFile::ReadKeyValueBool( szValue, pManifest->m_bIsCordoning ); + } + + return ChunkFile_Ok; +} + + +//----------------------------------------------------------------------------- +// Parses the VMF chunk that pertains to all the cordons in the map: +// +// cordons +// { +// "active" "true" +// cordon +// { +// "active" "true" +// "box" +// { +// "mins" "-1024, -1024, -1024" +// "maxs" "1024, 1024, 1024" +// } +// ...may be more boxes... +// } +// ...may be more cordons... +// } +// +//----------------------------------------------------------------------------- +ChunkFileResult_t CManifest::LoadCordonsCallback( CChunkFile *pFile, CManifest *pManifest ) +{ + CChunkHandlerMap Handlers; + Handlers.AddHandler( "cordon", (ChunkHandler_t)CManifest::LoadCordonCallback, pManifest ); + + pFile->PushHandlers(&Handlers); + ChunkFileResult_t eResult = pFile->ReadChunk( (KeyHandler_t)LoadCordonsKeyCallback, pManifest ); + pFile->PopHandlers(); + + return(eResult); +} + +extern ChunkFileResult_t LoadSolidCallback(CChunkFile *pFile, LoadEntity_t *pLoadEntity); + +ChunkFileResult_t CManifest::LoadManifestCordoningPrefsCallback( CChunkFile *pFile, CManifest *pDoc ) +{ + pDoc->m_CordoningMapEnt = &g_MainMap->entities[g_MainMap->num_entities]; + g_MainMap->num_entities++; + memset( pDoc->m_CordoningMapEnt, 0, sizeof( *pDoc->m_CordoningMapEnt ) ); + pDoc->m_CordoningMapEnt->firstbrush = g_MainMap->nummapbrushes; + pDoc->m_CordoningMapEnt->numbrushes = 0; + + LoadEntity_t LoadEntity; + LoadEntity.pEntity = pDoc->m_CordoningMapEnt; + + // No default flags/contents + LoadEntity.nBaseFlags = 0; + LoadEntity.nBaseContents = 0; + + // + // Set up handlers for the subchunks that we are interested in. + // + CChunkHandlerMap Handlers; + Handlers.AddHandler( "cordons", ( ChunkHandler_t )CManifest::LoadCordonsCallback, pDoc ); + Handlers.AddHandler("solid", (ChunkHandler_t)::LoadSolidCallback, &LoadEntity); + pFile->PushHandlers(&Handlers); + + ChunkFileResult_t eResult = ChunkFile_Ok; + + eResult = pFile->ReadChunk(); + + pFile->PopHandlers(); + + return( eResult ); +} + + +//----------------------------------------------------------------------------- +// Purpose: this function will create a new entity pair +// Input : pKey - the key of the pair +// pValue - the value of the pair +// Output : returns a newly created epair structure +//----------------------------------------------------------------------------- +epair_t *CManifest::CreateEPair( char *pKey, char *pValue ) +{ + epair_t *pEPair = new epair_t; + + pEPair->key = new char[ strlen( pKey ) + 1 ]; + pEPair->value = new char[ strlen( pValue ) + 1 ]; + + strcpy( pEPair->key, pKey ); + strcpy( pEPair->value, pValue ); + + return pEPair; +} + + +//----------------------------------------------------------------------------- +// Purpose: this function will load in all of the submaps belonging to this manifest, +// except for the top level map, which is loaded separately. +// Input : pMapFile - the top level map that was previously loaded +// pszFileName - the absolute file name of the top level map file +// Output : returns true if all submaps were loaded +//----------------------------------------------------------------------------- +bool CManifest::LoadSubMaps( CMapFile *pMapFile, const char *pszFileName ) +{ + entity_t *InstanceEntity; + epair_t *pEPair; + + InstanceEntity = &pMapFile->entities[ pMapFile->num_entities ]; + pMapFile->num_entities++; + memset( InstanceEntity, 0, sizeof( *InstanceEntity ) ); + + InstanceEntity->origin.Init( 0.0f, 0.0f, 0.0f ); + pEPair = CreateEPair( "classname", "worldspawn" ); + pEPair->next = InstanceEntity->epairs; + InstanceEntity->epairs = pEPair; + + for( int i = 0; i < m_Maps.Count(); i++ ) + { + // if ( m_Maps[ i ]->m_bTopLevelMap == false ) + { + char FileName[ MAX_PATH ]; + + sprintf( FileName, "%s%s", m_InstancePath, m_Maps[ i ]->m_RelativeMapFileName ); + + InstanceEntity = &pMapFile->entities[ pMapFile->num_entities ]; + pMapFile->num_entities++; + + memset( InstanceEntity, 0, sizeof( *InstanceEntity ) ); + InstanceEntity->origin.Init( 0.0f, 0.0f, 0.0f ); + + pEPair = CreateEPair( "angles", "0 0 0" ); + pEPair->next = InstanceEntity->epairs; + InstanceEntity->epairs = pEPair; + + char temp[ 128 ]; + sprintf( temp, "%d", GameData::NAME_FIXUP_NONE ); + + pEPair = CreateEPair( "fixup_style", temp ); + pEPair->next = InstanceEntity->epairs; + InstanceEntity->epairs = pEPair; + + pEPair = CreateEPair( "classname", "func_instance" ); + pEPair->next = InstanceEntity->epairs; + InstanceEntity->epairs = pEPair; + + pEPair = CreateEPair( "file", m_Maps[ i ]->m_RelativeMapFileName ); + pEPair->next = InstanceEntity->epairs; + InstanceEntity->epairs = pEPair; + + if ( m_Maps[ i ]->m_bTopLevelMap == true ) + { + pEPair = CreateEPair( "toplevel", "1" ); + pEPair->next = InstanceEntity->epairs; + InstanceEntity->epairs = pEPair; + } + } + } + + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : +// Output : +//----------------------------------------------------------------------------- +bool CManifest::LoadVMFManifestUserPrefs( const char *pszFileName ) +{ + char UserName[ MAX_PATH ], FileName[ MAX_PATH ], UserPrefsFileName[ MAX_PATH ]; + DWORD UserNameSize; + + UserNameSize = sizeof( UserName ); + if ( GetUserName( UserName, &UserNameSize ) == 0 ) + { + strcpy( UserPrefsFileName, "default" ); + } + + sprintf( UserPrefsFileName, "\\%s.vmm_prefs", UserName ); + V_StripExtension( pszFileName, FileName, sizeof( FileName ) ); + strcat( FileName, UserPrefsFileName ); + + FILE *fp = fopen( FileName, "rb" ); + if ( !fp ) + { + return false; + } + + CChunkFile File; + ChunkFileResult_t eResult = File.Open( FileName, ChunkFile_Read ); + + if ( eResult == ChunkFile_Ok ) + { + // + // Set up handlers for the subchunks that we are interested in. + // + CChunkHandlerMap Handlers; + Handlers.AddHandler( "cordoning", ( ChunkHandler_t )CManifest::LoadManifestCordoningPrefsCallback, this ); + + // Handlers.SetErrorHandler( ( ChunkErrorHandler_t )CMapDoc::HandleLoadError, this); + + File.PushHandlers(&Handlers); + + while( eResult == ChunkFile_Ok ) + { + eResult = File.ReadChunk(); + } + + if ( eResult == ChunkFile_EOF ) + { + eResult = ChunkFile_Ok; + } + + File.PopHandlers(); + } + + if ( eResult == ChunkFile_Ok ) + { + } + else + { + // no pref message for now + // GetMainWnd()->MessageBox( File.GetErrorText( eResult ), "Error loading manifest!", MB_OK | MB_ICONEXCLAMATION ); + } + + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: Loads a .VMM file. +// Input : pszFileName - Full path of the map file to load. +//----------------------------------------------------------------------------- +bool CManifest::LoadVMFManifest( const char *pszFileName ) +{ + V_StripExtension( pszFileName, m_InstancePath, sizeof( m_InstancePath ) ); + strcat( m_InstancePath, "\\" ); + + CChunkFile File; + ChunkFileResult_t eResult = File.Open( pszFileName, ChunkFile_Read ); + if ( eResult != ChunkFile_Ok ) + { + g_MapError.ReportError( File.GetErrorText( eResult ) ); + return false; + } + + CChunkHandlerMap Handlers; + Handlers.AddHandler( "Maps", ( ChunkHandler_t )LoadManifestMapsCallback, this ); + + File.PushHandlers(&Handlers); + + while (eResult == ChunkFile_Ok) + { + eResult = File.ReadChunk(); + } + + if (eResult == ChunkFile_EOF) + { + eResult = ChunkFile_Ok; + } + + File.PopHandlers(); + + if ( eResult == ChunkFile_Ok ) + { + int index = g_Maps.AddToTail( new CMapFile() ); + g_LoadingMap = g_Maps[ index ]; + if ( g_MainMap == NULL ) + { + g_MainMap = g_LoadingMap; + } + + LoadSubMaps( g_LoadingMap, pszFileName ); + + LoadVMFManifestUserPrefs( pszFileName ); + } + + return ( eResult == ChunkFile_Ok ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : +// Output : +//----------------------------------------------------------------------------- +void CManifest::CordonWorld( ) +{ + if ( m_bIsCordoning == false ) + { + return; + } + + for ( int i = 0; i < g_MainMap->num_entities; i++ ) + { + if ( i == 0 ) + { // for world spawn, we look at brushes + for( int nBrushNum = 0; nBrushNum < g_MainMap->entities[ i ].numbrushes; nBrushNum++ ) + { + int nIndex = g_MainMap->entities[ i ].firstbrush + nBrushNum; + + bool bRemove = true; + + for( int nCordon = 0; nCordon < m_Cordons.Count(); nCordon++ ) + { + if ( m_Cordons[ nCordon ].m_bActive == false ) + { + continue; + } + + for( int nBox = 0; nBox < m_Cordons[ nCordon ].m_Boxes.Count(); nBox++ ) + { + if ( m_Cordons[ nCordon ].m_Boxes[ nBox ].IsIntersectingBox( g_MainMap->mapbrushes[ nIndex ].mins, g_MainMap->mapbrushes[ nIndex ].maxs ) == true ) + { + bRemove = false; + break; + } + } + + if ( bRemove == false ) + { + break; + } + } + + if ( bRemove ) + { + int nSize = ( g_MainMap->entities[ i ].numbrushes - nBrushNum - 1 ) * sizeof( g_MainMap->mapbrushes[ 0 ] ); + memmove( &g_MainMap->mapbrushes[ nIndex ], &g_MainMap->mapbrushes[ nIndex + 1 ], nSize ); + g_MainMap->entities[ i ].numbrushes--; + nBrushNum--; + } + } + } + else if ( &g_MainMap->entities[ i ] != m_CordoningMapEnt ) + { // for all other entities, even if they include brushes, we look at origin + if ( g_MainMap->entities[ i ].numbrushes == 0 && g_MainMap->entities[ i ].epairs == NULL ) + { + continue; + } + + bool bRemove = true; + + for( int nCordon = 0; nCordon < m_Cordons.Count(); nCordon++ ) + { + if ( m_Cordons[ nCordon ].m_bActive == false ) + { + continue; + } + + for( int nBox = 0; nBox < m_Cordons[ nCordon ].m_Boxes.Count(); nBox++ ) + { + if ( m_Cordons[ nCordon ].m_Boxes[ nBox ].ContainsPoint( g_MainMap->entities[ i ].origin ) == true ) + { + bRemove = false; + break; + } + } + + if ( bRemove == false ) + { + break; + } + } + + if ( bRemove ) + { + g_MainMap->entities[ i ].numbrushes = 0; + g_MainMap->entities[ i ].epairs = NULL; + } + } + } + + if ( m_CordoningMapEnt ) + { + g_MainMap->MoveBrushesToWorldGeneral( m_CordoningMapEnt ); + m_CordoningMapEnt->numbrushes = 0; + m_CordoningMapEnt->epairs = NULL; + } +} diff --git a/mp/src/utils/vbsp/manifest.h b/mp/src/utils/vbsp/manifest.h new file mode 100644 index 00000000..f3b915a1 --- /dev/null +++ b/mp/src/utils/vbsp/manifest.h @@ -0,0 +1,73 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=====================================================================================// + +#ifndef __MANIFEST_H +#define __MANIFEST_H + +#ifdef _WIN32 +#pragma once +#endif + +#include "boundbox.h" + +// +// Each cordon is a named collection of bounding boxes. +// +struct Cordon_t +{ + inline Cordon_t() + { + m_bActive = false; + } + + CUtlString m_szName; + bool m_bActive; // True means cull using this cordon when cordoning is enabled. + CUtlVector m_Boxes; +}; + +class CManifestMap +{ +public: + CManifestMap( void ); + char m_RelativeMapFileName[ MAX_PATH ]; + bool m_bTopLevelMap; +}; + +class CManifest +{ +public: + CManifest( void ); + + static ChunkFileResult_t LoadManifestMapKeyCallback( const char *szKey, const char *szValue, CManifestMap *pManifestMap ); + static ChunkFileResult_t LoadManifestVMFCallback( CChunkFile *pFile, CManifest *pManifest ); + static ChunkFileResult_t LoadManifestMapsCallback( CChunkFile *pFile, CManifest *pManifest ); + static ChunkFileResult_t LoadCordonBoxCallback( CChunkFile *pFile, Cordon_t *pCordon ); + static ChunkFileResult_t LoadCordonBoxKeyCallback( const char *szKey, const char *szValue, BoundBox *pBox ); + static ChunkFileResult_t LoadCordonKeyCallback( const char *szKey, const char *szValue, Cordon_t *pCordon ); + static ChunkFileResult_t LoadCordonCallback( CChunkFile *pFile, CManifest *pManifest ); + static ChunkFileResult_t LoadCordonsKeyCallback( const char *pszKey, const char *pszValue, CManifest *pManifest ); + static ChunkFileResult_t LoadCordonsCallback( CChunkFile *pFile, CManifest *pManifest ); + static ChunkFileResult_t LoadManifestCordoningPrefsCallback( CChunkFile *pFile, CManifest *pManifest ); + + bool LoadSubMaps( CMapFile *pMapFile, const char *pszFileName ); + epair_t *CreateEPair( char *pKey, char *pValue ); + bool LoadVMFManifest( const char *pszFileName ); + const char *GetInstancePath( ) { return m_InstancePath; } + + void CordonWorld( ); + +private: + bool LoadVMFManifestUserPrefs( const char *pszFileName ); + + + CUtlVector< CManifestMap * > m_Maps; + char m_InstancePath[ MAX_PATH ]; + bool m_bIsCordoning; + CUtlVector< Cordon_t > m_Cordons; + entity_t *m_CordoningMapEnt; +}; + +#endif // #ifndef __MANIFEST_H diff --git a/mp/src/utils/vbsp/map.cpp b/mp/src/utils/vbsp/map.cpp new file mode 100644 index 00000000..34219bd4 --- /dev/null +++ b/mp/src/utils/vbsp/map.cpp @@ -0,0 +1,3304 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "vbsp.h" +#include "map_shared.h" +#include "disp_vbsp.h" +#include "tier1/strtools.h" +#include "builddisp.h" +#include "tier0/icommandline.h" +#include "KeyValues.h" +#include "materialsub.h" +#include "fgdlib/fgdlib.h" +#include "manifest.h" + +#ifdef VSVMFIO +#include "VmfImport.h" +#endif // VSVMFIO + + +// undefine to make plane finding use linear sort +#define USE_HASHING + +#define RENDER_NORMAL_EPSILON 0.00001 +#define RENDER_DIST_EPSILON 0.01f + +#define BRUSH_CLIP_EPSILON 0.01f // this should probably be the same + // as clip epsilon, but it is 0.1f and I + // currently don't know how that number was + // come to (cab) - this is 0.01 of an inch + // for clipping brush solids +struct LoadSide_t +{ + mapbrush_t *pBrush; + side_t *pSide; + int nSideIndex; + int nBaseFlags; + int nBaseContents; + Vector planepts[3]; + brush_texture_t td; +}; + + +extern qboolean onlyents; + + +CUtlVector< CMapFile * > g_Maps; +CMapFile *g_MainMap = NULL; +CMapFile *g_LoadingMap = NULL; + +char CMapFile::m_InstancePath[ MAX_PATH ] = ""; +int CMapFile::m_InstanceCount = 0; +int CMapFile::c_areaportals = 0; + +void CMapFile::Init( void ) +{ + entity_num = 0; + num_entities = 0; + + nummapplanes = 0; + memset( mapplanes, 0, sizeof( mapplanes ) ); + + nummapbrushes = 0; + memset( mapbrushes, 0, sizeof( mapbrushes ) ); + + nummapbrushsides = 0; + memset( brushsides, 0, sizeof( brushsides ) ); + + memset( side_brushtextures, 0, sizeof( side_brushtextures ) ); + + memset( planehash, 0, sizeof( planehash ) ); + + m_ConnectionPairs = NULL; + + m_StartMapOverlays = g_aMapOverlays.Count(); + m_StartMapWaterOverlays = g_aMapWaterOverlays.Count(); + + c_boxbevels = 0; + c_edgebevels = 0; + c_clipbrushes = 0; + g_ClipTexinfo = -1; +} + + +// All the brush sides referenced by info_no_dynamic_shadow entities. +CUtlVector g_NoDynamicShadowSides; + + +void TestExpandBrushes (void); + +ChunkFileResult_t LoadDispDistancesCallback(CChunkFile *pFile, mapdispinfo_t *pMapDispInfo); +ChunkFileResult_t LoadDispDistancesKeyCallback(const char *szKey, const char *szValue, mapdispinfo_t *pMapDispInfo); +ChunkFileResult_t LoadDispInfoCallback(CChunkFile *pFile, mapdispinfo_t **ppMapDispInfo ); +ChunkFileResult_t LoadDispInfoKeyCallback(const char *szKey, const char *szValue, mapdispinfo_t *pMapDispInfo); +ChunkFileResult_t LoadDispNormalsCallback(CChunkFile *pFile, mapdispinfo_t *pMapDispInfo); +ChunkFileResult_t LoadDispNormalsKeyCallback(const char *szKey, const char *szValue, mapdispinfo_t *pMapDispInfo); +ChunkFileResult_t LoadDispOffsetsCallback(CChunkFile *pFile, mapdispinfo_t *pMapDispInfo); +ChunkFileResult_t LoadDispOffsetsKeyCallback(const char *szKey, const char *szValue, mapdispinfo_t *pMapDispInfo); +ChunkFileResult_t LoadDispAlphasCallback(CChunkFile *pFile, mapdispinfo_t *pMapDispInfo); +ChunkFileResult_t LoadDispAlphasKeyCallback(const char *szKey, const char *szValue, mapdispinfo_t *pMapDispInfo); +ChunkFileResult_t LoadDispTriangleTagsCallback(CChunkFile *pFile, mapdispinfo_t *pMapDispInfo); +ChunkFileResult_t LoadDispTriangleTagsKeyCallback(const char *szKey, const char *szValue, mapdispinfo_t *pMapDispInfo); + +#ifdef VSVMFIO +ChunkFileResult_t LoadDispOffsetNormalsCallback(CChunkFile *pFile, mapdispinfo_t *pMapDispInfo); +ChunkFileResult_t LoadDispOffsetNormalsKeyCallback(const char *szKey, const char *szValue, mapdispinfo_t *pMapDispInfo); +#endif // VSVMFIO + +ChunkFileResult_t LoadEntityCallback(CChunkFile *pFile, int nParam); +ChunkFileResult_t LoadEntityKeyCallback(const char *szKey, const char *szValue, LoadEntity_t *pLoadEntity); + +ChunkFileResult_t LoadConnectionsCallback(CChunkFile *pFile, LoadEntity_t *pLoadEntity); +ChunkFileResult_t LoadConnectionsKeyCallback(const char *szKey, const char *szValue, LoadEntity_t *pLoadEntity); + +ChunkFileResult_t LoadSolidCallback(CChunkFile *pFile, LoadEntity_t *pLoadEntity); +ChunkFileResult_t LoadSolidKeyCallback(const char *szKey, const char *szValue, mapbrush_t *pLoadBrush); + +ChunkFileResult_t LoadSideCallback(CChunkFile *pFile, LoadSide_t *pSideInfo); +ChunkFileResult_t LoadSideKeyCallback(const char *szKey, const char *szValue, LoadSide_t *pSideInfo); + + + +/* +============================================================================= + +PLANE FINDING + +============================================================================= +*/ + + +/* +================= +PlaneTypeForNormal +================= +*/ +int PlaneTypeForNormal (Vector& normal) +{ + vec_t ax, ay, az; + +// NOTE: should these have an epsilon around 1.0? + if (normal[0] == 1.0 || normal[0] == -1.0) + return PLANE_X; + if (normal[1] == 1.0 || normal[1] == -1.0) + return PLANE_Y; + if (normal[2] == 1.0 || normal[2] == -1.0) + return PLANE_Z; + + ax = fabs(normal[0]); + ay = fabs(normal[1]); + az = fabs(normal[2]); + + if (ax >= ay && ax >= az) + return PLANE_ANYX; + if (ay >= ax && ay >= az) + return PLANE_ANYY; + return PLANE_ANYZ; +} + +/* +================ +PlaneEqual +================ +*/ +qboolean PlaneEqual (plane_t *p, Vector& normal, vec_t dist, float normalEpsilon, float distEpsilon) +{ +#if 1 + if ( + fabs(p->normal[0] - normal[0]) < normalEpsilon + && fabs(p->normal[1] - normal[1]) < normalEpsilon + && fabs(p->normal[2] - normal[2]) < normalEpsilon + && fabs(p->dist - dist) < distEpsilon ) + return true; +#else + if (p->normal[0] == normal[0] + && p->normal[1] == normal[1] + && p->normal[2] == normal[2] + && p->dist == dist) + return true; +#endif + return false; +} + +/* +================ +AddPlaneToHash +================ +*/ +void CMapFile::AddPlaneToHash (plane_t *p) +{ + int hash; + + hash = (int)fabs(p->dist) / 8; + hash &= (PLANE_HASHES-1); + + p->hash_chain = planehash[hash]; + planehash[hash] = p; +} + +/* +================ +CreateNewFloatPlane +================ +*/ +int CMapFile::CreateNewFloatPlane (Vector& normal, vec_t dist) +{ + plane_t *p, temp; + + if (VectorLength(normal) < 0.5) + g_MapError.ReportError ("FloatPlane: bad normal"); + // create a new plane + if (nummapplanes+2 > MAX_MAP_PLANES) + g_MapError.ReportError ("MAX_MAP_PLANES"); + + p = &mapplanes[nummapplanes]; + VectorCopy (normal, p->normal); + p->dist = dist; + p->type = (p+1)->type = PlaneTypeForNormal (p->normal); + + VectorSubtract (vec3_origin, normal, (p+1)->normal); + (p+1)->dist = -dist; + + nummapplanes += 2; + + // allways put axial planes facing positive first + if (p->type < 3) + { + if (p->normal[0] < 0 || p->normal[1] < 0 || p->normal[2] < 0) + { + // flip order + temp = *p; + *p = *(p+1); + *(p+1) = temp; + + AddPlaneToHash (p); + AddPlaneToHash (p+1); + return nummapplanes - 1; + } + } + + AddPlaneToHash (p); + AddPlaneToHash (p+1); + return nummapplanes - 2; +} + + +/* +============== +SnapVector +============== +*/ +bool SnapVector (Vector& normal) +{ + int i; + + for (i=0 ; i<3 ; i++) + { + if ( fabs(normal[i] - 1) < RENDER_NORMAL_EPSILON ) + { + VectorClear (normal); + normal[i] = 1; + return true; + } + + if ( fabs(normal[i] - -1) < RENDER_NORMAL_EPSILON ) + { + VectorClear (normal); + normal[i] = -1; + return true; + } + } + + return false; +} + + +//----------------------------------------------------------------------------- +// Purpose: Snaps normal to axis-aligned if it is within an epsilon of axial. +// Rounds dist to integer if it is within an epsilon of integer. +// Input : normal - Plane normal vector (assumed to be unit length). +// dist - Plane constant. +//----------------------------------------------------------------------------- +void SnapPlane(Vector &normal, vec_t &dist) +{ + SnapVector(normal); + + if (fabs(dist - RoundInt(dist)) < RENDER_DIST_EPSILON) + { + dist = RoundInt(dist); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Snaps normal to axis-aligned if it is within an epsilon of axial. +// Recalculates dist if the normal was snapped. Rounds dist to integer +// if it is within an epsilon of integer. +// Input : normal - Plane normal vector (assumed to be unit length). +// dist - Plane constant. +// p0, p1, p2 - Three points on the plane. +//----------------------------------------------------------------------------- +void SnapPlane(Vector &normal, vec_t &dist, const Vector &p0, const Vector &p1, const Vector &p2) +{ + if (SnapVector(normal)) + { + // + // Calculate a new plane constant using the snapped normal. Use the + // centroid of the three plane points to minimize error. This is like + // rotating the plane around the centroid. + // + Vector p3 = (p0 + p1 + p2) / 3.0f; + dist = normal.Dot(p3); + if ( g_snapAxialPlanes ) + { + dist = RoundInt(dist); + } + } + + if (fabs(dist - RoundInt(dist)) < RENDER_DIST_EPSILON) + { + dist = RoundInt(dist); + } +} + + +/* +============= +FindFloatPlane + +============= +*/ +#ifndef USE_HASHING +int CMapFile::FindFloatPlane (Vector& normal, vec_t dist) +{ + int i; + plane_t *p; + + SnapPlane(normal, dist); + for (i=0, p=mapplanes ; ihash_chain) + { + if (PlaneEqual (p, normal, dist, RENDER_NORMAL_EPSILON, RENDER_DIST_EPSILON)) + return p-mapplanes; + } + } + + return CreateNewFloatPlane (normal, dist); +} +#endif + + +//----------------------------------------------------------------------------- +// Purpose: Builds a plane normal and distance from three points on the plane. +// If the normal is nearly axial, it will be snapped to be axial. Looks +// up the plane in the unique planes. +// Input : p0, p1, p2 - Three points on the plane. +// Output : Returns the index of the plane in the planes list. +//----------------------------------------------------------------------------- +int CMapFile::PlaneFromPoints(const Vector &p0, const Vector &p1, const Vector &p2) +{ + Vector t1, t2, normal; + vec_t dist; + + VectorSubtract (p0, p1, t1); + VectorSubtract (p2, p1, t2); + CrossProduct (t1, t2, normal); + VectorNormalize (normal); + + dist = DotProduct (p0, normal); + + SnapPlane(normal, dist, p0, p1, p2); + + return FindFloatPlane (normal, dist); +} + + +/* +=========== +BrushContents +=========== +*/ +int BrushContents (mapbrush_t *b) +{ + int contents; + int unionContents = 0; + side_t *s; + int i; + + s = &b->original_sides[0]; + contents = s->contents; + unionContents = contents; + for (i=1 ; inumsides ; i++, s++) + { + s = &b->original_sides[i]; + + unionContents |= s->contents; +#if 0 + if (s->contents != contents) + { + Msg("Brush %i: mixed face contents\n", b->id); + break; + } +#endif + } + + // NOTE: we're making slime translucent so that it doesn't block lighting on things floating on its surface + int transparentContents = unionContents & (CONTENTS_WINDOW|CONTENTS_GRATE|CONTENTS_WATER|CONTENTS_SLIME); + if ( transparentContents ) + { + contents |= transparentContents | CONTENTS_TRANSLUCENT; + contents &= ~CONTENTS_SOLID; + } + + return contents; +} + + +//============================================================================ + +bool IsAreaPortal( char const *pClassName ) +{ + // If the class name starts with "func_areaportal", then it's considered an area portal. + char const *pBaseName = "func_areaportal"; + char const *pCur = pBaseName; + while( *pCur && *pClassName ) + { + if( *pCur != *pClassName ) + break; + + ++pCur; + ++pClassName; + } + + return *pCur == 0; +} + + +/* +================= +AddBrushBevels + +Adds any additional planes necessary to allow the brush to be expanded +against axial bounding boxes +================= +*/ +void CMapFile::AddBrushBevels (mapbrush_t *b) +{ + int axis, dir; + int i, j, k, l, order; + side_t sidetemp; + brush_texture_t tdtemp; + side_t *s, *s2; + Vector normal; + float dist; + winding_t *w, *w2; + Vector vec, vec2; + float d; + + // + // add the axial planes + // + order = 0; + for (axis=0 ; axis <3 ; axis++) + { + for (dir=-1 ; dir <= 1 ; dir+=2, order++) + { + // see if the plane is allready present + for (i=0, s=b->original_sides ; inumsides ; i++,s++) + { + if (mapplanes[s->planenum].normal[axis] == dir) + break; + } + + if (i == b->numsides) + { // add a new side + if (nummapbrushsides == MAX_MAP_BRUSHSIDES) + g_MapError.ReportError ("MAX_MAP_BRUSHSIDES"); + nummapbrushsides++; + b->numsides++; + VectorClear (normal); + normal[axis] = dir; + if (dir == 1) + dist = b->maxs[axis]; + else + dist = -b->mins[axis]; + s->planenum = FindFloatPlane (normal, dist); + s->texinfo = b->original_sides[0].texinfo; + s->contents = b->original_sides[0].contents; + s->bevel = true; + c_boxbevels++; + } + + // if the plane is not in it canonical order, swap it + if (i != order) + { + sidetemp = b->original_sides[order]; + b->original_sides[order] = b->original_sides[i]; + b->original_sides[i] = sidetemp; + + j = b->original_sides - brushsides; + tdtemp = side_brushtextures[j+order]; + side_brushtextures[j+order] = side_brushtextures[j+i]; + side_brushtextures[j+i] = tdtemp; + } + } + } + + // + // add the edge bevels + // + if (b->numsides == 6) + return; // pure axial + + // test the non-axial plane edges + for (i=6 ; inumsides ; i++) + { + s = b->original_sides + i; + w = s->winding; + if (!w) + continue; + for (j=0 ; jnumpoints ; j++) + { + k = (j+1)%w->numpoints; + VectorSubtract (w->p[j], w->p[k], vec); + if (VectorNormalize (vec) < 0.5) + continue; + SnapVector (vec); + for (k=0 ; k<3 ; k++) + if ( vec[k] == -1 || vec[k] == 1) + break; // axial + if (k != 3) + continue; // only test non-axial edges + + // try the six possible slanted axials from this edge + for (axis=0 ; axis <3 ; axis++) + { + for (dir=-1 ; dir <= 1 ; dir+=2) + { + // construct a plane + VectorClear (vec2); + vec2[axis] = dir; + CrossProduct (vec, vec2, normal); + if (VectorNormalize (normal) < 0.5) + continue; + dist = DotProduct (w->p[j], normal); + + // if all the points on all the sides are + // behind this plane, it is a proper edge bevel + for (k=0 ; knumsides ; k++) + { + // if this plane has allready been used, skip it + // NOTE: Use a larger tolerance for collision planes than for rendering planes + if ( PlaneEqual(&mapplanes[b->original_sides[k].planenum], normal, dist, 0.01f, 0.01f ) ) + break; + + w2 = b->original_sides[k].winding; + if (!w2) + continue; + for (l=0 ; lnumpoints ; l++) + { + d = DotProduct (w2->p[l], normal) - dist; + if (d > 0.1) + break; // point in front + } + if (l != w2->numpoints) + break; + } + + if (k != b->numsides) + continue; // wasn't part of the outer hull + // add this plane + if (nummapbrushsides == MAX_MAP_BRUSHSIDES) + g_MapError.ReportError ("MAX_MAP_BRUSHSIDES"); + nummapbrushsides++; + s2 = &b->original_sides[b->numsides]; + s2->planenum = FindFloatPlane (normal, dist); + s2->texinfo = b->original_sides[0].texinfo; + s2->contents = b->original_sides[0].contents; + s2->bevel = true; + c_edgebevels++; + b->numsides++; + } + } + } + } +} + +/* +================ +MakeBrushWindings + +makes basewindigs for sides and mins / maxs for the brush +================ +*/ +qboolean CMapFile::MakeBrushWindings (mapbrush_t *ob) +{ + int i, j; + winding_t *w; + side_t *side; + plane_t *plane; + + ClearBounds (ob->mins, ob->maxs); + + for (i=0 ; inumsides ; i++) + { + plane = &mapplanes[ob->original_sides[i].planenum]; + w = BaseWindingForPlane (plane->normal, plane->dist); + for (j=0 ; jnumsides && w; j++) + { + if (i == j) + continue; + if (ob->original_sides[j].bevel) + continue; + plane = &mapplanes[ob->original_sides[j].planenum^1]; +// ChopWindingInPlace (&w, plane->normal, plane->dist, 0); //CLIP_EPSILON); + // adding an epsilon here, due to precision issues creating complex + // displacement surfaces (cab) + ChopWindingInPlace( &w, plane->normal, plane->dist, BRUSH_CLIP_EPSILON ); + } + + side = &ob->original_sides[i]; + side->winding = w; + if (w) + { + side->visible = true; + for (j=0 ; jnumpoints ; j++) + AddPointToBounds (w->p[j], ob->mins, ob->maxs); + } + } + + for (i=0 ; i<3 ; i++) + { + if (ob->mins[i] < MIN_COORD_INTEGER || ob->maxs[i] > MAX_COORD_INTEGER) + Msg("Brush %i: bounds out of range\n", ob->id); + if (ob->mins[i] > MAX_COORD_INTEGER || ob->maxs[i] < MIN_COORD_INTEGER) + Msg("Brush %i: no visible sides on brush\n", ob->id); + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Takes all of the brushes from the current entity and adds them to the +// world's brush list. Used by func_detail and func_areaportal. +// THIS ROUTINE MAY ONLY BE USED DURING ENTITY LOADING. +// Input : mapent - Entity whose brushes are to be moved to the world. +//----------------------------------------------------------------------------- +void CMapFile::MoveBrushesToWorld( entity_t *mapent ) +{ + int newbrushes; + int worldbrushes; + mapbrush_t *temp; + int i; + + // this is pretty gross, because the brushes are expected to be + // in linear order for each entity + + newbrushes = mapent->numbrushes; + worldbrushes = entities[0].numbrushes; + + temp = (mapbrush_t *)malloc(newbrushes*sizeof(mapbrush_t)); + memcpy (temp, mapbrushes + mapent->firstbrush, newbrushes*sizeof(mapbrush_t)); + +#if 0 // let them keep their original brush numbers + for (i=0 ; inumbrushes = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: Takes all of the brushes from the current entity and adds them to the +// world's brush list. Used by func_detail and func_areaportal. +// Input : mapent - Entity whose brushes are to be moved to the world. +//----------------------------------------------------------------------------- +void CMapFile::MoveBrushesToWorldGeneral( entity_t *mapent ) +{ + int newbrushes; + int worldbrushes; + mapbrush_t *temp; + int i; + + for( i = 0; i < nummapdispinfo; i++ ) + { + if ( mapdispinfo[ i ].entitynum == ( mapent - entities ) ) + { + mapdispinfo[ i ].entitynum = 0; + } + } + + // this is pretty gross, because the brushes are expected to be + // in linear order for each entity + newbrushes = mapent->numbrushes; + worldbrushes = entities[0].numbrushes; + + temp = (mapbrush_t *)malloc(newbrushes*sizeof(mapbrush_t)); + memcpy (temp, mapbrushes + mapent->firstbrush, newbrushes*sizeof(mapbrush_t)); + +#if 0 // let them keep their original brush numbers + for (i=0 ; ifirstbrush - worldbrushes) ); + + + // wwwxxxmmyyy + + // copy the new brushes down + memcpy (mapbrushes + worldbrushes, temp, sizeof(mapbrush_t) * newbrushes); + + // fix up indexes + entities[0].numbrushes += newbrushes; + for (i=1 ; ifirstbrush ) // if we use <=, then we'll remap the passed in ent, which we don't want to + { + entities[ i ].firstbrush += newbrushes; + } + } + free (temp); + + mapent->numbrushes = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: Iterates the sides of brush and removed CONTENTS_DETAIL from each side +// Input : *brush - +//----------------------------------------------------------------------------- +void RemoveContentsDetailFromBrush( mapbrush_t *brush ) +{ + // Only valid on non-world brushes + Assert( brush->entitynum != 0 ); + + side_t *s; + int i; + + s = &brush->original_sides[0]; + for ( i=0 ; inumsides ; i++, s++ ) + { + if ( s->contents & CONTENTS_DETAIL ) + { + s->contents &= ~CONTENTS_DETAIL; + } + } + +} + +//----------------------------------------------------------------------------- +// Purpose: Iterates all brushes in an entity and removes CONTENTS_DETAIL from all brushes +// Input : *mapent - +//----------------------------------------------------------------------------- +void CMapFile::RemoveContentsDetailFromEntity( entity_t *mapent ) +{ + int i; + for ( i = 0; i < mapent->numbrushes; i++ ) + { + int brushnum = mapent->firstbrush + i; + + mapbrush_t *brush = &mapbrushes[ brushnum ]; + RemoveContentsDetailFromBrush( brush ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pFile - +// *pDisp - +// Output : ChunkFileResult_t +//----------------------------------------------------------------------------- +ChunkFileResult_t LoadDispDistancesCallback(CChunkFile *pFile, mapdispinfo_t *pMapDispInfo) +{ + return(pFile->ReadChunk((KeyHandler_t)LoadDispDistancesKeyCallback, pMapDispInfo)); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : szKey - +// szValue - +// pDisp - +// Output : ChunkFileResult_t +//----------------------------------------------------------------------------- +ChunkFileResult_t LoadDispDistancesKeyCallback(const char *szKey, const char *szValue, mapdispinfo_t *pMapDispInfo) +{ + if (!strnicmp(szKey, "row", 3)) + { + char szBuf[MAX_KEYVALUE_LEN]; + strcpy(szBuf, szValue); + + int nCols = (1 << pMapDispInfo->power) + 1; + int nRow = atoi(&szKey[3]); + + char *pszNext = strtok(szBuf, " "); + int nIndex = nRow * nCols; + + while (pszNext != NULL) + { + pMapDispInfo->dispDists[nIndex] = (float)atof(pszNext); + pszNext = strtok(NULL, " "); + nIndex++; + } + } + + return(ChunkFile_Ok); +} + + +//----------------------------------------------------------------------------- +// Purpose: load in the displacement info "chunk" from the .map file into the +// vbsp map displacement info data structure +// Output : return the index of the map displacement info +//----------------------------------------------------------------------------- +ChunkFileResult_t LoadDispInfoCallback(CChunkFile *pFile, mapdispinfo_t **ppMapDispInfo ) +{ + // + // check to see if we exceeded the maximum displacement info list size + // + if (nummapdispinfo > MAX_MAP_DISPINFO) + { + g_MapError.ReportError( "ParseDispInfoChunk: nummapdispinfo > MAX_MAP_DISPINFO" ); + } + + // get a pointer to the next available displacement info slot + mapdispinfo_t *pMapDispInfo = &mapdispinfo[nummapdispinfo]; + nummapdispinfo++; + + // + // Set up handlers for the subchunks that we are interested in. + // + CChunkHandlerMap Handlers; + Handlers.AddHandler("normals", (ChunkHandler_t)LoadDispNormalsCallback, pMapDispInfo); + Handlers.AddHandler("distances", (ChunkHandler_t)LoadDispDistancesCallback, pMapDispInfo); + Handlers.AddHandler("offsets", (ChunkHandler_t)LoadDispOffsetsCallback, pMapDispInfo); + Handlers.AddHandler("alphas", (ChunkHandler_t)LoadDispAlphasCallback, pMapDispInfo); + Handlers.AddHandler("triangle_tags", (ChunkHandler_t)LoadDispTriangleTagsCallback, pMapDispInfo); + +#ifdef VSVMFIO + Handlers.AddHandler("offset_normals", (ChunkHandler_t)LoadDispOffsetNormalsCallback, pMapDispInfo); +#endif // VSVMFIO + + // + // Read the displacement chunk. + // + pFile->PushHandlers(&Handlers); + ChunkFileResult_t eResult = pFile->ReadChunk((KeyHandler_t)LoadDispInfoKeyCallback, pMapDispInfo); + pFile->PopHandlers(); + + if (eResult == ChunkFile_Ok) + { + // return a pointer to the displacement info + *ppMapDispInfo = pMapDispInfo; + } + + return(eResult); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *szKey - +// *szValue - +// *mapent - +// Output : ChunkFileResult_t +//----------------------------------------------------------------------------- +ChunkFileResult_t LoadDispInfoKeyCallback(const char *szKey, const char *szValue, mapdispinfo_t *pMapDispInfo) +{ + if (!stricmp(szKey, "power")) + { + CChunkFile::ReadKeyValueInt(szValue, pMapDispInfo->power); + } +#ifdef VSVMFIO + else if (!stricmp(szKey, "elevation")) + { + CChunkFile::ReadKeyValueFloat(szValue, pMapDispInfo->m_elevation); + } +#endif // VSVMFIO + else if (!stricmp(szKey, "uaxis")) + { + CChunkFile::ReadKeyValueVector3(szValue, pMapDispInfo->uAxis); + } + else if (!stricmp(szKey, "vaxis")) + { + CChunkFile::ReadKeyValueVector3(szValue, pMapDispInfo->vAxis); + } + else if( !stricmp( szKey, "startposition" ) ) + { + CChunkFile::ReadKeyValueVector3( szValue, pMapDispInfo->startPosition ); + } + else if( !stricmp( szKey, "flags" ) ) + { + CChunkFile::ReadKeyValueInt( szValue, pMapDispInfo->flags ); + } +#if 0 // old data + else if (!stricmp( szKey, "alpha" ) ) + { + CChunkFile::ReadKeyValueVector4( szValue, pMapDispInfo->alphaValues ); + } +#endif + else if (!stricmp(szKey, "mintess")) + { + CChunkFile::ReadKeyValueInt(szValue, pMapDispInfo->minTess); + } + else if (!stricmp(szKey, "smooth")) + { + CChunkFile::ReadKeyValueFloat(szValue, pMapDispInfo->smoothingAngle); + } + + return(ChunkFile_Ok); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pFile - +// *pDisp - +// Output : ChunkFileResult_t +//----------------------------------------------------------------------------- +ChunkFileResult_t LoadDispNormalsCallback(CChunkFile *pFile, mapdispinfo_t *pMapDispInfo) +{ + return(pFile->ReadChunk((KeyHandler_t)LoadDispNormalsKeyCallback, pMapDispInfo)); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *szKey - +// *szValue - +// *pDisp - +// Output : ChunkFileResult_t +//----------------------------------------------------------------------------- +ChunkFileResult_t LoadDispNormalsKeyCallback(const char *szKey, const char *szValue, mapdispinfo_t *pMapDispInfo) +{ + if (!strnicmp(szKey, "row", 3)) + { + char szBuf[MAX_KEYVALUE_LEN]; + strcpy(szBuf, szValue); + + int nCols = (1 << pMapDispInfo->power) + 1; + int nRow = atoi(&szKey[3]); + + char *pszNext0 = strtok(szBuf, " "); + char *pszNext1 = strtok(NULL, " "); + char *pszNext2 = strtok(NULL, " "); + + int nIndex = nRow * nCols; + + while ((pszNext0 != NULL) && (pszNext1 != NULL) && (pszNext2 != NULL)) + { + pMapDispInfo->vectorDisps[nIndex][0] = (float)atof(pszNext0); + pMapDispInfo->vectorDisps[nIndex][1] = (float)atof(pszNext1); + pMapDispInfo->vectorDisps[nIndex][2] = (float)atof(pszNext2); + + pszNext0 = strtok(NULL, " "); + pszNext1 = strtok(NULL, " "); + pszNext2 = strtok(NULL, " "); + + nIndex++; + } + } + + return(ChunkFile_Ok); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *szKey - +// *szValue - +// *pDisp - +// Output : ChunkFileResult_t +//----------------------------------------------------------------------------- +ChunkFileResult_t LoadDispOffsetsCallback(CChunkFile *pFile, mapdispinfo_t *pMapDispInfo) +{ + return(pFile->ReadChunk((KeyHandler_t)LoadDispOffsetsKeyCallback, pMapDispInfo)); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *szKey - +// *szValue - +// *pDisp - +// Output : ChunkFileResult_t +//----------------------------------------------------------------------------- +ChunkFileResult_t LoadDispOffsetsKeyCallback(const char *szKey, const char *szValue, mapdispinfo_t *pMapDispInfo) +{ + if (!strnicmp(szKey, "row", 3)) + { + char szBuf[MAX_KEYVALUE_LEN]; + strcpy(szBuf, szValue); + + int nCols = (1 << pMapDispInfo->power) + 1; + int nRow = atoi(&szKey[3]); + + char *pszNext0 = strtok(szBuf, " "); + char *pszNext1 = strtok(NULL, " "); + char *pszNext2 = strtok(NULL, " "); + + int nIndex = nRow * nCols; + + while ((pszNext0 != NULL) && (pszNext1 != NULL) && (pszNext2 != NULL)) + { + pMapDispInfo->vectorOffsets[nIndex][0] = (float)atof(pszNext0); + pMapDispInfo->vectorOffsets[nIndex][1] = (float)atof(pszNext1); + pMapDispInfo->vectorOffsets[nIndex][2] = (float)atof(pszNext2); + + pszNext0 = strtok(NULL, " "); + pszNext1 = strtok(NULL, " "); + pszNext2 = strtok(NULL, " "); + + nIndex++; + } + } + + return(ChunkFile_Ok); +} + + +#ifdef VSVMFIO +ChunkFileResult_t LoadDispOffsetNormalsCallback(CChunkFile *pFile, mapdispinfo_t *pMapDispInfo) +{ + return(pFile->ReadChunk((KeyHandler_t)LoadDispOffsetNormalsKeyCallback, pMapDispInfo)); +} + + +ChunkFileResult_t LoadDispOffsetNormalsKeyCallback(const char *szKey, const char *szValue, mapdispinfo_t *pMapDispInfo) +{ + if (!strnicmp(szKey, "row", 3)) + { + char szBuf[MAX_KEYVALUE_LEN]; + strcpy(szBuf, szValue); + + int nCols = (1 << pMapDispInfo->power) + 1; + int nRow = atoi(&szKey[3]); + + char *pszNext0 = strtok(szBuf, " "); + char *pszNext1 = strtok(NULL, " "); + char *pszNext2 = strtok(NULL, " "); + + int nIndex = nRow * nCols; + + while ((pszNext0 != NULL) && (pszNext1 != NULL) && (pszNext2 != NULL)) + { + pMapDispInfo->m_offsetNormals[nIndex][0] = (float)atof(pszNext0); + pMapDispInfo->m_offsetNormals[nIndex][1] = (float)atof(pszNext1); + pMapDispInfo->m_offsetNormals[nIndex][2] = (float)atof(pszNext2); + + pszNext0 = strtok(NULL, " "); + pszNext1 = strtok(NULL, " "); + pszNext2 = strtok(NULL, " "); + + nIndex++; + } + } + + return(ChunkFile_Ok); +} +#endif // VSVMFIO + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *szKey - +// *szValue - +// *pDisp - +// Output : ChunkFileResult_t +//----------------------------------------------------------------------------- +ChunkFileResult_t LoadDispAlphasCallback(CChunkFile *pFile, mapdispinfo_t *pMapDispInfo) +{ + return(pFile->ReadChunk((KeyHandler_t)LoadDispAlphasKeyCallback, pMapDispInfo)); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *szKey - +// *szValue - +// *pDisp - +// Output : ChunkFileResult_t +//----------------------------------------------------------------------------- +ChunkFileResult_t LoadDispAlphasKeyCallback(const char *szKey, const char *szValue, mapdispinfo_t *pMapDispInfo) +{ + if (!strnicmp(szKey, "row", 3)) + { + char szBuf[MAX_KEYVALUE_LEN]; + strcpy(szBuf, szValue); + + int nCols = (1 << pMapDispInfo->power) + 1; + int nRow = atoi(&szKey[3]); + + char *pszNext0 = strtok(szBuf, " "); + + int nIndex = nRow * nCols; + + while (pszNext0 != NULL) + { + pMapDispInfo->alphaValues[nIndex] = (float)atof(pszNext0); + pszNext0 = strtok(NULL, " "); + nIndex++; + } + } + + return(ChunkFile_Ok); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +ChunkFileResult_t LoadDispTriangleTagsCallback(CChunkFile *pFile, mapdispinfo_t *pMapDispInfo) +{ + return(pFile->ReadChunk((KeyHandler_t)LoadDispTriangleTagsKeyCallback, pMapDispInfo)); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +ChunkFileResult_t LoadDispTriangleTagsKeyCallback(const char *szKey, const char *szValue, mapdispinfo_t *pMapDispInfo) +{ + if ( !strnicmp( szKey, "row", 3 ) ) + { + char szBuf[MAX_KEYVALUE_LEN]; + strcpy( szBuf, szValue ); + + int nCols = ( 1 << pMapDispInfo->power ); + int nRow = atoi( &szKey[3] ); + + char *pszNext = strtok( szBuf, " " ); + + int nIndex = nRow * nCols; + int iTri = nIndex * 2; + + while ( pszNext != NULL ) + { + // Collapse the tags here! + unsigned short nTriTags = ( unsigned short )atoi( pszNext ); + + // Walkable + bool bWalkable = ( ( nTriTags & COREDISPTRI_TAG_WALKABLE ) != 0 ); + if ( ( ( nTriTags & COREDISPTRI_TAG_FORCE_WALKABLE_BIT ) != 0 ) ) + { + bWalkable = ( ( nTriTags & COREDISPTRI_TAG_FORCE_WALKABLE_VAL ) != 0 ); + } + + // Buildable + bool bBuildable = ( ( nTriTags & COREDISPTRI_TAG_BUILDABLE ) != 0 ); + if ( ( ( nTriTags & COREDISPTRI_TAG_FORCE_BUILDABLE_BIT ) != 0 ) ) + { + bBuildable = ( ( nTriTags & COREDISPTRI_TAG_FORCE_BUILDABLE_VAL ) != 0 ); + } + + nTriTags = 0; + if ( bWalkable ) + { + nTriTags |= DISPTRI_TAG_WALKABLE; + } + + if ( bBuildable ) + { + nTriTags |= DISPTRI_TAG_BUILDABLE; + } + + pMapDispInfo->triTags[iTri] = nTriTags; + pszNext = strtok( NULL, " " ); + iTri++; + } + } + + return( ChunkFile_Ok ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : brushSideID - +// Output : int +//----------------------------------------------------------------------------- +int CMapFile::SideIDToIndex( int brushSideID ) +{ + int i; + for ( i = 0; i < nummapbrushsides; i++ ) + { + if ( brushsides[i].id == brushSideID ) + { + return i; + } + } + Assert( 0 ); + return -1; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *mapent - +// *key - +//----------------------------------------------------------------------------- +void ConvertSideList( entity_t *mapent, char *key ) +{ + char *pszSideList = ValueForKey( mapent, key ); + + if (pszSideList) + { + char *pszTmpList = ( char* )_alloca( strlen( pszSideList ) + 1 ); + strcpy( pszTmpList, pszSideList ); + + bool bFirst = true; + char szNewValue[1024]; + szNewValue[0] = '\0'; + + const char *pszScan = strtok( pszTmpList, " " ); + if ( !pszScan ) + return; + do + { + int nSideID; + + if ( sscanf( pszScan, "%d", &nSideID ) == 1 ) + { + int nIndex = g_LoadingMap->SideIDToIndex(nSideID); + if (nIndex != -1) + { + if (!bFirst) + { + strcat( szNewValue, " " ); + } + else + { + bFirst = false; + } + + char szIndex[15]; + itoa( nIndex, szIndex, 10 ); + strcat( szNewValue, szIndex ); + } + } + } while ( ( pszScan = strtok( NULL, " " ) ) ); + + SetKeyValue( mapent, key, szNewValue ); + } +} + + +// Add all the sides referenced by info_no_dynamic_shadows entities to g_NoDynamicShadowSides. +ChunkFileResult_t HandleNoDynamicShadowsEnt( entity_t *pMapEnt ) +{ + // Get the list of the sides. + char *pSideList = ValueForKey( pMapEnt, "sides" ); + + // Parse the side list. + char *pScan = strtok( pSideList, " " ); + if( pScan ) + { + do + { + int brushSideID; + if( sscanf( pScan, "%d", &brushSideID ) == 1 ) + { + if ( g_NoDynamicShadowSides.Find( brushSideID ) == -1 ) + g_NoDynamicShadowSides.AddToTail( brushSideID ); + } + } while( ( pScan = strtok( NULL, " " ) ) ); + } + + // Clear out this entity. + pMapEnt->epairs = NULL; + return ( ChunkFile_Ok ); +} + + +static ChunkFileResult_t LoadOverlayDataTransitionKeyCallback( const char *szKey, const char *szValue, mapoverlay_t *pOverlay ) +{ + if ( !stricmp( szKey, "material" ) ) + { + // Get the material name. + const char *pMaterialName = szValue; + if( g_ReplaceMaterials ) + { + pMaterialName = ReplaceMaterialName( szValue ); + } + + Assert( strlen( pMaterialName ) < OVERLAY_MAP_STRLEN ); + if ( strlen( pMaterialName ) >= OVERLAY_MAP_STRLEN ) + { + Error( "Overlay Material Name (%s) > OVERLAY_MAP_STRLEN (%d)", pMaterialName, OVERLAY_MAP_STRLEN ); + return ChunkFile_Fail; + } + strcpy( pOverlay->szMaterialName, pMaterialName ); + } + else if ( !stricmp( szKey, "StartU") ) + { + CChunkFile::ReadKeyValueFloat( szValue, pOverlay->flU[0] ); + } + else if ( !stricmp( szKey, "EndU" ) ) + { + CChunkFile::ReadKeyValueFloat( szValue, pOverlay->flU[1] ); + } + else if ( !stricmp( szKey, "StartV" ) ) + { + CChunkFile::ReadKeyValueFloat( szValue, pOverlay->flV[0] ); + } + else if ( !stricmp( szKey, "EndV" ) ) + { + CChunkFile::ReadKeyValueFloat( szValue, pOverlay->flV[1] ); + } + else if ( !stricmp( szKey, "BasisOrigin" ) ) + { + CChunkFile::ReadKeyValueVector3( szValue, pOverlay->vecOrigin ); + } + else if ( !stricmp( szKey, "BasisU" ) ) + { + CChunkFile::ReadKeyValueVector3( szValue, pOverlay->vecBasis[0] ); + } + else if ( !stricmp( szKey, "BasisV" ) ) + { + CChunkFile::ReadKeyValueVector3( szValue, pOverlay->vecBasis[1] ); + } + else if ( !stricmp( szKey, "BasisNormal" ) ) + { + CChunkFile::ReadKeyValueVector3( szValue, pOverlay->vecBasis[2] ); + } + else if ( !stricmp( szKey, "uv0" ) ) + { + CChunkFile::ReadKeyValueVector3( szValue, pOverlay->vecUVPoints[0] ); + } + else if ( !stricmp( szKey, "uv1" ) ) + { + CChunkFile::ReadKeyValueVector3( szValue, pOverlay->vecUVPoints[1] ); + } + else if ( !stricmp( szKey, "uv2" ) ) + { + CChunkFile::ReadKeyValueVector3( szValue, pOverlay->vecUVPoints[2] ); + } + else if ( !stricmp( szKey, "uv3" ) ) + { + CChunkFile::ReadKeyValueVector3( szValue, pOverlay->vecUVPoints[3] ); + } + else if ( !stricmp( szKey, "sides" ) ) + { + const char *pSideList = szValue; + char *pTmpList = ( char* )_alloca( strlen( pSideList ) + 1 ); + strcpy( pTmpList, pSideList ); + const char *pScan = strtok( pTmpList, " " ); + if ( !pScan ) + return ChunkFile_Fail; + + pOverlay->aSideList.Purge(); + pOverlay->aFaceList.Purge(); + + do + { + int nSideId; + if ( sscanf( pScan, "%d", &nSideId ) == 1 ) + { + pOverlay->aSideList.AddToTail( nSideId ); + } + } while ( ( pScan = strtok( NULL, " " ) ) ); + } + + return ChunkFile_Ok; +} + +static ChunkFileResult_t LoadOverlayDataTransitionCallback( CChunkFile *pFile, int nParam ) +{ + int iOverlay = g_aMapWaterOverlays.AddToTail(); + mapoverlay_t *pOverlay = &g_aMapWaterOverlays[iOverlay]; + if ( !pOverlay ) + return ChunkFile_Fail; + + pOverlay->nId = ( MAX_MAP_OVERLAYS + 1 ) + g_aMapWaterOverlays.Count() - 1; + pOverlay->m_nRenderOrder = 0; + + ChunkFileResult_t eResult = pFile->ReadChunk( ( KeyHandler_t )LoadOverlayDataTransitionKeyCallback, pOverlay ); + return eResult; +} + +static ChunkFileResult_t LoadOverlayTransitionCallback( CChunkFile *pFile, int nParam ) +{ + CChunkHandlerMap Handlers; + Handlers.AddHandler( "overlaydata", ( ChunkHandler_t )LoadOverlayDataTransitionCallback, 0 ); + pFile->PushHandlers( &Handlers ); + + ChunkFileResult_t eResult = pFile->ReadChunk( NULL, NULL ); + + pFile->PopHandlers(); + + return eResult; +} + +//----------------------------------------------------------------------------- +// Purpose: Iterates all brushes in a ladder entity, generates its mins and maxs. +// These are stored in the object, since the brushes are going to go away. +// Input : *mapent - +//----------------------------------------------------------------------------- +void CMapFile::AddLadderKeys( entity_t *mapent ) +{ + Vector mins, maxs; + ClearBounds( mins, maxs ); + + int i; + for ( i = 0; i < mapent->numbrushes; i++ ) + { + int brushnum = mapent->firstbrush + i; + mapbrush_t *brush = &mapbrushes[ brushnum ]; + + AddPointToBounds( brush->mins, mins, maxs ); + AddPointToBounds( brush->maxs, mins, maxs ); + } + + char buf[16]; + + Q_snprintf( buf, sizeof(buf), "%2.2f", mins.x ); + SetKeyValue( mapent, "mins.x", buf ); + + Q_snprintf( buf, sizeof(buf), "%2.2f", mins.y ); + SetKeyValue( mapent, "mins.y", buf ); + + Q_snprintf( buf, sizeof(buf), "%2.2f", mins.z ); + SetKeyValue( mapent, "mins.z", buf ); + + Q_snprintf( buf, sizeof(buf), "%2.2f", maxs.x ); + SetKeyValue( mapent, "maxs.x", buf ); + + Q_snprintf( buf, sizeof(buf), "%2.2f", maxs.y ); + SetKeyValue( mapent, "maxs.y", buf ); + + Q_snprintf( buf, sizeof(buf), "%2.2f", maxs.z ); + SetKeyValue( mapent, "maxs.z", buf ); +} + +ChunkFileResult_t LoadEntityCallback(CChunkFile *pFile, int nParam) +{ + return g_LoadingMap->LoadEntityCallback( pFile, nParam ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pFile - +// ulParam - +// Output : ChunkFileResult_t +//----------------------------------------------------------------------------- +ChunkFileResult_t CMapFile::LoadEntityCallback(CChunkFile *pFile, int nParam) +{ + if (num_entities == MAX_MAP_ENTITIES) + { + // Exits. + g_MapError.ReportError ("num_entities == MAX_MAP_ENTITIES"); + } + + entity_t *mapent = &entities[num_entities]; + num_entities++; + memset(mapent, 0, sizeof(*mapent)); + mapent->firstbrush = nummapbrushes; + mapent->numbrushes = 0; + //mapent->portalareas[0] = -1; + //mapent->portalareas[1] = -1; + + LoadEntity_t LoadEntity; + LoadEntity.pEntity = mapent; + + // No default flags/contents + LoadEntity.nBaseFlags = 0; + LoadEntity.nBaseContents = 0; + + // + // Set up handlers for the subchunks that we are interested in. + // + CChunkHandlerMap Handlers; + Handlers.AddHandler("solid", (ChunkHandler_t)::LoadSolidCallback, &LoadEntity); + Handlers.AddHandler("connections", (ChunkHandler_t)LoadConnectionsCallback, &LoadEntity); + Handlers.AddHandler( "overlaytransition", ( ChunkHandler_t )LoadOverlayTransitionCallback, 0 ); + + // + // Read the entity chunk. + // + pFile->PushHandlers(&Handlers); + ChunkFileResult_t eResult = pFile->ReadChunk((KeyHandler_t)LoadEntityKeyCallback, &LoadEntity); + pFile->PopHandlers(); + + if (eResult == ChunkFile_Ok) + { + GetVectorForKey (mapent, "origin", mapent->origin); + + const char *pMinDXLevelStr = ValueForKey( mapent, "mindxlevel" ); + const char *pMaxDXLevelStr = ValueForKey( mapent, "maxdxlevel" ); + if( *pMinDXLevelStr != '\0' || *pMaxDXLevelStr != '\0' ) + { + int min = 0; + int max = 0; + if( *pMinDXLevelStr ) + { + min = atoi( pMinDXLevelStr ); + } + if( *pMaxDXLevelStr ) + { + max = atoi( pMaxDXLevelStr ); + } + + // Set min and max to default values. + if( min == 0 ) + { + min = g_nDXLevel; + } + if( max == 0 ) + { + max = g_nDXLevel; + } + if( ( g_nDXLevel != 0 ) && ( g_nDXLevel < min || g_nDXLevel > max ) ) + { + mapent->numbrushes = 0; + mapent->epairs = NULL; + return(ChunkFile_Ok); + } + } + + // offset all of the planes and texinfo + if ( mapent->origin[0] || mapent->origin[1] || mapent->origin[2] ) + { + for (int i=0 ; inumbrushes ; i++) + { + mapbrush_t *b = &mapbrushes[mapent->firstbrush + i]; + for (int j=0 ; jnumsides ; j++) + { + side_t *s = &b->original_sides[j]; + vec_t newdist = mapplanes[s->planenum].dist - DotProduct (mapplanes[s->planenum].normal, mapent->origin); + s->planenum = FindFloatPlane (mapplanes[s->planenum].normal, newdist); + if ( !onlyents ) + { + s->texinfo = TexinfoForBrushTexture (&mapplanes[s->planenum], &side_brushtextures[s-brushsides], mapent->origin); + } + } + MakeBrushWindings (b); + } + } + + // + // func_detail brushes are moved into the world entity. The CONTENTS_DETAIL flag was set by the loader. + // + const char *pClassName = ValueForKey( mapent, "classname" ); + + if ( !strcmp( "func_detail", pClassName ) ) + { + MoveBrushesToWorld (mapent); + mapent->numbrushes = 0; + + // clear out this entity + mapent->epairs = NULL; + return(ChunkFile_Ok); + } + + // these get added to a list for processing the portal file + // but aren't necessary to emit to the BSP + if ( !strcmp( "func_viscluster", pClassName ) ) + { + AddVisCluster(mapent); + return(ChunkFile_Ok); + } + + // + // func_ladder brushes are moved into the world entity. We convert the func_ladder to an info_ladder + // that holds the ladder's mins and maxs, and leave the entity. This helps the bots figure out ladders. + // + if ( !strcmp( "func_ladder", pClassName ) ) + { + AddLadderKeys( mapent ); + + MoveBrushesToWorld (mapent); + + // Convert to info_ladder entity + SetKeyValue( mapent, "classname", "info_ladder" ); + + return(ChunkFile_Ok); + } + + if( !strcmp( "env_cubemap", pClassName ) ) + { + if( ( g_nDXLevel == 0 ) || ( g_nDXLevel >= 70 ) ) + { + const char *pSideListStr = ValueForKey( mapent, "sides" ); + int size; + size = IntForKey( mapent, "cubemapsize" ); + Cubemap_InsertSample( mapent->origin, size ); + Cubemap_SaveBrushSides( pSideListStr ); + } + // clear out this entity + mapent->epairs = NULL; + return(ChunkFile_Ok); + } + + if ( !strcmp( "test_sidelist", pClassName ) ) + { + ConvertSideList(mapent, "sides"); + return ChunkFile_Ok; + } + + if ( !strcmp( "info_overlay", pClassName ) ) + { + int iAccessorID = Overlay_GetFromEntity( mapent ); + + if ( iAccessorID < 0 ) + { + // Clear out this entity. + mapent->epairs = NULL; + } + else + { + // Convert to info_overlay_accessor entity + SetKeyValue( mapent, "classname", "info_overlay_accessor" ); + + // Remember the id for accessing the overlay + char buf[16]; + Q_snprintf( buf, sizeof(buf), "%i", iAccessorID ); + SetKeyValue( mapent, "OverlayID", buf ); + } + + return ( ChunkFile_Ok ); + } + + if ( !strcmp( "info_overlay_transition", pClassName ) ) + { + // Clear out this entity. + mapent->epairs = NULL; + return ( ChunkFile_Ok ); + } + + if ( Q_stricmp( pClassName, "info_no_dynamic_shadow" ) == 0 ) + { + return HandleNoDynamicShadowsEnt( mapent ); + } + + if ( Q_stricmp( pClassName, "func_instance_parms" ) == 0 ) + { + // Clear out this entity. + mapent->epairs = NULL; + return ( ChunkFile_Ok ); + } + + // areaportal entities move their brushes, but don't eliminate + // the entity + if( IsAreaPortal( pClassName ) ) + { + char str[128]; + + if (mapent->numbrushes != 1) + { + Error ("Entity %i: func_areaportal can only be a single brush", num_entities-1); + } + + mapbrush_t *b = &mapbrushes[nummapbrushes-1]; + b->contents = CONTENTS_AREAPORTAL; + c_areaportals++; + mapent->areaportalnum = c_areaportals; + + // set the portal number as "portalnumber" + sprintf (str, "%i", c_areaportals); + SetKeyValue (mapent, "portalnumber", str); + + MoveBrushesToWorld (mapent); + return(ChunkFile_Ok); + } + +#ifdef VSVMFIO + if ( !Q_stricmp( pClassName, "light" ) ) + { + CVmfImport::GetVmfImporter()->ImportLightCallback( + ValueForKey( mapent, "hammerid" ), + ValueForKey( mapent, "origin" ), + ValueForKey( mapent, "_light" ), + ValueForKey( mapent, "_lightHDR" ), + ValueForKey( mapent, "_lightscaleHDR" ), + ValueForKey( mapent, "_quadratic_attn" ) ); + } + + if ( !Q_stricmp( pClassName, "light_spot" ) ) + { + CVmfImport::GetVmfImporter()->ImportLightSpotCallback( + ValueForKey( mapent, "hammerid" ), + ValueForKey( mapent, "origin" ), + ValueForKey( mapent, "angles" ), + ValueForKey( mapent, "pitch" ), + ValueForKey( mapent, "_light" ), + ValueForKey( mapent, "_lightHDR" ), + ValueForKey( mapent, "_lightscaleHDR" ), + ValueForKey( mapent, "_quadratic_attn" ), + ValueForKey( mapent, "_inner_cone" ), + ValueForKey( mapent, "_cone" ), + ValueForKey( mapent, "_exponent" ) ); + } + + if ( !Q_stricmp( pClassName, "light_dynamic" ) ) + { + CVmfImport::GetVmfImporter()->ImportLightDynamicCallback( + ValueForKey( mapent, "hammerid" ), + ValueForKey( mapent, "origin" ), + ValueForKey( mapent, "angles" ), + ValueForKey( mapent, "pitch" ), + ValueForKey( mapent, "_light" ), + ValueForKey( mapent, "_quadratic_attn" ), + ValueForKey( mapent, "_inner_cone" ), + ValueForKey( mapent, "_cone" ), + ValueForKey( mapent, "brightness" ), + ValueForKey( mapent, "distance" ), + ValueForKey( mapent, "spotlight_radius" ) ); + } + + if ( !Q_stricmp( pClassName, "light_environment" ) ) + { + CVmfImport::GetVmfImporter()->ImportLightEnvironmentCallback( + ValueForKey( mapent, "hammerid" ), + ValueForKey( mapent, "origin" ), + ValueForKey( mapent, "angles" ), + ValueForKey( mapent, "pitch" ), + ValueForKey( mapent, "_light" ), + ValueForKey( mapent, "_lightHDR" ), + ValueForKey( mapent, "_lightscaleHDR" ), + ValueForKey( mapent, "_ambient" ), + ValueForKey( mapent, "_ambientHDR" ), + ValueForKey( mapent, "_AmbientScaleHDR" ), + ValueForKey( mapent, "SunSpreadAngle" ) ); + } + + const char *pModel = ValueForKey( mapent, "model" ); + if ( pModel && Q_strlen( pModel ) ) + { + CVmfImport::GetVmfImporter()->ImportModelCallback( + pModel, + ValueForKey( mapent, "hammerid" ), + ValueForKey( mapent, "angles" ), + ValueForKey( mapent, "origin" ), + MDagPath() ); + } +#endif // VSVMFIO + + // If it's not in the world at this point, unmark CONTENTS_DETAIL from all sides... + if ( mapent != &entities[ 0 ] ) + { + RemoveContentsDetailFromEntity( mapent ); + } + + return(ChunkFile_Ok); + } + + return(eResult); +} + + +entity_t* EntityByName( char const *pTestName ) +{ + if( !pTestName ) + return 0; + + for( int i=0; i < g_MainMap->num_entities; i++ ) + { + entity_t *e = &g_MainMap->entities[i]; + + const char *pName = ValueForKey( e, "targetname" ); + if( stricmp( pName, pTestName ) == 0 ) + return e; + } + + return 0; +} + + +void CMapFile::ForceFuncAreaPortalWindowContents() +{ + // Now go through all areaportal entities and force CONTENTS_WINDOW + // on the brushes of the bmodels they point at. + char *targets[] = {"target", "BackgroundBModel"}; + int nTargets = sizeof(targets) / sizeof(targets[0]); + + for( int i=0; i < num_entities; i++ ) + { + entity_t *e = &entities[i]; + + const char *pClassName = ValueForKey( e, "classname" ); + + // Don't do this on "normal" func_areaportal entities. Those are tied to doors + // and should be opaque when closed. But areaportal windows (and any other + // distance-based areaportals) should be windows because they are normally open/transparent + if( !IsAreaPortal( pClassName ) || !Q_stricmp( pClassName, "func_areaportal" ) ) + continue; + +// const char *pTestEntName = ValueForKey( e, "targetname" ); + + for( int iTarget=0; iTarget < nTargets; iTarget++ ) + { + char const *pEntName = ValueForKey( e, targets[iTarget] ); + if( !pEntName[0] ) + continue; + + entity_t *pBrushEnt = EntityByName( pEntName ); + if( !pBrushEnt ) + continue; + + for( int iBrush=0; iBrush < pBrushEnt->numbrushes; iBrush++ ) + { + mapbrushes[pBrushEnt->firstbrush + iBrush].contents &= ~CONTENTS_SOLID; + mapbrushes[pBrushEnt->firstbrush + iBrush].contents |= CONTENTS_TRANSLUCENT | CONTENTS_WINDOW; + } + } + } +} + + +// ============ Instancing ============ + +// #define MERGE_INSTANCE_DEBUG_INFO 1 + +#define INSTANCE_VARIABLE_KEY "replace" + +static GameData GD; + +//----------------------------------------------------------------------------- +// Purpose: this function will read in a standard key / value file +// Input : pFilename - the absolute name of the file to read +// Output : returns the KeyValues of the file, NULL if the file could not be read. +//----------------------------------------------------------------------------- +static KeyValues *ReadKeyValuesFile( const char *pFilename ) +{ + // Read in the gameinfo.txt file and null-terminate it. + FILE *fp = fopen( pFilename, "rb" ); + if ( !fp ) + return NULL; + CUtlVector buf; + fseek( fp, 0, SEEK_END ); + buf.SetSize( ftell( fp ) + 1 ); + fseek( fp, 0, SEEK_SET ); + fread( buf.Base(), 1, buf.Count()-1, fp ); + fclose( fp ); + buf[buf.Count()-1] = 0; + + KeyValues *kv = new KeyValues( "" ); + if ( !kv->LoadFromBuffer( pFilename, buf.Base() ) ) + { + kv->deleteThis(); + return NULL; + } + + return kv; +} + + +//----------------------------------------------------------------------------- +// Purpose: this function will set a secondary lookup path for instances. +// Input : pszInstancePath - the secondary lookup path +//----------------------------------------------------------------------------- +void CMapFile::SetInstancePath( const char *pszInstancePath ) +{ + strcpy( m_InstancePath, pszInstancePath ); + V_strlower( m_InstancePath ); + V_FixSlashes( m_InstancePath ); +} + + +//----------------------------------------------------------------------------- +// Purpose: This function will attempt to find a full path given the base and relative names. +// Input : pszBaseFileName - the base file that referenced this instance +// pszInstanceFileName - the relative file name of this instance +// Output : Returns true if it was able to locate the file +// pszOutFileName - the full path to the file name if located +//----------------------------------------------------------------------------- +bool CMapFile::DeterminePath( const char *pszBaseFileName, const char *pszInstanceFileName, char *pszOutFileName ) +{ + char szInstanceFileNameFixed[ MAX_PATH ]; + const char *pszMapPath = "\\maps\\"; + + strcpy( szInstanceFileNameFixed, pszInstanceFileName ); + V_SetExtension( szInstanceFileNameFixed, ".vmf", sizeof( szInstanceFileNameFixed ) ); + V_FixSlashes( szInstanceFileNameFixed ); + + // first, try to find a relative location based upon the Base file name + strcpy( pszOutFileName, pszBaseFileName ); + V_StripFilename( pszOutFileName ); + + strcat( pszOutFileName, "\\" ); + strcat( pszOutFileName, szInstanceFileNameFixed ); + + if ( g_pFullFileSystem->FileExists( pszOutFileName ) ) + { + return true; + } + + // second, try to find the master 'maps' directory and make it relative from that + strcpy( pszOutFileName, pszBaseFileName ); + V_StripFilename( pszOutFileName ); + V_RemoveDotSlashes( pszOutFileName ); + V_FixDoubleSlashes( pszOutFileName ); + V_strlower( pszOutFileName ); + strcat( pszOutFileName, "\\" ); + + char *pos = strstr( pszOutFileName, pszMapPath ); + if ( pos ) + { + pos += strlen( pszMapPath ); + *pos = 0; + strcat( pszOutFileName, szInstanceFileNameFixed ); + + if ( g_pFullFileSystem->FileExists( pszOutFileName ) ) + { + return true; + } + } + + if ( m_InstancePath[ 0 ] != 0 ) + { + sprintf( szInstanceFileNameFixed, "%s%s", m_InstancePath, pszInstanceFileName ); + + if ( g_pFullFileSystem->FileExists( szInstanceFileNameFixed, "GAME" ) ) + { + char FullPath[ MAX_PATH ]; + g_pFullFileSystem->RelativePathToFullPath( szInstanceFileNameFixed, "GAME", FullPath, sizeof( FullPath ) ); + strcpy( pszOutFileName, FullPath ); + + return true; + } + } + + pszOutFileName[ 0 ] = 0; + + return false; +} + + +//----------------------------------------------------------------------------- +// Purpose: this function will check the main map for any func_instances. It will +// also attempt to load in the gamedata file for instancing remapping help. +// Input : none +// Output : none +//----------------------------------------------------------------------------- +void CMapFile::CheckForInstances( const char *pszFileName ) +{ + if ( this != g_MainMap ) + { // all sub-instances will be appended to the main map master list as they are read in + // so the main loop below will naturally get to the appended ones. + return; + } + + char GameInfoPath[ MAX_PATH ]; + + g_pFullFileSystem->RelativePathToFullPath( "gameinfo.txt", "MOD", GameInfoPath, sizeof( GameInfoPath ) ); + KeyValues *GameInfoKV = ReadKeyValuesFile( GameInfoPath ); + if ( !GameInfoKV ) + { + Msg( "Could not locate gameinfo.txt for Instance Remapping at %s\n", GameInfoPath ); + return; + } + + const char *InstancePath = GameInfoKV->GetString( "InstancePath", NULL ); + if ( InstancePath ) + { + CMapFile::SetInstancePath( InstancePath ); + } + + const char *GameDataFile = GameInfoKV->GetString( "GameData", NULL ); + if ( !GameDataFile ) + { + Msg( "Could not locate 'GameData' key in %s\n", GameInfoPath ); + return; + } + + char FDGPath[ MAX_PATH ]; + if ( !g_pFullFileSystem->RelativePathToFullPath( GameDataFile, "EXECUTABLE_PATH", FDGPath, sizeof( FDGPath ) ) ) + { + if ( !g_pFullFileSystem->RelativePathToFullPath( GameDataFile, "", FDGPath, sizeof( FDGPath ) ) ) + { + Msg( "Could not locate GameData file %s\n", GameDataFile ); + } + } + + GD.Load( FDGPath ); + + // this list will grow as instances are merged onto it. sub-instances are merged and + // automatically done in this processing. + for ( int i = 0; i < num_entities; i++ ) + { + char *pEntity = ValueForKey( &entities[ i ], "classname" ); + if ( !strcmp( pEntity, "func_instance" ) ) + { + char *pInstanceFile = ValueForKey( &entities[ i ], "file" ); + if ( pInstanceFile[ 0 ] ) + { + char InstancePath[ MAX_PATH ]; + bool bLoaded = false; + + if ( DeterminePath( pszFileName, pInstanceFile, InstancePath ) ) + { + if ( LoadMapFile( InstancePath ) ) + { + MergeInstance( &entities[ i ], g_LoadingMap ); + delete g_LoadingMap; + bLoaded = true; + } + } + + if ( bLoaded == false ) + { + Color red( 255, 0, 0, 255 ); + + ColorSpewMessage( SPEW_ERROR, &red, "Could not open instance file %s\n", pInstanceFile ); + } + } + + entities[ i ].numbrushes = 0; + entities[ i ].epairs = NULL; + } + } + + g_LoadingMap = this; +} + + +//----------------------------------------------------------------------------- +// Purpose: this function will do all of the necessary work to merge the instance +// into the main map. +// Input : pInstanceEntity - the entity of the func_instance +// Instance - the map file of the instance +// Output : none +//----------------------------------------------------------------------------- +void CMapFile::MergeInstance( entity_t *pInstanceEntity, CMapFile *Instance ) +{ + matrix3x4_t mat; + QAngle angles; + Vector OriginOffset = pInstanceEntity->origin; + + m_InstanceCount++; + + GetAnglesForKey( pInstanceEntity, "angles", angles ); + AngleMatrix( angles, OriginOffset, mat ); + +#ifdef MERGE_INSTANCE_DEBUG_INFO + Msg( "Instance Remapping: O:( %g, %g, %g ) A:( %g, %g, %g )\n", OriginOffset.x, OriginOffset.y, OriginOffset.z, angles.x, angles.y, angles.z ); +#endif // #ifdef MERGE_INSTANCE_DEBUG_INFO + MergePlanes( pInstanceEntity, Instance, OriginOffset, angles, mat ); + MergeBrushes( pInstanceEntity, Instance, OriginOffset, angles, mat ); + MergeBrushSides( pInstanceEntity, Instance, OriginOffset, angles, mat ); + MergeEntities( pInstanceEntity, Instance, OriginOffset, angles, mat ); + MergeOverlays( pInstanceEntity, Instance, OriginOffset, angles, mat ); +} + + +//----------------------------------------------------------------------------- +// Purpose: this function will merge in the map planes from the instance into +// the main map. +// Input : pInstanceEntity - the entity of the func_instance +// Instance - the map file of the instance +// InstanceOrigin - the translation of the instance +// InstanceAngle - the rotation of the instance +// InstanceMatrix - the translation / rotation matrix of the instance +// Output : none +//----------------------------------------------------------------------------- +void CMapFile::MergePlanes( entity_t *pInstanceEntity, CMapFile *Instance, Vector &InstanceOrigin, QAngle &InstanceAngle, matrix3x4_t &InstanceMatrix ) +{ + // Each pair of planes needs to be added to the main map + for ( int i = 0; i < Instance->nummapplanes; i += 2 ) + { + FindFloatPlane( Instance->mapplanes[i].normal, Instance->mapplanes[i].dist ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: this function will merge in the map brushes from the instance into +// the main map. +// Input : pInstanceEntity - the entity of the func_instance +// Instance - the map file of the instance +// InstanceOrigin - the translation of the instance +// InstanceAngle - the rotation of the instance +// InstanceMatrix - the translation / rotation matrix of the instance +// Output : none +//----------------------------------------------------------------------------- +void CMapFile::MergeBrushes( entity_t *pInstanceEntity, CMapFile *Instance, Vector &InstanceOrigin, QAngle &InstanceAngle, matrix3x4_t &InstanceMatrix ) +{ + int max_brush_id = 0; + + for( int i = 0; i < nummapbrushes; i++ ) + { + if ( mapbrushes[ i ].id > max_brush_id ) + { + max_brush_id = mapbrushes[ i ].id; + } + } + + for( int i = 0; i < Instance->nummapbrushes; i++ ) + { + mapbrushes[ nummapbrushes + i ] = Instance->mapbrushes[ i ]; + + mapbrush_t *brush = &mapbrushes[ nummapbrushes + i ]; + brush->entitynum += num_entities; + brush->brushnum += nummapbrushes; + + if ( i < Instance->entities[ 0 ].numbrushes || ( brush->contents & CONTENTS_LADDER ) != 0 ) + { // world spawn brushes as well as ladders we physically move + Vector minsIn = brush->mins; + Vector maxsIn = brush->maxs; + + TransformAABB( InstanceMatrix, minsIn, maxsIn, brush->mins, brush->maxs ); + } + else + { + } + brush->id += max_brush_id; + + int index = brush->original_sides - Instance->brushsides; + brush->original_sides = &brushsides[ nummapbrushsides + index ]; + } + + nummapbrushes += Instance->nummapbrushes; +} + + +//----------------------------------------------------------------------------- +// Purpose: this function will merge in the map sides from the instance into +// the main map. +// Input : pInstanceEntity - the entity of the func_instance +// Instance - the map file of the instance +// InstanceOrigin - the translation of the instance +// InstanceAngle - the rotation of the instance +// InstanceMatrix - the translation / rotation matrix of the instance +// Output : none +//----------------------------------------------------------------------------- +void CMapFile::MergeBrushSides( entity_t *pInstanceEntity, CMapFile *Instance, Vector &InstanceOrigin, QAngle &InstanceAngle, matrix3x4_t &InstanceMatrix ) +{ + int max_side_id = 0; + + for( int i = 0; i < nummapbrushsides; i++ ) + { + if ( brushsides[ i ].id > max_side_id ) + { + max_side_id = brushsides[ i ].id; + } + } + + for( int i = 0; i < Instance->nummapbrushsides; i++ ) + { + brushsides[ nummapbrushsides + i ] = Instance->brushsides[ i ]; + + side_t *side = &brushsides[ nummapbrushsides + i ]; + // The planes got merged & remapped. So you need to search for the output plane index on each side + // NOTE: You could optimize this by saving off an index map in MergePlanes + side->planenum = FindFloatPlane( Instance->mapplanes[side->planenum].normal, Instance->mapplanes[side->planenum].dist ); + side->id += max_side_id; + + // this could be pre-processed into a list for quicker checking + bool bNeedsTranslation = ( side->pMapDisp && side->pMapDisp->entitynum == 0 ); + if ( !bNeedsTranslation ) + { // check for sides that are part of the world spawn - those need translating + for( int j = 0; j < Instance->entities[ 0 ].numbrushes; j++ ) + { + int loc = Instance->mapbrushes[ j ].original_sides - Instance->brushsides; + + if ( i >= loc && i < ( loc + Instance->mapbrushes[ j ].numsides ) ) + { + bNeedsTranslation = true; + break; + } + } + } + if ( !bNeedsTranslation ) + { // sides for ladders are outside of the world spawn, but also need translating + for( int j = Instance->entities[ 0 ].numbrushes; j < Instance->nummapbrushes; j++ ) + { + int loc = Instance->mapbrushes[ j ].original_sides - Instance->brushsides; + + if ( i >= loc && i < ( loc + Instance->mapbrushes[ j ].numsides ) && ( Instance->mapbrushes[ j ].contents & CONTENTS_LADDER ) != 0 ) + { + bNeedsTranslation = true; + break; + } + } + } + if ( bNeedsTranslation ) + { // we only want to do the adjustment on world spawn brushes, not entity brushes + if ( side->winding ) + { + for( int point = 0; point < side->winding->numpoints; point++ ) + { + Vector inPoint = side->winding->p[ point ]; + VectorTransform( inPoint, InstanceMatrix, side->winding->p[ point ] ); + } + } + + int planenum = side->planenum; + cplane_t inPlane, outPlane; + inPlane.normal = mapplanes[ planenum ].normal; + inPlane.dist = mapplanes[ planenum ].dist; + + MatrixTransformPlane( InstanceMatrix, inPlane, outPlane ); + planenum = FindFloatPlane( outPlane.normal, outPlane.dist ); + side->planenum = planenum; + + brush_texture_t bt = Instance->side_brushtextures[ i ]; + + VectorRotate( Instance->side_brushtextures[ i ].UAxis, InstanceMatrix, bt.UAxis ); + VectorRotate( Instance->side_brushtextures[ i ].VAxis, InstanceMatrix, bt.VAxis ); + bt.shift[ 0 ] -= InstanceOrigin.Dot( bt.UAxis ) / bt.textureWorldUnitsPerTexel[ 0 ]; + bt.shift[ 1 ] -= InstanceOrigin.Dot( bt.VAxis ) / bt.textureWorldUnitsPerTexel[ 1 ]; + + if ( !onlyents ) + { + side->texinfo = TexinfoForBrushTexture ( &mapplanes[ side->planenum ], &bt, vec3_origin ); + } + } + + if ( side->pMapDisp ) + { + mapdispinfo_t *disp = side->pMapDisp; + + disp->brushSideID = side->id; + Vector inPoint = disp->startPosition; + VectorTransform( inPoint, InstanceMatrix, disp->startPosition ); + + disp->face.originalface = side; + disp->face.texinfo = side->texinfo; + disp->face.planenum = side->planenum; + disp->entitynum += num_entities; + + for( int point = 0; point < disp->face.w->numpoints; point++ ) + { + Vector inPoint = disp->face.w->p[ point ]; + VectorTransform( inPoint, InstanceMatrix, disp->face.w->p[ point ] ); + } + + } + } + + nummapbrushsides += Instance->nummapbrushsides; +} + + +//----------------------------------------------------------------------------- +// Purpose: this function will look for replace parameters in the function instance +// to see if there is anything in the epair that should be replaced. +// Input : pPair - the epair with the value +// pInstanceEntity - the func_instance that may ahve replace keywords +// Output : pPair - the value field may be updated +//----------------------------------------------------------------------------- +void CMapFile::ReplaceInstancePair( epair_t *pPair, entity_t *pInstanceEntity ) +{ + char Value[ MAX_KEYVALUE_LEN ], NewValue[ MAX_KEYVALUE_LEN ]; + bool Overwritten = false; + + strcpy( NewValue, pPair->value ); + for ( epair_t *epInstance = pInstanceEntity->epairs; epInstance != NULL; epInstance = epInstance->next ) + { + if ( strnicmp( epInstance->key, INSTANCE_VARIABLE_KEY, strlen( INSTANCE_VARIABLE_KEY ) ) == 0 ) + { + char InstanceVariable[ MAX_KEYVALUE_LEN ]; + + strcpy( InstanceVariable, epInstance->value ); + + char *ValuePos = strchr( InstanceVariable, ' ' ); + if ( !ValuePos ) + { + continue; + } + *ValuePos = 0; + ValuePos++; + + strcpy( Value, NewValue ); + if ( !V_StrSubst( Value, InstanceVariable, ValuePos, NewValue, sizeof( NewValue ), false ) ) + { + Overwritten = true; + break; + } + } + } + + if ( !Overwritten && strcmp( pPair->value, NewValue ) != 0 ) + { + free( pPair->value ); + pPair->value = copystring( NewValue ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: this function will merge in the entities from the instance into +// the main map. +// Input : pInstanceEntity - the entity of the func_instance +// Instance - the map file of the instance +// InstanceOrigin - the translation of the instance +// InstanceAngle - the rotation of the instance +// InstanceMatrix - the translation / rotation matrix of the instance +// Output : none +//----------------------------------------------------------------------------- +void CMapFile::MergeEntities( entity_t *pInstanceEntity, CMapFile *Instance, Vector &InstanceOrigin, QAngle &InstanceAngle, matrix3x4_t &InstanceMatrix ) +{ + int max_entity_id = 0; + char temp[ 2048 ]; + char NameFixup[ 128 ]; + entity_t *WorldspawnEnt = NULL; + GameData::TNameFixup FixupStyle; + + char *pTargetName = ValueForKey( pInstanceEntity, "targetname" ); + char *pName = ValueForKey( pInstanceEntity, "name" ); + if ( pTargetName[ 0 ] ) + { + sprintf( NameFixup, "%s", pTargetName ); + } + else if ( pName[ 0 ] ) + { + sprintf( NameFixup, "%s", pName ); + } + else + { + sprintf( NameFixup, "InstanceAuto%d", m_InstanceCount ); + } + + for( int i = 0; i < num_entities; i++ ) + { + char *pID = ValueForKey( &entities[ i ], "hammerid" ); + if ( pID[ 0 ] ) + { + int value = atoi( pID ); + if ( value > max_entity_id ) + { + max_entity_id = value; + } + } + } + + FixupStyle = ( GameData::TNameFixup )( IntForKey( pInstanceEntity, "fixup_style" ) ); + + for( int i = 0; i < Instance->num_entities; i++ ) + { + entities[ num_entities + i ] = Instance->entities[ i ]; + + entity_t *entity = &entities[ num_entities + i ]; + entity->firstbrush += ( nummapbrushes - Instance->nummapbrushes ); + + char *pID = ValueForKey( entity, "hammerid" ); + if ( pID[ 0 ] ) + { + int value = atoi( pID ); + value += max_entity_id; + sprintf( temp, "%d", value ); + + SetKeyValue( entity, "hammerid", temp ); + } + + char *pEntity = ValueForKey( entity, "classname" ); + if ( strcmpi( pEntity, "worldspawn" ) == 0 ) + { + WorldspawnEnt = entity; + } + else + { + Vector inOrigin = entity->origin; + VectorTransform( inOrigin, InstanceMatrix, entity->origin ); + + // search for variables coming from the func_instance to replace inside of the instance + // this is done before entity fixup, so fixup may occur on the replaced value. Not sure if this is a desired order of operation yet. + for ( epair_t *ep = entity->epairs; ep != NULL; ep = ep->next ) + { + ReplaceInstancePair( ep, pInstanceEntity ); + } + +#ifdef MERGE_INSTANCE_DEBUG_INFO + Msg( "Remapping class %s\n", pEntity ); +#endif // #ifdef MERGE_INSTANCE_DEBUG_INFO + GDclass *EntClass = GD.BeginInstanceRemap( pEntity, NameFixup, InstanceOrigin, InstanceAngle ); + if ( EntClass ) + { + for( int i = 0; i < EntClass->GetVariableCount(); i++ ) + { + GDinputvariable *EntVar = EntClass->GetVariableAt( i ); + char *pValue = ValueForKey( entity, ( char * )EntVar->GetName() ); + if ( GD.RemapKeyValue( EntVar->GetName(), pValue, temp, FixupStyle ) ) + { +#ifdef MERGE_INSTANCE_DEBUG_INFO + Msg( " %d. Remapped %s: from %s to %s\n", i, EntVar->GetName(), pValue, temp ); +#endif // #ifdef MERGE_INSTANCE_DEBUG_INFO + SetKeyValue( entity, EntVar->GetName(), temp ); + } + else + { +#ifdef MERGE_INSTANCE_DEBUG_INFO + Msg( " %d. Ignored %s: %s\n", i, EntVar->GetName(), pValue ); +#endif // #ifdef MERGE_INSTANCE_DEBUG_INFO + } + } + } + + if ( strcmpi( pEntity, "func_simpleladder" ) == 0 ) + { // hate having to do this, but the key values are so screwed up + AddLadderKeys( entity ); +/* Vector vInNormal, vOutNormal; + + vInNormal.x = FloatForKey( entity, "normal.x" ); + vInNormal.y = FloatForKey( entity, "normal.y" ); + vInNormal.z = FloatForKey( entity, "normal.z" ); + VectorRotate( vInNormal, InstanceMatrix, vOutNormal ); + + Q_snprintf( temp, sizeof( temp ), "%f", vOutNormal.x ); + SetKeyValue( entity, "normal.x", temp ); + + Q_snprintf( temp, sizeof( temp ), "%f", vOutNormal.y ); + SetKeyValue( entity, "normal.y", temp ); + + Q_snprintf( temp, sizeof( temp ), "%f", vOutNormal.z ); + SetKeyValue( entity, "normal.z", temp );*/ + } + } + +#ifdef MERGE_INSTANCE_DEBUG_INFO + Msg( "Instance Entity %d remapped to %d\n", i, num_entities + i ); + Msg( " FirstBrush: from %d to %d\n", Instance->entities[ i ].firstbrush, entity->firstbrush ); + Msg( " KV Pairs:\n" ); + for ( epair_t *ep = entity->epairs; ep->next != NULL; ep = ep->next ) + { + Msg( " %s %s\n", ep->key, ep->value ); + } +#endif // #ifdef MERGE_INSTANCE_DEBUG_INFO + } + + // search for variables coming from the func_instance to replace inside of the instance + // this is done before connection fix up, so fix up may occur on the replaced value. Not sure if this is a desired order of operation yet. + for( CConnectionPairs *Connection = Instance->m_ConnectionPairs; Connection; Connection = Connection->m_Next ) + { + ReplaceInstancePair( Connection->m_Pair, pInstanceEntity ); + } + + for( CConnectionPairs *Connection = Instance->m_ConnectionPairs; Connection; Connection = Connection->m_Next ) + { + char *newValue, *oldValue; + char origValue[ 4096 ]; + int extraLen = 0; + + oldValue = Connection->m_Pair->value; + strcpy( origValue, oldValue ); + char *pos = strchr( origValue, ',' ); + if ( pos ) + { // null terminate the first field + *pos = NULL; + extraLen = strlen( pos + 1) + 1; // for the comma we just null'd + } + + if ( GD.RemapNameField( origValue, temp, FixupStyle ) ) + { + newValue = new char [ strlen( temp ) + extraLen + 1 ]; + strcpy( newValue, temp ); + if ( pos ) + { + strcat( newValue, "," ); + strcat( newValue, pos + 1 ); + } + + Connection->m_Pair->value = newValue; + delete oldValue; + } + } + + num_entities += Instance->num_entities; + + MoveBrushesToWorldGeneral( WorldspawnEnt ); + WorldspawnEnt->numbrushes = 0; + WorldspawnEnt->epairs = NULL; +} + + +//----------------------------------------------------------------------------- +// Purpose: this function will translate overlays from the instance into +// the main map. +// Input : InstanceEntityNum - the entity number of the func_instance +// Instance - the map file of the instance +// InstanceOrigin - the translation of the instance +// InstanceAngle - the rotation of the instance +// InstanceMatrix - the translation / rotation matrix of the instance +// Output : none +//----------------------------------------------------------------------------- +void CMapFile::MergeOverlays( entity_t *pInstanceEntity, CMapFile *Instance, Vector &InstanceOrigin, QAngle &InstanceAngle, matrix3x4_t &InstanceMatrix ) +{ + for( int i = Instance->m_StartMapOverlays; i < g_aMapOverlays.Count(); i++ ) + { + Overlay_Translate( &g_aMapOverlays[ i ], InstanceOrigin, InstanceAngle, InstanceMatrix ); + } + for( int i = Instance->m_StartMapWaterOverlays; i < g_aMapWaterOverlays.Count(); i++ ) + { + Overlay_Translate( &g_aMapWaterOverlays[ i ], InstanceOrigin, InstanceAngle, InstanceMatrix ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Loads a VMF or MAP file. If the file has a .MAP extension, the MAP +// loader is used, otherwise the file is assumed to be in VMF format. +// Input : pszFileName - Full path of the map file to load. +//----------------------------------------------------------------------------- +bool LoadMapFile( const char *pszFileName ) +{ + bool bLoadingManifest = false; + CManifest *pMainManifest = NULL; + ChunkFileResult_t eResult; + + // + // Dummy this up for the texture handling. This can be removed when old .MAP file + // support is removed. + // + g_nMapFileVersion = 400; + + const char *pszExtension =V_GetFileExtension( pszFileName ); + if ( pszExtension && strcmpi( pszExtension, "vmm" ) == 0 ) + { + pMainManifest = new CManifest(); + if ( pMainManifest->LoadVMFManifest( pszFileName ) ) + { + eResult = ChunkFile_Ok; + pszFileName = pMainManifest->GetInstancePath(); + } + else + { + eResult = ChunkFile_Fail; + } + bLoadingManifest = true; + } + else + { + // + // Open the file. + // + CChunkFile File; + eResult = File.Open(pszFileName, ChunkFile_Read); + + // + // Read the file. + // + if (eResult == ChunkFile_Ok) + { + int index = g_Maps.AddToTail( new CMapFile() ); + g_LoadingMap = g_Maps[ index ]; + if ( g_MainMap == NULL ) + { + g_MainMap = g_LoadingMap; + } + + if ( g_MainMap == g_LoadingMap || verbose ) + { + Msg( "Loading %s\n", pszFileName ); + } + + + // reset the displacement info count + // nummapdispinfo = 0; + + // + // Set up handlers for the subchunks that we are interested in. + // + CChunkHandlerMap Handlers; + Handlers.AddHandler("world", (ChunkHandler_t)LoadEntityCallback, 0); + Handlers.AddHandler("entity", (ChunkHandler_t)LoadEntityCallback, 0); + + File.PushHandlers(&Handlers); + + // + // Read the sub-chunks. We ignore keys in the root of the file. + // + while (eResult == ChunkFile_Ok) + { + eResult = File.ReadChunk(); + } + + File.PopHandlers(); + } + else + { + Error("Error opening %s: %s.\n", pszFileName, File.GetErrorText(eResult)); + } + } + + if ((eResult == ChunkFile_Ok) || (eResult == ChunkFile_EOF)) + { + // Update the overlay/side list(s). + Overlay_UpdateSideLists( g_LoadingMap->m_StartMapOverlays ); + OverlayTransition_UpdateSideLists( g_LoadingMap->m_StartMapWaterOverlays ); + + g_LoadingMap->CheckForInstances( pszFileName ); + + if ( pMainManifest ) + { + pMainManifest->CordonWorld(); + } + + ClearBounds (g_LoadingMap->map_mins, g_LoadingMap->map_maxs); + for (int i=0 ; ientities[0].numbrushes ; i++) + { + // HLTOOLS: Raise map limits + if (g_LoadingMap->mapbrushes[i].mins[0] > MAX_COORD_INTEGER) + { + continue; // no valid points + } + + AddPointToBounds (g_LoadingMap->mapbrushes[i].mins, g_LoadingMap->map_mins, g_LoadingMap->map_maxs); + AddPointToBounds (g_LoadingMap->mapbrushes[i].maxs, g_LoadingMap->map_mins, g_LoadingMap->map_maxs); + } + + qprintf ("%5i brushes\n", g_LoadingMap->nummapbrushes); + qprintf ("%5i clipbrushes\n", g_LoadingMap->c_clipbrushes); + qprintf ("%5i total sides\n", g_LoadingMap->nummapbrushsides); + qprintf ("%5i boxbevels\n", g_LoadingMap->c_boxbevels); + qprintf ("%5i edgebevels\n", g_LoadingMap->c_edgebevels); + qprintf ("%5i entities\n", g_LoadingMap->num_entities); + qprintf ("%5i planes\n", g_LoadingMap->nummapplanes); + qprintf ("%5i areaportals\n", g_LoadingMap->c_areaportals); + qprintf ("size: %5.0f,%5.0f,%5.0f to %5.0f,%5.0f,%5.0f\n", g_LoadingMap->map_mins[0],g_LoadingMap->map_mins[1],g_LoadingMap->map_mins[2], + g_LoadingMap->map_maxs[0],g_LoadingMap->map_maxs[1],g_LoadingMap->map_maxs[2]); + + //TestExpandBrushes(); + + // Clear the error reporting + g_MapError.ClearState(); + } + + if ( g_MainMap == g_LoadingMap ) + { + num_entities = g_MainMap->num_entities; + memcpy( entities, g_MainMap->entities, sizeof( g_MainMap->entities ) ); + } + g_LoadingMap->ForceFuncAreaPortalWindowContents(); + + return ( ( eResult == ChunkFile_Ok ) || ( eResult == ChunkFile_EOF ) ); +} + +ChunkFileResult_t LoadSideCallback(CChunkFile *pFile, LoadSide_t *pSideInfo) +{ + return g_LoadingMap->LoadSideCallback( pFile, pSideInfo ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : pFile - +// pParent - +// Output : ChunkFileResult_t +//----------------------------------------------------------------------------- +ChunkFileResult_t CMapFile::LoadSideCallback(CChunkFile *pFile, LoadSide_t *pSideInfo) +{ + if (nummapbrushsides == MAX_MAP_BRUSHSIDES) + { + g_MapError.ReportError ("MAX_MAP_BRUSHSIDES"); + } + + pSideInfo->pSide = &brushsides[nummapbrushsides]; + + side_t *side = pSideInfo->pSide; + mapbrush_t *b = pSideInfo->pBrush; + g_MapError.BrushSide( pSideInfo->nSideIndex++ ); + + // initialize the displacement info + pSideInfo->pSide->pMapDisp = NULL; + + // + // Set up handlers for the subchunks that we are interested in. + // + CChunkHandlerMap Handlers; + Handlers.AddHandler( "dispinfo", ( ChunkHandler_t )LoadDispInfoCallback, &side->pMapDisp ); + + // + // Read the side chunk. + // + pFile->PushHandlers(&Handlers); + ChunkFileResult_t eResult = pFile->ReadChunk((KeyHandler_t)LoadSideKeyCallback, pSideInfo); + pFile->PopHandlers(); + + if (eResult == ChunkFile_Ok) + { + side->contents |= pSideInfo->nBaseContents; + side->surf |= pSideInfo->nBaseFlags; + pSideInfo->td.flags |= pSideInfo->nBaseFlags; + + if (side->contents & (CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP) ) + { + side->contents |= CONTENTS_DETAIL; + } + + if (fulldetail ) + { + side->contents &= ~CONTENTS_DETAIL; + } + + if (!(side->contents & (ALL_VISIBLE_CONTENTS | CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP) ) ) + { + side->contents |= CONTENTS_SOLID; + } + + // hints and skips are never detail, and have no content + if (side->surf & (SURF_HINT|SURF_SKIP) ) + { + side->contents = 0; + } + + // + // find the plane number + // + int planenum = PlaneFromPoints(pSideInfo->planepts[0], pSideInfo->planepts[1], pSideInfo->planepts[2]); + if (planenum != -1) + { + // + // See if the plane has been used already. + // + int k; + for ( k = 0; k < b->numsides; k++) + { + side_t *s2 = b->original_sides + k; + if (s2->planenum == planenum) + { + g_MapError.ReportWarning("duplicate plane"); + break; + } + if ( s2->planenum == (planenum^1) ) + { + g_MapError.ReportWarning("mirrored plane"); + break; + } + } + + // + // If the plane hasn't been used already, keep this side. + // + if (k == b->numsides) + { + side = b->original_sides + b->numsides; + side->planenum = planenum; + if ( !onlyents ) + { + side->texinfo = TexinfoForBrushTexture (&mapplanes[planenum], &pSideInfo->td, vec3_origin); + } + + // save the td off in case there is an origin brush and we + // have to recalculate the texinfo + if (nummapbrushsides == MAX_MAP_BRUSHSIDES) + g_MapError.ReportError ("MAX_MAP_BRUSHSIDES"); + side_brushtextures[nummapbrushsides] = pSideInfo->td; + nummapbrushsides++; + b->numsides++; + +#ifdef VSVMFIO + // Tell Maya We Have Another Side + if ( CVmfImport::GetVmfImporter() ) + { + CVmfImport::GetVmfImporter()->AddSideCallback( + b, side, pSideInfo->td, + pSideInfo->planepts[ 0 ], pSideInfo->planepts[ 1 ], pSideInfo->planepts[ 2 ] ); + } +#endif // VSVMFIO + + } + } + else + { + g_MapError.ReportWarning("plane with no normal"); + } + } + + return(eResult); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : szKey - +// szValue - +// pSideInfo - +// Output : +//----------------------------------------------------------------------------- +ChunkFileResult_t LoadSideKeyCallback(const char *szKey, const char *szValue, LoadSide_t *pSideInfo) +{ + if (!stricmp(szKey, "plane")) + { + int nRead = sscanf(szValue, "(%f %f %f) (%f %f %f) (%f %f %f)", + &pSideInfo->planepts[0][0], &pSideInfo->planepts[0][1], &pSideInfo->planepts[0][2], + &pSideInfo->planepts[1][0], &pSideInfo->planepts[1][1], &pSideInfo->planepts[1][2], + &pSideInfo->planepts[2][0], &pSideInfo->planepts[2][1], &pSideInfo->planepts[2][2]); + + if (nRead != 9) + { + g_MapError.ReportError("parsing plane definition"); + } + } + else if (!stricmp(szKey, "material")) + { + // Get the material name. + if( g_ReplaceMaterials ) + { + szValue = ReplaceMaterialName( szValue ); + } + + strcpy(pSideInfo->td.name, szValue); + g_MapError.TextureState(szValue); + + // Find default flags and values for this material. + int mt = FindMiptex(pSideInfo->td.name); + pSideInfo->td.flags = textureref[mt].flags; + pSideInfo->td.lightmapWorldUnitsPerLuxel = textureref[mt].lightmapWorldUnitsPerLuxel; + + pSideInfo->pSide->contents = textureref[mt].contents; + pSideInfo->pSide->surf = pSideInfo->td.flags; + } + else if (!stricmp(szKey, "uaxis")) + { + int nRead = sscanf(szValue, "[%f %f %f %f] %f", &pSideInfo->td.UAxis[0], &pSideInfo->td.UAxis[1], &pSideInfo->td.UAxis[2], &pSideInfo->td.shift[0], &pSideInfo->td.textureWorldUnitsPerTexel[0]); + if (nRead != 5) + { + g_MapError.ReportError("parsing U axis definition"); + } + } + else if (!stricmp(szKey, "vaxis")) + { + int nRead = sscanf(szValue, "[%f %f %f %f] %f", &pSideInfo->td.VAxis[0], &pSideInfo->td.VAxis[1], &pSideInfo->td.VAxis[2], &pSideInfo->td.shift[1], &pSideInfo->td.textureWorldUnitsPerTexel[1]); + if (nRead != 5) + { + g_MapError.ReportError("parsing V axis definition"); + } + } + else if (!stricmp(szKey, "lightmapscale")) + { + pSideInfo->td.lightmapWorldUnitsPerLuxel = atoi(szValue); + if (pSideInfo->td.lightmapWorldUnitsPerLuxel == 0.0f) + { + g_MapError.ReportWarning("luxel size of 0"); + pSideInfo->td.lightmapWorldUnitsPerLuxel = g_defaultLuxelSize; + } + pSideInfo->td.lightmapWorldUnitsPerLuxel *= g_luxelScale; + if (pSideInfo->td.lightmapWorldUnitsPerLuxel < g_minLuxelScale) + { + pSideInfo->td.lightmapWorldUnitsPerLuxel = g_minLuxelScale; + } + } + else if (!stricmp(szKey, "contents")) + { + pSideInfo->pSide->contents |= atoi(szValue); + } + else if (!stricmp(szKey, "flags")) + { + pSideInfo->td.flags |= atoi(szValue); + pSideInfo->pSide->surf = pSideInfo->td.flags; + } + else if (!stricmp(szKey, "id")) + { + pSideInfo->pSide->id = atoi( szValue ); + } + else if (!stricmp(szKey, "smoothing_groups")) + { + pSideInfo->pSide->smoothingGroups = atoi( szValue ); + } + + return(ChunkFile_Ok); +} + + +//----------------------------------------------------------------------------- +// Purpose: Reads the connections chunk of the entity. +// Input : pFile - Chunk file to load from. +// pLoadEntity - Structure to receive loaded entity information. +// Output : ChunkFileResult_t +//----------------------------------------------------------------------------- +ChunkFileResult_t LoadConnectionsCallback(CChunkFile *pFile, LoadEntity_t *pLoadEntity) +{ + return(pFile->ReadChunk((KeyHandler_t)LoadConnectionsKeyCallback, pLoadEntity)); +} + + +//----------------------------------------------------------------------------- +// Purpose: Parses a key/value pair from the entity connections chunk. +// Input : szKey - Key indicating the name of the entity output. +// szValue - Comma delimited fields in the following format: +// ,,,, +// pLoadEntity - Structure to receive loaded entity information. +// Output : ChunkFileResult_t +//----------------------------------------------------------------------------- +ChunkFileResult_t LoadConnectionsKeyCallback(const char *szKey, const char *szValue, LoadEntity_t *pLoadEntity) +{ + return g_LoadingMap->LoadConnectionsKeyCallback( szKey, szValue, pLoadEntity ); +} + +ChunkFileResult_t CMapFile::LoadConnectionsKeyCallback(const char *szKey, const char *szValue, LoadEntity_t *pLoadEntity) +{ + // + // Create new input and fill it out. + // + epair_t *pOutput = new epair_t; + + pOutput->key = new char [strlen(szKey) + 1]; + pOutput->value = new char [strlen(szValue) + 1]; + + strcpy(pOutput->key, szKey); + strcpy(pOutput->value, szValue); + + m_ConnectionPairs = new CConnectionPairs( pOutput, m_ConnectionPairs ); + + // + // Append it to the end of epairs list. + // + pOutput->next = NULL; + + if (!pLoadEntity->pEntity->epairs) + { + pLoadEntity->pEntity->epairs = pOutput; + } + else + { + epair_t *ep; + for ( ep = pLoadEntity->pEntity->epairs; ep->next != NULL; ep = ep->next ) + { + } + ep->next = pOutput; + } + + return(ChunkFile_Ok); +} + + +ChunkFileResult_t LoadSolidCallback(CChunkFile *pFile, LoadEntity_t *pLoadEntity) +{ + return g_LoadingMap->LoadSolidCallback( pFile, pLoadEntity ); +}; + +//----------------------------------------------------------------------------- +// Purpose: +// Input : pFile - +// pParent - +// Output : ChunkFileResult_t +//----------------------------------------------------------------------------- +ChunkFileResult_t CMapFile::LoadSolidCallback(CChunkFile *pFile, LoadEntity_t *pLoadEntity) +{ + if (nummapbrushes == MAX_MAP_BRUSHES) + { + g_MapError.ReportError ("nummapbrushes == MAX_MAP_BRUSHES"); + } + + mapbrush_t *b = &mapbrushes[nummapbrushes]; + b->original_sides = &brushsides[nummapbrushsides]; + b->entitynum = num_entities-1; + b->brushnum = nummapbrushes - pLoadEntity->pEntity->firstbrush; + + LoadSide_t SideInfo; + SideInfo.pBrush = b; + SideInfo.nSideIndex = 0; + SideInfo.nBaseContents = pLoadEntity->nBaseContents; + SideInfo.nBaseFlags = pLoadEntity->nBaseFlags; + + // + // Set up handlers for the subchunks that we are interested in. + // + CChunkHandlerMap Handlers; + Handlers.AddHandler("side", (ChunkHandler_t)::LoadSideCallback, &SideInfo); + + // + // Read the solid chunk. + // + pFile->PushHandlers(&Handlers); + ChunkFileResult_t eResult = pFile->ReadChunk((KeyHandler_t)LoadSolidKeyCallback, b); + pFile->PopHandlers(); + + if (eResult == ChunkFile_Ok) + { + // get the content for the entire brush + b->contents = BrushContents (b); + + // allow detail brushes to be removed + if (nodetail && (b->contents & CONTENTS_DETAIL) && !HasDispInfo( b ) ) + { + b->numsides = 0; + return(ChunkFile_Ok); + } + + // allow water brushes to be removed + if (nowater && (b->contents & MASK_WATER) ) + { + b->numsides = 0; + return(ChunkFile_Ok); + } + + // create windings for sides and bounds for brush + MakeBrushWindings (b); + + // + // brushes that will not be visible at all will never be + // used as bsp splitters + // + // only do this on the world entity + // + if ( b->entitynum == 0 ) + { + if (b->contents & (CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP) ) + { + if ( g_ClipTexinfo < 0 ) + { + g_ClipTexinfo = b->original_sides[0].texinfo; + } + c_clipbrushes++; + for (int i=0 ; inumsides ; i++) + { + b->original_sides[i].texinfo = TEXINFO_NODE; + } + } + } + + // + // origin brushes are removed, but they set + // the rotation origin for the rest of the brushes + // in the entity. After the entire entity is parsed, + // the planenums and texinfos will be adjusted for + // the origin brush + // + if (b->contents & CONTENTS_ORIGIN) + { + char string[32]; + Vector origin; + + if (num_entities == 1) + { + Error("Brush %i: origin brushes not allowed in world", b->id); + } + + VectorAdd (b->mins, b->maxs, origin); + VectorScale (origin, 0.5, origin); + + sprintf (string, "%i %i %i", (int)origin[0], (int)origin[1], (int)origin[2]); + SetKeyValue (&entities[b->entitynum], "origin", string); + + VectorCopy (origin, entities[b->entitynum].origin); + + // don't keep this brush + b->numsides = 0; + + return(ChunkFile_Ok); + } + +#ifdef VSVMFIO + if ( CVmfImport::GetVmfImporter() ) + { + CVmfImport::GetVmfImporter()->MapBrushToMayaCallback( b ); + } +#endif // VSVMFIO + + // + // find a map brushes with displacement surfaces and remove them from the "world" + // + if( HasDispInfo( b ) ) + { + // add the base face data to the displacement surface + DispGetFaceInfo( b ); + + // don't keep this brush + b->numsides = 0; + + return( ChunkFile_Ok ); + } + + AddBrushBevels (b); + + nummapbrushes++; + pLoadEntity->pEntity->numbrushes++; + } + else + { + return eResult; + } + + return(ChunkFile_Ok); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : pFile - +// parent - +// Output : ChunkFileResult_t +//----------------------------------------------------------------------------- +ChunkFileResult_t LoadSolidKeyCallback(const char *szKey, const char *szValue, mapbrush_t *pLoadBrush) +{ + if (!stricmp(szKey, "id")) + { + pLoadBrush->id = atoi(szValue); + g_MapError.BrushState(pLoadBrush->id); + } + + return ChunkFile_Ok; +} + + +/* +================ +TestExpandBrushes + +Expands all the brush planes and saves a new map out +================ +*/ +void CMapFile::TestExpandBrushes (void) +{ + FILE *f; + side_t *s; + int i, j, bn; + winding_t *w; + char *name = "expanded.map"; + mapbrush_t *brush; + vec_t dist; + + Msg ("writing %s\n", name); + f = fopen (name, "wb"); + if (!f) + Error ("Can't write %s\b", name); + + fprintf (f, "{\n\"classname\" \"worldspawn\"\n"); + fprintf( f, "\"mapversion\" \"220\"\n\"sounds\" \"1\"\n\"MaxRange\" \"4096\"\n\"mapversion\" \"220\"\n\"wad\" \"vert.wad;dev.wad;generic.wad;spire.wad;urb.wad;cit.wad;water.wad\"\n" ); + + + for (bn=0 ; bnnumsides ; i++) + { + s = brush->original_sides + i; + dist = mapplanes[s->planenum].dist; + for (j=0 ; j<3 ; j++) + dist += fabs( 16 * mapplanes[s->planenum].normal[j] ); + + w = BaseWindingForPlane (mapplanes[s->planenum].normal, dist); + + fprintf (f,"( %i %i %i ) ", (int)w->p[0][0], (int)w->p[0][1], (int)w->p[0][2]); + fprintf (f,"( %i %i %i ) ", (int)w->p[1][0], (int)w->p[1][1], (int)w->p[1][2]); + fprintf (f,"( %i %i %i ) ", (int)w->p[2][0], (int)w->p[2][1], (int)w->p[2][2]); + + fprintf (f, "%s [ 0 0 1 -512 ] [ 0 -1 0 -256 ] 0 1 1 \n", + TexDataStringTable_GetString( GetTexData( texinfo[s->texinfo].texdata )->nameStringTableID ) ); + + FreeWinding (w); + } + fprintf (f, "}\n"); + } + fprintf (f, "}\n"); + + fclose (f); + + Error ("can't proceed after expanding brushes"); +} + + +//----------------------------------------------------------------------------- +// Purpose: load in the displacement info "chunk" from the .map file into the +// vbsp map displacement info data structure +// Output: return the pointer to the displacement map +//----------------------------------------------------------------------------- +mapdispinfo_t *ParseDispInfoChunk( void ) +{ + int i, j; + int vertCount; + mapdispinfo_t *pMapDispInfo; + + // + // check to see if we exceeded the maximum displacement info list size + // + if( nummapdispinfo > MAX_MAP_DISPINFO ) + g_MapError.ReportError( "ParseDispInfoChunk: nummapdispinfo > MAX_MAP_DISPINFO"); + + // get a pointer to the next available displacement info slot + pMapDispInfo = &mapdispinfo[nummapdispinfo]; + nummapdispinfo++; + + // + // get the chunk opener - "{" + // + GetToken( false ); + if( strcmp( token, "{" ) ) + g_MapError.ReportError( "ParseDispInfoChunk: Illegal Chunk! - {" ); + + // + // + // get the displacement info attribs + // + // + + // power + GetToken( true ); + pMapDispInfo->power = atoi( token ); + + // u and v mapping axes + for( i = 0; i < 2; i++ ) + { + GetToken( false ); + if( strcmp( token, "[" ) ) + g_MapError.ReportError( "ParseDispInfoChunk: Illegal Chunk! - [" ); + + for( j = 0; j < 3; j++ ) + { + GetToken( false ); + + if( i == 0 ) + { + pMapDispInfo->uAxis[j] = atof( token ); + } + else + { + pMapDispInfo->vAxis[j] = atof( token ); + } + } + + GetToken( false ); + if( strcmp( token, "]" ) ) + g_MapError.ReportError( "ParseDispInfoChunk: Illegal Chunk! - ]" ); + } + + // max displacement value + if( g_nMapFileVersion < 350 ) + { + GetToken( false ); + pMapDispInfo->maxDispDist = atof( token ); + } + + // minimum tesselation value + GetToken( false ); + pMapDispInfo->minTess = atoi( token ); + + // light smoothing angle + GetToken( false ); + pMapDispInfo->smoothingAngle = atof( token ); + + // + // get the displacement info displacement normals + // + GetToken( true ); + pMapDispInfo->vectorDisps[0][0] = atof( token ); + GetToken( false ); + pMapDispInfo->vectorDisps[0][1] = atof( token ); + GetToken( false ); + pMapDispInfo->vectorDisps[0][2] = atof( token ); + + vertCount = ( ( ( 1 << pMapDispInfo->power ) + 1 ) * ( ( 1 << pMapDispInfo->power ) + 1 ) ); + for( i = 1; i < vertCount; i++ ) + { + GetToken( false ); + pMapDispInfo->vectorDisps[i][0] = atof( token ); + GetToken( false ); + pMapDispInfo->vectorDisps[i][1] = atof( token ); + GetToken( false ); + pMapDispInfo->vectorDisps[i][2] = atof( token ); + } + + // + // get the displacement info displacement values + // + GetToken( true ); + pMapDispInfo->dispDists[0] = atof( token ); + + for( i = 1; i < vertCount; i++ ) + { + GetToken( false ); + pMapDispInfo->dispDists[i] = atof( token ); + } + + // + // get the chunk closer - "}" + // + GetToken( true ); + if( strcmp( token, "}" ) ) + g_MapError.ReportError( "ParseDispInfoChunk: Illegal Chunk! - }" ); + + // return the index of the displacement info slot + return pMapDispInfo; +} + + diff --git a/mp/src/utils/vbsp/map.h b/mp/src/utils/vbsp/map.h new file mode 100644 index 00000000..44b1d934 --- /dev/null +++ b/mp/src/utils/vbsp/map.h @@ -0,0 +1,18 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef MAP_H +#define MAP_H +#ifdef _WIN32 +#pragma once +#endif + + +// All the brush sides referenced by info_no_dynamic_shadow entities. +extern CUtlVector g_NoDynamicShadowSides; + + +#endif // MAP_H diff --git a/mp/src/utils/vbsp/materialpatch.cpp b/mp/src/utils/vbsp/materialpatch.cpp new file mode 100644 index 00000000..e05b979d --- /dev/null +++ b/mp/src/utils/vbsp/materialpatch.cpp @@ -0,0 +1,440 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +#include "vbsp.h" +#include "UtlBuffer.h" +#include "utlsymbol.h" +#include "utlrbtree.h" +#include "KeyValues.h" +#include "bsplib.h" +#include "materialpatch.h" +#include "tier1/strtools.h" + +// case insensitive +static CUtlSymbolTable s_SymbolTable( 0, 32, true ); + +struct NameTranslationLookup_t +{ + CUtlSymbol m_OriginalFileName; + CUtlSymbol m_PatchFileName; +}; + +static bool NameTranslationLessFunc( NameTranslationLookup_t const& src1, + NameTranslationLookup_t const& src2 ) +{ + return src1.m_PatchFileName < src2.m_PatchFileName; +} + +CUtlRBTree s_MapPatchedMatToOriginalMat( 0, 256, NameTranslationLessFunc ); + +void AddNewTranslation( const char *pOriginalMaterialName, const char *pNewMaterialName ) +{ + NameTranslationLookup_t newEntry; + + newEntry.m_OriginalFileName = s_SymbolTable.AddString( pOriginalMaterialName ); + newEntry.m_PatchFileName = s_SymbolTable.AddString( pNewMaterialName ); + + s_MapPatchedMatToOriginalMat.Insert( newEntry ); +} + +const char *GetOriginalMaterialNameForPatchedMaterial( const char *pPatchMaterialName ) +{ + const char *pRetName = NULL; + int id; + NameTranslationLookup_t lookup; + lookup.m_PatchFileName = s_SymbolTable.AddString( pPatchMaterialName ); + do + { + id = s_MapPatchedMatToOriginalMat.Find( lookup ); + if( id >= 0 ) + { + NameTranslationLookup_t &found = s_MapPatchedMatToOriginalMat[id]; + lookup.m_PatchFileName = found.m_OriginalFileName; + pRetName = s_SymbolTable.String( found.m_OriginalFileName ); + } + } while( id >= 0 ); + if( !pRetName ) + { + // This isn't a patched material, so just return the original name. + return pPatchMaterialName; + } + return pRetName; +} + + +void CreateMaterialPatchRecursive( KeyValues *pOriginalKeyValues, KeyValues *pPatchKeyValues, int nKeys, const MaterialPatchInfo_t *pInfo ) +{ + int i; + for( i = 0; i < nKeys; i++ ) + { + const char *pVal = pOriginalKeyValues->GetString( pInfo[i].m_pKey, NULL ); + if( !pVal ) + continue; + if( pInfo[i].m_pRequiredOriginalValue && Q_stricmp( pVal, pInfo[i].m_pRequiredOriginalValue ) != 0 ) + continue; + pPatchKeyValues->SetString( pInfo[i].m_pKey, pInfo[i].m_pValue ); + } + KeyValues *pScan; + for( pScan = pOriginalKeyValues->GetFirstTrueSubKey(); pScan; pScan = pScan->GetNextTrueSubKey() ) + { + CreateMaterialPatchRecursive( pScan, pPatchKeyValues->FindKey( pScan->GetName(), true ), nKeys, pInfo ); + } +} + +//----------------------------------------------------------------------------- +// A version which allows you to patch multiple key values +//----------------------------------------------------------------------------- +void CreateMaterialPatch( const char *pOriginalMaterialName, const char *pNewMaterialName, + int nKeys, const MaterialPatchInfo_t *pInfo, MaterialPatchType_t nPatchType ) +{ + char pOldVMTFile[ 512 ]; + char pNewVMTFile[ 512 ]; + + AddNewTranslation( pOriginalMaterialName, pNewMaterialName ); + + Q_snprintf( pOldVMTFile, 512, "materials/%s.vmt", pOriginalMaterialName ); + Q_snprintf( pNewVMTFile, 512, "materials/%s.vmt", pNewMaterialName ); + +// printf( "Creating material patch file %s which points at %s\n", newVMTFile, oldVMTFile ); + + KeyValues *kv = new KeyValues( "patch" ); + if ( !kv ) + { + Error( "Couldn't allocate KeyValues for %s!!!", pNewMaterialName ); + } + + kv->SetString( "include", pOldVMTFile ); + + const char *pSectionName = (nPatchType == PATCH_INSERT) ? "insert" : "replace"; + KeyValues *section = kv->FindKey( pSectionName, true ); + + if( nPatchType == PATCH_REPLACE ) + { + char name[512]; + Q_snprintf( name, 512, "materials/%s.vmt", GetOriginalMaterialNameForPatchedMaterial( pOriginalMaterialName ) ); + KeyValues *origkv = new KeyValues( "blah" ); + + if ( !origkv->LoadFromFile( g_pFileSystem, name ) ) + { + origkv->deleteThis(); + Assert( 0 ); + return; + } + + CreateMaterialPatchRecursive( origkv, section, nKeys, pInfo ); + origkv->deleteThis(); + } + else + { + for ( int i = 0; i < nKeys; ++i ) + { + section->SetString( pInfo[i].m_pKey, pInfo[i].m_pValue ); + } + } + + // Write patched .vmt into a memory buffer + CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER ); + kv->RecursiveSaveToFile( buf, 0 ); + + // Add to pak file for this .bsp + AddBufferToPak( GetPakFile(), pNewVMTFile, (void*)buf.Base(), buf.TellPut(), true ); + + // Cleanup + kv->deleteThis(); +} + + +//----------------------------------------------------------------------------- +// Patches a single keyvalue in a material +//----------------------------------------------------------------------------- +void CreateMaterialPatch( const char *pOriginalMaterialName, const char *pNewMaterialName, + const char *pNewKey, const char *pNewValue, MaterialPatchType_t nPatchType ) +{ + MaterialPatchInfo_t info; + info.m_pKey = pNewKey; + info.m_pValue = pNewValue; + CreateMaterialPatch( pOriginalMaterialName, pNewMaterialName, 1, &info, nPatchType ); +} + + +//----------------------------------------------------------------------------- +// Scan material + all subsections for key +//----------------------------------------------------------------------------- +static bool DoesMaterialHaveKey( KeyValues *pKeyValues, const char *pKeyName ) +{ + const char *pVal; + pVal = pKeyValues->GetString( pKeyName, NULL ); + if ( pVal != NULL ) + return true; + + for( KeyValues *pSubKey = pKeyValues->GetFirstTrueSubKey(); pSubKey; pSubKey = pSubKey->GetNextTrueSubKey() ) + { + if ( DoesMaterialHaveKey( pSubKey, pKeyName) ) + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Scan material + all subsections for key/value pair +//----------------------------------------------------------------------------- +static bool DoesMaterialHaveKeyValuePair( KeyValues *pKeyValues, const char *pKeyName, const char *pSearchValue ) +{ + const char *pVal; + pVal = pKeyValues->GetString( pKeyName, NULL ); + if ( pVal != NULL && ( Q_stricmp( pSearchValue, pVal ) == 0 ) ) + return true; + + for( KeyValues *pSubKey = pKeyValues->GetFirstTrueSubKey(); pSubKey; pSubKey = pSubKey->GetNextTrueSubKey() ) + { + if ( DoesMaterialHaveKeyValuePair( pSubKey, pKeyName, pSearchValue ) ) + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Scan material + all subsections for key +//----------------------------------------------------------------------------- +bool DoesMaterialHaveKey( const char *pMaterialName, const char *pKeyName ) +{ + char name[512]; + Q_snprintf( name, 512, "materials/%s.vmt", GetOriginalMaterialNameForPatchedMaterial( pMaterialName ) ); + KeyValues *kv = new KeyValues( "blah" ); + + if ( !kv->LoadFromFile( g_pFileSystem, name ) ) + { + kv->deleteThis(); + return NULL; + } + + bool retVal = DoesMaterialHaveKey( kv, pKeyName ); + + kv->deleteThis(); + return retVal; +} + +//----------------------------------------------------------------------------- +// Scan material + all subsections for key/value pair +//----------------------------------------------------------------------------- +bool DoesMaterialHaveKeyValuePair( const char *pMaterialName, const char *pKeyName, const char *pSearchValue ) +{ + char name[512]; + Q_snprintf( name, 512, "materials/%s.vmt", GetOriginalMaterialNameForPatchedMaterial( pMaterialName ) ); + KeyValues *kv = new KeyValues( "blah" ); + + if ( !kv->LoadFromFile( g_pFileSystem, name ) ) + { + kv->deleteThis(); + return NULL; + } + + bool retVal = DoesMaterialHaveKeyValuePair( kv, pKeyName, pSearchValue ); + + kv->deleteThis(); + return retVal; +} + +//----------------------------------------------------------------------------- +// Gets a material value from a material. Ignores all patches +//----------------------------------------------------------------------------- +bool GetValueFromMaterial( const char *pMaterialName, const char *pKey, char *pValue, int len ) +{ + char name[512]; + Q_snprintf( name, 512, "materials/%s.vmt", GetOriginalMaterialNameForPatchedMaterial( pMaterialName ) ); + KeyValues *kv = new KeyValues( "blah" ); + + if ( !kv->LoadFromFile( g_pFileSystem, name ) ) + { +// Assert( 0 ); + kv->deleteThis(); + return NULL; + } + + const char *pTmpValue = kv->GetString( pKey, NULL ); + if( pTmpValue ) + { + Q_strncpy( pValue, pTmpValue, len ); + } + + kv->deleteThis(); + return ( pTmpValue != NULL ); +} + + +//----------------------------------------------------------------------------- +// Finds the original material associated with a patched material +//----------------------------------------------------------------------------- +MaterialSystemMaterial_t FindOriginalMaterial( const char *materialName, bool *pFound, bool bComplain ) +{ + MaterialSystemMaterial_t matID; + matID = FindMaterial( GetOriginalMaterialNameForPatchedMaterial( materialName ), pFound, bComplain ); + return matID; +} + + +//----------------------------------------------------------------------------- +// Load keyvalues from the local pack file, or from a file +//----------------------------------------------------------------------------- +bool LoadKeyValuesFromPackOrFile( const char *pFileName, KeyValues *pKeyValues ) +{ + CUtlBuffer buf; + if ( ReadFileFromPak( GetPakFile(), pFileName, true, buf ) ) + { + return pKeyValues->LoadFromBuffer( pFileName, buf ); + } + + return pKeyValues->LoadFromFile( g_pFileSystem, pFileName ); +} + + +//----------------------------------------------------------------------------- +// VMT parser +//----------------------------------------------------------------------------- +static void InsertKeyValues( KeyValues &dst, KeyValues& src, bool bCheckForExistence ) +{ + KeyValues *pSrcVar = src.GetFirstSubKey(); + while( pSrcVar ) + { + if ( !bCheckForExistence || dst.FindKey( pSrcVar->GetName() ) ) + { + switch( pSrcVar->GetDataType() ) + { + case KeyValues::TYPE_STRING: + dst.SetString( pSrcVar->GetName(), pSrcVar->GetString() ); + break; + case KeyValues::TYPE_INT: + dst.SetInt( pSrcVar->GetName(), pSrcVar->GetInt() ); + break; + case KeyValues::TYPE_FLOAT: + dst.SetFloat( pSrcVar->GetName(), pSrcVar->GetFloat() ); + break; + case KeyValues::TYPE_PTR: + dst.SetPtr( pSrcVar->GetName(), pSrcVar->GetPtr() ); + break; + } + } + pSrcVar = pSrcVar->GetNextKey(); + } +} + +static void ExpandPatchFile( KeyValues &keyValues ) +{ + int nCount = 0; + while( nCount < 10 && stricmp( keyValues.GetName(), "patch" ) == 0 ) + { +// WriteKeyValuesToFile( "patch.txt", keyValues ); + const char *pIncludeFileName = keyValues.GetString( "include" ); + if( !pIncludeFileName ) + return; + + KeyValues * includeKeyValues = new KeyValues( "vmt" ); + int nBufLen = Q_strlen( pIncludeFileName ) + Q_strlen( "materials/.vmt" ) + 1; + char *pFileName = ( char * )stackalloc( nBufLen ); + Q_strncpy( pFileName, pIncludeFileName, nBufLen ); + bool bSuccess = LoadKeyValuesFromPackOrFile( pFileName, includeKeyValues ); + if ( !bSuccess ) + { + includeKeyValues->deleteThis(); + return; + } + + KeyValues *pInsertSection = keyValues.FindKey( "insert" ); + if( pInsertSection ) + { + InsertKeyValues( *includeKeyValues, *pInsertSection, false ); + keyValues = *includeKeyValues; + } + + KeyValues *pReplaceSection = keyValues.FindKey( "replace" ); + if( pReplaceSection ) + { + InsertKeyValues( *includeKeyValues, *pReplaceSection, true ); + keyValues = *includeKeyValues; + } + + // Could add other commands here, like "delete", "rename", etc. + + includeKeyValues->deleteThis(); + nCount++; + } + + if( nCount >= 10 ) + { + Warning( "Infinite recursion in patch file?\n" ); + } +} + +KeyValues *LoadMaterialKeyValues( const char *pMaterialName, unsigned int nFlags ) +{ + // Load the underlying file + KeyValues *kv = new KeyValues( "blah" ); + + char pFullMaterialName[512]; + Q_snprintf( pFullMaterialName, 512, "materials/%s.vmt", pMaterialName ); + if ( !LoadKeyValuesFromPackOrFile( pFullMaterialName, kv ) ) + { + // Assert( 0 ); + kv->deleteThis(); + return NULL; + } + + if( nFlags & LOAD_MATERIAL_KEY_VALUES_FLAGS_EXPAND_PATCH ) + { + ExpandPatchFile( *kv ); + } + + return kv; +} + +void WriteMaterialKeyValuesToPak( const char *pMaterialName, KeyValues *kv ) +{ + char pFullMaterialName[512]; + Q_snprintf( pFullMaterialName, 512, "materials/%s.vmt", pMaterialName ); + + // Write patched .vmt into a memory buffer + CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER ); + kv->RecursiveSaveToFile( buf, 0 ); + + // Add to pak file for this .bsp + AddBufferToPak( GetPakFile(), pFullMaterialName, (void*)buf.Base(), buf.TellPut(), true ); + + // Cleanup + kv->deleteThis(); +} + + +//----------------------------------------------------------------------------- +// Gets a keyvalue from a *patched* material +//----------------------------------------------------------------------------- +bool GetValueFromPatchedMaterial( const char *pMaterialName, const char *pKey, char *pValue, int len ) +{ + // Load the underlying file so that we can check if env_cubemap is in there. + KeyValues *kv = new KeyValues( "blah" ); + + char pFullMaterialName[512]; + Q_snprintf( pFullMaterialName, 512, "materials/%s.vmt", pMaterialName ); + if ( !LoadKeyValuesFromPackOrFile( pFullMaterialName, kv ) ) + { +// Assert( 0 ); + kv->deleteThis(); + return NULL; + } + + ExpandPatchFile( *kv ); + + const char *pTmpValue = kv->GetString( pKey, NULL ); + if( pTmpValue ) + { + Q_strncpy( pValue, pTmpValue, len ); + } + + kv->deleteThis(); + return ( pTmpValue != NULL ); +} diff --git a/mp/src/utils/vbsp/materialpatch.h b/mp/src/utils/vbsp/materialpatch.h new file mode 100644 index 00000000..334fe013 --- /dev/null +++ b/mp/src/utils/vbsp/materialpatch.h @@ -0,0 +1,60 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef MATERIALPATCH_H +#define MATERIALPATCH_H +#ifdef _WIN32 +#pragma once +#endif + +#include "utilmatlib.h" + +struct MaterialPatchInfo_t +{ + const char *m_pKey; + const char *m_pRequiredOriginalValue; // NULL if you don't require one. + const char *m_pValue; + MaterialPatchInfo_t() + { + memset( this, 0, sizeof( *this ) ); + } +}; + +enum MaterialPatchType_t +{ + PATCH_INSERT = 0, // Add the key no matter what + PATCH_REPLACE, // Add the key only if it exists +}; + +void CreateMaterialPatch( const char *pOriginalMaterialName, const char *pNewMaterialName, + const char *pNewKey, const char *pNewValue, MaterialPatchType_t nPatchType ); + +// A version which allows you to use multiple key values +void CreateMaterialPatch( const char *pOriginalMaterialName, const char *pNewMaterialName, + int nKeys, const MaterialPatchInfo_t *pInfo, MaterialPatchType_t nPatchType ); + +// This gets a keyvalue from the *unpatched* version of the passed-in material +bool GetValueFromMaterial( const char *pMaterialName, const char *pKey, char *pValue, int len ); + +// Gets a keyvalue from a *patched* material +bool GetValueFromPatchedMaterial( const char *pMaterialName, const char *pKey, char *pValue, int len ); + +const char *GetOriginalMaterialNameForPatchedMaterial( const char *pPatchMaterialName ); + +MaterialSystemMaterial_t FindOriginalMaterial( const char *materialName, bool *pFound, bool bComplain = true ); + +bool DoesMaterialHaveKeyValuePair( const char *pMaterialName, const char *pKeyName, const char *pSearchValue ); +bool DoesMaterialHaveKey( const char *pMaterialName, const char *pKeyName ); + +enum LoadMaterialKeyValuesFlags_t +{ + LOAD_MATERIAL_KEY_VALUES_FLAGS_EXPAND_PATCH = 1, +}; + +KeyValues *LoadMaterialKeyValues( const char *pMaterialName, unsigned int nFlags ); +void WriteMaterialKeyValuesToPak( const char *pMaterialName, KeyValues *kv ); + +#endif // MATERIALPATCH_H diff --git a/mp/src/utils/vbsp/materialsub.cpp b/mp/src/utils/vbsp/materialsub.cpp new file mode 100644 index 00000000..d8eb40df --- /dev/null +++ b/mp/src/utils/vbsp/materialsub.cpp @@ -0,0 +1,90 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: This file loads a KeyValues file containing material name mappings. +// When the bsp is compiled, all materials listed in the file will +// be replaced by the second material in the pair. +// +//============================================================================= + +#include "vbsp.h" +#include "materialsub.h" +#include "KeyValues.h" +#include "tier1/strtools.h" + +bool g_ReplaceMaterials = false; + +static KeyValues *kv = 0; +static KeyValues *allMapKeys = 0; +static KeyValues *curMapKeys = 0; + +//----------------------------------------------------------------------------- +// Purpose: Loads the KeyValues file for materials replacements +//----------------------------------------------------------------------------- +void LoadMaterialReplacementKeys( const char *gamedir, const char *mapname ) +{ + // Careful with static variables + if( kv ) + { + kv->deleteThis(); + kv = 0; + } + if( allMapKeys ) + allMapKeys = 0; + if( curMapKeys ) + curMapKeys = 0; + + Msg( "Loading Replacement Keys\n" ); + + // Attach the path to the keyValues file + char path[1024]; + Q_snprintf( path, sizeof( path ), "%scfg\\materialsub.cfg", gamedir ); + + // Load the keyvalues file + kv = new KeyValues( "MaterialReplacements" ); + + Msg( "File path: %s", path ); + if( !kv->LoadFromFile( g_pFileSystem, path ) ) + { + Msg( "Failed to load KeyValues file!\n" ); + g_ReplaceMaterials = false; + kv->deleteThis(); + kv = 0; + return; + } + + // Load global replace keys + allMapKeys = kv->FindKey( "AllMaps", true ); + + // Load keys for the current map + curMapKeys = kv->FindKey( mapname ); + + allMapKeys->ChainKeyValue( curMapKeys ); +} + +//----------------------------------------------------------------------------- +// Purpose: Deletes all keys +//----------------------------------------------------------------------------- +void DeleteMaterialReplacementKeys( void ) +{ + if( kv ) + { + kv->deleteThis(); + kv = 0; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Replace the passed-in material name with a replacement name, if one exists +//----------------------------------------------------------------------------- +const char* ReplaceMaterialName( const char *name ) +{ + // Look for the material name in the global and map KeyValues + // If it's not there, just return the original name + + // HACK: This stinks - KeyValues won't take a string with '/' in it. + // If they did, this could be a simple pointer swap. + char newName[1024]; + Q_strncpy( newName, name, sizeof( newName ) ); + Q_FixSlashes( newName ); + return allMapKeys->GetString( newName, name ); +} \ No newline at end of file diff --git a/mp/src/utils/vbsp/materialsub.h b/mp/src/utils/vbsp/materialsub.h new file mode 100644 index 00000000..c1de0698 --- /dev/null +++ b/mp/src/utils/vbsp/materialsub.h @@ -0,0 +1,25 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: This file loads a KeyValues file containing material name mappings. +// When the bsp is compiled, all materials listed in the file will +// be replaced by the second material in the pair. +// +//============================================================================= + +#ifndef MATERIALSUB_H +#define MATERIALSUB_H +#ifdef _WIN32 +#pragma once +#endif + +extern bool g_ReplaceMaterials; + +// Setup / Cleanup +void LoadMaterialReplacementKeys( const char *gamedir, const char *mapname ); +void DeleteMaterialReplacementKeys( void ); + +// Takes a material name and returns it's replacement, if there is one. +// If there isn't a replacement, it returns the original. +const char* ReplaceMaterialName( const char *name ); + +#endif // MATERIALSUB_H \ No newline at end of file diff --git a/mp/src/utils/vbsp/nodraw.cpp b/mp/src/utils/vbsp/nodraw.cpp new file mode 100644 index 00000000..4fa493ea --- /dev/null +++ b/mp/src/utils/vbsp/nodraw.cpp @@ -0,0 +1,32 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +#include "vbsp.h" + +Vector draw_mins, draw_maxs; + +void Draw_ClearWindow (void) +{ +} + +//============================================================ + +#define GLSERV_PORT 25001 + + +void GLS_BeginScene (void) +{ +} + +void GLS_Winding (winding_t *w, int code) +{ +} + +void GLS_EndScene (void) +{ +} diff --git a/mp/src/utils/vbsp/normals.cpp b/mp/src/utils/vbsp/normals.cpp new file mode 100644 index 00000000..1696342c --- /dev/null +++ b/mp/src/utils/vbsp/normals.cpp @@ -0,0 +1,50 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +#include "bsplib.h" +#include "vbsp.h" + + +void SaveVertexNormals( void ) +{ + int i, j; + dface_t *f; + texinfo_t *tex; + + + g_numvertnormalindices = 0; + g_numvertnormals = 0; + + for( i = 0 ;itexinfo]; + + for( j = 0; j < f->numedges; j++ ) + { + if( g_numvertnormalindices == MAX_MAP_VERTNORMALINDICES ) + { + Error( "g_numvertnormalindices == MAX_MAP_VERTNORMALINDICES (%d)", MAX_MAP_VERTNORMALINDICES ); + } + + g_vertnormalindices[g_numvertnormalindices] = g_numvertnormals; + g_numvertnormalindices++; + } + + // Add this face plane's normal. + // Note: this doesn't do an exhaustive vertex normal match because the vrad does it. + // The result is that a little extra memory is wasted coming out of vbsp, but it + // goes away after vrad. + if( g_numvertnormals == MAX_MAP_VERTNORMALS ) + { + Error( "g_numvertnormals == MAX_MAP_VERTNORMALS (%d)", MAX_MAP_VERTNORMALS ); + } + + g_vertnormals[g_numvertnormals] = dplanes[f->planenum].normal; + g_numvertnormals++; + } +} diff --git a/mp/src/utils/vbsp/notes.txt b/mp/src/utils/vbsp/notes.txt new file mode 100644 index 00000000..b86b3e3f --- /dev/null +++ b/mp/src/utils/vbsp/notes.txt @@ -0,0 +1,66 @@ +// ----------------------------------------------------------------- +// JAY: +// +DONE: Fix tools for HL2/TF2 coordinate space +DONE: Load textures from Half-Life .WADs +DONE: Write out Q2 texture format (not miptex) +DONE: Write test map viewer +DONE: Test detail brushes +DONE: view portals to test +NOT DOING:Write out HL style collision trees +DONE: new engine loader +DONE: new vis in HL2 engine - looks simple now +DONE: Do QRAD backwards? i.e. use Valve QRAD, and merge in the Q2 file formats? probably +DONE: Integrate Ken's qrad code into qrad3 +DONE: add area portal visibility to HL2 engine +DONE: write area portal entities for HL2/TF2 +DONE: test area portal code +Split clusters for outdoor vis + +// ----------------------------------------------------------------- + +QBSP3 Chop is based on recursive subdivision. + - Force natural alignment of some sort to eliminate slivers where brushes meet ? + + +Use Q2 style ladder indicator? yes +Use Q2 style friction indicator? probably or not if physics based + + +// ----------------------------------------------------------------- +// CHARLIE: +// + +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +NOTE: set DISP_PROTO to compile with displacement map info -- not on by default until + the prototype is done, the checked in .exe does not have displacement map functionality +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +DONE: put DISP_PROTO defines around all of the displacement code until prototyping is done +DONE: add displacement map structure +DONE: add displacement face structure +DONE: change face/side structures to accept displacement texinfo and displacement face indices +DONE: change .map loader to parse displacment map info +DONE: don't allow merge or subdivision of displacement faces +DONE: when splitting brushes, the new generated side get initialized to -1 +DONE: add find displacement face functionality, then create it if not found +DONE: add find displacement map functionality, then create it if not found +DONE: initialize the displacement data before loading the .map file +initialize the face data with dispface and dispmap = -1, is this necessary???? +DONE: copy from bsp tool face to bsp file face -- the displacement info +DONE: add/copy lumps +DONE: swap data for writing to bsp -- not really necessary, but to keep in sync with the rest +DONE: write .bsp data out +DONE: add displacement data to .bsp statistics -- print file + +Test maps: +DONE: map where disp face gets split by block node +DONE: map where disp face attempts merge/split +DONE: map with texture disp face +DONE: map with lots of disp faces +DONE: map with multiple disp faces referencing one map +DONE: map with multiple disp faces on one brush +DONE: map with multiple disp faces on one brush referencing one map +DONE: map with funky texture split encased on one portal + +//------------------------------------------------------------------ \ No newline at end of file diff --git a/mp/src/utils/vbsp/overlay.cpp b/mp/src/utils/vbsp/overlay.cpp new file mode 100644 index 00000000..fcd2e924 --- /dev/null +++ b/mp/src/utils/vbsp/overlay.cpp @@ -0,0 +1,487 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "vbsp.h" +#include "disp_vbsp.h" +#include "builddisp.h" +#include "mathlib/vmatrix.h" + +void Overlay_BuildBasisOrigin( doverlay_t *pOverlay ); + +// Overlay list. +CUtlVector g_aMapOverlays; +CUtlVector g_aMapWaterOverlays; + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +int Overlay_GetFromEntity( entity_t *pMapEnt ) +{ + int iAccessorID = -1; + + // Allocate the new overlay. + int iOverlay = g_aMapOverlays.AddToTail(); + mapoverlay_t *pMapOverlay = &g_aMapOverlays[iOverlay]; + + // Get the overlay data. + pMapOverlay->nId = g_aMapOverlays.Count() - 1; + + if ( ValueForKey( pMapEnt, "targetname" )[ 0 ] != '\0' ) + { + // Overlay has a name, remember it's ID for accessing + iAccessorID = pMapOverlay->nId; + } + + pMapOverlay->flU[0] = FloatForKey( pMapEnt, "StartU" ); + pMapOverlay->flU[1] = FloatForKey( pMapEnt, "EndU" ); + pMapOverlay->flV[0] = FloatForKey( pMapEnt, "StartV" ); + pMapOverlay->flV[1] = FloatForKey( pMapEnt, "EndV" ); + + pMapOverlay->flFadeDistMinSq = FloatForKey( pMapEnt, "fademindist" ); + if ( pMapOverlay->flFadeDistMinSq > 0 ) + { + pMapOverlay->flFadeDistMinSq *= pMapOverlay->flFadeDistMinSq; + } + + pMapOverlay->flFadeDistMaxSq = FloatForKey( pMapEnt, "fademaxdist" ); + if ( pMapOverlay->flFadeDistMaxSq > 0 ) + { + pMapOverlay->flFadeDistMaxSq *= pMapOverlay->flFadeDistMaxSq; + } + + GetVectorForKey( pMapEnt, "BasisOrigin", pMapOverlay->vecOrigin ); + + pMapOverlay->m_nRenderOrder = IntForKey( pMapEnt, "RenderOrder" ); + if ( pMapOverlay->m_nRenderOrder < 0 || pMapOverlay->m_nRenderOrder >= OVERLAY_NUM_RENDER_ORDERS ) + Error( "Overlay (%s) at %f %f %f has invalid render order (%d).\n", ValueForKey( pMapEnt, "material" ), + pMapOverlay->vecOrigin.x, pMapOverlay->vecOrigin.y, pMapOverlay->vecOrigin.z, + pMapOverlay->m_nRenderOrder ); + + GetVectorForKey( pMapEnt, "uv0", pMapOverlay->vecUVPoints[0] ); + GetVectorForKey( pMapEnt, "uv1", pMapOverlay->vecUVPoints[1] ); + GetVectorForKey( pMapEnt, "uv2", pMapOverlay->vecUVPoints[2] ); + GetVectorForKey( pMapEnt, "uv3", pMapOverlay->vecUVPoints[3] ); + + GetVectorForKey( pMapEnt, "BasisU", pMapOverlay->vecBasis[0] ); + GetVectorForKey( pMapEnt, "BasisV", pMapOverlay->vecBasis[1] ); + GetVectorForKey( pMapEnt, "BasisNormal", pMapOverlay->vecBasis[2] ); + + const char *pMaterialName = ValueForKey( pMapEnt, "material" ); + Assert( strlen( pMaterialName ) < OVERLAY_MAP_STRLEN ); + if ( strlen( pMaterialName ) >= OVERLAY_MAP_STRLEN ) + { + Error( "Overlay Material Name (%s) too long! > OVERLAY_MAP_STRLEN (%d)", pMaterialName, OVERLAY_MAP_STRLEN ); + return -1; + } + strcpy( pMapOverlay->szMaterialName, pMaterialName ); + + // Convert the sidelist to side id(s). + const char *pSideList = ValueForKey( pMapEnt, "sides" ); + char *pTmpList = ( char* )_alloca( strlen( pSideList ) + 1 ); + strcpy( pTmpList, pSideList ); + const char *pScan = strtok( pTmpList, " " ); + if ( !pScan ) + return iAccessorID; + + pMapOverlay->aSideList.Purge(); + pMapOverlay->aFaceList.Purge(); + + do + { + int nSideId; + if ( sscanf( pScan, "%d", &nSideId ) == 1 ) + { + pMapOverlay->aSideList.AddToTail( nSideId ); + } + } while ( ( pScan = strtok( NULL, " " ) ) ); + + return iAccessorID; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +side_t *GetSide( int nSideId ) +{ + for( int iSide = 0; iSide < g_LoadingMap->nummapbrushsides; ++iSide ) + { + if ( g_LoadingMap->brushsides[iSide].id == nSideId ) + return &g_LoadingMap->brushsides[iSide]; + } + + return NULL; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void Overlay_UpdateSideLists( int StartIndex ) +{ + int nMapOverlayCount = g_aMapOverlays.Count(); + for( int iMapOverlay = StartIndex; iMapOverlay < nMapOverlayCount; ++iMapOverlay ) + { + mapoverlay_t *pMapOverlay = &g_aMapOverlays.Element( iMapOverlay ); + if ( pMapOverlay ) + { + int nSideCount = pMapOverlay->aSideList.Count(); + for( int iSide = 0; iSide < nSideCount; ++iSide ) + { + side_t *pSide = GetSide( pMapOverlay->aSideList[iSide] ); + if ( pSide ) + { + if ( pSide->aOverlayIds.Find( pMapOverlay->nId ) == -1 ) + { + pSide->aOverlayIds.AddToTail( pMapOverlay->nId ); + } + } + } + } + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void OverlayTransition_UpdateSideLists( int StartIndex ) +{ + int nOverlayCount = g_aMapWaterOverlays.Count(); + for( int iOverlay = StartIndex; iOverlay < nOverlayCount; ++iOverlay ) + { + mapoverlay_t *pOverlay = &g_aMapWaterOverlays.Element( iOverlay ); + if ( pOverlay ) + { + int nSideCount = pOverlay->aSideList.Count(); + for( int iSide = 0; iSide < nSideCount; ++iSide ) + { + side_t *pSide = GetSide( pOverlay->aSideList[iSide] ); + if ( pSide ) + { + if ( pSide->aWaterOverlayIds.Find( pOverlay->nId ) == -1 ) + { + pSide->aWaterOverlayIds.AddToTail( pOverlay->nId ); + } + } + } + } + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void Overlay_AddFaceToLists( int iFace, side_t *pSide ) +{ + int nOverlayIdCount = pSide->aOverlayIds.Count(); + for( int iOverlayId = 0; iOverlayId < nOverlayIdCount; ++iOverlayId ) + { + mapoverlay_t *pMapOverlay = &g_aMapOverlays.Element( pSide->aOverlayIds[iOverlayId] ); + if ( pMapOverlay ) + { + if( pMapOverlay->aFaceList.Find( iFace ) == -1 ) + { + pMapOverlay->aFaceList.AddToTail( iFace ); + } + } + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void OverlayTransition_AddFaceToLists( int iFace, side_t *pSide ) +{ + int nOverlayIdCount = pSide->aWaterOverlayIds.Count(); + for( int iOverlayId = 0; iOverlayId < nOverlayIdCount; ++iOverlayId ) + { + mapoverlay_t *pMapOverlay = &g_aMapWaterOverlays.Element( pSide->aWaterOverlayIds[iOverlayId] - ( MAX_MAP_OVERLAYS + 1 ) ); + if ( pMapOverlay ) + { + if( pMapOverlay->aFaceList.Find( iFace ) == -1 ) + { + pMapOverlay->aFaceList.AddToTail( iFace ); + } + } + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void Overlay_EmitOverlayFace( mapoverlay_t *pMapOverlay ) +{ + Assert( g_nOverlayCount < MAX_MAP_OVERLAYS ); + if ( g_nOverlayCount >= MAX_MAP_OVERLAYS ) + { + Error ( "Too Many Overlays!\nMAX_MAP_OVERLAYS = %d", MAX_MAP_OVERLAYS ); + return; + } + + doverlay_t *pOverlay = &g_Overlays[g_nOverlayCount]; + doverlayfade_t *pOverlayFade = &g_OverlayFades[g_nOverlayCount]; + + g_nOverlayCount++; + + // Conver the map overlay into a .bsp overlay (doverlay_t). + if ( pOverlay ) + { + pOverlay->nId = pMapOverlay->nId; + + pOverlay->flU[0] = pMapOverlay->flU[0]; + pOverlay->flU[1] = pMapOverlay->flU[1]; + pOverlay->flV[0] = pMapOverlay->flV[0]; + pOverlay->flV[1] = pMapOverlay->flV[1]; + + VectorCopy( pMapOverlay->vecUVPoints[0], pOverlay->vecUVPoints[0] ); + VectorCopy( pMapOverlay->vecUVPoints[1], pOverlay->vecUVPoints[1] ); + VectorCopy( pMapOverlay->vecUVPoints[2], pOverlay->vecUVPoints[2] ); + VectorCopy( pMapOverlay->vecUVPoints[3], pOverlay->vecUVPoints[3] ); + + VectorCopy( pMapOverlay->vecOrigin, pOverlay->vecOrigin ); + + VectorCopy( pMapOverlay->vecBasis[2], pOverlay->vecBasisNormal ); + + pOverlay->SetRenderOrder( pMapOverlay->m_nRenderOrder ); + + // Encode the BasisU into the unused z component of the vecUVPoints 0, 1, 2 + pOverlay->vecUVPoints[0].z = pMapOverlay->vecBasis[0].x; + pOverlay->vecUVPoints[1].z = pMapOverlay->vecBasis[0].y; + pOverlay->vecUVPoints[2].z = pMapOverlay->vecBasis[0].z; + + // Encode whether or not the v axis should be flipped. + Vector vecCross = pMapOverlay->vecBasis[2].Cross( pMapOverlay->vecBasis[0] ); + if ( vecCross.Dot( pMapOverlay->vecBasis[1] ) < 0.0f ) + { + pOverlay->vecUVPoints[3].z = 1.0f; + } + + // Texinfo. + texinfo_t texInfo; + texInfo.flags = 0; + texInfo.texdata = FindOrCreateTexData( pMapOverlay->szMaterialName ); + for( int iVec = 0; iVec < 2; ++iVec ) + { + for( int iAxis = 0; iAxis < 3; ++iAxis ) + { + texInfo.lightmapVecsLuxelsPerWorldUnits[iVec][iAxis] = 0.0f; + texInfo.textureVecsTexelsPerWorldUnits[iVec][iAxis] = 0.0f; + } + + texInfo.lightmapVecsLuxelsPerWorldUnits[iVec][3] = -99999.0f; + texInfo.textureVecsTexelsPerWorldUnits[iVec][3] = -99999.0f; + } + pOverlay->nTexInfo = FindOrCreateTexInfo( texInfo ); + + // Face List + int nFaceCount = pMapOverlay->aFaceList.Count(); + Assert( nFaceCount < OVERLAY_BSP_FACE_COUNT ); + if ( nFaceCount >= OVERLAY_BSP_FACE_COUNT ) + { + Error( "Overlay touching too many faces (touching %d, max %d)\nOverlay %s at %.1f %.1f %.1f", nFaceCount, OVERLAY_BSP_FACE_COUNT, pMapOverlay->szMaterialName, pMapOverlay->vecOrigin.x, pMapOverlay->vecOrigin.y, pMapOverlay->vecOrigin.z ); + return; + } + + pOverlay->SetFaceCount( nFaceCount ); + for( int iFace = 0; iFace < nFaceCount; ++iFace ) + { + pOverlay->aFaces[iFace] = pMapOverlay->aFaceList.Element( iFace ); + } + } + + // Convert the map overlay fade data into a .bsp overlay fade (doverlayfade_t). + if ( pOverlayFade ) + { + pOverlayFade->flFadeDistMinSq = pMapOverlay->flFadeDistMinSq; + pOverlayFade->flFadeDistMaxSq = pMapOverlay->flFadeDistMaxSq; + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void OverlayTransition_EmitOverlayFace( mapoverlay_t *pMapOverlay ) +{ + Assert( g_nWaterOverlayCount < MAX_MAP_WATEROVERLAYS ); + if ( g_nWaterOverlayCount >= MAX_MAP_WATEROVERLAYS ) + { + Error ( "Too many water overlays!\nMAX_MAP_WATEROVERLAYS = %d", MAX_MAP_WATEROVERLAYS ); + return; + } + + dwateroverlay_t *pOverlay = &g_WaterOverlays[g_nWaterOverlayCount]; + g_nWaterOverlayCount++; + + // Conver the map overlay into a .bsp overlay (doverlay_t). + if ( pOverlay ) + { + pOverlay->nId = pMapOverlay->nId; + + pOverlay->flU[0] = pMapOverlay->flU[0]; + pOverlay->flU[1] = pMapOverlay->flU[1]; + pOverlay->flV[0] = pMapOverlay->flV[0]; + pOverlay->flV[1] = pMapOverlay->flV[1]; + + VectorCopy( pMapOverlay->vecUVPoints[0], pOverlay->vecUVPoints[0] ); + VectorCopy( pMapOverlay->vecUVPoints[1], pOverlay->vecUVPoints[1] ); + VectorCopy( pMapOverlay->vecUVPoints[2], pOverlay->vecUVPoints[2] ); + VectorCopy( pMapOverlay->vecUVPoints[3], pOverlay->vecUVPoints[3] ); + + VectorCopy( pMapOverlay->vecOrigin, pOverlay->vecOrigin ); + + VectorCopy( pMapOverlay->vecBasis[2], pOverlay->vecBasisNormal ); + + pOverlay->SetRenderOrder( pMapOverlay->m_nRenderOrder ); + + // Encode the BasisU into the unused z component of the vecUVPoints 0, 1, 2 + pOverlay->vecUVPoints[0].z = pMapOverlay->vecBasis[0].x; + pOverlay->vecUVPoints[1].z = pMapOverlay->vecBasis[0].y; + pOverlay->vecUVPoints[2].z = pMapOverlay->vecBasis[0].z; + + // Encode whether or not the v axis should be flipped. + Vector vecCross = pMapOverlay->vecBasis[2].Cross( pMapOverlay->vecBasis[0] ); + if ( vecCross.Dot( pMapOverlay->vecBasis[1] ) < 0.0f ) + { + pOverlay->vecUVPoints[3].z = 1.0f; + } + + // Texinfo. + texinfo_t texInfo; + texInfo.flags = 0; + texInfo.texdata = FindOrCreateTexData( pMapOverlay->szMaterialName ); + for( int iVec = 0; iVec < 2; ++iVec ) + { + for( int iAxis = 0; iAxis < 3; ++iAxis ) + { + texInfo.lightmapVecsLuxelsPerWorldUnits[iVec][iAxis] = 0.0f; + texInfo.textureVecsTexelsPerWorldUnits[iVec][iAxis] = 0.0f; + } + + texInfo.lightmapVecsLuxelsPerWorldUnits[iVec][3] = -99999.0f; + texInfo.textureVecsTexelsPerWorldUnits[iVec][3] = -99999.0f; + } + pOverlay->nTexInfo = FindOrCreateTexInfo( texInfo ); + + // Face List + int nFaceCount = pMapOverlay->aFaceList.Count(); + Assert( nFaceCount < WATEROVERLAY_BSP_FACE_COUNT ); + if ( nFaceCount >= WATEROVERLAY_BSP_FACE_COUNT ) + { + Error( "Water Overlay touching too many faces (touching %d, max %d)\nOverlay %s at %.1f %.1f %.1f", nFaceCount, OVERLAY_BSP_FACE_COUNT, pMapOverlay->szMaterialName, pMapOverlay->vecOrigin.x, pMapOverlay->vecOrigin.y, pMapOverlay->vecOrigin.z ); + return; + } + + pOverlay->SetFaceCount( nFaceCount ); + for( int iFace = 0; iFace < nFaceCount; ++iFace ) + { + pOverlay->aFaces[iFace] = pMapOverlay->aFaceList.Element( iFace ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Overlay_EmitOverlayFaces( void ) +{ + int nMapOverlayCount = g_aMapOverlays.Count(); + for( int iMapOverlay = 0; iMapOverlay < nMapOverlayCount; ++iMapOverlay ) + { + Overlay_EmitOverlayFace( &g_aMapOverlays.Element( iMapOverlay ) ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void OverlayTransition_EmitOverlayFaces( void ) +{ + int nMapOverlayCount = g_aMapWaterOverlays.Count(); + for( int iMapOverlay = 0; iMapOverlay < nMapOverlayCount; ++iMapOverlay ) + { + OverlayTransition_EmitOverlayFace( &g_aMapWaterOverlays.Element( iMapOverlay ) ); + } +} + + +//----------------------------------------------------------------------------- +// These routines were mostly stolen from MapOverlay.cpp in Hammer +//----------------------------------------------------------------------------- +#define OVERLAY_BASIS_U 0 +#define OVERLAY_BASIS_V 1 +#define OVERLAY_BASIS_NORMAL 2 +#define OVERLAY_HANDLES_COUNT 4 + + +inline void TransformPoint( const VMatrix& matrix, Vector &point ) +{ + Vector orgVector = point; + matrix.V3Mul( orgVector, point ); +} + + +inline bool fequal( float value, float target, float delta) { return ( (value<(target+delta))&&(value>(target-delta)) ); } + + +//----------------------------------------------------------------------------- +// Purpose: this function translate / rotate an overlay. +// Input : pOverlay - the overlay to be translated +// OriginOffset - the translation +// AngleOffset - the rotation +// Matrix - the translation / rotation matrix +// Output : none +//----------------------------------------------------------------------------- +void Overlay_Translate( mapoverlay_t *pOverlay, Vector &OriginOffset, QAngle &AngleOffset, matrix3x4_t &Matrix ) +{ + VMatrix tmpMatrix( Matrix ); + + Vector temp = pOverlay->vecOrigin; + VectorTransform( temp, Matrix, pOverlay->vecOrigin ); + + // erase move component + tmpMatrix.SetTranslation( vec3_origin ); + + // check if matrix would still change something + if ( !tmpMatrix.IsIdentity() ) + { + // make sure axes are normalized (they should be anyways) + pOverlay->vecBasis[OVERLAY_BASIS_U].NormalizeInPlace(); + pOverlay->vecBasis[OVERLAY_BASIS_V].NormalizeInPlace(); + + Vector vecU = pOverlay->vecBasis[OVERLAY_BASIS_U]; + Vector vecV = pOverlay->vecBasis[OVERLAY_BASIS_V]; + Vector vecNormal = pOverlay->vecBasis[OVERLAY_BASIS_NORMAL]; + + TransformPoint( tmpMatrix, vecU ); + TransformPoint( tmpMatrix, vecV ); + TransformPoint( tmpMatrix, vecNormal ); + + float fScaleU = vecU.Length(); + float fScaleV = vecV.Length(); + float flScaleNormal = vecNormal.Length(); + + bool bIsUnit = ( fequal( fScaleU, 1.0f, 0.0001 ) && fequal( fScaleV, 1.0f, 0.0001 ) && fequal( flScaleNormal, 1.0f, 0.0001 ) ); + bool bIsPerp = ( fequal( DotProduct( vecU, vecV ), 0.0f, 0.0025 ) && fequal( DotProduct( vecU, vecNormal ), 0.0f, 0.0025 ) && fequal( DotProduct( vecV, vecNormal ), 0.0f, 0.0025 ) ); + + // if ( fequal(fScaleU,1,0.0001) && fequal(fScaleV,1,0.0001) && fequal(DotProduct( vecU, vecV ),0,0.0025) ) + if ( bIsUnit && bIsPerp ) + { + // transformation doesnt scale or shear anything, so just update base axes + pOverlay->vecBasis[OVERLAY_BASIS_U] = vecU; + pOverlay->vecBasis[OVERLAY_BASIS_V] = vecV; + pOverlay->vecBasis[OVERLAY_BASIS_NORMAL] = vecNormal; + } + else + { + // more complex transformation, move UV coordinates, but leave base axes + for ( int iHandle=0; iHandlevecUVPoints[iHandle]; + Vector vecPos = ( vecUV.x * pOverlay->vecBasis[OVERLAY_BASIS_U] + vecUV.y * pOverlay->vecBasis[OVERLAY_BASIS_V] ); + + // to transform in world space + TransformPoint( tmpMatrix, vecPos ); + + vecUV.x = pOverlay->vecBasis[OVERLAY_BASIS_U].Dot( vecPos ); + vecUV.y = pOverlay->vecBasis[OVERLAY_BASIS_V].Dot( vecPos ); + + pOverlay->vecUVPoints[iHandle] = vecUV; + } + } + } + +} diff --git a/mp/src/utils/vbsp/portals.cpp b/mp/src/utils/vbsp/portals.cpp new file mode 100644 index 00000000..ec2a2695 --- /dev/null +++ b/mp/src/utils/vbsp/portals.cpp @@ -0,0 +1,1684 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +#include "vbsp.h" +#include "utlvector.h" +#include "mathlib/vmatrix.h" +#include "iscratchpad3d.h" +#include "csg.h" +#include "fmtstr.h" + +int c_active_portals; +int c_peak_portals; +int c_boundary; +int c_boundary_sides; + +/* +=========== +AllocPortal +=========== +*/ +portal_t *AllocPortal (void) +{ + static int s_PortalCount = 0; + + portal_t *p; + + if (numthreads == 1) + c_active_portals++; + if (c_active_portals > c_peak_portals) + c_peak_portals = c_active_portals; + + p = (portal_t*)malloc (sizeof(portal_t)); + memset (p, 0, sizeof(portal_t)); + p->id = s_PortalCount; + ++s_PortalCount; + + return p; +} + +void FreePortal (portal_t *p) +{ + if (p->winding) + FreeWinding (p->winding); + if (numthreads == 1) + c_active_portals--; + free (p); +} + +//============================================================== + +/* +============== +VisibleContents + +Returns the single content bit of the +strongest visible content present +============== +*/ +int VisibleContents (int contents) +{ + int i; + + for (i=1 ; i<=LAST_VISIBLE_CONTENTS ; i<<=1) + { + if (contents & i ) + { + return i; + } + } + + return 0; +} + + +/* +=============== +ClusterContents +=============== +*/ +int ClusterContents (node_t *node) +{ + int c1, c2, c; + + if (node->planenum == PLANENUM_LEAF) + return node->contents; + + c1 = ClusterContents(node->children[0]); + c2 = ClusterContents(node->children[1]); + c = c1|c2; + + // a cluster may include some solid detail areas, but + // still be seen into + if ( ! (c1&CONTENTS_SOLID) || ! (c2&CONTENTS_SOLID) ) + c &= ~CONTENTS_SOLID; + return c; +} + +/* +============= +Portal_VisFlood + +Returns true if the portal is empty or translucent, allowing +the PVS calculation to see through it. +The nodes on either side of the portal may actually be clusters, +not leafs, so all contents should be ored together +============= +*/ +qboolean Portal_VisFlood (portal_t *p) +{ + int c1, c2; + + if (!p->onnode) + return false; // to global outsideleaf + + c1 = ClusterContents(p->nodes[0]); + c2 = ClusterContents(p->nodes[1]); + + if (!VisibleContents (c1^c2)) + return true; + + if (c1 & (CONTENTS_TRANSLUCENT|CONTENTS_DETAIL)) + c1 = 0; + if (c2 & (CONTENTS_TRANSLUCENT|CONTENTS_DETAIL)) + c2 = 0; + + if ( (c1|c2) & CONTENTS_SOLID ) + return false; // can't see through solid + + if (! (c1 ^ c2)) + return true; // identical on both sides + + if (!VisibleContents (c1^c2)) + return true; + return false; +} + + +/* +=============== +Portal_EntityFlood + +The entity flood determines which areas are +"outside" on the map, which are then filled in. +Flowing from side s to side !s +=============== +*/ +qboolean Portal_EntityFlood (portal_t *p, int s) +{ + if (p->nodes[0]->planenum != PLANENUM_LEAF + || p->nodes[1]->planenum != PLANENUM_LEAF) + Error ("Portal_EntityFlood: not a leaf"); + + // can never cross to a solid + if ( (p->nodes[0]->contents & CONTENTS_SOLID) + || (p->nodes[1]->contents & CONTENTS_SOLID) ) + return false; + + // can flood through everything else + return true; +} + +qboolean Portal_AreaLeakFlood (portal_t *p, int s) +{ + if ( !Portal_EntityFlood( p, s ) ) + return false; + + // can never cross through areaportal + if ( (p->nodes[0]->contents & CONTENTS_AREAPORTAL) + || (p->nodes[1]->contents & CONTENTS_AREAPORTAL) ) + return false; + + // can flood through everything else + return true; +} + + +//============================================================================= + +int c_tinyportals; + +/* +============= +AddPortalToNodes +============= +*/ +void AddPortalToNodes (portal_t *p, node_t *front, node_t *back) +{ + if (p->nodes[0] || p->nodes[1]) + Error ("AddPortalToNode: allready included"); + + p->nodes[0] = front; + p->next[0] = front->portals; + front->portals = p; + + p->nodes[1] = back; + p->next[1] = back->portals; + back->portals = p; +} + + +/* +============= +RemovePortalFromNode +============= +*/ +void RemovePortalFromNode (portal_t *portal, node_t *l) +{ + portal_t **pp, *t; + +// remove reference to the current portal + pp = &l->portals; + while (1) + { + t = *pp; + if (!t) + Error ("RemovePortalFromNode: portal not in leaf"); + + if ( t == portal ) + break; + + if (t->nodes[0] == l) + pp = &t->next[0]; + else if (t->nodes[1] == l) + pp = &t->next[1]; + else + Error ("RemovePortalFromNode: portal not bounding leaf"); + } + + if (portal->nodes[0] == l) + { + *pp = portal->next[0]; + portal->nodes[0] = NULL; + } + else if (portal->nodes[1] == l) + { + *pp = portal->next[1]; + portal->nodes[1] = NULL; + } +} + +//============================================================================ + +void PrintPortal (portal_t *p) +{ + int i; + winding_t *w; + + w = p->winding; + for (i=0 ; inumpoints ; i++) + Msg ("(%5.0f,%5.0f,%5.0f)\n",w->p[i][0] + , w->p[i][1], w->p[i][2]); +} + +// because of water areaportals support, the areaportal may not be the only brush on this node +bspbrush_t *AreaportalBrushForNode( node_t *node ) +{ + bspbrush_t *b = node->brushlist; + while ( b && !(b->original->contents & CONTENTS_AREAPORTAL) ) + { + b = b->next; + } + Assert( b->original->entitynum != 0 ); + return b; +} + +/* +================ +MakeHeadnodePortals + +The created portals will face the global outside_node +================ +*/ +// buffer space around sides of nodes +#define SIDESPACE 8 +void MakeHeadnodePortals (tree_t *tree) +{ + Vector bounds[2]; + int i, j, n; + portal_t *p, *portals[6]; + plane_t bplanes[6], *pl; + node_t *node; + + node = tree->headnode; + +// pad with some space so there will never be null volume leafs + for (i=0 ; i<3 ; i++) + { + bounds[0][i] = tree->mins[i] - SIDESPACE; + bounds[1][i] = tree->maxs[i] + SIDESPACE; + } + + tree->outside_node.planenum = PLANENUM_LEAF; + tree->outside_node.brushlist = NULL; + tree->outside_node.portals = NULL; + tree->outside_node.contents = 0; + + for (i=0 ; i<3 ; i++) + for (j=0 ; j<2 ; j++) + { + n = j*3 + i; + + p = AllocPortal (); + portals[n] = p; + + pl = &bplanes[n]; + memset (pl, 0, sizeof(*pl)); + if (j) + { + pl->normal[i] = -1; + pl->dist = -bounds[j][i]; + } + else + { + pl->normal[i] = 1; + pl->dist = bounds[j][i]; + } + p->plane = *pl; + p->winding = BaseWindingForPlane (pl->normal, pl->dist); + AddPortalToNodes (p, node, &tree->outside_node); + } + +// clip the basewindings by all the other planes + for (i=0 ; i<6 ; i++) + { + for (j=0 ; j<6 ; j++) + { + if (j == i) + continue; + ChopWindingInPlace (&portals[i]->winding, bplanes[j].normal, bplanes[j].dist, ON_EPSILON); + } + } +} + +//=================================================== + + +/* +================ +BaseWindingForNode +================ +*/ +#define BASE_WINDING_EPSILON 0.001 +#define SPLIT_WINDING_EPSILON 0.001 + +winding_t *BaseWindingForNode (node_t *node) +{ + winding_t *w; + node_t *n; + plane_t *plane; + Vector normal; + vec_t dist; + + w = BaseWindingForPlane (g_MainMap->mapplanes[node->planenum].normal, g_MainMap->mapplanes[node->planenum].dist); + + // clip by all the parents + for (n=node->parent ; n && w ; ) + { + plane = &g_MainMap->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; + Vector normal; + float dist = 0.0f; + int side = 0; + + w = BaseWindingForNode (node); + + // clip the portal by all the other portals in the node + for (p = node->portals ; p && w; p = p->next[side]) + { + if (p->nodes[0] == node) + { + side = 0; + VectorCopy (p->plane.normal, normal); + dist = p->plane.dist; + } + else if (p->nodes[1] == node) + { + side = 1; + VectorSubtract (vec3_origin, p->plane.normal, normal); + dist = -p->plane.dist; + } + else + { + Error ("CutNodePortals_r: mislinked portal"); + } + + ChopWindingInPlace (&w, normal, dist, 0.1); + } + + if (!w) + { + return; + } + + if (WindingIsTiny (w)) + { + c_tinyportals++; + FreeWinding (w); + return; + } + + + new_portal = AllocPortal (); + new_portal->plane = g_MainMap->mapplanes[node->planenum]; + new_portal->onnode = node; + new_portal->winding = w; + + AddPortalToNodes (new_portal, node->children[0], node->children[1]); +} + + +/* +============== +SplitNodePortals + +Move or split the portals that bound node so that the node's +children have portals instead of node. +============== +*/ +void SplitNodePortals (node_t *node) +{ + portal_t *p, *next_portal, *new_portal; + node_t *f, *b, *other_node; + int side = 0; + plane_t *plane; + winding_t *frontwinding, *backwinding; + + plane = &g_MainMap->mapplanes[node->planenum]; + f = node->children[0]; + b = node->children[1]; + + for (p = node->portals ; p ; p = next_portal) + { + if (p->nodes[0] == node) + side = 0; + else if (p->nodes[1] == node) + side = 1; + else + Error ("CutNodePortals_r: mislinked portal"); + next_portal = p->next[side]; + + other_node = p->nodes[!side]; + RemovePortalFromNode (p, p->nodes[0]); + RemovePortalFromNode (p, p->nodes[1]); + +// +// cut the portal into two portals, one on each side of the cut plane +// + ClipWindingEpsilon (p->winding, plane->normal, plane->dist, + SPLIT_WINDING_EPSILON, &frontwinding, &backwinding); + + if (frontwinding && WindingIsTiny(frontwinding)) + { + FreeWinding (frontwinding); + frontwinding = NULL; + c_tinyportals++; + } + + if (backwinding && WindingIsTiny(backwinding)) + { + FreeWinding (backwinding); + backwinding = NULL; + c_tinyportals++; + } + + if (!frontwinding && !backwinding) + { // tiny windings on both sides + continue; + } + + if (!frontwinding) + { + FreeWinding (backwinding); + if (side == 0) + AddPortalToNodes (p, b, other_node); + else + AddPortalToNodes (p, other_node, b); + continue; + } + if (!backwinding) + { + FreeWinding (frontwinding); + if (side == 0) + AddPortalToNodes (p, f, other_node); + else + AddPortalToNodes (p, other_node, f); + continue; + } + + // the winding is split + new_portal = AllocPortal (); + *new_portal = *p; + new_portal->winding = backwinding; + FreeWinding (p->winding); + p->winding = frontwinding; + + if (side == 0) + { + AddPortalToNodes (p, f, other_node); + AddPortalToNodes (new_portal, b, other_node); + } + else + { + AddPortalToNodes (p, other_node, f); + AddPortalToNodes (new_portal, other_node, b); + } + } + + node->portals = NULL; +} + + +/* +================ +CalcNodeBounds +================ +*/ +void CalcNodeBounds (node_t *node) +{ + portal_t *p; + int s; + int i; + + // calc mins/maxs for both leafs and nodes + ClearBounds (node->mins, node->maxs); + for (p = node->portals ; p ; p = p->next[s]) + { + s = (p->nodes[1] == node); + for (i=0 ; iwinding->numpoints ; i++) + AddPointToBounds (p->winding->p[i], node->mins, node->maxs); + } +} + + +/* +================== +MakeTreePortals_r +================== +*/ +void MakeTreePortals_r (node_t *node) +{ + int i; + + CalcNodeBounds (node); + if (node->mins[0] >= node->maxs[0]) + { + Warning("WARNING: node without a volume\n"); + } + + for (i=0 ; i<3 ; i++) + { + if (node->mins[i] < (MIN_COORD_INTEGER-SIDESPACE) || node->maxs[i] > (MAX_COORD_INTEGER+SIDESPACE)) + { + const char *pMatName = ""; + // split by brush side + if ( node->side ) + { + texinfo_t *pTexInfo = &texinfo[node->side->texinfo]; + dtexdata_t *pTexData = GetTexData( pTexInfo->texdata ); + pMatName = TexDataStringTable_GetString( pTexData->nameStringTableID ); + } + Vector point = node->portals->winding->p[0]; + Warning("WARNING: BSP node with unbounded volume (material: %s, near %s)\n", pMatName, VecToString(point) ); + break; + } + } + if (node->planenum == PLANENUM_LEAF) + return; + + MakeNodePortal (node); + SplitNodePortals (node); + + MakeTreePortals_r (node->children[0]); + MakeTreePortals_r (node->children[1]); +} + +/* +================== +MakeTreePortals +================== +*/ +void MakeTreePortals (tree_t *tree) +{ + MakeHeadnodePortals (tree); + MakeTreePortals_r (tree->headnode); +} + +/* +========================================================= + +FLOOD ENTITIES + +========================================================= +*/ + +//----------------------------------------------------------------------------- +// Purpose: Floods outward from the given node, marking visited nodes with +// the number of hops from a node with an entity. If we ever mark +// the outside_node for this tree, we've leaked. +// Input : node - +// dist - +//----------------------------------------------------------------------------- +void FloodPortals_r (node_t *node, int dist) +{ + portal_t *p; + int s; + + node->occupied = dist; + + for (p=node->portals ; p ; p = p->next[s]) + { + s = (p->nodes[1] == node); + + // Skip nodes that have already been marked. + if (p->nodes[!s]->occupied) + continue; + + // Skip portals that lead to or from nodes with solid contents. + if (!Portal_EntityFlood (p, s)) + continue; + + FloodPortals_r (p->nodes[!s], dist+1); + } +} + +void FloodAreaLeak_r( node_t *node, int dist ) +{ + portal_t *p; + int s; + + node->occupied = dist; + + for (p=node->portals ; p ; p = p->next[s]) + { + s = (p->nodes[1] == node); + + if (p->nodes[!s]->occupied) + continue; + + if (!Portal_AreaLeakFlood (p, s)) + continue; + + FloodAreaLeak_r( p->nodes[!s], dist+1 ); + } +} + +void ClearOccupied_r( node_t *headnode ) +{ + if ( !headnode ) + return; + + headnode->occupied = 0; + ClearOccupied_r( headnode->children[0] ); + ClearOccupied_r( headnode->children[1] ); +} + +void FloodAreaLeak( node_t *headnode, node_t *pFirstSide ) +{ + ClearOccupied_r( headnode ); + FloodAreaLeak_r( pFirstSide, 2 ); +} + +//----------------------------------------------------------------------------- +// Purpose: For the given entity at the given origin, finds the leaf node in the +// BSP tree that the entity occupies. +// +// We then flood outward from that leaf to see if the entity leaks. +// Input : headnode - +// origin - +// occupant - +// Output : Returns false if the entity is in solid, true if it is not. +//----------------------------------------------------------------------------- +qboolean PlaceOccupant (node_t *headnode, Vector& origin, entity_t *occupant) +{ + node_t *node; + vec_t d; + plane_t *plane; + + // find the leaf to start in + node = headnode; + while (node->planenum != PLANENUM_LEAF) + { + plane = &g_MainMap->mapplanes[node->planenum]; + d = DotProduct (origin, plane->normal) - plane->dist; + if (d >= 0) + node = node->children[0]; + else + node = node->children[1]; + } + + if (node->contents == CONTENTS_SOLID) + return false; + + node->occupant = occupant; + + // Flood outward from here to see if this entity leaks. + FloodPortals_r (node, 1); + + return true; +} + +/* +============= +FloodEntities + +Marks all nodes that can be reached by entites +============= +*/ +qboolean FloodEntities (tree_t *tree) +{ + int i; + Vector origin; + char *cl; + qboolean inside; + node_t *headnode; + + headnode = tree->headnode; + qprintf ("--- FloodEntities ---\n"); + inside = false; + tree->outside_node.occupied = 0; + + for (i=1 ; ioutside_node.occupied) + { + qprintf ("entity reached from outside -- no filling\n" ); + } + + return (qboolean)(inside && !tree->outside_node.occupied); +} + + +/* +========================================================= + +FLOOD AREAS + +========================================================= +*/ + +int c_areas; + +bool IsAreaportalNode( node_t *node ) +{ + return ( node->contents & CONTENTS_AREAPORTAL ) ? true : false; +} +/* +============= +FloodAreas_r +============= +*/ + +void FloodAreas_r (node_t *node, portal_t *pSeeThrough) +{ + portal_t *p; + int s; + bspbrush_t *b; + entity_t *e; + + if ( IsAreaportalNode(node) ) + { + // this node is part of an area portal + b = AreaportalBrushForNode( node ); + e = &entities[b->original->entitynum]; + + // if the current area has allready touched this + // portal, we are done + if (e->portalareas[0] == c_areas || e->portalareas[1] == c_areas) + return; + + // note the current area as bounding the portal + if (e->portalareas[1]) + { + Warning("WARNING: areaportal entity %i (brush %i) touches > 2 areas\n", b->original->entitynum, b->original->id ); + return; + } + + if (e->portalareas[0]) + { + e->portalareas[1] = c_areas; + e->m_pPortalsLeadingIntoAreas[1] = pSeeThrough; + } + else + { + e->portalareas[0] = c_areas; + e->m_pPortalsLeadingIntoAreas[0] = pSeeThrough; + } + + return; + } + + if (node->area) + return; // allready got it + node->area = c_areas; + + for (p=node->portals ; p ; p = p->next[s]) + { + s = (p->nodes[1] == node); +#if 0 + if (p->nodes[!s]->occupied) + continue; +#endif + if (!Portal_EntityFlood (p, s)) + continue; + + FloodAreas_r (p->nodes[!s], p); + } +} + +/* +============= +FindAreas_r + +Just decend the tree, and for each node that hasn't had an +area set, flood fill out from there +============= +*/ +void FindAreas_r (node_t *node) +{ + if (node->planenum != PLANENUM_LEAF) + { + FindAreas_r (node->children[0]); + FindAreas_r (node->children[1]); + return; + } + + if (node->area) + return; // allready got it + + if (node->contents & CONTENTS_SOLID) + return; + + if (!node->occupied) + return; // not reachable by entities + + // area portals are allways only flooded into, never + // out of + if (IsAreaportalNode(node)) + return; + + c_areas++; + FloodAreas_r (node, NULL); +} + + +void ReportAreaportalLeak( tree_t *tree, node_t *node ) +{ + portal_t *p, *pStart = NULL; + int s; + + // Find a portal out of this areaportal into empty space + for (p=node->portals ; p ; p = p->next[s]) + { + s = (p->nodes[1] == node); + if ( !Portal_EntityFlood( p, !s ) ) + continue; + if ( p->nodes[!s]->contents & CONTENTS_AREAPORTAL ) + continue; + + pStart = p; + break; + } + + if ( pStart ) + { + s = pStart->nodes[0] == node; + Assert(!(pStart->nodes[s]->contents & CONTENTS_AREAPORTAL) ); + // flood fill the area outside this areaportal brush + FloodAreaLeak( tree->headnode, pStart->nodes[s] ); + + // find the portal into the longest path around the portal + portal_t *pBest = NULL; + int bestDist = 0; + for (p=node->portals ; p ; p = p->next[s]) + { + if ( p == pStart ) + continue; + + s = (p->nodes[1] == node); + if ( p->nodes[!s]->occupied > bestDist ) + { + pBest = p; + bestDist = p->nodes[!s]->occupied; + } + } + if ( pBest ) + { + s = (pBest->nodes[0] == node); + // write the linefile that goes from pBest to pStart + AreaportalLeakFile( tree, pStart, pBest, pBest->nodes[s] ); + } + } +} + + +/* +============= +SetAreaPortalAreas_r + +Just decend the tree, and for each node that hasn't had an +area set, flood fill out from there +============= +*/ +void SetAreaPortalAreas_r (tree_t *tree, node_t *node) +{ + bspbrush_t *b; + entity_t *e; + + if (node->planenum != PLANENUM_LEAF) + { + SetAreaPortalAreas_r (tree, node->children[0]); + SetAreaPortalAreas_r (tree, node->children[1]); + return; + } + + if (IsAreaportalNode(node)) + { + if (node->area) + return; // already set + + b = AreaportalBrushForNode( node ); + e = &entities[b->original->entitynum]; + node->area = e->portalareas[0]; + if (!e->portalareas[1]) + { + ReportAreaportalLeak( tree, node ); + Warning("\nBrush %i: areaportal brush doesn't touch two areas\n", b->original->id); + return; + } + } +} + + +// Return a positive value between 0 and 2*PI telling the angle distance +// from flBaseAngle to flTestAngle. +float AngleOffset( float flBaseAngle, float flTestAngle ) +{ + while( flTestAngle > flBaseAngle ) + flTestAngle -= 2 * M_PI; + + return fmod( flBaseAngle - flTestAngle, (float) (2 * M_PI) ); +} + + +int FindUniquePoints( const Vector2D *pPoints, int nPoints, int *indexMap, int nMaxIndexMapPoints, float flTolerance ) +{ + float flToleranceSqr = flTolerance * flTolerance; + + // This could be slightly more efficient. + int nUniquePoints = 0; + for ( int i=0; i < nPoints; i++ ) + { + int j; + for ( j=0; j < nUniquePoints; j++ ) + { + if ( pPoints[i].DistToSqr( pPoints[indexMap[j]] ) < flToleranceSqr ) + break; + } + if ( j == nUniquePoints ) + { + if ( nUniquePoints >= nMaxIndexMapPoints ) + Error( "FindUniquePoints: overflowed unique point list (size %d).", nMaxIndexMapPoints ); + + indexMap[nUniquePoints++] = i; + } + } + + return nUniquePoints; +} + +// Build a 2D convex hull of the set of points. +// This essentially giftwraps the points as it walks around the perimeter. +int Convex2D( Vector2D const *pPoints, int nPoints, int *indices, int nMaxIndices ) +{ + int nIndices = 0; + bool touched[512]; + int indexMap[512]; + + if( nPoints == 0 ) + return 0; + + + // If we don't collapse the points into a unique set, we can loop around forever + // and max out nMaxIndices. + nPoints = FindUniquePoints( pPoints, nPoints, indexMap, ARRAYSIZE( indexMap ), 0.1f ); + memset( touched, 0, nPoints*sizeof(touched[0]) ); + + // Find the (lower) left side. + int i; + int iBest = 0; + for( i=1; i < nPoints; i++ ) + { + if( pPoints[indexMap[i]].x < pPoints[indexMap[iBest]].x || + (pPoints[indexMap[i]].x == pPoints[indexMap[iBest]].x && pPoints[indexMap[i]].y < pPoints[indexMap[iBest]].y) ) + { + iBest = i; + } + } + + touched[iBest] = true; + indices[0] = indexMap[iBest]; + nIndices = 1; + + Vector2D curEdge( 0, 1 ); + + // Wind around clockwise. + while( 1 ) + { + Vector2D const *pStartPoint = &pPoints[ indices[nIndices-1] ]; + + float flEdgeAngle = atan2( curEdge.y, curEdge.x ); + + int iMinAngle = -1; + float flMinAngle = 5000; + + for( i=0; i < nPoints; i++ ) + { + Vector2D vTo = pPoints[indexMap[i]] - *pStartPoint; + float flDistToSqr = vTo.LengthSqr(); + if ( flDistToSqr <= 0.1f ) + continue; + + // Get the angle from the edge to this point. + float flAngle = atan2( vTo.y, vTo.x ); + flAngle = AngleOffset( flEdgeAngle, flAngle ); + + if( fabs( flAngle - flMinAngle ) < 0.00001f ) + { + float flDistToTestSqr = pStartPoint->DistToSqr( pPoints[iMinAngle] ); + + // If the angle is the same, pick the point farthest away. + // unless the current one is closing the face loop + if ( iMinAngle != indices[0] && flDistToSqr > flDistToTestSqr ) + { + flMinAngle = flAngle; + iMinAngle = indexMap[i]; + } + } + else if( flAngle < flMinAngle ) + { + flMinAngle = flAngle; + iMinAngle = indexMap[i]; + } + } + + if( iMinAngle == -1 ) + { + // Couldn't find a point? + Assert( false ); + break; + } + else if( iMinAngle == indices[0] ) + { + // Finished. + break; + } + else + { + // Add this point. + if( nIndices >= nMaxIndices ) + break; + + for ( int jj = 0; jj < nIndices; jj++ ) + { + // if this assert hits, this routine is broken and is generating a spiral + // rather than a closed polygon - basically an edge overlap of some kind + Assert(indices[jj] != iMinAngle ); + } + + indices[nIndices] = iMinAngle; + ++nIndices; + } + + curEdge = pPoints[indices[nIndices-1]] - pPoints[indices[nIndices-2]]; + } + + return nIndices; +} + +void FindPortalsLeadingToArea_R( + node_t *pHeadNode, + int iSrcArea, + int iDestArea, + plane_t *pPlane, + CUtlVector &portals ) +{ + if (pHeadNode->planenum != PLANENUM_LEAF) + { + FindPortalsLeadingToArea_R( pHeadNode->children[0], iSrcArea, iDestArea, pPlane, portals ); + FindPortalsLeadingToArea_R( pHeadNode->children[1], iSrcArea, iDestArea, pPlane, portals ); + return; + } + + // Ok.. this is a leaf, check its portals. + int s; + for( portal_t *p = pHeadNode->portals; p ;p = p->next[!s] ) + { + s = (p->nodes[0] == pHeadNode); + + if( !p->nodes[0]->occupied || !p->nodes[1]->occupied ) + continue; + + if( p->nodes[1]->area == iDestArea && p->nodes[0]->area == iSrcArea || + p->nodes[0]->area == iDestArea && p->nodes[1]->area == iSrcArea ) + { + // Make sure the plane normals point the same way. + plane_t *pMapPlane = &g_MainMap->mapplanes[p->onnode->planenum]; + float flDot = fabs( pMapPlane->normal.Dot( pPlane->normal ) ); + if( fabs( 1 - flDot ) < 0.01f ) + { + Vector vPlanePt1 = pPlane->normal * pPlane->dist; + Vector vPlanePt2 = pMapPlane->normal * pMapPlane->dist; + + if( vPlanePt1.DistToSqr( vPlanePt2 ) < 0.01f ) + { + portals.AddToTail( p ); + } + } + } + } +} + + +void EmitClipPortalGeometry( node_t *pHeadNode, portal_t *pPortal, int iSrcArea, dareaportal_t *dp ) +{ + // Build a list of all the points in portals from the same original face. + CUtlVector portals; + FindPortalsLeadingToArea_R( + pHeadNode, + iSrcArea, + dp->otherarea, + &pPortal->plane, + portals ); + + CUtlVector points; + for( int iPortal=0; iPortal < portals.Size(); iPortal++ ) + { + portal_t *pPointPortal = portals[iPortal]; + winding_t *pWinding = pPointPortal->winding; + for( int i=0; i < pWinding->numpoints; i++ ) + { + points.AddToTail( pWinding->p[i] ); + } + } + + // Get the 2D convex hull. + + //// First transform them into a plane. + QAngle vAngles; + Vector vecs[3]; + + VectorAngles( pPortal->plane.normal, vAngles ); + AngleVectors( vAngles, &vecs[0], &vecs[1], &vecs[2] ); + VMatrix mTransform; + mTransform.Identity(); + mTransform.SetBasisVectors( vecs[0], vecs[1], vecs[2] ); + VMatrix mInvTransform = mTransform.Transpose(); + + int i; + CUtlVector points2D; + for( i=0; i < points.Size(); i++ ) + { + Vector vTest = mTransform * points[i]; + points2D.AddToTail( Vector2D( vTest.y, vTest.z ) ); + } + + // Build the hull. + int indices[512]; + int nIndices = Convex2D( points2D.Base(), points2D.Size(), indices, 512 ); + + // Output the hull. + dp->m_FirstClipPortalVert = g_nClipPortalVerts; + dp->m_nClipPortalVerts = nIndices; + + if ( nIndices >= 32 ) + { + Warning( "Warning: area portal has %d verts. Could be a vbsp bug.\n", nIndices ); + } + + if( dp->m_FirstClipPortalVert + dp->m_nClipPortalVerts >= MAX_MAP_PORTALVERTS ) + { + Vector *p = pPortal->winding->p; + Error( "MAX_MAP_PORTALVERTS (probably a broken areaportal near %.1f %.1f %.1f ", p->x, p->y, p->z ); + } + + for( i=0; i < nIndices; i++ ) + { + g_ClipPortalVerts[g_nClipPortalVerts] = points[ indices[i] ]; + ++g_nClipPortalVerts; + } +} + + +// Sets node_t::area for non-leaf nodes (this allows an optimization in the renderer). +void SetNodeAreaIndices_R( node_t *node ) +{ + // All leaf area indices should already be set. + if( node->planenum == PLANENUM_LEAF ) + return; + + // Have the children set their area indices. + SetNodeAreaIndices_R( node->children[0] ); + SetNodeAreaIndices_R( node->children[1] ); + + // If all children (leaves or nodes) are in the same area, then set our area + // to this area as well. Otherwise, set it to -1. + if( node->children[0]->area == node->children[1]->area ) + node->area = node->children[0]->area; + else + node->area = -1; +} + + +/* +============= +EmitAreaPortals + +============= +*/ +void EmitAreaPortals (node_t *headnode) +{ + entity_t *e; + dareaportal_t *dp; + + if (c_areas > MAX_MAP_AREAS) + Error ("Map is split into too many unique areas (max = %d)\nProbably too many areaportals", MAX_MAP_AREAS); + numareas = c_areas+1; + numareaportals = 1; // leave 0 as an error + + // Reset the clip portal vert info. + g_nClipPortalVerts = 0; + + for (int iSrcArea=1 ; iSrcArea<=c_areas ; iSrcArea++) + { + dareas[iSrcArea].firstareaportal = numareaportals; + for (int j=0 ; jareaportalnum) + continue; + + if (e->portalareas[0] == iSrcArea || e->portalareas[1] == iSrcArea) + { + int iSide = (e->portalareas[0] == iSrcArea); + + // We're only interested in the portal that divides the two areas. + // One of the portals that leads into the CONTENTS_AREAPORTAL just bounds + // the same two areas but the other bounds two different ones. + portal_t *pLeadingPortal = e->m_pPortalsLeadingIntoAreas[0]; + if( pLeadingPortal->nodes[0]->area == pLeadingPortal->nodes[1]->area ) + pLeadingPortal = e->m_pPortalsLeadingIntoAreas[1]; + + if( pLeadingPortal ) + { + Assert( pLeadingPortal->nodes[0]->area != pLeadingPortal->nodes[1]->area ); + + dp = &dareaportals[numareaportals]; + numareaportals++; + + dp->m_PortalKey = e->areaportalnum; + dp->otherarea = e->portalareas[iSide]; + dp->planenum = pLeadingPortal->onnode->planenum; + + Assert( pLeadingPortal->nodes[0]->planenum == PLANENUM_LEAF ); + Assert( pLeadingPortal->nodes[1]->planenum == PLANENUM_LEAF ); + + if( pLeadingPortal->nodes[0]->area == dp->otherarea ) + { + // Use the flipped version of the plane. + dp->planenum = (dp->planenum & ~1) | (~dp->planenum & 1); + } + + EmitClipPortalGeometry( headnode, pLeadingPortal, iSrcArea, dp ); + } + } + } + + dareas[iSrcArea].numareaportals = numareaportals - dareas[iSrcArea].firstareaportal; + } + + SetNodeAreaIndices_R( headnode ); + + qprintf ("%5i numareas\n", numareas); + qprintf ("%5i numareaportals\n", numareaportals); +} + +/* +============= +FloodAreas + +Mark each leaf with an area, bounded by CONTENTS_AREAPORTAL +============= +*/ +void FloodAreas (tree_t *tree) +{ + int start = Plat_FloatTime(); + qprintf ("--- FloodAreas ---\n"); + Msg("Processing areas..."); + FindAreas_r (tree->headnode); + SetAreaPortalAreas_r (tree, tree->headnode); + qprintf ("%5i areas\n", c_areas); + Msg("done (%d)\n", (int)(Plat_FloatTime() - start) ); +} + +//====================================================== + +int c_outside; +int c_inside; +int c_solid; + +void FillOutside_r (node_t *node) +{ + if (node->planenum != PLANENUM_LEAF) + { + FillOutside_r (node->children[0]); + FillOutside_r (node->children[1]); + return; + } + + // anything not reachable by an entity + // can be filled away + if (!node->occupied) + { + if (node->contents != CONTENTS_SOLID) + { + c_outside++; + node->contents = CONTENTS_SOLID; + } + else + c_solid++; + } + else + c_inside++; + +} + +/* +============= +FillOutside + +Fill all nodes that can't be reached by entities +============= +*/ +void FillOutside (node_t *headnode) +{ + c_outside = 0; + c_inside = 0; + c_solid = 0; + qprintf ("--- FillOutside ---\n"); + FillOutside_r (headnode); + qprintf ("%5i solid leafs\n", c_solid); + qprintf ("%5i leafs filled\n", c_outside); + qprintf ("%5i inside leafs\n", c_inside); +} + + +static float ComputeDistFromPlane( winding_t *pWinding, plane_t *pPlane, float maxdist ) +{ + float totaldist = 0.0f; + for (int i = 0; i < pWinding->numpoints; ++i) + { + totaldist += fabs(DotProduct( pPlane->normal, pWinding->p[i] ) - pPlane->dist); + if (totaldist > maxdist) + return totaldist; + } + return totaldist; +} + + +//----------------------------------------------------------------------------- +// Display portal error +//----------------------------------------------------------------------------- +static void DisplayPortalError( portal_t *p, int viscontents ) +{ + char contents[3][1024]; + PrintBrushContentsToString( p->nodes[0]->contents, contents[0], sizeof( contents[0] ) ); + PrintBrushContentsToString( p->nodes[1]->contents, contents[1], sizeof( contents[1] ) ); + PrintBrushContentsToString( viscontents, contents[2], sizeof( contents[2] ) ); + + Vector center; + WindingCenter( p->winding, center ); + Warning( "\nFindPortalSide: Couldn't find a good match for which brush to assign to a portal near (%.1f %.1f %.1f)\n", center.x, center.y, center.z); + Warning( "Leaf 0 contents: %s\n", contents[0] ); + Warning( "Leaf 1 contents: %s\n", contents[1] ); + Warning( "viscontents (node 0 contents ^ node 1 contents): %s\n", contents[2] ); + Warning( "This means that none of the brushes in leaf 0 or 1 that touches the portal has %s\n", contents[2] ); + Warning( "Check for a huge brush enclosing the coordinates above that has contents %s\n", contents[2] ); + Warning( "Candidate brush IDs: " ); + + CUtlVector listed; + for (int j=0 ; j<2 ; j++) + { + node_t *n = p->nodes[j]; + for (bspbrush_t *bb=n->brushlist ; bb ; bb=bb->next) + { + mapbrush_t *brush = bb->original; + if ( brush->contents & viscontents ) + { + if ( listed.Find( brush->brushnum ) == -1 ) + { + listed.AddToTail( brush->brushnum ); + Warning( "Brush %d: ", brush->id ); + } + } + } + } + Warning( "\n\n" ); +} + + +//============================================================== + +/* +============ +FindPortalSide + +Finds a brush side to use for texturing the given portal +============ +*/ +void FindPortalSide (portal_t *p) +{ + int viscontents; + bspbrush_t *bb; + mapbrush_t *brush; + node_t *n; + int i,j; + int planenum; + side_t *side, *bestside; + float bestdist; + plane_t *p1, *p2; + + // decide which content change is strongest + // solid > lava > water, etc + viscontents = VisibleContents (p->nodes[0]->contents ^ p->nodes[1]->contents); + if (!viscontents) + { + return; + } + + planenum = p->onnode->planenum; + bestside = NULL; + bestdist = 1000000; + + for (j=0 ; j<2 ; j++) + { + n = p->nodes[j]; + p1 = &g_MainMap->mapplanes[p->onnode->planenum]; + + for (bb=n->brushlist ; bb ; bb=bb->next) + { + brush = bb->original; + if ( !(brush->contents & viscontents) ) + continue; + + for (i=0 ; inumsides ; i++) + { + side = &brush->original_sides[i]; + if (side->bevel) + continue; + if (side->texinfo == TEXINFO_NODE) + continue; // non-visible + + if ((side->planenum&~1) == planenum) + { // exact match + bestside = &brush->original_sides[i]; + bestdist = 0.0f; + goto gotit; + } + + p2 = &g_MainMap->mapplanes[side->planenum&~1]; + + float dist = ComputeDistFromPlane( p->winding, p2, bestdist ); + if (dist < bestdist) + { + bestside = side; + bestdist = dist; + } + } + } + } + +gotit: + if (!bestside) + qprintf ("WARNING: side not found for portal\n"); + + // Compute average dist, check for problems... + if ((bestdist / p->winding->numpoints) > 2) + { + static int nWarnCount = 0; + if ( nWarnCount < 8 ) + { + DisplayPortalError( p, viscontents ); + if ( ++nWarnCount == 8 ) + { + Warning("*** Suppressing further FindPortalSide errors.... ***\n" ); + } + } + } + + p->sidefound = true; + p->side = bestside; +} + + +/* +=============== +MarkVisibleSides_r + +=============== +*/ +void MarkVisibleSides_r (node_t *node) +{ + portal_t *p; + int s; + + if (node->planenum != PLANENUM_LEAF) + { + MarkVisibleSides_r (node->children[0]); + MarkVisibleSides_r (node->children[1]); + return; + } + + // empty leafs are never boundary leafs + if (!node->contents) + return; + + // see if there is a visible face + for (p=node->portals ; p ; p = p->next[!s]) + { + s = (p->nodes[0] == node); + if (!p->onnode) + continue; // edge of world + if (!p->sidefound) + FindPortalSide (p); + if (p->side) + p->side->visible = true; + } + +} + +/* +============= +MarkVisibleSides + +============= +*/ +// UNDONE: Put detail brushes in a separate list (not mapbrushes) ? +void MarkVisibleSides (tree_t *tree, int startbrush, int endbrush, int detailScreen) +{ + int i, j; + mapbrush_t *mb; + int numsides; + qboolean detail; + + qprintf ("--- MarkVisibleSides ---\n"); + + // clear all the visible flags + for (i=startbrush ; imapbrushes[i]; + + if ( detailScreen != FULL_DETAIL ) + { + qboolean onlyDetail = (detailScreen==ONLY_DETAIL)?true:false; + // true for detail brushes + detail = (mb->contents & CONTENTS_DETAIL) ? true : false; + if ( onlyDetail ^ detail ) + { + // both of these must have the same value or we're not interested in this brush + continue; + } + } + + numsides = mb->numsides; + for (j=0 ; joriginal_sides[j].visible = false; + } + + // set visible flags on the sides that are used by portals + MarkVisibleSides_r (tree->headnode); +} + + +//----------------------------------------------------------------------------- +// Used to determine which sides are visible +//----------------------------------------------------------------------------- +void MarkVisibleSides (tree_t *tree, mapbrush_t **ppBrushes, int nCount ) +{ + qprintf ("--- MarkVisibleSides ---\n"); + + // clear all the visible flags + int i, j; + for ( i=0; i < nCount; ++i ) + { + mapbrush_t *mb = ppBrushes[i]; + int numsides = mb->numsides; + for (j=0 ; joriginal_sides[j].visible = false; + } + } + + // set visible flags on the sides that are used by portals + MarkVisibleSides_r( tree->headnode ); +} + diff --git a/mp/src/utils/vbsp/portals.h b/mp/src/utils/vbsp/portals.h new file mode 100644 index 00000000..e8b45c7b --- /dev/null +++ b/mp/src/utils/vbsp/portals.h @@ -0,0 +1,19 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef PORTALS_H +#define PORTALS_H +#ifdef _WIN32 +#pragma once +#endif + + +// Sets up the g_ClipPortalIndices array. +void TranslateClipPortalIndices(); + + +#endif // PORTALS_H diff --git a/mp/src/utils/vbsp/prtfile.cpp b/mp/src/utils/vbsp/prtfile.cpp new file mode 100644 index 00000000..c192176b --- /dev/null +++ b/mp/src/utils/vbsp/prtfile.cpp @@ -0,0 +1,374 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +#include "vbsp.h" +#include "collisionutils.h" +/* +============================================================================== + +PORTAL FILE GENERATION + +Save out name.prt for qvis to read +============================================================================== +*/ + + +#define PORTALFILE "PRT1" + +struct cluster_portals_t +{ + CUtlVector portals; +}; + +int num_visclusters; // clusters the player can be in +int num_visportals; + +int g_SkyCluster = -1; + +void WriteFloat (FILE *f, vec_t v) +{ + if ( fabs(v - RoundInt(v)) < 0.001 ) + fprintf (f,"%i ",(int)RoundInt(v)); + else + fprintf (f,"%f ",v); +} + + +/* +================= +WritePortalFile_r +================= +*/ +void WritePortalFile(FILE *pFile, const CUtlVector &list) +{ + portal_t *p; + winding_t *w; + Vector normal; + vec_t dist; + + for ( int clusterIndex = 0; clusterIndex < list.Count(); clusterIndex++ ) + { + for ( int j = 0; j < list[clusterIndex].portals.Count(); j++ ) + { + p = list[clusterIndex].portals[j]; + w = p->winding; + // 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 (pFile,"%i %i %i ",w->numpoints, p->nodes[1]->cluster, p->nodes[0]->cluster); + } + else + { + fprintf (pFile,"%i %i %i ",w->numpoints, p->nodes[0]->cluster, p->nodes[1]->cluster); + } + + for (int i=0 ; inumpoints ; i++) + { + fprintf (pFile,"("); + WriteFloat (pFile, w->p[i][0]); + WriteFloat (pFile, w->p[i][1]); + WriteFloat (pFile, w->p[i][2]); + fprintf (pFile,") "); + } + fprintf (pFile,"\n"); + } + } +} + +struct viscluster_t +{ + bspbrush_t *pBrushes; + int clusterIndex; + int leafCount; + int leafStart; +}; + +static CUtlVector g_VisClusters; + +// add to the list of brushes the merge leaves into single vis clusters +void AddVisCluster( entity_t *pFuncVisCluster ) +{ + viscluster_t tmp; + Vector clipMins, clipMaxs; + clipMins[0] = clipMins[1] = clipMins[2] = MIN_COORD_INTEGER; + clipMaxs[0] = clipMaxs[1] = clipMaxs[2] = MAX_COORD_INTEGER; + + // build the map brushes out into the minimum non-overlapping set of brushes + bspbrush_t *pBSPBrush = MakeBspBrushList( pFuncVisCluster->firstbrush, pFuncVisCluster->firstbrush + pFuncVisCluster->numbrushes, + clipMins, clipMaxs, NO_DETAIL); + tmp.pBrushes = ChopBrushes( pBSPBrush ); + + // store the entry in the list + tmp.clusterIndex = -1; + tmp.leafCount = 0; + tmp.leafStart = 0; + +#if DEBUG_VISUALIZE_CLUSTERS + int debug = atoi(ValueForKey(pFuncVisCluster,"debug")); + if ( debug ) + { + Msg("Debug vis cluster %d\n", g_VisClusters.Count() ); + } +#endif + + g_VisClusters.AddToTail( tmp ); + + // clear out this entity so it won't get written to the bsp + pFuncVisCluster->epairs = NULL; + pFuncVisCluster->numbrushes = 0; +} + +// returns the total overlapping volume of intersection between the node and the brush list +float VolumeOfIntersection( bspbrush_t *pBrushList, node_t *pNode ) +{ + float volume = 0.0f; + for ( bspbrush_t *pBrush = pBrushList; pBrush; pBrush = pBrush->next ) + { + if ( IsBoxIntersectingBox( pNode->mins, pNode->maxs, pBrush->mins, pBrush->maxs ) ) + { + bspbrush_t *pIntersect = IntersectBrush( pNode->volume, pBrush ); + if ( pIntersect ) + { + volume += BrushVolume( pIntersect ); + FreeBrush( pIntersect ); + } + } + } + + return volume; +} + +// Search for a forced cluster that this node is within +// NOTE: Returns the first one found, these won't merge themselves together +int GetVisCluster( node_t *pNode ) +{ + float maxVolume = BrushVolume(pNode->volume) * 0.10f; // needs to cover at least 10% of the volume to overlap + int maxIndex = -1; + // UNDONE: This could get slow + for ( int i = g_VisClusters.Count(); --i >= 0; ) + { + float volume = VolumeOfIntersection( g_VisClusters[i].pBrushes, pNode ); + if ( volume > maxVolume ) + { + volume = maxVolume; + maxIndex = i; + } + } + return maxIndex; +} +/* +================ +NumberLeafs_r +================ +*/ +void BuildVisLeafList_r (node_t *node, CUtlVector &leaves) +{ + if (node->planenum != PLANENUM_LEAF) + { // decision node + node->cluster = -99; + BuildVisLeafList_r (node->children[0], leaves); + BuildVisLeafList_r (node->children[1], leaves); + return; + } + + if ( node->contents & CONTENTS_SOLID ) + { // solid block, viewpoint never inside + node->cluster = -1; + return; + } + leaves.AddToTail(node); +} + +// Give each leaf in the list of empty leaves a vis cluster number +// some are within func_viscluster volumes and will be merged together +// every other leaf gets its own unique number +void NumberLeafs( const CUtlVector &leaves ) +{ + for ( int i = 0; i < leaves.Count(); i++ ) + { + node_t *node = leaves[i]; + int visCluster = GetVisCluster( node ); + if ( visCluster >= 0 ) + { + if ( g_VisClusters[visCluster].clusterIndex < 0 ) + { + g_VisClusters[visCluster].clusterIndex = num_visclusters; + num_visclusters++; + } + g_VisClusters[visCluster].leafCount++; + node->cluster = g_VisClusters[visCluster].clusterIndex; + } + else + { + if ( !g_bSkyVis && Is3DSkyboxArea( node->area ) ) + { + if ( g_SkyCluster < 0 ) + { + // allocate a cluster for the sky + g_SkyCluster = num_visclusters; + num_visclusters++; + } + node->cluster = g_SkyCluster; + } + else + { + node->cluster = num_visclusters; + num_visclusters++; + } + } + } + +#if DEBUG_VISUALIZE_CLUSTERS + for ( int i = 0; i < g_VisClusters.Count(); i++ ) + { + char name[256]; + sprintf(name, "u:\\main\\game\\ep2\\maps\\vis_%02d.gl", i ); + FileHandle_t fp = g_pFileSystem->Open( name, "w" ); + Msg("Writing %s\n", name ); + for ( bspbrush_t *pBrush = g_VisClusters[i].pBrushes; pBrush; pBrush = pBrush->next ) + { + for (int i = 0; i < pBrush->numsides; i++ ) + OutputWindingColor( pBrush->sides[i].winding, fp, 0, 255, 0 ); + } + for ( int j = 0; j < leaves.Count(); j++ ) + { + if ( leaves[j]->cluster == g_VisClusters[i].clusterIndex ) + { + bspbrush_t *pBrush = leaves[j]->volume; + for (int k = 0; k < pBrush->numsides; k++ ) + OutputWindingColor( pBrush->sides[k].winding, fp, 64 + (j&31), 64, 64 - (j&31) ); + } + } + g_pFileSystem->Close(fp); + } +#endif +} + +// build a list of all vis portals that connect clusters +int BuildPortalList( CUtlVector &portalList, const CUtlVector &leaves ) +{ + int portalCount = 0; + for ( int i = 0; i < leaves.Count(); i++ ) + { + node_t *node = leaves[i]; + // count the portals + for (portal_t *p = node->portals ; p ; ) + { + if (p->nodes[0] == node) // only write out from first leaf + { + if ( p->nodes[0]->cluster != p->nodes[1]->cluster ) + { + if (Portal_VisFlood (p)) + { + portalCount++; + portalList[node->cluster].portals.AddToTail(p); + } + } + p = p->next[0]; + } + else + p = p->next[1]; + } + } + return portalCount; +} + + +/* +================ +CreateVisPortals_r +================ +*/ +void CreateVisPortals_r (node_t *node) +{ + // stop as soon as we get to a leaf + if (node->planenum == PLANENUM_LEAF ) + return; + + MakeNodePortal (node); + SplitNodePortals (node); + + CreateVisPortals_r (node->children[0]); + CreateVisPortals_r (node->children[1]); +} + +int clusterleaf; +void SaveClusters_r (node_t *node) +{ + if (node->planenum == PLANENUM_LEAF) + { + dleafs[clusterleaf++].cluster = node->cluster; + return; + } + SaveClusters_r (node->children[0]); + SaveClusters_r (node->children[1]); +} + +/* +================ +WritePortalFile +================ +*/ +void WritePortalFile (tree_t *tree) +{ + char filename[1024]; + node_t *headnode; + int start = Plat_FloatTime(); + + qprintf ("--- WritePortalFile ---\n"); + + sprintf (filename, "%s.prt", source); + Msg ("writing %s...", filename); + + headnode = tree->headnode; + + FreeTreePortals_r (headnode); + MakeHeadnodePortals (tree); + + CreateVisPortals_r (headnode); + +// set the cluster field in every leaf and count the total number of portals + num_visclusters = 0; + Msg("Building visibility clusters...\n"); + CUtlVector leaves; + BuildVisLeafList_r( headnode, leaves ); + + NumberLeafs (leaves); + CUtlVector portalList; + portalList.SetCount( num_visclusters ); + num_visportals = BuildPortalList( portalList, leaves ); +// write the file + FILE *pf = fopen (filename, "w"); + if (!pf) + Error ("Error opening %s", filename); + + fprintf (pf, "%s\n", PORTALFILE); + fprintf (pf, "%i\n", num_visclusters); + fprintf (pf, "%i\n", num_visportals); + + qprintf ("%5i visclusters\n", num_visclusters); + qprintf ("%5i visportals\n", num_visportals); + + WritePortalFile(pf, portalList); + + fclose (pf); + + // we need to store the clusters out now because ordering + // issues made us do this after writebsp... + clusterleaf = 1; + SaveClusters_r (headnode); + + Msg("done (%d)\n", (int)(Plat_FloatTime() - start) ); +} + diff --git a/mp/src/utils/vbsp/staticprop.cpp b/mp/src/utils/vbsp/staticprop.cpp new file mode 100644 index 00000000..810465b3 --- /dev/null +++ b/mp/src/utils/vbsp/staticprop.cpp @@ -0,0 +1,741 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Places "detail" objects which are client-only renderable things +// +// $Revision: $ +// $NoKeywords: $ +//=============================================================================// + +#include "vbsp.h" +#include "bsplib.h" +#include "utlvector.h" +#include "bspfile.h" +#include "gamebspfile.h" +#include "VPhysics_Interface.h" +#include "Studio.h" +#include "byteswap.h" +#include "UtlBuffer.h" +#include "CollisionUtils.h" +#include +#include "CModel.h" +#include "PhysDll.h" +#include "utlsymbol.h" +#include "tier1/strtools.h" +#include "KeyValues.h" + +static void SetCurrentModel( studiohdr_t *pStudioHdr ); +static void FreeCurrentModelVertexes(); + +IPhysicsCollision *s_pPhysCollision = NULL; + +//----------------------------------------------------------------------------- +// These puppies are used to construct the game lumps +//----------------------------------------------------------------------------- +static CUtlVector s_StaticPropDictLump; +static CUtlVector s_StaticPropLump; +static CUtlVector s_StaticPropLeafLump; + + +//----------------------------------------------------------------------------- +// Used to build the static prop +//----------------------------------------------------------------------------- +struct StaticPropBuild_t +{ + char const* m_pModelName; + char const* m_pLightingOrigin; + Vector m_Origin; + QAngle m_Angles; + int m_Solid; + int m_Skin; + int m_Flags; + float m_FadeMinDist; + float m_FadeMaxDist; + bool m_FadesOut; + float m_flForcedFadeScale; + unsigned short m_nMinDXLevel; + unsigned short m_nMaxDXLevel; +}; + + +//----------------------------------------------------------------------------- +// Used to cache collision model generation +//----------------------------------------------------------------------------- +struct ModelCollisionLookup_t +{ + CUtlSymbol m_Name; + CPhysCollide* m_pCollide; +}; + +static bool ModelLess( ModelCollisionLookup_t const& src1, ModelCollisionLookup_t const& src2 ) +{ + return src1.m_Name < src2.m_Name; +} + +static CUtlRBTree s_ModelCollisionCache( 0, 32, ModelLess ); +static CUtlVector s_LightingInfo; + + +//----------------------------------------------------------------------------- +// Gets the keyvalues from a studiohdr +//----------------------------------------------------------------------------- +bool StudioKeyValues( studiohdr_t* pStudioHdr, KeyValues *pValue ) +{ + if ( !pStudioHdr ) + return false; + + return pValue->LoadFromBuffer( pStudioHdr->pszName(), pStudioHdr->KeyValueText() ); +} + + +//----------------------------------------------------------------------------- +// Makes sure the studio model is a static prop +//----------------------------------------------------------------------------- +enum isstaticprop_ret +{ + RET_VALID, + RET_FAIL_NOT_MARKED_STATIC_PROP, + RET_FAIL_DYNAMIC, +}; + +isstaticprop_ret IsStaticProp( studiohdr_t* pHdr ) +{ + if (!(pHdr->flags & STUDIOHDR_FLAGS_STATIC_PROP)) + return RET_FAIL_NOT_MARKED_STATIC_PROP; + + // If it's got a propdata section in the model's keyvalues, it's not allowed to be a prop_static + KeyValues *modelKeyValues = new KeyValues(pHdr->pszName()); + if ( StudioKeyValues( pHdr, modelKeyValues ) ) + { + KeyValues *sub = modelKeyValues->FindKey("prop_data"); + if ( sub ) + { + if ( !(sub->GetInt( "allowstatic", 0 )) ) + { + modelKeyValues->deleteThis(); + return RET_FAIL_DYNAMIC; + } + } + } + modelKeyValues->deleteThis(); + + return RET_VALID; +} + + +//----------------------------------------------------------------------------- +// Add static prop model to the list of models +//----------------------------------------------------------------------------- + +static int AddStaticPropDictLump( char const* pModelName ) +{ + StaticPropDictLump_t dictLump; + strncpy( dictLump.m_Name, pModelName, DETAIL_NAME_LENGTH ); + + for (int i = s_StaticPropDictLump.Size(); --i >= 0; ) + { + if (!memcmp(&s_StaticPropDictLump[i], &dictLump, sizeof(dictLump) )) + return i; + } + + return s_StaticPropDictLump.AddToTail( dictLump ); +} + + +//----------------------------------------------------------------------------- +// Load studio model vertex data from a file... +//----------------------------------------------------------------------------- +bool LoadStudioModel( char const* pModelName, char const* pEntityType, CUtlBuffer& buf ) +{ + if ( !g_pFullFileSystem->ReadFile( pModelName, NULL, buf ) ) + return false; + + // Check that it's valid + if (strncmp ((const char *) buf.PeekGet(), "IDST", 4) && + strncmp ((const char *) buf.PeekGet(), "IDAG", 4)) + { + return false; + } + + studiohdr_t* pHdr = (studiohdr_t*)buf.PeekGet(); + + Studio_ConvertStudioHdrToNewVersion( pHdr ); + + if (pHdr->version != STUDIO_VERSION) + { + return false; + } + + isstaticprop_ret isStaticProp = IsStaticProp(pHdr); + if ( isStaticProp != RET_VALID ) + { + if ( isStaticProp == RET_FAIL_NOT_MARKED_STATIC_PROP ) + { + Warning("Error! To use model \"%s\"\n" + " with %s, it must be compiled with $staticprop!\n", pModelName, pEntityType ); + } + else if ( isStaticProp == RET_FAIL_DYNAMIC ) + { + Warning("Error! %s using model \"%s\", which must be used on a dynamic entity (i.e. prop_physics). Deleted.\n", pEntityType, pModelName ); + } + return false; + } + + // ensure reset + pHdr->pVertexBase = NULL; + pHdr->pIndexBase = NULL; + + return true; +} + + +//----------------------------------------------------------------------------- +// Computes a convex hull from a studio mesh +//----------------------------------------------------------------------------- +static CPhysConvex* ComputeConvexHull( mstudiomesh_t* pMesh ) +{ + // Generate a list of all verts in the mesh + Vector** ppVerts = (Vector**)stackalloc(pMesh->numvertices * sizeof(Vector*) ); + const mstudio_meshvertexdata_t *vertData = pMesh->GetVertexData(); + Assert( vertData ); // This can only return NULL on X360 for now + for (int i = 0; i < pMesh->numvertices; ++i) + { + ppVerts[i] = vertData->Position(i); + } + + // Generate a convex hull from the verts + return s_pPhysCollision->ConvexFromVerts( ppVerts, pMesh->numvertices ); +} + + +//----------------------------------------------------------------------------- +// Computes a convex hull from the studio model +//----------------------------------------------------------------------------- +CPhysCollide* ComputeConvexHull( studiohdr_t* pStudioHdr ) +{ + CUtlVector convexHulls; + + for (int body = 0; body < pStudioHdr->numbodyparts; ++body ) + { + mstudiobodyparts_t *pBodyPart = pStudioHdr->pBodypart( body ); + for( int model = 0; model < pBodyPart->nummodels; ++model ) + { + mstudiomodel_t *pStudioModel = pBodyPart->pModel( model ); + for( int mesh = 0; mesh < pStudioModel->nummeshes; ++mesh ) + { + // Make a convex hull for each mesh + // NOTE: This won't work unless the model has been compiled + // with $staticprop + mstudiomesh_t *pStudioMesh = pStudioModel->pMesh( mesh ); + convexHulls.AddToTail( ComputeConvexHull( pStudioMesh ) ); + } + } + } + + // Convert an array of convex elements to a compiled collision model + // (this deletes the convex elements) + return s_pPhysCollision->ConvertConvexToCollide( convexHulls.Base(), convexHulls.Size() ); +} + + +//----------------------------------------------------------------------------- +// Add, find collision model in cache +//----------------------------------------------------------------------------- +static CPhysCollide* GetCollisionModel( char const* pModelName ) +{ + // Convert to a common string + char* pTemp = (char*)_alloca(strlen(pModelName) + 1); + strcpy( pTemp, pModelName ); + _strlwr( pTemp ); + + char* pSlash = strchr( pTemp, '\\' ); + while( pSlash ) + { + *pSlash = '/'; + pSlash = strchr( pTemp, '\\' ); + } + + // Find it in the cache + ModelCollisionLookup_t lookup; + lookup.m_Name = pTemp; + int i = s_ModelCollisionCache.Find( lookup ); + if (i != s_ModelCollisionCache.InvalidIndex()) + return s_ModelCollisionCache[i].m_pCollide; + + // Load the studio model file + CUtlBuffer buf; + if (!LoadStudioModel(pModelName, "prop_static", buf)) + { + Warning("Error loading studio model \"%s\"!\n", pModelName ); + + // This way we don't try to load it multiple times + lookup.m_pCollide = 0; + s_ModelCollisionCache.Insert( lookup ); + + return 0; + } + + // Compute the convex hull of the model... + studiohdr_t* pStudioHdr = (studiohdr_t*)buf.PeekGet(); + + // necessary for vertex access + SetCurrentModel( pStudioHdr ); + + lookup.m_pCollide = ComputeConvexHull( pStudioHdr ); + s_ModelCollisionCache.Insert( lookup ); + + if ( !lookup.m_pCollide ) + { + Warning("Bad geometry on \"%s\"!\n", pModelName ); + } + + // Debugging + if (g_DumpStaticProps) + { + static int propNum = 0; + char tmp[128]; + sprintf( tmp, "staticprop%03d.txt", propNum ); + DumpCollideToGlView( lookup.m_pCollide, tmp ); + ++propNum; + } + + FreeCurrentModelVertexes(); + + // Insert into cache... + return lookup.m_pCollide; +} + + +//----------------------------------------------------------------------------- +// Tests a single leaf against the static prop +//----------------------------------------------------------------------------- + +static bool TestLeafAgainstCollide( int depth, int* pNodeList, + Vector const& origin, QAngle const& angles, CPhysCollide* pCollide ) +{ + // Copy the planes in the node list into a list of planes + float* pPlanes = (float*)_alloca(depth * 4 * sizeof(float) ); + int idx = 0; + for (int i = depth; --i >= 0; ++idx ) + { + int sign = (pNodeList[i] < 0) ? -1 : 1; + int node = (sign < 0) ? - pNodeList[i] - 1 : pNodeList[i]; + dnode_t* pNode = &dnodes[node]; + dplane_t* pPlane = &dplanes[pNode->planenum]; + + pPlanes[idx*4] = sign * pPlane->normal[0]; + pPlanes[idx*4+1] = sign * pPlane->normal[1]; + pPlanes[idx*4+2] = sign * pPlane->normal[2]; + pPlanes[idx*4+3] = sign * pPlane->dist; + } + + // Make a convex solid out of the planes + CPhysConvex* pPhysConvex = s_pPhysCollision->ConvexFromPlanes( pPlanes, depth, 0.0f ); + + // This should never happen, but if it does, return no collision + Assert( pPhysConvex ); + if (!pPhysConvex) + return false; + + CPhysCollide* pLeafCollide = s_pPhysCollision->ConvertConvexToCollide( &pPhysConvex, 1 ); + + // Collide the leaf solid with the static prop solid + trace_t tr; + s_pPhysCollision->TraceCollide( vec3_origin, vec3_origin, pLeafCollide, vec3_angle, + pCollide, origin, angles, &tr ); + + s_pPhysCollision->DestroyCollide( pLeafCollide ); + + return (tr.startsolid != 0); +} + +//----------------------------------------------------------------------------- +// Find all leaves that intersect with this bbox + test against the static prop.. +//----------------------------------------------------------------------------- + +static void ComputeConvexHullLeaves_R( int node, int depth, int* pNodeList, + Vector const& mins, Vector const& maxs, + Vector const& origin, QAngle const& angles, CPhysCollide* pCollide, + CUtlVector& leafList ) +{ + Assert( pNodeList && pCollide ); + Vector cornermin, cornermax; + + while( node >= 0 ) + { + dnode_t* pNode = &dnodes[node]; + dplane_t* pPlane = &dplanes[pNode->planenum]; + + // Arbitrary split plane here + for (int i = 0; i < 3; ++i) + { + if (pPlane->normal[i] >= 0) + { + cornermin[i] = mins[i]; + cornermax[i] = maxs[i]; + } + else + { + cornermin[i] = maxs[i]; + cornermax[i] = mins[i]; + } + } + + if (DotProduct( pPlane->normal, cornermax ) <= pPlane->dist) + { + // Add the node to the list of nodes + pNodeList[depth] = node; + ++depth; + + node = pNode->children[1]; + } + else if (DotProduct( pPlane->normal, cornermin ) >= pPlane->dist) + { + // In this case, we are going in front of the plane. That means that + // this plane must have an outward normal facing in the oppisite direction + // We indicate this be storing a negative node index in the node list + pNodeList[depth] = - node - 1; + ++depth; + + node = pNode->children[0]; + } + else + { + // Here the box is split by the node. First, we'll add the plane as if its + // outward facing normal is in the direction of the node plane, then + // we'll have to reverse it for the other child... + pNodeList[depth] = node; + ++depth; + + ComputeConvexHullLeaves_R( pNode->children[1], + depth, pNodeList, mins, maxs, origin, angles, pCollide, leafList ); + + pNodeList[depth - 1] = - node - 1; + ComputeConvexHullLeaves_R( pNode->children[0], + depth, pNodeList, mins, maxs, origin, angles, pCollide, leafList ); + return; + } + } + + Assert( pNodeList && pCollide ); + + // Never add static props to solid leaves + if ( (dleafs[-node-1].contents & CONTENTS_SOLID) == 0 ) + { + if (TestLeafAgainstCollide( depth, pNodeList, origin, angles, pCollide )) + { + leafList.AddToTail( -node - 1 ); + } + } +} + +//----------------------------------------------------------------------------- +// Places Static Props in the level +//----------------------------------------------------------------------------- + +static void ComputeStaticPropLeaves( CPhysCollide* pCollide, Vector const& origin, + QAngle const& angles, CUtlVector& leafList ) +{ + // Compute an axis-aligned bounding box for the collide + Vector mins, maxs; + s_pPhysCollision->CollideGetAABB( &mins, &maxs, pCollide, origin, angles ); + + // Find all leaves that intersect with the bounds + int tempNodeList[1024]; + ComputeConvexHullLeaves_R( 0, 0, tempNodeList, mins, maxs, + origin, angles, pCollide, leafList ); +} + + +//----------------------------------------------------------------------------- +// Computes the lighting origin +//----------------------------------------------------------------------------- +static bool ComputeLightingOrigin( StaticPropBuild_t const& build, Vector& lightingOrigin ) +{ + for (int i = s_LightingInfo.Count(); --i >= 0; ) + { + int entIndex = s_LightingInfo[i]; + + // Check against all lighting info entities + char const* pTargetName = ValueForKey( &entities[entIndex], "targetname" ); + if (!Q_strcmp(pTargetName, build.m_pLightingOrigin)) + { + GetVectorForKey( &entities[entIndex], "origin", lightingOrigin ); + return true; + } + } + + return false; +} + + +//----------------------------------------------------------------------------- +// Places Static Props in the level +//----------------------------------------------------------------------------- +static void AddStaticPropToLump( StaticPropBuild_t const& build ) +{ + // Get the collision model + CPhysCollide* pConvexHull = GetCollisionModel( build.m_pModelName ); + if (!pConvexHull) + return; + + // Compute the leaves the static prop's convex hull hits + CUtlVector< unsigned short > leafList; + ComputeStaticPropLeaves( pConvexHull, build.m_Origin, build.m_Angles, leafList ); + + if ( !leafList.Count() ) + { + Warning( "Static prop %s outside the map (%.2f, %.2f, %.2f)\n", build.m_pModelName, build.m_Origin.x, build.m_Origin.y, build.m_Origin.z ); + return; + } + // Insert an element into the lump data... + int i = s_StaticPropLump.AddToTail( ); + StaticPropLump_t& propLump = s_StaticPropLump[i]; + propLump.m_PropType = AddStaticPropDictLump( build.m_pModelName ); + VectorCopy( build.m_Origin, propLump.m_Origin ); + VectorCopy( build.m_Angles, propLump.m_Angles ); + propLump.m_FirstLeaf = s_StaticPropLeafLump.Count(); + propLump.m_LeafCount = leafList.Count(); + propLump.m_Solid = build.m_Solid; + propLump.m_Skin = build.m_Skin; + propLump.m_Flags = build.m_Flags; + if (build.m_FadesOut) + { + propLump.m_Flags |= STATIC_PROP_FLAG_FADES; + } + propLump.m_FadeMinDist = build.m_FadeMinDist; + propLump.m_FadeMaxDist = build.m_FadeMaxDist; + propLump.m_flForcedFadeScale = build.m_flForcedFadeScale; + propLump.m_nMinDXLevel = build.m_nMinDXLevel; + propLump.m_nMaxDXLevel = build.m_nMaxDXLevel; + + if (build.m_pLightingOrigin && *build.m_pLightingOrigin) + { + if (ComputeLightingOrigin( build, propLump.m_LightingOrigin )) + { + propLump.m_Flags |= STATIC_PROP_USE_LIGHTING_ORIGIN; + } + } + + // Add the leaves to the leaf lump + for (int j = 0; j < leafList.Size(); ++j) + { + StaticPropLeafLump_t insert; + insert.m_Leaf = leafList[j]; + s_StaticPropLeafLump.AddToTail( insert ); + } +} + + +//----------------------------------------------------------------------------- +// Places static props in the lump +//----------------------------------------------------------------------------- + +static void SetLumpData( ) +{ + GameLumpHandle_t handle = g_GameLumps.GetGameLumpHandle(GAMELUMP_STATIC_PROPS); + if (handle != g_GameLumps.InvalidGameLump()) + g_GameLumps.DestroyGameLump(handle); + + int dictsize = s_StaticPropDictLump.Size() * sizeof(StaticPropDictLump_t); + int objsize = s_StaticPropLump.Size() * sizeof(StaticPropLump_t); + int leafsize = s_StaticPropLeafLump.Size() * sizeof(StaticPropLeafLump_t); + int size = dictsize + objsize + leafsize + 3 * sizeof(int); + + handle = g_GameLumps.CreateGameLump( GAMELUMP_STATIC_PROPS, size, 0, GAMELUMP_STATIC_PROPS_VERSION ); + + // Serialize the data + CUtlBuffer buf( g_GameLumps.GetGameLump(handle), size ); + buf.PutInt( s_StaticPropDictLump.Size() ); + if (dictsize) + buf.Put( s_StaticPropDictLump.Base(), dictsize ); + buf.PutInt( s_StaticPropLeafLump.Size() ); + if (leafsize) + buf.Put( s_StaticPropLeafLump.Base(), leafsize ); + buf.PutInt( s_StaticPropLump.Size() ); + if (objsize) + buf.Put( s_StaticPropLump.Base(), objsize ); +} + + +//----------------------------------------------------------------------------- +// Places Static Props in the level +//----------------------------------------------------------------------------- + +void EmitStaticProps() +{ + CreateInterfaceFn physicsFactory = GetPhysicsFactory(); + if ( physicsFactory ) + { + s_pPhysCollision = (IPhysicsCollision *)physicsFactory( VPHYSICS_COLLISION_INTERFACE_VERSION, NULL ); + if( !s_pPhysCollision ) + return; + } + + // Generate a list of lighting origins, and strip them out + int i; + for ( i = 0; i < num_entities; ++i) + { + char* pEntity = ValueForKey(&entities[i], "classname"); + if (!Q_strcmp(pEntity, "info_lighting")) + { + s_LightingInfo.AddToTail(i); + } + } + + // Emit specifically specified static props + for ( i = 0; i < num_entities; ++i) + { + char* pEntity = ValueForKey(&entities[i], "classname"); + if (!strcmp(pEntity, "static_prop") || !strcmp(pEntity, "prop_static")) + { + StaticPropBuild_t build; + + GetVectorForKey( &entities[i], "origin", build.m_Origin ); + GetAnglesForKey( &entities[i], "angles", build.m_Angles ); + build.m_pModelName = ValueForKey( &entities[i], "model" ); + build.m_Solid = IntForKey( &entities[i], "solid" ); + build.m_Skin = IntForKey( &entities[i], "skin" ); + build.m_FadeMaxDist = FloatForKey( &entities[i], "fademaxdist" ); + build.m_Flags = 0;//IntForKey( &entities[i], "spawnflags" ) & STATIC_PROP_WC_MASK; + if (IntForKey( &entities[i], "ignorenormals" ) == 1) + { + build.m_Flags |= STATIC_PROP_IGNORE_NORMALS; + } + if (IntForKey( &entities[i], "disableshadows" ) == 1) + { + build.m_Flags |= STATIC_PROP_NO_SHADOW; + } + if (IntForKey( &entities[i], "disablevertexlighting" ) == 1) + { + build.m_Flags |= STATIC_PROP_NO_PER_VERTEX_LIGHTING; + } + if (IntForKey( &entities[i], "disableselfshadowing" ) == 1) + { + build.m_Flags |= STATIC_PROP_NO_SELF_SHADOWING; + } + + if (IntForKey( &entities[i], "screenspacefade" ) == 1) + { + build.m_Flags |= STATIC_PROP_SCREEN_SPACE_FADE; + } + + const char *pKey = ValueForKey( &entities[i], "fadescale" ); + if ( pKey && pKey[0] ) + { + build.m_flForcedFadeScale = FloatForKey( &entities[i], "fadescale" ); + } + else + { + build.m_flForcedFadeScale = 1; + } + build.m_FadesOut = (build.m_FadeMaxDist > 0); + build.m_pLightingOrigin = ValueForKey( &entities[i], "lightingorigin" ); + if (build.m_FadesOut) + { + build.m_FadeMinDist = FloatForKey( &entities[i], "fademindist" ); + if (build.m_FadeMinDist < 0) + { + build.m_FadeMinDist = build.m_FadeMaxDist; + } + } + else + { + build.m_FadeMinDist = 0; + } + build.m_nMinDXLevel = (unsigned short)IntForKey( &entities[i], "mindxlevel" ); + build.m_nMaxDXLevel = (unsigned short)IntForKey( &entities[i], "maxdxlevel" ); + AddStaticPropToLump( build ); + + // strip this ent from the .bsp file + entities[i].epairs = 0; + } + } + + // Strip out lighting origins; has to be done here because they are used when + // static props are made + for ( i = s_LightingInfo.Count(); --i >= 0; ) + { + // strip this ent from the .bsp file + entities[s_LightingInfo[i]].epairs = 0; + } + + + SetLumpData( ); +} + +static studiohdr_t *g_pActiveStudioHdr; +static void SetCurrentModel( studiohdr_t *pStudioHdr ) +{ + // track the correct model + g_pActiveStudioHdr = pStudioHdr; +} + +static void FreeCurrentModelVertexes() +{ + Assert( g_pActiveStudioHdr ); + + if ( g_pActiveStudioHdr->pVertexBase ) + { + free( g_pActiveStudioHdr->pVertexBase ); + g_pActiveStudioHdr->pVertexBase = NULL; + } +} + +const vertexFileHeader_t * mstudiomodel_t::CacheVertexData( void * pModelData ) +{ + char fileName[260]; + FileHandle_t fileHandle; + vertexFileHeader_t *pVvdHdr; + + Assert( pModelData == NULL ); + Assert( g_pActiveStudioHdr ); + + if ( g_pActiveStudioHdr->pVertexBase ) + { + return (vertexFileHeader_t *)g_pActiveStudioHdr->pVertexBase; + } + + // mandatory callback to make requested data resident + // load and persist the vertex file + strcpy( fileName, "models/" ); + strcat( fileName, g_pActiveStudioHdr->pszName() ); + Q_StripExtension( fileName, fileName, sizeof( fileName ) ); + strcat( fileName, ".vvd" ); + + // load the model + fileHandle = g_pFileSystem->Open( fileName, "rb" ); + if ( !fileHandle ) + { + Error( "Unable to load vertex data \"%s\"\n", fileName ); + } + + // Get the file size + int size = g_pFileSystem->Size( fileHandle ); + if (size == 0) + { + g_pFileSystem->Close( fileHandle ); + Error( "Bad size for vertex data \"%s\"\n", fileName ); + } + + pVvdHdr = (vertexFileHeader_t *)malloc(size); + g_pFileSystem->Read( pVvdHdr, size, fileHandle ); + g_pFileSystem->Close( fileHandle ); + + // check header + if (pVvdHdr->id != MODEL_VERTEX_FILE_ID) + { + Error("Error Vertex File %s id %d should be %d\n", fileName, pVvdHdr->id, MODEL_VERTEX_FILE_ID); + } + if (pVvdHdr->version != MODEL_VERTEX_FILE_VERSION) + { + Error("Error Vertex File %s version %d should be %d\n", fileName, pVvdHdr->version, MODEL_VERTEX_FILE_VERSION); + } + if (pVvdHdr->checksum != g_pActiveStudioHdr->checksum) + { + Error("Error Vertex File %s checksum %d should be %d\n", fileName, pVvdHdr->checksum, g_pActiveStudioHdr->checksum); + } + + g_pActiveStudioHdr->pVertexBase = (void*)pVvdHdr; + return pVvdHdr; +} + diff --git a/mp/src/utils/vbsp/textures.cpp b/mp/src/utils/vbsp/textures.cpp new file mode 100644 index 00000000..fc2034eb --- /dev/null +++ b/mp/src/utils/vbsp/textures.cpp @@ -0,0 +1,737 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +#include "vbsp.h" +#include "utilmatlib.h" +#include "physdll.h" +#include +#include +#include "tier1/strtools.h" +#include "materialpatch.h" +#include "KeyValues.h" + +void LoadSurfaceProperties( void ); + +IPhysicsSurfaceProps *physprops = NULL; + +int nummiptex; +textureref_t textureref[MAX_MAP_TEXTURES]; + +bool g_bHasWater = false; + +extern qboolean onlyents; + +dtexdata_t *GetTexData( int index ) +{ + if ( index < 0 ) + return NULL; + Assert( !onlyents ); + return &dtexdata[ index ]; +} + +static qboolean StringIsTrue( const char *str ) +{ + if( Q_strcasecmp( str, "true" ) == 0 ) + { + return true; + } + if( Q_strcasecmp( str, "1" ) == 0 ) + { + return true; + } + return false; +} + +int FindMiptex (const char *name) +{ + int i; + MaterialSystemMaterial_t matID; + const char *propVal, *propVal2; + int opacity; + bool found; + + for (i=0 ; inormal, baseaxis[i*3]); + if (dot > best) + { + best = dot; + bestaxis = i; + } + } + + VectorCopy (baseaxis[bestaxis*3+1], xv); + VectorCopy (baseaxis[bestaxis*3+2], yv); +} + + + +int g_SurfaceProperties[MAX_MAP_TEXDATA]; + + +int GetSurfaceProperties( MaterialSystemMaterial_t matID, const char *pMatName ) +{ + const char *pPropString = NULL; + int surfaceIndex = -1; + + if ( physprops ) + { + pPropString = GetMaterialVar( matID, "$surfaceprop" ); + if ( pPropString ) + { + surfaceIndex = physprops->GetSurfaceIndex( pPropString ); + if ( surfaceIndex < 0 ) + { + Msg("Can't find surfaceprop %s for material %s, using default\n", pPropString, pMatName ); + surfaceIndex = physprops->GetSurfaceIndex( pPropString ); + surfaceIndex = physprops->GetSurfaceIndex( "default" ); + } + } + } + + return surfaceIndex; +} + +int GetSurfaceProperties2( MaterialSystemMaterial_t matID, const char *pMatName ) +{ + const char *pPropString = NULL; + int surfaceIndex = -1; + + if ( physprops ) + { + pPropString = GetMaterialVar( matID, "$surfaceprop2" ); + if ( pPropString ) + { + surfaceIndex = physprops->GetSurfaceIndex( pPropString ); + if ( surfaceIndex < 0 ) + { + Msg("Can't find surfacepropblend %s for material %s, using default\n", pPropString, pMatName ); + surfaceIndex = physprops->GetSurfaceIndex( "default" ); + } + } + else + { + // No surface property 2. + return -1; + } + } + + return surfaceIndex; +} + +//----------------------------------------------------------------------------- +// Purpose: Finds or adds a texdata for the specified name ( same as below except +// instead of finding the named texture, copies the settings from the passed +// in sourceTexture. ) +// Used for creation of one off .vmt files for water surface textures +// Input : *pName - texture name +// Output : int index into dtexdata array +//----------------------------------------------------------------------------- +int FindAliasedTexData( const char *pName_, dtexdata_t *sourceTexture ) +{ + char *pName = ( char * )_alloca( strlen( pName_ ) + 1 ); + strcpy( pName, pName_ ); + strlwr( pName ); + int i, output; + bool found; + dtexdata_t *pTexData; + MaterialSystemMaterial_t matID; + + for ( i = 0; i < numtexdata; i++ ) + { + if ( !strcmp( pName, TexDataStringTable_GetString( GetTexData( i )->nameStringTableID ) ) ) + return i; + } + + + output = numtexdata; + if ( numtexdata >= MAX_MAP_TEXDATA ) + { + Error( "Too many unique texture mappings, max = %d\n", MAX_MAP_TEXDATA ); + } + pTexData = GetTexData( output ); + numtexdata++; + + // Save the name of the material. + pTexData->nameStringTableID = TexDataStringTable_AddOrFindString( pName ); + + // Get the width, height, view_width, view_height, and reflectivity from the material system. + matID = FindOriginalMaterial( TexDataStringTable_GetString( sourceTexture->nameStringTableID ), &found, false ); + if( matID == MATERIAL_NOT_FOUND || (!found) ) + { + qprintf( "WARNING: material not found: \"%s\"\n", pName ); + return -1; + } + + GetMaterialDimensions( matID, &pTexData->width, &pTexData->height ); + pTexData->view_width = pTexData->width; // undone: what is this? + pTexData->view_height = pTexData->height; // undone: what is this? + + GetMaterialReflectivity( matID, pTexData->reflectivity.Base() ); + g_SurfaceProperties[output] = GetSurfaceProperties( matID, pName ); + + return output; +} + + +//----------------------------------------------------------------------------- +// Finds a texdata for the specified name, returns -1 if not found +//----------------------------------------------------------------------------- +int FindTexData( const char *pName ) +{ + // Make sure the texdata doesn't already exist. + for( int i = 0; i < numtexdata; i++ ) + { + char const *pTexDataName = TexDataStringTable_GetString( GetTexData( i )->nameStringTableID ); + if ( !Q_stricmp( pTexDataName, pName ) ) + return i; + } + return -1; +} + + + +//----------------------------------------------------------------------------- +// Purpose: Finds or adds a texdata for the specified name +// Input : *pName - texture name +// Output : int index into dtexdata array +//----------------------------------------------------------------------------- +int FindOrCreateTexData( const char *pName_ ) +{ + char *pName = ( char * )_alloca( strlen( pName_ ) + 1 ); + strcpy( pName, pName_ ); + + int nOutput = FindTexData( pName ); + if ( nOutput >= 0 ) + return nOutput; + + // Didn't find it, add a new one + nOutput = numtexdata; + if ( numtexdata >= MAX_MAP_TEXDATA ) + { + Error( "Too many unique texture mappings, max = %d\n", MAX_MAP_TEXDATA ); + } + dtexdata_t *pTexData = GetTexData( nOutput ); + numtexdata++; + + // Save the name of the material. + pTexData->nameStringTableID = TexDataStringTable_AddOrFindString( pName ); + + // Get the width, height, view_width, view_height, and reflectivity from the material system. + bool bFound; + MaterialSystemMaterial_t matID = FindOriginalMaterial( pName, &bFound ); + if ( matID == MATERIAL_NOT_FOUND || (!bFound) ) + { + qprintf( "WARNING: material not found: \"%s\"\n", pName ); + return nOutput; + } + + GetMaterialDimensions( matID, &pTexData->width, &pTexData->height ); + pTexData->view_width = pTexData->width; // undone: what is this? + pTexData->view_height = pTexData->height; // undone: what is this? + + GetMaterialReflectivity( matID, pTexData->reflectivity.Base() ); + g_SurfaceProperties[nOutput] = GetSurfaceProperties( matID, pName ); + +#if 0 + Msg( "reflectivity: %f %f %f\n", + pTexData->reflectivity[0], + pTexData->reflectivity[1], + pTexData->reflectivity[2] ); +#endif + + return nOutput; +} + +int AddCloneTexData( dtexdata_t *pExistingTexData, char const *cloneTexDataName ) +{ + int existingIndex = pExistingTexData - GetTexData( 0 ); + dtexdata_t *pNewTexData = GetTexData( numtexdata ); + int newIndex = numtexdata; + numtexdata++; + + *pNewTexData = *pExistingTexData; + pNewTexData->nameStringTableID = TexDataStringTable_AddOrFindString( cloneTexDataName ); + g_SurfaceProperties[newIndex] = g_SurfaceProperties[existingIndex]; + + return newIndex; +} + + +//----------------------------------------------------------------------------- +// Finds a texinfo that exactly matches the passed in texinfo +//----------------------------------------------------------------------------- +int FindTexInfo( const texinfo_t &searchTexInfo ) +{ + for( int i = 0; i < texinfo.Count(); i++ ) + { + // Just an early-out for performance + if ( texinfo[i].texdata != searchTexInfo.texdata ) + continue; + + if ( !memcmp( &texinfo[i], &searchTexInfo, sizeof( texinfo_t ) ) ) + return i; + } + + return -1; +} + + +//----------------------------------------------------------------------------- +// Finds or creates a texinfo that exactly matches the passed in texinfo +//----------------------------------------------------------------------------- +int FindOrCreateTexInfo( const texinfo_t &searchTexInfo ) +{ + int i = FindTexInfo( searchTexInfo ); + if ( i >= 0 ) + return i; + + i = texinfo.AddToTail( searchTexInfo ); + + if ( onlyents ) + { + Error( "FindOrCreateTexInfo: Tried to create new texinfo during -onlyents compile!\nMust compile without -onlyents" ); + } + + return i; +} + +int TexinfoForBrushTexture (plane_t *plane, brush_texture_t *bt, const Vector& origin) +{ + Vector vecs[2]; + int sv, tv; + vec_t ang, sinv, cosv; + vec_t ns, nt; + texinfo_t tx; + int i, j; + + if (!bt->name[0]) + return 0; + + memset (&tx, 0, sizeof(tx)); + + // HLTOOLS - add support for texture vectors stored in the map file + if (g_nMapFileVersion < 220) + { + TextureAxisFromPlane(plane, vecs[0], vecs[1]); + } + + if (!bt->textureWorldUnitsPerTexel[0]) + bt->textureWorldUnitsPerTexel[0] = 1; + if (!bt->textureWorldUnitsPerTexel[1]) + bt->textureWorldUnitsPerTexel[1] = 1; + + + float shiftScaleU = 1.0f / 16.0f; + float shiftScaleV = 1.0f / 16.0f; + + if (g_nMapFileVersion < 220) + { + // rotate axis + if (bt->rotate == 0) + { sinv = 0 ; cosv = 1; } + else if (bt->rotate == 90) + { sinv = 1 ; cosv = 0; } + else if (bt->rotate == 180) + { sinv = 0 ; cosv = -1; } + else if (bt->rotate == 270) + { sinv = -1 ; cosv = 0; } + else + { + ang = bt->rotate / 180 * M_PI; + sinv = sin(ang); + cosv = cos(ang); + } + + if (vecs[0][0]) + sv = 0; + else if (vecs[0][1]) + sv = 1; + else + sv = 2; + + if (vecs[1][0]) + tv = 0; + else if (vecs[1][1]) + tv = 1; + else + tv = 2; + + for (i=0 ; i<2 ; i++) + { + ns = cosv * vecs[i][sv] - sinv * vecs[i][tv]; + nt = sinv * vecs[i][sv] + cosv * vecs[i][tv]; + vecs[i][sv] = ns; + vecs[i][tv] = nt; + } + + for (i=0 ; i<2 ; i++) + { + for (j=0 ; j<3 ; j++) + { + tx.textureVecsTexelsPerWorldUnits[i][j] = vecs[i][j] / bt->textureWorldUnitsPerTexel[i]; + tx.lightmapVecsLuxelsPerWorldUnits[i][j] = tx.textureVecsTexelsPerWorldUnits[i][j] / 16.0f; + } + } + } + else + { + tx.textureVecsTexelsPerWorldUnits[0][0] = bt->UAxis[0] / bt->textureWorldUnitsPerTexel[0]; + tx.textureVecsTexelsPerWorldUnits[0][1] = bt->UAxis[1] / bt->textureWorldUnitsPerTexel[0]; + tx.textureVecsTexelsPerWorldUnits[0][2] = bt->UAxis[2] / bt->textureWorldUnitsPerTexel[0]; + + tx.textureVecsTexelsPerWorldUnits[1][0] = bt->VAxis[0] / bt->textureWorldUnitsPerTexel[1]; + tx.textureVecsTexelsPerWorldUnits[1][1] = bt->VAxis[1] / bt->textureWorldUnitsPerTexel[1]; + tx.textureVecsTexelsPerWorldUnits[1][2] = bt->VAxis[2] / bt->textureWorldUnitsPerTexel[1]; + + tx.lightmapVecsLuxelsPerWorldUnits[0][0] = bt->UAxis[0] / bt->lightmapWorldUnitsPerLuxel; + tx.lightmapVecsLuxelsPerWorldUnits[0][1] = bt->UAxis[1] / bt->lightmapWorldUnitsPerLuxel; + tx.lightmapVecsLuxelsPerWorldUnits[0][2] = bt->UAxis[2] / bt->lightmapWorldUnitsPerLuxel; + + tx.lightmapVecsLuxelsPerWorldUnits[1][0] = bt->VAxis[0] / bt->lightmapWorldUnitsPerLuxel; + tx.lightmapVecsLuxelsPerWorldUnits[1][1] = bt->VAxis[1] / bt->lightmapWorldUnitsPerLuxel; + tx.lightmapVecsLuxelsPerWorldUnits[1][2] = bt->VAxis[2] / bt->lightmapWorldUnitsPerLuxel; + + shiftScaleU = bt->textureWorldUnitsPerTexel[0] / bt->lightmapWorldUnitsPerLuxel; + shiftScaleV = bt->textureWorldUnitsPerTexel[1] / bt->lightmapWorldUnitsPerLuxel; + } + + tx.textureVecsTexelsPerWorldUnits[0][3] = bt->shift[0] + + DOT_PRODUCT( origin, tx.textureVecsTexelsPerWorldUnits[0] ); + tx.textureVecsTexelsPerWorldUnits[1][3] = bt->shift[1] + + DOT_PRODUCT( origin, tx.textureVecsTexelsPerWorldUnits[1] ); + + tx.lightmapVecsLuxelsPerWorldUnits[0][3] = shiftScaleU * bt->shift[0] + + DOT_PRODUCT( origin, tx.lightmapVecsLuxelsPerWorldUnits[0] ); + tx.lightmapVecsLuxelsPerWorldUnits[1][3] = shiftScaleV * bt->shift[1] + + DOT_PRODUCT( origin, tx.lightmapVecsLuxelsPerWorldUnits[1] ); + + tx.flags = bt->flags; + tx.texdata = FindOrCreateTexData( bt->name ); + + // find the texinfo + return FindOrCreateTexInfo( tx ); +} + + +void LoadSurfacePropFile( const char *pMaterialFilename ) +{ + FileHandle_t fp = g_pFileSystem->Open( pMaterialFilename, "rb" ); + + if ( fp == FILESYSTEM_INVALID_HANDLE ) + { + return; + } + + int len = g_pFileSystem->Size( fp ); + + char *pText = new char[len]; + g_pFileSystem->Read( pText, len, fp ); + g_pFileSystem->Close( fp ); + + physprops->ParseSurfaceData( pMaterialFilename, pText ); + + delete[] pText; +} +//----------------------------------------------------------------------------- +// Purpose: Loads the surface properties database into the physics DLL +//----------------------------------------------------------------------------- +void LoadSurfaceProperties( void ) +{ + CreateInterfaceFn physicsFactory = GetPhysicsFactory(); + if ( !physicsFactory ) + return; + + physprops = (IPhysicsSurfaceProps *)physicsFactory( VPHYSICS_SURFACEPROPS_INTERFACE_VERSION, NULL ); + + const char *SURFACEPROP_MANIFEST_FILE = "scripts/surfaceproperties_manifest.txt"; + KeyValues *manifest = new KeyValues( SURFACEPROP_MANIFEST_FILE ); + if ( manifest->LoadFromFile( g_pFileSystem, SURFACEPROP_MANIFEST_FILE, "GAME" ) ) + { + for ( KeyValues *sub = manifest->GetFirstSubKey(); sub != NULL; sub = sub->GetNextKey() ) + { + if ( !Q_stricmp( sub->GetName(), "file" ) ) + { + // Add + LoadSurfacePropFile( sub->GetString() ); + continue; + } + } + } + + manifest->deleteThis(); +} + + diff --git a/mp/src/utils/vbsp/tree.cpp b/mp/src/utils/vbsp/tree.cpp new file mode 100644 index 00000000..0f72844c --- /dev/null +++ b/mp/src/utils/vbsp/tree.cpp @@ -0,0 +1,207 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +#include "vbsp.h" + +extern int c_nodes; + +void RemovePortalFromNode (portal_t *portal, node_t *l); + +node_t *NodeForPoint (node_t *node, Vector& origin) +{ + plane_t *plane; + vec_t d; + + while (node->planenum != PLANENUM_LEAF) + { + plane = &g_MainMap->mapplanes[node->planenum]; + d = DotProduct (origin, plane->normal) - plane->dist; + if (d >= 0) + node = node->children[0]; + else + node = node->children[1]; + } + + return node; +} + + + +/* +============= +FreeTreePortals_r +============= +*/ +void FreeTreePortals_r (node_t *node) +{ + portal_t *p, *nextp; + int s; + + // free children + if (node->planenum != PLANENUM_LEAF) + { + FreeTreePortals_r (node->children[0]); + FreeTreePortals_r (node->children[1]); + } + + // free portals + for (p=node->portals ; p ; p=nextp) + { + s = (p->nodes[1] == node); + nextp = p->next[s]; + + RemovePortalFromNode (p, p->nodes[!s]); + FreePortal (p); + } + node->portals = NULL; +} + +/* +============= +FreeTree_r +============= +*/ +void FreeTree_r (node_t *node) +{ + face_t *f, *nextf; + + // free children + if (node->planenum != PLANENUM_LEAF) + { + FreeTree_r (node->children[0]); + FreeTree_r (node->children[1]); + } + + // free bspbrushes + FreeBrushList (node->brushlist); + + // free faces + for (f=node->faces ; f ; f=nextf) + { + nextf = f->next; + FreeFace (f); + } + + // free the node + if (node->volume) + FreeBrush (node->volume); + + if (numthreads == 1) + c_nodes--; + free (node); +} + + +/* +============= +FreeTree +============= +*/ +void FreeTree (tree_t *tree) +{ + if ( !tree ) + return; + + FreeTreePortals_r (tree->headnode); + FreeTree_r (tree->headnode); + free (tree); +} + +//=============================================================== + +void PrintTree_r (node_t *node, int depth) +{ + int i; + plane_t *plane; + bspbrush_t *bb; + + for (i=0 ; iplanenum == PLANENUM_LEAF) + { + if (!node->brushlist) + Msg ("NULL\n"); + else + { + for (bb=node->brushlist ; bb ; bb=bb->next) + Msg ("%i ", bb->original->brushnum); + Msg ("\n"); + } + return; + } + + plane = &g_MainMap->mapplanes[node->planenum]; + Msg ("#%i (%5.2f %5.2f %5.2f):%5.2f\n", node->planenum, + plane->normal[0], plane->normal[1], plane->normal[2], + plane->dist); + PrintTree_r (node->children[0], depth+1); + PrintTree_r (node->children[1], depth+1); +} + +/* +========================================================= + +NODES THAT DON'T SEPERATE DIFFERENT CONTENTS CAN BE PRUNED + +========================================================= +*/ + +int c_pruned; + +/* +============ +PruneNodes_r +============ +*/ +void PruneNodes_r (node_t *node) +{ + bspbrush_t *b, *next; + + if (node->planenum == PLANENUM_LEAF) + return; + PruneNodes_r (node->children[0]); + PruneNodes_r (node->children[1]); + + if ( (node->children[0]->contents & CONTENTS_SOLID) + && (node->children[1]->contents & CONTENTS_SOLID) ) + { + if (node->faces) + Error ("node->faces seperating CONTENTS_SOLID"); + if (node->children[0]->faces || node->children[1]->faces) + Error ("!node->faces with children"); + + // FIXME: free stuff + node->planenum = PLANENUM_LEAF; + node->contents = CONTENTS_SOLID; + + if (node->brushlist) + Error ("PruneNodes: node->brushlist"); + + // combine brush lists + node->brushlist = node->children[1]->brushlist; + + for (b=node->children[0]->brushlist ; b ; b=next) + { + next = b->next; + b->next = node->brushlist; + node->brushlist = b; + } + + c_pruned++; + } +} + + +void PruneNodes (node_t *node) +{ + qprintf ("--- PruneNodes ---\n"); + c_pruned = 0; + PruneNodes_r (node); + qprintf ("%5i pruned nodes\n", c_pruned); +} + +//=========================================================== diff --git a/mp/src/utils/vbsp/vbsp-2010.vcxproj b/mp/src/utils/vbsp/vbsp-2010.vcxproj new file mode 100644 index 00000000..9719c104 --- /dev/null +++ b/mp/src/utils/vbsp/vbsp-2010.vcxproj @@ -0,0 +1,371 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + Vbsp + {E4F39B89-9731-571D-B69D-C1B8FE56C056} + + + + Application + MultiByte + vbsp + + + Application + MultiByte + vbsp + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30319.1 + .\Debug\win32\ + .\Debug\win32\ + ..\..\devtools\vstools;$(ExecutablePath);$(Path) + true + true + true + true + true + .\Release\win32\ + .\Release\win32\ + ..\..\devtools\vstools;$(ExecutablePath);$(Path) + true + true + false + true + true + + + + if EXIST ..\..\..\game\bin\$(TargetFileName) for /f "delims=" %%A in ('attrib "..\..\..\game\bin\$(TargetFileName)"') do set valveTmpIsReadOnly="%%A" set valveTmpIsReadOnlyLetter=%valveTmpIsReadOnly:~6,1% if "%valveTmpIsReadOnlyLetter%"=="R" del /q "$(TargetDir)"$(TargetFileName) if exist ..\..\devtools\bin\vpc.exe ..\..\devtools\bin\vpc.exe -crc2 vbsp.vcxproj if ERRORLEVEL 1 exit 1 + + + /MP + Disabled + ..\..\common;..\..\public;..\..\public\tier0;..\..\public\tier1;..\common;..\vmpi + _HAS_ITERATOR_DEBUGGING=0;WIN32;_WIN32;_DEBUG;DEBUG;_WINDOWS;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;RAD_TELEMETRY_DISABLED;COMPILER_MSVC32;MACRO_MATHLIB;PROTECTED_THINGS_DISABLE;VPCGAMECAPS=VALVE;PROJECTDIR=D:\dev\games\rel\hl2\src\utils\vbsp;_DLL_EXT=.dll;VPCGAME=valve + true + false + Default + MultiThreadedDebug + true + StreamingSIMDExtensions + Fast + true + true + true + false + NotUsing + false + NoListing + $(IntDir)/ + $(IntDir)/ + $(IntDir)/ + false + Level4 + true + EditAndContinue + CompileAsCpp + $(IntDir)/ + Prompt + + + _DEBUG;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE + 1033 + + + + + /NXCOMPAT /ignore:4221 + %(AdditionalDependencies);ws2_32.lib;odbc32.lib;odbccp32.lib;winmm.lib + NotSet + $(OutDir)\vbsp.exe + true + libc;libcd;libcmt + true + $(IntDir)/$(TargetName).pdb + false + $(IntDir)/$(TargetName).map + Console + + MachineX86 + PromptImmediately + false + + + true + + + true + + + true + $(OutDir)/vbsp.bsc + + + Publishing to ..\..\..\game\bin + if not exist "..\..\..\game\bin" mkdir "..\..\..\game\bin" copy "$(TargetDir)"$(TargetFileName) ..\..\..\game\bin\$(TargetFileName) if ERRORLEVEL 1 goto BuildEventFailed if exist "$(TargetDir)"$(TargetName).map copy "$(TargetDir)"$(TargetName).map ..\..\..\game\bin\$(TargetName).map copy "$(TargetDir)"$(TargetName).pdb ..\..\..\game\bin\$(TargetName).pdb if ERRORLEVEL 1 goto BuildEventFailed goto BuildEventOK :BuildEventFailed echo *** ERROR! PostBuildStep FAILED for $(ProjectName)! EXE or DLL is probably running. *** del /q "$(TargetDir)"$(TargetFileName) exit 1 :BuildEventOK + + + + + + + if EXIST ..\..\..\game\bin\$(TargetFileName) for /f "delims=" %%A in ('attrib "..\..\..\game\bin\$(TargetFileName)"') do set valveTmpIsReadOnly="%%A" set valveTmpIsReadOnlyLetter=%valveTmpIsReadOnly:~6,1% if "%valveTmpIsReadOnlyLetter%"=="R" del /q "$(TargetDir)"$(TargetFileName) if exist ..\..\devtools\bin\vpc.exe ..\..\devtools\bin\vpc.exe -crc2 vbsp.vcxproj if ERRORLEVEL 1 exit 1 + + + /MP /d2Zi+ + MaxSpeed + AnySuitable + true + Speed + ..\..\common;..\..\public;..\..\public\tier0;..\..\public\tier1;..\common;..\vmpi + WIN32;_WIN32;NDEBUG;_WINDOWS;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;RAD_TELEMETRY_DISABLED;COMPILER_MSVC32;MACRO_MATHLIB;PROTECTED_THINGS_DISABLE;VPCGAMECAPS=VALVE;PROJECTDIR=D:\dev\games\rel\hl2\src\utils\vbsp;_DLL_EXT=.dll;VPCGAME=valve + true + false + MultiThreaded + false + true + StreamingSIMDExtensions + Fast + true + true + true + false + NotUsing + false + NoListing + $(IntDir)/ + $(IntDir)/ + $(IntDir)/ + false + Level4 + true + ProgramDatabase + CompileAsCpp + $(IntDir)/ + Prompt + + + NDEBUG;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE + 1033 + + + + + /DYNAMICBASE /NXCOMPAT /ignore:4221 + %(AdditionalDependencies);ws2_32.lib;odbc32.lib;odbccp32.lib;winmm.lib + NotSet + $(OutDir)\vbsp.exe + true + libc;libcd;libcmtd + true + $(IntDir)/$(TargetName).pdb + false + $(IntDir)/$(TargetName).map + Console + true + true + + MachineX86 + PromptImmediately + + + true + + + true + + + true + $(OutDir)/vbsp.bsc + + + Publishing to ..\..\..\game\bin + if not exist "..\..\..\game\bin" mkdir "..\..\..\game\bin" copy "$(TargetDir)"$(TargetFileName) ..\..\..\game\bin\$(TargetFileName) if ERRORLEVEL 1 goto BuildEventFailed if exist "$(TargetDir)"$(TargetName).map copy "$(TargetDir)"$(TargetName).map ..\..\..\game\bin\$(TargetName).map copy "$(TargetDir)"$(TargetName).pdb ..\..\..\game\bin\$(TargetName).pdb if ERRORLEVEL 1 goto BuildEventFailed goto BuildEventOK :BuildEventFailed echo *** ERROR! PostBuildStep FAILED for $(ProjectName)! EXE or DLL is probably running. *** del /q "$(TargetDir)"$(TargetFileName) exit 1 :BuildEventOK + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + NotUsing + NotUsing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Compiling pointeroverride.asm + "$(VCInstallDir)bin\ml.exe" /c /Cp /Zi /Fo"$(IntDir)\%(Filename).obj" "%(FullPath)" + $(IntDir)\%(Filename).obj + Compiling pointeroverride.asm + "$(VCInstallDir)bin\ml.exe" /c /Cp /Zi /Fo"$(IntDir)\%(Filename).obj" "%(FullPath)" + $(IntDir)\%(Filename).obj + + + + + + + + + diff --git a/mp/src/utils/vbsp/vbsp-2010.vcxproj.filters b/mp/src/utils/vbsp/vbsp-2010.vcxproj.filters new file mode 100644 index 00000000..64aedaa4 --- /dev/null +++ b/mp/src/utils/vbsp/vbsp-2010.vcxproj.filters @@ -0,0 +1,446 @@ + + + + + {1680C80B-FF1E-EA4D-9817-CC12254F2E40} + + + {34125DCB-B916-13A4-10E6-A29CCCB0DD70} + + + {C5D73B3A-C648-896C-B7CE-F174808E5BA5} + + + {70104EB0-EB8F-8C16-99FB-ED7579D3A29D} + + + {BA03E055-4FA2-FCE3-8A1C-D348547D379C} + + + {A7DC6913-C602-1488-0EDF-DE69D12F2421} + + + + + Link Libraries + + + Link Libraries + + + Link Libraries + + + Link Libraries + + + Link Libraries + + + Link Libraries + + + Link Libraries + + + Link Libraries + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files\Common header files + + + Header Files\Common header files + + + Header Files\Common header files + + + Header Files\Common header files + + + Header Files\Common header files + + + Header Files\Common header files + + + Header Files\Common header files + + + Header Files\Common header files + + + Header Files\Common header files + + + Header Files\Common header files + + + Header Files\Common header files + + + Header Files\Common header files + + + Header Files\Common header files + + + Header Files\Common header files + + + Header Files\Common header files + + + Header Files\Common header files + + + Header Files\Common header files + + + Header Files\Common header files + + + Public Headers + + + Public Headers + + + Public Headers + + + Public Headers + + + Public Headers + + + Public Headers + + + Public Headers + + + Public Headers + + + Public Headers + + + Public Headers + + + Public Headers + + + Public Headers + + + Public Headers + + + Public Headers + + + Public Headers + + + Public Headers + + + Public Headers + + + Public Headers + + + Public Headers + + + Public Headers + + + Public Headers + + + Public Headers + + + Public Headers + + + Public Headers + + + Public Headers + + + Public Headers + + + Public Headers + + + Public Headers + + + Public Headers + + + Public Headers + + + Public Headers + + + Public Headers + + + Public Headers + + + Public Headers + + + Public Headers + + + Public Headers + + + Public Headers + + + Public Headers + + + Public Headers + + + Public Headers + + + Public Headers + + + Source Files\Common Files + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files\Common Files + + + Source Files\Common Files + + + Source Files\Common Files + + + Source Files\Common Files + + + Source Files\Common Files + + + Source Files\Common Files + + + Source Files\Common Files + + + Source Files\Common Files + + + Source Files\Common Files + + + Source Files\Common Files + + + Source Files\Common Files + + + Source Files\Common Files + + + Source Files\Common Files + + + + + + + Source Files + + + + + + + + diff --git a/mp/src/utils/vbsp/vbsp.cpp b/mp/src/utils/vbsp/vbsp.cpp new file mode 100644 index 00000000..1c7b7ec9 --- /dev/null +++ b/mp/src/utils/vbsp/vbsp.cpp @@ -0,0 +1,1404 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: BSP Building tool +// +// $NoKeywords: $ +//=============================================================================// + +#include "vbsp.h" +#include "detail.h" +#include "physdll.h" +#include "utilmatlib.h" +#include "disp_vbsp.h" +#include "writebsp.h" +#include "tier0/icommandline.h" +#include "materialsystem/imaterialsystem.h" +#include "map.h" +#include "tools_minidump.h" +#include "materialsub.h" +#include "loadcmdline.h" +#include "byteswap.h" +#include "worldvertextransitionfixup.h" + +extern float g_maxLightmapDimension; + +char source[1024]; +char mapbase[ 64 ]; +char name[1024]; +char materialPath[1024]; + +vec_t microvolume = 1.0; +qboolean noprune; +qboolean glview; +qboolean nodetail; +qboolean fulldetail; +qboolean onlyents; +bool onlyprops; +qboolean nomerge; +qboolean nomergewater = false; +qboolean nowater; +qboolean nocsg; +qboolean noweld; +qboolean noshare; +qboolean nosubdiv; +qboolean notjunc; +qboolean noopt; +qboolean leaktest; +qboolean verboseentities; +qboolean dumpcollide = false; +qboolean g_bLowPriority = false; +qboolean g_DumpStaticProps = false; +qboolean g_bSkyVis = false; // skybox vis is off by default, toggle this to enable it +bool g_bLightIfMissing = false; +bool g_snapAxialPlanes = false; +bool g_bKeepStaleZip = false; +bool g_NodrawTriggers = false; +bool g_DisableWaterLighting = false; +bool g_bAllowDetailCracks = false; +bool g_bNoVirtualMesh = false; + +float g_defaultLuxelSize = DEFAULT_LUXEL_SIZE; +float g_luxelScale = 1.0f; +float g_minLuxelScale = 1.0f; +bool g_BumpAll = false; + +int g_nDXLevel = 0; // default dxlevel if you don't specify it on the command-line. +CUtlVector g_SkyAreas; +char outbase[32]; + +// HLTOOLS: Introduce these calcs to make the block algorithm proportional to the proper +// world coordinate extents. Assumes square spatial constraints. +#define BLOCKS_SIZE 1024 +#define BLOCKS_SPACE (COORD_EXTENT/BLOCKS_SIZE) +#define BLOCKX_OFFSET ((BLOCKS_SPACE/2)+1) +#define BLOCKY_OFFSET ((BLOCKS_SPACE/2)+1) +#define BLOCKS_MIN (-(BLOCKS_SPACE/2)) +#define BLOCKS_MAX ((BLOCKS_SPACE/2)-1) + +int block_xl = BLOCKS_MIN, block_xh = BLOCKS_MAX, block_yl = BLOCKS_MIN, block_yh = BLOCKS_MAX; + +int entity_num; + + +node_t *block_nodes[BLOCKS_SPACE+2][BLOCKS_SPACE+2]; + +//----------------------------------------------------------------------------- +// Assign occluder areas (must happen *after* the world model is processed) +//----------------------------------------------------------------------------- +void AssignOccluderAreas( tree_t *pTree ); +static void Compute3DSkyboxAreas( node_t *headnode, CUtlVector& areas ); + + +/* +============ +BlockTree + +============ +*/ +node_t *BlockTree (int xl, int yl, int xh, int yh) +{ + node_t *node; + Vector normal; + float dist; + int mid; + + if (xl == xh && yl == yh) + { + node = block_nodes[xl+BLOCKX_OFFSET][yl+BLOCKY_OFFSET]; + if (!node) + { // return an empty leaf + node = AllocNode (); + node->planenum = PLANENUM_LEAF; + node->contents = 0; //CONTENTS_SOLID; + return node; + } + return node; + } + + // create a seperator along the largest axis + node = AllocNode (); + + if (xh - xl > yh - yl) + { // split x axis + mid = xl + (xh-xl)/2 + 1; + normal[0] = 1; + normal[1] = 0; + normal[2] = 0; + dist = mid*BLOCKS_SIZE; + node->planenum = g_MainMap->FindFloatPlane (normal, dist); + node->children[0] = BlockTree ( mid, yl, xh, yh); + node->children[1] = BlockTree ( xl, yl, mid-1, yh); + } + else + { + mid = yl + (yh-yl)/2 + 1; + normal[0] = 0; + normal[1] = 1; + normal[2] = 0; + dist = mid*BLOCKS_SIZE; + node->planenum = g_MainMap->FindFloatPlane (normal, dist); + node->children[0] = BlockTree ( xl, mid, xh, yh); + node->children[1] = BlockTree ( xl, yl, xh, mid-1); + } + + return node; +} + +/* +============ +ProcessBlock_Thread + +============ +*/ +int brush_start, brush_end; +void ProcessBlock_Thread (int threadnum, int blocknum) +{ + int xblock, yblock; + Vector mins, maxs; + bspbrush_t *brushes; + tree_t *tree; + node_t *node; + + yblock = block_yl + blocknum / (block_xh-block_xl+1); + xblock = block_xl + blocknum % (block_xh-block_xl+1); + + qprintf ("############### block %2i,%2i ###############\n", xblock, yblock); + + mins[0] = xblock*BLOCKS_SIZE; + mins[1] = yblock*BLOCKS_SIZE; + mins[2] = MIN_COORD_INTEGER; + maxs[0] = (xblock+1)*BLOCKS_SIZE; + maxs[1] = (yblock+1)*BLOCKS_SIZE; + maxs[2] = MAX_COORD_INTEGER; + + // the makelist and chopbrushes could be cached between the passes... + brushes = MakeBspBrushList (brush_start, brush_end, mins, maxs, NO_DETAIL); + if (!brushes) + { + node = AllocNode (); + node->planenum = PLANENUM_LEAF; + node->contents = CONTENTS_SOLID; + block_nodes[xblock+BLOCKX_OFFSET][yblock+BLOCKY_OFFSET] = node; + return; + } + + FixupAreaportalWaterBrushes( brushes ); + if (!nocsg) + brushes = ChopBrushes (brushes); + + tree = BrushBSP (brushes, mins, maxs); + + block_nodes[xblock+BLOCKX_OFFSET][yblock+BLOCKY_OFFSET] = tree->headnode; +} + + +/* +============ +ProcessWorldModel + +============ +*/ +void SplitSubdividedFaces( node_t *headnode ); // garymcthack +void ProcessWorldModel (void) +{ + entity_t *e; + tree_t *tree = NULL; + qboolean leaked; + int optimize; + int start; + + e = &entities[entity_num]; + + brush_start = e->firstbrush; + brush_end = brush_start + e->numbrushes; + leaked = false; + + // + // perform per-block operations + // + if (block_xh * BLOCKS_SIZE > g_MainMap->map_maxs[0]) + { + block_xh = floor(g_MainMap->map_maxs[0]/BLOCKS_SIZE); + } + if ( (block_xl+1) * BLOCKS_SIZE < g_MainMap->map_mins[0]) + { + block_xl = floor(g_MainMap->map_mins[0]/BLOCKS_SIZE); + } + if (block_yh * BLOCKS_SIZE > g_MainMap->map_maxs[1]) + { + block_yh = floor(g_MainMap->map_maxs[1]/BLOCKS_SIZE); + } + if ( (block_yl+1) * BLOCKS_SIZE < g_MainMap->map_mins[1]) + { + block_yl = floor(g_MainMap->map_mins[1]/BLOCKS_SIZE); + } + + // HLTOOLS: updated to +/- MAX_COORD_INTEGER ( new world size limits / worldsize.h ) + if (block_xl < BLOCKS_MIN) + { + block_xl = BLOCKS_MIN; + } + if (block_yl < BLOCKS_MIN) + { + block_yl = BLOCKS_MIN; + } + if (block_xh > BLOCKS_MAX) + { + block_xh = BLOCKS_MAX; + } + if (block_yh > BLOCKS_MAX) + { + block_yh = BLOCKS_MAX; + } + + for (optimize = 0 ; optimize <= 1 ; optimize++) + { + qprintf ("--------------------------------------------\n"); + + RunThreadsOnIndividual ((block_xh-block_xl+1)*(block_yh-block_yl+1), + !verbose, ProcessBlock_Thread); + + // + // build the division tree + // oversizing the blocks guarantees that all the boundaries + // will also get nodes. + // + + qprintf ("--------------------------------------------\n"); + + tree = AllocTree (); + tree->headnode = BlockTree (block_xl-1, block_yl-1, block_xh+1, block_yh+1); + + tree->mins[0] = (block_xl)*BLOCKS_SIZE; + tree->mins[1] = (block_yl)*BLOCKS_SIZE; + tree->mins[2] = g_MainMap->map_mins[2] - 8; + + tree->maxs[0] = (block_xh+1)*BLOCKS_SIZE; + tree->maxs[1] = (block_yh+1)*BLOCKS_SIZE; + tree->maxs[2] = g_MainMap->map_maxs[2] + 8; + + // + // perform the global operations + // + + // make the portals/faces by traversing down to each empty leaf + MakeTreePortals (tree); + + if (FloodEntities (tree)) + { + // turns everthing outside into solid + FillOutside (tree->headnode); + } + else + { + Warning( ("**** leaked ****\n") ); + leaked = true; + LeakFile (tree); + if (leaktest) + { + Warning( ("--- MAP LEAKED ---\n") ); + exit (0); + } + } + + // mark the brush sides that actually turned into faces + MarkVisibleSides (tree, brush_start, brush_end, NO_DETAIL); + if (noopt || leaked) + break; + if (!optimize) + { + // If we are optimizing, free the tree. Next time we will construct it again, but + // we'll use the information in MarkVisibleSides() so we'll only split with planes that + // actually contribute renderable geometry + FreeTree (tree); + } + } + + FloodAreas (tree); + + RemoveAreaPortalBrushes_R( tree->headnode ); + + start = Plat_FloatTime(); + Msg("Building Faces..."); + // this turns portals with one solid side into faces + // it also subdivides each face if necessary to fit max lightmap dimensions + MakeFaces (tree->headnode); + Msg("done (%d)\n", (int)(Plat_FloatTime() - start) ); + + if (glview) + { + WriteGLView (tree, source); + } + + AssignOccluderAreas( tree ); + Compute3DSkyboxAreas( tree->headnode, g_SkyAreas ); + face_t *pLeafFaceList = NULL; + if ( !nodetail ) + { + pLeafFaceList = MergeDetailTree( tree, brush_start, brush_end ); + } + + start = Plat_FloatTime(); + + Msg("FixTjuncs...\n"); + + // This unifies the vertex list for all edges (splits collinear edges to remove t-junctions) + // It also welds the list of vertices out of each winding/portal and rounds nearly integer verts to integer + pLeafFaceList = FixTjuncs (tree->headnode, pLeafFaceList); + + // this merges all of the solid nodes that have separating planes + if (!noprune) + { + Msg("PruneNodes...\n"); + PruneNodes (tree->headnode); + } + +// Msg( "SplitSubdividedFaces...\n" ); +// SplitSubdividedFaces( tree->headnode ); + + Msg("WriteBSP...\n"); + WriteBSP (tree->headnode, pLeafFaceList); + Msg("done (%d)\n", (int)(Plat_FloatTime() - start) ); + + if (!leaked) + { + WritePortalFile (tree); + } + + FreeTree( tree ); + FreeLeafFaces( pLeafFaceList ); +} + +/* +============ +ProcessSubModel + +============ +*/ +void ProcessSubModel( ) +{ + entity_t *e; + int start, end; + tree_t *tree; + bspbrush_t *list; + Vector mins, maxs; + + e = &entities[entity_num]; + + start = e->firstbrush; + end = start + e->numbrushes; + + mins[0] = mins[1] = mins[2] = MIN_COORD_INTEGER; + maxs[0] = maxs[1] = maxs[2] = MAX_COORD_INTEGER; + list = MakeBspBrushList (start, end, mins, maxs, FULL_DETAIL); + + if (!nocsg) + list = ChopBrushes (list); + tree = BrushBSP (list, mins, maxs); + + // This would wind up crashing the engine because we'd have a negative leaf index in dmodel_t::headnode. + if ( tree->headnode->planenum == PLANENUM_LEAF ) + { + const char *pClassName = ValueForKey( e, "classname" ); + const char *pTargetName = ValueForKey( e, "targetname" ); + Error( "bmodel %d has no head node (class '%s', targetname '%s')", entity_num, pClassName, pTargetName ); + } + + MakeTreePortals (tree); + +#if DEBUG_BRUSHMODEL + if ( entity_num == DEBUG_BRUSHMODEL ) + WriteGLView( tree, "tree_all" ); +#endif + + MarkVisibleSides (tree, start, end, FULL_DETAIL); + MakeFaces (tree->headnode); + + FixTjuncs( tree->headnode, NULL ); + WriteBSP( tree->headnode, NULL ); + +#if DEBUG_BRUSHMODEL + if ( entity_num == DEBUG_BRUSHMODEL ) + { + WriteGLView( tree, "tree_vis" ); + WriteGLViewFaces( tree, "tree_faces" ); + } +#endif + + FreeTree (tree); +} + + +//----------------------------------------------------------------------------- +// Returns true if the entity is a func_occluder +//----------------------------------------------------------------------------- +bool IsFuncOccluder( int entity_num ) +{ + entity_t *mapent = &entities[entity_num]; + const char *pClassName = ValueForKey( mapent, "classname" ); + return (strcmp("func_occluder", pClassName) == 0); +} + + +//----------------------------------------------------------------------------- +// Computes the area of a brush's occluders +//----------------------------------------------------------------------------- +float ComputeOccluderBrushArea( mapbrush_t *pBrush ) +{ + float flArea = 0.0f; + for ( int j = 0; j < pBrush->numsides; ++j ) + { + side_t *pSide = &(pBrush->original_sides[j]); + + // Skip nodraw surfaces + if ( texinfo[pSide->texinfo].flags & SURF_NODRAW ) + continue; + + if ( !pSide->winding ) + continue; + + flArea += WindingArea( pSide->winding ); + } + + return flArea; +} + + +//----------------------------------------------------------------------------- +// Clips all occluder brushes against each other +//----------------------------------------------------------------------------- +static tree_t *ClipOccluderBrushes( ) +{ + // Create a list of all occluder brushes in the level + CUtlVector< mapbrush_t * > mapBrushes( 1024, 1024 ); + for ( entity_num=0; entity_num < g_MainMap->num_entities; ++entity_num ) + { + if (!IsFuncOccluder(entity_num)) + continue; + + entity_t *e = &entities[entity_num]; + int end = e->firstbrush + e->numbrushes; + int i; + for ( i = e->firstbrush; i < end; ++i ) + { + mapBrushes.AddToTail( &g_MainMap->mapbrushes[i] ); + } + } + + int nBrushCount = mapBrushes.Count(); + if ( nBrushCount == 0 ) + return NULL; + + Vector mins, maxs; + mins[0] = mins[1] = mins[2] = MIN_COORD_INTEGER; + maxs[0] = maxs[1] = maxs[2] = MAX_COORD_INTEGER; + + bspbrush_t *list = MakeBspBrushList( mapBrushes.Base(), nBrushCount, mins, maxs ); + + if (!nocsg) + list = ChopBrushes (list); + tree_t *tree = BrushBSP (list, mins, maxs); + MakeTreePortals (tree); + MarkVisibleSides (tree, mapBrushes.Base(), nBrushCount); + MakeFaces( tree->headnode ); + + // NOTE: This will output the occluder face vertices + planes + FixTjuncs( tree->headnode, NULL ); + + return tree; +} + + +//----------------------------------------------------------------------------- +// Generate a list of unique sides in the occluder tree +//----------------------------------------------------------------------------- +static void GenerateOccluderSideList( int nEntity, CUtlVector &occluderSides ) +{ + entity_t *e = &entities[nEntity]; + int end = e->firstbrush + e->numbrushes; + int i, j; + for ( i = e->firstbrush; i < end; ++i ) + { + mapbrush_t *mb = &g_MainMap->mapbrushes[i]; + for ( j = 0; j < mb->numsides; ++j ) + { + occluderSides.AddToTail( &(mb->original_sides[j]) ); + } + } +} + + +//----------------------------------------------------------------------------- +// Generate a list of unique faces in the occluder tree +//----------------------------------------------------------------------------- +static void GenerateOccluderFaceList( node_t *pOccluderNode, CUtlVector &occluderFaces ) +{ + if (pOccluderNode->planenum == PLANENUM_LEAF) + return; + + for ( face_t *f=pOccluderNode->faces ; f ; f = f->next ) + { + occluderFaces.AddToTail( f ); + } + + GenerateOccluderFaceList( pOccluderNode->children[0], occluderFaces ); + GenerateOccluderFaceList( pOccluderNode->children[1], occluderFaces ); +} + + +//----------------------------------------------------------------------------- +// For occluder area assignment +//----------------------------------------------------------------------------- +struct OccluderInfo_t +{ + int m_nOccluderEntityIndex; +}; + +static CUtlVector< OccluderInfo_t > g_OccluderInfo; + + +//----------------------------------------------------------------------------- +// Emits occluder brushes +//----------------------------------------------------------------------------- +static void EmitOccluderBrushes() +{ + char str[64]; + + g_OccluderData.RemoveAll(); + g_OccluderPolyData.RemoveAll(); + g_OccluderVertexIndices.RemoveAll(); + + tree_t *pOccluderTree = ClipOccluderBrushes(); + if (!pOccluderTree) + return; + + CUtlVector faceList( 1024, 1024 ); + CUtlVector sideList( 1024, 1024 ); + GenerateOccluderFaceList( pOccluderTree->headnode, faceList ); + +#ifdef _DEBUG + int *pEmitted = (int*)stackalloc( faceList.Count() * sizeof(int) ); + memset( pEmitted, 0, faceList.Count() * sizeof(int) ); +#endif + + for ( entity_num=1; entity_num < num_entities; ++entity_num ) + { + if (!IsFuncOccluder(entity_num)) + continue; + + // Output only those parts of the occluder tree which are a part of the brush + int nOccluder = g_OccluderData.AddToTail(); + doccluderdata_t &occluderData = g_OccluderData[ nOccluder ]; + occluderData.firstpoly = g_OccluderPolyData.Count(); + occluderData.mins.Init( FLT_MAX, FLT_MAX, FLT_MAX ); + occluderData.maxs.Init( -FLT_MAX, -FLT_MAX, -FLT_MAX ); + occluderData.flags = 0; + occluderData.area = -1; + + // NOTE: If you change the algorithm by which occluder numbers are allocated, + // then you must also change FixupOnlyEntsOccluderEntities() below + sprintf (str, "%i", nOccluder); + SetKeyValue (&entities[entity_num], "occludernumber", str); + + int nIndex = g_OccluderInfo.AddToTail(); + g_OccluderInfo[nIndex].m_nOccluderEntityIndex = entity_num; + + sideList.RemoveAll(); + GenerateOccluderSideList( entity_num, sideList ); + for ( int i = faceList.Count(); --i >= 0; ) + { + // Skip nodraw surfaces, but not triggers that have been marked as nodraw + face_t *f = faceList[i]; + if ( ( texinfo[f->texinfo].flags & SURF_NODRAW ) && + (( texinfo[f->texinfo].flags & SURF_TRIGGER ) == 0 ) ) + continue; + + // Only emit faces that appear in the side list of the occluder + for ( int j = sideList.Count(); --j >= 0; ) + { + if ( sideList[j] != f->originalface ) + continue; + + if ( f->numpoints < 3 ) + continue; + + // not a final face + Assert ( !f->merged && !f->split[0] && !f->split[1] ); + +#ifdef _DEBUG + Assert( !pEmitted[i] ); + pEmitted[i] = entity_num; +#endif + + int k = g_OccluderPolyData.AddToTail(); + doccluderpolydata_t *pOccluderPoly = &g_OccluderPolyData[k]; + + pOccluderPoly->planenum = f->planenum; + pOccluderPoly->vertexcount = f->numpoints; + pOccluderPoly->firstvertexindex = g_OccluderVertexIndices.Count(); + for( k = 0; k < f->numpoints; ++k ) + { + g_OccluderVertexIndices.AddToTail( f->vertexnums[k] ); + + const Vector &p = dvertexes[f->vertexnums[k]].point; + VectorMin( occluderData.mins, p, occluderData.mins ); + VectorMax( occluderData.maxs, p, occluderData.maxs ); + } + + break; + } + } + + occluderData.polycount = g_OccluderPolyData.Count() - occluderData.firstpoly; + + // Mark this brush as not having brush geometry so it won't be re-emitted with a brush model + entities[entity_num].numbrushes = 0; + } + + FreeTree( pOccluderTree ); +} + + +//----------------------------------------------------------------------------- +// Set occluder area +//----------------------------------------------------------------------------- +void SetOccluderArea( int nOccluder, int nArea, int nEntityNum ) +{ + if ( g_OccluderData[nOccluder].area <= 0 ) + { + g_OccluderData[nOccluder].area = nArea; + } + else if ( (nArea != 0) && (g_OccluderData[nOccluder].area != nArea) ) + { + const char *pTargetName = ValueForKey( &entities[nEntityNum], "targetname" ); + if (!pTargetName) + { + pTargetName = ""; + } + Warning("Occluder \"%s\" straddles multiple areas. This is invalid!\n", pTargetName ); + } +} + + +//----------------------------------------------------------------------------- +// Assign occluder areas (must happen *after* the world model is processed) +//----------------------------------------------------------------------------- +void AssignAreaToOccluder( int nOccluder, tree_t *pTree, bool bCrossAreaPortals ) +{ + int nFirstPoly = g_OccluderData[nOccluder].firstpoly; + int nEntityNum = g_OccluderInfo[nOccluder].m_nOccluderEntityIndex; + for ( int j = 0; j < g_OccluderData[nOccluder].polycount; ++j ) + { + doccluderpolydata_t *pOccluderPoly = &g_OccluderPolyData[nFirstPoly + j]; + int nFirstVertex = pOccluderPoly->firstvertexindex; + for ( int k = 0; k < pOccluderPoly->vertexcount; ++k ) + { + int nVertexIndex = g_OccluderVertexIndices[nFirstVertex + k]; + node_t *pNode = NodeForPoint( pTree->headnode, dvertexes[ nVertexIndex ].point ); + + SetOccluderArea( nOccluder, pNode->area, nEntityNum ); + + int nOtherSideIndex; + portal_t *pPortal; + for ( pPortal = pNode->portals; pPortal; pPortal = pPortal->next[!nOtherSideIndex] ) + { + nOtherSideIndex = (pPortal->nodes[0] == pNode) ? 1 : 0; + if (!pPortal->onnode) + continue; // edge of world + + // Don't cross over area portals for the area check + if ((!bCrossAreaPortals) && pPortal->nodes[nOtherSideIndex]->contents & CONTENTS_AREAPORTAL) + continue; + + int nAdjacentArea = pPortal->nodes[nOtherSideIndex] ? pPortal->nodes[nOtherSideIndex]->area : 0; + SetOccluderArea( nOccluder, nAdjacentArea, nEntityNum ); + } + } + } +} + + +//----------------------------------------------------------------------------- +// Assign occluder areas (must happen *after* the world model is processed) +//----------------------------------------------------------------------------- +void AssignOccluderAreas( tree_t *pTree ) +{ + for ( int i = 0; i < g_OccluderData.Count(); ++i ) + { + AssignAreaToOccluder( i, pTree, false ); + + // This can only have happened if the only valid portal out leads into an areaportal + if ( g_OccluderData[i].area <= 0 ) + { + AssignAreaToOccluder( i, pTree, true ); + } + } +} + + + +//----------------------------------------------------------------------------- +// Make sure the func_occluders have the appropriate data set +//----------------------------------------------------------------------------- +void FixupOnlyEntsOccluderEntities() +{ + char str[64]; + int nOccluder = 0; + for ( entity_num=1; entity_num < num_entities; ++entity_num ) + { + if (!IsFuncOccluder(entity_num)) + continue; + + // NOTE: If you change the algorithm by which occluder numbers are allocated above, + // then you must also change this + sprintf (str, "%i", nOccluder); + SetKeyValue (&entities[entity_num], "occludernumber", str); + ++nOccluder; + } +} + + +void MarkNoDynamicShadowSides() +{ + for ( int iSide=0; iSide < g_MainMap->nummapbrushsides; iSide++ ) + { + g_MainMap->brushsides[iSide].m_bDynamicShadowsEnabled = true; + } + + for ( int i=0; i < g_NoDynamicShadowSides.Count(); i++ ) + { + int brushSideID = g_NoDynamicShadowSides[i]; + + // Find the side with this ID. + for ( int iSide=0; iSide < g_MainMap->nummapbrushsides; iSide++ ) + { + if ( g_MainMap->brushsides[iSide].id == brushSideID ) + g_MainMap->brushsides[iSide].m_bDynamicShadowsEnabled = false; + } + } +} + +//----------------------------------------------------------------------------- +// Compute the 3D skybox areas +//----------------------------------------------------------------------------- +static void Compute3DSkyboxAreas( node_t *headnode, CUtlVector& areas ) +{ + for (int i = 0; i < g_MainMap->num_entities; ++i) + { + char* pEntity = ValueForKey(&entities[i], "classname"); + if (!strcmp(pEntity, "sky_camera")) + { + // Found a 3D skybox camera, get a leaf that lies in it + node_t *pLeaf = PointInLeaf( headnode, entities[i].origin ); + if (pLeaf->contents & CONTENTS_SOLID) + { + Error ("Error! Entity sky_camera in solid volume! at %.1f %.1f %.1f\n", entities[i].origin.x, entities[i].origin.y, entities[i].origin.z); + } + areas.AddToTail( pLeaf->area ); + } + } +} + +bool Is3DSkyboxArea( int area ) +{ + for ( int i = g_SkyAreas.Count(); --i >=0; ) + { + if ( g_SkyAreas[i] == area ) + return true; + } + return false; +} + + +/* +============ +ProcessModels +============ +*/ +void ProcessModels (void) +{ + BeginBSPFile (); + + // Mark sides that have no dynamic shadows. + MarkNoDynamicShadowSides(); + + // emit the displacement surfaces + EmitInitialDispInfos(); + + // Clip occluder brushes against each other, + // Remove them from the list of models to process below + EmitOccluderBrushes( ); + + for ( entity_num=0; entity_num < num_entities; ++entity_num ) + { + entity_t *pEntity = &entities[entity_num]; + if ( !pEntity->numbrushes ) + continue; + + qprintf ("############### model %i ###############\n", nummodels); + + BeginModel (); + + if (entity_num == 0) + { + ProcessWorldModel(); + } + else + { + ProcessSubModel( ); + } + + EndModel (); + + if (!verboseentities) + { + verbose = false; // don't bother printing submodels + } + } + + // Turn the skybox into a cubemap in case we don't build env_cubemap textures. + Cubemap_CreateDefaultCubemaps(); + EndBSPFile (); +} + + +void LoadPhysicsDLL( void ) +{ + PhysicsDLLPath( "vphysics.dll" ); +} + + +void PrintCommandLine( int argc, char **argv ) +{ + Warning( "Command line: " ); + for ( int z=0; z < argc; z++ ) + { + Warning( "\"%s\" ", argv[z] ); + } + Warning( "\n\n" ); +} + + +int RunVBSP( int argc, char **argv ) +{ + int i; + double start, end; + char path[1024]; + + CommandLine()->CreateCmdLine( argc, argv ); + MathLib_Init( 2.2f, 2.2f, 0.0f, OVERBRIGHT, false, false, false, false ); + InstallSpewFunction(); + SpewActivate( "developer", 1 ); + + CmdLib_InitFileSystem( argv[ argc-1 ] ); + + Q_StripExtension( ExpandArg( argv[ argc-1 ] ), source, sizeof( source ) ); + Q_FileBase( source, mapbase, sizeof( mapbase ) ); + strlwr( mapbase ); + + LoadCmdLineFromFile( argc, argv, mapbase, "vbsp" ); + + Msg( "Valve Software - vbsp.exe (%s)\n", __DATE__ ); + + for (i=1 ; i : Override the VPROJECT environment variable.\n" + " -game : Same as -vproject.\n" + "\n" ); + + if ( verbose ) + { + Warning( + "Other options :\n" + " -novconfig : Don't bring up graphical UI on vproject errors.\n" + " -threads : Control the number of threads vbsp uses (defaults to the # of\n" + " processors on your machine).\n" + " -verboseentities: If -v is on, this disables verbose output for submodels.\n" + " -noweld : Don't join face vertices together.\n" + " -nocsg : Don't chop out intersecting brush areas.\n" + " -noshare : Emit unique face edges instead of sharing them.\n" + " -notjunc : Don't fixup t-junctions.\n" + " -noopt : By default, vbsp removes the 'outer shell' of the map, which\n" + " are all the faces you can't see because you can never get\n" + " outside the map. -noopt disables this behaviour.\n" + " -noprune : Don't prune neighboring solid nodes.\n" + " -nomerge : Don't merge together chopped faces on nodes.\n" + " -nomergewater: Don't merge together chopped faces on water.\n" + " -nosubdiv : Don't subdivide faces for lightmapping.\n" + " -micro <#> : vbsp will warn when brushes are output with a volume less\n" + " than this number (default: 1.0).\n" + " -fulldetail : Mark all detail geometry as normal geometry (so all detail\n" + " geometry will affect visibility).\n" + " -leaktest : Stop processing the map if a leak is detected. Whether or not\n" + " this flag is set, a leak file will be written out at\n" + " .lin, and it can be imported into Hammer.\n" + " -bumpall : Force all surfaces to be bump mapped.\n" + " -snapaxial : Snap axial planes to integer coordinates.\n" + " -block # # : Control the grid size mins that vbsp chops the level on.\n" + " -blocks # # # # : Enter the mins and maxs for the grid size vbsp uses.\n" + " -dumpstaticprops: Dump static props to staticprop*.txt\n" + " -dumpcollide : Write files with collision info.\n" + " -forceskyvis : Enable vis calculations in 3d skybox leaves\n" + " -luxelscale # : Scale all lightmaps by this amount (default: 1.0).\n" + " -minluxelscale #: No luxel scale will be lower than this amount (default: 1.0).\n" + " -lightifmissing : Force lightmaps to be generated for all surfaces even if\n" + " they don't need lightmaps.\n" + " -keepstalezip : Keep the BSP's zip files intact but regenerate everything\n" + " else.\n" + " -virtualdispphysics : Use virtual (not precomputed) displacement collision models\n" + " -xbox : Enable mandatory xbox options\n" + " -x360 : Generate Xbox360 version of vsp\n" + " -nox360 : Disable generation Xbox360 version of vsp (default)\n" + " -replacematerials : Substitute materials according to materialsub.txt in content\\maps\n" + " -FullMinidumps : Write large minidumps on crash.\n" + ); + } + + DeleteCmdLine( argc, argv ); + CmdLib_Cleanup(); + CmdLib_Exit( 1 ); + } + + start = Plat_FloatTime(); + + // Run in the background? + if( g_bLowPriority ) + { + SetLowPriority(); + } + + if( ( g_nDXLevel != 0 ) && ( g_nDXLevel < 80 ) ) + { + g_BumpAll = false; + } + + if( g_luxelScale == 1.0f ) + { + if ( g_nDXLevel == 70 ) + { + g_luxelScale = 4.0f; + } + } + + ThreadSetDefault (); + numthreads = 1; // multiple threads aren't helping... + + // Setup the logfile. + char logFile[512]; + _snprintf( logFile, sizeof(logFile), "%s.log", source ); + SetSpewFunctionLogFile( logFile ); + + LoadPhysicsDLL(); + LoadSurfaceProperties(); + +#if 0 + Msg( "qdir: %s This is the the path of the initial source file \n", qdir ); + Msg( "gamedir: %s This is the base engine + mod-specific game dir (e.g. d:/tf2/mytfmod/) \n", gamedir ); + Msg( "basegamedir: %s This is the base engine + base game directory (e.g. e:/hl2/hl2/, or d:/tf2/tf2/ )\n", basegamedir ); +#endif + + sprintf( materialPath, "%smaterials", gamedir ); + InitMaterialSystem( materialPath, CmdLib_GetFileSystemFactory() ); + Msg( "materialPath: %s\n", materialPath ); + + // delete portal and line files + sprintf (path, "%s.prt", source); + remove (path); + sprintf (path, "%s.lin", source); + remove (path); + + strcpy (name, ExpandArg (argv[i])); + + const char *pszExtension = V_GetFileExtension( name ); + if ( !pszExtension ) + { + V_SetExtension( name, ".vmm", sizeof( name ) ); + if ( !FileExists( name ) ) + { + V_SetExtension( name, ".vmf", sizeof( name ) ); + } + } + + char platformBSPFileName[1024]; + GetPlatformMapPath( source, platformBSPFileName, g_nDXLevel, 1024 ); + + // if we're combining materials, load the script file + if ( g_ReplaceMaterials ) + { + LoadMaterialReplacementKeys( gamedir, mapbase ); + } + + // + // if onlyents, just grab the entites and resave + // + if (onlyents) + { + LoadBSPFile (platformBSPFileName); + num_entities = 0; + // Clear out the cubemap samples since they will be reparsed even with -onlyents + g_nCubemapSamples = 0; + + // Mark as stale since the lighting could be screwed with new ents. + AddBufferToPak( GetPakFile(), "stale.txt", "stale", strlen( "stale" ) + 1, false ); + + LoadMapFile (name); + SetModelNumbers (); + SetLightStyles (); + + // NOTE: If we ever precompute lighting for static props in + // vrad, EmitStaticProps should be removed here + + // Emit static props found in the .vmf file + EmitStaticProps(); + + // NOTE: Don't deal with detail props here, it blows away lighting + + // Recompute the skybox + ComputeBoundsNoSkybox(); + + // Make sure that we have a water lod control eneity if we have water in the map. + EnsurePresenceOfWaterLODControlEntity(); + + // Make sure the func_occluders have the appropriate data set + FixupOnlyEntsOccluderEntities(); + + // Doing this here because stuff abov may filter out entities + UnparseEntities (); + + WriteBSPFile (platformBSPFileName); + } + else if (onlyprops) + { + // In the only props case, deal with static + detail props only + LoadBSPFile (platformBSPFileName); + + LoadMapFile(name); + SetModelNumbers(); + SetLightStyles(); + + // Emit static props found in the .vmf file + EmitStaticProps(); + + // Place detail props found in .vmf and based on material properties + LoadEmitDetailObjectDictionary( gamedir ); + EmitDetailObjects(); + + WriteBSPFile (platformBSPFileName); + } + else + { + // + // start from scratch + // + + // Load just the file system from the bsp + if( g_bKeepStaleZip && FileExists( platformBSPFileName ) ) + { + LoadBSPFile_FileSystemOnly (platformBSPFileName); + // Mark as stale since the lighting could be screwed with new ents. + AddBufferToPak( GetPakFile(), "stale.txt", "stale", strlen( "stale" ) + 1, false ); + } + + LoadMapFile (name); + WorldVertexTransitionFixup(); + if( ( g_nDXLevel == 0 ) || ( g_nDXLevel >= 70 ) ) + { + Cubemap_FixupBrushSidesMaterials(); + Cubemap_AttachDefaultCubemapToSpecularSides(); + Cubemap_AddUnreferencedCubemaps(); + } + SetModelNumbers (); + SetLightStyles (); + LoadEmitDetailObjectDictionary( gamedir ); + ProcessModels (); + } + + end = Plat_FloatTime(); + + char str[512]; + GetHourMinuteSecondsString( (int)( end - start ), str, sizeof( str ) ); + Msg( "%s elapsed\n", str ); + + DeleteCmdLine( argc, argv ); + ReleasePakFileLumps(); + DeleteMaterialReplacementKeys(); + ShutdownMaterialSystem(); + CmdLib_Cleanup(); + return 0; +} + + +/* +============= +main +============ +*/ +int main (int argc, char **argv) +{ + // Install an exception handler. + SetupDefaultToolsMinidumpHandler(); + return RunVBSP( argc, argv ); +} + + diff --git a/mp/src/utils/vbsp/vbsp.h b/mp/src/utils/vbsp/vbsp.h new file mode 100644 index 00000000..9b994d38 --- /dev/null +++ b/mp/src/utils/vbsp/vbsp.h @@ -0,0 +1,657 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +#if !defined( VBSP_H ) +#define VBSP_H + + +#include "cmdlib.h" +#include "mathlib/vector.h" +#include "scriplib.h" +#include "polylib.h" +#include "threads.h" +#include "bsplib.h" +#include "qfiles.h" +#include "utilmatlib.h" +#include "ChunkFile.h" + +#ifdef WIN32 +#pragma warning( disable: 4706 ) +#endif + +class CUtlBuffer; + +#define MAX_BRUSH_SIDES 128 +#define CLIP_EPSILON 0.1 + +#define TEXINFO_NODE -1 // side is allready on a node + +// this will output glview files for the given brushmodel. Brushmodel 1 is the world, 2 is the first brush entity, etc. +#define DEBUG_BRUSHMODEL 0 + +struct portal_t; +struct node_t; + +struct plane_t : public dplane_t +{ + plane_t *hash_chain; + + plane_t() { normal.Init(); } +}; + + +struct brush_texture_t +{ + Vector UAxis; + Vector VAxis; + vec_t shift[2]; + vec_t rotate; + vec_t textureWorldUnitsPerTexel[2]; + vec_t lightmapWorldUnitsPerLuxel; + char name[TEXTURE_NAME_LENGTH]; + int flags; + + brush_texture_t() : UAxis(0,0,0), VAxis(0,0,0) {} +}; + +struct mapdispinfo_t; + +struct side_t +{ + int planenum; + int texinfo; + mapdispinfo_t *pMapDisp; + + winding_t *winding; + side_t *original; // bspbrush_t sides will reference the mapbrush_t sides + int contents; // from miptex + int surf; // from miptex + qboolean visible; // choose visble planes first + qboolean tested; // this plane allready checked as a split + qboolean bevel; // don't ever use for bsp splitting + + side_t *next; + int origIndex; + int id; // This is the unique id generated by worldcraft for this side. + unsigned int smoothingGroups; + CUtlVector aOverlayIds; // List of overlays that reside on this side. + CUtlVector aWaterOverlayIds; // List of water overlays that reside on this side. + bool m_bDynamicShadowsEnabled; // Goes into dface_t::SetDynamicShadowsEnabled(). +}; + +struct mapbrush_t +{ + int entitynum; + int brushnum; + int id; // The unique ID of this brush in the editor, used for reporting errors. + int contents; + Vector mins, maxs; + int numsides; + side_t *original_sides; +}; + +#define PLANENUM_LEAF -1 + +#define MAXEDGES 32 + +struct face_t +{ + int id; + + face_t *next; // on node + + // the chain of faces off of a node can be merged or split, + // but each face_t along the way will remain in the chain + // until the entire tree is freed + face_t *merged; // if set, this face isn't valid anymore + face_t *split[2]; // if set, this face isn't valid anymore + + portal_t *portal; + int texinfo; + int dispinfo; + // This is only for surfaces that are the boundaries of fog volumes + // (ie. water surfaces) + // All of the rest of the surfaces can look at their leaf to find out + // what fog volume they are in. + node_t *fogVolumeLeaf; + + int planenum; + int contents; // faces in different contents can't merge + int outputnumber; + winding_t *w; + int numpoints; + qboolean badstartvert; // tjunctions cannot be fixed without a midpoint vertex + int vertexnums[MAXEDGES]; + side_t *originalface; // save the "side" this face came from + int firstPrimID; + int numPrims; + unsigned int smoothingGroups; +}; + +void EmitFace( face_t *f, qboolean onNode ); + +struct mapdispinfo_t +{ + face_t face; + int entitynum; + int power; + int minTess; + float smoothingAngle; + Vector uAxis; + Vector vAxis; + Vector startPosition; + float alphaValues[MAX_DISPVERTS]; + float maxDispDist; + float dispDists[MAX_DISPVERTS]; + Vector vectorDisps[MAX_DISPVERTS]; + Vector vectorOffsets[MAX_DISPVERTS]; + int contents; + int brushSideID; + unsigned short triTags[MAX_DISPTRIS]; + int flags; + +#ifdef VSVMFIO + float m_elevation; // "elevation" + Vector m_offsetNormals[ MAX_DISPTRIS ]; // "offset_normals" +#endif // VSVMFIO + +}; + +extern int nummapdispinfo; +extern mapdispinfo_t mapdispinfo[MAX_MAP_DISPINFO]; + +extern float g_defaultLuxelSize; +extern float g_luxelScale; +extern float g_minLuxelScale; +extern bool g_BumpAll; +extern int g_nDXLevel; + +int GetDispInfoEntityNum( mapdispinfo_t *pDisp ); +void ComputeBoundsNoSkybox( ); + +struct bspbrush_t +{ + int id; + bspbrush_t *next; + Vector mins, maxs; + int side, testside; // side of node during construction + mapbrush_t *original; + int numsides; + side_t sides[6]; // variably sized +}; + + +#define MAX_NODE_BRUSHES 8 + +struct leafface_t +{ + face_t *pFace; + leafface_t *pNext; +}; + +struct node_t +{ + int id; + + // both leafs and nodes + int planenum; // -1 = leaf node + node_t *parent; + Vector mins, maxs; // valid after portalization + bspbrush_t *volume; // one for each leaf/node + + // nodes only + side_t *side; // the side that created the node + node_t *children[2]; + face_t *faces; // these are the cutup ones that live in the plane of "side". + + // leafs only + bspbrush_t *brushlist; // fragments of all brushes in this leaf + leafface_t *leaffacelist; + int contents; // OR of all brush contents + int occupied; // 1 or greater can reach entity + entity_t *occupant; // for leak file testing + int cluster; // for portalfile writing + int area; // for areaportals + portal_t *portals; // also on nodes during construction + int diskId; // dnodes or dleafs index after this has been emitted +}; + + +struct portal_t +{ + int id; + plane_t plane; + node_t *onnode; // NULL = outside box + node_t *nodes[2]; // [0] = front side of plane + portal_t *next[2]; + winding_t *winding; + qboolean sidefound; // false if ->side hasn't been checked + side_t *side; // NULL = non-visible + face_t *face[2]; // output face in bsp file +}; + + +struct tree_t +{ + node_t *headnode; + node_t outside_node; + Vector mins, maxs; + bool leaked; +}; + + +extern int entity_num; + +struct LoadSide_t; +struct LoadEntity_t; +class CManifest; + +class CMapFile +{ +public: + CMapFile( void ) { Init(); } + + void Init( void ); + + void AddPlaneToHash (plane_t *p); + int CreateNewFloatPlane (Vector& normal, vec_t dist); + int FindFloatPlane (Vector& normal, vec_t dist); + int PlaneFromPoints(const Vector &p0, const Vector &p1, const Vector &p2); + void AddBrushBevels (mapbrush_t *b); + qboolean MakeBrushWindings (mapbrush_t *ob); + void MoveBrushesToWorld( entity_t *mapent ); + void MoveBrushesToWorldGeneral( entity_t *mapent ); + void RemoveContentsDetailFromEntity( entity_t *mapent ); + int SideIDToIndex( int brushSideID ); + void AddLadderKeys( entity_t *mapent ); + ChunkFileResult_t LoadEntityCallback(CChunkFile *pFile, int nParam); + void ForceFuncAreaPortalWindowContents(); + ChunkFileResult_t LoadSideCallback(CChunkFile *pFile, LoadSide_t *pSideInfo); + ChunkFileResult_t LoadConnectionsKeyCallback(const char *szKey, const char *szValue, LoadEntity_t *pLoadEntity); + ChunkFileResult_t LoadSolidCallback(CChunkFile *pFile, LoadEntity_t *pLoadEntity); + void TestExpandBrushes(void); + + static char m_InstancePath[ MAX_PATH ]; + static void SetInstancePath( const char *pszInstancePath ); + static const char *GetInstancePath( void ) { return m_InstancePath; } + static bool DeterminePath( const char *pszBaseFileName, const char *pszInstanceFileName, char *pszOutFileName ); + + void CheckForInstances( const char *pszFileName ); + void MergeInstance( entity_t *pInstanceEntity, CMapFile *Instance ); + void MergePlanes( entity_t *pInstanceEntity, CMapFile *Instance, Vector &InstanceOrigin, QAngle &InstanceAngle, matrix3x4_t &InstanceMatrix ); + void MergeBrushes( entity_t *pInstanceEntity, CMapFile *Instance, Vector &InstanceOrigin, QAngle &InstanceAngle, matrix3x4_t &InstanceMatrix ); + void MergeBrushSides( entity_t *pInstanceEntity, CMapFile *Instance, Vector &InstanceOrigin, QAngle &InstanceAngle, matrix3x4_t &InstanceMatrix ); + void ReplaceInstancePair( epair_t *pPair, entity_t *pInstanceEntity ); + void MergeEntities( entity_t *pInstanceEntity, CMapFile *Instance, Vector &InstanceOrigin, QAngle &InstanceAngle, matrix3x4_t &InstanceMatrix ); + void MergeOverlays( entity_t *pInstanceEntity, CMapFile *Instance, Vector &InstanceOrigin, QAngle &InstanceAngle, matrix3x4_t &InstanceMatrix ); + + static int m_InstanceCount; + static int c_areaportals; + + plane_t mapplanes[MAX_MAP_PLANES]; + int nummapplanes; + + #define PLANE_HASHES 1024 + plane_t *planehash[PLANE_HASHES]; + + int nummapbrushes; + mapbrush_t mapbrushes[MAX_MAP_BRUSHES]; + + Vector map_mins, map_maxs; + + int nummapbrushsides; + side_t brushsides[MAX_MAP_BRUSHSIDES]; + + brush_texture_t side_brushtextures[MAX_MAP_BRUSHSIDES]; + + int num_entities; + entity_t entities[MAX_MAP_ENTITIES]; + + int c_boxbevels; + int c_edgebevels; + int c_clipbrushes; + int g_ClipTexinfo; + + class CConnectionPairs + { + public: + CConnectionPairs( epair_t *pair, CConnectionPairs *next ) + { + m_Pair = pair; + m_Next = next; + } + + epair_t *m_Pair; + CConnectionPairs *m_Next; + }; + + CConnectionPairs *m_ConnectionPairs; + + int m_StartMapOverlays; + int m_StartMapWaterOverlays; +}; + +extern CMapFile *g_MainMap; +extern CMapFile *g_LoadingMap; + +extern CUtlVector< CMapFile * > g_Maps; + +extern int g_nMapFileVersion; + +extern qboolean noprune; +extern qboolean nodetail; +extern qboolean fulldetail; +extern qboolean nomerge; +extern qboolean nomergewater; +extern qboolean nosubdiv; +extern qboolean nowater; +extern qboolean noweld; +extern qboolean noshare; +extern qboolean notjunc; +extern qboolean nocsg; +extern qboolean noopt; +extern qboolean dumpcollide; +extern qboolean nodetailcuts; +extern qboolean g_DumpStaticProps; +extern qboolean g_bSkyVis; +extern vec_t microvolume; +extern bool g_snapAxialPlanes; +extern bool g_NodrawTriggers; +extern bool g_DisableWaterLighting; +extern bool g_bAllowDetailCracks; +extern bool g_bNoVirtualMesh; +extern char outbase[32]; + +extern char source[1024]; +extern char mapbase[ 64 ]; +extern CUtlVector g_SkyAreas; + +bool LoadMapFile( const char *pszFileName ); +int GetVertexnum( Vector& v ); +bool Is3DSkyboxArea( int area ); + +//============================================================================= + +// textures.c + +struct textureref_t +{ + char name[TEXTURE_NAME_LENGTH]; + int flags; + float lightmapWorldUnitsPerLuxel; + int contents; +}; + +extern textureref_t textureref[MAX_MAP_TEXTURES]; + +int FindMiptex (const char *name); + +int TexinfoForBrushTexture (plane_t *plane, brush_texture_t *bt, const Vector& origin); +int GetSurfaceProperties2( MaterialSystemMaterial_t matID, const char *pMatName ); + +extern int g_SurfaceProperties[MAX_MAP_TEXDATA]; +void LoadSurfaceProperties( void ); + +int PointLeafnum ( dmodel_t* pModel, const Vector& p ); + +//============================================================================= + +void FindGCD (int *v); + +mapbrush_t *Brush_LoadEntity (entity_t *ent); +int PlaneTypeForNormal (Vector& normal); +qboolean MakeBrushPlanes (mapbrush_t *b); +int FindIntPlane (int *inormal, int *iorigin); +void CreateBrush (int brushnum); + + +//============================================================================= +// detail objects +//============================================================================= + +void LoadEmitDetailObjectDictionary( char const* pGameDir ); +void EmitDetailObjects(); + +//============================================================================= +// static props +//============================================================================= + +void EmitStaticProps(); +bool LoadStudioModel( char const* pFileName, char const* pEntityType, CUtlBuffer& buf ); + +//============================================================================= +//============================================================================= +// procedurally created .vmt files +//============================================================================= + +void EmitStaticProps(); + +// draw.c + +extern Vector draw_mins, draw_maxs; +extern bool g_bLightIfMissing; + +void Draw_ClearWindow (void); +void DrawWinding (winding_t *w); + +void GLS_BeginScene (void); +void GLS_Winding (winding_t *w, int code); +void GLS_EndScene (void); + +//============================================================================= + +// csg + +enum detailscreen_e +{ + FULL_DETAIL = 0, + ONLY_DETAIL = 1, + NO_DETAIL = 2, +}; + +#define TRANSPARENT_CONTENTS (CONTENTS_GRATE|CONTENTS_WINDOW) + +#include "csg.h" + +//============================================================================= + +// brushbsp + +void WriteBrushList (char *name, bspbrush_t *brush, qboolean onlyvis); + +bspbrush_t *CopyBrush (bspbrush_t *brush); + +void SplitBrush (bspbrush_t *brush, int planenum, + bspbrush_t **front, bspbrush_t **back); + +tree_t *AllocTree (void); +node_t *AllocNode (void); +bspbrush_t *AllocBrush (int numsides); +int CountBrushList (bspbrush_t *brushes); +void FreeBrush (bspbrush_t *brushes); +vec_t BrushVolume (bspbrush_t *brush); +node_t *NodeForPoint (node_t *node, Vector& origin); + +void BoundBrush (bspbrush_t *brush); +void FreeBrushList (bspbrush_t *brushes); +node_t *PointInLeaf (node_t *node, Vector& point); + +tree_t *BrushBSP (bspbrush_t *brushlist, Vector& mins, Vector& maxs); + +#define PSIDE_FRONT 1 +#define PSIDE_BACK 2 +#define PSIDE_BOTH (PSIDE_FRONT|PSIDE_BACK) +#define PSIDE_FACING 4 +int BrushBspBoxOnPlaneSide (const Vector& mins, const Vector& maxs, dplane_t *plane); +extern qboolean WindingIsTiny (winding_t *w); + +//============================================================================= + +// portals.c + +int VisibleContents (int contents); + +void MakeHeadnodePortals (tree_t *tree); +void MakeNodePortal (node_t *node); +void SplitNodePortals (node_t *node); + +qboolean Portal_VisFlood (portal_t *p); + +qboolean FloodEntities (tree_t *tree); +void FillOutside (node_t *headnode); +void FloodAreas (tree_t *tree); +void MarkVisibleSides (tree_t *tree, int start, int end, int detailScreen); +void MarkVisibleSides (tree_t *tree, mapbrush_t **ppBrushes, int nCount ); +void FreePortal (portal_t *p); +void EmitAreaPortals (node_t *headnode); + +void MakeTreePortals (tree_t *tree); + +//============================================================================= + +// glfile.c + +void OutputWinding (winding_t *w, FileHandle_t glview); +void OutputWindingColor (winding_t *w, FileHandle_t glview, int r, int g, int b); +void WriteGLView (tree_t *tree, char *source); +void WriteGLViewFaces (tree_t *tree, const char *source); +void WriteGLViewBrushList( bspbrush_t *pList, const char *pName ); +//============================================================================= + +// leakfile.c + +void LeakFile (tree_t *tree); +void AreaportalLeakFile( tree_t *tree, portal_t *pStartPortal, portal_t *pEndPortal, node_t *pStart ); + +//============================================================================= + +// prtfile.c + +void AddVisCluster( entity_t *pFuncVisCluster ); +void WritePortalFile (tree_t *tree); + +//============================================================================= + +// writebsp.c + +void SetModelNumbers (void); +void SetLightStyles (void); + +void BeginBSPFile (void); +void WriteBSP (node_t *headnode, face_t *pLeafFaceList); +void EndBSPFile (void); +void BeginModel (void); +void EndModel (void); + +extern int firstmodeledge; +extern int firstmodelface; + +//============================================================================= + +// faces.c + +void MakeFaces (node_t *headnode); +void MakeDetailFaces (node_t *headnode); +face_t *FixTjuncs( node_t *headnode, face_t *pLeafFaceList ); + +face_t *AllocFace (void); +void FreeFace (face_t *f); +void FreeFaceList( face_t *pFaces ); + +void MergeFaceList(face_t **pFaceList); +void SubdivideFaceList(face_t **pFaceList); + +extern face_t *edgefaces[MAX_MAP_EDGES][2]; + + +//============================================================================= + +// tree.c + +void FreeTree (tree_t *tree); +void FreeTree_r (node_t *node); +void PrintTree_r (node_t *node, int depth); +void FreeTreePortals_r (node_t *node); +void PruneNodes_r (node_t *node); +void PruneNodes (node_t *node); + +// Returns true if the entity is a func_occluder +bool IsFuncOccluder( int entity_num ); + + +//============================================================================= +// ivp.cpp +class CPhysCollide; +void EmitPhysCollision(); +void DumpCollideToGlView( CPhysCollide *pCollide, const char *pFilename ); +void EmitWaterVolumesForBSP( dmodel_t *pModel, node_t *headnode ); + +//============================================================================= +// find + find or create the texdata +int FindTexData( const char *pName ); +int FindOrCreateTexData( const char *pName ); +// Add a clone of an existing texdata with a new name +int AddCloneTexData( dtexdata_t *pExistingTexData, char const *cloneTexDataName ); +int FindOrCreateTexInfo( const texinfo_t &searchTexInfo ); +int FindAliasedTexData( const char *pName, dtexdata_t *sourceTexture ); +int FindTexInfo( const texinfo_t &searchTexInfo ); + +//============================================================================= +// normals.c +void SaveVertexNormals( void ); + +//============================================================================= +// cubemap.cpp +void Cubemap_InsertSample( const Vector& origin, int size ); +void Cubemap_CreateDefaultCubemaps( void ); +void Cubemap_SaveBrushSides( const char *pSideListStr ); +void Cubemap_FixupBrushSidesMaterials( void ); +void Cubemap_AttachDefaultCubemapToSpecularSides( void ); +// Add skipped cubemaps that are referenced by the engine +void Cubemap_AddUnreferencedCubemaps( void ); + +//============================================================================= +// overlay.cpp +#define OVERLAY_MAP_STRLEN 256 + +struct mapoverlay_t +{ + int nId; + unsigned short m_nRenderOrder; + char szMaterialName[OVERLAY_MAP_STRLEN]; + float flU[2]; + float flV[2]; + float flFadeDistMinSq; + float flFadeDistMaxSq; + Vector vecUVPoints[4]; + Vector vecOrigin; + Vector vecBasis[3]; + CUtlVector aSideList; + CUtlVector aFaceList; +}; + +extern CUtlVector g_aMapOverlays; +extern CUtlVector g_aMapWaterOverlays; + +int Overlay_GetFromEntity( entity_t *pMapEnt ); +void Overlay_UpdateSideLists( int StartIndex ); +void Overlay_AddFaceToLists( int iFace, side_t *pSide ); +void Overlay_EmitOverlayFaces( void ); +void OverlayTransition_UpdateSideLists( int StartIndex ); +void OverlayTransition_AddFaceToLists( int iFace, side_t *pSide ); +void OverlayTransition_EmitOverlayFaces( void ); +void Overlay_Translate( mapoverlay_t *pOverlay, Vector &OriginOffset, QAngle &AngleOffset, matrix3x4_t &Matrix ); + +//============================================================================= + +void RemoveAreaPortalBrushes_R( node_t *node ); + +dtexdata_t *GetTexData( int index ); + +#endif + diff --git a/mp/src/utils/vbsp/worldvertextransitionfixup.cpp b/mp/src/utils/vbsp/worldvertextransitionfixup.cpp new file mode 100644 index 00000000..5d00f3f5 --- /dev/null +++ b/mp/src/utils/vbsp/worldvertextransitionfixup.cpp @@ -0,0 +1,212 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#include "bsplib.h" +#include "vbsp.h" +#include "tier1/UtlBuffer.h" +#include "tier1/utlvector.h" +#include "KeyValues.h" +#include "materialpatch.h" + +struct entitySideList_t +{ + int firstBrushSide; + int brushSideCount; +}; + +static bool SideIsNotDispAndHasDispMaterial( int iSide ) +{ + side_t *pSide = &g_MainMap->brushsides[iSide]; + + // If it's a displacement, then it's fine to have a displacement-only material. + if ( pSide->pMapDisp ) + { + return false; + } + + pSide->texinfo; + + return true; +} + +static void BackSlashToForwardSlash( char *pname ) +{ + while ( *pname ) { + if ( *pname == '\\' ) + *pname = '/'; + pname++; + } +} + +//----------------------------------------------------------------------------- +// Generate patched material name +//----------------------------------------------------------------------------- +static void GeneratePatchedMaterialName( const char *pMaterialName, char *pBuffer, int nMaxLen ) +{ + int nLen = Q_snprintf( pBuffer, nMaxLen, "maps/%s/%s_wvt_patch", mapbase, pMaterialName ); + + Assert( nLen < TEXTURE_NAME_LENGTH - 1 ); + if ( nLen >= TEXTURE_NAME_LENGTH - 1 ) + { + Error( "Generated worldvertextransition patch name : %s too long! (max = %d)\n", pBuffer, TEXTURE_NAME_LENGTH ); + } + + BackSlashToForwardSlash( pBuffer ); + Q_strlower( pBuffer ); +} + +static void RemoveKey( KeyValues *kv, const char *pSubKeyName ) +{ + KeyValues *pSubKey = kv->FindKey( pSubKeyName ); + if( pSubKey ) + { + kv->RemoveSubKey( pSubKey ); + pSubKey->deleteThis(); + } +} + +void CreateWorldVertexTransitionPatchedMaterial( const char *pOriginalMaterialName, const char *pPatchedMaterialName ) +{ + KeyValues *kv = LoadMaterialKeyValues( pOriginalMaterialName, 0 ); + if( kv ) + { + // change shader to Lightmappedgeneric (from worldvertextransition*) + kv->SetName( "LightmappedGeneric" ); + // don't need no stinking $basetexture2 or any other second texture vars + RemoveKey( kv, "$basetexture2" ); + RemoveKey( kv, "$bumpmap2" ); + RemoveKey( kv, "$bumpframe2" ); + RemoveKey( kv, "$basetexture2noenvmap" ); + RemoveKey( kv, "$blendmodulatetexture" ); + RemoveKey( kv, "$maskedblending" ); + RemoveKey( kv, "$surfaceprop2" ); + // If we didn't want a basetexture on the first texture in the blend, we don't want an envmap at all. + KeyValues *basetexturenoenvmap = kv->FindKey( "$BASETEXTURENOENVMAP" ); + if( basetexturenoenvmap->GetInt() ) + { + RemoveKey( kv, "$envmap" ); + } + + Warning( "Patching WVT material: %s\n", pPatchedMaterialName ); + WriteMaterialKeyValuesToPak( pPatchedMaterialName, kv ); + } +} + +int CreateBrushVersionOfWorldVertexTransitionMaterial( int originalTexInfo ) +{ + // Don't make cubemap tex infos for nodes + if ( originalTexInfo == TEXINFO_NODE ) + return originalTexInfo; + + texinfo_t *pTexInfo = &texinfo[originalTexInfo]; + dtexdata_t *pTexData = GetTexData( pTexInfo->texdata ); + const char *pOriginalMaterialName = TexDataStringTable_GetString( pTexData->nameStringTableID ); + + // Get out of here if the originalTexInfo is already a patched wvt material + if ( Q_stristr( pOriginalMaterialName, "_wvt_patch" ) ) + return originalTexInfo; + + char patchedMaterialName[1024]; + GeneratePatchedMaterialName( pOriginalMaterialName, patchedMaterialName, 1024 ); +// Warning( "GeneratePatchedMaterialName: %s %s\n", pMaterialName, patchedMaterialName ); + + // Make sure the texdata doesn't already exist. + int nTexDataID = FindTexData( patchedMaterialName ); + bool bHasTexData = (nTexDataID != -1); + if( !bHasTexData ) + { + // Create the new vmt material file + CreateWorldVertexTransitionPatchedMaterial( pOriginalMaterialName, patchedMaterialName ); + + // Make a new texdata + nTexDataID = AddCloneTexData( pTexData, patchedMaterialName ); + } + + Assert( nTexDataID != -1 ); + + texinfo_t newTexInfo; + newTexInfo = *pTexInfo; + newTexInfo.texdata = nTexDataID; + + int nTexInfoID = -1; + + // See if we need to make a new texinfo + bool bHasTexInfo = false; + if( bHasTexData ) + { + nTexInfoID = FindTexInfo( newTexInfo ); + bHasTexInfo = (nTexInfoID != -1); + } + + // Make a new texinfo if we need to. + if( !bHasTexInfo ) + { + nTexInfoID = texinfo.AddToTail( newTexInfo ); + } + + Assert( nTexInfoID != -1 ); + return nTexInfoID; +} + +const char *GetShaderNameForTexInfo( int iTexInfo ) +{ + texinfo_t *pTexInfo = &texinfo[iTexInfo]; + dtexdata_t *pTexData = GetTexData( pTexInfo->texdata ); + const char *pMaterialName = TexDataStringTable_GetString( pTexData->nameStringTableID ); + MaterialSystemMaterial_t hMaterial = FindMaterial( pMaterialName, NULL, false ); + const char *pShaderName = GetMaterialShaderName( hMaterial ); + return pShaderName; +} + +void WorldVertexTransitionFixup( void ) +{ + CUtlVector sideList; + sideList.SetCount( g_MainMap->num_entities ); + int i; + for ( i = 0; i < g_MainMap->num_entities; i++ ) + { + sideList[i].firstBrushSide = 0; + sideList[i].brushSideCount = 0; + } + + for ( i = 0; i < g_MainMap->nummapbrushes; i++ ) + { + sideList[g_MainMap->mapbrushes[i].entitynum].brushSideCount += g_MainMap->mapbrushes[i].numsides; + } + int curSide = 0; + for ( i = 0; i < g_MainMap->num_entities; i++ ) + { + sideList[i].firstBrushSide = curSide; + curSide += sideList[i].brushSideCount; + } + + int currentEntity = 0; + for ( int iSide = 0; iSide < g_MainMap->nummapbrushsides; ++iSide ) + { + side_t *pSide = &g_MainMap->brushsides[iSide]; + + // skip displacments + if ( pSide->pMapDisp ) + continue; + + if( pSide->texinfo < 0 ) + continue; + + const char *pShaderName = GetShaderNameForTexInfo( pSide->texinfo ); + if ( !pShaderName || !Q_stristr( pShaderName, "worldvertextransition" ) ) + { + continue; + } + + while ( currentEntity < g_MainMap->num_entities-1 && + iSide > sideList[currentEntity].firstBrushSide + sideList[currentEntity].brushSideCount ) + { + currentEntity++; + } + + pSide->texinfo = CreateBrushVersionOfWorldVertexTransitionMaterial( pSide->texinfo ); + } +} \ No newline at end of file diff --git a/mp/src/utils/vbsp/worldvertextransitionfixup.h b/mp/src/utils/vbsp/worldvertextransitionfixup.h new file mode 100644 index 00000000..6297dca5 --- /dev/null +++ b/mp/src/utils/vbsp/worldvertextransitionfixup.h @@ -0,0 +1,15 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#ifndef WORLDVERTEXTRANSITIONFIXUP_H +#define WORLDVERTEXTRANSITIONFIXUP_H +#ifdef _WIN32 +#pragma once +#endif + +void WorldVertexTransitionFixup( void ); + +#endif // WORLDVERTEXTRANSITIONFIXUP_H diff --git a/mp/src/utils/vbsp/writebsp.cpp b/mp/src/utils/vbsp/writebsp.cpp new file mode 100644 index 00000000..c776bede --- /dev/null +++ b/mp/src/utils/vbsp/writebsp.cpp @@ -0,0 +1,1552 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +#include "vbsp.h" +#include "disp_vbsp.h" +#include "utlvector.h" +#include "faces.h" +#include "builddisp.h" +#include "tier1/strtools.h" +#include "utilmatlib.h" +#include "utldict.h" +#include "map.h" + +int c_nofaces; +int c_facenodes; + +// NOTE: This is a global used to link faces back to the tree node/portals they came from +// it's used when filling water volumes +node_t *dfacenodes[MAX_MAP_FACES]; + + +/* +========================================================= + +ONLY SAVE OUT PLANES THAT ARE ACTUALLY USED AS NODES + +========================================================= +*/ + +void EmitFaceVertexes (face_t **list, face_t *f); +void AssignOccluderAreas(); + +/* +============ +EmitPlanes + +There is no oportunity to discard planes, because all of the original +brushes will be saved in the map. +============ +*/ +void EmitPlanes (void) +{ + int i; + dplane_t *dp; + plane_t *mp; + int planetranslate[MAX_MAP_PLANES]; + + mp = g_MainMap->mapplanes; + for (i=0 ; inummapplanes ; i++, mp++) + { + dp = &dplanes[numplanes]; + planetranslate[i] = numplanes; + VectorCopy ( mp->normal, dp->normal); + dp->dist = mp->dist; + dp->type = mp->type; + numplanes++; + } +} + + +//======================================================== + +void EmitMarkFace (dleaf_t *leaf_p, face_t *f) +{ + int i; + int facenum; + + while (f->merged) + f = f->merged; + + if (f->split[0]) + { + EmitMarkFace (leaf_p, f->split[0]); + EmitMarkFace (leaf_p, f->split[1]); + return; + } + + facenum = f->outputnumber; + if (facenum == -1) + return; // degenerate face + + if (facenum < 0 || facenum >= numfaces) + Error ("Bad leafface"); + for (i=leaf_p->firstleafface ; i= MAX_MAP_LEAFFACES) + Error ("Too many detail brush faces, max = %d\n", MAX_MAP_LEAFFACES); + + dleaffaces[numleaffaces] = facenum; + numleaffaces++; + } + +} + + +/* +================== +EmitLeaf +================== +*/ +void EmitLeaf (node_t *node) +{ + dleaf_t *leaf_p; + portal_t *p; + int s; + face_t *f; + bspbrush_t *b; + int i; + int brushnum; + leafface_t *pList; + + // emit a leaf + if (numleafs >= MAX_MAP_LEAFS) + Error ("Too many BSP leaves, max = %d", MAX_MAP_LEAFS); + + node->diskId = numleafs; + leaf_p = &dleafs[numleafs]; + numleafs++; + + if( nummodels == 0 ) + { + leaf_p->cluster = node->cluster; + } + else + { + // Submodels don't have clusters. If this isn't set to -1 here, then there + // will be multiple leaves (albeit from different models) that reference + // the same cluster and parts of the code like ivp.cpp's ConvertWaterModelToPhysCollide + // won't work. + leaf_p->cluster = -1; + } + + leaf_p->contents = node->contents; + leaf_p->area = node->area; + + // By default, assume the leaf can see the skybox. + // VRAD will do the actual computation to see if it really can see the skybox + leaf_p->flags = LEAF_FLAGS_SKY; + + // + // write bounding box info + // + VECTOR_COPY (node->mins, leaf_p->mins); + VECTOR_COPY (node->maxs, leaf_p->maxs); + + // + // write the leafbrushes + // + leaf_p->firstleafbrush = numleafbrushes; + for (b=node->brushlist ; b ; b=b->next) + { + if (numleafbrushes >= MAX_MAP_LEAFBRUSHES) + Error ("Too many brushes in one leaf, max = %d", MAX_MAP_LEAFBRUSHES); + + brushnum = b->original - g_MainMap->mapbrushes; + for (i=leaf_p->firstleafbrush ; inumleafbrushes = numleafbrushes - leaf_p->firstleafbrush; + + // + // write the leaffaces + // + if (leaf_p->contents & CONTENTS_SOLID) + return; // no leaffaces in solids + + leaf_p->firstleafface = numleaffaces; + + for (p = node->portals ; p ; p = p->next[s]) + { + s = (p->nodes[1] == node); + f = p->face[s]; + if (!f) + continue; // not a visible portal + + EmitMarkFace (leaf_p, f); + } + + // emit the detail faces + for ( pList = node->leaffacelist; pList; pList = pList->pNext ) + { + EmitMarkFace( leaf_p, pList->pFace ); + } + + + leaf_p->numleaffaces = numleaffaces - leaf_p->firstleafface; +} + +// per face plane - original face "side" list +side_t *pOrigFaceSideList[MAX_MAP_PLANES]; + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +int CreateOrigFace( face_t *f ) +{ + int i, j; + dface_t *of; + side_t *side; + int vIndices[128]; + int eIndex[2]; + winding_t *pWinding; + + // not a real face! + if( !f->w ) + return -1; + + // get the original face -- the "side" + side = f->originalface; + + // get the original face winding + if( !side->winding ) + { + return -1; + } + + // + // get the next original face + // + if( numorigfaces >= MAX_MAP_FACES ) + Error( "Too many faces in map, max = %d", MAX_MAP_FACES ); + of = &dorigfaces[numorigfaces]; + numorigfaces++; + + // set original face to -1 -- it is an origianl face! + of->origFace = -1; + + // + // add side to plane list + // + side->next = pOrigFaceSideList[f->planenum]; + pOrigFaceSideList[f->planenum] = side; + side->origIndex = numorigfaces - 1; + + pWinding = CopyWinding( side->winding ); + + // + // plane info + // + of->planenum = side->planenum; + if ( side->contents & CONTENTS_DETAIL ) + of->onNode = 0; + else + of->onNode = 1; + of->side = side->planenum & 1; + + // + // edge info + // + of->firstedge = numsurfedges; + of->numedges = side->winding->numpoints; + + // + // material info + // + of->texinfo = side->texinfo; + of->dispinfo = f->dispinfo; + + // + // save the vertices + // + for( i = 0; i < pWinding->numpoints; i++ ) + { + // + // compare vertices + // + vIndices[i] = GetVertexnum( pWinding->p[i] ); + } + + // + // save off points -- as edges + // + for( i = 0; i < pWinding->numpoints; i++ ) + { + // + // look for matching edges first + // + eIndex[0] = vIndices[i]; + eIndex[1] = vIndices[(i+1)%pWinding->numpoints]; + + for( j = firstmodeledge; j < numedges; j++ ) + { + if( ( eIndex[0] == dedges[j].v[1] ) && + ( eIndex[1] == dedges[j].v[0] ) && + ( edgefaces[j][0]->contents == f->contents ) ) + { + // check for multiple backward edges!! -- shouldn't have + if( edgefaces[j][1] ) + continue; + + // set back edge + edgefaces[j][1] = f; + + // + // get next surface edge + // + if( numsurfedges >= MAX_MAP_SURFEDGES ) + Error( "Too much brush geometry in bsp, numsurfedges == MAX_MAP_SURFEDGES" ); + dsurfedges[numsurfedges] = -j; + numsurfedges++; + break; + } + } + + if( j == numedges ) + { + // + // get next edge + // + AddEdge( eIndex[0], eIndex[1], f ); + + // + // get next surface edge + // + if( numsurfedges >= MAX_MAP_SURFEDGES ) + Error( "Too much brush geometry in bsp, numsurfedges == MAX_MAP_SURFEDGES" ); + dsurfedges[numsurfedges] = ( numedges - 1 ); + numsurfedges++; + } + } + + // return the index + return ( numorigfaces - 1 ); +} + + +//----------------------------------------------------------------------------- +// Purpose: search for a face within the origface list and return the index if +// found +// Input: f - the face to compare +// Output: the index of the face it found, -1 if not found +//----------------------------------------------------------------------------- +int FindOrigFace( face_t *f ) +{ + int i; + static int bClear = 0; + side_t *pSide; + + // + // initially clear the face side lists (per face plane) + // + if( !bClear ) + { + for( i = 0; i < MAX_MAP_PLANES; i++ ) + { + pOrigFaceSideList[i] = NULL; + } + bClear = 1; + } + + // + // compare the sides + // + for( pSide = pOrigFaceSideList[f->planenum]; pSide; pSide = pSide->next ) + { + if( pSide == f->originalface ) + return pSide->origIndex; + } + + // original face not found in list + return -1; +} + + +//----------------------------------------------------------------------------- +// Purpose: to find an the original face within the list of original faces, if +// a match is not found then create a new origFace -- either way pass +// back the index of the origface in the list +// Input: f - face containing the original face information +// Output: the index of the origface in the origface list +//----------------------------------------------------------------------------- +int FindOrCreateOrigFace( face_t *f ) +{ + int index; + + // check for an original face + if( !f->originalface ) + return -1; + + // + // find or create a orig face and return the index + // + index = FindOrigFace( f ); + + if( index == -1 ) + return CreateOrigFace( f ); + else if( index == -2 ) + return -1; + + return index; +} + +/* +================== +EmitFace +================== +*/ +void EmitFace( face_t *f, qboolean onNode ) +{ + dface_t *df; + int i; + int e; + +// void SubdivideFaceBySubdivSize( face_t *f ); // garymcthack +// SubdivideFaceBySubdivSize( f ); + + // set initial output number + f->outputnumber = -1; + + // degenerated + if( f->numpoints < 3 ) + return; + + // not a final face + if( f->merged || f->split[0] || f->split[1] ) + return; + + // don't emit NODRAW faces for runtime + if ( texinfo[f->texinfo].flags & SURF_NODRAW ) + { + // keep NODRAW terrain surfaces though + if ( f->dispinfo == -1 ) + return; + Warning("NODRAW on terrain surface!\n"); + } + + // save output number so leaffaces can use + f->outputnumber = numfaces; + + // + // get the next available .bsp face slot + // + if (numfaces >= MAX_MAP_FACES) + Error( "Too many faces in map, max = %d", MAX_MAP_FACES ); + df = &dfaces[numfaces]; + + // Save the correlation between dfaces and faces -- since dfaces doesnt have worldcraft face id + dfaceids.AddToTail(); + dfaceids[numfaces].hammerfaceid = f->originalface->id; + + numfaces++; + + // + // plane info - planenum is used by qlight, but not quake + // + df->planenum = f->planenum; + df->onNode = onNode; + df->side = f->planenum & 1; + + // + // material info + // + df->texinfo = f->texinfo; + df->dispinfo = f->dispinfo; + df->smoothingGroups = f->smoothingGroups; + + // save the original "side"/face data + df->origFace = FindOrCreateOrigFace( f ); + df->surfaceFogVolumeID = -1; + dfacenodes[numfaces-1] = f->fogVolumeLeaf; + if ( f->fogVolumeLeaf ) + { + Assert( f->fogVolumeLeaf->planenum == PLANENUM_LEAF ); + } + + // + // edge info + // + df->firstedge = numsurfedges; + df->numedges = f->numpoints; + + // UNDONE: Nodraw faces have no winding - revisit to see if this is necessary + if ( f->w ) + { + df->area = WindingArea( f->w ); + } + else + { + df->area = 0; + } + + df->firstPrimID = f->firstPrimID; + df->SetNumPrims( f->numPrims ); + df->SetDynamicShadowsEnabled( f->originalface->m_bDynamicShadowsEnabled ); + + // + // save off points -- as edges + // + for( i = 0; i < f->numpoints; i++ ) + { + //e = GetEdge (f->pts[i], f->pts[(i+1)%f->numpoints], f); + e = GetEdge2 (f->vertexnums[i], f->vertexnums[(i+1)%f->numpoints], f); + + if (numsurfedges >= MAX_MAP_SURFEDGES) + Error( "Too much brush geometry in bsp, numsurfedges == MAX_MAP_SURFEDGES" ); + dsurfedges[numsurfedges] = e; + numsurfedges++; + } + + // Create overlay face lists. + side_t *pSide = f->originalface; + if ( pSide ) + { + int nOverlayCount = pSide->aOverlayIds.Count(); + if ( nOverlayCount > 0 ) + { + Overlay_AddFaceToLists( ( numfaces - 1 ), pSide ); + } + + nOverlayCount = pSide->aWaterOverlayIds.Count(); + if ( nOverlayCount > 0 ) + { + OverlayTransition_AddFaceToLists( ( numfaces - 1 ), pSide ); + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Emit all of the faces stored at the leaves (faces from detail brushes) +//----------------------------------------------------------------------------- +void EmitLeafFaces( face_t *pLeafFaceList ) +{ + face_t *f = pLeafFaceList; + while ( f ) + { + EmitFace( f, false ); + f = f->next; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Free the list of faces stored at the leaves +//----------------------------------------------------------------------------- +void FreeLeafFaces( face_t *pLeafFaceList ) +{ + int count = 0; + face_t *f, *next; + + f = pLeafFaceList; + + while ( f ) + { + next = f->next; + FreeFace( f ); + f = next; + count++; + } +} + +/* +============ +EmitDrawingNode_r +============ +*/ +int EmitDrawNode_r (node_t *node) +{ + dnode_t *n; + face_t *f; + int i; + + if (node->planenum == PLANENUM_LEAF) + { + EmitLeaf (node); + return -numleafs; + } + + // emit a node + if (numnodes == MAX_MAP_NODES) + Error ("MAX_MAP_NODES"); + node->diskId = numnodes; + + n = &dnodes[numnodes]; + numnodes++; + + VECTOR_COPY (node->mins, n->mins); + VECTOR_COPY (node->maxs, n->maxs); + + if (node->planenum & 1) + Error ("WriteDrawNodes_r: odd planenum"); + n->planenum = node->planenum; + n->firstface = numfaces; + n->area = node->area; + + if (!node->faces) + c_nofaces++; + else + c_facenodes++; + + for (f=node->faces ; f ; f=f->next) + EmitFace (f, true); + + n->numfaces = numfaces - n->firstface; + + + // + // recursively output the other nodes + // + for (i=0 ; i<2 ; i++) + { + if (node->children[i]->planenum == PLANENUM_LEAF) + { + n->children[i] = -(numleafs + 1); + EmitLeaf (node->children[i]); + } + else + { + n->children[i] = numnodes; + EmitDrawNode_r (node->children[i]); + } + } + + return n - dnodes; +} + + +//========================================================= + +// This will generate a scratchpad file with the level's geometry in it and the noshadow faces drawn red. +// #define SCRATCHPAD_NO_SHADOW_FACES +#if defined( SCRATCHPAD_NO_SHADOW_FACES ) + #include "scratchpad_helpers.h" + IScratchPad3D *g_pPad; +#endif + + +void MarkNoShadowFaces() +{ +#if defined( SCRATCHPAD_NO_SHADOW_FACES ) + g_pPad = ScratchPad3D_Create(); + ScratchPad_DrawWorld( g_pPad, false, CSPColor(1,1,1,0.3) ); + + for ( int iFace=0; iFace < numfaces; iFace++ ) + { + dface_t *pFace = &dfaces[iFace]; + + if ( !pFace->AreDynamicShadowsEnabled() ) + { + ScratchPad_DrawFace( g_pPad, pFace, iFace, CSPColor(1,0,0,1), Vector(1,0,0) ); + ScratchPad_DrawFace( g_pPad, pFace, iFace, CSPColor(1,0,0,1), Vector(-1,0,0) ); + ScratchPad_DrawFace( g_pPad, pFace, iFace, CSPColor(1,0,0,1), Vector(0,1,0) ); + } + } + g_pPad->Release(); +#endif +} + +struct texinfomap_t +{ + int refCount; + int outputIndex; +}; +struct texdatamap_t +{ + int refCount; + int outputIndex; +}; + +// Find the best used texinfo to remap this brush side +int FindMatchingBrushSideTexinfo( int sideIndex, const texinfomap_t *pMap ) +{ + dbrushside_t &side = dbrushsides[sideIndex]; + // find one with the same flags & surfaceprops (even if the texture name is different) + int sideTexFlags = texinfo[side.texinfo].flags; + int sideTexData = texinfo[side.texinfo].texdata; + int sideSurfaceProp = g_SurfaceProperties[sideTexData]; + for ( int j = 0; j < texinfo.Count(); j++ ) + { + if ( pMap[j].refCount > 0 && + texinfo[j].flags == sideTexFlags && + g_SurfaceProperties[texinfo[j].texdata] == sideSurfaceProp ) + { + // found one + return j; + } + } + + // can't find a better match + return side.texinfo; +} + +// Remove all unused texinfos and rebuild array +void ComapctTexinfoArray( texinfomap_t *pMap ) +{ + CUtlVector old; + old.CopyArray( texinfo.Base(), texinfo.Count() ); + texinfo.RemoveAll(); + int firstSky = -1; + int first2DSky = -1; + for ( int i = 0; i < old.Count(); i++ ) + { + if ( !pMap[i].refCount ) + { + pMap[i].outputIndex = -1; + continue; + } + // only add one sky texinfo + one 2D sky texinfo + if ( old[i].flags & SURF_SKY2D ) + { + if ( first2DSky < 0 ) + { + first2DSky = texinfo.AddToTail( old[i] ); + } + pMap[i].outputIndex = first2DSky; + continue; + } + if ( old[i].flags & SURF_SKY ) + { + if ( firstSky < 0 ) + { + firstSky = texinfo.AddToTail( old[i] ); + } + pMap[i].outputIndex = firstSky; + continue; + } + pMap[i].outputIndex = texinfo.AddToTail( old[i] ); + } +} + +void CompactTexdataArray( texdatamap_t *pMap ) +{ + CUtlVector oldStringData; + oldStringData.CopyArray( g_TexDataStringData.Base(), g_TexDataStringData.Count() ); + g_TexDataStringData.RemoveAll(); + CUtlVector oldStringTable; + oldStringTable.CopyArray( g_TexDataStringTable.Base(), g_TexDataStringTable.Count() ); + g_TexDataStringTable.RemoveAll(); + CUtlVector oldTexData; + oldTexData.CopyArray( dtexdata, numtexdata ); + // clear current table and rebuild + numtexdata = 0; + for ( int i = 0; i < oldTexData.Count(); i++ ) + { + // unreferenced, note in map and skip + if ( !pMap[i].refCount ) + { + pMap[i].outputIndex = -1; + continue; + } + pMap[i].outputIndex = numtexdata; + + // get old string and re-add to table + const char *pString = &oldStringData[oldStringTable[oldTexData[i].nameStringTableID]]; + int nameIndex = TexDataStringTable_AddOrFindString( pString ); + // copy old texdata and fixup with new name in compacted table + dtexdata[numtexdata] = oldTexData[i]; + dtexdata[numtexdata].nameStringTableID = nameIndex; + numtexdata++; + } +} + +void CompactTexinfos() +{ + Msg("Compacting texture/material tables...\n"); + texinfomap_t *texinfoMap = new texinfomap_t[texinfo.Count()]; + texdatamap_t *texdataMap = new texdatamap_t[numtexdata]; + memset( texinfoMap, 0, sizeof(texinfoMap[0])*texinfo.Count() ); + memset( texdataMap, 0, sizeof(texdataMap[0])*numtexdata ); + int i; + // get texinfos referenced by faces + for ( i = 0; i < numfaces; i++ ) + { + texinfoMap[dfaces[i].texinfo].refCount++; + } + // get texinfos referenced by brush sides + for ( i = 0; i < numbrushsides; i++ ) + { + // not referenced by any visible geometry + Assert( dbrushsides[i].texinfo >= 0 ); + if ( !texinfoMap[dbrushsides[i].texinfo].refCount ) + { + dbrushsides[i].texinfo = FindMatchingBrushSideTexinfo( i, texinfoMap ); + // didn't find anything suitable, go ahead and reference it + if ( !texinfoMap[dbrushsides[i].texinfo].refCount ) + { + texinfoMap[dbrushsides[i].texinfo].refCount++; + } + } + } + // get texinfos referenced by overlays + for ( i = 0; i < g_nOverlayCount; i++ ) + { + texinfoMap[g_Overlays[i].nTexInfo].refCount++; + } + for ( i = 0; i < numleafwaterdata; i++ ) + { + if ( dleafwaterdata[i].surfaceTexInfoID >= 0 ) + { + texinfoMap[dleafwaterdata[i].surfaceTexInfoID].refCount++; + } + } + for ( i = 0; i < *pNumworldlights; i++ ) + { + if ( dworldlights[i].texinfo >= 0 ) + { + texinfoMap[dworldlights[i].texinfo].refCount++; + } + } + for ( i = 0; i < g_nWaterOverlayCount; i++ ) + { + if ( g_WaterOverlays[i].nTexInfo >= 0 ) + { + texinfoMap[g_WaterOverlays[i].nTexInfo].refCount++; + } + } + // reference all used texdatas + for ( i = 0; i < texinfo.Count(); i++ ) + { + if ( texinfoMap[i].refCount > 0 ) + { + texdataMap[texinfo[i].texdata].refCount++; + } + } + + int oldCount = texinfo.Count(); + int oldTexdataCount = numtexdata; + int oldTexdataString = g_TexDataStringData.Count(); + ComapctTexinfoArray( texinfoMap ); + CompactTexdataArray( texdataMap ); + for ( i = 0; i < texinfo.Count(); i++ ) + { + int mapIndex = texdataMap[texinfo[i].texdata].outputIndex; + Assert( mapIndex >= 0 ); + texinfo[i].texdata = mapIndex; + //const char *pName = TexDataStringTable_GetString( dtexdata[texinfo[i].texdata].nameStringTableID ); + } + // remap texinfos on faces + for ( i = 0; i < numfaces; i++ ) + { + Assert( texinfoMap[dfaces[i].texinfo].outputIndex >= 0 ); + dfaces[i].texinfo = texinfoMap[dfaces[i].texinfo].outputIndex; + } + // remap texinfos on brushsides + for ( i = 0; i < numbrushsides; i++ ) + { + Assert( texinfoMap[dbrushsides[i].texinfo].outputIndex >= 0 ); + dbrushsides[i].texinfo = texinfoMap[dbrushsides[i].texinfo].outputIndex; + } + // remap texinfos on overlays + for ( i = 0; i < g_nOverlayCount; i++ ) + { + g_Overlays[i].nTexInfo = texinfoMap[g_Overlays[i].nTexInfo].outputIndex; + } + // remap leaf water data + for ( i = 0; i < numleafwaterdata; i++ ) + { + if ( dleafwaterdata[i].surfaceTexInfoID >= 0 ) + { + dleafwaterdata[i].surfaceTexInfoID = texinfoMap[dleafwaterdata[i].surfaceTexInfoID].outputIndex; + } + } + // remap world lights + for ( i = 0; i < *pNumworldlights; i++ ) + { + if ( dworldlights[i].texinfo >= 0 ) + { + dworldlights[i].texinfo = texinfoMap[dworldlights[i].texinfo].outputIndex; + } + } + // remap water overlays + for ( i = 0; i < g_nWaterOverlayCount; i++ ) + { + if ( g_WaterOverlays[i].nTexInfo >= 0 ) + { + g_WaterOverlays[i].nTexInfo = texinfoMap[g_WaterOverlays[i].nTexInfo].outputIndex; + } + } + + Msg("Reduced %d texinfos to %d\n", oldCount, texinfo.Count() ); + Msg("Reduced %d texdatas to %d (%d bytes to %d)\n", oldTexdataCount, numtexdata, oldTexdataString, g_TexDataStringData.Count() ); + + delete[] texinfoMap; + delete[] texdataMap; +} + +/* +============ +WriteBSP +============ +*/ +void WriteBSP (node_t *headnode, face_t *pLeafFaceList ) +{ + int i; + int oldfaces; + int oldorigfaces; + + c_nofaces = 0; + c_facenodes = 0; + + qprintf ("--- WriteBSP ---\n"); + + oldfaces = numfaces; + oldorigfaces = numorigfaces; + + GetEdge2_InitOptimizedList(); + EmitLeafFaces( pLeafFaceList ); + dmodels[nummodels].headnode = EmitDrawNode_r (headnode); + + // Only emit area portals for the main world. + if( nummodels == 0 ) + { + EmitAreaPortals (headnode); + } + + // + // add all displacement faces for the particular model + // + for( i = 0; i < nummapdispinfo; i++ ) + { + int entityIndex = GetDispInfoEntityNum( &mapdispinfo[i] ); + if( entityIndex == entity_num ) + { + EmitFaceVertexes( NULL, &mapdispinfo[i].face ); + EmitFace( &mapdispinfo[i].face, FALSE ); + } + } + + EmitWaterVolumesForBSP( &dmodels[nummodels], headnode ); + qprintf ("%5i nodes with faces\n", c_facenodes); + qprintf ("%5i nodes without faces\n", c_nofaces); + qprintf ("%5i faces\n", numfaces-oldfaces); + qprintf( "%5i original faces\n", numorigfaces-oldorigfaces ); +} + + + +//=========================================================== + +/* +============ +SetModelNumbers +============ +*/ +void SetModelNumbers (void) +{ + int i; + int models; + char value[10]; + + models = 1; + for (i=1 ; inummapbrushes; + + for (bnum=0 ; bnumnummapbrushes ; bnum++) + { + b = &g_MainMap->mapbrushes[bnum]; + db = &dbrushes[bnum]; + + db->contents = b->contents; + db->firstside = numbrushsides; + db->numsides = b->numsides; + for (j=0 ; jnumsides ; j++) + { + if (numbrushsides == MAX_MAP_BRUSHSIDES) + Error ("MAX_MAP_BRUSHSIDES"); + cp = &dbrushsides[numbrushsides]; + numbrushsides++; + cp->planenum = b->original_sides[j].planenum; + cp->texinfo = b->original_sides[j].texinfo; + if ( cp->texinfo == -1 ) + { + cp->texinfo = g_MainMap->g_ClipTexinfo; + } + cp->bevel = b->original_sides[j].bevel; + } + + // add any axis planes not contained in the brush to bevel off corners + for (x=0 ; x<3 ; x++) + for (s=-1 ; s<=1 ; s+=2) + { + // add the plane + VectorCopy (vec3_origin, normal); + normal[x] = s; + if (s == -1) + dist = -b->mins[x]; + else + dist = b->maxs[x]; + planenum = g_MainMap->FindFloatPlane (normal, dist); + for (i=0 ; inumsides ; i++) + if (b->original_sides[i].planenum == planenum) + break; + if (i == b->numsides) + { + if (numbrushsides >= MAX_MAP_BRUSHSIDES) + Error ("MAX_MAP_BRUSHSIDES"); + + dbrushsides[numbrushsides].planenum = planenum; + dbrushsides[numbrushsides].texinfo = + dbrushsides[numbrushsides-1].texinfo; + numbrushsides++; + db->numsides++; + } + } + } +} + + + +/* +================== +BeginBSPFile +================== +*/ +void BeginBSPFile (void) +{ + // these values may actually be initialized + // if the file existed when loaded, so clear them explicitly + nummodels = 0; + numfaces = 0; + numnodes = 0; + numbrushsides = 0; + numvertexes = 0; + numleaffaces = 0; + numleafbrushes = 0; + numsurfedges = 0; + + // edge 0 is not used, because 0 can't be negated + numedges = 1; + + // leave vertex 0 as an error + numvertexes = 1; + + // leave leaf 0 as an error + numleafs = 1; + dleafs[0].contents = CONTENTS_SOLID; + + // BUGBUG: This doesn't work! +#if 0 + // make a default empty leaf for the tracing code + memset( &dleafs[1], 0, sizeof(dleafs[1]) ); + dleafs[1].contents = CONTENTS_EMPTY; +#endif +} + +// We can't calculate this properly until vvis (since we need vis to do this), so we set +// to zero everywhere by default. +static void ClearDistToClosestWater( void ) +{ + int i; + for( i = 0; i < numleafs; i++ ) + { + g_LeafMinDistToWater[i] = 0; + } +} + + +void DiscoverMacroTextures() +{ + CUtlDict tempDict; + + g_FaceMacroTextureInfos.SetSize( numfaces ); + for ( int iFace=0; iFace < numfaces; iFace++ ) + { + texinfo_t *pTexInfo = &texinfo[dfaces[iFace].texinfo]; + if ( pTexInfo->texdata < 0 ) + continue; + + dtexdata_t *pTexData = &dtexdata[pTexInfo->texdata]; + const char *pMaterialName = &g_TexDataStringData[ g_TexDataStringTable[pTexData->nameStringTableID] ]; + + MaterialSystemMaterial_t hMaterial = FindMaterial( pMaterialName, NULL, false ); + + const char *pMacroTextureName = GetMaterialVar( hMaterial, "$macro_texture" ); + if ( pMacroTextureName ) + { + if ( tempDict.Find( pMacroTextureName ) == tempDict.InvalidIndex() ) + { + Msg( "-- DiscoverMacroTextures: %s\n", pMacroTextureName ); + tempDict.Insert( pMacroTextureName, 0 ); + } + + int stringID = TexDataStringTable_AddOrFindString( pMacroTextureName ); + g_FaceMacroTextureInfos[iFace].m_MacroTextureNameID = (unsigned short)stringID; + } + else + { + g_FaceMacroTextureInfos[iFace].m_MacroTextureNameID = 0xFFFF; + } + } +} + + +// Make sure that we have a water lod control entity if we have water in the map. +void EnsurePresenceOfWaterLODControlEntity( void ) +{ + extern bool g_bHasWater; + if( !g_bHasWater ) + { + // Don't bother if there isn't any water in the map. + return; + } + for( int i=0; i < num_entities; i++ ) + { + entity_t *e = &entities[i]; + + const char *pClassName = ValueForKey( e, "classname" ); + if( !Q_stricmp( pClassName, "water_lod_control" ) ) + { + // Found one!!!! + return; + } + } + + // None found, add one. + Warning( "Water found with no water_lod_control entity, creating a default one.\n" ); + + entity_t *mapent = &entities[num_entities]; + num_entities++; + memset(mapent, 0, sizeof(*mapent)); + mapent->firstbrush = g_MainMap->nummapbrushes; + mapent->numbrushes = 0; + + SetKeyValue( mapent, "classname", "water_lod_control" ); + SetKeyValue( mapent, "cheapwaterstartdistance", "1000" ); + SetKeyValue( mapent, "cheapwaterenddistance", "2000" ); +} + + +/* +============ +EndBSPFile +============ +*/ +void EndBSPFile (void) +{ + // Mark noshadow faces. + MarkNoShadowFaces(); + + EmitBrushes (); + EmitPlanes (); + + // stick flat normals at the verts + SaveVertexNormals(); + + // Figure out lightmap extents for all faces. + UpdateAllFaceLightmapExtents(); + + // Generate geometry and lightmap alpha for displacements. + EmitDispLMAlphaAndNeighbors(); + + // Emit overlay data. + Overlay_EmitOverlayFaces(); + OverlayTransition_EmitOverlayFaces(); + + // phys collision needs dispinfo to operate (needs to generate phys collision for displacement surfs) + EmitPhysCollision(); + + // We can't calculate this properly until vvis (since we need vis to do this), so we set + // to zero everywhere by default. + ClearDistToClosestWater(); + + // Emit static props found in the .vmf file + EmitStaticProps(); + + // Place detail props found in .vmf and based on material properties + EmitDetailObjects(); + + // Compute bounds after creating disp info because we need to reference it + ComputeBoundsNoSkybox(); + + // Make sure that we have a water lod control eneity if we have water in the map. + EnsurePresenceOfWaterLODControlEntity(); + + // Doing this here because stuff about may filter out entities + UnparseEntities (); + + // remove unused texinfos + CompactTexinfos(); + + // Figure out which faces want macro textures. + DiscoverMacroTextures(); + + char targetPath[1024]; + GetPlatformMapPath( source, targetPath, g_nDXLevel, 1024 ); + Msg ("Writing %s\n", targetPath); + WriteBSPFile (targetPath); +} + + +/* +================== +BeginModel +================== +*/ +int firstmodleaf; +void BeginModel (void) +{ + dmodel_t *mod; + int start, end; + mapbrush_t *b; + int j; + entity_t *e; + Vector mins, maxs; + + if (nummodels == MAX_MAP_MODELS) + Error ("Too many brush models in map, max = %d", MAX_MAP_MODELS); + mod = &dmodels[nummodels]; + + mod->firstface = numfaces; + + firstmodleaf = numleafs; + firstmodeledge = numedges; + firstmodelface = numfaces; + + // + // bound the brushes + // + e = &entities[entity_num]; + + start = e->firstbrush; + end = start + e->numbrushes; + ClearBounds (mins, maxs); + + for (j=start ; jmapbrushes[j]; + if (!b->numsides) + continue; // not a real brush (origin brush) + AddPointToBounds (b->mins, mins, maxs); + AddPointToBounds (b->maxs, mins, maxs); + } + + VectorCopy (mins, mod->mins); + VectorCopy (maxs, mod->maxs); +} + + +/* +================== +EndModel +================== +*/ +void EndModel (void) +{ + dmodel_t *mod; + + mod = &dmodels[nummodels]; + + mod->numfaces = numfaces - mod->firstface; + + nummodels++; +} + + + +//----------------------------------------------------------------------------- +// figure out which leaf a point is in +//----------------------------------------------------------------------------- +static int PointLeafnum_r (const Vector& p, int num) +{ + float d; + while (num >= 0) + { + dnode_t* node = dnodes + num; + dplane_t* plane = dplanes + node->planenum; + + if (plane->type < 3) + d = p[plane->type] - plane->dist; + else + d = DotProduct (plane->normal, p) - plane->dist; + if (d < 0) + num = node->children[1]; + else + num = node->children[0]; + } + + return -1 - num; +} + +int PointLeafnum ( dmodel_t* pModel, const Vector& p ) +{ + return PointLeafnum_r (p, pModel->headnode); +} + + +//----------------------------------------------------------------------------- +// Adds a noew to the bounding box +//----------------------------------------------------------------------------- +static void AddNodeToBounds(int node, CUtlVector& skipAreas, Vector& mins, Vector& maxs) +{ + // not a leaf + if (node >= 0) + { + AddNodeToBounds( dnodes[node].children[0], skipAreas, mins, maxs ); + AddNodeToBounds( dnodes[node].children[1], skipAreas, mins, maxs ); + } + else + { + int leaf = - 1 - node; + + // Don't bother with solid leaves + if (dleafs[leaf].contents & CONTENTS_SOLID) + return; + + // Skip 3D skybox + int i; + for ( i = skipAreas.Count(); --i >= 0; ) + { + if (dleafs[leaf].area == skipAreas[i]) + return; + } + + unsigned int firstface = dleafs[leaf].firstleafface; + for ( i = 0; i < dleafs[leaf].numleaffaces; ++i ) + { + unsigned int face = dleaffaces[ firstface + i ]; + + // Skip skyboxes + nodraw + texinfo_t& tex = texinfo[dfaces[face].texinfo]; + if (tex.flags & (SURF_SKY | SURF_NODRAW)) + continue; + + unsigned int firstedge = dfaces[face].firstedge; + Assert( firstedge >= 0 ); + + for (int j = 0; j < dfaces[face].numedges; ++j) + { + Assert( firstedge+j < numsurfedges ); + int edge = abs(dsurfedges[firstedge+j]); + dedge_t* pEdge = &dedges[edge]; + Assert( pEdge->v[0] >= 0 ); + Assert( pEdge->v[1] >= 0 ); + AddPointToBounds (dvertexes[pEdge->v[0]].point, mins, maxs); + AddPointToBounds (dvertexes[pEdge->v[1]].point, mins, maxs); + } + } + } +} + + +//----------------------------------------------------------------------------- +// Check to see if a displacement lives in any leaves that are not +// in the 3d skybox +//----------------------------------------------------------------------------- +bool IsBoxInsideWorld( int node, CUtlVector &skipAreas, const Vector &vecMins, const Vector &vecMaxs ) +{ + while( 1 ) + { + // leaf + if (node < 0) + { + // get the leaf + int leaf = - 1 - node; + + // Don't bother with solid leaves + if (dleafs[leaf].contents & CONTENTS_SOLID) + return false; + + // Skip 3D skybox + int i; + for ( i = skipAreas.Count(); --i >= 0; ) + { + if ( dleafs[leaf].area == skipAreas[i] ) + return false; + } + + return true; + } + + // + // get displacement bounding box position relative to the node plane + // + dnode_t *pNode = &dnodes[ node ]; + dplane_t *pPlane = &dplanes[ pNode->planenum ]; + + int sideResult = BrushBspBoxOnPlaneSide( vecMins, vecMaxs, pPlane ); + + // front side + if( sideResult == 1 ) + { + node = pNode->children[0]; + } + // back side + else if( sideResult == 2 ) + { + node = pNode->children[1]; + } + //split + else + { + if ( IsBoxInsideWorld( pNode->children[0], skipAreas, vecMins, vecMaxs ) ) + return true; + + node = pNode->children[1]; + } + } +} + + +//----------------------------------------------------------------------------- +// Adds the displacement surfaces in the world to the bounds +//----------------------------------------------------------------------------- +void AddDispsToBounds( int nHeadNode, CUtlVector& skipAreas, Vector &vecMins, Vector &vecMaxs ) +{ + Vector vecDispMins, vecDispMaxs; + + // first determine how many displacement surfaces there will be per leaf + int i; + for ( i = 0; i < g_dispinfo.Count(); ++i ) + { + ComputeDispInfoBounds( i, vecDispMins, vecDispMaxs ); + if ( IsBoxInsideWorld( nHeadNode, skipAreas, vecDispMins, vecDispMaxs ) ) + { + AddPointToBounds( vecDispMins, vecMins, vecMaxs ); + AddPointToBounds( vecDispMaxs, vecMins, vecMaxs ); + } + } +} + + +//----------------------------------------------------------------------------- +// Compute the bounding box, excluding 3D skybox + skybox, add it to keyvalues +//----------------------------------------------------------------------------- +void ComputeBoundsNoSkybox( ) +{ + // Iterate over all world leaves, skip those which are part of skybox + Vector mins, maxs; + ClearBounds (mins, maxs); + AddNodeToBounds( dmodels[0].headnode, g_SkyAreas, mins, maxs ); + AddDispsToBounds( dmodels[0].headnode, g_SkyAreas, mins, maxs ); + + // Add the bounds to the worldspawn data + for (int i = 0; i < num_entities; ++i) + { + char* pEntity = ValueForKey(&entities[i], "classname"); + if (!strcmp(pEntity, "worldspawn")) + { + char string[32]; + sprintf (string, "%i %i %i", (int)mins[0], (int)mins[1], (int)mins[2]); + SetKeyValue (&entities[i], "world_mins", string); + sprintf (string, "%i %i %i", (int)maxs[0], (int)maxs[1], (int)maxs[2]); + SetKeyValue (&entities[i], "world_maxs", string); + break; + } + } +} + + diff --git a/mp/src/utils/vbsp/writebsp.h b/mp/src/utils/vbsp/writebsp.h new file mode 100644 index 00000000..e1ad084e --- /dev/null +++ b/mp/src/utils/vbsp/writebsp.h @@ -0,0 +1,34 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef WRITEBSP_H +#define WRITEBSP_H +#ifdef _WIN32 +#pragma once +#endif + +#include "bspfile.h" +#include "utlmap.h" + +struct node_t; + +//----------------------------------------------------------------------------- +// Emits occluder faces +//----------------------------------------------------------------------------- +void EmitOccluderFaces (node_t *node); + + +//----------------------------------------------------------------------------- +// Purpose: Free the list of faces stored at the leaves +//----------------------------------------------------------------------------- +void FreeLeafFaces( face_t *pLeafFaceList ); + +//----------------------------------------------------------------------------- +// Purpose: Make sure that we have a water lod control entity if we have water in the map. +//----------------------------------------------------------------------------- +void EnsurePresenceOfWaterLODControlEntity( void ); + +#endif // WRITEBSP_H diff --git a/mp/src/utils/vice/vice-2010.vcxproj b/mp/src/utils/vice/vice-2010.vcxproj new file mode 100644 index 00000000..12e10a33 --- /dev/null +++ b/mp/src/utils/vice/vice-2010.vcxproj @@ -0,0 +1,247 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + Vice + {03F753C0-8BA5-FF2B-D7D2-EE230B4683B1} + + + + Application + MultiByte + vice + + + Application + MultiByte + vice + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30319.1 + .\Debug\win32\ + .\Debug\win32\ + ..\..\devtools\vstools;$(ExecutablePath);$(Path) + true + true + true + true + true + .\Release\win32\ + .\Release\win32\ + ..\..\devtools\vstools;$(ExecutablePath);$(Path) + true + true + false + true + true + + + + if EXIST ..\..\..\game\bin\$(TargetFileName) for /f "delims=" %%A in ('attrib "..\..\..\game\bin\$(TargetFileName)"') do set valveTmpIsReadOnly="%%A" set valveTmpIsReadOnlyLetter=%valveTmpIsReadOnly:~6,1% if "%valveTmpIsReadOnlyLetter%"=="R" del /q "$(TargetDir)"$(TargetFileName) if exist ..\..\devtools\bin\vpc.exe ..\..\devtools\bin\vpc.exe -crc2 vice.vcxproj if ERRORLEVEL 1 exit 1 + + + /MP + Disabled + ..\..\common;..\..\public;..\..\public\tier0;..\..\public\tier1;..\common + _HAS_ITERATOR_DEBUGGING=0;WIN32;_WIN32;_DEBUG;DEBUG;_WINDOWS;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;RAD_TELEMETRY_DISABLED;COMPILER_MSVC32;VPCGAMECAPS=VALVE;PROJECTDIR=D:\dev\games\rel\hl2\src\utils\vice;_DLL_EXT=.dll;VPCGAME=valve + true + false + Default + MultiThreadedDebug + true + StreamingSIMDExtensions + Fast + true + true + true + false + NotUsing + false + NoListing + $(IntDir)/ + $(IntDir)/ + $(IntDir)/ + false + Level4 + true + EditAndContinue + CompileAsCpp + $(IntDir)/ + Prompt + + + _DEBUG;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE + 1033 + + + + + /NXCOMPAT /ignore:4221 + %(AdditionalDependencies) + NotSet + $(OutDir)\vice.exe + true + libc;libcd;libcmt + true + $(IntDir)/$(TargetName).pdb + false + $(IntDir)/$(TargetName).map + Console + + MachineX86 + PromptImmediately + false + + + true + + + true + + + true + $(OutDir)/vice.bsc + + + Publishing to ..\..\..\game\bin + if not exist "..\..\..\game\bin" mkdir "..\..\..\game\bin" copy "$(TargetDir)"$(TargetFileName) ..\..\..\game\bin\$(TargetFileName) if ERRORLEVEL 1 goto BuildEventFailed if exist "$(TargetDir)"$(TargetName).map copy "$(TargetDir)"$(TargetName).map ..\..\..\game\bin\$(TargetName).map copy "$(TargetDir)"$(TargetName).pdb ..\..\..\game\bin\$(TargetName).pdb if ERRORLEVEL 1 goto BuildEventFailed goto BuildEventOK :BuildEventFailed echo *** ERROR! PostBuildStep FAILED for $(ProjectName)! EXE or DLL is probably running. *** del /q "$(TargetDir)"$(TargetFileName) exit 1 :BuildEventOK + + + + + + + if EXIST ..\..\..\game\bin\$(TargetFileName) for /f "delims=" %%A in ('attrib "..\..\..\game\bin\$(TargetFileName)"') do set valveTmpIsReadOnly="%%A" set valveTmpIsReadOnlyLetter=%valveTmpIsReadOnly:~6,1% if "%valveTmpIsReadOnlyLetter%"=="R" del /q "$(TargetDir)"$(TargetFileName) if exist ..\..\devtools\bin\vpc.exe ..\..\devtools\bin\vpc.exe -crc2 vice.vcxproj if ERRORLEVEL 1 exit 1 + + + /MP /d2Zi+ + MaxSpeed + AnySuitable + true + Speed + ..\..\common;..\..\public;..\..\public\tier0;..\..\public\tier1;..\common + WIN32;_WIN32;NDEBUG;_WINDOWS;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;RAD_TELEMETRY_DISABLED;COMPILER_MSVC32;VPCGAMECAPS=VALVE;PROJECTDIR=D:\dev\games\rel\hl2\src\utils\vice;_DLL_EXT=.dll;VPCGAME=valve + true + false + MultiThreaded + false + true + StreamingSIMDExtensions + Fast + true + true + true + false + NotUsing + false + NoListing + $(IntDir)/ + $(IntDir)/ + $(IntDir)/ + false + Level4 + true + ProgramDatabase + CompileAsCpp + $(IntDir)/ + Prompt + + + NDEBUG;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE + 1033 + + + + + /DYNAMICBASE /NXCOMPAT /ignore:4221 + %(AdditionalDependencies) + NotSet + $(OutDir)\vice.exe + true + libc;libcd;libcmtd + true + $(IntDir)/$(TargetName).pdb + false + $(IntDir)/$(TargetName).map + Console + true + true + + MachineX86 + PromptImmediately + + + true + + + true + + + true + $(OutDir)/vice.bsc + + + Publishing to ..\..\..\game\bin + if not exist "..\..\..\game\bin" mkdir "..\..\..\game\bin" copy "$(TargetDir)"$(TargetFileName) ..\..\..\game\bin\$(TargetFileName) if ERRORLEVEL 1 goto BuildEventFailed if exist "$(TargetDir)"$(TargetName).map copy "$(TargetDir)"$(TargetName).map ..\..\..\game\bin\$(TargetName).map copy "$(TargetDir)"$(TargetName).pdb ..\..\..\game\bin\$(TargetName).pdb if ERRORLEVEL 1 goto BuildEventFailed goto BuildEventOK :BuildEventFailed echo *** ERROR! PostBuildStep FAILED for $(ProjectName)! EXE or DLL is probably running. *** del /q "$(TargetDir)"$(TargetFileName) exit 1 :BuildEventOK + + + + + + + + + + + + + + + + + + + + + NotUsing + NotUsing + + + + + + + + Compiling pointeroverride.asm + "$(VCInstallDir)bin\ml.exe" /c /Cp /Zi /Fo"$(IntDir)\%(Filename).obj" "%(FullPath)" + $(IntDir)\%(Filename).obj + Compiling pointeroverride.asm + "$(VCInstallDir)bin\ml.exe" /c /Cp /Zi /Fo"$(IntDir)\%(Filename).obj" "%(FullPath)" + $(IntDir)\%(Filename).obj + + + + + + + + diff --git a/mp/src/utils/vice/vice-2010.vcxproj.filters b/mp/src/utils/vice/vice-2010.vcxproj.filters new file mode 100644 index 00000000..82155f11 --- /dev/null +++ b/mp/src/utils/vice/vice-2010.vcxproj.filters @@ -0,0 +1,65 @@ + + + + + {1680C80B-FF1E-EA4D-9817-CC12254F2E40} + + + {C5D73B3A-C648-896C-B7CE-F174808E5BA5} + + + {BA03E055-4FA2-FCE3-8A1C-D348547D379C} + + + + + Link Libraries + + + Link Libraries + + + Link Libraries + + + Link Libraries + + + Link Libraries + + + + + Header Files + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + + + Source Files + + + + + diff --git a/mp/src/utils/vice/vice.cpp b/mp/src/utils/vice/vice.cpp new file mode 100644 index 00000000..0ed9c32e --- /dev/null +++ b/mp/src/utils/vice/vice.cpp @@ -0,0 +1,277 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// vice.cpp : Defines the entry point for the console application. +// + +#include +#include +#include +#include +#include "tier1/strtools.h" +#include +#include "conio.h" +#include +#include +#include "UtlBuffer.h" +#include "tier0/dbg.h" +#include "cmdlib.h" +#include "tier0/icommandline.h" +#include "windows.h" + +#include "mathlib/IceKey.h" +#include + +#define FF_TRYAGAIN 1 +#define FF_DONTPROCESS 2 + +#undef GetCurrentDirectory + +static bool g_NoPause = false; +static bool g_Quiet = false; +static bool g_Encrypt = false; +static bool g_Decrypt = false; +static char g_ICEKey[16]; +static char g_Extension[16]; + +static void Pause( void ) +{ + if( !g_NoPause ) + { + printf( "Hit a key to continue\n" ); + getch(); + } +} + +static void Exit(const char *msg) +{ + fprintf( stderr, msg ); + Pause(); + exit( -1 ); +} + +static void Usage( void ) +{ + fprintf( stderr, "Usage: vice [-quiet] [-nopause] [-encrypt key] [-decrypt key] [-newext name] file file2 . . .\n" ); + fprintf( stderr, "-quiet : don't print anything out, don't pause for input\n" ); + fprintf( stderr, "-nopause : don't pause for input\n" ); + fprintf( stderr, "-encrypt : encrypt files with given key\n" ); + fprintf( stderr, "-decrypt : decypt files with given key\n" ); + fprintf( stderr, "-newext : new output file extension\n" ); + Pause(); + exit( -1 ); +} + + +bool Process_File( char *pInputBaseName, int maxlen ) +{ + Q_FixSlashes( pInputBaseName, '/' ); + // Q_StripExtension( pInputBaseName, pInputBaseName, maxlen ); + + if( !g_Quiet ) + { + printf( "input file: %s\n", pInputBaseName ); + } + + FileHandle_t f = g_pFullFileSystem->Open(pInputBaseName, "rb", "vice" ); + + if (!f) + Error("Could not open input file"); + + int fileSize = g_pFullFileSystem->Size(f); + + unsigned char *buffer = (unsigned char*)_alloca(fileSize); + + g_pFullFileSystem->Read(buffer, fileSize, f); // read into local buffer + g_pFullFileSystem->Close( f ); // close file after reading + + IceKey ice( 0 ); // level 0 = 64bit key + ice.set( (unsigned char*) g_ICEKey ); // set key + + int blockSize = ice.blockSize(); + + unsigned char *temp = (unsigned char *)_alloca( fileSize ); + unsigned char *p1 = buffer; + unsigned char *p2 = temp; + + // encrypt data in 8 byte blocks + int bytesLeft = fileSize; + while ( bytesLeft >= blockSize ) + { + if ( g_Encrypt ) + { + ice.encrypt( p1, p2 ); + } + else if ( g_Decrypt ) + { + ice.decrypt( p1, p2 ); + } + else + { + memcpy( p2, p1, blockSize ); + } + + bytesLeft -= blockSize; + p1+=blockSize; + p2+=blockSize; + } + + memcpy( p2, p1, bytesLeft ); + + Q_SetExtension( pInputBaseName, g_Extension, maxlen ); + + if( !g_Quiet ) + { + printf( "output file: %s\n", pInputBaseName ); + } + + f = g_pFullFileSystem->Open(pInputBaseName, "wb", "vice" ); + + if (!f) + Exit("Could not open output file"); + + g_pFullFileSystem->Write( temp, fileSize, f ); // read into local buffer + g_pFullFileSystem->Close( f ); // close file after reading + + return TRUE; +} + +int main(int argc, char* argv[]) +{ + CommandLine()->CreateCmdLine( argc, argv ); + + + if( argc < 2 ) + { + Usage(); + } + char *pInputBaseName = NULL; + int i = 1; + strcpy( g_Extension, ".dat" ); + while( i < argc ) + { + if( stricmp( argv[i], "-quiet" ) == 0 ) + { + i++; + g_Quiet = true; + g_NoPause = true; // no point in pausing if we aren't going to print anything out. + } + if( stricmp( argv[i], "-nopause" ) == 0 ) + { + i++; + g_NoPause = true; + } + if( stricmp( argv[i], "-encrypt" ) == 0 ) + { + g_Encrypt = true; + i++; + + if ( strlen( argv[i] ) != 8 ) + { + Exit("Error - ICE key must be a 8 char text.\n"); + } + + Q_strncpy( g_ICEKey, argv[i], sizeof(g_ICEKey) ); + i++; + + } + if( stricmp( argv[i], "-decrypt" ) == 0 ) + { + g_Decrypt = true; + i++; + + if ( strlen( argv[i] ) != 8 ) + { + Exit("Error - ICE key must be a 8 char text.\n"); + } + + Q_strncpy( g_ICEKey, argv[i], sizeof(g_ICEKey) ); + i++; + + } + if( stricmp( argv[i], "-newext" ) == 0 ) + { + i++; + + if ( strlen( argv[i] ) > 5 ) + { + Exit("Error - extension must be smaller than 4 chars.\n"); + } + + Q_strncpy( g_Extension, argv[i], sizeof(g_Extension) ); + i++; + + } + else + { + break; + } + } + + if ( i >= argc ) + { + Exit("Error - missing files in commandline.\n"); + } + + CmdLib_InitFileSystem( argv[i] ); + + g_pFullFileSystem->GetCurrentDirectory( gamedir, sizeof(gamedir) ); + g_pFullFileSystem->AddSearchPath( gamedir, "vice" ); + + + Q_FixSlashes( gamedir, '/' ); + + for( ; i < argc; i++ ) + { + pInputBaseName = argv[i]; + int maxlen = Q_strlen( pInputBaseName ) + 1; + + + if ( strstr( pInputBaseName, "*.") ) + { + char search[ MAX_PATH ]; + char fname[ MAX_PATH ]; + char ext[_MAX_EXT]; + + _splitpath( pInputBaseName, NULL, NULL, fname, ext ); //find extension wanted + fname[strlen(fname)-1] = 0; // remove * + + sprintf( search, "%s\\*%s", gamedir, ext ); + + Q_FixSlashes( search, '/' ); + + WIN32_FIND_DATA wfd; + HANDLE hResult; + memset(&wfd, 0, sizeof(WIN32_FIND_DATA)); + + hResult = FindFirstFile( search, &wfd ); + + while ( hResult != INVALID_HANDLE_VALUE ) + { + if ( !strnicmp( fname, wfd.cFileName, strlen(fname) ) ) + { + if ( !Process_File( wfd.cFileName, sizeof( wfd.cFileName ) ) ) + break; + } + + if ( !FindNextFile( hResult, &wfd) ) + break; + + } + + FindClose( hResult ); + } + else + { + Process_File( pInputBaseName, maxlen ); + } + } + + Pause(); + return 0; +} + diff --git a/mp/src/utils/vmpi/ichannel.h b/mp/src/utils/vmpi/ichannel.h new file mode 100644 index 00000000..42466cfc --- /dev/null +++ b/mp/src/utils/vmpi/ichannel.h @@ -0,0 +1,49 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef ICHANNEL_H +#define ICHANNEL_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "tier1/utlvector.h" + + +class IChannel +{ +public: + // Note: this also releases any channels contained inside. So if you make a reliable + // channel that contains an unreliable channel and release the reliable one, + // it will automatically release the unreliable one it contains. + virtual void Release() = 0; + + // Send data to the destination. + virtual bool Send( const void *pData, int len ) = 0; + + // This version puts all the chunks into one packet and ships it off. + virtual bool SendChunks( void const * const *pChunks, const int *pChunkLengths, int nChunks ) = 0; + + // Check for any packets coming in from the destination. + // Returns false if no packet was received. + // + // flTimeout can be used to make it wait for data. + // + // Note: this is most efficient if you keep the buffer around between calls so it only + // reallocates it when it needs more space. + virtual bool Recv( CUtlVector &data, double flTimeout=0 ) = 0; + + // Returns false if the connection has been broken. + virtual bool IsConnected() = 0; + + // If IsConnected returns false, you can call this to find out why the socket got disconnected. + virtual void GetDisconnectReason( CUtlVector &reason ) = 0; +}; + + +#endif // ICHANNEL_H diff --git a/mp/src/utils/vmpi/imysqlwrapper.h b/mp/src/utils/vmpi/imysqlwrapper.h new file mode 100644 index 00000000..f5a8dfa3 --- /dev/null +++ b/mp/src/utils/vmpi/imysqlwrapper.h @@ -0,0 +1,115 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef MYSQL_WRAPPER_H +#define MYSQL_WRAPPER_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "utlvector.h" +#include "interface.h" + + +class IMySQLRowSet; + + +class CColumnValue +{ +public: + + CColumnValue( IMySQLRowSet *pSQL, int iColumn ); + + const char* String(); + long Int32(); + +private: + IMySQLRowSet *m_pSQL; + int m_iColumn; +}; + + + +class IMySQLRowSet +{ +public: + virtual void Release() = 0; + + // Get the number of columns in the data returned from the last query (if it was a select statement). + virtual int NumFields() = 0; + + // Get the name of each column returned by the last query. + virtual const char* GetFieldName( int iColumn ) = 0; + + // Call this in a loop until it returns false to iterate over all rows the query returned. + virtual bool NextRow() = 0; + + // You can call this to start iterating over the result set from the start again. + // Note: after calling this, you have to call NextRow() to actually get the first row's value ready. + virtual bool SeekToFirstRow() = 0; + + virtual CColumnValue GetColumnValue( int iColumn ) = 0; + virtual CColumnValue GetColumnValue( const char *pColumnName ) = 0; + + virtual const char* GetColumnValue_String( int iColumn ) = 0; + virtual long GetColumnValue_Int( int iColumn ) = 0; + + // You can call this to get the index of a column for faster lookups with GetColumnValue( int ). + // Returns -1 if the column can't be found. + virtual int GetColumnIndex( const char *pColumnName ) = 0; +}; + + +class IMySQL : public IMySQLRowSet +{ +public: + virtual bool InitMySQL( const char *pDBName, const char *pHostName="", const char *pUserName="", const char *pPassword="" ) = 0; + virtual void Release() = 0; + + // These execute SQL commands. They return 0 if the query was successful. + virtual int Execute( const char *pString ) = 0; + + // This reads in all of the data in the last row set you queried with Execute and builds a separate + // copy. This is useful in some of the VMPI tools to have a thread repeatedly execute a slow query, then + // store off the results for the main thread to parse. + virtual IMySQLRowSet* DuplicateRowSet() = 0; + + // If you just inserted rows into a table with an AUTO_INCREMENT column, + // then this returns the (unique) value of that column. + virtual unsigned long InsertID() = 0; + + // Returns the last error message, if an error took place + virtual const char * GetLastError() = 0; +}; + + +#define MYSQL_WRAPPER_VERSION_NAME "MySQLWrapper001" + + +// ------------------------------------------------------------------------------------------------ // +// Inlines. +// ------------------------------------------------------------------------------------------------ // + +inline CColumnValue::CColumnValue( IMySQLRowSet *pSQL, int iColumn ) +{ + m_pSQL = pSQL; + m_iColumn = iColumn; +} + +inline const char* CColumnValue::String() +{ + return m_pSQL->GetColumnValue_String( m_iColumn ); +} + +inline long CColumnValue::Int32() +{ + return m_pSQL->GetColumnValue_Int( m_iColumn ); +} + + +#endif // MYSQL_WRAPPER_H diff --git a/mp/src/utils/vmpi/iphelpers.h b/mp/src/utils/vmpi/iphelpers.h new file mode 100644 index 00000000..570a0c74 --- /dev/null +++ b/mp/src/utils/vmpi/iphelpers.h @@ -0,0 +1,162 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +#ifndef IPHELPERS_H +#define IPHELPERS_H + + +#include "ichannel.h" + + +// Loops that poll sockets should Sleep for this amount of time between iterations +// so they don't hog all the CPU. +#define LOOP_POLL_INTERVAL 5 + + +// Useful for putting the arguments into a printf statement. +#define EXPAND_ADDR( x ) (x).ip[0], (x).ip[1], (x).ip[2], (x).ip[3], (x).port + + +// This is a simple wrapper layer for UDP sockets. +class CIPAddr +{ +public: + CIPAddr(); + CIPAddr( const int inputIP[4], const int inputPort ); + CIPAddr( int ip0, int ip1, int ip2, int ip3, int ipPort ); + + void Init( int ip0, int ip1, int ip2, int ip3, int ipPort ); + bool operator==( const CIPAddr &o ) const; + bool operator!=( const CIPAddr &o ) const; + + // Setup to send to the local machine on the specified port. + void SetupLocal( int inPort ); + +public: + + unsigned char ip[4]; + unsigned short port; +}; + + + +// The chunk walker provides an easy way to copy data out of the chunks as though it were a +// single contiguous chunk of memory.s +class CChunkWalker +{ +public: + CChunkWalker( void const * const *pChunks, const int *pChunkLengths, int nChunks ); + + int GetTotalLength() const; + void CopyTo( void *pOut, int nBytes ); + +private: + + void const * const *m_pChunks; + const int *m_pChunkLengths; + int m_nChunks; + + int m_iCurChunk; + int m_iCurChunkPos; + + int m_TotalLength; +}; + + +// This class makes loop that wait on something look nicer. ALL loops using this class +// should follow this pattern, or they can wind up with unforeseen delays that add a whole +// lot of lag. +// +// CWaitTimer waitTimer( 5.0 ); +// while ( 1 ) +// { +// do your thing here like Recv() from a socket. +// +// if ( waitTimer.ShouldKeepWaiting() ) +// Sleep() for some time interval like 5ms so you don't hog the CPU +// else +// BREAK HERE +// } +class CWaitTimer +{ +public: + CWaitTimer( double flSeconds ); + + bool ShouldKeepWaiting(); + +private: + unsigned long m_StartTime; + unsigned long m_WaitMS; +}; + + +// Helper function to get time in milliseconds. +unsigned long SampleMilliseconds(); + + +class ISocket +{ +public: + + // Call this when you're done. + virtual void Release() = 0; + + + // Bind the socket so you can send and receive with it. + // If you bind to port 0, then the system will select the port for you. + virtual bool Bind( const CIPAddr *pAddr ) = 0; + virtual bool BindToAny( const unsigned short port ) = 0; + + + // Broadcast some data. + virtual bool Broadcast( const void *pData, const int len, const unsigned short port ) = 0; + + // Send a packet. + virtual bool SendTo( const CIPAddr *pAddr, const void *pData, const int len ) = 0; + virtual bool SendChunksTo( const CIPAddr *pAddr, void const * const *pChunks, const int *pChunkLengths, int nChunks ) = 0; + + // Receive a packet. Returns the length received or -1 if no data came in. + // If pFrom is set, then it is filled in with the sender's IP address. + virtual int RecvFrom( void *pData, int maxDataLen, CIPAddr *pFrom ) = 0; + + // How long has it been since we successfully received a packet? + virtual double GetRecvTimeout() = 0; +}; + +// Create a connectionless socket that you can send packets out of. +ISocket* CreateIPSocket(); + +// This sets up the socket to receive multicast data on the specified group. +// By default, localInterface is INADDR_ANY, but if you want to specify a specific interface +// the data should come in through, you can. +ISocket* CreateMulticastListenSocket( + const CIPAddr &addr, + const CIPAddr &localInterface = CIPAddr() + ); + + +// Setup a CIPAddr from the string. The string can be a dotted IP address or +// a hostname, and it can be followed by a colon and a port number like "1.2.3.4:3443" +// or "myhostname.valvesoftware.com:2342". +// +// Note: if the string does not contain a port, then pOut->port will be left alone. +bool ConvertStringToIPAddr( const char *pStr, CIPAddr *pOut ); + +// Do a DNS lookup on the IP. +// You can optionally get a service name back too. +bool ConvertIPAddrToString( const CIPAddr *pIn, char *pOut, int outLen ); + + +void IP_GetLastErrorString( char *pStr, int maxLen ); + +void SockAddrToIPAddr( const struct sockaddr_in *pIn, CIPAddr *pOut ); +void IPAddrToSockAddr( const CIPAddr *pIn, struct sockaddr_in *pOut ); + + +#endif + diff --git a/mp/src/utils/vmpi/messbuf.h b/mp/src/utils/vmpi/messbuf.h new file mode 100644 index 00000000..2f09e884 --- /dev/null +++ b/mp/src/utils/vmpi/messbuf.h @@ -0,0 +1,52 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// +// MessageBuffer - handy for packing and upacking +// structures to be sent as messages +// +#ifndef _MESSAGEBUFFER +#define _MESSAGEBUFFER + +#include +#define DEFAULT_MESSAGE_BUFFER_SIZE 2048 + +class MessageBuffer { + public: + char * data; + + MessageBuffer(); + MessageBuffer(int size); + ~MessageBuffer(); + + int getSize(); + int getLen(); + int setLen(int len); + int getOffset(); + int setOffset(int offset); + + int write(void const * p, int bytes); + int update(int loc, void const * p, int bytes); + int extract(int loc, void * p, int bytes); + int read(void * p, int bytes); + + int WriteString( const char *pString ); + int ReadString( char *pOut, int bufferLength ); + + void clear(); + void clear(int minsize); + void reset(int minsize); + void print(FILE * ofile, int num); + + private: + void resize(int minsize); + int size; + int offset; + int len; +}; + +#endif diff --git a/mp/src/utils/vmpi/threadhelpers.h b/mp/src/utils/vmpi/threadhelpers.h new file mode 100644 index 00000000..1e955a5e --- /dev/null +++ b/mp/src/utils/vmpi/threadhelpers.h @@ -0,0 +1,110 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef THREADHELPERS_H +#define THREADHELPERS_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "tier1/utllinkedlist.h" + + +#define SIZEOF_CS 24 // sizeof( CRITICAL_SECTION ) + + +class CCriticalSection +{ +public: + CCriticalSection(); + ~CCriticalSection(); + + +protected: + + friend class CCriticalSectionLock; + + void Lock(); + void Unlock(); + + +public: + char m_CS[SIZEOF_CS]; + + // Used to protect against deadlock in debug mode. +//#if defined( _DEBUG ) + CUtlLinkedList m_Locks; + char m_DeadlockProtect[SIZEOF_CS]; +//#endif +}; + + +// Use this to lock a critical section. +class CCriticalSectionLock +{ +public: + CCriticalSectionLock( CCriticalSection *pCS ); + ~CCriticalSectionLock(); + void Lock(); + void Unlock(); + +private: + CCriticalSection *m_pCS; + bool m_bLocked; +}; + + +template< class T > +class CCriticalSectionData : private CCriticalSection +{ +public: + // You only have access to the data between Lock() and Unlock(). + T* Lock() + { + CCriticalSection::Lock(); + return &m_Data; + } + + void Unlock() + { + CCriticalSection::Unlock(); + } + +private: + T m_Data; +}; + + + +// ------------------------------------------------------------------------------------------------ // +// CEvent. +// ------------------------------------------------------------------------------------------------ // +class CEvent +{ +public: + CEvent(); + ~CEvent(); + + bool Init( bool bManualReset, bool bInitialState ); + void Term(); + + void* GetEventHandle() const; + + // Signal the event. + bool SetEvent(); + + // Unset the event's signalled status. + bool ResetEvent(); + + +private: + void *m_hEvent; +}; + + +#endif // THREADHELPERS_H diff --git a/mp/src/utils/vmpi/vmpi.h b/mp/src/utils/vmpi/vmpi.h new file mode 100644 index 00000000..b2fe9641 --- /dev/null +++ b/mp/src/utils/vmpi/vmpi.h @@ -0,0 +1,217 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef VMPI_H +#define VMPI_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "vmpi_defs.h" +#include "messbuf.h" +#include "iphelpers.h" + + +// These are called to handle incoming messages. +// Return true if you handled the message and false otherwise. +// Note: the first byte in each message is the packet ID. +typedef bool (*VMPIDispatchFn)( MessageBuffer *pBuf, int iSource, int iPacketID ); + +typedef void (*VMPI_Disconnect_Handler)( int procID, const char *pReason ); + + +// Which machine is the master. +#define VMPI_MASTER_ID 0 + +#define VMPI_SEND_TO_ALL -2 +#define VMPI_PERSISTENT -3 // If this is set as the destination for a packet, it is sent to all + // workers, and also to new workers that connect. + +#define MAX_VMPI_PACKET_IDS 32 + + +#define VMPI_TIMEOUT_INFINITE 0xFFFFFFFF + + +// Instantiate one of these to register a dispatch. +class CDispatchReg +{ +public: + CDispatchReg( int iPacketID, VMPIDispatchFn fn ); +}; + + +// Enums for all the command line parameters. +#define VMPI_PARAM_SDK_HIDDEN 0x0001 // Hidden in SDK mode. + +#define VMPI_PARAM( paramName, paramFlags, helpText ) paramName, +enum EVMPICmdLineParam +{ + k_eVMPICmdLineParam_FirstParam=0, + k_eVMPICmdLineParam_VMPIParam, + #include "vmpi_parameters.h" + k_eVMPICmdLineParam_LastParam +}; +#undef VMPI_PARAM + + +// Shared by all the tools. +extern bool g_bUseMPI; +extern bool g_bMPIMaster; // Set to true if we're the master in a VMPI session. +extern int g_iVMPIVerboseLevel; // Higher numbers make it spit out more data. + +extern bool g_bMPI_Stats; // Send stats to the MySQL database? +extern bool g_bMPI_StatsTextOutput; // Send text output in the stats? + +// These can be watched or modified to check bandwidth statistics. +extern int g_nBytesSent; +extern int g_nMessagesSent; +extern int g_nBytesReceived; +extern int g_nMessagesReceived; + +extern int g_nMulticastBytesSent; +extern int g_nMulticastBytesReceived; + +extern int g_nMaxWorkerCount; + + +enum VMPIRunMode +{ + VMPI_RUN_NETWORKED, + VMPI_RUN_LOCAL // Just make a local process and have it do the work. +}; + + +enum VMPIFileSystemMode +{ + VMPI_FILESYSTEM_MULTICAST, // Multicast out, find workers, have them do work. + VMPI_FILESYSTEM_BROADCAST, // Broadcast out, find workers, have them do work. + VMPI_FILESYSTEM_TCP // TCP filesystem. +}; + + +// If this precedes the dependency filename, then it will transfer all the files in the specified directory. +#define VMPI_DEPENDENCY_DIRECTORY_TOKEN '*' + + +// It's good to specify a disconnect handler here immediately. If you don't have a handler +// and the master disconnects, you'll lockup forever inside a dispatch loop because you +// never handled the master disconnecting. +// +// Note: runMode is only relevant for the VMPI master. The worker always connects to the master +// the same way. +bool VMPI_Init( + int &argc, + char **&argv, + const char *pDependencyFilename, + VMPI_Disconnect_Handler handler = NULL, + VMPIRunMode runMode = VMPI_RUN_NETWORKED, // Networked or local?, + bool bConnectingAsService = false + ); + +// Used when hosting a patch. +void VMPI_Init_PatchMaster( int argc, char **argv ); + +void VMPI_Finalize(); + +VMPIRunMode VMPI_GetRunMode(); +VMPIFileSystemMode VMPI_GetFileSystemMode(); + +// Note: this number can change on the master. +int VMPI_GetCurrentNumberOfConnections(); + + +// Dispatch messages until it gets one with the specified packet ID. +// If subPacketID is not set to -1, then the second byte must match that as well. +// +// Note: this WILL dispatch packets with matching packet IDs and give them a chance to handle packets first. +// +// If bWait is true, then this function either succeeds or Error() is called. If it's false, then if the first available message +// is handled by a dispatch, this function returns false. +bool VMPI_DispatchUntil( MessageBuffer *pBuf, int *pSource, int packetID, int subPacketID = -1, bool bWait = true ); + +// This waits for the next message and dispatches it. +// You can specify a timeout in milliseconds. If the timeout expires, the function returns false. +bool VMPI_DispatchNextMessage( unsigned long timeout=VMPI_TIMEOUT_INFINITE ); + +// This should be called periodically in modal loops that don't call other VMPI functions. This will +// check for disconnected sockets and call disconnect handlers so the app can error out if +// it loses all of its connections. +// +// This can be used in place of a Sleep() call by specifying a timeout value. +void VMPI_HandleSocketErrors( unsigned long timeout=0 ); + + + +enum VMPISendFlags +{ + k_eVMPISendFlags_GroupPackets = 0x0001 +}; + +// Use these to send data to one of the machines. +// If iDest is VMPI_SEND_TO_ALL, then the message goes to all the machines. +// Flags is a combination of the VMPISendFlags enums. +bool VMPI_SendData( void *pData, int nBytes, int iDest, int fVMPISendFlags=0 ); +bool VMPI_SendChunks( void const * const *pChunks, const int *pChunkLengths, int nChunks, int iDest, int fVMPISendFlags=0 ); +bool VMPI_Send2Chunks( const void *pChunk1, int chunk1Len, const void *pChunk2, int chunk2Len, int iDest, int fVMPISendFlags=0 ); // for convenience.. +bool VMPI_Send3Chunks( const void *pChunk1, int chunk1Len, const void *pChunk2, int chunk2Len, const void *pChunk3, int chunk3Len, int iDest, int fVMPISendFlags=0 ); + +// Flush any groups that were queued with k_eVMPISendFlags_GroupPackets. +// If msInterval is > 0, then it will check a timer and only flush that often (so you can call this a lot, and have it check). +void VMPI_FlushGroupedPackets( unsigned long msInterval=0 ); + +// This registers a function that gets called when a connection is terminated ungracefully. +void VMPI_AddDisconnectHandler( VMPI_Disconnect_Handler handler ); + +// Returns false if the process has disconnected ungracefully (disconnect handlers +// would have been called for it too). +bool VMPI_IsProcConnected( int procID ); + +// Returns true if the process is just a service (in which case it should only get file IO traffic). +bool VMPI_IsProcAService( int procID ); + +// Simple wrapper for Sleep() so people can avoid including windows.h +void VMPI_Sleep( unsigned long ms ); + +// VMPI sends machine names around first thing. +const char* VMPI_GetLocalMachineName(); +const char* VMPI_GetMachineName( int iProc ); +bool VMPI_HasMachineNameBeenSet( int iProc ); + +// Returns 0xFFFFFFFF if the ID hasn't been set. +unsigned long VMPI_GetJobWorkerID( int iProc ); +void VMPI_SetJobWorkerID( int iProc, unsigned long jobWorkerID ); + +// Search a command line to find arguments. Looks for pName, and if it finds it, returns the +// argument following it. If pName is the last argument, it returns pDefault. If it doesn't +// find pName, returns NULL. +const char* VMPI_FindArg( int argc, char **argv, const char *pName, const char *pDefault = "" ); + +// (Threadsafe) get and set the current stage. This info winds up in the VMPI database. +void VMPI_GetCurrentStage( char *pOut, int strLen ); +void VMPI_SetCurrentStage( const char *pCurStage ); + +// VMPI is always broadcasting this job in the background. +// This changes the password to 'debugworker' and allows more workers in. +// This can be used if workers are dying on certain work units. Then a programmer +// can run vmpi_service with -superdebug and debug the whole thing. +void VMPI_InviteDebugWorkers(); + +bool VMPI_IsSDKMode(); + +// Lookup a command line parameter string. +const char* VMPI_GetParamString( EVMPICmdLineParam eParam ); +int VMPI_GetParamFlags( EVMPICmdLineParam eParam ); +const char* VMPI_GetParamHelpString( EVMPICmdLineParam eParam ); +bool VMPI_IsParamUsed( EVMPICmdLineParam eParam ); // Returns true if the specified parameter is on the command line. + +// Can be called from error handlers and if -mpi_Restart is used, it'll automatically restart the process. +bool VMPI_HandleAutoRestart(); + + +#endif // VMPI_H diff --git a/mp/src/utils/vmpi/vmpi_defs.h b/mp/src/utils/vmpi/vmpi_defs.h new file mode 100644 index 00000000..7845d9f9 --- /dev/null +++ b/mp/src/utils/vmpi/vmpi_defs.h @@ -0,0 +1,147 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef VMPI_DEFS_H +#define VMPI_DEFS_H +#ifdef _WIN32 +#pragma once +#endif + + +// This goes in front of all packets. +#define VMPI_PROTOCOL_VERSION 5 + +// This represents the protocol between the service and its UI. +#define VMPI_SERVICE_UI_PROTOCOL_VERSION 1 + +// NOTE: the service version (embedded in vmpi_service.exe as a resource) is the version +// that is used to apply patches. +#define VMPI_SERVICE_IDS_VERSION_STRING 102 // This matches IDS_VERSION_STRING in vmpi_service.exe. + +// Known packet IDs in various systems. +#define VMPI_PACKETID_FILESYSTEM 0 // The file system reserves this packet ID. + // All application traffic must set its first byte to something other + // than this value. +#define VMPI_SHARED_PACKET_ID 10 + + +// Turn this on, and the various service apps will log stuff. +//#define VMPI_SERVICE_LOGS + + +// This value is put in the RunningTimeMS until the job is finished. This is how +// the job_search app knows if a job never finished. +#define RUNNINGTIME_MS_SENTINEL 0xFEDCBAFD + + + +#define VMPI_SERVICE_NAME_INTERNAL "VMPI" +#define VMPI_SERVICE_NAME "Valve MPI Service" + +// Stuff in the registry goes under here (in HKEY_LOCAL_MACHINE). +#define VMPI_SERVICE_KEY "Software\\Valve\\VMPI" +#define SERVICE_INSTALL_LOCATION_KEY "InstallLocation" + +// The VMPI service listens on one of these ports to talk to the UI. +#define VMPI_SERVICE_FIRST_UI_PORT 23300 +#define VMPI_SERVICE_LAST_UI_PORT 23310 + +// Port numbers that the master will use to broadcast unless -mpi_port is used. +#define VMPI_MASTER_FIRST_PORT 23311 +#define VMPI_MASTER_LAST_PORT 23330 + + +// Packet IDs for vmpi_service to talk to UI clients. +#define VMPI_SERVICE_TO_UI_CONSOLE_TEXT 0 // Print some text to the UI's console. +#define VMPI_SERVICE_TO_UI_STATE 1 // Updates state reflecting whether it's idle, busy, etc. +#define VMPI_SERVICE_TO_UI_PATCHING 2 // Updates state reflecting whether it's idle, busy, etc. +#define VMPI_SERVICE_TO_UI_EXIT 3 // Updates state reflecting whether it's idle, busy, etc. + + // Application state.. these are communicated between the service and the UI. + enum + { + VMPI_SERVICE_STATE_IDLE=0, + VMPI_SERVICE_STATE_BUSY, + VMPI_SERVICE_STATE_DISABLED + }; +#define VMPI_SERVICE_DISABLE 2 // Stop waiting for jobs.. +#define VMPI_SERVICE_ENABLE 3 +#define VMPI_SERVICE_UPDATE_PASSWORD 4 // New password. +#define VMPI_SERVICE_EXIT 5 // User chose "exit" from the menu. Kill the service. +#define VMPI_SERVICE_SKIP_CSX_JOBS 6 +#define VMPI_SERVICE_SCREENSAVER_MODE 7 + + +// The worker service waits on this range of ports. +#define VMPI_SERVICE_PORT 23397 +#define VMPI_LAST_SERVICE_PORT (VMPI_SERVICE_PORT + 15) + + +#define VMPI_WORKER_PORT_FIRST 22340 +#define VMPI_WORKER_PORT_LAST 22350 + +// The VMPI service downloader is still a worker but it uses this port range so the +// master knows it's just downloading the exes. +#define VMPI_SERVICE_DOWNLOADER_PORT_FIRST 22351 +#define VMPI_SERVICE_DOWNLOADER_PORT_LAST 22360 + +// Give it a small range so they can have multiple masters running. +#define VMPI_MASTER_PORT_FIRST 21140 +#define VMPI_MASTER_PORT_LAST 21145 +#define VMPI_MASTER_FILESYSTEM_BROADCAST_PORT 21146 + + + + +// Protocol. + +// The message format is: +// - VMPI_PROTOCOL_VERSION +// - null-terminated password string (or VMPI_PASSWORD_OVERRIDE followed by a zero to process it regardless of pw). +// - packet ID +// - payload + + +#define VMPI_PASSWORD_OVERRIDE -111 + + +#define VMPI_MESSAGE_BASE 71 + + +// This is the broadcast message from the main (rank 0) process looking for workers. +#define VMPI_LOOKING_FOR_WORKERS (VMPI_MESSAGE_BASE+0) + +// This is so an app can find out what machines are running the service. +#define VMPI_PING_REQUEST (VMPI_MESSAGE_BASE+2) +#define VMPI_PING_RESPONSE (VMPI_MESSAGE_BASE+3) + +// This tells the service to quit. +#define VMPI_STOP_SERVICE (VMPI_MESSAGE_BASE+6) + +// This tells the service to kill any process it has running. +#define VMPI_KILL_PROCESS (VMPI_MESSAGE_BASE+7) + +// This tells the service to patch itself. +#define VMPI_SERVICE_PATCH (VMPI_MESSAGE_BASE+8) + +// Sent back to the master via UDP to tell it if its job has started and ended. +#define VMPI_NOTIFY_START_STATUS (VMPI_MESSAGE_BASE+9) +#define VMPI_NOTIFY_END_STATUS (VMPI_MESSAGE_BASE+10) + +#define VMPI_FORCE_PASSWORD_CHANGE (VMPI_MESSAGE_BASE+11) + + +// These states are sent from the service to the services browser. +#define VMPI_STATE_IDLE 0 +#define VMPI_STATE_BUSY 1 +#define VMPI_STATE_PATCHING 2 +#define VMPI_STATE_DISABLED 3 +#define VMPI_STATE_SCREENSAVER_DISABLED 4 +#define VMPI_STATE_DOWNLOADING 5 + + +#endif // VMPI_DEFS_H diff --git a/mp/src/utils/vmpi/vmpi_dispatch.h b/mp/src/utils/vmpi/vmpi_dispatch.h new file mode 100644 index 00000000..d91c5b1d --- /dev/null +++ b/mp/src/utils/vmpi/vmpi_dispatch.h @@ -0,0 +1,15 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef VMPI_DISPATCH_H +#define VMPI_DISPATCH_H +#ifdef _WIN32 +#pragma once +#endif + + +#endif // VMPI_DISPATCH_H diff --git a/mp/src/utils/vmpi/vmpi_distribute_work.h b/mp/src/utils/vmpi/vmpi_distribute_work.h new file mode 100644 index 00000000..cfd37d8b --- /dev/null +++ b/mp/src/utils/vmpi/vmpi_distribute_work.h @@ -0,0 +1,89 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef VMPI_DISTRIBUTE_WORK_H +#define VMPI_DISTRIBUTE_WORK_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "messbuf.h" +#include "utlvector.h" + + +class IWorkUnitDistributorCallbacks +{ +public: + // Called every 200ms or so as it does the work. + // Return true to stop distributing work. + virtual bool Update() { return false; } + + // Called when a subsequent number of work units is completed. + // e.g. results received in the following order will trigger + // the following calls to OnWorkUnitsCompleted: + // Work unit numbers: wu2 wu4 wu5 wu1 wu0 wu6 wu3 + // Calling OnWorkUnitsCompleted with arg: - - - - 3 - 7 + // because when wu0 is received we already have { wu0, wu1, wu2 } so we signal + // that 3 subsequent work units completed, like wise by the time when wu3 is + // received we already have a full set { wu0, wu1, wu2, wu3, wu4, wu5, wu6 } + // and signal that 7 work units completed. + virtual void OnWorkUnitsCompleted( uint64 numWorkUnits ) { return; } +}; + + +enum EWorkUnitDistributor +{ + k_eWorkUnitDistributor_Default, + k_eWorkUnitDistributor_SDK +}; + +// Tells which work unit distributor is going to be used. +EWorkUnitDistributor VMPI_GetActiveWorkUnitDistributor(); + + +// Before calling DistributeWork, you can set this and it'll call your virtual functions. +extern IWorkUnitDistributorCallbacks *g_pDistributeWorkCallbacks; + + +// You must append data to pBuf with the work unit results. +// Note: pBuf will be NULL if this is a local thread doing work on the master. +typedef void (*ProcessWorkUnitFn)( int iThread, uint64 iWorkUnit, MessageBuffer *pBuf ); + +// pBuf is ready to read the results written to the buffer in ProcessWorkUnitFn. +typedef void (*ReceiveWorkUnitFn)( uint64 iWorkUnit, MessageBuffer *pBuf, int iWorker ); + + +// Use a CDispatchReg to register this function with whatever packet ID you give to DistributeWork. +bool DistributeWorkDispatch( MessageBuffer *pBuf, int iSource, int iPacketID ); + + + +// This is the function vrad and vvis use to divide the work units and send them out. +// It maintains a sliding window of work units so it can always keep the clients busy. +// +// The workers implement processFn to do the job work in a work unit. +// This function must send back a packet formatted with: +// cPacketID (char), cSubPacketID (char), iWorkUnit (int), (app-specific data for the results) +// +// The masters implement receiveFn to receive a work unit's results. +// +// Returns time it took to finish the work. +double DistributeWork( + uint64 nWorkUnits, // how many work units to dole out + char cPacketID, // This packet ID must be reserved for DistributeWork and DistributeWorkDispatch + // must be registered with it. + ProcessWorkUnitFn processFn, // workers implement this to process a work unit and send results back + ReceiveWorkUnitFn receiveFn // the master implements this to receive a work unit + ); + + +// VMPI calls this before shutting down because any threads that DistributeWork has running must stop, +// otherwise it can crash if a thread tries to send data in the middle of shutting down. +void DistributeWork_Cancel(); + + +#endif // VMPI_DISTRIBUTE_WORK_H diff --git a/mp/src/utils/vmpi/vmpi_filesystem.h b/mp/src/utils/vmpi/vmpi_filesystem.h new file mode 100644 index 00000000..889d8abc --- /dev/null +++ b/mp/src/utils/vmpi/vmpi_filesystem.h @@ -0,0 +1,53 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef VMPI_FILESYSTEM_H +#define VMPI_FILESYSTEM_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "interface.h" + + +class IFileSystem; +class MessageBuffer; + + +// Use this to read virtual files. +#define VMPI_VIRTUAL_FILES_PATH_ID "VMPI_VIRTUAL_FILES_PATH_ID" + + +// When you hook the file system with VMPI and are a worker, it blocks on file reads +// and uses MPI to communicate with the master to transfer files it needs over. +// +// The filesystem, by default (and it maxFileSystemMemoryUsage is left at zero), +// keeps the contents of the files that get opened in memory. You can pass in a +// value here to put a cap on it, in which case it'll unload the least-recently-used +// files when it hits the limit. +IFileSystem* VMPI_FileSystem_Init( int maxFileSystemMemoryUsage, IFileSystem *pPassThru ); + +// On the master machine, this really should be called before the app shuts down and +// global destructors are called. If it isn't, it might lock up waiting for a thread to exit. +// +// This returns the original filesystem you passed into VMPI_FileSystem_Init so you can uninitialize it. +IFileSystem* VMPI_FileSystem_Term(); + +// Causes it to error out on any Open() calls. +void VMPI_FileSystem_DisableFileAccess(); + +// Returns a factory that will hand out BASEFILESYSTEM_INTERFACE_VERSION when asked for it. +CreateInterfaceFn VMPI_FileSystem_GetFactory(); + +// This function creates a virtual file that workers can then open and read out of. +// NOTE: when reading from the file, you must use VMPI_VIRTUAL_FILES_PATH_ID as the path ID +// or else it won't find the file. +void VMPI_FileSystem_CreateVirtualFile( const char *pFilename, const void *pData, unsigned long fileLength ); + + +#endif // VMPI_FILESYSTEM_H diff --git a/mp/src/utils/vmpi/vmpi_parameters.h b/mp/src/utils/vmpi/vmpi_parameters.h new file mode 100644 index 00000000..db467cd0 --- /dev/null +++ b/mp/src/utils/vmpi/vmpi_parameters.h @@ -0,0 +1,31 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +VMPI_PARAM( mpi_Worker, 0, "Workers use this to connect to a VMPI job. Specify the IP address of the master. Example: -mpi_worker 1.2.3.4 or -mpi_worker 1.2.3.4:242" ) +VMPI_PARAM( mpi_Port, 0, "Use this on the master to force it to bind to a specified port. Otherwise it binds to 23311 (and ascending port numbers if 23311 doesn't work)." ) +VMPI_PARAM( mpi_Graphics, 0, "Show a graphical representation of work units [grey=work unit not sent yet, red=sent, green=completed, blue=in-process]" ) +VMPI_PARAM( mpi_Retry, 0, "Use this on the worker to have it retry connecting to the master forever. Otherwise it will exit if it can't connect to the master immediately." ) +VMPI_PARAM( mpi_AutoRestart, 0, "Use this on the worker to have it restart with the same command line parameters after completing a job. Useful in conjunction with -mpi_Retry to have an always-on worker ready to do work." ) +VMPI_PARAM( mpi_TrackEvents, 0, "Enables a debug menu during jobs (press D to access). Note: -mpi_Graphics automatically enables -mpi_TrackEvents." ) +VMPI_PARAM( mpi_ShowDistributeWorkStats, 0, "After finishing a stage in the work unit processing, shows statistics." ) +VMPI_PARAM( mpi_TimingWait, 0, "Causes the master to wait for a keypress to start so workers can connect before it starts. Used for performance measurements." ) +VMPI_PARAM( mpi_WorkerCount, 0, "Set the maximum number of workers allowed in the job." ) +VMPI_PARAM( mpi_AutoLocalWorker, 0, "Used on the master's machine. Automatically spawn a worker on the local machine. Used for testing." ) +VMPI_PARAM( mpi_FileTransmitRate, 0, "VMPI file transmission rate in kB/sec." ) +VMPI_PARAM( mpi_Verbose, 0, "Set to 0, 1, or 2 to control verbosity of debug output." ) +VMPI_PARAM( mpi_NoMasterWorkerThreads, 0, "Don't process work units locally (in the master). Only used by the SDK work unit distributor." ) +VMPI_PARAM( mpi_SDKMode, VMPI_PARAM_SDK_HIDDEN, "Force VMPI to run in SDK mode." ) +VMPI_PARAM( mpi_UseSDKDistributor, VMPI_PARAM_SDK_HIDDEN, "Use the SDK work unit distributor. Optimized for low numbers of workers and higher latency. Note that this will automatically be used in SDK distributions." ) +VMPI_PARAM( mpi_UseDefaultDistributor, VMPI_PARAM_SDK_HIDDEN, "Use the default work unit distributor. Optimized for high numbers of workers, higher numbers of work units, and lower latency. Note that this will automatically be used in non-SDK distributions." ) +VMPI_PARAM( mpi_NoTimeout, VMPI_PARAM_SDK_HIDDEN, "Don't timeout VMPI sockets. Used for testing." ) +VMPI_PARAM( mpi_DontSetThreadPriorities, VMPI_PARAM_SDK_HIDDEN, "Don't set worker thread priorities to idle." ) +VMPI_PARAM( mpi_GroupPackets, VMPI_PARAM_SDK_HIDDEN, "Delay and group some of the worker packets instead of sending immediately." ) +VMPI_PARAM( mpi_Stats, VMPI_PARAM_SDK_HIDDEN, "Enables the use of a database to store compile statistics." ) +VMPI_PARAM( mpi_Stats_TextOutput, VMPI_PARAM_SDK_HIDDEN, "Enables the workers storing all of their text output into the stats database." ) +VMPI_PARAM( mpi_pw, VMPI_PARAM_SDK_HIDDEN, "Non-SDK only. Sets a password on the VMPI job. Workers must also use the same -mpi_pw [password] argument or else the master will ignore their requests to join the job." ) +VMPI_PARAM( mpi_CalcShuffleCRC, VMPI_PARAM_SDK_HIDDEN, "Calculate a CRC for shuffled work unit arrays in the SDK work unit distributor." ) +VMPI_PARAM( mpi_Job_Watch, VMPI_PARAM_SDK_HIDDEN, "Automatically launches vmpi_job_watch.exe on the job." ) +VMPI_PARAM( mpi_Local, VMPI_PARAM_SDK_HIDDEN, "Similar to -mpi_AutoLocalWorker, but the automatically-spawned worker's console window is hidden." ) \ No newline at end of file diff --git a/mp/src/utils/vrad/disp_vrad.cpp b/mp/src/utils/vrad/disp_vrad.cpp new file mode 100644 index 00000000..e1ef3a2e --- /dev/null +++ b/mp/src/utils/vrad/disp_vrad.cpp @@ -0,0 +1,332 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "disp_vrad.h" +#include "utllinkedlist.h" +#include "utlvector.h" +#include "iscratchpad3d.h" +#include "scratchpadutils.h" + + +//#define USE_SCRATCHPAD +#if defined( USE_SCRATCHPAD ) + static IScratchPad3D *g_pPad = 0; +#endif + + +int FindNeighborCornerVert( CCoreDispInfo *pDisp, const Vector &vTest ) +{ + CDispUtilsHelper *pDispHelper = pDisp; + + int iClosest = 0; + float flClosest = 1e24; + for ( int iCorner=0; iCorner < 4; iCorner++ ) + { + // Has it been touched? + CVertIndex cornerVert = pDispHelper->GetPowerInfo()->GetCornerPointIndex( iCorner ); + int iCornerVert = pDispHelper->VertIndexToInt( cornerVert ); + const Vector &vCornerVert = pDisp->GetVert( iCornerVert ); + + float flDist = vCornerVert.DistTo( vTest ); + if ( flDist < flClosest ) + { + iClosest = iCorner; + flClosest = flDist; + } + } + + if ( flClosest <= 0.1f ) + return iClosest; + else + return -1; +} + + +int GetAllNeighbors( const CCoreDispInfo *pDisp, int iNeighbors[512] ) +{ + int nNeighbors = 0; + + // Check corner neighbors. + for ( int iCorner=0; iCorner < 4; iCorner++ ) + { + const CDispCornerNeighbors *pCorner = pDisp->GetCornerNeighbors( iCorner ); + + for ( int i=0; i < pCorner->m_nNeighbors; i++ ) + { + if ( nNeighbors < _ARRAYSIZE( iNeighbors ) ) + iNeighbors[nNeighbors++] = pCorner->m_Neighbors[i]; + } + } + + for ( int iEdge=0; iEdge < 4; iEdge++ ) + { + const CDispNeighbor *pEdge = pDisp->GetEdgeNeighbor( iEdge ); + + for ( int i=0; i < 2; i++ ) + { + if ( pEdge->m_SubNeighbors[i].IsValid() ) + if ( nNeighbors < 512 ) + iNeighbors[nNeighbors++] = pEdge->m_SubNeighbors[i].GetNeighborIndex(); + } + } + + return nNeighbors; +} + + +void BlendCorners( CCoreDispInfo **ppListBase, int listSize ) +{ + CUtlVector nbCornerVerts; + + for ( int iDisp=0; iDisp < listSize; iDisp++ ) + { + CCoreDispInfo *pDisp = ppListBase[iDisp]; + + int iNeighbors[512]; + int nNeighbors = GetAllNeighbors( pDisp, iNeighbors ); + + // Make sure we have room for all the neighbors. + nbCornerVerts.RemoveAll(); + nbCornerVerts.EnsureCapacity( nNeighbors ); + nbCornerVerts.AddMultipleToTail( nNeighbors ); + + // For each corner. + for ( int iCorner=0; iCorner < 4; iCorner++ ) + { + // Has it been touched? + CVertIndex cornerVert = pDisp->GetCornerPointIndex( iCorner ); + int iCornerVert = pDisp->VertIndexToInt( cornerVert ); + const Vector &vCornerVert = pDisp->GetVert( iCornerVert ); + + // For each displacement sharing this corner.. + Vector vAverage = pDisp->GetNormal( iCornerVert ); + + for ( int iNeighbor=0; iNeighbor < nNeighbors; iNeighbor++ ) + { + int iNBListIndex = iNeighbors[iNeighbor]; + CCoreDispInfo *pNeighbor = ppListBase[iNBListIndex]; + + // Find out which vert it is on the neighbor. + int iNBCorner = FindNeighborCornerVert( pNeighbor, vCornerVert ); + if ( iNBCorner == -1 ) + { + nbCornerVerts[iNeighbor] = -1; // remove this neighbor from the list. + } + else + { + CVertIndex viNBCornerVert = pNeighbor->GetCornerPointIndex( iNBCorner ); + int iNBVert = pNeighbor->VertIndexToInt( viNBCornerVert ); + nbCornerVerts[iNeighbor] = iNBVert; + vAverage += pNeighbor->GetNormal( iNBVert ); + } + } + + + // Blend all the neighbor normals with this one. + VectorNormalize( vAverage ); + pDisp->SetNormal( iCornerVert, vAverage ); + +#if defined( USE_SCRATCHPAD ) + ScratchPad_DrawArrowSimple( + g_pPad, + pDisp->GetVert( iCornerVert ), + pDisp->GetNormal( iCornerVert ), + Vector( 0, 0, 1 ), + 25 ); +#endif + + for ( int iNeighbor=0; iNeighbor < nNeighbors; iNeighbor++ ) + { + int iNBListIndex = iNeighbors[iNeighbor]; + if ( nbCornerVerts[iNeighbor] == -1 ) + continue; + + CCoreDispInfo *pNeighbor = ppListBase[iNBListIndex]; + pNeighbor->SetNormal( nbCornerVerts[iNeighbor], vAverage ); + } + } + } +} + + +void BlendTJuncs( CCoreDispInfo **ppListBase, int listSize ) +{ + for ( int iDisp=0; iDisp < listSize; iDisp++ ) + { + CCoreDispInfo *pDisp = ppListBase[iDisp]; + + for ( int iEdge=0; iEdge < 4; iEdge++ ) + { + CDispNeighbor *pEdge = pDisp->GetEdgeNeighbor( iEdge ); + + CVertIndex viMidPoint = pDisp->GetEdgeMidPoint( iEdge ); + int iMidPoint = pDisp->VertIndexToInt( viMidPoint ); + + if ( pEdge->m_SubNeighbors[0].IsValid() && pEdge->m_SubNeighbors[1].IsValid() ) + { + const Vector &vMidPoint = pDisp->GetVert( iMidPoint ); + + CCoreDispInfo *pNeighbor1 = ppListBase[pEdge->m_SubNeighbors[0].GetNeighborIndex()]; + CCoreDispInfo *pNeighbor2 = ppListBase[pEdge->m_SubNeighbors[1].GetNeighborIndex()]; + + int iNBCorners[2]; + iNBCorners[0] = FindNeighborCornerVert( pNeighbor1, vMidPoint ); + iNBCorners[1] = FindNeighborCornerVert( pNeighbor2, vMidPoint ); + + if ( iNBCorners[0] != -1 && iNBCorners[1] != -1 ) + { + CVertIndex viNBCorners[2] = + { + pNeighbor1->GetCornerPointIndex( iNBCorners[0] ), + pNeighbor2->GetCornerPointIndex( iNBCorners[1] ) + }; + + Vector vAverage = pDisp->GetNormal( iMidPoint ); + vAverage += pNeighbor1->GetNormal( viNBCorners[0] ); + vAverage += pNeighbor2->GetNormal( viNBCorners[1] ); + + VectorNormalize( vAverage ); + pDisp->SetNormal( iMidPoint, vAverage ); + pNeighbor1->SetNormal( viNBCorners[0], vAverage ); + pNeighbor2->SetNormal( viNBCorners[1], vAverage ); + +#if defined( USE_SCRATCHPAD ) + ScratchPad_DrawArrowSimple( g_pPad, pDisp->GetVert( iMidPoint ), pDisp->GetNormal( iMidPoint ), Vector( 0, 1, 1 ), 25 ); +#endif + } + } + } + } +} + + +void BlendEdges( CCoreDispInfo **ppListBase, int listSize ) +{ + for ( int iDisp=0; iDisp < listSize; iDisp++ ) + { + CCoreDispInfo *pDisp = ppListBase[iDisp]; + + for ( int iEdge=0; iEdge < 4; iEdge++ ) + { + CDispNeighbor *pEdge = pDisp->GetEdgeNeighbor( iEdge ); + + for ( int iSub=0; iSub < 2; iSub++ ) + { + CDispSubNeighbor *pSub = &pEdge->m_SubNeighbors[iSub]; + if ( !pSub->IsValid() ) + continue; + + CCoreDispInfo *pNeighbor = ppListBase[ pSub->GetNeighborIndex() ]; + + int iEdgeDim = g_EdgeDims[iEdge]; + + CDispSubEdgeIterator it; + it.Start( pDisp, iEdge, iSub, true ); + + // Get setup on the first corner vert. + it.Next(); + CVertIndex viPrevPos = it.GetVertIndex(); + + while ( it.Next() ) + { + // Blend the two. + if ( !it.IsLastVert() ) + { + Vector vAverage = pDisp->GetNormal( it.GetVertIndex() ) + pNeighbor->GetNormal( it.GetNBVertIndex() ); + VectorNormalize( vAverage ); + + pDisp->SetNormal( it.GetVertIndex(), vAverage ); + pNeighbor->SetNormal( it.GetNBVertIndex(), vAverage ); + +#if defined( USE_SCRATCHPAD ) + ScratchPad_DrawArrowSimple( g_pPad, pDisp->GetVert( it.GetVertIndex() ), pDisp->GetNormal( it.GetVertIndex() ), Vector( 1, 0, 0 ), 25 ); +#endif + } + + // Now blend the in-between verts (if this edge is high-res). + int iPrevPos = viPrevPos[ !iEdgeDim ]; + int iCurPos = it.GetVertIndex()[ !iEdgeDim ]; + + for ( int iTween = iPrevPos+1; iTween < iCurPos; iTween++ ) + { + float flPercent = RemapVal( iTween, iPrevPos, iCurPos, 0, 1 ); + Vector vNormal; + VectorLerp( pDisp->GetNormal( viPrevPos ), pDisp->GetNormal( it.GetVertIndex() ), flPercent, vNormal ); + VectorNormalize( vNormal ); + + CVertIndex viTween; + viTween[iEdgeDim] = it.GetVertIndex()[ iEdgeDim ]; + viTween[!iEdgeDim] = iTween; + pDisp->SetNormal( viTween, vNormal ); + +#if defined( USE_SCRATCHPAD ) + ScratchPad_DrawArrowSimple( g_pPad, pDisp->GetVert( viTween ), pDisp->GetNormal( viTween ), Vector( 1, 0.5, 0 ), 25 ); +#endif + } + + viPrevPos = it.GetVertIndex(); + } + } + } + } +} + + +#if defined( USE_SCRATCHPAD ) + void ScratchPad_DrawOriginalNormals( const CCoreDispInfo *pListBase, int listSize ) + { + for ( int i=0; i < listSize; i++ ) + { + const CCoreDispInfo *pDisp = &pListBase[i]; + const CPowerInfo *pPowerInfo = pDisp->GetPowerInfo(); + + // Draw the triangles. + for ( int iTri=0; iTri < pPowerInfo->GetNumTriInfos(); iTri++ ) + { + const CTriInfo *pTriInfo = pPowerInfo->GetTriInfo( iTri ); + + for ( int iLine=0; iLine < 3; iLine++ ) + { + const Vector &v1 = pDisp->GetVert( pTriInfo->m_Indices[iLine] ); + const Vector &v2 = pDisp->GetVert( pTriInfo->m_Indices[(iLine+1)%3] ); + + g_pPad->DrawLine( CSPVert( v1 ), CSPVert( v2 ) ); + } + } + + // Draw the normals. + CDispCircumferenceIterator it( pPowerInfo->GetSideLength() ); + while ( it.Next() ) + { + ScratchPad_DrawArrowSimple( + g_pPad, + pDisp->GetVert( it.GetVertIndex() ), + pDisp->GetNormal( it.GetVertIndex() ), + Vector( 0, 1, 0 ), + 15 ); + } + } + } +#endif + + +void SmoothNeighboringDispSurfNormals( CCoreDispInfo **ppListBase, int listSize ) +{ +//#if defined( USE_SCRATCHPAD ) +// g_pPad = ScratchPad3D_Create(); +// ScratchPad_DrawOriginalNormals( pListBase, listSize ); +//#endif + + BlendTJuncs( ppListBase, listSize ); + + BlendCorners( ppListBase, listSize ); + + BlendEdges( ppListBase, listSize ); +} + + + diff --git a/mp/src/utils/vrad/disp_vrad.h b/mp/src/utils/vrad/disp_vrad.h new file mode 100644 index 00000000..ec6bb838 --- /dev/null +++ b/mp/src/utils/vrad/disp_vrad.h @@ -0,0 +1,22 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef DISP_VRAD_H +#define DISP_VRAD_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "builddisp.h" + + +// Blend the normals of neighboring displacement surfaces so they match at edges and corners. +void SmoothNeighboringDispSurfNormals( CCoreDispInfo **ppListBase, int listSize ); + + +#endif // DISP_VRAD_H diff --git a/mp/src/utils/vrad/iincremental.h b/mp/src/utils/vrad/iincremental.h new file mode 100644 index 00000000..fbde8c86 --- /dev/null +++ b/mp/src/utils/vrad/iincremental.h @@ -0,0 +1,71 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef IINCREMENTAL_H +#define IINCREMENTAL_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "mathlib/vector.h" +#include "utlvector.h" + + +typedef unsigned short IncrementalLightID; + + +// Incremental lighting manager. +class IIncremental +{ +// IIncremental overrides. +public: + + virtual ~IIncremental() {} + + // Sets up for incremental mode. The BSP file (in bsplib) should be loaded + // already so it can detect if the incremental file is up to date. + virtual bool Init( char const *pBSPFilename, char const *pIncrementalFilename ) = 0; + + // Prepare to light. You must call Init once, but then you can + // do as many Prepare/AddLight/Finalize phases as you want. + virtual bool PrepareForLighting() = 0; + + // Called every time light is added to a face. + // NOTE: This is the ONLY threadsafe function in IIncremental. + virtual void AddLightToFace( + IncrementalLightID lightID, + int iFace, + int iSample, + int lmSize, + float dot, + int iThread ) = 0; + + // Called when it's done applying light from the specified light to the specified face. + virtual void FinishFace ( + IncrementalLightID lightID, + int iFace, + int iThread ) = 0; + + // For each face that was changed during the lighting process, save out + // new data for it in the incremental file. + // Returns false if the incremental lighting isn't active. + virtual bool Finalize() = 0; + + // Grows touched to a size of 'numfaces' and sets each byte to 0 or 1 telling + // if the face's lightmap was updated in Finalize. + virtual void GetFacesTouched( CUtlVector &touched ) = 0; + + // This saves the .r0 file and updates the lighting in the BSP file. + virtual bool Serialize() = 0; +}; + + +extern IIncremental* GetIncremental(); + + +#endif // IINCREMENTAL_H diff --git a/mp/src/utils/vrad/imagepacker.cpp b/mp/src/utils/vrad/imagepacker.cpp new file mode 100644 index 00000000..612b0d57 --- /dev/null +++ b/mp/src/utils/vrad/imagepacker.cpp @@ -0,0 +1,141 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// The copyright to the contents herein is the property of Valve, L.L.C. +// The contents may be used and/or copied only with the written permission of +// Valve, L.L.C., or in accordance with the terms and conditions stipulated in +// the agreement/contract under which the contents have been supplied. +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// $NoKeywords: $ +//============================================================================= + +#include "vrad.h" +#include "imagepacker.h" + + +bool CImagePacker::Reset( int maxLightmapWidth, int maxLightmapHeight ) +{ + int i; + + Assert( maxLightmapWidth <= MAX_MAX_LIGHTMAP_WIDTH ); + + m_MaxLightmapWidth = maxLightmapWidth; + m_MaxLightmapHeight = maxLightmapHeight; + + m_MaxBlockWidth = maxLightmapWidth + 1; + m_MaxBlockHeight = maxLightmapHeight + 1; + + m_AreaUsed = 0; + m_MinimumHeight = -1; + for( i = 0; i < m_MaxLightmapWidth; i++ ) + { + m_pLightmapWavefront[i] = -1; + } + return true; +} + + +inline int CImagePacker::GetMaxYIndex( int firstX, int width ) +{ + int maxY = -1; + int maxYIndex = 0; + for( int x = firstX; x < firstX + width; ++x ) + { + // NOTE: Want the equals here since we'll never be able to fit + // in between the multiple instances of maxY + if( m_pLightmapWavefront[x] >= maxY ) + { + maxY = m_pLightmapWavefront[x]; + maxYIndex = x; + } + } + return maxYIndex; +} + + +bool CImagePacker::AddBlock( int width, int height, int *returnX, int *returnY ) +{ + // If we've already determined that a block this big couldn't fit + // then blow off checking again... + if ( ( width >= m_MaxBlockWidth ) && ( height >= m_MaxBlockHeight ) ) + return false; + + int bestX = -1; + int maxYIdx; + int outerX = 0; + int outerMinY = m_MaxLightmapHeight; + int lastX = m_MaxLightmapWidth - width; + int lastMaxYVal = -2; + while (outerX <= lastX) + { + // Skip all tiles that have the last Y value, these + // aren't going to change our min Y value + if (m_pLightmapWavefront[outerX] == lastMaxYVal) + { + ++outerX; + continue; + } + + maxYIdx = GetMaxYIndex( outerX, width ); + lastMaxYVal = m_pLightmapWavefront[maxYIdx]; + if (outerMinY > lastMaxYVal) + { + outerMinY = lastMaxYVal; + bestX = outerX; + } + outerX = maxYIdx + 1; + } + + if( bestX == -1 ) + { + // If we failed to add it, remember the block size that failed + // *only if both dimensions are smaller*!! + // Just because a 1x10 block failed, doesn't mean a 10x1 block will fail + if ( ( width <= m_MaxBlockWidth ) && ( height <= m_MaxBlockHeight ) ) + { + m_MaxBlockWidth = width; + m_MaxBlockHeight = height; + } + + return false; + } + + // Set the return positions for the block. + *returnX = bestX; + *returnY = outerMinY + 1; + + // Check if it actually fit height-wise. + // hack + // if( *returnY + height > maxLightmapHeight ) + if( *returnY + height >= m_MaxLightmapHeight - 1 ) + { + if ( ( width <= m_MaxBlockWidth ) && ( height <= m_MaxBlockHeight ) ) + { + m_MaxBlockWidth = width; + m_MaxBlockHeight = height; + } + + return false; + } + + // It fit! + // Keep up with the smallest possible size for the image so far. + if( *returnY + height > m_MinimumHeight ) + m_MinimumHeight = *returnY + height; + + // Update the wavefront info. + int x; + for( x = bestX; x < bestX + width; x++ ) + { + m_pLightmapWavefront[x] = outerMinY + height; + } + + // AddBlockToLightmapImage( *returnX, *returnY, width, height ); + m_AreaUsed += width * height; + + return true; +} + diff --git a/mp/src/utils/vrad/imagepacker.h b/mp/src/utils/vrad/imagepacker.h new file mode 100644 index 00000000..0ee1f50e --- /dev/null +++ b/mp/src/utils/vrad/imagepacker.h @@ -0,0 +1,51 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// The copyright to the contents herein is the property of Valve, L.L.C. +// The contents may be used and/or copied only with the written permission of +// Valve, L.L.C., or in accordance with the terms and conditions stipulated in +// the agreement/contract under which the contents have been supplied. +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// $NoKeywords: $ +//============================================================================= + +#ifndef IMAGEPACKER_H +#define IMAGEPACKER_H + +#ifdef _WIN32 +#pragma once +#endif + +#define MAX_MAX_LIGHTMAP_WIDTH 2048 + + +//----------------------------------------------------------------------------- +// This packs a single lightmap +//----------------------------------------------------------------------------- +class CImagePacker +{ +public: + bool Reset( int maxLightmapWidth, int maxLightmapHeight ); + bool AddBlock( int width, int height, int *returnX, int *returnY ); + +protected: + int GetMaxYIndex( int firstX, int width ); + + int m_MaxLightmapWidth; + int m_MaxLightmapHeight; + int m_pLightmapWavefront[MAX_MAX_LIGHTMAP_WIDTH]; + int m_AreaUsed; + int m_MinimumHeight; + + // For optimization purposes: + // These store the width + height of the first image + // that was unable to be stored in this image + int m_MaxBlockWidth; + int m_MaxBlockHeight; +}; + + +#endif // IMAGEPACKER_H diff --git a/mp/src/utils/vrad/incremental.cpp b/mp/src/utils/vrad/incremental.cpp new file mode 100644 index 00000000..9dd877e0 --- /dev/null +++ b/mp/src/utils/vrad/incremental.cpp @@ -0,0 +1,766 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +#include "incremental.h" +#include "lightmap.h" + + + +static bool g_bFileError = false; + + +// -------------------------------------------------------------------------------- // +// Static helpers. +// -------------------------------------------------------------------------------- // + +static bool CompareLights( dworldlight_t *a, dworldlight_t *b ) +{ + static float flEpsilon = 1e-7; + + bool a1 = VectorsAreEqual( a->origin, b->origin, flEpsilon ); + bool a2 = VectorsAreEqual( a->intensity, b->intensity, 1.1f ); // intensities are huge numbers + bool a3 = VectorsAreEqual( a->normal, b->normal, flEpsilon ); + bool a4 = fabs( a->constant_attn - b->constant_attn ) < flEpsilon; + bool a5 = fabs( a->linear_attn - b->linear_attn ) < flEpsilon; + bool a6 = fabs( a->quadratic_attn - b->quadratic_attn ) < flEpsilon; + bool a7 = fabs( float( a->flags - b->flags ) ) < flEpsilon; + bool a8 = fabs( a->stopdot - b->stopdot ) < flEpsilon; + bool a9 = fabs( a->stopdot2 - b->stopdot2 ) < flEpsilon; + bool a10 = fabs( a->exponent - b->exponent ) < flEpsilon; + bool a11 = fabs( a->radius - b->radius ) < flEpsilon; + + return a1 && a2 && a3 && a4 && a5 && a6 && a7 && a8 && a9 && a10 && a11; +} + + +long FileOpen( char const *pFilename, bool bRead ) +{ + g_bFileError = false; + return (long)g_pFileSystem->Open( pFilename, bRead ? "rb" : "wb" ); +} + + +void FileClose( long fp ) +{ + if( fp ) + g_pFileSystem->Close( (FILE*)fp ); +} + + +// Returns true if there was an error reading from the file. +bool FileError() +{ + return g_bFileError; +} + +static inline void FileRead( long fp, void *pOut, int size ) +{ + if( g_bFileError || g_pFileSystem->Read( pOut, size, (FileHandle_t)fp ) != size ) + { + g_bFileError = true; + memset( pOut, 0, size ); + } +} + + +template +static inline void FileRead( long fp, T &out ) +{ + FileRead( fp, &out, sizeof(out) ); +} + + +static inline void FileWrite( long fp, void const *pData, int size ) +{ + if( g_bFileError || g_pFileSystem->Write( pData, size, (FileHandle_t)fp ) != size ) + { + g_bFileError = true; + } +} + + +template +static inline void FileWrite( long fp, T out ) +{ + FileWrite( fp, &out, sizeof(out) ); +} + + +IIncremental* GetIncremental() +{ + static CIncremental inc; + return &inc; +} + + +// -------------------------------------------------------------------------------- // +// CIncremental. +// -------------------------------------------------------------------------------- // + +CIncremental::CIncremental() +{ + m_TotalMemory = 0; + m_pIncrementalFilename = NULL; + m_pBSPFilename = NULL; + m_bSuccessfulRun = false; +} + + +CIncremental::~CIncremental() +{ +} + + +bool CIncremental::Init( char const *pBSPFilename, char const *pIncrementalFilename ) +{ + m_pBSPFilename = pBSPFilename; + m_pIncrementalFilename = pIncrementalFilename; + return true; +} + + +bool CIncremental::PrepareForLighting() +{ + if( !m_pBSPFilename ) + return false; + + // Clear the touched faces list. + m_FacesTouched.SetSize( numfaces ); + memset( m_FacesTouched.Base(), 0, numfaces ); + + // If we haven't done a complete successful run yet, then we either haven't + // loaded the lights, or a run was aborted and our lights are half-done so we + // should reload them. + if( !m_bSuccessfulRun ) + LoadIncrementalFile(); + + // unmatched = a list of the lights we have + CUtlLinkedList unmatched; + for( int i=m_Lights.Head(); i != m_Lights.InvalidIndex(); i = m_Lights.Next(i) ) + unmatched.AddToTail( i ); + + // Match the light lists and get rid of lights that we already have all the data for. + directlight_t *pNext; + directlight_t **pPrev = &activelights; + for( directlight_t *dl=activelights; dl != NULL; dl = pNext ) + { + pNext = dl->next; + + //float flClosest = 3000000000; + //CIncLight *pClosest = 0; + + // Look for this light in our light list. + int iNextUnmatched, iUnmatched; + for( iUnmatched=unmatched.Head(); iUnmatched != unmatched.InvalidIndex(); iUnmatched = iNextUnmatched ) + { + iNextUnmatched = unmatched.Next( iUnmatched ); + + CIncLight *pLight = m_Lights[ unmatched[iUnmatched] ]; + + //float flTest = (pLight->m_Light.origin - dl->light.origin).Length(); + //if( flTest < flClosest ) + //{ + // flClosest = flTest; + // pClosest = pLight; + //} + + if( CompareLights( &dl->light, &pLight->m_Light ) ) + { + unmatched.Remove( iUnmatched ); + + // Ok, we have this light's data already, yay! + // Get rid of it from the active light list. + *pPrev = dl->next; + free( dl ); + dl = 0; + break; + } + } + + //bool bTest=false; + //if(bTest) + // CompareLights( &dl->light, &pClosest->m_Light ); + + if( iUnmatched == unmatched.InvalidIndex() ) + pPrev = &dl->next; + } + + // Remove any of our lights that were unmatched. + for( int iUnmatched=unmatched.Head(); iUnmatched != unmatched.InvalidIndex(); iUnmatched = unmatched.Next( iUnmatched ) ) + { + CIncLight *pLight = m_Lights[ unmatched[iUnmatched] ]; + + // First tag faces that it touched so they get recomposited. + for( unsigned short iFace=pLight->m_LightFaces.Head(); iFace != pLight->m_LightFaces.InvalidIndex(); iFace = pLight->m_LightFaces.Next( iFace ) ) + { + m_FacesTouched[ pLight->m_LightFaces[iFace]->m_FaceIndex ] = 1; + } + + delete pLight; + m_Lights.Remove( unmatched[iUnmatched] ); + } + + // Now add a light structure for each new light. + AddLightsForActiveLights(); + + return true; +} + + +bool CIncremental::ReadIncrementalHeader( long fp, CIncrementalHeader *pHeader ) +{ + int version; + FileRead( fp, version ); + if( version != INCREMENTALFILE_VERSION ) + return false; + + int nFaces; + FileRead( fp, nFaces ); + + pHeader->m_FaceLightmapSizes.SetSize( nFaces ); + FileRead( fp, pHeader->m_FaceLightmapSizes.Base(), sizeof(CIncrementalHeader::CLMSize) * nFaces ); + + return !FileError(); +} + + +bool CIncremental::WriteIncrementalHeader( long fp ) +{ + int version = INCREMENTALFILE_VERSION; + FileWrite( fp, version ); + + int nFaces = numfaces; + FileWrite( fp, nFaces ); + + CIncrementalHeader hdr; + hdr.m_FaceLightmapSizes.SetSize( nFaces ); + + for( int i=0; i < nFaces; i++ ) + { + hdr.m_FaceLightmapSizes[i].m_Width = g_pFaces[i].m_LightmapTextureSizeInLuxels[0]; + hdr.m_FaceLightmapSizes[i].m_Height = g_pFaces[i].m_LightmapTextureSizeInLuxels[1]; + } + + FileWrite( fp, hdr.m_FaceLightmapSizes.Base(), sizeof(CIncrementalHeader::CLMSize) * nFaces ); + + return !FileError(); +} + + +bool CIncremental::IsIncrementalFileValid() +{ + long fp = FileOpen( m_pIncrementalFilename, true ); + if( !fp ) + return false; + + bool bValid = false; + CIncrementalHeader hdr; + if( ReadIncrementalHeader( fp, &hdr ) ) + { + // If the number of faces is the same and their lightmap sizes are the same, + // then this file is considered a legitimate incremental file. + if( hdr.m_FaceLightmapSizes.Count() == numfaces ) + { + int i; + for( i=0; i < numfaces; i++ ) + { + if( hdr.m_FaceLightmapSizes[i].m_Width != g_pFaces[i].m_LightmapTextureSizeInLuxels[0] || + hdr.m_FaceLightmapSizes[i].m_Height != g_pFaces[i].m_LightmapTextureSizeInLuxels[1] ) + { + break; + } + } + + // Were all faces valid? + if( i == numfaces ) + bValid = true; + } + } + + FileClose( fp ); + return bValid && !FileError(); +} + + +void CIncremental::AddLightToFace( + IncrementalLightID lightID, + int iFace, + int iSample, + int lmSize, + float dot, + int iThread ) +{ + // If we're not being used, don't do anything. + if( !m_pIncrementalFilename ) + return; + + CIncLight *pLight = m_Lights[lightID]; + + // Check for the 99.99% case in which the face already exists. + CLightFace *pFace; + if( pLight->m_pCachedFaces[iThread] && + pLight->m_pCachedFaces[iThread]->m_FaceIndex == iFace ) + { + pFace = pLight->m_pCachedFaces[iThread]; + } + else + { + bool bNew; + + EnterCriticalSection( &pLight->m_CS ); + pFace = pLight->FindOrCreateLightFace( iFace, lmSize, &bNew ); + LeaveCriticalSection( &pLight->m_CS ); + + pLight->m_pCachedFaces[iThread] = pFace; + + if( bNew ) + m_TotalMemory += pFace->m_LightValues.Count() * sizeof( pFace->m_LightValues[0] ); + } + + // Add this into the light's data. + pFace->m_LightValues[iSample].m_Dot = dot; +} + + +unsigned short DecodeCharOrShort( CUtlBuffer *pIn ) +{ + unsigned short val = pIn->GetUnsignedChar(); + if( val & 0x80 ) + { + val = ((val & 0x7F) << 8) | pIn->GetUnsignedChar(); + } + + return val; +} + + +void EncodeCharOrShort( CUtlBuffer *pBuf, unsigned short val ) +{ + if( (val & 0xFF80) == 0 ) + { + pBuf->PutUnsignedChar( (unsigned char)val ); + } + else + { + if( val > 32767 ) + val = 32767; + + pBuf->PutUnsignedChar( (val >> 8) | 0x80 ); + pBuf->PutUnsignedChar( val & 0xFF ); + } +} + + +void DecompressLightData( CUtlBuffer *pIn, CUtlVector *pOut ) +{ + int iOut = 0; + while( pIn->TellGet() < pIn->TellPut() ) + { + unsigned char runLength = pIn->GetUnsignedChar(); + unsigned short usVal = DecodeCharOrShort( pIn ); + + while( runLength > 0 ) + { + --runLength; + + pOut->Element(iOut).m_Dot = usVal; + ++iOut; + } + } +} + +#ifdef _WIN32 +#pragma warning (disable:4701) +#endif + +void CompressLightData( + CLightValue const *pValues, + int nValues, + CUtlBuffer *pBuf ) +{ + unsigned char runLength=0; + unsigned short flLastValue; + + for( int i=0; i < nValues; i++ ) + { + unsigned short flCurValue = (unsigned short)pValues[i].m_Dot; + + if( i == 0 ) + { + flLastValue = flCurValue; + runLength = 1; + } + else if( flCurValue == flLastValue && runLength < 255 ) + { + ++runLength; + } + else + { + pBuf->PutUnsignedChar( runLength ); + EncodeCharOrShort( pBuf, flLastValue ); + + flLastValue = flCurValue; + runLength = 1; + } + } + + // Write the end.. + if( runLength ) + { + pBuf->PutUnsignedChar( runLength ); + EncodeCharOrShort( pBuf, flLastValue ); + } +} + +#ifdef _WIN32 +#pragma warning (default:4701) +#endif + +void MultiplyValues( CUtlVector &values, float scale ) +{ + for( int i=0; i < values.Count(); i++ ) + values[i].m_Dot *= scale; +} + + +void CIncremental::FinishFace( + IncrementalLightID lightID, + int iFace, + int iThread ) +{ + CIncLight *pLight = m_Lights[lightID]; + + // Check for the 99.99% case in which the face already exists. + CLightFace *pFace; + if( pLight->m_pCachedFaces[iThread] && pLight->m_pCachedFaces[iThread]->m_FaceIndex == iFace ) + { + pFace = pLight->m_pCachedFaces[iThread]; + + // Compress the data. + MultiplyValues( pFace->m_LightValues, pLight->m_flMaxIntensity ); + + pFace->m_CompressedData.SeekPut( CUtlBuffer::SEEK_HEAD, 0 ); + CompressLightData( + pFace->m_LightValues.Base(), + pFace->m_LightValues.Count(), + &pFace->m_CompressedData ); + +#if 0 + // test decompression + CUtlVector test; + test.SetSize( 2048 ); + pFace->m_CompressedData.SeekGet( CUtlBuffer::SEEK_HEAD, 0 ); + DecompressLightData( &pFace->m_CompressedData, &test ); +#endif + + if( pFace->m_CompressedData.TellPut() == 0 ) + { + // No contribution.. delete this face from the light. + EnterCriticalSection( &pLight->m_CS ); + pLight->m_LightFaces.Remove( pFace->m_LightFacesIndex ); + delete pFace; + LeaveCriticalSection( &pLight->m_CS ); + } + else + { + // Discard the uncompressed data. + pFace->m_LightValues.Purge(); + m_FacesTouched[ pFace->m_FaceIndex ] = 1; + } + } +} + + +bool CIncremental::Finalize() +{ + // If we're not being used, don't do anything. + if( !m_pIncrementalFilename || !m_pBSPFilename ) + return false; + + CUtlVector faceLights; + LinkLightsToFaces( faceLights ); + + Vector faceLight[(MAX_LIGHTMAP_DIM_WITHOUT_BORDER+2) * (MAX_LIGHTMAP_DIM_WITHOUT_BORDER+2)]; + CUtlVector faceLightValues; + faceLightValues.SetSize( (MAX_LIGHTMAP_DIM_WITHOUT_BORDER+2) * (MAX_LIGHTMAP_DIM_WITHOUT_BORDER+2) ); + + // Only update the faces we've touched. + for( int facenum = 0; facenum < numfaces; facenum++ ) + { + if( !m_FacesTouched[facenum] || !faceLights[facenum].Count() ) + continue; + + int w = g_pFaces[facenum].m_LightmapTextureSizeInLuxels[0]+1; + int h = g_pFaces[facenum].m_LightmapTextureSizeInLuxels[1]+1; + int nLuxels = w * h; + assert( nLuxels <= sizeof(faceLight) / sizeof(faceLight[0]) ); + + // Clear the lighting for this face. + memset( faceLight, 0, nLuxels * sizeof(Vector) ); + + // Composite all the light contributions. + for( int iFace=0; iFace < faceLights[facenum].Count(); iFace++ ) + { + CLightFace *pFace = faceLights[facenum][iFace]; + + pFace->m_CompressedData.SeekGet( CUtlBuffer::SEEK_HEAD, 0 ); + DecompressLightData( &pFace->m_CompressedData, &faceLightValues ); + + for( int iSample=0; iSample < nLuxels; iSample++ ) + { + float flDot = faceLightValues[iSample].m_Dot; + if( flDot ) + { + VectorMA( + faceLight[iSample], + flDot / pFace->m_pLight->m_flMaxIntensity, + pFace->m_pLight->m_Light.intensity, + faceLight[iSample] ); + } + } + } + + // Convert to the floating-point representation in the BSP file. + Vector *pSrc = faceLight; + unsigned char *pDest = &(*pdlightdata)[ g_pFaces[facenum].lightofs ]; + + for( int iSample=0; iSample < nLuxels; iSample++ ) + { + VectorToColorRGBExp32( *pSrc, *( ColorRGBExp32 *)pDest ); + pDest += 4; + pSrc++; + } + } + + m_bSuccessfulRun = true; + return true; +} + + +void CIncremental::GetFacesTouched( CUtlVector &touched ) +{ + touched.CopyArray( m_FacesTouched.Base(), m_FacesTouched.Count() ); +} + + +bool CIncremental::Serialize() +{ + if( !SaveIncrementalFile() ) + return false; + + WriteBSPFile( (char*)m_pBSPFilename ); + return true; +} + + +void CIncremental::Term() +{ + m_Lights.PurgeAndDeleteElements(); + m_TotalMemory = 0; +} + + +void CIncremental::AddLightsForActiveLights() +{ + // Create our lights. + for( directlight_t *dl=activelights; dl != NULL; dl = dl->next ) + { + CIncLight *pLight = new CIncLight; + dl->m_IncrementalID = m_Lights.AddToTail( pLight ); + + // Copy the light information. + pLight->m_Light = dl->light; + pLight->m_flMaxIntensity = max( dl->light.intensity[0], max( dl->light.intensity[1], dl->light.intensity[2] ) ); + } +} + + +bool CIncremental::LoadIncrementalFile() +{ + Term(); + + if( !IsIncrementalFileValid() ) + return false; + + long fp = FileOpen( m_pIncrementalFilename, true ); + if( !fp ) + return false; + + // Read the header. + CIncrementalHeader hdr; + if( !ReadIncrementalHeader( fp, &hdr ) ) + { + FileClose( fp ); + return false; + } + + + // Read the lights. + int nLights; + FileRead( fp, nLights ); + for( int iLight=0; iLight < nLights; iLight++ ) + { + CIncLight *pLight = new CIncLight; + m_Lights.AddToTail( pLight ); + + FileRead( fp, pLight->m_Light ); + pLight->m_flMaxIntensity = + max( pLight->m_Light.intensity.x, + max( pLight->m_Light.intensity.y, pLight->m_Light.intensity.z ) ); + + int nFaces; + FileRead( fp, nFaces ); + assert( nFaces < 70000 ); + + for( int iFace=0; iFace < nFaces; iFace++ ) + { + CLightFace *pFace = new CLightFace; + pLight->m_LightFaces.AddToTail( pFace ); + + pFace->m_pLight = pLight; + FileRead( fp, pFace->m_FaceIndex ); + + int dataSize; + FileRead( fp, dataSize ); + + pFace->m_CompressedData.SeekPut( CUtlBuffer::SEEK_HEAD, 0 ); + while( dataSize ) + { + --dataSize; + + unsigned char ucData; + FileRead( fp, ucData ); + + pFace->m_CompressedData.PutUnsignedChar( ucData ); + } + } + } + + + FileClose( fp ); + return !FileError(); +} + + +bool CIncremental::SaveIncrementalFile() +{ + long fp = FileOpen( m_pIncrementalFilename, false ); + if( !fp ) + return false; + + if( !WriteIncrementalHeader( fp ) ) + { + FileClose( fp ); + return false; + } + + // Write the lights. + int nLights = m_Lights.Count(); + FileWrite( fp, nLights ); + for( int iLight=m_Lights.Head(); iLight != m_Lights.InvalidIndex(); iLight = m_Lights.Next( iLight ) ) + { + CIncLight *pLight = m_Lights[iLight]; + + FileWrite( fp, pLight->m_Light ); + + int nFaces = pLight->m_LightFaces.Count(); + FileWrite( fp, nFaces ); + for( int iFace=pLight->m_LightFaces.Head(); iFace != pLight->m_LightFaces.InvalidIndex(); iFace = pLight->m_LightFaces.Next( iFace ) ) + { + CLightFace *pFace = pLight->m_LightFaces[iFace]; + + FileWrite( fp, pFace->m_FaceIndex ); + + int dataSize = pFace->m_CompressedData.TellPut(); + FileWrite( fp, dataSize ); + + pFace->m_CompressedData.SeekGet( CUtlBuffer::SEEK_HEAD, 0 ); + while( dataSize ) + { + --dataSize; + FileWrite( fp, pFace->m_CompressedData.GetUnsignedChar() ); + } + } + } + + + FileClose( fp ); + return !FileError(); +} + + +void CIncremental::LinkLightsToFaces( CUtlVector &faceLights ) +{ + faceLights.SetSize( numfaces ); + + for( int iLight=m_Lights.Head(); iLight != m_Lights.InvalidIndex(); iLight = m_Lights.Next( iLight ) ) + { + CIncLight *pLight = m_Lights[iLight]; + + for( int iFace=pLight->m_LightFaces.Head(); iFace != pLight->m_LightFaces.InvalidIndex(); iFace = pLight->m_LightFaces.Next( iFace ) ) + { + CLightFace *pFace = pLight->m_LightFaces[iFace]; + + if( m_FacesTouched[pFace->m_FaceIndex] ) + faceLights[ pFace->m_FaceIndex ].AddToTail( pFace ); + } + } +} + + +// ------------------------------------------------------------------ // +// CIncLight +// ------------------------------------------------------------------ // + +CIncLight::CIncLight() +{ + memset( m_pCachedFaces, 0, sizeof(m_pCachedFaces) ); + InitializeCriticalSection( &m_CS ); +} + + +CIncLight::~CIncLight() +{ + m_LightFaces.PurgeAndDeleteElements(); + DeleteCriticalSection( &m_CS ); +} + + +CLightFace* CIncLight::FindOrCreateLightFace( int iFace, int lmSize, bool *bNew ) +{ + if( bNew ) + *bNew = false; + + + // Look for it. + for( int i=m_LightFaces.Head(); i != m_LightFaces.InvalidIndex(); i=m_LightFaces.Next(i) ) + { + CLightFace *pFace = m_LightFaces[i]; + + if( pFace->m_FaceIndex == iFace ) + { + assert( pFace->m_LightValues.Count() == lmSize ); + return pFace; + } + } + + // Ok, create one. + CLightFace *pFace = new CLightFace; + pFace->m_LightFacesIndex = m_LightFaces.AddToTail( pFace ); + pFace->m_pLight = this; + + pFace->m_FaceIndex = iFace; + pFace->m_LightValues.SetSize( lmSize ); + memset( pFace->m_LightValues.Base(), 0, sizeof( CLightValue ) * lmSize ); + + if( bNew ) + *bNew = true; + + return pFace; +} + + diff --git a/mp/src/utils/vrad/incremental.h b/mp/src/utils/vrad/incremental.h new file mode 100644 index 00000000..fdac144e --- /dev/null +++ b/mp/src/utils/vrad/incremental.h @@ -0,0 +1,175 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef INCREMENTAL_H +#define INCREMENTAL_H +#ifdef _WIN32 +#pragma once +#endif + + + +#include "iincremental.h" +#include "utllinkedlist.h" +#include "utlvector.h" +#include "utlbuffer.h" +#include "vrad.h" + + +#define INCREMENTALFILE_VERSION 31241 + + +class CIncLight; + + +class CLightValue +{ +public: + float m_Dot; +}; + + +class CLightFace +{ +public: + unsigned short m_FaceIndex; // global face index + unsigned short m_LightFacesIndex; // index into CIncLight::m_LightFaces. + + // The lightmap grid for this face. Only used while building lighting data for a face. + // Compressed into m_CompressedData immediately afterwards. + CUtlVector m_LightValues; + + CUtlBuffer m_CompressedData; + CIncLight *m_pLight; +}; + + +class CIncLight +{ +public: + CIncLight(); + ~CIncLight(); + + CLightFace* FindOrCreateLightFace( int iFace, int lmSize, bool *bNew=NULL ); + + +public: + + CRITICAL_SECTION m_CS; + + // This is the light for which m_LightFaces was built. + dworldlight_t m_Light; + + CLightFace *m_pCachedFaces[MAX_TOOL_THREADS+1]; + + // The list of faces that this light contributes to. + CUtlLinkedList m_LightFaces; + + // Largest value in intensity of light. Used to scale dot products up into a + // range where their values make sense. + float m_flMaxIntensity; +}; + + +class CIncrementalHeader +{ +public: + class CLMSize + { + public: + unsigned char m_Width; + unsigned char m_Height; + }; + + CUtlVector m_FaceLightmapSizes; +}; + + +class CIncremental : public IIncremental +{ +public: + + CIncremental(); + ~CIncremental(); + + + +// IIncremental overrides. +public: + + virtual bool Init( char const *pBSPFilename, char const *pIncrementalFilename ); + + // Load the light definitions out of the incremental file. + // Figure out which lights have changed. + // Change 'activelights' to only consist of new or changed lights. + virtual bool PrepareForLighting(); + + virtual void AddLightToFace( + IncrementalLightID lightID, + int iFace, + int iSample, + int lmSize, + float dot, + int iThread ); + + virtual void FinishFace( + IncrementalLightID lightID, + int iFace, + int iThread ); + + // For each face that was changed during the lighting process, save out + // new data for it in the incremental file. + virtual bool Finalize(); + + virtual void GetFacesTouched( CUtlVector &touched ); + + virtual bool Serialize(); + + +private: + + // Read/write the header from the file. + bool ReadIncrementalHeader( long fp, CIncrementalHeader *pHeader ); + bool WriteIncrementalHeader( long fp ); + + // Returns true if the incremental file is valid and we can use InitUpdate. + bool IsIncrementalFileValid(); + + void Term(); + + // For each light in 'activelights', add a light to m_Lights and link them together. + void AddLightsForActiveLights(); + + // Load and save the state. + bool LoadIncrementalFile(); + bool SaveIncrementalFile(); + + typedef CUtlVector CFaceLightList; + void LinkLightsToFaces( CUtlVector &faceLights ); + + +private: + + char const *m_pIncrementalFilename; + char const *m_pBSPFilename; + + CUtlLinkedList + m_Lights; + + // The face index is set to 1 if a face has new lighting data applied to it. + // This is used to optimize the set of lightmaps we recomposite. + CUtlVector m_FacesTouched; + + int m_TotalMemory; + + // Set to true when one or more runs were completed successfully. + bool m_bSuccessfulRun; +}; + + + +#endif // INCREMENTAL_H diff --git a/mp/src/utils/vrad/leaf_ambient_lighting.cpp b/mp/src/utils/vrad/leaf_ambient_lighting.cpp new file mode 100644 index 00000000..ea26c8c6 --- /dev/null +++ b/mp/src/utils/vrad/leaf_ambient_lighting.cpp @@ -0,0 +1,708 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "vrad.h" +#include "leaf_ambient_lighting.h" +#include "bsplib.h" +#include "vraddetailprops.h" +#include "mathlib/anorms.h" +#include "pacifier.h" +#include "coordsize.h" +#include "vstdlib/random.h" +#include "bsptreedata.h" +#include "messbuf.h" +#include "vmpi.h" +#include "vmpi_distribute_work.h" + +static TableVector g_BoxDirections[6] = +{ + { 1, 0, 0 }, + { -1, 0, 0 }, + { 0, 1, 0 }, + { 0, -1, 0 }, + { 0, 0, 1 }, + { 0, 0, -1 }, +}; + + + +static void ComputeAmbientFromSurface( dface_t *surfID, dworldlight_t* pSkylight, + Vector& radcolor ) +{ + if ( !surfID ) + return; + + texinfo_t *pTexInfo = &texinfo[surfID->texinfo]; + + // If we hit the sky, use the sky ambient + if ( pTexInfo->flags & SURF_SKY ) + { + if ( pSkylight ) + { + // add in sky ambient + VectorCopy( pSkylight->intensity, radcolor ); + } + } + else + { + Vector reflectivity = dtexdata[pTexInfo->texdata].reflectivity; + VectorMultiply( radcolor, reflectivity, radcolor ); + } +} + + +// TODO: it's CRAZY how much lighting code we share with the engine. It should all be shared code. +float Engine_WorldLightAngle( const dworldlight_t *wl, const Vector& lnormal, const Vector& snormal, const Vector& delta ) +{ + float dot, dot2; + + Assert( wl->type == emit_surface ); + + dot = DotProduct( snormal, delta ); + if (dot < 0) + return 0; + + dot2 = -DotProduct (delta, lnormal); + if (dot2 <= ON_EPSILON/10) + return 0; // behind light surface + + return dot * dot2; +} + + +// TODO: it's CRAZY how much lighting code we share with the engine. It should all be shared code. +float Engine_WorldLightDistanceFalloff( const dworldlight_t *wl, const Vector& delta ) +{ + Assert( wl->type == emit_surface ); + + // Cull out stuff that's too far + if (wl->radius != 0) + { + if ( DotProduct( delta, delta ) > (wl->radius * wl->radius)) + return 0.0f; + } + + return InvRSquared(delta); +} + + +void AddEmitSurfaceLights( const Vector &vStart, Vector lightBoxColor[6] ) +{ + fltx4 fractionVisible; + + FourVectors vStart4, wlOrigin4; + vStart4.DuplicateVector ( vStart ); + + for ( int iLight=0; iLight < *pNumworldlights; iLight++ ) + { + dworldlight_t *wl = &dworldlights[iLight]; + + // Should this light even go in the ambient cubes? + if ( !( wl->flags & DWL_FLAGS_INAMBIENTCUBE ) ) + continue; + + Assert( wl->type == emit_surface ); + + // Can this light see the point? + wlOrigin4.DuplicateVector ( wl->origin ); + TestLine ( vStart4, wlOrigin4, &fractionVisible ); + if ( !TestSignSIMD ( CmpGtSIMD ( fractionVisible, Four_Zeros ) ) ) + continue; + + // Add this light's contribution. + Vector vDelta = wl->origin - vStart; + float flDistanceScale = Engine_WorldLightDistanceFalloff( wl, vDelta ); + + Vector vDeltaNorm = vDelta; + VectorNormalize( vDeltaNorm ); + float flAngleScale = Engine_WorldLightAngle( wl, wl->normal, vDeltaNorm, vDeltaNorm ); + + float ratio = flDistanceScale * flAngleScale * SubFloat ( fractionVisible, 0 ); + if ( ratio == 0 ) + continue; + + for ( int i=0; i < 6; i++ ) + { + float t = DotProduct( g_BoxDirections[i], vDeltaNorm ); + if ( t > 0 ) + { + lightBoxColor[i] += wl->intensity * (t * ratio); + } + } + } +} + + +void ComputeAmbientFromSphericalSamples( int iThread, const Vector &vStart, Vector lightBoxColor[6] ) +{ + // Figure out the color that rays hit when shot out from this position. + Vector radcolor[NUMVERTEXNORMALS]; + float tanTheta = tan(VERTEXNORMAL_CONE_INNER_ANGLE); + + for ( int i = 0; i < NUMVERTEXNORMALS; i++ ) + { + Vector vEnd = vStart + g_anorms[i] * (COORD_EXTENT * 1.74); + + // Now that we've got a ray, see what surface we've hit + Vector lightStyleColors[MAX_LIGHTSTYLES]; + lightStyleColors[0].Init(); // We only care about light style 0 here. + CalcRayAmbientLighting( iThread, vStart, vEnd, tanTheta, lightStyleColors ); + + radcolor[i] = lightStyleColors[0]; + } + + // accumulate samples into radiant box + for ( int j = 6; --j >= 0; ) + { + float t = 0; + + lightBoxColor[j].Init(); + + for (int i = 0; i < NUMVERTEXNORMALS; i++) + { + float c = DotProduct( g_anorms[i], g_BoxDirections[j] ); + if (c > 0) + { + t += c; + lightBoxColor[j] += radcolor[i] * c; + } + } + + lightBoxColor[j] *= 1/t; + } + + // Now add direct light from the emit_surface lights. These go in the ambient cube because + // there are a ton of them and they are often so dim that they get filtered out by r_worldlightmin. + AddEmitSurfaceLights( vStart, lightBoxColor ); +} + + +bool IsLeafAmbientSurfaceLight( dworldlight_t *wl ) +{ + static const float g_flWorldLightMinEmitSurface = 0.005f; + static const float g_flWorldLightMinEmitSurfaceDistanceRatio = ( InvRSquared( Vector( 0, 0, 512 ) ) ); + + if ( wl->type != emit_surface ) + return false; + + if ( wl->style != 0 ) + return false; + + float intensity = max( wl->intensity[0], wl->intensity[1] ); + intensity = max( intensity, wl->intensity[2] ); + + return (intensity * g_flWorldLightMinEmitSurfaceDistanceRatio) < g_flWorldLightMinEmitSurface; +} + + +class CLeafSampler +{ +public: + CLeafSampler( int iThread ) : m_iThread(iThread) {} + + // Generate a random point in the leaf's bounding volume + // reject any points that aren't actually in the leaf + // do a couple of tracing heuristics to eliminate points that are inside detail brushes + // or underneath displacement surfaces in the leaf + // return once we have a valid point, use the center if one can't be computed quickly + void GenerateLeafSamplePosition( int leafIndex, const CUtlVector &leafPlanes, Vector &samplePosition ) + { + dleaf_t *pLeaf = dleafs + leafIndex; + + float dx = pLeaf->maxs[0] - pLeaf->mins[0]; + float dy = pLeaf->maxs[1] - pLeaf->mins[1]; + float dz = pLeaf->maxs[2] - pLeaf->mins[2]; + bool bValid = false; + for ( int i = 0; i < 1000 && !bValid; i++ ) + { + samplePosition.x = pLeaf->mins[0] + m_random.RandomFloat(0, dx); + samplePosition.y = pLeaf->mins[1] + m_random.RandomFloat(0, dy); + samplePosition.z = pLeaf->mins[2] + m_random.RandomFloat(0, dz); + bValid = true; + + for ( int j = leafPlanes.Count(); --j >= 0 && bValid; ) + { + float d = DotProduct(leafPlanes[j].normal, samplePosition) - leafPlanes[j].dist; + if ( d < DIST_EPSILON ) + { + // not inside the leaf, try again + bValid = false; + break; + } + } + if ( !bValid ) + continue; + + for ( int j = 0; j < 6; j++ ) + { + Vector start = samplePosition; + int axis = j%3; + start[axis] = (j<3) ? pLeaf->mins[axis] : pLeaf->maxs[axis]; + float t; + Vector normal; + CastRayInLeaf( m_iThread, samplePosition, start, leafIndex, &t, &normal ); + if ( t == 0.0f ) + { + // inside a func_detail, try again. + bValid = false; + break; + } + if ( t != 1.0f ) + { + Vector delta = start - samplePosition; + if ( DotProduct(delta, normal) > 0 ) + { + // hit backside of displacement, try again. + bValid = false; + break; + } + } + } + } + if ( !bValid ) + { + // didn't generate a valid sample point, just use the center of the leaf bbox + samplePosition = ( Vector( pLeaf->mins[0], pLeaf->mins[1], pLeaf->mins[2] ) + Vector( pLeaf->maxs[0], pLeaf->maxs[1], pLeaf->maxs[2] ) ) * 0.5f; + } + } + +private: + int m_iThread; + CUniformRandomStream m_random; +}; + +// gets a list of the planes pointing into a leaf +void GetLeafBoundaryPlanes( CUtlVector &list, int leafIndex ) +{ + list.RemoveAll(); + int nodeIndex = leafparents[leafIndex]; + int child = -(leafIndex + 1); + while ( nodeIndex >= 0 ) + { + dnode_t *pNode = dnodes + nodeIndex; + dplane_t *pNodePlane = dplanes + pNode->planenum; + if ( pNode->children[0] == child ) + { + // front side + list.AddToTail( *pNodePlane ); + } + else + { + // back side + int plane = list.AddToTail(); + list[plane].dist = -pNodePlane->dist; + list[plane].normal = -pNodePlane->normal; + list[plane].type = pNodePlane->type; + } + child = nodeIndex; + nodeIndex = nodeparents[child]; + } +} + +// this stores each sample of the ambient lighting +struct ambientsample_t +{ + Vector pos; + Vector cube[6]; +}; + +// add the sample to the list. If we exceed the maximum number of samples, the worst sample will +// be discarded. This has the effect of converging on the best samples when enough are added. +void AddSampleToList( CUtlVector &list, const Vector &samplePosition, Vector *pCube ) +{ + const int MAX_SAMPLES = 16; + + int index = list.AddToTail(); + list[index].pos = samplePosition; + for ( int i = 0; i < 6; i++ ) + { + list[index].cube[i] = pCube[i]; + } + + if ( list.Count() <= MAX_SAMPLES ) + return; + + int nearestNeighborIndex = 0; + float nearestNeighborDist = FLT_MAX; + float nearestNeighborTotal = 0; + for ( int i = 0; i < list.Count(); i++ ) + { + int closestIndex = 0; + float closestDist = FLT_MAX; + float totalDC = 0; + for ( int j = 0; j < list.Count(); j++ ) + { + if ( j == i ) + continue; + float dist = (list[i].pos - list[j].pos).Length(); + float maxDC = 0; + for ( int k = 0; k < 6; k++ ) + { + // color delta is computed per-component, per cube side + for (int s = 0; s < 3; s++ ) + { + float dc = fabs(list[i].cube[k][s] - list[j].cube[k][s]); + maxDC = max(maxDC,dc); + } + totalDC += maxDC; + } + // need a measurable difference in color or we'll just rely on position + if ( maxDC < 1e-4f ) + { + maxDC = 0; + } + else if ( maxDC > 1.0f ) + { + maxDC = 1.0f; + } + // selection criteria is 10% distance, 90% color difference + // choose samples that fill the space (large distance from each other) + // and have largest color variation + float distanceFactor = 0.1f + (maxDC * 0.9f); + dist *= distanceFactor; + + // find the "closest" sample to this one + if ( dist < closestDist ) + { + closestDist = dist; + closestIndex = j; + } + } + // the sample with the "closest" neighbor is rejected + if ( closestDist < nearestNeighborDist || (closestDist == nearestNeighborDist && totalDC < nearestNeighborTotal) ) + { + nearestNeighborDist = closestDist; + nearestNeighborIndex = i; + } + } + list.FastRemove( nearestNeighborIndex ); +} + +// max number of units in gamma space of per-side delta +int CubeDeltaGammaSpace( Vector *pCube0, Vector *pCube1 ) +{ + int maxDelta = 0; + // do this comparison in gamma space to try and get a perceptual basis for the compare + for ( int i = 0; i < 6; i++ ) + { + for ( int j = 0; j < 3; j++ ) + { + int val0 = LinearToScreenGamma( pCube0[i][j] ); + int val1 = LinearToScreenGamma( pCube1[i][j] ); + int delta = abs(val0-val1); + if ( delta > maxDelta ) + maxDelta = delta; + } + } + return maxDelta; +} +// reconstruct the ambient lighting for a leaf at the given position in worldspace +// optionally skip one of the entries in the list +void Mod_LeafAmbientColorAtPos( Vector *pOut, const Vector &pos, const CUtlVector &list, int skipIndex ) +{ + for ( int i = 0; i < 6; i++ ) + { + pOut[i].Init(); + } + float totalFactor = 0; + for ( int i = 0; i < list.Count(); i++ ) + { + if ( i == skipIndex ) + continue; + // do an inverse squared distance weighted average of the samples to reconstruct + // the original function + float dist = (list[i].pos - pos).LengthSqr(); + float factor = 1.0f / (dist + 1.0f); + totalFactor += factor; + for ( int j = 0; j < 6; j++ ) + { + pOut[j] += list[i].cube[j] * factor; + } + } + for ( int i = 0; i < 6; i++ ) + { + pOut[i] *= (1.0f / totalFactor); + } +} + +// this samples the lighting at each sample and removes any unnecessary samples +void CompressAmbientSampleList( CUtlVector &list ) +{ + Vector testCube[6]; + for ( int i = 0; i < list.Count(); i++ ) + { + if ( list.Count() > 1 ) + { + Mod_LeafAmbientColorAtPos( testCube, list[i].pos, list, i ); + if ( CubeDeltaGammaSpace(testCube, list[i].cube) < 3 ) + { + list.FastRemove(i); + i--; + } + } + } +} + +// basically this is an intersection routine that returns a distance between the boxes +float AABBDistance( const Vector &mins0, const Vector &maxs0, const Vector &mins1, const Vector &maxs1 ) +{ + Vector delta; + for ( int i = 0; i < 3; i++ ) + { + float greatestMin = max(mins0[i], mins1[i]); + float leastMax = min(maxs0[i], maxs1[i]); + delta[i] = (greatestMin < leastMax) ? 0 : (leastMax - greatestMin); + } + return delta.Length(); +} + +// build a list of leaves from a query +class CLeafList : public ISpatialLeafEnumerator +{ +public: + virtual bool EnumerateLeaf( int leaf, int context ) + { + m_list.AddToTail(leaf); + return true; + } + + CUtlVector m_list; +}; + +// conver short[3] to vector +static void LeafBounds( int leafIndex, Vector &mins, Vector &maxs ) +{ + for ( int i = 0; i < 3; i++ ) + { + mins[i] = dleafs[leafIndex].mins[i]; + maxs[i] = dleafs[leafIndex].maxs[i]; + } +} + +// returns the index of the nearest leaf with ambient samples +int NearestNeighborWithLight(int leafID) +{ + Vector mins, maxs; + LeafBounds( leafID, mins, maxs ); + Vector size = maxs - mins; + CLeafList leafList; + ToolBSPTree()->EnumerateLeavesInBox( mins-size, maxs+size, &leafList, 0 ); + float bestDist = FLT_MAX; + int bestIndex = leafID; + for ( int i = 0; i < leafList.m_list.Count(); i++ ) + { + int testIndex = leafList.m_list[i]; + if ( !g_pLeafAmbientIndex->Element(testIndex).ambientSampleCount ) + continue; + + Vector testMins, testMaxs; + LeafBounds( testIndex, testMins, testMaxs ); + float dist = AABBDistance( mins, maxs, testMins, testMaxs ); + if ( dist < bestDist ) + { + bestDist = dist; + bestIndex = testIndex; + } + } + return bestIndex; +} + +// maps a float to a byte fraction between min & max +static byte Fixed8Fraction( float t, float tMin, float tMax ) +{ + if ( tMax <= tMin ) + return 0; + + float frac = RemapValClamped( t, tMin, tMax, 0.0f, 255.0f ); + return byte(frac+0.5f); +} + +CUtlVector< CUtlVector > g_LeafAmbientSamples; + +void ComputeAmbientForLeaf( int iThread, int leafID, CUtlVector &list ) +{ + CUtlVector leafPlanes; + CLeafSampler sampler( iThread ); + + GetLeafBoundaryPlanes( leafPlanes, leafID ); + list.RemoveAll(); + // this heuristic tries to generate at least one sample per volume (chosen to be similar to the size of a player) in the space + int xSize = (dleafs[leafID].maxs[0] - dleafs[leafID].mins[0]) / 32; + int ySize = (dleafs[leafID].maxs[1] - dleafs[leafID].mins[1]) / 32; + int zSize = (dleafs[leafID].maxs[2] - dleafs[leafID].mins[2]) / 64; + xSize = max(xSize,1); + ySize = max(xSize,1); + zSize = max(xSize,1); + // generate update 128 candidate samples, always at least one sample + int volumeCount = xSize * ySize * zSize; + if ( g_bFastAmbient ) + { + // save compute time, only do one sample + volumeCount = 1; + } + int sampleCount = clamp( volumeCount, 1, 128 ); + if ( dleafs[leafID].contents & CONTENTS_SOLID ) + { + // don't generate any samples in solid leaves + // NOTE: We copy the nearest non-solid leaf sample pointers into this leaf at the end + return; + } + Vector cube[6]; + for ( int i = 0; i < sampleCount; i++ ) + { + // compute each candidate sample and add to the list + Vector samplePosition; + sampler.GenerateLeafSamplePosition( leafID, leafPlanes, samplePosition ); + ComputeAmbientFromSphericalSamples( iThread, samplePosition, cube ); + // note this will remove the least valuable sample once the limit is reached + AddSampleToList( list, samplePosition, cube ); + } + + // remove any samples that can be reconstructed with the remaining data + CompressAmbientSampleList( list ); +} + +static void ThreadComputeLeafAmbient( int iThread, void *pUserData ) +{ + CUtlVector list; + while (1) + { + int leafID = GetThreadWork (); + if (leafID == -1) + break; + list.RemoveAll(); + ComputeAmbientForLeaf(iThread, leafID, list); + // copy to the output array + g_LeafAmbientSamples[leafID].SetCount( list.Count() ); + for ( int i = 0; i < list.Count(); i++ ) + { + g_LeafAmbientSamples[leafID].Element(i) = list.Element(i); + } + } +} + +void VMPI_ProcessLeafAmbient( int iThread, uint64 iLeaf, MessageBuffer *pBuf ) +{ + CUtlVector list; + ComputeAmbientForLeaf(iThread, (int)iLeaf, list); + + VMPI_SetCurrentStage( "EncodeLeafAmbientResults" ); + + // Encode the results. + int nSamples = list.Count(); + pBuf->write( &nSamples, sizeof( nSamples ) ); + if ( nSamples ) + { + pBuf->write( list.Base(), list.Count() * sizeof( ambientsample_t ) ); + } +} + +//----------------------------------------------------------------------------- +// Called on the master when a worker finishes processing a static prop. +//----------------------------------------------------------------------------- +void VMPI_ReceiveLeafAmbientResults( uint64 leafID, MessageBuffer *pBuf, int iWorker ) +{ + // Decode the results. + int nSamples; + pBuf->read( &nSamples, sizeof( nSamples ) ); + + g_LeafAmbientSamples[leafID].SetCount( nSamples ); + if ( nSamples ) + { + pBuf->read(g_LeafAmbientSamples[leafID].Base(), nSamples * sizeof(ambientsample_t) ); + } +} + + +void ComputePerLeafAmbientLighting() +{ + // Figure out which lights should go in the per-leaf ambient cubes. + int nInAmbientCube = 0; + int nSurfaceLights = 0; + for ( int i=0; i < *pNumworldlights; i++ ) + { + dworldlight_t *wl = &dworldlights[i]; + + if ( IsLeafAmbientSurfaceLight( wl ) ) + wl->flags |= DWL_FLAGS_INAMBIENTCUBE; + else + wl->flags &= ~DWL_FLAGS_INAMBIENTCUBE; + + if ( wl->type == emit_surface ) + ++nSurfaceLights; + + if ( wl->flags & DWL_FLAGS_INAMBIENTCUBE ) + ++nInAmbientCube; + } + + Msg( "%d of %d (%d%% of) surface lights went in leaf ambient cubes.\n", nInAmbientCube, nSurfaceLights, nSurfaceLights ? ((nInAmbientCube*100) / nSurfaceLights) : 0 ); + + g_LeafAmbientSamples.SetCount(numleafs); + + if ( g_bUseMPI ) + { + // Distribute the work among the workers. + VMPI_SetCurrentStage( "ComputeLeafAmbientLighting" ); + DistributeWork( numleafs, VMPI_DISTRIBUTEWORK_PACKETID, VMPI_ProcessLeafAmbient, VMPI_ReceiveLeafAmbientResults ); + } + else + { + RunThreadsOn(numleafs, true, ThreadComputeLeafAmbient); + } + + // now write out the data + Msg("Writing leaf ambient..."); + g_pLeafAmbientIndex->RemoveAll(); + g_pLeafAmbientLighting->RemoveAll(); + g_pLeafAmbientIndex->SetCount( numleafs ); + g_pLeafAmbientLighting->EnsureCapacity( numleafs*4 ); + for ( int leafID = 0; leafID < numleafs; leafID++ ) + { + const CUtlVector &list = g_LeafAmbientSamples[leafID]; + g_pLeafAmbientIndex->Element(leafID).ambientSampleCount = list.Count(); + if ( !list.Count() ) + { + g_pLeafAmbientIndex->Element(leafID).firstAmbientSample = 0; + } + else + { + g_pLeafAmbientIndex->Element(leafID).firstAmbientSample = g_pLeafAmbientLighting->Count(); + // compute the samples in disk format. Encode the positions in 8-bits using leaf bounds fractions + for ( int i = 0; i < list.Count(); i++ ) + { + int outIndex = g_pLeafAmbientLighting->AddToTail(); + dleafambientlighting_t &light = g_pLeafAmbientLighting->Element(outIndex); + + light.x = Fixed8Fraction( list[i].pos.x, dleafs[leafID].mins[0], dleafs[leafID].maxs[0] ); + light.y = Fixed8Fraction( list[i].pos.y, dleafs[leafID].mins[1], dleafs[leafID].maxs[1] ); + light.z = Fixed8Fraction( list[i].pos.z, dleafs[leafID].mins[2], dleafs[leafID].maxs[2] ); + light.pad = 0; + for ( int side = 0; side < 6; side++ ) + { + VectorToColorRGBExp32( list[i].cube[side], light.cube.m_Color[side] ); + } + } + } + } + for ( int i = 0; i < numleafs; i++ ) + { + // UNDONE: Do this dynamically in the engine instead. This will allow us to sample across leaf + // boundaries always which should improve the quality of lighting in general + if ( g_pLeafAmbientIndex->Element(i).ambientSampleCount == 0 ) + { + if ( !(dleafs[i].contents & CONTENTS_SOLID) ) + { + Msg("Bad leaf ambient for leaf %d\n", i ); + } + + int refLeaf = NearestNeighborWithLight(i); + g_pLeafAmbientIndex->Element(i).ambientSampleCount = 0; + g_pLeafAmbientIndex->Element(i).firstAmbientSample = refLeaf; + } + } + Msg("done\n"); +} + diff --git a/mp/src/utils/vrad/leaf_ambient_lighting.h b/mp/src/utils/vrad/leaf_ambient_lighting.h new file mode 100644 index 00000000..b6bb50eb --- /dev/null +++ b/mp/src/utils/vrad/leaf_ambient_lighting.h @@ -0,0 +1,17 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef LEAF_AMBIENT_LIGHTING_H +#define LEAF_AMBIENT_LIGHTING_H +#ifdef _WIN32 +#pragma once +#endif + + +void ComputePerLeafAmbientLighting(); + + +#endif // LEAF_AMBIENT_LIGHTING_H diff --git a/mp/src/utils/vrad/lightmap.cpp b/mp/src/utils/vrad/lightmap.cpp new file mode 100644 index 00000000..b6f8c1f7 --- /dev/null +++ b/mp/src/utils/vrad/lightmap.cpp @@ -0,0 +1,3576 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +#include "vrad.h" +#include "lightmap.h" +#include "radial.h" +#include "mathlib/bumpvects.h" +#include "tier1/utlvector.h" +#include "vmpi.h" +#include "mathlib/anorms.h" +#include "map_utils.h" +#include "mathlib/halton.h" +#include "imagepacker.h" +#include "tier1/utlrbtree.h" +#include "tier1/utlbuffer.h" +#include "bitmap/tgawriter.h" +#include "mathlib/quantize.h" +#include "bitmap/imageformat.h" +#include "coordsize.h" + +enum +{ + AMBIENT_ONLY = 0x1, + NON_AMBIENT_ONLY = 0x2, +}; + +#define SMOOTHING_GROUP_HARD_EDGE 0xff000000 + +//==========================================================================// +// CNormalList. +//==========================================================================// + +// This class keeps a list of unique normals and provides a fast +class CNormalList +{ +public: + CNormalList(); + + // Adds the normal if unique. Otherwise, returns the normal's index into m_Normals. + int FindOrAddNormal( Vector const &vNormal ); + + +public: + + CUtlVector m_Normals; + + +private: + + // This represents a grid from (-1,-1,-1) to (1,1,1). + enum {NUM_SUBDIVS = 8}; + CUtlVector m_NormalGrid[NUM_SUBDIVS][NUM_SUBDIVS][NUM_SUBDIVS]; +}; + + +int g_iCurFace; +edgeshare_t edgeshare[MAX_MAP_EDGES]; + +Vector face_centroids[MAX_MAP_EDGES]; + +int vertexref[MAX_MAP_VERTS]; +int *vertexface[MAX_MAP_VERTS]; +faceneighbor_t faceneighbor[MAX_MAP_FACES]; + +static directlight_t *gSkyLight = NULL; +static directlight_t *gAmbient = NULL; + +//==========================================================================// +// CNormalList implementation. +//==========================================================================// + +CNormalList::CNormalList() : m_Normals( 128 ) +{ + for( int i=0; i < sizeof(m_NormalGrid)/sizeof(m_NormalGrid[0][0][0]); i++ ) + { + (&m_NormalGrid[0][0][0] + i)->SetGrowSize( 16 ); + } +} + +int CNormalList::FindOrAddNormal( Vector const &vNormal ) +{ + int gi[3]; + + // See which grid element it's in. + for( int iDim=0; iDim < 3; iDim++ ) + { + gi[iDim] = (int)( ((vNormal[iDim] + 1.0f) * 0.5f) * NUM_SUBDIVS - 0.000001f ); + gi[iDim] = min( gi[iDim], NUM_SUBDIVS ); + gi[iDim] = max( gi[iDim], 0 ); + } + + // Look for a matching vector in there. + CUtlVector *pGridElement = &m_NormalGrid[gi[0]][gi[1]][gi[2]]; + for( int i=0; i < pGridElement->Size(); i++ ) + { + int iNormal = pGridElement->Element(i); + + Vector *pVec = &m_Normals[iNormal]; + //if( pVec->DistToSqr(vNormal) < 0.00001f ) + if( *pVec == vNormal ) + return iNormal; + } + + // Ok, add a new one. + pGridElement->AddToTail( m_Normals.Size() ); + return m_Normals.AddToTail( vNormal ); +} + +// FIXME: HACK until the plane normals are made more happy +void GetBumpNormals( const float* sVect, const float* tVect, const Vector& flatNormal, + const Vector& phongNormal, Vector bumpNormals[NUM_BUMP_VECTS] ) +{ + Vector stmp( sVect[0], sVect[1], sVect[2] ); + Vector ttmp( tVect[0], tVect[1], tVect[2] ); + GetBumpNormals( stmp, ttmp, flatNormal, phongNormal, bumpNormals ); +} + +int EdgeVertex( dface_t *f, int edge ) +{ + int k; + + if (edge < 0) + edge += f->numedges; + else if (edge >= f->numedges) + edge = edge % f->numedges; + + k = dsurfedges[f->firstedge + edge]; + if (k < 0) + { + // Msg("(%d %d) ", dedges[-k].v[1], dedges[-k].v[0] ); + return dedges[-k].v[1]; + } + else + { + // Msg("(%d %d) ", dedges[k].v[0], dedges[k].v[1] ); + return dedges[k].v[0]; + } +} + + +/* + ============ + PairEdges + ============ +*/ +void PairEdges (void) +{ + int i, j, k, n, m; + dface_t *f; + int numneighbors; + int tmpneighbor[64]; + faceneighbor_t *fn; + + // count number of faces that reference each vertex + for (i=0, f = g_pFaces; inumedges ; j++) + { + // Store the count in vertexref + vertexref[EdgeVertex(f,j)]++; + } + } + + // allocate room + for (i = 0; i < numvertexes; i++) + { + // use the count from above to allocate a big enough array + vertexface[i] = ( int* )calloc( vertexref[i], sizeof( vertexface[0] ) ); + // clear the temporary data + vertexref[i] = 0; + } + + // store a list of every face that uses a particular vertex + for (i=0, f = g_pFaces ; inumedges ; j++) + { + n = EdgeVertex(f,j); + + for (k = 0; k < vertexref[n]; k++) + { + if (vertexface[n][k] == i) + break; + } + if (k >= vertexref[n]) + { + // add the face to the list + vertexface[n][k] = i; + vertexref[n]++; + } + } + } + + // calc normals and set displacement surface flag + for (i=0, f = g_pFaces; iplanenum].normal, fn->facenormal ); + + // set displacement surface flag + fn->bHasDisp = false; + if( ValidDispFace( f ) ) + { + fn->bHasDisp = true; + } + } + + // find neighbors + for (i=0, f = g_pFaces ; inormal = ( Vector* )calloc( f->numedges, sizeof( fn->normal[0] ) ); + + // look up all faces sharing vertices and add them to the list + for (j=0 ; jnumedges ; j++) + { + n = EdgeVertex(f,j); + + for (k = 0; k < vertexref[n]; k++) + { + double cos_normals_angle; + Vector *pNeighbornormal; + + // skip self + if (vertexface[n][k] == i) + continue; + + // if this face doens't have a displacement -- don't consider displacement neighbors + if( ( !fn->bHasDisp ) && ( faceneighbor[vertexface[n][k]].bHasDisp ) ) + continue; + + pNeighbornormal = &faceneighbor[vertexface[n][k]].facenormal; + cos_normals_angle = DotProduct( *pNeighbornormal, fn->facenormal ); + + // add normal if >= threshold or its a displacement surface (this is only if the original + // face is a displacement) + if ( fn->bHasDisp ) + { + // Always smooth with and against a displacement surface. + VectorAdd( fn->normal[j], *pNeighbornormal, fn->normal[j] ); + } + else + { + // No smoothing - use of method (backwards compatibility). + if ( ( f->smoothingGroups == 0 ) && ( g_pFaces[vertexface[n][k]].smoothingGroups == 0 ) ) + { + if ( cos_normals_angle >= smoothing_threshold ) + { + VectorAdd( fn->normal[j], *pNeighbornormal, fn->normal[j] ); + } + else + { + // not considered a neighbor + continue; + } + } + else + { + unsigned int smoothingGroup = ( f->smoothingGroups & g_pFaces[vertexface[n][k]].smoothingGroups ); + + // Hard edge. + if ( ( smoothingGroup & SMOOTHING_GROUP_HARD_EDGE ) != 0 ) + continue; + + if ( smoothingGroup != 0 ) + { + VectorAdd( fn->normal[j], *pNeighbornormal, fn->normal[j] ); + } + else + { + // not considered a neighbor + continue; + } + } + } + + // look to see if we've already added this one + for (m = 0; m < numneighbors; m++) + { + if (tmpneighbor[m] == vertexface[n][k]) + break; + } + + if (m >= numneighbors) + { + // add to neighbor list + tmpneighbor[m] = vertexface[n][k]; + numneighbors++; + if ( numneighbors > ARRAYSIZE(tmpneighbor) ) + { + Error("Stack overflow in neighbors\n"); + } + } + } + } + + if (numneighbors) + { + // copy over neighbor list + fn->numneighbors = numneighbors; + fn->neighbor = ( int* )calloc( numneighbors, sizeof( fn->neighbor[0] ) ); + for (m = 0; m < numneighbors; m++) + { + fn->neighbor[m] = tmpneighbor[m]; + } + } + + // fixup normals + for (j = 0; j < f->numedges; j++) + { + VectorAdd( fn->normal[j], fn->facenormal, fn->normal[j] ); + VectorNormalize( fn->normal[j] ); + } + } +} + + +void SaveVertexNormals( void ) +{ + faceneighbor_t *fn; + int i, j; + dface_t *f; + CNormalList normalList; + + g_numvertnormalindices = 0; + + for( i = 0 ;inumedges; j++ ) + { + Vector vNormal; + if( fn->normal ) + { + vNormal = fn->normal[j]; + } + else + { + // original faces don't have normals + vNormal.Init( 0, 0, 0 ); + } + + if( g_numvertnormalindices == MAX_MAP_VERTNORMALINDICES ) + { + Error( "g_numvertnormalindices == MAX_MAP_VERTNORMALINDICES" ); + } + + g_vertnormalindices[g_numvertnormalindices] = (unsigned short)normalList.FindOrAddNormal( vNormal ); + g_numvertnormalindices++; + } + } + + if( normalList.m_Normals.Size() > MAX_MAP_VERTNORMALS ) + { + Error( "g_numvertnormals > MAX_MAP_VERTNORMALS" ); + } + + // Copy the list of unique vert normals into g_vertnormals. + g_numvertnormals = normalList.m_Normals.Size(); + memcpy( g_vertnormals, normalList.m_Normals.Base(), sizeof(g_vertnormals[0]) * normalList.m_Normals.Size() ); +} + +/* + ================================================================= + + LIGHTMAP SAMPLE GENERATION + + ================================================================= +*/ + + +//----------------------------------------------------------------------------- +// Purpose: Spits out an error message with information about a lightinfo_t. +// Input : s - Error message string. +// l - lightmap info struct. +//----------------------------------------------------------------------------- +void ErrorLightInfo(const char *s, lightinfo_t *l) +{ + texinfo_t *tex = &texinfo[l->face->texinfo]; + winding_t *w = WindingFromFace(&g_pFaces[l->facenum], l->modelorg); + + // + // Show the face center and material name if possible. + // + if (w != NULL) + { + // Don't exit, we'll try to recover... + Vector vecCenter; + WindingCenter(w, vecCenter); +// FreeWinding(w); + + Warning("%s at (%g, %g, %g)\n\tmaterial=%s\n", s, (double)vecCenter.x, (double)vecCenter.y, (double)vecCenter.z, TexDataStringTable_GetString( dtexdata[tex->texdata].nameStringTableID ) ); + } + // + // If not, just show the material name. + // + else + { + Warning("%s at (degenerate face)\n\tmaterial=%s\n", s, TexDataStringTable_GetString( dtexdata[tex->texdata].nameStringTableID )); + } +} + + + +void CalcFaceVectors(lightinfo_t *l) +{ + texinfo_t *tex; + int i, j; + + tex = &texinfo[l->face->texinfo]; + + // move into lightinfo_t + for (i=0 ; i<2 ; i++) + { + for (j=0 ; j<3 ; j++) + { + l->worldToLuxelSpace[i][j] = tex->lightmapVecsLuxelsPerWorldUnits[i][j]; + } + } + + //Solve[ { x * w00 + y * w01 + z * w02 - s == 0, x * w10 + y * w11 + z * w12 - t == 0, A * x + B * y + C * z + D == 0 }, { x, y, z } ] + //Rule(x,( C*s*w11 - B*s*w12 + B*t*w02 - C*t*w01 + D*w02*w11 - D*w01*w12) / (+ A*w01*w12 - A*w02*w11 + B*w02*w10 - B*w00*w12 + C*w00*w11 - C*w01*w10 )), + //Rule(y,( A*s*w12 - C*s*w10 + C*t*w00 - A*t*w02 + D*w00*w12 - D*w02*w10) / (+ A*w01*w12 - A*w02*w11 + B*w02*w10 - B*w00*w12 + C*w00*w11 - C*w01*w10 )), + //Rule(z,( B*s*w10 - A*s*w11 + A*t*w01 - B*t*w00 + D*w01*w10 - D*w00*w11) / (+ A*w01*w12 - A*w02*w11 + B*w02*w10 - B*w00*w12 + C*w00*w11 - C*w01*w10 )))) + + Vector luxelSpaceCross; + + luxelSpaceCross[0] = + tex->lightmapVecsLuxelsPerWorldUnits[1][1] * tex->lightmapVecsLuxelsPerWorldUnits[0][2] - + tex->lightmapVecsLuxelsPerWorldUnits[1][2] * tex->lightmapVecsLuxelsPerWorldUnits[0][1]; + luxelSpaceCross[1] = + tex->lightmapVecsLuxelsPerWorldUnits[1][2] * tex->lightmapVecsLuxelsPerWorldUnits[0][0] - + tex->lightmapVecsLuxelsPerWorldUnits[1][0] * tex->lightmapVecsLuxelsPerWorldUnits[0][2]; + luxelSpaceCross[2] = + tex->lightmapVecsLuxelsPerWorldUnits[1][0] * tex->lightmapVecsLuxelsPerWorldUnits[0][1] - + tex->lightmapVecsLuxelsPerWorldUnits[1][1] * tex->lightmapVecsLuxelsPerWorldUnits[0][0]; + + float det = -DotProduct( l->facenormal, luxelSpaceCross ); + if ( fabs( det ) < 1.0e-20 ) + { + Warning(" warning - face vectors parallel to face normal. bad lighting will be produced\n" ); + l->luxelOrigin = vec3_origin; + } + else + { + // invert the matrix + l->luxelToWorldSpace[0][0] = (l->facenormal[2] * l->worldToLuxelSpace[1][1] - l->facenormal[1] * l->worldToLuxelSpace[1][2]) / det; + l->luxelToWorldSpace[1][0] = (l->facenormal[1] * l->worldToLuxelSpace[0][2] - l->facenormal[2] * l->worldToLuxelSpace[0][1]) / det; + l->luxelOrigin[0] = -(l->facedist * luxelSpaceCross[0]) / det; + l->luxelToWorldSpace[0][1] = (l->facenormal[0] * l->worldToLuxelSpace[1][2] - l->facenormal[2] * l->worldToLuxelSpace[1][0]) / det; + l->luxelToWorldSpace[1][1] = (l->facenormal[2] * l->worldToLuxelSpace[0][0] - l->facenormal[0] * l->worldToLuxelSpace[0][2]) / det; + l->luxelOrigin[1] = -(l->facedist * luxelSpaceCross[1]) / det; + l->luxelToWorldSpace[0][2] = (l->facenormal[1] * l->worldToLuxelSpace[1][0] - l->facenormal[0] * l->worldToLuxelSpace[1][1]) / det; + l->luxelToWorldSpace[1][2] = (l->facenormal[0] * l->worldToLuxelSpace[0][1] - l->facenormal[1] * l->worldToLuxelSpace[0][0]) / det; + l->luxelOrigin[2] = -(l->facedist * luxelSpaceCross[2]) / det; + + // adjust for luxel offset + VectorMA( l->luxelOrigin, -tex->lightmapVecsLuxelsPerWorldUnits[0][3], l->luxelToWorldSpace[0], l->luxelOrigin ); + VectorMA( l->luxelOrigin, -tex->lightmapVecsLuxelsPerWorldUnits[1][3], l->luxelToWorldSpace[1], l->luxelOrigin ); + } + // compensate for org'd bmodels + VectorAdd (l->luxelOrigin, l->modelorg, l->luxelOrigin); +} + + + +winding_t *LightmapCoordWindingForFace( lightinfo_t *l ) +{ + int i; + winding_t *w; + + w = WindingFromFace( l->face, l->modelorg ); + + for (i = 0; i < w->numpoints; i++) + { + Vector2D coord; + WorldToLuxelSpace( l, w->p[i], coord ); + w->p[i].x = coord.x; + w->p[i].y = coord.y; + w->p[i].z = 0; + } + + return w; +} + + +void WriteCoordWinding (FILE *out, lightinfo_t *l, winding_t *w, Vector& color ) +{ + int i; + Vector pos; + + fprintf (out, "%i\n", w->numpoints); + for (i=0 ; inumpoints ; i++) + { + LuxelSpaceToWorld( l, w->p[i][0], w->p[i][1], pos ); + fprintf (out, "%5.2f %5.2f %5.2f %5.3f %5.3f %5.3f\n", + pos[0], + pos[1], + pos[2], + color[ 0 ] / 256, + color[ 1 ] / 256, + color[ 2 ] / 256 ); + } +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void DumpFaces( lightinfo_t *pLightInfo, int ndxFace ) +{ + static FileHandle_t out; + + // get face data + faceneighbor_t *fn = &faceneighbor[ndxFace]; + Vector ¢roid = face_centroids[ndxFace]; + + // disable threading (not a multi-threadable function!) + ThreadLock(); + + if( !out ) + { + // open the file + out = g_pFileSystem->Open( "face.txt", "w" ); + if( !out ) + return; + } + + // + // write out face + // + for( int ndxEdge = 0; ndxEdge < pLightInfo->face->numedges; ndxEdge++ ) + { +// int edge = dsurfedges[pLightInfo->face->firstedge+ndxEdge]; + + Vector p1, p2; + VectorAdd( dvertexes[EdgeVertex( pLightInfo->face, ndxEdge )].point, pLightInfo->modelorg, p1 ); + VectorAdd( dvertexes[EdgeVertex( pLightInfo->face, ndxEdge+1 )].point, pLightInfo->modelorg, p2 ); + + Vector &n1 = fn->normal[ndxEdge]; + Vector &n2 = fn->normal[(ndxEdge+1)%pLightInfo->face->numedges]; + + CmdLib_FPrintf( out, "3\n"); + + CmdLib_FPrintf(out, "%f %f %f %f %f %f\n", p1[0], p1[1], p1[2], n1[0] * 0.5 + 0.5, n1[1] * 0.5 + 0.5, n1[2] * 0.5 + 0.5 ); + + CmdLib_FPrintf(out, "%f %f %f %f %f %f\n", p2[0], p2[1], p2[2], n2[0] * 0.5 + 0.5, n2[1] * 0.5 + 0.5, n2[2] * 0.5 + 0.5 ); + + CmdLib_FPrintf(out, "%f %f %f %f %f %f\n", centroid[0] + pLightInfo->modelorg[0], + centroid[1] + pLightInfo->modelorg[1], + centroid[2] + pLightInfo->modelorg[2], + fn->facenormal[0] * 0.5 + 0.5, + fn->facenormal[1] * 0.5 + 0.5, + fn->facenormal[2] * 0.5 + 0.5 ); + + } + + // enable threading + ThreadUnlock(); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool BuildFacesamplesAndLuxels_DoFast( lightinfo_t *pLightInfo, facelight_t *pFaceLight ) +{ + // lightmap size + int width = pLightInfo->face->m_LightmapTextureSizeInLuxels[0]+1; + int height = pLightInfo->face->m_LightmapTextureSizeInLuxels[1]+1; + + // ratio of world area / lightmap area + texinfo_t *pTex = &texinfo[pLightInfo->face->texinfo]; + pFaceLight->worldAreaPerLuxel = 1.0 / ( sqrt( DotProduct( pTex->lightmapVecsLuxelsPerWorldUnits[0], + pTex->lightmapVecsLuxelsPerWorldUnits[0] ) ) * + sqrt( DotProduct( pTex->lightmapVecsLuxelsPerWorldUnits[1], + pTex->lightmapVecsLuxelsPerWorldUnits[1] ) ) ); + + // + // quickly create samples and luxels (copy over samples) + // + pFaceLight->numsamples = width * height; + pFaceLight->sample = ( sample_t* )calloc( pFaceLight->numsamples, sizeof( *pFaceLight->sample ) ); + if( !pFaceLight->sample ) + return false; + + pFaceLight->numluxels = width * height; + pFaceLight->luxel = ( Vector* )calloc( pFaceLight->numluxels, sizeof( *pFaceLight->luxel ) ); + if( !pFaceLight->luxel ) + return false; + + sample_t *pSamples = pFaceLight->sample; + Vector *pLuxels = pFaceLight->luxel; + + for( int t = 0; t < height; t++ ) + { + for( int s = 0; s < width; s++ ) + { + pSamples->s = s; + pSamples->t = t; + pSamples->coord[0] = s; + pSamples->coord[1] = t; + // unused but initialized anyway + pSamples->mins[0] = s - 0.5; + pSamples->mins[1] = t - 0.5; + pSamples->maxs[0] = s + 0.5; + pSamples->maxs[1] = t + 0.5; + pSamples->area = pFaceLight->worldAreaPerLuxel; + LuxelSpaceToWorld( pLightInfo, pSamples->coord[0], pSamples->coord[1], pSamples->pos ); + VectorCopy( pSamples->pos, *pLuxels ); + + pSamples++; + pLuxels++; + } + } + + return true; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool BuildSamplesAndLuxels_DoFast( lightinfo_t *pLightInfo, facelight_t *pFaceLight, int ndxFace ) +{ + // build samples for a "face" + if( pLightInfo->face->dispinfo == -1 ) + { + return BuildFacesamplesAndLuxels_DoFast( pLightInfo, pFaceLight ); + } + // build samples for a "displacement" + else + { + return StaticDispMgr()->BuildDispSamplesAndLuxels_DoFast( pLightInfo, pFaceLight, ndxFace ); + } +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool BuildFacesamples( lightinfo_t *pLightInfo, facelight_t *pFaceLight ) +{ + // lightmap size + int width = pLightInfo->face->m_LightmapTextureSizeInLuxels[0]+1; + int height = pLightInfo->face->m_LightmapTextureSizeInLuxels[1]+1; + + // ratio of world area / lightmap area + texinfo_t *pTex = &texinfo[pLightInfo->face->texinfo]; + pFaceLight->worldAreaPerLuxel = 1.0 / ( sqrt( DotProduct( pTex->lightmapVecsLuxelsPerWorldUnits[0], + pTex->lightmapVecsLuxelsPerWorldUnits[0] ) ) * + sqrt( DotProduct( pTex->lightmapVecsLuxelsPerWorldUnits[1], + pTex->lightmapVecsLuxelsPerWorldUnits[1] ) ) ); + + // allocate a large number of samples for creation -- get copied later! + char sampleData[sizeof(sample_t)*SINGLE_BRUSH_MAP*2]; + sample_t *samples = (sample_t*)sampleData; // use a char array to speed up the debug version. + sample_t *pSamples = samples; + + // lightmap space winding + winding_t *pLightmapWinding = LightmapCoordWindingForFace( pLightInfo ); + + // + // build vector pointing along the lightmap cutting planes + // + Vector sNorm( 1.0f, 0.0f, 0.0f ); + Vector tNorm( 0.0f, 1.0f, 0.0f ); + + // sample center offset + float sampleOffset = ( do_centersamples ) ? 0.5 : 1.0; + + // + // clip the lightmap "spaced" winding by the lightmap cutting planes + // + winding_t *pWindingT1, *pWindingT2; + winding_t *pWindingS1, *pWindingS2; + float dist; + + for( int t = 0; t < height && pLightmapWinding; t++ ) + { + dist = t + sampleOffset; + + // lop off a sample in the t dimension + // hack - need a separate epsilon for lightmap space since ON_EPSILON is for texture space + ClipWindingEpsilon( pLightmapWinding, tNorm, dist, ON_EPSILON / 16.0f, &pWindingT1, &pWindingT2 ); + + for( int s = 0; s < width && pWindingT2; s++ ) + { + dist = s + sampleOffset; + + // lop off a sample in the s dimension, and put it in ws2 + // hack - need a separate epsilon for lightmap space since ON_EPSILON is for texture space + ClipWindingEpsilon( pWindingT2, sNorm, dist, ON_EPSILON / 16.0f, &pWindingS1, &pWindingS2 ); + + // + // s2 winding is a single sample worth of winding + // + if( pWindingS2 ) + { + // save the s, t positions + pSamples->s = s; + pSamples->t = t; + + // get the lightmap space area of ws2 and convert to world area + // and find the center (then convert it to 2D) + Vector center; + pSamples->area = WindingAreaAndBalancePoint( pWindingS2, center ) * pFaceLight->worldAreaPerLuxel; + pSamples->coord[0] = center.x; + pSamples->coord[1] = center.y; + + // find winding bounds (then convert it to 2D) + Vector minbounds, maxbounds; + WindingBounds( pWindingS2, minbounds, maxbounds ); + pSamples->mins[0] = minbounds.x; + pSamples->mins[1] = minbounds.y; + pSamples->maxs[0] = maxbounds.x; + pSamples->maxs[1] = maxbounds.y; + + // convert from lightmap space to world space + LuxelSpaceToWorld( pLightInfo, pSamples->coord[0], pSamples->coord[1], pSamples->pos ); + + if (g_bDumpPatches || (do_extra && pSamples->area < pFaceLight->worldAreaPerLuxel - EQUAL_EPSILON)) + { + // + // convert the winding from lightmaps space to world for debug rendering and sub-sampling + // + Vector worldPos; + for( int ndxPt = 0; ndxPt < pWindingS2->numpoints; ndxPt++ ) + { + LuxelSpaceToWorld( pLightInfo, pWindingS2->p[ndxPt].x, pWindingS2->p[ndxPt].y, worldPos ); + VectorCopy( worldPos, pWindingS2->p[ndxPt] ); + } + pSamples->w = pWindingS2; + } + else + { + // winding isn't needed, free it. + pSamples->w = NULL; + FreeWinding( pWindingS2 ); + } + + pSamples++; + } + + // + // if winding T2 still exists free it and set it equal S1 (the rest of the row minus the sample just created) + // + if( pWindingT2 ) + { + FreeWinding( pWindingT2 ); + } + + // clip the rest of "s" + pWindingT2 = pWindingS1; + } + + // + // if the original lightmap winding exists free it and set it equal to T1 (the rest of the winding not cut into samples) + // + if( pLightmapWinding ) + { + FreeWinding( pLightmapWinding ); + } + + if( pWindingT2 ) + { + FreeWinding( pWindingT2 ); + } + + pLightmapWinding = pWindingT1; + } + + // + // copy over samples + // + pFaceLight->numsamples = pSamples - samples; + pFaceLight->sample = ( sample_t* )calloc( pFaceLight->numsamples, sizeof( *pFaceLight->sample ) ); + if( !pFaceLight->sample ) + return false; + + memcpy( pFaceLight->sample, samples, pFaceLight->numsamples * sizeof( *pFaceLight->sample ) ); + + // supply a default sample normal (face normal - assumed flat) + for( int ndxSample = 0; ndxSample < pFaceLight->numsamples; ndxSample++ ) + { + Assert ( VectorLength ( pLightInfo->facenormal ) > 1.0e-20); + pFaceLight->sample[ndxSample].normal = pLightInfo->facenormal; + } + + // statistics - warning?! + if( pFaceLight->numsamples == 0 ) + { + Msg( "no samples %d\n", pLightInfo->face - g_pFaces ); + } + + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: Free any windings used by this facelight. It's currently assumed they're not needed again +//----------------------------------------------------------------------------- +void FreeSampleWindings( facelight_t *fl ) +{ + int i; + for (i = 0; i < fl->numsamples; i++) + { + if (fl->sample[i].w) + { + FreeWinding( fl->sample[i].w ); + fl->sample[i].w = NULL; + } + } +} + + + +//----------------------------------------------------------------------------- +// Purpose: build the sample data for each lightmapped primitive type +//----------------------------------------------------------------------------- +bool BuildSamples( lightinfo_t *pLightInfo, facelight_t *pFaceLight, int ndxFace ) +{ + // build samples for a "face" + if( pLightInfo->face->dispinfo == -1 ) + { + return BuildFacesamples( pLightInfo, pFaceLight ); + } + // build samples for a "displacement" + else + { + return StaticDispMgr()->BuildDispSamples( pLightInfo, pFaceLight, ndxFace ); + } +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool BuildFaceLuxels( lightinfo_t *pLightInfo, facelight_t *pFaceLight ) +{ + // lightmap size + int width = pLightInfo->face->m_LightmapTextureSizeInLuxels[0]+1; + int height = pLightInfo->face->m_LightmapTextureSizeInLuxels[1]+1; + + // calcuate actual luxel points + pFaceLight->numluxels = width * height; + pFaceLight->luxel = ( Vector* )calloc( pFaceLight->numluxels, sizeof( *pFaceLight->luxel ) ); + if( !pFaceLight->luxel ) + return false; + + for( int t = 0; t < height; t++ ) + { + for( int s = 0; s < width; s++ ) + { + LuxelSpaceToWorld( pLightInfo, s, t, pFaceLight->luxel[s+t*width] ); + } + } + + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: build the luxels (find the luxel centers) for each lightmapped +// primitive type +//----------------------------------------------------------------------------- +bool BuildLuxels( lightinfo_t *pLightInfo, facelight_t *pFaceLight, int ndxFace ) +{ + // build luxels for a "face" + if( pLightInfo->face->dispinfo == -1 ) + { + return BuildFaceLuxels( pLightInfo, pFaceLight ); + } + // build luxels for a "displacement" + else + { + return StaticDispMgr()->BuildDispLuxels( pLightInfo, pFaceLight, ndxFace ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: for each face, find the center of each luxel; for each texture +// aligned grid point, back project onto the plane and get the world +// xyz value of the sample point +// NOTE: ndxFace = facenum +//----------------------------------------------------------------------------- +void CalcPoints( lightinfo_t *pLightInfo, facelight_t *pFaceLight, int ndxFace ) +{ + // debugging! + if( g_bDumpPatches ) + { + DumpFaces( pLightInfo, ndxFace ); + } + + // quick and dirty! + if( do_fast ) + { + if( !BuildSamplesAndLuxels_DoFast( pLightInfo, pFaceLight, ndxFace ) ) + { + Msg( "Face %d: (Fast)Error Building Samples and Luxels\n", ndxFace ); + } + return; + } + + // build the samples + if( !BuildSamples( pLightInfo, pFaceLight, ndxFace ) ) + { + Msg( "Face %d: Error Building Samples\n", ndxFace ); + } + + // build the luxels + if( !BuildLuxels( pLightInfo, pFaceLight, ndxFace ) ) + { + Msg( "Face %d: Error Building Luxels\n", ndxFace ); + } +} + + +//============================================================== + +directlight_t *activelights; +directlight_t *freelights; + +facelight_t facelight[MAX_MAP_FACES]; +int numdlights; + +/* + ================== + FindTargetEntity + ================== +*/ +entity_t *FindTargetEntity (char *target) +{ + int i; + char *n; + + for (i=0 ; iindex = numdlights++; + + VectorCopy( origin, dl->light.origin ); + + dl->light.cluster = ClusterFromPoint(dl->light.origin); + SetDLightVis( dl, dl->light.cluster ); + + dl->facenum = -1; + + if ( bAddToList ) + { + dl->next = activelights; + activelights = dl; + } + + return dl; +} + +void AddDLightToActiveList( directlight_t *dl ) +{ + dl->next = activelights; + activelights = dl; +} + +void FreeDLights() +{ + gSkyLight = NULL; + gAmbient = NULL; + + directlight_t *pNext; + for( directlight_t *pCur=activelights; pCur; pCur=pNext ) + { + pNext = pCur->next; + free( pCur ); + } + activelights = 0; +} + + +void SetDLightVis( directlight_t *dl, int cluster ) +{ + if (dl->pvs == NULL) + { + dl->pvs = (byte *)calloc( 1, (dvis->numclusters / 8) + 1 ); + } + + GetVisCache( -1, cluster, dl->pvs ); +} + +void MergeDLightVis( directlight_t *dl, int cluster ) +{ + if (dl->pvs == NULL) + { + SetDLightVis( dl, cluster ); + } + else + { + byte pvs[MAX_MAP_CLUSTERS/8]; + GetVisCache( -1, cluster, pvs ); + + // merge both vis graphs + for (int i = 0; i < (dvis->numclusters / 8) + 1; i++) + { + dl->pvs[i] |= pvs[i]; + } + } +} + + +/* + ============= + LightForKey + ============= +*/ +int LightForKey (entity_t *ent, char *key, Vector& intensity ) +{ + char *pLight; + + pLight = ValueForKey( ent, key ); + + return LightForString( pLight, intensity ); +} + +int LightForString( char *pLight, Vector& intensity ) +{ + double r, g, b, scaler; + int argCnt; + + VectorFill( intensity, 0 ); + + // scanf into doubles, then assign, so it is vec_t size independent + r = g = b = scaler = 0; + double r_hdr,g_hdr,b_hdr,scaler_hdr; + argCnt = sscanf ( pLight, "%lf %lf %lf %lf %lf %lf %lf %lf", + &r, &g, &b, &scaler, &r_hdr,&g_hdr,&b_hdr,&scaler_hdr ); + + if (argCnt==8) // 2 4-tuples + { + if (g_bHDR) + { + r=r_hdr; + g=g_hdr; + b=b_hdr; + scaler=scaler_hdr; + } + argCnt=4; + } + + // make sure light is legal + if( r < 0.0f || g < 0.0f || b < 0.0f || scaler < 0.0f ) + { + intensity.Init( 0.0f, 0.0f, 0.0f ); + return false; + } + + intensity[0] = pow( r / 255.0, 2.2 ) * 255; // convert to linear + + switch( argCnt) + { + case 1: + // The R,G,B values are all equal. + intensity[1] = intensity[2] = intensity[0]; + break; + + case 3: + case 4: + // Save the other two G,B values. + intensity[1] = pow( g / 255.0, 2.2 ) * 255; + intensity[2] = pow( b / 255.0, 2.2 ) * 255; + + // Did we also get an "intensity" scaler value too? + if ( argCnt == 4 ) + { + // Scale the normalized 0-255 R,G,B values by the intensity scaler + VectorScale( intensity, scaler / 255.0, intensity ); + } + break; + + default: + printf("unknown light specifier type - %s\n",pLight); + return false; + } + // scale up source lights by scaling factor + VectorScale( intensity, lightscale, intensity ); + return true; +} + +//----------------------------------------------------------------------------- +// Various parsing methods +//----------------------------------------------------------------------------- + +static void ParseLightGeneric( entity_t *e, directlight_t *dl ) +{ + entity_t *e2; + char *target; + Vector dest; + + dl->light.style = (int)FloatForKey (e, "style"); + + // get intenfsity + if( g_bHDR && LightForKey( e, "_lightHDR", dl->light.intensity ) ) + { + } + else + { + LightForKey( e, "_light", dl->light.intensity ); + } + + // check angle, targets + target = ValueForKey (e, "target"); + if (target[0]) + { // point towards target + e2 = FindTargetEntity (target); + if (!e2) + Warning("WARNING: light at (%i %i %i) has missing target\n", + (int)dl->light.origin[0], (int)dl->light.origin[1], (int)dl->light.origin[2]); + else + { + GetVectorForKey (e2, "origin", dest); + VectorSubtract (dest, dl->light.origin, dl->light.normal); + VectorNormalize (dl->light.normal); + } + } + else + { + // point down angle + Vector angles; + GetVectorForKey( e, "angles", angles ); + float pitch = FloatForKey (e, "pitch"); + float angle = FloatForKey (e, "angle"); + SetupLightNormalFromProps( QAngle( angles.x, angles.y, angles.z ), angle, pitch, dl->light.normal ); + } + if ( g_bHDR ) + VectorScale( dl->light.intensity, + FloatForKeyWithDefault( e, "_lightscaleHDR", 1.0 ), + dl->light.intensity ); +} + +static void SetLightFalloffParams( entity_t * e, directlight_t * dl ) +{ + float d50=FloatForKey( e, "_fifty_percent_distance" ); + dl->m_flStartFadeDistance = 0; + dl->m_flEndFadeDistance = - 1; + dl->m_flCapDist = 1.0e22; + if ( d50 ) + { + float d0 = FloatForKey( e, "_zero_percent_distance" ); + if ( d0 < d50 ) + { + Warning( "light has _fifty_percent_distance of %f but _zero_percent_distance of %f\n", d50, d0); + d0 = 2.0 * d50; + } + float a = 0, b = 1, c = 0; + if ( ! SolveInverseQuadraticMonotonic( 0, 1.0, d50, 2.0, d0, 256.0, a, b, c )) + { + Warning( "can't solve quadratic for light %f %f\n", d50, d0 ); + } + // it it possible that the parameters couldn't be used because of enforing monoticity. If so, rescale so at + // least the 50 percent value is right +// printf("50 percent=%f 0 percent=%f\n",d50,d0); +// printf("a=%f b=%f c=%f\n",a,b,c); + float v50 = c + d50 * ( b + d50 * a ); + float scale = 2.0 / v50; + a *= scale; + b *= scale; + c *= scale; +// printf("scaled=%f a=%f b=%f c=%f\n",scale,a,b,c); +// for(float d=0;d<1000;d+=20) +// printf("at %f, %f\n",d,1.0/(c+d*(b+d*a))); + dl->light.quadratic_attn = a; + dl->light.linear_attn = b; + dl->light.constant_attn = c; + + + + if ( IntForKey(e, "_hardfalloff" ) ) + { + dl->m_flEndFadeDistance = d0; + dl->m_flStartFadeDistance = 0.75 * d0 + 0.25 * d50; // start fading 3/4 way between 50 and 0. could allow adjust + } + else + { + // now, we will find the point at which the 1/x term reaches its maximum value, and + // prevent the light from going past there. If a user specifes an extreme falloff, the + // quadratic will start making the light brighter at some distance. We handle this by + // fading it from the minimum brightess point down to zero at 10x the minimum distance + if ( fabs( a ) > 0. ) + { + float flMax = b / ( - 2.0 * a ); // where f' = 0 + if ( flMax > 0.0 ) + { + dl->m_flCapDist = flMax; + dl->m_flStartFadeDistance = flMax; + dl->m_flEndFadeDistance = 10.0 * flMax; + } + } + } + } + else + { + dl->light.constant_attn = FloatForKey (e, "_constant_attn" ); + dl->light.linear_attn = FloatForKey (e, "_linear_attn" ); + dl->light.quadratic_attn = FloatForKey (e, "_quadratic_attn" ); + + dl->light.radius = FloatForKey (e, "_distance"); + + // clamp values to >= 0 + if ( dl->light.constant_attn < EQUAL_EPSILON ) + dl->light.constant_attn = 0; + + if ( dl->light.linear_attn < EQUAL_EPSILON ) + dl->light.linear_attn = 0; + + if ( dl->light.quadratic_attn < EQUAL_EPSILON ) + dl->light.quadratic_attn = 0; + + if ( dl->light.constant_attn < EQUAL_EPSILON && dl->light.linear_attn < EQUAL_EPSILON && dl->light.quadratic_attn < EQUAL_EPSILON ) + dl->light.constant_attn = 1; + + // scale intensity for unit 100 distance + float ratio = ( dl->light.constant_attn + 100 * dl->light.linear_attn + 100 * 100 * dl->light.quadratic_attn ); + if ( ratio > 0 ) + { + VectorScale( dl->light.intensity, ratio, dl->light.intensity ); + } + } +} + +static void ParseLightSpot( entity_t* e, directlight_t* dl ) +{ + Vector dest; + GetVectorForKey (e, "origin", dest ); + dl = AllocDLight( dest, true ); + + ParseLightGeneric( e, dl ); + + dl->light.type = emit_spotlight; + + dl->light.stopdot = FloatForKey (e, "_inner_cone"); + if (!dl->light.stopdot) + dl->light.stopdot = 10; + + dl->light.stopdot2 = FloatForKey (e, "_cone"); + if (!dl->light.stopdot2) + dl->light.stopdot2 = dl->light.stopdot; + if (dl->light.stopdot2 < dl->light.stopdot) + dl->light.stopdot2 = dl->light.stopdot; + + // This is a point light if stop dots are 180... + if ((dl->light.stopdot == 180) && (dl->light.stopdot2 == 180)) + { + dl->light.stopdot = dl->light.stopdot2 = 0; + dl->light.type = emit_point; + dl->light.exponent = 0; + } + else + { + // Clamp to 90, that's all DX8 can handle! + if (dl->light.stopdot > 90) + { + Warning("WARNING: light_spot at (%i %i %i) has inner angle larger than 90 degrees! Clamping to 90...\n", + (int)dl->light.origin[0], (int)dl->light.origin[1], (int)dl->light.origin[2]); + dl->light.stopdot = 90; + } + + if (dl->light.stopdot2 > 90) + { + Warning("WARNING: light_spot at (%i %i %i) has outer angle larger than 90 degrees! Clamping to 90...\n", + (int)dl->light.origin[0], (int)dl->light.origin[1], (int)dl->light.origin[2]); + dl->light.stopdot2 = 90; + } + + dl->light.stopdot2 = (float)cos(dl->light.stopdot2/180*M_PI); + dl->light.stopdot = (float)cos(dl->light.stopdot/180*M_PI); + dl->light.exponent = FloatForKey (e, "_exponent"); + } + + SetLightFalloffParams(e,dl); +} + +// NOTE: This is just a heuristic. It traces a finite number of rays to find sky +// NOTE: Full vis is necessary to make this 100% correct. +bool CanLeafTraceToSky( int iLeaf ) +{ + // UNDONE: Really want a point inside the leaf here. Center is a guess, may not be in the leaf + // UNDONE: Clip this to each plane bounding the leaf to guarantee + Vector center = vec3_origin; + for ( int i = 0; i < 3; i++ ) + { + center[i] = ( (float)(dleafs[iLeaf].mins[i] + dleafs[iLeaf].maxs[i]) ) * 0.5f; + } + + FourVectors center4, delta; + fltx4 fractionVisible; + for ( int j = 0; j < NUMVERTEXNORMALS; j+=4 ) + { + // search back to see if we can hit a sky brush + delta.LoadAndSwizzle( g_anorms[j], g_anorms[min( j+1, NUMVERTEXNORMALS-1 )], + g_anorms[min( j+2, NUMVERTEXNORMALS-1 )], g_anorms[min( j+3, NUMVERTEXNORMALS-1 )] ); + delta *= -MAX_TRACE_LENGTH; + delta += center4; + + // return true if any hits sky + TestLine_DoesHitSky ( center4, delta, &fractionVisible ); + if ( TestSignSIMD ( CmpGtSIMD ( fractionVisible, Four_Zeros ) ) ) + return true; + } + + return false; +} + +void BuildVisForLightEnvironment( void ) +{ + // Create the vis. + for ( int iLeaf = 0; iLeaf < numleafs; ++iLeaf ) + { + dleafs[iLeaf].flags &= ~( LEAF_FLAGS_SKY | LEAF_FLAGS_SKY2D ); + unsigned int iFirstFace = dleafs[iLeaf].firstleafface; + for ( int iLeafFace = 0; iLeafFace < dleafs[iLeaf].numleaffaces; ++iLeafFace ) + { + unsigned int iFace = dleaffaces[iFirstFace+iLeafFace]; + + texinfo_t &tex = texinfo[g_pFaces[iFace].texinfo]; + if ( tex.flags & SURF_SKY ) + { + if ( tex.flags & SURF_SKY2D ) + { + dleafs[iLeaf].flags |= LEAF_FLAGS_SKY2D; + } + else + { + dleafs[iLeaf].flags |= LEAF_FLAGS_SKY; + } + MergeDLightVis( gSkyLight, dleafs[iLeaf].cluster ); + MergeDLightVis( gAmbient, dleafs[iLeaf].cluster ); + break; + } + } + } + + // Second pass to set flags on leaves that don't contain sky, but touch leaves that + // contain sky. + byte pvs[MAX_MAP_CLUSTERS / 8]; + + int nLeafBytes = (numleafs >> 3) + 1; + unsigned char *pLeafBits = (unsigned char *)stackalloc( nLeafBytes * sizeof(unsigned char) ); + unsigned char *pLeaf2DBits = (unsigned char *)stackalloc( nLeafBytes * sizeof(unsigned char) ); + memset( pLeafBits, 0, nLeafBytes ); + memset( pLeaf2DBits, 0, nLeafBytes ); + + for ( int iLeaf = 0; iLeaf < numleafs; ++iLeaf ) + { + // If this leaf has light (3d skybox) in it, then don't bother + if ( dleafs[iLeaf].flags & LEAF_FLAGS_SKY ) + continue; + + // Don't bother with this leaf if it's solid + if ( dleafs[iLeaf].contents & CONTENTS_SOLID ) + continue; + + // See what other leaves are visible from this leaf + GetVisCache( -1, dleafs[iLeaf].cluster, pvs ); + + // Now check out all other leaves + int nByte = iLeaf >> 3; + int nBit = 1 << ( iLeaf & 0x7 ); + for ( int iLeaf2 = 0; iLeaf2 < numleafs; ++iLeaf2 ) + { + if ( iLeaf2 == iLeaf ) + continue; + + if ( !(dleafs[iLeaf2].flags & ( LEAF_FLAGS_SKY | LEAF_FLAGS_SKY2D ) ) ) + continue; + + // Can this leaf see into the leaf with the sky in it? + if ( !PVSCheck( pvs, dleafs[iLeaf2].cluster ) ) + continue; + + if ( dleafs[iLeaf2].flags & LEAF_FLAGS_SKY2D ) + { + pLeaf2DBits[ nByte ] |= nBit; + } + if ( dleafs[iLeaf2].flags & LEAF_FLAGS_SKY ) + { + pLeafBits[ nByte ] |= nBit; + + // As soon as we know this leaf needs to draw the 3d skybox, we're done + break; + } + } + } + + // Must set the bits in a separate pass so as to not flood-fill LEAF_FLAGS_SKY everywhere + // pLeafbits is a bit array of all leaves that need to be marked as seeing sky + for ( int iLeaf = 0; iLeaf < numleafs; ++iLeaf ) + { + // If this leaf has light (3d skybox) in it, then don't bother + if ( dleafs[iLeaf].flags & LEAF_FLAGS_SKY ) + continue; + + // Don't bother with this leaf if it's solid + if ( dleafs[iLeaf].contents & CONTENTS_SOLID ) + continue; + + // Check to see if this is a 2D skybox leaf + if ( pLeaf2DBits[ iLeaf >> 3 ] & (1 << ( iLeaf & 0x7 )) ) + { + dleafs[iLeaf].flags |= LEAF_FLAGS_SKY2D; + } + + // If this is a 3D skybox leaf, then we don't care if it was previously a 2D skybox leaf + if ( pLeafBits[ iLeaf >> 3 ] & (1 << ( iLeaf & 0x7 )) ) + { + dleafs[iLeaf].flags |= LEAF_FLAGS_SKY; + dleafs[iLeaf].flags &= ~LEAF_FLAGS_SKY2D; + } + else + { + // if radial vis was used on this leaf some of the portals leading + // to sky may have been culled. Try tracing to find sky. + if ( dleafs[iLeaf].flags & LEAF_FLAGS_RADIAL ) + { + if ( CanLeafTraceToSky(iLeaf) ) + { + // FIXME: Should make a version that checks if we hit 2D skyboxes.. oh well. + dleafs[iLeaf].flags |= LEAF_FLAGS_SKY; + } + } + } + } +} + +static char *ValueForKeyWithDefault (entity_t *ent, char *key, char *default_value = NULL) +{ + epair_t *ep; + + for (ep=ent->epairs ; ep ; ep=ep->next) + if (!strcmp (ep->key, key) ) + return ep->value; + return default_value; +} + +static void ParseLightEnvironment( entity_t* e, directlight_t* dl ) +{ + Vector dest; + GetVectorForKey (e, "origin", dest ); + dl = AllocDLight( dest, false ); + + ParseLightGeneric( e, dl ); + + char *angle_str=ValueForKeyWithDefault( e, "SunSpreadAngle" ); + if (angle_str) + { + g_SunAngularExtent=atof(angle_str); + g_SunAngularExtent=sin((M_PI/180.0)*g_SunAngularExtent); + printf("sun extent from map=%f\n",g_SunAngularExtent); + } + if ( !gSkyLight ) + { + // Sky light. + gSkyLight = dl; + dl->light.type = emit_skylight; + + // Sky ambient light. + gAmbient = AllocDLight( dl->light.origin, false ); + gAmbient->light.type = emit_skyambient; + if( g_bHDR && LightForKey( e, "_ambientHDR", gAmbient->light.intensity ) ) + { + // we have a valid HDR ambient light value + } + else if ( !LightForKey( e, "_ambient", gAmbient->light.intensity ) ) + { + VectorScale( dl->light.intensity, 0.5, gAmbient->light.intensity ); + } + if ( g_bHDR ) + { + VectorScale( gAmbient->light.intensity, + FloatForKeyWithDefault( e, "_AmbientScaleHDR", 1.0 ), + gAmbient->light.intensity ); + } + + BuildVisForLightEnvironment(); + + // Add sky and sky ambient lights to the list. + AddDLightToActiveList( gSkyLight ); + AddDLightToActiveList( gAmbient ); + } +} + +static void ParseLightPoint( entity_t* e, directlight_t* dl ) +{ + Vector dest; + GetVectorForKey (e, "origin", dest ); + dl = AllocDLight( dest, true ); + + ParseLightGeneric( e, dl ); + + dl->light.type = emit_point; + + SetLightFalloffParams(e,dl); +} + +/* + ============= + CreateDirectLights + ============= +*/ +#define DIRECT_SCALE (100.0*100.0) +void CreateDirectLights (void) +{ + unsigned i; + CPatch *p = NULL; + directlight_t *dl = NULL; + entity_t *e = NULL; + char *name; + Vector dest; + + numdlights = 0; + + FreeDLights(); + + // + // surfaces + // + unsigned int uiPatchCount = g_Patches.Count(); + for (i=0; i< uiPatchCount; i++) + { + p = &g_Patches.Element( i ); + + // skip parent patches + if (p->child1 != g_Patches.InvalidIndex() ) + continue; + + if (p->basearea < 1e-6) + continue; + + if( VectorAvg( p->baselight ) >= dlight_threshold ) + { + dl = AllocDLight( p->origin, true ); + + dl->light.type = emit_surface; + VectorCopy (p->normal, dl->light.normal); + Assert( VectorLength( p->normal ) > 1.0e-20 ); + // scale intensity by number of texture instances + VectorScale( p->baselight, lightscale * p->area * p->scale[0] * p->scale[1] / p->basearea, dl->light.intensity ); + + // scale to a range that results in actual light + VectorScale( dl->light.intensity, DIRECT_SCALE, dl->light.intensity ); + } + } + + // + // entities + // + for (i=0 ; i<(unsigned)num_entities ; i++) + { + e = &entities[i]; + name = ValueForKey (e, "classname"); + if (strncmp (name, "light", 5)) + continue; + + // Light_dynamic is actually a real entity; not to be included here... + if (!strcmp (name, "light_dynamic")) + continue; + + if (!strcmp (name, "light_spot")) + { + ParseLightSpot( e, dl ); + } + else if (!strcmp(name, "light_environment")) + { + ParseLightEnvironment( e, dl ); + } + else if (!strcmp(name, "light")) + { + ParseLightPoint( e, dl ); + } + else + { + qprintf( "unsupported light entity: \"%s\"\n", name ); + } + } + + qprintf ("%i direct lights\n", numdlights); + // exit(1); +} + +/* + ============= + ExportDirectLightsToWorldLights + ============= +*/ + +void ExportDirectLightsToWorldLights() +{ + directlight_t *dl; + + // In case the level has already been VRADed. + *pNumworldlights = 0; + + for (dl = activelights; dl != NULL; dl = dl->next ) + { + dworldlight_t *wl = &dworldlights[(*pNumworldlights)++]; + + if (*pNumworldlights > MAX_MAP_WORLDLIGHTS) + { + Error("too many lights %d / %d\n", *pNumworldlights, MAX_MAP_WORLDLIGHTS ); + } + + wl->cluster = dl->light.cluster; + wl->type = dl->light.type; + wl->style = dl->light.style; + VectorCopy( dl->light.origin, wl->origin ); + // FIXME: why does vrad want 0 to 255 and not 0 to 1?? + VectorScale( dl->light.intensity, (1.0 / 255.0), wl->intensity ); + VectorCopy( dl->light.normal, wl->normal ); + wl->stopdot = dl->light.stopdot; + wl->stopdot2 = dl->light.stopdot2; + wl->exponent = dl->light.exponent; + wl->radius = dl->light.radius; + wl->constant_attn = dl->light.constant_attn; + wl->linear_attn = dl->light.linear_attn; + wl->quadratic_attn = dl->light.quadratic_attn; + wl->flags = 0; + } +} + +/* + ============= + GatherSampleLight + ============= +*/ +#define NORMALFORMFACTOR 40.156979 // accumuated dot products for hemisphere + +#define CONSTANT_DOT (.7/2) + +#define NSAMPLES_SUN_AREA_LIGHT 30 // number of samples to take for an + // non-point sun light + +// Helper function - gathers light from sun (emit_skylight) +void GatherSampleSkyLightSSE( SSE_sampleLightOutput_t &out, directlight_t *dl, int facenum, + FourVectors const& pos, FourVectors *pNormals, int normalCount, int iThread, + int nLFlags, int static_prop_index_to_ignore, + float flEpsilon ) +{ + bool bIgnoreNormals = ( nLFlags & GATHERLFLAGS_IGNORE_NORMALS ) != 0; + bool force_fast = ( nLFlags & GATHERLFLAGS_FORCE_FAST ) != 0; + + fltx4 dot; + + if ( bIgnoreNormals ) + dot = ReplicateX4( CONSTANT_DOT ); + else + dot = NegSIMD( pNormals[0] * dl->light.normal ); + + dot = MaxSIMD( dot, Four_Zeros ); + int zeroMask = TestSignSIMD ( CmpEqSIMD( dot, Four_Zeros ) ); + if (zeroMask == 0xF) + return; + + int nsamples = 1; + if ( g_SunAngularExtent > 0.0f ) + { + nsamples = NSAMPLES_SUN_AREA_LIGHT; + if ( do_fast || force_fast ) + nsamples /= 4; + } + + fltx4 totalFractionVisible = Four_Zeros; + fltx4 fractionVisible = Four_Zeros; + + DirectionalSampler_t sampler; + + for ( int d = 0; d < nsamples; d++ ) + { + // determine visibility of skylight + // serach back to see if we can hit a sky brush + Vector delta; + VectorScale( dl->light.normal, -MAX_TRACE_LENGTH, delta ); + if ( d ) + { + // jitter light source location + Vector ofs = sampler.NextValue(); + ofs *= MAX_TRACE_LENGTH * g_SunAngularExtent; + delta += ofs; + } + FourVectors delta4; + delta4.DuplicateVector ( delta ); + delta4 += pos; + + TestLine_DoesHitSky ( pos, delta4, &fractionVisible, true, static_prop_index_to_ignore ); + + totalFractionVisible = AddSIMD ( totalFractionVisible, fractionVisible ); + } + + fltx4 seeAmount = MulSIMD ( totalFractionVisible, ReplicateX4 ( 1.0f / nsamples ) ); + out.m_flDot[0] = MulSIMD ( dot, seeAmount ); + out.m_flFalloff = Four_Ones; + out.m_flSunAmount = MulSIMD ( seeAmount, ReplicateX4( 10000.0f ) ); + for ( int i = 1; i < normalCount; i++ ) + { + if ( bIgnoreNormals ) + out.m_flDot[i] = ReplicateX4 ( CONSTANT_DOT ); + else + { + out.m_flDot[i] = NegSIMD( pNormals[i] * dl->light.normal ); + out.m_flDot[i] = MulSIMD( out.m_flDot[i], seeAmount ); + } + } +} + +// Helper function - gathers light from ambient sky light +void GatherSampleAmbientSkySSE( SSE_sampleLightOutput_t &out, directlight_t *dl, int facenum, + FourVectors const& pos, FourVectors *pNormals, int normalCount, int iThread, + int nLFlags, int static_prop_index_to_ignore, + float flEpsilon ) +{ + + bool bIgnoreNormals = ( nLFlags & GATHERLFLAGS_IGNORE_NORMALS ) != 0; + bool force_fast = ( nLFlags & GATHERLFLAGS_FORCE_FAST ) != 0; + + fltx4 sumdot = Four_Zeros; + fltx4 ambient_intensity[NUM_BUMP_VECTS+1]; + fltx4 possibleHitCount[NUM_BUMP_VECTS+1]; + fltx4 dots[NUM_BUMP_VECTS+1]; + + for ( int i = 0; i < normalCount; i++ ) + { + ambient_intensity[i] = Four_Zeros; + possibleHitCount[i] = Four_Zeros; + } + + DirectionalSampler_t sampler; + int nsky_samples = NUMVERTEXNORMALS; + if (do_fast || force_fast ) + nsky_samples /= 4; + else + nsky_samples *= g_flSkySampleScale; + + for (int j = 0; j < nsky_samples; j++) + { + FourVectors anorm; + anorm.DuplicateVector( sampler.NextValue() ); + + if ( bIgnoreNormals ) + dots[0] = ReplicateX4( CONSTANT_DOT ); + else + dots[0] = NegSIMD( pNormals[0] * anorm ); + + fltx4 validity = CmpGtSIMD( dots[0], ReplicateX4( EQUAL_EPSILON ) ); + + // No possibility of anybody getting lit + if ( !TestSignSIMD( validity ) ) + continue; + + dots[0] = AndSIMD( validity, dots[0] ); + sumdot = AddSIMD( dots[0], sumdot ); + possibleHitCount[0] = AddSIMD( AndSIMD( validity, Four_Ones ), possibleHitCount[0] ); + + for ( int i = 1; i < normalCount; i++ ) + { + if ( bIgnoreNormals ) + dots[i] = ReplicateX4( CONSTANT_DOT ); + else + dots[i] = NegSIMD( pNormals[i] * anorm ); + fltx4 validity2 = CmpGtSIMD( dots[i], ReplicateX4 ( EQUAL_EPSILON ) ); + dots[i] = AndSIMD( validity2, dots[i] ); + possibleHitCount[i] = AddSIMD( AndSIMD( AndSIMD( validity, validity2 ), Four_Ones ), possibleHitCount[i] ); + } + + // search back to see if we can hit a sky brush + FourVectors delta = anorm; + delta *= -MAX_TRACE_LENGTH; + delta += pos; + FourVectors surfacePos = pos; + FourVectors offset = anorm; + offset *= -flEpsilon; + surfacePos -= offset; + + fltx4 fractionVisible = Four_Ones; + TestLine_DoesHitSky( surfacePos, delta, &fractionVisible, true, static_prop_index_to_ignore ); + for ( int i = 0; i < normalCount; i++ ) + { + fltx4 addedAmount = MulSIMD( fractionVisible, dots[i] ); + ambient_intensity[i] = AddSIMD( ambient_intensity[i], addedAmount ); + } + + } + + out.m_flFalloff = Four_Ones; + for ( int i = 0; i < normalCount; i++ ) + { + // now scale out the missing parts of the hemisphere of this bump basis vector + fltx4 factor = ReciprocalSIMD( possibleHitCount[0] ); + factor = MulSIMD( factor, possibleHitCount[i] ); + out.m_flDot[i] = MulSIMD( factor, sumdot ); + out.m_flDot[i] = ReciprocalSIMD( out.m_flDot[i] ); + out.m_flDot[i] = MulSIMD( ambient_intensity[i], out.m_flDot[i] ); + } + +} + +// Helper function - gathers light from area lights, spot lights, and point lights +void GatherSampleStandardLightSSE( SSE_sampleLightOutput_t &out, directlight_t *dl, int facenum, + FourVectors const& pos, FourVectors *pNormals, int normalCount, int iThread, + int nLFlags, int static_prop_index_to_ignore, + float flEpsilon ) +{ + bool bIgnoreNormals = ( nLFlags & GATHERLFLAGS_IGNORE_NORMALS ) != 0; + + FourVectors src; + src.DuplicateVector( vec3_origin ); + + if (dl->facenum == -1) + { + src.DuplicateVector( dl->light.origin ); + } + + // Find light vector + FourVectors delta; + delta = src; + delta -= pos; + fltx4 dist2 = delta.length2(); + fltx4 rpcDist = ReciprocalSqrtSIMD( dist2 ); + delta *= rpcDist; + fltx4 dist = SqrtEstSIMD( dist2 );//delta.VectorNormalize(); + + // Compute dot + fltx4 dot = ReplicateX4( (float) CONSTANT_DOT ); + if ( !bIgnoreNormals ) + dot = delta * pNormals[0]; + dot = MaxSIMD( Four_Zeros, dot ); + + // Affix dot to zero if past fade distz + bool bHasHardFalloff = ( dl->m_flEndFadeDistance > dl->m_flStartFadeDistance ); + if ( bHasHardFalloff ) + { + fltx4 notPastFadeDist = CmpLeSIMD ( dist, ReplicateX4 ( dl->m_flEndFadeDistance ) ); + dot = AndSIMD( dot, notPastFadeDist ); // dot = 0 if past fade distance + if ( !TestSignSIMD ( notPastFadeDist ) ) + return; + } + + dist = MaxSIMD( dist, Four_Ones ); + fltx4 falloffEvalDist = MinSIMD( dist, ReplicateX4( dl->m_flCapDist ) ); + + fltx4 constant, linear, quadratic; + fltx4 dot2, inCone, inFringe, mult; + FourVectors offset; + + switch (dl->light.type) + { + case emit_point: + constant = ReplicateX4( dl->light.constant_attn ); + linear = ReplicateX4( dl->light.linear_attn ); + quadratic = ReplicateX4( dl->light.quadratic_attn ); + + out.m_flFalloff = MulSIMD( falloffEvalDist, falloffEvalDist ); + out.m_flFalloff = MulSIMD( out.m_flFalloff, quadratic ); + out.m_flFalloff = AddSIMD( out.m_flFalloff, MulSIMD( linear, falloffEvalDist ) ); + out.m_flFalloff = AddSIMD( out.m_flFalloff, constant ); + out.m_flFalloff = ReciprocalSIMD( out.m_flFalloff ); + break; + + case emit_surface: + dot2 = delta * dl->light.normal; + dot2 = NegSIMD( dot2 ); + + // Light behind surface yields zero dot + dot2 = MaxSIMD( Four_Zeros, dot2 ); + if ( TestSignSIMD( CmpEqSIMD( Four_Zeros, dot ) ) == 0xF ) + return; + + out.m_flFalloff = ReciprocalSIMD ( dist2 ); + out.m_flFalloff = MulSIMD( out.m_flFalloff, dot2 ); + + // move the endpoint away from the surface by epsilon to prevent hitting the surface with the trace + offset.DuplicateVector ( dl->light.normal ); + offset *= DIST_EPSILON; + src += offset; + break; + + case emit_spotlight: + dot2 = delta * dl->light.normal; + dot2 = NegSIMD( dot2 ); + + // Affix dot2 to zero if outside light cone + inCone = CmpGtSIMD( dot2, ReplicateX4( dl->light.stopdot2 ) ); + if ( !TestSignSIMD ( inCone ) ) + return; + dot = AndSIMD( inCone, dot ); + + constant = ReplicateX4( dl->light.constant_attn ); + linear = ReplicateX4( dl->light.linear_attn ); + quadratic = ReplicateX4( dl->light.quadratic_attn ); + + out.m_flFalloff = MulSIMD( falloffEvalDist, falloffEvalDist ); + out.m_flFalloff = MulSIMD( out.m_flFalloff, quadratic ); + out.m_flFalloff = AddSIMD( out.m_flFalloff, MulSIMD( linear, falloffEvalDist ) ); + out.m_flFalloff = AddSIMD( out.m_flFalloff, constant ); + out.m_flFalloff = ReciprocalSIMD( out.m_flFalloff ); + out.m_flFalloff = MulSIMD( out.m_flFalloff, dot2 ); + + // outside the inner cone + inFringe = CmpLeSIMD( dot2, ReplicateX4( dl->light.stopdot ) ); + mult = ReplicateX4( dl->light.stopdot - dl->light.stopdot2 ); + mult = ReciprocalSIMD( mult ); + mult = MulSIMD( mult, SubSIMD( dot2, ReplicateX4( dl->light.stopdot2 ) ) ); + mult = MinSIMD( mult, Four_Ones ); + mult = MaxSIMD( mult, Four_Zeros ); + + // pow is fixed point, so this isn't the most accurate, but it doesn't need to be + if ( (dl->light.exponent != 0.0f ) && ( dl->light.exponent != 1.0f ) ) + mult = PowSIMD( mult, dl->light.exponent ); + + // if not in between inner and outer cones, mult by 1 + mult = AndSIMD( inFringe, mult ); + mult = AddSIMD( mult, AndNotSIMD( inFringe, Four_Ones ) ); + out.m_flFalloff = MulSIMD( mult, out.m_flFalloff ); + break; + + } + + // we may be in the fade region - modulate lighting by the fade curve + //float t = ( dist - dl->m_flStartFadeDistance ) / + // ( dl->m_flEndFadeDistance - dl->m_flStartFadeDistance ); + if ( bHasHardFalloff ) + { + fltx4 t = ReplicateX4( dl->m_flEndFadeDistance - dl->m_flStartFadeDistance ); + t = ReciprocalSIMD( t ); + t = MulSIMD( t, SubSIMD( dist, ReplicateX4( dl->m_flStartFadeDistance ) ) ); + + // clamp t to [0...1] + t = MinSIMD( t, Four_Ones ); + t = MaxSIMD( t, Four_Zeros ); + t = SubSIMD( Four_Ones, t ); + + // Using QuinticInterpolatingPolynomial, SSE-ified + // t * t * t *( t * ( t* 6.0 - 15.0 ) + 10.0 ) + mult = SubSIMD( MulSIMD( ReplicateX4( 6.0f ), t ), ReplicateX4( 15.0f ) ); + mult = AddSIMD( MulSIMD( mult, t ), ReplicateX4( 10.0f ) ); + mult = MulSIMD( MulSIMD( t, t), mult ); + mult = MulSIMD( t, mult ); + out.m_flFalloff = MulSIMD( mult, out.m_flFalloff ); + } + + // Raytrace for visibility function + fltx4 fractionVisible = Four_Ones; + TestLine( pos, src, &fractionVisible, static_prop_index_to_ignore); + dot = MulSIMD( fractionVisible, dot ); + out.m_flDot[0] = dot; + + for ( int i = 1; i < normalCount; i++ ) + { + if ( bIgnoreNormals ) + out.m_flDot[i] = ReplicateX4( (float) CONSTANT_DOT ); + else + { + out.m_flDot[i] = pNormals[i] * delta; + out.m_flDot[i] = MaxSIMD( Four_Zeros, out.m_flDot[i] ); + } + } +} + +// returns dot product with normal and delta +// dl - light +// pos - position of sample +// normal - surface normal of sample +// out.m_flDot[] - returned dot products with light vector and each normal +// out.m_flFalloff - amount of light falloff +void GatherSampleLightSSE( SSE_sampleLightOutput_t &out, directlight_t *dl, int facenum, + FourVectors const& pos, FourVectors *pNormals, int normalCount, int iThread, + int nLFlags, + int static_prop_index_to_ignore, + float flEpsilon ) +{ + for ( int b = 0; b < normalCount; b++ ) + out.m_flDot[b] = Four_Zeros; + out.m_flFalloff = Four_Zeros; + out.m_flSunAmount = Four_Zeros; + Assert( normalCount <= (NUM_BUMP_VECTS+1) ); + + // skylights work fundamentally differently than normal lights + switch( dl->light.type ) + { + case emit_skylight: + GatherSampleSkyLightSSE( out, dl, facenum, pos, pNormals, normalCount, + iThread, nLFlags, static_prop_index_to_ignore, flEpsilon ); + break; + case emit_skyambient: + GatherSampleAmbientSkySSE( out, dl, facenum, pos, pNormals, normalCount, + iThread, nLFlags, static_prop_index_to_ignore, flEpsilon ); + break; + case emit_point: + case emit_surface: + case emit_spotlight: + GatherSampleStandardLightSSE( out, dl, facenum, pos, pNormals, normalCount, + iThread, nLFlags, static_prop_index_to_ignore, flEpsilon ); + break; + default: + Error ("Bad dl->light.type"); + return; + } + + // NOTE: Notice here that if the light is on the back side of the face + // (tested by checking the dot product of the face normal and the light position) + // we don't want it to contribute to *any* of the bumped lightmaps. It glows + // in disturbing ways if we don't do this. + out.m_flDot[0] = MaxSIMD ( out.m_flDot[0], Four_Zeros ); + fltx4 notZero = CmpGtSIMD( out.m_flDot[0], Four_Zeros ); + for ( int n = 1; n < normalCount; n++ ) + { + out.m_flDot[n] = MaxSIMD( out.m_flDot[n], Four_Zeros ); + out.m_flDot[n] = AndSIMD( out.m_flDot[n], notZero ); + } + +} + +/* + ============= + AddSampleToPatch + + Take the sample's collected light and + add it back into the apropriate patch + for the radiosity pass. + ============= +*/ +void AddSampleToPatch (sample_t *s, LightingValue_t& light, int facenum) +{ + CPatch *patch; + Vector mins, maxs; + int i; + + if (numbounce == 0) + return; + if( VectorAvg( light.m_vecLighting ) < 1) + return; + + // + // fixed the sample position and normal -- need to find the equiv pos, etc to set up + // patches + // + if( g_FacePatches.Element( facenum ) == g_FacePatches.InvalidIndex() ) + return; + + float radius = sqrt( s->area ) / 2.0; + + CPatch *pNextPatch = NULL; + for( patch = &g_Patches.Element( g_FacePatches.Element( facenum ) ); patch; patch = pNextPatch ) + { + // next patch + pNextPatch = NULL; + if( patch->ndxNext != g_Patches.InvalidIndex() ) + { + pNextPatch = &g_Patches.Element( patch->ndxNext ); + } + + if (patch->sky) + continue; + + // skip patches with children + if ( patch->child1 != g_Patches.InvalidIndex() ) + continue; + + // see if the point is in this patch (roughly) + WindingBounds (patch->winding, mins, maxs); + + for (i=0 ; i<3 ; i++) + { + if (mins[i] > s->pos[i] + radius) + goto nextpatch; + if (maxs[i] < s->pos[i] - radius) + goto nextpatch; + } + + // add the sample to the patch + patch->samplearea += s->area; + VectorMA( patch->samplelight, s->area, light.m_vecLighting, patch->samplelight ); + + nextpatch:; + } + // don't worry if some samples don't find a patch +} + + +void GetPhongNormal( int facenum, Vector const& spot, Vector& phongnormal ) +{ + int j; + dface_t *f = &g_pFaces[facenum]; +// dplane_t *p = &dplanes[f->planenum]; + Vector facenormal, vspot; + + VectorCopy( dplanes[f->planenum].normal, facenormal ); + VectorCopy( facenormal, phongnormal ); + + if ( smoothing_threshold != 1 ) + { + faceneighbor_t *fn = &faceneighbor[facenum]; + + // Calculate modified point normal for surface + // Use the edge normals iff they are defined. Bend the surface towards the edge normal(s) + // Crude first attempt: find nearest edge normal and do a simple interpolation with facenormal. + // Second attempt: find edge points+center that bound the point and do a three-point triangulation(baricentric) + // Better third attempt: generate the point normals for all vertices and do baricentric triangulation. + + for (j=0 ; jnumedges ; j++) + { + Vector v1, v2; + //int e = dsurfedges[f->firstedge + j]; + //int e1 = dsurfedges[f->firstedge + ((j+f->numedges-1)%f->numedges)]; + //int e2 = dsurfedges[f->firstedge + ((j+1)%f->numedges)]; + + //edgeshare_t *es = &edgeshare[abs(e)]; + //edgeshare_t *es1 = &edgeshare[abs(e1)]; + //edgeshare_t *es2 = &edgeshare[abs(e2)]; + // dface_t *f2; + float a1, a2, aa, bb, ab; + int vert1, vert2; + + Vector& n1 = fn->normal[j]; + Vector& n2 = fn->normal[(j+1)%f->numedges]; + + /* + if (VectorCompare( n1, fn->facenormal ) + && VectorCompare( n2, fn->facenormal) ) + continue; + */ + + vert1 = EdgeVertex( f, j ); + vert2 = EdgeVertex( f, j+1 ); + + Vector& p1 = dvertexes[vert1].point; + Vector& p2 = dvertexes[vert2].point; + + // Build vectors from the middle of the face to the edge vertexes and the sample pos. + VectorSubtract( p1, face_centroids[facenum], v1 ); + VectorSubtract( p2, face_centroids[facenum], v2 ); + VectorSubtract( spot, face_centroids[facenum], vspot ); + aa = DotProduct( v1, v1 ); + bb = DotProduct( v2, v2 ); + ab = DotProduct( v1, v2 ); + a1 = (bb * DotProduct( v1, vspot ) - ab * DotProduct( vspot, v2 )) / (aa * bb - ab * ab); + a2 = (DotProduct( vspot, v2 ) - a1 * ab) / bb; + + // Test center to sample vector for inclusion between center to vertex vectors (Use dot product of vectors) + if ( a1 >= 0.0 && a2 >= 0.0) + { + // calculate distance from edge to pos + Vector temp; + float scale; + + // Interpolate between the center and edge normals based on sample position + scale = 1.0 - a1 - a2; + VectorScale( fn->facenormal, scale, phongnormal ); + VectorScale( n1, a1, temp ); + VectorAdd( phongnormal, temp, phongnormal ); + VectorScale( n2, a2, temp ); + VectorAdd( phongnormal, temp, phongnormal ); + Assert( VectorLength( phongnormal ) > 1.0e-20 ); + VectorNormalize( phongnormal ); + + /* + if (a1 > 1 || a2 > 1 || a1 + a2 > 1) + { + Msg("\n%.2f %.2f\n", a1, a2 ); + Msg("%.2f %.2f %.2f\n", v1[0], v1[1], v1[2] ); + Msg("%.2f %.2f %.2f\n", v2[0], v2[1], v2[2] ); + Msg("%.2f %.2f %.2f\n", vspot[0], vspot[1], vspot[2] ); + exit(1); + + a1 = 0; + } + */ + /* + phongnormal[0] = (((j + 1) & 4) != 0) * 255; + phongnormal[1] = (((j + 1) & 2) != 0) * 255; + phongnormal[2] = (((j + 1) & 1) != 0) * 255; + */ + return; + } + } + } +} + +void GetPhongNormal( int facenum, FourVectors const& spot, FourVectors& phongnormal ) +{ + int j; + dface_t *f = &g_pFaces[facenum]; + // dplane_t *p = &dplanes[f->planenum]; + Vector facenormal; + FourVectors vspot; + + VectorCopy( dplanes[f->planenum].normal, facenormal ); + phongnormal.DuplicateVector( facenormal ); + + FourVectors faceCentroid; + faceCentroid.DuplicateVector( face_centroids[facenum] ); + + if ( smoothing_threshold != 1 ) + { + faceneighbor_t *fn = &faceneighbor[facenum]; + + // Calculate modified point normal for surface + // Use the edge normals iff they are defined. Bend the surface towards the edge normal(s) + // Crude first attempt: find nearest edge normal and do a simple interpolation with facenormal. + // Second attempt: find edge points+center that bound the point and do a three-point triangulation(baricentric) + // Better third attempt: generate the point normals for all vertices and do baricentric triangulation. + + for ( j = 0; j < f->numedges; ++j ) + { + Vector v1, v2; + fltx4 a1, a2; + float aa, bb, ab; + int vert1, vert2; + + Vector& n1 = fn->normal[j]; + Vector& n2 = fn->normal[(j+1)%f->numedges]; + + vert1 = EdgeVertex( f, j ); + vert2 = EdgeVertex( f, j+1 ); + + Vector& p1 = dvertexes[vert1].point; + Vector& p2 = dvertexes[vert2].point; + + // Build vectors from the middle of the face to the edge vertexes and the sample pos. + VectorSubtract( p1, face_centroids[facenum], v1 ); + VectorSubtract( p2, face_centroids[facenum], v2 ); + //VectorSubtract( spot, face_centroids[facenum], vspot ); + vspot = spot; + vspot -= faceCentroid; + aa = DotProduct( v1, v1 ); + bb = DotProduct( v2, v2 ); + ab = DotProduct( v1, v2 ); + //a1 = (bb * DotProduct( v1, vspot ) - ab * DotProduct( vspot, v2 )) / (aa * bb - ab * ab); + a1 = ReciprocalSIMD( ReplicateX4( aa * bb - ab * ab ) ); + a1 = MulSIMD( a1, SubSIMD( MulSIMD( ReplicateX4( bb ), vspot * v1 ), MulSIMD( ReplicateX4( ab ), vspot * v2 ) ) ); + //a2 = (DotProduct( vspot, v2 ) - a1 * ab) / bb; + a2 = ReciprocalSIMD( ReplicateX4( bb ) ); + a2 = MulSIMD( a2, SubSIMD( vspot * v2, MulSIMD( a1, ReplicateX4( ab ) ) ) ); + + fltx4 resultMask = AndSIMD( CmpGeSIMD( a1, Four_Zeros ), CmpGeSIMD( a2, Four_Zeros ) ); + + if ( !TestSignSIMD( resultMask ) ) + continue; + + // Store the old phong normal to avoid overwriting already computed phong normals + FourVectors oldPhongNormal = phongnormal; + + // calculate distance from edge to pos + FourVectors temp; + fltx4 scale; + + // Interpolate between the center and edge normals based on sample position + scale = SubSIMD( SubSIMD( Four_Ones, a1 ), a2 ); + phongnormal.DuplicateVector( fn->facenormal ); + phongnormal *= scale; + temp.DuplicateVector( n1 ); + temp *= a1; + phongnormal += temp; + temp.DuplicateVector( n2 ); + temp *= a2; + phongnormal += temp; + + // restore the old phong normals + phongnormal.x = AddSIMD( AndSIMD( resultMask, phongnormal.x ), AndNotSIMD( resultMask, oldPhongNormal.x ) ); + phongnormal.y = AddSIMD( AndSIMD( resultMask, phongnormal.y ), AndNotSIMD( resultMask, oldPhongNormal.y ) ); + phongnormal.z = AddSIMD( AndSIMD( resultMask, phongnormal.z ), AndNotSIMD( resultMask, oldPhongNormal.z ) ); + } + + phongnormal.VectorNormalize(); + } +} + + + +int GetVisCache( int lastoffset, int cluster, byte *pvs ) +{ + // get the PVS for the pos to limit the number of checks + if ( !visdatasize ) + { + memset (pvs, 255, (dvis->numclusters+7)/8 ); + lastoffset = -1; + } + else + { + if (cluster < 0) + { + // Error, point embedded in wall + // sampled[0][1] = 255; + memset (pvs, 255, (dvis->numclusters+7)/8 ); + lastoffset = -1; + } + else + { + int thisoffset = dvis->bitofs[ cluster ][DVIS_PVS]; + if ( thisoffset != lastoffset ) + { + if ( thisoffset == -1 ) + { + Error ("visofs == -1"); + } + + DecompressVis (&dvisdata[thisoffset], pvs); + } + lastoffset = thisoffset; + } + } + return lastoffset; +} + + +void BuildPatchLights( int facenum ); + +void DumpSamples( int ndxFace, facelight_t *pFaceLight ) +{ + ThreadLock(); + + dface_t *pFace = &g_pFaces[ndxFace]; + if( pFace ) + { + bool bBumpped = ( ( texinfo[pFace->texinfo].flags & SURF_BUMPLIGHT ) != 0 ); + + for( int iStyle = 0; iStyle < 4; ++iStyle ) + { + if( pFace->styles[iStyle] != 255 ) + { + for ( int iBump = 0; iBump < 4; ++iBump ) + { + if ( iBump == 0 || ( iBump > 0 && bBumpped ) ) + { + for( int iSample = 0; iSample < pFaceLight->numsamples; ++iSample ) + { + sample_t *pSample = &pFaceLight->sample[iSample]; + WriteWinding( pFileSamples[iStyle][iBump], pSample->w, pFaceLight->light[iStyle][iBump][iSample].m_vecLighting ); + if( bDumpNormals ) + { + WriteNormal( pFileSamples[iStyle][iBump], pSample->pos, pSample->normal, 15.0f, pSample->normal * 255.0f ); + } + } + } + } + } + } + } + + ThreadUnlock(); +} + + +//----------------------------------------------------------------------------- +// Allocates light sample data +//----------------------------------------------------------------------------- +static inline void AllocateLightstyleSamples( facelight_t* fl, int styleIndex, int numnormals ) +{ + for (int n = 0; n < numnormals; ++n) + { + fl->light[styleIndex][n] = ( LightingValue_t* )calloc( fl->numsamples, sizeof(LightingValue_t ) ); + } +} + + +//----------------------------------------------------------------------------- +// Used to find an existing lightstyle on a face +//----------------------------------------------------------------------------- +static inline int FindLightstyle( dface_t* f, int lightstyle ) +{ + for (int k = 0; k < MAXLIGHTMAPS; k++) + { + if (f->styles[k] == lightstyle) + return k; + } + + return -1; +} + +static int FindOrAllocateLightstyleSamples( dface_t* f, facelight_t *fl, int lightstyle, int numnormals ) +{ + // Search the lightstyles associated with the face for a match + int k; + for (k = 0; k < MAXLIGHTMAPS; k++) + { + if (f->styles[k] == lightstyle) + break; + + // Found an empty entry, we can use it for a new lightstyle + if (f->styles[k] == 255) + { + AllocateLightstyleSamples( fl, k, numnormals ); + f->styles[k] = lightstyle; + break; + } + } + + // Check for overflow + if (k >= MAXLIGHTMAPS) + return -1; + + return k; +} + + +//----------------------------------------------------------------------------- +// Compute the illumination point + normal for the sample +//----------------------------------------------------------------------------- +static void ComputeIlluminationPointAndNormalsSSE( lightinfo_t const& l, FourVectors const &pos, FourVectors const &norm, SSE_SampleInfo_t* pInfo, int numSamples ) +{ + + Vector v[4]; + + pInfo->m_Points = pos; + bool computeNormals = ( pInfo->m_NormalCount > 1 && ( pInfo->m_IsDispFace || !l.isflat ) ); + + // FIXME: move sample point off the surface a bit, this is done so that + // light sampling will not be affected by a bug where raycasts will + // intersect with the face being lit. We really should just have that + // logic in GatherSampleLight + FourVectors faceNormal; + faceNormal.DuplicateVector( l.facenormal ); + pInfo->m_Points += faceNormal; + + if ( pInfo->m_IsDispFace ) + { + pInfo->m_PointNormals[0] = norm; + } + else if ( !l.isflat ) + { + // If the face isn't flat, use a phong-based normal instead + FourVectors modelorg; + modelorg.DuplicateVector( l.modelorg ); + FourVectors vecSample = pos; + vecSample -= modelorg; + GetPhongNormal( pInfo->m_FaceNum, vecSample, pInfo->m_PointNormals[0] ); + } + + if ( computeNormals ) + { + Vector bv[4][NUM_BUMP_VECTS]; + for ( int i = 0; i < 4; ++i ) + { + // TODO: using Vec may slow things down a bit + GetBumpNormals( pInfo->m_pTexInfo->textureVecsTexelsPerWorldUnits[0], + pInfo->m_pTexInfo->textureVecsTexelsPerWorldUnits[1], + l.facenormal, pInfo->m_PointNormals[0].Vec( i ), bv[i] ); + } + for ( int b = 0; b < NUM_BUMP_VECTS; ++b ) + { + pInfo->m_PointNormals[b+1].LoadAndSwizzle ( bv[0][b], bv[1][b], bv[2][b], bv[3][b] ); + } + } + + // TODO: this may slow things down a bit ( using Vec ) + for ( int i = 0; i < 4; ++i ) + pInfo->m_Clusters[i] = ClusterFromPoint( pos.Vec( i ) ); +} + +//----------------------------------------------------------------------------- +// Iterates over all lights and computes lighting at up to 4 sample points +//----------------------------------------------------------------------------- +static void GatherSampleLightAt4Points( SSE_SampleInfo_t& info, int sampleIdx, int numSamples ) +{ + SSE_sampleLightOutput_t out; + + // Iterate over all direct lights and add them to the particular sample + for (directlight_t *dl = activelights; dl != NULL; dl = dl->next) + { + // is this lights cluster visible? + fltx4 dotMask = Four_Zeros; + bool skipLight = true; + for( int s = 0; s < numSamples; s++ ) + { + if( PVSCheck( dl->pvs, info.m_Clusters[s] ) ) + { + dotMask = SetComponentSIMD( dotMask, s, 1.0f ); + skipLight = false; + } + } + if ( skipLight ) + continue; + + GatherSampleLightSSE( out, dl, info.m_FaceNum, info.m_Points, info.m_PointNormals, info.m_NormalCount, info.m_iThread ); + + // Apply the PVS check filter and compute falloff x dot + fltx4 fxdot[NUM_BUMP_VECTS + 1]; + skipLight = true; + for ( int b = 0; b < info.m_NormalCount; b++ ) + { + fxdot[b] = MulSIMD( out.m_flDot[b], dotMask ); + fxdot[b] = MulSIMD( fxdot[b], out.m_flFalloff ); + if ( !IsAllZeros( fxdot[b] ) ) + { + skipLight = false; + } + } + if ( skipLight ) + continue; + + // Figure out the lightstyle for this particular sample + int lightStyleIndex = FindOrAllocateLightstyleSamples( info.m_pFace, info.m_pFaceLight, + dl->light.style, info.m_NormalCount ); + if (lightStyleIndex < 0) + { + if (info.m_WarnFace != info.m_FaceNum) + { + Warning ("\nWARNING: Too many light styles on a face at (%f, %f, %f)\n", + info.m_Points.x.m128_f32[0], info.m_Points.y.m128_f32[0], info.m_Points.z.m128_f32[0] ); + info.m_WarnFace = info.m_FaceNum; + } + continue; + } + + // pLightmaps is an array of the lightmaps for each normal direction, + // here's where the result of the sample gathering goes + LightingValue_t** pLightmaps = info.m_pFaceLight->light[lightStyleIndex]; + + // Incremental lighting only cares about lightstyle zero + if( g_pIncremental && (dl->light.style == 0) ) + { + for ( int i = 0; i < numSamples; i++ ) + { + g_pIncremental->AddLightToFace( dl->m_IncrementalID, info.m_FaceNum, sampleIdx + i, + info.m_LightmapSize, SubFloat( fxdot[0], i ), info.m_iThread ); + } + } + + for( int n = 0; n < info.m_NormalCount; ++n ) + { + for ( int i = 0; i < numSamples; i++ ) + { + pLightmaps[n][sampleIdx + i].AddLight( SubFloat( fxdot[n], i ), dl->light.intensity, SubFloat( out.m_flSunAmount, i ) ); + } + } + } +} + + + +//----------------------------------------------------------------------------- +// Iterates over all lights and computes lighting at a sample point +//----------------------------------------------------------------------------- +static void ResampleLightAt4Points( SSE_SampleInfo_t& info, int lightStyleIndex, int flags, LightingValue_t pLightmap[4][NUM_BUMP_VECTS+1] ) +{ + SSE_sampleLightOutput_t out; + + // Clear result + for ( int i = 0; i < 4; ++i ) + { + for ( int n = 0; n < info.m_NormalCount; ++n ) + { + pLightmap[i][n].Zero(); + } + } + + // Iterate over all direct lights and add them to the particular sample + for (directlight_t *dl = activelights; dl != NULL; dl = dl->next) + { + if ((flags & AMBIENT_ONLY) && (dl->light.type != emit_skyambient)) + continue; + + if ((flags & NON_AMBIENT_ONLY) && (dl->light.type == emit_skyambient)) + continue; + + // Only add contributions that match the lightstyle + Assert( lightStyleIndex <= MAXLIGHTMAPS ); + Assert( info.m_pFace->styles[lightStyleIndex] != 255 ); + if (dl->light.style != info.m_pFace->styles[lightStyleIndex]) + continue; + + // is this lights cluster visible? + fltx4 dotMask = Four_Zeros; + bool skipLight = true; + for( int s = 0; s < 4; s++ ) + { + if( PVSCheck( dl->pvs, info.m_Clusters[s] ) ) + { + dotMask = SetComponentSIMD( dotMask, s, 1.0f ); + skipLight = false; + } + } + if ( skipLight ) + continue; + + // NOTE: Notice here that if the light is on the back side of the face + // (tested by checking the dot product of the face normal and the light position) + // we don't want it to contribute to *any* of the bumped lightmaps. It glows + // in disturbing ways if we don't do this. + GatherSampleLightSSE( out, dl, info.m_FaceNum, info.m_Points, info.m_PointNormals, info.m_NormalCount, info.m_iThread ); + + // Apply the PVS check filter and compute falloff x dot + fltx4 fxdot[NUM_BUMP_VECTS + 1]; + for ( int b = 0; b < info.m_NormalCount; b++ ) + { + fxdot[b] = MulSIMD( out.m_flFalloff, out.m_flDot[b] ); + fxdot[b] = MulSIMD( fxdot[b], dotMask ); + } + + // Compute the contributions to each of the bumped lightmaps + // The first sample is for non-bumped lighting. + // The other sample are for bumpmapping. + for( int i = 0; i < 4; ++i ) + { + for( int n = 0; n < info.m_NormalCount; ++n ) + { + pLightmap[i][n].AddLight( SubFloat( fxdot[n], i ), dl->light.intensity, SubFloat( out.m_flSunAmount, i ) ); + } + } + } +} + +bool PointsInWinding ( FourVectors const & point, winding_t *w, int &invalidBits ) +{ + FourVectors edge, toPt, cross, testCross, p0, p1; + fltx4 invalidMask; + + // + // get the first normal to test + // + p0.DuplicateVector( w->p[0] ); + p1.DuplicateVector( w->p[1] ); + toPt = point; + toPt -= p0; + edge = p1; + edge -= p0; + testCross = edge ^ toPt; + testCross.VectorNormalizeFast(); + + for( int ndxPt = 1; ndxPt < w->numpoints; ndxPt++ ) + { + p0.DuplicateVector( w->p[ndxPt] ); + p1.DuplicateVector( w->p[(ndxPt+1)%w->numpoints] ); + toPt = point; + toPt -= p0; + edge = p1; + edge -= p0; + cross = edge ^ toPt; + cross.VectorNormalizeFast(); + + fltx4 dot = cross * testCross; + invalidMask = OrSIMD( invalidMask, CmpLtSIMD( dot, Four_Zeros ) ); + + invalidBits = TestSignSIMD ( invalidMask ); + if ( invalidBits == 0xF ) + return false; + } + + return true; +} + +//----------------------------------------------------------------------------- +// Perform supersampling at a particular point +//----------------------------------------------------------------------------- +static int SupersampleLightAtPoint( lightinfo_t& l, SSE_SampleInfo_t& info, + int sampleIndex, int lightStyleIndex, LightingValue_t *pLight, int flags ) +{ + sample_t& sample = info.m_pFaceLight->sample[sampleIndex]; + + // Get the position of the original sample in lightmapspace + Vector2D temp; + WorldToLuxelSpace( &l, sample.pos, temp ); + Vector sampleLightOrigin( temp[0], temp[1], 0.0f ); + + // Some parameters related to supersampling + float sampleWidth = ( flags & NON_AMBIENT_ONLY ) ? 4 : 2; + float cscale = 1.0f / sampleWidth; + float csshift = -((sampleWidth - 1) * cscale) / 2.0; + + // Clear out the light values + for (int i = 0; i < info.m_NormalCount; ++i ) + pLight[i].Zero(); + + int subsampleCount = 0; + + FourVectors superSampleNormal; + superSampleNormal.DuplicateVector( sample.normal ); + + FourVectors superSampleLightCoord; + FourVectors superSamplePosition; + + if ( flags & NON_AMBIENT_ONLY ) + { + float aRow[4]; + for ( int coord = 0; coord < 4; ++coord ) + aRow[coord] = csshift + coord * cscale; + fltx4 sseRow = LoadUnalignedSIMD( aRow ); + + for (int s = 0; s < 4; ++s) + { + // make sure the coordinate is inside of the sample's winding and when normalizing + // below use the number of samples used, not just numsamples and some of them + // will be skipped if they are not inside of the winding + superSampleLightCoord.DuplicateVector( sampleLightOrigin ); + superSampleLightCoord.x = AddSIMD( superSampleLightCoord.x, ReplicateX4( aRow[s] ) ); + superSampleLightCoord.y = AddSIMD( superSampleLightCoord.y, sseRow ); + + // Figure out where the supersample exists in the world, and make sure + // it lies within the sample winding + LuxelSpaceToWorld( &l, superSampleLightCoord[0], superSampleLightCoord[1], superSamplePosition ); + + // A winding should exist only if the sample wasn't a uniform luxel, or if g_bDumpPatches is true. + int invalidBits = 0; + if ( sample.w && !PointsInWinding( superSamplePosition, sample.w, invalidBits ) ) + continue; + + // Compute the super-sample illumination point and normal + // We're assuming the flat normal is the same for all supersamples + ComputeIlluminationPointAndNormalsSSE( l, superSamplePosition, superSampleNormal, &info, 4 ); + + // Resample the non-ambient light at this point... + LightingValue_t result[4][NUM_BUMP_VECTS+1]; + ResampleLightAt4Points( info, lightStyleIndex, NON_AMBIENT_ONLY, result ); + + // Got more subsamples + for ( int i = 0; i < 4; i++ ) + { + if ( !( ( invalidBits >> i ) & 0x1 ) ) + { + for ( int n = 0; n < info.m_NormalCount; ++n ) + { + pLight[n].AddLight( result[i][n] ); + } + ++subsampleCount; + } + } + } + } + else + { + FourVectors superSampleOffsets; + superSampleOffsets.LoadAndSwizzle( Vector( csshift, csshift, 0 ), Vector( csshift, csshift + cscale, 0), + Vector( csshift + cscale, csshift, 0 ), Vector( csshift + cscale, csshift + cscale, 0 ) ); + superSampleLightCoord.DuplicateVector( sampleLightOrigin ); + superSampleLightCoord += superSampleOffsets; + + LuxelSpaceToWorld( &l, superSampleLightCoord[0], superSampleLightCoord[1], superSamplePosition ); + + int invalidBits = 0; + if ( sample.w && !PointsInWinding( superSamplePosition, sample.w, invalidBits ) ) + return 0; + + ComputeIlluminationPointAndNormalsSSE( l, superSamplePosition, superSampleNormal, &info, 4 ); + + LightingValue_t result[4][NUM_BUMP_VECTS+1]; + ResampleLightAt4Points( info, lightStyleIndex, AMBIENT_ONLY, result ); + + // Got more subsamples + for ( int i = 0; i < 4; i++ ) + { + if ( !( ( invalidBits >> i ) & 0x1 ) ) + { + for ( int n = 0; n < info.m_NormalCount; ++n ) + { + pLight[n].AddLight( result[i][n] ); + } + ++subsampleCount; + } + } + } + + return subsampleCount; +} + + +//----------------------------------------------------------------------------- +// Compute gradients of a lightmap +//----------------------------------------------------------------------------- +static void ComputeLightmapGradients( SSE_SampleInfo_t& info, bool const* pHasProcessedSample, + float* pIntensity, float* gradient ) +{ + int w = info.m_LightmapWidth; + int h = info.m_LightmapHeight; + facelight_t* fl = info.m_pFaceLight; + + for (int i=0 ; inumsamples ; i++) + { + // Don't supersample the same sample twice + if (pHasProcessedSample[i]) + continue; + + gradient[i] = 0.0f; + sample_t& sample = fl->sample[i]; + + // Choose the maximum gradient of all bumped lightmap intensities + for ( int n = 0; n < info.m_NormalCount; ++n ) + { + int j = n * info.m_LightmapSize + sample.s + sample.t * w; + + if (sample.t > 0) + { + if (sample.s > 0) gradient[i] = max( gradient[i], fabs( pIntensity[j] - pIntensity[j-1-w] ) ); + gradient[i] = max( gradient[i], fabs( pIntensity[j] - pIntensity[j-w] ) ); + if (sample.s < w-1) gradient[i] = max( gradient[i], fabs( pIntensity[j] - pIntensity[j+1-w] ) ); + } + if (sample.t < h-1) + { + if (sample.s > 0) gradient[i] = max( gradient[i], fabs( pIntensity[j] - pIntensity[j-1+w] ) ); + gradient[i] = max( gradient[i], fabs( pIntensity[j] - pIntensity[j+w] ) ); + if (sample.s < w-1) gradient[i] = max( gradient[i], fabs( pIntensity[j] - pIntensity[j+1+w] ) ); + } + if (sample.s > 0) gradient[i] = max( gradient[i], fabs( pIntensity[j] - pIntensity[j-1] ) ); + if (sample.s < w-1) gradient[i] = max( gradient[i], fabs( pIntensity[j] - pIntensity[j+1] ) ); + } + } +} + +//----------------------------------------------------------------------------- +// ComputeLuxelIntensity... +//----------------------------------------------------------------------------- +static inline void ComputeLuxelIntensity( SSE_SampleInfo_t& info, int sampleIdx, + LightingValue_t **ppLightSamples, float* pSampleIntensity ) +{ + // Compute a separate intensity for each + sample_t& sample = info.m_pFaceLight->sample[sampleIdx]; + int destIdx = sample.s + sample.t * info.m_LightmapWidth; + for (int n = 0; n < info.m_NormalCount; ++n) + { + float intensity = ppLightSamples[n][sampleIdx].Intensity(); + + // convert to a linear perception space + pSampleIntensity[n * info.m_LightmapSize + destIdx] = pow( intensity / 256.0, 1.0 / 2.2 ); + } +} + +//----------------------------------------------------------------------------- +// Compute the maximum intensity based on all bumped lighting +//----------------------------------------------------------------------------- +static void ComputeSampleIntensities( SSE_SampleInfo_t& info, LightingValue_t **ppLightSamples, float* pSampleIntensity ) +{ + for (int i=0; inumsamples; i++) + { + ComputeLuxelIntensity( info, i, ppLightSamples, pSampleIntensity ); + } +} + +//----------------------------------------------------------------------------- +// Perform supersampling on a particular lightstyle +//----------------------------------------------------------------------------- +static void BuildSupersampleFaceLights( lightinfo_t& l, SSE_SampleInfo_t& info, int lightstyleIndex ) +{ + LightingValue_t pAmbientLight[NUM_BUMP_VECTS+1]; + LightingValue_t pDirectLight[NUM_BUMP_VECTS+1]; + + // This is used to make sure we don't supersample a light sample more than once + int processedSampleSize = info.m_LightmapSize * sizeof(bool); + bool* pHasProcessedSample = (bool*)stackalloc( processedSampleSize ); + memset( pHasProcessedSample, 0, processedSampleSize ); + + // This is used to compute a simple gradient computation of the light samples + // We're going to store the maximum intensity of all bumped samples at each sample location + float* pGradient = (float*)stackalloc( info.m_pFaceLight->numsamples * sizeof(float) ); + float* pSampleIntensity = (float*)stackalloc( info.m_NormalCount * info.m_LightmapSize * sizeof(float) ); + + // Compute the maximum intensity of all lighting associated with this lightstyle + // for all bumped lighting + LightingValue_t **ppLightSamples = info.m_pFaceLight->light[lightstyleIndex]; + ComputeSampleIntensities( info, ppLightSamples, pSampleIntensity ); + + Vector *pVisualizePass = NULL; + if (debug_extra) + { + int visualizationSize = info.m_pFaceLight->numsamples * sizeof(Vector); + pVisualizePass = (Vector*)stackalloc( visualizationSize ); + memset( pVisualizePass, 0, visualizationSize ); + } + + // What's going on here is that we're looking for large lighting discontinuities + // (large light intensity gradients) as a clue that we should probably be supersampling + // in that area. Because the supersampling operation will cause lighting changes, + // we've found that it's good to re-check the gradients again and see if any other + // areas should be supersampled as a result of the previous pass. Keep going + // until all the gradients are reasonable or until we hit a max number of passes + bool do_anotherpass = true; + int pass = 1; + while (do_anotherpass && pass <= extrapasses) + { + // Look for lighting discontinuities to see what we should be supersampling + ComputeLightmapGradients( info, pHasProcessedSample, pSampleIntensity, pGradient ); + + do_anotherpass = false; + + // Now check all of the samples and supersample those which we have + // marked as having high gradients + for (int i=0 ; inumsamples; ++i) + { + // Don't supersample the same sample twice + if (pHasProcessedSample[i]) + continue; + + // Don't supersample if the lighting is pretty uniform near the sample + if (pGradient[i] < 0.0625) + continue; + + // Joy! We're supersampling now, and we therefore must do another pass + // Also, we need never bother with this sample again + pHasProcessedSample[i] = true; + do_anotherpass = true; + + if (debug_extra) + { + // Mark the little visualization bitmap with a color indicating + // which pass it was updated on. + pVisualizePass[i][0] = (pass & 1) * 255; + pVisualizePass[i][1] = (pass & 2) * 128; + pVisualizePass[i][2] = (pass & 4) * 64; + } + + // Supersample the ambient light for each bump direction vector + int ambientSupersampleCount = SupersampleLightAtPoint( l, info, i, lightstyleIndex, pAmbientLight, AMBIENT_ONLY ); + + // Supersample the non-ambient light for each bump direction vector + int directSupersampleCount = SupersampleLightAtPoint( l, info, i, lightstyleIndex, pDirectLight, NON_AMBIENT_ONLY ); + + // Because of sampling problems, small area triangles may have no samples. + // In this case, just use what we already have + if ( ambientSupersampleCount > 0 && directSupersampleCount > 0 ) + { + // Add the ambient + directional terms together, stick it back into the lightmap + for (int n = 0; n < info.m_NormalCount; ++n) + { + ppLightSamples[n][i].Zero(); + ppLightSamples[n][i].AddWeighted( pDirectLight[n],1.0f / directSupersampleCount ); + ppLightSamples[n][i].AddWeighted( pAmbientLight[n], 1.0f / ambientSupersampleCount ); + } + + // Recompute the luxel intensity based on the supersampling + ComputeLuxelIntensity( info, i, ppLightSamples, pSampleIntensity ); + } + + } + + // We've finished another pass + pass++; + } + + if (debug_extra) + { + // Copy colors representing which supersample pass the sample was messed with + // into the actual lighting values so we can visualize it + for (int i=0 ; inumsamples ; ++i) + { + for (int j = 0; j facenum = facenum; + + pl->face = f; + + // + // rotate plane + // + VectorCopy (dplanes[f->planenum].normal, pl->facenormal); + pl->facedist = dplanes[f->planenum].dist; + + // get the origin offset for rotating bmodels + VectorCopy (face_offset[facenum], pl->modelorg); + + CalcFaceVectors( pl ); + + // figure out if the surface is flat + pl->isflat = true; + if (smoothing_threshold != 1) + { + faceneighbor_t *fn = &faceneighbor[facenum]; + + for (int j=0 ; jnumedges ; j++) + { + float dot = DotProduct( pl->facenormal, fn->normal[j] ); + if (dot < 1.0 - EQUAL_EPSILON) + { + pl->isflat = false; + break; + } + } + } +} + +static void InitSampleInfo( lightinfo_t const& l, int iThread, SSE_SampleInfo_t& info ) +{ + info.m_LightmapWidth = l.face->m_LightmapTextureSizeInLuxels[0]+1; + info.m_LightmapHeight = l.face->m_LightmapTextureSizeInLuxels[1]+1; + info.m_LightmapSize = info.m_LightmapWidth * info.m_LightmapHeight; + + // How many lightmaps are we going to need? + info.m_pTexInfo = &texinfo[l.face->texinfo]; + info.m_NormalCount = (info.m_pTexInfo->flags & SURF_BUMPLIGHT) ? NUM_BUMP_VECTS + 1 : 1; + info.m_FaceNum = l.facenum; + info.m_pFace = l.face; + info.m_pFaceLight = &facelight[info.m_FaceNum]; + info.m_IsDispFace = ValidDispFace( info.m_pFace ); + info.m_iThread = iThread; + info.m_WarnFace = -1; + + info.m_NumSamples = info.m_pFaceLight->numsamples; + info.m_NumSampleGroups = ( info.m_NumSamples & 0x3) ? ( info.m_NumSamples / 4 ) + 1 : ( info.m_NumSamples / 4 ); + + // initialize normals if the surface is flat + if (l.isflat) + { + info.m_PointNormals[0].DuplicateVector( l.facenormal ); + + // use facenormal along with the smooth normal to build the three bump map vectors + if( info.m_NormalCount > 1 ) + { + Vector bumpVects[NUM_BUMP_VECTS]; + GetBumpNormals( info.m_pTexInfo->textureVecsTexelsPerWorldUnits[0], + info.m_pTexInfo->textureVecsTexelsPerWorldUnits[1], l.facenormal, + l.facenormal, bumpVects );//&info.m_PointNormal[1] ); + + for ( int b = 0; b < NUM_BUMP_VECTS; ++b ) + { + info.m_PointNormals[b + 1].DuplicateVector( bumpVects[b] ); + } + } + } +} + +void BuildFacelights (int iThread, int facenum) +{ + int i, j; + + lightinfo_t l; + dface_t *f; + facelight_t *fl; + SSE_SampleInfo_t sampleInfo; + directlight_t *dl; + Vector spot; + Vector v[4], n[4]; + + if( g_bInterrupt ) + return; + + // FIXME: Is there a better way to do this? Like, in RunThreadsOn, for instance? + // Don't pay this cost unless we have to; this is super perf-critical code. + if (g_pIncremental) + { + // Both threads will be accessing this so it needs to be protected or else thread A + // will load it in and thread B will increment it but its increment will be + // overwritten by thread A when thread A writes it back. + ThreadLock(); + ++g_iCurFace; + ThreadUnlock(); + } + + // some surfaces don't need lightmaps + f = &g_pFaces[facenum]; + f->lightofs = -1; + for (j=0 ; jstyles[j] = 255; + + // Trivial-reject the whole face? + if( !( g_FacesVisibleToLights[facenum>>3] & (1 << (facenum & 7)) ) ) + return; + + if ( texinfo[f->texinfo].flags & TEX_SPECIAL) + return; // non-lit texture + + // check for patches for this face. If none it must be degenerate. Ignore. + if( g_FacePatches.Element( facenum ) == g_FacePatches.InvalidIndex() ) + return; + + fl = &facelight[facenum]; + + InitLightinfo( &l, facenum ); + CalcPoints( &l, fl, facenum ); + InitSampleInfo( l, iThread, sampleInfo ); + + // Allocate sample positions/normals to SSE + int numGroups = ( fl->numsamples & 0x3) ? ( fl->numsamples / 4 ) + 1 : ( fl->numsamples / 4 ); + + // always allocate style 0 lightmap + f->styles[0] = 0; + AllocateLightstyleSamples( fl, 0, sampleInfo.m_NormalCount ); + + // sample the lights at each sample location + for ( int grp = 0; grp < numGroups; ++grp ) + { + int nSample = 4 * grp; + + sample_t *sample = sampleInfo.m_pFaceLight->sample + nSample; + int numSamples = min ( 4, sampleInfo.m_pFaceLight->numsamples - nSample ); + + FourVectors positions; + FourVectors normals; + + for ( int i = 0; i < 4; i++ ) + { + v[i] = ( i < numSamples ) ? sample[i].pos : sample[numSamples - 1].pos; + n[i] = ( i < numSamples ) ? sample[i].normal : sample[numSamples - 1].normal; + } + positions.LoadAndSwizzle( v[0], v[1], v[2], v[3] ); + normals.LoadAndSwizzle( n[0], n[1], n[2], n[3] ); + + ComputeIlluminationPointAndNormalsSSE( l, positions, normals, &sampleInfo, numSamples ); + + // Fixup sample normals in case of smooth faces + if ( !l.isflat ) + { + for ( int i = 0; i < numSamples; i++ ) + sample[i].normal = sampleInfo.m_PointNormals[0].Vec( i ); + } + + // Iterate over all the lights and add their contribution to this group of spots + GatherSampleLightAt4Points( sampleInfo, nSample, numSamples ); + } + + // Tell the incremental light manager that we're done with this face. + if( g_pIncremental ) + { + for (dl = activelights; dl != NULL; dl = dl->next) + { + // Only deal with lightstyle 0 for incremental lighting + if (dl->light.style == 0) + g_pIncremental->FinishFace( dl->m_IncrementalID, facenum, iThread ); + } + + // Don't have to deal with patch lights (only direct lighting is used) + // or supersampling + return; + } + + // get rid of the -extra functionality on displacement surfaces + if (do_extra && !sampleInfo.m_IsDispFace) + { + // For each lightstyle, perform a supersampling pass + for ( i = 0; i < MAXLIGHTMAPS; ++i ) + { + // Stop when we run out of lightstyles + if (f->styles[i] == 255) + break; + + BuildSupersampleFaceLights( l, sampleInfo, i ); + } + } + + if (!g_bUseMPI) + { + // + // This is done on the master node when MPI is used + // + BuildPatchLights( facenum ); + } + + if( g_bDumpPatches ) + { + DumpSamples( facenum, fl ); + } + else + { + FreeSampleWindings( fl ); + } + +} + +void BuildPatchLights( int facenum ) +{ + int i, k; + + CPatch *patch; + + dface_t *f = &g_pFaces[facenum]; + facelight_t *fl = &facelight[facenum]; + + for( k = 0; k < MAXLIGHTMAPS; k++ ) + { + if (f->styles[k] == 0) + break; + } + + if (k >= MAXLIGHTMAPS) + return; + + for (i = 0; i < fl->numsamples; i++) + { + AddSampleToPatch( &fl->sample[i], fl->light[k][0][i], facenum); + } + + // check for a valid face + if( g_FacePatches.Element( facenum ) == g_FacePatches.InvalidIndex() ) + return; + + // push up sampled light to parents (children always exist first in the list) + CPatch *pNextPatch; + for( patch = &g_Patches.Element( g_FacePatches.Element( facenum ) ); patch; patch = pNextPatch ) + { + // next patch + pNextPatch = NULL; + if( patch->ndxNext != g_Patches.InvalidIndex() ) + { + pNextPatch = &g_Patches.Element( patch->ndxNext ); + } + + // skip patches without parents + if( patch->parent == g_Patches.InvalidIndex() ) +// if (patch->parent == -1) + continue; + + CPatch *parent = &g_Patches.Element( patch->parent ); + + parent->samplearea += patch->samplearea; + VectorAdd( parent->samplelight, patch->samplelight, parent->samplelight ); + } + + // average up the direct light on each patch for radiosity + if (numbounce > 0) + { + for( patch = &g_Patches.Element( g_FacePatches.Element( facenum ) ); patch; patch = pNextPatch ) + { + // next patch + pNextPatch = NULL; + if( patch->ndxNext != g_Patches.InvalidIndex() ) + { + pNextPatch = &g_Patches.Element( patch->ndxNext ); + } + + if (patch->samplearea) + { + float scale; + Vector v; + scale = 1.0 / patch->samplearea; + + VectorScale( patch->samplelight, scale, v ); + VectorAdd( patch->totallight.light[0], v, patch->totallight.light[0] ); + VectorAdd( patch->directlight, v, patch->directlight ); + } + } + } + + // pull totallight from children (children always exist first in the list) + for( patch = &g_Patches.Element( g_FacePatches.Element( facenum ) ); patch; patch = pNextPatch ) + { + // next patch + pNextPatch = NULL; + if( patch->ndxNext != g_Patches.InvalidIndex() ) + { + pNextPatch = &g_Patches.Element( patch->ndxNext ); + } + + if ( patch->child1 != g_Patches.InvalidIndex() ) + { + float s1, s2; + CPatch *child1; + CPatch *child2; + + child1 = &g_Patches.Element( patch->child1 ); + child2 = &g_Patches.Element( patch->child2 ); + + s1 = child1->area / (child1->area + child2->area); + s2 = child2->area / (child1->area + child2->area); + + VectorScale( child1->totallight.light[0], s1, patch->totallight.light[0] ); + VectorMA( patch->totallight.light[0], s2, child2->totallight.light[0], patch->totallight.light[0] ); + + VectorCopy( patch->totallight.light[0], patch->directlight ); + } + } + + bool needsBumpmap = false; + if( texinfo[f->texinfo].flags & SURF_BUMPLIGHT ) + { + needsBumpmap = true; + } + + // add an ambient term if desired + if (ambient[0] || ambient[1] || ambient[2]) + { + for( int j=0; j < MAXLIGHTMAPS && f->styles[j] != 255; j++ ) + { + if ( f->styles[j] == 0 ) + { + for (i = 0; i < fl->numsamples; i++) + { + fl->light[j][0][i].m_vecLighting += ambient; + if( needsBumpmap ) + { + fl->light[j][1][i].m_vecLighting += ambient; + fl->light[j][2][i].m_vecLighting += ambient; + fl->light[j][3][i].m_vecLighting += ambient; + } + } + break; + } + } + } + + // light from dlight_threshold and above is sent out, but the + // texture itself should still be full bright + +#if 0 + // if( VectorAvg( g_FacePatches[facenum]->baselight ) >= dlight_threshold) // Now all lighted surfaces glow + { + for( j=0; j < MAXLIGHTMAPS && f->styles[j] != 255; j++ ) + { + if ( f->styles[j] == 0 ) + { + // BUG: shouldn't this be done for all patches on the face? + for (i=0 ; inumsamples ; i++) + { + // garymctchange + VectorAdd( fl->light[j][0][i], g_FacePatches[facenum]->baselight, fl->light[j][0][i] ); + if( needsBumpmap ) + { + for( bumpSample = 1; bumpSample < NUM_BUMP_VECTS + 1; bumpSample++ ) + { + VectorAdd( fl->light[j][bumpSample][i], g_FacePatches[facenum]->baselight, fl->light[j][bumpSample][i] ); + } + } + } + break; + } + } + } +#endif +} + + +/* + ============= + PrecompLightmapOffsets + ============= +*/ + +void PrecompLightmapOffsets() +{ + int facenum; + dface_t *f; + int lightstyles; + int lightdatasize = 0; + + // NOTE: We store avg face light data in this lump *before* the lightmap data itself + // in *reverse order* of the way the lightstyles appear in the styles array. + for( facenum = 0; facenum < numfaces; facenum++ ) + { + f = &g_pFaces[facenum]; + + if ( texinfo[f->texinfo].flags & TEX_SPECIAL) + continue; // non-lit texture + + if ( dlight_map != 0 ) + f->styles[1] = 0; + + for (lightstyles=0; lightstyles < MAXLIGHTMAPS; lightstyles++ ) + { + if ( f->styles[lightstyles] == 255 ) + break; + } + + if ( !lightstyles ) + continue; + + // Reserve room for the avg light color data + lightdatasize += lightstyles * 4; + + f->lightofs = lightdatasize; + + bool needsBumpmap = false; + if( texinfo[f->texinfo].flags & SURF_BUMPLIGHT ) + { + needsBumpmap = true; + } + + int nLuxels = (f->m_LightmapTextureSizeInLuxels[0]+1) * (f->m_LightmapTextureSizeInLuxels[1]+1); + if( needsBumpmap ) + { + lightdatasize += nLuxels * 4 * lightstyles * ( NUM_BUMP_VECTS + 1 ); + } + else + { + lightdatasize += nLuxels * 4 * lightstyles; + } + } + + // The incremental lighting code needs us to preserve the contents of dlightdata + // since it only recomposites lighting for faces that have lights that touch them. + if( g_pIncremental && pdlightdata->Count() ) + return; + + pdlightdata->SetSize( lightdatasize ); +} + +// Clamp the three values for bumped lighting such that we trade off directionality for brightness. +static void ColorClampBumped( Vector& color1, Vector& color2, Vector& color3 ) +{ + Vector maxs; + Vector *colors[3] = { &color1, &color2, &color3 }; + maxs[0] = VectorMaximum( color1 ); + maxs[1] = VectorMaximum( color2 ); + maxs[2] = VectorMaximum( color3 ); + + // HACK! Clean this up, and add some else statements +#define CONDITION(a,b,c) do { if( maxs[a] >= maxs[b] && maxs[b] >= maxs[c] ) { order[0] = a; order[1] = b; order[2] = c; } } while( 0 ) + + int order[3]; + CONDITION(0,1,2); + CONDITION(0,2,1); + CONDITION(1,0,2); + CONDITION(1,2,0); + CONDITION(2,0,1); + CONDITION(2,1,0); + + int i; + for( i = 0; i < 3; i++ ) + { + float max = VectorMaximum( *colors[order[i]] ); + if( max <= 1.0f ) + { + continue; + } + // This channel is too bright. . take half of the amount that we are over and + // add it to the other two channel. + float factorToRedist = ( max - 1.0f ) / max; + Vector colorToRedist = factorToRedist * *colors[order[i]]; + *colors[order[i]] -= colorToRedist; + colorToRedist *= 0.5f; + *colors[order[(i+1)%3]] += colorToRedist; + *colors[order[(i+2)%3]] += colorToRedist; + } + + ColorClamp( color1 ); + ColorClamp( color2 ); + ColorClamp( color3 ); + + if( color1[0] < 0.f ) color1[0] = 0.f; + if( color1[1] < 0.f ) color1[1] = 0.f; + if( color1[2] < 0.f ) color1[2] = 0.f; + if( color2[0] < 0.f ) color2[0] = 0.f; + if( color2[1] < 0.f ) color2[1] = 0.f; + if( color2[2] < 0.f ) color2[2] = 0.f; + if( color3[0] < 0.f ) color3[0] = 0.f; + if( color3[1] < 0.f ) color3[1] = 0.f; + if( color3[2] < 0.f ) color3[2] = 0.f; +} + +static void LinearToBumpedLightmap( + const float *linearColor, + const float *linearBumpColor1, + const float *linearBumpColor2, + const float *linearBumpColor3, + unsigned char *ret, + unsigned char *retBump1, + unsigned char *retBump2, + unsigned char *retBump3 ) +{ + const Vector &linearBump1 = *( ( const Vector * )linearBumpColor1 ); + const Vector &linearBump2 = *( ( const Vector * )linearBumpColor2 ); + const Vector &linearBump3 = *( ( const Vector * )linearBumpColor3 ); + + Vector gammaGoal; + // gammaGoal is premultiplied by 1/overbright, which we want + gammaGoal[0] = LinearToVertexLight( linearColor[0] ); + gammaGoal[1] = LinearToVertexLight( linearColor[1] ); + gammaGoal[2] = LinearToVertexLight( linearColor[2] ); + Vector bumpAverage = linearBump1; + bumpAverage += linearBump2; + bumpAverage += linearBump3; + bumpAverage *= ( 1.0f / 3.0f ); + + Vector correctionScale; + if( *( int * )&bumpAverage[0] != 0 && *( int * )&bumpAverage[1] != 0 && *( int * )&bumpAverage[2] != 0 ) + { + // fast path when we know that we don't have to worry about divide by zero. + VectorDivide( gammaGoal, bumpAverage, correctionScale ); +// correctionScale = gammaGoal / bumpSum; + } + else + { + correctionScale.Init( 0.0f, 0.0f, 0.0f ); + if( bumpAverage[0] != 0.0f ) + { + correctionScale[0] = gammaGoal[0] / bumpAverage[0]; + } + if( bumpAverage[1] != 0.0f ) + { + correctionScale[1] = gammaGoal[1] / bumpAverage[1]; + } + if( bumpAverage[2] != 0.0f ) + { + correctionScale[2] = gammaGoal[2] / bumpAverage[2]; + } + } + Vector correctedBumpColor1; + Vector correctedBumpColor2; + Vector correctedBumpColor3; + VectorMultiply( linearBump1, correctionScale, correctedBumpColor1 ); + VectorMultiply( linearBump2, correctionScale, correctedBumpColor2 ); + VectorMultiply( linearBump3, correctionScale, correctedBumpColor3 ); + + Vector check = ( correctedBumpColor1 + correctedBumpColor2 + correctedBumpColor3 ) / 3.0f; + + ColorClampBumped( correctedBumpColor1, correctedBumpColor2, correctedBumpColor3 ); + + ret[0] = RoundFloatToByte( gammaGoal[0] * 255.0f ); + ret[1] = RoundFloatToByte( gammaGoal[1] * 255.0f ); + ret[2] = RoundFloatToByte( gammaGoal[2] * 255.0f ); + retBump1[0] = RoundFloatToByte( correctedBumpColor1[0] * 255.0f ); + retBump1[1] = RoundFloatToByte( correctedBumpColor1[1] * 255.0f ); + retBump1[2] = RoundFloatToByte( correctedBumpColor1[2] * 255.0f ); + retBump2[0] = RoundFloatToByte( correctedBumpColor2[0] * 255.0f ); + retBump2[1] = RoundFloatToByte( correctedBumpColor2[1] * 255.0f ); + retBump2[2] = RoundFloatToByte( correctedBumpColor2[2] * 255.0f ); + retBump3[0] = RoundFloatToByte( correctedBumpColor3[0] * 255.0f ); + retBump3[1] = RoundFloatToByte( correctedBumpColor3[1] * 255.0f ); + retBump3[2] = RoundFloatToByte( correctedBumpColor3[2] * 255.0f ); +} + +//----------------------------------------------------------------------------- +// Convert a RGBExp32 to a RGBA8888 +// This matches the engine's conversion, so the lighting result is consistent. +//----------------------------------------------------------------------------- +void ConvertRGBExp32ToRGBA8888( const ColorRGBExp32 *pSrc, unsigned char *pDst ) +{ + Vector linearColor; + Vector vertexColor; + + // convert from ColorRGBExp32 to linear space + linearColor[0] = TexLightToLinear( ((ColorRGBExp32 *)pSrc)->r, ((ColorRGBExp32 *)pSrc)->exponent ); + linearColor[1] = TexLightToLinear( ((ColorRGBExp32 *)pSrc)->g, ((ColorRGBExp32 *)pSrc)->exponent ); + linearColor[2] = TexLightToLinear( ((ColorRGBExp32 *)pSrc)->b, ((ColorRGBExp32 *)pSrc)->exponent ); + + // convert from linear space to lightmap space + // cannot use mathlib routine directly because it doesn't match + // the colorspace version found in the engine, which *is* the same sequence here + vertexColor[0] = LinearToVertexLight( linearColor[0] ); + vertexColor[1] = LinearToVertexLight( linearColor[1] ); + vertexColor[2] = LinearToVertexLight( linearColor[2] ); + + // this is really a color normalization with a floor + ColorClamp( vertexColor ); + + // final [0..255] scale + pDst[0] = RoundFloatToByte( vertexColor[0] * 255.0f ); + pDst[1] = RoundFloatToByte( vertexColor[1] * 255.0f ); + pDst[2] = RoundFloatToByte( vertexColor[2] * 255.0f ); + pDst[3] = 255; +} + diff --git a/mp/src/utils/vrad/lightmap.h b/mp/src/utils/vrad/lightmap.h new file mode 100644 index 00000000..0912c43f --- /dev/null +++ b/mp/src/utils/vrad/lightmap.h @@ -0,0 +1,141 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef LIGHTMAP_H +#define LIGHTMAP_H +#pragma once + +#include "mathlib/bumpvects.h" +#include "bsplib.h" + +typedef struct +{ + dface_t *faces[2]; + Vector interface_normal; + qboolean coplanar; +} edgeshare_t; + +extern edgeshare_t edgeshare[MAX_MAP_EDGES]; + + +//============================================== + +// This is incremented each time BuildFaceLights and FinalLightFace +// are called. It's used for a status bar in WorldCraft. +extern int g_iCurFace; + +extern int vertexref[MAX_MAP_VERTS]; +extern int *vertexface[MAX_MAP_VERTS]; + +struct faceneighbor_t +{ + int numneighbors; // neighboring faces that share vertices + int *neighbor; // neighboring face list (max of 64) + + Vector *normal; // adjusted normal per vertex + Vector facenormal; // face normal + + bool bHasDisp; // is this surface a displacement surface??? +}; + +extern faceneighbor_t faceneighbor[MAX_MAP_FACES]; + +//============================================== + + +struct sample_t +{ + // in local luxel space + winding_t *w; + int s, t; + Vector2D coord; + Vector2D mins; + Vector2D maxs; + // in world units + Vector pos; + Vector normal; + float area; +}; + +struct facelight_t +{ + // irregularly shaped light sample data, clipped by face and luxel grid + int numsamples; + sample_t *sample; + LightingValue_t *light[MAXLIGHTMAPS][NUM_BUMP_VECTS+1]; // result of direct illumination, indexed by sample + + // regularly spaced lightmap grid + int numluxels; + Vector *luxel; // world space position of luxel + Vector *luxelNormals; // world space normal of luxel + float worldAreaPerLuxel; +}; + +extern directlight_t *activelights; +extern directlight_t *freelights; + +extern facelight_t facelight[MAX_MAP_FACES]; +extern int numdlights; + + +//============================================== + +struct lightinfo_t +{ + vec_t facedist; + Vector facenormal; + + Vector facemid; // world coordinates of center + + Vector modelorg; // for origined bmodels + + Vector luxelOrigin; + Vector worldToLuxelSpace[2]; // s = (world - luxelOrigin) . worldToLuxelSpace[0], t = (world - luxelOrigin) . worldToLuxelSpace[1] + Vector luxelToWorldSpace[2]; // world = luxelOrigin + s * luxelToWorldSpace[0] + t * luxelToWorldSpace[1] + + int facenum; + dface_t *face; + + int isflat; + int hasbumpmap; +}; + +struct SSE_SampleInfo_t +{ + int m_FaceNum; + int m_WarnFace; + dface_t *m_pFace; + facelight_t *m_pFaceLight; + int m_LightmapWidth; + int m_LightmapHeight; + int m_LightmapSize; + int m_NormalCount; + int m_iThread; + texinfo_t *m_pTexInfo; + bool m_IsDispFace; + + int m_NumSamples; + int m_NumSampleGroups; + int m_Clusters[4]; + FourVectors m_Points; + FourVectors m_PointNormals[ NUM_BUMP_VECTS + 1 ]; +}; + +extern void InitLightinfo( lightinfo_t *l, int facenum ); + +void FreeDLights(); + +void ExportDirectLightsToWorldLights(); + + +#endif // LIGHTMAP_H diff --git a/mp/src/utils/vrad/macro_texture.cpp b/mp/src/utils/vrad/macro_texture.cpp new file mode 100644 index 00000000..8511d979 --- /dev/null +++ b/mp/src/utils/vrad/macro_texture.cpp @@ -0,0 +1,166 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "tier1/strtools.h" +#include "macro_texture.h" +#include "bsplib.h" +#include "cmdlib.h" +#include "vtf/vtf.h" +#include "tier1/utldict.h" +#include "tier1/utlbuffer.h" +#include "bitmap/imageformat.h" + + +class CMacroTextureData +{ +public: + int m_Width, m_Height; + CUtlMemory m_ImageData; +}; + + +CMacroTextureData *g_pGlobalMacroTextureData = NULL; + +// Which macro texture each map face uses. +static CUtlDict g_MacroTextureLookup; // Stores a list of unique macro textures. +static CUtlVector g_FaceMacroTextures; // Which macro texture each face wants to use. +static Vector g_MacroWorldMins, g_MacroWorldMaxs; + + +CMacroTextureData* FindMacroTexture( const char *pFilename ) +{ + int index = g_MacroTextureLookup.Find( pFilename ); + if ( g_MacroTextureLookup.IsValidIndex( index ) ) + return g_MacroTextureLookup[index]; + else + return NULL; +} + + +CMacroTextureData* LoadMacroTextureFile( const char *pFilename ) +{ + FileHandle_t hFile = g_pFileSystem->Open( pFilename, "rb" ); + if ( hFile == FILESYSTEM_INVALID_HANDLE ) + return NULL; + + // Read the file in. + CUtlVector tempData; + tempData.SetSize( g_pFileSystem->Size( hFile ) ); + g_pFileSystem->Read( tempData.Base(), tempData.Count(), hFile ); + g_pFileSystem->Close( hFile ); + + + // Now feed the data into a CUtlBuffer (great...) + CUtlBuffer buf; + buf.Put( tempData.Base(), tempData.Count() ); + + + // Now make a texture out of it. + IVTFTexture *pTex = CreateVTFTexture(); + if ( !pTex->Unserialize( buf ) ) + Error( "IVTFTexture::Unserialize( %s ) failed.", pFilename ); + + pTex->ConvertImageFormat( IMAGE_FORMAT_RGBA8888, false ); // Get it in a format we like. + + + // Now convert to a CMacroTextureData. + CMacroTextureData *pData = new CMacroTextureData; + pData->m_Width = pTex->Width(); + pData->m_Height = pTex->Height(); + pData->m_ImageData.EnsureCapacity( pData->m_Width * pData->m_Height * 4 ); + memcpy( pData->m_ImageData.Base(), pTex->ImageData(), pData->m_Width * pData->m_Height * 4 ); + + DestroyVTFTexture( pTex ); + + Msg( "-- LoadMacroTextureFile: %s\n", pFilename ); + return pData; +} + + +void InitMacroTexture( const char *pBSPFilename ) +{ + // Get the world bounds (same ones used by minimaps and level designers know how to use). + int i = 0; + for (i; i < num_entities; ++i) + { + char* pEntity = ValueForKey(&entities[i], "classname"); + if( !strcmp(pEntity, "worldspawn") ) + { + GetVectorForKey( &entities[i], "world_mins", g_MacroWorldMins ); + GetVectorForKey( &entities[i], "world_maxs", g_MacroWorldMaxs ); + break; + } + } + + if ( i == num_entities ) + { + Warning( "MaskOnMacroTexture: can't find worldspawn" ); + return; + } + + + // Load the macro texture that is mapped onto everything. + char mapName[512], vtfFilename[512]; + Q_FileBase( pBSPFilename, mapName, sizeof( mapName ) ); + Q_snprintf( vtfFilename, sizeof( vtfFilename ), "materials/macro/%s/base.vtf", mapName ); + g_pGlobalMacroTextureData = LoadMacroTextureFile( vtfFilename ); + + + // Now load the macro texture for each face. + g_FaceMacroTextures.SetSize( numfaces ); + for ( int iFace=0; iFace < numfaces; iFace++ ) + { + g_FaceMacroTextures[iFace] = NULL; + + if ( iFace < g_FaceMacroTextureInfos.Count() ) + { + unsigned short stringID = g_FaceMacroTextureInfos[iFace].m_MacroTextureNameID; + if ( stringID != 0xFFFF ) + { + const char *pMacroTextureName = &g_TexDataStringData[ g_TexDataStringTable[stringID] ]; + Q_snprintf( vtfFilename, sizeof( vtfFilename ), "%smaterials/%s.vtf", gamedir, pMacroTextureName ); + + g_FaceMacroTextures[iFace] = FindMacroTexture( vtfFilename ); + if ( !g_FaceMacroTextures[iFace] ) + { + g_FaceMacroTextures[iFace] = LoadMacroTextureFile( vtfFilename ); + if ( g_FaceMacroTextures[iFace] ) + { + g_MacroTextureLookup.Insert( vtfFilename, g_FaceMacroTextures[iFace] ); + } + } + } + } + } +} + + +inline Vector SampleMacroTexture( const CMacroTextureData *t, const Vector &vWorldPos ) +{ + int ix = (int)RemapVal( vWorldPos.x, g_MacroWorldMins.x, g_MacroWorldMaxs.x, 0, t->m_Width-0.00001 ); + int iy = (int)RemapVal( vWorldPos.y, g_MacroWorldMins.y, g_MacroWorldMaxs.y, 0, t->m_Height-0.00001 ); + ix = clamp( ix, 0, t->m_Width-1 ); + iy = t->m_Height - 1 - clamp( iy, 0, t->m_Height-1 ); + + const unsigned char *pInputColor = &t->m_ImageData[(iy*t->m_Width + ix) * 4]; + return Vector( pInputColor[0] / 255.0, pInputColor[1] / 255.0, pInputColor[2] / 255.0 ); +} + + +void ApplyMacroTextures( int iFace, const Vector &vWorldPos, Vector &outLuxel ) +{ + // Add the global macro texture. + Vector vGlobal; + if ( g_pGlobalMacroTextureData ) + outLuxel *= SampleMacroTexture( g_pGlobalMacroTextureData, vWorldPos ); + + // Now add the per-material macro texture. + if ( g_FaceMacroTextures[iFace] ) + outLuxel *= SampleMacroTexture( g_FaceMacroTextures[iFace], vWorldPos ); +} + + + diff --git a/mp/src/utils/vrad/macro_texture.h b/mp/src/utils/vrad/macro_texture.h new file mode 100644 index 00000000..249c2474 --- /dev/null +++ b/mp/src/utils/vrad/macro_texture.h @@ -0,0 +1,24 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef MACRO_TEXTURE_H +#define MACRO_TEXTURE_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "mathlib/vector.h" + + +// The macro texture looks for a TGA file with the same name as the BSP file and in +// the same directory. If it finds one, it maps this texture onto the world dimensions +// (in the worldspawn entity) and masks all lightmaps with it. +void InitMacroTexture( const char *pBSPFilename ); +void ApplyMacroTextures( int iFace, const Vector &vWorldPos, Vector &outLuxel ); + + +#endif // MACRO_TEXTURE_H diff --git a/mp/src/utils/vrad/mpivrad.cpp b/mp/src/utils/vrad/mpivrad.cpp new file mode 100644 index 00000000..d54dfaeb --- /dev/null +++ b/mp/src/utils/vrad/mpivrad.cpp @@ -0,0 +1,496 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// +// mpivrad.cpp +// + +#include +#include +#include "vrad.h" +#include "physdll.h" +#include "lightmap.h" +#include "tier1/strtools.h" +#include "radial.h" +#include "utlbuffer.h" +#include "pacifier.h" +#include "messbuf.h" +#include "bsplib.h" +#include "consolewnd.h" +#include "vismat.h" +#include "vmpi_filesystem.h" +#include "vmpi_dispatch.h" +#include "utllinkedlist.h" +#include "vmpi.h" +#include "mpi_stats.h" +#include "vmpi_distribute_work.h" +#include "vmpi_tools_shared.h" + + + + +CUtlVector g_LightResultsFilename; + + +extern int total_transfer; +extern int max_transfer; + +extern void BuildVisLeafs(int); +extern void BuildPatchLights( int facenum ); + + +// Handle VRAD packets. +bool VRAD_DispatchFn( MessageBuffer *pBuf, int iSource, int iPacketID ) +{ + switch( pBuf->data[1] ) + { + case VMPI_SUBPACKETID_PLIGHTDATA_RESULTS: + { + const char *pFilename = &pBuf->data[2]; + g_LightResultsFilename.CopyArray( pFilename, strlen( pFilename ) + 1 ); + return true; + } + + default: + return false; + } +} +CDispatchReg g_VRADDispatchReg( VMPI_VRAD_PACKET_ID, VRAD_DispatchFn ); // register to handle the messages we want +CDispatchReg g_DistributeWorkReg( VMPI_DISTRIBUTEWORK_PACKETID, DistributeWorkDispatch ); + + + +void VRAD_SetupMPI( int &argc, char **&argv ) +{ + CmdLib_AtCleanup( VMPI_Stats_Term ); + + // + // Preliminary check -mpi flag + // + if ( !VMPI_FindArg( argc, argv, "-mpi", "" ) && !VMPI_FindArg( argc, argv, VMPI_GetParamString( mpi_Worker ), "" ) ) + return; + + // Force local mode? + VMPIRunMode mode; + if ( VMPI_FindArg( argc, argv, VMPI_GetParamString( mpi_Local ), "" ) ) + mode = VMPI_RUN_LOCAL; + else + mode = VMPI_RUN_NETWORKED; + + VMPI_Stats_InstallSpewHook(); + + // + // Extract mpi specific arguments + // + Msg( "Initializing VMPI...\n" ); + if ( !VMPI_Init( + argc, + argv, + "dependency_info_vrad.txt", + HandleMPIDisconnect, + mode + ) ) + { + Error( "MPI_Init failed." ); + } + + StatsDB_InitStatsDatabase( argc, argv, "dbinfo_vrad.txt" ); +} + + +//----------------------------------------- +// +// Run BuildFaceLights across all available processing nodes +// and collect the results. +// + +CCycleCount g_CPUTime; + + +template void WriteValues( MessageBuffer *pmb, T const *pSrc, int nNumValues) +{ + pmb->write(pSrc, sizeof( pSrc[0]) * nNumValues ); +} + +template int ReadValues( MessageBuffer *pmb, T *pDest, int nNumValues) +{ + return pmb->read( pDest, sizeof( pDest[0]) * nNumValues ); +} + + +//-------------------------------------------------- +// Serialize face data +void SerializeFace( MessageBuffer * pmb, int facenum ) +{ + int i, n; + + dface_t * f = &g_pFaces[facenum]; + facelight_t * fl = &facelight[facenum]; + + pmb->write(f, sizeof(dface_t)); + pmb->write(fl, sizeof(facelight_t)); + + WriteValues( pmb, fl->sample, fl->numsamples); + + // + // Write the light information + // + for (i=0; ilight[i][n]) + { + WriteValues( pmb, fl->light[i][n], fl->numsamples); + } + } + } + + if (fl->luxel) + WriteValues( pmb, fl->luxel, fl->numluxels); + + if (fl->luxelNormals) + WriteValues( pmb, fl->luxelNormals, fl->numluxels); +} + +//-------------------------------------------------- +// UnSerialize face data +// +void UnSerializeFace( MessageBuffer * pmb, int facenum, int iSource ) +{ + int i, n; + + dface_t * f = &g_pFaces[facenum]; + facelight_t * fl = &facelight[facenum]; + + if (pmb->read(f, sizeof(dface_t)) < 0) + Error("UnSerializeFace - invalid dface_t from %s (mb len: %d, offset: %d)", VMPI_GetMachineName( iSource ), pmb->getLen(), pmb->getOffset() ); + + if (pmb->read(fl, sizeof(facelight_t)) < 0) + Error("UnSerializeFace - invalid facelight_t from %s (mb len: %d, offset: %d)", VMPI_GetMachineName( iSource ), pmb->getLen(), pmb->getOffset() ); + + fl->sample = (sample_t *) calloc(fl->numsamples, sizeof(sample_t)); + if (pmb->read(fl->sample, sizeof(sample_t) * fl->numsamples) < 0) + Error("UnSerializeFace - invalid sample_t from %s (mb len: %d, offset: %d, fl->numsamples: %d)", VMPI_GetMachineName( iSource ), pmb->getLen(), pmb->getOffset(), fl->numsamples ); + + // + // Read the light information + // + for (i=0; ilight[i][n]) + { + fl->light[i][n] = (LightingValue_t *) calloc( fl->numsamples, sizeof(LightingValue_t ) ); + if ( ReadValues( pmb, fl->light[i][n], fl->numsamples) < 0) + Error("UnSerializeFace - invalid fl->light from %s (mb len: %d, offset: %d)", VMPI_GetMachineName( iSource ), pmb->getLen(), pmb->getOffset() ); + } + } + } + + if (fl->luxel) { + fl->luxel = (Vector *) calloc(fl->numluxels, sizeof(Vector)); + if (ReadValues( pmb, fl->luxel, fl->numluxels) < 0) + Error("UnSerializeFace - invalid fl->luxel from %s (mb len: %d, offset: %d)", VMPI_GetMachineName( iSource ), pmb->getLen(), pmb->getOffset() ); + } + + if (fl->luxelNormals) { + fl->luxelNormals = (Vector *) calloc(fl->numluxels, sizeof( Vector )); + if ( ReadValues( pmb, fl->luxelNormals, fl->numluxels) < 0) + Error("UnSerializeFace - invalid fl->luxelNormals from %s (mb len: %d, offset: %d)", VMPI_GetMachineName( iSource ), pmb->getLen(), pmb->getOffset() ); + } + +} + + +void MPI_ReceiveFaceResults( uint64 iWorkUnit, MessageBuffer *pBuf, int iWorker ) +{ + UnSerializeFace( pBuf, iWorkUnit, iWorker ); +} + + +void MPI_ProcessFaces( int iThread, uint64 iWorkUnit, MessageBuffer *pBuf ) +{ + // Do BuildFacelights on the face. + CTimeAdder adder( &g_CPUTime ); + + BuildFacelights( iThread, iWorkUnit ); + + // Send the results. + if ( pBuf ) + { + SerializeFace( pBuf, iWorkUnit ); + } +} + + +void RunMPIBuildFacelights() +{ + g_CPUTime.Init(); + + Msg( "%-20s ", "BuildFaceLights:" ); + if ( g_bMPIMaster ) + { + StartPacifier(""); + } + + VMPI_SetCurrentStage( "RunMPIBuildFaceLights" ); + double elapsed = DistributeWork( + numfaces, + VMPI_DISTRIBUTEWORK_PACKETID, + MPI_ProcessFaces, + MPI_ReceiveFaceResults ); + + if ( g_bMPIMaster ) + { + EndPacifier(false); + Msg( " (%d)\n", (int)elapsed ); + } + + if ( g_bMPIMaster ) + { + // + // BuildPatchLights is normally called from BuildFacelights(), + // but in MPI mode we have the master do the calculation + // We might be able to speed this up by doing while the master + // is idling in the above loop. Wouldn't want to slow down the + // handing out of work - maybe another thread? + // + for ( int i=0; i < numfaces; ++i ) + { + BuildPatchLights(i); + } + } + else + { + if ( g_iVMPIVerboseLevel >= 1 ) + Msg( "\n\n%.1f%% CPU utilization during BuildFaceLights\n\n", ( g_CPUTime.GetSeconds() * 100 / elapsed ) ); + } +} + + +//----------------------------------------- +// +// Run BuildVisLeafs across all available processing nodes +// and collect the results. +// + +// This function is called when the master receives results back from a worker. +void MPI_ReceiveVisLeafsResults( uint64 iWorkUnit, MessageBuffer *pBuf, int iWorker ) +{ + int patchesInCluster = 0; + + pBuf->read(&patchesInCluster, sizeof(patchesInCluster)); + + for ( int k=0; k < patchesInCluster; ++k ) + { + int patchnum = 0; + pBuf->read(&patchnum, sizeof(patchnum)); + + CPatch * patch = &g_Patches[patchnum]; + int numtransfers; + pBuf->read( &numtransfers, sizeof(numtransfers) ); + patch->numtransfers = numtransfers; + if (numtransfers) + { + patch->transfers = new transfer_t[numtransfers]; + pBuf->read(patch->transfers, numtransfers * sizeof(transfer_t)); + } + + total_transfer += numtransfers; + if (max_transfer < numtransfers) + max_transfer = numtransfers; + } +} + + +// Temporary variables used during callbacks. If we're going to be threadsafe, these +// should go in a structure and get passed around. +class CVMPIVisLeafsData +{ +public: + MessageBuffer *m_pVisLeafsMB; + int m_nPatchesInCluster; + transfer_t *m_pBuildVisLeafsTransfers; +}; + +CVMPIVisLeafsData g_VMPIVisLeafsData[MAX_TOOL_THREADS+1]; + + + +// This is called by BuildVisLeafs_Cluster every time it finishes a patch. +// The results are appended to g_VisLeafsMB and sent back to the master when all clusters are done. +void MPI_AddPatchData( int iThread, int patchnum, CPatch *patch ) +{ + CVMPIVisLeafsData *pData = &g_VMPIVisLeafsData[iThread]; + if ( pData->m_pVisLeafsMB ) + { + // Add in results for this patch + ++pData->m_nPatchesInCluster; + pData->m_pVisLeafsMB->write(&patchnum, sizeof(patchnum)); + pData->m_pVisLeafsMB->write(&patch->numtransfers, sizeof(patch->numtransfers)); + pData->m_pVisLeafsMB->write( patch->transfers, patch->numtransfers * sizeof(transfer_t) ); + } +} + + +// This handles a work unit sent by the master. Each work unit here is a +// list of clusters. +void MPI_ProcessVisLeafs( int iThread, uint64 iWorkUnit, MessageBuffer *pBuf ) +{ + CTimeAdder adder( &g_CPUTime ); + + CVMPIVisLeafsData *pData = &g_VMPIVisLeafsData[iThread]; + int iCluster = iWorkUnit; + + // Start this cluster. + pData->m_nPatchesInCluster = 0; + pData->m_pVisLeafsMB = pBuf; + + // Write a temp value in there. We overwrite it later. + int iSavePos = 0; + if ( pBuf ) + { + iSavePos = pBuf->getLen(); + pBuf->write( &pData->m_nPatchesInCluster, sizeof(pData->m_nPatchesInCluster) ); + } + + // Collect the results in MPI_AddPatchData. + BuildVisLeafs_Cluster( iThread, pData->m_pBuildVisLeafsTransfers, iCluster, MPI_AddPatchData ); + + // Now send the results back.. + if ( pBuf ) + { + pBuf->update( iSavePos, &pData->m_nPatchesInCluster, sizeof(pData->m_nPatchesInCluster) ); + pData->m_pVisLeafsMB = NULL; + } +} + + +void RunMPIBuildVisLeafs() +{ + g_CPUTime.Init(); + + Msg( "%-20s ", "BuildVisLeafs :" ); + if ( g_bMPIMaster ) + { + StartPacifier(""); + } + + memset( g_VMPIVisLeafsData, 0, sizeof( g_VMPIVisLeafsData ) ); + if ( !g_bMPIMaster || VMPI_GetActiveWorkUnitDistributor() == k_eWorkUnitDistributor_SDK ) + { + // Allocate space for the transfers for each thread. + for ( int i=0; i < numthreads; i++ ) + { + g_VMPIVisLeafsData[i].m_pBuildVisLeafsTransfers = BuildVisLeafs_Start(); + } + } + + // + // Slaves ask for work via GetMPIBuildVisLeafWork() + // Results are returned in BuildVisRow() + // + VMPI_SetCurrentStage( "RunMPIBuildVisLeafs" ); + + double elapsed = DistributeWork( + dvis->numclusters, + VMPI_DISTRIBUTEWORK_PACKETID, + MPI_ProcessVisLeafs, + MPI_ReceiveVisLeafsResults ); + + // Free the transfers from each thread. + for ( int i=0; i < numthreads; i++ ) + { + if ( g_VMPIVisLeafsData[i].m_pBuildVisLeafsTransfers ) + BuildVisLeafs_End( g_VMPIVisLeafsData[i].m_pBuildVisLeafsTransfers ); + } + + if ( g_bMPIMaster ) + { + EndPacifier(false); + Msg( " (%d)\n", (int)elapsed ); + } + else + { + if ( g_iVMPIVerboseLevel >= 1 ) + Msg( "%.1f%% CPU utilization during PortalFlow\n", (g_CPUTime.GetSeconds() * 100.0f / elapsed) / numthreads ); + } +} + +void VMPI_DistributeLightData() +{ + if ( !g_bUseMPI ) + return; + + if ( g_bMPIMaster ) + { + const char *pVirtualFilename = "--plightdata--"; + + CUtlBuffer lightFaceData; + + // write out the light data + lightFaceData.EnsureCapacity( pdlightdata->Count() + (numfaces * (MAXLIGHTMAPS+sizeof(int))) ); + Q_memcpy( lightFaceData.PeekPut(), pdlightdata->Base(), pdlightdata->Count() ); + lightFaceData.SeekPut( CUtlBuffer::SEEK_HEAD, pdlightdata->Count() ); + + // write out the relevant face info into the stream + for ( int i = 0; i < numfaces; i++ ) + { + for ( int j = 0; j < MAXLIGHTMAPS; j++ ) + { + lightFaceData.PutChar(g_pFaces[i].styles[j]); + } + lightFaceData.PutInt(g_pFaces[i].lightofs); + } + VMPI_FileSystem_CreateVirtualFile( pVirtualFilename, lightFaceData.Base(), lightFaceData.TellMaxPut() ); + + char cPacketID[2] = { VMPI_VRAD_PACKET_ID, VMPI_SUBPACKETID_PLIGHTDATA_RESULTS }; + VMPI_Send2Chunks( cPacketID, sizeof( cPacketID ), pVirtualFilename, strlen( pVirtualFilename ) + 1, VMPI_PERSISTENT ); + } + else + { + VMPI_SetCurrentStage( "VMPI_DistributeLightData" ); + + // Wait until we've received the filename from the master. + while ( g_LightResultsFilename.Count() == 0 ) + { + VMPI_DispatchNextMessage(); + } + + // Open + FileHandle_t fp = g_pFileSystem->Open( g_LightResultsFilename.Base(), "rb", VMPI_VIRTUAL_FILES_PATH_ID ); + if ( !fp ) + Error( "Can't open '%s' to read lighting info.", g_LightResultsFilename.Base() ); + + int size = g_pFileSystem->Size( fp ); + int faceSize = (numfaces*(MAXLIGHTMAPS+sizeof(int))); + + if ( size > faceSize ) + { + int lightSize = size - faceSize; + CUtlBuffer faceData; + pdlightdata->EnsureCount( lightSize ); + faceData.EnsureCapacity( faceSize ); + + g_pFileSystem->Read( pdlightdata->Base(), lightSize, fp ); + g_pFileSystem->Read( faceData.Base(), faceSize, fp ); + g_pFileSystem->Close( fp ); + + faceData.SeekPut( CUtlBuffer::SEEK_HEAD, faceSize ); + + // write out the face data + for ( int i = 0; i < numfaces; i++ ) + { + for ( int j = 0; j < MAXLIGHTMAPS; j++ ) + { + g_pFaces[i].styles[j] = faceData.GetChar(); + } + g_pFaces[i].lightofs = faceData.GetInt(); + } + } + } +} + + diff --git a/mp/src/utils/vrad/mpivrad.h b/mp/src/utils/vrad/mpivrad.h new file mode 100644 index 00000000..990b3df8 --- /dev/null +++ b/mp/src/utils/vrad/mpivrad.h @@ -0,0 +1,36 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef MPIVRAD_H +#define MPIVRAD_H +#ifdef _WIN32 +#pragma once +#endif + + +#define VMPI_VRAD_PACKET_ID 1 + // Sub packet IDs. + #define VMPI_SUBPACKETID_VIS_LEAFS 0 + #define VMPI_SUBPACKETID_BUILDFACELIGHTS 1 + #define VMPI_SUBPACKETID_PLIGHTDATA_RESULTS 2 + +// DistributeWork owns this packet ID. +#define VMPI_DISTRIBUTEWORK_PACKETID 2 + + +// Called first thing in the exe. +void VRAD_SetupMPI( int &argc, char **&argv ); + +void RunMPIBuildFacelights(void); +void RunMPIBuildVisLeafs(void); +void VMPI_DistributeLightData(); + +// This handles disconnections. They're usually not fatal for the master. +void HandleMPIDisconnect( int procID ); + + +#endif // MPIVRAD_H diff --git a/mp/src/utils/vrad/notes.txt b/mp/src/utils/vrad/notes.txt new file mode 100644 index 00000000..f0ca18b3 --- /dev/null +++ b/mp/src/utils/vrad/notes.txt @@ -0,0 +1,22 @@ +//============================================================================= +// CHARLIE: + +DONE: just rename files from .c to .cpp in source safe so history is kept +converting files of to .cpp files -- just get stuff recompiling first +make sure all common files and external dependencies handle .cpp files +add the dispmap class +add the dispface class +get rid of patches.c or patches.cpp as the case may be + +DONE: create a parallel array for visited displacement faces +change the visited array to a flag in the "bsp" ddispface_t structure +DONE: add reset visited displacement face functionality +DONE: visited neighbor flags +DONE: reset visited neighbor flags +DONE: move the radial_t structure and some of its functionality to a more global scope +DONE: create a finallightdispface +DONE: generate luxels for displacement face +DONE: fix precomplightmapoffsets to take displacement faces into account +fill in luxel data from sample data +modify the GetPhongNormal and GatherSampleLight functions to use displacement face normals at "spot" +make a lightinfo list? -- so we don't regenerate initlightinfo so many times? diff --git a/mp/src/utils/vrad/origface.cpp b/mp/src/utils/vrad/origface.cpp new file mode 100644 index 00000000..43a19577 --- /dev/null +++ b/mp/src/utils/vrad/origface.cpp @@ -0,0 +1,51 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//=============================================================================// + +#include "vrad.h" + +bool bOrigFacesTouched[MAX_MAP_FACES]; + + +//----------------------------------------------------------------------------- +// Pupose: clear (reset) the bOrigFacesTouched list -- parellels the original +// face list allowing an original face to only be processed once in +// pairing edges! +//----------------------------------------------------------------------------- +void ResetOrigFacesTouched( void ) +{ + for( int i = 0; i < MAX_MAP_FACES; i++ ) + { + bOrigFacesTouched[i] = false; + } +} + + +//----------------------------------------------------------------------------- +// Purpose: mark an original faces as touched (dirty) +// Input: index - index of the original face touched +//----------------------------------------------------------------------------- +void SetOrigFaceTouched( int index ) +{ + bOrigFacesTouched[index] = true; +} + + +//----------------------------------------------------------------------------- +// Purpose: return whether or not an original face has been touched +// Input: index - index of the original face touched +// Output: true/false +//----------------------------------------------------------------------------- +bool IsOrigFaceTouched( int index ) +{ + return bOrigFacesTouched[index]; +} diff --git a/mp/src/utils/vrad/radial.cpp b/mp/src/utils/vrad/radial.cpp new file mode 100644 index 00000000..46e3b925 --- /dev/null +++ b/mp/src/utils/vrad/radial.cpp @@ -0,0 +1,882 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +#include "vrad.h" +#include "lightmap.h" +#include "radial.h" +#include "mathlib/bumpvects.h" +#include "utlrbtree.h" +#include "mathlib/VMatrix.h" +#include "macro_texture.h" + + +void WorldToLuxelSpace( lightinfo_t const *l, Vector const &world, Vector2D &coord ) +{ + Vector pos; + + VectorSubtract( world, l->luxelOrigin, pos ); + coord[0] = DotProduct( pos, l->worldToLuxelSpace[0] ) - l->face->m_LightmapTextureMinsInLuxels[0]; + coord[1] = DotProduct( pos, l->worldToLuxelSpace[1] ) - l->face->m_LightmapTextureMinsInLuxels[1]; +} + +void LuxelSpaceToWorld( lightinfo_t const *l, float s, float t, Vector &world ) +{ + Vector pos; + + s += l->face->m_LightmapTextureMinsInLuxels[0]; + t += l->face->m_LightmapTextureMinsInLuxels[1]; + VectorMA( l->luxelOrigin, s, l->luxelToWorldSpace[0], pos ); + VectorMA( pos, t, l->luxelToWorldSpace[1], world ); +} + +void WorldToLuxelSpace( lightinfo_t const *l, FourVectors const &world, FourVectors &coord ) +{ + FourVectors luxelOrigin; + luxelOrigin.DuplicateVector ( l->luxelOrigin ); + + FourVectors pos = world; + pos -= luxelOrigin; + + coord.x = pos * l->worldToLuxelSpace[0]; + coord.x = SubSIMD ( coord.x, ReplicateX4 ( l->face->m_LightmapTextureMinsInLuxels[0] ) ); + coord.y = pos * l->worldToLuxelSpace[1]; + coord.y = SubSIMD ( coord.y, ReplicateX4 ( l->face->m_LightmapTextureMinsInLuxels[1] ) ); + coord.z = Four_Zeros; +} + +void LuxelSpaceToWorld( lightinfo_t const *l, fltx4 s, fltx4 t, FourVectors &world ) +{ + world.DuplicateVector ( l->luxelOrigin ); + FourVectors st; + + s = AddSIMD ( s, ReplicateX4 ( l->face->m_LightmapTextureMinsInLuxels[0] ) ); + st.DuplicateVector ( l->luxelToWorldSpace[0] ); + st *= s; + world += st; + + t = AddSIMD ( t, ReplicateX4 ( l->face->m_LightmapTextureMinsInLuxels[1] ) ); + st.DuplicateVector ( l->luxelToWorldSpace[1] ); + st *= t; + world += st; +} + + + +void AddDirectToRadial( radial_t *rad, + Vector const &pnt, + Vector2D const &coordmins, Vector2D const &coordmaxs, + LightingValue_t const light[NUM_BUMP_VECTS+1], + bool hasBumpmap, bool neighborHasBumpmap ) +{ + int s_min, s_max, t_min, t_max; + Vector2D coord; + int s, t; + float ds, dt; + float r; + float area; + int bumpSample; + + // convert world pos into local lightmap texture coord + WorldToLuxelSpace( &rad->l, pnt, coord ); + + s_min = ( int )( coordmins[0] ); + t_min = ( int )( coordmins[1] ); + s_max = ( int )( coordmaxs[0] + 0.9999f ) + 1; // ???? + t_max = ( int )( coordmaxs[1] + 0.9999f ) + 1; + + s_min = max( s_min, 0 ); + t_min = max( t_min, 0 ); + s_max = min( s_max, rad->w ); + t_max = min( t_max, rad->h ); + + for( s = s_min; s < s_max; s++ ) + { + for( t = t_min; t < t_max; t++ ) + { + float s0 = max( coordmins[0] - s, -1.0 ); + float t0 = max( coordmins[1] - t, -1.0 ); + float s1 = min( coordmaxs[0] - s, 1.0 ); + float t1 = min( coordmaxs[1] - t, 1.0 ); + + area = (s1 - s0) * (t1 - t0); + + if (area > EQUAL_EPSILON) + { + ds = fabs( coord[0] - s ); + dt = fabs( coord[1] - t ); + + r = max( ds, dt ); + + if (r < 0.1) + { + r = area / 0.1; + } + else + { + r = area / r; + } + + int i = s+t*rad->w; + + if( hasBumpmap ) + { + if( neighborHasBumpmap ) + { + for( bumpSample = 0; bumpSample < NUM_BUMP_VECTS + 1; bumpSample++ ) + { + rad->light[bumpSample][i].AddWeighted( light[bumpSample], r ); + } + } + else + { + rad->light[0][i].AddWeighted(light[0],r ); + for( bumpSample = 1; bumpSample < NUM_BUMP_VECTS + 1; bumpSample++ ) + { + rad->light[bumpSample][i].AddWeighted( light[0], r * OO_SQRT_3 ); + } + } + } + else + { + rad->light[0][i].AddWeighted( light[0], r ); + } + + rad->weight[i] += r; + } + } + } +} + + + +void AddBouncedToRadial( radial_t *rad, + Vector const &pnt, + Vector2D const &coordmins, Vector2D const &coordmaxs, + Vector const light[NUM_BUMP_VECTS+1], + bool hasBumpmap, bool neighborHasBumpmap ) +{ + int s_min, s_max, t_min, t_max; + Vector2D coord; + int s, t; + float ds, dt; + float r; + int bumpSample; + + // convert world pos into local lightmap texture coord + WorldToLuxelSpace( &rad->l, pnt, coord ); + + float dists, distt; + + dists = (coordmaxs[0] - coordmins[0]); + distt = (coordmaxs[1] - coordmins[1]); + + // patches less than a luxel in size could be mistakeningly filtered, so clamp. + dists = max( 1.0, dists ); + distt = max( 1.0, distt ); + + // find possible domain of patch influence + s_min = ( int )( coord[0] - dists * RADIALDIST ); + t_min = ( int )( coord[1] - distt * RADIALDIST ); + s_max = ( int )( coord[0] + dists * RADIALDIST + 1.0f ); + t_max = ( int )( coord[1] + distt * RADIALDIST + 1.0f ); + + // clamp to valid luxel + s_min = max( s_min, 0 ); + t_min = max( t_min, 0 ); + s_max = min( s_max, rad->w ); + t_max = min( t_max, rad->h ); + + for( s = s_min; s < s_max; s++ ) + { + for( t = t_min; t < t_max; t++ ) + { + // patch influence is based on patch size + ds = ( coord[0] - s ) / dists; + dt = ( coord[1] - t ) / distt; + + r = RADIALDIST2 - (ds * ds + dt * dt); + + int i = s+t*rad->w; + + if (r > 0) + { + if( hasBumpmap ) + { + if( neighborHasBumpmap ) + { + for( bumpSample = 0; bumpSample < NUM_BUMP_VECTS + 1; bumpSample++ ) + { + rad->light[bumpSample][i].AddWeighted( light[bumpSample], r ); + } + } + else + { + rad->light[0][i].AddWeighted( light[0], r ); + for( bumpSample = 1; bumpSample < NUM_BUMP_VECTS + 1; bumpSample++ ) + { + rad->light[bumpSample][i].AddWeighted( light[0], r * OO_SQRT_3 ); + } + } + } + else + { + rad->light[0][i].AddWeighted( light[0], r ); + } + + rad->weight[i] += r; + } + } + } +} + +void PatchLightmapCoordRange( radial_t *rad, int ndxPatch, Vector2D &mins, Vector2D &maxs ) +{ + winding_t *w; + int i; + Vector2D coord; + + mins.Init( 1E30, 1E30 ); + maxs.Init( -1E30, -1E30 ); + + CPatch *patch = &g_Patches.Element( ndxPatch ); + w = patch->winding; + + for (i = 0; i < w->numpoints; i++) + { + WorldToLuxelSpace( &rad->l, w->p[i], coord ); + mins[0] = min( mins[0], coord[0] ); + maxs[0] = max( maxs[0], coord[0] ); + mins[1] = min( mins[1], coord[1] ); + maxs[1] = max( maxs[1], coord[1] ); + } +} + +radial_t *AllocateRadial( int facenum ) +{ + radial_t *rad; + + rad = ( radial_t* )calloc( 1, sizeof( *rad ) ); + + rad->facenum = facenum; + InitLightinfo( &rad->l, facenum ); + + rad->w = rad->l.face->m_LightmapTextureSizeInLuxels[0]+1; + rad->h = rad->l.face->m_LightmapTextureSizeInLuxels[1]+1; + + return rad; +} + +void FreeRadial( radial_t *rad ) +{ + if (rad) + free( rad ); +} + + +radial_t *BuildPatchRadial( int facenum ) +{ + int j; + radial_t *rad; + CPatch *patch; + faceneighbor_t *fn; + Vector2D mins, maxs; + bool needsBumpmap, neighborNeedsBumpmap; + + needsBumpmap = texinfo[g_pFaces[facenum].texinfo].flags & SURF_BUMPLIGHT ? true : false; + + rad = AllocateRadial( facenum ); + + fn = &faceneighbor[ rad->facenum ]; + + CPatch *pNextPatch; + + if( g_FacePatches.Element( rad->facenum ) != g_FacePatches.InvalidIndex() ) + { + for( patch = &g_Patches.Element( g_FacePatches.Element( rad->facenum ) ); patch; patch = pNextPatch ) + { + // next patch + pNextPatch = NULL; + if( patch->ndxNext != g_Patches.InvalidIndex() ) + { + pNextPatch = &g_Patches.Element( patch->ndxNext ); + } + + // skip patches with children + if (patch->child1 != g_Patches.InvalidIndex() ) + continue; + + // get the range of patch lightmap texture coords + int ndxPatch = patch - g_Patches.Base(); + PatchLightmapCoordRange( rad, ndxPatch, mins, maxs ); + + if (patch->numtransfers == 0) + { + // Error, using patch that was never evaluated or has no samples + // patch->totallight[1] = 255; + } + + // + // displacement surface patch origin position and normal vectors have been changed to + // represent the displacement surface position and normal -- for radial "blending" + // we need to get the base surface patch origin! + // + if( ValidDispFace( &g_pFaces[facenum] ) ) + { + Vector patchOrigin; + WindingCenter (patch->winding, patchOrigin ); + AddBouncedToRadial( rad, patchOrigin, mins, maxs, patch->totallight.light, + needsBumpmap, needsBumpmap ); + } + else + { + AddBouncedToRadial( rad, patch->origin, mins, maxs, patch->totallight.light, + needsBumpmap, needsBumpmap ); + } + } + } + + for (j=0 ; jnumneighbors; j++) + { + if( g_FacePatches.Element( fn->neighbor[j] ) != g_FacePatches.InvalidIndex() ) + { + for( patch = &g_Patches.Element( g_FacePatches.Element( fn->neighbor[j] ) ); patch; patch = pNextPatch ) + { + // next patch + pNextPatch = NULL; + if( patch->ndxNext != g_Patches.InvalidIndex() ) + { + pNextPatch = &g_Patches.Element( patch->ndxNext ); + } + + // skip patches with children + if (patch->child1 != g_Patches.InvalidIndex() ) + continue; + + // get the range of patch lightmap texture coords + int ndxPatch = patch - g_Patches.Base(); + PatchLightmapCoordRange( rad, ndxPatch, mins, maxs ); + + neighborNeedsBumpmap = texinfo[g_pFaces[facenum].texinfo].flags & SURF_BUMPLIGHT ? true : false; + + // + // displacement surface patch origin position and normal vectors have been changed to + // represent the displacement surface position and normal -- for radial "blending" + // we need to get the base surface patch origin! + // + if( ValidDispFace( &g_pFaces[fn->neighbor[j]] ) ) + { + Vector patchOrigin; + WindingCenter (patch->winding, patchOrigin ); + AddBouncedToRadial( rad, patchOrigin, mins, maxs, patch->totallight.light, + needsBumpmap, needsBumpmap ); + } + else + { + AddBouncedToRadial( rad, patch->origin, mins, maxs, patch->totallight.light, + needsBumpmap, needsBumpmap ); + } + } + } + } + + return rad; +} + + +radial_t *BuildLuxelRadial( int facenum, int style ) +{ + LightingValue_t light[NUM_BUMP_VECTS + 1]; + + facelight_t *fl = &facelight[facenum]; + faceneighbor_t *fn = &faceneighbor[facenum]; + + radial_t *rad = AllocateRadial( facenum ); + + bool needsBumpmap = texinfo[g_pFaces[facenum].texinfo].flags & SURF_BUMPLIGHT ? true : false; + + for (int k=0 ; knumsamples ; k++) + { + if( needsBumpmap ) + { + for( int bumpSample = 0; bumpSample < NUM_BUMP_VECTS + 1; bumpSample++ ) + { + light[bumpSample] = fl->light[style][bumpSample][k]; + } + } + else + { + light[0] = fl->light[style][0][k]; + } + + AddDirectToRadial( rad, fl->sample[k].pos, fl->sample[k].mins, fl->sample[k].maxs, light, needsBumpmap, needsBumpmap ); + } + + for (int j = 0; j < fn->numneighbors; j++) + { + fl = &facelight[fn->neighbor[j]]; + + bool neighborHasBumpmap = false; + + if( texinfo[g_pFaces[fn->neighbor[j]].texinfo].flags & SURF_BUMPLIGHT ) + { + neighborHasBumpmap = true; + } + + int nstyle = 0; + + // look for style that matches + if (g_pFaces[fn->neighbor[j]].styles[nstyle] != g_pFaces[facenum].styles[style]) + { + for (nstyle = 1; nstyle < MAXLIGHTMAPS; nstyle++ ) + if ( g_pFaces[fn->neighbor[j]].styles[nstyle] == g_pFaces[facenum].styles[style] ) + break; + + // if not found, skip this neighbor + if (nstyle >= MAXLIGHTMAPS) + continue; + } + + lightinfo_t l; + + InitLightinfo( &l, fn->neighbor[j] ); + + for (int k=0 ; knumsamples ; k++) + { + if( neighborHasBumpmap ) + { + for( int bumpSample = 0; bumpSample < NUM_BUMP_VECTS + 1; bumpSample++ ) + { + light[bumpSample] = fl->light[nstyle][bumpSample][k]; + } + } + else + { + light[0]=fl->light[nstyle][0][k]; + } + + Vector tmp; + Vector2D mins, maxs; + + LuxelSpaceToWorld( &l, fl->sample[k].mins[0], fl->sample[k].mins[1], tmp ); + WorldToLuxelSpace( &rad->l, tmp, mins ); + LuxelSpaceToWorld( &l, fl->sample[k].maxs[0], fl->sample[k].maxs[1], tmp ); + WorldToLuxelSpace( &rad->l, tmp, maxs ); + + AddDirectToRadial( rad, fl->sample[k].pos, mins, maxs, light, + needsBumpmap, neighborHasBumpmap ); + } + } + + return rad; +} + + +//----------------------------------------------------------------------------- +// Purpose: returns the closest light value for a given point on the surface +// this is normally a 1:1 mapping +//----------------------------------------------------------------------------- +bool SampleRadial( radial_t *rad, Vector& pnt, LightingValue_t light[NUM_BUMP_VECTS + 1], int bumpSampleCount ) +{ + int bumpSample; + Vector2D coord; + + WorldToLuxelSpace( &rad->l, pnt, coord ); + int u = ( int )( coord[0] + 0.5f ); + int v = ( int )( coord[1] + 0.5f ); + int i = u + v * rad->w; + + if (u < 0 || u > rad->w || v < 0 || v > rad->h) + { + static bool warning = false; + if ( !warning ) + { + // punting over to KenB + // 2d coord indexes off of lightmap, generation of pnt seems suspect + Warning( "SampleRadial: Punting, Waiting for fix\n" ); + warning = true; + } + for( bumpSample = 0; bumpSample < bumpSampleCount; bumpSample++ ) + { + light[bumpSample].m_vecLighting.Init( 2550, 0, 0 ); + } + return false; + } + + bool baseSampleOk = true; + for( bumpSample = 0; bumpSample < bumpSampleCount; bumpSample++ ) + { + light[bumpSample].Zero(); + + if (rad->weight[i] > WEIGHT_EPS) + { + light[bumpSample]= rad->light[bumpSample][i]; + light[bumpSample].Scale( 1.0 / rad->weight[i] ); + } + else + { + if ( bRed2Black ) + { + // Error, luxel has no samples + light[bumpSample].m_vecLighting.Init( 0, 0, 0 ); + } + else + { + // Error, luxel has no samples + // Yes, it actually should be 2550 + light[bumpSample].m_vecLighting.Init( 2550, 0, 0 ); + } + + if (bumpSample == 0) + baseSampleOk = false; + } + } + + return baseSampleOk; +} + +bool FloatLess( float const& src1, float const& src2 ) +{ + return src1 < src2; +} + + +//----------------------------------------------------------------------------- +// Debugging! +//----------------------------------------------------------------------------- +void GetRandomColor( unsigned char *color ) +{ + static bool firstTime = true; + + if( firstTime ) + { + firstTime = false; + srand( 0 ); + } + + color[0] = ( unsigned char )( rand() * ( 255.0f / VALVE_RAND_MAX ) ); + color[1] = ( unsigned char )( rand() * ( 255.0f / VALVE_RAND_MAX ) ); + color[2] = ( unsigned char )( rand() * ( 255.0f / VALVE_RAND_MAX ) ); +} + + +#if 0 +// debugging! -- not accurate! +void DumpLuxels( facelight_t *pFaceLight, Vector *luxelColors, int ndxFace ) +{ + static FileHandle_t pFpLuxels = NULL; + + ThreadLock(); + + if( !pFpLuxels ) + { + pFpLuxels = g_pFileSystem->Open( "luxels.txt", "w" ); + } + + dface_t *pFace = &g_pFaces[ndxFace]; + bool bDisp = ( pFace->dispinfo != -1 ); + + for( int ndx = 0; ndx < pFaceLight->numluxels; ndx++ ) + { + WriteWinding( pFpLuxels, pFaceLight->sample[ndx].w, luxelColors[ndx] ); + if( bDumpNormals && bDisp ) + { + WriteNormal( pFpLuxels, pFaceLight->luxel[ndx], pFaceLight->luxelNormals[ndx], 15.0f, Vector( 255, 255, 0 ) ); + } + } + + ThreadUnlock(); +} +#endif + + +static FileHandle_t pFileLuxels[4] = { NULL, NULL, NULL, NULL }; + +void DumpDispLuxels( int iFace, Vector &color, int iLuxel, int nBump ) +{ + // Lock the thread and dump the luxel data. + ThreadLock(); + + // Get the face and facelight data. + facelight_t *pFaceLight = &facelight[iFace]; + + // Open the luxel files. + char szFileName[512]; + for ( int iBump = 0; iBump < ( NUM_BUMP_VECTS+1 ); ++iBump ) + { + if ( pFileLuxels[iBump] == NULL ) + { + sprintf( szFileName, "luxels_bump%d.txt", iBump ); + pFileLuxels[iBump] = g_pFileSystem->Open( szFileName, "w" ); + } + } + + WriteWinding( pFileLuxels[nBump], pFaceLight->sample[iLuxel].w, color ); + + ThreadUnlock(); +} + +void CloseDispLuxels() +{ + for ( int iBump = 0; iBump < ( NUM_BUMP_VECTS+1 ); ++iBump ) + { + if ( pFileLuxels[iBump] ) + { + g_pFileSystem->Close( pFileLuxels[iBump] ); + } + } +} + +/* +============= +FinalLightFace + +Add the indirect lighting on top of the direct +lighting and save into final map format +============= +*/ +void FinalLightFace( int iThread, int facenum ) +{ + dface_t *f; + int i, j, k; + facelight_t *fl; + float minlight; + int lightstyles; + LightingValue_t lb[NUM_BUMP_VECTS + 1], v[NUM_BUMP_VECTS + 1]; + unsigned char *pdata[NUM_BUMP_VECTS + 1]; + int bumpSample; + radial_t *rad = NULL; + radial_t *prad = NULL; + + f = &g_pFaces[facenum]; + + // test for non-lit texture + if ( texinfo[f->texinfo].flags & TEX_SPECIAL) + return; + + fl = &facelight[facenum]; + + + for (lightstyles=0; lightstyles < MAXLIGHTMAPS; lightstyles++ ) + { + if ( f->styles[lightstyles] == 255 ) + break; + } + if ( !lightstyles ) + return; + + + // + // sample the triangulation + // + minlight = FloatForKey (face_entity[facenum], "_minlight") * 128; + + bool needsBumpmap = ( texinfo[f->texinfo].flags & SURF_BUMPLIGHT ) ? true : false; + int bumpSampleCount = needsBumpmap ? NUM_BUMP_VECTS + 1 : 1; + + bool bDisp = ( f->dispinfo != -1 ); + +//#define RANDOM_COLOR + +#ifdef RANDOM_COLOR + unsigned char randomColor[3]; + GetRandomColor( randomColor ); +#endif + + + // NOTE: I'm using these RB trees to sort all the illumination values + // to compute median colors. Turns out that this is a somewhat better + // method that using the average; usually if there are surfaces + // with a large light intensity variation, the extremely bright regions + // have a very small area and tend to influence the average too much. + CUtlRBTree< float, int > m_Red( 0, 256, FloatLess ); + CUtlRBTree< float, int > m_Green( 0, 256, FloatLess ); + CUtlRBTree< float, int > m_Blue( 0, 256, FloatLess ); + + for (k=0 ; k < lightstyles; k++ ) + { + m_Red.RemoveAll(); + m_Green.RemoveAll(); + m_Blue.RemoveAll(); + + if (!do_fast) + { + if( !bDisp ) + { + rad = BuildLuxelRadial( facenum, k ); + } + else + { + rad = StaticDispMgr()->BuildLuxelRadial( facenum, k, needsBumpmap ); + } + } + + if (numbounce > 0 && k == 0) + { + // currently only radiosity light non-displacement surfaces! + if( !bDisp ) + { + prad = BuildPatchRadial( facenum ); + } + else + { + prad = StaticDispMgr()->BuildPatchRadial( facenum, needsBumpmap ); + } + } + + // pack the nonbump texture and the three bump texture for the given + // lightstyle right next to each other. + // NOTE: Even though it's building positions for all bump-mapped data, + // it isn't going to use those positions (see loop over bumpSample below) + // The file offset is correctly computed to only store space for 1 set + // of light data if we don't have bumped lighting. + for( bumpSample = 0; bumpSample < bumpSampleCount; ++bumpSample ) + { + pdata[bumpSample] = &(*pdlightdata)[f->lightofs + (k * bumpSampleCount + bumpSample) * fl->numluxels*4]; + } + + // Compute the average luxel color, but not for the bump samples + Vector avg( 0.0f, 0.0f, 0.0f ); + int avgCount = 0; + + for (j=0 ; jnumluxels; j++) + { + // garymct - direct lighting + bool baseSampleOk = true; + + if (!do_fast) + { + if( !bDisp ) + { + baseSampleOk = SampleRadial( rad, fl->luxel[j], lb, bumpSampleCount ); + } + else + { + baseSampleOk = StaticDispMgr()->SampleRadial( facenum, rad, fl->luxel[j], j, lb, bumpSampleCount, false ); + } + } + else + { + for ( int iBump = 0 ; iBump < bumpSampleCount; iBump++ ) + { + lb[iBump] = fl->light[0][iBump][j]; + } + } + + if (prad) + { + // garymct - bounced light + // v is indirect light that is received on the luxel. + if( !bDisp ) + { + SampleRadial( prad, fl->luxel[j], v, bumpSampleCount ); + } + else + { + StaticDispMgr()->SampleRadial( facenum, prad, fl->luxel[j], j, v, bumpSampleCount, true ); + } + + for( bumpSample = 0; bumpSample < bumpSampleCount; ++bumpSample ) + { + lb[bumpSample].AddLight( v[bumpSample] ); + } + } + + if ( bDisp && g_bDumpPatches ) + { + for( bumpSample = 0; bumpSample < bumpSampleCount; ++bumpSample ) + { + DumpDispLuxels( facenum, lb[bumpSample].m_vecLighting, j, bumpSample ); + } + } + + if (fl->numsamples == 0) + { + for( i = 0; i < bumpSampleCount; i++ ) + { + lb[i].Init( 255, 0, 0 ); + } + baseSampleOk = false; + } + + int bumpSample; + for( bumpSample = 0; bumpSample < bumpSampleCount; bumpSample++ ) + { + // clip from the bottom first + // garymct: minlight is a per entity minimum light value? + for( i=0; i<3; i++ ) + { + lb[bumpSample].m_vecLighting[i] = max( lb[bumpSample].m_vecLighting[i], minlight ); + } + + // Do the average light computation, I'm assuming (perhaps incorrectly?) + // that all luxels in a particular lightmap have the same area here. + // Also, don't bother doing averages for the bump samples. Doing it here + // because of the minlight clamp above + the random color testy thingy. + // Also have to do it before Vec3toColorRGBExp32 because it + // destructively modifies lb[bumpSample] (Feh!) + if ((bumpSample == 0) && baseSampleOk) + { + ++avgCount; + + ApplyMacroTextures( facenum, fl->luxel[j], lb[0].m_vecLighting ); + + // For median computation + m_Red.Insert( lb[bumpSample].m_vecLighting[0] ); + m_Green.Insert( lb[bumpSample].m_vecLighting[1] ); + m_Blue.Insert( lb[bumpSample].m_vecLighting[2] ); + } + +#ifdef RANDOM_COLOR + pdata[bumpSample][0] = randomColor[0] / ( bumpSample + 1 ); + pdata[bumpSample][1] = randomColor[1] / ( bumpSample + 1 ); + pdata[bumpSample][2] = randomColor[2] / ( bumpSample + 1 ); + pdata[bumpSample][3] = 0; +#else + // convert to a 4 byte r,g,b,signed exponent format + VectorToColorRGBExp32( Vector( lb[bumpSample].m_vecLighting.x, lb[bumpSample].m_vecLighting.y, + lb[bumpSample].m_vecLighting.z ), *( ColorRGBExp32 *)pdata[bumpSample] ); +#endif + + pdata[bumpSample] += 4; + } + } + FreeRadial( rad ); + if (prad) + { + FreeRadial( prad ); + prad = NULL; + } + + // Compute the median color for this lightstyle + // Remember, the data goes *before* the specified light_ofs, in *reverse order* + ColorRGBExp32 *pAvgColor = dface_AvgLightColor( f, k ); + if (avgCount == 0) + { + Vector median( 0, 0, 0 ); + VectorToColorRGBExp32( median, *pAvgColor ); + } + else + { + unsigned int r, g, b; + r = m_Red.FirstInorder(); + g = m_Green.FirstInorder(); + b = m_Blue.FirstInorder(); + avgCount >>= 1; + while (avgCount > 0) + { + r = m_Red.NextInorder(r); + g = m_Green.NextInorder(g); + b = m_Blue.NextInorder(b); + --avgCount; + } + + Vector median( m_Red[r], m_Green[g], m_Blue[b] ); + VectorToColorRGBExp32( median, *pAvgColor ); + } + } +} diff --git a/mp/src/utils/vrad/radial.h b/mp/src/utils/vrad/radial.h new file mode 100644 index 00000000..862167cb --- /dev/null +++ b/mp/src/utils/vrad/radial.h @@ -0,0 +1,76 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef RADIAL_H +#define RADIAL_H +#pragma once + +#include "mathlib/bumpvects.h" +#include "mathlib/ssemath.h" +#include "lightmap.h" + +#define RADIALDIST2 2 // (1.25*1.25+1.25*1.25) +#define RADIALDIST 1.42 // 1.77 // sqrt( RADIALDIST2 ) + +#define WEIGHT_EPS 0.00001f + +//----------------------------------------------------------------------------- +// The radial_t data structure is used to accumulate irregularly spaced and irregularly +// shaped direct and indirect lighting samples into a uniformly spaced and shaped luxel grid. +// +// The name "radial" is more historical than discriptive; it stems from the filtering method, +// one of several methods initially tried. Since all the other methods have since been deleted, +// it would probably be more accurate to rename it something like "LuxelAccumulationBucket" or +// something similar, but since "radial" is fairly meaningless it's not like it's actually confusing +// the issue. +//----------------------------------------------------------------------------- +typedef struct radial_s +{ + int facenum; + lightinfo_t l; + int w, h; + float weight[SINGLEMAP]; + LightingValue_t light[NUM_BUMP_VECTS + 1][SINGLEMAP]; +} radial_t; + + +void WorldToLuxelSpace( lightinfo_t const *l, Vector const &world, Vector2D &coord ); +void LuxelSpaceToWorld( lightinfo_t const *l, float s, float t, Vector &world ); + +void WorldToLuxelSpace( lightinfo_t const *l, FourVectors const &world, FourVectors &coord ); +void LuxelSpaceToWorld( lightinfo_t const *l, fltx4 s, fltx4 t, FourVectors &world ); + +void AddDirectToRadial( radial_t *rad, + Vector const &pnt, + Vector2D const &coordmins, Vector2D const &coordmaxs, + Vector const light[NUM_BUMP_VECTS+1], + bool hasBumpmap, bool neighborHasBumpmap ); + +void AddBounceToRadial( radial_t *rad, + Vector const &pnt, + Vector2D const &coordmins, Vector2D const &coordmaxs, + Vector const light[NUM_BUMP_VECTS+1], + bool hasBumpmap, bool neighborHasBumpmap ); + +bool SampleRadial( radial_t *rad, Vector& pnt, Vector light[NUM_BUMP_VECTS+1], int bumpSampleCount ); + +radial_t *AllocateRadial( int facenum ); +void FreeRadial( radial_t *rad ); + +bool SampleRadial( radial_t *rad, Vector& pnt, Vector light[NUM_BUMP_VECTS + 1], int bumpSampleCount ); +radial_t *BuildPatchRadial( int facenum ); + +// utilities +bool FloatLess( float const& src1, float const& src2 ); + +#endif diff --git a/mp/src/utils/vrad/samplehash.cpp b/mp/src/utils/vrad/samplehash.cpp new file mode 100644 index 00000000..fb35d351 --- /dev/null +++ b/mp/src/utils/vrad/samplehash.cpp @@ -0,0 +1,230 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +#include "vrad.h" +#include "lightmap.h" + +#define SAMPLEHASH_NUM_BUCKETS 65536 +#define SAMPLEHASH_GROW_SIZE 0 +#define SAMPLEHASH_INIT_SIZE 0 + +int samplesAdded = 0; +int patchSamplesAdded = 0; +static unsigned short g_PatchIterationKey = 0; + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool SampleData_CompareFunc( SampleData_t const &src1, SampleData_t const &src2 ) +{ + return ( ( src1.x == src2.x ) && + ( src1.y == src2.y ) && + ( src1.z == src2.z ) ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +unsigned int SampleData_KeyFunc( SampleData_t const &src ) +{ + return ( src.x + src.y + src.z ); +} + + +CUtlHash g_SampleHashTable( SAMPLEHASH_NUM_BUCKETS, + SAMPLEHASH_GROW_SIZE, + SAMPLEHASH_INIT_SIZE, + SampleData_CompareFunc, SampleData_KeyFunc ); + + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +UtlHashHandle_t SampleData_Find( sample_t *pSample ) +{ + SampleData_t sampleData; + sampleData.x = ( int )( pSample->pos.x / SAMPLEHASH_VOXEL_SIZE ) * 100; + sampleData.y = ( int )( pSample->pos.y / SAMPLEHASH_VOXEL_SIZE ) * 10; + sampleData.z = ( int )( pSample->pos.z / SAMPLEHASH_VOXEL_SIZE ); + + return g_SampleHashTable.Find( sampleData ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +UtlHashHandle_t SampleData_InsertIntoHashTable( sample_t *pSample, SampleHandle_t sampleHandle ) +{ + SampleData_t sampleData; + sampleData.x = ( int )( pSample->pos.x / SAMPLEHASH_VOXEL_SIZE ) * 100; + sampleData.y = ( int )( pSample->pos.y / SAMPLEHASH_VOXEL_SIZE ) * 10; + sampleData.z = ( int )( pSample->pos.z / SAMPLEHASH_VOXEL_SIZE ); + + UtlHashHandle_t handle = g_SampleHashTable.AllocEntryFromKey( sampleData ); + + SampleData_t *pSampleData = &g_SampleHashTable.Element( handle ); + pSampleData->x = sampleData.x; + pSampleData->y = sampleData.y; + pSampleData->z = sampleData.z; + pSampleData->m_Samples.AddToTail( sampleHandle ); + + samplesAdded++; + + return handle; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +UtlHashHandle_t SampleData_AddSample( sample_t *pSample, SampleHandle_t sampleHandle ) +{ + + // find the key -- if it doesn't exist add new sample data to the + // hash table + UtlHashHandle_t handle = SampleData_Find( pSample ); + if( handle == g_SampleHashTable.InvalidHandle() ) + { + handle = SampleData_InsertIntoHashTable( pSample, sampleHandle ); + } + else + { + SampleData_t *pSampleData = &g_SampleHashTable.Element( handle ); + pSampleData->m_Samples.AddToTail( sampleHandle ); + + samplesAdded++; + } + + return handle; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void SampleData_Log( void ) +{ + if( g_bLogHashData ) + { + g_SampleHashTable.Log( "samplehash.txt" ); + } +} + + +//============================================================================= +//============================================================================= +// +// PatchSample Functions +// +//============================================================================= +//============================================================================= + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool PatchSampleData_CompareFunc( PatchSampleData_t const &src1, PatchSampleData_t const &src2 ) +{ + return ( ( src1.x == src2.x ) && + ( src1.y == src2.y ) && + ( src1.z == src2.z ) ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +unsigned int PatchSampleData_KeyFunc( PatchSampleData_t const &src ) +{ + return ( src.x + src.y + src.z ); +} + + +CUtlHash g_PatchSampleHashTable( SAMPLEHASH_NUM_BUCKETS, + SAMPLEHASH_GROW_SIZE, + SAMPLEHASH_INIT_SIZE, + PatchSampleData_CompareFunc, PatchSampleData_KeyFunc ); + +void GetPatchSampleHashXYZ( const Vector &vOrigin, int &x, int &y, int &z ) +{ + x = ( int )( vOrigin.x / SAMPLEHASH_VOXEL_SIZE ); + y = ( int )( vOrigin.y / SAMPLEHASH_VOXEL_SIZE ); + z = ( int )( vOrigin.z / SAMPLEHASH_VOXEL_SIZE ); +} + + +unsigned short IncrementPatchIterationKey() +{ + if ( g_PatchIterationKey == 0xFFFF ) + { + g_PatchIterationKey = 1; + for ( int i=0; i < g_Patches.Count(); i++ ) + g_Patches[i].m_IterationKey = 0; + } + else + { + g_PatchIterationKey++; + } + return g_PatchIterationKey; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void PatchSampleData_AddSample( CPatch *pPatch, int ndxPatch ) +{ + int patchSampleMins[3], patchSampleMaxs[3]; + +#if defined( SAMPLEHASH_USE_AREA_PATCHES ) + GetPatchSampleHashXYZ( pPatch->mins, patchSampleMins[0], patchSampleMins[1], patchSampleMins[2] ); + GetPatchSampleHashXYZ( pPatch->maxs, patchSampleMaxs[0], patchSampleMaxs[1], patchSampleMaxs[2] ); +#else + // If not using area patches, just use the patch's origin to add it to the voxels. + GetPatchSampleHashXYZ( pPatch->origin, patchSampleMins[0], patchSampleMins[1], patchSampleMins[2] ); + memcpy( patchSampleMaxs, patchSampleMins, sizeof( patchSampleMaxs ) ); +#endif + + // Make sure mins are smaller than maxs so we don't iterate for 4 bil. + Assert( patchSampleMins[0] <= patchSampleMaxs[0] && patchSampleMins[1] <= patchSampleMaxs[1] && patchSampleMins[2] <= patchSampleMaxs[2] ); + patchSampleMins[0] = min( patchSampleMins[0], patchSampleMaxs[0] ); + patchSampleMins[1] = min( patchSampleMins[1], patchSampleMaxs[1] ); + patchSampleMins[2] = min( patchSampleMins[2], patchSampleMaxs[2] ); + + int iterateCoords[3]; + for ( iterateCoords[0]=patchSampleMins[0]; iterateCoords[0] <= patchSampleMaxs[0]; iterateCoords[0]++ ) + { + for ( iterateCoords[1]=patchSampleMins[1]; iterateCoords[1] <= patchSampleMaxs[1]; iterateCoords[1]++ ) + { + for ( iterateCoords[2]=patchSampleMins[2]; iterateCoords[2] <= patchSampleMaxs[2]; iterateCoords[2]++ ) + { + // find the key -- if it doesn't exist add new sample data to the + // hash table + PatchSampleData_t iteratePatch; + iteratePatch.x = iterateCoords[0] * 100; + iteratePatch.y = iterateCoords[1] * 10; + iteratePatch.z = iterateCoords[2]; + + UtlHashHandle_t handle = g_PatchSampleHashTable.Find( iteratePatch ); + if( handle == g_PatchSampleHashTable.InvalidHandle() ) + { + UtlHashHandle_t handle = g_PatchSampleHashTable.AllocEntryFromKey( iteratePatch ); + + PatchSampleData_t *pPatchData = &g_PatchSampleHashTable.Element( handle ); + pPatchData->x = iteratePatch.x; + pPatchData->y = iteratePatch.y; + pPatchData->z = iteratePatch.z; + pPatchData->m_ndxPatches.AddToTail( ndxPatch ); + + patchSamplesAdded++; + } + else + { + PatchSampleData_t *pPatchData = &g_PatchSampleHashTable.Element( handle ); + pPatchData->m_ndxPatches.AddToTail( ndxPatch ); + + patchSamplesAdded++; + } + } + } + } +} + diff --git a/mp/src/utils/vrad/trace.cpp b/mp/src/utils/vrad/trace.cpp new file mode 100644 index 00000000..f049fc40 --- /dev/null +++ b/mp/src/utils/vrad/trace.cpp @@ -0,0 +1,644 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//===========================================================================// +// trace.c + +//============================================================================= + +#include "vrad.h" +#include "trace.h" +#include "Cmodel.h" +#include "mathlib/vmatrix.h" + + +//============================================================================= + +class CToolTrace : public CBaseTrace +{ +public: + CToolTrace() {} + + Vector mins; + Vector maxs; + Vector extents; + + texinfo_t *surface; + + qboolean ispoint; + +private: + CToolTrace( const CToolTrace& ); +}; + + +// 1/32 epsilon to keep floating point happy +#define DIST_EPSILON (0.03125) + +// JAYHL2: This used to be -1, but that caused lots of epsilon issues +// around slow sloping planes. Perhaps Quake2 limited maps to a certain +// slope / angle on walkable ground. It has to be a negative number +// so that the tests work out. +#define NEVER_UPDATED -9999 + +//============================================================================= + +bool DM_RayDispIntersectTest( CVRADDispColl *pTree, Vector& rayStart, Vector& rayEnd, CToolTrace *pTrace ); +void DM_ClipBoxToBrush( CToolTrace *trace, const Vector & mins, const Vector & maxs, const Vector& p1, const Vector& p2, dbrush_t *brush ); + +//============================================================================= + +float TraceLeafBrushes( int leafIndex, const Vector &start, const Vector &end, CBaseTrace &traceOut ) +{ + dleaf_t *pLeaf = dleafs + leafIndex; + CToolTrace trace; + memset( &trace, 0, sizeof(trace) ); + trace.ispoint = true; + trace.startsolid = false; + trace.fraction = 1.0; + + for ( int i = 0; i < pLeaf->numleafbrushes; i++ ) + { + int brushnum = dleafbrushes[pLeaf->firstleafbrush+i]; + dbrush_t *b = &dbrushes[brushnum]; + if ( !(b->contents & MASK_OPAQUE)) + continue; + + Vector zeroExtents = vec3_origin; + DM_ClipBoxToBrush( &trace, zeroExtents, zeroExtents, start, end, b); + if ( trace.fraction != 1.0 || trace.startsolid ) + { + if ( trace.startsolid ) + trace.fraction = 0.0f; + traceOut = trace; + return trace.fraction; + } + } + traceOut = trace; + return 1.0f; +} + +DispTested_t s_DispTested[MAX_TOOL_THREADS+1]; + +// this just uses the average coverage for the triangle +class CCoverageCount : public ITransparentTriangleCallback +{ +public: + CCoverageCount() + { + m_coverage = Four_Zeros; + } + + virtual bool VisitTriangle_ShouldContinue( const TriIntersectData_t &triangle, const FourRays &rays, fltx4 *pHitMask, fltx4 *b0, fltx4 *b1, fltx4 *b2, int32 hitID ) + { + float color = g_RtEnv.GetTriangleColor( hitID ).x; + m_coverage = AddSIMD( m_coverage, AndSIMD ( *pHitMask, ReplicateX4 ( color ) ) ); + m_coverage = MinSIMD( m_coverage, Four_Ones ); + + fltx4 onesMask = CmpEqSIMD( m_coverage, Four_Ones ); + + // we should continue if the ones that hit the triangle have onesMask set to zero + // so hitMask & onesMask != hitMask + // so hitMask & onesMask == hitMask means we're done + // so ts(hitMask & onesMask == hitMask) != 0xF says go on + return 0xF != TestSignSIMD ( CmpEqSIMD ( AndSIMD( *pHitMask, onesMask ), *pHitMask ) ); + } + + fltx4 GetCoverage() + { + return m_coverage; + } + + fltx4 GetFractionVisible() + { + return SubSIMD ( Four_Ones, m_coverage ); + } + + fltx4 m_coverage; +}; + +// this will sample the texture to get a coverage at the ray intersection point +class CCoverageCountTexture : public CCoverageCount +{ +public: + virtual bool VisitTriangle_ShouldContinue( const TriIntersectData_t &triangle, const FourRays &rays, fltx4 *pHitMask, fltx4 *b0, fltx4 *b1, fltx4 *b2, int32 hitID ) + { + int sign = TestSignSIMD( *pHitMask ); + float addedCoverage[4]; + for ( int s = 0; s < 4; s++) + { + addedCoverage[s] = 0.0f; + if ( ( sign >> s) & 0x1 ) + { + addedCoverage[s] = ComputeCoverageFromTexture( b0->m128_f32[s], b1->m128_f32[s], b2->m128_f32[s], hitID ); + } + } + m_coverage = AddSIMD( m_coverage, LoadUnalignedSIMD( addedCoverage ) ); + m_coverage = MinSIMD( m_coverage, Four_Ones ); + fltx4 onesMask = CmpEqSIMD( m_coverage, Four_Ones ); + + // we should continue if the ones that hit the triangle have onesMask set to zero + // so hitMask & onesMask != hitMask + // so hitMask & onesMask == hitMask means we're done + // so ts(hitMask & onesMask == hitMask) != 0xF says go on + return 0xF != TestSignSIMD ( CmpEqSIMD ( AndSIMD( *pHitMask, onesMask ), *pHitMask ) ); + } +}; + +void TestLine( const FourVectors& start, const FourVectors& stop, + fltx4 *pFractionVisible, int static_prop_index_to_ignore ) +{ + FourRays myrays; + myrays.origin = start; + myrays.direction = stop; + myrays.direction -= myrays.origin; + fltx4 len = myrays.direction.length(); + myrays.direction *= ReciprocalSIMD( len ); + + RayTracingResult rt_result; + CCoverageCountTexture coverageCallback; + + g_RtEnv.Trace4Rays(myrays, Four_Zeros, len, &rt_result, TRACE_ID_STATICPROP | static_prop_index_to_ignore, g_bTextureShadows ? &coverageCallback : 0 ); + + // Assume we can see the targets unless we get hits + float visibility[4]; + for ( int i = 0; i < 4; i++ ) + { + visibility[i] = 1.0f; + if ( ( rt_result.HitIds[i] != -1 ) && + ( rt_result.HitDistance.m128_f32[i] < len.m128_f32[i] ) ) + { + visibility[i] = 0.0f; + } + } + *pFractionVisible = LoadUnalignedSIMD( visibility ); + if ( g_bTextureShadows ) + *pFractionVisible = MinSIMD( *pFractionVisible, coverageCallback.GetFractionVisible() ); +} + + + +/* +================ +DM_ClipBoxToBrush +================ +*/ +void DM_ClipBoxToBrush( CToolTrace *trace, const Vector& mins, const Vector& maxs, const Vector& p1, const Vector& p2, + dbrush_t *brush) +{ + dplane_t *plane, *clipplane; + float dist; + Vector ofs; + float d1, d2; + float f; + dbrushside_t *side, *leadside; + + if (!brush->numsides) + return; + + float enterfrac = NEVER_UPDATED; + float leavefrac = 1.f; + clipplane = NULL; + + bool getout = false; + bool startout = false; + leadside = NULL; + + // Loop interchanged, so we don't have to check trace->ispoint every side. + if ( !trace->ispoint ) + { + for (int i=0 ; inumsides ; ++i) + { + side = &dbrushsides[brush->firstside+i]; + plane = dplanes + side->planenum; + + // FIXME: special case for axial + + // general box case + // push the plane out apropriately for mins/maxs + + // FIXME: use signbits into 8 way lookup for each mins/maxs + ofs.x = (plane->normal.x < 0) ? maxs.x : mins.x; + ofs.y = (plane->normal.y < 0) ? maxs.y : mins.y; + ofs.z = (plane->normal.z < 0) ? maxs.z : mins.z; +// for (j=0 ; j<3 ; j++) +// { + // Set signmask to either 0 if the sign is negative, or 0xFFFFFFFF is the sign is positive: + //int signmask = (((*(int *)&(plane->normal[j]))&0x80000000) >> 31) - 1; + + //float temp = maxs[j]; + //*(int *)&(ofs[j]) = (~signmask) & (*(int *)&temp); + //float temp1 = mins[j]; + //*(int *)&(ofs[j]) |= (signmask) & (*(int *)&temp1); +// } + dist = DotProduct (ofs, plane->normal); + dist = plane->dist - dist; + + d1 = DotProduct (p1, plane->normal) - dist; + d2 = DotProduct (p2, plane->normal) - dist; + + // if completely in front of face, no intersection + if (d1 > 0 && d2 > 0) + return; + + if (d2 > 0) + getout = true; // endpoint is not in solid + if (d1 > 0) + startout = true; + + if (d1 <= 0 && d2 <= 0) + continue; + + // crosses face + if (d1 > d2) + { // enter + f = (d1-DIST_EPSILON) / (d1-d2); + if (f > enterfrac) + { + enterfrac = f; + clipplane = plane; + leadside = side; + } + } + else + { // leave + f = (d1+DIST_EPSILON) / (d1-d2); + if (f < leavefrac) + leavefrac = f; + } + } + } + else + { + for (int i=0 ; inumsides ; ++i) + { + side = &dbrushsides[brush->firstside+i]; + plane = dplanes + side->planenum; + + // FIXME: special case for axial + + // special point case + // don't ray trace against bevel planes + if( side->bevel == 1 ) + continue; + + dist = plane->dist; + d1 = DotProduct (p1, plane->normal) - dist; + d2 = DotProduct (p2, plane->normal) - dist; + + // if completely in front of face, no intersection + if (d1 > 0 && d2 > 0) + return; + + if (d2 > 0) + getout = true; // endpoint is not in solid + if (d1 > 0) + startout = true; + + if (d1 <= 0 && d2 <= 0) + continue; + + // crosses face + if (d1 > d2) + { // enter + f = (d1-DIST_EPSILON) / (d1-d2); + if (f > enterfrac) + { + enterfrac = f; + clipplane = plane; + leadside = side; + } + } + else + { // leave + f = (d1+DIST_EPSILON) / (d1-d2); + if (f < leavefrac) + leavefrac = f; + } + } + } + + + + if (!startout) + { // original point was inside brush + trace->startsolid = true; + if (!getout) + trace->allsolid = true; + return; + } + if (enterfrac < leavefrac) + { + if (enterfrac > NEVER_UPDATED && enterfrac < trace->fraction) + { + if (enterfrac < 0) + enterfrac = 0; + trace->fraction = enterfrac; + trace->plane.dist = clipplane->dist; + trace->plane.normal = clipplane->normal; + trace->plane.type = clipplane->type; + if (leadside->texinfo!=-1) + trace->surface = &texinfo[leadside->texinfo]; + else + trace->surface = 0; + trace->contents = brush->contents; + } + } +} + +void TestLine_DoesHitSky( FourVectors const& start, FourVectors const& stop, + fltx4 *pFractionVisible, bool canRecurse, int static_prop_to_skip, bool bDoDebug ) +{ + FourRays myrays; + myrays.origin = start; + myrays.direction = stop; + myrays.direction -= myrays.origin; + fltx4 len = myrays.direction.length(); + myrays.direction *= ReciprocalSIMD( len ); + RayTracingResult rt_result; + CCoverageCountTexture coverageCallback; + + g_RtEnv.Trace4Rays(myrays, Four_Zeros, len, &rt_result, TRACE_ID_STATICPROP | static_prop_to_skip, g_bTextureShadows? &coverageCallback : 0); + + if ( bDoDebug ) + { + WriteTrace( "trace.txt", myrays, rt_result ); + } + + float aOcclusion[4]; + for ( int i = 0; i < 4; i++ ) + { + aOcclusion[i] = 0.0f; + if ( ( rt_result.HitIds[i] != -1 ) && + ( rt_result.HitDistance.m128_f32[i] < len.m128_f32[i] ) ) + { + int id = g_RtEnv.OptimizedTriangleList[rt_result.HitIds[i]].m_Data.m_IntersectData.m_nTriangleID; + if ( !( id & TRACE_ID_SKY ) ) + aOcclusion[i] = 1.0f; + } + } + fltx4 occlusion = LoadUnalignedSIMD( aOcclusion ); + if (g_bTextureShadows) + occlusion = MaxSIMD ( occlusion, coverageCallback.GetCoverage() ); + + bool fullyOccluded = ( TestSignSIMD( CmpGeSIMD( occlusion, Four_Ones ) ) == 0xF ); + + // if we hit sky, and we're not in a sky camera's area, try clipping into the 3D sky boxes + if ( (! fullyOccluded) && canRecurse && (! g_bNoSkyRecurse ) ) + { + FourVectors dir = stop; + dir -= start; + dir.VectorNormalize(); + + int leafIndex = -1; + leafIndex = PointLeafnum( start.Vec( 0 ) ); + if ( leafIndex >= 0 ) + { + int area = dleafs[leafIndex].area; + if (area >= 0 && area < numareas) + { + if (area_sky_cameras[area] < 0) + { + int cam; + for (cam = 0; cam < num_sky_cameras; ++cam) + { + FourVectors skystart, skytrans, skystop; + skystart.DuplicateVector( sky_cameras[cam].origin ); + skystop = start; + skystop *= sky_cameras[cam].world_to_sky; + skystart += skystop; + + skystop = dir; + skystop *= MAX_TRACE_LENGTH; + skystop += skystart; + TestLine_DoesHitSky ( skystart, skystop, pFractionVisible, false, static_prop_to_skip, bDoDebug ); + occlusion = AddSIMD ( occlusion, Four_Ones ); + occlusion = SubSIMD ( occlusion, *pFractionVisible ); + } + } + } + } + } + + occlusion = MaxSIMD( occlusion, Four_Zeros ); + occlusion = MinSIMD( occlusion, Four_Ones ); + *pFractionVisible = SubSIMD( Four_Ones, occlusion ); +} + + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +int PointLeafnum_r( const Vector &point, int ndxNode ) +{ + // while loop here is to avoid recursion overhead + while( ndxNode >= 0 ) + { + dnode_t *pNode = dnodes + ndxNode; + dplane_t *pPlane = dplanes + pNode->planenum; + + float dist; + if( pPlane->type < 3 ) + { + dist = point[pPlane->type] - pPlane->dist; + } + else + { + dist = DotProduct( pPlane->normal, point ) - pPlane->dist; + } + + if( dist < 0.0f ) + { + ndxNode = pNode->children[1]; + } + else + { + ndxNode = pNode->children[0]; + } + } + + return ( -1 - ndxNode ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +int PointLeafnum( const Vector &point ) +{ + return PointLeafnum_r( point, 0 ); +} + +// this iterates the list of entities looking for _vradshadows 1 +// each brush entity containing this key is added to the raytracing environment +// as a triangle soup model. + +dmodel_t *BrushmodelForEntity( entity_t *pEntity ) +{ + const char *pModelname = ValueForKey( pEntity, "model" ); + if ( Q_strlen(pModelname) > 1 ) + { + int modelIndex = atol( pModelname + 1 ); + if ( modelIndex > 0 && modelIndex < nummodels ) + { + return &dmodels[modelIndex]; + } + } + return NULL; +} + +void AddBrushToRaytraceEnvironment( dbrush_t *pBrush, const VMatrix &xform ) +{ + if ( !( pBrush->contents & MASK_OPAQUE ) ) + return; + + Vector v0, v1, v2; + for (int i = 0; i < pBrush->numsides; i++ ) + { + dbrushside_t *side = &dbrushsides[pBrush->firstside + i]; + dplane_t *plane = &dplanes[side->planenum]; + texinfo_t *tx = &texinfo[side->texinfo]; + winding_t *w = BaseWindingForPlane (plane->normal, plane->dist); + + if ( tx->flags & SURF_SKY || side->dispinfo ) + continue; + + for (int j=0 ; jnumsides && w; j++) + { + if (i == j) + continue; + dbrushside_t *pOtherSide = &dbrushsides[pBrush->firstside + j]; + if (pOtherSide->bevel) + continue; + plane = &dplanes[pOtherSide->planenum^1]; + ChopWindingInPlace (&w, plane->normal, plane->dist, 0); + } + if ( w ) + { + for ( int j = 2; j < w->numpoints; j++ ) + { + v0 = xform.VMul4x3(w->p[0]); + v1 = xform.VMul4x3(w->p[j-1]); + v2 = xform.VMul4x3(w->p[j]); + Vector fullCoverage; + fullCoverage.x = 1.0f; + g_RtEnv.AddTriangle(TRACE_ID_OPAQUE, v0, v1, v2, fullCoverage); + } + FreeWinding( w ); + } + } +} + + +// recurse the bsp and build a list of brushes at the leaves under this node +void GetBrushes_r( int node, CUtlVector &list ) +{ + if ( node < 0 ) + { + int leafIndex = -1 - node; + // Add the solids in the leaf + for ( int i = 0; i < dleafs[leafIndex].numleafbrushes; i++ ) + { + int brushIndex = dleafbrushes[dleafs[leafIndex].firstleafbrush + i]; + if ( list.Find(brushIndex) < 0 ) + { + list.AddToTail( brushIndex ); + } + } + } + else + { + // recurse + dnode_t *pnode = dnodes + node; + + GetBrushes_r( pnode->children[0], list ); + GetBrushes_r( pnode->children[1], list ); + } +} + + +void AddBrushes( dmodel_t *pModel, const VMatrix &xform ) +{ + if ( pModel ) + { + CUtlVector brushList; + GetBrushes_r( pModel->headnode, brushList ); + for ( int i = 0; i < brushList.Count(); i++ ) + { + int ndxBrush = brushList[i]; + AddBrushToRaytraceEnvironment( &dbrushes[ndxBrush], xform ); + } + } +} + + +// Adds the brush entities that cast shadows to the raytrace environment +void ExtractBrushEntityShadowCasters() +{ + for ( int i = 0; i < num_entities; i++ ) + { + if ( IntForKey( &entities[i], "vrad_brush_cast_shadows" ) != 0 ) + { + Vector origin; + QAngle angles; + GetVectorForKey( &entities[i], "origin", origin ); + GetAnglesForKey( &entities[i], "angles", angles ); + VMatrix xform; + xform.SetupMatrixOrgAngles( origin, angles ); + AddBrushes( BrushmodelForEntity( &entities[i] ), xform ); + } + } +} + +void AddBrushesForRayTrace( void ) +{ + if ( !nummodels ) + return; + + VMatrix identity; + identity.Identity(); + + CUtlVector brushList; + GetBrushes_r ( dmodels[0].headnode, brushList ); + + for ( int i = 0; i < brushList.Size(); i++ ) + { + dbrush_t *brush = &dbrushes[brushList[i]]; + AddBrushToRaytraceEnvironment ( brush, identity ); + } + + for ( int i = 0; i < dmodels[0].numfaces; i++ ) + { + int ndxFace = dmodels[0].firstface + i; + dface_t *face = &g_pFaces[ndxFace]; + + texinfo_t *tx = &texinfo[face->texinfo]; + if ( !( tx->flags & SURF_SKY ) ) + continue; + + Vector points[MAX_POINTS_ON_WINDING]; + + for ( int j = 0; j < face->numedges; j++ ) + { + int surfEdge = dsurfedges[face->firstedge + j]; + short v; + + if (surfEdge < 0) + v = dedges[-surfEdge].v[1]; + else + v = dedges[surfEdge].v[0]; + + dvertex_t *dv = &dvertexes[v]; + points[j] = dv->point; + } + + for ( int j = 2; j < face->numedges; j++ ) + { + Vector fullCoverage; + fullCoverage.x = 1.0f; + g_RtEnv.AddTriangle ( TRACE_ID_SKY, points[0], points[j - 1], points[j], fullCoverage ); + } + } +} diff --git a/mp/src/utils/vrad/vismat.cpp b/mp/src/utils/vrad/vismat.cpp new file mode 100644 index 00000000..7491f69f --- /dev/null +++ b/mp/src/utils/vrad/vismat.cpp @@ -0,0 +1,483 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +#include "vrad.h" +#include "vmpi.h" +#ifdef MPI +#include "messbuf.h" +static MessageBuffer mb; +#endif + +#define HALFBIT + +extern char source[MAX_PATH]; +extern char vismatfile[_MAX_PATH]; +extern char incrementfile[_MAX_PATH]; +extern qboolean incremental; + +/* +=================================================================== + +VISIBILITY MATRIX + +Determine which patches can see each other +Use the PVS to accelerate if available +=================================================================== +*/ + +#define TEST_EPSILON 0.1 +#define PLANE_TEST_EPSILON 0.01 // patch must be this much in front of the plane to be considered "in front" +#define PATCH_FACE_OFFSET 0.1 // push patch origins off from the face by this amount to avoid self collisions + +#define STREAM_SIZE 512 + +class CTransferMaker +{ +public: + + CTransferMaker( transfer_t *all_transfers ); + ~CTransferMaker(); + + FORCEINLINE void TestMakeTransfer( Vector start, Vector stop, int ndxShooter, int ndxReciever ) + { + g_RtEnv.AddToRayStream( m_RayStream, start, stop, &m_pResults[m_nTests] ); + m_pShooterPatches[m_nTests] = ndxShooter; + m_pRecieverPatches[m_nTests] = ndxReciever; + ++m_nTests; + } + + void Finish(); + +private: + + int m_nTests; + RayTracingSingleResult *m_pResults; + int *m_pShooterPatches; + int *m_pRecieverPatches; + RayStream m_RayStream; + transfer_t *m_AllTransfers; +}; + +CTransferMaker::CTransferMaker( transfer_t *all_transfers ) : + m_AllTransfers( all_transfers ), m_nTests( 0 ) +{ + m_pResults = (RayTracingSingleResult *)calloc( 1, MAX_PATCHES * sizeof ( RayTracingSingleResult ) ); + m_pShooterPatches = (int *)calloc( 1, MAX_PATCHES * sizeof( int ) ); + m_pRecieverPatches = (int *)calloc( 1, MAX_PATCHES * sizeof( int ) ); +} + +CTransferMaker::~CTransferMaker() +{ + free ( m_pResults ); + free ( m_pShooterPatches ); + free (m_pRecieverPatches ); +} + +void CTransferMaker::Finish() +{ + g_RtEnv.FinishRayStream( m_RayStream ); + for ( int i = 0; i < m_nTests; ++i ) + { + if ( m_pResults[i].HitID == -1 || m_pResults[i].HitDistance >= m_pResults[i].ray_length ) + { + MakeTransfer( m_pShooterPatches[i], m_pRecieverPatches[i], m_AllTransfers ); + } + } + m_nTests = 0; +} + + +dleaf_t* PointInLeaf (int iNode, Vector const& point) +{ + if ( iNode < 0 ) + return &dleafs[ (-1-iNode) ]; + + dnode_t *node = &dnodes[iNode]; + dplane_t *plane = &dplanes[ node->planenum ]; + + float dist = DotProduct (point, plane->normal) - plane->dist; + if ( dist > TEST_EPSILON ) + { + return PointInLeaf( node->children[0], point ); + } + else if ( dist < -TEST_EPSILON ) + { + return PointInLeaf( node->children[1], point ); + } + else + { + dleaf_t *pTest = PointInLeaf( node->children[0], point ); + if ( pTest->cluster != -1 ) + return pTest; + + return PointInLeaf( node->children[1], point ); + } +} + + +int ClusterFromPoint( Vector const& point ) +{ + dleaf_t *leaf = PointInLeaf( 0, point ); + + return leaf->cluster; +} + +void PvsForOrigin (Vector& org, byte *pvs) +{ + int visofs; + int cluster; + + if (!visdatasize) + { + memset (pvs, 255, (dvis->numclusters+7)/8 ); + return; + } + + cluster = ClusterFromPoint( org ); + if ( cluster < 0 ) + { + visofs = -1; + } + else + { + visofs = dvis->bitofs[ cluster ][DVIS_PVS]; + } + + if (visofs == -1) + Error ("visofs == -1"); + + DecompressVis (&dvisdata[visofs], pvs); +} + + +void TestPatchToPatch( int ndxPatch1, int ndxPatch2, int head, transfer_t *transfers, CTransferMaker &transferMaker, int iThread ) +{ + Vector tmp; + + // + // get patches + // + if( ndxPatch1 == g_Patches.InvalidIndex() || ndxPatch2 == g_Patches.InvalidIndex() ) + return; + + CPatch *patch = &g_Patches.Element( ndxPatch1 ); + CPatch *patch2 = &g_Patches.Element( ndxPatch2 ); + + if (patch2->child1 != g_Patches.InvalidIndex() ) + { + // check to see if we should use a child node instead + + VectorSubtract( patch->origin, patch2->origin, tmp ); + // SQRT( 1/4 ) + // FIXME: should be based on form-factor (ie. include visible angle, etc) + if ( DotProduct(tmp, tmp) * 0.0625 < patch2->area ) + { + TestPatchToPatch( ndxPatch1, patch2->child1, head, transfers, transferMaker, iThread ); + TestPatchToPatch( ndxPatch1, patch2->child2, head, transfers, transferMaker, iThread ); + return; + } + } + + // check vis between patch and patch2 + // if bit has not already been set + // && v2 is not behind light plane + // && v2 is visible from v1 + if ( DotProduct( patch2->origin, patch->normal ) > patch->planeDist + PLANE_TEST_EPSILON ) + { + // push out origins from face so that don't intersect their owners + Vector p1, p2; + VectorAdd( patch->origin, patch->normal, p1 ); + VectorAdd( patch2->origin, patch2->normal, p2 ); + transferMaker.TestMakeTransfer( p1, p2, ndxPatch1, ndxPatch2 ); + } +} + + +/* +============== +TestPatchToFace + +Sets vis bits for all patches in the face +============== +*/ +void TestPatchToFace (unsigned patchnum, int facenum, int head, transfer_t *transfers, CTransferMaker &transferMaker, int iThread ) +{ + if( faceParents.Element( facenum ) == g_Patches.InvalidIndex() || patchnum == g_Patches.InvalidIndex() ) + return; + + CPatch *patch = &g_Patches.Element( patchnum ); + CPatch *patch2 = &g_Patches.Element( faceParents.Element( facenum ) ); + + // if emitter is behind that face plane, skip all patches + + CPatch *pNextPatch; + + if ( patch2 && DotProduct(patch->origin, patch2->normal) > patch2->planeDist + PLANE_TEST_EPSILON ) + { + // we need to do a real test + for( ; patch2; patch2 = pNextPatch ) + { + // next patch + pNextPatch = NULL; + if( patch2->ndxNextParent != g_Patches.InvalidIndex() ) + { + pNextPatch = &g_Patches.Element( patch2->ndxNextParent ); + } + + /* + // skip patches too far away + VectorSubtract( patch->origin, patch2->origin, tmp ); + if (DotProduct( tmp, tmp ) > 512 * 512) + continue; + */ + + int ndxPatch2 = patch2 - g_Patches.Base(); + TestPatchToPatch( patchnum, ndxPatch2, head, transfers, transferMaker, iThread ); + } + } +} + + +struct ClusterDispList_t +{ + CUtlVector dispFaces; +}; + +static CUtlVector g_ClusterDispFaces; + +//----------------------------------------------------------------------------- +// Helps us find all displacements associated with a particular cluster +//----------------------------------------------------------------------------- +void AddDispsToClusterTable( void ) +{ + g_ClusterDispFaces.SetCount( g_ClusterLeaves.Count() ); + + // + // add displacement faces to the cluster table + // + for( int ndxFace = 0; ndxFace < numfaces; ndxFace++ ) + { + // search for displacement faces + if( g_pFaces[ndxFace].dispinfo == -1 ) + continue; + + // + // get the clusters associated with the face + // + if( g_FacePatches.Element( ndxFace ) != g_FacePatches.InvalidIndex() ) + { + CPatch *pNextPatch = NULL; + for( CPatch *pPatch = &g_Patches.Element( g_FacePatches.Element( ndxFace ) ); pPatch; pPatch = pNextPatch ) + { + // next patch + pNextPatch = NULL; + if( pPatch->ndxNext != g_Patches.InvalidIndex() ) + { + pNextPatch = &g_Patches.Element( pPatch->ndxNext ); + } + + if( pPatch->clusterNumber != g_Patches.InvalidIndex() ) + { + int ndxDisp = g_ClusterDispFaces[pPatch->clusterNumber].dispFaces.Find( ndxFace ); + if( ndxDisp == -1 ) + { + ndxDisp = g_ClusterDispFaces[pPatch->clusterNumber].dispFaces.AddToTail(); + g_ClusterDispFaces[pPatch->clusterNumber].dispFaces[ndxDisp] = ndxFace; + } + } + } + } + } +} + + +/* +============== +BuildVisRow + +Calc vis bits from a single patch +============== +*/ +void BuildVisRow (int patchnum, byte *pvs, int head, transfer_t *transfers, CTransferMaker &transferMaker, int iThread ) +{ + int j, k, l, leafIndex; + CPatch *patch; + dleaf_t *leaf; + byte face_tested[MAX_MAP_FACES]; + byte disp_tested[MAX_MAP_FACES]; + + patch = &g_Patches.Element( patchnum ); + + memset( face_tested, 0, numfaces ) ; + memset( disp_tested, 0, numfaces ); + + for (j=0; jnumclusters; j++) + { + if ( ! ( pvs[(j)>>3] & (1<<((j)&7)) ) ) + { + continue; // not in pvs + } + + for ( leafIndex = 0; leafIndex < g_ClusterLeaves[j].leafCount; leafIndex++ ) + { + leaf = dleafs + g_ClusterLeaves[j].leafs[leafIndex]; + + for (k=0 ; knumleaffaces; k++) + { + l = dleaffaces[leaf->firstleafface + k]; + // faces can be marksurfed by multiple leaves, but + // don't bother testing again + if (face_tested[l]) + { + continue; + } + face_tested[l] = 1; + + // don't check patches on the same face + if (patch->faceNumber == l) + continue; + TestPatchToFace (patchnum, l, head, transfers, transferMaker, iThread ); + } + } + + int dispCount = g_ClusterDispFaces[j].dispFaces.Size(); + for( int ndxDisp = 0; ndxDisp < dispCount; ndxDisp++ ) + { + int ndxFace = g_ClusterDispFaces[j].dispFaces[ndxDisp]; + if( disp_tested[ndxFace] ) + continue; + + disp_tested[ndxFace] = 1; + + // don't check patches on the same face + if( patch->faceNumber == ndxFace ) + continue; + + TestPatchToFace( patchnum, ndxFace, head, transfers, transferMaker, iThread ); + } + } + + + // Msg("%d) Transfers: %5d\n", patchnum, patch->numtransfers); +} + + + +/* +=========== +BuildVisLeafs + + This is run by multiple threads +=========== +*/ + +transfer_t* BuildVisLeafs_Start() +{ + return (transfer_t *)calloc( 1, MAX_PATCHES * sizeof( transfer_t ) ); +} + + +// If PatchCB is non-null, it is called after each row is generated (used by MPI). +void BuildVisLeafs_Cluster( + int threadnum, + transfer_t *transfers, + int iCluster, + void (*PatchCB)(int iThread, int patchnum, CPatch *patch) + ) +{ + byte pvs[(MAX_MAP_CLUSTERS+7)/8]; + CPatch *patch; + int head; + unsigned patchnum; + + DecompressVis( &dvisdata[ dvis->bitofs[ iCluster ][DVIS_PVS] ], pvs); + head = 0; + + CTransferMaker transferMaker( transfers ); + + // light every patch in the cluster + if( clusterChildren.Element( iCluster ) != clusterChildren.InvalidIndex() ) + { + CPatch *pNextPatch; + for( patch = &g_Patches.Element( clusterChildren.Element( iCluster ) ); patch; patch = pNextPatch ) + { + // + // next patch + // + pNextPatch = NULL; + if( patch->ndxNextClusterChild != g_Patches.InvalidIndex() ) + { + pNextPatch = &g_Patches.Element( patch->ndxNextClusterChild ); + } + + patchnum = patch - g_Patches.Base(); + + // build to all other world clusters + BuildVisRow (patchnum, pvs, head, transfers, transferMaker, threadnum ); + transferMaker.Finish(); + + // do the transfers + MakeScales( patchnum, transfers ); + + // Let MPI aggregate the data if it's being used. + if ( PatchCB ) + PatchCB( threadnum, patchnum, patch ); + } + } +} + + +void BuildVisLeafs_End( transfer_t *transfers ) +{ + free( transfers ); +} + + +void BuildVisLeafs( int threadnum, void *pUserData ) +{ + transfer_t *transfers = BuildVisLeafs_Start(); + + while ( 1 ) + { + // + // build a minimal BSP tree that only + // covers areas relevent to the PVS + // + // JAY: Now this returns a cluster index + int iCluster = GetThreadWork(); + if ( iCluster == -1 ) + break; + + BuildVisLeafs_Cluster( threadnum, transfers, iCluster, NULL ); + } + + BuildVisLeafs_End( transfers ); +} + + +/* +============== +BuildVisMatrix +============== +*/ +void BuildVisMatrix (void) +{ + if ( g_bUseMPI ) + { + RunMPIBuildVisLeafs(); + } + else + { + RunThreadsOn (dvis->numclusters, true, BuildVisLeafs); + } +} + +void FreeVisMatrix (void) +{ + +} diff --git a/mp/src/utils/vrad/vismat.h b/mp/src/utils/vrad/vismat.h new file mode 100644 index 00000000..74e0ba4c --- /dev/null +++ b/mp/src/utils/vrad/vismat.h @@ -0,0 +1,34 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef VISMAT_H +#define VISMAT_H +#ifdef _WIN32 +#pragma once +#endif + + + +void BuildVisLeafs( int threadnum ); + + +// MPI uses these. +struct transfer_t; +transfer_t* BuildVisLeafs_Start(); + +// If PatchCB is non-null, it is called after each row is generated (used by MPI). +void BuildVisLeafs_Cluster( + int threadnum, + transfer_t *transfers, + int iCluster, + void (*PatchCB)(int iThread, int patchnum, CPatch *patch) ); + +void BuildVisLeafs_End( transfer_t *transfers ); + + + +#endif // VISMAT_H diff --git a/mp/src/utils/vrad/vrad.cpp b/mp/src/utils/vrad/vrad.cpp new file mode 100644 index 00000000..854956ec --- /dev/null +++ b/mp/src/utils/vrad/vrad.cpp @@ -0,0 +1,2936 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +// vrad.c + +#include "vrad.h" +#include "physdll.h" +#include "lightmap.h" +#include "tier1/strtools.h" +#include "vmpi.h" +#include "macro_texture.h" +#include "vmpi_tools_shared.h" +#include "leaf_ambient_lighting.h" +#include "tools_minidump.h" +#include "loadcmdline.h" +#include "byteswap.h" + +#define ALLOWDEBUGOPTIONS (0 || _DEBUG) + +static FileHandle_t pFpTrans = NULL; + +/* + +NOTES +----- + +every surface must be divided into at least two patches each axis + +*/ + +CUtlVector g_Patches; +CUtlVector g_FacePatches; // constains all patches, children first +CUtlVector faceParents; // contains only root patches, use next parent to iterate +CUtlVector clusterChildren; +CUtlVector emitlight; +CUtlVector addlight; + +int num_sky_cameras; +sky_camera_t sky_cameras[MAX_MAP_AREAS]; +int area_sky_cameras[MAX_MAP_AREAS]; + +entity_t *face_entity[MAX_MAP_FACES]; +Vector face_offset[MAX_MAP_FACES]; // for rotating bmodels +int fakeplanes; + +unsigned numbounce = 100; // 25; /* Originally this was 8 */ + +float maxchop = 4; // coarsest allowed number of luxel widths for a patch +float minchop = 4; // "-chop" tightest number of luxel widths for a patch, used on edges +float dispchop = 8.0f; // number of luxel widths for a patch +float g_MaxDispPatchRadius = 1500.0f; // Maximum radius allowed for displacement patches +qboolean g_bDumpPatches; +bool bDumpNormals = false; +bool g_bDumpRtEnv = false; +bool bRed2Black = true; +bool g_bFastAmbient = false; +bool g_bNoSkyRecurse = false; + +int junk; + +Vector ambient( 0, 0, 0 ); + +float lightscale = 1.0; +float dlight_threshold = 0.1; // was DIRECT_LIGHT constant + +char source[MAX_PATH] = ""; +char platformPath[MAX_PATH] = ""; + +char level_name[MAX_PATH] = ""; // map filename, without extension or path info + +char global_lights[MAX_PATH] = ""; +char designer_lights[MAX_PATH] = ""; +char level_lights[MAX_PATH] = ""; + +char vismatfile[_MAX_PATH] = ""; +char incrementfile[_MAX_PATH] = ""; + +IIncremental *g_pIncremental = 0; +bool g_bInterrupt = false; // Wsed with background lighting in WC. Tells VRAD + // to stop lighting. +float g_SunAngularExtent=0.0; + +float g_flSkySampleScale = 1.0; + +bool g_bLargeDispSampleRadius = false; + +bool g_bOnlyStaticProps = false; +bool g_bShowStaticPropNormals = false; + + +float gamma = 0.5; +float indirect_sun = 1.0; +float reflectivityScale = 1.0; +qboolean do_extra = true; +bool debug_extra = false; +qboolean do_fast = false; +qboolean do_centersamples = false; +int extrapasses = 4; +float smoothing_threshold = 0.7071067; // cos(45.0*(M_PI/180)) +// Cosine of smoothing angle(in radians) +float coring = 1.0; // Light threshold to force to blackness(minimizes lightmaps) +qboolean texscale = true; +int dlight_map = 0; // Setting to 1 forces direct lighting into different lightmap than radiosity + +float luxeldensity = 1.0; +unsigned num_degenerate_faces; + +qboolean g_bLowPriority = false; +qboolean g_bLogHashData = false; +bool g_bNoDetailLighting = false; +double g_flStartTime; +bool g_bStaticPropLighting = false; +bool g_bStaticPropPolys = false; +bool g_bTextureShadows = false; +bool g_bDisablePropSelfShadowing = false; + + +CUtlVector g_FacesVisibleToLights; + +RayTracingEnvironment g_RtEnv; + +dface_t *g_pFaces=0; + +// this is a list of material names used on static props which shouldn't cast shadows. a +// sequential search is used since we allow substring matches. its not time critical, and this +// functionality is a stopgap until vrad starts reading .vmt files. +CUtlVector g_NonShadowCastingMaterialStrings; +/* +=================================================================== + +MISC + +=================================================================== +*/ + + +int leafparents[MAX_MAP_LEAFS]; +int nodeparents[MAX_MAP_NODES]; + +void MakeParents (int nodenum, int parent) +{ + int i, j; + dnode_t *node; + + nodeparents[nodenum] = parent; + node = &dnodes[nodenum]; + + for (i=0 ; i<2 ; i++) + { + j = node->children[i]; + if (j < 0) + leafparents[-j - 1] = nodenum; + else + MakeParents (j, nodenum); + } +} + + +/* +=================================================================== + + TEXTURE LIGHT VALUES + +=================================================================== +*/ + +typedef struct +{ + char name[256]; + Vector value; + char *filename; +} texlight_t; + +#define MAX_TEXLIGHTS 128 + +texlight_t texlights[MAX_TEXLIGHTS]; +int num_texlights; + +/* +============ +ReadLightFile +============ +*/ +void ReadLightFile (char *filename) +{ + char buf[1024]; + int file_texlights = 0; + + FileHandle_t f = g_pFileSystem->Open( filename, "r" ); + if (!f) + { + Warning("Warning: Couldn't open texlight file %s.\n", filename); + return; + } + + Msg("[Reading texlights from '%s']\n", filename); + while ( CmdLib_FGets( buf, sizeof( buf ), f ) ) + { + // check ldr/hdr + char * scan = buf; + if ( !strnicmp( "hdr:", scan, 4) ) + { + scan += 4; + if ( ! g_bHDR ) + { + continue; + } + } + if ( !strnicmp( "ldr:", scan, 4) ) + { + scan += 4; + if ( g_bHDR ) + { + continue; + } + } + + scan += strspn( scan, " \t" ); + char NoShadName[1024]; + if ( sscanf(scan,"noshadow %s",NoShadName)==1) + { + char * dot = strchr( NoShadName, '.' ); + if ( dot ) // if they specify .vmt, kill it + * dot = 0; + //printf("add %s as a non shadow casting material\n",NoShadName); + g_NonShadowCastingMaterialStrings.AddToTail( strdup( NoShadName )); + } + else if ( sscanf( scan, "forcetextureshadow %s", NoShadName ) == 1 ) + { + //printf("add %s as a non shadow casting material\n",NoShadName); + ForceTextureShadowsOnModel( NoShadName ); + } + else + { + char szTexlight[256]; + Vector value; + if ( num_texlights == MAX_TEXLIGHTS ) + Error ("Too many texlights, max = %d", MAX_TEXLIGHTS); + + int argCnt = sscanf (scan, "%s ",szTexlight ); + + if( argCnt != 1 ) + { + if ( strlen( scan ) > 4 ) + Msg( "ignoring bad texlight '%s' in %s", scan, filename ); + continue; + } + + LightForString( scan + strlen( szTexlight ) + 1, value ); + + int j = 0; + for( j; j < num_texlights; j ++ ) + { + if ( strcmp( texlights[j].name, szTexlight ) == 0 ) + { + if ( strcmp( texlights[j].filename, filename ) == 0 ) + { + Msg( "ERROR\a: Duplication of '%s' in file '%s'!\n", + texlights[j].name, texlights[j].filename ); + } + else if ( texlights[j].value[0] != value[0] + || texlights[j].value[1] != value[1] + || texlights[j].value[2] != value[2] ) + { + Warning( "Warning: Overriding '%s' from '%s' with '%s'!\n", + texlights[j].name, texlights[j].filename, filename ); + } + else + { + Warning( "Warning: Redundant '%s' def in '%s' AND '%s'!\n", + texlights[j].name, texlights[j].filename, filename ); + } + break; + } + } + strcpy( texlights[j].name, szTexlight ); + VectorCopy( value, texlights[j].value ); + texlights[j].filename = filename; + file_texlights ++; + + num_texlights = max( num_texlights, j + 1 ); + } + } + qprintf ( "[%i texlights parsed from '%s']\n\n", file_texlights, filename); + g_pFileSystem->Close( f ); +} + + +/* +============ +LightForTexture +============ +*/ +void LightForTexture( const char *name, Vector& result ) +{ + int i; + + result[ 0 ] = result[ 1 ] = result[ 2 ] = 0; + + char baseFilename[ MAX_PATH ]; + + if ( Q_strncmp( "maps/", name, 5 ) == 0 ) + { + // this might be a patch texture for cubemaps. try to parse out the original filename. + if ( Q_strncmp( level_name, name + 5, Q_strlen( level_name ) ) == 0 ) + { + const char *base = name + 5 + Q_strlen( level_name ); + if ( *base == '/' ) + { + ++base; // step past the path separator + + // now we've gotten rid of the 'maps/level_name/' part, so we're left with + // 'originalName_%d_%d_%d'. + strcpy( baseFilename, base ); + bool foundSeparators = true; + for ( int i=0; i<3; ++i ) + { + char *underscore = Q_strrchr( baseFilename, '_' ); + if ( underscore && *underscore ) + { + *underscore = '\0'; + } + else + { + foundSeparators = false; + } + } + + if ( foundSeparators ) + { + name = baseFilename; + } + } + } + } + + for (i=0 ; inumedges); + w->numpoints = f->numedges; + + for (i=0 ; inumedges ; i++) + { + se = dsurfedges[f->firstedge + i]; + if (se < 0) + v = dedges[-se].v[1]; + else + v = dedges[se].v[0]; + + dv = &dvertexes[v]; + VectorAdd (dv->point, origin, w->p[i]); + } + + RemoveColinearPoints (w); + + return w; +} + +/* +============= +BaseLightForFace +============= +*/ +void BaseLightForFace( dface_t *f, Vector& light, float *parea, Vector& reflectivity ) +{ + texinfo_t *tx; + dtexdata_t *texdata; + + // + // check for light emited by texture + // + tx = &texinfo[f->texinfo]; + texdata = &dtexdata[tx->texdata]; + + LightForTexture (TexDataStringTable_GetString( texdata->nameStringTableID ), light); + + + *parea = texdata->height * texdata->width; + + VectorScale( texdata->reflectivity, reflectivityScale, reflectivity ); + + // always keep this less than 1 or the solution will not converge + for ( int i = 0; i < 3; i++ ) + { + if ( reflectivity[i] > 0.99 ) + reflectivity[i] = 0.99; + } +} + +qboolean IsSky (dface_t *f) +{ + texinfo_t *tx; + + tx = &texinfo[f->texinfo]; + if (tx->flags & SURF_SKY) + return true; + return false; +} + +#ifdef STATIC_FOG +/*============= +IsFog +=============*/ +qboolean IsFog( dface_t *f ) +{ + texinfo_t *tx; + + tx = &texinfo[f->texinfo]; + + // % denotes a fog texture + if( tx->texture[0] == '%' ) + return true; + + return false; +} +#endif + + +void ProcessSkyCameras() +{ + int i; + num_sky_cameras = 0; + for (i = 0; i < numareas; ++i) + { + area_sky_cameras[i] = -1; + } + + for (i = 0; i < num_entities; ++i) + { + entity_t *e = &entities[i]; + const char *name = ValueForKey (e, "classname"); + if (stricmp (name, "sky_camera")) + continue; + + Vector origin; + GetVectorForKey( e, "origin", origin ); + int node = PointLeafnum( origin ); + int area = -1; + if (node >= 0 && node < numleafs) area = dleafs[node].area; + float scale = FloatForKey( e, "scale" ); + + if (scale > 0.0f) + { + sky_cameras[num_sky_cameras].origin = origin; + sky_cameras[num_sky_cameras].sky_to_world = scale; + sky_cameras[num_sky_cameras].world_to_sky = 1.0f / scale; + sky_cameras[num_sky_cameras].area = area; + + if (area >= 0 && area < numareas) + { + area_sky_cameras[area] = num_sky_cameras; + } + + ++num_sky_cameras; + } + } + +} + + +/* +============= +MakePatchForFace +============= +*/ +float totalarea; +void MakePatchForFace (int fn, winding_t *w) +{ + dface_t *f = g_pFaces + fn; + float area; + CPatch *patch; + Vector centroid(0,0,0); + int i, j; + texinfo_t *tx; + + // get texture info + tx = &texinfo[f->texinfo]; + + // No patches at all for fog! +#ifdef STATIC_FOG + if ( IsFog( f ) ) + return; +#endif + + // the sky needs patches or the form factors don't work out correctly + // if (IsSky( f ) ) + // return; + + area = WindingArea (w); + if (area <= 0) + { + num_degenerate_faces++; + // Msg("degenerate face\n"); + return; + } + + totalarea += area; + + // get a patch + int ndxPatch = g_Patches.AddToTail(); + patch = &g_Patches[ndxPatch]; + memset( patch, 0, sizeof( CPatch ) ); + patch->ndxNext = g_Patches.InvalidIndex(); + patch->ndxNextParent = g_Patches.InvalidIndex(); + patch->ndxNextClusterChild = g_Patches.InvalidIndex(); + patch->child1 = g_Patches.InvalidIndex(); + patch->child2 = g_Patches.InvalidIndex(); + patch->parent = g_Patches.InvalidIndex(); + patch->needsBumpmap = tx->flags & SURF_BUMPLIGHT ? true : false; + + // link and save patch data + patch->ndxNext = g_FacePatches.Element( fn ); + g_FacePatches[fn] = ndxPatch; +// patch->next = face_g_Patches[fn]; +// face_g_Patches[fn] = patch; + + // compute a separate scale for chop - since the patch "scale" is the texture scale + // we want textures with higher resolution lighting to be chopped up more + float chopscale[2]; + chopscale[0] = chopscale[1] = 16.0f; + if ( texscale ) + { + // Compute the texture "scale" in s,t + for( i=0; i<2; i++ ) + { + patch->scale[i] = 0.0f; + chopscale[i] = 0.0f; + for( j=0; j<3; j++ ) + { + patch->scale[i] += + tx->textureVecsTexelsPerWorldUnits[i][j] * + tx->textureVecsTexelsPerWorldUnits[i][j]; + chopscale[i] += + tx->lightmapVecsLuxelsPerWorldUnits[i][j] * + tx->lightmapVecsLuxelsPerWorldUnits[i][j]; + } + patch->scale[i] = sqrt( patch->scale[i] ); + chopscale[i] = sqrt( chopscale[i] ); + } + } + else + { + patch->scale[0] = patch->scale[1] = 1.0f; + } + + patch->area = area; + + patch->sky = IsSky( f ); + + // chop scaled up lightmaps coarser + patch->luxscale = ((chopscale[0]+chopscale[1])/2); + patch->chop = maxchop; + + +#ifdef STATIC_FOG + patch->fog = FALSE; +#endif + + patch->winding = w; + + patch->plane = &dplanes[f->planenum]; + + // make a new plane to adjust for origined bmodels + if (face_offset[fn][0] || face_offset[fn][1] || face_offset[fn][2] ) + { + dplane_t *pl; + + // origin offset faces must create new planes + if (numplanes + fakeplanes >= MAX_MAP_PLANES) + { + Error ("numplanes + fakeplanes >= MAX_MAP_PLANES"); + } + pl = &dplanes[numplanes + fakeplanes]; + fakeplanes++; + + *pl = *(patch->plane); + pl->dist += DotProduct (face_offset[fn], pl->normal); + patch->plane = pl; + } + + patch->faceNumber = fn; + WindingCenter (w, patch->origin); + + // Save "center" for generating the face normals later. + VectorSubtract( patch->origin, face_offset[fn], face_centroids[fn] ); + + VectorCopy( patch->plane->normal, patch->normal ); + + WindingBounds (w, patch->face_mins, patch->face_maxs); + VectorCopy( patch->face_mins, patch->mins ); + VectorCopy( patch->face_maxs, patch->maxs ); + + BaseLightForFace( f, patch->baselight, &patch->basearea, patch->reflectivity ); + + // Chop all texlights very fine. + if ( !VectorCompare( patch->baselight, vec3_origin ) ) + { + // patch->chop = do_extra ? maxchop / 2 : maxchop; + tx->flags |= SURF_LIGHT; + } + + // get rid of do extra functionality on displacement surfaces + if( ValidDispFace( f ) ) + { + patch->chop = maxchop; + } + + // FIXME: If we wanted to add a dependency from vrad to the material system, + // we could do this. It would add a bunch of file accesses, though: + + /* + // Check for a material var which would override the patch chop + bool bFound; + const char *pMaterialName = TexDataStringTable_GetString( dtexdata[ tx->texdata ].nameStringTableID ); + MaterialSystemMaterial_t hMaterial = FindMaterial( pMaterialName, &bFound, false ); + if ( bFound ) + { + const char *pChopValue = GetMaterialVar( hMaterial, "%chop" ); + if ( pChopValue ) + { + float flChopValue; + if ( sscanf( pChopValue, "%f", &flChopValue ) > 0 ) + { + patch->chop = flChopValue; + } + } + } + */ +} + + +entity_t *EntityForModel (int modnum) +{ + int i; + char *s; + char name[16]; + + sprintf (name, "*%i", modnum); + // search the entities for one using modnum + for (i=0 ; inumfaces ; j++) + { + fn = mod->firstface + j; + face_entity[fn] = ent; + VectorCopy (origin, face_offset[fn]); + f = &g_pFaces[fn]; + if( f->dispinfo == -1 ) + { + w = WindingFromFace (f, origin ); + MakePatchForFace( fn, w ); + } + } + } + + if (num_degenerate_faces > 0) + { + qprintf("%d degenerate faces\n", num_degenerate_faces ); + } + + qprintf ("%i square feet [%.2f square inches]\n", (int)(totalarea/144), totalarea ); + + // make the displacement surface patches + StaticDispMgr()->MakePatches(); +} + +/* +======================================================================= + +SUBDIVIDE + +======================================================================= +*/ + + +//----------------------------------------------------------------------------- +// Purpose: does this surface take/emit light +//----------------------------------------------------------------------------- +bool PreventSubdivision( CPatch *patch ) +{ + dface_t *f = g_pFaces + patch->faceNumber; + texinfo_t *tx = &texinfo[f->texinfo]; + + if (tx->flags & SURF_NOCHOP) + return true; + + if (tx->flags & SURF_NOLIGHT && !(tx->flags & SURF_LIGHT)) + return true; + + return false; +} + + +//----------------------------------------------------------------------------- +// Purpose: subdivide the "parent" patch +//----------------------------------------------------------------------------- +int CreateChildPatch( int nParentIndex, winding_t *pWinding, float flArea, const Vector &vecCenter ) +{ + int nChildIndex = g_Patches.AddToTail(); + + CPatch *child = &g_Patches[nChildIndex]; + CPatch *parent = &g_Patches[nParentIndex]; + + // copy all elements of parent patch to children + *child = *parent; + + // Set up links + child->ndxNext = g_Patches.InvalidIndex(); + child->ndxNextParent = g_Patches.InvalidIndex(); + child->ndxNextClusterChild = g_Patches.InvalidIndex(); + child->child1 = g_Patches.InvalidIndex(); + child->child2 = g_Patches.InvalidIndex(); + child->parent = nParentIndex; + child->m_IterationKey = 0; + + child->winding = pWinding; + child->area = flArea; + + VectorCopy( vecCenter, child->origin ); + if ( ValidDispFace( g_pFaces + child->faceNumber ) ) + { + // shouldn't get here anymore!! + Msg( "SubdividePatch: Error - Should not be here!\n" ); + StaticDispMgr()->GetDispSurfNormal( child->faceNumber, child->origin, child->normal, true ); + } + else + { + GetPhongNormal( child->faceNumber, child->origin, child->normal ); + } + + child->planeDist = child->plane->dist; + WindingBounds(child->winding, child->mins, child->maxs); + + if ( !VectorCompare( child->baselight, vec3_origin ) ) + { + // don't check edges on surf lights + return nChildIndex; + } + + // Subdivide patch towards minchop if on the edge of the face + Vector total; + VectorSubtract( child->maxs, child->mins, total ); + VectorScale( total, child->luxscale, total ); + if ( child->chop > minchop && (total[0] < child->chop) && (total[1] < child->chop) && (total[2] < child->chop) ) + { + for ( int i=0; i<3; ++i ) + { + if ( (child->face_maxs[i] == child->maxs[i] || child->face_mins[i] == child->mins[i] ) + && total[i] > minchop ) + { + child->chop = max( minchop, child->chop / 2 ); + break; + } + } + } + + return nChildIndex; +} + + +//----------------------------------------------------------------------------- +// Purpose: subdivide the "parent" patch +//----------------------------------------------------------------------------- +void SubdividePatch( int ndxPatch ) +{ + winding_t *w, *o1, *o2; + Vector total; + Vector split; + vec_t dist; + vec_t widest = -1; + int i, widest_axis = -1; + bool bSubdivide = false; + + // get the current patch + CPatch *patch = &g_Patches.Element( ndxPatch ); + if ( !patch ) + return; + + // never subdivide sky patches + if ( patch->sky ) + return; + + // get the patch winding + w = patch->winding; + + // subdivide along the widest axis + VectorSubtract (patch->maxs, patch->mins, total); + VectorScale( total, patch->luxscale, total ); + for (i=0 ; i<3 ; i++) + { + if ( total[i] > widest ) + { + widest_axis = i; + widest = total[i]; + } + + if ( (total[i] >= patch->chop) && (total[i] >= minchop) ) + { + bSubdivide = true; + } + } + + if ((!bSubdivide) && widest_axis != -1) + { + // make more square + if (total[widest_axis] > total[(widest_axis + 1) % 3] * 2 && total[widest_axis] > total[(widest_axis + 2) % 3] * 2) + { + if (patch->chop > minchop) + { + bSubdivide = true; + patch->chop = max( minchop, patch->chop / 2 ); + } + } + } + + if ( !bSubdivide ) + return; + + // split the winding + VectorCopy (vec3_origin, split); + split[widest_axis] = 1; + dist = (patch->mins[widest_axis] + patch->maxs[widest_axis])*0.5f; + ClipWindingEpsilon (w, split, dist, ON_EPSILON, &o1, &o2); + + // calculate the area of the patches to see if they are "significant" + Vector center1, center2; + float area1 = WindingAreaAndBalancePoint( o1, center1 ); + float area2 = WindingAreaAndBalancePoint( o2, center2 ); + + if( area1 == 0 || area2 == 0 ) + { + Msg( "zero area child patch\n" ); + return; + } + + // create new child patches + int ndxChild1Patch = CreateChildPatch( ndxPatch, o1, area1, center1 ); + int ndxChild2Patch = CreateChildPatch( ndxPatch, o2, area2, center2 ); + + // FIXME: This could go into CreateChildPatch if child1, child2 were stored in the patch as child[0], child[1] + patch = &g_Patches.Element( ndxPatch ); + patch->child1 = ndxChild1Patch; + patch->child2 = ndxChild2Patch; + + SubdividePatch( ndxChild1Patch ); + SubdividePatch( ndxChild2Patch ); +} + + +/* +============= +SubdividePatches +============= +*/ +void SubdividePatches (void) +{ + unsigned i, num; + + if (numbounce == 0) + return; + + unsigned int uiPatchCount = g_Patches.Size(); + qprintf ("%i patches before subdivision\n", uiPatchCount); + + for (i = 0; i < uiPatchCount; i++) + { + CPatch *pCur = &g_Patches.Element( i ); + pCur->planeDist = pCur->plane->dist; + + pCur->ndxNextParent = faceParents.Element( pCur->faceNumber ); + faceParents[pCur->faceNumber] = pCur - g_Patches.Base(); + } + + for (i=0 ; i< uiPatchCount; i++) + { + CPatch *patch = &g_Patches.Element( i ); + patch->parent = -1; + if ( PreventSubdivision(patch) ) + continue; + + if (!do_fast) + { + if( g_pFaces[patch->faceNumber].dispinfo == -1 ) + { + SubdividePatch( i ); + } + else + { + StaticDispMgr()->SubdividePatch( i ); + } + } + } + + // fixup next pointers + for (i = 0; i < (unsigned)numfaces; i++) + { + g_FacePatches[i] = g_FacePatches.InvalidIndex(); + } + + uiPatchCount = g_Patches.Size(); + for (i = 0; i < uiPatchCount; i++) + { + CPatch *pCur = &g_Patches.Element( i ); + pCur->ndxNext = g_FacePatches.Element( pCur->faceNumber ); + g_FacePatches[pCur->faceNumber] = pCur - g_Patches.Base(); + +#if 0 + CPatch *prev; + prev = face_g_Patches[g_Patches[i].faceNumber]; + g_Patches[i].next = prev; + face_g_Patches[g_Patches[i].faceNumber] = &g_Patches[i]; +#endif + } + + // Cache off the leaf number: + // We have to do this after subdivision because some patches span leaves. + // (only the faces for model #0 are split by it's BSP which is what governs the PVS, and the leaves we're interested in) + // Sub models (1-255) are only split for the BSP that their model forms. + // When those patches are subdivided their origins can end up in a different leaf. + // The engine will split (clip) those faces at run time to the world BSP because the models + // are dynamic and can be moved. In the software renderer, they must be split exactly in order + // to sort per polygon. + for ( i = 0; i < uiPatchCount; i++ ) + { + g_Patches[i].clusterNumber = ClusterFromPoint( g_Patches[i].origin ); + + // + // test for point in solid space (can happen with detail and displacement surfaces) + // + if( g_Patches[i].clusterNumber == -1 ) + { + for( int j = 0; j < g_Patches[i].winding->numpoints; j++ ) + { + int clusterNumber = ClusterFromPoint( g_Patches[i].winding->p[j] ); + if( clusterNumber != -1 ) + { + g_Patches[i].clusterNumber = clusterNumber; + break; + } + } + } + } + + // build the list of patches that need to be lit + for ( num = 0; num < uiPatchCount; num++ ) + { + // do them in reverse order + i = uiPatchCount - num - 1; + + // skip patches with children + CPatch *pCur = &g_Patches.Element( i ); + if( pCur->child1 == g_Patches.InvalidIndex() ) + { + if( pCur->clusterNumber != - 1 ) + { + pCur->ndxNextClusterChild = clusterChildren.Element( pCur->clusterNumber ); + clusterChildren[pCur->clusterNumber] = pCur - g_Patches.Base(); + } + } + +#if 0 + if (g_Patches[i].child1 == g_Patches.InvalidIndex() ) + { + if( g_Patches[i].clusterNumber != -1 ) + { + g_Patches[i].nextclusterchild = cluster_children[g_Patches[i].clusterNumber]; + cluster_children[g_Patches[i].clusterNumber] = &g_Patches[i]; + } + } +#endif + } + + qprintf ("%i patches after subdivision\n", uiPatchCount); +} + + +//===================================================================== + +/* +============= +MakeScales + + This is the primary time sink. + It can be run multi threaded. +============= +*/ +int total_transfer; +int max_transfer; + + +//----------------------------------------------------------------------------- +// Purpose: Computes the form factor from a polygon patch to a differential patch +// using formula 81 of Philip Dutre's Global Illumination Compendium, +// phil@graphics.cornell.edu, http://www.graphics.cornell.edu/~phil/GI/ +//----------------------------------------------------------------------------- +float FormFactorPolyToDiff ( CPatch *pPolygon, CPatch* pDifferential ) +{ + winding_t *pWinding = pPolygon->winding; + + float flFormFactor = 0.0f; + + for ( int iPoint = 0; iPoint < pWinding->numpoints; iPoint++ ) + { + int iNextPoint = ( iPoint < pWinding->numpoints - 1 ) ? iPoint + 1 : 0; + + Vector vGammaVector, vVector1, vVector2; + VectorSubtract( pWinding->p[ iPoint ], pDifferential->origin, vVector1 ); + VectorSubtract( pWinding->p[ iNextPoint ], pDifferential->origin, vVector2 ); + VectorNormalize( vVector1 ); + VectorNormalize( vVector2 ); + CrossProduct( vVector1, vVector2, vGammaVector ); + float flSinAlpha = VectorNormalize( vGammaVector ); + if (flSinAlpha < -1.0f || flSinAlpha > 1.0f) + return 0.0f; + vGammaVector *= asin( flSinAlpha ); + + flFormFactor += DotProduct( vGammaVector, pDifferential->normal ); + } + + flFormFactor *= ( 0.5f / pPolygon->area ); // divide by pi later, multiply by area later + + return flFormFactor; +} + + +//----------------------------------------------------------------------------- +// Purpose: Computes the form factor from a differential element to a differential +// element. This is okay when the distance between patches is 5 times +// greater than patch size. Lecture slides by Pat Hanrahan, +// http://graphics.stanford.edu/courses/cs348b-00/lectures/lecture17/radiosity.2.pdf +//----------------------------------------------------------------------------- +float FormFactorDiffToDiff ( CPatch *pDiff1, CPatch* pDiff2 ) +{ + Vector vDelta; + VectorSubtract( pDiff1->origin, pDiff2->origin, vDelta ); + float flLength = VectorNormalize( vDelta ); + + return -DotProduct( vDelta, pDiff1->normal ) * DotProduct( vDelta, pDiff2->normal ) / ( flLength * flLength ); +} + + + +void MakeTransfer( int ndxPatch1, int ndxPatch2, transfer_t *all_transfers ) +//void MakeTransfer (CPatch *patch, CPatch *patch2, transfer_t *all_transfers ) +{ + Vector delta; + vec_t scale; + float trans; + transfer_t *transfer; + + // + // get patches + // + if( ndxPatch1 == g_Patches.InvalidIndex() || ndxPatch2 == g_Patches.InvalidIndex() ) + return; + + CPatch *pPatch1 = &g_Patches.Element( ndxPatch1 ); + CPatch *pPatch2 = &g_Patches.Element( ndxPatch2 ); + + if (IsSky( &g_pFaces[ pPatch2->faceNumber ] ) ) + return; + + // overflow check! + if ( pPatch1->numtransfers >= MAX_PATCHES) + { + return; + } + + // hack for patch areas that area <= 0 (degenerate) + if ( pPatch2->area <= 0) + { + return; + } + + transfer = &all_transfers[pPatch1->numtransfers]; + + scale = FormFactorDiffToDiff( pPatch2, pPatch1 ); + + // patch normals may be > 90 due to smoothing groups + if (scale <= 0) + { + //Msg("scale <= 0\n"); + return; + } + + // Test 5 times rule + Vector vDelta; + VectorSubtract( pPatch1->origin, pPatch2->origin, vDelta ); + float flThreshold = ( M_PI * 0.04 ) * DotProduct( vDelta, vDelta ); + + if (flThreshold < pPatch2->area) + { + scale = FormFactorPolyToDiff( pPatch2, pPatch1 ); + if (scale <= 0.0) + return; + } + + trans = (pPatch2->area*scale); + + if (trans <= TRANSFER_EPSILON) + { + return; + } + + transfer->patch = pPatch2 - g_Patches.Base(); + + // FIXME: why is this not trans? + transfer->transfer = trans; + +#if 0 + // DEBUG! Dump patches and transfer connection for displacements. This creates a lot of data, so only + // use it when you really want it - that is why it is #if-ed out. + if ( g_bDumpPatches ) + { + if ( !pFpTrans ) + { + pFpTrans = g_pFileSystem->Open( "trans.txt", "w" ); + } + Vector light = pPatch1->totallight.light[0] + pPatch1->directlight; + WriteWinding( pFpTrans, pPatch1->winding, light ); + light = pPatch2->totallight.light[0] + pPatch2->directlight; + WriteWinding( pFpTrans, pPatch2->winding, light ); + WriteLine( pFpTrans, pPatch1->origin, pPatch2->origin, Vector( 255, 0, 255 ) ); + } +#endif + + pPatch1->numtransfers++; +} + + +void MakeScales ( int ndxPatch, transfer_t *all_transfers ) +{ + int j; + float total; + transfer_t *t, *t2; + total = 0; + + if( ndxPatch == g_Patches.InvalidIndex() ) + return; + CPatch *patch = &g_Patches.Element( ndxPatch ); + + // copy the transfers out + if (patch->numtransfers) + { + if (patch->numtransfers > max_transfer) + { + max_transfer = patch->numtransfers; + } + + + patch->transfers = ( transfer_t* )calloc (1, patch->numtransfers * sizeof(transfer_t)); + if (!patch->transfers) + Error ("Memory allocation failure"); + + // get total transfer energy + t2 = all_transfers; + + // overflow check! + for (j=0 ; jnumtransfers ; j++, t2++) + { + total += t2->transfer; + } + + // the total transfer should be PI, but we need to correct errors due to overlaping surfaces + if (total > M_PI) + total = 1.0f/total; + else + total = 1.0f/M_PI; + + t = patch->transfers; + t2 = all_transfers; + for (j=0 ; jnumtransfers ; j++, t++, t2++) + { + t->transfer = t2->transfer*total; + t->patch = t2->patch; + } + if (patch->numtransfers > max_transfer) + { + max_transfer = patch->numtransfers; + } + } + else + { + // Error - patch has no transfers + // patch->totallight[2] = 255; + } + + ThreadLock (); + total_transfer += patch->numtransfers; + ThreadUnlock (); +} + +/* +============= +WriteWorld +============= +*/ +void WriteWorld (char *name, int iBump) +{ + unsigned j; + FileHandle_t out; + CPatch *patch; + + out = g_pFileSystem->Open( name, "w" ); + if (!out) + Error ("Couldn't open %s", name); + + unsigned int uiPatchCount = g_Patches.Size(); + for (j=0; jchild1 != g_Patches.InvalidIndex() ) + continue; + + if( patch->clusterNumber == -1 ) + { + Vector vGreen; + VectorClear( vGreen ); + vGreen[1] = 256.0f; + WriteWinding( out, patch->winding, vGreen ); + } + else + { + Vector light = patch->totallight.light[iBump] + patch->directlight; + WriteWinding( out, patch->winding, light ); + if( bDumpNormals ) + { + WriteNormal( out, patch->origin, patch->plane->normal, 15.0f, patch->plane->normal * 255.0f ); + } + } + } + + g_pFileSystem->Close( out ); +} + +void WriteRTEnv (char *name) +{ + FileHandle_t out; + + out = g_pFileSystem->Open( name, "w" ); + if (!out) + Error ("Couldn't open %s", name); + + winding_t *triw = AllocWinding( 3 ); + triw->numpoints = 3; + + for( int i = 0; i < g_RtEnv.OptimizedTriangleList.Size(); i++ ) + { + triw->p[0] = g_RtEnv.OptimizedTriangleList[i].Vertex( 0); + triw->p[1] = g_RtEnv.OptimizedTriangleList[i].Vertex( 1); + triw->p[2] = g_RtEnv.OptimizedTriangleList[i].Vertex( 2); + int id = g_RtEnv.OptimizedTriangleList[i].m_Data.m_GeometryData.m_nTriangleID; + Vector color(0, 0, 0); + if (id & TRACE_ID_OPAQUE) color.Init(0, 255, 0); + if (id & TRACE_ID_SKY) color.Init(0, 0, 255); + if (id & TRACE_ID_STATICPROP) color.Init(255, 0, 0); + WriteWinding(out, triw, color); + } + FreeWinding(triw); + + g_pFileSystem->Close( out ); +} + +void WriteWinding (FileHandle_t out, winding_t *w, Vector& color ) +{ + int i; + + CmdLib_FPrintf (out, "%i\n", w->numpoints); + for (i=0 ; inumpoints ; i++) + { + CmdLib_FPrintf (out, "%5.2f %5.2f %5.2f %5.3f %5.3f %5.3f\n", + w->p[i][0], + w->p[i][1], + w->p[i][2], + color[ 0 ] / 256, + color[ 1 ] / 256, + color[ 2 ] / 256 ); + } +} + + +void WriteNormal( FileHandle_t out, Vector const &nPos, Vector const &nDir, + float length, Vector const &color ) +{ + CmdLib_FPrintf( out, "2\n" ); + CmdLib_FPrintf( out, "%5.2f %5.2f %5.2f %5.3f %5.3f %5.3f\n", + nPos.x, nPos.y, nPos.z, + color.x / 256, color.y / 256, color.z / 256 ); + CmdLib_FPrintf( out, "%5.2f %5.2f %5.2f %5.3f %5.3f %5.3f\n", + nPos.x + ( nDir.x * length ), + nPos.y + ( nDir.y * length ), + nPos.z + ( nDir.z * length ), + color.x / 256, color.y / 256, color.z / 256 ); +} + +void WriteLine( FileHandle_t out, const Vector &vecPos1, const Vector &vecPos2, const Vector &color ) +{ + CmdLib_FPrintf( out, "2\n" ); + CmdLib_FPrintf( out, "%5.2f %5.2f %5.2f %5.3f %5.3f %5.3f\n", + vecPos1.x, vecPos1.y, vecPos1.z, + color.x / 256, color.y / 256, color.z / 256 ); + CmdLib_FPrintf( out, "%5.2f %5.2f %5.2f %5.3f %5.3f %5.3f\n", + vecPos2.x, vecPos2.y, vecPos2.z, + color.x / 256, color.y / 256, color.z / 256 ); +} + +void WriteTrace( const char *pFileName, const FourRays &rays, const RayTracingResult& result ) +{ + FileHandle_t out; + + out = g_pFileSystem->Open( pFileName, "a" ); + if (!out) + Error ("Couldn't open %s", pFileName); + + // Draws rays + for ( int i = 0; i < 4; ++i ) + { + Vector vecOrigin = rays.origin.Vec(i); + Vector vecEnd = rays.direction.Vec(i); + VectorNormalize( vecEnd ); + vecEnd *= SubFloat( result.HitDistance, i ); + vecEnd += vecOrigin; + WriteLine( out, vecOrigin, vecEnd, Vector( 256, 0, 0 ) ); + WriteNormal( out, vecEnd, result.surface_normal.Vec(i), 10.0f, Vector( 256, 265, 0 ) ); + } + + g_pFileSystem->Close( out ); +} + + +/* +============= +CollectLight +============= +*/ +// patch's totallight += new light received to each patch +// patch's emitlight = addlight (newly received light from GatherLight) +// patch's addlight = 0 +// pull received light from children. +void CollectLight( Vector& total ) +{ + int i, j; + CPatch *patch; + + VectorFill( total, 0 ); + + // process patches in reverse order so that children are processed before their parents + unsigned int uiPatchCount = g_Patches.Size(); + for( i = uiPatchCount - 1; i >= 0; i-- ) + { + patch = &g_Patches.Element( i ); + int normalCount = patch->needsBumpmap ? NUM_BUMP_VECTS+1 : 1; + // sky's never collect light, it is just dropped + if (patch->sky) + { + VectorFill( emitlight[ i ], 0 ); + } + else if ( patch->child1 == g_Patches.InvalidIndex() ) + { + // This is a leaf node. + for ( j = 0; j < normalCount; j++ ) + { + VectorAdd( patch->totallight.light[j], addlight[i].light[j], patch->totallight.light[j] ); + } + VectorCopy( addlight[i].light[0], emitlight[i] ); + VectorAdd( total, emitlight[i], total ); + } + else + { + // This is an interior node. + // Pull received light from children. + float s1, s2; + CPatch *child1; + CPatch *child2; + + child1 = &g_Patches[patch->child1]; + child2 = &g_Patches[patch->child2]; + + // BUG: This doesn't do anything? + if ((int)patch->area != (int)(child1->area + child2->area)) + s1 = 0; + + s1 = child1->area / (child1->area + child2->area); + s2 = child2->area / (child1->area + child2->area); + + // patch->totallight = s1 * child1->totallight + s2 * child2->totallight + for ( j = 0; j < normalCount; j++ ) + { + VectorScale( child1->totallight.light[j], s1, patch->totallight.light[j] ); + VectorMA( patch->totallight.light[j], s2, child2->totallight.light[j], patch->totallight.light[j] ); + } + + // patch->emitlight = s1 * child1->emitlight + s2 * child2->emitlight + VectorScale( emitlight[patch->child1], s1, emitlight[i] ); + VectorMA( emitlight[i], s2, emitlight[patch->child2], emitlight[i] ); + } + for ( j = 0; j < NUM_BUMP_VECTS+1; j++ ) + { + VectorFill( addlight[ i ].light[j], 0 ); + } + } +} + +/* +============= +GatherLight + +Get light from other patches + Run multi-threaded +============= +*/ + +#ifdef _WIN32 +#pragma warning (disable:4701) +#endif + +extern void GetBumpNormals( const float* sVect, const float* tVect, const Vector& flatNormal, + const Vector& phongNormal, Vector bumpNormals[NUM_BUMP_VECTS] ); + + +void PreGetBumpNormalsForDisp( texinfo_t *pTexinfo, Vector &vecU, Vector &vecV, Vector &vecNormal ) +{ + Vector vecTexU( pTexinfo->textureVecsTexelsPerWorldUnits[0][0], pTexinfo->textureVecsTexelsPerWorldUnits[0][1], pTexinfo->textureVecsTexelsPerWorldUnits[0][2] ); + Vector vecTexV( pTexinfo->textureVecsTexelsPerWorldUnits[1][0], pTexinfo->textureVecsTexelsPerWorldUnits[1][1], pTexinfo->textureVecsTexelsPerWorldUnits[1][2] ); + Vector vecLightU( pTexinfo->lightmapVecsLuxelsPerWorldUnits[0][0], pTexinfo->lightmapVecsLuxelsPerWorldUnits[0][1], pTexinfo->lightmapVecsLuxelsPerWorldUnits[0][2] ); + Vector vecLightV( pTexinfo->lightmapVecsLuxelsPerWorldUnits[1][0], pTexinfo->lightmapVecsLuxelsPerWorldUnits[1][1], pTexinfo->lightmapVecsLuxelsPerWorldUnits[1][2] ); + + VectorNormalize( vecTexU ); + VectorNormalize( vecTexV ); + VectorNormalize( vecLightU ); + VectorNormalize( vecLightV ); + + bool bDoConversion = false; + if ( fabs( vecTexU.Dot( vecLightU ) ) < 0.999f ) + { + bDoConversion = true; + } + + if ( fabs( vecTexV.Dot( vecLightV ) ) < 0.999f ) + { + bDoConversion = true; + } + + if ( bDoConversion ) + { + matrix3x4_t matTex( vecTexU, vecTexV, vecNormal, vec3_origin ); + matrix3x4_t matLight( vecLightU, vecLightV, vecNormal, vec3_origin ); + matrix3x4_t matTmp; + ConcatTransforms ( matLight, matTex, matTmp ); + MatrixGetColumn( matTmp, 0, vecU ); + MatrixGetColumn( matTmp, 1, vecV ); + MatrixGetColumn( matTmp, 2, vecNormal ); + + Assert( fabs( vecTexU.Dot( vecTexV ) ) <= 0.001f ); + return; + } + + vecU = vecTexU; + vecV = vecTexV; +} + +void GatherLight (int threadnum, void *pUserData) +{ + int i, j, k; + transfer_t *trans; + int num; + CPatch *patch; + Vector sum, v; + + while (1) + { + j = GetThreadWork (); + if (j == -1) + break; + + patch = &g_Patches[j]; + + trans = patch->transfers; + num = patch->numtransfers; + if ( patch->needsBumpmap ) + { + Vector delta; + Vector bumpSum[NUM_BUMP_VECTS+1]; + Vector normals[NUM_BUMP_VECTS+1]; + + // Disps + bool bDisp = ( g_pFaces[patch->faceNumber].dispinfo != -1 ); + if ( bDisp ) + { + normals[0] = patch->normal; + texinfo_t *pTexinfo = &texinfo[g_pFaces[patch->faceNumber].texinfo]; + Vector vecTexU, vecTexV; + PreGetBumpNormalsForDisp( pTexinfo, vecTexU, vecTexV, normals[0] ); + + // use facenormal along with the smooth normal to build the three bump map vectors + GetBumpNormals( vecTexU, vecTexV, normals[0], normals[0], &normals[1] ); + } + else + { + GetPhongNormal( patch->faceNumber, patch->origin, normals[0] ); + + texinfo_t *pTexinfo = &texinfo[g_pFaces[patch->faceNumber].texinfo]; + // use facenormal along with the smooth normal to build the three bump map vectors + GetBumpNormals( pTexinfo->textureVecsTexelsPerWorldUnits[0], + pTexinfo->textureVecsTexelsPerWorldUnits[1], patch->normal, + normals[0], &normals[1] ); + } + + // force the base lightmap to use the flat normal instead of the phong normal + // FIXME: why does the patch not use the phong normal? + normals[0] = patch->normal; + + for ( i = 0; i < NUM_BUMP_VECTS+1; i++ ) + { + VectorFill( bumpSum[i], 0 ); + } + + float dot; + for (k=0 ; kpatch]; + + // get vector to other patch + VectorSubtract (patch2->origin, patch->origin, delta); + VectorNormalize (delta); + // find light emitted from other patch + for(i=0; i<3; i++) + { + v[i] = emitlight[trans->patch][i] * patch2->reflectivity[i]; + } + // remove normal already factored into transfer steradian + float scale = 1.0f / DotProduct (delta, patch->normal); + VectorScale( v, trans->transfer * scale, v ); + + Vector bumpTransfer; + for ( i = 0; i < NUM_BUMP_VECTS+1; i++ ) + { + dot = DotProduct( delta, normals[i] ); + if ( dot <= 0 ) + { +// Assert( i > 0 ); // if this hits, then the transfer shouldn't be here. It doesn't face the flat normal of this face! + continue; + } + bumpTransfer = v * dot; + VectorAdd( bumpSum[i], bumpTransfer, bumpSum[i] ); + } + } + for ( i = 0; i < NUM_BUMP_VECTS+1; i++ ) + { + VectorCopy( bumpSum[i], addlight[j].light[i] ); + } + } + else + { + VectorFill( sum, 0 ); + for (k=0 ; kpatch][i] * g_Patches[trans->patch].reflectivity[i]; + } + VectorScale( v, trans->transfer, v ); + VectorAdd( sum, v, sum ); + } + VectorCopy( sum, addlight[j].light[0] ); + } + } +} + +#ifdef _WIN32 +#pragma warning (default:4701) +#endif + + +/* +============= +BounceLight +============= +*/ +void BounceLight (void) +{ + unsigned i; + Vector added; + char name[64]; + qboolean bouncing = numbounce > 0; + + unsigned int uiPatchCount = g_Patches.Size(); + for (i=0 ; iOpen( "lightemit.txt", "w" ); + + unsigned int uiPatchCount = g_Patches.Size(); + for (i=0 ; iClose( dFp ); + + for (i=0; iemitlight to receiver->addlight + unsigned int uiPatchCount = g_Patches.Size(); + RunThreadsOn (uiPatchCount, true, GatherLight); + // move newly received light (addlight) to light to be sent out (emitlight) + // start at children and pull light up to parents + // light is always received to leaf patches + CollectLight( added ); + + qprintf ("\tBounce #%i added RGB(%.0f, %.0f, %.0f)\n", i+1, added[0], added[1], added[2] ); + + if ( i+1 == numbounce || (added[0] < 1.0 && added[1] < 1.0 && added[2] < 1.0) ) + bouncing = false; + + i++; + if ( g_bDumpPatches && !bouncing && i != 1) + { + sprintf (name, "bounce%i.txt", i); + WriteWorld (name, 0); + } + } +} + + + +//----------------------------------------------------------------------------- +// Purpose: Counts the number of clusters in a map with no visibility +// Output : int +//----------------------------------------------------------------------------- +int CountClusters( void ) +{ + int clusterCount = 0; + + for ( int i = 0; i < numleafs; i++ ) + { + if ( dleafs[i].cluster > clusterCount ) + clusterCount = dleafs[i].cluster; + } + + return clusterCount + 1; +} + + +/* +============= +RadWorld +============= +*/ +void RadWorld_Start() +{ + unsigned i; + + if (luxeldensity < 1.0) + { + // Remember the old lightmap vectors. + float oldLightmapVecs[MAX_MAP_TEXINFO][2][4]; + for (i = 0; i < texinfo.Count(); i++) + { + for( int j=0; j < 2; j++ ) + { + for( int k=0; k < 3; k++ ) + { + oldLightmapVecs[i][j][k] = texinfo[i].lightmapVecsLuxelsPerWorldUnits[j][k]; + } + } + } + + // rescale luxels to be no denser than "luxeldensity" + for (i = 0; i < texinfo.Count(); i++) + { + texinfo_t *tx = &texinfo[i]; + + for (int j = 0; j < 2; j++ ) + { + Vector tmp( tx->lightmapVecsLuxelsPerWorldUnits[j][0], tx->lightmapVecsLuxelsPerWorldUnits[j][1], tx->lightmapVecsLuxelsPerWorldUnits[j][2] ); + float scale = VectorNormalize( tmp ); + // only rescale them if the current scale is "tighter" than the desired scale + // FIXME: since this writes out to the BSP file every run, once it's set high it can't be reset + // to a lower value. + if (fabs( scale ) > luxeldensity) + { + if (scale < 0) + { + scale = -luxeldensity; + } + else + { + scale = luxeldensity; + } + VectorScale( tmp, scale, tmp ); + tx->lightmapVecsLuxelsPerWorldUnits[j][0] = tmp.x; + tx->lightmapVecsLuxelsPerWorldUnits[j][1] = tmp.y; + tx->lightmapVecsLuxelsPerWorldUnits[j][2] = tmp.z; + } + } + } + + UpdateAllFaceLightmapExtents(); + } + + MakeParents (0, -1); + + BuildClusterTable(); + + // turn each face into a single patch + MakePatches (); + PairEdges (); + + // store the vertex normals calculated in PairEdges + // so that the can be written to the bsp file for + // use in the engine + SaveVertexNormals(); + + // subdivide patches to a maximum dimension + SubdividePatches (); + + // add displacement faces to cluster table + AddDispsToClusterTable(); + + // create directlights out of patches and lights + CreateDirectLights (); + + // set up sky cameras + ProcessSkyCameras(); +} + + +// This function should fill in the indices into g_pFaces[] for the faces +// with displacements that touch the specified leaf. +void STUB_GetDisplacementsTouchingLeaf( int iLeaf, CUtlVector &dispFaces ) +{ +} + + +void BuildFacesVisibleToLights( bool bAllVisible ) +{ + g_FacesVisibleToLights.SetSize( numfaces/8 + 1 ); + + if( bAllVisible ) + { + memset( g_FacesVisibleToLights.Base(), 0xFF, g_FacesVisibleToLights.Count() ); + return; + } + + // First merge all the light PVSes. + CUtlVector aggregate; + aggregate.SetSize( (dvis->numclusters/8) + 1 ); + memset( aggregate.Base(), 0, aggregate.Count() ); + + int nDWords = aggregate.Count() / 4; + int nBytes = aggregate.Count() - nDWords*4; + + for( directlight_t *dl = activelights; dl != NULL; dl = dl->next ) + { + byte *pIn = dl->pvs; + byte *pOut = aggregate.Base(); + for( int iDWord=0; iDWord < nDWords; iDWord++ ) + { + *((unsigned long*)pOut) |= *((unsigned long*)pIn); + pIn += 4; + pOut += 4; + } + + for( int iByte=0; iByte < nBytes; iByte++ ) + { + *pOut |= *pIn; + ++pOut; + ++pIn; + } + } + + + // Now tag any faces that are visible to this monster PVS. + for( int iCluster=0; iCluster < dvis->numclusters; iCluster++ ) + { + if( g_ClusterLeaves[iCluster].leafCount ) + { + if( aggregate[iCluster>>3] & (1 << (iCluster & 7)) ) + { + for ( int i = 0; i < g_ClusterLeaves[iCluster].leafCount; i++ ) + { + int iLeaf = g_ClusterLeaves[iCluster].leafs[i]; + + // Tag all the faces. + int iFace; + for( iFace=0; iFace < dleafs[iLeaf].numleaffaces; iFace++ ) + { + int index = dleafs[iLeaf].firstleafface + iFace; + index = dleaffaces[index]; + + assert( index < numfaces ); + g_FacesVisibleToLights[index >> 3] |= (1 << (index & 7)); + } + + // Fill in STUB_GetDisplacementsTouchingLeaf when it's available + // so displacements get relit. + CUtlVector dispFaces; + STUB_GetDisplacementsTouchingLeaf( iLeaf, dispFaces ); + for( iFace=0; iFace < dispFaces.Count(); iFace++ ) + { + int index = dispFaces[iFace]; + g_FacesVisibleToLights[index >> 3] |= (1 << (index & 7)); + } + } + } + } + } + + // For stats.. figure out how many faces it's going to touch. + int nFacesToProcess = 0; + for( int i=0; i < numfaces; i++ ) + { + if( g_FacesVisibleToLights[i>>3] & (1 << (i & 7)) ) + ++nFacesToProcess; + } +} + + + +void MakeAllScales (void) +{ + // determine visibility between patches + BuildVisMatrix (); + + // release visibility matrix + FreeVisMatrix (); + + Msg("transfers %d, max %d\n", total_transfer, max_transfer ); + + qprintf ("transfer lists: %5.1f megs\n" + , (float)total_transfer * sizeof(transfer_t) / (1024*1024)); +} + + +// Helper function. This can be useful to visualize the world and faces and see which face +// corresponds to which dface. +#if 0 + #include "iscratchpad3d.h" + void ScratchPad_DrawWorld() + { + IScratchPad3D *pPad = ScratchPad3D_Create(); + pPad->SetAutoFlush( false ); + + for ( int i=0; i < numfaces; i++ ) + { + dface_t *f = &g_pFaces[i]; + + // Draw the face's outline, then put text for its face index on it too. + CUtlVector points; + for ( int iEdge = 0; iEdge < f->numedges; iEdge++ ) + { + int v; + int se = dsurfedges[f->firstedge + iEdge]; + if ( se < 0 ) + v = dedges[-se].v[1]; + else + v = dedges[se].v[0]; + + dvertex_t *dv = &dvertexes[v]; + points.AddToTail( dv->point ); + } + + // Draw the outline. + Vector vCenter( 0, 0, 0 ); + for ( iEdge=0; iEdge < points.Count(); iEdge++ ) + { + pPad->DrawLine( CSPVert( points[iEdge] ), CSPVert( points[(iEdge+1)%points.Count()] ) ); + vCenter += points[iEdge]; + } + vCenter /= points.Count(); + + // Draw the text. + char str[512]; + Q_snprintf( str, sizeof( str ), "%d", i ); + + CTextParams params; + + params.m_bCentered = true; + params.m_bOutline = true; + params.m_flLetterWidth = 2; + params.m_vColor.Init( 1, 0, 0 ); + + VectorAngles( dplanes[f->planenum].normal, params.m_vAngles ); + params.m_bTwoSided = true; + + params.m_vPos = vCenter; + + pPad->DrawText( str, params ); + } + + pPad->Release(); + } +#endif + + +bool RadWorld_Go() +{ + g_iCurFace = 0; + + InitMacroTexture( source ); + + if( g_pIncremental ) + { + g_pIncremental->PrepareForLighting(); + + // Cull out faces that aren't visible to any of the lights that we're updating with. + BuildFacesVisibleToLights( false ); + } + else + { + // Mark all faces visible.. when not doing incremental lighting, it's highly + // likely that all faces are going to be touched by at least one light so don't + // waste time here. + BuildFacesVisibleToLights( true ); + } + + // build initial facelights + if (g_bUseMPI) + { + // RunThreadsOnIndividual (numfaces, true, BuildFacelights); + RunMPIBuildFacelights(); + } + else + { + RunThreadsOnIndividual (numfaces, true, BuildFacelights); + } + + // Was the process interrupted? + if( g_pIncremental && (g_iCurFace != numfaces) ) + return false; + + // Figure out the offset into lightmap data for each face. + PrecompLightmapOffsets(); + + // If we're doing incremental lighting, stop here. + if( g_pIncremental ) + { + g_pIncremental->Finalize(); + } + else + { + // free up the direct lights now that we have facelights + ExportDirectLightsToWorldLights(); + + if ( g_bDumpPatches ) + { + for( int iBump = 0; iBump < 4; ++iBump ) + { + char szName[64]; + sprintf ( szName, "bounce0_%d.txt", iBump ); + WriteWorld( szName, iBump ); + } + } + + if (numbounce > 0) + { + // allocate memory for emitlight/addlight + emitlight.SetSize( g_Patches.Size() ); + memset( emitlight.Base(), 0, g_Patches.Size() * sizeof( Vector ) ); + addlight.SetSize( g_Patches.Size() ); + memset( addlight.Base(), 0, g_Patches.Size() * sizeof( bumplights_t ) ); + + MakeAllScales (); + + // spread light around + BounceLight (); + } + + // + // displacement surface luxel accumulation (make threaded!!!) + // + StaticDispMgr()->StartTimer( "Build Patch/Sample Hash Table(s)....." ); + StaticDispMgr()->InsertSamplesDataIntoHashTable(); + StaticDispMgr()->InsertPatchSampleDataIntoHashTable(); + StaticDispMgr()->EndTimer(); + + // blend bounced light into direct light and save + VMPI_SetCurrentStage( "FinalLightFace" ); + if ( !g_bUseMPI || g_bMPIMaster ) + RunThreadsOnIndividual (numfaces, true, FinalLightFace); + + // Distribute the lighting data to workers. + VMPI_DistributeLightData(); + + Msg("FinalLightFace Done\n"); fflush(stdout); + } + + return true; +} + +// declare the sample file pointer -- the whole debug print system should +// be reworked at some point!! +FileHandle_t pFileSamples[4][4]; + +void LoadPhysicsDLL( void ) +{ + PhysicsDLLPath( "VPHYSICS.DLL" ); +} + + +void InitDumpPatchesFiles() +{ + for( int iStyle = 0; iStyle < 4; ++iStyle ) + { + for ( int iBump = 0; iBump < 4; ++iBump ) + { + char szFilename[MAX_PATH]; + sprintf( szFilename, "samples_style%d_bump%d.txt", iStyle, iBump ); + pFileSamples[iStyle][iBump] = g_pFileSystem->Open( szFilename, "w" ); + if( !pFileSamples[iStyle][iBump] ) + { + Error( "Can't open %s for -dump.\n", szFilename ); + } + } + } +} + + +void VRAD_LoadBSP( char const *pFilename ) +{ + ThreadSetDefault (); + + g_flStartTime = Plat_FloatTime(); + + if( g_bLowPriority ) + { + SetLowPriority(); + } + + strcpy( level_name, source ); + + // This must come after InitFileSystem because the file system pointer might change. + if ( g_bDumpPatches ) + InitDumpPatchesFiles(); + + // This part is just for VMPI. VMPI's file system needs the basedir in front of all filenames, + // so we prepend qdir here. + strcpy( source, ExpandPath( source ) ); + + if ( !g_bUseMPI ) + { + // Setup the logfile. + char logFile[512]; + _snprintf( logFile, sizeof(logFile), "%s.log", source ); + SetSpewFunctionLogFile( logFile ); + } + + LoadPhysicsDLL(); + + // Set the required global lights filename and try looking in qproject + strcpy( global_lights, "lights.rad" ); + if ( !g_pFileSystem->FileExists( global_lights ) ) + { + // Otherwise, try looking in the BIN directory from which we were run from + Msg( "Could not find lights.rad in %s.\nTrying VRAD BIN directory instead...\n", + global_lights ); + GetModuleFileName( NULL, global_lights, sizeof( global_lights ) ); + Q_ExtractFilePath( global_lights, global_lights, sizeof( global_lights ) ); + strcat( global_lights, "lights.rad" ); + } + + // Set the optional level specific lights filename + strcpy( level_lights, source ); + + Q_DefaultExtension( level_lights, ".rad", sizeof( level_lights ) ); + if ( !g_pFileSystem->FileExists( level_lights ) ) + *level_lights = 0; + + ReadLightFile(global_lights); // Required + if ( *designer_lights ) ReadLightFile(designer_lights); // Command-line + if ( *level_lights ) ReadLightFile(level_lights); // Optional & implied + + strcpy(incrementfile, source); + Q_DefaultExtension(incrementfile, ".r0", sizeof(incrementfile)); + Q_DefaultExtension(source, ".bsp", sizeof( source )); + + GetPlatformMapPath( source, platformPath, 0, MAX_PATH ); + + Msg( "Loading %s\n", platformPath ); + VMPI_SetCurrentStage( "LoadBSPFile" ); + LoadBSPFile (platformPath); + + // now, set whether or not static prop lighting is present + if (g_bStaticPropLighting) + g_LevelFlags |= g_bHDR? LVLFLAGS_BAKED_STATIC_PROP_LIGHTING_HDR : LVLFLAGS_BAKED_STATIC_PROP_LIGHTING_NONHDR; + else + { + g_LevelFlags &= ~( LVLFLAGS_BAKED_STATIC_PROP_LIGHTING_HDR | LVLFLAGS_BAKED_STATIC_PROP_LIGHTING_NONHDR ); + } + + // now, we need to set our face ptr depending upon hdr, and if hdr, init it + if (g_bHDR) + { + g_pFaces = dfaces_hdr; + if (numfaces_hdr==0) + { + numfaces_hdr = numfaces; + memcpy( dfaces_hdr, dfaces, numfaces*sizeof(dfaces[0]) ); + } + } + else + { + g_pFaces = dfaces; + } + + + ParseEntities (); + ExtractBrushEntityShadowCasters(); + + StaticPropMgr()->Init(); + StaticDispMgr()->Init(); + + if (!visdatasize) + { + Msg("No vis information, direct lighting only.\n"); + numbounce = 0; + ambient[0] = ambient[1] = ambient[2] = 0.1f; + dvis->numclusters = CountClusters(); + } + + // + // patches and referencing data (ensure capacity) + // + // TODO: change the maxes to the amount from the bsp!! + // +// g_Patches.EnsureCapacity( MAX_PATCHES ); + + g_FacePatches.SetSize( MAX_MAP_FACES ); + faceParents.SetSize( MAX_MAP_FACES ); + clusterChildren.SetSize( MAX_MAP_CLUSTERS ); + + int ndx; + for ( ndx = 0; ndx < MAX_MAP_FACES; ndx++ ) + { + g_FacePatches[ndx] = g_FacePatches.InvalidIndex(); + faceParents[ndx] = faceParents.InvalidIndex(); + } + + for ( ndx = 0; ndx < MAX_MAP_CLUSTERS; ndx++ ) + { + clusterChildren[ndx] = clusterChildren.InvalidIndex(); + } + + // Setup ray tracer + AddBrushesForRayTrace(); + StaticDispMgr()->AddPolysForRayTrace(); + StaticPropMgr()->AddPolysForRayTrace(); + + // Dump raytracer for glview + if ( g_bDumpRtEnv ) + WriteRTEnv("trace.txt"); + + // Build acceleration structure + printf ( "Setting up ray-trace acceleration structure... "); + float start = Plat_FloatTime(); + g_RtEnv.SetupAccelerationStructure(); + float end = Plat_FloatTime(); + printf ( "Done (%.2f seconds)\n", end-start ); + +#if 0 // To test only k-d build + exit(0); +#endif + + RadWorld_Start(); + + // Setup incremental lighting. + if( g_pIncremental ) + { + if( !g_pIncremental->Init( source, incrementfile ) ) + { + Error( "Unable to load incremental lighting file in %s.\n", incrementfile ); + return; + } + } +} + + +void VRAD_ComputeOtherLighting() +{ + // Compute lighting for the bsp file + if ( !g_bNoDetailLighting ) + { + ComputeDetailPropLighting( THREADINDEX_MAIN ); + } + + ComputePerLeafAmbientLighting(); + + // bake the static props high quality vertex lighting into the bsp + if ( !do_fast && g_bStaticPropLighting ) + { + StaticPropMgr()->ComputeLighting( THREADINDEX_MAIN ); + } +} + +extern void CloseDispLuxels(); + +void VRAD_Finish() +{ + Msg( "Ready to Finish\n" ); + fflush( stdout ); + + if ( verbose ) + { + PrintBSPFileSizes(); + } + + Msg( "Writing %s\n", platformPath ); + VMPI_SetCurrentStage( "WriteBSPFile" ); + WriteBSPFile(platformPath); + + if ( g_bDumpPatches ) + { + for ( int iStyle = 0; iStyle < 4; ++iStyle ) + { + for ( int iBump = 0; iBump < 4; ++iBump ) + { + g_pFileSystem->Close( pFileSamples[iStyle][iBump] ); + } + } + } + + CloseDispLuxels(); + + StaticPropMgr()->Shutdown(); + + double end = Plat_FloatTime(); + + char str[512]; + GetHourMinuteSecondsString( (int)( end - g_flStartTime ), str, sizeof( str ) ); + Msg( "%s elapsed\n", str ); + + ReleasePakFileLumps(); +} + + +// Run startup code like initialize mathlib (called from main() and from the +// WorldCraft interface into vrad). +void VRAD_Init() +{ + MathLib_Init( 2.2f, 2.2f, 0.0f, 2.0f, false, false, false, false ); + InstallAllocationFunctions(); + InstallSpewFunction(); +} + + +int ParseCommandLine( int argc, char **argv, bool *onlydetail ) +{ + *onlydetail = false; + + // default to LDR + SetHDRMode( false ); + int i; + for( i=1 ; i 1.0) + luxeldensity = 1.0 / luxeldensity; + } + else + { + Warning("Error: expected a value after '-luxeldensity'\n" ); + return 1; + } + } + else if( !Q_stricmp( argv[i], "-low" ) ) + { + g_bLowPriority = true; + } + else if( !Q_stricmp( argv[i], "-loghash" ) ) + { + g_bLogHashData = true; + } + else if( !Q_stricmp( argv[i], "-onlydetail" ) ) + { + *onlydetail = true; + } + else if (!Q_stricmp(argv[i],"-softsun")) + { + if ( ++i < argc ) + { + g_SunAngularExtent=atof(argv[i]); + g_SunAngularExtent=sin((M_PI/180.0)*g_SunAngularExtent); + printf("sun extent=%f\n",g_SunAngularExtent); + } + else + { + Warning("Error: expected an angular extent value (0..180) '-softsun'\n" ); + return 1; + } + } + else if ( !Q_stricmp( argv[i], "-maxdispsamplesize" ) ) + { + if ( ++i < argc ) + { + g_flMaxDispSampleSize = ( float )atof( argv[i] ); + } + else + { + Warning( "Error: expected a sample size after '-maxdispsamplesize'\n" ); + return 1; + } + } + else if ( stricmp( argv[i], "-StopOnExit" ) == 0 ) + { + g_bStopOnExit = true; + } + else if ( stricmp( argv[i], "-steam" ) == 0 ) + { + } + else if ( stricmp( argv[i], "-allowdebug" ) == 0 ) + { + // Don't need to do anything, just don't error out. + } + else if ( !Q_stricmp( argv[i], CMDLINEOPTION_NOVCONFIG ) ) + { + } + else if ( !Q_stricmp( argv[i], "-vproject" ) || !Q_stricmp( argv[i], "-game" ) ) + { + ++i; + } + else if ( !Q_stricmp( argv[i], "-FullMinidumps" ) ) + { + EnableFullMinidumps( true ); + } + else if ( !Q_stricmp( argv[i], "-hdr" ) ) + { + SetHDRMode( true ); + } + else if ( !Q_stricmp( argv[i], "-ldr" ) ) + { + SetHDRMode( false ); + } + else if (!Q_stricmp(argv[i],"-maxchop")) + { + if ( ++i < argc ) + { + maxchop = (float)atof (argv[i]); + if ( maxchop < 1 ) + { + Warning("Error: expected positive value after '-maxchop'\n" ); + return 1; + } + } + else + { + Warning("Error: expected a value after '-maxchop'\n" ); + return 1; + } + } + else if (!Q_stricmp(argv[i],"-chop")) + { + if ( ++i < argc ) + { + minchop = (float)atof (argv[i]); + if ( minchop < 1 ) + { + Warning("Error: expected positive value after '-chop'\n" ); + return 1; + } + minchop = min( minchop, maxchop ); + } + else + { + Warning("Error: expected a value after '-chop'\n" ); + return 1; + } + } + else if ( !Q_stricmp( argv[i], "-dispchop" ) ) + { + if ( ++i < argc ) + { + dispchop = ( float )atof( argv[i] ); + if ( dispchop < 1.0f ) + { + Warning( "Error: expected positive value after '-dipschop'\n" ); + return 1; + } + } + else + { + Warning( "Error: expected a value after '-dispchop'\n" ); + return 1; + } + } + else if ( !Q_stricmp( argv[i], "-disppatchradius" ) ) + { + if ( ++i < argc ) + { + g_MaxDispPatchRadius = ( float )atof( argv[i] ); + if ( g_MaxDispPatchRadius < 10.0f ) + { + Warning( "Error: g_MaxDispPatchRadius < 10.0\n" ); + return 1; + } + } + else + { + Warning( "Error: expected a value after '-disppatchradius'\n" ); + return 1; + } + } + +#if ALLOWDEBUGOPTIONS + else if (!Q_stricmp(argv[i],"-scale")) + { + if ( ++i < argc ) + { + lightscale = (float)atof (argv[i]); + } + else + { + Warning("Error: expected a value after '-scale'\n" ); + return 1; + } + } + else if (!Q_stricmp(argv[i],"-ambient")) + { + if ( i+3 < argc ) + { + ambient[0] = (float)atof (argv[++i]) * 128; + ambient[1] = (float)atof (argv[++i]) * 128; + ambient[2] = (float)atof (argv[++i]) * 128; + } + else + { + Warning("Error: expected three color values after '-ambient'\n" ); + return 1; + } + } + else if (!Q_stricmp(argv[i],"-dlight")) + { + if ( ++i < argc ) + { + dlight_threshold = (float)atof (argv[i]); + } + else + { + Warning("Error: expected a value after '-dlight'\n" ); + return 1; + } + } + else if (!Q_stricmp(argv[i],"-sky")) + { + if ( ++i < argc ) + { + indirect_sun = (float)atof (argv[i]); + } + else + { + Warning("Error: expected a value after '-sky'\n" ); + return 1; + } + } + else if (!Q_stricmp(argv[i],"-notexscale")) + { + texscale = false; + } + else if (!Q_stricmp(argv[i],"-coring")) + { + if ( ++i < argc ) + { + coring = (float)atof( argv[i] ); + } + else + { + Warning("Error: expected a light threshold after '-coring'\n" ); + return 1; + } + } +#endif + // NOTE: the -mpi checks must come last here because they allow the previous argument + // to be -mpi as well. If it game before something else like -game, then if the previous + // argument was -mpi and the current argument was something valid like -game, it would skip it. + else if ( !Q_strncasecmp( argv[i], "-mpi", 4 ) || !Q_strncasecmp( argv[i-1], "-mpi", 4 ) ) + { + if ( stricmp( argv[i], "-mpi" ) == 0 ) + g_bUseMPI = true; + + // Any other args that start with -mpi are ok too. + if ( i == argc - 1 && V_stricmp( argv[i], "-mpi_ListParams" ) != 0 ) + break; + } + else + { + break; + } + } + + return i; +} + + +void PrintCommandLine( int argc, char **argv ) +{ + Warning( "Command line: " ); + for ( int z=0; z < argc; z++ ) + { + Warning( "\"%s\" ", argv[z] ); + } + Warning( "\n\n" ); +} + + +void PrintUsage( int argc, char **argv ) +{ + PrintCommandLine( argc, argv ); + + Warning( + "usage : vrad [options...] bspfile\n" + "example: vrad c:\\hl2\\hl2\\maps\\test\n" + "\n" + "Common options:\n" + "\n" + " -v (or -verbose): Turn on verbose output (also shows more command\n" + " -bounce # : Set max number of bounces (default: 100).\n" + " -fast : Quick and dirty lighting.\n" + " -fastambient : Per-leaf ambient sampling is lower quality to save compute time.\n" + " -final : High quality processing. equivalent to -extrasky 16.\n" + " -extrasky n : trace N times as many rays for indirect light and sky ambient.\n" + " -low : Run as an idle-priority process.\n" + " -mpi : Use VMPI to distribute computations.\n" + " -rederror : Show errors in red.\n" + "\n" + " -vproject : Override the VPROJECT environment variable.\n" + " -game : Same as -vproject.\n" + "\n" + "Other options:\n" + " -novconfig : Don't bring up graphical UI on vproject errors.\n" + " -dump : Write debugging .txt files.\n" + " -dumpnormals : Write normals to debug files.\n" + " -dumptrace : Write ray-tracing environment to debug files.\n" + " -threads : Control the number of threads vbsp uses (defaults to the #\n" + " or processors on your machine).\n" + " -lights : Load a lights file in addition to lights.rad and the\n" + " level lights file.\n" + " -noextra : Disable supersampling.\n" + " -debugextra : Places debugging data in lightmaps to visualize\n" + " supersampling.\n" + " -smooth # : Set the threshold for smoothing groups, in degrees\n" + " (default 45).\n" + " -dlightmap : Force direct lighting into different lightmap than\n" + " radiosity.\n" + " -stoponexit : Wait for a keypress on exit.\n" + " -mpi_pw : Use a password to choose a specific set of VMPI workers.\n" + " -nodetaillight : Don't light detail props.\n" + " -centersamples : Move sample centers.\n" + " -luxeldensity # : Rescale all luxels by the specified amount (default: 1.0).\n" + " The number specified must be less than 1.0 or it will be\n" + " ignored.\n" + " -loghash : Log the sample hash table to samplehash.txt.\n" + " -onlydetail : Only light detail props and per-leaf lighting.\n" + " -maxdispsamplesize #: Set max displacement sample size (default: 512).\n" + " -softsun : Treat the sun as an area light source of size degrees." + " Produces soft shadows.\n" + " Recommended values are between 0 and 5. Default is 0.\n" + " -FullMinidumps : Write large minidumps on crash.\n" + " -chop : Smallest number of luxel widths for a bounce patch, used on edges\n" + " -maxchop : Coarsest allowed number of luxel widths for a patch, used in face interiors\n" + "\n" + " -LargeDispSampleRadius: This can be used if there are splotches of bounced light\n" + " on terrain. The compile will take longer, but it will gather\n" + " light across a wider area.\n" + " -StaticPropLighting : generate backed static prop vertex lighting\n" + " -StaticPropPolys : Perform shadow tests of static props at polygon precision\n" + " -OnlyStaticProps : Only perform direct static prop lighting (vrad debug option)\n" + " -StaticPropNormals : when lighting static props, just show their normal vector\n" + " -textureshadows : Allows texture alpha channels to block light - rays intersecting alpha surfaces will sample the texture\n" + " -noskyboxrecurse : Turn off recursion into 3d skybox (skybox shadows on world)\n" + " -nossprops : Globally disable self-shadowing on static props\n" + "\n" +#if 1 // Disabled for the initial SDK release with VMPI so we can get feedback from selected users. + ); +#else + " -mpi_ListParams : Show a list of VMPI parameters.\n" + "\n" + ); + + // Show VMPI parameters? + for ( int i=1; i < argc; i++ ) + { + if ( V_stricmp( argv[i], "-mpi_ListParams" ) == 0 ) + { + Warning( "VMPI-specific options:\n\n" ); + + bool bIsSDKMode = VMPI_IsSDKMode(); + for ( int i=k_eVMPICmdLineParam_FirstParam+1; i < k_eVMPICmdLineParam_LastParam; i++ ) + { + if ( (VMPI_GetParamFlags( (EVMPICmdLineParam)i ) & VMPI_PARAM_SDK_HIDDEN) && bIsSDKMode ) + continue; + + Warning( "[%s]\n", VMPI_GetParamString( (EVMPICmdLineParam)i ) ); + Warning( VMPI_GetParamHelpString( (EVMPICmdLineParam)i ) ); + Warning( "\n\n" ); + } + break; + } + } +#endif +} + + +int RunVRAD( int argc, char **argv ) +{ +#if defined(_MSC_VER) && ( _MSC_VER >= 1310 ) + Msg("Valve Software - vrad.exe SSE (" __DATE__ ")\n" ); +#else + Msg("Valve Software - vrad.exe (" __DATE__ ")\n" ); +#endif + + Msg("\n Valve Radiosity Simulator \n"); + + verbose = true; // Originally FALSE + + bool onlydetail; + int i = ParseCommandLine( argc, argv, &onlydetail ); + if (i != argc - 1) + { + PrintUsage( argc, argv ); + DeleteCmdLine( argc, argv ); + CmdLib_Exit( 1 ); + } + + VRAD_LoadBSP( argv[i] ); + + if ( (! onlydetail) && (! g_bOnlyStaticProps ) ) + { + RadWorld_Go(); + } + + VRAD_ComputeOtherLighting(); + + VRAD_Finish(); + + VMPI_SetCurrentStage( "master done" ); + + DeleteCmdLine( argc, argv ); + CmdLib_Cleanup(); + return 0; +} + + +int VRAD_Main(int argc, char **argv) +{ + g_pFileSystem = NULL; // Safeguard against using it before it's properly initialized. + + VRAD_Init(); + + // This must come first. + VRAD_SetupMPI( argc, argv ); + + // Initialize the filesystem, so additional commandline options can be loaded + Q_StripExtension( argv[ argc - 1 ], source, sizeof( source ) ); + CmdLib_InitFileSystem( argv[ argc - 1 ] ); + Q_FileBase( source, source, sizeof( source ) ); + +#if !defined( _DEBUG ) + if ( g_bUseMPI && !g_bMPIMaster ) + { + SetupToolsMinidumpHandler( VMPI_ExceptionFilter ); + } + else +#endif + { + LoadCmdLineFromFile( argc, argv, source, "vrad" ); // Don't do this if we're a VMPI worker.. + SetupDefaultToolsMinidumpHandler(); + } + + return RunVRAD( argc, argv ); +} + + + + + diff --git a/mp/src/utils/vrad/vrad.h b/mp/src/utils/vrad/vrad.h new file mode 100644 index 00000000..b9dc0a3f --- /dev/null +++ b/mp/src/utils/vrad/vrad.h @@ -0,0 +1,610 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef VRAD_H +#define VRAD_H +#pragma once + + +#include "commonmacros.h" +#include "worldsize.h" +#include "cmdlib.h" +#include "mathlib/mathlib.h" +#include "bsplib.h" +#include "polylib.h" +#include "threads.h" +#include "builddisp.h" +#include "VRAD_DispColl.h" +#include "UtlMemory.h" +#include "UtlHash.h" +#include "utlvector.h" +#include "iincremental.h" +#include "raytrace.h" + + +#ifdef _WIN32 +#include +#endif + +#include +#include + +#pragma warning(disable: 4142 4028) +#include +#pragma warning(default: 4142 4028) + +#include +#include +#include + + +// Can remove these options if they don't generate problems. +//#define SAMPLEHASH_USE_AREA_PATCHES // Add patches to sample hash based on their AABB instead of as a single point. +#define SAMPLEHASH_QUERY_ONCE // Big optimization - causes way less sample hash queries. + +extern float dispchop; // "-dispchop" tightest number of luxel widths for a patch, used on edges +extern float g_MaxDispPatchRadius; + +//----------------------------------------------------------------------------- +// forward declarations +//----------------------------------------------------------------------------- + +struct Ray_t; + +#define TRANSFER_EPSILON 0.0000001 + +struct directlight_t +{ + int index; + + directlight_t *next; + dworldlight_t light; + + byte *pvs; // accumulated domain of the light + int facenum; // domain of attached lights + int texdata; // texture source of traced lights + + Vector snormal; + Vector tnormal; + float sscale; + float tscale; + float soffset; + float toffset; + + int dorecalc; // position, vector, spot angle, etc. + IncrementalLightID m_IncrementalID; + + // hard-falloff lights (lights that fade to an actual zero). between m_flStartFadeDistance and + // m_flEndFadeDistance, a smoothstep to zero will be done, so that the light goes to zero at + // the end. + float m_flStartFadeDistance; + float m_flEndFadeDistance; + float m_flCapDist; // max distance to feed in + + directlight_t(void) + { + m_flEndFadeDistance = -1.0; // end= 0 && + m_vecLighting.y >= 0 && + m_vecLighting.z >= 0 && + m_vecLighting.x < 1e10 && + m_vecLighting.y < 1e10 && + m_vecLighting.z < 1e10 ); + } + + FORCEINLINE void Zero( void ) + { + m_vecLighting.Init( 0, 0, 0 ); + m_flDirectSunAmount = 0.0; + } + + FORCEINLINE void Scale( float m_flScale ) + { + m_vecLighting *= m_flScale; + m_flDirectSunAmount *= m_flScale; + } + + FORCEINLINE void AddWeighted( LightingValue_t const &src, float flWeight ) + { + m_vecLighting += flWeight * src.m_vecLighting; + m_flDirectSunAmount += flWeight * src.m_flDirectSunAmount; + } + + FORCEINLINE void AddWeighted( Vector const &src, float flWeight ) + { + m_vecLighting += flWeight * src; + } + + FORCEINLINE float Intensity( void ) const + { + return m_vecLighting.x + m_vecLighting.y + m_vecLighting.z; + } + + FORCEINLINE void AddLight( float flAmount, Vector const &vecColor, float flSunAmount = 0.0 ) + { + VectorMA( m_vecLighting, flAmount, vecColor, m_vecLighting ); + m_flDirectSunAmount += flSunAmount; + Assert( this->IsValid() ); + } + + + FORCEINLINE void AddLight( LightingValue_t const &src ) + { + m_vecLighting += src.m_vecLighting; + m_flDirectSunAmount += src.m_flDirectSunAmount; + Assert( this->IsValid() ); + } + + FORCEINLINE void Init( float x, float y, float z ) + { + m_vecLighting.Init( x, y, z ); + m_flDirectSunAmount = 0.0; + } + + +}; + + +#define MAX_PATCHES (4*65536) + +struct CPatch +{ + winding_t *winding; + Vector mins, maxs, face_mins, face_maxs; + + Vector origin; // adjusted off face by face normal + + dplane_t *plane; // plane (corrected for facing) + + unsigned short m_IterationKey; // Used to prevent touching the same patch multiple times in the same query. + // See IncrementPatchIterationKey(). + + // these are packed into one dword + unsigned int normalMajorAxis : 2; // the major axis of base face normal + unsigned int sky : 1; + unsigned int needsBumpmap : 1; + unsigned int pad : 28; + + Vector normal; // adjusted for phong shading + + float planeDist; // Fixes up patch planes for brush models with an origin brush + + float chop; // smallest acceptable width of patch face + float luxscale; // average luxels per world coord + float scale[2]; // Scaling of texture in s & t + + bumplights_t totallight; // accumulated by radiosity + // does NOT include light + // accounted for by direct lighting + Vector baselight; // emissivity only + float basearea; // surface per area per baselight instance + + Vector directlight; // direct light value + float area; + + Vector reflectivity; // Average RGB of texture, modified by material type. + + Vector samplelight; + float samplearea; // for averaging direct light + int faceNumber; + int clusterNumber; + + int parent; // patch index of parent + int child1; // patch index for children + int child2; + + int ndxNext; // next patch index in face + int ndxNextParent; // next parent patch index in face + int ndxNextClusterChild; // next terminal child index in cluster +// struct patch_s *next; // next in face +// struct patch_s *nextparent; // next in face +// struct patch_s *nextclusterchild; // next terminal child in cluster + + int numtransfers; + transfer_t *transfers; + + short indices[3]; // displacement use these for subdivision +}; + + +extern CUtlVector g_Patches; +extern CUtlVector g_FacePatches; // constains all patches, children first +extern CUtlVector faceParents; // contains only root patches, use next parent to iterate +extern CUtlVector clusterChildren; + + +struct sky_camera_t +{ + Vector origin; + float world_to_sky; + float sky_to_world; + int area; +}; + +extern int num_sky_cameras; +extern sky_camera_t sky_cameras[MAX_MAP_AREAS]; +extern int area_sky_cameras[MAX_MAP_AREAS]; +void ProcessSkyCameras(); + +extern entity_t *face_entity[MAX_MAP_FACES]; +extern Vector face_offset[MAX_MAP_FACES]; // for rotating bmodels +extern Vector face_centroids[MAX_MAP_EDGES]; +extern int leafparents[MAX_MAP_LEAFS]; +extern int nodeparents[MAX_MAP_NODES]; +extern float lightscale; +extern float dlight_threshold; +extern float coring; +extern qboolean g_bDumpPatches; +extern bool bRed2Black; +extern bool g_bNoSkyRecurse; +extern bool bDumpNormals; +extern bool g_bFastAmbient; +extern float maxchop; +extern FileHandle_t pFileSamples[4][4]; +extern qboolean g_bLowPriority; +extern qboolean do_fast; +extern bool g_bInterrupt; // Was used with background lighting in WC. Tells VRAD to stop lighting. +extern IIncremental *g_pIncremental; // null if not doing incremental lighting + +extern float g_flSkySampleScale; // extra sampling factor for indirect light + +extern bool g_bLargeDispSampleRadius; +extern bool g_bStaticPropPolys; +extern bool g_bTextureShadows; +extern bool g_bShowStaticPropNormals; +extern bool g_bDisablePropSelfShadowing; + +extern CUtlVector g_NonShadowCastingMaterialStrings; +extern void ForceTextureShadowsOnModel( const char *pModelName ); +extern bool IsModelTextureShadowsForced( const char *pModelName ); + +// Raytracing + +#define TRACE_ID_SKY 0x01000000 // sky face ray blocker +#define TRACE_ID_OPAQUE 0x02000000 // everyday light blocking face +#define TRACE_ID_STATICPROP 0x04000000 // static prop - lower bits are prop ID +extern RayTracingEnvironment g_RtEnv; + +#include "mpivrad.h" + +void MakeShadowSplits (void); + +//============================================== + +void BuildVisMatrix (void); +void BuildClusterTable( void ); +void AddDispsToClusterTable( void ); +void FreeVisMatrix (void); +// qboolean CheckVisBit (unsigned int p1, unsigned int p2); +void TouchVMFFile (void); + +//============================================== + +extern qboolean do_extra; +extern qboolean do_fast; +extern qboolean do_centersamples; +extern int extrapasses; +extern Vector ambient; +extern float maxlight; +extern unsigned numbounce; +extern qboolean g_bLogHashData; +extern bool debug_extra; +extern directlight_t *activelights; +extern directlight_t *freelights; + +// because of hdr having two face lumps (light styles can cause them to be different, among other +// things), we need to always access (r/w) face data though this pointer +extern dface_t *g_pFaces; + + +extern bool g_bMPIProps; + +extern byte nodehit[MAX_MAP_NODES]; +extern float gamma; +extern float indirect_sun; +extern float smoothing_threshold; +extern int dlight_map; + +extern float g_flMaxDispSampleSize; +extern float g_SunAngularExtent; + +extern char source[MAX_PATH]; + +// Used by incremental lighting to trivial-reject faces. +// There is a bit in here for each face telling whether or not any of the +// active lights can see the face. +extern CUtlVector g_FacesVisibleToLights; + +void MakeTnodes (dmodel_t *bm); +void PairEdges (void); + +void SaveVertexNormals( void ); + +qboolean IsIncremental(char *filename); +int SaveIncremental(char *filename); +int PartialHead (void); +void BuildFacelights (int facenum, int threadnum); +void PrecompLightmapOffsets(); +void FinalLightFace (int threadnum, int facenum); +void PvsForOrigin (Vector& org, byte *pvs); +void ConvertRGBExp32ToRGBA8888( const ColorRGBExp32 *pSrc, unsigned char *pDst ); + +inline byte PVSCheck( const byte *pvs, int iCluster ) +{ + if ( iCluster >= 0 ) + { + return pvs[iCluster >> 3] & ( 1 << ( iCluster & 7 ) ); + } + else + { + // PointInLeaf still returns -1 for valid points sometimes and rather than + // have black samples, we assume the sample is in the PVS. + return 1; + } +} + +// outputs 1 in fractionVisible if no occlusion, 0 if full occlusion, and in-between values +void TestLine( FourVectors const& start, FourVectors const& stop, fltx4 *pFractionVisible, int static_prop_index_to_ignore=-1); + +// returns 1 if the ray sees the sky, 0 if it doesn't, and in-between values for partial coverage +void TestLine_DoesHitSky( FourVectors const& start, FourVectors const& stop, + fltx4 *pFractionVisible, bool canRecurse = true, int static_prop_to_skip=-1, bool bDoDebug = false ); + +// converts any marked brush entities to triangles for shadow casting +void ExtractBrushEntityShadowCasters ( void ); +void AddBrushesForRayTrace ( void ); + +void BaseLightForFace( dface_t *f, Vector& light, float *parea, Vector& reflectivity ); +void CreateDirectLights (void); +void GetPhongNormal( int facenum, Vector const& spot, Vector& phongnormal ); +int LightForString( char *pLight, Vector& intensity ); +void MakeTransfer( int ndxPatch1, int ndxPatch2, transfer_t *all_transfers ); +void MakeScales( int ndxPatch, transfer_t *all_transfers ); + +// Run startup code like initialize mathlib. +void VRAD_Init(); + +// Load the BSP file and prepare to do the lighting. +// This is called after any command-line parameters have been set. +void VRAD_LoadBSP( char const *pFilename ); + +int VRAD_Main(int argc, char **argv); + +// This performs an actual lighting pass. +// Returns true if the process was interrupted (with g_bInterrupt). +bool RadWorld_Go(); + +dleaf_t *PointInLeaf (Vector const& point); +int ClusterFromPoint( Vector const& point ); +winding_t *WindingFromFace (dface_t *f, Vector& origin ); + +void WriteWinding (FileHandle_t out, winding_t *w, Vector& color ); +void WriteNormal( FileHandle_t out, Vector const &nPos, Vector const &nDir, + float length, Vector const &color ); +void WriteLine( FileHandle_t out, const Vector &vecPos1, const Vector &vecPos2, const Vector &color ); +void WriteTrace( const char *pFileName, const FourRays &rays, const RayTracingResult& result ); + +#ifdef STATIC_FOG +qboolean IsFog( dface_t * f ); +#endif + +#define CONTENTS_EMPTY 0 +#define TEX_SPECIAL (SURF_SKY|SURF_NOLIGHT) + +//============================================================================= + +// trace.cpp + +bool AddDispCollTreesToWorld( void ); +int PointLeafnum( Vector const &point ); +float TraceLeafBrushes( int leafIndex, const Vector &start, const Vector &end, CBaseTrace &traceOut ); + +//============================================================================= + +// dispinfo.cpp + +struct SSE_sampleLightOutput_t +{ + fltx4 m_flDot[NUM_BUMP_VECTS+1]; + fltx4 m_flFalloff; + fltx4 m_flSunAmount; +}; + +#define GATHERLFLAGS_FORCE_FAST 1 +#define GATHERLFLAGS_IGNORE_NORMALS 2 + +// SSE Gather light stuff +void GatherSampleLightSSE( SSE_sampleLightOutput_t &out, directlight_t *dl, int facenum, + FourVectors const& pos, FourVectors *pNormals, int normalCount, int iThread, + int nLFlags = 0, // GATHERLFLAGS_xxx + int static_prop_to_skip=-1, + float flEpsilon = 0.0 ); +//void GatherSampleSkyLightSSE( SSE_sampleLightOutput_t &out, directlight_t *dl, int facenum, +// FourVectors const& pos, FourVectors *pNormals, int normalCount, int iThread, +// int nLFlags = 0, +// int static_prop_to_skip=-1, +// float flEpsilon = 0.0 ); +//void GatherSampleAmbientSkySSE( SSE_sampleLightOutput_t &out, directlight_t *dl, int facenum, +// FourVectors const& pos, FourVectors *pNormals, int normalCount, int iThread, +// int nLFlags = 0, // GATHERLFLAGS_xxx +// int static_prop_to_skip=-1, +// float flEpsilon = 0.0 ); +//void GatherSampleStandardLightSSE( SSE_sampleLightOutput_t &out, directlight_t *dl, int facenum, +// FourVectors const& pos, FourVectors *pNormals, int normalCount, int iThread, +// int nLFlags = 0, // GATHERLFLAGS_xxx +// int static_prop_to_skip=-1, +// float flEpsilon = 0.0 ); + +//----------------------------------------------------------------------------- +// VRad Displacements +//----------------------------------------------------------------------------- + +struct facelight_t; +typedef struct radial_s radial_t; +struct lightinfo_t; + +// NOTE: should probably come up with a bsptreetested_t struct or something, +// see below (PropTested_t) +struct DispTested_t +{ + int m_Enum; + int *m_pTested; +}; + +class IVRadDispMgr +{ +public: + // creation/destruction + virtual void Init( void ) = 0; + virtual void Shutdown( void ) = 0; + + // "CalcPoints" + virtual bool BuildDispSamples( lightinfo_t *pLightInfo, facelight_t *pFaceLight, int ndxFace ) = 0; + virtual bool BuildDispLuxels( lightinfo_t *pLightInfo, facelight_t *pFaceLight, int ndxFace ) = 0; + virtual bool BuildDispSamplesAndLuxels_DoFast( lightinfo_t *pLightInfo, facelight_t *pFaceLight, int ndxFace ) = 0; + + // patching functions + virtual void MakePatches( void ) = 0; + virtual void SubdividePatch( int iPatch ) = 0; + + // pre "FinalLightFace" + virtual void InsertSamplesDataIntoHashTable( void ) = 0; + virtual void InsertPatchSampleDataIntoHashTable( void ) = 0; + + // "FinalLightFace" + virtual radial_t *BuildLuxelRadial( int ndxFace, int ndxStyle, bool bBump ) = 0; + virtual bool SampleRadial( int ndxFace, radial_t *pRadial, Vector const &vPos, int ndxLxl, LightingValue_t *pLightSample, int sampleCount, bool bPatch ) = 0; + virtual radial_t *BuildPatchRadial( int ndxFace, bool bBump ) = 0; + + // utility + virtual void GetDispSurfNormal( int ndxFace, Vector &pt, Vector &ptNormal, bool bInside ) = 0; + virtual void GetDispSurf( int ndxFace, CVRADDispColl **ppDispTree ) = 0; + + // bsp tree functions + virtual bool ClipRayToDisp( DispTested_t &dispTested, Ray_t const &ray ) = 0; + virtual bool ClipRayToDispInLeaf( DispTested_t &dispTested, Ray_t const &ray, int ndxLeaf ) = 0; + virtual void ClipRayToDispInLeaf( DispTested_t &dispTested, Ray_t const &ray, + int ndxLeaf, float& dist, dface_t*& pFace, Vector2D& luxelCoord ) = 0; + virtual void ClipRayToDispInLeaf( DispTested_t &dispTested, Ray_t const &ray, + int ndxLeaf, float& dist, Vector *pNormal ) = 0; + virtual void StartRayTest( DispTested_t &dispTested ) = 0; + virtual void AddPolysForRayTrace() = 0; + + // general timing -- should be moved!! + virtual void StartTimer( const char *name ) = 0; + virtual void EndTimer( void ) = 0; +}; + +IVRadDispMgr *StaticDispMgr( void ); + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline bool ValidDispFace( dface_t *pFace ) +{ + if( !pFace ) { return false; } + if( pFace->dispinfo == -1 ) { return false; } + if( pFace->numedges != 4 ) { return false; } + + return true; +} + +#define SAMPLEHASH_VOXEL_SIZE 64.0f +typedef unsigned int SampleHandle_t; // the upper 16 bits = facelight index (works because max face are 65536) + // the lower 16 bits = sample index inside of facelight +struct sample_t; +struct SampleData_t +{ + unsigned short x, y, z; + CUtlVector m_Samples; +}; + +struct PatchSampleData_t +{ + unsigned short x, y, z; + CUtlVector m_ndxPatches; +}; + +UtlHashHandle_t SampleData_AddSample( sample_t *pSample, SampleHandle_t sampleHandle ); +void PatchSampleData_AddSample( CPatch *pPatch, int ndxPatch ); +unsigned short IncrementPatchIterationKey(); +void SampleData_Log( void ); + +extern CUtlHash g_SampleHashTable; +extern CUtlHash g_PatchSampleHashTable; + +extern int samplesAdded; +extern int patchSamplesAdded; + +//----------------------------------------------------------------------------- +// Computes lighting for the detail props +//----------------------------------------------------------------------------- + +void ComputeDetailPropLighting( int iThread ); +void ComputeIndirectLightingAtPoint( Vector &position, Vector &normal, Vector &outColor, + int iThread, bool force_fast = false, bool bIgnoreNormals = false ); + +//----------------------------------------------------------------------------- +// VRad static props +//----------------------------------------------------------------------------- +class IPhysicsCollision; +struct PropTested_t +{ + int m_Enum; + int* m_pTested; + IPhysicsCollision *pThreadedCollision; +}; + +class IVradStaticPropMgr +{ +public: + // methods of IStaticPropMgr + virtual void Init() = 0; + virtual void Shutdown() = 0; + virtual void ComputeLighting( int iThread ) = 0; + virtual void AddPolysForRayTrace() = 0; +}; + +//extern PropTested_t s_PropTested[MAX_TOOL_THREADS+1]; +extern DispTested_t s_DispTested[MAX_TOOL_THREADS+1]; + +IVradStaticPropMgr* StaticPropMgr(); + +extern float ComputeCoverageFromTexture( float b0, float b1, float b2, int32 hitID ); + +#endif // VRAD_H diff --git a/mp/src/utils/vrad/vrad_dispcoll.cpp b/mp/src/utils/vrad/vrad_dispcoll.cpp new file mode 100644 index 00000000..df69a4ac --- /dev/null +++ b/mp/src/utils/vrad/vrad_dispcoll.cpp @@ -0,0 +1,1080 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "vrad.h" +#include "VRAD_DispColl.h" +#include "DispColl_Common.h" +#include "radial.h" +#include "CollisionUtils.h" +#include "tier0\dbg.h" + +#define SAMPLE_BBOX_SLOP 5.0f +#define TRIEDGE_EPSILON 0.001f + +float g_flMaxDispSampleSize = 512.0f; + +static FileHandle_t pDispFile = FILESYSTEM_INVALID_HANDLE; + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +CVRADDispColl::CVRADDispColl() +{ + m_iParent = -1; + + m_flSampleRadius2 = 0.0f; + m_flPatchSampleRadius2 = 0.0f; + + m_flSampleWidth = 0.0f; + m_flSampleHeight = 0.0f; + + m_aLuxelCoords.Purge(); + m_aVertNormals.Purge(); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +CVRADDispColl::~CVRADDispColl() +{ + m_aLuxelCoords.Purge(); + m_aVertNormals.Purge(); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool CVRADDispColl::Create( CCoreDispInfo *pDisp ) +{ + // Base class create. + if( !CDispCollTree::Create( pDisp ) ) + return false; + + // Allocate VRad specific memory. + m_aLuxelCoords.SetSize( GetSize() ); + m_aVertNormals.SetSize( GetSize() ); + + // VRad specific base surface data. + CCoreDispSurface *pSurf = pDisp->GetSurface(); + m_iParent = pSurf->GetHandle(); + + // VRad specific displacement surface data. + for ( int iVert = 0; iVert < m_aVerts.Count(); ++iVert ) + { + pDisp->GetNormal( iVert, m_aVertNormals[iVert] ); + pDisp->GetLuxelCoord( 0, iVert, m_aLuxelCoords[iVert] ); + } + + // Re-calculate the lightmap size (in uv) so that the luxels give + // a better world-space uniform approx. due to the non-linear nature + // of the displacement surface in uv-space + dface_t *pFace = &g_pFaces[m_iParent]; + if( pFace ) + { + CalcSampleRadius2AndBox( pFace ); + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CVRADDispColl::CalcSampleRadius2AndBox( dface_t *pFace ) +{ + // Get the luxel sample size. + texinfo_t *pTexInfo = &texinfo[pFace->texinfo]; + Assert ( pTexInfo ); + if ( !pTexInfo ) + return; + + // Todo: Width = Height now, should change all the code to look at one value. + Vector vecTmp( pTexInfo->lightmapVecsLuxelsPerWorldUnits[0][0], + pTexInfo->lightmapVecsLuxelsPerWorldUnits[0][1], + pTexInfo->lightmapVecsLuxelsPerWorldUnits[0][2] ); + float flWidth = 1.0f / VectorLength( vecTmp ); + float flHeight = flWidth; + + // Save off the sample width and height. + m_flSampleWidth = flWidth; + m_flSampleHeight = flHeight; + + // Calculate the sample radius squared. + float flSampleRadius = sqrt( ( ( flWidth * flWidth ) + ( flHeight * flHeight ) ) ) * 2.2f;//RADIALDIST2; + if ( flSampleRadius > g_flMaxDispSampleSize ) + { + flSampleRadius = g_flMaxDispSampleSize; + } + m_flSampleRadius2 = flSampleRadius * flSampleRadius; + + // Calculate the patch radius - the max sample edge length * the number of luxels per edge "chop." + float flSampleSize = max( m_flSampleWidth, m_flSampleHeight ); + float flPatchSampleRadius = flSampleSize * dispchop * 2.2f; + if ( flPatchSampleRadius > g_MaxDispPatchRadius ) + { + flPatchSampleRadius = g_MaxDispPatchRadius; + Warning( "Patch Sample Radius Clamped!\n" ); + } + m_flPatchSampleRadius2 = flPatchSampleRadius * flPatchSampleRadius; +} + +//----------------------------------------------------------------------------- +// Purpose: Get the min/max of the displacement surface. +//----------------------------------------------------------------------------- +void CVRADDispColl::GetSurfaceMinMax( Vector &boxMin, Vector &boxMax ) +{ + // Initialize the minimum and maximum box + boxMin = m_aVerts[0]; + boxMax = m_aVerts[0]; + + for( int i = 1; i < m_aVerts.Count(); i++ ) + { + if( m_aVerts[i].x < boxMin.x ) { boxMin.x = m_aVerts[i].x; } + if( m_aVerts[i].y < boxMin.y ) { boxMin.y = m_aVerts[i].y; } + if( m_aVerts[i].z < boxMin.z ) { boxMin.z = m_aVerts[i].z; } + + if( m_aVerts[i].x > boxMax.x ) { boxMax.x = m_aVerts[i].x; } + if( m_aVerts[i].y > boxMax.y ) { boxMax.y = m_aVerts[i].y; } + if( m_aVerts[i].z > boxMax.z ) { boxMax.z = m_aVerts[i].z; } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Find the minor projection axes based on the given normal. +//----------------------------------------------------------------------------- +void CVRADDispColl::GetMinorAxes( Vector const &vecNormal, int &nAxis0, int &nAxis1 ) +{ + nAxis0 = 0; + nAxis1 = 1; + + if( FloatMakePositive( vecNormal.x ) > FloatMakePositive( vecNormal.y ) ) + { + if( FloatMakePositive( vecNormal.x ) > FloatMakePositive( vecNormal.z ) ) + { + nAxis0 = 1; + nAxis1 = 2; + } + } + else + { + if( FloatMakePositive( vecNormal.y ) > FloatMakePositive( vecNormal.z ) ) + { + nAxis0 = 0; + nAxis1 = 2; + } + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CVRADDispColl::BaseFacePlaneToDispUV( Vector const &vecPlanePt, Vector2D &dispUV ) +{ + PointInQuadToBarycentric( m_vecSurfPoints[0], m_vecSurfPoints[3], m_vecSurfPoints[2], m_vecSurfPoints[1], vecPlanePt, dispUV ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CVRADDispColl::DispUVToSurfPoint( Vector2D const &dispUV, Vector &vecPoint, float flPushEps ) +{ + // Check to see that the point is on the surface. + if ( dispUV.x < 0.0f || dispUV.x > 1.0f || dispUV.y < 0.0f || dispUV.y > 1.0f ) + return; + + // Get the displacement power. + int nWidth = ( ( 1 << m_nPower ) + 1 ); + int nHeight = nWidth; + + // Scale the U, V coordinates to the displacement grid size. + float flU = dispUV.x * static_cast( nWidth - 1.000001f ); + float flV = dispUV.y * static_cast( nHeight - 1.000001f ); + + // Find the base U, V. + int nSnapU = static_cast( flU ); + int nSnapV = static_cast( flV ); + + // Use this to get the triangle orientation. + bool bOdd = ( ( ( nSnapV * nWidth ) + nSnapU ) % 2 == 1 ); + + // Top Left to Bottom Right + if( bOdd ) + { + DispUVToSurf_TriTLToBR( vecPoint, flPushEps, flU, flV, nSnapU, nSnapV, nWidth, nHeight ); + } + // Bottom Left to Top Right + else + { + DispUVToSurf_TriBLToTR( vecPoint, flPushEps, flU, flV, nSnapU, nSnapV, nWidth, nHeight ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CVRADDispColl::DispUVToSurf_TriTLToBR( Vector &vecPoint, float flPushEps, + float flU, float flV, int nSnapU, int nSnapV, + int nWidth, int nHeight ) +{ + int nNextU = nSnapU + 1; + int nNextV = nSnapV + 1; + if ( nNextU == nWidth) { --nNextU; } + if ( nNextV == nHeight ) { --nNextV; } + + float flFracU = flU - static_cast( nSnapU ); + float flFracV = flV - static_cast( nSnapV ); + + if( ( flFracU + flFracV ) >= ( 1.0f + TRIEDGE_EPSILON ) ) + { + int nIndices[3]; + nIndices[0] = nNextV * nWidth + nSnapU; + nIndices[1] = nNextV * nWidth + nNextU; + nIndices[2] = nSnapV * nWidth + nNextU; + + Vector edgeU = m_aVerts[nIndices[0]] - m_aVerts[nIndices[1]]; + Vector edgeV = m_aVerts[nIndices[2]] - m_aVerts[nIndices[1]]; + vecPoint = m_aVerts[nIndices[1]] + edgeU * ( 1.0f - flFracU ) + edgeV * ( 1.0f - flFracV ); + + if ( flPushEps != 0.0f ) + { + Vector vecNormal; + vecNormal = CrossProduct( edgeU, edgeV ); + VectorNormalize( vecNormal ); + vecPoint += ( vecNormal * flPushEps ); + } + } + else + { + int nIndices[3]; + nIndices[0] = nSnapV * nWidth + nSnapU; + nIndices[1] = nNextV * nWidth + nSnapU; + nIndices[2] = nSnapV * nWidth + nNextU; + + Vector edgeU = m_aVerts[nIndices[2]] - m_aVerts[nIndices[0]]; + Vector edgeV = m_aVerts[nIndices[1]] - m_aVerts[nIndices[0]]; + vecPoint = m_aVerts[nIndices[0]] + edgeU * flFracU + edgeV * flFracV; + + if ( flPushEps != 0.0f ) + { + Vector vecNormal; + vecNormal = CrossProduct( edgeU, edgeV ); + VectorNormalize( vecNormal ); + vecPoint += ( vecNormal * flPushEps ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CVRADDispColl::DispUVToSurf_TriBLToTR( Vector &vecPoint, float flPushEps, + float flU, float flV, int nSnapU, int nSnapV, + int nWidth, int nHeight ) +{ + int nNextU = nSnapU + 1; + int nNextV = nSnapV + 1; + if ( nNextU == nWidth) { --nNextU; } + if ( nNextV == nHeight ) { --nNextV; } + + float flFracU = flU - static_cast( nSnapU ); + float flFracV = flV - static_cast( nSnapV ); + + if( flFracU < flFracV ) + { + int nIndices[3]; + nIndices[0] = nSnapV * nWidth + nSnapU; + nIndices[1] = nNextV * nWidth + nSnapU; + nIndices[2] = nNextV * nWidth + nNextU; + + Vector edgeU = m_aVerts[nIndices[2]] - m_aVerts[nIndices[1]]; + Vector edgeV = m_aVerts[nIndices[0]] - m_aVerts[nIndices[1]]; + vecPoint = m_aVerts[nIndices[1]] + edgeU * flFracU + edgeV * ( 1.0f - flFracV ); + + if ( flPushEps != 0.0f ) + { + Vector vecNormal; + vecNormal = CrossProduct( edgeV, edgeU ); + VectorNormalize( vecNormal ); + vecPoint += ( vecNormal * flPushEps ); + } + } + else + { + int nIndices[3]; + nIndices[0] = nSnapV * nWidth + nSnapU; + nIndices[1] = nNextV * nWidth + nNextU; + nIndices[2] = nSnapV * nWidth + nNextU; + + Vector edgeU = m_aVerts[nIndices[0]] - m_aVerts[nIndices[2]]; + Vector edgeV = m_aVerts[nIndices[1]] - m_aVerts[nIndices[2]]; + vecPoint = m_aVerts[nIndices[2]] + edgeU * ( 1.0f - flFracU ) + edgeV * flFracV; + + if ( flPushEps != 0.0f ) + { + Vector vecNormal; + vecNormal = CrossProduct( edgeV, edgeU ); + VectorNormalize( vecNormal ); + vecPoint += ( vecNormal * flPushEps ); + } + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CVRADDispColl::DispUVToSurfNormal( Vector2D const &dispUV, Vector &vecNormal ) +{ + // Check to see that the point is on the surface. + if ( dispUV.x < 0.0f || dispUV.x > 1.0f || dispUV.y < 0.0f || dispUV.y > 1.0f ) + return; + + // Get the displacement power. + int nWidth = ( ( 1 << m_nPower ) + 1 ); + int nHeight = nWidth; + + // Scale the U, V coordinates to the displacement grid size. + float flU = dispUV.x * static_cast( nWidth - 1.000001f ); + float flV = dispUV.y * static_cast( nHeight - 1.000001f ); + + // Find the base U, V. + int nSnapU = static_cast( flU ); + int nSnapV = static_cast( flV ); + + int nNextU = nSnapU + 1; + int nNextV = nSnapV + 1; + if ( nNextU == nWidth) { --nNextU; } + if ( nNextV == nHeight ) { --nNextV; } + + float flFracU = flU - static_cast( nSnapU ); + float flFracV = flV - static_cast( nSnapV ); + + // Get the four normals "around" the "spot" + int iQuad[VRAD_QUAD_SIZE]; + iQuad[0] = ( nSnapV * nWidth ) + nSnapU; + iQuad[1] = ( nNextV * nWidth ) + nSnapU; + iQuad[2] = ( nNextV * nWidth ) + nNextU; + iQuad[3] = ( nSnapV * nWidth ) + nNextU; + + // Find the blended normal (bi-linear). + Vector vecTmpNormals[2], vecBlendedNormals[2], vecDispNormals[4]; + + for ( int iVert = 0; iVert < VRAD_QUAD_SIZE; ++iVert ) + { + GetVertNormal( iQuad[iVert], vecDispNormals[iVert] ); + } + + vecTmpNormals[0] = vecDispNormals[0] * ( 1.0f - flFracU ); + vecTmpNormals[1] = vecDispNormals[3] * flFracU; + vecBlendedNormals[0] = vecTmpNormals[0] + vecTmpNormals[1]; + VectorNormalize( vecBlendedNormals[0] ); + + vecTmpNormals[0] = vecDispNormals[1] * ( 1.0f - flFracU ); + vecTmpNormals[1] = vecDispNormals[2] * flFracU; + vecBlendedNormals[1] = vecTmpNormals[0] + vecTmpNormals[1]; + VectorNormalize( vecBlendedNormals[1] ); + + vecTmpNormals[0] = vecBlendedNormals[0] * ( 1.0f - flFracV ); + vecTmpNormals[1] = vecBlendedNormals[1] * flFracV; + + vecNormal = vecTmpNormals[0] + vecTmpNormals[1]; + VectorNormalize( vecNormal ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : float +//----------------------------------------------------------------------------- +float CVRADDispColl::CreateParentPatches( void ) +{ + // Save the total surface area of the displacement. + float flTotalArea = 0.0f; + + // Get the number of displacement subdivisions. + int nInterval = GetWidth(); + + Vector vecPoints[4]; + vecPoints[0].Init( m_aVerts[0].x, m_aVerts[0].y, m_aVerts[0].z ); + vecPoints[1].Init( m_aVerts[(nInterval*(nInterval-1))].x, m_aVerts[(nInterval*(nInterval-1))].y, m_aVerts[(nInterval*(nInterval-1))].z ); + vecPoints[2].Init( m_aVerts[((nInterval*nInterval)-1)].x, m_aVerts[((nInterval*nInterval)-1)].y, m_aVerts[((nInterval*nInterval)-1)].z ); + vecPoints[3].Init( m_aVerts[(nInterval-1)].x, m_aVerts[(nInterval-1)].y, m_aVerts[(nInterval-1)].z ); + + // Create and initialize the patch. + int iPatch = g_Patches.AddToTail(); + if ( iPatch == g_Patches.InvalidIndex() ) + return flTotalArea; + + // Keep track of the area of the patches. + float flArea = 0.0f; + if ( !InitParentPatch( iPatch, vecPoints, flArea ) ) + { + g_Patches.Remove( iPatch ); + flArea = 0.0f; + } + + // Return the displacement area. + return flArea; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : iParentPatch - +// nLevel - +//----------------------------------------------------------------------------- +void CVRADDispColl::CreateChildPatchesFromRoot( int iParentPatch, int *pChildPatch ) +{ + // Initialize the child patch indices. + pChildPatch[0] = g_Patches.InvalidIndex(); + pChildPatch[1] = g_Patches.InvalidIndex(); + + // Get the number of displacement subdivisions. + int nInterval = GetWidth(); + + // Get the parent patch. + CPatch *pParentPatch = &g_Patches[iParentPatch]; + if ( !pParentPatch ) + return; + + // Split along the longest edge. + Vector vecEdges[4]; + vecEdges[0] = pParentPatch->winding->p[1] - pParentPatch->winding->p[0]; + vecEdges[1] = pParentPatch->winding->p[2] - pParentPatch->winding->p[1]; + vecEdges[2] = pParentPatch->winding->p[3] - pParentPatch->winding->p[2]; + vecEdges[3] = pParentPatch->winding->p[3] - pParentPatch->winding->p[0]; + + // Should the patch be subdivided - check the area. + float flMaxLength = max( m_flSampleWidth, m_flSampleHeight ); + float flMinEdgeLength = flMaxLength * dispchop; + + // Find the longest edge. + float flEdgeLength = 0.0f; + int iLongEdge = -1; + for ( int iEdge = 0; iEdge < 4; ++iEdge ) + { + float flLength = vecEdges[iEdge].Length(); + if ( flEdgeLength < flLength ) + { + flEdgeLength = vecEdges[iEdge].Length(); + iLongEdge = iEdge; + } + } + + // Small enough already, return. + if ( flEdgeLength < flMinEdgeLength ) + return; + + // Test area as well so we don't allow slivers. + float flMinArea = ( dispchop * flMaxLength ) * ( dispchop * flMaxLength ); + Vector vecNormal = vecEdges[3].Cross( vecEdges[0] ); + float flTestArea = VectorNormalize( vecNormal ); + if ( flTestArea < flMinArea ) + return; + + // Get the points for the first triangle. + int iPoints[3]; + Vector vecPoints[3]; + float flArea; + + iPoints[0] = ( nInterval * nInterval ) - 1; + iPoints[1] = 0; + iPoints[2] = nInterval * ( nInterval - 1 ); + for ( int iPoint = 0; iPoint < 3; ++iPoint ) + { + VectorCopy( m_aVerts[iPoints[iPoint]], vecPoints[iPoint] ); + } + + // Create and initialize the patch. + pChildPatch[0] = g_Patches.AddToTail(); + if ( pChildPatch[0] == g_Patches.InvalidIndex() ) + return; + + if ( !InitPatch( pChildPatch[0], iParentPatch, 0, vecPoints, iPoints, flArea ) ) + { + g_Patches.Remove( pChildPatch[0] ); + pChildPatch[0] = g_Patches.InvalidIndex(); + return; + } + + // Get the points for the second triangle. + iPoints[0] = 0; + iPoints[1] = ( nInterval * nInterval ) - 1; + iPoints[2] = nInterval - 1; + for ( int iPoint = 0; iPoint < 3; ++iPoint ) + { + VectorCopy( m_aVerts[iPoints[iPoint]], vecPoints[iPoint] ); + } + + // Create and initialize the patch. + pChildPatch[1] = g_Patches.AddToTail(); + if ( pChildPatch[1] == g_Patches.InvalidIndex() ) + { + g_Patches.Remove( pChildPatch[0] ); + pChildPatch[0] = g_Patches.InvalidIndex(); + return; + } + + if ( !InitPatch( pChildPatch[1], iParentPatch, 1, vecPoints, iPoints, flArea ) ) + { + g_Patches.Remove( pChildPatch[0] ); + pChildPatch[0] = g_Patches.InvalidIndex(); + g_Patches.Remove( pChildPatch[1] ); + pChildPatch[1] = g_Patches.InvalidIndex(); + return; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : flMinArea - +// Output : float +//----------------------------------------------------------------------------- +void CVRADDispColl::CreateChildPatches( int iParentPatch, int nLevel ) +{ + // Get the parent patch. + CPatch *pParentPatch = &g_Patches[iParentPatch]; + if ( !pParentPatch ) + return; + + // The root face is a quad - special case. + if ( pParentPatch->winding->numpoints == 4 ) + { + int iChildPatch[2]; + CreateChildPatchesFromRoot( iParentPatch, iChildPatch ); + if ( iChildPatch[0] != g_Patches.InvalidIndex() && iChildPatch[1] != g_Patches.InvalidIndex() ) + { + CreateChildPatches( iChildPatch[0], 0 ); + CreateChildPatches( iChildPatch[1], 0 ); + } + return; + } + + // Calculate the the area of the patch (triangle!). + Assert( pParentPatch->winding->numpoints == 3 ); + if ( pParentPatch->winding->numpoints != 3 ) + return; + + // Should the patch be subdivided - check the area. + float flMaxLength = max( m_flSampleWidth, m_flSampleHeight ); + float flMinEdgeLength = flMaxLength * dispchop; + + // Split along the longest edge. + Vector vecEdges[3]; + vecEdges[0] = pParentPatch->winding->p[1] - pParentPatch->winding->p[0]; + vecEdges[1] = pParentPatch->winding->p[2] - pParentPatch->winding->p[0]; + vecEdges[2] = pParentPatch->winding->p[2] - pParentPatch->winding->p[1]; + + // Find the longest edge. + float flEdgeLength = 0.0f; + int iLongEdge = -1; + for ( int iEdge = 0; iEdge < 3; ++iEdge ) + { + if ( flEdgeLength < vecEdges[iEdge].Length() ) + { + flEdgeLength = vecEdges[iEdge].Length(); + iLongEdge = iEdge; + } + } + + // Small enough already, return. + if ( flEdgeLength < flMinEdgeLength ) + return; + + // Test area as well so we don't allow slivers. + float flMinArea = ( dispchop * flMaxLength ) * ( dispchop * flMaxLength ) * 0.5f; + Vector vecNormal = vecEdges[1].Cross( vecEdges[0] ); + float flTestArea = VectorNormalize( vecNormal ); + flTestArea *= 0.5f; + if ( flTestArea < flMinArea ) + return; + + // Check to see if any more displacement verts exist - go to subdivision if not. + if ( nLevel >= ( m_nPower * 2 ) ) + { + CreateChildPatchesSub( iParentPatch ); + return; + } + + int nChildIndices[2][3]; + int nNewIndex = ( pParentPatch->indices[1] + pParentPatch->indices[0] ) / 2; + nChildIndices[0][0] = pParentPatch->indices[2]; + nChildIndices[0][1] = pParentPatch->indices[0]; + nChildIndices[0][2] = nNewIndex; + + nChildIndices[1][0] = pParentPatch->indices[1]; + nChildIndices[1][1] = pParentPatch->indices[2]; + nChildIndices[1][2] = nNewIndex; + + Vector vecChildPoints[2][3]; + for ( int iTri = 0; iTri < 2; ++iTri ) + { + for ( int iPoint = 0; iPoint < 3; ++iPoint ) + { + VectorCopy( m_aVerts[nChildIndices[iTri][iPoint]], vecChildPoints[iTri][iPoint] ); + } + } + + // Create and initialize the children patches. + int iChildPatch[2] = { -1, -1 }; + for ( int iChild = 0; iChild < 2; ++iChild ) + { + iChildPatch[iChild] = g_Patches.AddToTail(); + + float flArea = 0.0f; + if ( !InitPatch( iChildPatch[iChild], iParentPatch, iChild, vecChildPoints[iChild], nChildIndices[iChild], flArea ) ) + { + if ( iChild == 0 ) + { + pParentPatch->child1 = g_Patches.InvalidIndex(); + g_Patches.Remove( iChildPatch[iChild] ); + break; + } + else + { + pParentPatch->child1 = g_Patches.InvalidIndex(); + pParentPatch->child2 = g_Patches.InvalidIndex(); + g_Patches.Remove( iChildPatch[iChild] ); + g_Patches.Remove( iChildPatch[0] ); + } + } + } + + // Continue creating children patches. + int nNewLevel = ++nLevel; + CreateChildPatches( iChildPatch[0], nNewLevel ); + CreateChildPatches( iChildPatch[1], nNewLevel ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : flMinArea - +// Output : float +//----------------------------------------------------------------------------- +void CVRADDispColl::CreateChildPatchesSub( int iParentPatch ) +{ + // Get the parent patch. + CPatch *pParentPatch = &g_Patches[iParentPatch]; + if ( !pParentPatch ) + return; + + // Calculate the the area of the patch (triangle!). + Assert( pParentPatch->winding->numpoints == 3 ); + if ( pParentPatch->winding->numpoints != 3 ) + return; + + // Should the patch be subdivided - check the area. + float flMaxLength = max( m_flSampleWidth, m_flSampleHeight ); + float flMinEdgeLength = flMaxLength * dispchop; + + // Split along the longest edge. + Vector vecEdges[3]; + vecEdges[0] = pParentPatch->winding->p[1] - pParentPatch->winding->p[0]; + vecEdges[1] = pParentPatch->winding->p[2] - pParentPatch->winding->p[0]; + vecEdges[2] = pParentPatch->winding->p[2] - pParentPatch->winding->p[1]; + + // Find the longest edge. + float flEdgeLength = 0.0f; + int iLongEdge = -1; + for ( int iEdge = 0; iEdge < 3; ++iEdge ) + { + if ( flEdgeLength < vecEdges[iEdge].Length() ) + { + flEdgeLength = vecEdges[iEdge].Length(); + iLongEdge = iEdge; + } + } + + // Small enough already, return. + if ( flEdgeLength < flMinEdgeLength ) + return; + + // Test area as well so we don't allow slivers. + float flMinArea = ( dispchop * flMaxLength ) * ( dispchop * flMaxLength ) * 0.5f; + Vector vecNormal = vecEdges[1].Cross( vecEdges[0] ); + float flTestArea = VectorNormalize( vecNormal ); + flTestArea *= 0.5f; + if ( flTestArea < flMinArea ) + return; + + // Create children patchs - 2 of them. + Vector vecChildPoints[2][3]; + switch ( iLongEdge ) + { + case 0: + { + vecChildPoints[0][0] = pParentPatch->winding->p[0]; + vecChildPoints[0][1] = ( pParentPatch->winding->p[0] + pParentPatch->winding->p[1] ) * 0.5f; + vecChildPoints[0][2] = pParentPatch->winding->p[2]; + + vecChildPoints[1][0] = ( pParentPatch->winding->p[0] + pParentPatch->winding->p[1] ) * 0.5f; + vecChildPoints[1][1] = pParentPatch->winding->p[1]; + vecChildPoints[1][2] = pParentPatch->winding->p[2]; + break; + } + case 1: + { + vecChildPoints[0][0] = pParentPatch->winding->p[0]; + vecChildPoints[0][1] = pParentPatch->winding->p[1]; + vecChildPoints[0][2] = ( pParentPatch->winding->p[1] + pParentPatch->winding->p[2] ) * 0.5f; + + vecChildPoints[1][0] = ( pParentPatch->winding->p[1] + pParentPatch->winding->p[2] ) * 0.5f; + vecChildPoints[1][1] = pParentPatch->winding->p[2]; + vecChildPoints[1][2] = pParentPatch->winding->p[0]; + break; + } + case 2: + { + vecChildPoints[0][0] = pParentPatch->winding->p[0]; + vecChildPoints[0][1] = pParentPatch->winding->p[1]; + vecChildPoints[0][2] = ( pParentPatch->winding->p[0] + pParentPatch->winding->p[2] ) * 0.5f; + + vecChildPoints[1][0] = ( pParentPatch->winding->p[0] + pParentPatch->winding->p[2] ) * 0.5f; + vecChildPoints[1][1] = pParentPatch->winding->p[1]; + vecChildPoints[1][2] = pParentPatch->winding->p[2]; + break; + } + } + + + // Create and initialize the children patches. + int iChildPatch[2] = { 0, 0 }; + int nChildIndices[3] = { -1, -1, -1 }; + for ( int iChild = 0; iChild < 2; ++iChild ) + { + iChildPatch[iChild] = g_Patches.AddToTail(); + + float flArea = 0.0f; + if ( !InitPatch( iChildPatch[iChild], iParentPatch, iChild, vecChildPoints[iChild], nChildIndices, flArea ) ) + { + if ( iChild == 0 ) + { + pParentPatch->child1 = g_Patches.InvalidIndex(); + g_Patches.Remove( iChildPatch[iChild] ); + break; + } + else + { + pParentPatch->child1 = g_Patches.InvalidIndex(); + pParentPatch->child2 = g_Patches.InvalidIndex(); + g_Patches.Remove( iChildPatch[iChild] ); + g_Patches.Remove( iChildPatch[0] ); + } + } + } + + // Continue creating children patches. + CreateChildPatchesSub( iChildPatch[0] ); + CreateChildPatchesSub( iChildPatch[1] ); +} + +int PlaneTypeForNormal (Vector& normal) +{ + vec_t ax, ay, az; + + // NOTE: should these have an epsilon around 1.0? + if (normal[0] == 1.0 || normal[0] == -1.0) + return PLANE_X; + if (normal[1] == 1.0 || normal[1] == -1.0) + return PLANE_Y; + if (normal[2] == 1.0 || normal[2] == -1.0) + return PLANE_Z; + + ax = fabs(normal[0]); + ay = fabs(normal[1]); + az = fabs(normal[2]); + + if (ax >= ay && ax >= az) + return PLANE_ANYX; + if (ay >= ax && ay >= az) + return PLANE_ANYY; + return PLANE_ANYZ; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : iPatch - +// iParentPatch - +// iChild - +// *pPoints - +// *pIndices - +// &flArea - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CVRADDispColl::InitParentPatch( int iPatch, Vector *pPoints, float &flArea ) +{ + // Get the current patch. + CPatch *pPatch = &g_Patches[iPatch]; + if ( !pPatch ) + return false; + + // Clear the patch data. + memset( pPatch, 0, sizeof( CPatch ) ); + + // This is a parent. + pPatch->ndxNext = g_FacePatches.Element( GetParentIndex() ); + g_FacePatches[GetParentIndex()] = iPatch; + pPatch->faceNumber = GetParentIndex(); + + // Initialize parent and children indices. + pPatch->child1 = g_Patches.InvalidIndex(); + pPatch->child2 = g_Patches.InvalidIndex(); + pPatch->parent = g_Patches.InvalidIndex(); + pPatch->ndxNextClusterChild = g_Patches.InvalidIndex(); + pPatch->ndxNextParent = g_Patches.InvalidIndex(); + + Vector vecEdges[2]; + vecEdges[0] = pPoints[1] - pPoints[0]; + vecEdges[1] = pPoints[3] - pPoints[0]; + + // Calculate the triangle normal and area. + Vector vecNormal = vecEdges[1].Cross( vecEdges[0] ); + flArea = VectorNormalize( vecNormal ); + + // Initialize the patch scale. + pPatch->scale[0] = pPatch->scale[1] = 1.0f; + + // Set the patch chop - minchop (that is what the minimum area is based on). + pPatch->chop = dispchop; + + // Displacements are not sky! + pPatch->sky = false; + + // Copy the winding. + Vector vecCenter( 0.0f, 0.0f, 0.0f ); + pPatch->winding = AllocWinding( 4 ); + pPatch->winding->numpoints = 4; + for ( int iPoint = 0; iPoint < 4; ++iPoint ) + { + VectorCopy( pPoints[iPoint], pPatch->winding->p[iPoint] ); + VectorAdd( pPoints[iPoint], vecCenter, vecCenter ); + } + + // Set the origin and normal. + VectorScale( vecCenter, ( 1.0f / 4.0f ), vecCenter ); + VectorCopy( vecCenter, pPatch->origin ); + VectorCopy( vecNormal, pPatch->normal ); + + // Create the plane. + pPatch->plane = new dplane_t; + if ( !pPatch->plane ) + return false; + + VectorCopy( vecNormal, pPatch->plane->normal ); + pPatch->plane->dist = vecNormal.Dot( pPoints[0] ); + pPatch->plane->type = PlaneTypeForNormal( pPatch->plane->normal ); + pPatch->planeDist = pPatch->plane->dist; + + // Set the area. + pPatch->area = flArea; + + // Calculate the mins/maxs. + Vector vecMin( FLT_MAX, FLT_MAX, FLT_MAX ); + Vector vecMax( FLT_MIN, FLT_MIN, FLT_MIN ); + for ( int iPoint = 0; iPoint < 4; ++iPoint ) + { + for ( int iAxis = 0; iAxis < 3; ++iAxis ) + { + vecMin[iAxis] = min( vecMin[iAxis], pPoints[iPoint][iAxis] ); + vecMax[iAxis] = max( vecMax[iAxis], pPoints[iPoint][iAxis] ); + } + } + + VectorCopy( vecMin, pPatch->mins ); + VectorCopy( vecMax, pPatch->maxs ); + VectorCopy( vecMin, pPatch->face_mins ); + VectorCopy( vecMax, pPatch->face_maxs ); + + // Check for bumpmap. + dface_t *pFace = dfaces + pPatch->faceNumber; + texinfo_t *pTexInfo = &texinfo[pFace->texinfo]; + pPatch->needsBumpmap = pTexInfo->flags & SURF_BUMPLIGHT ? true : false; + + // Misc... + pPatch->m_IterationKey = 0; + + // Calculate the base light, area, and reflectivity. + BaseLightForFace( &g_pFaces[pPatch->faceNumber], pPatch->baselight, &pPatch->basearea, pPatch->reflectivity ); + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pPatch - +// *pPoints - +// &vecNormal - +// flArea - +//----------------------------------------------------------------------------- +bool CVRADDispColl::InitPatch( int iPatch, int iParentPatch, int iChild, Vector *pPoints, int *pIndices, float &flArea ) +{ + // Get the current patch. + CPatch *pPatch = &g_Patches[iPatch]; + if ( !pPatch ) + return false; + + // Clear the patch data. + memset( pPatch, 0, sizeof( CPatch ) ); + + // Setup the parent if we are not the parent. + CPatch *pParentPatch = NULL; + if ( iParentPatch != g_Patches.InvalidIndex() ) + { + // Get the parent patch. + pParentPatch = &g_Patches[iParentPatch]; + if ( !pParentPatch ) + return false; + } + + // Attach the face to the correct lists. + if ( !pParentPatch ) + { + // This is a parent. + pPatch->ndxNext = g_FacePatches.Element( GetParentIndex() ); + g_FacePatches[GetParentIndex()] = iPatch; + pPatch->faceNumber = GetParentIndex(); + } + else + { + pPatch->ndxNext = g_Patches.InvalidIndex(); + pPatch->faceNumber = pParentPatch->faceNumber; + + // Attach to the parent patch. + if ( iChild == 0 ) + { + pParentPatch->child1 = iPatch; + } + else + { + pParentPatch->child2 = iPatch; + } + } + + // Initialize parent and children indices. + pPatch->child1 = g_Patches.InvalidIndex(); + pPatch->child2 = g_Patches.InvalidIndex(); + pPatch->ndxNextClusterChild = g_Patches.InvalidIndex(); + pPatch->ndxNextParent = g_Patches.InvalidIndex(); + pPatch->parent = iParentPatch; + + // Get triangle edges. + Vector vecEdges[3]; + vecEdges[0] = pPoints[1] - pPoints[0]; + vecEdges[1] = pPoints[2] - pPoints[0]; + vecEdges[2] = pPoints[2] - pPoints[1]; + + // Find the longest edge. +// float flEdgeLength = 0.0f; +// for ( int iEdge = 0; iEdge < 3; ++iEdge ) +// { +// if ( flEdgeLength < vecEdges[iEdge].Length() ) +// { +// flEdgeLength = vecEdges[iEdge].Length(); +// } +// } + + // Calculate the triangle normal and area. + Vector vecNormal = vecEdges[1].Cross( vecEdges[0] ); + flArea = VectorNormalize( vecNormal ); + flArea *= 0.5f; + + // Initialize the patch scale. + pPatch->scale[0] = pPatch->scale[1] = 1.0f; + + // Set the patch chop - minchop (that is what the minimum area is based on). + pPatch->chop = dispchop; + + // Displacements are not sky! + pPatch->sky = false; + + // Copy the winding. + Vector vecCenter( 0.0f, 0.0f, 0.0f ); + pPatch->winding = AllocWinding( 3 ); + pPatch->winding->numpoints = 3; + for ( int iPoint = 0; iPoint < 3; ++iPoint ) + { + VectorCopy( pPoints[iPoint], pPatch->winding->p[iPoint] ); + VectorAdd( pPoints[iPoint], vecCenter, vecCenter ); + + pPatch->indices[iPoint] = static_cast( pIndices[iPoint] ); + } + + // Set the origin and normal. + VectorScale( vecCenter, ( 1.0f / 3.0f ), vecCenter ); + VectorCopy( vecCenter, pPatch->origin ); + VectorCopy( vecNormal, pPatch->normal ); + + // Create the plane. + pPatch->plane = new dplane_t; + if ( !pPatch->plane ) + return false; + + VectorCopy( vecNormal, pPatch->plane->normal ); + pPatch->plane->dist = vecNormal.Dot( pPoints[0] ); + pPatch->plane->type = PlaneTypeForNormal( pPatch->plane->normal ); + pPatch->planeDist = pPatch->plane->dist; + + // Set the area. + pPatch->area = flArea; + + // Calculate the mins/maxs. + Vector vecMin( FLT_MAX, FLT_MAX, FLT_MAX ); + Vector vecMax( FLT_MIN, FLT_MIN, FLT_MIN ); + for ( int iPoint = 0; iPoint < 3; ++iPoint ) + { + for ( int iAxis = 0; iAxis < 3; ++iAxis ) + { + vecMin[iAxis] = min( vecMin[iAxis], pPoints[iPoint][iAxis] ); + vecMax[iAxis] = max( vecMax[iAxis], pPoints[iPoint][iAxis] ); + } + } + + VectorCopy( vecMin, pPatch->mins ); + VectorCopy( vecMax, pPatch->maxs ); + + if ( !pParentPatch ) + { + VectorCopy( vecMin, pPatch->face_mins ); + VectorCopy( vecMax, pPatch->face_maxs ); + } + else + { + VectorCopy( pParentPatch->face_mins, pPatch->face_mins ); + VectorCopy( pParentPatch->face_maxs, pPatch->face_maxs ); + } + + // Check for bumpmap. + dface_t *pFace = dfaces + pPatch->faceNumber; + texinfo_t *pTexInfo = &texinfo[pFace->texinfo]; + pPatch->needsBumpmap = pTexInfo->flags & SURF_BUMPLIGHT ? true : false; + + // Misc... + pPatch->m_IterationKey = 0; + + // Get the base light for the face. + if ( !pParentPatch ) + { + BaseLightForFace( &g_pFaces[pPatch->faceNumber], pPatch->baselight, &pPatch->basearea, pPatch->reflectivity ); + } + else + { + VectorCopy( pParentPatch->baselight, pPatch->baselight ); + pPatch->basearea = pParentPatch->basearea; + pPatch->reflectivity = pParentPatch->reflectivity; + } + + return true; +} + +void CVRADDispColl::AddPolysForRayTrace( void ) +{ + if ( !( m_nContents & MASK_OPAQUE ) ) + return; + + for ( int ndxTri = 0; ndxTri < m_aTris.Size(); ndxTri++ ) + { + CDispCollTri *tri = m_aTris.Base() + ndxTri; + int v[3]; + for ( int ndxv = 0; ndxv < 3; ndxv++ ) + v[ndxv] = tri->GetVert(ndxv); + + Vector fullCoverage; + fullCoverage.x = 1.0f; + g_RtEnv.AddTriangle( TRACE_ID_OPAQUE, m_aVerts[v[0]], m_aVerts[v[1]], m_aVerts[v[2]], fullCoverage ); + } +} \ No newline at end of file diff --git a/mp/src/utils/vrad/vrad_dispcoll.h b/mp/src/utils/vrad/vrad_dispcoll.h new file mode 100644 index 00000000..668d3118 --- /dev/null +++ b/mp/src/utils/vrad/vrad_dispcoll.h @@ -0,0 +1,80 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef VRAD_DISPCOLL_H +#define VRAD_DISPCOLL_H +#pragma once + +#include +#include "DispColl_Common.h" + +//============================================================================= +// +// VRAD specific collision +// +#define VRAD_QUAD_SIZE 4 + +struct CPatch; + +class CVRADDispColl : public CDispCollTree +{ +public: + + // Creation/Destruction Functions + CVRADDispColl(); + ~CVRADDispColl(); + bool Create( CCoreDispInfo *pDisp ); + + // Patches. + bool InitPatch( int iPatch, int iParentPatch, int iChild, Vector *pPoints, int *pIndices, float &flArea ); + bool InitParentPatch( int iPatch, Vector *pPoints, float &flArea ); + float CreateParentPatches( void ); + void CreateChildPatches( int iParentPatch, int nLevel ); + void CreateChildPatchesFromRoot( int iParentPatch, int *pChildPatch ); + void CreateChildPatchesSub( int iParentPatch ); + + // Operations Functions + void BaseFacePlaneToDispUV( Vector const &vecPlanePt, Vector2D &dispUV ); + void DispUVToSurfPoint( Vector2D const &dispUV, Vector &vecPoint, float flPushEps ); + void DispUVToSurfNormal( Vector2D const &dispUV, Vector &vecNormal ); + + // Data. + inline float GetSampleRadius2( void ) { return m_flSampleRadius2; } + inline float GetPatchSampleRadius2( void ) { return m_flPatchSampleRadius2; } + + inline int GetParentIndex( void ) { return m_iParent; } + inline void GetParentFaceNormal( Vector &vecNormal ) { vecNormal = m_vecStabDir; } + + inline void GetVert( int iVert, Vector &vecVert ) { Assert( ( iVert >= 0 ) && ( iVert < GetSize() ) ); vecVert = m_aVerts[iVert]; } + inline void GetVertNormal( int iVert, Vector &vecNormal ) { Assert( ( iVert >= 0 ) && ( iVert < GetSize() ) ); vecNormal = m_aVertNormals[iVert]; } + inline Vector2D const& GetLuxelCoord( int iLuxel ) { Assert( ( iLuxel >= 0 ) && ( iLuxel < GetSize() ) ); return m_aLuxelCoords[iLuxel]; } + + // Raytracing + void AddPolysForRayTrace( void ); + +protected: + + void CalcSampleRadius2AndBox( dface_t *pFace ); + + // Utility. + void DispUVToSurf_TriTLToBR( Vector &vecPoint, float flPushEps, float flU, float flV, int nSnapU, int nSnapV, int nWidth, int nHeight ); + void DispUVToSurf_TriBLToTR( Vector &vecPoint, float flPushEps, float flU, float flV, int nSnapU, int nSnapV, int nWidth, int nHeight ); + void GetSurfaceMinMax( Vector &boxMin, Vector &boxMax ); + void GetMinorAxes( Vector const &vecNormal, int &nAxis0, int &nAxis1 ); + +protected: + + int m_iParent; // Parent index + float m_flSampleRadius2; // Sampling radius + float m_flPatchSampleRadius2; // Patch sampling radius (max bound) + float m_flSampleWidth; + float m_flSampleHeight; + CUtlVector m_aLuxelCoords; // Lightmap coordinates. + CUtlVector m_aVertNormals; // Displacement vertex normals +}; + +#endif // VRAD_DISPCOLL_H \ No newline at end of file diff --git a/mp/src/utils/vrad/vrad_dll-2010.vcxproj b/mp/src/utils/vrad/vrad_dll-2010.vcxproj new file mode 100644 index 00000000..4b41d1a1 --- /dev/null +++ b/mp/src/utils/vrad/vrad_dll-2010.vcxproj @@ -0,0 +1,405 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + Vrad_dll + {90A78BD4-2532-39D9-6D34-7A3C2648508C} + + + + DynamicLibrary + MultiByte + vrad_dll + + + DynamicLibrary + MultiByte + vrad_dll + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30319.1 + .\Debug\win32\ + .\Debug\win32\ + ..\..\devtools\vstools;$(ExecutablePath);$(Path) + true + true + true + false + true + .\Release\win32\ + .\Release\win32\ + ..\..\devtools\vstools;$(ExecutablePath);$(Path) + true + true + false + false + true + + + + + + /MP + Disabled + ..\..\common;..\..\public;..\..\public\tier0;..\..\public\tier1;..\common;..\vmpi;..\vmpi\mysql\mysqlpp\include;..\vmpi\mysql\include + _HAS_ITERATOR_DEBUGGING=0;WIN32;_WIN32;_DEBUG;DEBUG;_WINDOWS;_USRDLL;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;DLLNAME=vrad_dll;RAD_TELEMETRY_DISABLED;COMPILER_MSVC32;MPI;PROTECTED_THINGS_DISABLE;VRAD;VPCGAMECAPS=VALVE;PROJECTDIR=D:\dev\games\rel\hl2\src\utils\vrad;_DLL_EXT=.dll;VPCGAME=valve + true + false + Default + MultiThreadedDebug + true + StreamingSIMDExtensions + Fast + true + true + NotUsing + $(IntDir)/ + $(IntDir)/ + $(IntDir)/ + false + false + Level4 + true + EditAndContinue + CompileAsCpp + $(IntDir)/ + Prompt + + + _DEBUG;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE + 1033 + + + + + /ignore:4221 + %(AdditionalDependencies);ws2_32.lib + NotSet + $(OutDir)\vrad_dll.dll + true + ..\..\lib\common;..\..\lib\public + libc;libcd;libcmt + true + $(IntDir)/$(TargetName).pdb + false + $(IntDir)/$(TargetName).map + Windows + + MachineX86 + PromptImmediately + false + false + + + true + + + true + + + true + $(OutDir)/vrad_dll.bsc + + + Publishing to ..\..\..\game\bin + if not exist "..\..\..\game\bin" mkdir "..\..\..\game\bin" copy "$(TargetDir)"$(TargetFileName) "..\..\..\game\bin\$(TargetFileName)" if ERRORLEVEL 1 goto BuildEventFailed if exist "$(TargetDir)"$(TargetName).map copy "$(TargetDir)"$(TargetName).map ..\..\..\game\bin\$(TargetName).map copy "$(TargetDir)"$(TargetName).pdb ..\..\..\game\bin\$(TargetName).pdb if ERRORLEVEL 1 goto BuildEventFailed goto BuildEventOK :BuildEventFailed echo *** ERROR! PostBuildStep FAILED for $(ProjectName)! EXE or DLL is probably running. *** del /q "$(TargetDir)"$(TargetFileName) exit 1 :BuildEventOK + + + + + + + + + /MP /d2Zi+ + MaxSpeed + AnySuitable + true + Speed + ..\..\common;..\..\public;..\..\public\tier0;..\..\public\tier1;..\common;..\vmpi;..\vmpi\mysql\mysqlpp\include;..\vmpi\mysql\include + WIN32;_WIN32;NDEBUG;_WINDOWS;_USRDLL;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;DLLNAME=vrad_dll;RAD_TELEMETRY_DISABLED;COMPILER_MSVC32;MPI;PROTECTED_THINGS_DISABLE;VRAD;VPCGAMECAPS=VALVE;PROJECTDIR=D:\dev\games\rel\hl2\src\utils\vrad;_DLL_EXT=.dll;VPCGAME=valve + true + false + MultiThreaded + false + true + StreamingSIMDExtensions + Fast + true + true + NotUsing + $(IntDir)/ + $(IntDir)/ + $(IntDir)/ + false + false + Level4 + true + ProgramDatabase + CompileAsCpp + $(IntDir)/ + Prompt + + + NDEBUG;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE + 1033 + + + + + /DYNAMICBASE /ignore:4221 + %(AdditionalDependencies);ws2_32.lib + NotSet + $(OutDir)\vrad_dll.dll + true + ..\..\lib\common;..\..\lib\public + libc;libcd;libcmtd + true + $(IntDir)/$(TargetName).pdb + false + $(IntDir)/$(TargetName).map + Windows + true + true + + MachineX86 + PromptImmediately + false + + + true + + + true + + + true + $(OutDir)/vrad_dll.bsc + + + Publishing to ..\..\..\game\bin + if not exist "..\..\..\game\bin" mkdir "..\..\..\game\bin" copy "$(TargetDir)"$(TargetFileName) "..\..\..\game\bin\$(TargetFileName)" if ERRORLEVEL 1 goto BuildEventFailed if exist "$(TargetDir)"$(TargetName).map copy "$(TargetDir)"$(TargetName).map ..\..\..\game\bin\$(TargetName).map copy "$(TargetDir)"$(TargetName).pdb ..\..\..\game\bin\$(TargetName).pdb if ERRORLEVEL 1 goto BuildEventFailed goto BuildEventOK :BuildEventFailed echo *** ERROR! PostBuildStep FAILED for $(ProjectName)! EXE or DLL is probably running. *** del /q "$(TargetDir)"$(TargetFileName) exit 1 :BuildEventOK + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + NotUsing + NotUsing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Compiling pointeroverride.asm + "$(VCInstallDir)bin\ml.exe" /c /Cp /Zi /Fo"$(IntDir)\%(Filename).obj" "%(FullPath)" + $(IntDir)\%(Filename).obj + Compiling pointeroverride.asm + "$(VCInstallDir)bin\ml.exe" /c /Cp /Zi /Fo"$(IntDir)\%(Filename).obj" "%(FullPath)" + $(IntDir)\%(Filename).obj + + + + + + + + + diff --git a/mp/src/utils/vrad/vrad_dll-2010.vcxproj.filters b/mp/src/utils/vrad/vrad_dll-2010.vcxproj.filters new file mode 100644 index 00000000..3fbfde77 --- /dev/null +++ b/mp/src/utils/vrad/vrad_dll-2010.vcxproj.filters @@ -0,0 +1,563 @@ + + + + + {1680C80B-FF1E-EA4D-9817-CC12254F2E40} + + + {AFC34ED7-EC78-E112-6213-16C13F1BBFB5} + + + {53AF07E1-D7C4-FEE3-01A5-43636D973BE6} + + + {C5D73B3A-C648-896C-B7CE-F174808E5BA5} + + + {BA03E055-4FA2-FCE3-8A1C-D348547D379C} + + + {A7DC6913-C602-1488-0EDF-DE69D12F2421} + + + {A405CE38-5F8E-1A97-6C69-59BB1FF172C4} + + + + + Link Libraries + + + Link Libraries + + + Link Libraries + + + Link Libraries + + + Link Libraries + + + Link Libraries + + + Link Libraries + + + Link Libraries + + + Link Libraries + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files\Common Header Files + + + Header Files\Common Header Files + + + Header Files\Common Header Files + + + Header Files\Common Header Files + + + Header Files\Common Header Files + + + Header Files\Common Header Files + + + Header Files\Common Header Files + + + Header Files\Common Header Files + + + Header Files\Common Header Files + + + Header Files\Common Header Files + + + Header Files\Common Header Files + + + Header Files\Common Header Files + + + Header Files\Common Header Files + + + Header Files\Common Header Files + + + Header Files\Common Header Files + + + Header Files\Common Header Files + + + Header Files\Common Header Files + + + Header Files\Common Header Files + + + Header Files\Common Header Files + + + Header Files\Common Header Files + + + Header Files\Common Header Files + + + Header Files\Common Header Files + + + Header Files\Public Header Files + + + Header Files\Public Header Files + + + Header Files\Public Header Files + + + Header Files\Public Header Files + + + Header Files\Public Header Files + + + Header Files\Public Header Files + + + Header Files\Public Header Files + + + Header Files\Public Header Files + + + Header Files\Public Header Files + + + Header Files\Public Header Files + + + Header Files\Public Header Files + + + Header Files\Public Header Files + + + Header Files\Public Header Files + + + Header Files\Public Header Files + + + Header Files\Public Header Files + + + Header Files\Public Header Files + + + Header Files\Public Header Files + + + Header Files\Public Header Files + + + Header Files\Public Header Files + + + Header Files\Public Header Files + + + Header Files\Public Header Files + + + Header Files\Public Header Files + + + Header Files\Public Header Files + + + Header Files\Public Header Files + + + Header Files\Public Header Files + + + Header Files\Public Header Files + + + Header Files\Public Header Files + + + Header Files\Public Header Files + + + Header Files\Public Header Files + + + Header Files\Public Header Files + + + Header Files\Public Header Files + + + Header Files\Public Header Files + + + Header Files\Public Header Files + + + Header Files\Public Header Files + + + Header Files\Public Header Files + + + Header Files\Public Header Files + + + Header Files\Public Header Files + + + Header Files\Public Header Files + + + Header Files\Public Header Files + + + Header Files\Public Header Files + + + Header Files\Public Header Files + + + Header Files\Public Header Files + + + Header Files\Public Header Files + + + Header Files\Public Header Files + + + Header Files\Public Header Files + + + Header Files\Public Header Files + + + Header Files\Public Header Files + + + Header Files\Public Header Files + + + Header Files\Public Header Files + + + Header Files\Public Header Files + + + Header Files\Public Header Files + + + Header Files\Public Header Files + + + Header Files\Public Header Files + + + Header Files\Public Header Files + + + Header Files\Public Header Files + + + Header Files\Public Header Files + + + Header Files\Public Header Files + + + Header Files\Public Header Files + + + Header Files\Public Header Files + + + Header Files\Public Header Files + + + Header Files\Public Header Files + + + Header Files\Public Header Files + + + Header Files\Public Header Files + + + Header Files\Public Header Files + + + Header Files\Public Header Files + + + Header Files\Public Header Files + + + Header Files\Public Header Files + + + Header Files\Public Header Files + + + Header Files\Public Header Files + + + Header Files\Public Header Files + + + Header Files\Public Header Files + + + Header Files\Public Header Files + + + Header Files\Public Header Files + + + Header Files\Public Header Files + + + Header Files\Public Header Files + + + Header Files\Public Header Files + + + Header Files\Public Header Files + + + Header Files\Public Header Files + + + Header Files\Public Header Files + + + Header Files\Public Header Files + + + Source Files + + + Source Files\Common Files + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files\Common Files + + + Source Files\Common Files + + + Source Files\Common Files + + + Source Files\Common Files + + + Source Files\Common Files + + + Source Files\Common Files + + + Source Files\Common Files + + + Source Files\Common Files + + + Source Files\Common Files + + + Source Files\Common Files + + + Source Files\Public Files + + + Source Files\Public Files + + + Source Files\Public Files + + + Source Files\Public Files + + + + + + + Source Files + + + + + + + + diff --git a/mp/src/utils/vrad/vraddetailprops.cpp b/mp/src/utils/vrad/vraddetailprops.cpp new file mode 100644 index 00000000..6712beb1 --- /dev/null +++ b/mp/src/utils/vrad/vraddetailprops.cpp @@ -0,0 +1,1034 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Revision: $ +// $NoKeywords: $ +// +// This file contains code to allow us to associate client data with bsp leaves. +// +//=============================================================================// + +#include "vrad.h" +#include "Bsplib.h" +#include "GameBSPFile.h" +#include "UtlBuffer.h" +#include "utlvector.h" +#include "CModel.h" +#include "studio.h" +#include "pacifier.h" +#include "vraddetailprops.h" +#include "mathlib/halton.h" +#include "messbuf.h" +#include "byteswap.h" + +bool LoadStudioModel( char const* pModelName, CUtlBuffer& buf ); + + +//----------------------------------------------------------------------------- +// Purpose: Writes a glview text file containing the collision surface in question +// Input : *pCollide - +// *pFilename - +//----------------------------------------------------------------------------- +void DumpRayToGlView( Ray_t const& ray, float dist, Vector* pColor, const char *pFilename ) +{ + Vector dir = ray.m_Delta; + float len = VectorNormalize(dir); + if (len < 1e-3) + return; + + Vector up( 0, 0, 1 ); + Vector crossDir; + if (fabs(DotProduct(up, dir)) - 1.0f < -1e-3 ) + { + CrossProduct( dir, up, crossDir ); + VectorNormalize(crossDir); + } + else + { + up.Init( 0, 1, 0 ); + CrossProduct( dir, up, crossDir ); + VectorNormalize(crossDir); + } + + Vector end; + Vector start1, start2; + VectorMA( ray.m_Start, dist, ray.m_Delta, end ); + VectorMA( ray.m_Start, -2, crossDir, start1 ); + VectorMA( ray.m_Start, 2, crossDir, start2 ); + + FileHandle_t fp = g_pFileSystem->Open( pFilename, "a" ); + int vert = 0; + CmdLib_FPrintf( fp, "3\n" ); + CmdLib_FPrintf( fp, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", start1.x, start1.y, start1.z, + pColor->x, pColor->y, pColor->z ); + vert++; + CmdLib_FPrintf( fp, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", start2.x, start2.y, start2.z, + pColor->x, pColor->y, pColor->z ); + vert++; + CmdLib_FPrintf( fp, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", end.x, end.y, end.z, + pColor->x, pColor->y, pColor->z ); + vert++; + g_pFileSystem->Close( fp ); +} + + +//----------------------------------------------------------------------------- +// This puppy is used to construct the game lumps +//----------------------------------------------------------------------------- +static CUtlVector s_DetailPropLightStyleLumpLDR; +static CUtlVector s_DetailPropLightStyleLumpHDR; +static CUtlVector *s_pDetailPropLightStyleLump = &s_DetailPropLightStyleLumpLDR; + +//----------------------------------------------------------------------------- +// An amount to add to each model to get to the model center +//----------------------------------------------------------------------------- +CUtlVector g_ModelCenterOffset; +CUtlVector g_SpriteCenterOffset; + +void VRadDetailProps_SetHDRMode( bool bHDR ) +{ + if( bHDR ) + { + s_pDetailPropLightStyleLump = &s_DetailPropLightStyleLumpHDR; + } + else + { + s_pDetailPropLightStyleLump = &s_DetailPropLightStyleLumpLDR; + } +} + +//----------------------------------------------------------------------------- +// Finds ambient sky lights +//----------------------------------------------------------------------------- +static directlight_t* FindAmbientSkyLight() +{ + static directlight_t *s_pCachedSkylight = NULL; + + // Don't keep searching for the same light. + if ( !s_pCachedSkylight ) + { + // find any ambient lights + directlight_t* dl; + for (dl = activelights; dl != 0; dl = dl->next) + { + if (dl->light.type == emit_skyambient) + { + s_pCachedSkylight = dl; + break; + } + } + } + + return s_pCachedSkylight; +} + + +//----------------------------------------------------------------------------- +// Compute world center of a prop +//----------------------------------------------------------------------------- +static void ComputeWorldCenter( DetailObjectLump_t& prop, Vector& center, Vector& normal ) +{ + // Transform the offset into world space + Vector forward, right; + AngleVectors( prop.m_Angles, &forward, &right, &normal ); + VectorCopy( prop.m_Origin, center ); + + // FIXME: Take orientation into account? + switch (prop.m_Type ) + { + case DETAIL_PROP_TYPE_MODEL: + VectorMA( center, g_ModelCenterOffset[prop.m_DetailModel].x, forward, center ); + VectorMA( center, -g_ModelCenterOffset[prop.m_DetailModel].y, right, center ); + VectorMA( center, g_ModelCenterOffset[prop.m_DetailModel].z, normal, center ); + break; + + case DETAIL_PROP_TYPE_SPRITE: + Vector vecOffset; + VectorMultiply( g_SpriteCenterOffset[prop.m_DetailModel], prop.m_flScale, vecOffset ); + VectorMA( center, vecOffset.x, forward, center ); + VectorMA( center, -vecOffset.y, right, center ); + VectorMA( center, vecOffset.z, normal, center ); + break; + } +} + + +//----------------------------------------------------------------------------- +// Computes max direct lighting for a single detal prop +//----------------------------------------------------------------------------- +static void ComputeMaxDirectLighting( DetailObjectLump_t& prop, Vector* maxcolor, int iThread ) +{ + // The max direct lighting must be along the direction to one + // of the static lights.... + + Vector origin, normal; + ComputeWorldCenter( prop, origin, normal ); + + if ( !origin.IsValid() || !normal.IsValid() ) + { + static bool s_Warned = false; + if ( !s_Warned ) + { + Warning("WARNING: Bogus detail props encountered!\n" ); + s_Warned = true; + } + + // fill with debug color + for ( int i = 0; i < MAX_LIGHTSTYLES; ++i) + { + maxcolor[i].Init(1,0,0); + } + return; + } + + int cluster = ClusterFromPoint(origin); + + Vector delta; + CUtlVector< directlight_t* > lights; + CUtlVector< Vector > directions; + + directlight_t* dl; + for (dl = activelights; dl != 0; dl = dl->next) + { + // skyambient doesn't affect dlights.. + if (dl->light.type == emit_skyambient) + continue; + + // is this lights cluster visible? + if ( PVSCheck( dl->pvs, cluster ) ) + { + lights.AddToTail(dl); + VectorSubtract( dl->light.origin, origin, delta ); + VectorNormalize( delta ); + directions.AddToTail( delta ); + } + } + + // Find the max illumination + int i; + for ( i = 0; i < MAX_LIGHTSTYLES; ++i) + { + maxcolor[i].Init(0,0,0); + } + + // NOTE: See version 10 for a method where we choose a normal based on whichever + // one produces the maximum possible illumination. This appeared to work better on + // e3_town, so I'm trying it now; hopefully it'll be good for all cases. + int j; + for ( j = 0; j < lights.Count(); ++j) + { + dl = lights[j]; + + SSE_sampleLightOutput_t out; + FourVectors origin4; + FourVectors normal4; + origin4.DuplicateVector( origin ); + normal4.DuplicateVector( normal ); + + GatherSampleLightSSE ( out, dl, -1, origin4, &normal4, 1, iThread ); + VectorMA( maxcolor[dl->light.style], out.m_flFalloff.m128_f32[0] * out.m_flDot[0].m128_f32[0], dl->light.intensity, maxcolor[dl->light.style] ); + } +} + + +//----------------------------------------------------------------------------- +// Computes the ambient term from a particular surface +//----------------------------------------------------------------------------- + +static void ComputeAmbientFromSurface( dface_t* pFace, directlight_t* pSkylight, + Vector& radcolor ) +{ + texinfo_t* pTex = &texinfo[pFace->texinfo]; + if (pTex) + { + // If we hit the sky, use the sky ambient + if (pTex->flags & SURF_SKY) + { + if (pSkylight) + { + // add in sky ambient + VectorDivide( pSkylight->light.intensity, 255.0f, radcolor ); + } + } + else + { + VectorMultiply( radcolor, dtexdata[pTex->texdata].reflectivity, radcolor ); + } + } +} + + +//----------------------------------------------------------------------------- +// Computes the lightmap color at a particular point +//----------------------------------------------------------------------------- + +static void ComputeLightmapColorFromAverage( dface_t* pFace, directlight_t* pSkylight, float scale, Vector pColor[MAX_LIGHTSTYLES] ) +{ + texinfo_t* pTex = &texinfo[pFace->texinfo]; + if (pTex->flags & SURF_SKY) + { + if (pSkylight) + { + // add in sky ambient + Vector amb = pSkylight->light.intensity / 255.0f; + pColor[0] += amb * scale; + } + return; + } + + for (int maps = 0 ; maps < MAXLIGHTMAPS && pFace->styles[maps] != 255 ; ++maps) + { + ColorRGBExp32* pAvgColor = dface_AvgLightColor( pFace, maps ); + + // this code expects values from [0..1] not [0..255] + Vector color; + color[0] = TexLightToLinear( pAvgColor->r, pAvgColor->exponent ); + color[1] = TexLightToLinear( pAvgColor->g, pAvgColor->exponent ); + color[2] = TexLightToLinear( pAvgColor->b, pAvgColor->exponent ); + + ComputeAmbientFromSurface( pFace, pSkylight, color ); + + int style = pFace->styles[maps]; + pColor[style] += color * scale; + } +} + + +//----------------------------------------------------------------------------- +// Returns true if the surface has bumped lightmaps +//----------------------------------------------------------------------------- + +static bool SurfHasBumpedLightmaps( dface_t *pSurf ) +{ + bool hasBumpmap = false; + if( ( texinfo[pSurf->texinfo].flags & SURF_BUMPLIGHT ) && + ( !( texinfo[pSurf->texinfo].flags & SURF_NOLIGHT ) ) ) + { + hasBumpmap = true; + } + return hasBumpmap; +} + +//----------------------------------------------------------------------------- +// Computes the lightmap color at a particular point +//----------------------------------------------------------------------------- + +static void ComputeLightmapColorPointSample( dface_t* pFace, directlight_t* pSkylight, Vector2D const& luv, float scale, Vector pColor[MAX_LIGHTSTYLES] ) +{ + // face unaffected by light + if (pFace->lightofs == -1 ) + return; + + int smax = ( pFace->m_LightmapTextureSizeInLuxels[0] ) + 1; + int tmax = ( pFace->m_LightmapTextureSizeInLuxels[1] ) + 1; + + // luv is in the space of the accumulated lightmap page; we need to convert + // it to be in the space of the surface + int ds = clamp( (int)luv.x, 0, smax-1 ); + int dt = clamp( (int)luv.y, 0, tmax-1 ); + + int offset = smax * tmax; + if ( SurfHasBumpedLightmaps( pFace ) ) + offset *= ( NUM_BUMP_VECTS + 1 ); + + ColorRGBExp32* pLightmap = (ColorRGBExp32*)&pdlightdata->Base()[pFace->lightofs]; + pLightmap += dt * smax + ds; + for (int maps = 0 ; maps < MAXLIGHTMAPS && pFace->styles[maps] != 255 ; ++maps) + { + int style = pFace->styles[maps]; + + Vector color; + color[0] = TexLightToLinear( pLightmap->r, pLightmap->exponent ); + color[1] = TexLightToLinear( pLightmap->g, pLightmap->exponent ); + color[2] = TexLightToLinear( pLightmap->b, pLightmap->exponent ); + + ComputeAmbientFromSurface( pFace, pSkylight, color ); + pColor[style] += color * scale; + + pLightmap += offset; + } +} + + +//----------------------------------------------------------------------------- +// Tests a particular node +//----------------------------------------------------------------------------- + +class CLightSurface : public IBSPNodeEnumerator +{ +public: + CLightSurface(int iThread) : m_pSurface(0), m_HitFrac(1.0f), m_bHasLuxel(false), m_iThread(iThread) {} + + // call back with a node and a context + bool EnumerateNode( int node, Ray_t const& ray, float f, int context ) + { + dface_t* pSkySurface = 0; + + // Compute the actual point + Vector pt; + VectorMA( ray.m_Start, f, ray.m_Delta, pt ); + + dnode_t* pNode = &dnodes[node]; + dface_t* pFace = &g_pFaces[pNode->firstface]; + for (int i=0 ; i < pNode->numfaces ; ++i, ++pFace) + { + // Don't take into account faces that are int a leaf + if ( !pFace->onNode ) + continue; + + // Don't test displacement faces + if ( pFace->dispinfo != -1 ) + continue; + + texinfo_t* pTex = &texinfo[pFace->texinfo]; + + // Don't immediately return when we hit sky; + // we may actually hit another surface + if (pTex->flags & SURF_SKY) + { + if (TestPointAgainstSkySurface( pt, pFace )) + { + pSkySurface = pFace; + } + + continue; + } + + if (TestPointAgainstSurface( pt, pFace, pTex )) + { + m_HitFrac = f; + m_pSurface = pFace; + m_bHasLuxel = true; + return false; + } + } + + // if we hit a sky surface, return it + m_pSurface = pSkySurface; + return (m_pSurface == 0); + } + + // call back with a leaf and a context + virtual bool EnumerateLeaf( int leaf, Ray_t const& ray, float start, float end, int context ) + { + bool hit = false; + dleaf_t* pLeaf = &dleafs[leaf]; + for (int i=0 ; i < pLeaf->numleaffaces ; ++i) + { + Assert( pLeaf->firstleafface + i < numleaffaces ); + Assert( dleaffaces[pLeaf->firstleafface + i] < numfaces ); + dface_t* pFace = &g_pFaces[dleaffaces[pLeaf->firstleafface + i]]; + + // Don't test displacement faces; we need to check another list + if ( pFace->dispinfo != -1 ) + continue; + + // Don't take into account faces that are on a node + if ( pFace->onNode ) + continue; + + // Find intersection point against detail brushes + texinfo_t* pTex = &texinfo[pFace->texinfo]; + + dplane_t* pPlane = &dplanes[pFace->planenum]; + + // Backface cull... + if (DotProduct( pPlane->normal, ray.m_Delta ) > 0) + continue; + + float startDotN = DotProduct( ray.m_Start, pPlane->normal ); + float deltaDotN = DotProduct( ray.m_Delta, pPlane->normal ); + + float front = startDotN + start * deltaDotN - pPlane->dist; + float back = startDotN + end * deltaDotN - pPlane->dist; + + int side = front < 0; + + // Blow it off if it doesn't split the plane... + if ( (back < 0) == side ) + continue; + + // Don't test a surface that is farther away from the closest found intersection + float f = front / (front-back); + float mid = start * (1.0f - f) + end * f; + if (mid >= m_HitFrac) + continue; + + Vector pt; + VectorMA( ray.m_Start, mid, ray.m_Delta, pt ); + + if (TestPointAgainstSurface( pt, pFace, pTex )) + { + m_HitFrac = mid; + m_pSurface = pFace; + hit = true; + m_bHasLuxel = true; + } + } + + // Now try to clip against all displacements in the leaf + float dist; + Vector2D luxelCoord; + dface_t *pDispFace; + StaticDispMgr()->ClipRayToDispInLeaf( s_DispTested[m_iThread], ray, leaf, dist, pDispFace, luxelCoord ); + if (dist < m_HitFrac) + { + m_HitFrac = dist; + m_pSurface = pDispFace; + Vector2DCopy( luxelCoord, m_LuxelCoord ); + hit = true; + m_bHasLuxel = true; + } + return !hit; + } + + bool FindIntersection( Ray_t const& ray ) + { + StaticDispMgr()->StartRayTest( s_DispTested[m_iThread] ); + return !EnumerateNodesAlongRay( ray, this, 0 ); + } + +private: + bool TestPointAgainstSurface( Vector const& pt, dface_t* pFace, texinfo_t* pTex ) + { + // no lightmaps on this surface? punt... + // FIXME: should be water surface? + if (pTex->flags & SURF_NOLIGHT) + return false; + + // See where in lightmap space our intersection point is + float s, t; + s = DotProduct (pt.Base(), pTex->lightmapVecsLuxelsPerWorldUnits[0]) + + pTex->lightmapVecsLuxelsPerWorldUnits[0][3]; + t = DotProduct (pt.Base(), pTex->lightmapVecsLuxelsPerWorldUnits[1]) + + pTex->lightmapVecsLuxelsPerWorldUnits[1][3]; + + // Not in the bounds of our lightmap? punt... + if( s < pFace->m_LightmapTextureMinsInLuxels[0] || t < pFace->m_LightmapTextureMinsInLuxels[1] ) + return false; + + // assuming a square lightmap (FIXME: which ain't always the case), + // lets see if it lies in that rectangle. If not, punt... + float ds = s - pFace->m_LightmapTextureMinsInLuxels[0]; + float dt = t - pFace->m_LightmapTextureMinsInLuxels[1]; + if( ds > pFace->m_LightmapTextureSizeInLuxels[0] || dt > pFace->m_LightmapTextureSizeInLuxels[1] ) + return false; + + m_LuxelCoord.x = ds; + m_LuxelCoord.y = dt; + + return true; + } + + bool TestPointAgainstSkySurface( Vector const &pt, dface_t *pFace ) + { + // Create sky face winding. + winding_t *pWinding = WindingFromFace( pFace, Vector( 0.0f, 0.0f, 0.0f ) ); + + // Test point in winding. (Since it is at the node, it is in the plane.) + bool bRet = PointInWinding( pt, pWinding ); + + FreeWinding( pWinding ); + + return bRet; + } + + +public: + int m_iThread; + dface_t* m_pSurface; + float m_HitFrac; + Vector2D m_LuxelCoord; + bool m_bHasLuxel; +}; + +bool CastRayInLeaf( int iThread, const Vector &start, const Vector &end, int leafIndex, float *pFraction, Vector *pNormal ) +{ + pFraction[0] = 1.0f; + + Ray_t ray; + ray.Init( start, end, vec3_origin, vec3_origin ); + CBaseTrace trace; + if ( TraceLeafBrushes( leafIndex, start, end, trace ) != 1.0f ) + { + pFraction[0] = trace.fraction; + *pNormal = trace.plane.normal; + } + else + { + Assert(!trace.startsolid && !trace.allsolid); + } + StaticDispMgr()->StartRayTest( s_DispTested[iThread] ); + // Now try to clip against all displacements in the leaf + float dist; + Vector normal; + StaticDispMgr()->ClipRayToDispInLeaf( s_DispTested[iThread], ray, leafIndex, dist, &normal ); + if ( dist < pFraction[0] ) + { + pFraction[0] = dist; + *pNormal = normal; + } + return pFraction[0] != 1.0f ? true : false; +} + +//----------------------------------------------------------------------------- +// Computes ambient lighting along a specified ray. +// Ray represents a cone, tanTheta is the tan of the inner cone angle +//----------------------------------------------------------------------------- +void CalcRayAmbientLighting( int iThread, const Vector &vStart, const Vector &vEnd, float tanTheta, Vector color[MAX_LIGHTSTYLES] ) +{ + Ray_t ray; + ray.Init( vStart, vEnd, vec3_origin, vec3_origin ); + + directlight_t *pSkyLight = FindAmbientSkyLight(); + + CLightSurface surfEnum(iThread); + if (!surfEnum.FindIntersection( ray )) + return; + + // compute the approximate radius of a circle centered around the intersection point + float dist = ray.m_Delta.Length() * tanTheta * surfEnum.m_HitFrac; + + // until 20" we use the point sample, then blend in the average until we're covering 40" + // This is attempting to model the ray as a cone - in the ideal case we'd simply sample all + // luxels in the intersection of the cone with the surface. Since we don't have surface + // neighbor information computed we'll just approximate that sampling with a blend between + // a point sample and the face average. + // This yields results that are similar in that aliasing is reduced at distance while + // point samples provide accuracy for intersections with near geometry + float scaleAvg = RemapValClamped( dist, 20, 40, 0.0f, 1.0f ); + + if ( !surfEnum.m_bHasLuxel ) + { + // don't have luxel UV, so just use average sample + scaleAvg = 1.0; + } + float scaleSample = 1.0f - scaleAvg; + + if (scaleAvg != 0) + { + ComputeLightmapColorFromAverage( surfEnum.m_pSurface, pSkyLight, scaleAvg, color ); + } + if (scaleSample != 0) + { + ComputeLightmapColorPointSample( surfEnum.m_pSurface, pSkyLight, surfEnum.m_LuxelCoord, scaleSample, color ); + } +} + +//----------------------------------------------------------------------------- +// Compute ambient lighting component at specified position. +//----------------------------------------------------------------------------- +static void ComputeAmbientLightingAtPoint( int iThread, const Vector &origin, Vector radcolor[NUMVERTEXNORMALS], Vector color[MAX_LIGHTSTYLES] ) +{ + // NOTE: I'm not dealing with shadow-casting static props here + // This is for speed, although we can add it if it turns out to + // be important + + // sample world by casting N rays distributed across a sphere + Vector upend; + + int j; + for ( j = 0; j < MAX_LIGHTSTYLES; ++j) + { + color[j].Init( 0,0,0 ); + } + + float tanTheta = tan(VERTEXNORMAL_CONE_INNER_ANGLE); + for (int i = 0; i < NUMVERTEXNORMALS; i++) + { + VectorMA( origin, COORD_EXTENT * 1.74, g_anorms[i], upend ); + + // Now that we've got a ray, see what surface we've hit + CalcRayAmbientLighting( iThread, origin, upend, tanTheta, color ); + +// DumpRayToGlView( ray, surfEnum.m_HitFrac, &color[0], "test.out" ); + } + + for ( j = 0; j < MAX_LIGHTSTYLES; ++j) + { + VectorMultiply( color[j], 255.0f / (float)NUMVERTEXNORMALS, color[j] ); + } +} + +//----------------------------------------------------------------------------- +// Trace hemispherical rays from a vertex, accumulating indirect +// sources at each ray termination. +//----------------------------------------------------------------------------- +void ComputeIndirectLightingAtPoint( Vector &position, Vector &normal, Vector &outColor, + int iThread, bool force_fast, bool bIgnoreNormals ) +{ + Ray_t ray; + CLightSurface surfEnum(iThread); + + outColor.Init(); + + + int nSamples = NUMVERTEXNORMALS; + if ( do_fast || force_fast ) + nSamples /= 4; + else + nSamples *= g_flSkySampleScale; + + float totalDot = 0; + DirectionalSampler_t sampler; + for (int j = 0; j < nSamples; j++) + { + Vector samplingNormal = sampler.NextValue(); + float dot; + + if ( bIgnoreNormals ) + dot = (0.7071/2); + else + dot = DotProduct( normal, samplingNormal ); + + if ( dot <= EQUAL_EPSILON ) + { + // reject angles behind our plane + continue; + } + + totalDot += dot; + + // trace to determine surface + Vector vEnd; + VectorScale( samplingNormal, MAX_TRACE_LENGTH, vEnd ); + VectorAdd( position, vEnd, vEnd ); + + ray.Init( position, vEnd, vec3_origin, vec3_origin ); + if ( !surfEnum.FindIntersection( ray ) ) + continue; + + // get color from surface lightmap + texinfo_t* pTex = &texinfo[surfEnum.m_pSurface->texinfo]; + if ( !pTex || pTex->flags & SURF_SKY ) + { + // ignore contribution from sky + // sky ambient already accounted for during direct pass + continue; + } + + if ( surfEnum.m_pSurface->styles[0] == 255 || surfEnum.m_pSurface->lightofs < 0 ) + { + // no light affects this face + continue; + } + + + Vector lightmapColor; + if ( !surfEnum.m_bHasLuxel ) + { + ColorRGBExp32* pAvgLightmapColor = dface_AvgLightColor( surfEnum.m_pSurface, 0 ); + ColorRGBExp32ToVector( *pAvgLightmapColor, lightmapColor ); + } + else + { + // get color from displacement + int smax = ( surfEnum.m_pSurface->m_LightmapTextureSizeInLuxels[0] ) + 1; + int tmax = ( surfEnum.m_pSurface->m_LightmapTextureSizeInLuxels[1] ) + 1; + + // luxelcoord is in the space of the accumulated lightmap page; we need to convert + // it to be in the space of the surface + int ds = clamp( (int)surfEnum.m_LuxelCoord.x, 0, smax-1 ); + int dt = clamp( (int)surfEnum.m_LuxelCoord.y, 0, tmax-1 ); + + ColorRGBExp32* pLightmap = (ColorRGBExp32*)&(*pdlightdata)[surfEnum.m_pSurface->lightofs]; + pLightmap += dt * smax + ds; + ColorRGBExp32ToVector( *pLightmap, lightmapColor ); + } + + VectorMultiply( lightmapColor, dtexdata[pTex->texdata].reflectivity, lightmapColor ); + VectorAdd( outColor, lightmapColor, outColor ); + } + + if ( totalDot ) + { + VectorScale( outColor, 1.0f/totalDot, outColor ); + } +} + +static void ComputeAmbientLighting( int iThread, DetailObjectLump_t& prop, Vector color[MAX_LIGHTSTYLES] ) +{ + Vector origin, normal; + ComputeWorldCenter( prop, origin, normal ); + + if ( !origin.IsValid() || !normal.IsValid() ) + { + static bool s_Warned = false; + if ( !s_Warned ) + { + Warning("WARNING: Bogus detail props encountered!\n" ); + s_Warned = true; + } + + // fill with debug color + for ( int i = 0; i < MAX_LIGHTSTYLES; ++i) + { + color[i].Init(1,0,0); + } + return; + } + + Vector radcolor[NUMVERTEXNORMALS]; + ComputeAmbientLightingAtPoint( iThread, origin, radcolor, color ); +} + + +//----------------------------------------------------------------------------- +// Computes lighting for a single detal prop +//----------------------------------------------------------------------------- + +static void ComputeLighting( DetailObjectLump_t& prop, int iThread ) +{ + // We're going to take the maximum of the ambient lighting and + // the strongest directional light. This works because we're assuming + // the props will have built-in faked lighting. + + Vector directColor[MAX_LIGHTSTYLES]; + Vector ambColor[MAX_LIGHTSTYLES]; + + // Get the max influence of all direct lights + ComputeMaxDirectLighting( prop, directColor, iThread ); + + // Get the ambient lighting + lightstyles + ComputeAmbientLighting( iThread, prop, ambColor ); + + // Base lighting + Vector totalColor; + VectorAdd( directColor[0], ambColor[0], totalColor ); + VectorToColorRGBExp32( totalColor, prop.m_Lighting ); + + bool hasLightstyles = false; + prop.m_LightStyleCount = 0; + + // lightstyles + for (int i = 1; i < MAX_LIGHTSTYLES; ++i ) + { + VectorAdd( directColor[i], ambColor[i], totalColor ); + totalColor *= 0.5f; + + if ((totalColor[0] != 0.0f) || (totalColor[1] != 0.0f) || + (totalColor[2] != 0.0f) ) + { + if (!hasLightstyles) + { + prop.m_LightStyles = s_pDetailPropLightStyleLump->Size(); + hasLightstyles = true; + } + + int j = s_pDetailPropLightStyleLump->AddToTail(); + VectorToColorRGBExp32( totalColor, (*s_pDetailPropLightStyleLump)[j].m_Lighting ); + (*s_pDetailPropLightStyleLump)[j].m_Style = i; + ++prop.m_LightStyleCount; + } + } +} + + +//----------------------------------------------------------------------------- +// Unserialization +//----------------------------------------------------------------------------- +static void UnserializeModelDict( CUtlBuffer& buf ) +{ + // Get origin offset for each model... + int count = buf.GetInt(); + while ( --count >= 0 ) + { + DetailObjectDictLump_t lump; + buf.Get( &lump, sizeof(DetailObjectDictLump_t) ); + + int i = g_ModelCenterOffset.AddToTail(); + + CUtlBuffer mdlbuf; + if (LoadStudioModel( lump.m_Name, mdlbuf )) + { + studiohdr_t* pHdr = (studiohdr_t*)mdlbuf.Base(); + VectorAdd( pHdr->hull_min, pHdr->hull_max, g_ModelCenterOffset[i] ); + g_ModelCenterOffset[i] *= 0.5f; + } + else + { + g_ModelCenterOffset[i].Init(0,0,0); + } + } +} + +static void UnserializeSpriteDict( CUtlBuffer& buf ) +{ + // Get origin offset for each model... + int count = buf.GetInt(); + while ( --count >= 0 ) + { + DetailSpriteDictLump_t lump; + buf.Get( &lump, sizeof(DetailSpriteDictLump_t) ); + + // For these sprites, x goes out the front, y right, z up + int i = g_SpriteCenterOffset.AddToTail(); + g_SpriteCenterOffset[i].x = 0.0f; + g_SpriteCenterOffset[i].y = lump.m_LR.x + lump.m_UL.x; + g_SpriteCenterOffset[i].z = lump.m_LR.y + lump.m_UL.y; + g_SpriteCenterOffset[i] *= 0.5f; + } +} + + +//----------------------------------------------------------------------------- +// Unserializes the detail props +//----------------------------------------------------------------------------- +static int UnserializeDetailProps( DetailObjectLump_t*& pProps ) +{ + GameLumpHandle_t handle = g_GameLumps.GetGameLumpHandle( GAMELUMP_DETAIL_PROPS ); + + if (g_GameLumps.GetGameLumpVersion(handle) != GAMELUMP_DETAIL_PROPS_VERSION) + return 0; + + // Unserialize + CUtlBuffer buf( g_GameLumps.GetGameLump(handle), g_GameLumps.GameLumpSize( handle ), CUtlBuffer::READ_ONLY ); + + UnserializeModelDict( buf ); + UnserializeSpriteDict( buf ); + + // Now we're pointing to the detail prop data + // This actually works because the scope of the game lump data + // is global and the buf was just pointing to it. + int count = buf.GetInt(); + if (count) + { + pProps = (DetailObjectLump_t*)buf.PeekGet(); + } + else + { + pProps = 0; + } + return count; +} + + +//----------------------------------------------------------------------------- +// Writes the detail lighting lump +//----------------------------------------------------------------------------- +static void WriteDetailLightingLump( int lumpID, int lumpVersion, CUtlVector &lumpData ) +{ + GameLumpHandle_t handle = g_GameLumps.GetGameLumpHandle(lumpID); + if (handle != g_GameLumps.InvalidGameLump()) + g_GameLumps.DestroyGameLump(handle); + int lightsize = lumpData.Size() * sizeof(DetailPropLightstylesLump_t); + int lumpsize = lightsize + sizeof(int); + + handle = g_GameLumps.CreateGameLump( lumpID, lumpsize, 0, lumpVersion ); + + // Serialize the data + CUtlBuffer buf( g_GameLumps.GetGameLump(handle), lumpsize ); + buf.PutInt( lumpData.Size() ); + if (lightsize) + buf.Put( lumpData.Base(), lightsize ); +} + +static void WriteDetailLightingLumps( void ) +{ + WriteDetailLightingLump( GAMELUMP_DETAIL_PROP_LIGHTING, GAMELUMP_DETAIL_PROP_LIGHTING_VERSION, s_DetailPropLightStyleLumpLDR ); + WriteDetailLightingLump( GAMELUMP_DETAIL_PROP_LIGHTING_HDR, GAMELUMP_DETAIL_PROP_LIGHTING_HDR_VERSION, s_DetailPropLightStyleLumpHDR ); +} + +// need to do this so that if we are building HDR data, the LDR data is intact, and vice versa.s +void UnserializeDetailPropLighting( int lumpID, int lumpVersion, CUtlVector &lumpData ) +{ + GameLumpHandle_t handle = g_GameLumps.GetGameLumpHandle( lumpID ); + + if( handle == g_GameLumps.InvalidGameLump() ) + { + return; + } + + if (g_GameLumps.GetGameLumpVersion(handle) != lumpVersion) + return; + + // Unserialize + CUtlBuffer buf( g_GameLumps.GetGameLump(handle), g_GameLumps.GameLumpSize( handle ), CUtlBuffer::READ_ONLY ); + + int count = buf.GetInt(); + if( !count ) + { + return; + } + lumpData.SetCount( count ); + int lightsize = lumpData.Size() * sizeof(DetailPropLightstylesLump_t); + buf.Get( lumpData.Base(), lightsize ); +} + +DetailObjectLump_t *g_pMPIDetailProps = NULL; + +void VMPI_ProcessDetailPropWU( int iThread, int iWorkUnit, MessageBuffer *pBuf ) +{ + CUtlVector *pDetailPropLump = s_pDetailPropLightStyleLump; + + DetailObjectLump_t& prop = g_pMPIDetailProps[iWorkUnit]; + ComputeLighting( prop, iThread ); + + // Send the results back... + pBuf->write( &prop.m_Lighting, sizeof( prop.m_Lighting ) ); + pBuf->write( &prop.m_LightStyleCount, sizeof( prop.m_LightStyleCount ) ); + pBuf->write( &prop.m_LightStyles, sizeof( prop.m_LightStyles ) ); + + for ( int i=0; i < prop.m_LightStyleCount; i++ ) + { + DetailPropLightstylesLump_t *l = &pDetailPropLump->Element( i + prop.m_LightStyles ); + pBuf->write( &l->m_Lighting, sizeof( l->m_Lighting ) ); + pBuf->write( &l->m_Style, sizeof( l->m_Style ) ); + } +} + + +void VMPI_ReceiveDetailPropWU( int iWorkUnit, MessageBuffer *pBuf, int iWorker ) +{ + CUtlVector *pDetailPropLump = s_pDetailPropLightStyleLump; + + DetailObjectLump_t& prop = g_pMPIDetailProps[iWorkUnit]; + + pBuf->read( &prop.m_Lighting, sizeof( prop.m_Lighting ) ); + pBuf->read( &prop.m_LightStyleCount, sizeof( prop.m_LightStyleCount ) ); + pBuf->read( &prop.m_LightStyles, sizeof( prop.m_LightStyles ) ); + + pDetailPropLump->EnsureCount( prop.m_LightStyles + prop.m_LightStyleCount ); + + for ( int i=0; i < prop.m_LightStyleCount; i++ ) + { + DetailPropLightstylesLump_t *l = &pDetailPropLump->Element( i + prop.m_LightStyles ); + pBuf->read( &l->m_Lighting, sizeof( l->m_Lighting ) ); + pBuf->read( &l->m_Style, sizeof( l->m_Style ) ); + } +} + +//----------------------------------------------------------------------------- +// Computes lighting for the detail props +//----------------------------------------------------------------------------- +void ComputeDetailPropLighting( int iThread ) +{ + // illuminate them all + DetailObjectLump_t* pProps; + int count = UnserializeDetailProps( pProps ); + if (!count) + return; + + // unserialize the lump that we aren't computing. + if( g_bHDR ) + { + UnserializeDetailPropLighting( GAMELUMP_DETAIL_PROP_LIGHTING, GAMELUMP_DETAIL_PROP_LIGHTING_VERSION, s_DetailPropLightStyleLumpLDR ); + } + else + { + UnserializeDetailPropLighting( GAMELUMP_DETAIL_PROP_LIGHTING_HDR, GAMELUMP_DETAIL_PROP_LIGHTING_HDR_VERSION, s_DetailPropLightStyleLumpHDR ); + } + + StartPacifier("Computing detail prop lighting : "); + + for (int i = 0; i < count; ++i) + { + UpdatePacifier( (float)i / (float)count ); + ComputeLighting( pProps[i], iThread ); + } + + // Write detail prop lightstyle lump... + WriteDetailLightingLumps(); + EndPacifier( true ); +} diff --git a/mp/src/utils/vrad/vraddetailprops.h b/mp/src/utils/vrad/vraddetailprops.h new file mode 100644 index 00000000..f9cc8da1 --- /dev/null +++ b/mp/src/utils/vrad/vraddetailprops.h @@ -0,0 +1,34 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef VRADDETAILPROPS_H +#define VRADDETAILPROPS_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "bspfile.h" +#include "mathlib/anorms.h" + + +// Calculate the lighting at whatever surface the ray hits. +// Note: this ADDS to the values already in color. So if you want absolute +// values in there, then clear the values in color[] first. +void CalcRayAmbientLighting( + int iThread, + const Vector &vStart, + const Vector &vEnd, + float tanTheta, // tangent of the inner angle of the cone + Vector color[MAX_LIGHTSTYLES] // The color contribution from each lightstyle. + ); + +bool CastRayInLeaf( int iThread, const Vector &start, const Vector &end, int leafIndex, float *pFraction, Vector *pNormal ); + +void ComputeDetailPropLighting( int iThread ); + + +#endif // VRADDETAILPROPS_H diff --git a/mp/src/utils/vrad/vraddisps.cpp b/mp/src/utils/vrad/vraddisps.cpp new file mode 100644 index 00000000..1e8d2606 --- /dev/null +++ b/mp/src/utils/vrad/vraddisps.cpp @@ -0,0 +1,1783 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "vrad.h" +#include "utlvector.h" +#include "cmodel.h" +#include "BSPTreeData.h" +#include "VRAD_DispColl.h" +#include "CollisionUtils.h" +#include "lightmap.h" +#include "Radial.h" +#include "CollisionUtils.h" +#include "mathlib/bumpvects.h" +#include "utlrbtree.h" +#include "tier0/fasttimer.h" +#include "disp_vrad.h" + +class CBSPDispRayDistanceEnumerator; + +//============================================================================= +// +// Displacement/Face List +// +class CBSPDispFaceListEnumerator : public ISpatialLeafEnumerator, public IBSPTreeDataEnumerator +{ +public: + + //========================================================================= + // + // Construction/Deconstruction + // + CBSPDispFaceListEnumerator() {}; + virtual ~CBSPDispFaceListEnumerator() + { + m_DispList.Purge(); + m_FaceList.Purge(); + } + + // ISpatialLeafEnumerator + bool EnumerateLeaf( int ndxLeaf, int context ); + + // IBSPTreeDataEnumerator + bool FASTCALL EnumerateElement( int userId, int context ); + +public: + + CUtlVector m_DispList; + CUtlVector m_FaceList; +}; + + +//============================================================================= +// +// RayEnumerator +// +class CBSPDispRayEnumerator : public ISpatialLeafEnumerator, public IBSPTreeDataEnumerator +{ +public: + // ISpatialLeafEnumerator + bool EnumerateLeaf( int ndxLeaf, int context ); + + // IBSPTreeDataEnumerator + bool FASTCALL EnumerateElement( int userId, int context ); +}; + +//============================================================================= +// +// VRad Displacement Manager +// +class CVRadDispMgr : public IVRadDispMgr +{ +public: + + //========================================================================= + // + // Construction/Deconstruction + // + CVRadDispMgr(); + virtual ~CVRadDispMgr(); + + // creation/destruction + void Init( void ); + void Shutdown( void ); + + // "CalcPoints" + bool BuildDispSamples( lightinfo_t *pLightInfo, facelight_t *pFaceLight, int ndxFace ); + bool BuildDispLuxels( lightinfo_t *pLightInfo, facelight_t *pFaceLight, int ndxFace ); + bool BuildDispSamplesAndLuxels_DoFast( lightinfo_t *pLightInfo, facelight_t *pFaceLight, int ndxFace ); + + // patching functions + void MakePatches( void ); + void SubdividePatch( int iPatch ); + + // pre "FinalLightFace" + void InsertSamplesDataIntoHashTable( void ); + void InsertPatchSampleDataIntoHashTable( void ); + + // "FinalLightFace" + radial_t *BuildLuxelRadial( int ndxFace, int ndxStyle, bool bBump ); + bool SampleRadial( int ndxFace, radial_t *pRadial, Vector const &vPos, int ndxLxl, LightingValue_t *pLightSample, int sampleCount, bool bPatch ); + radial_t *BuildPatchRadial( int ndxFace, bool bBump ); + + // utility + void GetDispSurfNormal( int ndxFace, Vector &pt, Vector &ptNormal, bool bInside ); + void GetDispSurf( int ndxFace, CVRADDispColl **ppDispTree ); + + // bsp tree functions + bool ClipRayToDisp( DispTested_t &dispTested, Ray_t const &ray ); + bool ClipRayToDispInLeaf( DispTested_t &dispTested, Ray_t const &ray, int ndxLeaf ); + void ClipRayToDispInLeaf( DispTested_t &dispTested, Ray_t const &ray, int ndxLeaf, + float& dist, dface_t*& pFace, Vector2D& luxelCoord ); + void ClipRayToDispInLeaf( DispTested_t &dispTested, Ray_t const &ray, + int ndxLeaf, float& dist, Vector *pNormal ); + + void StartRayTest( DispTested_t &dispTested ); + void AddPolysForRayTrace( void ); + + // general timing -- should be moved!! + void StartTimer( const char *name ); + void EndTimer( void ); + + //========================================================================= + // + // Enumeration Methods + // + bool DispRay_EnumerateLeaf( int ndxLeaf, int context ); + bool DispRay_EnumerateElement( int userId, int context ); + bool DispRayDistance_EnumerateElement( int userId, CBSPDispRayDistanceEnumerator* pEnum ); + + bool DispFaceList_EnumerateLeaf( int ndxLeaf, int context ); + bool DispFaceList_EnumerateElement( int userId, int context ); + +private: + + //========================================================================= + // + // BSP Tree Helpers + // + void InsertDispIntoTree( int ndxDisp ); + void RemoveDispFromTree( int ndxDisp ); + + //========================================================================= + // + // Displacement Data Loader (from .bsp) + // + void UnserializeDisps( void ); + void DispBuilderInit( CCoreDispInfo *pBuilderDisp, dface_t *pFace, int ndxFace ); + + //========================================================================= + // + // Sampling Helpers + // + void RadialLuxelBuild( CVRADDispColl *pDispTree, radial_t *pRadial, int ndxStyle, bool bBump ); + void RadialLuxelAddSamples( int ndxFace, Vector const &luxelPt, Vector const &luxelNormal, float radius, + radial_t *pRadial, int ndxRadial, bool bBump, int lightStyle ); + + void RadialPatchBuild( CVRADDispColl *pDispTree, radial_t *pRadial, bool bBump ); + void RadialLuxelAddPatch( int ndxFace, Vector const &luxelPt, + Vector const &luxelNormal, float radius, + radial_t *pRadial, int ndxRadial, bool bBump, + CUtlVector &interestingPatches ); + + bool IsNeighbor( int iDispFace, int iNeighborFace ); + + void GetInterestingPatchesForLuxels( + int ndxFace, + CUtlVector &interestingPatches, + float patchSampleRadius ); + +private: + + struct DispCollTree_t + { + CVRADDispColl *m_pDispTree; + BSPTreeDataHandle_t m_Handle; + }; + + struct EnumContext_t + { + DispTested_t *m_pDispTested; + Ray_t const *m_pRay; + }; + + CUtlVector m_DispTrees; + + IBSPTreeData *m_pBSPTreeData; + + CBSPDispRayEnumerator m_EnumDispRay; + CBSPDispFaceListEnumerator m_EnumDispFaceList; + + int sampleCount; + Vector *m_pSamplePos; + + CFastTimer m_Timer; +}; + +//----------------------------------------------------------------------------- +// Purpose: expose IVRadDispMgr to vrad +//----------------------------------------------------------------------------- + +static CVRadDispMgr s_DispMgr; + +IVRadDispMgr *StaticDispMgr( void ) +{ + return &s_DispMgr; +} + + +//============================================================================= +// +// Displacement/Face List +// +// ISpatialLeafEnumerator +bool CBSPDispFaceListEnumerator::EnumerateLeaf( int ndxLeaf, int context ) +{ + return s_DispMgr.DispFaceList_EnumerateLeaf( ndxLeaf, context ); +} + +// IBSPTreeDataEnumerator +bool FASTCALL CBSPDispFaceListEnumerator::EnumerateElement( int userId, int context ) +{ + return s_DispMgr.DispFaceList_EnumerateElement( userId, context ); +} + + +//============================================================================= +// +// RayEnumerator +// +bool CBSPDispRayEnumerator::EnumerateLeaf( int ndxLeaf, int context ) +{ + return s_DispMgr.DispRay_EnumerateLeaf( ndxLeaf, context ); +} + +bool FASTCALL CBSPDispRayEnumerator::EnumerateElement( int userId, int context ) +{ + return s_DispMgr.DispRay_EnumerateElement( userId, context ); +} + + +//----------------------------------------------------------------------------- +// Here's an enumerator that we use for testing against disps in a leaf... +//----------------------------------------------------------------------------- + +class CBSPDispRayDistanceEnumerator : public IBSPTreeDataEnumerator +{ +public: + CBSPDispRayDistanceEnumerator() : m_Distance(1.0f), m_pSurface(0) {} + + // IBSPTreeDataEnumerator + bool FASTCALL EnumerateElement( int userId, int context ) + { + return s_DispMgr.DispRayDistance_EnumerateElement( userId, this ); + } + + float m_Distance; + dface_t* m_pSurface; + DispTested_t *m_pDispTested; + Ray_t const *m_pRay; + Vector2D m_LuxelCoord; + Vector m_Normal; +}; + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +CVRadDispMgr::CVRadDispMgr() +{ + m_pBSPTreeData = CreateBSPTreeData(); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +CVRadDispMgr::~CVRadDispMgr() +{ + DestroyBSPTreeData( m_pBSPTreeData ); +} + + +//----------------------------------------------------------------------------- +// Insert a displacement into the tree for collision +//----------------------------------------------------------------------------- +void CVRadDispMgr::InsertDispIntoTree( int ndxDisp ) +{ + DispCollTree_t &dispTree = m_DispTrees[ndxDisp]; + CDispCollTree *pDispTree = dispTree.m_pDispTree; + + // get the bounding box of the tree + Vector boxMin, boxMax; + pDispTree->GetBounds( boxMin, boxMax ); + + // add the displacement to the tree so we will collide against it + dispTree.m_Handle = m_pBSPTreeData->Insert( ndxDisp, boxMin, boxMax ); +} + + +//----------------------------------------------------------------------------- +// Remove a displacement from the tree for collision +//----------------------------------------------------------------------------- +void CVRadDispMgr::RemoveDispFromTree( int ndxDisp ) +{ + // release the tree handle + if( m_DispTrees[ndxDisp].m_Handle != TREEDATA_INVALID_HANDLE ) + { + m_pBSPTreeData->Remove( m_DispTrees[ndxDisp].m_Handle ); + m_DispTrees[ndxDisp].m_Handle = TREEDATA_INVALID_HANDLE; + } +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CVRadDispMgr::Init( void ) +{ + // initialize the bsp tree + m_pBSPTreeData->Init( ToolBSPTree() ); + + // read in displacements that have been compiled into the bsp file + UnserializeDisps(); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CVRadDispMgr::Shutdown( void ) +{ + // remove all displacements from the tree + for( int ndxDisp = m_DispTrees.Size(); ndxDisp >= 0; ndxDisp-- ) + { + RemoveDispFromTree( ndxDisp ); + } + + // shutdown the bsp tree + m_pBSPTreeData->Shutdown(); + + // purge the displacement collision tree list + m_DispTrees.Purge(); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CVRadDispMgr::DispBuilderInit( CCoreDispInfo *pBuilderDisp, dface_t *pFace, int ndxFace ) +{ + // get the .bsp displacement + ddispinfo_t *pDisp = &g_dispinfo[pFace->dispinfo]; + if( !pDisp ) + return; + + // + // initlialize the displacement base surface + // + CCoreDispSurface *pSurf = pBuilderDisp->GetSurface(); + pSurf->SetPointCount( 4 ); + pSurf->SetHandle( ndxFace ); + pSurf->SetContents( pDisp->contents ); + + Vector pt[4]; + int ndxPt; + for( ndxPt = 0; ndxPt < 4; ndxPt++ ) + { + int eIndex = dsurfedges[pFace->firstedge+ndxPt]; + if( eIndex < 0 ) + { + pSurf->SetPoint( ndxPt, dvertexes[dedges[-eIndex].v[1]].point ); + } + else + { + pSurf->SetPoint( ndxPt, dvertexes[dedges[eIndex].v[0]].point ); + } + + VectorCopy( pSurf->GetPoint(ndxPt), pt[ndxPt] ); + } + + // + // calculate the displacement surface normal + // + Vector vFaceNormal; + pSurf->GetNormal( vFaceNormal ); + for( ndxPt = 0; ndxPt < 4; ndxPt++ ) + { + pSurf->SetPointNormal( ndxPt, vFaceNormal ); + } + + // set the surface initial point info + pSurf->SetPointStart( pDisp->startPosition ); + pSurf->FindSurfPointStartIndex(); + pSurf->AdjustSurfPointData(); + + Vector vecTmp( texinfo[pFace->texinfo].lightmapVecsLuxelsPerWorldUnits[0][0], + texinfo[pFace->texinfo].lightmapVecsLuxelsPerWorldUnits[0][1], + texinfo[pFace->texinfo].lightmapVecsLuxelsPerWorldUnits[0][2] ); + int nLuxelsPerWorldUnit = static_cast( 1.0f / VectorLength( vecTmp ) ); + Vector vecU( texinfo[pFace->texinfo].lightmapVecsLuxelsPerWorldUnits[0][0], + texinfo[pFace->texinfo].lightmapVecsLuxelsPerWorldUnits[0][1], + texinfo[pFace->texinfo].lightmapVecsLuxelsPerWorldUnits[0][2] ); + Vector vecV( texinfo[pFace->texinfo].lightmapVecsLuxelsPerWorldUnits[1][0], + texinfo[pFace->texinfo].lightmapVecsLuxelsPerWorldUnits[1][1], + texinfo[pFace->texinfo].lightmapVecsLuxelsPerWorldUnits[1][2] ); + pSurf->CalcLuxelCoords( nLuxelsPerWorldUnit, false, vecU, vecV ); + + pBuilderDisp->SetNeighborData( pDisp->m_EdgeNeighbors, pDisp->m_CornerNeighbors ); + + CDispVert *pVerts = &g_DispVerts[ pDisp->m_iDispVertStart ]; + CDispTri *pTris = &g_DispTris[pDisp->m_iDispTriStart]; + + // + // initialize the displacement data + // + pBuilderDisp->InitDispInfo( + pDisp->power, + pDisp->minTess, + pDisp->smoothingAngle, + pVerts, + pTris ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CVRadDispMgr::UnserializeDisps( void ) +{ + // temporarily create the "builder" displacements + CUtlVector builderDisps; + for ( int iDisp = 0; iDisp < g_dispinfo.Count(); ++iDisp ) + { + CCoreDispInfo *pDisp = new CCoreDispInfo; + if ( !pDisp ) + { + builderDisps.Purge(); + return; + } + + int nIndex = builderDisps.AddToTail(); + pDisp->SetListIndex( nIndex ); + builderDisps[nIndex] = pDisp; + } + + // Set them up as CDispUtilsHelpers. + for ( int iDisp = 0; iDisp < g_dispinfo.Count(); ++iDisp ) + { + builderDisps[iDisp]->SetDispUtilsHelperInfo( builderDisps.Base(), g_dispinfo.Count() ); + } + + // + // find all faces with displacement data and initialize + // + for( int ndxFace = 0; ndxFace < numfaces; ndxFace++ ) + { + dface_t *pFace = &g_pFaces[ndxFace]; + if( ValidDispFace( pFace ) ) + { + DispBuilderInit( builderDisps[pFace->dispinfo], pFace, ndxFace ); + } + } + + // generate the displacement surfaces + for( int iDisp = 0; iDisp < g_dispinfo.Count(); ++iDisp ) + { + builderDisps[iDisp]->Create(); + } + + // smooth edge normals + SmoothNeighboringDispSurfNormals( builderDisps.Base(), g_dispinfo.Count() ); + + // + // create the displacement collision tree and add it to the bsp tree + // + CVRADDispColl *pDispTrees = new CVRADDispColl[g_dispinfo.Count()]; + if( !pDispTrees ) + return; + + m_DispTrees.AddMultipleToTail( g_dispinfo.Count() ); + + for( int iDisp = 0; iDisp < g_dispinfo.Count(); iDisp++ ) + { + pDispTrees[iDisp].Create( builderDisps[iDisp] ); + + m_DispTrees[iDisp].m_pDispTree = &pDispTrees[iDisp]; + m_DispTrees[iDisp].m_Handle = TREEDATA_INVALID_HANDLE; + + InsertDispIntoTree( iDisp ); + } + + // free "builder" disps + builderDisps.Purge(); +} + +//----------------------------------------------------------------------------- +// Purpose: create a set of patches for each displacement surface to transfer +// bounced light around with +//----------------------------------------------------------------------------- +void CVRadDispMgr::MakePatches( void ) +{ + // Collect stats - keep track of the total displacement surface area. + float flTotalArea = 0.0f; + + // Create patches for all of the displacements. + int nTreeCount = m_DispTrees.Size(); + for( int iTree = 0; iTree < nTreeCount; ++iTree ) + { + // Get the current displacement collision tree. + CVRADDispColl *pDispTree = m_DispTrees[iTree].m_pDispTree; + if( !pDispTree ) + continue; + + flTotalArea += pDispTree->CreateParentPatches(); + } + + // Print stats. + qprintf( "%i Displacements\n", nTreeCount ); + qprintf( "%i Square Feet [%.2f Square Inches]\n", ( int )( flTotalArea / 144.0f ), flTotalArea ); +} +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CVRadDispMgr::SubdividePatch( int iPatch ) +{ + // Get the current patch to subdivide. + CPatch *pPatch = &g_Patches[iPatch]; + if ( !pPatch ) + return; + + // Create children patches. + DispCollTree_t &dispTree = m_DispTrees[g_pFaces[pPatch->faceNumber].dispinfo]; + CVRADDispColl *pTree = dispTree.m_pDispTree; + if( pTree ) + { + pTree->CreateChildPatches( iPatch, 0 ); + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CVRadDispMgr::StartRayTest( DispTested_t &dispTested ) +{ + if( m_DispTrees.Size() > 0 ) + { + if( dispTested.m_pTested == 0 ) + { + dispTested.m_pTested = new int[m_DispTrees.Size()]; + memset( dispTested.m_pTested, 0, m_DispTrees.Size() * sizeof( int ) ); + dispTested.m_Enum = 0; + } + ++dispTested.m_Enum; + } +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool CVRadDispMgr::ClipRayToDisp( DispTested_t &dispTested, Ray_t const &ray ) +{ + StartRayTest( dispTested ); + + EnumContext_t ctx; + ctx.m_pRay = &ray; + ctx.m_pDispTested = &dispTested; + + // If it got through without a hit, it returns true + return !m_pBSPTreeData->EnumerateLeavesAlongRay( ray, &m_EnumDispRay, ( int )&ctx ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool CVRadDispMgr::ClipRayToDispInLeaf( DispTested_t &dispTested, Ray_t const &ray, + int ndxLeaf ) +{ + EnumContext_t ctx; + ctx.m_pRay = &ray; + ctx.m_pDispTested = &dispTested; + + return !m_pBSPTreeData->EnumerateElementsInLeaf( ndxLeaf, &m_EnumDispRay, ( int )&ctx ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CVRadDispMgr::ClipRayToDispInLeaf( DispTested_t &dispTested, Ray_t const &ray, + int ndxLeaf, float& dist, dface_t*& pFace, Vector2D& luxelCoord ) +{ + CBSPDispRayDistanceEnumerator rayTestEnum; + rayTestEnum.m_pRay = &ray; + rayTestEnum.m_pDispTested = &dispTested; + + m_pBSPTreeData->EnumerateElementsInLeaf( ndxLeaf, &rayTestEnum, 0 ); + + dist = rayTestEnum.m_Distance; + pFace = rayTestEnum.m_pSurface; + if (pFace) + { + Vector2DCopy( rayTestEnum.m_LuxelCoord, luxelCoord ); + } +} + +void CVRadDispMgr::ClipRayToDispInLeaf( DispTested_t &dispTested, Ray_t const &ray, + int ndxLeaf, float& dist, Vector *pNormal ) +{ + CBSPDispRayDistanceEnumerator rayTestEnum; + rayTestEnum.m_pRay = &ray; + rayTestEnum.m_pDispTested = &dispTested; + + m_pBSPTreeData->EnumerateElementsInLeaf( ndxLeaf, &rayTestEnum, 0 ); + dist = rayTestEnum.m_Distance; + if ( rayTestEnum.m_pSurface ) + { + *pNormal = rayTestEnum.m_Normal; + } +} + +void CVRadDispMgr::AddPolysForRayTrace( void ) +{ + int nTreeCount = m_DispTrees.Size(); + for( int iTree = 0; iTree < nTreeCount; ++iTree ) + { + // Get the current displacement collision tree. + CVRADDispColl *pDispTree = m_DispTrees[iTree].m_pDispTree; + + // Add the triangles of the tree to the RT environment + pDispTree->AddPolysForRayTrace(); + } +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CVRadDispMgr::GetDispSurfNormal( int ndxFace, Vector &pt, Vector &ptNormal, + bool bInside ) +{ + // get the displacement surface data + DispCollTree_t &dispTree = m_DispTrees[g_pFaces[ndxFace].dispinfo]; + CVRADDispColl *pDispTree = dispTree.m_pDispTree; + + // find the parameterized displacement indices + Vector2D uv; + pDispTree->BaseFacePlaneToDispUV( pt, uv ); + + if( bInside ) + { + if( uv[0] < 0.0f || uv[0] > 1.0f ) { Msg( "Disp UV (%f) outside bounds!\n", uv[0] ); } + if( uv[1] < 0.0f || uv[1] > 1.0f ) { Msg( "Disp UV (%f) outside bounds!\n", uv[1] ); } + } + + if( uv[0] < 0.0f ) { uv[0] = 0.0f; } + if( uv[0] > 1.0f ) { uv[0] = 1.0f; } + if( uv[1] < 0.0f ) { uv[1] = 0.0f; } + if( uv[1] > 1.0f ) { uv[1] = 1.0f; } + + // get the normal at "pt" + pDispTree->DispUVToSurfNormal( uv, ptNormal ); + + // get the new "pt" + pDispTree->DispUVToSurfPoint( uv, pt, 1.0f ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CVRadDispMgr::GetDispSurf( int ndxFace, CVRADDispColl **ppDispTree ) +{ + DispCollTree_t &dispTree = m_DispTrees[g_pFaces[ndxFace].dispinfo]; + *ppDispTree = dispTree.m_pDispTree; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool CVRadDispMgr::DispRay_EnumerateLeaf( int ndxLeaf, int context ) +{ + return m_pBSPTreeData->EnumerateElementsInLeaf( ndxLeaf, &m_EnumDispRay, context ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool CVRadDispMgr::DispRay_EnumerateElement( int userId, int context ) +{ + DispCollTree_t &dispTree = m_DispTrees[userId]; + EnumContext_t *pCtx = ( EnumContext_t* )context; + + // don't test twice (check tested value) + if( pCtx->m_pDispTested->m_pTested[userId] == pCtx->m_pDispTested->m_Enum ) + return true; + + // set the tested value + pCtx->m_pDispTested->m_pTested[userId] = pCtx->m_pDispTested->m_Enum; + + // false mean stop iterating -- return false if we hit! (NOTE: opposite return + // result of the collision tree's ray test, thus the !) + CBaseTrace trace; + trace.fraction = 1.0f; + return ( !dispTree.m_pDispTree->AABBTree_Ray( *pCtx->m_pRay, pCtx->m_pRay->InvDelta(), &trace, true ) ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +bool CVRadDispMgr::DispRayDistance_EnumerateElement( int userId, CBSPDispRayDistanceEnumerator* pCtx ) +{ + DispCollTree_t &dispTree = m_DispTrees[userId]; + + // don't test twice (check tested value) + if( pCtx->m_pDispTested->m_pTested[userId] == pCtx->m_pDispTested->m_Enum ) + return true; + + // set the tested value + pCtx->m_pDispTested->m_pTested[userId] = pCtx->m_pDispTested->m_Enum; + + // Test the ray, if it's closer than previous tests, use it! + RayDispOutput_t output; + output.ndxVerts[0] = -1; + output.ndxVerts[1] = -1; + output.ndxVerts[2] = -1; + output.ndxVerts[3] = -1; + output.u = -1.0f; + output.v = -1.0f; + output.dist = FLT_MAX; + + if (dispTree.m_pDispTree->AABBTree_Ray( *pCtx->m_pRay, output )) + { + if (output.dist < pCtx->m_Distance) + { + pCtx->m_Distance = output.dist; + pCtx->m_pSurface = &g_pFaces[dispTree.m_pDispTree->GetParentIndex()]; + + // Get the luxel coordinate + ComputePointFromBarycentric( + dispTree.m_pDispTree->GetLuxelCoord(output.ndxVerts[0]), + dispTree.m_pDispTree->GetLuxelCoord(output.ndxVerts[1]), + dispTree.m_pDispTree->GetLuxelCoord(output.ndxVerts[2]), + output.u, output.v, pCtx->m_LuxelCoord ); + + Vector v0,v1,v2; + dispTree.m_pDispTree->GetVert( output.ndxVerts[0], v0 ); + dispTree.m_pDispTree->GetVert( output.ndxVerts[1], v1 ); + dispTree.m_pDispTree->GetVert( output.ndxVerts[2], v2 ); + Vector e0 = v1-v0; + Vector e1 = v2-v0; + pCtx->m_Normal = CrossProduct( e0, e1 ); + VectorNormalize(pCtx->m_Normal); + } + } + + return true; +} + +//----------------------------------------------------------------------------- +// Test a ray against a particular dispinfo +//----------------------------------------------------------------------------- + +/* +float CVRadDispMgr::ClipRayToDisp( Ray_t const &ray, int dispinfo ) +{ + assert( m_DispTrees.IsValidIndex(dispinfo) ); + + RayDispOutput_t output; + if (!m_DispTrees[dispinfo].m_pDispTree->AABBTree_Ray( ray, output )) + return 1.0f; + return output.dist; +} +*/ + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool CVRadDispMgr::DispFaceList_EnumerateLeaf( int ndxLeaf, int context ) +{ + // + // add the faces found in this leaf to the face list + // + dleaf_t *pLeaf = &dleafs[ndxLeaf]; + for( int ndxFace = 0; ndxFace < pLeaf->numleaffaces; ndxFace++ ) + { + // get the current face index + int ndxLeafFace = pLeaf->firstleafface + ndxFace; + + // check to see if the face already lives in the list + int ndx; + int size = m_EnumDispFaceList.m_FaceList.Size(); + for( ndx = 0; ndx < size; ndx++ ) + { + if( m_EnumDispFaceList.m_FaceList[ndx] == ndxLeafFace ) + break; + } + + if( ndx == size ) + { + int ndxList = m_EnumDispFaceList.m_FaceList.AddToTail(); + m_EnumDispFaceList.m_FaceList[ndxList] = ndxLeafFace; + } + } + + return m_pBSPTreeData->EnumerateElementsInLeaf( ndxLeaf, &m_EnumDispFaceList, context ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool CVRadDispMgr::DispFaceList_EnumerateElement( int userId, int context ) +{ + DispCollTree_t &dispTree = m_DispTrees[userId]; + CVRADDispColl *pDispTree = dispTree.m_pDispTree; + if( !pDispTree ) + return false; + + // check to see if the displacement already lives in the list + int ndx; + int size = m_EnumDispFaceList.m_DispList.Size(); + for( ndx = 0; ndx < size; ndx++ ) + { + if( m_EnumDispFaceList.m_DispList[ndx] == pDispTree ) + break; + } + + if( ndx == size ) + { + int ndxList = m_EnumDispFaceList.m_DispList.AddToTail(); + m_EnumDispFaceList.m_DispList[ndxList] = pDispTree; + } + + return true; +} + + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline void GetSampleLight( facelight_t *pFaceLight, int ndxStyle, bool bBumped, + int ndxSample, LightingValue_t *pSampleLight ) +{ +// SampleLight[0].Init( 20.0f, 10.0f, 10.0f ); +// return; + + // get sample from bumped lighting data + if( bBumped ) + { + for( int ndxBump = 0; ndxBump < ( NUM_BUMP_VECTS+1 ); ndxBump++ ) + { + pSampleLight[ndxBump] = pFaceLight->light[ndxStyle][ndxBump][ndxSample]; + } + } + // just a generally lit surface + else + { + pSampleLight[0] = pFaceLight->light[ndxStyle][0][ndxSample]; + } +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void AddSampleLightToRadial( Vector const &samplePos, Vector const &sampleNormal, + LightingValue_t *pSampleLight, float sampleRadius2, + Vector const &luxelPos, Vector const &luxelNormal, + radial_t *pRadial, int ndxRadial, bool bBumped, + bool bNeighborBumped ) +{ + // check normals to see if sample contributes any light at all + float angle = sampleNormal.Dot( luxelNormal ); + if ( angle < 0.15f ) + return; + + // calculate the light vector + Vector vSegment = samplePos - luxelPos; + + // get the distance to the light + float dist = vSegment.Length(); + float dist2 = dist * dist; + + // Check to see if the light is within the influence. + float influence = 1.0f - ( dist2 / ( sampleRadius2 ) ); + if( influence <= 0.0f ) + return; + + influence *= angle; + + if( bBumped ) + { + if( bNeighborBumped ) + { + for( int ndxBump = 0; ndxBump < ( NUM_BUMP_VECTS+1 ); ndxBump++ ) + { + pRadial->light[ndxBump][ndxRadial].AddWeighted( pSampleLight[ndxBump], influence ); + } + pRadial->weight[ndxRadial] += influence; + } + else + { + influence *= 0.05f; + for( int ndxBump = 0; ndxBump < ( NUM_BUMP_VECTS+1 ); ndxBump++ ) + { + pRadial->light[ndxBump][ndxRadial].AddWeighted( pSampleLight[0], influence ); + } + pRadial->weight[ndxRadial] += influence; + } + } + else + { + pRadial->light[0][ndxRadial].AddWeighted( pSampleLight[0], influence ); + pRadial->weight[ndxRadial] += influence; + } +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool CVRadDispMgr::IsNeighbor( int iFace, int iNeighborFace ) +{ + if ( iFace == iNeighborFace ) + return true; + + faceneighbor_t *pFaceNeighbor = &faceneighbor[iFace]; + for ( int iNeighbor = 0; iNeighbor < pFaceNeighbor->numneighbors; iNeighbor++ ) + { + if ( pFaceNeighbor->neighbor[iNeighbor] == iNeighborFace ) + return true; + } + + return false; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CVRadDispMgr::RadialLuxelAddSamples( int ndxFace, Vector const &luxelPt, Vector const &luxelNormal, float radius, + radial_t *pRadial, int ndxRadial, bool bBump, int lightStyle ) +{ + // calculate one over the voxel size + float ooVoxelSize = 1.0f / SAMPLEHASH_VOXEL_SIZE; + + // + // find voxel info + // + int voxelMin[3], voxelMax[3]; + for( int axis = 0; axis < 3; axis++ ) + { + voxelMin[axis] = ( int )( ( luxelPt[axis] - radius ) * ooVoxelSize ); + voxelMax[axis] = ( int )( ( luxelPt[axis] + radius ) * ooVoxelSize ) + 1; + } + + SampleData_t sampleData; + for( int ndxZ = voxelMin[2]; ndxZ < voxelMax[2] + 1; ndxZ++ ) + { + for( int ndxY = voxelMin[1]; ndxY < voxelMax[1] + 1; ndxY++ ) + { + for( int ndxX = voxelMin[0]; ndxX < voxelMax[0] + 1; ndxX++ ) + { + sampleData.x = ndxX * 100; + sampleData.y = ndxY * 10; + sampleData.z = ndxZ; + + UtlHashHandle_t handle = g_SampleHashTable.Find( sampleData ); + if( handle != g_SampleHashTable.InvalidHandle() ) + { + SampleData_t *pSampleData = &g_SampleHashTable.Element( handle ); + int count = pSampleData->m_Samples.Count(); + for( int ndx = 0; ndx < count; ndx++ ) + { + SampleHandle_t sampleHandle = pSampleData->m_Samples.Element( ndx ); + int ndxSample = ( sampleHandle & 0x0000ffff ); + int ndxFaceLight = ( ( sampleHandle >> 16 ) & 0x0000ffff ); + + facelight_t *pFaceLight = &facelight[ndxFaceLight]; + if( pFaceLight && IsNeighbor( ndxFace, ndxFaceLight ) ) + { + // + // check for similar lightstyles + // + dface_t *pFace = &g_pFaces[ndxFaceLight]; + if( pFace ) + { + int ndxNeighborStyle = -1; + for( int ndxLightStyle = 0; ndxLightStyle < MAXLIGHTMAPS; ndxLightStyle++ ) + { + if( pFace->styles[ndxLightStyle] == lightStyle ) + { + ndxNeighborStyle = ndxLightStyle; + break; + } + } + if( ndxNeighborStyle == -1 ) + continue; + + // is this surface bumped??? + bool bNeighborBump = texinfo[pFace->texinfo].flags & SURF_BUMPLIGHT ? true : false; + + LightingValue_t sampleLight[NUM_BUMP_VECTS+1]; + GetSampleLight( pFaceLight, ndxNeighborStyle, bNeighborBump, ndxSample, sampleLight ); + AddSampleLightToRadial( pFaceLight->sample[ndxSample].pos, pFaceLight->sample[ndxSample].normal, + sampleLight, radius*radius, luxelPt, luxelNormal, pRadial, ndxRadial, + bBump, bNeighborBump ); + } + } + } + } + } + } + } +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CVRadDispMgr::RadialLuxelBuild( CVRADDispColl *pDispTree, radial_t *pRadial, + int ndxStyle, bool bBump ) +{ + // + // get data lighting data + // + int ndxFace = pDispTree->GetParentIndex(); + + dface_t *pFace = &g_pFaces[ndxFace]; + facelight_t *pFaceLight = &facelight[ndxFace]; + + // get the influence radius + float radius2 = pDispTree->GetSampleRadius2(); + float radius = ( float )sqrt( radius2 ); + + int radialSize = pRadial->w * pRadial->h; + for( int ndxRadial = 0; ndxRadial < radialSize; ndxRadial++ ) + { + RadialLuxelAddSamples( ndxFace, pFaceLight->luxel[ndxRadial], pFaceLight->luxelNormals[ndxRadial], + radius, pRadial, ndxRadial, bBump, pFace->styles[ndxStyle] ); + } +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +radial_t *CVRadDispMgr::BuildLuxelRadial( int ndxFace, int ndxStyle, bool bBump ) +{ + // allocate the radial + radial_t *pRadial = AllocateRadial( ndxFace ); + if( !pRadial ) + return NULL; + + // + // step 1: get the displacement surface to be lit + // + DispCollTree_t &dispTree = m_DispTrees[g_pFaces[ndxFace].dispinfo]; + CVRADDispColl *pDispTree = dispTree.m_pDispTree; + if( !pDispTree ) + return NULL; + + // step 2: build radial luxels + RadialLuxelBuild( pDispTree, pRadial, ndxStyle, bBump ); + + // step 3: return the built radial + return pRadial; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool CVRadDispMgr::SampleRadial( int ndxFace, radial_t *pRadial, Vector const &vPos, int ndxLxl, + LightingValue_t *pLightSample, int sampleCount, bool bPatch ) +{ + bool bGoodSample = true; + for ( int count = 0; count < sampleCount; count++ ) + { + pLightSample[count].Zero(); + + if ( pRadial->weight[ndxLxl] > 0.0f ) + { + pLightSample[count].AddWeighted( pRadial->light[count][ndxLxl], ( 1.0f / pRadial->weight[ndxLxl] ) ); + } + else + { + // error, luxel has no samples (not for patches) + if ( !bPatch ) + { + // Yes, 2550 is correct! + // pLightSample[count].Init( 2550.0f, 0.0f, 2550.0f ); + if( count == 0 ) + bGoodSample = false; + } + } + } + + return bGoodSample; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void GetPatchLight( CPatch *pPatch, bool bBump, Vector *pPatchLight ) +{ + VectorCopy( pPatch->totallight.light[0], pPatchLight[0] ); + + if( bBump ) + { + for( int ndxBump = 1; ndxBump < ( NUM_BUMP_VECTS + 1 ); ndxBump++ ) + { + VectorCopy( pPatch->totallight.light[ndxBump], pPatchLight[ndxBump] ); + } + } +} + +extern void GetBumpNormals( const float* sVect, const float* tVect, const Vector& flatNormal, + const Vector& phongNormal, Vector bumpNormals[NUM_BUMP_VECTS] ); +extern void PreGetBumpNormalsForDisp( texinfo_t *pTexinfo, Vector &vecU, Vector &vecV, Vector &vecNormal ); + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void AddPatchLightToRadial( Vector const &patchOrigin, Vector const &patchNormal, + Vector *pPatchLight, float patchRadius2, + Vector const &luxelPos, Vector const &luxelNormal, + radial_t *pRadial, int ndxRadial, bool bBump, + bool bNeighborBump ) +{ + // calculate the light vector + Vector vSegment = patchOrigin - luxelPos; + + // get the distance to the light + float dist = vSegment.Length(); + float dist2 = dist * dist; + + // Check to see if the light is within the sample influence. + float influence = 1.0f - ( dist2 / ( patchRadius2 ) ); + if ( influence <= 0.0f ) + return; + + if( bBump ) + { + Vector normals[NUM_BUMP_VECTS+1]; + normals[0] = luxelNormal; + texinfo_t *pTexinfo = &texinfo[g_pFaces[pRadial->facenum].texinfo]; + Vector vecTexU, vecTexV; + PreGetBumpNormalsForDisp( pTexinfo, vecTexU, vecTexV, normals[0] ); + GetBumpNormals( vecTexU, vecTexV, normals[0], normals[0], &normals[1] ); + + if( bNeighborBump ) + { + float flScale = patchNormal.Dot( normals[0] ); + flScale = clamp( flScale, 0.0f, flScale ); + float flBumpInfluence = influence * flScale; + + for( int ndxBump = 0; ndxBump < ( NUM_BUMP_VECTS+1 ); ndxBump++ ) + { + pRadial->light[ndxBump][ndxRadial].AddWeighted( pPatchLight[ndxBump], flBumpInfluence ); + } + + pRadial->weight[ndxRadial] += flBumpInfluence; + } + else + { + float flScale = patchNormal.Dot( normals[0] ); + flScale = clamp( flScale, 0.0f, flScale ); + float flBumpInfluence = influence * flScale * 0.05f; + + for( int ndxBump = 0; ndxBump < ( NUM_BUMP_VECTS+1 ); ndxBump++ ) + { + pRadial->light[ndxBump][ndxRadial].AddWeighted( pPatchLight[0], flBumpInfluence ); + } + + pRadial->weight[ndxRadial] += flBumpInfluence; + } + } + else + { + float flScale = patchNormal.Dot( luxelNormal ); + flScale = clamp( flScale, 0.0f, flScale ); + influence *= flScale; + pRadial->light[0][ndxRadial].AddWeighted( pPatchLight[0], influence ); + + // add the weight value + pRadial->weight[ndxRadial] += influence; + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CVRadDispMgr::RadialLuxelAddPatch( int ndxFace, Vector const &luxelPt, + Vector const &luxelNormal, float radius, + radial_t *pRadial, int ndxRadial, bool bBump, + CUtlVector &interestingPatches ) +{ +#ifdef SAMPLEHASH_QUERY_ONCE + for ( int i=0; i < interestingPatches.Count(); i++ ) + { + CPatch *pPatch = interestingPatches[i]; + bool bNeighborBump = texinfo[g_pFaces[pPatch->faceNumber].texinfo].flags & SURF_BUMPLIGHT ? true : false; + + Vector patchLight[NUM_BUMP_VECTS+1]; + GetPatchLight( pPatch, bBump, patchLight ); + AddPatchLightToRadial( pPatch->origin, pPatch->normal, patchLight, radius*radius, + luxelPt, luxelNormal, pRadial, ndxRadial, bBump, bNeighborBump ); + } +#else + // calculate one over the voxel size + float ooVoxelSize = 1.0f / SAMPLEHASH_VOXEL_SIZE; + + // + // find voxel info + // + int voxelMin[3], voxelMax[3]; + for ( int axis = 0; axis < 3; axis++ ) + { + voxelMin[axis] = ( int )( ( luxelPt[axis] - radius ) * ooVoxelSize ); + voxelMax[axis] = ( int )( ( luxelPt[axis] + radius ) * ooVoxelSize ) + 1; + } + + unsigned short curIterationKey = IncrementPatchIterationKey(); + PatchSampleData_t patchData; + for ( int ndxZ = voxelMin[2]; ndxZ < voxelMax[2] + 1; ndxZ++ ) + { + for ( int ndxY = voxelMin[1]; ndxY < voxelMax[1] + 1; ndxY++ ) + { + for ( int ndxX = voxelMin[0]; ndxX < voxelMax[0] + 1; ndxX++ ) + { + patchData.x = ndxX * 100; + patchData.y = ndxY * 10; + patchData.z = ndxZ; + + UtlHashHandle_t handle = g_PatchSampleHashTable.Find( patchData ); + if ( handle != g_PatchSampleHashTable.InvalidHandle() ) + { + PatchSampleData_t *pPatchData = &g_PatchSampleHashTable.Element( handle ); + int count = pPatchData->m_ndxPatches.Count(); + for ( int ndx = 0; ndx < count; ndx++ ) + { + int ndxPatch = pPatchData->m_ndxPatches.Element( ndx ); + CPatch *pPatch = &g_Patches.Element( ndxPatch ); + if ( pPatch && pPatch->m_IterationKey != curIterationKey ) + { + pPatch->m_IterationKey = curIterationKey; + + if ( IsNeighbor( ndxFace, pPatch->faceNumber ) ) + { + bool bNeighborBump = texinfo[g_pFaces[pPatch->faceNumber].texinfo].flags & SURF_BUMPLIGHT ? true : false; + + Vector patchLight[NUM_BUMP_VECTS+1]; + GetPatchLight( pPatch, bBump, patchLight ); + AddPatchLightToRadial( pPatch->origin, pPatch->normal, patchLight, radius*radius, + luxelPt, luxelNormal, pRadial, ndxRadial, bBump, bNeighborBump ); + } + } + } + } + } + } + } +#endif +} + + +void CVRadDispMgr::GetInterestingPatchesForLuxels( + int ndxFace, + CUtlVector &interestingPatches, + float patchSampleRadius ) +{ + facelight_t *pFaceLight = &facelight[ndxFace]; + + // Get the max bounds of all voxels that these luxels touch. + Vector vLuxelMin( FLT_MAX, FLT_MAX, FLT_MAX ); + Vector vLuxelMax( -FLT_MAX, -FLT_MAX, -FLT_MAX ); + for ( int i=0; i < pFaceLight->numluxels; i++ ) + { + VectorMin( pFaceLight->luxel[i], vLuxelMin, vLuxelMin ); + VectorMax( pFaceLight->luxel[i], vLuxelMax, vLuxelMax ); + } + + int allVoxelMin[3], allVoxelMax[3]; + for ( int axis = 0; axis < 3; axis++ ) + { + allVoxelMin[axis] = ( int )( ( vLuxelMin[axis] - patchSampleRadius ) / SAMPLEHASH_VOXEL_SIZE ); + allVoxelMax[axis] = ( int )( ( vLuxelMax[axis] + patchSampleRadius ) / SAMPLEHASH_VOXEL_SIZE ) + 1; + } + int allVoxelSize[3] = { allVoxelMax[0] - allVoxelMin[0], allVoxelMax[1] - allVoxelMin[1], allVoxelMax[2] - allVoxelMin[2] }; + + + // Now figure out exactly which voxels these luxels touch. + CUtlVector voxelBits; + voxelBits.SetSize( ((allVoxelSize[0] * allVoxelSize[1] * allVoxelSize[2]) + 7) / 8 ); + memset( voxelBits.Base(), 0, voxelBits.Count() ); + + for ( int i=0; i < pFaceLight->numluxels; i++ ) + { + int voxelMin[3], voxelMax[3]; + for ( int axis=0; axis < 3; axis++ ) + { + voxelMin[axis] = ( int )( ( pFaceLight->luxel[i][axis] - patchSampleRadius ) / SAMPLEHASH_VOXEL_SIZE ); + voxelMax[axis] = ( int )( ( pFaceLight->luxel[i][axis] + patchSampleRadius ) / SAMPLEHASH_VOXEL_SIZE ) + 1; + } + + for ( int x=voxelMin[0]; x < voxelMax[0]; x++ ) + { + for ( int y=voxelMin[1]; y < voxelMax[1]; y++ ) + { + for ( int z=voxelMin[2]; z < voxelMax[2]; z++ ) + { + int iBit = (z - allVoxelMin[2])*(allVoxelSize[0]*allVoxelSize[1]) + + (y-allVoxelMin[1])*allVoxelSize[0] + + (x-allVoxelMin[0]); + voxelBits[iBit>>3] |= (1 << (iBit & 7)); + } + } + } + } + + + // Now get the list of patches that touch those voxels. + unsigned short curIterationKey = IncrementPatchIterationKey(); + + for ( int x=0; x < allVoxelSize[0]; x++ ) + { + for ( int y=0; y < allVoxelSize[1]; y++ ) + { + for ( int z=0; z < allVoxelSize[2]; z++ ) + { + // Make sure this voxel has any luxels that care about it. + int iBit = z*(allVoxelSize[0]*allVoxelSize[1]) + y*allVoxelSize[0] + x; + unsigned char val = voxelBits[iBit>>3] & (1 << (iBit & 7)); + if ( !val ) + continue; + + PatchSampleData_t patchData; + patchData.x = (x + allVoxelMin[0]) * 100; + patchData.y = (y + allVoxelMin[1]) * 10; + patchData.z = (z + allVoxelMin[2]); + + UtlHashHandle_t handle = g_PatchSampleHashTable.Find( patchData ); + if ( handle != g_PatchSampleHashTable.InvalidHandle() ) + { + PatchSampleData_t *pPatchData = &g_PatchSampleHashTable.Element( handle ); + + // For all patches that touch this hash table element.. + for ( int ndx = 0; ndx < pPatchData->m_ndxPatches.Count(); ndx++ ) + { + int ndxPatch = pPatchData->m_ndxPatches.Element( ndx ); + CPatch *pPatch = &g_Patches.Element( ndxPatch ); + + // If we haven't touched the patch already and it's a valid neighbor, then we want to use it. + if ( pPatch && pPatch->m_IterationKey != curIterationKey ) + { + pPatch->m_IterationKey = curIterationKey; + + if ( IsNeighbor( ndxFace, pPatch->faceNumber ) ) + { + interestingPatches.AddToTail( pPatch ); + } + } + } + } + } + } + } +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CVRadDispMgr::RadialPatchBuild( CVRADDispColl *pDispTree, radial_t *pRadial, + bool bBump ) +{ + // + // get data lighting data + // + int ndxFace = pDispTree->GetParentIndex(); + facelight_t *pFaceLight = &facelight[ndxFace]; + + // get the influence radius + float radius2 = pDispTree->GetPatchSampleRadius2(); + float radius = ( float )sqrt( radius2 ); + + CUtlVector interestingPatches; +#ifdef SAMPLEHASH_QUERY_ONCE + GetInterestingPatchesForLuxels( ndxFace, interestingPatches, radius ); +#endif + + int radialSize = pRadial->w * pRadial->h; + for( int ndxRadial = 0; ndxRadial < radialSize; ndxRadial++ ) + { + RadialLuxelAddPatch( + ndxFace, + pFaceLight->luxel[ndxRadial], + pFaceLight->luxelNormals[ndxRadial], + radius, + pRadial, + ndxRadial, + bBump, + interestingPatches ); + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +radial_t *CVRadDispMgr::BuildPatchRadial( int ndxFace, bool bBump ) +{ + // allocate the radial + radial_t *pRadial = AllocateRadial( ndxFace ); + if( !pRadial ) + return NULL; + + // + // step 1: get the displacement surface to be lit + // + DispCollTree_t &dispTree = m_DispTrees[g_pFaces[ndxFace].dispinfo]; + CVRADDispColl *pDispTree = dispTree.m_pDispTree; + if( !pDispTree ) + return NULL; + + // step 2: build radial of patch light + RadialPatchBuild( pDispTree, pRadial, bBump ); + + // step 3: return the built radial + return pRadial; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool SampleInSolid( sample_t *pSample ) +{ + int ndxLeaf = PointLeafnum( pSample->pos ); + return ( dleafs[ndxLeaf].contents == CONTENTS_SOLID ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CVRadDispMgr::InsertSamplesDataIntoHashTable( void ) +{ + int totalSamples = 0; +#if 0 + int totalSamplesInSolid = 0; +#endif + + for( int ndxFace = 0; ndxFace < numfaces; ndxFace++ ) + { + dface_t *pFace = &g_pFaces[ndxFace]; + facelight_t *pFaceLight = &facelight[ndxFace]; + if( !pFace || !pFaceLight ) + continue; + + if( texinfo[pFace->texinfo].flags & TEX_SPECIAL ) + continue; + +#if 0 + bool bDisp = ( pFace->dispinfo != -1 ); +#endif + // + // for each sample + // + for( int ndxSample = 0; ndxSample < pFaceLight->numsamples; ndxSample++ ) + { + sample_t *pSample = &pFaceLight->sample[ndxSample]; + if( pSample ) + { +#if 0 + if( bDisp ) + { + // test sample to see if the displacement samples resides in solid + if( SampleInSolid( pSample ) ) + { + totalSamplesInSolid++; + continue; + } + } +#endif + + // create the sample handle + SampleHandle_t sampleHandle = ndxSample; + sampleHandle |= ( ndxFace << 16 ); + + SampleData_AddSample( pSample, sampleHandle ); + } + + } + + totalSamples += pFaceLight->numsamples; + } + +#if 0 + // not implemented yet!!! + Msg( "%d samples in solid\n", totalSamplesInSolid ); +#endif + + // log the distribution + SampleData_Log(); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CVRadDispMgr::InsertPatchSampleDataIntoHashTable( void ) +{ + // don't insert patch samples if we are not bouncing light + if( numbounce <= 0 ) + return; + + int totalPatchSamples = 0; + + for( int ndxFace = 0; ndxFace < numfaces; ndxFace++ ) + { + dface_t *pFace = &g_pFaces[ndxFace]; + facelight_t *pFaceLight = &facelight[ndxFace]; + if( !pFace || !pFaceLight ) + continue; + + if( texinfo[pFace->texinfo].flags & TEX_SPECIAL ) + continue; + + // + // for each patch + // + CPatch *pNextPatch = NULL; + if( g_FacePatches.Element( ndxFace ) != g_FacePatches.InvalidIndex() ) + { + for( CPatch *pPatch = &g_Patches.Element( g_FacePatches.Element( ndxFace ) ); pPatch; pPatch = pNextPatch ) + { + // next patch + pNextPatch = NULL; + if( pPatch->ndxNext != g_Patches.InvalidIndex() ) + { + pNextPatch = &g_Patches.Element( pPatch->ndxNext ); + } + + // skip patches with children + if( pPatch->child1 != g_Patches.InvalidIndex() ) + continue; + + int ndxPatch = pPatch - g_Patches.Base(); + PatchSampleData_AddSample( pPatch, ndxPatch ); + + totalPatchSamples++; + } + } + } +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CVRadDispMgr::StartTimer( const char *name ) +{ + Msg( name ); + m_Timer.Start(); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CVRadDispMgr::EndTimer( void ) +{ + m_Timer.End(); + CCycleCount duration = m_Timer.GetDuration(); + double seconds = duration.GetSeconds(); + + Msg( "Done<%1.4lf sec>\n", seconds ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool CVRadDispMgr::BuildDispSamples( lightinfo_t *pLightInfo, facelight_t *pFaceLight, int ndxFace ) +{ + // get the tree assosciated with the face + DispCollTree_t &dispTree = m_DispTrees[g_pFaces[ndxFace].dispinfo]; + CVRADDispColl *pDispTree = dispTree.m_pDispTree; + if( !pDispTree ) + return false; + + // lightmap size + int width = pLightInfo->face->m_LightmapTextureSizeInLuxels[0]+1; + int height = pLightInfo->face->m_LightmapTextureSizeInLuxels[1]+1; + + // calculate the steps in uv space + float stepU = 1.0f / ( float )width; + float stepV = 1.0f / ( float )height; + float halfStepU = stepU * 0.5f; + float halfStepV = stepV * 0.5f; + + // + // build the winding points (used to generate world space winding and + // calculate the area of the "sample") + // + int ndxU, ndxV; + + // NOTE: These allocations are necessary to avoid stack overflow + // FIXME: Solve with storing global per-thread temp buffers if there's + // a performance problem with this solution... + bool bTempAllocationNecessary = ((height + 1) * (width + 1)) > SINGLE_BRUSH_MAP; + + Vector worldPointBuffer[SINGLE_BRUSH_MAP]; + sample_t sampleBuffer[SINGLE_BRUSH_MAP*2]; + + Vector *pWorldPoints = worldPointBuffer; + sample_t *pSamples = sampleBuffer; + if (bTempAllocationNecessary) + { + pWorldPoints = new Vector[ SINGLEMAP ]; + pSamples = new sample_t[ SINGLEMAP ]; + } + + for( ndxV = 0; ndxV < ( height + 1 ); ndxV++ ) + { + for( ndxU = 0; ndxU < ( width + 1 ); ndxU++ ) + { + int ndx = ( ndxV * ( width + 1 ) ) + ndxU; + + Vector2D uv( ndxU * stepU, ndxV * stepV ); + pDispTree->DispUVToSurfPoint( uv, pWorldPoints[ndx], 0.0f ); + } + } + + + for( ndxV = 0; ndxV < height; ndxV++ ) + { + for( ndxU = 0; ndxU < width; ndxU++ ) + { + // build the winding + winding_t *pWinding = AllocWinding( 4 ); + if( pWinding ) + { + pWinding->numpoints = 4; + pWinding->p[0] = pWorldPoints[(ndxV*(width+1))+ndxU]; + pWinding->p[1] = pWorldPoints[((ndxV+1)*(width+1))+ndxU]; + pWinding->p[2] = pWorldPoints[((ndxV+1)*(width+1))+(ndxU+1)]; + pWinding->p[3] = pWorldPoints[(ndxV*(width+1))+(ndxU+1)]; + + // calculate the area + float area = WindingArea( pWinding ); + + int ndxSample = ( ndxV * width ) + ndxU; + pSamples[ndxSample].w = pWinding; + pSamples[ndxSample].area = area; + } + else + { + Msg( "BuildDispSamples: WARNING - failed winding allocation\n" ); + } + } + } + + // + // build the samples points (based on s, t and sampleoffset (center of samples); + // generates world space position and normal) + // + for( ndxV = 0; ndxV < height; ndxV++ ) + { + for( ndxU = 0; ndxU < width; ndxU++ ) + { + int ndxSample = ( ndxV * width ) + ndxU; + pSamples[ndxSample].s = ndxU; + pSamples[ndxSample].t = ndxV; + pSamples[ndxSample].coord[0] = ( ndxU * stepU ) + halfStepU; + pSamples[ndxSample].coord[1] = ( ndxV * stepV ) + halfStepV; + pDispTree->DispUVToSurfPoint( pSamples[ndxSample].coord, pSamples[ndxSample].pos, 1.0f ); + pDispTree->DispUVToSurfNormal( pSamples[ndxSample].coord, pSamples[ndxSample].normal ); + } + } + + // + // copy over samples + // + pFaceLight->numsamples = width * height; + pFaceLight->sample = ( sample_t* )calloc( pFaceLight->numsamples, sizeof( *pFaceLight->sample ) ); + if( !pFaceLight->sample ) + goto buildDispSamplesError; + + memcpy( pFaceLight->sample, pSamples, pFaceLight->numsamples * sizeof( *pFaceLight->sample ) ); + + // statistics - warning?! + if( pFaceLight->numsamples == 0 ) + { + Msg( "BuildDispSamples: WARNING - no samples %d\n", pLightInfo->face - g_pFaces ); + } + + if (bTempAllocationNecessary) + { + delete[] pWorldPoints; + delete[] pSamples; + } + + return true; + +buildDispSamplesError: + if (bTempAllocationNecessary) + { + delete[] pWorldPoints; + delete[] pSamples; + } + + return false; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool CVRadDispMgr::BuildDispLuxels( lightinfo_t *pLightInfo, facelight_t *pFaceLight, int ndxFace ) +{ + // get the tree assosciated with the face + DispCollTree_t &dispTree = m_DispTrees[g_pFaces[ndxFace].dispinfo]; + CVRADDispColl *pDispTree = dispTree.m_pDispTree; + if( !pDispTree ) + return false; + + // lightmap size + int width = pLightInfo->face->m_LightmapTextureSizeInLuxels[0]+1; + int height = pLightInfo->face->m_LightmapTextureSizeInLuxels[1]+1; + + // calcuate actual luxel points + pFaceLight->numluxels = width * height; + pFaceLight->luxel = ( Vector* )calloc( pFaceLight->numluxels, sizeof( *pFaceLight->luxel ) ); + pFaceLight->luxelNormals = ( Vector* )calloc( pFaceLight->numluxels, sizeof( Vector ) ); + if( !pFaceLight->luxel || !pFaceLight->luxelNormals ) + return false; + + float stepU = 1.0f / ( float )( width - 1 ); + float stepV = 1.0f / ( float )( height - 1 ); + + for( int ndxV = 0; ndxV < height; ndxV++ ) + { + for( int ndxU = 0; ndxU < width; ndxU++ ) + { + int ndxLuxel = ( ndxV * width ) + ndxU; + + Vector2D uv( ndxU * stepU, ndxV * stepV ); + pDispTree->DispUVToSurfPoint( uv, pFaceLight->luxel[ndxLuxel], 1.0f ); + pDispTree->DispUVToSurfNormal( uv, pFaceLight->luxelNormals[ndxLuxel] ); + } + } + + return true; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool CVRadDispMgr::BuildDispSamplesAndLuxels_DoFast( lightinfo_t *pLightInfo, facelight_t *pFaceLight, int ndxFace ) +{ + // get the tree assosciated with the face + DispCollTree_t &dispTree = m_DispTrees[g_pFaces[ndxFace].dispinfo]; + CVRADDispColl *pDispTree = dispTree.m_pDispTree; + if( !pDispTree ) + return false; + + // lightmap size + int width = pLightInfo->face->m_LightmapTextureSizeInLuxels[0]+1; + int height = pLightInfo->face->m_LightmapTextureSizeInLuxels[1]+1; + + // calcuate actual luxel points + pFaceLight->numsamples = width * height; + pFaceLight->sample = ( sample_t* )calloc( pFaceLight->numsamples, sizeof( *pFaceLight->sample ) ); + if( !pFaceLight->sample ) + return false; + + pFaceLight->numluxels = width * height; + pFaceLight->luxel = ( Vector* )calloc( pFaceLight->numluxels, sizeof( *pFaceLight->luxel ) ); + pFaceLight->luxelNormals = ( Vector* )calloc( pFaceLight->numluxels, sizeof( Vector ) ); + if( !pFaceLight->luxel || !pFaceLight->luxelNormals ) + return false; + + float stepU = 1.0f / ( float )( width - 1 ); + float stepV = 1.0f / ( float )( height - 1 ); + float halfStepU = stepU * 0.5f; + float halfStepV = stepV * 0.5f; + + for( int ndxV = 0; ndxV < height; ndxV++ ) + { + for( int ndxU = 0; ndxU < width; ndxU++ ) + { + int ndx = ( ndxV * width ) + ndxU; + + pFaceLight->sample[ndx].s = ndxU; + pFaceLight->sample[ndx].t = ndxV; + pFaceLight->sample[ndx].coord[0] = ( ndxU * stepU ) + halfStepU; + pFaceLight->sample[ndx].coord[1] = ( ndxV * stepV ) + halfStepV; + + pDispTree->DispUVToSurfPoint( pFaceLight->sample[ndx].coord, pFaceLight->sample[ndx].pos, 1.0f ); + pDispTree->DispUVToSurfNormal( pFaceLight->sample[ndx].coord, pFaceLight->sample[ndx].normal ); + + pFaceLight->luxel[ndx] = pFaceLight->sample[ndx].pos; + pFaceLight->luxelNormals[ndx] = pFaceLight->sample[ndx].normal; + } + } + + return true; +} diff --git a/mp/src/utils/vrad/vraddll.cpp b/mp/src/utils/vrad/vraddll.cpp new file mode 100644 index 00000000..87f8d7fc --- /dev/null +++ b/mp/src/utils/vrad/vraddll.cpp @@ -0,0 +1,243 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +//#include +#include "vraddll.h" +#include "bsplib.h" +#include "vrad.h" +#include "map_shared.h" +#include "lightmap.h" +#include "threads.h" + + +static CUtlVector g_LastGoodLightData; +static CUtlVector g_FacesTouched; + + +static CVRadDLL g_VRadDLL; +EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CVRadDLL, IVRadDLL, VRAD_INTERFACE_VERSION, g_VRadDLL ); +EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CVRadDLL, ILaunchableDLL, LAUNCHABLE_DLL_INTERFACE_VERSION, g_VRadDLL ); + + +// ---------------------------------------------------------------------------- // +// temporary static array data size tracking +// original data size = 143 megs +// - converting ddispindices, ddispverts, g_dispinfo, and dlightdata to CUtlVector +// - 51 megs +// ---------------------------------------------------------------------------- // + +class dat +{ +public: + char *name; + int size; +}; +#define DATENTRY(name) {#name, sizeof(name)} + +dat g_Dats[] = +{ + DATENTRY(dmodels), + DATENTRY(dvisdata), + DATENTRY(dlightdataLDR), + DATENTRY(dlightdataHDR), + DATENTRY(dentdata), + DATENTRY(dleafs), + DATENTRY(dplanes), + DATENTRY(dvertexes), + DATENTRY(g_vertnormalindices), + DATENTRY(g_vertnormals), + DATENTRY(texinfo), + DATENTRY(dtexdata), + DATENTRY(g_dispinfo), + DATENTRY(dorigfaces), + DATENTRY(g_primitives), + DATENTRY(g_primverts), + DATENTRY(g_primindices), + DATENTRY(dfaces), + DATENTRY(dedges), + DATENTRY(dleaffaces), + DATENTRY(dleafbrushes), + DATENTRY(dsurfedges), + DATENTRY(dbrushes), + DATENTRY(dbrushsides), + DATENTRY(dareas), + DATENTRY(dareaportals), + DATENTRY(dworldlights), + DATENTRY(dleafwaterdata), + DATENTRY(g_ClipPortalVerts), + DATENTRY(g_CubemapSamples), + DATENTRY(g_TexDataStringData), + DATENTRY(g_TexDataStringTable), + DATENTRY(g_Overlays) +}; + +int CalcDatSize() +{ + int ret = 0; + int count = sizeof( g_Dats ) / sizeof( g_Dats[0] ); + + int i; + for( i=1; i < count; i++ ) + { + if( g_Dats[i-1].size > g_Dats[i].size ) + { + dat temp = g_Dats[i-1]; + g_Dats[i-1] = g_Dats[i]; + g_Dats[i] = temp; + + if( i > 1 ) + i -= 2; + else + i -= 1; + } + } + + for( i=0; i < count; i++ ) + ret += g_Dats[i].size; + + return ret; +} + +int g_TotalDatSize = CalcDatSize(); + + + + +int CVRadDLL::main( int argc, char **argv ) +{ + return VRAD_Main( argc, argv ); +} + + +bool CVRadDLL::Init( char const *pFilename ) +{ + VRAD_Init(); + + // Set options and run vrad startup code. + do_fast = true; + g_bLowPriorityThreads = true; + g_pIncremental = GetIncremental(); + + VRAD_LoadBSP( pFilename ); + return true; +} + + +void CVRadDLL::Release() +{ +} + + +void CVRadDLL::GetBSPInfo( CBSPInfo *pInfo ) +{ + pInfo->dlightdata = pdlightdata->Base(); + pInfo->lightdatasize = pdlightdata->Count(); + + pInfo->dfaces = dfaces; + pInfo->m_pFacesTouched = g_FacesTouched.Base(); + pInfo->numfaces = numfaces; + + pInfo->dvertexes = dvertexes; + pInfo->numvertexes = numvertexes; + + pInfo->dedges = dedges; + pInfo->numedges = numedges; + + pInfo->dsurfedges = dsurfedges; + pInfo->numsurfedges = numsurfedges; + + pInfo->texinfo = texinfo.Base(); + pInfo->numtexinfo = texinfo.Count(); + + pInfo->g_dispinfo = g_dispinfo.Base(); + pInfo->g_numdispinfo = g_dispinfo.Count(); + + pInfo->dtexdata = dtexdata; + pInfo->numtexdata = numtexdata; + + pInfo->texDataStringData = g_TexDataStringData.Base(); + pInfo->nTexDataStringData = g_TexDataStringData.Count(); + + pInfo->texDataStringTable = g_TexDataStringTable.Base(); + pInfo->nTexDataStringTable = g_TexDataStringTable.Count(); +} + + +bool CVRadDLL::DoIncrementalLight( char const *pVMFFile ) +{ + char tempPath[MAX_PATH], tempFilename[MAX_PATH]; + GetTempPath( sizeof( tempPath ), tempPath ); + GetTempFileName( tempPath, "vmf_entities_", 0, tempFilename ); + + FileHandle_t fp = g_pFileSystem->Open( tempFilename, "wb" ); + if( !fp ) + return false; + + g_pFileSystem->Write( pVMFFile, strlen(pVMFFile)+1, fp ); + g_pFileSystem->Close( fp ); + + // Parse the new entities. + if( !LoadEntsFromMapFile( tempFilename ) ) + return false; + + // Create lights. + CreateDirectLights(); + + // set up sky cameras + ProcessSkyCameras(); + + g_bInterrupt = false; + if( RadWorld_Go() ) + { + // Save off the last finished lighting results for the BSP. + g_LastGoodLightData.CopyArray( pdlightdata->Base(), pdlightdata->Count() ); + if( g_pIncremental ) + g_pIncremental->GetFacesTouched( g_FacesTouched ); + + return true; + } + else + { + g_iCurFace = 0; + return false; + } +} + + +bool CVRadDLL::Serialize() +{ + if( !g_pIncremental ) + return false; + + if( g_LastGoodLightData.Count() > 0 ) + { + pdlightdata->CopyArray( g_LastGoodLightData.Base(), g_LastGoodLightData.Count() ); + + if( g_pIncremental->Serialize() ) + { + // Delete this so it doesn't keep re-saving it. + g_LastGoodLightData.Purge(); + return true; + } + } + + return false; +} + + +float CVRadDLL::GetPercentComplete() +{ + return (float)g_iCurFace / numfaces; +} + + +void CVRadDLL::Interrupt() +{ + g_bInterrupt = true; +} + + diff --git a/mp/src/utils/vrad/vraddll.h b/mp/src/utils/vrad/vraddll.h new file mode 100644 index 00000000..506e8eb6 --- /dev/null +++ b/mp/src/utils/vrad/vraddll.h @@ -0,0 +1,34 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef VRADDLL_H +#define VRADDLL_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "ivraddll.h" +#include "ilaunchabledll.h" + + +class CVRadDLL : public IVRadDLL, public ILaunchableDLL +{ +// IVRadDLL overrides. +public: + virtual int main( int argc, char **argv ); + virtual bool Init( char const *pFilename ); + virtual void Release(); + virtual void GetBSPInfo( CBSPInfo *pInfo ); + virtual bool DoIncrementalLight( char const *pVMFFile ); + virtual bool Serialize(); + virtual float GetPercentComplete(); + virtual void Interrupt(); +}; + + +#endif // VRADDLL_H diff --git a/mp/src/utils/vrad/vradstaticprops.cpp b/mp/src/utils/vrad/vradstaticprops.cpp new file mode 100644 index 00000000..f240d94f --- /dev/null +++ b/mp/src/utils/vrad/vradstaticprops.cpp @@ -0,0 +1,1915 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Revision: $ +// $NoKeywords: $ +// +// This file contains code to allow us to associate client data with bsp leaves. +// +//=============================================================================// + +#include "vrad.h" +#include "mathlib/vector.h" +#include "UtlBuffer.h" +#include "utlvector.h" +#include "GameBSPFile.h" +#include "BSPTreeData.h" +#include "VPhysics_Interface.h" +#include "Studio.h" +#include "Optimize.h" +#include "Bsplib.h" +#include "CModel.h" +#include "PhysDll.h" +#include "phyfile.h" +#include "collisionutils.h" +#include "tier1/KeyValues.h" +#include "pacifier.h" +#include "materialsystem/imaterial.h" +#include "materialsystem/hardwareverts.h" +#include "byteswap.h" +#include "mpivrad.h" +#include "vtf/vtf.h" +#include "tier1/utldict.h" +#include "tier1/utlsymbol.h" + +#include "messbuf.h" +#include "vmpi.h" +#include "vmpi_distribute_work.h" + + +#define ALIGN_TO_POW2(x,y) (((x)+(y-1))&~(y-1)) + +// identifies a vertex embedded in solid +// lighting will be copied from nearest valid neighbor +struct badVertex_t +{ + int m_ColorVertex; + Vector m_Position; + Vector m_Normal; +}; + +// a final colored vertex +struct colorVertex_t +{ + Vector m_Color; + Vector m_Position; + bool m_bValid; +}; + +class CComputeStaticPropLightingResults +{ +public: + ~CComputeStaticPropLightingResults() + { + m_ColorVertsArrays.PurgeAndDeleteElements(); + } + + CUtlVector< CUtlVector* > m_ColorVertsArrays; +}; + +//----------------------------------------------------------------------------- +// Globals +//----------------------------------------------------------------------------- +CUtlSymbolTable g_ForcedTextureShadowsModels; + +// DON'T USE THIS FROM WITHIN A THREAD. THERE IS A THREAD CONTEXT CREATED +// INSIDE PropTested_t. USE THAT INSTEAD. +IPhysicsCollision *s_pPhysCollision = NULL; + +//----------------------------------------------------------------------------- +// Vrad's static prop manager +//----------------------------------------------------------------------------- + +class CVradStaticPropMgr : public IVradStaticPropMgr +{ +public: + // constructor, destructor + CVradStaticPropMgr(); + virtual ~CVradStaticPropMgr(); + + // methods of IStaticPropMgr + void Init(); + void Shutdown(); + + // iterate all the instanced static props and compute their vertex lighting + void ComputeLighting( int iThread ); + +private: + // VMPI stuff. + static void VMPI_ProcessStaticProp_Static( int iThread, uint64 iStaticProp, MessageBuffer *pBuf ); + static void VMPI_ReceiveStaticPropResults_Static( uint64 iStaticProp, MessageBuffer *pBuf, int iWorker ); + void VMPI_ProcessStaticProp( int iThread, int iStaticProp, MessageBuffer *pBuf ); + void VMPI_ReceiveStaticPropResults( int iStaticProp, MessageBuffer *pBuf, int iWorker ); + + // local thread version + static void ThreadComputeStaticPropLighting( int iThread, void *pUserData ); + void ComputeLightingForProp( int iThread, int iStaticProp ); + + // Methods associated with unserializing static props + void UnserializeModelDict( CUtlBuffer& buf ); + void UnserializeModels( CUtlBuffer& buf ); + void UnserializeStaticProps(); + + // Creates a collision model + void CreateCollisionModel( char const* pModelName ); + +private: + // Unique static prop models + struct StaticPropDict_t + { + vcollide_t m_loadedModel; + CPhysCollide* m_pModel; + Vector m_Mins; // Bounding box is in local coordinates + Vector m_Maxs; + studiohdr_t* m_pStudioHdr; + CUtlBuffer m_VtxBuf; + CUtlVector m_textureShadowIndex; // each texture has an index if this model casts texture shadows + CUtlVector m_triangleMaterialIndex;// each triangle has an index if this model casts texture shadows + }; + + struct MeshData_t + { + CUtlVector m_Verts; + int m_nLod; + }; + + // A static prop instance + struct CStaticProp + { + Vector m_Origin; + QAngle m_Angles; + Vector m_mins; + Vector m_maxs; + Vector m_LightingOrigin; + int m_ModelIdx; + BSPTreeDataHandle_t m_Handle; + CUtlVector m_MeshData; + int m_Flags; + bool m_bLightingOriginValid; + }; + + // Enumeration context + struct EnumContext_t + { + PropTested_t* m_pPropTested; + Ray_t const* m_pRay; + }; + + // The list of all static props + CUtlVector m_StaticPropDict; + CUtlVector m_StaticProps; + + bool m_bIgnoreStaticPropTrace; + + void ComputeLighting( CStaticProp &prop, int iThread, int prop_index, CComputeStaticPropLightingResults *pResults ); + void ApplyLightingToStaticProp( CStaticProp &prop, const CComputeStaticPropLightingResults *pResults ); + + void SerializeLighting(); + void AddPolysForRayTrace(); + void BuildTriList( CStaticProp &prop ); +}; + + +//----------------------------------------------------------------------------- +// Expose IVradStaticPropMgr to vrad +//----------------------------------------------------------------------------- + +static CVradStaticPropMgr g_StaticPropMgr; +IVradStaticPropMgr* StaticPropMgr() +{ + return &g_StaticPropMgr; +} + + +//----------------------------------------------------------------------------- +// constructor, destructor +//----------------------------------------------------------------------------- + +CVradStaticPropMgr::CVradStaticPropMgr() +{ + // set to ignore static prop traces + m_bIgnoreStaticPropTrace = false; +} + +CVradStaticPropMgr::~CVradStaticPropMgr() +{ +} + +//----------------------------------------------------------------------------- +// Makes sure the studio model is a static prop +//----------------------------------------------------------------------------- + +bool IsStaticProp( studiohdr_t* pHdr ) +{ + if (!(pHdr->flags & STUDIOHDR_FLAGS_STATIC_PROP)) + return false; + + return true; +} + + +//----------------------------------------------------------------------------- +// Load a file into a Utlbuf +//----------------------------------------------------------------------------- +static bool LoadFile( char const* pFileName, CUtlBuffer& buf ) +{ + if ( !g_pFullFileSystem ) + return false; + + return g_pFullFileSystem->ReadFile( pFileName, NULL, buf ); +} + + +//----------------------------------------------------------------------------- +// Constructs the file name from the model name +//----------------------------------------------------------------------------- +static char const* ConstructFileName( char const* pModelName ) +{ + static char buf[1024]; + sprintf( buf, "%s%s", gamedir, pModelName ); + return buf; +} + + +//----------------------------------------------------------------------------- +// Computes a convex hull from a studio mesh +//----------------------------------------------------------------------------- +static CPhysConvex* ComputeConvexHull( mstudiomesh_t* pMesh, studiohdr_t *pStudioHdr ) +{ + const mstudio_meshvertexdata_t *vertData = pMesh->GetVertexData( (void *)pStudioHdr ); + Assert( vertData ); // This can only return NULL on X360 for now + + // Generate a list of all verts in the mesh + Vector** ppVerts = (Vector**)_alloca(pMesh->numvertices * sizeof(Vector*) ); + for (int i = 0; i < pMesh->numvertices; ++i) + { + ppVerts[i] = vertData->Position(i); + } + + // Generate a convex hull from the verts + return s_pPhysCollision->ConvexFromVerts( ppVerts, pMesh->numvertices ); +} + + +//----------------------------------------------------------------------------- +// Computes a convex hull from the studio model +//----------------------------------------------------------------------------- +CPhysCollide* ComputeConvexHull( studiohdr_t* pStudioHdr ) +{ + CUtlVector convexHulls; + + for (int body = 0; body < pStudioHdr->numbodyparts; ++body ) + { + mstudiobodyparts_t *pBodyPart = pStudioHdr->pBodypart( body ); + for( int model = 0; model < pBodyPart->nummodels; ++model ) + { + mstudiomodel_t *pStudioModel = pBodyPart->pModel( model ); + for( int mesh = 0; mesh < pStudioModel->nummeshes; ++mesh ) + { + // Make a convex hull for each mesh + // NOTE: This won't work unless the model has been compiled + // with $staticprop + mstudiomesh_t *pStudioMesh = pStudioModel->pMesh( mesh ); + convexHulls.AddToTail( ComputeConvexHull( pStudioMesh, pStudioHdr ) ); + } + } + } + + // Convert an array of convex elements to a compiled collision model + // (this deletes the convex elements) + return s_pPhysCollision->ConvertConvexToCollide( convexHulls.Base(), convexHulls.Size() ); +} + + +//----------------------------------------------------------------------------- +// Load studio model vertex data from a file... +//----------------------------------------------------------------------------- + +bool LoadStudioModel( char const* pModelName, CUtlBuffer& buf ) +{ + // No luck, gotta build it + // Construct the file name... + if (!LoadFile( pModelName, buf )) + { + Warning("Error! Unable to load model \"%s\"\n", pModelName ); + return false; + } + + // Check that it's valid + if (strncmp ((const char *) buf.PeekGet(), "IDST", 4) && + strncmp ((const char *) buf.PeekGet(), "IDAG", 4)) + { + Warning("Error! Invalid model file \"%s\"\n", pModelName ); + return false; + } + + studiohdr_t* pHdr = (studiohdr_t*)buf.PeekGet(); + + Studio_ConvertStudioHdrToNewVersion( pHdr ); + + if (pHdr->version != STUDIO_VERSION) + { + Warning("Error! Invalid model version \"%s\"\n", pModelName ); + return false; + } + + if (!IsStaticProp(pHdr)) + { + Warning("Error! To use model \"%s\"\n" + " as a static prop, it must be compiled with $staticprop!\n", pModelName ); + return false; + } + + // ensure reset + pHdr->pVertexBase = NULL; + pHdr->pIndexBase = NULL; + + return true; +} + +bool LoadStudioCollisionModel( char const* pModelName, CUtlBuffer& buf ) +{ + char tmp[1024]; + Q_strncpy( tmp, pModelName, sizeof( tmp ) ); + Q_SetExtension( tmp, ".phy", sizeof( tmp ) ); + // No luck, gotta build it + if (!LoadFile( tmp, buf )) + { + // this is not an error, the model simply has no PHY file + return false; + } + + phyheader_t *header = (phyheader_t *)buf.PeekGet(); + + if ( header->size != sizeof(*header) || header->solidCount <= 0 ) + return false; + + return true; +} + +bool LoadVTXFile( char const* pModelName, const studiohdr_t *pStudioHdr, CUtlBuffer& buf ) +{ + char filename[MAX_PATH]; + + // construct filename + Q_StripExtension( pModelName, filename, sizeof( filename ) ); + strcat( filename, ".dx80.vtx" ); + + if ( !LoadFile( filename, buf ) ) + { + Warning( "Error! Unable to load file \"%s\"\n", filename ); + return false; + } + + OptimizedModel::FileHeader_t* pVtxHdr = (OptimizedModel::FileHeader_t *)buf.Base(); + + // Check that it's valid + if ( pVtxHdr->version != OPTIMIZED_MODEL_FILE_VERSION ) + { + Warning( "Error! Invalid VTX file version: %d, expected %d \"%s\"\n", pVtxHdr->version, OPTIMIZED_MODEL_FILE_VERSION, filename ); + return false; + } + if ( pVtxHdr->checkSum != pStudioHdr->checksum ) + { + Warning( "Error! Invalid VTX file checksum: %d, expected %d \"%s\"\n", pVtxHdr->checkSum, pStudioHdr->checksum, filename ); + return false; + } + + return true; +} + +//----------------------------------------------------------------------------- +// Gets a vertex position from a strip index +//----------------------------------------------------------------------------- +inline static Vector* PositionFromIndex( const mstudio_meshvertexdata_t *vertData, mstudiomesh_t* pMesh, OptimizedModel::StripGroupHeader_t* pStripGroup, int i ) +{ + OptimizedModel::Vertex_t* pVert = pStripGroup->pVertex( i ); + return vertData->Position( pVert->origMeshVertID ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Writes a glview text file containing the collision surface in question +// Input : *pCollide - +// *pFilename - +//----------------------------------------------------------------------------- +void DumpCollideToGlView( vcollide_t *pCollide, const char *pFilename ) +{ + if ( !pCollide ) + return; + + Msg("Writing %s...\n", pFilename ); + + FILE *fp = fopen( pFilename, "w" ); + for (int i = 0; i < pCollide->solidCount; ++i) + { + Vector *outVerts; + int vertCount = s_pPhysCollision->CreateDebugMesh( pCollide->solids[i], &outVerts ); + int triCount = vertCount / 3; + int vert = 0; + + unsigned char r = (i & 1) * 64 + 64; + unsigned char g = (i & 2) * 64 + 64; + unsigned char b = (i & 4) * 64 + 64; + + float fr = r / 255.0f; + float fg = g / 255.0f; + float fb = b / 255.0f; + + for ( int i = 0; i < triCount; i++ ) + { + fprintf( fp, "3\n" ); + fprintf( fp, "%6.3f %6.3f %6.3f %.2f %.3f %.3f\n", + outVerts[vert].x, outVerts[vert].y, outVerts[vert].z, fr, fg, fb ); + vert++; + fprintf( fp, "%6.3f %6.3f %6.3f %.2f %.3f %.3f\n", + outVerts[vert].x, outVerts[vert].y, outVerts[vert].z, fr, fg, fb ); + vert++; + fprintf( fp, "%6.3f %6.3f %6.3f %.2f %.3f %.3f\n", + outVerts[vert].x, outVerts[vert].y, outVerts[vert].z, fr, fg, fb ); + vert++; + } + s_pPhysCollision->DestroyDebugMesh( vertCount, outVerts ); + } + fclose( fp ); +} + + +static bool PointInTriangle( const Vector2D &p, const Vector2D &v0, const Vector2D &v1, const Vector2D &v2 ) +{ + float coords[3]; + GetBarycentricCoords2D( v0, v1, v2, p, coords ); + for ( int i = 0; i < 3; i++ ) + { + if ( coords[i] < 0.0f || coords[i] > 1.0f ) + return false; + } + float sum = coords[0] + coords[1] + coords[2]; + if ( sum > 1.0f ) + return false; + return true; +} + +bool LoadFileIntoBuffer( CUtlBuffer &buf, const char *pFilename ) +{ + FileHandle_t fileHandle = g_pFileSystem->Open( pFilename, "rb" ); + if ( !fileHandle ) + return false; + + // Get the file size + int texSize = g_pFileSystem->Size( fileHandle ); + buf.EnsureCapacity( texSize ); + int nBytesRead = g_pFileSystem->Read( buf.Base(), texSize, fileHandle ); + g_pFileSystem->Close( fileHandle ); + buf.SeekPut( CUtlBuffer::SEEK_HEAD, nBytesRead ); + buf.SeekGet( CUtlBuffer::SEEK_HEAD, 0 ); + return true; +} + +// keeps a list of all textures that cast shadows via alpha channel +class CShadowTextureList +{ +public: + // This loads a vtf and converts it to RGB8888 format + unsigned char *LoadVTFRGB8888( const char *pName, int *pWidth, int *pHeight, bool *pClampU, bool *pClampV ) + { + char szPath[MAX_PATH]; + Q_strncpy( szPath, "materials/", sizeof( szPath ) ); + Q_strncat( szPath, pName, sizeof( szPath ), COPY_ALL_CHARACTERS ); + Q_strncat( szPath, ".vtf", sizeof( szPath ), COPY_ALL_CHARACTERS ); + Q_FixSlashes( szPath, CORRECT_PATH_SEPARATOR ); + + CUtlBuffer buf; + if ( !LoadFileIntoBuffer( buf, szPath ) ) + return NULL; + IVTFTexture *pTex = CreateVTFTexture(); + if (!pTex->Unserialize( buf )) + return NULL; + Msg("Loaded alpha texture %s\n", szPath ); + unsigned char *pSrcImage = pTex->ImageData( 0, 0, 0, 0, 0, 0 ); + int iWidth = pTex->Width(); + int iHeight = pTex->Height(); + ImageFormat dstFormat = IMAGE_FORMAT_RGBA8888; + ImageFormat srcFormat = pTex->Format(); + *pClampU = (pTex->Flags() & TEXTUREFLAGS_CLAMPS) ? true : false; + *pClampV = (pTex->Flags() & TEXTUREFLAGS_CLAMPT) ? true : false; + unsigned char *pDstImage = new unsigned char[ImageLoader::GetMemRequired( iWidth, iHeight, 1, dstFormat, false )]; + + if( !ImageLoader::ConvertImageFormat( pSrcImage, srcFormat, + pDstImage, dstFormat, iWidth, iHeight, 0, 0 ) ) + { + delete[] pDstImage; + return NULL; + } + + *pWidth = iWidth; + *pHeight = iHeight; + return pDstImage; + } + + // Checks the database for the material and loads if necessary + // returns true if found and pIndex will be the index, -1 if no alpha shadows + bool FindOrLoadIfValid( const char *pMaterialName, int *pIndex ) + { + *pIndex = -1; + int index = m_Textures.Find(pMaterialName); + bool bFound = false; + if ( index != m_Textures.InvalidIndex() ) + { + bFound = true; + *pIndex = index; + } + else + { + KeyValues *pVMT = new KeyValues("vmt"); + CUtlBuffer buf(0,0,CUtlBuffer::TEXT_BUFFER); + LoadFileIntoBuffer( buf, pMaterialName ); + if ( pVMT->LoadFromBuffer( pMaterialName, buf ) ) + { + bFound = true; + if ( pVMT->FindKey("$translucent") || pVMT->FindKey("$alphatest") ) + { + KeyValues *pBaseTexture = pVMT->FindKey("$basetexture"); + if ( pBaseTexture ) + { + const char *pBaseTextureName = pBaseTexture->GetString(); + if ( pBaseTextureName ) + { + int w, h; + bool bClampU = false; + bool bClampV = false; + unsigned char *pImageBits = LoadVTFRGB8888( pBaseTextureName, &w, &h, &bClampU, &bClampV ); + if ( pImageBits ) + { + int index = m_Textures.Insert( pMaterialName ); + m_Textures[index].InitFromRGB8888( w, h, pImageBits ); + *pIndex = index; + if ( pVMT->FindKey("$nocull") ) + { + // UNDONE: Support this? Do we need to emit two triangles? + m_Textures[index].allowBackface = true; + } + m_Textures[index].clampU = bClampU; + m_Textures[index].clampV = bClampV; + delete[] pImageBits; + } + } + } + } + + } + pVMT->deleteThis(); + } + + return bFound; + } + + + // iterate the textures for the model and load each one into the database + // this is used on models marked to cast texture shadows + void LoadAllTexturesForModel( studiohdr_t *pHdr, int *pTextureList ) + { + for ( int i = 0; i < pHdr->numtextures; i++ ) + { + int textureIndex = -1; + // try to add each texture to the transparent shadow manager + char szPath[MAX_PATH]; + + // iterate quietly through all specified directories until a valid material is found + for ( int j = 0; j < pHdr->numcdtextures; j++ ) + { + Q_strncpy( szPath, "materials/", sizeof( szPath ) ); + Q_strncat( szPath, pHdr->pCdtexture( j ), sizeof( szPath ) ); + const char *textureName = pHdr->pTexture( i )->pszName(); + Q_strncat( szPath, textureName, sizeof( szPath ), COPY_ALL_CHARACTERS ); + Q_strncat( szPath, ".vmt", sizeof( szPath ), COPY_ALL_CHARACTERS ); + Q_FixSlashes( szPath, CORRECT_PATH_SEPARATOR ); + if ( FindOrLoadIfValid( szPath, &textureIndex ) ) + break; + } + + pTextureList[i] = textureIndex; + } + } + + int AddMaterialEntry( int shadowTextureIndex, const Vector2D &t0, const Vector2D &t1, const Vector2D &t2 ) + { + int index = m_MaterialEntries.AddToTail(); + m_MaterialEntries[index].textureIndex = shadowTextureIndex; + m_MaterialEntries[index].uv[0] = t0; + m_MaterialEntries[index].uv[1] = t1; + m_MaterialEntries[index].uv[2] = t2; + return index; + } + + // HACKHACK: Compute the average coverage for this triangle by sampling the AABB of its texture space + float ComputeCoverageForTriangle( int shadowTextureIndex, const Vector2D &t0, const Vector2D &t1, const Vector2D &t2 ) + { + float umin = min(t0.x, t1.x); + umin = min(umin, t2.x); + float umax = max(t0.x, t1.x); + umax = max(umax, t2.x); + + float vmin = min(t0.y, t1.y); + vmin = min(vmin, t2.y); + float vmax = max(t0.y, t1.y); + vmax = max(vmax, t2.y); + + // UNDONE: Do something about tiling + umin = clamp(umin, 0, 1); + umax = clamp(umax, 0, 1); + vmin = clamp(vmin, 0, 1); + vmax = clamp(vmax, 0, 1); + Assert(umin>=0.0f && umax <= 1.0f); + Assert(vmin>=0.0f && vmax <= 1.0f); + const alphatexture_t &tex = m_Textures.Element(shadowTextureIndex); + int u0 = umin * (tex.width-1); + int u1 = umax * (tex.width-1); + int v0 = vmin * (tex.height-1); + int v1 = vmax * (tex.height-1); + + int total = 0; + int count = 0; + for ( int v = v0; v <= v1; v++ ) + { + int row = (v * tex.width); + for ( int u = u0; u <= u1; u++ ) + { + total += tex.pAlphaTexels[row + u]; + count++; + } + } + if ( count ) + { + float coverage = float(total) / (count * 255.0f); + return coverage; + } + return 1.0f; + } + + int SampleMaterial( int materialIndex, const Vector &coords, bool bBackface ) + { + const materialentry_t &mat = m_MaterialEntries[materialIndex]; + const alphatexture_t &tex = m_Textures.Element(m_MaterialEntries[materialIndex].textureIndex); + if ( bBackface && !tex.allowBackface ) + return 0; + Vector2D uv = coords.x * mat.uv[0] + coords.y * mat.uv[1] + coords.z * mat.uv[2]; + int u = RoundFloatToInt( uv[0] * tex.width ); + int v = RoundFloatToInt( uv[1] * tex.height ); + + // asume power of 2, clamp or wrap + // UNDONE: Support clamp? This code should work +#if 0 + u = tex.clampU ? clamp(u,0,(tex.width-1)) : (u & (tex.width-1)); + v = tex.clampV ? clamp(v,0,(tex.height-1)) : (v & (tex.height-1)); +#else + // for now always wrap + u &= (tex.width-1); + v &= (tex.height-1); +#endif + + return tex.pAlphaTexels[v * tex.width + u]; + } + + struct alphatexture_t + { + short width; + short height; + bool allowBackface; + bool clampU; + bool clampV; + unsigned char *pAlphaTexels; + + void InitFromRGB8888( int w, int h, unsigned char *pTexels ) + { + width = w; + height = h; + pAlphaTexels = new unsigned char[w*h]; + for ( int i = 0; i < h; i++ ) + { + for ( int j = 0; j < w; j++ ) + { + int index = (i*w) + j; + pAlphaTexels[index] = pTexels[index*4 + 3]; + } + } + } + }; + struct materialentry_t + { + int textureIndex; + Vector2D uv[3]; + }; + // this is the list of textures we've loaded + // only load each one once + CUtlDict< alphatexture_t, unsigned short > m_Textures; + CUtlVector m_MaterialEntries; +}; + +// global to keep the shadow-casting texture list and their alpha bits +CShadowTextureList g_ShadowTextureList; + +float ComputeCoverageFromTexture( float b0, float b1, float b2, int32 hitID ) +{ + const float alphaScale = 1.0f / 255.0f; + // UNDONE: Pass ray down to determine backfacing? + //Vector normal( tri.m_flNx, tri.m_flNy, tri.m_flNz ); + //bool bBackface = DotProduct(delta, tri.N) > 0 ? true : false; + Vector coords(b0,b1,b2); + return alphaScale * g_ShadowTextureList.SampleMaterial( g_RtEnv.GetTriangleMaterial(hitID), coords, false ); +} + +// this is here to strip models/ or .mdl or whatnot +void CleanModelName( const char *pModelName, char *pOutput, int outLen ) +{ + // strip off leading models/ if it exists + const char *pModelDir = "models/"; + int modelLen = Q_strlen(pModelDir); + + if ( !Q_strnicmp(pModelName, pModelDir, modelLen ) ) + { + pModelName += modelLen; + } + Q_strncpy( pOutput, pModelName, outLen ); + + // truncate any .mdl extension + char *dot = strchr(pOutput,'.'); + if ( dot ) + { + *dot = 0; + } + +} + + +void ForceTextureShadowsOnModel( const char *pModelName ) +{ + char buf[1024]; + CleanModelName( pModelName, buf, sizeof(buf) ); + if ( !g_ForcedTextureShadowsModels.Find(buf).IsValid()) + { + g_ForcedTextureShadowsModels.AddString(buf); + } +} + +bool IsModelTextureShadowsForced( const char *pModelName ) +{ + char buf[1024]; + CleanModelName( pModelName, buf, sizeof(buf) ); + return g_ForcedTextureShadowsModels.Find(buf).IsValid(); +} + + +//----------------------------------------------------------------------------- +// Creates a collision model (based on the render geometry!) +//----------------------------------------------------------------------------- +void CVradStaticPropMgr::CreateCollisionModel( char const* pModelName ) +{ + CUtlBuffer buf; + CUtlBuffer bufvtx; + CUtlBuffer bufphy; + + int i = m_StaticPropDict.AddToTail(); + m_StaticPropDict[i].m_pModel = NULL; + m_StaticPropDict[i].m_pStudioHdr = NULL; + + if ( !LoadStudioModel( pModelName, buf ) ) + { + VectorCopy( vec3_origin, m_StaticPropDict[i].m_Mins ); + VectorCopy( vec3_origin, m_StaticPropDict[i].m_Maxs ); + return; + } + + studiohdr_t* pHdr = (studiohdr_t*)buf.Base(); + + VectorCopy( pHdr->hull_min, m_StaticPropDict[i].m_Mins ); + VectorCopy( pHdr->hull_max, m_StaticPropDict[i].m_Maxs ); + + if ( LoadStudioCollisionModel( pModelName, bufphy ) ) + { + phyheader_t header; + bufphy.Get( &header, sizeof(header) ); + + vcollide_t *pCollide = &m_StaticPropDict[i].m_loadedModel; + s_pPhysCollision->VCollideLoad( pCollide, header.solidCount, (const char *)bufphy.PeekGet(), bufphy.TellPut() - bufphy.TellGet() ); + m_StaticPropDict[i].m_pModel = m_StaticPropDict[i].m_loadedModel.solids[0]; + + /* + static int propNum = 0; + char tmp[128]; + sprintf( tmp, "staticprop%03d.txt", propNum ); + DumpCollideToGlView( pCollide, tmp ); + ++propNum; + */ + } + else + { + // mark this as unused + m_StaticPropDict[i].m_loadedModel.solidCount = 0; + + // CPhysCollide* pPhys = CreatePhysCollide( pHdr, pVtxHdr ); + m_StaticPropDict[i].m_pModel = ComputeConvexHull( pHdr ); + } + + // clone it + m_StaticPropDict[i].m_pStudioHdr = (studiohdr_t *)malloc( buf.Size() ); + memcpy( m_StaticPropDict[i].m_pStudioHdr, (studiohdr_t*)buf.Base(), buf.Size() ); + + if ( !LoadVTXFile( pModelName, m_StaticPropDict[i].m_pStudioHdr, m_StaticPropDict[i].m_VtxBuf ) ) + { + // failed, leave state identified as disabled + m_StaticPropDict[i].m_VtxBuf.Purge(); + } + + if ( g_bTextureShadows ) + { + if ( (pHdr->flags & STUDIOHDR_FLAGS_CAST_TEXTURE_SHADOWS) || IsModelTextureShadowsForced(pModelName) ) + { + m_StaticPropDict[i].m_textureShadowIndex.RemoveAll(); + m_StaticPropDict[i].m_triangleMaterialIndex.RemoveAll(); + m_StaticPropDict[i].m_textureShadowIndex.AddMultipleToTail( pHdr->numtextures ); + g_ShadowTextureList.LoadAllTexturesForModel( pHdr, m_StaticPropDict[i].m_textureShadowIndex.Base() ); + } + } +} + + +//----------------------------------------------------------------------------- +// Unserialize static prop model dictionary +//----------------------------------------------------------------------------- +void CVradStaticPropMgr::UnserializeModelDict( CUtlBuffer& buf ) +{ + int count = buf.GetInt(); + while ( --count >= 0 ) + { + StaticPropDictLump_t lump; + buf.Get( &lump, sizeof(StaticPropDictLump_t) ); + + CreateCollisionModel( lump.m_Name ); + } +} + +void CVradStaticPropMgr::UnserializeModels( CUtlBuffer& buf ) +{ + int count = buf.GetInt(); + + m_StaticProps.AddMultipleToTail(count); + for ( int i = 0; i < count; ++i ) + { + StaticPropLump_t lump; + buf.Get( &lump, sizeof(StaticPropLump_t) ); + + VectorCopy( lump.m_Origin, m_StaticProps[i].m_Origin ); + VectorCopy( lump.m_Angles, m_StaticProps[i].m_Angles ); + VectorCopy( lump.m_LightingOrigin, m_StaticProps[i].m_LightingOrigin ); + m_StaticProps[i].m_bLightingOriginValid = ( lump.m_Flags & STATIC_PROP_USE_LIGHTING_ORIGIN ) > 0; + m_StaticProps[i].m_ModelIdx = lump.m_PropType; + m_StaticProps[i].m_Handle = TREEDATA_INVALID_HANDLE; + m_StaticProps[i].m_Flags = lump.m_Flags; + } +} + +//----------------------------------------------------------------------------- +// Unserialize static props +//----------------------------------------------------------------------------- + +void CVradStaticPropMgr::UnserializeStaticProps() +{ + // Unserialize static props, insert them into the appropriate leaves + GameLumpHandle_t handle = g_GameLumps.GetGameLumpHandle( GAMELUMP_STATIC_PROPS ); + int size = g_GameLumps.GameLumpSize( handle ); + if (!size) + return; + + if ( g_GameLumps.GetGameLumpVersion( handle ) != GAMELUMP_STATIC_PROPS_VERSION ) + { + Error( "Cannot load the static props... encountered a stale map version. Re-vbsp the map." ); + } + + if ( g_GameLumps.GetGameLump( handle ) ) + { + CUtlBuffer buf( g_GameLumps.GetGameLump(handle), size, CUtlBuffer::READ_ONLY ); + UnserializeModelDict( buf ); + + // Skip the leaf list data + int count = buf.GetInt(); + buf.SeekGet( CUtlBuffer::SEEK_CURRENT, count * sizeof(StaticPropLeafLump_t) ); + + UnserializeModels( buf ); + } +} + +//----------------------------------------------------------------------------- +// Level init, shutdown +//----------------------------------------------------------------------------- + +void CVradStaticPropMgr::Init() +{ + CreateInterfaceFn physicsFactory = GetPhysicsFactory(); + if ( !physicsFactory ) + Error( "Unable to load vphysics DLL." ); + + s_pPhysCollision = (IPhysicsCollision *)physicsFactory( VPHYSICS_COLLISION_INTERFACE_VERSION, NULL ); + if( !s_pPhysCollision ) + { + Error( "Unable to get '%s' for physics interface.", VPHYSICS_COLLISION_INTERFACE_VERSION ); + return; + } + + // Read in static props that have been compiled into the bsp file + UnserializeStaticProps(); +} + +void CVradStaticPropMgr::Shutdown() +{ + + // Remove all static prop model data + for (int i = m_StaticPropDict.Size(); --i >= 0; ) + { + studiohdr_t *pStudioHdr = m_StaticPropDict[i].m_pStudioHdr; + if ( pStudioHdr ) + { + if ( pStudioHdr->pVertexBase ) + { + free( pStudioHdr->pVertexBase ); + } + free( pStudioHdr ); + } + } + + m_StaticProps.Purge(); + m_StaticPropDict.Purge(); +} + +void ComputeLightmapColor( dface_t* pFace, Vector &color ) +{ + texinfo_t* pTex = &texinfo[pFace->texinfo]; + if ( pTex->flags & SURF_SKY ) + { + // sky ambient already accounted for in direct component + return; + } +} + +bool PositionInSolid( Vector &position ) +{ + int ndxLeaf = PointLeafnum( position ); + if ( dleafs[ndxLeaf].contents & CONTENTS_SOLID ) + { + // position embedded in solid + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Trace from a vertex to each direct light source, accumulating its contribution. +//----------------------------------------------------------------------------- +void ComputeDirectLightingAtPoint( Vector &position, Vector &normal, Vector &outColor, int iThread, + int static_prop_id_to_skip=-1, int nLFlags = 0) +{ + SSE_sampleLightOutput_t sampleOutput; + + outColor.Init(); + + // Iterate over all direct lights and accumulate their contribution + int cluster = ClusterFromPoint( position ); + for ( directlight_t *dl = activelights; dl != NULL; dl = dl->next ) + { + if ( dl->light.style ) + { + // skip lights with style + continue; + } + + // is this lights cluster visible? + if ( !PVSCheck( dl->pvs, cluster ) ) + continue; + + // push the vertex towards the light to avoid surface acne + Vector adjusted_pos = position; + float flEpsilon = 0.0; + + if (dl->light.type != emit_skyambient) + { + // push towards the light + Vector fudge; + if ( dl->light.type == emit_skylight ) + fudge = -( dl->light.normal); + else + { + fudge = dl->light.origin-position; + VectorNormalize( fudge ); + } + fudge *= 4.0; + adjusted_pos += fudge; + } + else + { + // push out along normal + adjusted_pos += 4.0 * normal; +// flEpsilon = 1.0; + } + + FourVectors adjusted_pos4; + FourVectors normal4; + adjusted_pos4.DuplicateVector( adjusted_pos ); + normal4.DuplicateVector( normal ); + + GatherSampleLightSSE( sampleOutput, dl, -1, adjusted_pos4, &normal4, 1, iThread, nLFlags | GATHERLFLAGS_FORCE_FAST, + static_prop_id_to_skip, flEpsilon ); + + VectorMA( outColor, sampleOutput.m_flFalloff.m128_f32[0] * sampleOutput.m_flDot[0].m128_f32[0], dl->light.intensity, outColor ); + } +} + +//----------------------------------------------------------------------------- +// Takes the results from a ComputeLighting call and applies it to the static prop in question. +//----------------------------------------------------------------------------- +void CVradStaticPropMgr::ApplyLightingToStaticProp( CStaticProp &prop, const CComputeStaticPropLightingResults *pResults ) +{ + if ( pResults->m_ColorVertsArrays.Count() == 0 ) + return; + + StaticPropDict_t &dict = m_StaticPropDict[prop.m_ModelIdx]; + studiohdr_t *pStudioHdr = dict.m_pStudioHdr; + OptimizedModel::FileHeader_t *pVtxHdr = (OptimizedModel::FileHeader_t *)dict.m_VtxBuf.Base(); + Assert( pStudioHdr && pVtxHdr ); + + int iCurColorVertsArray = 0; + for ( int bodyID = 0; bodyID < pStudioHdr->numbodyparts; ++bodyID ) + { + OptimizedModel::BodyPartHeader_t* pVtxBodyPart = pVtxHdr->pBodyPart( bodyID ); + mstudiobodyparts_t *pBodyPart = pStudioHdr->pBodypart( bodyID ); + + for ( int modelID = 0; modelID < pBodyPart->nummodels; ++modelID ) + { + OptimizedModel::ModelHeader_t* pVtxModel = pVtxBodyPart->pModel( modelID ); + mstudiomodel_t *pStudioModel = pBodyPart->pModel( modelID ); + + const CUtlVector &colorVerts = *pResults->m_ColorVertsArrays[iCurColorVertsArray++]; + + for ( int nLod = 0; nLod < pVtxHdr->numLODs; nLod++ ) + { + OptimizedModel::ModelLODHeader_t *pVtxLOD = pVtxModel->pLOD( nLod ); + + for ( int nMesh = 0; nMesh < pStudioModel->nummeshes; ++nMesh ) + { + mstudiomesh_t* pMesh = pStudioModel->pMesh( nMesh ); + OptimizedModel::MeshHeader_t* pVtxMesh = pVtxLOD->pMesh( nMesh ); + + for ( int nGroup = 0; nGroup < pVtxMesh->numStripGroups; ++nGroup ) + { + OptimizedModel::StripGroupHeader_t* pStripGroup = pVtxMesh->pStripGroup( nGroup ); + int nMeshIdx = prop.m_MeshData.AddToTail(); + prop.m_MeshData[nMeshIdx].m_Verts.AddMultipleToTail( pStripGroup->numVerts ); + prop.m_MeshData[nMeshIdx].m_nLod = nLod; + + for ( int nVertex = 0; nVertex < pStripGroup->numVerts; ++nVertex ) + { + int nIndex = pMesh->vertexoffset + pStripGroup->pVertex( nVertex )->origMeshVertID; + + Assert( nIndex < pStudioModel->numvertices ); + prop.m_MeshData[nMeshIdx].m_Verts[nVertex] = colorVerts[nIndex].m_Color; + } + } + } + } + } + } +} + +//----------------------------------------------------------------------------- +// Trace rays from each unique vertex, accumulating direct and indirect +// sources at each ray termination. Use the winding data to distribute the unique vertexes +// into the rendering layout. +//----------------------------------------------------------------------------- +void CVradStaticPropMgr::ComputeLighting( CStaticProp &prop, int iThread, int prop_index, CComputeStaticPropLightingResults *pResults ) +{ + CUtlVector badVerts; + + StaticPropDict_t &dict = m_StaticPropDict[prop.m_ModelIdx]; + studiohdr_t *pStudioHdr = dict.m_pStudioHdr; + OptimizedModel::FileHeader_t *pVtxHdr = (OptimizedModel::FileHeader_t *)dict.m_VtxBuf.Base(); + if ( !pStudioHdr || !pVtxHdr ) + { + // must have model and its verts for lighting computation + // game will fallback to fullbright + return; + } + + if (prop.m_Flags & STATIC_PROP_NO_PER_VERTEX_LIGHTING ) + return; + + VMPI_SetCurrentStage( "ComputeLighting" ); + + for ( int bodyID = 0; bodyID < pStudioHdr->numbodyparts; ++bodyID ) + { + mstudiobodyparts_t *pBodyPart = pStudioHdr->pBodypart( bodyID ); + + for ( int modelID = 0; modelID < pBodyPart->nummodels; ++modelID ) + { + mstudiomodel_t *pStudioModel = pBodyPart->pModel( modelID ); + + // light all unique vertexes + CUtlVector *pColorVertsArray = new CUtlVector; + pResults->m_ColorVertsArrays.AddToTail( pColorVertsArray ); + + CUtlVector &colorVerts = *pColorVertsArray; + colorVerts.EnsureCount( pStudioModel->numvertices ); + memset( colorVerts.Base(), 0, colorVerts.Count() * sizeof(colorVertex_t) ); + + int numVertexes = 0; + for ( int meshID = 0; meshID < pStudioModel->nummeshes; ++meshID ) + { + mstudiomesh_t *pStudioMesh = pStudioModel->pMesh( meshID ); + const mstudio_meshvertexdata_t *vertData = pStudioMesh->GetVertexData((void *)pStudioHdr); + Assert( vertData ); // This can only return NULL on X360 for now + for ( int vertexID = 0; vertexID < pStudioMesh->numvertices; ++vertexID ) + { + Vector sampleNormal; + Vector samplePosition; + // transform position and normal into world coordinate system + matrix3x4_t matrix; + AngleMatrix( prop.m_Angles, prop.m_Origin, matrix ); + VectorTransform( *vertData->Position( vertexID ), matrix, samplePosition ); + AngleMatrix( prop.m_Angles, matrix ); + VectorTransform( *vertData->Normal( vertexID ), matrix, sampleNormal ); + + if ( PositionInSolid( samplePosition ) ) + { + // vertex is in solid, add to the bad list, and recover later + badVertex_t badVertex; + badVertex.m_ColorVertex = numVertexes; + badVertex.m_Position = samplePosition; + badVertex.m_Normal = sampleNormal; + badVerts.AddToTail( badVertex ); + } + else + { + Vector direct_pos=samplePosition; + int skip_prop = -1; + if ( g_bDisablePropSelfShadowing || ( prop.m_Flags & STATIC_PROP_NO_SELF_SHADOWING ) ) + { + skip_prop = prop_index; + } + + int nFlags = ( prop.m_Flags & STATIC_PROP_IGNORE_NORMALS ) ? GATHERLFLAGS_IGNORE_NORMALS : 0; + + Vector directColor(0,0,0); + ComputeDirectLightingAtPoint( direct_pos, + sampleNormal, directColor, iThread, + skip_prop, nFlags ); + Vector indirectColor(0,0,0); + + if (g_bShowStaticPropNormals) + { + directColor= sampleNormal; + directColor += Vector(1.0,1.0,1.0); + directColor *= 50.0; + } + else + { + if (numbounce >= 1) + ComputeIndirectLightingAtPoint( + samplePosition, sampleNormal, + indirectColor, iThread, true, + ( prop.m_Flags & STATIC_PROP_IGNORE_NORMALS) != 0 ); + } + + colorVerts[numVertexes].m_bValid = true; + colorVerts[numVertexes].m_Position = samplePosition; + VectorAdd( directColor, indirectColor, colorVerts[numVertexes].m_Color ); + } + + numVertexes++; + } + } + + // color in the bad vertexes + // when entire model has no lighting origin and no valid neighbors + // must punt, leave black coloring + if ( badVerts.Count() && ( prop.m_bLightingOriginValid || badVerts.Count() != numVertexes ) ) + { + for ( int nBadVertex = 0; nBadVertex < badVerts.Count(); nBadVertex++ ) + { + Vector bestPosition; + if ( prop.m_bLightingOriginValid ) + { + // use the specified lighting origin + VectorCopy( prop.m_LightingOrigin, bestPosition ); + } + else + { + // find the closest valid neighbor + int best = 0; + float closest = FLT_MAX; + for ( int nColorVertex = 0; nColorVertex < numVertexes; nColorVertex++ ) + { + if ( !colorVerts[nColorVertex].m_bValid ) + { + // skip invalid neighbors + continue; + } + Vector delta; + VectorSubtract( colorVerts[nColorVertex].m_Position, badVerts[nBadVertex].m_Position, delta ); + float distance = VectorLength( delta ); + if ( distance < closest ) + { + closest = distance; + best = nColorVertex; + } + } + + // use the best neighbor as the direction to crawl + VectorCopy( colorVerts[best].m_Position, bestPosition ); + } + + // crawl toward best position + // sudivide to determine a closer valid point to the bad vertex, and re-light + Vector midPosition; + int numIterations = 20; + while ( --numIterations > 0 ) + { + VectorAdd( bestPosition, badVerts[nBadVertex].m_Position, midPosition ); + VectorScale( midPosition, 0.5f, midPosition ); + if ( PositionInSolid( midPosition ) ) + break; + bestPosition = midPosition; + } + + // re-light from better position + Vector directColor; + ComputeDirectLightingAtPoint( bestPosition, badVerts[nBadVertex].m_Normal, directColor, iThread ); + + Vector indirectColor; + ComputeIndirectLightingAtPoint( bestPosition, badVerts[nBadVertex].m_Normal, + indirectColor, iThread, true ); + + // save results, not changing valid status + // to ensure this offset position is not considered as a viable candidate + colorVerts[badVerts[nBadVertex].m_ColorVertex].m_Position = bestPosition; + VectorAdd( directColor, indirectColor, colorVerts[badVerts[nBadVertex].m_ColorVertex].m_Color ); + } + } + + // discard bad verts + badVerts.Purge(); + } + } +} + +//----------------------------------------------------------------------------- +// Write the lighitng to bsp pak lump +//----------------------------------------------------------------------------- +void CVradStaticPropMgr::SerializeLighting() +{ + char filename[MAX_PATH]; + CUtlBuffer utlBuf; + + // illuminate them all + int count = m_StaticProps.Count(); + if ( !count ) + { + // nothing to do + return; + } + + char mapName[MAX_PATH]; + Q_FileBase( source, mapName, sizeof( mapName ) ); + + int size; + for (int i = 0; i < count; ++i) + { + // no need to write this file if we didn't compute the data + // props marked this way will not load the info anyway + if ( m_StaticProps[i].m_Flags & STATIC_PROP_NO_PER_VERTEX_LIGHTING ) + continue; + + if (g_bHDR) + { + sprintf( filename, "sp_hdr_%d.vhv", i ); + } + else + { + sprintf( filename, "sp_%d.vhv", i ); + } + + int totalVertexes = 0; + for ( int j=0; jm_nVersion = VHV_VERSION; + pVhvHdr->m_nChecksum = m_StaticPropDict[m_StaticProps[i].m_ModelIdx].m_pStudioHdr->checksum; + pVhvHdr->m_nVertexFlags = VERTEX_COLOR; + pVhvHdr->m_nVertexSize = 4; + pVhvHdr->m_nVertexes = totalVertexes; + pVhvHdr->m_nMeshes = m_StaticProps[i].m_MeshData.Count(); + + for (int n=0; nm_nMeshes; n++) + { + // construct mesh dictionary + HardwareVerts::MeshHeader_t *pMesh = pVhvHdr->pMesh( n ); + pMesh->m_nLod = m_StaticProps[i].m_MeshData[n].m_nLod; + pMesh->m_nVertexes = m_StaticProps[i].m_MeshData[n].m_Verts.Count(); + pMesh->m_nOffset = (unsigned int)pVertexData - (unsigned int)pVhvHdr; + + // construct vertexes + for (int k=0; km_nVertexes; k++) + { + Vector &vector = m_StaticProps[i].m_MeshData[n].m_Verts[k]; + + ColorRGBExp32 rgbColor; + VectorToColorRGBExp32( vector, rgbColor ); + unsigned char dstColor[4]; + ConvertRGBExp32ToRGBA8888( &rgbColor, dstColor ); + + // b,g,r,a order + pVertexData[0] = dstColor[2]; + pVertexData[1] = dstColor[1]; + pVertexData[2] = dstColor[0]; + pVertexData[3] = dstColor[3]; + pVertexData += 4; + } + } + + // align to end of file + pVertexData = (unsigned char *)((unsigned int)pVertexData - (unsigned int)pVhvHdr); + pVertexData = (unsigned char*)pVhvHdr + ALIGN_TO_POW2( (unsigned int)pVertexData, 512 ); + + AddBufferToPak( GetPakFile(), filename, (void*)pVhvHdr, pVertexData - (unsigned char*)pVhvHdr, false ); + } +} + +void CVradStaticPropMgr::VMPI_ProcessStaticProp_Static( int iThread, uint64 iStaticProp, MessageBuffer *pBuf ) +{ + g_StaticPropMgr.VMPI_ProcessStaticProp( iThread, iStaticProp, pBuf ); +} + +void CVradStaticPropMgr::VMPI_ReceiveStaticPropResults_Static( uint64 iStaticProp, MessageBuffer *pBuf, int iWorker ) +{ + g_StaticPropMgr.VMPI_ReceiveStaticPropResults( iStaticProp, pBuf, iWorker ); +} + +//----------------------------------------------------------------------------- +// Called on workers to do the computation for a static prop and send +// it to the master. +//----------------------------------------------------------------------------- +void CVradStaticPropMgr::VMPI_ProcessStaticProp( int iThread, int iStaticProp, MessageBuffer *pBuf ) +{ + // Compute the lighting. + CComputeStaticPropLightingResults results; + ComputeLighting( m_StaticProps[iStaticProp], iThread, iStaticProp, &results ); + + VMPI_SetCurrentStage( "EncodeLightingResults" ); + + // Encode the results. + int nLists = results.m_ColorVertsArrays.Count(); + pBuf->write( &nLists, sizeof( nLists ) ); + + for ( int i=0; i < nLists; i++ ) + { + CUtlVector &curList = *results.m_ColorVertsArrays[i]; + int count = curList.Count(); + pBuf->write( &count, sizeof( count ) ); + pBuf->write( curList.Base(), curList.Count() * sizeof( colorVertex_t ) ); + } +} + +//----------------------------------------------------------------------------- +// Called on the master when a worker finishes processing a static prop. +//----------------------------------------------------------------------------- +void CVradStaticPropMgr::VMPI_ReceiveStaticPropResults( int iStaticProp, MessageBuffer *pBuf, int iWorker ) +{ + // Read in the results. + CComputeStaticPropLightingResults results; + + int nLists; + pBuf->read( &nLists, sizeof( nLists ) ); + + for ( int i=0; i < nLists; i++ ) + { + CUtlVector *pList = new CUtlVector; + results.m_ColorVertsArrays.AddToTail( pList ); + + int count; + pBuf->read( &count, sizeof( count ) ); + pList->SetSize( count ); + pBuf->read( pList->Base(), count * sizeof( colorVertex_t ) ); + } + + // Apply the results. + ApplyLightingToStaticProp( m_StaticProps[iStaticProp], &results ); +} + + +void CVradStaticPropMgr::ComputeLightingForProp( int iThread, int iStaticProp ) +{ + // Compute the lighting. + CComputeStaticPropLightingResults results; + ComputeLighting( m_StaticProps[iStaticProp], iThread, iStaticProp, &results ); + ApplyLightingToStaticProp( m_StaticProps[iStaticProp], &results ); +} + +void CVradStaticPropMgr::ThreadComputeStaticPropLighting( int iThread, void *pUserData ) +{ + while (1) + { + int j = GetThreadWork (); + if (j == -1) + break; + CComputeStaticPropLightingResults results; + g_StaticPropMgr.ComputeLightingForProp( iThread, j ); + } +} + +//----------------------------------------------------------------------------- +// Computes lighting for the static props. +// Must be after all other surface lighting has been computed for the indirect sampling. +//----------------------------------------------------------------------------- +void CVradStaticPropMgr::ComputeLighting( int iThread ) +{ + // illuminate them all + int count = m_StaticProps.Count(); + if ( !count ) + { + // nothing to do + return; + } + + StartPacifier( "Computing static prop lighting : " ); + + // ensure any traces against us are ignored because we have no inherit lighting contribution + m_bIgnoreStaticPropTrace = true; + + if ( g_bUseMPI ) + { + // Distribute the work among the workers. + VMPI_SetCurrentStage( "CVradStaticPropMgr::ComputeLighting" ); + + DistributeWork( + count, + VMPI_DISTRIBUTEWORK_PACKETID, + &CVradStaticPropMgr::VMPI_ProcessStaticProp_Static, + &CVradStaticPropMgr::VMPI_ReceiveStaticPropResults_Static ); + } + else + { + RunThreadsOn(count, true, ThreadComputeStaticPropLighting); + } + + // restore default + m_bIgnoreStaticPropTrace = false; + + // save data to bsp + SerializeLighting(); + + EndPacifier( true ); +} + +//----------------------------------------------------------------------------- +// Adds all static prop polys to the ray trace store. +//----------------------------------------------------------------------------- +void CVradStaticPropMgr::AddPolysForRayTrace( void ) +{ + int count = m_StaticProps.Count(); + if ( !count ) + { + // nothing to do + return; + } + + // Triangle coverage of 1 (full coverage) + Vector fullCoverage; + fullCoverage.x = 1.0f; + + for ( int nProp = 0; nProp < count; ++nProp ) + { + CStaticProp &prop = m_StaticProps[nProp]; + StaticPropDict_t &dict = m_StaticPropDict[prop.m_ModelIdx]; + + if ( prop.m_Flags & STATIC_PROP_NO_SHADOW ) + continue; + + // If not using static prop polys, use AABB + if ( !g_bStaticPropPolys ) + { + if ( dict.m_pModel ) + { + VMatrix xform; + xform.SetupMatrixOrgAngles ( prop.m_Origin, prop.m_Angles ); + ICollisionQuery *queryModel = s_pPhysCollision->CreateQueryModel( dict.m_pModel ); + for ( int nConvex = 0; nConvex < queryModel->ConvexCount(); ++nConvex ) + { + for ( int nTri = 0; nTri < queryModel->TriangleCount( nConvex ); ++nTri ) + { + Vector verts[3]; + queryModel->GetTriangleVerts( nConvex, nTri, verts ); + for ( int nVert = 0; nVert < 3; ++nVert ) + verts[nVert] = xform.VMul4x3(verts[nVert]); + g_RtEnv.AddTriangle ( TRACE_ID_STATICPROP | nProp, verts[0], verts[1], verts[2], fullCoverage ); + } + } + s_pPhysCollision->DestroyQueryModel( queryModel ); + } + else + { + VectorAdd ( dict.m_Mins, prop.m_Origin, prop.m_mins ); + VectorAdd ( dict.m_Maxs, prop.m_Origin, prop.m_maxs ); + g_RtEnv.AddAxisAlignedRectangularSolid ( TRACE_ID_STATICPROP | nProp, prop.m_mins, prop.m_maxs, fullCoverage ); + } + + continue; + } + + studiohdr_t *pStudioHdr = dict.m_pStudioHdr; + OptimizedModel::FileHeader_t *pVtxHdr = (OptimizedModel::FileHeader_t *)dict.m_VtxBuf.Base(); + if ( !pStudioHdr || !pVtxHdr ) + { + // must have model and its verts for decoding triangles + return; + } + // only init the triangle table the first time + bool bInitTriangles = dict.m_triangleMaterialIndex.Count() ? false : true; + int triangleIndex = 0; + + // meshes are deeply hierarchial, divided between three stores, follow the white rabbit + // body parts -> models -> lod meshes -> strip groups -> strips + // the vertices and indices are pooled, the trick is knowing the offset to determine your indexed base + for ( int bodyID = 0; bodyID < pStudioHdr->numbodyparts; ++bodyID ) + { + OptimizedModel::BodyPartHeader_t* pVtxBodyPart = pVtxHdr->pBodyPart( bodyID ); + mstudiobodyparts_t *pBodyPart = pStudioHdr->pBodypart( bodyID ); + + for ( int modelID = 0; modelID < pBodyPart->nummodels; ++modelID ) + { + OptimizedModel::ModelHeader_t* pVtxModel = pVtxBodyPart->pModel( modelID ); + mstudiomodel_t *pStudioModel = pBodyPart->pModel( modelID ); + + // assuming lod 0, could iterate if required + int nLod = 0; + OptimizedModel::ModelLODHeader_t *pVtxLOD = pVtxModel->pLOD( nLod ); + + for ( int nMesh = 0; nMesh < pStudioModel->nummeshes; ++nMesh ) + { + // check if this mesh's material is in the no shadow material name list + mstudiomesh_t* pMesh = pStudioModel->pMesh( nMesh ); + mstudiotexture_t *pTxtr=pStudioHdr->pTexture(pMesh->material); + //printf("mat idx=%d mat name=%s\n",pMesh->material,pTxtr->pszName()); + bool bSkipThisMesh = false; + for(int check=0; checkpszName(), + g_NonShadowCastingMaterialStrings[check] ) ) + { + //printf("skip mat name=%s\n",pTxtr->pszName()); + bSkipThisMesh = true; + break; + } + } + if ( bSkipThisMesh) + continue; + + int shadowTextureIndex = -1; + if ( dict.m_textureShadowIndex.Count() ) + { + shadowTextureIndex = dict.m_textureShadowIndex[pMesh->material]; + } + + + OptimizedModel::MeshHeader_t* pVtxMesh = pVtxLOD->pMesh( nMesh ); + const mstudio_meshvertexdata_t *vertData = pMesh->GetVertexData( (void *)pStudioHdr ); + Assert( vertData ); // This can only return NULL on X360 for now + + for ( int nGroup = 0; nGroup < pVtxMesh->numStripGroups; ++nGroup ) + { + OptimizedModel::StripGroupHeader_t* pStripGroup = pVtxMesh->pStripGroup( nGroup ); + + int nStrip; + for ( nStrip = 0; nStrip < pStripGroup->numStrips; nStrip++ ) + { + OptimizedModel::StripHeader_t *pStrip = pStripGroup->pStrip( nStrip ); + + if ( pStrip->flags & OptimizedModel::STRIP_IS_TRILIST ) + { + for ( int i = 0; i < pStrip->numIndices; i += 3 ) + { + int idx = pStrip->indexOffset + i; + + unsigned short i1 = *pStripGroup->pIndex( idx ); + unsigned short i2 = *pStripGroup->pIndex( idx + 1 ); + unsigned short i3 = *pStripGroup->pIndex( idx + 2 ); + + int vertex1 = pStripGroup->pVertex( i1 )->origMeshVertID; + int vertex2 = pStripGroup->pVertex( i2 )->origMeshVertID; + int vertex3 = pStripGroup->pVertex( i3 )->origMeshVertID; + + // transform position into world coordinate system + matrix3x4_t matrix; + AngleMatrix( prop.m_Angles, prop.m_Origin, matrix ); + + Vector position1; + Vector position2; + Vector position3; + VectorTransform( *vertData->Position( vertex1 ), matrix, position1 ); + VectorTransform( *vertData->Position( vertex2 ), matrix, position2 ); + VectorTransform( *vertData->Position( vertex3 ), matrix, position3 ); + unsigned short flags = 0; + int materialIndex = -1; + Vector color = vec3_origin; + if ( shadowTextureIndex >= 0 ) + { + if ( bInitTriangles ) + { + // add texture space and texture index to material database + // now + float coverage = g_ShadowTextureList.ComputeCoverageForTriangle(shadowTextureIndex, *vertData->Texcoord(vertex1), *vertData->Texcoord(vertex2), *vertData->Texcoord(vertex3) ); + if ( coverage < 1.0f ) + { + materialIndex = g_ShadowTextureList.AddMaterialEntry( shadowTextureIndex, *vertData->Texcoord(vertex1), *vertData->Texcoord(vertex2), *vertData->Texcoord(vertex3) ); + color.x = coverage; + } + else + { + materialIndex = -1; + } + dict.m_triangleMaterialIndex.AddToTail(materialIndex); + } + else + { + materialIndex = dict.m_triangleMaterialIndex[triangleIndex]; + triangleIndex++; + } + if ( materialIndex >= 0 ) + { + flags = FCACHETRI_TRANSPARENT; + } + } +// printf( "\ngl 3\n" ); +// printf( "gl %6.3f %6.3f %6.3f 1 0 0\n", XYZ(position1)); +// printf( "gl %6.3f %6.3f %6.3f 0 1 0\n", XYZ(position2)); +// printf( "gl %6.3f %6.3f %6.3f 0 0 1\n", XYZ(position3)); + g_RtEnv.AddTriangle( TRACE_ID_STATICPROP | nProp, + position1, position2, position3, + color, flags, materialIndex); + } + } + else + { + // all tris expected to be discrete tri lists + // must fixme if stripping ever occurs + printf( "unexpected strips found\n" ); + Assert( 0 ); + return; + } + } + } + } + } + } + } +} + +struct tl_tri_t +{ + Vector p0; + Vector p1; + Vector p2; + Vector n0; + Vector n1; + Vector n2; + + bool operator == (const tl_tri_t &t) const + { + return ( p0 == t.p0 && + p1 == t.p1 && + p2 == t.p2 && + n0 == t.n0 && + n1 == t.n1 && + n2 == t.n2 ); + } +}; + +struct tl_vert_t +{ + Vector m_position; + CUtlLinkedList< tl_tri_t, int > m_triList; +}; + +void AddTriVertsToList( CUtlVector< tl_vert_t > &triListVerts, int vertIndex, Vector vertPosition, Vector p0, Vector p1, Vector p2, Vector n0, Vector n1, Vector n2 ) +{ + tl_tri_t tlTri; + + tlTri.p0 = p0; + tlTri.p1 = p1; + tlTri.p2 = p2; + tlTri.n0 = n0; + tlTri.n1 = n1; + tlTri.n2 = n2; + + triListVerts.EnsureCapacity( vertIndex+1 ); + + triListVerts[vertIndex].m_position = vertPosition; + + int index = triListVerts[vertIndex].m_triList.Find( tlTri ); + if ( !triListVerts[vertIndex].m_triList.IsValidIndex( index ) ) + { + // not in list, add to list of triangles + triListVerts[vertIndex].m_triList.AddToTail( tlTri ); + } +} + +//----------------------------------------------------------------------------- +// Builds a list of tris for every vertex +//----------------------------------------------------------------------------- +void CVradStaticPropMgr::BuildTriList( CStaticProp &prop ) +{ + // the generated list will consist of a list of verts + // each vert will have a linked list of triangles that it belongs to + CUtlVector< tl_vert_t > triListVerts; + + StaticPropDict_t &dict = m_StaticPropDict[prop.m_ModelIdx]; + studiohdr_t *pStudioHdr = dict.m_pStudioHdr; + OptimizedModel::FileHeader_t *pVtxHdr = (OptimizedModel::FileHeader_t *)dict.m_VtxBuf.Base(); + if ( !pStudioHdr || !pVtxHdr ) + { + // must have model and its verts for decoding triangles + return; + } + + // meshes are deeply hierarchial, divided between three stores, follow the white rabbit + // body parts -> models -> lod meshes -> strip groups -> strips + // the vertices and indices are pooled, the trick is knowing the offset to determine your indexed base + for ( int bodyID = 0; bodyID < pStudioHdr->numbodyparts; ++bodyID ) + { + OptimizedModel::BodyPartHeader_t* pVtxBodyPart = pVtxHdr->pBodyPart( bodyID ); + mstudiobodyparts_t *pBodyPart = pStudioHdr->pBodypart( bodyID ); + + for ( int modelID = 0; modelID < pBodyPart->nummodels; ++modelID ) + { + OptimizedModel::ModelHeader_t* pVtxModel = pVtxBodyPart->pModel( modelID ); + mstudiomodel_t *pStudioModel = pBodyPart->pModel( modelID ); + + // get the specified lod, assuming lod 0 + int nLod = 0; + OptimizedModel::ModelLODHeader_t *pVtxLOD = pVtxModel->pLOD( nLod ); + + // must reset because each model has their own vertexes [0..n] + // in order for this to be monolithic for the entire prop the list must be segmented + triListVerts.Purge(); + + for ( int nMesh = 0; nMesh < pStudioModel->nummeshes; ++nMesh ) + { + mstudiomesh_t* pMesh = pStudioModel->pMesh( nMesh ); + OptimizedModel::MeshHeader_t* pVtxMesh = pVtxLOD->pMesh( nMesh ); + const mstudio_meshvertexdata_t *vertData = pMesh->GetVertexData( (void *)pStudioHdr ); + Assert( vertData ); // This can only return NULL on X360 for now + + for ( int nGroup = 0; nGroup < pVtxMesh->numStripGroups; ++nGroup ) + { + OptimizedModel::StripGroupHeader_t* pStripGroup = pVtxMesh->pStripGroup( nGroup ); + + int nStrip; + for ( nStrip = 0; nStrip < pStripGroup->numStrips; nStrip++ ) + { + OptimizedModel::StripHeader_t *pStrip = pStripGroup->pStrip( nStrip ); + + if ( pStrip->flags & OptimizedModel::STRIP_IS_TRILIST ) + { + for ( int i = 0; i < pStrip->numIndices; i += 3 ) + { + int idx = pStrip->indexOffset + i; + + unsigned short i1 = *pStripGroup->pIndex( idx ); + unsigned short i2 = *pStripGroup->pIndex( idx + 1 ); + unsigned short i3 = *pStripGroup->pIndex( idx + 2 ); + + int vertex1 = pStripGroup->pVertex( i1 )->origMeshVertID; + int vertex2 = pStripGroup->pVertex( i2 )->origMeshVertID; + int vertex3 = pStripGroup->pVertex( i3 )->origMeshVertID; + + // transform position into world coordinate system + matrix3x4_t matrix; + AngleMatrix( prop.m_Angles, prop.m_Origin, matrix ); + + Vector position1; + Vector position2; + Vector position3; + VectorTransform( *vertData->Position( vertex1 ), matrix, position1 ); + VectorTransform( *vertData->Position( vertex2 ), matrix, position2 ); + VectorTransform( *vertData->Position( vertex3 ), matrix, position3 ); + + Vector normal1; + Vector normal2; + Vector normal3; + VectorTransform( *vertData->Normal( vertex1 ), matrix, normal1 ); + VectorTransform( *vertData->Normal( vertex2 ), matrix, normal2 ); + VectorTransform( *vertData->Normal( vertex3 ), matrix, normal3 ); + + AddTriVertsToList( triListVerts, pMesh->vertexoffset + vertex1, position1, position1, position2, position3, normal1, normal2, normal3 ); + AddTriVertsToList( triListVerts, pMesh->vertexoffset + vertex2, position2, position1, position2, position3, normal1, normal2, normal3 ); + AddTriVertsToList( triListVerts, pMesh->vertexoffset + vertex3, position3, position1, position2, position3, normal1, normal2, normal3 ); + } + } + else + { + // all tris expected to be discrete tri lists + // must fixme if stripping ever occurs + printf( "unexpected strips found\n" ); + Assert( 0 ); + return; + } + } + } + } + } + } +} + +const vertexFileHeader_t * mstudiomodel_t::CacheVertexData( void *pModelData ) +{ + studiohdr_t *pActiveStudioHdr = static_cast(pModelData); + Assert( pActiveStudioHdr ); + + if ( pActiveStudioHdr->pVertexBase ) + { + return (vertexFileHeader_t *)pActiveStudioHdr->pVertexBase; + } + + // mandatory callback to make requested data resident + // load and persist the vertex file + char fileName[MAX_PATH]; + strcpy( fileName, "models/" ); + strcat( fileName, pActiveStudioHdr->pszName() ); + Q_StripExtension( fileName, fileName, sizeof( fileName ) ); + strcat( fileName, ".vvd" ); + + // load the model + FileHandle_t fileHandle = g_pFileSystem->Open( fileName, "rb" ); + if ( !fileHandle ) + { + Error( "Unable to load vertex data \"%s\"\n", fileName ); + } + + // Get the file size + int vvdSize = g_pFileSystem->Size( fileHandle ); + if ( vvdSize == 0 ) + { + g_pFileSystem->Close( fileHandle ); + Error( "Bad size for vertex data \"%s\"\n", fileName ); + } + + vertexFileHeader_t *pVvdHdr = (vertexFileHeader_t *)malloc( vvdSize ); + g_pFileSystem->Read( pVvdHdr, vvdSize, fileHandle ); + g_pFileSystem->Close( fileHandle ); + + // check header + if ( pVvdHdr->id != MODEL_VERTEX_FILE_ID ) + { + Error("Error Vertex File %s id %d should be %d\n", fileName, pVvdHdr->id, MODEL_VERTEX_FILE_ID); + } + if ( pVvdHdr->version != MODEL_VERTEX_FILE_VERSION ) + { + Error("Error Vertex File %s version %d should be %d\n", fileName, pVvdHdr->version, MODEL_VERTEX_FILE_VERSION); + } + if ( pVvdHdr->checksum != pActiveStudioHdr->checksum ) + { + Error("Error Vertex File %s checksum %d should be %d\n", fileName, pVvdHdr->checksum, pActiveStudioHdr->checksum); + } + + // need to perform mesh relocation fixups + // allocate a new copy + vertexFileHeader_t *pNewVvdHdr = (vertexFileHeader_t *)malloc( vvdSize ); + if ( !pNewVvdHdr ) + { + Error( "Error allocating %d bytes for Vertex File '%s'\n", vvdSize, fileName ); + } + + // load vertexes and run fixups + Studio_LoadVertexes( pVvdHdr, pNewVvdHdr, 0, true ); + + // discard original + free( pVvdHdr ); + pVvdHdr = pNewVvdHdr; + + pActiveStudioHdr->pVertexBase = (void*)pVvdHdr; + return pVvdHdr; +} diff --git a/mp/src/utils/vrad_launcher/stdafx.cpp b/mp/src/utils/vrad_launcher/stdafx.cpp new file mode 100644 index 00000000..ecf65582 --- /dev/null +++ b/mp/src/utils/vrad_launcher/stdafx.cpp @@ -0,0 +1,15 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// stdafx.cpp : source file that includes just the standard includes +// vrad_launcher.pch will be the pre-compiled header +// stdafx.obj will contain the pre-compiled type information + +#include "stdafx.h" + +// TODO: reference any additional headers you need in STDAFX.H +// and not in this file diff --git a/mp/src/utils/vrad_launcher/stdafx.h b/mp/src/utils/vrad_launcher/stdafx.h new file mode 100644 index 00000000..1c89a814 --- /dev/null +++ b/mp/src/utils/vrad_launcher/stdafx.h @@ -0,0 +1,32 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// stdafx.h : include file for standard system include files, +// or project specific include files that are used frequently, but +// are changed infrequently +// + +#if !defined(AFX_STDAFX_H__4A047C84_94D7_4563_A08C_35E52A52AECC__INCLUDED_) +#define AFX_STDAFX_H__4A047C84_94D7_4563_A08C_35E52A52AECC__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers + +#include +#include +#include "interface.h" +#include "ivraddll.h" + +// TODO: reference additional headers your program requires here + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_STDAFX_H__4A047C84_94D7_4563_A08C_35E52A52AECC__INCLUDED_) diff --git a/mp/src/utils/vrad_launcher/vrad_launcher-2010.vcxproj b/mp/src/utils/vrad_launcher/vrad_launcher-2010.vcxproj new file mode 100644 index 00000000..334d4e51 --- /dev/null +++ b/mp/src/utils/vrad_launcher/vrad_launcher-2010.vcxproj @@ -0,0 +1,245 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + Vrad_launcher + {0B6929D0-4447-E035-E47A-EBFCE557D5B3} + + + + Application + MultiByte + vrad + + + Application + MultiByte + vrad + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30319.1 + .\Debug\win32\ + .\Debug\win32\ + ..\..\devtools\vstools;$(ExecutablePath);$(Path) + true + true + true + true + true + .\Release\win32\ + .\Release\win32\ + ..\..\devtools\vstools;$(ExecutablePath);$(Path) + true + true + false + true + true + + + + if EXIST ..\..\..\game\bin\$(TargetFileName) for /f "delims=" %%A in ('attrib "..\..\..\game\bin\$(TargetFileName)"') do set valveTmpIsReadOnly="%%A" set valveTmpIsReadOnlyLetter=%valveTmpIsReadOnly:~6,1% if "%valveTmpIsReadOnlyLetter%"=="R" del /q "$(TargetDir)"$(TargetFileName) if exist ..\..\devtools\bin\vpc.exe ..\..\devtools\bin\vpc.exe -crc2 vrad_launcher.vcxproj if ERRORLEVEL 1 exit 1 + + + /MP + Disabled + ..\..\common;..\..\public;..\..\public\tier0;..\..\public\tier1 + _HAS_ITERATOR_DEBUGGING=0;WIN32;_WIN32;_DEBUG;DEBUG;_WINDOWS;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;RAD_TELEMETRY_DISABLED;COMPILER_MSVC32;VPCGAMECAPS=VALVE;PROJECTDIR=D:\dev\games\rel\hl2\src\utils\vrad_launcher;_DLL_EXT=.dll;VPCGAME=valve + true + false + Default + MultiThreadedDebug + true + StreamingSIMDExtensions + Fast + true + true + true + false + Use + Debug/vrad_launcher.pch + false + NoListing + $(IntDir)/ + $(IntDir)/ + $(IntDir)/ + false + Level4 + true + EditAndContinue + CompileAsCpp + $(IntDir)/ + Prompt + + + _DEBUG;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE + 1033 + + + + + /NXCOMPAT /ignore:4221 + %(AdditionalDependencies) + NotSet + $(OutDir)\vrad.exe + true + libc;libcd;libcmt + true + $(IntDir)/$(TargetName).pdb + false + $(IntDir)/$(TargetName).map + Console + + MachineX86 + PromptImmediately + false + + + true + + + true + + + true + $(OutDir)/vrad.bsc + + + Publishing to ..\..\..\game\bin + if not exist "..\..\..\game\bin" mkdir "..\..\..\game\bin" copy "$(TargetDir)"$(TargetFileName) ..\..\..\game\bin\$(TargetFileName) if ERRORLEVEL 1 goto BuildEventFailed if exist "$(TargetDir)"$(TargetName).map copy "$(TargetDir)"$(TargetName).map ..\..\..\game\bin\$(TargetName).map copy "$(TargetDir)"$(TargetName).pdb ..\..\..\game\bin\$(TargetName).pdb if ERRORLEVEL 1 goto BuildEventFailed goto BuildEventOK :BuildEventFailed echo *** ERROR! PostBuildStep FAILED for $(ProjectName)! EXE or DLL is probably running. *** del /q "$(TargetDir)"$(TargetFileName) exit 1 :BuildEventOK + + + + + + + if EXIST ..\..\..\game\bin\$(TargetFileName) for /f "delims=" %%A in ('attrib "..\..\..\game\bin\$(TargetFileName)"') do set valveTmpIsReadOnly="%%A" set valveTmpIsReadOnlyLetter=%valveTmpIsReadOnly:~6,1% if "%valveTmpIsReadOnlyLetter%"=="R" del /q "$(TargetDir)"$(TargetFileName) if exist ..\..\devtools\bin\vpc.exe ..\..\devtools\bin\vpc.exe -crc2 vrad_launcher.vcxproj if ERRORLEVEL 1 exit 1 + + + /MP /d2Zi+ + MaxSpeed + AnySuitable + true + Speed + ..\..\common;..\..\public;..\..\public\tier0;..\..\public\tier1 + WIN32;_WIN32;NDEBUG;_WINDOWS;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;RAD_TELEMETRY_DISABLED;COMPILER_MSVC32;VPCGAMECAPS=VALVE;PROJECTDIR=D:\dev\games\rel\hl2\src\utils\vrad_launcher;_DLL_EXT=.dll;VPCGAME=valve + true + false + MultiThreaded + false + true + StreamingSIMDExtensions + Fast + true + true + true + false + Use + Debug/vrad_launcher.pch + false + NoListing + $(IntDir)/ + $(IntDir)/ + $(IntDir)/ + false + Level4 + true + ProgramDatabase + CompileAsCpp + $(IntDir)/ + Prompt + + + NDEBUG;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE + 1033 + + + + + /DYNAMICBASE /NXCOMPAT /ignore:4221 + %(AdditionalDependencies) + NotSet + $(OutDir)\vrad.exe + true + libc;libcd;libcmtd + true + $(IntDir)/$(TargetName).pdb + false + $(IntDir)/$(TargetName).map + Console + true + true + + MachineX86 + PromptImmediately + + + true + + + true + + + true + $(OutDir)/vrad.bsc + + + Publishing to ..\..\..\game\bin + if not exist "..\..\..\game\bin" mkdir "..\..\..\game\bin" copy "$(TargetDir)"$(TargetFileName) ..\..\..\game\bin\$(TargetFileName) if ERRORLEVEL 1 goto BuildEventFailed if exist "$(TargetDir)"$(TargetName).map copy "$(TargetDir)"$(TargetName).map ..\..\..\game\bin\$(TargetName).map copy "$(TargetDir)"$(TargetName).pdb ..\..\..\game\bin\$(TargetName).pdb if ERRORLEVEL 1 goto BuildEventFailed goto BuildEventOK :BuildEventFailed echo *** ERROR! PostBuildStep FAILED for $(ProjectName)! EXE or DLL is probably running. *** del /q "$(TargetDir)"$(TargetFileName) exit 1 :BuildEventOK + + + + + + + + + + + + + + + + + Create + Create + + + + + + + + Compiling pointeroverride.asm + "$(VCInstallDir)bin\ml.exe" /c /Cp /Zi /Fo"$(IntDir)\%(Filename).obj" "%(FullPath)" + $(IntDir)\%(Filename).obj + Compiling pointeroverride.asm + "$(VCInstallDir)bin\ml.exe" /c /Cp /Zi /Fo"$(IntDir)\%(Filename).obj" "%(FullPath)" + $(IntDir)\%(Filename).obj + + + + + + + + diff --git a/mp/src/utils/vrad_launcher/vrad_launcher-2010.vcxproj.filters b/mp/src/utils/vrad_launcher/vrad_launcher-2010.vcxproj.filters new file mode 100644 index 00000000..0790b009 --- /dev/null +++ b/mp/src/utils/vrad_launcher/vrad_launcher-2010.vcxproj.filters @@ -0,0 +1,53 @@ + + + + + {1680C80B-FF1E-EA4D-9817-CC12254F2E40} + + + {C5D73B3A-C648-896C-B7CE-F174808E5BA5} + + + {BA03E055-4FA2-FCE3-8A1C-D348547D379C} + + + + + Link Libraries + + + Link Libraries + + + Link Libraries + + + + + Header Files + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + + + + + Source Files + + + + + diff --git a/mp/src/utils/vrad_launcher/vrad_launcher.cpp b/mp/src/utils/vrad_launcher/vrad_launcher.cpp new file mode 100644 index 00000000..fc458172 --- /dev/null +++ b/mp/src/utils/vrad_launcher/vrad_launcher.cpp @@ -0,0 +1,145 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// vrad_launcher.cpp : Defines the entry point for the console application. +// + +#include "stdafx.h" +#include +#include "tier1/strtools.h" +#include "tier0/icommandline.h" + + +char* GetLastErrorString() +{ + static char err[2048]; + + LPVOID lpMsgBuf; + FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + GetLastError(), + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language + (LPTSTR) &lpMsgBuf, + 0, + NULL + ); + + strncpy( err, (char*)lpMsgBuf, sizeof( err ) ); + LocalFree( lpMsgBuf ); + + err[ sizeof( err ) - 1 ] = 0; + + return err; +} + + +void MakeFullPath( const char *pIn, char *pOut, int outLen ) +{ + if ( pIn[0] == '/' || pIn[0] == '\\' || pIn[1] == ':' ) + { + // It's already a full path. + Q_strncpy( pOut, pIn, outLen ); + } + else + { + _getcwd( pOut, outLen ); + Q_strncat( pOut, "\\", outLen, COPY_ALL_CHARACTERS ); + Q_strncat( pOut, pIn, outLen, COPY_ALL_CHARACTERS ); + } +} + +int main(int argc, char* argv[]) +{ + char dllName[512]; + + CommandLine()->CreateCmdLine( argc, argv ); + + // check whether they used the -both switch. If this is specified, vrad will be run + // twice, once with -hdr and once without + int both_arg=0; + for(int arg=1;argmain( argc, argv ); + Sys_UnloadModule( pModule ); + pModule=0; + } + return returnValue; +} + diff --git a/mp/src/utils/vtf2tga/vtf2tga-2010.vcxproj b/mp/src/utils/vtf2tga/vtf2tga-2010.vcxproj new file mode 100644 index 00000000..74c38d03 --- /dev/null +++ b/mp/src/utils/vtf2tga/vtf2tga-2010.vcxproj @@ -0,0 +1,261 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + Vtf2tga + {6B017447-F682-A137-8DF4-4608281F2C9F} + + + + Application + MultiByte + vtf2tga + + + Application + MultiByte + vtf2tga + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30319.1 + .\Debug\win32\ + .\Debug\win32\ + ..\..\devtools\vstools;$(ExecutablePath);$(Path) + true + true + true + true + true + .\Release\win32\ + .\Release\win32\ + ..\..\devtools\vstools;$(ExecutablePath);$(Path) + true + true + false + true + true + + + + if EXIST ..\..\..\game\bin\$(TargetFileName) for /f "delims=" %%A in ('attrib "..\..\..\game\bin\$(TargetFileName)"') do set valveTmpIsReadOnly="%%A" set valveTmpIsReadOnlyLetter=%valveTmpIsReadOnly:~6,1% if "%valveTmpIsReadOnlyLetter%"=="R" del /q "$(TargetDir)"$(TargetFileName) if exist ..\..\devtools\bin\vpc.exe ..\..\devtools\bin\vpc.exe -crc2 vtf2tga.vcxproj if ERRORLEVEL 1 exit 1 + + + /MP + Disabled + ..\..\common;..\..\public;..\..\public\tier0;..\..\public\tier1 + _HAS_ITERATOR_DEBUGGING=0;WIN32;_WIN32;_DEBUG;DEBUG;_WINDOWS;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;RAD_TELEMETRY_DISABLED;COMPILER_MSVC32;VPCGAMECAPS=VALVE;PROJECTDIR=D:\dev\games\rel\hl2\src\utils\vtf2tga;_DLL_EXT=.dll;VPCGAME=valve + true + false + Default + MultiThreadedDebug + true + StreamingSIMDExtensions + Fast + true + true + true + false + NotUsing + false + NoListing + $(IntDir)/ + $(IntDir)/ + $(IntDir)/ + false + Level4 + true + EditAndContinue + CompileAsCpp + $(IntDir)/ + Prompt + + + _DEBUG;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE + 1033 + + + + + /NXCOMPAT /ignore:4221 + %(AdditionalDependencies) + NotSet + $(OutDir)\vtf2tga.exe + true + libc;libcd;libcmt + true + $(IntDir)/$(TargetName).pdb + false + $(IntDir)/$(TargetName).map + Console + + MachineX86 + PromptImmediately + false + + + true + + + true + + + true + $(OutDir)/vtf2tga.bsc + + + Publishing to ..\..\..\game\bin + if not exist "..\..\..\game\bin" mkdir "..\..\..\game\bin" copy "$(TargetDir)"$(TargetFileName) ..\..\..\game\bin\$(TargetFileName) if ERRORLEVEL 1 goto BuildEventFailed if exist "$(TargetDir)"$(TargetName).map copy "$(TargetDir)"$(TargetName).map ..\..\..\game\bin\$(TargetName).map copy "$(TargetDir)"$(TargetName).pdb ..\..\..\game\bin\$(TargetName).pdb if ERRORLEVEL 1 goto BuildEventFailed goto BuildEventOK :BuildEventFailed echo *** ERROR! PostBuildStep FAILED for $(ProjectName)! EXE or DLL is probably running. *** del /q "$(TargetDir)"$(TargetFileName) exit 1 :BuildEventOK + + + + + + + if EXIST ..\..\..\game\bin\$(TargetFileName) for /f "delims=" %%A in ('attrib "..\..\..\game\bin\$(TargetFileName)"') do set valveTmpIsReadOnly="%%A" set valveTmpIsReadOnlyLetter=%valveTmpIsReadOnly:~6,1% if "%valveTmpIsReadOnlyLetter%"=="R" del /q "$(TargetDir)"$(TargetFileName) if exist ..\..\devtools\bin\vpc.exe ..\..\devtools\bin\vpc.exe -crc2 vtf2tga.vcxproj if ERRORLEVEL 1 exit 1 + + + /MP /d2Zi+ + MaxSpeed + AnySuitable + true + Speed + ..\..\common;..\..\public;..\..\public\tier0;..\..\public\tier1 + WIN32;_WIN32;NDEBUG;_WINDOWS;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;RAD_TELEMETRY_DISABLED;COMPILER_MSVC32;VPCGAMECAPS=VALVE;PROJECTDIR=D:\dev\games\rel\hl2\src\utils\vtf2tga;_DLL_EXT=.dll;VPCGAME=valve + true + false + MultiThreaded + false + true + StreamingSIMDExtensions + Fast + true + true + true + false + NotUsing + false + NoListing + $(IntDir)/ + $(IntDir)/ + $(IntDir)/ + false + Level4 + true + ProgramDatabase + CompileAsCpp + $(IntDir)/ + Prompt + + + NDEBUG;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE + 1033 + + + + + /DYNAMICBASE /NXCOMPAT /ignore:4221 + %(AdditionalDependencies) + NotSet + $(OutDir)\vtf2tga.exe + true + libc;libcd;libcmtd + true + $(IntDir)/$(TargetName).pdb + false + $(IntDir)/$(TargetName).map + Console + true + true + + MachineX86 + PromptImmediately + + + true + + + true + + + true + $(OutDir)/vtf2tga.bsc + + + Publishing to ..\..\..\game\bin + if not exist "..\..\..\game\bin" mkdir "..\..\..\game\bin" copy "$(TargetDir)"$(TargetFileName) ..\..\..\game\bin\$(TargetFileName) if ERRORLEVEL 1 goto BuildEventFailed if exist "$(TargetDir)"$(TargetName).map copy "$(TargetDir)"$(TargetName).map ..\..\..\game\bin\$(TargetName).map copy "$(TargetDir)"$(TargetName).pdb ..\..\..\game\bin\$(TargetName).pdb if ERRORLEVEL 1 goto BuildEventFailed goto BuildEventOK :BuildEventFailed echo *** ERROR! PostBuildStep FAILED for $(ProjectName)! EXE or DLL is probably running. *** del /q "$(TargetDir)"$(TargetFileName) exit 1 :BuildEventOK + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + NotUsing + NotUsing + + + + + + + + Compiling pointeroverride.asm + "$(VCInstallDir)bin\ml.exe" /c /Cp /Zi /Fo"$(IntDir)\%(Filename).obj" "%(FullPath)" + $(IntDir)\%(Filename).obj + Compiling pointeroverride.asm + "$(VCInstallDir)bin\ml.exe" /c /Cp /Zi /Fo"$(IntDir)\%(Filename).obj" "%(FullPath)" + $(IntDir)\%(Filename).obj + + + + + + + + diff --git a/mp/src/utils/vtf2tga/vtf2tga-2010.vcxproj.filters b/mp/src/utils/vtf2tga/vtf2tga-2010.vcxproj.filters new file mode 100644 index 00000000..34553462 --- /dev/null +++ b/mp/src/utils/vtf2tga/vtf2tga-2010.vcxproj.filters @@ -0,0 +1,107 @@ + + + + + {1680C80B-FF1E-EA4D-9817-CC12254F2E40} + + + {C5D73B3A-C648-896C-B7CE-F174808E5BA5} + + + {BA03E055-4FA2-FCE3-8A1C-D348547D379C} + + + + + Link Libraries + + + Link Libraries + + + Link Libraries + + + Link Libraries + + + Link Libraries + + + Link Libraries + + + Link Libraries + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + + + + + Source Files + + + + + diff --git a/mp/src/utils/vtf2tga/vtf2tga.cpp b/mp/src/utils/vtf2tga/vtf2tga.cpp new file mode 100644 index 00000000..08c73340 --- /dev/null +++ b/mp/src/utils/vtf2tga/vtf2tga.cpp @@ -0,0 +1,316 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include +#include "mathlib/mathlib.h" +#include "bitmap/tgawriter.h" +#include "tier1/strtools.h" +#include "vtf/vtf.h" +#include "tier1/UtlBuffer.h" +#include "tier0/dbg.h" +#include "tier0/icommandline.h" +#include "tier1/utlbuffer.h" +#include "tier2/tier2.h" +#include "filesystem.h" + + +//----------------------------------------------------------------------------- +// HDRFIXME: move this somewhere else. +//----------------------------------------------------------------------------- +static void PFMWrite( float *pFloatImage, const char *pFilename, int width, int height ) +{ + FILE *fp; + fp = fopen( pFilename, "wb" ); + fprintf( fp, "PF\n%d %d\n-1.000000\n", width, height ); + int i; + for( i = height-1; i >= 0; i-- ) + { + float *pRow = &pFloatImage[3 * width * i]; + fwrite( pRow, width * sizeof( float ) * 3, 1, fp ); + } + fclose( fp ); +} + +SpewRetval_t VTF2TGAOutputFunc( SpewType_t spewType, char const *pMsg ) +{ + printf( pMsg ); + fflush( stdout ); + + if (spewType == SPEW_ERROR) + return SPEW_ABORT; + return (spewType == SPEW_ASSERT) ? SPEW_DEBUGGER : SPEW_CONTINUE; +} + +static void Usage( void ) +{ + Error( "Usage: vtf2tga -i [-o ] [-mip]\n" ); + exit( -1 ); +} + +int main( int argc, char **argv ) +{ + SpewOutputFunc( VTF2TGAOutputFunc ); + CommandLine()->CreateCmdLine( argc, argv ); + MathLib_Init( 2.2f, 2.2f, 0.0f, 1.0f, false, false, false, false ); + InitDefaultFileSystem(); + + const char *pVTFFileName = CommandLine()->ParmValue( "-i" ); + const char *pTGAFileName = CommandLine()->ParmValue( "-o" ); + bool bGenerateMipLevels = CommandLine()->CheckParm( "-mip" ) != NULL; + if ( !pVTFFileName ) + { + Usage(); + } + + if ( !pTGAFileName ) + { + pTGAFileName = pVTFFileName; + } + + char pCurrentDirectory[MAX_PATH]; + if ( _getcwd( pCurrentDirectory, sizeof(pCurrentDirectory) ) == NULL ) + { + fprintf( stderr, "Unable to get the current directory\n" ); + return -1; + } + Q_StripTrailingSlash( pCurrentDirectory ); + + char pBuf[MAX_PATH]; + if ( !Q_IsAbsolutePath( pTGAFileName ) ) + { + Q_snprintf( pBuf, sizeof(pBuf), "%s\\%s", pCurrentDirectory, pTGAFileName ); + } + else + { + Q_strncpy( pBuf, pTGAFileName, sizeof(pBuf) ); + } + Q_FixSlashes( pBuf ); + + char pOutFileNameBase[MAX_PATH]; + Q_StripExtension( pBuf, pOutFileNameBase, MAX_PATH ); + + char pActualVTFFileName[MAX_PATH]; + Q_strncpy( pActualVTFFileName, pVTFFileName, MAX_PATH ); + if ( !Q_strstr( pActualVTFFileName, ".vtf" ) ) + { + Q_strcat( pActualVTFFileName, ".vtf", MAX_PATH ); + } + + FILE *vtfFp = fopen( pActualVTFFileName, "rb" ); + if( !vtfFp ) + { + Error( "Can't open %s\n", pActualVTFFileName ); + exit( -1 ); + } + + fseek( vtfFp, 0, SEEK_END ); + int srcVTFLength = ftell( vtfFp ); + fseek( vtfFp, 0, SEEK_SET ); + + CUtlBuffer buf; + buf.EnsureCapacity( srcVTFLength ); + int nBytesRead = fread( buf.Base(), 1, srcVTFLength, vtfFp ); + fclose( vtfFp ); + buf.SeekPut( CUtlBuffer::SEEK_HEAD, nBytesRead ); + + IVTFTexture *pTex = CreateVTFTexture(); + if (!pTex->Unserialize( buf )) + { + Error( "*** Error reading in .VTF file %s\n", pActualVTFFileName ); + exit(-1); + } + + Msg( "vtf width: %d\n", pTex->Width() ); + Msg( "vtf height: %d\n", pTex->Height() ); + Msg( "vtf numFrames: %d\n", pTex->FrameCount() ); + + Msg( "TEXTUREFLAGS_POINTSAMPLE=%s\n", ( pTex->Flags() & TEXTUREFLAGS_POINTSAMPLE ) ? "true" : "false" ); + Msg( "TEXTUREFLAGS_TRILINEAR=%s\n", ( pTex->Flags() & TEXTUREFLAGS_TRILINEAR ) ? "true" : "false" ); + Msg( "TEXTUREFLAGS_CLAMPS=%s\n", ( pTex->Flags() & TEXTUREFLAGS_CLAMPS ) ? "true" : "false" ); + Msg( "TEXTUREFLAGS_CLAMPT=%s\n", ( pTex->Flags() & TEXTUREFLAGS_CLAMPT ) ? "true" : "false" ); + Msg( "TEXTUREFLAGS_CLAMPU=%s\n", ( pTex->Flags() & TEXTUREFLAGS_CLAMPU ) ? "true" : "false" ); + Msg( "TEXTUREFLAGS_BORDER=%s\n", ( pTex->Flags() & TEXTUREFLAGS_BORDER ) ? "true" : "false" ); + Msg( "TEXTUREFLAGS_ANISOTROPIC=%s\n", ( pTex->Flags() & TEXTUREFLAGS_ANISOTROPIC ) ? "true" : "false" ); + Msg( "TEXTUREFLAGS_HINT_DXT5=%s\n", ( pTex->Flags() & TEXTUREFLAGS_HINT_DXT5 ) ? "true" : "false" ); + Msg( "TEXTUREFLAGS_SRGB=%s\n", ( pTex->Flags() & TEXTUREFLAGS_SRGB ) ? "true" : "false" ); + Msg( "TEXTUREFLAGS_NORMAL=%s\n", ( pTex->Flags() & TEXTUREFLAGS_NORMAL ) ? "true" : "false" ); + Msg( "TEXTUREFLAGS_NOMIP=%s\n", ( pTex->Flags() & TEXTUREFLAGS_NOMIP ) ? "true" : "false" ); + Msg( "TEXTUREFLAGS_NOLOD=%s\n", ( pTex->Flags() & TEXTUREFLAGS_NOLOD ) ? "true" : "false" ); + Msg( "TEXTUREFLAGS_ALL_MIPS=%s\n", ( pTex->Flags() & TEXTUREFLAGS_ALL_MIPS ) ? "true" : "false" ); + Msg( "TEXTUREFLAGS_PROCEDURAL=%s\n", ( pTex->Flags() & TEXTUREFLAGS_PROCEDURAL ) ? "true" : "false" ); + Msg( "TEXTUREFLAGS_ONEBITALPHA=%s\n", ( pTex->Flags() & TEXTUREFLAGS_ONEBITALPHA ) ? "true" : "false" ); + Msg( "TEXTUREFLAGS_EIGHTBITALPHA=%s\n", ( pTex->Flags() & TEXTUREFLAGS_EIGHTBITALPHA ) ? "true" : "false" ); + Msg( "TEXTUREFLAGS_ENVMAP=%s\n", ( pTex->Flags() & TEXTUREFLAGS_ENVMAP ) ? "true" : "false" ); + Msg( "TEXTUREFLAGS_RENDERTARGET=%s\n", ( pTex->Flags() & TEXTUREFLAGS_RENDERTARGET ) ? "true" : "false" ); + Msg( "TEXTUREFLAGS_DEPTHRENDERTARGET=%s\n", ( pTex->Flags() & TEXTUREFLAGS_DEPTHRENDERTARGET ) ? "true" : "false" ); + Msg( "TEXTUREFLAGS_NODEBUGOVERRIDE=%s\n", ( pTex->Flags() & TEXTUREFLAGS_NODEBUGOVERRIDE ) ? "true" : "false" ); + Msg( "TEXTUREFLAGS_SINGLECOPY=%s\n", ( pTex->Flags() & TEXTUREFLAGS_SINGLECOPY ) ? "true" : "false" ); + + Vector vecReflectivity = pTex->Reflectivity(); + Msg( "vtf reflectivity: %f %f %f\n", vecReflectivity[0], vecReflectivity[1], vecReflectivity[2] ); + Msg( "transparency: " ); + if( pTex->Flags() & TEXTUREFLAGS_EIGHTBITALPHA ) + { + Msg( "eightbitalpha\n" ); + } + else if( pTex->Flags() & TEXTUREFLAGS_ONEBITALPHA ) + { + Msg( "onebitalpha\n" ); + } + else + { + Msg( "noalpha\n" ); + } + ImageFormat srcFormat = pTex->Format(); + Msg( "vtf format: %s\n", ImageLoader::GetName( srcFormat ) ); + + int iTGANameLen = Q_strlen( pOutFileNameBase ); + + int iFaceCount = pTex->FaceCount(); + int nFrameCount = pTex->FrameCount(); + bool bIsCubeMap = pTex->IsCubeMap(); + + int iLastMipLevel = bGenerateMipLevels ? pTex->MipCount() - 1 : 0; + for( int iFrame = 0; iFrame < nFrameCount; ++iFrame ) + { + for ( int iMipLevel = 0; iMipLevel <= iLastMipLevel; ++iMipLevel ) + { + int iWidth, iHeight, iDepth; + pTex->ComputeMipLevelDimensions( iMipLevel, &iWidth, &iHeight, &iDepth ); + + for (int iCubeFace = 0; iCubeFace < iFaceCount; ++iCubeFace) + { + for ( int z = 0; z < iDepth; ++z ) + { + // Construct output filename + char *pTempNameBuf = (char *)stackalloc( iTGANameLen + 13 ); + Q_strncpy( pTempNameBuf, pOutFileNameBase, iTGANameLen + 1 ); + char *pExt = Q_strrchr( pTempNameBuf, '.' ); + if ( pExt ) + { + pExt = 0; + } + + if ( bIsCubeMap ) + { + Assert( pTex->Depth() == 1 ); // shouldn't this be 1 instead of 0? + static const char *pCubeFaceName[7] = { "rt", "lf", "bk", "ft", "up", "dn", "sph" }; + Q_strcat( pTempNameBuf, pCubeFaceName[iCubeFace], iTGANameLen + 13 ); + } + + if ( nFrameCount > 1 ) + { + char pTemp[4]; + Q_snprintf( pTemp, 4, "%03d", iFrame ); + Q_strcat( pTempNameBuf, pTemp, iTGANameLen + 13 ); + } + + if ( iLastMipLevel != 0 ) + { + char pTemp[8]; + Q_snprintf( pTemp, 8, "_mip%d", iMipLevel ); + Q_strcat( pTempNameBuf, pTemp, iTGANameLen + 13 ); + } + + if ( pTex->Depth() > 1 ) + { + char pTemp[6]; + Q_snprintf( pTemp, 6, "_z%03d", z ); + Q_strcat( pTempNameBuf, pTemp, iTGANameLen + 13 ); + } + + if( srcFormat == IMAGE_FORMAT_RGBA16161616F ) + { + Q_strcat( pTempNameBuf, ".pfm", iTGANameLen + 13 ); + } + else + { + Q_strcat( pTempNameBuf, ".tga", iTGANameLen + 13 ); + } + + unsigned char *pSrcImage = pTex->ImageData( iFrame, iCubeFace, iMipLevel, 0, 0, z ); + + ImageFormat dstFormat; + if( srcFormat == IMAGE_FORMAT_RGBA16161616F ) + { + dstFormat = IMAGE_FORMAT_RGB323232F; + } + else + { + if( ImageLoader::IsTransparent( srcFormat ) || (srcFormat == IMAGE_FORMAT_ATI1N ) || (srcFormat == IMAGE_FORMAT_ATI2N )) + { + dstFormat = IMAGE_FORMAT_BGRA8888; + } + else + { + dstFormat = IMAGE_FORMAT_BGR888; + } + } + // dstFormat = IMAGE_FORMAT_RGBA8888; + // dstFormat = IMAGE_FORMAT_RGB888; + // dstFormat = IMAGE_FORMAT_BGRA8888; + // dstFormat = IMAGE_FORMAT_BGR888; + // dstFormat = IMAGE_FORMAT_BGRA5551; + // dstFormat = IMAGE_FORMAT_BGR565; + // dstFormat = IMAGE_FORMAT_BGRA4444; + // printf( "dstFormat: %s\n", ImageLoader::GetName( dstFormat ) ); + unsigned char *pDstImage = new unsigned char[ImageLoader::GetMemRequired( iWidth, iHeight, 1, dstFormat, false )]; + if( !ImageLoader::ConvertImageFormat( pSrcImage, srcFormat, + pDstImage, dstFormat, iWidth, iHeight, 0, 0 ) ) + { + Error( "Error converting from %s to %s\n", + ImageLoader::GetName( srcFormat ), ImageLoader::GetName( dstFormat ) ); + exit( -1 ); + } + + if( dstFormat != IMAGE_FORMAT_RGB323232F ) + { + if( ImageLoader::IsTransparent( dstFormat ) && ( dstFormat != IMAGE_FORMAT_RGBA8888 ) ) + { + unsigned char *tmpImage = pDstImage; + pDstImage = new unsigned char[ImageLoader::GetMemRequired( iWidth, iHeight, 1, IMAGE_FORMAT_RGBA8888, false )]; + if( !ImageLoader::ConvertImageFormat( tmpImage, dstFormat, pDstImage, IMAGE_FORMAT_RGBA8888, + iWidth, iHeight, 0, 0 ) ) + { + Error( "Error converting from %s to %s\n", + ImageLoader::GetName( dstFormat ), ImageLoader::GetName( IMAGE_FORMAT_RGBA8888 ) ); + } + dstFormat = IMAGE_FORMAT_RGBA8888; + } + else if( !ImageLoader::IsTransparent( dstFormat ) && ( dstFormat != IMAGE_FORMAT_RGB888 ) ) + { + unsigned char *tmpImage = pDstImage; + pDstImage = new unsigned char[ImageLoader::GetMemRequired( iWidth, iHeight, 1, IMAGE_FORMAT_RGB888, false )]; + if( !ImageLoader::ConvertImageFormat( tmpImage, dstFormat, pDstImage, IMAGE_FORMAT_RGB888, + iWidth, iHeight, 0, 0 ) ) + { + Error( "Error converting from %s to %s\n", + ImageLoader::GetName( dstFormat ), ImageLoader::GetName( IMAGE_FORMAT_RGB888 ) ); + } + dstFormat = IMAGE_FORMAT_RGB888; + } + + CUtlBuffer outBuffer; + TGAWriter::WriteToBuffer( pDstImage, outBuffer, iWidth, iHeight, + dstFormat, dstFormat ); + if ( !g_pFullFileSystem->WriteFile( pTempNameBuf, NULL, outBuffer ) ) + { + fprintf( stderr, "unable to write %s\n", pTempNameBuf ); + } + } + else + { + PFMWrite( ( float * )pDstImage, pTempNameBuf, iWidth, iHeight ); + } + } + } + } + } + + // leak leak leak leak leak, leak leak, leak leak (Blue Danube) + return 0; +} diff --git a/mp/src/utils/vtfdiff/vtfdiff-2010.vcxproj b/mp/src/utils/vtfdiff/vtfdiff-2010.vcxproj new file mode 100644 index 00000000..06d0e26e --- /dev/null +++ b/mp/src/utils/vtfdiff/vtfdiff-2010.vcxproj @@ -0,0 +1,244 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + Vtfdiff + {81EE9F71-4DFD-8670-B3EA-7B4E931E9845} + + + + Application + MultiByte + vtfdiff + + + Application + MultiByte + vtfdiff + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30319.1 + .\Debug\win32\ + .\Debug\win32\ + ..\..\devtools\vstools;$(ExecutablePath);$(Path) + true + true + true + true + true + .\Release\win32\ + .\Release\win32\ + ..\..\devtools\vstools;$(ExecutablePath);$(Path) + true + true + false + true + true + + + + if EXIST ..\..\..\game\bin\$(TargetFileName) for /f "delims=" %%A in ('attrib "..\..\..\game\bin\$(TargetFileName)"') do set valveTmpIsReadOnly="%%A" set valveTmpIsReadOnlyLetter=%valveTmpIsReadOnly:~6,1% if "%valveTmpIsReadOnlyLetter%"=="R" del /q "$(TargetDir)"$(TargetFileName) if exist ..\..\devtools\bin\vpc.exe ..\..\devtools\bin\vpc.exe -crc2 vtfdiff.vcxproj if ERRORLEVEL 1 exit 1 + + + /MP + Disabled + ..\..\common;..\..\public;..\..\public\tier0;..\..\public\tier1 + _HAS_ITERATOR_DEBUGGING=0;WIN32;_WIN32;_DEBUG;DEBUG;_WINDOWS;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;RAD_TELEMETRY_DISABLED;COMPILER_MSVC32;VPCGAMECAPS=VALVE;PROJECTDIR=D:\dev\games\rel\hl2\src\utils\vtfdiff;_DLL_EXT=.dll;VPCGAME=valve + true + false + Default + MultiThreadedDebug + true + StreamingSIMDExtensions + Fast + true + true + true + false + NotUsing + false + NoListing + $(IntDir)/ + $(IntDir)/ + $(IntDir)/ + false + Level4 + true + EditAndContinue + CompileAsCpp + $(IntDir)/ + Prompt + + + _DEBUG;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE + 1033 + + + + + /NXCOMPAT /ignore:4221 + %(AdditionalDependencies) + NotSet + $(OutDir)\vtfdiff.exe + true + libc;libcd;libcmt + true + $(IntDir)/$(TargetName).pdb + false + $(IntDir)/$(TargetName).map + Console + + MachineX86 + PromptImmediately + false + + + true + + + true + + + true + $(OutDir)/vtfdiff.bsc + + + Publishing to ..\..\..\game\bin + if not exist "..\..\..\game\bin" mkdir "..\..\..\game\bin" copy "$(TargetDir)"$(TargetFileName) ..\..\..\game\bin\$(TargetFileName) if ERRORLEVEL 1 goto BuildEventFailed if exist "$(TargetDir)"$(TargetName).map copy "$(TargetDir)"$(TargetName).map ..\..\..\game\bin\$(TargetName).map copy "$(TargetDir)"$(TargetName).pdb ..\..\..\game\bin\$(TargetName).pdb if ERRORLEVEL 1 goto BuildEventFailed goto BuildEventOK :BuildEventFailed echo *** ERROR! PostBuildStep FAILED for $(ProjectName)! EXE or DLL is probably running. *** del /q "$(TargetDir)"$(TargetFileName) exit 1 :BuildEventOK + + + + + + + if EXIST ..\..\..\game\bin\$(TargetFileName) for /f "delims=" %%A in ('attrib "..\..\..\game\bin\$(TargetFileName)"') do set valveTmpIsReadOnly="%%A" set valveTmpIsReadOnlyLetter=%valveTmpIsReadOnly:~6,1% if "%valveTmpIsReadOnlyLetter%"=="R" del /q "$(TargetDir)"$(TargetFileName) if exist ..\..\devtools\bin\vpc.exe ..\..\devtools\bin\vpc.exe -crc2 vtfdiff.vcxproj if ERRORLEVEL 1 exit 1 + + + /MP /d2Zi+ + MaxSpeed + AnySuitable + true + Speed + ..\..\common;..\..\public;..\..\public\tier0;..\..\public\tier1 + WIN32;_WIN32;NDEBUG;_WINDOWS;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;RAD_TELEMETRY_DISABLED;COMPILER_MSVC32;VPCGAMECAPS=VALVE;PROJECTDIR=D:\dev\games\rel\hl2\src\utils\vtfdiff;_DLL_EXT=.dll;VPCGAME=valve + true + false + MultiThreaded + false + true + StreamingSIMDExtensions + Fast + true + true + true + false + NotUsing + false + NoListing + $(IntDir)/ + $(IntDir)/ + $(IntDir)/ + false + Level4 + true + ProgramDatabase + CompileAsCpp + $(IntDir)/ + Prompt + + + NDEBUG;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE + 1033 + + + + + /DYNAMICBASE /NXCOMPAT /ignore:4221 + %(AdditionalDependencies) + NotSet + $(OutDir)\vtfdiff.exe + true + libc;libcd;libcmtd + true + $(IntDir)/$(TargetName).pdb + false + $(IntDir)/$(TargetName).map + Console + true + true + + MachineX86 + PromptImmediately + + + true + + + true + + + true + $(OutDir)/vtfdiff.bsc + + + Publishing to ..\..\..\game\bin + if not exist "..\..\..\game\bin" mkdir "..\..\..\game\bin" copy "$(TargetDir)"$(TargetFileName) ..\..\..\game\bin\$(TargetFileName) if ERRORLEVEL 1 goto BuildEventFailed if exist "$(TargetDir)"$(TargetName).map copy "$(TargetDir)"$(TargetName).map ..\..\..\game\bin\$(TargetName).map copy "$(TargetDir)"$(TargetName).pdb ..\..\..\game\bin\$(TargetName).pdb if ERRORLEVEL 1 goto BuildEventFailed goto BuildEventOK :BuildEventFailed echo *** ERROR! PostBuildStep FAILED for $(ProjectName)! EXE or DLL is probably running. *** del /q "$(TargetDir)"$(TargetFileName) exit 1 :BuildEventOK + + + + + + + + + + + + + + + + + + NotUsing + NotUsing + + + + + + + + Compiling pointeroverride.asm + "$(VCInstallDir)bin\ml.exe" /c /Cp /Zi /Fo"$(IntDir)\%(Filename).obj" "%(FullPath)" + $(IntDir)\%(Filename).obj + Compiling pointeroverride.asm + "$(VCInstallDir)bin\ml.exe" /c /Cp /Zi /Fo"$(IntDir)\%(Filename).obj" "%(FullPath)" + $(IntDir)\%(Filename).obj + + + + + + + + diff --git a/mp/src/utils/vtfdiff/vtfdiff-2010.vcxproj.filters b/mp/src/utils/vtfdiff/vtfdiff-2010.vcxproj.filters new file mode 100644 index 00000000..59996012 --- /dev/null +++ b/mp/src/utils/vtfdiff/vtfdiff-2010.vcxproj.filters @@ -0,0 +1,53 @@ + + + + + {C5D73B3A-C648-896C-B7CE-F174808E5BA5} + + + {BA03E055-4FA2-FCE3-8A1C-D348547D379C} + + + + + Link Libraries + + + Link Libraries + + + Link Libraries + + + Link Libraries + + + Link Libraries + + + Link Libraries + + + Link Libraries + + + + + + + Source Files + + + Source Files + + + + + + + Source Files + + + + + diff --git a/mp/src/utils/vtfdiff/vtfdiff.cpp b/mp/src/utils/vtfdiff/vtfdiff.cpp new file mode 100644 index 00000000..bee0d777 --- /dev/null +++ b/mp/src/utils/vtfdiff/vtfdiff.cpp @@ -0,0 +1,438 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +#include +#include +#include +#include +#include "vtf/vtf.h" +#include "tier1/UtlBuffer.h" +#include "tier1/utlmap.h" +#include "bitmap/imageformat.h" +#include "mathlib/vector.h" +#include + +void Usage( void ) +{ + printf( "Usage: vtfdiff file1.vtf file2.vtf\n" ); +} + +bool LoadFileIntoBuffer( const char *pFileName, CUtlBuffer &buf ) +{ + struct _stat statBuf; + if( _stat( pFileName, &statBuf ) != 0 ) + { + goto error; + } + + buf.EnsureCapacity( statBuf.st_size ); + FILE *fp; + fp = fopen( pFileName, "rb" ); + if( !fp ) + { + goto error; + } + + int nBytesRead = fread( buf.Base(), 1, statBuf.st_size, fp ); + fclose( fp ); + + buf.SeekPut( CUtlBuffer::SEEK_HEAD, nBytesRead ); + return true; + +error: + printf( "Can't find file %s\n", pFileName ); + return false; +} + +char const * ResourceToString( uint32 uiResType ) +{ + static char chBuffer[256]; + + switch ( uiResType ) + { + case VTF_LEGACY_RSRC_LOW_RES_IMAGE: + return "VTF_LEGACY_RSRC_LOW_RES_IMAGE"; + case VTF_LEGACY_RSRC_IMAGE: + return "VTF_LEGACY_RSRC_IMAGE"; + case VTF_RSRC_SHEET: + return "VTF_RSRC_SHEET"; + case MK_VTF_RSRC_ID( 'C','R','C' ): + return "CRC"; + case VTF_RSRC_TEXTURE_LOD_SETTINGS: + return "VTF_RSRC_TEXTURE_LOD_SETTINGS"; + + default: + sprintf( chBuffer, "0x%08X", uiResType ); + return chBuffer; + } + + return chBuffer; +} + +void PrintFlags( int flags ) +{ +#define PRNFLAG( flagname ) \ + if ( ( flags & (flagname) ) == (flagname) ) \ + { \ + flags &=~ (flagname); \ + printf( "%s%s", #flagname + strlen("TEXTUREFLAGS_"), flags ? "|" : "" ); \ + } \ + + + PRNFLAG( TEXTUREFLAGS_POINTSAMPLE ) + PRNFLAG( TEXTUREFLAGS_TRILINEAR ) + PRNFLAG( TEXTUREFLAGS_CLAMPS ) + PRNFLAG( TEXTUREFLAGS_CLAMPT ) + PRNFLAG( TEXTUREFLAGS_ANISOTROPIC ) + PRNFLAG( TEXTUREFLAGS_HINT_DXT5 ) + PRNFLAG( TEXTUREFLAGS_SRGB ) + PRNFLAG( TEXTUREFLAGS_NORMAL ) + PRNFLAG( TEXTUREFLAGS_NOMIP ) + PRNFLAG( TEXTUREFLAGS_NOLOD ) + PRNFLAG( TEXTUREFLAGS_ALL_MIPS ) + PRNFLAG( TEXTUREFLAGS_PROCEDURAL ) + PRNFLAG( TEXTUREFLAGS_ONEBITALPHA ) + PRNFLAG( TEXTUREFLAGS_EIGHTBITALPHA ) + PRNFLAG( TEXTUREFLAGS_ENVMAP ) + PRNFLAG( TEXTUREFLAGS_RENDERTARGET ) + PRNFLAG( TEXTUREFLAGS_DEPTHRENDERTARGET ) + PRNFLAG( TEXTUREFLAGS_NODEBUGOVERRIDE ) + PRNFLAG( TEXTUREFLAGS_SINGLECOPY ) + PRNFLAG( TEXTUREFLAGS_UNUSED_00080000 ) + PRNFLAG( TEXTUREFLAGS_UNUSED_00100000 ) + PRNFLAG( TEXTUREFLAGS_UNUSED_00200000 ) + PRNFLAG( TEXTUREFLAGS_UNUSED_00400000 ) + PRNFLAG( TEXTUREFLAGS_NODEPTHBUFFER ) + PRNFLAG( TEXTUREFLAGS_UNUSED_01000000 ) + PRNFLAG( TEXTUREFLAGS_CLAMPU ) + PRNFLAG( TEXTUREFLAGS_VERTEXTEXTURE ) + PRNFLAG( TEXTUREFLAGS_SSBUMP ) + PRNFLAG( TEXTUREFLAGS_UNUSED_10000000 ) + PRNFLAG( TEXTUREFLAGS_BORDER ) + PRNFLAG( TEXTUREFLAGS_UNUSED_40000000 ) + PRNFLAG( TEXTUREFLAGS_UNUSED_80000000 ) + +#undef PRNFLAG + + if ( flags ) + { + printf( "0x%08X", flags ); + } +} + +int main( int argc, char **argv ) +{ + if( argc != 3 ) + { + Usage(); + return 10; + } + + CUtlBuffer file1; + CUtlBuffer file2; + + if ( !LoadFileIntoBuffer( argv[1], file1 ) ) + return 21; + if ( !LoadFileIntoBuffer( argv[2], file2 ) ) + return 22; + + IVTFTexture *pTexture1 = CreateVTFTexture(); + IVTFTexture *pTexture2 = CreateVTFTexture(); + + IVTFTexture *arrTextures[2] = { pTexture1, pTexture2 }; + + bool bMatch = true; + + if( !pTexture1->Unserialize( file1 ) ) + { + printf( "error loading %s\n", argv[1] ); + return 31; + } + if( !pTexture2->Unserialize( file2 ) ) + { + printf( "error loading %s\n", argv[2] ); + return 32; + } + + if( pTexture1->Width() != pTexture2->Width() || + pTexture1->Height() != pTexture2->Height() || + pTexture1->Depth() != pTexture2->Depth() ) + { + printf( "%s dimensions differ: %dx%dx%d != %dx%dx%d\n", + argv[1], + ( int )pTexture1->Width(), ( int )pTexture1->Height(), ( int )pTexture1->Depth(), + ( int )pTexture2->Width(), ( int )pTexture2->Height(), ( int )pTexture2->Depth() ); + bMatch = false; + } + + if( pTexture1->LowResWidth() != pTexture2->LowResWidth() || + pTexture1->LowResHeight() != pTexture2->LowResHeight() ) + { + printf( "%s lowres dimensions differ: %dx%d != %dx%d\n", + argv[1], + ( int )pTexture1->LowResWidth(), ( int )pTexture1->LowResHeight(), + ( int )pTexture2->LowResWidth(), ( int )pTexture2->LowResHeight() ); + bMatch = false; + } + + if( pTexture1->MipCount() != pTexture2->MipCount() ) + { + printf( "%s differing mipcounts: %d != %d\n", + argv[1], + ( int )pTexture1->MipCount(), ( int )pTexture2->MipCount() ); + bMatch = false; + } + + if( pTexture1->FaceCount() != pTexture2->FaceCount() ) + { + printf( "%s differing facecount: %d != %d\n", + argv[1], + ( int )pTexture1->FaceCount(), ( int )pTexture2->FaceCount() ); + bMatch = false; + } + + if( pTexture1->FrameCount() != pTexture2->FrameCount() ) + { + printf( "%s differing framecount: %d != %d\n", + argv[1], + ( int )pTexture1->FrameCount(), ( int )pTexture2->FrameCount() ); + bMatch = false; + } + + if( pTexture1->Flags() != pTexture2->Flags() ) + { + printf( "%s differing flags: \"", + argv[1] ); + PrintFlags( pTexture1->Flags() ); + printf( "\" != \"" ); + PrintFlags( pTexture2->Flags() ); + printf( "\"\n" ); + bMatch = false; + } + + if( pTexture1->BumpScale() != pTexture2->BumpScale() ) + { + printf( "%s differing bumpscale: %f != %f\n", + argv[1], + ( float )pTexture1->BumpScale(), ( float )pTexture2->BumpScale() ); + bMatch = false; + } + + if( pTexture1->Format() != pTexture2->Format() ) + { + printf( "%s differing image format: %s != %s\n", + argv[1], + ImageLoader::GetName( pTexture1->Format() ), + ImageLoader::GetName( pTexture2->Format() ) ); + bMatch = false; + } + + if( pTexture1->LowResFormat() != pTexture2->LowResFormat() ) + { + Assert(0); + printf( "%s differing lowres image format: %s != %s\n", + argv[1], + ImageLoader::GetName( pTexture1->LowResFormat() ), + ImageLoader::GetName( pTexture2->LowResFormat() ) ); + bMatch = false; + } + + const Vector &vReflectivity1 = pTexture1->Reflectivity(); + const Vector &vReflectivity2 = pTexture2->Reflectivity(); + if( !VectorsAreEqual( vReflectivity1, vReflectivity2, 0.0001f ) ) + { + printf( "%s differing reflectivity: [%f,%f,%f] != [%f,%f,%f]\n", + argv[1], + ( float )pTexture1->Reflectivity()[0], + ( float )pTexture1->Reflectivity()[1], + ( float )pTexture1->Reflectivity()[2], + ( float )pTexture2->Reflectivity()[0], + ( float )pTexture2->Reflectivity()[1], + ( float )pTexture2->Reflectivity()[2] ); + bMatch = false; + } + + if ( pTexture1->ComputeTotalSize() != pTexture2->ComputeTotalSize() ) + { + printf( "%s differing image data size: %d != %d\n", + argv[1], + ( int )pTexture1->ComputeTotalSize(), ( int )pTexture2->ComputeTotalSize() ); + bMatch = false; + } + + if ( bMatch ) + { + unsigned char const *pData1 = pTexture1->ImageData(); + unsigned char const *pData2 = pTexture2->ImageData(); + + int const iSize = pTexture1->ComputeTotalSize(); + + if( memcmp( pData1, pData2, iSize) != 0 ) + { + printf( "%s image data different\n", argv[1] ); + + if (( pTexture1->Format() == IMAGE_FORMAT_DXT1 ) || ( pTexture1->Format() == IMAGE_FORMAT_DXT3 ) || + ( pTexture1->Format() == IMAGE_FORMAT_DXT5 ) || ( pTexture1->Format() == IMAGE_FORMAT_ATI2N ) || + ( pTexture1->Format() == IMAGE_FORMAT_ATI1N ) ) + { + int i, numOffsetsComplained = 0; + for( i = 0; i < iSize; i++ ) + { + if( pData1[i] != pData2[i] ) + { + printf( "image data at offset %d different\n", i ); + if ( numOffsetsComplained ++ > 10 ) + { + printf( "image data significantly differs!\n" ); + break; + } + } + } + } + else + { + for( int iFrame = 0; iFrame < pTexture1->FrameCount(); ++iFrame ) + { + for ( int iMipLevel = 0; iMipLevel < pTexture1->MipCount(); ++iMipLevel ) + { + int nMipWidth, nMipHeight, nMipDepth; + pTexture1->ComputeMipLevelDimensions( iMipLevel, &nMipWidth, &nMipHeight, &nMipDepth ); + + for (int iCubeFace = 0; iCubeFace < pTexture1->FrameCount(); ++iCubeFace) + { + for ( int z = 0; z < nMipDepth; ++z ) + { + pData1 = pTexture1->ImageData( iFrame, iCubeFace, iMipLevel, 0, 0, z ); + pData2 = pTexture2->ImageData( iFrame, iCubeFace, iMipLevel, 0, 0, z ); + + int nMipSize = pTexture1->ComputeMipSize( iMipLevel ); + if ( memcmp( pData1, pData2, nMipSize ) ) + { + bool bBreak = false; + + for ( int y = 0; y < nMipHeight; ++y ) + { + for ( int x = 0; x < nMipWidth; ++x ) + { + unsigned char const *pData1a = pTexture1->ImageData( iFrame, iCubeFace, iMipLevel, x, y, z ); + unsigned char const *pData2a = pTexture2->ImageData( iFrame, iCubeFace, iMipLevel, x, y, z ); + + if ( memcmp( pData1a, pData2a, ImageLoader::SizeInBytes( pTexture1->Format() ) ) ) + { + printf( "Frame %d Mip level %d Face %d Z-slice %d texel (%d,%d) different!\n", + iFrame, iMipLevel, iCubeFace, z, x, y ); + bBreak = true; + break; + } + } + + if ( bBreak ) + break; + } + } + } + } + } + } + } + + bMatch = false; + } + } + + // Lowres data + { + int iDummy, iSize1, iSize2; + pTexture1->LowResFileInfo( &iDummy, &iSize1 ); + pTexture2->LowResFileInfo( &iDummy, &iSize2 ); + + if ( iSize1 != iSize2 ) + { + printf( "%s differing low res image data size: %d != %d\n", argv[1], iSize1, iSize2 ); + bMatch = false; + } + + if ( bMatch ) + { + if ( memcmp( pTexture1->LowResImageData(), pTexture2->LowResImageData(), iSize1 ) != 0 ) + { + printf( "%s differing low res image data\n", + argv[1] ); + bMatch = false; + } + } + } + + // Check other resources + { + int numRes1 = pTexture1->GetResourceTypes( NULL, 0 ); + int numRes2 = pTexture2->GetResourceTypes( NULL, 0 ); + + // List of resource types checked or insignificant diffs + typedef CUtlMap< int, bool > MapResTypes; + MapResTypes mapTypes( DefLessFunc( int ) ); + mapTypes.Insert( VTF_LEGACY_RSRC_LOW_RES_IMAGE, true ); + mapTypes.Insert( VTF_LEGACY_RSRC_IMAGE, true ); + mapTypes.Insert( MK_VTF_RSRC_ID( 'C','R','C' ), true ); + + uint32 *puiresbuffer = ( uint32 * ) stackalloc( ( numRes1 + numRes2 ) * sizeof( uint32 ) ); + + int arrNums[2] = { numRes1, numRes2 }; + + for ( int itx = 0; itx < 2; ++ itx ) + { + arrTextures[itx]->GetResourceTypes( puiresbuffer, arrNums[itx] ); + while ( arrNums[itx] --> 0 ) + { + uint32 uiResType = puiresbuffer[ arrNums[itx] ]; + if ( mapTypes.Find( uiResType ) != mapTypes.InvalidIndex() ) + continue; + + mapTypes.Insert( uiResType, true ); + + size_t numBytes1, numBytes2; + void const *pvResData1 = pTexture1->GetResourceData( uiResType, &numBytes1 ); + void const *pvResData2 = pTexture2->GetResourceData( uiResType, &numBytes2 ); + + if ( !pvResData1 != !pvResData2 ) + { + printf( "%s different resource %s %s\n", + argv[1], + ResourceToString( uiResType ), + pvResData1 ? "present" : "missing" ); + bMatch = false; + } + else if ( numBytes1 != numBytes2 ) + { + printf( "%s different resource %s size %lld != %lld\n", + argv[1], + ResourceToString( uiResType ), + (long long)numBytes1, (long long)numBytes2 ); + bMatch = false; + } + else if ( memcmp( pvResData1, pvResData2, numBytes1 ) != 0 ) + { + printf( "%s different resource %s data\n", + argv[1], + ResourceToString( uiResType ) ); + bMatch = false; + } + } + } + } + + + + if( bMatch ) + { + return 0; + } + else + { + return 1; + } +} diff --git a/mp/src/utils/vvis/WaterDist.cpp b/mp/src/utils/vvis/WaterDist.cpp new file mode 100644 index 00000000..5ed380c8 --- /dev/null +++ b/mp/src/utils/vvis/WaterDist.cpp @@ -0,0 +1,30 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "bsplib.h" + +// input: +// from bsplib.h: +// numleafs +// dleafs + +void EmitDistanceToWaterInfo( void ) +{ + int leafID; + for( leafID = 0; leafID < numleafs; leafID++ ) + { + dleaf_t *pLeaf = &dleafs[leafID]; + if( pLeaf->leafWaterDataID == -1 ) + { + // FIXME: set the distance to water to infinity here just in case. + continue; + } + + // Get the vis set for this leaf. + + } +} + diff --git a/mp/src/utils/vvis/flow.cpp b/mp/src/utils/vvis/flow.cpp new file mode 100644 index 00000000..7234e68a --- /dev/null +++ b/mp/src/utils/vvis/flow.cpp @@ -0,0 +1,881 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +#include "vis.h" +#include "vmpi.h" + +int g_TraceClusterStart = -1; +int g_TraceClusterStop = -1; +/* + + each portal will have a list of all possible to see from first portal + + if (!thread->portalmightsee[portalnum]) + + portal mightsee + + for p2 = all other portals in leaf + get sperating planes + for all portals that might be seen by p2 + mark as unseen if not present in seperating plane + flood fill a new mightsee + save as passagemightsee + + + void CalcMightSee (leaf_t *leaf, +*/ + +int CountBits (byte *bits, int numbits) +{ + int i; + int c; + + c = 0; + for (i=0 ; ipstack_head.next ; p ; p=p->next) + { +// Msg ("="); + if (p->leaf == leaf) + Error ("CheckStack: leaf recursion"); + for (p2=thread->pstack_head.next ; p2 != p ; p2=p2->next) + if (p2->leaf == p->leaf) + Error ("CheckStack: late leaf recursion"); + } +// Msg ("\n"); +} + + +winding_t *AllocStackWinding (pstack_t *stack) +{ + int i; + + for (i=0 ; i<3 ; i++) + { + if (stack->freewindings[i]) + { + stack->freewindings[i] = 0; + return &stack->windings[i]; + } + } + + Error ("Out of memory. AllocStackWinding: failed"); + + return NULL; +} + +void FreeStackWinding (winding_t *w, pstack_t *stack) +{ + int i; + + i = w - stack->windings; + + if (i<0 || i>2) + return; // not from local + + if (stack->freewindings[i]) + Error ("FreeStackWinding: allready free"); + stack->freewindings[i] = 1; +} + +/* +============== +ChopWinding + +============== +*/ + +#ifdef _WIN32 +#pragma warning (disable:4701) +#endif + +winding_t *ChopWinding (winding_t *in, pstack_t *stack, plane_t *split) +{ + vec_t dists[128]; + int sides[128]; + int counts[3]; + vec_t dot; + int i, j; + Vector mid; + winding_t *neww; + + counts[0] = counts[1] = counts[2] = 0; + +// determine sides for each point + for (i=0 ; inumpoints ; i++) + { + dot = DotProduct (in->points[i], split->normal); + dot -= split->dist; + dists[i] = dot; + if (dot > ON_VIS_EPSILON) + sides[i] = SIDE_FRONT; + else if (dot < -ON_VIS_EPSILON) + sides[i] = SIDE_BACK; + else + { + sides[i] = SIDE_ON; + } + counts[sides[i]]++; + } + + if (!counts[1]) + return in; // completely on front side + + if (!counts[0]) + { + FreeStackWinding (in, stack); + return NULL; + } + + sides[i] = sides[0]; + dists[i] = dists[0]; + + neww = AllocStackWinding (stack); + + neww->numpoints = 0; + + for (i=0 ; inumpoints ; i++) + { + Vector& p1 = in->points[i]; + + if (neww->numpoints == MAX_POINTS_ON_FIXED_WINDING) + { + FreeStackWinding (neww, stack); + return in; // can't chop -- fall back to original + } + + if (sides[i] == SIDE_ON) + { + VectorCopy (p1, neww->points[neww->numpoints]); + neww->numpoints++; + continue; + } + + if (sides[i] == SIDE_FRONT) + { + VectorCopy (p1, neww->points[neww->numpoints]); + neww->numpoints++; + } + + if (sides[i+1] == SIDE_ON || sides[i+1] == sides[i]) + continue; + + if (neww->numpoints == MAX_POINTS_ON_FIXED_WINDING) + { + FreeStackWinding (neww, stack); + return in; // can't chop -- fall back to original + } + + // generate a split point + Vector& p2 = in->points[(i+1)%in->numpoints]; + + dot = dists[i] / (dists[i]-dists[i+1]); + for (j=0 ; j<3 ; j++) + { // avoid round off error when possible + if (split->normal[j] == 1) + mid[j] = split->dist; + else if (split->normal[j] == -1) + mid[j] = -split->dist; + else + mid[j] = p1[j] + dot*(p2[j]-p1[j]); + } + + VectorCopy (mid, neww->points[neww->numpoints]); + neww->numpoints++; + } + +// free the original winding + FreeStackWinding (in, stack); + + return neww; +} + +#ifdef _WIN32 +#pragma warning (default:4701) +#endif + +/* +============== +ClipToSeperators + +Source, pass, and target are an ordering of portals. + +Generates seperating planes canidates by taking two points from source and one +point from pass, and clips target by them. + +If target is totally clipped away, that portal can not be seen through. + +Normal clip keeps target on the same side as pass, which is correct if the +order goes source, pass, target. If the order goes pass, source, target then +flipclip should be set. +============== +*/ +winding_t *ClipToSeperators (winding_t *source, winding_t *pass, winding_t *target, bool flipclip, pstack_t *stack) +{ + int i, j, k, l; + plane_t plane; + Vector v1, v2; + float d; + vec_t length; + int counts[3]; + bool fliptest; + +// check all combinations + for (i=0 ; inumpoints ; i++) + { + l = (i+1)%source->numpoints; + VectorSubtract (source->points[l] , source->points[i], v1); + + // fing a vertex of pass that makes a plane that puts all of the + // vertexes of pass on the front side and all of the vertexes of + // source on the back side + for (j=0 ; jnumpoints ; j++) + { + VectorSubtract (pass->points[j], source->points[i], v2); + + plane.normal[0] = v1[1]*v2[2] - v1[2]*v2[1]; + plane.normal[1] = v1[2]*v2[0] - v1[0]*v2[2]; + plane.normal[2] = v1[0]*v2[1] - v1[1]*v2[0]; + + // if points don't make a valid plane, skip it + + length = plane.normal[0] * plane.normal[0] + + plane.normal[1] * plane.normal[1] + + plane.normal[2] * plane.normal[2]; + + if (length < ON_VIS_EPSILON) + continue; + + length = 1/sqrt(length); + + plane.normal[0] *= length; + plane.normal[1] *= length; + plane.normal[2] *= length; + + plane.dist = DotProduct (pass->points[j], plane.normal); + + // + // find out which side of the generated seperating plane has the + // source portal + // +#if 1 + fliptest = false; + for (k=0 ; knumpoints ; k++) + { + if (k == i || k == l) + continue; + d = DotProduct (source->points[k], plane.normal) - plane.dist; + if (d < -ON_VIS_EPSILON) + { // source is on the negative side, so we want all + // pass and target on the positive side + fliptest = false; + break; + } + else if (d > ON_VIS_EPSILON) + { // source is on the positive side, so we want all + // pass and target on the negative side + fliptest = true; + break; + } + } + if (k == source->numpoints) + continue; // planar with source portal +#else + fliptest = flipclip; +#endif + // + // flip the normal if the source portal is backwards + // + if (fliptest) + { + VectorSubtract (vec3_origin, plane.normal, plane.normal); + plane.dist = -plane.dist; + } +#if 1 + // + // if all of the pass portal points are now on the positive side, + // this is the seperating plane + // + counts[0] = counts[1] = counts[2] = 0; + for (k=0 ; knumpoints ; k++) + { + if (k==j) + continue; + d = DotProduct (pass->points[k], plane.normal) - plane.dist; + if (d < -ON_VIS_EPSILON) + break; + else if (d > ON_VIS_EPSILON) + counts[0]++; + else + counts[2]++; + } + if (k != pass->numpoints) + continue; // points on negative side, not a seperating plane + + if (!counts[0]) + continue; // planar with seperating plane +#else + k = (j+1)%pass->numpoints; + d = DotProduct (pass->points[k], plane.normal) - plane.dist; + if (d < -ON_VIS_EPSILON) + continue; + k = (j+pass->numpoints-1)%pass->numpoints; + d = DotProduct (pass->points[k], plane.normal) - plane.dist; + if (d < -ON_VIS_EPSILON) + continue; +#endif + // + // flip the normal if we want the back side + // + if (flipclip) + { + VectorSubtract (vec3_origin, plane.normal, plane.normal); + plane.dist = -plane.dist; + } + + // + // clip target by the seperating plane + // + target = ChopWinding (target, stack, &plane); + if (!target) + return NULL; // target is not visible + + // JAY: End the loop, no need to find additional separators on this edge ? +// j = pass->numpoints; + } + } + + return target; +} + + +class CPortalTrace +{ +public: + CUtlVector m_list; + CThreadFastMutex m_mutex; +} g_PortalTrace; + +void WindingCenter (winding_t *w, Vector ¢er) +{ + int i; + float scale; + + VectorCopy (vec3_origin, center); + for (i=0 ; inumpoints ; i++) + VectorAdd (w->points[i], center, center); + + scale = 1.0/w->numpoints; + VectorScale (center, scale, center); +} + +Vector ClusterCenter( int cluster ) +{ + Vector mins, maxs; + ClearBounds(mins, maxs); + int count = leafs[cluster].portals.Count(); + for ( int i = 0; i < count; i++ ) + { + winding_t *w = leafs[cluster].portals[i]->winding; + for ( int j = 0; j < w->numpoints; j++ ) + { + AddPointToBounds( w->points[j], mins, maxs ); + } + } + return (mins + maxs) * 0.5f; +} + + +void DumpPortalTrace( pstack_t *pStack ) +{ + AUTO_LOCK_FM(g_PortalTrace.m_mutex); + if ( g_PortalTrace.m_list.Count() ) + return; + + Warning("Dumped cluster trace!!!\n"); + Vector mid; + mid = ClusterCenter( g_TraceClusterStart ); + g_PortalTrace.m_list.AddToTail(mid); + for ( ; pStack != NULL; pStack = pStack->next ) + { + winding_t *w = pStack->pass ? pStack->pass : pStack->portal->winding; + WindingCenter (w, mid); + g_PortalTrace.m_list.AddToTail(mid); + for ( int i = 0; i < w->numpoints; i++ ) + { + g_PortalTrace.m_list.AddToTail(w->points[i]); + g_PortalTrace.m_list.AddToTail(mid); + } + for ( int i = 0; i < w->numpoints; i++ ) + { + g_PortalTrace.m_list.AddToTail(w->points[i]); + } + g_PortalTrace.m_list.AddToTail(w->points[0]); + g_PortalTrace.m_list.AddToTail(mid); + } + mid = ClusterCenter( g_TraceClusterStop ); + g_PortalTrace.m_list.AddToTail(mid); +} + +void WritePortalTrace( const char *source ) +{ + Vector mid; + FILE *linefile; + char filename[1024]; + + if ( !g_PortalTrace.m_list.Count() ) + { + Warning("No trace generated from %d to %d\n", g_TraceClusterStart, g_TraceClusterStop ); + return; + } + + sprintf (filename, "%s.lin", source); + linefile = fopen (filename, "w"); + if (!linefile) + Error ("Couldn't open %s\n", filename); + + for ( int i = 0; i < g_PortalTrace.m_list.Count(); i++ ) + { + Vector p = g_PortalTrace.m_list[i]; + fprintf (linefile, "%f %f %f\n", p[0], p[1], p[2]); + } + fclose (linefile); + Warning("Wrote %s!!!\n", filename); +} + +/* +================== +RecursiveLeafFlow + +Flood fill through the leafs +If src_portal is NULL, this is the originating leaf +================== +*/ +void RecursiveLeafFlow (int leafnum, threaddata_t *thread, pstack_t *prevstack) +{ + pstack_t stack; + portal_t *p; + plane_t backplane; + leaf_t *leaf; + int i, j; + long *test, *might, *vis, more; + int pnum; + + // Early-out if we're a VMPI worker that's told to exit. If we don't do this here, then the + // worker might spin its wheels for a while on an expensive work unit and not be available to the pool. + // This is pretty common in vis. + if ( g_bVMPIEarlyExit ) + return; + + if ( leafnum == g_TraceClusterStop ) + { + DumpPortalTrace(&thread->pstack_head); + return; + } + thread->c_chains++; + + leaf = &leafs[leafnum]; + + prevstack->next = &stack; + + stack.next = NULL; + stack.leaf = leaf; + stack.portal = NULL; + + might = (long *)stack.mightsee; + vis = (long *)thread->base->portalvis; + + // check all portals for flowing into other leafs + for (i=0 ; iportals.Count() ; i++) + { + + p = leaf->portals[i]; + pnum = p - portals; + + if ( ! (prevstack->mightsee[pnum >> 3] & (1<<(pnum&7)) ) ) + { + continue; // can't possibly see it + } + + // if the portal can't see anything we haven't allready seen, skip it + if (p->status == stat_done) + { + test = (long *)p->portalvis; + } + else + { + test = (long *)p->portalflood; + } + + more = 0; + for (j=0 ; jmightsee)[j] & test[j]; + more |= (might[j] & ~vis[j]); + } + + if ( !more && CheckBit( thread->base->portalvis, pnum ) ) + { // can't see anything new + continue; + } + + // get plane of portal, point normal into the neighbor leaf + stack.portalplane = p->plane; + VectorSubtract (vec3_origin, p->plane.normal, backplane.normal); + backplane.dist = -p->plane.dist; + + stack.portal = p; + stack.next = NULL; + stack.freewindings[0] = 1; + stack.freewindings[1] = 1; + stack.freewindings[2] = 1; + + float d = DotProduct (p->origin, thread->pstack_head.portalplane.normal); + d -= thread->pstack_head.portalplane.dist; + if (d < -p->radius) + { + continue; + } + else if (d > p->radius) + { + stack.pass = p->winding; + } + else + { + stack.pass = ChopWinding (p->winding, &stack, &thread->pstack_head.portalplane); + if (!stack.pass) + continue; + } + + + d = DotProduct (thread->base->origin, p->plane.normal); + d -= p->plane.dist; + if (d > thread->base->radius) + { + continue; + } + else if (d < -thread->base->radius) + { + stack.source = prevstack->source; + } + else + { + stack.source = ChopWinding (prevstack->source, &stack, &backplane); + if (!stack.source) + continue; + } + + + if (!prevstack->pass) + { // the second leaf can only be blocked if coplanar + + // mark the portal as visible + SetBit( thread->base->portalvis, pnum ); + + RecursiveLeafFlow (p->leaf, thread, &stack); + continue; + } + + stack.pass = ClipToSeperators (stack.source, prevstack->pass, stack.pass, false, &stack); + if (!stack.pass) + continue; + + stack.pass = ClipToSeperators (prevstack->pass, stack.source, stack.pass, true, &stack); + if (!stack.pass) + continue; + + // mark the portal as visible + SetBit( thread->base->portalvis, pnum ); + + // flow through it for real + RecursiveLeafFlow (p->leaf, thread, &stack); + } +} + + +/* +=============== +PortalFlow + +generates the portalvis bit vector +=============== +*/ +void PortalFlow (int iThread, int portalnum) +{ + threaddata_t data; + int i; + portal_t *p; + int c_might, c_can; + + p = sorted_portals[portalnum]; + p->status = stat_working; + + c_might = CountBits (p->portalflood, g_numportals*2); + + memset (&data, 0, sizeof(data)); + data.base = p; + + data.pstack_head.portal = p; + data.pstack_head.source = p->winding; + data.pstack_head.portalplane = p->plane; + for (i=0 ; iportalflood)[i]; + + RecursiveLeafFlow (p->leaf, &data, &data.pstack_head); + + + p->status = stat_done; + + c_can = CountBits (p->portalvis, g_numportals*2); + + qprintf ("portal:%4i mightsee:%4i cansee:%4i (%i chains)\n", + (int)(p - portals), c_might, c_can, data.c_chains); +} + + +/* +=============================================================================== + +This is a rough first-order aproximation that is used to trivially reject some +of the final calculations. + + +Calculates portalfront and portalflood bit vectors + +=============================================================================== +*/ + +int c_flood, c_vis; + +/* +================== +SimpleFlood + +================== +*/ +void SimpleFlood (portal_t *srcportal, int leafnum) +{ + int i; + leaf_t *leaf; + portal_t *p; + int pnum; + + leaf = &leafs[leafnum]; + + for (i=0 ; iportals.Count(); i++) + { + p = leaf->portals[i]; + pnum = p - portals; + if ( !CheckBit( srcportal->portalfront, pnum ) ) + continue; + + if ( CheckBit( srcportal->portalflood, pnum ) ) + continue; + + SetBit( srcportal->portalflood, pnum ); + + SimpleFlood (srcportal, p->leaf); + } +} + +/* +============== +BasePortalVis +============== +*/ +void BasePortalVis (int iThread, int portalnum) +{ + int j, k; + portal_t *tp, *p; + float d; + winding_t *w; + Vector segment; + double dist2, minDist2; + + // get the portal + p = portals+portalnum; + + // + // allocate memory for bitwise vis solutions for this portal + // + p->portalfront = (byte*)malloc (portalbytes); + memset (p->portalfront, 0, portalbytes); + + p->portalflood = (byte*)malloc (portalbytes); + memset (p->portalflood, 0, portalbytes); + + p->portalvis = (byte*)malloc (portalbytes); + memset (p->portalvis, 0, portalbytes); + + // + // test the given portal against all of the portals in the map + // + for (j=0, tp = portals ; jwinding; + for (k=0 ; knumpoints ; k++) + { + d = DotProduct (w->points[k], p->plane.normal) - p->plane.dist; + if (d > ON_VIS_EPSILON) + break; + } + if (k == w->numpoints) + continue; // no points on front + + // + // + // + w = p->winding; + for (k=0 ; knumpoints ; k++) + { + d = DotProduct (w->points[k], tp->plane.normal) - tp->plane.dist; + if (d < -ON_VIS_EPSILON) + break; + } + if (k == w->numpoints) + continue; // no points on front + + // + // if using radius visibility -- check to see if any portal points lie inside of the + // radius given + // + if( g_bUseRadius ) + { + w = tp->winding; + minDist2 = 1024000000.0; // 32000^2 + for( k = 0; k < w->numpoints; k++ ) + { + VectorSubtract( w->points[k], p->origin, segment ); + dist2 = ( segment[0] * segment[0] ) + ( segment[1] * segment[1] ) + ( segment[2] * segment[2] ); + if( dist2 < minDist2 ) + { + minDist2 = dist2; + } + } + + if( minDist2 > g_VisRadius ) + continue; + } + + // add current portal to given portal's list of visible portals + SetBit( p->portalfront, j ); + } + + SimpleFlood (p, p->leaf); + + p->nummightsee = CountBits (p->portalflood, g_numportals*2); +// Msg ("portal %i: %i mightsee\n", portalnum, p->nummightsee); + c_flood += p->nummightsee; +} + + + + + +/* +=============================================================================== + +This is a second order aproximation + +Calculates portalvis bit vector + +WAAAAAAY too slow. + +=============================================================================== +*/ + +/* +================== +RecursiveLeafBitFlow + +================== +*/ +void RecursiveLeafBitFlow (int leafnum, byte *mightsee, byte *cansee) +{ + portal_t *p; + leaf_t *leaf; + int i, j; + long more; + int pnum; + byte newmight[MAX_PORTALS/8]; + + leaf = &leafs[leafnum]; + +// check all portals for flowing into other leafs + for (i=0 ; iportals.Count(); i++) + { + p = leaf->portals[i]; + pnum = p - portals; + + // if some previous portal can't see it, skip + if ( !CheckBit( mightsee, pnum ) ) + continue; + + // if this portal can see some portals we mightsee, recurse + more = 0; + for (j=0 ; jportalflood)[j]; + more |= ((long *)newmight)[j] & ~((long *)cansee)[j]; + } + + if (!more) + continue; // can't see anything new + + SetBit( cansee, pnum ); + + RecursiveLeafBitFlow (p->leaf, newmight, cansee); + } +} + +/* +============== +BetterPortalVis +============== +*/ +void BetterPortalVis (int portalnum) +{ + portal_t *p; + + p = portals+portalnum; + + RecursiveLeafBitFlow (p->leaf, p->portalflood, p->portalvis); + + // build leaf vis information + p->nummightsee = CountBits (p->portalvis, g_numportals*2); + c_vis += p->nummightsee; +} + + diff --git a/mp/src/utils/vvis/mpivis.cpp b/mp/src/utils/vvis/mpivis.cpp new file mode 100644 index 00000000..6da76ebc --- /dev/null +++ b/mp/src/utils/vvis/mpivis.cpp @@ -0,0 +1,640 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +#include +#include "vis.h" +#include "threads.h" +#include "stdlib.h" +#include "pacifier.h" +#include "mpi_stats.h" +#include "vmpi.h" +#include "vmpi_dispatch.h" +#include "vmpi_filesystem.h" +#include "vmpi_distribute_work.h" +#include "iphelpers.h" +#include "threadhelpers.h" +#include "vstdlib/random.h" +#include "vmpi_tools_shared.h" +#include +#include "scratchpad_helpers.h" + + +#define VMPI_VVIS_PACKET_ID 1 + // Sub packet IDs. + #define VMPI_SUBPACKETID_DISCONNECT_NOTIFY 3 // We send ourselves this when there is a disconnect. + #define VMPI_SUBPACKETID_BASEPORTALVIS 5 + #define VMPI_SUBPACKETID_PORTALFLOW 6 + #define VMPI_BASEPORTALVIS_RESULTS 7 + #define VMPI_BASEPORTALVIS_WORKER_DONE 8 + #define VMPI_PORTALFLOW_RESULTS 9 + #define VMPI_SUBPACKETID_BASEPORTALVIS_SYNC 11 + #define VMPI_SUBPACKETID_PORTALFLOW_SYNC 12 + #define VMPI_SUBPACKETID_MC_ADDR 13 + +// DistributeWork owns this packet ID. +#define VMPI_DISTRIBUTEWORK_PACKETID 2 + + +extern bool fastvis; + +// The worker waits until these are true. +bool g_bBasePortalVisSync = false; +bool g_bPortalFlowSync = false; + +CUtlVector g_BasePortalVisResultsFilename; + +CCycleCount g_CPUTime; + + +// This stuff is all for the multicast channel the master uses to send out the portal results. +ISocket *g_pPortalMCSocket = NULL; +CIPAddr g_PortalMCAddr; +bool g_bGotMCAddr = false; +HANDLE g_hMCThread = NULL; +CEvent g_MCThreadExitEvent; +unsigned long g_PortalMCThreadUniqueID = 0; +int g_nMulticastPortalsReceived = 0; + + +// Handle VVIS packets. +bool VVIS_DispatchFn( MessageBuffer *pBuf, int iSource, int iPacketID ) +{ + switch ( pBuf->data[1] ) + { + case VMPI_SUBPACKETID_MC_ADDR: + { + pBuf->setOffset( 2 ); + pBuf->read( &g_PortalMCAddr, sizeof( g_PortalMCAddr ) ); + g_bGotMCAddr = true; + return true; + } + + case VMPI_SUBPACKETID_DISCONNECT_NOTIFY: + { + // This is just used to cause nonblocking dispatches to jump out so loops like the one + // in AppBarrier can handle the fact that there are disconnects. + return true; + } + + case VMPI_SUBPACKETID_BASEPORTALVIS_SYNC: + { + g_bBasePortalVisSync = true; + return true; + } + + case VMPI_SUBPACKETID_PORTALFLOW_SYNC: + { + g_bPortalFlowSync = true; + return true; + } + + case VMPI_BASEPORTALVIS_RESULTS: + { + const char *pFilename = &pBuf->data[2]; + g_BasePortalVisResultsFilename.CopyArray( pFilename, strlen( pFilename ) + 1 ); + return true; + } + + default: + { + return false; + } + } +} +CDispatchReg g_VVISDispatchReg( VMPI_VVIS_PACKET_ID, VVIS_DispatchFn ); // register to handle the messages we want +CDispatchReg g_DistributeWorkReg( VMPI_DISTRIBUTEWORK_PACKETID, DistributeWorkDispatch ); + + + +void VMPI_DeletePortalMCSocket() +{ + // Stop the thread if it exists. + if ( g_hMCThread ) + { + g_MCThreadExitEvent.SetEvent(); + WaitForSingleObject( g_hMCThread, INFINITE ); + CloseHandle( g_hMCThread ); + g_hMCThread = NULL; + } + + if ( g_pPortalMCSocket ) + { + g_pPortalMCSocket->Release(); + g_pPortalMCSocket = NULL; + } +} + + +void VVIS_SetupMPI( int &argc, char **&argv ) +{ + if ( !VMPI_FindArg( argc, argv, "-mpi", "" ) && !VMPI_FindArg( argc, argv, VMPI_GetParamString( mpi_Worker ), "" ) ) + return; + + CmdLib_AtCleanup( VMPI_Stats_Term ); + CmdLib_AtCleanup( VMPI_DeletePortalMCSocket ); + + VMPI_Stats_InstallSpewHook(); + + // Force local mode? + VMPIRunMode mode; + if ( VMPI_FindArg( argc, argv, VMPI_GetParamString( mpi_Local ), "" ) ) + mode = VMPI_RUN_LOCAL; + else + mode = VMPI_RUN_NETWORKED; + + // + // Extract mpi specific arguments + // + Msg( "Initializing VMPI...\n" ); + if ( !VMPI_Init( argc, argv, "dependency_info_vvis.txt", HandleMPIDisconnect, mode ) ) + { + Error( "MPI_Init failed." ); + } + + StatsDB_InitStatsDatabase( argc, argv, "dbinfo_vvis.txt" ); +} + + +void ProcessBasePortalVis( int iThread, uint64 iPortal, MessageBuffer *pBuf ) +{ + CTimeAdder adder( &g_CPUTime ); + + BasePortalVis( iThread, iPortal ); + + // Send my result to the master + if ( pBuf ) + { + portal_t * p = &portals[iPortal]; + pBuf->write( p->portalfront, portalbytes ); + pBuf->write( p->portalflood, portalbytes ); + } +} + + +void ReceiveBasePortalVis( uint64 iWorkUnit, MessageBuffer *pBuf, int iWorker ) +{ + portal_t * p = &portals[iWorkUnit]; + if ( p->portalflood != 0 || p->portalfront != 0 || p->portalvis != 0) + { + Msg("Duplicate portal %llu\n", iWorkUnit); + } + + if ( pBuf->getLen() - pBuf->getOffset() != portalbytes*2 ) + Error( "Invalid packet in ReceiveBasePortalVis." ); + + // + // allocate memory for bitwise vis solutions for this portal + // + p->portalfront = (byte*)malloc (portalbytes); + pBuf->read( p->portalfront, portalbytes ); + + p->portalflood = (byte*)malloc (portalbytes); + pBuf->read( p->portalflood, portalbytes ); + + p->portalvis = (byte*)malloc (portalbytes); + memset (p->portalvis, 0, portalbytes); + + p->nummightsee = CountBits( p->portalflood, g_numportals*2 ); +} + + +//----------------------------------------- +// +// Run BasePortalVis across all available processing nodes +// Then collect and redistribute the results. +// +void RunMPIBasePortalVis() +{ + int i; + + Msg( "\n\nportalbytes: %d\nNum Work Units: %d\nTotal data size: %d\n", portalbytes, g_numportals*2, portalbytes*g_numportals*2 ); + Msg("%-20s ", "BasePortalVis:"); + if ( g_bMPIMaster ) + StartPacifier(""); + + + VMPI_SetCurrentStage( "RunMPIBasePortalVis" ); + + // Note: we're aiming for about 1500 portals in a map, so about 3000 work units. + g_CPUTime.Init(); + double elapsed = DistributeWork( + g_numportals * 2, // # work units + VMPI_DISTRIBUTEWORK_PACKETID, // packet ID + ProcessBasePortalVis, // Worker function to process work units + ReceiveBasePortalVis // Master function to receive work results + ); + + if ( g_bMPIMaster ) + { + EndPacifier( false ); + Msg( " (%d)\n", (int)elapsed ); + } + + // + // Distribute the results to all the workers. + // + if ( g_bMPIMaster ) + { + if ( !fastvis ) + { + VMPI_SetCurrentStage( "SendPortalResults" ); + + // Store all the portal results in a temp file and multicast that to the workers. + CUtlVector allPortalData; + allPortalData.SetSize( g_numportals * 2 * portalbytes * 2 ); + + char *pOut = allPortalData.Base(); + for ( i=0; i < g_numportals * 2; i++) + { + portal_t *p = &portals[i]; + + memcpy( pOut, p->portalfront, portalbytes ); + pOut += portalbytes; + + memcpy( pOut, p->portalflood, portalbytes ); + pOut += portalbytes; + } + + const char *pVirtualFilename = "--portal-results--"; + VMPI_FileSystem_CreateVirtualFile( pVirtualFilename, allPortalData.Base(), allPortalData.Count() ); + + char cPacketID[2] = { VMPI_VVIS_PACKET_ID, VMPI_BASEPORTALVIS_RESULTS }; + VMPI_Send2Chunks( cPacketID, sizeof( cPacketID ), pVirtualFilename, strlen( pVirtualFilename ) + 1, VMPI_PERSISTENT ); + } + } + else + { + VMPI_SetCurrentStage( "RecvPortalResults" ); + + // Wait until we've received the filename from the master. + while ( g_BasePortalVisResultsFilename.Count() == 0 ) + { + VMPI_DispatchNextMessage(); + } + + // Open + FileHandle_t fp = g_pFileSystem->Open( g_BasePortalVisResultsFilename.Base(), "rb", VMPI_VIRTUAL_FILES_PATH_ID ); + if ( !fp ) + Error( "Can't open '%s' to read portal info.", g_BasePortalVisResultsFilename.Base() ); + + for ( i=0; i < g_numportals * 2; i++) + { + portal_t *p = &portals[i]; + + p->portalfront = (byte*)malloc (portalbytes); + g_pFileSystem->Read( p->portalfront, portalbytes, fp ); + + p->portalflood = (byte*)malloc (portalbytes); + g_pFileSystem->Read( p->portalflood, portalbytes, fp ); + + p->portalvis = (byte*)malloc (portalbytes); + memset (p->portalvis, 0, portalbytes); + + p->nummightsee = CountBits (p->portalflood, g_numportals*2); + } + + g_pFileSystem->Close( fp ); + } + + + if ( !g_bMPIMaster ) + { + if ( g_iVMPIVerboseLevel >= 1 ) + Msg( "\n%% worker CPU utilization during BasePortalVis: %.1f\n", (g_CPUTime.GetSeconds() * 100.0f / elapsed) / numthreads ); + } +} + + + +void ProcessPortalFlow( int iThread, uint64 iPortal, MessageBuffer *pBuf ) +{ + // Process Portal and distribute results + CTimeAdder adder( &g_CPUTime ); + + PortalFlow( iThread, iPortal ); + + // Send my result to root and potentially the other slaves + // The slave results are read in RecursiveLeafFlow + // + if ( pBuf ) + { + portal_t * p = sorted_portals[iPortal]; + pBuf->write( p->portalvis, portalbytes ); + } +} + + +void ReceivePortalFlow( uint64 iWorkUnit, MessageBuffer *pBuf, int iWorker ) +{ + portal_t *p = sorted_portals[iWorkUnit]; + + if ( p->status != stat_done ) + { + pBuf->read( p->portalvis, portalbytes ); + p->status = stat_done; + + + // Multicast the status of this portal out. + if ( g_pPortalMCSocket ) + { + char cPacketID[2] = { VMPI_VVIS_PACKET_ID, VMPI_PORTALFLOW_RESULTS }; + void *chunks[4] = { cPacketID, &g_PortalMCThreadUniqueID, &iWorkUnit, p->portalvis }; + int chunkLengths[4] = { sizeof( cPacketID ), sizeof( g_PortalMCThreadUniqueID ), sizeof( iWorkUnit ), portalbytes }; + + g_pPortalMCSocket->SendChunksTo( &g_PortalMCAddr, chunks, chunkLengths, ARRAYSIZE( chunks ) ); + } + } +} + + +DWORD WINAPI PortalMCThreadFn( LPVOID p ) +{ + CUtlVector data; + data.SetSize( portalbytes + 128 ); + + DWORD waitTime = 0; + while ( WaitForSingleObject( g_MCThreadExitEvent.GetEventHandle(), waitTime ) != WAIT_OBJECT_0 ) + { + CIPAddr ipFrom; + int len = g_pPortalMCSocket->RecvFrom( data.Base(), data.Count(), &ipFrom ); + if ( len == -1 ) + { + waitTime = 20; + } + else + { + // These lengths must match exactly what is sent in ReceivePortalFlow. + if ( len == 2 + sizeof( g_PortalMCThreadUniqueID ) + sizeof( int ) + portalbytes ) + { + // Perform more validation... + if ( data[0] == VMPI_VVIS_PACKET_ID && data[1] == VMPI_PORTALFLOW_RESULTS ) + { + if ( *((unsigned long*)&data[2]) == g_PortalMCThreadUniqueID ) + { + int iWorkUnit = *((int*)&data[6]); + if ( iWorkUnit >= 0 && iWorkUnit < g_numportals*2 ) + { + portal_t *p = sorted_portals[iWorkUnit]; + if ( p ) + { + ++g_nMulticastPortalsReceived; + memcpy( p->portalvis, &data[10], portalbytes ); + p->status = stat_done; + waitTime = 0; + } + } + } + } + } + } + } + + return 0; +} + + +void MCThreadCleanupFn() +{ + g_MCThreadExitEvent.SetEvent(); +} + + +// --------------------------------------------------------------------------------- // +// Cheesy hack to let them stop the job early and keep the results of what has +// been done so far. +// --------------------------------------------------------------------------------- // + +class CVisDistributeWorkCallbacks : public IWorkUnitDistributorCallbacks +{ +public: + CVisDistributeWorkCallbacks() + { + m_bExitedEarly = false; + m_iState = STATE_NONE; + } + + virtual bool Update() + { + if ( kbhit() ) + { + int key = toupper( getch() ); + if ( m_iState == STATE_NONE ) + { + if ( key == 'M' ) + { + m_iState = STATE_AT_MENU; + Warning("\n\n" + "----------------------\n" + "1. Write scratchpad file.\n" + "2. Exit early and use fast vis for remaining portals.\n" + "\n" + "0. Exit menu.\n" + "----------------------\n" + "\n" + ); + } + } + else if ( m_iState == STATE_AT_MENU ) + { + if ( key == '1' ) + { + Warning( + "\n" + "\nWriting scratchpad file." + "\nCommand line: scratchpad3dviewer -file scratch.pad\n" + "\nRed portals are the portals that are fast vis'd." + "\n" + ); + m_iState = STATE_NONE; + IScratchPad3D *pPad = ScratchPad3D_Create( "scratch.pad" ); + if ( pPad ) + { + ScratchPad_DrawWorld( pPad, false ); + + // Draw the portals that haven't been vis'd. + for ( int i=0; i < g_numportals*2; i++ ) + { + portal_t *p = sorted_portals[i]; + ScratchPad_DrawWinding( pPad, p->winding->numpoints, p->winding->points, Vector( 1, 0, 0 ), Vector( .3, .3, .3 ) ); + } + + pPad->Release(); + } + } + else if ( key == '2' ) + { + // Exit the process early. + m_bExitedEarly = true; + return true; + } + else if ( key == '0' ) + { + m_iState = STATE_NONE; + Warning( "\n\nExited menu.\n\n" ); + } + } + } + + return false; + } + +public: + enum + { + STATE_NONE, + STATE_AT_MENU + }; + + bool m_bExitedEarly; + int m_iState; // STATE_ enum. +}; + + +CVisDistributeWorkCallbacks g_VisDistributeWorkCallbacks; + + +void CheckExitedEarly() +{ + if ( g_VisDistributeWorkCallbacks.m_bExitedEarly ) + { + Warning( "\nExited early, using fastvis results...\n" ); + Warning( "Exited early, using fastvis results...\n" ); + + // Use the fastvis results for portals that we didn't get results for. + for ( int i=0; i < g_numportals*2; i++ ) + { + if ( sorted_portals[i]->status != stat_done ) + { + sorted_portals[i]->portalvis = sorted_portals[i]->portalflood; + sorted_portals[i]->status = stat_done; + } + } + } +} + + +//----------------------------------------- +// +// Run PortalFlow across all available processing nodes +// +void RunMPIPortalFlow() +{ + Msg( "%-20s ", "MPIPortalFlow:" ); + if ( g_bMPIMaster ) + StartPacifier(""); + + // Workers wait until we get the MC socket address. + g_PortalMCThreadUniqueID = StatsDB_GetUniqueJobID(); + if ( g_bMPIMaster ) + { + CCycleCount cnt; + cnt.Sample(); + CUniformRandomStream randomStream; + randomStream.SetSeed( cnt.GetMicroseconds() ); + + g_PortalMCAddr.port = randomStream.RandomInt( 22000, 25000 ); // Pulled out of something else. + g_PortalMCAddr.ip[0] = (unsigned char)RandomInt( 225, 238 ); + g_PortalMCAddr.ip[1] = (unsigned char)RandomInt( 0, 255 ); + g_PortalMCAddr.ip[2] = (unsigned char)RandomInt( 0, 255 ); + g_PortalMCAddr.ip[3] = (unsigned char)RandomInt( 3, 255 ); + + g_pPortalMCSocket = CreateIPSocket(); + int i=0; + for ( i; i < 5; i++ ) + { + if ( g_pPortalMCSocket->BindToAny( randomStream.RandomInt( 20000, 30000 ) ) ) + break; + } + if ( i == 5 ) + { + Error( "RunMPIPortalFlow: can't open a socket to multicast on." ); + } + + char cPacketID[2] = { VMPI_VVIS_PACKET_ID, VMPI_SUBPACKETID_MC_ADDR }; + VMPI_Send2Chunks( cPacketID, sizeof( cPacketID ), &g_PortalMCAddr, sizeof( g_PortalMCAddr ), VMPI_PERSISTENT ); + } + else + { + VMPI_SetCurrentStage( "wait for MC address" ); + + while ( !g_bGotMCAddr ) + { + VMPI_DispatchNextMessage(); + } + + // Open our multicast receive socket. + g_pPortalMCSocket = CreateMulticastListenSocket( g_PortalMCAddr ); + if ( !g_pPortalMCSocket ) + { + char err[512]; + IP_GetLastErrorString( err, sizeof( err ) ); + Error( "RunMPIPortalFlow: CreateMulticastListenSocket failed. (%s).", err ); + } + + // Make a thread to listen for the data on the multicast socket. + DWORD dwDummy = 0; + g_MCThreadExitEvent.Init( false, false ); + + // Make sure we kill the MC thread if the app exits ungracefully. + CmdLib_AtCleanup( MCThreadCleanupFn ); + + g_hMCThread = CreateThread( + NULL, + 0, + PortalMCThreadFn, + NULL, + 0, + &dwDummy ); + + if ( !g_hMCThread ) + { + Error( "RunMPIPortalFlow: CreateThread failed for multicast receive thread." ); + } + } + + VMPI_SetCurrentStage( "RunMPIBasePortalFlow" ); + + + g_pDistributeWorkCallbacks = &g_VisDistributeWorkCallbacks; + + g_CPUTime.Init(); + double elapsed = DistributeWork( + g_numportals * 2, // # work units + VMPI_DISTRIBUTEWORK_PACKETID, // packet ID + ProcessPortalFlow, // Worker function to process work units + ReceivePortalFlow // Master function to receive work results + ); + + g_pDistributeWorkCallbacks = NULL; + + CheckExitedEarly(); + + // Stop the multicast stuff. + VMPI_DeletePortalMCSocket(); + + if( !g_bMPIMaster ) + { + if ( g_iVMPIVerboseLevel >= 1 ) + { + Msg( "Received %d (out of %d) portals from multicast.\n", g_nMulticastPortalsReceived, g_numportals * 2 ); + Msg( "%.1f%% CPU utilization during PortalFlow\n", (g_CPUTime.GetSeconds() * 100.0f / elapsed) / numthreads ); + } + + Msg( "VVIS worker finished. Over and out.\n" ); + VMPI_SetCurrentStage( "worker done" ); + + CmdLib_Exit( 0 ); + } + + if ( g_bMPIMaster ) + { + EndPacifier( false ); + Msg( " (%d)\n", (int)elapsed ); + } +} + diff --git a/mp/src/utils/vvis/mpivis.h b/mp/src/utils/vvis/mpivis.h new file mode 100644 index 00000000..a6ee349e --- /dev/null +++ b/mp/src/utils/vvis/mpivis.h @@ -0,0 +1,21 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef MPIVIS_H +#define MPIVIS_H +#ifdef _WIN32 +#pragma once +#endif + + +void VVIS_SetupMPI( int &argc, char **&argv ); + + +void RunMPIBasePortalVis(); +void RunMPIPortalFlow(); + + +#endif // MPIVIS_H diff --git a/mp/src/utils/vvis/vis.h b/mp/src/utils/vvis/vis.h new file mode 100644 index 00000000..c4b58414 --- /dev/null +++ b/mp/src/utils/vvis/vis.h @@ -0,0 +1,125 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// vis.h + +#include "cmdlib.h" +#include "mathlib/mathlib.h" +#include "bsplib.h" + + +#define MAX_PORTALS 65536 + +#define PORTALFILE "PRT1" + +extern bool g_bUseRadius; // prototyping TF2, "radius vis" solution +extern double g_VisRadius; // the radius for the TF2 "radius vis" + +struct plane_t +{ + Vector normal; + float dist; +}; + +#define MAX_POINTS_ON_WINDING 64 +#define MAX_POINTS_ON_FIXED_WINDING 12 + +struct winding_t +{ + qboolean original; // don't free, it's part of the portal + int numpoints; + Vector points[MAX_POINTS_ON_FIXED_WINDING]; // variable sized +}; + +winding_t *NewWinding (int points); +void FreeWinding (winding_t *w); +winding_t *CopyWinding (winding_t *w); + + +typedef enum {stat_none, stat_working, stat_done} vstatus_t; +struct portal_t +{ + plane_t plane; // normal pointing into neighbor + int leaf; // neighbor + + Vector origin; // for fast clip testing + float radius; + + winding_t *winding; + vstatus_t status; + byte *portalfront; // [portals], preliminary + byte *portalflood; // [portals], intermediate + byte *portalvis; // [portals], final + + int nummightsee; // bit count on portalflood for sort +}; + +struct leaf_t +{ + CUtlVector portals; +}; + + +struct pstack_t +{ + byte mightsee[MAX_PORTALS/8]; // bit string + pstack_t *next; + leaf_t *leaf; + portal_t *portal; // portal exiting + winding_t *source; + winding_t *pass; + + winding_t windings[3]; // source, pass, temp in any order + int freewindings[3]; + + plane_t portalplane; +}; + +struct threaddata_t +{ + portal_t *base; + int c_chains; + pstack_t pstack_head; +}; + +extern int g_numportals; +extern int portalclusters; + +extern portal_t *portals; +extern leaf_t *leafs; + +extern int c_portaltest, c_portalpass, c_portalcheck; +extern int c_portalskip, c_leafskip; +extern int c_vistest, c_mighttest; +extern int c_chains; + +extern byte *vismap, *vismap_p, *vismap_end; // past visfile + +extern int testlevel; + +extern byte *uncompressed; + +extern int leafbytes, leaflongs; +extern int portalbytes, portallongs; + + +void LeafFlow (int leafnum); + + +void BasePortalVis (int iThread, int portalnum); +void BetterPortalVis (int portalnum); +void PortalFlow (int iThread, int portalnum); +void WritePortalTrace( const char *source ); + +extern portal_t *sorted_portals[MAX_MAP_PORTALS*2]; +extern int g_TraceClusterStart, g_TraceClusterStop; + +int CountBits (byte *bits, int numbits); + +#define CheckBit( bitstring, bitNumber ) ( (bitstring)[ ((bitNumber) >> 3) ] & ( 1 << ( (bitNumber) & 7 ) ) ) +#define SetBit( bitstring, bitNumber ) ( (bitstring)[ ((bitNumber) >> 3) ] |= ( 1 << ( (bitNumber) & 7 ) ) ) +#define ClearBit( bitstring, bitNumber ) ( (bitstring)[ ((bitNumber) >> 3) ] &= ~( 1 << ( (bitNumber) & 7 ) ) ) diff --git a/mp/src/utils/vvis/vvis.cpp b/mp/src/utils/vvis/vvis.cpp new file mode 100644 index 00000000..577c1cc3 --- /dev/null +++ b/mp/src/utils/vvis/vvis.cpp @@ -0,0 +1,1240 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// vis.c + +#include +#include "vis.h" +#include "threads.h" +#include "stdlib.h" +#include "pacifier.h" +#include "vmpi.h" +#include "mpivis.h" +#include "tier1/strtools.h" +#include "collisionutils.h" +#include "tier0/icommandline.h" +#include "vmpi_tools_shared.h" +#include "ilaunchabledll.h" +#include "tools_minidump.h" +#include "loadcmdline.h" +#include "byteswap.h" + + +int g_numportals; +int portalclusters; + +char inbase[32]; + +portal_t *portals; +leaf_t *leafs; + +int c_portaltest, c_portalpass, c_portalcheck; + +byte *uncompressedvis; + +byte *vismap, *vismap_p, *vismap_end; // past visfile +int originalvismapsize; + +int leafbytes; // (portalclusters+63)>>3 +int leaflongs; + +int portalbytes, portallongs; + +bool fastvis; +bool nosort; + +int totalvis; + +portal_t *sorted_portals[MAX_MAP_PORTALS*2]; + +bool g_bUseRadius = false; +double g_VisRadius = 4096.0f * 4096.0f; + +bool g_bLowPriority = false; + +//============================================================================= + +void PlaneFromWinding (winding_t *w, plane_t *plane) +{ + Vector v1, v2; + +// calc plane + VectorSubtract (w->points[2], w->points[1], v1); + VectorSubtract (w->points[0], w->points[1], v2); + CrossProduct (v2, v1, plane->normal); + VectorNormalize (plane->normal); + plane->dist = DotProduct (w->points[0], plane->normal); +} + + +/* +================== +NewWinding +================== +*/ +winding_t *NewWinding (int points) +{ + winding_t *w; + int size; + + if (points > MAX_POINTS_ON_WINDING) + Error ("NewWinding: %i points, max %d", points, MAX_POINTS_ON_WINDING); + + size = (int)(&((winding_t *)0)->points[points]); + w = (winding_t*)malloc (size); + memset (w, 0, size); + + return w; +} + +void pw(winding_t *w) +{ + int i; + for (i=0 ; inumpoints ; i++) + Msg ("(%5.1f, %5.1f, %5.1f)\n",w->points[i][0], w->points[i][1],w->points[i][2]); +} + +void prl(leaf_t *l) +{ + int i; + portal_t *p; + plane_t pl; + + int count = l->portals.Count(); + for (i=0 ; iportals[i]; + pl = p->plane; + Msg ("portal %4i to leaf %4i : %7.1f : (%4.1f, %4.1f, %4.1f)\n",(int)(p-portals),p->leaf,pl.dist, pl.normal[0], pl.normal[1], pl.normal[2]); + } +} + + +//============================================================================= + +/* +============= +SortPortals + +Sorts the portals from the least complex, so the later ones can reuse +the earlier information. +============= +*/ +int PComp (const void *a, const void *b) +{ + if ( (*(portal_t **)a)->nummightsee == (*(portal_t **)b)->nummightsee) + return 0; + if ( (*(portal_t **)a)->nummightsee < (*(portal_t **)b)->nummightsee) + return -1; + + return 1; +} + +void BuildTracePortals( int clusterStart ) +{ + leaf_t *leaf = &leafs[g_TraceClusterStart]; + g_numportals = leaf->portals.Count(); + for ( int i = 0; i < g_numportals; i++ ) + { + sorted_portals[i] = leaf->portals[i]; + } +} + +void SortPortals (void) +{ + int i; + + for (i=0 ; ileaf ); + } + } + + c_leafs = CountBits (leafbits, portalclusters); + + return c_leafs; +} + + +/* +=============== +ClusterMerge + +Merges the portal visibility for a leaf +=============== +*/ +void ClusterMerge (int clusternum) +{ + leaf_t *leaf; +// byte portalvector[MAX_PORTALS/8]; + byte portalvector[MAX_PORTALS/4]; // 4 because portal bytes is * 2 + byte uncompressed[MAX_MAP_LEAFS/8]; + int i, j; + int numvis; + portal_t *p; + int pnum; + + // OR together all the portalvis bits + + memset (portalvector, 0, portalbytes); + leaf = &leafs[clusternum]; + for (i=0 ; i < leaf->portals.Count(); i++) + { + p = leaf->portals[i]; + if (p->status != stat_done) + Error ("portal not done %d %p %p\n", i, p, portals); + for (j=0 ; jportalvis)[j]; + pnum = p - portals; + SetBit( portalvector, pnum ); + } + + // convert portal bits to leaf bits + numvis = LeafVectorFromPortalVector (portalvector, uncompressed); + +#if 0 + // func_viscluster makes this happen all the time because it allows a non-convex set of portals + // My analysis says this is ok, but it does make this check for errors in vis kind of useless + if ( CheckBit( uncompressed, clusternum ) ) + Warning("WARNING: Cluster portals saw into cluster\n"); +#endif + + SetBit( uncompressed, clusternum ); + numvis++; // count the leaf itself + + // save uncompressed for PHS calculation + memcpy (uncompressedvis + clusternum*leafbytes, uncompressed, leafbytes); + + qprintf ("cluster %4i : %4i visible\n", clusternum, numvis); + totalvis += numvis; +} + +static int CompressAndCrosscheckClusterVis( int clusternum ) +{ + int optimized = 0; + byte compressed[MAX_MAP_LEAFS/8]; +// +// compress the bit string +// + byte *uncompressed = uncompressedvis + clusternum*leafbytes; + for ( int i = 0; i < portalclusters; i++ ) + { + if ( i == clusternum ) + continue; + + if ( CheckBit( uncompressed, i ) ) + { + byte *other = uncompressedvis + i*leafbytes; + if ( !CheckBit( other, clusternum ) ) + { + ClearBit( uncompressed, i ); + optimized++; + } + } + } + int numbytes = CompressVis( uncompressed, compressed ); + + byte *dest = vismap_p; + vismap_p += numbytes; + + if (vismap_p > vismap_end) + Error ("Vismap expansion overflow"); + + dvis->bitofs[clusternum][DVIS_PVS] = dest-vismap; + + memcpy( dest, compressed, numbytes ); + + // check vis data + DecompressVis( vismap + dvis->bitofs[clusternum][DVIS_PVS], compressed ); + + return optimized; +} + + +/* +================== +CalcPortalVis +================== +*/ +void CalcPortalVis (void) +{ + int i; + + // fastvis just uses mightsee for a very loose bound + if( fastvis ) + { + for (i=0 ; iwinding; + VectorCopy (vec3_origin, total); + for (i=0 ; inumpoints ; i++) + { + VectorAdd (total, w->points[i], total); + } + + for (i=0 ; i<3 ; i++) + total[i] /= w->numpoints; + + bestr = 0; + for (i=0 ; inumpoints ; i++) + { + VectorSubtract (w->points[i], total, dist); + r = VectorLength (dist); + if (r > bestr) + bestr = r; + } + VectorCopy (total, p->origin); + p->radius = bestr; +} + +/* +============ +LoadPortals +============ +*/ +void LoadPortals (char *name) +{ + int i, j; + portal_t *p; + leaf_t *l; + char magic[80]; + int numpoints; + winding_t *w; + int leafnums[2]; + plane_t plane; + + FILE *f; + + // Open the portal file. + if ( g_bUseMPI ) + { + // If we're using MPI, copy off the file to a temporary first. This will download the file + // from the MPI master, then we get to use nice functions like fscanf on it. + char tempPath[MAX_PATH], tempFile[MAX_PATH]; + if ( GetTempPath( sizeof( tempPath ), tempPath ) == 0 ) + { + Error( "LoadPortals: GetTempPath failed.\n" ); + } + + if ( GetTempFileName( tempPath, "vvis_portal_", 0, tempFile ) == 0 ) + { + Error( "LoadPortals: GetTempFileName failed.\n" ); + } + + // Read all the data from the network file into memory. + FileHandle_t hFile = g_pFileSystem->Open(name, "r"); + if ( hFile == FILESYSTEM_INVALID_HANDLE ) + Error( "LoadPortals( %s ): couldn't get file from master.\n", name ); + + CUtlVector data; + data.SetSize( g_pFileSystem->Size( hFile ) ); + g_pFileSystem->Read( data.Base(), data.Count(), hFile ); + g_pFileSystem->Close( hFile ); + + // Dump it into a temp file. + f = fopen( tempFile, "wt" ); + fwrite( data.Base(), 1, data.Count(), f ); + fclose( f ); + + // Open the temp file up. + f = fopen( tempFile, "rSTD" ); // read only, sequential, temporary, delete on close + } + else + { + f = fopen( name, "r" ); + } + + if ( !f ) + Error ("LoadPortals: couldn't read %s\n",name); + + if (fscanf (f,"%79s\n%i\n%i\n",magic, &portalclusters, &g_numportals) != 3) + Error ("LoadPortals %s: failed to read header", name); + if (stricmp(magic,PORTALFILE)) + Error ("LoadPortals %s: not a portal file", name); + + Msg ("%4i portalclusters\n", portalclusters); + Msg ("%4i numportals\n", g_numportals); + + if (g_numportals * 2 >= MAX_PORTALS) + { + Error("The map overflows the max portal count (%d of max %d)!\n", g_numportals, MAX_PORTALS / 2 ); + } + + // these counts should take advantage of 64 bit systems automatically + leafbytes = ((portalclusters+63)&~63)>>3; + leaflongs = leafbytes/sizeof(long); + + portalbytes = ((g_numportals*2+63)&~63)>>3; + portallongs = portalbytes/sizeof(long); + +// each file portal is split into two memory portals + portals = (portal_t*)malloc(2*g_numportals*sizeof(portal_t)); + memset (portals, 0, 2*g_numportals*sizeof(portal_t)); + + leafs = (leaf_t*)malloc(portalclusters*sizeof(leaf_t)); + memset (leafs, 0, portalclusters*sizeof(leaf_t)); + + originalvismapsize = portalclusters*leafbytes; + uncompressedvis = (byte*)malloc(originalvismapsize); + + vismap = vismap_p = dvisdata; + dvis->numclusters = portalclusters; + vismap_p = (byte *)&dvis->bitofs[portalclusters]; + + vismap_end = vismap + MAX_MAP_VISIBILITY; + + for (i=0, p=portals ; i MAX_POINTS_ON_WINDING) + Error ("LoadPortals: portal %i has too many points", i); + if ( (unsigned)leafnums[0] > portalclusters + || (unsigned)leafnums[1] > portalclusters) + Error ("LoadPortals: reading portal %i", i); + + w = p->winding = NewWinding (numpoints); + w->original = true; + w->numpoints = numpoints; + + for (j=0 ; jpoints[j][k] = v[k]; + } + fscanf (f, "\n"); + + // calc plane + PlaneFromWinding (w, &plane); + + // create forward portal + l = &leafs[leafnums[0]]; + l->portals.AddToTail(p); + + p->winding = w; + VectorSubtract (vec3_origin, plane.normal, p->plane.normal); + p->plane.dist = -plane.dist; + p->leaf = leafnums[1]; + SetPortalSphere (p); + p++; + + // create backwards portal + l = &leafs[leafnums[1]]; + l->portals.AddToTail(p); + + p->winding = NewWinding(w->numpoints); + p->winding->numpoints = w->numpoints; + for (j=0 ; jnumpoints ; j++) + { + VectorCopy (w->points[w->numpoints-1-j], p->winding->points[j]); + } + + p->plane = plane; + p->leaf = leafnums[0]; + SetPortalSphere (p); + p++; + + } + + fclose (f); +} + + +/* +================ +CalcPAS + +Calculate the PAS (Potentially Audible Set) +by ORing together all the PVS visible from a leaf +================ +*/ +void CalcPAS (void) +{ + int i, j, k, l, index; + int bitbyte; + long *dest, *src; + byte *scan; + int count; + byte uncompressed[MAX_MAP_LEAFS/8]; + byte compressed[MAX_MAP_LEAFS/8]; + + Msg ("Building PAS...\n"); + + count = 0; + for (i=0 ; i= portalclusters) + Error ("Bad bit in PVS"); // pad bits should be 0 + src = (long *)(uncompressedvis + index*leafbytes); + dest = (long *)uncompressed; + for (l=0 ; l vismap_end) + Error ("Vismap expansion overflow"); + + dvis->bitofs[i][DVIS_PAS] = (byte *)dest-vismap; + + memcpy (dest, compressed, j); + } + + Msg ("Average clusters audible: %i\n", count/portalclusters); +} + + + +static void GetBoundsForFace( int faceID, Vector &faceMin, Vector &faceMax ) +{ + ClearBounds( faceMin, faceMax ); + dface_t *pFace = &dfaces[faceID]; + int i; + for( i = pFace->firstedge; i < pFace->firstedge + pFace->numedges; i++ ) + { + int edgeID = dsurfedges[i]; + if( edgeID < 0 ) + { + edgeID = -edgeID; + } + dedge_t *pEdge = &dedges[edgeID]; + dvertex_t *pVert0 = &dvertexes[pEdge->v[0]]; + dvertex_t *pVert1 = &dvertexes[pEdge->v[1]]; + AddPointToBounds( pVert0->point, faceMin, faceMax ); + AddPointToBounds( pVert1->point, faceMin, faceMax ); + } +} + +// FIXME: should stick this in mathlib +static float GetMinDistanceBetweenBoundingBoxes( const Vector &min1, const Vector &max1, + const Vector &min2, const Vector &max2 ) +{ + if( IsBoxIntersectingBox( min1, max1, min2, max2 ) ) + { + return 0.0f; + } + + Vector axisDist; + int i; + for( i = 0; i < 3; i++ ) + { + if( min1[i] <= max2[i] && max1[i] >= min2[i] ) + { + // the intersection in this dimension. + axisDist[i] = 0.0f; + } + else + { + float dist1, dist2; + dist1 = min1[i] - max2[i]; + dist2 = min2[i] - max1[i]; + axisDist[i] = dist1 > dist2 ? dist1 : dist2; + Assert( axisDist[i] > 0.0f ); + } + } + + float mag = axisDist.Length(); + Assert( mag > 0.0f ); + return mag; +} + +static float CalcDistanceFromLeafToWater( int leafNum ) +{ + byte uncompressed[MAX_MAP_LEAFS/8]; + + int j, k; + + // If we know that this one doesn't see a water surface then don't bother doing anything. + if( ((dleafs[leafNum].contents & CONTENTS_TESTFOGVOLUME) == 0) && ( dleafs[leafNum].leafWaterDataID == -1 ) ) + return 65535; // FIXME: make a define for this. + + // First get the vis data.. + int cluster = dleafs[leafNum].cluster; + if (cluster < 0) + return 65535; // FIXME: make a define for this. + + DecompressVis( &dvisdata[dvis->bitofs[cluster][DVIS_PVS]], uncompressed ); + + float minDist = 65535.0f; // FIXME: make a define for this. + + Vector leafMin, leafMax; + + leafMin[0] = ( float )dleafs[leafNum].mins[0]; + leafMin[1] = ( float )dleafs[leafNum].mins[1]; + leafMin[2] = ( float )dleafs[leafNum].mins[2]; + leafMax[0] = ( float )dleafs[leafNum].maxs[0]; + leafMax[1] = ( float )dleafs[leafNum].maxs[1]; + leafMax[2] = ( float )dleafs[leafNum].maxs[2]; + +/* + CUtlVector temp; + + // build a convex solid out of the planes so that we can get at the triangles. + for( j = dleafs[i].firstleafbrush; j < dleafs[i].firstleafbrush + dleafs[i].numleafbrushes; j++ ) + { + dbrush_t *pBrush = &dbrushes[j]; + for( k = pBrush->firstside; k < pBrush->firstside + pBrush->numsides; k++ ) + { + dbrushside_t *pside = dbrushsides + k; + dplane_t *pplane = dplanes + pside->planenum; + AddListPlane( &temp, pplane->normal[0], pplane->normal[1], pplane->normal[2], pplane->dist ); + } + CPhysConvex *pConvex = physcollision->ConvexFromPlanes( (float *)temp.Base(), temp.Count(), VPHYSICS_MERGE ); + ConvertConvexToCollide( &pConvex, + temp.RemoveAll(); + } +*/ + + // Iterate over all potentially visible clusters from this leaf + for (j = 0; j < dvis->numclusters; ++j) + { + // Don't need to bother if this is the same as the current cluster + if (j == cluster) + continue; + + // If the cluster isn't in our current pvs, then get out of here. + if ( !CheckBit( uncompressed, j ) ) + continue; + + // Found a visible cluster, now iterate over all leaves + // inside that cluster + for (k = 0; k < g_ClusterLeaves[j].leafCount; ++k) + { + int nClusterLeaf = g_ClusterLeaves[j].leafs[k]; + + // Don't bother testing the ones that don't see a water boundary. + if( ((dleafs[nClusterLeaf].contents & CONTENTS_TESTFOGVOLUME) == 0) && ( dleafs[nClusterLeaf].leafWaterDataID == -1 ) ) + continue; + + // Find the minimum distance between each surface on the boundary of the leaf + // that we have the pvs for and each water surface in the leaf that we are testing. + int nFirstFaceID = dleafs[nClusterLeaf].firstleafface; + for( int leafFaceID = 0; leafFaceID < dleafs[nClusterLeaf].numleaffaces; ++leafFaceID ) + { + int faceID = dleaffaces[nFirstFaceID + leafFaceID]; + dface_t *pFace = &dfaces[faceID]; + if( pFace->texinfo == -1 ) + continue; + + texinfo_t *pTexInfo = &texinfo[pFace->texinfo]; + if( pTexInfo->flags & SURF_WARP ) + { + // Woo hoo!!! We found a water face. + // compare the bounding box of the face with the bounding + // box of the leaf that we are looking from and see + // what the closest distance is. + // FIXME: this could be a face/face distance between the water + // face and the bounding volume of the leaf. + + // Get the bounding box of the face + Vector faceMin, faceMax; + GetBoundsForFace( faceID, faceMin, faceMax ); + float dist = GetMinDistanceBetweenBoundingBoxes( leafMin, leafMax, faceMin, faceMax ); + if( dist < minDist ) + { + minDist = dist; + } + } + } + } + } + return minDist; +} + +static void CalcDistanceFromLeavesToWater( void ) +{ + int i; + for( i = 0; i < numleafs; i++ ) + { + g_LeafMinDistToWater[i] = ( unsigned short )CalcDistanceFromLeafToWater( i ); + } +} + +//----------------------------------------------------------------------------- +// Using the PVS, compute the visible fog volumes from each leaf +//----------------------------------------------------------------------------- +static void CalcVisibleFogVolumes() +{ + byte uncompressed[MAX_MAP_LEAFS/8]; + + int i, j, k; + + // Clear the contents flags for water testing + for (i = 0; i < numleafs; ++i) + { + dleafs[i].contents &= ~CONTENTS_TESTFOGVOLUME; + g_LeafMinDistToWater[i] = 65535; + } + + for (i = 0; i < numleafs; ++i) + { + // If we've already discovered that this leaf needs testing, + // no need to go through the work again... + if (dleafs[i].contents & CONTENTS_TESTFOGVOLUME) + { + Assert((dleafs[i].contents & (CONTENTS_SLIME | CONTENTS_WATER)) == 0); + continue; + } + + // Don't bother checking fog volumes from solid leaves + if (dleafs[i].contents & CONTENTS_SOLID) + continue; + + // Look only for leaves which are visible from leaves that have fluid in them. + if ( dleafs[i].leafWaterDataID == -1 ) + continue; + + // Don't bother about looking from CONTENTS_SLIME; we're not going to treat that as interesting. + // because slime is opaque + if ( dleafs[i].contents & CONTENTS_SLIME ) + continue; + + // First get the vis data.. + int cluster = dleafs[i].cluster; + if (cluster < 0) + continue; + + DecompressVis( &dvisdata[dvis->bitofs[cluster][DVIS_PVS]], uncompressed ); + + // Iterate over all potentially visible clusters from this leaf + for (j = 0; j < dvis->numclusters; ++j) + { + // Don't need to bother if this is the same as the current cluster + if (j == cluster) + continue; + + if ( !CheckBit( uncompressed, j ) ) + continue; + + // Found a visible cluster, now iterate over all leaves + // inside that cluster + for (k = 0; k < g_ClusterLeaves[j].leafCount; ++k) + { + int nClusterLeaf = g_ClusterLeaves[j].leafs[k]; + + // Don't bother checking fog volumes from solid leaves + if ( dleafs[nClusterLeaf].contents & CONTENTS_SOLID ) + continue; + + // Don't bother checking from any leaf that's got fluid in it + if ( dleafs[nClusterLeaf].leafWaterDataID != -1 ) + continue; + + // Here, we've found a case where a non-liquid leaf is visible from a liquid leaf + // So, in this case, we have to do the expensive test during rendering. + dleafs[nClusterLeaf].contents |= CONTENTS_TESTFOGVOLUME; + } + } + } +} + + +//----------------------------------------------------------------------------- +// Compute the bounding box, excluding 3D skybox + skybox, add it to keyvalues +//----------------------------------------------------------------------------- +float DetermineVisRadius( ) +{ + float flRadius = -1; + + // Check the max vis range to determine the vis radius + for (int i = 0; i < num_entities; ++i) + { + char* pEntity = ValueForKey(&entities[i], "classname"); + if (!stricmp(pEntity, "env_fog_controller")) + { + flRadius = FloatForKey (&entities[i], "farz"); + if (flRadius == 0.0f) + flRadius = -1.0f; + break; + } + } + + return flRadius; +} + +void MarkLeavesAsRadial() +{ + for ( int i = 0; i < numleafs; i++ ) + { + dleafs[i].flags |= LEAF_FLAGS_RADIAL; + } +} + + +int ParseCommandLine( int argc, char **argv ) +{ + int i; + for (i=1 ; i : Override the VPROJECT environment variable.\n" + " -game : Same as -vproject.\n" + "\n" + "Other options:\n" + " -novconfig : Don't bring up graphical UI on vproject errors.\n" + " -radius_override: Force a vis radius, regardless of whether an\n" + " -mpi_pw : Use a password to choose a specific set of VMPI workers.\n" + " -threads : Control the number of threads vbsp uses (defaults to the #\n" + " or processors on your machine).\n" + " -nosort : Don't sort portals (sorting is an optimization).\n" + " -tmpin : Make portals come from \\tmp\\.\n" + " -tmpout : Make portals come from \\tmp\\.\n" + " -trace : Writes a linefile that traces the vis from one cluster to another for debugging map vis.\n" + " -FullMinidumps : Write large minidumps on crash.\n" + " -x360 : Generate Xbox360 version of vsp\n" + " -nox360 : Disable generation Xbox360 version of vsp (default)\n" + "\n" +#if 1 // Disabled for the initial SDK release with VMPI so we can get feedback from selected users. + ); +#else + " -mpi_ListParams : Show a list of VMPI parameters.\n" + "\n" + ); + + // Show VMPI parameters? + for ( int i=1; i < argc; i++ ) + { + if ( V_stricmp( argv[i], "-mpi_ListParams" ) == 0 ) + { + Warning( "VMPI-specific options:\n\n" ); + + bool bIsSDKMode = VMPI_IsSDKMode(); + for ( int i=k_eVMPICmdLineParam_FirstParam+1; i < k_eVMPICmdLineParam_LastParam; i++ ) + { + if ( (VMPI_GetParamFlags( (EVMPICmdLineParam)i ) & VMPI_PARAM_SDK_HIDDEN) && bIsSDKMode ) + continue; + + Warning( "[%s]\n", VMPI_GetParamString( (EVMPICmdLineParam)i ) ); + Warning( VMPI_GetParamHelpString( (EVMPICmdLineParam)i ) ); + Warning( "\n\n" ); + } + break; + } + } +#endif +} + + +int RunVVis( int argc, char **argv ) +{ + char portalfile[1024]; + char source[1024]; + double start, end; + + + Msg( "Valve Software - vvis.exe (%s)\n", __DATE__ ); + + verbose = false; + + Q_StripExtension( argv[ argc - 1 ], source, sizeof( source ) ); + CmdLib_InitFileSystem( argv[ argc - 1 ] ); + + Q_FileBase( source, source, sizeof( source ) ); + + LoadCmdLineFromFile( argc, argv, source, "vvis" ); + int i = ParseCommandLine( argc, argv ); + + // This part is just for VMPI. VMPI's file system needs the basedir in front of all filenames, + // so we prepend qdir here. + strcpy( source, ExpandPath( source ) ); + + if (i != argc - 1) + { + PrintUsage( argc, argv ); + DeleteCmdLine( argc, argv ); + CmdLib_Exit( 1 ); + } + + start = Plat_FloatTime(); + + + if (!g_bUseMPI) + { + // Setup the logfile. + char logFile[512]; + _snprintf( logFile, sizeof(logFile), "%s.log", source ); + SetSpewFunctionLogFile( logFile ); + } + + // Run in the background? + if( g_bLowPriority ) + { + SetLowPriority(); + } + + ThreadSetDefault (); + + char targetPath[1024]; + GetPlatformMapPath( source, targetPath, 0, 1024 ); + Msg ("reading %s\n", targetPath); + LoadBSPFile (targetPath); + if (numnodes == 0 || numfaces == 0) + Error ("Empty map"); + ParseEntities (); + + // Check the VMF for a vis radius + if (!g_bUseRadius) + { + float flRadius = DetermineVisRadius( ); + if (flRadius > 0.0f) + { + g_bUseRadius = true; + g_VisRadius = flRadius * flRadius; + } + } + + if ( g_bUseRadius ) + { + MarkLeavesAsRadial(); + } + + if ( inbase[0] == 0 ) + { + strcpy( portalfile, source ); + } + else + { + sprintf ( portalfile, "%s%s", inbase, argv[i] ); + Q_StripExtension( portalfile, portalfile, sizeof( portalfile ) ); + } + strcat (portalfile, ".prt"); + + Msg ("reading %s\n", portalfile); + LoadPortals (portalfile); + + // don't write out results when simply doing a trace + if ( g_TraceClusterStart < 0 ) + { + CalcVis (); + CalcPAS (); + + // We need a mapping from cluster to leaves, since the PVS + // deals with clusters for both CalcVisibleFogVolumes and + BuildClusterTable(); + + CalcVisibleFogVolumes(); + CalcDistanceFromLeavesToWater(); + + visdatasize = vismap_p - dvisdata; + Msg ("visdatasize:%i compressed from %i\n", visdatasize, originalvismapsize*2); + + Msg ("writing %s\n", targetPath); + WriteBSPFile (targetPath); + } + else + { + if ( g_TraceClusterStart < 0 || g_TraceClusterStart >= portalclusters || g_TraceClusterStop < 0 || g_TraceClusterStop >= portalclusters ) + { + Error("Invalid cluster trace: %d to %d, valid range is 0 to %d\n", g_TraceClusterStart, g_TraceClusterStop, portalclusters-1 ); + } + if ( g_bUseMPI ) + { + Warning("Can't compile trace in MPI mode\n"); + } + CalcVisTrace (); + WritePortalTrace(source); + } + + end = Plat_FloatTime(); + + char str[512]; + GetHourMinuteSecondsString( (int)( end - start ), str, sizeof( str ) ); + Msg( "%s elapsed\n", str ); + + ReleasePakFileLumps(); + DeleteCmdLine( argc, argv ); + CmdLib_Cleanup(); + return 0; +} + + +/* +=========== +main +=========== +*/ +int main (int argc, char **argv) +{ + CommandLine()->CreateCmdLine( argc, argv ); + + MathLib_Init( 2.2f, 2.2f, 0.0f, 1.0f, false, false, false, false ); + InstallAllocationFunctions(); + InstallSpewFunction(); + + VVIS_SetupMPI( argc, argv ); + + // Install an exception handler. + if ( g_bUseMPI && !g_bMPIMaster ) + SetupToolsMinidumpHandler( VMPI_ExceptionFilter ); + else + SetupDefaultToolsMinidumpHandler(); + + return RunVVis( argc, argv ); +} + + +// When VVIS is used as a DLL (makes debugging vmpi vvis a lot easier), this is used to +// get it going. +class CVVisDLL : public ILaunchableDLL +{ +public: + virtual int main( int argc, char **argv ) + { + return ::main( argc, argv ); + } +}; + +EXPOSE_SINGLE_INTERFACE( CVVisDLL, ILaunchableDLL, LAUNCHABLE_DLL_INTERFACE_VERSION ); diff --git a/mp/src/utils/vvis/vvis_dll-2010.vcxproj b/mp/src/utils/vvis/vvis_dll-2010.vcxproj new file mode 100644 index 00000000..3099cd82 --- /dev/null +++ b/mp/src/utils/vvis/vvis_dll-2010.vcxproj @@ -0,0 +1,291 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + Vvis_dll + {AC70A841-561F-4DAE-7864-E50541AD99ED} + + + + DynamicLibrary + MultiByte + vvis_dll + + + DynamicLibrary + MultiByte + vvis_dll + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30319.1 + .\Debug\win32\ + .\Debug\win32\ + ..\..\devtools\vstools;$(ExecutablePath);$(Path) + true + true + true + false + true + .\Release\win32\ + .\Release\win32\ + ..\..\devtools\vstools;$(ExecutablePath);$(Path) + true + true + false + false + true + + + + + + /MP + Disabled + ..\..\common;..\..\public;..\..\public\tier0;..\..\public\tier1;..\common;..\vmpi;..\vmpi\mysql\include + _HAS_ITERATOR_DEBUGGING=0;WIN32;_WIN32;_DEBUG;DEBUG;_WINDOWS;_USRDLL;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;DLLNAME=vvis_dll;RAD_TELEMETRY_DISABLED;COMPILER_MSVC32;MPI;PROTECTED_THINGS_DISABLE;VPCGAMECAPS=VALVE;PROJECTDIR=D:\dev\games\rel\hl2\src\utils\vvis;_DLL_EXT=.dll;VPCGAME=valve + true + false + Default + MultiThreadedDebug + true + StreamingSIMDExtensions + Fast + true + true + NotUsing + $(IntDir)/ + $(IntDir)/ + $(IntDir)/ + false + false + Level4 + true + EditAndContinue + CompileAsCpp + $(IntDir)/ + Prompt + + + _DEBUG;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE + 1033 + + + + + /ignore:4221 + %(AdditionalDependencies);odbc32.lib;odbccp32.lib;ws2_32.lib + NotSet + $(OutDir)\vvis_dll.dll + true + ..\..\lib\common;..\..\lib\public + libc;libcd;libcmt + true + $(IntDir)/$(TargetName).pdb + false + $(IntDir)/$(TargetName).map + Windows + + MachineX86 + PromptImmediately + false + false + + + true + + + true + + + true + $(OutDir)/vvis_dll.bsc + + + Publishing to ..\..\..\game\bin + if not exist "..\..\..\game\bin" mkdir "..\..\..\game\bin" copy "$(TargetDir)"$(TargetFileName) "..\..\..\game\bin\$(TargetFileName)" if ERRORLEVEL 1 goto BuildEventFailed if exist "$(TargetDir)"$(TargetName).map copy "$(TargetDir)"$(TargetName).map ..\..\..\game\bin\$(TargetName).map copy "$(TargetDir)"$(TargetName).pdb ..\..\..\game\bin\$(TargetName).pdb if ERRORLEVEL 1 goto BuildEventFailed goto BuildEventOK :BuildEventFailed echo *** ERROR! PostBuildStep FAILED for $(ProjectName)! EXE or DLL is probably running. *** del /q "$(TargetDir)"$(TargetFileName) exit 1 :BuildEventOK + + + + + + + + + /MP /d2Zi+ + MaxSpeed + AnySuitable + true + Speed + ..\..\common;..\..\public;..\..\public\tier0;..\..\public\tier1;..\common;..\vmpi;..\vmpi\mysql\include + WIN32;_WIN32;NDEBUG;_WINDOWS;_USRDLL;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;DLLNAME=vvis_dll;RAD_TELEMETRY_DISABLED;COMPILER_MSVC32;MPI;PROTECTED_THINGS_DISABLE;VPCGAMECAPS=VALVE;PROJECTDIR=D:\dev\games\rel\hl2\src\utils\vvis;_DLL_EXT=.dll;VPCGAME=valve + true + false + MultiThreaded + false + true + StreamingSIMDExtensions + Fast + true + true + NotUsing + $(IntDir)/ + $(IntDir)/ + $(IntDir)/ + false + false + Level4 + true + ProgramDatabase + CompileAsCpp + $(IntDir)/ + Prompt + + + NDEBUG;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE + 1033 + + + + + /DYNAMICBASE /ignore:4221 + %(AdditionalDependencies);odbc32.lib;odbccp32.lib;ws2_32.lib + NotSet + $(OutDir)\vvis_dll.dll + true + ..\..\lib\common;..\..\lib\public + libc;libcd;libcmtd + true + $(IntDir)/$(TargetName).pdb + false + $(IntDir)/$(TargetName).map + Windows + true + true + + MachineX86 + PromptImmediately + false + + + true + + + true + + + true + $(OutDir)/vvis_dll.bsc + + + Publishing to ..\..\..\game\bin + if not exist "..\..\..\game\bin" mkdir "..\..\..\game\bin" copy "$(TargetDir)"$(TargetFileName) "..\..\..\game\bin\$(TargetFileName)" if ERRORLEVEL 1 goto BuildEventFailed if exist "$(TargetDir)"$(TargetName).map copy "$(TargetDir)"$(TargetName).map ..\..\..\game\bin\$(TargetName).map copy "$(TargetDir)"$(TargetName).pdb ..\..\..\game\bin\$(TargetName).pdb if ERRORLEVEL 1 goto BuildEventFailed goto BuildEventOK :BuildEventFailed echo *** ERROR! PostBuildStep FAILED for $(ProjectName)! EXE or DLL is probably running. *** del /q "$(TargetDir)"$(TargetFileName) exit 1 :BuildEventOK + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Compiling pointeroverride.asm + "$(VCInstallDir)bin\ml.exe" /c /Cp /Zi /Fo"$(IntDir)\%(Filename).obj" "%(FullPath)" + $(IntDir)\%(Filename).obj + Compiling pointeroverride.asm + "$(VCInstallDir)bin\ml.exe" /c /Cp /Zi /Fo"$(IntDir)\%(Filename).obj" "%(FullPath)" + $(IntDir)\%(Filename).obj + + + + + + + + diff --git a/mp/src/utils/vvis/vvis_dll-2010.vcxproj.filters b/mp/src/utils/vvis/vvis_dll-2010.vcxproj.filters new file mode 100644 index 00000000..d25742b8 --- /dev/null +++ b/mp/src/utils/vvis/vvis_dll-2010.vcxproj.filters @@ -0,0 +1,218 @@ + + + + + {1680C80B-FF1E-EA4D-9817-CC12254F2E40} + + + {C5D73B3A-C648-896C-B7CE-F174808E5BA5} + + + {BA03E055-4FA2-FCE3-8A1C-D348547D379C} + + + + + Link Libraries + + + Link Libraries + + + Link Libraries + + + Link Libraries + + + Link Libraries + + + Link Libraries + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Source Files + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + + + Source Files + + + + + diff --git a/mp/src/utils/vvis_launcher/StdAfx.cpp b/mp/src/utils/vvis_launcher/StdAfx.cpp new file mode 100644 index 00000000..4165f58f --- /dev/null +++ b/mp/src/utils/vvis_launcher/StdAfx.cpp @@ -0,0 +1,15 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// stdafx.cpp : source file that includes just the standard includes +// vvis_launcher.pch will be the pre-compiled header +// stdafx.obj will contain the pre-compiled type information + +#include "stdafx.h" + +// TODO: reference any additional headers you need in STDAFX.H +// and not in this file diff --git a/mp/src/utils/vvis_launcher/StdAfx.h b/mp/src/utils/vvis_launcher/StdAfx.h new file mode 100644 index 00000000..8446846d --- /dev/null +++ b/mp/src/utils/vvis_launcher/StdAfx.h @@ -0,0 +1,31 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// stdafx.h : include file for standard system include files, +// or project specific include files that are used frequently, but +// are changed infrequently +// + +#if !defined(AFX_STDAFX_H__29316173_1244_4B6A_B361_1ADB126E69F2__INCLUDED_) +#define AFX_STDAFX_H__29316173_1244_4B6A_B361_1ADB126E69F2__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers + +#include +#include +#include "interface.h" + +// TODO: reference additional headers your program requires here + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_STDAFX_H__29316173_1244_4B6A_B361_1ADB126E69F2__INCLUDED_) diff --git a/mp/src/utils/vvis_launcher/vvis_launcher-2010.vcxproj b/mp/src/utils/vvis_launcher/vvis_launcher-2010.vcxproj new file mode 100644 index 00000000..62aee797 --- /dev/null +++ b/mp/src/utils/vvis_launcher/vvis_launcher-2010.vcxproj @@ -0,0 +1,248 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + Vvis_launcher + {E3E2CF1C-9EE4-3173-C39F-D0D4F5483CB6} + + + + Application + MultiByte + vvis + + + Application + MultiByte + vvis + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30319.1 + .\Debug\win32\ + .\Debug\win32\ + ..\..\devtools\vstools;$(ExecutablePath);$(Path) + true + true + true + true + true + .\Release\win32\ + .\Release\win32\ + ..\..\devtools\vstools;$(ExecutablePath);$(Path) + true + true + false + true + true + + + + if EXIST ..\..\..\game\bin\$(TargetFileName) for /f "delims=" %%A in ('attrib "..\..\..\game\bin\$(TargetFileName)"') do set valveTmpIsReadOnly="%%A" set valveTmpIsReadOnlyLetter=%valveTmpIsReadOnly:~6,1% if "%valveTmpIsReadOnlyLetter%"=="R" del /q "$(TargetDir)"$(TargetFileName) if exist ..\..\devtools\bin\vpc.exe ..\..\devtools\bin\vpc.exe -crc2 vvis_launcher.vcxproj if ERRORLEVEL 1 exit 1 + + + /MP + Disabled + ..\..\common;..\..\public;..\..\public\tier0;..\..\public\tier1;..\common + _HAS_ITERATOR_DEBUGGING=0;WIN32;_WIN32;_DEBUG;DEBUG;_WINDOWS;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;RAD_TELEMETRY_DISABLED;COMPILER_MSVC32;VPCGAMECAPS=VALVE;PROJECTDIR=D:\dev\games\rel\hl2\src\utils\vvis_launcher;_DLL_EXT=.dll;VPCGAME=valve + true + false + Default + MultiThreadedDebug + true + StreamingSIMDExtensions + Fast + true + true + true + false + Use + Debug/vvis_launcher.pch + false + NoListing + $(IntDir)/ + $(IntDir)/ + $(IntDir)/ + false + Level4 + true + EditAndContinue + CompileAsCpp + $(IntDir)/ + Prompt + + + _DEBUG;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE + 1033 + + + + + /NXCOMPAT /ignore:4221 + %(AdditionalDependencies) + NotSet + $(OutDir)\vvis.exe + true + libc;libcd;libcmt + true + $(IntDir)/$(TargetName).pdb + false + $(IntDir)/$(TargetName).map + Console + + MachineX86 + PromptImmediately + false + + + true + + + true + + + true + $(OutDir)/vvis.bsc + + + Publishing to ..\..\..\game\bin + if not exist "..\..\..\game\bin" mkdir "..\..\..\game\bin" copy "$(TargetDir)"$(TargetFileName) ..\..\..\game\bin\$(TargetFileName) if ERRORLEVEL 1 goto BuildEventFailed if exist "$(TargetDir)"$(TargetName).map copy "$(TargetDir)"$(TargetName).map ..\..\..\game\bin\$(TargetName).map copy "$(TargetDir)"$(TargetName).pdb ..\..\..\game\bin\$(TargetName).pdb if ERRORLEVEL 1 goto BuildEventFailed goto BuildEventOK :BuildEventFailed echo *** ERROR! PostBuildStep FAILED for $(ProjectName)! EXE or DLL is probably running. *** del /q "$(TargetDir)"$(TargetFileName) exit 1 :BuildEventOK + + + + + + + if EXIST ..\..\..\game\bin\$(TargetFileName) for /f "delims=" %%A in ('attrib "..\..\..\game\bin\$(TargetFileName)"') do set valveTmpIsReadOnly="%%A" set valveTmpIsReadOnlyLetter=%valveTmpIsReadOnly:~6,1% if "%valveTmpIsReadOnlyLetter%"=="R" del /q "$(TargetDir)"$(TargetFileName) if exist ..\..\devtools\bin\vpc.exe ..\..\devtools\bin\vpc.exe -crc2 vvis_launcher.vcxproj if ERRORLEVEL 1 exit 1 + + + /MP /d2Zi+ + MaxSpeed + AnySuitable + true + Speed + ..\..\common;..\..\public;..\..\public\tier0;..\..\public\tier1;..\common + WIN32;_WIN32;NDEBUG;_WINDOWS;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;RAD_TELEMETRY_DISABLED;COMPILER_MSVC32;VPCGAMECAPS=VALVE;PROJECTDIR=D:\dev\games\rel\hl2\src\utils\vvis_launcher;_DLL_EXT=.dll;VPCGAME=valve + true + false + MultiThreaded + false + true + StreamingSIMDExtensions + Fast + true + true + true + false + Use + Debug/vvis_launcher.pch + false + NoListing + $(IntDir)/ + $(IntDir)/ + $(IntDir)/ + false + Level4 + true + ProgramDatabase + CompileAsCpp + $(IntDir)/ + Prompt + + + NDEBUG;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE + 1033 + + + + + /DYNAMICBASE /NXCOMPAT /ignore:4221 + %(AdditionalDependencies) + NotSet + $(OutDir)\vvis.exe + true + libc;libcd;libcmtd + true + $(IntDir)/$(TargetName).pdb + false + $(IntDir)/$(TargetName).map + Console + true + true + + MachineX86 + PromptImmediately + + + true + + + true + + + true + $(OutDir)/vvis.bsc + + + Publishing to ..\..\..\game\bin + if not exist "..\..\..\game\bin" mkdir "..\..\..\game\bin" copy "$(TargetDir)"$(TargetFileName) ..\..\..\game\bin\$(TargetFileName) if ERRORLEVEL 1 goto BuildEventFailed if exist "$(TargetDir)"$(TargetName).map copy "$(TargetDir)"$(TargetName).map ..\..\..\game\bin\$(TargetName).map copy "$(TargetDir)"$(TargetName).pdb ..\..\..\game\bin\$(TargetName).pdb if ERRORLEVEL 1 goto BuildEventFailed goto BuildEventOK :BuildEventFailed echo *** ERROR! PostBuildStep FAILED for $(ProjectName)! EXE or DLL is probably running. *** del /q "$(TargetDir)"$(TargetFileName) exit 1 :BuildEventOK + + + + + + + + + + + + + + + + NotUsing + NotUsing + + + Create + Create + + + + + + + + Compiling pointeroverride.asm + "$(VCInstallDir)bin\ml.exe" /c /Cp /Zi /Fo"$(IntDir)\%(Filename).obj" "%(FullPath)" + $(IntDir)\%(Filename).obj + Compiling pointeroverride.asm + "$(VCInstallDir)bin\ml.exe" /c /Cp /Zi /Fo"$(IntDir)\%(Filename).obj" "%(FullPath)" + $(IntDir)\%(Filename).obj + + + + + + + + diff --git a/mp/src/utils/vvis_launcher/vvis_launcher-2010.vcxproj.filters b/mp/src/utils/vvis_launcher/vvis_launcher-2010.vcxproj.filters new file mode 100644 index 00000000..c742afd3 --- /dev/null +++ b/mp/src/utils/vvis_launcher/vvis_launcher-2010.vcxproj.filters @@ -0,0 +1,53 @@ + + + + + {1680C80B-FF1E-EA4D-9817-CC12254F2E40} + + + {C5D73B3A-C648-896C-B7CE-F174808E5BA5} + + + {BA03E055-4FA2-FCE3-8A1C-D348547D379C} + + + + + Link Libraries + + + Link Libraries + + + Link Libraries + + + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + Source Files + + + + + + + Source Files + + + + + diff --git a/mp/src/utils/vvis_launcher/vvis_launcher.cpp b/mp/src/utils/vvis_launcher/vvis_launcher.cpp new file mode 100644 index 00000000..6d628040 --- /dev/null +++ b/mp/src/utils/vvis_launcher/vvis_launcher.cpp @@ -0,0 +1,79 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// vvis_launcher.cpp : Defines the entry point for the console application. +// + +#include "stdafx.h" +#include +#include "tier1/strtools.h" +#include "tier0/icommandline.h" +#include "ilaunchabledll.h" + + + +char* GetLastErrorString() +{ + static char err[2048]; + + LPVOID lpMsgBuf; + FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + GetLastError(), + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language + (LPTSTR) &lpMsgBuf, + 0, + NULL + ); + + strncpy( err, (char*)lpMsgBuf, sizeof( err ) ); + LocalFree( lpMsgBuf ); + + err[ sizeof( err ) - 1 ] = 0; + + return err; +} + + +int main(int argc, char* argv[]) +{ + CommandLine()->CreateCmdLine( argc, argv ); + const char *pDLLName = "vvis_dll.dll"; + + CSysModule *pModule = Sys_LoadModule( pDLLName ); + if ( !pModule ) + { + printf( "vvis launcher error: can't load %s\n%s", pDLLName, GetLastErrorString() ); + return 1; + } + + CreateInterfaceFn fn = Sys_GetFactory( pModule ); + if( !fn ) + { + printf( "vvis launcher error: can't get factory from %s\n", pDLLName ); + Sys_UnloadModule( pModule ); + return 2; + } + + int retCode = 0; + ILaunchableDLL *pDLL = (ILaunchableDLL*)fn( LAUNCHABLE_DLL_INTERFACE_VERSION, &retCode ); + if( !pDLL ) + { + printf( "vvis launcher error: can't get IVVisDLL interface from %s\n", pDLLName ); + Sys_UnloadModule( pModule ); + return 3; + } + + pDLL->main( argc, argv ); + Sys_UnloadModule( pModule ); + + return 0; +} + -- cgit v1.2.3