From 1dd2c2f2dbec06a66acf1b417756f671e055dc48 Mon Sep 17 00:00:00 2001 From: Sam Date: Sun, 2 Dec 2018 16:18:12 +0100 Subject: [PATCH] Vluzacn's ZHLT V34 with gitignore --- .gitignore | 72 + readme.txt | 25 + src.zip | Bin 0 -> 441406 bytes src/Terms of Use.txt | 54 + src/zhlt-vluzacn/Backup/zhlt_vc2005.sln | 43 + src/zhlt-vluzacn/Makefile | 322 + src/zhlt-vluzacn/UpgradeLog.htm | Bin 0 -> 36970 bytes src/zhlt-vluzacn/common/TimeCounter.h | 49 + src/zhlt-vluzacn/common/anorms.h | 649 ++ src/zhlt-vluzacn/common/blockmem.cpp | 161 + src/zhlt-vluzacn/common/blockmem.h | 21 + src/zhlt-vluzacn/common/boundingbox.h | 158 + src/zhlt-vluzacn/common/bspfile.cpp | 2024 ++++ src/zhlt-vluzacn/common/bspfile.h | 473 + src/zhlt-vluzacn/common/cmdlib.cpp | 583 ++ src/zhlt-vluzacn/common/cmdlib.h | 711 ++ src/zhlt-vluzacn/common/cmdlinecfg.cpp | 352 + src/zhlt-vluzacn/common/cmdlinecfg.h | 5 + src/zhlt-vluzacn/common/filelib.cpp | 217 + src/zhlt-vluzacn/common/filelib.h | 23 + src/zhlt-vluzacn/common/files.cpp | 0 src/zhlt-vluzacn/common/hlassert.h | 41 + src/zhlt-vluzacn/common/log.cpp | 959 ++ src/zhlt-vluzacn/common/log.h | 89 + src/zhlt-vluzacn/common/mathlib.cpp | 9 + src/zhlt-vluzacn/common/mathlib.h | 293 + src/zhlt-vluzacn/common/mathtypes.h | 18 + src/zhlt-vluzacn/common/messages.cpp | 135 + src/zhlt-vluzacn/common/messages.h | 106 + src/zhlt-vluzacn/common/resourcelock.cpp | 61 + src/zhlt-vluzacn/common/resourcelock.h | 12 + src/zhlt-vluzacn/common/scriplib.cpp | 370 + src/zhlt-vluzacn/common/scriplib.h | 26 + src/zhlt-vluzacn/common/threads.cpp | 739 ++ src/zhlt-vluzacn/common/threads.h | 55 + src/zhlt-vluzacn/common/win32fix.h | 73 + src/zhlt-vluzacn/common/winding.cpp | 1308 +++ src/zhlt-vluzacn/common/winding.h | 136 + src/zhlt-vluzacn/hlbsp/brink.cpp | 2066 ++++ src/zhlt-vluzacn/hlbsp/bsp5.h | 400 + src/zhlt-vluzacn/hlbsp/hlbsp.vcproj | 385 + src/zhlt-vluzacn/hlbsp/hlbsp.vcxproj | 179 + src/zhlt-vluzacn/hlbsp/hlbsp.vcxproj.filters | 135 + src/zhlt-vluzacn/hlbsp/merge.cpp | 286 + src/zhlt-vluzacn/hlbsp/outside.cpp | 739 ++ src/zhlt-vluzacn/hlbsp/portals.cpp | 433 + src/zhlt-vluzacn/hlbsp/qbsp.cpp | 2318 +++++ src/zhlt-vluzacn/hlbsp/solidbsp.cpp | 2430 +++++ src/zhlt-vluzacn/hlbsp/surfaces.cpp | 470 + src/zhlt-vluzacn/hlbsp/tjunc.cpp | 669 ++ src/zhlt-vluzacn/hlbsp/writebsp.cpp | 1167 +++ src/zhlt-vluzacn/hlcsg/ansitoutf8.cpp | 22 + src/zhlt-vluzacn/hlcsg/autowad.cpp | 423 + src/zhlt-vluzacn/hlcsg/brush.cpp | 2254 ++++ src/zhlt-vluzacn/hlcsg/brushunion.cpp | 371 + src/zhlt-vluzacn/hlcsg/csg.h | 479 + src/zhlt-vluzacn/hlcsg/hlcsg.vcproj | 401 + src/zhlt-vluzacn/hlcsg/hlcsg.vcxproj | 183 + src/zhlt-vluzacn/hlcsg/hlcsg.vcxproj.filters | 143 + src/zhlt-vluzacn/hlcsg/hullfile.cpp | 98 + src/zhlt-vluzacn/hlcsg/map.cpp | 1516 +++ src/zhlt-vluzacn/hlcsg/netvis_in_vis.cpp | 0 src/zhlt-vluzacn/hlcsg/properties.cpp | 60 + src/zhlt-vluzacn/hlcsg/qcsg.cpp | 3077 ++++++ src/zhlt-vluzacn/hlcsg/textures.cpp | 1240 +++ src/zhlt-vluzacn/hlcsg/wadcfg.cpp | 590 ++ src/zhlt-vluzacn/hlcsg/wadinclude.cpp | 211 + src/zhlt-vluzacn/hlcsg/wadpath.cpp | 174 + src/zhlt-vluzacn/hlcsg/wadpath.h | 30 + src/zhlt-vluzacn/hlrad/compress.cpp | 73 + src/zhlt-vluzacn/hlrad/compress.h | 318 + src/zhlt-vluzacn/hlrad/hlrad.vcproj | 413 + src/zhlt-vluzacn/hlrad/hlrad.vcxproj | 186 + src/zhlt-vluzacn/hlrad/hlrad.vcxproj.filters | 156 + src/zhlt-vluzacn/hlrad/lerp.cpp | 3382 ++++++ src/zhlt-vluzacn/hlrad/lightmap.cpp | 9179 +++++++++++++++++ src/zhlt-vluzacn/hlrad/loadtextures.cpp | 1507 +++ src/zhlt-vluzacn/hlrad/mathutil.cpp | 991 ++ src/zhlt-vluzacn/hlrad/nomatrix.cpp | 330 + src/zhlt-vluzacn/hlrad/qrad.cpp | 5919 +++++++++++ src/zhlt-vluzacn/hlrad/qrad.h | 1146 ++ src/zhlt-vluzacn/hlrad/qradutil.cpp | 1051 ++ src/zhlt-vluzacn/hlrad/sparse.cpp | 944 ++ src/zhlt-vluzacn/hlrad/trace.cpp | 1108 ++ src/zhlt-vluzacn/hlrad/transfers.cpp | 238 + src/zhlt-vluzacn/hlrad/transparency.cpp | 452 + src/zhlt-vluzacn/hlrad/vismatrix.cpp | 664 ++ src/zhlt-vluzacn/hlrad/vismatrixutil.cpp | 1333 +++ src/zhlt-vluzacn/hlvis/flow.cpp | 1771 ++++ src/zhlt-vluzacn/hlvis/hlvis.vcproj | 365 + src/zhlt-vluzacn/hlvis/hlvis.vcxproj | 174 + src/zhlt-vluzacn/hlvis/hlvis.vcxproj.filters | 120 + src/zhlt-vluzacn/hlvis/vis.cpp | 2082 ++++ src/zhlt-vluzacn/hlvis/vis.h | 240 + src/zhlt-vluzacn/hlvis/zones.cpp | 155 + src/zhlt-vluzacn/hlvis/zones.h | 75 + src/zhlt-vluzacn/ripent/ripent.cpp | 1332 +++ src/zhlt-vluzacn/ripent/ripent.h | 31 + src/zhlt-vluzacn/ripent/ripent.vcproj | 353 + src/zhlt-vluzacn/ripent/ripent.vcxproj | 172 + .../ripent/ripent.vcxproj.filters | 114 + src/zhlt-vluzacn/template/BaseMath.h | 183 + src/zhlt-vluzacn/template/EndianMath.h | 185 + src/zhlt-vluzacn/template/ReferenceArray.h | 153 + src/zhlt-vluzacn/template/ReferenceCounter.h | 233 + src/zhlt-vluzacn/template/ReferenceObject.h | 232 + src/zhlt-vluzacn/template/ReferencePtr.h | 155 + src/zhlt-vluzacn/template/basictypes.h | 77 + src/zhlt-vluzacn/zhlt.sln | 44 + src/zhlt-vluzacn/zhlt_vc2005.sln | 48 + tools/lights.rad | 48 + tools/settings.txt | 119 + tools/wad.cfg | 56 + tools/zhlt.fgd | 247 + tools/zhlt.wad | Bin 0 -> 73508 bytes 115 files changed, 71475 insertions(+) create mode 100644 .gitignore create mode 100644 readme.txt create mode 100644 src.zip create mode 100644 src/Terms of Use.txt create mode 100644 src/zhlt-vluzacn/Backup/zhlt_vc2005.sln create mode 100644 src/zhlt-vluzacn/Makefile create mode 100644 src/zhlt-vluzacn/UpgradeLog.htm create mode 100644 src/zhlt-vluzacn/common/TimeCounter.h create mode 100644 src/zhlt-vluzacn/common/anorms.h create mode 100644 src/zhlt-vluzacn/common/blockmem.cpp create mode 100644 src/zhlt-vluzacn/common/blockmem.h create mode 100644 src/zhlt-vluzacn/common/boundingbox.h create mode 100644 src/zhlt-vluzacn/common/bspfile.cpp create mode 100644 src/zhlt-vluzacn/common/bspfile.h create mode 100644 src/zhlt-vluzacn/common/cmdlib.cpp create mode 100644 src/zhlt-vluzacn/common/cmdlib.h create mode 100644 src/zhlt-vluzacn/common/cmdlinecfg.cpp create mode 100644 src/zhlt-vluzacn/common/cmdlinecfg.h create mode 100644 src/zhlt-vluzacn/common/filelib.cpp create mode 100644 src/zhlt-vluzacn/common/filelib.h create mode 100644 src/zhlt-vluzacn/common/files.cpp create mode 100644 src/zhlt-vluzacn/common/hlassert.h create mode 100644 src/zhlt-vluzacn/common/log.cpp create mode 100644 src/zhlt-vluzacn/common/log.h create mode 100644 src/zhlt-vluzacn/common/mathlib.cpp create mode 100644 src/zhlt-vluzacn/common/mathlib.h create mode 100644 src/zhlt-vluzacn/common/mathtypes.h create mode 100644 src/zhlt-vluzacn/common/messages.cpp create mode 100644 src/zhlt-vluzacn/common/messages.h create mode 100644 src/zhlt-vluzacn/common/resourcelock.cpp create mode 100644 src/zhlt-vluzacn/common/resourcelock.h create mode 100644 src/zhlt-vluzacn/common/scriplib.cpp create mode 100644 src/zhlt-vluzacn/common/scriplib.h create mode 100644 src/zhlt-vluzacn/common/threads.cpp create mode 100644 src/zhlt-vluzacn/common/threads.h create mode 100644 src/zhlt-vluzacn/common/win32fix.h create mode 100644 src/zhlt-vluzacn/common/winding.cpp create mode 100644 src/zhlt-vluzacn/common/winding.h create mode 100644 src/zhlt-vluzacn/hlbsp/brink.cpp create mode 100644 src/zhlt-vluzacn/hlbsp/bsp5.h create mode 100644 src/zhlt-vluzacn/hlbsp/hlbsp.vcproj create mode 100644 src/zhlt-vluzacn/hlbsp/hlbsp.vcxproj create mode 100644 src/zhlt-vluzacn/hlbsp/hlbsp.vcxproj.filters create mode 100644 src/zhlt-vluzacn/hlbsp/merge.cpp create mode 100644 src/zhlt-vluzacn/hlbsp/outside.cpp create mode 100644 src/zhlt-vluzacn/hlbsp/portals.cpp create mode 100644 src/zhlt-vluzacn/hlbsp/qbsp.cpp create mode 100644 src/zhlt-vluzacn/hlbsp/solidbsp.cpp create mode 100644 src/zhlt-vluzacn/hlbsp/surfaces.cpp create mode 100644 src/zhlt-vluzacn/hlbsp/tjunc.cpp create mode 100644 src/zhlt-vluzacn/hlbsp/writebsp.cpp create mode 100644 src/zhlt-vluzacn/hlcsg/ansitoutf8.cpp create mode 100644 src/zhlt-vluzacn/hlcsg/autowad.cpp create mode 100644 src/zhlt-vluzacn/hlcsg/brush.cpp create mode 100644 src/zhlt-vluzacn/hlcsg/brushunion.cpp create mode 100644 src/zhlt-vluzacn/hlcsg/csg.h create mode 100644 src/zhlt-vluzacn/hlcsg/hlcsg.vcproj create mode 100644 src/zhlt-vluzacn/hlcsg/hlcsg.vcxproj create mode 100644 src/zhlt-vluzacn/hlcsg/hlcsg.vcxproj.filters create mode 100644 src/zhlt-vluzacn/hlcsg/hullfile.cpp create mode 100644 src/zhlt-vluzacn/hlcsg/map.cpp create mode 100644 src/zhlt-vluzacn/hlcsg/netvis_in_vis.cpp create mode 100644 src/zhlt-vluzacn/hlcsg/properties.cpp create mode 100644 src/zhlt-vluzacn/hlcsg/qcsg.cpp create mode 100644 src/zhlt-vluzacn/hlcsg/textures.cpp create mode 100644 src/zhlt-vluzacn/hlcsg/wadcfg.cpp create mode 100644 src/zhlt-vluzacn/hlcsg/wadinclude.cpp create mode 100644 src/zhlt-vluzacn/hlcsg/wadpath.cpp create mode 100644 src/zhlt-vluzacn/hlcsg/wadpath.h create mode 100644 src/zhlt-vluzacn/hlrad/compress.cpp create mode 100644 src/zhlt-vluzacn/hlrad/compress.h create mode 100644 src/zhlt-vluzacn/hlrad/hlrad.vcproj create mode 100644 src/zhlt-vluzacn/hlrad/hlrad.vcxproj create mode 100644 src/zhlt-vluzacn/hlrad/hlrad.vcxproj.filters create mode 100644 src/zhlt-vluzacn/hlrad/lerp.cpp create mode 100644 src/zhlt-vluzacn/hlrad/lightmap.cpp create mode 100644 src/zhlt-vluzacn/hlrad/loadtextures.cpp create mode 100644 src/zhlt-vluzacn/hlrad/mathutil.cpp create mode 100644 src/zhlt-vluzacn/hlrad/nomatrix.cpp create mode 100644 src/zhlt-vluzacn/hlrad/qrad.cpp create mode 100644 src/zhlt-vluzacn/hlrad/qrad.h create mode 100644 src/zhlt-vluzacn/hlrad/qradutil.cpp create mode 100644 src/zhlt-vluzacn/hlrad/sparse.cpp create mode 100644 src/zhlt-vluzacn/hlrad/trace.cpp create mode 100644 src/zhlt-vluzacn/hlrad/transfers.cpp create mode 100644 src/zhlt-vluzacn/hlrad/transparency.cpp create mode 100644 src/zhlt-vluzacn/hlrad/vismatrix.cpp create mode 100644 src/zhlt-vluzacn/hlrad/vismatrixutil.cpp create mode 100644 src/zhlt-vluzacn/hlvis/flow.cpp create mode 100644 src/zhlt-vluzacn/hlvis/hlvis.vcproj create mode 100644 src/zhlt-vluzacn/hlvis/hlvis.vcxproj create mode 100644 src/zhlt-vluzacn/hlvis/hlvis.vcxproj.filters create mode 100644 src/zhlt-vluzacn/hlvis/vis.cpp create mode 100644 src/zhlt-vluzacn/hlvis/vis.h create mode 100644 src/zhlt-vluzacn/hlvis/zones.cpp create mode 100644 src/zhlt-vluzacn/hlvis/zones.h create mode 100644 src/zhlt-vluzacn/ripent/ripent.cpp create mode 100644 src/zhlt-vluzacn/ripent/ripent.h create mode 100644 src/zhlt-vluzacn/ripent/ripent.vcproj create mode 100644 src/zhlt-vluzacn/ripent/ripent.vcxproj create mode 100644 src/zhlt-vluzacn/ripent/ripent.vcxproj.filters create mode 100644 src/zhlt-vluzacn/template/BaseMath.h create mode 100644 src/zhlt-vluzacn/template/EndianMath.h create mode 100644 src/zhlt-vluzacn/template/ReferenceArray.h create mode 100644 src/zhlt-vluzacn/template/ReferenceCounter.h create mode 100644 src/zhlt-vluzacn/template/ReferenceObject.h create mode 100644 src/zhlt-vluzacn/template/ReferencePtr.h create mode 100644 src/zhlt-vluzacn/template/basictypes.h create mode 100644 src/zhlt-vluzacn/zhlt.sln create mode 100644 src/zhlt-vluzacn/zhlt_vc2005.sln create mode 100644 tools/lights.rad create mode 100644 tools/settings.txt create mode 100644 tools/wad.cfg create mode 100644 tools/zhlt.fgd create mode 100644 tools/zhlt.wad diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..062d5af --- /dev/null +++ b/.gitignore @@ -0,0 +1,72 @@ +# Windows image file caches +Thumbs.db +ehthumbs.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# ========================= +# Operating System Files +# ========================= + +# OSX +# ========================= + +.DS_Store +.AppleDouble +.LSOverride + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +*.obj +*.exe +*.tlog +*.pdb +*.mdl +*.sdf +*.opendb +*.db +*.suo +*.log +*.idb +*.ilk +*.user +*.bmp +*.iobj +*.ipdb +*.ipch +*.res +*.exp +*.lastcodeanalysissucceeded +*.nativecodeanalysis.all.xml +*.nativecodeanalysis.xml +*.aps +*.bsc diff --git a/readme.txt b/readme.txt new file mode 100644 index 0000000..5420df8 --- /dev/null +++ b/readme.txt @@ -0,0 +1,25 @@ + +---- How to install ---- + +1. Open Hammer or Batch Compiler's configuration dialog. + Set CSG, BSP, VIS, RAD tool pathes to the 'hlcsg.exe', 'hlbsp.exe', 'hlvis.exe', 'hlrad.exe' in 'tools' folder. + If you are running 64-bit Windows, use the 'hlcsg_x64.exe', 'hlbsp_x64.exe', 'hlvis_x64.exe' and 'hlrad_x64.exe'. + The main benefit of the 64-bit version is no memory allocation failures, because the 64-bit tools have access to more than 2GB of system memory. + +2. Open Hammer's configuration dialog. + Add 'zhlt.wad' into your wad list. + Add 'zhlt.fgd' into your fgd list. + +3. You may also be interested in the these tools (which are well supported by this compiler): +- Larger grid patch for Hammer + http://forums.svencoop.com/showthread.php?p=461330#post461330 +- Floating point coordinates output patch for Hammer + http://forums.svencoop.com/showthread.php?t=39037 + + +---- About ---- + +Vluzacn's ZHLT v34 (2015/8/17) +This is Vluzacn's (email: vluzacn@163.com) 34th custom build of ZHLT 3.4 (http://zhlt.info/) +More information are available at http://forums.svencoop.com/showthread.php?t=38059 + diff --git a/src.zip b/src.zip new file mode 100644 index 0000000000000000000000000000000000000000..99e2293cb93fb57f099c3899539ea82e865f1cf2 GIT binary patch literal 441406 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 z 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 z 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≺*f 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

6bl$U0Z!&U`J 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

!=#n;s@qB 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

yJ8R70&RAtkjN;_Iy~S&=YLg!!@>uG)PN) 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 z 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 literal 0 HcmV?d00001 diff --git a/src/zhlt-vluzacn/common/TimeCounter.h b/src/zhlt-vluzacn/common/TimeCounter.h new file mode 100644 index 0000000..7ae7422 --- /dev/null +++ b/src/zhlt-vluzacn/common/TimeCounter.h @@ -0,0 +1,49 @@ +#ifndef TIMECOUNTER_H__ +#define TIMECOUNTER_H__ + +#if _MSC_VER >= 1000 +#pragma once +#endif + +#include "cmdlib.h" + +class TimeCounter +{ +public: + void start() + { + start = I_FloatTime(); + } + + void stop() + { + double stop = I_FloatTime(); + accum += stop - start; + } + + double getTotal() const + { + return accum; + } + + void reset() + { + memset(this, 0, sizeof(*this)); + } + +// Construction +public: + TimeCounter() + { + reset(); + } + // Default Destructor ok + // Default Copy Constructor ok + // Default Copy Operator ok + +protected: + double start; + double accum; +}; + +#endif//TIMECOUNTER_H__ \ No newline at end of file diff --git a/src/zhlt-vluzacn/common/anorms.h b/src/zhlt-vluzacn/common/anorms.h new file mode 100644 index 0000000..536940a --- /dev/null +++ b/src/zhlt-vluzacn/common/anorms.h @@ -0,0 +1,649 @@ + +{ +-0.525731, 0.000000, 0.850651} + +, +{ +-0.442863, 0.238856, 0.864188} + +, +{ +-0.295242, 0.000000, 0.955423} + +, +{ +-0.309017, 0.500000, 0.809017} + +, +{ +-0.162460, 0.262866, 0.951056} + +, +{ +0.000000, 0.000000, 1.000000} + +, +{ +0.000000, 0.850651, 0.525731} + +, +{ +-0.147621, 0.716567, 0.681718} + +, +{ +0.147621, 0.716567, 0.681718} + +, +{ +0.000000, 0.525731, 0.850651} + +, +{ +0.309017, 0.500000, 0.809017} + +, +{ +0.525731, 0.000000, 0.850651} + +, +{ +0.295242, 0.000000, 0.955423} + +, +{ +0.442863, 0.238856, 0.864188} + +, +{ +0.162460, 0.262866, 0.951056} + +, +{ +-0.681718, 0.147621, 0.716567} + +, +{ +-0.809017, 0.309017, 0.500000} + +, +{ +-0.587785, 0.425325, 0.688191} + +, +{ +-0.850651, 0.525731, 0.000000} + +, +{ +-0.864188, 0.442863, 0.238856} + +, +{ +-0.716567, 0.681718, 0.147621} + +, +{ +-0.688191, 0.587785, 0.425325} + +, +{ +-0.500000, 0.809017, 0.309017} + +, +{ +-0.238856, 0.864188, 0.442863} + +, +{ +-0.425325, 0.688191, 0.587785} + +, +{ +-0.716567, 0.681718, -0.147621} + +, +{ +-0.500000, 0.809017, -0.309017} + +, +{ +-0.525731, 0.850651, 0.000000} + +, +{ +0.000000, 0.850651, -0.525731} + +, +{ +-0.238856, 0.864188, -0.442863} + +, +{ +0.000000, 0.955423, -0.295242} + +, +{ +-0.262866, 0.951056, -0.162460} + +, +{ +0.000000, 1.000000, 0.000000} + +, +{ +0.000000, 0.955423, 0.295242} + +, +{ +-0.262866, 0.951056, 0.162460} + +, +{ +0.238856, 0.864188, 0.442863} + +, +{ +0.262866, 0.951056, 0.162460} + +, +{ +0.500000, 0.809017, 0.309017} + +, +{ +0.238856, 0.864188, -0.442863} + +, +{ +0.262866, 0.951056, -0.162460} + +, +{ +0.500000, 0.809017, -0.309017} + +, +{ +0.850651, 0.525731, 0.000000} + +, +{ +0.716567, 0.681718, 0.147621} + +, +{ +0.716567, 0.681718, -0.147621} + +, +{ +0.525731, 0.850651, 0.000000} + +, +{ +0.425325, 0.688191, 0.587785} + +, +{ +0.864188, 0.442863, 0.238856} + +, +{ +0.688191, 0.587785, 0.425325} + +, +{ +0.809017, 0.309017, 0.500000} + +, +{ +0.681718, 0.147621, 0.716567} + +, +{ +0.587785, 0.425325, 0.688191} + +, +{ +0.955423, 0.295242, 0.000000} + +, +{ +1.000000, 0.000000, 0.000000} + +, +{ +0.951056, 0.162460, 0.262866} + +, +{ +0.850651, -0.525731, 0.000000} + +, +{ +0.955423, -0.295242, 0.000000} + +, +{ +0.864188, -0.442863, 0.238856} + +, +{ +0.951056, -0.162460, 0.262866} + +, +{ +0.809017, -0.309017, 0.500000} + +, +{ +0.681718, -0.147621, 0.716567} + +, +{ +0.850651, 0.000000, 0.525731} + +, +{ +0.864188, 0.442863, -0.238856} + +, +{ +0.809017, 0.309017, -0.500000} + +, +{ +0.951056, 0.162460, -0.262866} + +, +{ +0.525731, 0.000000, -0.850651} + +, +{ +0.681718, 0.147621, -0.716567} + +, +{ +0.681718, -0.147621, -0.716567} + +, +{ +0.850651, 0.000000, -0.525731} + +, +{ +0.809017, -0.309017, -0.500000} + +, +{ +0.864188, -0.442863, -0.238856} + +, +{ +0.951056, -0.162460, -0.262866} + +, +{ +0.147621, 0.716567, -0.681718} + +, +{ +0.309017, 0.500000, -0.809017} + +, +{ +0.425325, 0.688191, -0.587785} + +, +{ +0.442863, 0.238856, -0.864188} + +, +{ +0.587785, 0.425325, -0.688191} + +, +{ +0.688191, 0.587785, -0.425325} + +, +{ +-0.147621, 0.716567, -0.681718} + +, +{ +-0.309017, 0.500000, -0.809017} + +, +{ +0.000000, 0.525731, -0.850651} + +, +{ +-0.525731, 0.000000, -0.850651} + +, +{ +-0.442863, 0.238856, -0.864188} + +, +{ +-0.295242, 0.000000, -0.955423} + +, +{ +-0.162460, 0.262866, -0.951056} + +, +{ +0.000000, 0.000000, -1.000000} + +, +{ +0.295242, 0.000000, -0.955423} + +, +{ +0.162460, 0.262866, -0.951056} + +, +{ +-0.442863, -0.238856, -0.864188} + +, +{ +-0.309017, -0.500000, -0.809017} + +, +{ +-0.162460, -0.262866, -0.951056} + +, +{ +0.000000, -0.850651, -0.525731} + +, +{ +-0.147621, -0.716567, -0.681718} + +, +{ +0.147621, -0.716567, -0.681718} + +, +{ +0.000000, -0.525731, -0.850651} + +, +{ +0.309017, -0.500000, -0.809017} + +, +{ +0.442863, -0.238856, -0.864188} + +, +{ +0.162460, -0.262866, -0.951056} + +, +{ +0.238856, -0.864188, -0.442863} + +, +{ +0.500000, -0.809017, -0.309017} + +, +{ +0.425325, -0.688191, -0.587785} + +, +{ +0.716567, -0.681718, -0.147621} + +, +{ +0.688191, -0.587785, -0.425325} + +, +{ +0.587785, -0.425325, -0.688191} + +, +{ +0.000000, -0.955423, -0.295242} + +, +{ +0.000000, -1.000000, 0.000000} + +, +{ +0.262866, -0.951056, -0.162460} + +, +{ +0.000000, -0.850651, 0.525731} + +, +{ +0.000000, -0.955423, 0.295242} + +, +{ +0.238856, -0.864188, 0.442863} + +, +{ +0.262866, -0.951056, 0.162460} + +, +{ +0.500000, -0.809017, 0.309017} + +, +{ +0.716567, -0.681718, 0.147621} + +, +{ +0.525731, -0.850651, 0.000000} + +, +{ +-0.238856, -0.864188, -0.442863} + +, +{ +-0.500000, -0.809017, -0.309017} + +, +{ +-0.262866, -0.951056, -0.162460} + +, +{ +-0.850651, -0.525731, 0.000000} + +, +{ +-0.716567, -0.681718, -0.147621} + +, +{ +-0.716567, -0.681718, 0.147621} + +, +{ +-0.525731, -0.850651, 0.000000} + +, +{ +-0.500000, -0.809017, 0.309017} + +, +{ +-0.238856, -0.864188, 0.442863} + +, +{ +-0.262866, -0.951056, 0.162460} + +, +{ +-0.864188, -0.442863, 0.238856} + +, +{ +-0.809017, -0.309017, 0.500000} + +, +{ +-0.688191, -0.587785, 0.425325} + +, +{ +-0.681718, -0.147621, 0.716567} + +, +{ +-0.442863, -0.238856, 0.864188} + +, +{ +-0.587785, -0.425325, 0.688191} + +, +{ +-0.309017, -0.500000, 0.809017} + +, +{ +-0.147621, -0.716567, 0.681718} + +, +{ +-0.425325, -0.688191, 0.587785} + +, +{ +-0.162460, -0.262866, 0.951056} + +, +{ +0.442863, -0.238856, 0.864188} + +, +{ +0.162460, -0.262866, 0.951056} + +, +{ +0.309017, -0.500000, 0.809017} + +, +{ +0.147621, -0.716567, 0.681718} + +, +{ +0.000000, -0.525731, 0.850651} + +, +{ +0.425325, -0.688191, 0.587785} + +, +{ +0.587785, -0.425325, 0.688191} + +, +{ +0.688191, -0.587785, 0.425325} + +, +{ +-0.955423, 0.295242, 0.000000} + +, +{ +-0.951056, 0.162460, 0.262866} + +, +{ +-1.000000, 0.000000, 0.000000} + +, +{ +-0.850651, 0.000000, 0.525731} + +, +{ +-0.955423, -0.295242, 0.000000} + +, +{ +-0.951056, -0.162460, 0.262866} + +, +{ +-0.864188, 0.442863, -0.238856} + +, +{ +-0.951056, 0.162460, -0.262866} + +, +{ +-0.809017, 0.309017, -0.500000} + +, +{ +-0.864188, -0.442863, -0.238856} + +, +{ +-0.951056, -0.162460, -0.262866} + +, +{ +-0.809017, -0.309017, -0.500000} + +, +{ +-0.681718, 0.147621, -0.716567} + +, +{ +-0.681718, -0.147621, -0.716567} + +, +{ +-0.850651, 0.000000, -0.525731} + +, +{ +-0.688191, 0.587785, -0.425325} + +, +{ +-0.587785, 0.425325, -0.688191} + +, +{ +-0.425325, 0.688191, -0.587785} + +, +{ +-0.425325, -0.688191, -0.587785} + +, +{ +-0.587785, -0.425325, -0.688191} + +, +{ +-0.688191, -0.587785, -0.425325} + +, diff --git a/src/zhlt-vluzacn/common/blockmem.cpp b/src/zhlt-vluzacn/common/blockmem.cpp new file mode 100644 index 0000000..a4e2330 --- /dev/null +++ b/src/zhlt-vluzacn/common/blockmem.cpp @@ -0,0 +1,161 @@ + +/// ********* WIN32 ********** + +#ifdef SYSTEM_WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#include +#include "cmdlib.h" +#include "messages.h" +#include "log.h" +#include "hlassert.h" +#include "blockmem.h" + +// ===================================================================================== +// AllocBlock +// ===================================================================================== +void* AllocBlock(const unsigned long size) +{ + void* pointer; + HANDLE h; + + if (!size) + { + Warning("Attempting to allocate 0 bytes"); + } + + h = GlobalAlloc(GMEM_FIXED | GMEM_ZEROINIT, size); +#ifdef HLRAD_HLASSUMENOMEMORY + hlassume (h != NULL, assume_NoMemory); +#endif + + if (h) + { + pointer = GlobalLock(h); + } + else + { + return NULL; + } + + return pointer; +} + +// ===================================================================================== +// FreeBlock +// ===================================================================================== +bool FreeBlock(void* pointer) +{ + HANDLE h; + + if (!pointer) + { + Warning("Freeing a null pointer"); + } + + h = GlobalHandle(pointer); + + if (h) + { + GlobalUnlock(h); + GlobalFree(h); + return true; + } + else + { + Warning("Could not translate pointer into handle"); + return false; + } +} + +#ifdef CHECK_HEAP +// ===================================================================================== +// HeapCheck +// ===================================================================================== +void HeapCheck() +{ + if (_heapchk() != _HEAPOK) + hlassert(false); +} +#endif + +// ===================================================================================== +// AllocBlock +// ===================================================================================== +// HeapAlloc/HeapFree is thread safe by default +void* Alloc(const unsigned long size) +{ + HeapCheck(); + return calloc(1, size); +} + +// ===================================================================================== +// AllocBlock +// ===================================================================================== +bool Free(void* pointer) +{ + HeapCheck(); + free(pointer); + return true; +} + +#endif /// ********* WIN32 ********** + + + + +/// ********* POSIX ********** + +#ifdef SYSTEM_POSIX +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#ifdef STDC_HEADERS +#include +#endif +#include "cmdlib.h" +#include "messages.h" +#include "log.h" + +// ===================================================================================== +// AllocBlock +// ===================================================================================== +void* AllocBlock(const unsigned long size) +{ + if (!size) + { + Warning("Attempting to allocate 0 bytes"); + } + return calloc(1, size); +} + +// ===================================================================================== +// FreeBlock +// ===================================================================================== +bool FreeBlock(void* pointer) +{ + if (!pointer) + { + Warning("Freeing a null pointer"); + } + free(pointer); + return true; +} + +// ===================================================================================== +// Alloc +// ===================================================================================== +void* Alloc(const unsigned long size) +{ + return AllocBlock(size); +} + +// ===================================================================================== +// Free +// ===================================================================================== +bool Free(void* pointer) +{ + return FreeBlock(pointer); +} + +#endif /// ********* POSIX ********** diff --git a/src/zhlt-vluzacn/common/blockmem.h b/src/zhlt-vluzacn/common/blockmem.h new file mode 100644 index 0000000..b0b657b --- /dev/null +++ b/src/zhlt-vluzacn/common/blockmem.h @@ -0,0 +1,21 @@ +#ifndef BLOCKMEM_H__ +#define BLOCKMEM_H__ +#include "cmdlib.h" //--vluzacn + +#if _MSC_VER >= 1000 +#pragma once +#endif + +extern void* AllocBlock(unsigned long size); +extern bool FreeBlock(void* pointer); + +extern void* Alloc(unsigned long size); +extern bool Free(void* pointer); + +#if defined(CHECK_HEAP) +extern void HeapCheck(); +#else +#define HeapCheck() +#endif + +#endif // BLOCKMEM_H__ diff --git a/src/zhlt-vluzacn/common/boundingbox.h b/src/zhlt-vluzacn/common/boundingbox.h new file mode 100644 index 0000000..c72df3d --- /dev/null +++ b/src/zhlt-vluzacn/common/boundingbox.h @@ -0,0 +1,158 @@ +// Copyright (C) 2000 Sean Cavanaugh +// This file is licensed under the terms of the Lesser GNU Public License +// (see LPGL.txt, or http://www.gnu.org/copyleft/lesser.txt) + +// AJM: +#pragma warning(disable: 4305) // truncation from 'const double' to 'float' + + +#ifndef BOUNDINGBOX_H__ +#define BOUNDINGBOX_H__ +#include "cmdlib.h" //--vluzacn + +#if _MSC_VER >= 1000 +#pragma once +#endif + +class BoundingBox +{ +public: + typedef enum + { + eDisjoint, // neither boxes touch + eUnion, // this box intersects with the other box + eSubset, // this box is inside the other box + eSuperset // this box is completly envelops the other box + } eBoundingState; + + // Tests if other box is completely outside of this box + bool testDisjoint(const BoundingBox& other) const + { +#ifdef ZHLT_BOUNDINGBOX_PRECISION_FIX + if ((m_Mins[0] > other.m_Maxs[0] + ON_EPSILON) || + (m_Mins[1] > other.m_Maxs[1] + ON_EPSILON) || + (m_Mins[2] > other.m_Maxs[2] + ON_EPSILON) || + (m_Maxs[0] < other.m_Mins[0] - ON_EPSILON) || + (m_Maxs[1] < other.m_Mins[1] - ON_EPSILON) || + (m_Maxs[2] < other.m_Mins[2] - ON_EPSILON)) +#else + if ((m_Mins[0] > other.m_Maxs[0]) || + (m_Mins[1] > other.m_Maxs[1]) || + (m_Mins[2] > other.m_Maxs[2]) || + (m_Maxs[0] < other.m_Mins[0]) || + (m_Maxs[1] < other.m_Mins[1]) || + (m_Maxs[2] < other.m_Mins[2])) +#endif + { + return true; + } + return false; + } + // returns true if this box is completely inside other box + bool testSubset(const BoundingBox& other) const + { + if ( + (m_Mins[0] >= other.m_Mins[0]) && + (m_Maxs[0] <= other.m_Maxs[0]) && + (m_Mins[1] >= other.m_Mins[1]) && + (m_Maxs[1] <= other.m_Maxs[1]) && + (m_Mins[2] >= other.m_Mins[2]) && + (m_Maxs[2] <= other.m_Maxs[2]) + ) + { + return true; + } + return false; + } + // returns true if this box contains the other box completely + bool testSuperset(const BoundingBox& other) const + { + return other.testSubset(*this); + } + // returns true if this box partially intersects the other box + bool testUnion(const BoundingBox& other) const + { + BoundingBox tmpBox; + tmpBox.m_Mins[0] = qmax(m_Mins[0], other.m_Mins[0]); + tmpBox.m_Mins[1] = qmax(m_Mins[1], other.m_Mins[1]); + tmpBox.m_Mins[2] = qmax(m_Mins[2], other.m_Mins[2]); + tmpBox.m_Maxs[0] = qmin(m_Maxs[0], other.m_Maxs[0]); + tmpBox.m_Maxs[1] = qmin(m_Maxs[1], other.m_Maxs[1]); + tmpBox.m_Maxs[2] = qmin(m_Maxs[2], other.m_Maxs[2]); + + if ((tmpBox.m_Mins[0] > tmpBox.m_Maxs[0]) || + (tmpBox.m_Mins[1] > tmpBox.m_Maxs[1]) || + (tmpBox.m_Mins[2] > tmpBox.m_Maxs[2])) + { + return false; + } + return true; + } + eBoundingState test(const BoundingBox& other) const + { + eBoundingState rval; + if (testDisjoint(other)) + { + rval = eDisjoint; + } + else if (testSubset(other)) + { + rval = eSubset; + } + else if (testSuperset(other)) + { + rval = eSuperset; + } + else + { + rval = eUnion; + } + return rval; + } + + void set(const vec3_t mins, const vec3_t maxs) + { + VectorCopy(mins, m_Mins); + VectorCopy(maxs, m_Maxs); + } + void reset() + { + VectorFill(m_Mins, 999999999.999); + VectorFill(m_Maxs, -999999999.999); + } + void add(const vec3_t point) + { + m_Mins[0] = qmin(m_Mins[0], point[0]); + m_Maxs[0] = qmax(m_Maxs[0], point[0]); + m_Mins[1] = qmin(m_Mins[1], point[1]); + m_Maxs[1] = qmax(m_Maxs[1], point[1]); + m_Mins[2] = qmin(m_Mins[2], point[2]); + m_Maxs[2] = qmax(m_Maxs[2], point[2]); + } + void add(const BoundingBox& other) + { + add(other.m_Mins); + add(other.m_Maxs); + } + +public: + // BoundingBox(const BoundingBox& other) // Default copy constructor ok + // BoundingBox& operator=(const BoundingBox& other); // Default copy operator ok + BoundingBox() + { + reset(); + } + BoundingBox(const vec3_t& mins, const vec3_t& maxs) + { + VectorCopy(mins, m_Mins); + VectorCopy(maxs, m_Maxs); + } + ~BoundingBox() {} + +public: + // Bounding box + vec3_t m_Mins; + vec3_t m_Maxs; +}; + +#endif//BOUNDINGBOX_H__ \ No newline at end of file diff --git a/src/zhlt-vluzacn/common/bspfile.cpp b/src/zhlt-vluzacn/common/bspfile.cpp new file mode 100644 index 0000000..cd35d3d --- /dev/null +++ b/src/zhlt-vluzacn/common/bspfile.cpp @@ -0,0 +1,2024 @@ +#include "cmdlib.h" +#include "filelib.h" +#include "messages.h" +#include "hlassert.h" +#include "log.h" +#include "mathlib.h" +#include "bspfile.h" +#include "scriplib.h" +#include "blockmem.h" + +//============================================================================= + +int g_max_map_miptex = DEFAULT_MAX_MAP_MIPTEX; +int g_max_map_lightdata = DEFAULT_MAX_MAP_LIGHTDATA; + +int g_nummodels; +dmodel_t g_dmodels[MAX_MAP_MODELS]; +int g_dmodels_checksum; + +int g_visdatasize; +byte g_dvisdata[MAX_MAP_VISIBILITY]; +int g_dvisdata_checksum; + +int g_lightdatasize; +byte* g_dlightdata; +int g_dlightdata_checksum; + +int g_texdatasize; +byte* g_dtexdata; // (dmiptexlump_t) +int g_dtexdata_checksum; + +int g_entdatasize; +char g_dentdata[MAX_MAP_ENTSTRING]; +int g_dentdata_checksum; + +int g_numleafs; +dleaf_t g_dleafs[MAX_MAP_LEAFS]; +int g_dleafs_checksum; + +int g_numplanes; +dplane_t g_dplanes[MAX_INTERNAL_MAP_PLANES]; +int g_dplanes_checksum; + +int g_numvertexes; +dvertex_t g_dvertexes[MAX_MAP_VERTS]; +int g_dvertexes_checksum; + +int g_numnodes; +dnode_t g_dnodes[MAX_MAP_NODES]; +int g_dnodes_checksum; + +int g_numtexinfo; +#ifdef HLCSG_HLBSP_REDUCETEXTURE +texinfo_t g_texinfo[MAX_INTERNAL_MAP_TEXINFO]; +#else +texinfo_t g_texinfo[MAX_MAP_TEXINFO]; +#endif +int g_texinfo_checksum; + +int g_numfaces; +dface_t g_dfaces[MAX_MAP_FACES]; +int g_dfaces_checksum; + +#ifdef ZHLT_XASH2 +int g_numclipnodes[MAX_MAP_HULLS - 1]; +dclipnode_t g_dclipnodes[MAX_MAP_HULLS - 1][MAX_MAP_CLIPNODES]; +int g_dclipnodes_checksum[MAX_MAP_HULLS - 1]; +#else +int g_numclipnodes; +dclipnode_t g_dclipnodes[MAX_MAP_CLIPNODES]; +int g_dclipnodes_checksum; +#endif + +int g_numedges; +dedge_t g_dedges[MAX_MAP_EDGES]; +int g_dedges_checksum; + +int g_nummarksurfaces; +unsigned short g_dmarksurfaces[MAX_MAP_MARKSURFACES]; +int g_dmarksurfaces_checksum; + +int g_numsurfedges; +int g_dsurfedges[MAX_MAP_SURFEDGES]; +int g_dsurfedges_checksum; + +int g_numentities; +entity_t g_entities[MAX_MAP_ENTITIES]; + +/* + * =============== + * FastChecksum + * =============== + */ + +static int FastChecksum(const void* const buffer, int bytes) +{ + int checksum = 0; + char* buf = (char*)buffer; + + while (bytes--) + { + checksum = rotl(checksum, 4) ^ (*buf); + buf++; + } + + return checksum; +} + +/* + * =============== + * CompressVis + * =============== + */ +int CompressVis(const byte* const src, const unsigned int src_length, byte* dest, unsigned int dest_length) +{ + unsigned int j; + byte* dest_p = dest; + unsigned int current_length = 0; + + for (j = 0; j < src_length; j++) + { + current_length++; + hlassume(current_length <= dest_length, assume_COMPRESSVIS_OVERFLOW); + + *dest_p = src[j]; + dest_p++; + + if (src[j]) + { + continue; + } + + unsigned char rep = 1; + + for (j++; j < src_length; j++) + { + if (src[j] || rep == 255) + { + break; + } + else + { + rep++; + } + } + current_length++; + hlassume(current_length <= dest_length, assume_COMPRESSVIS_OVERFLOW); + + *dest_p = rep; + dest_p++; + j--; + } + + return dest_p - dest; +} + +// ===================================================================================== +// DecompressVis +// +// ===================================================================================== +void DecompressVis(const byte* src, byte* const dest, const unsigned int dest_length) +{ + unsigned int current_length = 0; + int c; + byte* out; + int row; + +#ifdef ZHLT_DecompressVis_FIX + row = (g_dmodels[0].visleafs + 7) >> 3; // same as the length used by VIS program in CompressVis + // The wrong size will cause DecompressVis to spend extremely long time once the source pointer runs into the invalid area in g_dvisdata (for example, in BuildFaceLights, some faces could hang for a few seconds), and sometimes to crash. +#else + row = (g_numleafs + 7) >> 3; +#endif + out = dest; + + do + { +#ifdef ZHLT_DecompressVis_FIX + hlassume (src - g_dvisdata < g_visdatasize, assume_DECOMPRESSVIS_OVERFLOW); +#endif + if (*src) + { + current_length++; + hlassume(current_length <= dest_length, assume_DECOMPRESSVIS_OVERFLOW); + + *out = *src; + out++; + src++; + continue; + } + +#ifdef ZHLT_DecompressVis_FIX + hlassume (&src[1] - g_dvisdata < g_visdatasize, assume_DECOMPRESSVIS_OVERFLOW); +#endif + c = src[1]; + src += 2; + while (c) + { + current_length++; + hlassume(current_length <= dest_length, assume_DECOMPRESSVIS_OVERFLOW); + + *out = 0; + out++; + c--; + + if (out - dest >= row) + { + return; + } + } + } + while (out - dest < row); +} + +// +// ===================================================================================== +// + +// ===================================================================================== +// SwapBSPFile +// byte swaps all data in a bsp file +// ===================================================================================== +static void SwapBSPFile(const bool todisk) +{ + int i, j, c; + dmodel_t* d; + dmiptexlump_t* mtl; + + // models + for (i = 0; i < g_nummodels; i++) + { + d = &g_dmodels[i]; + + for (j = 0; j < MAX_MAP_HULLS; j++) + { + d->headnode[j] = LittleLong(d->headnode[j]); + } + + d->visleafs = LittleLong(d->visleafs); + d->firstface = LittleLong(d->firstface); + d->numfaces = LittleLong(d->numfaces); + + for (j = 0; j < 3; j++) + { + d->mins[j] = LittleFloat(d->mins[j]); + d->maxs[j] = LittleFloat(d->maxs[j]); + d->origin[j] = LittleFloat(d->origin[j]); + } + } + + // + // vertexes + // + for (i = 0; i < g_numvertexes; i++) + { + for (j = 0; j < 3; j++) + { + g_dvertexes[i].point[j] = LittleFloat(g_dvertexes[i].point[j]); + } + } + + // + // planes + // + for (i = 0; i < g_numplanes; i++) + { + for (j = 0; j < 3; j++) + { + g_dplanes[i].normal[j] = LittleFloat(g_dplanes[i].normal[j]); + } + g_dplanes[i].dist = LittleFloat(g_dplanes[i].dist); + g_dplanes[i].type = (planetypes)LittleLong(g_dplanes[i].type); + } + + // + // texinfos + // + for (i = 0; i < g_numtexinfo; i++) + { + for (j = 0; j < 8; j++) + { + g_texinfo[i].vecs[0][j] = LittleFloat(g_texinfo[i].vecs[0][j]); + } + g_texinfo[i].miptex = LittleLong(g_texinfo[i].miptex); + g_texinfo[i].flags = LittleLong(g_texinfo[i].flags); + } + + // + // faces + // + for (i = 0; i < g_numfaces; i++) + { + g_dfaces[i].texinfo = LittleShort(g_dfaces[i].texinfo); + g_dfaces[i].planenum = LittleShort(g_dfaces[i].planenum); + g_dfaces[i].side = LittleShort(g_dfaces[i].side); + g_dfaces[i].lightofs = LittleLong(g_dfaces[i].lightofs); + g_dfaces[i].firstedge = LittleLong(g_dfaces[i].firstedge); + g_dfaces[i].numedges = LittleShort(g_dfaces[i].numedges); + } + + // + // nodes + // + for (i = 0; i < g_numnodes; i++) + { + g_dnodes[i].planenum = LittleLong(g_dnodes[i].planenum); + for (j = 0; j < 3; j++) + { + g_dnodes[i].mins[j] = LittleShort(g_dnodes[i].mins[j]); + g_dnodes[i].maxs[j] = LittleShort(g_dnodes[i].maxs[j]); + } + g_dnodes[i].children[0] = LittleShort(g_dnodes[i].children[0]); + g_dnodes[i].children[1] = LittleShort(g_dnodes[i].children[1]); + g_dnodes[i].firstface = LittleShort(g_dnodes[i].firstface); + g_dnodes[i].numfaces = LittleShort(g_dnodes[i].numfaces); + } + + // + // leafs + // + for (i = 0; i < g_numleafs; i++) + { + g_dleafs[i].contents = LittleLong(g_dleafs[i].contents); + for (j = 0; j < 3; j++) + { + g_dleafs[i].mins[j] = LittleShort(g_dleafs[i].mins[j]); + g_dleafs[i].maxs[j] = LittleShort(g_dleafs[i].maxs[j]); + } + + g_dleafs[i].firstmarksurface = LittleShort(g_dleafs[i].firstmarksurface); + g_dleafs[i].nummarksurfaces = LittleShort(g_dleafs[i].nummarksurfaces); + g_dleafs[i].visofs = LittleLong(g_dleafs[i].visofs); + } + + // + // clipnodes + // +#ifdef ZHLT_XASH2 + for (int hull = 1; hull < MAX_MAP_HULLS; hull++) + { + for (i = 0; i < g_numclipnodes[hull - 1]; i++) + { + g_dclipnodes[hull - 1][i].planenum = LittleLong(g_dclipnodes[hull - 1][i].planenum); + g_dclipnodes[hull - 1][i].children[0] = LittleShort(g_dclipnodes[hull - 1][i].children[0]); + g_dclipnodes[hull - 1][i].children[1] = LittleShort(g_dclipnodes[hull - 1][i].children[1]); + } + } +#else + for (i = 0; i < g_numclipnodes; i++) + { + g_dclipnodes[i].planenum = LittleLong(g_dclipnodes[i].planenum); + g_dclipnodes[i].children[0] = LittleShort(g_dclipnodes[i].children[0]); + g_dclipnodes[i].children[1] = LittleShort(g_dclipnodes[i].children[1]); + } +#endif + + // + // miptex + // + if (g_texdatasize) + { + mtl = (dmiptexlump_t*)g_dtexdata; + if (todisk) + { + c = mtl->nummiptex; + } + else + { + c = LittleLong(mtl->nummiptex); + } + mtl->nummiptex = LittleLong(mtl->nummiptex); + for (i = 0; i < c; i++) + { + mtl->dataofs[i] = LittleLong(mtl->dataofs[i]); + } + } + + // + // marksurfaces + // + for (i = 0; i < g_nummarksurfaces; i++) + { + g_dmarksurfaces[i] = LittleShort(g_dmarksurfaces[i]); + } + + // + // surfedges + // + for (i = 0; i < g_numsurfedges; i++) + { + g_dsurfedges[i] = LittleLong(g_dsurfedges[i]); + } + + // + // edges + // + for (i = 0; i < g_numedges; i++) + { + g_dedges[i].v[0] = LittleShort(g_dedges[i].v[0]); + g_dedges[i].v[1] = LittleShort(g_dedges[i].v[1]); + } +} + +// ===================================================================================== +// CopyLump +// balh +// ===================================================================================== +static int CopyLump(int lump, void* dest, int size, const dheader_t* const header) +{ + int length, ofs; + + length = header->lumps[lump].filelen; + ofs = header->lumps[lump].fileofs; + + if (length % size) + { + Error("LoadBSPFile: odd lump size"); + } + + //special handling for tex and lightdata to keep things from exploding - KGP + if(lump == LUMP_TEXTURES && dest == (void*)g_dtexdata) + { hlassume(g_max_map_miptex > length,assume_MAX_MAP_MIPTEX); } + else if(lump == LUMP_LIGHTING && dest == (void*)g_dlightdata) + { hlassume(g_max_map_lightdata > length,assume_MAX_MAP_LIGHTING); } + + memcpy(dest, (byte*) header + ofs, length); + + return length / size; +} + + +// ===================================================================================== +// LoadBSPFile +// balh +// ===================================================================================== +void LoadBSPFile(const char* const filename) +{ + dheader_t* header; + LoadFile(filename, (char**)&header); + LoadBSPImage(header); +} + +// ===================================================================================== +// LoadBSPImage +// balh +// ===================================================================================== +void LoadBSPImage(dheader_t* const header) +{ + unsigned int i; + + // swap the header + for (i = 0; i < sizeof(dheader_t) / 4; i++) + { + ((int*)header)[i] = LittleLong(((int*)header)[i]); + } + + if (header->version != BSPVERSION) + { + Error("BSP is version %i, not %i", header->version, BSPVERSION); + } + + g_nummodels = CopyLump(LUMP_MODELS, g_dmodels, sizeof(dmodel_t), header); + g_numvertexes = CopyLump(LUMP_VERTEXES, g_dvertexes, sizeof(dvertex_t), header); + g_numplanes = CopyLump(LUMP_PLANES, g_dplanes, sizeof(dplane_t), header); + g_numleafs = CopyLump(LUMP_LEAFS, g_dleafs, sizeof(dleaf_t), header); + g_numnodes = CopyLump(LUMP_NODES, g_dnodes, sizeof(dnode_t), header); + g_numtexinfo = CopyLump(LUMP_TEXINFO, g_texinfo, sizeof(texinfo_t), header); +#ifdef ZHLT_XASH2 + for (int hull = 1; hull < MAX_MAP_HULLS; hull++) + { + int lump; + switch (hull) + { + case 1: lump = LUMP_CLIPNODES; break; + case 2: lump = LUMP_CLIPNODES2; break; + case 3: lump = LUMP_CLIPNODES3; break; + default: + Error ("bad hull number %d", hull); + break; + } + g_numclipnodes[hull - 1] = CopyLump(lump, g_dclipnodes[hull - 1], sizeof(dclipnode_t), header); + } +#else + g_numclipnodes = CopyLump(LUMP_CLIPNODES, g_dclipnodes, sizeof(dclipnode_t), header); +#endif + g_numfaces = CopyLump(LUMP_FACES, g_dfaces, sizeof(dface_t), header); + g_nummarksurfaces = CopyLump(LUMP_MARKSURFACES, g_dmarksurfaces, sizeof(g_dmarksurfaces[0]), header); + g_numsurfedges = CopyLump(LUMP_SURFEDGES, g_dsurfedges, sizeof(g_dsurfedges[0]), header); + g_numedges = CopyLump(LUMP_EDGES, g_dedges, sizeof(dedge_t), header); + g_texdatasize = CopyLump(LUMP_TEXTURES, g_dtexdata, 1, header); + g_visdatasize = CopyLump(LUMP_VISIBILITY, g_dvisdata, 1, header); + g_lightdatasize = CopyLump(LUMP_LIGHTING, g_dlightdata, 1, header); + g_entdatasize = CopyLump(LUMP_ENTITIES, g_dentdata, 1, header); + + Free(header); // everything has been copied out + + // + // swap everything + // + SwapBSPFile(false); + + g_dmodels_checksum = FastChecksum(g_dmodels, g_nummodels * sizeof(g_dmodels[0])); + g_dvertexes_checksum = FastChecksum(g_dvertexes, g_numvertexes * sizeof(g_dvertexes[0])); + g_dplanes_checksum = FastChecksum(g_dplanes, g_numplanes * sizeof(g_dplanes[0])); + g_dleafs_checksum = FastChecksum(g_dleafs, g_numleafs * sizeof(g_dleafs[0])); + g_dnodes_checksum = FastChecksum(g_dnodes, g_numnodes * sizeof(g_dnodes[0])); + g_texinfo_checksum = FastChecksum(g_texinfo, g_numtexinfo * sizeof(g_texinfo[0])); +#ifdef ZHLT_XASH2 + for (int hull = 1; hull < MAX_MAP_HULLS; hull++) + { + g_dclipnodes_checksum[hull - 1] = FastChecksum(g_dclipnodes[hull - 1], g_numclipnodes[hull - 1] * sizeof(g_dclipnodes[hull - 1][0])); + } +#else + g_dclipnodes_checksum = FastChecksum(g_dclipnodes, g_numclipnodes * sizeof(g_dclipnodes[0])); +#endif + g_dfaces_checksum = FastChecksum(g_dfaces, g_numfaces * sizeof(g_dfaces[0])); + g_dmarksurfaces_checksum = FastChecksum(g_dmarksurfaces, g_nummarksurfaces * sizeof(g_dmarksurfaces[0])); + g_dsurfedges_checksum = FastChecksum(g_dsurfedges, g_numsurfedges * sizeof(g_dsurfedges[0])); + g_dedges_checksum = FastChecksum(g_dedges, g_numedges * sizeof(g_dedges[0])); + g_dtexdata_checksum = FastChecksum(g_dtexdata, g_numedges * sizeof(g_dtexdata[0])); + g_dvisdata_checksum = FastChecksum(g_dvisdata, g_visdatasize * sizeof(g_dvisdata[0])); + g_dlightdata_checksum = FastChecksum(g_dlightdata, g_lightdatasize * sizeof(g_dlightdata[0])); + g_dentdata_checksum = FastChecksum(g_dentdata, g_entdatasize * sizeof(g_dentdata[0])); +} + +// +// ===================================================================================== +// + +// ===================================================================================== +// AddLump +// balh +// ===================================================================================== +static void AddLump(int lumpnum, void* data, int len, dheader_t* header, FILE* bspfile) +{ + lump_t* lump =&header->lumps[lumpnum]; + lump->fileofs = LittleLong(ftell(bspfile)); + lump->filelen = LittleLong(len); + SafeWrite(bspfile, data, (len + 3) & ~3); +} + +// ===================================================================================== +// WriteBSPFile +// Swaps the bsp file in place, so it should not be referenced again +// ===================================================================================== +void WriteBSPFile(const char* const filename) +{ + dheader_t outheader; + dheader_t* header; + FILE* bspfile; + + header = &outheader; + memset(header, 0, sizeof(dheader_t)); + + SwapBSPFile(true); + + header->version = LittleLong(BSPVERSION); + + bspfile = SafeOpenWrite(filename); + SafeWrite(bspfile, header, sizeof(dheader_t)); // overwritten later + + // LUMP TYPE DATA LENGTH HEADER BSPFILE + AddLump(LUMP_PLANES, g_dplanes, g_numplanes * sizeof(dplane_t), header, bspfile); + AddLump(LUMP_LEAFS, g_dleafs, g_numleafs * sizeof(dleaf_t), header, bspfile); + AddLump(LUMP_VERTEXES, g_dvertexes, g_numvertexes * sizeof(dvertex_t), header, bspfile); + AddLump(LUMP_NODES, g_dnodes, g_numnodes * sizeof(dnode_t), header, bspfile); + AddLump(LUMP_TEXINFO, g_texinfo, g_numtexinfo * sizeof(texinfo_t), header, bspfile); + AddLump(LUMP_FACES, g_dfaces, g_numfaces * sizeof(dface_t), header, bspfile); +#ifdef ZHLT_XASH2 + for (int hull = 1; hull < MAX_MAP_HULLS; hull++) + { + int lump; + switch (hull) + { + case 1: lump = LUMP_CLIPNODES; break; + case 2: lump = LUMP_CLIPNODES2; break; + case 3: lump = LUMP_CLIPNODES3; break; + default: + Error ("bad hull number %d", hull); + break; + } + AddLump(lump, g_dclipnodes[hull - 1], g_numclipnodes[hull - 1] * sizeof(dclipnode_t), header, bspfile); + } +#else + AddLump(LUMP_CLIPNODES, g_dclipnodes, g_numclipnodes * sizeof(dclipnode_t), header, bspfile); +#endif + + AddLump(LUMP_MARKSURFACES, g_dmarksurfaces, g_nummarksurfaces * sizeof(g_dmarksurfaces[0]), header, bspfile); + AddLump(LUMP_SURFEDGES, g_dsurfedges, g_numsurfedges * sizeof(g_dsurfedges[0]), header, bspfile); + AddLump(LUMP_EDGES, g_dedges, g_numedges * sizeof(dedge_t), header, bspfile); + AddLump(LUMP_MODELS, g_dmodels, g_nummodels * sizeof(dmodel_t), header, bspfile); + + AddLump(LUMP_LIGHTING, g_dlightdata, g_lightdatasize, header, bspfile); + AddLump(LUMP_VISIBILITY,g_dvisdata, g_visdatasize, header, bspfile); + AddLump(LUMP_ENTITIES, g_dentdata, g_entdatasize, header, bspfile); + AddLump(LUMP_TEXTURES, g_dtexdata, g_texdatasize, header, bspfile); + + fseek(bspfile, 0, SEEK_SET); + SafeWrite(bspfile, header, sizeof(dheader_t)); + + fclose(bspfile); +} + +#ifdef ZHLT_64BIT_FIX + +#ifdef PLATFORM_CAN_CALC_EXTENT +// ===================================================================================== +// GetFaceExtents (with PLATFORM_CAN_CALC_EXTENT on) +// ===================================================================================== +#ifdef SYSTEM_WIN32 +#ifdef VERSION_32BIT +static void CorrectFPUPrecision () +{ + unsigned int currentcontrol; + if (_controlfp_s (¤tcontrol, 0, 0)) + { + Warning ("Couldn't get FPU precision"); + } + else + { + unsigned int val = (currentcontrol & _MCW_PC); + if (val != _PC_53) + { + Warning ("FPU precision is %s. Setting to %s.", (val == _PC_24? "24": val == _PC_64? "64": "invalid"), "53"); + if (_controlfp_s (¤tcontrol, _PC_53, _MCW_PC) + || (currentcontrol & _MCW_PC) != _PC_53) + { + Warning ("Couldn't set FPU precision"); + } + } + } +} +#endif +#ifdef VERSION_64BIT +static void CorrectFPUPrecision () +{ + // do nothing, because we use SSE registers +} +#endif +#endif + +#ifdef SYSTEM_POSIX +static void CorrectFPUPrecision () +{ + // just leave it to default and see if CalcFaceExtents_test gives us any error +} +#endif + +float CalculatePointVecsProduct (const volatile float *point, const volatile float *vecs) +{ + volatile double val; + volatile double tmp; + + val = (double)point[0] * (double)vecs[0]; // always do one operation at a time and save to memory + tmp = (double)point[1] * (double)vecs[1]; + val = val + tmp; + tmp = (double)point[2] * (double)vecs[2]; + val = val + tmp; + val = val + (double)vecs[3]; + + return (float)val; +} + +bool CalcFaceExtents_test () +{ + const int numtestcases = 6; + volatile float testcases[numtestcases][8] = { + {1, 1, 1, 1, 0.375 * DBL_EPSILON, 0.375 * DBL_EPSILON, -1, 0}, + {1, 1, 1, 0.375 * DBL_EPSILON, 0.375 * DBL_EPSILON, 1, -1, DBL_EPSILON}, + {DBL_EPSILON, DBL_EPSILON, 1, 0.375, 0.375, 1, -1, DBL_EPSILON}, + {1, 1, 1, 1, 1, 0.375 * FLT_EPSILON, -2, 0.375 * FLT_EPSILON}, + {1, 1, 1, 1, 0.375 * FLT_EPSILON, 1, -2, 0.375 * FLT_EPSILON}, + {1, 1, 1, 0.375 * FLT_EPSILON, 1, 1, -2, 0.375 * FLT_EPSILON}}; + bool ok; + + // If the test failed, please check: + // 1. whether the calculation is performed on FPU + // 2. whether the register precision is too low + + CorrectFPUPrecision (); + + ok = true; + for (int i = 0; i < 6; i++) + { + float val = CalculatePointVecsProduct (&testcases[i][0], &testcases[i][3]); + if (val != testcases[i][7]) + { + Warning ("internal error: CalcFaceExtents_test failed on case %d (%.20f != %.20f).", i, val, testcases[i][7]); + ok = false; + } + } + return ok; +} + +void GetFaceExtents (int facenum, int mins_out[2], int maxs_out[2]) +{ + CorrectFPUPrecision (); + + dface_t *f; + float mins[2], maxs[2], val; + int i, j, e; + dvertex_t *v; + texinfo_t *tex; + int bmins[2], bmaxs[2]; + + f = &g_dfaces[facenum]; + + mins[0] = mins[1] = 999999; + maxs[0] = maxs[1] = -99999; + +#ifdef ZHLT_EMBEDLIGHTMAP + tex = &g_texinfo[ParseTexinfoForFace (f)]; +#else + tex = &g_texinfo[f->texinfo]; +#endif + + for (i = 0; i < f->numedges; i++) + { + e = g_dsurfedges[f->firstedge + i]; + if (e >= 0) + { + v = &g_dvertexes[g_dedges[e].v[0]]; + } + else + { + v = &g_dvertexes[g_dedges[-e].v[1]]; + } + for (j = 0; j < 2; j++) + { + // The old code: val = v->point[0] * tex->vecs[j][0] + v->point[1] * tex->vecs[j][1] + v->point[2] * tex->vecs[j][2] + tex->vecs[j][3]; + // was meant to be compiled for x86 under MSVC (prior to VS 11), so the intermediate values were stored as 64-bit double by default. + // The new code will produce the same result as the old code, but it's portable for different platforms. + // See this article for details: Intermediate Floating-Point Precision by Bruce-Dawson http://www.altdevblogaday.com/2012/03/22/intermediate-floating-point-precision/ + + // The essential reason for having this ugly code is to get exactly the same value as the counterpart of game engine. + // The counterpart of game engine is the function CalcFaceExtents in HLSDK. + // So we must also know how Valve compiles HLSDK. I think Valve compiles HLSDK with VC6.0 in the past. + val = CalculatePointVecsProduct (v->point, tex->vecs[j]); + if (val < mins[j]) + { + mins[j] = val; + } + if (val > maxs[j]) + { + maxs[j] = val; + } + } + } + + for (i = 0; i < 2; i++) + { + bmins[i] = (int)floor (mins[i] / TEXTURE_STEP); + bmaxs[i] = (int)ceil (maxs[i] / TEXTURE_STEP); + } + + for (i = 0; i < 2; i++) + { + mins_out[i] = bmins[i]; + maxs_out[i] = bmaxs[i]; + } +} + +// ===================================================================================== +// WriteExtentFile +// ===================================================================================== +void WriteExtentFile (const char *const filename) +{ + FILE *f; + f = fopen (filename, "w"); + if (!f) + { + Error ("Error opening %s: %s", filename, strerror(errno)); + } + fprintf (f, "%i\n", g_numfaces); + for (int i = 0; i < g_numfaces; i++) + { + int mins[2]; + int maxs[2]; + GetFaceExtents (i, mins, maxs); + fprintf (f, "%i %i %i %i\n", mins[0], mins[1], maxs[0], maxs[1]); + } + fclose (f); +} + +#else + +typedef struct +{ + int mins[2]; + int maxs[2]; +} +faceextent_t; + +bool g_faceextents_loaded = false; +faceextent_t g_faceextents[MAX_MAP_FACES]; //[g_numfaces] + +// ===================================================================================== +// LoadExtentFile +// ===================================================================================== +void LoadExtentFile (const char *const filename) +{ + FILE *f; + f = fopen (filename, "r"); + if (!f) + { + Error ("Error opening %s: %s", filename, strerror(errno)); + } + int count; + int numfaces; + count = fscanf (f, "%i\n", (int *)&numfaces); + if (count != 1) + { + Error ("LoadExtentFile (line %i): scanf failure", 1); + } + if (numfaces != g_numfaces) + { + Error ("LoadExtentFile: numfaces(%i) doesn't match g_numfaces(%i)", numfaces, g_numfaces); + } + for (int i = 0; i < g_numfaces; i++) + { + faceextent_t *e = &g_faceextents[i]; + count = fscanf (f, "%i %i %i %i\n", (int *)&e->mins[0], (int *)&e->mins[1], (int *)&e->maxs[0], (int *)&e->maxs[1]); + if (count != 4) + { + Error ("LoadExtentFile (line %i): scanf failure", i + 2); + } + } + fclose (f); + g_faceextents_loaded = true; +} + +// ===================================================================================== +// GetFaceExtents (with PLATFORM_CAN_CALC_EXTENT off) +// ===================================================================================== +// ZHLT_EMBEDLIGHTMAP: the result of "GetFaceExtents" and the values stored in ".ext" file should always be the original extents; +// the new extents of the "?_rad" textures should never appear ("?_rad" textures should be transparent to the tools). +// As a consequance, the reported AllocBlock might be inaccurate (usually falsely larger), but it accurately predicts the amount of AllocBlock after the embedded lightmaps are deleted. +void GetFaceExtents (int facenum, int mins_out[2], int maxs_out[2]) +{ + if (!g_faceextents_loaded) + { + Error ("GetFaceExtents: internal error: extent file has not been loaded."); + } + + faceextent_t *e = &g_faceextents[facenum]; + int i; + + for (i = 0; i < 2; i++) + { + mins_out[i] = e->mins[i]; + maxs_out[i] = e->maxs[i]; + } +} +#endif + +#endif +// +// ===================================================================================== +// +#ifdef ZHLT_CHART_AllocBlock +const int BLOCK_WIDTH = 128; +const int BLOCK_HEIGHT = 128; +typedef struct lightmapblock_s +{ + lightmapblock_s *next; + bool used; + int allocated[BLOCK_WIDTH]; +} +lightmapblock_t; +void DoAllocBlock (lightmapblock_t *blocks, int w, int h) +{ + lightmapblock_t *block; + // code from Quake + int i, j; + int best, best2; + int x, y; + if (w < 1 || h < 1) + { + Error ("DoAllocBlock: internal error."); + } + for (block = blocks; block; block = block->next) + { + best = BLOCK_HEIGHT; + for (i = 0; i < BLOCK_WIDTH - w; i++) + { + best2 = 0; + for (j = 0; j < w; j++) + { + if (block->allocated[i + j] >= best) + break; + if (block->allocated[i + j] > best2) + best2 = block->allocated[i + j]; + } + if (j == w) + { + x = i; + y = best = best2; + } + } + if (best + h <= BLOCK_HEIGHT) + { + block->used = true; + for (i = 0; i < w; i++) + { + block->allocated[x + i] = best + h; + } + return; + } + if (!block->next) + { // need to allocate a new block + if (!block->used) + { + Warning ("CountBlocks: invalid extents %dx%d", w, h); + return; + } + block->next = (lightmapblock_t *)malloc (sizeof (lightmapblock_t)); + hlassume (block->next != NULL, assume_NoMemory); + memset (block->next, 0, sizeof (lightmapblock_t)); + } + } +} +int CountBlocks () +{ +#ifdef ZHLT_64BIT_FIX +#if !defined (PLATFORM_CAN_CALC_EXTENT) && !defined (HLRAD) + return -1; // otherwise GetFaceExtents will error +#endif +#endif + lightmapblock_t *blocks; + blocks = (lightmapblock_t *)malloc (sizeof (lightmapblock_t)); + hlassume (blocks != NULL, assume_NoMemory); + memset (blocks, 0, sizeof (lightmapblock_t)); + int k; + for (k = 0; k < g_numfaces; k++) + { + dface_t *f = &g_dfaces[k]; +#ifdef ZHLT_EMBEDLIGHTMAP + const char *texname = GetTextureByNumber (ParseTexinfoForFace (f)); +#else + const char *texname = GetTextureByNumber (f->texinfo); +#endif + if (!strncmp (texname, "sky", 3) //sky, no lightmap allocation. + || !strncmp (texname, "!", 1) || !strncasecmp (texname, "water", 5) || !strncasecmp (texname, "laser", 5) //water, no lightmap allocation. +#ifdef ZHLT_EMBEDLIGHTMAP + || (g_texinfo[ParseTexinfoForFace (f)].flags & TEX_SPECIAL) //aaatrigger, I don't know. +#else + || (g_texinfo[f->texinfo].flags & TEX_SPECIAL) //aaatrigger, I don't know. +#endif + ) + { + continue; + } + int extents[2]; + vec3_t point; + { +#ifdef ZHLT_64BIT_FIX + int bmins[2]; + int bmaxs[2]; + int i; + GetFaceExtents (k, bmins, bmaxs); + for (i = 0; i < 2; i++) + { + extents[i] = (bmaxs[i] - bmins[i]) * TEXTURE_STEP; + } + + VectorClear (point); + if (f->numedges > 0) + { + int e = g_dsurfedges[f->firstedge]; + dvertex_t *v = &g_dvertexes[g_dedges[abs (e)].v[e >= 0? 0: 1]]; + VectorCopy (v->point, point); + } +#else + float mins[2], maxs[2]; + int bmins[2], bmaxs[2]; + texinfo_t *tex; + tex = &g_texinfo[f->texinfo]; + mins[0] = mins[1] = 999999; + maxs[0] = maxs[1] = -99999; + VectorClear (point); + int i; + for (i = 0; i < f->numedges; i++) + { + int e; + dvertex_t *v; + int j; + e = g_dsurfedges[f->firstedge + i]; + if (e >= 0) + { + v = &g_dvertexes[g_dedges[e].v[0]]; + } + else + { + v = &g_dvertexes[g_dedges[-e].v[1]]; + } + if (i == 0) + { + VectorCopy (v->point, point); + } + for (j = 0; j < 2; j++) + { + float val = v->point[0] * tex->vecs[j][0] + v->point[1] * tex->vecs[j][1] + + v->point[2] * tex->vecs[j][2] + tex->vecs[j][3]; + if (val < mins[j]) + { + mins[j] = val; + } + if (val > maxs[j]) + { + maxs[j] = val; + } + } + } + for (i = 0; i < 2; i++) + { + bmins[i] = floor (mins[i] / TEXTURE_STEP); + bmaxs[i] = ceil (maxs[i] / TEXTURE_STEP); + extents[i] = (bmaxs[i] - bmins[i]) * TEXTURE_STEP; + } +#endif + } + if (extents[0] < 0 || extents[1] < 0 || extents[0] > qmax (512, MAX_SURFACE_EXTENT * TEXTURE_STEP) || extents[1] > qmax (512, MAX_SURFACE_EXTENT * TEXTURE_STEP)) + // the default restriction from the engine is 512, but place 'max (512, MAX_SURFACE_EXTENT * TEXTURE_STEP)' here in case someone raise the limit + { + Warning ("Bad surface extents %d/%d at position (%.0f,%.0f,%.0f)", extents[0], extents[1], point[0], point[1], point[2]); + continue; + } + DoAllocBlock (blocks, (extents[0] / TEXTURE_STEP) + 1, (extents[1] / TEXTURE_STEP) + 1); + } + int count = 0; + lightmapblock_t *next; + for (; blocks; blocks = next) + { + if (blocks->used) + { + count++; + } + next = blocks->next; + free (blocks); + } + return count; +} +#endif +#ifdef ZHLT_CHART_WADFILES +bool NoWadTextures () +{ + // copied from loadtextures.cpp + int numtextures = g_texdatasize? ((dmiptexlump_t *)g_dtexdata)->nummiptex: 0; + for (int i = 0; i < numtextures; i++) + { + int offset = ((dmiptexlump_t *)g_dtexdata)->dataofs[i]; + int size = g_texdatasize - offset; + if (offset < 0 || size < (int)sizeof (miptex_t)) + { + // missing textures have ofs -1 + continue; + } + miptex_t *mt = (miptex_t *)&g_dtexdata[offset]; + if (!mt->offsets[0]) + { + return false; + } + } + return true; +} +char *FindWadValue () + // return NULL for syntax error + // this function needs to be as stable as possible because it might be called from ripent +{ + int linestart, lineend; + bool inentity = false; + for (linestart = 0; linestart < g_entdatasize; ) + { + for (lineend = linestart; lineend < g_entdatasize; lineend++) + if (g_dentdata[lineend] == '\r' || g_dentdata[lineend] == '\n') + break; + if (lineend == linestart + 1) + { + if (g_dentdata[linestart] == '{') + { + if (inentity) + return NULL; + inentity = true; + } + else if (g_dentdata[linestart] == '}') + { + if (!inentity) + return NULL; + inentity = false; + return _strdup (""); // only parse the first entity + } + else + return NULL; + } + else + { + if (!inentity) + return NULL; + int quotes[4]; + int i, j; + for (i = 0, j = linestart; i < 4; i++, j++) + { + for (; j < lineend; j++) + if (g_dentdata[j] == '\"') + break; + if (j >= lineend) + break; + quotes[i] = j; + } + if (i != 4 || quotes[0] != linestart || quotes[3] != lineend - 1) + { + return NULL; + } + if (quotes[1] - (quotes[0] + 1) == (int)strlen ("wad") && !strncmp (&g_dentdata[quotes[0] + 1], "wad", strlen ("wad"))) + { + int len = quotes[3] - (quotes[2] + 1); + char *value = (char *)malloc (len + 1); + hlassume (value != NULL, assume_NoMemory); + memcpy (value, &g_dentdata[quotes[2] + 1], len); + value[len] = '\0'; + return value; + } + } + for (linestart = lineend; linestart < g_entdatasize; linestart++) + if (g_dentdata[linestart] != '\r' && g_dentdata[linestart] != '\n') + break; + } + return NULL; +} +#endif + +#define ENTRIES(a) (sizeof(a)/sizeof(*(a))) +#define ENTRYSIZE(a) (sizeof(*(a))) + +// ===================================================================================== +// ArrayUsage +// blah +// ===================================================================================== +static int ArrayUsage(const char* const szItem, const int items, const int maxitems, const int itemsize) +{ + float percentage = maxitems ? items * 100.0 / maxitems : 0.0; + +#ifdef ZHLT_MAX_MAP_LEAFS + Log("%-13s %7i/%-7i %8i/%-8i (%4.1f%%)\n", szItem, items, maxitems, items * itemsize, maxitems * itemsize, percentage); +#else + Log("%-12s %7i/%-7i %7i/%-7i (%4.1f%%)\n", szItem, items, maxitems, items * itemsize, maxitems * itemsize, percentage); +#endif + + return items * itemsize; +} + +// ===================================================================================== +// GlobUsage +// pritn out global ussage line in chart +// ===================================================================================== +static int GlobUsage(const char* const szItem, const int itemstorage, const int maxstorage) +{ + float percentage = maxstorage ? itemstorage * 100.0 / maxstorage : 0.0; + +#ifdef ZHLT_MAX_MAP_LEAFS + Log("%-13s [variable] %8i/%-8i (%4.1f%%)\n", szItem, itemstorage, maxstorage, percentage); +#else + Log("%-12s [variable] %7i/%-7i (%4.1f%%)\n", szItem, itemstorage, maxstorage, percentage); +#endif + + return itemstorage; +} + +// ===================================================================================== +// PrintBSPFileSizes +// Dumps info about current file +// ===================================================================================== +void PrintBSPFileSizes() +{ + int numtextures = g_texdatasize ? ((dmiptexlump_t*)g_dtexdata)->nummiptex : 0; + int totalmemory = 0; +#ifdef ZHLT_CHART_AllocBlock + int numallocblocks = CountBlocks (); + int maxallocblocks = 64; +#endif +#ifdef ZHLT_CHART_WADFILES + bool nowadtextures = NoWadTextures (); // We don't have this check at hlcsg, because only legacy compile tools don't empty "wad" value in "-nowadtextures" compiles. + char *wadvalue = FindWadValue (); +#endif + + Log("\n"); + Log("Object names Objects/Maxobjs Memory / Maxmem Fullness\n"); + Log("------------ --------------- --------------- --------\n"); + + totalmemory += ArrayUsage("models", g_nummodels, ENTRIES(g_dmodels), ENTRYSIZE(g_dmodels)); + totalmemory += ArrayUsage("planes", g_numplanes, MAX_MAP_PLANES, ENTRYSIZE(g_dplanes)); + totalmemory += ArrayUsage("vertexes", g_numvertexes, ENTRIES(g_dvertexes), ENTRYSIZE(g_dvertexes)); + totalmemory += ArrayUsage("nodes", g_numnodes, ENTRIES(g_dnodes), ENTRYSIZE(g_dnodes)); +#ifdef HLCSG_HLBSP_REDUCETEXTURE + totalmemory += ArrayUsage("texinfos", g_numtexinfo, MAX_MAP_TEXINFO, ENTRYSIZE(g_texinfo)); +#else + totalmemory += ArrayUsage("texinfos", g_numtexinfo, ENTRIES(g_texinfo), ENTRYSIZE(g_texinfo)); +#endif + totalmemory += ArrayUsage("faces", g_numfaces, ENTRIES(g_dfaces), ENTRYSIZE(g_dfaces)); +#ifdef ZHLT_WARNWORLDFACES + totalmemory += ArrayUsage("* worldfaces", (g_nummodels > 0? g_dmodels[0].numfaces: 0), MAX_MAP_WORLDFACES, 0); +#endif +#ifdef ZHLT_XASH2 + for (int hull = 1; hull < MAX_MAP_HULLS; hull++) + { + char buffer[32]; + sprintf (buffer, "clipnodes%d", hull); + totalmemory += ArrayUsage(buffer, g_numclipnodes[hull - 1], ENTRIES(g_dclipnodes[hull - 1]), ENTRYSIZE(g_dclipnodes[hull - 1])); + } +#else + totalmemory += ArrayUsage("clipnodes", g_numclipnodes, ENTRIES(g_dclipnodes), ENTRYSIZE(g_dclipnodes)); +#endif +#ifdef ZHLT_MAX_MAP_LEAFS + totalmemory += ArrayUsage("leaves", g_numleafs, MAX_MAP_LEAFS, ENTRYSIZE(g_dleafs)); + totalmemory += ArrayUsage("* worldleaves", (g_nummodels > 0? g_dmodels[0].visleafs: 0), MAX_MAP_LEAFS_ENGINE, 0); +#else + totalmemory += ArrayUsage("leaves", g_numleafs, ENTRIES(g_dleafs), ENTRYSIZE(g_dleafs)); +#endif + totalmemory += ArrayUsage("marksurfaces", g_nummarksurfaces, ENTRIES(g_dmarksurfaces), ENTRYSIZE(g_dmarksurfaces)); + totalmemory += ArrayUsage("surfedges", g_numsurfedges, ENTRIES(g_dsurfedges), ENTRYSIZE(g_dsurfedges)); + totalmemory += ArrayUsage("edges", g_numedges, ENTRIES(g_dedges), ENTRYSIZE(g_dedges)); + + totalmemory += GlobUsage("texdata", g_texdatasize, g_max_map_miptex); + totalmemory += GlobUsage("lightdata", g_lightdatasize, g_max_map_lightdata); + totalmemory += GlobUsage("visdata", g_visdatasize, sizeof(g_dvisdata)); + totalmemory += GlobUsage("entdata", g_entdatasize, sizeof(g_dentdata)); +#ifdef ZHLT_CHART_AllocBlock +#ifdef ZHLT_64BIT_FIX + if (numallocblocks == -1) + { + Log ("* AllocBlock [ not available to the " PLATFORM_VERSIONSTRING " version ]\n"); + } + else + { +#endif + totalmemory += ArrayUsage ("* AllocBlock", numallocblocks, maxallocblocks, 0); +#ifdef ZHLT_64BIT_FIX + } +#endif +#endif + + Log("%i textures referenced\n", numtextures); + + Log("=== Total BSP file data space used: %d bytes ===\n", totalmemory); +#ifdef ZHLT_CHART_WADFILES + if (nowadtextures) + { + Log ("Wad files required to run the map: (None)\n"); + } + else if (wadvalue == NULL) + { + Log ("Wad files required to run the map: (Couldn't parse wad keyvalue from entity data)\n"); + } + else + { + Log ("Wad files required to run the map: \"%s\"\n", wadvalue); + } + if (wadvalue) + { + free (wadvalue); + } +#endif +} + + +#ifdef ZHLT_EMBEDLIGHTMAP +// ===================================================================================== +// ParseImplicitTexinfoFromTexture +// purpose: get the actual texinfo for a face. the tools shouldn't directly use f->texinfo after embedlightmap is done +// ===================================================================================== +int ParseImplicitTexinfoFromTexture (int miptex) +{ + int texinfo; + int numtextures = g_texdatasize? ((dmiptexlump_t *)g_dtexdata)->nummiptex: 0; + int offset; + int size; + miptex_t *mt; + char name[16]; + + if (miptex < 0 || miptex >= numtextures) + { + Warning ("ParseImplicitTexinfoFromTexture: internal error: invalid texture number %d.", miptex); + return -1; + } + offset = ((dmiptexlump_t *)g_dtexdata)->dataofs[miptex]; + size = g_texdatasize - offset; + if (offset < 0 || g_dtexdata + offset < (byte *)&((dmiptexlump_t *)g_dtexdata)->dataofs[numtextures] || + size < (int)sizeof (miptex_t)) + { + return -1; + } + + mt = (miptex_t *)&g_dtexdata[offset]; + safe_strncpy (name, mt->name, 16); + + if (!(strlen (name) >= 6 && !strncasecmp (&name[1], "_rad", 4) && '0' <= name[5] && name[5] <= '9')) + { + return -1; + } + + texinfo = atoi (&name[5]); + if (texinfo < 0 || texinfo >= g_numtexinfo) + { + Warning ("Invalid index of original texinfo: %d parsed from texture name '%s'.", texinfo, name); + return -1; + } + + return texinfo; +} + +int ParseTexinfoForFace (const dface_t *f) +{ + int texinfo; + int miptex; + int texinfo2; + + texinfo = f->texinfo; + miptex = g_texinfo[texinfo].miptex; + if (miptex != -1) + { + texinfo2 = ParseImplicitTexinfoFromTexture (miptex); + if (texinfo2 != -1) + { + texinfo = texinfo2; + } + } + + return texinfo; +} + +// ===================================================================================== +// DeleteEmbeddedLightmaps +// removes all "?_rad*" textures that are created by hlrad +// this function does nothing if the map has no textures with name "?_rad*" +// ===================================================================================== +void DeleteEmbeddedLightmaps () +{ + int countrestoredfaces = 0; + int countremovedtexinfos = 0; + int countremovedtextures = 0; + int i; + int numtextures = g_texdatasize? ((dmiptexlump_t *)g_dtexdata)->nummiptex: 0; + + // Step 1: parse the original texinfo index stored in each "?_rad*" texture + // and restore the texinfo for the faces that have had their lightmap embedded + + for (i = 0; i < g_numfaces; i++) + { + dface_t *f = &g_dfaces[i]; + int texinfo; + + texinfo = ParseTexinfoForFace (f); + if (texinfo != f->texinfo) + { + f->texinfo = texinfo; + countrestoredfaces++; + } + } + + // Step 2: remove redundant texinfo + { + bool *texinfoused = (bool *)malloc (g_numtexinfo * sizeof (bool)); + hlassume (texinfoused != NULL, assume_NoMemory); + + for (i = 0; i < g_numtexinfo; i++) + { + texinfoused[i] = false; + } + for (i = 0; i < g_numfaces; i++) + { + dface_t *f = &g_dfaces[i]; + + if (f->texinfo < 0 || f->texinfo >= g_numtexinfo) + { + continue; + } + texinfoused[f->texinfo] = true; + } + for (i = g_numtexinfo - 1; i > -1; i--) + { + texinfo_t *info = &g_texinfo[i]; + + if (texinfoused[i]) + { + break; // still used by a face; should not remove this texinfo + } + if (info->miptex < 0 || info->miptex >= numtextures) + { + break; // invalid; should not remove this texinfo + } + if (ParseImplicitTexinfoFromTexture (info->miptex) == -1) + { + break; // not added by hlrad; should not remove this texinfo + } + countremovedtexinfos++; + } + g_numtexinfo = i + 1; // shrink g_texinfo + free (texinfoused); + } + + // Step 3: remove redundant textures + { + int numremaining; // number of remaining textures + bool *textureused = (bool *)malloc (numtextures * sizeof (bool)); + hlassume (textureused != NULL, assume_NoMemory); + + for (i = 0; i < numtextures; i++) + { + textureused[i] = false; + } + for (i = 0; i < g_numtexinfo; i++) + { + texinfo_t *info = &g_texinfo[i]; + + if (info->miptex < 0 || info->miptex >= numtextures) + { + continue; + } + textureused[info->miptex] = true; + } + for (i = numtextures - 1; i > -1; i--) + { + if (textureused[i] || ParseImplicitTexinfoFromTexture (i) == -1) + { + break; // should not remove this texture + } + countremovedtextures++; + } + numremaining = i + 1; + free (textureused); + + if (numremaining < numtextures) + { + dmiptexlump_t *texdata = (dmiptexlump_t *)g_dtexdata; + byte *dataaddr = (byte *)&texdata->dataofs[texdata->nummiptex]; + int datasize = (g_dtexdata + texdata->dataofs[numremaining]) - dataaddr; + byte *newdataaddr = (byte *)&texdata->dataofs[numremaining]; + memmove (newdataaddr, dataaddr, datasize); + g_texdatasize = (newdataaddr + datasize) - g_dtexdata; + texdata->nummiptex = numremaining; + for (i = 0; i < numremaining; i++) + { + if (texdata->dataofs[i] < 0) // bad texture + { + continue; + } + texdata->dataofs[i] += newdataaddr - dataaddr; + } + + numtextures = texdata->nummiptex; + } + } + + if (countrestoredfaces > 0 || countremovedtexinfos > 0 || countremovedtextures > 0) + { + Log ("DeleteEmbeddedLightmaps: restored %d faces, removed %d texinfos and %d textures.\n", + countrestoredfaces, countremovedtexinfos, countremovedtextures); + } +} + + +#endif +// ===================================================================================== +// ParseEpair +// entity key/value pairs +// ===================================================================================== +epair_t* ParseEpair() +{ + epair_t* e; + + e = (epair_t*)Alloc(sizeof(epair_t)); + + if (strlen(g_token) >= MAX_KEY - 1) + Error("ParseEpair: Key token too long (%i > MAX_KEY)", (int)strlen(g_token)); + + e->key = _strdup(g_token); + GetToken(false); + + if (strlen(g_token) >= MAX_VAL - 1) //MAX_VALUE //vluzacn + Error("ParseEpar: Value token too long (%i > MAX_VALUE)", (int)strlen(g_token)); + + e->value = _strdup(g_token); + + return e; +} + +/* + * ================ + * ParseEntity + * ================ + */ + +#ifdef ZHLT_INFO_COMPILE_PARAMETERS +// AJM: each tool should have its own version of GetParamsFromEnt which parseentity calls +extern void GetParamsFromEnt(entity_t* mapent); +#endif + +bool ParseEntity() +{ + epair_t* e; + entity_t* mapent; + + if (!GetToken(true)) + { + return false; + } + + if (strcmp(g_token, "{")) + { + Error("ParseEntity: { not found"); + } + + if (g_numentities == MAX_MAP_ENTITIES) + { + Error("g_numentities == MAX_MAP_ENTITIES"); + } + + mapent = &g_entities[g_numentities]; + g_numentities++; + + while (1) + { + if (!GetToken(true)) + { + Error("ParseEntity: EOF without closing brace"); + } + if (!strcmp(g_token, "}")) + { + break; + } + e = ParseEpair(); + e->next = mapent->epairs; + mapent->epairs = e; + } + +#ifdef ZHLT_INFO_COMPILE_PARAMETERS // AJM + if (!strcmp(ValueForKey(mapent, "classname"), "info_compile_parameters")) + { + Log("Map entity info_compile_parameters detected, using compile settings\n"); + GetParamsFromEnt(mapent); + } +#endif +#ifdef ZHLT_ENTITY_LIGHTSURFACE + // ugly code + if (!strncmp(ValueForKey (mapent, "classname"), "light", 5) && *ValueForKey (mapent, "_tex")) + { + SetKeyValue (mapent, "convertto", ValueForKey (mapent, "classname")); + SetKeyValue (mapent, "classname", "light_surface"); + } +#endif +#ifdef ZHLT_ENTITY_LIGHTSHADOW + if (!strcmp (ValueForKey (mapent, "convertfrom"), "light_shadow") + #ifdef ZHLT_ENTITY_LIGHTBOUNCE + || !strcmp (ValueForKey (mapent, "convertfrom"), "light_bounce") + #endif + ) + { + SetKeyValue (mapent, "convertto", ValueForKey (mapent, "classname")); + SetKeyValue (mapent, "classname", ValueForKey (mapent, "convertfrom")); + SetKeyValue (mapent, "convertfrom", ""); + } +#endif +#ifdef ZHLT_ENTITY_INFOSUNLIGHT + if (!strcmp (ValueForKey (mapent, "classname"), "light_environment") && + !strcmp (ValueForKey (mapent, "convertfrom"), "info_sunlight")) + { + while (mapent->epairs) + { + DeleteKey (mapent, mapent->epairs->key); + } + memset (mapent, 0, sizeof(entity_t)); + g_numentities--; + return true; + } + if (!strcmp (ValueForKey (mapent, "classname"), "light_environment") && + IntForKey (mapent, "_fake")) + { + SetKeyValue (mapent, "classname", "info_sunlight"); + } +#endif + + return true; +} + +// ===================================================================================== +// ParseEntities +// Parses the dentdata string into entities +// ===================================================================================== +void ParseEntities() +{ + g_numentities = 0; + ParseFromMemory(g_dentdata, g_entdatasize); + + while (ParseEntity()) + { + } +} + +// ===================================================================================== +// UnparseEntities +// Generates the dentdata string from all the entities +// ===================================================================================== +#ifdef ZHLT_ENTITY_INFOSUNLIGHT +int anglesforvector (float angles[3], const float vector[3]) +{ + float z = vector[2], r = sqrt (vector[0] * vector[0] + vector[1] * vector[1]); + float tmp; + if (sqrt (z*z + r*r) < NORMAL_EPSILON) + { + return -1; + } + else + { + tmp = sqrt (z*z + r*r); + z /= tmp, r /= tmp; + if (r < NORMAL_EPSILON) + { + if (z < 0) + { + angles[0] = -90, angles[1] = 0; + } + else + { + angles[0] = 90, angles[1] = 0; + } + } + else + { + angles[0] = atan (z / r) / Q_PI * 180; + float x = vector[0], y = vector[1]; + tmp = sqrt (x*x + y*y); + x /= tmp, y /= tmp; + if (x < -1 + NORMAL_EPSILON) + { + angles[1] = -180; + } + else + { + if (y >= 0) + { + angles[1] = 2 * atan (y / (1+x)) / Q_PI * 180; + } + else + { + angles[1] = 2 * atan (y / (1+x)) / Q_PI * 180 + 360; + } + } + } + } + angles[2] = 0; + return 0; +} +#endif +void UnparseEntities() +{ + char* buf; + char* end; + epair_t* ep; + char line[MAXTOKEN]; + int i; + + buf = g_dentdata; + end = buf; + *end = 0; + +#ifdef ZHLT_ENTITY_INFOSUNLIGHT + for (i = 0; i < g_numentities; i++) + { + entity_t *mapent = &g_entities[i]; + if (!strcmp (ValueForKey (mapent, "classname"), "info_sunlight") || + !strcmp (ValueForKey (mapent, "classname"), "light_environment") ) + { + float vec[3] = {0,0,0}; + { + sscanf (ValueForKey (mapent, "angles"), "%f %f %f", &vec[0], &vec[1], &vec[2]); + float pitch = FloatForKey(mapent, "pitch"); + if (pitch) + vec[0] = pitch; + + const char *target = ValueForKey (mapent, "target"); + if (target[0]) + { + entity_t *targetent = FindTargetEntity (target); + if (targetent) + { + float origin1[3] = {0,0,0}, origin2[3] = {0,0,0}, normal[3]; + sscanf (ValueForKey (mapent, "origin"), "%f %f %f", &origin1[0], &origin1[1], &origin1[2]); + sscanf (ValueForKey (targetent, "origin"), "%f %f %f", &origin2[0], &origin2[1], &origin2[2]); + VectorSubtract (origin2, origin1, normal); + anglesforvector (vec, normal); + } + } + } + char stmp[1024]; + safe_snprintf (stmp, 1024, "%g %g %g", vec[0], vec[1], vec[2]); + SetKeyValue (mapent, "angles", stmp); + DeleteKey (mapent, "pitch"); + + if (!strcmp (ValueForKey (mapent, "classname"), "info_sunlight")) + { + if (g_numentities == MAX_MAP_ENTITIES) + { + Error("g_numentities == MAX_MAP_ENTITIES"); + } + entity_t *newent = &g_entities[g_numentities++]; + newent->epairs = mapent->epairs; + SetKeyValue (newent, "classname", "light_environment"); + SetKeyValue (newent, "_fake", "1"); + mapent->epairs = NULL; + } + } + } +#endif +#ifdef ZHLT_ENTITY_LIGHTSHADOW + for (i = 0; i < g_numentities; i++) + { + entity_t *mapent = &g_entities[i]; + if (!strcmp (ValueForKey (mapent, "classname"), "light_shadow") + #ifdef ZHLT_ENTITY_LIGHTBOUNCE + || !strcmp (ValueForKey (mapent, "classname"), "light_bounce") + #endif + ) + { + SetKeyValue (mapent, "convertfrom", ValueForKey (mapent, "classname")); + SetKeyValue (mapent, "classname", (*ValueForKey (mapent, "convertto")? ValueForKey (mapent, "convertto"): "light")); + SetKeyValue (mapent, "convertto", ""); + } + } +#endif +#ifdef ZHLT_ENTITY_LIGHTSURFACE + // ugly code + for (i = 0; i < g_numentities; i++) + { + entity_t *mapent = &g_entities[i]; + if (!strcmp (ValueForKey (mapent, "classname"), "light_surface")) + { + if (!*ValueForKey (mapent, "_tex")) + { + SetKeyValue (mapent, "_tex", " "); + } + const char *newclassname = ValueForKey (mapent, "convertto"); + if (!*newclassname) + { + SetKeyValue (mapent, "classname", "light"); + } + else if (strncmp (newclassname, "light", 5)) + { + Error ("New classname for 'light_surface' should begin with 'light' not '%s'.\n", newclassname); + } + else + { + SetKeyValue (mapent, "classname", newclassname); + } + SetKeyValue (mapent, "convertto", ""); + } + } +#endif +#ifdef HLCSG_OPTIMIZELIGHTENTITY +#ifdef HLCSG + extern bool g_nolightopt; + if (!g_nolightopt) + { + int i, j; + int count = 0; + bool *lightneedcompare = (bool *)malloc (g_numentities * sizeof (bool)); + hlassume (lightneedcompare != NULL, assume_NoMemory); + memset (lightneedcompare, 0, g_numentities * sizeof(bool)); + for (i = g_numentities - 1; i > -1; i--) + { + entity_t *ent = &g_entities[i]; + const char *classname = ValueForKey (ent, "classname"); + const char *targetname = ValueForKey (ent, "targetname"); + int style = IntForKey (ent, "style"); + if (!targetname[0] || strcmp (classname, "light") && strcmp (classname, "light_spot") && strcmp (classname, "light_environment")) + continue; + for (j = i + 1; j < g_numentities; j++) + { + if (!lightneedcompare[j]) + continue; + entity_t *ent2 = &g_entities[j]; + const char *targetname2 = ValueForKey (ent2, "targetname"); + int style2 = IntForKey (ent2, "style"); + if (style == style2 && !strcmp (targetname, targetname2)) + break; + } + if (j < g_numentities) + { + DeleteKey (ent, "targetname"); + count++; + } + else + { + lightneedcompare[i] = true; + } + } + if (count > 0) + { + Log ("%d redundant named lights optimized.\n", count); + } + free (lightneedcompare); + } +#endif +#endif + for (i = 0; i < g_numentities; i++) + { + ep = g_entities[i].epairs; + if (!ep) + { + continue; // ent got removed + } + + strcat(end, "{\n"); + end += 2; + + for (ep = g_entities[i].epairs; ep; ep = ep->next) + { + sprintf(line, "\"%s\" \"%s\"\n", ep->key, ep->value); + strcat(end, line); + end += strlen(line); + } + strcat(end, "}\n"); + end += 2; + + if (end > buf + MAX_MAP_ENTSTRING) + { + Error("Entity text too long"); + } + } + g_entdatasize = end - buf + 1; +} + +// ===================================================================================== +// SetKeyValue +// makes a keyvalue +// ===================================================================================== +#ifdef ZHLT_DELETEKEY +void DeleteKey(entity_t* ent, const char* const key) +{ + epair_t **pep; + for (pep = &ent->epairs; *pep; pep = &(*pep)->next) + { + if (!strcmp ((*pep)->key, key)) + { + epair_t *ep = *pep; + *pep = ep->next; + Free(ep->key); + Free(ep->value); + Free(ep); + return; + } + } +} +#endif +void SetKeyValue(entity_t* ent, const char* const key, const char* const value) +{ + epair_t* ep; + +#ifdef ZHLT_DELETEKEY + if (!value[0]) + { + DeleteKey (ent, key); + return; + } +#endif + for (ep = ent->epairs; ep; ep = ep->next) + { + if (!strcmp(ep->key, key)) + { +#ifdef ZHLT_DELETEKEY + char *value2 = strdup (value); + Free (ep->value); + ep->value = value2; +#else + Free(ep->value); + ep->value = strdup(value); +#endif + return; + } + } + ep = (epair_t*)Alloc(sizeof(*ep)); + ep->next = ent->epairs; + ent->epairs = ep; + ep->key = strdup(key); + ep->value = strdup(value); +} + +// ===================================================================================== +// ValueForKey +// returns the value for a passed entity and key +// ===================================================================================== +const char* ValueForKey(const entity_t* const ent, const char* const key) +{ + epair_t* ep; + + for (ep = ent->epairs; ep; ep = ep->next) + { + if (!strcmp(ep->key, key)) + { + return ep->value; + } + } + return ""; +} + +// ===================================================================================== +// IntForKey +// ===================================================================================== +int IntForKey(const entity_t* const ent, const char* const key) +{ + return atoi(ValueForKey(ent, key)); +} + +// ===================================================================================== +// FloatForKey +// ===================================================================================== +vec_t FloatForKey(const entity_t* const ent, const char* const key) +{ + return atof(ValueForKey(ent, key)); +} + +// ===================================================================================== +// GetVectorForKey +// returns value for key in vec[0-2] +// ===================================================================================== +void GetVectorForKey(const entity_t* const ent, const char* const key, vec3_t vec) +{ + const char* k; + double v1, v2, v3; + + k = ValueForKey(ent, key); + // scanf into doubles, then assign, so it is vec_t size independent + v1 = v2 = v3 = 0; + sscanf(k, "%lf %lf %lf", &v1, &v2, &v3); + vec[0] = v1; + vec[1] = v2; + vec[2] = v3; +} + +// ===================================================================================== +// FindTargetEntity +// +// ===================================================================================== +entity_t *FindTargetEntity(const char* const target) +{ + int i; + const char* n; + + for (i = 0; i < g_numentities; i++) + { + n = ValueForKey(&g_entities[i], "targetname"); + if (!strcmp(n, target)) + { + return &g_entities[i]; + } + } + + return NULL; +} + + +void dtexdata_init() +{ + g_dtexdata = (byte*)AllocBlock(g_max_map_miptex); + hlassume(g_dtexdata != NULL, assume_NoMemory); + g_dlightdata = (byte*)AllocBlock(g_max_map_lightdata); + hlassume(g_dlightdata != NULL, assume_NoMemory); +} + +void CDECL dtexdata_free() +{ + FreeBlock(g_dtexdata); + g_dtexdata = NULL; + FreeBlock(g_dlightdata); + g_dlightdata = NULL; +} + +// ===================================================================================== +// GetTextureByNumber +// Touchy function, can fail with a page fault if all the data isnt kosher +// (i.e. map was compiled with missing textures) +// ===================================================================================== +#ifdef HLCSG_HLBSP_VOIDTEXINFO +static char emptystring[1] = {'\0'}; +#endif +char* GetTextureByNumber(int texturenumber) +{ +#ifdef HLCSG_HLBSP_VOIDTEXINFO + if (texturenumber == -1) + return emptystring; +#endif + texinfo_t* info; + miptex_t* miptex; + int ofs; + + info = &g_texinfo[texturenumber]; + ofs = ((dmiptexlump_t*)g_dtexdata)->dataofs[info->miptex]; + miptex = (miptex_t*)(&g_dtexdata[ofs]); + + return miptex->name; +} + +// ===================================================================================== +// EntityForModel +// returns entity addy for given modelnum +// ===================================================================================== +entity_t* EntityForModel(const int modnum) +{ + int i; + const char* s; + char name[16]; + + sprintf(name, "*%i", modnum); + // search the entities for one using modnum + for (i = 0; i < g_numentities; i++) + { + s = ValueForKey(&g_entities[i], "model"); + if (!strcmp(s, name)) + { + return &g_entities[i]; + } + } + + return &g_entities[0]; +} \ No newline at end of file diff --git a/src/zhlt-vluzacn/common/bspfile.h b/src/zhlt-vluzacn/common/bspfile.h new file mode 100644 index 0000000..662d027 --- /dev/null +++ b/src/zhlt-vluzacn/common/bspfile.h @@ -0,0 +1,473 @@ +#ifndef BSPFILE_H__ +#define BSPFILE_H__ +#include "cmdlib.h" //--vluzacn + +#if _MSC_VER >= 1000 +#pragma once +#endif + +// upper design bounds + +#define MAX_MAP_HULLS 4 +// hard limit + +#define MAX_MAP_MODELS 512 //400 //vluzacn +// variable, but 400 brush entities is very stressful on the engine and network code as it is + +#define MAX_MAP_BRUSHES 32768 +// arbitrary, but large numbers of brushes generally require more lightmap's than the compiler can handle + +#define MAX_ENGINE_ENTITIES 16384 //1024 //vluzacn +#define MAX_MAP_ENTITIES 16384 //2048 //vluzacn +// hard limit, in actuallity it is too much, as temporary entities in the game plus static map entities can overflow + +#define MAX_MAP_ENTSTRING (2048*1024) //(512*1024) //vluzacn +// abitrary, 512Kb of string data should be plenty even with TFC FGD's + +#define MAX_MAP_PLANES 32768 // TODO: This can be larger, because although faces can only use plane 0~32767, clipnodes can use plane 0-65535. --vluzacn +#define MAX_INTERNAL_MAP_PLANES (256*1024) +// (from email): I have been building a rather complicated map, and using your latest +// tools (1.61) it seemed to compile fine. However, in game, the engine was dropping +// a lot of faces from almost every FUNC_WALL, and also caused a strange texture +// phenomenon in software mode (see attached screen shot). When I compiled with v1.41, +// I noticed that it hit the MAX_MAP_PLANES limit of 32k. After deleting some brushes +// I was able to bring the map under the limit, and all of the previous errors went away. + +#define MAX_MAP_NODES 32767 +// hard limit (negative short's are used as contents values) +#define MAX_MAP_CLIPNODES 32767 +// hard limit (negative short's are used as contents values) + +#ifdef ZHLT_MAX_MAP_LEAFS +#define MAX_MAP_LEAFS 32760 +#define MAX_MAP_LEAFS_ENGINE 8192 +// No problem has been observed in testmap or reported, except when viewing the map from outside (some leafs missing, no crash). +// This problem indicates that engine's MAX_MAP_LEAFS is 8192 (for reason, see: Quake - gl_model.c - Mod_Init). +// I don't know if visleafs > 8192 will cause Mod_DecompressVis overflow. +#else +#define MAX_MAP_LEAFS 8192 +// hard limit (halflife depends on it to setup pvs bits correctly) +#endif + +#define MAX_MAP_VERTS 65535 +#define MAX_MAP_FACES 65535 // This ought to be 32768, otherwise faces(in world) can become invisible. --vluzacn +#ifdef ZHLT_WARNWORLDFACES +#define MAX_MAP_WORLDFACES 32768 +#endif +#define MAX_MAP_MARKSURFACES 65535 +// hard limit (data structures store them as unsigned shorts) + +#define MAX_MAP_TEXTURES 4096 //512 //vluzacn +// hard limit (halflife limitation) // I used 2048 different textures in a test map and everything looks fine in both opengl and d3d mode. + +#define MAX_MAP_TEXINFO 32767 +// hard limit (face.texinfo is signed short) +#ifdef HLCSG_HLBSP_REDUCETEXTURE +#define MAX_INTERNAL_MAP_TEXINFO 262144 +#endif + +#define MAX_MAP_EDGES 256000 +#define MAX_MAP_SURFEDGES 512000 +// arbtirary + +#define DEFAULT_MAX_MAP_MIPTEX 0x2000000 //0x400000 //vluzacn +// 4Mb of textures is enough especially considering the number of people playing the game +// still with voodoo1 and 2 class cards with limited local memory. + +#define DEFAULT_MAX_MAP_LIGHTDATA 0x3000000 //0x600000 //vluzacn +// arbitrary + +#define MAX_MAP_VISIBILITY 0x800000 //0x200000 //vluzacn +// arbitrary + +// these are for entity key:value pairs +#define MAX_KEY 128 //32 //vluzacn +#define MAX_VAL 4096 // the name used to be MAX_VALUE //vluzacn +// quote from yahn: 'probably can raise these values if needed' + +// texture size limit + +#define MAX_TEXTURE_SIZE 348972 //((256 * 256 * sizeof(short) * 3) / 2) //stop compiler from warning 512*512 texture. --vluzacn +// this is arbitrary, and needs space for the largest realistic texture plus +// room for its mipmaps.' This value is primarily used to catch damanged or invalid textures +// in a wad file + +#ifdef ZHLT_XASH2 +#define TEXTURE_STEP 8 +#define MAX_SURFACE_EXTENT 64 +#else +#define TEXTURE_STEP 16 // this constant was previously defined in lightmap.cpp. --vluzacn +#define MAX_SURFACE_EXTENT 16 // if lightmap extent exceeds 16, the map will not be able to load in 'Software' renderer and HLDS. //--vluzacn +#endif + +#ifdef ZHLT_LARGERANGE +#define ENGINE_ENTITY_RANGE 4096.0 +#endif +//============================================================================= + +#ifdef ZHLT_XASH2 +#define BSPVERSION 31 +#else +#define BSPVERSION 30 +#endif +#define TOOLVERSION 2 + + +// +// BSP File Structures +// + + +typedef struct +{ + int fileofs, filelen; +} +lump_t; + +#define LUMP_ENTITIES 0 +#define LUMP_PLANES 1 +#define LUMP_TEXTURES 2 +#define LUMP_VERTEXES 3 +#define LUMP_VISIBILITY 4 +#define LUMP_NODES 5 +#define LUMP_TEXINFO 6 +#define LUMP_FACES 7 +#define LUMP_LIGHTING 8 +#define LUMP_CLIPNODES 9 +#define LUMP_LEAFS 10 +#define LUMP_MARKSURFACES 11 +#define LUMP_EDGES 12 +#define LUMP_SURFEDGES 13 +#define LUMP_MODELS 14 +#ifdef ZHLT_XASH2 +#define LUMP_CLIPNODES2 15 +#define LUMP_CLIPNODES3 16 +#define HEADER_LUMPS 17 +#else +#define HEADER_LUMPS 15 +#endif + +//#define LUMP_MISCPAD -1 +//#define LUMP_ZEROPAD -2 + +typedef struct +{ + float mins[3], maxs[3]; + float origin[3]; + int headnode[MAX_MAP_HULLS]; + int visleafs; // not including the solid leaf 0 + int firstface, numfaces; +} +dmodel_t; + +typedef struct +{ + int version; + lump_t lumps[HEADER_LUMPS]; +} +dheader_t; + +typedef struct +{ + int nummiptex; + int dataofs[4]; // [nummiptex] +} +dmiptexlump_t; + +#define MIPLEVELS 4 +typedef struct miptex_s +{ + char name[16]; + unsigned width, height; + unsigned offsets[MIPLEVELS]; // four mip maps stored +} +miptex_t; + +typedef struct +{ + float point[3]; +} +dvertex_t; + +typedef struct +{ + float normal[3]; + float dist; + planetypes type; // PLANE_X - PLANE_ANYZ ?remove? trivial to regenerate +} +dplane_t; + +typedef enum +{ + CONTENTS_EMPTY = -1, + CONTENTS_SOLID = -2, + CONTENTS_WATER = -3, + CONTENTS_SLIME = -4, + CONTENTS_LAVA = -5, + CONTENTS_SKY = -6, + CONTENTS_ORIGIN = -7, // removed at csg time +#ifndef HLCSG_CUSTOMHULL + CONTENTS_CLIP = -8, // changed to contents_solid +#endif + + CONTENTS_CURRENT_0 = -9, + CONTENTS_CURRENT_90 = -10, + CONTENTS_CURRENT_180 = -11, + CONTENTS_CURRENT_270 = -12, + CONTENTS_CURRENT_UP = -13, + CONTENTS_CURRENT_DOWN = -14, + + CONTENTS_TRANSLUCENT = -15, + CONTENTS_HINT = -16, // Filters down to CONTENTS_EMPTY by bsp, ENGINE SHOULD NEVER SEE THIS + +#ifdef ZHLT_NULLTEX + CONTENTS_NULL = -17, // AJM // removed in csg and bsp, VIS or RAD shouldnt have to deal with this, only clip planes! +#endif + +#ifdef ZHLT_DETAIL // AJM + CONTENTS_DETAIL = -18, +#endif + +#ifdef HLCSG_HLBSP_CUSTOMBOUNDINGBOX + CONTENTS_BOUNDINGBOX = -19, // similar to CONTENTS_ORIGIN +#endif + +#ifdef HLCSG_EMPTYBRUSH + CONTENTS_TOEMPTY = -32, +#endif +} +contents_t; + +// !!! if this is changed, it must be changed in asm_i386.h too !!! +typedef struct +{ + int planenum; + short children[2]; // negative numbers are -(leafs+1), not nodes + short mins[3]; // for sphere culling + short maxs[3]; + unsigned short firstface; + unsigned short numfaces; // counting both sides +} +dnode_t; + +typedef struct +{ + int planenum; + short children[2]; // negative numbers are contents +} +dclipnode_t; + +typedef struct texinfo_s +{ + float vecs[2][4]; // [s/t][xyz offset] + int miptex; + int flags; +} +texinfo_t; + +#define TEX_SPECIAL 1 // sky or slime or null, no lightmap or 256 subdivision +#ifdef ZHLT_HIDDENSOUNDTEXTURE +#define TEX_SHOULDHIDE 16384 // this flag is temporary; it might be set after CSG, but will be dropped after BSP +#endif + +// note that edge 0 is never used, because negative edge nums are used for +// counterclockwise use of the edge in a face +typedef struct +{ + unsigned short v[2]; // vertex numbers +} +dedge_t; + +#define MAXLIGHTMAPS 4 +typedef struct +{ + unsigned short planenum; + short side; + + int firstedge; // we must support > 64k edges + short numedges; + short texinfo; + + // lighting info + byte styles[MAXLIGHTMAPS]; + int lightofs; // start of [numstyles*surfsize] samples +} +dface_t; + +#define AMBIENT_WATER 0 +#define AMBIENT_SKY 1 +#define AMBIENT_SLIME 2 +#define AMBIENT_LAVA 3 + +#define NUM_AMBIENTS 4 // automatic ambient sounds + +// leaf 0 is the generic CONTENTS_SOLID leaf, used for all solid areas +// all other leafs need visibility info +typedef struct +{ + int contents; + int visofs; // -1 = no visibility info + + short mins[3]; // for frustum culling + short maxs[3]; + + unsigned short firstmarksurface; + unsigned short nummarksurfaces; + + byte ambient_level[NUM_AMBIENTS]; +} +dleaf_t; + +//============================================================================ + +#define ANGLE_UP -1.0 //#define ANGLE_UP -1 //--vluzacn +#define ANGLE_DOWN -2.0 //#define ANGLE_DOWN -2 //--vluzacn + +// +// BSP File Data +// + +extern int g_nummodels; +extern dmodel_t g_dmodels[MAX_MAP_MODELS]; +extern int g_dmodels_checksum; + +extern int g_visdatasize; +extern byte g_dvisdata[MAX_MAP_VISIBILITY]; +extern int g_dvisdata_checksum; + +extern int g_lightdatasize; +extern byte* g_dlightdata; +extern int g_dlightdata_checksum; + +extern int g_texdatasize; +extern byte* g_dtexdata; // (dmiptexlump_t) +extern int g_dtexdata_checksum; + +extern int g_entdatasize; +extern char g_dentdata[MAX_MAP_ENTSTRING]; +extern int g_dentdata_checksum; + +extern int g_numleafs; +extern dleaf_t g_dleafs[MAX_MAP_LEAFS]; +extern int g_dleafs_checksum; + +extern int g_numplanes; +extern dplane_t g_dplanes[MAX_INTERNAL_MAP_PLANES]; +extern int g_dplanes_checksum; + +extern int g_numvertexes; +extern dvertex_t g_dvertexes[MAX_MAP_VERTS]; +extern int g_dvertexes_checksum; + +extern int g_numnodes; +extern dnode_t g_dnodes[MAX_MAP_NODES]; +extern int g_dnodes_checksum; + +extern int g_numtexinfo; +#ifdef HLCSG_HLBSP_REDUCETEXTURE +extern texinfo_t g_texinfo[MAX_INTERNAL_MAP_TEXINFO]; +#else +extern texinfo_t g_texinfo[MAX_MAP_TEXINFO]; +#endif +extern int g_texinfo_checksum; + +extern int g_numfaces; +extern dface_t g_dfaces[MAX_MAP_FACES]; +extern int g_dfaces_checksum; + +#ifdef ZHLT_XASH2 +extern int g_numclipnodes[MAX_MAP_HULLS - 1]; +extern dclipnode_t g_dclipnodes[MAX_MAP_HULLS - 1][MAX_MAP_CLIPNODES]; +extern int g_dclipnodes_checksum[MAX_MAP_HULLS - 1]; +#else +extern int g_numclipnodes; +extern dclipnode_t g_dclipnodes[MAX_MAP_CLIPNODES]; +extern int g_dclipnodes_checksum; +#endif + +extern int g_numedges; +extern dedge_t g_dedges[MAX_MAP_EDGES]; +extern int g_dedges_checksum; + +extern int g_nummarksurfaces; +extern unsigned short g_dmarksurfaces[MAX_MAP_MARKSURFACES]; +extern int g_dmarksurfaces_checksum; + +extern int g_numsurfedges; +extern int g_dsurfedges[MAX_MAP_SURFEDGES]; +extern int g_dsurfedges_checksum; + +extern void DecompressVis(const byte* src, byte* const dest, const unsigned int dest_length); +extern int CompressVis(const byte* const src, const unsigned int src_length, byte* dest, unsigned int dest_length); + +extern void LoadBSPImage(dheader_t* header); +extern void LoadBSPFile(const char* const filename); +extern void WriteBSPFile(const char* const filename); +extern void PrintBSPFileSizes(); +#ifdef ZHLT_64BIT_FIX +#ifdef PLATFORM_CAN_CALC_EXTENT +extern void WriteExtentFile (const char *const filename); +extern bool CalcFaceExtents_test (); +#else +extern void LoadExtentFile (const char *const filename); +#endif +extern void GetFaceExtents (int facenum, int mins_out[2], int maxs_out[2]); +#endif +#ifdef ZHLT_EMBEDLIGHTMAP +extern int ParseImplicitTexinfoFromTexture (int miptex); +extern int ParseTexinfoForFace (const dface_t *f); +extern void DeleteEmbeddedLightmaps (); +#endif + +// +// Entity Related Stuff +// + +typedef struct epair_s +{ + struct epair_s* next; + char* key; + char* value; +} +epair_t; + +typedef struct +{ + vec3_t origin; + int firstbrush; + int numbrushes; + epair_t* epairs; +} +entity_t; + +extern int g_numentities; +extern entity_t g_entities[MAX_MAP_ENTITIES]; + +extern void ParseEntities(); +extern void UnparseEntities(); + +#ifdef ZHLT_DELETEKEY +extern void DeleteKey(entity_t* ent, const char* const key); +#endif +extern void SetKeyValue(entity_t* ent, const char* const key, const char* const value); +extern const char* ValueForKey(const entity_t* const ent, const char* const key); +extern int IntForKey(const entity_t* const ent, const char* const key); +extern vec_t FloatForKey(const entity_t* const ent, const char* const key); +extern void GetVectorForKey(const entity_t* const ent, const char* const key, vec3_t vec); + +extern entity_t* FindTargetEntity(const char* const target); +extern epair_t* ParseEpair(); +extern entity_t* EntityForModel(int modnum); + +// +// Texture Related Stuff +// + +extern int g_max_map_miptex; +extern int g_max_map_lightdata; +extern void dtexdata_init(); +extern void CDECL dtexdata_free(); + +extern char* GetTextureByNumber(int texturenumber); + +#endif //BSPFILE_H__ diff --git a/src/zhlt-vluzacn/common/cmdlib.cpp b/src/zhlt-vluzacn/common/cmdlib.cpp new file mode 100644 index 0000000..797eca4 --- /dev/null +++ b/src/zhlt-vluzacn/common/cmdlib.cpp @@ -0,0 +1,583 @@ +#ifdef SYSTEM_WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#endif + +#include "cmdlib.h" +#include "messages.h" +#include "hlassert.h" +#include "blockmem.h" +#include "log.h" +#include "mathlib.h" + +#ifdef SYSTEM_POSIX +#ifdef HAVE_SYS_STAT_H +#include +#endif +#ifdef HAVE_FCNTL_H +#include +#endif +#ifdef HAVE_UNISTD_H +#include +#endif +#endif + +#define PATHSEPARATOR(c) ((c) == '\\' || (c) == '/') + +/* + * ================ + * I_FloatTime + * ================ + */ + +double I_FloatTime() +{ +#ifdef SYSTEM_WIN32 + FILETIME ftime; + double rval; + + GetSystemTimeAsFileTime(&ftime); + + rval = ftime.dwLowDateTime; + rval += ((__int64)ftime.dwHighDateTime) << 32; + + return (rval / 10000000.0); +#endif + +#ifdef SYSTEM_POSIX + struct timeval tp; + struct timezone tzp; + static int secbase; + + gettimeofday(&tp, &tzp); + + if (!secbase) + { + secbase = tp.tv_sec; + return tp.tv_usec / 1000000.0; + } + + return (tp.tv_sec - secbase) + tp.tv_usec / 1000000.0; +#endif +} + +#ifdef SYSTEM_POSIX +char* strupr(char* string) +{ + int i; + int len = strlen(string); + + for (i = 0; i < len; i++) + { + string[i] = toupper(string[i]); + } + return string; +} + +char* strlwr(char* string) +{ + int i; + int len = strlen(string); + + for (i = 0; i < len; i++) + { + string[i] = tolower(string[i]); + } + return string; +} +#endif + +// Case Insensitive substring matching +const char* stristr(const char* const string, const char* const substring) +{ + char* string_copy; + char* substring_copy; + const char* match; + + string_copy = _strdup(string); + _strlwr(string_copy); + + substring_copy = _strdup(substring); + _strlwr(substring_copy); + + match = strstr(string_copy, substring_copy); + if (match) + { + match = (string + (match - string_copy)); + } + + free(string_copy); + free(substring_copy); + return match; +} + +/*-------------------------------------------------------------------- +// New implementation of FlipSlashes, DefaultExtension, StripFilename, +// StripExtension, ExtractFilePath, ExtractFile, ExtractFileBase, etc. +----------------------------------------------------------------------*/ +#ifdef ZHLT_NEW_FILE_FUNCTIONS //added "const". --vluzacn + +//Since all of these functions operate around either the extension +//or the directory path, centralize getting both numbers here so we +//can just reference them everywhere else. Use strrchr to give a +//speed boost while we're at it. +inline void getFilePositions(const char* path, int* extension_position, int* directory_position) +{ + const char* ptr = strrchr(path,'.'); + if(ptr == 0) + { *extension_position = -1; } + else + { *extension_position = ptr - path; } + + ptr = qmax(strrchr(path,'/'),strrchr(path,'\\')); + if(ptr == 0) + { *directory_position = -1; } + else + { + *directory_position = ptr - path; + if(*directory_position > *extension_position) + { *extension_position = -1; } + + //cover the case where we were passed a directory - get 2nd-to-last slash + if(*directory_position == (int)strlen(path) - 1) + { + do + { + --(*directory_position); + } + while(*directory_position > -1 && path[*directory_position] != '/' && path[*directory_position] != '\\'); + } + } +} + +char* FlipSlashes(char* string) +{ + char* ptr = string; + if(SYSTEM_SLASH_CHAR == '\\') + { + while(ptr = strchr(ptr,'/')) + { *ptr = SYSTEM_SLASH_CHAR; } + } + else + { + while(ptr = strchr(ptr,'\\')) + { *ptr = SYSTEM_SLASH_CHAR; } + } + return string; +} + +void DefaultExtension(char* path, const char* extension) +{ + int extension_pos, directory_pos; + getFilePositions(path,&extension_pos,&directory_pos); + if(extension_pos == -1) + { strcat(path,extension); } +} + +void StripFilename(char* path) +{ + int extension_pos, directory_pos; + getFilePositions(path,&extension_pos,&directory_pos); + if(directory_pos == -1) + { path[0] = 0; } + else + { path[directory_pos] = 0; } +} + +void StripExtension(char* path) +{ + int extension_pos, directory_pos; + getFilePositions(path,&extension_pos,&directory_pos); + if(extension_pos != -1) + { path[extension_pos] = 0; } +} + +void ExtractFilePath(const char* const path, char* dest) +{ + int extension_pos, directory_pos; + getFilePositions(path,&extension_pos,&directory_pos); + if(directory_pos != -1) + { + memcpy(dest,path,directory_pos+1); //include directory slash + dest[directory_pos+1] = 0; + } + else + { dest[0] = 0; } +} + +void ExtractFile(const char* const path, char* dest) +{ + int extension_pos, directory_pos; + getFilePositions(path,&extension_pos,&directory_pos); + + int length = strlen(path); + +#ifdef ZHLT_FILE_FUNCTIONS_FIX + length -= directory_pos + 1; +#else + if(directory_pos == -1) { directory_pos = 0; } + else { length -= directory_pos + 1; } +#endif + + memcpy(dest,path+directory_pos+1,length); //exclude directory slash + dest[length] = 0; +} + +void ExtractFileBase(const char* const path, char* dest) +{ + int extension_pos, directory_pos; + getFilePositions(path,&extension_pos,&directory_pos); + int length = extension_pos == -1 ? strlen(path) : extension_pos; + +#ifdef ZHLT_FILE_FUNCTIONS_FIX + length -= directory_pos + 1; +#else + if(directory_pos == -1) { directory_pos = 0; } + else { length -= directory_pos + 1; } +#endif + + memcpy(dest,path+directory_pos+1,length); //exclude directory slash + dest[length] = 0; +} + +void ExtractFileExtension(const char* const path, char* dest) +{ + int extension_pos, directory_pos; + getFilePositions(path,&extension_pos,&directory_pos); + if(extension_pos != -1) + { + int length = strlen(path) - extension_pos; + memcpy(dest,path+extension_pos,length); //include extension '.' + dest[length] = 0; + } + else + { dest[0] = 0; } +} +//------------------------------------------------------------------- +#else //old cmdlib functions + +char* FlipSlashes(char* string) +{ + while (*string) + { + if (PATHSEPARATOR(*string)) + { + *string = SYSTEM_SLASH_CHAR; + } + string++; + } + return string; +} + +void DefaultExtension(char* path, const char* extension) +{ + char* src; + + // + // if path doesn't have a .EXT, append extension + // (extension should include the .) + // + src = path + strlen(path) - 1; + + while (!PATHSEPARATOR(*src) && src != path) + { + if (*src == '.') + return; // it has an extension + src--; + } + + strcat(path, extension); +} + +void StripFilename(char* path) +{ + int length; + + length = strlen(path) - 1; + while (length > 0 && !PATHSEPARATOR(path[length])) + length--; + path[length] = 0; +} + +void StripExtension(char* path) +{ + int length; + + length = strlen(path) - 1; + while (length > 0 && path[length] != '.') + { + length--; + if (PATHSEPARATOR(path[length])) + return; // no extension + } + if (length) + path[length] = 0; +} + +/* + * ==================== + * Extract file parts + * ==================== + */ +void ExtractFilePath(const char* const path, char* dest) +{ + hlassert (path != dest); + + const char* src; + + src = path + strlen(path) - 1; + + // + // back up until a \ or the start + // + while (src != path && !PATHSEPARATOR(*(src - 1))) + src--; + + memcpy(dest, path, src - path); + dest[src - path] = 0; +} + +void ExtractFile(const char* const path, char* dest) +{ + hlassert (path != dest); + + const char* src; + + src = path + strlen(path) - 1; + + while (src != path && !PATHSEPARATOR(*(src - 1))) + src--; + + while (*src) + { + *dest++ = *src++; + } + *dest = 0; +} + +void ExtractFileBase(const char* const path, char* dest) +{ + hlassert (path != dest); + + const char* src; + + src = path + strlen(path) - 1; + + // + // back up until a \ or the start + // + while (src != path && !PATHSEPARATOR(*(src - 1))) + src--; + + while (*src && *src != '.') + { + *dest++ = *src++; + } + *dest = 0; +} + +void ExtractFileExtension(const char* const path, char* dest) +{ + hlassert (path != dest); + + const char* src; + + src = path + strlen(path) - 1; + + // + // back up until a . or the start + // + while (src != path && *(src - 1) != '.') + src--; + if (src == path) + { + *dest = 0; // no extension + return; + } + + strcpy_s(dest, src); +} + +#endif + +/* + * ============================================================================ + * + * BYTE ORDER FUNCTIONS + * + * ============================================================================ + */ + +#ifdef WORDS_BIGENDIAN + +short LittleShort(const short l) +{ + byte b1, b2; + + b1 = l & 255; + b2 = (l >> 8) & 255; + + return (b1 << 8) + b2; +} + +short BigShort(const short l) +{ + return l; +} + +int LittleLong(const int l) +{ + byte b1, b2, b3, b4; + + b1 = l & 255; + b2 = (l >> 8) & 255; + b3 = (l >> 16) & 255; + b4 = (l >> 24) & 255; + + return ((int)b1 << 24) + ((int)b2 << 16) + ((int)b3 << 8) + b4; +} + +int BigLong(const int l) +{ + return l; +} + +float LittleFloat(const float l) +{ + union + { + byte b[4]; + float f; + } + in , out; + + in.f = l; + out.b[0] = in.b[3]; + out.b[1] = in.b[2]; + out.b[2] = in.b[1]; + out.b[3] = in.b[0]; + + return out.f; +} + +float BigFloat(const float l) +{ + return l; +} + +#else // Little endian (Intel, etc) + +short BigShort(const short l) +{ + byte b1, b2; + + b1 = (byte) (l & 255); + b2 = (byte) ((l >> 8) & 255); + + return (short)((b1 << 8) + b2); +} + +short LittleShort(const short l) +{ + return l; +} + +int BigLong(const int l) +{ + byte b1, b2, b3, b4; + + b1 = (byte) (l & 255); + b2 = (byte) ((l >> 8) & 255); + b3 = (byte) ((l >> 16) & 255); + b4 = (byte) ((l >> 24) & 255); + + return ((int)b1 << 24) + ((int)b2 << 16) + ((int)b3 << 8) + b4; +} + +int LittleLong(const int l) +{ + return l; +} + +float BigFloat(const float l) +{ + union + { + byte b[4]; + float f; + } + in , out; + + in.f = l; + out.b[0] = in.b[3]; + out.b[1] = in.b[2]; + out.b[2] = in.b[1]; + out.b[3] = in.b[0]; + + return out.f; +} + +float LittleFloat(const float l) +{ + return l; +} + +#endif + +//============================================================================= + +bool CDECL FORMAT_PRINTF(3,4) safe_snprintf(char* const dest, const size_t count, const char* const args, ...) +{ + size_t amt; + va_list argptr; + + hlassert(count > 0); + + va_start(argptr, args); + amt = vsnprintf(dest, count, args, argptr); + va_end(argptr); + + // truncated (bad!, snprintf doesn't null terminate the string when this happens) + if (amt == count) + { + dest[count - 1] = 0; + return false; + } + + return true; +} + +bool safe_strncpy(char* const dest, const char* const src, const size_t count) +{ + return safe_snprintf(dest, count, "%s", src); +} + +bool safe_strncat(char* const dest, const char* const src, const size_t count) +{ + if (count) + { + strncat(dest, src, count); + + dest[count - 1] = 0; // Ensure it is null terminated + return true; + } + else + { + Warning("safe_strncat passed empty count"); + return false; + } +} + +bool TerminatedString(const char* buffer, const int size) +{ + int x; + + for (x = 0; x < size; x++, buffer++) + { + if ((*buffer) == 0) + { + return true; + } + } + return false; +} diff --git a/src/zhlt-vluzacn/common/cmdlib.h b/src/zhlt-vluzacn/common/cmdlib.h new file mode 100644 index 0000000..1b177cc --- /dev/null +++ b/src/zhlt-vluzacn/common/cmdlib.h @@ -0,0 +1,711 @@ +#ifndef CMDLIB_H__ +#define CMDLIB_H__ + +#if _MSC_VER >= 1000 +#pragma once +#endif + +//#define MODIFICATIONS_STRING "Submit detailed bug reports to (zoner@gearboxsoftware.com)\n" +//#define MODIFICATIONS_STRING "Submit detailed bug reports to (merlinis@bigpond.net.au)\n" +//#define MODIFICATIONS_STRING "Submit detailed bug reports to (amckern@yahoo.com)\n" +#define MODIFICATIONS_STRING "Submit detailed bug reports to (vluzacn@163.com)\n" //--vluzacn + +#ifdef _DEBUG +#define ZHLT_VERSIONSTRING "v3.4 dbg" +#else +#define ZHLT_VERSIONSTRING "v3.4" +#endif + +#define HACK_VERSIONSTRING "VL34" //--vluzacn + +#if !defined (HLCSG) && !defined (HLBSP) && !defined (HLVIS) && !defined (HLRAD) && !defined (RIPENT) //--vluzacn +#error "You must define one of these in the settings of each project: HLCSG, HLBSP, HLVIS, HLRAD, RIPENT. The most likely cause is that you didn't load the project from the sln file." +#endif +#if !defined (VERSION_32BIT) && !defined (VERSION_64BIT) && !defined (VERSION_LINUX) && !defined (VERSION_OTHER) //--vluzacn +#error "You must define one of these in the settings of each project: VERSION_32BIT, VERSION_64BIT, VERSION_LINUX, VERSION_OTHER. The most likely cause is that you didn't load the project from the sln file." +#endif + +#ifdef VERSION_32BIT +#define PLATFORM_VERSIONSTRING "32-bit" +#define PLATFORM_CAN_CALC_EXTENT +#endif +#ifdef VERSION_64BIT +#define PLATFORM_VERSIONSTRING "64-bit" +#define PLATFORM_CAN_CALC_EXTENT +#endif +#ifdef VERSION_LINUX +#define PLATFORM_VERSIONSTRING "linux" +#define PLATFORM_CAN_CALC_EXTENT +#endif +#ifdef VERSION_OTHER +#define PLATFORM_VERSIONSTRING "???" +#endif + +//===================================================================== +// AJM: Different features of the tools can be undefined here +// these are not officially beta tested, but seem to work okay + +// ZHLT_* features are spread across more than one tool. Hence, changing +// one of these settings probably means recompiling the whole set +#define ZHLT_INFO_COMPILE_PARAMETERS // ALL TOOLS +#define ZHLT_NULLTEX // HLCSG, HLBSP +#define ZHLT_TEXLIGHT // HLCSG, HLRAD - triggerable texlights by LRC +#define ZHLT_GENERAL // ALL TOOLS - general changes +#define ZHLT_NEW_FILE_FUNCTIONS // ALL TOOLS - file path/extension extraction functions +//#define ZHLT_DETAIL // HLCSG, HLBSP - detail brushes //should never turn on +//#define ZHLT_PROGRESSFILE // ALL TOOLS - estimate progress reporting to -progressfile //should never turn on +//#define ZHLT_NSBOB //should never turn on +#define ZHLT_VectorMA_FIX //--vluzacn +#define ZHLT_LARGERANGE //--vluzacn +#define ZHLT_CONSOLE //--vluzacn +#define ZHLT_PARAMFILE //--vluzacn +#define ZHLT_LANGFILE //--vluzacn +#define ZHLT_DELETEKEY //--vluzacn +#define ZHLT_ENTITY_LIGHTSHADOW //--vluzacn + #ifdef ZHLT_DELETEKEY +#define ZHLT_ENTITY_INFOSUNLIGHT //--vluzacn + #endif +#define ZHLT_PLANETYPE_FIX // Very Important !! --vluzacn +#define ZHLT_WINDING_FIX // Very Important !! --vluzacn +#define ZHLT_FILE_FUNCTIONS_FIX //--vluzacn +#define ZHLT_MAX_MAP_LEAFS //--vluzacn +#define ZHLT_WINDING_RemoveColinearPoints_VL //Important //--vluzacn +#define ZHLT_WINDING_EPSILON //--vluzacn +#define ZHLT_BOUNDINGBOX_PRECISION_FIX //--vluzacn +#define ZHLT_ENTITY_LIGHTSURFACE //--vluzacn +#define ZHLT_CHART_AllocBlock //--vluzacn +#define ZHLT_TEXNAME_CHARSET //--vluzacn +#define ZHLT_NOWADDIR //--vluzacn +//#define ZHLT_XASH // build the compiler for Xash engine //--vluzacn + #ifdef ZHLT_XASH +//#define ZHLT_XASH2 // build the compiler for Xash engine with change in bsp format //--vluzacn + #endif +#define ZHLT_CHART_WADFILES //--vluzacn +#define ZHLT_DEFAULTEXTENSION_FIX //--vluzacn +#define ZHLT_FREETEXTUREAXIS //--vluzacn +#define ZHLT_WARNWORLDFACES //--vluzacn + #ifdef ZHLT_ENTITY_LIGHTSHADOW +#define ZHLT_ENTITY_LIGHTBOUNCE //--vluzacn + #endif +#define ZHLT_DecompressVis_FIX //--vluzacn +#define ZHLT_64BIT_FIX //--vluzacn + #ifdef ZHLT_64BIT_FIX +#define ZHLT_EMBEDLIGHTMAP // this feature requires HLRAD_TEXTURE and RIPENT_TEXTURE //--vluzacn + #endif +//#define ZHLT_HIDDENSOUNDTEXTURE //--vluzacn + +#define COMMON_HULLU // winding optimisations by hullu + + #ifdef SYSTEM_WIN32 +#define RIPENT_PAUSE //--vluzacn + #endif +#define RIPENT_TEXTURE //--vluzacn + +// tool specific settings below only mean a recompile of the tool affected +#define HLCSG_CLIPECONOMY +#define HLCSG_WADCFG +#define HLCSG_WADCFG_NEW // rewritten HLCSG_WADCFG. --vluzacn +#define HLCSG_AUTOWAD + +#define HLCSG_PRECISIONCLIP +#define HLCSG_FASTFIND +#ifdef ZHLT_NULLTEX + #define HLCSG_NULLIFY_INVISIBLE //requires null textures as prerequisite +#endif + +#define HLCSG_COPYBRUSH //--vluzacn + #ifdef HLCSG_COPYBRUSH + #ifdef HLCSG_CLIPECONOMY +#define HLCSG_CUSTOMHULL //--vluzacn + #endif + #endif + #ifdef ZHLT_DELETEKEY +#define HLCSG_SCALESIZE //--vluzacn + #endif +#define HLCSG_SEARCHWADPATH_VL //--vluzacn + #ifdef SYSTEM_WIN32 +#define HLCSG_GAMETEXTMESSAGE_UTF8 //--vluzacn + #endif +#define HLCSG_LOGVERSION //--vluzacn + #ifdef HLCSG_COPYBRUSH +#define HLCSG_COPYMODELKEYVALUE //--vluzacn + #endif +#define HLCSG_CheckBrushContents_FIX //--vluzacn +#define HLCSG_ERROR_MISSINGTEXTURE //--vluzacn +#define HLCSG_BEVELMISSINGFIX //--vluzacn +#define HLCSG_SORTBRUSH_FIX //--vluzacn +#define HLBSP_REMOVEHULL2 //--vluzacn +#define HLCSG_CHART_FIX //--vluzacn +#define HLCSG_TextureContents_FIX //--vluzacn +#define HLCSG_KEEPLOG //--vluzacn +#define HLCSG_FUNCGROUP_FIX //--vluzacn +#define HLCSG_COUNT_NEW //--vluzacn +#define HLCSG_ALLOWHINTINENTITY //--vluzacn +#define HLCSG_PRICISION_FIX // Important!! --vluzacn +#define HLBSP_MAX_LEAF_FACES //--vluzacn + #ifdef ZHLT_NULLTEX +#define HLBSP_SKY_SOLID //--vluzacn +#define HLCSG_HLBSP_CONTENTSNULL_FIX //--vluzacn + #endif +#define HLCSG_HLBSP_ALLOWEMPTYENTITY // needs more testing --vluzacn +#define HLBSP_ChooseMidPlane_FIX //--vluzacn + #ifdef HLBSP_ChooseMidPlane_FIX +#define HLBSP_ChoosePlane_VL //--vluzacn + #endif +#define HLCSG_HLBSP_REDUCETEXTURE //--vluzacn +#define HLBSP_DELETELEAKFILE //--vluzacn +#define HLBSP_FILL //--vluzacn +#define HLBSP_WARNMIXEDCONTENTS //--vluzacn +#define HLBSP_NULLFACEOUTPUT_FIX //--vluzacn +#define HLCSG_STRIPWADPATH //--vluzacn +#define HLCSG_NOREDUNDANTKEY //--vluzacn +#define HLCSG_HLBSP_CUSTOMBOUNDINGBOX //--vluzacn +#define HLCSG_HLBSP_VOIDTEXINFO //--vluzacn + #ifdef HLCSG_HLBSP_VOIDTEXINFO + #ifdef HLCSG_SORTBRUSH_FIX +#define HLCSG_EMPTYBRUSH //--vluzacn + #endif + #endif + #ifdef HLCSG_EMPTYBRUSH +#define HLCSG_WATERBACKFACE_FIX // remove this if you have fixed the engine's bug of drawing water backface. --vluzacn +#define HLCSG_NOSPLITBYHINT //--vluzacn +#define HLCSG_CUSTOMCONTENT //--vluzacn + #endif +#define HLBSP_NewFaceFromFace_FIX //--vluzacn + #ifdef ZHLT_DELETEKEY +#define HLCSG_OPTIMIZELIGHTENTITY //--vluzacn + #endif +#define HLCSG_STYLEHACK //--vluzacn +#define HLCSG_CSGBrush_BRUSHNUM_FIX //--vluzacn +#define HLBSP_TJUNC_PRECISION_FIX //--vluzacn + #ifdef HLBSP_ChoosePlane_VL +#define HLBSP_BALANCE //--vluzacn + #endif +#define HLBSP_TryMerge_PLANENUM_FIX //--vluzacn +#define HLCSG_SORTBRUSH_KEEPORDER //--vluzacn +#define HLCSG_FACENORMALEPSILON //--vluzacn + #ifdef ZHLT_WINDING_EPSILON +#define HLCSG_MakeHullFaces_PRECISE //--vluzacn + #endif +#define HLCSG_VIEWSURFACE //--vluzacn +#define HLBSP_TryMerge_PRECISION_FIX //--vluzacn + #ifdef ZHLT_WINDING_RemoveColinearPoints_VL +#define HLBSP_REMOVECOLINEARPOINTS //--vluzacn + #endif +#define HLBSP_SubdivideFace_FIX //--vluzacn +#define HLCSG_HLBSP_DOUBLEPLANE //--vluzacn + #ifdef HLBSP_ChoosePlane_VL +#define HLBSP_AVOIDEPSILONSPLIT //--vluzacn + #endif +#define HLCSG_HULLFILE_AUTOPATH //--vluzacn +//#define HLBSP_SUBDIVIDE_INMID // this may contribute to 'AllocBlock: full' problem though it may generate fewer faces. --vluzacn + #ifdef HLBSP_ChoosePlane_VL + #ifdef HLCSG_HLBSP_ALLOWEMPTYENTITY // possible that in a model there are surfaces but no splits +#define HLCSG_HLBSP_SOLIDHINT //--vluzacn + #endif + #endif +#define HLCSG_SORTSIDES //--vluzacn + #ifdef HLCSG_CUSTOMHULL + #ifdef HLCSG_FUNCGROUP_FIX + #ifdef HLCSG_CSGBrush_BRUSHNUM_FIX + #ifdef HLCSG_HLBSP_VOIDTEXINFO + #ifdef ZHLT_WINDING_EPSILON +#define ZHLT_DETAILBRUSH //--vluzacn + #endif + #endif + #endif + #endif + #endif +#define HLBSP_VIEWPORTAL //--vluzacn +#define HLBSP_EDGESHARE_SAMESIDE //--vluzacn +#define HLBSP_MarkLeakTrail_FIX //--vluzacn + #ifdef HLCSG_NULLIFY_INVISIBLE +#define HLCSG_NULLIFYAAATRIGGER //--vluzacn + #endif + #ifdef HLCSG_CUSTOMHULL + #ifdef HLCSG_CUSTOMCONTENT +#define HLCSG_PASSBULLETSBRUSH //--vluzacn + #endif + #endif +#define HLCSG_ONLYENTS_NOWADCHANGE //--vluzacn +#define HLCSG_NOFAKESPLITS //--vluzacn + #ifdef ZHLT_LARGERANGE +#define HLBSP_MAXNODESIZE_SKYBOX //--vluzacn + #endif + #ifdef ZHLT_DETAILBRUSH +#define ZHLT_CLIPNODEDETAILLEVEL //--vluzacn + #endif +#define HLBSP_HIDDENFACE //--vluzacn + #ifdef ZHLT_WINDING_EPSILON +#define HLBSP_BRINKHACK //--vluzacn + #endif +#define HLBSP_MERGECLIPNODE // Will this break the BSP file format? //--vluzacn +#define HLCSG_CLIPTYPEPRECISE_EPSILON_FIX //--vluzacn + #ifdef HLBSP_BRINKHACK +#define HLBSP_BRINKNOTUSEDBYLEAF_FIX //--vluzacn + #endif + #ifdef HLBSP_ChoosePlane_VL + #ifdef HLBSP_BALANCE +#define HLBSP_FAST_SELECTPARTITION //--vluzacn + #endif + #endif + #ifdef ZHLT_DETAILBRUSH +#define HLBSP_DETAILBRUSH_CULL //--vluzacn + #endif + #ifdef ZHLT_DETAILBRUSH +#define HLBSP_SPLITFACE_FIX //--vluzacn + #endif + #ifdef HLCSG_ONLYENTS_NOWADCHANGE +#define HLCSG_AUTOWAD_TEXTURELIST_FIX //--vluzacn + #endif + #ifdef HLCSG_PRECISIONCLIP + #ifdef HLCSG_CUSTOMHULL + #ifdef HLCSG_AUTOWAD_TEXTURELIST_FIX + #ifdef ZHLT_DELETEKEY +#define HLCSG_HULLBRUSH //--vluzacn + #endif + #endif + #endif + #endif +#define HLCSG_TEXMAP64_FIX //--vluzacn + #ifdef HLBSP_FAST_SELECTPARTITION +#define HLBSP_CHOOSEMIDPLANE //--vluzacn + #endif + #ifdef HLBSP_BRINKHACK +#define HLBSP_BRINKHACK_BUGFIX //--vluzacn + #endif + #ifdef ZHLT_DETAILBRUSH +#define HLBSP_REMOVECOVEREDFACES //--vluzacn + #endif +#define HLCSG_FILEREADFAILURE_FIX //--vluzacn +#define HLBSP_DELETETEMPFILES //--vluzacn + #ifdef HLCSG_ONLYENTS_NOWADCHANGE + #ifdef HLCSG_AUTOWAD + #ifdef HLCSG_WADCFG_NEW + #ifdef HLCSG_TEXMAP64_FIX +#define HLCSG_AUTOWAD_NEW //--vluzacn + #endif + #endif + #endif + #endif + #ifdef HLCSG_HLBSP_VOIDTEXINFO +#define HLCSG_WARNBADTEXINFO //--vluzacn + #endif +#define HLBSP_HASH_FIX //--vluzacn + #ifdef ZHLT_DETAILBRUSH +#define HLCSG_COPLANARPRIORITY //--vluzacn + #endif + +#define HLVIS_MAXDIST +#define HLVIS_OVERVIEW //--vluzacn + #ifdef HLVIS_MAXDIST +#define HLVIS_MAXDIST_NEW // GetShortestDistance used to crash randomly for no reason (compiled with VS2010), and I couldn't make it work even after fixing several obvious bugs. So replaced it with this. --vluzacn + #endif + #ifdef HLVIS_OVERVIEW +#define HLVIS_SKYBOXMODEL //--vluzacn + #endif + + +#define HLRAD_INFO_TEXLIGHTS +#define HLRAD_WHOME // encompases all of Adam Foster's changes +#define HLRAD_HULLU // semi-opaque brush based entities and effects by hullu + +#define HLRAD_TRANSNONORMALIZE //--vluzacn +#define HLRAD_OPAQUE_DIFFUSE_FIX //--vluzacn + #ifdef HLRAD_TRANSNONORMALIZE +#define HLRAD_NOSWAP //--vluzacn +#define HLRAD_TRANSTOTAL_HACK //--vluzacn + #endif + #ifdef HLRAD_HULLU +#define HLRAD_TRANSPARENCY_CPP //--vluzacn + #endif + #ifdef HLRAD_TRANSPARENCY_CPP +#define HLRAD_TestSegmentAgainstOpaqueList_VL //--vluzacn + #endif +#define HLRAD_ENTSTRIPRAD //--vluzacn +#define HLRAD_CHOP_FIX //--vluzacn +#define HLRAD_CUSTOMCHOP // don't use this --vluzacn +#define HLRAD_RGBTRANSFIX //--vluzacn + #ifdef HLRAD_TRANSNONORMALIZE + #ifdef HLRAD_RGBTRANSFIX +#define HLRAD_TRANSWEIRDFIX //--vluzacn + #endif + #endif +#define HLRAD_MDL_LIGHT_HACK //--vluzacn +#define HLRAD_MINLIGHT //--vluzacn +#define HLRAD_FinalLightFace_VL // Compensate for engine's bug of no gamma correction when adding dynamic light styles together. --vluzacn + #ifdef HLRAD_TestSegmentAgainstOpaqueList_VL +#define HLRAD_POINT_IN_EDGE_FIX //--vluzacn + #endif +#define HLRAD_MULTISKYLIGHT //--vluzacn +#define HLRAD_ALLOWZEROBRIGHTNESS //--vluzacn + #ifdef HLRAD_TestSegmentAgainstOpaqueList_VL +#define HLRAD_OPAQUE_GROUP //--vluzacn //obsolete + #endif + #ifdef HLRAD_OPAQUE_GROUP +#define HLRAD_OPAQUE_RANGE //--vluzacn //obsolete + #endif +#define HLRAD_MATH_VL //--vluzacn + #ifdef HLRAD_NOSWAP + #ifdef HLRAD_TRANSWEIRDFIX +#define HLRAD_TRANSFERDATA_COMPRESS //--vluzacn + #endif + #endif +#define HLRAD_TRANCPARENCYLOSS_FIX //--vluzacn +#define HLRAD_STYLE_CORING //--vluzacn + #ifdef HLRAD_TestSegmentAgainstOpaqueList_VL + #ifdef HLRAD_STYLE_CORING + #ifdef HLRAD_MULTISKYLIGHT + #ifdef HLRAD_FinalLightFace_VL +#define HLRAD_OPAQUE_STYLE //--vluzacn + #endif + #endif + #endif + #endif + #ifdef HLRAD_NOSWAP +#define HLRAD_CheckVisBitNoVismatrix_NOSWAP //--vluzacn + #endif + #ifdef HLRAD_OPAQUE_STYLE + #ifdef HLRAD_CheckVisBitNoVismatrix_NOSWAP +#define HLRAD_OPAQUE_STYLE_BOUNCE //--vluzacn + #endif + #endif +#define HLRAD_GetPhongNormal_VL //--vluzacn +#define HLRAD_CUSTOMSMOOTH //--vluzacn +#define HLRAD_READABLE_EXCEEDSTYLEWARNING //--vluzacn +#define HLRAD_NUDGE_SMALLSTEP //--vluzacn +#define HLRAD_HLASSUMENOMEMORY //debug //--vluzacn +#define HLRAD_TestLine_EDGE_FIX //--vluzacn +#define HLRAD_STYLEREPORT //--vluzacn +#define HLRAD_SKYFIX_FIX //--vluzacn +#define HLRAD_NUDGE_VL //--vluzacn +#define HLRAD_WEIGHT_FIX //--vluzacn +#define HLRAD_PATCHBLACK_FIX //--vluzacn +#define HLRAD_HuntForWorld_EDGE_FIX // similar to HLRAD_TestLine_EDGE_FIX. --vluzacn +#define HLRAD_WITHOUTVIS //--vluzacn + #ifdef HLRAD_NUDGE_VL +#define HLRAD_SNAPTOWINDING //--vluzacn + #endif +#define HLRAD_HuntForWorld_FIX //--vluzacn + #ifdef HLRAD_HuntForWorld_FIX + #ifdef HLRAD_HuntForWorld_EDGE_FIX + #ifdef HLRAD_GetPhongNormal_VL + #ifdef HLRAD_SNAPTOWINDING +#define HLRAD_CalcPoints_NEW // --vluzacn + #endif + #endif + #endif + #endif +#define HLRAD_DPLANEOFFSET_MISCFIX //--vluzacn +#define HLRAD_NEGATIVEDIVIDEND_MISCFIX //--vluzacn +#define HLRAD_LERP_FIX //--vluzacn + #ifdef ZHLT_TEXLIGHT + #ifdef HLRAD_LERP_FIX +#define HLRAD_LERP_VL //--vluzacn + #endif + #endif + #ifdef HLRAD_LERP_VL +#define HLRAD_LERP_TRY5POINTS //--vluzacn + #endif +#define HLRAD_DEBUG_DRAWPOINTS //--vluzacn +#define HLRAD_SubdividePatch_NOTMIDDLE //--vluzacn + #ifdef HLRAD_CalcPoints_NEW +#define HLRAD_PHONG_FROMORIGINAL //--vluzacn + #endif + #ifdef HLRAD_GetPhongNormal_VL +#define HLRAD_SMOOTH_FACELIST //--vluzacn + #endif +#define HLRAD_SortPatches_FIX // Important!! --vluzacn + #ifdef HLRAD_MULTISKYLIGHT +#define HLRAD_GatherPatchLight //--vluzacn + #endif + #ifdef HLRAD_GatherPatchLight +#define HLRAD_SOFTSKY //--vluzacn + #endif +#define HLRAD_OPAQUE_NODE //--vluzacn + #ifdef HLRAD_CheckVisBitNoVismatrix_NOSWAP +#define HLRAD_TRANSLUCENT //--vluzacn + #endif + #ifdef HLRAD_OPAQUE_NODE + #ifdef HLRAD_CalcPoints_NEW +#define HLRAD_OPAQUE_BLOCK //--vluzacn + #endif + #endif +#define HLRAD_EDGESHARE_NOSPECIAL //--vluzacn + #ifdef HLRAD_SMOOTH_FACELIST +#define HLRAD_SMOOTH_TEXNORMAL //--vluzacn + #endif +#define HLRAD_TEXTURE //--vluzacn + #ifdef HLRAD_TEXTURE +#define HLRAD_REFLECTIVITY //--vluzacn + #endif +#define HLRAD_VIS_FIX //--vluzacn +#define HLRAD_ENTITYBOUNCE_FIX //--vluzacn + #ifdef HLRAD_TEXTURE + #ifdef HLRAD_OPAQUE_NODE +#define HLRAD_OPAQUE_ALPHATEST //--vluzacn + #endif + #endif + #ifdef HLRAD_GatherPatchLight + #ifdef ZHLT_TEXLIGHT +#define HLRAD_TEXLIGHTTHRESHOLD_FIX //--vluzacn + #endif + #endif + #ifdef HLRAD_REFLECTIVITY + #ifdef HLRAD_TEXLIGHTTHRESHOLD_FIX +#define HLRAD_CUSTOMTEXLIGHT //--vluzacn + #endif + #endif +#define HLRAD_ARG_MISC //--vluzacn +#define HLRAD_PairEdges_FACESIDE_FIX //--vluzacn + #ifdef HLRAD_ENTITYBOUNCE_FIX +#define HLRAD_VISMATRIX_NOMARKSURFACES //--vluzacn + #endif +#define HLRAD_WATERBLOCKLIGHT //--vluzacn + #ifdef HLRAD_MDL_LIGHT_HACK +#define HLRAD_MDL_LIGHT_HACK_NEW //--vluzacn + #endif + #ifdef HLRAD_LERP_VL + #ifdef HLRAD_SMOOTH_FACELIST +#define HLRAD_LERP_FACELIST //--vluzacn + #endif + #endif +#define HLRAD_WATERBACKFACE_FIX // remove this if you have fixed the engine's bug of drawing water backface. --vluzacn + #ifdef HLRAD_SMOOTH_TEXNORMAL + #ifdef HLRAD_LERP_VL + #ifdef HLRAD_CalcPoints_NEW +#define HLRAD_LERP_TEXNORMAL //--vluzacn + #endif + #endif + #endif +#define HLRAD_REDUCELIGHTMAP //--vluzacn + #ifdef HLRAD_STYLE_CORING + #ifdef ZHLT_TEXLIGHT + #ifdef HLRAD_REDUCELIGHTMAP +#define HLRAD_AUTOCORING //--vluzacn + #endif + #endif + #endif +#define HLRAD_OPAQUEINSKY_FIX //--vluzacn + #ifdef HLRAD_SOFTSKY +#define HLRAD_SUNSPREAD //--vluzacn + #endif + #ifdef HLRAD_MULTISKYLIGHT + #ifdef HLRAD_WHOME +#define HLRAD_SUNDIFFUSE //--vluzacn + #endif + #endif + #ifdef HLRAD_GatherPatchLight +#define HLRAD_FASTMODE //--vluzacn + #endif +#define HLRAD_OVERWRITEVERTEX_FIX //--vluzacn + #ifdef HLRAD_CUSTOMTEXLIGHT +#define HLRAD_TEXLIGHT_SPOTS_FIX //--vluzacn + #endif + #ifdef HLRAD_OPAQUE_STYLE_BOUNCE + #ifdef HLRAD_REFLECTIVITY +#define HLRAD_BOUNCE_STYLE //--vluzacn + #endif + #endif + #ifdef HLRAD_CalcPoints_NEW +#define HLRAD_BLUR //--vluzacn + #endif + #ifdef HLRAD_NOSWAP + #ifdef HLRAD_TRANSWEIRDFIX + #ifdef HLRAD_SOFTSKY +#define HLRAD_ACCURATEBOUNCE //--vluzacn + #endif + #endif + #endif + #ifdef HLRAD_TEXLIGHT_SPOTS_FIX + #ifdef HLRAD_ACCURATEBOUNCE +#define HLRAD_ACCURATEBOUNCE_TEXLIGHT // note: this reduces the compile time in '-extra' mode //--vluzacn + #endif + #endif + #ifdef ZHLT_TEXLIGHT + #ifdef HLRAD_CalcPoints_NEW + #ifdef HLRAD_AUTOCORING +#define HLRAD_ACCURATEBOUNCE_SAMPLELIGHT //--vluzacn + #endif + #endif + #endif + #ifdef HLRAD_ACCURATEBOUNCE_TEXLIGHT +#define HLRAD_ACCURATEBOUNCE_ALTERNATEORIGIN //--vluzacn + #endif + #ifdef HLRAD_PATCHBLACK_FIX + #ifdef HLRAD_NOSWAP +#define HLRAD_ACCURATEBOUNCE_REDUCEAREA //--vluzacn + #endif + #endif + #ifdef HLRAD_CUSTOMTEXLIGHT +#define HLRAD_CUSTOMTEXLIGHT_COLOR //--vluzacn + #endif + #ifdef ZHLT_WINDING_EPSILON +#define HLRAD_SUBDIVIDEPATCH_NEW //--vluzacn + #endif + #ifdef HLRAD_NOSWAP +#define HLRAD_DIVERSE_LIGHTING //--vluzacn + #endif + #ifdef HLRAD_CalcPoints_NEW + #ifdef HLRAD_BLUR + #ifdef HLRAD_GetPhongNormal_VL + #ifdef ZHLT_TEXLIGHT + #ifdef HLRAD_SNAPTOWINDING +#define HLRAD_GROWSAMPLE //--vluzacn + #endif + #endif + #endif + #endif + #endif + #ifdef HLRAD_BLUR +#define HLRAD_AVOIDNORMALFLIP //--vluzacn + #endif + #ifdef HLRAD_BLUR + #ifdef HLRAD_GROWSAMPLE +#define HLRAD_BLUR_MINIMALSQUARE //--vluzacn + #endif + #endif + #ifdef HLRAD_BLUR_MINIMALSQUARE +#define HLRAD_AVOIDWALLBLEED //--vluzacn + #endif + #ifdef HLRAD_FinalLightFace_VL +#define HLRAD_PRESERVELIGHTMAPCOLOR //--vluzacn + #endif +#define HLRAD_MORE_PATCHES //--vluzacn + #ifdef HLRAD_VISMATRIX_NOMARKSURFACES +#define HLRAD_SPARSEVISMATRIX_FAST //--vluzacn + #endif + #ifdef HLRAD_LERP_VL + #ifdef HLRAD_SMOOTH_FACELIST + #ifdef HLRAD_GROWSAMPLE + #ifdef HLRAD_DEBUG_DRAWPOINTS +#define HLRAD_LOCALTRIANGULATION //--vluzacn + #endif + #endif + #endif + #endif + #ifdef HLRAD_LOCALTRIANGULATION +#define HLRAD_BILINEARINTERPOLATION //--vluzacn + #endif +#define HLRAD_TEXLIGHTGAP //--vluzacn + #ifdef HLRAD_LOCALTRIANGULATION +#define HLRAD_FARPATCH_FIX //--vluzacn + #endif +#define HLRAD_TRANSPARENCY_FAST //--vluzacn + +#if defined (ZHLT_XASH) || defined (ZHLT_XASH2) +#if !defined (ZHLT_TEXLIGHT) || !defined (HLRAD_LERP_VL) || !defined (HLRAD_AUTOCORING) || !defined (HLRAD_MULTISKYLIGHT) || !defined (HLRAD_FinalLightFace_VL) || !defined (HLRAD_AVOIDNORMALFLIP) +#error "ZHLT_XASH has not been implemented for current configuration" +#endif +#endif +//===================================================================== + +#if _MSC_VER <1400 +#define strcpy_s strcpy //--vluzacn +#define sprintf_s sprintf //--vluzacn +#endif +#if _MSC_VER >= 1400 +#pragma warning(disable: 4996) +#endif + +#ifdef __MINGW32__ +#include +#endif + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#if 0 //--vluzacn +// AJM: gnu compiler fix +#ifdef __GNUC__ +#define _alloca __builtin_alloca +#define alloca __builtin_alloca +#endif +#endif + +#include "win32fix.h" +#include "mathtypes.h" + +#ifdef SYSTEM_WIN32 +#pragma warning(disable: 4127) // conditional expression is constant +#pragma warning(disable: 4115) // named type definition in parentheses +#pragma warning(disable: 4244) // conversion from 'type' to type', possible loss of data +// AJM +#pragma warning(disable: 4786) // identifier was truncated to '255' characters in the browser information +#pragma warning(disable: 4305) // truncation from 'const double' to 'float' +#pragma warning(disable: 4800) // forcing value to bool 'true' or 'false' (performance warning) +#endif + + +#ifdef STDC_HEADERS +#include +#include +#include +#include +#include +#include +#include +#include +#endif + +#include //--vluzacn + +#ifdef HAVE_SYS_TIME_H +#include +#endif +#ifdef HAVE_UNISTD_H +#include +#endif + +#ifdef ZHLT_NETVIS +#include "c2cpp.h" +#endif + +#ifdef SYSTEM_WIN32 +#define SYSTEM_SLASH_CHAR '\\' +#define SYSTEM_SLASH_STR "\\" +#endif +#ifdef SYSTEM_POSIX +#define SYSTEM_SLASH_CHAR '/' +#define SYSTEM_SLASH_STR "/" +#endif + +// the dec offsetof macro doesn't work very well... +#define myoffsetof(type,identifier) ((size_t)&((type*)0)->identifier) +#define sizeofElement(type,identifier) (sizeof((type*)0)->identifier) + +#ifdef SYSTEM_POSIX +extern char* strupr(char* string); +extern char* strlwr(char* string); +#endif +extern const char* stristr(const char* const string, const char* const substring); +extern bool CDECL FORMAT_PRINTF(3,4) safe_snprintf(char* const dest, const size_t count, const char* const args, ...); +extern bool safe_strncpy(char* const dest, const char* const src, const size_t count); +extern bool safe_strncat(char* const dest, const char* const src, const size_t count); +extern bool TerminatedString(const char* buffer, const int size); + +extern char* FlipSlashes(char* string); + +extern double I_FloatTime(); + +extern int CheckParm(char* check); + +extern void DefaultExtension(char* path, const char* extension); +extern void DefaultPath(char* path, char* basepath); +extern void StripFilename(char* path); +extern void StripExtension(char* path); + +extern void ExtractFile(const char* const path, char* dest); +extern void ExtractFilePath(const char* const path, char* dest); +extern void ExtractFileBase(const char* const path, char* dest); +extern void ExtractFileExtension(const char* const path, char* dest); + +extern short BigShort(short l); +extern short LittleShort(short l); +extern int BigLong(int l); +extern int LittleLong(int l); +extern float BigFloat(float l); +extern float LittleFloat(float l); + +#endif //CMDLIB_H__ diff --git a/src/zhlt-vluzacn/common/cmdlinecfg.cpp b/src/zhlt-vluzacn/common/cmdlinecfg.cpp new file mode 100644 index 0000000..fe369da --- /dev/null +++ b/src/zhlt-vluzacn/common/cmdlinecfg.cpp @@ -0,0 +1,352 @@ +#include "cmdlib.h" +#include "scriplib.h" +#include "cmdlinecfg.h" +#include "log.h" +#include +#include +#include +#include +#ifdef SYSTEM_WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#endif + +#ifdef ZHLT_PARAMFILE +const char paramfilename[_MAX_PATH] = "settings.txt"; +const char sepchr = '\n'; +bool error = false; +#define SEPSTR "\n" + +int plen (const char *p) +{ + int l; + for (l = 0; ; l++) + { + if (p[l] == '\0') + return -1; + if (p[l] == sepchr) + return l; + } +} +bool pvalid (const char *p) +{ + return plen (p) >= 0; +} +bool pmatch (const char *cmdlineparam, const char *param) +{ + int cl, cstart, cend, pl, pstart, pend, k; + cl = plen (cmdlineparam); + pl = plen (param); + if (cl < 0 || pl < 0) + return false; + bool anystart = (pl > 0 && param[0] == '*'); + bool anyend = (pl > 0 && param[pl-1] == '*'); + pstart = anystart ? 1 : 0; + pend = anyend ? pl-1 : pl; + if (pend < pstart) pend = pstart; + for (cstart = 0; cstart <= cl; ++cstart) + { + for (cend = cl; cend >= cstart; --cend) + { + if (cend - cstart == pend - pstart) + { + for (k = 0; k < cend - cstart; ++k) + if (tolower (cmdlineparam[k+cstart]) != tolower (param[k+pstart])) + break; + if (k == cend - cstart) + return true; + } + if (!anyend) + break; + } + if (!anystart) + break; + } + return false; +} +char * pnext (char *p) +{ + return p + (plen (p) + 1); +} +char * findparams (char *cmdlineparams, char *params) +{ + char *c1, *c, *p; + for (c1 = cmdlineparams; pvalid (c1); c1 = pnext (c1)) + { + for (c = c1, p = params; pvalid (p); c = pnext (c), p = pnext (p)) + if (!pvalid (c) || !pmatch (c, p)) + break; + if (!pvalid (p)) + return c1; + } + return NULL; +} +void addparams (char *cmdline, char *params, unsigned int n) +{ + if (strlen (cmdline) + strlen (params) + 1 <= n) + strcat (cmdline, params); + else + error = true; +} +void delparams (char *cmdline, char *params) +{ + char *c, *p; + if (!pvalid (params)) //avoid infinite loop + return; + while (cmdline = findparams (cmdline, params), cmdline != NULL) + { + for (c = cmdline, p = params; pvalid (p); c = pnext (c), p = pnext (p)) + ; + memmove (cmdline, c, strlen (c) + 1); + } +} +typedef enum +{ + IFDEF, IFNDEF, ELSE, ENDIF, DEFINE, UNDEF +} +command_t; +typedef struct +{ + int stack; + bool skip; + int skipstack; +} +execute_t; +void parsecommand (execute_t &e, char *cmdline, char *words, unsigned int n) +{ + command_t t; + if (!pvalid (words)) + return; + if (pmatch (words, "#ifdef" SEPSTR)) + t = IFDEF; + else if (pmatch (words, "#ifndef" SEPSTR)) + t = IFNDEF; + else if (pmatch (words, "#else" SEPSTR)) + t = ELSE; + else if (pmatch (words, "#endif" SEPSTR)) + t = ENDIF; + else if (pmatch (words, "#define" SEPSTR)) + t = DEFINE; + else if (pmatch (words, "#undef" SEPSTR)) + t = UNDEF; + else + return; + if (t == IFDEF || t == IFNDEF) + { + e.stack ++; + if (!e.skip) + { + if (t == IFDEF && findparams (cmdline, pnext (words)) || + t == IFNDEF && !findparams (cmdline, pnext (words))) + e.skip = false; + else + { + e.skipstack = e.stack; + e.skip = true; + } + } + } + else if (t == ELSE) + { + if (e.skip) + { + if (e.stack == e.skipstack) + e.skip = false; + } + else + { + e.skipstack = e.stack; + e.skip = true; + } + } + else if (t == ENDIF) + { + if (e.skip) + { + if (e.stack == e.skipstack) + e.skip = false; + } + e.stack --; + } + else + { + if (!e.skip) + { + if (t == DEFINE) + addparams (cmdline, pnext(words), n); + if (t == UNDEF) + delparams (cmdline, pnext(words)); + } + } +} +const char * nextword (const char *s, char *token, unsigned int n) +{ + unsigned int i; + const char *c; + bool quote, comment, content; + for (c=s, i=0, quote=false, comment=false, content=false; c[0] != '\0'; c++) + { + if (c[0]=='\"') + quote = !quote; + if (c[0]=='\n') + quote = false; + if (c[0]=='\n') + comment = false; + if (!quote && c[0]=='/' && c[1]=='/') + comment = true; + if (!comment && !(c[0]=='\n' || isspace (c[0]))) + content = true; + if (!quote && !comment && content && (c[0]=='\n' || isspace (c[0]))) + break; + if (content && c[0]!='\"') + if (i"); + strcat (cmdline, SEPSTR); + for (i=1; i +#include +#include +#endif + +#ifdef SYSTEM_POSIX +#ifdef HAVE_SYS_STAT_H +#include +#endif + +#ifdef HAVE_FCNTL_H +#include +#endif + +#ifdef HAVE_UNISTD_H +#include +#endif +#endif + +#include "cmdlib.h" +#include "messages.h" +#include "log.h" +#include "mathtypes.h" +#include "mathlib.h" +#include "blockmem.h" + +/* + * ============== + * getfiletime + * ============== + */ + +time_t getfiletime(const char* const filename) +{ + time_t filetime = 0; + struct stat filestat; + + if (stat(filename, &filestat) == 0) + filetime = qmax(filestat.st_mtime, filestat.st_ctime); + + return filetime; +} + +/* + * ============== + * getfilesize + * ============== + */ +long getfilesize(const char* const filename) +{ + long size = 0; + struct stat filestat; + + if (stat(filename, &filestat) == 0) + size = filestat.st_size; + + return size; +} + +/* + * ============== + * getfiledata + * ============== + */ +long getfiledata(const char* const filename, char* buffer, const int buffersize) +{ + long size = 0; + int handle; + time_t start, end; + + time(&start); + + if ((handle = _open(filename, O_RDONLY)) != -1) + { + int bytesread; + + Log("%-20s Restoring [%-13s - ", "BuildVisMatrix:", filename); + while ((bytesread = _read(handle, buffer, qmin(32 * 1024, buffersize - size))) > 0) + { + size += bytesread; + buffer += bytesread; + } + _close(handle); + time(&end); + Log("%10.3fMB] (%d)\n", size / (1024.0 * 1024.0), end - start); + } + + if (buffersize != size) + { + Warning("Invalid file [%s] found. File will be rebuilt!\n", filename); + _unlink(filename); + } + + return size; +} + +/* + * ================ + * filelength + * ================ + */ +int q_filelength(FILE* f) +{ + int pos; + int end; + + pos = ftell(f); + fseek(f, 0, SEEK_END); + end = ftell(f); + fseek(f, pos, SEEK_SET); + + return end; +} + +/* + * ================ + * exists + * ================ + */ +bool q_exists(const char* const filename) +{ + FILE* f; + + f = fopen(filename, "rb"); + + if (!f) + { + IfDebug(Developer(DEVELOPER_LEVEL_SPAM, "Checking for existance of file %s (failed)\n", filename)); + return false; + } + else + { + fclose(f); + IfDebug(Developer(DEVELOPER_LEVEL_SPAM, "Checking for existance of file %s (success)\n", filename)); + return true; + } +} + + + + +FILE* SafeOpenWrite(const char* const filename) +{ + FILE* f; + + f = fopen(filename, "wb"); + + if (!f) + Error("Error opening %s: %s", filename, strerror(errno)); + + return f; +} + +FILE* SafeOpenRead(const char* const filename) +{ + FILE* f; + + f = fopen(filename, "rb"); + + if (!f) + Error("Error opening %s: %s", filename, strerror(errno)); + + return f; +} + +void SafeRead(FILE* f, void* buffer, int count) +{ + if (fread(buffer, 1, count, f) != (size_t) count) + Error("File read failure"); +} + +void SafeWrite(FILE* f, const void* const buffer, int count) +{ + if (fwrite(buffer, 1, count, f) != (size_t) count) + Error("File write failure"); //Error("File read failure"); //--vluzacn +} + +/* + * ============== + * LoadFile + * ============== + */ +int LoadFile(const char* const filename, char** bufferptr) +{ + FILE* f; + int length; + char* buffer; + + f = SafeOpenRead(filename); + length = q_filelength(f); + buffer = (char*)Alloc(length + 1); + SafeRead(f, buffer, length); + fclose(f); + + *bufferptr = buffer; + return length; +} + +/* + * ============== + * SaveFile + * ============== + */ +void SaveFile(const char* const filename, const void* const buffer, int count) +{ + FILE* f; + + f = SafeOpenWrite(filename); + SafeWrite(f, buffer, count); + fclose(f); +} + diff --git a/src/zhlt-vluzacn/common/filelib.h b/src/zhlt-vluzacn/common/filelib.h new file mode 100644 index 0000000..75cc439 --- /dev/null +++ b/src/zhlt-vluzacn/common/filelib.h @@ -0,0 +1,23 @@ +#ifndef FILELIB_H__ +#define FILELIB_H__ +#include "cmdlib.h" //--vluzacn + +#if _MSC_VER >= 1000 +#pragma once +#endif + +extern time_t getfiletime(const char* const filename); +extern long getfilesize(const char* const filename); +extern long getfiledata(const char* const filename, char* buffer, const int buffersize); +extern bool q_exists(const char* const filename); +extern int q_filelength(FILE* f); + +extern FILE* SafeOpenWrite(const char* const filename); +extern FILE* SafeOpenRead(const char* const filename); +extern void SafeRead(FILE* f, void* buffer, int count); +extern void SafeWrite(FILE* f, const void* const buffer, int count); + +extern int LoadFile(const char* const filename, char** bufferptr); +extern void SaveFile(const char* const filename, const void* const buffer, int count); + +#endif //**/ FILELIB_H__ diff --git a/src/zhlt-vluzacn/common/files.cpp b/src/zhlt-vluzacn/common/files.cpp new file mode 100644 index 0000000..e69de29 diff --git a/src/zhlt-vluzacn/common/hlassert.h b/src/zhlt-vluzacn/common/hlassert.h new file mode 100644 index 0000000..b07c944 --- /dev/null +++ b/src/zhlt-vluzacn/common/hlassert.h @@ -0,0 +1,41 @@ +#ifndef HLASSERT_H__ +#define HLASSERT_H__ +#include "cmdlib.h" //--vluzacn + +#if _MSC_VER >= 1000 +#pragma once +#endif + +#ifdef SYSTEM_WIN32 +#ifdef _DEBUG + +#include "log.h" + +#define assume(exp, message) {if (!(exp)) {Log("\n***** ERROR *****\nAssume '%s' failed\n at %s:%d\n %s\n\n", #exp, __FILE__, __LINE__, message); __asm{int 3} }} +#define hlassert(exp) assume(exp, "") + +#else // _DEBUG + +#define assume(exp, message) {if (!(exp)) {Error("\nAssume '%s' failed\n at %s:%d\n %s\n\n", #exp, __FILE__, __LINE__, message);}} +#define hlassert(exp) + +#endif // _DEBUG +#endif // SYSTEM_WIN32 + +#ifdef SYSTEM_POSIX +#ifdef _DEBUG + +#include "log.h" + +#define assume(exp, message) {if (!(exp)) {Log("\n***** ERROR *****\nAssume '%s' failed\n at %s:%d\n %s\n\n", #exp, __FILE__, __LINE__, message); exit(-1); }} +#define hlassert(exp) assume(exp, "") + +#else // _DEBUG + +#define assume(exp, message) {if (!(exp)) {Error("\nAssume '%s' failed\n at %s:%d\n %s\n\n", #exp, __FILE__, __LINE__, message);}} //#define assume(exp, message) {if (!(exp)) {Error("\nAssume '%s' failed\n\n", #exp, __FILE__, __LINE__, message);}} //--vluzacn +#define hlassert(exp) + +#endif // _DEBUG +#endif // SYSTEM_POSIX + +#endif // SYSTEM_POSIX HLASSERT_H__ diff --git a/src/zhlt-vluzacn/common/log.cpp b/src/zhlt-vluzacn/common/log.cpp new file mode 100644 index 0000000..42f4dc3 --- /dev/null +++ b/src/zhlt-vluzacn/common/log.cpp @@ -0,0 +1,959 @@ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef ZHLT_NETVIS +#ifdef SYSTEM_WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#endif +#endif + +#ifdef STDC_HEADERS +#include +#include +#include +#endif + +#ifdef HAVE_UNISTD_H +#include +#endif + +#ifdef ZHLT_NETVIS +#include "../netvis/c2cpp.h" +#endif + +#include "cmdlib.h" +#include "messages.h" +#include "hlassert.h" +#include "log.h" +#include "filelib.h" + +#ifdef ZHLT_CONSOLE +#ifdef SYSTEM_WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#endif +#endif + +#ifdef ZHLT_LANGFILE +#include "scriplib.h" +#endif + +char* g_Program = "Uninitialized variable ::g_Program"; +char g_Mapname[_MAX_PATH] = "Uninitialized variable ::g_Mapname"; + +developer_level_t g_developer = DEFAULT_DEVELOPER; +bool g_verbose = DEFAULT_VERBOSE; +bool g_log = DEFAULT_LOG; + +unsigned long g_clientid = 0; +unsigned long g_nextclientid = 0; + +static FILE* CompileLog = NULL; +static bool fatal = false; + +#ifdef ZHLT_CONSOLE +bool twice = false; +bool useconsole = false; +FILE *conout = NULL; +#endif + +#ifdef ZHLT_LANGFILE +int g_lang_count = 0; +const int g_lang_max = 1024; +char* g_lang[g_lang_max][2]; +#endif + +//////// + +void ResetTmpFiles() +{ + if (g_log) + { + char filename[_MAX_PATH]; + + safe_snprintf(filename, _MAX_PATH, "%s.bsp", g_Mapname); + _unlink(filename); + + safe_snprintf(filename, _MAX_PATH, "%s.inc", g_Mapname); + _unlink(filename); + + safe_snprintf(filename, _MAX_PATH, "%s.p0", g_Mapname); + _unlink(filename); + + safe_snprintf(filename, _MAX_PATH, "%s.p1", g_Mapname); + _unlink(filename); + + safe_snprintf(filename, _MAX_PATH, "%s.p2", g_Mapname); + _unlink(filename); + + safe_snprintf(filename, _MAX_PATH, "%s.p3", g_Mapname); + _unlink(filename); + + safe_snprintf(filename, _MAX_PATH, "%s.prt", g_Mapname); + _unlink(filename); + + safe_snprintf(filename, _MAX_PATH, "%s.pts", g_Mapname); + _unlink(filename); + + safe_snprintf(filename, _MAX_PATH, "%s.lin", g_Mapname); + _unlink(filename); + +#ifndef HLCSG_ONLYENTS_NOWADCHANGE + safe_snprintf(filename, _MAX_PATH, "%s.wic", g_Mapname); + _unlink(filename); +#endif +#ifdef HLCSG_HLBSP_ALLOWEMPTYENTITY + + safe_snprintf(filename, _MAX_PATH, "%s.hsz", g_Mapname); + _unlink(filename); +#endif +#ifdef HLCSG_HLBSP_DOUBLEPLANE + + safe_snprintf(filename, _MAX_PATH, "%s.pln", g_Mapname); + _unlink(filename); +#endif +#ifdef ZHLT_DETAILBRUSH + + safe_snprintf(filename, _MAX_PATH, "%s.b0", g_Mapname); + _unlink(filename); + + safe_snprintf(filename, _MAX_PATH, "%s.b1", g_Mapname); + _unlink(filename); + + safe_snprintf(filename, _MAX_PATH, "%s.b2", g_Mapname); + _unlink(filename); + + safe_snprintf(filename, _MAX_PATH, "%s.b3", g_Mapname); + _unlink(filename); +#endif +#ifdef ZHLT_NOWADDIR + + safe_snprintf(filename, _MAX_PATH, "%s.wa_", g_Mapname); + _unlink(filename); +#endif +#ifdef ZHLT_64BIT_FIX + + safe_snprintf(filename, _MAX_PATH, "%s.ext", g_Mapname); + _unlink(filename); +#endif +#ifdef ZHLT_XASH + + safe_snprintf(filename, _MAX_PATH, "%s.dlit", g_Mapname); + _unlink(filename); +#endif + } +} + +void ResetLog() +{ + if (g_log) + { + char logfilename[_MAX_PATH]; + + safe_snprintf(logfilename, _MAX_PATH, "%s.log", g_Mapname); + _unlink(logfilename); + } +} + +void ResetErrorLog() +{ + if (g_log) + { + char logfilename[_MAX_PATH]; + + safe_snprintf(logfilename, _MAX_PATH, "%s.err", g_Mapname); + _unlink(logfilename); + } +} + +void CheckForErrorLog() +{ + if (g_log) + { + char logfilename[_MAX_PATH]; + + safe_snprintf(logfilename, _MAX_PATH, "%s.err", g_Mapname); + if (q_exists(logfilename)) + { + Log(">> There was a problem compiling the map.\n" + ">> Check the file %s.log for the cause.\n", + g_Mapname); + exit(1); + } + } +} + +/////// + +void LogError(const char* const message) +{ + if (g_log && CompileLog) + { + char logfilename[_MAX_PATH]; + FILE* ErrorLog = NULL; + + safe_snprintf(logfilename, _MAX_PATH, "%s.err", g_Mapname); + ErrorLog = fopen(logfilename, "a"); + + if (ErrorLog) + { + fprintf(ErrorLog, "%s: %s\n", g_Program, message); + fflush(ErrorLog); + fclose(ErrorLog); + ErrorLog = NULL; + } + else + { +#ifdef ZHLT_LANGFILE + fprintf(stderr, Localize ("ERROR: Could not open error logfile %s"), logfilename); +#else + fprintf(stderr, "ERROR: Could not open error logfile %s", logfilename); +#endif + fflush(stderr); +#ifdef ZHLT_CONSOLE + if (twice) + { +#ifdef ZHLT_LANGFILE + fprintf (conout, Localize ("ERROR: Could not open error logfile %s"), logfilename); +#else + fprintf (conout, "ERROR: Could not open error logfile %s", logfilename); +#endif + fflush (conout); + } +#endif + } + } +} + +void CDECL OpenLog(const int clientid) +{ + if (g_log) + { + char logfilename[_MAX_PATH]; + +#ifdef ZHLT_NETVIS + #ifdef SYSTEM_WIN32 + if (clientid) + { + char computername[MAX_COMPUTERNAME_LENGTH + 1]; + unsigned long size = sizeof(computername); + + if (!GetComputerName(computername, &size)) + { + safe_strncpy(computername, "unknown", sizeof(computername)); + } + safe_snprintf(logfilename, _MAX_PATH, "%s-%s-%d.log", g_Mapname, computername, clientid); + } + else + #endif + #ifdef SYSTEM_POSIX + if (clientid) + { + char computername[_MAX_PATH]; + unsigned long size = sizeof(computername); + + if (gethostname(computername, size)) + { + safe_strncpy(computername, "unknown", sizeof(computername)); + } + safe_snprintf(logfilename, _MAX_PATH, "%s-%s-%d.log", g_Mapname, computername, clientid); + } + #endif +#endif + { + safe_snprintf(logfilename, _MAX_PATH, "%s.log", g_Mapname); + } + CompileLog = fopen(logfilename, "a"); + + if (!CompileLog) + { +#ifdef ZHLT_LANGFILE + fprintf(stderr, Localize ("ERROR: Could not open logfile %s"), logfilename); +#else + fprintf(stderr, "ERROR: Could not open logfile %s", logfilename); +#endif + fflush(stderr); +#ifdef ZHLT_CONSOLE + if (twice) + { +#ifdef ZHLT_LANGFILE + fprintf (conout, Localize ("ERROR: Could not open logfile %s"), logfilename); +#else + fprintf (conout, "ERROR: Could not open logfile %s", logfilename); +#endif + fflush (conout); + } +#endif + } + } +} + +void CDECL CloseLog() +{ + if (g_log && CompileLog) + { + LogEnd(); + fflush(CompileLog); + fclose(CompileLog); + CompileLog = NULL; + } +} + +// +// Every function up to this point should check g_log, the functions below should not +// + +#ifdef SYSTEM_WIN32 +// AJM: fprintf/flush wasnt printing newline chars correctly (prefixed with \r) under win32 +// due to the fact that those streams are in byte mode, so this function prefixes +// all \n with \r automatically. +// NOTE: system load may be more with this method, but there isnt that much logging going +// on compared to the time taken to compile the map, so its negligable. +void Safe_WriteLog(const char* const message) +{ + const char* c; + + if (!CompileLog) + return; + + c = &message[0]; + + while (1) + { + if (!*c) + return; // end of string + + if (*c == '\n') + fputc('\r', CompileLog); + + fputc(*c, CompileLog); + + c++; + } +} +#endif + +void WriteLog(const char* const message) +{ + +#ifndef SYSTEM_WIN32 + if (CompileLog) + { + fprintf(CompileLog, "%s", message); //fprintf(CompileLog, message); //--vluzacn + fflush(CompileLog); + } +#else + Safe_WriteLog(message); +#endif + + fprintf(stdout, "%s", message); //fprintf(stdout, message); //--vluzacn + fflush(stdout); +#ifdef ZHLT_CONSOLE + if (twice) + { + fprintf (conout, "%s", message); + fflush (conout); + } +#endif +} + +// ===================================================================================== +// CheckFatal +// ===================================================================================== +void CheckFatal() +{ + if (fatal) + { + hlassert(false); + exit(1); + } +} + +#define MAX_ERROR 2048 +#define MAX_WARNING 2048 +#define MAX_MESSAGE 2048 + +// ===================================================================================== +// Error +// for formatted error messages, fatals out +// ===================================================================================== +void CDECL FORMAT_PRINTF(1,2) Error(const char* const error, ...) +{ + char message[MAX_ERROR]; + char message2[MAX_ERROR]; + va_list argptr; + + /*#if defined( SYSTEM_WIN32 ) && !defined( __MINGW32__ ) && !defined( __BORLANDC__ ) + { + char* wantint3 = getenv("WANTINT3"); + if (wantint3) + { + if (atoi(wantint3)) + { + __asm + { + int 3; + } + } + } + } +#endif*/ + + va_start(argptr, error); +#ifdef ZHLT_LANGFILE + vsnprintf(message, MAX_ERROR, Localize (error), argptr); +#else + vsnprintf(message, MAX_ERROR, error, argptr); +#endif + va_end(argptr); + +#ifdef ZHLT_LANGFILE + safe_snprintf(message2, MAX_MESSAGE, "%s%s\n", Localize ("Error: "), message); +#else + safe_snprintf(message2, MAX_MESSAGE, "Error: %s\n", message); +#endif + WriteLog(message2); + LogError(message2); + + fatal = 1; + CheckFatal(); +} + +// ===================================================================================== +// Fatal +// For formatted 'fatal' warning messages +// automatically appends an extra newline to the message +// This function sets a flag that the compile should abort before completing +// ===================================================================================== +void CDECL FORMAT_PRINTF(2,3) Fatal(assume_msgs msgid, const char* const warning, ...) +{ + char message[MAX_WARNING]; + char message2[MAX_WARNING]; + + va_list argptr; + + va_start(argptr, warning); +#ifdef ZHLT_LANGFILE + vsnprintf(message, MAX_WARNING, Localize (warning), argptr); +#else + vsnprintf(message, MAX_WARNING, warning, argptr); +#endif + va_end(argptr); + +#ifdef ZHLT_LANGFILE + safe_snprintf(message2, MAX_MESSAGE, "%s%s\n", Localize ("Error: "), message); +#else + safe_snprintf(message2, MAX_MESSAGE, "Error: %s\n", message); +#endif + WriteLog(message2); + LogError(message2); + + { + char message[MAX_MESSAGE]; + const MessageTable_t* msg = GetAssume(msgid); + +#ifdef ZHLT_LANGFILE + safe_snprintf(message, MAX_MESSAGE, "%s\n%s%s\n%s%s\n", Localize (msg->title), Localize ("Description: "), Localize (msg->text), Localize ("Howto Fix: "), Localize (msg->howto)); +#else + safe_snprintf(message, MAX_MESSAGE, "%s\nDescription: %s\nHowto Fix: %s\n", msg->title, msg->text, msg->howto); +#endif + PrintOnce(message); + } + + fatal = 1; +} + +// ===================================================================================== +// PrintOnce +// This function is only callable one time. Further calls will be ignored +// ===================================================================================== +void CDECL FORMAT_PRINTF(1,2) PrintOnce(const char* const warning, ...) +{ + char message[MAX_WARNING]; + char message2[MAX_WARNING]; + va_list argptr; + static int count = 0; + + if (count > 0) // make sure it only gets called once + { + return; + } + count++; + + va_start(argptr, warning); +#ifdef ZHLT_LANGFILE + vsnprintf(message, MAX_WARNING, Localize (warning), argptr); +#else + vsnprintf(message, MAX_WARNING, warning, argptr); +#endif + va_end(argptr); + +#ifdef ZHLT_LANGFILE + safe_snprintf(message2, MAX_MESSAGE, "%s%s\n", Localize ("Error: "), message); +#else + safe_snprintf(message2, MAX_MESSAGE, "Error: %s\n", message); +#endif + WriteLog(message2); + LogError(message2); +} + +// ===================================================================================== +// Warning +// For formatted warning messages +// automatically appends an extra newline to the message +// ===================================================================================== +void CDECL FORMAT_PRINTF(1,2) Warning(const char* const warning, ...) +{ + char message[MAX_WARNING]; + char message2[MAX_WARNING]; + + va_list argptr; + + va_start(argptr, warning); +#ifdef ZHLT_LANGFILE + vsnprintf(message, MAX_WARNING, Localize (warning), argptr); +#else + vsnprintf(message, MAX_WARNING, warning, argptr); +#endif + va_end(argptr); + +#ifdef ZHLT_LANGFILE + safe_snprintf(message2, MAX_MESSAGE, "%s%s\n", Localize ("Warning: "), message); +#else + safe_snprintf(message2, MAX_MESSAGE, "Warning: %s\n", message); +#endif + WriteLog(message2); +} + +// ===================================================================================== +// Verbose +// Same as log but only prints when in verbose mode +// ===================================================================================== +void CDECL FORMAT_PRINTF(1,2) Verbose(const char* const warning, ...) +{ + if (g_verbose) + { + char message[MAX_MESSAGE]; + + va_list argptr; + + va_start(argptr, warning); +#ifdef ZHLT_LANGFILE + vsnprintf(message, MAX_MESSAGE, Localize (warning), argptr); +#else + vsnprintf(message, MAX_MESSAGE, warning, argptr); +#endif + va_end(argptr); + + WriteLog(message); + } +} + +// ===================================================================================== +// Developer +// Same as log but only prints when in developer mode +// ===================================================================================== +void CDECL FORMAT_PRINTF(2,3) Developer(developer_level_t level, const char* const warning, ...) +{ + if (level <= g_developer) + { + char message[MAX_MESSAGE]; + + va_list argptr; + + va_start(argptr, warning); +#ifdef ZHLT_LANGFILE + vsnprintf(message, MAX_MESSAGE, Localize (warning), argptr); +#else + vsnprintf(message, MAX_MESSAGE, warning, argptr); +#endif + va_end(argptr); + + WriteLog(message); + } +} + +// ===================================================================================== +// DisplayDeveloperLevel +// ===================================================================================== +static void DisplayDeveloperLevel() +{ + char message[MAX_MESSAGE]; + + safe_strncpy(message, "Developer messages enabled : [", MAX_MESSAGE); + if (g_developer >= DEVELOPER_LEVEL_MEGASPAM) + { + safe_strncat(message, "MegaSpam ", MAX_MESSAGE); + } + if (g_developer >= DEVELOPER_LEVEL_SPAM) + { + safe_strncat(message, "Spam ", MAX_MESSAGE); + } + if (g_developer >= DEVELOPER_LEVEL_FLUFF) + { + safe_strncat(message, "Fluff ", MAX_MESSAGE); + } + if (g_developer >= DEVELOPER_LEVEL_MESSAGE) + { + safe_strncat(message, "Message ", MAX_MESSAGE); + } + if (g_developer >= DEVELOPER_LEVEL_WARNING) + { + safe_strncat(message, "Warning ", MAX_MESSAGE); + } + if (g_developer >= DEVELOPER_LEVEL_ERROR) + { + safe_strncat(message, "Error", MAX_MESSAGE); + } + if (g_developer) + { + safe_strncat(message, "]\n", MAX_MESSAGE); + Log(message); + } +} + +// ===================================================================================== +// Log +// For formatted log output messages +// ===================================================================================== +void CDECL FORMAT_PRINTF(1,2) Log(const char* const warning, ...) +{ + char message[MAX_MESSAGE]; + + va_list argptr; + + va_start(argptr, warning); +#ifdef ZHLT_LANGFILE + vsnprintf(message, MAX_MESSAGE, Localize (warning), argptr); +#else + vsnprintf(message, MAX_MESSAGE, warning, argptr); +#endif + va_end(argptr); + + WriteLog(message); +} + +// ===================================================================================== +// LogArgs +// ===================================================================================== +static void LogArgs(int argc, char** argv) +{ + int i; + + Log("Command line: "); + for (i = 0; i < argc; i++) + { + if (strchr(argv[i], ' ')) + { + Log("\"%s\" ", argv[i]); //Log("\"%s\"", argv[i]); //--vluzacn + } + else + { + Log("%s ", argv[i]); + } + } + Log("\n"); +} + +// ===================================================================================== +// Banner +// ===================================================================================== +void Banner() +{ + Log("%s " ZHLT_VERSIONSTRING " " HACK_VERSIONSTRING +#ifdef ZHLT_64BIT_FIX +#ifndef VERSION_32BIT + " " PLATFORM_VERSIONSTRING +#endif +#endif + " (%s)\n", g_Program, __DATE__); + //Log("BUGGY %s (built: %s)\nUse at own risk.\n", g_Program, __DATE__); +#ifdef ZHLT_XASH2 + Log (" - special edition for Xash with change in bsp format\n"); +#else +#ifdef ZHLT_XASH + Log(" - special edition for Xash\n"); +#endif +#endif + + Log("Zoner's Half-Life Compilation Tools -- Custom Build\n" + "Based on code modifications by Sean 'Zoner' Cavanaugh\n" + "Based on Valve's version, modified with permission.\n" + MODIFICATIONS_STRING); + +} + +// ===================================================================================== +// LogStart +// ===================================================================================== +void LogStart(int argc, char** argv) +{ + Banner(); + Log("----- BEGIN %s -----\n", g_Program); + LogArgs(argc, argv); + DisplayDeveloperLevel(); +} + +// ===================================================================================== +// LogEnd +// ===================================================================================== +void LogEnd() +{ + Log("\n----- END %s -----\n\n\n\n", g_Program); +} + +// ===================================================================================== +// hlassume +// my assume +// ===================================================================================== +void hlassume(bool exp, assume_msgs msgid) +{ + if (!exp) + { + char message[MAX_MESSAGE]; + const MessageTable_t* msg = GetAssume(msgid); + +#ifdef ZHLT_LANGFILE + safe_snprintf(message, MAX_MESSAGE, "%s\n%s%s\n%s%s\n", Localize (msg->title), Localize ("Description: "), Localize (msg->text), Localize ("Howto Fix: "), Localize (msg->howto)); +#else + safe_snprintf(message, MAX_MESSAGE, "%s\nDescription: %s\nHowto Fix: %s\n", msg->title, msg->text, msg->howto); +#endif + Error(message); + } +} + +// ===================================================================================== +// seconds_to_hhmm +// ===================================================================================== +static void seconds_to_hhmm(unsigned int elapsed_time, unsigned& days, unsigned& hours, unsigned& minutes, unsigned& seconds) +{ + seconds = elapsed_time % 60; + elapsed_time /= 60; + + minutes = elapsed_time % 60; + elapsed_time /= 60; + + hours = elapsed_time % 24; + elapsed_time /= 24; + + days = elapsed_time; +} + +// ===================================================================================== +// LogTimeElapsed +// ===================================================================================== +void LogTimeElapsed(float elapsed_time) +{ + unsigned days = 0; + unsigned hours = 0; + unsigned minutes = 0; + unsigned seconds = 0; + + seconds_to_hhmm(elapsed_time, days, hours, minutes, seconds); + + if (days) + { + Log("%.2f seconds elapsed [%ud %uh %um %us]\n", elapsed_time, days, hours, minutes, seconds); + } + else if (hours) + { + Log("%.2f seconds elapsed [%uh %um %us]\n", elapsed_time, hours, minutes, seconds); + } + else if (minutes) + { + Log("%.2f seconds elapsed [%um %us]\n", elapsed_time, minutes, seconds); + } + else + { + Log("%.2f seconds elapsed\n", elapsed_time); + } +} + +#ifdef ZHLT_CONSOLE +#ifdef SYSTEM_WIN32 +void wait () +{ + Sleep (1000); +} +int InitConsole (int argc, char **argv) +{ + int i; + bool wrong = false; + twice = false; + useconsole = true; + for (i = 1; i < argc; ++i) + { + if (!strcasecmp (argv[i], "-console")) + { + if (i + 1 < argc) + { + if (!strcasecmp (argv[i+1], "0")) + useconsole = false; + else if (!strcasecmp (argv[i+1], "1")) + useconsole = true; + else + wrong = true; + } + else + wrong = true; + } + } + if (useconsole) + twice = AllocConsole (); + if (useconsole) + { + conout = fopen ("CONOUT$", "w"); + if (!conout) + { + useconsole = false; + twice = false; + Warning ("Can not open 'CONOUT$'"); + if (twice) + FreeConsole (); + } + } + if (twice) + atexit (&wait); + if (wrong) + return -1; + return 0; +} +#else +int InitConsole (int argc, char **argv) +{ + twice = false; + useconsole = false; + return 0; +} +#endif +void CDECL FORMAT_PRINTF(1,2) PrintConsole(const char* const warning, ...) +{ + char message[MAX_MESSAGE]; + + va_list argptr; + + va_start(argptr, warning); +//ZHLT_LANGFILE: don't call function Localize here because of performance issue + vsnprintf(message, MAX_MESSAGE, warning, argptr); + va_end(argptr); + + if (useconsole) + { + fprintf (conout, "%s", message); + fflush (conout); + } + else + { + fprintf (stdout, "%s", message); + } +} +#endif + +#ifdef ZHLT_LANGFILE +int loadlangfileline (char *line, int n, FILE *f) +{ + int i = 0, c = 0; + bool special = false; + while (1) + { + c = fgetc (f); + if (c == '\r') + continue; + if (c == '\n' || c == EOF) + break; + if (c == '\\' && !special) + { + special = true; + } + else + { + if (special) + { + switch (c) + { + case 'n': c = '\n'; break; + case 't': c = '\t'; break; + case 'v': c = '\v'; break; + case 'b': c = '\b'; break; + case 'r': c = '\r'; break; + case 'f': c = '\f'; break; + case 'a': c = '\a'; break; + case '\\': c = '\\'; break; + case '?': c = '\?'; break; + case '\'': c = '\''; break; + case '"': c = '\"'; break; + default: break; + } + } + if (i < n - 1) + line[i++] = c; + else + { + Warning ("line too long in localization file"); + break; + } + special = false; + } + } + line[i] = '\0'; + if (c == EOF) + return 1; + return 0; +} +const char * Localize (const char *s) +{ + int i; + for (i=0; i= 1000 +#pragma once +#endif + +#include "mathtypes.h" +#include "messages.h" + +typedef enum +{ + DEVELOPER_LEVEL_ALWAYS, + DEVELOPER_LEVEL_ERROR, + DEVELOPER_LEVEL_WARNING, + DEVELOPER_LEVEL_MESSAGE, + DEVELOPER_LEVEL_FLUFF, + DEVELOPER_LEVEL_SPAM, + DEVELOPER_LEVEL_MEGASPAM +} +developer_level_t; + +// +// log.c globals +// + +extern char* g_Program; +extern char g_Mapname[_MAX_PATH]; + +#define DEFAULT_DEVELOPER DEVELOPER_LEVEL_ALWAYS +#define DEFAULT_VERBOSE false +#define DEFAULT_LOG true + +extern developer_level_t g_developer; +extern bool g_verbose; +extern bool g_log; +extern unsigned long g_clientid; // Client id of this program +extern unsigned long g_nextclientid; // Client id of next client to spawn from this server + +// +// log.c Functions +// + +extern void ResetTmpFiles(); +extern void ResetLog(); +extern void ResetErrorLog(); +extern void CheckForErrorLog(); + +extern void CDECL OpenLog(int clientid); +extern void CDECL CloseLog(); +extern void WriteLog(const char* const message); + +extern void CheckFatal(); + +extern void CDECL FORMAT_PRINTF(2,3) Developer(developer_level_t level, const char* const message, ...); + +#ifdef _DEBUG +#define IfDebug(x) (x) +#else +#define IfDebug(x) +#endif + +#ifdef ZHLT_LANGFILE +extern const char * Localize (const char *s); +extern void LoadLangFile (const char *name, const char *programpath); +#endif +#ifdef ZHLT_CONSOLE +extern int InitConsole(int argc, char **argv); +extern void CDECL FORMAT_PRINTF(1,2) PrintConsole(const char* const message, ...); +#endif +extern void CDECL FORMAT_PRINTF(1,2) Verbose(const char* const message, ...); +extern void CDECL FORMAT_PRINTF(1,2) Log(const char* const message, ...); +extern void CDECL FORMAT_PRINTF(1,2) Error(const char* const error, ...); +extern void CDECL FORMAT_PRINTF(2,3) Fatal(assume_msgs msgid, const char* const error, ...); +extern void CDECL FORMAT_PRINTF(1,2) Warning(const char* const warning, ...); + +extern void CDECL FORMAT_PRINTF(1,2) PrintOnce(const char* const message, ...); + +extern void LogStart(const int argc, char** argv); +extern void LogEnd(); +extern void Banner(); + +extern void LogTimeElapsed(float elapsed_time); + +// Should be in hlassert.h, but well so what +extern void hlassume(bool exp, assume_msgs msgid); + +#endif // Should be in hlassert.h, but well so what LOG_H__ diff --git a/src/zhlt-vluzacn/common/mathlib.cpp b/src/zhlt-vluzacn/common/mathlib.cpp new file mode 100644 index 0000000..4629ec8 --- /dev/null +++ b/src/zhlt-vluzacn/common/mathlib.cpp @@ -0,0 +1,9 @@ +#include "cmdlib.h" +#include "messages.h" +#include "log.h" +#include "hlassert.h" +#include "mathtypes.h" +#include "mathlib.h" +#include "win32fix.h" + +const vec3_t vec3_origin = { 0, 0, 0 }; diff --git a/src/zhlt-vluzacn/common/mathlib.h b/src/zhlt-vluzacn/common/mathlib.h new file mode 100644 index 0000000..2634e74 --- /dev/null +++ b/src/zhlt-vluzacn/common/mathlib.h @@ -0,0 +1,293 @@ +#ifndef MATHLIB_H__ +#define MATHLIB_H__ +#include "cmdlib.h" //--vluzacn + +#if _MSC_VER >= 1000 +#pragma once +#endif + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef STDC_HEADERS +#include +#include +#endif + +#if !defined(qmax) +#define qmax(a,b) (((a) > (b)) ? (a) : (b)) // changed 'max' to 'qmax'. --vluzacn +#endif + +#if !defined(qmin) +#define qmin(a,b) (((a) < (b)) ? (a) : (b)) // changed 'min' to 'qmin'. --vluzacn +#endif + +#define Q_PI 3.14159265358979323846 + +extern const vec3_t vec3_origin; + +// HLCSG_HLBSP_DOUBLEPLANE: We could use smaller epsilon for hlcsg and hlbsp (hlcsg and hlbsp use double as vec_t), which will totally eliminate all epsilon errors. But we choose this big epsilon to tolerate the imprecision caused by Hammer. Basically, this is a balance between precision and flexibility. +#ifdef ZHLT_LARGERANGE +#define NORMAL_EPSILON 0.00001 +#define ON_EPSILON 0.04 // we should ensure that (float)BOGUS_RANGE < (float)(BOGUA_RANGE + 0.2 * ON_EPSILON) +#define EQUAL_EPSILON 0.004 +#else +#define NORMAL_EPSILON 0.00001 +#define ON_EPSILON 0.01 +#define EQUAL_EPSILON 0.001 +#endif + + +// +// Vector Math +// + + +#define DotProduct(x,y) ( (x)[0] * (y)[0] + (x)[1] * (y)[1] + (x)[2] * (y)[2]) +#define CrossProduct(a, b, dest) \ +{ \ + (dest)[0] = (a)[1] * (b)[2] - (a)[2] * (b)[1]; \ + (dest)[1] = (a)[2] * (b)[0] - (a)[0] * (b)[2]; \ + (dest)[2] = (a)[0] * (b)[1] - (a)[1] * (b)[0]; \ +} + +#define VectorMidpoint(a,b,c) { (c)[0]=((a)[0]+(b)[0])/2; (c)[1]=((a)[1]+(b)[1])/2; (c)[2]=((a)[2]+(b)[2])/2; } + +#define VectorFill(a,b) { (a)[0]=(b); (a)[1]=(b); (a)[2]=(b);} +#define VectorAvg(a) ( ( (a)[0] + (a)[1] + (a)[2] ) / 3 ) + +#define VectorSubtract(a,b,c) { (c)[0]=(a)[0]-(b)[0]; (c)[1]=(a)[1]-(b)[1]; (c)[2]=(a)[2]-(b)[2]; } +#define VectorAdd(a,b,c) { (c)[0]=(a)[0]+(b)[0]; (c)[1]=(a)[1]+(b)[1]; (c)[2]=(a)[2]+(b)[2]; } +#define VectorMultiply(a,b,c) { (c)[0]=(a)[0]*(b)[0]; (c)[1]=(a)[1]*(b)[1]; (c)[2]=(a)[2]*(b)[2]; } +#define VectorDivide(a,b,c) { (c)[0]=(a)[0]/(b)[0]; (c)[1]=(a)[1]/(b)[1]; (c)[2]=(a)[2]/(b)[2]; } + +#define VectorSubtractVec(a,b,c) { (c)[0]=(a)[0]-(b); (c)[1]=(a)[1]-(b); (c)[2]=(a)[2]-(b); } +#define VectorAddVec(a,b,c) { (c)[0]=(a)[0]+(b); (c)[1]=(a)[1]+(b); (c)[2]=(a)[2]+(b); } +#define VecSubtractVector(a,b,c) { (c)[0]=(a)-(b)[0]; (c)[1]=(a)-(b)[1]; (c)[2]=(a)-(b)[2]; } +#define VecAddVector(a,b,c) { (c)[0]=(a)+(b)[0]; (c)[1]=(a)[(b)[1]; (c)[2]=(a)+(b)[2]; } + +#define VectorMultiplyVec(a,b,c) { (c)[0]=(a)[0]*(b);(c)[1]=(a)[1]*(b);(c)[2]=(a)[2]*(b); } +#define VectorDivideVec(a,b,c) { (c)[0]=(a)[0]/(b);(c)[1]=(a)[1]/(b);(c)[2]=(a)[2]/(b); } + +#define VectorScale(a,b,c) { (c)[0]=(a)[0]*(b);(c)[1]=(a)[1]*(b);(c)[2]=(a)[2]*(b); } + +#define VectorCopy(a,b) { (b)[0]=(a)[0]; (b)[1]=(a)[1]; (b)[2]=(a)[2]; } +#define VectorClear(a) { (a)[0] = (a)[1] = (a)[2] = 0.0; } + +#define VectorMaximum(a) ( qmax( (a)[0], qmax( (a)[1], (a)[2] ) ) ) +#define VectorMinimum(a) ( qmin( (a)[0], qmin( (a)[1], (a)[2] ) ) ) + +#define VectorInverse(a) \ +{ \ + (a)[0] = -((a)[0]); \ + (a)[1] = -((a)[1]); \ + (a)[2] = -((a)[2]); \ +} +#define VectorRound(a) floor((a) + 0.5) +#ifdef ZHLT_VectorMA_FIX +#define VectorMA(a, scale, b, dest) \ +{ \ + (dest)[0] = (a)[0] + (scale) * (b)[0]; \ + (dest)[1] = (a)[1] + (scale) * (b)[1]; \ + (dest)[2] = (a)[2] + (scale) * (b)[2]; \ +} +#else +#define VectorMA(a, scale, b, dest) \ +{ \ + (dest)[0] = (a)[0] + scale * (b)[0]; \ + (dest)[1] = (a)[1] + scale * (b)[1]; \ + (dest)[2] = (a)[2] + scale * (b)[2]; \ +} +#endif +#define VectorLength(a) sqrt((double) ((double)((a)[0] * (a)[0]) + (double)( (a)[1] * (a)[1]) + (double)( (a)[2] * (a)[2])) ) +#define VectorCompareMinimum(a,b,c) { (c)[0] = qmin((a)[0], (b)[0]); (c)[1] = qmin((a)[1], (b)[1]); (c)[2] = qmin((a)[2], (b)[2]); } +#define VectorCompareMaximum(a,b,c) { (c)[0] = qmax((a)[0], (b)[0]); (c)[1] = qmax((a)[1], (b)[1]); (c)[2] = qmax((a)[2], (b)[2]); } + +inline vec_t VectorNormalize(vec3_t v) +{ + double length; + + length = DotProduct(v, v); + length = sqrt(length); + if (length < NORMAL_EPSILON) + { + VectorClear(v); + return 0.0; + } + + v[0] /= length; + v[1] /= length; + v[2] /= length; + + return length; +} + +inline bool VectorCompare(const vec3_t v1, const vec3_t v2) +{ + int i; + + for (i = 0; i < 3; i++) + { + if (fabs(v1[i] - v2[i]) > EQUAL_EPSILON) + { + return false; + } + } + return true; +} + + +// +// Portable bit rotation +// + + +#ifdef SYSTEM_POSIX +#undef rotl +#undef rotr + +inline unsigned int rotl(unsigned value, unsigned int amt) +{ + unsigned t1, t2; + + t1 = value >> ((sizeof(unsigned) * CHAR_BIT) - amt); + + t2 = value << amt; + return (t1 | t2); +} + +inline unsigned int rotr(unsigned value, unsigned int amt) +{ + unsigned t1, t2; + + t1 = value << ((sizeof(unsigned) * CHAR_BIT) - amt); + + t2 = value >> amt; + return (t1 | t2); +} +#endif + + +// +// Misc +// + + +inline bool isPointFinite(const vec_t* p) +{ + if (finite(p[0]) && finite(p[1]) && finite(p[2])) + { + return true; + } + return false; +} + + +// +// Planetype Math +// + + +typedef enum +{ + plane_x = 0, + plane_y, + plane_z, + plane_anyx, + plane_anyy, + plane_anyz +} +planetypes; + +#define last_axial plane_z +#ifdef HLCSG_FACENORMALEPSILON +#define DIR_EPSILON 0.0001 +#endif + +#ifdef ZHLT_PLANETYPE_FIX +inline planetypes PlaneTypeForNormal(vec3_t normal) +{ + vec_t ax, ay, az; + + ax = fabs(normal[0]); + ay = fabs(normal[1]); + az = fabs(normal[2]); +#ifdef HLCSG_FACENORMALEPSILON + if (ax > 1.0 - DIR_EPSILON && ay < DIR_EPSILON && az < DIR_EPSILON) + { + return plane_x; + } + + if (ay > 1.0 - DIR_EPSILON && az < DIR_EPSILON && ax < DIR_EPSILON) + { + return plane_y; + } + + if (az > 1.0 - DIR_EPSILON && ax < DIR_EPSILON && ay < DIR_EPSILON) + { + return plane_z; + } +#else + if (ax > 1.0 - NORMAL_EPSILON && ay < NORMAL_EPSILON && az < NORMAL_EPSILON) + { + return plane_x; + } + + if (ay > 1.0 - NORMAL_EPSILON && az < NORMAL_EPSILON && ax < NORMAL_EPSILON) + { + return plane_y; + } + + if (az > 1.0 - NORMAL_EPSILON && ax < NORMAL_EPSILON && ay < NORMAL_EPSILON) + { + return plane_z; + } +#endif + + if ((ax >= ay) && (ax >= az)) + { + return plane_anyx; + } + if ((ay >= ax) && (ay >= az)) + { + return plane_anyy; + } + return plane_anyz; +} +#else +inline planetypes PlaneTypeForNormal(vec3_t normal) +{ + vec_t ax, ay, az; + + ax = fabs(normal[0]); + if (ax == 1.0) + { + return plane_x; + } + + ay = fabs(normal[1]); + if (ay == 1.0) + { + return plane_y; + } + + az = fabs(normal[2]); + if (az == 1.0) + { + return plane_z; + } + + if ((ax > ay) && (ax > az)) + { + return plane_anyx; + } + if ((ay > ax) && (ay > az)) + { + return plane_anyy; + } + return plane_anyz; +} +#endif + +#endif //MATHLIB_H__ diff --git a/src/zhlt-vluzacn/common/mathtypes.h b/src/zhlt-vluzacn/common/mathtypes.h new file mode 100644 index 0000000..f86f653 --- /dev/null +++ b/src/zhlt-vluzacn/common/mathtypes.h @@ -0,0 +1,18 @@ +#ifndef MATHTYPES_H__ +#define MATHTYPES_H__ +#include "cmdlib.h" //--vluzacn + +#if _MSC_VER >= 1000 +#pragma once +#endif + +typedef unsigned char byte; + +#ifdef DOUBLEVEC_T +typedef double vec_t; +#else +typedef float vec_t; +#endif +typedef vec_t vec3_t[3]; // x,y,z + +#endif //MATHTYPES_H__ diff --git a/src/zhlt-vluzacn/common/messages.cpp b/src/zhlt-vluzacn/common/messages.cpp new file mode 100644 index 0000000..2192cab --- /dev/null +++ b/src/zhlt-vluzacn/common/messages.cpp @@ -0,0 +1,135 @@ +#include "messages.h" + +// AJM: because these are repeated, they use up redundant memory. +// consequently ive made them into const strings which each occurance can point to. + +// Common descriptions +const char* const internallimit = "The compiler tool hit an internal limit"; +const char* const internalerror = "The compiler tool had an internal error"; +const char* const maperror = "The map has a problem which must be fixed"; + +// Common explanations +#define contact contactvluzacn //"contact zoner@gearboxsoftware.com immidiately" +const char* const selfexplanitory = "self explanitory"; +const char* const reference = "Check the file http://www.zhlt.info/common-mapping-problems.html for a detailed explanation of this problem"; +const char* const simplify = "The map is too complex for the game engine/compile tools to handle. Simplify"; +const char* const contactmerl = "contact amckern@yahoo.com concerning this issue."; +const char* const contactvluzacn = "contact vluzacn@163.com concerning this issue."; + +static const MessageTable_t assumes[assume_last] = { + {"invalid assume message", "This is a message should never be printed.", contact}, + + // generic + {"Memory allocation failure", "The program failled to allocate a block of memory.", +#ifdef ZHLT_64BIT_FIX + #ifdef HLRAD + sizeof (intptr_t) <= 4? "The map is too complex for the compile tools to handle. Switch to the 64-bit version of hlrad if possible." : + "Likely causes are (in order of likeliness) : the partition holding the swapfile is full; swapfile size is smaller than required; memory fragmentation; heap corruption" + #else + contact + #endif +#else + "Likely causes are (in order of likeliness) : the partition holding the swapfile is full; swapfile size is smaller than required; memory fragmentation; heap corruption" +#endif + }, + {"NULL Pointer", internalerror, contact}, + {"Bad Thread Workcount", internalerror, contact}, + + // qcsg + {"Missing '[' in texturedef (U)", maperror, reference}, + {"plane with no normal", maperror, reference}, + {"brush with coplanar faces", maperror, reference}, + {"brush outside world", maperror, reference}, + {"mixed face contents", maperror, reference}, + {"Brush type not allowed in world", maperror, reference}, + {"Brush type not allowed in entity", maperror, reference}, + {"No visibile brushes", "All brushes are CLIP or ORIGIN (at least one must be normal/visible)", selfexplanitory}, + {"Entity with ONLY an ORIGIN brush", "All entities need at least one visible brush to function properly. CLIP, HINT, ORIGIN, do not count as visible brushes.", selfexplanitory}, + {"Could not find WAD file", "The compile tools could not locate a wad file that the map was referencing.", "Make sure the wad's listed in the level editor actually all exist"}, + {"Exceeded MAX_TRIANGLES", internallimit, contact}, + {"Exceeded MAX_SWITCHED_LIGHTS", "The maximum number of switchable light entities has been reached", selfexplanitory}, + {"Exceeded MAX_TEXFILES", internallimit, contact}, + + // qbsp + {"LEAK in the map", maperror, reference}, + {"Exceeded MAX_LEAF_FACES", "This error is almost always caused by an invalid brush, by having huge rooms, or scaling a texture down to extremely small values (between -1 and 1)", + "Find the invalid brush. Any imported prefabs, carved brushes, or vertex manipulated brushes should be suspect"}, + {"Exceeded MAX_WEDGES", internallimit, contact}, + {"Exceeded MAX_WVERTS", internallimit, contact}, + {"Exceeded MAX_SUPERFACEEDGES", internallimit, contact}, + {"Empty Solid Entity", "A solid entity in the map (func_wall for example) has no brushes.", "If using Worldcraft, do a check for problems and fix any occurences of 'Empty solid'"}, + + // vis + {"Leaf portal saw into leaf", maperror, reference}, + {"Exceeded MAX_PORTALS_ON_LEAF", maperror, reference}, + {"Invalid client/server state", internalerror, contact}, + + // qrad + {"Exceeded MAX_TEXLIGHTS", "The maximum number of texture lights in use by a single map has been reached", + "Use fewer texture lights."}, + {"Exceeded MAX_PATCHES", maperror, reference}, + {"Transfer < 0", internalerror, contact}, + {"Bad Surface Extents", maperror, reference}, + {"Malformed face normal", "The texture alignment of a visible face is unusable", "If using Worldcraft, do a check for problems and fix any occurences of 'Texture axis perpindicular to face'"}, + {"No Lights!", "lighting of map halted (I assume you do not want a pitch black map!)", "Put some lights in the map."}, + {"Bad Light Type", internalerror, contact}, + {"Exceeded MAX_SINGLEMAP", internallimit, contact}, + + // common + {"Unable to create thread", internalerror, contact}, + {"Exceeded MAX_MAP_PLANES", "The maximum number of plane definitions has been reached", + "The map has grown too complex"}, + {"Exceeded MAX_MAP_TEXTURES", "The maximum number of textures for a map has been reached", selfexplanitory}, + + {"Exceeded MAX_MAP_MIPTEX", "Texture memory usage on the map has exceeded the limit", + "Merge similar textures, remove unused textures from the map"}, + {"Exceeded MAX_MAP_TEXINFO", internallimit, contact}, + {"Exceeded MAX_MAP_SIDES", internallimit, contact}, + {"Exceeded MAX_MAP_BRUSHES", "The maximum number of brushes for a map has been reached", selfexplanitory}, + {"Exceeded MAX_MAP_ENTITIES", "The maximum number of entities for the compile tools has been reached", selfexplanitory}, + {"Exceeded MAX_ENGINE_ENTITIES", "The maximum number of entities for the half-life engine has been reached", selfexplanitory}, + + {"Exceeded MAX_MAP_MODELS", "The maximum number of brush based entities has been reached", + "Remove unnecessary brush entities, consolidate similar entities into a single entity"}, + {"Exceeded MAX_MAP_VERTS", "The maximum number of vertices for a map has been reached", simplify}, // internallimit, contact //--vluzacn + {"Exceeded MAX_MAP_EDGES", internallimit, contact}, + + {"Exceeded MAX_MAP_CLIPNODES", maperror, reference}, + {"Exceeded MAX_MAP_MARKSURFACES", internallimit, contact}, + {"Exceeded MAX_MAP_FACES", "The maximum number of faces for a map has been reached", "This error is typically caused by having a large face with a small texture scale on it, or overly complex maps."}, + {"Exceeded MAX_MAP_SURFEDGES", internallimit, contact}, + {"Exceeded MAX_MAP_NODES", "The maximum number of nodes for a map has been reached", simplify}, + {"CompressVis Overflow", internalerror, contact}, + {"DecompressVis Overflow", internalerror, contact}, +#ifdef ZHLT_MAX_MAP_LEAFS + {"Exceeded MAX_MAP_LEAFS", "The maximum number of leaves for a map has been reached", simplify}, +#endif + {"Execution Cancelled", "Tool execution was cancelled either by the user or due to a fatal compile setting", selfexplanitory}, + {"Internal Error", internalerror, contact}, + //KGP added + {"Exceeded MAX_MAP_LIGHTING","You have run out of light data memory" ,"Use the -lightdata <#> command line option to increase your maximum light memory. The default is 32768 (KB)."}, // 6144 (KB) //--vluzacn + {"Exceeded MAX_INTERNAL_MAP_PLANES", "The maximum number of plane definitions has been reached", "The map has grown too complex"}, +#ifdef HLRAD_TEXTURE + {"Could not locate WAD file", "The compile tools could not locate a wad file that the map was referencing.", +#ifdef ZHLT_NOWADDIR + "Make sure the file '.wa_' exists. This is a file generated by hlcsg and you should not delete it. If you have to run hlrad without this file, use '-waddir' to specify folders where hlrad can find all the wad files." +#else + "Configure game directory pathes for hlrad in file '\\settings.txt', and make sure this wad file either has been included into bsp by hlcsg or exists in one of the game directories." +#endif + }, +#endif +#ifdef ZHLT_64BIT_FIX + {"Couldn't open extent file", ".ext doesn't exist. This file is required by the " PLATFORM_VERSIONSTRING " version of hlrad.", "Make sure hlbsp has run correctly. Alternatively, run 'ripent.exe -writeextentfile ' to create the extent file."}, +#endif +}; + +const MessageTable_t* GetAssume(assume_msgs id) +{ + if (!(id > assume_first && id < assume_last))//(!(id > assume_first) && (id < assume_last)) --vluzacn + { + id = assume_first; + } + return &assumes[id]; +} + + diff --git a/src/zhlt-vluzacn/common/messages.h b/src/zhlt-vluzacn/common/messages.h new file mode 100644 index 0000000..40c6248 --- /dev/null +++ b/src/zhlt-vluzacn/common/messages.h @@ -0,0 +1,106 @@ +#ifndef MESSAGES_H__ +#define MESSAGES_H__ +#include "cmdlib.h" //--vluzacn + +#if _MSC_VER >= 1000 +#pragma once +#endif + +typedef struct +{ + const char* title; + const char* text; + const char* howto; +} +MessageTable_t; + +typedef enum +{ + assume_first = 0, + + // generic + assume_NoMemory, + assume_ValidPointer, + assume_BadWorkcount, + + // qcsg + assume_MISSING_BRACKET_IN_TEXTUREDEF, + assume_PLANE_WITH_NO_NORMAL, + assume_BRUSH_WITH_COPLANAR_FACES, + assume_BRUSH_OUTSIDE_WORLD, + assume_MIXED_FACE_CONTENTS, + assume_BRUSH_NOT_ALLOWED_IN_WORLD, + assume_BRUSH_NOT_ALLOWED_IN_ENTITY, + assume_NO_VISIBILE_BRUSHES, + assume_ONLY_ORIGIN, + assume_COULD_NOT_FIND_WAD, + assume_MAX_TRIANGLES, + assume_MAX_SWITCHED_LIGHTS, + assume_MAX_TEXFILES, + + // qbsp + assume_LEAK, + assume_MAX_LEAF_FACES, + assume_MAX_WEDGES, + assume_MAX_WVERTS, + assume_MAX_SUPERFACEEDGES, + assume_EmptySolid, + + // vis + assume_LEAF_PORTAL_SAW_INTO_LEAF, + assume_MAX_PORTALS_ON_LEAF, + assume_VALID_NETVIS_STATE, + + // qrad + assume_MAX_TEXLIGHTS, + assume_MAX_PATCHES, + assume_TransferError, + assume_BadSurfaceExtents, + assume_MalformedTextureFace, + assume_NoLights, + assume_BadLightType, + assume_MAX_SINGLEMAP, + + // common + assume_THREAD_ERROR, + assume_MAX_MAP_PLANES, + assume_MAX_MAP_TEXTURES, + assume_MAX_MAP_MIPTEX, + assume_MAX_MAP_TEXINFO, + assume_MAX_MAP_SIDES, + assume_MAX_MAP_BRUSHES, + assume_MAX_MAP_ENTITIES, + assume_MAX_ENGINE_ENTITIES, + assume_MAX_MAP_MODELS, + assume_MAX_MAP_VERTS, + assume_MAX_MAP_EDGES, + assume_MAX_MAP_CLIPNODES, + assume_MAX_MAP_MARKSURFACES, + assume_MAX_MAP_FACES, + assume_MAX_MAP_SURFEDGES, + assume_MAX_MAP_NODES, + assume_COMPRESSVIS_OVERFLOW, + assume_DECOMPRESSVIS_OVERFLOW, +#ifdef ZHLT_MAX_MAP_LEAFS + assume_MAX_MAP_LEAFS, +#endif + // AJM: added in + assume_TOOL_CANCEL, + assume_GENERIC, + // KGP: added + assume_MAX_MAP_LIGHTING, + assume_MAX_INTERNAL_MAP_PLANES, +#ifdef HLRAD_TEXTURE + assume_COULD_NOT_LOCATE_WAD, +#endif +#ifdef ZHLT_64BIT_FIX + assume_NO_EXTENT_FILE, +#endif + + assume_last +} +assume_msgs; + +extern const MessageTable_t* GetAssume(assume_msgs id); + +#endif // commonc MESSAGES_H__ diff --git a/src/zhlt-vluzacn/common/resourcelock.cpp b/src/zhlt-vluzacn/common/resourcelock.cpp new file mode 100644 index 0000000..966c8e5 --- /dev/null +++ b/src/zhlt-vluzacn/common/resourcelock.cpp @@ -0,0 +1,61 @@ +#ifdef SYSTEM_WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#endif + +#include "cmdlib.h" +#include "messages.h" +#include "log.h" +#include "blockmem.h" + +#include "resourcelock.h" + +#ifdef SYSTEM_WIN32 + +typedef struct +{ + HANDLE Mutex; +} +ResourceLock_t; + +void* CreateResourceLock(int LockNumber) +{ + char lockname[_MAX_PATH]; + char mapname[_MAX_PATH]; + ResourceLock_t* lock = (ResourceLock_t*)Alloc(sizeof(ResourceLock_t)); + + ExtractFile(g_Mapname, mapname); + safe_snprintf(lockname, _MAX_PATH, "%d%s", LockNumber, mapname); + + lock->Mutex = CreateMutex(NULL, FALSE, lockname); + + if (lock->Mutex == NULL) + { + LPVOID lpMsgBuf; + + Log("lock->Mutex is NULL! [%s]", lockname); + FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) & lpMsgBuf, 0, NULL); + Error((LPCTSTR)lpMsgBuf); + } + + WaitForSingleObject(lock->Mutex, INFINITE); + + return lock; +} + +void ReleaseResourceLock(void** lock) +{ + ResourceLock_t* lockTmp = (ResourceLock_t*)*lock; + + if (!ReleaseMutex(lockTmp->Mutex)) + { + Error("Failed to release mutex"); + } + Free(lockTmp); + *lock = NULL; +} + +#endif diff --git a/src/zhlt-vluzacn/common/resourcelock.h b/src/zhlt-vluzacn/common/resourcelock.h new file mode 100644 index 0000000..aabd918 --- /dev/null +++ b/src/zhlt-vluzacn/common/resourcelock.h @@ -0,0 +1,12 @@ +#ifndef RESOURCELOCK_H__ +#define RESOURCELOCK_H__ +#include "cmdlib.h" //--vluzacn + +#if _MSC_VER >= 1000 +#pragma once +#endif + +extern void* CreateResourceLock(int LockNumber); +extern void ReleaseResourceLock(void** lock); + +#endif // RESOURCE_LOCK_H__ diff --git a/src/zhlt-vluzacn/common/scriplib.cpp b/src/zhlt-vluzacn/common/scriplib.cpp new file mode 100644 index 0000000..eddd53d --- /dev/null +++ b/src/zhlt-vluzacn/common/scriplib.cpp @@ -0,0 +1,370 @@ +#include "cmdlib.h" +#include "filelib.h" +#include "messages.h" +#include "log.h" +#include "scriplib.h" + +char g_token[MAXTOKEN]; +char g_TXcommand; + +typedef struct +{ + char filename[_MAX_PATH]; + char* buffer; + char* script_p; + char* end_p; + int line; +} +script_t; + + +#define MAX_INCLUDES 8 + + +static script_t s_scriptstack[MAX_INCLUDES]; +script_t* s_script; +int s_scriptline; +bool s_endofscript; +bool s_tokenready; // only true if UnGetToken was just called + + +// AddScriptToStack +// LoadScriptFile +// ParseFromMemory +// UnGetToken +// EndOfScript +// GetToken +// TokenAvailable + +// ===================================================================================== +// AddScriptToStack +// ===================================================================================== +static void AddScriptToStack(const char* const filename) +{ + int size; + + s_script++; + + if (s_script == &s_scriptstack[MAX_INCLUDES]) + Error("s_script file exceeded MAX_INCLUDES"); + + strcpy_s(s_script->filename, filename); + + size = LoadFile(s_script->filename, (char**)&s_script->buffer); + + Log("Entering %s\n", s_script->filename); + + s_script->line = 1; + s_script->script_p = s_script->buffer; + s_script->end_p = s_script->buffer + size; +} + +// ===================================================================================== +// LoadScriptFile +// ===================================================================================== +void LoadScriptFile(const char* const filename) +{ + s_script = s_scriptstack; + AddScriptToStack(filename); + + s_endofscript = false; + s_tokenready = false; +} + +// ===================================================================================== +// ParseFromMemory +// ===================================================================================== +void ParseFromMemory(char* buffer, const int size) +{ + s_script = s_scriptstack; + s_script++; + + if (s_script == &s_scriptstack[MAX_INCLUDES]) + Error("s_script file exceeded MAX_INCLUDES"); + + strcpy_s(s_script->filename, "memory buffer"); + + s_script->buffer = buffer; + s_script->line = 1; + s_script->script_p = s_script->buffer; + s_script->end_p = s_script->buffer + size; + + s_endofscript = false; + s_tokenready = false; +} + +// ===================================================================================== +// UnGetToken +/* + * Signals that the current g_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() +{ + s_tokenready = true; +} + +// ===================================================================================== +// EndOfScript +// ===================================================================================== +bool EndOfScript(const bool crossline) +{ + if (!crossline) + Error("Line %i is incomplete (did you place a \" inside an entity string?) \n", s_scriptline); + + if (!strcmp(s_script->filename, "memory buffer")) + { + s_endofscript = true; + return false; + } + + free(s_script->buffer); + + if (s_script == s_scriptstack + 1) + { + s_endofscript = true; + return false; + } + + s_script--; + s_scriptline = s_script->line; + + Log("returning to %s\n", s_script->filename); + + return GetToken(crossline); +} + +// ===================================================================================== +// GetToken +// ===================================================================================== +bool GetToken(const bool crossline) +{ + char *token_p; + + if (s_tokenready) // is a g_token allready waiting? + { + s_tokenready = false; + return true; + } + + if (s_script->script_p >= s_script->end_p) + return EndOfScript(crossline); + + // skip space +skipspace: +#ifdef ZHLT_TEXNAME_CHARSET + while (*s_script->script_p <= 32 && *s_script->script_p >= 0) +#else + while (*s_script->script_p <= 32) +#endif + { + if (s_script->script_p >= s_script->end_p) + return EndOfScript(crossline); + + if (*s_script->script_p++ == '\n') + { + if (!crossline) + Error("Line %i is incomplete (did you place a \" inside an entity string?) \n", s_scriptline); + s_scriptline = s_script->line++; + } + } + + if (s_script->script_p >= s_script->end_p) + return EndOfScript(crossline); + + // comment fields + if (*s_script->script_p == ';' || *s_script->script_p == '#' || // semicolon and # is comment field + (*s_script->script_p == '/' && *((s_script->script_p) + 1) == '/')) // also make // a comment field + { + if (!crossline) + Error("Line %i is incomplete (did you place a \" inside an entity string?) \n", s_scriptline); + + //ets+++ + if (*s_script->script_p == '/') + s_script->script_p++; + if (s_script->script_p[1] == 'T' && s_script->script_p[2] == 'X') + g_TXcommand = s_script->script_p[3]; // AR: "//TX#"-style comment + + //ets--- + while (*s_script->script_p++ != '\n') + { + if (s_script->script_p >= s_script->end_p) + return EndOfScript(crossline); + } + //ets+++ + s_scriptline = s_script->line++; // AR: this line was missing + //ets--- + goto skipspace; + } + + // copy g_token + token_p = g_token; + + if (*s_script->script_p == '"') + { + // quoted token + s_script->script_p++; + while (*s_script->script_p != '"') + { + *token_p++ = *s_script->script_p++; + + if (s_script->script_p == s_script->end_p) + break; + + if (token_p == &g_token[MAXTOKEN]) + Error("Token too large on line %i\n", s_scriptline); + } + s_script->script_p++; + } + else + { + // regular token +#ifdef ZHLT_TEXNAME_CHARSET + while ((*s_script->script_p > 32 || *s_script->script_p < 0) && *s_script->script_p != ';') +#else + while (*s_script->script_p > 32 && *s_script->script_p != ';') +#endif + { + *token_p++ = *s_script->script_p++; + + if (s_script->script_p == s_script->end_p) + break; + + if (token_p == &g_token[MAXTOKEN]) + Error("Token too large on line %i\n", s_scriptline); + } + } + + *token_p = 0; + + if (!strcmp(g_token, "$include")) + { + GetToken(false); + AddScriptToStack(g_token); + return GetToken(crossline); + } + + return true; +} + +#if 0 +// AJM: THIS IS REDUNDANT +// ===================================================================================== +// ParseWadToken +// basically the same as gettoken, except it isnt limited by MAXTOKEN and is +// specificaly designed to parse out the wadpaths from the wad keyvalue and dump +// them into the wadpaths list +// this was implemented as a hack workaround for Token Too Large errors caused by +// having long wadpaths or lots of wads in the map editor. +extern void PushWadPath(const char* const path, bool inuse); +// ===================================================================================== +void ParseWadToken(const bool crossline) +{ + // code somewhat copied from GetToken() + int i, j; + char* token_p; + char temp[_MAX_PATH]; + + if (s_script->script_p >= s_script->end_p) + return; + + // skip space + while (*s_script->script_p <= 32) + { + if (s_script->script_p >= s_script->end_p) + return; + + if (*s_script->script_p++ == '\n') + { + if (!crossline) + Error("Line %i is incomplete (did you place a \" inside an entity string?) \n", s_scriptline); + s_scriptline = s_script->line++; + } + } + + // EXPECT A QUOTE + if (*s_script->script_p++ != '"') + Error("Line %i: Expected a wadpaths definition, got '%s'\n", s_scriptline, *--s_script->script_p); + + // load wadpaths manually + bool endoftoken = false; + for (i = 0; !endoftoken; i++) + { + // get the path + for (j = 0; ; j++) + { + token_p = ++s_script->script_p; + + // assert max path length + if (j > _MAX_PATH) + Error("Line %i: Wadpath definition %i is too long (%s)\n", s_scriptline, temp); + + if (*token_p == '\n') + Error("Line %i: Expected a wadpaths definition, got linebreak\n", s_scriptline); + + if (*token_p == '"') // end of wadpath definition + { + if (i == 0 && j == 0) // no wadpaths! + { + Warning("No wadpaths specified.\n"); + return; + } + + endoftoken = true; + break; + } + + if (*token_p == ';') // end of this wadpath + break; + + temp[j] = *token_p; + temp[j + 1] = 0; + } + + // push it into the list + PushWadPath(temp, true); + temp[0] = 0; + } + + for (; *s_script->script_p != '\n'; s_script->script_p++) + { + } +} +#endif + +// ===================================================================================== +// TokenAvailable +// returns true if there is another token on the line +// ===================================================================================== +bool TokenAvailable() +{ + char *search_p; + + search_p = s_script->script_p; + + if (search_p >= s_script->end_p) + return false; + + while (*search_p <= 32) + { + if (*search_p == '\n') + return false; + + search_p++; + + if (search_p == s_script->end_p) + return false; + } + + if (*search_p == ';') + return false; + + return true; +} diff --git a/src/zhlt-vluzacn/common/scriplib.h b/src/zhlt-vluzacn/common/scriplib.h new file mode 100644 index 0000000..8457dc4 --- /dev/null +++ b/src/zhlt-vluzacn/common/scriplib.h @@ -0,0 +1,26 @@ +#ifndef SCRIPLIB_H__ +#define SCRIPLIB_H__ + +#if _MSC_VER >= 1000 +#pragma once +#endif + +#include "cmdlib.h" + +#define MAXTOKEN 4096 + +extern char g_token[MAXTOKEN]; +extern char g_TXcommand; // global for Quark maps texture alignment hack + +extern void LoadScriptFile(const char* const filename); +extern void ParseFromMemory(char* buffer, int size); + +extern bool GetToken(bool crossline); +extern void UnGetToken(); +extern bool TokenAvailable(); + +#define MAX_WAD_PATHS 42 +extern char g_szWadPaths[MAX_WAD_PATHS][_MAX_PATH]; +extern int g_iNumWadPaths; + +#endif //**/ SCRIPLIB_H__ diff --git a/src/zhlt-vluzacn/common/threads.cpp b/src/zhlt-vluzacn/common/threads.cpp new file mode 100644 index 0000000..32d4c12 --- /dev/null +++ b/src/zhlt-vluzacn/common/threads.cpp @@ -0,0 +1,739 @@ +#ifdef SYSTEM_WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#include +#endif + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "cmdlib.h" +#include "messages.h" +#include "log.h" +#include "threads.h" +#include "blockmem.h" + +#ifdef SYSTEM_POSIX +#ifdef HAVE_SYS_TIME_H +#include +#endif +#ifdef HAVE_SYS_RESOURCE_H +#include +#include +#endif +#ifdef HAVE_PTHREAD_H +#include +#endif +#endif + +#include "hlassert.h" + +q_threadpriority g_threadpriority = DEFAULT_THREAD_PRIORITY; + +#define THREADTIMES_SIZE 100 +#define THREADTIMES_SIZEf (float)(THREADTIMES_SIZE) + +static int dispatch = 0; +static int workcount = 0; +static int oldf = 0; +static bool pacifier = false; +static bool threaded = false; +static double threadstart = 0; +static double threadtimes[THREADTIMES_SIZE]; + +int GetThreadWork() +{ + int r, f, i; + double ct, finish, finish2, finish3; +#ifdef ZHLT_LANGFILE + static const char *s1 = NULL; // avoid frequent call of Localize() in PrintConsole + static const char *s2 = NULL; +#endif + + ThreadLock(); +#ifdef ZHLT_LANGFILE + if (s1 == NULL) + s1 = Localize (" (%d%%: est. time to completion %ld/%ld/%ld secs) "); + if (s2 == NULL) + s2 = Localize (" (%d%%: est. time to completion <1 sec) "); +#endif + + if (dispatch == 0) + { + oldf = 0; + } + + if (dispatch > workcount) + { + Developer(DEVELOPER_LEVEL_ERROR, "dispatch > workcount!!!\n"); + ThreadUnlock(); + return -1; + } + if (dispatch == workcount) + { + Developer(DEVELOPER_LEVEL_MESSAGE, "dispatch == workcount, work is complete\n"); + ThreadUnlock(); + return -1; + } + if (dispatch < 0) + { + Developer(DEVELOPER_LEVEL_ERROR, "negative dispatch!!!\n"); + ThreadUnlock(); + return -1; + } + + f = THREADTIMES_SIZE * dispatch / workcount; + if (pacifier) + { +#ifdef ZHLT_CONSOLE + PrintConsole +#else + printf +#endif + ("\r%6d /%6d", dispatch, workcount); +#ifdef ZHLT_PROGRESSFILE // AJM + if (g_progressfile) + { + + + } +#endif + + if (f != oldf) + { + ct = I_FloatTime(); + /* Fill in current time for threadtimes record */ + for (i = oldf; i <= f; i++) + { + if (threadtimes[i] < 1) + { + threadtimes[i] = ct; + } + } + oldf = f; + + if (f > 10) + { + finish = (ct - threadtimes[0]) * (THREADTIMES_SIZEf - f) / f; + finish2 = 10.0 * (ct - threadtimes[f - 10]) * (THREADTIMES_SIZEf - f) / THREADTIMES_SIZEf; + finish3 = THREADTIMES_SIZEf * (ct - threadtimes[f - 1]) * (THREADTIMES_SIZEf - f) / THREADTIMES_SIZEf; + + if (finish > 1.0) + { +#ifdef ZHLT_CONSOLE + PrintConsole +#else + printf +#endif +#ifdef ZHLT_LANGFILE + (s1, f, (long)(finish), (long)(finish2), + (long)(finish3)); +#else + (" (%d%%: est. time to completion %ld/%ld/%ld secs) ", f, (long)(finish), (long)(finish2), + (long)(finish3)); +#endif +#ifdef ZHLT_PROGRESSFILE // AJM + if (g_progressfile) + { + + + } +#endif + } + else + { +#ifdef ZHLT_CONSOLE + PrintConsole +#else + printf +#endif +#ifdef ZHLT_LANGFILE + (s2, f); +#else + (" (%d%%: est. time to completion <1 sec) ", f); +#endif + +#ifdef ZHLT_PROGRESSFILE // AJM + if (g_progressfile) + { + + + } +#endif + } + } + } + } + else + { + if (f != oldf) + { + oldf = f; + switch (f) + { + case 10: + case 20: + case 30: + case 40: + case 50: + case 60: + case 70: + case 80: + case 90: + case 100: +/* + case 5: + case 15: + case 25: + case 35: + case 45: + case 55: + case 65: + case 75: + case 85: + case 95: +*/ +#ifdef ZHLT_CONSOLE + PrintConsole +#else + printf +#endif + ("%d%%...", f); + default: + break; + } + } + } + + r = dispatch; + dispatch++; + + ThreadUnlock(); + return r; +} + +q_threadfunction workfunction; + +#ifdef SYSTEM_WIN32 +#pragma warning(push) +#pragma warning(disable: 4100) // unreferenced formal parameter +#endif +static void ThreadWorkerFunction(int unused) +{ + int work; + + while ((work = GetThreadWork()) != -1) + { + workfunction(work); + } +} + +#ifdef SYSTEM_WIN32 +#pragma warning(pop) +#endif + +void RunThreadsOnIndividual(int workcnt, bool showpacifier, q_threadfunction func) +{ + workfunction = func; + RunThreadsOn(workcnt, showpacifier, ThreadWorkerFunction); +} + +#ifndef SINGLE_THREADED + +/*==================== +| Begin SYSTEM_WIN32 +=*/ +#ifdef SYSTEM_WIN32 + +#define USED +#include + +int g_numthreads = DEFAULT_NUMTHREADS; +static CRITICAL_SECTION crit; +static int enter; + +void ThreadSetPriority(q_threadpriority type) +{ + int val; + + g_threadpriority = type; + + switch (g_threadpriority) + { + case eThreadPriorityLow: + val = IDLE_PRIORITY_CLASS; + break; + + case eThreadPriorityHigh: + val = HIGH_PRIORITY_CLASS; + break; + + case eThreadPriorityNormal: + default: + val = NORMAL_PRIORITY_CLASS; + break; + } + + SetPriorityClass(GetCurrentProcess(), val); +} + +#if 0 +static void AdjustPriority(HANDLE hThread) +{ + int val; + + switch (g_threadpriority) + { + case eThreadPriorityLow: + val = THREAD_PRIORITY_HIGHEST; + break; + + case eThreadPriorityHigh: + val = THREAD_PRIORITY_LOWEST; + break; + + case eThreadPriorityNormal: + default: + val = THREAD_PRIORITY_NORMAL; + break; + } + SetThreadPriority(hThread, val); +} +#endif + +void ThreadSetDefault() +{ + SYSTEM_INFO info; + + if (g_numthreads == -1) // not set manually + { + GetSystemInfo(&info); + g_numthreads = info.dwNumberOfProcessors; + if (g_numthreads < 1 || g_numthreads > 32) + { + g_numthreads = 1; + } + } +} + +void ThreadLock() +{ + if (!threaded) + { + return; + } + EnterCriticalSection(&crit); + if (enter) + { + Warning("Recursive ThreadLock\n"); + } + enter++; +} + +void ThreadUnlock() +{ + if (!threaded) + { + return; + } + if (!enter) + { + Error("ThreadUnlock without lock\n"); + } + enter--; + LeaveCriticalSection(&crit); +} + +q_threadfunction q_entry; + +static DWORD WINAPI ThreadEntryStub(LPVOID pParam) +{ + q_entry((int)pParam); + return 0; +} + +void threads_InitCrit() +{ + InitializeCriticalSection(&crit); + threaded = true; +} + +void threads_UninitCrit() +{ + DeleteCriticalSection(&crit); +} + +void RunThreadsOn(int workcnt, bool showpacifier, q_threadfunction func) +{ + DWORD threadid[MAX_THREADS]; + HANDLE threadhandle[MAX_THREADS]; + int i; + double start, end; + + threadstart = I_FloatTime(); + start = threadstart; + for (i = 0; i < THREADTIMES_SIZE; i++) + { + threadtimes[i] = 0; + } + dispatch = 0; + workcount = workcnt; + oldf = -1; + pacifier = showpacifier; + threaded = true; + q_entry = func; + + if (workcount < dispatch) + { + Developer(DEVELOPER_LEVEL_ERROR, "RunThreadsOn: Workcount(%i) < dispatch(%i)\n", workcount, dispatch); + } + hlassume(workcount >= dispatch, assume_BadWorkcount); + + // + // Create all the threads (suspended) + // + threads_InitCrit(); + for (i = 0; i < g_numthreads; i++) + { + HANDLE hThread = CreateThread(NULL, + 0, + (LPTHREAD_START_ROUTINE) ThreadEntryStub, + (LPVOID) i, + CREATE_SUSPENDED, + &threadid[i]); + + if (hThread != NULL) + { + threadhandle[i] = hThread; + } + else + { + 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); + // Process any inserts in lpMsgBuf. + // ... + // Display the string. + Developer(DEVELOPER_LEVEL_ERROR, "CreateThread #%d [%08X] failed : %s\n", i, threadhandle[i], lpMsgBuf); + Fatal(assume_THREAD_ERROR, "Unable to create thread #%d", i); + // Free the buffer. + LocalFree(lpMsgBuf); + } + } + CheckFatal(); + + // Start all the threads + for (i = 0; i < g_numthreads; i++) + { + if (ResumeThread(threadhandle[i]) == 0xFFFFFFFF) + { + 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); + // Process any inserts in lpMsgBuf. + // ... + // Display the string. + Developer(DEVELOPER_LEVEL_ERROR, "ResumeThread #%d [%08X] failed : %s\n", i, threadhandle[i], lpMsgBuf); + Fatal(assume_THREAD_ERROR, "Unable to start thread #%d", i); + // Free the buffer. + LocalFree(lpMsgBuf); + } + } + CheckFatal(); + + // Wait for threads to complete + for (i = 0; i < g_numthreads; i++) + { + Developer(DEVELOPER_LEVEL_MESSAGE, "WaitForSingleObject on thread #%d [%08X]\n", i, threadhandle[i]); + WaitForSingleObject(threadhandle[i], INFINITE); + } + threads_UninitCrit(); + + q_entry = NULL; + threaded = false; + end = I_FloatTime(); + if (pacifier) + { +#ifdef ZHLT_CONSOLE + PrintConsole +#else + printf +#endif + ("\r%60s\r", ""); + } + Log(" (%.2f seconds)\n", end - start); +} + +#endif + +/*= +| End SYSTEM_WIN32 +=====================*/ + +/*==================== +| Begin SYSTEM_POSIX +=*/ +#ifdef SYSTEM_POSIX + +#define USED + +int g_numthreads = DEFAULT_NUMTHREADS; + +void ThreadSetPriority(q_threadpriority type) +{ + int val; + + g_threadpriority = type; + + // Currently in Linux land users are incapable of raising the priority level of their processes + // Unless you are root -high is useless . . . + switch (g_threadpriority) + { + case eThreadPriorityLow: + val = PRIO_MAX; + break; + + case eThreadPriorityHigh: + val = PRIO_MIN; + break; + + case eThreadPriorityNormal: + default: + val = 0; + break; + } + setpriority(PRIO_PROCESS, 0, val); +} + +void ThreadSetDefault() +{ + if (g_numthreads == -1) + { + g_numthreads = 1; + } +} + +typedef void* pthread_addr_t; +pthread_mutex_t* my_mutex; + +void ThreadLock() +{ + if (my_mutex) + { + pthread_mutex_lock(my_mutex); + } +} + +void ThreadUnlock() +{ + if (my_mutex) + { + pthread_mutex_unlock(my_mutex); + } +} + +q_threadfunction q_entry; + +static void* CDECL ThreadEntryStub(void* pParam) +{ +#ifdef ZHLT_64BIT_FIX + q_entry((int)(intptr_t)pParam); +#else + q_entry((int)pParam); +#endif + return NULL; +} + +void threads_InitCrit() +{ + pthread_mutexattr_t mattrib; + + if (!my_mutex) + { + my_mutex = (pthread_mutex_t*)Alloc(sizeof(*my_mutex)); + if (pthread_mutexattr_init(&mattrib) == -1) + { + Error("pthread_mutex_attr_init failed"); + } + if (pthread_mutex_init(my_mutex, &mattrib) == -1) + { + Error("pthread_mutex_init failed"); + } + } +} + +void threads_UninitCrit() +{ + Free(my_mutex); + my_mutex = NULL; +} + +/* + * ============= + * RunThreadsOn + * ============= + */ +void RunThreadsOn(int workcnt, bool showpacifier, q_threadfunction func) +{ + int i; + pthread_t work_threads[MAX_THREADS]; + pthread_addr_t status; + pthread_attr_t attrib; + double start, end; + + threadstart = I_FloatTime(); + start = threadstart; + for (i = 0; i < THREADTIMES_SIZE; i++) + { + threadtimes[i] = 0; + } + + dispatch = 0; + workcount = workcnt; + oldf = -1; + pacifier = showpacifier; + threaded = true; + q_entry = func; + + if (pacifier) + { + setbuf(stdout, NULL); + } + + threads_InitCrit(); + + if (pthread_attr_init(&attrib) == -1) + { + Error("pthread_attr_init failed"); + } +#ifdef _POSIX_THREAD_ATTR_STACKSIZE + if (pthread_attr_setstacksize(&attrib, 0x400000) == -1) + { + Error("pthread_attr_setstacksize failed"); + } +#endif + + for (i = 0; i < g_numthreads; i++) + { + if (pthread_create(&work_threads[i], &attrib, ThreadEntryStub, (void*)i) == -1) + { + Error("pthread_create failed"); + } + } + + for (i = 0; i < g_numthreads; i++) + { + if (pthread_join(work_threads[i], &status) == -1) + { + Error("pthread_join failed"); + } + } + + threads_UninitCrit(); + + q_entry = NULL; + threaded = false; + + end = I_FloatTime(); + if (pacifier) + { +#ifdef ZHLT_CONSOLE + PrintConsole +#else + printf +#endif + ("\r%60s\r", ""); + } + + Log(" (%.2f seconds)\n", end - start); +} + +#endif /*SYSTEM_POSIX */ + +/*= +| End SYSTEM_POSIX +=====================*/ + +#endif /*SINGLE_THREADED */ + +/*==================== +| Begin SINGLE_THREADED +=*/ +#ifdef SINGLE_THREADED + +int g_numthreads = 1; + +void ThreadSetPriority(q_threadpriority type) +{ +} + +void threads_InitCrit() +{ +} + +void threads_UninitCrit() +{ +} + +void ThreadSetDefault() +{ + g_numthreads = 1; +} + +void ThreadLock() +{ +} + +void ThreadUnlock() +{ +} + +void RunThreadsOn(int workcnt, bool showpacifier, q_threadfunction func) +{ + int i; + double start, end; + + dispatch = 0; + workcount = workcnt; + oldf = -1; + pacifier = showpacifier; + threadstart = I_FloatTime(); + start = threadstart; + for (i = 0; i < THREADTIMES_SIZE; i++) + { + threadtimes[i] = 0.0; + } + + if (pacifier) + { + setbuf(stdout, NULL); + } + func(0); + + end = I_FloatTime(); + + if (pacifier) + { +#ifdef ZHLT_CONSOLE + PrintConsole +#else + printf +#endif + ("\r%60s\r", ""); + } + + Log(" (%.2f seconds)\n", end - start); +} + +#endif + +/*= +| End SINGLE_THREADED +=====================*/ diff --git a/src/zhlt-vluzacn/common/threads.h b/src/zhlt-vluzacn/common/threads.h new file mode 100644 index 0000000..683ee4a --- /dev/null +++ b/src/zhlt-vluzacn/common/threads.h @@ -0,0 +1,55 @@ +#ifndef THREADS_H__ +#define THREADS_H__ +#include "cmdlib.h" //--vluzacn + +#if _MSC_VER >= 1000 +#pragma once +#endif + +#define MAX_THREADS 64 + +typedef enum +{ + eThreadPriorityLow = -1, + eThreadPriorityNormal, + eThreadPriorityHigh +} +q_threadpriority; + +typedef void (*q_threadfunction) (int); + +#ifdef SYSTEM_WIN32 +#define DEFAULT_NUMTHREADS -1 +#endif +#ifdef SYSTEM_POSIX +#define DEFAULT_NUMTHREADS 1 +#endif + +#define DEFAULT_THREAD_PRIORITY eThreadPriorityNormal + +extern int g_numthreads; +extern q_threadpriority g_threadpriority; + +extern void ThreadSetPriority(q_threadpriority type); +extern void ThreadSetDefault(); +extern int GetThreadWork(); +extern void ThreadLock(); +extern void ThreadUnlock(); + +extern void RunThreadsOnIndividual(int workcnt, bool showpacifier, q_threadfunction); +extern void RunThreadsOn(int workcnt, bool showpacifier, q_threadfunction); + +#ifdef ZHLT_NETVIS +extern void threads_InitCrit(); +extern void threads_UninitCrit(); +#endif + +#ifdef ZHLT_LANGFILE +#define NamedRunThreadsOn(n,p,f) { Log("%s\n", Localize(#f ":")); RunThreadsOn(n,p,f); } +#define NamedRunThreadsOnIndividual(n,p,f) { Log("%s\n", Localize(#f ":")); RunThreadsOnIndividual(n,p,f); } +#else +#define NamedRunThreadsOn(n,p,f) { Log("%s\n", #f ":"); RunThreadsOn(n,p,f); } +#define NamedRunThreadsOnIndividual(n,p,f) { Log("%s\n", #f ":"); RunThreadsOnIndividual(n,p,f); } +#endif + +#endif //**/ THREADS_H__ diff --git a/src/zhlt-vluzacn/common/win32fix.h b/src/zhlt-vluzacn/common/win32fix.h new file mode 100644 index 0000000..e02acfe --- /dev/null +++ b/src/zhlt-vluzacn/common/win32fix.h @@ -0,0 +1,73 @@ +#ifndef WIN32FIX_H__ +#define WIN32FIX_H__ +#include "cmdlib.h" //--vluzacn + +#if _MSC_VER >= 1000 +#pragma once +#endif + +#include + +///////////////////////////// +#ifdef SYSTEM_WIN32 + +#define alloca _alloca + +#define strncasecmp _strnicmp +#define strcasecmp _stricmp + +#if _MSC_VER < 1400 // AdamR: Ignore this definition in Visual Studio 2005 and later +#define snprintf _snprintf +#define vsnprintf _vsnprintf +#endif + +#define finite _finite + +#define rotl _rotl +#define rotr _rotr + +#undef STDCALL +#undef FASTCALL +#undef CDECL + +#define STDCALL __stdcall +#define FASTCALL __fastcall +#define CDECL __cdecl + +#define INLINE __inline + +#define FORCEINLINE __forceinline //--vluzacn +#define FORMAT_PRINTF(STRING_INDEX,FIRST_TO_CHECK) //--vluzacn + +#endif +///////////////////////////// + +///////////////////////////// +#ifdef SYSTEM_POSIX +#define _MAX_PATH 4096 +#define _MAX_DRIVE 4096 +#define _MAX_DIR 4096 +#define _MAX_FNAME 4096 +#define _MAX_EXT 4096 + +#define STDCALL +#define FASTCALL +#define CDECL + +#define INLINE inline + +#define _strdup strdup //--vluzacn +#define _strupr strupr //--vluzacn +#define _strlwr strlwr //--vluzacn +#define _open open //--vluzacn +#define _read read //--vluzacn +#define _close close //--vluzacn +#define _unlink unlink //--vluzacn + +#define FORCEINLINE __inline__ __attribute__((always_inline)) //--vluzacn +#define FORMAT_PRINTF(STRING_INDEX,FIRST_TO_CHECK) __attribute__((format (printf, STRING_INDEX, FIRST_TO_CHECK))) //--vluzacn + +#endif +///////////////////////////// + +#endif ///////////////////////////// WIN32FIX_H__ diff --git a/src/zhlt-vluzacn/common/winding.cpp b/src/zhlt-vluzacn/common/winding.cpp new file mode 100644 index 0000000..68c559f --- /dev/null +++ b/src/zhlt-vluzacn/common/winding.cpp @@ -0,0 +1,1308 @@ +#pragma warning(disable: 4018) //amckern - 64bit - '<' Singed/Unsigned Mismatch + +#include "winding.h" + +#include "cmdlib.h" +#include "log.h" +#include "mathlib.h" +#include "hlassert.h" + +#undef BOGUS_RANGE +#undef ON_EPSILON + +#ifdef ZHLT_LARGERANGE +#define BOGUS_RANGE 80000.0 +#else +#define BOGUS_RANGE 10000.0 +#endif +#ifdef ZHLT_WINDING_EPSILON +#define ON_EPSILON epsilon +#else +#ifdef ZHLT_LARGERANGE +#define ON_EPSILON 0.04 +#else +#define ON_EPSILON 0.01 +#endif +#endif + +// +// Winding Public Methods +// + +void Winding::Print() const +{ + UINT32 x; + + for (x=0; x= 3); + + if (m_NumPoints >= 3) + { + VectorSubtract(m_Points[2], m_Points[1], v1); + VectorSubtract(m_Points[0], m_Points[1], v2); + + CrossProduct(v2, v1, plane_normal); + VectorNormalize(plane_normal); + VectorCopy(plane_normal, plane.normal); // change from vec_t + plane.dist = DotProduct(m_Points[0], plane.normal); + } + else + { + VectorClear(plane.normal); + plane.dist = 0.0; + } +} + +void Winding::getPlane(vec3_t& normal, vec_t& dist) const +{ + vec3_t v1, v2; + + //hlassert(m_NumPoints >= 3); + + if (m_NumPoints >= 3) + { + VectorSubtract(m_Points[1], m_Points[0], v1); + VectorSubtract(m_Points[2], m_Points[0], v2); + CrossProduct(v2, v1, normal); + VectorNormalize(normal); + dist = DotProduct(m_Points[0], normal); + } + else + { + VectorClear(normal); + dist = 0.0; + } +} + +vec_t Winding::getArea() const +{ + unsigned int i; + vec3_t d1, d2, cross; + vec_t total; + + //hlassert(m_NumPoints >= 3); + + total = 0.0; + if (m_NumPoints >= 3) + { + for (i = 2; i < m_NumPoints; i++) + { + VectorSubtract(m_Points[i - 1], m_Points[0], d1); + VectorSubtract(m_Points[i], m_Points[0], d2); + CrossProduct(d1, d2, cross); + total += 0.5 * VectorLength(cross); + } + } + return total; +} + +void Winding::getBounds(vec3_t& mins, vec3_t& maxs) const +{ + BoundingBox bounds; + unsigned x; + + for (x=0; x 0) + { + VectorCopy(vec3_origin, center); + for (i = 0; i < m_NumPoints; i++) + { + VectorAdd(m_Points[i], center, center); + } + + scale = 1.0 / m_NumPoints; + VectorScale(center, scale, center); + } + else + { + VectorClear(center); + } +} + +Winding* Winding::Copy() const +{ + Winding* newWinding = new Winding(*this); + return newWinding; +} + +void Winding::Check( +#ifdef ZHLT_WINDING_EPSILON + vec_t epsilon +#endif + ) const +{ + unsigned int i, j; + vec_t* p1; + vec_t* p2; + vec_t d, edgedist; + vec3_t dir, edgenormal, facenormal; + vec_t area; + vec_t facedist; + + if (m_NumPoints < 3) + { + Error("Winding::Check : %i points", m_NumPoints); + } + + area = getArea(); + if (area < 1) + { + Error("Winding::Check : %f area", area); + } + + getPlane(facenormal, facedist); + + for (i = 0; i < m_NumPoints; i++) + { + p1 = m_Points[i]; + + for (j = 0; j < 3; j++) + { + if (p1[j] > BOGUS_RANGE || p1[j] < -BOGUS_RANGE) + { + Error("Winding::Check : BOGUS_RANGE: %f", p1[j]); + } + } + + j = i + 1 == m_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("Winding::Check : point off plane"); + } + + // check the edge isn't degenerate + p2 = m_Points[j]; + VectorSubtract(p2, p1, dir); + + if (VectorLength(dir) < ON_EPSILON) + { + Error("Winding::Check : 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; j < m_NumPoints; j++) + { + if (j == i) + { + continue; + } + d = DotProduct(m_Points[j], edgenormal); + if (d > edgedist) + { + Error("Winding::Check : non-convex"); + } + } + } +} + +bool Winding::Valid() const +{ + // TODO: Check to ensure there are 3 non-colinear points + if ((m_NumPoints < 3) || (!m_Points)) + { + return false; + } + return true; +} + +// +// Construction +// + +Winding::Winding() +{ + m_Points = NULL; + m_NumPoints = m_MaxPoints = 0; +} + +Winding::Winding(vec3_t *points, UINT32 numpoints) +{ + hlassert(numpoints >= 3); + m_NumPoints = numpoints; + m_MaxPoints = (m_NumPoints + 3) & ~3; // groups of 4 + + m_Points = new vec3_t[m_MaxPoints]; + memcpy(m_Points, points, sizeof(vec3_t) * m_NumPoints); +} + +void Winding::initFromPoints(vec3_t *points, UINT32 numpoints) +{ + hlassert(numpoints >= 3); + + Reset(); + + m_NumPoints = numpoints; + m_MaxPoints = (m_NumPoints + 3) & ~3; // groups of 4 + + m_Points = new vec3_t[m_MaxPoints]; + memcpy(m_Points, points, sizeof(vec3_t) * m_NumPoints); +} + +Winding& Winding::operator=(const Winding& other) +{ + delete[] m_Points; + m_NumPoints = other.m_NumPoints; + m_MaxPoints = (m_NumPoints + 3) & ~3; // groups of 4 + + m_Points = new vec3_t[m_MaxPoints]; + memcpy(m_Points, other.m_Points, sizeof(vec3_t) * m_NumPoints); + return *this; +} + + +Winding::Winding(UINT32 numpoints) +{ + hlassert(numpoints >= 3); + m_NumPoints = numpoints; + m_MaxPoints = (m_NumPoints + 3) & ~3; // groups of 4 + + m_Points = new vec3_t[m_MaxPoints]; + memset(m_Points, 0, sizeof(vec3_t) * m_NumPoints); +} + +Winding::Winding(const Winding& other) +{ + m_NumPoints = other.m_NumPoints; + m_MaxPoints = (m_NumPoints + 3) & ~3; // groups of 4 + + m_Points = new vec3_t[m_MaxPoints]; + memcpy(m_Points, other.m_Points, sizeof(vec3_t) * m_NumPoints); +} + +Winding::~Winding() +{ + delete[] m_Points; +} + + +void Winding::initFromPlane(const vec3_t normal, const vec_t dist) +{ + int i; + vec_t max, v; + vec3_t org, vright, vup; + + // find the major axis + + max = -BOGUS_RANGE; + int x = -1; + for (i = 0; i < 3; i++) + { + v = fabs(normal[i]); + if (v > max) + { + max = v; + x = i; + } + } + if (x == -1) + { + Error("Winding::initFromPlane no major axis found\n"); + } + + 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, BOGUS_RANGE, vup); + VectorScale(vright, BOGUS_RANGE, vright); + + // project a really big axis aligned box onto the plane + m_NumPoints = 4; + m_Points = new vec3_t[m_NumPoints]; + + VectorSubtract(org, vright, m_Points[0]); + VectorAdd(m_Points[0], vup, m_Points[0]); + + VectorAdd(org, vright, m_Points[1]); + VectorAdd(m_Points[1], vup, m_Points[1]); + + VectorAdd(org, vright, m_Points[2]); + VectorSubtract(m_Points[2], vup, m_Points[2]); + + VectorSubtract(org, vright, m_Points[3]); + VectorSubtract(m_Points[3], vup, m_Points[3]); +} + +Winding::Winding(const vec3_t normal, const vec_t dist) +{ + initFromPlane(normal, dist); +} + +Winding::Winding(const dface_t& face +#ifdef ZHLT_WINDING_EPSILON + , vec_t epsilon +#endif + ) +{ + int se; + dvertex_t* dv; + int v; + + m_NumPoints = face.numedges; + m_Points = new vec3_t[m_NumPoints]; + + unsigned i; + for (i = 0; i < face.numedges; i++) + { + se = g_dsurfedges[face.firstedge + i]; + if (se < 0) + { + v = g_dedges[-se].v[1]; + } + else + { + v = g_dedges[se].v[0]; + } + + dv = &g_dvertexes[v]; + VectorCopy(dv->point, m_Points[i]); + } + + RemoveColinearPoints( +#ifdef ZHLT_WINDING_EPSILON + epsilon +#endif + ); +} + +Winding::Winding(const dplane_t& plane) +{ + vec3_t normal; + vec_t dist; + + VectorCopy(plane.normal, normal); + dist = plane.dist; + initFromPlane(normal, dist); +} + +// +// Specialized Functions +// + +#ifdef ZHLT_WINDING_RemoveColinearPoints_VL +// Remove the colinear point of any three points that forms a triangle which is thinner than ON_EPSILON +void Winding::RemoveColinearPoints( +#ifdef ZHLT_WINDING_EPSILON + vec_t epsilon +#endif + ) +{ + unsigned int i; + vec3_t v1, v2; + vec_t *p1, *p2, *p3; + for (i = 0; i < m_NumPoints; i++) + { + p1 = m_Points[(i+m_NumPoints-1)%m_NumPoints]; + p2 = m_Points[i]; + p3 = m_Points[(i+1)%m_NumPoints]; + VectorSubtract (p2, p1, v1); + VectorSubtract (p3, p2, v2); + // v1 or v2 might be close to 0 + if (DotProduct (v1, v2) * DotProduct (v1, v2) >= DotProduct (v1, v1) * DotProduct (v2, v2) + - ON_EPSILON * ON_EPSILON * (DotProduct (v1, v1) + DotProduct (v2, v2) + ON_EPSILON * ON_EPSILON)) + // v2 == k * v1 + v3 && abs (v3) < ON_EPSILON || v1 == k * v2 + v3 && abs (v3) < ON_EPSILON + { + m_NumPoints--; + for (; i < m_NumPoints; i++) + { + VectorCopy (m_Points[i+1], m_Points[i]); + } + i = -1; + continue; + } + } +} +#else /*ZHLT_WINDING_RemoveColinearPoints_VL*/ +#ifdef COMMON_HULLU +void Winding::RemoveColinearPoints( +#ifdef ZHLT_WINDING_EPSILON + , vec_t epsilon +#endif + ) +{ + unsigned int i; + unsigned int nump; + int j; + vec3_t v1, v2; + vec3_t p[128]; + + //JK: Did the optimizations... + + if (m_NumPoints>1) + { + VectorSubtract(m_Points[0], m_Points[m_NumPoints - 1], v2); + VectorNormalize(v2); + } + nump=0; + for (i = 0; i < m_NumPoints; i++) + { + j = (i + 1) % m_NumPoints; // i + 1 + + VectorSubtract(m_Points[i], m_Points[j], v1); + + VectorNormalize(v1); + + VectorAdd(v1, v2, v2); + + if (!VectorCompare(v2, vec3_origin)) + { + VectorCopy(m_Points[i], p[nump]); + nump++; + } +#if 0 + else + { + Log("v3 was (%4.3f %4.3f %4.3f)\n", v2[0], v2[1], v2[2]); + } +#endif + //Set v2 for next round + v2[0]=-v1[0]; + v2[1]=-v1[1]; + v2[2]=-v1[2]; + } + + if (nump == m_NumPoints) + { + return; + } + +#if 0 + Warning("RemoveColinearPoints: Removed %u points, from %u to %u\n", m_NumPoints - nump, m_NumPoints, nump); + Warning("Before :\n"); + Print(); +#endif + m_NumPoints = nump; + memcpy(m_Points, p, nump * sizeof(vec3_t)); + +#if 0 + Warning("After :\n"); + Print(); + + Warning("==========\n"); +#endif +} + + +#else /*COMMON_HULLU*/ + + +void Winding::RemoveColinearPoints( +#ifdef ZHLT_WINDING_EPSILON + , vec_t epsilon +#endif + ) +{ + unsigned int i; + unsigned int nump; + int j, k; + vec3_t v1, v2, v3; + vec3_t p[128]; + + // TODO: OPTIMIZE: this could be 1/2 the number of vectornormalize calls by caching one of the previous values through the loop + // TODO: OPTIMIZE: Remove the modulo operations inside the loop and replace with compares + nump = 0; + for (i = 0; i < m_NumPoints; i++) + { + j = (i + 1) % m_NumPoints; // i + 1 + k = (i + m_NumPoints - 1) % m_NumPoints; // i - 1 + VectorSubtract(m_Points[i], m_Points[j], v1); + VectorSubtract(m_Points[i], m_Points[k], v2); + VectorNormalize(v1); + VectorNormalize(v2); + VectorAdd(v1, v2, v3); + if (!VectorCompare(v3, vec3_origin)) + { + VectorCopy(m_Points[i], p[nump]); + nump++; + } +#if 0 + else + { + Log("v3 was (%4.3f %4.3f %4.3f)\n", v3[0], v3[1], v3[2]); + } +#endif + } + + if (nump == m_NumPoints) + { + return; + } + +#if 0 + Warning("RemoveColinearPoints: Removed %u points, from %u to %u\n", m_NumPoints - nump, m_NumPoints, nump); + Warning("Before :\n"); + Print(); +#endif + m_NumPoints = nump; + memcpy(m_Points, p, nump * sizeof(vec3_t)); + +#if 0 + Warning("After :\n"); + Print(); + + Warning("==========\n"); +#endif +} + +#endif /*!COMMON_HULLU*/ +#endif /*ZHLT_WINDING_RemoveColinearPoints_VL*/ + + +void Winding::Clip(const dplane_t& plane, Winding** front, Winding** back +#ifdef ZHLT_WINDING_EPSILON + , vec_t epsilon +#endif + ) +{ + vec3_t normal; + vec_t dist; + VectorCopy(plane.normal, normal); + dist = plane.dist; + Clip(normal, dist, front, back +#ifdef ZHLT_WINDING_EPSILON + , epsilon +#endif + ); +} + + +void Winding::Clip(const vec3_t normal, const vec_t dist, Winding** front, Winding** back +#ifdef ZHLT_WINDING_EPSILON + , vec_t epsilon +#endif + ) +{ + vec_t dists[MAX_POINTS_ON_WINDING + 4]; + int sides[MAX_POINTS_ON_WINDING + 4]; + int counts[3]; + vec_t dot; + unsigned int i, j; + unsigned int maxpts; + + counts[0] = counts[1] = counts[2] = 0; + + // determine sides for each point + for (i = 0; i < m_NumPoints; i++) + { + dot = DotProduct(m_Points[i], normal); + dot -= dist; + dists[i] = dot; + if (dot > ON_EPSILON) + { + sides[i] = SIDE_FRONT; + } + else if (dot < -ON_EPSILON) + { + sides[i] = SIDE_BACK; + } + else + { + sides[i] = SIDE_ON; + } + counts[sides[i]]++; + } + sides[i] = sides[0]; + dists[i] = dists[0]; + + if (!counts[0]) + { + *front = NULL; + *back = new Winding(*this); + return; + } + if (!counts[1]) + { + *front = new Winding(*this); + *back = NULL; + return; + } + + maxpts = m_NumPoints + 4; // can't use counts[0]+2 because + // of fp grouping errors + + Winding* f = new Winding(maxpts); + Winding* b = new Winding(maxpts); + + *front = f; + *back = b; + + f->m_NumPoints = 0; + b->m_NumPoints = 0; + + for (i = 0; i < m_NumPoints; i++) + { + vec_t* p1 = m_Points[i]; + + if (sides[i] == SIDE_ON) + { + VectorCopy(p1, f->m_Points[f->m_NumPoints]); + VectorCopy(p1, b->m_Points[b->m_NumPoints]); + f->m_NumPoints++; + b->m_NumPoints++; + continue; + } + else if (sides[i] == SIDE_FRONT) + { + VectorCopy(p1, f->m_Points[f->m_NumPoints]); + f->m_NumPoints++; + } + else if (sides[i] == SIDE_BACK) + { + VectorCopy(p1, b->m_Points[b->m_NumPoints]); + b->m_NumPoints++; + } + + if ((sides[i + 1] == SIDE_ON) | (sides[i + 1] == sides[i])) // | instead of || for branch optimization + { + continue; + } + + // generate a split point + vec3_t mid; + unsigned int tmp = i + 1; + if (tmp >= m_NumPoints) + { + tmp = 0; + } + vec_t* p2 = m_Points[tmp]; + dot = dists[i] / (dists[i] - dists[i + 1]); +#if 0 + for (j = 0; j < 3; j++) + { // avoid round off error when possible + if (normal[j] < 1.0 - NORMAL_EPSILON) + { + if (normal[j] > -1.0 + NORMAL_EPSILON) + { + mid[j] = p1[j] + dot * (p2[j] - p1[j]); + } + else + { + mid[j] = -dist; + } + } + else + { + mid[j] = dist; + } + } +#else + 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]); + } +#endif + + VectorCopy(mid, f->m_Points[f->m_NumPoints]); + VectorCopy(mid, b->m_Points[b->m_NumPoints]); + f->m_NumPoints++; + b->m_NumPoints++; + } + + if ((f->m_NumPoints > maxpts) | (b->m_NumPoints > maxpts)) // | instead of || for branch optimization + { + Error("Winding::Clip : points exceeded estimate"); + } + if ((f->m_NumPoints > MAX_POINTS_ON_WINDING) | (b->m_NumPoints > MAX_POINTS_ON_WINDING)) // | instead of || for branch optimization + { + Error("Winding::Clip : MAX_POINTS_ON_WINDING"); + } + f->RemoveColinearPoints( +#ifdef ZHLT_WINDING_EPSILON + epsilon +#endif + ); + b->RemoveColinearPoints( +#ifdef ZHLT_WINDING_EPSILON + epsilon +#endif + ); +#ifdef ZHLT_WINDING_RemoveColinearPoints_VL + if (f->m_NumPoints == 0) + { + delete f; + *front = NULL; + } + if (b->m_NumPoints == 0) + { + delete b; + *back = NULL; + } +#endif +} + +bool Winding::Chop(const vec3_t normal, const vec_t dist +#ifdef ZHLT_WINDING_EPSILON + , vec_t epsilon +#endif + ) +{ + Winding* f; + Winding* b; + + Clip(normal, dist, &f, &b +#ifdef ZHLT_WINDING_EPSILON + , epsilon +#endif + ); + if (b) + { + delete b; + } + + if (f) + { + delete[] m_Points; + m_NumPoints = f->m_NumPoints; + m_Points = f->m_Points; + f->m_Points = NULL; + delete f; + return true; + } + else + { + m_NumPoints = 0; + delete[] m_Points; + m_Points = NULL; + return false; + } +} + +int Winding::WindingOnPlaneSide(const vec3_t normal, const vec_t dist +#ifdef ZHLT_WINDING_EPSILON + , vec_t epsilon +#endif + ) +{ + bool front, back; + unsigned int i; + vec_t d; + + front = false; + back = false; + for (i = 0; i < m_NumPoints; i++) + { + d = DotProduct(m_Points[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; +} + + +bool Winding::Clip(const dplane_t& split, bool keepon +#ifdef ZHLT_WINDING_EPSILON + , vec_t epsilon +#endif + ) +{ + vec_t dists[MAX_POINTS_ON_WINDING]; + int sides[MAX_POINTS_ON_WINDING]; + int counts[3]; + vec_t dot; + int i, j; + + counts[0] = counts[1] = counts[2] = 0; + + // determine sides for each point + // do this exactly, with no epsilon so tiny portals still work + for (i = 0; i < m_NumPoints; i++) + { + dot = DotProduct(m_Points[i], split.normal); + dot -= split.dist; + dists[i] = dot; + if (dot > ON_EPSILON) + { + sides[i] = SIDE_FRONT; + } +#ifdef ZHLT_WINDING_FIX + else if (dot < -ON_EPSILON) +#else + else if (dot < ON_EPSILON) +#endif + { + sides[i] = SIDE_BACK; + } + else + { + sides[i] = SIDE_ON; + } + counts[sides[i]]++; + } + sides[i] = sides[0]; + dists[i] = dists[0]; + + if (keepon && !counts[0] && !counts[1]) + { + return true; + } + + if (!counts[0]) + { + delete[] m_Points; + m_Points = NULL; + m_NumPoints = 0; + return false; + } + + if (!counts[1]) + { + return true; + } + + unsigned maxpts = m_NumPoints + 4; // can't use counts[0]+2 because of fp grouping errors + unsigned newNumPoints = 0; + vec3_t* newPoints = new vec3_t[maxpts]; + memset(newPoints, 0, sizeof(vec3_t) * maxpts); + + for (i = 0; i < m_NumPoints; i++) + { + vec_t* p1 = m_Points[i]; + + if (sides[i] == SIDE_ON) + { + VectorCopy(p1, newPoints[newNumPoints]); + newNumPoints++; + continue; + } + else if (sides[i] == SIDE_FRONT) + { + VectorCopy(p1, newPoints[newNumPoints]); + newNumPoints++; + } + + if (sides[i + 1] == SIDE_ON || sides[i + 1] == sides[i]) + { + continue; + } + + // generate a split point + vec3_t mid; + unsigned int tmp = i + 1; + if (tmp >= m_NumPoints) + { + tmp = 0; + } + vec_t* p2 = m_Points[tmp]; + dot = dists[i] / (dists[i] - dists[i + 1]); +#ifdef ZHLT_WINDING_FIX // we must guarantee that no reversed edge will form + 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]); + } +#else + for (j = 0; j < 3; j++) + { // avoid round off error when possible + if (split.normal[j] < 1.0 - NORMAL_EPSILON) + { + if (split.normal[j] > -1.0 + NORMAL_EPSILON) + { + mid[j] = p1[j] + dot * (p2[j] - p1[j]); + } + else + { + mid[j] = -split.dist; + } + } + else + { + mid[j] = split.dist; + } + } +#endif + + VectorCopy(mid, newPoints[newNumPoints]); + newNumPoints++; + } + + if (newNumPoints > maxpts) + { + Error("Winding::Clip : points exceeded estimate"); + } + + delete[] m_Points; + m_Points = newPoints; + m_NumPoints = newNumPoints; + + RemoveColinearPoints( +#ifdef ZHLT_WINDING_EPSILON + epsilon +#endif + ); +#ifdef ZHLT_WINDING_RemoveColinearPoints_VL + if (m_NumPoints == 0) + { + delete[] m_Points; + m_Points = NULL; + m_NumPoints = 0; + return false; + } +#endif + + return true; +} + +/* + * ================== + * Divide + * Divides a winding by a plane, producing one or two windings. The + * original winding is not damaged or freed. If only on one side, the + * returned winding will be the input winding. If on both sides, two + * new windings will be created. + * ================== + */ + +void Winding::Divide(const dplane_t& split, Winding** front, Winding** back +#ifdef ZHLT_WINDING_EPSILON + , vec_t epsilon +#endif + ) +{ + vec_t dists[MAX_POINTS_ON_WINDING]; + int sides[MAX_POINTS_ON_WINDING]; + int counts[3]; + vec_t dot; + int i, j; + int maxpts; + + counts[0] = counts[1] = counts[2] = 0; + + // determine sides for each point + for (i = 0; i < m_NumPoints; i++) + { + dot = DotProduct(m_Points[i], split.normal); + dot -= split.dist; + dists[i] = dot; + if (dot > ON_EPSILON) + { + sides[i] = SIDE_FRONT; + } + else if (dot < -ON_EPSILON) + { + sides[i] = SIDE_BACK; + } + else + { + sides[i] = SIDE_ON; + } + counts[sides[i]]++; + } + sides[i] = sides[0]; + dists[i] = dists[0]; + + *front = *back = NULL; + +#ifdef HLBSP_SPLITFACE_FIX + if (!counts[0] && !counts[1]) + { + vec_t sum = 0.0; + for (i = 0; i < m_NumPoints; i++) + { + dot = DotProduct(m_Points[i], split.normal); + dot -= split.dist; + sum += dot; + } + if (sum > NORMAL_EPSILON) + { + *front = this; + } + else + { + *back = this; + } + return; + } +#endif + if (!counts[0]) + { + *back = this; // Makes this function non-const + return; + } + if (!counts[1]) + { + *front = this; // Makes this function non-const + return; + } + + maxpts = m_NumPoints + 4; // can't use counts[0]+2 because + // of fp grouping errors + + Winding* f = new Winding(maxpts); + Winding* b = new Winding(maxpts); + + *front = f; + *back = b; + + f->m_NumPoints = 0; + b->m_NumPoints = 0; + + for (i = 0; i < m_NumPoints; i++) + { + vec_t* p1 = m_Points[i]; + + if (sides[i] == SIDE_ON) + { + VectorCopy(p1, f->m_Points[f->m_NumPoints]); + VectorCopy(p1, b->m_Points[b->m_NumPoints]); + f->m_NumPoints++; + b->m_NumPoints++; + continue; + } + else if (sides[i] == SIDE_FRONT) + { + VectorCopy(p1, f->m_Points[f->m_NumPoints]); + f->m_NumPoints++; + } + else if (sides[i] == SIDE_BACK) + { + VectorCopy(p1, b->m_Points[b->m_NumPoints]); + b->m_NumPoints++; + } + + if (sides[i + 1] == SIDE_ON || sides[i + 1] == sides[i]) + { + continue; + } + + // generate a split point + vec3_t mid; + unsigned int tmp = i + 1; + if (tmp >= m_NumPoints) + { + tmp = 0; + } + vec_t* p2 = m_Points[tmp]; + dot = dists[i] / (dists[i] - dists[i + 1]); +#ifdef ZHLT_WINDING_FIX + 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]); + } +#else + for (j = 0; j < 3; j++) + { // avoid round off error when possible + if (split.normal[j] < 1.0 - NORMAL_EPSILON) + { + if (split.normal[j] > -1.0 + NORMAL_EPSILON) + { + mid[j] = p1[j] + dot * (p2[j] - p1[j]); + } + else + { + mid[j] = -split.dist; + } + } + else + { + mid[j] = split.dist; + } + } +#endif + + VectorCopy(mid, f->m_Points[f->m_NumPoints]); + VectorCopy(mid, b->m_Points[b->m_NumPoints]); + f->m_NumPoints++; + b->m_NumPoints++; + } + + if (f->m_NumPoints > maxpts || b->m_NumPoints > maxpts) + { + Error("Winding::Divide : points exceeded estimate"); + } + + f->RemoveColinearPoints( +#ifdef ZHLT_WINDING_EPSILON + epsilon +#endif + ); + b->RemoveColinearPoints( +#ifdef ZHLT_WINDING_EPSILON + epsilon +#endif + ); +#ifdef ZHLT_WINDING_RemoveColinearPoints_VL + if (f->m_NumPoints == 0) + { + delete f; + delete b; + *front = NULL; + *back = this; + } + else if (b->m_NumPoints == 0) + { + delete f; + delete b; + *back = NULL; + *front = this; + } +#endif +} + + +void Winding::addPoint(const vec3_t newpoint) +{ + if (m_NumPoints >= m_MaxPoints) + { + resize(m_NumPoints + 1); + } + VectorCopy(newpoint, m_Points[m_NumPoints]); + m_NumPoints++; +} + + +void Winding::insertPoint(const vec3_t newpoint, const unsigned int offset) +{ + if (offset >= m_NumPoints) + { + addPoint(newpoint); + } + else + { + if (m_NumPoints >= m_MaxPoints) + { + resize(m_NumPoints + 1); + } + + unsigned x; + for (x = m_NumPoints; x>offset; x--) + { + VectorCopy(m_Points[x-1], m_Points[x]); + } + VectorCopy(newpoint, m_Points[x]); + + m_NumPoints++; + } +} + + +void Winding::resize(UINT32 newsize) +{ + newsize = (newsize + 3) & ~3; // groups of 4 + + vec3_t* newpoints = new vec3_t[newsize]; + m_NumPoints = qmin(newsize, m_NumPoints); + memcpy(newpoints, m_Points, m_NumPoints); + delete[] m_Points; + m_Points = newpoints; + m_MaxPoints = newsize; +} + +void Winding::CopyPoints(vec3_t *points, int &numpoints) +{ + if(!points) + { + numpoints = 0; + return; + } + + memcpy(points, m_Points, sizeof(vec3_t) * m_NumPoints); + + numpoints = m_NumPoints; +} + +void Winding::Reset(void) +{ + if(m_Points) + { + delete [] m_Points; + m_Points = NULL; + } + + m_NumPoints = m_MaxPoints = 0; +} \ No newline at end of file diff --git a/src/zhlt-vluzacn/common/winding.h b/src/zhlt-vluzacn/common/winding.h new file mode 100644 index 0000000..99b0b51 --- /dev/null +++ b/src/zhlt-vluzacn/common/winding.h @@ -0,0 +1,136 @@ +#ifndef WINDING_H__ +#define WINDING_H__ +#include "cmdlib.h" //--vluzacn + +#if _MSC_VER >= 1000 +#pragma once +#endif + +#include "basictypes.h" +#include "mathtypes.h" +#include "win32fix.h" +#include "mathlib.h" +#include "bspfile.h" +#include "boundingbox.h" + +#define MAX_POINTS_ON_WINDING 128 +// TODO: FIX THIS STUPID SHIT (MAX_POINTS_ON_WINDING) + +#define BASE_WINDING_DISTANCE 9000 + +#define SIDE_FRONT 0 +#define SIDE_ON 2 +#define SIDE_BACK 1 +#define SIDE_CROSS -2 + +#ifdef HLCSG_HLBSP_DOUBLEPLANE +#ifdef HLBSP +#ifndef DOUBLEVEC_T +#error you must add -dDOUBLEVEC_T to the project! +#endif +#define dplane_t plane_t +#define g_dplanes g_mapplanes +typedef struct +{ + vec3_t normal; + vec3_t unused_origin; + vec_t dist; + planetypes type; +} dplane_t; +extern dplane_t g_dplanes[MAX_INTERNAL_MAP_PLANES]; +#endif +#endif +class Winding +{ +public: + // General Functions + void Print() const; + void getPlane(dplane_t& plane) const; + void getPlane(vec3_t& normal, vec_t& dist) const; + vec_t getArea() const; + void getBounds(BoundingBox& bounds) const; + void getBounds(vec3_t& mins, vec3_t& maxs) const; + void getCenter(vec3_t& center) const; + Winding* Copy() const; + void Check( +#ifdef ZHLT_WINDING_EPSILON + vec_t epsilon = ON_EPSILON +#endif + ) const; // Developer check for validity + bool Valid() const; // Runtime/user/normal check for validity + void addPoint(const vec3_t newpoint); + void insertPoint(const vec3_t newpoint, const unsigned int offset); + + // Specialized Functions + void RemoveColinearPoints( +#ifdef ZHLT_WINDING_EPSILON + vec_t epsilon = ON_EPSILON +#endif + ); + bool Clip(const dplane_t& split, bool keepon +#ifdef ZHLT_WINDING_EPSILON + , vec_t epsilon = ON_EPSILON +#endif + ); // For hlbsp + void Clip(const dplane_t& split, Winding** front, Winding** back +#ifdef ZHLT_WINDING_EPSILON + , vec_t epsilon = ON_EPSILON +#endif + ); + void Clip(const vec3_t normal, const vec_t dist, Winding** front, Winding** back +#ifdef ZHLT_WINDING_EPSILON + , vec_t epsilon = ON_EPSILON +#endif + ); + bool Chop(const vec3_t normal, const vec_t dist +#ifdef ZHLT_WINDING_EPSILON + , vec_t epsilon = ON_EPSILON +#endif + ); + void Divide(const dplane_t& split, Winding** front, Winding** back +#ifdef ZHLT_WINDING_EPSILON + , vec_t epsilon = ON_EPSILON +#endif + ); + int WindingOnPlaneSide(const vec3_t normal, const vec_t dist +#ifdef ZHLT_WINDING_EPSILON + , vec_t epsilon = ON_EPSILON +#endif + ); + void CopyPoints(vec3_t *points, int &numpoints); + + void initFromPoints(vec3_t *points, UINT32 numpoints); + void Reset(void); // Resets the structure + +protected: + void resize(UINT32 newsize); + +public: + // Construction + Winding(); // Do nothing :) + Winding(vec3_t *points, UINT32 numpoints); // Create from raw points + Winding(const dface_t& face +#ifdef ZHLT_WINDING_EPSILON + , vec_t epsilon = ON_EPSILON +#endif + ); + Winding(const dplane_t& face); + Winding(const vec3_t normal, const vec_t dist); + Winding(UINT32 points); + Winding(const Winding& other); + virtual ~Winding(); + Winding& operator=(const Winding& other); + + // Misc +private: + void initFromPlane(const vec3_t normal, const vec_t dist); + +public: + // Data + UINT32 m_NumPoints; + vec3_t* m_Points; +protected: + UINT32 m_MaxPoints; +}; + +#endif \ No newline at end of file diff --git a/src/zhlt-vluzacn/hlbsp/brink.cpp b/src/zhlt-vluzacn/hlbsp/brink.cpp new file mode 100644 index 0000000..731021c --- /dev/null +++ b/src/zhlt-vluzacn/hlbsp/brink.cpp @@ -0,0 +1,2066 @@ +#include "bsp5.h" + +#ifdef HLBSP_BRINKHACK + +#include +#include +#include + +// TODO: we should consider corners in addition to brinks. +// TODO: use bcircle_t structure only to find out all possible "movement"s, then send then down the bsp tree to determine which leafs may incorrectly block the movement. + +// The image of a typical buggy brink (with type BrinkFloorBlocking): +// +// brinkstoppoint +// x------------x +// / / \. +// / / \. +// / move >>>>xblocked +// / / \. +// / / x +// x------------x / +// | brinkstartpoint/ +// | | \ / Z +// | | \ / | Y +// | | \ / | / +// x------------x----x |/ +// O-------X +// + + +typedef struct bpartition_s +{ + int planenum; + bool planeside; + int content; + bbrinklevel_e type; + + bpartition_s *next; +} +bpartition_t; + +typedef struct bclipnode_s +{ + bool isleaf; + + int planenum; + const dplane_t *plane; + bclipnode_s *children[2]; // children[0] is the front side of the plane (SIDE_FRONT = 0) + + int content; + bpartition_t *partitions; + + struct btreeleaf_s *treeleaf; +} +bclipnode_t; + +typedef struct bbrinknode_s +{ // we will only focus on the bsp shape which encircles a brink, so we extract the clipnodes that meets with the brink and store them here + bool isleaf; + + int planenum; + const dplane_t *plane; + int children[2]; + + int content; + bclipnode_t *clipnode; +} +bbrinknode_t; + +typedef struct +{ + vec3_t start, stop; + vec3_t direction; + + int numnodes; // including both nodes and leafs + std::vector< bbrinknode_t > *nodes; + + struct btreeedge_s *edge; // only for use in deciding brink type +} +bbrink_t; + +bbrink_t *CopyBrink (bbrink_t *other) +{ + bbrink_t *b; + hlassume (b = (bbrink_t *)malloc (sizeof (bbrink_t)), assume_NoMemory); + VectorCopy (other->direction, b->direction); + VectorCopy (other->start, b->start); + VectorCopy (other->stop, b->stop); + b->numnodes = other->numnodes; + b->nodes = new std::vector< bbrinknode_t >(*other->nodes); + return b; +} + +void DeleteBrink (bbrink_t *b) +{ + delete b->nodes; + free (b); +} + +bbrink_t *CreateBrink (vec3_t start, vec3_t stop) +{ + bbrink_t *b; + hlassume (b = (bbrink_t *)malloc (sizeof (bbrink_t)), assume_NoMemory); + + VectorCopy (start, b->start); + VectorCopy (stop, b->stop); + VectorSubtract (stop, start, b->direction); + + b->numnodes = 1; + b->nodes = new std::vector< bbrinknode_t >; + bbrinknode_t newnode; + newnode.isleaf = true; + newnode.clipnode = NULL; + b->nodes->push_back (newnode); + + // CreateBrink must be followed by BrinkSplitClipnode + return b; +} + +void PrintBrink (const bbrink_t *b) +{ + Log ("direction %f %f %f start %f %f %f stop %f %f %f\n", b->direction[0], b->direction[1], b->direction[2], b->start[0], b->start[1], b->start[2], b->stop[0], b->stop[1], b->stop[2]); + Log ("numnodes %d\n", b->numnodes); + for (int i = 0; i < b->numnodes; i++) + { + bbrinknode_t *n = &(*b->nodes)[i]; + if (n->isleaf) + { + Log ("leaf[%d] content %d\n", i, n->content); + } + else + { + Log ("node[%d]-[%d:%d] plane %f %f %f %f\n", i, n->children[0], n->children[1], n->plane->normal[0], n->plane->normal[1], n->plane->normal[2], n->plane->dist); + } + } +} + +void BrinkSplitClipnode (bbrink_t *b, const dplane_t *plane, int planenum, bclipnode_t *prev, bclipnode_t *n0, bclipnode_t *n1) +{ + int found; + int numfound = 0; + for (int i = 0; i < b->numnodes; i++) + { + bbrinknode_t *node = &(*b->nodes)[i]; + if (node->isleaf && node->clipnode == prev) + { + found = i; + numfound++; + } + } + if (numfound == 0) + { + PrintOnce ("BrinkSplitClipnode: internal error: couldn't find clipnode"); + hlassume (false, assume_first); + } + if (numfound > 1) + { + PrintOnce ("BrinkSplitClipnode: internal error: found more than one clipnode"); + hlassume (false, assume_first); + } + if (n0 == n1) + { + PrintOnce ("BrinkSplitClipnode: internal error: n0 == n1"); + hlassume (false, assume_first); + } + b->nodes->resize (b->numnodes + 2); + bbrinknode_t *node = &(*b->nodes)[found]; + bbrinknode_t *front = &(*b->nodes)[b->numnodes]; + bbrinknode_t *back = &(*b->nodes)[b->numnodes + 1]; + + node->clipnode = NULL; + node->isleaf = false; + node->plane = plane; + node->planenum = planenum; + node->children[0] = b->numnodes; + node->children[1] = b->numnodes + 1; + + front->isleaf = true; + front->content = n0->content; + front->clipnode = n0; + + back->isleaf = true; + back->content = n1->content; + back->clipnode = n1; + + b->numnodes += 2; +} + +void BrinkReplaceClipnode (bbrink_t *b, bclipnode_t *prev, bclipnode_t *n) +{ + int found; + int numfound = 0; + for (int i = 0; i < b->numnodes; i++) + { + bbrinknode_t *node = &(*b->nodes)[i]; + if (node->isleaf && node->clipnode == prev) + { + found = i; + numfound++; + } + } + if (numfound == 0) + { + PrintOnce ("BrinkSplitClipnode: internal error: couldn't find clipnode"); + hlassume (false, assume_first); + } + if (numfound > 1) + { + PrintOnce ("BrinkSplitClipnode: internal error: found more than one clipnode"); + hlassume (false, assume_first); + } + bbrinknode_t *node = &(*b->nodes)[found]; + node->clipnode = n; + node->content = n->content; +} + + + +// compute the structure of the whole bsp tree + +struct btreepoint_s; // 0d object +struct btreeedge_s; // 1d object +struct btreeface_s; // 2d object +struct btreeleaf_s; // 3d object + +typedef struct +{ + btreepoint_s *p; + bool side; +} +btreepoint_r; + +typedef struct +{ + btreeedge_s *e; + bool side; +} +btreeedge_r; + +typedef struct +{ + btreeface_s *f; + bool side; +} +btreeface_r; + +typedef struct +{ + btreeleaf_s *l; + bool side; +} +btreeleaf_r; + +typedef std::list< btreepoint_r > btreepoint_l; +typedef std::list< btreeedge_r > btreeedge_l; +typedef std::list< btreeface_r > btreeface_l; +typedef std::list< btreeleaf_r > btreeleaf_l; + +typedef struct btreepoint_s +{ + vec3_t v; + bool infinite; + + btreeedge_l *edges; // this is a reversed reference + + bool tmp_tested; + vec_t tmp_dist; + int tmp_side; +} +btreepoint_t; + +typedef struct btreeedge_s +{ + btreepoint_r points[2]; // pointing from points[1] to points[0] + bool infinite; // both points are infinite (i.e. this edge lies on the bounding box) + + btreeface_l *faces; // this is a reversed reference + + bbrink_t *brink; // not defined for infinite edges + + bool tmp_tested; + int tmp_side; +#ifdef HLBSP_BRINKNOTUSEDBYLEAF_FIX + bool tmp_onleaf[2]; +#endif +} +btreeedge_t; + +typedef struct btreeface_s +{ + btreeedge_l *edges; // empty faces are allowed (in order to preserve topological correctness) + bool infinite; // when the face is infinite, all its edges must also be infinite + + btreeleaf_r leafs[2]; // pointing from leafs[0] to leafs[1] // this is a reversed reference + + const dplane_t *plane; // not defined for infinite face + int planenum; + bool planeside; // if ture, this face is pointing at -plane->normal + + bool tmp_tested; + int tmp_side; +} +btreeface_t; + +typedef struct btreeleaf_s +{ + btreeface_l *faces; + bool infinite; // note: the infinite leaf is not convex + + bclipnode_t *clipnode; // not defined for infinite leaf +} +btreeleaf_t; + +btreepoint_t *AllocTreepoint (int &numobjects, bool infinite) +{ + numobjects++; + btreepoint_t *tp = (btreepoint_t *)malloc (sizeof (btreepoint_t)); + hlassume (tp != NULL, assume_NoMemory); + tp->edges = new btreeedge_l (); + tp->infinite = infinite; + return tp; +} + +btreeedge_t *AllocTreeedge (int &numobjects, bool infinite) +{ + numobjects++; + btreeedge_t *te = (btreeedge_t *)malloc (sizeof (btreeedge_t)); + hlassume (te != NULL, assume_NoMemory); + te->points[0].p = NULL; + te->points[0].side = false; + te->points[1].p = NULL; + te->points[1].side = true; + te->faces = new btreeface_l (); + te->infinite = infinite; + // should be followed by SetEdgePoints + return te; +} + +void AttachPointToEdge (btreeedge_t *te, btreepoint_t *tp, bool side) +{ + if (te->points[side].p) + { + PrintOnce ("AttachPointToEdge: internal error: point occupied."); + hlassume (false, assume_first); + } + if (te->infinite && !tp->infinite) + { + PrintOnce ("AttachPointToEdge: internal error: attaching a finite object to an infinite object."); + hlassume (false, assume_first); + } + te->points[side].p = tp; + + btreeedge_r er; + er.e = te; + er.side = side; + tp->edges->push_back (er); +} + +void SetEdgePoints (btreeedge_t *te, btreepoint_t *tp0, btreepoint_t *tp1) +{ + AttachPointToEdge (te, tp0, false); + AttachPointToEdge (te, tp1, true); +} + +btreeface_t *AllocTreeface (int &numobjects, bool infinite) +{ + numobjects++; + btreeface_t *tf = (btreeface_t *)malloc (sizeof (btreeface_t)); + hlassume (tf != NULL, assume_NoMemory); + tf->edges = new btreeedge_l (); + tf->leafs[0].l = NULL; + tf->leafs[0].side = false; + tf->leafs[1].l = NULL; + tf->leafs[1].side = true; + tf->infinite = infinite; + return tf; +} + +void AttachEdgeToFace (btreeface_t *tf, btreeedge_t *te, int side) +{ + if (tf->infinite && !te->infinite) + { + PrintOnce ("AttachEdgeToFace: internal error: attaching a finite object to an infinite object."); + hlassume (false, assume_first); + } + btreeedge_r er; + er.e = te; + er.side = side; + tf->edges->push_back (er); + + btreeface_r fr; + fr.f = tf; + fr.side = side; + te->faces->push_back (fr); +} + +void AttachFaceToLeaf (btreeleaf_t *tl, btreeface_t *tf, int side) +{ + if (tl->infinite && !tf->infinite) + { + PrintOnce ("AttachFaceToLeaf: internal error: attaching a finite object to an infinite object."); + hlassume (false, assume_first); + } + btreeface_r fr; + fr.f = tf; + fr.side = side; + tl->faces->push_back (fr); + + if (tf->leafs[side].l) + { + PrintOnce ("AttachFaceToLeaf: internal error: leaf occupied."); + hlassume (false, assume_first); + } + tf->leafs[side].l = tl; +} + +void SetFaceLeafs (btreeface_t *tf, btreeleaf_t *tl0, btreeleaf_t *tl1) +{ + AttachFaceToLeaf (tl0, tf, false); + AttachFaceToLeaf (tl1, tf, true); +} + +btreeleaf_t *AllocTreeleaf (int &numobjects, bool infinite) +{ + numobjects++; + btreeleaf_t *tl = (btreeleaf_t *)malloc (sizeof (btreeleaf_t)); + hlassume (tl != NULL, assume_NoMemory); + tl->faces = new btreeface_l (); + tl->infinite = infinite; + return tl; +} + +btreeleaf_t *BuildOutside (int &numobjects) +{ + btreeleaf_t *leaf_outside; + leaf_outside = AllocTreeleaf (numobjects, true); + leaf_outside->clipnode = NULL; + return leaf_outside; +} + +btreeleaf_t *BuildBaseCell (int &numobjects, bclipnode_t *clipnode, vec_t range, btreeleaf_t *leaf_outside) +{ + btreepoint_t *tp[8]; + for (int i = 0; i < 8; i++) + { + tp[i] = AllocTreepoint (numobjects, true); + if (i & 1) + tp[i]->v[0] = range; + else + tp[i]->v[0] = -range; + if (i & 2) + tp[i]->v[1] = range; + else + tp[i]->v[1] = -range; + if (i & 4) + tp[i]->v[2] = range; + else + tp[i]->v[2] = -range; + } + btreeedge_t *te[12]; + for (int i = 0; i < 12; i++) + { + te[i] = AllocTreeedge (numobjects, true); + } + SetEdgePoints (te[0], tp[1], tp[0]); + SetEdgePoints (te[1], tp[3], tp[2]); + SetEdgePoints (te[2], tp[5], tp[4]); + SetEdgePoints (te[3], tp[7], tp[6]); + SetEdgePoints (te[4], tp[2], tp[0]); + SetEdgePoints (te[5], tp[3], tp[1]); + SetEdgePoints (te[6], tp[6], tp[4]); + SetEdgePoints (te[7], tp[7], tp[5]); + SetEdgePoints (te[8], tp[4], tp[0]); + SetEdgePoints (te[9], tp[5], tp[1]); + SetEdgePoints (te[10], tp[6], tp[2]); + SetEdgePoints (te[11], tp[7], tp[3]); + btreeface_t *tf[6]; + for (int i = 0; i < 6; i++) + { + tf[i] = AllocTreeface (numobjects, true); + } + AttachEdgeToFace (tf[0], te[4], true); + AttachEdgeToFace (tf[0], te[6], false); + AttachEdgeToFace (tf[0], te[8], false); + AttachEdgeToFace (tf[0], te[10], true); + AttachEdgeToFace (tf[1], te[5], false); + AttachEdgeToFace (tf[1], te[7], true); + AttachEdgeToFace (tf[1], te[9], true); + AttachEdgeToFace (tf[1], te[11], false); + AttachEdgeToFace (tf[2], te[0], false); + AttachEdgeToFace (tf[2], te[2], true); + AttachEdgeToFace (tf[2], te[8], true); + AttachEdgeToFace (tf[2], te[9], false); + AttachEdgeToFace (tf[3], te[1], true); + AttachEdgeToFace (tf[3], te[3], false); + AttachEdgeToFace (tf[3], te[10], false); + AttachEdgeToFace (tf[3], te[11], true); + AttachEdgeToFace (tf[4], te[0], true); + AttachEdgeToFace (tf[4], te[1], false); + AttachEdgeToFace (tf[4], te[4], false); + AttachEdgeToFace (tf[4], te[5], true); + AttachEdgeToFace (tf[5], te[2], false); + AttachEdgeToFace (tf[5], te[3], true); + AttachEdgeToFace (tf[5], te[6], true); + AttachEdgeToFace (tf[5], te[7], false); + btreeleaf_t *tl; + tl = AllocTreeleaf (numobjects, false); + for (int i = 0; i < 6; i++) + { + SetFaceLeafs (tf[i], tl, leaf_outside); + } + tl->clipnode = clipnode; + return tl; +} + +btreepoint_t *GetPointFromEdge (btreeedge_t *te, bool side) +{ + if (!te->points[side].p) + { + PrintOnce ("GetPointFromEdge: internal error: point not set."); + hlassume (false, assume_first); + } + return te->points[side].p; +} + +void RemoveEdgeFromList (btreeedge_l *el, btreeedge_t *te, bool side) +{ + btreeedge_l::iterator ei; + for (ei = el->begin (); ei != el->end (); ei++) + { + if (ei->e == te && ei->side == side) + { + el->erase (ei); + return; // only remove one copy if there are many (in order to preserve topological correctness) + } + } + PrintOnce ("RemoveEdgeFromList: internal error: edge not found."); + hlassume (false, assume_first); +} + +void RemovePointFromEdge (btreeedge_t *te, btreepoint_t *tp, bool side) // warning: the point will not be freed +{ + if (te->points[side].p != tp) + { + PrintOnce ("RemovePointFromEdge: internal error: point not found."); + hlassume (false, assume_first); + } + te->points[side].p = NULL; + + RemoveEdgeFromList (tp->edges, te, side); +} + +void DeletePoint (int &numobjects, btreepoint_t *tp) +{ + if (!tp->edges->empty ()) + { + PrintOnce ("DeletePoint: internal error: point used by edge."); + hlassume (false, assume_first); + } + delete tp->edges; + free (tp); + numobjects--; +} + +void RemoveFaceFromList (btreeface_l *fl, btreeface_t *tf, bool side) +{ + btreeface_l::iterator fi; + for (fi = fl->begin (); fi != fl->end (); fi++) + { + if (fi->f == tf && fi->side == side) + { + fl->erase (fi); + return; + } + } + PrintOnce ("RemoveFaceFromList: internal error: face not found."); + hlassume (false, assume_first); +} + +void RemoveEdgeFromFace (btreeface_t *tf, btreeedge_t *te, bool side) +{ + RemoveEdgeFromList (tf->edges, te, side); + RemoveFaceFromList (te->faces, tf, side); +} + +void DeleteEdge (int &numobjects, btreeedge_t *te) // warning: points in this edge could be freed if not reference by any other edges +{ + if (!te->faces->empty ()) + { + PrintOnce ("DeleteEdge: internal error: edge used by face."); + hlassume (false, assume_first); + } + if (!te->infinite) + { + DeleteBrink (te->brink); + } + for (int side = 0; side < 2; side++) + { + btreepoint_t *tp; + tp = GetPointFromEdge (te, side); + RemovePointFromEdge (te, tp, side); + if (tp->edges->empty ()) + { + DeletePoint (numobjects, tp); + } + } + delete te->faces; + free (te); + numobjects--; +} + +btreeleaf_t *GetLeafFromFace (btreeface_t *tf, bool side) +{ + if (!tf->leafs[side].l) + { + PrintOnce ("GetLeafFromFace: internal error: Leaf not set."); + hlassume (false, assume_first); + } + return tf->leafs[side].l; +} + +void RemoveFaceFromLeaf (btreeleaf_t *tl, btreeface_t *tf, bool side) +{ + if (tf->leafs[side].l != tl) + { + PrintOnce ("RemoveFaceFromLeaf: internal error: leaf not found."); + hlassume (false, assume_first); + } + tf->leafs[side].l = NULL; + + RemoveFaceFromList (tl->faces, tf, side); +} + +void DeleteFace (int &numobjects, btreeface_t *tf) // warning: edges in this face could be freed if not reference by any other faces +{ + btreeedge_l::iterator ei; + while ((ei = tf->edges->begin ()) != tf->edges->end ()) + { + btreeedge_t *te = ei->e; + RemoveFaceFromList (te->faces, tf, ei->side); + tf->edges->erase (ei); + if (te->faces->empty ()) + { + DeleteEdge (numobjects, te); + } + } + for (int side = 0; side < 2; side++) + { + if (tf->leafs[side].l) + { + PrintOnce ("DeleteFace: internal error: face used by leaf."); + hlassume (false, assume_first); + } + } + delete tf->edges; + free (tf); + numobjects--; +} + +void DeleteLeaf (int &numobjects, btreeleaf_t *tl) +{ + btreeface_l::iterator fi; + while ((fi = tl->faces->begin ()) != tl->faces->end ()) + { + btreeface_t *tf = fi->f; + RemoveFaceFromLeaf (tl, tf, fi->side); + if (!tf->leafs[false].l && !tf->leafs[true].l) + { + DeleteFace (numobjects, tf); + } + } + delete tl->faces; + free (tl); + numobjects--; +} + +void SplitTreeLeaf (int &numobjects, btreeleaf_t *tl, const dplane_t *plane, int planenum, vec_t epsilon, btreeleaf_t *&front, btreeleaf_t *&back, bclipnode_t *c0, bclipnode_t *c1) +{ + btreeface_l::iterator fi; + btreeedge_l::iterator ei; + bool restart = false; + + // clear all the flags + for (fi = tl->faces->begin (); fi != tl->faces->end (); fi++) + { + btreeface_t *tf = fi->f; + tf->tmp_tested = false; + for (ei = tf->edges->begin (); ei != tf->edges->end (); ei++) + { + btreeedge_t *te = ei->e; + te->tmp_tested = false; + for (int side = 0; side < 2; side++) + { + btreepoint_t *tp = GetPointFromEdge (te, side); + tp->tmp_tested = false; + } + } + } + + // split each point + for (fi = tl->faces->begin (); fi != tl->faces->end (); fi++) + { + btreeface_t *tf = fi->f; + for (ei = tf->edges->begin (); ei != tf->edges->end (); ei++) + { + btreeedge_t *te = ei->e; + for (int side = 0; side < 2; side++) + { + btreepoint_t *tp = GetPointFromEdge (te, side); + if (tp->tmp_tested) + { + continue; + } + tp->tmp_tested = true; + vec_t dist = DotProduct (tp->v, plane->normal) - plane->dist; + tp->tmp_dist = dist; + if (dist > epsilon) + { + tp->tmp_side = SIDE_FRONT; + } + else if (dist < -epsilon) + { + tp->tmp_side = SIDE_BACK; + } + else + { + tp->tmp_side = SIDE_ON; + } +#ifdef HLBSP_BRINKHACK_BUGFIX +#if 0 + // let's mess up something and see whether the code is fragile or robust + static int randcounter = 0; + if (randcounter++ % 8 == 0) tp->tmp_side = randcounter % 3; +#endif +#endif + } + } + } + + // split each edge + for (fi = tl->faces->begin (); fi != tl->faces->end (); fi++) + { + btreeface_t *tf = fi->f; + for (ei = tf->edges->begin (); ei != tf->edges->end (); restart? restart = false, ei = tf->edges->begin (): ei++) + { + btreeedge_t *te = ei->e; + if (te->tmp_tested) // splitted + { + continue; + } + te->tmp_tested = true; + te->tmp_side = SIDE_ON; + for (int side = 0; side < 2; side++) + { + btreepoint_t *tp = GetPointFromEdge (te, side); + if (te->tmp_side == SIDE_ON) + { + te->tmp_side = tp->tmp_side; + } + else if (tp->tmp_side != SIDE_ON && tp->tmp_side != te->tmp_side) + { + te->tmp_side = SIDE_CROSS; + } + } +#ifdef HLBSP_BRINKNOTUSEDBYLEAF_FIX + // The plane does not necessarily split the leaf into two, because of epsilon problem etc., and this will cause "Error: CollectBrinks_r: not leaf" on some maps. + // In addition, by the time of this step (split edges), the plane has not splitted the leaf yet, so splitting the brink leafs now will break the integrety of the entire geometry. (We want the four steps to be as independent on each other as possible, that is, the entire geometry remains valid after each step.) +#else + if (!te->infinite) + { + if (te->tmp_side == SIDE_ON) + { + // since the plane splits the leaf into two, it must also split the brink into two + BrinkSplitClipnode (te->brink, plane, planenum, tl->clipnode, c0, c1); + } + else if (te->tmp_side == SIDE_FRONT) + { + BrinkReplaceClipnode (te->brink, tl->clipnode, c0); + } + else if (te->tmp_side == SIDE_BACK) + { + BrinkReplaceClipnode (te->brink, tl->clipnode, c1); + } + } +#endif + if (te->tmp_side == SIDE_CROSS) + { + btreepoint_t *tp0 = GetPointFromEdge (te, false); + btreepoint_t *tp1 = GetPointFromEdge (te, true); + btreepoint_t *tpmid = AllocTreepoint (numobjects, te->infinite); + tpmid->tmp_tested = true; + tpmid->tmp_dist = 0; + tpmid->tmp_side = SIDE_ON; + vec_t frac = tp0->tmp_dist / (tp0->tmp_dist - tp1->tmp_dist); + for (int k = 0; k < 3; k++) + { + tpmid->v[k] = tp0->v[k] + frac * (tp1->v[k] - tp0->v[k]); + } + btreeedge_t *te0 = AllocTreeedge (numobjects, te->infinite); + SetEdgePoints (te0, tp0, tpmid); + te0->tmp_tested = true; + te0->tmp_side = tp0->tmp_side; + if (!te0->infinite) + { + te0->brink = CopyBrink (te->brink); + VectorCopy (tpmid->v, te0->brink->start); + VectorCopy (tp0->v, te0->brink->stop); +#ifndef HLBSP_BRINKNOTUSEDBYLEAF_FIX + BrinkReplaceClipnode (te0->brink, tl->clipnode, (tp0->tmp_side == SIDE_BACK? c1: c0)); +#endif + } + btreeedge_t *te1 = AllocTreeedge (numobjects, te->infinite); + SetEdgePoints (te1, tpmid, tp1); + te1->tmp_tested = true; + te1->tmp_side = tp1->tmp_side; + if (!te1->infinite) + { + te1->brink = CopyBrink (te->brink); + VectorCopy (tp1->v, te1->brink->start); + VectorCopy (tpmid->v, te1->brink->stop); +#ifndef HLBSP_BRINKNOTUSEDBYLEAF_FIX + BrinkReplaceClipnode (te1->brink, tl->clipnode, (tp1->tmp_side == SIDE_BACK? c1: c0)); +#endif + } + btreeface_l::iterator fj; + while ((fj = te->faces->begin ()) != te->faces->end ()) + { + AttachEdgeToFace (fj->f, te0, fj->side); + AttachEdgeToFace (fj->f, te1, fj->side); + RemoveEdgeFromFace (fj->f, te, fj->side); + } + DeleteEdge (numobjects, te); + restart = true; + } + } + } + + // split each face + for (fi = tl->faces->begin (); fi != tl->faces->end (); restart? restart = false, fi = tl->faces->begin (): fi++) + { + btreeface_t *tf = fi->f; + if (tf->tmp_tested) + { + continue; + } + tf->tmp_tested = true; + tf->tmp_side = SIDE_ON; + for (ei = tf->edges->begin (); ei != tf->edges->end (); ei++) + { + if (tf->tmp_side == SIDE_ON) + { + tf->tmp_side = ei->e->tmp_side; + } + else if (ei->e->tmp_side != SIDE_ON && ei->e->tmp_side != tf->tmp_side) + { + tf->tmp_side = SIDE_CROSS; + } + } + if (tf->tmp_side == SIDE_CROSS) + { + btreeface_t *frontface, *backface; + frontface = AllocTreeface (numobjects, tf->infinite); + if (!tf->infinite) + { + frontface->plane = tf->plane; + frontface->planenum = tf->planenum; + frontface->planeside = tf->planeside; + } + SetFaceLeafs (frontface, GetLeafFromFace (tf, false), GetLeafFromFace (tf, true)); + frontface->tmp_tested = true; + frontface->tmp_side = SIDE_FRONT; + backface = AllocTreeface (numobjects, tf->infinite); + if (!tf->infinite) + { + backface->plane = tf->plane; + backface->planenum = tf->planenum; + backface->planeside = tf->planeside; + } + SetFaceLeafs (backface, GetLeafFromFace (tf, false), GetLeafFromFace (tf, true)); + backface->tmp_tested = true; + backface->tmp_side = SIDE_BACK; + + std::map< btreepoint_t *, int > vertexes; + std::map< btreepoint_t *, int >::iterator vertex, vertex2; + for (ei = tf->edges->begin (); ei != tf->edges->end (); ei++) + { + if (ei->e->tmp_side != SIDE_BACK) + { + AttachEdgeToFace (frontface, ei->e, ei->side); + } + else + { + AttachEdgeToFace (backface, ei->e, ei->side); + + btreeedge_t *e = ei->e; + for (int side = 0; side < 2; side++) + { + btreepoint_t *p = GetPointFromEdge (e, side); + vertexes[p] += ((bool)side == ei->side? 1: -1); // the default value is 0 if vertexes[p] does not exist + vertex = vertexes.find (p); + if (vertex->second == 0) + { + vertexes.erase (vertex); + } + } + } + } + if (vertexes.size () != 2) + { + Developer (DEVELOPER_LEVEL_WARNING, "SplitTreeLeaf: got invalid edge from split\n"); + } + + while (1) + { + for (vertex = vertexes.begin (); vertex != vertexes.end (); vertex++) + { + if (vertex->second > 0) + { + break; + } + } + for (vertex2 = vertexes.begin (); vertex2 != vertexes.end (); vertex2++) + { + if (vertex2->second < 0) + { + break; + } + } + if (vertex == vertexes.end () && vertex2 == vertexes.end ()) + { + break; + } + if (vertex == vertexes.end () || vertex2 == vertexes.end ()) + { + PrintOnce ("SplitTreeLeaf: internal error: couldn't link edges"); + hlassume (false, assume_first); + } + if (vertex->second != 1 || vertex2->second != -1) + { + Developer (DEVELOPER_LEVEL_WARNING, "SplitTreeLeaf: got deformed edge from split\n"); + } + if (vertex->first->tmp_side != SIDE_ON || vertex2->first->tmp_side != SIDE_ON) + { + PrintOnce ("SplitTreeLeaf: internal error: tmp_side != SIDE_ON"); + hlassume (false, assume_first); + } + + btreeedge_t *te; + te = AllocTreeedge (numobjects, tf->infinite); + SetEdgePoints (te, vertex->first, vertex2->first); + if (!te->infinite) + { + te->brink = CreateBrink (vertex2->first->v, vertex->first->v); + if (GetLeafFromFace (tf, tf->planeside)->infinite || GetLeafFromFace (tf, !tf->planeside)->infinite) + { + PrintOnce ("SplitTreeLeaf: internal error: an infinite object contains a finite object"); + hlassume (false, assume_first); + } + BrinkSplitClipnode (te->brink, tf->plane, tf->planenum, NULL, GetLeafFromFace (tf, tf->planeside)->clipnode, GetLeafFromFace (tf, !tf->planeside)->clipnode); +#ifndef HLBSP_BRINKNOTUSEDBYLEAF_FIX + BrinkSplitClipnode (te->brink, plane, planenum, tl->clipnode, c0, c1); +#endif + } + te->tmp_tested = true; + te->tmp_side = SIDE_ON; + AttachEdgeToFace (frontface, te, false); + AttachEdgeToFace (backface, te, true); + + vertex->second--; + vertex2->second++; + } + + for (int side = 0; side < 2; side++) + { + RemoveFaceFromLeaf (GetLeafFromFace (tf, side), tf, side); + } + DeleteFace (numobjects, tf); + restart = true; + } + } + + // split the leaf + { + if (tl->infinite) + { + PrintOnce ("SplitTreeLeaf: internal error: splitting the infinite leaf"); + hlassume (false, assume_first); + } + front = AllocTreeleaf (numobjects, tl->infinite); + back = AllocTreeleaf (numobjects, tl->infinite); + front->clipnode = c0; + back->clipnode = c1; + + int tmp_side = SIDE_ON; + for (fi = tl->faces->begin (); fi != tl->faces->end (); fi++) + { + if (tmp_side == SIDE_ON) + { + tmp_side = fi->f->tmp_side; + } + else if (fi->f->tmp_side != SIDE_ON && fi->f->tmp_side != tmp_side) + { + tmp_side = SIDE_CROSS; + } + } + + std::map< btreeedge_t *, int > edges; + std::map< btreeedge_t *, int >::iterator edge; + + while ((fi = tl->faces->begin ()) != tl->faces->end ()) + { + btreeface_t *tf = fi->f; + int side = fi->side; + RemoveFaceFromLeaf (tl, tf, side); // because we can only store 2 leafs for a face + + // fi is unusable now + if (tf->tmp_side == SIDE_FRONT || tf->tmp_side == SIDE_ON && tmp_side != SIDE_BACK) + { + AttachFaceToLeaf (front, tf, side); + } + else if (tf->tmp_side == SIDE_BACK || tf->tmp_side == SIDE_ON && tmp_side == SIDE_BACK) + { + AttachFaceToLeaf (back, tf, side); + + if (tmp_side == SIDE_CROSS) + { + for (ei = tf->edges->begin (); ei != tf->edges->end (); ei++) + { + edges[ei->e] += (ei->side == (bool)side? 1: -1); + edge = edges.find (ei->e); + if (edge->second == 0) + { + edges.erase (edge); + } + } + } + } + } + + if (tmp_side == SIDE_CROSS) + { + btreeface_t *tf; + tf = AllocTreeface (numobjects, tl->infinite); + if (!tf->infinite) + { + tf->plane = plane; + tf->planenum = planenum; + tf->planeside = false; + } + tf->tmp_tested = true; + tf->tmp_side = SIDE_ON; + SetFaceLeafs (tf, front, back); + for (edge = edges.begin (); edge != edges.end (); edge++) + { + if (edge->first->tmp_side != SIDE_ON) + { + PrintOnce ("SplitTreeLeaf: internal error"); + hlassume (false, assume_first); + } + while (edge->second > 0) + { + AttachEdgeToFace (tf, edge->first, false); + edge->second--; + } + while (edge->second < 0) + { + AttachEdgeToFace (tf, edge->first, true); + edge->second++; + } + } + } + +#ifndef HLBSP_BRINKHACK_BUGFIX + DeleteLeaf (numobjects, tl); +#endif +#ifdef HLBSP_BRINKNOTUSEDBYLEAF_FIX + btreeleaf_t *(frontback[2]) = {front, back}; + for (int side = 0; side < 2; side++) + { + for (fi = frontback[side]->faces->begin (); fi != frontback[side]->faces->end (); fi++) + { + for (ei = fi->f->edges->begin (); ei != fi->f->edges->end (); ei++) + { + ei->e->tmp_onleaf[0] = ei->e->tmp_onleaf[1] = false; + ei->e->tmp_tested = false; + } + } + } + for (int side = 0; side < 2; side++) + { + for (fi = frontback[side]->faces->begin (); fi != frontback[side]->faces->end (); fi++) + { + for (ei = fi->f->edges->begin (); ei != fi->f->edges->end (); ei++) + { + ei->e->tmp_onleaf[side] = true; + } + } + } + for (int side = 0; side < 2; side++) + { + for (fi = frontback[side]->faces->begin (); fi != frontback[side]->faces->end (); fi++) + { + for (ei = fi->f->edges->begin (); ei != fi->f->edges->end (); ei++) + { + if (ei->e->tmp_tested) + { + continue; + } + ei->e->tmp_tested = true; + if (!ei->e->infinite) + { + if (ei->e->tmp_onleaf[0] && ei->e->tmp_onleaf[1]) + { + if (ei->e->tmp_side != SIDE_ON) + { + PrintOnce ("SplitTreeLeaf: internal error"); + hlassume (false, assume_first); + } + BrinkSplitClipnode (ei->e->brink, plane, planenum, tl->clipnode, c0, c1); + } + else if (ei->e->tmp_onleaf[0]) + { + if (ei->e->tmp_side == SIDE_BACK) + { + PrintOnce ("SplitTreeLeaf: internal error"); + hlassume (false, assume_first); + } + BrinkReplaceClipnode (ei->e->brink, tl->clipnode, c0); + } + else if (ei->e->tmp_onleaf[1]) + { + if (ei->e->tmp_side == SIDE_FRONT) + { + PrintOnce ("SplitTreeLeaf: internal error"); + hlassume (false, assume_first); + } + BrinkReplaceClipnode (ei->e->brink, tl->clipnode, c1); + } + } + } + } + } +#endif +#ifdef HLBSP_BRINKHACK_BUGFIX + DeleteLeaf (numobjects, tl); +#endif + } +} + +void BuildTreeCells_r (int &numobjects, bclipnode_t *c) +{ + if (c->isleaf) + { + return; + } + btreeleaf_t *tl, *front, *back; + tl = c->treeleaf; + SplitTreeLeaf (numobjects, tl, c->plane, c->planenum, ON_EPSILON, front, back, c->children[0], c->children[1]); + c->treeleaf = NULL; + c->children[0]->treeleaf = front; + c->children[1]->treeleaf = back; + BuildTreeCells_r (numobjects, c->children[0]); + BuildTreeCells_r (numobjects, c->children[1]); +} + + + +typedef struct bbrinkinfo_s +{ + int numclipnodes; + bclipnode_t *clipnodes; + int numobjects; + btreeleaf_t *leaf_outside; + int numbrinks; + bbrink_t **brinks; +} +bbrinkinfo_t; + +#define MAXCLIPNODES (MAX_MAP_CLIPNODES*8) + +bclipnode_t *ExpandClipnodes_r (bclipnode_t *bclipnodes, int &numbclipnodes, const dclipnode_t *clipnodes, int headnode) +{ + if (numbclipnodes >= MAXCLIPNODES) + { + Error ("ExpandClipnodes_r: exceeded MAXCLIPNODES"); + } + bclipnode_t *c = &bclipnodes[numbclipnodes]; + numbclipnodes++; + if (headnode < 0) + { + c->isleaf = true; + c->content = headnode; + c->partitions = NULL; + } + else + { + c->isleaf = false; + c->planenum = clipnodes[headnode].planenum; + c->plane = &g_dplanes[c->planenum]; + for (int k = 0; k < 2; k++) + { + c->children[k] = ExpandClipnodes_r (bclipnodes, numbclipnodes, clipnodes, clipnodes[headnode].children[k]); + } + } + return c; +} + +void ExpandClipnodes (bbrinkinfo_t *info, const dclipnode_t *clipnodes, int headnode) +{ + bclipnode_t *bclipnodes = (bclipnode_t *)malloc (MAXCLIPNODES * sizeof (bclipnode_t)); // 262144 * 30byte = 7.5MB + hlassume (bclipnodes != NULL, assume_NoMemory); + info->numclipnodes = 0; + ExpandClipnodes_r (bclipnodes, info->numclipnodes, clipnodes, headnode); + info->clipnodes = (bclipnode_t *)malloc (info->numclipnodes * sizeof (bclipnode_t)); + hlassume (info->clipnodes != NULL, assume_NoMemory); + memcpy (info->clipnodes, bclipnodes, info->numclipnodes * sizeof (bclipnode_t)); + for (int i = 0; i < info->numclipnodes; i++) + { + for (int k = 0; k < 2; k++) + { + info->clipnodes[i].children[k] = info->clipnodes + (bclipnodes[i].children[k] - bclipnodes); + } + } + free (bclipnodes); +} + +void BuildTreeCells (bbrinkinfo_t *info) +{ + info->numobjects = 0; + info->leaf_outside = BuildOutside (info->numobjects); + info->clipnodes[0].treeleaf = BuildBaseCell (info->numobjects, &info->clipnodes[0], BOGUS_RANGE, info->leaf_outside); + BuildTreeCells_r (info->numobjects, &info->clipnodes[0]); +} + +void DeleteTreeCells_r (int &numobjects, bclipnode_t *node) +{ + if (node->treeleaf) + { + DeleteLeaf (numobjects, node->treeleaf); + node->treeleaf = NULL; + } + if (!node->isleaf) + { + DeleteTreeCells_r (numobjects, node->children[0]); + DeleteTreeCells_r (numobjects, node->children[1]); + } +} + +void DeleteTreeCells (bbrinkinfo_t *info) +{ + DeleteLeaf (info->numobjects, info->leaf_outside); + info->leaf_outside = NULL; + DeleteTreeCells_r (info->numobjects, &info->clipnodes[0]); + if (info->numobjects != 0) + { + PrintOnce ("DeleteTreeCells: internal error: numobjects != 0"); + hlassume (false, assume_first); + } +} + +void ClearMarks_r (bclipnode_t *node) +{ + if (node->isleaf) + { + btreeface_l::iterator fi; + btreeedge_l::iterator ei; + for (fi = node->treeleaf->faces->begin (); fi != node->treeleaf->faces->end (); fi++) + { + for (ei = fi->f->edges->begin (); ei != fi->f->edges->end (); ei++) + { + ei->e->tmp_tested = false; + } + } + } + else + { + ClearMarks_r (node->children[0]); + ClearMarks_r (node->children[1]); + } +} + +void CollectBrinks_r (bclipnode_t *node, int &numbrinks, bbrink_t **brinks) +{ + if (node->isleaf) + { + btreeface_l::iterator fi; + btreeedge_l::iterator ei; + for (fi = node->treeleaf->faces->begin (); fi != node->treeleaf->faces->end (); fi++) + { + for (ei = fi->f->edges->begin (); ei != fi->f->edges->end (); ei++) + { + if (ei->e->tmp_tested) + { + continue; + } + ei->e->tmp_tested = true; + if (!ei->e->infinite) + { + if (brinks != NULL) + { + brinks[numbrinks] = ei->e->brink; + brinks[numbrinks]->edge = ei->e; + for (int i = 0; i < brinks[numbrinks]->numnodes; i++) + { + bbrinknode_t *node = &(*brinks[numbrinks]->nodes)[i]; + if (node->isleaf && !node->clipnode->isleaf) + { + PrintOnce ("CollectBrinks_r: internal error: not leaf"); + hlassume (false, assume_first); + } + } + } + numbrinks++; + } + } + } + } + else + { + CollectBrinks_r (node->children[0], numbrinks, brinks); + CollectBrinks_r (node->children[1], numbrinks, brinks); + } +} + +void CollectBrinks (bbrinkinfo_t *info) +{ + info->numbrinks = 0; + ClearMarks_r (&info->clipnodes[0]); + CollectBrinks_r (&info->clipnodes[0], info->numbrinks, NULL); + hlassume (info->brinks = (bbrink_t **)malloc (info->numbrinks * sizeof (bbrink_t *)), assume_NoMemory); + info->numbrinks = 0; + ClearMarks_r (&info->clipnodes[0]); + CollectBrinks_r (&info->clipnodes[0], info->numbrinks, info->brinks); +} + +void FreeBrinks (bbrinkinfo_t *info) +{ + free (info->brinks); +} + +struct bwedge_s; +struct bsurface_s; + +typedef struct bwedge_s +{ + int content; + int nodenum; + bsurface_s *prev; + bsurface_s *next; +} +bwedge_t; + +typedef struct bsurface_s +{ + vec3_t normal; // pointing clockwise + int nodenum; + bool nodeside; + bwedge_s *prev; + bwedge_s *next; +} +bsurface_t; + +#define MAXBRINKWEDGES 64 + +typedef struct +{ + vec3_t axis; + vec3_t basenormal; + int numwedges[2]; // the front and back side of nodes[0] + bwedge_t wedges[2][MAXBRINKWEDGES]; // in counterclosewise order + bsurface_t surfaces[2][MAXBRINKWEDGES]; // the surface between two adjacent wedges +} +bcircle_t; + +bool CalculateCircle (bbrink_t *b, bcircle_t *c) +{ + VectorCopy (b->direction, c->axis); + if (!VectorNormalize (c->axis)) + { + return false; + } + VectorCopy ((*b->nodes)[0].plane->normal, c->basenormal); + + int side, i; + for (side = 0; side < 2; side++) + { + vec3_t facing; + CrossProduct (c->basenormal, c->axis, facing); + VectorScale (facing, side? -1: 1, facing); + if (VectorNormalize (facing) < 1 - 0.01) + { + return false; + } + + // sort the wedges + c->numwedges[side] = 1; + c->wedges[side][0].nodenum = (*b->nodes)[0].children[side]; + c->surfaces[side][0].nodenum = 0; + c->surfaces[side][0].nodeside = !side; + while (1) + { + for (i = 0; i < c->numwedges[side]; i++) + { + int nodenum = c->wedges[side][i].nodenum; + bbrinknode_t *node = &(*b->nodes)[nodenum]; + if (!node->isleaf) + { + memmove (&c->wedges[side][i + 1], &c->wedges[side][i], (c->numwedges[side] - i) * sizeof (bwedge_t)); + memmove (&c->surfaces[side][i + 2], &c->surfaces[side][i + 1], (c->numwedges[side] - 1 - i) * sizeof (bsurface_t)); + c->numwedges[side]++; + bool flipnode = (DotProduct (node->plane->normal, facing) < 0); + c->wedges[side][i].nodenum = node->children[flipnode]; + c->wedges[side][i + 1].nodenum = node->children[!flipnode]; + c->surfaces[side][i + 1].nodenum = nodenum; + c->surfaces[side][i + 1].nodeside = flipnode; + break; + } + } + if (i == c->numwedges[side]) + { + break; + } + } + } + if ((c->numwedges[0] + c->numwedges[1]) * 2 - 1 != b->numnodes) + { + PrintOnce ("CalculateCircle: internal error 1"); + hlassume (false, assume_first); + } + + // fill in other information + for (side = 0; side < 2; side++) + { + for (i = 0; i < c->numwedges[side]; i++) + { + bwedge_t *w = &c->wedges[side][i]; + bbrinknode_t *node = &(*b->nodes)[w->nodenum]; + if (!node->clipnode->isleaf) + { + PrintOnce ("CalculateCircle: internal error: not leaf"); + hlassume (false, assume_first); + } + w->content = node->content; + w->prev = &c->surfaces[side][i]; + w->next = (i == c->numwedges[side] - 1)? &c->surfaces[!side][0]: &c->surfaces[side][i + 1]; + w->prev->next = w; + w->next->prev = w; + } + for (i = 0; i < c->numwedges[side]; i++) + { + bsurface_t *s = &c->surfaces[side][i]; + bbrinknode_t *node = &(*b->nodes)[s->nodenum]; + VectorScale (node->plane->normal, s->nodeside? -1: 1, s->normal); + } + } + + // check the normals + for (side = 0; side < 2; side++) + { + for (i = 0; i < c->numwedges[side]; i++) + { + bwedge_t *w = &c->wedges[side][i]; + if (i == 0 && i == c->numwedges[side] - 1) // 180 degrees + { + continue; + } + vec3_t v; + CrossProduct (w->prev->normal, w->next->normal, v); + if (!VectorNormalize (v) || + DotProduct (v, c->axis) < 1 - 0.01) + { + return false; + } + } + } + return true; +} + +void PrintCircle (const bcircle_t *c) +{ + Log ("axis %f %f %f\n", c->axis[0], c->axis[1], c->axis[2]); + Log ("basenormal %f %f %f\n", c->basenormal[0], c->basenormal[1], c->basenormal[2]); + Log ("numwedges %d %d\n", c->numwedges[0], c->numwedges[1]); + for (int side = 0; side < 2; side++) + { + for (int i = 0; i < c->numwedges[side]; i++) + { + const bwedge_t *w = &c->wedges[side][i]; + const bsurface_t *s = &c->surfaces[side][i]; + Log ("surface[%d][%d] nodenum %d nodeside %d normal %f %f %f\n", side, i, s->nodenum, s->nodeside, s->normal[0], s->normal[1], s->normal[2]); + Log ("wedge[%d][%d] nodenum %d content %d\n", side, i, w->nodenum, w->content); + } + } +} + +bool AddPartition (bclipnode_t *clipnode, int planenum, bool planeside, int content, bbrinklevel_e brinktype) +{ + // make sure we won't do any harm + btreeface_l::iterator fi; + btreeedge_l::iterator ei; + int side; + if (!clipnode->isleaf) + { + return false; + } + bool onback = false; + for (fi = clipnode->treeleaf->faces->begin (); fi != clipnode->treeleaf->faces->end (); fi++) + { + for (ei = fi->f->edges->begin (); ei != fi->f->edges->end (); ei++) + { + for (side = 0; side < 2; side++) + { + btreepoint_t *tp = GetPointFromEdge (ei->e, side); + const dplane_t *plane = &g_dplanes[planenum]; + vec_t dist = DotProduct (tp->v, plane->normal) - plane->dist; + if (planeside? dist < -ON_EPSILON: dist > ON_EPSILON) + { + return false; + } + if (planeside? dist > ON_EPSILON: dist < -ON_EPSILON) + { + onback = true; + } + } + } + } + if (!onback) + { + return false; // the whole leaf is on the plane, or the leaf doesn't consist of any vertex + } + bpartition_t *p = (bpartition_t *)malloc (sizeof (bpartition_t)); + hlassume (p != NULL, assume_NoMemory); + p->next = clipnode->partitions; + p->planenum = planenum; + p->planeside = planeside; + p->content = content; + p->type = brinktype; + clipnode->partitions = p; + return true; +} + +void AnalyzeBrinks (bbrinkinfo_t *info) +{ + int countgood = 0; + int countinvalid = 0; + int countskipped = 0; + int countfixed = 0; + int i, j, side; + for (i = 0; i < info->numbrinks; i++) + { + bbrink_t *b = info->brinks[i]; + if (b->numnodes <= 5) // quickly reject the most trivial brinks + { +#ifdef HLBSP_BRINKNOTUSEDBYLEAF_FIX + if (b->numnodes != 3 && b->numnodes != 5) + { + PrintOnce ("AnalyzeBrinks: internal error 1"); + hlassume (false, assume_first); + } + // because a brink won't necessarily be split twice after its creation + if (b->numnodes == 3) + { + if (g_developer >= DEVELOPER_LEVEL_FLUFF) + { + Developer (DEVELOPER_LEVEL_FLUFF, "Brink wasn't split by the second plane:\n"); + PrintBrink (b); + } + countinvalid++; + } + else + { + countgood++; + } + continue; +#else + hlassume (b->numnodes == 5, assume_first); + countgood++; + continue; +#endif + } + + if (b->numnodes > 2 * MAXBRINKWEDGES - 1) + { + if (g_developer >= DEVELOPER_LEVEL_MEGASPAM) + { + Developer (DEVELOPER_LEVEL_MEGASPAM, "Skipping complicated brink:\n"); + PrintBrink (b); + } + countskipped++; + continue; + } + bcircle_t c; + // build the circle to find out the planes a player may move along + if (!CalculateCircle (b, &c)) + { + if (g_developer >= DEVELOPER_LEVEL_FLUFF) + { + Developer (DEVELOPER_LEVEL_FLUFF, "CalculateCircle failed for brink:\n"); + PrintBrink (b); + } + countinvalid++; + continue; + } + + int transitionfound[2]; + bsurface_t *transitionpos[2]; + bool transitionside[2]; + for (side = 0; side < 2; side++) + { + transitionfound[side] = 0; + for (j = 1; j < c.numwedges[side]; j++) // we will later consider the surfaces on the first split + { + bsurface_t *s = &c.surfaces[side][j]; + if ((s->prev->content == CONTENTS_SOLID) != (s->next->content == CONTENTS_SOLID)) + { + transitionfound[side]++; + transitionpos[side] = s; + transitionside[side] = (s->prev->content == CONTENTS_SOLID); + } + } + } + + if (transitionfound[0] == 0 || transitionfound[1] == 0) + { + // at least one side of the first split is completely SOLID or EMPTY. no bugs in this case + countgood++; + continue; + } + + if (transitionfound[0] > 1 || transitionfound[1] > 1 || + (c.surfaces[0][0].prev->content == CONTENTS_SOLID) != (c.surfaces[0][0].next->content == CONTENTS_SOLID) || + (c.surfaces[1][0].prev->content == CONTENTS_SOLID) != (c.surfaces[1][0].next->content == CONTENTS_SOLID)) + { + // there must at least 3 transition surfaces now, which is too complicated. just leave it unfixed + if (g_developer >= DEVELOPER_LEVEL_MEGASPAM) + { + Developer (DEVELOPER_LEVEL_MEGASPAM, "Skipping complicated brink:\n"); + PrintBrink (b); + PrintCircle (&c); + } + countskipped++; + continue; + } + + if (transitionside[1] != !transitionside[0]) + { + PrintOnce ("AnalyzeBrinks: internal error 2"); + hlassume (false, assume_first); + } + bool bfix = false; + bool berror = false; + vec3_t vup = {0, 0, 1}; + bool isfloor; + bool onfloor; + bool blocking; + { + isfloor = false; + for (int side2 = 0; side2 < 2; side2++) + { + vec3_t normal; + VectorScale (transitionpos[side2]->normal, transitionside[side2]? -1: 1, normal); // pointing from SOLID to EMPTY + if (DotProduct (normal, vup) > BRINK_FLOOR_THRESHOLD) + { + isfloor = true; + } + } + } + { + onfloor = false; + for (int side2 = 0; side2 < 2; side2++) + { + btreepoint_t *tp = GetPointFromEdge (b->edge, side2); +#ifdef HLBSP_BRINKHACK_BUGFIX + if (tp->infinite) + { + continue; + } +#endif + for (btreeedge_l::iterator ei = tp->edges->begin (); ei != tp->edges->end (); ei++) + { + for (btreeface_l::iterator fi = ei->e->faces->begin (); fi != ei->e->faces->end (); fi++) + { +#ifdef HLBSP_BRINKHACK_BUGFIX + if (fi->f->infinite || GetLeafFromFace (fi->f, false)->infinite || GetLeafFromFace (fi->f, true)->infinite) + { + PrintOnce ("AnalyzeBrinks: internal error: an infinite object contains a finite object"); + hlassume (false, assume_first); + } +#endif + for (int side3 = 0; side3 < 2; side3++) + { + vec3_t normal; + VectorScale (fi->f->plane->normal, (fi->f->planeside != (bool)side3)? -1: 1, normal); + if (DotProduct (normal, vup) > BRINK_FLOOR_THRESHOLD + && GetLeafFromFace (fi->f, side3)->clipnode->content == CONTENTS_SOLID + && GetLeafFromFace (fi->f, !side3)->clipnode->content != CONTENTS_SOLID) + { + onfloor = true; + } + } + } + } + } + } + // this code does not fix all the bugs, it only aims to fix most of the bugs + for (side = 0; side < 2; side++) + { + bsurface_t *smovement = transitionpos[side]; + bsurface_t *s; + for (s = transitionside[!side]? &c.surfaces[!side][0]: &c.surfaces[side][0]; ; s = transitionside[!side]? s->next->next: s->prev->prev) + { + bwedge_t *w = transitionside[!side]? s->next: s->prev; + bsurface_t *snext = transitionside[!side]? w->next: w->prev; + vec3_t tmp; + vec_t dot; + CrossProduct (smovement->normal, snext->normal, tmp); + dot = DotProduct (tmp, c.axis); + if (transitionside[!side]? dot < 0.01: dot > -0.01) + { + break; + } + if (w->content != CONTENTS_SOLID) + { + break; + } + if (snext == (transitionside[!side]? &c.surfaces[side][0]: &c.surfaces[!side][0])) + { + Developer (DEVELOPER_LEVEL_ERROR, "AnalyzeBrinks: surface past 0\n"); + break; + } + bfix = true; + { + if (DotProduct (smovement->normal, s->normal) > 0.01) + { + blocking = false; + } + else + { + blocking = true; + } + } + bclipnode_t *clipnode = (*b->nodes)[w->nodenum].clipnode; + int planenum = (*b->nodes)[smovement->nodenum].planenum; + bool planeside = transitionside[!side]? smovement->nodeside: !smovement->nodeside; + bbrinklevel_e brinktype; + brinktype = isfloor? (blocking? BrinkFloorBlocking: BrinkFloor): onfloor? (blocking? BrinkWallBlocking: BrinkWall): BrinkAny; + if (!AddPartition (clipnode, planenum, planeside, CONTENTS_EMPTY, brinktype)) + { + berror = true; + } + } + } + if (berror) + { + if (g_developer >= DEVELOPER_LEVEL_FLUFF) + { + Developer (DEVELOPER_LEVEL_FLUFF, "AddPartition failed for brink:\n"); + PrintBrink (b); + } + countinvalid++; + } + else if (!bfix) + { + countgood++; + } + else + { + countfixed++; + } + } + Developer (DEVELOPER_LEVEL_MESSAGE, "brinks: good = %d skipped = %d fixed = %d invalid = %d\n", countgood, countskipped, countfixed, countinvalid); +} + +void DeleteClipnodes (bbrinkinfo_t *info) +{ + for (int i = 0; i < info->numclipnodes; i++) + { +#ifdef HLBSP_BRINKHACK_BUGFIX + if (!info->clipnodes[i].isleaf) + { + continue; + } +#endif + bpartition_t *p; + while ((p = info->clipnodes[i].partitions) != NULL) + { + info->clipnodes[i].partitions = p->next; + free (p); + } + } + free (info->clipnodes); +} + +void SortPartitions (bbrinkinfo_t *info) // to merge same partition planes and compress clipnodes better if using HLBSP_MERGECLIPNODE +{ + int countfloorblocking = 0; + int countfloor = 0; + int countwallblocking = 0; + int countwall = 0; + int countany = 0; + for (int i = 0; i < info->numclipnodes; i++) + { + bclipnode_t *clipnode = &info->clipnodes[i]; + if (!clipnode->isleaf) + { + continue; + } + bpartition_t *current, **pp, *partitions; + partitions = clipnode->partitions; + clipnode->partitions = NULL; + while ((current = partitions) != NULL) + { + partitions = current->next; + if (current->content != CONTENTS_EMPTY) + { + PrintOnce ("SortPartitions: content of partition was not empty."); + hlassume (false, assume_first); + } + for (pp = &clipnode->partitions; *pp; pp = &(*pp)->next) + { + if ((*pp)->planenum > current->planenum || + (*pp)->planenum == current->planenum && (*pp)->planeside >= current->planeside) // normally the planeside should be identical + { + break; + } + } + if (*pp && (*pp)->planenum == current->planenum && (*pp)->planeside == current->planeside) + { + (*pp)->type = qmin ((*pp)->type, current->type); // pick the lowest (most important) level from the existing partition and the current partition + free (current); + continue; + } + switch (current->type) + { + case BrinkFloorBlocking: + countfloorblocking++; + break; + case BrinkFloor: + countfloor++; + break; + case BrinkWallBlocking: + countwallblocking++; + break; + case BrinkWall: + countwall++; + break; + case BrinkAny: + countany++; + break; + default: + PrintOnce ("SortPartitions: internal error"); + hlassume (false, assume_first); + break; + } + current->next = *pp; + *pp = current; + } + } + Developer (DEVELOPER_LEVEL_MESSAGE, "partitions: floorblocking = %d floor = %d wallblocking = %d wall = %d any = %d\n", countfloorblocking, countfloor, countwallblocking, countwall, countany); +} + +void *CreateBrinkinfo (const dclipnode_t *clipnodes, int headnode) +{ + bbrinkinfo_t *info; + try + { + hlassume (info = (bbrinkinfo_t *)malloc (sizeof (bbrinkinfo_t)), assume_NoMemory); + ExpandClipnodes (info, clipnodes, headnode); + BuildTreeCells (info); + CollectBrinks (info); + AnalyzeBrinks (info); + FreeBrinks (info); + DeleteTreeCells (info); + SortPartitions (info); + } + catch (std::bad_alloc) + { + hlassume (false, assume_NoMemory); + } + return info; +} + +#ifdef HLBSP_MERGECLIPNODE +extern int count_mergedclipnodes; +typedef std::map< std::pair< int, std::pair< int, int > >, int > clipnodemap_t; +inline clipnodemap_t::key_type MakeKey (const dclipnode_t &c) +{ + return std::make_pair (c.planenum, std::make_pair (c.children[0], c.children[1])); +} +#endif + +bool FixBrinks_r_r (const bclipnode_t *clipnode, const bpartition_t *p, bbrinklevel_e level, int &headnode_out, dclipnode_t *begin, dclipnode_t *end, dclipnode_t *¤t +#ifdef HLBSP_MERGECLIPNODE + , clipnodemap_t *outputmap +#endif + ) +{ + while (p && p->type > level) + { + p = p->next; + } + if (p == NULL) + { + headnode_out = clipnode->content; + return true; + } + dclipnode_t *cn; +#ifdef HLBSP_MERGECLIPNODE + dclipnode_t tmpclipnode; + cn = &tmpclipnode; + dclipnode_t *c = current; + current++; +#else + if (current >= end) + { + return false; + } + cn = current; + current++; +#endif + cn->planenum = p->planenum; + cn->children[p->planeside] = p->content; + int r; + if (!FixBrinks_r_r (clipnode, p->next, level, r, begin, end, current +#ifdef HLBSP_MERGECLIPNODE + , outputmap +#endif + )) + { + return false; + } + cn->children[!p->planeside] = r; +#ifdef HLBSP_MERGECLIPNODE + clipnodemap_t::iterator output; + output = outputmap->find (MakeKey (*cn)); + if (g_noclipnodemerge || output == outputmap->end ()) + { + if (c >= end) + { + return false; + } + *c = *cn; + (*outputmap)[MakeKey (*cn)] = c - begin; + headnode_out = c - begin; + } + else + { + count_mergedclipnodes++; + if (current != c + 1) + { + Error ("Merge clipnodes: internal error"); + } + current = c; + headnode_out = output->second; // use the existing clipnode + } +#else + headnode_out = cn - begin; +#endif + return true; +} + +bool FixBrinks_r (const bclipnode_t *clipnode, bbrinklevel_e level, int &headnode_out, dclipnode_t *begin, dclipnode_t *end, dclipnode_t *¤t +#ifdef HLBSP_MERGECLIPNODE + , clipnodemap_t *outputmap +#endif + ) +{ + if (clipnode->isleaf) + { + return FixBrinks_r_r (clipnode, clipnode->partitions, level, headnode_out, begin, end, current +#ifdef HLBSP_MERGECLIPNODE + , outputmap +#endif + ); + } + else + { + dclipnode_t *cn; +#ifdef HLBSP_MERGECLIPNODE + dclipnode_t tmpclipnode; + cn = &tmpclipnode; + dclipnode_t *c = current; + current++; +#else + if (current >= end) + { + return false; + } + cn = current; + current++; +#endif + cn->planenum = clipnode->planenum; + for (int k = 0; k < 2; k++) + { + int r; + if (!FixBrinks_r (clipnode->children[k], level, r, begin, end, current +#ifdef HLBSP_MERGECLIPNODE + , outputmap +#endif + )) + { + return false; + } + cn->children[k] = r; + } +#ifdef HLBSP_MERGECLIPNODE + clipnodemap_t::iterator output; + output = outputmap->find (MakeKey (*cn)); + if (g_noclipnodemerge || output == outputmap->end ()) + { + if (c >= end) + { + return false; + } + *c = *cn; + (*outputmap)[MakeKey (*cn)] = c - begin; + headnode_out = c - begin; + } + else + { + count_mergedclipnodes++; + if (current != c + 1) + { + Error ("Merge clipnodes: internal error"); + } + current = c; + headnode_out = output->second; // use existing clipnode + } +#else + headnode_out = cn - begin; +#endif + return true; + } +} + +bool FixBrinks (const void *brinkinfo, bbrinklevel_e level, int &headnode_out, dclipnode_t *clipnodes_out, int maxsize, int size, int &size_out) +{ + const bbrinkinfo_t *info = (const bbrinkinfo_t *)brinkinfo; + dclipnode_t *begin = clipnodes_out; + dclipnode_t *end = &clipnodes_out[maxsize]; + dclipnode_t *current = &clipnodes_out[size]; +#ifdef HLBSP_MERGECLIPNODE + clipnodemap_t outputmap; +#endif + int r; + if (!FixBrinks_r (&info->clipnodes[0], level, r, begin, end, current +#ifdef HLBSP_MERGECLIPNODE + , &outputmap +#endif + )) + { + return false; + } + headnode_out = r; + size_out = current - begin; + return true; +} + +void DeleteBrinkinfo (void *brinkinfo) +{ + bbrinkinfo_t *info = (bbrinkinfo_t *)brinkinfo; + DeleteClipnodes (info); + free (info); +} + +#endif diff --git a/src/zhlt-vluzacn/hlbsp/bsp5.h b/src/zhlt-vluzacn/hlbsp/bsp5.h new file mode 100644 index 0000000..ec86cd1 --- /dev/null +++ b/src/zhlt-vluzacn/hlbsp/bsp5.h @@ -0,0 +1,400 @@ +#ifndef HLBSP_H__ +#define HLBSP_H__ + +#if _MSC_VER >= 1000 +#pragma once +#endif + +#include "cmdlib.h" +#include "messages.h" +#include "win32fix.h" +#include "log.h" +#include "hlassert.h" +#include "mathlib.h" +#include "bspfile.h" +#include "blockmem.h" +#include "filelib.h" +#include "threads.h" +#include "winding.h" +#ifdef ZHLT_PARAMFILE +#include "cmdlinecfg.h" +#endif + +#define ENTITIES_VOID "entities.void" +#define ENTITIES_VOID_EXT ".void" + +#ifdef ZHLT_LARGERANGE +#define BOGUS_RANGE 144000 +#else +#define BOGUS_RANGE 18000 +#endif + +// the exact bounding box of the brushes is expanded some for the headnode +// volume. is this still needed? +#define SIDESPACE 24 + +//============================================================================ + +#define MIN_SUBDIVIDE_SIZE 64 + +#ifdef ZHLT_GENERAL +#define MAX_SUBDIVIDE_SIZE 512 +#else +#define MAX_SUBDIVIDE_SIZE 240 +#endif + +#define DEFAULT_SUBDIVIDE_SIZE ((MAX_SURFACE_EXTENT-1)*TEXTURE_STEP) //#define DEFAULT_SUBDIVIDE_SIZE 240 //--vluzacn + +#define MIN_MAXNODE_SIZE 64 +#ifdef ZHLT_LARGERANGE +#define MAX_MAXNODE_SIZE 65536 +#else +#define MAX_MAXNODE_SIZE 8192 +#endif +#define DEFAULT_MAXNODE_SIZE 1024 + +#define DEFAULT_NOFILL false +#ifdef HLBSP_FILL +#define DEFAULT_NOINSIDEFILL false +#endif +#define DEFAULT_NOTJUNC false +#ifdef HLBSP_BRINKHACK +#define DEFAULT_NOBRINK false +#endif +#define DEFAULT_NOCLIP false +#define DEFAULT_NOOPT false +#ifdef HLBSP_MERGECLIPNODE +#define DEFAULT_NOCLIPNODEMERGE false +#endif +#define DEFAULT_LEAKONLY false +#define DEFAULT_WATERVIS false +#define DEFAULT_CHART false +#define DEFAULT_INFO true + +#ifdef ZHLT_NULLTEX // AJM +#define DEFAULT_NULLTEX true +#endif + +#ifdef ZHLT_PROGRESSFILE // AJM +#define DEFAULT_PROGRESSFILE NULL // progress file is only used if g_progressfile is non-null +#endif + +#ifdef SYSTEM_WIN32 +#define DEFAULT_ESTIMATE false +#endif + +#ifdef SYSTEM_POSIX +#define DEFAULT_ESTIMATE true +#endif + +#ifdef ZHLT_DETAIL // AJM +#define DEFAULT_DETAIL true +#endif + +#define MAXEDGES 48 // 32 +#define MAXPOINTS 28 // don't let a base face get past this + // because it can be split more later +#define MAXNODESIZE 1024 // Valve default is 1024 + +typedef enum +{ + face_normal = 0, + face_hint, + face_skip, +#ifdef ZHLT_NULLTEX // AJM + face_null, +#endif +#ifdef ZHLT_DETAIL // AJM + face_detail +#endif +#ifdef HLCSG_HLBSP_SOLIDHINT + face_discardable, // contents must not differ between front and back +#endif +} +facestyle_e; + +typedef struct face_s // This structure is layed out so 'pts' is on a quad-word boundary (and the pointers are as well) +{ + struct face_s* next; + int planenum; + int texturenum; + int contents; // contents in front of face +#ifdef ZHLT_DETAILBRUSH + int detaillevel; // defined by hlcsg + int *outputedges; // used in WriteDrawNodes +#endif + + struct face_s* original; // face on node + int outputnumber; // only valid for original faces after write surfaces + int numpoints; + facestyle_e facestyle; +#ifdef HLBSP_REMOVECOVEREDFACES + int referenced; // only valid for original faces +#endif + + // vector quad word aligned + vec3_t pts[MAXEDGES]; // FIXME: change to use winding_t + +} +face_t; + +typedef struct surface_s +{ + struct surface_s* next; + int planenum; + vec3_t mins, maxs; + struct node_s* onnode; // true if surface has already been used + // as a splitting node + face_t* faces; // links to all the faces on either side of the surf +#ifdef ZHLT_DETAILBRUSH + int detaillevel; // minimum detail level of its faces +#endif +} +surface_t; + +typedef struct +{ + vec3_t mins, maxs; + surface_t* surfaces; +} +surfchain_t; + +#ifdef ZHLT_DETAILBRUSH +typedef struct side_s +{ + struct side_s *next; + dplane_t plane; // facing inside (reversed when loading brush file) + Winding *w; // (also reversed) +} +side_t; + +typedef struct brush_s +{ + struct brush_s *next; + side_t *sides; +} +brush_t; + +#endif +// +// there is a node_t structure for every node and leaf in the bsp tree +// +#define PLANENUM_LEAF -1 +#ifdef HLBSP_DETAILBRUSH_CULL +#define BOUNDS_EXPANSION 1.0 // expand the bounds of detail leafs when clipping its boundsbrush, to prevent some strange brushes in the func_detail from clipping away the entire boundsbrush making the func_detail invisible. +#endif + +typedef struct node_s +{ + surface_t* surfaces; +#ifdef ZHLT_DETAILBRUSH + brush_t *detailbrushes; +#ifdef HLBSP_DETAILBRUSH_CULL + brush_t *boundsbrush; + vec3_t loosemins, loosemaxs; // all leafs and nodes have this, while 'mins' and 'maxs' are only valid for nondetail leafs and nodes. +#endif +#endif + +#ifdef ZHLT_DETAILBRUSH + bool isdetail; // is under a diskleaf + bool isportalleaf; // not detail and children are detail; only visleafs have contents, portals, mins, maxs + bool iscontentsdetail; // inside a detail brush +#endif + vec3_t mins, maxs; // bounding volume of portals; + + // information for decision nodes + int planenum; // -1 = leaf node + struct node_s* children[2]; // only valid for decision nodes + face_t* faces; // decision nodes only, list for both sides + + // information for leafs + int contents; // leaf nodes (0 for decision nodes) + face_t** markfaces; // leaf nodes only, point to node faces + struct portal_s* portals; + int visleafnum; // -1 = solid + int valid; // for flood filling + int occupied; // light number in leaf for outside filling +#ifdef HLBSP_FILL + int empty; +#endif +} +node_t; + +#define NUM_HULLS 4 + +//============================================================================= +// solidbsp.c +extern void SubdivideFace(face_t* f, face_t** prevptr); +extern node_t* SolidBSP(const surfchain_t* const surfhead, +#ifdef ZHLT_DETAILBRUSH + brush_t *detailbrushes, +#endif + bool report_progress); + +//============================================================================= +// merge.c +extern void MergePlaneFaces(surface_t* plane); +extern void MergeAll(surface_t* surfhead); + +//============================================================================= +// surfaces.c +extern void MakeFaceEdges(); +extern int GetEdge(const vec3_t p1, const vec3_t p2, face_t* f); + +//============================================================================= +// portals.c +typedef struct portal_s +{ + dplane_t plane; + node_t* onnode; // NULL = outside box + node_t* nodes[2]; // [0] = front side of plane + struct portal_s* next[2]; + Winding* winding; +} +portal_t; + +extern node_t g_outside_node; // portals outside the world face this + +extern void AddPortalToNodes(portal_t* p, node_t* front, node_t* back); +extern void RemovePortalFromNode(portal_t* portal, node_t* l); +extern void MakeHeadnodePortals(node_t* node, const vec3_t mins, const vec3_t maxs); + +extern void FreePortals(node_t* node); +extern void WritePortalfile(node_t* headnode); + +//============================================================================= +// tjunc.c +void tjunc(node_t* headnode); + +//============================================================================= +// writebsp.c +extern void WriteClipNodes(node_t* headnode); +extern void WriteDrawNodes(node_t* headnode); + +extern void BeginBSPFile(); +extern void FinishBSPFile(); + +//============================================================================= +// outside.c +extern node_t* FillOutside(node_t* node, bool leakfile, unsigned hullnum); +extern void LoadAllowableOutsideList(const char* const filename); +extern void FreeAllowableOutsideList(); +#ifdef HLBSP_FILL +extern void FillInside (node_t* node); +#endif + +//============================================================================= +// misc functions +extern void GetParamsFromEnt(entity_t* mapent); + +extern face_t* AllocFace(); +extern void FreeFace(face_t* f); + +extern struct portal_s* AllocPortal(); +extern void FreePortal(struct portal_s* p); + +extern surface_t* AllocSurface(); +extern void FreeSurface(surface_t* s); + +#ifdef ZHLT_DETAILBRUSH +extern side_t * AllocSide (); +extern void FreeSide (side_t *s); +extern side_t * NewSideFromSide (const side_t *s); +extern brush_t *AllocBrush (); +extern void FreeBrush (brush_t *b); +extern brush_t *NewBrushFromBrush (const brush_t *b); +extern void SplitBrush (brush_t *in, const dplane_t *split, brush_t **front, brush_t **back); +#ifdef HLBSP_DETAILBRUSH_CULL +extern brush_t *BrushFromBox (const vec3_t mins, const vec3_t maxs); +extern void CalcBrushBounds (const brush_t *b, vec3_t &mins, vec3_t &maxs); +#endif +#endif + +extern node_t* AllocNode(); + +extern bool CheckFaceForHint(const face_t* const f); +extern bool CheckFaceForSkip(const face_t* const f); +#ifdef ZHLT_NULLTEX// AJM +extern bool CheckFaceForNull(const face_t* const f); +#endif +#ifdef HLCSG_HLBSP_SOLIDHINT +extern bool CheckFaceForDiscardable (const face_t *f); +#endif +#ifdef HLBSP_BRINKHACK +#define BRINK_FLOOR_THRESHOLD 0.7 +typedef enum +{ + BrinkNone = 0, + BrinkFloorBlocking, + BrinkFloor, + BrinkWallBlocking, + BrinkWall, + BrinkAny, +} bbrinklevel_e; +extern void *CreateBrinkinfo (const dclipnode_t *clipnodes, int headnode); +extern bool FixBrinks (const void *brinkinfo, bbrinklevel_e level, int &headnode_out, dclipnode_t *clipnodes_out, int maxsize, int size, int &size_out); +extern void DeleteBrinkinfo (void *brinkinfo); +#endif + + +// ===================================================================================== +//Cpt_Andrew - UTSky Check +// ===================================================================================== +extern bool CheckFaceForEnv_Sky(const face_t* const f); +// ===================================================================================== + + +#ifdef ZHLT_DETAIL // AJM +extern bool CheckFaceForDetail(const face_t* const f); +#endif + +//============================================================================= +// cull.c +extern void CullStuff(); + +//============================================================================= +// qbsp.c +extern bool g_nofill; +#ifdef HLBSP_FILL +extern bool g_noinsidefill; +#endif +extern bool g_notjunc; +#ifdef HLBSP_BRINKHACK +extern bool g_nobrink; +#endif +#ifdef HLBSP_MERGECLIPNODE +extern bool g_noclipnodemerge; +#endif +extern bool g_watervis; +extern bool g_chart; +extern bool g_estimate; +extern int g_maxnode_size; +extern int g_subdivide_size; +extern int g_hullnum; +extern bool g_bLeakOnly; +extern bool g_bLeaked; +extern char g_portfilename[_MAX_PATH]; +extern char g_pointfilename[_MAX_PATH]; +extern char g_linefilename[_MAX_PATH]; +extern char g_bspfilename[_MAX_PATH]; +#ifdef ZHLT_64BIT_FIX +extern char g_extentfilename[_MAX_PATH]; +#endif + + +#ifdef ZHLT_DETAIL // AJM +extern bool g_bDetailBrushes; +#endif + +#ifdef ZHLT_NULLTEX // AJM +extern bool g_bUseNullTex; +#endif + +#ifdef HLBSP_REMOVEHULL2 +extern bool g_nohull2; +#endif + +extern face_t* NewFaceFromFace(const face_t* const in); +extern void SplitFace(face_t* in, const dplane_t* const split, face_t** front, face_t** back); + +#endif // qbsp.c====================================================================== HLBSP_H__ diff --git a/src/zhlt-vluzacn/hlbsp/hlbsp.vcproj b/src/zhlt-vluzacn/hlbsp/hlbsp.vcproj new file mode 100644 index 0000000..ab47e0a --- /dev/null +++ b/src/zhlt-vluzacn/hlbsp/hlbsp.vcproj @@ -0,0 +1,385 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/zhlt-vluzacn/hlbsp/hlbsp.vcxproj b/src/zhlt-vluzacn/hlbsp/hlbsp.vcxproj new file mode 100644 index 0000000..dae1293 --- /dev/null +++ b/src/zhlt-vluzacn/hlbsp/hlbsp.vcxproj @@ -0,0 +1,179 @@ + + + + + Release + Win32 + + + Release + x64 + + + + + + {E75CEB5E-EBAE-1E7D-A626-81604E140A5F} + + + + Application + false + MultiByte + v140 + + + Application + false + MultiByte + v140 + + + + + + + + + + + + + + + .\Release\ + .\Release\ + false + + + .\Release_x64\ + .\Release_x64\ + false + + + + MultiThreaded + Default + true + true + MaxSpeed + true + Level3 + ..\common;..\template;%(AdditionalIncludeDirectories) + HLBSP;VERSION_32BIT;NDEBUG;DOUBLEVEC_T;WIN32;_CONSOLE;SYSTEM_WIN32;STDC_HEADERS;%(PreprocessorDefinitions) + .\Release\ + true + .\Release\hlbsp.pch + .\Release\ + .\Release\ + true + true + + + .\Release\hlbsp.tlb + + + 0x0409 + NDEBUG;%(PreprocessorDefinitions) + + + true + .\Release\hlbsp.bsc + + + true + Console + false + .\Release\hlbsp.exe + binmode.obj;%(AdditionalDependencies) + 4194304 + 1048576 + false + + + + + MultiThreaded + Default + true + true + MaxSpeed + true + Level3 + ..\common;..\template;%(AdditionalIncludeDirectories) + HLBSP;VERSION_64BIT;NDEBUG;DOUBLEVEC_T;WIN32;_CONSOLE;SYSTEM_WIN32;STDC_HEADERS;%(PreprocessorDefinitions) + .\Release_x64\ + true + .\Release_x64\hlbsp.pch + .\Release_x64\ + .\Release_x64\ + true + true + + + .\Release_x64\hlbsp.tlb + + + 0x0409 + NDEBUG;%(PreprocessorDefinitions) + + + true + .\Release_x64\hlbsp.bsc + + + true + Console + false + .\Release_x64\hlbsp.exe + binmode.obj;%(AdditionalDependencies) + 4194304 + 1048576 + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/zhlt-vluzacn/hlbsp/hlbsp.vcxproj.filters b/src/zhlt-vluzacn/hlbsp/hlbsp.vcxproj.filters new file mode 100644 index 0000000..df0da79 --- /dev/null +++ b/src/zhlt-vluzacn/hlbsp/hlbsp.vcxproj.filters @@ -0,0 +1,135 @@ + + + + + {34a14629-6c37-476f-ba39-948b5dced614} + cpp;c;cxx;rc;def;r;odl;idl;hpj;bat;for;f90 + + + {46b3a3bf-b611-48da-acfe-72baee07a4a2} + + + {787e705b-96d4-4178-b5d0-07e4b95ccb7c} + h;hpp;hxx;hm;inl;fi;fd + + + {47e82a67-8aea-4014-84d1-7495aceb84c2} + ico;cur;bmp;dlg;rc2;rct;bin;cnt;rtf;gif;jpg;jpeg;jpe + + + + + Source Files\common + + + Source Files\common + + + Source Files\common + + + Source Files\common + + + Source Files\common + + + Source Files\common + + + Source Files\common + + + Source Files\common + + + Source Files\common + + + Source Files\common + + + Source Files\common + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source 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 + + + \ No newline at end of file diff --git a/src/zhlt-vluzacn/hlbsp/merge.cpp b/src/zhlt-vluzacn/hlbsp/merge.cpp new file mode 100644 index 0000000..eae078a --- /dev/null +++ b/src/zhlt-vluzacn/hlbsp/merge.cpp @@ -0,0 +1,286 @@ +#include "bsp5.h" + +// TryMerge +// MergeFaceToList +// FreeMergeListScraps +// MergePlaneFaces +// MergeAll + +#define CONTINUOUS_EPSILON ON_EPSILON + +// ===================================================================================== +// 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. +// ===================================================================================== +static face_t* TryMerge(face_t* f1, face_t* f2) +{ + vec_t* p1; + vec_t* p2; + vec_t* p3; + vec_t* p4; + vec_t* back; + face_t* newf; + int i; + int j; + int k; + int l; + vec3_t normal; + vec3_t delta; + vec3_t planenormal; + vec_t dot; + dplane_t* plane; + bool keep1; + bool keep2; + + if (f1->numpoints == -1 || f2->numpoints == -1) + { + return NULL; + } + if (f1->texturenum != f2->texturenum) + { + return NULL; + } + if (f1->contents != f2->contents) + { + return NULL; + } +#ifdef HLBSP_TryMerge_PLANENUM_FIX + if (f1->planenum != f2->planenum) + { + return NULL; + } + if (f1->facestyle != f2->facestyle) + { + return NULL; + } +#endif +#ifdef ZHLT_DETAILBRUSH + if (f1->detaillevel != f2->detaillevel) + { + return NULL; + } +#endif + + // + // find a common edge + // + p1 = p2 = NULL; // shut up the compiler + j = 0; + + for (i = 0; i < f1->numpoints; i++) + { + p1 = f1->pts[i]; + p2 = f1->pts[(i + 1) % f1->numpoints]; + for (j = 0; j < f2->numpoints; j++) + { + p3 = f2->pts[j]; + p4 = f2->pts[(j + 1) % f2->numpoints]; + for (k = 0; k < 3; k++) + { +#ifdef HLBSP_TryMerge_PRECISION_FIX + if (fabs(p1[k] - p4[k]) > ON_EPSILON) + { + break; + } + if (fabs(p2[k] - p3[k]) > ON_EPSILON) + { + break; + } +#else + if (fabs(p1[k] - p4[k]) > EQUAL_EPSILON) + { + break; + } + if (fabs(p2[k] - p3[k]) > EQUAL_EPSILON) + { + break; + } +#endif + } + 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 + // + plane = &g_dplanes[f1->planenum]; + VectorCopy(plane->normal, planenormal); + + back = f1->pts[(i + f1->numpoints - 1) % f1->numpoints]; + VectorSubtract(p1, back, delta); + CrossProduct(planenormal, delta, normal); + VectorNormalize(normal); + + back = f2->pts[(j + 2) % f2->numpoints]; + VectorSubtract(back, p1, delta); + dot = DotProduct(delta, normal); + if (dot > CONTINUOUS_EPSILON) + { + return NULL; // not a convex polygon + } + keep1 = dot < -CONTINUOUS_EPSILON; + + back = f1->pts[(i + 2) % f1->numpoints]; + VectorSubtract(back, p2, delta); + CrossProduct(planenormal, delta, normal); + VectorNormalize(normal); + + back = f2->pts[(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 = dot < -CONTINUOUS_EPSILON; + + // + // build the new polygon + // + if (f1->numpoints + f2->numpoints > MAXEDGES) + { + // Error ("TryMerge: too many edges!"); + return NULL; + } + + newf = NewFaceFromFace(f1); + + // 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->pts[k], newf->pts[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->pts[l], newf->pts[newf->numpoints]); + newf->numpoints++; + } + + return newf; +} + +// ===================================================================================== +// MergeFaceToList +// ===================================================================================== +static face_t* MergeFaceToList(face_t* face, face_t* list) +{ + face_t* newf; + face_t* f; + + for (f = list; f; f = f->next) + { + //CheckColinear (f); + newf = TryMerge(face, f); + if (!newf) + { + continue; + } + FreeFace(face); + f->numpoints = -1; // merged out + return MergeFaceToList(newf, list); + } + + // didn't merge, so add at start + face->next = list; + return face; +} + +// ===================================================================================== +// FreeMergeListScraps +// ===================================================================================== +static face_t* FreeMergeListScraps(face_t* merged) +{ + face_t* head; + face_t* next; + + head = NULL; + for (; merged; merged = next) + { + next = merged->next; + if (merged->numpoints == -1) + { + FreeFace(merged); + } + else + { + merged->next = head; + head = merged; + } + } + + return head; +} + +// ===================================================================================== +// MergePlaneFaces +// ===================================================================================== +void MergePlaneFaces(surface_t* plane) +{ + face_t* f1; + face_t* next; + face_t* merged; + + merged = NULL; + + for (f1 = plane->faces; f1; f1 = next) + { + next = f1->next; + merged = MergeFaceToList(f1, merged); + } + + // chain all of the non-empty faces to the plane + plane->faces = FreeMergeListScraps(merged); +} + +// ===================================================================================== +// MergeAll +// ===================================================================================== +void MergeAll(surface_t* surfhead) +{ + surface_t* surf; + int mergefaces; + face_t* f; + + Verbose("---- MergeAll ----\n"); + + mergefaces = 0; + for (surf = surfhead; surf; surf = surf->next) + { + MergePlaneFaces(surf); + for (f = surf->faces; f; f = f->next) + { + mergefaces++; + } + } + + Verbose("%i mergefaces\n", mergefaces); +} diff --git a/src/zhlt-vluzacn/hlbsp/outside.cpp b/src/zhlt-vluzacn/hlbsp/outside.cpp new file mode 100644 index 0000000..68c6820 --- /dev/null +++ b/src/zhlt-vluzacn/hlbsp/outside.cpp @@ -0,0 +1,739 @@ +#pragma warning(disable: 4267) // 'size_t' to 'unsigned int', possible loss of data + +#include "bsp5.h" + +// PointInLeaf +// PlaceOccupant +// MarkLeakTrail +// RecursiveFillOutside +// ClearOutFaces_r +// isClassnameAllowableOutside +// FreeAllowableOutsideList +// LoadAllowableOutsideList +// FillOutside + +static int outleafs; +static int valid; +static int c_falsenodes; +static int c_free_faces; +static int c_keep_faces; + +// ===================================================================================== +// PointInLeaf +// ===================================================================================== +static node_t* PointInLeaf(node_t* node, const vec3_t point) +{ + vec_t d; + +#ifdef ZHLT_DETAILBRUSH + if (node->isportalleaf) +#else + if (node->contents) +#endif + { + //Log("PointInLeaf::node->contents == %i\n", node->contents); + return node; + } + + d = DotProduct(g_dplanes[node->planenum].normal, point) - g_dplanes[node->planenum].dist; + + if (d > 0) + return PointInLeaf(node->children[0], point); + + return PointInLeaf(node->children[1], point); +} + +// ===================================================================================== +// PlaceOccupant +// ===================================================================================== +static bool PlaceOccupant(const int num, const vec3_t point, node_t* headnode) +{ + node_t* n; + + n = PointInLeaf(headnode, point); + if (n->contents == CONTENTS_SOLID) + { + return false; + } + //Log("PlaceOccupant::n->contents == %i\n", n->contents); + + n->occupied = num; + return true; +} + +// ===================================================================================== +// MarkLeakTrail +// ===================================================================================== +static portal_t* prevleaknode; +static FILE* pointfile; +static FILE* linefile; + +static void MarkLeakTrail(portal_t* n2) +{ + int i; + vec3_t p1, p2, dir; + float len; + portal_t* n1; + + n1 = prevleaknode; + prevleaknode = n2; + + if (!n1) + { + return; + } + +#ifdef HLBSP_MarkLeakTrail_FIX + n1->winding->getCenter(p1); + n2->winding->getCenter(p2); +#else + n2->winding->getCenter(p1); + n1->winding->getCenter(p2); +#endif + + // Linefile + fprintf(linefile, "%f %f %f - %f %f %f\n", p1[0], p1[1], p1[2], p2[0], p2[1], p2[2]); + + // Pointfile + fprintf(pointfile, "%f %f %f\n", p1[0], p1[1], p1[2]); + + VectorSubtract(p2, p1, dir); + len = VectorLength(dir); + VectorNormalize(dir); + + while (len > 2) + { + fprintf(pointfile, "%f %f %f\n", p1[0], p1[1], p1[2]); + for (i = 0; i < 3; i++) + p1[i] += dir[i] * 2; + len -= 2; + } +} + +// ===================================================================================== +// RecursiveFillOutside +// Returns true if an occupied leaf is reached +// If fill is false, just check, don't fill +// ===================================================================================== +#ifdef ZHLT_DETAILBRUSH +static void FreeDetailNode_r (node_t *n) +{ + int i; + if (n->planenum == -1) + { + if (!(n->isportalleaf && n->contents == CONTENTS_SOLID)) + { + free (n->markfaces); + n->markfaces = NULL; + } + return; + } + for (i = 0; i < 2; i++) + { + FreeDetailNode_r (n->children[i]); + free (n->children[i]); + n->children[i] = NULL; + } + face_t *f, *next; + for (f = n->faces; f; f = next) + { + next = f->next; + FreeFace (f); + } + n->faces = NULL; +} +static void FillLeaf (node_t *l) +{ + if (!l->isportalleaf) + { + Warning ("FillLeaf: not leaf"); + return; + } + if (l->contents == CONTENTS_SOLID) + { + Warning ("FillLeaf: fill solid"); + return; + } + FreeDetailNode_r (l); + l->contents = CONTENTS_SOLID; + l->planenum = -1; +} +#endif +static int hit_occupied; +static int backdraw; +static bool RecursiveFillOutside(node_t* l, const bool fill) +{ + portal_t* p; + int s; + + if ((l->contents == CONTENTS_SOLID) || (l->contents == CONTENTS_SKY) +#ifdef ZHLT_DETAIL + || (l->contents == CONTENTS_DETAIL) +#endif + ) + { + /*if (l->contents != CONTENTS_SOLID) + Log("RecursiveFillOutside::l->contents == %i \n", l->contents);*/ + + return false; + } + + if (l->valid == valid) + { + return false; + } + + if (l->occupied) + { + hit_occupied = l->occupied; + backdraw = 1000; + return true; + } + + l->valid = valid; + + // fill it and it's neighbors + if (fill) + { +#ifdef ZHLT_DETAILBRUSH + FillLeaf (l); +#else + l->contents = CONTENTS_SOLID; + l->planenum = -1; +#endif + } + outleafs++; + + for (p = l->portals; p;) + { + s = (p->nodes[0] == l); + + if (RecursiveFillOutside(p->nodes[s], fill)) + { // leaked, so stop filling + if (backdraw-- > 0) + { + MarkLeakTrail(p); + } + return true; + } + p = p->next[!s]; + } + + return false; +} + +// ===================================================================================== +// ClearOutFaces_r +// Removes unused nodes +// ===================================================================================== +#ifdef ZHLT_DETAILBRUSH +static void MarkFacesInside_r (node_t *node) +{ + if (node->planenum == -1) + { + face_t **fp; + for (fp = node->markfaces; *fp; fp++) + { + (*fp)->outputnumber = 0; + } + } + else + { + MarkFacesInside_r (node->children[0]); + MarkFacesInside_r (node->children[1]); + } +} +#endif +static node_t* ClearOutFaces_r(node_t* node) +{ + face_t* f; + face_t* fnext; +#ifndef ZHLT_DETAILBRUSH + face_t** fp; +#endif + portal_t* p; + + // mark the node and all it's faces, so they + // can be removed if no children use them + + node->valid = 0; // will be set if any children touch it + for (f = node->faces; f; f = f->next) + { + f->outputnumber = -1; + } + + // go down the children +#ifdef ZHLT_DETAILBRUSH + if (!node->isportalleaf) +#else + if (node->planenum != -1) +#endif + { + // + // decision node + // + node->children[0] = ClearOutFaces_r(node->children[0]); + node->children[1] = ClearOutFaces_r(node->children[1]); + + // free any faces not in open child leafs + f = node->faces; + node->faces = NULL; + + for (; f; f = fnext) + { + fnext = f->next; + if (f->outputnumber == -1) + { // never referenced, so free it + c_free_faces++; + FreeFace(f); + } + else + { + c_keep_faces++; + f->next = node->faces; + node->faces = f; + } + } + + if (!node->valid) + { + // Here leaks memory. --vluzacn + // this node does not touch any interior leafs + + // if both children are solid, just make this node solid + if (node->children[0]->contents == CONTENTS_SOLID && node->children[1]->contents == CONTENTS_SOLID) + { + node->contents = CONTENTS_SOLID; + node->planenum = -1; +#ifdef ZHLT_DETAILBRUSH + node->isportalleaf = true; +#endif + return node; + } + + // if one child is solid, shortcut down the other side + if (node->children[0]->contents == CONTENTS_SOLID) + { + return node->children[1]; + } + if (node->children[1]->contents == CONTENTS_SOLID) + { + return node->children[0]; + } + + c_falsenodes++; + } + return node; + } + + // + // leaf node + // + if (node->contents != CONTENTS_SOLID) + { + // this node is still inside + + // mark all the nodes used as portals + for (p = node->portals; p;) + { + if (p->onnode) + { + p->onnode->valid = 1; + } + if (p->nodes[0] == node) // only write out from first leaf + { + p = p->next[0]; + } + else + { + p = p->next[1]; + } + } + +#ifdef ZHLT_DETAILBRUSH + MarkFacesInside_r (node); +#else + // mark all of the faces to be drawn + for (fp = node->markfaces; *fp; fp++) + { + (*fp)->outputnumber = 0; + } +#endif + + return node; + } + +#ifndef ZHLT_DETAILBRUSH + // this was a filled in node, so free the markfaces + if (node->planenum != -1) + { + free(node->markfaces); + } +#endif + + return node; +} + +// ===================================================================================== +// isClassnameAllowableOutside +// ===================================================================================== +#define MAX_ALLOWABLE_OUTSIDE_GROWTH_SIZE 64 + +unsigned g_nAllowableOutside = 0; +unsigned g_maxAllowableOutside = 0; +char** g_strAllowableOutsideList; + +bool isClassnameAllowableOutside(const char* const classname) +{ + if (g_strAllowableOutsideList) + { + unsigned x; + char** list = g_strAllowableOutsideList; + + for (x = 0; x < g_nAllowableOutside; x++, list++) + { + if (list) + { + if (!strcasecmp(classname, *list)) + { + return true; + } + } + } + } + + return false; +} + +// ===================================================================================== +// FreeAllowableOutsideList +// ===================================================================================== +void FreeAllowableOutsideList() +{ + if (g_strAllowableOutsideList) + { + free(g_strAllowableOutsideList); + g_strAllowableOutsideList = NULL; + } +} + +// ===================================================================================== +// LoadAllowableOutsideList +// ===================================================================================== +void LoadAllowableOutsideList(const char* const filename) +{ + char* fname; + int i, x, y; + char* pData; + char* pszData; + + if (!filename) + { + return; + } + else + { + unsigned len = strlen(filename) + 5; + + fname = (char*)Alloc(len); + safe_snprintf(fname, len, "%s", filename); + } + + if (q_exists(fname)) + { + if ((i = LoadFile(fname, &pData))) + { + Log("Reading allowable void entities from file '%s'\n", fname); + g_nAllowableOutside = 0; + for (pszData = pData, y = 0, x = 0; x < i; x++) + { + if ((pData[x] == '\n') || (pData[x] == '\r')) + { + pData[x] = 0; + if (strlen(pszData)) + { + if (g_nAllowableOutside == g_maxAllowableOutside) + { + g_maxAllowableOutside += MAX_ALLOWABLE_OUTSIDE_GROWTH_SIZE; + g_strAllowableOutsideList = + + (char**)realloc(g_strAllowableOutsideList, sizeof(char*) * g_maxAllowableOutside); + } + + g_strAllowableOutsideList[y++] = pszData; + g_nAllowableOutside++; + + Verbose("Adding entity '%s' to the allowable void list\n", pszData); + } + pszData = pData + x + 1; + } + } + } + } +} + +// ===================================================================================== +// FillOutside +// ===================================================================================== +node_t* FillOutside(node_t* node, const bool leakfile, const unsigned hullnum) +{ + int s; + int i; + bool inside; + bool ret; + vec3_t origin; + const char* cl; + + Verbose("----- FillOutside ----\n"); + + if (g_nofill) + { + Log("skipped\n"); + return node; + } +#ifdef HLBSP_REMOVEHULL2 + if (hullnum == 2 && g_nohull2) + return node; +#endif + + // + // place markers for all entities so + // we know if we leak inside + // + inside = false; + for (i = 1; i < g_numentities; i++) + { + GetVectorForKey(&g_entities[i], "origin", origin); + cl = ValueForKey(&g_entities[i], "classname"); + if (!isClassnameAllowableOutside(cl)) + { + /*if (!VectorCompare(origin, vec3_origin)) + */ if (*ValueForKey(&g_entities[i], "origin")) //--vluzacn + { + origin[2] += 1; // so objects on floor are ok + + // nudge playerstart around if needed so clipping hulls allways + // have a vlaid point + if (!strcmp(cl, "info_player_start")) + { + int x, y; + + for (x = -16; x <= 16; x += 16) + { + for (y = -16; y <= 16; y += 16) + { + origin[0] += x; + origin[1] += y; + if (PlaceOccupant(i, origin, node)) + { + inside = true; + goto gotit; + } + origin[0] -= x; + origin[1] -= y; + } + } + gotit:; + } + else + { + if (PlaceOccupant(i, origin, node)) + inside = true; + } + } + } + } + + if (!inside) + { + Warning("No entities exist in hull %i, no filling performed for this hull", hullnum); + return node; + } + + if(!g_outside_node.portals) + { + Warning("No outside node portal found in hull %i, no filling performed for this hull",hullnum); + return node; + } + + s = !(g_outside_node.portals->nodes[1] == &g_outside_node); + + // first check to see if an occupied leaf is hit + outleafs = 0; + valid++; + + prevleaknode = NULL; + + if (leakfile) + { + pointfile = fopen(g_pointfilename, "w"); + if (!pointfile) + { + Error("Couldn't open pointfile %s\n", g_pointfilename); + } + + linefile = fopen(g_linefilename, "w"); + if (!linefile) + { + Error("Couldn't open linefile %s\n", g_linefilename); + } + } + + ret = RecursiveFillOutside(g_outside_node.portals->nodes[s], false); + + if (leakfile) + { + fclose(pointfile); + fclose(linefile); + } + + if (ret) + { + GetVectorForKey(&g_entities[hit_occupied], "origin", origin); + + + { + Warning("=== LEAK in hull %i ===\nEntity %s @ (%4.0f,%4.0f,%4.0f)", + hullnum, ValueForKey(&g_entities[hit_occupied], "classname"), origin[0], origin[1], origin[2]); + PrintOnce( + "\n A LEAK is a hole in the map, where the inside of it is exposed to the\n" + "(unwanted) outside region. The entity listed in the error is just a helpful\n" + "indication of where the beginning of the leak pointfile starts, so the\n" + "beginning of the line can be quickly found and traced to until reaching the\n" + "outside. Unless this entity is accidentally on the outside of the map, it\n" + "probably should not be deleted. Some complex rotating objects entities need\n" + "their origins outside the map. To deal with these, just enclose the origin\n" + "brush with a solid world brush\n"); + } + + if (!g_bLeaked) + { + // First leak spits this out + Log("Leak pointfile generated\n\n"); + } + + if (g_bLeakOnly) + { + Error("Stopped by leak."); + } + + g_bLeaked = true; + + return node; + } +#ifdef HLBSP_DELETELEAKFILE + if (leakfile && !ret) + { + unlink(g_linefilename); + unlink(g_pointfilename); + } +#endif + + // now go back and fill things in + valid++; + RecursiveFillOutside(g_outside_node.portals->nodes[s], true); + + // remove faces and nodes from filled in leafs + c_falsenodes = 0; + c_free_faces = 0; + c_keep_faces = 0; + node = ClearOutFaces_r(node); + + Verbose("%5i outleafs\n", outleafs); + Verbose("%5i freed faces\n", c_free_faces); + Verbose("%5i keep faces\n", c_keep_faces); + Verbose("%5i falsenodes\n", c_falsenodes); + + // save portal file for vis tracing + if ((hullnum == 0) && leakfile) + { + WritePortalfile(node); + } + + return node; +} + +#ifdef HLBSP_FILL +void ResetMark_r (node_t* node) +{ +#ifdef ZHLT_DETAILBRUSH + if (node->isportalleaf) +#else + if (node->planenum == -1) +#endif + { + if (node->contents == CONTENTS_SOLID || node->contents == CONTENTS_SKY) + { + node->empty = 0; + } + else + { + node->empty = 1; + } + } + else + { + ResetMark_r (node->children[0]); + ResetMark_r (node->children[1]); + } +} +void MarkOccupied_r (node_t* node) +{ + if (node->empty == 1) + { + node->empty = 0; + portal_t* p; + int s; + for (p = node->portals; p; p = p->next[!s]) + { + s = (p->nodes[0] == node); + MarkOccupied_r (p->nodes[s]); + } + } +} +void RemoveUnused_r (node_t* node) +{ +#ifdef ZHLT_DETAILBRUSH + if (node->isportalleaf) +#else + if (node->planenum == -1) +#endif + { + if (node->empty == 1) + { +#ifdef ZHLT_DETAILBRUSH + FillLeaf (node); +#else + node->contents = CONTENTS_SOLID; +#endif + } + } + else + { + RemoveUnused_r (node->children[0]); + RemoveUnused_r (node->children[1]); + } +} +void FillInside (node_t* node) +{ + int i; + g_outside_node.empty = 0; + ResetMark_r (node); + for (i = 1; i < g_numentities; i++) + { + if (*ValueForKey(&g_entities[i], "origin")) + { + vec3_t origin; + node_t* innode; + GetVectorForKey(&g_entities[i], "origin", origin); + origin[2] += 1; + innode = PointInLeaf (node, origin); + MarkOccupied_r (innode); + origin[2] -= 2; + innode = PointInLeaf (node, origin); + MarkOccupied_r (innode); + } + } + RemoveUnused_r (node); +} +#endif diff --git a/src/zhlt-vluzacn/hlbsp/portals.cpp b/src/zhlt-vluzacn/hlbsp/portals.cpp new file mode 100644 index 0000000..a9b770c --- /dev/null +++ b/src/zhlt-vluzacn/hlbsp/portals.cpp @@ -0,0 +1,433 @@ +//#pragma warning(disable: 4018) // '<' : signed/unsigned mismatch + +#include "bsp5.h" + +node_t g_outside_node; // portals outside the world face this + +//============================================================================= + +/* + * ============= + * 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; + portal_t* 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; + } +} + +//============================================================================ + +/* + * ================ + * MakeHeadnodePortals + * + * The created portals will face the global g_outside_node + * ================ + */ +void MakeHeadnodePortals(node_t* node, const vec3_t mins, const vec3_t maxs) +{ + vec3_t bounds[2]; + int i, j, n; + portal_t* p; + portal_t* portals[6]; + dplane_t bplanes[6]; + dplane_t* pl; + + // pad with some space so there will never be null volume leafs + for (i = 0; i < 3; i++) + { + bounds[0][i] = mins[i] - SIDESPACE; + bounds[1][i] = maxs[i] + SIDESPACE; + } + + g_outside_node.contents = CONTENTS_SOLID; + g_outside_node.portals = NULL; + + 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 = new Winding(*pl); + AddPortalToNodes(p, node, &g_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; + } + portals[i]->winding->Clip(bplanes[j], true); + } + } +} + +/* + * ============================================================================== + * + * PORTAL FILE GENERATION + * + * ============================================================================== + */ + +static FILE* pf; +#ifdef HLBSP_VIEWPORTAL +static FILE *pf_view; +extern bool g_viewportal; +#endif +static int num_visleafs; // leafs the player can be in +static int num_visportals; + +static void WritePortalFile_r(const node_t* const node) +{ + int i; + portal_t* p; + Winding* w; + dplane_t plane2; + +#ifdef ZHLT_DETAILBRUSH + if (!node->isportalleaf) +#else + if (!node->contents) +#endif + { + WritePortalFile_r(node->children[0]); + WritePortalFile_r(node->children[1]); + return; + } + + if (node->contents == CONTENTS_SOLID) + { + return; + } + + for (p = node->portals; p;) + { + w = p->winding; + if (w && p->nodes[0] == node) + { + if (p->nodes[0]->contents == p->nodes[1]->contents) + { + // 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 + w->getPlane(plane2); + if (DotProduct(p->plane.normal, plane2.normal) < 1.0 - ON_EPSILON) + { // backwards... +#ifdef ZHLT_WINDING_FIX + if (DotProduct(p->plane.normal, plane2.normal) > -1.0 + ON_EPSILON) + { + Warning ("Colinear portal @"); + w->Print (); + } + else + { + Warning ("Backward portal @"); + w->Print (); + } +#endif + fprintf(pf, "%u %i %i ", w->m_NumPoints, p->nodes[1]->visleafnum, p->nodes[0]->visleafnum); + } + else + { + fprintf(pf, "%u %i %i ", w->m_NumPoints, p->nodes[0]->visleafnum, p->nodes[1]->visleafnum); + } + + for (i = 0; i < w->m_NumPoints; i++) + { + fprintf(pf, "(%f %f %f) ", w->m_Points[i][0], w->m_Points[i][1], w->m_Points[i][2]); + } + fprintf(pf, "\n"); +#ifdef HLBSP_VIEWPORTAL + if (g_viewportal) + { + vec3_t center, center1, center2; + vec3_t from = {0.0, 0.0, -65536}; + w->getCenter (center); + VectorMA (center, 0.5, p->plane.normal, center1); + VectorMA (center, -0.5, p->plane.normal, center2); + fprintf (pf_view, "%5.2f %5.2f %5.2f\n", from[0], from[1], from[2]); + fprintf (pf_view, "%5.2f %5.2f %5.2f\n", center1[0], center1[1], center1[2]); + for (i = 0; i < w->m_NumPoints; i++) + { + vec_t *p1, *p2; + p1 = w->m_Points[i]; + p2 = w->m_Points[(i+1)%w->m_NumPoints]; + fprintf (pf_view, "%5.2f %5.2f %5.2f\n", p1[0], p1[1], p1[2]); + fprintf (pf_view, "%5.2f %5.2f %5.2f\n", p2[0], p2[1], p2[2]); + fprintf (pf_view, "%5.2f %5.2f %5.2f\n", center2[0], center2[1], center2[2]); + fprintf (pf_view, "%5.2f %5.2f %5.2f\n", center1[0], center1[1], center1[2]); + } + } +#endif + } + } + + if (p->nodes[0] == node) + { + p = p->next[0]; + } + else + { + p = p->next[1]; + } + } + +} + +/* + * ================ + * NumberLeafs_r + * ================ + */ +static void NumberLeafs_r(node_t* node) +{ + portal_t* p; + +#ifdef ZHLT_DETAILBRUSH + if (!node->isportalleaf) +#else + if (!node->contents) +#endif + { // decision node + node->visleafnum = -99; + NumberLeafs_r(node->children[0]); + NumberLeafs_r(node->children[1]); + return; + } + + if (node->contents == CONTENTS_SOLID) + { // solid block, viewpoint never inside + node->visleafnum = -1; + return; + } + + node->visleafnum = num_visleafs++; + + for (p = node->portals; p;) + { + if (p->nodes[0] == node) // only write out from first leaf + { + if (p->nodes[0]->contents == p->nodes[1]->contents) + { + num_visportals++; + } + p = p->next[0]; + } + else + { + p = p->next[1]; + } + } +} + +#ifdef ZHLT_DETAILBRUSH +static int CountChildLeafs_r (node_t *node) +{ + if (node->planenum == -1) + { // dleaf + if (node->iscontentsdetail) + { // solid + return 0; + } + else + { + return 1; + } + } + else + { // node + int count = 0; + count += CountChildLeafs_r (node->children[0]); + count += CountChildLeafs_r (node->children[1]); + return count; + } +} +static void WriteLeafCount_r (node_t *node) +{ + if (!node->isportalleaf) + { + WriteLeafCount_r (node->children[0]); + WriteLeafCount_r (node->children[1]); + } + else + { + if (node->contents == CONTENTS_SOLID) + { + return; + } + int count = CountChildLeafs_r (node); + fprintf (pf, "%i\n", count); + } +} +#endif +/* + * ================ + * WritePortalfile + * ================ + */ +void WritePortalfile(node_t* headnode) +{ + // set the visleafnum field in every leaf and count the total number of portals + num_visleafs = 0; + num_visportals = 0; + NumberLeafs_r(headnode); + + // write the file + pf = fopen(g_portfilename, "w"); + if (!pf) + { + Error("Error writing portal file %s", g_portfilename); + } +#ifdef HLBSP_VIEWPORTAL + if (g_viewportal) + { + char filename[_MAX_PATH]; + safe_snprintf(filename, _MAX_PATH, "%s_portal.pts", g_Mapname); + pf_view = fopen (filename, "w"); + if (!pf_view) + { + Error ("Couldn't open %s", filename); + } + Log ("Writing '%s' ...\n", filename); + } +#endif + + fprintf(pf, "%i\n", num_visleafs); + fprintf(pf, "%i\n", num_visportals); + +#ifdef ZHLT_DETAILBRUSH + WriteLeafCount_r (headnode); +#endif + WritePortalFile_r(headnode); + fclose(pf); +#ifdef HLBSP_VIEWPORTAL + if (g_viewportal) + { + fclose (pf_view); + } +#endif + Log("BSP generation successful, writing portal file '%s'\n", g_portfilename); +} + +//=================================================== + +void FreePortals(node_t* node) +{ + portal_t* p; + portal_t* nextp; + +#ifdef ZHLT_DETAILBRUSH + if (!node->isportalleaf) +#else + if (node->planenum != -1) +#endif + { + FreePortals(node->children[0]); + FreePortals(node->children[1]); + return; + } + + for (p = node->portals; p; p = nextp) + { + if (p->nodes[0] == node) + { + nextp = p->next[0]; + } + else + { + nextp = p->next[1]; + } + RemovePortalFromNode(p, p->nodes[0]); + RemovePortalFromNode(p, p->nodes[1]); + delete p->winding; + FreePortal(p); + } +} diff --git a/src/zhlt-vluzacn/hlbsp/qbsp.cpp b/src/zhlt-vluzacn/hlbsp/qbsp.cpp new file mode 100644 index 0000000..7d70b79 --- /dev/null +++ b/src/zhlt-vluzacn/hlbsp/qbsp.cpp @@ -0,0 +1,2318 @@ +/* + + BINARY SPACE PARTITION -aka- B S P + + Code based on original code from Valve Software, + Modified by Sean "Zoner" Cavanaugh (seanc@gearboxsoftware.com) with permission. + Modified by Tony "Merl" Moore (merlinis@bigpond.net.au) [AJM] + +*/ + +#ifdef SYSTEM_WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#endif + +#include "bsp5.h" + +/* + + NOTES + + +*/ + +#ifdef HLCSG_HLBSP_ALLOWEMPTYENTITY +vec3_t g_hull_size[NUM_HULLS][2] = +{ + {// 0x0x0 + {0, 0, 0}, {0, 0, 0} + } + , + {// 32x32x72 + {-16, -16, -36}, {16, 16, 36} + } + , + {// 64x64x64 + {-32, -32, -32}, {32, 32, 32} + } + , + {// 32x32x36 + {-16, -16, -18}, {16, 16, 18} + } +}; +#endif +static FILE* polyfiles[NUM_HULLS]; +#ifdef ZHLT_DETAILBRUSH +static FILE* brushfiles[NUM_HULLS]; +#endif +int g_hullnum = 0; + +static face_t* validfaces[MAX_INTERNAL_MAP_PLANES]; + +char g_bspfilename[_MAX_PATH]; +char g_pointfilename[_MAX_PATH]; +char g_linefilename[_MAX_PATH]; +char g_portfilename[_MAX_PATH]; +#ifdef ZHLT_64BIT_FIX +char g_extentfilename[_MAX_PATH]; +#endif + +// command line flags +bool g_noopt = DEFAULT_NOOPT; // don't optimize BSP on write +#ifdef HLBSP_MERGECLIPNODE +bool g_noclipnodemerge = DEFAULT_NOCLIPNODEMERGE; +#endif +bool g_nofill = DEFAULT_NOFILL; // dont fill "-nofill" +#ifdef HLBSP_FILL +bool g_noinsidefill = DEFAULT_NOINSIDEFILL; +#endif +bool g_notjunc = DEFAULT_NOTJUNC; +#ifdef HLBSP_BRINKHACK +bool g_nobrink = DEFAULT_NOBRINK; +#endif +bool g_noclip = DEFAULT_NOCLIP; // no clipping hull "-noclip" +bool g_chart = DEFAULT_CHART; // print out chart? "-chart" +bool g_estimate = DEFAULT_ESTIMATE; // estimate mode "-estimate" +bool g_info = DEFAULT_INFO; +bool g_bLeakOnly = DEFAULT_LEAKONLY; // leakonly mode "-leakonly" +bool g_bLeaked = false; +int g_subdivide_size = DEFAULT_SUBDIVIDE_SIZE; + +#ifdef ZHLT_NULLTEX // AJM +bool g_bUseNullTex = DEFAULT_NULLTEX; // "-nonulltex" +#endif + +#ifdef ZHLT_DETAIL // AJM +bool g_bDetailBrushes = DEFAULT_DETAIL; // "-nodetail" +#endif + +#ifdef ZHLT_PROGRESSFILE // AJM +char* g_progressfile = DEFAULT_PROGRESSFILE; // "-progressfile path" +#endif + +#ifdef HLBSP_REMOVEHULL2 +bool g_nohull2 = false; +#endif + +#ifdef HLBSP_VIEWPORTAL +bool g_viewportal = false; +#endif + +#ifdef HLCSG_HLBSP_DOUBLEPLANE +dplane_t g_dplanes[MAX_INTERNAL_MAP_PLANES]; +#endif + + +#ifdef ZHLT_INFO_COMPILE_PARAMETERS// AJM +// ===================================================================================== +// GetParamsFromEnt +// this function is called from parseentity when it encounters the +// info_compile_parameters entity. each tool should have its own version of this +// to handle its own specific settings. +// ===================================================================================== +void GetParamsFromEnt(entity_t* mapent) +{ + int iTmp; + + Log("\nCompile Settings detected from info_compile_parameters entity\n"); + + // verbose(choices) : "Verbose compile messages" : 0 = [ 0 : "Off" 1 : "On" ] + iTmp = IntForKey(mapent, "verbose"); + if (iTmp == 1) + { + g_verbose = true; + } + else if (iTmp == 0) + { + g_verbose = false; + } + Log("%30s [ %-9s ]\n", "Compile Option", "setting"); + Log("%30s [ %-9s ]\n", "Verbose Compile Messages", g_verbose ? "on" : "off"); + + // estimate(choices) :"Estimate Compile Times?" : 0 = [ 0: "Yes" 1: "No" ] + if (IntForKey(mapent, "estimate")) + { + g_estimate = true; + } + else + { + g_estimate = false; + } + Log("%30s [ %-9s ]\n", "Estimate Compile Times", g_estimate ? "on" : "off"); + + // priority(choices) : "Priority Level" : 0 = [ 0 : "Normal" 1 : "High" -1 : "Low" ] + if (!strcmp(ValueForKey(mapent, "priority"), "1")) + { + g_threadpriority = eThreadPriorityHigh; + Log("%30s [ %-9s ]\n", "Thread Priority", "high"); + } + else if (!strcmp(ValueForKey(mapent, "priority"), "-1")) + { + g_threadpriority = eThreadPriorityLow; + Log("%30s [ %-9s ]\n", "Thread Priority", "low"); + } + + /* + hlbsp(choices) : "HLBSP" : 0 = + [ + 0 : "Off" + 1 : "Normal" + 2 : "Leakonly" + ] + */ + iTmp = IntForKey(mapent, "hlbsp"); + if (iTmp == 0) + { + Fatal(assume_TOOL_CANCEL, + "%s flag was not checked in info_compile_parameters entity, execution of %s cancelled", g_Program, g_Program); + CheckFatal(); + } + else if (iTmp == 1) + { + g_bLeakOnly = false; + } + else if (iTmp == 2) + { + g_bLeakOnly = true; + } + Log("%30s [ %-9s ]\n", "Leakonly Mode", g_bLeakOnly ? "on" : "off"); + + iTmp = IntForKey(mapent, "noopt"); + if(iTmp == 0) + { + g_noopt = false; + } + else + { + g_noopt = true; + } + + /* + nocliphull(choices) : "Generate clipping hulls" : 0 = + [ + 0 : "Yes" + 1 : "No" + ] + */ + iTmp = IntForKey(mapent, "nocliphull"); + if (iTmp == 0) + { + g_noclip = false; + } + else if (iTmp == 1) + { + g_noclip = true; + } + Log("%30s [ %-9s ]\n", "Clipping Hull Generation", g_noclip ? "off" : "on"); + + ////////////////// + Verbose("\n"); +} +#endif + +// ===================================================================================== +// Extract File stuff (ExtractFile | ExtractFilePath | ExtractFileBase) +// +// With VS 2005 - and the 64 bit build, i had to pull 3 classes over from +// cmdlib.cpp even with the proper includes to get rid of the lnk2001 error +// +// amckern - amckern@yahoo.com +// ===================================================================================== + +// Code Deleted. --vluzacn + +// ===================================================================================== +// NewFaceFromFace +// Duplicates the non point information of a face, used by SplitFace and MergeFace. +// ===================================================================================== +face_t* NewFaceFromFace(const face_t* const in) +{ + face_t* newf; + + newf = AllocFace(); + + newf->planenum = in->planenum; + newf->texturenum = in->texturenum; + newf->original = in->original; + newf->contents = in->contents; +#ifdef HLBSP_NewFaceFromFace_FIX + newf->facestyle = in->facestyle; +#endif +#ifdef ZHLT_DETAILBRUSH + newf->detaillevel = in->detaillevel; +#endif + + return newf; +} + +// ===================================================================================== +// SplitFaceTmp +// blah +// ===================================================================================== +static void SplitFaceTmp(face_t* in, const dplane_t* const split, face_t** front, face_t** back) +{ + vec_t dists[MAXEDGES + 1]; + int sides[MAXEDGES + 1]; + int counts[3]; + vec_t dot; + int i; + int j; + face_t* newf; + face_t* new2; + vec_t* p1; + vec_t* p2; + vec3_t mid; + + if (in->numpoints < 0) + { + Error("SplitFace: freed face"); + } + counts[0] = counts[1] = counts[2] = 0; + + + // determine sides for each point + for (i = 0; i < in->numpoints; i++) + { + dot = DotProduct(in->pts[i], split->normal); + dot -= split->dist; + dists[i] = dot; + if (dot > ON_EPSILON) + { + sides[i] = SIDE_FRONT; + } + else if (dot < -ON_EPSILON) + { + sides[i] = SIDE_BACK; + } + else + { + sides[i] = SIDE_ON; + } + counts[sides[i]]++; + } + sides[i] = sides[0]; + dists[i] = dists[0]; + +#ifdef HLBSP_SPLITFACE_FIX + if (!counts[0] && !counts[1]) + { + if (in->detaillevel) + { + // put front face in front node, and back face in back node. + const dplane_t *faceplane = &g_dplanes[in->planenum]; + if (DotProduct (faceplane->normal, split->normal) > NORMAL_EPSILON) // usually near 1.0 or -1.0 + { + *front = in; + *back = NULL; + } + else + { + *front = NULL; + *back = in; + } + } + else + { + // not func_detail. front face and back face need to pair. + vec_t sum = 0.0; + for (i = 0; i < in->numpoints; i++) + { + dot = DotProduct(in->pts[i], split->normal); + dot -= split->dist; + sum += dot; + } + if (sum > NORMAL_EPSILON) + { + *front = in; + *back = NULL; + } + else + { + *front = NULL; + *back = in; + } + } + return; + } +#endif + if (!counts[0]) + { + *front = NULL; + *back = in; + return; + } + if (!counts[1]) + { + *front = in; + *back = NULL; + return; + } + + *back = newf = NewFaceFromFace(in); + *front = new2 = NewFaceFromFace(in); + + // distribute the points and generate splits + + for (i = 0; i < in->numpoints; i++) + { + if (newf->numpoints > MAXEDGES || new2->numpoints > MAXEDGES) + { + Error("SplitFace: numpoints > MAXEDGES"); + } + + p1 = in->pts[i]; + + if (sides[i] == SIDE_ON) + { + VectorCopy(p1, newf->pts[newf->numpoints]); + newf->numpoints++; + VectorCopy(p1, new2->pts[new2->numpoints]); + new2->numpoints++; + continue; + } + + if (sides[i] == SIDE_FRONT) + { + VectorCopy(p1, new2->pts[new2->numpoints]); + new2->numpoints++; + } + else + { + VectorCopy(p1, newf->pts[newf->numpoints]); + newf->numpoints++; + } + + if (sides[i + 1] == SIDE_ON || sides[i + 1] == sides[i]) + { + continue; + } + + // generate a split point + p2 = in->pts[(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, newf->pts[newf->numpoints]); + newf->numpoints++; + VectorCopy(mid, new2->pts[new2->numpoints]); + new2->numpoints++; + } + + if (newf->numpoints > MAXEDGES || new2->numpoints > MAXEDGES) + { + Error("SplitFace: numpoints > MAXEDGES"); + } +#ifdef HLBSP_REMOVECOLINEARPOINTS + { + Winding *wd = new Winding (newf->numpoints); + int x; + for (x = 0; x < newf->numpoints; x++) + { + VectorCopy (newf->pts[x], wd->m_Points[x]); + } + wd->RemoveColinearPoints (); + newf->numpoints = wd->m_NumPoints; + for (x = 0; x < newf->numpoints; x++) + { + VectorCopy (wd->m_Points[x], newf->pts[x]); + } + delete wd; + if (newf->numpoints == 0) + { + *back = NULL; + } + } + { + Winding *wd = new Winding (new2->numpoints); + int x; + for (x = 0; x < new2->numpoints; x++) + { + VectorCopy (new2->pts[x], wd->m_Points[x]); + } + wd->RemoveColinearPoints (); + new2->numpoints = wd->m_NumPoints; + for (x = 0; x < new2->numpoints; x++) + { + VectorCopy (wd->m_Points[x], new2->pts[x]); + } + delete wd; + if (new2->numpoints == 0) + { + *front = NULL; + } + } +#endif +} + +// ===================================================================================== +// SplitFace +// blah +// ===================================================================================== +void SplitFace(face_t* in, const dplane_t* const split, face_t** front, face_t** back) +{ + SplitFaceTmp(in, split, front, back); + + // free the original face now that is is represented by the fragments + if (*front && *back) + { + FreeFace(in); + } +} + +// ===================================================================================== +// AllocFace +// ===================================================================================== +face_t* AllocFace() +{ + face_t* f; + + f = (face_t*)malloc(sizeof(face_t)); + memset(f, 0, sizeof(face_t)); + + f->planenum = -1; + + return f; +} + +// ===================================================================================== +// FreeFace +// ===================================================================================== +void FreeFace(face_t* f) +{ + free(f); +} + +// ===================================================================================== +// AllocSurface +// ===================================================================================== +surface_t* AllocSurface() +{ + surface_t* s; + + s = (surface_t*)malloc(sizeof(surface_t)); + memset(s, 0, sizeof(surface_t)); + + return s; +} + +// ===================================================================================== +// FreeSurface +// ===================================================================================== +void FreeSurface(surface_t* s) +{ + free(s); +} + +// ===================================================================================== +// AllocPortal +// ===================================================================================== +portal_t* AllocPortal() +{ + portal_t* p; + + p = (portal_t*)malloc(sizeof(portal_t)); + memset(p, 0, sizeof(portal_t)); + + return p; +} + +// ===================================================================================== +// FreePortal +// ===================================================================================== +void FreePortal(portal_t* p) // consider: inline +{ + free(p); +} + + +#ifdef ZHLT_DETAILBRUSH +side_t *AllocSide () +{ + side_t *s; + s = (side_t *)malloc (sizeof (side_t)); + memset (s, 0, sizeof (side_t)); + return s; +} + +void FreeSide (side_t *s) +{ + if (s->w) + { + delete s->w; + } + free (s); + return; +} + +side_t *NewSideFromSide (const side_t *s) +{ + side_t *news; + news = AllocSide (); + news->plane = s->plane; + news->w = new Winding (*s->w); + return news; +} + +brush_t *AllocBrush () +{ + brush_t *b; + b = (brush_t *)malloc (sizeof (brush_t)); + memset (b, 0, sizeof (brush_t)); + return b; +} + +void FreeBrush (brush_t *b) +{ + if (b->sides) + { + side_t *s, *next; + for (s = b->sides; s; s = next) + { + next = s->next; + FreeSide (s); + } + } + free (b); + return; +} + +brush_t *NewBrushFromBrush (const brush_t *b) +{ + brush_t *newb; + newb = AllocBrush (); + side_t *s, **pnews; + for (s = b->sides, pnews = &newb->sides; s; s = s->next, pnews = &(*pnews)->next) + { + *pnews = NewSideFromSide (s); + } + return newb; +} + +void ClipBrush (brush_t **b, const dplane_t *split, vec_t epsilon) +{ + side_t *s, **pnext; + Winding *w; + for (pnext = &(*b)->sides, s = *pnext; s; s = *pnext) + { + if (s->w->Clip (*split, false, epsilon)) + { + pnext = &s->next; + } + else + { + *pnext = s->next; + FreeSide (s); + } + } + if (!(*b)->sides) + { // empty brush + FreeBrush (*b); + *b = NULL; + return; + } + w = new Winding (*split); + for (s = (*b)->sides; s; s = s->next) + { + if (!w->Clip (s->plane, false, epsilon)) + { + break; + } + } + if (w->m_NumPoints == 0) + { + delete w; + } + else + { + s = AllocSide (); + s->plane = *split; + s->w = w; + s->next = (*b)->sides; + (*b)->sides = s; + } + return; +} + +void SplitBrush (brush_t *in, const dplane_t *split, brush_t **front, brush_t **back) + // 'in' will be freed +{ + in->next = NULL; + bool onfront; + bool onback; + onfront = false; + onback = false; + side_t *s; + for (s = in->sides; s; s = s->next) + { + switch (s->w->WindingOnPlaneSide (split->normal, split->dist, 2 * ON_EPSILON)) + { + case SIDE_CROSS: + onfront = true; + onback = true; + break; + case SIDE_FRONT: + onfront = true; + break; + case SIDE_BACK: + onback = true; + break; + case SIDE_ON: + break; + } + if (onfront && onback) + break; + } + if (!onfront && !onback) + { + FreeBrush (in); + *front = NULL; + *back = NULL; + return; + } + if (!onfront) + { + *front = NULL; + *back = in; + return; + } + if (!onback) + { + *front = in; + *back = NULL; + return; + } + *front = in; + *back = NewBrushFromBrush (in); + dplane_t frontclip = *split; + dplane_t backclip = *split; + VectorSubtract (vec3_origin, backclip.normal, backclip.normal); + backclip.dist = -backclip.dist; + ClipBrush (front, &frontclip, NORMAL_EPSILON); + ClipBrush (back, &backclip, NORMAL_EPSILON); + return; +} + +#ifdef HLBSP_DETAILBRUSH_CULL +brush_t *BrushFromBox (const vec3_t mins, const vec3_t maxs) +{ + brush_t *b = AllocBrush (); + dplane_t planes[6]; + for (int k = 0; k < 3; k++) + { + VectorFill (planes[k].normal, 0.0); + planes[k].normal[k] = 1.0; + planes[k].dist = mins[k]; + VectorFill (planes[k+3].normal, 0.0); + planes[k+3].normal[k] = -1.0; + planes[k+3].dist = -maxs[k]; + } + b->sides = AllocSide (); + b->sides->plane = planes[0]; + b->sides->w = new Winding (planes[0]); + for (int k = 1; k < 6; k++) + { + ClipBrush (&b, &planes[k], NORMAL_EPSILON); + if (b == NULL) + { + break; + } + } + return b; +} + +void CalcBrushBounds (const brush_t *b, vec3_t &mins, vec3_t &maxs) +{ + VectorFill (mins, BOGUS_RANGE); + VectorFill (maxs, -BOGUS_RANGE); + for (side_t *s = b->sides; s; s = s->next) + { + vec3_t windingmins, windingmaxs; + s->w->getBounds (windingmins, windingmaxs); + VectorCompareMinimum (mins, windingmins, mins); + VectorCompareMaximum (maxs, windingmaxs, maxs); + } +} +#endif + +#endif +// ===================================================================================== +// AllocNode +// blah +// ===================================================================================== +node_t* AllocNode() +{ + node_t* n; + + n = (node_t*)malloc(sizeof(node_t)); + memset(n, 0, sizeof(node_t)); + + return n; +} + +// ===================================================================================== +// AddPointToBounds +// ===================================================================================== +void AddPointToBounds(const vec3_t v, vec3_t mins, vec3_t maxs) +{ + int i; + vec_t val; + + for (i = 0; i < 3; i++) + { + val = v[i]; + if (val < mins[i]) + { + mins[i] = val; + } + if (val > maxs[i]) + { + maxs[i] = val; + } + } +} + +// ===================================================================================== +// AddFaceToBounds +// ===================================================================================== +static void AddFaceToBounds(const face_t* const f, vec3_t mins, vec3_t maxs) +{ + int i; + + for (i = 0; i < f->numpoints; i++) + { + AddPointToBounds(f->pts[i], mins, maxs); + } +} + +// ===================================================================================== +// ClearBounds +// ===================================================================================== +static void ClearBounds(vec3_t mins, vec3_t maxs) +{ + mins[0] = mins[1] = mins[2] = 99999; + maxs[0] = maxs[1] = maxs[2] = -99999; +} + +// ===================================================================================== +// SurflistFromValidFaces +// blah +// ===================================================================================== +static surfchain_t* SurflistFromValidFaces() +{ + surface_t* n; + int i; + face_t* f; + face_t* next; + surfchain_t* sc; + + sc = (surfchain_t*)malloc(sizeof(*sc)); + ClearBounds(sc->mins, sc->maxs); + sc->surfaces = NULL; + + // grab planes from both sides + for (i = 0; i < g_numplanes; i += 2) + { + if (!validfaces[i] && !validfaces[i + 1]) + { + continue; + } + n = AllocSurface(); + n->next = sc->surfaces; + sc->surfaces = n; + ClearBounds(n->mins, n->maxs); +#ifdef ZHLT_DETAILBRUSH + n->detaillevel = -1; +#endif + n->planenum = i; + + n->faces = NULL; + for (f = validfaces[i]; f; f = next) + { + next = f->next; + f->next = n->faces; + n->faces = f; + AddFaceToBounds(f, n->mins, n->maxs); +#ifdef ZHLT_DETAILBRUSH + if (n->detaillevel == -1 || f->detaillevel < n->detaillevel) + { + n->detaillevel = f->detaillevel; + } +#endif + } + for (f = validfaces[i + 1]; f; f = next) + { + next = f->next; + f->next = n->faces; + n->faces = f; + AddFaceToBounds(f, n->mins, n->maxs); +#ifdef ZHLT_DETAILBRUSH + if (n->detaillevel == -1 || f->detaillevel < n->detaillevel) + { + n->detaillevel = f->detaillevel; + } +#endif + } + + AddPointToBounds(n->mins, sc->mins, sc->maxs); + AddPointToBounds(n->maxs, sc->mins, sc->maxs); + + validfaces[i] = NULL; + validfaces[i + 1] = NULL; + } + + // merge all possible polygons + + MergeAll(sc->surfaces); + + return sc; +} + +#ifdef ZHLT_NULLTEX// AJM +// ===================================================================================== +// CheckFaceForNull +// Returns true if the passed face is facetype null +// ===================================================================================== +bool CheckFaceForNull(const face_t* const f) +{ +#ifdef HLBSP_SKY_SOLID + if (f->contents == CONTENTS_SKY) + { + const char *name = GetTextureByNumber (f->texturenum); + if (strncasecmp(name, "sky", 3)) // for env_rain + return true; + } +#endif + // null faces are only of facetype face_null if we are using null texture stripping + if (g_bUseNullTex) + { +#ifdef HLCSG_HLBSP_VOIDTEXINFO + const char *name = GetTextureByNumber (f->texturenum); + if (!strncasecmp(name, "null", 4)) + return true; + return false; +#else + texinfo_t* info; + miptex_t* miptex; + int ofs; + + info = &g_texinfo[f->texturenum]; + ofs = ((dmiptexlump_t*)g_dtexdata)->dataofs[info->miptex]; + miptex = (miptex_t*)(&g_dtexdata[ofs]); + + if (!strcasecmp(miptex->name, "null")) + return true; + #ifdef HLCSG_CUSTOMHULL + else if (!strncasecmp(miptex->name, "null", 4)) + return true; + #else + else + return false; + #endif +#endif + } + else // otherwise, under normal cases, null textured faces should be facetype face_normal + { + return false; + } +} +// ===================================================================================== +//Cpt_Andrew - UTSky Check +// ===================================================================================== +bool CheckFaceForEnv_Sky(const face_t* const f) +{ +#ifdef HLCSG_HLBSP_VOIDTEXINFO + const char *name = GetTextureByNumber (f->texturenum); + if (!strncasecmp (name, "env_sky", 7)) + return true; + return false; +#else + texinfo_t* info; + miptex_t* miptex; + int ofs; + + info = &g_texinfo[f->texturenum]; + ofs = ((dmiptexlump_t*)g_dtexdata)->dataofs[info->miptex]; + miptex = (miptex_t*)(&g_dtexdata[ofs]); + + if (!strcasecmp(miptex->name, "env_sky")) + return true; + else + return false; +#endif +} +// ===================================================================================== + + + + +#endif + +#ifdef ZHLT_DETAIL +// ===================================================================================== +// CheckFaceForDetail +// Returns true if the passed face is part of a detail brush +// ===================================================================================== +bool CheckFaceForDetail(const face_t* const f) +{ + if (f->contents == CONTENTS_DETAIL) + { + //Log("CheckFaceForDetail:: got a detail face"); + return true; + } + + return false; +} +#endif + +// ===================================================================================== +// CheckFaceForHint +// Returns true if the passed face is facetype hint +// ===================================================================================== +bool CheckFaceForHint(const face_t* const f) +{ +#ifdef HLCSG_HLBSP_VOIDTEXINFO + const char *name = GetTextureByNumber (f->texturenum); + if (!strncasecmp (name, "hint", 4)) + return true; + return false; +#else + texinfo_t* info; + miptex_t* miptex; + int ofs; + + info = &g_texinfo[f->texturenum]; + ofs = ((dmiptexlump_t *)g_dtexdata)->dataofs[info->miptex]; + miptex = (miptex_t *)(&g_dtexdata[ofs]); + + if (!strcasecmp(miptex->name, "hint")) + { + return true; + } + else + { + return false; + } +#endif +} + +// ===================================================================================== +// CheckFaceForSkipt +// Returns true if the passed face is facetype skip +// ===================================================================================== +bool CheckFaceForSkip(const face_t* const f) +{ +#ifdef HLCSG_HLBSP_VOIDTEXINFO + const char *name = GetTextureByNumber (f->texturenum); + if (!strncasecmp (name, "skip", 4)) + return true; + return false; +#else + texinfo_t* info; + miptex_t* miptex; + int ofs; + + info = &g_texinfo[f->texturenum]; + ofs = ((dmiptexlump_t*)g_dtexdata)->dataofs[info->miptex]; + miptex = (miptex_t*)(&g_dtexdata[ofs]); + + if (!strcasecmp(miptex->name, "skip")) + { + return true; + } + else + { + return false; + } +#endif +} + +#ifdef HLCSG_HLBSP_SOLIDHINT +bool CheckFaceForDiscardable (const face_t *f) +{ + const char *name = GetTextureByNumber (f->texturenum); + if (!strncasecmp (name, "SOLIDHINT", 9)) + return true; + return false; +} + +#endif +// ===================================================================================== +// SetFaceType +// ===================================================================================== +static facestyle_e SetFaceType(face_t* f) +{ + if (CheckFaceForHint(f)) + { + f->facestyle = face_hint; + } + else if (CheckFaceForSkip(f)) + { + f->facestyle = face_skip; + } +#ifdef ZHLT_NULLTEX // AJM + else if (CheckFaceForNull(f)) + { + f->facestyle = face_null; + } +#endif +#ifdef HLCSG_HLBSP_SOLIDHINT + else if (CheckFaceForDiscardable (f)) + { + f->facestyle = face_discardable; + } +#endif + +// ===================================================================================== +//Cpt_Andrew - Env_Sky Check +// ===================================================================================== + //else if (CheckFaceForUTSky(f)) + else if (CheckFaceForEnv_Sky(f)) + { + f->facestyle = face_null; + } +// ===================================================================================== + + +#ifdef ZHLT_DETAIL + else if (CheckFaceForDetail(f)) + { + //Log("SetFaceType::detail face\n"); + f->facestyle = face_detail; + } +#endif + else + { + f->facestyle = face_normal; + } + return f->facestyle; +} + +// ===================================================================================== +// ReadSurfs +// ===================================================================================== +static surfchain_t* ReadSurfs(FILE* file) +{ + int r; +#ifdef ZHLT_DETAILBRUSH + int detaillevel; +#endif + int planenum, g_texinfo, contents, numpoints; + face_t* f; + int i; + double v[3]; + int line = 0; +#ifdef HLCSG_HLBSP_DOUBLEPLANE + double inaccuracy, inaccuracy_count = 0.0, inaccuracy_total = 0.0, inaccuracy_max = 0.0; +#endif + + // read in the polygons + while (1) + { +#ifdef HLBSP_REMOVEHULL2 + if (file == polyfiles[2] && g_nohull2) + break; +#endif + line++; +#ifdef ZHLT_DETAILBRUSH + r = fscanf(file, "%i %i %i %i %i\n", &detaillevel, &planenum, &g_texinfo, &contents, &numpoints); +#else + r = fscanf(file, "%i %i %i %i\n", &planenum, &g_texinfo, &contents, &numpoints); +#endif + if (r == 0 || r == -1) + { + return NULL; + } + if (planenum == -1) // end of model + { +#ifdef HLCSG_HLBSP_DOUBLEPLANE + Developer (DEVELOPER_LEVEL_MEGASPAM, "inaccuracy: average %.8f max %.8f\n", inaccuracy_total / inaccuracy_count, inaccuracy_max); +#endif + break; + } +#ifdef ZHLT_DETAILBRUSH + if (r != 5) +#else + if (r != 4) +#endif + { + Error("ReadSurfs (line %i): scanf failure", line); + } + if (numpoints > MAXPOINTS) + { + Error("ReadSurfs (line %i): %i > MAXPOINTS\nThis is caused by a face with too many verticies (typically found on end-caps of high-poly cylinders)\n", line, numpoints); + } + if (planenum > g_numplanes) + { + Error("ReadSurfs (line %i): %i > g_numplanes\n", line, planenum); + } + if (g_texinfo > g_numtexinfo) + { + Error("ReadSurfs (line %i): %i > g_numtexinfo", line, g_texinfo); + } +#ifdef ZHLT_DETAILBRUSH + if (detaillevel < 0) + { + Error("ReadSurfs (line %i): detaillevel %i < 0", line, detaillevel); + } +#endif + + if (!strcasecmp(GetTextureByNumber(g_texinfo), "skip")) + { + Verbose("ReadSurfs (line %i): skipping a surface", line); + + for (i = 0; i < numpoints; i++) + { + line++; + //Verbose("skipping line %d", line); + r = fscanf(file, "%lf %lf %lf\n", &v[0], &v[1], &v[2]); + if (r != 3) + { + Error("::ReadSurfs (face_skip), fscanf of points failed at line %i", line); + } + } + fscanf(file, "\n"); + continue; + } + + f = AllocFace(); +#ifdef ZHLT_DETAILBRUSH + f->detaillevel = detaillevel; +#endif + f->planenum = planenum; + f->texturenum = g_texinfo; + f->contents = contents; + f->numpoints = numpoints; + f->next = validfaces[planenum]; + validfaces[planenum] = f; + + SetFaceType(f); + + for (i = 0; i < f->numpoints; i++) + { + line++; + r = fscanf(file, "%lf %lf %lf\n", &v[0], &v[1], &v[2]); + if (r != 3) + { + Error("::ReadSurfs (face_normal), fscanf of points failed at line %i", line); + } + VectorCopy(v, f->pts[i]); +#ifdef HLCSG_HLBSP_DOUBLEPLANE + if (DEVELOPER_LEVEL_MEGASPAM <= g_developer) + { + const dplane_t *plane = &g_dplanes[f->planenum]; + inaccuracy = fabs (DotProduct (f->pts[i], plane->normal) - plane->dist); + inaccuracy_count++; + inaccuracy_total += inaccuracy; + inaccuracy_max = qmax (inaccuracy, inaccuracy_max); + } +#endif + } + fscanf(file, "\n"); + } + + return SurflistFromValidFaces(); +} +#ifdef ZHLT_DETAILBRUSH +static brush_t *ReadBrushes (FILE *file) +{ + brush_t *brushes = NULL; + while (1) + { +#ifdef HLBSP_REMOVEHULL2 + if (file == brushfiles[2] && g_nohull2) + break; +#endif + int r; + int brushinfo; + r = fscanf (file, "%i\n", &brushinfo); + if (r == 0 || r == -1) + { + if (brushes == NULL) + { + Error ("ReadBrushes: no more models"); + } + else + { + Error ("ReadBrushes: file end"); + } + } + if (brushinfo == -1) + { + break; + } + brush_t *b; + b = AllocBrush (); + b->next = brushes; + brushes = b; + side_t **psn; + psn = &b->sides; + while (1) + { + int planenum; + int numpoints; + r = fscanf (file, "%i %u\n", &planenum, &numpoints); + if (r != 2) + { + Error ("ReadBrushes: get side failed"); + } + if (planenum == -1) + { + break; + } + side_t *s; + s = AllocSide (); + s->plane = g_dplanes[planenum ^ 1]; + s->w = new Winding (numpoints); + int x; + for (x = 0; x < numpoints; x++) + { + double v[3]; + r = fscanf (file, "%lf %lf %lf\n", &v[0], &v[1], &v[2]); + if (r != 3) + { + Error ("ReadBrushes: get point failed"); + } + VectorCopy (v, s->w->m_Points[numpoints - 1 - x]); + } + s->next = NULL; + *psn = s; + psn = &s->next; + } + } + return brushes; +} +#endif + + +// ===================================================================================== +// ProcessModel +// ===================================================================================== +static bool ProcessModel() +{ + surfchain_t* surfs; +#ifdef ZHLT_DETAILBRUSH + brush_t *detailbrushes; +#endif + node_t* nodes; + dmodel_t* model; + int startleafs; + + surfs = ReadSurfs(polyfiles[0]); + + if (!surfs) + return false; // all models are done +#ifdef ZHLT_DETAILBRUSH + detailbrushes = ReadBrushes (brushfiles[0]); +#endif + + hlassume(g_nummodels < MAX_MAP_MODELS, assume_MAX_MAP_MODELS); + + startleafs = g_numleafs; + int modnum = g_nummodels; + model = &g_dmodels[modnum]; + g_nummodels++; + +// Log("ProcessModel: %i (%i f)\n", modnum, model->numfaces); + + g_hullnum = 0; //vluzacn +#ifdef HLCSG_HLBSP_ALLOWEMPTYENTITY + VectorFill (model->mins, 99999); + VectorFill (model->maxs, -99999); + { + if (surfs->mins[0] > surfs->maxs[0]) + { + Developer (DEVELOPER_LEVEL_FLUFF, "model %d hull %d empty\n", modnum, g_hullnum); + } + else + { + vec3_t mins, maxs; + int i; + VectorSubtract (surfs->mins, g_hull_size[g_hullnum][0], mins); + VectorSubtract (surfs->maxs, g_hull_size[g_hullnum][1], maxs); + for (i = 0; i < 3; i++) + { + if (mins[i] > maxs[i]) + { + vec_t tmp; + tmp = (mins[i] + maxs[i]) / 2; + mins[i] = tmp; + maxs[i] = tmp; + } + } + for (i = 0; i < 3; i++) + { + model->maxs[i] = qmax (model->maxs[i], maxs[i]); + model->mins[i] = qmin (model->mins[i], mins[i]); + } + } + } +#else + VectorCopy(surfs->mins, model->mins); + VectorCopy(surfs->maxs, model->maxs); +#endif + + // SolidBSP generates a node tree + nodes = SolidBSP(surfs, +#ifdef ZHLT_DETAILBRUSH + detailbrushes, +#endif + modnum==0); + + // build all the portals in the bsp tree + // some portals are solid polygons, and some are paths to other leafs + if (g_nummodels == 1 && !g_nofill) // assume non-world bmodels are simple + { +#ifdef HLBSP_FILL + if (!g_noinsidefill) + FillInside (nodes); +#endif + nodes = FillOutside(nodes, (g_bLeaked != true), 0); // make a leakfile if bad + } + + FreePortals(nodes); + + // fix tjunctions + tjunc(nodes); + + MakeFaceEdges(); + + // emit the faces for the bsp file + model->headnode[0] = g_numnodes; + model->firstface = g_numfaces; +#ifdef HLCSG_HLBSP_ALLOWEMPTYENTITY + bool novisiblebrushes = false; + // model->headnode[0]<0 will crash HL, so must split it. + if (nodes->planenum == -1) + { + novisiblebrushes = true; + if (nodes->markfaces[0] != NULL) + hlassume(false, assume_EmptySolid); + if (g_numplanes == 0) + Error ("No valid planes.\n"); + nodes->planenum = 0; // arbitrary plane + nodes->children[0] = AllocNode (); + nodes->children[0]->planenum = -1; + nodes->children[0]->contents = CONTENTS_EMPTY; +#ifdef ZHLT_DETAILBRUSH + nodes->children[0]->isdetail = false; + nodes->children[0]->isportalleaf = true; + nodes->children[0]->iscontentsdetail = false; +#endif + nodes->children[0]->faces = NULL; + nodes->children[0]->markfaces = (face_t**)calloc (1, sizeof(face_t*)); + VectorFill (nodes->children[0]->mins, 0); + VectorFill (nodes->children[0]->maxs, 0); + nodes->children[1] = AllocNode (); + nodes->children[1]->planenum = -1; + nodes->children[1]->contents = CONTENTS_EMPTY; +#ifdef ZHLT_DETAILBRUSH + nodes->children[1]->isdetail = false; + nodes->children[1]->isportalleaf = true; + nodes->children[1]->iscontentsdetail = false; +#endif + nodes->children[1]->faces = NULL; + nodes->children[1]->markfaces = (face_t**)calloc (1, sizeof(face_t*)); + VectorFill (nodes->children[1]->mins, 0); + VectorFill (nodes->children[1]->maxs, 0); + nodes->contents = 0; +#ifdef ZHLT_DETAILBRUSH + nodes->isdetail = false; + nodes->isportalleaf = false; +#endif + nodes->faces = NULL; + nodes->markfaces = NULL; + VectorFill (nodes->mins, 0); + VectorFill (nodes->maxs, 0); + } +#endif + WriteDrawNodes(nodes); + model->numfaces = g_numfaces - model->firstface; + model->visleafs = g_numleafs - startleafs; + + if (g_noclip) + { + /* + KGP 12/31/03 - store empty content type in headnode pointers to signify + lack of clipping information in a way that doesn't crash the half-life + engine at runtime. + */ + model->headnode[1] = CONTENTS_EMPTY; + model->headnode[2] = CONTENTS_EMPTY; + model->headnode[3] = CONTENTS_EMPTY; +#if defined (HLCSG_HLBSP_CUSTOMBOUNDINGBOX) || defined (HLCSG_HLBSP_ALLOWEMPTYENTITY) + goto skipclip; +#else + return true; +#endif + } + + // the clipping hulls are simpler + for (g_hullnum = 1; g_hullnum < NUM_HULLS; g_hullnum++) + { + surfs = ReadSurfs(polyfiles[g_hullnum]); +#ifdef ZHLT_DETAILBRUSH + detailbrushes = ReadBrushes (brushfiles[g_hullnum]); +#endif +#ifdef HLCSG_HLBSP_ALLOWEMPTYENTITY + { + int hullnum = g_hullnum; + if (surfs->mins[0] > surfs->maxs[0]) + { + Developer (DEVELOPER_LEVEL_MESSAGE, "model %d hull %d empty\n", modnum, hullnum); + } + else + { + vec3_t mins, maxs; + int i; + VectorSubtract (surfs->mins, g_hull_size[hullnum][0], mins); + VectorSubtract (surfs->maxs, g_hull_size[hullnum][1], maxs); + for (i = 0; i < 3; i++) + { + if (mins[i] > maxs[i]) + { + vec_t tmp; + tmp = (mins[i] + maxs[i]) / 2; + mins[i] = tmp; + maxs[i] = tmp; + } + } + for (i = 0; i < 3; i++) + { + model->maxs[i] = qmax (model->maxs[i], maxs[i]); + model->mins[i] = qmin (model->mins[i], mins[i]); + } + } + } +#endif + nodes = SolidBSP(surfs, +#ifdef ZHLT_DETAILBRUSH + detailbrushes, +#endif + modnum==0); + if (g_nummodels == 1 && !g_nofill) // assume non-world bmodels are simple + { + nodes = FillOutside(nodes, (g_bLeaked != true), g_hullnum); + } + FreePortals(nodes); + /* + KGP 12/31/03 - need to test that the head clip node isn't empty; if it is + we need to set model->headnode equal to the content type of the head, or create + a trivial single-node case where the content type is the same for both leaves + if setting the content type is invalid. + */ + if(nodes->planenum == -1) //empty! + { + model->headnode[g_hullnum] = nodes->contents; + } + else + { +#ifdef ZHLT_XASH2 + model->headnode[g_hullnum] = g_numclipnodes[g_hullnum - 1]; +#else + model->headnode[g_hullnum] = g_numclipnodes; +#endif + WriteClipNodes(nodes); + } + } +#if defined (HLCSG_HLBSP_CUSTOMBOUNDINGBOX) || defined (HLCSG_HLBSP_ALLOWEMPTYENTITY) + skipclip: +#endif + +#ifdef HLCSG_HLBSP_CUSTOMBOUNDINGBOX + { + entity_t *ent; + ent = EntityForModel (modnum); + if (ent != &g_entities[0] && *ValueForKey (ent, "zhlt_minsmaxs")) + { + double origin[3], mins[3], maxs[3]; + VectorClear (origin); + sscanf (ValueForKey (ent, "origin"), "%lf %lf %lf", &origin[0], &origin[1], &origin[2]); + if (sscanf (ValueForKey (ent, "zhlt_minsmaxs"), "%lf %lf %lf %lf %lf %lf", &mins[0], &mins[1], &mins[2], &maxs[0], &maxs[1], &maxs[2]) == 6) + { + VectorSubtract (mins, origin, model->mins); + VectorSubtract (maxs, origin, model->maxs); + } + } + } +#endif +#ifdef HLCSG_HLBSP_ALLOWEMPTYENTITY + Developer (DEVELOPER_LEVEL_MESSAGE, "model %d - mins=(%g,%g,%g) maxs=(%g,%g,%g)\n", modnum, + model->mins[0], model->mins[1], model->mins[2], model->maxs[0], model->maxs[1], model->maxs[2]); + if (model->mins[0] > model->maxs[0]) + { + entity_t *ent = EntityForModel (g_nummodels - 1); + if (g_nummodels - 1 != 0 && ent == &g_entities[0]) + { + ent = NULL; + } + Warning ("Empty solid entity: model %d (entity: classname \"%s\", origin \"%s\", targetname \"%s\")", + g_nummodels - 1, + (ent? ValueForKey (ent, "classname"): "unknown"), + (ent? ValueForKey (ent, "origin"): "unknown"), + (ent? ValueForKey (ent, "targetname"): "unknown")); + VectorClear (model->mins); // fix "backward minsmaxs" in HL + VectorClear (model->maxs); + } + else if (novisiblebrushes) + { + entity_t *ent = EntityForModel (g_nummodels - 1); + if (g_nummodels - 1 != 0 && ent == &g_entities[0]) + { + ent = NULL; + } + Warning ("No visible brushes in solid entity: model %d (entity: classname \"%s\", origin \"%s\", targetname \"%s\", range (%.0f,%.0f,%.0f) - (%.0f,%.0f,%.0f))", + g_nummodels - 1, + (ent? ValueForKey (ent, "classname"): "unknown"), + (ent? ValueForKey (ent, "origin"): "unknown"), + (ent? ValueForKey (ent, "targetname"): "unknown"), + model->mins[0], model->mins[1], model->mins[2], model->maxs[0], model->maxs[1], model->maxs[2]); + } +#endif + return true; +} + +// ===================================================================================== +// Usage +// ===================================================================================== +static void Usage() +{ + Banner(); + + Log("\n-= %s Options =-\n\n", g_Program); +#ifdef ZHLT_CONSOLE + Log(" -console # : Set to 0 to turn off the pop-up console (default is 1)\n"); +#endif +#ifdef ZHLT_LANGFILE + Log(" -lang file : localization file\n"); +#endif + Log(" -leakonly : Run BSP only enough to check for LEAKs\n"); + Log(" -subdivide # : Sets the face subdivide size\n"); + Log(" -maxnodesize # : Sets the maximum portal node size\n\n"); + Log(" -notjunc : Don't break edges on t-junctions (not for final runs)\n"); +#ifdef HLBSP_BRINKHACK + Log(" -nobrink : Don't smooth brinks (not for final runs)\n"); +#endif + Log(" -noclip : Don't process the clipping hull (not for final runs)\n"); + Log(" -nofill : Don't fill outside (will mask LEAKs) (not for final runs)\n"); +#ifdef HLBSP_FILL + Log(" -noinsidefill : Don't fill empty spaces\n"); +#endif + Log(" -noopt : Don't optimize planes on BSP write (not for final runs)\n"); +#ifdef HLBSP_MERGECLIPNODE + Log(" -noclipnodemerge: Don't optimize clipnodes\n"); +#endif + Log(" -texdata # : Alter maximum texture memory limit (in kb)\n"); + Log(" -lightdata # : Alter maximum lighting memory limit (in kb)\n"); + Log(" -chart : display bsp statitics\n"); + Log(" -low | -high : run program an altered priority level\n"); + Log(" -nolog : don't generate the compile logfiles\n"); + Log(" -threads # : manually specify the number of threads to run\n"); +#ifdef SYSTEM_WIN32 + Log(" -estimate : display estimated time during compile\n"); +#endif +#ifdef ZHLT_PROGRESSFILE // AJM + Log(" -progressfile path : specify the path to a file for progress estimate output\n"); +#endif +#ifdef SYSTEM_POSIX + Log(" -noestimate : do not display continuous compile time estimates\n"); +#endif + +#ifdef ZHLT_NULLTEX // AJM + Log(" -nonulltex : Don't strip NULL faces\n"); +#endif + +#ifdef ZHLT_DETAIL // AJM + Log(" -nodetail : don't handle detail brushes\n"); +#endif + +#ifdef HLBSP_REMOVEHULL2 + Log(" -nohull2 : Don't generate hull 2 (the clipping hull for large monsters and pushables)\n"); +#endif + +#ifdef HLBSP_VIEWPORTAL + Log(" -viewportal : Show portal boundaries in 'mapname_portal.pts' file\n"); +#endif + + Log(" -verbose : compile with verbose messages\n"); + Log(" -noinfo : Do not show tool configuration information\n"); + Log(" -dev # : compile with developer message\n\n"); + Log(" mapfile : The mapfile to compile\n\n"); + + exit(1); +} + +// ===================================================================================== +// Settings +// ===================================================================================== +static void Settings() +{ + char* tmp; + + if (!g_info) + return; + + Log("\nCurrent %s Settings\n", g_Program); + Log("Name | Setting | Default\n" "-------------------|-----------|-------------------------\n"); + + // ZHLT Common Settings + if (DEFAULT_NUMTHREADS == -1) + { + Log("threads [ %7d ] [ Varies ]\n", g_numthreads); + } + else + { + Log("threads [ %7d ] [ %7d ]\n", g_numthreads, DEFAULT_NUMTHREADS); + } + + Log("verbose [ %7s ] [ %7s ]\n", g_verbose ? "on" : "off", DEFAULT_VERBOSE ? "on" : "off"); + Log("log [ %7s ] [ %7s ]\n", g_log ? "on" : "off", DEFAULT_LOG ? "on" : "off"); + Log("developer [ %7d ] [ %7d ]\n", g_developer, DEFAULT_DEVELOPER); + Log("chart [ %7s ] [ %7s ]\n", g_chart ? "on" : "off", DEFAULT_CHART ? "on" : "off"); + Log("estimate [ %7s ] [ %7s ]\n", g_estimate ? "on" : "off", DEFAULT_ESTIMATE ? "on" : "off"); + Log("max texture memory [ %7d ] [ %7d ]\n", g_max_map_miptex, DEFAULT_MAX_MAP_MIPTEX); + + switch (g_threadpriority) + { + case eThreadPriorityNormal: + default: + tmp = "Normal"; + break; + case eThreadPriorityLow: + tmp = "Low"; + break; + case eThreadPriorityHigh: + tmp = "High"; + break; + } + Log("priority [ %7s ] [ %7s ]\n", tmp, "Normal"); + Log("\n"); + + // HLBSP Specific Settings + Log("noclip [ %7s ] [ %7s ]\n", g_noclip ? "on" : "off", DEFAULT_NOCLIP ? "on" : "off"); + Log("nofill [ %7s ] [ %7s ]\n", g_nofill ? "on" : "off", DEFAULT_NOFILL ? "on" : "off"); +#ifdef HLBSP_FILL + Log("noinsidefill [ %7s ] [ %7s ]\n", g_noinsidefill ? "on" : "off", DEFAULT_NOINSIDEFILL ? "on" : "off"); +#endif + Log("noopt [ %7s ] [ %7s ]\n", g_noopt ? "on" : "off", DEFAULT_NOOPT ? "on" : "off"); +#ifdef HLBSP_MERGECLIPNODE + Log("no clipnode merging [ %7s ] [ %7s ]\n", g_noclipnodemerge? "on": "off", DEFAULT_NOCLIPNODEMERGE? "on": "off"); +#endif +#ifdef ZHLT_NULLTEX // AJM + Log("null tex. stripping [ %7s ] [ %7s ]\n", g_bUseNullTex ? "on" : "off", DEFAULT_NULLTEX ? "on" : "off" ); +#endif +#ifdef ZHLT_DETAIL // AJM + Log("detail brushes [ %7s ] [ %7s ]\n", g_bDetailBrushes ? "on" : "off", DEFAULT_DETAIL ? "on" : "off" ); +#endif + Log("notjunc [ %7s ] [ %7s ]\n", g_notjunc ? "on" : "off", DEFAULT_NOTJUNC ? "on" : "off"); +#ifdef HLBSP_BRINKHACK + Log("nobrink [ %7s ] [ %7s ]\n", g_nobrink? "on": "off", DEFAULT_NOBRINK? "on": "off"); +#endif + Log("subdivide size [ %7d ] [ %7d ] (Min %d) (Max %d)\n", + g_subdivide_size, DEFAULT_SUBDIVIDE_SIZE, MIN_SUBDIVIDE_SIZE, MAX_SUBDIVIDE_SIZE); + Log("max node size [ %7d ] [ %7d ] (Min %d) (Max %d)\n", + g_maxnode_size, DEFAULT_MAXNODE_SIZE, MIN_MAXNODE_SIZE, MAX_MAXNODE_SIZE); +#ifdef HLBSP_REMOVEHULL2 + Log("remove hull 2 [ %7s ] [ %7s ]\n", g_nohull2? "on": "off", "off"); +#endif + Log("\n\n"); +} + +// ===================================================================================== +// ProcessFile +// ===================================================================================== +static void ProcessFile(const char* const filename) +{ + int i; + char name[_MAX_PATH]; + + // delete existing files + safe_snprintf(g_portfilename, _MAX_PATH, "%s.prt", filename); + unlink(g_portfilename); + + safe_snprintf(g_pointfilename, _MAX_PATH, "%s.pts", filename); + unlink(g_pointfilename); + + safe_snprintf(g_linefilename, _MAX_PATH, "%s.lin", filename); + unlink(g_linefilename); + +#ifdef ZHLT_64BIT_FIX + safe_snprintf (g_extentfilename, _MAX_PATH, "%s.ext", filename); + unlink (g_extentfilename); +#endif + // open the hull files + for (i = 0; i < NUM_HULLS; i++) + { + //mapname.p[0-3] + sprintf(name, "%s.p%i", filename, i); + polyfiles[i] = fopen(name, "r"); + + if (!polyfiles[i]) + Error("Can't open %s", name); +#ifdef ZHLT_DETAILBRUSH + sprintf(name, "%s.b%i", filename, i); + brushfiles[i] = fopen(name, "r"); + if (!brushfiles[i]) + Error("Can't open %s", name); +#endif + } +#ifdef HLCSG_HLBSP_ALLOWEMPTYENTITY + { + FILE *f; + char name[_MAX_PATH]; + safe_snprintf (name, _MAX_PATH, "%s.hsz", filename); + f = fopen (name, "r"); + if (!f) + { + Warning("Couldn't open %s", name); + } + else + { + float x1,y1,z1; + float x2,y2,z2; + for (i = 0; i < NUM_HULLS; i++) + { + int count; + count = fscanf (f, "%f %f %f %f %f %f\n", &x1, &y1, &z1, &x2, &y2, &z2); + if (count != 6) + { + Error ("Load hull size (line %i): scanf failure", i+1); + } + g_hull_size[i][0][0] = x1; + g_hull_size[i][0][1] = y1; + g_hull_size[i][0][2] = z1; + g_hull_size[i][1][0] = x2; + g_hull_size[i][1][1] = y2; + g_hull_size[i][1][2] = z2; + } + fclose (f); + } + } +#endif + + // load the output of csg + safe_snprintf(g_bspfilename, _MAX_PATH, "%s.bsp", filename); + LoadBSPFile(g_bspfilename); + ParseEntities(); + + Settings(); // AJM: moved here due to info_compile_parameters entity + +#ifdef HLCSG_HLBSP_DOUBLEPLANE + { + char name[_MAX_PATH]; + safe_snprintf (name, _MAX_PATH, "%s.pln", filename); + FILE *planefile = fopen (name, "rb"); + if (!planefile) + { + Warning("Couldn't open %s", name); +#undef dplane_t +#undef g_dplanes + for (i = 0; i < g_numplanes; i++) + { + plane_t *mp = &g_mapplanes[i]; + dplane_t *dp = &g_dplanes[i]; + VectorCopy (dp->normal, mp->normal); + mp->dist = dp->dist; + mp->type = dp->type; + } +#define dplane_t plane_t +#define g_dplanes g_mapplanes + } + else + { + if (q_filelength (planefile) != g_numplanes * sizeof (dplane_t)) + { + Error ("Invalid plane data"); + } + SafeRead (planefile, g_dplanes, g_numplanes * sizeof (dplane_t)); + fclose (planefile); + } + } +#endif + // init the tables to be shared by all models + BeginBSPFile(); + + // process each model individually + while (ProcessModel()) + ; + + // write the updated bsp file out + FinishBSPFile(); +#ifdef HLBSP_DELETETEMPFILES + + // Because the bsp file has been updated, these polyfiles are no longer valid. + for (i = 0; i < NUM_HULLS; i++) + { + sprintf (name, "%s.p%i", filename, i); + fclose (polyfiles[i]); + polyfiles[i] = NULL; + unlink (name); +#ifdef ZHLT_DETAILBRUSH + sprintf(name, "%s.b%i", filename, i); + fclose (brushfiles[i]); + brushfiles[i] = NULL; + unlink (name); +#endif + } +#ifdef HLCSG_HLBSP_ALLOWEMPTYENTITY + safe_snprintf (name, _MAX_PATH, "%s.hsz", filename); + unlink (name); +#endif +#ifdef HLCSG_HLBSP_DOUBLEPLANE + safe_snprintf (name, _MAX_PATH, "%s.pln", filename); + unlink (name); +#endif +#endif +} + +// ===================================================================================== +// main +// ===================================================================================== +int main(const int argc, char** argv) +{ + int i; + double start, end; + const char* mapname_from_arg = NULL; + + g_Program = "hlbsp"; + +#ifdef ZHLT_PARAMFILE + int argcold = argc; + char ** argvold = argv; + { + int argc; + char ** argv; + ParseParamFile (argcold, argvold, argc, argv); + { +#endif +#ifdef ZHLT_CONSOLE + if (InitConsole (argc, argv) < 0) + Usage(); +#endif + // if we dont have any command line argvars, print out usage and die + if (argc == 1) + Usage(); + + // check command line args + for (i = 1; i < argc; i++) + { + if (!strcasecmp(argv[i], "-threads")) + { + if (i + 1 < argc) //added "1" .--vluzacn + { + int g_numthreads = atoi(argv[++i]); + + if (g_numthreads < 1) + { + Log("Expected value of at least 1 for '-threads'\n"); + Usage(); + } + } + else + { + Usage(); + } + } +#ifdef ZHLT_CONSOLE + else if (!strcasecmp(argv[i], "-console")) + { +#ifndef SYSTEM_WIN32 + Warning("The option '-console #' is only valid for Windows."); +#endif + if (i + 1 < argc) + ++i; + else + Usage(); + } +#endif + else if (!strcasecmp(argv[i], "-notjunc")) + { + g_notjunc = true; + } +#ifdef HLBSP_BRINKHACK + else if (!strcasecmp (argv[i], "-nobrink")) + { + g_nobrink = true; + } +#endif + else if (!strcasecmp(argv[i], "-noclip")) + { + g_noclip = true; + } + else if (!strcasecmp(argv[i], "-nofill")) + { + g_nofill = true; + } +#ifdef HLBSP_FILL + else if (!strcasecmp(argv[i], "-noinsidefill")) + { + g_noinsidefill = true; + } +#endif + +#ifdef SYSTEM_WIN32 + else if (!strcasecmp(argv[i], "-estimate")) + { + g_estimate = true; + } +#endif + +#ifdef SYSTEM_POSIX + else if (!strcasecmp(argv[i], "-noestimate")) + { + g_estimate = false; + } +#endif + +#ifdef ZHLT_NETVIS + else if (!strcasecmp(argv[i], "-client")) + { + if (i + 1 < argc) //added "1" .--vluzacn + { + g_clientid = atoi(argv[++i]); + } + else + { + Usage(); + } + } +#endif + +#ifdef ZHLT_PROGRESSFILE // AJM + else if (!strcasecmp(argv[i], "-progressfile")) + { + if (i + 1 < argc) //added "1" .--vluzacn + { + g_progressfile = argv[++i]; + } + else + { + Log("Error: -progressfile: expected path to progress file following parameter\n"); + Usage(); + } + } +#endif + + else if (!strcasecmp(argv[i], "-dev")) + { + if (i + 1 < argc) //added "1" .--vluzacn + { + g_developer = (developer_level_t)atoi(argv[++i]); + } + else + { + Usage(); + } + } + else if (!strcasecmp(argv[i], "-verbose")) + { + g_verbose = true; + } + else if (!strcasecmp(argv[i], "-noinfo")) + { + g_info = false; + } + else if (!strcasecmp(argv[i], "-leakonly")) + { + g_bLeakOnly = true; + } + else if (!strcasecmp(argv[i], "-chart")) + { + g_chart = true; + } + else if (!strcasecmp(argv[i], "-low")) + { + g_threadpriority = eThreadPriorityLow; + } + else if (!strcasecmp(argv[i], "-high")) + { + g_threadpriority = eThreadPriorityHigh; + } + else if (!strcasecmp(argv[i], "-nolog")) + { + g_log = false; + } + +#ifdef ZHLT_NULLTEX // AJM + else if (!strcasecmp(argv[i], "-nonulltex")) + { + g_bUseNullTex = false; + } +#endif + +#ifdef ZHLT_DETAIL // AJM + else if (!strcasecmp(argv[i], "-nodetail")) + { + g_bDetailBrushes = false; + } +#endif + +#ifdef HLBSP_REMOVEHULL2 + else if (!strcasecmp (argv[i], "-nohull2")) + { + g_nohull2 = true; + } +#endif + + else if (!strcasecmp(argv[i], "-noopt")) + { + g_noopt = true; + } +#ifdef HLBSP_MERGECLIPNODE + else if (!strcasecmp (argv[i], "-noclipnodemerge")) + { + g_noclipnodemerge = true; + } +#endif + else if (!strcasecmp(argv[i], "-subdivide")) + { + if (i + 1 < argc) //added "1" .--vluzacn + { + g_subdivide_size = atoi(argv[++i]); + if (g_subdivide_size > MAX_SUBDIVIDE_SIZE) + { + Warning + ("Maximum value for subdivide size is %i, '-subdivide %i' ignored", + MAX_SUBDIVIDE_SIZE, g_subdivide_size); + g_subdivide_size = MAX_SUBDIVIDE_SIZE; + } + else if (g_subdivide_size < MIN_SUBDIVIDE_SIZE) + { + Warning + ("Mininum value for subdivide size is %i, '-subdivide %i' ignored", + MIN_SUBDIVIDE_SIZE, g_subdivide_size); + g_subdivide_size = MIN_SUBDIVIDE_SIZE; //MAX_SUBDIVIDE_SIZE; //--vluzacn + } + } + else + { + Usage(); + } + } + else if (!strcasecmp(argv[i], "-maxnodesize")) + { + if (i + 1 < argc) //added "1" .--vluzacn + { + g_maxnode_size = atoi(argv[++i]); + if (g_maxnode_size > MAX_MAXNODE_SIZE) + { + Warning + ("Maximum value for max node size is %i, '-maxnodesize %i' ignored", + MAX_MAXNODE_SIZE, g_maxnode_size); + g_maxnode_size = MAX_MAXNODE_SIZE; + } + else if (g_maxnode_size < MIN_MAXNODE_SIZE) + { + Warning + ("Mininimum value for max node size is %i, '-maxnodesize %i' ignored", + MIN_MAXNODE_SIZE, g_maxnode_size); + g_maxnode_size = MIN_MAXNODE_SIZE; //MAX_MAXNODE_SIZE; //vluzacn + } + } + else + { + Usage(); + } + } +#ifdef HLBSP_VIEWPORTAL + else if (!strcasecmp (argv[i], "-viewportal")) + { + g_viewportal = true; + } +#endif + else if (!strcasecmp(argv[i], "-texdata")) + { + if (i + 1 < argc) //added "1" .--vluzacn + { + int x = atoi(argv[++i]) * 1024; + + //if (x > g_max_map_miptex) //--vluzacn + { + g_max_map_miptex = x; + } + } + else + { + Usage(); + } + } + else if (!strcasecmp(argv[i], "-lightdata")) + { + if (i + 1 < argc) //added "1" .--vluzacn + { + int x = atoi(argv[++i]) * 1024; + + //if (x > g_max_map_lightdata) //--vluzacn + { + g_max_map_lightdata = x; + } + } + else + { + Usage(); + } + } +#ifdef ZHLT_LANGFILE + else if (!strcasecmp (argv[i], "-lang")) + { + if (i + 1 < argc) + { + char tmp[_MAX_PATH]; +#ifdef SYSTEM_WIN32 + GetModuleFileName (NULL, tmp, _MAX_PATH); +#else + safe_strncpy (tmp, argv[0], _MAX_PATH); +#endif + LoadLangFile (argv[++i], tmp); + } + else + { + Usage(); + } + } +#endif + else if (argv[i][0] == '-') + { + Log("Unknown option \"%s\"\n", argv[i]); + Usage(); + } + else if (!mapname_from_arg) + { + mapname_from_arg = argv[i]; + } + else + { + Log("Unknown option \"%s\"\n", argv[i]); + Usage(); + } + } +#ifdef HLBSP_SUBDIVIDE_INMID + if (g_subdivide_size % TEXTURE_STEP != 0) + { + Warning ("Subdivide size must be a multiple of %d", (int)TEXTURE_STEP); + g_subdivide_size = TEXTURE_STEP * (g_subdivide_size / TEXTURE_STEP); + } +#endif + + if (!mapname_from_arg) + { + Log("No mapfile specified\n"); + Usage(); + } + + safe_strncpy(g_Mapname, mapname_from_arg, _MAX_PATH); + FlipSlashes(g_Mapname); + StripExtension(g_Mapname); + OpenLog(g_clientid); + atexit(CloseLog); + ThreadSetDefault(); + ThreadSetPriority(g_threadpriority); +#ifdef ZHLT_PARAMFILE + LogStart(argcold, argvold); + { + int i; + Log("Arguments: "); + for (i = 1; i < argc; i++) + { + if (strchr(argv[i], ' ')) + { + Log("\"%s\" ", argv[i]); + } + else + { + Log("%s ", argv[i]); + } + } + Log("\n"); + } +#else + LogStart(argc, argv); +#endif + + CheckForErrorLog(); + +#ifdef ZHLT_64BIT_FIX +#ifdef PLATFORM_CAN_CALC_EXTENT + hlassume (CalcFaceExtents_test (), assume_first); +#endif +#endif + dtexdata_init(); + atexit(dtexdata_free); + //Settings(); + // END INIT + + // Load the .void files for allowable entities in the void + { +#ifndef ZHLT_DEFAULTEXTENSION_FIX + char g_source[_MAX_PATH]; +#endif + char strSystemEntitiesVoidFile[_MAX_PATH]; + char strMapEntitiesVoidFile[_MAX_PATH]; + +#ifndef ZHLT_DEFAULTEXTENSION_FIX + safe_strncpy(g_source, mapname_from_arg, _MAX_PATH); + StripExtension(g_source); +#endif + + // try looking in the current directory + safe_strncpy(strSystemEntitiesVoidFile, ENTITIES_VOID, _MAX_PATH); + if (!q_exists(strSystemEntitiesVoidFile)) + { + char tmp[_MAX_PATH]; + // try looking in the directory we were run from +#ifdef SYSTEM_WIN32 + GetModuleFileName(NULL, tmp, _MAX_PATH); +#else + safe_strncpy(tmp, argv[0], _MAX_PATH); +#endif + ExtractFilePath(tmp, strSystemEntitiesVoidFile); + safe_strncat(strSystemEntitiesVoidFile, ENTITIES_VOID, _MAX_PATH); + } + + // Set the optional level specific lights filename +#ifdef ZHLT_DEFAULTEXTENSION_FIX + safe_snprintf(strMapEntitiesVoidFile, _MAX_PATH, "%s" ENTITIES_VOID_EXT, g_Mapname); +#else + safe_strncpy(strMapEntitiesVoidFile, g_source, _MAX_PATH); + DefaultExtension(strMapEntitiesVoidFile, ENTITIES_VOID_EXT); +#endif + + LoadAllowableOutsideList(strSystemEntitiesVoidFile); // default entities.void + if (*strMapEntitiesVoidFile) + { + LoadAllowableOutsideList(strMapEntitiesVoidFile); // automatic mapname.void + } + } + + // BEGIN BSP + start = I_FloatTime(); + + ProcessFile(g_Mapname); + + end = I_FloatTime(); + LogTimeElapsed(end - start); + // END BSP + + FreeAllowableOutsideList(); + +#ifdef ZHLT_PARAMFILE + } + } +#endif + return 0; +} diff --git a/src/zhlt-vluzacn/hlbsp/solidbsp.cpp b/src/zhlt-vluzacn/hlbsp/solidbsp.cpp new file mode 100644 index 0000000..b26ff2e --- /dev/null +++ b/src/zhlt-vluzacn/hlbsp/solidbsp.cpp @@ -0,0 +1,2430 @@ +//#pragma warning(disable: 4018) // '<' : signed/unsigned mismatch + +#include "bsp5.h" + +// FaceSide +// ChooseMidPlaneFromList +// ChoosePlaneFromList +// SelectPartition + +// CalcSurfaceInfo +// DivideSurface +// SplitNodeSurfaces +// RankForContents +// ContentsForRank + +// FreeLeafSurfs +// LinkLeafFaces +// MakeNodePortal +// SplitNodePortals +// CalcNodeBounds +// CopyFacesToNode +// BuildBspTree_r +// SolidBSP + +// Each node or leaf will have a set of portals that completely enclose +// the volume of the node and pass into an adjacent node. +#ifdef HLBSP_FAST_SELECTPARTITION +#include +#endif + +int g_maxnode_size = DEFAULT_MAXNODE_SIZE; + +static bool g_reportProgress = false; +static int g_numProcessed = 0; +static int g_numReported = 0; + +static void ResetStatus(bool report_progress) +{ + g_reportProgress = report_progress; + g_numProcessed = g_numReported = 0; +} + +static void UpdateStatus(void) +{ + if(g_reportProgress) + { + ++g_numProcessed; + if((g_numProcessed / 500) > g_numReported) + { + g_numReported = (g_numProcessed / 500); + Log("%d...",g_numProcessed); + } + } +} + +// ===================================================================================== +// FaceSide +// For BSP hueristic +// ===================================================================================== +static int FaceSide(face_t* in, const dplane_t* const split +#ifdef HLBSP_AVOIDEPSILONSPLIT + , double *epsilonsplit = NULL +#endif + ) +{ +#ifdef HLBSP_AVOIDEPSILONSPLIT + const vec_t epsilonmin = 0.002, epsilonmax = 0.2; + vec_t d_front, d_back; +#else + int frontcount, backcount; +#endif + vec_t dot; + int i; + vec_t* p; + +#ifdef HLBSP_AVOIDEPSILONSPLIT + d_front = d_back = 0; +#else + frontcount = backcount = 0; +#endif + + // axial planes are fast + if (split->type <= last_axial) + { + vec_t splitGtEp = split->dist + ON_EPSILON; // Invariant moved out of loop + vec_t splitLtEp = split->dist - ON_EPSILON; // Invariant moved out of loop + + for (i = 0, p = in->pts[0] + split->type; i < in->numpoints; i++, p += 3) + { +#ifdef HLBSP_AVOIDEPSILONSPLIT + dot = *p - split->dist; + if (dot > d_front) + d_front = dot; + if (dot < d_back) + d_back = dot; +#else + if (*p > splitGtEp) + { + if (backcount) + { + return SIDE_ON; + } + frontcount = 1; + } + else if (*p < splitLtEp) + { + if (frontcount) + { + return SIDE_ON; + } + backcount = 1; + } +#endif + } + } + else + { + // sloping planes take longer + for (i = 0, p = in->pts[0]; i < in->numpoints; i++, p += 3) + { + dot = DotProduct(p, split->normal); + dot -= split->dist; +#ifdef HLBSP_AVOIDEPSILONSPLIT + if (dot > d_front) + d_front = dot; + if (dot < d_back) + d_back = dot; +#else + if (dot > ON_EPSILON) + { + if (backcount) + { + return SIDE_ON; + } + frontcount = 1; + } + else if (dot < -ON_EPSILON) + { + if (frontcount) + { + return SIDE_ON; + } + backcount = 1; + } +#endif + } + } +#ifdef HLBSP_AVOIDEPSILONSPLIT + if (d_front <= ON_EPSILON) + { + if (d_front > epsilonmin || d_back > -epsilonmax) + { + if (epsilonsplit) + (*epsilonsplit)++; + } + return SIDE_BACK; + } + if (d_back >= -ON_EPSILON) + { + if (d_back < -epsilonmin || d_front < epsilonmax) + { + if (epsilonsplit) + (*epsilonsplit)++; + } + return SIDE_FRONT; + } + if (d_front < epsilonmax || d_back > -epsilonmax) + { + if (epsilonsplit) + (*epsilonsplit)++; + } + return SIDE_ON; +#else + + if (!frontcount) + { + return SIDE_BACK; + } + if (!backcount) + { + return SIDE_FRONT; + } + + return SIDE_ON; +#endif +} + +#ifdef HLBSP_FAST_SELECTPARTITION +// organize all surfaces into a tree structure to accelerate intersection test +// can reduce more than 90% compile time for very complicated maps + +typedef struct surfacetreenode_s +{ + int size; // can be zero, which invalidates mins and maxs +#ifdef HLCSG_HLBSP_SOLIDHINT + int size_discardable; +#endif + vec3_t mins; + vec3_t maxs; + bool isleaf; + // node + surfacetreenode_s *children[2]; + std::vector< face_t * > *nodefaces; +#ifdef HLCSG_HLBSP_SOLIDHINT + int nodefaces_discardablesize; +#endif + // leaf + std::vector< face_t * > *leaffaces; +} +surfacetreenode_t; + +typedef struct +{ + bool dontbuild; + vec_t epsilon; // if a face is not epsilon far from the splitting plane, put it in result.middle + surfacetreenode_t *headnode; + struct + { + int frontsize; + int backsize; + std::vector< face_t * > *middle; // may contains coplanar faces and discardable(SOLIDHINT) faces + } + result; // "public" +} +surfacetree_t; + +void BuildSurfaceTree_r (surfacetree_t *tree, surfacetreenode_t *node) +{ + node->size = node->leaffaces->size (); +#ifdef HLCSG_HLBSP_SOLIDHINT + node->size_discardable = 0; +#endif + if (node->size == 0) + { + node->isleaf = true; + return; + } + + VectorFill (node->mins, BOGUS_RANGE); + VectorFill (node->maxs, -BOGUS_RANGE); + for (std::vector< face_t * >::iterator i = node->leaffaces->begin (); i != node->leaffaces->end (); ++i) + { + face_t *f = *i; + for (int x = 0; x < f->numpoints; x++) + { + VectorCompareMinimum (node->mins, f->pts[x], node->mins); + VectorCompareMaximum (node->maxs, f->pts[x], node->maxs); + } +#ifdef HLCSG_HLBSP_SOLIDHINT + if (f->facestyle == face_discardable) + { + node->size_discardable++; + } +#endif + } + + int bestaxis = -1; + { + vec_t bestdelta = 0; + for (int k = 0; k < 3; k++) + { + if (node->maxs[k] - node->mins[k] > bestdelta + ON_EPSILON) + { + bestaxis = k; + bestdelta = node->maxs[k] - node->mins[k]; + } + } + } + if (node->size <= 5 || tree->dontbuild == true || bestaxis == -1) + { + node->isleaf = true; + return; + } + + node->isleaf = false; + vec_t dist, dist1, dist2; + dist = (node->mins[bestaxis] + node->maxs[bestaxis]) / 2; + dist1 = (3 * node->mins[bestaxis] + node->maxs[bestaxis]) / 4; + dist2 = (node->mins[bestaxis] + 3 * node->maxs[bestaxis]) / 4; + // Each child node is at most 3/4 the size of the parent node. + // Most faces should be passed to a child node, faces left in the parent node are the ones whose dimensions are large enough to be comparable to the dimension of the parent node. + node->nodefaces = new std::vector< face_t * >; +#ifdef HLCSG_HLBSP_SOLIDHINT + node->nodefaces_discardablesize = 0; +#endif + node->children[0] = (surfacetreenode_t *)malloc (sizeof (surfacetreenode_t)); + node->children[0]->leaffaces = new std::vector< face_t * >; + node->children[1] = (surfacetreenode_t *)malloc (sizeof (surfacetreenode_t)); + node->children[1]->leaffaces = new std::vector< face_t * >; + for (std::vector< face_t * >::iterator i = node->leaffaces->begin (); i != node->leaffaces->end (); ++i) + { + face_t *f = *i; + vec_t low = BOGUS_RANGE; + vec_t high = -BOGUS_RANGE; + for (int x = 0; x < f->numpoints; x++) + { + low = qmin (low, f->pts[x][bestaxis]); + high = qmax (high, f->pts[x][bestaxis]); + } + if (low < dist1 + ON_EPSILON && high > dist2 - ON_EPSILON) + { + node->nodefaces->push_back (f); +#ifdef HLCSG_HLBSP_SOLIDHINT + if (f->facestyle == face_discardable) + { + node->nodefaces_discardablesize++; + } +#endif + } + else if (low >= dist1 && high <= dist2) + { + if ((low + high) / 2 > dist) + { + node->children[0]->leaffaces->push_back (f); + } + else + { + node->children[1]->leaffaces->push_back (f); + } + } + else if (low >= dist1) + { + node->children[0]->leaffaces->push_back (f); + } + else if (high <= dist2) + { + node->children[1]->leaffaces->push_back (f); + } + } + if (node->children[0]->leaffaces->size () == node->leaffaces->size () || node->children[1]->leaffaces->size () == node->leaffaces->size ()) + { + Warning ("BuildSurfaceTree_r: didn't split node with bound (%f,%f,%f)-(%f,%f,%f)", node->mins[0], node->mins[1], node->mins[2], node->maxs[0], node->maxs[1], node->maxs[2]); + delete node->children[0]->leaffaces; + delete node->children[1]->leaffaces; + free (node->children[0]); + free (node->children[1]); + delete node->nodefaces; + node->isleaf = true; + return; + } + delete node->leaffaces; + BuildSurfaceTree_r (tree, node->children[0]); + BuildSurfaceTree_r (tree, node->children[1]); +} + +surfacetree_t *BuildSurfaceTree (surface_t *surfaces, vec_t epsilon) +{ + surfacetree_t *tree; + tree = (surfacetree_t *)malloc (sizeof (surfacetree_t)); + tree->epsilon = epsilon; + tree->result.middle = new std::vector< face_t * >; + tree->headnode = (surfacetreenode_t *)malloc (sizeof (surfacetreenode_t)); + tree->headnode->leaffaces = new std::vector< face_t * >; + { + surface_t *p2; + face_t *f; + for (p2 = surfaces; p2; p2 = p2->next) + { + if (p2->onnode) + { + continue; + } + for (f = p2->faces; f; f = f->next) + { + tree->headnode->leaffaces->push_back (f); + } + } + } + tree->dontbuild = tree->headnode->leaffaces->size () < 20; + BuildSurfaceTree_r (tree, tree->headnode); + if (tree->dontbuild) + { + *tree->result.middle = *tree->headnode->leaffaces; + tree->result.backsize = 0; + tree->result.frontsize = 0; + } + return tree; +} + +void TestSurfaceTree_r (surfacetree_t *tree, const surfacetreenode_t *node, const dplane_t *split) +{ + if (node->size == 0) + { + return; + } + vec_t low, high; + low = high = -split->dist; + for (int k = 0; k < 3; k++) + { + if (split->normal[k] >= 0) + { + high += split->normal[k] * node->maxs[k]; + low += split->normal[k] * node->mins[k]; + } + else + { + high += split->normal[k] * node->mins[k]; + low += split->normal[k] * node->maxs[k]; + } + } + if (low > tree->epsilon) + { + tree->result.frontsize += node->size; +#ifdef HLCSG_HLBSP_SOLIDHINT + tree->result.frontsize -= node->size_discardable; +#endif + return; + } + if (high < -tree->epsilon) + { + tree->result.backsize += node->size; +#ifdef HLCSG_HLBSP_SOLIDHINT + tree->result.backsize -= node->size_discardable; +#endif + return; + } + if (node->isleaf) + { + for (std::vector< face_t * >::iterator i = node->leaffaces->begin (); i != node->leaffaces->end (); ++i) + { + tree->result.middle->push_back (*i); + } + } + else + { + for (std::vector< face_t * >::iterator i = node->nodefaces->begin (); i != node->nodefaces->end (); ++i) + { + tree->result.middle->push_back (*i); + } + TestSurfaceTree_r (tree, node->children[0], split); + TestSurfaceTree_r (tree, node->children[1], split); + } +} + +void TestSurfaceTree (surfacetree_t *tree, const dplane_t *split) +{ + if (tree->dontbuild) + { + return; + } + tree->result.middle->clear (); + tree->result.backsize = 0; + tree->result.frontsize = 0; + TestSurfaceTree_r (tree, tree->headnode, split); +} + +void DeleteSurfaceTree_r (surfacetreenode_t *node) +{ + if (node->isleaf) + { + delete node->leaffaces; + } + else + { + DeleteSurfaceTree_r (node->children[0]); + free (node->children[0]); + DeleteSurfaceTree_r (node->children[1]); + free (node->children[1]); + delete node->nodefaces; + } +} + +void DeleteSurfaceTree (surfacetree_t *tree) +{ + DeleteSurfaceTree_r (tree->headnode); + free (tree->headnode); + delete tree->result.middle; + free (tree); +} + +#endif +// ===================================================================================== +// ChooseMidPlaneFromList +// When there are a huge number of planes, just choose one closest +// to the middle. +// ===================================================================================== +static surface_t* ChooseMidPlaneFromList(surface_t* surfaces, const vec3_t mins, const vec3_t maxs +#ifdef ZHLT_DETAILBRUSH + , int detaillevel +#endif + ) +{ + int j, l; + surface_t* p; + surface_t* bestsurface; + vec_t bestvalue; + vec_t value; + vec_t dist; + dplane_t* plane; +#ifdef HLBSP_CHOOSEMIDPLANE + surfacetree_t* surfacetree; + std::vector< face_t * >::iterator it; + face_t* f; + + surfacetree = BuildSurfaceTree (surfaces, ON_EPSILON); +#endif + + // + // pick the plane that splits the least + // +#ifdef HLBSP_CHOOSEMIDPLANE + bestvalue = 9e30; +#else +#ifdef ZHLT_LARGERANGE + bestvalue = 6.0f * BOGUS_RANGE * BOGUS_RANGE; +#else + bestvalue = 6 * 8192 * 8192; +#endif +#endif + bestsurface = NULL; + + for (p = surfaces; p; p = p->next) + { + if (p->onnode) + { + continue; + } +#ifdef ZHLT_DETAILBRUSH + if (p->detaillevel != detaillevel) + { + continue; + } +#endif + + plane = &g_dplanes[p->planenum]; + + // check for axis aligned surfaces + l = plane->type; + if (l > last_axial) + { + continue; + } + + // + // calculate the split metric along axis l, smaller values are better + // + value = 0; + + dist = plane->dist * plane->normal[l]; +#ifdef HLBSP_MAXNODESIZE_SKYBOX + if (maxs[l] - dist < ON_EPSILON || dist - mins[l] < ON_EPSILON) + continue; +#endif +#ifdef HLBSP_ChooseMidPlane_FIX + if (maxs[l] - dist < g_maxnode_size/2.0 - ON_EPSILON || dist - mins[l] < g_maxnode_size/2.0 - ON_EPSILON) + continue; +#endif +#ifdef HLBSP_CHOOSEMIDPLANE + double crosscount = 0; + double frontcount = 0; + double backcount = 0; + double coplanarcount = 0; + + TestSurfaceTree (surfacetree, plane); + frontcount += surfacetree->result.frontsize; + backcount += surfacetree->result.backsize; + for (it = surfacetree->result.middle->begin (); it != surfacetree->result.middle->end (); ++it) + { + f = *it; +#ifdef HLCSG_HLBSP_SOLIDHINT + if (f->facestyle == face_discardable) + { + continue; + } +#endif + if (f->planenum == p->planenum || f->planenum == (p->planenum ^ 1)) + { + coplanarcount++; + continue; + } + switch (FaceSide (f, plane)) + { + case SIDE_FRONT: + frontcount++; + break; + case SIDE_BACK: + backcount++; + break; + case SIDE_ON: + crosscount++; + break; + } + } + + double frontsize = frontcount + 0.5 * coplanarcount + 0.5 * crosscount; + double frontfrac = (maxs[l] - dist) / (maxs[l] - mins[l]); + double backsize = backcount + 0.5 * coplanarcount + 0.5 * crosscount; + double backfrac = (dist - mins[l]) / (maxs[l] - mins[l]); + value = crosscount + 0.1 * (frontsize * (log (frontfrac) / log (2.0)) + backsize * (log (backfrac) / log (2.0))); + // the first part is how the split will increase the number of faces + // the second part is how the split will increase the average depth of the bsp tree +#else + for (j = 0; j < 3; j++) + { + if (j == l) + { + value += (maxs[l] - dist) * (maxs[l] - dist); + value += (dist - mins[l]) * (dist - mins[l]); + } + else + { + value += 2 * (maxs[j] - mins[j]) * (maxs[j] - mins[j]); + } + } +#endif + + if (value > bestvalue) + { + continue; + } + + // + // currently the best! + // + bestvalue = value; + bestsurface = p; + } + +#ifdef HLBSP_CHOOSEMIDPLANE + DeleteSurfaceTree (surfacetree); +#endif + if (!bestsurface) + { +#ifdef HLBSP_ChooseMidPlane_FIX + return NULL; +#else + for (p = surfaces; p; p = p->next) + { + if (!p->onnode) + { +#ifdef ZHLT_DETAILBRUSH + if (p->detaillevel != detaillevel) + { + continue; + } +#endif + return p; // first valid surface + } + } + Error("ChooseMidPlaneFromList: no valid planes"); +#endif + } + + return bestsurface; +} + +// ===================================================================================== +// ChoosePlaneFromList +// Choose the plane that splits the least faces +// ===================================================================================== +#ifdef HLBSP_ChoosePlane_VL +static surface_t* ChoosePlaneFromList(surface_t* surfaces, const vec3_t mins, const vec3_t maxs +#ifdef ZHLT_DETAILBRUSH + // mins and maxs are invalid when detaillevel > 0 + , int detaillevel +#endif + ) +{ + surface_t* p; + surface_t* p2; + surface_t* bestsurface; + vec_t bestvalue; + vec_t value; + dplane_t* plane; + face_t* f; +#ifdef HLBSP_FAST_SELECTPARTITION + double planecount; + double totalsplit; + double avesplit; + double (*tmpvalue)[2]; + surfacetree_t* surfacetree; + std::vector< face_t * >::iterator it; + + planecount = 0; + totalsplit = 0; + tmpvalue = (double (*)[2])malloc (g_numplanes * sizeof (double [2])); + surfacetree = BuildSurfaceTree (surfaces, ON_EPSILON); +#endif + +#ifndef HLBSP_FAST_SELECTPARTITION + double avesplit; + double planecount; + { + planecount = 0; + double totalsplit = 0; + for (p = surfaces; p; p = p->next) + { + if (p->onnode) + { + continue; + } +#ifdef ZHLT_DETAILBRUSH + if (p->detaillevel != detaillevel) + { + continue; + } +#endif + planecount++; + plane = &g_dplanes[p->planenum]; + for (p2 = surfaces; p2; p2 = p2->next) + { + if (p2->onnode || p2 == p) + { + continue; + } + for (f = p2->faces; f; f = f->next) + { +#ifdef HLCSG_HLBSP_SOLIDHINT + if (f->facestyle == face_discardable) + { + continue; + } +#endif + if (FaceSide (f, plane) == SIDE_ON) + { + totalsplit++; + } + } + } + } + avesplit = (double)totalsplit / (double)planecount; + } +#endif + // + // pick the plane that splits the least + // + bestvalue = 9e30; + bestsurface = NULL; + + for (p = surfaces; p; p = p->next) + { + if (p->onnode) + { + continue; + } +#ifdef ZHLT_DETAILBRUSH + if (p->detaillevel != detaillevel) + { + continue; + } +#endif +#ifdef HLBSP_FAST_SELECTPARTITION + planecount++; +#endif + + double crosscount = 0; // use double here because we need to perform "crosscount++" + double frontcount = 0; + double backcount = 0; + double coplanarcount = 0; +#ifdef HLBSP_AVOIDEPSILONSPLIT + double epsilonsplit = 0; +#endif + + plane = &g_dplanes[p->planenum]; + + for (f = p->faces; f; f = f->next) + { +#ifdef HLCSG_HLBSP_SOLIDHINT + if (f->facestyle == face_discardable) + { + continue; + } +#endif + coplanarcount++; + } +#ifdef HLBSP_FAST_SELECTPARTITION + TestSurfaceTree (surfacetree, plane); + { + frontcount += surfacetree->result.frontsize; + backcount += surfacetree->result.backsize; + for (it = surfacetree->result.middle->begin (); it != surfacetree->result.middle->end (); ++it) + { + f = *it; + if (f->planenum == p->planenum || f->planenum == (p->planenum ^ 1)) + { + continue; + } +#else + for (p2 = surfaces; p2; p2 = p2->next) + { + if (p2->onnode || p2 == p) + { + continue; + } + for (f = p2->faces; f; f = f->next) + { +#endif +#ifdef HLCSG_HLBSP_SOLIDHINT + if (f->facestyle == face_discardable) + { +#ifdef HLBSP_AVOIDEPSILONSPLIT + FaceSide (f, plane, &epsilonsplit); +#endif + continue; + } +#endif + switch (FaceSide(f, plane +#ifdef HLBSP_AVOIDEPSILONSPLIT + , &epsilonsplit +#endif + )) + { + case SIDE_FRONT: + frontcount++; + break; + case SIDE_BACK: + backcount++; + break; + case SIDE_ON: +#ifdef HLBSP_FAST_SELECTPARTITION + totalsplit++; +#endif + crosscount++; + break; + } + } + } + + value = crosscount - sqrt (coplanarcount); // Not optimized. --vluzacn +#ifdef HLCSG_HLBSP_SOLIDHINT + if (coplanarcount == 0) + { + crosscount += 1; + } +#endif +#ifdef HLBSP_BALANCE + // This is the most efficient code among what I have ever tested: + // (1) BSP file is small, despite possibility of slowing down vis and rad (but still faster than the original non BSP balancing method). + // (2) Factors need not adjust across various maps. + double frac = (coplanarcount / 2 + crosscount / 2 + frontcount) / (coplanarcount + frontcount + backcount + crosscount); + double ent = (0.0001 < frac && frac < 0.9999)? (- frac * log (frac) / log (2.0) - (1 - frac) * log (1 - frac) / log (2.0)): 0.0; // the formula tends to 0 when frac=0,1 +#ifdef HLBSP_FAST_SELECTPARTITION + tmpvalue[p->planenum][1] = crosscount * (1 - ent); +#else + value += crosscount * avesplit * (1 - ent); +#endif +#endif +#ifdef HLBSP_AVOIDEPSILONSPLIT + value += epsilonsplit * 10000; +#endif + +#ifdef HLBSP_FAST_SELECTPARTITION + tmpvalue[p->planenum][0] = value; +#else + if (value < bestvalue) + { + // + // currently the best! + // + bestvalue = value; + bestsurface = p; + } +#endif + } +#ifdef HLBSP_FAST_SELECTPARTITION + avesplit = totalsplit / planecount; + for (p = surfaces; p; p = p->next) + { + if (p->onnode) + { + continue; + } +#ifdef ZHLT_DETAILBRUSH + if (p->detaillevel != detaillevel) + { + continue; + } +#endif + value = tmpvalue[p->planenum][0] + avesplit * tmpvalue[p->planenum][1]; + if (value < bestvalue) + { + bestvalue = value; + bestsurface = p; + } + } +#endif + + if (!bestsurface) + Error("ChoosePlaneFromList: no valid planes"); +#ifdef HLBSP_FAST_SELECTPARTITION + free (tmpvalue); + DeleteSurfaceTree (surfacetree); +#endif + return bestsurface; +} +#else +static surface_t* ChoosePlaneFromList(surface_t* surfaces, const vec3_t mins, const vec3_t maxs +#ifdef ZHLT_DETAILBRUSH + // mins and maxs are invalid when detaillevel > 0 + , int detaillevel +#endif + ) +{ + int j; + int k; + int l; + surface_t* p; + surface_t* p2; + surface_t* bestsurface; + vec_t bestvalue; + vec_t bestdistribution; + vec_t value; + vec_t dist; + dplane_t* plane; + face_t* f; + + // + // pick the plane that splits the least + // +#define UNDESIREABLE_HINT_FACTOR 10000 +#define WORST_VALUE 100000000 + bestvalue = WORST_VALUE; + bestsurface = NULL; + bestdistribution = 9e30; + + for (p = surfaces; p; p = p->next) + { + if (p->onnode) + { + continue; + } +#ifdef ZHLT_DETAILBRUSH + if (p->detaillevel != detaillevel) + { + continue; + } +#endif + +#ifdef ZHLT_DETAIL + if (g_bDetailBrushes) + { + // AJM: cycle though all faces, and make sure none of them are detail + // if any of them are, this surface isnt to cause a bsp split + for (face_t* f = p->faces; f; f = f->next) + { + if (f->contents == CONTENTS_DETAIL) + { + //Log("ChoosePlaneFromList::got a detial surface, skipping...\n"); + continue; // wrong. --vluzacn + } + } + } +#endif + + plane = &g_dplanes[p->planenum]; + k = 0; + + for (p2 = surfaces; p2; p2 = p2->next) + { + if (p2 == p) + { + continue; + } + if (p2->onnode) + { + continue; + } + + for (f = p2->faces; f; f = f->next) + { + // Give this face (a hint brush fragment) a large 'undesireable' value, only split when we have to) + if (f->facestyle == face_hint) + { + k += UNDESIREABLE_HINT_FACTOR; + hlassert(k < WORST_VALUE); + if (k >= WORST_VALUE) + { + Warning("::ChoosePlaneFromList() surface fragmentation undesireability exceeded WORST_VALUE"); + k = WORST_VALUE - 1; + } + } + if (FaceSide(f, plane) == SIDE_ON) + { + k++; + if (k >= bestvalue) + { + break; + } + } + + } + if (k > bestvalue) + { + break; + } + } + + if (k > bestvalue) + { + continue; + } + + // if equal numbers, axial planes win, then decide on spatial subdivision + + if (k < bestvalue || (k == bestvalue && (plane->type <= last_axial))) + { + // check for axis aligned surfaces + l = plane->type; + + if (l <= last_axial) + { // axial aligned + // + // calculate the split metric along axis l + // + value = 0; + + for (j = 0; j < 3; j++) + { + if (j == l) + { + dist = plane->dist * plane->normal[l]; + value += (maxs[l] - dist) * (maxs[l] - dist); + value += (dist - mins[l]) * (dist - mins[l]); + } + else + { + value += 2 * (maxs[j] - mins[j]) * (maxs[j] - mins[j]); + } + } + + if (value > bestdistribution && k == bestvalue) + { + continue; + } + bestdistribution = value; + } + // + // currently the best! + // + bestvalue = k; + bestsurface = p; + } + } + + return bestsurface; +} +#endif + +// ===================================================================================== +// SelectPartition +// Selects a surface from a linked list of surfaces to split the group on +// returns NULL if the surface list can not be divided any more (a leaf) +// ===================================================================================== +#ifdef ZHLT_DETAILBRUSH +int CalcSplitDetaillevel (const node_t *node) +{ + int bestdetaillevel = -1; + surface_t *s; + face_t *f; + for (s = node->surfaces; s; s = s->next) + { + if (s->onnode) + { + continue; + } +#ifdef HLCSG_HLBSP_SOLIDHINT + for (f = s->faces; f; f = f->next) + { + if (f->facestyle == face_discardable) + { + continue; + } + if (bestdetaillevel == -1 || f->detaillevel < bestdetaillevel) + { + bestdetaillevel = f->detaillevel; + } + } +#else + if (bestdetaillevel == -1 || s->detaillevel < bestdetaillevel) + { + bestdetaillevel = s->detaillevel; + } +#endif + } + return bestdetaillevel; +} +#endif +static surface_t* SelectPartition(surface_t* surfaces, const node_t* const node, const bool usemidsplit +#ifdef ZHLT_DETAILBRUSH + , int splitdetaillevel +#endif +#ifdef HLBSP_MAXNODESIZE_SKYBOX + , vec3_t validmins, vec3_t validmaxs +#endif + ) +{ +#ifdef ZHLT_DETAILBRUSH + if (splitdetaillevel == -1) + { + return NULL; + } + // now we MUST choose a surface of this detail level +#else + int i; + surface_t* p; + surface_t* bestsurface; + + // + // count surface choices + // + i = 0; + bestsurface = NULL; + for (p = surfaces; p; p = p->next) + { + if (!p->onnode) + { +#ifdef ZHLT_DETAIL + if (g_bDetailBrushes) + { + // AJM: cycle though all faces, and make sure none of them are detail + // if any of them are, this surface isnt to cause a bsp split + for (face_t* f = p->faces; f; f = f->next) + { + if (f->contents == CONTENTS_DETAIL) + { + //Log("SelectPartition::got a detial surface, skipping...\n"); + continue; + } + } + } +#endif +#ifdef HLCSG_HLBSP_SOLIDHINT + face_t *f; + for (f = p->faces; f; f = f->next) + { +#ifdef ZHLT_DETAILBRUSH + if (f->detaillevel != splitdetaillevel) + { + continue; + } +#endif + if (f->facestyle != face_discardable) + { + break; + } + } + if (!f) + { + continue; // this surface is discardable + // if all surfaces are discardable, this will become a leaf node + } +#endif +#ifdef ZHLT_DETAILBRUSH + if (p->detaillevel != splitdetaillevel) + { + continue; + } +#endif + i++; + bestsurface = p; + } + } + + if (i == 0) + { + return NULL; // this is a leafnode + } + +#ifndef HLCSG_HLBSP_SOLIDHINT // although there is only one undiscardable surface, maybe discardable surfaces should split first. + if (i == 1) + { + return bestsurface; // this is a final split + } +#endif +#endif + +#ifdef HLBSP_ChooseMidPlane_FIX + if (usemidsplit) + { + surface_t *s = ChooseMidPlaneFromList(surfaces, +#ifdef HLBSP_MAXNODESIZE_SKYBOX + validmins, validmaxs +#else + node->mins, node->maxs +#endif +#ifdef ZHLT_DETAILBRUSH + , splitdetaillevel +#endif + ); + if (s != NULL) + return s; + } + return ChoosePlaneFromList(surfaces, node->mins, node->maxs +#ifdef ZHLT_DETAILBRUSH + , splitdetaillevel +#endif + ); +#else + if (usemidsplit) + { + // do fast way for clipping hull + return ChooseMidPlaneFromList(surfaces, +#ifdef HLBSP_MAXNODESIZE_SKYBOX + validmins, validmaxs +#else + node->mins, node->maxs +#endif +#ifdef ZHLT_DETAILBRUSH + , splitdetaillevel +#endif + ); + } + else + { + // do slow way to save poly splits for drawing hull + return ChoosePlaneFromList(surfaces, node->mins, node->maxs +#ifdef ZHLT_DETAILBRUSH + , splitdetaillevel +#endif + ); + } +#endif +} + +// ===================================================================================== +// CalcSurfaceInfo +// Calculates the bounding box +// ===================================================================================== +static void CalcSurfaceInfo(surface_t* surf) +{ + int i; + int j; + face_t* f; + + hlassume(surf->faces != NULL, assume_ValidPointer); // "CalcSurfaceInfo() surface without a face" + + // + // calculate a bounding box + // + for (i = 0; i < 3; i++) + { + surf->mins[i] = 99999; + surf->maxs[i] = -99999; + } + +#ifdef ZHLT_DETAILBRUSH + surf->detaillevel = -1; +#endif + for (f = surf->faces; f; f = f->next) + { + if (f->contents >= 0) + { + Error("Bad contents"); + } + for (i = 0; i < f->numpoints; i++) + { + for (j = 0; j < 3; j++) + { + if (f->pts[i][j] < surf->mins[j]) + { + surf->mins[j] = f->pts[i][j]; + } + if (f->pts[i][j] > surf->maxs[j]) + { + surf->maxs[j] = f->pts[i][j]; + } + } + } +#ifdef ZHLT_DETAILBRUSH + if (surf->detaillevel == -1 || f->detaillevel < surf->detaillevel) + { + surf->detaillevel = f->detaillevel; + } +#endif + } +} +#ifdef ZHLT_DETAILBRUSH +#ifdef HLCSG_HLBSP_SOLIDHINT +void FixDetaillevelForDiscardable (node_t *node, int detaillevel) +{ + // when we move on to the next detaillevel, some discardable faces of previous detail level remain not on node (because they are discardable). remove them now + surface_t *s, **psnext; + face_t *f, **pfnext; + for (psnext = &node->surfaces; s = *psnext, s != NULL; ) + { + if (s->onnode) + { + psnext = &s->next; + continue; + } + hlassume (s->faces, assume_ValidPointer); + for (pfnext = &s->faces; f = *pfnext, f != NULL; ) + { + if (detaillevel == -1 || f->detaillevel < detaillevel) + { + *pfnext = f->next; + FreeFace (f); + } + else + { + pfnext = &f->next; + } + } + if (!s->faces) + { + *psnext = s->next; + FreeSurface (s); + } + else + { + psnext = &s->next; + CalcSurfaceInfo (s); + hlassume (!(detaillevel == -1 || s->detaillevel < detaillevel), assume_first); + } + } +} +#endif +#endif + +// ===================================================================================== +// DivideSurface +// ===================================================================================== +static void DivideSurface(surface_t* in, const dplane_t* const split, surface_t** front, surface_t** back) +{ + face_t* facet; + face_t* next; + face_t* frontlist; + face_t* backlist; + face_t* frontfrag; + face_t* backfrag; + surface_t* news; + dplane_t* inplane; + + inplane = &g_dplanes[in->planenum]; + + // parallel case is easy + + if (inplane->normal[0] == split->normal[0] + && inplane->normal[1] == split->normal[1] + && inplane->normal[2] == split->normal[2]) + { + if (inplane->dist > split->dist) + { + *front = in; + *back = NULL; + } + else if (inplane->dist < split->dist) + { + *front = NULL; + *back = in; + } + else + { // split the surface into front and back + frontlist = NULL; + backlist = NULL; + for (facet = in->faces; facet; facet = next) + { + next = facet->next; + if (facet->planenum & 1) + { + facet->next = backlist; + backlist = facet; + } + else + { + facet->next = frontlist; + frontlist = facet; + } + } + goto makesurfs; + } + return; + } + + // do a real split. may still end up entirely on one side + // OPTIMIZE: use bounding box for fast test + frontlist = NULL; + backlist = NULL; + + for (facet = in->faces; facet; facet = next) + { + next = facet->next; + SplitFace(facet, split, &frontfrag, &backfrag); + if (frontfrag) + { + frontfrag->next = frontlist; + frontlist = frontfrag; + } + if (backfrag) + { + backfrag->next = backlist; + backlist = backfrag; + } + } + + // if nothing actually got split, just move the in plane +makesurfs: +#ifdef HLBSP_REMOVECOLINEARPOINTS + if (frontlist == NULL && backlist == NULL) + { + *front = NULL; + *back = NULL; + return; + } +#endif + if (frontlist == NULL) + { + *front = NULL; + *back = in; + in->faces = backlist; + return; + } + + if (backlist == NULL) + { + *front = in; + *back = NULL; + in->faces = frontlist; + return; + } + + // stuff got split, so allocate one new surface and reuse in + news = AllocSurface(); + *news = *in; + news->faces = backlist; + *back = news; + + in->faces = frontlist; + *front = in; + + // recalc bboxes and flags + CalcSurfaceInfo(news); + CalcSurfaceInfo(in); +} + +// ===================================================================================== +// SplitNodeSurfaces +// ===================================================================================== +static void SplitNodeSurfaces(surface_t* surfaces, const node_t* const node) +{ + surface_t* p; + surface_t* next; + surface_t* frontlist; + surface_t* backlist; + surface_t* frontfrag; + surface_t* backfrag; + dplane_t* splitplane; + + splitplane = &g_dplanes[node->planenum]; + + frontlist = NULL; + backlist = NULL; + + for (p = surfaces; p; p = next) + { + next = p->next; + DivideSurface(p, splitplane, &frontfrag, &backfrag); + + if (frontfrag) + { + if (!frontfrag->faces) + { + Error("surface with no faces"); + } + frontfrag->next = frontlist; + frontlist = frontfrag; + } + if (backfrag) + { + if (!backfrag->faces) + { + Error("surface with no faces"); + } + backfrag->next = backlist; + backlist = backfrag; + } + } + + node->children[0]->surfaces = frontlist; + node->children[1]->surfaces = backlist; +} +#ifdef ZHLT_DETAILBRUSH +static void SplitNodeBrushes (brush_t *brushes, const node_t *node) +{ + brush_t *frontlist, *frontfrag; + brush_t *backlist, *backfrag; + brush_t *b, *next; + const dplane_t *splitplane; + frontlist = NULL; + backlist = NULL; + splitplane = &g_dplanes[node->planenum]; + for (b = brushes; b; b = next) + { + next = b->next; + SplitBrush (b, splitplane, &frontfrag, &backfrag); + if (frontfrag) + { + frontfrag->next = frontlist; + frontlist = frontfrag; + } + if (backfrag) + { + backfrag->next = backlist; + backlist = backfrag; + } + } + node->children[0]->detailbrushes = frontlist; + node->children[1]->detailbrushes = backlist; +} +#endif + +// ===================================================================================== +// RankForContents +// ===================================================================================== +static int RankForContents(const int contents) +{ + //Log("SolidBSP::RankForContents - contents type is %i ",contents); + switch (contents) + { +#ifdef ZHLT_NULLTEX // AJM +#ifndef HLCSG_HLBSP_CONTENTSNULL_FIX + case CONTENTS_NULL: + //Log("(null)\n"); + //return 13; + return -2; +#endif +#endif + + case CONTENTS_EMPTY: + //Log("(empty)\n"); + return 0; + case CONTENTS_WATER: + //Log("(water)\n"); + return 1; + case CONTENTS_TRANSLUCENT: + //Log("(traslucent)\n"); + return 2; + case CONTENTS_CURRENT_0: + //Log("(current_0)\n"); + return 3; + case CONTENTS_CURRENT_90: + //Log("(current_90)\n"); + return 4; + case CONTENTS_CURRENT_180: + //Log("(current_180)\n"); + return 5; + case CONTENTS_CURRENT_270: + //Log("(current_270)\n"); + return 6; + case CONTENTS_CURRENT_UP: + //Log("(current_up)\n"); + return 7; + case CONTENTS_CURRENT_DOWN: + //Log("(current_down)\n"); + return 8; + case CONTENTS_SLIME: + //Log("(slime)\n"); + return 9; + case CONTENTS_LAVA: + //Log("(lava)\n"); + return 10; + case CONTENTS_SKY: + //Log("(sky)\n"); + return 11; + case CONTENTS_SOLID: + //Log("(solid)\n"); + return 12; + +#ifdef ZHLT_DETAIL + case CONTENTS_DETAIL: + return 13; + //Log("(detail)\n"); +#endif + + default: + hlassert(false); + Error("RankForContents: bad contents %i", contents); + } + return -1; +} + +// ===================================================================================== +// ContentsForRank +// ===================================================================================== +static int ContentsForRank(const int rank) +{ + switch (rank) + { +#ifdef ZHLT_NULLTEX // AJM +#ifndef HLCSG_HLBSP_CONTENTSNULL_FIX + case -2: + return CONTENTS_NULL; // has at leat one face with null +#endif +#endif + + case -1: +#ifdef HLCSG_HLBSP_ALLOWEMPTYENTITY + return CONTENTS_EMPTY; +#else + return CONTENTS_SOLID; // no faces at all +#endif + case 0: + return CONTENTS_EMPTY; + case 1: + return CONTENTS_WATER; + case 2: + return CONTENTS_TRANSLUCENT; + case 3: + return CONTENTS_CURRENT_0; + case 4: + return CONTENTS_CURRENT_90; + case 5: + return CONTENTS_CURRENT_180; + case 6: + return CONTENTS_CURRENT_270; + case 7: + return CONTENTS_CURRENT_UP; + case 8: + return CONTENTS_CURRENT_DOWN; + case 9: + return CONTENTS_SLIME; + case 10: + return CONTENTS_LAVA; + case 11: + return CONTENTS_SKY; + case 12: + return CONTENTS_SOLID; + +#ifdef ZHLT_DETAIL // AJM + case 13: + return CONTENTS_DETAIL; +#endif + + default: + hlassert(false); + Error("ContentsForRank: bad rank %i", rank); + } + return -1; +} + +// ===================================================================================== +// FreeLeafSurfs +// ===================================================================================== +static void FreeLeafSurfs(node_t* leaf) +{ + surface_t* surf; + surface_t* snext; + face_t* f; + face_t* fnext; + + for (surf = leaf->surfaces; surf; surf = snext) + { + snext = surf->next; + for (f = surf->faces; f; f = fnext) + { + fnext = f->next; + FreeFace(f); + } + FreeSurface(surf); + } + + leaf->surfaces = NULL; +} +#ifdef ZHLT_DETAILBRUSH +static void FreeLeafBrushes (node_t *leaf) +{ + brush_t *b, *next; + for (b = leaf->detailbrushes; b; b = next) + { + next = b->next; + FreeBrush (b); + } + leaf->detailbrushes = NULL; +} +#endif + +// ===================================================================================== +// LinkLeafFaces +// Determines the contents of the leaf and creates the final list of original faces +// that have some fragment inside this leaf +// ===================================================================================== +#ifdef HLBSP_MAX_LEAF_FACES +#define MAX_LEAF_FACES 16384 +#else +#define MAX_LEAF_FACES 1024 +#endif + +#ifdef HLBSP_WARNMIXEDCONTENTS +const char* ContentsToString(int contents) +{ + switch (contents) + { + case CONTENTS_EMPTY: + return "EMPTY"; + case CONTENTS_SOLID: + return "SOLID"; + case CONTENTS_WATER: + return "WATER"; + case CONTENTS_SLIME: + return "SLIME"; + case CONTENTS_LAVA: + return "LAVA"; + case CONTENTS_SKY: + return "SKY"; + case CONTENTS_CURRENT_0: + return "CURRENT_0"; + case CONTENTS_CURRENT_90: + return "CURRENT_90"; + case CONTENTS_CURRENT_180: + return "CURRENT_180"; + case CONTENTS_CURRENT_270: + return "CURRENT_270"; + case CONTENTS_CURRENT_UP: + return "CURRENT_UP"; + case CONTENTS_CURRENT_DOWN: + return "CURRENT_DOWN"; + case CONTENTS_TRANSLUCENT: + return "TRANSLUCENT"; + default: + return "UNKNOWN"; + } +} +#endif +static void LinkLeafFaces(surface_t* planelist, node_t* leafnode) +{ + face_t* f; + surface_t* surf; + int rank, r; +#ifndef ZHLT_DETAILBRUSH + int nummarkfaces; + face_t* markfaces[MAX_LEAF_FACES]; + + leafnode->faces = NULL; + leafnode->planenum = -1; +#endif + + rank = -1; + for (surf = planelist; surf; surf = surf->next) + { +#ifdef ZHLT_DETAILBRUSH + if (!surf->onnode) + { + continue; + } +#endif + for (f = surf->faces; f; f = f->next) + { + if ((f->contents == CONTENTS_HINT)) + { + f->contents = CONTENTS_EMPTY; + } +#ifdef ZHLT_DETAILBRUSH + if (f->detaillevel) + { + continue; + } +#endif + r = RankForContents(f->contents); + if (r > rank) + { + rank = r; + } + } + } +#ifdef HLBSP_WARNMIXEDCONTENTS + for (surf = planelist; surf; surf = surf->next) + { +#ifdef ZHLT_DETAILBRUSH + if (!surf->onnode) + { + continue; + } +#endif + for (f = surf->faces; f; f = f->next) + { +#ifdef ZHLT_DETAILBRUSH + if (f->detaillevel) + { + continue; + } +#endif + r = RankForContents(f->contents); + if (r != rank) + break; + } + if (f) + break; + } + if (surf) + { + entity_t *ent = EntityForModel (g_nummodels - 1); + if (g_nummodels - 1 != 0 && ent == &g_entities[0]) + { + ent = NULL; + } + Warning ("Ambiguous leafnode content ( %s and %s ) at (%.0f,%.0f,%.0f)-(%.0f,%.0f,%.0f) in hull %d of model %d (entity: classname \"%s\", origin \"%s\", targetname \"%s\")", + ContentsToString (ContentsForRank(r)), ContentsToString (ContentsForRank(rank)), + leafnode->mins[0], leafnode->mins[1], leafnode->mins[2], leafnode->maxs[0], leafnode->maxs[1], leafnode->maxs[2], + g_hullnum, g_nummodels - 1, + (ent? ValueForKey (ent, "classname"): "unknown"), + (ent? ValueForKey (ent, "origin"): "unknown"), + (ent? ValueForKey (ent, "targetname"): "unknown")); + for (surface_t *surf2 = planelist; surf2; surf2 = surf2->next) + { + for (face_t *f2 = surf2->faces; f2; f2 = f2->next) + { + Developer (DEVELOPER_LEVEL_SPAM, "content = %d plane = %d normal = (%g,%g,%g)\n", f2->contents, f2->planenum, + g_dplanes[f2->planenum].normal[0], g_dplanes[f2->planenum].normal[1], g_dplanes[f2->planenum].normal[2]); + for (int i = 0; i < f2->numpoints; i++) + { + Developer (DEVELOPER_LEVEL_SPAM, "(%g,%g,%g)\n", f2->pts[i][0], f2->pts[i][1], f2->pts[i][2]); + } + } + } + } +#endif + + leafnode->contents = ContentsForRank(rank); + +#ifndef ZHLT_DETAILBRUSH + if (leafnode->contents != CONTENTS_SOLID) + { + nummarkfaces = 0; + for (surf = leafnode->surfaces; surf; surf = surf->next) + { + for (f = surf->faces; f; f = f->next) + { +#ifdef HLCSG_HLBSP_SOLIDHINT + if (f->original == NULL) + { + continue; + } +#endif + hlassume(nummarkfaces < MAX_LEAF_FACES, assume_MAX_LEAF_FACES); + + markfaces[nummarkfaces++] = f->original; + } + } + + markfaces[nummarkfaces] = NULL; // end marker + nummarkfaces++; + + leafnode->markfaces = (face_t**)malloc(nummarkfaces * sizeof(*leafnode->markfaces)); + memcpy(leafnode->markfaces, markfaces, nummarkfaces * sizeof(*leafnode->markfaces)); + } + + FreeLeafSurfs(leafnode); + leafnode->surfaces = NULL; +#endif +} +#ifdef ZHLT_DETAILBRUSH +static void MakeLeaf (node_t *leafnode) +{ + int nummarkfaces; + face_t *markfaces[MAX_LEAF_FACES + 1]; + surface_t *surf; + face_t *f; + + leafnode->planenum = -1; + + leafnode->iscontentsdetail = leafnode->detailbrushes != NULL; + FreeLeafBrushes (leafnode); + leafnode->detailbrushes = NULL; +#ifdef HLBSP_DETAILBRUSH_CULL + if (leafnode->boundsbrush) + { + FreeBrush (leafnode->boundsbrush); + } + leafnode->boundsbrush = NULL; +#endif + + if (!(leafnode->isportalleaf && leafnode->contents == CONTENTS_SOLID)) + { + nummarkfaces = 0; + for (surf = leafnode->surfaces; surf; surf = surf->next) + { + if (!surf->onnode) + { + continue; + } +#ifdef HLCSG_HLBSP_SOLIDHINT + if (!surf->onnode) + { + continue; + } +#endif + for (f = surf->faces; f; f = f->next) + { + if (f->original == NULL) + { // because it is not on node or its content is solid + continue; + } +#ifdef HLCSG_HLBSP_SOLIDHINT + if (f->original == NULL) + { + continue; + } +#endif + hlassume(nummarkfaces < MAX_LEAF_FACES, assume_MAX_LEAF_FACES); + + markfaces[nummarkfaces++] = f->original; + } + } + markfaces[nummarkfaces] = NULL; // end marker + nummarkfaces++; + + leafnode->markfaces = (face_t**)malloc(nummarkfaces * sizeof(*leafnode->markfaces)); + memcpy(leafnode->markfaces, markfaces, nummarkfaces * sizeof(*leafnode->markfaces)); + } + + FreeLeafSurfs(leafnode); + leafnode->surfaces = NULL; + +} +#endif + +// ===================================================================================== +// MakeNodePortal +// Create the new portal by taking the full plane winding for the cutting plane and +// clipping it by all of the planes from the other portals. +// Each portal tracks the node that created it, so unused nodes can be removed later. +// ===================================================================================== +static void MakeNodePortal(node_t* node) +{ + portal_t* new_portal; + portal_t* p; + dplane_t* plane; + dplane_t clipplane; + Winding * w; + int side = 0; + + plane = &g_dplanes[node->planenum]; + w = new Winding(*plane); + + new_portal = AllocPortal(); + new_portal->plane = *plane; + new_portal->onnode = node; + + for (p = node->portals; p; p = p->next[side]) + { + clipplane = p->plane; + if (p->nodes[0] == node) + { + side = 0; + } + else if (p->nodes[1] == node) + { + clipplane.dist = -clipplane.dist; + VectorSubtract(vec3_origin, clipplane.normal, clipplane.normal); + side = 1; + } + else + { + Error("MakeNodePortal: mislinked portal"); + } + + w->Clip(clipplane, true); +#ifdef ZHLT_WINDING_RemoveColinearPoints_VL + if (w->m_NumPoints == 0) +#else + if (!w) +#endif + { +#ifdef ZHLT_WINDING_RemoveColinearPoints_VL + Developer (DEVELOPER_LEVEL_WARNING, +#else + Warning( +#endif + "MakeNodePortal:new portal was clipped away from node@(%.0f,%.0f,%.0f)-(%.0f,%.0f,%.0f)", + node->mins[0], node->mins[1], node->mins[2], node->maxs[0], node->maxs[1], node->maxs[2]); + FreePortal(new_portal); + return; + } + } + + 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. +// ===================================================================================== +static void SplitNodePortals(node_t *node) +{ + portal_t* p; + portal_t* next_portal; + portal_t* new_portal; + node_t* f; + node_t* b; + node_t* other_node; + int side = 0; + dplane_t* plane; + Winding* frontwinding; + Winding* backwinding; + + plane = &g_dplanes[node->planenum]; + f = node->children[0]; + b = node->children[1]; + + for (p = node->portals; p; p = next_portal) + { + if (p->nodes[0] == node) + { + side = 0; + } + else if (p->nodes[1] == node) + { + side = 1; + } + else + { + Error("SplitNodePortals: mislinked portal"); + } + next_portal = p->next[side]; + + other_node = p->nodes[!side]; + RemovePortalFromNode(p, p->nodes[0]); + RemovePortalFromNode(p, p->nodes[1]); + + // cut the portal into two portals, one on each side of the cut plane + p->winding->Divide(*plane, &frontwinding, &backwinding); + +#ifdef ZHLT_WINDING_RemoveColinearPoints_VL + if (!frontwinding && !backwinding) + { + continue; + } +#endif + if (!frontwinding) + { + if (side == 0) + { + AddPortalToNodes(p, b, other_node); + } + else + { + AddPortalToNodes(p, other_node, b); + } + continue; + } + if (!backwinding) + { + 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; + delete 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 +// Determines the boundaries of a node by minmaxing all the portal points, whcih +// completely enclose the node. +// Returns true if the node should be midsplit.(very large) +// ===================================================================================== +static bool CalcNodeBounds(node_t* node +#ifdef HLBSP_MAXNODESIZE_SKYBOX + , vec3_t validmins, vec3_t validmaxs +#endif + ) +{ + int i; + int j; + vec_t v; + portal_t* p; + portal_t* next_portal; + int side = 0; + +#ifdef ZHLT_DETAILBRUSH + if (node->isdetail) + { + return false; + } +#endif +#ifdef ZHLT_LARGERANGE + node->mins[0] = node->mins[1] = node->mins[2] = BOGUS_RANGE; + node->maxs[0] = node->maxs[1] = node->maxs[2] = -BOGUS_RANGE; +#else + node->mins[0] = node->mins[1] = node->mins[2] = 9999; + node->maxs[0] = node->maxs[1] = node->maxs[2] = -9999; +#endif + + 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("CalcNodeBounds: mislinked portal"); + } + next_portal = p->next[side]; + + for (i = 0; i < p->winding->m_NumPoints; i++) + { + for (j = 0; j < 3; j++) + { + v = p->winding->m_Points[i][j]; + if (v < node->mins[j]) + { + node->mins[j] = v; + } + if (v > node->maxs[j]) + { + node->maxs[j] = v; + } + } + } + } + +#ifdef ZHLT_DETAILBRUSH + if (node->isportalleaf) + { + return false; + } +#endif +#ifdef HLBSP_MAXNODESIZE_SKYBOX + for (i = 0; i < 3; i++) + { + validmins[i] = qmax (node->mins[i], -(ENGINE_ENTITY_RANGE + g_maxnode_size)); + validmaxs[i] = qmin (node->maxs[i], ENGINE_ENTITY_RANGE + g_maxnode_size); + } + for (i = 0; i < 3; i++) + { + if (validmaxs[i] - validmins[i] <= ON_EPSILON) + { + return false; + } + } + for (i = 0; i < 3; i++) + { + if (validmaxs[i] - validmins[i] > g_maxnode_size + ON_EPSILON) + { + return true; + } + } +#else + for (i = 0; i < 3; i++) + { + if (node->maxs[i] - node->mins[i] > g_maxnode_size) + { + return true; + } + } +#endif + return false; +} + +// ===================================================================================== +// CopyFacesToNode +// Do a final merge attempt, then subdivide the faces to surface cache size if needed. +// These are final faces that will be drawable in the game. +// Copies of these faces are further chopped up into the leafs, but they will reference these originals. +// ===================================================================================== +static void CopyFacesToNode(node_t* node, surface_t* surf) +{ + face_t** prevptr; + face_t* f; + face_t* newf; + + // merge as much as possible + MergePlaneFaces(surf); + + // subdivide large faces + prevptr = &surf->faces; + while (1) + { + f = *prevptr; + if (!f) + { + break; + } + SubdivideFace(f, prevptr); + f = *prevptr; + prevptr = &f->next; + } + + // copy the faces to the node, and consider them the originals + node->surfaces = NULL; + node->faces = NULL; + for (f = surf->faces; f; f = f->next) + { +#ifdef HLCSG_HLBSP_SOLIDHINT + if (f->facestyle == face_discardable) + { + continue; + } +#endif + if (f->contents != CONTENTS_SOLID) + { + newf = AllocFace(); + *newf = *f; + f->original = newf; + newf->next = node->faces; + node->faces = newf; + } + } +} + +// ===================================================================================== +// BuildBspTree_r +// ===================================================================================== +static void BuildBspTree_r(node_t* node) +{ + surface_t* split; + bool midsplit; + surface_t* allsurfs; +#ifdef HLBSP_MAXNODESIZE_SKYBOX + vec3_t validmins, validmaxs; +#endif + + midsplit = CalcNodeBounds(node +#ifdef HLBSP_MAXNODESIZE_SKYBOX + , validmins, validmaxs +#endif + ); +#ifdef HLBSP_DETAILBRUSH_CULL + if (node->boundsbrush) + { + CalcBrushBounds (node->boundsbrush, node->loosemins, node->loosemaxs); + } + else + { + VectorFill (node->loosemins, BOGUS_RANGE); + VectorFill (node->loosemaxs, -BOGUS_RANGE); + } +#endif + +#ifdef ZHLT_DETAILBRUSH + int splitdetaillevel = CalcSplitDetaillevel (node); +#ifdef HLCSG_HLBSP_SOLIDHINT + FixDetaillevelForDiscardable (node, splitdetaillevel); +#endif +#endif + split = SelectPartition(node->surfaces, node, midsplit +#ifdef ZHLT_DETAILBRUSH + , splitdetaillevel +#endif +#ifdef HLBSP_MAXNODESIZE_SKYBOX + , validmins, validmaxs +#endif + ); +#ifdef ZHLT_DETAILBRUSH + if (!node->isdetail && (!split || split->detaillevel > 0)) + { + node->isportalleaf = true; + LinkLeafFaces (node->surfaces, node); // set contents + if (node->contents == CONTENTS_SOLID) + { + split = NULL; + } + } + else + { + node->isportalleaf = false; + } +#endif + if (!split) + { // this is a leaf node +#ifdef ZHLT_DETAILBRUSH + MakeLeaf (node); +#else + node->planenum = PLANENUM_LEAF; + LinkLeafFaces(node->surfaces, node); +#endif + return; + } + + // these are final polygons + split->onnode = node; // can't use again + allsurfs = node->surfaces; + node->planenum = split->planenum; + node->faces = NULL; + CopyFacesToNode(node, split); + + node->children[0] = AllocNode(); + node->children[1] = AllocNode(); +#ifdef ZHLT_DETAILBRUSH + node->children[0]->isdetail = split->detaillevel > 0; + node->children[1]->isdetail = split->detaillevel > 0; +#endif + + // split all the polysurfaces into front and back lists + SplitNodeSurfaces(allsurfs, node); +#ifdef ZHLT_DETAILBRUSH + SplitNodeBrushes (node->detailbrushes, node); +#ifdef HLBSP_DETAILBRUSH_CULL + if (node->boundsbrush) + { + for (int k = 0; k < 2; k++) + { + dplane_t p; + brush_t *copy, *front, *back; + if (k == 0) + { // front child + VectorCopy (g_dplanes[split->planenum].normal, p.normal); + p.dist = g_dplanes[split->planenum].dist - BOUNDS_EXPANSION; + } + else + { // back child + VecSubtractVector (0, g_dplanes[split->planenum].normal, p.normal); + p.dist = -g_dplanes[split->planenum].dist - BOUNDS_EXPANSION; + } + copy = NewBrushFromBrush (node->boundsbrush); + SplitBrush (copy, &p, &front, &back); + if (back) + { + FreeBrush (back); + } + if (!front) + { + Warning ("BuildBspTree_r: bounds was clipped away at (%f,%f,%f)-(%f,%f,%f).", node->loosemins[0], node->loosemins[1], node->loosemins[2], node->loosemaxs[0], node->loosemaxs[1], node->loosemaxs[2]); + } + node->children[k]->boundsbrush = front; + } + FreeBrush (node->boundsbrush); + } + node->boundsbrush = NULL; +#endif +#endif + +#ifdef ZHLT_DETAILBRUSH + if (!split->detaillevel) + { + MakeNodePortal (node); + SplitNodePortals (node); + } +#else + // create the portal that seperates the two children + MakeNodePortal(node); + + // carve the portals on the boundaries of the node + SplitNodePortals(node); +#endif + + // recursively do the children + BuildBspTree_r(node->children[0]); + BuildBspTree_r(node->children[1]); + UpdateStatus(); +} + +// ===================================================================================== +// SolidBSP +// Takes a chain of surfaces plus a split type, and returns a bsp tree with faces +// off the nodes. +// The original surface chain will be completely freed. +// ===================================================================================== +node_t* SolidBSP(const surfchain_t* const surfhead, +#ifdef ZHLT_DETAILBRUSH + brush_t *detailbrushes, +#endif + bool report_progress) +{ + node_t* headnode; + + ResetStatus(report_progress); + double start_time = I_FloatTime(); + if(report_progress) + { + Log("SolidBSP [hull %d] ",g_hullnum); + } + else + { + Verbose("----- SolidBSP -----\n"); + } + + headnode = AllocNode(); + headnode->surfaces = surfhead->surfaces; +#ifdef ZHLT_DETAILBRUSH + headnode->detailbrushes = detailbrushes; + headnode->isdetail = false; +#ifdef HLBSP_DETAILBRUSH_CULL + vec3_t brushmins, brushmaxs; + VectorAddVec (surfhead->mins, -SIDESPACE, brushmins); + VectorAddVec (surfhead->maxs, SIDESPACE, brushmaxs); + headnode->boundsbrush = BrushFromBox (brushmins, brushmaxs); +#endif +#endif + +#ifndef HLCSG_HLBSP_ALLOWEMPTYENTITY + if (!surfhead->surfaces) + { + // nothing at all to build +#ifdef ZHLT_DETAILBRUSH + headnode->isportalleaf = true; + headnode->iscontentsdetail = (headnode->detailbrushes != NULL); + FreeLeafBrushes (headnode->detailbrushes); +#endif + headnode->planenum = -1; + headnode->contents = CONTENTS_EMPTY; + return headnode; + } +#endif + + // generate six portals that enclose the entire world + MakeHeadnodePortals(headnode, surfhead->mins, surfhead->maxs); + + // recursively partition everything + BuildBspTree_r(headnode); + + double end_time = I_FloatTime(); + if(report_progress) + { + Log("%d (%.2f seconds)\n",++g_numProcessed,(end_time - start_time)); + } + + return headnode; +} diff --git a/src/zhlt-vluzacn/hlbsp/surfaces.cpp b/src/zhlt-vluzacn/hlbsp/surfaces.cpp new file mode 100644 index 0000000..b5b8758 --- /dev/null +++ b/src/zhlt-vluzacn/hlbsp/surfaces.cpp @@ -0,0 +1,470 @@ +#include "bsp5.h" + +// SubdivideFace + +// InitHash +// HashVec + +// GetVertex +// GetEdge +// MakeFaceEdges + +static int subdivides; + +/* a surface has all of the faces that could be drawn on a given plane + the outside filling stage can remove some of them so a better bsp can be generated */ + +// ===================================================================================== +// SubdivideFace +// If the face is >256 in either texture direction, carve a valid sized +// piece off and insert the remainder in the next link +// ===================================================================================== +void SubdivideFace(face_t* f, face_t** prevptr) +{ + vec_t mins, maxs; + vec_t v; + int axis; + int i; + dplane_t plane; + face_t* front; + face_t* back; + face_t* next; + texinfo_t* tex; + vec3_t temp; + + // special (non-surface cached) faces don't need subdivision + +#ifdef HLCSG_HLBSP_VOIDTEXINFO + if (f->texturenum == -1) + { + return; + } +#endif + tex = &g_texinfo[f->texturenum]; + + if (tex->flags & TEX_SPECIAL) + { + return; + } + + if (f->facestyle == face_hint) + { + return; + } + if (f->facestyle == face_skip) + { + return; + } + +#ifdef ZHLT_NULLTEX // AJM + if (f->facestyle == face_null) + return; // ideally these should have their tex_special flag set, so its here jic +#endif +#ifdef HLCSG_HLBSP_SOLIDHINT + if (f->facestyle == face_discardable) + return; +#endif + + for (axis = 0; axis < 2; axis++) + { + while (1) + { +#ifdef HLBSP_SUBDIVIDE_INMID + const int maxlightmapsize = g_subdivide_size / TEXTURE_STEP + 1; + int lightmapmins, lightmapmaxs; + if (f->numpoints == 0) + { + break; + } + for (i = 0; i < f->numpoints; i++) + { + v = DotProduct (f->pts[i], tex->vecs[axis]) + tex->vecs[axis][3]; + if (i == 0 || v < mins) + { + mins = v; + } + if (i == 0 || v > maxs) + { + maxs = v; + } + } + lightmapmins = (int)floor (mins / TEXTURE_STEP - 0.05); // the worst case + lightmapmaxs = (int)ceil (maxs / TEXTURE_STEP + 0.05); // the worst case + if (lightmapmaxs - lightmapmins <= maxlightmapsize) + { + break; + } +#else +#ifdef ZHLT_64BIT_FIX + mins = 99999999; + maxs = -99999999; +#else + mins = 999999; + maxs = -999999; +#endif + + for (i = 0; i < f->numpoints; i++) + { + v = DotProduct(f->pts[i], tex->vecs[axis]); + if (v < mins) + { + mins = v; + } + if (v > maxs) + { + maxs = v; + } + } + + if ((maxs - mins) <= g_subdivide_size) + { + break; + } +#endif + + // split it + subdivides++; + + VectorCopy(tex->vecs[axis], temp); + v = VectorNormalize(temp); + + VectorCopy(temp, plane.normal); +#ifdef HLBSP_SUBDIVIDE_INMID + int splitpos; + if ((lightmapmaxs - lightmapmins - 1) - (maxlightmapsize - 1) < (maxlightmapsize - 1) / 4) // don't create very thin face + { + splitpos = lightmapmins + (maxlightmapsize - 1) - (maxlightmapsize - 1) / 4; + } + else + { + splitpos = lightmapmins + (maxlightmapsize - 1); + } + plane.dist = ((splitpos + 0.5) * TEXTURE_STEP - tex->vecs[axis][3]) / v; +#else + plane.dist = (mins + g_subdivide_size - TEXTURE_STEP) / v; //plane.dist = (mins + g_subdivide_size - 16) / v; //--vluzacn +#endif + next = f->next; + SplitFace(f, &plane, &front, &back); + if (!front || !back) + { + Developer(DEVELOPER_LEVEL_SPAM, "SubdivideFace: didn't split the %d-sided polygon @(%.0f,%.0f,%.0f)", + f->numpoints, f->pts[0][0], f->pts[0][1], f->pts[0][2]); +#ifndef HLBSP_SubdivideFace_FIX + break; +#endif + } +#ifdef HLBSP_SubdivideFace_FIX + f = next; + if (front) + { + front->next = f; + f = front; + } + if (back) + { + back->next = f; + f = back; + } + *prevptr = f; +#else + *prevptr = back; + back->next = front; + front->next = next; + f = back; +#endif + } + } +} + +//=========================================================================== + +typedef struct hashvert_s +{ + struct hashvert_s* next; + vec3_t point; + int num; + int numplanes; // for corner determination + int planenums[2]; + int numedges; +} +hashvert_t; + +// #define POINT_EPSILON 0.01 +#define POINT_EPSILON (ON_EPSILON / 2) //#define POINT_EPSILON ON_EPSILON //--vluzacn + +static hashvert_t hvertex[MAX_MAP_VERTS]; +static hashvert_t* hvert_p; + +static face_t* edgefaces[MAX_MAP_EDGES][2]; +static int firstmodeledge = 1; +static int firstmodelface; + +//============================================================================ + +#define NUM_HASH 4096 + +static hashvert_t* hashverts[NUM_HASH]; + +static vec3_t hash_min; +static vec3_t hash_scale; +#ifdef HLBSP_HASH_FIX +// It's okay if the coordinates go under hash_min, because they are hashed in a cyclic way (modulus by hash_numslots) +// So please don't change the hardcoded hash_min and scale +static int hash_numslots[3]; +#define MAX_HASH_NEIGHBORS 4 +#endif + +// ===================================================================================== +// InitHash +// ===================================================================================== +static void InitHash() +{ + vec3_t size; + vec_t volume; + vec_t scale; + int newsize[2]; + int i; + + memset(hashverts, 0, sizeof(hashverts)); + + for (i = 0; i < 3; i++) + { + hash_min[i] = -8000; + size[i] = 16000; + } + + volume = size[0] * size[1]; + + scale = sqrt(volume / NUM_HASH); + +#ifdef HLBSP_HASH_FIX + hash_numslots[0] = (int)floor (size[0] / scale); + hash_numslots[1] = (int)floor (size[1] / scale); + while (hash_numslots[0] * hash_numslots[1] > NUM_HASH) + { + Developer (DEVELOPER_LEVEL_WARNING, "hash_numslots[0] * hash_numslots[1] > NUM_HASH"); + hash_numslots[0]--; + hash_numslots[1]--; + } + + hash_scale[0] = hash_numslots[0] / size[0]; + hash_scale[1] = hash_numslots[1] / size[1]; +#else + newsize[0] = size[0] / scale; + newsize[1] = size[1] / scale; + + hash_scale[0] = newsize[0] / size[0]; + hash_scale[1] = newsize[1] / size[1]; + hash_scale[2] = newsize[1]; +#endif + + hvert_p = hvertex; +} + +// ===================================================================================== +// HashVec +// ===================================================================================== +#ifdef HLBSP_HASH_FIX +static int HashVec (const vec3_t vec, int *num_hashneighbors, int *hashneighbors) + // returned value: the one bucket that a new vertex may "write" into + // returned hashneighbors: the buckets that we should "read" to check for an existing vertex +{ + int h; + int i; + int x; + int y; + int slot[2]; + vec_t normalized[2]; + vec_t slotdiff[2]; + + for (i = 0; i < 2; i++) + { + normalized[i] = hash_scale[i] * (vec[i] - hash_min[i]); + slot[i] = (int)floor (normalized[i]); + slotdiff[i] = normalized[i] - (vec_t)slot[i]; + + slot[i] = (slot[i] + hash_numslots[i]) % hash_numslots[i]; + slot[i] = (slot[i] + hash_numslots[i]) % hash_numslots[i]; // do it twice to handle negative values + } + + h = slot[0] * hash_numslots[1] + slot[1]; + + *num_hashneighbors = 0; + for (x = -1; x <= 1; x++) + { + if (x == -1 && slotdiff[0] > hash_scale[0] * (2 * POINT_EPSILON) || + x == 1 && slotdiff[0] < 1 - hash_scale[0] * (2 * POINT_EPSILON)) + { + continue; + } + for (y = -1; y <= 1; y++) + { + if (y == -1 && slotdiff[1] > hash_scale[1] * (2 * POINT_EPSILON) || + y == 1 && slotdiff[1] < 1 - hash_scale[1] * (2 * POINT_EPSILON)) + { + continue; + } + if (*num_hashneighbors >= MAX_HASH_NEIGHBORS) + { + Error ("HashVec: internal error."); + } + hashneighbors[*num_hashneighbors] = + ((slot[0] + x + hash_numslots[0]) % hash_numslots[0]) * hash_numslots[1] + + (slot[1] + y + hash_numslots[1]) % hash_numslots[1]; + (*num_hashneighbors)++; + } + } + + return h; +} +#else // This HashVec function was subtly but horribly wrong... +static unsigned HashVec(const vec3_t vec) +{ + unsigned h; + + h = hash_scale[0] * (vec[0] - hash_min[0]) * hash_scale[2] + hash_scale[1] * (vec[1] - hash_min[1]); + if (h >= NUM_HASH) + { + return NUM_HASH - 1; + } + return h; +} +#endif + +// ===================================================================================== +// GetVertex +// ===================================================================================== +static int GetVertex(const vec3_t in, const int planenum) +{ + int h; + int i; + hashvert_t* hv; + vec3_t vert; +#ifdef HLBSP_HASH_FIX + int num_hashneighbors; + int hashneighbors[MAX_HASH_NEIGHBORS]; +#endif + + for (i = 0; i < 3; i++) + { + if (fabs(in[i] - VectorRound(in[i])) < 0.001) + { + vert[i] = VectorRound(in[i]); + } + else + { + vert[i] = in[i]; + } + } + +#ifdef HLBSP_HASH_FIX + h = HashVec(vert, &num_hashneighbors, hashneighbors); +#else + h = HashVec(vert); +#endif + +#ifdef HLBSP_HASH_FIX + for (i = 0; i < num_hashneighbors; i++) + for (hv = hashverts[hashneighbors[i]]; hv; hv = hv->next) +#else + for (hv = hashverts[h]; hv; hv = hv->next) +#endif + { + if (fabs(hv->point[0] - vert[0]) < POINT_EPSILON + && fabs(hv->point[1] - vert[1]) < POINT_EPSILON && fabs(hv->point[2] - vert[2]) < POINT_EPSILON) + { + hv->numedges++; + if (hv->numplanes == 3) + { + return hv->num; // allready known to be a corner + } + for (i = 0; i < hv->numplanes; i++) + { + if (hv->planenums[i] == planenum) + { + return hv->num; // allready know this plane + } + } + if (hv->numplanes != 2) + { + hv->planenums[hv->numplanes] = planenum; + } + hv->numplanes++; + return hv->num; + } + } + + hv = hvert_p; + hv->numedges = 1; + hv->numplanes = 1; + hv->planenums[0] = planenum; + hv->next = hashverts[h]; + hashverts[h] = hv; + VectorCopy(vert, hv->point); + hv->num = g_numvertexes; + hlassume(hv->num != MAX_MAP_VERTS, assume_MAX_MAP_VERTS); + hvert_p++; + + // emit a vertex + hlassume(g_numvertexes < MAX_MAP_VERTS, assume_MAX_MAP_VERTS); + + g_dvertexes[g_numvertexes].point[0] = vert[0]; + g_dvertexes[g_numvertexes].point[1] = vert[1]; + g_dvertexes[g_numvertexes].point[2] = vert[2]; + g_numvertexes++; + + return hv->num; +} + +//=========================================================================== + +// ===================================================================================== +// GetEdge +// Don't allow four way edges +// ===================================================================================== +int GetEdge(const vec3_t p1, const vec3_t p2, face_t* f) +{ + int v1; + int v2; + dedge_t* edge; + int i; + + hlassert(f->contents); + + v1 = GetVertex(p1, f->planenum); + v2 = GetVertex(p2, f->planenum); + for (i = firstmodeledge; i < g_numedges; i++) + { + edge = &g_dedges[i]; + if (v1 == edge->v[1] && v2 == edge->v[0] && !edgefaces[i][1] && edgefaces[i][0]->contents == f->contents +#ifdef HLBSP_EDGESHARE_SAMESIDE + && edgefaces[i][0]->planenum != (f->planenum ^ 1) + && edgefaces[i][0]->contents == f->contents +#endif + ) + { + edgefaces[i][1] = f; + return -i; + } + } + + // emit an edge + hlassume(g_numedges < MAX_MAP_EDGES, assume_MAX_MAP_EDGES); + edge = &g_dedges[g_numedges]; + g_numedges++; + edge->v[0] = v1; + edge->v[1] = v2; + edgefaces[i][0] = f; + + return i; +} + +// ===================================================================================== +// MakeFaceEdges +// ===================================================================================== +void MakeFaceEdges() +{ + InitHash(); + firstmodeledge = g_numedges; + firstmodelface = g_numfaces; +} diff --git a/src/zhlt-vluzacn/hlbsp/tjunc.cpp b/src/zhlt-vluzacn/hlbsp/tjunc.cpp new file mode 100644 index 0000000..81d65d6 --- /dev/null +++ b/src/zhlt-vluzacn/hlbsp/tjunc.cpp @@ -0,0 +1,669 @@ +#include "bsp5.h" + +typedef struct wvert_s +{ + vec_t t; + struct wvert_s* prev; + struct wvert_s* next; +} +wvert_t; + +typedef struct wedge_s +{ + struct wedge_s* next; + vec3_t dir; + vec3_t origin; + wvert_t head; +} +wedge_t; + +static int numwedges; +static int numwverts; +static int tjuncs; +static int tjuncfaces; + +#define MAX_WVERTS 0x40000 +#define MAX_WEDGES 0x20000 + +static wvert_t wverts[MAX_WVERTS]; +static wedge_t wedges[MAX_WEDGES]; + +//============================================================================ + +#ifdef HLBSP_HASH_FIX +#define NUM_HASH 4096 +#else +#define NUM_HASH 1024 +#endif + +wedge_t* wedge_hash[NUM_HASH]; + +static vec3_t hash_min; +static vec3_t hash_scale; +#ifdef HLBSP_HASH_FIX +// It's okay if the coordinates go under hash_min, because they are hashed in a cyclic way (modulus by hash_numslots) +// So please don't change the hardcoded hash_min and scale +static int hash_numslots[3]; +#define MAX_HASH_NEIGHBORS 4 +#endif + +static void InitHash(const vec3_t mins, const vec3_t maxs) +{ + vec3_t size; + vec_t volume; + vec_t scale; + int newsize[2]; + +#ifdef HLBSP_HASH_FIX + // Let's ignore the parameters and make things more predictable, so there won't be strange cases such as division by 0 or extreme scaling values. + VectorFill(hash_min, -8000); + VectorFill(size, 16000); +#else + VectorCopy(mins, hash_min); + VectorSubtract(maxs, mins, size); +#endif + memset(wedge_hash, 0, sizeof(wedge_hash)); + + volume = size[0] * size[1]; + + scale = sqrt(volume / NUM_HASH); + +#ifdef HLBSP_HASH_FIX + hash_numslots[0] = (int)floor (size[0] / scale); + hash_numslots[1] = (int)floor (size[1] / scale); + while (hash_numslots[0] * hash_numslots[1] > NUM_HASH) + { + Developer (DEVELOPER_LEVEL_WARNING, "hash_numslots[0] * hash_numslots[1] > NUM_HASH"); + hash_numslots[0]--; + hash_numslots[1]--; + } + + hash_scale[0] = hash_numslots[0] / size[0]; + hash_scale[1] = hash_numslots[1] / size[1]; +#else + newsize[0] = size[0] / scale; + newsize[1] = size[1] / scale; + + hash_scale[0] = newsize[0] / size[0]; + hash_scale[1] = newsize[1] / size[1]; + hash_scale[2] = newsize[1]; +#endif +} + +#ifdef HLBSP_HASH_FIX +static int HashVec (const vec3_t vec, int *num_hashneighbors, int *hashneighbors) +{ + int h; + int i; + int x; + int y; + int slot[2]; + vec_t normalized[2]; + vec_t slotdiff[2]; + + for (i = 0; i < 2; i++) + { + normalized[i] = hash_scale[i] * (vec[i] - hash_min[i]); + slot[i] = (int)floor (normalized[i]); + slotdiff[i] = normalized[i] - (vec_t)slot[i]; + + slot[i] = (slot[i] + hash_numslots[i]) % hash_numslots[i]; + slot[i] = (slot[i] + hash_numslots[i]) % hash_numslots[i]; // do it twice to handle negative values + } + + h = slot[0] * hash_numslots[1] + slot[1]; + + *num_hashneighbors = 0; + for (x = -1; x <= 1; x++) + { + if (x == -1 && slotdiff[0] > hash_scale[0] * (2 * ON_EPSILON) || + x == 1 && slotdiff[0] < 1 - hash_scale[0] * (2 * ON_EPSILON)) + { + continue; + } + for (y = -1; y <= 1; y++) + { + if (y == -1 && slotdiff[1] > hash_scale[1] * (2 * ON_EPSILON) || + y == 1 && slotdiff[1] < 1 - hash_scale[1] * (2 * ON_EPSILON)) + { + continue; + } + if (*num_hashneighbors >= MAX_HASH_NEIGHBORS) + { + Error ("HashVec: internal error."); + } + hashneighbors[*num_hashneighbors] = + ((slot[0] + x + hash_numslots[0]) % hash_numslots[0]) * hash_numslots[1] + + (slot[1] + y + hash_numslots[1]) % hash_numslots[1]; + (*num_hashneighbors)++; + } + } + + return h; +} +#else +static unsigned HashVec(const vec3_t vec) +{ + unsigned h; + + h = hash_scale[0] * (vec[0] - hash_min[0]) * hash_scale[2] + hash_scale[1] * (vec[1] - hash_min[1]); + if (h >= NUM_HASH) + { + return NUM_HASH - 1; + } + return h; +} +#endif + +//============================================================================ + +static bool CanonicalVector(vec3_t vec) +{ + if (VectorNormalize(vec)) + { + if (vec[0] > NORMAL_EPSILON ) + { + return true; + } + else if (vec[0] < -NORMAL_EPSILON ) + { + VectorSubtract(vec3_origin, vec, vec); + return true; + } + else + { + vec[0] = 0; + } + + if (vec[1] > NORMAL_EPSILON ) + { + return true; + } + else if (vec[1] < -NORMAL_EPSILON ) + { + VectorSubtract(vec3_origin, vec, vec); + return true; + } + else + { + vec[1] = 0; + } + + if (vec[2] > NORMAL_EPSILON ) + { + return true; + } + else if (vec[2] < -NORMAL_EPSILON ) + { + VectorSubtract(vec3_origin, vec, vec); + return true; + } + else + { + vec[2] = 0; + } +// hlassert(false); + return false; + } +// hlassert(false); + return false; +} + +static wedge_t *FindEdge(const vec3_t p1, const vec3_t p2, vec_t* t1, vec_t* t2) +{ + vec3_t origin; + vec3_t dir; + wedge_t* w; + vec_t temp; + int h; +#ifdef HLBSP_HASH_FIX + int num_hashneighbors; + int hashneighbors[MAX_HASH_NEIGHBORS]; +#endif + + VectorSubtract(p2, p1, dir); + if (!CanonicalVector(dir)) + { +#if _DEBUG + Warning("CanonicalVector: degenerate @ (%4.3f %4.3f %4.3f )\n", p1[0], p1[1], p1[2]); +#endif + } + + *t1 = DotProduct(p1, dir); + *t2 = DotProduct(p2, dir); + + VectorMA(p1, -*t1, dir, origin); + + if (*t1 > *t2) + { + temp = *t1; + *t1 = *t2; + *t2 = temp; + } + +#ifdef HLBSP_HASH_FIX + h = HashVec(origin, &num_hashneighbors, hashneighbors); +#else + h = HashVec(origin); +#endif + +#ifdef HLBSP_HASH_FIX + for (int i = 0; i < num_hashneighbors; ++i) + for (w = wedge_hash[hashneighbors[i]]; w; w = w->next) +#else + for (w = wedge_hash[h]; w; w = w->next) +#endif + { +#ifdef HLBSP_TJUNC_PRECISION_FIX + if (fabs (w->origin[0] - origin[0]) > EQUAL_EPSILON || + fabs (w->origin[1] - origin[1]) > EQUAL_EPSILON || + fabs (w->origin[2] - origin[2]) > EQUAL_EPSILON ) + { + continue; + } + if (fabs (w->dir[0] - dir[0]) > NORMAL_EPSILON || + fabs (w->dir[1] - dir[1]) > NORMAL_EPSILON || + fabs (w->dir[2] - dir[2]) > NORMAL_EPSILON ) + { + continue; + } +#else + temp = w->origin[0] - origin[0]; + if (temp < -EQUAL_EPSILON || temp > EQUAL_EPSILON) + { + continue; + } + temp = w->origin[1] - origin[1]; + if (temp < -EQUAL_EPSILON || temp > EQUAL_EPSILON) + { + continue; + } + temp = w->origin[2] - origin[2]; + if (temp < -EQUAL_EPSILON || temp > EQUAL_EPSILON) + { + continue; + } + + temp = w->dir[0] - dir[0]; + if (temp < -EQUAL_EPSILON || temp > EQUAL_EPSILON) + { + continue; + } + temp = w->dir[1] - dir[1]; + if (temp < -EQUAL_EPSILON || temp > EQUAL_EPSILON) + { + continue; + } + temp = w->dir[2] - dir[2]; + if (temp < -EQUAL_EPSILON || temp > EQUAL_EPSILON) + { + continue; + } +#endif + + return w; + } + + hlassume(numwedges < MAX_WEDGES, assume_MAX_WEDGES); + w = &wedges[numwedges]; + numwedges++; + + w->next = wedge_hash[h]; + wedge_hash[h] = w; + + VectorCopy(origin, w->origin); + VectorCopy(dir, w->dir); + w->head.next = w->head.prev = &w->head; + w->head.t = 99999; + return w; +} + +/* + * =============== + * AddVert + * + * =============== + */ +#define T_EPSILON ON_EPSILON + +static void AddVert(const wedge_t* const w, const vec_t t) +{ + wvert_t* v; + wvert_t* newv; + + v = w->head.next; + do + { + if (fabs(v->t - t) < T_EPSILON) + { + return; + } + if (v->t > t) + { + break; + } + v = v->next; + } + while (1); + + // insert a new wvert before v + hlassume(numwverts < MAX_WVERTS, assume_MAX_WVERTS); + + newv = &wverts[numwverts]; + numwverts++; + + newv->t = t; + newv->next = v; + newv->prev = v->prev; + v->prev->next = newv; + v->prev = newv; +} + +/* + * =============== + * AddEdge + * =============== + */ +static void AddEdge(const vec3_t p1, const vec3_t p2) +{ + wedge_t* w; + vec_t t1; + vec_t t2; + + w = FindEdge(p1, p2, &t1, &t2); + AddVert(w, t1); + AddVert(w, t2); +} + +/* + * =============== + * AddFaceEdges + * + * =============== + */ +static void AddFaceEdges(const face_t* const f) +{ + int i, j; + + for (i = 0; i < f->numpoints; i++) + { + j = (i + 1) % f->numpoints; + AddEdge(f->pts[i], f->pts[j]); + } +} + +//============================================================================ + +static byte superfacebuf[1024 * 16]; +static face_t* superface = (face_t*)superfacebuf; +static int MAX_SUPERFACEEDGES = (sizeof(superfacebuf) - sizeof(face_t) + sizeof(superface->pts)) / sizeof(vec3_t); +static face_t* newlist; + +static void SplitFaceForTjunc(face_t* f, face_t* original) +{ + int i; + face_t* newface; + face_t* chain; + vec3_t dir, test; + vec_t v; + int firstcorner, lastcorner; + +#ifdef _DEBUG + static int counter = 0; + + Log("SplitFaceForTjunc %d\n", counter++); +#endif + + chain = NULL; + do + { + hlassume(f->original == NULL, assume_ValidPointer); // "SplitFaceForTjunc: f->original" + + if (f->numpoints <= MAXPOINTS) + { // the face is now small enough without more cutting + // so copy it back to the original + *original = *f; + original->original = chain; + original->next = newlist; + newlist = original; + return; + } + + tjuncfaces++; + +restart: + // find the last corner + VectorSubtract(f->pts[f->numpoints - 1], f->pts[0], dir); + VectorNormalize(dir); + for (lastcorner = f->numpoints - 1; lastcorner > 0; lastcorner--) + { + VectorSubtract(f->pts[lastcorner - 1], f->pts[lastcorner], test); + VectorNormalize(test); + v = DotProduct(test, dir); + if (v < 1.0 - ON_EPSILON || v > 1.0 + ON_EPSILON) + { + break; + } + } + + // find the first corner + VectorSubtract(f->pts[1], f->pts[0], dir); + VectorNormalize(dir); + for (firstcorner = 1; firstcorner < f->numpoints - 1; firstcorner++) + { + VectorSubtract(f->pts[firstcorner + 1], f->pts[firstcorner], test); + VectorNormalize(test); + v = DotProduct(test, dir); + if (v < 1.0 - ON_EPSILON || v > 1.0 + ON_EPSILON) + { + break; + } + } + + if (firstcorner + 2 >= MAXPOINTS) + { + // rotate the point winding + VectorCopy(f->pts[0], test); + for (i = 1; i < f->numpoints; i++) + { + VectorCopy(f->pts[i], f->pts[i - 1]); + } + VectorCopy(test, f->pts[f->numpoints - 1]); + goto restart; + } + + // cut off as big a piece as possible, less than MAXPOINTS, and not + // past lastcorner + + newface = NewFaceFromFace(f); + + hlassume(f->original == NULL, assume_ValidPointer); // "SplitFaceForTjunc: f->original" + + newface->original = chain; + chain = newface; + newface->next = newlist; + newlist = newface; + if (f->numpoints - firstcorner <= MAXPOINTS) + { + newface->numpoints = firstcorner + 2; + } + else if (lastcorner + 2 < MAXPOINTS && f->numpoints - lastcorner <= MAXPOINTS) + { + newface->numpoints = lastcorner + 2; + } + else + { + newface->numpoints = MAXPOINTS; + } + + for (i = 0; i < newface->numpoints; i++) + { + VectorCopy(f->pts[i], newface->pts[i]); + } + + for (i = newface->numpoints - 1; i < f->numpoints; i++) + { + VectorCopy(f->pts[i], f->pts[i - (newface->numpoints - 2)]); + } + f->numpoints -= (newface->numpoints - 2); + } + while (1); + +} + +/* + * =============== + * FixFaceEdges + * + * =============== + */ +static void FixFaceEdges(face_t* f) +{ + int i; + int j; + int k; + wedge_t* w; + wvert_t* v; + vec_t t1; + vec_t t2; + + *superface = *f; + +restart: + for (i = 0; i < superface->numpoints; i++) + { + j = (i + 1) % superface->numpoints; + + w = FindEdge(superface->pts[i], superface->pts[j], &t1, &t2); + + for (v = w->head.next; v->t < t1 + T_EPSILON; v = v->next) + { + } + + if (v->t < t2 - T_EPSILON) + { + tjuncs++; + // insert a new vertex here + for (k = superface->numpoints; k > j; k--) + { + VectorCopy(superface->pts[k - 1], superface->pts[k]); + } + VectorMA(w->origin, v->t, w->dir, superface->pts[j]); + superface->numpoints++; + hlassume(superface->numpoints < MAX_SUPERFACEEDGES, assume_MAX_SUPERFACEEDGES); + goto restart; + } + } + + if (superface->numpoints <= MAXPOINTS) + { + *f = *superface; + f->next = newlist; + newlist = f; + return; + } + + // the face needs to be split into multiple faces because of too many edges + + SplitFaceForTjunc(superface, f); + +} + +//============================================================================ + +static void tjunc_find_r(node_t* node) +{ + face_t* f; + + if (node->planenum == PLANENUM_LEAF) + { + return; + } + + for (f = node->faces; f; f = f->next) + { + AddFaceEdges(f); + } + + tjunc_find_r(node->children[0]); + tjunc_find_r(node->children[1]); +} + +static void tjunc_fix_r(node_t* node) +{ + face_t* f; + face_t* next; + + if (node->planenum == PLANENUM_LEAF) + { + return; + } + + newlist = NULL; + + for (f = node->faces; f; f = next) + { + next = f->next; + FixFaceEdges(f); + } + + node->faces = newlist; + + tjunc_fix_r(node->children[0]); + tjunc_fix_r(node->children[1]); +} + +/* + * =========== + * tjunc + * + * =========== + */ +void tjunc(node_t* headnode) +{ + vec3_t maxs, mins; + int i; + + Verbose("---- tjunc ----\n"); + + if (g_notjunc) + { + return; + } + + // + // identify all points on common edges + // + + // origin points won't allways be inside the map, so extend the hash area + for (i = 0; i < 3; i++) + { + if (fabs(headnode->maxs[i]) > fabs(headnode->mins[i])) + { + maxs[i] = fabs(headnode->maxs[i]); + } + else + { + maxs[i] = fabs(headnode->mins[i]); + } + } + VectorSubtract(vec3_origin, maxs, mins); + + InitHash(mins, maxs); + + numwedges = numwverts = 0; + + tjunc_find_r(headnode); + + Verbose("%i world edges %i edge points\n", numwedges, numwverts); + + // + // add extra vertexes on edges where needed + // + tjuncs = tjuncfaces = 0; + + tjunc_fix_r(headnode); + + Verbose("%i edges added by tjunctions\n", tjuncs); + Verbose("%i faces added by tjunctions\n", tjuncfaces); +} diff --git a/src/zhlt-vluzacn/hlbsp/writebsp.cpp b/src/zhlt-vluzacn/hlbsp/writebsp.cpp new file mode 100644 index 0000000..a88dd64 --- /dev/null +++ b/src/zhlt-vluzacn/hlbsp/writebsp.cpp @@ -0,0 +1,1167 @@ +#include "bsp5.h" + +// WriteClipNodes_r +// WriteClipNodes +// WriteDrawLeaf +// WriteFace +// WriteDrawNodes_r +// FreeDrawNodes_r +// WriteDrawNodes +// BeginBSPFile +// FinishBSPFile + +#include + +typedef std::map< int, int > PlaneMap; +static PlaneMap gPlaneMap; +static int gNumMappedPlanes; +static dplane_t gMappedPlanes[MAX_MAP_PLANES]; +extern bool g_noopt; +#ifdef HLCSG_HLBSP_REDUCETEXTURE +typedef std::map< int, int > texinfomap_t; +static int g_nummappedtexinfo; +static texinfo_t g_mappedtexinfo[MAX_MAP_TEXINFO]; +static texinfomap_t g_texinfomap; +#endif +#ifdef HLBSP_MERGECLIPNODE +int count_mergedclipnodes; +typedef std::map< std::pair< int, std::pair< int, int > >, int > clipnodemap_t; +inline clipnodemap_t::key_type MakeKey (const dclipnode_t &c) +{ + return std::make_pair (c.planenum, std::make_pair (c.children[0], c.children[1])); +} +#endif + +// ===================================================================================== +// WritePlane +// hook for plane optimization +// ===================================================================================== +static int WritePlane(int planenum) +{ + planenum = planenum & (~1); + + if(g_noopt) + { + return planenum; + } + + PlaneMap::iterator item = gPlaneMap.find(planenum); + if(item != gPlaneMap.end()) + { + return item->second; + } + //add plane to BSP + hlassume(gNumMappedPlanes < MAX_MAP_PLANES, assume_MAX_MAP_PLANES); + gMappedPlanes[gNumMappedPlanes] = g_dplanes[planenum]; + gPlaneMap.insert(PlaneMap::value_type(planenum,gNumMappedPlanes)); + + return gNumMappedPlanes++; +} + +#ifdef HLCSG_HLBSP_REDUCETEXTURE + +// ===================================================================================== +// WriteTexinfo +// ===================================================================================== +static int WriteTexinfo (int texinfo) +{ + if (texinfo < 0 || texinfo >= g_numtexinfo) + { + Error ("Bad texinfo number %d.\n", texinfo); + } + + if (g_noopt) + { + return texinfo; + } + + texinfomap_t::iterator it; + it = g_texinfomap.find (texinfo); + if (it != g_texinfomap.end ()) + { + return it->second; + } + + int c; + hlassume (g_nummappedtexinfo < MAX_MAP_TEXINFO, assume_MAX_MAP_TEXINFO); + c = g_nummappedtexinfo; + g_mappedtexinfo[g_nummappedtexinfo] = g_texinfo[texinfo]; + g_texinfomap.insert (texinfomap_t::value_type (texinfo, g_nummappedtexinfo)); + g_nummappedtexinfo++; + return c; +} + +#endif +// ===================================================================================== +// WriteClipNodes_r +// ===================================================================================== +static int WriteClipNodes_r(node_t* node +#ifdef ZHLT_DETAILBRUSH + , const node_t *portalleaf +#endif +#ifdef HLBSP_MERGECLIPNODE + , clipnodemap_t *outputmap +#endif + ) +{ + int i, c; + dclipnode_t* cn; + int num; + +#ifdef ZHLT_DETAILBRUSH + if (node->isportalleaf) + { + if (node->contents == CONTENTS_SOLID) + { + free (node); + return CONTENTS_SOLID; + } + else + { + portalleaf = node; + } + } + if (node->planenum == -1) + { + if (node->iscontentsdetail) + { + num = CONTENTS_SOLID; + } + else + { + num = portalleaf->contents; + } + free (node->markfaces); + free (node); + return num; + } +#else + if (node->planenum == -1) + { + num = node->contents; + free(node->markfaces); + free(node); + return num; + } +#endif + +#ifdef ZHLT_XASH2 +#ifdef HLBSP_MERGECLIPNODE + dclipnode_t tmpclipnode; // this clipnode will be inserted into g_dclipnodes[c] if it can't be merged + cn = &tmpclipnode; + c = g_numclipnodes[g_hullnum - 1]; + g_numclipnodes[g_hullnum - 1]++; +#else + // emit a clipnode + hlassume(g_numclipnodes[g_hullnum - 1] < MAX_MAP_CLIPNODES, assume_MAX_MAP_CLIPNODES); + + c = g_numclipnodes[g_hullnum - 1]; + cn = &g_dclipnodes[g_hullnum - 1][g_numclipnodes]; + g_numclipnodes[g_hullnum - 1]++; +#endif +#else +#ifdef HLBSP_MERGECLIPNODE + dclipnode_t tmpclipnode; // this clipnode will be inserted into g_dclipnodes[c] if it can't be merged + cn = &tmpclipnode; + c = g_numclipnodes; + g_numclipnodes++; +#else + // emit a clipnode + hlassume(g_numclipnodes < MAX_MAP_CLIPNODES, assume_MAX_MAP_CLIPNODES); + + c = g_numclipnodes; + cn = &g_dclipnodes[g_numclipnodes]; + g_numclipnodes++; +#endif +#endif + if (node->planenum & 1) + { + Error("WriteClipNodes_r: odd planenum"); + } + cn->planenum = WritePlane(node->planenum); + for (i = 0; i < 2; i++) + { + cn->children[i] = WriteClipNodes_r(node->children[i] +#ifdef ZHLT_DETAILBRUSH + , portalleaf +#endif +#ifdef HLBSP_MERGECLIPNODE + , outputmap +#endif + ); + } +#ifdef HLBSP_MERGECLIPNODE + clipnodemap_t::iterator output; + output = outputmap->find (MakeKey (*cn)); + if (g_noclipnodemerge || output == outputmap->end ()) + { + hlassume (c < MAX_MAP_CLIPNODES, assume_MAX_MAP_CLIPNODES); +#ifdef ZHLT_XASH2 + g_dclipnodes[g_hullnum - 1][c] = *cn; +#else + g_dclipnodes[c] = *cn; +#endif + (*outputmap)[MakeKey (*cn)] = c; + } + else + { + count_mergedclipnodes++; +#ifdef ZHLT_XASH2 + if (g_numclipnodes[g_hullnum - 1] != c + 1) + { + Error ("Merge clipnodes: internal error"); + } + g_numclipnodes[g_hullnum - 1] = c; +#else + if (g_numclipnodes != c + 1) + { + Error ("Merge clipnodes: internal error"); + } + g_numclipnodes = c; +#endif + c = output->second; // use existing clipnode + } +#endif + + free(node); + return c; +} + +// ===================================================================================== +// WriteClipNodes +// Called after the clipping hull is completed. Generates a disk format +// representation and frees the original memory. +// ===================================================================================== +void WriteClipNodes(node_t* nodes) +{ +#ifdef HLBSP_MERGECLIPNODE + // we only merge among the clipnodes of the same hull of the same model + clipnodemap_t outputmap; +#endif + WriteClipNodes_r(nodes +#ifdef ZHLT_DETAILBRUSH + , NULL +#endif +#ifdef HLBSP_MERGECLIPNODE + , &outputmap +#endif + ); +} + +// ===================================================================================== +// WriteDrawLeaf +// ===================================================================================== +#ifdef ZHLT_DETAILBRUSH +static int WriteDrawLeaf (node_t *node, const node_t *portalleaf) +#else +static void WriteDrawLeaf(const node_t* const node) +#endif +{ + face_t** fp; + face_t* f; + dleaf_t* leaf_p; +#ifdef ZHLT_DETAILBRUSH + int leafnum = g_numleafs; +#endif + + // emit a leaf +#ifdef ZHLT_MAX_MAP_LEAFS + hlassume (g_numleafs < MAX_MAP_LEAFS, assume_MAX_MAP_LEAFS); +#endif + leaf_p = &g_dleafs[g_numleafs]; + g_numleafs++; + +#ifdef ZHLT_DETAILBRUSH + leaf_p->contents = portalleaf->contents; +#else + leaf_p->contents = node->contents; +#endif + + // + // write bounding box info + // +#ifdef ZHLT_DETAILBRUSH +#ifdef HLBSP_DETAILBRUSH_CULL + vec3_t mins, maxs; +#if 0 + printf ("leaf isdetail = %d loosebound = (%f,%f,%f)-(%f,%f,%f) portalleaf = (%f,%f,%f)-(%f,%f,%f)\n", node->isdetail, + node->loosemins[0], node->loosemins[1], node->loosemins[2], node->loosemaxs[0], node->loosemaxs[1], node->loosemaxs[2], + portalleaf->mins[0], portalleaf->mins[1], portalleaf->mins[2], portalleaf->maxs[0], portalleaf->maxs[1], portalleaf->maxs[2]); +#endif + if (node->isdetail) + { + // intersect its loose bounds with the strict bounds of its parent portalleaf + VectorCompareMaximum (portalleaf->mins, node->loosemins, mins); + VectorCompareMinimum (portalleaf->maxs, node->loosemaxs, maxs); + } + else + { + VectorCopy (node->mins, mins); + VectorCopy (node->maxs, maxs); + } +#ifdef ZHLT_LARGERANGE + for (int k = 0; k < 3; k++) + { + leaf_p->mins[k] = (short)qmax (-32767, qmin ((int)mins[k], 32767)); + leaf_p->maxs[k] = (short)qmax (-32767, qmin ((int)maxs[k], 32767)); + } +#else + VectorCopy (mins, leaf_p->mins); + VectorCopy (maxs, leaf_p->maxs); +#endif +#else +#ifdef ZHLT_LARGERANGE + for (int k = 0; k < 3; k++) + { + leaf_p->mins[k] = (short)qmax (-32767, qmin ((int)portalleaf->mins[k], 32767)); + leaf_p->maxs[k] = (short)qmax (-32767, qmin ((int)portalleaf->maxs[k], 32767)); + } +#else + VectorCopy (portalleaf->mins, leaf_p->mins); + VectorCopy (portalleaf->maxs, leaf_p->maxs); +#endif +#endif +#else +#ifdef ZHLT_LARGERANGE + for (int k = 0; k < 3; k++) + { + leaf_p->mins[k] = (short)qmax (-32767, qmin ((int)node->mins[k], 32767)); + leaf_p->maxs[k] = (short)qmax (-32767, qmin ((int)node->maxs[k], 32767)); + } +#else + VectorCopy(node->mins, leaf_p->mins); + VectorCopy(node->maxs, leaf_p->maxs); +#endif +#endif + + leaf_p->visofs = -1; // no vis info yet + + // + // write the marksurfaces + // + leaf_p->firstmarksurface = g_nummarksurfaces; + + hlassume(node->markfaces != NULL, assume_EmptySolid); + + for (fp = node->markfaces; *fp; fp++) + { + // emit a marksurface + f = *fp; + do + { +#ifdef HLBSP_NULLFACEOUTPUT_FIX + // fix face 0 being seen everywhere + if (f->outputnumber == -1) + { + f = f->original; + continue; + } +#endif +#if defined(HLBSP_HIDDENFACE) || defined(ZHLT_HIDDENSOUNDTEXTURE) + bool ishidden = false; + { + const char *name = GetTextureByNumber (f->texturenum); + if (strlen (name) >= 7 && !strcasecmp (&name[strlen (name) - 7], "_HIDDEN")) + { + ishidden = true; + } +#ifdef ZHLT_HIDDENSOUNDTEXTURE + if (f->texturenum >= 0 && (g_texinfo[f->texturenum].flags & TEX_SHOULDHIDE)) + { + ishidden = true; + } +#endif + } + if (ishidden) + { + f = f->original; + continue; + } +#endif + g_dmarksurfaces[g_nummarksurfaces] = f->outputnumber; + hlassume(g_nummarksurfaces < MAX_MAP_MARKSURFACES, assume_MAX_MAP_MARKSURFACES); + g_nummarksurfaces++; + f = f->original; // grab tjunction split faces + } + while (f); + } + free(node->markfaces); + + leaf_p->nummarksurfaces = g_nummarksurfaces - leaf_p->firstmarksurface; +#ifdef ZHLT_DETAILBRUSH + return leafnum; +#endif +} + +// ===================================================================================== +// WriteFace +// ===================================================================================== +static void WriteFace(face_t* f) +{ + dface_t* df; + int i; + int e; + + if ( CheckFaceForHint(f) + || CheckFaceForSkip(f) +#ifdef ZHLT_NULLTEX + || CheckFaceForNull(f) // AJM +#endif +#ifdef HLCSG_HLBSP_SOLIDHINT + || CheckFaceForDiscardable (f) +#endif +#ifdef HLCSG_HLBSP_VOIDTEXINFO + || f->texturenum == -1 +#endif +#ifdef HLBSP_REMOVECOVEREDFACES + || f->referenced == 0 // this face is not referenced by any nonsolid leaf because it is completely covered by func_details +#endif + +// ===================================================================================== +//Cpt_Andrew - Env_Sky Check +// ===================================================================================== + || CheckFaceForEnv_Sky(f) +// ===================================================================================== + + ) + { +#ifdef HLBSP_NULLFACEOUTPUT_FIX + f->outputnumber = -1; +#endif + return; + } + + f->outputnumber = g_numfaces; + + df = &g_dfaces[g_numfaces]; + hlassume(g_numfaces < MAX_MAP_FACES, assume_MAX_MAP_FACES); + g_numfaces++; + + df->planenum = WritePlane(f->planenum); + df->side = f->planenum & 1; + df->firstedge = g_numsurfedges; + df->numedges = f->numpoints; +#ifdef HLCSG_HLBSP_REDUCETEXTURE + df->texinfo = WriteTexinfo (f->texturenum); +#else + df->texinfo = f->texturenum; +#endif + for (i = 0; i < f->numpoints; i++) + { +#ifdef ZHLT_DETAILBRUSH + e = f->outputedges[i]; +#else + e = GetEdge(f->pts[i], f->pts[(i + 1) % f->numpoints], f); +#endif + hlassume(g_numsurfedges < MAX_MAP_SURFEDGES, assume_MAX_MAP_SURFEDGES); + g_dsurfedges[g_numsurfedges] = e; + g_numsurfedges++; + } +#ifdef ZHLT_DETAILBRUSH + free (f->outputedges); + f->outputedges = NULL; +#endif +} + +// ===================================================================================== +// WriteDrawNodes_r +// ===================================================================================== +#ifdef ZHLT_DETAILBRUSH +static int WriteDrawNodes_r (node_t *node, const node_t *portalleaf) +#else +static void WriteDrawNodes_r(const node_t* const node) +#endif +{ +#ifdef ZHLT_DETAILBRUSH + if (node->isportalleaf) + { + if (node->contents == CONTENTS_SOLID) + { + return -1; + } + else + { + portalleaf = node; + // Warning: make sure parent data have not been freed when writing children. + } + } + if (node->planenum == -1) + { + if (node->iscontentsdetail) + { + free(node->markfaces); + return -1; + } + else + { + int leafnum = WriteDrawLeaf (node, portalleaf); + return -1 - leafnum; + } + } +#endif + dnode_t* n; + int i; + face_t* f; +#ifdef ZHLT_DETAILBRUSH + int nodenum = g_numnodes; +#endif + + // emit a node + hlassume(g_numnodes < MAX_MAP_NODES, assume_MAX_MAP_NODES); + n = &g_dnodes[g_numnodes]; + g_numnodes++; + +#ifdef ZHLT_DETAILBRUSH + vec3_t mins, maxs; +#if 0 + if (node->isdetail || node->isportalleaf) + printf ("node isdetail = %d loosebound = (%f,%f,%f)-(%f,%f,%f) portalleaf = (%f,%f,%f)-(%f,%f,%f)\n", node->isdetail, + node->loosemins[0], node->loosemins[1], node->loosemins[2], node->loosemaxs[0], node->loosemaxs[1], node->loosemaxs[2], + portalleaf->mins[0], portalleaf->mins[1], portalleaf->mins[2], portalleaf->maxs[0], portalleaf->maxs[1], portalleaf->maxs[2]); +#endif + if (node->isdetail) + { +#ifdef HLBSP_DETAILBRUSH_CULL + // intersect its loose bounds with the strict bounds of its parent portalleaf + VectorCompareMaximum (portalleaf->mins, node->loosemins, mins); + VectorCompareMinimum (portalleaf->maxs, node->loosemaxs, maxs); +#else + VectorCopy (portalleaf->mins, mins); + VectorCopy (portalleaf->maxs, maxs); +#endif + } + else + { + VectorCopy (node->mins, mins); + VectorCopy (node->maxs, maxs); + } +#ifdef ZHLT_LARGERANGE + for (int k = 0; k < 3; k++) + { + n->mins[k] = (short)qmax (-32767, qmin ((int)mins[k], 32767)); + n->maxs[k] = (short)qmax (-32767, qmin ((int)maxs[k], 32767)); + } +#else + VectorCopy (mins, n->mins); + VectorCopy (maxs, n->maxs); +#endif +#else +#ifdef ZHLT_LARGERANGE + for (int k = 0; k < 3; k++) + { + n->mins[k] = (short)qmax (-32767, qmin ((int)node->mins[k], 32767)); + n->maxs[k] = (short)qmax (-32767, qmin ((int)node->maxs[k], 32767)); + } +#else + VectorCopy(node->mins, n->mins); + VectorCopy(node->maxs, n->maxs); +#endif +#endif + + if (node->planenum & 1) + { + Error("WriteDrawNodes_r: odd planenum"); + } + n->planenum = WritePlane(node->planenum); + n->firstface = g_numfaces; + + for (f = node->faces; f; f = f->next) + { + WriteFace(f); + } + + n->numfaces = g_numfaces - n->firstface; + + // + // recursively output the other nodes + // + for (i = 0; i < 2; i++) + { +#ifdef ZHLT_DETAILBRUSH + n->children[i] = WriteDrawNodes_r (node->children[i], portalleaf); +#else + if (node->children[i]->planenum == -1) + { + if (node->children[i]->contents == CONTENTS_SOLID) + { + n->children[i] = -1; + } + else + { + n->children[i] = -(g_numleafs + 1); + WriteDrawLeaf(node->children[i]); + } + } + else + { + n->children[i] = g_numnodes; + WriteDrawNodes_r(node->children[i]); + } +#endif + } +#ifdef ZHLT_DETAILBRUSH + return nodenum; +#endif +} + +// ===================================================================================== +// FreeDrawNodes_r +// ===================================================================================== +static void FreeDrawNodes_r(node_t* node) +{ + int i; + face_t* f; + face_t* next; + + for (i = 0; i < 2; i++) + { + if (node->children[i]->planenum != -1) + { + FreeDrawNodes_r(node->children[i]); + } + } + + // + // free the faces on the node + // + for (f = node->faces; f; f = next) + { + next = f->next; + FreeFace(f); + } + + free(node); +} + +// ===================================================================================== +// WriteDrawNodes +// Called after a drawing hull is completed +// Frees all nodes and faces +// ===================================================================================== +#ifdef ZHLT_DETAILBRUSH +void OutputEdges_face (face_t *f) +{ + if (CheckFaceForHint(f) + || CheckFaceForSkip(f) +#ifdef ZHLT_NULLTEX + || CheckFaceForNull(f) // AJM +#endif +#ifdef HLCSG_HLBSP_SOLIDHINT + || CheckFaceForDiscardable (f) +#endif +#ifdef HLCSG_HLBSP_VOIDTEXINFO + || f->texturenum == -1 +#endif +#ifdef HLBSP_REMOVECOVEREDFACES + || f->referenced == 0 +#endif + || CheckFaceForEnv_Sky(f)//Cpt_Andrew - Env_Sky Check + ) + { + return; + } + f->outputedges = (int *)malloc (f->numpoints * sizeof (int)); + hlassume (f->outputedges != NULL, assume_NoMemory); + int i; + for (i = 0; i < f->numpoints; i++) + { + int e = GetEdge (f->pts[i], f->pts[(i + 1) % f->numpoints], f); + f->outputedges[i] = e; + } +} +int OutputEdges_r (node_t *node, int detaillevel) +{ + int next = -1; + if (node->planenum == -1) + { + return next; + } + face_t *f; + for (f = node->faces; f; f = f->next) + { + if (f->detaillevel > detaillevel) + { + if (next == -1? true: f->detaillevel < next) + { + next = f->detaillevel; + } + } + if (f->detaillevel == detaillevel) + { + OutputEdges_face (f); + } + } + int i; + for (i = 0; i < 2; i++) + { + int r = OutputEdges_r (node->children[i], detaillevel); + if (r == -1? false: next == -1? true: r < next) + { + next = r; + } + } + return next; +} +#endif +#ifdef HLBSP_REMOVECOVEREDFACES +static void RemoveCoveredFaces_r (node_t *node) +{ + if (node->isportalleaf) + { + if (node->contents == CONTENTS_SOLID) + { + return; // stop here, don't go deeper into children + } + } + if (node->planenum == -1) + { + // this is a leaf + if (node->iscontentsdetail) + { + return; + } + else + { + face_t **fp; + for (fp = node->markfaces; *fp; fp++) + { + for (face_t *f = *fp; f; f = f->original) // for each tjunc subface + { + f->referenced++; // mark the face as referenced + } + } + } + return; + } + + // this is a node + for (face_t *f = node->faces; f; f = f->next) + { + f->referenced = 0; // clear the mark + } + + RemoveCoveredFaces_r (node->children[0]); + RemoveCoveredFaces_r (node->children[1]); +} +#endif +void WriteDrawNodes(node_t* headnode) +{ +#ifdef ZHLT_DETAILBRUSH +#ifdef HLBSP_REMOVECOVEREDFACES + RemoveCoveredFaces_r (headnode); // fill "referenced" value +#endif + // higher detail level should not compete for edge pairing with lower detail level. + int detaillevel, nextdetaillevel; + for (detaillevel = 0; detaillevel != -1; detaillevel = nextdetaillevel) + { + nextdetaillevel = OutputEdges_r (headnode, detaillevel); + } + WriteDrawNodes_r (headnode, NULL); +#else + if (headnode->contents < 0) + { + WriteDrawLeaf(headnode); + } + else + { + WriteDrawNodes_r(headnode); + FreeDrawNodes_r(headnode); + } +#endif +} + + +// ===================================================================================== +// BeginBSPFile +// ===================================================================================== +void BeginBSPFile() +{ + // these values may actually be initialized + // if the file existed when loaded, so clear them explicitly + gNumMappedPlanes = 0; + gPlaneMap.clear(); +#ifdef HLCSG_HLBSP_REDUCETEXTURE + g_nummappedtexinfo = 0; + g_texinfomap.clear (); +#endif +#ifdef HLBSP_MERGECLIPNODE + count_mergedclipnodes = 0; +#endif + g_nummodels = 0; + g_numfaces = 0; + g_numnodes = 0; +#ifdef ZHLT_XASH2 + for (int hull = 1; hull < MAX_MAP_HULLS; hull++) + { + g_numclipnodes[hull - 1] = 0; + } +#else + g_numclipnodes = 0; +#endif + g_numvertexes = 0; + g_nummarksurfaces = 0; + g_numsurfedges = 0; + + // edge 0 is not used, because 0 can't be negated + g_numedges = 1; + + // leaf 0 is common solid with no faces + g_numleafs = 1; + g_dleafs[0].contents = CONTENTS_SOLID; +} + +// ===================================================================================== +// FinishBSPFile +// ===================================================================================== +void FinishBSPFile() +{ + Verbose("--- FinishBSPFile ---\n"); + +#ifdef ZHLT_MAX_MAP_LEAFS + if (g_dmodels[0].visleafs > MAX_MAP_LEAFS_ENGINE) + { + Warning ("Number of world leaves(%d) exceeded MAX_MAP_LEAFS(%d)\nIf you encounter problems when running your map, consider this the most likely cause.\n", g_dmodels[0].visleafs, MAX_MAP_LEAFS_ENGINE); + } +#endif +#ifdef ZHLT_WARNWORLDFACES + if (g_dmodels[0].numfaces > MAX_MAP_WORLDFACES) + { + Warning ("Number of world faces(%d) exceeded %d. Some faces will disappear in game.\nTo reduce world faces, change some world brushes (including func_details) to func_walls.\n", g_dmodels[0].numfaces, MAX_MAP_WORLDFACES); + } +#endif +#ifdef HLBSP_MERGECLIPNODE + Developer (DEVELOPER_LEVEL_MESSAGE, "count_mergedclipnodes = %d\n", count_mergedclipnodes); + if (!g_noclipnodemerge) + { +#ifdef ZHLT_XASH2 + int total = 0; + for (int hull = 1; hull < MAX_MAP_HULLS; hull++) + { + total += g_numclipnodes[hull - 1]; + } + Log ("Reduced %d clipnodes to %d\n", total + count_mergedclipnodes, total); +#else + Log ("Reduced %d clipnodes to %d\n", g_numclipnodes + count_mergedclipnodes, g_numclipnodes); +#endif + } +#endif + if(!g_noopt) + { +#ifdef HLCSG_HLBSP_REDUCETEXTURE + { + Log ("Reduced %d texinfos to %d\n", g_numtexinfo, g_nummappedtexinfo); + for (int i = 0; i < g_nummappedtexinfo; i++) + { + g_texinfo[i] = g_mappedtexinfo[i]; + } + g_numtexinfo = g_nummappedtexinfo; + } + { + dmiptexlump_t *l = (dmiptexlump_t *)g_dtexdata; + int &g_nummiptex = l->nummiptex; + bool *Used = (bool *)calloc (g_nummiptex, sizeof(bool)); + int Num = 0, Size = 0; + int *Map = (int *)malloc (g_nummiptex * sizeof(int)); + int i; + hlassume (Used != NULL && Map != NULL, assume_NoMemory); + int *lumpsizes = (int *)malloc (g_nummiptex * sizeof (int)); + const int newdatasizemax = g_texdatasize - ((byte *)&l->dataofs[g_nummiptex] - (byte *)l); + byte *newdata = (byte *)malloc (newdatasizemax); + int newdatasize = 0; + hlassume (lumpsizes != NULL && newdata != NULL, assume_NoMemory); + int total = 0; + for (i = 0; i < g_nummiptex; i++) + { + if (l->dataofs[i] == -1) + { + lumpsizes[i] = -1; + continue; + } + lumpsizes[i] = g_texdatasize - l->dataofs[i]; + for (int j = 0; j < g_nummiptex; j++) + { + int lumpsize = l->dataofs[j] - l->dataofs[i]; + if (l->dataofs[j] == -1 || lumpsize < 0 || lumpsize == 0 && j <= i) + continue; + if (lumpsize < lumpsizes[i]) + lumpsizes[i] = lumpsize; + } + total += lumpsizes[i]; + } + if (total != newdatasizemax) + { + Warning ("Bad texdata structure.\n"); + goto skipReduceTexdata; + } + for (i = 0; i < g_numtexinfo; i++) + { + texinfo_t *t = &g_texinfo[i]; + if (t->miptex < 0 || t->miptex >= g_nummiptex) + { + Warning ("Bad miptex number %d.\n", t->miptex); + goto skipReduceTexdata; + } + Used[t->miptex] = true; + } + for (i = 0; i < g_nummiptex; i++) + { + const int MAXWADNAME = 16; + char name[MAXWADNAME]; + int j, k; + if (l->dataofs[i] < 0) + continue; + if (Used[i] == true) + { + miptex_t *m = (miptex_t *)((byte *)l + l->dataofs[i]); + if (m->name[0] != '+' && m->name[0] != '-') + continue; + safe_strncpy (name, m->name, MAXWADNAME); + if (name[1] == '\0') + continue; + for (j = 0; j < 20; j++) + { + if (j < 10) + name[1] = '0' + j; + else + name[1] = 'A' + j - 10; + for (k = 0; k < g_nummiptex; k++) + { + if (l->dataofs[k] < 0) + continue; + miptex_t *m2 = (miptex_t *)((byte *)l + l->dataofs[k]); + if (!strcasecmp (name, m2->name)) + Used[k] = true; + } + } + } + } + for (i = 0; i < g_nummiptex; i++) + { + if (Used[i]) + { + Map[i] = Num; + Num++; + } + else + { + Map[i] = -1; + } + } + for (i = 0; i < g_numtexinfo; i++) + { + texinfo_t *t = &g_texinfo[i]; + t->miptex = Map[t->miptex]; + } + Size += (byte *)&l->dataofs[Num] - (byte *)l; + for (i = 0; i < g_nummiptex; i++) + { + if (Used[i]) + { + if (lumpsizes[i] == -1) + { + l->dataofs[Map[i]] = -1; + } + else + { + memcpy ((byte *)newdata + newdatasize, (byte *)l + l->dataofs[i], lumpsizes[i]); + l->dataofs[Map[i]] = Size; + newdatasize += lumpsizes[i]; + Size += lumpsizes[i]; + } + } + } + memcpy (&l->dataofs[Num], newdata, newdatasize); + Log ("Reduced %d texdatas to %d (%d bytes to %d)\n", g_nummiptex, Num, g_texdatasize, Size); + g_nummiptex = Num; + g_texdatasize = Size; + skipReduceTexdata:; + free (lumpsizes); + free (newdata); + free (Used); + free (Map); + } + Log ("Reduced %d planes to %d\n", g_numplanes, gNumMappedPlanes); +#endif + for(int counter = 0; counter < gNumMappedPlanes; counter++) + { + g_dplanes[counter] = gMappedPlanes[counter]; + } + g_numplanes = gNumMappedPlanes; + } +#ifdef HLCSG_HLBSP_REDUCETEXTURE + else + { + hlassume (g_numtexinfo < MAX_MAP_TEXINFO, assume_MAX_MAP_TEXINFO); + hlassume (g_numplanes < MAX_MAP_PLANES, assume_MAX_MAP_PLANES); + } +#endif +#ifdef HLBSP_BRINKHACK + if (!g_nobrink) + { + Log ("FixBrinks:\n"); +#ifdef ZHLT_XASH2 + dclipnode_t *clipnodes[MAX_MAP_HULLS - 1]; + int numclipnodes[MAX_MAP_HULLS - 1]; + for (int hull = 1; hull < MAX_MAP_HULLS; hull++) + { + clipnodes[hull - 1] = (dclipnode_t *)malloc (MAX_MAP_CLIPNODES * sizeof (dclipnode_t)); + hlassume (clipnodes[hull - 1] != NULL, assume_NoMemory); + } +#else + dclipnode_t *clipnodes; //[MAX_MAP_CLIPNODES] + int numclipnodes; + clipnodes = (dclipnode_t *)malloc (MAX_MAP_CLIPNODES * sizeof (dclipnode_t)); + hlassume (clipnodes != NULL, assume_NoMemory); +#endif + void *(*brinkinfo)[NUM_HULLS]; //[MAX_MAP_MODELS] + int (*headnode)[NUM_HULLS]; //[MAX_MAP_MODELS] + brinkinfo = (void *(*)[NUM_HULLS])malloc (MAX_MAP_MODELS * sizeof (void *[NUM_HULLS])); + hlassume (brinkinfo != NULL, assume_NoMemory); + headnode = (int (*)[NUM_HULLS])malloc (MAX_MAP_MODELS * sizeof (int [NUM_HULLS])); + hlassume (headnode != NULL, assume_NoMemory); + + int i, j, level; + for (i = 0; i < g_nummodels; i++) + { + dmodel_t *m = &g_dmodels[i]; + Developer (DEVELOPER_LEVEL_MESSAGE, " model %d\n", i); + for (j = 1; j < NUM_HULLS; j++) + { +#ifdef ZHLT_XASH2 + brinkinfo[i][j] = CreateBrinkinfo (g_dclipnodes[j - 1], m->headnode[j]); +#else + brinkinfo[i][j] = CreateBrinkinfo (g_dclipnodes, m->headnode[j]); +#endif + } + } + for (level = BrinkAny; level > BrinkNone; level--) + { +#ifdef ZHLT_XASH2 + for (int hull = 1; hull < MAX_MAP_HULLS; hull++) + { + numclipnodes[hull - 1] = 0; + } +#else + numclipnodes = 0; +#endif +#ifdef HLBSP_MERGECLIPNODE + count_mergedclipnodes = 0; +#endif + for (i = 0; i < g_nummodels; i++) + { + for (j = 1; j < NUM_HULLS; j++) + { +#ifdef ZHLT_XASH2 + if (!FixBrinks (brinkinfo[i][j], (bbrinklevel_e) level, headnode[i][j], clipnodes[j - 1], MAX_MAP_CLIPNODES, numclipnodes[j - 1], numclipnodes[j - 1])) +#else + if (!FixBrinks (brinkinfo[i][j], (bbrinklevel_e) level, headnode[i][j], clipnodes, MAX_MAP_CLIPNODES, numclipnodes, numclipnodes)) +#endif + { + break; + } + } + if (j < NUM_HULLS) + { + break; + } + } + if (i == g_nummodels) + { + break; + } + } + for (i = 0; i < g_nummodels; i++) + { + for (j = 1; j < NUM_HULLS; j++) + { + DeleteBrinkinfo (brinkinfo[i][j]); + } + } + if (level == BrinkNone) + { + Warning ("No brinks have been fixed because clipnode data is almost full."); + } + else + { + if (level != BrinkAny) + { + Warning ("Not all brinks have been fixed because clipnode data is almost full."); + } +#ifdef HLBSP_MERGECLIPNODE + Developer (DEVELOPER_LEVEL_MESSAGE, "count_mergedclipnodes = %d\n", count_mergedclipnodes); +#endif +#ifdef ZHLT_XASH2 + int g_numclipnodes_total = 0; + int numclipnodes_total = 0; + for (int hull = 1; hull < MAX_MAP_HULLS; hull++) + { + g_numclipnodes_total += g_numclipnodes[hull - 1]; + numclipnodes_total += numclipnodes[hull - 1]; + } + Log ("Increased %d clipnodes to %d.\n", g_numclipnodes_total, numclipnodes_total); + for (int hull = 1; hull < MAX_MAP_HULLS; hull++) + { + g_numclipnodes[hull - 1] = numclipnodes[hull - 1]; + memcpy (g_dclipnodes[hull - 1], clipnodes[hull - 1], numclipnodes[hull - 1] * sizeof (dclipnode_t)); + } +#else + Log ("Increased %d clipnodes to %d.\n", g_numclipnodes, numclipnodes); + g_numclipnodes = numclipnodes; + memcpy (g_dclipnodes, clipnodes, numclipnodes * sizeof (dclipnode_t)); +#endif + for (i = 0; i < g_nummodels; i++) + { + dmodel_t *m = &g_dmodels[i]; + for (j = 1; j < NUM_HULLS; j++) + { + m->headnode[j] = headnode[i][j]; + } + } + } + free (brinkinfo); + free (headnode); +#ifdef ZHLT_XASH2 + for (int hull = 1; hull < MAX_MAP_HULLS; hull++) + { + free (clipnodes[hull - 1]); + } +#else + free (clipnodes); +#endif + } +#endif + +#ifdef ZHLT_HIDDENSOUNDTEXTURE + for (int i = 0; i < g_numtexinfo; i++) + { + g_texinfo[i].flags &= ~TEX_SHOULDHIDE; + } +#endif +#ifdef ZHLT_64BIT_FIX +#ifdef PLATFORM_CAN_CALC_EXTENT + WriteExtentFile (g_extentfilename); +#else + Warning ("The " PLATFORM_VERSIONSTRING " version of hlbsp couldn't create extent file. The lack of extent file may cause hlrad error."); +#endif +#endif + if (g_chart) + { + PrintBSPFileSizes(); + } + +#ifdef HLCSG_HLBSP_DOUBLEPLANE +#undef dplane_t // this allow us to temporarily access the raw data directly without the layer of indirection +#undef g_dplanes + for (int i = 0; i < g_numplanes; i++) + { + plane_t *mp = &g_mapplanes[i]; + dplane_t *dp = &g_dplanes[i]; + VectorCopy (mp->normal, dp->normal); + dp->dist = mp->dist; + dp->type = mp->type; + } +#define dplane_t plane_t +#define g_dplanes g_mapplanes +#endif + WriteBSPFile(g_bspfilename); +} diff --git a/src/zhlt-vluzacn/hlcsg/ansitoutf8.cpp b/src/zhlt-vluzacn/hlcsg/ansitoutf8.cpp new file mode 100644 index 0000000..f8a433d --- /dev/null +++ b/src/zhlt-vluzacn/hlcsg/ansitoutf8.cpp @@ -0,0 +1,22 @@ + +#include "csg.h" + +#ifdef HLCSG_GAMETEXTMESSAGE_UTF8 +#define WIN32_LEAN_AND_MEAN +#include + +char * ANSItoUTF8 (const char *string) +{ + int len; + char *utf8; + wchar_t *unicode; + len = MultiByteToWideChar (CP_ACP, 0, string, -1, NULL, 0); + unicode = (wchar_t *)calloc (len+1, sizeof(wchar_t)); + MultiByteToWideChar (CP_ACP, 0, string, -1, unicode, len); + len = WideCharToMultiByte (CP_UTF8, 0, unicode, -1, NULL, 0, NULL, NULL); + utf8 = (char *)calloc (len+1, sizeof(char)); + WideCharToMultiByte (CP_UTF8, 0, unicode, -1, utf8, len, NULL, NULL); + free (unicode); + return utf8; +} +#endif diff --git a/src/zhlt-vluzacn/hlcsg/autowad.cpp b/src/zhlt-vluzacn/hlcsg/autowad.cpp new file mode 100644 index 0000000..d94fe7b --- /dev/null +++ b/src/zhlt-vluzacn/hlcsg/autowad.cpp @@ -0,0 +1,423 @@ +// AJM: Added this file in +#include "csg.h" +#include "cmdlib.h" + +#ifndef HLCSG_AUTOWAD_NEW +#ifdef HLCSG_AUTOWAD + +#define MAX_AUTOWAD_TEXNAME 32 // 32 char limit in array size in brush_texture_t struct + +int g_numUsedTextures = 0; + +typedef struct autowad_texname_s // non-dupicate list of textures used in map +{ + char name[MAX_AUTOWAD_TEXNAME]; + autowad_texname_s* next; +} autowad_texname_t; + +autowad_texname_t* g_autowad_texname; + +// ===================================================================================== +// Extract File stuff (ExtractFile | ExtractFilePath | ExtractFileBase) +// +// With VS 2005 - and the 64 bit build, i had to pull 3 classes over from +// cmdlib.cpp even with the proper includes to get rid of the lnk2001 error +// +// amckern - amckern@yahoo.com +// ===================================================================================== + +// Code Deleted. --vluzacn + +// ===================================================================================== +// autowad_PushName +// adds passed texname as an entry to autowad_wadnames list, without duplicates +// ===================================================================================== +void autowad_PushName(const char* const texname) +{ + autowad_texname_t* tex; + + if (!g_autowad_texname) + { + // first texture, make an entry + tex = (autowad_texname_t*)malloc(sizeof(autowad_texname_t)); + tex->next = NULL; + strcpy_s(tex->name, texname); + + g_autowad_texname = tex; + g_numUsedTextures++; + +#ifdef _DEBUG + Log("[dbg] Used texture: %i[%s]\n", g_numUsedTextures, texname); +#endif + return; + } + + // otherwise, see if texname isnt already in the list + for (tex = g_autowad_texname; ;tex = tex->next) + { + if (!strcmp(tex->name, texname)) + return; // dont bother adding it again + + if (!tex->next) + break; // end of list + } + + // unique texname + g_numUsedTextures++; + autowad_texname_t* last; + last = tex; + tex = (autowad_texname_t*)malloc(sizeof(autowad_texname_t)); + strcpy_s(tex->name, texname); + tex->next = NULL; + last->next = tex; + +#ifdef _DEBUG + Log("[dbg] Used texture: %i[%s]\n", g_numUsedTextures, texname); +#endif +} + +// ===================================================================================== +// autowad_PurgeName +// ===================================================================================== +void autowad_PurgeName(const char* const texname) +{ + autowad_texname_t* prev; + autowad_texname_t* current; + + if (!g_autowad_texname) + return; + + current = g_autowad_texname; + prev = NULL; + for (; ;) + { + if (!strcmp(current->name, texname)) + { + if (!prev) // start of the list + { + g_autowad_texname = current->next; + } + else // middle of list + { + prev->next = current->next; + } + + free(current); + return; + } + + if (!current->next) + { + //Log(" AUTOWAD: Purge Tex: texname '%s' does not exist\n", texname); + return; // end of list + } + + // move along + prev = current; + current = current->next; + } +} + +// ===================================================================================== +// autowad_PopName +// removes a name from the autowad list, puts it in memory and retuns pointer (which +// shoudl be destructed by the calling function later) +// ===================================================================================== +char* autowad_PopName() +{ + // todo: code + return NULL; +} + +// ===================================================================================== +// autowad_cleanup +// frees memory used by autowad procedure +// ===================================================================================== +void autowad_cleanup() +{ + if (g_autowad_texname != NULL) + { + autowad_texname_t* current; + autowad_texname_t* next; + + for (current = g_autowad_texname; ; current = next) + { + if (!current) + break; + + //Log("[aw] removing tex %s\n", current->name); + next = current->next; + free(current); + } + } +} + +// ===================================================================================== +// autowad_IsUsedTexture +// is this texture in the autowad list? +// ===================================================================================== +bool autowad_IsUsedTexture(const char* const texname) +{ + autowad_texname_t* current; + + if (!g_autowad_texname) + return false; + + for (current = g_autowad_texname; ; current = current->next) + { + //Log("atw: IUT: Comparing: '%s' with '%s'\n", current->name, texname); + if (!strcmp(current->name, texname)) + return true; + + if (!current->next) + return false; // reached end of list + } + + return false; +} + +#ifndef HLCSG_AUTOWAD_TEXTURELIST_FIX +// ===================================================================================== +// GetUsedTextures +// ===================================================================================== +void GetUsedTextures() +{ + int i; + side_t* bs; + + for (i = 0; i < g_numbrushsides; i++) + { + bs = &g_brushsides[i]; + autowad_PushName(bs->td.name); + } +} +#endif + +// ===================================================================================== +// autowad_UpdateUsedWads +// ===================================================================================== + +// yes, these should be in a header/common lib, but i cant be bothered + +#define MAXWADNAME 16 + +typedef struct +{ + char identification[4]; // should be WAD2/WAD3 + int numlumps; + int infotableofs; +} wadinfo_t; + +typedef struct +{ + int filepos; + int disksize; + int size; // uncompressed + char type; + char compression; + char pad1, pad2; + char name[MAXWADNAME]; // must be null terminated + + int iTexFile; // index of the wad this texture is located in + +} lumpinfo_t; + +static void CleanupName(const char* const in, char* out) +{ + int i; + + for (i = 0; i < MAXWADNAME; i++) + { + if (!in[i]) + { + break; + } + + out[i] = toupper(in[i]); + } + + for (; i < MAXWADNAME; i++) + { + out[i] = 0; + } +} + +void autowad_UpdateUsedWads() +{ + // see which wadfiles are needed + // code for this wad loop somewhat copied from below + wadinfo_t thiswad; + lumpinfo_t* thislump = NULL; + wadpath_t* currentwad; + char* pszWadFile; + FILE* texfile; + const char* pszWadroot = getenv("WADROOT"); + int i, j; + int nTexLumps = 0; + +#ifdef _DEBUG + Log("[dbg] Starting wad dependency check\n"); +#endif + + // open each wadpath and sort though its contents + for (i = 0; i < g_iNumWadPaths; i++) + { + currentwad = g_pWadPaths[i]; + pszWadFile = currentwad->path; + currentwad->usedbymap = false; // guilty until proven innocent + +#ifdef _DEBUG + Log(" Parsing wad: '%s'\n", pszWadFile); +#endif + + texfile = fopen(pszWadFile, "rb"); + + #ifdef SYSTEM_WIN32 + if (!texfile) + { + // cant find it, maybe this wad file has a hard code drive + if (pszWadFile[1] == ':') + { + pszWadFile += 2; // skip past the drive + texfile = fopen(pszWadFile, "rb"); + } + } + #endif + + char szTmp[_MAX_PATH]; + if (!texfile && pszWadroot) + { + char szFile[_MAX_PATH]; + char szSubdir[_MAX_PATH]; + + ExtractFile(pszWadFile, szFile); + + ExtractFilePath(pszWadFile, szTmp); + ExtractFile(szTmp, szSubdir); + + // szSubdir will have a trailing separator + safe_snprintf(szTmp, _MAX_PATH, "%s" SYSTEM_SLASH_STR "%s%s", pszWadroot, szSubdir, szFile); + texfile = fopen(szTmp, "rb"); + + #ifdef SYSTEM_POSIX + if (!texfile) + { + // cant find it, Convert to lower case and try again + strlwr(szTmp); + texfile = fopen(szTmp, "rb"); + } + #endif + } + + #ifdef HLCSG_SEARCHWADPATH_VL + #ifdef SYSTEM_WIN32 + if (!texfile && pszWadFile[0] == '\\') + { + char tmp[_MAX_PATH]; + int l; + for (l = 'C'; l <= 'Z'; ++l) + { + safe_snprintf (tmp, _MAX_PATH, "%c:%s", l, pszWadFile); + texfile = fopen (tmp, "rb"); + if (texfile) + { + break; + } + } + } + #endif + #endif + + if (!texfile) // still cant find it, skip this one + { +#ifdef HLCSG_SEARCHWADPATH_VL + pszWadFile = currentwad->path; // correct it back +#endif + if(pszWadroot) + { + Warning("Wad file '%s' not found, also tried '%s'",pszWadFile,szTmp); + } + else + { + Warning("Wad file '%s' not found",pszWadFile); + } + continue; + } + + // look and see if we're supposed to include the textures from this WAD in the bsp. + { + WadInclude_i it; + bool including = false; + for (it = g_WadInclude.begin(); it != g_WadInclude.end(); it++) + { + if (stristr(pszWadFile, it->c_str())) + { + #ifdef _DEBUG + Log(" - including wad\n"); + #endif + including = true; + currentwad->usedbymap = true; + break; + } + } + if (including) + { + //fclose(texfile); + //continue; + } + } + + // read in this wadfiles information + SafeRead(texfile, &thiswad, sizeof(thiswad)); + + // make sure its a valid format + if (strncmp(thiswad.identification, "WAD2", 4) && strncmp(thiswad.identification, "WAD3", 4)) + { + fclose(texfile); + continue; + } + + thiswad.numlumps = LittleLong(thiswad.numlumps); + thiswad.infotableofs = LittleLong(thiswad.infotableofs); + + // read in lump + if (fseek(texfile, thiswad.infotableofs, SEEK_SET)) + { + fclose(texfile); + continue; // had trouble reading, skip this wad + } + + // memalloc for this lump + thislump = (lumpinfo_t*)realloc(thislump, (nTexLumps + thiswad.numlumps) * sizeof(lumpinfo_t)); + // BUGBUG: is this destructed? + + // for each texlump + for (j = 0; j < thiswad.numlumps; j++, nTexLumps++) + { + SafeRead(texfile, &thislump[nTexLumps], (sizeof(lumpinfo_t) - sizeof(int)) ); // iTexFile is NOT read from file + + CleanupName(thislump[nTexLumps].name, thislump[nTexLumps].name); + + if (autowad_IsUsedTexture(thislump[nTexLumps].name)) + { + currentwad->usedbymap = true; + currentwad->usedtextures++; + #ifdef _DEBUG + Log(" - Used wadfile: [%s]\n", thislump[nTexLumps].name); + #endif + autowad_PurgeName(thislump[nTexLumps].name); + } + } + + fclose(texfile); + } + +#ifdef _DEBUG + Log("[dbg] End wad dependency check\n\n"); +#endif + return; +} + + +#endif // HLCSG_AUTOWAD +#endif diff --git a/src/zhlt-vluzacn/hlcsg/brush.cpp b/src/zhlt-vluzacn/hlcsg/brush.cpp new file mode 100644 index 0000000..329e860 --- /dev/null +++ b/src/zhlt-vluzacn/hlcsg/brush.cpp @@ -0,0 +1,2254 @@ +#include "csg.h" + +plane_t g_mapplanes[MAX_INTERNAL_MAP_PLANES]; +int g_nummapplanes; +#ifdef HLCSG_HULLBRUSH +hullshape_t g_defaulthulls[NUM_HULLS]; +int g_numhullshapes; +hullshape_t g_hullshapes[MAX_HULLSHAPES]; +#endif + +#ifdef ZHLT_LARGERANGE +#define DIST_EPSILON 0.04 +#else +#define DIST_EPSILON 0.01 +#endif + +#if !defined HLCSG_FASTFIND + +/* + * ============= + * FindIntPlane + * + * Returns which plane number to use for a given integer defined plane. + * + * ============= + */ + +int FindIntPlane(const vec_t* const normal, const vec_t* const origin) +{ + int i, j; + plane_t* p; + plane_t temp; + vec_t t; + bool locked; + + p = g_mapplanes; + locked = false; + i = 0; + + while (1) + { + if (i == g_nummapplanes) + { + if (!locked) + { + locked = true; + ThreadLock(); // make sure we don't race + } + if (i == g_nummapplanes) + { + break; // we didn't race + } + } + +#ifdef HLCSG_FACENORMALEPSILON + t = DotProduct (origin, p->normal) - p->dist; + if (fabs (t) < DIST_EPSILON) + { + for (j = 0; j < 3; j++) + { + if (fabs(normal[j] - p->normal[j]) > DIR_EPSILON) + { + break; + } + } + if (j == 3) + { + if (locked) + { + ThreadUnlock(); + } + return i; + } + } +#else + t = 0; // Unrolled loop + t += (origin[0] - p->origin[0]) * normal[0]; + t += (origin[1] - p->origin[1]) * normal[1]; + t += (origin[2] - p->origin[2]) * normal[2]; + + if (fabs(t) < DIST_EPSILON) + { // on plane + // see if the normal is forward, backwards, or off + for (j = 0; j < 3; j++) + { + if (fabs(normal[j] - p->normal[j]) > NORMAL_EPSILON) + { + break; + } + } + if (j == 3) + { + if (locked) + { + ThreadUnlock(); + } + return i; + } + } +#endif + + i++; + p++; + } + + hlassert(locked); + + // create a new plane + p->origin[0] = origin[0]; + p->origin[1] = origin[1]; + p->origin[2] = origin[2]; + + (p + 1)->origin[0] = origin[0]; + (p + 1)->origin[1] = origin[1]; + (p + 1)->origin[2] = origin[2]; + + p->normal[0] = normal[0]; + p->normal[1] = normal[1]; + p->normal[2] = normal[2]; + + (p + 1)->normal[0] = -normal[0]; + (p + 1)->normal[1] = -normal[1]; + (p + 1)->normal[2] = -normal[2]; + + hlassume(g_nummapplanes < MAX_INTERNAL_MAP_PLANES, assume_MAX_INTERNAL_MAP_PLANES); + + VectorNormalize(p->normal); + + p->type = (p + 1)->type = PlaneTypeForNormal(p->normal); +#ifdef ZHLT_PLANETYPE_FIX + if (p->type <= last_axial) + { + for (int i = 0; i < 3; i++) + { + if (i == p->type) + p->normal[i] = p->normal[i] > 0? 1: -1; + else + p->normal[i] = 0; + } + } +#endif + + p->dist = DotProduct(origin, p->normal); + VectorSubtract(vec3_origin, p->normal, (p + 1)->normal); + (p + 1)->dist = -p->dist; + + // always put axial planes facing positive first + if (p->type <= last_axial) + { + if (normal[0] < 0 || normal[1] < 0 || normal[2] < 0) + { + // flip order + temp = *p; + *p = *(p + 1); + *(p + 1) = temp; + g_nummapplanes += 2; + ThreadUnlock(); + return i + 1; + } + } + + g_nummapplanes += 2; + ThreadUnlock(); + return i; +} + +#else //ifdef HLCSG_FASTFIND + +// ===================================================================================== +// FindIntPlane, fast version (replacement by KGP) +// This process could be optimized by placing the planes in a (non hash-) set and using +// half of the inner loop check below as the comparator; I'd expect the speed gain to be +// very large given the change from O(N^2) to O(NlogN) to build the set of planes. +// ===================================================================================== + +int FindIntPlane(const vec_t* const normal, const vec_t* const origin) +{ + int returnval; + plane_t* p; + plane_t temp; + vec_t t; + + returnval = 0; + + find_plane: + for( ; returnval < g_nummapplanes; returnval++) + { + // BUG: there might be some multithread issue --vluzacn +#ifdef HLCSG_FACENORMALEPSILON + if( -DIR_EPSILON < (t = normal[0] - g_mapplanes[returnval].normal[0]) && t < DIR_EPSILON && + -DIR_EPSILON < (t = normal[1] - g_mapplanes[returnval].normal[1]) && t < DIR_EPSILON && + -DIR_EPSILON < (t = normal[2] - g_mapplanes[returnval].normal[2]) && t < DIR_EPSILON ) + { + t = DotProduct (origin, g_mapplanes[returnval].normal) - g_mapplanes[returnval].dist; + + if (-DIST_EPSILON < t && t < DIST_EPSILON) + { return returnval; } + } +#else + if( -NORMAL_EPSILON < (t = normal[0] - g_mapplanes[returnval].normal[0]) && t < NORMAL_EPSILON && + -NORMAL_EPSILON < (t = normal[1] - g_mapplanes[returnval].normal[1]) && t < NORMAL_EPSILON && + -NORMAL_EPSILON < (t = normal[2] - g_mapplanes[returnval].normal[2]) && t < NORMAL_EPSILON ) + { + //t = (origin - plane_origin) dot (normal), unrolled + t = (origin[0] - g_mapplanes[returnval].origin[0]) * normal[0] + + (origin[1] - g_mapplanes[returnval].origin[1]) * normal[1] + + (origin[2] - g_mapplanes[returnval].origin[2]) * normal[2]; + + if (-DIST_EPSILON < t && t < DIST_EPSILON) // on plane + { return returnval; } + } +#endif + } + + ThreadLock(); + if(returnval != g_nummapplanes) // make sure we don't race + { + ThreadUnlock(); + goto find_plane; //check to see if other thread added plane we need + } + + // create new planes - double check that we have room for 2 planes + hlassume(g_nummapplanes+1 < MAX_INTERNAL_MAP_PLANES, assume_MAX_INTERNAL_MAP_PLANES); + + p = &g_mapplanes[g_nummapplanes]; + + VectorCopy(origin,p->origin); + VectorCopy(normal,p->normal); + VectorNormalize(p->normal); + p->type = PlaneTypeForNormal(p->normal); +#ifdef ZHLT_PLANETYPE_FIX + if (p->type <= last_axial) + { + for (int i = 0; i < 3; i++) + { + if (i == p->type) + p->normal[i] = p->normal[i] > 0? 1: -1; + else + p->normal[i] = 0; + } + } +#endif + p->dist = DotProduct(origin, p->normal); + + VectorCopy(origin,(p+1)->origin); + VectorSubtract(vec3_origin,p->normal,(p+1)->normal); + (p+1)->type = p->type; + (p+1)->dist = -p->dist; + + // always put axial planes facing positive first +#ifdef ZHLT_PLANETYPE_FIX + if (normal[(p->type)%3] < 0) +#else + if (p->type <= last_axial && (normal[0] < 0 || normal[1] < 0 || normal[2] < 0)) // flip order +#endif + { + temp = *p; + *p = *(p+1); + *(p+1) = temp; + returnval = g_nummapplanes+1; + } + else + { returnval = g_nummapplanes; } + + g_nummapplanes += 2; + ThreadUnlock(); + return returnval; +} + +#endif //HLCSG_FASTFIND + + +int PlaneFromPoints(const vec_t* const p0, const vec_t* const p1, const vec_t* const p2) +{ + vec3_t v1, v2; + vec3_t normal; + + VectorSubtract(p0, p1, v1); + VectorSubtract(p2, p1, v2); + CrossProduct(v1, v2, normal); + if (VectorNormalize(normal)) + { + return FindIntPlane(normal, p0); + } + return -1; +} + +#ifdef HLCSG_PRECISIONCLIP + +const char ClipTypeStrings[5][11] = {{"smallest"},{"normalized"},{"simple"},{"precise"},{"legacy"}}; + +const char* GetClipTypeString(cliptype ct) +{ + return ClipTypeStrings[ct]; +} + + +// ===================================================================================== +// AddHullPlane (subroutine for replacement of ExpandBrush, KGP) +// Called to add any and all clip hull planes by the new ExpandBrush. +// ===================================================================================== + +void AddHullPlane(brushhull_t* hull, const vec_t* const normal, const vec_t* const origin, const bool check_planenum) +{ + int planenum = FindIntPlane(normal,origin); + //check to see if this plane is already in the brush (optional to speed + //up cases where we know the plane hasn't been added yet, like axial case) + if(check_planenum) + { +#ifndef HLCSG_HULLBRUSH + if(g_mapplanes[planenum].type <= last_axial) //we know axial planes are added in last step + { return; } +#endif + + bface_t* current_face; + for(current_face = hull->faces; current_face; current_face = current_face->next) + { + if(current_face->planenum == planenum) + { return; } //don't add a plane twice + } + } + bface_t* new_face = (bface_t*)Alloc(sizeof(bface_t)); // TODO: This leaks + new_face->planenum = planenum; + new_face->plane = &g_mapplanes[new_face->planenum]; + new_face->next = hull->faces; + new_face->contents = CONTENTS_EMPTY; + hull->faces = new_face; +#ifdef HLCSG_HLBSP_VOIDTEXINFO + new_face->texinfo = -1; +#else + new_face->texinfo = 0; +#endif +} + +// ===================================================================================== +// ExpandBrush (replacement by KGP) +// Since the six bounding box planes were always added anyway, they've been moved to +// an explicit separate step eliminating the need to check for duplicate planes (which +// should be using plane numbers instead of the full definition anyway). +// +// The core of the new function adds additional bevels to brushes containing faces that +// have 3 nonzero normal components -- this is necessary to finish the beveling process, +// but is turned off by default for backward compatability and because the number of +// clipnodes and faces will go up with the extra beveling. The advantage of the extra +// precision comes from the absense of "sticky" outside corners on ackward geometry. +// +// Another source of "sticky" walls has been the inconsistant offset along each axis +// (variant with plane normal in the old code). The normal component of the offset has +// been scrapped (it made a ~29% difference in the worst case of 45 degrees, or about 10 +// height units for a standard half-life player hull). The new offsets generate fewer +// clipping nodes and won't cause players to stick when moving across 2 brushes that flip +// sign along an axis (this wasn't noticible on floors because the engine took care of the +// invisible 0-3 unit steps it generated, but was noticible with walls). +// +// To prevent players from floating 10 units above the floor, the "precise" hull generation +// option still uses the plane normal when the Z component is high enough for the plane to +// be considered a floor. The "simple" hull generation option always uses the full hull +// distance, resulting in lower clipnode counts. +// +// Bevel planes might be added twice (once from each side of the edge), so a planenum +// based check is used to see if each has been added before. +// ===================================================================================== +// Correction: //--vluzacn +// Clipnode size depends on complexity of the surface of expanded brushes as a whole, not number of brush sides. +// Data from a sample map: +// cliptype simple precise legacy normalized smallest +// clipnodecount 971 1089 1202 1232 1000 + +#ifdef HLCSG_HULLBRUSH +void ExpandBrushWithHullBrush (const brush_t *brush, const brushhull_t *hull0, const hullbrush_t *hb, brushhull_t *hull) +{ + const hullbrushface_t *hbf; + const hullbrushedge_t *hbe; + const hullbrushvertex_t *hbv; + bface_t *f; + vec3_t normal; + vec3_t origin; + bool *axialbevel; + bool warned; + + axialbevel = (bool *)malloc (hb->numfaces * sizeof (bool)); + memset (axialbevel, 0, hb->numfaces * sizeof (bool)); + warned = false; + + // check for collisions of face-vertex type. face-edge type is also permitted. face-face type is excluded. + for (f = hull0->faces; f; f = f->next) + { + hullbrushface_t brushface; + VectorCopy (f->plane->normal, brushface.normal); + VectorCopy (f->plane->origin, brushface.point); + + // check for coplanar hull brush face + for (hbf = hb->faces; hbf < hb->faces + hb->numfaces; hbf++) + { + if (-DotProduct (hbf->normal, brushface.normal) < 1 - ON_EPSILON) + { + continue; + } + // now test precisely + vec_t dotmin; + vec_t dotmax; + dotmin = BOGUS_RANGE; + dotmax = -BOGUS_RANGE; + hlassume (hbf->numvertexes >= 1, assume_first); + for (vec3_t *v = hbf->vertexes; v < hbf->vertexes + hbf->numvertexes; v++) + { + vec_t dot; + dot = DotProduct (*v, brushface.normal); + dotmin = qmin (dotmin, dot); + dotmax = qmax (dotmax, dot); + } + if (dotmax - dotmin <= EQUAL_EPSILON) + { + break; + } + } + if (hbf < hb->faces + hb->numfaces) + { + if (f->bevel) + { + axialbevel[hbf - hb->faces] = true; + } + continue; // the same plane will be added in the last stage + } + + // find the impact point + vec3_t bestvertex; + vec_t bestdist; + bestdist = BOGUS_RANGE; + hlassume (hb->numvertexes >= 1, assume_first); + for (hbv = hb->vertexes; hbv < hb->vertexes + hb->numvertexes; hbv++) + { + if (hbv == hb->vertexes || DotProduct (hbv->point, brushface.normal) < bestdist - NORMAL_EPSILON) + { + bestdist = DotProduct (hbv->point, brushface.normal); + VectorCopy (hbv->point, bestvertex); + } + } + + // add hull plane for this face + VectorCopy (brushface.normal, normal); + if (f->bevel) + { + VectorCopy (brushface.point, origin); + } + else + { + VectorSubtract (brushface.point, bestvertex, origin); + } + AddHullPlane (hull, normal, origin, true); + } + + // check for edge-edge type. edge-face type and face-edge type are excluded. + for (f = hull0->faces; f; f = f->next) + { + for (int i = 0; i < f->w->m_NumPoints; i++) // for each edge in f + { + hullbrushedge_t brushedge; + VectorCopy (f->plane->normal, brushedge.normals[0]); + VectorCopy (f->w->m_Points[(i + 1) % f->w->m_NumPoints], brushedge.vertexes[0]); + VectorCopy (f->w->m_Points[i], brushedge.vertexes[1]); + VectorCopy (brushedge.vertexes[0], brushedge.point); + VectorSubtract (brushedge.vertexes[1], brushedge.vertexes[0], brushedge.delta); + + // fill brushedge.normals[1] + int found; + found = 0; + for (bface_t *f2 = hull0->faces; f2; f2 = f2->next) + { + for (int j = 0; j < f2->w->m_NumPoints; j++) + { + if (VectorCompare (f2->w->m_Points[(j + 1) % f2->w->m_NumPoints], brushedge.vertexes[1]) && + VectorCompare (f2->w->m_Points[j], brushedge.vertexes[0])) + { + VectorCopy (f2->plane->normal, brushedge.normals[1]); + found++; + } + } + } + if (found != 1) + { + if (!warned) + { + Warning("Illegal Brush (edge without opposite face): Entity %i, Brush %i\n", +#ifdef HLCSG_COUNT_NEW + brush->originalentitynum, brush->originalbrushnum +#else + brush->entitynum, brush->brushnum +#endif + ); + warned = true; + } + continue; + } + + // make sure the math is accurate + vec_t len; + len = VectorLength (brushedge.delta); + CrossProduct (brushedge.normals[0], brushedge.normals[1], brushedge.delta); + if (!VectorNormalize (brushedge.delta)) + { + continue; + } + VectorScale (brushedge.delta, len, brushedge.delta); + + // check for each edge in the hullbrush + for (hbe = hb->edges; hbe < hb->edges + hb->numedges; hbe++) + { + vec_t dot[4]; + dot[0] = DotProduct (hbe->delta, brushedge.normals[0]); + dot[1] = DotProduct (hbe->delta, brushedge.normals[1]); + dot[2] = DotProduct (brushedge.delta, hbe->normals[0]); + dot[3] = DotProduct (brushedge.delta, hbe->normals[1]); + if (dot[0] <= ON_EPSILON || dot[1] >= -ON_EPSILON || dot[2] <= ON_EPSILON || dot[3] >= -ON_EPSILON) + { + continue; + } + + // in the outer loop, each edge in the brush will be iterated twice (once from f and once from the corresponding f2) + // but since brushedge.delta are exactly the opposite between the two iterations + // only one of them can reach here + vec3_t e1; + vec3_t e2; + VectorCopy (brushedge.delta, e1); + VectorNormalize (e1); + VectorCopy (hbe->delta, e2); + VectorNormalize (e2); + CrossProduct (e1, e2, normal); + if (!VectorNormalize (normal)) + { + continue; + } + VectorSubtract (brushedge.point, hbe->point, origin); + AddHullPlane (hull, normal, origin, true); + } + } + } + + // check for vertex-face type. edge-face type and face-face type are permitted. + for (hbf = hb->faces; hbf < hb->faces + hb->numfaces; hbf++) + { + // find the impact point + vec3_t bestvertex; + vec_t bestdist; + bestdist = BOGUS_RANGE; + if (!hull0->faces) + { + continue; + } + for (f = hull0->faces; f; f = f->next) + { + for (vec3_t *v = f->w->m_Points; v < f->w->m_Points + f->w->m_NumPoints; v++) + { + if (DotProduct (*v, hbf->normal) < bestdist - NORMAL_EPSILON) + { + bestdist = DotProduct (*v, hbf->normal); + VectorCopy (*v, bestvertex); + } + } + } + + // add hull plane for this face + VectorSubtract (vec3_origin, hbf->normal, normal); + if (axialbevel[hbf - hb->faces]) + { + VectorCopy (bestvertex, origin); + } + else + { + VectorSubtract (bestvertex, hbf->point, origin); + } + AddHullPlane (hull, normal, origin, true); + } + + free (axialbevel); +} + +#endif +void ExpandBrush(brush_t* brush, const int hullnum) +{ +#ifdef HLCSG_HULLBRUSH + const hullshape_t *hs = &g_defaulthulls[hullnum]; + { // look up the name of its hull shape in g_hullshapes[] + const char *name = brush->hullshapes[hullnum]; + if (name && *name) + { + bool found = false; + for (int i = 0; i < g_numhullshapes; i++) + { + const hullshape_t *s = &g_hullshapes[i]; + if (!strcmp (name, s->id)) + { + if (found) + { + Warning ("Entity %i, Brush %i: Found several info_hullshape entities with the same name '%s'.", +#ifdef HLCSG_COUNT_NEW + brush->originalentitynum, brush->originalbrushnum, +#else + brush->entitynum, brush->brushnum, +#endif + name); + } + hs = s; + found = true; + } + } + if (!found) + { + Error ("Entity %i, Brush %i: Couldn't find info_hullshape entity '%s'.", +#ifdef HLCSG_COUNT_NEW + brush->originalentitynum, brush->originalbrushnum, +#else + brush->entitynum, brush->brushnum, +#endif + name); + } + } + } + + if (!hs->disabled) + { + if (hs->numbrushes == 0) + { + return; // leave this hull of this brush empty (noclip) + } + ExpandBrushWithHullBrush (brush, &brush->hulls[0], hs->brushes[0], &brush->hulls[hullnum]); + + return; + } +#endif + //for looping through the faces and constructing the hull + bface_t* current_face; + plane_t* current_plane; + brushhull_t* hull; + vec3_t origin, normal; + + //for non-axial bevel testing + Winding* winding; + bface_t* other_face; + plane_t* other_plane; + Winding* other_winding; + vec3_t edge_start, edge_end, edge, bevel_edge; + unsigned int counter, counter2, dir; + bool start_found,end_found; + bool axialbevel[last_axial+1][2] = { {false,false}, {false,false}, {false,false} }; + + bool warned = false; + + hull = &brush->hulls[hullnum]; + + // step 1: for collision between player vertex and brush face. --vluzacn + for(current_face = brush->hulls[0].faces; current_face; current_face = current_face->next) + { + current_plane = current_face->plane; + + //don't bother adding axial planes, + //they're defined by adding the bounding box anyway + if(current_plane->type <= last_axial) + { + //flag case where bounding box shouldn't expand +#ifdef HLCSG_CUSTOMHULL + if (current_face->bevel) +#else + if((g_texinfo[current_face->texinfo].flags & TEX_BEVEL)) +#endif + { + switch(current_plane->type) + { + case plane_x: + axialbevel[plane_x][(current_plane->normal[0] > 0 ? 1 : 0)] = true; + break; + case plane_y: + axialbevel[plane_y][(current_plane->normal[1] > 0 ? 1 : 0)] = true; + break; + case plane_z: + axialbevel[plane_z][(current_plane->normal[2] > 0 ? 1 : 0)] = true; + break; + } + } + continue; + } + + //add the offset non-axial plane to the expanded hull + VectorCopy(current_plane->origin, origin); + VectorCopy(current_plane->normal, normal); + + //old code multiplied offset by normal -- this led to post-csg "sticky" walls where a + //slope met an axial plane from the next brush since the offset from the slope would be less + //than the full offset for the axial plane -- the discontinuity also contributes to increased + //clipnodes. If the normal is zero along an axis, shifting the origin in that direction won't + //change the plane number, so I don't explicitly test that case. The old method is still used if + //preciseclip is turned off to allow backward compatability -- some of the improperly beveled edges + //grow using the new origins, and might cause additional problems. + +#ifdef HLCSG_CUSTOMHULL + if (current_face->bevel) +#else + if((g_texinfo[current_face->texinfo].flags & TEX_BEVEL)) +#endif + { + //don't adjust origin - we'll correct g_texinfo's flags in a later step + } +#ifdef HLCSG_CLIPTYPEPRECISE_EPSILON_FIX + // The old offset will generate an extremely small gap when the normal is close to axis, causing epsilon errors (ambiguous leafnode content, player falling into ground, etc.). + // For example: with the old shifting method, slopes with angle arctan(1/8) and arctan(1/64) will result in gaps of 0.0299 unit and 0.000488 unit respectively, which are smaller than ON_EPSILON, while in both 'simple' cliptype and the new method, the gaps are 2.0 units and 0.25 unit, which are good. + // This also reduce the number of clipnodes used for cliptype precise. + // The maximum difference in height between the old offset and the new offset is 0.86 unit for standing player and 6.9 units for ducking player. (when FLOOR_Z = 0.7) + // And another reason not to use the old offset is that the old offset is quite wierd. It might appears at first glance that it regards the player as an ellipse, but in fact it isn't, and the player's feet may even sink slightly into the ground theoretically for slopes of certain angles. + else if (g_cliptype == clip_precise && normal[2] > FLOOR_Z) + { + origin[0] += 0; + origin[1] += 0; + origin[2] += g_hull_size[hullnum][1][2]; + } + else if (g_cliptype == clip_legacy || g_cliptype == clip_normalized) +#else + else if(g_cliptype == clip_legacy || (g_cliptype == clip_precise && (normal[2] > FLOOR_Z)) || g_cliptype == clip_normalized) +#endif + { + if(normal[0]) + { origin[0] += normal[0] * (normal[0] > 0 ? g_hull_size[hullnum][1][0] : -g_hull_size[hullnum][0][0]); } + if(normal[1]) + { origin[1] += normal[1] * (normal[1] > 0 ? g_hull_size[hullnum][1][1] : -g_hull_size[hullnum][0][1]); } + if(normal[2]) + { origin[2] += normal[2] * (normal[2] > 0 ? g_hull_size[hullnum][1][2] : -g_hull_size[hullnum][0][2]); } + } + else + { + origin[0] += g_hull_size[hullnum][(normal[0] > 0 ? 1 : 0)][0]; + origin[1] += g_hull_size[hullnum][(normal[1] > 0 ? 1 : 0)][1]; + origin[2] += g_hull_size[hullnum][(normal[2] > 0 ? 1 : 0)][2]; + } + + AddHullPlane(hull,normal,origin,false); + } //end for loop over all faces + + // step 2: for collision between player edge and brush edge. --vluzacn + + //split bevel check into a second pass so we don't have to check for duplicate planes when adding offset planes + //in step above -- otherwise a bevel plane might duplicate an offset plane, causing problems later on. + + //only executes if cliptype is simple, normalized or precise + if(g_cliptype == clip_simple || g_cliptype == clip_precise || g_cliptype == clip_normalized) + { + for(current_face = brush->hulls[0].faces; current_face; current_face = current_face->next) + { + current_plane = current_face->plane; +#ifndef HLCSG_BEVELMISSINGFIX + // for example, (0, 0.707, 0.707) -edge- (0.707, 0, -0.707). --vluzacn + if(current_plane->type <= last_axial || !current_plane->normal[0] || !current_plane->normal[1] || !current_plane->normal[2]) + { continue; } //only add bevels to completely non-axial planes +#endif + + //test to see if the plane is completely non-axial (if it is, need to add bevels to any + //existing "inflection edges" where there's a sign change with a neighboring plane's normal for + //a given axis) + + //move along winding and find plane on other side of each edge. If normals change sign, + //add a new plane by offsetting the points of the winding to bevel the edge in that direction. + //It's possible to have inflection in multiple directions -- in this case, a new plane + //must be added for each sign change in the edge. + + winding = current_face->w; + + for(counter = 0; counter < (winding->m_NumPoints); counter++) //for each edge + { + VectorCopy(winding->m_Points[counter],edge_start); + VectorCopy(winding->m_Points[(counter+1)%winding->m_NumPoints],edge_end); + + //grab the edge (find relative length) + VectorSubtract(edge_end,edge_start,edge); + + //brute force - need to check every other winding for common points -- if the points match, the + //other face is the one we need to look at. + for(other_face = brush->hulls[0].faces; other_face; other_face = other_face->next) + { + if(other_face == current_face) + { continue; } + start_found = false; + end_found = false; + other_winding = other_face->w; + for(counter2 = 0; counter2 < other_winding->m_NumPoints; counter2++) + { + if(!start_found && VectorCompare(other_winding->m_Points[counter2],edge_start)) + { start_found = true; } + if(!end_found && VectorCompare(other_winding->m_Points[counter2],edge_end)) + { end_found = true; } + if(start_found && end_found) + { break; } //we've found the face we want, move on to planar comparison + } // for each point in other winding + if(start_found && end_found) + { break; } //we've found the face we want, move on to planar comparison + } // for each face + + if(!other_face) + { + if(hullnum == 1 && !warned) + { + Warning("Illegal Brush (edge without opposite face): Entity %i, Brush %i\n", +#ifdef HLCSG_COUNT_NEW + brush->originalentitynum, brush->originalbrushnum +#else + brush->entitynum, brush->brushnum +#endif + ); + warned = true; + } + continue; + } + + other_plane = other_face->plane; + + + //check each direction for sign change in normal -- zero can be safely ignored + for(dir = 0; dir < 3; dir++) + { +#ifdef HLCSG_BEVELMISSINGFIX + if(current_plane->normal[dir]*other_plane->normal[dir] < -NORMAL_EPSILON) //sign changed, add bevel +#else + if(current_plane->normal[dir]*other_plane->normal[dir] < 0) //sign changed, add bevel +#endif + { + //pick direction of bevel edge by looking at normal of existing planes + VectorClear(bevel_edge); + bevel_edge[dir] = (current_plane->normal[dir] > 0) ? -1 : 1; + + //find normal by taking normalized cross of the edge vector and the bevel edge + CrossProduct(edge,bevel_edge,normal); + + //normalize to length 1 + VectorNormalize(normal); +#ifdef HLCSG_BEVELMISSINGFIX + if (fabs (normal[(dir+1)%3]) <= NORMAL_EPSILON || fabs (normal[(dir+2)%3]) <= NORMAL_EPSILON) + { // coincide with axial plane + continue; + } +#endif + + //get the origin + VectorCopy(edge_start,origin); + + //unrolled loop - legacy never hits this point, so don't test for it +#ifdef HLCSG_CLIPTYPEPRECISE_EPSILON_FIX + if (g_cliptype == clip_precise && normal[2] > FLOOR_Z) + { + origin[0] += 0; + origin[1] += 0; + origin[2] += g_hull_size[hullnum][1][2]; + } + else if (g_cliptype == clip_normalized) +#else + if((g_cliptype == clip_precise && (normal[2] > FLOOR_Z)) || g_cliptype == clip_normalized) +#endif + { + if(normal[0]) + { origin[0] += normal[0] * (normal[0] > 0 ? g_hull_size[hullnum][1][0] : -g_hull_size[hullnum][0][0]); } + if(normal[1]) + { origin[1] += normal[1] * (normal[1] > 0 ? g_hull_size[hullnum][1][1] : -g_hull_size[hullnum][0][1]); } + if(normal[2]) + { origin[2] += normal[2] * (normal[2] > 0 ? g_hull_size[hullnum][1][2] : -g_hull_size[hullnum][0][2]); } + } + else //simple or precise for non-floors + { + //note: if normal == 0 in direction indicated, shifting origin doesn't change plane # + origin[0] += g_hull_size[hullnum][(normal[0] > 0 ? 1 : 0)][0]; + origin[1] += g_hull_size[hullnum][(normal[1] > 0 ? 1 : 0)][1]; + origin[2] += g_hull_size[hullnum][(normal[2] > 0 ? 1 : 0)][2]; + } + + //add the bevel plane to the expanded hull + AddHullPlane(hull,normal,origin,true); //double check that this edge hasn't been added yet + } + } //end for loop (check for each direction) + } //end for loop (over all edges in face) + } //end for loop (over all faces in hull 0) + } //end if completely non-axial + + // step 3: for collision between player face and brush vertex. --vluzacn + + //add the bounding box to the expanded hull -- for a + //completely axial brush, this is the only necessary step + + //add mins + VectorAdd(brush->hulls[0].bounds.m_Mins, g_hull_size[hullnum][0], origin); + normal[0] = -1; + normal[1] = 0; + normal[2] = 0; + AddHullPlane(hull,normal,(axialbevel[plane_x][0] ? brush->hulls[0].bounds.m_Mins : origin),false); + normal[0] = 0; + normal[1] = -1; + AddHullPlane(hull,normal,(axialbevel[plane_y][0] ? brush->hulls[0].bounds.m_Mins : origin),false); + normal[1] = 0; + normal[2] = -1; + AddHullPlane(hull,normal,(axialbevel[plane_z][0] ? brush->hulls[0].bounds.m_Mins : origin),false); + + normal[2] = 0; + + //add maxes + VectorAdd(brush->hulls[0].bounds.m_Maxs, g_hull_size[hullnum][1], origin); + normal[0] = 1; + AddHullPlane(hull,normal,(axialbevel[plane_x][1] ? brush->hulls[0].bounds.m_Maxs : origin),false); + normal[0] = 0; + normal[1] = 1; + AddHullPlane(hull,normal,(axialbevel[plane_y][1] ? brush->hulls[0].bounds.m_Maxs : origin),false); + normal[1] = 0; + normal[2] = 1; + AddHullPlane(hull,normal,(axialbevel[plane_z][1] ? brush->hulls[0].bounds.m_Maxs : origin),false); +/* + bface_t* hull_face; //sanity check + + for(hull_face = hull->faces; hull_face; hull_face = hull_face->next) + { + for(current_face = brush->hulls[0].faces; current_face; current_face = current_face->next) + { + if(current_face->w->m_NumPoints < 3) + { continue; } + for(counter = 0; counter < current_face->w->m_NumPoints; counter++) + { + if(DotProduct(hull_face->plane->normal,hull_face->plane->origin) < DotProduct(hull_face->plane->normal,current_face->w->m_Points[counter])) + { + Warning("Illegal Brush (clip hull [%i] has backward face): Entity %i, Brush %i\n",hullnum, +#ifdef HLCSG_COUNT_NEW + brush->originalentitynum, brush->originalbrushnum +#else + brush->entitynum, brush->brushnum +#endif + ); + break; + } + } + } + } +*/ +} +#else //!HLCSG_PRECISIONCLIP + +#define MAX_HULL_POINTS 32 +#define MAX_HULL_EDGES 64 + +typedef struct +{ + brush_t* b; + int hullnum; + int num_hull_points; + vec3_t hull_points[MAX_HULL_POINTS]; + vec3_t hull_corners[MAX_HULL_POINTS * 8]; + int num_hull_edges; + int hull_edges[MAX_HULL_EDGES][2]; +} expand_t; + +/* + * ============= + * IPlaneEquiv + * + * ============= + */ +bool IPlaneEquiv(const plane_t* const p1, const plane_t* const p2) +{ + vec_t t; + int j; + + // see if origin is on plane + t = 0; + for (j = 0; j < 3; j++) + { + t += (p2->origin[j] - p1->origin[j]) * p2->normal[j]; + } + if (fabs(t) > DIST_EPSILON) + { + return false; + } + + // see if the normal is forward, backwards, or off + for (j = 0; j < 3; j++) + { + if (fabs(p2->normal[j] - p1->normal[j]) > NORMAL_EPSILON) + { + break; + } + } + if (j == 3) + { + return true; + } + + for (j = 0; j < 3; j++) + { + if (fabs(p2->normal[j] - p1->normal[j]) > NORMAL_EPSILON) + { + break; + } + } + if (j == 3) + { + return true; + } + + return false; +} + +/* + * ============ + * AddBrushPlane + * ============= + */ +void AddBrushPlane(const expand_t* const ex, const plane_t* const plane) +{ + plane_t* pl; + bface_t* f; + bface_t* nf; + brushhull_t* h; + + h = &ex->b->hulls[ex->hullnum]; + // see if the plane has allready been added + for (f = h->faces; f; f = f->next) + { + pl = f->plane; + if (IPlaneEquiv(plane, pl)) + { + return; + } + } + + nf = (bface_t*)Alloc(sizeof(*nf)); // TODO: This leaks + nf->planenum = FindIntPlane(plane->normal, plane->origin); + nf->plane = &g_mapplanes[nf->planenum]; + nf->next = h->faces; + nf->contents = CONTENTS_EMPTY; + h->faces = nf; + +#ifdef HLCSG_HLBSP_VOIDTEXINFO + nf->texinfo = -1; +#else + nf->texinfo = 0; // all clip hulls have same texture +#endif +} + +// ===================================================================================== +// ExpandBrush +// ===================================================================================== +void ExpandBrush(brush_t* b, const int hullnum) +{ + int x; + int s; + int corner; + bface_t* brush_faces; + bface_t* f; + bface_t* nf; + plane_t* p; + plane_t plane; + vec3_t origin; + vec3_t normal; + expand_t ex; + brushhull_t* h; + bool axial; + + brush_faces = b->hulls[0].faces; + h = &b->hulls[hullnum]; + + ex.b = b; + ex.hullnum = hullnum; + ex.num_hull_points = 0; + ex.num_hull_edges = 0; + + // expand all of the planes + + axial = true; + + // for each of this brushes faces + for (f = brush_faces; f; f = f->next) + { + p = f->plane; + if (p->type > last_axial) // ajm: last_axial == (planetypes enum)plane_z == (2) + { + axial = false; // not an xyz axial plane + } + + VectorCopy(p->origin, origin); + VectorCopy(p->normal, normal); + + for (x = 0; x < 3; x++) + { + if (p->normal[x] > 0) + { + corner = g_hull_size[hullnum][1][x]; + } + else if (p->normal[x] < 0) + { + corner = -g_hull_size[hullnum][0][x]; + } + else + { + corner = 0; + } + origin[x] += p->normal[x] * corner; + } + nf = (bface_t*)Alloc(sizeof(*nf)); // TODO: This leaks + + nf->planenum = FindIntPlane(normal, origin); + nf->plane = &g_mapplanes[nf->planenum]; + nf->next = h->faces; + nf->contents = CONTENTS_EMPTY; + h->faces = nf; +#ifdef HLCSG_HLBSP_VOIDTEXINFO + nf->texinfo = -1; +#else + nf->texinfo = 0; // all clip hulls have same texture +#endif +// nf->texinfo = f->texinfo; // Hack to view clipping hull with textures (might crash halflife) + } + + // if this was an axial brush, we are done + if (axial) + { + return; + } + + // 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, plane.normal); + plane.normal[x] = s; + if (s == -1) + { + VectorAdd(b->hulls[0].bounds.m_Mins, g_hull_size[hullnum][0], plane.origin); + } + else + { + VectorAdd(b->hulls[0].bounds.m_Maxs, g_hull_size[hullnum][1], plane.origin); + } + AddBrushPlane(&ex, &plane); + } + } +} + +#endif //HLCSG_PRECISIONCLIP + +// ===================================================================================== +// MakeHullFaces +// ===================================================================================== +#ifdef HLCSG_SORTSIDES +void SortSides (brushhull_t *h) +{ + int numsides; + bface_t **sides; + vec3_t *normals; + bool *isused; + int i, j; + int *sorted; + bface_t *f; + for (numsides = 0, f = h->faces; f; f = f->next) + { + numsides++; + } + sides = (bface_t **)malloc (numsides * sizeof (bface_t *)); + hlassume (sides != NULL, assume_NoMemory); + normals = (vec3_t *)malloc (numsides * sizeof (vec3_t)); + hlassume (normals != NULL, assume_NoMemory); + isused = (bool *)malloc (numsides * sizeof (bool)); + hlassume (isused != NULL, assume_NoMemory); + sorted = (int *)malloc (numsides * sizeof (int)); + hlassume (sorted != NULL, assume_NoMemory); + for (i = 0, f = h->faces; f; i++, f = f->next) + { + sides[i] = f; + isused[i] = false; + const plane_t *p = &g_mapplanes[f->planenum]; + VectorCopy (p->normal, normals[i]); + } + for (i = 0; i < numsides; i++) + { + int bestside; + int bestaxial = -1; + for (j = 0; j < numsides; j++) + { + if (isused[j]) + { + continue; + } + int axial = (fabs (normals[j][0]) < NORMAL_EPSILON) + (fabs (normals[j][1]) < NORMAL_EPSILON) + (fabs (normals[j][2]) < NORMAL_EPSILON); + if (axial > bestaxial) + { + bestside = j; + bestaxial = axial; + } + } + sorted[i] = bestside; + isused[bestside] = true; + } + for (i = -1; i < numsides; i++) + { + *(i >= 0? &sides[sorted[i]]->next: &h->faces) = (i + 1 < numsides? sides[sorted[i + 1]]: NULL); + } + free (sides); + free (normals); + free (isused); + free (sorted); +} +#endif +void MakeHullFaces(const brush_t* const b, brushhull_t *h) +{ + bface_t* f; + bface_t* f2; +#ifdef HLCSG_PRECISIONCLIP //#ifdef HLCSG_PRECISECLIP //vluzacn +#ifndef HLCSG_CUSTOMHULL + bool warned = false; +#endif +#endif +#ifdef HLCSG_SORTSIDES + // this will decrease AllocBlock amount + SortSides (h); +#endif + +restart: + h->bounds.reset(); + + // for each face in this brushes hull + for (f = h->faces; f; f = f->next) + { + Winding* w = new Winding(f->plane->normal, f->plane->dist); + for (f2 = h->faces; f2; f2 = f2->next) + { + if (f == f2) + { + continue; + } + const plane_t* p = &g_mapplanes[f2->planenum ^ 1]; + if (!w->Chop(p->normal, p->dist +#ifdef HLCSG_MakeHullFaces_PRECISE + , NORMAL_EPSILON // fix "invalid brush" in ExpandBrush +#endif + )) // Nothing left to chop (getArea will return 0 for us in this case for below) + { + break; + } + } +#ifdef HLCSG_MakeHullFaces_PRECISE + w->RemoveColinearPoints (ON_EPSILON); +#endif + if (w->getArea() < 0.1) + { +#ifdef HLCSG_PRECISIONCLIP //#ifdef HLCSG_PRECISECLIP //vluzacn +#ifndef HLCSG_CUSTOMHULL // this occurs when there are BEVEL faces. + if(w->getArea() == 0 && !warned) //warn user when there's a bad brush (face not contributing) + { + Warning("Illegal Brush (plane doesn't contribute to final shape): Entity %i, Brush %i\n", +#ifdef HLCSG_COUNT_NEW + b->originalentitynum, b->originalbrushnum +#else + b->entitynum, b->brushnum +#endif + ); + warned = true; + } +#endif +#endif + delete w; + if (h->faces == f) + { + h->faces = f->next; + } + else + { + for (f2 = h->faces; f2->next != f; f2 = f2->next) + { + ; + } + f2->next = f->next; + } + goto restart; + } + else + { + f->w = w; + f->contents = CONTENTS_EMPTY; + unsigned int i; + for (i = 0; i < w->m_NumPoints; i++) + { + h->bounds.add(w->m_Points[i]); + } + } + } + + unsigned int i; + for (i = 0; i < 3; i++) + { + if (h->bounds.m_Mins[i] < -BOGUS_RANGE / 2 || h->bounds.m_Maxs[i] > BOGUS_RANGE / 2) + { + Fatal(assume_BRUSH_OUTSIDE_WORLD, "Entity %i, Brush %i: outside world(+/-%d): (%.0f,%.0f,%.0f)-(%.0f,%.0f,%.0f)", +#ifdef HLCSG_COUNT_NEW + b->originalentitynum, b->originalbrushnum, +#else + b->entitynum, b->brushnum, +#endif + BOGUS_RANGE / 2, + h->bounds.m_Mins[0], h->bounds.m_Mins[1], h->bounds.m_Mins[2], + h->bounds.m_Maxs[0], h->bounds.m_Maxs[1], h->bounds.m_Maxs[2]); + } + } +} + +// ===================================================================================== +// MakeBrushPlanes +// ===================================================================================== +bool MakeBrushPlanes(brush_t* b) +{ + int i; + int j; + int planenum; + side_t* s; + bface_t* f; + vec3_t origin; + + // + // if the origin key is set (by an origin brush), offset all of the values + // + GetVectorForKey(&g_entities[b->entitynum], "origin", origin); + + // + // convert to mapplanes + // + // for each side in this brush + for (i = 0; i < b->numsides; i++) + { + s = &g_brushsides[b->firstside + i]; + for (j = 0; j < 3; j++) + { + VectorSubtract(s->planepts[j], origin, s->planepts[j]); + } + planenum = PlaneFromPoints(s->planepts[0], s->planepts[1], s->planepts[2]); + if (planenum == -1) + { + Fatal(assume_PLANE_WITH_NO_NORMAL, "Entity %i, Brush %i, Side %i: plane with no normal", +#ifdef HLCSG_COUNT_NEW + b->originalentitynum, b->originalbrushnum +#else + b->entitynum, b->brushnum +#endif + , i); + } + + // + // see if the plane has been used already + // + for (f = b->hulls[0].faces; f; f = f->next) + { + if (f->planenum == planenum || f->planenum == (planenum ^ 1)) + { + Fatal(assume_BRUSH_WITH_COPLANAR_FACES, "Entity %i, Brush %i, Side %i: has a coplanar plane at (%.0f, %.0f, %.0f), texture %s", +#ifdef HLCSG_COUNT_NEW + b->originalentitynum, b->originalbrushnum +#else + b->entitynum, b->brushnum +#endif + , i, s->planepts[0][0] + origin[0], s->planepts[0][1] + origin[1], + s->planepts[0][2] + origin[2], s->td.name); + } + } + + f = (bface_t*)Alloc(sizeof(*f)); // TODO: This leaks + + f->planenum = planenum; + f->plane = &g_mapplanes[planenum]; + f->next = b->hulls[0].faces; + b->hulls[0].faces = f; + f->texinfo = g_onlyents ? 0 : TexinfoForBrushTexture(f->plane, &s->td, origin +#ifdef ZHLT_HIDDENSOUNDTEXTURE + , s->shouldhide +#endif + ); +#ifdef HLCSG_CUSTOMHULL + f->bevel = b->bevel || s->bevel; +#endif + } + + return true; +} + + +// ===================================================================================== +// TextureContents +// ===================================================================================== +static contents_t TextureContents(const char* const name) +{ +#ifdef HLCSG_CUSTOMCONTENT + if (!strncasecmp(name, "contentsolid", 12)) + return CONTENTS_SOLID; + if (!strncasecmp(name, "contentwater", 12)) + return CONTENTS_WATER; + if (!strncasecmp(name, "contentempty", 12)) + return CONTENTS_TOEMPTY; + if (!strncasecmp(name, "contentsky", 10)) + return CONTENTS_SKY; +#endif + if (!strncasecmp(name, "sky", 3)) + return CONTENTS_SKY; + +// ===================================================================================== +//Cpt_Andrew - Env_Sky Check +// ===================================================================================== +#ifdef HLCSG_TextureContents_FIX + if (!strncasecmp(name, "env_sky", 7)) +#else + if (!strncasecmp(name, "env_sky", 3)) +#endif + return CONTENTS_SKY; +// ===================================================================================== + + if (!strncasecmp(name + 1, "!lava", 5)) + return CONTENTS_LAVA; + + if (!strncasecmp(name + 1, "!slime", 6)) + return CONTENTS_SLIME; +#ifdef HLCSG_TextureContents_FIX + if (!strncasecmp(name, "!lava", 5)) + return CONTENTS_LAVA; + + if (!strncasecmp(name, "!slime", 6)) + return CONTENTS_SLIME; +#endif + + if (name[0] == '!') //optimized -- don't check for current unless it's liquid (KGP) + { + if (!strncasecmp(name, "!cur_90", 7)) + return CONTENTS_CURRENT_90; + if (!strncasecmp(name, "!cur_0", 6)) + return CONTENTS_CURRENT_0; + if (!strncasecmp(name, "!cur_270", 8)) + return CONTENTS_CURRENT_270; + if (!strncasecmp(name, "!cur_180", 8)) + return CONTENTS_CURRENT_180; + if (!strncasecmp(name, "!cur_up", 7)) + return CONTENTS_CURRENT_UP; + if (!strncasecmp(name, "!cur_dwn", 8)) + return CONTENTS_CURRENT_DOWN; + return CONTENTS_WATER; //default for liquids + } + + if (!strncasecmp(name, "origin", 6)) + return CONTENTS_ORIGIN; +#ifdef HLCSG_HLBSP_CUSTOMBOUNDINGBOX + if (!strncasecmp(name, "boundingbox", 11)) + return CONTENTS_BOUNDINGBOX; +#endif + +#ifndef HLCSG_CUSTOMHULL + if (!strncasecmp(name, "clip", 4)) + return CONTENTS_CLIP; +#endif + +#ifdef HLCSG_HLBSP_SOLIDHINT + if (!strncasecmp(name, "solidhint", 9)) + return CONTENTS_NULL; +#endif +#ifdef HLCSG_NOSPLITBYHINT + if (!strncasecmp(name, "splitface", 9)) + return CONTENTS_HINT; + if (!strncasecmp(name, "hint", 4)) + return CONTENTS_TOEMPTY; + if (!strncasecmp(name, "skip", 4)) + return CONTENTS_TOEMPTY; +#else + if (!strncasecmp(name, "hint", 4)) + return CONTENTS_HINT; + if (!strncasecmp(name, "skip", 4)) + return CONTENTS_HINT; +#endif + + if (!strncasecmp(name, "translucent", 11)) + return CONTENTS_TRANSLUCENT; + + if (name[0] == '@') + return CONTENTS_TRANSLUCENT; + +#ifdef ZHLT_NULLTEX // AJM: + if (!strncasecmp(name, "null", 4)) + return CONTENTS_NULL; +#ifdef HLCSG_PRECISIONCLIP // KGP + if(!strncasecmp(name,"bevel",5)) + return CONTENTS_NULL; +#endif //precisionclip +#endif //nulltex + + return CONTENTS_SOLID; +} + +// ===================================================================================== +// ContentsToString +// ===================================================================================== +const char* ContentsToString(const contents_t type) +{ + switch (type) + { + case CONTENTS_EMPTY: + return "EMPTY"; + case CONTENTS_SOLID: + return "SOLID"; + case CONTENTS_WATER: + return "WATER"; + case CONTENTS_SLIME: + return "SLIME"; + case CONTENTS_LAVA: + return "LAVA"; + case CONTENTS_SKY: + return "SKY"; + case CONTENTS_ORIGIN: + return "ORIGIN"; +#ifdef HLCSG_HLBSP_CUSTOMBOUNDINGBOX + case CONTENTS_BOUNDINGBOX: + return "BOUNDINGBOX"; +#endif +#ifndef HLCSG_CUSTOMHULL + case CONTENTS_CLIP: + return "CLIP"; +#endif + case CONTENTS_CURRENT_0: + return "CURRENT_0"; + case CONTENTS_CURRENT_90: + return "CURRENT_90"; + case CONTENTS_CURRENT_180: + return "CURRENT_180"; + case CONTENTS_CURRENT_270: + return "CURRENT_270"; + case CONTENTS_CURRENT_UP: + return "CURRENT_UP"; + case CONTENTS_CURRENT_DOWN: + return "CURRENT_DOWN"; + case CONTENTS_TRANSLUCENT: + return "TRANSLUCENT"; + case CONTENTS_HINT: + return "HINT"; + +#ifdef ZHLT_NULLTEX // AJM + case CONTENTS_NULL: + return "NULL"; +#endif + +#ifdef ZHLT_DETAIL // AJM + case CONTENTS_DETAIL: + return "DETAIL"; +#endif + +#ifdef HLCSG_EMPTYBRUSH + case CONTENTS_TOEMPTY: + return "EMPTY"; +#endif + + default: + return "UNKNOWN"; + } +} + +// ===================================================================================== +// CheckBrushContents +// Perfoms abitrary checking on brush surfaces and states to try and catch errors +// ===================================================================================== +contents_t CheckBrushContents(const brush_t* const b) +{ + contents_t best_contents; + contents_t contents; + side_t* s; + int i; +#ifdef HLCSG_CheckBrushContents_FIX + int best_i; +#endif +#ifdef HLCSG_CUSTOMCONTENT + bool assigned = false; +#endif + + s = &g_brushsides[b->firstside]; + + // cycle though the sides of the brush and attempt to get our best side contents for + // determining overall brush contents +#ifdef HLCSG_CheckBrushContents_FIX + if (b->numsides == 0) + { + Error ("Entity %i, Brush %i: Brush with no sides.\n", +#ifdef HLCSG_COUNT_NEW + b->originalentitynum, b->originalbrushnum +#else + b->entitynum, b->brushnum +#endif + ); + } + best_i = 0; +#endif + best_contents = TextureContents(s->td.name); +#ifdef HLCSG_CUSTOMCONTENT + // Difference between SKIP, ContentEmpty: + // SKIP doesn't split space in bsp process, ContentEmpty splits space normally. + if (!(strncasecmp (s->td.name, "content", 7) && strncasecmp (s->td.name, "skip", 4))) + assigned = true; +#endif + s++; + for (i = 1; i < b->numsides; i++, s++) + { + contents_t contents_consider = TextureContents(s->td.name); +#ifdef HLCSG_CUSTOMCONTENT + if (assigned) + continue; + if (!(strncasecmp (s->td.name, "content", 7) && strncasecmp (s->td.name, "skip", 4))) + { + best_i = i; + best_contents = contents_consider; + assigned = true; + } +#endif + if (contents_consider > best_contents) + { +#ifdef HLCSG_CheckBrushContents_FIX + best_i = i; +#endif + // if our current surface contents is better (larger) than our best, make it our best. + best_contents = contents_consider; + } + } + contents = best_contents; + + // attempt to pick up on mixed_face_contents errors + s = &g_brushsides[b->firstside]; +#ifdef HLCSG_CheckBrushContents_FIX + for (i = 0; i < b->numsides; i++, s++) +#else + s++; + for (i = 1; i < b->numsides; i++, s++) +#endif + { + contents_t contents2 = TextureContents(s->td.name); +#ifdef HLCSG_CUSTOMCONTENT + if (assigned + && strncasecmp (s->td.name, "content", 7) + && strncasecmp (s->td.name, "skip", 4) + && contents2 != CONTENTS_ORIGIN + && contents2 != CONTENTS_HINT +#ifdef HLCSG_HLBSP_CUSTOMBOUNDINGBOX + && contents2 != CONTENTS_BOUNDINGBOX +#endif + ) + { + continue; // overwrite content for this texture + } +#endif + + // AJM: sky and null types are not to cause mixed face contents + if (contents2 == CONTENTS_SKY) + continue; + +#ifdef ZHLT_NULLTEX + if (contents2 == CONTENTS_NULL) + continue; +#endif + + if (contents2 != best_contents) + { + Fatal(assume_MIXED_FACE_CONTENTS, "Entity %i, Brush %i: mixed face contents\n Texture %s and %s", +#ifdef HLCSG_COUNT_NEW + b->originalentitynum, b->originalbrushnum, +#else + b->entitynum, b->brushnum, +#endif +#ifdef HLCSG_CheckBrushContents_FIX + g_brushsides[b->firstside + best_i].td.name, +#else + g_brushsides[b->firstside].td.name, +#endif + s->td.name); + } + } +#ifdef HLCSG_HLBSP_CONTENTSNULL_FIX + if (contents == CONTENTS_NULL) + contents = CONTENTS_SOLID; +#endif + + // check to make sure we dont have an origin brush as part of worldspawn + if ((b->entitynum == 0) || (strcmp("func_group", ValueForKey(&g_entities[b->entitynum], "classname"))==0)) + { + if (contents == CONTENTS_ORIGIN +#ifdef HLCSG_FUNCGROUP_FIX + && b->entitynum == 0 +#endif +#ifdef HLCSG_HLBSP_CUSTOMBOUNDINGBOX + || contents == CONTENTS_BOUNDINGBOX +#endif + ) + { + Fatal(assume_BRUSH_NOT_ALLOWED_IN_WORLD, "Entity %i, Brush %i: %s brushes not allowed in world\n(did you forget to tie this origin brush to a rotating entity?)", +#ifdef HLCSG_COUNT_NEW + b->originalentitynum, b->originalbrushnum, +#else + b->entitynum, b->brushnum, +#endif + ContentsToString(contents)); + } + } + else + { + // otherwise its not worldspawn, therefore its an entity. check to make sure this brush is allowed + // to be an entity. + switch (contents) + { + case CONTENTS_SOLID: + case CONTENTS_WATER: + case CONTENTS_SLIME: + case CONTENTS_LAVA: + case CONTENTS_ORIGIN: +#ifdef HLCSG_HLBSP_CUSTOMBOUNDINGBOX + case CONTENTS_BOUNDINGBOX: +#endif +#ifndef HLCSG_CUSTOMHULL + case CONTENTS_CLIP: +#endif +#ifdef HLCSG_ALLOWHINTINENTITY + case CONTENTS_HINT: +#endif +#ifdef HLCSG_EMPTYBRUSH + case CONTENTS_TOEMPTY: +#endif +#ifdef ZHLT_NULLTEX // AJM +#ifndef HLCSG_HLBSP_CONTENTSNULL_FIX + case CONTENTS_NULL: +#endif + break; +#endif + default: + Fatal(assume_BRUSH_NOT_ALLOWED_IN_ENTITY, "Entity %i, Brush %i: %s brushes not allowed in entity", +#ifdef HLCSG_COUNT_NEW + b->originalentitynum, b->originalbrushnum, +#else + b->entitynum, b->brushnum, +#endif + ContentsToString(contents)); + break; + } + } + + return contents; +} + + +// ===================================================================================== +// CreateBrush +// makes a brush! +// ===================================================================================== +#ifdef HLCSG_CUSTOMHULL +void CreateBrush(const int brushnum) //--vluzacn +{ + brush_t* b; + int contents; + int h; + + b = &g_mapbrushes[brushnum]; + + contents = b->contents; + + if (contents == CONTENTS_ORIGIN) + return; +#ifdef HLCSG_HLBSP_CUSTOMBOUNDINGBOX + if (contents == CONTENTS_BOUNDINGBOX) + return; +#endif + + // HULL 0 + MakeBrushPlanes(b); + MakeHullFaces(b, &b->hulls[0]); + + if (contents == CONTENTS_HINT) + return; +#ifdef HLCSG_EMPTYBRUSH + if (contents == CONTENTS_TOEMPTY) + return; +#endif + + if (g_noclip) + { + if (b->cliphull) + { + b->hulls[0].faces = NULL; + } + return; + } + + if (b->cliphull) + { + for (h = 1; h < NUM_HULLS; h++) + { + if (b->cliphull & (1 << h)) + { + ExpandBrush(b, h); + MakeHullFaces(b, &b->hulls[h]); + } + } + b->contents = CONTENTS_SOLID; + b->hulls[0].faces = NULL; + } + else + { + if (b->noclip) + return; + for (h = 1; h < NUM_HULLS; h++) + { + ExpandBrush(b, h); + MakeHullFaces(b, &b->hulls[h]); + } + } +} +#else /*HLCSG_CUSTOMHULL*/ +void CreateBrush(const int brushnum) +{ + brush_t* b; + int contents; + int h; + + b = &g_mapbrushes[brushnum]; + + contents = b->contents; + + if (contents == CONTENTS_ORIGIN) + return; +#ifdef HLCSG_HLBSP_CUSTOMBOUNDINGBOX + if (contents == CONTENTS_BOUNDINGBOX) + return; +#endif + + // HULL 0 + MakeBrushPlanes(b); + MakeHullFaces(b, &b->hulls[0]); + + // these brush types do not need to be represented in the clipping hull + switch (contents) + { + case CONTENTS_LAVA: + case CONTENTS_SLIME: + case CONTENTS_WATER: + case CONTENTS_TRANSLUCENT: + case CONTENTS_HINT: + return; + } +#ifdef HLCSG_EMPTYBRUSH + if (contents == CONTENTS_TOEMPTY) + return; +#endif + +#ifdef HLCSG_CLIPECONOMY // AJM + if (b->noclip) + return; +#endif + + // HULLS 1-3 + if (!g_noclip) + { + for (h = 1; h < NUM_HULLS; h++) + { + ExpandBrush(b, h); + MakeHullFaces(b, &b->hulls[h]); + } + } + + // clip brushes don't stay in the drawing hull + if (contents == CONTENTS_CLIP) + { + b->hulls[0].faces = NULL; + b->contents = CONTENTS_SOLID; + } +} +#endif /*HLCSG_CUSTOMHULL*/ +#ifdef HLCSG_HULLBRUSH +hullbrush_t *CreateHullBrush (const brush_t *b) +{ + const int MAXSIZE = 256; + + hullbrush_t *hb; + int numplanes; + plane_t planes[MAXSIZE]; + Winding *w[MAXSIZE]; + int numedges; + hullbrushedge_t edges[MAXSIZE]; + int numvertexes; + hullbrushvertex_t vertexes[MAXSIZE]; + int i; + int j; + int k; + int e; + int e2; + vec3_t origin; + bool failed = false; + + // planes + + numplanes = 0; + GetVectorForKey (&g_entities[b->entitynum], "origin", origin); + + for (i = 0; i < b->numsides; i++) + { + side_t *s; + vec3_t p[3]; + vec3_t v1; + vec3_t v2; + vec3_t normal; + planetypes axial; + + s = &g_brushsides[b->firstside + i]; + for (j = 0; j < 3; j++) + { + VectorSubtract (s->planepts[j], origin, p[j]); + for (k = 0; k < 3; k++) + { + if (fabs (p[j][k] - floor (p[j][k] + 0.5)) <= ON_EPSILON && p[j][k] != floor (p[j][k] + 0.5)) + { + Warning ("Entity %i, Brush %i: vertex (%4.8f %4.8f %4.8f) of an info_hullshape entity is slightly off-grid.", +#ifdef HLCSG_COUNT_NEW + b->originalentitynum, b->originalbrushnum, +#else + b->entitynum, b->brushnum, +#endif + p[j][0], p[j][1], p[j][2]); + } + } + } + VectorSubtract (p[0], p[1], v1); + VectorSubtract (p[2], p[1], v2); + CrossProduct (v1, v2, normal); + if (!VectorNormalize (normal)) + { + failed = true; + continue; + } + for (k = 0; k < 3; k++) + { + if (fabs (normal[k]) < NORMAL_EPSILON) + { + normal[k] = 0.0; + VectorNormalize (normal); + } + } + axial = PlaneTypeForNormal (normal); + if (axial <= last_axial) + { + int sign = normal[axial] > 0? 1: -1; + VectorClear (normal); + normal[axial] = sign; + } + + if (numplanes >= MAXSIZE) + { + failed = true; + continue; + } + VectorCopy (normal, planes[numplanes].normal); + planes[numplanes].dist = DotProduct (p[1], normal); + numplanes++; + } + + // windings + + for (i = 0; i < numplanes; i++) + { + w[i] = new Winding (planes[i].normal, planes[i].dist); + for (j = 0; j < numplanes; j++) + { + if (j == i) + { + continue; + } + vec3_t normal; + vec_t dist; + VectorSubtract (vec3_origin, planes[j].normal, normal); + dist = -planes[j].dist; + if (!w[i]->Chop (normal, dist)) + { + failed = true; + break; + } + } + } + + // edges + numedges = 0; + for (i = 0; i < numplanes; i++) + { + for (e = 0; e < w[i]->m_NumPoints; e++) + { + hullbrushedge_t *edge; + int found; + if (numedges >= MAXSIZE) + { + failed = true; + continue; + } + edge = &edges[numedges]; + VectorCopy (w[i]->m_Points[(e + 1) % w[i]->m_NumPoints], edge->vertexes[0]); + VectorCopy (w[i]->m_Points[e], edge->vertexes[1]); + VectorCopy (edge->vertexes[0], edge->point); + VectorSubtract (edge->vertexes[1], edge->vertexes[0], edge->delta); + if (VectorLength (edge->delta) < 1 - ON_EPSILON) + { + failed = true; + continue; + } + VectorCopy (planes[i].normal, edge->normals[0]); + found = 0; + for (k = 0; k < numplanes; k++) + { + for (e2 = 0; e2 < w[k]->m_NumPoints; e2++) + { + if (VectorCompare (w[k]->m_Points[(e2 + 1) % w[k]->m_NumPoints], edge->vertexes[1]) && + VectorCompare (w[k]->m_Points[e2], edge->vertexes[0])) + { + found++; + VectorCopy (planes[k].normal, edge->normals[1]); + j = k; + } + } + } + if (found != 1) + { + failed = true; + continue; + } + if (fabs (DotProduct (edge->vertexes[0], edge->normals[0]) - planes[i].dist) > NORMAL_EPSILON + || fabs (DotProduct (edge->vertexes[1], edge->normals[0]) - planes[i].dist) > NORMAL_EPSILON + || fabs (DotProduct (edge->vertexes[0], edge->normals[1]) - planes[j].dist) > NORMAL_EPSILON + || fabs (DotProduct (edge->vertexes[1], edge->normals[1]) - planes[j].dist) > NORMAL_EPSILON) + { + failed = true; + continue; + } + if (j > i) + { + numedges++; + } + } + } + + // vertexes + numvertexes = 0; + for (i = 0; i < numplanes; i++) + { + for (e = 0; e < w[i]->m_NumPoints; e++) + { + vec3_t v; + VectorCopy (w[i]->m_Points[e], v); + for (j = 0; j < numvertexes; j++) + { + if (VectorCompare (vertexes[j].point, v)) + { + break; + } + } + if (j < numvertexes) + { + continue; + } + if (numvertexes > MAXSIZE) + { + failed = true; + continue; + } + + VectorCopy (v, vertexes[numvertexes].point); + numvertexes++; + + for (k = 0; k < numplanes; k++) + { + if (fabs (DotProduct (v, planes[k].normal) - planes[k].dist) < ON_EPSILON) + { + if (fabs (DotProduct (v, planes[k].normal) - planes[k].dist) > NORMAL_EPSILON) + { + failed = true; + } + } + } + } + } + + // copy to hull brush + + if (!failed) + { + hb = (hullbrush_t *)malloc (sizeof (hullbrush_t)); + hlassume (hb != NULL, assume_NoMemory); + + hb->numfaces = numplanes; + hb->faces = (hullbrushface_t *)malloc (hb->numfaces * sizeof (hullbrushface_t)); + hlassume (hb->faces != NULL, assume_NoMemory); + for (i = 0; i < numplanes; i++) + { + hullbrushface_t *f = &hb->faces[i]; + VectorCopy (planes[i].normal, f->normal); + VectorCopy (w[i]->m_Points[0], f->point); + f->numvertexes = w[i]->m_NumPoints; + f->vertexes = (vec3_t *)malloc (f->numvertexes * sizeof (vec3_t)); + hlassume (f->vertexes != NULL, assume_NoMemory); + for (k = 0; k < w[i]->m_NumPoints; k++) + { + VectorCopy (w[i]->m_Points[k], f->vertexes[k]); + } + } + + hb->numedges = numedges; + hb->edges = (hullbrushedge_t *)malloc (hb->numedges * sizeof (hullbrushedge_t)); + hlassume (hb->edges != NULL, assume_NoMemory); + memcpy (hb->edges, edges, hb->numedges * sizeof (hullbrushedge_t)); + + hb->numvertexes = numvertexes; + hb->vertexes = (hullbrushvertex_t *)malloc (hb->numvertexes * sizeof (hullbrushvertex_t)); + hlassume (hb->vertexes != NULL, assume_NoMemory); + memcpy (hb->vertexes, vertexes, hb->numvertexes * sizeof (hullbrushvertex_t)); + + Developer (DEVELOPER_LEVEL_MESSAGE, "info_hullshape @ (%.0f,%.0f,%.0f): %d faces, %d edges, %d vertexes.\n", origin[0], origin[1], origin[2], hb->numfaces, hb->numedges, hb->numvertexes); + } + else + { + hb = NULL; + Error ("Entity %i, Brush %i: invalid brush. This brush cannot be used for info_hullshape.", +#ifdef HLCSG_COUNT_NEW + b->originalentitynum, b->originalbrushnum +#else + b->entitynum, b->brushnum +#endif + ); + } + + for (i = 0; i < numplanes; i++) + { + delete w[i]; + } + + return hb; +} + +hullbrush_t *CopyHullBrush (const hullbrush_t *hb) +{ + hullbrush_t *hb2; + hb2 = (hullbrush_t *)malloc (sizeof (hullbrush_t)); + hlassume (hb2 != NULL, assume_NoMemory); + memcpy (hb2, hb, sizeof (hullbrush_t)); + hb2->faces = (hullbrushface_t *)malloc (hb->numfaces * sizeof (hullbrushface_t)); + hlassume (hb2->faces != NULL, assume_NoMemory); + memcpy (hb2->faces, hb->faces, hb->numfaces * sizeof (hullbrushface_t)); + hb2->edges = (hullbrushedge_t *)malloc (hb->numedges * sizeof (hullbrushedge_t)); + hlassume (hb2->edges != NULL, assume_NoMemory); + memcpy (hb2->edges, hb->edges, hb->numedges * sizeof (hullbrushedge_t)); + hb2->vertexes = (hullbrushvertex_t *)malloc (hb->numvertexes * sizeof (hullbrushvertex_t)); + hlassume (hb2->vertexes != NULL, assume_NoMemory); + memcpy (hb2->vertexes, hb->vertexes, hb->numvertexes * sizeof (hullbrushvertex_t)); + for (int i = 0; i < hb->numfaces; i++) + { + hullbrushface_t *f2 = &hb2->faces[i]; + const hullbrushface_t *f = &hb->faces[i]; + f2->vertexes = (vec3_t *)malloc (f->numvertexes * sizeof (vec3_t)); + hlassume (f2->vertexes != NULL, assume_NoMemory); + memcpy (f2->vertexes, f->vertexes, f->numvertexes * sizeof (vec3_t)); + } + return hb2; +} + +void DeleteHullBrush (hullbrush_t *hb) +{ + for (hullbrushface_t *hbf = hb->faces; hbf < hb->faces + hb->numfaces; hbf++) + { + if (hbf->vertexes) + { + free (hbf->vertexes); + } + } + free (hb->faces); + free (hb->edges); + free (hb->vertexes); + free (hb); +} + +void InitDefaultHulls () +{ + for (int h = 0; h < NUM_HULLS; h++) + { + hullshape_t *hs = &g_defaulthulls[h]; + hs->id = _strdup (""); + hs->disabled = true; + hs->numbrushes = 0; + hs->brushes = (hullbrush_t **)malloc (0 * sizeof (hullbrush_t *)); + hlassume (hs->brushes != NULL, assume_NoMemory); + } +} + +void CreateHullShape (int entitynum, bool disabled, const char *id, int defaulthulls) +{ + entity_t *entity; + hullshape_t *hs; + + entity = &g_entities[entitynum]; + if (!*ValueForKey (entity, "origin")) + { + Warning ("info_hullshape with no ORIGIN brush."); + } + if (g_numhullshapes >= MAX_HULLSHAPES) + { + Error ("Too many info_hullshape entities. Can not exceed %d.", MAX_HULLSHAPES); + } + hs = &g_hullshapes[g_numhullshapes]; + g_numhullshapes++; + + hs->id = _strdup (id); + hs->disabled = disabled; + hs->numbrushes = 0; + hs->brushes = (hullbrush_t **)malloc (entity->numbrushes * sizeof (hullbrush_t *)); + for (int i = 0; i < entity->numbrushes; i++) + { + brush_t *b = &g_mapbrushes[entity->firstbrush + i]; + if (b->contents == CONTENTS_ORIGIN) + { + continue; + } + hs->brushes[hs->numbrushes] = CreateHullBrush (b); + hs->numbrushes++; + } + if (hs->numbrushes >= 2) + { + brush_t *b = &g_mapbrushes[entity->firstbrush]; + Error ("Entity %i, Brush %i: Too many brushes in info_hullshape.", +#ifdef HLCSG_COUNT_NEW + b->originalentitynum, b->originalbrushnum +#else + b->entitynum, b->brushnum +#endif + ); + } + + for (int h = 0; h < NUM_HULLS; h++) + { + if (defaulthulls & (1 << h)) + { + hullshape_t *target = &g_defaulthulls[h]; + for (int i = 0; i < target->numbrushes; i++) + { + DeleteHullBrush (target->brushes[i]); + } + free (target->brushes); + free (target->id); + target->id = _strdup (hs->id); + target->disabled = hs->disabled; + target->numbrushes = hs->numbrushes; + target->brushes = (hullbrush_t **)malloc (hs->numbrushes * sizeof (hullbrush_t *)); + hlassume (target->brushes != NULL, assume_NoMemory); + for (int i = 0; i < hs->numbrushes; i++) + { + target->brushes[i] = CopyHullBrush (hs->brushes[i]); + } + } + } +} +#endif diff --git a/src/zhlt-vluzacn/hlcsg/brushunion.cpp b/src/zhlt-vluzacn/hlcsg/brushunion.cpp new file mode 100644 index 0000000..1afaa86 --- /dev/null +++ b/src/zhlt-vluzacn/hlcsg/brushunion.cpp @@ -0,0 +1,371 @@ +#include "csg.h" + +vec_t g_BrushUnionThreshold = DEFAULT_BRUSH_UNION_THRESHOLD; + +static Winding* NewWindingFromPlane(const brushhull_t* const hull, const int planenum) +{ + Winding* winding; + Winding* front; + Winding* back; + bface_t* face; + plane_t* plane; + + plane = &g_mapplanes[planenum]; + winding = new Winding(plane->normal, plane->dist); + + for (face = hull->faces; face; face = face->next) + { + plane = &g_mapplanes[face->planenum]; + winding->Clip(plane->normal, plane->dist, &front, &back); + delete winding; + + if (front) + { + delete front; + } + if (back) + { + winding = back; + } + else + { + Developer(DEVELOPER_LEVEL_ERROR, "NewFaceFromPlane returning NULL"); + return NULL; + } + } + + return winding; +} + +static void AddFaceToList(bface_t** head, bface_t* newface) +{ + hlassert(newface); + hlassert(newface->w); + if (!*head) + { + *head = newface; + return; + } + else + { + bface_t* node = *head; + + while (node->next) + { + node = node->next; + } + node->next = newface; + newface->next = NULL; + } +} + +static int NumberOfHullFaces(const brushhull_t* const hull) +{ + int x; + bface_t* face; + + if (!hull->faces) + { + return 0; + } + + for (x = 0, face = hull->faces; face; face = face->next, x++) + { // counter + } + + return x; +} + +// Returns false if union of brushes is obviously zero +static void AddPlaneToUnion(brushhull_t* hull, const int planenum) +{ + bool need_new_face = false; + + bface_t* new_face_list; + + bface_t* face; + bface_t* next; + + plane_t* split; + Winding* front; + Winding* back; + + new_face_list = NULL; + + next = NULL; + + hlassert(hull); + + if (!hull->faces) + { + return; + } + hlassert(hull->faces->w); + + for (face = hull->faces; face; face = next) + { + hlassert(face->w); + next = face->next; + + // Duplicate plane, ignore + if (face->planenum == planenum) + { + AddFaceToList(&new_face_list, CopyFace(face)); + continue; + } + + split = &g_mapplanes[planenum]; + face->w->Clip(split->normal, split->dist, &front, &back); + + if (front) + { + delete front; + need_new_face = true; + + if (back) + { // Intersected the face + delete face->w; + face->w = back; + AddFaceToList(&new_face_list, CopyFace(face)); + } + } + else + { + // Completely missed it, back is identical to face->w so it is destroyed + if (back) + { + delete back; + AddFaceToList(&new_face_list, CopyFace(face)); + } + } + hlassert(face->w); + } + + FreeFaceList(hull->faces); + hull->faces = new_face_list; + + if (need_new_face && (NumberOfHullFaces(hull) > 2)) + { + Winding* new_winding = NewWindingFromPlane(hull, planenum); + + if (new_winding) + { + bface_t* new_face = (bface_t*)Alloc(sizeof(bface_t)); + + new_face->planenum = planenum; + new_face->w = new_winding; + + new_face->next = hull->faces; + hull->faces = new_face; + } + } +} + +static vec_t CalculateSolidVolume(const brushhull_t* const hull) +{ + // calculate polyhedron origin + // subdivide face winding into triangles + + // for each face + // calculate volume of triangle of face to origin + // add subidivided volume chunk to total + + int x = 0; + vec_t volume = 0.0; + vec_t inverse; + vec3_t midpoint = { 0.0, 0.0, 0.0 }; + + bface_t* face; + + for (face = hull->faces; face; face = face->next, x++) + { + vec3_t facemid; + + face->w->getCenter(facemid); + VectorAdd(midpoint, facemid, midpoint); + Developer(DEVELOPER_LEVEL_MESSAGE, "Midpoint for face %d is %f %f %f\n", x, facemid[0], facemid[1], facemid[2]); + } + + inverse = 1.0 / x; + + VectorScale(midpoint, inverse, midpoint); + + Developer(DEVELOPER_LEVEL_MESSAGE, "Midpoint for hull is %f %f %f\n", midpoint[0], midpoint[1], midpoint[2]); + + for (face = hull->faces; face; face = face->next, x++) + { + plane_t* plane = &g_mapplanes[face->planenum]; + vec_t area = face->w->getArea(); + vec_t dist = DotProduct(plane->normal, midpoint); + + dist -= plane->dist; + dist = fabs(dist); + + volume += area * dist / 3.0; + } + + Developer(DEVELOPER_LEVEL_MESSAGE, "Volume for brush is %f\n", volume); + + return volume; +} + +static void DumpHullWindings(const brushhull_t* const hull) +{ + int x = 0; + bface_t* face; + + for (face = hull->faces; face; face = face->next) + { + Developer(DEVELOPER_LEVEL_MEGASPAM, "Winding %d\n", x++); + face->w->Print(); + Developer(DEVELOPER_LEVEL_MEGASPAM, "\n"); + } +} + +static bool isInvalidHull(const brushhull_t* const hull) +{ + int x = 0; + bface_t* face; + + vec3_t mins = { 99999.0, 99999.0, 99999.0 }; + vec3_t maxs = { -99999.0, -99999.0, -99999.0 }; + + for (face = hull->faces; face; face = face->next) + { + unsigned int y; + Winding* winding = face->w; + + for (y = 0; y < winding->m_NumPoints; y++) + { + VectorCompareMinimum(mins, winding->m_Points[y], mins); + VectorCompareMaximum(maxs, winding->m_Points[y], maxs); + } + } + + for (x = 0; x < 3; x++) + { + if ((mins[x] < (-BOGUS_RANGE / 2)) || (maxs[x] > (BOGUS_RANGE / 2))) + { + return true; + } + } + return false; +} + +void CalculateBrushUnions(const int brushnum) +{ + int bn, hull; + brush_t* b1; + brush_t* b2; + brushhull_t* bh1; + brushhull_t* bh2; + entity_t* e; + + b1 = &g_mapbrushes[brushnum]; + e = &g_entities[b1->entitynum]; + + for (hull = 0; hull < 1 /* NUM_HULLS */ ; hull++) + { + bh1 = &b1->hulls[hull]; + if (!bh1->faces) // Skip it if it is not in this hull + { + continue; + } + + for (bn = brushnum + 1; bn < e->numbrushes; bn++) + { // Only compare if b2 > b1, tests are communitive + b2 = &g_mapbrushes[e->firstbrush + bn]; + bh2 = &b2->hulls[hull]; + + if (!bh2->faces) // Skip it if it is not in this hull + { + continue; + } + if (b1->contents != b2->contents) + { + continue; // different contents, ignore + } + + Developer(DEVELOPER_LEVEL_SPAM, "Processing hull %d brush %d and brush %d\n", hull, brushnum, bn); + + { + brushhull_t union_hull; + bface_t* face; + + union_hull.bounds = bh1->bounds; + + union_hull.faces = CopyFaceList(bh1->faces); + + for (face = bh2->faces; face; face = face->next) + { + AddPlaneToUnion(&union_hull, face->planenum); + } + + // union was clipped away (no intersection) + if (!union_hull.faces) + { + continue; + } + + if (g_developer >= DEVELOPER_LEVEL_MESSAGE) + { + Log("\nUnion windings\n"); + DumpHullWindings(&union_hull); + + Log("\nBrush %d windings\n", brushnum); + DumpHullWindings(bh1); + + Log("\nBrush %d windings\n", bn); + DumpHullWindings(bh2); + } + + + { + vec_t volume_brush_1; + vec_t volume_brush_2; + vec_t volume_brush_union; + vec_t volume_ratio_1; + vec_t volume_ratio_2; + + if (isInvalidHull(&union_hull)) + { + FreeFaceList(union_hull.faces); + continue; + } + + volume_brush_union = CalculateSolidVolume(&union_hull); + volume_brush_1 = CalculateSolidVolume(bh1); + volume_brush_2 = CalculateSolidVolume(bh2); + + volume_ratio_1 = volume_brush_union / volume_brush_1; + volume_ratio_2 = volume_brush_union / volume_brush_2; + + if ((volume_ratio_1 > g_BrushUnionThreshold) || (g_developer >= DEVELOPER_LEVEL_MESSAGE)) + { + volume_ratio_1 *= 100.0; + Warning("Entity %d : Brush %d intersects with brush %d by %2.3f percent", +#ifdef HLCSG_COUNT_NEW + b1->originalentitynum, b1->originalbrushnum, b2->originalbrushnum, +#else + b1->entitynum, brushnum, bn, +#endif + volume_ratio_1); + } + if ((volume_ratio_2 > g_BrushUnionThreshold) || (g_developer >= DEVELOPER_LEVEL_MESSAGE)) + { + volume_ratio_2 *= 100.0; + Warning("Entity %d : Brush %d intersects with brush %d by %2.3f percent", +#ifdef HLCSG_COUNT_NEW + b1->originalentitynum, b2->originalbrushnum, b1->originalbrushnum, +#else + b1->entitynum, bn, brushnum, +#endif + volume_ratio_2); + } + } + + FreeFaceList(union_hull.faces); + } + } + } +} diff --git a/src/zhlt-vluzacn/hlcsg/csg.h b/src/zhlt-vluzacn/hlcsg/csg.h new file mode 100644 index 0000000..88eacd3 --- /dev/null +++ b/src/zhlt-vluzacn/hlcsg/csg.h @@ -0,0 +1,479 @@ +#ifndef HLCSG_H__ +#define HLCSG_H__ + +#if _MSC_VER >= 1000 +#pragma once +#endif + +#pragma warning(disable: 4786) // identifier was truncated to '255' characters in the browser information +#include +#include +#include + +#include "cmdlib.h" +#include "messages.h" +#include "win32fix.h" +#include "log.h" +#include "hlassert.h" +#include "mathlib.h" +#include "scriplib.h" +#include "winding.h" +#include "threads.h" +#include "bspfile.h" +#include "blockmem.h" +#include "filelib.h" +#include "boundingbox.h" +// AJM: added in +#include "wadpath.h" +#ifdef ZHLT_PARAMFILE +#include "cmdlinecfg.h" +#endif + +#ifndef DOUBLEVEC_T +#error you must add -dDOUBLEVEC_T to the project! +#endif + +#define DEFAULT_BRUSH_UNION_THRESHOLD 0.0f +#ifdef ZHLT_WINDING_RemoveColinearPoints_VL +#define DEFAULT_TINY_THRESHOLD 0.0 +#else +#define DEFAULT_TINY_THRESHOLD 0.5 +#endif +#define DEFAULT_NOCLIP false +#define DEFAULT_ONLYENTS false +#define DEFAULT_WADTEXTURES true +#define DEFAULT_SKYCLIP true +#define DEFAULT_CHART false +#define DEFAULT_INFO true + +#ifdef HLCSG_PRECISIONCLIP // KGP +#ifdef HLCSG_CLIPTYPEPRECISE_EPSILON_FIX +#define FLOOR_Z 0.7 // Quake default +#else +#define FLOOR_Z 0.5 +#endif +#define DEFAULT_CLIPTYPE clip_simple //clip_legacy //--vluzacn +#endif + +#ifdef ZHLT_NULLTEX // AJM +#define DEFAULT_NULLTEX true +#endif + +#ifdef HLCSG_CLIPECONOMY // AJM +#ifdef HLCSG_CUSTOMHULL // default clip economy off +#define DEFAULT_CLIPNAZI false +#else +#define DEFAULT_CLIPNAZI true +#endif +#endif + +#ifdef HLCSG_AUTOWAD // AJM +#define DEFAULT_WADAUTODETECT false +#endif + +#ifdef ZHLT_DETAIL // AJM +#define DEFAULT_DETAIL true +#endif + +#ifdef ZHLT_PROGRESSFILE // AJM +#define DEFAULT_PROGRESSFILE NULL // progress file is only used if g_progressfile is non-null +#endif +#ifdef HLCSG_SCALESIZE +#define DEFAULT_SCALESIZE -1.0 //dont scale +#endif +#ifdef HLCSG_KEEPLOG +#define DEFAULT_RESETLOG true +#endif +#ifdef HLCSG_OPTIMIZELIGHTENTITY +#define DEFAULT_NOLIGHTOPT false +#endif +#ifdef HLCSG_GAMETEXTMESSAGE_UTF8 +#define DEFAULT_NOUTF8 false +#endif +#ifdef HLCSG_NULLIFYAAATRIGGER +#define DEFAULT_NULLIFYTRIGGER true +#endif + +// AJM: added in +#define UNLESS(a) if (!(a)) + +#ifdef SYSTEM_WIN32 +#define DEFAULT_ESTIMATE false +#endif + +#ifdef SYSTEM_POSIX +#define DEFAULT_ESTIMATE true +#endif + +#ifdef ZHLT_LARGERANGE +#define BOGUS_RANGE 65534 +#else +#define BOGUS_RANGE 8192 +#endif + +#ifdef HLCSG_HULLBRUSH +#define MAX_HULLSHAPES 128 // arbitrary + +#endif +typedef struct +{ + vec3_t normal; + vec3_t origin; + vec_t dist; + planetypes type; +} plane_t; + + + +typedef struct +{ + vec3_t UAxis; + vec3_t VAxis; + vec_t shift[2]; + vec_t rotate; + vec_t scale[2]; +} valve_vects; + +typedef struct +{ + float vects[2][4]; +} quark_vects; + +typedef union +{ + valve_vects valve; + quark_vects quark; +} +vects_union; + +extern int g_nMapFileVersion; // map file version * 100 (ie 201), zero for pre-Worldcraft 2.0.1 maps + +typedef struct +{ + char txcommand; + vects_union vects; + char name[32]; +} brush_texture_t; + +typedef struct side_s +{ + brush_texture_t td; +#ifdef HLCSG_CUSTOMHULL + bool bevel; +#endif +#ifdef ZHLT_HIDDENSOUNDTEXTURE + bool shouldhide; +#endif + vec_t planepts[3][3]; +} side_t; + +typedef struct bface_s +{ + struct bface_s* next; + int planenum; + plane_t* plane; + Winding* w; + int texinfo; + bool used; // just for face counting + int contents; + int backcontents; +#ifdef HLCSG_CUSTOMHULL + bool bevel; //used for ExpandBrush +#endif + BoundingBox bounds; +} bface_t; + +// NUM_HULLS should be no larger than MAX_MAP_HULLS +#define NUM_HULLS 4 + +typedef struct +{ + BoundingBox bounds; + bface_t* faces; +} brushhull_t; + +typedef struct brush_s +{ +#ifdef HLCSG_COUNT_NEW + int originalentitynum; + int originalbrushnum; +#endif + int entitynum; + int brushnum; + + int firstside; + int numsides; + +#ifdef HLCSG_CLIPECONOMY // AJM + unsigned int noclip; // !!!FIXME: this should be a flag bitfield so we can use it for other stuff (ie. is this a detail brush...) +#endif +#ifdef HLCSG_CUSTOMHULL + unsigned int cliphull; + bool bevel; +#endif +#ifdef ZHLT_DETAILBRUSH + int detaillevel; + int chopdown; // allow this brush to chop brushes of lower detail level + int chopup; // allow this brush to be chopped by brushes of higher detail level +#ifdef ZHLT_CLIPNODEDETAILLEVEL + int clipnodedetaillevel; +#endif +#ifdef HLCSG_COPLANARPRIORITY + int coplanarpriority; +#endif +#endif +#ifdef HLCSG_HULLBRUSH + char * hullshapes[NUM_HULLS]; // might be NULL +#endif + + int contents; + brushhull_t hulls[NUM_HULLS]; +} brush_t; + +#ifdef HLCSG_HULLBRUSH +typedef struct +{ + vec3_t normal; + vec3_t point; + + int numvertexes; + vec3_t *vertexes; +} hullbrushface_t; + +typedef struct +{ + vec3_t normals[2]; + vec3_t point; + + vec3_t vertexes[2]; + vec3_t delta; // delta has the same direction as CrossProduct(normals[0],normals[1]) +} hullbrushedge_t; + +typedef struct +{ + vec3_t point; +} hullbrushvertex_t; + +typedef struct +{ + int numfaces; + hullbrushface_t *faces; + int numedges; + hullbrushedge_t *edges; + int numvertexes; + hullbrushvertex_t *vertexes; +} hullbrush_t; + +typedef struct +{ + char *id; + bool disabled; + int numbrushes; // must be 0 or 1 + hullbrush_t **brushes; +} hullshape_t; + +#endif +#ifdef HLCSG_GAMETEXTMESSAGE_UTF8 +extern char * ANSItoUTF8 (const char *); +#endif + +//============================================================================= +// map.c + +extern int g_nummapbrushes; +extern brush_t g_mapbrushes[MAX_MAP_BRUSHES]; + +#define MAX_MAP_SIDES (MAX_MAP_BRUSHES*6) + +extern int g_numbrushsides; +extern side_t g_brushsides[MAX_MAP_SIDES]; + +#ifdef HLCSG_HULLBRUSH +extern hullshape_t g_defaulthulls[NUM_HULLS]; +extern int g_numhullshapes; +extern hullshape_t g_hullshapes[MAX_HULLSHAPES]; + +#endif +extern void TextureAxisFromPlane(const plane_t* const pln, vec3_t xv, vec3_t yv); +extern void LoadMapFile(const char* const filename); + +//============================================================================= +// textures.c + +typedef std::deque< std::string >::iterator WadInclude_i; +extern std::deque< std::string > g_WadInclude; // List of substrings to wadinclude + +extern void WriteMiptex(); +extern int TexinfoForBrushTexture(const plane_t* const plane, brush_texture_t* bt, const vec3_t origin +#ifdef ZHLT_HIDDENSOUNDTEXTURE + , bool shouldhide +#endif + ); +#ifdef HLCSG_HLBSP_VOIDTEXINFO +extern const char *GetTextureByNumber_CSG(int texturenumber); +#endif + +//============================================================================= +// brush.c + +extern brush_t* Brush_LoadEntity(entity_t* ent, int hullnum); +extern contents_t CheckBrushContents(const brush_t* const b); + +extern void CreateBrush(int brushnum); +#ifdef HLCSG_HULLBRUSH +extern void CreateHullShape (int entitynum, bool disabled, const char *id, int defaulthulls); +extern void InitDefaultHulls (); +#endif + +//============================================================================= +// csg.c + +extern bool g_chart; +extern bool g_onlyents; +extern bool g_noclip; +extern bool g_wadtextures; +extern bool g_skyclip; +extern bool g_estimate; +extern const char* g_hullfile; + +#ifdef ZHLT_NULLTEX // AJM: +extern bool g_bUseNullTex; +#endif + +#ifdef ZHLT_DETAIL // AJM +extern bool g_bDetailBrushes; +#endif + +#ifdef HLCSG_CLIPECONOMY // AJM: +extern bool g_bClipNazi; +#endif + +#ifdef HLCSG_PRECISIONCLIP // KGP +#define EnumPrint(a) #a +typedef enum{clip_smallest,clip_normalized,clip_simple,clip_precise,clip_legacy} cliptype; +extern cliptype g_cliptype; +extern const char* GetClipTypeString(cliptype); +#ifndef HLCSG_CUSTOMHULL +#define TEX_BEVEL 32768 +#endif +#endif + +#ifdef ZHLT_PROGRESSFILE // AJM +extern char* g_progressfile ; +#endif +#ifdef HLCSG_SCALESIZE +extern vec_t g_scalesize; +#endif +#ifdef HLCSG_KEEPLOG +extern bool g_resetlog; +#endif +#ifdef HLCSG_OPTIMIZELIGHTENTITY +extern bool g_nolightopt; +#endif +#ifdef HLCSG_GAMETEXTMESSAGE_UTF8 +extern bool g_noutf8; +#endif +#ifdef HLCSG_NULLIFYAAATRIGGER +extern bool g_nullifytrigger; +#endif + +extern vec_t g_tiny_threshold; +extern vec_t g_BrushUnionThreshold; + +extern plane_t g_mapplanes[MAX_INTERNAL_MAP_PLANES]; +extern int g_nummapplanes; + +extern bface_t* NewFaceFromFace(const bface_t* const in); +extern bface_t* CopyFace(const bface_t* const f); + +extern void FreeFace(bface_t* f); + +extern bface_t* CopyFaceList(bface_t* f); +extern void FreeFaceList(bface_t* f); + +extern void GetParamsFromEnt(entity_t* mapent); + +#ifndef HLCSG_ONLYENTS_NOWADCHANGE +//============================================================================= +// wadinclude.c +// passed 'filename' is extensionless, the function cats ".wic" at runtime + +extern void LoadWadincludeFile(const char* const filename); +extern void SaveWadincludeFile(const char* const filename); +extern void HandleWadinclude(); +#endif + +//============================================================================= +// brushunion.c +void CalculateBrushUnions(int brushnum); + +//============================================================================ +// hullfile.cpp +extern vec3_t g_hull_size[NUM_HULLS][2]; +extern void LoadHullfile(const char* filename); + +#ifdef HLCSG_WADCFG_NEW +extern const char *g_wadcfgfile; +extern const char *g_wadconfigname; +extern void LoadWadcfgfile (const char *filename); +extern void LoadWadconfig (const char *filename, const char *configname); +#endif +#ifndef HLCSG_WADCFG_NEW +#ifdef HLCSG_WADCFG // AJM: +//============================================================================ +// wadcfg.cpp + +extern void LoadWadConfigFile(); +extern void ProcessWadConfiguration(); +extern bool g_bWadConfigsLoaded; +extern void WadCfg_cleanup(); + +#define MAX_WAD_CFG_NAME 32 +extern char wadconfigname[MAX_WAD_CFG_NAME]; + +//JK: needed in wadcfg.cpp for *nix.. +#ifndef SYSTEM_WIN32 +extern char *g_apppath; +#endif + +//JK: +extern char *g_wadcfgfile; + +#endif // HLCSG_WADCFG +#endif + +#ifdef HLCSG_AUTOWAD +//============================================================================ +// autowad.cpp AJM + +extern bool g_bWadAutoDetect; +#ifndef HLCSG_AUTOWAD_NEW +extern int g_numUsedTextures; + +#ifndef HLCSG_AUTOWAD_TEXTURELIST_FIX +extern void GetUsedTextures(); +#endif +//extern bool autowad_IsUsedTexture(const char* const texname); +//extern bool autowad_IsUsedWad(const char* const path); +//extern void autowad_PurgeName(const char* const texname); +extern void autowad_cleanup(); +extern void autowad_UpdateUsedWads(); +#ifdef HLCSG_AUTOWAD_TEXTURELIST_FIX +extern void autowad_PushName(const char *texname); +#endif +#endif + +#endif // HLCSG_AUTOWAD + +//============================================================================= +// properties.cpp + +#ifdef HLCSG_NULLIFY_INVISIBLE // KGP +#include +#include +extern void properties_initialize(const char* filename); +extern std::set< std::string > g_invisible_items; +#endif + +//============================================================================ +#endif//HLCSG_H__ diff --git a/src/zhlt-vluzacn/hlcsg/hlcsg.vcproj b/src/zhlt-vluzacn/hlcsg/hlcsg.vcproj new file mode 100644 index 0000000..e8a76cd --- /dev/null +++ b/src/zhlt-vluzacn/hlcsg/hlcsg.vcproj @@ -0,0 +1,401 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/zhlt-vluzacn/hlcsg/hlcsg.vcxproj b/src/zhlt-vluzacn/hlcsg/hlcsg.vcxproj new file mode 100644 index 0000000..aa47a84 --- /dev/null +++ b/src/zhlt-vluzacn/hlcsg/hlcsg.vcxproj @@ -0,0 +1,183 @@ + + + + + Release + Win32 + + + Release + x64 + + + + + + {505681C2-3E57-300B-D330-46DD50C147D2} + + + + Application + false + MultiByte + v140 + + + Application + false + MultiByte + v140 + + + + + + + + + + + + + + + .\Release\ + .\Release\ + false + + + .\Release_x64\ + .\Release_x64\ + false + + + + MultiThreaded + Default + true + true + MaxSpeed + true + Level3 + ..\common;..\template;%(AdditionalIncludeDirectories) + HLCSG;VERSION_32BIT;NDEBUG;DOUBLEVEC_T;WIN32;_CONSOLE;SYSTEM_WIN32;STDC_HEADERS;%(PreprocessorDefinitions) + .\Release\ + true + .\Release\hlcsg.pch + .\Release\ + .\Release\ + true + true + + + .\Release\hlcsg.tlb + + + 0x0409 + NDEBUG;%(PreprocessorDefinitions) + + + true + .\Release\hlcsg.bsc + + + true + Console + false + .\Release\hlcsg.exe + binmode.obj;%(AdditionalDependencies) + 4194304 + 1048576 + false + + + + + MultiThreaded + Default + true + true + MaxSpeed + true + Level3 + ..\common;..\template;%(AdditionalIncludeDirectories) + HLCSG;VERSION_64BIT;NDEBUG;DOUBLEVEC_T;WIN32;_CONSOLE;SYSTEM_WIN32;STDC_HEADERS;%(PreprocessorDefinitions) + .\Release_x64\ + true + .\Release_x64\hlcsg.pch + .\Release_x64\ + .\Release_x64\ + true + true + + + .\Release_x64\hlcsg.tlb + + + 0x0409 + NDEBUG;%(PreprocessorDefinitions) + + + true + .\Release_x64\hlcsg.bsc + + + true + Console + false + .\Release_x64\hlcsg.exe + binmode.obj;%(AdditionalDependencies) + 4194304 + 1048576 + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/zhlt-vluzacn/hlcsg/hlcsg.vcxproj.filters b/src/zhlt-vluzacn/hlcsg/hlcsg.vcxproj.filters new file mode 100644 index 0000000..1e560f6 --- /dev/null +++ b/src/zhlt-vluzacn/hlcsg/hlcsg.vcxproj.filters @@ -0,0 +1,143 @@ + + + + + {9711f31f-2bf7-4a75-bcac-07a3d63cde1a} + cpp;c;cxx;rc;def;r;odl;idl;hpj;bat;for;f90 + + + {5de8d4ab-7f83-4409-9b7a-72d405820b95} + + + {8844422d-8c81-4887-b6b4-330edaccfdbe} + h;hpp;hxx;hm;inl;fi;fd + + + + + Source Files\common + + + Source Files\common + + + Source Files\common + + + Source Files\common + + + Source Files\common + + + Source Files\common + + + Source Files\common + + + Source Files\common + + + Source Files\common + + + Source Files\common + + + Source Files\common + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source 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 + + + \ No newline at end of file diff --git a/src/zhlt-vluzacn/hlcsg/hullfile.cpp b/src/zhlt-vluzacn/hlcsg/hullfile.cpp new file mode 100644 index 0000000..d4fbbe9 --- /dev/null +++ b/src/zhlt-vluzacn/hlcsg/hullfile.cpp @@ -0,0 +1,98 @@ +#include "csg.h" + +vec3_t g_hull_size[NUM_HULLS][2] = +{ + {// 0x0x0 + {0, 0, 0}, {0, 0, 0} + } + , + {// 32x32x72 + {-16, -16, -36}, {16, 16, 36} + } + , + {// 64x64x64 + {-32, -32, -32}, {32, 32, 32} + } + , + {// 32x32x36 + {-16, -16, -18}, {16, 16, 18} + } +}; + +void LoadHullfile(const char* filename) +{ + if (filename == NULL) + { + return; + } + + if (q_exists(filename)) + { + Log("Loading hull definitions from '%s'\n", filename); + } + else + { + Error("Could not find hull definition file '%s'\n", filename); + return; + } + + float x1,y1,z1; + float x2,y2,z2; + char magic; + + FILE* file = fopen(filename, "r"); + + int count; + int i; + + magic = (char)fgetc(file); + rewind(file); + + if(magic == '(') + { // Test for old-style hull-file + + for (i=0; i < NUM_HULLS; i++) + { + count = fscanf(file, "( %f %f %f ) ( %f %f %f )\n", &x1, &y1, &z1, &x2, &y2, &z2); + if (count != 6) + { + Error("Could not parse old hull definition file '%s' (%d, %d)\n", filename, i, count); + } + + g_hull_size[i][0][0] = x1; + g_hull_size[i][0][1] = y1; + g_hull_size[i][0][2] = z1; + + g_hull_size[i][1][0] = x2; + g_hull_size[i][1][1] = y2; + g_hull_size[i][1][2] = z2; + + } + + } + else + { + // Skip hull 0 (visibile polygons) + for (i=1; ifirstbrush + entity->numbrushes != g_nummapbrushes) + { + Error ("CopyCurrentBrush: internal error."); + } + brush_t *newb = &g_mapbrushes[g_nummapbrushes]; + g_nummapbrushes++; + hlassume (g_nummapbrushes <= MAX_MAP_BRUSHES, assume_MAX_MAP_BRUSHES); + memcpy (newb, brush, sizeof (brush_t)); + newb->firstside = g_numbrushsides; + g_numbrushsides += brush->numsides; + hlassume (g_numbrushsides <= MAX_MAP_SIDES, assume_MAX_MAP_SIDES); + memcpy (&g_brushsides[newb->firstside], &g_brushsides[brush->firstside], brush->numsides * sizeof (side_t)); + newb->entitynum = entity - g_entities; + newb->brushnum = entity->numbrushes; + entity->numbrushes++; +#ifdef HLCSG_HULLBRUSH + for (int h = 0; h < NUM_HULLS; h++) + { + if (brush->hullshapes[h] != NULL) + { + newb->hullshapes[h] = _strdup (brush->hullshapes[h]); + } + else + { + newb->hullshapes[h] = NULL; + } + } +#endif + return newb; +} +#endif +#ifdef HLCSG_HULLBRUSH +void DeleteCurrentEntity (entity_t *entity) +{ + if (entity != &g_entities[g_numentities - 1]) + { + Error ("DeleteCurrentEntity: internal error."); + } + if (entity->firstbrush + entity->numbrushes != g_nummapbrushes) + { + Error ("DeleteCurrentEntity: internal error."); + } + for (int i = entity->numbrushes - 1; i >= 0; i--) + { + brush_t *b = &g_mapbrushes[entity->firstbrush + i]; + if (b->firstside + b->numsides != g_numbrushsides) + { + Error ("DeleteCurrentEntity: internal error. (Entity %i, Brush %i)", +#ifdef HLCSG_COUNT_NEW + b->originalentitynum, b->originalbrushnum +#else + b->entitynum, b->brushnum +#endif + ); + } + memset (&g_brushsides[b->firstside], 0, b->numsides * sizeof (side_t)); + g_numbrushsides -= b->numsides; + for (int h = 0; h < NUM_HULLS; h++) + { + if (b->hullshapes[h]) + { + free (b->hullshapes[h]); + } + } + } + memset (&g_mapbrushes[entity->firstbrush], 0, entity->numbrushes * sizeof (brush_t)); + g_nummapbrushes -= entity->numbrushes; + while (entity->epairs) + { + DeleteKey (entity, entity->epairs->key); + } + memset (entity, 0, sizeof(entity_t)); + g_numentities--; +} +#endif +// ===================================================================================== +// TextureAxisFromPlane +// ===================================================================================== +void TextureAxisFromPlane(const plane_t* const pln, vec3_t xv, vec3_t yv) +{ + int bestaxis; + vec_t dot, best; + int i; + + best = 0; + bestaxis = 0; + + for (i = 0; i < 6; i++) + { + dot = DotProduct(pln->normal, s_baseaxis[i * 3]); + if (dot > best) + { + best = dot; + bestaxis = i; + } + } + + VectorCopy(s_baseaxis[bestaxis * 3 + 1], xv); + VectorCopy(s_baseaxis[bestaxis * 3 + 2], yv); +} + +#define ScaleCorrection (1.0/128.0) + +#ifndef HLCSG_CUSTOMHULL +// ===================================================================================== +// CopySKYtoCLIP +// clips a particluar sky brush +// ===================================================================================== +static void CopySKYtoCLIP(const brush_t* const b) +{ + int i; + entity_t* mapent; + brush_t* newbrush; + + if (b->contents != CONTENTS_SKY) + Error("[MOD] CopySKYtoCLIP: Got a NON-SKY for passed brush! (%s)",b->contents ); //Error("[MOD] CopySKYtoCLIP: Got a NON-SKY for passed brush! (%d)",b->contents ); //--vluzacn + + hlassert(b->contents == CONTENTS_SKY); // Only SKY brushes should be passed down to this function(sanity check) + hlassert(b->entitynum == 0); // SKY must be in worldspawn entity + + mapent = &g_entities[b->entitynum]; + mapent->numbrushes++; + + newbrush = &g_mapbrushes[g_nummapbrushes]; +#ifdef HLCSG_COUNT_NEW + newbrush->originalentitynum = b->originalentitynum; + newbrush->originalbrushnum = b->originalbrushnum; +#endif + newbrush->entitynum = b->entitynum; + newbrush->brushnum = g_nummapbrushes - mapent->firstbrush; + newbrush->firstside = g_numbrushsides; + newbrush->numsides = b->numsides; + newbrush->contents = CONTENTS_CLIP; +#ifdef HLCSG_CLIPECONOMY + newbrush->noclip = 0; +#endif +#ifdef ZHLT_DETAILBRUSH + newbrush->detaillevel = b->detaillevel; + newbrush->chopdown = b->chopdown; + newbrush->chopup = b->chopup; +#ifdef ZHLT_CLIPNODEDETAILLEVEL + newbrush->clipnodedetaillevel = b->clipnodedetaillevel; +#endif +#ifdef HLCSG_COPLANARPRIORITY + newbrush->coplanarpriority = b->coplanarpriority; +#endif +#endif + + for (i = 0; i < b->numsides; i++) + { + int j; + + side_t* side = &g_brushsides[g_numbrushsides]; + + *side = g_brushsides[b->firstside + i]; +#ifdef HLCSG_CUSTOMHULL + safe_strncpy(side->td.name, "NULL", sizeof(side->td.name)); +#else + safe_strncpy(side->td.name, "CLIP", sizeof(side->td.name)); +#endif + + for (j = 0; j < NUM_HULLS; j++) + { + newbrush->hulls[j].faces = NULL; + newbrush->hulls[j].bounds = b->hulls[j].bounds; + } + + g_numbrushsides++; + hlassume(g_numbrushsides < MAX_MAP_SIDES, assume_MAX_MAP_SIDES); + } + + g_nummapbrushes++; + hlassume(g_nummapbrushes < MAX_MAP_BRUSHES, assume_MAX_MAP_BRUSHES); +} + +// ===================================================================================== +// HandleSKYCLIP +// clips the whole sky, unconditional of g_skyclip +// ===================================================================================== +static void HandleSKYCLIP() +{ + int i; + int last; + entity_t* e = &g_entities[0]; + + for (i = e->firstbrush, last = e->firstbrush + e->numbrushes; i < last; i++) + { + if (g_mapbrushes[i].contents == CONTENTS_SKY +#ifdef HLCSG_CUSTOMHULL + && g_mapbrushes[i].noclip == false +#endif + ) + { + CopySKYtoCLIP(&g_mapbrushes[i]); + } + } +} +#endif + +// ===================================================================================== +// CheckForInvisible +// see if a brush is part of an invisible entity (KGP) +// ===================================================================================== +#ifdef HLCSG_NULLIFY_INVISIBLE +static bool CheckForInvisible(entity_t* mapent) +{ + using namespace std; + + string keyval(ValueForKey(mapent,"classname")); + if(g_invisible_items.count(keyval)) + { return true; } + + keyval.assign(ValueForKey(mapent,"targetname")); + if(g_invisible_items.count(keyval)) + { return true; } + + keyval.assign(ValueForKey(mapent,"zhlt_invisible")); + if(!keyval.empty() && strcmp(keyval.c_str(),"0")) + { return true; } + + return false; +} +#endif +// ===================================================================================== +// ParseBrush +// parse a brush from script +// ===================================================================================== +#ifdef HLCSG_COPYBRUSH +static void ParseBrush(entity_t* mapent) +#else +static contents_t ParseBrush(entity_t* mapent) +#endif +{ + brush_t* b; + int i, j; + side_t* side; + contents_t contents; + bool ok; +#ifdef HLCSG_NULLIFY_INVISIBLE // KGP + bool nullify = CheckForInvisible(mapent); +#endif + hlassume(g_nummapbrushes < MAX_MAP_BRUSHES, assume_MAX_MAP_BRUSHES); + + b = &g_mapbrushes[g_nummapbrushes]; + g_nummapbrushes++; + b->firstside = g_numbrushsides; +#ifdef HLCSG_COUNT_NEW + b->originalentitynum = g_numparsedentities; + b->originalbrushnum = g_numparsedbrushes; +#endif + b->entitynum = g_numentities - 1; + b->brushnum = g_nummapbrushes - mapent->firstbrush - 1; + +#ifdef HLCSG_CLIPECONOMY // AJM + b->noclip = 0; +#endif +#ifdef HLCSG_CUSTOMHULL + if (IntForKey(mapent, "zhlt_noclip")) + { + b->noclip = 1; + } +#endif +#ifdef HLCSG_CUSTOMHULL + b->cliphull = 0; + b->bevel = false; +#endif +#ifdef ZHLT_DETAILBRUSH + { + b->detaillevel = IntForKey (mapent, "zhlt_detaillevel"); + b->chopdown = IntForKey (mapent, "zhlt_chopdown"); + b->chopup = IntForKey (mapent, "zhlt_chopup"); +#ifdef ZHLT_CLIPNODEDETAILLEVEL + b->clipnodedetaillevel = IntForKey (mapent, "zhlt_clipnodedetaillevel"); +#endif +#ifdef HLCSG_COPLANARPRIORITY + b->coplanarpriority = IntForKey (mapent, "zhlt_coplanarpriority"); +#endif + bool wrong = false; + if (b->detaillevel < 0) + { + wrong = true; + b->detaillevel = 0; + } + if (b->chopdown < 0) + { + wrong = true; + b->chopdown = 0; + } + if (b->chopup < 0) + { + wrong = true; + b->chopup = 0; + } +#ifdef ZHLT_CLIPNODEDETAILLEVEL + if (b->clipnodedetaillevel < 0) + { + wrong = true; + b->clipnodedetaillevel = 0; + } +#endif + if (wrong) + { + Warning ("Entity %i, Brush %i: incorrect settings for detail brush.", +#ifdef HLCSG_COUNT_NEW + b->originalentitynum, b->originalbrushnum +#else + b->entitynum, b->brushnum +#endif + ); + } + } +#endif +#ifdef HLCSG_HULLBRUSH + for (int h = 0; h < NUM_HULLS; h++) + { + char key[16]; + const char *value; + sprintf (key, "zhlt_hull%d", h); + value = ValueForKey (mapent, key); + if (*value) + { + b->hullshapes[h] = _strdup (value); + } + else + { + b->hullshapes[h] = NULL; + } + } +#endif + + mapent->numbrushes++; + + ok = GetToken(true); + while (ok) + { + g_TXcommand = 0; + if (!strcmp(g_token, "}")) + { + break; + } + + hlassume(g_numbrushsides < MAX_MAP_SIDES, assume_MAX_MAP_SIDES); + side = &g_brushsides[g_numbrushsides]; + g_numbrushsides++; + + b->numsides++; + +#ifdef HLCSG_CUSTOMHULL + side->bevel = false; +#endif +#ifdef ZHLT_HIDDENSOUNDTEXTURE + side->shouldhide = false; +#endif + // read the three point plane definition + for (i = 0; i < 3; i++) + { + if (i != 0) + { + GetToken(true); + } + if (strcmp(g_token, "(")) + { + Error("Parsing Entity %i, Brush %i, Side %i : Expecting '(' got '%s'", +#ifdef HLCSG_COUNT_NEW + b->originalentitynum, b->originalbrushnum, +#else + b->entitynum, b->brushnum, +#endif + b->numsides, g_token); + } + + for (j = 0; j < 3; j++) + { + GetToken(false); + side->planepts[i][j] = atof(g_token); + } + + GetToken(false); + if (strcmp(g_token, ")")) + { + Error("Parsing Entity %i, Brush %i, Side %i : Expecting ')' got '%s'", +#ifdef HLCSG_COUNT_NEW + b->originalentitynum, b->originalbrushnum, +#else + b->entitynum, b->brushnum, +#endif + b->numsides, g_token); + } + } + + // read the texturedef + GetToken(false); + _strupr(g_token); +#ifdef HLCSG_CUSTOMHULL + { + if (!strncasecmp (g_token, "NOCLIP", 6) || !strncasecmp (g_token, "NULLNOCLIP", 10)) + { + strcpy (g_token, "NULL"); + b->noclip = true; + } + if (!strncasecmp (g_token, "BEVELBRUSH", 10)) + { + strcpy (g_token, "NULL"); + b->bevel = true; + } + if (!strncasecmp (g_token, "BEVEL", 5)) + { + strcpy (g_token, "NULL"); + side->bevel = true; + } + if (!strncasecmp (g_token, "CLIP", 4)) + { + b->cliphull |= (1 << NUM_HULLS); // arbitrary nonexistent hull + int h; + if (!strncasecmp (g_token, "CLIPHULL", 8) && (h = g_token[8] - '0', 0 < h && h < NUM_HULLS)) + { + b->cliphull |= (1 << h); // hull h + } + if (!strncasecmp (g_token, "CLIPBEVEL", 9)) + { + side->bevel = true; + } + if (!strncasecmp (g_token, "CLIPBEVELBRUSH", 14)) + { + b->bevel = true; + } +#ifdef HLCSG_PASSBULLETSBRUSH + strcpy (g_token, "SKIP"); +#else + strcpy (g_token, "NULL"); +#endif + } + } +#endif + safe_strncpy(side->td.name, g_token, sizeof(side->td.name)); + + if (g_nMapFileVersion < 220) // Worldcraft 2.1-, Radiant + { + GetToken(false); + side->td.vects.valve.shift[0] = atof(g_token); + GetToken(false); + side->td.vects.valve.shift[1] = atof(g_token); + GetToken(false); + side->td.vects.valve.rotate = atof(g_token); + GetToken(false); + side->td.vects.valve.scale[0] = atof(g_token); + GetToken(false); + side->td.vects.valve.scale[1] = atof(g_token); + } + else // Worldcraft 2.2+ + { + // texture U axis + GetToken(false); + if (strcmp(g_token, "[")) + { + hlassume(false, assume_MISSING_BRACKET_IN_TEXTUREDEF); + } + + GetToken(false); + side->td.vects.valve.UAxis[0] = atof(g_token); + GetToken(false); + side->td.vects.valve.UAxis[1] = atof(g_token); + GetToken(false); + side->td.vects.valve.UAxis[2] = atof(g_token); + GetToken(false); + side->td.vects.valve.shift[0] = atof(g_token); + + GetToken(false); + if (strcmp(g_token, "]")) + { + Error("missing ']' in texturedef (U)"); + } + + // texture V axis + GetToken(false); + if (strcmp(g_token, "[")) + { + Error("missing '[' in texturedef (V)"); + } + + GetToken(false); + side->td.vects.valve.VAxis[0] = atof(g_token); + GetToken(false); + side->td.vects.valve.VAxis[1] = atof(g_token); + GetToken(false); + side->td.vects.valve.VAxis[2] = atof(g_token); + GetToken(false); + side->td.vects.valve.shift[1] = atof(g_token); + + GetToken(false); + if (strcmp(g_token, "]")) + { + Error("missing ']' in texturedef (V)"); + } + + // Texture rotation is implicit in U/V axes. + GetToken(false); + side->td.vects.valve.rotate = 0; + + // texure scale + GetToken(false); + side->td.vects.valve.scale[0] = atof(g_token); + GetToken(false); + side->td.vects.valve.scale[1] = atof(g_token); + } + + ok = GetToken(true); // Done with line, this reads the first item from the next line + + if ((g_TXcommand == '1' || g_TXcommand == '2')) + { + // We are QuArK mode and need to translate some numbers to align textures its way + // from QuArK, the texture vectors are given directly from the three points + vec3_t TexPt[2]; + int k; + float dot22, dot23, dot33, mdet, aa, bb, dd; + + k = g_TXcommand - '0'; + for (j = 0; j < 3; j++) + { + TexPt[1][j] = (side->planepts[k][j] - side->planepts[0][j]) * ScaleCorrection; + } + k = 3 - k; + for (j = 0; j < 3; j++) + { + TexPt[0][j] = (side->planepts[k][j] - side->planepts[0][j]) * ScaleCorrection; + } + + dot22 = DotProduct(TexPt[0], TexPt[0]); + dot23 = DotProduct(TexPt[0], TexPt[1]); + dot33 = DotProduct(TexPt[1], TexPt[1]); + mdet = dot22 * dot33 - dot23 * dot23; + if (mdet < 1E-6 && mdet > -1E-6) + { + aa = bb = dd = 0; + Warning + ("Degenerate QuArK-style brush texture : Entity %i, Brush %i @ (%f,%f,%f) (%f,%f,%f) (%f,%f,%f)", +#ifdef HLCSG_COUNT_NEW + b->originalentitynum, b->originalbrushnum, +#else + b->entitynum, b->brushnum, +#endif + side->planepts[0][0], side->planepts[0][1], side->planepts[0][2], + side->planepts[1][0], side->planepts[1][1], side->planepts[1][2], side->planepts[2][0], + side->planepts[2][1], side->planepts[2][2]); + } + else + { + mdet = 1.0 / mdet; + aa = dot33 * mdet; + bb = -dot23 * mdet; + //cc = -dot23*mdet; // cc = bb + dd = dot22 * mdet; + } + + for (j = 0; j < 3; j++) + { + side->td.vects.quark.vects[0][j] = aa * TexPt[0][j] + bb * TexPt[1][j]; + side->td.vects.quark.vects[1][j] = -( /*cc */ bb * TexPt[0][j] + dd * TexPt[1][j]); + } + + side->td.vects.quark.vects[0][3] = -DotProduct(side->td.vects.quark.vects[0], side->planepts[0]); + side->td.vects.quark.vects[1][3] = -DotProduct(side->td.vects.quark.vects[1], side->planepts[0]); + } + + side->td.txcommand = g_TXcommand; // Quark stuff, but needs setting always + }; +#ifdef HLCSG_CUSTOMHULL + if (b->cliphull != 0) // has CLIP* texture + { + unsigned int mask_anyhull = 0; + for (int h = 1; h < NUM_HULLS; h++) + { + mask_anyhull |= (1 << h); + } + if ((b->cliphull & mask_anyhull) == 0) // no CLIPHULL1 or CLIPHULL2 or CLIPHULL3 texture + { + b->cliphull |= mask_anyhull; // CLIP all hulls + } + } +#endif + + b->contents = contents = CheckBrushContents(b); +#ifdef HLCSG_NULLIFY_INVISIBLE //this part has been moved down from the next line after '_strupr(g_token);'. --vluzacn + for (j = 0; j < b->numsides; j++) + { + side = &g_brushsides[b->firstside + j]; + if(nullify && strncasecmp(side->td.name,"BEVEL",5) && strncasecmp(side->td.name,"ORIGIN",6) +#ifdef HLCSG_ALLOWHINTINENTITY + && strncasecmp(side->td.name,"HINT",4) && strncasecmp(side->td.name,"SKIP",4) +#endif +#ifdef HLCSG_HLBSP_SOLIDHINT + && strncasecmp(side->td.name,"SOLIDHINT",9) +#endif +#ifdef HLCSG_NOSPLITBYHINT + && strncasecmp(side->td.name,"SPLITFACE",9) +#endif +#ifdef HLCSG_HLBSP_CUSTOMBOUNDINGBOX + && strncasecmp(side->td.name,"BOUNDINGBOX",11) +#endif +#ifdef HLCSG_CUSTOMCONTENT + && strncasecmp(side->td.name,"CONTENT",7) && strncasecmp(side->td.name,"SKY",3) +#endif + ) + { + safe_strncpy(side->td.name,"NULL",sizeof(side->td.name)); + } + } +#endif +#ifdef HLCSG_NOSPLITBYHINT + for (j = 0; j < b->numsides; j++) + { + // change to SKIP now that we have set brush content. + side = &g_brushsides[b->firstside + j]; + if (!strncasecmp (side->td.name, "SPLITFACE", 9)) + { + strcpy (side->td.name, "SKIP"); + } + } +#endif +#ifdef HLCSG_CUSTOMCONTENT + for (j = 0; j < b->numsides; j++) + { + side = &g_brushsides[b->firstside + j]; + if (!strncasecmp (side->td.name, "CONTENT", 7)) + { + strcpy (side->td.name, "NULL"); + } + } +#endif +#ifdef HLCSG_NULLIFYAAATRIGGER + if (g_nullifytrigger) + { + for (j = 0; j < b->numsides; j++) + { + side = &g_brushsides[b->firstside + j]; + if (!strncasecmp (side->td.name, "AAATRIGGER", 10)) + { + strcpy (side->td.name, "NULL"); + } + } + } +#endif + + // + // origin brushes are removed, but they set + // the rotation origin for the rest of the brushes + // in the entity + // + + if (contents == CONTENTS_ORIGIN) + { +#ifdef HLCSG_HLBSP_CUSTOMBOUNDINGBOX + if (*ValueForKey (mapent, "origin")) + { + Error ("Entity %i, Brush %i: Only one ORIGIN brush allowed.", + #ifdef HLCSG_COUNT_NEW + b->originalentitynum, b->originalbrushnum + #else + b->entitynum, b->brushnum + #endif + ); + } +#endif + char string[MAXTOKEN]; + vec3_t origin; + + b->contents = CONTENTS_SOLID; + CreateBrush(mapent->firstbrush + b->brushnum); // to get sizes + b->contents = contents; + + for (i = 0; i < NUM_HULLS; i++) + { + b->hulls[i].faces = NULL; + } + + if (b->entitynum != 0) // Ignore for WORLD (code elsewhere enforces no ORIGIN in world message) + { + VectorAdd(b->hulls[0].bounds.m_Mins, b->hulls[0].bounds.m_Maxs, origin); + VectorScale(origin, 0.5, origin); + + safe_snprintf(string, MAXTOKEN, "%i %i %i", (int)origin[0], (int)origin[1], (int)origin[2]); + SetKeyValue(&g_entities[b->entitynum], "origin", string); + } + } +#ifdef HLCSG_COPYMODELKEYVALUE + if (*ValueForKey (&g_entities[b->entitynum], "zhlt_usemodel")) + { + memset (&g_brushsides[b->firstside], 0, b->numsides * sizeof (side_t)); + g_numbrushsides -= b->numsides; +#ifdef HLCSG_HULLBRUSH + for (int h = 0; h < NUM_HULLS; h++) + { + if (b->hullshapes[h]) + { + free (b->hullshapes[h]); + } + } +#endif + memset (b, 0, sizeof (brush_t)); + g_nummapbrushes--; + mapent->numbrushes--; + return; + } +#endif +#ifdef HLCSG_HULLBRUSH + if (!strcmp (ValueForKey (&g_entities[b->entitynum], "classname"), "info_hullshape")) + { + // all brushes should be erased, but not now. + return; + } +#endif +#ifdef HLCSG_HLBSP_CUSTOMBOUNDINGBOX + if (contents == CONTENTS_BOUNDINGBOX) + { + if (*ValueForKey (mapent, "zhlt_minsmaxs")) + { + Error ("Entity %i, Brush %i: Only one BoundingBox brush allowed.", + #ifdef HLCSG_COUNT_NEW + b->originalentitynum, b->originalbrushnum + #else + b->entitynum, b->brushnum + #endif + ); + } + char string[MAXTOKEN]; + vec3_t mins, maxs; + char *origin = NULL; + if (*ValueForKey (mapent, "origin")) + { + origin = strdup (ValueForKey (mapent, "origin")); + SetKeyValue (mapent, "origin", ""); + } + + b->contents = CONTENTS_SOLID; + CreateBrush(mapent->firstbrush + b->brushnum); // to get sizes + b->contents = contents; + + for (i = 0; i < NUM_HULLS; i++) + { + b->hulls[i].faces = NULL; + } + + if (b->entitynum != 0) // Ignore for WORLD (code elsewhere enforces no ORIGIN in world message) + { + VectorCopy(b->hulls[0].bounds.m_Mins, mins); + VectorCopy(b->hulls[0].bounds.m_Maxs, maxs); + + safe_snprintf(string, MAXTOKEN, "%.0f %.0f %.0f %.0f %.0f %.0f", mins[0], mins[1], mins[2], maxs[0], maxs[1], maxs[2]); + SetKeyValue(&g_entities[b->entitynum], "zhlt_minsmaxs", string); + } + + if (origin) + { + SetKeyValue (mapent, "origin", origin); + free (origin); + } + } +#endif +#ifdef HLCSG_CUSTOMHULL + if (g_skyclip && b->contents == CONTENTS_SKY && !b->noclip) + { + brush_t *newb = CopyCurrentBrush (mapent, b); + newb->contents = CONTENTS_SOLID; + newb->cliphull = ~0; + for (j = 0; j < newb->numsides; j++) + { + side = &g_brushsides[newb->firstside + j]; + strcpy (side->td.name, "NULL"); + } + } +#endif +#ifdef HLCSG_PASSBULLETSBRUSH + if (b->cliphull != 0 && b->contents == CONTENTS_TOEMPTY) + { + // check for mix of CLIP and normal texture + bool mixed = false; + for (j = 0; j < b->numsides; j++) + { + side = &g_brushsides[b->firstside + j]; + if (!strncasecmp (side->td.name, "NULL", 4)) + { // this is not supposed to be a HINT brush, so remove all invisible faces from hull 0. + strcpy (side->td.name, "SKIP"); + } + if (strncasecmp (side->td.name, "SKIP", 4)) + mixed = true; + } + if (mixed) + { + brush_t *newb = CopyCurrentBrush (mapent, b); + newb->cliphull = 0; + } + b->contents = CONTENTS_SOLID; + for (j = 0; j < b->numsides; j++) + { + side = &g_brushsides[b->firstside + j]; + strcpy (side->td.name, "NULL"); + } + } +#endif + +#ifndef HLCSG_COPYBRUSH + return contents; +#endif +} + + +// ===================================================================================== +// ParseMapEntity +// parse an entity from script +// ===================================================================================== +bool ParseMapEntity() +{ + bool all_clip = true; + int this_entity; + entity_t* mapent; + epair_t* e; + +#ifdef HLCSG_COUNT_NEW + g_numparsedbrushes = 0; +#endif + if (!GetToken(true)) + { + return false; + } + + this_entity = g_numentities; + + if (strcmp(g_token, "{")) + { + Error("Parsing Entity %i, expected '{' got '%s'", +#ifdef HLCSG_COUNT_NEW + g_numparsedentities, +#else + this_entity, +#endif + g_token); + } + + hlassume(g_numentities < MAX_MAP_ENTITIES, assume_MAX_MAP_ENTITIES); + g_numentities++; + + mapent = &g_entities[this_entity]; + mapent->firstbrush = g_nummapbrushes; + mapent->numbrushes = 0; + + while (1) + { + if (!GetToken(true)) + Error("ParseEntity: EOF without closing brace"); + + if (!strcmp(g_token, "}")) // end of our context + break; + + if (!strcmp(g_token, "{")) // must be a brush + { +#ifdef HLCSG_COPYBRUSH + ParseBrush (mapent); +#else + contents_t contents = ParseBrush(mapent); +#endif +#ifdef HLCSG_COUNT_NEW + g_numparsedbrushes++; +#endif + +#ifndef HLCSG_COPYBRUSH + if ((contents != CONTENTS_CLIP) + && (contents != CONTENTS_ORIGIN) +#ifdef HLCSG_HLBSP_CUSTOMBOUNDINGBOX + && contents != CONTENTS_BOUNDINGBOX +#endif + ) + all_clip = false; +#endif + } + else // else assume an epair + { + e = ParseEpair(); + if (mapent->numbrushes > 0) Warning ("Error: ParseEntity: Keyvalue comes after brushes."); //--vluzacn + + if (!strcmp(e->key, "mapversion")) + { + g_nMapFileVersion = atoi(e->value); + } + +#ifdef HLCSG_NOREDUNDANTKEY + SetKeyValue (mapent, e->key, e->value); + Free (e->key); + Free (e->value); + Free (e); +#else + e->next = mapent->epairs; + mapent->epairs = e; +#endif + } + } +#ifdef HLCSG_COPYBRUSH + { + int i; + for (i = 0; i < mapent->numbrushes; i++) + { + brush_t *brush = &g_mapbrushes[mapent->firstbrush + i]; + if ( + #ifdef HLCSG_CUSTOMHULL + brush->cliphull == 0 + #else + brush->contents != CONTENTS_CLIP + #endif + && brush->contents != CONTENTS_ORIGIN + #ifdef HLCSG_HLBSP_CUSTOMBOUNDINGBOX + && brush->contents != CONTENTS_BOUNDINGBOX + #endif + ) + { + all_clip = false; + } + } + } +#endif +#ifdef HLCSG_COPYMODELKEYVALUE + if (*ValueForKey (mapent, "zhlt_usemodel")) + { + if (!*ValueForKey (mapent, "origin")) + Warning ("Entity %i: 'zhlt_usemodel' requires the entity to have an origin brush.", +#ifdef HLCSG_COUNT_NEW + g_numparsedentities +#else + this_entity +#endif + ); + mapent->numbrushes = 0; + } +#endif +#ifdef HLCSG_SCALESIZE +#ifdef HLCSG_HULLBRUSH + if (strcmp (ValueForKey (mapent, "classname"), "info_hullshape")) // info_hullshape is not affected by '-scale' +#endif + { + bool ent_move_b = false, ent_scale_b = false, ent_gscale_b = false; + vec3_t ent_move = {0,0,0}, ent_scale_origin = {0,0,0}; + vec_t ent_scale = 1, ent_gscale = 1; + + if (g_scalesize > 0) + { + ent_gscale_b = true; + ent_gscale = g_scalesize; + } + double v[4] = {0,0,0,0}; + if (*ValueForKey (mapent, "zhlt_transform")) + { + switch + (sscanf(ValueForKey (mapent, "zhlt_transform"), "%lf %lf %lf %lf", v, v+1, v+2, v+3)) + { + case 1: + ent_scale_b = true; + ent_scale = v[0]; + break; + case 3: + ent_move_b = true; + VectorCopy (v, ent_move); + break; + case 4: + ent_scale_b = true; + ent_scale = v[0]; + ent_move_b = true; + VectorCopy (v+1, ent_move); + break; + default: + Warning ("bad value '%s' for key 'zhlt_transform'", ValueForKey (mapent, "zhlt_transform")); + } + DeleteKey (mapent, "zhlt_transform"); + } + GetVectorForKey (mapent, "origin", ent_scale_origin); + + if (ent_move_b || ent_scale_b || ent_gscale_b) + { + if (g_nMapFileVersion < 220 || g_brushsides[0].td.txcommand != 0) + { + Warning ("hlcsg scaling hack is not supported in Worldcraft 2.1- or QuArK mode"); + } + else + { + int ibrush, iside, ipoint; + brush_t *brush; + side_t *side; + vec_t *point; + for (ibrush = 0, brush = g_mapbrushes + mapent->firstbrush; ibrush < mapent->numbrushes; ++ibrush, ++brush) + { + for (iside = 0, side = g_brushsides + brush->firstside; iside < brush->numsides; ++iside, ++side) + { + for (ipoint = 0; ipoint < 3; ++ipoint) + { + point = side->planepts[ipoint]; + if (ent_scale_b) + { + VectorSubtract (point, ent_scale_origin, point); + VectorScale (point, ent_scale, point); + VectorAdd (point, ent_scale_origin, point); + } + if (ent_move_b) + { + VectorAdd (point, ent_move, point); + + } + if (ent_gscale_b) + { + VectorScale (point, ent_gscale, point); + } + } +#ifdef ZHLT_FREETEXTUREAXIS + // note that tex->vecs = td.vects.valve.Axis / td.vects.valve.scale + // tex->vecs[3] = vects.valve.shift + Dot(origin, tex->vecs) + // and texcoordinate = Dot(worldposition, tex->vecs) + tex->vecs[3] + bool zeroscale = false; + if (!side->td.vects.valve.scale[0]) + { + side->td.vects.valve.scale[0] = 1; + } + if (!side->td.vects.valve.scale[1]) + { + side->td.vects.valve.scale[1] = 1; + } + if (ent_scale_b) + { + vec_t coord[2]; + if (fabs (side->td.vects.valve.scale[0]) > NORMAL_EPSILON) + { + coord[0] = DotProduct (ent_scale_origin, side->td.vects.valve.UAxis) / side->td.vects.valve.scale[0] + side->td.vects.valve.shift[0]; + side->td.vects.valve.scale[0] *= ent_scale; + if (fabs (side->td.vects.valve.scale[0]) > NORMAL_EPSILON) + { + side->td.vects.valve.shift[0] = coord[0] - DotProduct (ent_scale_origin, side->td.vects.valve.UAxis) / side->td.vects.valve.scale[0]; + } + else + { + zeroscale = true; + } + } + else + { + zeroscale = true; + } + if (fabs (side->td.vects.valve.scale[1]) > NORMAL_EPSILON) + { + coord[1] = DotProduct (ent_scale_origin, side->td.vects.valve.VAxis) / side->td.vects.valve.scale[1] + side->td.vects.valve.shift[1]; + side->td.vects.valve.scale[1] *= ent_scale; + if (fabs (side->td.vects.valve.scale[1]) > NORMAL_EPSILON) + { + side->td.vects.valve.shift[1] = coord[1] - DotProduct (ent_scale_origin, side->td.vects.valve.VAxis) / side->td.vects.valve.scale[1]; + } + else + { + zeroscale = true; + } + } + else + { + zeroscale = true; + } + } + if (ent_move_b) + { + if (fabs (side->td.vects.valve.scale[0]) > NORMAL_EPSILON) + { + side->td.vects.valve.shift[0] -= DotProduct (ent_move, side->td.vects.valve.UAxis) / side->td.vects.valve.scale[0]; + } + else + { + zeroscale = true; + } + if (fabs (side->td.vects.valve.scale[1]) > NORMAL_EPSILON) + { + side->td.vects.valve.shift[1] -= DotProduct (ent_move, side->td.vects.valve.VAxis) / side->td.vects.valve.scale[1]; + } + else + { + zeroscale = true; + } + } + if (ent_gscale_b) + { + side->td.vects.valve.scale[0] *= ent_gscale; + side->td.vects.valve.scale[1] *= ent_gscale; + } + if (zeroscale) + { + Error ("Entity %i, Brush %i: invalid texture scale.\n", + #ifdef HLCSG_COUNT_NEW + brush->originalentitynum, brush->originalbrushnum + #else + this_entity, ibrush + #endif + ); + } +#else + vec3_t U, V, position; + // assume: UAxis and VAxis are perpendicular normalized vectors. + VectorScale (side->td.vects.valve.UAxis, - side->td.vects.valve.shift[0] * side->td.vects.valve.scale[0], U); + VectorScale (side->td.vects.valve.VAxis, - side->td.vects.valve.shift[1] * side->td.vects.valve.scale[1], V); + VectorAdd (U, V, position); + if (ent_scale_b) + { + VectorSubtract (position, ent_scale_origin, position); + VectorScale (position, ent_scale, position); + VectorAdd (position, ent_scale_origin, position); + side->td.vects.valve.scale[0] *= ent_scale; + side->td.vects.valve.scale[1] *= ent_scale; + } + if (ent_move_b) + { + VectorAdd (position, ent_move, position); + } + if (ent_gscale_b) + { + VectorScale (position, ent_gscale, position); + side->td.vects.valve.scale[0] *= ent_gscale; + side->td.vects.valve.scale[1] *= ent_gscale; + } + side->td.vects.valve.shift[0] = DotProduct (position, side->td.vects.valve.UAxis) / - side->td.vects.valve.scale[0]; + side->td.vects.valve.shift[1] = DotProduct (position, side->td.vects.valve.VAxis) / - side->td.vects.valve.scale[1]; +#endif + } + } + if (ent_gscale_b) + { + if (*ValueForKey (mapent, "origin")) + { + double v[3]; + int origin[3]; + char string[MAXTOKEN]; + int i; + GetVectorForKey (mapent, "origin", v); + VectorScale (v, ent_gscale, v); + for (i=0; i<3; ++i) + origin[i] = (int)(v[i]>=0? v[i]+0.5: v[i]-0.5); + safe_snprintf(string, MAXTOKEN, "%d %d %d", origin[0], origin[1], origin[2]); + SetKeyValue (mapent, "origin", string); + } + } +#ifdef HLCSG_HLBSP_CUSTOMBOUNDINGBOX + { + double b[2][3]; + if (sscanf (ValueForKey (mapent, "zhlt_minsmaxs"), "%lf %lf %lf %lf %lf %lf", &b[0][0], &b[0][1], &b[0][2], &b[1][0], &b[1][1], &b[1][2]) == 6) + { + for (int i = 0; i < 2; i++) + { + vec_t *point = b[i]; + if (ent_scale_b) + { + VectorSubtract (point, ent_scale_origin, point); + VectorScale (point, ent_scale, point); + VectorAdd (point, ent_scale_origin, point); + } + if (ent_move_b) + { + VectorAdd (point, ent_move, point); + + } + if (ent_gscale_b) + { + VectorScale (point, ent_gscale, point); + } + } + char string[MAXTOKEN]; + safe_snprintf(string, MAXTOKEN, "%.0f %.0f %.0f %.0f %.0f %.0f", b[0][0], b[0][1], b[0][2], b[1][0], b[1][1], b[1][2]); + SetKeyValue (mapent, "zhlt_minsmaxs", string); + } + } +#endif + } + } + } +#endif + + +#ifndef HLCSG_HLBSP_ALLOWEMPTYENTITY + if (mapent->numbrushes && all_clip) + Fatal(assume_NO_VISIBILE_BRUSHES, "Entity %i has no visible brushes\n", +#ifdef HLCSG_COUNT_NEW + g_numparsedentities +#else + this_entity +#endif + ); +#endif + + CheckFatal(); +#ifdef HLCSG_LOGVERSION + if (this_entity == 0) + { + // Let the map tell which version of the compiler it comes from, to help tracing compiler bugs. + char versionstring [128]; + sprintf (versionstring, "ZHLT " ZHLT_VERSIONSTRING " " HACK_VERSIONSTRING " (%s)", __DATE__); + SetKeyValue (mapent, "compiler", versionstring); + } +#endif + +#ifdef ZHLT_DETAIL // AJM + if (!strcmp(ValueForKey(mapent, "classname"), "info_detail") && g_bDetailBrushes && this_entity != 0) + { + // mark all of the brushes in this entity as contents_detail + for (int i = mapent->firstbrush; i < mapent->firstbrush + mapent->numbrushes; i++) + { + g_mapbrushes[i].contents = CONTENTS_DETAIL; + } + + // move these brushes to worldspawn + { + brush_t* temp; + int newbrushes; + int worldbrushes; + int i; + + newbrushes = mapent->numbrushes; + worldbrushes = g_entities[0].numbrushes; + + temp = (brush_t*)Alloc(newbrushes * sizeof(brush_t)); + memcpy(temp, g_mapbrushes + mapent->firstbrush, newbrushes * sizeof(brush_t)); + + for (i = 0; i < newbrushes; i++) + { + temp[i].entitynum = 0; + } + + // make space to move the brushes (overlapped copy) + memmove(g_mapbrushes + worldbrushes + newbrushes, + g_mapbrushes + worldbrushes, sizeof(brush_t) * (g_nummapbrushes - worldbrushes - newbrushes)); + + // copy the new brushes down + memcpy(g_mapbrushes + worldbrushes, temp, sizeof(brush_t) * newbrushes); + + // fix up indexes + g_numentities--; + g_entities[0].numbrushes += newbrushes; + for (i = 1; i < g_numentities; i++) + { + g_entities[i].firstbrush += newbrushes; + } + memset(mapent, 0, sizeof(*mapent)); + Free(temp); + } + + // delete this entity + g_numentities--; + return true; + } +#endif + + +#ifdef ZHLT_INFO_COMPILE_PARAMETERS // AJM + if (!strcmp(ValueForKey(mapent, "classname"), "info_compile_parameters")) + { + GetParamsFromEnt(mapent); + } +#endif + +#ifndef HLCSG_CUSTOMHULL + // if its the worldspawn entity and we need to skyclip, then do it + if ((this_entity == 0) && g_skyclip) // first entitiy + { + HandleSKYCLIP(); + } +#endif + +#ifndef HLCSG_HLBSP_ALLOWEMPTYENTITY + // if the given entity only has one brush and its an origin brush + if ((mapent->numbrushes == 1) && (g_mapbrushes[mapent->firstbrush].contents == CONTENTS_ORIGIN)) + { + brushhull_t* hull = g_mapbrushes[mapent->firstbrush].hulls; + + Error("Entity %i, contains ONLY an origin brush near (%.0f,%.0f,%.0f)\n", +#ifdef HLCSG_COUNT_NEW + g_numparsedentities, +#else + this_entity, +#endif + hull->bounds.m_Mins[0], hull->bounds.m_Mins[1], hull->bounds.m_Mins[2]); + } +#endif + + GetVectorForKey(mapent, "origin", mapent->origin); + +#ifdef HLCSG_FUNCGROUP_FIX + if (!strcmp("func_group", ValueForKey(mapent, "classname")) +#ifdef ZHLT_DETAILBRUSH + || !strcmp("func_detail", ValueForKey (mapent, "classname")) +#endif + ) +#else + // group entities are just for editor convenience + // toss all brushes into the world entity + if (!g_onlyents && !strcmp("func_group", ValueForKey(mapent, "classname"))) +#endif + { +#ifdef ZHLT_HIDDENSOUNDTEXTURE + if (IntForKey (mapent,"zhlt_hidden")) + { + for (int i = 0; i < mapent->numbrushes; i++) + { + brush_t *b = &g_mapbrushes[mapent->firstbrush + i]; + for (int j = 0; j < b->numsides; j++) + { + side_t *s = &g_brushsides[b->firstside + j]; + s->shouldhide = true; + } + } + } +#endif + // this is pretty gross, because the brushes are expected to be + // in linear order for each entity + brush_t* temp; + int newbrushes; + int worldbrushes; + int i; + + newbrushes = mapent->numbrushes; + worldbrushes = g_entities[0].numbrushes; + + temp = (brush_t*)Alloc(newbrushes * sizeof(brush_t)); + memcpy(temp, g_mapbrushes + mapent->firstbrush, newbrushes * sizeof(brush_t)); + + for (i = 0; i < newbrushes; i++) + { + temp[i].entitynum = 0; +#ifdef HLCSG_FUNCGROUP_FIX + temp[i].brushnum += worldbrushes; +#endif + } + + // make space to move the brushes (overlapped copy) + memmove(g_mapbrushes + worldbrushes + newbrushes, + g_mapbrushes + worldbrushes, sizeof(brush_t) * (g_nummapbrushes - worldbrushes - newbrushes)); + + // copy the new brushes down + memcpy(g_mapbrushes + worldbrushes, temp, sizeof(brush_t) * newbrushes); + + // fix up indexes + g_numentities--; + g_entities[0].numbrushes += newbrushes; + for (i = 1; i < g_numentities; i++) + { + g_entities[i].firstbrush += newbrushes; + } + memset(mapent, 0, sizeof(*mapent)); + Free(temp); +#ifdef HLCSG_FUNCGROUP_FIX + return true; +#endif + } + +#ifdef HLCSG_HULLBRUSH + if (!strcmp (ValueForKey (mapent, "classname"), "info_hullshape")) + { + bool disabled; + const char *id; + int defaulthulls; + disabled = IntForKey (mapent, "disabled"); + id = ValueForKey (mapent, "targetname"); + defaulthulls = IntForKey (mapent, "defaulthulls"); + CreateHullShape (this_entity, disabled, id, defaulthulls); + DeleteCurrentEntity (mapent); + return true; + } +#endif +#ifdef ZHLT_LARGERANGE + if (fabs (mapent->origin[0]) > ENGINE_ENTITY_RANGE + ON_EPSILON || + fabs (mapent->origin[1]) > ENGINE_ENTITY_RANGE + ON_EPSILON || + fabs (mapent->origin[2]) > ENGINE_ENTITY_RANGE + ON_EPSILON ) + { + const char *classname = ValueForKey (mapent, "classname"); + if (strncmp (classname, "light", 5)) + { + Warning ("Entity %i (classname \"%s\"): origin outside +/-%.0f: (%.0f,%.0f,%.0f)", +#ifdef HLCSG_COUNT_NEW + g_numparsedentities, +#else + this_entity, +#endif + classname, (double)ENGINE_ENTITY_RANGE, mapent->origin[0], mapent->origin[1], mapent->origin[2]); + } + } +#endif + return true; +} + +// ===================================================================================== +// CountEngineEntities +// ===================================================================================== +unsigned int CountEngineEntities() +{ + unsigned int x; + unsigned num_engine_entities = 0; + entity_t* mapent = g_entities; + + // for each entity in the map + for (x=0; x +#include +using namespace std; + +set< string > g_invisible_items; + +void properties_initialize(const char* filename) +{ + if (filename == NULL) + { return; } + + if (q_exists(filename)) + { Log("Loading null entity list from '%s'\n", filename); } + else + { + Error("Could not find null entity list file '%s'\n", filename); + return; + } + + ifstream file(filename,ios::in); + if(!file) + { + file.close(); + return; + } + + + //begin reading list of items + char line[MAX_VAL]; //MAX_VALUE //vluzacn + memset(line,0,sizeof(char)*4096); + while(!file.eof()) + { + string str; + getline(file,str); + { //--vluzacn + char *s = strdup (str.c_str ()); + int i; + for (i = 0; s[i] != '\0'; i++) + { + if (s[i] == '\n' || s[i] == '\r') + { + s[i] = '\0'; + } + } + str.assign (s); + free (s); + } + if(str.size() < 1) + { continue; } + g_invisible_items.insert(str); + } + file.close(); +} + +#endif \ No newline at end of file diff --git a/src/zhlt-vluzacn/hlcsg/qcsg.cpp b/src/zhlt-vluzacn/hlcsg/qcsg.cpp new file mode 100644 index 0000000..25263d5 --- /dev/null +++ b/src/zhlt-vluzacn/hlcsg/qcsg.cpp @@ -0,0 +1,3077 @@ +//#pragma warning(disable: 4018) // '<' : signed/unsigned mismatch + +/* + + CONSTRUCTIVE SOLID GEOMETRY -aka- C S G + + Code based on original code from Valve Software, + Modified by Sean "Zoner" Cavanaugh (seanc@gearboxsoftware.com) with permission. + Modified by Tony "Merl" Moore (merlinis@bigpond.net.au) [AJM] + +*/ + +#include "csg.h" +#ifdef SYSTEM_WIN32 +#define WIN32_LEAN_AND_MEAN +#include //--vluzacn +#endif + +/* + + NOTES + + - check map size for +/- 4k limit at load time + - allow for multiple wad.cfg configurations per compile + +*/ + +static FILE* out[NUM_HULLS]; // pointer to each of the hull out files (.p0, .p1, ect.) +#ifdef HLCSG_VIEWSURFACE +static FILE* out_view[NUM_HULLS]; +#endif +#ifdef ZHLT_DETAILBRUSH +static FILE* out_detailbrush[NUM_HULLS]; +#endif +static int c_tiny; +static int c_tiny_clip; +static int c_outfaces; +static int c_csgfaces; +BoundingBox world_bounds; + +#ifndef HLCSG_WADCFG_NEW +#ifdef HLCSG_WADCFG +char wadconfigname[MAX_WAD_CFG_NAME]; +#endif +#endif + +vec_t g_tiny_threshold = DEFAULT_TINY_THRESHOLD; + +bool g_noclip = DEFAULT_NOCLIP; // no clipping hull "-noclip" +bool g_onlyents = DEFAULT_ONLYENTS; // onlyents mode "-onlyents" +bool g_wadtextures = DEFAULT_WADTEXTURES; // "-nowadtextures" +bool g_chart = DEFAULT_CHART; // show chart "-chart" +bool g_skyclip = DEFAULT_SKYCLIP; // no sky clipping "-noskyclip" +bool g_estimate = DEFAULT_ESTIMATE; // progress estimates "-estimate" +bool g_info = DEFAULT_INFO; // "-info" ? +const char* g_hullfile = NULL; // external hullfile "-hullfie sdfsd" +#ifdef HLCSG_WADCFG_NEW +const char* g_wadcfgfile = NULL; +const char* g_wadconfigname = NULL; +#endif + +#ifdef ZHLT_NULLTEX // AJM +bool g_bUseNullTex = DEFAULT_NULLTEX; // "-nonulltex" +#endif + +#ifdef HLCSG_PRECISIONCLIP // KGP +cliptype g_cliptype = DEFAULT_CLIPTYPE; // "-cliptype " +#endif + +#ifdef HLCSG_NULLIFY_INVISIBLE +const char* g_nullfile = NULL; +#endif + +#ifdef HLCSG_CLIPECONOMY // AJM +bool g_bClipNazi = DEFAULT_CLIPNAZI; // "-noclipeconomy" +#endif + +#ifdef HLCSG_AUTOWAD // AJM +bool g_bWadAutoDetect = DEFAULT_WADAUTODETECT; // "-wadautodetect" +#endif + +#ifdef ZHLT_DETAIL // AJM +bool g_bDetailBrushes = DEFAULT_DETAIL; // "-detail" +#endif + +#ifdef ZHLT_PROGRESSFILE // AJM +char* g_progressfile = DEFAULT_PROGRESSFILE; // "-progressfile path" +#endif +#ifdef HLCSG_SCALESIZE +vec_t g_scalesize = DEFAULT_SCALESIZE; +#endif +#ifdef HLCSG_KEEPLOG +bool g_resetlog = DEFAULT_RESETLOG; +#endif +#ifdef HLCSG_OPTIMIZELIGHTENTITY +bool g_nolightopt = DEFAULT_NOLIGHTOPT; +#endif +#ifdef HLCSG_GAMETEXTMESSAGE_UTF8 +bool g_noutf8 = DEFAULT_NOUTF8; +#endif +#ifdef HLCSG_NULLIFYAAATRIGGER +bool g_nullifytrigger = DEFAULT_NULLIFYTRIGGER; +#endif +#ifdef HLCSG_VIEWSURFACE +bool g_viewsurface = false; +#endif + +#ifdef ZHLT_INFO_COMPILE_PARAMETERS +// ===================================================================================== +// GetParamsFromEnt +// parses entity keyvalues for setting information +// ===================================================================================== +void GetParamsFromEnt(entity_t* mapent) +{ + int iTmp; + char szTmp[256]; + + Log("\nCompile Settings detected from info_compile_parameters entity\n"); + + // verbose(choices) : "Verbose compile messages" : 0 = [ 0 : "Off" 1 : "On" ] + iTmp = IntForKey(mapent, "verbose"); + if (iTmp == 1) + { + g_verbose = true; + } + else if (iTmp == 0) + { + g_verbose = false; + } + Log("%30s [ %-9s ]\n", "Compile Option", "setting"); + Log("%30s [ %-9s ]\n", "Verbose Compile Messages", g_verbose ? "on" : "off"); + + // estimate(choices) :"Estimate Compile Times?" : 0 = [ 0: "Yes" 1: "No" ] + if (IntForKey(mapent, "estimate")) + { + g_estimate = true; + } + else + { + g_estimate = false; + } + Log("%30s [ %-9s ]\n", "Estimate Compile Times", g_estimate ? "on" : "off"); + + // priority(choices) : "Priority Level" : 0 = [ 0 : "Normal" 1 : "High" -1 : "Low" ] + if (!strcmp(ValueForKey(mapent, "priority"), "1")) + { + g_threadpriority = eThreadPriorityHigh; + Log("%30s [ %-9s ]\n", "Thread Priority", "high"); + } + else if (!strcmp(ValueForKey(mapent, "priority"), "-1")) + { + g_threadpriority = eThreadPriorityLow; + Log("%30s [ %-9s ]\n", "Thread Priority", "low"); + } + + // texdata(string) : "Texture Data Memory" : "4096" + iTmp = IntForKey(mapent, "texdata") * 1024; + if (iTmp > g_max_map_miptex) + { + g_max_map_miptex = iTmp; + } + sprintf_s(szTmp, "%i", g_max_map_miptex); + Log("%30s [ %-9s ]\n", "Texture Data Memory", szTmp); + + // hullfile(string) : "Custom Hullfile" + if (ValueForKey(mapent, "hullfile")) + { + g_hullfile = ValueForKey(mapent, "hullfile"); + Log("%30s [ %-9s ]\n", "Custom Hullfile", g_hullfile); + } + +#ifdef HLCSG_AUTOWAD + // wadautodetect(choices) : "Wad Auto Detect" : 0 = [ 0 : "Off" 1 : "On" ] + if (!strcmp(ValueForKey(mapent, "wadautodetect"), "1")) + { + g_bWadAutoDetect = true; + } + else + { + g_bWadAutoDetect = false; + } + Log("%30s [ %-9s ]\n", "Wad Auto Detect", g_bWadAutoDetect ? "on" : "off"); +#endif + +#ifndef HLCSG_WADCFG_NEW +#ifdef HLCSG_WADCFG + // wadconfig(string) : "Custom Wad Configuration" : "" + if (strlen(ValueForKey(mapent, "wadconfig")) > 0) + { + safe_strncpy(wadconfigname, ValueForKey(mapent, "wadconfig"), MAX_WAD_CFG_NAME); + Log("%30s [ %-9s ]\n", "Custom Wad Configuration", wadconfigname); + } +#endif +#endif +#ifdef HLCSG_WADCFG_NEW + // wadconfig(string) : "Custom Wad Configuration" : "" + if (*ValueForKey(mapent, "wadconfig")) + { + g_wadconfigname = _strdup (ValueForKey(mapent, "wadconfig")); + Log("%30s [ %-9s ]\n", "Custom Wad Configuration Name", g_wadconfigname); + } + // wadcfgfile(string) : "Custom Wad Configuration File" : "" + if (*ValueForKey(mapent, "wadcfgfile")) + { + g_wadcfgfile = _strdup (ValueForKey(mapent, "wadcfgfile")); + Log("%30s [ %-9s ]\n", "Custom Wad Configuration File", g_wadcfgfile); + } +#endif + +#ifdef HLCSG_CLIPECONOMY + // noclipeconomy(choices) : "Strip Uneeded Clipnodes?" : 1 = [ 1 : "Yes" 0 : "No" ] + iTmp = IntForKey(mapent, "noclipeconomy"); + if (iTmp == 1) + { + g_bClipNazi = true; + } + else if (iTmp == 0) + { + g_bClipNazi = false; + } + Log("%30s [ %-9s ]\n", "Clipnode Economy Mode", g_bClipNazi ? "on" : "off"); +#endif + + /* + hlcsg(choices) : "HLCSG" : 1 = + [ + 1 : "Normal" + 2 : "Onlyents" + 0 : "Off" + ] + */ + iTmp = IntForKey(mapent, "hlcsg"); + g_onlyents = false; + if (iTmp == 2) + { + g_onlyents = true; + } + else if (iTmp == 0) + { + Fatal(assume_TOOL_CANCEL, + "%s was set to \"Off\" (0) in info_compile_parameters entity, execution cancelled", g_Program); + CheckFatal(); + } + Log("%30s [ %-9s ]\n", "Onlyents", g_onlyents ? "on" : "off"); + + /* + nocliphull(choices) : "Generate clipping hulls" : 0 = + [ + 0 : "Yes" + 1 : "No" + ] + */ + iTmp = IntForKey(mapent, "nocliphull"); + if (iTmp == 1) + { + g_noclip = true; + } + else + { + g_noclip = false; + } + Log("%30s [ %-9s ]\n", "Clipping Hull Generation", g_noclip ? "off" : "on"); +#ifdef HLCSG_PRECISIONCLIP + // cliptype(choices) : "Clip Hull Type" : 4 = [ 0 : "Smallest" 1 : "Normalized" 2: "Simple" 3 : "Precise" 4 : "Legacy" ] + iTmp = IntForKey(mapent, "cliptype"); + switch(iTmp) + { + case 0: + g_cliptype = clip_smallest; + break; + case 1: + g_cliptype = clip_normalized; + break; + case 2: + g_cliptype = clip_simple; + break; + case 3: + g_cliptype = clip_precise; + break; + default: + g_cliptype = clip_legacy; + break; + } + Log("%30s [ %-9s ]\n", "Clip Hull Type", GetClipTypeString(g_cliptype)); +#endif + /* + noskyclip(choices) : "No Sky Clip" : 0 = + [ + 1 : "On" + 0 : "Off" + ] + */ + iTmp = IntForKey(mapent, "noskyclip"); + if (iTmp == 1) + { + g_skyclip = false; + } + else + { + g_skyclip = true; + } + Log("%30s [ %-9s ]\n", "Sky brush clip generation", g_skyclip ? "on" : "off"); + + /////////////// + Log("\n"); +} +#endif + +#ifndef HLCSG_CUSTOMHULL +#ifdef HLCSG_PRECISIONCLIP +// ===================================================================================== +// FixBevelTextures +// ===================================================================================== + +void FixBevelTextures() +{ + for(int counter = 0; counter < g_numtexinfo; counter++) + { + if(g_texinfo[counter].flags & TEX_BEVEL) + { g_texinfo[counter].flags &= ~TEX_BEVEL; } + } +} +#endif +#endif + +// ===================================================================================== +// NewFaceFromFace +// Duplicates the non point information of a face, used by SplitFace +// ===================================================================================== +bface_t* NewFaceFromFace(const bface_t* const in) +{ + bface_t* newf; + + newf = (bface_t*)Alloc(sizeof(bface_t)); + + newf->contents = in->contents; + newf->texinfo = in->texinfo; + newf->planenum = in->planenum; + newf->plane = in->plane; +#ifdef HLCSG_EMPTYBRUSH + newf->backcontents = in->backcontents; +#endif + + return newf; +} + +// ===================================================================================== +// FreeFace +// ===================================================================================== +void FreeFace(bface_t* f) +{ + delete f->w; + Free(f); +} + +#ifndef HLCSG_NOFAKESPLITS +// ===================================================================================== +// ClipFace +// Clips a faces by a plane, returning the fragment on the backside and adding any +// fragment to the outside. +// Faces exactly on the plane will stay inside unless overdrawn by later brush. +// Frontside is the side of the plane that holds the outside list. +// Precedence is necesary to handle overlapping coplanar faces. +#define SPLIT_EPSILON 0.3 +// ===================================================================================== +static bface_t* ClipFace(bface_t* f, bface_t** outside, const int splitplane, const bool precedence) +{ + bface_t* front; // clip face + Winding* fw; // forward wind + Winding* bw; // back wind + plane_t* split; // plane to clip on + + // handle exact plane matches special + + if (f->planenum == (splitplane ^ 1)) + return f; // opposite side, so put on inside list + + if (f->planenum == splitplane) // coplanar + { + // this fragment will go to the inside, because + // the earlier one was clipped to the outside + if (precedence) + return f; + + f->next = *outside; + *outside = f; + return NULL; + } + + split = &g_mapplanes[splitplane]; + f->w->Clip(split->normal, split->dist, &fw, &bw); + + if (!fw) + { + delete bw; + return f; + } + else if (!bw) + { + delete fw; + f->next = *outside; + *outside = f; + return NULL; + } + else + { + delete f->w; + + front = NewFaceFromFace(f); + front->w = fw; + fw->getBounds(front->bounds); + front->next = *outside; + *outside = front; + + f->w = bw; + bw->getBounds(f->bounds); + + return f; + } +} +#endif + +// ===================================================================================== +// WriteFace +// ===================================================================================== +void WriteFace(const int hull, const bface_t* const f +#ifdef ZHLT_DETAILBRUSH + , int detaillevel +#endif + ) +{ + unsigned int i; + Winding* w; + + ThreadLock(); + if (!hull) + c_csgfaces++; + + // .p0 format + w = f->w; + + // plane summary +#ifdef ZHLT_DETAILBRUSH + fprintf (out[hull], "%i %i %i %i %u\n", detaillevel, f->planenum, f->texinfo, f->contents, w->m_NumPoints); +#else + fprintf(out[hull], "%i %i %i %u\n", f->planenum, f->texinfo, f->contents, w->m_NumPoints); +#endif + + // for each of the points on the face + for (i = 0; i < w->m_NumPoints; i++) + { + // write the co-ords +#ifdef HLCSG_PRICISION_FIX + fprintf(out[hull], "%5.8f %5.8f %5.8f\n", w->m_Points[i][0], w->m_Points[i][1], w->m_Points[i][2]); +#else + fprintf(out[hull], "%5.2f %5.2f %5.2f\n", w->m_Points[i][0], w->m_Points[i][1], w->m_Points[i][2]); +#endif + } + + // put in an extra line break + fprintf(out[hull], "\n"); +#ifdef HLCSG_VIEWSURFACE + if (g_viewsurface) + { + static bool side = false; + side = !side; + if (side) + { + vec3_t center, center2; + w->getCenter (center); + VectorAdd (center, f->plane->normal, center2); + fprintf (out_view[hull], "%5.2f %5.2f %5.2f\n", center2[0], center2[1], center2[2]); + for (i = 0; i < w->m_NumPoints; i++) + { + vec_t *p1, *p2; + p1 = w->m_Points[i]; + p2 = w->m_Points[(i+1)%w->m_NumPoints]; + fprintf (out_view[hull], "%5.2f %5.2f %5.2f\n", center[0], center[1], center[2]); + fprintf (out_view[hull], "%5.2f %5.2f %5.2f\n", p1[0], p1[1], p1[2]); + fprintf (out_view[hull], "%5.2f %5.2f %5.2f\n", p2[0], p2[1], p2[2]); + } + fprintf (out_view[hull], "%5.2f %5.2f %5.2f\n", center[0], center[1], center[2]); + fprintf (out_view[hull], "%5.2f %5.2f %5.2f\n", center2[0], center2[1], center2[2]); + } + } +#endif + + ThreadUnlock(); +} +#ifdef ZHLT_DETAILBRUSH +void WriteDetailBrush (int hull, const bface_t *faces) +{ + ThreadLock (); + fprintf (out_detailbrush[hull], "0\n"); + for (const bface_t *f = faces; f; f = f->next) + { + Winding *w = f->w; + fprintf (out_detailbrush[hull], "%i %u\n", f->planenum, w->m_NumPoints); + for (int i = 0; i < w->m_NumPoints; i++) + { + fprintf (out_detailbrush[hull], "%5.8f %5.8f %5.8f\n", w->m_Points[i][0], w->m_Points[i][1], w->m_Points[i][2]); + } + } + fprintf (out_detailbrush[hull], "-1 -1\n"); + ThreadUnlock (); +} +#endif + +// ===================================================================================== +// SaveOutside +// The faces remaining on the outside list are final polygons. Write them to the +// output file. +// Passable contents (water, lava, etc) will generate a mirrored copy of the face +// to be seen from the inside. +// ===================================================================================== +static void SaveOutside(const brush_t* const b, const int hull, bface_t* outside, const int mirrorcontents) +{ + bface_t* f; + bface_t* f2; + bface_t* next; + int i; + vec3_t temp; + + for (f = outside; f; f = next) + { + next = f->next; + +#ifdef HLCSG_EMPTYBRUSH + int frontcontents, backcontents; + int texinfo = f->texinfo; + const char *texname = GetTextureByNumber_CSG (texinfo); + frontcontents = f->contents; + if (mirrorcontents == CONTENTS_TOEMPTY) + { + backcontents = f->backcontents; + } + else + { + backcontents = mirrorcontents; + } + if (frontcontents == CONTENTS_TOEMPTY) + { + frontcontents = CONTENTS_EMPTY; + } + if (backcontents == CONTENTS_TOEMPTY) + { + backcontents = CONTENTS_EMPTY; + } + + bool frontnull, backnull; + frontnull = false; + backnull = false; + if (mirrorcontents == CONTENTS_TOEMPTY) + { + if (strncasecmp (texname, "SKIP", 4) && strncasecmp (texname, "HINT", 4) + #ifdef HLCSG_HLBSP_SOLIDHINT + && strncasecmp (texname, "SOLIDHINT", 9) + #endif + ) + // SKIP and HINT are special textures for hlbsp + { + backnull = true; + } + } + #ifdef HLCSG_HLBSP_SOLIDHINT + if (!strncasecmp (texname, "SOLIDHINT", 9)) + { + if (frontcontents != backcontents) + { + frontnull = backnull = true; // not discardable, so remove "SOLIDHINT" texture name and behave like NULL + } + } + #endif + #ifdef HLCSG_WATERBACKFACE_FIX + if (b->entitynum != 0 && !strncasecmp (texname, "!", 1)) + { + backnull = true; // strip water face on one side + } + #endif +#endif + +#ifdef HLCSG_EMPTYBRUSH + f->contents = frontcontents; + f->texinfo = frontnull? -1: texinfo; +#endif + if (f->w->getArea() < g_tiny_threshold) + { + c_tiny++; + Verbose("Entity %i, Brush %i: tiny fragment\n", +#ifdef HLCSG_COUNT_NEW + b->originalentitynum, b->originalbrushnum +#else + b->entitynum, b->brushnum +#endif + ); + continue; + } + + // count unique faces + if (!hull) + { + for (f2 = b->hulls[hull].faces; f2; f2 = f2->next) + { + if (f2->planenum == f->planenum) + { + if (!f2->used) + { + f2->used = true; + c_outfaces++; + } + break; + } + } + } + +#ifdef HLCSG_WARNBADTEXINFO + // check the texture alignment of this face + if (!hull) + { + int texinfo = f->texinfo; + const char *texname = GetTextureByNumber_CSG (texinfo); + texinfo_t *tex = &g_texinfo[texinfo]; + + if (texinfo != -1 // nullified textures (NULL, BEVEL, aaatrigger, etc.) + && !(tex->flags & TEX_SPECIAL) // sky + && strncasecmp (texname, "SKIP", 4) && strncasecmp (texname, "HINT", 4) // HINT and SKIP will be nullified only after hlbsp + #ifdef HLCSG_HLBSP_SOLIDHINT + && strncasecmp (texname, "SOLIDHINT", 9) + #endif + ) + { + // check for "Malformed face (%d) normal" + vec3_t texnormal; + CrossProduct (tex->vecs[1], tex->vecs[0], texnormal); + VectorNormalize (texnormal); + if (fabs (DotProduct (texnormal, f->plane->normal)) <= NORMAL_EPSILON) + { + Warning ("Entity %i, Brush %i: Malformed texture alignment (texture %s): Texture axis perpendicular to face.", + #ifdef HLCSG_COUNT_NEW + b->originalentitynum, b->originalbrushnum, + #else + b->entitynum, b->brushnum, + #endif + texname + ); + } + + // check for "Bad surface extents" + bool bad; + int i; + int j; + vec_t val; + + bad = false; + for (i = 0; i < f->w->m_NumPoints; i++) + { + for (j = 0; j < 2; j++) + { + val = DotProduct (f->w->m_Points[i], tex->vecs[j]) + tex->vecs[j][3]; + if (val < -99999 || val > 999999) + { + bad = true; + } + } + } + if (bad) + { + Warning ("Entity %i, Brush %i: Malformed texture alignment (texture %s): Bad surface extents.", + #ifdef HLCSG_COUNT_NEW + b->originalentitynum, b->originalbrushnum, + #else + b->entitynum, b->brushnum, + #endif + texname + ); + } + } + } + +#endif + WriteFace(hull, f +#ifdef ZHLT_DETAILBRUSH + , +#ifdef ZHLT_CLIPNODEDETAILLEVEL + (hull? b->clipnodedetaillevel: b->detaillevel) +#else + b->detaillevel +#endif +#endif + ); + + // if (mirrorcontents != CONTENTS_SOLID) + { + f->planenum ^= 1; + f->plane = &g_mapplanes[f->planenum]; +#ifdef HLCSG_EMPTYBRUSH + f->contents = backcontents; + f->texinfo = backnull? -1: texinfo; +#else + f->contents = mirrorcontents; +#endif + + // swap point orders + for (i = 0; i < f->w->m_NumPoints / 2; i++) // add points backwards + { + VectorCopy(f->w->m_Points[i], temp); + VectorCopy(f->w->m_Points[f->w->m_NumPoints - 1 - i], f->w->m_Points[i]); + VectorCopy(temp, f->w->m_Points[f->w->m_NumPoints - 1 - i]); + } + WriteFace(hull, f +#ifdef ZHLT_DETAILBRUSH + , +#ifdef ZHLT_CLIPNODEDETAILLEVEL + (hull? b->clipnodedetaillevel: b->detaillevel) +#else + b->detaillevel +#endif +#endif + ); + } + + FreeFace(f); + } +} + +// ===================================================================================== +// CopyFace +// ===================================================================================== +bface_t* CopyFace(const bface_t* const f) +{ + bface_t* n; + + n = NewFaceFromFace(f); + n->w = f->w->Copy(); + n->bounds = f->bounds; + return n; +} + +// ===================================================================================== +// CopyFaceList +// ===================================================================================== +bface_t* CopyFaceList(bface_t* f) +{ + bface_t* head; + bface_t* n; + + if (f) + { + head = CopyFace(f); + n = head; + f = f->next; + + while (f) + { + n->next = CopyFace(f); + + n = n->next; + f = f->next; + } + + return head; + } + else + { + return NULL; + } +} + +// ===================================================================================== +// FreeFaceList +// ===================================================================================== +void FreeFaceList(bface_t* f) +{ + if (f) + { + if (f->next) + { + FreeFaceList(f->next); + } + FreeFace(f); + } +} + +// ===================================================================================== +// CopyFacesToOutside +// Make a copy of all the faces of the brush, so they can be chewed up by other +// brushes. +// All of the faces start on the outside list. +// As other brushes take bites out of the faces, the fragments are moved to the +// inside list, so they can be freed when they are determined to be completely +// enclosed in solid. +// ===================================================================================== +static bface_t* CopyFacesToOutside(brushhull_t* bh) +{ + bface_t* f; + bface_t* newf; + bface_t* outside; + + outside = NULL; + + for (f = bh->faces; f; f = f->next) + { + newf = CopyFace(f); + newf->w->getBounds(newf->bounds); + newf->next = outside; + outside = newf; + } + + return outside; +} + +// ===================================================================================== +// CSGBrush +// ===================================================================================== +#ifdef ZHLT_DETAILBRUSH +extern const char *ContentsToString (const contents_t type); +#endif +static void CSGBrush(int brushnum) +{ + int hull; + brush_t* b1; + brush_t* b2; + brushhull_t* bh1; + brushhull_t* bh2; + int bn; + bool overwrite; + bface_t* f; + bface_t* f2; + bface_t* next; +#ifndef HLCSG_NOFAKESPLITS + bface_t* fcopy; +#endif + bface_t* outside; +#ifndef HLCSG_NOFAKESPLITS + bface_t* oldoutside; +#endif + entity_t* e; + vec_t area; + + // get entity and brush info from the given brushnum that we can work with + b1 = &g_mapbrushes[brushnum]; + e = &g_entities[b1->entitynum]; + + // for each of the hulls + for (hull = 0; hull < NUM_HULLS; hull++) + { + bh1 = &b1->hulls[hull]; +#ifdef ZHLT_DETAILBRUSH + if (bh1->faces && +#ifdef ZHLT_CLIPNODEDETAILLEVEL + (hull? b1->clipnodedetaillevel: b1->detaillevel) +#else + b1->detaillevel +#endif + ) + { + switch (b1->contents) + { + case CONTENTS_ORIGIN: + #ifdef HLCSG_HLBSP_CUSTOMBOUNDINGBOX + case CONTENTS_BOUNDINGBOX: + #endif + case CONTENTS_HINT: + #ifdef HLCSG_EMPTYBRUSH + case CONTENTS_TOEMPTY: + #endif + break; + default: + Error ("Entity %i, Brush %i: %s brushes not allowed in detail\n", + #ifdef HLCSG_COUNT_NEW + b1->originalentitynum, b1->originalbrushnum, + #else + b1->entitynum, b1->brushnum, + #endif + ContentsToString((contents_t)b1->contents)); + break; + case CONTENTS_SOLID: + WriteDetailBrush (hull, bh1->faces); + break; + } + } +#endif + + // set outside to a copy of the brush's faces + outside = CopyFacesToOutside(bh1); + overwrite = false; +#ifdef HLCSG_EMPTYBRUSH + if (b1->contents == CONTENTS_TOEMPTY) + { + for (f = outside; f; f = f->next) + { + f->contents = CONTENTS_TOEMPTY; + f->backcontents = CONTENTS_TOEMPTY; + } + } +#endif + + // for each brush in entity e + for (bn = 0; bn < e->numbrushes; bn++) + { + // see if b2 needs to clip a chunk out of b1 +#ifdef HLCSG_CSGBrush_BRUSHNUM_FIX + if (e->firstbrush + bn == brushnum) + { + continue; + } + overwrite = e->firstbrush + bn > brushnum; +#else + if (bn == brushnum) + { + overwrite = true; // later brushes now overwrite + continue; + } +#endif + + b2 = &g_mapbrushes[e->firstbrush + bn]; + bh2 = &b2->hulls[hull]; +#ifdef HLCSG_EMPTYBRUSH + if (b2->contents == CONTENTS_TOEMPTY) + continue; +#endif +#ifdef ZHLT_DETAILBRUSH + if ( +#ifdef ZHLT_CLIPNODEDETAILLEVEL + (hull? (b2->clipnodedetaillevel - 0 > b1->clipnodedetaillevel + 0): (b2->detaillevel - b2->chopdown > b1->detaillevel + b1->chopup)) +#else + b2->detaillevel - b2->chopdown > b1->detaillevel + b1->chopup +#endif + ) + continue; // you can't chop + if (b2->contents == b1->contents && +#ifdef ZHLT_CLIPNODEDETAILLEVEL + (hull? (b2->clipnodedetaillevel != b1->clipnodedetaillevel): (b2->detaillevel != b1->detaillevel)) +#else + b2->detaillevel != b1->detaillevel +#endif + ) + { + overwrite = +#ifdef ZHLT_CLIPNODEDETAILLEVEL + (hull? (b2->clipnodedetaillevel < b1->clipnodedetaillevel): (b2->detaillevel < b1->detaillevel)) +#else + b2->detaillevel < b1->detaillevel +#endif + ; + } +#endif +#ifdef HLCSG_COPLANARPRIORITY + if (b2->contents == b1->contents + && hull == 0 && b2->detaillevel == b1->detaillevel + && b2->coplanarpriority != b1->coplanarpriority) + { + overwrite = b2->coplanarpriority > b1->coplanarpriority; + } +#endif + + if (!bh2->faces) + continue; // brush isn't in this hull + + // check brush bounding box first + // TODO: use boundingbox method instead + if (bh1->bounds.testDisjoint(bh2->bounds)) + { + continue; + } + + // divide faces by the planes of the b2 to find which + // fragments are inside + + f = outside; + outside = NULL; + for (; f; f = next) + { + next = f->next; + + // check face bounding box first + if (bh2->bounds.testDisjoint(f->bounds)) + { // this face doesn't intersect brush2's bbox + f->next = outside; + outside = f; + continue; + } +#ifdef ZHLT_DETAILBRUSH + if ( +#ifdef ZHLT_CLIPNODEDETAILLEVEL + (hull? (b2->clipnodedetaillevel > b1->clipnodedetaillevel): (b2->detaillevel > b1->detaillevel)) +#else + b2->detaillevel > b1->detaillevel +#endif + ) + { + const char *texname = GetTextureByNumber_CSG (f->texinfo); + if (f->texinfo == -1 || !strncasecmp (texname, "SKIP", 4) || !strncasecmp (texname, "HINT", 4) + #ifdef HLCSG_HLBSP_SOLIDHINT + || !strncasecmp (texname, "SOLIDHINT", 9) + #endif + ) + { + // should not nullify the fragment inside detail brush + f->next = outside; + outside = f; + continue; + } + } +#endif + +#ifndef HLCSG_NOFAKESPLITS + oldoutside = outside; + fcopy = CopyFace(f); // save to avoid fake splits +#endif + + // throw pieces on the front sides of the planes + // into the outside list, return the remains on the inside +#ifdef HLCSG_NOFAKESPLITS + // find the fragment inside brush2 + Winding *w = new Winding (*f->w); + for (f2 = bh2->faces; f2; f2 = f2->next) + { + if (f->planenum == f2->planenum) + { + if (!overwrite) + { + // face plane is outside brush2 + w->m_NumPoints = 0; + break; + } + else + { + continue; + } + } + if (f->planenum == (f2->planenum ^ 1)) + { + continue; + } + Winding *fw; + Winding *bw; + w->Clip (f2->plane->normal, f2->plane->dist, &fw, &bw); + if (fw) + { + delete fw; + } + if (bw) + { + delete w; + w = bw; + } + else + { + w->m_NumPoints = 0; + break; + } + } + // do real split + if (w->m_NumPoints) + { + for (f2 = bh2->faces; f2; f2 = f2->next) + { + if (f->planenum == f2->planenum || f->planenum == (f2->planenum ^ 1)) + { + continue; + } + int valid = 0; + int x; + for (x = 0; x < w->m_NumPoints; x++) + { + vec_t dist = DotProduct (w->m_Points[x], f2->plane->normal) - f2->plane->dist; + if (dist >= -ON_EPSILON*4) // only estimate + { + valid++; + } + } + if (valid >= 2) + { // this splitplane forms an edge + Winding *fw; + Winding *bw; + f->w->Clip (f2->plane->normal, f2->plane->dist, &fw, &bw); + if (fw) + { + bface_t *front = NewFaceFromFace (f); + front->w = fw; + fw->getBounds (front->bounds); + front->next = outside; + outside = front; + } + if (bw) + { + delete f->w; + f->w = bw; + bw->getBounds (f->bounds); + } + else + { + FreeFace (f); + f = NULL; + break; + } + } + } + } + else + { + f->next = outside; + outside = f; + f = NULL; + } + delete w; +#else + for (f2 = bh2->faces; f2 && f; f2 = f2->next) + { + f = ClipFace(f, &outside, f2->planenum, overwrite); + } +#endif + + area = f ? f->w->getArea() : 0; + if (f && area < g_tiny_threshold) + { + Verbose("Entity %i, Brush %i: tiny penetration\n", +#ifdef HLCSG_COUNT_NEW + b1->originalentitynum, b1->originalbrushnum +#else + b1->entitynum, b1->brushnum +#endif + ); + c_tiny_clip++; + FreeFace(f); + f = NULL; + } + if (f) + { + // there is one convex fragment of the original + // face left inside brush2 +#ifndef HLCSG_NOFAKESPLITS + FreeFace(fcopy); +#endif + +#ifdef ZHLT_DETAILBRUSH + if ( +#ifdef ZHLT_CLIPNODEDETAILLEVEL + (hull? (b2->clipnodedetaillevel > b1->clipnodedetaillevel): (b2->detaillevel > b1->detaillevel)) +#else + b2->detaillevel > b1->detaillevel +#endif + ) + { // don't chop or set contents, only nullify + f->next = outside; + outside = f; + f->texinfo = -1; + continue; + } + if ( +#ifdef ZHLT_CLIPNODEDETAILLEVEL + (hull? b2->clipnodedetaillevel < b1->clipnodedetaillevel: b2->detaillevel < b1->detaillevel) +#else + b2->detaillevel < b1->detaillevel +#endif + && b2->contents == CONTENTS_SOLID) + { // real solid + FreeFace (f); + continue; + } +#endif +#ifdef HLCSG_EMPTYBRUSH + if (b1->contents == CONTENTS_TOEMPTY) + { + bool onfront = true, onback = true; + for (f2 = bh2->faces; f2; f2 = f2->next) + { + if (f->planenum == (f2->planenum ^ 1)) + onback = false; + if (f->planenum == f2->planenum) + onfront = false; + } + if (onfront && f->contents < b2->contents) + f->contents = b2->contents; + if (onback && f->backcontents < b2->contents) + f->backcontents = b2->contents; + if (f->contents == CONTENTS_SOLID && f->backcontents == CONTENTS_SOLID +#ifdef HLCSG_HLBSP_SOLIDHINT + && strncasecmp (GetTextureByNumber_CSG (f->texinfo), "SOLIDHINT", 9) +#endif + ) + { + FreeFace (f); + } + else + { + f->next = outside; + outside = f; + } + continue; + } +#endif + if (b1->contents > b2->contents +#ifdef HLCSG_HLBSP_SOLIDHINT + || b1->contents == b2->contents && !strncasecmp (GetTextureByNumber_CSG (f->texinfo), "SOLIDHINT", 9) +#endif + ) + { // inside a water brush + f->contents = b2->contents; + f->next = outside; + outside = f; + } + else // inside a solid brush + { + FreeFace(f); // throw it away + } + } +#ifndef HLCSG_NOFAKESPLITS + else + { // the entire thing was on the outside, even + // though the bounding boxes intersected, + // which will never happen with axial planes + + // free the fragments chopped to the outside + while (outside != oldoutside) + { + f2 = outside->next; + FreeFace(outside); + outside = f2; + } + + // revert to the original face to avoid + // unneeded false cuts + fcopy->next = outside; + outside = fcopy; + } +#endif + } + + } + + // all of the faces left in outside are real surface faces + SaveOutside(b1, hull, outside, b1->contents); + } +} + +// +// ===================================================================================== +// + +// ===================================================================================== +// EmitPlanes +// ===================================================================================== +static void EmitPlanes() +{ + int i; + dplane_t* dp; + plane_t* mp; + + g_numplanes = g_nummapplanes; + mp = g_mapplanes; + dp = g_dplanes; +#ifdef HLCSG_HLBSP_DOUBLEPLANE + { + char name[_MAX_PATH]; + safe_snprintf (name, _MAX_PATH, "%s.pln", g_Mapname); + FILE *planeout = fopen (name, "wb"); + if (!planeout) + Error("Couldn't open %s", name); + SafeWrite (planeout, g_mapplanes, g_nummapplanes * sizeof (plane_t)); + fclose (planeout); + } +#endif + for (i = 0; i < g_nummapplanes; i++, mp++, dp++) + { + //if (!(mp->redundant)) + //{ + // Log("EmitPlanes: plane %i non redundant\n", i); + VectorCopy(mp->normal, dp->normal); + dp->dist = mp->dist; + dp->type = mp->type; + // } + //else + // { + // Log("EmitPlanes: plane %i redundant\n", i); + // } + } +} + +// ===================================================================================== +// SetModelNumbers +// blah +// ===================================================================================== +static void SetModelNumbers() +{ + int i; + int models; + char value[10]; + + models = 1; + for (i = 1; i < g_numentities; i++) + { + if (g_entities[i].numbrushes) + { + safe_snprintf(value, sizeof(value), "*%i", models); + models++; + SetKeyValue(&g_entities[i], "model", value); + } + } +} + +#ifdef HLCSG_COPYMODELKEYVALUE +void ReuseModel () +{ + int i; + for (i = g_numentities - 1; i >= 1; i--) // so it won't affect the remaining entities in the loop when we move this entity backward + { + const char *name = ValueForKey (&g_entities[i], "zhlt_usemodel"); + if (!*name) + { + continue; + } + int j; + for (j = 1; j < g_numentities; j++) + { + if (*ValueForKey (&g_entities[j], "zhlt_usemodel")) + { + continue; + } + if (!strcmp (name, ValueForKey (&g_entities[j], "targetname"))) + { + break; + } + } + if (j == g_numentities) + { + if (!strcasecmp (name, "null")) + { + SetKeyValue (&g_entities[i], "model", ""); + continue; + } + Error ("zhlt_usemodel: can not find target entity '%s', or that entity is also using 'zhlt_usemodel'.\n", name); + } + SetKeyValue (&g_entities[i], "model", ValueForKey (&g_entities[j], "model")); + if (j > i) + { + // move this entity backward + // to prevent precache error in case of .mdl/.spr and wrong result of EntityForModel in case of map model + entity_t tmp; + tmp = g_entities[i]; + memmove (&g_entities[i], &g_entities[i + 1], ((j + 1) - (i + 1)) * sizeof (entity_t)); + g_entities[j] = tmp; + } + } +} +#endif + +// ===================================================================================== +// SetLightStyles +// ===================================================================================== +#define MAX_SWITCHED_LIGHTS 32 +#define MAX_LIGHTTARGETS_NAME 64 + +static void SetLightStyles() +{ + int stylenum; + const char* t; + entity_t* e; + int i, j; + char value[10]; + char lighttargets[MAX_SWITCHED_LIGHTS][MAX_LIGHTTARGETS_NAME]; + +#ifdef ZHLT_TEXLIGHT + bool newtexlight = false; +#endif + + // any light that is controlled (has a targetname) + // must have a unique style number generated for it + + stylenum = 0; + for (i = 1; i < g_numentities; i++) + { + e = &g_entities[i]; + + t = ValueForKey(e, "classname"); + if (strncasecmp(t, "light", 5)) + { +#ifdef ZHLT_TEXLIGHT + //LRC: + // if it's not a normal light entity, allocate it a new style if necessary. + t = ValueForKey(e, "style"); + switch (atoi(t)) + { + case 0: // not a light, no style, generally pretty boring + continue; + case -1: // normal switchable texlight + safe_snprintf(value, sizeof(value), "%i", 32 + stylenum); + SetKeyValue(e, "style", value); + stylenum++; + continue; + case -2: // backwards switchable texlight + safe_snprintf(value, sizeof(value), "%i", -(32 + stylenum)); + SetKeyValue(e, "style", value); + stylenum++; + continue; + case -3: // (HACK) a piggyback texlight: switched on and off by triggering a real light that has the same name + SetKeyValue(e, "style", "0"); // just in case the level designer didn't give it a name + newtexlight = true; + // don't 'continue', fall out + } + //LRC (ends) +#else + continue; +#endif + } + t = ValueForKey(e, "targetname"); +#ifdef HLCSG_STYLEHACK + if (*ValueForKey (e, "zhlt_usestyle")) + { + t = ValueForKey(e, "zhlt_usestyle"); + if (!strcasecmp (t, "null")) + { + t = ""; + } + } +#endif + if (!t[0]) + { + continue; + } + + // find this targetname + for (j = 0; j < stylenum; j++) + { + if (!strcmp(lighttargets[j], t)) + { + break; + } + } + if (j == stylenum) + { + hlassume(stylenum < MAX_SWITCHED_LIGHTS, assume_MAX_SWITCHED_LIGHTS); + safe_strncpy(lighttargets[j], t, MAX_LIGHTTARGETS_NAME); + stylenum++; + } + safe_snprintf(value, sizeof(value), "%i", 32 + j); + SetKeyValue(e, "style", value); + } + +} + +// ===================================================================================== +// ConvertHintToEmtpy +// ===================================================================================== +static void ConvertHintToEmpty() +{ + int i; + + // Convert HINT brushes to EMPTY after they have been carved by csg + for (i = 0; i < MAX_MAP_BRUSHES; i++) + { + if (g_mapbrushes[i].contents == CONTENTS_HINT) + { + g_mapbrushes[i].contents = CONTENTS_EMPTY; + } + } +} + +// ===================================================================================== +// WriteBSP +// ===================================================================================== +#ifdef HLCSG_ONLYENTS_NOWADCHANGE +void LoadWadValue () +{ + char *wadvalue; + ParseFromMemory (g_dentdata, g_entdatasize); + epair_t *e; + entity_t ent0; + entity_t *mapent = &ent0; + memset (mapent, 0, sizeof (entity_t)); + if (!GetToken (true)) + { + wadvalue = strdup (""); + } + else + { + if (strcmp (g_token, "{")) + { + Error ("ParseEntity: { not found"); + } + while (1) + { + if (!GetToken (true)) + { + Error ("ParseEntity: EOF without closing brace"); + } + if (!strcmp (g_token, "}")) + { + break; + } + e = ParseEpair (); + e->next = mapent->epairs; + mapent->epairs = e; + } + wadvalue = strdup (ValueForKey (mapent, "wad")); + epair_t *next; + for (e = mapent->epairs; e; e = next) + { + next = e->next; + free (e->key); + free (e->value); + free (e); + } + } + if (*wadvalue) + { + Log ("Wad files required to run the map: \"%s\"\n", wadvalue); + } + else + { + Log ("Wad files required to run the map: (None)\n"); + } + SetKeyValue (&g_entities[0], "wad", wadvalue); + free (wadvalue); +} +#endif +void WriteBSP(const char* const name) +{ + char path[_MAX_PATH]; + +#ifdef ZHLT_DEFAULTEXTENSION_FIX + safe_snprintf(path, _MAX_PATH, "%s.bsp", name); +#else + safe_strncpy(path, name, _MAX_PATH); + DefaultExtension(path, ".bsp"); +#endif + + SetModelNumbers(); +#ifdef HLCSG_COPYMODELKEYVALUE + ReuseModel(); +#endif + SetLightStyles(); + + if (!g_onlyents) + WriteMiptex(); +#ifdef HLCSG_ONLYENTS_NOWADCHANGE + if (g_onlyents) + { + LoadWadValue (); + } +#endif + + UnparseEntities(); + ConvertHintToEmpty(); // this is ridiculous. --vluzacn +#ifdef HLCSG_CHART_FIX + if (g_chart) + PrintBSPFileSizes(); +#endif + WriteBSPFile(path); +} + +// +// ===================================================================================== +// + +// AJM: added in function +// ===================================================================================== +// CopyGenerictoCLIP +// clips a generic brush +// ===================================================================================== +/*static void CopyGenerictoCLIP(const brush_t* const b) +{ + // code blatently ripped from CopySKYtoCLIP() + + int i; + entity_t* mapent; + brush_t* newbrush; + + mapent = &g_entities[b->entitynum]; + mapent->numbrushes++; + + newbrush = &g_mapbrushes[g_nummapbrushes]; +#ifdef HLCSG_COUNT_NEW + newbrush->originalentitynum = b->originalentitynum; + newbrush->originalbrushnum = b->originalbrushnum; +#endif + newbrush->entitynum = b->entitynum; + newbrush->brushnum = g_nummapbrushes - mapent->firstbrush; + newbrush->firstside = g_numbrushsides; + newbrush->numsides = b->numsides; + newbrush->contents = CONTENTS_CLIP; + newbrush->noclip = 0; + + for (i = 0; i < b->numsides; i++) + { + int j; + + side_t* side = &g_brushsides[g_numbrushsides]; + + *side = g_brushsides[b->firstside + i]; + safe_strncpy(side->td.name, "CLIP", sizeof(side->td.name)); + + for (j = 0; j < NUM_HULLS; j++) + { + newbrush->hulls[j].faces = NULL; + newbrush->hulls[j].bounds = b->hulls[j].bounds; + } + + g_numbrushsides++; + hlassume(g_numbrushsides < MAX_MAP_SIDES, assume_MAX_MAP_SIDES); + } + + g_nummapbrushes++; + hlassume(g_nummapbrushes < MAX_MAP_BRUSHES, assume_MAX_MAP_BRUSHES); +}*/ + +#ifdef HLCSG_CLIPECONOMY +// AJM: added in +unsigned int BrushClipHullsDiscarded = 0; +unsigned int ClipNodesDiscarded = 0; + +//AJM: added in function +static void MarkEntForNoclip(entity_t* ent) +{ + int i; + brush_t* b; + + for (i = ent->firstbrush; i < ent->firstbrush + ent->numbrushes; i++) + { + b = &g_mapbrushes[i]; + b->noclip = 1; + + BrushClipHullsDiscarded++; + ClipNodesDiscarded += b->numsides; + } +} + +// AJM +// ===================================================================================== +// CheckForNoClip +// marks the noclip flag on any brushes that dont need clipnode generation, eg. func_illusionaries +// ===================================================================================== +static void CheckForNoClip() +{ + int i; + entity_t* ent; + + char entclassname[MAX_KEY]; + int spawnflags; +#ifdef HLCSG_CUSTOMHULL + int count = 0; +#endif + + if (!g_bClipNazi) + return; // NO CLIP FOR YOU!!! + + for (i = 0; i < g_numentities; i++) + { + if (!g_entities[i].numbrushes) + continue; // not a model + + if (!i) + continue; // dont waste our time with worldspawn + + ent = &g_entities[i]; + + strcpy_s(entclassname, ValueForKey(ent, "classname")); + spawnflags = atoi(ValueForKey(ent, "spawnflags")); + int skin = IntForKey(ent, "skin"); //vluzacn + +#ifndef HLCSG_CUSTOMHULL + // condition 0, it's marked noclip (KGP) + if(strlen(ValueForKey(ent,"zhlt_noclip")) && strcmp(ValueForKey(ent,"zhlt_noclip"),"0")) + { + MarkEntForNoclip(ent); + } + // condition 1 //modified. --vluzacn + else +#endif + if ((skin != -16) && + ( + !strcmp(entclassname, "env_bubbles") + || !strcmp(entclassname, "func_illusionary") + || (spawnflags & 8) && + ( /* NOTE: func_doors as far as i can tell may need clipnodes for their + player collision detection, so for now, they stay out of it. */ + !strcmp(entclassname, "func_train") + || !strcmp(entclassname, "func_door") + || !strcmp(entclassname, "func_water") + || !strcmp(entclassname, "func_door_rotating") + || !strcmp(entclassname, "func_pendulum") + || !strcmp(entclassname, "func_train") + || !strcmp(entclassname, "func_tracktrain") + || !strcmp(entclassname, "func_vehicle") + ) + || (skin != 0) && (!strcmp(entclassname, "func_door") || !strcmp(entclassname, "func_water")) + || (spawnflags & 2) && (!strcmp(entclassname, "func_conveyor")) + || (spawnflags & 1) && (!strcmp(entclassname, "func_rot_button")) + || (spawnflags & 64) && (!strcmp(entclassname, "func_rotating")) + )) + { + MarkEntForNoclip(ent); +#ifdef HLCSG_CUSTOMHULL + count++; +#endif + } + /* + // condition 6: its a func_wall, while we noclip it, we remake the clipnodes manually + else if (!strncasecmp(entclassname, "func_wall", 9)) + { + for (int j = ent->firstbrush; j < ent->firstbrush + ent->numbrushes; j++) + CopyGenerictoCLIP(&g_mapbrushes[i]); + + MarkEntForNoclip(ent); + } +*/ + } + +#ifdef HLCSG_CUSTOMHULL + Log("%i entities discarded from clipping hulls\n", count); +#else + Log("%i brushes (totalling %i sides) discarded from clipping hulls\n", BrushClipHullsDiscarded, ClipNodesDiscarded); +#endif +} +#endif + +// ===================================================================================== +// ProcessModels +// ===================================================================================== +#ifndef HLCSG_SORTBRUSH_FIX +#define NUM_TYPECONTENTS 5 // AJM: should reflect the number of values below +int typecontents[NUM_TYPECONTENTS] = { + CONTENTS_WATER, CONTENTS_SLIME, CONTENTS_LAVA, CONTENTS_SKY, CONTENTS_HINT +}; +#endif + + +static void ProcessModels() +{ + int i, j, type; + int placed; + int first, contents; + brush_t temp; + + for (i = 0; i < g_numentities; i++) + { + if (!g_entities[i].numbrushes) // only models + continue; + + // sort the contents down so stone bites water, etc + first = g_entities[i].firstbrush; +#ifdef HLCSG_SORTBRUSH_KEEPORDER + brush_t *temps = (brush_t *)malloc (g_entities[i].numbrushes * sizeof (brush_t)); + hlassume (temps, assume_NoMemory); + for (j = 0; j < g_entities[i].numbrushes; j++) + { + temps[j] = g_mapbrushes[first + j]; + } + int placedcontents; + bool b_placedcontents = false; + for (placed = 0; placed < g_entities[i].numbrushes; ) + { + bool b_contents = false; + for (j = 0; j < g_entities[i].numbrushes; j++) + { + brush_t *brush = &temps[j]; + if (b_placedcontents && brush->contents <= placedcontents) + continue; + if (b_contents && brush->contents >= contents) + continue; + b_contents = true; + contents = brush->contents; + } + for (j = 0; j < g_entities[i].numbrushes; j++) + { + brush_t *brush = &temps[j]; + if (brush->contents == contents) + { + g_mapbrushes[first + placed] = *brush; + placed++; + } + } + b_placedcontents = true; + placedcontents = contents; + } + free (temps); +#else +#ifdef HLCSG_SORTBRUSH_FIX + placed = 0; + while (placed < g_entities[i].numbrushes) + { + for (j = placed; j < g_entities[i].numbrushes; j++) + { + if (j == placed) + { + contents = g_mapbrushes[first + j].contents; + } + else + { + contents = qmin(g_mapbrushes[first + j].contents, contents); + } + } + for (j = placed; j < g_entities[i].numbrushes; j++) + { + if (g_mapbrushes[first + j].contents == contents) + { + temp = g_mapbrushes[first + placed]; + g_mapbrushes[first + placed] = g_mapbrushes[first + j]; + g_mapbrushes[first + j] = temp; + placed++; + } + } + } +#else + placed = 0; + for (type = 0; type < NUM_TYPECONTENTS; type++) // for each of the contents types + { + contents = typecontents[type]; + for (j = placed + 1; j < g_entities[i].numbrushes; j++) // for each of the model's brushes + { + // if this brush is of the contents type in this for iteration + if (g_mapbrushes[first + j].contents == contents) + { + temp = g_mapbrushes[first + placed]; + g_mapbrushes[first + placed] = g_mapbrushes[j]; + g_mapbrushes[j] = temp; + placed++; + } + } + } +#endif +#endif + + // csg them in order + if (i == 0) // if its worldspawn.... + { + NamedRunThreadsOnIndividual(g_entities[i].numbrushes, g_estimate, CSGBrush); + CheckFatal(); + } + else + { + for (j = 0; j < g_entities[i].numbrushes; j++) + { + CSGBrush(first + j); + } + } + + // write end of model marker + for (j = 0; j < NUM_HULLS; j++) + { +#ifdef ZHLT_DETAILBRUSH + fprintf (out[j], "-1 -1 -1 -1 -1\n"); + fprintf (out_detailbrush[j], "-1\n"); +#else + fprintf(out[j], "-1 -1 -1 -1\n"); +#endif + } + } +} + +// ===================================================================================== +// SetModelCenters +// ===================================================================================== +static void SetModelCenters(int entitynum) +{ + int i; + int last; + char string[MAXTOKEN]; + entity_t* e = &g_entities[entitynum]; + BoundingBox bounds; + vec3_t center; + + if ((entitynum == 0) || (e->numbrushes == 0)) // skip worldspawn and point entities + return; + + if (!*ValueForKey(e, "light_origin")) // skip if its not a zhlt_flags light_origin + return; + + for (i = e->firstbrush, last = e->firstbrush + e->numbrushes; i < last; i++) + { + if (g_mapbrushes[i].contents != CONTENTS_ORIGIN +#ifdef HLCSG_HLBSP_CUSTOMBOUNDINGBOX + && g_mapbrushes[i].contents != CONTENTS_BOUNDINGBOX +#endif + ) + { + bounds.add(g_mapbrushes[i].hulls->bounds); + } + } + + VectorAdd(bounds.m_Mins, bounds.m_Maxs, center); + VectorScale(center, 0.5, center); + + safe_snprintf(string, MAXTOKEN, "%i %i %i", (int)center[0], (int)center[1], (int)center[2]); + SetKeyValue(e, "model_center", string); +} + +// +// ===================================================================================== +// + +// ===================================================================================== +// BoundWorld +// ===================================================================================== +static void BoundWorld() +{ + int i; + brushhull_t* h; + + world_bounds.reset(); + + for (i = 0; i < g_nummapbrushes; i++) + { + h = &g_mapbrushes[i].hulls[0]; + if (!h->faces) + { + continue; + } + world_bounds.add(h->bounds); + } + + Verbose("World bounds: (%i %i %i) to (%i %i %i)\n", + (int)world_bounds.m_Mins[0], (int)world_bounds.m_Mins[1], (int)world_bounds.m_Mins[2], + (int)world_bounds.m_Maxs[0], (int)world_bounds.m_Maxs[1], (int)world_bounds.m_Maxs[2]); +} + +// ===================================================================================== +// Usage +// prints out usage sheet +// ===================================================================================== +static void Usage() +{ + Banner(); // TODO: Call banner from main CSG process? + + Log("\n-= %s Options =-\n\n", g_Program); +#ifdef ZHLT_CONSOLE + Log(" -console # : Set to 0 to turn off the pop-up console (default is 1)\n"); +#endif +#ifdef ZHLT_LANGFILE + Log(" -lang file : localization file\n"); +#endif + Log(" -nowadtextures : include all used textures into bsp\n"); + Log(" -wadinclude file : place textures used from wad specified into bsp\n"); + Log(" -noclip : don't create clipping hull\n"); + +#ifdef HLCSG_CLIPECONOMY // AJM +#ifdef HLCSG_CUSTOMHULL // default clip economy off + Log(" -clipeconomy : turn clipnode economy mode on\n"); +#else + Log(" -noclipeconomy : turn clipnode economy mode off\n"); +#endif +#endif + +#ifdef HLCSG_PRECISIONCLIP // KGP + Log(" -cliptype value : set to smallest, normalized, simple, precise, or legacy (default)\n"); +#endif +#ifdef HLCSG_NULLIFY_INVISIBLE // KGP + Log(" -nullfile file : specify list of entities to retexture with NULL\n"); +#endif + + Log(" -onlyents : do an entity update from .map to .bsp\n"); + Log(" -noskyclip : disable automatic clipping of SKY brushes\n"); + Log(" -tiny # : minmum brush face surface area before it is discarded\n"); + Log(" -brushunion # : threshold to warn about overlapping brushes\n\n"); + Log(" -hullfile file : Reads in custom collision hull dimensions\n"); +#ifdef HLCSG_WADCFG_NEW + Log(" -wadcfgfile file : wad configuration file\n"); + Log(" -wadconfig name : use the old wad configuration approach (select a group from wad.cfg)\n"); +#endif + Log(" -texdata # : Alter maximum texture memory limit (in kb)\n"); + Log(" -lightdata # : Alter maximum lighting memory limit (in kb)\n"); + Log(" -chart : display bsp statitics\n"); + Log(" -low | -high : run program an altered priority level\n"); + Log(" -nolog : don't generate the compile logfiles\n"); +#ifdef HLCSG_KEEPLOG + Log(" -noresetlog : Do not delete log file\n"); +#endif + Log(" -threads # : manually specify the number of threads to run\n"); +#ifdef SYSTEM_WIN32 + Log(" -estimate : display estimated time during compile\n"); +#endif +#ifdef ZHLT_PROGRESSFILE // AJM + Log(" -progressfile path : specify the path to a file for progress estimate output\n"); +#endif +#ifdef SYSTEM_POSIX + Log(" -noestimate : do not display continuous compile time estimates\n"); +#endif + Log(" -verbose : compile with verbose messages\n"); + Log(" -noinfo : Do not show tool configuration information\n"); + +#ifdef ZHLT_NULLTEX // AJM + Log(" -nonulltex : Turns off null texture stripping\n"); +#endif +#ifdef HLCSG_NULLIFYAAATRIGGER + Log(" -nonullifytrigger: don't remove 'aaatrigger' texture\n"); +#endif + +#ifdef ZHLT_DETAIL // AJM + Log(" -nodetail : dont handle detail brushes\n"); +#endif + +#ifdef HLCSG_OPTIMIZELIGHTENTITY + Log(" -nolightopt : don't optimize engine light entities\n"); +#endif + +#ifdef HLCSG_GAMETEXTMESSAGE_UTF8 + Log(" -notextconvert : don't convert game_text message from Windows ANSI to UTF8 format\n"); +#endif + + Log(" -dev # : compile with developer message\n\n"); + +#ifndef HLCSG_WADCFG_NEW +#ifdef HLCSG_WADCFG // AJM + Log(" -wadconfig name : Specify a configuration to use from wad.cfg\n"); + Log(" -wadcfgfile path : Manually specify a path to the wad.cfg file\n"); //JK: +#endif +#endif + +#ifdef HLCSG_AUTOWAD // AJM: + Log(" -wadautodetect : Force auto-detection of wadfiles\n"); +#endif + +#ifdef HLCSG_SCALESIZE + Log(" -scale # : Scale the world. Use at your own risk.\n"); +#endif + Log(" mapfile : The mapfile to compile\n\n"); + + exit(1); +} + +// ===================================================================================== +// DumpWadinclude +// prints out the wadinclude list +// ===================================================================================== +static void DumpWadinclude() +{ + Log("Wadinclude list :\n"); + WadInclude_i it; + for (it = g_WadInclude.begin(); it != g_WadInclude.end(); it++) + { + Log("[%s]\n", it->c_str()); + } +} + +// ===================================================================================== +// Settings +// prints out settings sheet +// ===================================================================================== +static void Settings() +{ + char* tmp; + + if (!g_info) + return; + + Log("\nCurrent %s Settings\n", g_Program); + Log("Name | Setting | Default\n" + "---------------------|-----------|-------------------------\n"); + + // ZHLT Common Settings + if (DEFAULT_NUMTHREADS == -1) + { + Log("threads [ %7d ] [ Varies ]\n", g_numthreads); + } + else + { + Log("threads [ %7d ] [ %7d ]\n", g_numthreads, DEFAULT_NUMTHREADS); + } + + Log("verbose [ %7s ] [ %7s ]\n", g_verbose ? "on" : "off", DEFAULT_VERBOSE ? "on" : "off"); + Log("log [ %7s ] [ %7s ]\n", g_log ? "on" : "off", DEFAULT_LOG ? "on" : "off"); +#ifdef HLCSG_KEEPLOG + Log("reset logfile [ %7s ] [ %7s ]\n", g_resetlog ? "on" : "off", DEFAULT_RESETLOG ? "on" : "off"); +#endif + + Log("developer [ %7d ] [ %7d ]\n", g_developer, DEFAULT_DEVELOPER); + Log("chart [ %7s ] [ %7s ]\n", g_chart ? "on" : "off", DEFAULT_CHART ? "on" : "off"); + Log("estimate [ %7s ] [ %7s ]\n", g_estimate ? "on" : "off", DEFAULT_ESTIMATE ? "on" : "off"); + Log("max texture memory [ %7d ] [ %7d ]\n", g_max_map_miptex, DEFAULT_MAX_MAP_MIPTEX); + Log("max lighting memory [ %7d ] [ %7d ]\n", g_max_map_lightdata, DEFAULT_MAX_MAP_LIGHTDATA); + + switch (g_threadpriority) + { + case eThreadPriorityNormal: + default: + tmp = "Normal"; + break; + case eThreadPriorityLow: + tmp = "Low"; + break; + case eThreadPriorityHigh: + tmp = "High"; + break; + } + Log("priority [ %7s ] [ %7s ]\n", tmp, "Normal"); + Log("\n"); + + // HLCSG Specific Settings + + Log("noclip [ %7s ] [ %7s ]\n", g_noclip ? "on" : "off", DEFAULT_NOCLIP ? "on" : "off"); + +#ifdef ZHLT_NULLTEX // AJM: + Log("null texture stripping[ %7s ] [ %7s ]\n", g_bUseNullTex ? "on" : "off", DEFAULT_NULLTEX ? "on" : "off"); +#endif + +#ifdef ZHLT_DETAIL // AJM + Log("detail brushes [ %7s ] [ %7s ]\n", g_bDetailBrushes ? "on" : "off", DEFAULT_DETAIL ? "on" : "off"); +#endif + +#ifdef HLCSG_CLIPECONOMY // AJM + Log("clipnode economy mode [ %7s ] [ %7s ]\n", g_bClipNazi ? "on" : "off", DEFAULT_CLIPNAZI ? "on" : "off"); +#endif + +#ifdef HLCSG_PRECISIONCLIP // KGP + Log("clip hull type [ %7s ] [ %7s ]\n", GetClipTypeString(g_cliptype), GetClipTypeString(DEFAULT_CLIPTYPE)); +#endif + + Log("onlyents [ %7s ] [ %7s ]\n", g_onlyents ? "on" : "off", DEFAULT_ONLYENTS ? "on" : "off"); + Log("wadtextures [ %7s ] [ %7s ]\n", g_wadtextures ? "on" : "off", DEFAULT_WADTEXTURES ? "on" : "off"); + Log("skyclip [ %7s ] [ %7s ]\n", g_skyclip ? "on" : "off", DEFAULT_SKYCLIP ? "on" : "off"); + Log("hullfile [ %7s ] [ %7s ]\n", g_hullfile ? g_hullfile : "None", "None"); +#ifdef HLCSG_WADCFG_NEW + Log("wad configuration file[ %7s ] [ %7s ]\n", g_wadcfgfile? g_wadcfgfile: "None", "None"); + Log("wad.cfg group name [ %7s ] [ %7s ]\n", g_wadconfigname? g_wadconfigname: "None", "None"); +#endif +#ifdef HLCSG_NULLIFY_INVISIBLE // KGP + Log("nullfile [ %7s ] [ %7s ]\n", g_nullfile ? g_nullfile : "None", "None"); +#endif +#ifdef HLCSG_NULLIFYAAATRIGGER + Log("nullify trigger [ %7s ] [ %7s ]\n", g_nullifytrigger? "on": "off", DEFAULT_NULLIFYTRIGGER? "on": "off"); +#endif + // calc min surface area + { + char tiny_penetration[10]; + char default_tiny_penetration[10]; + + safe_snprintf(tiny_penetration, sizeof(tiny_penetration), "%3.3f", g_tiny_threshold); + safe_snprintf(default_tiny_penetration, sizeof(default_tiny_penetration), "%3.3f", DEFAULT_TINY_THRESHOLD); + Log("min surface area [ %7s ] [ %7s ]\n", tiny_penetration, default_tiny_penetration); + } + + // calc union threshold + { + char brush_union[10]; + char default_brush_union[10]; + + safe_snprintf(brush_union, sizeof(brush_union), "%3.3f", g_BrushUnionThreshold); + safe_snprintf(default_brush_union, sizeof(default_brush_union), "%3.3f", DEFAULT_BRUSH_UNION_THRESHOLD); + Log("brush union threshold [ %7s ] [ %7s ]\n", brush_union, default_brush_union); + } +#ifdef HLCSG_SCALESIZE + { + char buf1[10]; + char buf2[10]; + + if (g_scalesize > 0) + safe_snprintf(buf1, sizeof(buf1), "%3.3f", g_scalesize); + else + strcpy (buf1, "None"); + if (DEFAULT_SCALESIZE > 0) + safe_snprintf(buf2, sizeof(buf2), "%3.3f", DEFAULT_SCALESIZE); + else + strcpy (buf2, "None"); + Log("map scaling [ %7s ] [ %7s ]\n", buf1, buf2); + } +#endif +#ifdef HLCSG_OPTIMIZELIGHTENTITY + Log("light name optimize [ %7s ] [ %7s ]\n", !g_nolightopt? "on" : "off", !DEFAULT_NOLIGHTOPT? "on" : "off"); +#endif +#ifdef HLCSG_GAMETEXTMESSAGE_UTF8 + Log("convert game_text [ %7s ] [ %7s ]\n", !g_noutf8? "on" : "off", !DEFAULT_NOUTF8? "on" : "off"); +#endif + + Log("\n"); +} + +// AJM: added in +// ===================================================================================== +// CSGCleanup +// ===================================================================================== +void CSGCleanup() +{ + //Log("CSGCleanup\n"); +#ifndef HLCSG_AUTOWAD_NEW +#ifdef HLCSG_AUTOWAD + autowad_cleanup(); +#endif +#endif +#ifndef HLCSG_WADCFG_NEW +#ifdef HLCSG_WADCFG + WadCfg_cleanup(); +#endif +#endif + FreeWadPaths(); +} + +// ===================================================================================== +// Main +// Oh, come on. +// ===================================================================================== +int main(const int argc, char** argv) +{ + int i; + char name[_MAX_PATH]; // mapanme + double start, end; // start/end time log + const char* mapname_from_arg = NULL; // mapname path from passed argvar + + g_Program = "hlcsg"; + +#ifdef ZHLT_PARAMFILE + int argcold = argc; + char ** argvold = argv; + { + int argc; + char ** argv; + ParseParamFile (argcold, argvold, argc, argv); + { +#endif +#ifdef ZHLT_CONSOLE + if (InitConsole (argc, argv) < 0) + Usage(); +#endif + if (argc == 1) + Usage(); + + // Hard coded list of -wadinclude files, used for HINT texture brushes so lazy + // mapmakers wont cause beta testers (or possibly end users) to get a wad + // error on zhlt.wad etc + g_WadInclude.push_back("zhlt.wad"); + +#ifndef HLCSG_WADCFG_NEW +#ifdef HLCSG_WADCFG + memset(wadconfigname, 0, sizeof(wadconfigname));//AJM +#endif +#endif +#ifdef HLCSG_HULLBRUSH + InitDefaultHulls (); +#endif + + // detect argv + for (i = 1; i < argc; i++) + { + if (!strcasecmp(argv[i], "-threads")) + { + if (i + 1 < argc) //added "1" .--vluzacn + { + g_numthreads = atoi(argv[++i]); + if (g_numthreads < 1) + { + Log("Expected value of at least 1 for '-threads'\n"); + Usage(); + } + } + else + { + Usage(); + } + } + +#ifdef ZHLT_CONSOLE + else if (!strcasecmp(argv[i], "-console")) + { +#ifndef SYSTEM_WIN32 + Warning("The option '-console #' is only valid for Windows."); +#endif + if (i + 1 < argc) + ++i; + else + Usage(); + } +#endif +#ifdef SYSTEM_WIN32 + else if (!strcasecmp(argv[i], "-estimate")) + { + g_estimate = true; + } +#endif + +#ifdef SYSTEM_POSIX + else if (!strcasecmp(argv[i], "-noestimate")) + { + g_estimate = false; + } +#endif + + else if (!strcasecmp(argv[i], "-dev")) + { + if (i + 1 < argc) //added "1" .--vluzacn + { + g_developer = (developer_level_t)atoi(argv[++i]); + } + else + { + Usage(); + } + } + else if (!strcasecmp(argv[i], "-verbose")) + { + g_verbose = true; + } + else if (!strcasecmp(argv[i], "-noinfo")) + { + g_info = false; + } + else if (!strcasecmp(argv[i], "-chart")) + { + g_chart = true; + } + else if (!strcasecmp(argv[i], "-low")) + { + g_threadpriority = eThreadPriorityLow; + } + else if (!strcasecmp(argv[i], "-high")) + { + g_threadpriority = eThreadPriorityHigh; + } + else if (!strcasecmp(argv[i], "-nolog")) + { + g_log = false; + } + else if (!strcasecmp(argv[i], "-skyclip")) + { + g_skyclip = true; + } + else if (!strcasecmp(argv[i], "-noskyclip")) + { + g_skyclip = false; + } + else if (!strcasecmp(argv[i], "-noclip")) + { + g_noclip = true; + } + else if (!strcasecmp(argv[i], "-onlyents")) + { + g_onlyents = true; + } + +#ifdef ZHLT_NULLTEX // AJM: added in -nonulltex + else if (!strcasecmp(argv[i], "-nonulltex")) + { + g_bUseNullTex = false; + } +#endif + +#ifdef HLCSG_CLIPECONOMY // AJM: added in -noclipeconomy +#ifdef HLCSG_CUSTOMHULL // default clip economy off + else if (!strcasecmp(argv[i], "-clipeconomy")) + { + g_bClipNazi = true; + } +#else + else if (!strcasecmp(argv[i], "-noclipeconomy")) + { + g_bClipNazi = false; + } +#endif +#endif + +#ifdef HLCSG_PRECISIONCLIP // KGP: added in -cliptype + else if (!strcasecmp(argv[i], "-cliptype")) + { + if (i + 1 < argc) //added "1" .--vluzacn + { + ++i; + if(!strcasecmp(argv[i],"smallest")) + { g_cliptype = clip_smallest; } + else if(!strcasecmp(argv[i],"normalized")) + { g_cliptype = clip_normalized; } + else if(!strcasecmp(argv[i],"simple")) + { g_cliptype = clip_simple; } + else if(!strcasecmp(argv[i],"precise")) + { g_cliptype = clip_precise; } + else if(!strcasecmp(argv[i],"legacy")) + { g_cliptype = clip_legacy; } + } + else + { + Log("Error: -cliptype: incorrect usage of parameter\n"); + Usage(); + } + } +#endif + +#ifndef HLCSG_WADCFG_NEW +#ifdef HLCSG_WADCFG + // AJM: added in -wadconfig + else if (!strcasecmp(argv[i], "-wadconfig")) + { + if (i + 1 < argc) //added "1" .--vluzacn + { + safe_strncpy(wadconfigname, argv[++i], MAX_WAD_CFG_NAME); + if (strlen(argv[i]) > MAX_WAD_CFG_NAME) + { + Warning("wad configuration name was truncated to %i chars", MAX_WAD_CFG_NAME); + wadconfigname[MAX_WAD_CFG_NAME] = 0; + } + } + else + { + Log("Error: -wadconfig: incorrect usage of parameter\n"); + Usage(); + } + } + + //JK: added in -wadcfgfile + else if (!strcasecmp(argv[i], "-wadcfgfile")) + { + if (i + 1 < argc) //added "1" .--vluzacn + { + g_wadcfgfile = argv[++i]; + } + else + { + Log("Error: -wadcfgfile: incorrect usage of parameter\n"); + Usage(); + } + } +#endif +#endif +#ifdef HLCSG_NULLIFY_INVISIBLE + else if (!strcasecmp(argv[i], "-nullfile")) + { + if (i + 1 < argc) //added "1" .--vluzacn + { + g_nullfile = argv[++i]; + } + else + { + Log("Error: -nullfile: expected path to null ent file following parameter\n"); + Usage(); + } + } +#endif + +#ifdef HLCSG_AUTOWAD // AJM + else if (!strcasecmp(argv[i], "-wadautodetect")) + { + g_bWadAutoDetect = true; + } +#endif + +#ifdef ZHLT_DETAIL // AJM + else if (!strcasecmp(argv[i], "-nodetail")) + { + g_bDetailBrushes = false; + } +#endif + +#ifdef ZHLT_PROGRESSFILE // AJM + else if (!strcasecmp(argv[i], "-progressfile")) + { + if (i + 1 < argc) //added "1" .--vluzacn + { + g_progressfile = argv[++i]; + } + else + { + Log("Error: -progressfile: expected path to progress file following parameter\n"); + Usage(); + } + } +#endif + + else if (!strcasecmp(argv[i], "-nowadtextures")) + { + g_wadtextures = false; + } + else if (!strcasecmp(argv[i], "-wadinclude")) + { + if (i + 1 < argc) //added "1" .--vluzacn + { + g_WadInclude.push_back(argv[++i]); + } + else + { + Usage(); + } + } + else if (!strcasecmp(argv[i], "-texdata")) + { + if (i + 1 < argc) //added "1" .--vluzacn + { + int x = atoi(argv[++i]) * 1024; + + //if (x > g_max_map_miptex) //--vluzacn + { + g_max_map_miptex = x; + } + } + else + { + Usage(); + } + } + else if (!strcasecmp(argv[i], "-lightdata")) + { + if (i + 1 < argc) //added "1" .--vluzacn + { + int x = atoi(argv[++i]) * 1024; + + //if (x > g_max_map_lightdata) //--vluzacn + { + g_max_map_lightdata = x; + } + } + else + { + Usage(); + } + } + else if (!strcasecmp(argv[i], "-brushunion")) + { + if (i + 1 < argc) //added "1" .--vluzacn + { + g_BrushUnionThreshold = (float)atof(argv[++i]); + } + else + { + Usage(); + } + } + else if (!strcasecmp(argv[i], "-tiny")) + { + if (i + 1 < argc) //added "1" .--vluzacn + { + g_tiny_threshold = (float)atof(argv[++i]); + } + else + { + Usage(); + } + } + else if (!strcasecmp(argv[i], "-hullfile")) + { + if (i + 1 < argc) //added "1" .--vluzacn + { + g_hullfile = argv[++i]; + } + else + { + Usage(); + } + } +#ifdef HLCSG_WADCFG_NEW + else if (!strcasecmp (argv[i], "-wadcfgfile")) + { + if (i + 1 < argc) + { + g_wadcfgfile = argv[++i]; + } + else + { + Usage (); + } + } + else if (!strcasecmp (argv[i], "-wadconfig")) + { + if (i + 1 < argc) + { + g_wadconfigname = argv[++i]; + } + else + { + Usage (); + } + } +#endif +#ifdef HLCSG_SCALESIZE + else if (!strcasecmp(argv[i], "-scale")) + { + if (i + 1 < argc) + { + g_scalesize = atof(argv[++i]); + } + else + { + Usage(); + } + } +#endif +#ifdef ZHLT_LANGFILE + else if (!strcasecmp (argv[i], "-lang")) + { + if (i + 1 < argc) + { + char tmp[_MAX_PATH]; +#ifdef SYSTEM_WIN32 + GetModuleFileName (NULL, tmp, _MAX_PATH); +#else + safe_strncpy (tmp, argv[0], _MAX_PATH); +#endif + LoadLangFile (argv[++i], tmp); + } + else + { + Usage(); + } + } +#endif +#ifdef HLCSG_KEEPLOG + else if (!strcasecmp (argv[i], "-noresetlog")) + { + g_resetlog = false; + } +#endif +#ifdef HLCSG_OPTIMIZELIGHTENTITY + else if (!strcasecmp (argv[i], "-nolightopt")) + { + g_nolightopt = true; + } +#endif +#ifdef HLCSG_GAMETEXTMESSAGE_UTF8 + else if (!strcasecmp (argv[i], "-notextconvert")) + { + g_noutf8 = true; + } +#endif +#ifdef HLCSG_VIEWSURFACE + else if (!strcasecmp (argv[i], "-viewsurface")) + { + g_viewsurface = true; + } +#endif +#ifdef HLCSG_NULLIFYAAATRIGGER + else if (!strcasecmp (argv[i], "-nonullifytrigger")) + { + g_nullifytrigger = false; + } +#endif + else if (argv[i][0] == '-') + { + Log("Unknown option \"%s\"\n", argv[i]); + Usage(); + } + else if (!mapname_from_arg) + { + mapname_from_arg = argv[i]; + } + else + { + Log("Unknown option \"%s\"\n", argv[i]); + Usage(); + } + } + + // no mapfile? + if (!mapname_from_arg) + { + // what a shame. + Log("No mapfile specified\n"); + Usage(); + } + + // handle mapname + safe_strncpy(g_Mapname, mapname_from_arg, _MAX_PATH); + FlipSlashes(g_Mapname); + StripExtension(g_Mapname); + + // onlyents + if (!g_onlyents) + ResetTmpFiles(); + + // other stuff + ResetErrorLog(); +#ifdef HLCSG_KEEPLOG + if (!g_onlyents && g_resetlog) +#endif + ResetLog(); + OpenLog(g_clientid); + atexit(CloseLog); +#ifdef ZHLT_PARAMFILE + LogStart(argcold, argvold); + { + int i; + Log("Arguments: "); + for (i = 1; i < argc; i++) + { + if (strchr(argv[i], ' ')) + { + Log("\"%s\" ", argv[i]); + } + else + { + Log("%s ", argv[i]); + } + } + Log("\n"); + } +#else + LogStart(argc, argv); +#endif +#ifdef ZHLT_64BIT_FIX +#ifdef PLATFORM_CAN_CALC_EXTENT + hlassume (CalcFaceExtents_test (), assume_first); +#endif +#endif + atexit(CSGCleanup); // AJM + dtexdata_init(); + atexit(dtexdata_free); + + // START CSG + // AJM: re-arranged some stuff up here so that the mapfile is loaded + // before settings are finalised and printed out, so that the info_compile_parameters + // entity can be dealt with effectively + start = I_FloatTime(); +#ifdef HLCSG_HULLFILE_AUTOPATH + if (g_hullfile) + { + char temp[_MAX_PATH]; + char test[_MAX_PATH]; + safe_strncpy (temp, g_Mapname, _MAX_PATH); + ExtractFilePath (temp, test); + safe_strncat (test, g_hullfile, _MAX_PATH); + if (q_exists (test)) + { + g_hullfile = strdup (test); + } + else + { +#ifdef SYSTEM_WIN32 + GetModuleFileName (NULL, temp, _MAX_PATH); +#else + safe_strncpy (temp, argv[0], _MAX_PATH); +#endif + ExtractFilePath (temp, test); + safe_strncat (test, g_hullfile, _MAX_PATH); + if (q_exists (test)) + { + g_hullfile = strdup (test); + } + } + } + if (g_nullfile) + { + char temp[_MAX_PATH]; + char test[_MAX_PATH]; + safe_strncpy (temp, g_Mapname, _MAX_PATH); + ExtractFilePath (temp, test); + safe_strncat (test, g_nullfile, _MAX_PATH); + if (q_exists (test)) + { + g_nullfile = strdup (test); + } + else + { +#ifdef SYSTEM_WIN32 + GetModuleFileName (NULL, temp, _MAX_PATH); +#else + safe_strncpy (temp, argv[0], _MAX_PATH); +#endif + ExtractFilePath (temp, test); + safe_strncat (test, g_nullfile, _MAX_PATH); + if (q_exists (test)) + { + g_nullfile = strdup (test); + } + } + } +#endif +#ifdef HLCSG_WADCFG_NEW + if (g_wadcfgfile) + { + char temp[_MAX_PATH]; + char test[_MAX_PATH]; + safe_strncpy (temp, g_Mapname, _MAX_PATH); + ExtractFilePath (temp, test); + safe_strncat (test, g_wadcfgfile, _MAX_PATH); + if (q_exists (test)) + { + g_wadcfgfile = strdup (test); + } + else + { +#ifdef SYSTEM_WIN32 + GetModuleFileName (NULL, temp, _MAX_PATH); +#else + safe_strncpy (temp, argv[0], _MAX_PATH); +#endif + ExtractFilePath (temp, test); + safe_strncat (test, g_wadcfgfile, _MAX_PATH); + if (q_exists (test)) + { + g_wadcfgfile = strdup (test); + } + } + } +#endif + + LoadHullfile(g_hullfile); // if the user specified a hull file, load it now +#ifdef HLCSG_NULLIFY_INVISIBLE + if(g_bUseNullTex) + { properties_initialize(g_nullfile); } +#endif + safe_strncpy(name, mapname_from_arg, _MAX_PATH); // make a copy of the nap name +#ifdef ZHLT_DEFAULTEXTENSION_FIX + FlipSlashes(name); +#endif + DefaultExtension(name, ".map"); // might be .reg + + LoadMapFile(name); + ThreadSetDefault(); + ThreadSetPriority(g_threadpriority); + Settings(); + + +#ifdef HLCSG_GAMETEXTMESSAGE_UTF8 + if (!g_noutf8) + { + int count = 0; + + for (i = 0; i < g_numentities; i++) + { + entity_t *ent = &g_entities[i]; + const char *value; + char *newvalue; + + if (strcmp (ValueForKey (ent, "classname"), "game_text")) + { + continue; + } + + value = ValueForKey (ent, "message"); + if (*value) + { + newvalue = ANSItoUTF8 (value); + if (strcmp (newvalue, value)) + { + SetKeyValue (ent, "message", newvalue); + count++; + } + free (newvalue); + } + } + + if (count) + { + Log ("%d game_text messages converted from Windows ANSI(CP_ACP) to UTF-8 encoding\n", count); + } + } +#endif +#ifdef HLCSG_ONLYENTS_NOWADCHANGE + if (!g_onlyents) + { +#endif +#ifdef HLCSG_WADCFG_NEW + if (g_wadconfigname) + { + char temp[_MAX_PATH]; + char test[_MAX_PATH]; +#ifdef SYSTEM_WIN32 + GetModuleFileName (NULL, temp, _MAX_PATH); +#else + safe_strncpy (temp, argv[0], _MAX_PATH); +#endif + ExtractFilePath (temp, test); + safe_strncat (test, "wad.cfg", _MAX_PATH); + + if (g_wadcfgfile) + { + safe_strncpy (test, g_wadcfgfile, _MAX_PATH); + } + + LoadWadconfig (test, g_wadconfigname); + } + else if (g_wadcfgfile) + { + if (!q_exists (g_wadcfgfile)) + { + Error("Couldn't find wad configuration file '%s'\n", g_wadcfgfile); + } + LoadWadcfgfile (g_wadcfgfile); + } + else + { + Log("Using mapfile wad configuration\n"); + GetUsedWads(); + } +#endif +#ifndef HLCSG_WADCFG_NEW +#ifdef HLCSG_WADCFG // AJM + // figure out what to do with the texture settings + if (wadconfigname[0]) // custom wad configuations will take precedence + { + LoadWadConfigFile(); + ProcessWadConfiguration(); + } + else + { + Log("Using mapfile wad configuration\n"); + } + if (!g_bWadConfigsLoaded) // dont try and override wad.cfg +#endif + { + GetUsedWads(); + } +#endif + +#ifdef HLCSG_AUTOWAD + if (g_bWadAutoDetect) + { + Log("Wadfiles not in use by the map will be excluded\n"); + } +#endif + + DumpWadinclude(); + Log("\n"); +#ifdef HLCSG_ONLYENTS_NOWADCHANGE + } +#endif + + // if onlyents, just grab the entites and resave + if (g_onlyents) + { + char out[_MAX_PATH]; + + safe_snprintf(out, _MAX_PATH, "%s.bsp", g_Mapname); + LoadBSPFile(out); +#ifndef HLCSG_ONLYENTS_NOWADCHANGE + LoadWadincludeFile(g_Mapname); + + HandleWadinclude(); +#endif + + // Write it all back out again. +#ifndef HLCSG_CHART_FIX + if (g_chart) + { + PrintBSPFileSizes(); + } +#endif + WriteBSP(g_Mapname); + + end = I_FloatTime(); + LogTimeElapsed(end - start); + return 0; + } +#ifndef HLCSG_ONLYENTS_NOWADCHANGE + else + { + SaveWadincludeFile(g_Mapname); + } +#endif + +#ifdef HLCSG_CLIPECONOMY // AJM + CheckForNoClip(); +#endif + + // createbrush + NamedRunThreadsOnIndividual(g_nummapbrushes, g_estimate, CreateBrush); + CheckFatal(); + +#ifdef HLCSG_PRECISIONCLIP // KGP - drop TEX_BEVEL flag +#ifndef HLCSG_CUSTOMHULL + FixBevelTextures(); +#endif +#endif + + // boundworld + BoundWorld(); + + Verbose("%5i map planes\n", g_nummapplanes); + + // Set model centers + for (i = 0; i < g_numentities; i++) SetModelCenters (i); //NamedRunThreadsOnIndividual(g_numentities, g_estimate, SetModelCenters); //--vluzacn + + // Calc brush unions + if ((g_BrushUnionThreshold > 0.0) && (g_BrushUnionThreshold <= 100.0)) + { + NamedRunThreadsOnIndividual(g_nummapbrushes, g_estimate, CalculateBrushUnions); + } + + // open hull files + for (i = 0; i < NUM_HULLS; i++) + { + char name[_MAX_PATH]; + + safe_snprintf(name, _MAX_PATH, "%s.p%i", g_Mapname, i); + + out[i] = fopen(name, "w"); + + if (!out[i]) + Error("Couldn't open %s", name); +#ifdef ZHLT_DETAILBRUSH + safe_snprintf(name, _MAX_PATH, "%s.b%i", g_Mapname, i); + out_detailbrush[i] = fopen(name, "w"); + if (!out_detailbrush[i]) + Error("Couldn't open %s", name); +#endif +#ifdef HLCSG_VIEWSURFACE + if (g_viewsurface) + { + safe_snprintf (name, _MAX_PATH, "%s_surface%i.pts", g_Mapname, i); + out_view[i] = fopen (name, "w"); + if (!out[i]) + Error ("Counldn't open %s", name); + } +#endif + } +#ifdef HLCSG_HLBSP_ALLOWEMPTYENTITY + { + FILE *f; + char name[_MAX_PATH]; + safe_snprintf (name, _MAX_PATH, "%s.hsz", g_Mapname); + f = fopen (name, "w"); + if (!f) + Error("Couldn't open %s", name); + float x1,y1,z1; + float x2,y2,z2; + for (i = 0; i < NUM_HULLS; i++) + { + x1 = g_hull_size[i][0][0]; + y1 = g_hull_size[i][0][1]; + z1 = g_hull_size[i][0][2]; + x2 = g_hull_size[i][1][0]; + y2 = g_hull_size[i][1][1]; + z2 = g_hull_size[i][1][2]; + fprintf (f, "%g %g %g %g %g %g\n", x1, y1, z1, x2, y2, z2); + } + fclose (f); + } +#endif + + ProcessModels(); + + Verbose("%5i csg faces\n", c_csgfaces); + Verbose("%5i used faces\n", c_outfaces); + Verbose("%5i tiny faces\n", c_tiny); + Verbose("%5i tiny clips\n", c_tiny_clip); + + // close hull files + for (i = 0; i < NUM_HULLS; i++) + { + fclose(out[i]); +#ifdef ZHLT_DETAILBRUSH + fclose (out_detailbrush[i]); +#endif +#ifdef HLCSG_VIEWSURFACE + if (g_viewsurface) + { + fclose (out_view[i]); + } +#endif + } + + EmitPlanes(); + +#ifndef HLCSG_CHART_FIX + if (g_chart) + PrintBSPFileSizes(); +#endif + + WriteBSP(g_Mapname); + + // AJM: debug +#if 0 + Log("\n---------------------------------------\n" + "Map Plane Usage:\n" + " # normal origin dist type\n" + " ( x, y, z) ( x, y, z) ( )\n" + ); + for (i = 0; i < g_nummapplanes; i++) + { + plane_t* p = &g_mapplanes[i]; + + Log( + "%3i (%4.0f, %4.0f, %4.0f) (%4.0f, %4.0f, %4.0f) (%5.0f) %i\n", + i, + p->normal[1], p->normal[2], p->normal[3], + p->origin[1], p->origin[2], p->origin[3], + p->dist, + p->type + ); + } + Log("---------------------------------------\n\n"); +#endif + + // elapsed time + end = I_FloatTime(); + LogTimeElapsed(end - start); + +#ifdef ZHLT_PARAMFILE + } + } +#endif + return 0; +} diff --git a/src/zhlt-vluzacn/hlcsg/textures.cpp b/src/zhlt-vluzacn/hlcsg/textures.cpp new file mode 100644 index 0000000..d0b199f --- /dev/null +++ b/src/zhlt-vluzacn/hlcsg/textures.cpp @@ -0,0 +1,1240 @@ +#include "csg.h" + +#define MAXWADNAME 16 +#define MAX_TEXFILES 128 + +// FindMiptex +// TEX_InitFromWad +// FindTexture +// LoadLump +// AddAnimatingTextures + + +typedef struct +{ + char identification[4]; // should be WAD2/WAD3 + int numlumps; + int infotableofs; +} wadinfo_t; + +typedef struct +{ + int filepos; + int disksize; + int size; // uncompressed + char type; + char compression; + char pad1, pad2; + char name[MAXWADNAME]; // must be null terminated // upper case + + int iTexFile; // index of the wad this texture is located in + +} lumpinfo_t; + +std::deque< std::string > g_WadInclude; +#ifndef HLCSG_AUTOWAD_NEW +std::map< int, bool > s_WadIncludeMap; +#endif + +static int nummiptex = 0; +static lumpinfo_t miptex[MAX_MAP_TEXTURES]; +static int nTexLumps = 0; +static lumpinfo_t* lumpinfo = NULL; +static int nTexFiles = 0; +static FILE* texfiles[MAX_TEXFILES]; +#ifdef HLCSG_AUTOWAD_NEW +static wadpath_t* texwadpathes[MAX_TEXFILES]; // maps index of the wad to its path +#endif + +#ifdef HLCSG_TEXMAP64_FIX +// The old buggy code in effect limit the number of brush sides to MAX_MAP_BRUSHES +#ifdef HLCSG_HLBSP_REDUCETEXTURE +static char *texmap[MAX_INTERNAL_MAP_TEXINFO]; +#else +static char *texmap[MAX_MAP_TEXINFO]; +#endif +static int numtexmap = 0; + +static int texmap_store (char *texname, bool shouldlock = true) + // This function should never be called unless a new entry in g_texinfo is being allocated. +{ + int i; + if (shouldlock) + { + ThreadLock (); + } +#ifdef HLCSG_HLBSP_REDUCETEXTURE + hlassume (numtexmap < MAX_INTERNAL_MAP_TEXINFO, assume_MAX_MAP_TEXINFO); // This error should never appear. +#else + hlassume (numtexmap < MAX_MAP_TEXINFO, assume_MAX_MAP_TEXINFO); // This error should never appear. +#endif + i = numtexmap; + texmap[numtexmap] = strdup (texname); + numtexmap++; + if (shouldlock) + { + ThreadUnlock (); + } + return i; +} + +static char *texmap_retrieve (int index) +{ + hlassume (0 <= index && index < numtexmap, assume_first); + return texmap[index]; +} + +static void texmap_clear () +{ + int i; + ThreadLock (); + for (i = 0; i < numtexmap; i++) + { + free (texmap[i]); + } + numtexmap = 0; + ThreadUnlock (); +} +#else +// fix for 64 bit machines +#if /* 64 bit */ + static char* texmap64[MAX_MAP_BRUSHES]; + static int tex_max64=0; + + static inline int texmap64_store(char *texname) + { + int curr_tex; + ThreadLock(); + if (tex_max64 >= MAX_MAP_BRUSHES) // no assert? + { +#ifdef ZHLT_CONSOLE + Error ("MAX_MAP_BRUSHES exceeded!"); +#else + printf("MAX_MAP_BRUSHES exceeded!\n"); + exit(-1); +#endif + } + curr_tex = tex_max64; + texmap64[tex_max64] = texname; + tex_max64++; + ThreadUnlock(); + return curr_tex; + } + + static inline char* texmap64_retrieve( int index) + { + if(index > tex_max64) + { +#ifdef ZHLT_CONSOLE + Error ("retrieving bogus texture index %d", index); +#else + printf("retrieving bogus texture index %d\n", index); + exit(-1); +#endif + } + return texmap64[index]; + } + +#else + #define texmap64_store( A ) ( (int) A) + #define texmap64_retrieve( A ) ( (char*) A) +#endif +#endif + +// ===================================================================================== +// CleanupName +// ===================================================================================== +static void CleanupName(const char* const in, char* out) +{ + int i; + + for (i = 0; i < MAXWADNAME; i++) + { + if (!in[i]) + { + break; + } + + out[i] = toupper(in[i]); + } + + for (; i < MAXWADNAME; i++) + { + out[i] = 0; + } +} + +// ===================================================================================== +// lump_sorters +// ===================================================================================== + +static int CDECL lump_sorter_by_wad_and_name(const void* lump1, const void* lump2) +{ + lumpinfo_t* plump1 = (lumpinfo_t*)lump1; + lumpinfo_t* plump2 = (lumpinfo_t*)lump2; + + if (plump1->iTexFile == plump2->iTexFile) + { + return strcmp(plump1->name, plump2->name); + } + else + { + return plump1->iTexFile - plump2->iTexFile; + } +} + +static int CDECL lump_sorter_by_name(const void* lump1, const void* lump2) +{ + lumpinfo_t* plump1 = (lumpinfo_t*)lump1; + lumpinfo_t* plump2 = (lumpinfo_t*)lump2; + + return strcmp(plump1->name, plump2->name); +} + +// ===================================================================================== +// FindMiptex +// Find and allocate a texture into the lump data +// ===================================================================================== +static int FindMiptex(const char* const name) +{ + int i; +#ifdef HLCSG_TEXMAP64_FIX + if (strlen (name) >= MAXWADNAME) + { + Error ("Texture name is too long (%s)\n", name); + } +#endif + + ThreadLock(); + for (i = 0; i < nummiptex; i++) + { + if (!strcmp(name, miptex[i].name)) + { + ThreadUnlock(); + return i; + } + } + + hlassume(nummiptex < MAX_MAP_TEXTURES, assume_MAX_MAP_TEXTURES); + safe_strncpy(miptex[i].name, name, MAXWADNAME); + nummiptex++; + ThreadUnlock(); + return i; +} + +// ===================================================================================== +// TEX_InitFromWad +// ===================================================================================== +bool TEX_InitFromWad() +{ + int i, j; + wadinfo_t wadinfo; +#ifndef HLCSG_AUTOWAD_NEW + char szTmpWad[1024]; // arbitrary, but needs to be large. +#endif + char* pszWadFile; + const char* pszWadroot; + wadpath_t* currentwad; + + Log("\n"); // looks cleaner +#ifdef HLCSG_AUTOWAD_NEW + // update wad inclusion + for (i = 0; i < g_iNumWadPaths; i++) + { + currentwad = g_pWadPaths[i]; + if (!g_wadtextures) // '-nowadtextures' + { + currentwad->usedbymap = false; // include this wad + } + for (WadInclude_i it = g_WadInclude.begin (); it != g_WadInclude.end (); it++) // '-wadinclude xxx.wad' + { + if (stristr (currentwad->path, it->c_str ())) + { + currentwad->usedbymap = false; // include this wad + } + } + } +#endif + +#ifndef HLCSG_AUTOWAD_NEW + szTmpWad[0] = 0; +#endif + pszWadroot = getenv("WADROOT"); + +#ifndef HLCSG_AUTOWAD_NEW +#ifdef HLCSG_AUTOWAD + autowad_UpdateUsedWads(); +#endif +#endif + + // for eachwadpath + for (i = 0; i < g_iNumWadPaths; i++) + { + FILE* texfile; // temporary used in this loop +#ifndef HLCSG_AUTOWAD_NEW + bool bExcludeThisWad = false; +#endif + + currentwad = g_pWadPaths[i]; + pszWadFile = currentwad->path; + + +#ifndef HLCSG_AUTOWAD_NEW +#ifdef HLCSG_AUTOWAD + #ifdef _DEBUG + Log("[dbg] Attempting to parse wad: '%s'\n", pszWadFile); + #endif + + if (g_bWadAutoDetect && !currentwad->usedtextures) + continue; + + #ifdef _DEBUG + Log("[dbg] Parsing wad\n"); + #endif +#endif +#endif + +#ifdef HLCSG_AUTOWAD_NEW + texwadpathes[nTexFiles] = currentwad; +#endif + texfiles[nTexFiles] = fopen(pszWadFile, "rb"); + + #ifdef SYSTEM_WIN32 + if (!texfiles[nTexFiles]) + { + // cant find it, maybe this wad file has a hard code drive + if (pszWadFile[1] == ':') + { + pszWadFile += 2; // skip past the drive + texfiles[nTexFiles] = fopen(pszWadFile, "rb"); + } + } + #endif + + if (!texfiles[nTexFiles] && pszWadroot) + { + char szTmp[_MAX_PATH]; + char szFile[_MAX_PATH]; + char szSubdir[_MAX_PATH]; + + ExtractFile(pszWadFile, szFile); + + ExtractFilePath(pszWadFile, szTmp); + ExtractFile(szTmp, szSubdir); + + // szSubdir will have a trailing separator + safe_snprintf(szTmp, _MAX_PATH, "%s" SYSTEM_SLASH_STR "%s%s", pszWadroot, szSubdir, szFile); + texfiles[nTexFiles] = fopen(szTmp, "rb"); + + #ifdef SYSTEM_POSIX + if (!texfiles[nTexFiles]) + { + // if we cant find it, Convert to lower case and try again + strlwr(szTmp); + texfiles[nTexFiles] = fopen(szTmp, "rb"); + } + #endif + } + + #ifdef HLCSG_SEARCHWADPATH_VL + #ifdef SYSTEM_WIN32 + if (!texfiles[nTexFiles] && pszWadFile[0] == '\\') + { + char tmp[_MAX_PATH]; + int l; + for (l = 'C'; l <= 'Z'; ++l) + { + safe_snprintf (tmp, _MAX_PATH, "%c:%s", l, pszWadFile); + texfiles[nTexFiles] = fopen (tmp, "rb"); + if (texfiles[nTexFiles]) + { + Developer (DEVELOPER_LEVEL_MESSAGE, "wad file found in drive '%c:' : %s\n", l, pszWadFile); + break; + } + } + } + #endif + #endif + + if (!texfiles[nTexFiles]) + { +#ifdef HLCSG_SEARCHWADPATH_VL + pszWadFile = currentwad->path; // correct it back +#endif + // still cant find it, error out + Fatal(assume_COULD_NOT_FIND_WAD, "Could not open wad file %s", pszWadFile); + continue; + } + +#ifdef HLCSG_WADCFG_NEW + pszWadFile = currentwad->path; // correct it back +#endif +#ifndef HLCSG_AUTOWAD_NEW + // look and see if we're supposed to include the textures from this WAD in the bsp. + WadInclude_i it; + for (it = g_WadInclude.begin(); it != g_WadInclude.end(); it++) + { + if (stristr(pszWadFile, it->c_str())) + { + Log("Including Wadfile: %s\n", pszWadFile); + bExcludeThisWad = true; // wadincluding this one + s_WadIncludeMap[nTexFiles] = true; + break; + } + } +#ifdef HLCSG_ONLYENTS_NOWADCHANGE + if (!bExcludeThisWad && !g_wadtextures) // -nowadtextures + { + Log("Including Wadfile: %s\n", pszWadFile); + bExcludeThisWad = true; + } +#endif + + if (!bExcludeThisWad) + { + Log("Using Wadfile: %s\n", pszWadFile); +#ifdef HLCSG_STRIPWADPATH + char tmp[_MAX_PATH]; + ExtractFile (pszWadFile, tmp); + safe_snprintf(szTmpWad, 1024, "%s%s;", szTmpWad, tmp); +#else + safe_snprintf(szTmpWad, 1024, "%s%s;", szTmpWad, pszWadFile); +#endif + } +#endif + + // temp assignment to make things cleaner: + texfile = texfiles[nTexFiles]; + + // read in this wadfiles information + SafeRead(texfile, &wadinfo, sizeof(wadinfo)); + + // make sure its a valid format + if (strncmp(wadinfo.identification, "WAD2", 4) && strncmp(wadinfo.identification, "WAD3", 4)) + { + Log(" - "); + Error("%s isn't a Wadfile!", pszWadFile); + } + + wadinfo.numlumps = LittleLong(wadinfo.numlumps); + wadinfo.infotableofs = LittleLong(wadinfo.infotableofs); + + // read in lump + if (fseek(texfile, wadinfo.infotableofs, SEEK_SET)) + Warning("fseek to %d in wadfile %s failed\n", wadinfo.infotableofs, pszWadFile); + + // memalloc for this lump + lumpinfo = (lumpinfo_t*)realloc(lumpinfo, (nTexLumps + wadinfo.numlumps) * sizeof(lumpinfo_t)); + + // for each texlump + for (j = 0; j < wadinfo.numlumps; j++, nTexLumps++) + { + SafeRead(texfile, &lumpinfo[nTexLumps], (sizeof(lumpinfo_t) - sizeof(int)) ); // iTexFile is NOT read from file + + if (!TerminatedString(lumpinfo[nTexLumps].name, MAXWADNAME)) + { + lumpinfo[nTexLumps].name[MAXWADNAME - 1] = 0; + Log(" - "); + Warning("Unterminated texture name : wad[%s] texture[%d] name[%s]\n", pszWadFile, nTexLumps, lumpinfo[nTexLumps].name); + } + + CleanupName(lumpinfo[nTexLumps].name, lumpinfo[nTexLumps].name); + + lumpinfo[nTexLumps].filepos = LittleLong(lumpinfo[nTexLumps].filepos); + lumpinfo[nTexLumps].disksize = LittleLong(lumpinfo[nTexLumps].disksize); + lumpinfo[nTexLumps].iTexFile = nTexFiles; + + if (lumpinfo[nTexLumps].disksize > MAX_TEXTURE_SIZE) + { + Log(" - "); + Warning("Larger than expected texture (%d bytes): '%s'", + lumpinfo[nTexLumps].disksize, lumpinfo[nTexLumps].name); + } + + } + + // AJM: this feature is dependant on autowad. :( + // CONSIDER: making it standard? +#ifdef HLCSG_AUTOWAD_NEW + currentwad->totaltextures = wadinfo.numlumps; +#else +#ifdef HLCSG_AUTOWAD + { + double percused = ((float)(currentwad->usedtextures) / (float)(g_numUsedTextures)) * 100; + Log(" - Contains %i used texture%s, %2.2f percent of map (%d textures in wad)\n", + currentwad->usedtextures, currentwad->usedtextures == 1 ? "" : "s", percused, wadinfo.numlumps); + } +#endif +#endif + + nTexFiles++; + hlassume(nTexFiles < MAX_TEXFILES, assume_MAX_TEXFILES); + } + + //Log("num of used textures: %i\n", g_numUsedTextures); + +#ifndef HLCSG_STRIPWADPATH + // This is absurd. --vluzacn + // AJM: Tommy suggested i add this warning message in, and it certianly doesnt + // hurt to be cautious. Especially one of the possible side effects he mentioned was svc_bad + if (nTexFiles > 8) + { + Log("\n"); + Warning("More than 8 wadfiles are in use. (%i)\n" + "This may be harmless, and if no strange side effects are occurring, then\n" + "it can safely be ignored. However, if your map starts exhibiting strange\n" + "or obscure errors, consider this as suspect.\n" + , nTexFiles); + } +#endif + + // sort texlumps in memory by name + qsort((void*)lumpinfo, (size_t) nTexLumps, sizeof(lumpinfo[0]), lump_sorter_by_name); + +#ifndef HLCSG_AUTOWAD_NEW +#ifdef HLCSG_CHART_FIX + Log("\n"); +#ifdef HLCSG_ONLYENTS_NOWADCHANGE + if (*szTmpWad) + { + Log ("Wad files required to run the map: \"%s\"\n", szTmpWad); + } + else + { + Log ("Wad files required to run the map: (None)\n"); + } +#else + Log("\"wad\" is \"%s\"\n", szTmpWad); +#endif +#endif + SetKeyValue(&g_entities[0], "wad", szTmpWad); + + Log("\n"); +#endif + CheckFatal(); + return true; +} + +// ===================================================================================== +// FindTexture +// ===================================================================================== +lumpinfo_t* FindTexture(const lumpinfo_t* const source) +{ + //Log("** PnFNFUNC: FindTexture\n"); + + lumpinfo_t* found = NULL; + + found = (lumpinfo_t*)bsearch(source, (void*)lumpinfo, (size_t) nTexLumps, sizeof(lumpinfo[0]), lump_sorter_by_name); + if (!found) + { + Warning("::FindTexture() texture %s not found!", source->name); + if (!strcmp(source->name, "NULL") +#ifdef HLCSG_PASSBULLETSBRUSH + || !strcmp (source->name, "SKIP") +#endif + ) + { + Log("Are you sure you included zhlt.wad in your wadpath list?\n"); + } + } + +#ifdef HLCSG_AUTOWAD_NEW + if (found) + { + // get the first and last matching lump + lumpinfo_t *first = found; + lumpinfo_t *last = found; + while (first - 1 >= lumpinfo && lump_sorter_by_name (first - 1, source) == 0) + { + first = first - 1; + } + while (last + 1 < lumpinfo + nTexLumps && lump_sorter_by_name (last + 1, source) == 0) + { + last = last + 1; + } + // find the best matching lump + lumpinfo_t *best = NULL; + for (found = first; found < last + 1; found++) + { + bool better = false; + if (best == NULL) + { + better = true; + } + else if (found->iTexFile != best->iTexFile) + { + wadpath_t *found_wadpath = texwadpathes[found->iTexFile]; + wadpath_t *best_wadpath = texwadpathes[best->iTexFile]; + if (found_wadpath->usedbymap != best_wadpath->usedbymap) + { + better = !found_wadpath->usedbymap; // included wad is better + } + else + { + better = found->iTexFile < best->iTexFile; // upper in the wad list is better + } + } + else if (found->filepos != best->filepos) + { + better = found->filepos < best->filepos; // when there are several lumps with the same name in one wad file + } + + if (better) + { + best = found; + } + } + found = best; + } +#endif + return found; +} + +// ===================================================================================== +// LoadLump +// ===================================================================================== +int LoadLump(const lumpinfo_t* const source, byte* dest, int* texsize +#ifdef HLCSG_FILEREADFAILURE_FIX + , int dest_maxsize +#endif +#ifdef ZHLT_NOWADDIR + , byte *&writewad_data, int &writewad_datasize +#endif + ) +{ +#ifdef ZHLT_NOWADDIR + writewad_data = NULL; + writewad_datasize = -1; +#endif + //Log("** PnFNFUNC: LoadLump\n"); + + *texsize = 0; + if (source->filepos) + { + if (fseek(texfiles[source->iTexFile], source->filepos, SEEK_SET)) + { + Warning("fseek to %d failed\n", source->filepos); +#ifdef HLCSG_FILEREADFAILURE_FIX + Error ("File read failure"); +#endif + } + *texsize = source->disksize; + +#ifdef HLCSG_AUTOWAD_NEW + if (texwadpathes[source->iTexFile]->usedbymap) +#else + bool wadinclude = false; + std::map< int, bool >::iterator it; + it = s_WadIncludeMap.find(source->iTexFile); + if (it != s_WadIncludeMap.end()) + { + wadinclude = it->second; + } + + // Should we just load the texture header w/o the palette & bitmap? + if (g_wadtextures && !wadinclude) +#endif + { + // Just read the miptex header and zero out the data offsets. + // We will load the entire texture from the WAD at engine runtime + int i; + miptex_t* miptex = (miptex_t*)dest; +#ifdef HLCSG_FILEREADFAILURE_FIX + hlassume ((int)sizeof (miptex_t) <= dest_maxsize, assume_MAX_MAP_MIPTEX); +#endif + SafeRead(texfiles[source->iTexFile], dest, sizeof(miptex_t)); + + for (i = 0; i < MIPLEVELS; i++) + miptex->offsets[i] = 0; +#ifdef ZHLT_NOWADDIR + writewad_data = (byte *)malloc (source->disksize); + hlassume (writewad_data != NULL, assume_NoMemory); + if (fseek (texfiles[source->iTexFile], source->filepos, SEEK_SET)) + Error ("File read failure"); + SafeRead (texfiles[source->iTexFile], writewad_data, source->disksize); + writewad_datasize = source->disksize; +#endif + return sizeof(miptex_t); + } + else + { + Developer(DEVELOPER_LEVEL_MESSAGE,"Including texture %s\n",source->name); + // Load the entire texture here so the BSP contains the texture +#ifdef HLCSG_FILEREADFAILURE_FIX + hlassume (source->disksize <= dest_maxsize, assume_MAX_MAP_MIPTEX); +#endif + SafeRead(texfiles[source->iTexFile], dest, source->disksize); + return source->disksize; + } + } + +#ifdef HLCSG_ERROR_MISSINGTEXTURE + Error("::LoadLump() texture %s not found!", source->name); +#else + Warning("::LoadLump() texture %s not found!", source->name); +#endif + return 0; +} + +// ===================================================================================== +// AddAnimatingTextures +// ===================================================================================== +void AddAnimatingTextures() +{ + int base; + int i, j, k; + char name[MAXWADNAME]; + + base = nummiptex; + + for (i = 0; i < base; i++) + { + if ((miptex[i].name[0] != '+') && (miptex[i].name[0] != '-')) + { + continue; + } + + safe_strncpy(name, miptex[i].name, MAXWADNAME); + + for (j = 0; j < 20; j++) + { + if (j < 10) + { + name[1] = '0' + j; + } + else + { + name[1] = 'A' + j - 10; // alternate animation + } + + // see if this name exists in the wadfile + for (k = 0; k < nTexLumps; k++) + { + if (!strcmp(name, lumpinfo[k].name)) + { + FindMiptex(name); // add to the miptex list + break; + } + } + } + } + + if (nummiptex - base) + { + Log("added %i additional animating textures.\n", nummiptex - base); + } +} + +#ifndef HLCSG_AUTOWAD_NEW +// ===================================================================================== +// GetWadPath +// AJM: this sub is obsolete +// ===================================================================================== +char* GetWadPath() +{ + const char* path = ValueForKey(&g_entities[0], "_wad"); + + if (!path || !path[0]) + { + path = ValueForKey(&g_entities[0], "wad"); + if (!path || !path[0]) + { + Warning("no wadfile specified"); + return strdup(""); + } + } + + return strdup(path); +} +#endif + +// ===================================================================================== +// WriteMiptex +// ===================================================================================== +void WriteMiptex() +{ + int len, texsize, totaltexsize = 0; + byte* data; + dmiptexlump_t* l; + double start, end; + + g_texdatasize = 0; + + start = I_FloatTime(); + { + if (!TEX_InitFromWad()) + return; + + AddAnimatingTextures(); + } + end = I_FloatTime(); + Verbose("TEX_InitFromWad & AddAnimatingTextures elapsed time = %ldms\n", (long)(end - start)); + + start = I_FloatTime(); + { + int i; + + for (i = 0; i < nummiptex; i++) + { + lumpinfo_t* found; + + found = FindTexture(miptex + i); + if (found) + { + miptex[i] = *found; +#ifdef HLCSG_AUTOWAD_NEW + texwadpathes[found->iTexFile]->usedtextures++; +#endif + } + else + { + miptex[i].iTexFile = miptex[i].filepos = miptex[i].disksize = 0; + } + } + } + end = I_FloatTime(); + Verbose("FindTextures elapsed time = %ldms\n", (long)(end - start)); + +#ifdef HLCSG_AUTOWAD_NEW + // Now we have filled lumpinfo for each miptex and the number of used textures for each wad. + { + char szTmpWad[MAX_VAL]; + int i; + + szTmpWad[0] = 0; + for (i = 0; i < nTexFiles; i++) + { + wadpath_t *currentwad = texwadpathes[i]; + if (!currentwad->usedbymap && (currentwad->usedtextures > 0 || !g_bWadAutoDetect)) + { + Log ("Including Wadfile: %s\n", currentwad->path); + double percused = (double)currentwad->usedtextures / (double)nummiptex * 100; + Log (" - Contains %i used texture%s, %2.2f percent of map (%d textures in wad)\n", + currentwad->usedtextures, currentwad->usedtextures == 1? "": "s", percused, currentwad->totaltextures); + } + } + for (i = 0; i < nTexFiles; i++) + { + wadpath_t *currentwad = texwadpathes[i]; + if (currentwad->usedbymap && (currentwad->usedtextures > 0 || !g_bWadAutoDetect)) + { + Log ("Using Wadfile: %s\n", currentwad->path); + double percused = (double)currentwad->usedtextures / (double)nummiptex * 100; + Log (" - Contains %i used texture%s, %2.2f percent of map (%d textures in wad)\n", + currentwad->usedtextures, currentwad->usedtextures == 1? "": "s", percused, currentwad->totaltextures); + #ifdef HLCSG_STRIPWADPATH + char tmp[_MAX_PATH]; + ExtractFile (currentwad->path, tmp); + safe_strncat (szTmpWad, tmp, MAX_VAL); + #else + safe_strncat (szTmpWad, currentwad->path, MAX_VAL); + #endif + safe_strncat (szTmpWad, ";", MAX_VAL); + } + } + + #ifdef HLCSG_CHART_FIX + Log("\n"); + if (*szTmpWad) + { + Log ("Wad files required to run the map: \"%s\"\n", szTmpWad); + } + else + { + Log ("Wad files required to run the map: (None)\n"); + } + #endif + SetKeyValue(&g_entities[0], "wad", szTmpWad); + } + +#endif + start = I_FloatTime(); + { + int i; + texinfo_t* tx = g_texinfo; + + // Sort them FIRST by wadfile and THEN by name for most efficient loading in the engine. + qsort((void*)miptex, (size_t) nummiptex, sizeof(miptex[0]), lump_sorter_by_wad_and_name); + + // Sleazy Hack 104 Pt 2 - After sorting the miptex array, reset the texinfos to point to the right miptexs + for (i = 0; i < g_numtexinfo; i++, tx++) + { +#ifdef HLCSG_TEXMAP64_FIX + char* miptex_name = texmap_retrieve(tx->miptex); +#else + char* miptex_name = texmap64_retrieve(tx->miptex); +#endif + + tx->miptex = FindMiptex(miptex_name); + +#ifndef HLCSG_TEXMAP64_FIX + // Free up the originally strdup()'ed miptex_name + free(miptex_name); +#endif + } +#ifdef HLCSG_TEXMAP64_FIX + texmap_clear (); +#endif + } + end = I_FloatTime(); + Verbose("qsort(miptex) elapsed time = %ldms\n", (long)(end - start)); + + start = I_FloatTime(); + { + int i; + + // Now setup to get the miptex data (or just the headers if using -wadtextures) from the wadfile + l = (dmiptexlump_t*)g_dtexdata; + data = (byte*) & l->dataofs[nummiptex]; + l->nummiptex = nummiptex; +#ifdef ZHLT_NOWADDIR + char writewad_name[_MAX_PATH]; + FILE *writewad_file; + int writewad_maxlumpinfos; + typedef struct + { + int filepos; + int disksize; + int size; + char type; + char compression; + char pad1, pad2; + char name[MAXWADNAME]; + } dlumpinfo_t; + dlumpinfo_t *writewad_lumpinfos; + wadinfo_t writewad_header; + safe_snprintf (writewad_name, _MAX_PATH, "%s.wa_", g_Mapname); + writewad_file = SafeOpenWrite (writewad_name); + writewad_maxlumpinfos = nummiptex; + writewad_lumpinfos = (dlumpinfo_t *)malloc (writewad_maxlumpinfos * sizeof (dlumpinfo_t)); + hlassume (writewad_lumpinfos != NULL, assume_NoMemory); + writewad_header.identification[0] = 'W'; + writewad_header.identification[1] = 'A'; + writewad_header.identification[2] = 'D'; + writewad_header.identification[3] = '3'; + writewad_header.numlumps = 0; + if (fseek (writewad_file, sizeof (wadinfo_t), SEEK_SET)) + Error ("File write failure"); +#endif + for (i = 0; i < nummiptex; i++) + { + l->dataofs[i] = data - (byte*) l; +#ifdef ZHLT_NOWADDIR + byte *writewad_data; + int writewad_datasize; + len = LoadLump (miptex + i, data, &texsize +#ifdef HLCSG_FILEREADFAILURE_FIX + , &g_dtexdata[g_max_map_miptex] - data +#endif + , writewad_data, writewad_datasize); + if (writewad_data) + { + dlumpinfo_t *writewad_lumpinfo = &writewad_lumpinfos[writewad_header.numlumps]; + writewad_lumpinfo->filepos = ftell (writewad_file); + writewad_lumpinfo->disksize = writewad_datasize; + writewad_lumpinfo->size = miptex[i].size; + writewad_lumpinfo->type = miptex[i].type; + writewad_lumpinfo->compression = miptex[i].compression; + writewad_lumpinfo->pad1 = miptex[i].pad1; + writewad_lumpinfo->pad2 = miptex[i].pad2; + memcpy (writewad_lumpinfo->name, miptex[i].name, MAXWADNAME); + writewad_header.numlumps++; + SafeWrite (writewad_file, writewad_data, writewad_datasize); + free (writewad_data); + } +#else + len = LoadLump(miptex + i, data, &texsize +#ifdef HLCSG_FILEREADFAILURE_FIX + , &g_dtexdata[g_max_map_miptex] - data +#endif + ); +#endif + + if (!len) + { + l->dataofs[i] = -1; // didn't find the texture + } + else + { + totaltexsize += texsize; + + hlassume(totaltexsize < g_max_map_miptex, assume_MAX_MAP_MIPTEX); + } + data += len; + } + g_texdatasize = data - g_dtexdata; +#ifdef ZHLT_NOWADDIR + writewad_header.infotableofs = ftell (writewad_file); + SafeWrite (writewad_file, writewad_lumpinfos, writewad_header.numlumps * sizeof (dlumpinfo_t)); + if (fseek (writewad_file, 0, SEEK_SET)) + Error ("File write failure"); + SafeWrite (writewad_file, &writewad_header, sizeof (wadinfo_t)); + if (fclose (writewad_file)) + Error ("File write failure"); +#endif + } + end = I_FloatTime(); + Log("Texture usage is at %1.2f mb (of %1.2f mb MAX)\n", (float)totaltexsize / (1024 * 1024), + (float)g_max_map_miptex / (1024 * 1024)); + Verbose("LoadLump() elapsed time = %ldms\n", (long)(end - start)); +} + +//========================================================================== + +// ===================================================================================== +// TexinfoForBrushTexture +// ===================================================================================== +int TexinfoForBrushTexture(const plane_t* const plane, brush_texture_t* bt, const vec3_t origin +#ifdef ZHLT_HIDDENSOUNDTEXTURE + , bool shouldhide +#endif + ) +{ + vec3_t vecs[2]; + int sv, tv; + vec_t ang, sinv, cosv; + vec_t ns, nt; + texinfo_t tx; + texinfo_t* tc; + int i, j, k; + +#ifdef HLCSG_HLBSP_VOIDTEXINFO + if (!strncasecmp(bt->name, "NULL", 4)) + { + return -1; + } +#endif + memset(&tx, 0, sizeof(tx)); +#ifndef HLCSG_CUSTOMHULL +#ifdef HLCSG_PRECISIONCLIP + if(!strncmp(bt->name,"BEVEL",5)) + { + tx.flags |= TEX_BEVEL; + safe_strncpy(bt->name,"NULL",5); + } +#endif +#endif +#ifndef HLCSG_AUTOWAD_NEW +#ifdef HLCSG_AUTOWAD_TEXTURELIST_FIX + ThreadLock (); + autowad_PushName (bt->name); + ThreadUnlock (); +#endif +#endif +#ifdef HLCSG_TEXMAP64_FIX + FindMiptex (bt->name); +#else + tx.miptex = FindMiptex(bt->name); + + // Note: FindMiptex() still needs to be called here to add it to the global miptex array + + // Very Sleazy Hack 104 - since the tx.miptex index will be bogus after we sort the miptex array later + // Put the string name of the miptex in this "index" until after we are done sorting it in WriteMiptex(). + tx.miptex = texmap64_store(strdup(bt->name)); +#endif + + // set the special flag + if (bt->name[0] == '*' + || !strncasecmp(bt->name, "sky", 3) + +// ===================================================================================== +//Cpt_Andrew - Env_Sky Check +// ===================================================================================== + || !strncasecmp(bt->name, "env_sky", 5) +// ===================================================================================== + +#ifndef HLCSG_CUSTOMHULL + || !strncasecmp(bt->name, "clip", 4) +#endif + || !strncasecmp(bt->name, "origin", 6) +#ifdef ZHLT_NULLTEX // AJM + || !strncasecmp(bt->name, "null", 4) +#endif + || !strncasecmp(bt->name, "aaatrigger", 10) + ) + { + // actually only 'sky' and 'aaatrigger' needs this. --vluzacn + tx.flags |= TEX_SPECIAL; + } +#ifdef ZHLT_HIDDENSOUNDTEXTURE + if (shouldhide) + { + tx.flags |= TEX_SHOULDHIDE; + } +#endif + + if (bt->txcommand) + { + memcpy(tx.vecs, bt->vects.quark.vects, sizeof(tx.vecs)); + if (origin[0] || origin[1] || origin[2]) + { + tx.vecs[0][3] += DotProduct(origin, tx.vecs[0]); + tx.vecs[1][3] += DotProduct(origin, tx.vecs[1]); + } + } + else + { + if (g_nMapFileVersion < 220) + { + TextureAxisFromPlane(plane, vecs[0], vecs[1]); + } + + if (!bt->vects.valve.scale[0]) + { + bt->vects.valve.scale[0] = 1; + } + if (!bt->vects.valve.scale[1]) + { + bt->vects.valve.scale[1] = 1; + } + + if (g_nMapFileVersion < 220) + { + // rotate axis + if (bt->vects.valve.rotate == 0) + { + sinv = 0; + cosv = 1; + } + else if (bt->vects.valve.rotate == 90) + { + sinv = 1; + cosv = 0; + } + else if (bt->vects.valve.rotate == 180) + { + sinv = 0; + cosv = -1; + } + else if (bt->vects.valve.rotate == 270) + { + sinv = -1; + cosv = 0; + } + else + { + ang = bt->vects.valve.rotate / 180 * Q_PI; + sinv = sin(ang); + cosv = cos(ang); + } + + if (vecs[0][0]) + { + sv = 0; + } + else if (vecs[0][1]) + { + sv = 1; + } + else + { + sv = 2; + } + + if (vecs[1][0]) + { + tv = 0; + } + else if (vecs[1][1]) + { + tv = 1; + } + else + { + tv = 2; + } + + for (i = 0; i < 2; i++) + { + ns = cosv * vecs[i][sv] - sinv * vecs[i][tv]; + nt = sinv * vecs[i][sv] + cosv * vecs[i][tv]; + vecs[i][sv] = ns; + vecs[i][tv] = nt; + } + + for (i = 0; i < 2; i++) + { + for (j = 0; j < 3; j++) + { + tx.vecs[i][j] = vecs[i][j] / bt->vects.valve.scale[i]; + } + } + } + else + { + vec_t scale; + + scale = 1 / bt->vects.valve.scale[0]; + VectorScale(bt->vects.valve.UAxis, scale, tx.vecs[0]); + + scale = 1 / bt->vects.valve.scale[1]; + VectorScale(bt->vects.valve.VAxis, scale, tx.vecs[1]); + } + + tx.vecs[0][3] = bt->vects.valve.shift[0] + DotProduct(origin, tx.vecs[0]); + tx.vecs[1][3] = bt->vects.valve.shift[1] + DotProduct(origin, tx.vecs[1]); + } + + // + // find the g_texinfo + // + ThreadLock(); + tc = g_texinfo; + for (i = 0; i < g_numtexinfo; i++, tc++) + { + // Sleazy hack 104, Pt 3 - Use strcmp on names to avoid dups +#ifdef HLCSG_TEXMAP64_FIX + if (strcmp (texmap_retrieve (tc->miptex), bt->name) != 0) +#else + if (strcmp(texmap64_retrieve((tc->miptex)), texmap64_retrieve((tx.miptex))) != 0) +#endif + { + continue; + } + if (tc->flags != tx.flags) + { + continue; + } + for (j = 0; j < 2; j++) + { + for (k = 0; k < 4; k++) + { + if (tc->vecs[j][k] != tx.vecs[j][k]) + { + goto skip; + } + } + } + ThreadUnlock(); + return i; +skip:; + } + +#ifdef HLCSG_HLBSP_REDUCETEXTURE + hlassume(g_numtexinfo < MAX_INTERNAL_MAP_TEXINFO, assume_MAX_MAP_TEXINFO); +#else + hlassume(g_numtexinfo < MAX_MAP_TEXINFO, assume_MAX_MAP_TEXINFO); +#endif + + *tc = tx; +#ifdef HLCSG_TEXMAP64_FIX + tc->miptex = texmap_store (bt->name, false); +#endif + g_numtexinfo++; + ThreadUnlock(); + return i; +} + +#ifdef HLCSG_HLBSP_VOIDTEXINFO +// Before WriteMiptex(), for each texinfo in g_texinfo, .miptex is a string rather than texture index, so this function should be used instead of GetTextureByNumber. +const char *GetTextureByNumber_CSG(int texturenumber) +{ + if (texturenumber == -1) + return ""; +#ifdef HLCSG_TEXMAP64_FIX + return texmap_retrieve (g_texinfo[texturenumber].miptex); +#else + return texmap64_retrieve (g_texinfo[texturenumber].miptex); +#endif +} +#endif diff --git a/src/zhlt-vluzacn/hlcsg/wadcfg.cpp b/src/zhlt-vluzacn/hlcsg/wadcfg.cpp new file mode 100644 index 0000000..3bfea60 --- /dev/null +++ b/src/zhlt-vluzacn/hlcsg/wadcfg.cpp @@ -0,0 +1,590 @@ +// AJM: ADDED THIS ENTIRE FILE IN + +#include "csg.h" +#ifdef HLCSG_WADCFG_NEW +void LoadWadconfig (const char *filename, const char *configname) +{ + Log ("Loading wad configuration '%s' from '%s' :\n", configname, filename); + int found = 0; + int count = 0; + int size; + char *buffer; + size = LoadFile (filename, &buffer); + ParseFromMemory (buffer, size); + while (GetToken (true)) + { + bool skip = true; + if (!strcasecmp (g_token, configname)) + { + skip = false; + found++; + } + if (!GetToken (true) || strcasecmp (g_token, "{")) + { + Error ("parsing '%s': missing '{'.", filename); + } + while (1) + { + if (!GetToken (true)) + { + Error ("parsing '%s': unexpected end of file.", filename); + } + if (!strcasecmp (g_token, "}")) + { + break; + } + if (skip) + { + continue; + } + Log (" "); + bool include = false; + if (!strcasecmp (g_token, "include")) + { + Log ("include "); + include = true; + if (!GetToken (true)) + { + Error ("parsing '%s': unexpected end of file.", filename); + } + } + Log ("\"%s\"\n", g_token); + if (g_iNumWadPaths >= MAX_WADPATHS) + { + Error ("parsing '%s': too many wad files.", filename); + } + count++; +#ifdef HLCSG_AUTOWAD_NEW + PushWadPath (g_token, !include); +#else + wadpath_t *current = (wadpath_t *)malloc (sizeof (wadpath_t)); + hlassume (current != NULL, assume_NoMemory); + g_pWadPaths[g_iNumWadPaths] = current; + g_iNumWadPaths++; + safe_strncpy (current->path, g_token, _MAX_PATH); + current->usedbymap = true; // what's this? + current->usedtextures = 0; + if (include) + { + Developer (DEVELOPER_LEVEL_MESSAGE, "LoadWadcfgfile: including '%s'.\n", current->path); + g_WadInclude.push_back(current->path); + } +#endif + } + } + if (found == 0) + { + Error ("Couldn't find wad configuration '%s' in file '%s'.\n", configname, filename); + } + if (found >= 2) + { + Error ("Found more than one wad configuration for '%s' in file '%s'.\n", configname, filename); + } + free (buffer); // should not be freed because it is still being used as script buffer + //Log ("Using custom wadfile configuration: '%s' (with %i wad%s)\n", configname, count, count > 1 ? "s" : ""); +} +void LoadWadcfgfile (const char *filename) +{ + Log ("Loading wad configuration file '%s' :\n", filename); + int count = 0; + int size; + char *buffer; + size = LoadFile (filename, &buffer); + ParseFromMemory (buffer, size); + while (GetToken (true)) + { + Log (" "); + bool include = false; + if (!strcasecmp (g_token, "include")) + { + Log ("include "); + include = true; + if (!GetToken (true)) + { + Error ("parsing '%s': unexpected end of file.", filename); + } + } + Log ("\"%s\"\n", g_token); + if (g_iNumWadPaths >= MAX_WADPATHS) + { + Error ("parsing '%s': too many wad files.", filename); + } + count++; +#ifdef HLCSG_AUTOWAD_NEW + PushWadPath (g_token, !include); +#else + wadpath_t *current = (wadpath_t *)malloc (sizeof (wadpath_t)); + hlassume (current != NULL, assume_NoMemory); + g_pWadPaths[g_iNumWadPaths] = current; + g_iNumWadPaths++; + safe_strncpy (current->path, g_token, _MAX_PATH); + current->usedbymap = true; // what's this? + current->usedtextures = 0; + if (include) + { + Developer (DEVELOPER_LEVEL_MESSAGE, "LoadWadcfgfile: including '%s'.\n", current->path); + g_WadInclude.push_back(current->path); + } +#endif + } + free (buffer); // should not be freed because it is still being used as script buffer + //Log ("Using custom wadfile configuration: '%s' (with %i wad%s)\n", filename, count, count > 1 ? "s" : ""); +} +#else +#ifdef HLCSG_WADCFG + +//#ifdef SYSTEM_WIN32 +#if defined(SYSTEM_WIN32) && !defined( __GNUC__ ) +# include "atlbase.h" // win32 registry API +#else +char *g_apppath = NULL; //JK: Stores application path +#endif + +#define WAD_CONFIG_FILE "wad.cfg" + +typedef struct wadname_s +{ + char wadname[_MAX_PATH]; + bool wadinclude; + struct wadname_s* next; +} wadname_t; + +typedef struct wadconfig_s +{ + char name[_MAX_PATH]; + int entries; + wadname_t* firstentry; + wadconfig_s* next; +} wadconfig_t; + +wadconfig_t* g_WadCfg; // anchor for the wadconfigurations linked list + +bool g_bWadConfigsLoaded = false; +int g_wadcfglinecount = 1; + +//JK: added in +char *g_wadcfgfile = NULL; + +// little helper function +void stripwadpath (char *str) +{ + char *p = str + strlen (str) - 1; + while ((*p == ' ' || *p == '\n' || *p == '\t') && p >= str) *p-- = '\0'; +} + +// ===================================================================================== +// WadCfgParseError +// ===================================================================================== +void WadCfgParseError(const char* message, int linenum, char* got) +{ + stripwadpath(got); // strip newlines + + Log("Error parsing " WAD_CONFIG_FILE ", line %i:\n" + "%s, got '%s'\n", linenum, message, got); + + Log("If you need help on usage of the wad.cfg file, be sure to check http://www.zhlt.info/using-wad.cfg.html that came" + " in the zip file with these tools.\n"); + + g_bWadConfigsLoaded = false; +} + +// ============================================================================ +// IsWhitespace +// ============================================================================ +bool IsWhitespace(const char ThinkChar) +{ + if ((ThinkChar == ' ') || (ThinkChar == '\t') || (ThinkChar == '\n') || (ThinkChar == 13) /* strange whitespace char */) + return true; + + return false; +} + +// ============================================================================ +// Safe_GetToken +// ============================================================================ +void Safe_GetToken(FILE* source, char* TokenBuffer, const unsigned int MaxBufferLength) +{ + char ThinkChar[1]; // 2 char array = thinkchar and null terminator + bool InToken = false; // are we getting a token? + bool InQuotes = false; // are we in quotes? + bool InComment = false; // are we in a comment (//)? + //fpos_t* sourcepos; + long sourcepos; + + TokenBuffer[0] = '\0'; + ThinkChar[1] = '\0'; + + while(!feof(source)) + { + if (strlen(TokenBuffer) >= MaxBufferLength) + return; // we cant add any more chars onto the buffer, its maxed out + + ThinkChar[0] = fgetc(source); + + if (ThinkChar[0] == '\n') // newline + { + g_wadcfglinecount++; + InComment = false; + } + + if (ThinkChar[0] == 'ÿ') + return; + + if (IsWhitespace(ThinkChar[0]) && !InToken) + continue; // whitespace before token, ignore + + if (ThinkChar[0] == '"') // quotes + { + if(InQuotes) + InQuotes = false; + else + InQuotes = true; + continue; // dont include quotes + } + + if (ThinkChar[0] == '/') + { + sourcepos = ftell(source); + // might be a comment, see if the next char is a forward slash + if (fgetc(source) == '/') // if it is, were in a comment + { + InComment = true; + continue; + } + else // if not, rewind pretend nothing happened... + { + fseek(source, sourcepos, 0); + } + } + + if ( + (IsWhitespace(ThinkChar[0]) && InToken && !InQuotes) // whitespace AFTER token and not in quotes + || (InToken && InComment) // we hit a comment, and we have our token + ) + { + //printf("[gt: %s]\n", TokenBuffer); + return; + } + + if (!InComment) + { + strcat(TokenBuffer, ThinkChar); + InToken = true; + } + } +} + +// ===================================================================================== +// GetWadConfig +// return true if we didnt encounter any fatal errors +#define MAX_TOKENBUFFER _MAX_PATH +// ===================================================================================== +bool GetWadConfig(FILE* wadcfg, wadconfig_t* wadconfig) +{ + char TokenBuffer[MAX_TOKENBUFFER]; + wadname_t* current; + wadname_t* previous; + + while (!feof(wadcfg)) + { + Safe_GetToken(wadcfg, TokenBuffer, MAX_TOKENBUFFER); + + if (!strcmp(TokenBuffer, "}")) + return true; // no more work to do + + if (!strcmp(TokenBuffer, ";")) + continue; // old seperator, no longer used but here for backwards compadibility + + if (!strcmp(TokenBuffer, "{")) // wtf + { + WadCfgParseError("Expected wadpath (Nested blocks illegal)", g_wadcfglinecount, TokenBuffer); + return false; + } + + // otherwise assume its a wadpath, make an entry in this configuration + current = (wadname_t*)malloc(sizeof(wadname_t)); + wadconfig->entries++; + current->next = NULL; + current->wadinclude = false; + + if (!strcmp(TokenBuffer, "include")) + { + current->wadinclude = true; + Safe_GetToken(wadcfg, TokenBuffer, MAX_TOKENBUFFER); + } + + strcpy_s(current->wadname, TokenBuffer); + + if (!wadconfig->firstentry) + { + wadconfig->firstentry = current; + } + else + { + previous->next = current; + } + + previous = current; + previous->next = NULL; + } + + safe_snprintf(TokenBuffer, MAX_TOKENBUFFER, "Unexptected end of file encountered while parsing configuration '%s'", wadconfig->name); + WadCfgParseError(TokenBuffer, g_wadcfglinecount, "(eof)"); + return false; +} +#undef MAX_TOKENBUFFER + +// ===================================================================================== +// LoadWadConfigFile +// ===================================================================================== +void LoadWadConfigFile() +{ + FILE* wadcfg; + wadconfig_t* current; + wadconfig_t* previous; + + char temp[_MAX_PATH]; + + //JK: If exists lets use user-defined file + if (g_wadcfgfile && q_exists(g_wadcfgfile)) + { + wadcfg = fopen(g_wadcfgfile, "r"); + } + else // find the wad.cfg file + { + char appdir[_MAX_PATH]; + char tmp[_MAX_PATH]; + + memset(tmp, 0, sizeof(tmp)); + memset(appdir, 0, sizeof(appdir)); + + // Get application directory (only an approximation on posix systems) + // try looking in the directory we were run from + #ifdef SYSTEM_WIN32 + GetModuleFileName(NULL, tmp, _MAX_PATH); + #else + safe_strncpy(tmp, g_apppath, _MAX_PATH); + #endif + + ExtractFilePath(tmp, appdir); + safe_snprintf(tmp, _MAX_PATH, "%s%s", appdir, WAD_CONFIG_FILE); + + #ifdef _DEBUG + Log("[dbg] Trying '%s'\n", tmp); + #endif + + if (!q_exists(tmp)) + { + // try the Half-Life directory + /*#ifdef SYSTEM_WIN32 + { + HKEY HLMachineKey; + DWORD disposition; + DWORD dwType, dwSize; + + // REG: create machinekey + RegCreateKeyEx(HKEY_LOCAL_MACHINE, TEXT("Software\\Valve\\Half-Life"), 0, NULL, + 0, 0, NULL, &HLMachineKey, &disposition); + // REG: get hl dir + dwType = REG_SZ; + dwSize = _MAX_PATH; + RegQueryValueEx(HLMachineKey, TEXT("InstallPath"), NULL, &dwType, (PBYTE)&appdir, &dwSize); + } + + safe_strncpy(tmp, appdir, _MAX_PATH); + safe_strncat(tmp, SYSTEM_SLASH_STR, _MAX_PATH); // system slash pointless as this will only work on win32, but anyway... + safe_strncat(tmp, WAD_CONFIG_FILE, _MAX_PATH); + + #ifdef _DEBUG + Log("[dbg] Trying '%s'\n", tmp); + #endif + + if (!q_exists(tmp)) + #endif // SYSTEM_WIN32*/ // not a good idea. --vluzacn + { + // still cant find it, error out + Log("Warning: could not find wad configurations file\n" + /*"Make sure that wad.cfg is in the Half-Life directory or the current working directory\n"*/ + ); + return; + } + } + + wadcfg = fopen(tmp, "r"); + } + + if (!wadcfg) + { + // cant open it, die + Log("Warning: could not open the wad configurations file\n" + "Make sure that wad.cfg is in the Half-Life directory or the current working directory\n" + ); + return; + } + + while(!feof(wadcfg)) + { + Safe_GetToken(wadcfg, temp, MAX_WAD_CFG_NAME); + + if (!strcmp(temp, "HalfLifePath=")) // backwards compadibitly + { + Safe_GetToken(wadcfg, temp, MAX_WAD_CFG_NAME); + Warning("Redundant line in " WAD_CONFIG_FILE ": \"HalfLifePath= %s\" - Ignoring...\n", + temp); + continue; + } + + if (feof(wadcfg)) + break; + + // assume its the name of a configuration + current = (wadconfig_t*)malloc(sizeof(wadconfig_t)); + safe_strncpy(current->name, temp, _MAX_PATH); + current->entries = 0; + current->next = NULL; + current->firstentry = NULL; + + // make sure the next char starts a wad configuration + Safe_GetToken(wadcfg, temp, _MAX_PATH); + if (strcmp(temp, "{")) + { + WadCfgParseError("Expected start of wadfile configuration, '{'", g_wadcfglinecount, temp); + //Log("[dbg] temp[0] is %i\n", temp[0]); + fclose(wadcfg); + return; + } + + // otherwise were ok, get the definition + if (!GetWadConfig(wadcfg, current)) + { + fclose(wadcfg); + return; + } + + // add this config to the list + if (!g_WadCfg) + { + g_WadCfg = current; + } + else + { + previous->next = current; + } + + previous = current; + previous->next = NULL; + } + + g_bWadConfigsLoaded = true; +} + +// ===================================================================================== +// ProcessWadConfiguration +// ===================================================================================== +void ProcessWadConfiguration() +{ + int usedwads = 0; + char szTmp[1024]; // arbitrary, but needs to be large. probably bigger than this. + wadconfig_t* config; + wadname_t* path; + + if(!g_bWadConfigsLoaded) // we did a graceful exit due to some warning/error, so dont complain about it + { + Log("Using mapfile wad configuration\n"); + return; + } + + szTmp[0] = 0; + config = g_WadCfg; + + if (!wadconfigname) + return; // this should never happen + + if (!config) + { + Warning("No configurations detected in wad.cfg\n" + "using map wad configuration"); + return; + } + + while (1) + { + if (!strcmp(config->name, wadconfigname)) + { + path = config->firstentry; + while (1) + { + if (!path) + break; + + Verbose("Wadpath from wad.cfg: '%s'\n", path->wadname); + PushWadPath(path->wadname, true); + safe_snprintf(szTmp, 1024, "%s%s;", szTmp, path->wadname); + usedwads++; + + if (path->wadinclude) + g_WadInclude.push_back(path->wadname); + + // next path + path = path->next; + } + break; // the user can only specify one wad configuration, were done here + } + + // next config + config = config->next; + if (!config) + break; + } + + if (usedwads) + { + Log("Using custom wadfile configuration: '%s' (with %i wad%s)\n", wadconfigname, usedwads, usedwads > 1 ? "s" : ""); + SetKeyValue(&g_entities[0], "wad", szTmp); + SetKeyValue(&g_entities[0], "_wad", szTmp); + } + else + { + Warning("no wadfiles are specified in configuration '%s' --\n" + "Using map wadfile configuration", wadconfigname); + g_bWadConfigsLoaded = false; + } + + return; +} + +// ===================================================================================== +// WadCfg_cleanup +// ===================================================================================== +void WadCfg_cleanup() +{ + wadconfig_t* config; + wadconfig_t* nextconfig; + wadname_t* path; + wadname_t* nextpath; + + config = g_WadCfg; + while (config) + { + path = config->firstentry; + while (path) + { + nextpath = path->next; + free(path); + path = nextpath; + } + + nextconfig = config->next; + free(config); + config = nextconfig; + } + + g_WadCfg = NULL; + g_bWadConfigsLoaded = false; + return; +} + + + +#endif // HLCSG_WADCFG +#endif diff --git a/src/zhlt-vluzacn/hlcsg/wadinclude.cpp b/src/zhlt-vluzacn/hlcsg/wadinclude.cpp new file mode 100644 index 0000000..ab6814b --- /dev/null +++ b/src/zhlt-vluzacn/hlcsg/wadinclude.cpp @@ -0,0 +1,211 @@ +#pragma warning(disable: 4267) // 'size_t' to 'unsigned int', possible loss of data + +#include "csg.h" + +#ifndef HLCSG_ONLYENTS_NOWADCHANGE +#ifdef HAVE_UNISTD_E +#include +#endif + +void LoadWadincludeFile(const char* const filename) +{ + char* fname; + int i, x; + char* pData = NULL; + char* pszData; + unsigned len = strlen(filename) + 5; + + fname = (char*)Alloc(len); + safe_snprintf(fname, len, "%s.wic", filename); + + if (q_exists(fname)) + { + i = LoadFile(fname, &pData); + + if (i == 0) + { + goto LoadWadincludeFileReturn; + } + } + else + { + Warning("WadInclude file %s does not exist", fname); + goto LoadWadincludeFileReturn; + } + + for (pszData = pData, x = 0; x < i; x++) + { + if (pData[x] == ';') + { + pData[x] = 0; + g_WadInclude.push_back(pszData); + pszData = pData + x + 1; + } + } + +LoadWadincludeFileReturn:; + Free(fname); + if (pData) + { + Free(pData); + } +} + +void SaveWadincludeFile(const char* const filename) +{ + char* fname; + FILE* file; + int x; + unsigned len = strlen(filename) + 5; + + fname = (char*)Alloc(len); + safe_snprintf(fname, len, "%s.wic", filename); + + _unlink(fname); + + file = SafeOpenWrite(fname); + + WadInclude_i it; + for (it = g_WadInclude.begin(); it != g_WadInclude.end(); it++) + { + x = it->size(); + if (x) + { + SafeWrite(file, it->c_str(), x); + SafeWrite(file, ";", 1); + } + } + + Free(fname); + fclose(file); +} + +// this function is called in place of tex_initfromwad for onlyents compiles +void HandleWadinclude() +{ + int i; + char szTmpWad[1024]; // arbitrary, but needs to be large. + char* pszWadFile; + wadpath_t* currentwad; + + Log("\n"); // looks cleaner + + szTmpWad[0] = 0; + +#ifdef HLCSG_AUTOWAD + if (g_bWadAutoDetect) + { + autowad_UpdateUsedWads(); + } +#endif + + // for eachwadpath + for (i = 0; i < g_iNumWadPaths; i++) + { + bool bExcludeThisWad = false; + + currentwad = g_pWadPaths[i]; + pszWadFile = currentwad->path; + +#ifdef HLCSG_AUTOWAD/* + #ifdef _DEBUG + Log("[dbg] HandleWIC: attempting to parse wad '%s'\n", currentwad->path); + #endif*/ + if (g_bWadAutoDetect && !currentwad->usedtextures) + continue;/* + #ifdef _DEBUG + Log("[dbg] HandleWIC: parsing wad\n"); + #endif*/ +#endif // HLCSG_AUTOWAD + + // look and see if we're supposed to include the textures from this WAD in the bsp. + WadInclude_i it; + for (it = g_WadInclude.begin(); it != g_WadInclude.end(); it++) + { + if (stristr(pszWadFile, it->c_str())) + { + Log("Including Wadfile: %s\n", pszWadFile); + bExcludeThisWad = true; // wadincluding this one + } + } + + if (!bExcludeThisWad) + { + Log("Using Wadfile: %s\n", pszWadFile); +#ifdef HLCSG_STRIPWADPATH + char tmp[_MAX_PATH]; + ExtractFile (pszWadFile, tmp); + safe_snprintf(szTmpWad, 1024, "%s%s;", szTmpWad, tmp); +#else + safe_snprintf(szTmpWad, 1024, "%s%s;", szTmpWad, pszWadFile); +#endif + } + } + + Log("\"wad\" is \"%s\"\n", szTmpWad); + + SetKeyValue(&g_entities[0], "wad", szTmpWad); + + Log("\n"); + CheckFatal(); +} + +#if 0 +void HandleWadinclude() +{ + // Code somewhat copied from TEX_InitFromWad() + + char szTmpPath[MAXTOKEN]; + char szNewWad[MAXTOKEN]; + char* pszWadFile; + bool bExcludeThisWad; + + const char* path = ValueForKey(&g_entities[0], "wad"); + + szNewWad[0] = 0; + safe_strncpy(szTmpPath, path, MAXTOKEN); + + // temporary kludge so we don't have to deal with no occurances of a semicolon + // in the path name .. + if (strchr(szTmpPath, ';') == NULL) + { + safe_strncat(szTmpPath, ";", MAXTOKEN); + } + + pszWadFile = strtok(szTmpPath, ";"); + + while (pszWadFile) + { + bExcludeThisWad = false; + + // look and see if we're supposed to include the textures from this WAD in the bsp. + WadInclude_i it; + for (it = g_WadInclude.begin(); it != g_WadInclude.end(); it++) + { + if (stristr(pszWadFile, it->c_str())) + { + Log("Embedding textures from WAD File [%s] into BSP\n", pszWadFile); + bExcludeThisWad = true; + } + } + + if (!bExcludeThisWad) + { + safe_strncat(szNewWad, pszWadFile, MAXTOKEN); + safe_strncat(szNewWad, ";", MAXTOKEN); + } + + if (!bExcludeThisWad) + { + Log("Using WAD File: %s\n", pszWadFile); + } + + // next wad file + pszWadFile = strtok(NULL, ";"); + } + + SetKeyValue(&g_entities[0], "wad", szNewWad); +} + +#endif +#endif /*HLCSG_ONLYENTS_NOWADCHANGE*/ diff --git a/src/zhlt-vluzacn/hlcsg/wadpath.cpp b/src/zhlt-vluzacn/hlcsg/wadpath.cpp new file mode 100644 index 0000000..8dadf97 --- /dev/null +++ b/src/zhlt-vluzacn/hlcsg/wadpath.cpp @@ -0,0 +1,174 @@ +// AJM: added this file in + +#include "csg.h" + +wadpath_t* g_pWadPaths[MAX_WADPATHS]; +int g_iNumWadPaths = 0; + + +// ===================================================================================== +// PushWadPath +// adds a wadpath into the wadpaths list, without duplicates +// ===================================================================================== +void PushWadPath(const char* const path, bool inuse) +{ + int i; + wadpath_t* current; + +#ifdef HLCSG_AUTOWAD_NEW + hlassume (g_iNumWadPaths < MAX_WADPATHS, assume_MAX_TEXFILES); +#else + if (!strlen(path)) + return; // no path + + // check for pre-existing path + for (i = 0; i < g_iNumWadPaths; i++) + { + current = g_pWadPaths[i]; + + if (!strcmp(current->path, path)) + return; + } +#endif + + current = (wadpath_t*)malloc(sizeof(wadpath_t)); + + safe_strncpy(current->path, path, _MAX_PATH); + current->usedbymap = inuse; + current->usedtextures = 0; // should be updated later in autowad procedures +#ifdef HLCSG_AUTOWAD_NEW + current->totaltextures = 0; +#endif + + g_pWadPaths[g_iNumWadPaths++] = current; + +#ifdef _DEBUG + Log("[dbg] PushWadPath: %i[%s]\n", g_iNumWadPaths, path); +#endif +} + +#ifndef HLCSG_AUTOWAD_NEW +// ===================================================================================== +// IsUsedWadPath +// ===================================================================================== +bool IsUsedWadPath(const char* const path) +{ + int i; + wadpath_t* current; + + for (i = 0; i < g_iNumWadPaths; i++) + { + current = g_pWadPaths[i]; + if (!strcmp(current->path, path)) + { + if (current->usedbymap) + return true; + + return false; + } + } + + return false; +} + +// ===================================================================================== +// IsListedWadPath +// ===================================================================================== +bool IsListedWadPath(const char* const path) +{ + int i; + wadpath_t* current; + + for (i = 0; i < g_iNumWadPaths; i++) + { + current = g_pWadPaths[i]; + if (!strcmp(current->path, path)) + return true; + } + + return false; +} +#endif + +// ===================================================================================== +// FreeWadPaths +// ===================================================================================== +void FreeWadPaths() +{ + int i; + wadpath_t* current; + + for (i = 0; i < g_iNumWadPaths; i++) + { + current = g_pWadPaths[i]; + free(current); + } +} + +// ===================================================================================== +// GetUsedWads +// parse the "wad" keyvalue into wadpath_t structs +// ===================================================================================== +void GetUsedWads() +{ + const char* pszWadPaths; + char szTmp[_MAX_PATH]; + int i, j; + + pszWadPaths = ValueForKey(&g_entities[0], "wad"); + +#ifdef HLCSG_AUTOWAD_NEW + for (i = 0; ; ) + { + for (j = i; pszWadPaths[j] != '\0'; j++) + { + if (pszWadPaths[j] == ';') + { + break; + } + } + if (j - i > 0) + { + int count = qmin (j - i, _MAX_PATH - 1); + memcpy (szTmp, &pszWadPaths[i], count); + szTmp[count] = '\0'; + + if (g_iNumWadPaths >= MAX_WADPATHS) + { + Error ("Too many wad files"); + } + PushWadPath (szTmp, true); + } + if (pszWadPaths[j] == '\0') + { + break; + } + else + { + i = j + 1; + } + } +#else + for(i = 0; i < MAX_WADPATHS; i++) + { + memset(szTmp, 0, sizeof(szTmp)); // are you happy zipster? + for (j = 0; j < _MAX_PATH; j++, pszWadPaths++) + { + if (pszWadPaths[0] == ';') + { + pszWadPaths++; + PushWadPath(szTmp, true); + break; + } + + if (pszWadPaths[0] == 0) + { + PushWadPath(szTmp, true); // fix by AmericanRPG for last wadpath ignorance bug + return; + } + + szTmp[j] = pszWadPaths[0]; + } + } +#endif +} \ No newline at end of file diff --git a/src/zhlt-vluzacn/hlcsg/wadpath.h b/src/zhlt-vluzacn/hlcsg/wadpath.h new file mode 100644 index 0000000..b90c76c --- /dev/null +++ b/src/zhlt-vluzacn/hlcsg/wadpath.h @@ -0,0 +1,30 @@ +// AJM: added file in +#ifndef WADPATH_H__ +#define WADPATH_H__ +#include "cmdlib.h" //--vluzacn + +#define MAX_WADPATHS 128 // arbitrary + +typedef struct +{ + char path[_MAX_PATH]; + bool usedbymap; // does this map requrie this wad to be included in the bsp? + int usedtextures; // number of textures in this wad the map actually uses +#ifdef HLCSG_AUTOWAD_NEW + int totaltextures; +#endif +} wadpath_t;//!!! the above two are VERY DIFFERENT. ie (usedtextures == 0) != (usedbymap == false) + +extern wadpath_t* g_pWadPaths[MAX_WADPATHS]; +extern int g_iNumWadPaths; + + +extern void PushWadPath(const char* const path, bool inuse); +#ifndef HLCSG_AUTOWAD_NEW +extern bool IsUsedWadPath(const char* const path); +extern bool IsListedWadPath(const char* const path); +#endif +extern void FreeWadPaths(); +extern void GetUsedWads(); + +#endif // WADPATH_H__ \ No newline at end of file diff --git a/src/zhlt-vluzacn/hlrad/compress.cpp b/src/zhlt-vluzacn/hlrad/compress.cpp new file mode 100644 index 0000000..41d50d5 --- /dev/null +++ b/src/zhlt-vluzacn/hlrad/compress.cpp @@ -0,0 +1,73 @@ +#include "cmdlib.h" +#include "compress.h" +#include "log.h" +#include +#include +#include + +const size_t unused_size = 3u; // located at the end of a block + +const char *(float_type_string[float_type_count]) = +{ + "32bit", + "16bit", + "8bit" +}; + +const size_t float_size[float_type_count] = +{ + 4u, + 2u, + 1u +}; + +const char *(vector_type_string[vector_type_count]) = +{ + "96bit", + "48bit", + "32bit", + "24bit" +}; + +const size_t vector_size[vector_type_count] = +{ + 12u, + 6u, + 4u, + 3u +}; + +void fail () +{ + Error ("Compatability test failed. Please disable HLRAD_TRANSFERDATA_COMPRESS in cmdlib.h and recompile ZHLT."); +} + +void compress_compatability_test () +{ + unsigned char *v = (unsigned char *)malloc (16u); + memset (v, 0, 16u); + if (sizeof(char) !=1 || sizeof(unsigned int) != 4 || sizeof(float) != 4) + fail (); + *(float *)(v+1) = 0.123f; + if (*(unsigned int *)v != 4226247936u || *(unsigned int *)(v+1) != 1039918957u) + fail (); + *(float *)(v+1) = -58; + if (*(unsigned int *)v != 1744830464u || *(unsigned int *)(v+1) != 3261595648u) + fail (); + float f[5] = {0.123f, 1.f, 0.f, 0.123f, 0.f}; + memset (v, ~0, 16u); + vector_compress (VECTOR24, v, &f[0], &f[1], &f[2]); + float_compress (FLOAT16, v+6, &f[3]); + float_compress (FLOAT16, v+4, &f[4]); + if (((unsigned int *)v)[0] != 4286318595u || ((unsigned int *)v)[1] != 3753771008u) + fail (); + float_decompress (FLOAT16, v+6, &f[3]); + float_decompress (FLOAT16, v+4, &f[4]); + vector_decompress (VECTOR24, v, &f[0], &f[1], &f[2]); + float ans[5] = {0.109375f,1.015625f,0.015625f,0.123001f,0.000000f}; + int i; + for (i=0; i<5; ++i) + if (f[i]-ans[i] > 0.00001f || f[i]-ans[i] < -0.00001f) + fail (); + free (v); +} \ No newline at end of file diff --git a/src/zhlt-vluzacn/hlrad/compress.h b/src/zhlt-vluzacn/hlrad/compress.h new file mode 100644 index 0000000..f7e03d5 --- /dev/null +++ b/src/zhlt-vluzacn/hlrad/compress.h @@ -0,0 +1,318 @@ +#include "cmdlib.h" //--vluzacn + + +#ifdef WORDS_BIGENDIAN +#error +#endif + +extern void compress_compatability_test (void); + +extern const size_t unused_size; // located at the end of a block + +typedef enum +{ + FLOAT32 = 0, + FLOAT16, + FLOAT8, + float_type_count +} +float_type; + +extern const char *float_type_string[]; + +extern const size_t float_size[]; + +typedef enum +{ + VECTOR96 = 0, + VECTOR48, + VECTOR32, + VECTOR24, + vector_type_count +} +vector_type; + +extern const char *vector_type_string[]; + +extern const size_t vector_size[]; + +inline unsigned int bitget + (unsigned int i, unsigned int start, unsigned int end) +{ + return (i & ~(~0u << end)) >> start; +} + +inline unsigned int bitput + (unsigned int i, unsigned int start, unsigned int end) +{ + return i << start; +} + +inline unsigned int bitclr + (unsigned int i, unsigned int start, unsigned int end) +{ + return i & (~(~0u << start) | (~0u << end)); +} + +inline unsigned int float_iswrong + (unsigned int i) +{ + return i >= 0x7F800000u; +} + +inline unsigned int float_istoobig + (unsigned int i) +{ + return i >= 0x40000000u; +} + +inline unsigned int float_istoosmall + (unsigned int i) +{ + return i < 0x30800000u; +} + +inline void float_compress + (float_type t, void *s, const float *f) +{ + unsigned int *m = (unsigned int *)s; + const unsigned int *p = (const unsigned int *)f; + switch (t) + { + case FLOAT32: + m[0] = *p; + break; + case FLOAT16: + m[0] = bitclr (m[0], 0, 16); + if (float_iswrong (*p)) + ; + else if (float_istoobig (*p)) + m[0] |= bitget (~0u, 0, 16); + else if (float_istoosmall (*p)) + ; + else + m[0] |= bitget (*p, 12, 28); + break; + case FLOAT8: + m[0] = bitclr (m[0], 0, 8); + if (float_iswrong (*p)) + ; + else if (float_istoobig (*p)) + m[0] |= bitget (~0u, 0, 8); + else if (float_istoosmall (*p)) + ; + else + m[0] |= bitget (*p, 20, 28); + break; + default: + ; + } +} + +inline void float_decompress + (float_type t, const void *s, float *f) +{ + const unsigned int *m = (const unsigned int *)s; + unsigned int *p = (unsigned int *)f; + switch (t) + { + case FLOAT32: + *p = m[0]; + break; + case FLOAT16: + if (bitget (m[0], 0, 16) == 0) + *p = 0; + else + *p + = bitput (1, 11, 12) + | bitput (bitget (m[0], 0, 16), 12, 28) + | bitput (3, 28, 32) + ; + break; + case FLOAT8: + if (bitget (m[0], 0, 8) == 0) + *p = 0; + else + *p + = bitput (1, 19, 20) + | bitput (bitget (m[0], 0, 8), 20, 28) + | bitput (3, 28, 32) + ; + break; + default: + ; + } +} + +inline void vector_compress + (vector_type t, void *s, const float *f1, const float *f2, const float *f3) +{ + unsigned int *m = (unsigned int *)s; + const unsigned int *p1 = (const unsigned int *)f1; + const unsigned int *p2 = (const unsigned int *)f2; + const unsigned int *p3 = (const unsigned int *)f3; + unsigned int max, i1, i2, i3; + switch (t) + { + case VECTOR96: + m[0] = *p1; + m[1] = *p2; + m[2] = *p3; + break; + case VECTOR48: + if (float_iswrong (*p1) || float_iswrong (*p2) || float_iswrong (*p3)) + break; + m[0] = 0, m[1] = bitclr (m[1], 0, 16); + if (float_istoobig (*p1)) + m[0] |= bitget (~0u, 0, 16); + else if (float_istoosmall (*p1)) + ; + else + m[0] |= bitget (*p1, 12, 28); + if (float_istoobig (*p2)) + m[0] |= bitput (bitget (~0u, 0, 16), 16, 32); + else if (float_istoosmall (*p2)) + ; + else + m[0] |= bitput (bitget (*p2, 12, 28), 16, 32); + if (float_istoobig (*p3)) + m[1] |= bitget (~0u, 0, 16); + else if (float_istoosmall (*p3)) + ; + else + m[1] |= bitget (*p3, 12, 28); + break; + case VECTOR32: + case VECTOR24: + if (float_iswrong (*p1) || float_iswrong (*p2) || float_iswrong (*p3)) + { + max = i1 = i2 = i3 = 0; + } + else + { + max = *p1>*p2? (*p1>*p3? *p1: *p3): (*p2>*p3? *p2: *p3); + max = float_istoobig (max)? 0x7F : float_istoosmall (max)? 0x60 : bitget (max, 23, 31); + i1 = float_istoobig (*p1)? ~0u : (bitget (*p1, 0, 23) | bitput (1, 23, 24)) >> (1 + max - bitget (*p1, 23, 31)); + i2 = float_istoobig (*p2)? ~0u : (bitget (*p2, 0, 23) | bitput (1, 23, 24)) >> (1 + max - bitget (*p2, 23, 31)); + i3 = float_istoobig (*p3)? ~0u : (bitget (*p3, 0, 23) | bitput (1, 23, 24)) >> (1 + max - bitget (*p3, 23, 31)); + } + if (t == VECTOR32) + m[0] = 0 + | bitput (bitget (i1, 14, 23), 0, 9) + | bitput (bitget (i2, 14, 23), 9, 18) + | bitput (bitget (i3, 14, 23), 18, 27) + | bitput (bitget (max, 0, 5), 27, 32) + ; + else + m[0] = bitclr (m[0], 0, 24) + | bitput (bitget (i1, 17, 23), 0, 6) + | bitput (bitget (i2, 17, 23), 6, 12) + | bitput (bitget (i3, 17, 23), 12, 18) + | bitput (bitget (max, 0, 5), 18, 23) + ; + break; + default: + ; + } +} + +inline void vector_decompress + (vector_type t, const void *s, float *f1, float *f2, float *f3) +{ + const unsigned int *m = (const unsigned int *)s; + unsigned int *p1 = (unsigned int *)f1; + unsigned int *p2 = (unsigned int *)f2; + unsigned int *p3 = (unsigned int *)f3; + switch (t) + { + case VECTOR96: + *p1 = m[0]; + *p2 = m[1]; + *p3 = m[2]; + break; + case VECTOR48: + if (bitget (m[0], 0, 16) == 0) + *p1 = 0; + else + *p1 + = bitput (1, 11, 12) + | bitput (bitget (m[0], 0, 16), 12, 28) + | bitput (3, 28, 32) + ; + if (bitget (m[0], 16, 32) == 0) + *p2 = 0; + else + *p2 + = bitput (1, 11, 12) + | bitput (bitget (m[0], 16, 32), 12, 28) + | bitput (3, 28, 32) + ; + if (bitget (m[1], 0, 16) == 0) + *p3 = 0; + else + *p3 + = bitput (1, 11, 12) + | bitput (bitget (m[1], 0, 16), 12, 28) + | bitput (3, 28, 32) + ; + break; + case VECTOR32: case VECTOR24: + float f; + if (t == VECTOR32) + { + *p1 + = bitput (1, 13, 14) + | bitput (bitget (m[0], 0, 9), 14, 23) + | bitput (bitget (m[0], 27, 32), 23, 28) + | bitput (3, 28, 32) + ; + *p2 + = bitput (1, 13, 14) + | bitput (bitget (m[0], 9, 18), 14, 23) + | bitput (bitget (m[0], 27, 32), 23, 28) + | bitput (3, 28, 32) + ; + *p3 + = bitput (1, 13, 14) + | bitput (bitget (m[0], 18, 27), 14, 23) + | bitput (bitget (m[0], 27, 32), 23, 28) + | bitput (3, 28, 32) + ; + *((unsigned int *)&f) + = bitput (bitget (m[0], 27, 32), 23, 28) + | bitput (3, 28, 32) + ; + } + else + { + *p1 + = bitput (1, 16, 17) + | bitput (bitget (m[0], 0, 6), 17, 23) + | bitput (bitget (m[0], 18, 23), 23, 28) + | bitput (3, 28, 32) + ; + *p2 + = bitput (1, 16, 17) + | bitput (bitget (m[0], 6, 12), 17, 23) + | bitput (bitget (m[0], 18, 23), 23, 28) + | bitput (3, 28, 32) + ; + *p3 + = bitput (1, 16, 17) + | bitput (bitget (m[0], 12, 18), 17, 23) + | bitput (bitget (m[0], 18, 23), 23, 28) + | bitput (3, 28, 32) + ; + *((unsigned int *)&f) + = bitput (bitget (m[0], 18, 23), 23, 28) + | bitput (3, 28, 32) + ; + } + *f1 = (*f1-f) * 2.f; + *f2 = (*f2-f) * 2.f; + *f3 = (*f3-f) * 2.f; + break; + default: + ; + } +} diff --git a/src/zhlt-vluzacn/hlrad/hlrad.vcproj b/src/zhlt-vluzacn/hlrad/hlrad.vcproj new file mode 100644 index 0000000..d8581a3 --- /dev/null +++ b/src/zhlt-vluzacn/hlrad/hlrad.vcproj @@ -0,0 +1,413 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/zhlt-vluzacn/hlrad/hlrad.vcxproj b/src/zhlt-vluzacn/hlrad/hlrad.vcxproj new file mode 100644 index 0000000..f6630c5 --- /dev/null +++ b/src/zhlt-vluzacn/hlrad/hlrad.vcxproj @@ -0,0 +1,186 @@ + + + + + Release + Win32 + + + Release + x64 + + + + + + {3B5F6C9B-1238-1ECF-14D8-01E4C3AB0EE1} + + + + Application + false + MultiByte + v140 + + + Application + false + MultiByte + v140 + + + + + + + + + + + + + + + .\Release\ + .\Release\ + false + + + .\Release_x64\ + .\Release_x64\ + false + + + + MultiThreaded + Default + true + true + MaxSpeed + true + Level3 + ..\common;..\template;%(AdditionalIncludeDirectories) + HLRAD;VERSION_32BIT;NDEBUG;WIN32;_CONSOLE;SYSTEM_WIN32;STDC_HEADERS;%(PreprocessorDefinitions) + .\Release\ + true + .\Release\hlrad.pch + .\Release\ + .\Release\ + true + true + + + .\Release\hlrad.tlb + + + 0x0409 + NDEBUG;%(PreprocessorDefinitions) + + + true + .\Release\hlrad.bsc + + + true + Console + false + .\Release\hlrad.exe + binmode.obj;%(AdditionalDependencies) + 4194304 + 1048576 + false + + + + + MultiThreaded + Default + true + true + MaxSpeed + true + Level3 + ..\common;..\template;%(AdditionalIncludeDirectories) + HLRAD;VERSION_64BIT;NDEBUG;WIN32;_CONSOLE;SYSTEM_WIN32;STDC_HEADERS;%(PreprocessorDefinitions) + .\Release_x64\ + true + .\Release_x64\hlrad.pch + .\Release_x64\ + .\Release_x64\ + true + true + + + .\Release_x64\hlrad.tlb + + + 0x0409 + NDEBUG;%(PreprocessorDefinitions) + + + true + .\Release_x64\hlrad.bsc + + + true + Console + false + .\Release_x64\hlrad.exe + binmode.obj;%(AdditionalDependencies) + 4194304 + 1048576 + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/zhlt-vluzacn/hlrad/hlrad.vcxproj.filters b/src/zhlt-vluzacn/hlrad/hlrad.vcxproj.filters new file mode 100644 index 0000000..4f0a450 --- /dev/null +++ b/src/zhlt-vluzacn/hlrad/hlrad.vcxproj.filters @@ -0,0 +1,156 @@ + + + + + {a0cf76ae-75aa-499a-b918-b2a6557e35d7} + cpp;c;cxx;rc;def;r;odl;idl;hpj;bat;for;f90 + + + {4848509b-0f96-431d-a338-8ad8791f845b} + + + {a32bc9fa-c773-4831-bbb7-b1e2b7aadf9f} + h;hpp;hxx;hm;inl;fi;fd + + + {115941c5-3fdd-402d-bbb3-96f9eef34328} + ico;cur;bmp;dlg;rc2;rct;bin;cnt;rtf;gif;jpg;jpeg;jpe + + + + + Source Files\common + + + Source Files\common + + + Source Files\common + + + Source Files\common + + + Source Files\common + + + Source Files\common + + + Source Files\common + + + Source Files\common + + + Source Files\common + + + Source Files\common + + + Source Files\common + + + 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 + + + + + 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 + + + \ No newline at end of file diff --git a/src/zhlt-vluzacn/hlrad/lerp.cpp b/src/zhlt-vluzacn/hlrad/lerp.cpp new file mode 100644 index 0000000..3ae95ec --- /dev/null +++ b/src/zhlt-vluzacn/hlrad/lerp.cpp @@ -0,0 +1,3382 @@ +#include "qrad.h" +#ifdef HLRAD_LOCALTRIANGULATION +#include +#include +#endif + +int g_lerp_enabled = DEFAULT_LERP_ENABLED; +#ifdef HLRAD_LOCALTRIANGULATION + +struct interpolation_t +{ + struct Point + { + int patchnum; + vec_t weight; + }; + + bool isbiased; + vec_t totalweight; + std::vector< Point > points; +}; + +struct localtriangulation_t +{ + struct Wedge + { + enum eShape + { + eTriangular, + eConvex, + eConcave, +#ifdef HLRAD_BILINEARINTERPOLATION + eSquareLeft, + eSquareRight, +#endif + }; + + eShape shape; + int leftpatchnum; + vec3_t leftspot; + vec3_t leftdirection; + // right side equals to the left side of the next wedge + + vec3_t wedgenormal; // for custom usage + }; + struct HullPoint + { + vec3_t spot; + vec3_t direction; + }; + + dplane_t plane; + Winding winding; + vec3_t center; // center is on the plane + + vec3_t normal; + int patchnum; + std::vector< int > neighborfaces; // including the face itself + + std::vector< Wedge > sortedwedges; // in clockwise order (same as Winding) + std::vector< HullPoint > sortedhullpoints; // in clockwise order (same as Winding) +}; + +struct facetriangulation_t +{ + struct Wall + { + vec3_t points[2]; + vec3_t direction; + vec3_t normal; + }; + + int facenum; + std::vector< int > neighbors; // including the face itself + std::vector< Wall > walls; + std::vector< localtriangulation_t * > localtriangulations; + std::vector< int > usedpatches; +}; + +facetriangulation_t *g_facetriangulations[MAX_MAP_FACES]; + +static bool CalcAdaptedSpot (const localtriangulation_t *lt, const vec3_t position, int surface, vec3_t spot) + // If the surface formed by the face and its neighbor faces is not flat, the surface should be unfolded onto the face plane + // CalcAdaptedSpot calculates the coordinate of the unfolded spot on the face plane from the original position on the surface + // CalcAdaptedSpot(center) = {0,0,0} + // CalcAdaptedSpot(position on the face plane) = position - center + // Param position: must include g_face_offset +{ + int i; + vec_t dot; + vec3_t surfacespot; + vec_t dist; + vec_t dist2; + vec3_t phongnormal; + vec_t frac; + vec3_t middle; + vec3_t v; + + for (i = 0; i < (int)lt->neighborfaces.size (); i++) + { + if (lt->neighborfaces[i] == surface) + { + break; + } + } + if (i == (int)lt->neighborfaces.size ()) + { + VectorClear (spot); + return false; + } + + VectorSubtract (position, lt->center, surfacespot); + dot = DotProduct (surfacespot, lt->normal); + VectorMA (surfacespot, -dot, lt->normal, spot); + + // use phong normal instead of face normal, because phong normal is a continuous function + GetPhongNormal (surface, position, phongnormal); + dot = DotProduct (spot, phongnormal); + if (fabs (dot) > ON_EPSILON) + { + frac = DotProduct (surfacespot, phongnormal) / dot; + frac = qmax (0, qmin (frac, 1)); // to correct some extreme cases + } + else + { + frac = 0; + } + VectorScale (spot, frac, middle); + + dist = VectorLength (spot); + VectorSubtract (surfacespot, middle, v); + dist2 = VectorLength (middle) + VectorLength (v); + + if (dist > ON_EPSILON && fabs (dist2 - dist) > ON_EPSILON) + { + VectorScale (spot, dist2 / dist, spot); + } + return true; +} + +static vec_t GetAngle (const vec3_t leftdirection, const vec3_t rightdirection, const vec3_t normal) +{ + vec_t angle; + vec3_t v; + + CrossProduct (rightdirection, leftdirection, v); + angle = atan2 (DotProduct (v, normal), DotProduct (rightdirection, leftdirection)); + + return angle; +} + +static vec_t GetAngleDiff (vec_t angle, vec_t base) +{ + vec_t diff; + + diff = angle - base; + if (diff < 0) + { + diff += 2 * Q_PI; + } + return diff; +} + +static vec_t GetFrac (const vec3_t leftspot, const vec3_t rightspot, const vec3_t direction, const vec3_t normal) +{ + vec3_t v; + vec_t dot1; + vec_t dot2; + vec_t frac; + + CrossProduct (direction, normal, v); + dot1 = DotProduct (leftspot, v); + dot2 = DotProduct (rightspot, v); + + // dot1 <= 0 < dot2 + if (dot1 >= -NORMAL_EPSILON) + { + if (g_drawlerp && dot1 > ON_EPSILON) + { + Developer (DEVELOPER_LEVEL_SPAM, "Debug: triangulation: internal error 1.\n"); + } + frac = 0.0; + } + else if (dot2 <= NORMAL_EPSILON) + { + if (g_drawlerp && dot2 < -ON_EPSILON) + { + Developer (DEVELOPER_LEVEL_SPAM, "Debug: triangulation: internal error 2.\n"); + } + frac = 1.0; + } + else + { + frac = dot1 / (dot1 - dot2); + frac = qmax (0, qmin (frac, 1)); + } + + return frac; +} + +static vec_t GetDirection (const vec3_t spot, const vec3_t normal, vec3_t direction_out) +{ + vec_t dot; + + dot = DotProduct (spot, normal); + VectorMA (spot, -dot, normal, direction_out); + return VectorNormalize (direction_out); +} + +static bool CalcWeight (const localtriangulation_t *lt, const vec3_t spot, vec_t *weight_out) + // It returns true when the point is inside the hull region (with boundary), even if weight = 0. +{ + vec3_t direction; + const localtriangulation_t::HullPoint *hp1; + const localtriangulation_t::HullPoint *hp2; + bool istoofar; + vec_t ratio; + + int i; + int j; + vec_t angle; + std::vector< vec_t > angles; + vec_t frac; + vec_t len; + vec_t dist; + + if (GetDirection (spot, lt->normal, direction) <= 2 * ON_EPSILON) + { + *weight_out = 1.0; + return true; + } + + if ((int)lt->sortedhullpoints.size () == 0) + { + *weight_out = 0.0; + return false; + } + + angles.resize ((int)lt->sortedhullpoints.size ()); + for (i = 0; i < (int)lt->sortedhullpoints.size (); i++) + { + angle = GetAngle (lt->sortedhullpoints[i].direction, direction, lt->normal); + angles[i] = GetAngleDiff (angle, 0); + } + j = 0; + for (i = 1; i < (int)lt->sortedhullpoints.size (); i++) + { + if (angles[i] < angles[j]) + { + j = i; + } + } + hp1 = <->sortedhullpoints[j]; + hp2 = <->sortedhullpoints[(j + 1) % (int)lt->sortedhullpoints.size ()]; + + frac = GetFrac (hp1->spot, hp2->spot, direction, lt->normal); + + len = (1 - frac) * DotProduct (hp1->spot, direction) + frac * DotProduct (hp2->spot, direction); + dist = DotProduct (spot, direction); + if (len <= ON_EPSILON / 4 || dist > len + 2 * ON_EPSILON) + { + istoofar = true; + ratio = 1.0; + } + else if (dist >= len - ON_EPSILON) + { + istoofar = false; // if we change this "false" to "true", we will see many places turned "green" in "-drawlerp" mode + ratio = 1.0; // in order to prevent excessively small weight + } + else + { + istoofar = false; + ratio = dist / len; + ratio = qmax (0, qmin (ratio, 1)); + } + + *weight_out = 1 - ratio; + return !istoofar; +} + +#ifdef HLRAD_BILINEARINTERPOLATION +static void CalcInterpolation_Square (const localtriangulation_t *lt, int i, const vec3_t spot, interpolation_t *interp) +{ + const localtriangulation_t::Wedge *w1; + const localtriangulation_t::Wedge *w2; + const localtriangulation_t::Wedge *w3; + vec_t weights[4]; + vec_t dot1; + vec_t dot2; + vec_t dot; + vec3_t normal1; + vec3_t normal2; + vec3_t normal; + vec_t frac; + vec_t frac_near; + vec_t frac_far; + vec_t ratio; + vec3_t mid_far; + vec3_t mid_near; + vec3_t test; + + w1 = <->sortedwedges[i]; + w2 = <->sortedwedges[(i + 1) % (int)lt->sortedwedges.size ()]; + w3 = <->sortedwedges[(i + 2) % (int)lt->sortedwedges.size ()]; + if (w1->shape != localtriangulation_t::Wedge::eSquareLeft || w2->shape != localtriangulation_t::Wedge::eSquareRight) + { + Error ("CalcInterpolation_Square: internal error: not square."); + } + + weights[0] = 0.0; + weights[1] = 0.0; + weights[2] = 0.0; + weights[3] = 0.0; + + // find mid_near on (o,p3), mid_far on (p1,p2), spot on (mid_near,mid_far) + CrossProduct (w1->leftdirection, lt->normal, normal1); + VectorNormalize (normal1); + CrossProduct (w2->wedgenormal, lt->normal, normal2); + VectorNormalize (normal2); + dot1 = DotProduct (spot, normal1) - 0; + dot2 = DotProduct (spot, normal2) - DotProduct (w3->leftspot, normal2); + if (dot1 <= NORMAL_EPSILON) + { + frac = 0.0; + } + else if (dot2 <= NORMAL_EPSILON) + { + frac = 1.0; + } + else + { + frac = dot1 / (dot1 + dot2); + frac = qmax (0, qmin (frac, 1)); + } + + dot1 = DotProduct (w3->leftspot, normal1) - 0; + dot2 = 0 - DotProduct (w3->leftspot, normal2); + if (dot1 <= NORMAL_EPSILON) + { + frac_near = 1.0; + } + else if (dot2 <= NORMAL_EPSILON) + { + frac_near = 0.0; + } + else + { + frac_near = (frac * dot2) / ((1 - frac) * dot1 + frac * dot2); + } + VectorScale (w3->leftspot, frac_near, mid_near); + + dot1 = DotProduct (w2->leftspot, normal1) - 0; + dot2 = DotProduct (w1->leftspot, normal2) - DotProduct (w3->leftspot, normal2); + if (dot1 <= NORMAL_EPSILON) + { + frac_far = 1.0; + } + else if (dot2 <= NORMAL_EPSILON) + { + frac_far = 0.0; + } + else + { + frac_far = (frac * dot2) / ((1 - frac) * dot1 + frac * dot2); + } + VectorScale (w1->leftspot, 1 - frac_far, mid_far); + VectorMA (mid_far, frac_far, w2->leftspot, mid_far); + + CrossProduct (lt->normal, w3->leftdirection, normal); + VectorNormalize (normal); + dot = DotProduct (spot, normal) - 0; + dot1 = (1 - frac_far) * DotProduct (w1->leftspot, normal) + frac_far * DotProduct (w2->leftspot, normal) - 0; + if (dot <= NORMAL_EPSILON) + { + ratio = 0.0; + } + else if (dot >= dot1) + { + ratio = 1.0; + } + else + { + ratio = dot / dot1; + ratio = qmax (0, qmin (ratio, 1)); + } + + VectorScale (mid_near, 1 - ratio, test); + VectorMA (test, ratio, mid_far, test); + VectorSubtract (test, spot, test); + if (g_drawlerp && VectorLength (test) > 4 * ON_EPSILON) + { + Developer (DEVELOPER_LEVEL_SPAM, "Debug: triangulation: internal error 12.\n"); + } + + weights[0] += 0.5 * (1 - ratio) * (1 - frac_near); + weights[3] += 0.5 * (1 - ratio) * frac_near; + weights[1] += 0.5 * ratio * (1 - frac_far); + weights[2] += 0.5 * ratio * frac_far; + + // find mid_near on (o,p1), mid_far on (p2,p3), spot on (mid_near,mid_far) + CrossProduct (lt->normal, w3->leftdirection, normal1); + VectorNormalize (normal1); + CrossProduct (w1->wedgenormal, lt->normal, normal2); + VectorNormalize (normal2); + dot1 = DotProduct (spot, normal1) - 0; + dot2 = DotProduct (spot, normal2) - DotProduct (w1->leftspot, normal2); + if (dot1 <= NORMAL_EPSILON) + { + frac = 0.0; + } + else if (dot2 <= NORMAL_EPSILON) + { + frac = 1.0; + } + else + { + frac = dot1 / (dot1 + dot2); + frac = qmax (0, qmin (frac, 1)); + } + + dot1 = DotProduct (w1->leftspot, normal1) - 0; + dot2 = 0 - DotProduct (w1->leftspot, normal2); + if (dot1 <= NORMAL_EPSILON) + { + frac_near = 1.0; + } + else if (dot2 <= NORMAL_EPSILON) + { + frac_near = 0.0; + } + else + { + frac_near = (frac * dot2) / ((1 - frac) * dot1 + frac * dot2); + } + VectorScale (w1->leftspot, frac_near, mid_near); + + dot1 = DotProduct (w2->leftspot, normal1) - 0; + dot2 = DotProduct (w3->leftspot, normal2) - DotProduct (w1->leftspot, normal2); + if (dot1 <= NORMAL_EPSILON) + { + frac_far = 1.0; + } + else if (dot2 <= NORMAL_EPSILON) + { + frac_far = 0.0; + } + else + { + frac_far = (frac * dot2) / ((1 - frac) * dot1 + frac * dot2); + } + VectorScale (w3->leftspot, 1 - frac_far, mid_far); + VectorMA (mid_far, frac_far, w2->leftspot, mid_far); + + CrossProduct (w1->leftdirection, lt->normal, normal); + VectorNormalize (normal); + dot = DotProduct (spot, normal) - 0; + dot1 = (1 - frac_far) * DotProduct (w3->leftspot, normal) + frac_far * DotProduct (w2->leftspot, normal) - 0; + if (dot <= NORMAL_EPSILON) + { + ratio = 0.0; + } + else if (dot >= dot1) + { + ratio = 1.0; + } + else + { + ratio = dot / dot1; + ratio = qmax (0, qmin (ratio, 1)); + } + + VectorScale (mid_near, 1 - ratio, test); + VectorMA (test, ratio, mid_far, test); + VectorSubtract (test, spot, test); + if (g_drawlerp && VectorLength (test) > 4 * ON_EPSILON) + { + Developer (DEVELOPER_LEVEL_SPAM, "Debug: triangulation: internal error 13.\n"); + } + + weights[0] += 0.5 * (1 - ratio) * (1 - frac_near); + weights[1] += 0.5 * (1 - ratio) * frac_near; + weights[3] += 0.5 * ratio * (1 - frac_far); + weights[2] += 0.5 * ratio * frac_far; + + interp->isbiased = false; + interp->totalweight = 1.0; + interp->points.resize (4); + interp->points[0].patchnum = lt->patchnum; + interp->points[0].weight = weights[0]; + interp->points[1].patchnum = w1->leftpatchnum; + interp->points[1].weight = weights[1]; + interp->points[2].patchnum = w2->leftpatchnum; + interp->points[2].weight = weights[2]; + interp->points[3].patchnum = w3->leftpatchnum; + interp->points[3].weight = weights[3]; +} + +#endif +static void CalcInterpolation (const localtriangulation_t *lt, const vec3_t spot, interpolation_t *interp) + // The interpolation function is defined over the entire plane, so CalcInterpolation never fails. +{ + vec3_t direction; + const localtriangulation_t::Wedge *w; + const localtriangulation_t::Wedge *wnext; + + int i; + int j; + vec_t angle; + std::vector< vec_t > angles; + + if (GetDirection (spot, lt->normal, direction) <= 2 * ON_EPSILON) + { + // spot happens to be at the center + interp->isbiased = false; + interp->totalweight = 1.0; + interp->points.resize (1); + interp->points[0].patchnum = lt->patchnum; + interp->points[0].weight = 1.0; + return; + } + + if ((int)lt->sortedwedges.size () == 0) // this local triangulation only has center patch + { + interp->isbiased = true; + interp->totalweight = 1.0; + interp->points.resize (1); + interp->points[0].patchnum = lt->patchnum; + interp->points[0].weight = 1.0; + return; + } + + // Find the wedge with minimum non-negative angle (counterclockwise) pass the spot + angles.resize ((int)lt->sortedwedges.size ()); + for (i = 0; i < (int)lt->sortedwedges.size (); i++) + { + angle = GetAngle (lt->sortedwedges[i].leftdirection, direction, lt->normal); + angles[i] = GetAngleDiff (angle, 0); + } + j = 0; + for (i = 1; i < (int)lt->sortedwedges.size (); i++) + { + if (angles[i] < angles[j]) + { + j = i; + } + } + w = <->sortedwedges[j]; + wnext = <->sortedwedges[(j + 1) % (int)lt->sortedwedges.size ()]; + + // Different wedge types have different interpolation methods + switch (w->shape) + { +#ifdef HLRAD_BILINEARINTERPOLATION + case localtriangulation_t::Wedge::eSquareLeft: + case localtriangulation_t::Wedge::eSquareRight: +#endif + case localtriangulation_t::Wedge::eTriangular: + // w->wedgenormal is undefined + { + vec_t frac; + vec_t len; + vec_t dist; + bool istoofar; + vec_t ratio; + + frac = GetFrac (w->leftspot, wnext->leftspot, direction, lt->normal); + + len = (1 - frac) * DotProduct (w->leftspot, direction) + frac * DotProduct (wnext->leftspot, direction); + dist = DotProduct (spot, direction); + if (len <= ON_EPSILON / 4 || dist > len + 2 * ON_EPSILON) + { + istoofar = true; + ratio = 1.0; + } + else if (dist >= len - ON_EPSILON) + { + istoofar = false; + ratio = 1.0; + } + else + { + istoofar = false; + ratio = dist / len; + ratio = qmax (0, qmin (ratio, 1)); + } + + if (istoofar) + { + interp->isbiased = true; + interp->totalweight = 1.0; + interp->points.resize (2); + interp->points[0].patchnum = w->leftpatchnum; + interp->points[0].weight = 1 - frac; + interp->points[1].patchnum = wnext->leftpatchnum; + interp->points[1].weight = frac; + } +#ifdef HLRAD_BILINEARINTERPOLATION + else if (w->shape == localtriangulation_t::Wedge::eSquareLeft) + { + i = w - <->sortedwedges[0]; + CalcInterpolation_Square (lt, i, spot, interp); + } + else if (w->shape == localtriangulation_t::Wedge::eSquareRight) + { + i = w - <->sortedwedges[0]; + i = (i - 1 + (int)lt->sortedwedges.size ()) % (int)lt->sortedwedges.size (); + CalcInterpolation_Square (lt, i, spot, interp); + } +#endif + else + { + interp->isbiased = false; + interp->totalweight = 1.0; + interp->points.resize (3); + interp->points[0].patchnum = lt->patchnum; + interp->points[0].weight = 1 - ratio; + interp->points[1].patchnum = w->leftpatchnum; + interp->points[1].weight = ratio * (1 - frac); + interp->points[2].patchnum = wnext->leftpatchnum; + interp->points[2].weight = ratio * frac; + } + } + break; + case localtriangulation_t::Wedge::eConvex: + // w->wedgenormal is the unit vector pointing from w->leftspot to wnext->leftspot + { + vec_t dot; + vec_t dot1; + vec_t dot2; + vec_t frac; + + dot1 = DotProduct (w->leftspot, w->wedgenormal) - DotProduct (spot, w->wedgenormal); + dot2 = DotProduct (wnext->leftspot, w->wedgenormal) - DotProduct (spot, w->wedgenormal); + dot = 0 - DotProduct (spot, w->wedgenormal); + // for eConvex type: dot1 < dot < dot2 + + if (g_drawlerp && (dot1 > dot || dot > dot2)) + { + Developer (DEVELOPER_LEVEL_SPAM, "Debug: triangulation: internal error 3.\n"); + } + if (dot1 >= -NORMAL_EPSILON) // 0 <= dot1 < dot < dot2 + { + interp->isbiased = true; + interp->totalweight = 1.0; + interp->points.resize (1); + interp->points[0].patchnum = w->leftpatchnum; + interp->points[0].weight = 1.0; + } + else if (dot2 <= NORMAL_EPSILON) // dot1 < dot < dot2 <= 0 + { + interp->isbiased = true; + interp->totalweight = 1.0; + interp->points.resize (1); + interp->points[0].patchnum = wnext->leftpatchnum; + interp->points[0].weight = 1.0; + } + else if (dot > 0) // dot1 < 0 < dot < dot2 + { + frac = dot1 / (dot1 - dot); + frac = qmax (0, qmin (frac, 1)); + + interp->isbiased = true; + interp->totalweight = 1.0; + interp->points.resize (2); + interp->points[0].patchnum = w->leftpatchnum; + interp->points[0].weight = 1 - frac; + interp->points[1].patchnum = lt->patchnum; + interp->points[1].weight = frac; + } + else // dot1 < dot <= 0 < dot2 + { + frac = dot / (dot - dot2); + frac = qmax (0, qmin (frac, 1)); + + interp->isbiased = true; + interp->totalweight = 1.0; + interp->points.resize (2); + interp->points[0].patchnum = lt->patchnum; + interp->points[0].weight = 1 - frac; + interp->points[1].patchnum = wnext->leftpatchnum; + interp->points[1].weight = frac; + } + } + break; + case localtriangulation_t::Wedge::eConcave: + { + vec_t len; + vec_t dist; + vec_t ratio; + + if (DotProduct (spot, w->wedgenormal) < 0) // the spot is closer to the left edge than the right edge + { + len = DotProduct (w->leftspot, w->leftdirection); + dist = DotProduct (spot, w->leftdirection); + if (g_drawlerp && len <= ON_EPSILON) + { + Developer (DEVELOPER_LEVEL_SPAM, "Debug: triangulation: internal error 4.\n"); + } + if (dist <= NORMAL_EPSILON) + { + interp->isbiased = true; + interp->totalweight = 1.0; + interp->points.resize (1); + interp->points[0].patchnum = lt->patchnum; + interp->points[0].weight = 1.0; + } + else if (dist >= len) + { + interp->isbiased = true; + interp->totalweight = 1.0; + interp->points.resize (1); + interp->points[0].patchnum = w->leftpatchnum; + interp->points[0].weight = 1.0; + } + else + { + ratio = dist / len; + ratio = qmax (0, qmin (ratio, 1)); + + interp->isbiased = true; + interp->totalweight = 1.0; + interp->points.resize (2); + interp->points[0].patchnum = lt->patchnum; + interp->points[0].weight = 1 - ratio; + interp->points[1].patchnum = w->leftpatchnum; + interp->points[1].weight = ratio; + } + } + else // the spot is closer to the right edge than the left edge + { + len = DotProduct (wnext->leftspot, wnext->leftdirection); + dist = DotProduct (spot, wnext->leftdirection); + if (g_drawlerp && len <= ON_EPSILON) + { + Developer (DEVELOPER_LEVEL_SPAM, "Debug: triangulation: internal error 5.\n"); + } + if (dist <= NORMAL_EPSILON) + { + interp->isbiased = true; + interp->totalweight = 1.0; + interp->points.resize (1); + interp->points[0].patchnum = lt->patchnum; + interp->points[0].weight = 1.0; + } + else if (dist >= len) + { + interp->isbiased = true; + interp->totalweight = 1.0; + interp->points.resize (1); + interp->points[0].patchnum = wnext->leftpatchnum; + interp->points[0].weight = 1.0; + } + else + { + ratio = dist / len; + ratio = qmax (0, qmin (ratio, 1)); + + interp->isbiased = true; + interp->totalweight = 1.0; + interp->points.resize (2); + interp->points[0].patchnum = lt->patchnum; + interp->points[0].weight = 1 - ratio; + interp->points[1].patchnum = wnext->leftpatchnum; + interp->points[1].weight = ratio; + } + } + } + break; + default: + Error ("CalcInterpolation: internal error: invalid wedge type."); + break; + } +} + +static void ApplyInterpolation (const interpolation_t *interp, int numstyles, const int *styles, vec3_t *outs +#ifdef ZHLT_XASH + , vec3_t *outs_direction +#endif + ) +{ + int i; + int j; + + for (j = 0; j < numstyles; j++) + { + VectorClear (outs[j]); +#ifdef ZHLT_XASH + VectorClear (outs_direction[j]); +#endif + } + if (interp->totalweight <= 0) + { + return; + } + for (i = 0; i < (int)interp->points.size (); i++) + { + for (j = 0; j < numstyles; j++) + { +#ifdef ZHLT_XASH + const vec3_t *b_direction; +#endif + const vec3_t *b = GetTotalLight (&g_patches[interp->points[i].patchnum], styles[j] +#ifdef ZHLT_XASH + , b_direction +#endif + ); + VectorMA (outs[j], interp->points[i].weight / interp->totalweight, *b, outs[j]); +#ifdef ZHLT_XASH + VectorMA (outs_direction[j], interp->points[i].weight / interp->totalweight, *b_direction, outs_direction[j]); +#endif + } + } +} + +// ===================================================================================== +// InterpolateSampleLight +// ===================================================================================== +void InterpolateSampleLight (const vec3_t position, int surface, int numstyles, const int *styles, vec3_t *outs +#ifdef ZHLT_XASH + , vec3_t *outs_direction +#endif + ) +{ + try + { + + const facetriangulation_t *ft; + interpolation_t *maininterp; + std::vector< vec_t > localweights; + std::vector< interpolation_t * > localinterps; + + int i; + int j; + int n; + const facetriangulation_t *ft2; + const localtriangulation_t *lt; + vec3_t spot; + vec_t weight; + interpolation_t *interp; + const localtriangulation_t *best; + vec3_t v; + vec_t dist; + vec_t bestdist; + vec_t dot; + + if (surface < 0 || surface >= g_numfaces) + { + Error ("InterpolateSampleLight: internal error: surface number out of range."); + } + ft = g_facetriangulations[surface]; + maininterp = new interpolation_t; + maininterp->points.reserve (64); + + // Calculate local interpolations and their weights + localweights.resize (0); + localinterps.resize (0); + if (g_lerp_enabled) + { + for (i = 0; i < (int)ft->neighbors.size (); i++) // for this face and each of its neighbors + { + ft2 = g_facetriangulations[ft->neighbors[i]]; + for (j = 0; j < (int)ft2->localtriangulations.size (); j++) // for each patch on that face + { + lt = ft2->localtriangulations[j]; + if (!CalcAdaptedSpot (lt, position, surface, spot)) + { + if (g_drawlerp && ft2 == ft) + { + Developer (DEVELOPER_LEVEL_SPAM, "Debug: triangulation: internal error 6.\n"); + } + continue; + } + if (!CalcWeight (lt, spot, &weight)) + { + continue; + } + interp = new interpolation_t; +#ifdef HLRAD_BILINEARINTERPOLATION + interp->points.reserve (4); +#else + interp->points.reserve (3); +#endif + CalcInterpolation (lt, spot, interp); + + localweights.push_back (weight); + localinterps.push_back (interp); + } + } + } + + // Combine into one interpolation + maininterp->isbiased = false; + maininterp->totalweight = 0; + maininterp->points.resize (0); + for (i = 0; i < (int)localinterps.size (); i++) + { + if (localinterps[i]->isbiased) + { + maininterp->isbiased = true; + } + for (j = 0; j < (int)localinterps[i]->points.size (); j++) + { + weight = localinterps[i]->points[j].weight * localweights[i]; + if (g_patches[localinterps[i]->points[j].patchnum].flags == ePatchFlagOutside) + { + weight *= 0.01; + } + n = (int)maininterp->points.size (); + maininterp->points.resize (n + 1); + maininterp->points[n].patchnum = localinterps[i]->points[j].patchnum; + maininterp->points[n].weight = weight; + maininterp->totalweight += weight; + } + } + if (maininterp->totalweight > 0) + { + ApplyInterpolation (maininterp, numstyles, styles, outs +#ifdef ZHLT_XASH + , outs_direction +#endif + ); + if (g_drawlerp) + { + for (j = 0; j < numstyles; j++) + { + // white or yellow + outs[j][0] = 100; + outs[j][1] = 100; + outs[j][2] = (maininterp->isbiased? 0: 100); + } + } + } + else + { + // try again, don't multiply localweights[i] (which equals to 0) + maininterp->isbiased = false; + maininterp->totalweight = 0; + maininterp->points.resize (0); + for (i = 0; i < (int)localinterps.size (); i++) + { + if (localinterps[i]->isbiased) + { + maininterp->isbiased = true; + } + for (j = 0; j < (int)localinterps[i]->points.size (); j++) + { + weight = localinterps[i]->points[j].weight; + if (g_patches[localinterps[i]->points[j].patchnum].flags == ePatchFlagOutside) + { + weight *= 0.01; + } + n = (int)maininterp->points.size (); + maininterp->points.resize (n + 1); + maininterp->points[n].patchnum = localinterps[i]->points[j].patchnum; + maininterp->points[n].weight = weight; + maininterp->totalweight += weight; + } + } + if (maininterp->totalweight > 0) + { + ApplyInterpolation (maininterp, numstyles, styles, outs +#ifdef ZHLT_XASH + , outs_direction +#endif + ); + if (g_drawlerp) + { + for (j = 0; j < numstyles; j++) + { + // red + outs[j][0] = 100; + outs[j][1] = 0; + outs[j][2] = (maininterp->isbiased? 0: 100); + } + } + } + else + { + // worst case, simply use the nearest patch + + best = NULL; + for (i = 0; i < (int)ft->localtriangulations.size (); i++) + { + lt = ft->localtriangulations[i]; + VectorCopy (position, v); + snap_to_winding (lt->winding, lt->plane, v); + VectorSubtract (v, position, v); + dist = VectorLength (v); + if (best == NULL || dist < bestdist - ON_EPSILON) + { + best = lt; + bestdist = dist; + } + } + + if (best) + { + lt = best; + VectorSubtract (position, lt->center, spot); + dot = DotProduct (spot, lt->normal); + VectorMA (spot, -dot, lt->normal, spot); + CalcInterpolation (lt, spot, maininterp); + + maininterp->totalweight = 0; + for (j = 0; j < (int)maininterp->points.size (); j++) + { + if (g_patches[maininterp->points[j].patchnum].flags == ePatchFlagOutside) + { + maininterp->points[j].weight *= 0.01; + } + maininterp->totalweight += maininterp->points[j].weight; + } + ApplyInterpolation (maininterp, numstyles, styles, outs +#ifdef ZHLT_XASH + , outs_direction +#endif + ); + if (g_drawlerp) + { + for (j = 0; j < numstyles; j++) + { + // green + outs[j][0] = 0; + outs[j][1] = 100; + outs[j][2] = (maininterp->isbiased? 0: 100); + } + } + } + else + { + maininterp->isbiased = true; + maininterp->totalweight = 0; + maininterp->points.resize (0); + ApplyInterpolation (maininterp, numstyles, styles, outs +#ifdef ZHLT_XASH + , outs_direction +#endif + ); + if (g_drawlerp) + { + for (j = 0; j < numstyles; j++) + { + // black + outs[j][0] = 0; + outs[j][1] = 0; + outs[j][2] = 0; + } + } + } + } + } + delete maininterp; + + for (i = 0; i < (int)localinterps.size (); i++) + { + delete localinterps[i]; + } + + } + catch (std::bad_alloc) + { + hlassume (false, assume_NoMemory); + } +} + +static bool TestLineSegmentIntersectWall (const facetriangulation_t *facetrian, const vec3_t p1, const vec3_t p2) +{ + int i; + const facetriangulation_t::Wall *wall; + vec_t front; + vec_t back; + vec_t dot1; + vec_t dot2; + vec_t dot; + vec_t bottom; + vec_t top; + vec_t frac; + + for (i = 0; i < (int)facetrian->walls.size (); i++) + { + wall = &facetrian->walls[i]; + bottom = DotProduct (wall->points[0], wall->direction); + top = DotProduct (wall->points[1], wall->direction); + front = DotProduct (p1, wall->normal) - DotProduct (wall->points[0], wall->normal); + back = DotProduct (p2, wall->normal) - DotProduct (wall->points[0], wall->normal); + if (front > ON_EPSILON && back > ON_EPSILON || front < -ON_EPSILON && back < -ON_EPSILON) + { + continue; + } + dot1 = DotProduct (p1, wall->direction); + dot2 = DotProduct (p2, wall->direction); + if (fabs (front) <= 2 * ON_EPSILON && fabs (back) <= 2 * ON_EPSILON) + { + top = qmin (top, qmax (dot1, dot2)); + bottom = qmax (bottom, qmin (dot1, dot2)); + } + else + { + frac = front / (front - back); + frac = qmax (0, qmin (frac, 1)); + dot = dot1 + frac * (dot2 - dot1); + top = qmin (top, dot); + bottom = qmax (bottom, dot); + } + if (top - bottom >= -ON_EPSILON) + { + return true; + } + } + + return false; +} +#ifdef HLRAD_FARPATCH_FIX + +static bool TestFarPatch (const localtriangulation_t *lt, const vec3_t p2, const Winding &p2winding) +{ + int i; + vec3_t v; + vec_t dist; + vec_t size1; + vec_t size2; + + size1 = 0; + for (i = 0; i < lt->winding.m_NumPoints; i++) + { + VectorSubtract (lt->winding.m_Points[i], lt->center, v); + dist = VectorLength (v); + if (dist > size1) + { + size1 = dist; + } + } + + size2 = 0; + for (i = 0; i < p2winding.m_NumPoints; i++) + { + VectorSubtract (p2winding.m_Points[i], p2, v); + dist = VectorLength (v); + if (dist > size2) + { + size2 = dist; + } + } + + VectorSubtract (p2, lt->center, v); + dist = VectorLength (v); + + return dist > 1.4 * (size1 + size2); +} +#endif + +#define TRIANGLE_SHAPE_THRESHOLD (115.0*Q_PI/180) +// If one of the angles in a triangle exceeds this threshold, the most distant point will be removed or the triangle will break into a convex-type wedge. + +static void GatherPatches (localtriangulation_t *lt, const facetriangulation_t *facetrian) +{ + int i; + int facenum2; + const dplane_t *dp2; + const patch_t *patch2; + int patchnum2; + vec3_t v; + localtriangulation_t::Wedge point; + std::vector< localtriangulation_t::Wedge > points; + std::vector< std::pair< vec_t, int > > angles; + vec_t angle; + + if (!g_lerp_enabled) + { + lt->sortedwedges.resize (0); + return; + } + + points.resize (0); + for (i = 0; i < (int)lt->neighborfaces.size (); i++) + { + facenum2 = lt->neighborfaces[i]; + dp2 = getPlaneFromFaceNumber (facenum2); + for (patch2 = g_face_patches[facenum2]; patch2; patch2 = patch2->next) + { + patchnum2 = patch2 - g_patches; + + point.leftpatchnum = patchnum2; + VectorMA (patch2->origin, -PATCH_HUNT_OFFSET, dp2->normal, v); + + // Do permission tests using the original position of the patch + if (patchnum2 == lt->patchnum || point_in_winding (lt->winding, lt->plane, v)) + { + continue; + } + if (facenum2 != facetrian->facenum && TestLineSegmentIntersectWall (facetrian, lt->center, v)) + { + continue; + } +#ifdef HLRAD_FARPATCH_FIX + if (TestFarPatch (lt, v, *patch2->winding)) + { + continue; + } +#endif + + // Store the adapted position of the patch + if (!CalcAdaptedSpot (lt, v, facenum2, point.leftspot)) + { + continue; + } + if (GetDirection (point.leftspot, lt->normal, point.leftdirection) <= 2 * ON_EPSILON) + { + continue; + } + points.push_back (point); + } + } + + // Sort the patches into clockwise order + angles.resize ((int)points.size ()); + for (i = 0; i < (int)points.size (); i++) + { + angle = GetAngle (points[0].leftdirection, points[i].leftdirection, lt->normal); + if (i == 0) + { + if (g_drawlerp && fabs (angle) > NORMAL_EPSILON) + { + Developer (DEVELOPER_LEVEL_SPAM, "Debug: triangulation: internal error 7.\n"); + } + angle = 0.0; + } + angles[i].first = GetAngleDiff (angle, 0); + angles[i].second = i; + } + std::sort (angles.begin (), angles.end ()); + + lt->sortedwedges.resize ((int)points.size ()); + for (i = 0; i < (int)points.size (); i++) + { + lt->sortedwedges[i] = points[angles[i].second]; + } +} + +static void PurgePatches (localtriangulation_t *lt) +{ + std::vector< localtriangulation_t::Wedge > points; + int i; + int cur; + std::vector< int > next; + std::vector< int > prev; + std::vector< int > valid; + std::vector< std::pair< vec_t, int > > dists; + vec_t angle; + vec3_t normal; + vec3_t v; + + points.swap (lt->sortedwedges); + lt->sortedwedges.resize (0); + + next.resize ((int)points.size ()); + prev.resize ((int)points.size ()); + valid.resize ((int)points.size ()); + dists.resize ((int)points.size ()); + for (i = 0; i < (int)points.size (); i++) + { + next[i] = (i + 1) % (int)points.size (); + prev[i] = (i - 1 + (int)points.size ()) % (int)points.size (); + valid[i] = 1; + dists[i].first = DotProduct (points[i].leftspot, points[i].leftdirection); + dists[i].second = i; + } + std::sort (dists.begin (), dists.end ()); + + for (i = 0; i < (int)points.size (); i++) + { + cur = dists[i].second; + if (valid[cur] == 0) + { + continue; + } + valid[cur] = 2; // mark current patch as final + + CrossProduct (points[cur].leftdirection, lt->normal, normal); + VectorNormalize (normal); + VectorScale (normal, cos (TRIANGLE_SHAPE_THRESHOLD), v); + VectorMA (v, sin (TRIANGLE_SHAPE_THRESHOLD), points[cur].leftdirection, v); + while (next[cur] != cur && valid[next[cur]] != 2) + { + angle = GetAngle (points[cur].leftdirection, points[next[cur]].leftdirection, lt->normal); + if (fabs (angle) <= (1.0*Q_PI/180) || + GetAngleDiff (angle, 0) <= Q_PI + NORMAL_EPSILON + && DotProduct (points[next[cur]].leftspot, v) >= DotProduct (points[cur].leftspot, v) - ON_EPSILON / 2) + { + // remove next patch + valid[next[cur]] = 0; + next[cur] = next[next[cur]]; + prev[next[cur]] = cur; + continue; + } + // the triangle is good + break; + } + + CrossProduct (lt->normal, points[cur].leftdirection, normal); + VectorNormalize (normal); + VectorScale (normal, cos (TRIANGLE_SHAPE_THRESHOLD), v); + VectorMA (v, sin (TRIANGLE_SHAPE_THRESHOLD), points[cur].leftdirection, v); + while (prev[cur] != cur && valid[prev[cur]] != 2) + { + angle = GetAngle (points[prev[cur]].leftdirection, points[cur].leftdirection, lt->normal); + if (fabs (angle) <= (1.0*Q_PI/180) || + GetAngleDiff (angle, 0) <= Q_PI + NORMAL_EPSILON + && DotProduct (points[prev[cur]].leftspot, v) >= DotProduct (points[cur].leftspot, v) - ON_EPSILON / 2) + { + // remove previous patch + valid[prev[cur]] = 0; + prev[cur] = prev[prev[cur]]; + next[prev[cur]] = cur; + continue; + } + // the triangle is good + break; + } + } + + for (i = 0; i < (int)points.size (); i++) + { + if (valid[i] == 2) + { + lt->sortedwedges.push_back (points[i]); + } + } +} + +static void PlaceHullPoints (localtriangulation_t *lt) +{ + int i; + int j; + int n; + vec3_t v; + vec_t dot; + vec_t angle; + localtriangulation_t::HullPoint hp; + std::vector< localtriangulation_t::HullPoint > spots; + std::vector< std::pair< vec_t, int > > angles; + const localtriangulation_t::Wedge *w; + const localtriangulation_t::Wedge *wnext; + std::vector< localtriangulation_t::HullPoint > arc_spots; + std::vector< vec_t > arc_angles; + std::vector< int > next; + std::vector< int > prev; + vec_t frac; + vec_t len; + vec_t dist; + + spots.reserve (lt->winding.m_NumPoints); + spots.resize (0); + for (i = 0; i < (int)lt->winding.m_NumPoints; i++) + { + VectorSubtract (lt->winding.m_Points[i], lt->center, v); + dot = DotProduct (v, lt->normal); + VectorMA (v, -dot, lt->normal, hp.spot); + if (!GetDirection (hp.spot, lt->normal, hp.direction)) + { + continue; + } + spots.push_back (hp); + } + + if ((int)lt->sortedwedges.size () == 0) + { + angles.resize ((int)spots.size ()); + for (i = 0; i < (int)spots.size (); i++) + { + angle = GetAngle (spots[0].direction, spots[i].direction, lt->normal); + if (i == 0) + { + angle = 0.0; + } + angles[i].first = GetAngleDiff (angle, 0); + angles[i].second = i; + } + std::sort (angles.begin (), angles.end ()); + lt->sortedhullpoints.resize (0); + for (i = 0; i < (int)spots.size (); i++) + { + if (g_drawlerp && angles[i].second != i) + { + Developer (DEVELOPER_LEVEL_SPAM, "Debug: triangulation: internal error 8.\n"); + } + lt->sortedhullpoints.push_back (spots[angles[i].second]); + } + return; + } + + lt->sortedhullpoints.resize (0); + for (i = 0; i < (int)lt->sortedwedges.size (); i++) + { + w = <->sortedwedges[i]; + wnext = <->sortedwedges[(i + 1) % (int)lt->sortedwedges.size ()]; + + angles.resize ((int)spots.size ()); + for (j = 0; j < (int)spots.size (); j++) + { + angle = GetAngle (w->leftdirection, spots[j].direction, lt->normal); + angles[j].first = GetAngleDiff (angle, 0); + angles[j].second = j; + } + std::sort (angles.begin (), angles.end ()); + angle = GetAngle (w->leftdirection, wnext->leftdirection, lt->normal); + if ((int)lt->sortedwedges.size () == 1) + { + angle = 2 * Q_PI; + } + else + { + angle = GetAngleDiff (angle, 0); + } + + arc_spots.resize ((int)spots.size () + 2); + arc_angles.resize ((int)spots.size () + 2); + next.resize ((int)spots.size () + 2); + prev.resize ((int)spots.size () + 2); + + VectorCopy (w->leftspot, arc_spots[0].spot); + VectorCopy (w->leftdirection, arc_spots[0].direction); + arc_angles[0] = 0; + next[0] = 1; + prev[0] = -1; + n = 1; + for (j = 0; j < (int)spots.size (); j++) + { + if (NORMAL_EPSILON <= angles[j].first && angles[j].first <= angle - NORMAL_EPSILON) + { + arc_spots[n] = spots[angles[j].second]; + arc_angles[n] = angles[j].first; + next[n] = n + 1; + prev[n] = n - 1; + n++; + } + } + VectorCopy (wnext->leftspot, arc_spots[n].spot); + VectorCopy (wnext->leftdirection, arc_spots[n].direction); + arc_angles[n] = angle; + next[n] = -1; + prev[n] = n - 1; + n++; + + for (j = 1; next[j] != -1; j = next[j]) + { + while (prev[j] != -1) + { + if (arc_angles[next[j]] - arc_angles[prev[j]] <= Q_PI + NORMAL_EPSILON) + { + frac = GetFrac (arc_spots[prev[j]].spot, arc_spots[next[j]].spot, arc_spots[j].direction, lt->normal); + len = (1 - frac) * DotProduct (arc_spots[prev[j]].spot, arc_spots[j].direction) + + frac * DotProduct (arc_spots[next[j]].spot, arc_spots[j].direction); + dist = DotProduct (arc_spots[j].spot, arc_spots[j].direction); + if (dist <= len + NORMAL_EPSILON) + { + j = prev[j]; + next[j] = next[next[j]]; + prev[next[j]] = j; + continue; + } + } + break; + } + } + + for (j = 0; next[j] != -1; j = next[j]) + { + lt->sortedhullpoints.push_back (arc_spots[j]); + } + } +} + +#ifdef HLRAD_BILINEARINTERPOLATION +static bool TryMakeSquare (localtriangulation_t *lt, int i) +{ + localtriangulation_t::Wedge *w1; + localtriangulation_t::Wedge *w2; + localtriangulation_t::Wedge *w3; + vec3_t v; + vec3_t dir1; + vec3_t dir2; + vec_t angle; + + w1 = <->sortedwedges[i]; + w2 = <->sortedwedges[(i + 1) % (int)lt->sortedwedges.size ()]; + w3 = <->sortedwedges[(i + 2) % (int)lt->sortedwedges.size ()]; + + // (o, p1, p2) and (o, p2, p3) must be triangles and not in a square + if (w1->shape != localtriangulation_t::Wedge::eTriangular || w2->shape != localtriangulation_t::Wedge::eTriangular) + { + return false; + } + + // (o, p1, p3) must be a triangle + angle = GetAngle (w1->leftdirection, w3->leftdirection, lt->normal); + angle = GetAngleDiff (angle, 0); + if (angle >= TRIANGLE_SHAPE_THRESHOLD) + { + return false; + } + + // (p2, p1, p3) must be a triangle + VectorSubtract (w1->leftspot, w2->leftspot, v); + if (!GetDirection (v, lt->normal, dir1)) + { + return false; + } + VectorSubtract (w3->leftspot, w2->leftspot, v); + if (!GetDirection (v, lt->normal, dir2)) + { + return false; + } + angle = GetAngle (dir2, dir1, lt->normal); + angle = GetAngleDiff (angle, 0); + if (angle >= TRIANGLE_SHAPE_THRESHOLD) + { + return false; + } + + w1->shape = localtriangulation_t::Wedge::eSquareLeft; + VectorSubtract (vec3_origin, dir1, w1->wedgenormal); + w2->shape = localtriangulation_t::Wedge::eSquareRight; + VectorCopy (dir2, w2->wedgenormal); + return true; +} + +static void FindSquares (localtriangulation_t *lt) +{ + int i; + localtriangulation_t::Wedge *w; + std::vector< std::pair< vec_t, int > > dists; + + if ((int)lt->sortedwedges.size () <= 2) + { + return; + } + + dists.resize ((int)lt->sortedwedges.size ()); + for (i = 0; i < (int)lt->sortedwedges.size (); i++) + { + w = <->sortedwedges[i]; + dists[i].first = DotProduct (w->leftspot, w->leftdirection); + dists[i].second = i; + } + std::sort (dists.begin (), dists.end ()); + + for (i = 0; i < (int)lt->sortedwedges.size (); i++) + { + TryMakeSquare (lt, dists[i].second); + TryMakeSquare (lt, (dists[i].second - 2 + (int)lt->sortedwedges.size ()) % (int)lt->sortedwedges.size ()); + } +} + +#endif +static localtriangulation_t *CreateLocalTriangulation (const facetriangulation_t *facetrian, int patchnum) +{ + localtriangulation_t *lt; + int i; + const patch_t *patch; + vec_t dot; + int facenum; + localtriangulation_t::Wedge *w; + localtriangulation_t::Wedge *wnext; + vec_t angle; + vec_t total; + vec3_t v; + vec3_t normal; + + facenum = facetrian->facenum; + patch = &g_patches[patchnum]; + lt = new localtriangulation_t; + + // Fill basic information for this local triangulation + lt->plane = *getPlaneFromFaceNumber (facenum); + lt->plane.dist += DotProduct (g_face_offset[facenum], lt->plane.normal); + lt->winding = *patch->winding; + VectorMA (patch->origin, -PATCH_HUNT_OFFSET, lt->plane.normal, lt->center); + dot = DotProduct (lt->center, lt->plane.normal) - lt->plane.dist; + VectorMA (lt->center, -dot, lt->plane.normal, lt->center); + if (!point_in_winding_noedge (lt->winding, lt->plane, lt->center, DEFAULT_EDGE_WIDTH)) + { + snap_to_winding_noedge (lt->winding, lt->plane, lt->center, DEFAULT_EDGE_WIDTH, 4 * DEFAULT_EDGE_WIDTH); + } + VectorCopy (lt->plane.normal, lt->normal); + lt->patchnum = patchnum; + lt->neighborfaces = facetrian->neighbors; + + // Gather all patches from nearby faces + GatherPatches (lt, facetrian); + + // Remove distant patches + PurgePatches (lt); + + // Calculate wedge types + total = 0.0; + for (i = 0; i < (int)lt->sortedwedges.size (); i++) + { + w = <->sortedwedges[i]; + wnext = <->sortedwedges[(i + 1) % (int)lt->sortedwedges.size ()]; + + angle = GetAngle (w->leftdirection, wnext->leftdirection, lt->normal); + if (g_drawlerp && ((int)lt->sortedwedges.size () >= 2 && fabs (angle) <= (0.9*Q_PI/180))) + { + Developer (DEVELOPER_LEVEL_SPAM, "Debug: triangulation: internal error 9.\n"); + } + angle = GetAngleDiff (angle, 0); + if ((int)lt->sortedwedges.size () == 1) + { + angle = 2 * Q_PI; + } + total += angle; + + if (angle <= Q_PI + NORMAL_EPSILON) + { + if (angle < TRIANGLE_SHAPE_THRESHOLD) + { + w->shape = localtriangulation_t::Wedge::eTriangular; + VectorClear (w->wedgenormal); + } + else + { + w->shape = localtriangulation_t::Wedge::eConvex; + VectorSubtract (wnext->leftspot, w->leftspot, v); + GetDirection (v, lt->normal, w->wedgenormal); + } + } + else + { + w->shape = localtriangulation_t::Wedge::eConcave; + VectorAdd (wnext->leftdirection, w->leftdirection, v); + CrossProduct (lt->normal, v, normal); + VectorSubtract (wnext->leftdirection, w->leftdirection, v); + VectorAdd (normal, v, normal); + GetDirection (normal, lt->normal, w->wedgenormal); + if (g_drawlerp && VectorLength (w->wedgenormal) == 0) + { + Developer (DEVELOPER_LEVEL_SPAM, "Debug: triangulation: internal error 10.\n"); + } + } + } + if (g_drawlerp && ((int)lt->sortedwedges.size () > 0 && fabs (total - 2 * Q_PI) > 10 * NORMAL_EPSILON)) + { + Developer (DEVELOPER_LEVEL_SPAM, "Debug: triangulation: internal error 11.\n"); + } +#ifdef HLRAD_BILINEARINTERPOLATION + FindSquares (lt); +#endif + + // Calculate hull points + PlaceHullPoints (lt); + + return lt; +} + +static void FreeLocalTriangulation (localtriangulation_t *lt) +{ + delete lt; +} + +static void FindNeighbors (facetriangulation_t *facetrian) +{ + int i; + int j; + int e; + const edgeshare_t *es; + int side; + const facelist_t *fl; + int facenum; + int facenum2; + const dface_t *f; + const dface_t *f2; + const dplane_t *dp; + const dplane_t *dp2; + + facenum = facetrian->facenum; + f = &g_dfaces[facenum]; + dp = getPlaneFromFace (f); + + facetrian->neighbors.resize (0); + + facetrian->neighbors.push_back (facenum); + + for (i = 0; i < f->numedges; i++) + { + e = g_dsurfedges[f->firstedge + i]; + es = &g_edgeshare[abs (e)]; + if (!es->smooth) + { + continue; + } + f2 = es->faces[e > 0? 1: 0]; + facenum2 = f2 - g_dfaces; + dp2 = getPlaneFromFace (f2); + if (DotProduct (dp->normal, dp2->normal) < -NORMAL_EPSILON) + { + continue; + } + for (j = 0; j < (int)facetrian->neighbors.size (); j++) + { + if (facetrian->neighbors[j] == facenum2) + { + break; + } + } + if (j == (int)facetrian->neighbors.size ()) + { + facetrian->neighbors.push_back (facenum2); + } + } + + for (i = 0; i < f->numedges; i++) + { + e = g_dsurfedges[f->firstedge + i]; + es = &g_edgeshare[abs (e)]; + if (!es->smooth) + { + continue; + } + for (side = 0; side < 2; side++) + { + for (fl = es->vertex_facelist[side]; fl; fl = fl->next) + { + f2 = fl->face; + facenum2 = f2 - g_dfaces; + dp2 = getPlaneFromFace (f2); + if (DotProduct (dp->normal, dp2->normal) < -NORMAL_EPSILON) + { + continue; + } + for (j = 0; j < (int)facetrian->neighbors.size (); j++) + { + if (facetrian->neighbors[j] == facenum2) + { + break; + } + } + if (j == (int)facetrian->neighbors.size ()) + { + facetrian->neighbors.push_back (facenum2); + } + } + } + } +} + +static void BuildWalls (facetriangulation_t *facetrian) +{ + int i; + int j; + int facenum; + int facenum2; + const dface_t *f; + const dface_t *f2; + const dplane_t *dp; + const dplane_t *dp2; + int e; + const edgeshare_t *es; + vec_t dot; + + facenum = facetrian->facenum; + f = &g_dfaces[facenum]; + dp = getPlaneFromFace (f); + + facetrian->walls.resize (0); + + for (i = 0; i < (int)facetrian->neighbors.size (); i++) + { + facenum2 = facetrian->neighbors[i]; + f2 = &g_dfaces[facenum2]; + dp2 = getPlaneFromFace (f2); + if (DotProduct (dp->normal, dp2->normal) <= 0.1) + { + continue; + } + for (j = 0; j < f2->numedges; j++) + { + e = g_dsurfedges[f2->firstedge + j]; + es = &g_edgeshare[abs (e)]; + if (!es->smooth) + { + facetriangulation_t::Wall wall; + + VectorAdd (g_dvertexes[g_dedges[abs(e)].v[0]].point, g_face_offset[facenum], wall.points[0]); + VectorAdd (g_dvertexes[g_dedges[abs(e)].v[1]].point, g_face_offset[facenum], wall.points[1]); + VectorSubtract (wall.points[1], wall.points[0], wall.direction); + dot = DotProduct (wall.direction, dp->normal); + VectorMA (wall.direction, -dot, dp->normal, wall.direction); + if (VectorNormalize (wall.direction)) + { + CrossProduct (wall.direction, dp->normal, wall.normal); + VectorNormalize (wall.normal); + facetrian->walls.push_back (wall); + } + } + } + } +} + +static void CollectUsedPatches (facetriangulation_t *facetrian) +{ + int i; + int j; + int k; + int patchnum; + const localtriangulation_t *lt; + const localtriangulation_t::Wedge *w; + + facetrian->usedpatches.resize (0); + for (i = 0; i < (int)facetrian->localtriangulations.size (); i++) + { + lt = facetrian->localtriangulations[i]; + + patchnum = lt->patchnum; + for (k = 0; k < (int)facetrian->usedpatches.size (); k++) + { + if (facetrian->usedpatches[k] == patchnum) + { + break; + } + } + if (k == (int)facetrian->usedpatches.size ()) + { + facetrian->usedpatches.push_back (patchnum); + } + + for (j = 0; j < (int)lt->sortedwedges.size (); j++) + { + w = <->sortedwedges[j]; + + patchnum = w->leftpatchnum; + for (k = 0; k < (int)facetrian->usedpatches.size (); k++) + { + if (facetrian->usedpatches[k] == patchnum) + { + break; + } + } + if (k == (int)facetrian->usedpatches.size ()) + { + facetrian->usedpatches.push_back (patchnum); + } + } + } +} + + +// ===================================================================================== +// CreateTriangulations +// ===================================================================================== +void CreateTriangulations (int facenum) +{ + try + { + + facetriangulation_t *facetrian; + int patchnum; + const patch_t *patch; + localtriangulation_t *lt; + + g_facetriangulations[facenum] = new facetriangulation_t; + facetrian = g_facetriangulations[facenum]; + + facetrian->facenum = facenum; + + // Find neighbors + FindNeighbors (facetrian); + + // Build walls + BuildWalls (facetrian); + + // Create local triangulation around each patch + facetrian->localtriangulations.resize (0); + for (patch = g_face_patches[facenum]; patch; patch = patch->next) + { + patchnum = patch - g_patches; + lt = CreateLocalTriangulation (facetrian, patchnum); + facetrian->localtriangulations.push_back (lt); + } + + // Collect used patches + CollectUsedPatches (facetrian); + + } + catch (std::bad_alloc) + { + hlassume (false, assume_NoMemory); + } +} + +// ===================================================================================== +// GetTriangulationPatches +// ===================================================================================== +void GetTriangulationPatches (int facenum, int *numpatches, const int **patches) +{ + const facetriangulation_t *facetrian; + + facetrian = g_facetriangulations[facenum]; + *numpatches = (int)facetrian->usedpatches.size (); + *patches = facetrian->usedpatches.data (); +} + +// ===================================================================================== +// FreeTriangulations +// ===================================================================================== +void FreeTriangulations () +{ + try + { + + int i; + int j; + facetriangulation_t *facetrian; + + for (i = 0; i < g_numfaces; i++) + { + facetrian = g_facetriangulations[i]; + + for (j = 0; j < (int)facetrian->localtriangulations.size (); j++) + { + FreeLocalTriangulation (facetrian->localtriangulations[j]); + } + + delete facetrian; + g_facetriangulations[i] = NULL; + } + + } + catch (std::bad_alloc) + { + hlassume (false, assume_NoMemory); + } +} + +#else +#ifdef HLRAD_LERP_VL +static bool LerpTriangle(const lerpTriangulation_t* trian, const vec3_t point, vec3_t result, + #ifdef ZHLT_XASH + vec3_t &result_direction, + #endif + int pt1, int pt2, int pt3, int style); +static bool LerpEdge(const lerpTriangulation_t* trian, const vec3_t point, vec3_t result, + #ifdef ZHLT_XASH + vec3_t &result_direction, + #endif + int pt1, int pt2, int style); +static bool LerpNearest(const lerpTriangulation_t* trian, const vec3_t point, vec3_t result, + #ifdef ZHLT_XASH + vec3_t &result_direction, + #endif + int pt1, int style); +#define LERP_EPSILON 0.5 +#endif + +#ifndef HLRAD_LERP_VL +// ===================================================================================== +// TestWallIntersectTri +// Returns true if wall polygon intersects patch polygon +// ===================================================================================== +static bool TestWallIntersectTri(const lerpTriangulation_t* const trian, const vec3_t p1, const vec3_t p2, const vec3_t p3) +{ +#ifdef HLRAD_LERP_VL + int x; + const lerpWall_t* wall; + const vec_t* normal = trian->plane->normal; + { + vec3_t d1, d2, n; + VectorSubtract (p3, p2, d1); + VectorSubtract (p1, p2, d2); + CrossProduct (d1, d2, n); + if (DotProduct (n, normal) < 0) + { + const vec_t* tmp; + tmp = p2; + p2 = p3; + p3 = tmp; + } + } + for (x = 0, wall = trian->walls; x < trian->numwalls; x++, wall++) + { + if (point_in_tri (wall->vertex0, trian->plane, p1, p2, p3)) + { + return true; + } + if (point_in_tri (wall->vertex1, trian->plane, p1, p2, p3)) + { + return true; + } + } + return false; +#else + int x; + const lerpWall_t* wall = trian->walls; + dplane_t plane; + + plane_from_points(p1, p2, p3, &plane); + + // Try first 'vertical' side + // Since we test each of the 3 segments from patch against wall, only one side of wall needs testing inside + // patch (since they either dont intersect at all at this point, or both line segments intersect inside) + for (x = 0; x < trian->numwalls; x++, wall++) + { + vec3_t point; + + // Try side A + if (intersect_linesegment_plane(&plane, wall->vertex[0], wall->vertex[3], point)) + { + if (point_in_tri(point, &plane, p1, p2, p3)) + { +#if 0 + Verbose + ("Wall side A point @ (%4.3f %4.3f %4.3f) inside patch (%4.3f %4.3f %4.3f) (%4.3f %4.3f %4.3f) (%4.3f %4.3f %4.3f)\n", + point[0], point[1], point[2], p1[0], p1[1], p1[2], p2[0], p2[1], p2[2], p3[0], p3[1], p3[2]); +#endif + return true; + } + } + } + return false; +#endif +} +#endif + +// ===================================================================================== +// TestLineSegmentIntersectWall +// Returns true if line would hit the 'wall' (to fix light streaking) +// ===================================================================================== +static bool TestLineSegmentIntersectWall(const lerpTriangulation_t* const trian, const vec3_t p1, const vec3_t p2 +#ifdef HLRAD_LERP_VL + , vec_t epsilon = ON_EPSILON +#endif + ) +{ + int x; + const lerpWall_t* wall = trian->walls; + + for (x = 0; x < trian->numwalls; x++, wall++) + { +#ifdef HLRAD_LERP_VL + vec_t front, back, frac; + vec3_t mid; + front = DotProduct (p1, wall->plane.normal) - wall->plane.dist; + back = DotProduct (p2, wall->plane.normal) - wall->plane.dist; + if (fabs (front) <= epsilon && fabs (back) <= epsilon) + { + if (DotProduct (p1, wall->increment) < DotProduct (wall->vertex0, wall->increment) - epsilon && + DotProduct (p2, wall->increment) < DotProduct (wall->vertex0, wall->increment) - epsilon ) + { + continue; + } + if (DotProduct (p1, wall->increment) > DotProduct (wall->vertex1, wall->increment) + epsilon && + DotProduct (p2, wall->increment) > DotProduct (wall->vertex1, wall->increment) + epsilon ) + { + continue; + } + return true; + } + if (front > 0.9 * epsilon && back > 0.9 * epsilon || front < -0.9 * epsilon && back < -0.9 * epsilon) + { + continue; + } + frac = front / (front - back); + frac = qmax (0, qmin (frac, 1)); + mid[0] = p1[0] + (p2[0] - p1[0]) * frac; + mid[1] = p1[1] + (p2[1] - p1[1]) * frac; + mid[2] = p1[2] + (p2[2] - p1[2]) * frac; + if (DotProduct (mid, wall->increment) < DotProduct (wall->vertex0, wall->increment) - epsilon || + DotProduct (mid, wall->increment) > DotProduct (wall->vertex1, wall->increment) + epsilon ) + { + continue; + } + return true; +#else + vec3_t point; + + if (intersect_linesegment_plane(&wall->plane, p1, p2, point)) + { + if (point_in_wall(wall, point)) + { +#if 0 + Verbose + ("Tested point @ (%4.3f %4.3f %4.3f) blocks segment from (%4.3f %4.3f %4.3f) to (%4.3f %4.3f %4.3f) intersects wall\n", + point[0], point[1], point[2], p1[0], p1[1], p1[2], p2[0], p2[1], p2[2]); +#endif + return true; + } + } +#endif + } + return false; +} + +#ifndef HLRAD_LERP_VL +// ===================================================================================== +// TestTriIntersectWall +// Returns true if line would hit the 'wall' (to fix light streaking) +// ===================================================================================== +static bool TestTriIntersectWall(const lerpTriangulation_t* trian, const vec3_t p1, const vec3_t p2, + const vec3_t p3) +{ + if (TestLineSegmentIntersectWall(trian, p1, p2) || TestLineSegmentIntersectWall(trian, p1, p3) + || TestLineSegmentIntersectWall(trian, p2, p3)) + { + return true; + } + return false; +} +#endif + +// ===================================================================================== +// LerpTriangle +// pt1 must be closest point +// ===================================================================================== +#ifdef HLRAD_LERP_VL +static bool LerpTriangle(const lerpTriangulation_t* trian, const vec3_t point, vec3_t result, + #ifdef ZHLT_XASH + vec3_t &result_direction, + #endif + int pt1, int pt2, int pt3, int style) +{ + patch_t *p1; + patch_t *p2; + patch_t *p3; + const vec_t *o1; + const vec_t *o2; + const vec_t *o3; + vec3_t v; + + p1 = trian->points[pt1]; + p2 = trian->points[pt2]; + p3 = trian->points[pt3]; +#ifdef HLRAD_LERP_TEXNORMAL + o1 = trian->points_pos[pt1]; + o2 = trian->points_pos[pt2]; + o3 = trian->points_pos[pt3]; +#else + o1 = p1->origin; + o2 = p2->origin; + o3 = p3->origin; +#endif + + dplane_t edge12, edge13, edge23; + + VectorSubtract (o2, o1, v); + CrossProduct (trian->plane->normal, v, edge12.normal); + VectorNormalize (edge12.normal); + edge12.dist = DotProduct (o1, edge12.normal); + + VectorSubtract (o3, o1, v); + CrossProduct (trian->plane->normal, v, edge13.normal); + VectorNormalize (edge13.normal); + edge13.dist = DotProduct (o1, edge13.normal); + + VectorSubtract (o3, o2, v); + CrossProduct (trian->plane->normal, v, edge23.normal); + VectorNormalize (edge23.normal); + edge23.dist = DotProduct (o2, edge23.normal); + + vec_t dist12 = DotProduct (point, edge12.normal) - edge12.dist; + vec_t dist13 = DotProduct (point, edge13.normal) - edge13.dist; + vec_t dist23 = DotProduct (point, edge23.normal) - edge23.dist; + + // the point could be on the edge. + if (fabs (dist12) < LERP_EPSILON) + { + if (LerpEdge (trian, point, result, + #ifdef ZHLT_XASH + result_direction, + #endif + pt1, pt2, style)) + { + return true; + } + } + if (fabs (dist13) < LERP_EPSILON) + { + if (LerpEdge (trian, point, result, + #ifdef ZHLT_XASH + result_direction, + #endif + pt1, pt3, style)) + { + return true; + } + } + if (fabs (dist23) < LERP_EPSILON) + { + if (LerpEdge (trian, point, result, + #ifdef ZHLT_XASH + result_direction, + #endif + pt2, pt3, style)) + { + return true; + } + } + + // now that the point is not on the edge, we should check the shape of the triangle and make sure that the point is precisely inside the triangle. + vec_t maxdist12 = DotProduct (o3, edge12.normal) - edge12.dist; + vec_t maxdist13 = DotProduct (o2, edge13.normal) - edge13.dist; + vec_t maxdist23 = DotProduct (o1, edge23.normal) - edge23.dist; + if (fabs (maxdist12) <= LERP_EPSILON || fabs (maxdist13) <= LERP_EPSILON || fabs (maxdist23) <= LERP_EPSILON) + { + return false; + } + if (dist12 / maxdist12 < 0 || dist12 / maxdist12 > 1 || + dist13 / maxdist13 < 0 || dist13 / maxdist13 > 1 || + dist23 / maxdist23 < 0 || dist23 / maxdist23 > 1 ) + { + return false; + } + + vec3_t testpoint; + VectorScale (p1->origin, 1 - dist13 / maxdist13 - dist12 / maxdist12, testpoint); + VectorMA (testpoint, dist13 / maxdist13, p2->origin, testpoint); + VectorMA (testpoint, dist12 / maxdist12, p3->origin, testpoint); + if (TestLineSegmentIntersectWall (trian, testpoint, p1->origin) || + TestLineSegmentIntersectWall (trian, testpoint, p2->origin) || + TestLineSegmentIntersectWall (trian, testpoint, p3->origin) || + TestLineSegmentIntersectWall (trian, p2->origin, p1->origin) || + TestLineSegmentIntersectWall (trian, p3->origin, p2->origin) || + TestLineSegmentIntersectWall (trian, p1->origin, p3->origin) ) + { + return false; + } + + const vec3_t *l1, *l2, *l3; + #ifdef ZHLT_XASH + const vec3_t *l1_d, *l2_d, *l3_d; + #endif + l1 = GetTotalLight(p1, style + #ifdef ZHLT_XASH + , l1_d + #endif + ); + l2 = GetTotalLight(p2, style + #ifdef ZHLT_XASH + , l2_d + #endif + ); + l3 = GetTotalLight(p3, style + #ifdef ZHLT_XASH + , l3_d + #endif + ); + + VectorScale (*l1, 1 - dist13 / maxdist13 - dist12 / maxdist12, result); + VectorMA (result, dist13 / maxdist13, *l2, result); + VectorMA (result, dist12 / maxdist12, *l3, result); + #ifdef ZHLT_XASH + VectorScale (*l1_d, 1 - dist13 / maxdist13 - dist12 / maxdist12, result_direction); + VectorMA (result_direction, dist13 / maxdist13, *l2_d, result_direction); + VectorMA (result_direction, dist12 / maxdist12, *l3_d, result_direction); + #endif + + return true; +} +#else +#ifdef ZHLT_TEXLIGHT +static void LerpTriangle(const lerpTriangulation_t* const trian, const vec3_t point, vec3_t result, const unsigned pt1, const unsigned pt2, const unsigned pt3, int style) //LRC +#else +static void LerpTriangle(const lerpTriangulation_t* const trian, const vec3_t point, vec3_t result, const unsigned pt1, const unsigned pt2, const unsigned pt3) +#endif +{ + patch_t* p1; + patch_t* p2; + patch_t* p3; + vec3_t base; + vec3_t d1; + vec3_t d2; + vec_t x; + vec_t y; + vec_t y1; + vec_t x2; + vec3_t v; + dplane_t ep1; + dplane_t ep2; + + p1 = trian->points[pt1]; + p2 = trian->points[pt2]; + p3 = trian->points[pt3]; + +#ifdef ZHLT_TEXLIGHT + VectorCopy(*GetTotalLight(p1, style), base); //LRC + VectorSubtract(*GetTotalLight(p2, style), base, d1); //LRC + VectorSubtract(*GetTotalLight(p3, style), base, d2); //LRC +#else + VectorCopy(p1->totallight, base); + VectorSubtract(p2->totallight, base, d1); + VectorSubtract(p3->totallight, base, d2); +#endif + + // Get edge normals + VectorSubtract(p1->origin, p2->origin, v); + VectorNormalize(v); + CrossProduct(v, trian->plane->normal, ep1.normal); + ep1.dist = DotProduct(p1->origin, ep1.normal); + + VectorSubtract(p1->origin, p3->origin, v); + VectorNormalize(v); + CrossProduct(v, trian->plane->normal, ep2.normal); + ep2.dist = DotProduct(p1->origin, ep2.normal); + + x = DotProduct(point, ep1.normal) - ep1.dist; + y = DotProduct(point, ep2.normal) - ep2.dist; + + y1 = DotProduct(p2->origin, ep2.normal) - ep2.dist; + x2 = DotProduct(p3->origin, ep1.normal) - ep1.dist; + + VectorCopy(base, result); + if (fabs(x2) >= ON_EPSILON) + { + int i; + + for (i = 0; i < 3; i++) + { + result[i] += x * d2[i] / x2; + } + } + if (fabs(y1) >= ON_EPSILON) + { + int i; + + for (i = 0; i < 3; i++) + { + result[i] += y * d1[i] / y1; + } + } +} +#endif + +// ===================================================================================== +// LerpNearest +// ===================================================================================== +#ifdef HLRAD_LERP_VL +static bool LerpNearest(const lerpTriangulation_t* trian, const vec3_t point, vec3_t result, + #ifdef ZHLT_XASH + vec3_t &result_direction, + #endif + int pt1, int style) +{ + patch_t *patch; + patch = trian->points[pt1]; + const vec3_t *l; + #ifdef ZHLT_XASH + const vec3_t *l_d; + #endif + l = GetTotalLight(patch, style + #ifdef ZHLT_XASH + , l_d + #endif + ); + VectorCopy (*l, result); + #ifdef ZHLT_XASH + VectorCopy (*l_d, result_direction); + #endif + return true; +} +#else +#ifdef ZHLT_TEXLIGHT +static void LerpNearest(const lerpTriangulation_t* const trian, vec3_t result, int style) //LRC +#else +static void LerpNearest(const lerpTriangulation_t* const trian, vec3_t result) +#endif +{ + unsigned x; + unsigned numpoints = trian->numpoints; + patch_t* patch; + + // Find nearest in original face + for (x = 0; x < numpoints; x++) + { + patch = trian->points[trian->dists[x].patch]; + + if (patch->faceNumber == trian->facenum) + { + #ifdef ZHLT_TEXLIGHT + VectorCopy(*GetTotalLight(patch, style), result); //LRC + #else + VectorCopy(patch->totallight, result); + #endif + return; + } + } + + // If none in nearest face, settle for nearest + if (numpoints) + { + #ifdef ZHLT_TEXLIGHT + VectorCopy(*GetTotalLight(trian->points[trian->dists[0].patch], style), result); //LRC + #else + VectorCopy(trian->points[trian->dists[0].patch]->totallight, result); + #endif + } + else + { + VectorClear(result); + } +} +#endif + +// ===================================================================================== +// LerpEdge +// ===================================================================================== +#ifdef HLRAD_LERP_VL +static bool LerpEdge(const lerpTriangulation_t* trian, const vec3_t point, vec3_t result, + #ifdef ZHLT_XASH + vec3_t &result_direction, + #endif + int pt1, int pt2, int style) +{ + patch_t *p1; + patch_t *p2; + const vec_t *o1; + const vec_t *o2; + vec3_t increment; + vec3_t normal; + vec_t x; + vec_t x1; + vec3_t base; + vec3_t d; + #ifdef ZHLT_XASH + vec3_t base_direction; + vec3_t d_direction; + #endif + p1 = trian->points[pt1]; + p2 = trian->points[pt2]; +#ifdef HLRAD_LERP_TEXNORMAL + o1 = trian->points_pos[pt1]; + o2 = trian->points_pos[pt2]; +#else + o1 = p1->origin; + o2 = p2->origin; +#endif + if (TestLineSegmentIntersectWall(trian, p1->origin, p2->origin)) + return false; + VectorSubtract (o2, o1, increment); + CrossProduct (trian->plane->normal, increment, normal); + CrossProduct (normal, trian->plane->normal, increment); + VectorNormalize (increment); + x = DotProduct (o2, increment) - DotProduct (o1, increment); + x1 = DotProduct (point, increment) - DotProduct (o1, increment); + if (x1 < -LERP_EPSILON || x1 > x + LERP_EPSILON) + { + return false; + } + const vec3_t *l; + #ifdef ZHLT_XASH + const vec3_t *l_d; + #endif + l = GetTotalLight(p1, style + #ifdef ZHLT_XASH + , l_d + #endif + ); + VectorCopy (*l, base); + #ifdef ZHLT_XASH + VectorCopy (*l_d, base_direction); + #endif + l = GetTotalLight(p2, style + #ifdef ZHLT_XASH + , l_d + #endif + ); + VectorSubtract (*l, base, d); + VectorCopy (base, result); + #ifdef ZHLT_XASH + VectorSubtract (*l_d, base_direction, d_direction); + VectorCopy (base_direction, result_direction); + #endif + if (x > LERP_EPSILON) + { + vec_t frac = x1 / x; + if (frac < 0) frac = 0; + if (frac > 1) frac = 1; + VectorMA (result, frac, d, result); + #ifdef ZHLT_XASH + VectorMA (result_direction, frac, d_direction, result_direction); + #endif + } + return true; +} +#else +#ifdef ZHLT_TEXLIGHT +static bool LerpEdge(const lerpTriangulation_t* const trian, const vec3_t point, vec3_t result, int style) //LRC +#else +static bool LerpEdge(const lerpTriangulation_t* const trian, const vec3_t point, vec3_t result) +#endif +{ + patch_t* p1; + patch_t* p2; +#ifndef HLRAD_LERP_VL + patch_t* p3; +#endif + vec3_t v1; + vec3_t v2; + vec_t d; + +#ifdef HLRAD_LERP_VL + p1 = trian->points[pt1]; + p2 = trian->points[pt2]; +#else + p1 = trian->points[trian->dists[0].patch]; + p2 = trian->points[trian->dists[1].patch]; + p3 = trian->points[trian->dists[2].patch]; +#endif + +#ifndef HLRAD_LERP_FIX + VectorSubtract(point, p1->origin, v2); + VectorNormalize(v2); +#endif + + // Try nearest and 2 + if (!TestLineSegmentIntersectWall(trian, p1->origin, p2->origin)) + { +#ifdef HLRAD_LERP_FIX + vec_t total_length, length1, length2; + VectorSubtract (p2->origin, p1->origin, v1); + CrossProduct (trian->plane->normal, v1, v2); + CrossProduct (v2, trian->plane->normal, v1); + length1 = DotProduct (v1, point) - DotProduct (v1, p1->origin); + length2 = DotProduct (v1, p2->origin) - DotProduct (v1, point); + total_length = DotProduct (v1, p2->origin) - DotProduct (v1, p1->origin); + if (total_length > 0 && length1 >= 0 && length2 >= 0) + { + int i; +#else + VectorSubtract(p2->origin, p1->origin, v1); + VectorNormalize(v1); + d = DotProduct(v2, v1); + if (d >= ON_EPSILON) + { + int i; + vec_t length1; + vec_t length2; + vec3_t segment; + vec_t total_length; + + VectorSubtract(point, p1->origin, segment); + length1 = VectorLength(segment); + VectorSubtract(point, p2->origin, segment); + length2 = VectorLength(segment); + total_length = length1 + length2; +#endif + + for (i = 0; i < 3; i++) + { +#ifdef ZHLT_TEXLIGHT + #ifdef HLRAD_LERP_FIX + result[i] = (((*GetTotalLight(p1, style))[i] * length2) + ((*GetTotalLight(p2, style))[i] * length1)) / total_length; //LRC + #else + result[i] = (((*GetTotalLight(p1, style))[i] * length2) + ((*GetTotalLight(p1, style))[i] * length1)) / total_length; //LRC + #endif +#else + result[i] = ((p1->totallight[i] * length2) + (p2->totallight[i] * length1)) / total_length; +#endif + } + return true; + } + } + +#ifndef HLRAD_LERP_VL + // Try nearest and 3 + if (!TestLineSegmentIntersectWall(trian, p1->origin, p3->origin)) + { +#ifdef HLRAD_LERP_FIX + vec_t total_length, length1, length2; + VectorSubtract (p3->origin, p1->origin, v1); + CrossProduct (trian->plane->normal, v1, v2); + CrossProduct (v2, trian->plane->normal, v1); + length1 = DotProduct (v1, point) - DotProduct (v1, p1->origin); + length2 = DotProduct (v1, p3->origin) - DotProduct (v1, point); + total_length = DotProduct (v1, p3->origin) - DotProduct (v1, p1->origin); + if (total_length > 0 && length1 >= 0 && length2 >= 0) + { + int i; +#else + VectorSubtract(p3->origin, p1->origin, v1); + VectorNormalize(v1); + d = DotProduct(v2, v1); + if (d >= ON_EPSILON) + { + int i; + vec_t length1; + vec_t length2; + vec3_t segment; + vec_t total_length; + + VectorSubtract(point, p1->origin, segment); + length1 = VectorLength(segment); + VectorSubtract(point, p3->origin, segment); + length2 = VectorLength(segment); + total_length = length1 + length2; +#endif + + for (i = 0; i < 3; i++) + { + #ifdef ZHLT_TEXLIGHT + result[i] = (((*GetTotalLight(p1, style))[i] * length2) + ((*GetTotalLight(p3, style))[i] * length1)) / total_length; //LRC + #else + result[i] = ((p1->totallight[i] * length2) + (p3->totallight[i] * length1)) / total_length; + #endif + } + return true; + } + } +#endif + return false; +} +#endif + + +// ===================================================================================== +// +// SampleTriangulation +// +// ===================================================================================== + +// ===================================================================================== +// dist_sorter +// ===================================================================================== +static int CDECL dist_sorter(const void* p1, const void* p2) +{ + lerpDist_t* dist1 = (lerpDist_t*) p1; + lerpDist_t* dist2 = (lerpDist_t*) p2; + +#ifdef HLRAD_LERP_VL + if (dist1->invalid < dist2->invalid) + return -1; + if (dist2->invalid < dist1->invalid) + return 1; + if (dist1->dist + ON_EPSILON < dist2->dist) + return -1; + if (dist2->dist + ON_EPSILON < dist1->dist) + return 1; + if (dist1->pos[0] + ON_EPSILON < dist2->pos[0]) + return -1; + if (dist2->pos[0] + ON_EPSILON < dist1->pos[0]) + return 1; + if (dist1->pos[1] + ON_EPSILON < dist2->pos[1]) + return -1; + if (dist2->pos[1] + ON_EPSILON < dist1->pos[1]) + return 1; + if (dist1->pos[2] + ON_EPSILON < dist2->pos[2]) + return -1; + if (dist2->pos[2] + ON_EPSILON < dist1->pos[2]) + return 1; + return 0; +#else + if (dist1->dist < dist2->dist) + { + return -1; + } + else if (dist1->dist > dist2->dist) + { + return 1; + } + else + { + return 0; + } +#endif +} + +// ===================================================================================== +// FindDists +// ===================================================================================== +static void FindDists(const lerpTriangulation_t* const trian, const vec3_t point) +{ + unsigned x; + unsigned numpoints = trian->numpoints; + patch_t** patch = trian->points; + lerpDist_t* dists = trian->dists; + vec3_t delta; +#ifdef HLRAD_LERP_VL + vec3_t testpoint; + { + VectorCopy (point, testpoint); + Winding *w = new Winding (*trian->face); + int i; + for (i = 0; i < w->m_NumPoints; i++) + { + VectorAdd (w->m_Points[i], g_face_offset[trian->facenum], w->m_Points[i]); + } + if (!point_in_winding_noedge (*w, *trian->plane, testpoint, DEFAULT_EDGE_WIDTH)) + { + snap_to_winding_noedge (*w, *trian->plane, testpoint, DEFAULT_EDGE_WIDTH, 4 * DEFAULT_EDGE_WIDTH); + } + delete w; + if (!TestLineSegmentIntersectWall (trian, point, testpoint, 2 * ON_EPSILON)) + { + VectorCopy (point, testpoint); + } + } +#endif + + for (x = 0; x < numpoints; x++, patch++, dists++) + { +#ifdef HLRAD_LERP_TEXNORMAL + VectorSubtract (trian->points_pos[x], point, delta); +#else + VectorSubtract((*patch)->origin, point, delta); +#endif +#ifdef HLRAD_LERP_VL + vec3_t normal; + CrossProduct (trian->plane->normal, delta, normal); + CrossProduct (normal, trian->plane->normal, delta); +#endif + dists->dist = VectorLength(delta); + dists->patch = x; +#ifdef HLRAD_LERP_VL + dists->invalid = TestLineSegmentIntersectWall (trian, testpoint, (*patch)->origin); + VectorCopy ((*patch)->origin, dists->pos); +#endif + } + + qsort((void*)trian->dists, (size_t) numpoints, sizeof(lerpDist_t), dist_sorter); +} + +// ===================================================================================== +// SampleTriangulation +// ===================================================================================== +#ifdef ZHLT_TEXLIGHT +#ifdef HLRAD_LERP_VL +void SampleTriangulation(const lerpTriangulation_t* const trian, const vec3_t point, vec3_t result, + #ifdef ZHLT_XASH + vec3_t &result_direction, + #endif + int style) +#else +void SampleTriangulation(const lerpTriangulation_t* const trian, vec3_t point, vec3_t result, int style) //LRC +#endif +#else +void SampleTriangulation(const lerpTriangulation_t* const trian, vec3_t point, vec3_t result) +#endif +{ + FindDists(trian, point); + +#ifdef HLRAD_LERP_VL + VectorClear(result); + #ifdef ZHLT_XASH + VectorClear (result_direction); + #endif + if (trian->numpoints >= 3 && trian->dists[2].invalid <= 0 && g_lerp_enabled) + { + int pt1 = trian->dists[0].patch; + int pt2 = trian->dists[1].patch; + int pt3 = trian->dists[2].patch; + if (LerpTriangle (trian, point, result, + #ifdef ZHLT_XASH + result_direction, + #endif + pt1, pt2, pt3, style)) + { + #ifdef HLRAD_DEBUG_DRAWPOINTS + if (g_drawlerp) + result[0] = 100, result[1] = 100, result[2] = 100; + #endif + return; + } +#ifdef HLRAD_LERP_TRY5POINTS + if (trian->numpoints >= 4 && trian->dists[3].invalid <= 0) + { + int pt4 = trian->dists[3].patch; + if (LerpTriangle (trian, point, result, + #ifdef ZHLT_XASH + result_direction, + #endif + pt1, pt2, pt4, style)) + { + #ifdef HLRAD_DEBUG_DRAWPOINTS + if (g_drawlerp) + result[0] = 100, result[1] = 100, result[2] = 0; + return; + #endif + } + if (LerpTriangle (trian, point, result, + #ifdef ZHLT_XASH + result_direction, + #endif + pt1, pt3, pt4, style)) + { + #ifdef HLRAD_DEBUG_DRAWPOINTS + if (g_drawlerp) + result[0] = 100, result[1] = 0, result[2] = 100; + #endif + return; + } + if (trian->numpoints >= 5 && trian->dists[4].invalid <= 0) + { + int pt5 = trian->dists[4].patch; + if (LerpTriangle (trian, point, result, + #ifdef ZHLT_XASH + result_direction, + #endif + pt1, pt2, pt5, style)) + { + #ifdef HLRAD_DEBUG_DRAWPOINTS + if (g_drawlerp) + result[0] = 100, result[1] = 0, result[2] = 0; + #endif + return; + } + if (LerpTriangle (trian, point, result, + #ifdef ZHLT_XASH + result_direction, + #endif + pt1, pt3, pt5, style)) + { + #ifdef HLRAD_DEBUG_DRAWPOINTS + if (g_drawlerp) + result[0] = 100, result[1] = 0, result[2] = 0; + #endif + return; + } + if (LerpTriangle (trian, point, result, + #ifdef ZHLT_XASH + result_direction, + #endif + pt1, pt4, pt5, style)) + { + #ifdef HLRAD_DEBUG_DRAWPOINTS + if (g_drawlerp) + result[0] = 100, result[1] = 0, result[2] = 0; + #endif + return; + } + } + } +#endif + } + if (trian->numpoints >= 2 && trian->dists[1].invalid <= 0 && g_lerp_enabled) + { + int pt1 = trian->dists[0].patch; + int pt2 = trian->dists[1].patch; + if (LerpEdge (trian, point, result, + #ifdef ZHLT_XASH + result_direction, + #endif + pt1, pt2, style)) + { + #ifdef HLRAD_DEBUG_DRAWPOINTS + if (g_drawlerp) + result[0] = 0, result[1] = 100, result[2] = 100; + #endif + return; + } + if (trian->numpoints >= 3 && trian->dists[2].invalid <= 0 ) + { + int pt3 = trian->dists[2].patch; + if (LerpEdge (trian, point, result, + #ifdef ZHLT_XASH + result_direction, + #endif + pt1, pt3, style)) + { + #ifdef HLRAD_DEBUG_DRAWPOINTS + if (g_drawlerp) + result[0] = 0, result[1] = 100, result[2] = 0; + #endif + return; + } + } + } + if (trian->numpoints >= 1) + { + int pt1 = trian->dists[0].patch; + if (LerpNearest (trian, point, result, + #ifdef ZHLT_XASH + result_direction, + #endif + pt1, style)) + { + #ifdef HLRAD_DEBUG_DRAWPOINTS + if (g_drawlerp) + result[0] = 0, result[1] = 0, result[2] = 100; + #endif + return; + } + } +#else + if ((trian->numpoints > 3) && (g_lerp_enabled)) + { + unsigned pt1; + unsigned pt2; + unsigned pt3; + vec_t* p1; + vec_t* p2; + vec_t* p3; + dplane_t plane; + + pt1 = trian->dists[0].patch; + pt2 = trian->dists[1].patch; + pt3 = trian->dists[2].patch; + + p1 = trian->points[pt1]->origin; + p2 = trian->points[pt2]->origin; + p3 = trian->points[pt3]->origin; + + plane_from_points(p1, p2, p3, &plane); + SnapToPlane(&plane, point, 0.0); + if (point_in_tri(point, &plane, p1, p2, p3)) + { // TODO check edges/tri for blocking by wall + if (!TestWallIntersectTri(trian, p1, p2, p3) && !TestTriIntersectWall(trian, p1, p2, p3)) + { +#ifdef ZHLT_TEXLIGHT + LerpTriangle(trian, point, result, pt1, pt2, pt3, style); //LRC +#else + LerpTriangle(trian, point, result, pt1, pt2, pt3); +#endif + return; + } + } + else + { +#ifdef ZHLT_TEXLIGHT + if (LerpEdge(trian, point, result, style)) //LRC +#else + if (LerpEdge(trian, point, result)) +#endif + { + return; + } + } + } + +#ifdef ZHLT_TEXLIGHT + LerpNearest(trian, result, style); //LRC +#else + LerpNearest(trian, result); +#endif +#endif +} + +// ===================================================================================== +// AddPatchToTriangulation +// ===================================================================================== +static void AddPatchToTriangulation(lerpTriangulation_t* trian, patch_t* patch) +{ +#ifdef HLRAD_PATCHBLACK_FIX + if (patch->flags != ePatchFlagOutside) +#else + if (!(patch->flags & ePatchFlagOutside)) +#endif + { + int pnum = trian->numpoints; + + if (pnum >= trian->maxpoints) + { + trian->points = (patch_t**)realloc(trian->points, sizeof(patch_t*) * (trian->maxpoints + DEFAULT_MAX_LERP_POINTS)); + + hlassume(trian->points != NULL, assume_NoMemory); + memset(trian->points + trian->maxpoints, 0, sizeof(patch_t*) * DEFAULT_MAX_LERP_POINTS); // clear the new block +#ifdef HLRAD_LERP_TEXNORMAL + trian->points_pos = (vec3_t *)realloc(trian->points_pos, sizeof (vec3_t) * (trian->maxpoints + DEFAULT_MAX_LERP_POINTS)); + hlassume (trian->points_pos != NULL, assume_NoMemory); + memset (trian->points_pos + trian->maxpoints, 0, sizeof (vec3_t) * DEFAULT_MAX_LERP_POINTS); +#endif + + trian->maxpoints += DEFAULT_MAX_LERP_POINTS; + } + + trian->points[pnum] = patch; +#ifdef HLRAD_LERP_TEXNORMAL + VectorCopy (patch->origin, trian->points_pos[pnum]); + if (patch->faceNumber != trian->facenum) + { + vec3_t snapdir; + dplane_t p1 = *trian->plane; + p1.dist += DotProduct (p1.normal, g_face_offset[trian->facenum]); + dplane_t p2 = *getPlaneFromFaceNumber (patch->faceNumber); + p2.dist += DotProduct (p2.normal, g_face_offset[patch->faceNumber]); +#ifdef HLRAD_GROWSAMPLE + // we have abandoned the texnormal approach, since the lighting should not be affected by the texnormal: it should only rely on the s,t coordinate of vertices, which doesn't include texnormal information + VectorAdd (p1.normal, p2.normal, snapdir); + if (!VectorNormalize (snapdir)) // normal2 = -normal1 + { + // skip this patch + return; + } +#else + VectorCopy (p1.normal, snapdir); + if (!GetIntertexnormal (patch->faceNumber, trian->facenum, snapdir)) + { + Warning ("AddPatchToTriangulation: internal error 1."); + } +#endif + VectorMA (trian->points_pos[pnum], -PATCH_HUNT_OFFSET, p2.normal, trian->points_pos[pnum]); + vec_t dist = (DotProduct (trian->points_pos[pnum], p1.normal) - p1.dist) / DotProduct (snapdir, p1.normal); + VectorMA (trian->points_pos[pnum], -dist, snapdir, trian->points_pos[pnum]); + } +#endif + trian->numpoints++; + } +} + +// ===================================================================================== +// CreateWalls +// ===================================================================================== +#ifdef HLRAD_LERP_VL +static void AddWall (lerpTriangulation_t *trian, const vec_t *v1, const vec_t *v2) // v1 & v2 should be without offset +{ + int facenum = trian->facenum; + const dplane_t* p = trian->plane; + vec3_t delta; + vec3_t p0; + vec3_t p1; + vec3_t normal; + lerpWall_t *wall; + + if (trian->numwalls >= trian->maxwalls) + { + trian->walls = + (lerpWall_t*)realloc(trian->walls, sizeof(lerpWall_t) * (trian->maxwalls + DEFAULT_MAX_LERP_WALLS)); + hlassume(trian->walls != NULL, assume_NoMemory); + memset(trian->walls + trian->maxwalls, 0, sizeof(lerpWall_t) * DEFAULT_MAX_LERP_WALLS); // clear the new block + trian->maxwalls += DEFAULT_MAX_LERP_WALLS; + } + + wall = &trian->walls[trian->numwalls]; + trian->numwalls++; + VectorAdd (v1, g_face_offset[facenum], p0); + VectorAdd (v2, g_face_offset[facenum], p1); + VectorSubtract (p1, p0, delta); + CrossProduct (p->normal, delta, normal); + if (VectorNormalize (normal) == 0.0) + { + trian->numwalls--; + return; + } + VectorCopy (normal, wall->plane.normal); + wall->plane.dist = DotProduct (normal, p0); + CrossProduct (normal, p->normal, wall->increment); + VectorCopy (p0, wall->vertex0); + VectorCopy (p1, wall->vertex1); +} +#endif +#ifndef HLRAD_LERP_FACELIST +static void CreateWalls(lerpTriangulation_t* trian, const dface_t* const face) +{ +#ifdef HLRAD_LERP_VL + const dplane_t* p = getPlaneFromFace(face); + int facenum = face - g_dfaces; + int x; + + for (x = 0; x < face->numedges; x++) + { + edgeshare_t* es; + dface_t* f2; + int edgenum = g_dsurfedges[face->firstedge + x]; + + if (edgenum > 0) + { + es = &g_edgeshare[edgenum]; + f2 = es->faces[1]; + } + else + { + es = &g_edgeshare[-edgenum]; + f2 = es->faces[0]; + } + if (!es->smooth) + { + AddWall (trian, g_dvertexes[g_dedges[abs(edgenum)].v[0]].point, g_dvertexes[g_dedges[abs(edgenum)].v[1]].point); + } + else + { + int facenum2 = f2 - g_dfaces; + int x2; + for (x2 = 0; x2 < f2->numedges; x2++) + { + edgeshare_t *es2; + dface_t *f3; + int edgenum2 = g_dsurfedges[f2->firstedge + x2]; + if (edgenum2 > 0) + { + es2 = &g_edgeshare[edgenum2]; + f3 = es2->faces[1]; + } + else + { + es2 = &g_edgeshare[-edgenum2]; + f3 = es2->faces[0]; + } + if (!es2->smooth) + { + AddWall (trian, g_dvertexes[g_dedges[abs(edgenum2)].v[0]].point, g_dvertexes[g_dedges[abs(edgenum2)].v[1]].point); + } + } + } + } +#else + const dplane_t* p = getPlaneFromFace(face); + int facenum = face - g_dfaces; + int x; + + for (x = 0; x < face->numedges; x++) + { + edgeshare_t* es; + dface_t* f2; + int edgenum = g_dsurfedges[face->firstedge + x]; + + if (edgenum > 0) + { + es = &g_edgeshare[edgenum]; + f2 = es->faces[1]; + } + else + { + es = &g_edgeshare[-edgenum]; + f2 = es->faces[0]; + } + + // Build Wall for non-coplanar neighbhors +#ifdef HLRAD_GetPhongNormal_VL + if (f2 && !es->smooth) +#else + if (f2 && !es->coplanar && VectorCompare(vec3_origin, es->interface_normal)) +#endif + { + const dplane_t* plane = getPlaneFromFace(f2); + + // if plane isn't facing us, ignore it + #ifdef HLRAD_DPLANEOFFSET_MISCFIX + if (DotProduct(plane->normal, g_face_centroids[facenum]) < plane->dist + DotProduct(plane->normal, g_face_offset[facenum])) + #else + if (DotProduct(plane->normal, g_face_centroids[facenum]) < plane->dist) + #endif + { + continue; + } + + { + vec3_t delta; + vec3_t p0; + vec3_t p1; + lerpWall_t* wall; + + if (trian->numwalls >= trian->maxwalls) + { + trian->walls = + (lerpWall_t*)realloc(trian->walls, sizeof(lerpWall_t) * (trian->maxwalls + DEFAULT_MAX_LERP_WALLS)); + hlassume(trian->walls != NULL, assume_NoMemory); + memset(trian->walls + trian->maxwalls, 0, sizeof(lerpWall_t) * DEFAULT_MAX_LERP_WALLS); // clear the new block + trian->maxwalls += DEFAULT_MAX_LERP_WALLS; + } + + wall = &trian->walls[trian->numwalls]; + trian->numwalls++; + + VectorScale(p->normal, 10000.0, delta); + + VectorCopy(g_dvertexes[g_dedges[abs(edgenum)].v[0]].point, p0); + VectorCopy(g_dvertexes[g_dedges[abs(edgenum)].v[1]].point, p1); + + // Adjust for origin-based models + // technically we should use the other faces g_face_offset entries + // If they are nonzero, it has to be from the same model with + // the same offset, so we are cool + VectorAdd(p0, g_face_offset[facenum], p0); + VectorAdd(p1, g_face_offset[facenum], p1); + + VectorAdd(p0, delta, wall->vertex[0]); + VectorAdd(p1, delta, wall->vertex[1]); + VectorSubtract(p1, delta, wall->vertex[2]); + VectorSubtract(p0, delta, wall->vertex[3]); + + { + vec3_t delta1; + vec3_t delta2; + vec3_t normal; + vec_t dist; + + VectorSubtract(wall->vertex[2], wall->vertex[1], delta1); + VectorSubtract(wall->vertex[0], wall->vertex[1], delta2); + CrossProduct(delta1, delta2, normal); + VectorNormalize(normal); + dist = DotProduct(normal, p0); + + VectorCopy(normal, wall->plane.normal); + wall->plane.dist = dist; + } + } + } + } +#endif +} +#endif + +// ===================================================================================== +// AllocTriangulation +// ===================================================================================== +static lerpTriangulation_t* AllocTriangulation() +{ + lerpTriangulation_t* trian = (lerpTriangulation_t*)calloc(1, sizeof(lerpTriangulation_t)); +#ifdef HLRAD_HLASSUMENOMEMORY + hlassume (trian != NULL, assume_NoMemory); +#endif + + trian->maxpoints = DEFAULT_MAX_LERP_POINTS; + trian->maxwalls = DEFAULT_MAX_LERP_WALLS; + + trian->points = (patch_t**)calloc(DEFAULT_MAX_LERP_POINTS, sizeof(patch_t*)); +#ifdef HLRAD_LERP_TEXNORMAL + trian->points_pos = (vec3_t *)calloc (DEFAULT_MAX_LERP_POINTS, sizeof(vec3_t)); + hlassume (trian->points_pos != NULL, assume_NoMemory); +#endif + + trian->walls = (lerpWall_t*)calloc(DEFAULT_MAX_LERP_WALLS, sizeof(lerpWall_t)); + + hlassume(trian->points != NULL, assume_NoMemory); + hlassume(trian->walls != NULL, assume_NoMemory); + + return trian; +} + +// ===================================================================================== +// FreeTriangulation +// ===================================================================================== +void FreeTriangulation(lerpTriangulation_t* trian) +{ + free(trian->dists); + free(trian->points); +#ifdef HLRAD_LERP_TEXNORMAL + free(trian->points_pos); +#endif + free(trian->walls); +#ifdef HLRAD_LERP_FACELIST + for (facelist_t *next; trian->allfaces; trian->allfaces = next) + { + next = trian->allfaces->next; + free (trian->allfaces); + } +#endif + free(trian); +} + +#ifdef HLRAD_LERP_FACELIST +void AddFaceToTrian (facelist_t **faces, dface_t *face) +{ + for (; *faces; faces = &(*faces)->next) + { + if ((*faces)->face == face) + { + return; + } + } + *faces = (facelist_t *)malloc (sizeof (facelist_t)); + hlassume (*faces != NULL, assume_NoMemory); + (*faces)->face = face; + (*faces)->next = NULL; +} +void FindFaces (lerpTriangulation_t *trian) +{ + int i, j; + AddFaceToTrian (&trian->allfaces, &g_dfaces[trian->facenum]); + for (j = 0; j < trian->face->numedges; j++) + { + int edgenum = g_dsurfedges[trian->face->firstedge + j]; + edgeshare_t *es = &g_edgeshare[abs (edgenum)]; + dface_t *f2; + if (!es->smooth) + { + continue; + } + if (edgenum > 0) + { + f2 = es->faces[1]; + } + else + { + f2 = es->faces[0]; + } + AddFaceToTrian (&trian->allfaces, f2); + } + for (j = 0; j < trian->face->numedges; j++) + { + int edgenum = g_dsurfedges[trian->face->firstedge + j]; + edgeshare_t *es = &g_edgeshare[abs (edgenum)]; + if (!es->smooth) + { + continue; + } + for (i = 0; i < 2; i++) + { + facelist_t *fl; + for (fl = es->vertex_facelist[i]; fl; fl = fl->next) + { + dface_t *f2 = fl->face; + AddFaceToTrian (&trian->allfaces, f2); + } + } + } +} +#endif +// ===================================================================================== +// CreateTriangulation +// ===================================================================================== +lerpTriangulation_t* CreateTriangulation(const unsigned int facenum) +{ + const dface_t* f = g_dfaces + facenum; + const dplane_t* p = getPlaneFromFace(f); + lerpTriangulation_t* trian = AllocTriangulation(); + patch_t* patch; + unsigned int j; + dface_t* f2; + + trian->facenum = facenum; + trian->plane = p; + trian->face = f; + +#ifdef HLRAD_LERP_FACELIST + FindFaces (trian); + facelist_t *fl; + for (fl = trian->allfaces; fl; fl = fl->next) + { + f2 = fl->face; + int facenum2 = fl->face - g_dfaces; + for (patch = g_face_patches[facenum2]; patch; patch = patch->next) + { + AddPatchToTriangulation (trian, patch); + } + for (j = 0; j < f2->numedges; j++) + { + int edgenum = g_dsurfedges[f2->firstedge + j]; + edgeshare_t *es = &g_edgeshare[abs(edgenum)]; + if (!es->smooth) + { + AddWall (trian, g_dvertexes[g_dedges[abs(edgenum)].v[0]].point, g_dvertexes[g_dedges[abs(edgenum)].v[1]].point); + } + } + } +#else + for (patch = g_face_patches[facenum]; patch; patch = patch->next) + { + AddPatchToTriangulation(trian, patch); + } + + CreateWalls(trian, f); + + for (j = 0; j < f->numedges; j++) + { + edgeshare_t* es; + int edgenum = g_dsurfedges[f->firstedge + j]; + + if (edgenum > 0) + { + es = &g_edgeshare[edgenum]; + f2 = es->faces[1]; + } + else + { + es = &g_edgeshare[-edgenum]; + f2 = es->faces[0]; + } + +#ifdef HLRAD_GetPhongNormal_VL + if (!es->smooth) +#else + if (!es->coplanar && VectorCompare(vec3_origin, es->interface_normal)) +#endif + { + continue; + } + + for (patch = g_face_patches[f2 - g_dfaces]; patch; patch = patch->next) + { + AddPatchToTriangulation(trian, patch); + } + } +#endif + + trian->dists = (lerpDist_t*)calloc(trian->numpoints, sizeof(lerpDist_t)); +#ifdef HLRAD_HULLU + //Get rid off error that seems to happen with some opaque faces (when opaque face have all edges 'out' of map) + if(trian->numpoints != 0) // this line should be removed. --vluzacn +#endif + hlassume(trian->dists != NULL, assume_NoMemory); + + return trian; +} +#endif diff --git a/src/zhlt-vluzacn/hlrad/lightmap.cpp b/src/zhlt-vluzacn/hlrad/lightmap.cpp new file mode 100644 index 0000000..295c2d9 --- /dev/null +++ b/src/zhlt-vluzacn/hlrad/lightmap.cpp @@ -0,0 +1,9179 @@ +#include "qrad.h" + +edgeshare_t g_edgeshare[MAX_MAP_EDGES]; +vec3_t g_face_centroids[MAX_MAP_EDGES]; // BUG: should this be [MAX_MAP_FACES]? +bool g_sky_lighting_fix = DEFAULT_SKY_LIGHTING_FIX; +#ifndef HLRAD_GROWSAMPLE +#ifdef HLRAD_SMOOTH_TEXNORMAL +vec3_t g_face_texnormals[MAX_MAP_FACES]; +#endif +#endif + +//#define TEXTURE_STEP 16.0 + +#ifndef HLRAD_GROWSAMPLE +#ifdef HLRAD_SMOOTH_TEXNORMAL +bool GetIntertexnormal (int facenum1, int facenum2, vec_t *out) +{ + vec3_t normal; + const dplane_t *p1 = getPlaneFromFaceNumber (facenum1); + const dplane_t *p2 = getPlaneFromFaceNumber (facenum2); + VectorAdd (g_face_texnormals[facenum1], g_face_texnormals[facenum2], normal); + if (!VectorNormalize (normal) + || DotProduct (normal, p1->normal) <= NORMAL_EPSILON + || DotProduct (normal, p2->normal) <= NORMAL_EPSILON + ) + { + return false; + } + if (out) + { + VectorCopy (normal, out); + } + return true; +} +#endif +#endif +// ===================================================================================== +// PairEdges +// ===================================================================================== +#ifdef HLRAD_SMOOTH_FACELIST +typedef struct +{ + int numclipplanes; + dplane_t *clipplanes; +} +intersecttest_t; +bool TestFaceIntersect (intersecttest_t *t, int facenum) +{ + dface_t *f2 = &g_dfaces[facenum]; + Winding *w = new Winding (*f2); + int k; + for (k = 0; k < w->m_NumPoints; k++) + { + VectorAdd (w->m_Points[k], g_face_offset[facenum], w->m_Points[k]); + } + for (k = 0; k < t->numclipplanes; k++) + { + if (!w->Clip (t->clipplanes[k], false +#ifdef ZHLT_WINDING_EPSILON + , ON_EPSILON*4 +#endif + )) + { + break; + } + } + bool intersect = w->m_NumPoints > 0; + delete w; + return intersect; +} +intersecttest_t *CreateIntersectTest (const dplane_t *p, int facenum) +{ + dface_t *f = &g_dfaces[facenum]; + intersecttest_t *t; + t = (intersecttest_t *)malloc (sizeof (intersecttest_t)); + hlassume (t != NULL, assume_NoMemory); + t->clipplanes = (dplane_t *)malloc (f->numedges * sizeof (dplane_t)); + hlassume (t->clipplanes != NULL, assume_NoMemory); + t->numclipplanes = 0; + int j; + for (j = 0; j < f->numedges; j++) + { + // should we use winding instead? + int edgenum = g_dsurfedges[f->firstedge + j]; + { + vec3_t v0, v1; + vec3_t dir, normal; + if (edgenum < 0) + { + VectorCopy (g_dvertexes[g_dedges[-edgenum].v[1]].point, v0); + VectorCopy (g_dvertexes[g_dedges[-edgenum].v[0]].point, v1); + } + else + { + VectorCopy (g_dvertexes[g_dedges[edgenum].v[0]].point, v0); + VectorCopy (g_dvertexes[g_dedges[edgenum].v[1]].point, v1); + } + VectorAdd (v0, g_face_offset[facenum], v0); + VectorAdd (v1, g_face_offset[facenum], v1); + VectorSubtract (v1, v0, dir); + CrossProduct (dir, p->normal, normal); // facing inward + if (!VectorNormalize (normal)) + { + continue; + } + VectorCopy (normal, t->clipplanes[t->numclipplanes].normal); + t->clipplanes[t->numclipplanes].dist = DotProduct (v0, normal); + t->numclipplanes++; + } + } + return t; +} +void FreeIntersectTest (intersecttest_t *t) +{ + free (t->clipplanes); + free (t); +} +#endif +#ifdef HLRAD_GetPhongNormal_VL +void AddFaceForVertexNormal_printerror (const int edgeabs, const int edgeend, dface_t *const f) +{ + if (DEVELOPER_LEVEL_WARNING <= g_developer) + { + int i, e; + Log ("AddFaceForVertexNormal - bad face:\n"); + Log (" edgeabs=%d edgeend=%d\n", edgeabs, edgeend); + for (i = 0; i < f->numedges; i++) + { + e = g_dsurfedges[f->firstedge + i]; + edgeshare_t *es = &g_edgeshare[abs(e)]; + int v0 = g_dedges[abs(e)].v[0], v1 = g_dedges[abs(e)].v[1]; + Log (" e=%d v0=%d(%f,%f,%f) v1=%d(%f,%f,%f) share0=%d share1=%d\n", e, + v0, g_dvertexes[v0].point[0], g_dvertexes[v0].point[1], g_dvertexes[v0].point[2], + v1, g_dvertexes[v1].point[0], g_dvertexes[v1].point[1], g_dvertexes[v1].point[2], + (es->faces[0]==NULL? -1: es->faces[0]-g_dfaces), (es->faces[1]==NULL? -1: es->faces[1]-g_dfaces)); + } + } +} +int AddFaceForVertexNormal (const int edgeabs, int &edgeabsnext, const int edgeend, int &edgeendnext, dface_t *const f, dface_t *&fnext, vec_t &angle, vec3_t &normal) +// Must guarantee these faces will form a loop or a chain, otherwise will result in endless loop. +// +// e[end]/enext[endnext] +// * +// |\. +// |a\ fnext +// | \, +// | f \. +// | \. +// e enext +// +{ + VectorCopy(getPlaneFromFace(f)->normal, normal); + int vnum = g_dedges[edgeabs].v[edgeend]; + int iedge, iedgenext, edge, edgenext; + int i, e, count1, count2; + vec_t dot; + for (count1 = count2 = 0, i = 0; i < f->numedges; i++) + { + e = g_dsurfedges[f->firstedge + i]; + if (g_dedges[abs(e)].v[0] == g_dedges[abs(e)].v[1]) + continue; + if (abs(e) == edgeabs) + { + iedge = i; + edge = e; + count1 ++; + } + else if (g_dedges[abs(e)].v[0] == vnum || g_dedges[abs(e)].v[1] == vnum) + { + iedgenext = i; + edgenext = e; + count2 ++; + } + } + if (count1 != 1 || count2 != 1) + { + AddFaceForVertexNormal_printerror (edgeabs, edgeend, f); + return -1; + } + int vnum11, vnum12, vnum21, vnum22; + vec3_t vec1, vec2; + vnum11 = g_dedges[abs(edge)].v[edge>0?0:1]; + vnum12 = g_dedges[abs(edge)].v[edge>0?1:0]; + vnum21 = g_dedges[abs(edgenext)].v[edgenext>0?0:1]; + vnum22 = g_dedges[abs(edgenext)].v[edgenext>0?1:0]; + if (vnum == vnum12 && vnum == vnum21 && vnum != vnum11 && vnum != vnum22) + { + VectorSubtract(g_dvertexes[vnum11].point, g_dvertexes[vnum].point, vec1); + VectorSubtract(g_dvertexes[vnum22].point, g_dvertexes[vnum].point, vec2); + edgeabsnext = abs(edgenext); + edgeendnext = edgenext>0?0:1; + } + else if (vnum == vnum11 && vnum == vnum22 && vnum != vnum12 && vnum != vnum21) + { + VectorSubtract(g_dvertexes[vnum12].point, g_dvertexes[vnum].point, vec1); + VectorSubtract(g_dvertexes[vnum21].point, g_dvertexes[vnum].point, vec2); + edgeabsnext = abs(edgenext); + edgeendnext = edgenext>0?1:0; + } + else + { + AddFaceForVertexNormal_printerror (edgeabs, edgeend, f); + return -1; + } + VectorNormalize(vec1); + VectorNormalize(vec2); + dot = DotProduct(vec1,vec2); + dot = dot>1? 1: dot<-1? -1: dot; + angle = acos(dot); + edgeshare_t *es = &g_edgeshare[edgeabsnext]; + if (!(es->faces[0] && es->faces[1])) + return 1; + if (es->faces[0] == f && es->faces[1] != f) + fnext = es->faces[1]; + else if (es->faces[1] == f && es->faces[0] != f) + fnext = es->faces[0]; + else + { + AddFaceForVertexNormal_printerror (edgeabs, edgeend, f); + return -1; + } + return 0; +} +#endif +#ifdef HLRAD_GROWSAMPLE + +static bool TranslateTexToTex (int facenum, int edgenum, int facenum2, matrix_t &m, matrix_t &m_inverse) + // This function creates a matrix that can translate texture coords in face1 into texture coords in face2. + // It keeps all points in the common edge invariant. For example, if there is a point in the edge, and in the texture of face1, its (s,t)=(16,0), and in face2, its (s,t)=(128,64), then we must let matrix*(16,0,0)=(128,64,0) +{ + matrix_t worldtotex; + matrix_t worldtotex2; + dedge_t *e; + int i; + dvertex_t *vert[2]; + vec3_t face_vert[2]; + vec3_t face2_vert[2]; + vec3_t face_axis[2]; + vec3_t face2_axis[2]; + const vec3_t v_up = {0, 0, 1}; + vec_t len; + vec_t len2; + matrix_t edgetotex, edgetotex2; + matrix_t inv, inv2; + + TranslateWorldToTex (facenum, worldtotex); + TranslateWorldToTex (facenum2, worldtotex2); + + e = &g_dedges[edgenum]; + for (i = 0; i < 2; i++) + { + vert[i] = &g_dvertexes[e->v[i]]; + ApplyMatrix (worldtotex, vert[i]->point, face_vert[i]); + face_vert[i][2] = 0; // this value is naturally close to 0 assuming that the edge is on the face plane, but let's make this more explicit. + ApplyMatrix (worldtotex2, vert[i]->point, face2_vert[i]); + face2_vert[i][2] = 0; + } + + VectorSubtract (face_vert[1], face_vert[0], face_axis[0]); + len = VectorLength (face_axis[0]); + CrossProduct (v_up, face_axis[0], face_axis[1]); + if (CalcMatrixSign (worldtotex) < 0.0) // the three vectors s, t, facenormal are in reverse order + { + VectorInverse (face_axis[1]); + } + + VectorSubtract (face2_vert[1], face2_vert[0], face2_axis[0]); + len2 = VectorLength (face2_axis[0]); + CrossProduct (v_up, face2_axis[0], face2_axis[1]); + if (CalcMatrixSign (worldtotex2) < 0.0) + { + VectorInverse (face2_axis[1]); + } + + VectorCopy (face_axis[0], edgetotex.v[0]); // / v[0][0] v[1][0] \ is a rotation (possibly with a reflection by the edge) + VectorCopy (face_axis[1], edgetotex.v[1]); // \ v[0][1] v[1][1] / + VectorScale (v_up, len, edgetotex.v[2]); // encode the length into the 3rd value of the matrix + VectorCopy (face_vert[0], edgetotex.v[3]); // map (0,0) into the origin point + + VectorCopy (face2_axis[0], edgetotex2.v[0]); + VectorCopy (face2_axis[1], edgetotex2.v[1]); + VectorScale (v_up, len2, edgetotex2.v[2]); + VectorCopy (face2_vert[0], edgetotex2.v[3]); + + if (!InvertMatrix (edgetotex, inv) || !InvertMatrix (edgetotex2, inv2)) + { + return false; + } + MultiplyMatrix (edgetotex2, inv, m); + MultiplyMatrix (edgetotex, inv2, m_inverse); + + return true; +} + +#endif +void PairEdges() +{ + int i, j, k; + dface_t* f; + edgeshare_t* e; + + memset(&g_edgeshare, 0, sizeof(g_edgeshare)); + + f = g_dfaces; + for (i = 0; i < g_numfaces; i++, f++) + { +#ifndef HLRAD_GROWSAMPLE +#ifdef HLRAD_SMOOTH_TEXNORMAL + { + const dplane_t *fp = getPlaneFromFace (f); + vec3_t texnormal; + const texinfo_t *tex = &g_texinfo[f->texinfo]; + CrossProduct (tex->vecs[1], tex->vecs[0], texnormal); + VectorNormalize (texnormal); + if (DotProduct (texnormal, fp->normal) < 0) + { + VectorSubtract (vec3_origin, texnormal, texnormal); + } + VectorCopy (texnormal, g_face_texnormals[i]); + } +#endif +#endif +#ifdef HLRAD_EDGESHARE_NOSPECIAL + if (g_texinfo[f->texinfo].flags & TEX_SPECIAL) + { + // special textures don't have lightmaps + continue; + } +#endif + for (j = 0; j < f->numedges; j++) + { + k = g_dsurfedges[f->firstedge + j]; + if (k < 0) + { + e = &g_edgeshare[-k]; + + hlassert(e->faces[1] == NULL); + e->faces[1] = f; + } + else + { + e = &g_edgeshare[k]; + + hlassert(e->faces[0] == NULL); + e->faces[0] = f; + } + + if (e->faces[0] && e->faces[1]) { + // determine if coplanar + if (e->faces[0]->planenum == e->faces[1]->planenum +#ifdef HLRAD_PairEdges_FACESIDE_FIX + && e->faces[0]->side == e->faces[1]->side +#endif + ) { + e->coplanar = true; +#ifdef HLRAD_GetPhongNormal_VL + VectorCopy(getPlaneFromFace(e->faces[0])->normal, e->interface_normal); + e->cos_normals_angle = 1.0; +#endif + } else { + // see if they fall into a "smoothing group" based on angle of the normals + vec3_t normals[2]; + + VectorCopy(getPlaneFromFace(e->faces[0])->normal, normals[0]); + VectorCopy(getPlaneFromFace(e->faces[1])->normal, normals[1]); + + e->cos_normals_angle = DotProduct(normals[0], normals[1]); + +#ifdef HLRAD_CUSTOMSMOOTH + vec_t smoothvalue; + int m0 = g_texinfo[e->faces[0]->texinfo].miptex; + int m1 = g_texinfo[e->faces[1]->texinfo].miptex; + smoothvalue = qmax (g_smoothvalues[m0], g_smoothvalues[m1]); + if (m0 != m1) + { + smoothvalue = qmax (smoothvalue, g_smoothing_threshold_2); + } + if (smoothvalue >= 1.0 - NORMAL_EPSILON) + { + smoothvalue = 2.0; + } +#endif + if (e->cos_normals_angle > (1.0 - NORMAL_EPSILON)) + { + e->coplanar = true; +#ifdef HLRAD_GetPhongNormal_VL + VectorCopy(getPlaneFromFace(e->faces[0])->normal, e->interface_normal); + e->cos_normals_angle = 1.0; +#endif + } +#ifdef HLRAD_CUSTOMSMOOTH + else if (e->cos_normals_angle >= qmax (smoothvalue - NORMAL_EPSILON, NORMAL_EPSILON)) + { +#else + else if (g_smoothing_threshold > 0.0) + { + if (e->cos_normals_angle >= g_smoothing_threshold) +#endif + { + VectorAdd(normals[0], normals[1], e->interface_normal); + VectorNormalize(e->interface_normal); + } + } + } +#ifdef HLRAD_TRANSLUCENT + if (!VectorCompare (g_translucenttextures[g_texinfo[e->faces[0]->texinfo].miptex], g_translucenttextures[g_texinfo[e->faces[1]->texinfo].miptex])) + { + e->coplanar = false; + VectorClear (e->interface_normal); + } +#endif +#ifdef HLRAD_DIVERSE_LIGHTING + { + int miptex0, miptex1; + miptex0 = g_texinfo[e->faces[0]->texinfo].miptex; + miptex1 = g_texinfo[e->faces[1]->texinfo].miptex; + if (fabs (g_lightingconeinfo[miptex0][0] - g_lightingconeinfo[miptex1][0]) > NORMAL_EPSILON || + fabs (g_lightingconeinfo[miptex0][1] - g_lightingconeinfo[miptex1][1]) > NORMAL_EPSILON ) + { + e->coplanar = false; + VectorClear (e->interface_normal); + } + } +#endif +#ifdef HLRAD_GetPhongNormal_VL + if (!VectorCompare(e->interface_normal, vec3_origin)) + { + e->smooth = true; + } +#ifndef HLRAD_GROWSAMPLE +#ifdef HLRAD_SMOOTH_TEXNORMAL + if (!GetIntertexnormal (e->faces[0] - g_dfaces, e->faces[1] - g_dfaces)) + { + e->coplanar = false; + VectorClear (e->interface_normal); + e->smooth = false; + } +#endif +#endif +#ifdef HLRAD_GROWSAMPLE + if (e->smooth) + { + // compute the matrix in advance + if (!TranslateTexToTex (e->faces[0] - g_dfaces, abs (k), e->faces[1] - g_dfaces, e->textotex[0], e->textotex[1])) + { + e->smooth = false; + e->coplanar = false; + VectorClear (e->interface_normal); + + dvertex_t *dv = &g_dvertexes[g_dedges[abs(k)].v[0]]; + Developer (DEVELOPER_LEVEL_MEGASPAM, "TranslateTexToTex failed on face %d and %d @(%f,%f,%f)", (int)(e->faces[0] - g_dfaces), (int)(e->faces[1] - g_dfaces), dv->point[0], dv->point[1], dv->point[2]); + } + } +#endif +#endif + } + } + } +#ifdef HLRAD_GetPhongNormal_VL + { + int edgeabs, edgeabsnext; + int edgeend, edgeendnext; + int d; + dface_t *f, *fcurrent, *fnext; + vec_t angle, angles; + vec3_t normal, normals; + vec3_t edgenormal; + int r, count; + for (edgeabs = 0; edgeabs < MAX_MAP_EDGES; edgeabs++) + { + e = &g_edgeshare[edgeabs]; + if (!e->smooth) + continue; + VectorCopy(e->interface_normal, edgenormal); + if (g_dedges[edgeabs].v[0] == g_dedges[edgeabs].v[1]) + { + vec3_t errorpos; + VectorCopy (g_dvertexes[g_dedges[edgeabs].v[0]].point, errorpos); + VectorAdd (errorpos, g_face_offset[e->faces[0] - g_dfaces], errorpos); + Developer (DEVELOPER_LEVEL_WARNING, "PairEdges: invalid edge at (%f,%f,%f)", errorpos[0], errorpos[1], errorpos[2]); + VectorCopy(edgenormal, e->vertex_normal[0]); + VectorCopy(edgenormal, e->vertex_normal[1]); + } + else + { + const dplane_t *p0 = getPlaneFromFace (e->faces[0]); + const dplane_t *p1 = getPlaneFromFace (e->faces[1]); +#ifdef HLRAD_SMOOTH_FACELIST + intersecttest_t *test0 = CreateIntersectTest (p0, e->faces[0] - g_dfaces); + intersecttest_t *test1 = CreateIntersectTest (p1, e->faces[1] - g_dfaces); +#endif + for (edgeend = 0; edgeend < 2; edgeend++) + { + vec3_t errorpos; + VectorCopy (g_dvertexes[g_dedges[edgeabs].v[edgeend]].point, errorpos); + VectorAdd (errorpos, g_face_offset[e->faces[0] - g_dfaces], errorpos); + angles = 0; + VectorClear (normals); + + for (d = 0; d < 2; d++) + { + f = e->faces[d]; + count = 0, fnext = f, edgeabsnext = edgeabs, edgeendnext = edgeend; + while (1) + { + fcurrent = fnext; + r = AddFaceForVertexNormal (edgeabsnext, edgeabsnext, edgeendnext, edgeendnext, fcurrent, fnext, angle, normal); + count++; + if (r == -1) + { + Developer (DEVELOPER_LEVEL_WARNING, "PairEdges: face edges mislink at (%f,%f,%f)", errorpos[0], errorpos[1], errorpos[2]); + break; + } + if (count >= 100) + { + Developer (DEVELOPER_LEVEL_WARNING, "PairEdges: faces mislink at (%f,%f,%f)", errorpos[0], errorpos[1], errorpos[2]); + break; + } + if (DotProduct (normal, p0->normal) <= NORMAL_EPSILON || DotProduct(normal, p1->normal) <= NORMAL_EPSILON) + break; + #ifdef HLRAD_CUSTOMSMOOTH + vec_t smoothvalue; + int m0 = g_texinfo[f->texinfo].miptex; + int m1 = g_texinfo[fcurrent->texinfo].miptex; + smoothvalue = qmax (g_smoothvalues[m0], g_smoothvalues[m1]); + if (m0 != m1) + { + smoothvalue = qmax (smoothvalue, g_smoothing_threshold_2); + } + if (smoothvalue >= 1.0 - NORMAL_EPSILON) + { + smoothvalue = 2.0; + } + if (DotProduct (edgenormal, normal) < qmax (smoothvalue - NORMAL_EPSILON, NORMAL_EPSILON)) + #else + if (DotProduct (edgenormal, normal) + NORMAL_EPSILON < g_smoothing_threshold) + #endif + break; +#ifndef HLRAD_GROWSAMPLE + #ifdef HLRAD_SMOOTH_TEXNORMAL + if (!GetIntertexnormal (fcurrent - g_dfaces, e->faces[0] - g_dfaces) || !GetIntertexnormal (fcurrent - g_dfaces, e->faces[1] - g_dfaces)) + break; + #endif +#endif + #ifdef HLRAD_SMOOTH_FACELIST + if (fcurrent != e->faces[0] && fcurrent != e->faces[1] && + (TestFaceIntersect (test0, fcurrent - g_dfaces) || TestFaceIntersect (test1, fcurrent - g_dfaces))) + { + Developer (DEVELOPER_LEVEL_WARNING, "Overlapping faces around corner (%f,%f,%f)\n", errorpos[0], errorpos[1], errorpos[2]); + break; + } + #endif + angles += angle; + VectorMA(normals, angle, normal, normals); + #ifdef HLRAD_SMOOTH_FACELIST + { + bool in = false; + if (fcurrent == e->faces[0] || fcurrent == e->faces[1]) + { + in = true; + } + for (facelist_t *l = e->vertex_facelist[edgeend]; l; l = l->next) + { + if (fcurrent == l->face) + { + in = true; + } + } + if (!in) + { + facelist_t *l = (facelist_t *)malloc (sizeof (facelist_t)); + hlassume (l != NULL, assume_NoMemory); + l->face = fcurrent; + l->next = e->vertex_facelist[edgeend]; + e->vertex_facelist[edgeend] = l; + } + } + #endif + if (r != 0 || fnext == f) + break; + } + } + + if (angles < NORMAL_EPSILON) + { + VectorCopy(edgenormal, e->vertex_normal[edgeend]); + Developer (DEVELOPER_LEVEL_WARNING, "PairEdges: no valid faces at (%f,%f,%f)", errorpos[0], errorpos[1], errorpos[2]); + } + else + { + VectorNormalize(normals); + VectorCopy(normals, e->vertex_normal[edgeend]); + } + } +#ifdef HLRAD_SMOOTH_FACELIST + FreeIntersectTest (test0); + FreeIntersectTest (test1); +#endif + } + if (e->coplanar) + { + if (!VectorCompare (e->vertex_normal[0], e->interface_normal) || !VectorCompare (e->vertex_normal[1], e->interface_normal)) + { + e->coplanar = false; + } + } + } + } +#endif +} + +#define MAX_SINGLEMAP ((MAX_SURFACE_EXTENT+1)*(MAX_SURFACE_EXTENT+1)) //#define MAX_SINGLEMAP (18*18*4) //--vluzacn + +#ifdef HLRAD_AVOIDWALLBLEED +typedef enum +{ + WALLFLAG_NONE = 0, + WALLFLAG_NUDGED = 0x1, + WALLFLAG_BLOCKED = 0x2, // this only happens when the entire face and its surroundings are covered by solid or opaque entities + WALLFLAG_SHADOWED = 0x4, +} +wallflag_t; + +#endif +typedef struct +{ + vec_t* light; + vec_t facedist; + vec3_t facenormal; +#ifdef HLRAD_TRANSLUCENT + bool translucent_b; + vec3_t translucent_v; +#endif +#ifdef HLRAD_DIVERSE_LIGHTING + int miptex; +#endif + + int numsurfpt; + vec3_t surfpt[MAX_SINGLEMAP]; +#ifdef HLRAD_GROWSAMPLE + vec3_t* surfpt_position; //[MAX_SINGLEMAP] // surfpt_position[] are valid positions for light tracing, while surfpt[] are positions for getting phong normal and doing patch interpolation + int* surfpt_surface; //[MAX_SINGLEMAP] // the face that owns this position +#endif +#ifdef HLRAD_CalcPoints_NEW + bool surfpt_lightoutside[MAX_SINGLEMAP]; +#endif + + vec3_t texorg; + vec3_t worldtotex[2]; // s = (world - texorg) . worldtotex[0] + vec3_t textoworld[2]; // world = texorg + s * textoworld[0] +#ifdef HLRAD_CalcPoints_NEW + vec3_t texnormal; +#endif + + vec_t exactmins[2], exactmaxs[2]; + + int texmins[2], texsize[2]; + int lightstyles[256]; + int surfnum; + dface_t* face; +#ifdef HLRAD_BLUR + int lmcache_density; // shared by both s and t direction + int lmcache_offset; // shared by both s and t direction + int lmcache_side; +#ifdef HLRAD_AUTOCORING + vec3_t (*lmcache)[ALLSTYLES]; // lm: short for lightmap // don't forget to free! +#ifdef ZHLT_XASH + vec3_t (*lmcache_direction)[ALLSTYLES]; +#endif +#else + vec3_t (*lmcache)[MAXLIGHTMAPS]; +#endif +#ifdef HLRAD_AVOIDNORMALFLIP + vec3_t *lmcache_normal; // record the phong normals +#endif +#ifdef HLRAD_AVOIDWALLBLEED + int *lmcache_wallflags; // wallflag_t +#endif + int lmcachewidth; + int lmcacheheight; +#endif +} +lightinfo_t; +#ifdef HLRAD_MDL_LIGHT_HACK +#ifndef HLRAD_MDL_LIGHT_HACK_NEW +typedef struct +{ + vec3_t texorg; + vec3_t offset; + vec3_t textoworld[2]; + vec3_t worldtotex[2]; + int texmins[2], texsize[2]; +} +facesampleinfo_t; +static facesampleinfo_t facesampleinfo[MAX_MAP_FACES]; +#endif +#endif + +// ===================================================================================== +// TextureNameFromFace +// ===================================================================================== +static const char* TextureNameFromFace(const dface_t* const f) +{ + texinfo_t* tx; + miptex_t* mt; + int ofs; + + // + // check for light emited by texture + // + tx = &g_texinfo[f->texinfo]; + + ofs = ((dmiptexlump_t*)g_dtexdata)->dataofs[tx->miptex]; + mt = (miptex_t*)((byte*) g_dtexdata + ofs); + + return mt->name; +} + +// ===================================================================================== +// CalcFaceExtents +// Fills in s->texmins[] and s->texsize[] +// also sets exactmins[] and exactmaxs[] +// ===================================================================================== +static void CalcFaceExtents(lightinfo_t* l) +{ + const int facenum = l->surfnum; + dface_t* s; + float mins[2], maxs[2], val; //vec_t mins[2], maxs[2], val; //vluzacn + int i, j, e; + dvertex_t* v; + texinfo_t* tex; + + s = l->face; + +#ifdef ZHLT_64BIT_FIX + mins[0] = mins[1] = 99999999; + maxs[0] = maxs[1] = -99999999; +#else + mins[0] = mins[1] = 999999; + maxs[0] = maxs[1] = -99999; // a little small, but same with Goldsrc. --vluzacn +#endif + + tex = &g_texinfo[s->texinfo]; + + for (i = 0; i < s->numedges; i++) + { + e = g_dsurfedges[s->firstedge + i]; + if (e >= 0) + { + v = g_dvertexes + g_dedges[e].v[0]; + } + else + { + v = g_dvertexes + g_dedges[-e].v[1]; + } + + for (j = 0; j < 2; j++) + { + val = v->point[0] * tex->vecs[j][0] + + v->point[1] * tex->vecs[j][1] + v->point[2] * tex->vecs[j][2] + tex->vecs[j][3]; + if (val < mins[j]) + { + mins[j] = val; + } + if (val > maxs[j]) + { + maxs[j] = val; + } + } + } + + for (i = 0; i < 2; i++) + { + l->exactmins[i] = mins[i]; + l->exactmaxs[i] = maxs[i]; + +#ifndef ZHLT_64BIT_FIX + mins[i] = floor(mins[i] / TEXTURE_STEP); //mins[i] = floor(mins[i] / 16.0); //--vluzacn + maxs[i] = ceil(maxs[i] / TEXTURE_STEP); //maxs[i] = ceil(maxs[i] / 16.0); //--vluzacn + + l->texmins[i] = mins[i]; + l->texsize[i] = maxs[i] - mins[i]; +#endif + } +#ifdef ZHLT_64BIT_FIX + int bmins[2]; + int bmaxs[2]; + GetFaceExtents (l->surfnum, bmins, bmaxs); + for (i = 0; i < 2; i++) + { + mins[i] = bmins[i]; + maxs[i] = bmaxs[i]; + l->texmins[i] = bmins[i]; + l->texsize[i] = bmaxs[i] - bmins[i]; + } +#endif + + if (!(tex->flags & TEX_SPECIAL)) + { + if ((l->texsize[0] > MAX_SURFACE_EXTENT) || (l->texsize[1] > MAX_SURFACE_EXTENT) + || l->texsize[0] < 0 || l->texsize[1] < 0 //--vluzacn + ) + { + ThreadLock(); + PrintOnce("\nfor Face %d (texture %s) at ", s - g_dfaces, TextureNameFromFace(s)); + + for (i = 0; i < s->numedges; i++) + { + e = g_dsurfedges[s->firstedge + i]; + if (e >= 0) + { + v = g_dvertexes + g_dedges[e].v[0]; + } + else + { + v = g_dvertexes + g_dedges[-e].v[1]; + } +#ifdef HLRAD_OVERWRITEVERTEX_FIX + vec3_t pos; + VectorAdd (v->point, g_face_offset[facenum], pos); + Log ("(%4.3f %4.3f %4.3f) ", pos[0], pos[1], pos[2]); +#else + VectorAdd(v->point, g_face_offset[facenum], v->point); + Log("(%4.3f %4.3f %4.3f) ", v->point[0], v->point[1], v->point[2]); +#endif + } + Log("\n"); + + Error( "Bad surface extents (%d x %d)\nCheck the file ZHLTProblems.html for a detailed explanation of this problem", l->texsize[0], l->texsize[1]); + } + } +#ifdef HLRAD_BLUR + // allocate sample light cache + { + if (g_extra + #ifdef HLRAD_FASTMODE + && !g_fastmode + #endif + ) + { + l->lmcache_density = 3; + } + else + { + l->lmcache_density = 1; + } + l->lmcache_side = (int)ceil ((0.5 * g_blur * l->lmcache_density - 0.5) * (1 - NORMAL_EPSILON)); + l->lmcache_offset = l->lmcache_side; + l->lmcachewidth = l->texsize[0] * l->lmcache_density + 1 + 2 * l->lmcache_side; + l->lmcacheheight = l->texsize[1] * l->lmcache_density + 1 + 2 * l->lmcache_side; + #ifdef HLRAD_AUTOCORING + l->lmcache = (vec3_t (*)[ALLSTYLES])malloc (l->lmcachewidth * l->lmcacheheight * sizeof (vec3_t [ALLSTYLES])); +#ifdef ZHLT_XASH + l->lmcache_direction = (vec3_t (*)[ALLSTYLES])malloc (l->lmcachewidth * l->lmcacheheight * sizeof (vec3_t [ALLSTYLES])); +#endif + #else + l->lmcache = (vec3_t (*)[MAXLIGHTMAPS])malloc (l->lmcachewidth * l->lmcacheheight * sizeof (vec3_t [MAXLIGHTMAPS])); + #endif + hlassume (l->lmcache != NULL, assume_NoMemory); +#ifdef ZHLT_XASH + hlassume (l->lmcache_direction != NULL, assume_NoMemory); +#endif +#ifdef HLRAD_AVOIDNORMALFLIP + l->lmcache_normal = (vec3_t *)malloc (l->lmcachewidth * l->lmcacheheight * sizeof (vec3_t)); + hlassume (l->lmcache_normal != NULL, assume_NoMemory); +#endif +#ifdef HLRAD_AVOIDWALLBLEED + l->lmcache_wallflags = (int *)malloc (l->lmcachewidth * l->lmcacheheight * sizeof (int)); + hlassume (l->lmcache_wallflags != NULL, assume_NoMemory); +#endif +#ifdef HLRAD_GROWSAMPLE + l->surfpt_position = (vec3_t *)malloc (MAX_SINGLEMAP * sizeof (vec3_t)); + l->surfpt_surface = (int *)malloc (MAX_SINGLEMAP * sizeof (int)); + hlassume (l->surfpt_position != NULL && l->surfpt_surface != NULL, assume_NoMemory); +#endif + } +#endif +} + +// ===================================================================================== +// CalcFaceVectors +// Fills in texorg, worldtotex. and textoworld +// ===================================================================================== +static void CalcFaceVectors(lightinfo_t* l) +{ + texinfo_t* tex; + int i, j; + vec3_t texnormal; + vec_t distscale; + vec_t dist, len; + + tex = &g_texinfo[l->face->texinfo]; + + // convert from float to double + for (i = 0; i < 2; i++) + { + for (j = 0; j < 3; j++) + { + l->worldtotex[i][j] = tex->vecs[i][j]; + } + } + + // calculate a normal to the texture axis. points can be moved along this + // without changing their S/T + CrossProduct(tex->vecs[1], tex->vecs[0], texnormal); + VectorNormalize(texnormal); + + // flip it towards plane normal + distscale = DotProduct(texnormal, l->facenormal); + if (distscale == 0.0) + { + const unsigned facenum = l->face - g_dfaces; + + ThreadLock(); + Log("Malformed face (%d) normal @ \n", facenum); + Winding* w = new Winding(*l->face); + { + const unsigned numpoints = w->m_NumPoints; + unsigned x; + for (x=0; xm_Points[x], g_face_offset[facenum], w->m_Points[x]); + } + } + w->Print(); + delete w; + ThreadUnlock(); + + hlassume(false, assume_MalformedTextureFace); + } + + if (distscale < 0) + { + distscale = -distscale; + VectorSubtract(vec3_origin, texnormal, texnormal); + } + + // distscale is the ratio of the distance along the texture normal to + // the distance along the plane normal + distscale = 1.0 / distscale; + +#ifdef ZHLT_FREETEXTUREAXIS + for (i = 0; i < 2; i++) + { + CrossProduct (l->worldtotex[!i], l->facenormal, l->textoworld[i]); + len = DotProduct (l->textoworld[i], l->worldtotex[i]); + VectorScale (l->textoworld[i], 1 / len, l->textoworld[i]); + } +#else + for (i = 0; i < 2; i++) + { + len = (float)VectorLength(l->worldtotex[i]); + dist = DotProduct(l->worldtotex[i], l->facenormal); + dist *= distscale; + VectorMA(l->worldtotex[i], -dist, texnormal, l->textoworld[i]); + VectorScale(l->textoworld[i], (1 / len) * (1 / len), l->textoworld[i]); + } +#endif + + // calculate texorg on the texture plane + for (i = 0; i < 3; i++) + { + l->texorg[i] = -tex->vecs[0][3] * l->textoworld[0][i] - tex->vecs[1][3] * l->textoworld[1][i]; + } + + // project back to the face plane +#ifdef HLRAD_GROWSAMPLE + dist = DotProduct(l->texorg, l->facenormal) - l->facedist; +#else + dist = DotProduct(l->texorg, l->facenormal) - l->facedist - DEFAULT_HUNT_OFFSET; +#endif + dist *= distscale; + VectorMA(l->texorg, -dist, texnormal, l->texorg); +#ifdef HLRAD_CalcPoints_NEW + VectorCopy (texnormal, l->texnormal); +#endif + +} + +// ===================================================================================== +// SetSurfFromST +// ===================================================================================== +static void SetSurfFromST(const lightinfo_t* const l, vec_t* surf, const vec_t s, const vec_t t) +{ + const int facenum = l->surfnum; + int j; + + for (j = 0; j < 3; j++) + { + surf[j] = l->texorg[j] + l->textoworld[0][j] * s + l->textoworld[1][j] * t; + } + + // Adjust for origin-based models + VectorAdd(surf, g_face_offset[facenum], surf); +} + +#ifndef HLRAD_CalcPoints_NEW +// ===================================================================================== +// FindSurfaceMidpoint +// ===================================================================================== +static dleaf_t* FindSurfaceMidpoint(const lightinfo_t* const l, vec_t* midpoint) +{ + int s, t; + int w, h; + vec_t starts, startt; + vec_t us, ut; + + vec3_t broken_midpoint; + vec3_t surface_midpoint; + int inside_point_count; + + dleaf_t* last_valid_leaf = NULL; + dleaf_t* leaf_mid; + + const int facenum = l->surfnum; + const dface_t* f = g_dfaces + facenum; + const dplane_t* p = getPlaneFromFace(f); + + const vec_t* face_delta = g_face_offset[facenum]; + + h = l->texsize[1] + 1; + w = l->texsize[0] + 1; + starts = (float)l->texmins[0] * TEXTURE_STEP; //starts = (float)l->texmins[0] * 16; //--vluzacn + startt = (float)l->texmins[1] * TEXTURE_STEP; //startt = (float)l->texmins[1] * 16; //--vluzacn + + // General case + inside_point_count = 0; + VectorClear(surface_midpoint); + for (t = 0; t < h; t++) + { + for (s = 0; s < w; s++) + { + us = starts + s * TEXTURE_STEP; + ut = startt + t * TEXTURE_STEP; + + SetSurfFromST(l, midpoint, us, ut); + if ((leaf_mid = PointInLeaf(midpoint)) != g_dleafs) + { + if ((leaf_mid->contents != CONTENTS_SKY) && (leaf_mid->contents != CONTENTS_SOLID)) + { + last_valid_leaf = leaf_mid; + inside_point_count++; + VectorAdd(surface_midpoint, midpoint, surface_midpoint); + } + } + } + } + + if (inside_point_count > 1) + { + vec_t tmp = 1.0 / inside_point_count; + + VectorScale(surface_midpoint, tmp, midpoint); + + //Verbose("Trying general at (%4.3f %4.3f %4.3f) %d\n", surface_midpoint[0], surface_midpoint[1], surface_midpoint[2], inside_point_count); + if ( + (leaf_mid = + HuntForWorld(midpoint, face_delta, p, DEFAULT_HUNT_SIZE, DEFAULT_HUNT_SCALE, DEFAULT_HUNT_OFFSET))) + { + //Verbose("general method succeeded at (%4.3f %4.3f %4.3f)\n", midpoint[0], midpoint[1], midpoint[2]); + return leaf_mid; + } + //Verbose("Tried general , failed at (%4.3f %4.3f %4.3f)\n", midpoint[0], midpoint[1], midpoint[2]); + } + else if (inside_point_count == 1) + { + //Verbose("Returning single point from general\n"); + VectorCopy(surface_midpoint, midpoint); + return last_valid_leaf; + } + else + { + //Verbose("general failed (no points)\n"); + } + + // Try harder + inside_point_count = 0; + VectorClear(surface_midpoint); + for (t = 0; t < h; t++) + { + for (s = 0; s < w; s++) + { + us = starts + s * TEXTURE_STEP; + ut = startt + t * TEXTURE_STEP; + + SetSurfFromST(l, midpoint, us, ut); + leaf_mid = + HuntForWorld(midpoint, face_delta, p, DEFAULT_HUNT_SIZE, DEFAULT_HUNT_SCALE, DEFAULT_HUNT_OFFSET); + if (leaf_mid != g_dleafs) + { + last_valid_leaf = leaf_mid; + inside_point_count++; + VectorAdd(surface_midpoint, midpoint, surface_midpoint); + } + } + } + + if (inside_point_count > 1) + { + vec_t tmp = 1.0 / inside_point_count; + + VectorScale(surface_midpoint, tmp, midpoint); + + if ( + (leaf_mid = + HuntForWorld(midpoint, face_delta, p, DEFAULT_HUNT_SIZE, DEFAULT_HUNT_SCALE, DEFAULT_HUNT_OFFSET))) + { + //Verbose("best method succeeded at (%4.3f %4.3f %4.3f)\n", midpoint[0], midpoint[1], midpoint[2]); + return leaf_mid; + } + //Verbose("Tried best, failed at (%4.3f %4.3f %4.3f)\n", midpoint[0], midpoint[1], midpoint[2]); + } + else if (inside_point_count == 1) + { + //Verbose("Returning single point from best\n"); + VectorCopy(surface_midpoint, midpoint); + return last_valid_leaf; + } + else + { + //Verbose("best failed (no points)\n"); + } + + // Original broken code + { + vec_t mids = (l->exactmaxs[0] + l->exactmins[0]) / 2; + vec_t midt = (l->exactmaxs[1] + l->exactmins[1]) / 2; + + SetSurfFromST(l, midpoint, mids, midt); + + if ((leaf_mid = PointInLeaf(midpoint)) != g_dleafs) + { + if ((leaf_mid->contents != CONTENTS_SKY) && (leaf_mid->contents != CONTENTS_SOLID)) + { + return leaf_mid; + } + } + + VectorCopy(midpoint, broken_midpoint); + //Verbose("Tried original method, failed at (%4.3f %4.3f %4.3f)\n", midpoint[0], midpoint[1], midpoint[2]); + } + + VectorCopy(broken_midpoint, midpoint); + return HuntForWorld(midpoint, face_delta, p, DEFAULT_HUNT_SIZE, DEFAULT_HUNT_SCALE, DEFAULT_HUNT_OFFSET); +} + +// ===================================================================================== +// SimpleNudge +// Return vec_t in point only valid when function returns true +// Use negative scales to push away from center instead +// ===================================================================================== +static bool SimpleNudge(vec_t* const point, const lightinfo_t* const l, vec_t* const s, vec_t* const t, const vec_t delta) +{ + const int facenum = l->surfnum; + const dface_t* f = g_dfaces + facenum; + const dplane_t* p = getPlaneFromFace(f); + const vec_t* face_delta = g_face_offset[facenum]; + const int h = l->texsize[1] + 1; + const int w = l->texsize[0] + 1; + const vec_t half_w = (vec_t)(w - 1) / 2.0; + const vec_t half_h = (vec_t)(h - 1) / 2.0; + const vec_t s_vec = *s; + const vec_t t_vec = *t; + vec_t s1; + vec_t t1; + + if (s_vec > half_w) + { + s1 = s_vec - delta; + } + else + { + s1 = s_vec + delta; + } + + SetSurfFromST(l, point, s1, t_vec); + if (HuntForWorld(point, face_delta, p, DEFAULT_HUNT_SIZE, DEFAULT_HUNT_SCALE, DEFAULT_HUNT_OFFSET)) + { + *s = s1; + return true; + } + + if (t_vec > half_h) + { + t1 = t_vec - delta; + } + else + { + t1 = t_vec + delta; + } + + SetSurfFromST(l, point, s_vec, t1); + if (HuntForWorld(point, face_delta, p, DEFAULT_HUNT_SIZE, DEFAULT_HUNT_SCALE, DEFAULT_HUNT_OFFSET)) + { + *t = t1; + return true; + } + + return false; +} +#endif + +typedef enum +{ + LightOutside, // Not lit + LightShifted, // used HuntForWorld on 100% dark face + LightShiftedInside, // moved to neighbhor on 2nd cleanup pass + LightNormal, // Normally lit with no movement + LightPulledInside, // Pulled inside by bleed code adjustments + LightSimpleNudge, // A simple nudge 1/3 or 2/3 towards center along S or T axist +#ifndef HLRAD_NUDGE_VL + LightSimpleNudgeEmbedded // A nudge either 1 full unit in each of S and T axis, or 1/3 or 2/3 AWAY from center +#endif +} +light_flag_t; + +// ===================================================================================== +// CalcPoints +// For each texture aligned grid point, back project onto the plane +// to get the world xyz value of the sample point +// ===================================================================================== +#ifdef HLRAD_CalcPoints_NEW +#ifndef HLRAD_GROWSAMPLE +static int PointInFace(const lightinfo_t *l, const vec_t* point) +{ + int facenum = l->surfnum; + const dface_t* f = &g_dfaces[facenum]; + Winding *w; + dplane_t plane; + VectorCopy (l->texnormal, plane.normal); + const dplane_t *p = &plane; + vec3_t new_point; + VectorSubtract (point, g_face_offset[facenum], new_point); + w = new Winding (*f); + if (point_in_winding (*w, *p, new_point)) + { + delete w; + return facenum; + } + delete w; + + int j; + for (j = 0; j < f->numedges; j++) + { + int e; + edgeshare_t *es; + dface_t* f2; + e = g_dsurfedges[f->firstedge + j]; + es = &g_edgeshare[abs(e)]; + if (!es->smooth) + continue; + f2 = es->faces[!(e<0)]; + const dplane_t *p2 = getPlaneFromFace (f2); + if (DotProduct (p->normal, p2->normal) < NORMAL_EPSILON) + continue; + w = new Winding (*f2); + if (point_in_winding (*w, *p, new_point)) + { + delete w; + return f2 - g_dfaces; + } + delete w; + } +#ifdef HLRAD_SMOOTH_FACELIST + for (j = 0; j < f->numedges; j++) + { + int e; + edgeshare_t *es; + dface_t* f2; + e = g_dsurfedges[f->firstedge + j]; + es = &g_edgeshare[abs(e)]; + if (!es->smooth) + continue; + for (int edgeend = 0; edgeend < 2; edgeend++) + { + for (facelist_t *l = es->vertex_facelist[edgeend]; l; l = l->next) + { + f2 = l->face; + const dplane_t *p2 = getPlaneFromFace (f2); + if (DotProduct (p->normal, p2->normal) < NORMAL_EPSILON) + continue; + w = new Winding (*f2); + if (point_in_winding (*w, *p, new_point)) + { + delete w; + return f2 - g_dfaces; + } + delete w; + } + } + } +#endif + return facenum; +} +#endif +static void SetSTFromSurf(const lightinfo_t* const l, const vec_t* surf, vec_t& s, vec_t& t) +{ + const int facenum = l->surfnum; + int j; + + s = t = 0; + for (j = 0; j < 3; j++) + { + s += (surf[j] - g_face_offset[facenum][j] - l->texorg[j]) * l->worldtotex[0][j]; + t += (surf[j] - g_face_offset[facenum][j] - l->texorg[j]) * l->worldtotex[1][j]; + } +} +#ifdef HLRAD_GROWSAMPLE + +typedef struct +{ + int edgenum; // g_dedges index + int edgeside; + int nextfacenum; // where to grow + bool tried; + + vec3_t point1; // start point + vec3_t point2; // end point + vec3_t direction; // normalized; from point1 to point2 + + bool noseam; + vec_t distance; // distance from origin + vec_t distancereduction; + vec_t flippedangle; + + vec_t ratio; // if ratio != 1, seam is unavoidable + matrix_t prevtonext; + matrix_t nexttoprev; +} +samplefragedge_t; + +typedef struct +{ + dplane_t planes[4]; +} +samplefragrect_t; + +typedef struct samplefrag_s +{ + samplefrag_s *next; // since this is a node in a list + samplefrag_s *parentfrag; // where it grew from + samplefragedge_t *parentedge; + int facenum; // facenum + + vec_t flippedangle; // copied from parent edge + bool noseam; // copied from parent edge + + matrix_t coordtomycoord; // v[2][2] > 0, v[2][0] = v[2][1] = v[0][2] = v[1][2] = 0.0 + matrix_t mycoordtocoord; + + vec3_t origin; // original s,t + vec3_t myorigin; // relative to the texture coordinate on that face + samplefragrect_t rect; // original rectangle that forms the boundary + samplefragrect_t myrect; // relative to the texture coordinate on that face + + Winding *winding; // a fragment of the original rectangle in the texture coordinate plane; windings of different frags should not overlap + dplane_t windingplane; // normal = (0,0,1) or (0,0,-1); if this normal is wrong, point_in_winding() will never return true + Winding *mywinding; // relative to the texture coordinate on that face + dplane_t mywindingplane; + + int numedges; // # of candicates for the next growth + samplefragedge_t *edges; // candicates for the next growth +} +samplefrag_t; + +typedef struct +{ + int maxsize; + int size; + samplefrag_t *head; +} +samplefraginfo_t; + +void ChopFrag (samplefrag_t *frag) + // fill winding, windingplane, mywinding, mywindingplane, numedges, edges +{ + // get the shape of the fragment by clipping the face using the boundaries + dface_t *f; + Winding *facewinding; + matrix_t worldtotex; + const vec3_t v_up = {0, 0, 1}; + + f = &g_dfaces[frag->facenum]; + facewinding = new Winding (*f); + + TranslateWorldToTex (frag->facenum, worldtotex); + frag->mywinding = new Winding (facewinding->m_NumPoints); + for (int x = 0; x < facewinding->m_NumPoints; x++) + { + ApplyMatrix (worldtotex, facewinding->m_Points[x], frag->mywinding->m_Points[x]); + frag->mywinding->m_Points[x][2] = 0.0; + } + frag->mywinding->RemoveColinearPoints (); + VectorCopy (v_up, frag->mywindingplane.normal); // this is the same as applying the worldtotex matrix to the faceplane + if (CalcMatrixSign (worldtotex) < 0.0) + { + frag->mywindingplane.normal[2] *= -1; + } + frag->mywindingplane.dist = 0.0; + + for (int x = 0; x < 4 && frag->mywinding->m_NumPoints > 0; x++) + { + frag->mywinding->Clip (frag->myrect.planes[x], false); + } + + frag->winding = new Winding (frag->mywinding->m_NumPoints); + for (int x = 0; x < frag->mywinding->m_NumPoints; x++) + { + ApplyMatrix (frag->mycoordtocoord, frag->mywinding->m_Points[x], frag->winding->m_Points[x]); + } + frag->winding->RemoveColinearPoints (); + VectorCopy (frag->mywindingplane.normal, frag->windingplane.normal); + if (CalcMatrixSign (frag->mycoordtocoord) < 0.0) + { + frag->windingplane.normal[2] *= -1; + } + frag->windingplane.dist = 0.0; + + delete facewinding; + + // find the edges where the fragment can grow in the future + frag->numedges = 0; + frag->edges = (samplefragedge_t *)malloc (f->numedges * sizeof (samplefragedge_t)); + hlassume (frag->edges != NULL, assume_NoMemory); + for (int i = 0; i < f->numedges; i++) + { + samplefragedge_t *e; + edgeshare_t *es; + dedge_t *de; + dvertex_t *dv1; + dvertex_t *dv2; + vec_t frac1, frac2; + vec_t edgelen; + vec_t dot, dot1, dot2; + vec3_t tmp, v, normal; + const matrix_t *m; + const matrix_t *m_inverse; + + e = &frag->edges[frag->numedges]; + + // some basic info + e->edgenum = abs (g_dsurfedges[f->firstedge + i]); + e->edgeside = (g_dsurfedges[f->firstedge + i] < 0? 1: 0); + es = &g_edgeshare[e->edgenum]; + if (!es->smooth) + { + continue; + } + if (es->faces[e->edgeside] - g_dfaces != frag->facenum) + { + Error ("internal error 1 in GrowSingleSampleFrag"); + } + m = &es->textotex[e->edgeside]; + m_inverse = &es->textotex[1-e->edgeside]; + e->nextfacenum = es->faces[1-e->edgeside] - g_dfaces; + if (e->nextfacenum == frag->facenum) + { + continue; // an invalid edge (usually very short) + } + e->tried = false; // because the frag hasn't been linked into the list yet + + // translate the edge points from world to the texture plane of the original frag + // so the distances are able to be compared among edges from different frags + de = &g_dedges[e->edgenum]; + dv1 = &g_dvertexes[de->v[e->edgeside]]; + dv2 = &g_dvertexes[de->v[1-e->edgeside]]; + ApplyMatrix (worldtotex, dv1->point, tmp); + ApplyMatrix (frag->mycoordtocoord, tmp, e->point1); + e->point1[2] = 0.0; + ApplyMatrix (worldtotex, dv2->point, tmp); + ApplyMatrix (frag->mycoordtocoord, tmp, e->point2); + e->point2[2] = 0.0; + VectorSubtract (e->point2, e->point1, e->direction); + edgelen = VectorNormalize (e->direction); + if (edgelen <= ON_EPSILON) + { + continue; + } + + // clip the edge + frac1 = 0; + frac2 = 1; + for (int x = 0; x < 4; x++) + { + vec_t dot1; + vec_t dot2; + + dot1 = DotProduct (e->point1, frag->rect.planes[x].normal) - frag->rect.planes[x].dist; + dot2 = DotProduct (e->point2, frag->rect.planes[x].normal) - frag->rect.planes[x].dist; + if (dot1 <= ON_EPSILON && dot2 <= ON_EPSILON) + { + frac1 = 1; + frac2 = 0; + } + else if (dot1 < 0) + { + frac1 = qmax (frac1, dot1 / (dot1 - dot2)); + } + else if (dot2 < 0) + { + frac2 = qmin (frac2, dot1 / (dot1 - dot2)); + } + } + if (edgelen * (frac2 - frac1) <= ON_EPSILON) + { + continue; + } + VectorMA (e->point1, edgelen * frac2, e->direction, e->point2); + VectorMA (e->point1, edgelen * frac1, e->direction, e->point1); + + // calculate the distance, etc., which are used to determine its priority + e->noseam = frag->noseam; + dot = DotProduct (frag->origin, e->direction); + dot1 = DotProduct (e->point1, e->direction); + dot2 = DotProduct (e->point2, e->direction); + dot = qmax (dot1, qmin (dot, dot2)); + VectorMA (e->point1, dot - dot1, e->direction, v); + VectorSubtract (v, frag->origin, v); + e->distance = VectorLength (v); + CrossProduct (e->direction, frag->windingplane.normal, normal); + VectorNormalize (normal); // points inward + e->distancereduction = DotProduct (v, normal); + e->flippedangle = frag->flippedangle + acos (qmin (es->cos_normals_angle, 1.0)); + + // calculate the matrix + e->ratio = (*m_inverse).v[2][2]; + if (e->ratio <= NORMAL_EPSILON || (1 / e->ratio) <= NORMAL_EPSILON) + { + Developer (DEVELOPER_LEVEL_SPAM, "TranslateTexToTex failed on face %d and %d @(%f,%f,%f)", frag->facenum, e->nextfacenum, dv1->point[0], dv1->point[1], dv1->point[2]); + continue; + } + + if (fabs (e->ratio - 1) < 0.005) + { + e->prevtonext = *m; + e->nexttoprev = *m_inverse; + } + else + { + e->noseam = false; + e->prevtonext = *m; + e->nexttoprev = *m_inverse; + } + + frag->numedges++; + } +} + +static samplefrag_t *GrowSingleFrag (const samplefraginfo_t *info, samplefrag_t *parent, samplefragedge_t *edge) +{ + samplefrag_t *frag; + bool overlap; + int numclipplanes; + dplane_t *clipplanes; + + frag = (samplefrag_t *)malloc (sizeof (samplefrag_t)); + hlassume (frag != NULL, assume_NoMemory); + + // some basic info + frag->next = NULL; + frag->parentfrag = parent; + frag->parentedge = edge; + frag->facenum = edge->nextfacenum; + + frag->flippedangle = edge->flippedangle; + frag->noseam = edge->noseam; + + // calculate the matrix + MultiplyMatrix (edge->prevtonext, parent->coordtomycoord, frag->coordtomycoord); + MultiplyMatrix (parent->mycoordtocoord, edge->nexttoprev, frag->mycoordtocoord); + + // fill in origin + VectorCopy (parent->origin, frag->origin); + ApplyMatrix (frag->coordtomycoord, frag->origin, frag->myorigin); + + // fill in boundaries + frag->rect = parent->rect; + for (int x = 0; x < 4; x++) + { + // since a plane's parameters are in the dual coordinate space, we translate the original absolute plane into this relative plane by multiplying the inverse matrix + ApplyMatrixOnPlane (frag->mycoordtocoord, frag->rect.planes[x].normal, frag->rect.planes[x].dist, frag->myrect.planes[x].normal, frag->myrect.planes[x].dist); + double len = VectorLength (frag->myrect.planes[x].normal); + if (!len) + { + Developer (DEVELOPER_LEVEL_MEGASPAM, "couldn't translate sample boundaries on face %d", frag->facenum); + free (frag); + return NULL; + } + VectorScale (frag->myrect.planes[x].normal, 1 / len, frag->myrect.planes[x].normal); + frag->myrect.planes[x].dist /= len; + } + + // chop windings and edges + ChopFrag (frag); + + if (frag->winding->m_NumPoints == 0 || frag->mywinding->m_NumPoints == 0) + { + // empty + delete frag->mywinding; + delete frag->winding; + free (frag->edges); + free (frag); + return NULL; + } + + // do overlap test + + overlap = false; + clipplanes = (dplane_t *)malloc (frag->winding->m_NumPoints * sizeof (dplane_t)); + hlassume (clipplanes != NULL, assume_NoMemory); + numclipplanes = 0; + for (int x = 0; x < frag->winding->m_NumPoints; x++) + { + vec3_t v; + VectorSubtract (frag->winding->m_Points[(x + 1) % frag->winding->m_NumPoints], frag->winding->m_Points[x], v); + CrossProduct (v, frag->windingplane.normal, clipplanes[numclipplanes].normal); + if (!VectorNormalize (clipplanes[numclipplanes].normal)) + { + continue; + } + clipplanes[numclipplanes].dist = DotProduct (frag->winding->m_Points[x], clipplanes[numclipplanes].normal); + numclipplanes++; + } + for (samplefrag_t *f2 = info->head; f2 && !overlap; f2 = f2->next) + { + Winding *w = new Winding (*f2->winding); + for (int x = 0; x < numclipplanes && w->m_NumPoints > 0; x++) + { + w->Clip (clipplanes[x], false +#ifdef ZHLT_WINDING_EPSILON + , 4 * ON_EPSILON +#endif + ); + } + if (w->m_NumPoints > 0) + { + overlap = true; + } + delete w; + } + free (clipplanes); + if (overlap) + { + // in the original texture plane, this fragment overlaps with some existing fragments + delete frag->mywinding; + delete frag->winding; + free (frag->edges); + free (frag); + return NULL; + } + + return frag; +} + +static bool FindBestEdge (samplefraginfo_t *info, samplefrag_t *&bestfrag, samplefragedge_t *&bestedge) +{ + samplefrag_t *f; + samplefragedge_t *e; + bool found; + + found = false; + + for (f = info->head; f; f = f->next) + { + for (e = f->edges; e < f->edges + f->numedges; e++) + { + if (e->tried) + { + continue; + } + + bool better; + + if (!found) + { + better = true; + } + else if ((e->flippedangle < Q_PI + NORMAL_EPSILON) != (bestedge->flippedangle < Q_PI + NORMAL_EPSILON)) + { + better = ((e->flippedangle < Q_PI + NORMAL_EPSILON) && !(bestedge->flippedangle < Q_PI + NORMAL_EPSILON)); + } + else if (e->noseam != bestedge->noseam) + { + better = (e->noseam && !bestedge->noseam); + } + else if (fabs (e->distance - bestedge->distance) > ON_EPSILON) + { + better = (e->distance < bestedge->distance); + } + else if (fabs (e->distancereduction - bestedge->distancereduction) > ON_EPSILON) + { + better = (e->distancereduction > bestedge->distancereduction); + } + else + { + better = e->edgenum < bestedge->edgenum; + } + + if (better) + { + found = true; + bestfrag = f; + bestedge = e; + } + } + } + + return found; +} + +static samplefraginfo_t *CreateSampleFrag (int facenum, vec_t s, vec_t t, +#ifdef HLRAD_BLUR_MINIMALSQUARE + const vec_t square[2][2], +#else + vec_t reach, +#endif + int maxsize) +{ + samplefraginfo_t *info; + const vec3_t v_s = {1, 0, 0}; + const vec3_t v_t = {0, 1, 0}; + + info = (samplefraginfo_t *)malloc (sizeof (samplefraginfo_t)); + hlassume (info != NULL, assume_NoMemory); + info->maxsize = maxsize; + info->size = 1; + info->head = (samplefrag_t *)malloc (sizeof (samplefrag_t)); + hlassume (info->head != NULL, assume_NoMemory); + + info->head->next = NULL; + info->head->parentfrag = NULL; + info->head->parentedge = NULL; + info->head->facenum = facenum; + + info->head->flippedangle = 0.0; + info->head->noseam = true; + + MatrixForScale (vec3_origin, 1.0, info->head->coordtomycoord); + MatrixForScale (vec3_origin, 1.0, info->head->mycoordtocoord); + + info->head->origin[0] = s; + info->head->origin[1] = t; + info->head->origin[2] = 0.0; + VectorCopy (info->head->origin, info->head->myorigin); + +#ifdef HLRAD_BLUR_MINIMALSQUARE + VectorScale (v_s, 1, info->head->rect.planes[0].normal); info->head->rect.planes[0].dist = square[0][0]; // smin + VectorScale (v_s, -1, info->head->rect.planes[1].normal); info->head->rect.planes[1].dist = -square[1][0]; // smax + VectorScale (v_t, 1, info->head->rect.planes[2].normal); info->head->rect.planes[2].dist = square[0][1]; // tmin + VectorScale (v_t, -1, info->head->rect.planes[3].normal); info->head->rect.planes[3].dist = -square[1][1]; // tmax +#else + VectorScale (v_s, 1, info->head->rect.planes[0].normal); info->head->rect.planes[0].dist = (s - reach); + VectorScale (v_s, -1, info->head->rect.planes[1].normal); info->head->rect.planes[1].dist = -(s + reach); + VectorScale (v_t, 1, info->head->rect.planes[2].normal); info->head->rect.planes[2].dist = (t - reach); + VectorScale (v_t, -1, info->head->rect.planes[3].normal); info->head->rect.planes[3].dist = -(t + reach); +#endif + info->head->myrect = info->head->rect; + + ChopFrag (info->head); + + if (info->head->winding->m_NumPoints == 0 || info->head->mywinding->m_NumPoints == 0) + { + // empty + delete info->head->mywinding; + delete info->head->winding; + free (info->head->edges); + free (info->head); + info->head = NULL; + info->size = 0; + } + else + { + // prune edges + for (samplefragedge_t *e = info->head->edges; e < info->head->edges + info->head->numedges; e++) + { + if (e->nextfacenum == info->head->facenum) + { + e->tried = true; + } + } + } + + while (info->size < info->maxsize) + { + samplefrag_t *bestfrag; + samplefragedge_t *bestedge; + samplefrag_t *newfrag; + + if (!FindBestEdge (info, bestfrag, bestedge)) + { + break; + } + + newfrag = GrowSingleFrag (info, bestfrag, bestedge); + bestedge->tried = true; + + if (newfrag) + { + newfrag->next = info->head; + info->head = newfrag; + info->size++; + + for (samplefrag_t *f = info->head; f; f = f->next) + { + for (samplefragedge_t *e = newfrag->edges; e < newfrag->edges + newfrag->numedges; e++) + { + if (e->nextfacenum == f->facenum) + { + e->tried = true; + } + } + } + for (samplefrag_t *f = info->head; f; f = f->next) + { + for (samplefragedge_t *e = f->edges; e < f->edges + f->numedges; e++) + { + if (e->nextfacenum == newfrag->facenum) + { + e->tried = true; + } + } + } + } + } + + return info; +} + +static bool IsFragEmpty (samplefraginfo_t *fraginfo) +{ + return (fraginfo->size == 0); +} + +static void DeleteSampleFrag (samplefraginfo_t *fraginfo) +{ + while (fraginfo->head) + { + samplefrag_t *f; + + f = fraginfo->head; + fraginfo->head = f->next; + delete f->mywinding; + delete f->winding; + free (f->edges); + free (f); + } + free (fraginfo); +} + +#endif +static light_flag_t SetSampleFromST(vec_t* const point, +#ifdef HLRAD_GROWSAMPLE + vec_t* const position, // a valid world position for light tracing + int* const surface, // the face used for phong normal and patch interpolation +#endif +#ifdef HLRAD_AVOIDWALLBLEED + bool *nudged, +#endif + const lightinfo_t* const l, const vec_t original_s, const vec_t original_t, +#ifdef HLRAD_GROWSAMPLE +#ifdef HLRAD_BLUR_MINIMALSQUARE + const vec_t square[2][2], // {smin, tmin}, {smax, tmax} +#else + const vec_t reach, // the size of the square that grows +#endif +#endif + eModelLightmodes lightmode) +{ +#ifdef HLRAD_GROWSAMPLE + light_flag_t LuxelFlag; + int facenum; + dface_t *face; + const dplane_t* faceplane; + samplefraginfo_t *fraginfo; + samplefrag_t *f; + + facenum = l->surfnum; + face = l->face; + faceplane = getPlaneFromFace (face); + + fraginfo = CreateSampleFrag (facenum, original_s, original_t, +#ifdef HLRAD_BLUR_MINIMALSQUARE + square, +#else + reach, +#endif + 100); + + bool found; + samplefrag_t *bestfrag; + vec3_t bestpos; + vec_t bests, bestt; + vec_t best_dist; +#ifdef HLRAD_AVOIDWALLBLEED + bool best_nudged; +#endif + + found = false; + for (f = fraginfo->head; f; f = f->next) + { + vec3_t pos; + vec_t s, t; + vec_t dist; + +#ifdef HLRAD_AVOIDWALLBLEED + bool nudged_one; +#endif + if (!FindNearestPosition (f->facenum, f->mywinding, f->mywindingplane, f->myorigin[0], f->myorigin[1], pos, &s, &t, &dist +#ifdef HLRAD_AVOIDWALLBLEED + , &nudged_one +#endif + )) + { + continue; + } + + bool better; + + if (!found) + { + better = true; + } +#ifdef HLRAD_AVOIDWALLBLEED + else if (nudged_one != best_nudged) + { + better = !nudged_one; + } +#endif + else if (fabs (dist - best_dist) > 2 * ON_EPSILON) + { + better = (dist < best_dist); + } + else if (f->noseam != bestfrag->noseam) + { + better = (f->noseam && !bestfrag->noseam); + } + else + { + better = (f->facenum < bestfrag->facenum); + } + + if (better) + { + found = true; + bestfrag = f; + VectorCopy (pos, bestpos); + bests = s; + bestt = t; + best_dist = dist; +#ifdef HLRAD_AVOIDWALLBLEED + best_nudged = nudged_one; +#endif + } + } + + if (found) + { + matrix_t worldtotex, textoworld; + vec3_t tex; + + TranslateWorldToTex (bestfrag->facenum, worldtotex); + if (!InvertMatrix (worldtotex, textoworld)) + { + const unsigned facenum = bestfrag->facenum; + ThreadLock (); + Log ("Malformed face (%d) normal @ \n", facenum); + Winding* w = new Winding (g_dfaces[facenum]); + for (int x = 0; x < w->m_NumPoints; x++) + { + VectorAdd (w->m_Points[x], g_face_offset[facenum], w->m_Points[x]); + } + w->Print (); + delete w; + ThreadUnlock (); + hlassume (false, assume_MalformedTextureFace); + } + + // point + tex[0] = bests; + tex[1] = bestt; + tex[2] = 0.0; + {vec3_t v; ApplyMatrix (textoworld, tex, v); VectorCopy (v, point);} + VectorAdd (point, g_face_offset[bestfrag->facenum], point); + // position + VectorCopy (bestpos, position); + // surface + *surface = bestfrag->facenum; +#ifdef HLRAD_AVOIDWALLBLEED + // whether nudged to fit + *nudged = best_nudged; +#endif + // returned value + LuxelFlag = LightNormal; + } + else + { + SetSurfFromST (l, point, original_s, original_t); + VectorMA (point, DEFAULT_HUNT_OFFSET, faceplane->normal, position); + *surface = facenum; +#ifdef HLRAD_AVOIDWALLBLEED + *nudged = true; +#endif + LuxelFlag = LightOutside; + } + + DeleteSampleFrag (fraginfo); + + return LuxelFlag; + +#else + light_flag_t LuxelFlag = LightOutside; + int huntsize = 3; + vec_t huntscale = 0.2; + vec_t width = DEFAULT_EDGE_WIDTH; + + int facenum = l->surfnum; + const vec_t* face_delta = g_face_offset[facenum]; + + const dface_t* f = &g_dfaces[facenum]; + const dplane_t* p = getPlaneFromFace (f); + Winding *wd = new Winding (*f); + { + int j; + for (j = 0; j < wd->m_NumPoints; j++) + { + VectorAdd (wd->m_Points[j], face_delta, wd->m_Points[j]); + } + } + + const vec_t* face_centroid = g_face_centroids[facenum]; + vec_t mids, midt; + SetSTFromSurf (l, face_centroid, mids, midt); + + vec3_t surf_original; + dleaf_t* leaf_original; + SetSurfFromST (l, surf_original, original_s, original_t); + leaf_original = HuntForWorld (surf_original, face_delta, p, 1, 0.0, DEFAULT_HUNT_OFFSET); + + int facenum_tosnap = PointInFace (l, surf_original); + const dface_t* f_tosnap = &g_dfaces[facenum_tosnap]; + const dplane_t* p_tosnap = getPlaneFromFace (f_tosnap); +#ifdef HLRAD_SMOOTH_TEXNORMAL + vec3_t snapdir; + if (!GetIntertexnormal (facenum, facenum_tosnap, snapdir)) + { + facenum_tosnap = facenum; + f_tosnap = f; + p_tosnap = p; + } +#endif + + vec3_t surf_direct; + dleaf_t* leaf_direct; + VectorCopy (surf_original, surf_direct); + { + vec_t dist; + vec_t scale; +#ifdef HLRAD_SMOOTH_TEXNORMAL + scale = DotProduct (snapdir, p_tosnap->normal); +#else + scale = DotProduct (l->texnormal, p_tosnap->normal); +#endif + dist = DotProduct (surf_direct, p_tosnap->normal) - DotProduct (face_delta, p_tosnap->normal) - p_tosnap->dist - DEFAULT_HUNT_OFFSET; +#ifdef HLRAD_SMOOTH_TEXNORMAL + VectorMA (surf_direct, - dist / scale, snapdir, surf_direct); +#else + VectorMA (surf_direct, - dist / scale, l->texnormal, surf_direct); +#endif + } + leaf_direct = HuntForWorld (surf_direct, face_delta, p_tosnap, huntsize, huntscale, DEFAULT_HUNT_OFFSET); + + if (LuxelFlag == LightOutside) + { + if (leaf_direct && point_in_winding_noedge (*wd, *p, surf_direct, width)) + { + LuxelFlag = LightNormal; + VectorCopy (surf_direct, point); + } + } + + if (LuxelFlag == LightOutside) + { + bool blocked_direct; + bool blocked_inwinding; + bool blocked_inwinding_noedge; + vec3_t surf_inwinding; + vec3_t surf_inwinding_noedge; + dleaf_t*leaf_inwinding; + dleaf_t*leaf_inwinding_noedge; +#ifdef HLRAD_HULLU + vec3_t transparency = { 1.0, 1.0, 1.0 }; +#endif +#ifdef HLRAD_OPAQUE_STYLE + int opaquestyle; +#endif + { + blocked_direct = (leaf_direct == NULL); + if (!point_in_winding (*wd, *p, surf_original)) + { + VectorCopy (surf_original, surf_inwinding); + snap_to_winding (*wd, *p, surf_inwinding); + leaf_inwinding = HuntForWorld (surf_inwinding, face_delta, p, huntsize, huntscale, DEFAULT_HUNT_OFFSET); + if ( blocked_direct + || !leaf_inwinding + || TestLine (surf_direct, surf_inwinding) != CONTENTS_EMPTY + || TestSegmentAgainstOpaqueList (surf_direct, surf_inwinding + #ifdef HLRAD_HULLU + , transparency + #endif + #ifdef HLRAD_OPAQUE_STYLE + , opaquestyle + #endif + ) == true + #ifdef HLRAD_OPAQUE_STYLE + || opaquestyle != -1 + #endif + #ifdef HLRAD_TRANSLUCENT + || l->translucent_b + #endif + ) + { + blocked_direct = true; + } + } + else + { + VectorCopy (surf_original, surf_inwinding); + leaf_inwinding = leaf_original; + } + blocked_inwinding = (leaf_inwinding == NULL); + if (!point_in_winding_noedge (*wd, *p, surf_inwinding, width)) + { + VectorCopy (surf_inwinding, surf_inwinding_noedge); + snap_to_winding_noedge (*wd, *p, surf_inwinding_noedge, width, 4 * width); + leaf_inwinding_noedge = HuntForWorld (surf_inwinding_noedge, face_delta, p, huntsize, huntscale, DEFAULT_HUNT_OFFSET); + if ( blocked_inwinding + || !leaf_inwinding_noedge + || TestLine (surf_inwinding, surf_inwinding_noedge) != CONTENTS_EMPTY + || TestSegmentAgainstOpaqueList (surf_inwinding, surf_inwinding_noedge + #ifdef HLRAD_HULLU + , transparency + #endif + #ifdef HLRAD_OPAQUE_STYLE + , opaquestyle + #endif + ) == true + #ifdef HLRAD_OPAQUE_STYLE + || opaquestyle != -1 + #endif + ) + { + blocked_inwinding = true; + } + } + else + { + VectorCopy (surf_inwinding, surf_inwinding_noedge); + leaf_inwinding_noedge = leaf_inwinding; + } + blocked_inwinding_noedge = (leaf_inwinding_noedge == NULL); + if (blocked_inwinding_noedge == true) + { + blocked_inwinding = true; + } + if (blocked_inwinding == true) + { + blocked_direct = true; + } + } + if (!blocked_direct) + { + LuxelFlag = LightNormal; + VectorCopy (surf_direct, point); + } + else if (!blocked_inwinding) + { + LuxelFlag = LightPulledInside; + VectorCopy (surf_inwinding, point); + } + else if (!blocked_inwinding_noedge) + { + LuxelFlag = LightPulledInside; + VectorCopy (surf_inwinding_noedge, point); + } + } + + if (LuxelFlag == LightOutside) + { + // this part is very slow + const int numnudges = 13; + vec_t nudgelist[numnudges][2] = {{0,0},{0.6,0},{0,0.6},{-0.6,0},{0,-0.6},{1.1,1.1},{1.1,-1.1},{-1.1,1.1},{-1.1,-1.1},{1.6,0},{0,1.6},{-1.6,0},{0,-1.6}}; + vec_t nudgescale_s, nudgescale_t; + nudgescale_s = original_s <= mids? TEXTURE_STEP: -TEXTURE_STEP; + nudgescale_t = original_t <= midt? TEXTURE_STEP: -TEXTURE_STEP; + int i; + for (i = 0; i < numnudges; i++) + { + vec_t s1 = original_s + nudgelist[i][0] * nudgescale_s; + vec_t t1 = original_t + nudgelist[i][1] * nudgescale_t; + vec3_t surf; + SetSurfFromST(l, surf, s1, t1); + if (point_in_winding (*wd, *p, surf) && HuntForWorld (surf, face_delta, p, 2, 0.5, DEFAULT_HUNT_OFFSET) && point_in_winding (*wd, *p, surf)) + { + LuxelFlag = LightSimpleNudge; + VectorCopy (surf, point); + break; + } + } + } + + if (LuxelFlag == LightOutside) + { + VectorCopy (surf_original, point); + } + + delete wd; + return LuxelFlag; +#endif +} +static void CalcPoints(lightinfo_t* l) +{ + const int facenum = l->surfnum; + const dface_t* f = g_dfaces + facenum; + const dplane_t* p = getPlaneFromFace (f); + const vec_t* face_delta = g_face_offset[facenum]; + const eModelLightmodes lightmode = g_face_lightmode[facenum]; + const int h = l->texsize[1] + 1; + const int w = l->texsize[0] + 1; + const vec_t starts = l->texmins[0] * TEXTURE_STEP; + const vec_t startt = l->texmins[1] * TEXTURE_STEP; + light_flag_t LuxelFlags[MAX_SINGLEMAP]; + light_flag_t* pLuxelFlags; + vec_t us, ut; + vec_t* surf; + int s, t; + l->numsurfpt = w * h; + for (t = 0; t < h; t++) + { + for (s = 0; s < w; s++) + { + surf = l->surfpt[s+w*t]; + pLuxelFlags = &LuxelFlags[s+w*t]; + us = starts + s * TEXTURE_STEP; + ut = startt + t * TEXTURE_STEP; +#ifdef HLRAD_BLUR_MINIMALSQUARE + vec_t square[2][2]; + square[0][0] = us - TEXTURE_STEP; + square[0][1] = ut - TEXTURE_STEP; + square[1][0] = us + TEXTURE_STEP; + square[1][1] = ut + TEXTURE_STEP; +#endif +#ifdef HLRAD_AVOIDWALLBLEED + bool nudged; +#endif + *pLuxelFlags = SetSampleFromST (surf, +#ifdef HLRAD_GROWSAMPLE + l->surfpt_position[s+w*t], &l->surfpt_surface[s+w*t], +#endif +#ifdef HLRAD_AVOIDWALLBLEED + &nudged, +#endif + l, us, ut, +#ifdef HLRAD_GROWSAMPLE +#ifdef HLRAD_BLUR_MINIMALSQUARE + square, +#else + TEXTURE_STEP, +#endif +#endif + lightmode); + } + } + { + int i, n; + int s_other, t_other; + light_flag_t* pLuxelFlags_other; + vec_t* surf_other; + bool adjusted; + for (i = 0; i < h + w; i++) + { // propagate valid light samples + adjusted = false; + for (t = 0; t < h; t++) + { + for (s = 0; s < w; s++) + { + surf = l->surfpt[s+w*t]; + pLuxelFlags = &LuxelFlags[s+w*t]; + if (*pLuxelFlags != LightOutside) + continue; + for (n = 0; n < 4; n++) + { + switch (n) + { + case 0: s_other = s + 1; t_other = t; break; + case 1: s_other = s - 1; t_other = t; break; + case 2: s_other = s; t_other = t + 1; break; + case 3: s_other = s; t_other = t - 1; break; + } + if (t_other < 0 || t_other >= h || s_other < 0 || s_other >= w) + continue; + surf_other = l->surfpt[s_other+w*t_other]; + pLuxelFlags_other = &LuxelFlags[s_other+w*t_other]; + if (*pLuxelFlags_other != LightOutside && *pLuxelFlags_other != LightShifted) + { + *pLuxelFlags = LightShifted; + VectorCopy (surf_other, surf); + #ifdef HLRAD_GROWSAMPLE + VectorCopy (l->surfpt_position[s_other+w*t_other], l->surfpt_position[s+w*t]); + l->surfpt_surface[s+w*t] = l->surfpt_surface[s_other+w*t_other]; + #endif + adjusted = true; + break; + } + } + } + } + for (t = 0; t < h; t++) + { + for (s = 0; s < w; s++) + { + pLuxelFlags = &LuxelFlags[s+w*t]; + if (*pLuxelFlags == LightShifted) + { + *pLuxelFlags = LightShiftedInside; + } + } + } + if (!adjusted) + break; + } + } + for (int i = 0; i < MAX_SINGLEMAP; i++) + { + l->surfpt_lightoutside[i] = (LuxelFlags[i] == LightOutside); + } +} +#else /*HLRAD_CalcPoints_NEW*/ +static void CalcPoints(lightinfo_t* l) +{ + const int facenum = l->surfnum; + const dface_t* f = g_dfaces + facenum; + const dplane_t* p = getPlaneFromFace (f); + + const vec_t* face_delta = g_face_offset[facenum]; + const eModelLightmodes lightmode = g_face_lightmode[facenum]; + +#ifdef HLRAD_NUDGE_VL + vec_t mids, midt; + { + // use winding center instead + vec3_t surf; + VectorSubtract (g_face_centroids[facenum], g_face_offset[facenum], surf); + VectorSubtract (surf, l->texorg, surf); + mids = DotProduct (surf, l->worldtotex[0]); + midt = DotProduct (surf, l->worldtotex[1]); + } +#else + const vec_t mids = (l->exactmaxs[0] + l->exactmins[0]) / 2; + const vec_t midt = (l->exactmaxs[1] + l->exactmins[1]) / 2; +#endif + + const int h = l->texsize[1] + 1; + const int w = l->texsize[0] + 1; + + const vec_t starts = (l->texmins[0] * TEXTURE_STEP); //const vec_t starts = (l->texmins[0] * 16); //--vluzacn + const vec_t startt = (l->texmins[1] * TEXTURE_STEP); //const vec_t startt = (l->texmins[1] * 16); //--vluzacn + + light_flag_t LuxelFlags[MAX_SINGLEMAP]; + light_flag_t* pLuxelFlags; + vec_t us, ut; + vec_t* surf; + vec3_t surface_midpoint; + dleaf_t* leaf_mid; + dleaf_t* leaf_surf; + int s, t; + int i; + + l->numsurfpt = w * h; + + memset(LuxelFlags, 0, sizeof(LuxelFlags)); + + leaf_mid = FindSurfaceMidpoint(l, surface_midpoint); +#if 0 + if (!leaf_mid) + { + Developer(DEVELOPER_LEVEL_FLUFF, "CalcPoints [face %d] (%4.3f %4.3f %4.3f) midpoint outside world\n", + facenum, surface_midpoint[0], surface_midpoint[1], surface_midpoint[2]); + } + else + { + Developer(DEVELOPER_LEVEL_FLUFF, "FindSurfaceMidpoint [face %d] @ (%4.3f %4.3f %4.3f)\n", + facenum, surface_midpoint[0], surface_midpoint[1], surface_midpoint[2]); + } +#endif + + // First pass, light normally, and pull any faces toward the center for bleed adjustment + + surf = l->surfpt[0]; + pLuxelFlags = LuxelFlags; + for (t = 0; t < h; t++) + { + for (s = 0; s < w; s++, surf += 3, pLuxelFlags++) + { + vec_t original_s = us = starts + s * TEXTURE_STEP; + vec_t original_t = ut = startt + t * TEXTURE_STEP; + + SetSurfFromST(l, surf, us, ut); + leaf_surf = HuntForWorld(surf, face_delta, p, DEFAULT_HUNT_SIZE, DEFAULT_HUNT_SCALE, DEFAULT_HUNT_OFFSET); + + if (!leaf_surf) + { + // At first try a 1/3 and 2/3 distance to nearest in each S and T axis towards the face midpoint + if (SimpleNudge(surf, l, &us, &ut, TEXTURE_STEP * (1.0 / 3.0))) + { + *pLuxelFlags = LightSimpleNudge; + } + else if (SimpleNudge(surf, l, &us, &ut, -TEXTURE_STEP * (1.0 / 3.0))) + { + *pLuxelFlags = LightSimpleNudge; + } + else if (SimpleNudge(surf, l, &us, &ut, TEXTURE_STEP * (2.0 / 3.0))) + { + *pLuxelFlags = LightSimpleNudge; + } + else if (SimpleNudge(surf, l, &us, &ut, -TEXTURE_STEP * (2.0 / 3.0))) + { + *pLuxelFlags = LightSimpleNudge; + } +#ifdef HLRAD_NUDGE_VL + else if (SimpleNudge(surf, l, &us, &ut, TEXTURE_STEP)) + { + *pLuxelFlags = LightSimpleNudge; + } + else if (SimpleNudge(surf, l, &us, &ut, -TEXTURE_STEP)) + { + *pLuxelFlags = LightSimpleNudge; + } +#else + // Next, if this is a model flagged with the 'Embedded' mode, try away from the facemid too + else if (lightmode & eModelLightmodeEmbedded) + { + SetSurfFromST(l, surf, us, ut); + if (SimpleNudge(surf, l, &us, &ut, TEXTURE_STEP)) + { + *pLuxelFlags = LightSimpleNudgeEmbedded; + continue; + } + if (SimpleNudge(surf, l, &us, &ut, -TEXTURE_STEP)) + { + *pLuxelFlags = LightSimpleNudgeEmbedded; + continue; + } + + SetSurfFromST(l, surf, original_s, original_t); + *pLuxelFlags = LightOutside; + continue; + } +#endif + } + +#ifndef HLRAD_NUDGE_VL + if (!(lightmode & eModelLightmodeEmbedded)) +#endif + { +#ifdef HLRAD_NUDGE_VL + // HLRAD_NUDGE_VL: only pull when light is blocked AND point is outside face. + vec3_t surf_nopull; + vec_t us_nopull = us, ut_nopull = ut; + Winding *wd = new Winding (*f); + int j; + for (j = 0; j < wd->m_NumPoints; j++) + { + VectorAdd (wd->m_Points[j], face_delta, wd->m_Points[j]); + } +#endif +#ifdef HLRAD_SNAPTOWINDING + bool nudge_succeeded = false; + SetSurfFromST(l, surf, us, ut); + leaf_surf = HuntForWorld(surf, face_delta, p, DEFAULT_HUNT_SIZE, DEFAULT_HUNT_SCALE, DEFAULT_HUNT_OFFSET); + if (leaf_surf && point_in_winding_noedge (*wd, *p, surf, 1.0)) + { + *pLuxelFlags = LightNormal; + nudge_succeeded = true; + } + else + { + SetSurfFromST(l, surf, us, ut); + snap_to_winding (*wd, *p, surf); + if (lightmode & eModelLightmodeConcave) + { + VectorScale (surf, 0.99, surf); + VectorMA (surf, 0.01, g_face_centroids[facenum], surf); + } + leaf_surf = HuntForWorld(surf, face_delta, p, DEFAULT_HUNT_SIZE, DEFAULT_HUNT_SCALE, DEFAULT_HUNT_OFFSET); + if (leaf_surf) + { + *pLuxelFlags = LightPulledInside; + nudge_succeeded = true; + } + } + +#else + // Pull the sample points towards the facemid if visibility is blocked + // and the facemid is inside the world + #ifdef HLRAD_NUDGE_SMALLSTEP + int nudge_divisor = 4 * qmax(qmax(w, h), 4); + #else + int nudge_divisor = qmax(qmax(w, h), 4); + #endif + int max_nudge = nudge_divisor + 1; + bool nudge_succeeded = false; + + vec_t nudge_s = (mids - us) / (vec_t)nudge_divisor; + vec_t nudge_t = (midt - ut) / (vec_t)nudge_divisor; + + // if a line can be traced from surf to facemid, the point is good + for (i = 0; i < max_nudge; i++) + { + // Make sure we are "in the world"(Not the zero leaf) + #ifndef HLRAD_NUDGE_VL + if (leaf_mid) + { + #endif + SetSurfFromST(l, surf, us, ut); + leaf_surf = + HuntForWorld(surf, face_delta, p, DEFAULT_HUNT_SIZE, DEFAULT_HUNT_SCALE, + DEFAULT_HUNT_OFFSET); + + if (leaf_surf) + { + #ifdef HLRAD_NUDGE_VL + if (point_in_winding_noedge (*wd, *p, surf, 1.0)) + { + #else + if (TestLine(surface_midpoint, surf) == CONTENTS_EMPTY) + { + if (lightmode & eModelLightmodeConcave) + { + #ifdef HLRAD_HULLU + vec3_t transparency = { 1.0, 1.0, 1.0 }; + #endif + #ifdef HLRAD_OPAQUE_STYLE + int opaquestyle; + #endif + if (TestSegmentAgainstOpaqueList(surface_midpoint, surf + #ifdef HLRAD_HULLU + , transparency + #endif + #ifdef HLRAD_OPAQUE_STYLE + , opaquestyle + #endif + ) + #ifdef HLRAD_OPAQUE_STYLE + || opaquestyle != -1 + #endif + ) + { + Log("SDF::4\n"); + us += nudge_s; + ut += nudge_t; + continue; // Try nudge again, we hit an opaque face + } + } + #endif + if (i) + { + *pLuxelFlags = LightPulledInside; + } + else + { + *pLuxelFlags = LightNormal; + } + nudge_succeeded = true; + break; + } + } + #ifndef HLRAD_NUDGE_VL + } + else + { + leaf_surf = PointInLeaf(surf); + if (leaf_surf != g_dleafs) + { + if ((leaf_surf->contents != CONTENTS_SKY) && (leaf_surf->contents != CONTENTS_SOLID)) + { + *pLuxelFlags = LightNormal; + nudge_succeeded = true; + break; + } + } + } + #endif + + us += nudge_s; + ut += nudge_t; + } +#endif /*HLRAD_SNAPTOWINDING*/ + + if (!nudge_succeeded) + { + SetSurfFromST(l, surf, original_s, original_t); + *pLuxelFlags = LightOutside; + } +#ifdef HLRAD_NUDGE_VL + delete wd; + if (*pLuxelFlags == LightPulledInside) + { + SetSurfFromST(l, surf_nopull, us_nopull, ut_nopull); + leaf_surf = + HuntForWorld(surf_nopull, face_delta, p, DEFAULT_HUNT_SIZE, DEFAULT_HUNT_SCALE, + DEFAULT_HUNT_OFFSET); + if (leaf_surf) + { + if (TestLine(surf, surf_nopull) == CONTENTS_EMPTY) + { +#ifdef HLRAD_HULLU + vec3_t transparency = { 1.0, 1.0, 1.0 }; +#endif +#ifdef HLRAD_OPAQUE_STYLE + int opaquestyle; +#endif + if (!TestSegmentAgainstOpaqueList(surf, surf_nopull +#ifdef HLRAD_HULLU + , transparency +#endif +#ifdef HLRAD_OPAQUE_STYLE + , opaquestyle +#endif + ) +#ifdef HLRAD_OPAQUE_STYLE + && opaquestyle == -1 +#endif + ) + { + *pLuxelFlags = LightNormal; + VectorCopy (surf_nopull, surf); + } + } + } + } +#endif + } + } + } + + // 2nd Pass, find units that are not lit and try to move them one half or unit worth + // in each direction and see if that is lit. + // This handles 1 x N lightmaps which are all dark everywhere and have no frame of refernece + // for a good center or directly lit areas + surf = l->surfpt[0]; + pLuxelFlags = LuxelFlags; +#if 0 + Developer(DEVELOPER_LEVEL_SPAM, + "w (%d) h (%d) dim (%d) leafmid (%4.3f %4.3f %4.3f) plane normal (%4.3f) (%4.3f) (%4.3f) dist (%f)\n", w, + h, w * h, surface_midpoint[0], surface_midpoint[1], surface_midpoint[2], p->normal[0], p->normal[1], + p->normal[2], p->dist); +#endif + { + int total_dark = 0; + int total_adjusted = 0; + + for (t = 0; t < h; t++) + { + for (s = 0; s < w; s++, surf += 3, pLuxelFlags++) + { + if (!*pLuxelFlags) + { +#if 0 + Developer(DEVELOPER_LEVEL_FLUFF, "Dark (%d %d) (%4.3f %4.3f %4.3f)\n", + s, t, surf[0], surf[1], surf[2]); +#endif + total_dark++; + if (HuntForWorld(surf, face_delta, p, DEFAULT_HUNT_SIZE, DEFAULT_HUNT_SCALE, DEFAULT_HUNT_OFFSET)) + { +#if 0 + Developer(DEVELOPER_LEVEL_FLUFF, "Shifted %d %d to (%4.3f %4.3f %4.3f)\n", s, t, surf[0], + surf[1], surf[2]); +#endif + *pLuxelFlags = LightShifted; + total_adjusted++; + } + else if (HuntForWorld(surf, face_delta, p, 101, 0.5, DEFAULT_HUNT_OFFSET)) + { +#if 0 + Developer(DEVELOPER_LEVEL_FLUFF, "Shifted %d %d to (%4.3f %4.3f %4.3f)\n", s, t, surf[0], + surf[1], surf[2]); +#endif + *pLuxelFlags = LightShifted; + total_adjusted++; + } + } + } + } +#if 0 + if (total_dark) + { + Developer(DEVELOPER_LEVEL_FLUFF, "Pass 2 : %d dark, %d corrected\n", total_dark, total_adjusted); + } +#endif + } + + // 3rd Pass, find units that are not lit and move them towards neighbhors who are + // Currently finds the first lit neighbhor and uses its data + surf = l->surfpt[0]; + pLuxelFlags = LuxelFlags; + { + int total_dark = 0; + int total_adjusted = 0; + + for (t = 0; t < h; t++) + { + for (s = 0; s < w; s++, surf += 3, pLuxelFlags++) + { + if (!*pLuxelFlags) + { + int x_min = qmax(0, s - 1); + int x_max = qmin(w, s + 1); + int y_min = qmax(0, t - 1); + int y_max = qmin(t, t + 1); + + int x, y; + +#if 0 + Developer(DEVELOPER_LEVEL_FLUFF, "Point outside (%d %d) (%4.3f %4.3f %4.3f)\n", + s, t, surf[0], surf[1], surf[2]); +#endif + + total_dark++; + + for (x = x_min; x < x_max; x++) + { + for (y = y_min; y < y_max; y++) + { + if (*pLuxelFlags >= LightNormal) + { + dleaf_t* leaf; + vec_t* other_surf = l->surfpt[0]; + + other_surf += ((y * w) + x) * 3; + + leaf = PointInLeaf(other_surf); + if ((leaf->contents != CONTENTS_SKY && leaf->contents != CONTENTS_SOLID)) + { + *pLuxelFlags = LightShiftedInside; +#if 0 + Developer(DEVELOPER_LEVEL_MESSAGE, + "Nudged (%d %d) (%4.3f %4.3f %4.3f) to (%d %d) (%4.3f %4.3f %4.3f) \n", + s, t, surf[0], surf[1], surf[2], x, y, other_surf[0], other_surf[1], + other_surf[2]); +#endif + VectorCopy(other_surf, surf); + total_adjusted++; + goto found_it; + } + } + } + } + } + found_it:; + } + } +#if 0 + if (total_dark) + { + Developer(DEVELOPER_LEVEL_FLUFF, "Pass 2 : %d dark, %d corrected\n", total_dark, total_adjusted); + } +#endif + } +} +#endif /*HLRAD_CalcPoints_NEW*/ + +//============================================================== + +typedef struct +{ + vec3_t pos; + vec3_t light; +#ifdef HLRAD_GROWSAMPLE + int surface; // this sample can grow into another face +#endif +#ifdef ZHLT_XASH + // this increases the maximum (at 100% AllocBlock, 4 light styles) possible usage of memory of all light samples from 100MB to 200MB + vec3_t light_direction; // sum of light direction * light contribution (rgb averaged) + vec3_t normal; // phong normal +#endif +} +sample_t; + +typedef struct +{ + int numsamples; + sample_t* samples[MAXLIGHTMAPS]; +} +facelight_t; + +static directlight_t* directlights[MAX_MAP_LEAFS]; +static facelight_t facelight[MAX_MAP_FACES]; +static int numdlights; + +#ifndef HLRAD_REFLECTIVITY +#define DIRECT_SCALE 0.1f +#endif + +// ===================================================================================== +// CreateDirectLights +// ===================================================================================== +void CreateDirectLights() +{ + unsigned i; + patch_t* p; + directlight_t* dl; + dleaf_t* leaf; + int leafnum; + entity_t* e; + entity_t* e2; + const char* name; + const char* target; + float angle; + vec3_t dest; + +#ifndef HLRAD_CUSTOMTEXLIGHT + // AJM: coplaner lighting + vec3_t temp_normal; +#endif + + numdlights = 0; +#ifdef HLRAD_STYLEREPORT + int styleused[ALLSTYLES]; + memset (styleused, 0, ALLSTYLES * sizeof(styleused[0])); + styleused[0] = true; + int numstyles = 1; +#endif + + // + // surfaces + // + for (i = 0, p = g_patches; i < g_num_patches; i++, p++) + { +#ifdef ZHLT_TEXLIGHT +#ifdef HLRAD_STYLEREPORT + if (p->emitstyle >= 0 && p->emitstyle < ALLSTYLES) + { + if (styleused[p->emitstyle] == false) + { + styleused[p->emitstyle] = true; + numstyles++; + } + } +#endif + if ( + #ifdef HLRAD_REFLECTIVITY + DotProduct (p->baselight, p->texturereflectivity) / 3 + #else + VectorAvg(p->baselight) + #endif + #ifdef HLRAD_TEXLIGHTTHRESHOLD_FIX + > 0.0 + #else + >= g_dlight_threshold + #endif + #ifdef HLRAD_CUSTOMTEXLIGHT + && !(g_face_texlights[p->faceNumber] + && *ValueForKey (g_face_texlights[p->faceNumber], "_scale") + && FloatForKey (g_face_texlights[p->faceNumber], "_scale") <= 0) + #endif + ) //LRC +#else + if ( + #ifdef HLRAD_REFLECTIVITY + DotProduct (p->totallight, p->texturereflectivity) / 3 + #else + VectorAvg(p->totallight) + #endif + >= g_dlight_threshold + ) +#endif + { + numdlights++; + dl = (directlight_t*)calloc(1, sizeof(directlight_t)); +#ifdef HLRAD_HLASSUMENOMEMORY + hlassume (dl != NULL, assume_NoMemory); +#endif + + VectorCopy(p->origin, dl->origin); + + leaf = PointInLeaf(dl->origin); + leafnum = leaf - g_dleafs; + + dl->next = directlights[leafnum]; + directlights[leafnum] = dl; +#ifdef ZHLT_TEXLIGHT + dl->style = p->emitstyle; //LRC +#endif +#ifdef HLRAD_GatherPatchLight + dl->topatch = false; + #ifdef HLRAD_TEXLIGHTTHRESHOLD_FIX + if (!p->emitmode) + { + dl->topatch = true; + } + #endif +#ifdef HLRAD_FASTMODE + if (g_fastmode) + { + dl->topatch = true; + } +#endif +#endif +#ifdef HLRAD_TEXLIGHT_SPOTS_FIX + dl->patch_area = p->area; + #ifdef HLRAD_ACCURATEBOUNCE_TEXLIGHT + dl->patch_emitter_range = p->emitter_range; + dl->patch = p; + #endif +#endif +#ifdef HLRAD_TEXLIGHTGAP + dl->texlightgap = g_texlightgap; +#ifdef HLRAD_CUSTOMTEXLIGHT + if (g_face_texlights[p->faceNumber] && *ValueForKey (g_face_texlights[p->faceNumber], "_texlightgap")) + { + dl->texlightgap = FloatForKey (g_face_texlights[p->faceNumber], "_texlightgap"); + } +#endif +#endif +#ifdef HLRAD_CUSTOMTEXLIGHT + dl->stopdot = 0.0; + dl->stopdot2 = 0.0; + if (g_face_texlights[p->faceNumber]) + { + if (*ValueForKey (g_face_texlights[p->faceNumber], "_cone")) + { + dl->stopdot = FloatForKey (g_face_texlights[p->faceNumber], "_cone"); + dl->stopdot = dl->stopdot >= 90? 0: (float)cos (dl->stopdot / 180 * Q_PI); + } + if (*ValueForKey (g_face_texlights[p->faceNumber], "_cone2")) + { + dl->stopdot2 = FloatForKey (g_face_texlights[p->faceNumber], "_cone2"); + dl->stopdot2 = dl->stopdot2 >= 90? 0: (float)cos (dl->stopdot2 / 180 * Q_PI); + } + if (dl->stopdot2 > dl->stopdot) + dl->stopdot2 = dl->stopdot; + } +#endif + + dl->type = emit_surface; + VectorCopy(getPlaneFromFaceNumber(p->faceNumber)->normal, dl->normal); +#ifdef ZHLT_TEXLIGHT + VectorCopy(p->baselight, dl->intensity); //LRC +#else + VectorCopy(p->totallight, dl->intensity); +#endif +#ifdef HLRAD_CUSTOMTEXLIGHT + if (g_face_texlights[p->faceNumber]) + { + if (*ValueForKey (g_face_texlights[p->faceNumber], "_scale")) + { + vec_t scale = FloatForKey (g_face_texlights[p->faceNumber], "_scale"); + VectorScale (dl->intensity, scale, dl->intensity); + } + } +#endif + VectorScale(dl->intensity, p->area, dl->intensity); +#ifdef HLRAD_ACCURATEBOUNCE_REDUCEAREA + VectorScale (dl->intensity, p->exposure, dl->intensity); +#endif +#ifdef HLRAD_REFLECTIVITY + VectorScale (dl->intensity, 1.0 / Q_PI, dl->intensity); + VectorMultiply (dl->intensity, p->texturereflectivity, dl->intensity); +#else + VectorScale(dl->intensity, DIRECT_SCALE, dl->intensity); +#endif + +#ifdef HLRAD_WATERBACKFACE_FIX + dface_t *f = &g_dfaces[p->faceNumber]; + if (g_face_entity[p->faceNumber] - g_entities != 0 && !strncasecmp (GetTextureByNumber (f->texinfo), "!", 1)) + { + directlight_t *dl2; + numdlights++; + dl2 = (directlight_t *)calloc (1, sizeof (directlight_t)); + hlassume (dl2 != NULL, assume_NoMemory); + *dl2 = *dl; + VectorMA (dl->origin, -2, dl->normal, dl2->origin); + VectorSubtract (vec3_origin, dl->normal, dl2->normal); + leaf = PointInLeaf (dl2->origin); + leafnum = leaf - g_dleafs; + dl2->next = directlights[leafnum]; + directlights[leafnum] = dl2; + } +#endif +#ifndef HLRAD_CUSTOMTEXLIGHT // no softlight hack + // -------------------------------------------------------------- + // Changes by Adam Foster - afoster@compsoc.man.ac.uk + // mazemaster's l33t backwards lighting (I still haven't a clue + // what it's supposed to be for) :-) +#ifdef HLRAD_WHOME + + if (g_softlight_hack[0] || g_softlight_hack[1] || g_softlight_hack[2]) + { + numdlights++; + dl = (directlight_t *) calloc(1, sizeof(directlight_t)); +#ifdef HLRAD_HLASSUMENOMEMORY + hlassume (dl != NULL, assume_NoMemory); +#endif + + VectorCopy(p->origin, dl->origin); + + leaf = PointInLeaf(dl->origin); + leafnum = leaf - g_dleafs; + + dl->next = directlights[leafnum]; + directlights[leafnum] = dl; + +#ifdef HLRAD_GatherPatchLight + dl->topatch = false; + #ifdef HLRAD_TEXLIGHTTHRESHOLD_FIX + if (!p->emitmode) + { + dl->topatch = true; + } + #endif +#ifdef HLRAD_FASTMODE + if (g_fastmode) + { + dl->topatch = true; + } +#endif +#endif +#ifdef HLRAD_TEXLIGHT_SPOTS_FIX + dl->patch_area = p->area; + #ifdef HLRAD_ACCURATEBOUNCE_TEXLIGHT + dl->patch_emitter_range = p->emitter_range; + dl->patch = p; + #endif +#endif +#ifdef HLRAD_TEXLIGHTGAP + dl->texlightgap = 0; +#endif + dl->type = emit_surface; + VectorCopy(getPlaneFromFaceNumber(p->faceNumber)->normal, dl->normal); + VectorScale(dl->normal, g_softlight_hack_distance, temp_normal); + VectorAdd(dl->origin, temp_normal, dl->origin); + VectorScale(dl->normal, -1, dl->normal); + +#ifdef ZHLT_TEXLIGHT + VectorCopy(p->baselight, dl->intensity); //LRC +#else + VectorCopy(p->totallight, dl->intensity); +#endif + VectorScale(dl->intensity, p->area, dl->intensity); +#ifdef HLRAD_ACCURATEBOUNCE_REDUCEAREA + VectorScale (dl->intensity, p->exposure, dl->intensity); +#endif +#ifdef HLRAD_REFLECTIVITY + VectorScale (dl->intensity, 1.0 / Q_PI, dl->intensity); + VectorMultiply (dl->intensity, p->texturereflectivity, dl->intensity); +#else + VectorScale(dl->intensity, DIRECT_SCALE, dl->intensity); +#endif + + dl->intensity[0] *= g_softlight_hack[0]; + dl->intensity[1] *= g_softlight_hack[1]; + dl->intensity[2] *= g_softlight_hack[2]; + } + +#endif + // -------------------------------------------------------------- +#endif + } + +#ifdef ZHLT_TEXLIGHT + //LRC VectorClear(p->totallight[0]); +#else + VectorClear(p->totallight); +#endif + } + + // + // entities + // + for (i = 0; i < (unsigned)g_numentities; i++) + { + const char* pLight; + double r, g, b, scaler; + float l1; + int argCnt; + + e = &g_entities[i]; + name = ValueForKey(e, "classname"); + if (strncmp(name, "light", 5)) + continue; +#ifdef HLRAD_STYLE_CORING + { + int style = IntForKey (e, "style"); + #ifdef ZHLT_TEXLIGHT + if (style < 0) + { + style = -style; + } + #endif + style = (unsigned char)style; + if (style > 0 && style < ALLSTYLES && *ValueForKey (e, "zhlt_stylecoring")) + { + g_corings[style] = FloatForKey (e, "zhlt_stylecoring"); + } + } +#endif +#ifdef HLRAD_OPAQUE_STYLE + if (!strcmp (name, "light_shadow") + #ifdef HLRAD_BOUNCE_STYLE + || !strcmp (name, "light_bounce") + #endif + ) + { +#ifdef HLRAD_STYLEREPORT + int style = IntForKey (e, "style"); + #ifdef ZHLT_TEXLIGHT + if (style < 0) + { + style = -style; + } + #endif + style = (unsigned char)style; + if (style >= 0 && style < ALLSTYLES) + { + if (styleused[style] == false) + { + styleused[style] = true; + numstyles++; + } + } +#endif + continue; + } +#endif +#ifdef HLRAD_CUSTOMTEXLIGHT + if (!strcmp (name, "light_surface")) + { + continue; + } +#endif + + numdlights++; + dl = (directlight_t*)calloc(1, sizeof(directlight_t)); +#ifdef HLRAD_HLASSUMENOMEMORY + hlassume (dl != NULL, assume_NoMemory); +#endif + + GetVectorForKey(e, "origin", dl->origin); + + leaf = PointInLeaf(dl->origin); + leafnum = leaf - g_dleafs; + + dl->next = directlights[leafnum]; + directlights[leafnum] = dl; + + dl->style = IntForKey(e, "style"); +#ifdef ZHLT_TEXLIGHT + if (dl->style < 0) + dl->style = -dl->style; //LRC +#endif +#ifdef HLRAD_STYLE_CORING + dl->style = (unsigned char)dl->style; + if (dl->style >= ALLSTYLES) + { + Error ("invalid light style: style (%d) >= ALLSTYLES (%d)", dl->style, ALLSTYLES); + } +#endif +#ifdef HLRAD_STYLEREPORT + if (dl->style >= 0 && dl->style < ALLSTYLES) + { + if (styleused[dl->style] == false) + { + styleused[dl->style] = true; + numstyles++; + } + } +#endif +#ifdef HLRAD_GatherPatchLight + dl->topatch = false; + if (IntForKey (e, "_fast") == 1) + { + dl->topatch = true; + } +#ifdef HLRAD_FASTMODE + if (g_fastmode) + { + dl->topatch = true; + } +#endif +#endif + pLight = ValueForKey(e, "_light"); + // scanf into doubles, then assign, so it is vec_t size independent + r = g = b = scaler = 0; + argCnt = sscanf(pLight, "%lf %lf %lf %lf", &r, &g, &b, &scaler); + dl->intensity[0] = (float)r; + if (argCnt == 1) + { + // The R,G,B values are all equal. + dl->intensity[1] = dl->intensity[2] = (float)r; + } + else if (argCnt == 3 || argCnt == 4) + { + // Save the other two G,B values. + dl->intensity[1] = (float)g; + dl->intensity[2] = (float)b; + + // 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 + dl->intensity[0] = dl->intensity[0] / 255 * (float)scaler; + dl->intensity[1] = dl->intensity[1] / 255 * (float)scaler; + dl->intensity[2] = dl->intensity[2] / 255 * (float)scaler; + } + } + else + { + Log("light at (%f,%f,%f) has bad or missing '_light' value : '%s'\n", + dl->origin[0], dl->origin[1], dl->origin[2], pLight); + continue; + } + + dl->fade = FloatForKey(e, "_fade"); + if (dl->fade == 0.0) + { + dl->fade = g_fade; + } + +#ifndef HLRAD_ARG_MISC + dl->falloff = IntForKey(e, "_falloff"); + if (dl->falloff == 0) + { + dl->falloff = g_falloff; + } +#endif + + target = ValueForKey(e, "target"); + + if (!strcmp(name, "light_spot") || !strcmp(name, "light_environment") || target[0]) + { + if (!VectorAvg(dl->intensity)) + { +#ifndef HLRAD_ALLOWZEROBRIGHTNESS + VectorFill(dl->intensity, 500); +#endif + } + dl->type = emit_spotlight; + dl->stopdot = FloatForKey(e, "_cone"); + if (!dl->stopdot) + { + dl->stopdot = 10; + } + dl->stopdot2 = FloatForKey(e, "_cone2"); + if (!dl->stopdot2) + { + dl->stopdot2 = dl->stopdot; + } + if (dl->stopdot2 < dl->stopdot) + { + dl->stopdot2 = dl->stopdot; + } + dl->stopdot2 = (float)cos(dl->stopdot2 / 180 * Q_PI); + dl->stopdot = (float)cos(dl->stopdot / 180 * Q_PI); + + if (!FindTargetEntity(target)) //--vluzacn + { + Warning("light at (%i %i %i) has missing target", + (int)dl->origin[0], (int)dl->origin[1], (int)dl->origin[2]); + target = ""; + } + if (target[0]) + { // point towards target + e2 = FindTargetEntity(target); + if (!e2) + { + Warning("light at (%i %i %i) has missing target", + (int)dl->origin[0], (int)dl->origin[1], (int)dl->origin[2]); + } + else + { + GetVectorForKey(e2, "origin", dest); + VectorSubtract(dest, dl->origin, dl->normal); + VectorNormalize(dl->normal); + } + } + else + { // point down angle + vec3_t vAngles; + + GetVectorForKey(e, "angles", vAngles); + + angle = (float)FloatForKey(e, "angle"); + if (angle == ANGLE_UP) + { + dl->normal[0] = dl->normal[1] = 0; + dl->normal[2] = 1; + } + else if (angle == ANGLE_DOWN) + { + dl->normal[0] = dl->normal[1] = 0; + dl->normal[2] = -1; + } + else + { + // if we don't have a specific "angle" use the "angles" YAW + if (!angle) + { + angle = vAngles[1]; + } + + dl->normal[2] = 0; + dl->normal[0] = (float)cos(angle / 180 * Q_PI); + dl->normal[1] = (float)sin(angle / 180 * Q_PI); + } + + angle = FloatForKey(e, "pitch"); + if (!angle) + { + // if we don't have a specific "pitch" use the "angles" PITCH + angle = vAngles[0]; + } + + dl->normal[2] = (float)sin(angle / 180 * Q_PI); + dl->normal[0] *= (float)cos(angle / 180 * Q_PI); + dl->normal[1] *= (float)cos(angle / 180 * Q_PI); + } + + if (FloatForKey(e, "_sky") || !strcmp(name, "light_environment")) + { + // ----------------------------------------------------------------------------------- + // Changes by Adam Foster - afoster@compsoc.man.ac.uk + // diffuse lighting hack - most of the following code nicked from earlier + // need to get diffuse intensity from new _diffuse_light key + // + // What does _sky do for spotlights, anyway? + // ----------------------------------------------------------------------------------- +#ifdef HLRAD_WHOME + pLight = ValueForKey(e, "_diffuse_light"); + r = g = b = scaler = 0; + argCnt = sscanf(pLight, "%lf %lf %lf %lf", &r, &g, &b, &scaler); + dl->diffuse_intensity[0] = (float)r; + if (argCnt == 1) + { + // The R,G,B values are all equal. + dl->diffuse_intensity[1] = dl->diffuse_intensity[2] = (float)r; + } + else if (argCnt == 3 || argCnt == 4) + { + // Save the other two G,B values. + dl->diffuse_intensity[1] = (float)g; + dl->diffuse_intensity[2] = (float)b; + + // 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 + dl->diffuse_intensity[0] = dl->diffuse_intensity[0] / 255 * (float)scaler; + dl->diffuse_intensity[1] = dl->diffuse_intensity[1] / 255 * (float)scaler; + dl->diffuse_intensity[2] = dl->diffuse_intensity[2] / 255 * (float)scaler; + } + } + else + { + // backwards compatibility with maps without _diffuse_light + + dl->diffuse_intensity[0] = dl->intensity[0]; + dl->diffuse_intensity[1] = dl->intensity[1]; + dl->diffuse_intensity[2] = dl->intensity[2]; + } +#endif + // ----------------------------------------------------------------------------------- +#ifdef HLRAD_SUNDIFFUSE + pLight = ValueForKey(e, "_diffuse_light2"); + r = g = b = scaler = 0; + argCnt = sscanf(pLight, "%lf %lf %lf %lf", &r, &g, &b, &scaler); + dl->diffuse_intensity2[0] = (float)r; + if (argCnt == 1) + { + // The R,G,B values are all equal. + dl->diffuse_intensity2[1] = dl->diffuse_intensity2[2] = (float)r; + } + else if (argCnt == 3 || argCnt == 4) + { + // Save the other two G,B values. + dl->diffuse_intensity2[1] = (float)g; + dl->diffuse_intensity2[2] = (float)b; + + // 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 + dl->diffuse_intensity2[0] = dl->diffuse_intensity2[0] / 255 * (float)scaler; + dl->diffuse_intensity2[1] = dl->diffuse_intensity2[1] / 255 * (float)scaler; + dl->diffuse_intensity2[2] = dl->diffuse_intensity2[2] / 255 * (float)scaler; + } + } + else + { + dl->diffuse_intensity2[0] = dl->diffuse_intensity[0]; + dl->diffuse_intensity2[1] = dl->diffuse_intensity[1]; + dl->diffuse_intensity2[2] = dl->diffuse_intensity[2]; + } +#endif + + dl->type = emit_skylight; + dl->stopdot2 = FloatForKey(e, "_sky"); // hack stopdot2 to a sky key number +#ifdef HLRAD_SUNSPREAD + dl->sunspreadangle = FloatForKey (e, "_spread"); + if (!g_allow_spread) + { + dl->sunspreadangle = 0; + } + if (dl->sunspreadangle < 0.0 || dl->sunspreadangle > 180) + { + Error ("Invalid spread angle '%s'. Please use a number between 0 and 180.\n", ValueForKey (e, "_spread")); + } + if (dl->sunspreadangle > 0.0) + { + int i; + vec_t testangle = dl->sunspreadangle; + if (dl->sunspreadangle < SUNSPREAD_THRESHOLD) + { + testangle = SUNSPREAD_THRESHOLD; // We will later centralize all the normals we have collected. + } + { + vec_t totalweight = 0; + int count; + vec_t testdot = cos (testangle * (Q_PI / 180.0)); + for (count = 0, i = 0; i < g_numskynormals[SUNSPREAD_SKYLEVEL]; i++) + { + vec3_t &testnormal = g_skynormals[SUNSPREAD_SKYLEVEL][i]; + vec_t dot = DotProduct (dl->normal, testnormal); + if (dot >= testdot - NORMAL_EPSILON) + { + totalweight += qmax (0, dot - testdot) * g_skynormalsizes[SUNSPREAD_SKYLEVEL][i]; // This is not the right formula when dl->sunspreadangle < SUNSPREAD_THRESHOLD, but it gives almost the same result as the right one. + count++; + } + } + if (count <= 10 || totalweight <= NORMAL_EPSILON) + { + Error ("collect spread normals: internal error: can not collect enough normals."); + } + dl->numsunnormals = count; + dl->sunnormals = (vec3_t *)malloc (count * sizeof (vec3_t)); + dl->sunnormalweights = (vec_t *)malloc (count * sizeof (vec_t)); + hlassume (dl->sunnormals != NULL, assume_NoMemory); + hlassume (dl->sunnormalweights != NULL, assume_NoMemory); + for (count = 0, i = 0; i < g_numskynormals[SUNSPREAD_SKYLEVEL]; i++) + { + vec3_t &testnormal = g_skynormals[SUNSPREAD_SKYLEVEL][i]; + vec_t dot = DotProduct (dl->normal, testnormal); + if (dot >= testdot - NORMAL_EPSILON) + { + if (count >= dl->numsunnormals) + { + Error ("collect spread normals: internal error."); + } + VectorCopy (testnormal, dl->sunnormals[count]); + dl->sunnormalweights[count] = qmax (0, dot - testdot) * g_skynormalsizes[SUNSPREAD_SKYLEVEL][i] / totalweight; + count++; + } + } + if (count != dl->numsunnormals) + { + Error ("collect spread normals: internal error."); + } + } + if (dl->sunspreadangle < SUNSPREAD_THRESHOLD) + { + for (i = 0; i < dl->numsunnormals; i++) + { + vec3_t tmp; + VectorScale (dl->sunnormals[i], 1 / DotProduct (dl->sunnormals[i], dl->normal), tmp); + VectorSubtract (tmp, dl->normal, tmp); + VectorMA (dl->normal, dl->sunspreadangle / SUNSPREAD_THRESHOLD, tmp, dl->sunnormals[i]); + VectorNormalize (dl->sunnormals[i]); + } + } + } + else + { + dl->numsunnormals = 1; + dl->sunnormals = (vec3_t *)malloc (sizeof (vec3_t)); + dl->sunnormalweights = (vec_t *)malloc (sizeof (vec_t)); + hlassume (dl->sunnormals != NULL, assume_NoMemory); + hlassume (dl->sunnormalweights != NULL, assume_NoMemory); + VectorCopy (dl->normal, dl->sunnormals[0]); + dl->sunnormalweights[0] = 1.0; + } +#endif + } + } + else + { + if (!VectorAvg(dl->intensity)) + { +#ifndef HLRAD_ALLOWZEROBRIGHTNESS + VectorFill(dl->intensity, 300); +#endif + } + dl->type = emit_point; + } + + if (dl->type != emit_skylight) + { + //why? --vluzacn + l1 = qmax(dl->intensity[0], qmax(dl->intensity[1], dl->intensity[2])); + l1 = l1 * l1 / 10; + + dl->intensity[0] *= l1; + dl->intensity[1] *= l1; + dl->intensity[2] *= l1; + } + } + +#ifndef HLRAD_ALLOWZEROBRIGHTNESS + hlassume(numdlights, assume_NoLights); +#endif +#ifdef HLRAD_GatherPatchLight + int countnormallights = 0, countfastlights = 0; + { + int l; + #ifdef HLRAD_VIS_FIX + for (l = 0; l < 1 + g_dmodels[0].visleafs; l++) + #else + for (l = 0; l < g_numleafs; l++) + #endif + { + for (dl = directlights[l]; dl; dl = dl->next) + { + switch (dl->type) + { + case emit_surface: + case emit_point: + case emit_spotlight: + if (!VectorCompare (dl->intensity, vec3_origin)) + { + if (dl->topatch) + { + countfastlights++; + } + else + { + countnormallights++; + } + } + break; + case emit_skylight: + if (!VectorCompare (dl->intensity, vec3_origin)) + { + if (dl->topatch) + { + countfastlights++; +#ifdef HLRAD_SUNSPREAD + if (dl->sunspreadangle > 0.0) + { + countfastlights--; + countfastlights += dl->numsunnormals; + } +#endif + } + else + { + countnormallights++; +#ifdef HLRAD_SUNSPREAD + if (dl->sunspreadangle > 0.0) + { + countnormallights--; + countnormallights += dl->numsunnormals; + } +#endif + } + } + #ifdef HLRAD_WHOME + if (g_indirect_sun > 0 && !VectorCompare (dl->diffuse_intensity, vec3_origin)) + #else + if (g_indirect_sun > 0 && !VectorCompare (dl->intensity, vec3_origin)) + #endif + { + #ifdef HLRAD_SOFTSKY + if (g_softsky) + { + countfastlights += g_numskynormals[SKYLEVEL_SOFTSKYON]; + } + else + { + #ifdef HLRAD_FASTMODE + countfastlights += g_numskynormals[SKYLEVEL_SOFTSKYOFF]; + #else + countnormallights += g_numskynormals[SKYLEVEL_SOFTSKYOFF]; + #endif + } + #else + countnormallights += 162; //NUMVERTEXNORMALS + #endif + } + break; + default: + hlassume(false, assume_BadLightType); + break; + } + } + } + } + Log("%i direct lights and %i fast direct lights\n", countnormallights, countfastlights); +#else + Log("%i direct lights\n", numdlights); +#endif +#ifdef HLRAD_STYLEREPORT + Log("%i light styles\n", numstyles); +#endif +#ifdef HLRAD_SKYFIX_FIX + // move all emit_skylight to leaf 0 (the solid leaf) + if (g_sky_lighting_fix) + { + directlight_t *skylights = NULL; + int l; + #ifdef HLRAD_VIS_FIX + for (l = 0; l < 1 + g_dmodels[0].visleafs; l++) + #else + for (l = 0; l < g_numleafs; l++) + #endif + { + directlight_t **pdl; + for (dl = directlights[l], pdl = &directlights[l]; dl; dl = *pdl) + { + if (dl->type == emit_skylight) + { + *pdl = dl->next; + dl->next = skylights; + skylights = dl; + } + else + { + pdl = &dl->next; + } + } + } + while ((dl = directlights[0]) != NULL) + { + // since they are in leaf 0, they won't emit a light anyway + directlights[0] = dl->next; + free(dl); + } + directlights[0] = skylights; + } +#endif +#ifdef ZHLT_ENTITY_INFOSUNLIGHT +#ifdef HLRAD_MULTISKYLIGHT + if (g_sky_lighting_fix) + { + int countlightenvironment = 0; + int countinfosunlight = 0; + for (int i = 0; i < g_numentities; i++) + { + entity_t *e = &g_entities[i]; + const char *classname = ValueForKey (e, "classname"); + if (!strcmp (classname, "light_environment")) + { + countlightenvironment++; + } + if (!strcmp (classname, "info_sunlight")) + { + countinfosunlight++; + } + } + if (countlightenvironment > 1 && countinfosunlight == 0) + { + // because the map is lit by more than one light_environments, but the game can only recognize one of them when setting sv_skycolor and sv_skyvec. + Warning ("More than one light_environments are in use. Add entity info_sunlight to clarify the sunlight's brightness for in-game model(.mdl) rendering."); + } + } +#endif +#endif +} + +// ===================================================================================== +// DeleteDirectLights +// ===================================================================================== +void DeleteDirectLights() +{ + int l; + directlight_t* dl; + +#ifdef HLRAD_VIS_FIX + for (l = 0; l < 1 + g_dmodels[0].visleafs; l++) +#else + for (l = 0; l < g_numleafs; l++) +#endif + { + dl = directlights[l]; + while (dl) + { + directlights[l] = dl->next; + free(dl); + dl = directlights[l]; + } + } + + // AJM: todo: strip light entities out at this point + // vluzacn: hlvis and hlrad must not modify entity data, because the following procedures are supposed to produce the same bsp file: + // 1> hlcsg -> hlbsp -> hlvis -> hlrad (a normal compile) + // 2) hlcsg -> hlbsp -> hlvis -> hlrad -> hlcsg -onlyents + // 3) hlcsg -> hlbsp -> hlvis -> hlrad -> hlcsg -onlyents -> hlrad +} + +// ===================================================================================== +// GatherSampleLight +// ===================================================================================== +#ifndef HLRAD_SOFTSKY +#define NUMVERTEXNORMALS 162 +double r_avertexnormals[NUMVERTEXNORMALS][3] = { +//#include "../common/anorms.h" + #include "anorms.h" //--vluzacn +}; +#endif +#ifdef HLRAD_SOFTSKY +int g_numskynormals[SKYLEVELMAX+1]; +vec3_t *g_skynormals[SKYLEVELMAX+1]; +vec_t *g_skynormalsizes[SKYLEVELMAX+1]; +typedef double point_t[3]; +typedef struct {int point[2]; bool divided; int child[2];} edge_t; +typedef struct {int edge[3]; int dir[3];} triangle_t; +void CopyToSkynormals (int skylevel, int numpoints, point_t *points, int numedges, edge_t *edges, int numtriangles, triangle_t *triangles) +{ + hlassume (numpoints == (1 << (2 * skylevel)) + 2, assume_first); + hlassume (numedges == (1 << (2 * skylevel)) * 4 - 4 , assume_first); + hlassume (numtriangles == (1 << (2 * skylevel)) * 2, assume_first); + g_numskynormals[skylevel] = numpoints; + g_skynormals[skylevel] = (vec3_t *)malloc (numpoints * sizeof (vec3_t)); + g_skynormalsizes[skylevel] = (vec_t *)malloc (numpoints * sizeof (vec_t)); + hlassume (g_skynormals[skylevel] != NULL, assume_NoMemory); + hlassume (g_skynormalsizes[skylevel] != NULL, assume_NoMemory); + int j, k; + for (j = 0; j < numpoints; j++) + { + VectorCopy (points[j], g_skynormals[skylevel][j]); + g_skynormalsizes[skylevel][j] = 0; + } + double totalsize = 0; + for (j = 0; j < numtriangles; j++) + { + int pt[3]; + for (k = 0; k < 3; k++) + { + pt[k] = edges[triangles[j].edge[k]].point[triangles[j].dir[k]]; + } + double currentsize; + double tmp[3]; + CrossProduct (points[pt[0]], points[pt[1]], tmp); + currentsize = DotProduct (tmp, points[pt[2]]); + hlassume (currentsize > 0, assume_first); + g_skynormalsizes[skylevel][pt[0]] += currentsize / 3.0; + g_skynormalsizes[skylevel][pt[1]] += currentsize / 3.0; + g_skynormalsizes[skylevel][pt[2]] += currentsize / 3.0; + totalsize += currentsize; + } + for (j = 0; j < numpoints; j++) + { + g_skynormalsizes[skylevel][j] /= totalsize; + } +#if 0 + printf ("g_numskynormals[%i]=%i\n", skylevel, g_numskynormals[skylevel]); + for (j = 0; j < numpoints; j += (numpoints / 20 + 1)) + { + printf ("g_skynormals[%i][%i]=%1.3f,%1.3f,%1.3f g_skynormalsizes[%i][%i]=%f\n", + skylevel, j, g_skynormals[skylevel][j][0], g_skynormals[skylevel][j][1], g_skynormals[skylevel][j][2], + skylevel, j, g_skynormalsizes[skylevel][j]); + } +#endif +} +void BuildDiffuseNormals () +{ + int i, j, k; + g_numskynormals[0] = 0; + g_skynormals[0] = NULL; //don't use this + g_skynormalsizes[0] = NULL; + int numpoints = 6; + point_t *points = (point_t *)malloc (((1 << (2 * SKYLEVELMAX)) + 2) * sizeof (point_t)); + hlassume (points != NULL, assume_NoMemory); + points[0][0] = 1, points[0][1] = 0, points[0][2] = 0; + points[1][0] = -1,points[1][1] = 0, points[1][2] = 0; + points[2][0] = 0, points[2][1] = 1, points[2][2] = 0; + points[3][0] = 0, points[3][1] = -1,points[3][2] = 0; + points[4][0] = 0, points[4][1] = 0, points[4][2] = 1; + points[5][0] = 0, points[5][1] = 0, points[5][2] = -1; + int numedges = 12; + edge_t *edges = (edge_t *)malloc (((1 << (2 * SKYLEVELMAX)) * 4 - 4) * sizeof (edge_t)); + hlassume (edges != NULL, assume_NoMemory); + edges[0].point[0] = 0, edges[0].point[1] = 2, edges[0].divided = false; + edges[1].point[0] = 2, edges[1].point[1] = 1, edges[1].divided = false; + edges[2].point[0] = 1, edges[2].point[1] = 3, edges[2].divided = false; + edges[3].point[0] = 3, edges[3].point[1] = 0, edges[3].divided = false; + edges[4].point[0] = 2, edges[4].point[1] = 4, edges[4].divided = false; + edges[5].point[0] = 4, edges[5].point[1] = 3, edges[5].divided = false; + edges[6].point[0] = 3, edges[6].point[1] = 5, edges[6].divided = false; + edges[7].point[0] = 5, edges[7].point[1] = 2, edges[7].divided = false; + edges[8].point[0] = 4, edges[8].point[1] = 0, edges[8].divided = false; + edges[9].point[0] = 0, edges[9].point[1] = 5, edges[9].divided = false; + edges[10].point[0] = 5, edges[10].point[1] = 1, edges[10].divided = false; + edges[11].point[0] = 1, edges[11].point[1] = 4, edges[11].divided = false; + int numtriangles = 8; + triangle_t *triangles = (triangle_t *)malloc (((1 << (2 * SKYLEVELMAX)) * 2) * sizeof (triangle_t)); + hlassume (triangles != NULL, assume_NoMemory); + triangles[0].edge[0] = 0, triangles[0].dir[0] = 0, triangles[0].edge[1] = 4, triangles[0].dir[1] = 0, triangles[0].edge[2] = 8, triangles[0].dir[2] = 0; + triangles[1].edge[0] = 1, triangles[1].dir[0] = 0, triangles[1].edge[1] = 11, triangles[1].dir[1] = 0, triangles[1].edge[2] = 4, triangles[1].dir[2] = 1; + triangles[2].edge[0] = 2, triangles[2].dir[0] = 0, triangles[2].edge[1] = 5, triangles[2].dir[1] = 1, triangles[2].edge[2] = 11, triangles[2].dir[2] = 1; + triangles[3].edge[0] = 3, triangles[3].dir[0] = 0, triangles[3].edge[1] = 8, triangles[3].dir[1] = 1, triangles[3].edge[2] = 5, triangles[3].dir[2] = 0; + triangles[4].edge[0] = 0, triangles[4].dir[0] = 1, triangles[4].edge[1] = 9, triangles[4].dir[1] = 0, triangles[4].edge[2] = 7, triangles[4].dir[2] = 0; + triangles[5].edge[0] = 1, triangles[5].dir[0] = 1, triangles[5].edge[1] = 7, triangles[5].dir[1] = 1, triangles[5].edge[2] = 10, triangles[5].dir[2] = 0; + triangles[6].edge[0] = 2, triangles[6].dir[0] = 1, triangles[6].edge[1] = 10, triangles[6].dir[1] = 1, triangles[6].edge[2] = 6, triangles[6].dir[2] = 1; + triangles[7].edge[0] = 3, triangles[7].dir[0] = 1, triangles[7].edge[1] = 6, triangles[7].dir[1] = 0, triangles[7].edge[2] = 9, triangles[7].dir[2] = 1; + CopyToSkynormals (1, numpoints, points, numedges, edges, numtriangles, triangles); + for (i = 1; i < SKYLEVELMAX; i++) + { + int oldnumedges = numedges; + for (j = 0; j < oldnumedges; j++) + { + if (!edges[j].divided) + { + hlassume (numpoints < (1 << (2 * SKYLEVELMAX)) + 2, assume_first); + point_t mid; + double len; + VectorAdd (points[edges[j].point[0]], points[edges[j].point[1]], mid); + len = sqrt (DotProduct (mid, mid)); + hlassume (len > 0.2, assume_first); + VectorScale (mid, 1 / len, mid); + int p2 = numpoints; + VectorCopy (mid, points[numpoints]); + numpoints++; + hlassume (numedges < (1 << (2 * SKYLEVELMAX)) * 4 - 4, assume_first); + edges[j].child[0] = numedges; + edges[numedges].divided = false; + edges[numedges].point[0] = edges[j].point[0]; + edges[numedges].point[1] = p2; + numedges++; + hlassume (numedges < (1 << (2 * SKYLEVELMAX)) * 4 - 4, assume_first); + edges[j].child[1] = numedges; + edges[numedges].divided = false; + edges[numedges].point[0] = p2; + edges[numedges].point[1] = edges[j].point[1]; + numedges++; + edges[j].divided = true; + } + } + int oldnumtriangles = numtriangles; + for (j = 0; j < oldnumtriangles; j++) + { + int mid[3]; + for (k = 0; k < 3; k++) + { + hlassume (numtriangles < (1 << (2 * SKYLEVELMAX)) * 2, assume_first); + mid[k] = edges[edges[triangles[j].edge[k]].child[0]].point[1]; + triangles[numtriangles].edge[0] = edges[triangles[j].edge[k]].child[1 - triangles[j].dir[k]]; + triangles[numtriangles].dir[0] = triangles[j].dir[k]; + triangles[numtriangles].edge[1] = edges[triangles[j].edge[(k+1)%3]].child[triangles[j].dir[(k+1)%3]]; + triangles[numtriangles].dir[1] = triangles[j].dir[(k+1)%3]; + triangles[numtriangles].edge[2] = numedges + k; + triangles[numtriangles].dir[2] = 1; + numtriangles++; + } + for (k = 0; k < 3; k++) + { + hlassume (numedges < (1 << (2 * SKYLEVELMAX)) * 4 - 4, assume_first); + triangles[j].edge[k] = numedges; + triangles[j].dir[k] = 0; + edges[numedges].divided = false; + edges[numedges].point[0] = mid[k]; + edges[numedges].point[1] = mid[(k+1)%3]; + numedges++; + } + } + CopyToSkynormals (i + 1, numpoints, points, numedges, edges, numtriangles, triangles); + } + free (points); + free (edges); + free (triangles); +} +#endif +static void GatherSampleLight(const vec3_t pos, const byte* const pvs, const vec3_t normal, vec3_t* sample +#ifdef ZHLT_XASH + , vec3_t* sample_direction +#endif + , byte* styles +#ifdef HLRAD_GatherPatchLight + , int step +#endif +#ifdef HLRAD_DIVERSE_LIGHTING + , int miptex +#endif +#ifdef HLRAD_TEXLIGHTGAP + , int texlightgap_surfacenum +#endif + ) +{ + int i; + directlight_t* l; +#ifndef HLRAD_OPAQUE_STYLE + vec3_t add; +#ifdef ZHLT_XASH + vec3_t add_direction; +#endif +#endif // now we always add the light into the total brightness of this sample immediately after each TestLine, because each TestLine may result in different style. + vec3_t delta; + float dot, dot2; + float dist; + float ratio; +#ifdef HLRAD_OPACITY // AJM + float l_opacity; +#endif + int style_index; +#ifdef HLRAD_GatherPatchLight + int step_match; +#endif +#ifdef HLRAD_MULTISKYLIGHT + bool sky_used = false; +#else + directlight_t* sky_used = NULL; +#endif +#ifdef HLRAD_ACCURATEBOUNCE_ALTERNATEORIGIN + vec3_t testline_origin; +#endif +#ifdef HLRAD_STYLE_CORING + vec3_t adds[ALLSTYLES]; +#ifdef ZHLT_XASH + vec3_t adds_direction[ALLSTYLES]; +#endif + int style; + memset (adds, 0, ALLSTYLES * sizeof(vec3_t)); +#ifdef ZHLT_XASH + memset (adds_direction, 0, ALLSTYLES * sizeof (vec3_t)); +#endif +#endif +#ifdef HLRAD_DIVERSE_LIGHTING + bool lighting_diversify; + vec_t lighting_power; + vec_t lighting_scale; + lighting_power = g_lightingconeinfo[miptex][0]; + lighting_scale = g_lightingconeinfo[miptex][1]; + lighting_diversify = (lighting_power != 1.0 || lighting_scale != 1.0); +#endif +#ifdef HLRAD_TEXLIGHTGAP + vec3_t texlightgap_textoworld[2]; + // calculates textoworld + { + dface_t *f = &g_dfaces[texlightgap_surfacenum]; + const dplane_t *dp = getPlaneFromFace (f); + texinfo_t *tex = &g_texinfo[f->texinfo]; + int x; + vec_t len; + + for (x = 0; x < 2; x++) + { + CrossProduct (tex->vecs[1 - x], dp->normal, texlightgap_textoworld[x]); + len = DotProduct (texlightgap_textoworld[x], tex->vecs[x]); + if (fabs (len) < NORMAL_EPSILON) + { + VectorClear (texlightgap_textoworld[x]); + } + else + { + VectorScale (texlightgap_textoworld[x], 1 / len, texlightgap_textoworld[x]); + } + } + } +#endif + +#ifdef HLRAD_SKYFIX_FIX +#ifdef HLRAD_VIS_FIX + for (i = 0; i < 1 + g_dmodels[0].visleafs; i++) +#else + for (i = 0; i < g_numleafs; i++) +#endif +#else +#ifdef HLRAD_VIS_FIX + for (i = 1; i < 1 + g_dmodels[0].visleafs; i++) +#else + for (i = 1; i < g_numleafs; i++) +#endif +#endif + { + l = directlights[i]; +#ifdef HLRAD_SKYFIX_FIX + if (l) + { + if (i == 0? g_sky_lighting_fix: pvs[(i - 1) >> 3] & (1 << ((i - 1) & 7))) + { + for (; l; l = l->next) + { +#else + if (l) + { + if (((l->type == emit_skylight) && (g_sky_lighting_fix)) || (pvs[(i - 1) >> 3] & (1 << ((i - 1) & 7)))) + { + for (; l; l = l->next) + { +#endif + // skylights work fundamentally differently than normal lights + if (l->type == emit_skylight) + { +#ifdef HLRAD_MULTISKYLIGHT + if (!g_sky_lighting_fix) + { + if (sky_used) + { + continue; + } + sky_used = true; + } +#ifndef HLRAD_OPAQUE_STYLE + VectorClear (add); +#endif + do // add sun light + { +#ifdef HLRAD_GatherPatchLight + // check step + step_match = (int)l->topatch; + if (step != step_match) + continue; +#endif +#ifdef HLRAD_ALLOWZEROBRIGHTNESS + // check intensity + if (!(l->intensity[0] || l->intensity[1] || l->intensity[2])) + continue; +#endif +#ifdef HLRAD_SUNSPREAD + // loop over the normals + for (int j = 0; j < l->numsunnormals; j++) + { +#endif + // make sure the angle is okay + #ifdef HLRAD_SUNSPREAD + dot = -DotProduct (normal, l->sunnormals[j]); + #else + dot = -DotProduct (normal, l->normal); + #endif + if (dot <= NORMAL_EPSILON) //ON_EPSILON / 10 //--vluzacn + { + continue; + } + + // search back to see if we can hit a sky brush + #ifdef HLRAD_SUNSPREAD + #ifdef ZHLT_LARGERANGE + VectorScale (l->sunnormals[j], -BOGUS_RANGE, delta); + #else + VectorScale (l->sunnormals[j], -10000, delta); + #endif + #else + #ifdef ZHLT_LARGERANGE + VectorScale (l->normal, -BOGUS_RANGE, delta); + #else + VectorScale (l->normal, -10000, delta); + #endif + #endif + VectorAdd(pos, delta, delta); + #ifdef HLRAD_OPAQUEINSKY_FIX + vec3_t skyhit; + VectorCopy (delta, skyhit); + #endif + if (TestLine(pos, delta + #ifdef HLRAD_OPAQUEINSKY_FIX + , skyhit + #endif + ) != CONTENTS_SKY) + { + continue; // occluded + } + + #ifdef HLRAD_HULLU + vec3_t transparency; + #endif + #ifdef HLRAD_OPAQUE_STYLE + int opaquestyle; + #endif + if (TestSegmentAgainstOpaqueList(pos, + #ifdef HLRAD_OPAQUEINSKY_FIX + skyhit + #else + delta + #endif + #ifdef HLRAD_HULLU + , transparency + #endif + #ifdef HLRAD_OPAQUE_STYLE + , opaquestyle + #endif + )) + { + continue; + } + + #ifdef ZHLT_XASH + vec3_t direction; + #ifdef HLRAD_SUNSPREAD + VectorCopy (l->sunnormals[j], direction); + #else + VectorCopy (l->normal, direction); + #endif + #endif + vec3_t add_one; + #ifdef HLRAD_DIVERSE_LIGHTING + if (lighting_diversify) + { + dot = lighting_scale * pow (dot, lighting_power); + } + #endif + #ifdef HLRAD_SUNSPREAD + VectorScale (l->intensity, dot * l->sunnormalweights[j], add_one); + #else + VectorScale(l->intensity, dot, add_one); + #endif + #ifdef HLRAD_HULLU + VectorMultiply(add_one, transparency, add_one); + #endif + #ifdef HLRAD_OPAQUE_STYLE + // add to the total brightness of this sample + style = l->style; + if (opaquestyle != -1) + { + if (style == 0 || style == opaquestyle) + style = opaquestyle; + else + continue; // dynamic light of other styles hits this toggleable opaque entity, then it completely vanishes. + } + VectorAdd (adds[style], add_one, adds[style]); + #ifdef ZHLT_XASH + vec_t avg = VectorAvg (add_one); + VectorMA (adds_direction[style], avg, direction, adds_direction[style]); + #endif + #else + // add to the contribution of this light + VectorAdd (add, add_one, add); + #ifdef ZHLT_XASH + vec_t avg = VectorAvg (add_one); + VectorMA (add_direction, avg, direction, add_direction); + #endif + #endif +#ifdef HLRAD_SUNSPREAD + } // (loop over the normals) +#endif + } + while (0); + do // add sky light + { +#ifdef HLRAD_GatherPatchLight + // check step + step_match = 0; + #ifdef HLRAD_SOFTSKY + if (g_softsky) + step_match = 1; + #endif + #ifdef HLRAD_FASTMODE + if (g_fastmode) + step_match = 1; + #endif + if (step != step_match) + continue; +#endif + // check intensity + if (g_indirect_sun <= 0.0 || + VectorCompare ( + #ifdef HLRAD_WHOME + l->diffuse_intensity, + #else + l->intensity, + #endif + vec3_origin) + #ifdef HLRAD_SUNDIFFUSE + && VectorCompare (l->diffuse_intensity2, vec3_origin) + #endif + ) + continue; + + vec3_t sky_intensity; + #ifndef HLRAD_SUNDIFFUSE + #ifndef HLRAD_SOFTSKY + #ifdef HLRAD_WHOME + VectorScale (l->diffuse_intensity, g_indirect_sun / (NUMVERTEXNORMALS * 2), sky_intensity); + #else + VectorScale (l->intensity, g_indirect_sun / (NUMVERTEXNORMALS * 2), sky_intensity); + #endif + #endif + #endif + + // loop over the normals + #ifdef HLRAD_SOFTSKY + vec3_t *skynormals = g_skynormals[g_softsky?SKYLEVEL_SOFTSKYON:SKYLEVEL_SOFTSKYOFF]; + vec_t *skyweights = g_skynormalsizes[g_softsky?SKYLEVEL_SOFTSKYON:SKYLEVEL_SOFTSKYOFF]; + for (int j = 0; j < g_numskynormals[g_softsky?SKYLEVEL_SOFTSKYON:SKYLEVEL_SOFTSKYOFF]; j++) + #else + for (int j = 0; j < NUMVERTEXNORMALS; j++) + #endif + { + // make sure the angle is okay + #ifdef HLRAD_SOFTSKY + dot = -DotProduct (normal, skynormals[j]); + #else + dot = -DotProduct (normal, r_avertexnormals[j]); + #endif + if (dot <= NORMAL_EPSILON) //ON_EPSILON / 10 //--vluzacn + { + continue; + } + + // search back to see if we can hit a sky brush + #ifdef HLRAD_SOFTSKY + #ifdef ZHLT_LARGERANGE + VectorScale (skynormals[j], -BOGUS_RANGE, delta); + #else + VectorScale (skynormals[j], -10000, delta); + #endif + #else + #ifdef ZHLT_LARGERANGE + VectorScale (r_avertexnormals[j], -BOGUS_RANGE, delta); + #else + VectorScale (r_avertexnormals[j], -10000, delta); + #endif + #endif + VectorAdd(pos, delta, delta); + #ifdef HLRAD_OPAQUEINSKY_FIX + vec3_t skyhit; + VectorCopy (delta, skyhit); + #endif + if (TestLine(pos, delta + #ifdef HLRAD_OPAQUEINSKY_FIX + , skyhit + #endif + ) != CONTENTS_SKY) + { + continue; // occluded + } + + #ifdef HLRAD_OPAQUE_DIFFUSE_FIX + #ifdef HLRAD_HULLU + vec3_t transparency; + #endif + #ifdef HLRAD_OPAQUE_STYLE + int opaquestyle; + #endif + if (TestSegmentAgainstOpaqueList(pos, + #ifdef HLRAD_OPAQUEINSKY_FIX + skyhit + #else + delta + #endif + #ifdef HLRAD_HULLU + , transparency + #endif + #ifdef HLRAD_OPAQUE_STYLE + , opaquestyle + #endif + )) + { + continue; + } + #endif /*HLRAD_OPAQUE_DIFFUSE_FIX*/ + + #ifdef ZHLT_XASH + vec3_t direction; + #ifdef HLRAD_SOFTSKY + VectorCopy (skynormals[j], direction); + #else + VectorCopy (r_avertexnormals[j], direction); + #endif + #endif + #ifdef HLRAD_SUNDIFFUSE + #ifdef HLRAD_SOFTSKY + vec_t factor = qmin (qmax (0.0, (1 - DotProduct (l->normal, skynormals[j])) / 2), 1.0); // how far this piece of sky has deviated from the sun + #else + vec_t factor = qmin (qmax (0.0, (1 - DotProduct (l->normal, r_avertexnormals[j])) / 2), 1.0); // how far this piece of sky has deviated from the sun + #endif + VectorScale (l->diffuse_intensity, 1 - factor, sky_intensity); + VectorMA (sky_intensity, factor, l->diffuse_intensity2, sky_intensity); + #ifdef HLRAD_SOFTSKY + VectorScale (sky_intensity, skyweights[j] * g_indirect_sun / 2, sky_intensity); + #else + VectorScale (sky_intensity, g_indirect_sun / (NUMVERTEXNORMALS * 2), sky_intensity); + #endif + #else + #ifdef HLRAD_SOFTSKY + #ifdef HLRAD_WHOME + VectorScale (l->diffuse_intensity, skyweights[j] * g_indirect_sun / 2, sky_intensity); + #else + VectorScale (l->intensity, skyweights[j] * g_indirect_sun / 2, sky_intensity); + #endif + #endif + #endif + vec3_t add_one; + #ifdef HLRAD_DIVERSE_LIGHTING + if (lighting_diversify) + { + dot = lighting_scale * pow (dot, lighting_power); + } + #endif + VectorScale(sky_intensity, dot, add_one); + #ifdef HLRAD_OPAQUE_DIFFUSE_FIX + #ifdef HLRAD_HULLU + VectorMultiply(add_one, transparency, add_one); + #endif + #endif /*HLRAD_OPAQUE_DIFFUSE_FIX*/ + #ifdef HLRAD_OPAQUE_STYLE + // add to the total brightness of this sample + style = l->style; + if (opaquestyle != -1) + { + if (style == 0 || style == opaquestyle) + style = opaquestyle; + else + continue; // dynamic light of other styles hits this toggleable opaque entity, then it completely vanishes. + } + VectorAdd (adds[style], add_one, adds[style]); + #ifdef ZHLT_XASH + vec_t avg = VectorAvg (add_one); + VectorMA (adds_direction[style], avg, direction, adds_direction[style]); + #endif + #else + // add to the contribution of this light + VectorAdd(add, add_one, add); + #ifdef ZHLT_XASH + vec_t avg = VectorAvg (add_one); + VectorMA (add_direction, avg, direction, add_direction); + #endif + #endif + } // (loop over the normals) + + } + while (0); + +#else /*HLRAD_MULTISKYLIGHT*/ + // only allow one of each sky type to hit any given point + if (sky_used) + { + continue; + } + sky_used = l; + + // make sure the angle is okay + dot = -DotProduct(normal, l->normal); + if (dot <= NORMAL_EPSILON) //ON_EPSILON / 10 //--vluzacn + { + continue; + } + + // search back to see if we can hit a sky brush +#ifdef ZHLT_LARGERANGE + VectorScale(l->normal, -BOGUS_RANGE, delta); +#else + VectorScale(l->normal, -10000, delta); +#endif + VectorAdd(pos, delta, delta); +#ifdef HLRAD_OPAQUEINSKY_FIX + vec3_t skyhit; + VectorCopy (delta, skyhit); +#endif + if (TestLine(pos, delta +#ifdef HLRAD_OPAQUEINSKY_FIX + , skyhit +#endif + ) != CONTENTS_SKY) + { + continue; // occluded + } + +#ifdef HLRAD_HULLU + vec3_t transparency = {1.0,1.0,1.0}; +#endif + if (TestSegmentAgainstOpaqueList(pos, +#ifdef HLRAD_OPAQUEINSKY_FIX + skyhit +#else + delta +#endif +#ifdef HLRAD_HULLU + , transparency +#endif + )) + { + continue; + } + +#ifdef HLRAD_DIVERSE_LIGHTING + if (lighting_diversify) + { + dot = lighting_scale * pow (dot, lighting_power); + } +#endif + VectorScale(l->intensity, dot, add); +#ifdef HLRAD_HULLU + VectorMultiply(add, transparency, add); +#endif + +#endif /*HLRAD_MULTISKYLIGHT*/ + } + else // not emit_skylight + { +#ifdef HLRAD_GatherPatchLight + step_match = (int)l->topatch; + if (step != step_match) + continue; +#endif +#ifdef HLRAD_ALLOWZEROBRIGHTNESS + if (!(l->intensity[0] || l->intensity[1] || l->intensity[2])) + continue; +#endif +#ifdef HLRAD_ACCURATEBOUNCE_ALTERNATEORIGIN + VectorCopy (l->origin, testline_origin); +#endif + float denominator; + + VectorSubtract(l->origin, pos, delta); +#ifdef HLRAD_ACCURATEBOUNCE_TEXLIGHT + if (l->type == emit_surface) + { + // move emitter back to its plane + VectorMA (delta, -PATCH_HUNT_OFFSET, l->normal, delta); + } +#endif + dist = VectorNormalize(delta); + dot = DotProduct(delta, normal); + // if (dot <= 0.0) + // continue; +#ifndef HLRAD_ACCURATEBOUNCE_ALTERNATEORIGIN + if (dot <= NORMAL_EPSILON) //ON_EPSILON / 10 //--vluzacn + { + continue; // behind sample surface + } +#endif + + if (dist < 1.0) + { + dist = 1.0; + } + +#ifdef HLRAD_ARG_MISC + denominator = dist * dist * l->fade; +#else + // Variable power falloff (1 = inverse linear, 2 = inverse square + denominator = dist * l->fade; + if (l->falloff == 2) + { + denominator *= dist; + } +#endif + +#ifdef HLRAD_OPAQUE_STYLE + vec3_t add; +#endif +#ifdef ZHLT_XASH + vec3_t direction; + VectorSubtract (vec3_origin, delta, direction); + #ifdef HLRAD_ACCURATEBOUNCE_TEXLIGHT + if ((-dot) > 0) + { + // reflect the direction back (this is not ideal!) + VectorMA (direction, -(-dot) * 2, normal, direction); + } + #endif +#endif + switch (l->type) + { + case emit_point: + { +#ifdef HLRAD_ACCURATEBOUNCE_ALTERNATEORIGIN + if (dot <= NORMAL_EPSILON) + { + continue; + } +#endif +#ifdef HLRAD_ARG_MISC + vec_t denominator = dist * dist * l->fade; +#else + // Variable power falloff (1 = inverse linear, 2 = inverse square + vec_t denominator = dist * l->fade; + + if (l->falloff == 2) + { + denominator *= dist; + } +#endif +#ifdef HLRAD_DIVERSE_LIGHTING + if (lighting_diversify) + { + dot = lighting_scale * pow (dot, lighting_power); + } +#endif + ratio = dot / denominator; + VectorScale(l->intensity, ratio, add); + break; + } + + case emit_surface: + { +#ifdef HLRAD_ACCURATEBOUNCE_ALTERNATEORIGIN + bool light_behind_surface = false; + if (dot <= NORMAL_EPSILON) + { + light_behind_surface = true; + } +#endif +#ifdef HLRAD_DIVERSE_LIGHTING + if (lighting_diversify + #ifdef HLRAD_ACCURATEBOUNCE_ALTERNATEORIGIN + && !light_behind_surface + #endif + ) + { + dot = lighting_scale * pow (dot, lighting_power); + } +#endif + dot2 = -DotProduct(delta, l->normal); +#ifdef HLRAD_TEXLIGHTGAP + // discard the texlight if the spot is too close to the texlight plane + if (l->texlightgap > 0) + { + vec_t test; + + test = dot2 * dist; // distance from spot to texlight plane; + test -= l->texlightgap * fabs (DotProduct (l->normal, texlightgap_textoworld[0])); // maximum distance reduction if the spot is allowed to shift l->texlightgap pixels along s axis + test -= l->texlightgap * fabs (DotProduct (l->normal, texlightgap_textoworld[1])); // maximum distance reduction if the spot is allowed to shift l->texlightgap pixels along t axis + if (test < -ON_EPSILON) + { + continue; + } + } +#endif +#ifdef HLRAD_CUSTOMTEXLIGHT + #ifdef HLRAD_ACCURATEBOUNCE_TEXLIGHT + if (dot2 * dist <= MINIMUM_PATCH_DISTANCE) + { + continue; + } + vec_t range = l->patch_emitter_range; + if (l->stopdot > 0.0) // stopdot2 > 0.0 or stopdot > 0.0 + { + vec_t range_scale; + range_scale = 1 - l->stopdot2 * l->stopdot2; + range_scale = 1 / sqrt (qmax (NORMAL_EPSILON, range_scale)); + // range_scale = 1 / sin (cone2) + range_scale = qmin (range_scale, 2); // restrict this to 2, because skylevel has limit. + range *= range_scale; // because smaller cones are more likely to create the ugly grid effect. + + if (dot2 <= l->stopdot2 + NORMAL_EPSILON) + { + if (dist >= range) // use the old method, which will merely give 0 in this case + { + continue; + } + ratio = 0.0; + } + else if (dot2 <= l->stopdot) + { + ratio = dot * dot2 * (dot2 - l->stopdot2) / (dist * dist * (l->stopdot - l->stopdot2)); + } + else + { + ratio = dot * dot2 / (dist * dist); + } + } + else + { + ratio = dot * dot2 / (dist * dist); + } + #else + if (dot2 <= l->stopdot2 + NORMAL_EPSILON) + { + continue; + } + if (dot2 <= l->stopdot) + { + ratio = dot * dot2 * (dot2 - l->stopdot2) / (dist * dist * (l->stopdot - l->stopdot2)); + } + else + { + ratio = dot * dot2 / (dist * dist); + } + #endif +#else + #ifdef HLRAD_ACCURATEBOUNCE_TEXLIGHT + if (dot2 * dist <= MINIMUM_PATCH_DISTANCE) + { + continue; + } + vec_t range = l->patch_emitter_range; + ratio = dot * dot2 / (dist * dist * g_fade); + #else + if (dot2 <= NORMAL_EPSILON) //ON_EPSILON / 10 //--vluzacn + { + continue; // behind light surface + } + +#ifdef HLRAD_ARG_MISC + vec_t denominator = dist * dist * g_fade; +#else + // Variable power falloff (1 = inverse linear, 2 = inverse square + vec_t denominator = dist * g_fade; + if (g_falloff == 2) + { + denominator *= dist; + } +#endif + ratio = dot * dot2 / denominator; + #endif +#endif + +#ifdef HLRAD_TEXLIGHT_SPOTS_FIX + // analogous to the one in MakeScales + // 0.4f is tested to be able to fully eliminate bright spots + if (ratio * l->patch_area > 0.4f) + { + ratio = 0.4f / l->patch_area; + } + #ifdef HLRAD_ACCURATEBOUNCE_TEXLIGHT + if (dist < range - ON_EPSILON) + { // do things slow + #ifdef HLRAD_ACCURATEBOUNCE_ALTERNATEORIGIN + if (light_behind_surface) + { + dot = 0.0; + ratio = 0.0; + } + GetAlternateOrigin (pos, normal, l->patch, testline_origin); + #endif + vec_t sightarea; + int skylevel = l->patch->emitter_skylevel; + #ifdef HLRAD_CUSTOMTEXLIGHT + if (l->stopdot > 0.0) // stopdot2 > 0.0 or stopdot > 0.0 + { + const vec_t *emitnormal = getPlaneFromFaceNumber (l->patch->faceNumber)->normal; + if (l->stopdot2 >= 0.8) // about 37deg + { + skylevel += 1; // because the range is larger + } + sightarea = CalcSightArea_SpotLight (pos, normal, l->patch->winding, emitnormal, l->stopdot, l->stopdot2, skylevel + #ifdef HLRAD_DIVERSE_LIGHTING + , lighting_power, lighting_scale + #endif + ); // because we have doubled the range + } + else + { + sightarea = CalcSightArea (pos, normal, l->patch->winding, skylevel + #ifdef HLRAD_DIVERSE_LIGHTING + , lighting_power, lighting_scale + #endif + ); + } + #else + sightarea = CalcSightArea (pos, normal, l->patch->winding, skylevel + #ifdef HLRAD_DIVERSE_LIGHTING + , lighting_power, lighting_scale + #endif + ); + #endif + + vec_t frac = dist / range; + frac = (frac - 0.5) * 2; // make a smooth transition between the two methods + frac = qmax (0, qmin (frac, 1)); + + vec_t ratio2 = (sightarea / l->patch_area); // because l->patch->area has been multiplied into l->intensity + #ifndef HLRAD_CUSTOMTEXLIGHT + // Variable power falloff (1 = inverse linear, 2 = inverse square + ratio2 /= g_fade; + #endif + ratio = frac * ratio + (1 - frac) * ratio2; + } + #ifdef HLRAD_ACCURATEBOUNCE_ALTERNATEORIGIN + else + { + if (light_behind_surface) + { + continue; + } + } + #endif + #endif +#endif + VectorScale(l->intensity, ratio, add); + break; + } + + case emit_spotlight: + { +#ifdef HLRAD_ACCURATEBOUNCE_ALTERNATEORIGIN + if (dot <= NORMAL_EPSILON) + { + continue; + } +#endif + dot2 = -DotProduct(delta, l->normal); + if (dot2 <= l->stopdot2) + { + continue; // outside light cone + } + + // Variable power falloff (1 = inverse linear, 2 = inverse square + vec_t denominator = dist * l->fade; +#ifndef HLRAD_ARG_MISC + if (l->falloff == 2) +#endif + { + denominator *= dist; + } +#ifdef HLRAD_DIVERSE_LIGHTING + if (lighting_diversify) + { + dot = lighting_scale * pow (dot, lighting_power); + } +#endif + ratio = dot * dot2 / denominator; + + if (dot2 <= l->stopdot) + { + ratio *= (dot2 - l->stopdot2) / (l->stopdot - l->stopdot2); + } + VectorScale(l->intensity, ratio, add); + break; + } + + default: + { + hlassume(false, assume_BadLightType); + break; + } + } +#ifdef HLRAD_OPAQUE_STYLE + if (TestLine (pos, + #ifdef HLRAD_ACCURATEBOUNCE_ALTERNATEORIGIN + testline_origin + #else + l->origin + #endif + ) != CONTENTS_EMPTY) + { + continue; + } + #ifdef HLRAD_HULLU + vec3_t transparency; + #endif + int opaquestyle; + if (TestSegmentAgainstOpaqueList (pos, + #ifdef HLRAD_ACCURATEBOUNCE_ALTERNATEORIGIN + testline_origin + #else + l->origin + #endif + #ifdef HLRAD_HULLU + , transparency + #endif + , opaquestyle)) + { + continue; + } + #ifdef HLRAD_HULLU + VectorMultiply (add, transparency, add); + #endif + // add to the total brightness of this sample + style = l->style; + if (opaquestyle != -1) + { + if (style == 0 || style == opaquestyle) + style = opaquestyle; + else + continue; // dynamic light of other styles hits this toggleable opaque entity, then it completely vanishes. + } + VectorAdd (adds[style], add, adds[style]); + #ifdef ZHLT_XASH + vec_t avg = VectorAvg (add); + VectorMA (adds_direction[style], avg, direction, adds_direction[style]); + #endif +#else + #ifdef ZHLT_XASH + VectorCopy (direcion, add_direction); // we'll scale it later + #endif +#endif + } // end emit_skylight + +#ifndef HLRAD_OPAQUE_STYLE +#ifndef HLRAD_STYLE_CORING + if (VectorMaximum(add) > (l->style ? g_coring : 0)) +#endif + { +#ifdef HLRAD_HULLU + vec3_t transparency = {1.0,1.0,1.0}; +#endif + + if (l->type != emit_skylight && TestLine(pos, + #ifdef HLRAD_ACCURATEBOUNCE_ALTERNATEORIGIN + testline_origin + #else + l->origin + #endif + ) != CONTENTS_EMPTY) + { + continue; // occluded + } + + if (l->type != emit_skylight) + { // Don't test from light_environment entities to face, the special sky code occludes correctly + if (TestSegmentAgainstOpaqueList(pos, + #ifdef HLRAD_ACCURATEBOUNCE_ALTERNATEORIGIN + testline_origin + #else + l->origin + #endif +#ifdef HLRAD_HULLU + , transparency +#endif +#ifdef HLRAD_OPAQUE_STYLE + , opaquestyle +#endif + )) + { + continue; + } + } + +#ifdef HLRAD_OPACITY + //VectorScale(add, l_opacity, add); +#endif + +#ifdef HLRAD_STYLE_CORING +#ifdef HLRAD_HULLU + if (l->type != emit_skylight) + { + VectorMultiply (add, transparency, add); + } +#endif +#ifdef ZHLT_XASH + if (l->type != emit_skylight) + { + vec3_t avg = VectorAvg (add); + VectorScale (add_direction, avg, add_direction); + } +#endif + VectorAdd (adds[l->style], add, adds[l->style]); +#ifdef ZHLT_XASH + VectorAdd (adds_direction[l->style], add_direction, adds_direction[l->style]); +#endif +#else + for (style_index = 0; style_index < MAXLIGHTMAPS; style_index++) + { + if (styles[style_index] == l->style || styles[style_index] == 255) + { + break; + } + } + + if (style_index == MAXLIGHTMAPS) + { +#ifdef HLRAD_READABLE_EXCEEDSTYLEWARNING + if (++stylewarningcount >= stylewarningnext) + { + stylewarningnext = stylewarningcount * 2; + Warning("Too many direct light styles on a face(%f,%f,%f)", pos[0], pos[1], pos[2]); + Warning(" total %d warnings for too many styles", stylewarningcount); + } +#else + Warning("Too many direct light styles on a face(%f,%f,%f)", pos[0], pos[1], pos[2]); +#endif + continue; + } + + if (styles[style_index] == 255) + { + styles[style_index] = l->style; + } + +#ifdef HLRAD_HULLU + VectorMultiply(add,transparency,add); +#endif + VectorAdd(sample[style_index], add, sample[style_index]); +#endif + } +#endif /*#ifndef HLRAD_OPAQUE_STYLE*/ + } + } + } + } + +#ifndef HLRAD_MULTISKYLIGHT + if (sky_used && g_indirect_sun != 0.0) + { + vec3_t total; + int j; + vec3_t sky_intensity; + + // ----------------------------------------------------------------------------------- + // Changes by Adam Foster - afoster@compsoc.man.ac.uk + // Instead of using intensity from sky_used->intensity, get it from the new sky_used->diffuse_intensity +#ifdef HLRAD_WHOME + VectorScale(sky_used->diffuse_intensity, g_indirect_sun / (NUMVERTEXNORMALS * 2), sky_intensity); +#else + VectorScale(sky_used->intensity, g_indirect_sun / (NUMVERTEXNORMALS * 2), sky_intensity); +#endif + // That should be it. Who knows - it might actually work! + // AJM: It DOES actually work. Havent you ever heard of beta testing.... + // ----------------------------------------------------------------------------------- + + total[0] = total[1] = total[2] = 0.0; + for (j = 0; j < NUMVERTEXNORMALS; j++) + { + // make sure the angle is okay + dot = -DotProduct(normal, r_avertexnormals[j]); + if (dot <= NORMAL_EPSILON) //ON_EPSILON / 10 //--vluzacn + { + continue; + } + + // search back to see if we can hit a sky brush +#ifdef ZHLT_LARGERANGE + VectorScale(r_avertexnormals[j], -BOGUS_RANGE, delta); +#else + VectorScale(r_avertexnormals[j], -10000, delta); +#endif + VectorAdd(pos, delta, delta); +#ifdef HLRAD_OPAQUEINSKY_FIX + vec3_t skyhit; + VectorCopy (delta, skyhit); +#endif + if (TestLine(pos, delta +#ifdef HLRAD_OPAQUEINSKY_FIX + , skyhit +#endif + ) != CONTENTS_SKY) + { + continue; // occluded + } + +#ifdef HLRAD_OPAQUE_DIFFUSE_FIX +#ifdef HLRAD_HULLU + vec3_t transparency = {1.0,1.0,1.0}; +#endif + if (TestSegmentAgainstOpaqueList(pos, +#ifdef HLRAD_OPAQUEINSKY_FIX + skyhit +#else + delta +#endif +#ifdef HLRAD_HULLU + , transparency +#endif + )) + { + continue; + } +#endif /*HLRAD_OPAQUE_DIFFUSE_FIX*/ +#ifdef HLRAD_DIVERSE_LIGHTING + if (lighting_diversify) + { + dot = lighting_scale * pow (dot, lighting_power); + } +#endif + VectorScale(sky_intensity, dot, add); +#ifdef HLRAD_OPAQUE_DIFFUSE_FIX +#ifdef HLRAD_HULLU + VectorMultiply(add, transparency, add); +#endif +#endif /*HLRAD_OPAQUE_DIFFUSE_FIX*/ + VectorAdd(total, add, total); + } + if (VectorMaximum(total) > 0) + { +#ifdef HLRAD_STYLE_CORING + VectorAdd (adds[sky_used->style], total, adds[sky_used->style]); +#else + for (style_index = 0; style_index < MAXLIGHTMAPS; style_index++) + { + if (styles[style_index] == sky_used->style || styles[style_index] == 255) + { + break; + } + } + + if (style_index == MAXLIGHTMAPS) + { +#ifdef HLRAD_READABLE_EXCEEDSTYLEWARNING + if (++stylewarningcount >= stylewarningnext) + { + stylewarningnext = stylewarningcount * 2; + Warning("Too many direct light styles on a face(%f,%f,%f)\n", pos[0], pos[1], pos[2]); + Warning(" total %d warnings for too many styles", stylewarningcount); + } +#else + Warning("Too many direct light styles on a face(%f,%f,%f)\n", pos[0], pos[1], pos[2]); +#endif + return; + } + + if (styles[style_index] == 255) + { + styles[style_index] = sky_used->style; + } + + VectorAdd(sample[style_index], total, sample[style_index]); +#endif + } + } +#endif /*HLRAD_MULTISKYLIGHT*/ +#ifdef HLRAD_STYLE_CORING + for (style = 0; style < ALLSTYLES; ++style) + { +#ifdef HLRAD_AUTOCORING + if (VectorMaximum(adds[style]) > g_corings[style] * 0.1) +#else + if (VectorMaximum(adds[style]) > g_corings[style]) +#endif + { + #ifdef HLRAD_AUTOCORING + for (style_index = 0; style_index < ALLSTYLES; style_index++) + #else + for (style_index = 0; style_index < MAXLIGHTMAPS; style_index++) + #endif + { + if (styles[style_index] == style || styles[style_index] == 255) + { + break; + } + } + + #ifdef HLRAD_AUTOCORING + if (style_index == ALLSTYLES) // shouldn't happen + #else + if (style_index == MAXLIGHTMAPS) + #endif + { +#ifdef HLRAD_READABLE_EXCEEDSTYLEWARNING + if (++stylewarningcount >= stylewarningnext) + { + stylewarningnext = stylewarningcount * 2; + Warning("Too many direct light styles on a face(%f,%f,%f)", pos[0], pos[1], pos[2]); + Warning(" total %d warnings for too many styles", stylewarningcount); + } +#else + Warning("Too many direct light styles on a face(%f,%f,%f)", pos[0], pos[1], pos[2]); +#endif + return; + } + + if (styles[style_index] == 255) + { + styles[style_index] = style; + } + + VectorAdd(sample[style_index], adds[style], sample[style_index]); +#ifdef ZHLT_XASH + VectorAdd (sample_direction[style_index], adds_direction[style], sample_direction[style_index]); +#endif + } +#ifdef HLRAD_AUTOCORING + else + { + if (VectorMaximum (adds[style]) > g_maxdiscardedlight + NORMAL_EPSILON) + { + ThreadLock (); + if (VectorMaximum (adds[style]) > g_maxdiscardedlight + NORMAL_EPSILON) + { + g_maxdiscardedlight = VectorMaximum (adds[style]); + VectorCopy (pos, g_maxdiscardedpos); + } + ThreadUnlock (); + } + } +#endif + } +#endif +} + +// ===================================================================================== +// AddSampleToPatch +// Take the sample's collected light and add it back into the apropriate patch for the radiosity pass. +// ===================================================================================== +#ifdef HLRAD_ACCURATEBOUNCE_SAMPLELIGHT +static void AddSamplesToPatches (const sample_t **samples, const unsigned char *styles, int facenum, const lightinfo_t *l) +{ +#ifndef HLRAD_GatherPatchLight + if (g_numbounce == 0) + { + return; + } +#endif + patch_t *patch; + int i, j, m, k; + int numtexwindings; + Winding **texwindings; + + numtexwindings = 0; + for (patch = g_face_patches[facenum]; patch; patch = patch->next) + { + numtexwindings++; + } + texwindings = (Winding **)malloc (numtexwindings * sizeof (Winding *)); + hlassume (texwindings != NULL, assume_NoMemory); + + // translate world winding into winding in s,t plane + for (j = 0, patch = g_face_patches[facenum]; j < numtexwindings; j++, patch = patch->next) + { + Winding *w = new Winding (patch->winding->m_NumPoints); + for (int x = 0; x < w->m_NumPoints; x++) + { + vec_t s, t; + SetSTFromSurf (l, patch->winding->m_Points[x], s, t); + w->m_Points[x][0] = s; + w->m_Points[x][1] = t; + w->m_Points[x][2] = 0.0; + } + w->RemoveColinearPoints (); + texwindings[j] = w; + } + + for (i = 0; i < l->numsurfpt; i++) + { + // prepare clip planes + vec_t s_vec, t_vec; + s_vec = l->texmins[0] * TEXTURE_STEP + (i % (l->texsize[0] + 1)) * TEXTURE_STEP; + t_vec = l->texmins[1] * TEXTURE_STEP + (i / (l->texsize[0] + 1)) * TEXTURE_STEP; + + dplane_t clipplanes[4]; + VectorClear (clipplanes[0].normal); + clipplanes[0].normal[0] = 1; + clipplanes[0].dist = s_vec - 0.5 * TEXTURE_STEP; + VectorClear (clipplanes[1].normal); + clipplanes[1].normal[0] = -1; + clipplanes[1].dist = -(s_vec + 0.5 * TEXTURE_STEP); + VectorClear (clipplanes[2].normal); + clipplanes[2].normal[1] = 1; + clipplanes[2].dist = t_vec - 0.5 * TEXTURE_STEP; + VectorClear (clipplanes[3].normal); + clipplanes[3].normal[1] = -1; + clipplanes[3].dist = -(t_vec + 0.5 * TEXTURE_STEP); + + // clip each patch + for (j = 0, patch = g_face_patches[facenum]; j < numtexwindings; j++, patch = patch->next) + { + Winding *w = new Winding (*texwindings[j]); + for (k = 0; k < 4; k++) + { + if (w->m_NumPoints) + { + w->Clip (clipplanes[k], false); + } + } + if (w->m_NumPoints) + { + // add sample to patch + vec_t area = w->getArea () / (TEXTURE_STEP * TEXTURE_STEP); + patch->samples += area; + for (m = 0; m < ALLSTYLES && styles[m] != 255; m++) + { + int style = styles[m]; + const sample_t *s = &samples[m][i]; + for (k = 0; k < ALLSTYLES && patch->totalstyle_all[k] != 255; k++) + { + if (patch->totalstyle_all[k] == style) + { + break; + } + } + if (k == ALLSTYLES) + { + #ifdef HLRAD_READABLE_EXCEEDSTYLEWARNING + if (++stylewarningcount >= stylewarningnext) + { + stylewarningnext = stylewarningcount * 2; + Warning("Too many direct light styles on a face(?,?,?)\n"); + Warning(" total %d warnings for too many styles", stylewarningcount); + } + #else + Warning("Too many direct light styles on a face(?,?,?)\n"); + #endif + } + else + { + if (patch->totalstyle_all[k] == 255) + { + patch->totalstyle_all[k] = style; + } + VectorMA (patch->samplelight_all[k], area, s->light, patch->samplelight_all[k]); + #ifdef ZHLT_XASH + VectorMA (patch->samplelight_all_direction[k], area, s->light_direction, patch->samplelight_all_direction[k]); + #endif + } + } + } + delete w; + } + } + + for (j = 0; j < numtexwindings; j++) + { + delete texwindings[j]; + } + free (texwindings); +} +#else +#ifdef ZHLT_TEXLIGHT +static void AddSampleToPatch(const sample_t* const s, const int facenum, int style) //LRC +#else +static void AddSampleToPatch(const sample_t* const s, const int facenum) +#endif +{ + patch_t* patch; + BoundingBox bounds; + int i; + +#ifndef HLRAD_GatherPatchLight + if (g_numbounce == 0) + { + return; + } +#endif + + for (patch = g_face_patches[facenum]; patch; patch = patch->next) + { + // see if the point is in this patch (roughly) + patch->winding->getBounds(bounds); + for (i = 0; i < 3; i++) + { + if (bounds.m_Mins[i] > s->pos[i] + 16) + { + goto nextpatch; + } + if (bounds.m_Maxs[i] < s->pos[i] - 16) + { + goto nextpatch; + } + } +#ifdef HLRAD_AUTOCORING + if (style == 0) + { + patch->samples++; + } +#endif + + // add the sample to the patch +#ifdef ZHLT_TEXLIGHT + //LRC: + #ifdef HLRAD_AUTOCORING + for (i = 0; i < ALLSTYLES && patch->totalstyle_all[i] != 255; i++) + { + if (patch->totalstyle_all[i] == style) + break; + } + if (i == ALLSTYLES) // shouldn't happen + #else + for (i = 0; i < MAXLIGHTMAPS && patch->totalstyle[i] != 255; i++) + { + if (patch->totalstyle[i] == style) + break; + } + if (i == MAXLIGHTMAPS) + #endif + { +#ifdef HLRAD_READABLE_EXCEEDSTYLEWARNING + if (++stylewarningcount >= stylewarningnext) + { + stylewarningnext = stylewarningcount * 2; + Warning("Too many direct light styles on a face(?,?,?)\n"); + Warning(" total %d warnings for too many styles", stylewarningcount); + } +#else + Warning("Too many direct light styles on a face(?,?,?)\n"); +#endif + } + else + { + #ifdef HLRAD_AUTOCORING + if (patch->totalstyle_all[i] == 255) + { + patch->totalstyle_all[i] = style; + } + VectorAdd(patch->samplelight_all[i], s->light, patch->samplelight_all[i]); + #ifdef ZHLT_XASH + VectorAdd (patch->samplelight_all_direction[i], s->light_direction, patch->samplelight_all_direction[i]); + #endif + #else + if (patch->totalstyle[i] == 255) + { + patch->totalstyle[i] = style; + } + + patch->samples[i]++; + VectorAdd(patch->samplelight[i], s->light, patch->samplelight[i]); + #endif + } + //LRC (ends) +#else + patch->samples++; + VectorAdd(patch->samplelight, s->light, patch->samplelight); +#endif + //return; + + nextpatch:; + } + + // don't worry if some samples don't find a patch +} +#endif + +// ===================================================================================== +// GetPhongNormal +// ===================================================================================== +void GetPhongNormal(int facenum, const vec3_t spot, vec3_t phongnormal) +{ + int j; +#ifdef HLRAD_GetPhongNormal_VL + int s; // split every edge into two parts +#endif + const dface_t* f = g_dfaces + facenum; + const dplane_t* p = getPlaneFromFace(f); + vec3_t facenormal; + + VectorCopy(p->normal, facenormal); + VectorCopy(facenormal, phongnormal); + +#ifndef HLRAD_CUSTOMSMOOTH + if (g_smoothing_threshold > 0.0) +#endif + { + // 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++) + { + vec3_t p1; + vec3_t p2; + vec3_t v1; + vec3_t v2; + vec3_t vspot; + unsigned prev_edge; + unsigned next_edge; + int e; + int e1; + int e2; + edgeshare_t* es; + edgeshare_t* es1; + edgeshare_t* es2; + float a1; + float a2; + float aa; + float bb; + float ab; + + if (j) + { +#ifdef HLRAD_NEGATIVEDIVIDEND_MISCFIX + prev_edge = f->firstedge + ((j + f->numedges - 1) % f->numedges); +#else + prev_edge = f->firstedge + ((j - 1) % f->numedges); +#endif + } + else + { + prev_edge = f->firstedge + f->numedges - 1; + } + + if ((j + 1) != f->numedges) + { + next_edge = f->firstedge + ((j + 1) % f->numedges); + } + else + { + next_edge = f->firstedge; + } + + e = g_dsurfedges[f->firstedge + j]; + e1 = g_dsurfedges[prev_edge]; + e2 = g_dsurfedges[next_edge]; + + es = &g_edgeshare[abs(e)]; + es1 = &g_edgeshare[abs(e1)]; + es2 = &g_edgeshare[abs(e2)]; + +#ifdef HLRAD_GetPhongNormal_VL + if ((!es->smooth || es->coplanar) && (!es1->smooth || es1->coplanar) && (!es2->smooth || es2->coplanar)) +#else + if ( + (es->coplanar && es1->coplanar && es2->coplanar) + || + (VectorCompare(es->interface_normal, vec3_origin) && + VectorCompare(es1->interface_normal, vec3_origin) && + VectorCompare(es2->interface_normal, vec3_origin))) +#endif + { + continue; + } + + if (e > 0) + { + VectorCopy(g_dvertexes[g_dedges[e].v[0]].point, p1); + VectorCopy(g_dvertexes[g_dedges[e].v[1]].point, p2); + } + else + { + VectorCopy(g_dvertexes[g_dedges[-e].v[1]].point, p1); + VectorCopy(g_dvertexes[g_dedges[-e].v[0]].point, p2); + } + + // Adjust for origin-based models + VectorAdd(p1, g_face_offset[facenum], p1); + VectorAdd(p2, g_face_offset[facenum], p2); +#ifdef HLRAD_GetPhongNormal_VL + for (s = 0; s < 2; s++) + { + vec3_t s1, s2; + if (s == 0) + { + VectorCopy(p1, s1); + } + else + { + VectorCopy(p2, s1); + } + + VectorAdd(p1,p2,s2); // edge center + VectorScale(s2,0.5,s2); + + VectorSubtract(s1, g_face_centroids[facenum], v1); + VectorSubtract(s2, g_face_centroids[facenum], v2); +#else + + // Build vectors from the middle of the face to the edge vertexes and the sample pos. + VectorSubtract(p1, g_face_centroids[facenum], v1); + VectorSubtract(p2, g_face_centroids[facenum], v2); +#endif + VectorSubtract(spot, g_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) +#ifdef HLRAD_GetPhongNormal_VL + if (a1 >= -0.01 && a2 >= -0.01) +#else + if (a1 >= 0.0 && a2 >= 0.0) +#endif + { + // calculate distance from edge to pos + vec3_t n1, n2; + vec3_t temp; + +#ifdef HLRAD_GetPhongNormal_VL + if (es->smooth) + if (s == 0) + {VectorCopy(es->vertex_normal[e>0?0:1], n1);} + else + {VectorCopy(es->vertex_normal[e>0?1:0], n1);} + else if (s == 0 && es1->smooth) + {VectorCopy(es1->vertex_normal[e1>0?1:0], n1);} + else if (s == 1 && es2->smooth) + {VectorCopy(es2->vertex_normal[e2>0?0:1], n1);} + else + {VectorCopy(facenormal, n1);} + + if (es->smooth) + {VectorCopy(es->interface_normal, n2);} + else + {VectorCopy(facenormal, n2);} +#else + VectorAdd(es->interface_normal, es1->interface_normal, n1) + + if (VectorCompare(n1, vec3_origin)) + { + VectorCopy(facenormal, n1); + } + VectorNormalize(n1); + + VectorAdd(es->interface_normal, es2->interface_normal, n2); + + if (VectorCompare(n2, vec3_origin)) + { + VectorCopy(facenormal, n2); + } + VectorNormalize(n2); +#endif + + // Interpolate between the center and edge normals based on sample position + VectorScale(facenormal, 1.0 - a1 - a2, phongnormal); + VectorScale(n1, a1, temp); + VectorAdd(phongnormal, temp, phongnormal); + VectorScale(n2, a2, temp); + VectorAdd(phongnormal, temp, phongnormal); + VectorNormalize(phongnormal); + break; + } +#ifdef HLRAD_GetPhongNormal_VL + } // s=0,1 +#endif + } + } +} + +const vec3_t s_circuscolors[] = { + {100000.0, 100000.0, 100000.0}, // white + {100000.0, 0.0, 0.0 }, // red + {0.0, 100000.0, 0.0 }, // green + {0.0, 0.0, 100000.0}, // blue + {0.0, 100000.0, 100000.0}, // cyan + {100000.0, 0.0, 100000.0}, // magenta + {100000.0, 100000.0, 0.0 } // yellow +}; + +// ===================================================================================== +// BuildFacelights +// ===================================================================================== +#ifdef HLRAD_BLUR +void CalcLightmap (lightinfo_t *l, byte *styles) +{ + int facenum; + int i, j; + byte pvs[(MAX_MAP_LEAFS + 7) / 8]; + int lastoffset; +#ifdef HLRAD_TRANSLUCENT + byte pvs2[(MAX_MAP_LEAFS + 7) / 8]; + int lastoffset2; +#endif + + facenum = l->surfnum; +#ifdef HLRAD_AUTOCORING + memset (l->lmcache, 0, l->lmcachewidth * l->lmcacheheight * sizeof (vec3_t [ALLSTYLES])); +#ifdef ZHLT_XASH + memset (l->lmcache_direction, 0, l->lmcachewidth * l->lmcacheheight * sizeof (vec3_t [ALLSTYLES])); +#endif +#else + memset (l->lmcache, 0, l->lmcachewidth * l->lmcacheheight * sizeof (vec3_t [MAXLIGHTMAPS])); +#endif + + // for each sample whose light we need to calculate + for (i = 0; i < l->lmcachewidth * l->lmcacheheight; i++) + { + vec_t s, t; + vec_t s_vec, t_vec; + int nearest_s, nearest_t; + vec3_t spot; + #ifdef HLRAD_GROWSAMPLE + #ifdef HLRAD_BLUR_MINIMALSQUARE + vec_t square[2][2]; // the max possible range in which this sample point affects the lighting on a face + #else + vec_t reach; // the max possible range in which a sample point affects the lighting on a face + #endif + vec3_t surfpt; // the point on the surface (with no HUNT_OFFSET applied), used for getting phong normal and doing patch interpolation + int surface; + #endif + vec3_t pointnormal; + bool blocked; + #ifdef HLRAD_TRANSLUCENT + vec3_t spot2; + vec3_t pointnormal2; + #endif + vec3_t *sampled; + #ifdef ZHLT_XASH + vec3_t *sampled_direction; + #endif + #ifdef HLRAD_AVOIDNORMALFLIP + vec3_t *normal_out; + #endif + #ifdef HLRAD_AVOIDWALLBLEED + bool nudged; + int *wallflags_out; + #endif + + // prepare input parameter and output parameter + { + s = ((i % l->lmcachewidth) - l->lmcache_offset) / (vec_t)l->lmcache_density; + t = ((i / l->lmcachewidth) - l->lmcache_offset) / (vec_t)l->lmcache_density; + s_vec = l->texmins[0] * TEXTURE_STEP + s * TEXTURE_STEP; + t_vec = l->texmins[1] * TEXTURE_STEP + t * TEXTURE_STEP; + nearest_s = qmax (0, qmin ((int)floor (s + 0.5), l->texsize[0])); + nearest_t = qmax (0, qmin ((int)floor (t + 0.5), l->texsize[1])); + sampled = l->lmcache[i]; + #ifdef ZHLT_XASH + sampled_direction = l->lmcache_direction[i]; + #endif + #ifdef HLRAD_AVOIDNORMALFLIP + normal_out = &l->lmcache_normal[i]; + #endif + #ifdef HLRAD_AVOIDWALLBLEED + wallflags_out = &l->lmcache_wallflags[i]; + #endif + #ifdef HLRAD_GROWSAMPLE + #ifdef HLRAD_BLUR_MINIMALSQUARE +// +// The following graph illustrates the range in which a sample point can affect the lighting of a face when g_blur = 1.5 and g_extra = on +// X : the sample point. They are placed on every TEXTURE_STEP/lmcache_density (=16.0/3) texture pixels. We calculate light for each sample point, which is the main time sink. +// + : the lightmap pixel. They are placed on every TEXTURE_STEP (=16.0) texture pixels, which is hard coded inside the GoldSrc engine. Their brightness are averaged from the sample points in a square with size g_blur*TEXTURE_STEP. +// o : indicates that this lightmap pixel is affected by the sample point 'X'. The higher g_blur, the more 'o'. +// |/ / / | : indicates that the brightness of this area is affected by the lightmap pixels 'o' and hence by the sample point 'X'. This is because the engine uses bilinear interpolation to display the lightmap. +// +// ============================================================================================================================================== +// || + + + + + + || + + + + + + || + + + + + + || + + + + + + || +// || || || || || +// || || || || || +// || + +-----+-----+ + + || + +-----+-----+-----+ + || + +-----+-----+-----+ + || + + +-----+-----+ + || +// || | / / / / / | || | / / / / / / / / | || | / / / / / / / / | || | / / / / / | || +// || |/ / / / / /| || |/ / / / / / / / /| || |/ / / / / / / / /| || |/ / / / / /| || +// || + + / / X / / + + + || + + / / o X / o / / + + || + + / / o / X o / / + + || + + + / / X / / + + || +// || |/ / / / / /| || |/ / / / / / / / /| || |/ / / / / / / / /| || |/ / / / / /| || +// || | / / / / / | || | / / / / / / / / | || | / / / / / / / / | || | / / / / / | || +// || + +-----+-----+ + + || + +-----+-----+-----+ + || + +-----+-----+-----+ + || + + +-----+-----+ + || +// || || || || || +// || || || || || +// || + + + + + + || + + + + + + || + + + + + + || + + + + + + || +// ============================================================================================================================================== +// || + + + + + + || + + + + + + || + + + + + + || + + + + + + || +// || || || || || +// || || || || || +// || + +-----+-----+ + + || + +-----+-----+-----+ + || + +-----+-----+-----+ + || + + +-----+-----+ + || +// || | / / / / / | || | / / / / / / / / | || | / / / / / / / / | || | / / / / / | || +// || |/ / / / / /| || |/ / / / / / / / /| || |/ / / / / / / / /| || |/ / / / / /| || +// || + + / / o / / + + + || + + / / o / / o / / + + || + + / / o / / o / / + + || + + + / / o / / + + || +// || |/ / /X/ / /| || |/ / / /X/ / / / /| || |/ / / / /X/ / / /| || |/ / /X/ / /| || +// || | / / / / / | || | / / / / / / / / | || | / / / / / / / / | || | / / / / / | || +// || + +/ / /o/ / /+ + + || + +/ / /o/ / /o/ / /+ + || + +/ / /o/ / /o/ / /+ + || + + +/ / /o/ / /+ + || +// || | / / / / / | || | / / / / / / / / | || | / / / / / / / / | || | / / / / / | || +// || |/ / / / / /| || |/ / / / / / / / /| || |/ / / / / / / / /| || |/ / / / / /| || +// || + +-----+-----+ + + || + +-----+-----+-----+ + || + +-----+-----+-----+ + || + + +-----+-----+ + || +// ============================================================================================================================================== +// + square[0][0] = l->texmins[0] * TEXTURE_STEP + ceil (s - (l->lmcache_side + 0.5) / (vec_t)l->lmcache_density) * TEXTURE_STEP - TEXTURE_STEP; + square[0][1] = l->texmins[1] * TEXTURE_STEP + ceil (t - (l->lmcache_side + 0.5) / (vec_t)l->lmcache_density) * TEXTURE_STEP - TEXTURE_STEP; + square[1][0] = l->texmins[0] * TEXTURE_STEP + floor (s + (l->lmcache_side + 0.5) / (vec_t)l->lmcache_density) * TEXTURE_STEP + TEXTURE_STEP; + square[1][1] = l->texmins[1] * TEXTURE_STEP + floor (t + (l->lmcache_side + 0.5) / (vec_t)l->lmcache_density) * TEXTURE_STEP + TEXTURE_STEP; + #else + reach = (0.5 / (vec_t)l->lmcache_density) * TEXTURE_STEP + 0.5 * g_blur * TEXTURE_STEP + TEXTURE_STEP; + #endif + #endif + } + // find world's position for the sample + { +#ifndef HLRAD_GROWSAMPLE + if (i == (nearest_s * l->lmcache_density + l->lmcache_offset) + + l->lmcachewidth * (nearest_t * l->lmcache_density + l->lmcache_offset)) // almost always true when compiled with no '-extra' + { + j = nearest_s + (l->texsize[0] + 1) * nearest_t; + VectorCopy (l->surfpt[j], spot); + blocked = l->surfpt_lightoutside[j]; + } + else +#endif + { + blocked = false; + if (SetSampleFromST ( + #ifdef HLRAD_GROWSAMPLE + surfpt, spot, &surface, + #else + spot, + #endif + #ifdef HLRAD_AVOIDWALLBLEED + &nudged, + #endif + l, s_vec, t_vec, + #ifdef HLRAD_GROWSAMPLE + #ifdef HLRAD_BLUR_MINIMALSQUARE + square, + #else + reach, + #endif + #endif + g_face_lightmode[facenum]) == LightOutside) + { + j = nearest_s + (l->texsize[0] + 1) * nearest_t; + if (l->surfpt_lightoutside[j]) + { + blocked = true; + } + else + { + #ifdef HLRAD_GROWSAMPLE + // the area this light sample has effect on is completely covered by solid, so take whatever valid position. + VectorCopy (l->surfpt[j], surfpt); + VectorCopy (l->surfpt_position[j], spot); + surface = l->surfpt_surface[j]; + #else + VectorCopy(l->surfpt[j], spot); + #endif + } + } + } + #ifdef HLRAD_TRANSLUCENT + if (l->translucent_b) + { +#ifdef HLRAD_GROWSAMPLE + const dplane_t *surfaceplane = getPlaneFromFaceNumber (surface); + Winding *surfacewinding = new Winding (g_dfaces[surface]); + + VectorCopy (spot, spot2); + for (int x = 0; x < surfacewinding->m_NumPoints; x++) + { + VectorAdd (surfacewinding->m_Points[x], g_face_offset[surface], surfacewinding->m_Points[x]); + } + if (!point_in_winding_noedge (*surfacewinding, *surfaceplane, spot2, 0.2)) + { + snap_to_winding_noedge (*surfacewinding, *surfaceplane, spot2, 0.2, 4 * 0.2); + } + VectorMA (spot2, -(g_translucentdepth + 2 * DEFAULT_HUNT_OFFSET), surfaceplane->normal, spot2); + + delete surfacewinding; +#else + vec3_t delta; + VectorSubtract (g_face_centroids[facenum], spot, delta); + VectorNormalize (delta); + VectorMA (spot, 0.2, delta, spot2); + VectorMA (spot2, -(g_translucentdepth + 2*DEFAULT_HUNT_OFFSET), l->facenormal, spot2); +#endif + } + #endif + #ifdef HLRAD_AVOIDWALLBLEED + *wallflags_out = WALLFLAG_NONE; + if (blocked) + { + *wallflags_out |= (WALLFLAG_BLOCKED | WALLFLAG_NUDGED); + } + if (nudged) + { + *wallflags_out |= WALLFLAG_NUDGED; + } + #endif + } + // calculate normal for the sample + { +#ifdef HLRAD_GROWSAMPLE + GetPhongNormal (surface, surfpt, pointnormal); +#else + #ifdef HLRAD_PHONG_FROMORIGINAL + vec3_t pos_original; + SetSurfFromST (l, pos_original, s_vec, t_vec); + { + // adjust sample's offset to 0 + vec_t scale; + scale = DotProduct (l->texnormal, l->facenormal); + VectorMA (pos_original, - DEFAULT_HUNT_OFFSET / scale, l->texnormal, pos_original); + } + GetPhongNormal(facenum, pos_original, pointnormal); + #else + GetPhongNormal(facenum, spot, pointnormal); + #endif +#endif + #ifdef HLRAD_TRANSLUCENT + if (l->translucent_b) + { + VectorSubtract (vec3_origin, pointnormal, pointnormal2); + } + #endif +#ifdef HLRAD_AVOIDNORMALFLIP + VectorCopy (pointnormal, *normal_out); +#endif + } + // calculate visibility for the sample + { + if (!g_visdatasize) + { + if (i == 0) + { + #ifdef ZHLT_DecompressVis_FIX + memset (pvs, 255, (g_dmodels[0].visleafs + 7) / 8); + #else + memset(pvs, 255, (g_numleafs + 7) / 8); + #endif + } + } + else + { + dleaf_t *leaf = PointInLeaf(spot); + int thisoffset = leaf->visofs; + if (i == 0 || thisoffset != lastoffset) + { + #ifdef HLRAD_VIS_FIX + if (thisoffset == -1) + { + #ifdef ZHLT_DecompressVis_FIX + memset (pvs, 0, (g_dmodels[0].visleafs + 7) / 8); + #else + memset (pvs, 0, (g_numleafs + 7) / 8); + #endif + } + else + { + DecompressVis(&g_dvisdata[leaf->visofs], pvs, sizeof(pvs)); + } + #else + hlassert(thisoffset != -1); + DecompressVis(&g_dvisdata[leaf->visofs], pvs, sizeof(pvs)); + #endif + } + lastoffset = thisoffset; + } + #ifdef HLRAD_TRANSLUCENT + if (l->translucent_b) + { + if (!g_visdatasize) + { + if (i == 0) + { + #ifdef ZHLT_DecompressVis_FIX + memset (pvs2, 255, (g_dmodels[0].visleafs + 7) / 8); + #else + memset(pvs2, 255, (g_numleafs + 7) / 8); + #endif + } + } + else + { + dleaf_t *leaf2 = PointInLeaf(spot2); + int thisoffset2 = leaf2->visofs; + if (i == 0 || thisoffset2 != lastoffset2) + { + #ifdef HLRAD_VIS_FIX + if (thisoffset2 == -1) + { + #ifdef ZHLT_DecompressVis_FIX + memset (pvs2, 0, (g_dmodels[0].visleafs + 7) / 8); + #else + memset(pvs2, 0, (g_numleafs + 7) / 8); + #endif + } + else + { + DecompressVis(&g_dvisdata[leaf2->visofs], pvs2, sizeof(pvs2)); + } + #else + hlassert(thisoffset2 != -1); + DecompressVis(&g_dvisdata[leaf2->visofs], pvs2, sizeof(pvs2)); + #endif + } + lastoffset2 = thisoffset2; + } + } + #endif + } + // gather light + { + if (!blocked) + { + GatherSampleLight(spot, pvs, pointnormal, sampled + #ifdef ZHLT_XASH + , sampled_direction + #endif + , styles + #ifdef HLRAD_GatherPatchLight + , 0 + #endif + #ifdef HLRAD_DIVERSE_LIGHTING + , l->miptex + #endif + #ifdef HLRAD_TEXLIGHTGAP + #ifdef HLRAD_GROWSAMPLE + , surface + #else + , facenum + #endif + #endif + ); + } + #ifdef HLRAD_TRANSLUCENT + if (l->translucent_b) + { + #ifdef HLRAD_AUTOCORING + vec3_t sampled2[ALLSTYLES]; + memset (sampled2, 0, ALLSTYLES * sizeof (vec3_t)); + #ifdef ZHLT_XASH + vec3_t sampled2_direction[ALLSTYLES]; + memset (sampled2_direction, 0, ALLSTYLES * sizeof (vec3_t)); + #endif + #else + vec3_t sampled2[MAXLIGHTMAPS]; + memset (sampled2, 0, MAXLIGHTMAPS * sizeof (vec3_t)); + #endif + if (!blocked) + { + GatherSampleLight(spot2, pvs2, pointnormal2, sampled2 + #ifdef ZHLT_XASH + , sampled2_direction + #endif + , styles + #ifdef HLRAD_GatherPatchLight + , 0 + #endif + #ifdef HLRAD_DIVERSE_LIGHTING + , l->miptex + #endif + #ifdef HLRAD_TEXLIGHTGAP + #ifdef HLRAD_GROWSAMPLE + , surface + #else + , facenum + #endif + #endif + ); + } + #ifdef HLRAD_AUTOCORING + for (j = 0; j < ALLSTYLES && styles[j] != 255; j++) + #else + for (j = 0; j < MAXLIGHTMAPS && styles[j] != 255; j++) + #endif + { + #ifdef ZHLT_XASH + // reflect the direction back + vec_t dot = DotProduct (sampled2_direction[j], pointnormal); + VectorMA (sampled2_direction[j], -dot * 2, pointnormal, sampled2_direction[j]); + vec_t front = 0, back = 0; + // calculate the change of brightness and adjust the direction. This is not totally right when the translucent value is not grayscale, + // but at least it still preserves the condition that VectorLength(light_direction) <= VectorAvg(light). + for (int x = 0; x < 3; x++) + { + front += ((1.0 - l->translucent_v[x]) * sampled[j][x]) / 3; + back += (l->translucent_v[x] * sampled2[j][x]) / 3; + } + front = fabs (VectorAvg (sampled[j])) > NORMAL_EPSILON? front / VectorAvg (sampled[j]): 0; + back = fabs (VectorAvg (sampled2[j])) > NORMAL_EPSILON? back / VectorAvg (sampled2[j]): 0; + #endif + for (int x = 0; x < 3; x++) + { + sampled[j][x] = (1.0 - l->translucent_v[x]) * sampled[j][x] + l->translucent_v[x] * sampled2[j][x]; + #ifdef ZHLT_XASH + sampled_direction[j][x] = front * sampled_direction[j][x] + back * sampled2_direction[j][x]; + #endif + } + } + } + #endif + #ifdef HLRAD_AVOIDWALLBLEED + if (g_drawnudge) + { + #ifdef HLRAD_AUTOCORING + for (j = 0; j < ALLSTYLES && styles[j] != 255; j++) + #else + for (j = 0; j < MAXLIGHTMAPS && styles[j] != 255; j++) + #endif + { + if (blocked && styles[j] == 0) + { + sampled[j][0] = 200; + sampled[j][1] = 0; + sampled[j][2] = 0; + } + else if (nudged && styles[j] == 0) // we assume style 0 is always present + { + VectorFill (sampled[j], 100); + } + else + { + VectorClear (sampled[j]); + } + } + } + #endif + } + } +} +#endif +void BuildFacelights(const int facenum) +{ + dface_t* f; +#ifdef HLRAD_AUTOCORING + unsigned char f_styles[ALLSTYLES]; + sample_t *fl_samples[ALLSTYLES]; +#ifndef HLRAD_BLUR + vec3_t sampled[ALLSTYLES]; +#ifdef ZHLT_XASH + vec3_t sampled_direction[ALLSTYLES]; + vec3_t sampled_normal; +#endif +#endif +#else +#ifndef HLRAD_BLUR + vec3_t sampled[MAXLIGHTMAPS]; +#endif +#endif + lightinfo_t l; + int i; + int j; + int k; + sample_t* s; + vec_t* spot; + patch_t* patch; + const dplane_t* plane; + byte pvs[(MAX_MAP_LEAFS + 7) / 8]; + int thisoffset = -1, lastoffset = -1; + int lightmapwidth; + int lightmapheight; + int size; +#ifdef HLRAD_TRANSLUCENT + vec3_t spot2, normal2; + vec3_t delta; + byte pvs2[(MAX_MAP_LEAFS + 7) / 8]; + int thisoffset2 = -1, lastoffset2 = -1; +#endif + +#ifndef HLRAD_TRANCPARENCYLOSS_FIX +#ifdef HLRAD_HULLU + bool b_transparency_loss = false; + vec_t light_left_for_facelight = 1.0; +#endif +#endif +#ifdef HLRAD_AVOIDWALLBLEED + int *sample_wallflags; +#endif + + f = &g_dfaces[facenum]; + + // + // some surfaces don't need lightmaps + // + f->lightofs = -1; +#ifdef HLRAD_AUTOCORING + for (j = 0; j < ALLSTYLES; j++) + { + f_styles[j] = 255; + } +#else + for (j = 0; j < MAXLIGHTMAPS; j++) + { + f->styles[j] = 255; + } +#endif + + if (g_texinfo[f->texinfo].flags & TEX_SPECIAL) + { +#ifdef HLRAD_AUTOCORING + for (j = 0; j < MAXLIGHTMAPS; j++) + { + f->styles[j] = 255; + } +#endif + return; // non-lit texture + } + +#ifdef HLRAD_AUTOCORING + f_styles[0] = 0; +#else + f->styles[0] = 0; // Everyone gets the style zero map. +#endif +#ifdef HLRAD_STYLE_CORING +#ifdef ZHLT_TEXLIGHT + if (g_face_patches[facenum] && g_face_patches[facenum]->emitstyle) + { +#ifdef HLRAD_AUTOCORING + f_styles[1] = g_face_patches[facenum]->emitstyle; +#else + f->styles[1] = g_face_patches[facenum]->emitstyle; +#endif + } +#endif +#endif + + memset(&l, 0, sizeof(l)); + + l.surfnum = facenum; + l.face = f; + +#ifdef HLRAD_TRANSLUCENT + VectorCopy (g_translucenttextures[g_texinfo[f->texinfo].miptex], l.translucent_v); + l.translucent_b = !VectorCompare (l.translucent_v, vec3_origin); +#endif +#ifdef HLRAD_DIVERSE_LIGHTING + l.miptex = g_texinfo[f->texinfo].miptex; +#endif +#ifndef HLRAD_TRANCPARENCYLOSS_FIX + // + // get transparency loss (part of light go through transparency faces.. reduce facelight on these) + // +#ifndef HLRAD_OPAQUE_NODE +#ifdef HLRAD_HULLU + for(unsigned int m = 0; m < g_opaque_face_count; m++) + { + opaqueList_t* opaque = &g_opaque_face_list[m]; + if(opaque->facenum == facenum && opaque->transparency) + { + vec_t transparency = VectorAvg (opaque->transparency_scale); //vec_t transparency = opaque->transparency; //--vluzacn + + b_transparency_loss = true; + + light_left_for_facelight = 1.0 - transparency; + if( light_left_for_facelight < 0.0 ) light_left_for_facelight = 0.0; + if( light_left_for_facelight > 1.0 ) light_left_for_facelight = 1.0; + + break; + } + } +#endif +#endif +#endif + + // + // rotate plane + // + plane = getPlaneFromFace(f); + VectorCopy(plane->normal, l.facenormal); + l.facedist = plane->dist; + + CalcFaceVectors(&l); + CalcFaceExtents(&l); + CalcPoints(&l); +#ifdef HLRAD_BLUR + CalcLightmap (&l +#ifdef HLRAD_AUTOCORING + , f_styles +#else + , f->styles +#endif + ); +#endif +#ifdef HLRAD_MDL_LIGHT_HACK +#ifndef HLRAD_MDL_LIGHT_HACK_NEW + VectorCopy (g_face_offset[facenum], facesampleinfo[facenum].offset); + for (i=0; i<2; ++i) + { + facesampleinfo[facenum].texmins[i] = l.texmins[i]; + facesampleinfo[facenum].texsize[i] = l.texsize[i]; + VectorCopy (l.textoworld[i], facesampleinfo[facenum].textoworld[i]); + VectorCopy (l.worldtotex[i], facesampleinfo[facenum].worldtotex[i]); + } + VectorCopy (l.texorg, facesampleinfo[facenum].texorg); +#endif +#endif + + lightmapwidth = l.texsize[0] + 1; + lightmapheight = l.texsize[1] + 1; + + size = lightmapwidth * lightmapheight; + hlassume(size <= MAX_SINGLEMAP, assume_MAX_SINGLEMAP); + + facelight[facenum].numsamples = l.numsurfpt; + +#ifdef HLRAD_AUTOCORING + for (k = 0; k < ALLSTYLES; k++) + { + fl_samples[k] = (sample_t *)calloc (l.numsurfpt, sizeof(sample_t)); + hlassume (fl_samples[k] != NULL, assume_NoMemory); + } +#else + for (k = 0; k < MAXLIGHTMAPS; k++) + { + facelight[facenum].samples[k] = (sample_t*)calloc(l.numsurfpt, sizeof(sample_t)); +#ifdef HLRAD_HLASSUMENOMEMORY + hlassume (facelight[facenum].samples[k] != NULL, assume_NoMemory); +#endif + } +#endif +#ifdef HLRAD_AUTOCORING + for (patch = g_face_patches[facenum]; patch; patch = patch->next) + { + hlassume (patch->totalstyle_all = (unsigned char *)malloc (ALLSTYLES * sizeof (unsigned char)), assume_NoMemory); + hlassume (patch->samplelight_all = (vec3_t *)malloc (ALLSTYLES * sizeof (vec3_t)), assume_NoMemory); + hlassume (patch->totallight_all = (vec3_t *)malloc (ALLSTYLES * sizeof (vec3_t)), assume_NoMemory); + hlassume (patch->directlight_all = (vec3_t *)malloc (ALLSTYLES * sizeof (vec3_t)), assume_NoMemory); +#ifdef ZHLT_XASH + hlassume (patch->samplelight_all_direction = (vec3_t *)malloc (ALLSTYLES * sizeof (vec3_t)), assume_NoMemory); + hlassume (patch->totallight_all_direction = (vec3_t *)malloc (ALLSTYLES * sizeof (vec3_t)), assume_NoMemory); + hlassume (patch->directlight_all_direction = (vec3_t *)malloc (ALLSTYLES * sizeof (vec3_t)), assume_NoMemory); +#endif + for (j = 0; j < ALLSTYLES; j++) + { + patch->totalstyle_all[j] = 255; + VectorClear (patch->samplelight_all[j]); + VectorClear (patch->totallight_all[j]); + VectorClear (patch->directlight_all[j]); +#ifdef ZHLT_XASH + VectorClear (patch->samplelight_all_direction[j]); + VectorClear (patch->totallight_all_direction[j]); + VectorClear (patch->directlight_all_direction[j]); +#endif + } + patch->totalstyle_all[0] = 0; + } +#endif + +#ifdef HLRAD_AVOIDWALLBLEED + sample_wallflags = (int *)malloc ((2 * l.lmcache_side + 1) * (2 * l.lmcache_side + 1) * sizeof (int)); +#endif + spot = l.surfpt[0]; + for (i = 0; i < l.numsurfpt; i++, spot += 3) + { +#ifndef HLRAD_BLUR + vec3_t pointnormal = { 0, 0, 0 }; +#endif + +#ifndef HLRAD_GROWSAMPLE +#ifdef HLRAD_LERP_TEXNORMAL + vec3_t spot_original; + { + vec_t s_vec = l.texmins[0] * TEXTURE_STEP + (i % lightmapwidth) * TEXTURE_STEP; + vec_t t_vec = l.texmins[1] * TEXTURE_STEP + (i / lightmapwidth) * TEXTURE_STEP; + SetSurfFromST (&l, spot_original, s_vec, t_vec); + { + // adjust sample's offset to 0 + vec_t scale; + scale = DotProduct (l.texnormal, l.facenormal); + VectorMA (spot_original, - DEFAULT_HUNT_OFFSET / scale, l.texnormal, spot_original); + } + } +#endif +#endif +#ifdef HLRAD_AUTOCORING + for (k = 0; k < ALLSTYLES; k++) + { +#ifdef HLRAD_GROWSAMPLE + VectorCopy(spot, fl_samples[k][i].pos); + fl_samples[k][i].surface = l.surfpt_surface[i]; +#else +#ifdef HLRAD_LERP_TEXNORMAL + VectorCopy(spot_original, fl_samples[k][i].pos); +#else + VectorCopy(spot, fl_samples[k][i].pos); +#endif +#endif + } +#else + for (k = 0; k < MAXLIGHTMAPS; k++) + { +#ifdef HLRAD_GROWSAMPLE + VectorCopy(spot, facelight[facenum].samples[k][i].pos); + facelight[facenum].samples[k][i].surface = l.surfpt_surface[i]; +#else +#ifdef HLRAD_LERP_TEXNORMAL + VectorCopy(spot_original, facelight[facenum].samples[k][i].pos); +#else + VectorCopy(spot, facelight[facenum].samples[k][i].pos); +#endif +#endif + } +#endif + +#ifdef HLRAD_BLUR + int s, t, pos; + int s_center, t_center; + vec_t sizehalf; + vec_t weighting, subsamples; +#ifdef HLRAD_AVOIDNORMALFLIP + vec3_t centernormal; + vec_t weighting_correction; +#endif +#ifdef HLRAD_AVOIDWALLBLEED + int pass; +#endif + s_center = (i % lightmapwidth) * l.lmcache_density + l.lmcache_offset; + t_center = (i / lightmapwidth) * l.lmcache_density + l.lmcache_offset; + sizehalf = 0.5 * g_blur * l.lmcache_density; + subsamples = 0.0; +#ifdef HLRAD_AVOIDNORMALFLIP + VectorCopy (l.lmcache_normal[s_center + l.lmcachewidth * t_center], centernormal); +#endif +#ifdef HLRAD_AVOIDWALLBLEED + if (g_bleedfix && !g_drawnudge) + { + int s_origin = s_center; + int t_origin = t_center; + for (s = s_center - l.lmcache_side; s <= s_center + l.lmcache_side; s++) + { + for (t = t_center - l.lmcache_side; t <= t_center + l.lmcache_side; t++) + { + int *pwallflags = &sample_wallflags[(s - s_center + l.lmcache_side) + (2 * l.lmcache_side + 1) * (t - t_center + l.lmcache_side)]; + *pwallflags = l.lmcache_wallflags[s + l.lmcachewidth * t]; + } + } + // project the "shadow" from the origin point + for (s = s_center - l.lmcache_side; s <= s_center + l.lmcache_side; s++) + { + for (t = t_center - l.lmcache_side; t <= t_center + l.lmcache_side; t++) + { + int *pwallflags = &sample_wallflags[(s - s_center + l.lmcache_side) + (2 * l.lmcache_side + 1) * (t - t_center + l.lmcache_side)]; + int coord[2] = {s - s_origin, t - t_origin}; + int axis = abs(coord[0]) >= abs(coord[1])? 0: 1; + int sign = coord[axis] >= 0? 1: -1; + bool blocked1 = false; + bool blocked2 = false; + for (int dist = 1; dist < abs (coord[axis]); dist++) + { + int test1[2]; + int test2[2]; + test1[axis] = test2[axis] = sign * dist; + double intercept = (double)coord[1-axis] * (double)test1[axis] / (double)coord[axis]; + test1[1-axis] = (int)floor (intercept + 0.01); + test2[1-axis] = (int)ceil (intercept - 0.01); + if (abs (test1[0] + s_origin - s_center) > l.lmcache_side || abs (test1[1] + t_origin - t_center) > l.lmcache_side || + abs (test2[0] + s_origin - s_center) > l.lmcache_side || abs (test2[1] + t_origin - t_center) > l.lmcache_side ) + { + Warning ("HLRAD_AVOIDWALLBLEED: internal error. Contact vluzacn@163.com concerning this issue."); + continue; + } + int wallflags1 = sample_wallflags[(test1[0] + s_origin - s_center + l.lmcache_side) + (2 * l.lmcache_side + 1) * (test1[1] + t_origin - t_center + l.lmcache_side)]; + int wallflags2 = sample_wallflags[(test2[0] + s_origin - s_center + l.lmcache_side) + (2 * l.lmcache_side + 1) * (test2[1] + t_origin - t_center + l.lmcache_side)]; + if (wallflags1 & WALLFLAG_NUDGED) + { + blocked1 = true; + } + if (wallflags2 & WALLFLAG_NUDGED) + { + blocked2 = true; + } + } + if (blocked1 && blocked2) + { + *pwallflags |= WALLFLAG_SHADOWED; + } + } + } + } +#endif +#ifdef HLRAD_AVOIDWALLBLEED + for (pass = 0; pass < 2; pass++) + { +#endif + for (s = s_center - l.lmcache_side; s <= s_center + l.lmcache_side; s++) + { + for (t = t_center - l.lmcache_side; t <= t_center + l.lmcache_side; t++) + { + weighting = (qmin (0.5, sizehalf - (s - s_center)) - qmax (-0.5, -sizehalf - (s - s_center))) + * (qmin (0.5, sizehalf - (t - t_center)) - qmax (-0.5, -sizehalf - (t - t_center))); +#ifdef HLRAD_AVOIDWALLBLEED + if (g_bleedfix && !g_drawnudge) + { + int wallflags = sample_wallflags[(s - s_center + l.lmcache_side) + (2 * l.lmcache_side + 1) * (t - t_center + l.lmcache_side)]; + if (wallflags & (WALLFLAG_BLOCKED | WALLFLAG_SHADOWED)) + { + continue; + } + if (wallflags & WALLFLAG_NUDGED) + { + if (pass == 0) + { + continue; + } + } + } +#endif + pos = s + l.lmcachewidth * t; + #ifdef HLRAD_AVOIDNORMALFLIP + // when blur distance (g_blur) is large, the subsample can be very far from the original lightmap sample (aligned with interval TEXTURE_STEP (16.0)) + // in some cases such as a thin cylinder, the subsample can even grow into the opposite side + // as a result, when exposed to a directional light, the light on the cylinder may "leak" into the opposite dark side + // this correction limits the effect of blur distance when the normal changes very fast + // this correction will not break the smoothness that HLRAD_GROWSAMPLE ensures + weighting_correction = DotProduct (l.lmcache_normal[pos], centernormal); + weighting_correction = (weighting_correction > 0)? weighting_correction * weighting_correction: 0; + weighting = weighting * weighting_correction; + #endif + #ifdef HLRAD_AUTOCORING + for (j = 0; j < ALLSTYLES && f_styles[j] != 255; j++) + { + VectorMA (fl_samples[j][i].light, weighting, l.lmcache[pos][j], fl_samples[j][i].light); + #ifdef ZHLT_XASH + VectorMA (fl_samples[j][i].light_direction, weighting, l.lmcache_direction[pos][j], fl_samples[j][i].light_direction); + #endif + } + #else + for (j = 0; j < MAXLIGHTMAPS && f->styles[j] != 255; j++) + { + VectorAdd (facelight[facenum].samples[j][i].light, weighting, l.lmcache[pos][j], facelight[facenum].samples[j][i].light); + } + #endif + subsamples += weighting; + } + } +#ifdef HLRAD_AVOIDWALLBLEED + if (subsamples > NORMAL_EPSILON) + { + break; + } + else + { + subsamples = 0.0; + #ifdef HLRAD_AUTOCORING + for (j = 0; j < ALLSTYLES && f_styles[j] != 255; j++) + { + VectorClear (fl_samples[j][i].light); + #ifdef ZHLT_XASH + VectorClear (fl_samples[j][i].light_direction); + #endif + } + #else + for (j = 0; j < MAXLIGHTMAPS && f->styles[j] != 255; j++) + { + VectorClear (facelight[facenum].samples[j][i].light); + } + #endif + } + } +#endif +#ifdef ZHLT_XASH + for (int k = 0; k < ALLSTYLES; k++) + { + VectorClear (fl_samples[k][i].normal); + } +#endif +#ifdef HLRAD_AVOIDWALLBLEED + if (subsamples > 0) +#else + if (subsamples > NORMAL_EPSILON) +#endif + { +#ifdef ZHLT_XASH + for (int k = 0; k < ALLSTYLES; k++) // fill 'sample.normal' for all 64 styles, just like what we did on 'sample.pos' + { + VectorCopy (centernormal, fl_samples[k][i].normal); + } +#endif + #ifdef HLRAD_AUTOCORING + for (j = 0; j < ALLSTYLES && f_styles[j] != 255; j++) + { + VectorScale (fl_samples[j][i].light, 1.0 / subsamples, fl_samples[j][i].light); + #ifdef ZHLT_XASH + VectorScale (fl_samples[j][i].light_direction, 1.0 / subsamples, fl_samples[j][i].light_direction); + #endif + #else + for (j = 0; j < MAXLIGHTMAPS && f->styles[j] != 255; j++) + { + VectorScale (facelight[facenum].samples[j][i].light, 1.0 / subsamples, facelight[facenum].samples[j][i].light); + #endif + #ifndef HLRAD_ACCURATEBOUNCE_SAMPLELIGHT + #ifdef ZHLT_TEXLIGHT + #ifdef HLRAD_AUTOCORING + AddSampleToPatch (&fl_samples[j][i], facenum, f_styles[j]); //LRC + #else + AddSampleToPatch(&facelight[facenum].samples[j][i], facenum, f->styles[j]); //LRC + #endif + #else + if (f->styles[j] == 0) + { + AddSampleToPatch(&facelight[facenum].samples[j][i], facenum); + } + #endif + #endif + } + } +#else + // get the PVS for the pos to limit the number of checks + if (!g_visdatasize) + { + #ifdef ZHLT_DecompressVis_FIX + memset (pvs, 255, (g_dmodels[0].visleafs + 7) / 8); + #else + memset(pvs, 255, (g_numleafs + 7) / 8); + #endif + lastoffset = -1; + } + else + { + dleaf_t* leaf = PointInLeaf(spot); + + thisoffset = leaf->visofs; + if (i == 0 || thisoffset != lastoffset) + { + #ifdef HLRAD_VIS_FIX + if (thisoffset == -1) + { + #ifdef ZHLT_DecompressVis_FIX + memset (pvs, 0, (g_dmodels[0].visleafs + 7) / 8); + #else + memset (pvs, 0, (g_numleafs + 7) / 8); + #endif + } + else + { + DecompressVis(&g_dvisdata[leaf->visofs], pvs, sizeof(pvs)); + } + #else + hlassert(thisoffset != -1); + DecompressVis(&g_dvisdata[leaf->visofs], pvs, sizeof(pvs)); + #endif + } + lastoffset = thisoffset; + } +#ifdef HLRAD_TRANSLUCENT + if (l.translucent_b) + { + VectorSubtract (g_face_centroids[facenum], spot, delta); + VectorNormalize (delta); + VectorMA (spot, 0.2, delta, spot2); + VectorMA (spot2, -(g_translucentdepth + 2*DEFAULT_HUNT_OFFSET), l.facenormal, spot2); + if (!g_visdatasize) + { + #ifdef ZHLT_DecompressVis_FIX + memset (pvs2, 255, (g_dmodels[0].visleafs + 7) / 8); + #else + memset(pvs2, 255, (g_numleafs + 7) / 8); + #endif + lastoffset2 = -1; + } + else + { + dleaf_t* leaf2 = PointInLeaf(spot2); + + thisoffset2 = leaf2->visofs; + if (i == 0 || thisoffset2 != lastoffset2) + { + #ifdef HLRAD_VIS_FIX + if (thisoffset2 == -1) + { + #ifdef ZHLT_DecompressVis_FIX + memset (pvs2, 0, (g_dmodels[0].visleafs + 7) / 8); + #else + memset(pvs2, 0, (g_numleafs + 7) / 8); + #endif + } + else + { + DecompressVis(&g_dvisdata[leaf2->visofs], pvs2, sizeof(pvs2)); + } + #else + hlassert(thisoffset2 != -1); + DecompressVis(&g_dvisdata[leaf2->visofs], pvs2, sizeof(pvs2)); + #endif + } + lastoffset2 = thisoffset2; + } + } +#endif + + memset(sampled, 0, sizeof(sampled)); +#ifdef ZHLT_XASH + memset (sampled_direction, 0, sizeof (sampled_direction)); + VectorClear (sampled_normal); +#endif + + // If we are doing "extra" samples, oversample the direct light around the point. + if (g_extra +#ifdef HLRAD_FASTMODE + && !g_fastmode +#endif + ) + { +#ifdef HLRAD_WEIGHT_FIX + int weighting[3][3] = { {1, 1, 1}, {1, 1, 1}, {1, 1, 1} }; // because we are using 1/3 dist not 1/2 +#else + int weighting[3][3] = { {5, 9, 5}, {9, 16, 9}, {5, 9, 5} }; +#endif + vec3_t pos; + int s, t, subsamples = 0; + + for (t = -1; t <= 1; t++) + { + for (s = -1; s <= 1; s++) + { +#ifndef HLRAD_CalcPoints_NEW + int subsample = i + t * lightmapwidth + s; + int sample_s = i % lightmapwidth; + int sample_t = i / lightmapwidth; + + if ((0 <= s + sample_s) && (s + sample_s < lightmapwidth) + && (0 <= t + sample_t)&&(t + sample_t styles +#endif +#ifdef HLRAD_GatherPatchLight + , 0 +#endif +#ifdef HLRAD_DIVERSE_LIGHTING + , l.miptex +#endif +#ifdef HLRAD_TEXLIGHTGAP + , facenum +#endif + ); +#ifdef HLRAD_CalcPoints_NEW + } +#endif +#ifdef HLRAD_TRANSLUCENT + if (l.translucent_b) + { +#ifdef HLRAD_AUTOCORING + vec3_t subsampled2[ALLSTYLES]; +#ifdef ZHLT_XASH + vec3_t subsampled2_direction[ALLSTYLES]; +#endif + for (j = 0; j < ALLSTYLES; j++) +#else + vec3_t subsampled2[MAXLIGHTMAPS]; + for (j = 0; j < MAXLIGHTMAPS; j++) +#endif + { + VectorFill(subsampled2[j], 0); +#ifdef ZHLT_XASH + VectorFill (subsampled2_direction[j], 0); +#endif + } + VectorSubtract (g_face_centroids[facenum], pos, delta); + VectorNormalize (delta); + VectorMA (pos, 0.2, delta, spot2); + VectorMA (spot2, -(g_translucentdepth + 2*DEFAULT_HUNT_OFFSET), l.facenormal, spot2); + VectorSubtract (vec3_origin, pointnormal, normal2); +#ifdef HLRAD_CalcPoints_NEW + if (!blocked) + { +#endif + GatherSampleLight(spot2, pvs2, normal2, subsampled2, +#ifdef ZHLT_XASH + subsampled2_direction, +#endif +#ifdef HLRAD_AUTOCORING + f_styles +#else + f->styles +#endif + #ifdef HLRAD_GatherPatchLight + , 0 + #endif +#ifdef HLRAD_DIVERSE_LIGHTING + , l.miptex +#endif +#ifdef HLRAD_TEXLIGHTGAP + , facenum +#endif + ); +#ifdef HLRAD_CalcPoints_NEW + } +#endif +#ifdef HLRAD_AUTOCORING + for (j = 0; j < ALLSTYLES && f_styles[j] != 255; j++) +#else + for (j = 0; j < MAXLIGHTMAPS && (f->styles[j] != 255); j++) +#endif + { + #ifdef ZHLT_XASH + // reflect the direction back + vec_t dot = DotProduct (subsampled2_direction[j], pointnormal); + VectorMA (subsampled2_direction[j], -dot * 2, pointnormal, subsampled2_direction[j]); + vec_t front = 0, back = 0; + // calculate the change of brightness and adjust the direction. This is not totally right when the translucent value is not grayscale, + // but at least it still preserves the condition that VectorLength(light_direction) <= VectorAvg(light). + for (int x = 0; x < 3; x++) + { + front += ((1.0 - l.translucent_v[x]) * subsampled[j][x]) / 3; + back += (l.translucent_v[x] * subsampled2[j][x]) / 3; + } + front = fabs (VectorAvg (subsampled[j])) > NORMAL_EPSILON? front / VectorAvg (subsampled[j]): 0; + back = fabs (VectorAvg (subsampled2[j])) > NORMAL_EPSILON? back / VectorAvg (subsampled2[j]): 0; + #endif + for (int x = 0; x < 3; x++) + { + subsampled[j][x] = (1.0 - l.translucent_v[x]) * subsampled[j][x] + l.translucent_v[x] * subsampled2[j][x]; + #ifdef ZHLT_XASH + subsampled_direction[j][x] = front * subsampled_direction[j][x] + back * subsampled2_direction[j][x]; + #endif + } + } + } +#endif +#ifdef HLRAD_AUTOCORING + for (j = 0; j < ALLSTYLES && f_styles[j] != 255; j++) +#else + for (j = 0; j < MAXLIGHTMAPS && (f->styles[j] != 255); j++) +#endif + { + VectorScale(subsampled[j], weighting[s + 1][t + 1], subsampled[j]); + VectorAdd(sampled[j], subsampled[j], sampled[j]); +#ifdef ZHLT_XASH + VectorScale (subsampled_direction[j], weighting[s + 1][t + 1], subsampled_direction[j]); + VectorAdd (sampled_direction[j], subsampled_direction[j], sampled_direction[j]); +#endif + } +#ifdef ZHLT_XASH + VectorMA (sampled_normal, weighting[s + 1][t + 1], pointnormal, sampled_normal); +#endif + subsamples += weighting[s + 1][t + 1]; + } + } + } +#ifdef HLRAD_AUTOCORING + for (j = 0; j < ALLSTYLES && f_styles[j] != 255; j++) +#else + for (j = 0; j < MAXLIGHTMAPS && (f->styles[j] != 255); j++) +#endif + { + VectorScale(sampled[j], 1.0 / subsamples, sampled[j]); +#ifdef ZHLT_XASH + VectorScale (sampled_direction[j], 1.0 / subsamples, sampled_direction[j]); +#endif + } +#ifdef ZHLT_XASH + VectorScale (sampled_normal, 1.0 / subsamples, sampled_normal); + if (!VectorNormalize (sampled_normal)) + { + VectorCopy (l.facenormal, sampled_normal); + } +#endif + } + else + { +#ifdef HLRAD_PHONG_FROMORIGINAL + vec_t s_vec = l.texmins[0] * TEXTURE_STEP + (i % lightmapwidth) * TEXTURE_STEP; + vec_t t_vec = l.texmins[1] * TEXTURE_STEP + (i / lightmapwidth) * TEXTURE_STEP; + // this will generate smoother light for cylinders partially embedded in solid, + vec3_t pos_original; + SetSurfFromST (&l, pos_original, s_vec, t_vec); + { + // adjust sample's offset to 0 + vec_t scale; + scale = DotProduct (l.texnormal, l.facenormal); + VectorMA (pos_original, - DEFAULT_HUNT_OFFSET / scale, l.texnormal, pos_original); + } + GetPhongNormal(facenum, pos_original, pointnormal); +#else + GetPhongNormal(facenum, spot, pointnormal); +#endif +#ifdef HLRAD_CalcPoints_NEW + bool blocked = l.surfpt_lightoutside[i]; + if (!blocked) + { +#endif + GatherSampleLight(spot, pvs, pointnormal, sampled, +#ifdef ZHLT_XASH + sampled_direction, +#endif +#ifdef HLRAD_AUTOCORING + f_styles +#else + f->styles +#endif +#ifdef HLRAD_GatherPatchLight + , 0 +#endif +#ifdef HLRAD_DIVERSE_LIGHTING + , l.miptex +#endif +#ifdef HLRAD_TEXLIGHTGAP + , facenum +#endif + ); +#ifdef HLRAD_CalcPoints_NEW + } +#endif +#ifdef HLRAD_TRANSLUCENT + if (l.translucent_b) + { +#ifdef HLRAD_AUTOCORING + vec3_t sampled2[ALLSTYLES]; +#ifdef ZHLT_XASH + vec3_t sampled2_direction[ALLSTYLES]; +#endif + for (j = 0; j < ALLSTYLES; j++) +#else + vec3_t sampled2[MAXLIGHTMAPS]; + for (j = 0; j < MAXLIGHTMAPS; j++) +#endif + { + VectorFill(sampled2[j], 0); +#ifdef ZHLT_XASH + VectorFill (sampled2_direction[j], 0); +#endif + } + VectorSubtract (vec3_origin, pointnormal, normal2); + GatherSampleLight(spot2, pvs2, normal2, sampled2, +#ifdef ZHLT_XASH + sampled2_direction, +#endif +#ifdef HLRAD_AUTOCORING + f_styles +#else + f->styles +#endif + #ifdef HLRAD_GatherPatchLight + , 0 + #endif +#ifdef HLRAD_DIVERSE_LIGHTING + , l.miptex +#endif +#ifdef HLRAD_TEXLIGHTGAP + , facenum +#endif + ); +#ifdef HLRAD_AUTOCORING + for (j = 0; j < ALLSTYLES && f_styles[j] != 255; j++) +#else + for (j = 0; j < MAXLIGHTMAPS && (f->styles[j] != 255); j++) +#endif + { + #ifdef ZHLT_XASH + vec_t dot = DotProduct (sampled2_direction[j], pointnormal); + VectorMA (sampled2_direction[j], -dot * 2, pointnormal, sampled2_direction[j]); + vec_t front = 0, back = 0; + for (int x = 0; x < 3; x++) + { + front += ((1.0 - l.translucent_v[x]) * sampled[j][x]) / 3; + back += (l.translucent_v[x] * sampled2[j][x]) / 3; + } + front = fabs (VectorAvg (sampled[j])) > NORMAL_EPSILON? front / VectorAvg (sampled[j]): 0; + back = fabs (VectorAvg (sampled2[j])) > NORMAL_EPSILON? back / VectorAvg (sampled2[j]): 0; + #endif + for (int x = 0; x < 3; x++) + { + sampled[j][x] = (1.0 - l.translucent_v[x]) * sampled[j][x] + l.translucent_v[x] * sampled2[j][x]; + #ifdef ZHLT_XASH + sampled_direction[j][x] = front * sampled_direction[j][x] + back * sampled2_direction[j][x]; + #endif + } + } + } +#endif +#ifdef ZHLT_XASH + VectorCopy (pointnormal, sampled_normal); +#endif + } + +#ifdef HLRAD_AUTOCORING + for (j = 0; j < ALLSTYLES && f_styles[j] != 255; j++) +#else + for (j = 0; j < MAXLIGHTMAPS && (f->styles[j] != 255); j++) +#endif + { +#ifdef HLRAD_AUTOCORING + VectorCopy (sampled[j], fl_samples[j][i].light); +#ifdef ZHLT_XASH + VectorCopy (sampled_direction[j], fl_samples[j][i].light_direction); +#endif +#else + VectorCopy(sampled[j], facelight[facenum].samples[j][i].light); +#endif + +#ifndef HLRAD_TRANCPARENCYLOSS_FIX +#ifdef HLRAD_HULLU + if(b_transparency_loss) + { +#ifdef HLRAD_AUTOCORING + VectorScale (fl_samples[j][i].light, light_left_for_facelight, fl_samples[j][i].light); +#ifdef ZHLT_XASH + VectorScale (fl_samples[j][i].light_direction, light_left_for_facelight, fl_samples[j][i].light_direction); +#endif +#else + VectorScale(facelight[facenum].samples[j][i].light, light_left_for_facelight, facelight[facenum].samples[j][i].light); +#endif + } +#endif +#endif + +#ifndef HLRAD_ACCURATEBOUNCE_SAMPLELIGHT +#ifdef ZHLT_TEXLIGHT + #ifdef HLRAD_AUTOCORING + AddSampleToPatch (&fl_samples[j][i], facenum, f_styles[j]); //LRC + #else + AddSampleToPatch(&facelight[facenum].samples[j][i], facenum, f->styles[j]); //LRC + #endif +#else + if (f->styles[j] == 0) + { + AddSampleToPatch(&facelight[facenum].samples[j][i], facenum); + } +#endif +#endif + } +#ifdef ZHLT_XASH + for (k = 0; k < ALLSTYLES; k++) // fill 'sample.normal' for all 64 styles, just like what we did on 'sample.pos' + { + VectorCopy (sampled_normal, fl_samples[k][i].normal); + } +#endif +#endif // ifndef HLRAD_BLUR + } // end of i loop +#ifdef HLRAD_AVOIDWALLBLEED + free (sample_wallflags); +#endif + + // average up the direct light on each patch for radiosity +#ifdef HLRAD_ACCURATEBOUNCE_SAMPLELIGHT + AddSamplesToPatches ((const sample_t **)fl_samples, f_styles, facenum, &l); +#endif +#ifndef HLRAD_GatherPatchLight + if (g_numbounce > 0) +#endif + { + for (patch = g_face_patches[facenum]; patch; patch = patch->next) + { +#ifdef ZHLT_TEXLIGHT + //LRC: + unsigned istyle; + #ifdef HLRAD_AUTOCORING + #ifdef HLRAD_ACCURATEBOUNCE_SAMPLELIGHT + if (patch->samples <= ON_EPSILON * ON_EPSILON) + patch->samples = 0.0; + #endif + if (patch->samples) + { + for (istyle = 0; istyle < ALLSTYLES && patch->totalstyle_all[istyle] != 255; istyle++) + { + vec3_t v; + VectorScale (patch->samplelight_all[istyle], 1.0f / patch->samples, v); + VectorAdd (patch->directlight_all[istyle], v, patch->directlight_all[istyle]); + #ifdef ZHLT_XASH + VectorScale (patch->samplelight_all_direction[istyle], 1.0f / patch->samples, v); + VectorAdd (patch->directlight_all_direction[istyle], v, patch->directlight_all_direction[istyle]); + #endif + } + } + #else + for (istyle = 0; istyle < MAXLIGHTMAPS && patch->totalstyle[istyle] != 255; istyle++) + { + if (patch->samples[istyle]) + { + vec3_t v; // BUGBUG: Use a weighted average instead? + + VectorScale(patch->samplelight[istyle], (1.0f / patch->samples[istyle]), v); + VectorAdd(patch->totallight[istyle], v, patch->totallight[istyle]); + VectorAdd(patch->directlight[istyle], v, patch->directlight[istyle]); + } + } + #endif + //LRC (ends) +#else + if (patch->samples) + { + vec3_t v; // BUGBUG: Use a weighted average instead? + + VectorScale(patch->samplelight, (1.0f / patch->samples), v); + VectorAdd(patch->totallight, v, patch->totallight); + VectorAdd(patch->directlight, v, patch->directlight); + } +#endif + } + } +#ifdef HLRAD_GatherPatchLight + for (patch = g_face_patches[facenum]; patch; patch = patch->next) + { + // get the PVS for the pos to limit the number of checks + if (!g_visdatasize) + { + #ifdef ZHLT_DecompressVis_FIX + memset (pvs, 255, (g_dmodels[0].visleafs + 7) / 8); + #else + memset(pvs, 255, (g_numleafs + 7) / 8); + #endif + lastoffset = -1; + } + else + { + dleaf_t* leaf = PointInLeaf(patch->origin); + + thisoffset = leaf->visofs; +#ifdef HLRAD_BLUR + if (patch == g_face_patches[facenum] || thisoffset != lastoffset) +#else + if (l.numsurfpt == 0 || thisoffset != lastoffset) +#endif + { + #ifdef HLRAD_VIS_FIX + if (thisoffset == -1) + { + #ifdef ZHLT_DecompressVis_FIX + memset (pvs, 0, (g_dmodels[0].visleafs + 7) / 8); + #else + memset(pvs, 0, (g_numleafs + 7) / 8); + #endif + } + else + { + DecompressVis(&g_dvisdata[leaf->visofs], pvs, sizeof(pvs)); + } + #else + hlassert(thisoffset != -1); + DecompressVis(&g_dvisdata[leaf->visofs], pvs, sizeof(pvs)); + #endif + } + lastoffset = thisoffset; + } +#ifdef HLRAD_TRANSLUCENT + if (l.translucent_b) + { + if (!g_visdatasize) + { + #ifdef ZHLT_DecompressVis_FIX + memset (pvs2, 255, (g_dmodels[0].visleafs + 7) / 8); + #else + memset(pvs2, 255, (g_numleafs + 7) / 8); + #endif + lastoffset2 = -1; + } + else + { + VectorMA (patch->origin, -(g_translucentdepth+2*PATCH_HUNT_OFFSET), l.facenormal, spot2); + dleaf_t* leaf2 = PointInLeaf(spot2); + + thisoffset2 = leaf2->visofs; + if (l.numsurfpt == 0 || thisoffset2 != lastoffset2) + { + #ifdef HLRAD_VIS_FIX + if (thisoffset2 == -1) + { + #ifdef ZHLT_DecompressVis_FIX + memset (pvs2, 0, (g_dmodels[0].visleafs + 7) / 8); + #else + memset(pvs2, 0, (g_numleafs + 7) / 8); + #endif + } + else + { + DecompressVis(&g_dvisdata[leaf2->visofs], pvs2, sizeof(pvs2)); + } + #else + hlassert(thisoffset2 != -1); + DecompressVis(&g_dvisdata[leaf2->visofs], pvs2, sizeof(pvs2)); + #endif + } + lastoffset2 = thisoffset2; + } + #ifdef HLRAD_AUTOCORING + vec3_t frontsampled[ALLSTYLES], backsampled[ALLSTYLES]; + #ifdef ZHLT_XASH + vec3_t frontsampled_direction[ALLSTYLES], backsampled_direction[ALLSTYLES]; + #endif + for (j = 0; j < ALLSTYLES; j++) + #else + vec3_t frontsampled[MAXLIGHTMAPS], backsampled[MAXLIGHTMAPS]; + for (j = 0; j < MAXLIGHTMAPS; j++) + #endif + { + VectorClear (frontsampled[j]); + VectorClear (backsampled[j]); + #ifdef ZHLT_XASH + VectorClear (frontsampled_direction[j]); + VectorClear (backsampled_direction[j]); + #endif + } + VectorSubtract (vec3_origin, l.facenormal, normal2); + GatherSampleLight (patch->origin, pvs, l.facenormal, frontsampled, + #ifdef ZHLT_XASH + frontsampled_direction, + #endif + #ifdef HLRAD_AUTOCORING + patch->totalstyle_all + #else + patch->totalstyle + #endif + , 1 + #ifdef HLRAD_DIVERSE_LIGHTING + , l.miptex + #endif + #ifdef HLRAD_TEXLIGHTGAP + , facenum + #endif + ); + GatherSampleLight (spot2, pvs2, normal2, backsampled, + #ifdef ZHLT_XASH + backsampled_direction, + #endif + #ifdef HLRAD_AUTOCORING + patch->totalstyle_all + #else + patch->totalstyle + #endif + , 1 + #ifdef HLRAD_DIVERSE_LIGHTING + , l.miptex + #endif + #ifdef HLRAD_TEXLIGHTGAP + , facenum + #endif + ); + #ifdef HLRAD_AUTOCORING + for (j = 0; j < ALLSTYLES && patch->totalstyle_all[j] != 255; j++) + #else + for (j = 0; j < MAXLIGHTMAPS && (patch->totalstyle[j] != 255); j++) + #endif + { + #ifdef ZHLT_XASH + vec_t dot = DotProduct (backsampled_direction[j], l.facenormal); + VectorMA (backsampled_direction[j], -dot * 2, l.facenormal, backsampled_direction[j]); + vec_t front = 0, back = 0; + for (int x = 0; x < 3; x++) + { + front += ((1.0 - l.translucent_v[x]) * frontsampled[j][x]) / 3; + back += (l.translucent_v[x] * backsampled[j][x]) / 3; + } + front = fabs (VectorAvg (frontsampled[j])) > NORMAL_EPSILON? front / VectorAvg (frontsampled[j]): 0; + back = fabs (VectorAvg (backsampled[j])) > NORMAL_EPSILON? back / VectorAvg (backsampled[j]): 0; + #endif + for (int x = 0; x < 3; x++) + { + #ifdef HLRAD_AUTOCORING + patch->totallight_all[j][x] += (1.0 - l.translucent_v[x]) * frontsampled[j][x] + l.translucent_v[x] * backsampled[j][x]; + #ifdef ZHLT_XASH + patch->totallight_all_direction[j][x] += front * frontsampled_direction[j][x] + back * backsampled_direction[j][x]; + #endif + #else + patch->totallight[j][x] += (1.0 - l.translucent_v[x]) * frontsampled[j][x] + l.translucent_v[x] * backsampled[j][x]; + #endif + } + } + } + else + { + GatherSampleLight (patch->origin, pvs, l.facenormal, + #ifdef HLRAD_AUTOCORING + patch->totallight_all, + #ifdef ZHLT_XASH + patch->totallight_all_direction, + #endif + patch->totalstyle_all + #else + patch->totallight, patch->totalstyle + #endif + , 1 + #ifdef HLRAD_DIVERSE_LIGHTING + , l.miptex + #endif + #ifdef HLRAD_TEXLIGHTGAP + , facenum + #endif + ); + } +#else + GatherSampleLight (patch->origin, pvs, l.facenormal, + #ifdef HLRAD_AUTOCORING + patch->totallight_all, + #ifdef ZHLT_XASH + patch->totallight_all_direction, + #endif + patch->totalstyle_all + #else + patch->totallight, patch->totalstyle + #endif + , 1 + #ifdef HLRAD_DIVERSE_LIGHTING + , l.miptex + #endif + #ifdef HLRAD_TEXLIGHTGAP + , facenum + #endif + ); +#endif + } +#endif + + // add an ambient term if desired + if (g_ambient[0] || g_ambient[1] || g_ambient[2]) + { +#ifdef HLRAD_AUTOCORING + for (j = 0; j < ALLSTYLES && f_styles[j] != 255; j++) + { + if (f_styles[j] == 0) + { + s = fl_samples[j]; +#else + for (j = 0; j < MAXLIGHTMAPS && f->styles[j] != 255; j++) + { + if (f->styles[j] == 0) + { + s = facelight[facenum].samples[j]; +#endif + for (i = 0; i < l.numsurfpt; i++, s++) + { + VectorAdd(s->light, g_ambient, s->light); +#ifdef ZHLT_XASH + vec_t avg = VectorAvg (g_ambient); + VectorMA (s->light_direction, -DIFFUSE_DIRECTION_SCALE * avg, s->normal, s->light_direction); +#endif + } + break; + } + } + + } + + // add circus lighting for finding black lightmaps + if (g_circus) + { +#ifdef HLRAD_AUTOCORING + for (j = 0; j < ALLSTYLES && f_styles[j] != 255; j++) + { + if (f_styles[j] == 0) + { +#else + for (j = 0; j < MAXLIGHTMAPS && f->styles[j] != 255; j++) + { + if (f->styles[j] == 0) + { +#endif + int amt = 7; + +#ifdef HLRAD_AUTOCORING + s = fl_samples[j]; +#else + s = facelight[facenum].samples[j]; +#endif + + while ((l.numsurfpt % amt) == 0) + { + amt--; + } + if (amt < 2) + { + amt = 7; + } + + for (i = 0; i < l.numsurfpt; i++, s++) + { + if ((s->light[0] == 0) && (s->light[1] == 0) && (s->light[2] == 0)) + { + VectorAdd(s->light, s_circuscolors[i % amt], s->light); + } + } + break; + } + } + } + + // light from dlight_threshold and above is sent out, but the + // texture itself should still be full bright + + // if( VectorAvg( face_patches[facenum]->baselight ) >= dlight_threshold) // Now all lighted surfaces glow + { +#ifdef ZHLT_TEXLIGHT + //LRC: + if (g_face_patches[facenum]) + { + #ifdef HLRAD_AUTOCORING + for (j = 0; j < ALLSTYLES && f_styles[j] != 255; j++) + { + if (f_styles[j] == g_face_patches[facenum]->emitstyle) + { + break; + } + } + if (j == ALLSTYLES) + #else + for (j = 0; j < MAXLIGHTMAPS && f->styles[j] != 255; j++) + { + if (f->styles[j] == g_face_patches[facenum]->emitstyle) //LRC + { + break; + } + } + + if (j == MAXLIGHTMAPS) + #endif + { +#ifdef HLRAD_READABLE_EXCEEDSTYLEWARNING + if (++stylewarningcount >= stylewarningnext) + { + stylewarningnext = stylewarningcount * 2; + Warning("Too many direct light styles on a face(?,?,?)"); + Warning(" total %d warnings for too many styles", stylewarningcount); + } +#else + Warning("Too many direct light styles on a face(?,?,?)"); +#endif + } + else + { + #ifdef HLRAD_AUTOCORING + if (f_styles[j] == 255) + { + f_styles[j] = g_face_patches[facenum]->emitstyle; + } + + s = fl_samples[j]; + #else + if (f->styles[j] == 255) + { + f->styles[j] = g_face_patches[facenum]->emitstyle; + } + + s = facelight[facenum].samples[j]; + #endif + for (i = 0; i < l.numsurfpt; i++, s++) + { + VectorAdd(s->light, g_face_patches[facenum]->baselight, s->light); +#ifdef ZHLT_XASH + vec_t avg = VectorAvg (g_face_patches[facenum]->baselight); + VectorMA (s->light_direction, -DIFFUSE_DIRECTION_SCALE * avg, s->normal, s->light_direction); +#endif + } + } + } + //LRC (ends) +#else + for (j = 0; j < MAXLIGHTMAPS && f->styles[j] != 255; j++) + { + if (f->styles[j] == 0) + { + if (g_face_patches[facenum]) + { + s = facelight[facenum].samples[j]; + for (i = 0; i < l.numsurfpt; i++, s++) + { + VectorAdd(s->light, g_face_patches[facenum]->baselight, s->light); + } + break; + } + } + } +#endif + } +#ifdef HLRAD_AUTOCORING + // samples + { + facelight_t *fl = &facelight[facenum]; + vec_t maxlights[ALLSTYLES]; + for (j = 0; j < ALLSTYLES && f_styles[j] != 255; j++) + { + maxlights[j] = 0; + for (i = 0; i < fl->numsamples; i++) + { + vec_t b = VectorMaximum (fl_samples[j][i].light); + maxlights[j] = qmax (maxlights[j], b); + } + if (maxlights[j] <= g_corings[f_styles[j]] * 0.1) // light is too dim, discard this style to reduce RAM usage + { + if (maxlights[j] > g_maxdiscardedlight + NORMAL_EPSILON) + { + ThreadLock (); + if (maxlights[j] > g_maxdiscardedlight + NORMAL_EPSILON) + { + g_maxdiscardedlight = maxlights[j]; + VectorCopy (g_face_centroids[facenum], g_maxdiscardedpos); + } + ThreadUnlock (); + } + maxlights[j] = 0; + } + } + for (k = 0; k < MAXLIGHTMAPS; k++) + { + int bestindex = -1; + if (k == 0) + { + bestindex = 0; + } + else + { + vec_t bestmaxlight = 0; + for (j = 1; j < ALLSTYLES && f_styles[j] != 255; j++) + { + if (maxlights[j] > bestmaxlight + NORMAL_EPSILON) + { + bestmaxlight = maxlights[j]; + bestindex = j; + } + } + } + if (bestindex != -1) + { + maxlights[bestindex] = 0; + f->styles[k] = f_styles[bestindex]; + fl->samples[k] = (sample_t *)malloc (fl->numsamples * sizeof (sample_t)); + hlassume (fl->samples[k] != NULL, assume_NoMemory); + memcpy (fl->samples[k], fl_samples[bestindex], fl->numsamples * sizeof (sample_t)); + } + else + { + f->styles[k] = 255; + fl->samples[k] = NULL; + } + } + for (j = 1; j < ALLSTYLES && f_styles[j] != 255; j++) + { + if (maxlights[j] > g_maxdiscardedlight + NORMAL_EPSILON) + { + ThreadLock (); + if (maxlights[j] > g_maxdiscardedlight + NORMAL_EPSILON) + { + g_maxdiscardedlight = maxlights[j]; + VectorCopy (g_face_centroids[facenum], g_maxdiscardedpos); + } + ThreadUnlock (); + } + } + for (j = 0; j < ALLSTYLES; j++) + { + free (fl_samples[j]); + } + } + // patches + for (patch = g_face_patches[facenum]; patch; patch = patch->next) + { + vec_t maxlights[ALLSTYLES]; + for (j = 0; j < ALLSTYLES && patch->totalstyle_all[j] != 255; j++) + { + maxlights[j] = VectorMaximum (patch->totallight_all[j]); + } + for (k = 0; k < MAXLIGHTMAPS; k++) + { + int bestindex = -1; + if (k == 0) + { + bestindex = 0; + } + else + { + vec_t bestmaxlight = 0; + for (j = 1; j < ALLSTYLES && patch->totalstyle_all[j] != 255; j++) + { + if (maxlights[j] > bestmaxlight + NORMAL_EPSILON) + { + bestmaxlight = maxlights[j]; + bestindex = j; + } + } + } + if (bestindex != -1) + { + maxlights[bestindex] = 0; + patch->totalstyle[k] = patch->totalstyle_all[bestindex]; + VectorCopy (patch->totallight_all[bestindex], patch->totallight[k]); + #ifdef ZHLT_XASH + VectorCopy (patch->totallight_all_direction[bestindex], patch->totallight_direction[k]); + #endif + } + else + { + patch->totalstyle[k] = 255; + } + } + for (j = 1; j < ALLSTYLES && patch->totalstyle_all[j] != 255; j++) + { + if (maxlights[j] > g_maxdiscardedlight + NORMAL_EPSILON) + { + ThreadLock (); + if (maxlights[j] > g_maxdiscardedlight + NORMAL_EPSILON) + { + g_maxdiscardedlight = maxlights[j]; + VectorCopy (patch->origin, g_maxdiscardedpos); + } + ThreadUnlock (); + } + } + for (j = 0; j < ALLSTYLES && patch->totalstyle_all[j] != 255; j++) + { + maxlights[j] = VectorMaximum (patch->directlight_all[j]); + } + for (k = 0; k < MAXLIGHTMAPS; k++) + { + int bestindex = -1; + if (k == 0) + { + bestindex = 0; + } + else + { + vec_t bestmaxlight = 0; + for (j = 1; j < ALLSTYLES && patch->totalstyle_all[j] != 255; j++) + { + if (maxlights[j] > bestmaxlight + NORMAL_EPSILON) + { + bestmaxlight = maxlights[j]; + bestindex = j; + } + } + } + if (bestindex != -1) + { + maxlights[bestindex] = 0; + patch->directstyle[k] = patch->totalstyle_all[bestindex]; + VectorCopy (patch->directlight_all[bestindex], patch->directlight[k]); + #ifdef ZHLT_XASH + VectorCopy (patch->directlight_all_direction[bestindex], patch->directlight_direction[k]); + #endif + } + else + { + patch->directstyle[k] = 255; + } + } + for (j = 1; j < ALLSTYLES && patch->totalstyle_all[j] != 255; j++) + { + if (maxlights[j] > g_maxdiscardedlight + NORMAL_EPSILON) + { + ThreadLock (); + if (maxlights[j] > g_maxdiscardedlight + NORMAL_EPSILON) + { + g_maxdiscardedlight = maxlights[j]; + VectorCopy (patch->origin, g_maxdiscardedpos); + } + ThreadUnlock (); + } + } + free (patch->totalstyle_all); + patch->totalstyle_all = NULL; + free (patch->samplelight_all); + patch->samplelight_all = NULL; + free (patch->totallight_all); + patch->totallight_all = NULL; + free (patch->directlight_all); + patch->directlight_all = NULL; + } +#endif +#ifdef HLRAD_BLUR + free (l.lmcache); +#ifdef ZHLT_XASH + free (l.lmcache_direction); +#endif +#ifdef HLRAD_AVOIDNORMALFLIP + free (l.lmcache_normal); +#endif +#ifdef HLRAD_AVOIDWALLBLEED + free (l.lmcache_wallflags); +#endif +#ifdef HLRAD_GROWSAMPLE + free (l.surfpt_position); + free (l.surfpt_surface); +#endif +#endif +} + +// ===================================================================================== +// PrecompLightmapOffsets +// ===================================================================================== +void PrecompLightmapOffsets() +{ + int facenum; + dface_t* f; + facelight_t* fl; + int lightstyles; + +#ifdef ZHLT_TEXLIGHT + int i; //LRC + patch_t* patch; //LRC +#endif + + g_lightdatasize = 0; +#ifdef ZHLT_XASH + g_dlitdatasize = 0; +#endif + + for (facenum = 0; facenum < g_numfaces; facenum++) + { + f = &g_dfaces[facenum]; + fl = &facelight[facenum]; + + if (g_texinfo[f->texinfo].flags & TEX_SPECIAL) + { + continue; // non-lit texture + } + +#ifdef HLRAD_ENTSTRIPRAD +#ifndef HLRAD_REDUCELIGHTMAP + if (IntForKey (g_face_entity[facenum], "zhlt_striprad")) + { + continue; + } +#endif +#endif + +#ifdef HLRAD_AUTOCORING + { + int i, j, k; + vec_t maxlights[ALLSTYLES]; + { + vec3_t maxlights1[ALLSTYLES]; + vec3_t maxlights2[ALLSTYLES]; + for (j = 0; j < ALLSTYLES; j++) + { + VectorClear (maxlights1[j]); + VectorClear (maxlights2[j]); + } + for (k = 0; k < MAXLIGHTMAPS && f->styles[k] != 255; k++) + { + for (i = 0; i < fl->numsamples; i++) + { + VectorCompareMaximum (maxlights1[f->styles[k]], fl->samples[k][i].light, maxlights1[f->styles[k]]); + } + } +#ifdef HLRAD_LOCALTRIANGULATION + int numpatches; + const int *patches; + GetTriangulationPatches (facenum, &numpatches, &patches); // collect patches and their neighbors + + for (i = 0; i < numpatches; i++) + { + patch = &g_patches[patches[i]]; +#else + for (patch = g_face_patches[facenum]; patch; patch = patch->next) + { +#endif + for (k = 0; k < MAXLIGHTMAPS && patch->totalstyle[k] != 255; k++) + { + VectorCompareMaximum (maxlights2[patch->totalstyle[k]], patch->totallight[k], maxlights2[patch->totalstyle[k]]); + } + } + for (j = 0; j < ALLSTYLES; j++) + { + vec3_t v; + VectorAdd (maxlights1[j], maxlights2[j], v); + maxlights[j] = VectorMaximum (v); + if (maxlights[j] <= g_corings[j] * 0.01) + { + if (maxlights[j] > g_maxdiscardedlight + NORMAL_EPSILON) + { + g_maxdiscardedlight = maxlights[j]; + VectorCopy (g_face_centroids[facenum], g_maxdiscardedpos); + } + maxlights[j] = 0; + } + } + } + unsigned char oldstyles[MAXLIGHTMAPS]; + sample_t *oldsamples[MAXLIGHTMAPS]; + for (k = 0; k < MAXLIGHTMAPS; k++) + { + oldstyles[k] = f->styles[k]; + oldsamples[k] = fl->samples[k]; + } + for (k = 0; k < MAXLIGHTMAPS; k++) + { + unsigned char beststyle = 255; + if (k == 0) + { + beststyle = 0; + } + else + { + vec_t bestmaxlight = 0; + for (j = 1; j < ALLSTYLES; j++) + { + if (maxlights[j] > bestmaxlight + NORMAL_EPSILON) + { + bestmaxlight = maxlights[j]; + beststyle = j; + } + } + } + if (beststyle != 255) + { + maxlights[beststyle] = 0; + f->styles[k] = beststyle; + fl->samples[k] = (sample_t *)malloc (fl->numsamples * sizeof (sample_t)); + hlassume (fl->samples[k] != NULL, assume_NoMemory); + for (i = 0; i < MAXLIGHTMAPS && oldstyles[i] != 255; i++) + { + if (oldstyles[i] == f->styles[k]) + { + break; + } + } + if (i < MAXLIGHTMAPS && oldstyles[i] != 255) + { + memcpy (fl->samples[k], oldsamples[i], fl->numsamples * sizeof (sample_t)); + } + else + { + memcpy (fl->samples[k], oldsamples[0], fl->numsamples * sizeof (sample_t)); // copy 'sample.pos' from style 0 to the new style - because 'sample.pos' is actually the same for all styles! (why did we decide to store it in many places?) + for (j = 0; j < fl->numsamples; j++) + { + VectorClear (fl->samples[k][j].light); + #ifdef ZHLT_XASH + VectorClear (fl->samples[k][j].light_direction); + #endif + } + } + } + else + { + f->styles[k] = 255; + fl->samples[k] = NULL; + } + } + for (j = 1; j < ALLSTYLES; j++) + { + if (maxlights[j] > g_maxdiscardedlight + NORMAL_EPSILON) + { + g_maxdiscardedlight = maxlights[j]; + VectorCopy (g_face_centroids[facenum], g_maxdiscardedpos); + } + } + for (k = 0; k < MAXLIGHTMAPS && oldstyles[k] != 255; k++) + { + free (oldsamples[k]); + } + } +#else +#ifdef ZHLT_TEXLIGHT + //LRC - find all the patch lightstyles, and add them to the ones used by this face +#ifdef HLRAD_STYLE_CORING + for (patch = g_face_patches[facenum]; patch; patch = patch->next) +#else + patch = g_face_patches[facenum]; + if (patch) +#endif + { + for (i = 0; i < MAXLIGHTMAPS && patch->totalstyle[i] != 255; i++) + { + for (lightstyles = 0; lightstyles < MAXLIGHTMAPS && f->styles[lightstyles] != 255; lightstyles++) + { + if (f->styles[lightstyles] == patch->totalstyle[i]) + break; + } + if (lightstyles == MAXLIGHTMAPS) + { +#ifdef HLRAD_READABLE_EXCEEDSTYLEWARNING + if (++stylewarningcount >= stylewarningnext) + { + stylewarningnext = stylewarningcount * 2; + Warning("Too many direct light styles on a face(?,?,?)\n"); + Warning(" total %d warnings for too many styles", stylewarningcount); + } +#else + Warning("Too many direct light styles on a face(?,?,?)\n"); +#endif + } + else if (f->styles[lightstyles] == 255) + { + f->styles[lightstyles] = patch->totalstyle[i]; +// Log("Face acquires new lightstyle %d at offset %d\n", f->styles[lightstyles], lightstyles); + } + } + } + //LRC (ends) +#endif +#endif + + for (lightstyles = 0; lightstyles < MAXLIGHTMAPS; lightstyles++) + { + if (f->styles[lightstyles] == 255) + { + break; + } + } + + if (!lightstyles) + { + continue; + } + + f->lightofs = g_lightdatasize; + g_lightdatasize += fl->numsamples * 3 * lightstyles; + hlassume (g_lightdatasize <= g_max_map_lightdata, assume_MAX_MAP_LIGHTING); //lightdata +#ifdef ZHLT_XASH + g_dlitdatasize += fl->numsamples * 3 * lightstyles; + hlassume (g_dlitdatasize < g_max_map_dlitdata, assume_MAX_MAP_LIGHTING); +#endif + + } +} +#ifdef HLRAD_REDUCELIGHTMAP +void ReduceLightmap () +{ + byte *oldlightdata = (byte *)malloc (g_lightdatasize); + hlassume (oldlightdata != NULL, assume_NoMemory); + memcpy (oldlightdata, g_dlightdata, g_lightdatasize); +#ifdef ZHLT_XASH + if (g_dlitdatasize != g_lightdatasize) + { + Error ("g_dlitdatasize != g_lightdatasize"); + } + byte *olddlitdata = (byte *)malloc (g_dlitdatasize); + hlassume (olddlitdata != NULL, assume_NoMemory); + memcpy (olddlitdata, g_ddlitdata, g_dlitdatasize); + g_dlitdatasize = 0; +#endif + g_lightdatasize = 0; + + int facenum; + for (facenum = 0; facenum < g_numfaces; facenum++) + { + dface_t *f = &g_dfaces[facenum]; + facelight_t *fl = &facelight[facenum]; + if (g_texinfo[f->texinfo].flags & TEX_SPECIAL) + { + continue; // non-lit texture + } +#ifdef HLRAD_ENTSTRIPRAD + // just need to zero the lightmap so that it won't contribute to lightdata size + if (IntForKey (g_face_entity[facenum], "zhlt_striprad")) + { + f->lightofs = g_lightdatasize; + for (int k = 0; k < MAXLIGHTMAPS; k++) + { + f->styles[k] = 255; + } + continue; + } +#endif +#if 0 //debug. --vluzacn + const char *lightmapcolor = ValueForKey (g_face_entity[facenum], "zhlt_rad"); + if (*lightmapcolor) + { + hlassume (MAXLIGHTMAPS == 4, assume_first); + int styles[4], values[4][3]; + if (sscanf (lightmapcolor, "%d=%d,%d,%d %d=%d,%d,%d %d=%d,%d,%d %d=%d,%d,%d" + , &styles[0], &values[0][0], &values[0][1], &values[0][2] + , &styles[1], &values[1][0], &values[1][1], &values[1][2] + , &styles[2], &values[2][0], &values[2][1], &values[2][2] + , &styles[3], &values[3][0], &values[3][1], &values[3][2] + ) != 16) + { + Error ("Bad value for 'zhlt_rad'."); + } + f->lightofs = g_lightdatasize; + int i, k; + for (k = 0; k < 4; k++) + { + f->styles[k] = 255; + } + for (k = 0; k < 4 && styles[k] != 255; k++) + { + f->styles[k] = styles[k]; + hlassume (g_lightdatasize + fl->numsamples * 3 <= g_max_map_lightdata, assume_MAX_MAP_LIGHTING); + #ifdef ZHLT_XASH + hlassume (g_dlitdatasize + fl->numsamples * 3 <= g_max_map_dlitdata, assume_MAX_MAP_LIGHTING); + #endif + for (i = 0; i < fl->numsamples; i++) + { + VectorCopy (values[k], (byte *)&g_dlightdata[g_lightdatasize + i * 3]); + #ifdef ZHLT_XASH + VectorFill ((byte *)&g_ddlitdata[g_lightdatasize + i * 3], 128); + #endif + } + g_lightdatasize += fl->numsamples * 3; + #ifdef ZHLT_XASH + g_dlitdatasize += fl->numsamples * 3; + #endif + } + continue; + } +#endif + if (f->lightofs == -1) + { + continue; + } + + int i, k; + int oldofs; + unsigned char oldstyles[MAXLIGHTMAPS]; + oldofs = f->lightofs; + f->lightofs = g_lightdatasize; + for (k = 0; k < MAXLIGHTMAPS; k++) + { + oldstyles[k] = f->styles[k]; + f->styles[k] = 255; + } + int numstyles = 0; + for (k = 0; k < MAXLIGHTMAPS && oldstyles[k] != 255; k++) + { + unsigned char maxb = 0; + for (i = 0; i < fl->numsamples; i++) + { + unsigned char *v = &oldlightdata[oldofs + fl->numsamples * 3 * k + i * 3]; + maxb = qmax (maxb, VectorMaximum (v)); + } + if (maxb <= 0) // black + { + continue; + } + f->styles[numstyles] = oldstyles[k]; + hlassume (g_lightdatasize + fl->numsamples * 3 * (numstyles + 1) <= g_max_map_lightdata, assume_MAX_MAP_LIGHTING); + memcpy (&g_dlightdata[f->lightofs + fl->numsamples * 3 * numstyles], &oldlightdata[oldofs + fl->numsamples * 3 * k], fl->numsamples * 3); +#ifdef ZHLT_XASH + hlassume (g_dlitdatasize + fl->numsamples * 3 * (numstyles + 1) <= g_max_map_dlitdata, assume_MAX_MAP_LIGHTING); + memcpy (&g_ddlitdata[f->lightofs + fl->numsamples * 3 * numstyles], &olddlitdata[oldofs + fl->numsamples * 3 * k], fl->numsamples * 3); +#endif + numstyles++; + } + g_lightdatasize += fl->numsamples * 3 * numstyles; +#ifdef ZHLT_XASH + g_dlitdatasize += fl->numsamples * 3 * numstyles; +#endif + } + free (oldlightdata); +#ifdef ZHLT_XASH + free (olddlitdata); +#endif +} +#endif + +#ifdef HLRAD_MDL_LIGHT_HACK + +// Change the sample light right under a mdl file entity's origin. +// Use this when "mdl" in shadow has incorrect brightness. + +const int MLH_MAXFACECOUNT = 16; +const int MLH_MAXSAMPLECOUNT = 4; +const vec_t MLH_LEFT = 0; +const vec_t MLH_RIGHT = 1; + +typedef struct +{ + vec3_t origin; + vec3_t floor; + struct + { + int num; + struct + { + bool exist; + int seq; + } + style[ALLSTYLES]; + struct + { + int num; + vec3_t pos; + unsigned char* (style[ALLSTYLES]); + } + sample[MLH_MAXSAMPLECOUNT]; + int samplecount; + } + face[MLH_MAXFACECOUNT]; + int facecount; +} mdllight_t; + +#ifdef HLRAD_MDL_LIGHT_HACK_NEW +int MLH_AddFace (mdllight_t *ml, int facenum) +{ + dface_t *f = &g_dfaces[facenum]; + int i, j; + for (i = 0; i < ml->facecount; i++) + { + if (ml->face[i].num == facenum) + { + return -1; + } + } + if (ml->facecount >= MLH_MAXFACECOUNT) + { + return -1; + } + i = ml->facecount; + ml->facecount++; + ml->face[i].num = facenum; + ml->face[i].samplecount = 0; + for (j = 0; j < ALLSTYLES; j++) + { + ml->face[i].style[j].exist = false; + } + for (j = 0; j < MAXLIGHTMAPS && f->styles[j] != 255; j++) + { + ml->face[i].style[f->styles[j]].exist = true; + ml->face[i].style[f->styles[j]].seq = j; + } + return i; +} +void MLH_AddSample (mdllight_t *ml, int facenum, int w, int h, int s, int t, const vec3_t pos) +{ + dface_t *f = &g_dfaces[facenum]; + int i, j; + int r = MLH_AddFace (ml, facenum); + if (r == -1) + { + return; + } + int size = w * h; + int num = s + w * t; + for (i = 0; i < ml->face[r].samplecount; i++) + { + if (ml->face[r].sample[i].num == num) + { + return; + } + } + if (ml->face[r].samplecount >= MLH_MAXSAMPLECOUNT) + { + return; + } + i = ml->face[r].samplecount; + ml->face[r].samplecount++; + ml->face[r].sample[i].num = num; + VectorCopy (pos, ml->face[r].sample[i].pos); + for (j = 0; j < ALLSTYLES; j++) + { + if (ml->face[r].style[j].exist) + { + ml->face[r].sample[i].style[j] = &g_dlightdata[f->lightofs + (num + size * ml->face[r].style[j].seq) * 3]; + } + } +} +void MLH_CalcExtents (const dface_t *f, int *texturemins, int *extents) +{ +#ifdef ZHLT_64BIT_FIX + int bmins[2]; + int bmaxs[2]; + int i; + + GetFaceExtents (f - g_dfaces, bmins, bmaxs); + for (i = 0; i < 2; i++) + { + texturemins[i] = bmins[i] * TEXTURE_STEP; + extents[i] = (bmaxs[i] - bmins[i]) * TEXTURE_STEP; + } +#else + float mins[2], maxs[2]; + int bmins[2], bmaxs[2]; + texinfo_t *tex; + tex = &g_texinfo[f->texinfo]; + mins[0] = mins[1] = 999999; + maxs[0] = maxs[1] = -99999; + int i; + for (i = 0; i < f->numedges; i++) + { + int e; + dvertex_t *v; + int j; + e = g_dsurfedges[f->firstedge + i]; + if (e >= 0) + { + v = &g_dvertexes[g_dedges[e].v[0]]; + } + else + { + v = &g_dvertexes[g_dedges[-e].v[1]]; + } + for (j = 0; j < 2; j++) + { + float val = v->point[0] * tex->vecs[j][0] + v->point[1] * tex->vecs[j][1] + + v->point[2] * tex->vecs[j][2] + tex->vecs[j][3]; + if (val < mins[j]) + { + mins[j] = val; + } + if (val > maxs[j]) + { + maxs[j] = val; + } + } + } + for (i = 0; i < 2; i++) + { + bmins[i] = floor (mins[i] / TEXTURE_STEP); + bmaxs[i] = ceil (maxs[i] / TEXTURE_STEP); + texturemins[i] = bmins[i] * TEXTURE_STEP; + extents[i] = (bmaxs[i] - bmins[i]) * TEXTURE_STEP; + } +#endif +} +void MLH_GetSamples_r (mdllight_t *ml, int nodenum, const float *start, const float *end) +{ + if (nodenum < 0) + return; + dnode_t *node = &g_dnodes[nodenum]; + dplane_t *plane; + float front, back, frac; + float mid[3]; + int side; + plane = &g_dplanes[node->planenum]; + front = DotProduct (start, plane->normal) - plane->dist; + back = DotProduct (end, plane->normal) - plane->dist; + side = front < 0; + if ((back < 0) == side) + { + MLH_GetSamples_r (ml, node->children[side], start, end); + return; + } + frac = front / (front - back); + mid[0] = start[0] + (end[0] - start[0]) * frac; + mid[1] = start[1] + (end[1] - start[1]) * frac; + mid[2] = start[2] + (end[2] - start[2]) * frac; + MLH_GetSamples_r (ml, node->children[side], start, mid); + if (ml->facecount > 0) + { + return; + } + { + int i; + for (i = 0; i < node->numfaces; i++) + { + dface_t *f = &g_dfaces[node->firstface + i]; + texinfo_t *tex = &g_texinfo[f->texinfo]; + const char *texname = GetTextureByNumber (f->texinfo); + if (!strncmp (texname, "sky", 3)) + { + continue; + } + if (f->lightofs == -1) + { + continue; + } + int s = (int)(DotProduct (mid, tex->vecs[0]) + tex->vecs[0][3]); + int t = (int)(DotProduct (mid, tex->vecs[1]) + tex->vecs[1][3]); + int texturemins[2], extents[2]; + MLH_CalcExtents (f, texturemins, extents); + if (s < texturemins[0] || t < texturemins[1]) + { + continue; + } + int ds = s - texturemins[0]; + int dt = t - texturemins[1]; + if (ds > extents[0] || dt > extents[1]) + { + continue; + } + ds >>= 4; + dt >>= 4; + MLH_AddSample (ml, node->firstface + i, extents[0] / TEXTURE_STEP + 1, extents[1] / TEXTURE_STEP + 1, ds, dt, mid); + break; + } + } + if (ml->facecount > 0) + { + VectorCopy (mid, ml->floor); + return; + } + MLH_GetSamples_r (ml, node->children[!side], mid, end); +} +void MLH_mdllightCreate (mdllight_t *ml) +{ + // code from Quake + float p[3]; + float end[3]; + ml->facecount = 0; + VectorCopy (ml->origin, ml->floor); + VectorCopy (ml->origin, p); + VectorCopy (ml->origin, end); + end[2] -= 2048; + MLH_GetSamples_r (ml, 0, p, end); +} +#else +void MLH_mdllightCreate (mdllight_t *ml) +{ + int i, j, k; + vec_t height, minheight = BOGUS_RANGE; + ml->facecount = 0; + for (i = 0; i < g_numfaces; ++i) + { + if (strcasecmp (ValueForKey (g_face_entity[i], "classname"), "worldspawn")) + continue; + const dface_t *f = &g_dfaces[i]; + const dplane_t *p = getPlaneFromFace(f); + Winding *w=new Winding (*f); + for (j = 0;j < w->m_NumPoints; j++) + { + VectorAdd(w->m_Points[j], g_face_offset[i], w->m_Points[j]); + } + vec3_t delta , sect; + VectorCopy (ml->origin, delta); + delta[2] -= BOGUS_RANGE; + if (intersect_linesegment_plane(p, ml->origin, delta, sect) && point_in_winding (*w, *p, sect)) + { + height = ml->origin[2] - sect[2]; + if (height >= 0 && height <= minheight) + minheight = height; + } + delete w; + } + VectorCopy (ml->origin, ml->floor); + ml->floor[2] -= minheight; + for (i = 0; i < g_numfaces; ++i) + { + if (strcasecmp (ValueForKey (g_face_entity[i], "classname"), "worldspawn")) + continue; + const dface_t *f = &g_dfaces[i]; + const dplane_t *p = getPlaneFromFace(f); + Winding *w=new Winding (*f); + if (g_texinfo[f->texinfo].flags & TEX_SPECIAL) + { + continue; // non-lit texture + } + for (j = 0;j < w->m_NumPoints; j++) + { + VectorAdd(w->m_Points[j], g_face_offset[i], w->m_Points[j]); + } + vec3_t delta , sect; + VectorCopy (ml->origin, delta); + delta[2] -= BOGUS_RANGE; + if (intersect_linesegment_plane(p, ml->origin, delta, sect) && VectorCompare (sect, ml->floor)) + { + bool inlightmap = false; + { + vec3_t v; + facesampleinfo_t *info = &facesampleinfo[i]; + int w = info->texsize[0] + 1; + int h = info->texsize[1] + 1; + vec_t vs, vt; + int s1, s2, t1, t2, s, t; + VectorCopy (ml->floor, v); + VectorSubtract (v, info->offset, v); + VectorSubtract (v, info->texorg, v); + vs = DotProduct (v, info->worldtotex[0]); + vt = DotProduct (v, info->worldtotex[1]); + s1 = (int)floor((vs-MLH_LEFT)/TEXTURE_STEP) - info->texmins[0]; + s2 = (int)floor((vs+MLH_RIGHT)/TEXTURE_STEP) - info->texmins[0]; + t1 = (int)floor((vt-MLH_LEFT)/TEXTURE_STEP) - info->texmins[1]; + t2 = (int)floor((vt+MLH_RIGHT)/TEXTURE_STEP) - info->texmins[1]; + for (s=s1; s<=s2; ++s) + for (t=t1; t<=t2; ++t) + if (s>=0 && s=0 && tfacecount < MLH_MAXFACECOUNT) + { + ml->face[ml->facecount].num = i; + ml->facecount++; + } + } + delete w; + } + for (i = 0; i < ml->facecount; ++i) + { + const dface_t *f = &g_dfaces[ml->face[i].num]; + for (j = 0; j < ALLSTYLES; ++j) + ml->face[i].style[j].exist = false; + for (j = 0; j < MAXLIGHTMAPS && f->styles[j] != 255; ++j) + { + ml->face[i].style[f->styles[j]].exist = true; + ml->face[i].style[f->styles[j]].seq = j; + } + ml->face[i].samplecount = 0; + if (j == 0) + continue; + + const facelight_t *fl=&facelight[ml->face[i].num]; + { + vec3_t v; + facesampleinfo_t *info = &facesampleinfo[ml->face[i].num]; + int w = info->texsize[0] + 1; + int h = info->texsize[1] + 1; + vec_t vs, vt; + int s1, s2, t1, t2, s, t; + VectorCopy (ml->floor, v); + VectorSubtract (v, info->offset, v); + VectorSubtract (v, info->texorg, v); + vs = DotProduct (v, info->worldtotex[0]); + vt = DotProduct (v, info->worldtotex[1]); + s1 = (int)floor((vs-MLH_LEFT)/TEXTURE_STEP) - info->texmins[0]; + s2 = (int)floor((vs+MLH_RIGHT)/TEXTURE_STEP) - info->texmins[0]; + t1 = (int)floor((vt-MLH_LEFT)/TEXTURE_STEP) - info->texmins[1]; + t2 = (int)floor((vt+MLH_RIGHT)/TEXTURE_STEP) - info->texmins[1]; + for (s=s1; s<=s2; ++s) + for (t=t1; t<=t2; ++t) + if (s>=0 && s=0 && tface[i].samplecount < MLH_MAXSAMPLECOUNT) + { + ml->face[i].sample[ml->face[i].samplecount].num = s + t * w; + VectorAdd (info->offset, info->texorg, v); + vs = TEXTURE_STEP * (s + info->texmins[0]); + vt = TEXTURE_STEP * (t + info->texmins[1]); + VectorMA (v, vs, info->textoworld[0], v); + VectorMA (v, vt, info->textoworld[1], v); + VectorCopy (v, ml->face[i].sample[ml->face[i].samplecount].pos); + ml->face[i].samplecount++; + } + } + + for (j = 0; j < ml->face[i].samplecount; ++j) + { + for (k = 0; k < ALLSTYLES; ++k) + if (ml->face[i].style[k].exist) + { + ml->face[i].sample[j].style[k] = + &g_dlightdata[f->lightofs + ml->face[i].style[k].seq * fl->numsamples * 3 + ml->face[i].sample[j].num * 3]; + } + } + } +} +#endif + +int MLH_CopyLight (const vec3_t from, const vec3_t to) +{ + int i, j, k, count = 0; + mdllight_t mlfrom, mlto; + VectorCopy (from, mlfrom.origin); + VectorCopy (to, mlto.origin); + MLH_mdllightCreate (&mlfrom); + MLH_mdllightCreate (&mlto); + if (mlfrom.facecount == 0 || mlfrom.face[0].samplecount == 0) + return -1; + for (i = 0; i < mlto.facecount; ++i) + for (j = 0; j < mlto.face[i].samplecount; ++j, ++count) + for (k = 0; k < ALLSTYLES; ++k) + if (mlto.face[i].style[k].exist && mlfrom.face[0].style[k].exist) + { + VectorCopy (mlfrom.face[0].sample[0].style[k],mlto.face[i].sample[j].style[k]); + Developer (DEVELOPER_LEVEL_SPAM, "Mdl Light Hack: face (%d) sample (%d) style (%d) position (%f,%f,%f)\n", + mlto.face[i].num, mlto.face[i].sample[j].num, k, + mlto.face[i].sample[j].pos[0], mlto.face[i].sample[j].pos[1], mlto.face[i].sample[j].pos[2]); + } + Developer (DEVELOPER_LEVEL_MESSAGE, "Mdl Light Hack: %d sample light copied from (%f,%f,%f) to (%f,%f,%f)\n", + count, mlfrom.floor[0], mlfrom.floor[1], mlfrom.floor[2], mlto.floor[0], mlto.floor[1], mlto.floor[2]); + return count; +} + +void MdlLightHack () +{ + int ient; + entity_t *ent1, *ent2; + vec3_t origin1, origin2; + const char *target; +#ifndef HLRAD_MDL_LIGHT_HACK_NEW + double start, end; +#endif + int used = 0, countent = 0, countsample = 0, r; +#ifndef HLRAD_MDL_LIGHT_HACK_NEW + start = I_FloatTime(); +#endif + for (ient = 0; ient < g_numentities; ++ient) + { + ent1 = &g_entities[ient]; + target = ValueForKey (ent1, "zhlt_copylight"); + if (!strcmp (target, "")) + continue; + used = 1; + ent2 = FindTargetEntity (target); + if (ent2 == NULL) + { + Warning ("target entity '%s' not found", target); + continue; + } + GetVectorForKey (ent1, "origin", origin1); + GetVectorForKey (ent2, "origin", origin2); + r = MLH_CopyLight (origin2, origin1); + if (r < 0) + Warning ("can not copy light from (%f,%f,%f)", origin2[0], origin2[1], origin2[2]); + else + { + countent += 1; + countsample += r; + } + } +#ifndef HLRAD_MDL_LIGHT_HACK_NEW + end = I_FloatTime(); +#endif + if (used) +#ifdef HLRAD_MDL_LIGHT_HACK_NEW + Log ("Adjust mdl light: modified %d samples for %d entities\n", countsample, countent); +#else + Log("Mdl Light Hack: %d entities %d samples (%.2f seconds)\n", countent, countsample, end - start); +#endif +} +#endif /*HLRAD_MDL_LIGHT_HACK*/ + +#ifdef HLRAD_GROWSAMPLE + +typedef struct facelightlist_s +{ + int facenum; + facelightlist_s *next; +} +facelightlist_t; + +static facelightlist_t *g_dependentfacelights[MAX_MAP_FACES]; + +// ===================================================================================== +// CreateFacelightDependencyList +// ===================================================================================== +void CreateFacelightDependencyList () +{ + int facenum; + dface_t *f; + facelight_t *fl; + int i; + int k; + int surface; + facelightlist_t *item; + + for (i = 0; i < MAX_MAP_FACES; i++) + { + g_dependentfacelights[i] = NULL; + } + + // for each face + for (facenum = 0; facenum < g_numfaces; facenum++) + { + f = &g_dfaces[facenum]; + fl = &facelight[facenum]; + if (g_texinfo[f->texinfo].flags & TEX_SPECIAL) + { + continue; + } + + for (k = 0; k < MAXLIGHTMAPS && f->styles[k] != 255; k++) + { + for (i = 0; i < fl->numsamples; i++) + { + surface = fl->samples[k][i].surface; // that surface contains at least one sample from this face + if (0 <= surface && surface < g_numfaces) + { + // insert this face into the dependency list of that surface + for (item = g_dependentfacelights[surface]; item != NULL; item = item->next) + { + if (item->facenum == facenum) + break; + } + if (item) + { + continue; + } + + item = (facelightlist_t *)malloc (sizeof (facelightlist_t)); + hlassume (item != NULL, assume_NoMemory); + item->facenum = facenum; + item->next = g_dependentfacelights[surface]; + g_dependentfacelights[surface] = item; + } + } + } + } +} + +// ===================================================================================== +// FreeFacelightDependencyList +// ===================================================================================== +void FreeFacelightDependencyList () +{ + int i; + facelightlist_t *item; + + for (i = 0; i < MAX_MAP_FACES; i++) + { + while (g_dependentfacelights[i]) + { + item = g_dependentfacelights[i]; + g_dependentfacelights[i] = item->next; + free (item); + } + } +} + +// ===================================================================================== +// ScaleDirectLights +// ===================================================================================== +void ScaleDirectLights () +{ + int facenum; + dface_t *f; + facelight_t *fl; + int i; + int k; + sample_t *samp; + + for (facenum = 0; facenum < g_numfaces; facenum++) + { + f = &g_dfaces[facenum]; + + if (g_texinfo[f->texinfo].flags & TEX_SPECIAL) + { + continue; + } + + fl = &facelight[facenum]; + + for (k = 0; k < MAXLIGHTMAPS && f->styles[k] != 255; k++) + { + for (i = 0; i < fl->numsamples; i++) + { + samp = &fl->samples[k][i]; + VectorScale (samp->light, g_direct_scale, samp->light); + #ifdef ZHLT_XASH + VectorScale (samp->light_direction, g_direct_scale, samp->light_direction); + #endif + } + } + } +} + +// ===================================================================================== +// AddPatchLights +// This function is run multithreaded +// ===================================================================================== +void AddPatchLights (int facenum) +{ + dface_t *f; +#ifndef HLRAD_LOCALTRIANGULATION + lerpTriangulation_t *trian; +#endif + facelightlist_t *item; + dface_t *f_other; + facelight_t *fl_other; + int k; + int i; + sample_t *samp; + + f = &g_dfaces[facenum]; + + if (g_texinfo[f->texinfo].flags & TEX_SPECIAL) + { + return; + } + +#ifndef HLRAD_LOCALTRIANGULATION + trian = CreateTriangulation (facenum); +#endif + + for (item = g_dependentfacelights[facenum]; item != NULL; item = item->next) + { + f_other = &g_dfaces[item->facenum]; + fl_other = &facelight[item->facenum]; + for (k = 0; k < MAXLIGHTMAPS && f_other->styles[k] != 255; k++) + { + for (i = 0; i < fl_other->numsamples; i++) + { + samp = &fl_other->samples[k][i]; + if (samp->surface != facenum) + { // the sample is not in this surface + continue; + } + + { + vec3_t v; + #ifdef ZHLT_XASH + vec3_t v_direction; + #endif + +#ifdef HLRAD_LOCALTRIANGULATION + int style = f_other->styles[k]; + InterpolateSampleLight (samp->pos, samp->surface, 1, &style, &v + #ifdef ZHLT_XASH + , &v_direction + #endif + ); +#else + SampleTriangulation (trian, samp->pos, v, + #ifdef ZHLT_XASH + v_direction, + #endif + f_other->styles[k]); //LRC +#endif + + #ifdef HLRAD_STYLE_CORING + VectorAdd (samp->light, v, v); + #ifdef ZHLT_XASH + VectorAdd (samp->light_direction, v_direction, v_direction); + #endif + if (VectorMaximum (v) >= g_corings[f_other->styles[k]]) + { + VectorCopy (v, samp->light); + #ifdef ZHLT_XASH + VectorCopy (v_direction, samp->light_direction); + #endif + } + else + { + #ifdef HLRAD_AUTOCORING + if (VectorMaximum (v) > g_maxdiscardedlight + NORMAL_EPSILON) + { + ThreadLock (); + if (VectorMaximum (v) > g_maxdiscardedlight + NORMAL_EPSILON) + { + g_maxdiscardedlight = VectorMaximum (v); + VectorCopy (samp->pos, g_maxdiscardedpos); + } + ThreadUnlock (); + } + #endif + } + #else + VectorAdd (samp->light, v, samp->light); + #endif + } + } // loop samples + } + } + +#ifndef HLRAD_LOCALTRIANGULATION + FreeTriangulation (trian); +#endif +} + +#endif /*HLRAD_GROWSAMPLE*/ +// ===================================================================================== +// FinalLightFace +// Add the indirect lighting on top of the direct lighting and save into final map format +// ===================================================================================== +void FinalLightFace(const int facenum) +{ +#ifdef HLRAD_DEBUG_DRAWPOINTS + if (facenum == 0 && g_drawsample) + { + char name[_MAX_PATH+20]; + sprintf (name, "%s_sample.pts", g_Mapname); + Log ("Writing '%s' ...\n", name); + FILE *f; + f = fopen(name, "w"); + if (f) + { + const int pos_count = 15; + const vec3_t pos[pos_count] = {{0,0,0},{1,0,0},{0,1,0},{-1,0,0},{0,-1,0},{1,0,0},{0,0,1},{-1,0,0},{0,0,-1},{0,-1,0},{0,0,1},{0,1,0},{0,0,-1},{1,0,0},{0,0,0}}; + int i, j, k; + vec3_t v, dist; + for (i = 0; i < g_numfaces; ++i) + { + const facelight_t *fl=&facelight[i]; + for (j = 0; j < fl->numsamples; ++j) + { + VectorCopy (fl->samples[0][j].pos, v); + VectorSubtract (v, g_drawsample_origin, dist); + if (DotProduct (dist, dist) < g_drawsample_radius * g_drawsample_radius) + { + for (k = 0; k < pos_count; ++k) + fprintf (f, "%g %g %g\n", v[0]+pos[k][0], v[1]+pos[k][1], v[2]+pos[k][2]); + } + } + } + fclose(f); + Log ("OK.\n"); + } + else + Log ("Error.\n"); + } +#endif + int i, j, k; + vec3_t lb, v; + facelight_t* fl; + sample_t* samp; + float minlight; + int lightstyles; + dface_t* f; +#ifndef HLRAD_GROWSAMPLE + lerpTriangulation_t* trian = NULL; +#endif +#ifdef HLRAD_FinalLightFace_VL + vec3_t *original_basiclight; + int (*final_basiclight)[3]; + int lbi[3]; +#ifdef ZHLT_XASH + vec3_t *original_basicdirection; + vec3_t *final_basicdirection; + vec3_t direction; + vec3_t directionnormals[3]; + vec3_t debug_original_light; + vec3_t debug_final_light; + vec3_t debug_original_direction; + vec3_t debug_final_direction; +#endif +#endif + + // ------------------------------------------------------------------------ + // Changes by Adam Foster - afoster@compsoc.man.ac.uk +#ifdef HLRAD_WHOME + float temp_rand; +#endif + // ------------------------------------------------------------------------ + + f = &g_dfaces[facenum]; + fl = &facelight[facenum]; + + if (g_texinfo[f->texinfo].flags & TEX_SPECIAL) + { + return; // non-lit texture + } + +#ifdef HLRAD_ENTSTRIPRAD +#ifndef HLRAD_REDUCELIGHTMAP + if (IntForKey (g_face_entity[facenum], "zhlt_striprad")) + { + return; + } +#endif +#endif + + for (lightstyles = 0; lightstyles < MAXLIGHTMAPS; lightstyles++) + { + if (f->styles[lightstyles] == 255) + { + break; + } + } + + if (!lightstyles) + { + return; + } + + // + // set up the triangulation + // +#ifndef HLRAD_GROWSAMPLE +#ifndef HLRAD_GatherPatchLight + if (g_numbounce) +#endif + { + trian = CreateTriangulation(facenum); + } +#endif + // + // sample the triangulation + // + minlight = FloatForKey(g_face_entity[facenum], "_minlight") * 128; + +#ifdef HLRAD_FinalLightFace_VL + original_basiclight = (vec3_t *)calloc (fl->numsamples, sizeof(vec3_t)); + final_basiclight = (int (*)[3])calloc (fl->numsamples, sizeof(int [3])); + hlassume (original_basiclight != NULL, assume_NoMemory); + hlassume (final_basiclight != NULL, assume_NoMemory); +#ifdef ZHLT_XASH + original_basicdirection = (vec3_t *)calloc (fl->numsamples, sizeof(vec3_t)); + final_basicdirection = (vec3_t *)calloc (fl->numsamples, sizeof (vec3_t)); + hlassume (original_basicdirection != NULL, assume_NoMemory); + hlassume (final_basicdirection != NULL, assume_NoMemory); +#endif +#endif + for (k = 0; k < lightstyles; k++) + { + samp = fl->samples[k]; + for (j = 0; j < fl->numsamples; j++, samp++) + { +#ifdef ZHLT_XASH + { + + VectorCopy (samp->normal, directionnormals[2]); + + vec3_t texdirections[2]; + const vec3_t &facenormal = getPlaneFromFace (f)->normal; + texinfo_t *tx = &g_texinfo[f->texinfo]; + for (int side = 0; side < 2; side++) + { + CrossProduct (facenormal, tx->vecs[!side], texdirections[side]); + VectorNormalize (texdirections[side]); + if (DotProduct (texdirections[side], tx->vecs[side]) < 0) + { + VectorSubtract (vec3_origin, texdirections[side], texdirections[side]); + } + } + + for (int side = 0; side < 2; side++) + { + vec_t dot; + dot = DotProduct (texdirections[side], samp->normal); + VectorMA (texdirections[side], -dot, samp->normal, directionnormals[side]); + VectorNormalize (directionnormals[side]); + } + VectorSubtract (vec3_origin, directionnormals[1], directionnormals[1]); + } +#endif +#ifdef HLRAD_GROWSAMPLE + VectorCopy (samp->light, lb); + #ifdef ZHLT_XASH + VectorCopy (samp->light_direction, direction); + #endif +#else + // Should be a VectorCopy, but we scale by 2 to compensate for an earlier lighting flaw + // Specifically, the directlight contribution was included in the bounced light AND the directlight + // Since many of the levels were built with this assumption, this "fudge factor" compensates for it. + + // Default direct_scale has been changed from 2 to 1 and default scale has been changed from 1 to 2. --vluzacn + VectorScale(samp->light, g_direct_scale, lb); +#ifdef ZHLT_XASH + VectorScale (samp->light_direction, g_direct_scale, direction); + vec3_t v_direction; +#endif + +#ifdef ZHLT_TEXLIGHT +#ifndef HLRAD_GatherPatchLight + if (g_numbounce)//LRC && (k == 0)) +#endif + { + SampleTriangulation(trian, samp->pos, v, + #ifdef ZHLT_XASH + v_direction, + #endif + f->styles[k]); //LRC +#else + if ( +#ifndef HLRAD_GatherPatchLight + g_numbounce && +#endif + (k == 0)) + { + SampleTriangulation(trian, samp->pos, v); +#endif + + if (isPointFinite(v)) + { +#ifdef HLRAD_STYLE_CORING + VectorAdd (lb, v, v); + #ifdef ZHLT_XASH + VectorAdd (direction, v_direction, v_direction); + #endif + if (VectorMaximum (v) >= g_corings[f->styles[k]]) + { + VectorCopy (v, lb); + #ifdef ZHLT_XASH + VectorCopy (v_direction, direction); + #endif + } + #ifdef HLRAD_AUTOCORING + else + { + if (VectorMaximum (v) > g_maxdiscardedlight + NORMAL_EPSILON) + { + ThreadLock (); + if (VectorMaximum (v) > g_maxdiscardedlight + NORMAL_EPSILON) + { + g_maxdiscardedlight = VectorMaximum (v); + VectorCopy (samp->pos, g_maxdiscardedpos); + } + ThreadUnlock (); + } + } + #endif +#else + VectorAdd(lb, v, lb); +#endif + } + else + { + Warning("point (%4.3f %4.3f %4.3f) infinite v (%4.3f %4.3f %4.3f)\n", + samp->pos[0], samp->pos[1], samp->pos[2], v[0], v[1], v[2]); + } + + + } +#endif +#ifdef HLRAD_FinalLightFace_VL + if (f->styles[0] != 0) + { + Warning ("wrong f->styles[0]"); + } + VectorCompareMaximum (lb, vec3_origin, lb); + if (k == 0) + { + VectorCopy (lb, original_basiclight[j]); + #ifdef ZHLT_XASH + VectorCopy (direction, original_basicdirection[j]); + #endif + } + else + { + VectorAdd (lb, original_basiclight[j], lb); + #ifdef ZHLT_XASH + VectorAdd (direction, original_basicdirection[j], direction); + #endif + } + #ifdef ZHLT_XASH + { + VectorCopy (lb, debug_original_light); + VectorCopy (direction, debug_original_direction); + // get the real direction + // this is what the direction should be after style 0 and this style are added together + vec_t avg = VectorAvg (lb); + if (avg > NORMAL_EPSILON) + { + VectorScale (direction, 1 / avg, direction); + } + else + { + VectorClear (direction); + } + } + #endif +#endif + // ------------------------------------------------------------------------ + // Changes by Adam Foster - afoster@compsoc.man.ac.uk + // colour lightscale: +#ifdef HLRAD_WHOME + lb[0] *= g_colour_lightscale[0]; + lb[1] *= g_colour_lightscale[1]; + lb[2] *= g_colour_lightscale[2]; +#else + VectorScale(lb, g_lightscale, lb); +#endif + // ------------------------------------------------------------------------ + + // clip from the bottom first + for (i = 0; i < 3; i++) + { + if (lb[i] < minlight) + { + lb[i] = minlight; + } + } + +#ifndef HLRAD_FinalLightFace_VL + // clip from the top + { + vec_t max = VectorMaximum(lb); + + if (max > g_maxlight) + { + vec_t scale = g_maxlight / max; + + lb[0] *= scale; + lb[1] *= scale; + lb[2] *= scale; + } + } +#endif + + // ------------------------------------------------------------------------ + // Changes by Adam Foster - afoster@compsoc.man.ac.uk +#ifdef HLRAD_WHOME + + // AJM: your code is formatted really wierd, and i cant understand a damn thing. + // so i reformatted it into a somewhat readable "normal" fashion. :P + + if ( g_colour_qgamma[0] != 1.0 ) + lb[0] = (float) pow(lb[0] / 256.0f, g_colour_qgamma[0]) * 256.0f; + + if ( g_colour_qgamma[1] != 1.0 ) + lb[1] = (float) pow(lb[1] / 256.0f, g_colour_qgamma[1]) * 256.0f; + + if ( g_colour_qgamma[2] != 1.0 ) + lb[2] = (float) pow(lb[2] / 256.0f, g_colour_qgamma[2]) * 256.0f; + + // Two different ways of adding noise to the lightmap - colour jitter + // (red, green and blue channels are independent), and mono jitter + // (monochromatic noise). For simulating dithering, on the cheap. :) + + // Tends to create seams between adjacent polygons, so not ideal. + + // Got really weird results when it was set to limit values to 256.0f - it + // was as if r, g or b could wrap, going close to zero. + + #ifndef HLRAD_FinalLightFace_VL + if (g_colour_jitter_hack[0] || g_colour_jitter_hack[1] || g_colour_jitter_hack[2]) + { + for (i = 0; i < 3; i++) + { + lb[i] += g_colour_jitter_hack[i] * ((float)rand() / RAND_MAX - 0.5); + if (lb[i] < 0.0f) + { + lb[i] = 0.0f; + } + else if (lb[i] > 255.0f) + { + lb[i] = 255.0f; + } + } + } + + if (g_jitter_hack[0] || g_jitter_hack[1] || g_jitter_hack[2]) + { + temp_rand = (float)rand() / RAND_MAX - 0.5; + for (i = 0; i < 3; i++) + { + lb[i] += g_jitter_hack[i] * temp_rand; + if (lb[i] < 0.0f) + { + lb[i] = 0.0f; + } + else if (lb[i] > 255.0f) + { + lb[i] = 255.0f; + } + } + } + #endif +#else + if (g_qgamma != 1.0) { + for (i = 0; i < 3; i++) { + lb[i] = (float) pow(lb[i] / 256.0f, g_qgamma) * 256.0f; + } + } +#endif + +#ifdef HLRAD_PRESERVELIGHTMAPCOLOR + // clip from the top + { + vec_t max = VectorMaximum (lb); + if (g_limitthreshold >= 0 && max > g_limitthreshold) + { + if (!g_drawoverload) + { + VectorScale (lb, g_limitthreshold / max, lb); + } + } + else + { + if (g_drawoverload) + { + VectorScale (lb, 0.1, lb); // darken good points + } + } + } +#endif +#ifdef HLRAD_MINLIGHT + for (i = 0; i < 3; ++i) + if (lb[i] < g_minlight) + lb[i] = g_minlight; +#endif + // ------------------------------------------------------------------------ +#ifdef HLRAD_FinalLightFace_VL + for (i = 0; i < 3; ++i) + { + lbi[i] = (int) floor (lb[i] + 0.5); + if (lbi[i] < 0) lbi[i] = 0; + } + #ifdef ZHLT_XASH + { + vec_t avg = (lbi[0] + lbi[1] + lbi[2]) / 3.0; + VectorScale (direction, avg, direction); + VectorCopy (lbi, debug_final_light); + VectorCopy (direction, debug_final_direction); + } + #endif + if (k == 0) + { + VectorCopy (lbi, final_basiclight[j]); + #ifdef ZHLT_XASH + VectorCopy (direction, final_basicdirection[j]); + #endif + } + else + { + VectorSubtract (lbi, final_basiclight[j], lbi); + #ifdef ZHLT_XASH + VectorSubtract (direction, final_basicdirection[j], direction); + #endif + } + #ifdef ZHLT_XASH + { + // because the direction will be multiplied with the brightness when styles are added together, now divide the direction by the brightness + vec_t avg = (lbi[0] + lbi[1] + lbi[2]) / 3.0; + avg = qmax (1, avg); + VectorScale (direction, 1 / avg, direction); + } + #endif + #ifdef HLRAD_WHOME + if (k == 0) + { + if (g_colour_jitter_hack[0] || g_colour_jitter_hack[1] || g_colour_jitter_hack[2]) + for (i = 0; i < 3; i++) + lbi[i] += g_colour_jitter_hack[i] * ((float)rand() / RAND_MAX - 0.5); + if (g_jitter_hack[0] || g_jitter_hack[1] || g_jitter_hack[2]) + { + temp_rand = (float)rand() / RAND_MAX - 0.5; + for (i = 0; i < 3; i++) + lbi[i] += g_jitter_hack[i] * temp_rand; + } + } + #endif + for (i = 0; i < 3; ++i) + { + if (lbi[i] < 0) lbi[i] = 0; + if (lbi[i] > 255) lbi[i] = 255; + } + { + unsigned char* colors = &g_dlightdata[f->lightofs + k * fl->numsamples * 3 + j * 3]; + + colors[0] = (unsigned char)lbi[0]; + colors[1] = (unsigned char)lbi[1]; + colors[2] = (unsigned char)lbi[2]; + } + #ifdef ZHLT_XASH + { + vec3_t v; + VectorScale (direction, g_directionscale, v); // the scale is calculated such that length(v) < 1 + if (DotProduct (v, v) > 1 + NORMAL_EPSILON) + { + #if 0 + { + ThreadLock (); + printf ("facenum = %d sample = %d styleindex = %d\n", facenum, j, k); + printf ("original_basiclight = %f %f %f\n", (vec_t)original_basiclight[j][0], (vec_t)original_basiclight[j][1], (vec_t)original_basiclight[j][2]); + printf ("original_basicdirection = %f %f %f\n", (vec_t)original_basicdirection[j][0], (vec_t)original_basicdirection[j][1], (vec_t)original_basicdirection[j][2]); + printf ("final_basiclight = %f %f %f\n", (vec_t)final_basiclight[j][0], (vec_t)final_basiclight[j][1], (vec_t)final_basiclight[j][2]); + printf ("final_basicdirection = %f %f %f\n", (vec_t)final_basicdirection[j][0], (vec_t)final_basicdirection[j][1], (vec_t)final_basicdirection[j][2]); + printf ("debug_original_light = %f %f %f\n", (vec_t)debug_original_light[0], (vec_t)debug_original_light[1], (vec_t)debug_original_light[2]); + printf ("debug_original_direction = %f %f %f\n", (vec_t)debug_original_direction[0], (vec_t)debug_original_direction[1], (vec_t)debug_original_direction[2]); + printf ("debug_final_light = %f %f %f\n", (vec_t)debug_final_light[0], (vec_t)debug_final_light[1], (vec_t)debug_final_light[2]); + printf ("debug_final_direction = %f %f %f\n", (vec_t)debug_final_direction[0], (vec_t)debug_final_direction[1], (vec_t)debug_final_direction[2]); + ThreadUnlock (); + } + #endif + VectorNormalize (v); + } + VectorSubtract (vec3_origin, v, v); // let the direction point from face sample to light source + unsigned char *dots = &g_ddlitdata[f->lightofs + k * fl->numsamples * 3 + j * 3]; + for (int x = 0; x < 3; x++) + { + int i; + i = DotProduct (v, directionnormals[x]) * 128 + 128; + i = qmax (0, qmin (i, 255)); + dots[x] = (unsigned char)i; + } + } + #endif +#else + { + unsigned char* colors = &g_dlightdata[f->lightofs + k * fl->numsamples * 3 + j * 3]; + + colors[0] = (unsigned char)lb[0]; + colors[1] = (unsigned char)lb[1]; + colors[2] = (unsigned char)lb[2]; + } +#endif + } + } +#ifdef HLRAD_FinalLightFace_VL + free (original_basiclight); + free (final_basiclight); +#ifdef ZHLT_XASH + free (original_basicdirection); + free (final_basicdirection); +#endif +#endif + +#ifndef HLRAD_GROWSAMPLE +#ifndef HLRAD_GatherPatchLight + if (g_numbounce) +#endif + { + FreeTriangulation(trian); + } +#endif +} + + +#ifdef ZHLT_TEXLIGHT +//LRC +vec3_t totallight_default = { 0, 0, 0 }; +#ifdef ZHLT_XASH +vec3_t totallight_default_direction = { 0, 0, 0 }; +#endif + +//LRC - utility for getting the right totallight value from a patch +vec3_t* GetTotalLight(patch_t* patch, int style +#ifdef ZHLT_XASH + , const vec3_t *&direction_out +#endif + ) +{ + int i; + for (i = 0; i < MAXLIGHTMAPS && patch->totalstyle[i] != 255; i++) + { + if (patch->totalstyle[i] == style) + { +#ifdef ZHLT_XASH + direction_out = &(patch->totallight_direction[i]); +#endif + return &(patch->totallight[i]); + } + } +#ifdef ZHLT_XASH + direction_out = &totallight_default_direction; +#endif + return &totallight_default; +} + +#endif diff --git a/src/zhlt-vluzacn/hlrad/loadtextures.cpp b/src/zhlt-vluzacn/hlrad/loadtextures.cpp new file mode 100644 index 0000000..280ea7d --- /dev/null +++ b/src/zhlt-vluzacn/hlrad/loadtextures.cpp @@ -0,0 +1,1507 @@ +#include "qrad.h" +#ifdef HLRAD_TEXTURE + +#ifdef WORDS_BIGENDIAN +#error "HLRAD_TEXTURE doesn't support WORDS_BIGENDIAN, because I have no big endian machine to test it" +#endif + +int g_numtextures; +radtexture_t *g_textures; + +typedef struct waddir_s +{ + struct waddir_s *next; + char path[_MAX_PATH]; +} waddir_t; +waddir_t *g_waddirs = NULL; + +void AddWadFolder (const char *path) +{ + waddir_t *waddir; + waddir = (waddir_t *)malloc (sizeof (waddir_t)); + hlassume (waddir != NULL, assume_NoMemory); + { + waddir_t **pos; + for (pos = &g_waddirs; *pos; pos = &(*pos)->next) + ; + waddir->next = *pos; + *pos = waddir; + } + safe_snprintf (waddir->path, _MAX_PATH, "%s", path); +} + +typedef struct +{ + int filepos; + int disksize; + int size; + char type; + char compression; + char pad1, pad2; + char name[16]; +} lumpinfo_t; + +typedef struct wadfile_s +{ + struct wadfile_s *next; + char path[_MAX_PATH]; + FILE *file; + int filesize; + int numlumps; + lumpinfo_t *lumpinfos; +} wadfile_t; + +wadfile_t *g_wadfiles = NULL; +bool g_wadfiles_opened; + +static int CDECL lump_sorter_by_name (const void *lump1, const void *lump2) +{ + lumpinfo_t *plump1 = (lumpinfo_t *)lump1; + lumpinfo_t *plump2 = (lumpinfo_t *)lump2; + return strcasecmp (plump1->name, plump2->name); +} + +void OpenWadFile (const char *name +#ifdef ZHLT_NOWADDIR + , bool fullpath = false +#endif + ) +{ + int i; + wadfile_t *wad; + wad = (wadfile_t *)malloc (sizeof (wadfile_t)); + hlassume (wad != NULL, assume_NoMemory); + { + wadfile_t **pos; + for (pos = &g_wadfiles; *pos; pos = &(*pos)->next) + ; + wad->next = *pos; + *pos = wad; + } +#ifdef ZHLT_NOWADDIR + if (fullpath) + { + safe_snprintf (wad->path, _MAX_PATH, "%s", name); + wad->file = fopen (wad->path, "rb"); + if (!wad->file) + { + Error ("Couldn't open %s", wad->path); + } + } + else + { +#endif + waddir_t *dir; + for (dir = g_waddirs; dir; dir = dir->next) + { + safe_snprintf (wad->path, _MAX_PATH, "%s\\%s", dir->path, name); + wad->file = fopen (wad->path, "rb"); + if (wad->file) + { + break; + } + } + if (!dir) + { + Fatal (assume_COULD_NOT_LOCATE_WAD, "Could not locate wad file %s", name); + return; + } +#ifdef ZHLT_NOWADDIR + } +#endif + Log ("Using Wadfile: %s\n", wad->path); + wad->filesize = q_filelength (wad->file); + struct + { + char identification[4]; + int numlumps; + int infotableofs; + } wadinfo; + if (wad->filesize < (int)sizeof (wadinfo)) + { + Error ("Invalid wad file '%s'.", wad->path); + } + SafeRead (wad->file, &wadinfo, sizeof (wadinfo)); + wadinfo.numlumps = LittleLong(wadinfo.numlumps); + wadinfo.infotableofs = LittleLong(wadinfo.infotableofs); + if (strncmp (wadinfo.identification, "WAD2", 4) && strncmp (wadinfo.identification, "WAD3", 4)) + Error ("%s isn't a Wadfile!", wad->path); + wad->numlumps = wadinfo.numlumps; + if (wad->numlumps < 0 || wadinfo.infotableofs < 0 || wadinfo.infotableofs + wad->numlumps * (int)sizeof (lumpinfo_t) > wad->filesize) + { + Error ("Invalid wad file '%s'.", wad->path); + } + wad->lumpinfos = (lumpinfo_t *)malloc (wad->numlumps * sizeof (lumpinfo_t)); + hlassume (wad->lumpinfos != NULL, assume_NoMemory); + if (fseek (wad->file, wadinfo.infotableofs, SEEK_SET)) + Error ("File read failure: %s", wad->path); + for (i = 0; i < wad->numlumps; i++) + { + SafeRead (wad->file, &wad->lumpinfos[i], sizeof (lumpinfo_t)); + if (!TerminatedString(wad->lumpinfos[i].name, 16)) + { + wad->lumpinfos[i].name[16 - 1] = 0; + Warning("Unterminated texture name : wad[%s] texture[%d] name[%s]\n", wad->path, i, wad->lumpinfos[i].name); + } + wad->lumpinfos[i].filepos = LittleLong(wad->lumpinfos[i].filepos); + wad->lumpinfos[i].disksize = LittleLong(wad->lumpinfos[i].disksize); + wad->lumpinfos[i].size = LittleLong(wad->lumpinfos[i].size); + } + qsort (wad->lumpinfos, wad->numlumps, sizeof (lumpinfo_t), lump_sorter_by_name); +} + +void TryOpenWadFiles () +{ + if (!g_wadfiles_opened) + { + g_wadfiles_opened = true; +#ifdef ZHLT_NOWADDIR + char filename[_MAX_PATH]; + safe_snprintf(filename, _MAX_PATH, "%s.wa_", g_Mapname); + if (q_exists (filename)) + { + OpenWadFile (filename, true); + } + else + { + Warning ("Couldn't open %s", filename); +#endif + Log ("Opening wad files from directories:\n"); + if (!g_waddirs) + { + Warning ("No wad directories have been set."); + } + else + { + waddir_t *dir; + for (dir = g_waddirs; dir; dir = dir->next) + { + Log (" %s\n", dir->path); + } + } + const char *value = ValueForKey (&g_entities[0], "wad"); + char path[MAX_VAL]; + int i, j; + for (i = 0, j = 0; i < strlen(value) + 1; i++) + { + if (value[i] == ';' || value[i] == '\0') + { + path[j] = '\0'; + if (path[0]) + { + char name[MAX_VAL]; + ExtractFile (path, name); + DefaultExtension (name, ".wad"); + OpenWadFile (name); + } + j = 0; + } + else + { + path[j] = value[i]; + j++; + } + } +#ifdef ZHLT_NOWADDIR + } +#endif + CheckFatal (); + } +} + +void TryCloseWadFiles () +{ + if (g_wadfiles_opened) + { + g_wadfiles_opened = false; + wadfile_t *wadfile, *next; + for (wadfile = g_wadfiles; wadfile; wadfile = next) + { + next = wadfile->next; + free (wadfile->lumpinfos); + fclose (wadfile->file); + free (wadfile); + } + g_wadfiles = NULL; + } +} + +void DefaultTexture (radtexture_t *tex, const char *name) +{ + int i; + tex->width = 16; + tex->height = 16; + strcpy (tex->name, name); + tex->name[16 - 1] = '\0'; + tex->canvas = (byte *)malloc (tex->width * tex->height); + hlassume (tex->canvas != NULL, assume_NoMemory); + for (i = 0; i < 256; i++) + { + VectorFill (tex->palette[i], 0x80); + } + for (i = 0; i < tex->width * tex->height; i++) + { + tex->canvas[i] = 0x00; + } +} + +void LoadTexture (radtexture_t *tex, const miptex_t *mt, int size) +{ + int i, j; + const miptex_t *header = mt; + const byte *data = (const byte *)mt; + tex->width = header->width; + tex->height = header->height; + strcpy (tex->name, header->name); + tex->name[16 - 1] = '\0'; + if (tex->width <= 0 || tex->height <= 0 || + tex->width % (2 * 1 << (MIPLEVELS - 1)) != 0 || tex->height % (2 * (1 << (MIPLEVELS - 1))) != 0) + { + Error ("Texture '%s': dimension (%dx%d) is not multiple of %d.", tex->name, tex->width, tex->height, 2 * (1 << (MIPLEVELS - 1))); + } + int mipsize; + for (mipsize = 0, i = 0; i < MIPLEVELS; i++) + { + if ((int)mt->offsets[i] != (int)sizeof (miptex_t) + mipsize) + { + Error ("Texture '%s': unexpected miptex offset.", tex->name); + } + mipsize += (tex->width >> i) * (tex->height >> i); + } + if (size < (int)sizeof (miptex_t) + mipsize + 2 + 256 * 3) + { + Error ("Texture '%s': no enough data.", tex->name); + } + if (*(unsigned short *)&data[sizeof (miptex_t) + mipsize] != 256) + { + Error ("Texture '%s': palette size is not 256.", tex->name); + } + tex->canvas = (byte *)malloc (tex->width * tex->height); + hlassume (tex->canvas != NULL, assume_NoMemory); + for (i = 0; i < tex->height; i++) + { + for (j = 0; j < tex->width; j++) + { + tex->canvas[i * tex->width + j] = data[sizeof (miptex_t) + i * tex->width + j]; + } + } + for (i = 0; i < 256; i++) + { + for (j = 0; j < 3; j++) + { + tex->palette[i][j] = data[sizeof (miptex_t) + mipsize + 2 + i * 3 + j]; + } + } +} + +void LoadTextureFromWad (radtexture_t *tex, const miptex_t *header) +{ + tex->width = header->width; + tex->height = header->height; + strcpy (tex->name, header->name); + tex->name[16 - 1] = '\0'; + wadfile_t *wad; + for (wad = g_wadfiles; wad; wad = wad->next) + { + lumpinfo_t temp, *found; + strcpy (temp.name, tex->name); + found = (lumpinfo_t *)bsearch (&temp, wad->lumpinfos, wad->numlumps, sizeof (lumpinfo_t), lump_sorter_by_name); + if (found) + { + Developer (DEVELOPER_LEVEL_MESSAGE, "Texture '%s': found in '%s'.\n", tex->name, wad->path); + if (found->type != 67 || found->compression != 0) + continue; + if (found->disksize < (int)sizeof (miptex_t) || found->filepos < 0 || found->filepos + found->disksize > wad->filesize) + { + Warning ("Texture '%s': invalid texture data in '%s'.", tex->name, wad->path); + continue; + } + miptex_t *mt = (miptex_t *)malloc (found->disksize); + hlassume (mt != NULL, assume_NoMemory); + if (fseek (wad->file, found->filepos, SEEK_SET)) + Error ("File read failure"); + SafeRead (wad->file, mt, found->disksize); + if (!TerminatedString(mt->name, 16)) + { + Warning("Texture '%s': invalid texture data in '%s'.", tex->name, wad->path); + free (mt); + continue; + } + Developer (DEVELOPER_LEVEL_MESSAGE, "Texture '%s': name '%s', width %d, height %d.\n", tex->name, mt->name, mt->width, mt->height); + if (strcasecmp (mt->name, tex->name)) + { + Warning("Texture '%s': texture name '%s' differs from its reference name '%s' in '%s'.", tex->name, mt->name, tex->name, wad->path); + } + LoadTexture (tex, mt, found->disksize); + free (mt); + break; + } + } + if (!wad) + { + Warning ("Texture '%s': texture is not found in wad files.", tex->name); + DefaultTexture (tex, tex->name); + return; + } +} + +void LoadTextures () +{ + if (!g_notextures) + { + Log ("Load Textures:\n"); + } + g_numtextures = g_texdatasize? ((dmiptexlump_t *)g_dtexdata)->nummiptex: 0; + g_textures = (radtexture_t *)malloc (g_numtextures * sizeof (radtexture_t)); + hlassume (g_textures != NULL, assume_NoMemory); + int i; + for (i = 0; i < g_numtextures; i++) + { + int offset = ((dmiptexlump_t *)g_dtexdata)->dataofs[i]; + int size = g_texdatasize - offset; + radtexture_t *tex = &g_textures[i]; + if (g_notextures) + { + DefaultTexture (tex, "DEFAULT"); + } + else if (offset < 0 || size < (int)sizeof (miptex_t)) + { + Warning ("Invalid texture data in '%s'.", g_source); + DefaultTexture (tex, ""); + } + else + { + miptex_t *mt = (miptex_t *)&g_dtexdata[offset]; + if (mt->offsets[0]) + { + Developer (DEVELOPER_LEVEL_MESSAGE, "Texture '%s': found in '%s'.\n", mt->name, g_source); + Developer (DEVELOPER_LEVEL_MESSAGE, "Texture '%s': name '%s', width %d, height %d.\n", mt->name, mt->name, mt->width, mt->height); + LoadTexture (tex, mt, size); + } + else + { + TryOpenWadFiles (); + LoadTextureFromWad (tex, mt); + } + } +#ifdef HLRAD_REFLECTIVITY + { + vec3_t total; + VectorClear (total); + for (int j = 0; j < tex->width * tex->height; j++) + { + vec3_t reflectivity; + if (tex->name[0] == '{' && tex->canvas[j] == 0xFF) + { + VectorFill (reflectivity, 0.0); + } + else + { + VectorScale (tex->palette[tex->canvas[j]], 1.0/255.0, reflectivity); + for (int k = 0; k < 3; k++) + { + reflectivity[k] = pow (reflectivity[k], g_texreflectgamma); + } + VectorScale (reflectivity, g_texreflectscale, reflectivity); + } + VectorAdd (total, reflectivity, total); + } + VectorScale (total, 1.0 / (double)(tex->width * tex->height), total); + VectorCopy (total, tex->reflectivity); + Developer (DEVELOPER_LEVEL_MESSAGE, "Texture '%s': reflectivity is (%f,%f,%f).\n", + tex->name, tex->reflectivity[0], tex->reflectivity[1], tex->reflectivity[2]); + if (VectorMaximum (tex->reflectivity) > 1.0 + NORMAL_EPSILON) + { + Warning ("Texture '%s': reflectivity (%f,%f,%f) greater than 1.0.", tex->name, tex->reflectivity[0], tex->reflectivity[1], tex->reflectivity[2]); + } + } +#endif + } + if (!g_notextures) + { + Log ("%i textures referenced\n", g_numtextures); + TryCloseWadFiles (); + } +} + +#ifdef ZHLT_EMBEDLIGHTMAP + +// color quantization algorithm + +#define CQ_DIM 3 + +template inline void CQ_VectorSubtract (const T a[CQ_DIM], const T2 b[CQ_DIM], T3 c[CQ_DIM]) +{ + for (int x = 0; x < CQ_DIM; x++) + { + c[x] = a[x] - b[x]; + } +} + +template inline void CQ_VectorAdd (const T a[CQ_DIM], const T2 b[CQ_DIM], T3 c[CQ_DIM]) +{ + for (int x = 0; x < CQ_DIM; x++) + { + c[x] = a[x] + b[x]; + } +} + +template inline void CQ_VectorScale (const T a[CQ_DIM], const T2 b, T c[CQ_DIM]) +{ + for (int x = 0; x < CQ_DIM; x++) + { + c[x] = a[x] * b; + } +} + +template inline void CQ_VectorCopy (const T a[CQ_DIM], T2 b[CQ_DIM]) +{ + for (int x = 0; x < CQ_DIM; x++) + { + b[x] = a[x]; + } +} + +template inline void CQ_VectorClear (T a[CQ_DIM]) +{ + for (int x = 0; x < CQ_DIM; x++) + { + a[x] = (T)0; + } +} + +template inline T CQ_DotProduct (const T a[CQ_DIM], const T b[CQ_DIM]) +{ + T dot = (T)0; + for (int x = 0; x < CQ_DIM; x++) + { + dot += a[x] * b[x]; + } + return dot; +} + +typedef struct +{ + int axis; + int dist; + double numpoints[2]; +} +cq_splitter_t; // partition the space into { point: point[axis] < dist } and { point: point[axis] >= dist } + +typedef struct cq_node_s +{ + bool isleafnode; + cq_node_s *parentnode; + cq_node_s *childrennode[2]; + + int numpoints; // numpoints > 0 + unsigned char (*refpoints)[CQ_DIM]; + double centerofpoints[CQ_DIM]; + + bool needsplit; + cq_splitter_t bestsplitter; + double splitpriority; +} +cq_node_t; // a cuboid region; the root node is the entire cube whose size is 255 + +typedef struct cq_searchnode_s +{ + bool isleafnode; + cq_searchnode_s *childrennode[2]; + + int planeaxis; + int planedist; + + int result; +} +cq_searchnode_t; + +static void CQ_SelectPartition (cq_node_t *node) +{ + CQ_VectorClear (node->centerofpoints); + for (int i = 0; i < node->numpoints; i++) + { + CQ_VectorAdd (node->centerofpoints, node->refpoints[i], node->centerofpoints); + } + CQ_VectorScale (node->centerofpoints, 1 / (double)node->numpoints, node->centerofpoints); + + node->needsplit = false; + for (int k = 0; k < CQ_DIM; k++) + { + double count; + double counts[256]; + double sum[CQ_DIM]; + double sums[256][CQ_DIM]; + + double bucketsums[256][CQ_DIM]; + int bucketsizes[256]; + + const unsigned char (*nodepoints)[CQ_DIM] = node->refpoints; + const int nodenumpoints = node->numpoints; + + memset (bucketsums, 0, 256 * sizeof (double [CQ_DIM])); + memset (bucketsizes, 0, 256 * sizeof (int)); + for (int i = 0; i < nodenumpoints; i++) + { + int j = nodepoints[i][k]; + bucketsizes[j]++; + CQ_VectorAdd (bucketsums[j], nodepoints[i], bucketsums[j]); + } + + int min = 256; + int max = -1; + count = 0; + CQ_VectorClear (sum); + for (int j = 0; j < 256; j++) + { + counts[j] = count; + CQ_VectorCopy (sum, sums[j]); + count += bucketsizes[j]; + CQ_VectorAdd (sum, bucketsums[j], sum); + if (bucketsizes[j] > 0) + { + if (j < min) + { + min = j; + } + if (j > max) + { + max = j; + } + } + } + if (max < min) + { + Error ("CQ_SelectPartition: internal error"); + } + // sweep along the axis and find the plane that maximize square error reduction + for (int j = min + 1; j < max + 1; j++) + { + double priority = 0; // the decrease in total square deviation + priority -= CQ_DotProduct (sum, sum) / count; + priority += CQ_DotProduct (sums[j], sums[j]) / counts[j]; + double remain[CQ_DIM]; + CQ_VectorSubtract (sum, sums[j], remain); // sums and counts are precise (have no round-off error) + priority += CQ_DotProduct (remain, remain) / (count - counts[j]); + if (node->needsplit == false || + priority > node->splitpriority + 0.1 || + priority >= node->splitpriority - 0.1 + && fabs (counts[j] - count / 2) < fabs (node->bestsplitter.numpoints[0] - count / 2)) + { + node->needsplit = true; + node->splitpriority = priority; + node->bestsplitter.axis = k; + node->bestsplitter.dist = j; + node->bestsplitter.numpoints[0] = counts[j]; + node->bestsplitter.numpoints[1] = count - counts[j]; + } + } + } +} + +static cq_searchnode_t *CQ_AllocSearchTree (int maxcolors) +{ + cq_searchnode_t *searchtree; + searchtree = (cq_searchnode_t *)malloc ((2 * maxcolors - 1) * sizeof (cq_searchnode_t)); + hlassume (searchtree != NULL, assume_NoMemory); + return searchtree; +} + +static void CQ_FreeSearchTree (cq_searchnode_t *searchtree) +{ + free (searchtree); +} + +static void CQ_CreatePalette (int numpoints, const unsigned char (*points)[CQ_DIM], + int maxcolors, unsigned char (*colors_out)[CQ_DIM], int &numcolors_out, + cq_searchnode_t *searchtree_out) //[2 * maxcolors - 1] +{ + if (numpoints <= 0 || maxcolors <= 0) + { + numcolors_out = 0; + return; + } + + unsigned char (*pointarray)[CQ_DIM]; + pointarray = (unsigned char (*)[CQ_DIM])malloc (numpoints * sizeof (unsigned char [CQ_DIM])); + hlassume (pointarray != NULL, assume_NoMemory); + memcpy (pointarray, points, numpoints * sizeof (unsigned char [CQ_DIM])); + + cq_node_t *n; + cq_searchnode_t *s; + int numnodes = 0; + int maxnodes = 2 * maxcolors - 1; + cq_node_t *nodes = (cq_node_t *)malloc (maxnodes * sizeof (cq_node_t)); + hlassume (nodes != NULL, assume_NoMemory); + + n = &nodes[0]; + numnodes++; + + n->isleafnode = true; + n->parentnode = NULL; + n->numpoints = numpoints; + n->refpoints = pointarray; + CQ_SelectPartition (n); + + for (int i = 1; i < maxcolors; i++) + { + bool needsplit; + double bestpriority; + cq_node_t *bestnode; + + needsplit = false; + for (int j = 0; j < numnodes; j++) + { + n = &nodes[j]; + if (!n->isleafnode || !n->needsplit) + { + continue; + } + if (needsplit == false || n->splitpriority > bestpriority + 0.1) + { + needsplit = true; + bestpriority = n->splitpriority; + bestnode = n; + } + } + if (!needsplit) + { + break; + } + + bestnode->isleafnode = false; + for (int k = 0; k < 2; k++) + { + n = &nodes[numnodes]; + numnodes++; + if (numnodes > maxnodes) + { + Error ("CQ_CreatePalette: internal error"); + } + + bestnode->childrennode[k] = n; + n->isleafnode = true; + n->parentnode = bestnode; + n->numpoints = 0; + n->refpoints = NULL; + } + + // partition the points using the best splitter + { + const int splitaxis = bestnode->bestsplitter.axis; + const int splitdist = bestnode->bestsplitter.dist; + const int numpoints = bestnode->numpoints; + unsigned char (*points)[CQ_DIM] = bestnode->refpoints; + + unsigned char (*left)[CQ_DIM]; + unsigned char (*right)[CQ_DIM]; + left = &bestnode->refpoints[0]; + right = &bestnode->refpoints[bestnode->numpoints - 1]; + while (1) + { + while ((*left)[splitaxis] < splitdist) + { + left++; + } + while ((*right)[splitaxis] >= splitdist) + { + right--; + } + if (left >= right) + { + break; + } + unsigned char tmp[CQ_DIM]; + CQ_VectorCopy (*left, tmp); + CQ_VectorCopy (*right, *left); + CQ_VectorCopy (tmp, *right); + } + if (right != left - 1) + { + Error ("CQ_CreatePalette: internal error"); + } + + bestnode->childrennode[0]->numpoints = left - bestnode->refpoints; + bestnode->childrennode[0]->refpoints = bestnode->refpoints; + bestnode->childrennode[1]->numpoints = &bestnode->refpoints[bestnode->numpoints] - left; + bestnode->childrennode[1]->refpoints = left; + if (bestnode->childrennode[0]->numpoints <= 0 || + bestnode->childrennode[0]->numpoints != bestnode->bestsplitter.numpoints[0]) + { + Error ("CQ_CreatePalette: internal error"); + } + if (bestnode->childrennode[1]->numpoints <= 0 || + bestnode->childrennode[1]->numpoints != bestnode->bestsplitter.numpoints[1]) + { + Error ("CQ_CreatePalette: internal error"); + } + } + + CQ_SelectPartition (bestnode->childrennode[0]); + CQ_SelectPartition (bestnode->childrennode[1]); + } + + for (int i = 0; i < numnodes; i++) + { + n = &nodes[i]; + s = &searchtree_out[i]; + s->isleafnode = n->isleafnode; + if (!n->isleafnode) + { + s->planeaxis = n->bestsplitter.axis; + s->planedist = n->bestsplitter.dist; + s->childrennode[0] = &searchtree_out[n->childrennode[0] - nodes]; + s->childrennode[1] = &searchtree_out[n->childrennode[1] - nodes]; + } + } + + numcolors_out = 0; + n = &nodes[0]; + while (1) + { + while (!n->isleafnode) + { + n = n->childrennode[0]; + } + s = &searchtree_out[n - nodes]; + s->result = numcolors_out; + for (int k = 0; k < CQ_DIM; k++) + { + int val = (int)floor (n->centerofpoints[k] + 0.5 + 0.00001); + val = qmax (0, qmin (val, 255)); + colors_out[numcolors_out][k] = val; + } + numcolors_out++; + while (n->parentnode) + { + if (n == n->parentnode->childrennode[0]) + { + break; + } + n = n->parentnode; + } + if (!n->parentnode) + { + break; + } + n = n->parentnode->childrennode[1]; + } + + if (2 * numcolors_out - 1 != numnodes) + { + Error ("CQ_CreatePalette: internal error"); + } + + free (pointarray); + free (nodes); +} + +static void CQ_MapPoint_r (int *bestdist, int *best, + cq_searchnode_t *node, const unsigned char (*colors)[CQ_DIM], + const unsigned char point[CQ_DIM], int searchradius) +{ + while (!node->isleafnode) + { + int dist = point[node->planeaxis] - node->planedist; + if (dist <= -searchradius) + { + node = node->childrennode[0]; + } + else if (dist >= searchradius - 1) + { + node = node->childrennode[1]; + } + else + { + CQ_MapPoint_r (bestdist, best, node->childrennode[0], colors, point, searchradius); + CQ_MapPoint_r (bestdist, best, node->childrennode[1], colors, point, searchradius); + return; + } + } + int dist = 0; + for (int k = 0; k < CQ_DIM; k++) + { + dist += (colors[node->result][k] - point[k]) * (colors[node->result][k] - point[k]); + } + if (dist <= *bestdist) + { + if (dist < *bestdist || node->result < *best) + { + *bestdist = dist; + *best = node->result; + } + } +} + +static int CQ_MapPoint (const unsigned char point[CQ_DIM], const unsigned char (*colors)[CQ_DIM], int numcolors, cq_searchnode_t *searchtree) +{ + if (numcolors <= 0) + { + Error ("CQ_MapPoint: internal error"); + } + + cq_searchnode_t *node; + int bestdist; + int best; + int searchradius; + + for (node = searchtree; !node->isleafnode; ) + { + node = node->childrennode[point[node->planeaxis] >= node->planedist]; + } + best = node->result; + bestdist = 0; + for (int k = 0; k < CQ_DIM; k++) + { + bestdist += (colors[best][k] - point[k]) * (colors[best][k] - point[k]); + } + + searchradius = (int)ceil(sqrt ((double)bestdist) + 0.1); + CQ_MapPoint_r (&bestdist, &best, searchtree, colors, point, searchradius); + return best; +} + +// ===================================================================================== +// EmbedLightmapInTextures +// check for "zhlt_embedlightmap" and update g_dfaces, g_texinfo, g_dtexdata and g_dlightdata +// ===================================================================================== + +#define RADTEXTURES_MAX 2048 // should be smaller than 62 * 62 and smaller than MAX_MAP_TEXTURES +static int g_newtextures_num = 0; +static byte *g_newtextures_data[RADTEXTURES_MAX]; +static int g_newtextures_size[RADTEXTURES_MAX]; + +int NewTextures_GetCurrentMiptexIndex () +{ + dmiptexlump_t *texdata = (dmiptexlump_t *)g_dtexdata; + return texdata->nummiptex + g_newtextures_num; +} + +void NewTextures_PushTexture (int size, void *data) +{ + if (g_newtextures_num >= RADTEXTURES_MAX) + { + Error ("the number of textures created by hlrad has exceeded its internal limit(%d).", (int)RADTEXTURES_MAX); + } + g_newtextures_data[g_newtextures_num] = (byte *)malloc (size); + hlassume (g_newtextures_data[g_newtextures_num] != NULL, assume_NoMemory); + memcpy (g_newtextures_data[g_newtextures_num], data, size); + g_newtextures_size[g_newtextures_num] = size; + g_newtextures_num++; +} + +void NewTextures_Write () +{ + if (!g_newtextures_num) + { + return; + } + + int i; + dmiptexlump_t *texdata = (dmiptexlump_t *)g_dtexdata; + + byte *dataaddr = (byte *)&texdata->dataofs[texdata->nummiptex]; + int datasize = (g_dtexdata + g_texdatasize) - dataaddr; + byte *newdataaddr = (byte *)&texdata->dataofs[texdata->nummiptex + g_newtextures_num]; + hlassume (g_texdatasize + (newdataaddr - dataaddr) <= g_max_map_miptex, assume_MAX_MAP_MIPTEX); + memmove (newdataaddr, dataaddr, datasize); + g_texdatasize += newdataaddr - dataaddr; + for (i = 0; i < texdata->nummiptex; i++) + { + if (texdata->dataofs[i] < 0) // bad texture + { + continue; + } + texdata->dataofs[i] += newdataaddr - dataaddr; + } + + hlassume (texdata->nummiptex + g_newtextures_num < MAX_MAP_TEXTURES, assume_MAX_MAP_TEXTURES); + for (i = 0; i < g_newtextures_num; i++) + { + hlassume (g_texdatasize + g_newtextures_size[i] <= g_max_map_miptex, assume_MAX_MAP_MIPTEX); + memcpy (g_dtexdata + g_texdatasize, g_newtextures_data[i], g_newtextures_size[i]); + texdata->dataofs[texdata->nummiptex + i] = g_texdatasize; + g_texdatasize += g_newtextures_size[i]; + } + texdata->nummiptex += g_newtextures_num; + + for (int i = 0; i < g_newtextures_num; i++) + { + free (g_newtextures_data[i]); + } + g_newtextures_num = 0; +} + +static unsigned int Hash (int size, void *data) +{ + unsigned int hash = 0; + for (int i = 0; i < size; i++) + { + hash = 31 * hash + ((unsigned char *)data)[i]; + } + return hash; +} + +static void GetLightInt (dface_t *face, const int texsize[2], int ix, int iy, vec3_t &light) +{ + ix = qmax (0, qmin (ix, texsize[0])); + iy = qmax (0, qmin (iy, texsize[1])); + VectorClear (light); + if (face->lightofs < 0) + { + return; + } + for (int k = 0; k < MAXLIGHTMAPS && face->styles[k] != 255; k++) + { + byte *samples = &g_dlightdata[face->lightofs + k * (texsize[0] + 1) * (texsize[1] + 1) * 3]; + if (face->styles[k] == 0) + { + VectorAdd (light, &samples[(iy * (texsize[0] + 1) + ix) * 3], light); + } + } +} + +static void GetLight (dface_t *face, const int texsize[2], double x, double y, vec3_t &light) +{ + int ix, iy; + double dx, dy; + ix = (int)floor (x); + iy = (int)floor (y); + dx = x - ix; + dx = qmax (0, qmin (dx, 1)); + dy = y - iy; + dy = qmax (0, qmin (dy, 1)); + + // do bilinear interpolation + vec3_t light00, light10, light01, light11; + GetLightInt (face, texsize, ix, iy, light00); + GetLightInt (face, texsize, ix + 1, iy, light10); + GetLightInt (face, texsize, ix, iy + 1, light01); + GetLightInt (face, texsize, ix + 1, iy + 1, light11); + vec3_t light0, light1; + VectorScale (light00, 1 - dy, light0); + VectorMA (light0, dy, light01, light0); + VectorScale (light10, 1 - dy, light1); + VectorMA (light1, dy, light11, light1); + VectorScale (light0, 1 - dx, light); + VectorMA (light, dx, light1, light); +} + +static bool GetValidTextureName (int miptex, char name[16]) +{ + int numtextures = g_texdatasize? ((dmiptexlump_t *)g_dtexdata)->nummiptex: 0; + int offset; + int size; + miptex_t *mt; + + if (miptex < 0 || miptex >= numtextures) + { + return false; + } + offset = ((dmiptexlump_t *)g_dtexdata)->dataofs[miptex]; + size = g_texdatasize - offset; + if (offset < 0 || g_dtexdata + offset < (byte *)&((dmiptexlump_t *)g_dtexdata)->dataofs[numtextures] || + size < (int)sizeof (miptex_t)) + { + return false; + } + + mt = (miptex_t *)&g_dtexdata[offset]; + safe_strncpy (name, mt->name, 16); + + if (strcmp (name, mt->name)) + { + return false; + } + + if (strlen (name) >= 5 && !strncasecmp (&name[1], "_rad", 4)) + { + return false; + } + + return true; +} + +void EmbedLightmapInTextures () +{ + if (!g_lightdatasize) + { + // hlrad hasn't run + return; + } + if (!g_texdatasize) + { + // texdata hasn't been initialized + return; + } + if (g_notextures) + { + // hlrad didn't load the wad files + return; + } + + int i, j, k; + int miplevel; + int count = 0; + int count_bytes = 0; + bool logged = false; + + for (i = 0; i < g_numfaces; i++) + { + dface_t *f = &g_dfaces[i]; + + if (f->lightofs == -1) // some faces don't have lightmap + { + continue; + } + if (f->texinfo < 0 || f->texinfo >= g_numtexinfo) + { + continue; + } + + entity_t *ent = g_face_entity[i]; + int originaltexinfonum = f->texinfo; + texinfo_t *originaltexinfo = &g_texinfo[originaltexinfonum]; + char texname[16]; + if (!GetValidTextureName (originaltexinfo->miptex, texname)) + { + continue; + } + radtexture_t *tex = &g_textures[originaltexinfo->miptex]; + + if (ent == &g_entities[0]) // world + { + continue; + } + if (!strncmp (texname, "sky", 3) + || originaltexinfo->flags & TEX_SPECIAL) // skip special surfaces + { + continue; + } + if (!IntForKey (ent, "zhlt_embedlightmap")) + { + continue; + } + + if (!logged) + { + Log ("\n"); + Log ("Embed Lightmap : "); + Developer (DEVELOPER_LEVEL_MESSAGE, "\n"); + logged = true; + } + + bool poweroftwo = DEFAULT_EMBEDLIGHTMAP_POWEROFTWO; + vec_t denominator = DEFAULT_EMBEDLIGHTMAP_DENOMINATOR; + vec_t gamma = DEFAULT_EMBEDLIGHTMAP_GAMMA; + int resolution = DEFAULT_EMBEDLIGHTMAP_RESOLUTION; + if (IntForKey (ent, "zhlt_embedlightmapresolution")) + { + resolution = IntForKey (ent, "zhlt_embedlightmapresolution"); + if (resolution <= 0 || resolution > TEXTURE_STEP || ((resolution - 1) & resolution) != 0) + { + Error ("resolution cannot be %d; valid values are 1, 2, 4 ... %d.", resolution, (int)TEXTURE_STEP); + } + } + + // calculate texture size and allocate memory for all miplevels + + int texturesize[2]; + float (*texture)[5]; // red, green, blue and alpha channel; the last one is number of samples + byte (*texturemips[MIPLEVELS])[4]; // red, green, blue and alpha channel + int s, t; + int texmins[2]; + int texmaxs[2]; + int texsize[2]; // texturesize = (texsize + 1) * TEXTURE_STEP + int side[2]; + + GetFaceExtents (i, texmins, texmaxs); + texsize[0] = texmaxs[0] - texmins[0]; + texsize[1] = texmaxs[1] - texmins[1]; + if (texsize[0] < 0 || texsize[1] < 0 || texsize[0] > MAX_SURFACE_EXTENT || texsize[1] > MAX_SURFACE_EXTENT) + { + Warning ("skipped a face with bad surface extents @ (%4.3f %4.3f %4.3f)", g_face_centroids[i][0], g_face_centroids[i][1], g_face_centroids[i][2]); + continue; + } + + for (k = 0; k < 2; k++) + { + texturesize[k] = (texsize[k] + 1) * TEXTURE_STEP; + if (texturesize[k] < texsize[k] * TEXTURE_STEP + resolution * 4) + { + texturesize[k] = texsize[k] * TEXTURE_STEP + resolution * 4; // prevent edge bleeding + } + texturesize[k] = (texturesize[k] + resolution - 1) / resolution; + texturesize[k] += 15 - (texturesize[k] + 15) % 16; // must be multiples of 16 + if (poweroftwo) + { + for (j = 0; j <= 30; j++) + { + if ((1 << j) >= texturesize[k]) + { + texturesize[k] = (1 << j); + break; + } + } + } + side[k] = (texturesize[k] * resolution - texsize[k] * TEXTURE_STEP) / 2; + } + texture = (float (*)[5])malloc (texturesize[0] * texturesize[1] * sizeof (float [5])); + hlassume (texture != NULL, assume_NoMemory); + for (miplevel = 0; miplevel < MIPLEVELS; miplevel++) + { + texturemips[miplevel] = (byte (*)[4])malloc ((texturesize[0] >> miplevel) * (texturesize[1] >> miplevel) * sizeof (byte [4])); + hlassume (texturemips[miplevel] != NULL, assume_NoMemory); + } + + // calculate the texture + + for (t = 0; t < texturesize[1]; t++) + { + for (s = 0; s < texturesize[0]; s++) + { + float (*dest)[5] = &texture[t * texturesize[0] + s]; + VectorFill (*dest, 0); + (*dest)[3] = 0; + (*dest)[4] = 0; + } + } + for (t = -side[1]; t < texsize[1] * TEXTURE_STEP + side[1]; t++) + { + for (s = -side[0]; s < texsize[0] * TEXTURE_STEP + side[0]; s++) + { + double s_vec, t_vec; + double src_s, src_t; + int src_is, src_it; + byte src_index; + byte src_color[3]; + double dest_s, dest_t; + int dest_is, dest_it; + float (*dest)[5]; + double light_s, light_t; + vec3_t light; + + s_vec = s + texmins[0] * TEXTURE_STEP + 0.5; + t_vec = t + texmins[1] * TEXTURE_STEP + 0.5; + + if (resolution == 1) + { + dest_s = s_vec; + dest_t = t_vec; + } + else // the final blurred texture is shifted by a half pixel so that lightmap samples align with the center of pixels + { + dest_s = s_vec / resolution + 0.5; + dest_t = t_vec / resolution + 0.5; + } + dest_s = dest_s - texturesize[0] * floor (dest_s / texturesize[0]); + dest_t = dest_t - texturesize[1] * floor (dest_t / texturesize[1]); + dest_is = (int)floor (dest_s); // dest_is = dest_s % texturesize[0] + dest_it = (int)floor (dest_t); // dest_it = dest_t % texturesize[1] + dest_is = qmax (0, qmin (dest_is, texturesize[0] - 1)); + dest_it = qmax (0, qmin (dest_it, texturesize[1] - 1)); + dest = &texture[dest_it * texturesize[0] + dest_is]; + + src_s = s_vec; + src_t = t_vec; + src_s = src_s - tex->width * floor (src_s / tex->width); + src_t = src_t - tex->height * floor (src_t / tex->height); + src_is = (int)floor (src_s); // src_is = src_s % tex->width + src_it = (int)floor (src_t); // src_it = src_t % tex->height + src_is = qmax (0, qmin (src_is, tex->width - 1)); + src_it = qmax (0, qmin (src_it, tex->height - 1)); + src_index = tex->canvas[src_it * tex->width + src_is]; + VectorCopy (tex->palette[src_index], src_color); + + // get light from the center of the destination pixel + light_s = (s_vec + resolution * (dest_is + 0.5 - dest_s)) / TEXTURE_STEP - texmins[0]; + light_t = (t_vec + resolution * (dest_it + 0.5 - dest_t)) / TEXTURE_STEP - texmins[1]; + GetLight (f, texsize, light_s, light_t, light); + + (*dest)[4] += 1; + if (!(texname[0] == '{' && src_index == 255)) + { + for (k = 0; k < 3; k++) + { + float v = src_color[k] * pow (light[k] / denominator, gamma); + (*dest)[k] += 255 * qmax (0, qmin (v, 255)); + } + (*dest)[3] += 255; + } + } + } + for (t = 0; t < texturesize[1]; t++) + { + for (s = 0; s < texturesize[0]; s++) + { + float (*src)[5] = &texture[t * texturesize[0] + s]; + byte (*dest)[4] = &texturemips[0][t * texturesize[0] + s]; + + if ((*src)[4] == 0) // no samples (outside face range?) + { + VectorFill (*dest, 0); + (*dest)[3] = 255; + } + else + { + if ((*src)[3] / (*src)[4] <= 0.4 * 255) // transparent + { + VectorFill (*dest, 0); + (*dest)[3] = 0; + } + else // normal + { + for (j = 0; j < 3; j++) + { + int val = (int)floor ((*src)[j] / (*src)[3] + 0.5); + (*dest)[j] = qmax (0, qmin (val, 255)); + } + (*dest)[3] = 255; + } + } + } + } + + for (miplevel = 1; miplevel < MIPLEVELS; miplevel++) + { + for (t = 0; t < (texturesize[1] >> miplevel); t++) + { + for (s = 0; s < (texturesize[0] >> miplevel); s++) + { + byte (*src[4])[4]; + byte (*dest)[4]; + double average[4]; + + dest = &texturemips[miplevel][t * (texturesize[0] >> miplevel) + s]; + src[0] = &texturemips[miplevel - 1][(2 * t) * (texturesize[0] >> (miplevel - 1)) + (2 * s)]; + src[1] = &texturemips[miplevel - 1][(2 * t) * (texturesize[0] >> (miplevel - 1)) + (2 * s + 1)]; + src[2] = &texturemips[miplevel - 1][(2 * t + 1) * (texturesize[0] >> (miplevel - 1)) + (2 * s)]; + src[3] = &texturemips[miplevel - 1][(2 * t + 1) * (texturesize[0] >> (miplevel - 1)) + (2 * s + 1)]; + + VectorClear (average); + average[3] = 0; + for (k = 0; k < 4; k++) + { + for (j = 0; j < 3; j++) + { + average[j] += (*src[k])[3] * (*src[k])[j]; + } + average[3] += (*src[k])[3]; + } + + if (average[3] / 4 <= 0.4 * 255) + { + VectorClear (*dest); + (*dest)[3] = 0; + } + else + { + for (j = 0; j < 3; j++) + { + int val = (int)floor (average[j] / average[3] + 0.5); + (*dest)[j] = qmax (0, qmin (val, 255)); + } + (*dest)[3] = 255; + } + } + } + } + + // create its palette + + byte palette[256][3]; + cq_searchnode_t *palettetree = CQ_AllocSearchTree (256); + int paletteoffset; + int palettenumcolors; + + { + int palettemaxcolors; + int numsamplepoints; + unsigned char (*samplepoints)[3]; + + if (texname[0] == '{') + { + paletteoffset = 0; + palettemaxcolors = 255; + VectorCopy (tex->palette[255], palette[255]); // the transparency color + } + /*else if (texname[0] == '!') + { + paletteoffset = 16; // because the 4th entry and the 5th entry are reserved for fog color and fog density + for (j = 0; j < 16; j++) + { + VectorCopy (tex->palette[j], palette[j]); + } + palettemaxcolors = 256 - 16; + }*/ + else + { + paletteoffset = 0; + palettemaxcolors = 256; + } + + samplepoints = (unsigned char (*)[3])malloc (texturesize[0] * texturesize[1] * sizeof (unsigned char [3])); + hlassume (samplepoints != NULL, assume_NoMemory); + numsamplepoints = 0; + for (t = 0; t < texturesize[1]; t++) + { + for (s = 0; s < texturesize[0]; s++) + { + byte (*src)[4] = &texturemips[0][t * texturesize[0] + s]; + if ((*src)[3] > 0) + { + VectorCopy (*src, samplepoints[numsamplepoints]); + numsamplepoints++; + } + } + } + + CQ_CreatePalette (numsamplepoints, samplepoints, palettemaxcolors, &palette[paletteoffset], palettenumcolors, palettetree); + for (j = palettenumcolors; j < palettemaxcolors; j++) + { + VectorClear (palette[paletteoffset + j]); + } + + free (samplepoints); + } + + // emit a texinfo + + hlassume (g_numtexinfo < MAX_MAP_TEXINFO, assume_MAX_MAP_TEXINFO); + f->texinfo = g_numtexinfo; + texinfo_t *info = &g_texinfo[g_numtexinfo]; + g_numtexinfo++; + + *info = g_texinfo[originaltexinfonum]; + if (resolution != 1) + { + // apply a scale and a shift over the original vectors + for (k = 0; k < 2; k++) + { + VectorScale (info->vecs[k], 1.0 / resolution, info->vecs[k]); + info->vecs[k][3] = info->vecs[k][3] / resolution + 0.5; + } + } + info->miptex = NewTextures_GetCurrentMiptexIndex (); + + // emit a texture + + int miptexsize; + + miptexsize = (int)sizeof (miptex_t); + for (miplevel = 0; miplevel < MIPLEVELS; miplevel++) + { + miptexsize += (texturesize[0] >> miplevel) * (texturesize[1] >> miplevel); + } + miptexsize += 2 + 256 * 3 + 2; + miptex_t *miptex = (miptex_t *)malloc (miptexsize); + hlassume (miptex != NULL, assume_NoMemory); + + memset (miptex, 0, sizeof (miptex_t)); + miptex->width = texturesize[0]; + miptex->height = texturesize[1]; + byte *p = (byte *)miptex + sizeof (miptex_t); + for (miplevel = 0; miplevel < MIPLEVELS; miplevel++) + { + miptex->offsets[miplevel] = p - (byte *)miptex; + for (int t = 0; t < (texturesize[1] >> miplevel); t++) + { + for (int s = 0; s < (texturesize[0] >> miplevel); s++) + { + byte (*src)[4] = &texturemips[miplevel][t * (texturesize[0] >> miplevel) + s]; + if ((*src)[3] > 0) + { + if (palettenumcolors) + { + unsigned char point[3]; + VectorCopy (*src, point); + *p = paletteoffset + CQ_MapPoint (point, &palette[paletteoffset], palettenumcolors, palettetree); + } + else // this should never happen + { + *p = paletteoffset + 0; + } + } + else + { + *p = 255; + } + p++; + } + } + } + *(short *)p = 256; + p += 2; + memcpy (p, palette, 256 * 3); + p += 256 * 3; + *(short *)p = 0; + p += 2; + if (p != (byte *)miptex + miptexsize) + { + Error ("EmbedLightmapInTextures: internal error"); + } + + if (texname[0] == '{') + { + strcpy (miptex->name, "{_rad"); + } + /*else if (texname[0] == '!') + { + strcpy (miptex->name, "!_rad"); + }*/ + else + { + strcpy (miptex->name, "__rad"); + } + if (originaltexinfonum < 0 || originaltexinfonum > 99999) + { + Error ("EmbedLightmapInTextures: internal error: texinfo out of range"); + } + miptex->name[5] = '0' + (originaltexinfonum / 10000) % 10; // store the original texinfo + miptex->name[6] = '0' + (originaltexinfonum / 1000) % 10; + miptex->name[7] = '0' + (originaltexinfonum / 100) % 10; + miptex->name[8] = '0' + (originaltexinfonum / 10) % 10; + miptex->name[9] = '0' + (originaltexinfonum) % 10; + char table[62]; + for (int k = 0; k < 62; k++) + { + table[k] = k >= 36? 'a' + (k - 36): k >= 10? 'A' + (k - 10): '0' + k; // same order as the ASCII table + } + miptex->name[10] = '\0'; + miptex->name[11] = '\0'; + miptex->name[12] = '\0'; + miptex->name[13] = '\0'; + miptex->name[14] = '\0'; + miptex->name[15] = '\0'; + unsigned int hash = Hash (miptexsize, miptex); + miptex->name[10] = table[(hash / 62 / 62) % 52 + 10]; + miptex->name[11] = table[(hash / 62) % 62]; + miptex->name[12] = table[(hash) % 62]; + miptex->name[13] = table[(count / 62) % 62]; + miptex->name[14] = table[(count) % 62]; + miptex->name[15] = '\0'; + NewTextures_PushTexture (miptexsize, miptex); + count++; + count_bytes += miptexsize; + Developer (DEVELOPER_LEVEL_MESSAGE, "Created texture '%s' for face (texture %s) at (%4.3f %4.3f %4.3f)\n", miptex->name, texname, g_face_centroids[i][0], g_face_centroids[i][1], g_face_centroids[i][2]); + + free (miptex); + + CQ_FreeSearchTree (palettetree); + + free (texture); + for (miplevel = 0; miplevel < MIPLEVELS; miplevel++) + { + free (texturemips[miplevel]); + } + } + NewTextures_Write (); // update texdata now + + if (logged) + { + Log ("added %d texinfos and textures (%d bytes)\n", count, count_bytes); + } +} + +#endif +#endif diff --git a/src/zhlt-vluzacn/hlrad/mathutil.cpp b/src/zhlt-vluzacn/hlrad/mathutil.cpp new file mode 100644 index 0000000..e255375 --- /dev/null +++ b/src/zhlt-vluzacn/hlrad/mathutil.cpp @@ -0,0 +1,991 @@ +#include "qrad.h" + +#ifdef HLRAD_SNAPTOWINDING +// ===================================================================================== +// point_in_winding +// returns whether the point is in the winding (including its edges) +// the point and all the vertexes of the winding can move freely along the plane's normal without changing the result +// ===================================================================================== +bool point_in_winding(const Winding& w, const dplane_t& plane, const vec_t* const point, vec_t epsilon/* = 0.0*/) +{ + int numpoints; + int x; + vec3_t delta; + vec3_t normal; + vec_t dist; + + numpoints = w.m_NumPoints; + + for (x = 0; x < numpoints; x++) + { + VectorSubtract (w.m_Points[(x+ 1) % numpoints], w.m_Points[x], delta); + CrossProduct (delta, plane.normal, normal); + dist = DotProduct (point, normal) - DotProduct (w.m_Points[x], normal); + + if (dist < 0.0 + && (epsilon == 0.0 || dist * dist > epsilon * epsilon * DotProduct (normal, normal))) + { + return false; + } + } + + return true; +} + +#else +// ===================================================================================== +// point_in_winding +// ===================================================================================== +bool point_in_winding(const Winding& w, const dplane_t& plane, const vec_t* const point) +{ + unsigned numpoints = w.m_NumPoints; + int x; + + for (x = 0; x < numpoints; x++) + { + vec3_t A; + vec3_t B; + vec3_t normal; + + VectorSubtract(w.m_Points[(x + 1) % numpoints], point, A); + VectorSubtract(w.m_Points[x], point, B); + CrossProduct(A, B, normal); +#ifndef HLRAD_MATH_VL + VectorNormalize(normal); +#endif + + if (DotProduct(normal, plane.normal) < 0.0) + { + return false; + } + } + + return true; +} + +#endif +#ifdef HLRAD_NUDGE_VL +// ===================================================================================== +// point_in_winding_noedge +// assume a ball is created from the point, this function checks whether the ball is entirely inside the winding +// parameter 'width' : the radius of the ball +// the point and all the vertexes of the winding can move freely along the plane's normal without changing the result +// ===================================================================================== +bool point_in_winding_noedge(const Winding& w, const dplane_t& plane, const vec_t* const point, vec_t width) +{ + int numpoints; + int x; + vec3_t delta; + vec3_t normal; + vec_t dist; + + numpoints = w.m_NumPoints; + + for (x = 0; x < numpoints; x++) + { + VectorSubtract (w.m_Points[(x+ 1) % numpoints], w.m_Points[x], delta); + CrossProduct (delta, plane.normal, normal); + dist = DotProduct (point, normal) - DotProduct (w.m_Points[x], normal); + + if (dist < 0.0 || dist * dist <= width * width * DotProduct (normal, normal)) + { + return false; + } + } + + return true; +} + +#endif +#ifdef HLRAD_SNAPTOWINDING +// ===================================================================================== +// snap_to_winding +// moves the point to the nearest point inside the winding +// if the point is not on the plane, the distance between the point and the plane is preserved +// the point and all the vertexes of the winding can move freely along the plane's normal without changing the result +// ===================================================================================== +void snap_to_winding(const Winding& w, const dplane_t& plane, vec_t* const point) +{ + int numpoints; + int x; + vec_t *p1, *p2; + vec3_t delta; + vec3_t normal; + vec_t dist; + vec_t dot1, dot2, dot; + vec3_t bestpoint; + vec_t bestdist; + bool in; + + numpoints = w.m_NumPoints; + + in = true; + for (x = 0; x < numpoints; x++) + { + p1 = w.m_Points[x]; + p2 = w.m_Points[(x + 1) % numpoints]; + VectorSubtract (p2, p1, delta); + CrossProduct (delta, plane.normal, normal); + dist = DotProduct (point, normal) - DotProduct (p1, normal); + + if (dist < 0.0) + { + in = false; + + CrossProduct (plane.normal, normal, delta); + dot = DotProduct (delta, point); + dot1 = DotProduct (delta, p1); + dot2 = DotProduct (delta, p2); + if (dot1 < dot && dot < dot2) + { + dist = dist / DotProduct (normal, normal); + VectorMA (point, -dist, normal, point); + return; + } + } + } + if (in) + { + return; + } + + for (x = 0; x < numpoints; x++) + { + p1 = w.m_Points[x]; + VectorSubtract (p1, point, delta); + dist = DotProduct (delta, plane.normal) / DotProduct (plane.normal, plane.normal); + VectorMA (delta, -dist, plane.normal, delta); + dot = DotProduct (delta, delta); + + if (x == 0 || dot < bestdist) + { + VectorAdd (point, delta, bestpoint); + bestdist = dot; + } + } + if (numpoints > 0) + { + VectorCopy (bestpoint, point); + } + return; +} + +// ===================================================================================== +// snap_to_winding_noedge +// first snaps the point into the winding +// then moves the point towards the inside for at most certain distance until: +// either 1) the point is not close to any of the edges +// or 2) the point can not be moved any more +// returns the maximal distance that the point can be kept away from all the edges +// in most of the cases, the maximal distance = width; in other cases, the maximal distance < width +// ===================================================================================== +vec_t snap_to_winding_noedge(const Winding& w, const dplane_t& plane, vec_t* const point, vec_t width, vec_t maxmove) +{ + int pass; + int numplanes; + dplane_t *planes; + int x; + vec3_t v; + vec_t newwidth; + vec_t bestwidth; + vec3_t bestpoint; + + snap_to_winding (w, plane, point); + + planes = (dplane_t *)malloc (w.m_NumPoints * sizeof (dplane_t)); + hlassume (planes != NULL, assume_NoMemory); + numplanes = 0; + for (x = 0; x < w.m_NumPoints; x++) + { + VectorSubtract (w.m_Points[(x + 1) % w.m_NumPoints], w.m_Points[x], v); + CrossProduct (v, plane.normal, planes[numplanes].normal); + if (!VectorNormalize (planes[numplanes].normal)) + { + continue; + } + planes[numplanes].dist = DotProduct (w.m_Points[x], planes[numplanes].normal); + numplanes++; + } + + bestwidth = 0; + VectorCopy (point, bestpoint); + newwidth = width; + + for (pass = 0; pass < 5; pass++) // apply binary search method for 5 iterations to find the maximal distance that the point can be kept away from all the edges + { + bool failed; + vec3_t newpoint; + Winding *newwinding; + + failed = true; + + newwinding = new Winding (w); + for (x = 0; x < numplanes && newwinding->m_NumPoints > 0; x++) + { + dplane_t clipplane = planes[x]; + clipplane.dist += newwidth; + newwinding->Clip (clipplane, false); + } + + if (newwinding->m_NumPoints > 0) + { + VectorCopy (point, newpoint); + snap_to_winding (*newwinding, plane, newpoint); + + VectorSubtract (newpoint, point, v); + if (VectorLength (v) <= maxmove + ON_EPSILON) + { + failed = false; + } + } + + delete newwinding; + + if (!failed) + { + bestwidth = newwidth; + VectorCopy (newpoint, bestpoint); + if (pass == 0) + { + break; + } + newwidth += width * pow (0.5, pass + 1); + } + else + { + newwidth -= width * pow (0.5, pass + 1); + } + } + + free (planes); + + VectorCopy (bestpoint, point); + return bestwidth; +} + +#endif +#ifndef HLRAD_OPAQUE_NODE +#ifdef HLRAD_POINT_IN_EDGE_FIX +bool point_in_winding_percentage(const Winding& w, const dplane_t& plane, const vec3_t point, const vec3_t ray, double &percentage) +{ + unsigned numpoints = w.m_NumPoints; + int x; + + int inedgecount = 0; + vec3_t inedgedir[2]; + + for (x = 0; x < numpoints; x++) + { + vec3_t A; + vec3_t B; + vec3_t normal; + + VectorSubtract(w.m_Points[(x + 1) % numpoints], point, A); + VectorSubtract(w.m_Points[x], point, B); + CrossProduct(A, B, normal); + + if (DotProduct(normal, plane.normal) == 0.0) + { + if (inedgecount < 2) + VectorSubtract(w.m_Points[(x + 1) % numpoints], w.m_Points[x], inedgedir[inedgecount]); + inedgecount++; + } + if (DotProduct(normal, plane.normal) < 0.0) + { + return false; + } + } + + switch (inedgecount) + { + case 0: + percentage = 1.0; + return true; + case 1: + percentage = 0.5; + return true; + case 2: + vec3_t tmp1, tmp2; + vec_t dot; + CrossProduct (inedgedir[0], ray, tmp1); + CrossProduct (inedgedir[1], ray, tmp2); + VectorNormalize (tmp1); + VectorNormalize (tmp2); + dot = DotProduct (tmp1, tmp2); + dot = dot>1? 1: dot<-1? -1: dot; + percentage = 0.5 - acos (dot) / (2 * Q_PI); + if (percentage < 0) + Warning ("internal error 1 in HLRAD_POINT_IN_EDGE_FIX"); + return true; + default: + Warning ("internal error 2 in HLRAD_POINT_IN_EDGE_FIX"); + return false; + } +} + +#endif +#endif +#ifndef HLRAD_LERP_VL +// ===================================================================================== +// point_in_wall +// ===================================================================================== +bool point_in_wall(const lerpWall_t* wall, vec3_t point) +{ + int x; + + // Liberal use of the magic number '4' for the hardcoded winding count + for (x = 0; x < 4; x++) + { + vec3_t A; + vec3_t B; + vec3_t normal; + + VectorSubtract(wall->vertex[x], wall->vertex[(x + 1) % 4], A); + VectorSubtract(wall->vertex[x], point, B); + CrossProduct(A, B, normal); +#ifndef HLRAD_MATH_VL + VectorNormalize(normal); +#endif + + if (DotProduct(normal, wall->plane.normal) < 0.0) + { + return false; + } + } + return true; +} + +// ===================================================================================== +// point_in_tri +// ===================================================================================== +bool point_in_tri(const vec3_t point, const dplane_t* const plane, const vec3_t p1, const vec3_t p2, const vec3_t p3) +{ + vec3_t A; + vec3_t B; + vec3_t normal; + + VectorSubtract(p1, p2, A); + VectorSubtract(p1, point, B); + CrossProduct(A, B, normal); +#ifndef HLRAD_MATH_VL + VectorNormalize(normal); +#endif + + if (DotProduct(normal, plane->normal) < 0.0) + { + return false; + } + + VectorSubtract(p2, p3, A); + VectorSubtract(p2, point, B); + CrossProduct(A, B, normal); +#ifndef HLRAD_MATH_VL + VectorNormalize(normal); +#endif + + if (DotProduct(normal, plane->normal) < 0.0) + { + return false; + } + + VectorSubtract(p3, p1, A); + VectorSubtract(p3, point, B); + CrossProduct(A, B, normal); +#ifndef HLRAD_MATH_VL + VectorNormalize(normal); +#endif + + if (DotProduct(normal, plane->normal) < 0.0) + { + return false; + } + return true; +} +#endif + +#ifdef HLRAD_TestSegmentAgainstOpaqueList_VL +bool intersect_linesegment_plane(const dplane_t* const plane, const vec_t* const p1, const vec_t* const p2, vec3_t point) +{ + vec_t part1; + vec_t part2; + int i; + part1 = DotProduct (p1, plane->normal) - plane->dist; + part2 = DotProduct (p2, plane->normal) - plane->dist; + if (part1 * part2 > 0 || part1 == part2) + return false; + for (i=0; i<3; ++i) + point[i] = (part1 * p2[i] - part2 * p1[i]) / (part1 - part2); + return true; +} +#else /*HLRAD_TestSegmentAgainstOpaqueList_VL*/ +// ===================================================================================== +// intersect_line_plane +// returns true if line hits plane, and parameter 'point' is filled with where +// ===================================================================================== +bool intersect_line_plane(const dplane_t* const plane, const vec_t* const p1, const vec_t* const p2, vec3_t point) +{ + vec3_t pop; + vec3_t line_vector; // normalized vector for the line; + vec3_t tmp; + vec3_t scaledDir; + vec_t partial; + vec_t total; + vec_t perc; + + // Get a normalized vector for the ray + VectorSubtract(p1, p2, line_vector); + VectorNormalize(line_vector); + + VectorScale(plane->normal, plane->dist, pop); + VectorSubtract(pop, p1, tmp); + partial = DotProduct(tmp, plane->normal); + total = DotProduct(line_vector, plane->normal); + + if (total == 0.0) + { + VectorClear(point); + return false; + } + + perc = partial / total; + VectorScale(line_vector, perc, scaledDir); + VectorAdd(p1, scaledDir, point); + return true; +} + +// ===================================================================================== +// intersect_linesegment_plane +// returns true if line hits plane, and parameter 'point' is filled with where +// ===================================================================================== +bool intersect_linesegment_plane(const dplane_t* const plane, const vec_t* const p1, const vec_t* const p2, vec3_t point) +{ + unsigned count = 0; + if (DotProduct(plane->normal, p1) <= plane->dist) + { + count++; + } + if (DotProduct(plane->normal, p2) <= plane->dist) + { + count++; + } + + if (count == 1) + { + return intersect_line_plane(plane, p1, p2, point); + } + else + { + return false; + } +} +#endif /*HLRAD_TestSegmentAgainstOpaqueList_VL*/ + +// ===================================================================================== +// plane_from_points +// ===================================================================================== +void plane_from_points(const vec3_t p1, const vec3_t p2, const vec3_t p3, dplane_t* plane) +{ + vec3_t delta1; + vec3_t delta2; + vec3_t normal; + + VectorSubtract(p3, p2, delta1); + VectorSubtract(p1, p2, delta2); + CrossProduct(delta1, delta2, normal); + VectorNormalize(normal); + plane->dist = DotProduct(normal, p1); + VectorCopy(normal, plane->normal); +} + +//LineSegmentIntersectsBounds --vluzacn +bool LineSegmentIntersectsBounds_r (const vec_t* p1, const vec_t* p2, const vec_t* mins, const vec_t* maxs, int d) +{ + vec_t lmin, lmax; + const vec_t* tmp; + vec3_t x1, x2; + int i; + d--; + if (p2[d]maxs[d]) + return false; + if (d==0) + return true; + lmin = p1[d]>=mins[d]? 0 : (mins[d]-p1[d])/(p2[d]-p1[d]); + lmax = p2[d]<=maxs[d]? 1 : (p2[d]-maxs[d])/(p2[d]-p1[d]); + for (i=0; inormal[0], + plane->normal[1], plane->normal[2], plane->dist); +#endif +#ifdef HLRAD_POINT_IN_EDGE_FIX + if (point_in_winding_percentage(*winding, *plane, point, direction, percentage)) +#else + if (point_in_winding(*winding, *plane, point)) +#endif + { +#if 0 + Log("Ray from (%4.3f %4.3f %4.3f) to (%4.3f %4.3f %4.3f) blocked by face %u @ (%4.3f %4.3f %4.3f)\n", + p1[0], p1[1], p1[2], + p2[0], p2[1], p2[2], g_opaque_face_list[x].facenum, point[0], point[1], point[2]); +#endif + +#ifdef HLRAD_HULLU + if(g_opaque_face_list[x].transparency) + { +#ifdef HLRAD_TestSegmentAgainstOpaqueList_VL + VectorCopy (g_opaque_face_list[x].transparency_scale, scale_one); +#endif + #ifdef HLRAD_POINT_IN_EDGE_FIX + if (percentage != 1.0) + for (i = 0; i < 3; ++i) + scale_one[i] = pow (scale_one[i], percentage); + #endif +#ifdef HLRAD_TestSegmentAgainstOpaqueList_VL + VectorMultiply(scale, scale_one, scale); +#else + VectorMultiply(scale, g_opaque_face_list[x].transparency_scale, scale); +#endif + } + else + { +#ifdef HLRAD_OPAQUE_STYLE + if (g_opaque_face_list[x].style == -1 || opaquestyleout != -1 && g_opaque_face_list[x].style != opaquestyleout) + { + VectorCopy(vec3_origin, scaleout); + opaquestyleout = -1; + return true; + } + else + { + opaquestyleout = g_opaque_face_list[x].style; + } +#else + #ifdef HLRAD_TestSegmentAgainstOpaqueList_VL + VectorCopy(vec3_origin, scaleout); + #else + VectorCopy(scale, scaleout); + #endif + return true; +#endif + } +#else + return true; +#endif + } + } + } + +#ifdef HLRAD_HULLU + VectorCopy(scale, scaleout); + if(scaleout[0] < 0.01 && scaleout[1] < 0.01 && scaleout[2] < 0.01) + { + return true; //so much shadowing that result is same as with normal opaque face + } +#endif + + return false; +#endif /*HLRAD_OPAQUE_NODE*/ +} + +#ifndef HLRAD_MATH_VL +// ===================================================================================== +// ProjectionPoint +// ===================================================================================== +void ProjectionPoint(const vec_t* const v, const vec_t* const p, vec_t* rval) +{ + vec_t val; + vec_t mag; + + mag = DotProduct(p, p); +#ifdef SYSTEM_POSIX + if (mag == 0) + { + // division by zero seems to work just fine on x86; + // it returns nan and the program still works!! + // this causes a floating point exception on Alphas, so... + mag = 0.00000001; + } +#endif + val = DotProduct(v, p) / mag; + + VectorScale(p, val, rval); +} +#endif + +// ===================================================================================== +// SnapToPlane +// ===================================================================================== +void SnapToPlane(const dplane_t* const plane, vec_t* const point, vec_t offset) +{ +#ifdef HLRAD_MATH_VL + vec_t dist; + dist = DotProduct (point, plane->normal) - plane->dist; + dist -= offset; + VectorMA (point, -dist, plane->normal, point); +#else + vec3_t delta; + vec3_t proj; + vec3_t pop; // point on plane + + VectorScale(plane->normal, plane->dist + offset, pop); + VectorSubtract(point, pop, delta); + ProjectionPoint(delta, plane->normal, proj); + VectorSubtract(delta, proj, delta); + VectorAdd(delta, pop, point); +#endif +} +#ifdef HLRAD_ACCURATEBOUNCE +// ===================================================================================== +// CalcSightArea +// ===================================================================================== +vec_t CalcSightArea (const vec3_t receiver_origin, const vec3_t receiver_normal, const Winding *emitter_winding, int skylevel + #ifdef HLRAD_DIVERSE_LIGHTING + , vec_t lighting_power, vec_t lighting_scale + #endif + ) +{ + // maybe there are faster ways in calculating the weighted area, but at least this way is not bad. + vec_t area = 0.0; + + int numedges = emitter_winding->m_NumPoints; + vec3_t *edges = (vec3_t *)malloc (numedges * sizeof (vec3_t)); + hlassume (edges != NULL, assume_NoMemory); + bool error = false; + for (int x = 0; x < numedges; x++) + { + vec3_t v1, v2, normal; + VectorSubtract (emitter_winding->m_Points[x], receiver_origin, v1); + VectorSubtract (emitter_winding->m_Points[(x + 1) % numedges], receiver_origin, v2); + CrossProduct (v1, v2, normal); // pointing inward + if (!VectorNormalize (normal)) + { + error = true; + } + VectorCopy (normal, edges[x]); + } + if (!error) + { + int i, j; + vec3_t *pnormal; + vec_t *psize; + vec_t dot; + vec3_t *pedge; + for (i = 0, pnormal = g_skynormals[skylevel], psize = g_skynormalsizes[skylevel]; i < g_numskynormals[skylevel]; i++, pnormal++, psize++) + { + dot = DotProduct (*pnormal, receiver_normal); + if (dot <= 0) + continue; + for (j = 0, pedge = edges; j < numedges; j++, pedge++) + { + if (DotProduct (*pnormal, *pedge) <= 0) + { + break; + } + } + if (j < numedges) + { + continue; + } + #ifdef HLRAD_DIVERSE_LIGHTING + if (lighting_power != 1.0) + { + dot = pow (dot, lighting_power); + } + #endif + area += dot * (*psize); + } + area = area * 4 * Q_PI; // convert to absolute sphere area + } + free (edges); +#ifdef HLRAD_DIVERSE_LIGHTING + area *= lighting_scale; +#endif + return area; +} + +#ifdef HLRAD_CUSTOMTEXLIGHT +vec_t CalcSightArea_SpotLight (const vec3_t receiver_origin, const vec3_t receiver_normal, const Winding *emitter_winding, const vec3_t emitter_normal, vec_t emitter_stopdot, vec_t emitter_stopdot2, int skylevel + #ifdef HLRAD_DIVERSE_LIGHTING + , vec_t lighting_power, vec_t lighting_scale + #endif + ) +{ + // stopdot = cos (cone) + // stopdot2 = cos (cone2) + // stopdot >= stopdot2 >= 0 + // ratio = 1.0 , when dot2 >= stopdot + // ratio = (dot - stopdot2) / (stopdot - stopdot2) , when stopdot > dot2 > stopdot2 + // ratio = 0.0 , when stopdot2 >= dot2 + vec_t area = 0.0; + + int numedges = emitter_winding->m_NumPoints; + vec3_t *edges = (vec3_t *)malloc (numedges * sizeof (vec3_t)); + hlassume (edges != NULL, assume_NoMemory); + bool error = false; + for (int x = 0; x < numedges; x++) + { + vec3_t v1, v2, normal; + VectorSubtract (emitter_winding->m_Points[x], receiver_origin, v1); + VectorSubtract (emitter_winding->m_Points[(x + 1) % numedges], receiver_origin, v2); + CrossProduct (v1, v2, normal); // pointing inward + if (!VectorNormalize (normal)) + { + error = true; + } + VectorCopy (normal, edges[x]); + } + if (!error) + { + int i, j; + vec3_t *pnormal; + vec_t *psize; + vec_t dot; + vec_t dot2; + vec3_t *pedge; + for (i = 0, pnormal = g_skynormals[skylevel], psize = g_skynormalsizes[skylevel]; i < g_numskynormals[skylevel]; i++, pnormal++, psize++) + { + dot = DotProduct (*pnormal, receiver_normal); + if (dot <= 0) + continue; + for (j = 0, pedge = edges; j < numedges; j++, pedge++) + { + if (DotProduct (*pnormal, *pedge) <= 0) + { + break; + } + } + if (j < numedges) + { + continue; + } + #ifdef HLRAD_DIVERSE_LIGHTING + if (lighting_power != 1.0) + { + dot = pow (dot, lighting_power); + } + #endif + dot2 = -DotProduct (*pnormal, emitter_normal); + if (dot2 <= emitter_stopdot2 + NORMAL_EPSILON) + { + dot = 0; + } + else if (dot2 < emitter_stopdot) + { + dot = dot * (dot2 - emitter_stopdot2) / (emitter_stopdot - emitter_stopdot2); + } + area += dot * (*psize); + } + area = area * 4 * Q_PI; // convert to absolute sphere area + } + free (edges); +#ifdef HLRAD_DIVERSE_LIGHTING + area *= lighting_scale; +#endif + return area; +} + +#endif +#endif +#ifdef HLRAD_ACCURATEBOUNCE_ALTERNATEORIGIN +// ===================================================================================== +// GetAlternateOrigin +// ===================================================================================== +void GetAlternateOrigin (const vec3_t pos, const vec3_t normal, const patch_t *patch, vec3_t &origin) +{ + const dplane_t *faceplane; + const vec_t *faceplaneoffset; + const vec_t *facenormal; + dplane_t clipplane; + Winding w; + + faceplane = getPlaneFromFaceNumber (patch->faceNumber); + faceplaneoffset = g_face_offset[patch->faceNumber]; + facenormal = faceplane->normal; + VectorCopy (normal, clipplane.normal); + clipplane.dist = DotProduct (pos, clipplane.normal); + + w = *patch->winding; + if (w.WindingOnPlaneSide (clipplane.normal, clipplane.dist) != SIDE_CROSS) + { + VectorCopy (patch->origin, origin); + } + else + { + w.Clip (clipplane, false); + if (w.m_NumPoints == 0) + { + VectorCopy (patch->origin, origin); + } + else + { + vec3_t center; + bool found; + vec3_t bestpoint; + vec_t bestdist = -1.0; + vec3_t point; + vec_t dist; + vec3_t v; + + w.getCenter (center); + found = false; + + VectorMA (center, PATCH_HUNT_OFFSET, facenormal, point); + if (HuntForWorld (point, faceplaneoffset, faceplane, 2, 1.0, PATCH_HUNT_OFFSET)) + { + VectorSubtract (point, center, v); + dist = VectorLength (v); + if (!found || dist < bestdist) + { + found = true; + VectorCopy (point, bestpoint); + bestdist = dist; + } + } + if (!found) + { + for (int i = 0; i < w.m_NumPoints; i++) + { + const vec_t *p1; + const vec_t *p2; + p1 = w.m_Points[i]; + p2 = w.m_Points[(i + 1) % w.m_NumPoints]; + VectorAdd (p1, p2, point); + VectorAdd (point, center, point); + VectorScale (point, 1.0/3.0, point); + VectorMA (point, PATCH_HUNT_OFFSET, facenormal, point); + if (HuntForWorld (point, faceplaneoffset, faceplane, 1, 0.0, PATCH_HUNT_OFFSET)) + { + VectorSubtract (point, center, v); + dist = VectorLength (v); + if (!found || dist < bestdist) + { + found = true; + VectorCopy (point, bestpoint); + bestdist = dist; + } + } + } + } + + if (found) + { + VectorCopy (bestpoint, origin); + } + else + { + VectorCopy (patch->origin, origin); + } + } + } +} + +#endif diff --git a/src/zhlt-vluzacn/hlrad/nomatrix.cpp b/src/zhlt-vluzacn/hlrad/nomatrix.cpp new file mode 100644 index 0000000..77f36ee --- /dev/null +++ b/src/zhlt-vluzacn/hlrad/nomatrix.cpp @@ -0,0 +1,330 @@ +#include "qrad.h" + +// ===================================================================================== +// CheckVisBit +// ===================================================================================== +static bool CheckVisBitNoVismatrix(unsigned patchnum1, unsigned patchnum2 +#ifdef HLRAD_HULLU + , vec3_t &transparency_out +#ifdef HLRAD_TRANSPARENCY_CPP + , unsigned int & +#endif +#endif + ) + // patchnum1=receiver, patchnum2=emitter. //HLRAD_CheckVisBitNoVismatrix_NOSWAP +{ +#ifdef HLRAD_HULLU +#ifndef HLRAD_CheckVisBitNoVismatrix_NOSWAP + // This fix was in vismatrix and sparse methods but not in nomatrix + // Without this nomatrix causes SwapTransfers output lots of errors + if (patchnum1 > patchnum2) + { + const unsigned a = patchnum1; + const unsigned b = patchnum2; + patchnum1 = b; + patchnum2 = a; + } +#endif + + if (patchnum1 > g_num_patches) + { + Warning("in CheckVisBit(), patchnum1 > num_patches"); + } + if (patchnum2 > g_num_patches) + { + Warning("in CheckVisBit(), patchnum2 > num_patches"); + } +#endif + + patch_t* patch = &g_patches[patchnum1]; + patch_t* patch2 = &g_patches[patchnum2]; + +#ifdef HLRAD_HULLU + VectorFill(transparency_out, 1.0); +#endif + + // if emitter is behind that face plane, skip all patches + + if (patch2) + { + const dplane_t* plane2 = getPlaneFromFaceNumber(patch2->faceNumber); + +#ifdef HLRAD_ACCURATEBOUNCE_ALTERNATEORIGIN + if (DotProduct (patch->origin, plane2->normal) > PatchPlaneDist (patch2) + ON_EPSILON - patch->emitter_range) +#else + if (DotProduct(patch->origin, plane2->normal) > (PatchPlaneDist(patch2) + MINIMUM_PATCH_DISTANCE)) +#endif + { + // we need to do a real test + + const dplane_t* plane = getPlaneFromFaceNumber(patch->faceNumber); + +#ifdef HLRAD_HULLU + vec3_t transparency = {1.0,1.0,1.0}; +#endif +#ifdef HLRAD_OPAQUE_STYLE + int opaquestyle = -1; +#endif + + // check vis between patch and patch2 + // if v2 is not behind light plane + // && v2 is visible from v1 +#ifdef HLRAD_ACCURATEBOUNCE_ALTERNATEORIGIN + vec3_t origin1, origin2; + vec3_t delta; + vec_t dist; + VectorSubtract (patch->origin, patch2->origin, delta); + dist = VectorLength (delta); + if (dist < patch2->emitter_range - ON_EPSILON) + { + GetAlternateOrigin (patch->origin, plane->normal, patch2, origin2); + } + else + { + VectorCopy (patch2->origin, origin2); + } + if (DotProduct (origin2, plane->normal) <= PatchPlaneDist (patch) + MINIMUM_PATCH_DISTANCE) + { + return false; + } + if (dist < patch->emitter_range - ON_EPSILON) + { + GetAlternateOrigin (patch2->origin, plane2->normal, patch, origin1); + } + else + { + VectorCopy (patch->origin, origin1); + } + if (DotProduct (origin1, plane2->normal) <= PatchPlaneDist (patch2) + MINIMUM_PATCH_DISTANCE) + { + return false; + } +#else + if (DotProduct(patch2->origin, plane->normal) <= (PatchPlaneDist(patch) + MINIMUM_PATCH_DISTANCE)) + { + return false; + } +#endif +#ifdef HLRAD_WATERBLOCKLIGHT + if (TestLine( + #ifdef HLRAD_ACCURATEBOUNCE_ALTERNATEORIGIN + origin1, origin2 + #else + patch->origin, patch2->origin + #endif + ) != CONTENTS_EMPTY) +#else + if (TestLine_r(0, + #ifdef HLRAD_ACCURATEBOUNCE_ALTERNATEORIGIN + origin1, origin2 + #else + patch->origin, patch2->origin + #endif + ) != CONTENTS_EMPTY) +#endif + { + return false; + } + if (TestSegmentAgainstOpaqueList( +#ifdef HLRAD_ACCURATEBOUNCE_ALTERNATEORIGIN + origin1, origin2 +#else + patch->origin, patch2->origin +#endif +#ifdef HLRAD_HULLU + , transparency +#endif +#ifdef HLRAD_OPAQUE_STYLE + , opaquestyle +#endif + )) + { + return false; + } + + { +#ifdef HLRAD_OPAQUE_STYLE_BOUNCE + if (opaquestyle != -1) + { + AddStyleToStyleArray (patchnum1, patchnum2, opaquestyle); + } +#endif +#ifdef HLRAD_HULLU + if(g_customshadow_with_bouncelight) + { + VectorCopy(transparency, transparency_out); + } +#endif + return true; + } + } + } + + return false; +} +#ifdef HLRAD_TRANSLUCENT + bool CheckVisBitBackwards(unsigned receiver, unsigned emitter, const vec3_t &backorigin, const vec3_t &backnormal +#ifdef HLRAD_HULLU + , vec3_t &transparency_out +#endif + ) +{ + patch_t* patch = &g_patches[receiver]; + patch_t* emitpatch = &g_patches[emitter]; + +#ifdef HLRAD_HULLU + VectorFill(transparency_out, 1.0); +#endif + + if (emitpatch) + { + const dplane_t* emitplane = getPlaneFromFaceNumber(emitpatch->faceNumber); + + if (DotProduct(backorigin, emitplane->normal) > (PatchPlaneDist(emitpatch) + MINIMUM_PATCH_DISTANCE)) + { + +#ifdef HLRAD_HULLU + vec3_t transparency = {1.0,1.0,1.0}; +#endif +#ifdef HLRAD_OPAQUE_STYLE + int opaquestyle = -1; +#endif + +#ifdef HLRAD_ACCURATEBOUNCE_ALTERNATEORIGIN + vec3_t emitorigin; + vec3_t delta; + vec_t dist; + VectorSubtract (backorigin, emitpatch->origin, delta); + dist = VectorLength (delta); + if (dist < emitpatch->emitter_range - ON_EPSILON) + { + GetAlternateOrigin (backorigin, backnormal, emitpatch, emitorigin); + } + else + { + VectorCopy (emitpatch->origin, emitorigin); + } + if (DotProduct (emitorigin, backnormal) <= DotProduct (backorigin, backnormal) + MINIMUM_PATCH_DISTANCE) + { + return false; + } +#else + if (DotProduct(emitpatch->origin, backnormal) <= (DotProduct(backorigin, backnormal) + MINIMUM_PATCH_DISTANCE)) + { + return false; + } +#endif +#ifdef HLRAD_WATERBLOCKLIGHT + if (TestLine( + #ifdef HLRAD_ACCURATEBOUNCE_ALTERNATEORIGIN + backorigin, emitorigin + #else + backorigin, emitpatch->origin + #endif + ) != CONTENTS_EMPTY) +#else + if (TestLine_r(0, + #ifdef HLRAD_ACCURATEBOUNCE_ALTERNATEORIGIN + backorigin, emitorigin + #else + backorigin, emitpatch->origin + #endif + ) != CONTENTS_EMPTY) +#endif + { + return false; + } + if (TestSegmentAgainstOpaqueList( +#ifdef HLRAD_ACCURATEBOUNCE_ALTERNATEORIGIN + backorigin, emitorigin +#else + backorigin, emitpatch->origin +#endif +#ifdef HLRAD_HULLU + , transparency +#endif +#ifdef HLRAD_OPAQUE_STYLE + , opaquestyle +#endif + )) + { + return false; + } + + { +#ifdef HLRAD_OPAQUE_STYLE_BOUNCE + if (opaquestyle != -1) + { + AddStyleToStyleArray (receiver, emitter, opaquestyle); + } +#endif +#ifdef HLRAD_HULLU + if(g_customshadow_with_bouncelight) + { + VectorCopy(transparency, transparency_out); + } +#endif + return true; + } + } + } + + return false; +} +#endif + +// +// end old vismat.c +//////////////////////////// + +void MakeScalesNoVismatrix() +{ + char transferfile[_MAX_PATH]; + + hlassume(g_num_patches < MAX_PATCHES, assume_MAX_PATCHES); + +#ifdef ZHLT_DEFAULTEXTENSION_FIX + safe_snprintf(transferfile, _MAX_PATH, "%s.inc", g_Mapname); +#else + safe_strncpy(transferfile, g_source, _MAX_PATH); + StripExtension(transferfile); + DefaultExtension(transferfile, ".inc"); +#endif + + if (!g_incremental || !readtransfers(transferfile, g_num_patches)) + { + g_CheckVisBit = CheckVisBitNoVismatrix; +#ifndef HLRAD_HULLU + NamedRunThreadsOn(g_num_patches, g_estimate, MakeScales); +#else + if(g_rgb_transfers) + {NamedRunThreadsOn(g_num_patches, g_estimate, MakeRGBScales);} + else + {NamedRunThreadsOn(g_num_patches, g_estimate, MakeScales);} +#endif + +#ifndef HLRAD_NOSWAP + // invert the transfers for gather vs scatter +#ifndef HLRAD_HULLU + NamedRunThreadsOnIndividual(g_num_patches, g_estimate, SwapTransfers); +#else + if(g_rgb_transfers) + {NamedRunThreadsOnIndividual(g_num_patches, g_estimate, SwapRGBTransfers);} + else + {NamedRunThreadsOnIndividual(g_num_patches, g_estimate, SwapTransfers);} +#endif +#endif /*HLRAD_NOSWAP*/ + if (g_incremental) + { + writetransfers(transferfile, g_num_patches); + } + else + { + unlink(transferfile); + } + DumpTransfersMemoryUsage(); +#ifdef HLRAD_OPAQUE_STYLE_BOUNCE + CreateFinalStyleArrays ("dynamic shadow array"); +#endif + } +} diff --git a/src/zhlt-vluzacn/hlrad/qrad.cpp b/src/zhlt-vluzacn/hlrad/qrad.cpp new file mode 100644 index 0000000..7235d18 --- /dev/null +++ b/src/zhlt-vluzacn/hlrad/qrad.cpp @@ -0,0 +1,5919 @@ +/* + + R A D I O S I T Y -aka- R A D + + Code based on original code from Valve Software, + Modified by Sean "Zoner" Cavanaugh (seanc@gearboxsoftware.com) with permission. + Modified by Tony "Merl" Moore (merlinis@bigpond.net.au) [AJM] + +*/ + +#ifdef SYSTEM_WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#endif + +#include +#include + +#include "qrad.h" + + +/* + * NOTES + * ----- + * every surface must be divided into at least two g_patches each axis + */ + +#ifdef ZHLT_XASH +int g_dlitdatasize = 0; +int g_max_map_dlitdata = 0; +byte *g_ddlitdata = NULL; +char g_dlitfile[_MAX_PATH] = ""; +bool g_drawdirection = false; +vec_t g_directionscale = 0.0; +#endif +#ifdef HLRAD_FASTMODE +bool g_fastmode = DEFAULT_FASTMODE; +#endif +typedef enum +{ + eMethodVismatrix, + eMethodSparseVismatrix, + eMethodNoVismatrix +} +eVisMethods; + +#ifdef HLRAD_ARG_MISC +eVisMethods g_method = DEFAULT_METHOD; +#else +eVisMethods g_method = eMethodVismatrix; +#endif + +vec_t g_fade = DEFAULT_FADE; +#ifndef HLRAD_ARG_MISC +int g_falloff = DEFAULT_FALLOFF; +#endif + +patch_t* g_face_patches[MAX_MAP_FACES]; +entity_t* g_face_entity[MAX_MAP_FACES]; +eModelLightmodes g_face_lightmode[MAX_MAP_FACES]; +#ifdef HLRAD_MORE_PATCHES +patch_t* g_patches; +#else +patch_t g_patches[MAX_PATCHES]; +#endif +#ifdef HLRAD_CUSTOMTEXLIGHT +entity_t* g_face_texlights[MAX_MAP_FACES]; +#endif +unsigned g_num_patches; + +#ifdef ZHLT_TEXLIGHT +#ifdef HLRAD_MORE_PATCHES +static vec3_t (*emitlight)[MAXLIGHTMAPS]; //LRC +static vec3_t (*addlight)[MAXLIGHTMAPS]; //LRC +#else +static vec3_t emitlight[MAX_PATCHES][MAXLIGHTMAPS]; //LRC +static vec3_t addlight[MAX_PATCHES][MAXLIGHTMAPS]; //LRC +#endif +#ifdef ZHLT_XASH +#ifdef HLRAD_MORE_PATCHES +static vec3_t (*emitlight_direction)[MAXLIGHTMAPS]; +static vec3_t (*addlight_direction)[MAXLIGHTMAPS]; +#else +static vec3_t emitlight_direction[MAX_PATCHES][MAXLIGHTMAPS]; +static vec3_t addlight_direction[MAX_PATCHES][MAXLIGHTMAPS]; +#endif +#endif +#ifdef HLRAD_AUTOCORING +#ifdef HLRAD_MORE_PATCHES +static unsigned char (*newstyles)[MAXLIGHTMAPS]; +#else +static unsigned char newstyles[MAX_PATCHES][MAXLIGHTMAPS]; +#endif +#endif +#else +#ifdef HLRAD_MORE_PATCHES +static vec3_t (*emitlight); +static vec3_t (*addlight); +#else +static vec3_t emitlight[MAX_PATCHES]; +static vec3_t addlight[MAX_PATCHES]; +#endif +#endif + +vec3_t g_face_offset[MAX_MAP_FACES]; // for rotating bmodels + +vec_t g_direct_scale = DEFAULT_DLIGHT_SCALE; + +unsigned g_numbounce = DEFAULT_BOUNCE; // 3; /* Originally this was 8 */ + +static bool g_dumppatches = DEFAULT_DUMPPATCHES; + +vec3_t g_ambient = { DEFAULT_AMBIENT_RED, DEFAULT_AMBIENT_GREEN, DEFAULT_AMBIENT_BLUE }; +#ifndef HLRAD_FinalLightFace_VL +float g_maxlight = DEFAULT_MAXLIGHT; // 196 /* Originally this was 196 */ +#endif +#ifdef HLRAD_PRESERVELIGHTMAPCOLOR +vec_t g_limitthreshold = DEFAULT_LIMITTHRESHOLD; +bool g_drawoverload = false; +#endif + +float g_lightscale = DEFAULT_LIGHTSCALE; +float g_dlight_threshold = DEFAULT_DLIGHT_THRESHOLD; // was DIRECT_LIGHT constant + +char g_source[_MAX_PATH] = ""; + +char g_vismatfile[_MAX_PATH] = ""; +bool g_incremental = DEFAULT_INCREMENTAL; +#ifndef HLRAD_WHOME +float g_qgamma = DEFAULT_GAMMA; +#endif +float g_indirect_sun = DEFAULT_INDIRECT_SUN; +bool g_extra = DEFAULT_EXTRA; +bool g_texscale = DEFAULT_TEXSCALE; + +float g_smoothing_threshold; +float g_smoothing_value = DEFAULT_SMOOTHING_VALUE; +#ifdef HLRAD_CUSTOMSMOOTH +float g_smoothing_threshold_2; +float g_smoothing_value_2 = DEFAULT_SMOOTHING2_VALUE; +#endif + +bool g_circus = DEFAULT_CIRCUS; +bool g_allow_opaques = DEFAULT_ALLOW_OPAQUES; +#ifdef HLRAD_SUNSPREAD +bool g_allow_spread = DEFAULT_ALLOW_SPREAD; +#endif + +// -------------------------------------------------------------------------- +// Changes by Adam Foster - afoster@compsoc.man.ac.uk +#ifdef HLRAD_WHOME +vec3_t g_colour_qgamma = { DEFAULT_COLOUR_GAMMA_RED, DEFAULT_COLOUR_GAMMA_GREEN, DEFAULT_COLOUR_GAMMA_BLUE }; +vec3_t g_colour_lightscale = { DEFAULT_COLOUR_LIGHTSCALE_RED, DEFAULT_COLOUR_LIGHTSCALE_GREEN, DEFAULT_COLOUR_LIGHTSCALE_BLUE }; +vec3_t g_colour_jitter_hack = { DEFAULT_COLOUR_JITTER_HACK_RED, DEFAULT_COLOUR_JITTER_HACK_GREEN, DEFAULT_COLOUR_JITTER_HACK_BLUE }; +vec3_t g_jitter_hack = { DEFAULT_JITTER_HACK_RED, DEFAULT_JITTER_HACK_GREEN, DEFAULT_JITTER_HACK_BLUE }; +#ifndef HLRAD_ARG_MISC +bool g_diffuse_hack = DEFAULT_DIFFUSE_HACK; +bool g_spotlight_hack = DEFAULT_SPOTLIGHT_HACK; +#endif +#ifndef HLRAD_CUSTOMTEXLIGHT // no softlight hack +vec3_t g_softlight_hack = { DEFAULT_SOFTLIGHT_HACK_RED, DEFAULT_SOFTLIGHT_HACK_GREEN, DEFAULT_SOFTLIGHT_HACK_BLUE }; +float g_softlight_hack_distance = DEFAULT_SOFTLIGHT_HACK_DISTANCE; +#endif +#endif +// -------------------------------------------------------------------------- + +#ifdef HLRAD_HULLU +bool g_customshadow_with_bouncelight = DEFAULT_CUSTOMSHADOW_WITH_BOUNCELIGHT; +bool g_rgb_transfers = DEFAULT_RGB_TRANSFERS; +#endif + +#ifdef HLRAD_TRANSTOTAL_HACK +float g_transtotal_hack = DEFAULT_TRANSTOTAL_HACK; +#endif +#ifdef HLRAD_MINLIGHT +unsigned char g_minlight = DEFAULT_MINLIGHT; +#endif +#ifdef HLRAD_TRANSFERDATA_COMPRESS +float_type g_transfer_compress_type = DEFAULT_TRANSFER_COMPRESS_TYPE; +vector_type g_rgbtransfer_compress_type = DEFAULT_RGBTRANSFER_COMPRESS_TYPE; +#endif +#ifdef HLRAD_SOFTSKY +bool g_softsky = DEFAULT_SOFTSKY; +#endif +#ifdef HLRAD_OPAQUE_BLOCK +int g_blockopaque = DEFAULT_BLOCKOPAQUE; +#endif +#ifdef HLRAD_TEXTURE +bool g_notextures = DEFAULT_NOTEXTURES; +#endif +#ifdef HLRAD_REFLECTIVITY +vec_t g_texreflectgamma = DEFAULT_TEXREFLECTGAMMA; +vec_t g_texreflectscale = DEFAULT_TEXREFLECTSCALE; +#endif +#ifdef HLRAD_AVOIDWALLBLEED +bool g_bleedfix = DEFAULT_BLEEDFIX; +#endif +#ifdef HLRAD_DEBUG_DRAWPOINTS +bool g_drawpatch = false; +bool g_drawsample = false; +vec3_t g_drawsample_origin = {0,0,0}; +vec_t g_drawsample_radius = 0; +bool g_drawedge = false; +bool g_drawlerp = false; +#endif +#ifdef HLRAD_AVOIDWALLBLEED +bool g_drawnudge = false; +#endif + +// Cosine of smoothing angle(in radians) +float g_coring = DEFAULT_CORING; // Light threshold to force to blackness(minimizes lightmaps) +bool g_chart = DEFAULT_CHART; +bool g_estimate = DEFAULT_ESTIMATE; +bool g_info = DEFAULT_INFO; + +#ifdef ZHLT_PROGRESSFILE // AJM +char* g_progressfile = DEFAULT_PROGRESSFILE; // "-progressfile path" +#endif + +// Patch creation and subdivision criteria +bool g_subdivide = DEFAULT_SUBDIVIDE; +vec_t g_chop = DEFAULT_CHOP; +vec_t g_texchop = DEFAULT_TEXCHOP; + +// Opaque faces +opaqueList_t* g_opaque_face_list = NULL; +unsigned g_opaque_face_count = 0; +unsigned g_max_opaque_face_count = 0; // Current array maximum (used for reallocs) +#ifndef HLRAD_OPAQUE_NODE +#ifdef HLRAD_OPAQUE_GROUP +opaqueGroup_t g_opaque_group_list[MAX_OPAQUE_GROUP_COUNT]; +unsigned g_opaque_group_count = 0; +#endif +#endif +#ifdef HLRAD_STYLE_CORING +vec_t g_corings[ALLSTYLES]; +#endif +#ifdef HLRAD_TRANSLUCENT +vec3_t* g_translucenttextures = NULL; +vec_t g_translucentdepth = DEFAULT_TRANSLUCENTDEPTH; +#endif +#ifdef HLRAD_BLUR +vec_t g_blur = DEFAULT_BLUR; +#endif +#ifdef HLRAD_ACCURATEBOUNCE +bool g_noemitterrange = DEFAULT_NOEMITTERRANGE; +#endif +#ifdef HLRAD_TEXLIGHTGAP +vec_t g_texlightgap = DEFAULT_TEXLIGHTGAP; +#endif + +// Misc +int leafparents[MAX_MAP_LEAFS]; +int nodeparents[MAX_MAP_NODES]; +#ifdef HLRAD_READABLE_EXCEEDSTYLEWARNING +int stylewarningcount = 0; +int stylewarningnext = 1; +#endif +#ifdef HLRAD_AUTOCORING +vec_t g_maxdiscardedlight = 0; +vec3_t g_maxdiscardedpos = {0, 0, 0}; +#endif + +#ifdef ZHLT_INFO_COMPILE_PARAMETERS +// ===================================================================================== +// GetParamsFromEnt +// this function is called from parseentity when it encounters the +// info_compile_parameters entity. each tool should have its own version of this +// to handle its own specific settings. +// ===================================================================================== +void GetParamsFromEnt(entity_t* mapent) +{ + int iTmp; + float flTmp; + char szTmp[256]; //lightdata + const char* pszTmp; + + Log("\nCompile Settings detected from info_compile_parameters entity\n"); + + // lightdata(string) : "Lighting Data Memory" : "8192" + iTmp = IntForKey(mapent, "lightdata") * 1024; //lightdata + if (iTmp > g_max_map_lightdata) //--vluzacn + { + g_max_map_lightdata = iTmp; + sprintf_s(szTmp, "%i", g_max_map_lightdata); + Log("%30s [ %-9s ]\n", "Lighting Data Memory", szTmp); + } + + // verbose(choices) : "Verbose compile messages" : 0 = [ 0 : "Off" 1 : "On" ] + iTmp = IntForKey(mapent, "verbose"); + if (iTmp == 1) + { + g_verbose = true; + } + else if (iTmp == 0) + { + g_verbose = false; + } + Log("%30s [ %-9s ]\n", "Compile Option", "setting"); + Log("%30s [ %-9s ]\n", "Verbose Compile Messages", g_verbose ? "on" : "off"); + + // estimate(choices) :"Estimate Compile Times?" : 0 = [ 0: "Yes" 1: "No" ] + if (IntForKey(mapent, "estimate")) + { + g_estimate = true; + } + else + { + g_estimate = false; + } + Log("%30s [ %-9s ]\n", "Estimate Compile Times", g_estimate ? "on" : "off"); + + // priority(choices) : "Priority Level" : 0 = [ 0 : "Normal" 1 : "High" -1 : "Low" ] + if (!strcmp(ValueForKey(mapent, "priority"), "1")) + { + g_threadpriority = eThreadPriorityHigh; + Log("%30s [ %-9s ]\n", "Thread Priority", "high"); + } + else if (!strcmp(ValueForKey(mapent, "priority"), "-1")) + { + g_threadpriority = eThreadPriorityLow; + Log("%30s [ %-9s ]\n", "Thread Priority", "low"); + } + + // bounce(integer) : "Number of radiosity bounces" : 0 + iTmp = IntForKey(mapent, "bounce"); + if (iTmp) + { + g_numbounce = abs(iTmp); + Log("%30s [ %-9s ]\n", "Number of radiosity bounces", ValueForKey(mapent, "bounce")); + } + +#ifdef HLRAD_HULLU + iTmp = IntForKey(mapent, "customshadowwithbounce"); + if (iTmp) + { + g_customshadow_with_bouncelight = true; + Log("%30s [ %-9s ]\n", "Custom Shadow with Bounce Light", ValueForKey(mapent, "customshadowwithbounce")); + } + iTmp = IntForKey(mapent, "rgbtransfers"); + if (iTmp) + { + g_rgb_transfers = true; + Log("%30s [ %-9s ]\n", "RGB Transfers", ValueForKey(mapent, "rgbtransfers")); + } +#endif + + // ambient(string) : "Ambient world light (0.0 to 1.0, R G B)" : "0 0 0" + //vec3_t g_ambient = { DEFAULT_AMBIENT_RED, DEFAULT_AMBIENT_GREEN, DEFAULT_AMBIENT_BLUE }; + pszTmp = ValueForKey(mapent, "ambient"); + if (pszTmp) + { + float red = 0, green = 0, blue = 0; + if (sscanf(pszTmp, "%f %f %f", &red, &green, &blue)) + { + if (red < 0 || red > 1 || green < 0 || green > 1 || blue < 0 || blue > 1) + { + Error("info_compile_parameters: Ambient World Light (ambient) all 3 values must be within the range of 0.0 to 1.0\n" + "Parsed values:\n" + " red [ %1.3f ] %s\n" + " green [ %1.3f ] %s\n" + " blue [ %1.3f ] %s\n" + , red, (red < 0 || red > 1) ? "OUT OF RANGE" : "" + , green, (green < 0 || green > 1) ? "OUT OF RANGE" : "" + , blue, (blue < 0 || blue > 1) ? "OUT OF RANGE" : "" ); + } + + if (red == 0 && green == 0 && blue == 0) + {} // dont bother setting null values + else + { + g_ambient[0] = red * 128; + g_ambient[1] = green * 128; + g_ambient[2] = blue * 128; + Log("%30s [ %1.3f %1.3f %1.3f ]\n", "Ambient world light (R G B)", red, green, blue); + } + } + else + { + Error("info_compile_parameters: Ambient World Light (ambient) has unrecognised value\n" + "This keyvalue accepts 3 numeric values from 0.000 to 1.000, use \"0 0 0\" if in doubt"); + } + } + + // smooth(integer) : "Smoothing threshold (in degrees)" : 0 + flTmp = FloatForKey(mapent, "smooth"); + if (flTmp) + { + /*g_smoothing_threshold = flTmp;*/ + g_smoothing_threshold = cos(g_smoothing_value * (Q_PI / 180.0)); // --vluzacn + Log("%30s [ %-9s ]\n", "Smoothing threshold", ValueForKey(mapent, "smooth")); + } + + // dscale(integer) : "Direct Lighting Scale" : 1 + flTmp = FloatForKey(mapent, "dscale"); + if (flTmp) + { + g_direct_scale = flTmp; + Log("%30s [ %-9s ]\n", "Direct Lighting Scale", ValueForKey(mapent, "dscale")); + } + + // chop(integer) : "Chop Size" : 64 + iTmp = IntForKey(mapent, "chop"); + if (iTmp) + { + g_chop = iTmp; + Log("%30s [ %-9s ]\n", "Chop Size", ValueForKey(mapent, "chop")); + } + + // texchop(integer) : "Texture Light Chop Size" : 32 + flTmp = FloatForKey(mapent, "texchop"); + if (flTmp) + { + g_texchop = flTmp; + Log("%30s [ %-9s ]\n", "Texture Light Chop Size", ValueForKey(mapent, "texchop")); + } + + /* + hlrad(choices) : "HLRAD" : 0 = + [ + 0 : "Off" + 1 : "Normal" + 2 : "Extra" + ] + */ + iTmp = IntForKey(mapent, "hlrad"); + if (iTmp == 0) + { + Fatal(assume_TOOL_CANCEL, + "%s flag was not checked in info_compile_parameters entity, execution of %s cancelled", g_Program, g_Program); + CheckFatal(); + } + else if (iTmp == 1) + { + g_extra = false; + } + else if (iTmp == 2) + { + g_extra = true; + } + Log("%30s [ %-9s ]\n", "Extra RAD", g_extra ? "on" : "off"); + + /* + sparse(choices) : "Vismatrix Method" : 2 = + [ + 0 : "No Vismatrix" + 1 : "Sparse Vismatrix" + 2 : "Normal" + ] + */ + iTmp = IntForKey(mapent, "sparse"); + if (iTmp == 1) + { + g_method = eMethodSparseVismatrix; + } + else if (iTmp == 0) + { + g_method = eMethodNoVismatrix; + } + else if (iTmp == 2) + { + g_method = eMethodVismatrix; + } + Log("%30s [ %-9s ]\n", "Sparse Vismatrix", g_method == eMethodSparseVismatrix ? "on" : "off"); + Log("%30s [ %-9s ]\n", "NoVismatrix", g_method == eMethodNoVismatrix ? "on" : "off"); + + /* + circus(choices) : "Circus RAD lighting" : 0 = + [ + 0 : "Off" + 1 : "On" + ] + */ + iTmp = IntForKey(mapent, "circus"); + if (iTmp == 0) + { + g_circus = false; + } + else if (iTmp == 1) + { + g_circus = true; + } + + Log("%30s [ %-9s ]\n", "Circus Lighting Mode", g_circus ? "on" : "off"); + + //////////////////// + Log("\n"); +} +#endif + +// ===================================================================================== +// MakeParents +// blah +// ===================================================================================== +static void MakeParents(const int nodenum, const int parent) +{ + int i; + int j; + dnode_t* node; + + nodeparents[nodenum] = parent; + node = g_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 +// +// ===================================================================================== + +// misc +typedef struct +{ + std::string name; + vec3_t value; + const char* filename; +} +texlight_t; + +static std::vector< texlight_t > s_texlights; +typedef std::vector< texlight_t >::iterator texlight_i; + +// ===================================================================================== +// ReadLightFile +// ===================================================================================== +static void ReadLightFile(const char* const filename) +{ + FILE* f; + char scan[MAXTOKEN]; + short argCnt; + unsigned int file_texlights = 0; + + f = fopen(filename, "r"); + if (!f) + { + Warning("Could not open texlight file %s", filename); + return; + } + else + { +#ifdef HLRAD_CUSTOMTEXLIGHT + Log("Reading texlights from '%s'\n", filename); +#else + Log("[Reading texlights from '%s']\n", filename); +#endif + } + + while (fgets(scan, sizeof(scan), f)) + { + char* comment; + char szTexlight[_MAX_PATH]; + vec_t r, g, b, i = 1; + + comment = strstr(scan, "//"); + if (comment) + { + // Newline and Null terminate the string early if there is a c++ style single line comment + comment[0] = '\n'; + comment[1] = 0; + } + + argCnt = sscanf(scan, "%s %f %f %f %f", szTexlight, &r, &g, &b, &i); + + if (argCnt == 2) + { + // With 1+1 args, the R,G,B values are all equal to the first value + g = b = r; + } + else if (argCnt == 5) + { + // With 1+4 args, the R,G,B values are "scaled" by the fourth numeric value i; + r *= i / 255.0; + g *= i / 255.0; + b *= i / 255.0; + } + else if (argCnt != 4) + { + if (strlen(scan) > 4) + { + Warning("ignoring bad texlight '%s' in %s", scan, filename); + } + continue; + } + + texlight_i it; + for (it = s_texlights.begin(); it != s_texlights.end(); it++) + { + if (strcmp(it->name.c_str(), szTexlight) == 0) + { + if (strcmp(it->filename, filename) == 0) + { + Warning("Duplication of texlight '%s' in file '%s'!", it->name.c_str(), it->filename); + } + else if (it->value[0] != r || it->value[1] != g || it->value[2] != b) + { + Warning("Overriding '%s' from '%s' with '%s'!", it->name.c_str(), it->filename, filename); + } + else + { + Warning("Redundant '%s' def in '%s' AND '%s'!", it->name.c_str(), it->filename, filename); + } + s_texlights.erase(it); + break; + } + } + + texlight_t texlight; + texlight.name = szTexlight; + texlight.value[0] = r; + texlight.value[1] = g; + texlight.value[2] = b; + texlight.filename = filename; + file_texlights++; + s_texlights.push_back(texlight); + } + fclose (f); //--vluzacn +#ifndef HLRAD_CUSTOMTEXLIGHT + Log("[%u texlights parsed from '%s']\n\n", file_texlights, filename); +#endif +} + +// ===================================================================================== +// LightForTexture +// ===================================================================================== +static void LightForTexture(const char* const name, vec3_t result) +{ + texlight_i it; + for (it = s_texlights.begin(); it != s_texlights.end(); it++) + { + if (!strcasecmp(name, it->name.c_str())) + { + VectorCopy(it->value, result); + return; + } + } + VectorClear(result); +} + + +// ===================================================================================== +// +// MAKE FACES +// +// ===================================================================================== + +// ===================================================================================== +// BaseLightForFace +// ===================================================================================== +static void BaseLightForFace(const dface_t* const f, vec3_t light) +{ +#ifdef HLRAD_CUSTOMTEXLIGHT + int fn = f - g_dfaces; + if (g_face_texlights[fn]) + { + double r, g, b, scaler; + switch (sscanf (ValueForKey (g_face_texlights[fn], "_light"), "%lf %lf %lf %lf", &r, &g, &b, &scaler)) + { + case -1: + case 0: + r = 0.0; + case 1: + g = b = r; + case 3: + break; + case 4: + r *= scaler / 255.0; + g *= scaler / 255.0; + b *= scaler / 255.0; + break; + default: + vec3_t origin; + GetVectorForKey (g_face_texlights[fn], "origin", origin); + Log("light at (%f,%f,%f) has bad or missing '_light' value : '%s'\n", + origin[0], origin[1], origin[2], ValueForKey (g_face_texlights[fn], "_light")); + r = g = b = 0; + break; + } + light[0] = r > 0? r: 0; + light[1] = g > 0? g: 0; + light[2] = b > 0? b: 0; + return; + } +#endif + texinfo_t* tx; + miptex_t* mt; + int ofs; + + // + // check for light emited by texture + // + tx = &g_texinfo[f->texinfo]; + + ofs = ((dmiptexlump_t*)g_dtexdata)->dataofs[tx->miptex]; + mt = (miptex_t*)((byte*) g_dtexdata + ofs); + + LightForTexture(mt->name, light); +} + +// ===================================================================================== +// IsSpecial +// ===================================================================================== +static bool IsSpecial(const dface_t* const f) +{ + return g_texinfo[f->texinfo].flags & TEX_SPECIAL; +} + +// ===================================================================================== +// PlacePatchInside +// ===================================================================================== +static bool PlacePatchInside(patch_t* patch) +{ + const dplane_t* plane; + const vec_t* face_offset = g_face_offset[patch->faceNumber]; + + plane = getPlaneFromFaceNumber(patch->faceNumber); + +#ifdef HLRAD_PATCHBLACK_FIX +#ifdef HLRAD_ACCURATEBOUNCE_REDUCEAREA + vec_t pointsfound; + vec_t pointstested; + pointsfound = pointstested = 0; +#endif + vec3_t center; + bool found; + vec3_t bestpoint; + vec_t bestdist = -1.0; + vec3_t point; + vec_t dist; + vec3_t v; + + patch->winding->getCenter (center); + found = false; + + VectorMA (center, PATCH_HUNT_OFFSET, plane->normal, point); +#ifdef HLRAD_ACCURATEBOUNCE_REDUCEAREA + pointstested++; +#endif + if (HuntForWorld (point, face_offset, plane, 4, 0.2, PATCH_HUNT_OFFSET) || + HuntForWorld (point, face_offset, plane, 4, 0.8, PATCH_HUNT_OFFSET)) + { +#ifdef HLRAD_ACCURATEBOUNCE_REDUCEAREA + pointsfound++; +#endif + VectorSubtract (point, center, v); + dist = VectorLength (v); + if (!found || dist < bestdist) + { + found = true; + VectorCopy (point, bestpoint); + bestdist = dist; + } + } +#ifndef HLRAD_ACCURATEBOUNCE_REDUCEAREA + if (!found) +#endif + { + for (int i = 0; i < patch->winding->m_NumPoints; i++) + { + const vec_t *p1; + const vec_t *p2; + p1 = patch->winding->m_Points[i]; + p2 = patch->winding->m_Points[(i+1)%patch->winding->m_NumPoints]; + VectorAdd (p1, p2, point); + VectorAdd (point, center, point); + VectorScale (point, 1.0/3.0, point); + VectorMA (point, PATCH_HUNT_OFFSET, plane->normal, point); + #ifdef HLRAD_ACCURATEBOUNCE_REDUCEAREA + pointstested++; + #endif + if (HuntForWorld (point, face_offset, plane, 4, 0.2, PATCH_HUNT_OFFSET) || + HuntForWorld (point, face_offset, plane, 4, 0.8, PATCH_HUNT_OFFSET)) + { + #ifdef HLRAD_ACCURATEBOUNCE_REDUCEAREA + pointsfound++; + #endif + VectorSubtract (point, center, v); + dist = VectorLength (v); + if (!found || dist < bestdist) + { + found = true; + VectorCopy (point, bestpoint); + bestdist = dist; + } + } + } + } + +#ifdef HLRAD_ACCURATEBOUNCE_REDUCEAREA + patch->exposure = pointsfound / pointstested; +#endif + if (found) + { + VectorCopy (bestpoint, patch->origin); + patch->flags = ePatchFlagNull; + return true; + } + else + { + VectorMA (center, PATCH_HUNT_OFFSET, plane->normal, patch->origin); + patch->flags = ePatchFlagOutside; + Developer(DEVELOPER_LEVEL_FLUFF, "Patch @ (%4.3f %4.3f %4.3f) outside world\n", + patch->origin[0], patch->origin[1], patch->origin[2]); + return false; + } +#else // obviously who wrote these code misunderstood the function of HuntForWorld + if (!HuntForWorld(patch->origin, face_offset, plane, 11, 0.1, 0.01) && + !HuntForWorld(patch->origin, face_offset, plane, 11, 0.1, 0.1) && + !HuntForWorld(patch->origin, face_offset, plane, 11, 0.1, 0.5) && + !HuntForWorld(patch->origin, face_offset, plane, 11, 0.1, -0.01) && + !HuntForWorld(patch->origin, face_offset, plane, 11, 0.1, -0.1)) + { + // Try offsetting it by the plane normal (1 unit away) and try again + + VectorAdd(plane->normal, patch->origin, patch->origin); // Original offset-into-world method + if (PointInLeaf(patch->origin) == g_dleafs) + { + if (!HuntForWorld(patch->origin, face_offset, plane, 11, 0.1, 0.01) && + !HuntForWorld(patch->origin, face_offset, plane, 11, 0.1, 0.1) && + !HuntForWorld(patch->origin, face_offset, plane, 11, 0.1, 0.5) && + !HuntForWorld(patch->origin, face_offset, plane, 11, 0.1, -0.01) && + !HuntForWorld(patch->origin, face_offset, plane, 11, 0.1, -0.1)) + { + patch->flags = (ePatchFlags)(patch->flags | ePatchFlagOutside); + Developer(DEVELOPER_LEVEL_MESSAGE, "Patch @ (%4.3f %4.3f %4.3f) outside world\n", + patch->origin[0], patch->origin[1], patch->origin[2]); + return false; + } + } + } + + return true; +#endif +} +#ifdef HLRAD_ACCURATEBOUNCE +static void UpdateEmitterInfo (patch_t *patch) +{ +#if ACCURATEBOUNCE_DEFAULT_SKYLEVEL + 3 > SKYLEVELMAX +#error "please raise SKYLEVELMAX" +#endif + const vec_t *origin = patch->origin; + const Winding *winding = patch->winding; + vec_t radius = ON_EPSILON; + for (int x = 0; x < winding->m_NumPoints; x++) + { + vec3_t delta; + vec_t dist; + VectorSubtract (winding->m_Points[x], origin, delta); + dist = VectorLength (delta); + if (dist > radius) + { + radius = dist; + } + } + int skylevel = ACCURATEBOUNCE_DEFAULT_SKYLEVEL; + vec_t area = winding->getArea (); + vec_t size = 0.8f; + if (area < size * radius * radius) // the shape is too thin + { + skylevel++; + size *= 0.25f; + if (area < size * radius * radius) + { + skylevel++; + size *= 0.25f; + if (area < size * radius * radius) + { + // stop here + radius = sqrt (area / size); + // just decrease the range to limit the use of the new method. because when the area is small, the new method becomes randomized and unstable. + } + } + } + patch->emitter_range = ACCURATEBOUNCE_THRESHOLD * radius; + if (g_noemitterrange) + { + patch->emitter_range = 0.0; + } + patch->emitter_skylevel = skylevel; +} +#endif + + +// ===================================================================================== +// +// SUBDIVIDE PATCHES +// +// ===================================================================================== + +// misc +#define MAX_SUBDIVIDE 16384 +static Winding* windingArray[MAX_SUBDIVIDE]; +static unsigned g_numwindings = 0; + +#ifdef HLRAD_SUBDIVIDEPATCH_NEW +// ===================================================================================== +// cutWindingWithGrid +// Caller must free this returned value at some point +// ===================================================================================== +static void cutWindingWithGrid (patch_t *patch, const dplane_t *plA, const dplane_t *plB) + // This function has been rewritten because the original one is not totally correct and may fail to do what it claims. +{ + // patch->winding->m_NumPoints must > 0 + // plA->dist and plB->dist will not be used + Winding *winding = NULL; + vec_t chop; + vec_t epsilon; + const int max_gridsize = 64; + vec_t gridstartA; + vec_t gridstartB; + int gridsizeA; + int gridsizeB; + vec_t gridchopA; + vec_t gridchopB; + int numstrips; + + winding = new Winding (*patch->winding); // perform all the operations on the copy + chop = patch->chop; + chop = qmax (1.0, chop); + epsilon = 0.6; + + // optimize the grid + { + vec_t minA; + vec_t maxA; + vec_t minB; + vec_t maxB; + + minA = minB = BOGUS_RANGE; + maxA = maxB = -BOGUS_RANGE; + for (int x = 0; x < winding->m_NumPoints; x++) + { + vec_t *point; + vec_t dotA; + vec_t dotB; + point = winding->m_Points[x]; + dotA = DotProduct (point, plA->normal); + minA = qmin (minA, dotA); + maxA = qmax (maxA, dotA); + dotB = DotProduct (point, plB->normal); + minB = qmin (minB, dotB); + maxB = qmax (maxB, dotB); + } + + gridchopA = chop; + gridsizeA = (int)ceil ((maxA - minA - 2 * epsilon) / gridchopA); + gridsizeA = qmax (1, gridsizeA); + if (gridsizeA > max_gridsize) + { + gridsizeA = max_gridsize; + gridchopA = (maxA - minA) / (vec_t)gridsizeA; + } + gridstartA = (minA + maxA) / 2.0 - (gridsizeA / 2.0) * gridchopA; + + gridchopB = chop; + gridsizeB = (int)ceil ((maxB - minB - 2 * epsilon) / gridchopB); + gridsizeB = qmax (1, gridsizeB); + if (gridsizeB > max_gridsize) + { + gridsizeB = max_gridsize; + gridchopB = (maxB - minB) / (vec_t)gridsizeB; + } + gridstartB = (minB + maxB) / 2.0 - (gridsizeB / 2.0) * gridchopB; + } + + // cut the winding by the direction of plane A and save into windingArray + { + g_numwindings = 0; + for (int i = 1; i < gridsizeA; i++) + { + vec_t dist; + Winding *front = NULL; + Winding *back = NULL; + + dist = gridstartA + i * gridchopA; + winding->Clip (plA->normal, dist, &front, &back); + + if (!front || front->WindingOnPlaneSide (plA->normal, dist, epsilon) == SIDE_ON) // ended + { + if (front) + { + delete front; + front = NULL; + } + if (back) + { + delete back; + back = NULL; + } + break; + } + if (!back || back->WindingOnPlaneSide (plA->normal, dist, epsilon) == SIDE_ON) // didn't begin + { + if (front) + { + delete front; + front = NULL; + } + if (back) + { + delete back; + back = NULL; + } + continue; + } + + delete winding; + winding = NULL; + + windingArray[g_numwindings] = back; + g_numwindings++; + back = NULL; + + winding = front; + front = NULL; + } + + windingArray[g_numwindings] = winding; + g_numwindings++; + winding = NULL; + } + + // cut by the direction of plane B + { + numstrips = g_numwindings; + for (int i = 0; i < numstrips; i++) + { + Winding *strip = windingArray[i]; + windingArray[i] = NULL; + + for (int j = 1; j < gridsizeB; j++) + { + vec_t dist; + Winding *front = NULL; + Winding *back = NULL; + + dist = gridstartB + j * gridchopB; + strip->Clip (plB->normal, dist, &front, &back); + + if (!front || front->WindingOnPlaneSide (plB->normal, dist, epsilon) == SIDE_ON) // ended + { + if (front) + { + delete front; + front = NULL; + } + if (back) + { + delete back; + back = NULL; + } + break; + } + if (!back || back->WindingOnPlaneSide (plB->normal, dist, epsilon) == SIDE_ON) // didn't begin + { + if (front) + { + delete front; + front = NULL; + } + if (back) + { + delete back; + back = NULL; + } + continue; + } + + delete strip; + strip = NULL; + + windingArray[g_numwindings] = back; + g_numwindings++; + back = NULL; + + strip = front; + front = NULL; + } + + windingArray[g_numwindings] = strip; + g_numwindings++; + strip = NULL; + } + } + + delete patch->winding; + patch->winding = NULL; +} +#else +// ===================================================================================== +// AddWindingToArray +// ===================================================================================== +static void AddWindingToArray(Winding* winding) +{ + unsigned x; + + Winding** wA = windingArray; + + for (x = 0; x < g_numwindings; x++, wA++) + { + if (*wA == winding) + { + return; + } + } + + windingArray[g_numwindings++] = winding; +} + +static void CreateStrips_r(Winding* winding, const vec3_t plane_normal, const vec_t plane_dist, vec_t step) +{ + Winding* A; + Winding* B; + vec_t areaA; + vec_t areaB; + + winding->Clip(plane_normal, plane_dist + step, &A, &B); + + if (A && B) + { + areaA = A->getArea(); + areaB = B->getArea(); + if ((areaA > 1.0) && (areaB > 1.0)) + { + delete winding; + CreateStrips_r(A, plane_normal, plane_dist + step, step); + CreateStrips_r(B, plane_normal, plane_dist + step, step); + return; + } + } + else + { // Try the other direction + if (A) + { + delete A; + } + if (B) + { + delete B; + } + + winding->Clip(plane_normal, plane_dist - step, &A, &B); + + if (A && B) + { + areaA = A->getArea(); + areaB = B->getArea(); + if ((areaA > 1.0) && (areaB > 1.0)) + { + delete winding; + CreateStrips_r(A, plane_normal, plane_dist - step, step); + CreateStrips_r(B, plane_normal, plane_dist - step, step); + return; + } + } + } + + // Last recursion, save it into the list + if (A) + { + delete A; + } + if (B) + { + delete B; + } + + AddWindingToArray(winding); + hlassume(g_numwindings < MAX_SUBDIVIDE, assume_GENERIC); +} + +// ===================================================================================== +// CreateStrips +// ===================================================================================== +static bool CreateStrips(Winding* winding, const dplane_t* plane, vec_t step) +{ + Winding* A; + Winding* B; + vec_t areaA; + vec_t areaB; + + winding->Clip(plane->normal, plane->dist, &A, &B); + + if (A && B) + { + areaA = A->getArea(); + areaB = B->getArea(); + if ((areaA > 1.0) && (areaB > 1.0)) + { + CreateStrips_r(A, (vec_t*)plane->normal, plane->dist, step); + CreateStrips_r(B, (vec_t*)plane->normal, plane->dist, step); + return true; + } + } + + if (A) + { + delete A; + } + if (B) + { + delete B; + } + + AddWindingToArray(winding); + hlassume(g_numwindings < MAX_SUBDIVIDE, assume_GENERIC); + return false; +} + +// ===================================================================================== +// cutWindingWithGrid +// Caller must free this returned value at some point +// ===================================================================================== +static void cutWindingWithGrid(patch_t* patch, const dplane_t* const plA, const dplane_t* const plB) +{ + Winding** winding; + unsigned int count; + unsigned int x; + +#ifdef HLRAD_SubdividePatch_NOTMIDDLE + dplane_t plA_adjusted = *plA; + dplane_t plB_adjusted = *plB; + vec_t Amin, Amax, Bmin, Bmax; + vec_t Ashift, Ashiftmin, Ashiftmax; + vec_t Bshift, Bshiftmin, Bshiftmax; + Amin = Bmin = BOGUS_RANGE; + Amax = Bmax = -BOGUS_RANGE; + for (x = 0; x < patch->winding->m_NumPoints; x++) + { + vec_t A, B; + const vec3_t &p = patch->winding->m_Points[x]; + A = DotProduct (plA->normal, p) - plA->dist; + B = DotProduct (plB->normal, p) - plB->dist; + if (A < Amin) Amin = A; + if (A > Amax) Amax = A; + if (B < Bmin) Bmin = B; + if (B > Bmax) Bmax = B; + } + Amin /= patch->chop; + Amax /= patch->chop; + Ashiftmin = Amax - floor (Amax - NORMAL_EPSILON); + Ashiftmax = Amin - floor (Amin + NORMAL_EPSILON); + Ashift = Ashiftmin <= Ashiftmax + NORMAL_EPSILON? (Ashiftmin + Ashiftmax) / 2: 0; + if (Ashift > 0.5) Ashift -= 1; + plA_adjusted.dist += Ashift * patch->chop; + Bmin /= patch->chop; + Bmax /= patch->chop; + Bshiftmin = Bmax - floor (Bmax - NORMAL_EPSILON); + Bshiftmax = Bmin - floor (Bmin + NORMAL_EPSILON); + Bshift = Bshiftmin <= Bshiftmax + NORMAL_EPSILON? (Bshiftmin + Bshiftmax) / 2: 0; + if (Bshift > 0.5) Bshift -= 1; + plB_adjusted.dist += Bshift * patch->chop; +#endif + g_numwindings = 0; +#ifdef HLRAD_SubdividePatch_NOTMIDDLE + if (CreateStrips(patch->winding, &plA_adjusted, patch->chop)) +#else + if (CreateStrips(patch->winding, plA, patch->chop)) +#endif + { + delete patch->winding; + patch->winding = NULL; // Invalidated by CreateStrips routine + } + count = g_numwindings; + + for (x = 0, winding = windingArray; x < count; x++, winding++) + { +#ifdef HLRAD_SubdividePatch_NOTMIDDLE + if (CreateStrips(*winding, &plB_adjusted, patch->chop)) +#else + if (CreateStrips(*winding, plB, patch->chop)) +#endif + { + delete *winding; + *winding = NULL; + } + } +} +#endif + +// ===================================================================================== +// getGridPlanes +// From patch, determine perpindicular grid planes to subdivide with (returned in planeA and planeB) +// assume S and T is perpindicular (they SHOULD be in worldcraft 3.3 but aren't always . . .) +// ===================================================================================== +static void getGridPlanes(const patch_t* const p, dplane_t* const pl) +{ + const patch_t* patch = p; + dplane_t* planes = pl; + const dface_t* f = g_dfaces + patch->faceNumber; + texinfo_t* tx = &g_texinfo[f->texinfo]; + dplane_t* plane = planes; + const dplane_t* faceplane = getPlaneFromFaceNumber(patch->faceNumber); + int x; + + for (x = 0; x < 2; x++, plane++) + { +#ifdef ZHLT_FREETEXTUREAXIS + // cut the patch along texel grid planes + vec_t val; + val = DotProduct (faceplane->normal, tx->vecs[!x]); + VectorMA (tx->vecs[!x], -val, faceplane->normal, plane->normal); +#else + vec3_t a, b, c; + vec3_t delta1, delta2; + + VectorCopy(patch->origin, a); + VectorAdd(patch->origin, faceplane->normal, b); + VectorAdd(patch->origin, tx->vecs[x], c); + + VectorSubtract(b, a, delta1); + VectorSubtract(c, a, delta2); + + CrossProduct(delta1, delta2, plane->normal); +#endif + VectorNormalize(plane->normal); + plane->dist = DotProduct(plane->normal, patch->origin); + } +} + +// ===================================================================================== +// SubdividePatch +// ===================================================================================== +static void SubdividePatch(patch_t* patch) +{ + dplane_t planes[2]; + dplane_t* plA = &planes[0]; + dplane_t* plB = &planes[1]; + Winding** winding; + unsigned x; + patch_t* new_patch; + + memset(windingArray, 0, sizeof(windingArray)); + g_numwindings = 0; + + getGridPlanes(patch, planes); + cutWindingWithGrid(patch, plA, plB); + + x = 0; + patch->next = NULL; + winding = windingArray; + while (*winding == NULL) + { + winding++; + x++; + } + patch->winding = *winding; + winding++; + x++; + patch->area = patch->winding->getArea(); + patch->winding->getCenter(patch->origin); + PlacePatchInside(patch); +#ifdef HLRAD_ACCURATEBOUNCE + UpdateEmitterInfo (patch); +#endif + + new_patch = g_patches + g_num_patches; + for (; x < g_numwindings; x++, winding++) + { + if (*winding) + { + memcpy(new_patch, patch, sizeof(patch_t)); + + new_patch->winding = *winding; + new_patch->area = new_patch->winding->getArea(); + new_patch->winding->getCenter(new_patch->origin); + PlacePatchInside(new_patch); +#ifdef HLRAD_ACCURATEBOUNCE + UpdateEmitterInfo (new_patch); +#endif + + new_patch++; + g_num_patches++; + hlassume(g_num_patches < MAX_PATCHES, assume_MAX_PATCHES); + } + } + + // ATTENTION: We let SortPatches relink all the ->next correctly! instead of doing it here too which is somewhat complicated +} + +// ===================================================================================== +// MakePatchForFace +static float totalarea = 0; +// ===================================================================================== + +#ifdef HLRAD_CUSTOMCHOP +vec_t *chopscales; //[nummiptex] +void ReadCustomChopValue() +{ + int num; + int i, k; + entity_t *mapent; + epair_t *ep; + + num = ((dmiptexlump_t *)g_dtexdata)->nummiptex; + chopscales = (vec_t *)malloc (num * sizeof(vec_t)); + for (i = 0; i < num; i++) + { + chopscales[i] = 1.0; + } + for (k = 0; k < g_numentities; k++) + { + mapent = &g_entities[k]; + if (strcmp(ValueForKey(mapent, "classname"), "info_chopscale")) + continue; + Developer (DEVELOPER_LEVEL_MESSAGE, "info_chopscale entity detected.\n"); + for (i = 0; i < num; i++) + { + const char *texname = ((miptex_t*)(g_dtexdata+((dmiptexlump_t*)g_dtexdata)->dataofs[i]))->name; + for (ep = mapent->epairs; ep; ep = ep->next) + { + if (strcasecmp (ep->key, texname)) + continue; + if (!strcasecmp (ep->key, "origin")) + continue; + if (atof (ep->value) <= 0) + continue; + chopscales[i] = atof (ep->value); + Developer (DEVELOPER_LEVEL_MESSAGE, "info_chopscale: %s = %f\n", texname, chopscales[i]); + } + } + } +} +vec_t ChopScaleForTexture (int facenum) +{ + return chopscales[g_texinfo[g_dfaces[facenum].texinfo].miptex]; +} +#endif +#ifdef HLRAD_CUSTOMSMOOTH +vec_t *g_smoothvalues; //[nummiptex] +void ReadCustomSmoothValue() +{ + int num; + int i, k; + entity_t *mapent; + epair_t *ep; + + num = ((dmiptexlump_t *)g_dtexdata)->nummiptex; + g_smoothvalues = (vec_t *)malloc (num * sizeof(vec_t)); + for (i = 0; i < num; i++) + { + g_smoothvalues[i] = g_smoothing_threshold; + } + for (k = 0; k < g_numentities; k++) + { + mapent = &g_entities[k]; + if (strcmp(ValueForKey(mapent, "classname"), "info_smoothvalue")) + continue; + Developer (DEVELOPER_LEVEL_MESSAGE, "info_smoothvalue entity detected.\n"); + for (i = 0; i < num; i++) + { + const char *texname = ((miptex_t*)(g_dtexdata+((dmiptexlump_t*)g_dtexdata)->dataofs[i]))->name; + for (ep = mapent->epairs; ep; ep = ep->next) + { + if (strcasecmp (ep->key, texname)) + continue; + if (!strcasecmp (ep->key, "origin")) + continue; + g_smoothvalues[i] = cos(atof (ep->value) * (Q_PI / 180.0)); + Developer (DEVELOPER_LEVEL_MESSAGE, "info_smoothvalue: %s = %f\n", texname, atof (ep->value)); + } + } + } +} +#endif +#ifdef HLRAD_TRANSLUCENT +void ReadTranslucentTextures() +{ + int num; + int i, k; + entity_t *mapent; + epair_t *ep; + + num = ((dmiptexlump_t *)g_dtexdata)->nummiptex; + g_translucenttextures = (vec3_t *)malloc (num * sizeof(vec3_t)); + for (i = 0; i < num; i++) + { + VectorClear (g_translucenttextures[i]); + } + for (k = 0; k < g_numentities; k++) + { + mapent = &g_entities[k]; + if (strcmp(ValueForKey(mapent, "classname"), "info_translucent")) + continue; + Developer (DEVELOPER_LEVEL_MESSAGE, "info_translucent entity detected.\n"); + for (i = 0; i < num; i++) + { + const char *texname = ((miptex_t*)(g_dtexdata+((dmiptexlump_t*)g_dtexdata)->dataofs[i]))->name; + for (ep = mapent->epairs; ep; ep = ep->next) + { + if (strcasecmp (ep->key, texname)) + continue; + if (!strcasecmp (ep->key, "origin")) + continue; + double r, g, b; + int count; + count = sscanf (ep->value, "%lf %lf %lf", &r, &g, &b); + if (count == 1) + { + g = b = r; + } + else if (count != 3) + { + Warning ("ignore bad translucent value '%s'", ep->value); + continue; + } + if (r < 0.0 || r > 1.0 || g < 0.0 || g > 1.0 || b < 0.0 || b > 1.0) + { + Warning ("translucent value should be 0.0-1.0"); + continue; + } + g_translucenttextures[i][0] = r; + g_translucenttextures[i][1] = g; + g_translucenttextures[i][2] = b; + Developer (DEVELOPER_LEVEL_MESSAGE, "info_translucent: %s = %f %f %f\n", texname, r, g, b); + } + } + } +} +#endif +#ifdef HLRAD_DIVERSE_LIGHTING +vec3_t *g_lightingconeinfo;//[nummiptex] +static vec_t DefaultScaleForPower (vec_t power) +{ + vec_t scale; + // scale = Pi / Integrate [2 Pi * Sin [x] * Cos[x] ^ power, {x, 0, Pi / 2}] + scale = (1 + power) / 2.0; + return scale; +} +void ReadLightingCone () +{ + int num; + int i, k; + entity_t *mapent; + epair_t *ep; + + num = ((dmiptexlump_t *)g_dtexdata)->nummiptex; + g_lightingconeinfo = (vec3_t *)malloc (num * sizeof(vec3_t)); + for (i = 0; i < num; i++) + { + g_lightingconeinfo[i][0] = 1.0; // default power + g_lightingconeinfo[i][1] = 1.0; // default scale + } + for (k = 0; k < g_numentities; k++) + { + mapent = &g_entities[k]; + if (strcmp(ValueForKey(mapent, "classname"), "info_angularfade")) + continue; + Developer (DEVELOPER_LEVEL_MESSAGE, "info_angularfade entity detected.\n"); + for (i = 0; i < num; i++) + { + const char *texname = ((miptex_t*)(g_dtexdata+((dmiptexlump_t*)g_dtexdata)->dataofs[i]))->name; + for (ep = mapent->epairs; ep; ep = ep->next) + { + if (strcasecmp (ep->key, texname)) + continue; + if (!strcasecmp (ep->key, "origin")) + continue; + double power, scale; + int count; + count = sscanf (ep->value, "%lf %lf", &power, &scale); + if (count == 1) + { + scale = 1.0; + } + else if (count != 2) + { + Warning ("ignore bad angular fade value '%s'", ep->value); + continue; + } + if (power < 0.0 || scale < 0.0) + { + Warning ("ignore disallowed angular fade value '%s'", ep->value); + continue; + } + scale *= DefaultScaleForPower (power); + g_lightingconeinfo[i][0] = power; + g_lightingconeinfo[i][1] = scale; + Developer (DEVELOPER_LEVEL_MESSAGE, "info_angularfade: %s = %f %f\n", texname, power, scale); + } + } + } +} +#endif + +static vec_t getScale(const patch_t* const patch) +{ + dface_t* f = g_dfaces + patch->faceNumber; + texinfo_t* tx = &g_texinfo[f->texinfo]; + + if (g_texscale) + { +#ifdef ZHLT_FREETEXTUREAXIS + const dplane_t* faceplane = getPlaneFromFace (f); + vec3_t vecs_perpendicular[2]; + vec_t scale[2]; + vec_t dot; + + // snap texture "vecs" to faceplane without affecting texture alignment + for (int x = 0; x < 2; x++) + { + dot = DotProduct (faceplane->normal, tx->vecs[x]); + VectorMA (tx->vecs[x], -dot, faceplane->normal, vecs_perpendicular[x]); + } + + scale[0] = 1 / qmax (NORMAL_EPSILON, VectorLength (vecs_perpendicular[0])); + scale[1] = 1 / qmax (NORMAL_EPSILON, VectorLength (vecs_perpendicular[1])); + + // don't care about the angle between vecs[0] and vecs[1] (given the length of "vecs", smaller angle = larger texel area), because gridplanes will have the same angle (also smaller angle = larger patch area) + + return sqrt (scale[0] * scale[1]); +#else + vec_t scale[2]; + + scale[0] = 0.0; + scale[1] = 0.0; + + scale[0] += tx->vecs[0][0] * tx->vecs[0][0]; + scale[0] += tx->vecs[0][1] * tx->vecs[0][1]; + scale[0] += tx->vecs[0][2] * tx->vecs[0][2]; + + scale[1] += tx->vecs[1][0] * tx->vecs[1][0]; + scale[1] += tx->vecs[1][1] * tx->vecs[1][1]; + scale[1] += tx->vecs[1][2] * tx->vecs[1][2]; + + scale[0] = sqrt(scale[0]); + scale[1] = sqrt(scale[1]); + + return 2.0 / ((scale[0] + scale[1])); +#endif + } + else + { + return 1.0; + } +} + +// ===================================================================================== +// getChop +// ===================================================================================== +#ifdef HLRAD_TEXLIGHTTHRESHOLD_FIX +static bool getEmitMode (const patch_t *patch) +{ + bool emitmode = false; + vec_t value = +#ifdef HLRAD_REFLECTIVITY + DotProduct (patch->baselight, patch->texturereflectivity) / 3 +#else + VectorAvg (patch->baselight) +#endif + ; +#ifdef HLRAD_CUSTOMTEXLIGHT + if (g_face_texlights[patch->faceNumber]) + { + if (*ValueForKey (g_face_texlights[patch->faceNumber], "_scale")) + { + value *= FloatForKey (g_face_texlights[patch->faceNumber], "_scale"); + } + } +#endif + if (value > 0.0) + { + emitmode = true; + } + if (value < g_dlight_threshold) + { + emitmode = false; + } +#ifdef HLRAD_CUSTOMTEXLIGHT + if (g_face_texlights[patch->faceNumber]) + { + switch (IntForKey (g_face_texlights[patch->faceNumber], "_fast")) + { + case 1: + emitmode = false; + break; + case 2: + emitmode = true; + break; + } + } +#endif + return emitmode; +} +#endif +static vec_t getChop(const patch_t* const patch) +{ + vec_t rval; + +#ifdef HLRAD_CUSTOMTEXLIGHT + if (g_face_texlights[patch->faceNumber]) + { + if (*ValueForKey (g_face_texlights[patch->faceNumber], "_chop")) + { + rval = FloatForKey (g_face_texlights[patch->faceNumber], "_chop"); + if (rval < 1.0) + { + rval = 1.0; + } + return rval; + } + } +#endif +#ifdef HLRAD_TEXLIGHTTHRESHOLD_FIX + if (!patch->emitmode) +#else + if (VectorCompare(patch->baselight, vec3_origin)) +#endif + { + rval = g_chop * getScale(patch); + } + else + { + rval = g_texchop * getScale(patch); +#ifdef HLRAD_ACCURATEBOUNCE_TEXLIGHT + // we needn't do this now, so let's save our compile time. +#else + if (g_extra) + { + rval *= 0.5; + } +#endif + } + +#ifdef HLRAD_CUSTOMCHOP + rval *= ChopScaleForTexture (patch->faceNumber); +#endif + return rval; +} + +// ===================================================================================== +// MakePatchForFace +// ===================================================================================== +#ifdef ZHLT_TEXLIGHT +static void MakePatchForFace(const int fn, Winding* w, int style +#ifdef HLRAD_BOUNCE_STYLE + , int bouncestyle +#endif + ) //LRC +#else +static void MakePatchForFace(const int fn, Winding* w +#ifdef HLRAD_BOUNCE_STYLE + , int bouncestyle +#endif + ) +#endif +{ + const dface_t* f = g_dfaces + fn; + + // No g_patches at all for the sky! + if (!IsSpecial(f)) + { +#ifdef HLRAD_CUSTOMTEXLIGHT +#ifdef ZHLT_TEXLIGHT + if (g_face_texlights[fn]) + { + style = IntForKey (g_face_texlights[fn], "style"); +#ifdef ZHLT_TEXLIGHT + if (style < 0) + style = -style; +#endif +#ifdef HLRAD_STYLE_CORING + style = (unsigned char)style; + if (style >= ALLSTYLES) + { + Error ("invalid light style: style (%d) >= ALLSTYLES (%d)", style, ALLSTYLES); + } +#endif + } +#endif +#endif + patch_t* patch; + vec3_t light; + vec3_t centroid = { 0, 0, 0 }; + + int numpoints = w->m_NumPoints; + + if (numpoints < 3) // WTF! (Actually happens in real-world maps too) + { + Developer(DEVELOPER_LEVEL_WARNING, "Face %d only has %d points on winding\n", fn, numpoints); + return; + } + if (numpoints > MAX_POINTS_ON_WINDING) + { + Error("numpoints %d > MAX_POINTS_ON_WINDING", numpoints); + return; + } + + patch = &g_patches[g_num_patches]; + hlassume(g_num_patches < MAX_PATCHES, assume_MAX_PATCHES); + memset(patch, 0, sizeof(patch_t)); + + patch->winding = w; + + patch->area = patch->winding->getArea(); + patch->winding->getCenter(patch->origin); + patch->faceNumber = fn; + + totalarea += patch->area; + +#ifndef HLRAD_PATCHBLACK_FIX + PlacePatchInside(patch); +#ifdef HLRAD_ACCURATEBOUNCE + UpdateEmitterInfo (patch); +#endif +#endif + + BaseLightForFace(f, light); +#ifdef ZHLT_TEXLIGHT + //LRC VectorCopy(light, patch->totallight); +#else + VectorCopy(light, patch->totallight); +#endif + VectorCopy(light, patch->baselight); + +#ifdef ZHLT_TEXLIGHT +#ifdef HLRAD_AUTOCORING + patch->emitstyle = style; +#else + //LRC + int i; + patch->totalstyle[0] = 0; + for (i = 1; i < MAXLIGHTMAPS; i++) + { + patch->totalstyle[i] = 255; + } + if (style) + { + patch->emitstyle = patch->totalstyle[1] = style; + } + //LRC (ends) +#endif +#endif + +#ifdef HLRAD_REFLECTIVITY + VectorCopy (g_textures[g_texinfo[f->texinfo].miptex].reflectivity, patch->texturereflectivity); +#ifdef HLRAD_CUSTOMTEXLIGHT_COLOR + if (g_face_texlights[fn] && *ValueForKey (g_face_texlights[fn], "_texcolor")) + { + vec3_t texturecolor; + vec3_t texturereflectivity; + GetVectorForKey (g_face_texlights[fn], "_texcolor", texturecolor); + for (int k = 0; k < 3; k++) + { + texturecolor[k] = floor (texturecolor[k] + 0.001); + } + if (VectorMinimum (texturecolor) < -0.001 || VectorMaximum (texturecolor) > 255.001) + { + vec3_t origin; + GetVectorForKey (g_face_texlights[fn], "origin", origin); + Error ("light_surface entity at (%g,%g,%g): texture color (%g,%g,%g) must be numbers between 0 and 255.", origin[0], origin[1], origin[2], texturecolor[0], texturecolor[1], texturecolor[2]); + } + VectorScale (texturecolor, 1.0 / 255.0, texturereflectivity); + for (int k = 0; k < 3; k++) + { + texturereflectivity[k] = pow (texturereflectivity[k], g_texreflectgamma); + } + VectorScale (texturereflectivity, g_texreflectscale, texturereflectivity); + if (VectorMaximum (texturereflectivity) > 1.0 + NORMAL_EPSILON) + { + Warning ("Texture '%s': reflectivity (%f,%f,%f) greater than 1.0.", g_textures[g_texinfo[f->texinfo].miptex].name, texturereflectivity[0], texturereflectivity[1], texturereflectivity[2]); + } + VectorCopy (texturereflectivity, patch->texturereflectivity); + } +#endif + { + vec_t opacity = 0.0; + if (g_face_entity[fn] - g_entities == 0) + { + opacity = 1.0; + } + else + { + int x; + for (x = 0; x < g_opaque_face_count; x++) + { + opaqueList_t *op = &g_opaque_face_list[x]; + #ifdef HLRAD_OPAQUE_NODE + if (op->entitynum == g_face_entity[fn] - g_entities) + #else + if (op->facenum == fn) + #endif + { + opacity = 1.0; + #ifdef HLRAD_HULLU + if (op->transparency) + { + opacity = 1.0 - VectorAvg (op->transparency_scale); + opacity = opacity > 1.0? 1.0: opacity < 0.0? 0.0: opacity; + } + #endif + #ifdef HLRAD_BOUNCE_STYLE + if (op->style != -1) + { // toggleable opaque entity + if (bouncestyle == -1) + { // by default + opacity = 0.0; // doesn't reflect light + } + } + #endif + break; + } + } + #ifdef HLRAD_BOUNCE_STYLE + if (x == g_opaque_face_count) + { // not opaque + if (bouncestyle != -1) + { // with light_bounce + opacity = 1.0; // reflects light + } + } + #endif + } + #ifdef ZHLT_HIDDENSOUNDTEXTURE + // if the face is a world face and it's not referenced by any leaf, it must be a hidden face, and shouldn't reflect light + if (g_dmodels[0].firstface <= fn && fn < g_dmodels[0].firstface + g_dmodels[0].numfaces) + { + bool found = false; + for (int x = 0; x < g_nummarksurfaces; x++) + { + if (g_dmarksurfaces[x] == fn) + { + found = true; + break; + } + } + if (!found) + { + opacity = 0.0; + } + } + #endif + VectorScale (patch->texturereflectivity, opacity, patch->bouncereflectivity); + } + #ifdef HLRAD_BOUNCE_STYLE + patch->bouncestyle = bouncestyle; + if (bouncestyle == 0) + { // there is an unnamed light_bounce + patch->bouncestyle = -1; // reflects light normally + } + #endif +#endif +#ifdef HLRAD_TEXLIGHTTHRESHOLD_FIX + patch->emitmode = getEmitMode (patch); +#endif + patch->scale = getScale(patch); + patch->chop = getChop(patch); +#ifdef HLRAD_TRANSLUCENT + VectorCopy (g_translucenttextures[g_texinfo[f->texinfo].miptex], patch->translucent_v); + patch->translucent_b = !VectorCompare (patch->translucent_v, vec3_origin); +#endif +#ifdef HLRAD_PATCHBLACK_FIX + PlacePatchInside(patch); +#ifdef HLRAD_ACCURATEBOUNCE + UpdateEmitterInfo (patch); +#endif +#endif + + g_face_patches[fn] = patch; + g_num_patches++; + + // Per-face data + { + int j; + + // Centroid of face for nudging samples in direct lighting pass + for (j = 0; j < f->numedges; j++) + { + int edge = g_dsurfedges[f->firstedge + j]; + + if (edge > 0) + { + VectorAdd(g_dvertexes[g_dedges[edge].v[0]].point, centroid, centroid); + VectorAdd(g_dvertexes[g_dedges[edge].v[1]].point, centroid, centroid); + } + else + { + VectorAdd(g_dvertexes[g_dedges[-edge].v[1]].point, centroid, centroid); + VectorAdd(g_dvertexes[g_dedges[-edge].v[0]].point, centroid, centroid); + } + } + + // Fixup centroid for anything with an altered origin (rotating models/turrets mostly) + // Save them for moving direct lighting points towards the face center + VectorScale(centroid, 1.0 / (f->numedges * 2), centroid); + VectorAdd(centroid, g_face_offset[fn], g_face_centroids[fn]); + } + + { + vec3_t mins; + vec3_t maxs; + + patch->winding->getBounds(mins, maxs); + + if (g_subdivide) + { + vec_t amt; + vec_t length; + vec3_t delta; + + VectorSubtract(maxs, mins, delta); + length = VectorLength(delta); +#ifdef HLRAD_CHOP_FIX + amt = patch->chop; +#else // loss patches on faces where texture scale < 1 and range < g_chop + if (VectorCompare(patch->baselight, vec3_origin)) + { + amt = g_chop; + } + else + { + amt = g_texchop; + } +#endif + + if (length > amt) + { + if (patch->area < 1.0) + { + Developer(DEVELOPER_LEVEL_WARNING, + "Patch at (%4.3f %4.3f %4.3f) (face %d) tiny area (%4.3f) not subdividing \n", + patch->origin[0], patch->origin[1], patch->origin[2], patch->faceNumber, patch->area); + } + else + { + SubdividePatch(patch); + } + } + } + } + } +} + +// ===================================================================================== +// AddFaceToOpaqueList +// ===================================================================================== +static void AddFaceToOpaqueList( +#ifdef HLRAD_OPAQUE_NODE + int entitynum, int modelnum, const vec3_t origin +#else + const unsigned facenum, const Winding* const winding +#endif +#ifdef HLRAD_HULLU + , const vec3_t &transparency_scale, const bool transparency +#endif +#ifndef HLRAD_OPAQUE_NODE +#ifdef HLRAD_OPAQUE_GROUP + , const dmodel_t* mod +#endif +#endif +#ifdef HLRAD_OPAQUE_STYLE + , int style +#endif +#ifdef HLRAD_OPAQUE_BLOCK + , bool block +#endif + ) +{ + if (g_opaque_face_count == g_max_opaque_face_count) + { + g_max_opaque_face_count += OPAQUE_ARRAY_GROWTH_SIZE; + g_opaque_face_list = (opaqueList_t*)realloc(g_opaque_face_list, sizeof(opaqueList_t) * g_max_opaque_face_count); +#ifdef HLRAD_HLASSUMENOMEMORY + hlassume (g_opaque_face_list != NULL, assume_NoMemory); +#endif + } + + { + opaqueList_t* opaque = &g_opaque_face_list[g_opaque_face_count]; + + g_opaque_face_count++; + +#ifdef HLRAD_OPAQUE_STYLE + if (transparency && style != -1) + { + Warning ("Dynamic shadow is not allowed in entity with custom shadow.\n"); + style = -1; + } +#endif +#ifdef HLRAD_HULLU + VectorCopy(transparency_scale, opaque->transparency_scale); + opaque->transparency = transparency; +#endif +#ifdef HLRAD_OPAQUE_NODE + opaque->entitynum = entitynum; + opaque->modelnum = modelnum; + VectorCopy (origin, opaque->origin); +#else + opaque->facenum = facenum; + getAdjustedPlaneFromFaceNumber(facenum, &opaque->plane); + opaque->winding = new Winding(*winding); +#endif +#ifndef HLRAD_OPAQUE_NODE +#ifdef HLRAD_OPAQUE_GROUP + { + int ig; + for (ig=0; ig=MAX_OPAQUE_GROUP_COUNT) + Error ("too many opaque models"); + g_opaque_group_list[ig].mod=mod; +#ifdef HLRAD_OPAQUE_RANGE + for (int i=0; i<3; ++i) + { + g_opaque_group_list[ig].mins[i]=mod->mins[i]+g_face_offset[facenum][i]-1; + g_opaque_group_list[ig].maxs[i]=mod->maxs[i]+g_face_offset[facenum][i]+1; + } +#endif + g_opaque_group_count++; + } + opaque->groupnum=ig; + } +#endif +#endif +#ifdef HLRAD_OPAQUE_STYLE + opaque->style = style; +#endif +#ifdef HLRAD_OPAQUE_BLOCK + opaque->block = block; +#endif + } +} + +// ===================================================================================== +// FreeOpaqueFaceList +// ===================================================================================== +static void FreeOpaqueFaceList() +{ + unsigned x; + opaqueList_t* opaque = g_opaque_face_list; + + for (x = 0; x < g_opaque_face_count; x++, opaque++) + { +#ifndef HLRAD_OPAQUE_NODE + delete opaque->winding; + opaque->winding = NULL; +#endif + } + free(g_opaque_face_list); + + g_opaque_face_list = NULL; + g_opaque_face_count = 0; + g_max_opaque_face_count = 0; +} +#ifdef HLRAD_OPAQUE_NODE +static void LoadOpaqueEntities() +{ + int modelnum, entnum; + for (modelnum = 0; modelnum < g_nummodels; modelnum++) + { + dmodel_t *model = &g_dmodels[modelnum]; + char stringmodel[16]; + sprintf (stringmodel, "*%i", modelnum); + for (entnum = 0; entnum < g_numentities; entnum++) + { + entity_t *ent = &g_entities[entnum]; + if (strcmp (ValueForKey (ent, "model"), stringmodel)) + continue; + vec3_t origin; + { + GetVectorForKey (ent, "origin", origin); + if (*ValueForKey (ent, "light_origin") && *ValueForKey (ent, "model_center")) + { + entity_t *ent2 = FindTargetEntity (ValueForKey (ent, "light_origin")); + if (ent2) + { + vec3_t light_origin, model_center; + GetVectorForKey (ent2, "origin", light_origin); + GetVectorForKey (ent, "model_center", model_center); + VectorSubtract (light_origin, model_center, origin); + } + } + } + bool opaque = false; + { + if (g_allow_opaques && (IntForKey (ent, "zhlt_lightflags") & eModelLightmodeOpaque)) + opaque = true; + } +#ifdef HLRAD_HULLU + vec3_t d_transparency; + VectorFill (d_transparency, 0.0); + bool b_transparency = false; + { + const char *s; + if (*(s = ValueForKey(ent, "zhlt_customshadow"))) + { + double r1 = 1.0, g1 = 1.0, b1 = 1.0, tmp = 1.0; + if (sscanf(s, "%lf %lf %lf", &r1, &g1, &b1) == 3) //RGB version + { + if(r1<0.0) r1 = 0.0; + if(g1<0.0) g1 = 0.0; + if(b1<0.0) b1 = 0.0; + d_transparency[0] = r1; + d_transparency[1] = g1; + d_transparency[2] = b1; + } + else if (sscanf(s, "%lf", &tmp) == 1) //Greyscale version + { + if(tmp<0.0) tmp = 0.0; + VectorFill(d_transparency, tmp); + } + } + if (!VectorCompare (d_transparency, vec3_origin)) + b_transparency = true; + } +#endif +#ifdef HLRAD_OPAQUE_STYLE + int opaquestyle = -1; + { + int j; + for (j = 0; j < g_numentities; j++) + { + entity_t *lightent = &g_entities[j]; + if (!strcmp (ValueForKey (lightent, "classname"), "light_shadow") + && *ValueForKey (lightent, "target") + && !strcmp (ValueForKey (lightent, "target"), ValueForKey (ent, "targetname"))) + { + opaquestyle = IntForKey (lightent, "style"); + #ifdef ZHLT_TEXLIGHT + if (opaquestyle < 0) + opaquestyle = -opaquestyle; + #endif + #ifdef HLRAD_STYLE_CORING + opaquestyle = (unsigned char)opaquestyle; + if (opaquestyle >= ALLSTYLES) + { + Error ("invalid light style: style (%d) >= ALLSTYLES (%d)", opaquestyle, ALLSTYLES); + } + #endif + break; + } + } + } +#endif +#ifdef HLRAD_OPAQUE_BLOCK + bool block = false; + { + if (g_blockopaque) + { + block = true; + if (IntForKey (ent, "zhlt_lightflags") & eModelLightmodeNonsolid) + block = false; + if (b_transparency) + block = false; +#ifdef HLRAD_OPAQUE_STYLE + if (opaquestyle != -1) + block = false; +#endif + } + } +#endif + if (opaque) + { + AddFaceToOpaqueList (entnum, modelnum, origin +#ifdef HLRAD_HULLU + , d_transparency, b_transparency +#endif +#ifdef HLRAD_OPAQUE_STYLE + , opaquestyle +#endif +#ifdef HLRAD_OPAQUE_BLOCK + , block +#endif + ); + } + } + } + { + Log("%i opaque models\n", g_opaque_face_count); + int i, facecount; + for (facecount = 0, i = 0; i < g_opaque_face_count; i++) + { + facecount += CountOpaqueFaces (g_opaque_face_list[i].modelnum); + } + Log("%i opaque faces\n", facecount); + } +} +#endif + +// ===================================================================================== +// MakePatches +// ===================================================================================== +#ifdef HLRAD_CUSTOMTEXLIGHT +static entity_t *FindTexlightEntity (int facenum) +{ + dface_t *face = &g_dfaces[facenum]; + const dplane_t *dplane = getPlaneFromFace (face); + const char *texname = GetTextureByNumber (face->texinfo); + entity_t *faceent = g_face_entity[facenum]; + vec3_t centroid; + Winding *w = new Winding (*face); + w->getCenter (centroid); + delete w; + VectorAdd (centroid, g_face_offset[facenum], centroid); + + entity_t *found = NULL; + vec_t bestdist = -1; + for (int i = 0; i < g_numentities; i++) + { + entity_t *ent = &g_entities[i]; + if (strcmp (ValueForKey (ent, "classname"), "light_surface")) + continue; + if (strcasecmp (ValueForKey (ent, "_tex"), texname)) + continue; + vec3_t delta; + GetVectorForKey (ent, "origin", delta); + VectorSubtract (delta, centroid, delta); + vec_t dist = VectorLength (delta); + if (*ValueForKey (ent, "_frange")) + { + if (dist > FloatForKey (ent, "_frange")) + continue; + } + if (*ValueForKey (ent, "_fdist")) + { + if (fabs (DotProduct (delta, dplane->normal)) > FloatForKey (ent, "_fdist")) + continue; + } + if (*ValueForKey (ent, "_fclass")) + { + if (strcmp (ValueForKey (faceent, "classname"), ValueForKey (ent, "_fclass"))) + continue; + } + if (*ValueForKey (ent, "_fname")) + { + if (strcmp (ValueForKey (faceent, "targetname"), ValueForKey (ent, "_fname"))) + continue; + } + if (bestdist >= 0 && dist > bestdist) + continue; + found = ent; + bestdist = dist; + } + return found; +} +#endif +static void MakePatches() +{ + int i; + int j; + unsigned int k; + dface_t* f; + int fn; + Winding* w; + dmodel_t* mod; + vec3_t origin; + entity_t* ent; + const char* s; + vec3_t light_origin; + vec3_t model_center; + bool b_light_origin; + bool b_model_center; + eModelLightmodes lightmode; + +#ifdef ZHLT_TEXLIGHT + int style; //LRC +#endif + +#ifndef HLRAD_OPAQUE_NODE +#ifdef HLRAD_HULLU + vec3_t d_transparency; + bool b_transparency; +#endif +#endif + + Log("%i faces\n", g_numfaces); + + Log("Create Patches : "); +#ifdef HLRAD_MORE_PATCHES + g_patches = (patch_t *)AllocBlock (MAX_PATCHES * sizeof (patch_t)); +#endif + + for (i = 0; i < g_nummodels; i++) + { + b_light_origin = false; + b_model_center = false; + lightmode = eModelLightmodeNull; + +#ifndef HLRAD_OPAQUE_NODE +#ifdef HLRAD_OPACITY // AJM + float l_opacity = 0.0f; // decimal percentage +#endif +#endif + + mod = g_dmodels + i; + ent = EntityForModel(i); + VectorCopy(vec3_origin, origin); + + if (*(s = ValueForKey(ent, "zhlt_lightflags"))) + { + lightmode = (eModelLightmodes)atoi(s); + } + + // models with origin brushes need to be offset into their in-use position + if (*(s = ValueForKey(ent, "origin"))) + { + double v1, v2, v3; + + if (sscanf(s, "%lf %lf %lf", &v1, &v2, &v3) == 3) + { + origin[0] = v1; + origin[1] = v2; + origin[2] = v3; + } + + } + + // Allow models to be lit in an alternate location (pt1) + if (*(s = ValueForKey(ent, "light_origin"))) + { + entity_t* e = FindTargetEntity(s); + + if (e) + { + if (*(s = ValueForKey(e, "origin"))) + { + double v1, v2, v3; + + if (sscanf(s, "%lf %lf %lf", &v1, &v2, &v3) == 3) + { + light_origin[0] = v1; + light_origin[1] = v2; + light_origin[2] = v3; + + b_light_origin = true; + } + } + } + } + + // Allow models to be lit in an alternate location (pt2) + if (*(s = ValueForKey(ent, "model_center"))) + { + double v1, v2, v3; + + if (sscanf(s, "%lf %lf %lf", &v1, &v2, &v3) == 3) + { + model_center[0] = v1; + model_center[1] = v2; + model_center[2] = v3; + + b_model_center = true; + } + } + +#ifndef HLRAD_OPAQUE_NODE +#ifdef HLRAD_HULLU + // Check for colored transparency/custom shadows + VectorFill(d_transparency, 1.0); + b_transparency = false; + + if (*(s = ValueForKey(ent, "zhlt_customshadow"))) + { + double r1 = 1.0, g1 = 1.0, b1 = 1.0, tmp = 1.0; + if (sscanf(s, "%lf %lf %lf", &r1, &g1, &b1) == 3) //RGB version + { + if(r1<0.0) r1 = 0.0; + if(g1<0.0) g1 = 0.0; + if(b1<0.0) b1 = 0.0; + + d_transparency[0] = r1; + d_transparency[1] = g1; + d_transparency[2] = b1; + b_transparency = true; + } + else if (sscanf(s, "%lf", &tmp) == 1) //Greyscale version + { + if(tmp<0.0) tmp = 0.0; + + VectorFill(d_transparency, tmp); + b_transparency = true; + } + } +#endif +#endif + // Allow models to be lit in an alternate location (pt3) + if (b_light_origin && b_model_center) + { + VectorSubtract(light_origin, model_center, origin); + } + +#ifdef ZHLT_TEXLIGHT + //LRC: + if (*(s = ValueForKey(ent, "style"))) + { + style = atoi(s); + if (style < 0) + style = -style; + } + else + { + style = 0; + } + //LRC (ends) + #ifdef HLRAD_STYLE_CORING + style = (unsigned char)style; + if (style >= ALLSTYLES) + { + Error ("invalid light style: style (%d) >= ALLSTYLES (%d)", style, ALLSTYLES); + } + #endif +#endif +#ifndef HLRAD_OPAQUE_NODE +#ifdef HLRAD_OPAQUE_STYLE + int opaquestyle = -1; + for (j = 0; j < g_numentities; j++) + { + entity_t *lightent = &g_entities[j]; + if (!strcmp (ValueForKey (lightent, "classname"), "light_shadow") + && *ValueForKey (lightent, "target") + && !strcmp (ValueForKey (lightent, "target"), ValueForKey (ent, "targetname"))) + { + opaquestyle = IntForKey (lightent, "style"); + #ifdef ZHLT_TEXLIGHT + if (opaquestyle < 0) + opaquestyle = -opaquestyle; + #endif + #ifdef HLRAD_STYLE_CORING + opaquestyle = (unsigned char)opaquestyle; + if (opaquestyle >= ALLSTYLES) + { + Error ("invalid light style: style (%d) >= ALLSTYLES (%d)", opaquestyle, ALLSTYLES); + } + #endif + break; + } + } +#endif +#endif +#ifdef HLRAD_BOUNCE_STYLE + int bouncestyle = -1; + { + int j; + for (j = 0; j < g_numentities; j++) + { + entity_t *lightent = &g_entities[j]; + if (!strcmp (ValueForKey (lightent, "classname"), "light_bounce") + && *ValueForKey (lightent, "target") + && !strcmp (ValueForKey (lightent, "target"), ValueForKey (ent, "targetname"))) + { + bouncestyle = IntForKey (lightent, "style"); + #ifdef ZHLT_TEXLIGHT + if (bouncestyle < 0) + bouncestyle = -bouncestyle; + #endif + #ifdef HLRAD_STYLE_CORING + bouncestyle = (unsigned char)bouncestyle; + if (bouncestyle >= ALLSTYLES) + { + Error ("invalid light style: style (%d) >= ALLSTYLES (%d)", bouncestyle, ALLSTYLES); + } + #endif + break; + } + } + } +#endif + + for (j = 0; j < mod->numfaces; j++) + { + fn = mod->firstface + j; + g_face_entity[fn] = ent; + VectorCopy(origin, g_face_offset[fn]); +#ifdef HLRAD_CUSTOMTEXLIGHT + g_face_texlights[fn] = FindTexlightEntity (fn); +#endif + g_face_lightmode[fn] = lightmode; + f = g_dfaces + fn; + w = new Winding(*f); + for (k = 0; k < w->m_NumPoints; k++) + { + VectorAdd(w->m_Points[k], origin, w->m_Points[k]); + } +#ifndef HLRAD_OPAQUE_NODE + if (g_allow_opaques) + { + if (lightmode & eModelLightmodeOpaque) + { + AddFaceToOpaqueList(fn, w +#ifdef HLRAD_HULLU + , d_transparency, b_transparency +#endif +#ifdef HLRAD_OPAQUE_GROUP + , mod +#endif +#ifdef HLRAD_OPAQUE_STYLE + , opaquestyle +#endif + ); + } + } +#endif +#ifdef ZHLT_TEXLIGHT + MakePatchForFace(fn, w, style +#ifdef HLRAD_BOUNCE_STYLE + , bouncestyle +#endif + ); //LRC +#else + MakePatchForFace(fn, w +#ifdef HLRAD_BOUNCE_STYLE + , bouncestyle +#endif + ); +#endif + } + } + + Log("%i base patches\n", g_num_patches); +#ifndef HLRAD_OPAQUE_NODE +#ifdef HLRAD_OPAQUE_GROUP + Log("%i opaque models\n", g_opaque_group_count); +#endif + Log("%i opaque faces\n", g_opaque_face_count); +#endif + Log("%i square feet [%.2f square inches]\n", (int)(totalarea / 144), totalarea); +} + +// ===================================================================================== +// patch_sorter +// ===================================================================================== +static int CDECL patch_sorter(const void* p1, const void* p2) +{ + patch_t* patch1 = (patch_t*)p1; + patch_t* patch2 = (patch_t*)p2; + + if (patch1->faceNumber < patch2->faceNumber) + { + return -1; + } + else if (patch1->faceNumber > patch2->faceNumber) + { + return 1; + } + else + { + return 0; + } +} + +// ===================================================================================== +// patch_sorter +// This sorts the patches by facenumber, which makes their runs compress even better +// ===================================================================================== +static void SortPatches() +{ +#ifdef HLRAD_MORE_PATCHES + // SortPatches is the ideal place to do this, because the address of the patches are going to be invalidated. + patch_t *old_patches = g_patches; + g_patches = (patch_t *)AllocBlock ((g_num_patches + 1) * sizeof (patch_t)); // allocate one extra slot considering how terribly the code were written + memcpy (g_patches, old_patches, g_num_patches * sizeof (patch_t)); + FreeBlock (old_patches); +#endif + qsort((void*)g_patches, (size_t) g_num_patches, sizeof(patch_t), patch_sorter); + + // Fixup g_face_patches & Fixup patch->next + memset(g_face_patches, 0, sizeof(g_face_patches)); + { + unsigned x; + patch_t* patch = g_patches + 1; + patch_t* prev = g_patches; + +#ifdef HLRAD_SortPatches_FIX + g_face_patches[prev->faceNumber] = prev; +#else + g_face_patches[0] = g_patches; +#endif + + for (x = 1; x < g_num_patches; x++, patch++) + { + if (patch->faceNumber != prev->faceNumber) + { + prev->next = NULL; + g_face_patches[patch->faceNumber] = patch; + } + else + { + prev->next = patch; + } + prev = patch; + } + } +#ifdef HLRAD_ENTITYBOUNCE_FIX + for (unsigned x = 0; x < g_num_patches; x++) + { + patch_t *patch = &g_patches[x]; + patch->leafnum = PointInLeaf (patch->origin) - g_dleafs; + } +#endif +} + +// ===================================================================================== +// FreePatches +// ===================================================================================== +static void FreePatches() +{ + unsigned x; + patch_t* patch = g_patches; + + // AJM EX + //Log("patches: %i of %i (%2.2lf percent)\n", g_num_patches, MAX_PATCHES, (double)((double)g_num_patches / (double)MAX_PATCHES)); + + for (x = 0; x < g_num_patches; x++, patch++) + { + delete patch->winding; + } + memset(g_patches, 0, sizeof(patch_t) * g_num_patches); +#ifdef HLRAD_MORE_PATCHES + FreeBlock (g_patches); + g_patches = NULL; +#endif +} + +//===================================================================== + +// ===================================================================================== +// WriteWorld +// ===================================================================================== +static void WriteWorld(const char* const name) +{ + unsigned i; + unsigned j; + FILE* out; + patch_t* patch; + Winding* w; + + out = fopen(name, "w"); + + if (!out) + Error("Couldn't open %s", name); + + for (j = 0, patch = g_patches; j < g_num_patches; j++, patch++) + { + w = patch->winding; + Log("%i\n", w->m_NumPoints); + for (i = 0; i < w->m_NumPoints; i++) + { +#ifdef ZHLT_TEXLIGHT + Log("%5.2f %5.2f %5.2f %5.3f %5.3f %5.3f\n", + w->m_Points[i][0], + w->m_Points[i][1], + w->m_Points[i][2], patch->totallight[0][0] / 256, patch->totallight[0][1] / 256, patch->totallight[0][2] / 256); //LRC +#else + Log("%5.2f %5.2f %5.2f %5.3f %5.3f %5.3f\n", + w->m_Points[i][0], + w->m_Points[i][1], + w->m_Points[i][2], patch->totallight[0] / 256, patch->totallight[1] / 256, patch->totallight[2] / 256); +#endif + } + Log("\n"); + } + + fclose(out); +} + +// ===================================================================================== +// CollectLight +// ===================================================================================== +static void CollectLight() +{ +#ifdef ZHLT_TEXLIGHT + unsigned j; //LRC +#endif + unsigned i; + patch_t* patch; + + for (i = 0, patch = g_patches; i < g_num_patches; i++, patch++) + { +#ifdef HLRAD_AUTOCORING + vec3_t newtotallight[MAXLIGHTMAPS]; +#ifdef ZHLT_XASH + vec3_t newtotallight_direction[MAXLIGHTMAPS]; +#endif + for (j = 0; j < MAXLIGHTMAPS && newstyles[i][j] != 255; j++) + { + VectorClear (newtotallight[j]); +#ifdef ZHLT_XASH + VectorClear (newtotallight_direction[j]); +#endif + int k; + for (k = 0; k < MAXLIGHTMAPS && patch->totalstyle[k] != 255; k++) + { + if (patch->totalstyle[k] == newstyles[i][j]) + { + VectorCopy (patch->totallight[k], newtotallight[j]); +#ifdef ZHLT_XASH + VectorCopy (patch->totallight_direction[k], newtotallight_direction[k]); +#endif + break; + } + } + } + for (j = 0; j < MAXLIGHTMAPS; j++) + { + if (newstyles[i][j] != 255) + { + patch->totalstyle[j] = newstyles[i][j]; + VectorCopy (newtotallight[j], patch->totallight[j]); + VectorCopy (addlight[i][j], emitlight[i][j]); +#ifdef ZHLT_XASH + VectorCopy (newtotallight_direction[j], patch->totallight_direction[j]); + VectorCopy (addlight_direction[i][j], emitlight_direction[i][j]); +#endif + } + else + { + patch->totalstyle[j] = 255; + } + } +#else +#ifdef ZHLT_TEXLIGHT + //LRC + for (j = 0; j < MAXLIGHTMAPS && patch->totalstyle[j] != 255; j++) + { + VectorAdd(patch->totallight[j], addlight[i][j], patch->totallight[j]); + #ifdef HLRAD_TRANSFERDATA_COMPRESS + VectorCopy(addlight[i][j], emitlight[i][j]); + #else + VectorScale(addlight[i][j], TRANSFER_SCALE, emitlight[i][j]); + #endif + VectorClear(addlight[i][j]); + } +#else + VectorAdd(patch->totallight, addlight[i], patch->totallight); + #ifdef HLRAD_TRANSFERDATA_COMPRESS + VectorCopy(addlight[i], emitlight[i]); + #else + VectorScale(addlight[i], TRANSFER_SCALE, emitlight[i]); + #endif + VectorClear(addlight[i]); +#endif +#endif + } +} + +// ===================================================================================== +// GatherLight +// Get light from other g_patches +// Run multi-threaded +// ===================================================================================== +#ifdef SYSTEM_WIN32 +#pragma warning(push) +#pragma warning(disable: 4100) // unreferenced formal parameter +#endif +static void GatherLight(int threadnum) +{ + int j; + patch_t* patch; + +#ifdef ZHLT_TEXLIGHT + unsigned k,m; //LRC +//LRC vec3_t sum; +#else + unsigned k; + vec3_t sum; +#endif + + unsigned iIndex; + transfer_data_t* tData; + transfer_index_t* tIndex; +#ifdef HLRAD_TRANSFERDATA_COMPRESS + float f; +#endif +#ifdef HLRAD_STYLE_CORING +#ifdef ZHLT_TEXLIGHT +#ifndef HLRAD_AUTOCORING + int style_index; +#endif + vec3_t adds[ALLSTYLES]; +#ifdef ZHLT_XASH + vec3_t adds_direction[ALLSTYLES]; +#endif + int style; +#endif +#endif +#ifdef HLRAD_OPAQUE_STYLE_BOUNCE + unsigned int fastfind_index = 0; +#endif + + while (1) + { + j = GetThreadWork(); + if (j == -1) + { + break; + } +#ifdef HLRAD_STYLE_CORING +#ifdef ZHLT_TEXLIGHT + memset (adds, 0, ALLSTYLES * sizeof(vec3_t)); +#ifdef ZHLT_XASH + memset (adds_direction, 0, ALLSTYLES * sizeof (vec3_t)); +#endif +#endif +#endif + + patch = &g_patches[j]; + + tData = patch->tData; + tIndex = patch->tIndex; + iIndex = patch->iIndex; + +#ifdef ZHLT_TEXLIGHT +#ifndef HLRAD_AUTOCORING + //LRC + for (m = 0; m < MAXLIGHTMAPS && patch->totalstyle[m] != 255; m++) + { + VectorClear(addlight[j][m]); + } +#endif +#else + VectorClear(sum); +#endif +#ifdef HLRAD_AUTOCORING + for (m = 0; m < MAXLIGHTMAPS && patch->totalstyle[m] != 255; m++) + { + VectorAdd (adds[patch->totalstyle[m]], patch->totallight[m], adds[patch->totalstyle[m]]); +#ifdef ZHLT_XASH + VectorAdd (adds_direction[patch->totalstyle[m]], patch->totallight_direction[m], adds_direction[patch->totalstyle[m]]); +#endif + } +#endif +#ifdef ZHLT_XASH +#ifdef HLRAD_TRANSLUCENT + const vec3_t &patchnormal = getPlaneFromFaceNumber (patch->faceNumber)->normal; +#endif +#endif + + for (k = 0; k < iIndex; k++, tIndex++) + { + unsigned l; + unsigned size = (tIndex->size + 1); + unsigned patchnum = tIndex->index; + + #ifdef HLRAD_TRANSFERDATA_COMPRESS + for (l = 0; l < size; l++, tData+=float_size[g_transfer_compress_type], patchnum++) + #else + for (l = 0; l < size; l++, tData++, patchnum++) + #endif + { + vec3_t v; +#ifdef ZHLT_TEXLIGHT + //LRC: + patch_t* emitpatch = &g_patches[patchnum]; + unsigned emitstyle; +#ifdef HLRAD_OPAQUE_STYLE_BOUNCE + int opaquestyle = -1; + GetStyle (j, patchnum, opaquestyle, fastfind_index); +#endif +#ifdef HLRAD_TRANSFERDATA_COMPRESS + float_decompress (g_transfer_compress_type, tData, &f); +#endif +#ifdef ZHLT_XASH + vec3_t direction; + VectorSubtract (patch->origin, emitpatch->origin, direction); + VectorNormalize (direction); +#ifdef HLRAD_TRANSLUCENT + vec_t dot = DotProduct (direction, patchnormal); + if (dot > 0) + { + // reflect the direction back + VectorMA (direction, -dot * 2, patchnormal, direction); + } +#endif +#endif + + // for each style on the emitting patch +#ifdef HLRAD_AUTOCORING + for (emitstyle = 0; emitstyle < MAXLIGHTMAPS && emitpatch->directstyle[emitstyle] != 255; emitstyle++) + { + #ifdef HLRAD_TRANSFERDATA_COMPRESS + VectorScale(emitpatch->directlight[emitstyle], f, v); + #else + VectorScale(emitpatch->directlight[emitstyle], (*tData) * TRANSFER_SCALE, v); + #endif + #ifdef HLRAD_REFLECTIVITY + VectorMultiply(v, emitpatch->bouncereflectivity, v); + #endif + if (isPointFinite (v)) + { + #ifdef HLRAD_OPAQUE_STYLE_BOUNCE + int addstyle = emitpatch->directstyle[emitstyle]; + #ifdef HLRAD_BOUNCE_STYLE + if (emitpatch->bouncestyle != -1) + { + if (addstyle == 0 || addstyle == emitpatch->bouncestyle) + addstyle = emitpatch->bouncestyle; + else + continue; + } + #endif + if (opaquestyle != -1) + { + if (addstyle == 0 || addstyle == opaquestyle) + addstyle = opaquestyle; + else + continue; + } + VectorAdd(adds[addstyle], v, adds[addstyle]); + #ifdef ZHLT_XASH + vec_t brightness = VectorAvg (v); + VectorMA (adds_direction[addstyle], brightness, direction, adds_direction[addstyle]); + #endif + #else + VectorAdd(adds[emitpatch->directstyle[emitstyle]], v, adds[emitpatch->directstyle[emitstyle]]); + #ifdef ZHLT_XASH + vec_t brightness = VectorAvg (v); + VectorMA (adds_direction[emitpatch->directstyle[emitstyle]], brightness, direction, adds_direction[emitpatch->directstyle[emitstyle]]); + #endif + #endif + } + } +#endif + for (emitstyle = 0; emitstyle < MAXLIGHTMAPS && emitpatch->totalstyle[emitstyle] != 255; emitstyle++) + { +#ifdef HLRAD_STYLE_CORING + #ifdef HLRAD_TRANSFERDATA_COMPRESS + VectorScale(emitlight[patchnum][emitstyle], f, v); + #else + #ifdef HLRAD_AUTOCORING + VectorScale(emitlight[patchnum][emitstyle], (*tData) * TRANSFER_SCALE, v); + #else + VectorScale(emitlight[patchnum][emitstyle], (*tData), v); + #endif + #endif + #ifdef HLRAD_REFLECTIVITY + VectorMultiply(v, emitpatch->bouncereflectivity, v); + #endif + if (isPointFinite(v)) + { + #ifdef HLRAD_OPAQUE_STYLE_BOUNCE + int addstyle = emitpatch->totalstyle[emitstyle]; + #ifdef HLRAD_BOUNCE_STYLE + if (emitpatch->bouncestyle != -1) + { + if (addstyle == 0 || addstyle == emitpatch->bouncestyle) + addstyle = emitpatch->bouncestyle; + else + continue; + } + #endif + if (opaquestyle != -1) + { + if (addstyle == 0 || addstyle == opaquestyle) + addstyle = opaquestyle; + else + continue; + } + VectorAdd(adds[addstyle], v, adds[addstyle]); + #ifdef ZHLT_XASH + vec_t brightness = VectorAvg (v); + VectorMA (adds_direction[addstyle], brightness, direction, adds_direction[addstyle]); + #endif + #else + VectorAdd(adds[emitpatch->totalstyle[emitstyle]], v, adds[emitpatch->totalstyle[emitstyle]]); + #ifdef ZHLT_XASH + vec_t brightness = VectorAvg (v); + VectorMA (adds_direction[emitpatch->totalstyle[emitstyle]], brightness, direction, adds_direction[emitpatch->totalstyle[emitstyle]]); + #endif + #endif + } + else + { + Verbose("GatherLight, v (%4.3f %4.3f %4.3f)@(%4.3f %4.3f %4.3f)\n", + v[0], v[1], v[2], patch->origin[0], patch->origin[1], patch->origin[2]); + } +#else + // find the matching style on this (destination) patch + for (m = 0; m < MAXLIGHTMAPS && patch->totalstyle[m] != 255; m++) + { + if (patch->totalstyle[m] == emitpatch->totalstyle[emitstyle]) + { + break; + } + } + + if (m == MAXLIGHTMAPS) + { +#ifdef HLRAD_READABLE_EXCEEDSTYLEWARNING + if (++stylewarningcount >= stylewarningnext) + { + stylewarningnext = stylewarningcount * 2; + Warning("Too many direct light styles on a face(?,?,?)"); + Warning(" total %d warnings for too many styles", stylewarningcount); + } +#else + Warning("Too many direct light styles on a face(?,?,?)"); +#endif + } + else + { + if (patch->totalstyle[m] == 255) + { + patch->totalstyle[m] = emitpatch->totalstyle[emitstyle]; +// Log("Granting new style %d to patch at idx %d\n", patch->totalstyle[m], m); + } + #ifdef HLRAD_TRANSFERDATA_COMPRESS + float_decompress (g_transfer_compress_type, tData, &f); + VectorScale(emitlight[patchnum][emitstyle], f, v); + #else + VectorScale(emitlight[patchnum][emitstyle], (*tData), v); + #endif + #ifdef HLRAD_REFLECTIVITY + VectorMultiply(v, emitpatch->bouncereflectivity, v); + #endif + if (isPointFinite(v)) + { + VectorAdd(addlight[j][m], v, addlight[j][m]); + } + else + { + Verbose("GatherLight, v (%4.3f %4.3f %4.3f)@(%4.3f %4.3f %4.3f)\n", + v[0], v[1], v[2], patch->origin[0], patch->origin[1], patch->origin[2]); + } + } +#endif + } + //LRC (ends) +#else + #ifdef HLRAD_TRANSFERDATA_COMPRESS + float_decompress (g_transfer_compress_type, tData, &f); + VectorScale(emitlight[patchnum], f, v); + #else + VectorScale(emitlight[patchnum], (*tData), v); + #endif + #ifdef HLRAD_REFLECTIVITY + VectorMultiply(v, emitpatch->bouncereflectivity, v); + #endif + if (isPointFinite(v)) + { + VectorAdd(sum, v, sum); + } + else + { + Verbose("GatherLight, v (%4.3f %4.3f %4.3f)@(%4.3f %4.3f %4.3f)\n", + v[0], v[1], v[2], patch->origin[0], patch->origin[1], patch->origin[2]); + } +#endif + } + } + +#ifdef HLRAD_AUTOCORING + vec_t maxlights[ALLSTYLES]; + for (style = 0; style < ALLSTYLES; style++) + { + maxlights[style] = VectorMaximum (adds[style]); + } + for (m = 0; m < MAXLIGHTMAPS; m++) + { + unsigned char beststyle = 255; + if (m == 0) + { + beststyle = 0; + } + else + { + vec_t bestmaxlight = 0; + for (style = 1; style < ALLSTYLES; style++) + { + if (maxlights[style] > bestmaxlight + NORMAL_EPSILON) + { + bestmaxlight = maxlights[style]; + beststyle = style; + } + } + } + if (beststyle != 255) + { + maxlights[beststyle] = 0; + newstyles[j][m] = beststyle; + VectorCopy (adds[beststyle], addlight[j][m]); +#ifdef ZHLT_XASH + VectorCopy (adds_direction[beststyle], addlight_direction[j][m]); +#endif + } + else + { + newstyles[j][m] = 255; + } + } + for (style = 1; style < ALLSTYLES; style++) + { + if (maxlights[style] > g_maxdiscardedlight + NORMAL_EPSILON) + { + ThreadLock (); + if (maxlights[style] > g_maxdiscardedlight + NORMAL_EPSILON) + { + g_maxdiscardedlight = maxlights[style]; + VectorCopy (patch->origin, g_maxdiscardedpos); + } + ThreadUnlock (); + } + } +#else +#ifdef ZHLT_TEXLIGHT + //LRC VectorCopy(sum, addlight[j]); +#ifdef HLRAD_STYLE_CORING + for (style = 0; style < ALLSTYLES; ++style) + { + if (VectorMaximum(adds[style]) > g_corings[style] * BOUNCE_CORING_SCALE) + { + for (style_index = 0; style_index < MAXLIGHTMAPS; style_index++) + { + if (patch->totalstyle[style_index] == style || patch->totalstyle[style_index] == 255) + { + break; + } + } + + if (style_index == MAXLIGHTMAPS) + { +#ifdef HLRAD_READABLE_EXCEEDSTYLEWARNING + if (++stylewarningcount >= stylewarningnext) + { + stylewarningnext = stylewarningcount * 2; + Warning("Too many indirect light styles on a face(%f,%f,%f)", patch->origin[0], patch->origin[1], patch->origin[2]); + Warning(" total %d warnings for too many styles", stylewarningcount); + } +#else + Warning("Too many indirect light styles on a face(%f,%f,%f)", patch->origin[0], patch->origin[1], patch->origin[2]); +#endif + } + else + { + if (patch->totalstyle[style_index] == 255) + { + patch->totalstyle[style_index] = style; + } + VectorAdd(addlight[j][style_index], adds[style], addlight[j][style_index]); + } + } + } +#endif +#else + VectorCopy(sum, addlight[j]); +#endif +#endif + } +} + +// RGB Transfer version +#ifdef HLRAD_HULLU +static void GatherRGBLight(int threadnum) +{ + int j; + patch_t* patch; + +#ifdef ZHLT_TEXLIGHT + unsigned k,m; //LRC +//LRC vec3_t sum; +#else + unsigned k; + vec3_t sum; +#endif + + unsigned iIndex; + rgb_transfer_data_t* tRGBData; + transfer_index_t* tIndex; +#ifdef HLRAD_TRANSFERDATA_COMPRESS + float f[3]; +#endif +#ifdef HLRAD_STYLE_CORING +#ifdef ZHLT_TEXLIGHT +#ifndef HLRAD_AUTOCORING + int style_index; +#endif + vec3_t adds[ALLSTYLES]; +#ifdef ZHLT_XASH + vec3_t adds_direction[ALLSTYLES]; +#endif + int style; +#endif +#endif +#ifdef HLRAD_OPAQUE_STYLE_BOUNCE + unsigned int fastfind_index = 0; +#endif + + while (1) + { + j = GetThreadWork(); + if (j == -1) + { + break; + } +#ifdef HLRAD_STYLE_CORING +#ifdef ZHLT_TEXLIGHT + memset (adds, 0, ALLSTYLES * sizeof(vec3_t)); +#ifdef ZHLT_XASH + memset (adds_direction, 0, ALLSTYLES * sizeof (vec3_t)); +#endif +#endif +#endif + + patch = &g_patches[j]; + + tRGBData = patch->tRGBData; + tIndex = patch->tIndex; + iIndex = patch->iIndex; + +#ifdef ZHLT_TEXLIGHT +#ifndef HLRAD_AUTOCORING + //LRC + for (m = 0; m < MAXLIGHTMAPS && patch->totalstyle[m] != 255; m++) + { + VectorClear(addlight[j][m]); + } +#endif +#else + VectorClear(sum); +#endif +#ifdef HLRAD_AUTOCORING + for (m = 0; m < MAXLIGHTMAPS && patch->totalstyle[m] != 255; m++) + { + VectorAdd (adds[patch->totalstyle[m]], patch->totallight[m], adds[patch->totalstyle[m]]); +#ifdef ZHLT_XASH + VectorAdd (adds_direction[patch->totalstyle[m]], patch->totallight_direction[m], adds_direction[patch->totalstyle[m]]); +#endif + } +#endif +#ifdef ZHLT_XASH +#ifdef HLRAD_TRANSLUCENT + const vec3_t &patchnormal = getPlaneFromFaceNumber (patch->faceNumber)->normal; +#endif +#endif + + for (k = 0; k < iIndex; k++, tIndex++) + { + unsigned l; + unsigned size = (tIndex->size + 1); + unsigned patchnum = tIndex->index; + #ifdef HLRAD_TRANSFERDATA_COMPRESS + for (l = 0; l < size; l++, tRGBData+=vector_size[g_rgbtransfer_compress_type], patchnum++) + #else + for (l = 0; l < size; l++, tRGBData++, patchnum++) + #endif + { + vec3_t v; +#ifdef ZHLT_TEXLIGHT + //LRC: + patch_t* emitpatch = &g_patches[patchnum]; + unsigned emitstyle; +#ifdef HLRAD_OPAQUE_STYLE_BOUNCE + int opaquestyle = -1; + GetStyle (j, patchnum, opaquestyle, fastfind_index); +#endif +#ifdef HLRAD_TRANSFERDATA_COMPRESS + vector_decompress (g_rgbtransfer_compress_type, tRGBData, &f[0], &f[1], &f[2]); +#endif +#ifdef ZHLT_XASH + vec3_t direction; + VectorSubtract (patch->origin, emitpatch->origin, direction); + VectorNormalize (direction); +#ifdef HLRAD_TRANSLUCENT + vec_t dot = DotProduct (direction, patchnormal); + if (dot > 0) + { + // reflect the direction back + VectorMA (direction, -dot * 2, patchnormal, direction); + } +#endif +#endif + + // for each style on the emitting patch +#ifdef HLRAD_AUTOCORING + for (emitstyle = 0; emitstyle < MAXLIGHTMAPS && emitpatch->directstyle[emitstyle] != 255; emitstyle++) + { + #ifdef HLRAD_TRANSFERDATA_COMPRESS + VectorMultiply(emitpatch->directlight[emitstyle], f, v); + #else + VectorMultiply(emitpatch->directlight[emitstyle], (*tRGBData), v); + VectorScale(v, TRANSFER_SCALE, v); + #endif + #ifdef HLRAD_REFLECTIVITY + VectorMultiply(v, emitpatch->bouncereflectivity, v); + #endif + if (isPointFinite (v)) + { + #ifdef HLRAD_OPAQUE_STYLE_BOUNCE + int addstyle = emitpatch->directstyle[emitstyle]; + #ifdef HLRAD_BOUNCE_STYLE + if (emitpatch->bouncestyle != -1) + { + if (addstyle == 0 || addstyle == emitpatch->bouncestyle) + addstyle = emitpatch->bouncestyle; + else + continue; + } + #endif + if (opaquestyle != -1) + { + if (addstyle == 0 || addstyle == opaquestyle) + addstyle = opaquestyle; + else + continue; + } + VectorAdd(adds[addstyle], v, adds[addstyle]); + #ifdef ZHLT_XASH + vec_t brightness = VectorAvg (v); + VectorMA (adds_direction[addstyle], brightness, direction, adds_direction[addstyle]); + #endif + #else + VectorAdd(adds[emitpatch->directstyle[emitstyle]], v, adds[emitpatch->directstyle[emitstyle]]); + #ifdef ZHLT_XASH + vec_t brightness = VectorAvg (v); + VectorMA (adds_direction[emitpatch->directstyle[emitstyle]], brightness, direction, adds_direction[emitpatch->directstyle[emitstyle]]); + #endif + #endif + } + } +#endif + for (emitstyle = 0; emitstyle < MAXLIGHTMAPS && emitpatch->totalstyle[emitstyle] != 255; emitstyle++) + { +#ifdef HLRAD_STYLE_CORING + #ifdef HLRAD_TRANSFERDATA_COMPRESS + VectorMultiply(emitlight[patchnum][emitstyle], f, v); + #else + #ifdef HLRAD_AUTOCORING + VectorMultiply(emitlight[patchnum][emitstyle], (*tRGBData), v); + VectorScale(v, TRANSFER_SCALE, v); + #else + VectorMultiply(emitlight[patchnum][emitstyle], (*tRGBData), v); + #endif + #endif + #ifdef HLRAD_REFLECTIVITY + VectorMultiply(v, emitpatch->bouncereflectivity, v); + #endif + if (isPointFinite(v)) + { + #ifdef HLRAD_OPAQUE_STYLE_BOUNCE + int addstyle = emitpatch->totalstyle[emitstyle]; + #ifdef HLRAD_BOUNCE_STYLE + if (emitpatch->bouncestyle != -1) + { + if (addstyle == 0 || addstyle == emitpatch->bouncestyle) + addstyle = emitpatch->bouncestyle; + else + continue; + } + #endif + if (opaquestyle != -1) + { + if (addstyle == 0 || addstyle == opaquestyle) + addstyle = opaquestyle; + else + continue; + } + VectorAdd(adds[addstyle], v, adds[addstyle]); + #ifdef ZHLT_XASH + vec_t brightness = VectorAvg (v); + VectorMA (adds_direction[addstyle], brightness, direction, adds_direction[addstyle]); + #endif + #else + VectorAdd(adds[emitpatch->totalstyle[emitstyle]], v, adds[emitpatch->totalstyle[emitstyle]]); + #ifdef ZHLT_XASH + vec_t brightness = VectorAvg (v); + VectorMA (adds_direction[emitpatch->totalstyle[emitstyle]], brightness, direction, adds_direction[emitpatch->totalstyle[emitstyle]]); + #endif + #endif + } + else + { + Verbose("GatherLight, v (%4.3f %4.3f %4.3f)@(%4.3f %4.3f %4.3f)\n", + v[0], v[1], v[2], patch->origin[0], patch->origin[1], patch->origin[2]); + } +#else + // find the matching style on this (destination) patch + for (m = 0; m < MAXLIGHTMAPS && patch->totalstyle[m] != 255; m++) + { + if (patch->totalstyle[m] == emitpatch->totalstyle[emitstyle]) + { + break; + } + } + + if (m == MAXLIGHTMAPS) + { +#ifdef HLRAD_READABLE_EXCEEDSTYLEWARNING + if (++stylewarningcount >= stylewarningnext) + { + stylewarningnext = stylewarningcount * 2; + Warning("Too many direct light styles on a face(?,?,?)"); + Warning(" total %d warnings for too many styles", stylewarningcount); + } +#else + Warning("Too many direct light styles on a face(?,?,?)"); +#endif + } + else + { + if (patch->totalstyle[m] == 255) + { + patch->totalstyle[m] = emitpatch->totalstyle[emitstyle]; +// Log("Granting new style %d to patch at idx %d\n", patch->totalstyle[m], m); + } + #ifdef HLRAD_TRANSFERDATA_COMPRESS + vector_decompress (g_rgbtransfer_compress_type, tRGBData, &f[0], &f[1], &f[2]); + VectorMultiply(emitlight[patchnum][emitstyle], f, v); + #else + VectorMultiply(emitlight[patchnum][emitstyle], (*tRGBData), v); + #endif + #ifdef HLRAD_REFLECTIVITY + VectorMultiply(v, emitpatch->bouncereflectivity, v); + #endif + if (isPointFinite(v)) + { + VectorAdd(addlight[j][m], v, addlight[j][m]); + } + else + { + Verbose("GatherLight, v (%4.3f %4.3f %4.3f)@(%4.3f %4.3f %4.3f)\n", + v[0], v[1], v[2], patch->origin[0], patch->origin[1], patch->origin[2]); + } + } +#endif + } + //LRC (ends) +#else + #ifdef HLRAD_TRANSFERDATA_COMPRESS + vector_decompress (g_rgbtransfer_compress_type, tRGBData, &f[0], &f[1], &f[2]); + VectorMultiply(emitlight[patchnum], (*tRGBData), v); + #else + VectorMultiply(emitlight[patchnum], (*tRGBData), v); + #endif + #ifdef HLRAD_REFLECTIVITY + VectorMultiply(v, emitpatch->bouncereflectivity, v); + #endif + if (isPointFinite(v)) + { + VectorAdd(sum, v, sum); + } + else + { + Verbose("GatherLight, v (%4.3f %4.3f %4.3f)@(%4.3f %4.3f %4.3f)\n", + v[0], v[1], v[2], patch->origin[0], patch->origin[1], patch->origin[2]); + } +#endif + } + } + +#ifdef HLRAD_AUTOCORING + vec_t maxlights[ALLSTYLES]; + for (style = 0; style < ALLSTYLES; style++) + { + maxlights[style] = VectorMaximum (adds[style]); + } + for (m = 0; m < MAXLIGHTMAPS; m++) + { + unsigned char beststyle = 255; + if (m == 0) + { + beststyle = 0; + } + else + { + vec_t bestmaxlight = 0; + for (style = 1; style < ALLSTYLES; style++) + { + if (maxlights[style] > bestmaxlight + NORMAL_EPSILON) + { + bestmaxlight = maxlights[style]; + beststyle = style; + } + } + } + if (beststyle != 255) + { + maxlights[beststyle] = 0; + newstyles[j][m] = beststyle; + VectorCopy (adds[beststyle], addlight[j][m]); +#ifdef ZHLT_XASH + VectorCopy (adds_direction[beststyle], addlight_direction[j][m]); +#endif + } + else + { + newstyles[j][m] = 255; + } + } + for (style = 1; style < ALLSTYLES; style++) + { + if (maxlights[style] > g_maxdiscardedlight + NORMAL_EPSILON) + { + ThreadLock (); + if (maxlights[style] > g_maxdiscardedlight + NORMAL_EPSILON) + { + g_maxdiscardedlight = maxlights[style]; + VectorCopy (patch->origin, g_maxdiscardedpos); + } + ThreadUnlock (); + } + } +#else +#ifdef ZHLT_TEXLIGHT + //LRC VectorCopy(sum, addlight[j]); +#ifdef HLRAD_STYLE_CORING + for (style = 0; style < ALLSTYLES; ++style) + { + if (VectorMaximum(adds[style]) > g_corings[style] * BOUNCE_CORING_SCALE) + { + for (style_index = 0; style_index < MAXLIGHTMAPS; style_index++) + { + if (patch->totalstyle[style_index] == style || patch->totalstyle[style_index] == 255) + { + break; + } + } + + if (style_index == MAXLIGHTMAPS) + { +#ifdef HLRAD_READABLE_EXCEEDSTYLEWARNING + if (++stylewarningcount >= stylewarningnext) + { + stylewarningnext = stylewarningcount * 2; + Warning("Too many indirect light styles on a face(%f,%f,%f)", patch->origin[0], patch->origin[1], patch->origin[2]); + Warning(" total %d warnings for too many styles", stylewarningcount); + } +#else + Warning("Too many indirect light styles on a face(%f,%f,%f)", patch->origin[0], patch->origin[1], patch->origin[2]); +#endif + } + else + { + if (patch->totalstyle[style_index] == 255) + { + patch->totalstyle[style_index] = style; + } + VectorAdd(addlight[j][style_index], adds[style], addlight[j][style_index]); + } + } + } +#endif +#else + VectorCopy(sum, addlight[j]); +#endif +#endif + } +} +#endif + +#ifdef SYSTEM_WIN32 +#pragma warning(pop) +#endif + +// ===================================================================================== +// BounceLight +// ===================================================================================== +static void BounceLight() +{ + unsigned i; + char name[64]; + +#ifdef ZHLT_TEXLIGHT + unsigned j; //LRC +#endif + + for (i = 0; i < g_num_patches; i++) + { +#ifdef HLRAD_AUTOCORING + patch_t *patch = &g_patches[i]; + for (j = 0; j < MAXLIGHTMAPS && patch->totalstyle[j] != 255; j++) + { + VectorCopy (patch->totallight[j], emitlight[i][j]); +#ifdef ZHLT_XASH + VectorCopy (patch->totallight_direction[j], emitlight_direction[i][j]); +#endif + } +#else +#ifdef ZHLT_TEXLIGHT + //LRC + for (j = 0; j < MAXLIGHTMAPS && g_patches[i].totalstyle[j] != 255; j++) + { + #ifdef HLRAD_TRANSFERDATA_COMPRESS + VectorCopy(g_patches[i].totallight[j], emitlight[i][j]); + #else + VectorScale(g_patches[i].totallight[j], TRANSFER_SCALE, emitlight[i][j]); + #endif + } +#else + #ifdef HLRAD_TRANSFERDATA_COMPRESS + VectorCopy(g_patches[i].totallight, emitlight[i]); + #else + VectorScale(g_patches[i].totallight, TRANSFER_SCALE, emitlight[i]); + #endif +#endif +#endif + } + + for (i = 0; i < g_numbounce; i++) + { +#ifdef ZHLT_CONSOLE + Log("Bounce %u ", i + 1); +#else + printf("Bounce %u ", i + 1); +#endif +#ifdef HLRAD_HULLU + if(g_rgb_transfers) + {NamedRunThreadsOn(g_num_patches, g_estimate, GatherRGBLight);} + else + {NamedRunThreadsOn(g_num_patches, g_estimate, GatherLight);} +#else + NamedRunThreadsOn(g_num_patches, g_estimate, GatherLight); +#endif + CollectLight(); + + if (g_dumppatches) + { + sprintf(name, "bounce%u.txt", i); + WriteWorld(name); + } + } +#ifdef HLRAD_AUTOCORING + for (i = 0; i < g_num_patches; i++) + { + patch_t *patch = &g_patches[i]; + for (j = 0; j < MAXLIGHTMAPS && patch->totalstyle[j] != 255; j++) + { + VectorCopy (emitlight[i][j], patch->totallight[j]); +#ifdef ZHLT_XASH + VectorCopy (emitlight_direction[i][j], patch->totallight_direction[j]); +#endif + } + } +#endif +} + +// ===================================================================================== +// CheckMaxPatches +// ===================================================================================== +static void CheckMaxPatches() +{ + switch (g_method) + { + case eMethodVismatrix: + hlassume(g_num_patches < MAX_VISMATRIX_PATCHES, assume_MAX_PATCHES); // should use "<=" instead. --vluzacn + break; + case eMethodSparseVismatrix: + hlassume(g_num_patches < MAX_SPARSE_VISMATRIX_PATCHES, assume_MAX_PATCHES); + break; + case eMethodNoVismatrix: + hlassume(g_num_patches < MAX_PATCHES, assume_MAX_PATCHES); + break; + } +} + +// ===================================================================================== +// MakeScalesStub +// ===================================================================================== +static void MakeScalesStub() +{ + switch (g_method) + { + case eMethodVismatrix: + MakeScalesVismatrix(); + break; + case eMethodSparseVismatrix: + MakeScalesSparseVismatrix(); + break; + case eMethodNoVismatrix: + MakeScalesNoVismatrix(); + break; + } +} + +// ===================================================================================== +// FreeTransfers +// ===================================================================================== +static void FreeTransfers() +{ + unsigned x; + patch_t* patch = g_patches; + + for (x = 0; x < g_num_patches; x++, patch++) + { + if (patch->tData) + { + FreeBlock(patch->tData); + patch->tData = NULL; + } +#ifdef HLRAD_HULLU + if (patch->tRGBData) + { + FreeBlock(patch->tRGBData); + patch->tRGBData = NULL; + } +#endif + if (patch->tIndex) + { + FreeBlock(patch->tIndex); + patch->tIndex = NULL; + } + } +} + +#ifdef ZHLT_XASH +vec_t FindDirectionScale (vec_t gamma) + // gamma 0.55(default) 0.6 0.7 0.8 0.9 1.0 0.5 0.4 0.3 0.2 0.1 + // returned value 0.266 0.313 0.424 0.565 0.751 1.000 0.225 0.155 0.099 0.055 0.023 +{ + if (gamma >= 1.0 - NORMAL_EPSILON) + { + return 1.0; + } + if (gamma < NORMAL_EPSILON) + { + return 0.0; + } + int numsteps = 200; + vec_t testmax = 2.0; + vec3_t staticlight; + vec3_t dynamiclight; + vec_t maxlength = 1; + int maxlength_i = -1; + int maxlength_j = -1; + int i, j; + for (i = 0; i < numsteps; i++) + { + for (j = 0; j < numsteps; j++) + { + vec_t directionlength; + staticlight[0] = 1.0; + staticlight[2] = staticlight[1] = testmax * (vec_t)i / (vec_t)numsteps; + dynamiclight[0] = testmax * (vec_t)j / (vec_t)numsteps; + dynamiclight[2] = dynamiclight[1] = 0.0; + { + vec3_t finalstaticlight, finaldynamiclight; + directionlength = (VectorAvg (staticlight) * 1.0 + VectorAvg (dynamiclight) * (-1.0)) / (VectorAvg (staticlight) + VectorAvg (dynamiclight)); + for (int k = 0; k < 3; k++) + { + finalstaticlight[k] = pow (staticlight[k], gamma); + finaldynamiclight[k] = pow (staticlight[k] + dynamiclight[k], gamma) - finalstaticlight[k]; + } + if (VectorAvg (finaldynamiclight) < NORMAL_EPSILON) + { + continue; + } + directionlength = (VectorAvg (finalstaticlight) + VectorAvg (finaldynamiclight)) * directionlength; + directionlength = fabs ((directionlength - VectorAvg (finalstaticlight) * 1.0) / VectorAvg (finaldynamiclight)); + } + if (directionlength > maxlength) + { + maxlength = directionlength; + maxlength_i = i; + maxlength_j = j; + } + } + } + Developer (DEVELOPER_LEVEL_MESSAGE, "maxlength = %f i = %d j = %d\n", maxlength, maxlength_i, maxlength_j); + return 1 / maxlength; +} +#endif +#ifdef ZHLT_EMBEDLIGHTMAP +static void ExtendLightmapBuffer () +{ + int maxsize; + int i; + int j; + int ofs; + dface_t *f; + + maxsize = 0; + for (i = 0; i < g_numfaces; i++) + { + f = &g_dfaces[i]; + if (f->lightofs >= 0) + { + ofs = f->lightofs; + for (j = 0; j < MAXLIGHTMAPS && f->styles[j] != 255; j++) + { + ofs += (MAX_SURFACE_EXTENT + 1) * (MAX_SURFACE_EXTENT + 1) * 3; + } + if (ofs > maxsize) + { + maxsize = ofs; + } + } + } + if (maxsize >= g_lightdatasize) + { + hlassume (maxsize <= g_max_map_lightdata, assume_MAX_MAP_LIGHTING); + memset (&g_dlightdata[g_lightdatasize], 0, maxsize - g_lightdatasize); + g_lightdatasize = maxsize; +#ifdef ZHLT_XASH + hlassume (maxsize < g_max_map_dlitdata, assume_MAX_MAP_LIGHTING); + memset (&g_ddlitdata[g_dlitdatasize], 0, maxsize - g_dlitdatasize); + g_dlitdatasize = maxsize; +#endif + } +} + +#endif +// ===================================================================================== +// RadWorld +// ===================================================================================== +static void RadWorld() +{ + unsigned i; +#ifdef ZHLT_TEXLIGHT + unsigned j; +#endif + + MakeBackplanes(); + MakeParents(0, -1); + MakeTnodes(&g_dmodels[0]); +#ifdef HLRAD_OPAQUE_NODE + CreateOpaqueNodes(); + LoadOpaqueEntities(); +#endif + + // turn each face into a single patch + MakePatches(); +#ifdef HLRAD_DEBUG_DRAWPOINTS + if (g_drawpatch) + { + char name[_MAX_PATH+20]; + sprintf (name, "%s_patch.pts", g_Mapname); + Log ("Writing '%s' ...\n", name); + FILE *f; + f = fopen(name, "w"); + if (f) + { + const int pos_count = 15; + const vec3_t pos[pos_count] = {{0,0,0},{1,0,0},{0,1,0},{-1,0,0},{0,-1,0},{1,0,0},{0,0,1},{-1,0,0},{0,0,-1},{0,-1,0},{0,0,1},{0,1,0},{0,0,-1},{1,0,0},{0,0,0}}; + int j, k; + patch_t *patch; + vec3_t v; + for (j = 0, patch = g_patches; j < g_num_patches; j++, patch++) + { + if (patch->flags == ePatchFlagOutside) + continue; + VectorCopy (patch->origin, v); + for (k = 0; k < pos_count; ++k) + fprintf (f, "%g %g %g\n", v[0]+pos[k][0], v[1]+pos[k][1], v[2]+pos[k][2]); + } + fclose(f); + Log ("OK.\n"); + } + else + Log ("Error.\n"); + } +#endif + CheckMaxPatches(); // Check here for exceeding max patches, to prevent a lot of work from occuring before an error occurs + SortPatches(); // Makes the runs in the Transfer Compression really good + PairEdges(); +#ifdef HLRAD_DEBUG_DRAWPOINTS + if (g_drawedge) + { + char name[_MAX_PATH+20]; + sprintf (name, "%s_edge.pts", g_Mapname); + Log ("Writing '%s' ...\n", name); + FILE *f; + f = fopen(name, "w"); + if (f) + { + const int pos_count = 15; + const vec3_t pos[pos_count] = {{0,0,0},{1,0,0},{0,1,0},{-1,0,0},{0,-1,0},{1,0,0},{0,0,1},{-1,0,0},{0,0,-1},{0,-1,0},{0,0,1},{0,1,0},{0,0,-1},{1,0,0},{0,0,0}}; + int j, k; + edgeshare_t *es; + vec3_t v; + for (j = 0, es = g_edgeshare; j < MAX_MAP_EDGES; j++, es++) + { +#ifdef HLRAD_GetPhongNormal_VL + if (es->smooth) +#else + if (es->coplanar || !VectorCompare (es->interface_normal, vec3_origin)) +#endif + { + int v0 = g_dedges[j].v[0], v1 = g_dedges[j].v[1]; + VectorAdd (g_dvertexes[v0].point, g_dvertexes[v1].point, v); + VectorScale (v, 0.5, v); + VectorAdd (v, es->interface_normal, v); + VectorAdd (v, g_face_offset[es->faces[0] - g_dfaces], v); + for (k = 0; k < pos_count; ++k) + fprintf (f, "%g %g %g\n", v[0]+pos[k][0], v[1]+pos[k][1], v[2]+pos[k][2]); + } + } + fclose(f); + Log ("OK.\n"); + } + else + Log ("Error.\n"); + } +#endif + +#ifdef HLRAD_SOFTSKY + BuildDiffuseNormals (); +#endif + // create directlights out of g_patches and lights + CreateDirectLights(); + + Log("\n"); + +#ifdef HLRAD_GROWSAMPLE + // generate a position map for each face + NamedRunThreadsOnIndividual(g_numfaces, g_estimate, FindFacePositions); + +#endif + // build initial facelights + NamedRunThreadsOnIndividual(g_numfaces, g_estimate, BuildFacelights); + +#ifdef HLRAD_GROWSAMPLE + FreePositionMaps (); + +#endif + // free up the direct lights now that we have facelights + DeleteDirectLights(); + + if (g_numbounce > 0) + { + // build transfer lists + MakeScalesStub(); + +#ifdef HLRAD_MORE_PATCHES + // these arrays are only used in CollectLight, GatherLight and BounceLight + #ifdef ZHLT_TEXLIGHT + emitlight = (vec3_t (*)[MAXLIGHTMAPS])AllocBlock ((g_num_patches + 1) * sizeof (vec3_t [MAXLIGHTMAPS])); + addlight = (vec3_t (*)[MAXLIGHTMAPS])AllocBlock ((g_num_patches + 1) * sizeof (vec3_t [MAXLIGHTMAPS])); + #ifdef ZHLT_XASH + emitlight_direction = (vec3_t (*)[MAXLIGHTMAPS])AllocBlock ((g_num_patches + 1) * sizeof (vec3_t [MAXLIGHTMAPS])); + addlight_direction = (vec3_t (*)[MAXLIGHTMAPS])AllocBlock ((g_num_patches + 1) * sizeof (vec3_t [MAXLIGHTMAPS])); + #endif + #ifdef HLRAD_AUTOCORING + newstyles = (unsigned char (*)[MAXLIGHTMAPS])AllocBlock ((g_num_patches + 1) * sizeof (unsigned char [MAXLIGHTMAPS])); + #endif + #else + emitlight = (vec3_t *)AllocBlock ((g_num_patches + 1) * sizeof (vec3_t)); + addlight = (vec3_t *)AllocBlock ((g_num_patches + 1) * sizeof (vec3_t)); + #endif +#endif + // spread light around + BounceLight(); + +#ifndef HLRAD_AUTOCORING + for (i = 0; i < g_num_patches; i++) + { +#ifdef ZHLT_TEXLIGHT// AJM + for (j = 0; j < MAXLIGHTMAPS && g_patches[i].totalstyle[j] != 255; j++) + { + VectorSubtract(g_patches[i].totallight[j], g_patches[i].directlight[j], g_patches[i].totallight[j]); + } +#else + VectorSubtract(g_patches[i].totallight, g_patches[i].directlight, g_patches[i].totallight); +#endif + } +#endif +#ifdef HLRAD_MORE_PATCHES + #ifdef ZHLT_TEXLIGHT + FreeBlock (emitlight); + emitlight = NULL; + FreeBlock (addlight); + addlight = NULL; + #ifdef ZHLT_XASH + FreeBlock (emitlight_direction); + emitlight_direction = NULL; + FreeBlock (addlight_direction); + addlight_direction = NULL; + #endif + #ifdef HLRAD_AUTOCORING + FreeBlock (newstyles); + newstyles = NULL; + #endif + #else + FreeBlock (emitlight); + emitlight = NULL; + FreeBlock (addlight); + addlight = NULL; + #endif +#endif + } +#ifndef HLRAD_AUTOCORING +#ifdef HLRAD_GatherPatchLight + if (g_numbounce <= 0) + { + for (i = 0; i < g_num_patches; i++) + { +#ifdef ZHLT_TEXLIGHT// AJM + for (j = 0; j < MAXLIGHTMAPS && g_patches[i].totalstyle[j] != 255; j++) + { + VectorSubtract(g_patches[i].totallight[j], g_patches[i].directlight[j], g_patches[i].totallight[j]); + } +#else + VectorSubtract(g_patches[i].totallight, g_patches[i].directlight, g_patches[i].totallight); +#endif + } + } +#endif +#endif + + FreeTransfers(); +#ifdef HLRAD_OPAQUE_STYLE_BOUNCE + FreeStyleArrays (); +#endif + +#ifdef HLRAD_LOCALTRIANGULATION + NamedRunThreadsOnIndividual (g_numfaces, g_estimate, CreateTriangulations); + +#endif + // blend bounced light into direct light and save + PrecompLightmapOffsets(); + +#ifdef ZHLT_XASH +#ifdef HLRAD_WHOME + g_directionscale = FindDirectionScale (VectorAvg (g_colour_qgamma)); +#else + g_directionscale = FindDirectionScale (g_qgamma); +#endif +#endif +#ifdef HLRAD_GROWSAMPLE + + ScaleDirectLights (); + +#ifndef HLRAD_GatherPatchLight + if (g_numbounce) +#endif + { + + CreateFacelightDependencyList (); + + NamedRunThreadsOnIndividual (g_numfaces, g_estimate, AddPatchLights); + + FreeFacelightDependencyList (); + } + +#endif +#ifdef HLRAD_LOCALTRIANGULATION + FreeTriangulations (); + +#endif + NamedRunThreadsOnIndividual(g_numfaces, g_estimate, FinalLightFace); +#ifdef HLRAD_AUTOCORING + if (g_maxdiscardedlight > 0.01) + { + Verbose ("Maximum brightness loss (too many light styles on a face) = %f @(%f, %f, %f)\n", g_maxdiscardedlight, g_maxdiscardedpos[0], g_maxdiscardedpos[1], g_maxdiscardedpos[2]); + } +#endif +#ifdef HLRAD_MDL_LIGHT_HACK + MdlLightHack (); +#endif +#ifdef HLRAD_REDUCELIGHTMAP + ReduceLightmap(); + if (g_lightdatasize == 0) + { + g_lightdatasize = 1; + g_dlightdata[0] = 0; + #ifdef ZHLT_XASH + g_dlitdatasize = 1; + g_ddlitdata[0] = 0; + #endif + } +#endif +#ifdef ZHLT_EMBEDLIGHTMAP + ExtendLightmapBuffer (); // expand the size of lightdata array (for a few KB) to ensure that game engine reads within its valid range +#endif +} + +// ===================================================================================== +// Usage +// ===================================================================================== +static void Usage() +{ + Banner(); + + Log("\n-= %s Options =-\n\n", g_Program); +#ifdef ZHLT_CONSOLE + Log(" -console # : Set to 0 to turn off the pop-up console (default is 1)\n"); +#endif +#ifdef ZHLT_LANGFILE + Log(" -lang file : localization file\n"); +#endif +#ifdef HLRAD_TEXTURE + Log(" -waddir folder : Search this folder for wad files.\n"); +#endif +#ifdef HLRAD_FASTMODE + Log(" -fast : Fast rad\n"); +#endif +#ifdef HLRAD_ARG_MISC + Log(" -vismatrix value: Set vismatrix method to normal, sparse or off .\n"); +#else + Log(" -sparse : Enable low memory vismatrix algorithm\n"); + Log(" -nomatrix : Disable usage of vismatrix entirely\n\n"); +#endif + Log(" -extra : Improve lighting quality by doing 9 point oversampling\n"); + Log(" -bounce # : Set number of radiosity bounces\n"); + Log(" -ambient r g b : Set ambient world light (0.0 to 1.0, r g b)\n"); +#ifndef HLRAD_FinalLightFace_VL + Log(" -maxlight # : Set maximum light intensity value\n"); +#endif +#ifdef HLRAD_PRESERVELIGHTMAPCOLOR + Log(" -limiter # : Set light clipping threshold (-1=None)\n"); +#endif + Log(" -circus : Enable 'circus' mode for locating unlit lightmaps\n"); +#ifdef HLRAD_SUNSPREAD + Log(" -nospread : Disable sunlight spread angles for this compile\n"); +#endif + Log(" -nopaque : Disable the opaque zhlt_lightflags for this compile\n\n"); + Log(" -smooth # : Set smoothing threshold for blending (in degrees)\n"); +#ifdef HLRAD_CUSTOMSMOOTH + Log(" -smooth2 # : Set smoothing threshold between different textures\n"); +#endif + Log(" -chop # : Set radiosity patch size for normal textures\n"); + Log(" -texchop # : Set radiosity patch size for texture light faces\n\n"); + Log(" -notexscale : Do not scale radiosity patches with texture scale\n"); + Log(" -coring # : Set lighting threshold before blackness\n"); + Log(" -dlight # : Set direct lighting threshold\n"); + Log(" -nolerp : Disable radiosity interpolation, nearest point instead\n\n"); + Log(" -fade # : Set global fade (larger values = shorter lights)\n"); +#ifndef HLRAD_ARG_MISC + Log(" -falloff # : Set global falloff mode (1 = inv linear, 2 = inv square)\n"); +#endif +#ifdef HLRAD_TEXLIGHTGAP + Log(" -texlightgap # : Set global gap distance for texlights\n"); +#endif + Log(" -scale # : Set global light scaling value\n"); + Log(" -gamma # : Set global gamma value\n\n"); + Log(" -sky # : Set ambient sunlight contribution in the shade outside\n"); + Log(" -lights file : Manually specify a lights.rad file to use\n"); + Log(" -noskyfix : Disable light_environment being global\n"); + Log(" -incremental : Use or create an incremental transfer list file\n\n"); + Log(" -dump : Dumps light patches to a file for hlrad debugging info\n\n"); + Log(" -texdata # : Alter maximum texture memory limit (in kb)\n"); + Log(" -lightdata # : Alter maximum lighting memory limit (in kb)\n"); //lightdata + Log(" -chart : display bsp statitics\n"); + Log(" -low | -high : run program an altered priority level\n"); + Log(" -nolog : Do not generate the compile logfiles\n"); + Log(" -threads # : manually specify the number of threads to run\n"); +#ifdef SYSTEM_WIN32 + Log(" -estimate : display estimated time during compile\n"); +#endif +#ifdef ZHLT_PROGRESSFILE // AJM + Log(" -progressfile path : specify the path to a file for progress estimate output\n"); +#endif +#ifdef SYSTEM_POSIX + Log(" -noestimate : Do not display continuous compile time estimates\n"); +#endif + Log(" -verbose : compile with verbose messages\n"); + Log(" -noinfo : Do not show tool configuration information\n"); + Log(" -dev # : compile with developer message\n\n"); + + // ------------------------------------------------------------------------ + // Changes by Adam Foster - afoster@compsoc.man.ac.uk +#ifdef HLRAD_WHOME + + // AJM: we dont need this extra crap + //Log("-= Unofficial features added by Adam Foster (afoster@compsoc.man.ac.uk) =-\n\n"); + Log(" -colourgamma r g b : Sets different gamma values for r, g, b\n" ); + Log(" -colourscale r g b : Sets different lightscale values for r, g ,b\n" ); + Log(" -colourjitter r g b : Adds noise, independent colours, for dithering\n"); + Log(" -jitter r g b : Adds noise, monochromatic, for dithering\n"); +#ifndef HLRAD_ARG_MISC + Log(" -nodiffuse : Disables light_environment diffuse hack\n"); + Log(" -nospotpoints : Disables light_spot spherical point sources\n"); +#endif +#ifndef HLRAD_CUSTOMTEXLIGHT // no softlight hack + Log(" -softlight r g b d : Scaling values for backwards-light hack\n\n"); +#endif + //Log("-= End of unofficial features! =-\n\n" ); + +#endif + // ------------------------------------------------------------------------ + +#ifdef HLRAD_HULLU + Log(" -customshadowwithbounce : Enables custom shadows with bounce light\n"); + Log(" -rgbtransfers : Enables RGB Transfers (for custom shadows)\n\n"); +#endif + +#ifdef HLRAD_TRANSTOTAL_HACK +#ifndef HLRAD_REFLECTIVITY + Log(" -bscale : Scaling light on every bounce\n\n"); +#endif +#endif +#ifdef HLRAD_MINLIGHT + Log(" -minlight # : Minimum final light (integer from 0 to 255)\n"); +#endif +#ifdef HLRAD_TRANSFERDATA_COMPRESS + { + int i; + Log(" -compress # : compress tranfer ("); + for (i=0; i= 0 ? buf1 : "None", buf2); +#endif + Log("circus mode [ %17s ] [ %17s ]\n", g_circus ? "on" : "off", DEFAULT_CIRCUS ? "on" : "off"); + + Log("\n"); + + safe_snprintf(buf1, sizeof(buf1), "%3.3f", g_smoothing_value); + safe_snprintf(buf2, sizeof(buf2), "%3.3f", DEFAULT_SMOOTHING_VALUE); + Log("smoothing threshold [ %17s ] [ %17s ]\n", buf1, buf2); +#ifdef HLRAD_CUSTOMSMOOTH + safe_snprintf(buf1, sizeof(buf1), g_smoothing_value_2<0? "no change": "%3.3f", g_smoothing_value_2); + safe_snprintf(buf2, sizeof(buf2), DEFAULT_SMOOTHING2_VALUE<0? "no change": "%3.3f", DEFAULT_SMOOTHING2_VALUE); + Log("smoothing threshold 2[ %17s ] [ %17s ]\n", buf1, buf2); +#endif + safe_snprintf(buf1, sizeof(buf1), "%3.3f", g_dlight_threshold); + safe_snprintf(buf2, sizeof(buf2), "%3.3f", DEFAULT_DLIGHT_THRESHOLD); + Log("direct threshold [ %17s ] [ %17s ]\n", buf1, buf2); + safe_snprintf(buf1, sizeof(buf1), "%3.3f", g_direct_scale); + safe_snprintf(buf2, sizeof(buf2), "%3.3f", DEFAULT_DLIGHT_SCALE); + Log("direct light scale [ %17s ] [ %17s ]\n", buf1, buf2); + safe_snprintf(buf1, sizeof(buf1), "%3.3f", g_coring); + safe_snprintf(buf2, sizeof(buf2), "%3.3f", DEFAULT_CORING); + Log("coring threshold [ %17s ] [ %17s ]\n", buf1, buf2); + Log("patch interpolation [ %17s ] [ %17s ]\n", g_lerp_enabled ? "on" : "off", DEFAULT_LERP_ENABLED ? "on" : "off"); + + Log("\n"); + + Log("texscale [ %17s ] [ %17s ]\n", g_texscale ? "on" : "off", DEFAULT_TEXSCALE ? "on" : "off"); + Log("patch subdividing [ %17s ] [ %17s ]\n", g_subdivide ? "on" : "off", DEFAULT_SUBDIVIDE ? "on" : "off"); + safe_snprintf(buf1, sizeof(buf1), "%3.3f", g_chop); + safe_snprintf(buf2, sizeof(buf2), "%3.3f", DEFAULT_CHOP); + Log("chop value [ %17s ] [ %17s ]\n", buf1, buf2); + safe_snprintf(buf1, sizeof(buf1), "%3.3f", g_texchop); + safe_snprintf(buf2, sizeof(buf2), "%3.3f", DEFAULT_TEXCHOP); + Log("texchop value [ %17s ] [ %17s ]\n", buf1, buf2); + Log("\n"); + + safe_snprintf(buf1, sizeof(buf1), "%3.3f", g_fade); + safe_snprintf(buf2, sizeof(buf2), "%3.3f", DEFAULT_FADE); + Log("global fade [ %17s ] [ %17s ]\n", buf1, buf2); +#ifndef HLRAD_ARG_MISC + Log("global falloff [ %17d ] [ %17d ]\n", g_falloff, DEFAULT_FALLOFF); +#endif +#ifdef HLRAD_TEXLIGHTGAP + safe_snprintf(buf1, sizeof(buf1), "%3.3f", g_texlightgap); + safe_snprintf(buf2, sizeof(buf2), "%3.3f", DEFAULT_TEXLIGHTGAP); + Log("global texlight gap [ %17s ] [ %17s ]\n", buf1, buf2); +#endif + + // ------------------------------------------------------------------------ + // Changes by Adam Foster - afoster@compsoc.man.ac.uk + // replaces the old stuff for displaying current values for gamma and lightscale +#ifdef HLRAD_WHOME + safe_snprintf(buf1, sizeof(buf1), "%1.3f %1.3f %1.3f", g_colour_lightscale[0], g_colour_lightscale[1], g_colour_lightscale[2]); + safe_snprintf(buf2, sizeof(buf2), "%1.3f %1.3f %1.3f", DEFAULT_COLOUR_LIGHTSCALE_RED, DEFAULT_COLOUR_LIGHTSCALE_GREEN, DEFAULT_COLOUR_LIGHTSCALE_BLUE); + Log("global light scale [ %17s ] [ %17s ]\n", buf1, buf2); + + safe_snprintf(buf1, sizeof(buf1), "%1.3f %1.3f %1.3f", g_colour_qgamma[0], g_colour_qgamma[1], g_colour_qgamma[2]); + safe_snprintf(buf2, sizeof(buf2), "%1.3f %1.3f %1.3f", DEFAULT_COLOUR_GAMMA_RED, DEFAULT_COLOUR_GAMMA_GREEN, DEFAULT_COLOUR_GAMMA_BLUE); + Log("global gamma [ %17s ] [ %17s ]\n", buf1, buf2); +#endif + // ------------------------------------------------------------------------ + + safe_snprintf(buf1, sizeof(buf1), "%3.3f", g_lightscale); + safe_snprintf(buf2, sizeof(buf2), "%3.3f", DEFAULT_LIGHTSCALE); + Log("global light scale [ %17s ] [ %17s ]\n", buf1, buf2); + +#ifndef HLRAD_WHOME + safe_snprintf(buf1, sizeof(buf1), "%3.3f", g_qgamma); + safe_snprintf(buf2, sizeof(buf2), "%3.3f", DEFAULT_GAMMA); + Log("global gamma amount [ %17s ] [ %17s ]\n", buf1, buf2); +#endif + + safe_snprintf(buf1, sizeof(buf1), "%3.3f", g_indirect_sun); + safe_snprintf(buf2, sizeof(buf2), "%3.3f", DEFAULT_INDIRECT_SUN); + Log("global sky diffusion [ %17s ] [ %17s ]\n", buf1, buf2); + + Log("\n"); +#ifdef HLRAD_SUNSPREAD + Log("spread angles [ %17s ] [ %17s ]\n", g_allow_spread ? "on" : "off", DEFAULT_ALLOW_SPREAD ? "on" : "off"); +#endif + Log("opaque entities [ %17s ] [ %17s ]\n", g_allow_opaques ? "on" : "off", DEFAULT_ALLOW_OPAQUES ? "on" : "off"); + Log("sky lighting fix [ %17s ] [ %17s ]\n", g_sky_lighting_fix ? "on" : "off", DEFAULT_SKY_LIGHTING_FIX ? "on" : "off"); + Log("incremental [ %17s ] [ %17s ]\n", g_incremental ? "on" : "off", DEFAULT_INCREMENTAL ? "on" : "off"); + Log("dump [ %17s ] [ %17s ]\n", g_dumppatches ? "on" : "off", DEFAULT_DUMPPATCHES ? "on" : "off"); + + // ------------------------------------------------------------------------ + // Changes by Adam Foster - afoster@compsoc.man.ac.uk + // displays information on all the brand-new features :) +#ifdef HLRAD_WHOME + + Log("\n"); + safe_snprintf(buf1, sizeof(buf1), "%3.1f %3.1f %3.1f", g_colour_jitter_hack[0], g_colour_jitter_hack[1], g_colour_jitter_hack[2]); + safe_snprintf(buf2, sizeof(buf2), "%3.1f %3.1f %3.1f", DEFAULT_COLOUR_JITTER_HACK_RED, DEFAULT_COLOUR_JITTER_HACK_GREEN, DEFAULT_COLOUR_JITTER_HACK_BLUE); + Log("colour jitter [ %17s ] [ %17s ]\n", buf1, buf2); + safe_snprintf(buf1, sizeof(buf1), "%3.1f %3.1f %3.1f", g_jitter_hack[0], g_jitter_hack[1], g_jitter_hack[2]); + safe_snprintf(buf2, sizeof(buf2), "%3.1f %3.1f %3.1f", DEFAULT_JITTER_HACK_RED, DEFAULT_JITTER_HACK_GREEN, DEFAULT_JITTER_HACK_BLUE); + Log("monochromatic jitter [ %17s ] [ %17s ]\n", buf1, buf2); + +#ifndef HLRAD_CUSTOMTEXLIGHT // no softlight hack + safe_snprintf(buf1, sizeof(buf1), "%2.1f %2.1f %2.1f %2.1f", g_softlight_hack[0], g_softlight_hack[1], g_softlight_hack[2], g_softlight_hack_distance); + safe_snprintf(buf2, sizeof(buf2), "%2.1f %2.1f %2.1f %2.1f", DEFAULT_SOFTLIGHT_HACK_RED, DEFAULT_SOFTLIGHT_HACK_GREEN, DEFAULT_SOFTLIGHT_HACK_BLUE, DEFAULT_SOFTLIGHT_HACK_DISTANCE); + Log("softlight hack [ %17s ] [ %17s ]\n", buf1, buf2); +#endif + +#ifndef HLRAD_ARG_MISC + Log("diffuse hack [ %17s ] [ %17s ]\n", g_diffuse_hack ? "on" : "off", DEFAULT_DIFFUSE_HACK ? "on" : "off"); + Log("spotlight points [ %17s ] [ %17s ]\n", g_spotlight_hack ? "on" : "off", DEFAULT_SPOTLIGHT_HACK ? "on" : "off"); +#endif + +#endif + // ------------------------------------------------------------------------ + +#ifdef HLRAD_HULLU + Log("\n"); + Log("custom shadows with bounce light\n" + " [ %17s ] [ %17s ]\n", g_customshadow_with_bouncelight ? "on" : "off", DEFAULT_CUSTOMSHADOW_WITH_BOUNCELIGHT ? "on" : "off"); + Log("rgb transfers [ %17s ] [ %17s ]\n", g_rgb_transfers ? "on" : "off", DEFAULT_RGB_TRANSFERS ? "on" : "off"); +#endif + +#ifdef HLRAD_TRANSTOTAL_HACK +#ifndef HLRAD_REFLECTIVITY + safe_snprintf(buf1, sizeof(buf1), "%3.3f", g_transtotal_hack); + safe_snprintf(buf2, sizeof(buf2), "%3.3f", DEFAULT_TRANSTOTAL_HACK); + Log("bounce scale [ %17s ] [ %17s ]\n", buf1, buf2); +#endif +#endif +#ifdef HLRAD_MINLIGHT + Log("minimum final light [ %17d ] [ %17d ]\n", (int)g_minlight, (int)DEFAULT_MINLIGHT); +#endif +#ifdef HLRAD_TRANSFERDATA_COMPRESS + sprintf (buf1, "%d (%s)", g_transfer_compress_type, float_type_string[g_transfer_compress_type]); + sprintf (buf2, "%d (%s)", DEFAULT_TRANSFER_COMPRESS_TYPE, float_type_string[DEFAULT_TRANSFER_COMPRESS_TYPE]); + Log("size of transfer [ %17s ] [ %17s ]\n", buf1, buf2); + sprintf (buf1, "%d (%s)", g_rgbtransfer_compress_type, vector_type_string[g_rgbtransfer_compress_type]); + sprintf (buf2, "%d (%s)", DEFAULT_RGBTRANSFER_COMPRESS_TYPE, vector_type_string[DEFAULT_RGBTRANSFER_COMPRESS_TYPE]); + Log("size of rgbtransfer [ %17s ] [ %17s ]\n", buf1, buf2); +#endif +#ifdef HLRAD_SOFTSKY + Log("soft sky [ %17s ] [ %17s ]\n", g_softsky ? "on" : "off", DEFAULT_SOFTSKY ? "on" : "off"); +#endif +#ifdef HLRAD_TRANSLUCENT + safe_snprintf(buf1, sizeof(buf1), "%3.3f", g_translucentdepth); + safe_snprintf(buf2, sizeof(buf2), "%3.3f", DEFAULT_TRANSLUCENTDEPTH); + Log("translucent depth [ %17s ] [ %17s ]\n", buf1, buf2); +#endif +#ifdef HLRAD_OPAQUE_BLOCK + Log("block opaque [ %17s ] [ %17s ]\n", g_blockopaque ? "on" : "off", DEFAULT_BLOCKOPAQUE ? "on" : "off"); +#endif +#ifdef HLRAD_TEXTURE + Log("ignore textures [ %17s ] [ %17s ]\n", g_notextures ? "on" : "off", DEFAULT_NOTEXTURES ? "on" : "off"); +#endif +#ifdef HLRAD_REFLECTIVITY + safe_snprintf(buf1, sizeof(buf1), "%3.3f", g_texreflectgamma); + safe_snprintf(buf2, sizeof(buf2), "%3.3f", DEFAULT_TEXREFLECTGAMMA); + Log("reflectivity gamma [ %17s ] [ %17s ]\n", buf1, buf2); + safe_snprintf(buf1, sizeof(buf1), "%3.3f", g_texreflectscale); + safe_snprintf(buf2, sizeof(buf2), "%3.3f", DEFAULT_TEXREFLECTSCALE); + Log("reflectivity scale [ %17s ] [ %17s ]\n", buf1, buf2); +#endif +#ifdef HLRAD_BLUR + safe_snprintf(buf1, sizeof(buf1), "%3.3f", g_blur); + safe_snprintf(buf2, sizeof(buf2), "%3.3f", DEFAULT_BLUR); + Log("blur size [ %17s ] [ %17s ]\n", buf1, buf2); +#endif +#ifdef HLRAD_ACCURATEBOUNCE + Log("no emitter range [ %17s ] [ %17s ]\n", g_noemitterrange ? "on" : "off", DEFAULT_NOEMITTERRANGE ? "on" : "off"); +#endif +#ifdef HLRAD_AVOIDWALLBLEED + Log("wall bleeding fix [ %17s ] [ %17s ]\n", g_bleedfix ? "on" : "off", DEFAULT_BLEEDFIX ? "on" : "off"); +#endif + + Log("\n\n"); +} + +#ifdef HLRAD_INFO_TEXLIGHTS +// AJM: added in +// ===================================================================================== +// ReadInfoTexlights +// try and parse texlight info from the info_texlights entity +// ===================================================================================== +void ReadInfoTexlights() +{ + int k; + int values; + int numtexlights = 0; + float r, g, b, i; + entity_t* mapent; + epair_t* ep; + texlight_t texlight; + + for (k = 0; k < g_numentities; k++) + { + mapent = &g_entities[k]; + + if (strcmp(ValueForKey(mapent, "classname"), "info_texlights")) + continue; + +#ifdef HLRAD_CUSTOMTEXLIGHT + Log("Reading texlights from info_texlights map entity\n"); +#else + Log("[Reading texlights from info_texlights map entity]\n"); +#endif + + for (ep = mapent->epairs; ep; ep = ep->next) + { + if ( !strcmp(ep->key, "classname") + || !strcmp(ep->key, "origin") + ) + continue; // we dont care about these keyvalues + + values = sscanf(ep->value, "%f %f %f %f", &r, &g, &b, &i); + + if (values == 1) + { + g = b = r; + } + else if (values == 4) // use brightness value. + { + r *= i / 255.0; + g *= i / 255.0; + b *= i / 255.0; + } + else if (values != 3) + { + Warning("ignoring bad texlight '%s' in info_texlights entity", ep->key); + continue; + } + + texlight.name = ep->key; + texlight.value[0] = r; + texlight.value[1] = g; + texlight.value[2] = b; + texlight.filename = "info_texlights"; + s_texlights.push_back(texlight); + numtexlights++; + } + +#ifndef HLRAD_CUSTOMTEXLIGHT + Log("[%i texlights parsed from info_texlights map entity]\n\n", numtexlights); +#endif + } +} +#endif + + +const char* lights_rad = "lights.rad"; +const char* ext_rad = ".rad"; +// ===================================================================================== +// LoadRadFiles +// ===================================================================================== +void LoadRadFiles(const char* const mapname, const char* const user_rad, const char* argv0) +{ + char global_lights[_MAX_PATH]; + char mapname_lights[_MAX_PATH]; + + char mapfile[_MAX_PATH]; + char mapdir[_MAX_PATH]; + char appdir[_MAX_PATH]; + + // Get application directory (only an approximation on posix systems) + // try looking in the directory we were run from + { + char tmp[_MAX_PATH]; + memset(tmp, 0, sizeof(tmp)); +#ifdef SYSTEM_WIN32 + GetModuleFileName(NULL, tmp, _MAX_PATH); +#else + safe_strncpy(tmp, argv0, _MAX_PATH); +#endif + ExtractFilePath(tmp, appdir); + } + + // Get map directory + ExtractFilePath(mapname, mapdir); +#ifdef ZHLT_DEFAULTEXTENSION_FIX + ExtractFile(mapname, mapfile); +#else + ExtractFileBase(mapname, mapfile); +#endif + + // Look for lights.rad in mapdir + safe_strncpy(global_lights, mapdir, _MAX_PATH); + safe_strncat(global_lights, lights_rad, _MAX_PATH); + if (q_exists(global_lights)) + { + ReadLightFile(global_lights); + } + else + { + // Look for lights.rad in appdir + safe_strncpy(global_lights, appdir, _MAX_PATH); + safe_strncat(global_lights, lights_rad, _MAX_PATH); + if (q_exists(global_lights)) + { + ReadLightFile(global_lights); + } + else + { + // Look for lights.rad in current working directory + safe_strncpy(global_lights, lights_rad, _MAX_PATH); + if (q_exists(global_lights)) + { + ReadLightFile(global_lights); + } + } + } + + // Look for mapname.rad in mapdir + safe_strncpy(mapname_lights, mapdir, _MAX_PATH); + safe_strncat(mapname_lights, mapfile, _MAX_PATH); +#ifdef ZHLT_DEFAULTEXTENSION_FIX + safe_strncat(mapname_lights, ext_rad, _MAX_PATH); +#else + DefaultExtension(mapname_lights, ext_rad); +#endif + if (q_exists(mapname_lights)) + { + ReadLightFile(mapname_lights); + } + + + if (user_rad) + { + char user_lights[_MAX_PATH]; + char userfile[_MAX_PATH]; + + ExtractFile(user_rad, userfile); + + // Look for user.rad from command line (raw) + safe_strncpy(user_lights, user_rad, _MAX_PATH); + if (q_exists(user_lights)) + { + ReadLightFile(user_lights); + } + else + { + // Try again with .rad enforced as extension + DefaultExtension(user_lights, ext_rad); + if (q_exists(user_lights)) + { + ReadLightFile(user_lights); + } + else + { + // Look for user.rad in mapdir + safe_strncpy(user_lights, mapdir, _MAX_PATH); + safe_strncat(user_lights, userfile, _MAX_PATH); + DefaultExtension(user_lights, ext_rad); + if (q_exists(user_lights)) + { + ReadLightFile(user_lights); + } + else + { + // Look for user.rad in appdir + safe_strncpy(user_lights, appdir, _MAX_PATH); + safe_strncat(user_lights, userfile, _MAX_PATH); + DefaultExtension(user_lights, ext_rad); + if (q_exists(user_lights)) + { + ReadLightFile(user_lights); + } + else + { + // Look for user.rad in current working directory + safe_strncpy(user_lights, userfile, _MAX_PATH); + DefaultExtension(user_lights, ext_rad); + if (q_exists(user_lights)) + { + ReadLightFile(user_lights); + } + } + } + } + } + } + +#ifdef HLRAD_INFO_TEXLIGHTS + ReadInfoTexlights(); // AJM +#endif +} +#ifdef ZHLT_XASH +void WriteDlitData (const char *filename) +{ + FILE *f; + if (g_drawdirection) + { + if (g_dlitdatasize != g_lightdatasize) + { + Error ("g_dlitdatasize != g_lightdatasize"); + } + memcpy (g_dlightdata, g_ddlitdata, g_lightdatasize); + } + f = SafeOpenWrite (filename); + fputc ('Q', f); + fputc ('L', f); + fputc ('I', f); + fputc ('T', f); + fputc (1, f); + fputc (0, f); + fputc (0, f); + fputc (0, f); + SafeWrite (f, g_ddlitdata, g_dlitdatasize); + fclose (f); +} +#endif + +// ===================================================================================== +// main +// ===================================================================================== +int main(const int argc, char** argv) +{ + int i; + double start, end; + const char* mapname_from_arg = NULL; + const char* user_lights = NULL; + + g_Program = "hlrad"; + +#ifdef ZHLT_PARAMFILE + int argcold = argc; + char ** argvold = argv; + { + int argc; + char ** argv; + ParseParamFile (argcold, argvold, argc, argv); + { +#endif +#ifdef ZHLT_CONSOLE + if (InitConsole (argc, argv) < 0) + Usage(); +#endif + if (argc == 1) + Usage(); + + for (i = 1; i < argc; i++) + { + if (!strcasecmp(argv[i], "-dump")) + { + g_dumppatches = true; + } +#ifdef ZHLT_CONSOLE + else if (!strcasecmp(argv[i], "-console")) + { +#ifndef SYSTEM_WIN32 + Warning("The option '-console #' is only valid for Windows."); +#endif + if (i + 1 < argc) + ++i; + else + Usage(); + } +#endif + else if (!strcasecmp(argv[i], "-bounce")) + { + if (i + 1 < argc) //added "1" .--vluzacn + { + g_numbounce = atoi(argv[++i]); + if (g_numbounce > 1000) + { + Log("Unexpectedly large value (>1000) for '-bounce'\n"); + Usage(); + } + } + else + { + Usage(); + } + } + else if (!strcasecmp(argv[i], "-dev")) + { + if (i + 1 < argc) //added "1" .--vluzacn + { + g_developer = (developer_level_t)atoi(argv[++i]); + } + else + { + Usage(); + } + } + else if (!strcasecmp(argv[i], "-verbose")) + { + g_verbose = true; + } + else if (!strcasecmp(argv[i], "-noinfo")) + { + g_info = false; + } + else if (!strcasecmp(argv[i], "-threads")) + { + if (i + 1 < argc) //added "1" .--vluzacn + { + g_numthreads = atoi(argv[++i]); + if (g_numthreads < 1) + { + Log("Expected value of at least 1 for '-threads'\n"); + Usage(); + } + } + else + { + Usage(); + } + } +#ifdef SYSTEM_WIN32 + else if (!strcasecmp(argv[i], "-estimate")) + { + g_estimate = true; + } +#endif +#ifdef SYSTEM_POSIX + else if (!strcasecmp(argv[i], "-noestimate")) + { + g_estimate = false; + } +#endif +#ifdef ZHLT_NETVIS + else if (!strcasecmp(argv[i], "-client")) + { + if (i + 1 < argc) //added "1" .--vluzacn + { + g_clientid = atoi(argv[++i]); + } + else + { + Usage(); + } + } +#endif +#ifdef HLRAD_FASTMODE + else if (!strcasecmp (argv[i], "-fast")) + { + g_fastmode = true; + } +#endif + else if (!strcasecmp(argv[i], "-nolerp")) + { + g_lerp_enabled = false; + } + else if (!strcasecmp(argv[i], "-chop")) + { + if (i + 1 < argc) //added "1" .--vluzacn + { + g_chop = atof(argv[++i]); + if (g_chop < 1) + { + Log("expected value greater than 1 for '-chop'\n"); + Usage(); + } + if (g_chop < 32) + { + Log("Warning: Chop values below 32 are not recommended."); + } + } + else + { + Usage(); + } + } + else if (!strcasecmp(argv[i], "-texchop")) + { + if (i + 1 < argc) //added "1" .--vluzacn + { + g_texchop = atof(argv[++i]); + if (g_texchop < 1) + { + Log("expected value greater than 1 for '-texchop'\n"); + Usage(); + } + if (g_texchop < 32) + { + Log("Warning: texchop values below 16 are not recommended."); + } + } + else + { + Usage(); + } + } + else if (!strcasecmp(argv[i], "-notexscale")) + { + g_texscale = false; + } + else if (!strcasecmp(argv[i], "-nosubdivide")) + { + if (i < argc) + { + g_subdivide = false; + } + else + { + Usage(); + } + } + else if (!strcasecmp(argv[i], "-scale")) + { + if (i + 1 < argc) //added "1" .--vluzacn + { + // ------------------------------------------------------------------------ + // Changes by Adam Foster - afoster@compsoc.man.ac.uk + // Munge monochrome lightscale into colour one +#ifdef HLRAD_WHOME + i++; + g_colour_lightscale[0] = (float)atof(argv[i]); + g_colour_lightscale[1] = (float)atof(argv[i]); + g_colour_lightscale[2] = (float)atof(argv[i]); +#else + g_lightscale = (float)atof(argv[++i]); +#endif + // ------------------------------------------------------------------------ + } + else + { + Usage(); + } + } +#ifndef HLRAD_ARG_MISC + else if (!strcasecmp(argv[i], "-falloff")) + { + if (i + 1 < argc) //added "1" .--vluzacn + { + g_falloff = (float)atoi(argv[++i]); + if ((g_falloff != 1) && (g_falloff != 2)) + { + Log("-falloff must be 1 or 2\n"); + Usage(); + } + } + else + { + Usage(); + } + } +#endif + else if (!strcasecmp(argv[i], "-fade")) + { + if (i + 1 < argc) //added "1" .--vluzacn + { + g_fade = (float)atof(argv[++i]); + if (g_fade < 0.0) + { + Log("-fade must be a positive number\n"); + Usage(); + } + } + else + { + Usage(); + } + } + else if (!strcasecmp(argv[i], "-ambient")) + { + if (i + 3 < argc) + { + g_ambient[0] = (float)atof(argv[++i]) * 128; + g_ambient[1] = (float)atof(argv[++i]) * 128; + g_ambient[2] = (float)atof(argv[++i]) * 128; + } + else + { + Error("expected three color values after '-ambient'\n"); + } + } +#ifndef HLRAD_FinalLightFace_VL + else if (!strcasecmp(argv[i], "-maxlight")) + { + if (i + 1 < argc) //added "1" .--vluzacn + { + g_maxlight = (float)atof(argv[++i]) * 128; + if (g_maxlight <= 0) + { + Log("expected positive value after '-maxlight'\n"); + Usage(); + } + } + else + { + Usage(); + } + } +#endif +#ifdef HLRAD_PRESERVELIGHTMAPCOLOR + else if (!strcasecmp(argv[i], "-limiter")) + { + if (i + 1 < argc) //added "1" .--vluzacn + { + g_limitthreshold = atof(argv[++i]); + } + else + { + Usage(); + } + } + else if (!strcasecmp(argv[i], "-drawoverload")) + { + g_drawoverload = true; + } +#endif + else if (!strcasecmp(argv[i], "-lights")) + { + if (i + 1 < argc) //added "1" .--vluzacn + { + user_lights = argv[++i]; + } + else + { + Usage(); + } + } + else if (!strcasecmp(argv[i], "-circus")) + { + g_circus = true; + } + else if (!strcasecmp(argv[i], "-noskyfix")) + { + g_sky_lighting_fix = false; + } + else if (!strcasecmp(argv[i], "-incremental")) + { + g_incremental = true; + } + else if (!strcasecmp(argv[i], "-chart")) + { + g_chart = true; + } + else if (!strcasecmp(argv[i], "-low")) + { + g_threadpriority = eThreadPriorityLow; + } + else if (!strcasecmp(argv[i], "-high")) + { + g_threadpriority = eThreadPriorityHigh; + } + else if (!strcasecmp(argv[i], "-nolog")) + { + g_log = false; + } + else if (!strcasecmp(argv[i], "-gamma")) + { + if (i + 1 < argc) //added "1" .--vluzacn + { + // ------------------------------------------------------------------------ + // Changes by Adam Foster - afoster@compsoc.man.ac.uk + // Munge values from original, monochrome gamma into colour gamma +#ifdef HLRAD_WHOME + i++; + g_colour_qgamma[0] = (float)atof(argv[i]); + g_colour_qgamma[1] = (float)atof(argv[i]); + g_colour_qgamma[2] = (float)atof(argv[i]); +#else + g_qgamma = (float)atof(argv[++i]); +#endif + // ------------------------------------------------------------------------ + } + else + { + Usage(); + } + } + else if (!strcasecmp(argv[i], "-dlight")) + { + if (i + 1 < argc) //added "1" .--vluzacn + { + g_dlight_threshold = (float)atof(argv[++i]); + } + else + { + Usage(); + } + } + else if (!strcasecmp(argv[i], "-extra")) + { + g_extra = true; + } + else if (!strcasecmp(argv[i], "-sky")) + { + if (i + 1 < argc) //added "1" .--vluzacn + { + g_indirect_sun = (float)atof(argv[++i]); + } + else + { + Usage(); + } + } + else if (!strcasecmp(argv[i], "-smooth")) + { + if (i + 1 < argc) //added "1" .--vluzacn + { + g_smoothing_value = atof(argv[++i]); + } + else + { + Usage(); + } + } +#ifdef HLRAD_CUSTOMSMOOTH + else if (!strcasecmp(argv[i], "-smooth2")) + { + if (i + 1 < argc) + { + g_smoothing_value_2 = atof(argv[++i]); + } + else + { + Usage(); + } + } +#endif + else if (!strcasecmp(argv[i], "-coring")) + { + if (i + 1 < argc) //added "1" .--vluzacn + { + g_coring = (float)atof(argv[++i]); + } + else + { + Usage(); + } + } + else if (!strcasecmp(argv[i], "-texdata")) + { + if (i + 1 < argc) //added "1" .--vluzacn + { + int x = atoi(argv[++i]) * 1024; + + //if (x > g_max_map_miptex) //--vluzacn + { + g_max_map_miptex = x; + } + } + else + { + Usage(); + } + } + else if (!strcasecmp(argv[i], "-lightdata")) //lightdata + { + if (i + 1 < argc) //--vluzacn + { + int x = atoi(argv[++i]) * 1024; + + //if (x > g_max_map_lightdata) //--vluzacn + { + g_max_map_lightdata = x; + } + } + else + { + Usage(); + } + } +#ifdef HLRAD_ARG_MISC + else if (!strcasecmp (argv[i], "-vismatrix")) + { + if (i + 1 < argc) + { + const char *value = argv[++i]; + if (!strcasecmp (value, "normal")) + { + g_method = eMethodVismatrix; + } + else if (!strcasecmp (value, "sparse")) + { + g_method = eMethodSparseVismatrix; + } + else if (!strcasecmp (value, "off")) + { + g_method = eMethodNoVismatrix; + } + else + { + Error ("Unknown vismatrix type: '%s'", value); + } + } + else + { + Usage (); + } + } +#else + else if (!strcasecmp(argv[i], "-sparse")) + { + g_method = eMethodSparseVismatrix; + } + else if (!strcasecmp(argv[i], "-nomatrix")) + { + g_method = eMethodNoVismatrix; + } +#endif +#ifdef HLRAD_SUNSPREAD + else if (!strcasecmp (argv[i], "-nospread")) + { + g_allow_spread = false; + } +#endif + else if (!strcasecmp(argv[i], "-nopaque") + || !strcasecmp(argv[i], "-noopaque")) //--vluzacn + { + g_allow_opaques = false; + } + else if (!strcasecmp(argv[i], "-dscale")) + { + if (i + 1 < argc) //added "1" .--vluzacn + { + g_direct_scale = (float)atof(argv[++i]); + } + else + { + Usage(); + } + } + + // ------------------------------------------------------------------------ + // Changes by Adam Foster - afoster@compsoc.man.ac.uk +#ifdef HLRAD_WHOME + else if (!strcasecmp(argv[i], "-colourgamma")) + { + if (i + 3 < argc) + { + g_colour_qgamma[0] = (float)atof(argv[++i]); + g_colour_qgamma[1] = (float)atof(argv[++i]); + g_colour_qgamma[2] = (float)atof(argv[++i]); + } + else + { + Error("expected three color values after '-colourgamma'\n"); + } + } + else if (!strcasecmp(argv[i], "-colourscale")) + { + if (i + 3 < argc) + { + g_colour_lightscale[0] = (float)atof(argv[++i]); + g_colour_lightscale[1] = (float)atof(argv[++i]); + g_colour_lightscale[2] = (float)atof(argv[++i]); + } + else + { + Error("expected three color values after '-colourscale'\n"); + } + } + + else if (!strcasecmp(argv[i], "-colourjitter")) + { + if (i + 3 < argc) + { + g_colour_jitter_hack[0] = (float)atof(argv[++i]); + g_colour_jitter_hack[1] = (float)atof(argv[++i]); + g_colour_jitter_hack[2] = (float)atof(argv[++i]); + } + else + { + Error("expected three color values after '-colourjitter'\n"); + } + } + else if (!strcasecmp(argv[i], "-jitter")) + { + if (i + 3 < argc) + { + g_jitter_hack[0] = (float)atof(argv[++i]); + g_jitter_hack[1] = (float)atof(argv[++i]); + g_jitter_hack[2] = (float)atof(argv[++i]); + } + else + { + Error("expected three color values after '-jitter'\n"); + } + } + +#ifndef HLRAD_ARG_MISC + else if (!strcasecmp(argv[i], "-nodiffuse")) + { + g_diffuse_hack = false; + } + else if (!strcasecmp(argv[i], "-nospotpoints")) + { + g_spotlight_hack = false; + } +#endif +#ifndef HLRAD_CUSTOMTEXLIGHT // no softlight hack + else if (!strcasecmp(argv[i], "-softlight")) + { + if (i + 4 < argc) + { + g_softlight_hack[0] = (float)atof(argv[++i]); + g_softlight_hack[1] = (float)atof(argv[++i]); + g_softlight_hack[2] = (float)atof(argv[++i]); + g_softlight_hack_distance = (float)atof(argv[++i]); + } + else + { + Error("expected three color scalers and a distance after '-softlight'\n"); + } + } +#endif +#endif + // ------------------------------------------------------------------------ + +#ifdef HLRAD_HULLU + else if (!strcasecmp(argv[i], "-customshadowwithbounce")) + { + g_customshadow_with_bouncelight = true; + } + else if (!strcasecmp(argv[i], "-rgbtransfers")) + { + g_rgb_transfers = true; + } +#endif + +#ifdef ZHLT_PROGRESSFILE // AJM + else if (!strcasecmp(argv[i], "-progressfile")) + { + if (i + 1 < argc) //added "1" .--vluzacn + { + g_progressfile = argv[++i]; + } + else + { + Log("Error: -progressfile: expected path to progress file following parameter\n"); + Usage(); + } + } +#endif + +#ifdef HLRAD_TRANSTOTAL_HACK + else if (!strcasecmp(argv[i], "-bscale")) + { +#ifdef HLRAD_REFLECTIVITY + Error ("'-bscale' is obsolete."); +#endif + if (i + 1 < argc) + { + g_transtotal_hack = (float)atof(argv[++i]); + } + else + { + Usage(); + } + } +#endif + +#ifdef HLRAD_MINLIGHT + else if (!strcasecmp(argv[i], "-minlight")) + { + if (i + 1 < argc) + { + int v = atoi(argv[++i]); + v = qmax (0, qmin (v, 255)); + g_minlight = (unsigned char)v; + } + else + { + Usage(); + } + } +#endif + +#ifdef HLRAD_SOFTSKY + else if (!strcasecmp(argv[i], "-softsky")) + { + if (i + 1 < argc) + { + g_softsky = (bool)atoi(argv[++i]); + } + else + { + Usage(); + } + } +#endif + +#ifdef HLRAD_DEBUG_DRAWPOINTS + else if (!strcasecmp(argv[i], "-drawpatch")) + { + g_drawpatch = true; + } + else if (!strcasecmp(argv[i], "-drawsample")) + { + g_drawsample = true; + if (i + 4 < argc) + { + g_drawsample_origin[0] = atof(argv[++i]); + g_drawsample_origin[1] = atof(argv[++i]); + g_drawsample_origin[2] = atof(argv[++i]); + g_drawsample_radius = atof(argv[++i]); + } + else + { + Usage(); + } + } + else if (!strcasecmp(argv[i], "-drawedge")) + { + g_drawedge = true; + } + else if (!strcasecmp(argv[i], "-drawlerp")) + { + g_drawlerp = true; + } +#endif +#ifdef HLRAD_AVOIDWALLBLEED + else if (!strcasecmp(argv[i], "-drawnudge")) + { + g_drawnudge = true; + } +#endif +#ifdef ZHLT_XASH + else if (!strcasecmp (argv[i], "-drawdirection")) + { + g_drawdirection = true; + } +#endif + +#ifdef HLRAD_TRANSFERDATA_COMPRESS + else if (!strcasecmp(argv[i], "-compress")) + { + if (i + 1 < argc) + { + g_transfer_compress_type = (float_type)atoi(argv[++i]); + if (g_transfer_compress_type < 0 || g_transfer_compress_type >= float_type_count) + Usage(); + } + else + { + Usage(); + } + } + else if (!strcasecmp(argv[i], "-rgbcompress")) + { + if (i + 1 < argc) + { + g_rgbtransfer_compress_type = (vector_type)atoi(argv[++i]); + if (g_rgbtransfer_compress_type < 0 || g_rgbtransfer_compress_type >= vector_type_count) + Usage(); + } + else + { + Usage(); + } + } +#endif +#ifdef HLRAD_TRANSLUCENT + else if (!strcasecmp (argv[i], "-depth")) + { + if (i + 1 < argc) + { + g_translucentdepth = atof(argv[++i]); + } + else + { + Usage (); + } + } +#endif +#ifdef HLRAD_OPAQUE_BLOCK + else if (!strcasecmp (argv[i], "-blockopaque")) + { + if (i + 1 < argc) + { + g_blockopaque = atoi(argv[++i]); + } + else + { + Usage (); + } + } +#endif +#ifdef HLRAD_TEXTURE + else if (!strcasecmp (argv[i], "-waddir")) + { + if (i + 1 < argc) + { + AddWadFolder (argv[++i]); + } + else + { + Usage (); + } + } + else if (!strcasecmp (argv[i], "-notextures")) + { + g_notextures = true; + } +#endif +#ifdef HLRAD_REFLECTIVITY + else if (!strcasecmp (argv[i], "-texreflectgamma")) + { + if (i + 1 < argc) + { + g_texreflectgamma = atof (argv[++i]); + } + else + { + Usage (); + } + } + else if (!strcasecmp (argv[i], "-texreflectscale")) + { + if (i + 1 < argc) + { + g_texreflectscale = atof (argv[++i]); + } + else + { + Usage (); + } + } +#endif +#ifdef HLRAD_BLUR + else if (!strcasecmp (argv[i], "-blur")) + { + if (i + 1 < argc) + { + g_blur = atof (argv[++i]); + } + else + { + Usage (); + } + } +#endif +#ifdef HLRAD_ACCURATEBOUNCE + else if (!strcasecmp (argv[i], "-noemitterrange")) + { + g_noemitterrange = true; + } +#endif +#ifdef HLRAD_AVOIDWALLBLEED + else if (!strcasecmp (argv[i], "-nobleedfix")) + { + g_bleedfix = false; + } +#endif +#ifdef HLRAD_TEXLIGHTGAP + else if (!strcasecmp (argv[i], "-texlightgap")) + { + if (i + 1 < argc) + { + g_texlightgap = atof (argv[++i]); + } + else + { + Usage (); + } + } +#endif +#ifdef ZHLT_LANGFILE + else if (!strcasecmp (argv[i], "-lang")) + { + if (i + 1 < argc) + { + char tmp[_MAX_PATH]; +#ifdef SYSTEM_WIN32 + GetModuleFileName (NULL, tmp, _MAX_PATH); +#else + safe_strncpy (tmp, argv[0], _MAX_PATH); +#endif + LoadLangFile (argv[++i], tmp); + } + else + { + Usage(); + } + } +#endif + + else if (argv[i][0] == '-') + { + Log("Unknown option \"%s\"\n", argv[i]); + Usage(); + } + else if (!mapname_from_arg) + { + mapname_from_arg = argv[i]; + } + else + { + Log("Unknown option \"%s\"\n", argv[i]); + Usage(); + } + } + + if (!mapname_from_arg) + { + Log("No mapname specified\n"); + Usage(); + } + + g_smoothing_threshold = (float)cos(g_smoothing_value * (Q_PI / 180.0)); + + safe_strncpy(g_Mapname, mapname_from_arg, _MAX_PATH); + FlipSlashes(g_Mapname); + StripExtension(g_Mapname); + OpenLog(g_clientid); + atexit(CloseLog); + ThreadSetDefault(); + ThreadSetPriority(g_threadpriority); +#ifdef ZHLT_PARAMFILE + LogStart(argcold, argvold); + { + int i; + Log("Arguments: "); + for (i = 1; i < argc; i++) + { + if (strchr(argv[i], ' ')) + { + Log("\"%s\" ", argv[i]); + } + else + { + Log("%s ", argv[i]); + } + } + Log("\n"); + } +#else + LogStart(argc, argv); +#endif + + CheckForErrorLog(); + +#ifdef HLRAD_TRANSFERDATA_COMPRESS + compress_compatability_test (); +#endif +#ifdef ZHLT_64BIT_FIX +#ifdef PLATFORM_CAN_CALC_EXTENT + hlassume (CalcFaceExtents_test (), assume_first); +#endif +#endif + dtexdata_init(); + atexit(dtexdata_free); +#ifdef ZHLT_XASH + g_max_map_dlitdata = g_max_map_lightdata; + g_ddlitdata = (byte *)malloc (g_max_map_dlitdata); + hlassume (g_ddlitdata != NULL, assume_NoMemory); + safe_snprintf (g_dlitfile, _MAX_PATH, "%s.dlit", g_Mapname); +#endif + // END INIT + + // BEGIN RAD + start = I_FloatTime(); + + // normalise maxlight +#ifndef HLRAD_FinalLightFace_VL + if (g_maxlight > 255) + g_maxlight = 255; +#endif + +#ifdef ZHLT_DEFAULTEXTENSION_FIX + safe_snprintf(g_source, _MAX_PATH, "%s.bsp", g_Mapname); +#else + strcpy(g_source, mapname_from_arg); + StripExtension(g_source); + DefaultExtension(g_source, ".bsp"); +#endif + LoadBSPFile(g_source); +#ifdef ZHLT_64BIT_FIX +#ifndef PLATFORM_CAN_CALC_EXTENT + char extentfilename[_MAX_PATH]; + safe_snprintf (extentfilename, _MAX_PATH, "%s.ext", g_Mapname); + Log ("Loading extent file '%s'\n", extentfilename); + if (!q_exists (extentfilename)) + { + hlassume (false, assume_NO_EXTENT_FILE); + } + LoadExtentFile (extentfilename); +#endif +#endif + ParseEntities(); +#ifdef HLRAD_FASTMODE + if (g_fastmode) + { + g_numbounce = 0; +#ifdef HLRAD_SOFTSKY + g_softsky = false; +#endif + } +#endif + Settings(); +#ifdef ZHLT_EMBEDLIGHTMAP + DeleteEmbeddedLightmaps (); +#endif +#ifdef HLRAD_TEXTURE + LoadTextures (); +#endif + LoadRadFiles(g_Mapname, user_lights, argv[0]); +#ifdef HLRAD_CUSTOMCHOP + ReadCustomChopValue (); +#endif +#ifdef HLRAD_CUSTOMSMOOTH + ReadCustomSmoothValue (); +#endif +#ifdef HLRAD_TRANSLUCENT + ReadTranslucentTextures (); +#endif +#ifdef HLRAD_DIVERSE_LIGHTING + ReadLightingCone (); +#endif +#ifdef HLRAD_CUSTOMSMOOTH + g_smoothing_threshold_2 = g_smoothing_value_2 < 0 ? g_smoothing_threshold : (float)cos(g_smoothing_value_2 * (Q_PI / 180.0)); +#endif +#ifdef HLRAD_STYLE_CORING + { + int style; + for (style = 0; style < ALLSTYLES; ++style) + { + g_corings[style] = style? g_coring: 0; + } + } +#endif +#ifdef HLRAD_ARG_MISC + if (g_direct_scale != 1.0) + { + Warning ("dscale value should be 1.0 for final compile.\nIf you need to adjust the bounced light, use the '-texreflectscale' and '-texreflectgamma' options instead."); + } +#ifdef HLRAD_WHOME + if (g_colour_lightscale[0] != 2.0 || g_colour_lightscale[1] != 2.0 || g_colour_lightscale[2] != 2.0) +#else + if (g_lightscale != 2.0) +#endif + { + Warning ("light scale value should be 2.0 for final compile.\nValues other than 2.0 will result in incorrect interpretation of light_environment's brightness when the engine loads the map."); + } +#endif +#ifdef HLRAD_DEBUG_DRAWPOINTS + if (g_drawlerp) + { + g_direct_scale = 0.0; + } +#endif + + if (!g_visdatasize) + { +#ifdef HLRAD_WITHOUTVIS + Warning("No vis information."); +#else + Warning("No vis information, direct lighting only."); + g_numbounce = 0; + g_ambient[0] = g_ambient[1] = g_ambient[2] = 0.1f; +#endif + } +#ifdef HLRAD_BLUR + if (g_blur < 1.0) + { + g_blur = 1.0; + } +#endif + + RadWorld(); + + FreeOpaqueFaceList(); + FreePatches(); +#ifdef HLRAD_OPAQUE_NODE + DeleteOpaqueNodes (); +#endif + +#ifdef ZHLT_EMBEDLIGHTMAP +#ifdef HLRAD_TEXTURE + EmbedLightmapInTextures (); +#endif +#endif + if (g_chart) + PrintBSPFileSizes(); + +#ifdef ZHLT_XASH + WriteDlitData (g_dlitfile); + free (g_ddlitdata); + g_ddlitdata = NULL; +#endif + WriteBSPFile(g_source); + + end = I_FloatTime(); + LogTimeElapsed(end - start); + // END RAD + +#ifdef ZHLT_PARAMFILE + } + } +#endif + return 0; +} diff --git a/src/zhlt-vluzacn/hlrad/qrad.h b/src/zhlt-vluzacn/hlrad/qrad.h new file mode 100644 index 0000000..0290dc6 --- /dev/null +++ b/src/zhlt-vluzacn/hlrad/qrad.h @@ -0,0 +1,1146 @@ +#ifndef HLRAD_H__ +#define HLRAD_H__ + +#if _MSC_VER >= 1000 +#pragma once +#endif + +#include "cmdlib.h" +#include "messages.h" +#include "win32fix.h" +#include "log.h" +#include "hlassert.h" +#include "mathlib.h" +#include "bspfile.h" +#include "winding.h" +#include "scriplib.h" +#include "threads.h" +#include "blockmem.h" +#include "filelib.h" +#include "winding.h" +#ifdef HLRAD_TRANSFERDATA_COMPRESS +#include "compress.h" +#endif +#ifdef ZHLT_PARAMFILE +#include "cmdlinecfg.h" +#endif + +#ifdef SYSTEM_WIN32 +#pragma warning(disable: 4142 4028) +#include +#pragma warning(default: 4142 4028) +#endif + +#ifdef HAVE_UNISTD_H +#include +#endif + +#ifdef HAVE_FCNTL_H +#include +#endif + +#ifdef STDC_HEADERS +#include +#endif + +#ifdef SYSTEM_WIN32 +#include +#endif + +#ifdef HLRAD_FASTMODE +#define DEFAULT_FASTMODE false +#endif +#ifdef HLRAD_ARG_MISC +#define DEFAULT_METHOD eMethodSparseVismatrix +#endif +#define DEFAULT_LERP_ENABLED true +#define DEFAULT_FADE 1.0 +#ifndef HLRAD_ARG_MISC +#define DEFAULT_FALLOFF 2 +#endif +#ifdef HLRAD_REFLECTIVITY +#define DEFAULT_BOUNCE 8 +#else +#define DEFAULT_BOUNCE 1 +#endif +#define DEFAULT_DUMPPATCHES false +#define DEFAULT_AMBIENT_RED 0.0 +#define DEFAULT_AMBIENT_GREEN 0.0 +#define DEFAULT_AMBIENT_BLUE 0.0 +#ifndef HLRAD_FinalLightFace_VL +#define DEFAULT_MAXLIGHT 256.0 +#endif +#ifdef HLRAD_PRESERVELIGHTMAPCOLOR +// 188 is the fullbright threshold for Goldsrc, regardless of the brightness and gamma settings in the graphic options. +// However, hlrad can only control the light values of each single light style. So the final in-game brightness may exceed 188 if you have set a high value in the "custom appearance" of the light, or if the face receives light from different styles. +#define DEFAULT_LIMITTHRESHOLD 188.0 +#endif +#define DEFAULT_TEXSCALE true +#define DEFAULT_CHOP 64.0 +#define DEFAULT_TEXCHOP 32.0 +#define DEFAULT_LIGHTSCALE 2.0 //1.0 //vluzacn +#ifdef HLRAD_REFLECTIVITY +#define DEFAULT_DLIGHT_THRESHOLD 10.0 +#else +#define DEFAULT_DLIGHT_THRESHOLD 25.0 +#endif +#define DEFAULT_DLIGHT_SCALE 1.0 //2.0 //vluzacn +#define DEFAULT_SMOOTHING_VALUE 50.0 +#ifdef HLRAD_CUSTOMSMOOTH +#define DEFAULT_SMOOTHING2_VALUE -1.0 +#endif +#define DEFAULT_INCREMENTAL false + +#ifdef ZHLT_PROGRESSFILE // AJM +#define DEFAULT_PROGRESSFILE NULL // progress file is only used if g_progressfile is non-null +#endif + +// ------------------------------------------------------------------------ +// Changes by Adam Foster - afoster@compsoc.man.ac.uk + +// superseded by DEFAULT_COLOUR_LIGHTSCALE_* +#ifndef HLRAD_WHOME + #define DEFAULT_LIGHTSCALE 2.0 //1.0 //vluzacn +#endif + +// superseded by DEFAULT_COLOUR_GAMMA_* +#ifndef HLRAD_WHOME +#ifdef HLRAD_REFLECTIVITY + #define DEFAULT_GAMMA 0.55 +#else + #define DEFAULT_GAMMA 0.5 +#endif +#endif +// ------------------------------------------------------------------------ + +#define DEFAULT_INDIRECT_SUN 1.0 +#define DEFAULT_EXTRA false +#define DEFAULT_SKY_LIGHTING_FIX true +#define DEFAULT_CIRCUS false +#ifdef HLRAD_AUTOCORING +#define DEFAULT_CORING 0.01 +#else +#define DEFAULT_CORING 0.1 //1.0 //vluzacn +#endif +#define DEFAULT_SUBDIVIDE true +#define DEFAULT_CHART false +#define DEFAULT_INFO true +#define DEFAULT_ALLOW_OPAQUES true +#ifdef HLRAD_SUNSPREAD +#define DEFAULT_ALLOW_SPREAD true +#endif + +// ------------------------------------------------------------------------ +// Changes by Adam Foster - afoster@compsoc.man.ac.uk +#ifdef HLRAD_WHOME + +#ifdef HLRAD_REFLECTIVITY +#define DEFAULT_COLOUR_GAMMA_RED 0.55 +#define DEFAULT_COLOUR_GAMMA_GREEN 0.55 +#define DEFAULT_COLOUR_GAMMA_BLUE 0.55 +#else +#define DEFAULT_COLOUR_GAMMA_RED 0.5 +#define DEFAULT_COLOUR_GAMMA_GREEN 0.5 +#define DEFAULT_COLOUR_GAMMA_BLUE 0.5 +#endif + +#define DEFAULT_COLOUR_LIGHTSCALE_RED 2.0 //1.0 //vluzacn +#define DEFAULT_COLOUR_LIGHTSCALE_GREEN 2.0 //1.0 //vluzacn +#define DEFAULT_COLOUR_LIGHTSCALE_BLUE 2.0 //1.0 //vluzacn + +#define DEFAULT_COLOUR_JITTER_HACK_RED 0.0 +#define DEFAULT_COLOUR_JITTER_HACK_GREEN 0.0 +#define DEFAULT_COLOUR_JITTER_HACK_BLUE 0.0 + +#define DEFAULT_JITTER_HACK_RED 0.0 +#define DEFAULT_JITTER_HACK_GREEN 0.0 +#define DEFAULT_JITTER_HACK_BLUE 0.0 + +#ifndef HLRAD_ARG_MISC +#define DEFAULT_DIFFUSE_HACK true +#define DEFAULT_SPOTLIGHT_HACK true +#endif + +#ifndef HLRAD_CUSTOMTEXLIGHT // no softlight hack +#define DEFAULT_SOFTLIGHT_HACK_RED 0.0 +#define DEFAULT_SOFTLIGHT_HACK_GREEN 0.0 +#define DEFAULT_SOFTLIGHT_HACK_BLUE 0.0 +#define DEFAULT_SOFTLIGHT_HACK_DISTANCE 0.0 +#endif + +#endif +// ------------------------------------------------------------------------ + +// O_o ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +// Changes by Jussi Kivilinna [http://hullu.xtragaming.com/] +#ifdef HLRAD_HULLU + // Transparency light support for bounced light(transfers) is extreamly slow + // for 'vismatrix' and 'sparse' atm. + // Only recommended to be used with 'nomatrix' mode + #define DEFAULT_CUSTOMSHADOW_WITH_BOUNCELIGHT false + + // RGB Transfers support for HLRAD .. to be used with -customshadowwithbounce + #define DEFAULT_RGB_TRANSFERS false +#endif +// o_O ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +#ifdef HLRAD_TRANSTOTAL_HACK + #define DEFAULT_TRANSTOTAL_HACK 0.2 //0.5 //vluzacn +#endif +#ifdef HLRAD_MINLIGHT + #define DEFAULT_MINLIGHT 0 +#endif +#ifdef HLRAD_TRANSFERDATA_COMPRESS + #define DEFAULT_TRANSFER_COMPRESS_TYPE FLOAT16 + #define DEFAULT_RGBTRANSFER_COMPRESS_TYPE VECTOR32 +#endif +#ifdef HLRAD_SOFTSKY + #define DEFAULT_SOFTSKY true +#endif +#ifdef HLRAD_OPAQUE_BLOCK + #define DEFAULT_BLOCKOPAQUE 1 +#endif +#ifdef HLRAD_TRANSLUCENT + #define DEFAULT_TRANSLUCENTDEPTH 2.0f +#endif +#ifdef HLRAD_TEXTURE + #define DEFAULT_NOTEXTURES false +#endif +#ifdef HLRAD_REFLECTIVITY + #define DEFAULT_TEXREFLECTGAMMA 1.76f // 2.0(texgamma cvar) / 2.5 (gamma cvar) * 2.2 (screen gamma) = 1.76 + #define DEFAULT_TEXREFLECTSCALE 0.7f // arbitrary (This is lower than 1.0, because textures are usually brightened in order to look better in Goldsrc. Textures are made brightened because Goldsrc is only able to darken the texture when combining the texture with the lightmap.) +#endif +#ifdef HLRAD_BLUR + #define DEFAULT_BLUR 1.5 // classic lighting is equivalent to "-blur 1.0" +#endif +#ifdef HLRAD_ACCURATEBOUNCE + #define DEFAULT_NOEMITTERRANGE false +#endif +#ifdef HLRAD_AVOIDWALLBLEED + #define DEFAULT_BLEEDFIX true +#endif +#ifdef ZHLT_EMBEDLIGHTMAP + #define DEFAULT_EMBEDLIGHTMAP_POWEROFTWO true + #define DEFAULT_EMBEDLIGHTMAP_DENOMINATOR 188.0 + #define DEFAULT_EMBEDLIGHTMAP_GAMMA 1.05 + #define DEFAULT_EMBEDLIGHTMAP_RESOLUTION 1 +#endif +#ifdef HLRAD_TEXLIGHTGAP + #define DEFAULT_TEXLIGHTGAP 0.0 +#endif + + +#ifdef SYSTEM_WIN32 +#define DEFAULT_ESTIMATE false +#endif +#ifdef SYSTEM_POSIX +#define DEFAULT_ESTIMATE true +#endif + +// Ideally matches what is in the FGD :) +#define SPAWNFLAG_NOBLEEDADJUST (1 << 0) + +// DEFAULT_HUNT_OFFSET is how many units in front of the plane to place the samples +// Unit of '1' causes the 1 unit crate trick to cause extra shadows +#define DEFAULT_HUNT_OFFSET 0.5 +// DEFAULT_HUNT_SIZE number of iterations (one based) of radial search in HuntForWorld +#define DEFAULT_HUNT_SIZE 11 +// DEFAULT_HUNT_SCALE amount to grow from origin point per iteration of DEFAULT_HUNT_SIZE in HuntForWorld +#define DEFAULT_HUNT_SCALE 0.1 +#ifdef HLRAD_SNAPTOWINDING +#define DEFAULT_EDGE_WIDTH 0.8 +#endif + +#define PATCH_HUNT_OFFSET 0.5 //--vluzacn +#define HUNT_WALL_EPSILON (3 * ON_EPSILON) // place sample at least this distance away from any wall //--vluzacn + +#ifdef HLRAD_ACCURATEBOUNCE +#define MINIMUM_PATCH_DISTANCE ON_EPSILON +#define ACCURATEBOUNCE_THRESHOLD 4.0 // If the receiver patch is closer to emitter patch than EXACTBOUNCE_THRESHOLD * emitter_patch->radius, calculate the exact visibility amount. +#define ACCURATEBOUNCE_DEFAULT_SKYLEVEL 5 // sample 1026 normals +#else +// If patches are allowed to be closer, the light gets amplified (which looks really damn weird) +#define MINIMUM_PATCH_DISTANCE 1.01 +#endif + +#ifndef HLRAD_AUTOCORING +#ifdef HLRAD_STYLE_CORING + #define BOUNCE_CORING_SCALE 0.5f +#endif +#endif + +#define ALLSTYLES 64 // HL limit. //--vluzacn + +#ifdef ZHLT_LARGERANGE +#define BOGUS_RANGE 131072 +#else +#define BOGUS_RANGE 16384 //--vluzacn +#endif + +#ifdef HLRAD_GROWSAMPLE +typedef struct +{ + vec_t v[4][3]; +} +matrix_t; + +// a 4x4 matrix that represents the following transformation (see the ApplyMatrix function) +// +// / X \ / v[0][0] v[1][0] v[2][0] v[3][0] \ / X \. +// | Y | -> | v[0][1] v[1][1] v[2][1] v[3][1] | | Y | +// | Z | | v[0][2] v[1][2] v[2][2] v[3][2] | | Z | +// \ 1 / \ 0 0 0 1 / \ 1 / + +#endif +// +// LIGHTMAP.C STUFF +// + +typedef enum +{ + emit_surface, + emit_point, + emit_spotlight, + emit_skylight +} +emittype_t; + +typedef struct directlight_s +{ + struct directlight_s* next; + emittype_t type; + int style; + vec3_t origin; + vec3_t intensity; + vec3_t normal; // for surfaces and spotlights + float stopdot; // for spotlights + float stopdot2; // for spotlights + + // 'Arghrad'-like features + vec_t fade; // falloff scaling for linear and inverse square falloff 1.0 = normal, 0.5 = farther, 2.0 = shorter etc +#ifndef HLRAD_ARG_MISC + unsigned char falloff; // falloff style 0 = default (inverse square), 1 = inverse falloff, 2 = inverse square (arghrad compat) +#endif + + // ----------------------------------------------------------------------------------- + // Changes by Adam Foster - afoster@compsoc.man.ac.uk + // Diffuse light_environment light colour + // Really horrible hack which probably won't work! +#ifdef HLRAD_WHOME + vec3_t diffuse_intensity; +#endif + // ----------------------------------------------------------------------------------- +#ifdef HLRAD_SUNDIFFUSE + vec3_t diffuse_intensity2; +#endif +#ifdef HLRAD_SUNSPREAD + vec_t sunspreadangle; + int numsunnormals; + vec3_t* sunnormals; + vec_t* sunnormalweights; +#endif + +#ifdef HLRAD_TEXLIGHT_SPOTS_FIX + vec_t patch_area; +#ifdef HLRAD_ACCURATEBOUNCE_TEXLIGHT + vec_t patch_emitter_range; + struct patch_s *patch; +#endif +#endif +#ifdef HLRAD_TEXLIGHTGAP + vec_t texlightgap; +#endif +#ifdef HLRAD_GatherPatchLight + bool topatch; +#endif +} directlight_t; + +#ifndef HLRAD_TRANSFERDATA_COMPRESS +#define TRANSFER_SCALE_VAL (USHRT_MAX/4) + +#define TRANSFER_SCALE (1.0 / TRANSFER_SCALE_VAL) +#define INVERSE_TRANSFER_SCALE (TRANSFER_SCALE_VAL) +#define TRANSFER_SCALE_MAX (TRANSFER_SCALE_VAL * 4) +#endif + +typedef struct +{ + unsigned size : 12; + unsigned index : 20; +} transfer_index_t; + +typedef unsigned transfer_raw_index_t; +#ifdef HLRAD_TRANSFERDATA_COMPRESS +typedef unsigned char transfer_data_t; +#else +typedef float transfer_data_t; +#endif + +#ifdef HLRAD_TRANSFERDATA_COMPRESS +typedef unsigned char rgb_transfer_data_t; +#else +//Special RGB mode for transfers +#ifdef HLRAD_HULLU + #if defined(HLRAD_HULLU_48BIT_RGB_TRANSFERS) && defined(HLRAD_HULLU_96BIT_RGB_TRANSFERS) + #error Conflict: Both HLRAD_HULLU_48BIT_RGB_TRANSFERS and HLRAD_HULLU_96BIT_RGB_TRANSFERS defined! + #elif defined(HLRAD_HULLU_96BIT_RGB_TRANSFERS) + //96bit (no noticeable difference to 48bit) + typedef float rgb_transfer_t[3]; + #else + //default.. 48bit + typedef unsigned short rgb_transfer_t[3]; + #endif + + typedef rgb_transfer_t rgb_transfer_data_t; +#endif +#endif + +#define MAX_COMPRESSED_TRANSFER_INDEX_SIZE ((1 << 12) - 1) + +#ifdef HLRAD_MORE_PATCHES +#define MAX_PATCHES (65535*16) // limited by transfer_index_t +#else +#define MAX_PATCHES (65535*4) +#endif +#define MAX_VISMATRIX_PATCHES 65535 +#define MAX_SPARSE_VISMATRIX_PATCHES MAX_PATCHES + +typedef enum +{ + ePatchFlagNull = 0, + ePatchFlagOutside = 1 +} ePatchFlags; + +#ifdef ZHLT_XASH +#define DIFFUSE_DIRECTION_SCALE (2.0/3.0) //Integrate[2 \[Pi] Sin[\[Theta]] Cos[\[Theta]], {\[Theta], 0, \[Pi]/2}] / Integrate[2 \[Pi] Sin[\[Theta]] Cos[\[Theta]] Cos[\[Theta]], {\[Theta], 0, \[Pi]/2}] +#endif +typedef struct patch_s +{ + struct patch_s* next; // next in face + vec3_t origin; // Center centroid of winding (cached info calculated from winding) + vec_t area; // Surface area of this patch (cached info calculated from winding) +#ifdef HLRAD_ACCURATEBOUNCE_REDUCEAREA + vec_t exposure; +#endif +#ifdef HLRAD_ACCURATEBOUNCE + vec_t emitter_range; // Range from patch origin (cached info calculated from winding) + int emitter_skylevel; // The "skylevel" used for sampling of normals, when the receiver patch is within the range of ACCURATEBOUNCE_THRESHOLD * this->radius. (cached info calculated from winding) +#endif + Winding* winding; // Winding (patches are triangles, so its easy) + vec_t scale; // Texture scale for this face (blend of S and T scale) + vec_t chop; // Texture chop for this face factoring in S and T scale + + unsigned iIndex; + unsigned iData; + + transfer_index_t* tIndex; + transfer_data_t* tData; +#ifdef HLRAD_HULLU + rgb_transfer_data_t* tRGBData; +#endif + + int faceNumber; + ePatchFlags flags; +#ifdef HLRAD_TRANSLUCENT + bool translucent_b; // gather light from behind + vec3_t translucent_v; +#endif +#ifdef HLRAD_REFLECTIVITY + vec3_t texturereflectivity; + vec3_t bouncereflectivity; +#endif + +#ifdef ZHLT_TEXLIGHT +#ifdef HLRAD_GatherPatchLight + unsigned char totalstyle[MAXLIGHTMAPS]; +#else + int totalstyle[MAXLIGHTMAPS]; //LRC - gives the styles for use by the new switchable totallight values +#endif +#ifdef HLRAD_AUTOCORING + unsigned char directstyle[MAXLIGHTMAPS]; +#endif + // HLRAD_AUTOCORING: totallight: all light gathered by patch + vec3_t totallight[MAXLIGHTMAPS]; // accumulated by radiosity does NOT include light accounted for by direct lighting +#ifdef ZHLT_XASH + vec3_t totallight_direction[MAXLIGHTMAPS]; +#endif + // HLRAD_AUTOCORING: directlight: emissive light gathered by sample + vec3_t directlight[MAXLIGHTMAPS]; // direct light only +#ifdef ZHLT_XASH + vec3_t directlight_direction[MAXLIGHTMAPS]; +#endif +#ifdef HLRAD_BOUNCE_STYLE + int bouncestyle; // light reflected from this patch must convert to this style. -1 = normal (don't convert) +#endif +#ifdef HLRAD_STYLE_CORING + unsigned char emitstyle; +#else + int emitstyle; //LRC - for switchable texlights +#endif + vec3_t baselight; // emissivity only, uses emitstyle +#ifdef HLRAD_TEXLIGHTTHRESHOLD_FIX + bool emitmode; // texlight emit mode. 1 for normal, 0 for fast. +#endif +#ifdef HLRAD_AUTOCORING +#ifdef HLRAD_ACCURATEBOUNCE_SAMPLELIGHT + vec_t samples; +#else + int samples; +#endif + vec3_t* samplelight_all; // NULL, or [ALLSTYLES] during BuildFacelights +#ifdef ZHLT_XASH + vec3_t* samplelight_all_direction; +#endif +#else + vec3_t samplelight[MAXLIGHTMAPS]; + int samples[MAXLIGHTMAPS]; // for averaging direct light +#endif +#ifdef HLRAD_AUTOCORING + unsigned char* totalstyle_all; // NULL, or [ALLSTYLES] during BuildFacelights + vec3_t* totallight_all; // NULL, or [ALLSTYLES] during BuildFacelights +#ifdef ZHLT_XASH + vec3_t* totallight_all_direction; +#endif + vec3_t* directlight_all; // NULL, or [ALLSTYLES] during BuildFacelights +#ifdef ZHLT_XASH + vec3_t* directlight_all_direction; +#endif +#endif +#else + vec3_t totallight; // accumulated by radiosity does NOT include light accounted for by direct lighting + vec3_t baselight; // emissivity only + vec3_t directlight; // direct light value + + vec3_t samplelight; + int samples; // for averaging direct light +#endif +#ifdef HLRAD_ENTITYBOUNCE_FIX + int leafnum; +#endif +} patch_t; + +#ifdef ZHLT_TEXLIGHT +//LRC +vec3_t* GetTotalLight(patch_t* patch, int style +#ifdef ZHLT_XASH + , const vec3_t *&direction_out +#endif + ); +#endif + +#ifdef HLRAD_SMOOTH_FACELIST +typedef struct facelist_s +{ + dface_t* face; + facelist_s* next; +} facelist_t; +#endif +typedef struct +{ + dface_t* faces[2]; + vec3_t interface_normal; // HLRAD_GetPhongNormal_VL: this field must be set when smooth==true +#ifdef HLRAD_GetPhongNormal_VL + vec3_t vertex_normal[2]; +#endif + vec_t cos_normals_angle; // HLRAD_GetPhongNormal_VL: this field must be set when smooth==true + bool coplanar; +#ifdef HLRAD_GetPhongNormal_VL + bool smooth; +#endif +#ifdef HLRAD_SMOOTH_FACELIST + facelist_t* vertex_facelist[2]; //possible smooth faces, not include faces[0] and faces[1] +#endif +#ifdef HLRAD_GROWSAMPLE + matrix_t textotex[2]; // how we translate texture coordinates from one face to the other face +#endif +} edgeshare_t; + +extern edgeshare_t g_edgeshare[MAX_MAP_EDGES]; + +// +// lerp.c stuff +// + +#ifndef HLRAD_LOCALTRIANGULATION +typedef struct lerprect_s +{ + dplane_t plane; // all walls will be perpindicular to face normal in some direction +#ifdef HLRAD_LERP_VL + vec3_t increment; + vec3_t vertex0, vertex1; +#else + vec3_t vertex[4]; +#endif +} +lerpWall_t; + +typedef struct lerpdist_s +{ + vec_t dist; + unsigned patch; +#ifdef HLRAD_LERP_VL + vec3_t pos; + int invalid; +#endif +} lerpDist_t; + +// Valve's default was 2048 originally. +// MAX_LERP_POINTS causes lerpTriangulation_t to consume : +// 2048 : roughly 17.5Mb +// 3072 : roughly 35Mb +// 4096 : roughly 70Mb +#define DEFAULT_MAX_LERP_POINTS 512 +#define DEFAULT_MAX_LERP_WALLS 128 + +typedef struct +{ + unsigned maxpoints; + unsigned numpoints; + + unsigned maxwalls; + unsigned numwalls; + patch_t** points; // maxpoints +#ifdef HLRAD_LERP_TEXNORMAL + vec3_t* points_pos; +#endif + lerpDist_t* dists; // numpoints after points is populated + lerpWall_t* walls; // maxwalls + + unsigned facenum; + const dface_t* face; + const dplane_t* plane; +#ifdef HLRAD_LERP_FACELIST + facelist_t* allfaces; +#endif +} +lerpTriangulation_t; + +#endif +// These are bitflags for lighting adjustments for special cases +typedef enum +{ + eModelLightmodeNull = 0, +#ifndef HLRAD_CalcPoints_NEW + eModelLightmodeEmbedded = 0x01, +#endif + eModelLightmodeOpaque = 0x02, +#ifndef HLRAD_CalcPoints_NEW + eModelLightmodeConcave = 0x04, +#endif +#ifdef HLRAD_OPAQUE_BLOCK + eModelLightmodeNonsolid = 0x08, // for opaque entities with {texture +#endif +} +eModelLightmodes; + +#ifndef HLRAD_OPAQUE_NODE +#ifdef HLRAD_OPAQUE_GROUP +#define MAX_OPAQUE_GROUP_COUNT 2048 +typedef struct +{ + const dmodel_t* mod; +#ifdef HLRAD_OPAQUE_RANGE + float mins[3]; + float maxs[3]; +#endif +} opaqueGroup_t; +#endif +#endif + +typedef struct +{ +#ifdef HLRAD_OPAQUE_NODE + int entitynum; + int modelnum; + vec3_t origin; +#else + Winding* winding; + dplane_t plane; + unsigned facenum; +#endif + +#ifdef HLRAD_HULLU + vec3_t transparency_scale; + bool transparency; +#endif +#ifndef HLRAD_OPAQUE_NODE +#ifdef HLRAD_OPAQUE_GROUP + unsigned groupnum; +#endif +#endif +#ifdef HLRAD_OPAQUE_STYLE + int style; // -1 = no style; transparency must be false if style >= 0 + // style0 and same style will change to this style, other styles will be blocked. +#endif +#ifdef HLRAD_OPAQUE_BLOCK + bool block; // this entity can't be seen inside, so all lightmap sample should move outside. +#endif + +} opaqueList_t; + +#define OPAQUE_ARRAY_GROWTH_SIZE 1024 + +#ifdef HLRAD_TEXTURE +typedef struct +{ + char name[16]; // not always same with the name in texdata + int width, height; + byte *canvas; //[height][width] + byte palette[256][3]; +#ifdef HLRAD_REFLECTIVITY + vec3_t reflectivity; +#endif +} radtexture_t; +extern int g_numtextures; +extern radtexture_t *g_textures; +extern void AddWadFolder (const char *path); +extern void LoadTextures (); +#ifdef ZHLT_EMBEDLIGHTMAP +#ifdef HLRAD_TEXTURE +extern void EmbedLightmapInTextures (); +#endif +#endif +#endif + +// +// qrad globals +// + +#ifdef ZHLT_XASH +extern int g_max_map_dlitdata; +extern int g_dlitdatasize; +extern byte *g_ddlitdata; +extern char g_dlitfile[_MAX_PATH]; +extern vec_t g_directionscale; +#endif +extern patch_t* g_face_patches[MAX_MAP_FACES]; +extern entity_t* g_face_entity[MAX_MAP_FACES]; +extern vec3_t g_face_offset[MAX_MAP_FACES]; // for models with origins +extern eModelLightmodes g_face_lightmode[MAX_MAP_FACES]; +extern vec3_t g_face_centroids[MAX_MAP_EDGES]; +#ifndef HLRAD_GROWSAMPLE +#ifdef HLRAD_SMOOTH_TEXNORMAL +extern vec3_t g_face_texnormals[MAX_MAP_FACES]; +extern bool GetIntertexnormal (int facenum1, int facenum2, vec_t *out = NULL); +#endif +#endif +#ifdef HLRAD_CUSTOMTEXLIGHT +extern entity_t* g_face_texlights[MAX_MAP_FACES]; +#endif +#ifdef HLRAD_MORE_PATCHES +extern patch_t* g_patches; // shrinked to its real size, because 1048576 patches * 256 bytes = 256MB will be too big +#else +extern patch_t g_patches[MAX_PATCHES]; +#endif +extern unsigned g_num_patches; + +extern float g_lightscale; +extern float g_dlight_threshold; +extern float g_coring; +extern int g_lerp_enabled; + +extern void MakeShadowSplits(); + +//============================================== + +#ifdef HLRAD_FASTMODE +extern bool g_fastmode; +#endif +extern bool g_extra; +extern vec3_t g_ambient; +extern vec_t g_direct_scale; +#ifndef HLRAD_FinalLightFace_VL +extern float g_maxlight; +#endif +#ifdef HLRAD_PRESERVELIGHTMAPCOLOR +extern vec_t g_limitthreshold; +extern bool g_drawoverload; +#endif +extern unsigned g_numbounce; +extern float g_qgamma; +extern float g_indirect_sun; +extern float g_smoothing_threshold; +extern float g_smoothing_value; +#ifdef HLRAD_CUSTOMSMOOTH +extern float g_smoothing_threshold_2; +extern float g_smoothing_value_2; +extern vec_t *g_smoothvalues; //[nummiptex] +#endif +extern bool g_estimate; +extern char g_source[_MAX_PATH]; +extern vec_t g_fade; +#ifndef HLRAD_ARG_MISC +extern int g_falloff; +#endif +extern bool g_incremental; +extern bool g_circus; +#ifdef HLRAD_SUNSPREAD +extern bool g_allow_spread; +#endif +extern bool g_sky_lighting_fix; +extern vec_t g_chop; // Chop value for normal textures +extern vec_t g_texchop; // Chop value for texture lights +extern opaqueList_t* g_opaque_face_list; +extern unsigned g_opaque_face_count; // opaque entity count //HLRAD_OPAQUE_NODE +extern unsigned g_max_opaque_face_count; // Current array maximum (used for reallocs) +#ifndef HLRAD_OPAQUE_NODE +#ifdef HLRAD_OPAQUE_GROUP +extern opaqueGroup_t g_opaque_group_list[MAX_OPAQUE_GROUP_COUNT]; +extern unsigned g_opaque_group_count; +#endif +#endif + +#ifdef ZHLT_PROGRESSFILE // AJM +extern char* g_progressfile ; +#endif + +// ------------------------------------------------------------------------ +// Changes by Adam Foster - afoster@compsoc.man.ac.uk +#ifdef HLRAD_WHOME + +extern vec3_t g_colour_qgamma; +extern vec3_t g_colour_lightscale; + +extern vec3_t g_colour_jitter_hack; +extern vec3_t g_jitter_hack; +#ifndef HLRAD_ARG_MISC +extern bool g_diffuse_hack; +extern bool g_spotlight_hack; +#endif +#ifndef HLRAD_CUSTOMTEXLIGHT // no softlight hack +extern vec3_t g_softlight_hack; +extern float g_softlight_hack_distance; +#endif + +#endif +// ------------------------------------------------------------------------ + + +#ifdef HLRAD_HULLU + extern bool g_customshadow_with_bouncelight; + extern bool g_rgb_transfers; +#ifdef HLRAD_TRANSPARENCY_CPP + extern const vec3_t vec3_one; +#endif +#endif + +#ifdef HLRAD_TRANSTOTAL_HACK + extern float g_transtotal_hack; +#endif +#ifdef HLRAD_MINLIGHT + extern unsigned char g_minlight; +#endif +#ifdef HLRAD_TRANSFERDATA_COMPRESS + extern float_type g_transfer_compress_type; + extern vector_type g_rgbtransfer_compress_type; +#endif +#ifdef HLRAD_SOFTSKY + extern bool g_softsky; +#endif +#ifdef HLRAD_OPAQUE_BLOCK + extern int g_blockopaque; +#endif +#ifdef HLRAD_DEBUG_DRAWPOINTS + extern bool g_drawpatch; + extern bool g_drawsample; + extern vec3_t g_drawsample_origin; + extern vec_t g_drawsample_radius; + extern bool g_drawedge; + extern bool g_drawlerp; +#endif +#ifdef HLRAD_AVOIDWALLBLEED + extern bool g_drawnudge; +#endif +#ifdef HLRAD_STYLE_CORING + extern float g_corings[ALLSTYLES]; +#endif +#ifdef HLRAD_READABLE_EXCEEDSTYLEWARNING + extern int stylewarningcount; // not thread safe + extern int stylewarningnext; // not thread safe +#endif +#ifdef HLRAD_TRANSLUCENT + extern vec3_t *g_translucenttextures; + extern vec_t g_translucentdepth; +#endif +#ifdef HLRAD_DIVERSE_LIGHTING + extern vec3_t *g_lightingconeinfo; //[nummiptex]; X component = power, Y component = scale, Z component = nothing +#endif +#ifdef HLRAD_TEXTURE + extern bool g_notextures; +#endif +#ifdef HLRAD_REFLECTIVITY + extern vec_t g_texreflectgamma; + extern vec_t g_texreflectscale; +#endif +#ifdef HLRAD_BLUR + extern vec_t g_blur; +#endif +#ifdef HLRAD_ACCURATEBOUNCE + extern bool g_noemitterrange; +#endif +#ifdef HLRAD_AVOIDWALLBLEED + extern bool g_bleedfix; +#endif +#ifdef HLRAD_AUTOCORING + extern vec_t g_maxdiscardedlight; + extern vec3_t g_maxdiscardedpos; +#endif +#ifdef HLRAD_TEXLIGHTGAP + extern vec_t g_texlightgap; +#endif + +extern void MakeTnodes(dmodel_t* bm); +extern void PairEdges(); +#ifdef HLRAD_SOFTSKY +#define SKYLEVELMAX 8 +#define SKYLEVEL_SOFTSKYON 7 +#define SKYLEVEL_SOFTSKYOFF 4 +#ifdef HLRAD_SUNSPREAD +#define SUNSPREAD_SKYLEVEL 7 +#define SUNSPREAD_THRESHOLD 15.0 +#endif +extern int g_numskynormals[SKYLEVELMAX+1]; // 0, 6, 18, 66, 258, 1026, 4098, 16386, 65538 +extern vec3_t* g_skynormals[SKYLEVELMAX+1]; //[numskynormals] +extern vec_t* g_skynormalsizes[SKYLEVELMAX+1]; // the weight of each normal +extern void BuildDiffuseNormals (); +#endif +extern void BuildFacelights(int facenum); +extern void PrecompLightmapOffsets(); +#ifdef HLRAD_REDUCELIGHTMAP +extern void ReduceLightmap (); +#endif +extern void FinalLightFace(int facenum); +#ifdef HLRAD_GROWSAMPLE +extern void ScaleDirectLights (); // run before AddPatchLights +extern void CreateFacelightDependencyList (); // run before AddPatchLights +extern void AddPatchLights (int facenum); +extern void FreeFacelightDependencyList (); +#endif +extern int TestLine(const vec3_t start, const vec3_t stop +#ifdef HLRAD_OPAQUEINSKY_FIX + , vec_t *skyhitout = NULL +#endif + ); +#ifndef HLRAD_WATERBLOCKLIGHT +extern int TestLine_r(int node, const vec3_t start, const vec3_t stop +#ifdef HLRAD_OPAQUEINSKY_FIX + , vec_t *skyhitout = NULL +#endif + ); +#endif +#ifdef HLRAD_OPAQUE_NODE +#define OPAQUE_NODE_INLINECALL +#ifdef OPAQUE_NODE_INLINECALL +typedef struct +{ + vec3_t mins, maxs; + int headnode; +} opaquemodel_t; +extern opaquemodel_t *opaquemodels; +#endif +extern void CreateOpaqueNodes(); +extern int TestLineOpaque(int modelnum, const vec3_t modelorigin, const vec3_t start, const vec3_t stop); +extern int CountOpaqueFaces(int modelnum); +extern void DeleteOpaqueNodes(); +#ifdef HLRAD_OPAQUE_BLOCK +#ifdef OPAQUE_NODE_INLINECALL +extern int TestPointOpaque_r (int nodenum, bool solid, const vec3_t point); +FORCEINLINE int TestPointOpaque (int modelnum, const vec3_t modelorigin, bool solid, const vec3_t point) // use "forceinline" because "inline" does nothing here +{ + opaquemodel_t *thismodel = &opaquemodels[modelnum]; + vec3_t newpoint; + VectorSubtract (point, modelorigin, newpoint); + int axial; + for (axial = 0; axial < 3; axial++) + { + if (newpoint[axial] > thismodel->maxs[axial]) + return 0; + if (newpoint[axial] < thismodel->mins[axial]) + return 0; + } + return TestPointOpaque_r (thismodel->headnode, solid, newpoint); +} +#else +extern int TestPointOpaque (int modelnum, const vec3_t modelorigin, bool solid, const vec3_t point); +#endif +#endif +#endif +extern void CreateDirectLights(); +extern void DeleteDirectLights(); +extern void GetPhongNormal(int facenum, const vec3_t spot, vec3_t phongnormal); // added "const" --vluzacn + +typedef bool (*funcCheckVisBit) (unsigned, unsigned +#ifdef HLRAD_HULLU + , vec3_t& +#ifdef HLRAD_TRANSPARENCY_CPP + , unsigned int& +#endif +#endif + ); +extern funcCheckVisBit g_CheckVisBit; +#ifdef HLRAD_TRANSLUCENT +extern bool CheckVisBitBackwards(unsigned receiver, unsigned emitter, const vec3_t &backorigin, const vec3_t &backnormal + #ifdef HLRAD_HULLU + , vec3_t &transparency_out + #endif + ); +#endif +#ifdef HLRAD_MDL_LIGHT_HACK +extern void MdlLightHack(void); +#endif + +// qradutil.c +extern vec_t PatchPlaneDist(const patch_t* const patch); +extern dleaf_t* PointInLeaf(const vec3_t point); +extern void MakeBackplanes(); +extern const dplane_t* getPlaneFromFace(const dface_t* const face); +extern const dplane_t* getPlaneFromFaceNumber(unsigned int facenum); +extern void getAdjustedPlaneFromFaceNumber(unsigned int facenum, dplane_t* plane); +extern dleaf_t* HuntForWorld(vec_t* point, const vec_t* plane_offset, const dplane_t* plane, int hunt_size, vec_t hunt_scale, vec_t hunt_offset); +#ifdef HLRAD_GROWSAMPLE +extern void ApplyMatrix (const matrix_t &m, const vec3_t in, vec3_t &out); +extern void ApplyMatrixOnPlane (const matrix_t &m_inverse, const vec3_t in_normal, vec_t in_dist, vec3_t &out_normal, vec_t &out_dist); +extern void MultiplyMatrix (const matrix_t &m_left, const matrix_t &m_right, matrix_t &m); +extern matrix_t MultiplyMatrix (const matrix_t &m_left, const matrix_t &m_right); +extern void MatrixForScale (const vec3_t center, vec_t scale, matrix_t &m); +extern matrix_t MatrixForScale (const vec3_t center, vec_t scale); +extern vec_t CalcMatrixSign (const matrix_t &m); +extern void TranslateWorldToTex (int facenum, matrix_t &m); +extern bool InvertMatrix (const matrix_t &m, matrix_t &m_inverse); +extern void FindFacePositions (int facenum); +extern void FreePositionMaps (); +extern bool FindNearestPosition (int facenum, const Winding *texwinding, const dplane_t &texplane, vec_t s, vec_t t, vec3_t &pos, vec_t *best_s, vec_t *best_t, vec_t *best_dist +#ifdef HLRAD_AVOIDWALLBLEED + , bool *nudged +#endif + ); +#endif + +// makescales.c +extern void MakeScalesVismatrix(); +extern void MakeScalesSparseVismatrix(); +extern void MakeScalesNoVismatrix(); + +// transfers.c +#ifdef ZHLT_64BIT_FIX +extern size_t g_total_transfer; +#else +extern unsigned g_total_transfer; +#endif +extern bool readtransfers(const char* const transferfile, long numpatches); +extern void writetransfers(const char* const transferfile, long total_patches); + +// vismatrixutil.c (shared between vismatrix.c and sparse.c) +#ifndef HLRAD_NOSWAP +extern void SwapTransfers(int patchnum); +#endif +extern void MakeScales(int threadnum); +extern void DumpTransfersMemoryUsage(); +#ifdef HLRAD_HULLU +#ifndef HLRAD_NOSWAP +extern void SwapRGBTransfers(int patchnum); +#endif +extern void MakeRGBScales(int threadnum); +#endif + +#ifdef HLRAD_TRANSPARENCY_CPP +#ifdef HLRAD_HULLU +// transparency.c (transparency array functions - shared between vismatrix.c and sparse.c) +extern void GetTransparency(const unsigned p1, const unsigned p2, vec3_t &trans, unsigned int &next_index); +extern void AddTransparencyToRawArray(const unsigned p1, const unsigned p2, const vec3_t trans); +extern void CreateFinalTransparencyArrays(const char *print_name); +extern void FreeTransparencyArrays(); +#endif +#endif +#ifdef HLRAD_OPAQUE_STYLE_BOUNCE +extern void GetStyle(const unsigned p1, const unsigned p2, int &style, unsigned int &next_index); +extern void AddStyleToStyleArray(const unsigned p1, const unsigned p2, const int style); +extern void CreateFinalStyleArrays(const char *print_name); +extern void FreeStyleArrays(); +#endif + +// lerp.c +#ifdef HLRAD_LOCALTRIANGULATION +extern void CreateTriangulations (int facenum); +extern void GetTriangulationPatches (int facenum, int *numpatches, const int **patches); +extern void InterpolateSampleLight (const vec3_t position, int surface, int numstyles, const int *styles, vec3_t *outs +#ifdef ZHLT_XASH + , vec3_t *outs_direction +#endif + ); +extern void FreeTriangulations (); +#else +#ifdef ZHLT_TEXLIGHT +#ifdef HLRAD_LERP_VL +extern void SampleTriangulation(const lerpTriangulation_t* const trian, const vec3_t point, vec3_t result, +#ifdef ZHLT_XASH + vec3_t &result_direction, +#endif + int style); //LRC +#else +extern void SampleTriangulation(const lerpTriangulation_t* const trian, vec3_t point, vec3_t result, int style); //LRC +#endif +#else +extern void SampleTriangulation(const lerpTriangulation_t* const trian, vec3_t point, vec3_t result); +#endif +extern void DestroyTriangulation(lerpTriangulation_t* trian); +extern lerpTriangulation_t* CreateTriangulation(unsigned int facenum); +extern void FreeTriangulation(lerpTriangulation_t* trian); +#endif + +// mathutil.c +extern bool TestSegmentAgainstOpaqueList(const vec_t* p1, const vec_t* p2 +#ifdef HLRAD_HULLU + , vec3_t &scaleout +#endif +#ifdef HLRAD_OPAQUE_STYLE + , int &opaquestyleout +#endif + ); +extern bool intersect_line_plane(const dplane_t* const plane, const vec_t* const p1, const vec_t* const p2, vec3_t point); +extern bool intersect_linesegment_plane(const dplane_t* const plane, const vec_t* const p1, const vec_t* const p2,vec3_t point); +extern void plane_from_points(const vec3_t p1, const vec3_t p2, const vec3_t p3, dplane_t* plane); +extern bool point_in_winding(const Winding& w, const dplane_t& plane, const vec_t* point +#ifdef HLRAD_SNAPTOWINDING + , vec_t epsilon = 0.0 +#endif + ); +#ifdef HLRAD_NUDGE_VL +extern bool point_in_winding_noedge(const Winding& w, const dplane_t& plane, const vec_t* point, vec_t width); +#ifdef HLRAD_SNAPTOWINDING +extern void snap_to_winding(const Winding& w, const dplane_t& plane, vec_t* point); +extern vec_t snap_to_winding_noedge(const Winding& w, const dplane_t& plane, vec_t* point, vec_t width, vec_t maxmove); +#endif +#endif +#ifndef HLRAD_OPAQUE_NODE +#ifdef HLRAD_POINT_IN_EDGE_FIX +extern bool point_in_winding_percentage(const Winding& w, const dplane_t& plane, const vec3_t point, const vec3_t ray, double &percentage); +#endif +#endif +#ifndef HLRAD_LOCALTRIANGULATION +extern bool point_in_wall(const lerpWall_t* wall, vec3_t point); +extern bool point_in_tri(const vec3_t point, const dplane_t* const plane, const vec3_t p1, const vec3_t p2, const vec3_t p3); +#endif +#ifndef HLRAD_MATH_VL +extern void ProjectionPoint(const vec_t* const v, const vec_t* const p, vec_t* rval); +#endif +extern void SnapToPlane(const dplane_t* const plane, vec_t* const point, vec_t offset); +#ifdef HLRAD_ACCURATEBOUNCE +extern vec_t CalcSightArea (const vec3_t receiver_origin, const vec3_t receiver_normal, const Winding *emitter_winding, int skylevel + #ifdef HLRAD_DIVERSE_LIGHTING + , vec_t lighting_power, vec_t lighting_scale + #endif + ); +#ifdef HLRAD_CUSTOMTEXLIGHT +extern vec_t CalcSightArea_SpotLight (const vec3_t receiver_origin, const vec3_t receiver_normal, const Winding *emitter_winding, const vec3_t emitter_normal, vec_t emitter_stopdot, vec_t emitter_stopdot2, int skylevel + #ifdef HLRAD_DIVERSE_LIGHTING + , vec_t lighting_power, vec_t lighting_scale + #endif + ); +#endif +#endif +#ifdef HLRAD_ACCURATEBOUNCE_ALTERNATEORIGIN +extern void GetAlternateOrigin (const vec3_t pos, const vec3_t normal, const patch_t *patch, vec3_t &origin); +#endif + +#endif //HLRAD_H__ diff --git a/src/zhlt-vluzacn/hlrad/qradutil.cpp b/src/zhlt-vluzacn/hlrad/qradutil.cpp new file mode 100644 index 0000000..33aed42 --- /dev/null +++ b/src/zhlt-vluzacn/hlrad/qradutil.cpp @@ -0,0 +1,1051 @@ +#include "qrad.h" + +static dplane_t backplanes[MAX_MAP_PLANES]; + +#ifdef HLRAD_HuntForWorld_EDGE_FIX +dleaf_t* PointInLeaf_Worst_r(int nodenum, const vec3_t point) +{ + vec_t dist; + dnode_t* node; + dplane_t* plane; + + while (nodenum >= 0) + { + node = &g_dnodes[nodenum]; + plane = &g_dplanes[node->planenum]; + dist = DotProduct(point, plane->normal) - plane->dist; + if (dist > HUNT_WALL_EPSILON) + { + nodenum = node->children[0]; + } + else if (dist < -HUNT_WALL_EPSILON) + { + nodenum = node->children[1]; + } + else + { + dleaf_t* result[2]; + result[0] = PointInLeaf_Worst_r(node->children[0], point); + result[1] = PointInLeaf_Worst_r(node->children[1], point); + if (result[0] == g_dleafs || result[0]->contents == CONTENTS_SOLID) + return result[0]; + if (result[1] == g_dleafs || result[1]->contents == CONTENTS_SOLID) + return result[1]; + if (result[0]->contents == CONTENTS_SKY) + return result[0]; + if (result[1]->contents == CONTENTS_SKY) + return result[1]; +#ifdef HLRAD_WATERBLOCKLIGHT + if (result[0]->contents == result[1]->contents) + return result[0]; + return g_dleafs; +#else + return result[0]; +#endif + } + } + + return &g_dleafs[-nodenum - 1]; +} +dleaf_t* PointInLeaf_Worst(const vec3_t point) +{ + return PointInLeaf_Worst_r(0, point); +} +#endif +dleaf_t* PointInLeaf(const vec3_t point) +{ + int nodenum; + vec_t dist; + dnode_t* node; + dplane_t* plane; + + nodenum = 0; + while (nodenum >= 0) + { + node = &g_dnodes[nodenum]; + plane = &g_dplanes[node->planenum]; + dist = DotProduct(point, plane->normal) - plane->dist; + if (dist >= 0.0) + { + nodenum = node->children[0]; + } + else + { + nodenum = node->children[1]; + } + } + + return &g_dleafs[-nodenum - 1]; +} + +/* + * ============== + * PatchPlaneDist + * Fixes up patch planes for brush models with an origin brush + * ============== + */ +vec_t PatchPlaneDist(const patch_t* const patch) +{ + const dplane_t* plane = getPlaneFromFaceNumber(patch->faceNumber); + + return plane->dist + DotProduct(g_face_offset[patch->faceNumber], plane->normal); +} + +void MakeBackplanes() +{ + int i; + + for (i = 0; i < g_numplanes; i++) + { + backplanes[i].dist = -g_dplanes[i].dist; + VectorSubtract(vec3_origin, g_dplanes[i].normal, backplanes[i].normal); + } +} + +const dplane_t* getPlaneFromFace(const dface_t* const face) +{ + if (!face) + { + Error("getPlaneFromFace() face was NULL\n"); + } + + if (face->side) + { + return &backplanes[face->planenum]; + } + else + { + return &g_dplanes[face->planenum]; + } +} + +const dplane_t* getPlaneFromFaceNumber(const unsigned int faceNumber) +{ + dface_t* face = &g_dfaces[faceNumber]; + + if (face->side) + { + return &backplanes[face->planenum]; + } + else + { + return &g_dplanes[face->planenum]; + } +} + +// Returns plane adjusted for face offset (for origin brushes, primarily used in the opaque code) +void getAdjustedPlaneFromFaceNumber(unsigned int faceNumber, dplane_t* plane) +{ + dface_t* face = &g_dfaces[faceNumber]; + const vec_t* face_offset = g_face_offset[faceNumber]; + + plane->type = (planetypes)0; + + if (face->side) + { + vec_t dist; + + VectorCopy(backplanes[face->planenum].normal, plane->normal); + dist = DotProduct(plane->normal, face_offset); + plane->dist = backplanes[face->planenum].dist + dist; + } + else + { + vec_t dist; + + VectorCopy(g_dplanes[face->planenum].normal, plane->normal); + dist = DotProduct(plane->normal, face_offset); + plane->dist = g_dplanes[face->planenum].dist + dist; + } +} + +// Will modify the plane with the new dist +void TranslatePlane(dplane_t* plane, const vec_t* delta) +{ +#ifdef HLRAD_MATH_VL + plane->dist += DotProduct (plane->normal, delta); +#else + vec3_t proj; + vec_t magnitude; + + ProjectionPoint(delta, plane->normal, proj); + magnitude = VectorLength(proj); + + if (DotProduct(plane->normal, delta) > 0) //if zero, magnitude will be zero. + { + plane->dist += magnitude; + } + else + { + plane->dist -= magnitude; + } +#endif +} + +// HuntForWorld will never return CONTENTS_SKY or CONTENTS_SOLID leafs +dleaf_t* HuntForWorld(vec_t* point, const vec_t* plane_offset, const dplane_t* plane, int hunt_size, vec_t hunt_scale, vec_t hunt_offset) +{ + dleaf_t* leaf; + int x, y, z; + int a; + + vec3_t current_point; + vec3_t original_point; + + vec3_t best_point; + dleaf_t* best_leaf = NULL; + vec_t best_dist = 99999999.0; + + vec3_t scales; + + dplane_t new_plane = *plane; + +#ifndef HLRAD_HuntForWorld_FIX + if (hunt_scale < 0.1) + { + hunt_scale = 0.1; + } +#endif + + scales[0] = 0.0; + scales[1] = -hunt_scale; + scales[2] = hunt_scale; + + VectorCopy(point, best_point); + VectorCopy(point, original_point); + + TranslatePlane(&new_plane, plane_offset); + +#ifndef HLRAD_HuntForWorld_FIX + if (!hunt_size) + { + hunt_size = DEFAULT_HUNT_SIZE; + } +#endif + +#ifdef HLRAD_HuntForWorld_FIX + for (a = 0; a < hunt_size; a++) +#else + for (a = 1; a < hunt_size; a++) +#endif + { + for (x = 0; x < 3; x++) + { + current_point[0] = original_point[0] + (scales[x % 3] * a); + for (y = 0; y < 3; y++) + { + current_point[1] = original_point[1] + (scales[y % 3] * a); + for (z = 0; z < 3; z++) + { +#ifdef HLRAD_HuntForWorld_FIX + if (a == 0) + { + if (x || y || z) + continue; + } +#endif + vec3_t delta; + vec_t dist; + + current_point[2] = original_point[2] + (scales[z % 3] * a); + + SnapToPlane(&new_plane, current_point, hunt_offset); + VectorSubtract(current_point, original_point, delta); +#ifdef HLRAD_MATH_VL + dist = DotProduct(delta, delta); +#else + dist = VectorLength(delta); +#endif + +#ifdef HLRAD_OPAQUE_BLOCK + { + int x; + for (x = 0; x < g_opaque_face_count; x++) + { + if (TestPointOpaque (g_opaque_face_list[x].modelnum, g_opaque_face_list[x].origin, g_opaque_face_list[x].block, current_point)) + break; + } + if (x < g_opaque_face_count) + continue; + } +#endif + if (dist < best_dist) + { +#ifdef HLRAD_HuntForWorld_EDGE_FIX + if ((leaf = PointInLeaf_Worst(current_point)) != g_dleafs) +#else + if ((leaf = PointInLeaf(current_point)) != g_dleafs) +#endif + { + if ((leaf->contents != CONTENTS_SKY) && (leaf->contents != CONTENTS_SOLID)) + { + if (x || y || z) + { + //dist = best_dist; +#ifdef HLRAD_HuntForWorld_FIX + best_dist = dist; +#endif + best_leaf = leaf; + VectorCopy(current_point, best_point); + continue; + } + else + { + VectorCopy(current_point, point); + return leaf; + } + } + } + } + } + } + } + if (best_leaf) + { + break; + } + } + + VectorCopy(best_point, point); + return best_leaf; +} +#ifdef HLRAD_GROWSAMPLE + +// ApplyMatrix: (x y z 1)T -> matrix * (x y z 1)T +void ApplyMatrix (const matrix_t &m, const vec3_t in, vec3_t &out) +{ + int i; + + hlassume (&in[0] != &out[0], assume_first); + VectorCopy (m.v[3], out); + for (i = 0; i < 3; i++) + { + VectorMA (out, in[i], m.v[i], out); + } +} + +// ApplyMatrixOnPlane: (x y z -dist) -> (x y z -dist) * matrix +void ApplyMatrixOnPlane (const matrix_t &m_inverse, const vec3_t in_normal, vec_t in_dist, vec3_t &out_normal, vec_t &out_dist) + // out_normal is not normalized +{ + int i; + + hlassume (&in_normal[0] != &out_normal[0], assume_first); + for (i = 0; i < 3; i++) + { + out_normal[i] = DotProduct (in_normal, m_inverse.v[i]); + } + out_dist = - (DotProduct (in_normal, m_inverse.v[3]) - in_dist); +} + +void MultiplyMatrix (const matrix_t &m_left, const matrix_t &m_right, matrix_t &m) + // The following two processes are equivalent: + // 1) ApplyMatrix (m1, v_in, v_temp), ApplyMatrix (m2, v_temp, v_out); + // 2) MultiplyMatrix (m2, m1, m), ApplyMatrix (m, v_in, v_out); +{ + int i, j; + const vec_t lastrow[4] = {0, 0, 0, 1}; + + hlassume (&m != &m_left && &m != &m_right, assume_first); + for (i = 0; i < 3; i++) + { + for (j = 0; j < 4; j++) + { + m.v[j][i] = m_left.v[0][i] * m_right.v[j][0] + + m_left.v[1][i] * m_right.v[j][1] + + m_left.v[2][i] * m_right.v[j][2] + + m_left.v[3][i] * lastrow[j]; + } + } +} + +matrix_t MultiplyMatrix (const matrix_t &m_left, const matrix_t &m_right) +{ + matrix_t m; + + MultiplyMatrix (m_left, m_right, m); + return m; +} + +void MatrixForScale (const vec3_t center, vec_t scale, matrix_t &m) +{ + int i; + + for (i = 0; i < 3; i++) + { + VectorClear (m.v[i]); + m.v[i][i] = scale; + } + VectorScale (center, 1 - scale, m.v[3]); +} + +matrix_t MatrixForScale (const vec3_t center, vec_t scale) +{ + matrix_t m; + + MatrixForScale (center, scale, m); + return m; +} + +vec_t CalcMatrixSign (const matrix_t &m) +{ + vec3_t v; + + CrossProduct (m.v[0], m.v[1], v); + return DotProduct (v, m.v[2]); +} + +void TranslateWorldToTex (int facenum, matrix_t &m) + // without g_face_offset +{ + dface_t *f; + texinfo_t *ti; + const dplane_t *fp; + int i; + + f = &g_dfaces[facenum]; + ti = &g_texinfo[f->texinfo]; + fp = getPlaneFromFace (f); + for (i = 0; i < 3; i++) + { + m.v[i][0] = ti->vecs[0][i]; + m.v[i][1] = ti->vecs[1][i]; + m.v[i][2] = fp->normal[i]; + } + m.v[3][0] = ti->vecs[0][3]; + m.v[3][1] = ti->vecs[1][i]; + m.v[3][2] = -fp->dist; +} + +bool InvertMatrix (const matrix_t &m, matrix_t &m_inverse) +{ + double texplanes[2][4]; + double faceplane[4]; + int i; + double texaxis[2][3]; + double normalaxis[3]; + double det, sqrlen1, sqrlen2, sqrlen3; + double texorg[3]; + + for (i = 0; i < 4; i++) + { + texplanes[0][i] = m.v[i][0]; + texplanes[1][i] = m.v[i][1]; + faceplane[i] = m.v[i][2]; + } + + sqrlen1 = DotProduct (texplanes[0], texplanes[0]); + sqrlen2 = DotProduct (texplanes[1], texplanes[1]); + sqrlen3 = DotProduct (faceplane, faceplane); + if (sqrlen1 <= NORMAL_EPSILON * NORMAL_EPSILON || sqrlen2 <= NORMAL_EPSILON * NORMAL_EPSILON || sqrlen3 <= NORMAL_EPSILON * NORMAL_EPSILON) + // s gradient, t gradient or face normal is too close to 0 + { + return false; + } + + CrossProduct (texplanes[0], texplanes[1], normalaxis); + det = DotProduct (normalaxis, faceplane); + if (det * det <= sqrlen1 * sqrlen2 * sqrlen3 * NORMAL_EPSILON * NORMAL_EPSILON) + // s gradient, t gradient and face normal are coplanar + { + return false; + } + VectorScale (normalaxis, 1 / det, normalaxis); + + CrossProduct (texplanes[1], faceplane, texaxis[0]); + VectorScale (texaxis[0], 1 / det, texaxis[0]); + + CrossProduct (faceplane, texplanes[0], texaxis[1]); + VectorScale (texaxis[1], 1 / det, texaxis[1]); + + VectorScale (normalaxis, -faceplane[3], texorg); + VectorMA (texorg, -texplanes[0][3], texaxis[0], texorg); + VectorMA (texorg, -texplanes[1][3], texaxis[1], texorg); + + VectorCopy (texaxis[0], m_inverse.v[0]); + VectorCopy (texaxis[1], m_inverse.v[1]); + VectorCopy (normalaxis, m_inverse.v[2]); + VectorCopy (texorg, m_inverse.v[3]); + return true; +} + +typedef struct +{ + bool valid; +#ifdef HLRAD_AVOIDWALLBLEED + bool nudged; +#endif + vec_t best_s; // FindNearestPosition will return this value + vec_t best_t; + vec3_t pos; // with DEFAULT_HUNT_OFFSET +} +position_t; + +// Size of potision_t (21) * positions per sample (9) * max number of samples (max AllocBlock (64) * 128 * 128) +// = 200MB of RAM +// But they are freed before BuildVisLeafs, so it's not a problem. + +typedef struct +{ + bool valid; + int facenum; + vec3_t face_offset; + vec3_t face_centroid; + matrix_t worldtotex; + matrix_t textoworld; + Winding *facewinding; + dplane_t faceplane; + Winding *facewindingwithoffset; + dplane_t faceplanewithoffset; + Winding *texwinding; + dplane_t texplane; // (0, 0, 1, 0) or (0, 0, -1, 0) + vec3_t texcentroid; + vec3_t start; // s_start, t_start, 0 + vec3_t step; // s_step, t_step, 0 + int w; // number of s + int h; // number of t + position_t *grid; // [h][w] +} +positionmap_t; + +static positionmap_t g_face_positions[MAX_MAP_FACES]; + +static bool IsPositionValid (positionmap_t *map, const vec3_t &pos_st, vec3_t &pos_out, bool usephongnormal = true, bool doedgetest = true, int hunt_size = 2, vec_t hunt_scale = 0.2) +{ + vec3_t pos; + vec3_t pos_normal; + vec_t hunt_offset; + + ApplyMatrix (map->textoworld, pos_st, pos); + VectorAdd (pos, map->face_offset, pos); + if (usephongnormal) + { + GetPhongNormal (map->facenum, pos, pos_normal); + } + else + { + VectorCopy (map->faceplanewithoffset.normal, pos_normal); + } + VectorMA (pos, DEFAULT_HUNT_OFFSET, pos_normal, pos); + + hunt_offset = DotProduct (pos, map->faceplanewithoffset.normal) - map->faceplanewithoffset.dist; // might be smaller than DEFAULT_HUNT_OFFSET + + // push the point 0.2 units around to avoid walls + if (!HuntForWorld (pos, vec3_origin, &map->faceplanewithoffset, hunt_size, hunt_scale, hunt_offset)) + { + return false; + } + + if (doedgetest && !point_in_winding_noedge (*map->facewindingwithoffset, map->faceplanewithoffset, pos, DEFAULT_EDGE_WIDTH)) + { + // if the sample has gone beyond face boundaries, be careful that it hasn't passed a wall + vec3_t test; +#ifdef HLRAD_HULLU + vec3_t transparency; +#endif +#ifdef HLRAD_OPAQUE_STYLE + int opaquestyle; +#endif + + VectorCopy (pos, test); + snap_to_winding_noedge (*map->facewindingwithoffset, map->faceplanewithoffset, test, DEFAULT_EDGE_WIDTH, 4 * DEFAULT_EDGE_WIDTH); + + if (!HuntForWorld (test, vec3_origin, &map->faceplanewithoffset, hunt_size, hunt_scale, hunt_offset)) + { + return false; + } + + if (TestLine (pos, test) != CONTENTS_EMPTY) + { + return false; + } + + if (TestSegmentAgainstOpaqueList (pos, test +#ifdef HLRAD_HULLU + , transparency +#endif +#ifdef HLRAD_OPAQUE_STYLE + , opaquestyle +#endif + ) == true +#ifdef HLRAD_OPAQUE_STYLE + || opaquestyle != -1 +#endif + ) + { + return false; + } + } + + VectorCopy (pos, pos_out); + return true; +} + +static void CalcSinglePosition (positionmap_t *map, int is, int it) +{ + position_t *p; + vec_t smin, smax, tmin, tmax; + dplane_t clipplanes[4]; + const vec3_t v_s = {1, 0, 0}; + const vec3_t v_t = {0, 1, 0}; + Winding *zone; + + p = &map->grid[is + map->w * it]; + smin = map->start[0] + is * map->step[0]; + smax = map->start[0] + (is + 1) * map->step[0]; + tmin = map->start[1] + it * map->step[1]; + tmax = map->start[1] + (it + 1) * map->step[1]; + + VectorScale (v_s, 1, clipplanes[0].normal); clipplanes[0].dist = smin; + VectorScale (v_s, -1, clipplanes[1].normal); clipplanes[1].dist = -smax; + VectorScale (v_t, 1, clipplanes[2].normal); clipplanes[2].dist = tmin; + VectorScale (v_t, -1, clipplanes[3].normal); clipplanes[3].dist = -tmax; + +#ifdef HLRAD_AVOIDWALLBLEED + p->nudged = true; // it's nudged unless it can get its position directly from its s,t +#endif + zone = new Winding (*map->texwinding); + for (int x = 0; x < 4 && zone->m_NumPoints > 0; x++) + { + zone->Clip (clipplanes[x], false); + } + if (zone->m_NumPoints == 0) + { + p->valid = false; + } + else + { + vec3_t original_st; + vec3_t test_st; + + original_st[0] = map->start[0] + (is + 0.5) * map->step[0]; + original_st[1] = map->start[1] + (it + 0.5) * map->step[1]; + original_st[2] = 0.0; + + p->valid = false; + + if (!p->valid) + { + VectorCopy (original_st, test_st); + snap_to_winding (*zone, map->texplane, test_st); + + if (IsPositionValid (map, test_st, p->pos)) + { + p->valid = true; +#ifdef HLRAD_AVOIDWALLBLEED + p->nudged = false; +#endif + p->best_s = test_st[0]; + p->best_t = test_st[1]; + } + } + + if (!p->valid) + { + zone->getCenter (test_st); + if (IsPositionValid (map, test_st, p->pos)) + { + p->valid = true; + p->best_s = test_st[0]; + p->best_t = test_st[1]; + } + } + + if (!p->valid +#ifdef HLRAD_FASTMODE + && !g_fastmode +#endif + ) + { + const int numnudges = 12; + vec3_t nudgelist[numnudges] = {{0.1, 0, 0}, {-0.1, 0, 0}, {0, 0.1, 0}, {0, -0.1, 0}, + {0.3, 0, 0}, {-0.3, 0, 0}, {0, 0.3, 0}, {0, -0.3, 0}, + {0.3, 0.3, 0}, {-0.3, 0.3, 0}, {-0.3, -0.3, 0}, {0.3, -0.3, 0}}; + + for (int i = 0; i < numnudges; i++) + { + VectorMultiply (nudgelist[i], map->step, test_st); + VectorAdd (test_st, original_st, test_st); + snap_to_winding (*zone, map->texplane, test_st); + + if (IsPositionValid (map, test_st, p->pos)) + { + p->valid = true; + p->best_s = test_st[0]; + p->best_t = test_st[1]; + break; + } + } + } + } + delete zone; +} + +void FindFacePositions (int facenum) + // this function must be called after g_face_offset and g_face_centroids and g_edgeshare have been calculated +{ + dface_t *f; + positionmap_t *map; + texinfo_t *ti; + vec3_t v; + const vec3_t v_up = {0, 0, 1}; + vec_t density; + vec_t texmins[2], texmaxs[2]; + int imins[2], imaxs[2]; + int is, it; + int x; + int k; + + f = &g_dfaces[facenum]; + map = &g_face_positions[facenum]; + map->valid = true; + map->facenum = facenum; + map->facewinding = NULL; + map->facewindingwithoffset = NULL; + map->texwinding = NULL; + map->grid = NULL; + + ti = &g_texinfo[f->texinfo]; + if (ti->flags & TEX_SPECIAL) + { + map->valid = false; + return; + } + + VectorCopy (g_face_offset[facenum], map->face_offset); + VectorCopy (g_face_centroids[facenum], map->face_centroid); + TranslateWorldToTex (facenum, map->worldtotex); + if (!InvertMatrix (map->worldtotex, map->textoworld)) + { + map->valid = false; + return; + } + + map->facewinding = new Winding (*f); + map->faceplane = *getPlaneFromFace (f); + map->facewindingwithoffset = new Winding (map->facewinding->m_NumPoints); + for (x = 0; x < map->facewinding->m_NumPoints; x++) + { + VectorAdd (map->facewinding->m_Points[x], map->face_offset, map->facewindingwithoffset->m_Points[x]); + } + map->faceplanewithoffset = map->faceplane; + map->faceplanewithoffset.dist = map->faceplane.dist + DotProduct (map->face_offset, map->faceplane.normal); + + map->texwinding = new Winding (map->facewinding->m_NumPoints); + for (x = 0; x < map->facewinding->m_NumPoints; x++) + { + ApplyMatrix (map->worldtotex, map->facewinding->m_Points[x], map->texwinding->m_Points[x]); + map->texwinding->m_Points[x][2] = 0.0; + } + map->texwinding->RemoveColinearPoints (); + VectorCopy (v_up, map->texplane.normal); + if (CalcMatrixSign (map->worldtotex) < 0.0) + { + map->texplane.normal[2] *= -1; + } + map->texplane.dist = 0.0; + if (map->texwinding->m_NumPoints == 0) + { + delete map->facewinding; + map->facewinding = NULL; + delete map->facewindingwithoffset; + map->facewindingwithoffset = NULL; + delete map->texwinding; + map->texwinding = NULL; + map->valid = false; + return; + } + VectorSubtract (map->face_centroid, map->face_offset, v); + ApplyMatrix (map->worldtotex, v, map->texcentroid); + map->texcentroid[2] = 0.0; + + for (x = 0; x < map->texwinding->m_NumPoints; x++) + { + for (k = 0; k < 2; k++) + { + if (x == 0 || map->texwinding->m_Points[x][k] < texmins[k]) + texmins[k] = map->texwinding->m_Points[x][k]; + if (x == 0 || map->texwinding->m_Points[x][k] > texmaxs[k]) + texmaxs[k] = map->texwinding->m_Points[x][k]; + } + } + density = 3.0; +#ifdef HLRAD_FASTMODE + if (g_fastmode) + { + density = 1.0; + } +#endif + map->step[0] = (vec_t)TEXTURE_STEP / density; + map->step[1] = (vec_t)TEXTURE_STEP / density; + map->step[2] = 1.0; + for (k = 0; k < 2; k++) + { + imins[k] = (int)floor (texmins[k] / map->step[k] + 0.5 - ON_EPSILON); + imaxs[k] = (int)ceil (texmaxs[k] / map->step[k] - 0.5 + ON_EPSILON); + } + map->start[0] = (imins[0] - 0.5) * map->step[0]; + map->start[1] = (imins[1] - 0.5) * map->step[1]; + map->start[2] = 0.0; + map->w = imaxs[0] - imins[0] + 1; + map->h = imaxs[1] - imins[1] + 1; + if (map->w <= 0 || map->h <= 0 || (double)map->w * (double)map->h > 99999999) + { + delete map->facewinding; + map->facewinding = NULL; + delete map->facewindingwithoffset; + map->facewindingwithoffset = NULL; + delete map->texwinding; + map->texwinding = NULL; + map->valid = false; + return; + } + + map->grid = (position_t *)malloc (map->w * map->h * sizeof (position_t)); + hlassume (map->grid != NULL, assume_NoMemory); + + for (it = 0; it < map->h; it++) + { + for (is = 0; is < map->w; is++) + { + CalcSinglePosition (map, is, it); + } + } + + return; +} + +void FreePositionMaps () +{ +#ifdef HLRAD_DEBUG_DRAWPOINTS + if (g_drawsample) + { + char name[_MAX_PATH+20]; + sprintf (name, "%s_positions.pts", g_Mapname); + Log ("Writing '%s' ...\n", name); + FILE *f; + f = fopen(name, "w"); + if (f) + { + const int pos_count = 15; + const vec3_t pos[pos_count] = {{0,0,0},{1,0,0},{0,1,0},{-1,0,0},{0,-1,0},{1,0,0},{0,0,1},{-1,0,0},{0,0,-1},{0,-1,0},{0,0,1},{0,1,0},{0,0,-1},{1,0,0},{0,0,0}}; + int i, j, k; + vec3_t v, dist; + for (i = 0; i < g_numfaces; ++i) + { + positionmap_t *map = &g_face_positions[i]; + if (!map->valid) + { + continue; + } + for (j = 0; j < map->h * map->w; ++j) + { + if (!map->grid[j].valid) + { + continue; + } + VectorCopy (map->grid[j].pos, v); + VectorSubtract (v, g_drawsample_origin, dist); + if (DotProduct (dist, dist) < g_drawsample_radius * g_drawsample_radius) + { + for (k = 0; k < pos_count; ++k) + fprintf (f, "%g %g %g\n", v[0]+pos[k][0], v[1]+pos[k][1], v[2]+pos[k][2]); + } + } + } + fclose(f); + Log ("OK.\n"); + } + else + Log ("Error.\n"); + } +#endif + for (int facenum = 0; facenum < g_numfaces; facenum++) + { + positionmap_t *map = &g_face_positions[facenum]; + if (map->valid) + { + delete map->facewinding; + map->facewinding = NULL; + delete map->facewindingwithoffset; + map->facewindingwithoffset = NULL; + delete map->texwinding; + map->texwinding = NULL; + free (map->grid); + map->grid = NULL; + map->valid = false; + } + } +} + +bool FindNearestPosition (int facenum, const Winding *texwinding, const dplane_t &texplane, vec_t s, vec_t t, vec3_t &pos, vec_t *best_s, vec_t *best_t, vec_t *dist +#ifdef HLRAD_AVOIDWALLBLEED + , bool *nudged +#endif + ) +{ + positionmap_t *map; + vec3_t original_st; + int x; + int itmin, itmax, ismin, ismax; + const vec3_t v_s = {1, 0, 0}; + const vec3_t v_t = {0, 1, 0}; + int is; + int it; + vec3_t v; + bool found; + int best_is; + int best_it; + vec_t best_dist; + + map = &g_face_positions[facenum]; + if (!map->valid) + { + return false; + } + + original_st[0] = s; + original_st[1] = t; + original_st[2] = 0.0; + + if (point_in_winding (*map->texwinding, map->texplane, original_st, 4 * ON_EPSILON)) + { + itmin = (int)ceil ((original_st[1] - map->start[1] - 2 * ON_EPSILON) / map->step[1]) - 1; + itmax = (int)floor ((original_st[1] - map->start[1] + 2 * ON_EPSILON) / map->step[1]); + ismin = (int)ceil ((original_st[0] - map->start[0] - 2 * ON_EPSILON) / map->step[0]) - 1; + ismax = (int)floor ((original_st[0] - map->start[0] + 2 * ON_EPSILON) / map->step[0]); + itmin = qmax (0, itmin); + itmax = qmin (itmax, map->h - 1); + ismin = qmax (0, ismin); + ismax = qmin (ismax, map->w - 1); + + found = false; +#ifdef HLRAD_AVOIDWALLBLEED + bool best_nudged = true; +#endif + for (it = itmin; it <= itmax; it++) + { + for (is = ismin; is <= ismax; is++) + { + position_t *p; + vec3_t current_st; + vec_t d; + + p = &map->grid[is + map->w * it]; + if (!p->valid) + { + continue; + } + current_st[0] = p->best_s; + current_st[1] = p->best_t; + current_st[2] = 0.0; + + VectorSubtract (current_st, original_st, v); + d = VectorLength (v); + + if (!found || +#ifdef HLRAD_AVOIDWALLBLEED + !p->nudged && best_nudged || + p->nudged == best_nudged + && +#endif + d < best_dist - 2 * ON_EPSILON) + { + found = true; + best_is = is; + best_it = it; + best_dist = d; +#ifdef HLRAD_AVOIDWALLBLEED + best_nudged = p->nudged; +#endif + } + } + } + + if (found) + { + position_t *p; + + p = &map->grid[best_is + map->w * best_it]; + VectorCopy (p->pos, pos); + *best_s = p->best_s; + *best_t = p->best_t; + *dist = 0.0; +#ifdef HLRAD_AVOIDWALLBLEED + *nudged = p->nudged; +#endif + return true; + } + } +#ifdef HLRAD_AVOIDWALLBLEED + *nudged = true; +#endif + + itmin = map->h; + itmax = -1; + ismin = map->w; + ismax = -1; + for (x = 0; x < texwinding->m_NumPoints; x++) + { + it = (int)floor ((texwinding->m_Points[x][1] - map->start[1] + 0.5 * ON_EPSILON) / map->step[1]); + itmin = qmin (itmin, it); + it = (int)ceil ((texwinding->m_Points[x][1] - map->start[1] - 0.5 * ON_EPSILON) / map->step[1]) - 1; + itmax = qmax (it, itmax); + is = (int)floor ((texwinding->m_Points[x][0] - map->start[0] + 0.5 * ON_EPSILON) / map->step[0]); + ismin = qmin (ismin, is); + is = (int)ceil ((texwinding->m_Points[x][0] - map->start[0] - 0.5 * ON_EPSILON) / map->step[0]) - 1; + ismax = qmax (is, ismax); + } + itmin = qmax (0, itmin); + itmax = qmin (itmax, map->h - 1); + ismin = qmax (0, ismin); + ismax = qmin (ismax, map->w - 1); + + found = false; + for (it = itmin; it <= itmax; it++) + { + for (is = ismin; is <= ismax; is++) + { + position_t *p; + vec3_t current_st; + vec_t d; + + p = &map->grid[is + map->w * it]; + if (!p->valid) + { + continue; + } + current_st[0] = p->best_s; + current_st[1] = p->best_t; + current_st[2] = 0.0; + + VectorSubtract (current_st, original_st, v); + d = VectorLength (v); + + if (!found || d < best_dist - ON_EPSILON) + { + found = true; + best_is = is; + best_it = it; + best_dist = d; + } + } + } + + if (found) + { + position_t *p; + + p = &map->grid[best_is + map->w * best_it]; + VectorCopy (p->pos, pos); + *best_s = p->best_s; + *best_t = p->best_t; + *dist = best_dist; + return true; + } + + return false; +} + + +#endif diff --git a/src/zhlt-vluzacn/hlrad/sparse.cpp b/src/zhlt-vluzacn/hlrad/sparse.cpp new file mode 100644 index 0000000..a41d822 --- /dev/null +++ b/src/zhlt-vluzacn/hlrad/sparse.cpp @@ -0,0 +1,944 @@ +#include "qrad.h" + +#ifndef HLRAD_TRANSPARENCY_CPP +// Transparency array + +#ifdef HLRAD_HULLU + +typedef struct { + unsigned x; + unsigned y; + vec3_t transparency; +} transparency_t; +static transparency_t *s_transparency_list=NULL; +static unsigned long s_transparency_count=0; +static unsigned long s_max_transparency_count=0; + +static void FindOpacity(const unsigned p1, const unsigned p2, vec3_t &out) +{ + for(unsigned long i = 0; i < s_transparency_count; i++) + { + if(s_transparency_list[i].x==p1 && s_transparency_list[i].y==p2) + { + VectorCopy(s_transparency_list[i].transparency, out); + return; + } + } + VectorFill(out, 1.0); +} + +#endif /*HLRAD_HULLU*/ +#endif + + +typedef struct +{ + unsigned offset:24; + unsigned values:8; +} +sparse_row_t; + +typedef struct +{ + sparse_row_t* row; + int count; +} +sparse_column_t; + +sparse_column_t* s_vismatrix; + +// Vismatrix protected +static unsigned IsVisbitInArray(const unsigned x, const unsigned y) +{ + int first, last, current; + int y_byte = y / 8; + sparse_row_t* row; + sparse_column_t* column = s_vismatrix + x; + + if (!column->count) + { + return -1; + } + + first = 0; + last = column->count - 1; + + // Warning("Searching . . ."); + // binary search to find visbit + while (1) + { + current = (first + last) / 2; + row = column->row + current; + // Warning("first %u, last %u, current %u, row %p, row->offset %u", first, last, current, row, row->offset); + if ((row->offset) < y_byte) + { + first = current + 1; + } + else if ((row->offset) > y_byte) + { + last = current - 1; + } + else + { + return current; + } + if (first > last) + { + return -1; + } + } +} + +#ifdef HLRAD_SPARSEVISMATRIX_FAST +static void SetVisColumn (int patchnum, bool uncompressedcolumn[MAX_SPARSE_VISMATRIX_PATCHES]) +{ + sparse_column_t *column; + int mbegin; + int m; + int i; + unsigned int bits; + + column = &s_vismatrix[patchnum]; + if (column->count || column->row) + { + Error ("SetVisColumn: column has been set"); + } + + for (mbegin = 0; mbegin < g_num_patches; mbegin += 8) + { + bits = 0; + for (m = mbegin; m < mbegin + 8; m++) + { + if (m >= g_num_patches) + { + break; + } + if (uncompressedcolumn[m]) // visible + { + if (m < patchnum) + { + Error ("SetVisColumn: invalid parameter: m < patchnum"); + } + bits |= (1 << (m - mbegin)); + } + } + if (bits) + { + column->count++; + } + } + + if (!column->count) + { + return; + } + column->row = (sparse_row_t *)malloc (column->count * sizeof (sparse_row_t)); + hlassume (column->row != NULL, assume_NoMemory); + + i = 0; + for (mbegin = 0; mbegin < g_num_patches; mbegin += 8) + { + bits = 0; + for (m = mbegin; m < mbegin + 8; m++) + { + if (m >= g_num_patches) + { + break; + } + if (uncompressedcolumn[m]) // visible + { + bits |= (1 << (m - mbegin)); + } + } + if (bits) + { + column->row[i].offset = mbegin / 8; + column->row[i].values = bits; + i++; + } + } + if (i != column->count) + { + Error ("SetVisColumn: internal error"); + } +} +#else +// Vismatrix protected +static void InsertVisbitIntoArray(const unsigned x, const unsigned y) +{ + unsigned count; + unsigned y_byte = y / 8; + sparse_column_t* column = s_vismatrix + x; + sparse_row_t* row = column->row; + + if (!column->count) + { + column->count++; + row = column->row = (sparse_row_t*)malloc(sizeof(sparse_row_t)); +#ifdef HLRAD_HLASSUMENOMEMORY + hlassume (row != NULL, assume_NoMemory); +#endif + row->offset = y_byte; + row->values = 1 << (y & 7); + return; + } + + // Insertion + count = 0; + while (count < column->count) + { + if (row->offset > y_byte) + { + unsigned newsize = (column->count + 1) * sizeof(sparse_row_t); + sparse_row_t* newrow = (sparse_row_t*)malloc(newsize); +#ifdef HLRAD_HLASSUMENOMEMORY + hlassume (newrow != NULL, assume_NoMemory); +#endif + + memcpy(newrow, column->row, count * sizeof(sparse_row_t)); + memcpy(newrow + count + 1, column->row + count, (column->count - count) * sizeof(sparse_row_t)); + + row = newrow + count; + row->offset = y_byte; + row->values = 1 << (y & 7); + + free(column->row); + column->row = newrow; + column->count++; + return; + } + + row++; + count++; + } + + // Append + { + unsigned newsize = (count + 1) * sizeof(sparse_row_t); + sparse_row_t* newrow = (sparse_row_t*)malloc(newsize); +#ifdef HLRAD_HLASSUMENOMEMORY + hlassume (newrow != NULL, assume_NoMemory); +#endif + + memcpy(newrow, column->row, column->count * sizeof(sparse_row_t)); + + row = newrow + column->count; + row->offset = y_byte; + row->values = 1 << (y & 7); + + free(column->row); + column->row = newrow; + column->count++; + return; + } +} + +// Vismatrix public +static void SetVisBit(unsigned x, unsigned y) +{ + unsigned offset; + + if (x == y) + { + return; + } + + if (x > y) + { + const unsigned a = x; + const unsigned b = y; + x = b; + y = a; + } + + if (x > g_num_patches) + { + Warning("in SetVisBit(), x > num_patches"); + } + if (y > g_num_patches) + { + Warning("in SetVisBit(), y > num_patches"); + } + + ThreadLock(); + + if ((offset = IsVisbitInArray(x, y)) != -1) + { + s_vismatrix[x].row[offset].values |= 1 << (y & 7); + } + else + { + InsertVisbitIntoArray(x, y); + } + + ThreadUnlock(); +} +#endif + +// Vismatrix public +#ifdef HLRAD_TRANSPARENCY_CPP +static bool CheckVisBitSparse(unsigned x, unsigned y +#ifdef HLRAD_HULLU + , vec3_t &transparency_out + , unsigned int &next_index +#endif + ) +{ +#ifdef HLRAD_HULLU + int offset; +#else + unsigned offset; +#endif + +#ifdef HLRAD_HULLU + VectorFill(transparency_out, 1.0); +#endif + + if (x == y) + { + return 1; + } + +#ifdef HLRAD_HULLU + const unsigned a = x; + const unsigned b = y; +#endif + + if (x > y) + { +#ifndef HLRAD_HULLU + const unsigned a = x; + const unsigned b = y; +#endif + x = b; + y = a; + } + + if (x > g_num_patches) + { + Warning("in CheckVisBit(), x > num_patches"); + } + if (y > g_num_patches) + { + Warning("in CheckVisBit(), y > num_patches"); + } + + if ((offset = IsVisbitInArray(x, y)) != -1) + { +#ifdef HLRAD_HULLU + if(g_customshadow_with_bouncelight) + { + GetTransparency(a, b, transparency_out, next_index); + } +#endif + return s_vismatrix[x].row[offset].values & (1 << (y & 7)); + } + + return false; +} +#else /*HLRAD_TRANSPARENCY_CPP*/ +static bool CheckVisBitSparse(unsigned x, unsigned y +#ifdef HLRAD_HULLU + , vec3_t &transparency_out +#endif + ) +{ + unsigned offset; + + if (x == y) + { +#ifdef HLRAD_HULLU + VectorFill(transparency_out, 1.0); +#endif + return 1; + } + + if (x > y) + { + const unsigned a = x; + const unsigned b = y; + x = b; + y = a; + } + + if (x > g_num_patches) + { + Warning("in CheckVisBit(), x > num_patches"); + } + if (y > g_num_patches) + { + Warning("in CheckVisBit(), y > num_patches"); + } + + if ((offset = IsVisbitInArray(x, y)) != -1) + { +#ifdef HLRAD_HULLU + if(g_customshadow_with_bouncelight) + { + vec3_t tmp = {1.0, 1.0, 1.0}; + FindOpacity(x, y, tmp); + VectorCopy(tmp, transparency_out); + } + else + { + VectorFill(transparency_out, 1.0); + } +#endif + return s_vismatrix[x].row[offset].values & (1 << (y & 7)); + } +#ifdef HLRAD_HULLU + VectorFill(transparency_out, 1.0); +#endif + return 0; +} +#endif /*HLRAD_TRANSPARENCY_CPP*/ + +/* + * ============== + * TestPatchToFace + * + * Sets vis bits for all patches in the face + * ============== + */ +static void TestPatchToFace(const unsigned patchnum, const int facenum, const int head +#ifdef HLRAD_ENTITYBOUNCE_FIX + , byte *pvs +#endif +#ifdef HLRAD_SPARSEVISMATRIX_FAST + , bool uncompressedcolumn[MAX_SPARSE_VISMATRIX_PATCHES] +#endif + ) +{ + patch_t* patch = &g_patches[patchnum]; + patch_t* patch2 = g_face_patches[facenum]; + + // if emitter is behind that face plane, skip all patches + + if (patch2) + { + const dplane_t* plane2 = getPlaneFromFaceNumber(facenum); + +#ifdef HLRAD_ACCURATEBOUNCE_ALTERNATEORIGIN + if (DotProduct (patch->origin, plane2->normal) > PatchPlaneDist (patch2) + ON_EPSILON - patch->emitter_range) +#else + if (DotProduct(patch->origin, plane2->normal) > (PatchPlaneDist(patch2) + MINIMUM_PATCH_DISTANCE)) +#endif + { + // we need to do a real test + const dplane_t* plane = getPlaneFromFaceNumber(patch->faceNumber); + + for (; patch2; patch2 = patch2->next) + { + unsigned m = patch2 - g_patches; + +#ifdef HLRAD_HULLU + vec3_t transparency = {1.0,1.0,1.0}; +#endif +#ifdef HLRAD_OPAQUE_STYLE + int opaquestyle = -1; +#endif + + // 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 (m > patchnum) + { +#ifdef HLRAD_ENTITYBOUNCE_FIX + if (patch2->leafnum == 0 || !(pvs[(patch2->leafnum - 1) >> 3] & (1 << ((patch2->leafnum - 1) & 7)))) + { + continue; + } +#endif +#ifdef HLRAD_ACCURATEBOUNCE_ALTERNATEORIGIN + vec3_t origin1, origin2; + vec3_t delta; + vec_t dist; + VectorSubtract (patch->origin, patch2->origin, delta); + dist = VectorLength (delta); + if (dist < patch2->emitter_range - ON_EPSILON) + { + GetAlternateOrigin (patch->origin, plane->normal, patch2, origin2); + } + else + { + VectorCopy (patch2->origin, origin2); + } + if (DotProduct (origin2, plane->normal) <= PatchPlaneDist (patch) + MINIMUM_PATCH_DISTANCE) + { + continue; + } + if (dist < patch->emitter_range - ON_EPSILON) + { + GetAlternateOrigin (patch2->origin, plane2->normal, patch, origin1); + } + else + { + VectorCopy (patch->origin, origin1); + } + if (DotProduct (origin1, plane2->normal) <= PatchPlaneDist (patch2) + MINIMUM_PATCH_DISTANCE) + { + continue; + } +#else + if (DotProduct(patch2->origin, plane->normal) <= (PatchPlaneDist(patch) + MINIMUM_PATCH_DISTANCE)) + { + continue; + } +#endif +#ifdef HLRAD_WATERBLOCKLIGHT + if (TestLine( + #ifdef HLRAD_ACCURATEBOUNCE_ALTERNATEORIGIN + origin1, origin2 + #else + patch->origin, patch2->origin + #endif + ) != CONTENTS_EMPTY) +#else + if (TestLine_r(head, + #ifdef HLRAD_ACCURATEBOUNCE_ALTERNATEORIGIN + origin1, origin2 + #else + patch->origin, patch2->origin + #endif + ) != CONTENTS_EMPTY) +#endif + { + continue; + } + if (TestSegmentAgainstOpaqueList( + #ifdef HLRAD_ACCURATEBOUNCE_ALTERNATEORIGIN + origin1, origin2 + #else + patch->origin, patch2->origin + #endif +#ifdef HLRAD_HULLU + , transparency +#endif +#ifdef HLRAD_OPAQUE_STYLE + , opaquestyle +#endif + )) + { + continue; + } + +#ifdef HLRAD_OPAQUE_STYLE_BOUNCE + if (opaquestyle != -1) + { + AddStyleToStyleArray (m, patchnum, opaquestyle); + AddStyleToStyleArray (patchnum, m, opaquestyle); + } +#endif + +#ifdef HLRAD_HULLU + #ifdef HLRAD_TRANSPARENCY_CPP + if(g_customshadow_with_bouncelight && !VectorCompare(transparency, vec3_one) ) + { + AddTransparencyToRawArray(patchnum, m, transparency); + } + #else + // transparency face fix table + if(g_customshadow_with_bouncelight && fabs(VectorAvg(transparency) - 1.0) < 0.001) + { + while(s_transparency_count>=s_max_transparency_count) + { + //new size + unsigned long oldsize = s_max_transparency_count; + s_max_transparency_count += 128; + + //realloc + s_transparency_list = (transparency_t*)realloc(s_transparency_list, s_max_transparency_count * sizeof(transparency_t)); + + // clean new memory + memset(&s_transparency_list[oldsize], 0, sizeof(transparency_t) * 128); + } + + //add to array + VectorCopy(transparency, s_transparency_list[s_transparency_count].transparency); + s_transparency_list[s_transparency_count].y = m; + s_transparency_list[s_transparency_count].x = patchnum; + + s_transparency_count++; + } + #endif +#endif /*HLRAD_HULLU*/ +#ifdef HLRAD_SPARSEVISMATRIX_FAST + uncompressedcolumn[m] = true; +#else + SetVisBit(m, patchnum); +#endif + } + } + } + } +} + +#ifndef HLRAD_VISMATRIX_NOMARKSURFACES +/* + * ============== + * BuildVisRow + * + * Calc vis bits from a single patch + * ============== + */ +static void BuildVisRow(const int patchnum, byte* pvs, const int head) +{ + int j, k, l; + byte face_tested[MAX_MAP_FACES]; + dleaf_t* leaf; + + memset(face_tested, 0, g_numfaces); + + // leaf 0 is the solid leaf (skipped) +#ifdef HLRAD_VIS_FIX + for (j = 1, leaf = g_dleafs + 1; j < 1 + g_dmodels[0].visleafs; j++, leaf++) +#else + for (j = 1, leaf = g_dleafs + 1; j < g_numleafs; j++, leaf++) +#endif + { + if (!(pvs[(j - 1) >> 3] & (1 << ((j - 1) & 7)))) + { + continue; // not in pvs + } + for (k = 0; k < leaf->nummarksurfaces; k++) + { + l = g_dmarksurfaces[leaf->firstmarksurface + k]; + + // faces can be marksurfed by multiple leaves, but + // don't bother testing again + if (face_tested[l]) + continue; + face_tested[l] = 1; + + TestPatchToFace(patchnum, l, head +#ifdef HLRAD_ENTITYBOUNCE_FIX + , pvs +#endif + ); + } + } +} +#endif + +/* + * =========== + * BuildVisLeafs + * + * This is run by multiple threads + * =========== + */ +#ifdef SYSTEM_WIN32 +#pragma warning(push) +#pragma warning(disable: 4100) // unreferenced formal parameter +#endif +static void BuildVisLeafs(int threadnum) +{ + int i; + int lface, facenum, facenum2; + byte pvs[(MAX_MAP_LEAFS + 7) / 8]; + dleaf_t* srcleaf; + dleaf_t* leaf; + patch_t* patch; + int head; + unsigned patchnum; +#ifdef HLRAD_SPARSEVISMATRIX_FAST + bool *uncompressedcolumn = (bool *)malloc (MAX_SPARSE_VISMATRIX_PATCHES * sizeof (bool)); + hlassume (uncompressedcolumn != NULL, assume_NoMemory); +#endif + + while (1) + { + // + // build a minimal BSP tree that only + // covers areas relevent to the PVS + // + i = GetThreadWork(); + if (i == -1) + { + break; + } + i++; // skip leaf 0 + srcleaf = &g_dleafs[i]; +#ifdef HLRAD_WITHOUTVIS + if (!g_visdatasize) + { + #ifdef ZHLT_DecompressVis_FIX + memset (pvs, 255, (g_dmodels[0].visleafs + 7) / 8); + #else + memset(pvs, 255, (g_numleafs + 7) / 8); + #endif + } + else + { +#endif +#ifdef HLRAD_VIS_FIX + if (srcleaf->visofs == -1) + { + Developer (DEVELOPER_LEVEL_ERROR, "Error: No visdata for leaf %d\n", i); + continue; + } +#endif + DecompressVis(&g_dvisdata[srcleaf->visofs], pvs, sizeof(pvs)); +#ifdef HLRAD_WITHOUTVIS + } +#endif + head = 0; + + // + // go through all the faces inside the + // leaf, and process the patches that + // actually have origins inside + // +#ifdef HLRAD_VISMATRIX_NOMARKSURFACES + for (facenum = 0; facenum < g_numfaces; facenum++) + { + for (patch = g_face_patches[facenum]; patch; patch = patch->next) + { + if (patch->leafnum != i) + continue; + patchnum = patch - g_patches; + #ifdef HLRAD_SPARSEVISMATRIX_FAST + for (int m = 0; m < g_num_patches; m++) + { + uncompressedcolumn[m] = false; + } + #endif + for (facenum2 = facenum + 1; facenum2 < g_numfaces; facenum2++) + TestPatchToFace (patchnum, facenum2, head, pvs + #ifdef HLRAD_SPARSEVISMATRIX_FAST + , uncompressedcolumn + #endif + ); + #ifdef HLRAD_SPARSEVISMATRIX_FAST + SetVisColumn (patchnum, uncompressedcolumn); + #endif + } + } +#else + for (lface = 0; lface < srcleaf->nummarksurfaces; lface++) + { + facenum = g_dmarksurfaces[srcleaf->firstmarksurface + lface]; + for (patch = g_face_patches[facenum]; patch; patch = patch->next) + { +#ifdef HLRAD_ENTITYBOUNCE_FIX + if (patch->leafnum != i) + { + continue; + } +#else + leaf = PointInLeaf(patch->origin); + if (leaf != srcleaf) + { + continue; + } +#endif + + patchnum = patch - g_patches; + // build to all other world leafs + BuildVisRow(patchnum, pvs, head); + + // build to bmodel faces + if (g_nummodels < 2) + { + continue; + } + for (facenum2 = g_dmodels[1].firstface; facenum2 < g_numfaces; facenum2++) + { + TestPatchToFace(patchnum, facenum2, head +#ifdef HLRAD_ENTITYBOUNCE_FIX + , pvs +#endif + ); + } + } + } +#ifdef HLRAD_ENTITYBOUNCE_FIX + if (g_nummodels >= 2) + { + for (facenum = g_dmodels[1].firstface; facenum < g_numfaces; facenum++) + { + for (patch = g_face_patches[facenum]; patch; patch = patch->next) + { + if (patch->leafnum != i) + continue; + patchnum = patch - g_patches; + // skip BuildVisRow here because entity patchnums are always bigger than world patchnums. + for (facenum2 = g_dmodels[1].firstface; facenum2 < g_numfaces; facenum2++) + TestPatchToFace(patchnum, facenum2, head, pvs); + } + } + } +#endif +#endif + + } +#ifdef HLRAD_SPARSEVISMATRIX_FAST + free (uncompressedcolumn); +#endif +} + +#ifdef SYSTEM_WIN32 +#pragma warning(pop) +#endif + +/* + * ============== + * BuildVisMatrix + * ============== + */ +static void BuildVisMatrix() +{ + s_vismatrix = (sparse_column_t*)AllocBlock(g_num_patches * sizeof(sparse_column_t)); + + if (!s_vismatrix) + { + Log("Failed to allocate vismatrix"); + hlassume(s_vismatrix != NULL, assume_NoMemory); + } + +#ifdef HLRAD_VIS_FIX + NamedRunThreadsOn(g_dmodels[0].visleafs, g_estimate, BuildVisLeafs); +#else + NamedRunThreadsOn(g_numleafs - 1, g_estimate, BuildVisLeafs); +#endif +} + +static void FreeVisMatrix() +{ + if (s_vismatrix) + { + unsigned x; + sparse_column_t* item; + + for (x = 0, item = s_vismatrix; x < g_num_patches; x++, item++) + { + if (item->row) + { + free(item->row); + } + } + if (FreeBlock(s_vismatrix)) + { + s_vismatrix = NULL; + } + else + { + Warning("Unable to free vismatrix"); + } + } + +#ifndef HLRAD_TRANSPARENCY_CPP +#ifdef HLRAD_HULLU + if(s_transparency_list) + { + free(s_transparency_list); + s_transparency_list = NULL; + } + s_transparency_count = s_max_transparency_count = 0; +#endif +#endif + +} + +static void DumpVismatrixInfo() +{ + unsigned totals[8]; +#ifdef ZHLT_64BIT_FIX + size_t total_vismatrix_memory; +#else + unsigned total_vismatrix_memory; +#endif + total_vismatrix_memory = sizeof(sparse_column_t) * g_num_patches; + + sparse_column_t* column_end = s_vismatrix + g_num_patches; + sparse_column_t* column = s_vismatrix; + + memset(totals, 0, sizeof(totals)); + + while (column < column_end) + { + total_vismatrix_memory += column->count * sizeof(sparse_row_t); + column++; + } + + Log("%-20s: %5.1f megs\n", "visibility matrix", total_vismatrix_memory / (1024 * 1024.0)); +} + +// +// end old vismat.c +//////////////////////////// + +void MakeScalesSparseVismatrix() +{ + char transferfile[_MAX_PATH]; + + hlassume(g_num_patches < MAX_SPARSE_VISMATRIX_PATCHES, assume_MAX_PATCHES); + +#ifdef ZHLT_DEFAULTEXTENSION_FIX + safe_snprintf(transferfile, _MAX_PATH, "%s.inc", g_Mapname); +#else + safe_strncpy(transferfile, g_source, _MAX_PATH); + StripExtension(transferfile); + DefaultExtension(transferfile, ".inc"); +#endif + + if (!g_incremental || !readtransfers(transferfile, g_num_patches)) + { + // determine visibility between g_patches + BuildVisMatrix(); + DumpVismatrixInfo(); + g_CheckVisBit = CheckVisBitSparse; + +#ifdef HLRAD_HULLU + #ifdef HLRAD_TRANSPARENCY_CPP + CreateFinalTransparencyArrays("custom shadow array"); + #else + if((s_max_transparency_count*sizeof(transparency_t))>=(1024 * 1024)) + Log("%-20s: %5.1f megs\n", "custom shadow array", (s_max_transparency_count*sizeof(transparency_t)) / (1024 * 1024.0)); + else if(s_transparency_count) + Log("%-20s: %5.1f kilos\n", "custom shadow array", (s_max_transparency_count*sizeof(transparency_t)) / 1024.0); + #endif +#endif + +#ifndef HLRAD_HULLU + NamedRunThreadsOn(g_num_patches, g_estimate, MakeScales); +#else + if(g_rgb_transfers) + {NamedRunThreadsOn(g_num_patches, g_estimate, MakeRGBScales);} + else + {NamedRunThreadsOn(g_num_patches, g_estimate, MakeScales);} +#endif + FreeVisMatrix(); +#ifdef HLRAD_HULLU + #ifdef HLRAD_TRANSPARENCY_CPP + FreeTransparencyArrays(); + #endif +#endif + +#ifndef HLRAD_NOSWAP + // invert the transfers for gather vs scatter +#ifndef HLRAD_HULLU + NamedRunThreadsOnIndividual(g_num_patches, g_estimate, SwapTransfers); +#else + if(g_rgb_transfers) + {NamedRunThreadsOnIndividual(g_num_patches, g_estimate, SwapRGBTransfers);} + else + {NamedRunThreadsOnIndividual(g_num_patches, g_estimate, SwapTransfers);} +#endif +#endif /*HLRAD_NOSWAP*/ + if (g_incremental) + { + writetransfers(transferfile, g_num_patches); + } + else + { + _unlink(transferfile); + } + // release visibility matrix + DumpTransfersMemoryUsage(); +#ifdef HLRAD_OPAQUE_STYLE_BOUNCE + CreateFinalStyleArrays ("dynamic shadow array"); +#endif + } +} diff --git a/src/zhlt-vluzacn/hlrad/trace.cpp b/src/zhlt-vluzacn/hlrad/trace.cpp new file mode 100644 index 0000000..27196e4 --- /dev/null +++ b/src/zhlt-vluzacn/hlrad/trace.cpp @@ -0,0 +1,1108 @@ +#include "cmdlib.h" +#include "mathlib.h" +#include "bspfile.h" +#include "log.h" //--vluzacn +#ifdef HLRAD_OPAQUE_NODE +#include "winding.h" +#endif +#ifdef HLRAD_OPAQUE_ALPHATEST +#include "qrad.h" +#endif + +// #define ON_EPSILON 0.001 + +typedef struct tnode_s +{ + planetypes type; + vec3_t normal; + float dist; + int children[2]; + int pad; +} tnode_t; + +static tnode_t* tnodes; +static tnode_t* tnode_p; + +/* + * ============== + * MakeTnode + * + * Converts the disk node structure into the efficient tracing structure + * ============== + */ +static void MakeTnode(const int nodenum) +{ + tnode_t* t; + dplane_t* plane; + int i; + dnode_t* node; + + t = tnode_p++; + + node = g_dnodes + nodenum; + plane = g_dplanes + node->planenum; + + t->type = plane->type; + VectorCopy(plane->normal, t->normal); +#ifdef ZHLT_PLANETYPE_FIX //debug + if (plane->normal[(plane->type)%3] < 0) + if (plane->type < 3) + Warning ("MakeTnode: negative plane"); + else + Developer (DEVELOPER_LEVEL_MESSAGE, "Warning: MakeTnode: negative plane\n"); +#endif + t->dist = plane->dist; + + for (i = 0; i < 2; i++) + { + if (node->children[i] < 0) + t->children[i] = g_dleafs[-node->children[i] - 1].contents; + else + { + t->children[i] = tnode_p - tnodes; + MakeTnode(node->children[i]); + } + } + +} + +/* + * ============= + * MakeTnodes + * + * Loads the node structure out of a .bsp file to be used for light occlusion + * ============= + */ +#if 0 // turn on for debugging. --vluzacn +#include + #define PERR(bSuccess, api){if(!(bSuccess)) printf("%s:Error %d from %s \ + on line %d\n", __FILE__, GetLastError(), api, __LINE__);} + void cls( HANDLE hConsole ) + { + COORD coordScreen = { 0, 0 }; /* here's where we'll home the + cursor */ + BOOL bSuccess; + DWORD cCharsWritten; + CONSOLE_SCREEN_BUFFER_INFO csbi; /* to get buffer info */ + DWORD dwConSize; /* number of character cells in + the current buffer */ + + /* get the number of character cells in the current buffer */ + + bSuccess = GetConsoleScreenBufferInfo( hConsole, &csbi ); + PERR( bSuccess, "GetConsoleScreenBufferInfo" ); + dwConSize = csbi.dwSize.X * csbi.dwSize.Y; + + /* fill the entire screen with blanks */ + + bSuccess = FillConsoleOutputCharacter( hConsole, (TCHAR) ' ', + dwConSize, coordScreen, &cCharsWritten ); + PERR( bSuccess, "FillConsoleOutputCharacter" ); + + /* get the current text attribute */ + + bSuccess = GetConsoleScreenBufferInfo( hConsole, &csbi ); + PERR( bSuccess, "ConsoleScreenBufferInfo" ); + + /* now set the buffer's attributes accordingly */ + + bSuccess = FillConsoleOutputAttribute( hConsole, csbi.wAttributes, + dwConSize, coordScreen, &cCharsWritten ); + PERR( bSuccess, "FillConsoleOutputAttribute" ); + + /* put the cursor at (0, 0) */ + + bSuccess = SetConsoleCursorPosition( hConsole, coordScreen ); + PERR( bSuccess, "SetConsoleCursorPosition" ); + return; + } +#include +void ViewTNode () +{ + int nodecount; + int nodes[128]; + int parents[128]; + int selection; + int linestart; + nodes[0] = 0; + parents[0] = -1; + nodecount = 1; + selection = 0; + linestart = 0; + HANDLE hConsole = CreateFile("CONOUT$", GENERIC_WRITE|GENERIC_READ, FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, 0); + while (1) + { + cls (hConsole); + cprintf ("\nTNodes:\n"); + { + int i; + for (i=0; i=linestart && i "); + else + cprintf ("%3d", i); + for (j=i; j=parents[j], j>=0;) + cprintf ("|"); + if (nodes[i] == CONTENTS_SOLID) + cprintf ("SOLID"); + else if (nodes[i] == CONTENTS_SKY) + cprintf ("SKY"); + else if (nodes[i] < 0) + cprintf ("EMPTY (%d)", nodes[i]); + else + { + cprintf ("NODE %d", nodes[i]); + tnode_t* tnode; + tnode = &tnodes[nodes[i]]; + planetypes d[6] = {plane_x, plane_y, plane_z, plane_anyx, plane_anyy, plane_anyz}; + char s[6][16] = {"_X", "_Y", "_Z", "anyX", "anyY", "anyZ"}; + int k; + for (k=0; k<6; k++) + if (tnode->type == d[k]) + { + cprintf (" %s", s[k]); + break; + } + if (k == 6) + cprintf (" type=%d", tnode->type); + cprintf (" N,D=(%g,%g,%g),%g", tnode->normal[0], tnode->normal[1], tnode->normal[2], tnode->dist); + cprintf (" C=(%d,%d)", tnode->children[0], tnode->children[1]); + cprintf (" pad=%d", tnode->pad); + } + cprintf ("\n"); + } + } + int c; + while (1) + { + switch (c=getch ()) + { + case 'q': + exit (0); + break; + case 224: + break; + case 72: + cprintf ("KEY: UP\n"); + selection--; + if (selection < 0) + selection = 0; + break; + case 80: + cprintf ("KEY: DOWN\n"); + selection++; + if (selection > nodecount-1) + selection = nodecount-1; + break; + case 73: + cprintf ("KEY: PGUP\n"); + linestart -= 10; + if (linestart < 0) + linestart = 0; + if (linestart >= nodecount) + linestart = nodecount /10 *10; + break; + case 81: + cprintf ("KEY: PGDOWN\n"); + linestart += 10; + if (linestart < 0) + linestart = 0; + if (linestart >= nodecount) + linestart = nodecount /10 *10; + break; + case 75: + cprintf ("KEY: LEFT\n"); + { + int i, j; + for (i=0; i=0;) + if (j==selection) + break; + if (j>=0) + break; + } + if (i>=nodecount) + { + if (parents[selection] >= 0) + selection = parents[selection]; + break; + } + } + { + int map[128]; + int mcount = 0; + int i; + int j; + for (i=0; i=0;) + if (j==selection) + map[i] = -1; + if (map[i] >= 0) + mcount++; + } + for (i=0; i= 0) + { + nodes[map[i]] = nodes[i]; + if (parents[i] >= 0) + parents[map[i]] = map[parents[i]]; + } + } + nodecount = mcount; + selection = map[selection]; + } + break; + case 77: + cprintf ("KEY: RIGHT\n"); + { + if (nodes[selection] < 0) + break; + int j; + for (j=0; j= 0 && parents[j] == selection) + break; + if (j= 0 && parents[j] > selection) + parents[j] += 2; + for (j = nodecount-1; j > selection; j--) + { + nodes[j+2] = nodes[j]; + parents[j+2] = parents[j]; + } + nodecount += 2; + nodes[selection+1] = tnode->children[0]; + nodes[selection+2] = tnode->children[1]; + parents[selection+1] = selection; + parents[selection+2] = selection; + } + break; + default: + cprintf ("KEY: U%d\n", c); + continue; + } + break; + } + } +} +#endif +void MakeTnodes(dmodel_t* /*bm*/) +{ + // 32 byte align the structs + tnodes = (tnode_t*)calloc((g_numnodes + 1), sizeof(tnode_t)); + + // The alignment doesn't have any effect at all. --vluzacn +#ifdef ZHLT_64BIT_FIX + int ofs = 31 - (int)(((uintptr_t)tnodes + (uintptr_t)31) & (uintptr_t)31); + tnodes = (tnode_t *)((byte *)tnodes + ofs); +#else + tnodes = (tnode_t*)(((int)tnodes + 31) & ~31); +#endif + tnode_p = tnodes; + + MakeTnode(0); +#if 0 //debug. vluzacn + ViewTNode (); +#endif +} + +//========================================================== + +int TestLine_r(const int node, const vec3_t start, const vec3_t stop +#ifdef HLRAD_WATERBLOCKLIGHT + , int &linecontent +#endif +#ifdef HLRAD_OPAQUEINSKY_FIX + , vec_t *skyhit +#endif + ) +{ + tnode_t* tnode; + float front, back; + vec3_t mid; + float frac; + int side; + int r; + +#ifdef HLRAD_WATERBLOCKLIGHT + if (node < 0) + { + if (node == linecontent) + return CONTENTS_EMPTY; +#ifdef HLRAD_OPAQUEINSKY_FIX + if (node == CONTENTS_SOLID) + { + return CONTENTS_SOLID; + } + if (node == CONTENTS_SKY) + { + if (skyhit) + { + VectorCopy (start, skyhit); + } + return CONTENTS_SKY; + } +#else + if (node == CONTENTS_SOLID || node == CONTENTS_SKY) + return node; +#endif + if (linecontent) + { + return CONTENTS_SOLID; + } + linecontent = node; + return CONTENTS_EMPTY; + } +#else +#ifdef HLRAD_OPAQUEINSKY_FIX + if (node == CONTENTS_SKY) + { + VectorCopy (start, skyhit); + } +#endif + if ( (node == CONTENTS_SOLID) + || (node == CONTENTS_SKY ) + /*|| (node == CONTENTS_NULL ) */ + ) + return node; + + if (node < 0) + return CONTENTS_EMPTY; +#endif + + tnode = &tnodes[node]; + switch (tnode->type) + { + case plane_x: + front = start[0] - tnode->dist; + back = stop[0] - tnode->dist; + break; + case plane_y: + front = start[1] - tnode->dist; + back = stop[1] - tnode->dist; + break; + case plane_z: + front = start[2] - tnode->dist; + back = stop[2] - tnode->dist; + break; + default: + front = (start[0] * tnode->normal[0] + start[1] * tnode->normal[1] + start[2] * tnode->normal[2]) - tnode->dist; + back = (stop[0] * tnode->normal[0] + stop[1] * tnode->normal[1] + stop[2] * tnode->normal[2]) - tnode->dist; + break; + } + +#ifdef HLRAD_TestLine_EDGE_FIX + if (front > ON_EPSILON/2 && back > ON_EPSILON/2) + { + return TestLine_r(tnode->children[0], start, stop +#ifdef HLRAD_WATERBLOCKLIGHT + , linecontent +#endif +#ifdef HLRAD_OPAQUEINSKY_FIX + , skyhit +#endif + ); + } + if (front < -ON_EPSILON/2 && back < -ON_EPSILON/2) + { + return TestLine_r(tnode->children[1], start, stop +#ifdef HLRAD_WATERBLOCKLIGHT + , linecontent +#endif +#ifdef HLRAD_OPAQUEINSKY_FIX + , skyhit +#endif + ); + } + if (fabs(front) <= ON_EPSILON && fabs(back) <= ON_EPSILON) + { + int r1 = TestLine_r(tnode->children[0], start, stop +#ifdef HLRAD_WATERBLOCKLIGHT + , linecontent +#endif +#ifdef HLRAD_OPAQUEINSKY_FIX + , skyhit +#endif + ); + if (r1 == CONTENTS_SOLID) + return CONTENTS_SOLID; + int r2 = TestLine_r(tnode->children[1], start, stop +#ifdef HLRAD_WATERBLOCKLIGHT + , linecontent +#endif +#ifdef HLRAD_OPAQUEINSKY_FIX + , skyhit +#endif + ); + if (r2 == CONTENTS_SOLID) + return CONTENTS_SOLID; + if (r1 == CONTENTS_SKY || r2 == CONTENTS_SKY) + return CONTENTS_SKY; + return CONTENTS_EMPTY; + } + side = (front - back) < 0; + frac = front / (front - back); + if (frac < 0) frac = 0; + if (frac > 1) frac = 1; + mid[0] = start[0] + (stop[0] - start[0]) * frac; + mid[1] = start[1] + (stop[1] - start[1]) * frac; + mid[2] = start[2] + (stop[2] - start[2]) * frac; + r = TestLine_r(tnode->children[side], start, mid +#ifdef HLRAD_WATERBLOCKLIGHT + , linecontent +#endif +#ifdef HLRAD_OPAQUEINSKY_FIX + , skyhit +#endif + ); + if (r != CONTENTS_EMPTY) + return r; + return TestLine_r(tnode->children[!side], mid, stop +#ifdef HLRAD_WATERBLOCKLIGHT + , linecontent +#endif +#ifdef HLRAD_OPAQUEINSKY_FIX + , skyhit +#endif + ); +#else //bug: light can go through edges of solid brushes + if (front >= -ON_EPSILON && back >= -ON_EPSILON) + return TestLine_r(tnode->children[0], start, stop +#ifdef HLRAD_WATERBLOCKLIGHT + , linecontent +#endif +#ifdef HLRAD_OPAQUEINSKY_FIX + , skyhit +#endif + ); + + if (front < ON_EPSILON && back < ON_EPSILON) + return TestLine_r(tnode->children[1], start, stop +#ifdef HLRAD_WATERBLOCKLIGHT + , linecontent +#endif +#ifdef HLRAD_OPAQUEINSKY_FIX + , skyhit +#endif + ); + + side = front < 0; + + frac = front / (front - back); + + mid[0] = start[0] + (stop[0] - start[0]) * frac; + mid[1] = start[1] + (stop[1] - start[1]) * frac; + mid[2] = start[2] + (stop[2] - start[2]) * frac; + + r = TestLine_r(tnode->children[side], start, mid +#ifdef HLRAD_WATERBLOCKLIGHT + , linecontent +#endif +#ifdef HLRAD_OPAQUEINSKY_FIX + , skyhit +#endif + ); + if (r != CONTENTS_EMPTY) + return r; + return TestLine_r(tnode->children[!side], mid, stop +#ifdef HLRAD_WATERBLOCKLIGHT + , linecontent +#endif +#ifdef HLRAD_OPAQUEINSKY_FIX + , skyhit +#endif + ); +#endif +} + +int TestLine(const vec3_t start, const vec3_t stop +#ifdef HLRAD_OPAQUEINSKY_FIX + , vec_t *skyhit +#endif + ) +{ +#ifdef HLRAD_WATERBLOCKLIGHT + int linecontent = 0; +#endif + return TestLine_r(0, start, stop +#ifdef HLRAD_WATERBLOCKLIGHT + , linecontent +#endif +#ifdef HLRAD_OPAQUEINSKY_FIX + , skyhit +#endif + ); +} + +#ifdef HLRAD_OPAQUE_NODE + +typedef struct +{ + Winding *winding; + dplane_t plane; + int numedges; + dplane_t *edges; +#ifdef HLRAD_OPAQUE_ALPHATEST + int texinfo; + bool tex_alphatest; + vec_t tex_vecs[2][4]; + int tex_width; + int tex_height; + const byte *tex_canvas; +#endif +} opaqueface_t; +opaqueface_t *opaquefaces; + +typedef struct opaquenode_s +{ + planetypes type; + vec3_t normal; + vec_t dist; + int children[2]; + int firstface; + int numfaces; +} opaquenode_t; +opaquenode_t *opaquenodes; + +#ifndef OPAQUE_NODE_INLINECALL +typedef struct +{ + vec3_t mins, maxs; + int headnode; +} opaquemodel_t; +#endif +opaquemodel_t *opaquemodels; + +bool TryMerge (opaqueface_t *f, const opaqueface_t *f2) +{ + if (!f->winding || !f2->winding) + { + return false; + } + if (fabs (f2->plane.dist - f->plane.dist) > ON_EPSILON + || fabs (f2->plane.normal[0] - f->plane.normal[0]) > NORMAL_EPSILON + || fabs (f2->plane.normal[1] - f->plane.normal[1]) > NORMAL_EPSILON + || fabs (f2->plane.normal[2] - f->plane.normal[2]) > NORMAL_EPSILON + ) + { + return false; + } +#ifdef HLRAD_OPAQUE_ALPHATEST + if ((f->tex_alphatest || f2->tex_alphatest) && f->texinfo != f2->texinfo) + { + return false; + } +#endif + + Winding *w = f->winding; + const Winding *w2 = f2->winding; + const vec_t *pA, *pB, *pC, *pD, *p2A, *p2B, *p2C, *p2D; + int i, i2; + + for (i = 0; i < w->m_NumPoints; i++) + { + for (i2 = 0; i2 < w2->m_NumPoints; i2++) + { + pA = w->m_Points[(i+w->m_NumPoints-1)%w->m_NumPoints]; + pB = w->m_Points[i]; + pC = w->m_Points[(i+1)%w->m_NumPoints]; + pD = w->m_Points[(i+2)%w->m_NumPoints]; + p2A = w2->m_Points[(i2+w2->m_NumPoints-1)%w2->m_NumPoints]; + p2B = w2->m_Points[i2]; + p2C = w2->m_Points[(i2+1)%w2->m_NumPoints]; + p2D = w2->m_Points[(i2+2)%w2->m_NumPoints]; + if (!VectorCompare (pB, p2C) || !VectorCompare (pC, p2B)) + { + continue; + } + break; + } + if (i2 == w2->m_NumPoints) + { + continue; + } + break; + } + if (i == w->m_NumPoints) + { + return false; + } + + const vec_t *normal = f->plane.normal; + vec3_t e1, e2; + dplane_t pl1, pl2; + int side1, side2; + + VectorSubtract (p2D, pA, e1); + CrossProduct (normal, e1, pl1.normal); // pointing outward + if (VectorNormalize (pl1.normal) == 0.0) + { + Developer (DEVELOPER_LEVEL_WARNING, "Warning: TryMerge: Empty edge.\n"); + return false; + } + pl1.dist = DotProduct (pA, pl1.normal); + if (DotProduct (pB, pl1.normal) - pl1.dist < -ON_EPSILON) + { + return false; + } + side1 = (DotProduct (pB, pl1.normal) - pl1.dist > ON_EPSILON)? 1: 0; + + VectorSubtract (pD, p2A, e2); + CrossProduct (normal, e2, pl2.normal); // pointing outward + if (VectorNormalize (pl2.normal) == 0.0) + { + Developer (DEVELOPER_LEVEL_WARNING, "Warning: TryMerge: Empty edge.\n"); + return false; + } + pl2.dist = DotProduct (p2A, pl2.normal); + if (DotProduct (p2B, pl2.normal) - pl2.dist < -ON_EPSILON) + { + return false; + } + side2 = (DotProduct (p2B, pl2.normal) - pl2.dist > ON_EPSILON)? 1: 0; + + Winding *neww = new Winding (w->m_NumPoints + w2->m_NumPoints - 4 + side1 + side2); + int j, k; + k = 0; + for (j = (i + 2) % w->m_NumPoints; j != i; j = (j + 1) % w->m_NumPoints) + { + VectorCopy (w->m_Points[j], neww->m_Points[k]); + k++; + } + if (side1) + { + VectorCopy (w->m_Points[j], neww->m_Points[k]); + k++; + } + for (j = (i2 + 2) % w2->m_NumPoints; j != i2; j = (j + 1) % w2->m_NumPoints) + { + VectorCopy (w2->m_Points[j], neww->m_Points[k]); + k++; + } + if (side2) + { + VectorCopy (w2->m_Points[j], neww->m_Points[k]); + k++; + } + neww->RemoveColinearPoints (); + if (neww->m_NumPoints < 3) + { + Developer (DEVELOPER_LEVEL_WARNING, "Warning: TryMerge: Empty winding.\n"); + delete neww; + neww = NULL; + } + delete f->winding; + f->winding = neww; + return true; +} + +int MergeOpaqueFaces (int firstface, int numfaces) +{ + int i, j, newnum; + opaqueface_t *faces = &opaquefaces[firstface]; + for (i = 0; i < numfaces; i++) + { + for (j = 0; j < i; j++) + { + if (TryMerge (&faces[i], &faces[j])) + { + delete faces[j].winding; + faces[j].winding = NULL; + j = -1; + continue; + } + } + } + for (i = 0, j = 0; i < numfaces; i++) + { + if (faces[i].winding) + { + faces[j] = faces[i]; + j++; + } + } + newnum = j; + for (; j < numfaces; j++) + { + memset (&faces[j], 0, sizeof(opaqueface_t)); + } + return newnum; +} + +void BuildFaceEdges (opaqueface_t *f) +{ + if (!f->winding) + return; + f->numedges = f->winding->m_NumPoints; + f->edges = (dplane_t *)calloc (f->numedges, sizeof (dplane_t)); + const vec_t *p1, *p2; + const vec_t *n = f->plane.normal; + vec3_t e; + dplane_t *pl; + int x; + for (x = 0; x < f->winding->m_NumPoints; x++) + { + p1 = f->winding->m_Points[x]; + p2 = f->winding->m_Points[(x+1)%f->winding->m_NumPoints]; + pl = &f->edges[x]; + VectorSubtract (p2, p1, e); + CrossProduct (n, e, pl->normal); + if (VectorNormalize (pl->normal) == 0.0) + { + Developer (DEVELOPER_LEVEL_WARNING, "Warning: BuildFaceEdges: Empty edge.\n"); + VectorClear (pl->normal); + pl->dist = -1; + continue; + } + pl->dist = DotProduct (pl->normal, p1); + } +} + +void CreateOpaqueNodes () +{ + int i, j; + opaquemodels = (opaquemodel_t *)calloc (g_nummodels, sizeof (opaquemodel_t)); + opaquenodes = (opaquenode_t *)calloc (g_numnodes, sizeof (opaquenode_t)); + opaquefaces = (opaqueface_t *)calloc (g_numfaces, sizeof (opaqueface_t)); + for (i = 0; i < g_numfaces; i++) + { + opaqueface_t *of = &opaquefaces[i]; + dface_t *df = &g_dfaces[i]; + of->winding = new Winding (*df); + if (of->winding->m_NumPoints < 3) + { + delete of->winding; + of->winding = NULL; + } + of->plane = g_dplanes[df->planenum]; + if (df->side) + { + VectorInverse (of->plane.normal); + of->plane.dist = -of->plane.dist; + } +#ifdef HLRAD_OPAQUE_ALPHATEST + of->texinfo = df->texinfo; + texinfo_t *info = &g_texinfo[of->texinfo]; + for (j = 0; j < 2; j++) + { + for (int k = 0; k < 4; k++) + { + of->tex_vecs[j][k] = info->vecs[j][k]; + } + } + radtexture_t *tex = &g_textures[info->miptex]; + of->tex_alphatest = tex->name[0] == '{'; + of->tex_width = tex->width; + of->tex_height = tex->height; + of->tex_canvas = tex->canvas; +#endif + } + for (i = 0; i < g_numnodes; i++) + { + opaquenode_t *on = &opaquenodes[i]; + dnode_t *dn = &g_dnodes[i]; + on->type = g_dplanes[dn->planenum].type; + VectorCopy (g_dplanes[dn->planenum].normal, on->normal); + on->dist = g_dplanes[dn->planenum].dist; + on->children[0] = dn->children[0]; + on->children[1] = dn->children[1]; + on->firstface = dn->firstface; + on->numfaces = dn->numfaces; + on->numfaces = MergeOpaqueFaces (on->firstface, on->numfaces); + } + for (i = 0; i < g_numfaces; i++) + { + BuildFaceEdges (&opaquefaces[i]); + } + for (i = 0; i < g_nummodels; i++) + { + opaquemodel_t *om = &opaquemodels[i]; + dmodel_t *dm = &g_dmodels[i]; + om->headnode = dm->headnode[0]; + for (j = 0; j < 3; j++) + { + om->mins[j] = dm->mins[j] - 1; + om->maxs[j] = dm->maxs[j] + 1; + } + } +} + +void DeleteOpaqueNodes () +{ + int i; + for (i = 0; i < g_numfaces; i++) + { + opaqueface_t *of = &opaquefaces[i]; + if (of->winding) + delete of->winding; + if (of->edges) + free (of->edges); + } + free (opaquefaces); + free (opaquenodes); + free (opaquemodels); +} + +int TestLineOpaque_face (int facenum, const vec3_t hit) +{ + opaqueface_t *thisface = &opaquefaces[facenum]; + int x; + if (thisface->numedges == 0) + { + Developer (DEVELOPER_LEVEL_WARNING, "Warning: TestLineOpaque: Empty face.\n"); + return 0; + } + for (x = 0; x < thisface->numedges; x++) + { + if (DotProduct (hit, thisface->edges[x].normal) - thisface->edges[x].dist > ON_EPSILON) + { + return 0; + } + } +#ifdef HLRAD_OPAQUE_ALPHATEST + if (thisface->tex_alphatest) + { + double x, y; + x = DotProduct (hit, thisface->tex_vecs[0]) + thisface->tex_vecs[0][3]; + y = DotProduct (hit, thisface->tex_vecs[1]) + thisface->tex_vecs[1][3]; + x = floor (x - thisface->tex_width * floor (x / thisface->tex_width)); + y = floor (y - thisface->tex_height * floor (y / thisface->tex_height)); + x = x > thisface->tex_width - 1? thisface->tex_width - 1: x < 0? 0: x; + y = y > thisface->tex_height - 1? thisface->tex_height - 1: y < 0? 0: y; + if (thisface->tex_canvas[(int)y * thisface->tex_width + (int)x] == 0xFF) + { + return 0; + } + } +#endif + return 1; +} + +int TestLineOpaque_r (int nodenum, const vec3_t start, const vec3_t stop) +{ + opaquenode_t *thisnode; + vec_t front, back; + if (nodenum < 0) + { + return 0; + } + thisnode = &opaquenodes[nodenum]; + switch (thisnode->type) + { + case plane_x: + front = start[0] - thisnode->dist; + back = stop[0] - thisnode->dist; + break; + case plane_y: + front = start[1] - thisnode->dist; + back = stop[1] - thisnode->dist; + break; + case plane_z: + front = start[2] - thisnode->dist; + back = stop[2] - thisnode->dist; + break; + default: + front = DotProduct (start, thisnode->normal) - thisnode->dist; + back = DotProduct (stop, thisnode->normal) - thisnode->dist; + } + if (front > ON_EPSILON / 2 && back > ON_EPSILON / 2) + { + return TestLineOpaque_r (thisnode->children[0], start, stop); + } + if (front < -ON_EPSILON / 2 && back < -ON_EPSILON / 2) + { + return TestLineOpaque_r (thisnode->children[1], start, stop); + } + if (fabs (front) <= ON_EPSILON && fabs (back) <= ON_EPSILON) + { + return TestLineOpaque_r (thisnode->children[0], start, stop) + || TestLineOpaque_r (thisnode->children[1], start, stop); + } + { + int side; + vec_t frac; + vec3_t mid; + int facenum; + side = (front - back) < 0; + frac = front / (front - back); + if (frac < 0) frac = 0; + if (frac > 1) frac = 1; + mid[0] = start[0] + (stop[0] - start[0]) * frac; + mid[1] = start[1] + (stop[1] - start[1]) * frac; + mid[2] = start[2] + (stop[2] - start[2]) * frac; + for (facenum = thisnode->firstface; facenum < thisnode->firstface + thisnode->numfaces; facenum++) + { + if (TestLineOpaque_face (facenum, mid)) + { + return 1; + } + } + return TestLineOpaque_r (thisnode->children[side], start, mid) + || TestLineOpaque_r (thisnode->children[!side], mid, stop); + } +} + +int TestLineOpaque (int modelnum, const vec3_t modelorigin, const vec3_t start, const vec3_t stop) +{ + opaquemodel_t *thismodel = &opaquemodels[modelnum]; + vec_t front, back, frac; + vec3_t p1, p2; + VectorSubtract (start, modelorigin, p1); + VectorSubtract (stop, modelorigin, p2); + int axial; + for (axial = 0; axial < 3; axial++) + { + front = p1[axial] - thismodel->maxs[axial]; + back = p2[axial] - thismodel->maxs[axial]; + if (front >= -ON_EPSILON && back >= -ON_EPSILON) + { + return 0; + } + if (front > ON_EPSILON || back > ON_EPSILON) + { + frac = front / (front - back); + if (front > back) + { + p1[0] = p1[0] + (p2[0] - p1[0]) * frac; + p1[1] = p1[1] + (p2[1] - p1[1]) * frac; + p1[2] = p1[2] + (p2[2] - p1[2]) * frac; + } + else + { + p2[0] = p1[0] + (p2[0] - p1[0]) * frac; + p2[1] = p1[1] + (p2[1] - p1[1]) * frac; + p2[2] = p1[2] + (p2[2] - p1[2]) * frac; + } + } + front = thismodel->mins[axial] - p1[axial]; + back = thismodel->mins[axial] - p2[axial]; + if (front >= -ON_EPSILON && back >= -ON_EPSILON) + { + return 0; + } + if (front > ON_EPSILON || back > ON_EPSILON) + { + frac = front / (front - back); + if (front > back) + { + p1[0] = p1[0] + (p2[0] - p1[0]) * frac; + p1[1] = p1[1] + (p2[1] - p1[1]) * frac; + p1[2] = p1[2] + (p2[2] - p1[2]) * frac; + } + else + { + p2[0] = p1[0] + (p2[0] - p1[0]) * frac; + p2[1] = p1[1] + (p2[1] - p1[1]) * frac; + p2[2] = p1[2] + (p2[2] - p1[2]) * frac; + } + } + } + return TestLineOpaque_r (thismodel->headnode, p1, p2); +} + +int CountOpaqueFaces_r (opaquenode_t *node) +{ + int count; + count = node->numfaces; + if (node->children[0] >= 0) + { + count += CountOpaqueFaces_r (&opaquenodes[node->children[0]]); + } + if (node->children[1] >= 0) + { + count += CountOpaqueFaces_r (&opaquenodes[node->children[1]]); + } + return count; +} + +int CountOpaqueFaces (int modelnum) +{ + return CountOpaqueFaces_r (&opaquenodes[opaquemodels[modelnum].headnode]); +} + +#ifdef HLRAD_OPAQUE_BLOCK +int TestPointOpaque_r (int nodenum, bool solid, const vec3_t point) +{ + opaquenode_t *thisnode; + vec_t dist; + while (1) + { + if (nodenum < 0) + { + if (solid && g_dleafs[-nodenum-1].contents == CONTENTS_SOLID) + return 1; + else + return 0; + } + thisnode = &opaquenodes[nodenum]; + switch (thisnode->type) + { + case plane_x: + dist = point[0] - thisnode->dist; + break; + case plane_y: + dist = point[1] - thisnode->dist; + break; + case plane_z: + dist = point[2] - thisnode->dist; + break; + default: + dist = DotProduct (point, thisnode->normal) - thisnode->dist; + } + if (dist > HUNT_WALL_EPSILON) + { + nodenum = thisnode->children[0]; + } + else if (dist < -HUNT_WALL_EPSILON) + { + nodenum = thisnode->children[1]; + } + else + { + break; + } + } + { + int facenum; + for (facenum = thisnode->firstface; facenum < thisnode->firstface + thisnode->numfaces; facenum++) + { + if (TestLineOpaque_face (facenum, point)) + { + return 1; + } + } + } + return TestPointOpaque_r (thisnode->children[0], solid, point) + || TestPointOpaque_r (thisnode->children[1], solid, point); +} + +#ifndef OPAQUE_NODE_INLINECALL +int TestPointOpaque (int modelnum, const vec3_t modelorigin, bool solid, const vec3_t point) +{ + opaquemodel_t *thismodel = &opaquemodels[modelnum]; + vec3_t newpoint; + VectorSubtract (point, modelorigin, newpoint); + int axial; + for (axial = 0; axial < 3; axial++) + { + if (newpoint[axial] > thismodel->maxs[axial]) + return 0; + if (newpoint[axial] < thismodel->mins[axial]) + return 0; + } + return TestPointOpaque_r (thismodel->headnode, solid, newpoint); +} +#endif +#endif + +#endif diff --git a/src/zhlt-vluzacn/hlrad/transfers.cpp b/src/zhlt-vluzacn/hlrad/transfers.cpp new file mode 100644 index 0000000..179f17a --- /dev/null +++ b/src/zhlt-vluzacn/hlrad/transfers.cpp @@ -0,0 +1,238 @@ +#include "qrad.h" + +#ifdef SYSTEM_WIN32 +#include +#include +#include "win32fix.h" +#endif + +#ifdef HAVE_SYS_STAT_H +#include +#endif + +/* + * ============= + * writetransfers + * ============= + */ + +void writetransfers(const char* const transferfile, const long total_patches) +{ + FILE *file; + + file = fopen(transferfile, "w+b"); + if (file != NULL) + { + unsigned amtwritten; + patch_t* patch; + + Log("Writing transfers file [%s]\n", transferfile); + + amtwritten = fwrite(&total_patches, sizeof(total_patches), 1, file); + if (amtwritten != 1) + { + goto FailedWrite; + } + + long patchcount = total_patches; + for (patch = g_patches; patchcount-- > 0; patch++) + { + amtwritten = fwrite(&patch->iIndex, sizeof(patch->iIndex), 1, file); + if (amtwritten != 1) + { + goto FailedWrite; + } + + if (patch->iIndex) + { + amtwritten = fwrite(patch->tIndex, sizeof(transfer_index_t), patch->iIndex, file); + if (amtwritten != patch->iIndex) + { + goto FailedWrite; + } + } + + amtwritten = fwrite(&patch->iData, sizeof(patch->iData), 1, file); + if (amtwritten != 1) + { + goto FailedWrite; + } + if (patch->iData) + { +#ifdef HLRAD_HULLU + if(g_rgb_transfers) + { + #ifdef HLRAD_TRANSFERDATA_COMPRESS + amtwritten = fwrite(patch->tRGBData, vector_size[g_rgbtransfer_compress_type], patch->iData, file); + #else + amtwritten = fwrite(patch->tRGBData, sizeof(rgb_transfer_data_t), patch->iData, file); + #endif + } + else + { + #ifdef HLRAD_TRANSFERDATA_COMPRESS + amtwritten = fwrite(patch->tData, float_size[g_transfer_compress_type], patch->iData, file); + #else + amtwritten = fwrite(patch->tData, sizeof(transfer_data_t), patch->iData, file); + #endif + } +#else + #ifdef HLRAD_TRANSFERDATA_COMPRESS + amtwritten = fwrite(patch->tData, float_size[g_transfer_compress_type], patch->iData, file); + #else + amtwritten = fwrite(patch->tData, sizeof(transfer_data_t), patch->iData, file); + #endif +#endif + if (amtwritten != patch->iData) + { + goto FailedWrite; + } + } + } + + fclose(file); + } + else + { + Error("Failed to open incremenetal file [%s] for writing\n", transferfile); + } + return; + + FailedWrite: + fclose(file); + unlink(transferfile); + //Warning("Failed to generate incremental file [%s] (probably ran out of disk space)\n"); + Warning("Failed to generate incremental file [%s] (probably ran out of disk space)\n", transferfile); //--vluzacn +} + +/* + * ============= + * readtransfers + * ============= + */ + +bool readtransfers(const char* const transferfile, const long numpatches) +{ + FILE* file; + long total_patches; + + file = fopen(transferfile, "rb"); + if (file != NULL) + { + unsigned amtread; + patch_t* patch; + + Log("Reading transfers file [%s]\n", transferfile); + + amtread = fread(&total_patches, sizeof(total_patches), 1, file); + if (amtread != 1) + { + goto FailedRead; + } + if (total_patches != numpatches) + { + goto FailedRead; + } + + long patchcount = total_patches; + for (patch = g_patches; patchcount-- > 0; patch++) + { + amtread = fread(&patch->iIndex, sizeof(patch->iIndex), 1, file); + if (amtread != 1) + { + goto FailedRead; + } + if (patch->iIndex) + { + patch->tIndex = (transfer_index_t*)AllocBlock(patch->iIndex * sizeof(transfer_index_t *)); + hlassume(patch->tIndex != NULL, assume_NoMemory); + amtread = fread(patch->tIndex, sizeof(transfer_index_t), patch->iIndex, file); + if (amtread != patch->iIndex) + { + goto FailedRead; + } + } + + amtread = fread(&patch->iData, sizeof(patch->iData), 1, file); + if (amtread != 1) + { + goto FailedRead; + } + if (patch->iData) + { +#ifdef HLRAD_HULLU + if(g_rgb_transfers) + { + #ifdef HLRAD_TRANSFERDATA_COMPRESS + patch->tRGBData = (rgb_transfer_data_t*)AllocBlock(patch->iData * vector_size[g_rgbtransfer_compress_type] + unused_size); + #else + patch->tRGBData = (rgb_transfer_data_t*)AllocBlock(patch->iData * sizeof(rgb_transfer_data_t *)); //wrong? --vluzacn + #endif + hlassume(patch->tRGBData != NULL, assume_NoMemory); + #ifdef HLRAD_TRANSFERDATA_COMPRESS + amtread = fread(patch->tRGBData, vector_size[g_rgbtransfer_compress_type], patch->iData, file); + #else + amtread = fread(patch->tRGBData, sizeof(rgb_transfer_data_t), patch->iData, file); + #endif + } + else + { + #ifdef HLRAD_TRANSFERDATA_COMPRESS + patch->tData = (transfer_data_t*)AllocBlock(patch->iData * float_size[g_transfer_compress_type] + unused_size); + #else + patch->tData = (transfer_data_t*)AllocBlock(patch->iData * sizeof(transfer_data_t *)); + #endif + hlassume(patch->tData != NULL, assume_NoMemory); + #ifdef HLRAD_TRANSFERDATA_COMPRESS + amtread = fread(patch->tData, float_size[g_transfer_compress_type], patch->iData, file); + #else + amtread = fread(patch->tData, sizeof(transfer_data_t), patch->iData, file); + #endif + } +#else + #ifdef HLRAD_TRANSFERDATA_COMPRESS + patch->tData = (transfer_data_t*)AllocBlock(patch->iData * float_size[g_transfer_compress_type] + unused_size); + #else + patch->tData = (transfer_data_t*)AllocBlock(patch->iData * sizeof(transfer_data_t *)); + #endif + hlassume(patch->tData != NULL, assume_NoMemory); + #ifdef HLRAD_TRANSFERDATA_COMPRESS + amtread = fread(patch->tData, float_size[g_transfer_compress_type], patch->iData, file); + #else + amtread = fread(patch->tData, sizeof(transfer_data_t), patch->iData, file); + #endif +#endif + if (amtread != patch->iData) + { + goto FailedRead; + } + } + } + + fclose(file); + //Warning("Finished reading transfers file [%s] %d\n", transferfile); + Warning("Finished reading transfers file [%s]\n", transferfile); //--vluzacn + return true; + } + Warning("Failed to open transfers file [%s]\n", transferfile); + return false; + + FailedRead: + { + unsigned x; + patch_t* patch = g_patches; + + for (x = 0; x < g_num_patches; x++, patch++) + { + FreeBlock(patch->tData); + FreeBlock(patch->tIndex); + patch->iData = 0; + patch->iIndex = 0; + patch->tData = NULL; + patch->tIndex = NULL; + } + } + fclose(file); + unlink(transferfile); + return false; +} diff --git a/src/zhlt-vluzacn/hlrad/transparency.cpp b/src/zhlt-vluzacn/hlrad/transparency.cpp new file mode 100644 index 0000000..2b82a09 --- /dev/null +++ b/src/zhlt-vluzacn/hlrad/transparency.cpp @@ -0,0 +1,452 @@ +#pragma warning(disable: 4018) //amckern - 64bit - '<' Singed/Unsigned Mismatch + +// +// Transparency Arrays for sparse and vismatrix methods +// +#include "qrad.h" + +#ifdef HLRAD_HULLU + +#ifndef HLRAD_TRANSPARENCY_FAST +#define TRANS_LIST_GROWTH 64 +#define RAW_LIST_GROWTH 2048 +#endif + +typedef struct { + unsigned p1; + unsigned p2; + unsigned data_index; +} transList_t; + +static vec3_t * s_trans_list = NULL; +static unsigned int s_trans_count = 0; +static unsigned int s_max_trans_count = 0; + +static transList_t* s_raw_list = NULL; +static unsigned int s_raw_count = 0; +static unsigned int s_max_raw_count = 0; // Current array maximum (used for reallocs) + +static transList_t* s_sorted_list = NULL; // Sorted first by p1 then p2 +static unsigned int s_sorted_count = 0; + +const vec3_t vec3_one = {1.0,1.0,1.0}; + +//=============================================== +// AddTransparencyToRawArray +//=============================================== +static unsigned AddTransparencyToDataList(const vec3_t trans) +{ + //Check if this value is in list already +#ifdef ZHLT_64BIT_FIX + for(unsigned int i = 0; i < s_trans_count; i++) +#else + for(int i = 0; i < s_trans_count; i++) +#endif + { + if( VectorCompare( trans, s_trans_list[i] ) ) + { + return i; + } + } + + //realloc if needed + while( s_trans_count >= s_max_trans_count ) + { + unsigned int old_max_count = s_max_trans_count; +#ifdef HLRAD_TRANSPARENCY_FAST + s_max_trans_count = qmax (64u, (unsigned int)((double)s_max_trans_count * 1.41)); +#else + s_max_trans_count += TRANS_LIST_GROWTH; +#endif +#ifdef ZHLT_64BIT_FIX + if (s_max_trans_count >= (unsigned int)INT_MAX) + { + Error ("AddTransparencyToDataList: array size exceeded INT_MAX"); + } +#endif + + s_trans_list = (vec3_t *)realloc( s_trans_list, sizeof(vec3_t) * s_max_trans_count ); +#ifdef HLRAD_HLASSUMENOMEMORY + hlassume (s_trans_list != NULL, assume_NoMemory); +#endif + +#ifdef HLRAD_TRANSPARENCY_FAST + memset( &s_trans_list[old_max_count], 0, sizeof(vec3_t) * (s_max_trans_count - old_max_count) ); +#else + memset( &s_trans_list[old_max_count], 0, sizeof(vec3_t) * TRANS_LIST_GROWTH ); +#endif + + if( old_max_count == 0 ) + { + VectorFill(s_trans_list[0], 1.0); + s_trans_count++; + } + } + + VectorCopy(trans, s_trans_list[s_trans_count]); + + return ( s_trans_count++ ); +} + +//=============================================== +// AddTransparencyToRawArray +//=============================================== +void AddTransparencyToRawArray(const unsigned p1, const unsigned p2, const vec3_t trans) +{ + //make thread safe + ThreadLock(); + + unsigned data_index = AddTransparencyToDataList(trans); + + //realloc if needed + while( s_raw_count >= s_max_raw_count ) + { + unsigned int old_max_count = s_max_raw_count; +#ifdef HLRAD_TRANSPARENCY_FAST + s_max_raw_count = qmax (64u, (unsigned int)((double)s_max_raw_count * 1.41)); +#else + s_max_raw_count += RAW_LIST_GROWTH; +#endif +#ifdef ZHLT_64BIT_FIX + if (s_max_raw_count >= (unsigned int)INT_MAX) + { + Error ("AddTransparencyToRawArray: array size exceeded INT_MAX"); + } +#endif + + s_raw_list = (transList_t *)realloc( s_raw_list, sizeof(transList_t) * s_max_raw_count ); +#ifdef HLRAD_HLASSUMENOMEMORY + hlassume (s_raw_list != NULL, assume_NoMemory); +#endif + +#ifdef HLRAD_TRANSPARENCY_FAST + memset( &s_raw_list[old_max_count], 0, sizeof(transList_t) * (s_max_raw_count - old_max_count) ); +#else + memset( &s_raw_list[old_max_count], 0, sizeof(transList_t) * RAW_LIST_GROWTH ); +#endif + } + + s_raw_list[s_raw_count].p1 = p1; + s_raw_list[s_raw_count].p2 = p2; + s_raw_list[s_raw_count].data_index = data_index; + + s_raw_count++; + + //unlock list + ThreadUnlock(); +} + +//=============================================== +// SortList +//=============================================== +static int CDECL SortList(const void *a, const void *b) +{ + const transList_t* item1 = (transList_t *)a; + const transList_t* item2 = (transList_t *)b; + + if( item1->p1 == item2->p1 ) + { + return item1->p2 - item2->p2; + } + else + { + return item1->p1 - item2->p1; + } +} + +//=============================================== +// CreateFinalTransparencyArrays +//=============================================== +void CreateFinalTransparencyArrays(const char *print_name) +{ + if( s_raw_count == 0 ) + { + s_raw_list = NULL; + s_raw_count = s_max_raw_count = 0; + return; + } + + //double sized (faster find function for sorted list) + s_sorted_count = s_raw_count * 2; + s_sorted_list = (transList_t *)malloc( sizeof(transList_t) * s_sorted_count ); +#ifdef HLRAD_HLASSUMENOMEMORY + hlassume (s_sorted_list != NULL, assume_NoMemory); +#endif + + //First half have p1>p2 + for( unsigned int i = 0; i < s_raw_count; i++ ) + { + s_sorted_list[i].p1 = s_raw_list[i].p2; + s_sorted_list[i].p2 = s_raw_list[i].p1; + s_sorted_list[i].data_index = s_raw_list[i].data_index; + } + //Second half have p1 1024 * 1024 ) + Log("%-20s: %5.1f megs \n", print_name, (double)size / (1024.0 * 1024.0)); + else if ( size > 1024 ) + Log("%-20s: %5.1f kilos\n", print_name, (double)size / 1024.0); + else + Log("%-20s: %5.1f bytes\n", print_name, (double)size); //--vluzacn + Developer (DEVELOPER_LEVEL_MESSAGE, "\ts_trans_count=%d\ts_sorted_count=%d\n", s_trans_count, s_sorted_count); //--vluzacn + +#if 0 + int total_1 = 0; + for(int i = 0; i < s_sorted_count; i++) + { + Log("a: %7i b: %7i di: %10i r: %3.1f g: %3.1f b: %3.1f\n", + s_sorted_list[i].p1, + s_sorted_list[i].p2, + s_sorted_list[i].data_index, + s_trans_list[s_sorted_list[i].data_index][0], + s_trans_list[s_sorted_list[i].data_index][1], + s_trans_list[s_sorted_list[i].data_index][2] + ); + total_1++; + } + + vec3_t rgb; + int total_2 = 0; + for(unsigned int next_index = 0, a = 0; a < g_num_patches; a++) + { + for(unsigned int b = 0; b < g_num_patches; b++) + { + GetTransparency(a, b, rgb, next_index); + + if(!VectorCompare(rgb,vec3_one)) + { + Log("a: %7i b: %7i ni: %10i r: %3.1f g: %3.1f b: %3.1f\n", + a, + b, + next_index, + rgb[0], + rgb[1], + rgb[2] + ); + total_2++; + } + } + } + + Log("total1: %i\ntotal2: %i\n",total_1,total_2); +#endif +} + +//=============================================== +// FreeTransparencyArrays +//=============================================== +void FreeTransparencyArrays( ) +{ + if (s_sorted_list) free(s_sorted_list); + if (s_trans_list) free(s_trans_list); + + s_trans_list = NULL; + s_sorted_list = NULL; + + s_max_trans_count = s_trans_count = s_sorted_count = 0; +} + +//=============================================== +// GetTransparency -- find transparency from list. remembers last location +//=============================================== +void GetTransparency(const unsigned p1, const unsigned p2, vec3_t &trans, unsigned int &next_index) +{ + VectorFill( trans, 1.0 ); + + for( unsigned i = next_index; i < s_sorted_count; i++ ) + { + if ( s_sorted_list[i].p1 < p1 ) + { + continue; + } + else if ( s_sorted_list[i].p1 == p1 ) + { + if ( s_sorted_list[i].p2 < p2 ) + { + continue; + } + else if ( s_sorted_list[i].p2 == p2 ) + { + VectorCopy( s_trans_list[s_sorted_list[i].data_index], trans ); + next_index = i + 1; + + return; + } + else //if ( s_sorted_list[i].p2 > p2 ) + { + next_index = i; + + return; + } + } + else //if ( s_sorted_list[i].p1 > p1 ) + { + next_index = i; + + return; + } + } + + next_index = s_sorted_count; +} + + +#endif /*HLRAD_HULLU*/ + + + + +#ifdef HLRAD_OPAQUE_STYLE_BOUNCE +#ifndef HLRAD_TRANSPARENCY_FAST +#define STYLE_LIST_GROWTH 2048 +#endif +typedef struct { + unsigned p1; + unsigned p2; + char style; +} styleList_t; +static styleList_t* s_style_list = NULL; +static unsigned int s_style_count = 0; +static unsigned int s_max_style_count = 0; +void AddStyleToStyleArray(const unsigned p1, const unsigned p2, const int style) +{ + if (style == -1) + return; + //make thread safe + ThreadLock(); + + //realloc if needed + while( s_style_count >= s_max_style_count ) + { + unsigned int old_max_count = s_max_style_count; +#ifdef HLRAD_TRANSPARENCY_FAST + s_max_style_count = qmax (64u, (unsigned int)((double)s_max_style_count * 1.41)); +#else + s_max_style_count += STYLE_LIST_GROWTH; +#endif +#ifdef ZHLT_64BIT_FIX + if (s_max_style_count >= (unsigned int)INT_MAX) + { + Error ("AddStyleToStyleArray: array size exceeded INT_MAX"); + } +#endif + + s_style_list = (styleList_t *)realloc( s_style_list, sizeof(styleList_t) * s_max_style_count ); +#ifdef HLRAD_HLASSUMENOMEMORY + hlassume (s_style_list != NULL, assume_NoMemory); +#endif + +#ifdef HLRAD_TRANSPARENCY_FAST + memset( &s_style_list[old_max_count], 0, sizeof(styleList_t) * (s_max_style_count - old_max_count) ); +#else + memset( &s_style_list[old_max_count], 0, sizeof(styleList_t) * STYLE_LIST_GROWTH ); +#endif + } + + s_style_list[s_style_count].p1 = p1; + s_style_list[s_style_count].p2 = p2; + s_style_list[s_style_count].style = (char)style; + + s_style_count++; + + //unlock list + ThreadUnlock(); +} +static int CDECL SortStyleList(const void *a, const void *b) +{ + const styleList_t* item1 = (styleList_t *)a; + const styleList_t* item2 = (styleList_t *)b; + + if( item1->p1 == item2->p1 ) + { + return item1->p2 - item2->p2; + } + else + { + return item1->p1 - item2->p1; + } +} +void CreateFinalStyleArrays(const char *print_name) +{ + if( s_style_count == 0 ) + { + return; + } + //need to sorted for fast search function + qsort( s_style_list, s_style_count, sizeof(styleList_t), SortStyleList ); + +#ifdef ZHLT_64BIT_FIX + size_t size = s_max_style_count * sizeof(styleList_t); +#else + unsigned size = s_max_style_count * sizeof(styleList_t); +#endif + if ( size > 1024 * 1024 ) + Log("%-20s: %5.1f megs \n", print_name, (double)size / (1024.0 * 1024.0)); + else if ( size > 1024 ) + Log("%-20s: %5.1f kilos\n", print_name, (double)size / 1024.0); + else + Log("%-20s: %5.1f bytes\n", print_name, (double)size); //--vluzacn +} +void FreeStyleArrays( ) +{ + if (s_style_count) free(s_style_list); + + s_style_list = NULL; + + s_max_style_count = s_style_count = 0; +} +void GetStyle(const unsigned p1, const unsigned p2, int &style, unsigned int &next_index) +{ + style = -1; + + for( unsigned i = next_index; i < s_style_count; i++ ) + { + if ( s_style_list[i].p1 < p1 ) + { + continue; + } + else if ( s_style_list[i].p1 == p1 ) + { + if ( s_style_list[i].p2 < p2 ) + { + continue; + } + else if ( s_style_list[i].p2 == p2 ) + { + style = (int)s_style_list[i].style; + next_index = i + 1; + + return; + } + else //if ( s_style_list[i].p2 > p2 ) + { + next_index = i; + + return; + } + } + else //if ( s_style_list[i].p1 > p1 ) + { + next_index = i; + + return; + } + } + + next_index = s_style_count; +} +#endif diff --git a/src/zhlt-vluzacn/hlrad/vismatrix.cpp b/src/zhlt-vluzacn/hlrad/vismatrix.cpp new file mode 100644 index 0000000..5b93289 --- /dev/null +++ b/src/zhlt-vluzacn/hlrad/vismatrix.cpp @@ -0,0 +1,664 @@ +#include "qrad.h" + +//////////////////////////// +// begin old vismat.c +// +#define HALFBIT + +// ===================================================================================== +// +// VISIBILITY MATRIX +// Determine which patches can see each other +// Use the PVS to accelerate if available +// +// ===================================================================================== + +static byte* s_vismatrix; + + + +#ifndef HLRAD_TRANSPARENCY_CPP +#ifdef HLRAD_HULLU +// ===================================================================================== +// OPACITY ARRAY +// ===================================================================================== + +typedef struct { + unsigned bitpos; + vec3_t transparency; +} transparency_t; + +static transparency_t *s_transparency_list = NULL; +static unsigned long s_transparency_count = 0; +static unsigned long s_max_transparency_count=0; + +static void FindOpacity(const unsigned bitpos, vec3_t &out) +{ + for(unsigned long i = 0; i < s_transparency_count; i++) + { + if( s_transparency_list[i].bitpos == bitpos ) + { + VectorCopy(s_transparency_list[i].transparency, out); + return; + } + } + VectorFill(out, 1.0); +} + +#endif /*HLRAD_HULLU*/ +#endif + +// ===================================================================================== +// TestPatchToFace +// Sets vis bits for all patches in the face +// ===================================================================================== +static void TestPatchToFace(const unsigned patchnum, const int facenum, const int head, const unsigned int bitpos +#ifdef HLRAD_ENTITYBOUNCE_FIX + , byte *pvs +#endif + ) +{ + patch_t* patch = &g_patches[patchnum]; + patch_t* patch2 = g_face_patches[facenum]; + + // if emitter is behind that face plane, skip all patches + + if (patch2) + { + const dplane_t* plane2 = getPlaneFromFaceNumber(facenum); + +#ifdef HLRAD_ACCURATEBOUNCE_ALTERNATEORIGIN + if (DotProduct (patch->origin, plane2->normal) > PatchPlaneDist (patch2) + ON_EPSILON - patch->emitter_range) +#else + if (DotProduct(patch->origin, plane2->normal) > (PatchPlaneDist(patch2) + MINIMUM_PATCH_DISTANCE)) +#endif + { + // we need to do a real test + const dplane_t* plane = getPlaneFromFaceNumber(patch->faceNumber); + + for (; patch2; patch2 = patch2->next) + { + unsigned m = patch2 - g_patches; + +#ifdef HLRAD_HULLU + vec3_t transparency = {1.0, 1.0, 1.0}; +#endif +#ifdef HLRAD_OPAQUE_STYLE + int opaquestyle = -1; +#endif + + // 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 (m > patchnum) + { +#ifdef HLRAD_ENTITYBOUNCE_FIX + if (patch2->leafnum == 0 || !(pvs[(patch2->leafnum - 1) >> 3] & (1 << ((patch2->leafnum - 1) & 7)))) + { + continue; + } +#endif +#ifdef HLRAD_ACCURATEBOUNCE_ALTERNATEORIGIN + vec3_t origin1, origin2; + vec3_t delta; + vec_t dist; + VectorSubtract (patch->origin, patch2->origin, delta); + dist = VectorLength (delta); + if (dist < patch2->emitter_range - ON_EPSILON) + { + GetAlternateOrigin (patch->origin, plane->normal, patch2, origin2); + } + else + { + VectorCopy (patch2->origin, origin2); + } + if (DotProduct (origin2, plane->normal) <= PatchPlaneDist (patch) + MINIMUM_PATCH_DISTANCE) + { + continue; + } + if (dist < patch->emitter_range - ON_EPSILON) + { + GetAlternateOrigin (patch2->origin, plane2->normal, patch, origin1); + } + else + { + VectorCopy (patch->origin, origin1); + } + if (DotProduct (origin1, plane2->normal) <= PatchPlaneDist (patch2) + MINIMUM_PATCH_DISTANCE) + { + continue; + } +#else + if (DotProduct(patch2->origin, plane->normal) <= (PatchPlaneDist(patch) + MINIMUM_PATCH_DISTANCE)) + { + continue; + } +#endif +#ifdef HLRAD_WATERBLOCKLIGHT + if (TestLine( + #ifdef HLRAD_ACCURATEBOUNCE_ALTERNATEORIGIN + origin1, origin2 + #else + patch->origin, patch2->origin + #endif + ) != CONTENTS_EMPTY) +#else + if (TestLine_r(head, + #ifdef HLRAD_ACCURATEBOUNCE_ALTERNATEORIGIN + origin1, origin2 + #else + patch->origin, patch2->origin + #endif + ) != CONTENTS_EMPTY) +#endif + { + continue; + } + if (TestSegmentAgainstOpaqueList( +#ifdef HLRAD_ACCURATEBOUNCE_ALTERNATEORIGIN + origin1, origin2 +#else + patch->origin, patch2->origin +#endif +#ifdef HLRAD_HULLU + , transparency +#endif +#ifdef HLRAD_OPAQUE_STYLE + , opaquestyle +#endif + )) + { + continue; + } + +#ifdef HLRAD_OPAQUE_STYLE_BOUNCE + if (opaquestyle != -1) + { + AddStyleToStyleArray (m, patchnum, opaquestyle); + AddStyleToStyleArray (patchnum, m, opaquestyle); + } +#endif + //Log("SDF::3\n"); + + // patchnum can see patch m + unsigned bitset = bitpos + m; + +#ifdef HLRAD_HULLU + #ifdef HLRAD_TRANSPARENCY_CPP + if(g_customshadow_with_bouncelight && !VectorCompare(transparency, vec3_one)) + // zhlt3.4: if(g_customshadow_with_bouncelight && VectorCompare(transparency, vec3_one)) . --vluzacn + { + AddTransparencyToRawArray(patchnum, m, transparency); + } + #else + // transparency face fix table + // TODO: this method makes MakeScale extreamly slow.. find new one + if(g_customshadow_with_bouncelight && fabs(VectorAvg(transparency) - 1.0) < 0.001) + //wrong? (and in sparse) --vluzacn + { + while(s_transparency_count >= s_max_transparency_count) + { + //new size + unsigned long old_max = s_max_transparency_count; + s_max_transparency_count += 128; + + //realloc + s_transparency_list = (transparency_t*)realloc(s_transparency_list, s_max_transparency_count * sizeof(transparency_t)); + + // clean new memory + memset(&s_transparency_list[old_max], 0, sizeof(transparency_t) * 128); + } + + //add to array + VectorCopy(transparency, s_transparency_list[s_transparency_count].transparency); + s_transparency_list[s_transparency_count].bitpos = bitset; + + s_transparency_count++; + } + #endif +#endif /*HLRAD_HULLU*/ + + ThreadLock (); //--vluzacn + s_vismatrix[bitset >> 3] |= 1 << (bitset & 7); + ThreadUnlock (); //--vluzacn + } + } + } + } +} + +#ifndef HLRAD_VISMATRIX_NOMARKSURFACES +// ===================================================================================== +// BuildVisRow +// Calc vis bits from a single patch +// ===================================================================================== +static void BuildVisRow(const int patchnum, byte* pvs, const int head, const unsigned int bitpos) +{ + int j, k, l; + byte face_tested[MAX_MAP_FACES]; + dleaf_t* leaf; + + memset(face_tested, 0, g_numfaces); + + // leaf 0 is the solid leaf (skipped) +#ifdef HLRAD_VIS_FIX + for (j = 1, leaf = g_dleafs + 1; j < 1 + g_dmodels[0].visleafs; j++, leaf++) +#else + for (j = 1, leaf = g_dleafs + 1; j < g_numleafs; j++, leaf++) +#endif + { + if (!(pvs[(j - 1) >> 3] & (1 << ((j - 1) & 7)))) + continue; // not in pvs + for (k = 0; k < leaf->nummarksurfaces; k++) + { + l = g_dmarksurfaces[leaf->firstmarksurface + k]; + + // faces can be marksurfed by multiple leaves, but + // don't bother testing again + if (face_tested[l]) + continue; + face_tested[l] = 1; + + TestPatchToFace(patchnum, l, head, bitpos +#ifdef HLRAD_ENTITYBOUNCE_FIX + , pvs +#endif + ); + } + } +} +#endif + +// ===================================================================================== +// BuildVisLeafs +// This is run by multiple threads +// ===================================================================================== +#ifdef SYSTEM_WIN32 +#pragma warning(push) +#pragma warning(disable: 4100) // unreferenced formal parameter +#endif +static void BuildVisLeafs(int threadnum) +{ + int i; + int lface, facenum, facenum2; + byte pvs[(MAX_MAP_LEAFS + 7) / 8]; + dleaf_t* srcleaf; + dleaf_t* leaf; + patch_t* patch; + int head; + unsigned bitpos; + unsigned patchnum; + + while (1) + { + // + // build a minimal BSP tree that only + // covers areas relevent to the PVS + // + i = GetThreadWork(); + if (i == -1) + break; + i++; // skip leaf 0 + srcleaf = &g_dleafs[i]; +#ifdef HLRAD_WITHOUTVIS + if (!g_visdatasize) + { + #ifdef ZHLT_DecompressVis_FIX + memset (pvs, 255, (g_dmodels[0].visleafs + 7) / 8); + #else + memset(pvs, 255, (g_numleafs + 7) / 8); + #endif + } + else + { +#endif +#ifdef HLRAD_VIS_FIX + if (srcleaf->visofs == -1) + { + Developer (DEVELOPER_LEVEL_ERROR, "Error: No visdata for leaf %d\n", i); + continue; + } +#endif + DecompressVis(&g_dvisdata[srcleaf->visofs], pvs, sizeof(pvs)); +#ifdef HLRAD_WITHOUTVIS + } +#endif + head = 0; + + // + // go through all the faces inside the + // leaf, and process the patches that + // actually have origins inside + // +#ifdef HLRAD_VISMATRIX_NOMARKSURFACES + for (facenum = 0; facenum < g_numfaces; facenum++) + { + for (patch = g_face_patches[facenum]; patch; patch = patch->next) + { + if (patch->leafnum != i) + continue; + patchnum = patch - g_patches; +#ifdef HALFBIT + bitpos = patchnum * g_num_patches - (patchnum * (patchnum + 1)) / 2; +#else + bitpos = patchnum * g_num_patches; +#endif + for (facenum2 = facenum + 1; facenum2 < g_numfaces; facenum2++) + TestPatchToFace (patchnum, facenum2, head, bitpos, pvs); + } + } +#else + for (lface = 0; lface < srcleaf->nummarksurfaces; lface++) + { + facenum = g_dmarksurfaces[srcleaf->firstmarksurface + lface]; + for (patch = g_face_patches[facenum]; patch; patch = patch->next) + { +#ifdef HLRAD_ENTITYBOUNCE_FIX + if (patch->leafnum != i) + continue; +#else + leaf = PointInLeaf(patch->origin); + if (leaf != srcleaf) + continue; +#endif + + patchnum = patch - g_patches; + +#ifdef HALFBIT + bitpos = patchnum * g_num_patches - (patchnum * (patchnum + 1)) / 2; +#else + bitpos = patchnum * g_num_patches; +#endif + // build to all other world leafs + BuildVisRow(patchnum, pvs, head, bitpos); + + // build to bmodel faces + if (g_nummodels < 2) + continue; + for (facenum2 = g_dmodels[1].firstface; facenum2 < g_numfaces; facenum2++) + TestPatchToFace(patchnum, facenum2, head, bitpos +#ifdef HLRAD_ENTITYBOUNCE_FIX + , pvs +#endif + ); + } + } +#ifdef HLRAD_ENTITYBOUNCE_FIX + if (g_nummodels >= 2) + { + for (facenum = g_dmodels[1].firstface; facenum < g_numfaces; facenum++) + { + for (patch = g_face_patches[facenum]; patch; patch = patch->next) + { + if (patch->leafnum != i) + continue; + patchnum = patch - g_patches; + #ifdef HALFBIT + bitpos = patchnum * g_num_patches - (patchnum * (patchnum + 1)) / 2; + #else + bitpos = patchnum * g_num_patches; + #endif + // skip BuildVisRow here because entity patchnums are always bigger than world patchnums. + for (facenum2 = g_dmodels[1].firstface; facenum2 < g_numfaces; facenum2++) + TestPatchToFace (patchnum, facenum2, head, bitpos, pvs); + } + } + } +#endif +#endif + + } +} + +#ifdef SYSTEM_WIN32 +#pragma warning(pop) +#endif + +// ===================================================================================== +// BuildVisMatrix +// ===================================================================================== +static void BuildVisMatrix() +{ + int c; + +#ifdef HALFBIT + c = ((g_num_patches + 1) * (g_num_patches + 1)) / 16; + c += 1; //--vluzacn +#else + c = g_num_patches * ((g_num_patches + 7) / 8); +#endif + + Log("%-20s: %5.1f megs\n", "visibility matrix", c / (1024 * 1024.0)); + + s_vismatrix = (byte*)AllocBlock(c); + + if (!s_vismatrix) + { + Log("Failed to allocate s_vismatrix"); + hlassume(s_vismatrix != NULL, assume_NoMemory); + } + +#ifdef HLRAD_VIS_FIX + NamedRunThreadsOn(g_dmodels[0].visleafs, g_estimate, BuildVisLeafs); +#else + NamedRunThreadsOn(g_numleafs - 1, g_estimate, BuildVisLeafs); +#endif +} + +static void FreeVisMatrix() +{ + if (s_vismatrix) + { + if (FreeBlock(s_vismatrix)) + { + s_vismatrix = NULL; + } + else + { + Warning("Unable to free s_vismatrix"); + } + } + +#ifndef HLRAD_TRANSPARENCY_CPP +#ifdef HLRAD_HULLU + if(s_transparency_list) + { + free(s_transparency_list); + s_transparency_list = NULL; + } + s_transparency_count = s_max_transparency_count = 0; +#endif +#endif + +} + +// ===================================================================================== +// CheckVisBit +// ===================================================================================== +#ifdef HLRAD_TRANSPARENCY_CPP +static bool CheckVisBitVismatrix(unsigned p1, unsigned p2 +#ifdef HLRAD_HULLU + , vec3_t &transparency_out + , unsigned int &next_index +#endif + ) +{ + unsigned bitpos; + +#ifdef HLRAD_HULLU + const unsigned a = p1; + const unsigned b = p2; + + VectorFill(transparency_out, 1.0); +#endif + + if (p1 > p2) + { +#ifndef HLRAD_HULLU + const unsigned a = p1; + const unsigned b = p2; +#endif + p1 = b; + p2 = a; + } + + if (p1 > g_num_patches) + { + Warning("in CheckVisBit(), p1 > num_patches"); + } + if (p2 > g_num_patches) + { + Warning("in CheckVisBit(), p2 > num_patches"); + } + +#ifdef HALFBIT + bitpos = p1 * g_num_patches - (p1 * (p1 + 1)) / 2 + p2; +#else + bitpos = p1 * g_num_patches + p2; +#endif + + if (s_vismatrix[bitpos >> 3] & (1 << (bitpos & 7))) + { +#ifdef HLRAD_HULLU + if(g_customshadow_with_bouncelight) + { + GetTransparency( a, b, transparency_out, next_index ); + } +#endif + return true; + } + + return false; +} +#else /*HLRAD_TRANSPARENCY_CPP*/ +static bool CheckVisBitVismatrix(unsigned p1, unsigned p2 +#ifdef HLRAD_HULLU + , vec3_t &transparency_out +#endif + ) +{ + unsigned bitpos; + + if (p1 > p2) + { + const unsigned a = p1; + const unsigned b = p2; + p1 = b; + p2 = a; + } + + if (p1 > g_num_patches) + { + Warning("in CheckVisBit(), p1 > num_patches"); + } + if (p2 > g_num_patches) + { + Warning("in CheckVisBit(), p2 > num_patches"); + } + +#ifdef HALFBIT + bitpos = p1 * g_num_patches - (p1 * (p1 + 1)) / 2 + p2; +#else + bitpos = p1 * g_num_patches + p2; +#endif + + if (s_vismatrix[bitpos >> 3] & (1 << (bitpos & 7))) + { +#ifdef HLRAD_HULLU + if(g_customshadow_with_bouncelight) + { + vec3_t getvalue = {1.0, 1.0, 1.0}; + FindOpacity(bitpos, getvalue); + VectorCopy(getvalue, transparency_out); + } + else + { + VectorFill(transparency_out, 1.0); + } +#endif + return true; + } + +#ifdef HLRAD_HULLU + VectorFill(transparency_out, 0.0); +#endif + + return false; +} +#endif /*HLRAD_TRANSPARENCY_CPP*/ + +// +// end old vismat.c +//////////////////////////// + +// ===================================================================================== +// MakeScalesVismatrix +// ===================================================================================== +void MakeScalesVismatrix() +{ + char transferfile[_MAX_PATH]; + + hlassume(g_num_patches < MAX_VISMATRIX_PATCHES, assume_MAX_PATCHES); + +#ifdef ZHLT_DEFAULTEXTENSION_FIX + safe_snprintf(transferfile, _MAX_PATH, "%s.inc", g_Mapname); +#else + safe_strncpy(transferfile, g_source, _MAX_PATH); + StripExtension(transferfile); + DefaultExtension(transferfile, ".inc"); +#endif + + if (!g_incremental || !readtransfers(transferfile, g_num_patches)) + { + // determine visibility between g_patches + BuildVisMatrix(); + g_CheckVisBit = CheckVisBitVismatrix; + +#ifdef HLRAD_HULLU + #ifdef HLRAD_TRANSPARENCY_CPP + CreateFinalTransparencyArrays("custom shadow array"); + #else + if((s_max_transparency_count*sizeof(transparency_t))>=(1024 * 1024)) + Log("%-20s: %5.1f megs\n", "custom shadow array", (s_max_transparency_count*sizeof(transparency_t)) / (1024 * 1024.0)); + else if(s_transparency_count) + Log("%-20s: %5.1f kilos\n", "custom shadow array", (s_max_transparency_count*sizeof(transparency_t)) / 1024.0); + #endif +#endif + +#ifndef HLRAD_HULLU + NamedRunThreadsOn(g_num_patches, g_estimate, MakeScales); +#else + if(g_rgb_transfers) + {NamedRunThreadsOn(g_num_patches, g_estimate, MakeRGBScales);} + else + {NamedRunThreadsOn(g_num_patches, g_estimate, MakeScales);} +#endif + FreeVisMatrix(); +#ifdef HLRAD_HULLU + #ifdef HLRAD_TRANSPARENCY_CPP + FreeTransparencyArrays(); + #endif +#endif + +#ifndef HLRAD_NOSWAP + // invert the transfers for gather vs scatter +#ifndef HLRAD_HULLU + NamedRunThreadsOnIndividual(g_num_patches, g_estimate, SwapTransfers); +#else + if(g_rgb_transfers) + {NamedRunThreadsOnIndividual(g_num_patches, g_estimate, SwapRGBTransfers);} + else + {NamedRunThreadsOnIndividual(g_num_patches, g_estimate, SwapTransfers);} +#endif +#endif /*HLRAD_NOSWAP*/ + if (g_incremental) + writetransfers(transferfile, g_num_patches); + else + _unlink(transferfile); + DumpTransfersMemoryUsage(); +#ifdef HLRAD_OPAQUE_STYLE_BOUNCE + CreateFinalStyleArrays ("dynamic shadow array"); +#endif + } +} diff --git a/src/zhlt-vluzacn/hlrad/vismatrixutil.cpp b/src/zhlt-vluzacn/hlrad/vismatrixutil.cpp new file mode 100644 index 0000000..d2a8539 --- /dev/null +++ b/src/zhlt-vluzacn/hlrad/vismatrixutil.cpp @@ -0,0 +1,1333 @@ +#include "qrad.h" + +funcCheckVisBit g_CheckVisBit = NULL; + +#ifdef ZHLT_64BIT_FIX +size_t g_total_transfer = 0; +size_t g_transfer_index_bytes = 0; +size_t g_transfer_data_bytes = 0; +#else +unsigned g_total_transfer = 0; +unsigned g_transfer_index_bytes = 0; +unsigned g_transfer_data_bytes = 0; +#endif + +#define COMPRESSED_TRANSFERS +//#undef COMPRESSED_TRANSFERS + +int FindTransferOffsetPatchnum(transfer_index_t* tIndex, const patch_t* const patch, const unsigned patchnum) +{ + // + // binary search for match + // + int low = 0; + int high = patch->iIndex - 1; + int offset; + + while (1) + { + offset = (low + high) / 2; + + if ((tIndex[offset].index + tIndex[offset].size) < patchnum) + { + low = offset + 1; + } + else if (tIndex[offset].index > patchnum) + { + high = offset - 1; + } + else + { + unsigned x; + unsigned int rval = 0; + transfer_index_t* pIndex = tIndex; + + for (x = 0; x < offset; x++, pIndex++) + { + rval += pIndex->size + 1; + } + rval += patchnum - tIndex[offset].index; + return rval; + } + if (low > high) + { + return -1; + } + } +} + +#ifdef COMPRESSED_TRANSFERS + +static unsigned GetLengthOfRun(const transfer_raw_index_t* raw, const transfer_raw_index_t* const end) +{ + unsigned run_size = 0; + + while (raw < end) + { + if (((*raw) + 1) == (*(raw + 1))) + { + raw++; + run_size++; + + if (run_size >= MAX_COMPRESSED_TRANSFER_INDEX_SIZE) + { + return run_size; + } + } + else + { + return run_size; + } + } + return run_size; +} + +static transfer_index_t* CompressTransferIndicies(transfer_raw_index_t* tRaw, const unsigned rawSize, unsigned* iSize) +{ + unsigned x; + unsigned size = rawSize; + unsigned compressed_count = 0; + + transfer_raw_index_t* raw = tRaw; + transfer_raw_index_t* end = tRaw + rawSize - 1; // -1 since we are comparing current with next and get errors when bumping into the 'end' + +#ifdef HLRAD_MORE_PATCHES + unsigned compressed_count_1 = 0; + + for (x = 0; x < rawSize; x++) + { + x += GetLengthOfRun (tRaw + x, tRaw + rawSize - 1); + compressed_count_1++; + } + + if (!compressed_count_1) + { + return NULL; + } + + transfer_index_t* CompressedArray = (transfer_index_t*)AllocBlock(sizeof(transfer_index_t) * compressed_count_1); +#else + transfer_index_t CompressedArray[MAX_PATCHES]; // somewhat big stack object (1 Mb with 256k patches) +#endif + transfer_index_t* compressed = CompressedArray; + + for (x = 0; x < size; x++, raw++, compressed++) + { + compressed->index = (*raw); + compressed->size = GetLengthOfRun(raw, end); // Zero based (count 0 still implies 1 item in the list, so 256 max entries result) + raw += compressed->size; + x += compressed->size; + compressed_count++; // number of entries in compressed table + } + + *iSize = compressed_count; + +#ifdef HLRAD_MORE_PATCHES + if (compressed_count != compressed_count_1) + { + Error ("CompressTransferIndicies: internal error"); + } + + ThreadLock(); + g_transfer_index_bytes += sizeof(transfer_index_t) * compressed_count; + ThreadUnlock(); + + return CompressedArray; +#else + if (compressed_count) + { + unsigned compressed_array_size = sizeof(transfer_index_t) * compressed_count; + transfer_index_t* rval = (transfer_index_t*)AllocBlock(compressed_array_size); + + ThreadLock(); + g_transfer_index_bytes += compressed_array_size; + ThreadUnlock(); + + memcpy(rval, CompressedArray, compressed_array_size); + return rval; + } + else + { + return NULL; + } +#endif +} + +#else /*COMPRESSED_TRANSFERS*/ + +static transfer_index_t* CompressTransferIndicies(const transfer_raw_index_t* tRaw, const unsigned rawSize, unsigned* iSize) +{ + unsigned x; + unsigned size = rawSize; + unsigned compressed_count = 0; + + transfer_raw_index_t* raw = tRaw; + transfer_raw_index_t* end = tRaw + rawSize; + +#ifdef HLRAD_MORE_PATCHES + if (!size) + { + return NULL; + } + + transfer_index_t CompressedArray = (transfer_index_t*)AllocBlock(sizeof(transfer_index_t) * size); +#else + transfer_index_t CompressedArray[MAX_PATCHES]; // somewhat big stack object (1 Mb with 256k patches) +#endif + transfer_index_t* compressed = CompressedArray; + + for (x = 0; x < size; x++, raw++, compressed++) + { + compressed->index = (*raw); + compressed->size = 0; + compressed_count++; // number of entries in compressed table + } + + *iSize = compressed_count; + +#ifdef HLRAD_MORE_PATCHES + ThreadLock(); + g_transfer_index_bytes += sizeof(transfer_index_t) * size; + ThreadUnlock(); + + return CompressedArray; +#else + if (compressed_count) + { + unsigned compressed_array_size = sizeof(transfer_index_t) * compressed_count; + transfer_index_t* rval = AllocBlock(compressed_array_size); + + ThreadLock(); + g_transfer_index_bytes += compressed_array_size; + ThreadUnlock(); + + memcpy(rval, CompressedArray, compressed_array_size); + return rval; + } + else + { + return NULL; + } +#endif +} +#endif /*COMPRESSED_TRANSFERS*/ + +/* + * ============= + * MakeScales + * + * This is the primary time sink. + * It can be run multi threaded. + * ============= + */ +#ifdef SYSTEM_WIN32 +#pragma warning(push) +#pragma warning(disable: 4100) // unreferenced formal parameter +#endif +void MakeScales(const int threadnum) +{ + int i; + unsigned j; + vec3_t delta; + vec_t dist; + int count; + float trans; + patch_t* patch; + patch_t* patch2; + float send; + vec3_t origin; + vec_t area; + const vec_t* normal1; + const vec_t* normal2; + +#ifdef HLRAD_HULLU +#ifdef HLRAD_TRANSPARENCY_CPP + unsigned int fastfind_index = 0; +#endif +#endif + + vec_t total; + +#ifdef HLRAD_TRANSFERDATA_COMPRESS + transfer_raw_index_t* tIndex; + float* tData; + +#ifdef HLRAD_MORE_PATCHES + transfer_raw_index_t* tIndex_All = (transfer_raw_index_t*)AllocBlock(sizeof(transfer_index_t) * (g_num_patches + 1)); + float* tData_All = (float*)AllocBlock(sizeof(float) * (g_num_patches + 1)); +#else + transfer_raw_index_t* tIndex_All = (transfer_raw_index_t*)AllocBlock(sizeof(transfer_index_t) * MAX_PATCHES); + float* tData_All = (float*)AllocBlock(sizeof(float) * MAX_PATCHES); +#endif +#else + transfer_raw_index_t* tIndex; + transfer_data_t* tData; + +#ifdef HLRAD_MORE_PATCHES + transfer_raw_index_t* tIndex_All = (transfer_raw_index_t*)AllocBlock(sizeof(transfer_index_t) * (g_num_patches + 1)); + transfer_data_t* tData_All = (transfer_data_t*)AllocBlock(sizeof(transfer_data_t) * (g_num_patches + 1)); +#else + transfer_raw_index_t* tIndex_All = (transfer_raw_index_t*)AllocBlock(sizeof(transfer_index_t) * MAX_PATCHES); + transfer_data_t* tData_All = (transfer_data_t*)AllocBlock(sizeof(transfer_data_t) * MAX_PATCHES); +#endif +#endif + + count = 0; + + while (1) + { + i = GetThreadWork(); + if (i == -1) + break; + + patch = g_patches + i; + patch->iIndex = 0; + patch->iData = 0; + +#ifndef HLRAD_TRANSNONORMALIZE + total = 0.0; +#endif + + tIndex = tIndex_All; + tData = tData_All; + + VectorCopy(patch->origin, origin); + normal1 = getPlaneFromFaceNumber(patch->faceNumber)->normal; + + area = patch->area; +#ifdef HLRAD_TRANSLUCENT + vec3_t backorigin; + vec3_t backnormal; + if (patch->translucent_b) + { + VectorMA (patch->origin, -(g_translucentdepth + 2*PATCH_HUNT_OFFSET), normal1, backorigin); + VectorSubtract (vec3_origin, normal1, backnormal); + } +#endif +#ifdef HLRAD_DIVERSE_LIGHTING + bool lighting_diversify; + vec_t lighting_power; + vec_t lighting_scale; + int miptex = g_texinfo[g_dfaces[patch->faceNumber].texinfo].miptex; + lighting_power = g_lightingconeinfo[miptex][0]; + lighting_scale = g_lightingconeinfo[miptex][1]; + lighting_diversify = (lighting_power != 1.0 || lighting_scale != 1.0); +#endif + + // find out which patch2's will collect light + // from patch + // HLRAD_NOSWAP: patch collect light from patch2 + + for (j = 0, patch2 = g_patches; j < g_num_patches; j++, patch2++) + { + vec_t dot1; + vec_t dot2; + +#ifdef HLRAD_HULLU + vec3_t transparency = {1.0,1.0,1.0}; +#endif +#ifdef HLRAD_TRANSLUCENT + bool useback; + useback = false; +#endif + + if (!g_CheckVisBit(i, j +#ifdef HLRAD_HULLU + , transparency +#ifdef HLRAD_TRANSPARENCY_CPP + , fastfind_index +#endif +#endif + ) || (i == j)) + { +#ifdef HLRAD_TRANSLUCENT + if (patch->translucent_b) + { + if ((i == j) || + !CheckVisBitBackwards(i, j, backorigin, backnormal + #ifdef HLRAD_HULLU + , transparency + #endif + )) + { + continue; + } + useback = true; + } + else + { + continue; + } +#else + continue; +#endif + } + + normal2 = getPlaneFromFaceNumber(patch2->faceNumber)->normal; + + // calculate transferemnce + VectorSubtract(patch2->origin, origin, delta); +#ifdef HLRAD_TRANSLUCENT + if (useback) + { + VectorSubtract (patch2->origin, backorigin, delta); + } +#endif +#ifdef HLRAD_ACCURATEBOUNCE + // move emitter back to its plane + VectorMA (delta, -PATCH_HUNT_OFFSET, normal2, delta); +#endif + + dist = VectorNormalize(delta); + dot1 = DotProduct(delta, normal1); +#ifdef HLRAD_TRANSLUCENT + if (useback) + { + dot1 = DotProduct (delta, backnormal); + } +#endif + dot2 = -DotProduct(delta, normal2); +#ifdef HLRAD_ACCURATEBOUNCE +#ifdef HLRAD_ACCURATEBOUNCE_ALTERNATEORIGIN + bool light_behind_surface = false; + if (dot1 <= NORMAL_EPSILON) + { + light_behind_surface = true; + } +#else + if (dot1 <= NORMAL_EPSILON) + { + continue; + } +#endif + if (dot2 * dist <= MINIMUM_PATCH_DISTANCE) + { + continue; + } +#endif + +#ifdef HLRAD_DIVERSE_LIGHTING + if (lighting_diversify + #ifdef HLRAD_ACCURATEBOUNCE_ALTERNATEORIGIN + && !light_behind_surface + #endif + ) + { + dot1 = lighting_scale * pow (dot1, lighting_power); + } +#endif + trans = (dot1 * dot2) / (dist * dist); // Inverse square falloff factoring angle between patch normals +#ifdef HLRAD_TRANSWEIRDFIX +#ifdef HLRAD_NOSWAP + if (trans * patch2->area > 0.8f) + trans = 0.8f / patch2->area; +#else + // HLRAD_TRANSWEIRDFIX: + // we should limit "trans(patch2receive) * patch1area" + // instead of "trans(patch2receive) * patch2area". + // also raise "0.4f" to "0.8f" ( 0.8/Q_PI = 1/4). + if (trans * area > 0.8f) + trans = 0.8f / area; +#endif +#endif +#ifdef HLRAD_ACCURATEBOUNCE + if (dist < patch2->emitter_range - ON_EPSILON) + { + #ifdef HLRAD_ACCURATEBOUNCE_ALTERNATEORIGIN + if (light_behind_surface) + { + trans = 0.0; + } + #endif + vec_t sightarea; + const vec_t *receiver_origin; + const vec_t *receiver_normal; + const Winding *emitter_winding; + receiver_origin = origin; + receiver_normal = normal1; + #ifdef HLRAD_TRANSLUCENT + if (useback) + { + receiver_origin = backorigin; + receiver_normal = backnormal; + } + #endif + emitter_winding = patch2->winding; + sightarea = CalcSightArea (receiver_origin, receiver_normal, emitter_winding, patch2->emitter_skylevel + #ifdef HLRAD_DIVERSE_LIGHTING + , lighting_power, lighting_scale + #endif + ); + + vec_t frac; + frac = dist / patch2->emitter_range; + frac = (frac - 0.5f) * 2.0f; // make a smooth transition between the two methods + frac = qmax (0, qmin (frac, 1)); + trans = frac * trans + (1 - frac) * (sightarea / patch2->area); // because later we will multiply this back + } + #ifdef HLRAD_ACCURATEBOUNCE_ALTERNATEORIGIN + else + { + if (light_behind_surface) + { + continue; + } + } + #endif +#endif + +#ifdef HLRAD_ACCURATEBOUNCE_REDUCEAREA + trans *= patch2->exposure; +#endif +#ifdef HLRAD_HULLU + trans = trans * VectorAvg(transparency); //hullu: add transparency effect +#endif +#ifdef HLRAD_TRANSLUCENT + if (patch->translucent_b) + { + if (useback) + { + trans *= VectorAvg (patch->translucent_v); + } + else + { + trans *= 1 - VectorAvg (patch->translucent_v); + } + } +#endif + +#ifndef HLRAD_ACCURATEBOUNCE + if (trans >= 0) +#endif + { +#ifndef HLRAD_TRANSWEIRDFIX +#ifdef HLRAD_NOSWAP + send = trans * area; +#else + send = trans * patch2->area; +#endif + + // Caps light from getting weird + if (send > 0.4f) + { +#ifdef HLRAD_NOSWAP + trans = 0.4f / area; +#else + trans = 0.4f / patch2->area; +#endif + send = 0.4f; + } +#endif /*HLRAD_TRANSWEIRDFIX*/ + +#ifndef HLRAD_TRANSNONORMALIZE + total += send; +#endif + +#ifdef HLRAD_TRANSFERDATA_COMPRESS + trans = trans * patch2->area; +#else + // scale to 16 bit (black magic) + // BUG: (in MakeRGBScales) convert to integer will lose data. --vluzacn +#ifdef HLRAD_NOSWAP + trans = trans * patch2->area * INVERSE_TRANSFER_SCALE; +#else + trans = trans * area * INVERSE_TRANSFER_SCALE; +#endif /*HLRAD_NOSWAP*/ + if (trans >= TRANSFER_SCALE_MAX) + { + trans = TRANSFER_SCALE_MAX; + } +#endif + } +#ifndef HLRAD_ACCURATEBOUNCE + else + { +#if 0 + Warning("transfer < 0 (%f): dist=(%f)\n" + " dot1=(%f) patch@(%4.3f %4.3f %4.3f) normal(%4.3f %4.3f %4.3f)\n" + " dot2=(%f) patch@(%4.3f %4.3f %4.3f) normal(%4.3f %4.3f %4.3f)\n", + trans, dist, + dot1, patch->origin[0], patch->origin[1], patch->origin[2], patch->normal[0], patch->normal[1], + patch->normal[2], dot2, patch2->origin[0], patch2->origin[1], patch2->origin[2], + patch2->normal[0], patch2->normal[1], patch2->normal[2]); +#endif + trans = 0.0; + } +#endif +#ifdef HLRAD_ACCURATEBOUNCE + if (trans <= 0.0) + { + continue; + } +#endif + + *tData = trans; + *tIndex = j; + tData++; + tIndex++; + patch->iData++; + count++; + } + + // copy the transfers out + if (patch->iData) + { +#ifdef HLRAD_TRANSFERDATA_COMPRESS + unsigned data_size = patch->iData * float_size[g_transfer_compress_type] + unused_size; +#else + unsigned data_size = patch->iData * sizeof(transfer_data_t); +#endif + + patch->tData = (transfer_data_t*)AllocBlock(data_size); + patch->tIndex = CompressTransferIndicies(tIndex_All, patch->iData, &patch->iIndex); + + hlassume(patch->tData != NULL, assume_NoMemory); + hlassume(patch->tIndex != NULL, assume_NoMemory); + + ThreadLock(); + g_transfer_data_bytes += data_size; + ThreadUnlock(); + +#ifdef HLRAD_REFLECTIVITY + total = 1 / Q_PI; +#else +#ifdef HLRAD_TRANSNONORMALIZE + #ifdef HLRAD_TRANSTOTAL_HACK + total = g_transtotal_hack / Q_PI; + #else + total = 0.5 / Q_PI; + #endif +#else // BAD assumption when there is SKY. + // + // normalize all transfers so exactly 50% of the light + // is transfered to the surroundings + // + + total = 0.5 / total; +#endif +#endif + { +#ifdef HLRAD_TRANSFERDATA_COMPRESS + unsigned x; + transfer_data_t* t1 = patch->tData; + float* t2 = tData_All; + + float f; + for (x = 0; x < patch->iData; x++, t1+=float_size[g_transfer_compress_type], t2++) + { + f = (*t2) * total; + float_compress (g_transfer_compress_type, t1, &f); + } +#else + unsigned x; + transfer_data_t* t1 = patch->tData; + transfer_data_t* t2 = tData_All; + + for (x = 0; x < patch->iData; x++, t1++, t2++) + { + (*t1) = (*t2) * total; + } +#endif + } + } + } + + FreeBlock(tIndex_All); + FreeBlock(tData_All); + + ThreadLock(); + g_total_transfer += count; + ThreadUnlock(); +} + +#ifdef SYSTEM_WIN32 +#pragma warning(pop) +#endif + +/* + * ============= + * SwapTransfersTask + * + * Change transfers from light sent out to light collected in. + * In an ideal world, they would be exactly symetrical, but + * because the form factors are only aproximated, then normalized, + * they will actually be rather different. + * ============= + */ +#ifndef HLRAD_NOSWAP +void SwapTransfers(const int patchnum) +{ + patch_t* patch = &g_patches[patchnum]; + transfer_index_t* tIndex = patch->tIndex; + transfer_data_t* tData = patch->tData; + unsigned x; + + for (x = 0; x < patch->iIndex; x++, tIndex++) + { + unsigned size = (tIndex->size + 1); + unsigned patchnum2 = tIndex->index; + unsigned y; + + for (y = 0; y < size; y++, tData++, patchnum2++) + { + patch_t* patch2 = &g_patches[patchnum2]; + + if (patchnum2 > patchnum) + { // done with this list + return; + } + else if (!patch2->iData) + { // Set to zero in this impossible case + Log("patch2 has no iData\n"); + (*tData) = 0; + continue; + } + else + { + transfer_index_t* tIndex2 = patch2->tIndex; + transfer_data_t* tData2 = patch2->tData; + int offset = FindTransferOffsetPatchnum(tIndex2, patch2, patchnum); + + if (offset >= 0) + { + transfer_data_t tmp = *tData; + + *tData = tData2[offset]; + tData2[offset] = tmp; + } + else + { // Set to zero in this impossible case + Log("FindTransferOffsetPatchnum returned -1 looking for patch %d in patch %d's transfer lists\n", + patchnum, patchnum2); + (*tData) = 0; + return; + } + } + } + } +} +#endif /*HLRAD_NOSWAP*/ + +#ifdef HLRAD_HULLU +/* + * ============= + * MakeScales + * + * This is the primary time sink. + * It can be run multi threaded. + * ============= + */ +#ifdef SYSTEM_WIN32 +#pragma warning(push) +#pragma warning(disable: 4100) // unreferenced formal parameter +#endif +void MakeRGBScales(const int threadnum) +{ + int i; + unsigned j; + vec3_t delta; + vec_t dist; + int count; + float trans[3]; + float trans_one; + patch_t* patch; + patch_t* patch2; + float send; + vec3_t origin; + vec_t area; + const vec_t* normal1; + const vec_t* normal2; + +#ifdef HLRAD_TRANSPARENCY_CPP + unsigned int fastfind_index = 0; +#endif + vec_t total; + +#ifdef HLRAD_TRANSFERDATA_COMPRESS + transfer_raw_index_t* tIndex; + float* tRGBData; + +#ifdef HLRAD_MORE_PATCHES + transfer_raw_index_t* tIndex_All = (transfer_raw_index_t*)AllocBlock(sizeof(transfer_index_t) * (g_num_patches + 1)); + float* tRGBData_All = (float*)AllocBlock(sizeof(float[3]) * (g_num_patches + 1)); +#else + transfer_raw_index_t* tIndex_All = (transfer_raw_index_t*)AllocBlock(sizeof(transfer_index_t) * MAX_PATCHES); + float* tRGBData_All = (float*)AllocBlock(sizeof(float[3]) * MAX_PATCHES); +#endif +#else + transfer_raw_index_t* tIndex; + rgb_transfer_data_t* tRGBData; + +#ifdef HLRAD_MORE_PATCHES + transfer_raw_index_t* tIndex_All = (transfer_raw_index_t*)AllocBlock(sizeof(transfer_index_t) * (g_num_patches + 1)); + rgb_transfer_data_t* tRGBData_All = (rgb_transfer_data_t*)AllocBlock(sizeof(rgb_transfer_data_t) * (g_num_patches + 1)); +#else + transfer_raw_index_t* tIndex_All = (transfer_raw_index_t*)AllocBlock(sizeof(transfer_index_t) * MAX_PATCHES); + rgb_transfer_data_t* tRGBData_All = (rgb_transfer_data_t*)AllocBlock(sizeof(rgb_transfer_data_t) * MAX_PATCHES); +#endif +#endif + + count = 0; + + while (1) + { + i = GetThreadWork(); + if (i == -1) + break; + + patch = g_patches + i; + patch->iIndex = 0; + patch->iData = 0; + +#ifndef HLRAD_TRANSNONORMALIZE + total = 0.0; +#endif + + tIndex = tIndex_All; + tRGBData = tRGBData_All; + + VectorCopy(patch->origin, origin); + normal1 = getPlaneFromFaceNumber(patch->faceNumber)->normal; + + area = patch->area; +#ifdef HLRAD_TRANSLUCENT + vec3_t backorigin; + vec3_t backnormal; + if (patch->translucent_b) + { + VectorMA (patch->origin, -(g_translucentdepth + 2*PATCH_HUNT_OFFSET), normal1, backorigin); + VectorSubtract (vec3_origin, normal1, backnormal); + } +#endif +#ifdef HLRAD_DIVERSE_LIGHTING + bool lighting_diversify; + vec_t lighting_power; + vec_t lighting_scale; + int miptex = g_texinfo[g_dfaces[patch->faceNumber].texinfo].miptex; + lighting_power = g_lightingconeinfo[miptex][0]; + lighting_scale = g_lightingconeinfo[miptex][1]; + lighting_diversify = (lighting_power != 1.0 || lighting_scale != 1.0); +#endif + + // find out which patch2's will collect light + // from patch + // HLRAD_NOSWAP: patch collect light from patch2 + + for (j = 0, patch2 = g_patches; j < g_num_patches; j++, patch2++) + { + vec_t dot1; + vec_t dot2; + vec3_t transparency = {1.0,1.0,1.0}; +#ifdef HLRAD_TRANSLUCENT + bool useback; + useback = false; +#endif + + if (!g_CheckVisBit(i, j + , transparency +#ifdef HLRAD_TRANSPARENCY_CPP + , fastfind_index +#endif + ) || (i == j)) + { +#ifdef HLRAD_TRANSLUCENT + if (patch->translucent_b) + { + if (!CheckVisBitBackwards(i, j, backorigin, backnormal + #ifdef HLRAD_HULLU + , transparency + #endif + ) || (i==j)) + { + continue; + } + useback = true; + } + else + { + continue; + } +#else + continue; +#endif + } + + normal2 = getPlaneFromFaceNumber(patch2->faceNumber)->normal; + + // calculate transferemnce + VectorSubtract(patch2->origin, origin, delta); +#ifdef HLRAD_TRANSLUCENT + if (useback) + { + VectorSubtract (patch2->origin, backorigin, delta); + } +#endif +#ifdef HLRAD_ACCURATEBOUNCE + // move emitter back to its plane + VectorMA (delta, -PATCH_HUNT_OFFSET, normal2, delta); +#endif + + dist = VectorNormalize(delta); + dot1 = DotProduct(delta, normal1); +#ifdef HLRAD_TRANSLUCENT + if (useback) + { + dot1 = DotProduct (delta, backnormal); + } +#endif + dot2 = -DotProduct(delta, normal2); +#ifdef HLRAD_ACCURATEBOUNCE +#ifdef HLRAD_ACCURATEBOUNCE_ALTERNATEORIGIN + bool light_behind_surface = false; + if (dot1 <= NORMAL_EPSILON) + { + light_behind_surface = true; + } +#else + if (dot1 <= NORMAL_EPSILON) + { + continue; + } +#endif + if (dot2 * dist <= MINIMUM_PATCH_DISTANCE) + { + continue; + } +#endif + +#ifdef HLRAD_DIVERSE_LIGHTING + if (lighting_diversify + #ifdef HLRAD_ACCURATEBOUNCE_ALTERNATEORIGIN + && !light_behind_surface + #endif + ) + { + dot1 = lighting_scale * pow (dot1, lighting_power); + } +#endif + trans_one = (dot1 * dot2) / (dist * dist); // Inverse square falloff factoring angle between patch normals + +#ifdef HLRAD_TRANSWEIRDFIX +#ifdef HLRAD_NOSWAP + if (trans_one * patch2->area > 0.8f) + { + trans_one = 0.8f / patch2->area; + } +#else + if (trans_one * area > 0.8f) + { + trans_one = 0.8f / area; + } +#endif +#endif +#ifdef HLRAD_ACCURATEBOUNCE + if (dist < patch2->emitter_range - ON_EPSILON) + { + #ifdef HLRAD_ACCURATEBOUNCE_ALTERNATEORIGIN + if (light_behind_surface) + { + trans_one = 0.0; + } + #endif + vec_t sightarea; + const vec_t *receiver_origin; + const vec_t *receiver_normal; + const Winding *emitter_winding; + receiver_origin = origin; + receiver_normal = normal1; + #ifdef HLRAD_TRANSLUCENT + if (useback) + { + receiver_origin = backorigin; + receiver_normal = backnormal; + } + #endif + emitter_winding = patch2->winding; + sightarea = CalcSightArea (receiver_origin, receiver_normal, emitter_winding, patch2->emitter_skylevel + #ifdef HLRAD_DIVERSE_LIGHTING + , lighting_power, lighting_scale + #endif + ); + + vec_t frac; + frac = dist / patch2->emitter_range; + frac = (frac - 0.5f) * 2.0f; // make a smooth transition between the two methods + frac = qmax (0, qmin (frac, 1)); + trans_one = frac * trans_one + (1 - frac) * (sightarea / patch2->area); // because later we will multiply this back + } + #ifdef HLRAD_ACCURATEBOUNCE_ALTERNATEORIGIN + else + { + if (light_behind_surface) + { + continue; + } + } + #endif +#endif +#ifdef HLRAD_ACCURATEBOUNCE_REDUCEAREA + trans_one *= patch2->exposure; +#endif + VectorFill(trans, trans_one); + VectorMultiply(trans, transparency, trans); //hullu: add transparency effect +#ifdef HLRAD_TRANSLUCENT + if (patch->translucent_b) + { + if (useback) + { + for (int x = 0; x < 3; x++) + { + trans[x] = patch->translucent_v[x] * trans[x]; + } + } + else + { + for (int x = 0; x < 3; x++) + { + trans[x] = (1 - patch->translucent_v[x]) * trans[x]; + } + } + } +#endif + +#ifdef HLRAD_RGBTRANSFIX +#ifdef HLRAD_ACCURATEBOUNCE + if (trans_one <= 0.0) + { + continue; + } +#else + if (trans_one >= 0) +#endif + { +#ifndef HLRAD_TRANSWEIRDFIX + #ifdef HLRAD_NOSWAP + send = trans_one * area; + #else + send = trans_one * patch2->area; + #endif + if (send > 0.4f) + { + #ifdef HLRAD_NOSWAP + trans_one = 0.4f / area; + #else + trans_one = 0.4f / patch2->area; + #endif + send = 0.4f; + VectorFill(trans, trans_one); + VectorMultiply(trans, transparency, trans); + } +#endif /*HLRAD_TRANSWEIRDFIX*/ + #ifndef HLRAD_TRANSNONORMALIZE + total += send; + #endif +#else /*HLRAD_RGBTRANSFIX*/ +#ifdef HLRAD_ACCURATEBOUNCE + if (VectorAvg(trans) <= 0.0) + { + continue; + } +#else + if (VectorAvg(trans) >= 0) +#endif + { + /////////////////////////////////////////RED + send = trans[0] * patch2->area; + // Caps light from getting weird + if (send > 0.4f) + { + trans[0] = 0.4f / patch2->area; + send = 0.4f; + } + #ifndef HLRAD_TRANSNONORMALIZE + total += send / 3.0f; + #endif + + /////////////////////////////////////////GREEN + send = trans[1] * patch2->area; + // Caps light from getting weird + if (send > 0.4f) + { + trans[1] = 0.4f / patch2->area; + send = 0.4f; + } + #ifndef HLRAD_TRANSNONORMALIZE + total += send / 3.0f; + #endif + + /////////////////////////////////////////BLUE + send = trans[2] * patch2->area; + // Caps light from getting weird + if (send > 0.4f) + { + trans[2] = 0.4f / patch2->area; + send = 0.4f; + } + #ifndef HLRAD_TRANSNONORMALIZE + total += send / 3.0f; + #endif +#endif /*HLRAD_RGBTRANSFIX*/ + +#ifdef HLRAD_TRANSFERDATA_COMPRESS + VectorScale(trans, patch2 -> area, trans); +#else + // scale to 16 bit (black magic) +#ifdef HLRAD_NOSWAP + VectorScale(trans, patch2 -> area * INVERSE_TRANSFER_SCALE, trans); +#else + VectorScale(trans, area * INVERSE_TRANSFER_SCALE, trans); +#endif /*HLRAD_NOSWAP*/ + + if (trans[0] >= TRANSFER_SCALE_MAX) + { + trans[0] = TRANSFER_SCALE_MAX; + } + if (trans[1] >= TRANSFER_SCALE_MAX) + { + trans[1] = TRANSFER_SCALE_MAX; + } + if (trans[2] >= TRANSFER_SCALE_MAX) + { + trans[2] = TRANSFER_SCALE_MAX; + } +#endif + } +#ifndef HLRAD_ACCURATEBOUNCE + else + { +#if 0 + Warning("transfer < 0 (%4.3f %4.3f %4.3f): dist=(%f)\n" + " dot1=(%f) patch@(%4.3f %4.3f %4.3f) normal(%4.3f %4.3f %4.3f)\n" + " dot2=(%f) patch@(%4.3f %4.3f %4.3f) normal(%4.3f %4.3f %4.3f)\n", + trans[0], trans[1], trans[2], dist, + dot1, patch->origin[0], patch->origin[1], patch->origin[2], patch->normal[0], patch->normal[1], + patch->normal[2], dot2, patch2->origin[0], patch2->origin[1], patch2->origin[2], + patch2->normal[0], patch2->normal[1], patch2->normal[2]); +#endif + VectorFill(trans,0.0); + } +#endif + +#ifdef HLRAD_TRANSFERDATA_COMPRESS + VectorCopy(trans, tRGBData); + *tIndex = j; + tRGBData+=3; + tIndex++; + patch->iData++; +#else + VectorCopy(trans, *tRGBData); + *tIndex = j; + tRGBData++; + tIndex++; + patch->iData++; +#endif + count++; + } + + // copy the transfers out + if (patch->iData) + { +#ifdef HLRAD_TRANSFERDATA_COMPRESS + unsigned data_size = patch->iData * vector_size[g_rgbtransfer_compress_type] + unused_size; +#else + unsigned data_size = patch->iData * sizeof(rgb_transfer_data_t); +#endif + + patch->tRGBData = (rgb_transfer_data_t*)AllocBlock(data_size); + patch->tIndex = CompressTransferIndicies(tIndex_All, patch->iData, &patch->iIndex); + + hlassume(patch->tRGBData != NULL, assume_NoMemory); + hlassume(patch->tIndex != NULL, assume_NoMemory); + + ThreadLock(); + g_transfer_data_bytes += data_size; + ThreadUnlock(); + +#ifdef HLRAD_REFLECTIVITY + total = 1 / Q_PI; +#else +#ifdef HLRAD_TRANSNONORMALIZE + #ifdef HLRAD_TRANSTOTAL_HACK + total = g_transtotal_hack / Q_PI; + #else + total = 0.5 / Q_PI; + #endif +#else + // + // normalize all transfers so exactly 50% of the light + // is transfered to the surroundings + // + total = 0.5 / total; +#endif +#endif + { +#ifdef HLRAD_TRANSFERDATA_COMPRESS + unsigned x; + rgb_transfer_data_t* t1 = patch->tRGBData; + float* t2 = tRGBData_All; + + float f[3]; + for (x = 0; x < patch->iData; x++, t1+=vector_size[g_rgbtransfer_compress_type], t2+=3) + { + VectorScale( t2, total, f ); + vector_compress (g_rgbtransfer_compress_type, t1, &f[0], &f[1], &f[2]); + } +#else + unsigned x; + rgb_transfer_data_t* t1 = patch->tRGBData; + rgb_transfer_data_t* t2 = tRGBData_All; + + for (x = 0; x < patch->iData; x++, t1++, t2++) + { + VectorScale( *t2, total, *t1 ); + } +#endif + } + } + } + + FreeBlock(tIndex_All); + FreeBlock(tRGBData_All); + + ThreadLock(); + g_total_transfer += count; + ThreadUnlock(); +} + +#ifdef SYSTEM_WIN32 +#pragma warning(pop) +#endif + +/* + * ============= + * SwapTransfersTask + * + * Change transfers from light sent out to light collected in. + * In an ideal world, they would be exactly symetrical, but + * because the form factors are only aproximated, then normalized, + * they will actually be rather different. + * ============= + */ +#ifndef HLRAD_NOSWAP +void SwapRGBTransfers(const int patchnum) +{ + patch_t* patch = &g_patches[patchnum]; + transfer_index_t* tIndex = patch->tIndex; + rgb_transfer_data_t* tRGBData= patch->tRGBData; + unsigned x; + + for (x = 0; x < patch->iIndex; x++, tIndex++) + { + unsigned size = (tIndex->size + 1); + unsigned patchnum2 = tIndex->index; + unsigned y; + + for (y = 0; y < size; y++, tRGBData++, patchnum2++) + { + patch_t* patch2 = &g_patches[patchnum2]; + + if (patchnum2 > patchnum) + { // done with this list + return; + } + else if (!patch2->iData) + { // Set to zero in this impossible case + Log("patch2 has no iData\n"); + VectorFill(*tRGBData, 0); + continue; + } + else + { + transfer_index_t* tIndex2 = patch2->tIndex; + rgb_transfer_data_t* tRGBData2 = patch2->tRGBData; + int offset = FindTransferOffsetPatchnum(tIndex2, patch2, patchnum); + + if (offset >= 0) + { + rgb_transfer_data_t tmp; + VectorCopy(*tRGBData, tmp) + + VectorCopy(tRGBData2[offset], *tRGBData); + VectorCopy(tmp, tRGBData2[offset]); + } + else + { // Set to zero in this impossible case + Log("FindTransferOffsetPatchnum returned -1 looking for patch %d in patch %d's transfer lists\n", + patchnum, patchnum2); + VectorFill(*tRGBData, 0); + return; + } + } + } + } +} +#endif /*HLRAD_NOSWAP*/ + +#endif /*HLRAD_HULLU*/ + + +#ifndef HLRAD_HULLU + +void DumpTransfersMemoryUsage() +{ +#ifdef ZHLT_64BIT_FIX + Log("Transfer Lists : %.0f transfers\n Indices : %.0f bytes\n Data : %.0f bytes\n", + (double)g_total_transfer, (double)g_transfer_index_bytes, (double)g_transfer_data_bytes); +#else + Log("Transfer Lists : %u transfers\n Indices : %u bytes\n Data : %u bytes\n", + g_total_transfer, g_transfer_index_bytes, g_transfer_data_bytes); +#endif +} + +#else + +//More human readable numbers +void DumpTransfersMemoryUsage() +{ +#ifdef ZHLT_64BIT_FIX + if(g_total_transfer > 1000*1000) + Log("Transfer Lists : %11.0f : %8.2fM transfers\n", (double)g_total_transfer, (double)g_total_transfer/(1000.0f*1000.0f)); + else if(g_total_transfer > 1000) + Log("Transfer Lists : %11.0f : %8.2fk transfers\n", (double)g_total_transfer, (double)g_total_transfer/1000.0f); + else + Log("Transfer Lists : %11.0f transfers\n", (double)g_total_transfer); + + if(g_transfer_index_bytes > 1024*1024) + Log(" Indices : %11.0f : %8.2fM bytes\n", (double)g_transfer_index_bytes, (double)g_transfer_index_bytes/(1024.0f * 1024.0f)); + else if(g_transfer_index_bytes > 1024) + Log(" Indices : %11.0f : %8.2fk bytes\n", (double)g_transfer_index_bytes, (double)g_transfer_index_bytes/1024.0f); + else + Log(" Indices : %11.0f bytes\n", (double)g_transfer_index_bytes); + + if(g_transfer_data_bytes > 1024*1024) + Log(" Data : %11.0f : %8.2fM bytes\n", (double)g_transfer_data_bytes, (double)g_transfer_data_bytes/(1024.0f * 1024.0f)); + else if(g_transfer_data_bytes > 1024) + Log(" Data : %11.0f : %8.2fk bytes\n", (double)g_transfer_data_bytes, (double)g_transfer_data_bytes/1024.0f); + else + Log(" Data : %11.0f bytes\n", (double)g_transfer_data_bytes); +#else + if(g_total_transfer > 1000*1000) + Log("Transfer Lists : %11u : %7.2fM transfers\n", g_total_transfer, g_total_transfer/(1000.0f*1000.0f)); + else if(g_total_transfer > 1000) + Log("Transfer Lists : %11u : %7.2fk transfers\n", g_total_transfer, g_total_transfer/1000.0f); + else + Log("Transfer Lists : %11u transfers\n", g_total_transfer); + + if(g_transfer_index_bytes > 1024*1024) + Log(" Indices : %11u : %7.2fM bytes\n", g_transfer_index_bytes, g_transfer_index_bytes/(1024.0f * 1024.0f)); + else if(g_transfer_index_bytes > 1024) + Log(" Indices : %11u : %7.2fk bytes\n", g_transfer_index_bytes, g_transfer_index_bytes/1024.0f); + else + Log(" Indices : %11u bytes\n", g_transfer_index_bytes); + + if(g_transfer_data_bytes > 1024*1024) + Log(" Data : %11u : %7.2fM bytes\n", g_transfer_data_bytes, g_transfer_data_bytes/(1024.0f * 1024.0f)); + else if(g_transfer_data_bytes > 1024) + Log(" Data : %11u : %7.2fk bytes\n", g_transfer_data_bytes, g_transfer_data_bytes/1024.0f); + else + Log(" Data : %11u bytes\n", g_transfer_data_bytes); //--vluzacn +#endif +} + +#endif + diff --git a/src/zhlt-vluzacn/hlvis/flow.cpp b/src/zhlt-vluzacn/hlvis/flow.cpp new file mode 100644 index 0000000..9e57485 --- /dev/null +++ b/src/zhlt-vluzacn/hlvis/flow.cpp @@ -0,0 +1,1771 @@ +#include "vis.h" + +// ===================================================================================== +// CheckStack +// ===================================================================================== +#ifdef USE_CHECK_STACK +static void CheckStack(const leaf_t* const leaf, const threaddata_t* const thread) +{ + pstack_t* p; + + for (p = thread->pstack_head.next; p; p = p->next) + { + if (p->leaf == leaf) + Error("CheckStack: leaf recursion"); + } +} +#endif + +// ===================================================================================== +// AllocStackWinding +// ===================================================================================== +inline static winding_t* AllocStackWinding(pstack_t* const stack) +{ + int i; + + for (i = 0; i < 3; i++) + { + if (stack->freewindings[i]) + { + stack->freewindings[i] = 0; + return &stack->windings[i]; + } + } + + Error("AllocStackWinding: failed"); + + return NULL; +} + +// ===================================================================================== +// FreeStackWinding +// ===================================================================================== +inline static void FreeStackWinding(const winding_t* const w, pstack_t* const 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 +// ===================================================================================== +inline winding_t* ChopWinding(winding_t* const in, pstack_t* const stack, const plane_t* const split) +{ + vec_t dists[128]; + int sides[128]; + int counts[3]; + vec_t dot; + int i; + vec3_t mid; + winding_t* neww; + + counts[0] = counts[1] = counts[2] = 0; + + if (in->numpoints > (sizeof(sides) / sizeof(*sides))) + { + Error("Winding with too many sides!"); + } + + // determine sides for each point + for (i = 0; i < in->numpoints; i++) + { + dot = DotProduct(in->points[i], split->normal); + dot -= split->dist; + dists[i] = dot; + if (dot > ON_EPSILON) + { + sides[i] = SIDE_FRONT; + } + else if (dot < -ON_EPSILON) + { + sides[i] = SIDE_BACK; + } + else + { + sides[i] = SIDE_ON; + } + counts[sides[i]]++; + } + + if (!counts[1]) + { + return in; // completely on front side + } + + if (!counts[0]) + { + FreeStackWinding(in, stack); + return NULL; + } + + sides[i] = sides[0]; + dists[i] = dists[0]; + + neww = AllocStackWinding(stack); + + neww->numpoints = 0; + + for (i = 0; i < in->numpoints; i++) + { + vec_t* p1 = in->points[i]; + + if (neww->numpoints == MAX_POINTS_ON_FIXED_WINDING) + { + Warning("ChopWinding : rejected(1) due to too many points\n"); + 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; + } + else if (sides[i] == SIDE_FRONT) + { + VectorCopy(p1, neww->points[neww->numpoints]); + neww->numpoints++; + } + + if ((sides[i + 1] == SIDE_ON) | (sides[i + 1] == sides[i])) // | instead of || for branch optimization + { + continue; + } + + if (neww->numpoints == MAX_POINTS_ON_FIXED_WINDING) + { + Warning("ChopWinding : rejected(2) due to too many points\n"); + FreeStackWinding(neww, stack); + return in; // can't chop -- fall back to original + } + + // generate a split point + { + unsigned tmp = i + 1; + if (tmp >= in->numpoints) + { + tmp = 0; + } + const vec_t* p2 = in->points[tmp]; + + dot = dists[i] / (dists[i] - dists[i + 1]); + + const vec_t* normal = split->normal; + const vec_t dist = split->dist; + unsigned int j; + for (j = 0; j < 3; j++) + { // avoid round off error when possible + if (normal[j] < (1.0 - NORMAL_EPSILON)) + { + if (normal[j] > (-1.0 + NORMAL_EPSILON)) + { + mid[j] = p1[j] + dot * (p2[j] - p1[j]); + } + else + { + mid[j] = -dist; + } + } + else + { + mid[j] = dist; + } + } + } + + VectorCopy(mid, neww->points[neww->numpoints]); + neww->numpoints++; + } + + // free the original winding + FreeStackWinding(in, stack); + + return neww; +} + +// ===================================================================================== +// AddPlane +// ===================================================================================== +#ifdef RVIS_LEVEL_2 +inline static void AddPlane(pstack_t* const stack, const plane_t* const split) +{ + int j; + + if (stack->clipPlaneCount) + { + for (j = 0; j < stack->clipPlaneCount; j++) + { + if (fabs((stack->clipPlane[j]).dist - split->dist) <= EQUAL_EPSILON && + VectorCompare((stack->clipPlane[j]).normal, split->normal)) + { + return; + } + } + } + stack->clipPlane[stack->clipPlaneCount] = *split; + stack->clipPlaneCount++; +} +#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 the target argument is NULL, then a list of clipping planes is built in +// stack instead. +// 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. +// ===================================================================================== +inline static winding_t* ClipToSeperators( + const winding_t* const source, + const winding_t* const pass, + winding_t* const a_target, + const bool flipclip, + pstack_t* const stack) +{ + int i, j, k, l; + plane_t plane; + vec3_t v1, v2; + float d; + int counts[3]; + bool fliptest; + winding_t* target = a_target; + + const unsigned int numpoints = source->numpoints; + + // check all combinations + for (i=0, l=1; i < numpoints; i++, l++) + { + if (l == numpoints) + { + l = 0; + } + + 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; j < pass->numpoints; j++) + { + VectorSubtract(pass->points[j], source->points[i], v2); + CrossProduct(v1, v2, plane.normal); + if (VectorNormalize(plane.normal) < ON_EPSILON) + { + continue; + } + plane.dist = DotProduct(pass->points[j], plane.normal); + + // find out which side of the generated seperating plane has the + // source portal + fliptest = false; + for (k = 0; k < numpoints; k++) + { + if ((k == i) | (k == l)) // | instead of || for branch optimization + { + continue; + } + d = DotProduct(source->points[k], plane.normal) - plane.dist; + if (d < -ON_EPSILON) + { // source is on the negative side, so we want all + // pass and target on the positive side + fliptest = false; + break; + } + else if (d > ON_EPSILON) + { // source is on the positive side, so we want all + // pass and target on the negative side + fliptest = true; + break; + } + } + if (k == numpoints) + { + continue; // planar with source portal + } + + // flip the normal if the source portal is backwards + if (fliptest) + { + VectorSubtract(vec3_origin, plane.normal, plane.normal); + plane.dist = -plane.dist; + } + + // 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; k < pass->numpoints; k++) + { + if (k == j) + { + continue; + } + d = DotProduct(pass->points[k], plane.normal) - plane.dist; + if (d < -ON_EPSILON) + { + break; + } + else if (d > ON_EPSILON) + { + counts[0]++; + } + else + { + counts[2]++; + } + } + if (k != pass->numpoints) + { + continue; // points on negative side, not a seperating plane + } + + if (!counts[0]) + { + continue; // planar with seperating plane + } + + // flip the normal if we want the back side + if (flipclip) + { + VectorSubtract(vec3_origin, plane.normal, plane.normal); + plane.dist = -plane.dist; + } + + if (target != NULL) + { + // clip target by the seperating plane + target = ChopWinding(target, stack, &plane); + if (!target) + { + return NULL; // target is not visible + } + } + else + { + AddPlane(stack, &plane); + } + +#ifdef RVIS_LEVEL_1 + break; /* Antony was here */ +#endif + } + } + + return target; +} + +// ===================================================================================== +// RecursiveLeafFlow +// Flood fill through the leafs +// If src_portal is NULL, this is the originating leaf +// ===================================================================================== +inline static void RecursiveLeafFlow(const int leafnum, const threaddata_t* const thread, const pstack_t* const prevstack) +{ + pstack_t stack; + leaf_t* leaf; + + leaf = &g_leafs[leafnum]; +#ifdef USE_CHECK_STACK + CheckStack(leaf, thread); +#endif + + { + const unsigned offset = leafnum >> 3; + const unsigned bit = (1 << (leafnum & 7)); + + // mark the leaf as visible + if (!(thread->leafvis[offset] & bit)) + { + thread->leafvis[offset] |= bit; + thread->base->numcansee++; + } + } + +#ifdef USE_CHECK_STACK + prevstack->next = &stack; + stack.next = NULL; +#endif + stack.head = prevstack->head; + stack.leaf = leaf; + stack.portal = NULL; +#ifdef RVIS_LEVEL_2 + stack.clipPlaneCount = -1; + stack.clipPlane = NULL; +#endif + + // check all portals for flowing into other leafs + unsigned i; + portal_t** plist = leaf->portals; + + for (i = 0; i < leaf->numportals; i++, plist++) + { + portal_t* p = *plist; + +#if ZHLT_ZONES + portal_t * head_p = stack.head->portal; + if (g_Zones->check(head_p->zone, p->zone)) + { + continue; + } +#endif + + { + const unsigned offset = p->leaf >> 3; + const unsigned bit = 1 << (p->leaf & 7); + + if (!(stack.head->mightsee[offset] & bit)) + { + continue; // can't possibly see it + } + if (!(prevstack->mightsee[offset] & bit)) + { + continue; // can't possibly see it + } + } + + // if the portal can't see anything we haven't allready seen, skip it + { + long* test; + + if (p->status == stat_done) + { + test = (long*)p->visbits; + } + else + { + test = (long*)p->mightsee; + } + + { + const int bitlongs = g_bitlongs; + + { + long* prevmight = (long*)prevstack->mightsee; + long* might = (long*)stack.mightsee; + + unsigned j; + for (j = 0; j < bitlongs; j++, test++, might++, prevmight++) + { + (*might) = (*prevmight) & (*test); + } + } + + { + long* might = (long*)stack.mightsee; + long* vis = (long*)thread->leafvis; + unsigned j; + for (j = 0; j < bitlongs; j++, might++, vis++) + { + if ((*might) & ~(*vis)) + { + break; + } + } + + if (j == g_bitlongs) + { // can't see anything new + continue; + } + } + } + } + + // get plane of portal, point normal into the neighbor leaf + stack.portalplane = &p->plane; + plane_t backplane; + VectorSubtract(vec3_origin, p->plane.normal, backplane.normal); + backplane.dist = -p->plane.dist; + + if (VectorCompare(prevstack->portalplane->normal, backplane.normal)) + { + continue; // can't go out a coplanar face + } + + stack.portal = p; +#ifdef USE_CHECK_STACK + stack.next = NULL; +#endif + stack.freewindings[0] = 1; + stack.freewindings[1] = 1; + stack.freewindings[2] = 1; + + stack.pass = ChopWinding(p->winding, &stack, thread->pstack_head.portalplane); + if (!stack.pass) + { + continue; + } + + stack.source = ChopWinding(prevstack->source, &stack, &backplane); + if (!stack.source) + { + continue; + } + + if (!prevstack->pass) + { // the second leaf can only be blocked if coplanar + RecursiveLeafFlow(p->leaf, thread, &stack); + continue; + } + + stack.pass = ChopWinding(stack.pass, &stack, prevstack->portalplane); + if (!stack.pass) + { + continue; + } + +#ifdef RVIS_LEVEL_2 + if (stack.clipPlaneCount == -1) + { + stack.clipPlaneCount = 0; + stack.clipPlane = (plane_t*)alloca(sizeof(plane_t) * prevstack->source->numpoints * prevstack->pass->numpoints); + + ClipToSeperators(prevstack->source, prevstack->pass, NULL, false, &stack); + ClipToSeperators(prevstack->pass, prevstack->source, NULL, true, &stack); + } + + if (stack.clipPlaneCount > 0) + { + unsigned j; + for (j = 0; j < stack.clipPlaneCount && stack.pass != NULL; j++) + { + stack.pass = ChopWinding(stack.pass, &stack, &(stack.clipPlane[j])); + } + + if (stack.pass == NULL) + continue; + } +#else + + 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; + } +#endif + + if (g_fullvis) + { + stack.source = ClipToSeperators(stack.pass, prevstack->pass, stack.source, false, &stack); + if (!stack.source) + { + continue; + } + + stack.source = ClipToSeperators(prevstack->pass, stack.pass, stack.source, true, &stack); + if (!stack.source) + { + continue; + } + } + + // flow through it for real + RecursiveLeafFlow(p->leaf, thread, &stack); + } + +#ifdef RVIS_LEVEL_2 +#if 0 + if (stack.clipPlane != NULL) + { + free(stack.clipPlane); + } +#endif +#endif +} + +// ===================================================================================== +// PortalFlow +// ===================================================================================== +void PortalFlow(portal_t* p) +{ + threaddata_t data; + unsigned i; + + if (p->status != stat_working) + Error("PortalFlow: reflowed"); + + p->visbits = (byte*)calloc(1, g_bitbytes); + + memset(&data, 0, sizeof(data)); + data.leafvis = p->visbits; + data.base = p; + + data.pstack_head.head = &data.pstack_head; + data.pstack_head.portal = p; + data.pstack_head.source = p->winding; + data.pstack_head.portalplane = &p->plane; + for (i = 0; i < g_bitlongs; i++) + { + ((long*)data.pstack_head.mightsee)[i] = ((long*)p->mightsee)[i]; + } + RecursiveLeafFlow(p->leaf, &data, &data.pstack_head); + +#ifdef ZHLT_NETVIS + p->fromclient = g_clientid; +#endif + p->status = stat_done; +#ifdef ZHLT_NETVIS + Flag_VIS_DONE_PORTAL(g_visportalindex); +#endif +} + +// ===================================================================================== +// SimpleFlood +// This is a rough first-order aproximation that is used to trivially reject some +// of the final calculations. +// ===================================================================================== +static void SimpleFlood(byte* const srcmightsee, const int leafnum, byte* const portalsee, unsigned int* const c_leafsee) +{ + unsigned i; + leaf_t* leaf; + portal_t* p; + + { + const unsigned offset = leafnum >> 3; + const unsigned bit = (1 << (leafnum & 7)); + + if (srcmightsee[offset] & bit) + { + return; + } + else + { + srcmightsee[offset] |= bit; + } + } + + (*c_leafsee)++; + leaf = &g_leafs[leafnum]; + + for (i = 0; i < leaf->numportals; i++) + { + p = leaf->portals[i]; + if (!portalsee[p - g_portals]) + { + continue; + } + SimpleFlood(srcmightsee, p->leaf, portalsee, c_leafsee); + } +} + +#define PORTALSEE_SIZE (MAX_PORTALS*2) +#ifdef SYSTEM_WIN32 +#pragma warning(push) +#pragma warning(disable: 4100) // unreferenced formal parameter +#endif + +#ifdef HLVIS_MAXDIST +#ifndef HLVIS_MAXDIST_NEW +// AJM: MVD +// ===================================================================================== +// BlockVis +// ===================================================================================== +void BlockVis(int unused) +{ + int i, j, k, l, m; + portal_t *p; + visblocker_t *v; + visblocker_t *v2; + leaf_t *leaf; + + while(1) + { + i = GetThreadWork(); + if(i == -1) + break; + + v = &g_visblockers[i]; + + // See which visblockers we need + for(j = 0; j < v->numnames; j++) + { + // Find visblocker + if(!(v2 = GetVisBlock(v->blocknames[j]))) + continue; + + // For each leaf in v2, eliminate visibility from v1 + for(k = 0; k < v->numleafs; k++) + { + leaf = &g_leafs[v->blockleafs[k]]; + + for(l = 0; l < leaf->numportals; l++) + { + p = leaf->portals[l]; + + for(m = 0; m < v2->numleafs; m++) + { + const unsigned offset = v2->blockleafs[m] >> 3; + const unsigned bit = (1 << (v2->blockleafs[m] & 7)); + + p->mightsee[offset] &= ~bit; + } + } + } + } + } +} + +// AJM: MVD +// ===================================================================================== +// GetSplitPortal +// This function returns a portal on leaf1 that sucessfully seperates leaf1 +// and leaf2 +// ===================================================================================== +static portal_t *GetSplitPortal(leaf_t *leaf1, leaf_t *leaf2) +{ + int i, k, l; + + portal_t *p1; + portal_t *t; + + float check_dist; + + for(i = 0, p1 = leaf1->portals[0]; i < leaf1->numportals; i++, p1++) + { + hlassert(p1->winding->numpoints >= 3); + + // Check to make sure all the points on the other leaf are in front of the portal plane + for(k = 0, t = leaf2->portals[0]; k < leaf2->numportals; k++, t++) + { + for(l = 0; l < t->winding->numpoints; l++) + { + check_dist = DotProduct(t->winding->points[l], p1->plane.normal) - p1->plane.dist; + + // We make the assumption that all portals face away from their parent leaf + if(check_dist < -ON_EPSILON) + goto PostLoop; + } + } + +PostLoop: + // If we didn't check all the leaf2 portals, then this leaf1 portal doesn't work + if(k < leaf2->numportals) + continue; + + // If we reach this point, we found a good portal + return p1; + } + + // Didn't find any + return NULL; +} + +// AJM: MVD +// ===================================================================================== +// MakeSplitPortalList +// This function returns a portal on leaf1 that sucessfully seperates leaf1 +// and leaf2 +// ===================================================================================== +static void MakeSplitPortalList(leaf_t *leaf1, leaf_t *leaf2, portal_t **portals, int *num_portals) +{ + int i, k, l; + + portal_t *p1; + portal_t *t; + + *num_portals = 0; + + float check_dist; + + portal_t p_list[MAX_PORTALS_ON_LEAF]; + int c_portal = 0; + + if(*portals) + delete [] *portals; + + for(i = 0, p1 = leaf1->portals[0]; i < leaf1->numportals; i++, p1++) + { + hlassert(p1->winding->numpoints >= 3); + + // Check to make sure all the points on the other leaf are in front of the portal plane + for(k = 0, t = leaf2->portals[0]; k < leaf2->numportals; k++, t++) + { + for(l = 0; l < t->winding->numpoints; l++) + { + check_dist = DotProduct(t->winding->points[l], p1->plane.normal) - p1->plane.dist; + + // We make the assumption that all portals face away from their parent leaf + if(check_dist < -ON_EPSILON) + goto PostLoop; + } + } + +PostLoop: + // If we didn't check all the leaf2 portals, then this leaf1 portal doesn't work + if(k < leaf2->numportals) + continue; + + // If we reach this point, we found a good portal + memcpy(&p_list[c_portal++], p1, sizeof(portal_t)); + + if(c_portal >= MAX_PORTALS_ON_LEAF) + Error("c_portal > MAX_PORTALS_ON_LEAF"); + } + + if(!c_portal) + return; + + *num_portals = c_portal; + + *portals = new portal_t[c_portal]; + memcpy(*portals, p_list, c_portal * sizeof(portal_t)); +} + +// AJM: MVD +// ===================================================================================== +// DisjointLeafVis +// This function returns TRUE if neither leaf can see the other +// Returns FALSE otherwise +// ===================================================================================== +static bool DisjointLeafVis(int leaf1, int leaf2) +{ + leaf_t *l = g_leafs + leaf1; + leaf_t *tl = g_leafs + leaf2; + + const unsigned offset_l = leaf1 >> 3; + const unsigned bit_l = (1 << (leaf1 & 7)); + + const unsigned offset_tl = leaf2 >> 3; + const unsigned bit_tl = (1 << (leaf2 & 7)); + + for(int k = 0; k < l->numportals; k++) + { + for(int m = 0; m < tl->numportals; m++) + { + if(l->portals[k]->mightsee[offset_tl] & bit_tl) + goto RetFalse; + if(tl->portals[m]->mightsee[offset_l] & bit_l) + goto RetFalse; + + if(l->portals[k]->status != stat_none) + { + if(l->portals[k]->visbits[offset_tl] & bit_tl) + goto RetFalse; + } + if(tl->portals[m]->status != stat_none) + { + if(tl->portals[m]->visbits[offset_l] & bit_l) + goto RetFalse; + } + } + } + + return true; + +RetFalse: + return false; +} + +// AJM: MVD +// ===================================================================================== +// GetPortalBounds +// This function take a portal and finds its bounds +// parallel to the normal of the portal. They will face inwards +// ===================================================================================== +static void GetPortalBounds(portal_t *p, plane_t **bounds) +{ + int i; + vec3_t vec1, vec2; + + hlassert(p->winding->numpoints >= 3); + + if(*bounds) + delete [] *bounds; + + *bounds = new plane_t[p->winding->numpoints]; + + // Loop through each set of points and create a plane boundary for each + for(i = 0; i < p->winding->numpoints; i++) + { + VectorSubtract(p->winding->points[(i + 1) % p->winding->numpoints],p->winding->points[i],vec1); + + // Create inward normal for this boundary + CrossProduct(p->plane.normal, vec1, vec2); + VectorNormalize(vec2); + + VectorCopy(vec2, (*bounds)[i].normal); + (*bounds)[i].dist = DotProduct(p->winding->points[i], vec2); + } +} + +// AJM: MVD +// ===================================================================================== +// ClipWindingsToBounds +// clips all the windings with all the planes (including original face) and outputs +// what's left int "out" +// ===================================================================================== +static void ClipWindingsToBounds(winding_t *windings, int numwindings, plane_t *bounds, int numbounds, plane_t &original_plane, winding_t **out, int &num_out) +{ + hlassert(windings); + hlassert(bounds); + + winding_t out_windings[MAX_PORTALS_ON_LEAF]; + num_out = 0; + + int h, i; + + *out = NULL; + + Winding wind; + + for(h = 0; h < numwindings; h++) + { + // For each winding... + // Create a winding with CWinding + + wind.initFromPoints(windings[h].points, windings[h].numpoints); + + // Clip winding to original plane + wind.Chop(original_plane.normal, original_plane.dist); + + for(i = 0; i < numbounds, wind.Valid(); i++) + { + // For each bound... + // Chop the winding to the bounds + wind.Chop(bounds[i].normal, bounds[i].dist); + } + + if(wind.Valid()) + { + // We have a valid winding, copy to array + wind.CopyPoints(&out_windings[num_out].points[0], out_windings[num_out].numpoints); + + num_out++; + } + } + + if(!num_out) // Everything was clipped away + return; + + // Otherwise, create out + *out = new winding_t[num_out]; + + memcpy(*out, out_windings, num_out * sizeof(winding_t)); +} + +// AJM: MVD +// ===================================================================================== +// GenerateWindingList +// This function generates a list of windings for a leaf through its portals +// ===================================================================================== +static void GenerateWindingList(leaf_t *leaf, winding_t **winds) +{ + + + winding_t windings[MAX_PORTALS_ON_LEAF]; + int numwinds = 0; + + int i; + + for(i = 0; i < leaf->numportals; i++) + { + memcpy(&windings[numwinds++], leaf->portals[i]->winding, sizeof(winding_t)); + } + + if(!numwinds) + return; + + *winds = new winding_t[numwinds]; + memcpy(*winds, &windings, sizeof(winding_t) * numwinds); +} + +// AJM: MVD +// ===================================================================================== +// CalcPortalBoundsAndClipPortals +// ===================================================================================== +static void CalcPortalBoundsAndClipPortals(portal_t *portal, leaf_t *leaf, winding_t **out, int &numout) +{ + plane_t *bounds = NULL; + winding_t *windings = NULL; + + GetPortalBounds(portal, &bounds); + GenerateWindingList(leaf, &windings); + + ClipWindingsToBounds(windings, leaf->numportals, bounds, portal->winding->numpoints, portal->plane, out, numout); + + delete bounds; + delete windings; +} + +// AJM: MVD +// ===================================================================================== +// GetShortestDistance +// Gets the shortest distance between both leaves +// ===================================================================================== +static float GetShortestDistance(leaf_t *leaf1, leaf_t *leaf2) +{ + winding_t *final = NULL; + int num_finals = 0; + + int i, x, y; + float check; + + for(i = 0; i < leaf1->numportals; i++) + { + CalcPortalBoundsAndClipPortals(leaf1->portals[i], leaf2, &final, num_finals); + + // Minimum point distance + for(x = 0; x < num_finals; x++) + { + for(y = 0; y < final[x].numpoints; y++) + { + check = DotProduct(leaf1->portals[i]->plane.normal, final[x].points[y]) - leaf1->portals[i]->plane.dist; + + if(check <= g_maxdistance) + return check; + } + } + + delete final; + } + + // Switch leaf 1 and 2 + for(i = 0; i < leaf2->numportals; i++) + { + CalcPortalBoundsAndClipPortals(leaf2->portals[i], leaf1, &final, num_finals); + + // Minimum point distance + for(x = 0; x < num_finals; x++) + { + for(y = 0; y < final[x].numpoints; y++) + { + check = DotProduct(leaf2->portals[i]->plane.normal, final[x].points[y]) - leaf2->portals[i]->plane.dist; + + if(check <= g_maxdistance) + return check; + } + } + + delete final; + } + + return 9E10; +} + +// AJM: MVD +// ===================================================================================== +// CalcSplitsAndDotProducts +// This function finds the splits of the leaf, and generates windings (if applicable) +// ===================================================================================== +static float CalcSplitsAndDotProducts(plane_t *org_split_plane, leaf_t *leaf1, leaf_t *leaf2, plane_t *bounds, int num_bounds) +{ + int i, j, k, l; + + portal_t *splits = NULL; + int num_splits; + + float dist; + float min_dist = 999999999.999; + + vec3_t i_points[MAX_POINTS_ON_FIXED_WINDING * MAX_PORTALS_ON_LEAF * 2]; + vec3_t delta; + int num_points = 0; + + // First get splits + MakeSplitPortalList(leaf1, leaf2, &splits, &num_splits); + + if(!num_splits) + return min_dist; + + // If the number of splits = 1, then clip the plane using the boundary windings + if(num_splits == 1) + { + Winding wind(splits[0].plane.normal, splits[0].plane.dist); + + for(i = 0; i < num_bounds; i++) + { + wind.Chop(bounds[i].normal, bounds[i].dist); + } + + // The wind is chopped - get closest dot product + for(i = 0; i < wind.m_NumPoints; i++) + { + dist = DotProduct(wind.m_Points[i], org_split_plane->normal) - org_split_plane->dist; + + min_dist = qmin(min_dist, dist); + } + + return min_dist; + } + + // In this case, we have more than one split point, and we must calculate all intersections + // Properties of convex objects allow us to assume that these intersections will be the closest + // points to the other leaf, and our other checks before this eliminate exception cases + + // Loop through each split portal, and using an inside loop, loop through every OTHER split portal + // Common portal points in more than one split portal are intersections! + for(i = 0; i < num_splits; i++) + { + for(j = 0; j < num_splits; j++) + { + if(i == j) + { + continue; + } + + // Loop through each point on both portals + for(k = 0; k < splits[i].winding->numpoints; k++) + { + for(l = 0; l < splits[j].winding->numpoints; l++) + { + VectorSubtract(splits[i].winding->points[k], splits[j].winding->points[l], delta); + + if(VectorLength(delta) < EQUAL_EPSILON) + { + memcpy(i_points[num_points++], splits[i].winding->points[k], sizeof(vec3_t)); + } + } + } + } + } + + // Loop through each intersection point and check + for(i = 0; i < num_points; i++) + { + dist = DotProduct(i_points[i], org_split_plane->normal) - org_split_plane->dist; + + min_dist = qmin(min_dist, dist); + } + + if(splits) + delete [] splits; + + return min_dist; +} + +#endif +#endif // HLVIS_MAXDIST + +// ===================================================================================== +// BasePortalVis +// ===================================================================================== +void BasePortalVis(int unused) +{ + int i, j, k; + portal_t* tp; + portal_t* p; + float d; + winding_t* w; + byte portalsee[PORTALSEE_SIZE]; + const int portalsize = (g_numportals * 2); + +#ifdef ZHLT_NETVIS + { + i = unused; +#else + while (1) + { + i = GetThreadWork(); + if (i == -1) + break; +#endif + p = g_portals + i; + + p->mightsee = (byte*)calloc(1, g_bitbytes); + + memset(portalsee, 0, portalsize); + +#if ZHLT_ZONES + UINT32 zone = p->zone; +#endif + + for (j = 0, tp = g_portals; j < portalsize; j++, tp++) + { + if (j == i) + { + continue; + } +#if ZHLT_ZONES + if (g_Zones->check(zone, tp->zone)) + { + continue; + } +#endif + + w = tp->winding; + for (k = 0; k < w->numpoints; k++) + { + d = DotProduct(w->points[k], p->plane.normal) - p->plane.dist; + if (d > ON_EPSILON) + { + break; + } + } + if (k == w->numpoints) + { + continue; // no points on front + } + + + w = p->winding; + for (k = 0; k < w->numpoints; k++) + { + d = DotProduct(w->points[k], tp->plane.normal) - tp->plane.dist; + if (d < -ON_EPSILON) + { + break; + } + } + if (k == w->numpoints) + { + continue; // no points on front + } + + + portalsee[j] = 1; + } + + SimpleFlood(p->mightsee, p->leaf, portalsee, &p->nummightsee); + Verbose("portal:%4i nummightsee:%4i \n", i, p->nummightsee); + } +} + +#ifdef HLVIS_MAXDIST +#ifdef HLVIS_MAXDIST_NEW +bool BestNormalFromWinding (const vec3_t *points, int numpoints, vec3_t &normal_out) +{ + const vec3_t *pt1, *pt2, *pt3; + int k; + vec3_t d, normal, edge; + vec_t dist, maxdist; + if (numpoints < 3) + { + return false; + } + pt1 = &points[0]; + maxdist = -1; + for (k = 0; k < numpoints; k++) + { + if (&points[k] == pt1) + { + continue; + } + VectorSubtract (points[k], *pt1, edge); + dist = DotProduct (edge, edge); + if (dist > maxdist) + { + maxdist = dist; + pt2 = &points[k]; + } + } + if (maxdist <= ON_EPSILON * ON_EPSILON) + { + return false; + } + maxdist = -1; + VectorSubtract (*pt2, *pt1, edge); + VectorNormalize (edge); + for (k = 0; k < numpoints; k++) + { + if (&points[k] == pt1 || &points[k] == pt2) + { + continue; + } + VectorSubtract (points[k], *pt1, d); + CrossProduct (edge, d, normal); + dist = DotProduct (normal, normal); + if (dist > maxdist) + { + maxdist = dist; + pt3 = &points[k]; + } + } + if (maxdist <= ON_EPSILON * ON_EPSILON) + { + return false; + } + VectorSubtract (*pt3, *pt1, d); + CrossProduct (edge, d, normal); + VectorNormalize (normal); + if (pt3 < pt2) + { + VectorScale (normal, -1, normal); + } + VectorCopy (normal, normal_out); + return true; +} + +vec_t WindingDist (const winding_t *w[2]) +{ + vec_t minsqrdist = 99999999.0 * 99999999.0; + vec_t sqrdist; + int a, b; + // point to point + for (a = 0; a < w[0]->numpoints; a++) + { + for (b = 0; b < w[1]->numpoints; b++) + { + vec3_t v; + VectorSubtract (w[0]->points[a], w[1]->points[b], v); + sqrdist = DotProduct (v, v); + if (sqrdist < minsqrdist) + { + minsqrdist = sqrdist; + } + } + } + // point to edge + for (int side = 0; side < 2; side++) + { + for (a = 0; a < w[side]->numpoints; a++) + { + for (b = 0; b < w[!side]->numpoints; b++) + { + const vec3_t &p = w[side]->points[a]; + const vec3_t &p1 = w[!side]->points[b]; + const vec3_t &p2 = w[!side]->points[(b + 1) % w[!side]->numpoints]; + vec3_t delta; + vec_t frac; + vec3_t v; + VectorSubtract (p2, p1, delta); + if (VectorNormalize (delta) <= ON_EPSILON) + { + continue; + } + frac = DotProduct (p, delta) - DotProduct (p1, delta); + if (frac <= ON_EPSILON || frac >= (DotProduct (p2, delta) - DotProduct (p1, delta)) - ON_EPSILON) + { + // p1 or p2 is closest to p + continue; + } + VectorMA (p1, frac, delta, v); + VectorSubtract (p, v, v); + sqrdist = DotProduct (v, v); + if (sqrdist < minsqrdist) + { + minsqrdist = sqrdist; + } + } + } + } + // edge to edge + for (a = 0; a < w[0]->numpoints; a++) + { + for (b = 0; b < w[1]->numpoints; b++) + { + const vec3_t &p1 = w[0]->points[a]; + const vec3_t &p2 = w[0]->points[(a + 1) % w[0]->numpoints]; + const vec3_t &p3 = w[1]->points[b]; + const vec3_t &p4 = w[1]->points[(b + 1) % w[1]->numpoints]; + vec3_t delta1; + vec3_t delta2; + vec3_t normal; + vec3_t normal1; + vec3_t normal2; + VectorSubtract (p2, p1, delta1); + VectorSubtract (p4, p3, delta2); + CrossProduct (delta1, delta2, normal); + if (!VectorNormalize (normal)) + { + continue; + } + CrossProduct (normal, delta1, normal1); // same direction as delta2 + CrossProduct (delta2, normal, normal2); // same direction as delta1 + if (VectorNormalize (normal1) <= ON_EPSILON || VectorNormalize (normal2) <= ON_EPSILON) + { + continue; + } + if (DotProduct (p3, normal1) >= DotProduct (p1, normal1) - ON_EPSILON || + DotProduct (p4, normal1) <= DotProduct (p1, normal1) + ON_EPSILON || + DotProduct (p1, normal2) >= DotProduct (p3, normal2) - ON_EPSILON || + DotProduct (p2, normal2) <= DotProduct (p3, normal2) + ON_EPSILON ) + { + // the edges are not crossing when viewed along normal + continue; + } + sqrdist = DotProduct (p3, normal) - DotProduct (p1, normal); + sqrdist = sqrdist * sqrdist; + if (sqrdist < minsqrdist) + { + minsqrdist = sqrdist; + } + } + } + // point to face and edge to face + for (int side = 0; side < 2; side++) + { + vec3_t planenormal; + vec_t planedist; + vec3_t *boundnormals; + vec_t *bounddists; + if (!BestNormalFromWinding (w[!side]->points, w[!side]->numpoints, planenormal)) + { + continue; + } + planedist = DotProduct (planenormal, w[!side]->points[0]); + hlassume (boundnormals = (vec3_t *)malloc (w[!side]->numpoints * sizeof (vec3_t)), assume_NoMemory); + hlassume (bounddists = (vec_t *)malloc (w[!side]->numpoints * sizeof (vec_t)), assume_NoMemory); + // build boundaries + for (b = 0; b < w[!side]->numpoints; b++) + { + vec3_t v; + const vec3_t &p1 = w[!side]->points[b]; + const vec3_t &p2 = w[!side]->points[(b + 1) % w[!side]->numpoints]; + VectorSubtract (p2, p1, v); + CrossProduct (v, planenormal, boundnormals[b]); + if (!VectorNormalize (boundnormals[b])) + { + bounddists[b] = 1.0; + } + else + { + bounddists[b] = DotProduct (p1, boundnormals[b]); + } + } + for (a = 0; a < w[side]->numpoints; a++) + { + const vec3_t &p = w[side]->points[a]; + for (b = 0; b < w[!side]->numpoints; b++) + { + if (DotProduct (p, boundnormals[b]) - bounddists[b] >= -ON_EPSILON) + { + break; + } + } + if (b < w[!side]->numpoints) + { + continue; + } + sqrdist = DotProduct (p, planenormal) - planedist; + sqrdist = sqrdist * sqrdist; + if (sqrdist < minsqrdist) + { + minsqrdist = sqrdist; + } + } + for (a = 0; a < w[side]->numpoints; a++) + { + const vec3_t &p1 = w[side]->points[a]; + const vec3_t &p2 = w[side]->points[(a + 1) % w[side]->numpoints]; + vec_t dist1 = DotProduct (p1, planenormal) - planedist; + vec_t dist2 = DotProduct (p2, planenormal) - planedist; + vec3_t delta; + vec_t frac; + vec3_t v; + if (dist1 > ON_EPSILON && dist2 < -ON_EPSILON || dist1 < -ON_EPSILON && dist2 > ON_EPSILON) + { + frac = dist1 / (dist1 - dist2); + VectorSubtract (p2, p1, delta); + VectorMA (p1, frac, delta, v); + for (b = 0; b < w[!side]->numpoints; b++) + { + if (DotProduct (v, boundnormals[b]) - bounddists[b] >= -ON_EPSILON) + { + break; + } + } + if (b < w[!side]->numpoints) + { + continue; + } + minsqrdist = 0; + } + } + free (boundnormals); + free (bounddists); + } + return (sqrt (minsqrdist)); +} +#endif +// AJM: MVD +// ===================================================================================== +// MaxDistVis +// ===================================================================================== +void MaxDistVis(int unused) +{ + int i, j, k, m; + int a, b, c, d; + leaf_t *l; + leaf_t *tl; + plane_t *boundary = NULL; + vec3_t delta; + + float new_dist; + + unsigned offset_l; + unsigned bit_l; + + unsigned offset_tl; + unsigned bit_tl; + + while(1) + { + i = GetThreadWork(); + if (i == -1) + break; + + l = &g_leafs[i]; + + for(j = i + 1, tl = g_leafs + j; j < g_portalleafs; j++, tl++) + { +#ifdef HLVIS_MAXDIST_NEW + + offset_l = i >> 3; + bit_l = (1 << (i & 7)); + + offset_tl = j >> 3; + bit_tl = (1 << (j & 7)); + + { + bool visible = false; + for (k = 0; k < l->numportals; k++) + { + if (l->portals[k]->visbits[offset_tl] & bit_tl) + { + visible = true; + } + } + for (m = 0; m < tl->numportals; m++) + { + if (tl->portals[m]->visbits[offset_l] & bit_l) + { + visible = true; + } + } + if (!visible) + { + goto NoWork; + } + } + + // rough check + { + vec3_t v; + vec_t dist; + const winding_t *w; + const leaf_t *leaf[2] = {l, tl}; + vec3_t center[2]; + vec_t radius[2]; + int count[2]; + for (int side = 0; side < 2; side++) + { + count[side] = 0; + VectorClear (center[side]); + for (a = 0; a < leaf[side]->numportals; a++) + { + w = leaf[side]->portals[a]->winding; + for (b = 0; b < w->numpoints; b++) + { + VectorAdd (w->points[b], center[side], center[side]); + count[side]++; + } + } + } + if (!count[0] && !count[1]) + { + goto Work; + } + for (int side = 0; side < 2; side++) + { + VectorScale (center[side], 1.0 / (vec_t)count[side], center[side]); + radius[side] = 0; + for (a = 0; a < leaf[side]->numportals; a++) + { + w = leaf[side]->portals[a]->winding; + for (b = 0; b < w->numpoints; b++) + { + VectorSubtract (w->points[b], center[side], v); + dist = DotProduct (v, v); + radius[side] = qmax (radius[side], dist); + } + } + radius[side] = sqrt (radius[side]); + } + VectorSubtract (center[0], center[1], v); + dist = VectorLength (v); + if (qmax (dist - radius[0] - radius[1], 0) >= g_maxdistance - ON_EPSILON) + { + goto Work; + } + if (dist + radius[0] + radius[1] < g_maxdistance - ON_EPSILON) + { + goto NoWork; + } + } + + // exact check + { + vec_t mindist = 9999999999; + vec_t dist; + for (k = 0; k < l->numportals; k++) + { + for (m = 0; m < tl->numportals; m++) + { + const winding_t *w[2]; + w[0] = l->portals[k]->winding; + w[1] = tl->portals[m]->winding; + dist = WindingDist (w); + mindist = qmin (dist, mindist); + } + } + if (mindist >= g_maxdistance - ON_EPSILON) + { + goto Work; + } + else + { + goto NoWork; + } + } +#else + if(j == i) // Ideally, should never be true + { + continue; + } + + // If they already can't see each other, no use checking + if(DisjointLeafVis(i, j)) + { + continue; + } + + new_dist = GetShortestDistance(l, tl); + + if(new_dist <= g_maxdistance) + continue; + + // Try out our NEW, IMPROVED ALGORITHM!!!! + + // Get a portal on Leaf 1 that completely seperates the two leafs + /*split = GetSplitPortal(l, tl); + + if(!split) + continue; + + // We have a split, so create the bounds + GetPortalBounds(split, &boundary); + + // Now get the dot product for all points on the other leaf + max_dist = 999999999.999; + + /// Do the first check if mode is >= 2 + if(g_mdmode >= 2) + { + for(k = 0; k < tl->numportals; k++) + { + for(m = 0; m < tl->portals[k]->winding->numpoints; m++) + { + for(n = 0; n < split->winding->numpoints; n++) // numpoints of split portals = number of boundaries + { + dist = DotProduct(tl->portals[k]->winding->points[m], boundary[n].normal) - boundary[n].dist; + + if(dist < -ON_EPSILON) + { + // Outside boundaries + //max_dot = MaxDotProduct(tl->portals[k]->winding->points[m], boundary, split->winding->numpoints); + + //max_dist = qmin(max_dist, max_dot); + + // Break so we don't do inside boundary check + break; + } + } + if(n < split->winding->numpoints) + continue; + + // We found a point that's inside all the boundries! + new_dist = DotProduct(tl->portals[k]->winding->points[m], split->plane.normal) - split->plane.dist; + + max_dist = qmin(max_dist, new_dist); + } + } + } + + // This is now a special check. If Leaf 2 has a split plane, we generate a polygon by clipping the plane + // with the borders. We then get the minimum dot products. If more than one split plane, use intersection. + // Only do this is g_mdmode is 3 + if(g_mdmode >= 3) // For future mode expansion + { + new_dist = CalcSplitsAndDotProducts(&split->plane, tl, l, boundary, split->winding->numpoints); + + max_dist = qmin(max_dist, new_dist); + }*/ + + // Third and final check. If the whole of leaf2 is outside of leaf1 boundaries, this one will catch it + // Basic "every point to every point" type of deal :) + // This is done by default all the time + for(a = 0; a < l->numportals; a++) + { + for(b = 0; b < tl->numportals; b++) + { + for(c = 0; c < l->portals[a]->winding->numpoints; c++) + { + for(d = 0; d < tl->portals[b]->winding->numpoints; d++) + { + VectorSubtract(l->portals[a]->winding->points[c], tl->portals[b]->winding->points[d], delta); + + if(VectorLength(delta) <= g_maxdistance) + goto NoWork; + } + } + } + } +#endif + +#ifdef HLVIS_MAXDIST_NEW +Work: + ThreadLock (); + for (k = 0; k < l->numportals; k++) + { + l->portals[k]->visbits[offset_tl] &= ~bit_tl; + } + for (m = 0; m < tl->numportals; m++) + { + tl->portals[m]->visbits[offset_l] &= ~bit_l; + } + ThreadUnlock (); +#else + offset_l = i >> 3; + bit_l = (1 << (i & 7)); + + offset_tl = j >> 3; + bit_tl = (1 << (j & 7)); + + for(k = 0; k < l->numportals; k++) + { + for(m = 0; m < tl->numportals; m++) + { + if(l->portals[k]->status != stat_none) + l->portals[k]->visbits[offset_tl] &= ~bit_tl; + else + l->portals[k]->mightsee[offset_tl] &= ~bit_tl; + + if(tl->portals[m]->status != stat_none) + tl->portals[m]->visbits[offset_l] &= ~bit_l; + else + tl->portals[m]->mightsee[offset_l] &= ~bit_l; + } + } +#endif + +NoWork: + continue; // Hack to keep label from causing compile error + } + } + + // Release potential memory + if(boundary) + delete [] boundary; +} +#endif // HLVIS_MAXDIST + +#ifdef SYSTEM_WIN32 +#pragma warning(pop) +#endif diff --git a/src/zhlt-vluzacn/hlvis/hlvis.vcproj b/src/zhlt-vluzacn/hlvis/hlvis.vcproj new file mode 100644 index 0000000..823cf6d --- /dev/null +++ b/src/zhlt-vluzacn/hlvis/hlvis.vcproj @@ -0,0 +1,365 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/zhlt-vluzacn/hlvis/hlvis.vcxproj b/src/zhlt-vluzacn/hlvis/hlvis.vcxproj new file mode 100644 index 0000000..062c9ae --- /dev/null +++ b/src/zhlt-vluzacn/hlvis/hlvis.vcxproj @@ -0,0 +1,174 @@ + + + + + Release + Win32 + + + Release + x64 + + + + + + {76051CAC-5741-AF85-0C95-3A214F58D9AD} + + + + Application + false + MultiByte + v140 + + + Application + false + MultiByte + v140 + + + + + + + + + + + + + + + .\Release\ + .\Release\ + false + + + .\Release_x64\ + .\Release_x64\ + false + + + + MultiThreaded + Default + true + true + MaxSpeed + true + Level3 + ..\common;..\template;%(AdditionalIncludeDirectories) + HLVIS;VERSION_32BIT;NDEBUG;WIN32;_CONSOLE;SYSTEM_WIN32;STDC_HEADERS;%(PreprocessorDefinitions) + .\Release\ + true + .\Release\hlvis.pch + .\Release\ + .\Release\ + true + true + + + .\Release\hlvis.tlb + + + 0x0409 + NDEBUG;%(PreprocessorDefinitions) + + + true + .\Release\hlvis.bsc + + + true + Console + false + .\Release\hlvis.exe + binmode.obj;%(AdditionalDependencies) + 4194304 + 1048576 + false + + + + + MultiThreaded + Default + true + true + MaxSpeed + true + Level3 + ..\common;..\template;%(AdditionalIncludeDirectories) + HLVIS;VERSION_64BIT;NDEBUG;WIN32;_CONSOLE;SYSTEM_WIN32;STDC_HEADERS;%(PreprocessorDefinitions) + .\Release_x64\ + true + .\Release_x64\hlvis.pch + .\Release_x64\ + .\Release_x64\ + true + true + + + .\Release_x64\hlvis.tlb + + + 0x0409 + NDEBUG;%(PreprocessorDefinitions) + + + true + .\Release_x64\hlvis.bsc + + + true + Console + false + .\Release_x64\hlvis.exe + binmode.obj;%(AdditionalDependencies) + 4194304 + 1048576 + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/zhlt-vluzacn/hlvis/hlvis.vcxproj.filters b/src/zhlt-vluzacn/hlvis/hlvis.vcxproj.filters new file mode 100644 index 0000000..6b08e2b --- /dev/null +++ b/src/zhlt-vluzacn/hlvis/hlvis.vcxproj.filters @@ -0,0 +1,120 @@ + + + + + {fa63ca73-d52e-4403-b61e-78b2094b9a49} + cpp;c;cxx;rc;def;r;odl;idl;hpj;bat;for;f90 + + + {03feec92-a451-440f-9819-099c78f94bd4} + + + {ae1a5973-32d3-4d83-bbd9-44b2d416ca7a} + h;hpp;hxx;hm;inl;fi;fd + + + {a270be60-5229-4d76-aded-9b909ed08a6c} + ico;cur;bmp;dlg;rc2;rct;bin;cnt;rtf;gif;jpg;jpeg;jpe + + + + + Source Files\common + + + Source Files\common + + + Source Files\common + + + Source Files\common + + + Source Files\common + + + Source Files\common + + + Source Files\common + + + Source Files\common + + + Source Files\common + + + Source Files\common + + + Source Files\common + + + Source Files + + + Source Files + + + Source 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 + + + \ No newline at end of file diff --git a/src/zhlt-vluzacn/hlvis/vis.cpp b/src/zhlt-vluzacn/hlvis/vis.cpp new file mode 100644 index 0000000..454bed8 --- /dev/null +++ b/src/zhlt-vluzacn/hlvis/vis.cpp @@ -0,0 +1,2082 @@ +/* + + VISIBLE INFORMATION SET -aka- V I S + + Code based on original code from Valve Software, + Modified by Sean "Zoner" Cavanaugh (seanc@gearboxsoftware.com) with permission. + Modified by Tony "Merl" Moore (merlinis@bigpond.net.au) + Contains code by Skyler "Zipster" York (zipster89134@hotmail.com) - Included with permission. + +*/ + +#include "vis.h" +#ifdef ZHLT_LANGFILE +#ifdef SYSTEM_WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#endif +#endif + +#ifdef ZHLT_NETVIS +#include "zlib.h" +#endif + +/* + + NOTES + +*/ + +int g_numportals = 0; +unsigned g_portalleafs = 0; + +portal_t* g_portals; + +leaf_t* g_leafs; +#ifdef ZHLT_DETAILBRUSH +int *g_leafstarts; +int *g_leafcounts; +int g_leafcount_all; +#endif + +// AJM: MVD +#ifdef HLVIS_MAXDIST +#ifndef HLVIS_MAXDIST_NEW +byte* g_mightsee; +visblocker_t g_visblockers[MAX_VISBLOCKERS]; +int g_numvisblockers = 0; +#endif +#endif +// + +static byte* vismap; +static byte* vismap_p; +static byte* vismap_end; // past visfile +static int originalvismapsize; + +byte* g_uncompressed; // [bitbytes*portalleafs] + +unsigned g_bitbytes; // (portalleafs+63)>>3 +unsigned g_bitlongs; + +bool g_fastvis = DEFAULT_FASTVIS; +bool g_fullvis = DEFAULT_FULLVIS; +bool g_estimate = DEFAULT_ESTIMATE; +bool g_chart = DEFAULT_CHART; +bool g_info = DEFAULT_INFO; + +#ifdef HLVIS_MAXDIST +// AJM: MVD +unsigned int g_maxdistance = DEFAULT_MAXDISTANCE_RANGE; +//bool g_postcompile = DEFAULT_POST_COMPILE; +// +#endif +#ifdef HLVIS_OVERVIEW +const int g_overview_max = MAX_MAP_ENTITIES; +overview_t g_overview[g_overview_max]; +int g_overview_count = 0; +leafinfo_t* g_leafinfos = NULL; +#endif + +#ifdef ZHLT_PROGRESSFILE // AJM +char* g_progressfile = DEFAULT_PROGRESSFILE; // "-progressfile path" +#endif + +static int totalvis = 0; + +#if ZHLT_ZONES +Zones* g_Zones; +#endif + +#ifdef ZHLT_NETVIS +// -- these are definitions and initializations of C/CPP common variables +volatile int g_visportalindex = UNINITIALIZED_PORTAL_INDEX; // a client's portal index : current portalindex being worked on + + +volatile int g_visportals = 0; // the total portals in the map +volatile int g_visleafs = 0; // the total portal leafs in the map +volatile int g_vislocalportal = 0; // number of portals solved locally +volatile enum vis_states g_visstate = VIS_STARTUP; // current step of execution +volatile enum vis_modes g_vismode = VIS_MODE_NULL; // style of execution (client or server) +volatile int g_visleafthread = 0; // control flag (are we ready to leafthread) +unsigned int g_rate = DEFAULT_NETVIS_RATE; +volatile double g_starttime = 0; // Start time (from I_FloatTime()) +volatile unsigned long g_idletime = 0; // Accumulated idle time in milliseconds (rolls over after 46.7 days, hopefully a vis client wont run that long) +volatile unsigned long g_serverindex = 0; // client only variable, server index for calculating percentage indicators on the client +short g_port = DEFAULT_NETVIS_PORT; +const char* g_server_addr = NULL; + + +volatile bool g_bsp_downloaded = false; // Client variable +volatile bool g_prt_downloaded = false; // Client variable +volatile bool g_mightsee_downloaded = false; // Client variable + +char* g_bsp_image = NULL; // Client/Server variable : Server uses it for cache for connecting clients, clients download it to memory to not require filesystem usage +char* g_prt_image = NULL; // Client/Server variable : Server uses it for cache for connecting clients, clients download it to memory to not require filesystem usage +unsigned long g_bsp_compressed_size = 0; // Server variable +unsigned long g_prt_compressed_size = 0; // Server variable +unsigned long g_bsp_size = 0; // Server variable +unsigned long g_prt_size = 0; // Server variable +#endif + +#ifdef ZHLT_INFO_COMPILE_PARAMETERS +// AJM: addded in +// ===================================================================================== +// GetParamsFromEnt +// this function is called from parseentity when it encounters the +// info_compile_parameters entity. each tool should have its own version of this +// to handle its own specific settings. +// ===================================================================================== +void GetParamsFromEnt(entity_t* mapent) +{ + int iTmp; + + Log("\nCompile Settings detected from info_compile_parameters entity\n"); + + // verbose(choices) : "Verbose compile messages" : 0 = [ 0 : "Off" 1 : "On" ] + iTmp = IntForKey(mapent, "verbose"); + if (iTmp == 1) + { + g_verbose = true; + } + else if (iTmp == 0) + { + g_verbose = false; + } + Log("%30s [ %-9s ]\n", "Compile Option", "setting"); + Log("%30s [ %-9s ]\n", "Verbose Compile Messages", g_verbose ? "on" : "off"); + + // estimate(choices) :"Estimate Compile Times?" : 0 = [ 0: "Yes" 1: "No" ] + if (IntForKey(mapent, "estimate")) + { + g_estimate = true; + } + else + { + g_estimate = false; + } + Log("%30s [ %-9s ]\n", "Estimate Compile Times", g_estimate ? "on" : "off"); + + // priority(choices) : "Priority Level" : 0 = [ 0 : "Normal" 1 : "High" -1 : "Low" ] + if (!strcmp(ValueForKey(mapent, "priority"), "1")) + { + g_threadpriority = eThreadPriorityHigh; + Log("%30s [ %-9s ]\n", "Thread Priority", "high"); + } + else if (!strcmp(ValueForKey(mapent, "priority"), "-1")) + { + g_threadpriority = eThreadPriorityLow; + Log("%30s [ %-9s ]\n", "Thread Priority", "low"); + } + + /* + hlvis(choices) : "HLVIS" : 2 = + [ + 0 : "Off" + 1 : "Fast" + 2 : "Normal" + 3 : "Full" + ] + */ + iTmp = IntForKey(mapent, "hlvis"); + if (iTmp == 0) + { + Fatal(assume_TOOL_CANCEL, + "%s flag was not checked in info_compile_parameters entity, execution of %s cancelled", g_Program, g_Program); + CheckFatal(); + } + else if (iTmp == 1) + { + g_fastvis = true; + g_fullvis = false; + } + else if (iTmp == 2) + { + g_fastvis = false; + g_fullvis = false; + } + else if (iTmp == 3) + { + g_fullvis = true; + g_fastvis = false; + } + Log("%30s [ %-9s ]\n", "Fast VIS", g_fastvis ? "on" : "off"); + Log("%30s [ %-9s ]\n", "Full VIS", g_fullvis ? "on" : "off" ); + + /////////////////// + Log("\n"); +} +#endif + +// ===================================================================================== +// PlaneFromWinding +// ===================================================================================== +static void PlaneFromWinding(winding_t* w, plane_t* plane) +{ + vec3_t v1; + vec3_t 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 +// ===================================================================================== +static winding_t* NewWinding(const int points) +{ + winding_t* w; + int size; + + if (points > MAX_POINTS_ON_WINDING) + { + Error("NewWinding: %i points > MAX_POINTS_ON_WINDING", points); + } + +#ifdef ZHLT_64BIT_FIX + size = (int)(intptr_t)((winding_t*)0)->points[points]; +#else + size = (int)((winding_t*)0)->points[points]; +#endif + w = (winding_t*)calloc(1, size); + + return w; +} + +//============================================================================= + +///////// +// NETVIS +#ifdef ZHLT_NETVIS + +// ===================================================================================== +// GetPortalPtr +// converts a portal index to a pointer +// ===================================================================================== +portal_t* GetPortalPtr(const long index) +{ + if (index < (g_numportals * 2)) + { + return g_portals + index; + } + else + { + return (NULL); + } +} + + +// ===================================================================================== +// GetNextPortalIndex +// This is called by ClientSockets +// ===================================================================================== +int GetNextPortalIndex() +{ + int j; + int best = NO_PORTAL_INDEX; + portal_t* p; + portal_t* tp; + int min; + + ThreadLock(); + + min = 99999; + p = NULL; + + for (j = 0, tp = g_portals; j < g_numportals * 2; j++, tp++) + { + if (tp->nummightsee < min && tp->status == stat_none) + { + min = tp->nummightsee; + p = tp; + best = j; + } + } + + if (p) + { + p->status = stat_working; + } + else + { + best = NO_PORTAL_INDEX; // hack to return NO_PORTAL_INDEX to the queue'ing code + } + + ThreadUnlock(); + + return best; +} + +// ===================================================================================== +// AllPortalsDone +// returns true if all portals are done... +// ===================================================================================== +static int AllPortalsDone() +{ + const unsigned numportals = g_numportals * 2; + portal_t* tp; + + unsigned j; + for (j = 0, tp = g_portals; j < numportals; j++, tp++) + { + if (tp->status != stat_done) + { + return 0; + } + } + + return 1; +} + +#endif +// NETVIS +/////////// + +// ===================================================================================== +// GetNextPortal +// Returns the next portal for a thread to work on +// Returns the portals from the least complex, so the later ones can reuse the earlier information. +// ===================================================================================== +static portal_t* GetNextPortal() +{ + int j; + portal_t* p; + portal_t* tp; + int min; + +#ifdef ZHLT_NETVIS + if (g_vismode == VIS_MODE_SERVER) + { +#else + { + if (GetThreadWork() == -1) + { + return NULL; + } +#endif + ThreadLock(); + + min = 99999; + p = NULL; + + for (j = 0, tp = g_portals; j < g_numportals * 2; j++, tp++) + { + if (tp->nummightsee < min && tp->status == stat_none) + { + min = tp->nummightsee; + p = tp; +#ifdef ZHLT_NETVIS + g_visportalindex = j; +#endif + } + } + + if (p) + { + p->status = stat_working; + } + + ThreadUnlock(); + + return p; + } +#ifdef ZHLT_NETVIS + else // AS CLIENT + { + while (getWorkFromClientQueue() == WAITING_FOR_PORTAL_INDEX) + { + unsigned delay = 100; + + g_idletime += delay; // This is the only point where the portal work goes idle, so its easy to add up just how idle it is. + if (!isConnectedToServer()) + { + Error("Unexepected disconnect from server(1)\n"); + } + NetvisSleep(delay); + } + + if (g_visportalindex == NO_PORTAL_INDEX) + { + g_visstate = VIS_CLIENT_DONE; + Send_VIS_GOING_DOWN(g_ClientSession); + return NULL; + } + + // convert index to pointer + tp = GetPortalPtr(g_visportalindex); + + if (tp) + { + tp->status = stat_working; + } + return (tp); + } +#endif +} + +#ifdef HLVIS_MAXDIST + +#ifndef ZHLT_DETAILBRUSH +// AJM: MVD +// ===================================================================================== +// DecompressAll +// ===================================================================================== +void DecompressAll(void) +{ + int i; + byte *dest; + + for(i = 0; i < g_portalleafs; i++) + { + dest = g_uncompressed + i * g_bitbytes; + + DecompressVis((const unsigned char*)(g_dvisdata + (byte)g_dleafs[i + 1].visofs), dest, g_bitbytes); + } +} + +// AJM: MVD +// ===================================================================================== +// CompressAll +// ===================================================================================== +void CompressAll(void) +{ + int i, x = 0; + byte *dest; + byte *src; + byte compressed[MAX_MAP_LEAFS / 8]; + + vismap_p = vismap; + + for(i = 0; i < g_portalleafs; i++) + { + memset(&compressed, 0, sizeof(compressed)); + + src = g_uncompressed + i * g_bitbytes; + + // Compress all leafs into global compression buffer + x = CompressVis(src, g_bitbytes, compressed, sizeof(compressed)); + + dest = vismap_p; + vismap_p += x; + + if (vismap_p > vismap_end) + { + Error("Vismap expansion overflow"); + } + + g_dleafs[i + 1].visofs = dest - vismap; // leaf 0 is a common solid + + memcpy(dest, compressed, x); + } +} +#endif + +#endif // HLVIS_MAXDIST + +// ===================================================================================== +// LeafThread +// ===================================================================================== +#ifdef SYSTEM_WIN32 +#pragma warning(push) +#pragma warning(disable: 4100) // unreferenced formal parameter +#endif + +#ifndef ZHLT_NETVIS +static void LeafThread(int unused) +{ + portal_t* p; + + while (1) + { + if (!(p = GetNextPortal())) + { + return; + } + + PortalFlow(p); + + Verbose("portal:%4i mightsee:%4i cansee:%4i\n", (int)(p - g_portals), p->nummightsee, p->numcansee); + } +} +#endif //!ZHLT_NETVIS + +#ifdef ZHLT_NETVIS + +static void LeafThread(int unused) +{ + if (g_vismode == VIS_MODE_CLIENT) + { + portal_t* p; + + g_visstate = VIS_BASE_PORTAL_VIS_SERVER_WAIT; + Send_VIS_LEAFTHREAD(g_visleafs, g_visportals, g_bitbytes); + while (!g_visleafthread) + { + if (!isConnectedToServer()) + { + Error("Unexepected disconnect from server(2)\n"); + } + NetvisSleep(100); + } + g_visstate = VIS_PORTAL_FLOW; + Send_VIS_WANT_FULL_SYNC(); + + while (!g_NetvisAbort) + { + if (!(p = GetNextPortal())) + { + return; + } + + PortalFlow(p); + Send_VIS_DONE_PORTAL(g_visportalindex, p); + g_vislocalportal++; + } + } + else if (g_vismode == VIS_MODE_SERVER) + { +#if 0 + // Server does zero work in ZHLT netvis + g_visstate = VIS_WAIT_CLIENTS; + while (!g_NetvisAbort) + { + NetvisSleep(1000); + if (AllPortalsDone()) + { + g_visstate = VIS_POST; + return; + } + } +#else + portal_t* p; + + g_visstate = VIS_WAIT_CLIENTS; + while (!g_NetvisAbort) + { + if (!(p = GetNextPortal())) + { + if (AllPortalsDone()) + { + g_visstate = VIS_POST; + return; + } + NetvisSleep(1000); // No need to churn while waiting on slow clients + continue; + } + PortalFlow(p); + g_vislocalportal++; + } +#endif + } + else + { + hlassume(false, assume_VALID_NETVIS_STATE); + } +} +#endif + +#ifdef SYSTEM_WIN32 +#pragma warning(pop) +#endif + +// ===================================================================================== +// LeafFlow +// Builds the entire visibility list for a leaf +// ===================================================================================== +static void LeafFlow(const int leafnum) +{ + leaf_t* leaf; + byte* outbuffer; + byte compressed[MAX_MAP_LEAFS / 8]; + unsigned i; + unsigned j; + int k; + int tmp; + int numvis; + byte* dest; + portal_t* p; + + // + // flow through all portals, collecting visible bits + // + memset(compressed, 0, sizeof(compressed)); + outbuffer = g_uncompressed + leafnum * g_bitbytes; + leaf = &g_leafs[leafnum]; + tmp = 0; + + const unsigned offset = leafnum >> 3; + const unsigned bit = (1 << (leafnum & 7)); + + for (i = 0; i < leaf->numportals; i++) + { + p = leaf->portals[i]; + if (p->status != stat_done) + { + Error("portal not done (leaf %d)", leafnum); + } + + { + byte* dst = outbuffer; + byte* src = p->visbits; + for (j=0; jleaf); + for (k = 0; k < p->winding->numpoints; k++) + { + Log(" (%4.3f %4.3f %4.3f)\n", p->winding->points[k][0], p->winding->points[k][1], p->winding->points[k][2]); + } + Log("\n"); + } + } + + outbuffer[offset] |= bit; + +#ifdef HLVIS_OVERVIEW + if (g_leafinfos[leafnum].isoverviewpoint) + { + for (i = 0; i < g_portalleafs; i++) + { + outbuffer[i >> 3] |= (1 << (i & 7)); + } + } +#ifdef HLVIS_SKYBOXMODEL + for (i = 0; i < g_portalleafs; i++) + { + if (g_leafinfos[i].isskyboxpoint) + { + outbuffer[i >> 3] |= (1 << (i & 7)); + } + } +#endif +#endif + numvis = 0; + for (i = 0; i < g_portalleafs; i++) + { + if (outbuffer[i >> 3] & (1 << (i & 7))) + { + numvis++; + } + } + + // + // compress the bit string + // + Verbose("leaf %4i : %4i visible\n", leafnum, numvis); + totalvis += numvis; + +#ifdef ZHLT_DETAILBRUSH + byte buffer2[MAX_MAP_LEAFS / 8]; + int diskbytes = (g_leafcount_all + 7) >> 3; + memset (buffer2, 0, diskbytes); + for (i = 0; i < g_portalleafs; i++) + { + for (j = 0; j < g_leafcounts[i]; j++) + { + int srcofs = i >> 3; + int srcbit = 1 << (i & 7); + int dstofs = (g_leafstarts[i] + j) >> 3; + int dstbit = 1 << ((g_leafstarts[i] + j) & 7); + if (outbuffer[srcofs] & srcbit) + { + buffer2[dstofs] |= dstbit; + } + } + } + i = CompressVis (buffer2, diskbytes, compressed, sizeof (compressed)); +#else + i = CompressVis(outbuffer, g_bitbytes, compressed, sizeof(compressed)); +#endif + + dest = vismap_p; + vismap_p += i; + + if (vismap_p > vismap_end) + { + Error("Vismap expansion overflow"); + } + +#ifdef ZHLT_DETAILBRUSH + for (j = 0; j < g_leafcounts[leafnum]; j++) + { + g_dleafs[g_leafstarts[leafnum] + j + 1].visofs = dest - vismap; + } +#else + g_dleafs[leafnum + 1].visofs = dest - vismap; // leaf 0 is a common solid +#endif + + memcpy(dest, compressed, i); +} + +// ===================================================================================== +// CalcPortalVis +// ===================================================================================== +static void CalcPortalVis() +{ +#ifndef ZHLT_NETVIS + // g_fastvis just uses mightsee for a very loose bound + if (g_fastvis) + { + int i; + + for (i = 0; i < g_numportals * 2; i++) + { + g_portals[i].visbits = g_portals[i].mightsee; + g_portals[i].status = stat_done; + } + return; + } +#endif + +#ifdef ZHLT_NETVIS + LeafThread(0); +#else + NamedRunThreadsOn(g_numportals * 2, g_estimate, LeafThread); +#endif +} + +////////////// +// ZHLT_NETVIS + +#ifdef ZHLT_NETVIS +// ===================================================================================== +// CalcVis +// ===================================================================================== +static void CalcVis() +{ + unsigned lastpercent = 0; + int x, size; + + if (g_vismode == VIS_MODE_SERVER) + { + g_visstate = VIS_BASE_PORTAL_VIS; + Log("BasePortalVis: \n"); + + for (x = 0, size = g_numportals * 2; x < size; x++) + { + unsigned percent = (x * 100 / size); + + if (percent && (percent != lastpercent) && ((percent % 10) == 0)) + { + lastpercent = percent; +#ifdef ZHLT_CONSOLE + PrintConsole +#else + printf +#endif + ("%d%%....", percent); + } + BasePortalVis(x); + } +#ifdef ZHLT_CONSOLE + PrintConsole +#else + printf +#endif + ("\n"); + } + else + { + Send_VIS_WANT_MIGHTSEE_DATA(); + while(!g_mightsee_downloaded) + { + if (!isConnectedToServer()) + { + Error("Unexepected disconnect from server(3)\n"); + } + NetvisSleep(100); + } + } + + g_visportals = g_numportals; + g_visleafs = g_portalleafs; + + g_starttime = I_FloatTime(); + StartStatusDisplayThread(g_rate); + CalcPortalVis(); + + if (g_vismode == VIS_MODE_SERVER) + { + unsigned int i; + + for (i = 0; i < g_portalleafs; i++) + { + LeafFlow(i); + } + + Log("average leafs visible: %i\n", totalvis / g_portalleafs); + } +} +#endif + +#ifndef ZHLT_NETVIS + +#ifdef HLVIS_MAXDIST +#ifndef HLVIS_MAXDIST_NEW +// AJM: MVD +// ===================================================================================== +// GetVisBlock +// ===================================================================================== +visblocker_t *GetVisBlock(char *name) +{ + int i; + visblocker_t *v; + + for(i = 0, v = &g_visblockers[0]; i < g_numvisblockers; i++, v++) + { + if(!strcmp(name, v->name)) + return v; + } + + return NULL; +} + +// AJM: MVD +// ===================================================================================== +// InitVisBlock +// ===================================================================================== +static void InitVisBlock(void) +{ + char visfile[_MAX_PATH]; + int i; + int x = 0; + int num_blocks; + int num_sides; + +#ifdef ZHLT_DEFAULTEXTENSION_FIX + safe_snprintf(visfile, _MAX_PATH, "%s.vis", g_Mapname); +#else + strcpy(visfile, g_Mapname); + DefaultExtension(visfile, ".vis"); +#endif + + if(!q_exists(visfile)) + return; + + FILE *fp = fopen(visfile, "r"); + + if(!fp) + return; + + while(!feof(fp)) + { + fscanf(fp, "%s\n", g_visblockers[x].name); + + fscanf(fp, "%d\n", &num_blocks); + + for(i = 0; i < num_blocks; i++) + { + fscanf(fp, "%s\n", g_visblockers[x].blocknames[i]); + } + + g_visblockers[x].numnames = num_blocks; + + fscanf(fp, "%d\n", &num_sides); + + for(i = 0; i < num_sides; i++) + { + fscanf(fp, "%f %f %f %f\n", &g_visblockers[x].planes[i].normal[0], + &g_visblockers[x].planes[i].normal[1], + &g_visblockers[x].planes[i].normal[2], + &g_visblockers[x].planes[i].dist); + } + + g_visblockers[x].numplanes = num_sides; + g_visblockers[x].numleafs = 0; + + x++; + } + + g_numvisblockers = x; +} + +// AJM: MVD +// ===================================================================================== +// SetupVisBlockLeafs +// Set up the leafs for the visblocker +// ===================================================================================== +static void SetupVisBlockLeafs(void) +{ + int i, j, k, l, q; + visblocker_t *v; + leaf_t *leaf; + portal_t *p; + plane_t *plane; + float dist; + + for(i = 0, v = &g_visblockers[0]; i < g_numvisblockers; i++, v++) + { + for(j = 0, leaf = &g_leafs[0]; j < g_portalleafs; j++, leaf++) + { + for(q = 0, p = leaf->portals[0]; q < leaf->numportals; q++, p++) + { + for(k = 0; k < p->winding->numpoints; k++) + { + for(l = 0, plane = &v->planes[0]; l < v->numplanes; l++, plane++) + { + dist = DotProduct(p->winding->points[k], plane->normal) - plane->dist; + + if(dist > ON_EPSILON) + goto PostLoop; + } + } + } + +PostLoop: + if(q != leaf->numportals) + continue; + + // If we reach this point, then the portal is completely inside the visblocker + v->blockleafs[v->numleafs++] = j; + } + } +} +#endif + +// AJM: MVD +// ===================================================================================== +// SaveVisData +// ===================================================================================== +void SaveVisData(const char *filename) +{ + int i; + FILE *fp = fopen(filename, "wb"); + + if(!fp) + return; + + SafeWrite(fp, g_dvisdata, (vismap_p - g_dvisdata)); + + // BUG BUG BUG! + // Leaf offsets need to be saved too!!!! + for(i = 0; i < g_numleafs; i++) + { + SafeWrite(fp, &g_dleafs[i].visofs, sizeof(int)); + } + + fclose(fp); +} + +#ifndef HLVIS_MAXDIST_NEW +// AJM: MVD +// ===================================================================================== +// ResetPortalStatus +// FIX: Used to reset p->status to stat_none; now it justs frees p->visbits +// ===================================================================================== +void ResetPortalStatus(void) +{ + int i; + portal_t* p = g_portals; + + for(i = 0; i < g_numportals * 2; i++, p++) + { + //p->status = stat_none; + free(p->visbits); + } +} +#endif + +#endif // HLVIS_MAXDIST + + +// AJM UNDONE HLVIS_MAXDIST THIS!!!!!!!!!!!!! + +// AJM: MVD modified +// ===================================================================================== +// CalcVis +// ===================================================================================== +static void CalcVis() +{ + unsigned i; + char visdatafile[_MAX_PATH]; + +#ifdef ZHLT_DEFAULTEXTENSION_FIX + safe_snprintf(visdatafile, _MAX_PATH, "%s.vdt", g_Mapname); +#else + strcpy(visdatafile, g_Mapname); + DefaultExtension(visdatafile, ".vdt"); +#endif + + // Remove this file + unlink(visdatafile); + +/* if(g_postcompile) + { + if(!g_maxdistance) + { + Error("Must use -maxdistance parameter with -postcompile"); + } + + // Decompress everything so we can edit it + DecompressAll(); + + NamedRunThreadsOn(g_portalleafs, g_estimate, PostMaxDistVis); + + // Recompress it + CompressAll(); + } + else + {*/ +// InitVisBlock(); +// SetupVisBlockLeafs(); + + NamedRunThreadsOn(g_numportals * 2, g_estimate, BasePortalVis); + +// if(g_numvisblockers) +// NamedRunThreadsOn(g_numvisblockers, g_estimate, BlockVis); + + // First do a normal VIS, save to file, then redo MaxDistVis + + CalcPortalVis(); + + // + // assemble the leaf vis lists by oring and compressing the portal lists + // + for (i = 0; i < g_portalleafs; i++) + { + LeafFlow(i); + } + + Log("average leafs visible: %i\n", totalvis / g_portalleafs); + + if(g_maxdistance) + { + totalvis = 0; + + Log("saving visdata to %s...\n", visdatafile); + SaveVisData(visdatafile); + + // We need to reset the uncompressed variable and portal visbits + free(g_uncompressed); + g_uncompressed = (byte*)calloc(g_portalleafs, g_bitbytes); + + vismap_p = g_dvisdata; + + // We don't need to run BasePortalVis again + NamedRunThreadsOn(g_portalleafs, g_estimate, MaxDistVis); + + // No need to run this - MaxDistVis now writes directly to visbits after the initial VIS + //CalcPortalVis(); + + for (i = 0; i < g_portalleafs; i++) + { + LeafFlow(i); + } + +#ifndef HLVIS_MAXDIST_NEW + // FIX: Used to reset p->status to stat_none; now it justs frees p->visbits + ResetPortalStatus(); +#endif + + Log("average maxdistance leafs visible: %i\n", totalvis / g_portalleafs); + } +// } +} +#endif //!ZHLT_NETVIS + +// ZHLT_NETVIS +////////////// + +// ===================================================================================== +// CheckNullToken +// ===================================================================================== +static INLINE void FASTCALL CheckNullToken(const char*const token) +{ + if (token == NULL) + { + Error("LoadPortals: Damaged or invalid .prt file\n"); + } +} + +// ===================================================================================== +// LoadPortals +// ===================================================================================== +static void LoadPortals(char* portal_image) +{ + int i, j; + portal_t* p; + leaf_t* l; + int numpoints; + winding_t* w; + int leafnums[2]; + plane_t plane; + const char* const seperators = " ()\r\n\t"; + char* token; + + token = strtok(portal_image, seperators); + CheckNullToken(token); + if (!sscanf(token, "%u", &g_portalleafs)) + { + Error("LoadPortals: failed to read header: number of leafs"); + } + + token = strtok(NULL, seperators); + CheckNullToken(token); + if (!sscanf(token, "%i", &g_numportals)) + { + Error("LoadPortals: failed to read header: number of portals"); + } + + Log("%4i portalleafs\n", g_portalleafs); + Log("%4i numportals\n", g_numportals); + + g_bitbytes = ((g_portalleafs + 63) & ~63) >> 3; + g_bitlongs = g_bitbytes / sizeof(long); + + // each file portal is split into two memory portals + g_portals = (portal_t*)calloc(2 * g_numportals, sizeof(portal_t)); + g_leafs = (leaf_t*)calloc(g_portalleafs, sizeof(leaf_t)); +#ifdef HLVIS_OVERVIEW + g_leafinfos = (leafinfo_t*)calloc(g_portalleafs, sizeof(leafinfo_t)); +#endif +#ifdef ZHLT_DETAILBRUSH + g_leafcounts = (int*)calloc(g_portalleafs, sizeof(int)); + g_leafstarts = (int*)calloc(g_portalleafs, sizeof(int)); +#endif + + originalvismapsize = g_portalleafs * ((g_portalleafs + 7) / 8); + + vismap = vismap_p = g_dvisdata; + vismap_end = vismap + MAX_MAP_VISIBILITY; + +#ifdef ZHLT_DETAILBRUSH + if (g_portalleafs > MAX_MAP_LEAFS) + { // this may cause hlvis to overflow, because numportalleafs can be larger than g_numleafs in some special cases + Error ("Too many portalleafs (g_portalleafs(%d) > MAX_MAP_LEAFS(%d)).", g_portalleafs, MAX_MAP_LEAFS); + } + g_leafcount_all = 0; + for (i = 0; i < g_portalleafs; i++) + { + unsigned rval = 0; + token = strtok(NULL, seperators); + CheckNullToken(token); + rval += sscanf(token, "%i", &g_leafcounts[i]); + if (rval != 1) + { + Error("LoadPortals: read leaf %i failed", i); + } + g_leafstarts[i] = g_leafcount_all; + g_leafcount_all += g_leafcounts[i]; + } + if (g_leafcount_all != g_dmodels[0].visleafs) + { // internal error (this should never happen) + Error ("Corrupted leaf mapping (g_leafcount_all(%d) != g_dmodels[0].visleafs(%d)).", g_leafcount_all, g_dmodels[0].visleafs); + } +#endif +#ifdef HLVIS_OVERVIEW + for (i = 0; i < g_portalleafs; i++) + { + for (j = 0; j < g_overview_count; j++) + { +#ifdef ZHLT_DETAILBRUSH + int d = g_overview[j].visleafnum - g_leafstarts[i]; + if (0 <= d && d < g_leafcounts[i]) +#else + if (g_overview[j].visleafnum == i) +#endif + { +#ifdef HLVIS_SKYBOXMODEL + if (g_overview[j].reverse) + { + g_leafinfos[i].isskyboxpoint = true; + } + else + { + g_leafinfos[i].isoverviewpoint = true; + } +#else + g_leafinfos[i].isoverviewpoint = true; +#endif + } + } + } +#endif + for (i = 0, p = g_portals; i < g_numportals; i++) + { + unsigned rval = 0; + + token = strtok(NULL, seperators); + CheckNullToken(token); + rval += sscanf(token, "%i", &numpoints); + token = strtok(NULL, seperators); + CheckNullToken(token); + rval += sscanf(token, "%i", &leafnums[0]); + token = strtok(NULL, seperators); + CheckNullToken(token); + rval += sscanf(token, "%i", &leafnums[1]); + + if (rval != 3) + { + Error("LoadPortals: reading portal %i", i); + } + if (numpoints > MAX_POINTS_ON_WINDING) + { + Error("LoadPortals: portal %i has too many points", i); + } + if (((unsigned)leafnums[0] > g_portalleafs) || ((unsigned)leafnums[1] > g_portalleafs)) + { + Error("LoadPortals: reading portal %i", i); + } + + w = p->winding = NewWinding(numpoints); + w->original = true; + w->numpoints = numpoints; + + for (j = 0; j < numpoints; j++) + { + int k; + double v[3]; + unsigned rval = 0; + + token = strtok(NULL, seperators); + CheckNullToken(token); + rval += sscanf(token, "%lf", &v[0]); + token = strtok(NULL, seperators); + CheckNullToken(token); + rval += sscanf(token, "%lf", &v[1]); + token = strtok(NULL, seperators); + CheckNullToken(token); + rval += sscanf(token, "%lf", &v[2]); + + // scanf into double, then assign to vec_t + if (rval != 3) + { + Error("LoadPortals: reading portal %i", i); + } + for (k = 0; k < 3; k++) + { + w->points[j][k] = v[k]; + } + } + + // calc plane + PlaneFromWinding(w, &plane); + + // create forward portal + l = &g_leafs[leafnums[0]]; + hlassume(l->numportals < MAX_PORTALS_ON_LEAF, assume_MAX_PORTALS_ON_LEAF); + l->portals[l->numportals] = p; + l->numportals++; + + p->winding = w; + VectorSubtract(vec3_origin, plane.normal, p->plane.normal); + p->plane.dist = -plane.dist; + p->leaf = leafnums[1]; + p++; + + // create backwards portal + l = &g_leafs[leafnums[1]]; + hlassume(l->numportals < MAX_PORTALS_ON_LEAF, assume_MAX_PORTALS_ON_LEAF); + l->portals[l->numportals] = p; + l->numportals++; + + p->winding = NewWinding(w->numpoints); + p->winding->numpoints = w->numpoints; + for (j = 0; j < w->numpoints; j++) + { + VectorCopy(w->points[w->numpoints - 1 - j], p->winding->points[j]); + } + + p->plane = plane; + p->leaf = leafnums[0]; + p++; + + } +} + +// ===================================================================================== +// LoadPortalsByFilename +// ===================================================================================== +static void LoadPortalsByFilename(const char* const filename) +{ + char* file_image; + + if (!q_exists(filename)) + { + Error("Portal file '%s' does not exist, cannot vis the map\n", filename); + } + LoadFile(filename, &file_image); + LoadPortals(file_image); + free(file_image); +} + + +#if ZHLT_ZONES +// ===================================================================================== +// AssignPortalsToZones +// ===================================================================================== +static void AssignPortalsToZones() +{ + hlassert(g_Zones != NULL); + + UINT32 count = 0; + + portal_t* p; + UINT32 x; + + UINT32 tmp[20]; + memset(tmp, 0, sizeof(tmp)); + + UINT32 numportals = g_numportals * 2; + for (x=0, p=g_portals; xwinding; + UINT32 numpoints = w->numpoints; + + UINT32 y; + + for (y=0; ypoints[y]); + } + + p->zone = g_Zones->getZoneFromBounds(bounds); + tmp[p->zone]++; + if (p->zone) + { + count++; + } + } + + for (x=0; x<15; x++) + { + Log("Zone %2u : %u\n", x, tmp[x]); + } + Log("%u of %u portals were contained in func_vis zones\n", count, numportals); +} +#endif + +// ===================================================================================== +// Usage +// ===================================================================================== +static void Usage() +{ + Banner(); + + Log("\n-= %s Options =-\n\n", g_Program); +#ifdef ZHLT_CONSOLE + Log(" -console # : Set to 0 to turn off the pop-up console (default is 1)\n"); +#endif +#ifdef ZHLT_LANGFILE + Log(" -lang file : localization file\n"); +#endif + Log(" -full : Full vis\n"); + Log(" -fast : Fast vis\n\n"); +#ifdef ZHLT_NETVIS + Log(" -connect address : Connect to netvis server at address as a client\n"); + Log(" -server : Run as the netvis server\n"); + Log(" -port # : Use a non-standard port for netvis\n"); + Log(" -rate # : Alter the display update rate\n\n"); +#endif + Log(" -texdata # : Alter maximum texture memory limit (in kb)\n"); + Log(" -lightdata # : Alter maximum lighting memory limit (in kb)\n"); //lightdata //--vluzacn + Log(" -chart : display bsp statitics\n"); + Log(" -low | -high : run program an altered priority level\n"); + Log(" -nolog : don't generate the compile logfiles\n"); + Log(" -threads # : manually specify the number of threads to run\n"); +#ifdef SYSTEM_WIN32 + Log(" -estimate : display estimated time during compile\n"); +#endif +#ifdef ZHLT_PROGRESSFILE // AJM + Log(" -progressfile path : specify the path to a file for progress estimate output\n"); +#endif +#ifdef SYSTEM_POSIX + Log(" -noestimate : do not display continuous compile time estimates\n"); +#endif +#ifdef HLVIS_MAXDIST // AJM: MVD + Log(" -maxdistance # : Alter the maximum distance for visibility\n"); +#endif + Log(" -verbose : compile with verbose messages\n"); + Log(" -noinfo : Do not show tool configuration information\n"); + Log(" -dev # : compile with developer message\n\n"); + Log(" mapfile : The mapfile to compile\n\n"); + +#ifdef ZHLT_NETVIS + Log("\n" + "In netvis one computer must be the server and all the rest are the clients.\n" + "The server should be started with : netvis -server mapname\n" + "And the clients should be started with : netvis -connect servername\n" + "\n" + "The default socket it uses is 21212 and can be changed with -port\n" + "The default update rate is 60 seconds and can be changed with -rate\n"); + +#endif + + exit(1); +} + +// ===================================================================================== +// Settings +// ===================================================================================== +static void Settings() +{ + char* tmp; + + if (!g_info) + { + return; + } + + Log("\n-= Current %s Settings =-\n", g_Program); + Log("Name | Setting | Default\n" "-------------------|-----------|-------------------------\n"); + + // ZHLT Common Settings + if (DEFAULT_NUMTHREADS == -1) + { + Log("threads [ %7d ] [ Varies ]\n", g_numthreads); + } + else + { + Log("threads [ %7d ] [ %7d ]\n", g_numthreads, DEFAULT_NUMTHREADS); + } + + Log("verbose [ %7s ] [ %7s ]\n", g_verbose ? "on" : "off", DEFAULT_VERBOSE ? "on" : "off"); + Log("log [ %7s ] [ %7s ]\n", g_log ? "on" : "off", DEFAULT_LOG ? "on" : "off"); + Log("developer [ %7d ] [ %7d ]\n", g_developer, DEFAULT_DEVELOPER); + Log("chart [ %7s ] [ %7s ]\n", g_chart ? "on" : "off", DEFAULT_CHART ? "on" : "off"); + Log("estimate [ %7s ] [ %7s ]\n", g_estimate ? "on" : "off", DEFAULT_ESTIMATE ? "on" : "off"); + Log("max texture memory [ %7d ] [ %7d ]\n", g_max_map_miptex, DEFAULT_MAX_MAP_MIPTEX); + +#ifdef HLVIS_MAXDIST // AJM: MVD + Log("max vis distance [ %7d ] [ %7d ]\n", g_maxdistance, DEFAULT_MAXDISTANCE_RANGE); + //Log("max dist only [ %7s ] [ %7s ]\n", g_postcompile ? "on" : "off", DEFAULT_POST_COMPILE ? "on" : "off"); +#endif + + switch (g_threadpriority) + { + case eThreadPriorityNormal: + default: + tmp = "Normal"; + break; + case eThreadPriorityLow: + tmp = "Low"; + break; + case eThreadPriorityHigh: + tmp = "High"; + break; + } + Log("priority [ %7s ] [ %7s ]\n", tmp, "Normal"); + Log("\n"); + + // HLVIS Specific Settings + Log("fast vis [ %7s ] [ %7s ]\n", g_fastvis ? "on" : "off", DEFAULT_FASTVIS ? "on" : "off"); + Log("full vis [ %7s ] [ %7s ]\n", g_fullvis ? "on" : "off", DEFAULT_FULLVIS ? "on" : "off"); + +#ifdef ZHLT_NETVIS + if (g_vismode == VIS_MODE_SERVER) + { + Log("netvis mode [ Server ]\n"); + } + else if (g_vismode == VIS_MODE_CLIENT) + { + Log("netvis mode [ Client, connected to %s ]\n", g_server_addr); + } + Log("netvis port [ %7d ] [ %7d ]\n", g_port, DEFAULT_NETVIS_PORT); + Log("netvis display rate [ %7d ] [ %7d ]\n", g_rate, DEFAULT_NETVIS_RATE); +#endif + + Log("\n\n"); +} + +#ifdef HLVIS_OVERVIEW +int VisLeafnumForPoint(const vec3_t point) +{ + int nodenum; + vec_t dist; + dnode_t* node; + dplane_t* plane; + + nodenum = 0; + while (nodenum >= 0) + { + node = &g_dnodes[nodenum]; + plane = &g_dplanes[node->planenum]; + dist = DotProduct(point, plane->normal) - plane->dist; + if (dist >= 0.0) + { + nodenum = node->children[0]; + } + else + { + nodenum = node->children[1]; + } + } + + return -nodenum - 2; +} +#endif +// ===================================================================================== +// main +// ===================================================================================== +int main(const int argc, char** argv) +{ + char portalfile[_MAX_PATH]; + char source[_MAX_PATH]; + int i; + double start, end; + const char* mapname_from_arg = NULL; + +#ifdef ZHLT_NETVIS + g_Program = "netvis"; +#else + g_Program = "hlvis"; +#endif + +#ifdef ZHLT_PARAMFILE + int argcold = argc; + char ** argvold = argv; + { + int argc; + char ** argv; + ParseParamFile (argcold, argvold, argc, argv); + { +#endif +#ifdef ZHLT_CONSOLE + if (InitConsole (argc, argv) < 0) + Usage(); +#endif + if (argc == 1) + { + Usage(); + } + + for (i = 1; i < argc; i++) + { + if (!strcasecmp(argv[i], "-threads")) + { + if (i + 1 < argc) //added "1" .--vluzacn + { + g_numthreads = atoi(argv[++i]); + if (g_numthreads < 1) + { + Log("Expected value of at least 1 for '-threads'\n"); + Usage(); + } + } + else + { + Usage(); + } + } + +#ifdef ZHLT_CONSOLE + else if (!strcasecmp(argv[i], "-console")) + { +#ifndef SYSTEM_WIN32 + Warning("The option '-console #' is only valid for Windows."); +#endif + if (i + 1 < argc) + ++i; + else + Usage(); + } +#endif +#ifdef SYSTEM_WIN32 + else if (!strcasecmp(argv[i], "-estimate")) + { + g_estimate = true; + } +#endif +#ifdef SYSTEM_POSIX + else if (!strcasecmp(argv[i], "-noestimate")) + { + g_estimate = false; + } +#endif +#ifdef ZHLT_NETVIS + else if (!strcasecmp(argv[i], "-server")) + { + g_vismode = VIS_MODE_SERVER; + } + else if (!strcasecmp(argv[i], "-connect")) + { + if (i + 1 < argc) //added "1" .--vluzacn + { + g_vismode = VIS_MODE_CLIENT; + g_server_addr = argv[++i]; + } + else + { + Usage(); + } + } + else if (!strcasecmp(argv[i], "-port")) + { + if (i + 1 < argc) //added "1" .--vluzacn + { + g_port = atoi(argv[++i]); + } + else + { + Usage(); + } + } + else if (!strcasecmp(argv[i], "-rate")) + { + if (i + 1 < argc) //added "1" .--vluzacn + { + g_rate = atoi(argv[++i]); + } + else + { + Usage(); + } + if (g_rate < 5) + { + Log("Minimum -rate is 5, setting to 5 seconds\n"); + g_rate = 5; + } + if (g_rate > 900) + { + Log("Maximum -rate is 900, setting to 900 seconds\n"); + g_rate = 900; + } + } +#endif +#ifndef ZHLT_NETVIS + else if (!strcasecmp(argv[i], "-fast")) + { + Log("g_fastvis = true\n"); + g_fastvis = true; + } +#endif + else if (!strcasecmp(argv[i], "-full")) + { + g_fullvis = true; + } + else if (!strcasecmp(argv[i], "-dev")) + { + if (i + 1 < argc) //added "1" .--vluzacn + { + g_developer = (developer_level_t)atoi(argv[++i]); + } + else + { + Usage(); + } + } + else if (!strcasecmp(argv[i], "-verbose")) + { + g_verbose = true; + } + + else if (!strcasecmp(argv[i], "-noinfo")) + { + g_info = false; + } + else if (!strcasecmp(argv[i], "-chart")) + { + g_chart = true; + } + else if (!strcasecmp(argv[i], "-low")) + { + g_threadpriority = eThreadPriorityLow; + } + else if (!strcasecmp(argv[i], "-high")) + { + g_threadpriority = eThreadPriorityHigh; + } + else if (!strcasecmp(argv[i], "-nolog")) + { + g_log = false; + } + else if (!strcasecmp(argv[i], "-texdata")) + { + if (i + 1 < argc) //added "1" .--vluzacn + { + int x = atoi(argv[++i]) * 1024; + + //if (x > g_max_map_miptex) //--vluzacn + { + g_max_map_miptex = x; + } + } + else + { + Usage(); + } + } + else if (!strcasecmp(argv[i], "-lightdata")) //lightdata + { + if (i + 1 < argc) //--vluzacn + { + int x = atoi(argv[++i]) * 1024; + + //if (x > g_max_map_lightdata) //--vluzacn + { + g_max_map_lightdata = x; //--vluzacn + } + } + else + { + Usage(); + } + } + +#ifdef ZHLT_PROGRESSFILE // AJM + else if (!strcasecmp(argv[i], "-progressfile")) + { + if (i + 1 < argc) //added "1" .--vluzacn + { + g_progressfile = argv[++i]; + } + else + { + Log("Error: -progressfile: expected path to progress file following parameter\n"); + Usage(); + } + } +#endif + +#ifdef HLVIS_MAXDIST + // AJM: MVD + else if(!strcasecmp(argv[i], "-maxdistance")) + { + if(i + 1 < argc) //added "1" .--vluzacn + { + g_maxdistance = abs(atoi(argv[++i])); + } + else + { + Usage(); + } + } +/* else if(!strcasecmp(argv[i], "-postcompile")) + { + g_postcompile = true; + }*/ +#endif +#ifdef ZHLT_LANGFILE + else if (!strcasecmp (argv[i], "-lang")) + { + if (i + 1 < argc) + { + char tmp[_MAX_PATH]; +#ifdef SYSTEM_WIN32 + GetModuleFileName (NULL, tmp, _MAX_PATH); +#else + safe_strncpy (tmp, argv[0], _MAX_PATH); +#endif + LoadLangFile (argv[++i], tmp); + } + else + { + Usage(); + } + } +#endif + + else if (argv[i][0] == '-') + { + Log("Unknown option \"%s\"", argv[i]); + Usage(); + } + else if (!mapname_from_arg) + { + mapname_from_arg = argv[i]; + } + else + { + Log("Unknown option \"%s\"\n", argv[i]); + Usage(); + } + } + +#ifdef ZHLT_NETVIS + threads_InitCrit(); + + if (g_vismode == VIS_MODE_CLIENT) + { + ConnectToServer(g_server_addr, g_port); + + while (!isConnectedToServer()) + { + NetvisSleep(100); + } + Send_VIS_LOGIN(); + while (!g_clientid) + { + if (!isConnectedToServer()) + { + Error("Unexepected disconnect from server(4)\n"); + } + NetvisSleep(100); + } + + mapname_from_arg = "proxy"; + } + else if (g_vismode == VIS_MODE_SERVER) + { + StartNetvisSocketServer(g_port); + + if (!mapname_from_arg) + { + Log("No mapfile specified\n"); + Usage(); + } + } + else + { + Log("Netvis must be run either as a server (-server)\n" "or as a client (-connect servername)\n\n"); + Usage(); + } + +#else + + if (!mapname_from_arg) + { + Log("No mapfile specified\n"); + Usage(); + } +#endif + +#ifdef ZHLT_NETVIS + if (g_vismode == VIS_MODE_CLIENT) + { + g_log = false; + } +#endif + + safe_strncpy(g_Mapname, mapname_from_arg, _MAX_PATH); + FlipSlashes(g_Mapname); + StripExtension(g_Mapname); + OpenLog(g_clientid); + atexit(CloseLog); + ThreadSetDefault(); + ThreadSetPriority(g_threadpriority); +#ifdef ZHLT_PARAMFILE + LogStart(argcold, argvold); + { + int i; + Log("Arguments: "); + for (i = 1; i < argc; i++) + { + if (strchr(argv[i], ' ')) + { + Log("\"%s\" ", argv[i]); + } + else + { + Log("%s ", argv[i]); + } + } + Log("\n"); + } +#else + LogStart(argc, argv); +#endif + +#ifdef ZHLT_NETVIS + if (g_vismode == VIS_MODE_CLIENT) + { + Log("ZHLT NETVIS Client #%d\n", g_clientid); + g_log = false; + } + else + { + Log("ZHLT NETVIS Server\n"); + } +#endif + + CheckForErrorLog(); + +#ifdef ZHLT_64BIT_FIX +#ifdef PLATFORM_CAN_CALC_EXTENT + hlassume (CalcFaceExtents_test (), assume_first); +#endif +#endif + dtexdata_init(); + atexit(dtexdata_free); + // END INIT + + // BEGIN VIS + start = I_FloatTime(); + + safe_strncpy(source, g_Mapname, _MAX_PATH); + safe_strncat(source, ".bsp", _MAX_PATH); + safe_strncpy(portalfile, g_Mapname, _MAX_PATH); + safe_strncat(portalfile, ".prt", _MAX_PATH); + +#ifdef ZHLT_NETVIS + + if (g_vismode == VIS_MODE_SERVER) + { + LoadBSPFile(source); + LoadPortalsByFilename(portalfile); + + char* bsp_image; + char* prt_image; + + g_bsp_size = LoadFile(source, &bsp_image); + g_prt_size = LoadFile(portalfile, &prt_image); + + g_bsp_compressed_size = (g_bsp_size * 1.01) + 12; // Magic numbers come from zlib documentation + g_prt_compressed_size = (g_prt_size * 1.01) + 12; // Magic numbers come from zlib documentation + + char* bsp_compressed_image = (char*)malloc(g_bsp_compressed_size); + char* prt_compressed_image = (char*)malloc(g_prt_compressed_size); + + int rval; + + rval = compress2((byte*)bsp_compressed_image, &g_bsp_compressed_size, (byte*)bsp_image, g_bsp_size, 5); + if (rval != Z_OK) + { + Error("zlib Compression error with bsp image\n"); + } + + rval = compress2((byte*)prt_compressed_image, &g_prt_compressed_size, (byte*)prt_image, g_prt_size, 7); + if (rval != Z_OK) + { + Error("zlib Compression error with prt image\n"); + } + + free(bsp_image); + free(prt_image); + + g_bsp_image = bsp_compressed_image; + g_prt_image = prt_compressed_image; + } + else if (g_vismode == VIS_MODE_CLIENT) + { + Send_VIS_WANT_BSP_DATA(); + while (!g_bsp_downloaded) + { + if (!isConnectedToServer()) + { + Error("Unexepected disconnect from server(5)\n"); + } + NetvisSleep(100); + } + Send_VIS_WANT_PRT_DATA(); + while (!g_prt_downloaded) + { + if (!isConnectedToServer()) + { + Error("Unexepected disconnect from server(6)\n"); + } + NetvisSleep(100); + } + LoadPortals(g_prt_image); + free(g_prt_image); + } + +#else // NOT ZHLT_NETVIS + + LoadBSPFile(source); + ParseEntities(); +#ifdef HLVIS_OVERVIEW + { + int i; + for (i = 0; i < g_numentities; i++) + { + if (!strcmp (ValueForKey (&g_entities[i], "classname"), "info_overview_point")) + { + if (g_overview_count < g_overview_max) + { + vec3_t p; + GetVectorForKey (&g_entities[i], "origin", p); + VectorCopy (p, g_overview[g_overview_count].origin); + g_overview[g_overview_count].visleafnum = VisLeafnumForPoint (p); +#ifdef HLVIS_SKYBOXMODEL + g_overview[g_overview_count].reverse = IntForKey (&g_entities[i], "reverse"); +#endif + g_overview_count++; + } + } + } + } +#endif + LoadPortalsByFilename(portalfile); + +# if ZHLT_ZONES + g_Zones = MakeZones(); + AssignPortalsToZones(); +# endif + +#endif + + Settings(); + g_uncompressed = (byte*)calloc(g_portalleafs, g_bitbytes); + + CalcVis(); + +#ifdef ZHLT_NETVIS + + if (g_vismode == VIS_MODE_SERVER) + { + g_visdatasize = vismap_p - g_dvisdata; + Log("g_visdatasize:%i compressed from %i\n", g_visdatasize, originalvismapsize); + + if (g_chart) + { + PrintBSPFileSizes(); + } + + WriteBSPFile(source); + + end = I_FloatTime(); + LogTimeElapsed(end - start); + + free(g_uncompressed); + // END VIS + +#ifndef SYSTEM_WIN32 + // Talk about cheese . . . + StopNetvisSocketServer(); +#endif + + } + else if (g_vismode == VIS_MODE_CLIENT) + { + +#ifndef SYSTEM_WIN32 + // Dont ask . . + DisconnectFromServer(); +#endif + + end = I_FloatTime(); + LogTimeElapsed(end - start); + + free(g_uncompressed); + // END VIS + } + threads_UninitCrit(); + +#else // NOT ZHLT_NETVIS + + g_visdatasize = vismap_p - g_dvisdata; + Log("g_visdatasize:%i compressed from %i\n", g_visdatasize, originalvismapsize); + + if (g_chart) + { + PrintBSPFileSizes(); + } + + WriteBSPFile(source); + + end = I_FloatTime(); + LogTimeElapsed(end - start); + + free(g_uncompressed); + // END VIS + +#endif // ZHLT_NETVIS +#ifdef ZHLT_PARAMFILE + } + } +#endif + + return 0; +} diff --git a/src/zhlt-vluzacn/hlvis/vis.h b/src/zhlt-vluzacn/hlvis/vis.h new file mode 100644 index 0000000..b8d9522 --- /dev/null +++ b/src/zhlt-vluzacn/hlvis/vis.h @@ -0,0 +1,240 @@ +#ifndef HLVIS_H__ +#define HLVIS_H__ + +#if _MSC_VER >= 1000 +#pragma once +#endif + +#include "cmdlib.h" +#include "messages.h" +#include "win32fix.h" +#include "log.h" +#include "hlassert.h" +#include "mathlib.h" +#include "bspfile.h" +#include "threads.h" +#include "filelib.h" + +#include "zones.h" +#ifdef ZHLT_PARAMFILE +#include "cmdlinecfg.h" +#endif + +#ifdef HLVIS_MAXDIST // AJM: MVD +#define DEFAULT_MAXDISTANCE_RANGE 0 +#ifndef HLVIS_MAXDIST_NEW +//#define DEFAULT_POST_COMPILE 0 +#define MAX_VISBLOCKERS 512 +#endif +#endif + +#ifdef ZHLT_PROGRESSFILE // AJM +#define DEFAULT_PROGRESSFILE NULL // progress file is only used if g_progressfile is non-null +#endif + +#define DEFAULT_FULLVIS false +#define DEFAULT_CHART false +#define DEFAULT_INFO true +#ifdef SYSTEM_WIN32 +#define DEFAULT_ESTIMATE false +#endif +#ifdef SYSTEM_POSIX +#define DEFAULT_ESTIMATE true +#endif +#define DEFAULT_FASTVIS false +#define DEFAULT_NETVIS_PORT 21212 +#define DEFAULT_NETVIS_RATE 60 + +#define MAX_PORTALS 32768 + +//#define USE_CHECK_STACK +#define RVIS_LEVEL_1 +#define RVIS_LEVEL_2 + +#define PORTALFILE "PRT1" // WTF? + +#define MAX_POINTS_ON_FIXED_WINDING 32 + +typedef struct +{ + bool original; // don't free, it's part of the portal + int numpoints; + vec3_t points[MAX_POINTS_ON_FIXED_WINDING]; +} winding_t; + +typedef struct +{ + vec3_t normal; + float dist; +} plane_t; + +typedef enum +{ + stat_none, + stat_working, + stat_done +} vstatus_t; + +typedef struct +{ + plane_t plane; // normal pointing into neighbor + int leaf; // neighbor + winding_t* winding; + vstatus_t status; + byte* visbits; + byte* mightsee; + unsigned nummightsee; + int numcansee; +#ifdef ZHLT_NETVIS + int fromclient; // which client did this come from +#endif + UINT32 zone; // Which zone is this portal a member of +} portal_t; + +typedef struct seperating_plane_s +{ + struct seperating_plane_s* next; + plane_t plane; // from portal is on positive side +} sep_t; + +typedef struct passage_s +{ + struct passage_s* next; + int from, to; // leaf numbers + sep_t* planes; +} passage_t; + +#define MAX_PORTALS_ON_LEAF 256 +typedef struct leaf_s +{ + unsigned numportals; + passage_t* passages; + portal_t* portals[MAX_PORTALS_ON_LEAF]; +} leaf_t; + +typedef struct pstack_s +{ + byte mightsee[MAX_MAP_LEAFS / 8]; // bit string +#ifdef USE_CHECK_STACK + struct pstack_s* next; +#endif + struct pstack_s* head; + + leaf_t* leaf; + portal_t* portal; // portal exiting + winding_t* source; + winding_t* pass; + + winding_t windings[3]; // source, pass, temp in any order + char freewindings[3]; + + const plane_t* portalplane; + +#ifdef RVIS_LEVEL_2 + int clipPlaneCount; + plane_t* clipPlane; +#endif +} pstack_t; + +typedef struct +{ + byte* leafvis; // bit string + // byte fullportal[MAX_PORTALS/8]; // bit string + portal_t* base; + pstack_t pstack_head; +} threaddata_t; + +#ifdef HLVIS_MAXDIST +#ifndef HLVIS_MAXDIST_NEW +// AJM: MVD +// Special VISBLOCKER entity structure +typedef struct +{ + char name[64]; + int numplanes; + plane_t planes[MAX_PORTALS_ON_LEAF]; + int numnames; + int numleafs; + int blockleafs[MAX_PORTALS]; + char blocknames[MAX_VISBLOCKERS][64]; +} visblocker_t; +#endif +#endif + +extern bool g_fastvis; +extern bool g_fullvis; + +extern int g_numportals; +extern unsigned g_portalleafs; + +#ifdef HLVIS_MAXDIST // AJM: MVD +extern unsigned int g_maxdistance; +//extern bool g_postcompile; +#endif +#ifdef HLVIS_OVERVIEW +typedef struct +{ + vec3_t origin; + int visleafnum; +#ifdef HLVIS_SKYBOXMODEL + int reverse; +#endif +} +overview_t; +extern const int g_overview_max; +extern overview_t g_overview[]; +extern int g_overview_count; + +typedef struct +{ + bool isoverviewpoint; +#ifdef HLVIS_OVERVIEW + bool isskyboxpoint; +#endif +} +leafinfo_t; +extern leafinfo_t *g_leafinfos; +#endif + +extern portal_t*g_portals; +extern leaf_t* g_leafs; + +#ifdef HLVIS_MAXDIST // AJM: MVD +#ifndef HLVIS_MAXDIST_NEW +// Visblockers +extern visblocker_t g_visblockers[MAX_VISBLOCKERS]; +extern int g_numvisblockers; +extern byte* g_mightsee; +#endif +#endif + +extern byte* g_uncompressed; +extern unsigned g_bitbytes; +extern unsigned g_bitlongs; + +extern volatile int g_vislocalpercent; + +extern Zones* g_Zones; + +extern void BasePortalVis(int threadnum); + + +#ifdef HLVIS_MAXDIST // AJM: MVD +#ifndef HLVIS_MAXDIST_NEW +extern visblocker_t *GetVisBlock(char *name); +extern void BlockVis(int unused); +#endif +extern void MaxDistVis(int threadnum); +//extern void PostMaxDistVis(int threadnum); +#endif + +extern void PortalFlow(portal_t* p); +extern void CalcAmbientSounds(); + +#ifdef ZHLT_NETVIS +#include "packet.h" +#include "c2cpp.h" +#include "NetvisSession.h" +#endif + +#endif // byte fullportal[MAX_PORTALS/8]; // bit string HLVIS_H__ diff --git a/src/zhlt-vluzacn/hlvis/zones.cpp b/src/zhlt-vluzacn/hlvis/zones.cpp new file mode 100644 index 0000000..5cd68b9 --- /dev/null +++ b/src/zhlt-vluzacn/hlvis/zones.cpp @@ -0,0 +1,155 @@ +// Copyright (C) 2000 Sean Cavanaugh +// This file is licensed under the terms of the Lesser GNU Public License +// (see LPGL.txt, or http://www.gnu.org/copyleft/lesser.txt) + +#include "vis.h" + + +void Zones::set(UINT32 zone, const BoundingBox& bounds) +{ + if (zone < m_ZoneCount) + { + m_ZoneBounds[zone] = bounds; + } +} + +UINT32 Zones::getZoneFromBounds(const BoundingBox& bounds) +{ + UINT32 x; + for (x=0; xnumedges); + + for (i = 0; i < f->numedges; i++) + { + se = g_dsurfedges[f->firstedge + i]; + if (se < 0) + { + v = g_dedges[-se].v[1]; + } + else + { + v = g_dedges[se].v[0]; + } + + dv = &g_dvertexes[v]; + VectorCopy(dv->point, w->m_Points[i]); + } + + return w; +} + +Zones* MakeZones(void) +{ + UINT32 x; + UINT32 func_vis_count = 0; + + ParseEntities(); + + // Note: we arent looping through entities because we only care if it has a winding/bounding box + + // First count the number of func_vis's + for (x=0; xepairs; keyvalue; keyvalue = keyvalue->next) + { + UINT32 other_id = atoi(keyvalue->key); + if (other_id) + { + zones->flag(func_vis_id, other_id); + } + } + } + + { + UINT32 j; + BoundingBox bounds; + dface_t* f = g_dfaces + mod->firstface; + + for (j = 0; j < mod->numfaces; j++, f++) + { + Winding* w = WindingFromFace(f); + UINT32 k; + + for (k = 0; k < w->m_NumPoints; k++) + { + bounds.add(w->m_Points[k]); + } + delete w; + } + + zones->set(func_vis_id, bounds); + + Log("Adding zone %u : mins(%4.3f %4.3f %4.3f) maxs(%4.3f %4.3f %4.3f)\n", func_vis_id, + bounds.m_Mins[0],bounds.m_Mins[1],bounds.m_Mins[2], + bounds.m_Maxs[0],bounds.m_Maxs[1],bounds.m_Maxs[2]); + } + } + } + + return zones; +} diff --git a/src/zhlt-vluzacn/hlvis/zones.h b/src/zhlt-vluzacn/hlvis/zones.h new file mode 100644 index 0000000..0b7ef0a --- /dev/null +++ b/src/zhlt-vluzacn/hlvis/zones.h @@ -0,0 +1,75 @@ +// Copyright (C) 2000 Sean Cavanaugh +// This file is licensed under the terms of the Lesser GNU Public License +// (see LPGL.txt, or http://www.gnu.org/copyleft/lesser.txt) + +#ifndef ZONING_H__ +#define ZONING_H__ + +#if _MSC_VER >= 1000 +#pragma once +#endif + +#include "basictypes.h" +#include "winding.h" +#include "boundingbox.h" + + +// Simple class of visibily flags and zone id's. No concept of location is in this class +class Zones +{ +public: + inline void flag(UINT32 src, UINT32 dst) + { + if ((src < m_ZoneCount) && (dst < m_ZoneCount)) + { + m_ZonePtrs[src][dst] = true; + m_ZonePtrs[dst][src] = true; + } + } + inline bool check(UINT32 zone1, UINT32 zone2) + { + if ((zone1 < m_ZoneCount) && (zone2 < m_ZoneCount)) + { + return m_ZonePtrs[zone1][zone2]; + } + return false; + } + + void set(UINT32 zone, const BoundingBox& bounds); + UINT32 getZoneFromBounds(const BoundingBox& bounds); + UINT32 getZoneFromWinding(const Winding& winding); + +public: + Zones(UINT32 ZoneCount) + { + m_ZoneCount = ZoneCount + 1; // Zone 0 is used for all points outside all nodes + m_ZoneVisMatrix = new bool[m_ZoneCount * m_ZoneCount]; + memset(m_ZoneVisMatrix, 0, sizeof(bool) * m_ZoneCount * m_ZoneCount); + m_ZonePtrs = new bool*[m_ZoneCount]; + m_ZoneBounds = new BoundingBox[m_ZoneCount]; + + UINT32 x; + bool* dstPtr = m_ZoneVisMatrix; + bool** srcPtr = m_ZonePtrs; + for (x=0; x +#endif +#endif +#ifdef ZHLT_LANGFILE +#ifdef SYSTEM_WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#endif +#endif + +typedef enum +{ + hl_undefined = -1, + hl_export = 0, + hl_import = 1 +} +hl_types; + +static hl_types g_mode = hl_undefined; +#ifdef RIPENT_TEXTURE +static hl_types g_texturemode = hl_undefined; +#endif + +// g_parse: command line switch (-parse). +// Added by: Ryan Gregg aka Nem +bool g_parse = DEFAULT_PARSE; +#ifdef RIPENT_TEXTURE +bool g_textureparse = DEFAULT_TEXTUREPARSE; +#endif + +bool g_chart = DEFAULT_CHART; + +bool g_info = DEFAULT_INFO; + +#ifdef RIPENT_PAUSE +bool g_pause = false; +#endif + +#ifdef ZHLT_64BIT_FIX +bool g_writeextentfile = DEFAULT_WRITEEXTENTFILE; +#endif + +#ifdef ZHLT_EMBEDLIGHTMAP +#ifdef RIPENT_TEXTURE +bool g_deleteembeddedlightmaps = DEFAULT_DELETEEMBEDDEDLIGHTMAPS; +#endif +#endif + + +// ScanForToken() +// Added by: Ryan Gregg aka Nem +// +// Scans entity data starting at iIndex for cToken. Every time a \n char +// is encountered iLine is incremented. If iToken is not null, the index +// cToken was found at is inserted into it. +bool ScanForToken(char cToken, int &iIndex, int &iLine, bool bIgnoreWhiteSpace, bool bIgnoreOthers, int *iToken = 0) +{ + for(; iIndex < g_entdatasize; iIndex++) + { + // If we found a null char, consider it end of data. + if(g_dentdata[iIndex] == '\0') + { + iIndex = g_entdatasize; + return false; + } + + // Count lines (for error message). + if(g_dentdata[iIndex] == '\n') + { + iLine++; + } + + // Ignore white space, if we are ignoring it. + if(!bIgnoreWhiteSpace && isspace(g_dentdata[iIndex])) + { + continue; + } + + if(g_dentdata[iIndex] != cToken) + { + if(bIgnoreOthers) + continue; + else + return false; + } + + // Advance the index past the token. + iIndex++; + + // Return the index of the token if requested. + if(iToken != 0) + { + *iToken = iIndex - 1; + } + + return true; + } + + // End of data. + return false; +} + +#include +typedef std::list< char * > CEntityPairList; +typedef std::list< CEntityPairList * > CEntityList; + +// ParseEntityData() +// Added by: Ryan Gregg aka Nem +// +// Pareses and reformats entity data stripping all non essential +// formatting and using the formatting options passed through this +// function. The length is specified because in some cases (i.e. the +// terminator) a null char is desired to be printed. +void ParseEntityData(const char *cTab, int iTabLength, const char *cNewLine, int iNewLineLength, const char *cTerminator, int iTerminatorLength) +{ + CEntityList EntityList; // Parsed entities. + + int iIndex = 0; // Current char in g_dentdata. + int iLine = 0; // Current line in g_dentdata. + + char cError[256] = ""; + + try + { + // + // Parse entity data. + // + + Log("\nParsing entity data.\n"); + + while(true) + { + // Parse the start of an entity. + if(!ScanForToken('{', iIndex, iLine, false, false)) + { + if(iIndex == g_entdatasize) + { + // We read all the entities. + break; + } + else + { + sprintf_s(cError, "expected token %s on line %d.", "{", iLine); + throw cError; + } + } + + CEntityPairList *EntityPairList = new CEntityPairList(); + + // Parse the rest of the entity. + while(true) + { + // Parse the key and value. + for(int j = 0; j < 2; j++) + { + int iStart; + // Parse the start of a string. + if(!ScanForToken('\"', iIndex, iLine, false, false, &iStart)) + { + sprintf_s(cError, "expected token %s on line %d.", "\"", iLine); + throw cError; + } + + int iEnd; + // Parse the end of a string. + if(!ScanForToken('\"', iIndex, iLine, true, true, &iEnd)) + { + sprintf_s(cError, "expected token %s on line %d.", "\"", iLine); + throw cError; + } + + // Extract the string. + int iLength = iEnd - iStart - 1; + char *cString = new char[iLength + 1]; + memcpy(cString, &g_dentdata[iStart + 1], iLength); + cString[iLength] = '\0'; + + // Save it. + EntityPairList->push_back(cString); + } + + // Parse the end of an entity. + if(!ScanForToken('}', iIndex, iLine, false, false)) + { + if(g_dentdata[iIndex] == '\"') + { + // We arn't done the entity yet. + continue; + } + else + { + sprintf_s(cError, "expected token %s on line %d.", "}", iLine); + throw cError; + } + } + + // We read the entity. + EntityList.push_back(EntityPairList); + break; + } + } + +#ifdef ZHLT_64BIT_FIX + Log("%d entities parsed.\n", (int)EntityList.size()); +#else + Log("%d entities parsed.\n", EntityList.size()); +#endif + + // + // Calculate new data length. + // + + int iNewLength = 0; + + for(CEntityList::iterator i = EntityList.begin(); i != EntityList.end(); ++i) + { + // Opening brace. + iNewLength += 1; + + // New line. + iNewLength += iNewLineLength; + + CEntityPairList *EntityPairList = *i; + + for(CEntityPairList::iterator j = EntityPairList->begin(); j != EntityPairList->end(); ++j) + { + // Tab. + iNewLength += iTabLength; + + // String. + iNewLength += 1; + iNewLength += (int)strlen(*j); + iNewLength += 1; + + // String seperator. + iNewLength += 1; + + ++j; + + // String. + iNewLength += 1; + iNewLength += (int)strlen(*j); + iNewLength += 1; + + // New line. + iNewLength += iNewLineLength; + } + + // Closing brace. + iNewLength += 1; + + // New line. + iNewLength += iNewLineLength; + } + + // Terminator. + iNewLength += iTerminatorLength; + + // + // Check our parsed data. + // + + assume(iNewLength != 0, "No entity data."); + assume(iNewLength < sizeof(g_dentdata), "Entity data size exceedes dentdata limit."); + + // + // Clear current data. + // + + g_entdatasize = 0; + + // + // Fill new data. + // + + Log("Formating entity data.\n\n"); + + for(CEntityList::iterator i = EntityList.begin(); i != EntityList.end(); ++i) + { + // Opening brace. + g_dentdata[g_entdatasize] = '{'; + g_entdatasize += 1; + + // New line. + memcpy(&g_dentdata[g_entdatasize], cNewLine, iNewLineLength); + g_entdatasize += iNewLineLength; + + CEntityPairList *EntityPairList = *i; + + for(CEntityPairList::iterator j = EntityPairList->begin(); j != EntityPairList->end(); ++j) + { + // Tab. + memcpy(&g_dentdata[g_entdatasize], cTab, iTabLength); + g_entdatasize += iTabLength; + + // String. + g_dentdata[g_entdatasize] = '\"'; + g_entdatasize += 1; + memcpy(&g_dentdata[g_entdatasize], *j, strlen(*j)); + g_entdatasize += (int)strlen(*j); + g_dentdata[g_entdatasize] = '\"'; + g_entdatasize += 1; + + // String seperator. + g_dentdata[g_entdatasize] = ' '; + g_entdatasize += 1; + + ++j; + + // String. + g_dentdata[g_entdatasize] = '\"'; + g_entdatasize += 1; + memcpy(&g_dentdata[g_entdatasize], *j, strlen(*j)); + g_entdatasize += (int)strlen(*j); + g_dentdata[g_entdatasize] = '\"'; + g_entdatasize += 1; + + // New line. + memcpy(&g_dentdata[g_entdatasize], cNewLine, iNewLineLength); + g_entdatasize += iNewLineLength; + } + + // Closing brace. + g_dentdata[g_entdatasize] = '}'; + g_entdatasize += 1; + + // New line. + memcpy(&g_dentdata[g_entdatasize], cNewLine, iNewLineLength); + g_entdatasize += iNewLineLength; + } + + // Terminator. + memcpy(&g_dentdata[g_entdatasize], cTerminator, iTerminatorLength); + g_entdatasize += iTerminatorLength; + + // + // Delete entity data. + // + + for(CEntityList::iterator i = EntityList.begin(); i != EntityList.end(); ++i) + { + CEntityPairList *EntityPairList = *i; + + for(CEntityPairList::iterator j = EntityPairList->begin(); j != EntityPairList->end(); ++j) + { + delete []*j; + } + + delete EntityPairList; + } + + //return true; + } + catch(...) + { + // + // Delete entity data. + // + + for(CEntityList::iterator i = EntityList.begin(); i != EntityList.end(); ++i) + { + CEntityPairList *EntityPairList = *i; + + for(CEntityPairList::iterator j = EntityPairList->begin(); j != EntityPairList->end(); ++j) + { + delete []*j; + } + + delete EntityPairList; + } + + // If we threw the error cError wont be null, this is + // a message, print it. + if(*cError != '\0') + { + Error(cError); + } + Error("unknowen exception."); + + //return false; + } +} + +static void ReadBSP(const char* const name) +{ + char filename[_MAX_PATH]; + +#ifdef ZHLT_DEFAULTEXTENSION_FIX + safe_snprintf(filename, _MAX_PATH, "%s.bsp", name); + + LoadBSPFile(filename); +#else + safe_strncpy(filename, name, _MAX_PATH); + StripExtension(filename); + DefaultExtension(filename, ".bsp"); + + LoadBSPFile(name); +#endif +#ifdef ZHLT_64BIT_FIX + if (g_writeextentfile) + { +#ifdef PLATFORM_CAN_CALC_EXTENT + hlassume (CalcFaceExtents_test (), assume_first); + char extentfilename[_MAX_PATH]; + safe_snprintf (extentfilename, _MAX_PATH, "%s.ext", name); + Log ("\nWriting %s.\n", extentfilename); + WriteExtentFile (extentfilename); +#else + Error ("-writeextentfile is not allowed in the " PLATFORM_VERSIONSTRING " version. Please use the 32-bit version of ripent."); +#endif + } +#endif +} + +static void WriteBSP(const char* const name) +{ + char filename[_MAX_PATH]; + +#ifdef ZHLT_DEFAULTEXTENSION_FIX + safe_snprintf(filename, _MAX_PATH, "%s.bsp", name); +#else + safe_strncpy(filename, name, _MAX_PATH); + StripExtension(filename); + DefaultExtension(filename, ".bsp"); +#endif + + Log ("\nUpdating %s.\n", filename); //--vluzacn + WriteBSPFile(filename); +} +#ifdef RIPENT_TEXTURE +#ifdef WORDS_BIGENDIAN +#error "I haven't added support for bigendian. Please disable RIPENT_TEXTURE in cmdlib.h ." +#endif +typedef struct +{ + char identification[4]; // should be WAD2/WAD3 + int numlumps; + int infotableofs; +} wadinfo_t; +typedef struct +{ + int filepos; + int disksize; + int size; // uncompressed + char type; + char compression; + char pad1, pad2; + char name[16]; // must be null terminated +} lumpinfo_t; +/*int TextureSize(const miptex_t *tex) +{ + int size = 0; + int w, h; + size += sizeof(miptex_t); + w = tex->width, h = tex->height; + for (int imip = 0; imip < MIPLEVELS; ++imip, w/=2, h/=2) + { + size += w * h; + } + size += 256 * 3 + 4; + return size; +} +void WriteEmptyTexture(FILE *outwad, const miptex_t *tex) +{ + miptex_t outtex; + int start, end; + int w, h; + memcpy (&outtex, tex, sizeof(miptex_t)); + start = ftell (outwad); + fseek (outwad, sizeof(miptex_t), SEEK_CUR); + w = tex->width, h = tex->height; + for (int imip = 0; imip < MIPLEVELS; ++imip, w/=2, h/=2) + { + void *tmp = calloc (w * h, 1); + outtex.offsets[imip] = ftell (outwad) - start; + SafeWrite (outwad, tmp, w * h); + free (tmp); + } + short s = 256; + SafeWrite (outwad, &s, sizeof(short)); + void *tmp = calloc (256 * 3 + 2, 1); // assume width and height are multiples of 16 + SafeWrite (outwad, tmp, 256 * 3 + 2); + free (tmp); + end = ftell (outwad); + fseek (outwad, start, SEEK_SET); + SafeWrite (outwad, &outtex, sizeof (miptex_t)); + fseek (outwad, end, SEEK_SET); +}*/ +static void WriteTextures(const char* const name) +{ + char wadfilename[_MAX_PATH]; + FILE *wadfile; +#ifdef ZHLT_DEFAULTEXTENSION_FIX + safe_snprintf(wadfilename, _MAX_PATH, "%s.wad", name); +#else + safe_strncpy(wadfilename, name, _MAX_PATH); + StripExtension(wadfilename); + DefaultExtension(wadfilename, ".wad"); +#endif + _unlink(wadfilename); + wadfile = SafeOpenWrite (wadfilename); + Log("\nWriting %s.\n", wadfilename); + + char texfilename[_MAX_PATH]; + FILE *texfile; +#ifdef ZHLT_DEFAULTEXTENSION_FIX + safe_snprintf(texfilename, _MAX_PATH, "%s.tex", name); +#else + safe_strncpy(texfilename, name, _MAX_PATH); + StripExtension(texfilename); + DefaultExtension(texfilename, ".tex"); +#endif + _unlink(texfilename); + if (!g_textureparse) + { + int dataofs = (int)(intptr_t)&((dmiptexlump_t*)NULL)->dataofs[((dmiptexlump_t*)g_dtexdata)->nummiptex]; + int wadofs = sizeof(wadinfo_t); + + wadinfo_t header; + header.identification[0] = 'W'; + header.identification[1] = 'A'; + header.identification[2] = 'D'; + header.identification[3] = '3'; + header.numlumps = ((dmiptexlump_t*)g_dtexdata)->nummiptex; + header.infotableofs = g_texdatasize - dataofs + wadofs; + SafeWrite (wadfile, &header, wadofs); + + SafeWrite (wadfile, (byte *)g_dtexdata + dataofs, g_texdatasize - dataofs); + + lumpinfo_t *info; + info = (lumpinfo_t *)malloc (((dmiptexlump_t*)g_dtexdata)->nummiptex * sizeof (lumpinfo_t)); + hlassume (info != NULL, assume_NoMemory); + memset (info, 0, header.numlumps * sizeof(lumpinfo_t)); + + for (int i = 0; i < header.numlumps; i++) + { + int ofs = ((dmiptexlump_t*)g_dtexdata)->dataofs[i]; + int size = 0; + if (ofs >= 0) + { + size = g_texdatasize - ofs; + for (int j = 0; j < ((dmiptexlump_t*)g_dtexdata)->nummiptex; ++j) + if (ofs < ((dmiptexlump_t*)g_dtexdata)->dataofs[j] && + ofs + size > ((dmiptexlump_t*)g_dtexdata)->dataofs[j]) + size = ((dmiptexlump_t*)g_dtexdata)->dataofs[j] - ofs; + } + info[i].filepos = ofs - dataofs + wadofs; + info[i].disksize = size; + info[i].size = size; + info[i].type = (ofs >= 0 && ((miptex_t*)(g_dtexdata+ofs))->offsets[0] > 0)? 67: 0; // prevent invalid texture from being processed by Wally + info[i].compression = 0; + strcpy (info[i].name, ofs >= 0? ((miptex_t*)(g_dtexdata+ofs))->name: "\rTEXTUREMISSING"); + } + SafeWrite (wadfile, info, header.numlumps * sizeof(lumpinfo_t)); + free (info); + } + else + { + texfile = SafeOpenWrite (texfilename); + Log("\nWriting %s.\n", texfilename); + + wadinfo_t header; + header.identification[0] = 'W'; + header.identification[1] = 'A'; + header.identification[2] = 'D'; + header.identification[3] = '3'; + header.numlumps = 0; + + lumpinfo_t *info; + info = (lumpinfo_t *)malloc (((dmiptexlump_t*)g_dtexdata)->nummiptex * sizeof (lumpinfo_t)); // might be more than needed + hlassume (info != NULL, assume_NoMemory); + + fprintf (texfile, "%d\r\n", ((dmiptexlump_t*)g_dtexdata)->nummiptex); + fseek (wadfile, sizeof(wadinfo_t), SEEK_SET); + + for (int itex = 0; itex < ((dmiptexlump_t*)g_dtexdata)->nummiptex; ++itex) + { + int ofs = ((dmiptexlump_t*)g_dtexdata)->dataofs[itex]; + miptex_t *tex = (miptex_t*)(g_dtexdata+ofs); + if (ofs < 0) + { + fprintf (texfile, "[-1]\r\n"); + } + else + { + int size = g_texdatasize - ofs; + for (int j = 0; j < ((dmiptexlump_t*)g_dtexdata)->nummiptex; ++j) + if (ofs < ((dmiptexlump_t*)g_dtexdata)->dataofs[j] && + ofs + size > ((dmiptexlump_t*)g_dtexdata)->dataofs[j]) + size = ((dmiptexlump_t*)g_dtexdata)->dataofs[j] - ofs; + bool included = false; + if (tex->offsets[0] > 0) + included = true; + if (included) + { + memset (&info[header.numlumps], 0, sizeof (lumpinfo_t)); + info[header.numlumps].filepos = ftell (wadfile); + SafeWrite (wadfile, tex, size); + info[header.numlumps].disksize = ftell (wadfile) - info[header.numlumps].filepos; + info[header.numlumps].size = info[header.numlumps].disksize; + info[header.numlumps].type = 67; + info[header.numlumps].compression = 0; + strcpy (info[header.numlumps].name, tex->name); + header.numlumps++; + } +#ifdef ZHLT_64BIT_FIX + fprintf (texfile, "[%d]", (int)strlen(tex->name)); +#else + fprintf (texfile, "[%d]", strlen(tex->name)); +#endif + SafeWrite (texfile, tex->name, strlen(tex->name)); + fprintf (texfile, " %d %d\r\n", tex->width, tex->height); + } + } + header.infotableofs = ftell (wadfile); + SafeWrite (wadfile, info, header.numlumps * sizeof(lumpinfo_t)); + fseek (wadfile, 0, SEEK_SET); + SafeWrite (wadfile, &header, sizeof(wadinfo_t)); + + fclose (texfile); + free (info); + } + fclose (wadfile); +} +inline void skipspace (FILE *f) {fscanf (f, "%*[ \t\r\n]s");} +inline void skipline (FILE *f) {fscanf (f, "%*[^\r\n]s");} +static void ReadTextures(const char *name) +{ + char wadfilename[_MAX_PATH]; + FILE *wadfile; +#ifdef ZHLT_DEFAULTEXTENSION_FIX + safe_snprintf(wadfilename, _MAX_PATH, "%s.wad", name); +#else + safe_strncpy(wadfilename, name, _MAX_PATH); + StripExtension(wadfilename); + DefaultExtension(wadfilename, ".wad"); +#endif + wadfile = SafeOpenRead (wadfilename); + Log("\nReading %s.\n", wadfilename); + + char texfilename[_MAX_PATH]; + FILE *texfile; +#ifdef ZHLT_DEFAULTEXTENSION_FIX + safe_snprintf(texfilename, _MAX_PATH, "%s.tex", name); +#else + safe_strncpy(texfilename, name, _MAX_PATH); + StripExtension(texfilename); + DefaultExtension(texfilename, ".tex"); +#endif + if (!g_textureparse) + { + wadinfo_t header; + int wadofs = sizeof(wadinfo_t); + SafeRead (wadfile, &header, wadofs); + ((dmiptexlump_t*)g_dtexdata)->nummiptex = header.numlumps; + int dataofs = (int)(intptr_t)&((dmiptexlump_t*)NULL)->dataofs[((dmiptexlump_t*)g_dtexdata)->nummiptex]; + g_texdatasize = header.infotableofs - wadofs + dataofs; + + SafeRead (wadfile, (byte *)g_dtexdata + dataofs, g_texdatasize - dataofs); + + lumpinfo_t *info; + info = (lumpinfo_t *)malloc (header.numlumps * sizeof (lumpinfo_t)); + hlassume (info != NULL, assume_NoMemory); + SafeRead (wadfile, info, header.numlumps * sizeof(lumpinfo_t)); + + for (int i = 0; i < header.numlumps; i++) + { + ((dmiptexlump_t*)g_dtexdata)->dataofs[i] = info[i].filepos - wadofs + dataofs; + } + + free (info); + } + else + { + texfile = SafeOpenRead (texfilename); + Log("\nReading %s.\n", texfilename); + + wadinfo_t header; + SafeRead (wadfile, &header, sizeof(wadinfo_t)); + fseek (wadfile, header.infotableofs, SEEK_SET); + + lumpinfo_t *info; + info = (lumpinfo_t *)malloc (header.numlumps * sizeof (lumpinfo_t)); + hlassume (info != NULL, assume_NoMemory); + SafeRead (wadfile, info, header.numlumps * sizeof(lumpinfo_t)); + + int nummiptex = 0; + if (skipspace (texfile), fscanf (texfile, "%d", &nummiptex) != 1) + Error ("File read failure"); + ((dmiptexlump_t*)g_dtexdata)->nummiptex = nummiptex; + g_texdatasize = (byte *)(&((dmiptexlump_t*)g_dtexdata)->dataofs[nummiptex]) - g_dtexdata; + + for (int itex = 0; itex < nummiptex; ++itex) + { + int len; + if (skipspace (texfile), fscanf (texfile, "[%d]", &len) != 1) + Error ("File read failure"); + if (len < 0) + { + ((dmiptexlump_t*)g_dtexdata)->dataofs[itex] = -1; + } + else + { + char name[16]; + if (len > 15) + Error ("Texture name is too long"); + memset (name, '\0', 16); + SafeRead (texfile, name, len); + ((dmiptexlump_t*)g_dtexdata)->dataofs[itex] = g_texdatasize; + miptex_t *tex = (miptex_t*)(g_dtexdata + g_texdatasize); + int j; + for (j = 0; j < header.numlumps; ++j) + if (!strcasecmp (name, info[j].name)) + break; + if (j == header.numlumps) + { + int w, h; + if (skipspace (texfile), fscanf (texfile, "%d", &w) != 1) + Error ("File read failure"); + if (skipspace (texfile), fscanf (texfile, "%d", &h) != 1) + Error ("File read failure"); + g_texdatasize += sizeof(miptex_t); + hlassume(g_texdatasize < g_max_map_miptex, assume_MAX_MAP_MIPTEX); + memset (tex, 0, sizeof(miptex_t)); + strcpy (tex->name, name); + tex->width = w; + tex->height = h; + for (int k = 0; k < MIPLEVELS; k++) + tex->offsets[k] = 0; + } + else + { + fseek (wadfile, info[j].filepos, SEEK_SET); + g_texdatasize += info[j].disksize; + hlassume(g_texdatasize < g_max_map_miptex, assume_MAX_MAP_MIPTEX); + SafeRead (wadfile, tex, info[j].disksize); + } + } + skipline (texfile); + } + + fclose (texfile); + free (info); + } + fclose (wadfile); +} +#endif /*RIPENT_TEXTURE*/ + +static void WriteEntities(const char* const name) +{ +#ifdef RIPENT_TEXTURE + char *bak_dentdata; + int bak_entdatasize; +#endif + char filename[_MAX_PATH]; + +#ifdef ZHLT_DEFAULTEXTENSION_FIX + safe_snprintf(filename, _MAX_PATH, "%s.ent", name); +#else + safe_strncpy(filename, name, _MAX_PATH); + StripExtension(filename); + DefaultExtension(filename, ".ent"); +#endif + _unlink(filename); + + { + if(g_parse) // Added by Nem. + { +#ifdef RIPENT_TEXTURE + bak_entdatasize = g_entdatasize; + bak_dentdata = (char *)malloc (g_entdatasize); + hlassume (bak_dentdata != NULL, assume_NoMemory); + memcpy (bak_dentdata, g_dentdata, g_entdatasize); +#endif + ParseEntityData(" ", 2, "\r\n", 2, "", 0); + } + + FILE *f = SafeOpenWrite(filename); + Log("\nWriting %s.\n", filename); // Added by Nem. + SafeWrite(f, g_dentdata, g_entdatasize); + fclose(f); +#ifdef RIPENT_TEXTURE + if (g_parse) + { + g_entdatasize = bak_entdatasize; + memcpy (g_dentdata, bak_dentdata, bak_entdatasize); + free (bak_dentdata); + } +#endif + } +} + +static void ReadEntities(const char* const name) +{ + char filename[_MAX_PATH]; + +#ifdef ZHLT_DEFAULTEXTENSION_FIX + safe_snprintf(filename, _MAX_PATH, "%s.ent", name); +#else + safe_strncpy(filename, name, _MAX_PATH); + StripExtension(filename); + DefaultExtension(filename, ".ent"); +#endif + + { + FILE *f = SafeOpenRead(filename); + Log("\nReading %s.\n", filename); // Added by Nem. + + g_entdatasize = q_filelength(f); + + assume(g_entdatasize != 0, "No entity data."); + assume(g_entdatasize < sizeof(g_dentdata), "Entity data size exceedes dentdata limit."); + + SafeRead(f, g_dentdata, g_entdatasize); + + fclose(f); + + if (g_dentdata[g_entdatasize-1] != 0) + { +// Log("g_dentdata[g_entdatasize-1] = %d\n", g_dentdata[g_entdatasize-1]); + + if(g_parse) // Added by Nem. + { + ParseEntityData("", 0, "\n", 1, "\0", 1); + } + else + { + if(g_dentdata[g_entdatasize - 1] != '\0') + { + g_dentdata[g_entdatasize] = '\0'; + g_entdatasize++; + } + } + } + } +} + +//====================================================================== + +static void Usage(void) +{ + //Log("%s " ZHLT_VERSIONSTRING "\n" MODIFICATIONS_STRING "\n", g_Program); + //Log(" Usage: ripent [-import|-export] [-texdata n] bspname\n"); + + // Modified to behave like other tools by Nem. + + Banner(); + Log("\n-= %s Options =-\n\n", g_Program); + +#ifdef ZHLT_CONSOLE + Log(" -console # : Set to 0 to turn off the pop-up console (default is 1)\n"); +#endif +#ifdef ZHLT_LANGFILE + Log(" -lang file : localization file\n"); +#endif + Log(" -export : Export entity data\n"); + Log(" -import : Import entity data\n\n"); + + Log(" -parse : Parse and format entity data\n\n"); +#ifdef RIPENT_TEXTURE + Log(" -textureexport : Export texture data\n"); + Log(" -textureimport : Import texture data\n"); + Log(" -textureparse : Parse and format texture data\n\n"); +#endif +#ifdef ZHLT_64BIT_FIX + Log(" -writeextentfile : Create extent file for the map\n"); +#endif +#ifdef ZHLT_EMBEDLIGHTMAP +#ifdef RIPENT_TEXTURE + Log(" -deleteembeddedlightmaps : Delete textures created by hlrad\n"); +#endif +#endif + + Log(" -texdata # : Alter maximum texture memory limit (in kb)\n"); + Log(" -lightdata # : Alter maximum lighting memory limit (in kb)\n"); + Log(" -chart : Display bsp statitics\n"); + Log(" -noinfo : Do not show tool configuration information\n\n"); +#ifdef RIPENT_PAUSE + Log(" -pause : Pause before exit\n\n"); +#endif + + Log(" mapfile : The mapfile to process\n\n"); + + exit(1); +} + +#ifdef RIPENT_PAUSE +void pause () +{ + if (g_pause) + { +#ifdef SYSTEM_WIN32 + Log("\nPress any key to continue\n"); + getch (); +#else + Log("\nThe option '-pause' is only valid for Windows\n"); +#endif + } +} +#endif + +// ===================================================================================== +// Settings +// ===================================================================================== +static void Settings() +{ + char* tmp; + + if (!g_info) + { + return; + } + + Log("\n-= Current %s Settings =-\n", g_Program); + Log("Name | Setting | Default\n" "-------------------|-----------|-------------------------\n"); + + // ZHLT Common Settings + Log("chart [ %7s ] [ %7s ]\n", g_chart ? "on" : "off", DEFAULT_CHART ? "on" : "off"); + Log("max texture memory [ %7d ] [ %7d ]\n", g_max_map_miptex, DEFAULT_MAX_MAP_MIPTEX); + Log("max lighting memory [ %7d ] [ %7d ]\n", g_max_map_lightdata, DEFAULT_MAX_MAP_LIGHTDATA); + +#ifdef RIPENT_TEXTURE + switch (g_mode) + { + case hl_import: + tmp = "Import"; + break; + case hl_export: + tmp = "Export"; + break; + case hl_undefined: + default: + tmp = "N/A"; + break; + } +#else + switch (g_mode) + { + case hl_import: + default: + tmp = "Import"; + break; + case hl_export: + tmp = "Export"; + break; + } +#endif + + Log("\n"); + + // RipEnt Specific Settings + Log("mode [ %7s ] [ %7s ]\n", tmp, "N/A"); + Log("parse [ %7s ] [ %7s ]\n", g_parse ? "on" : "off", DEFAULT_PARSE ? "on" : "off"); +#ifdef RIPENT_TEXTURE + switch (g_texturemode) + { + case hl_import: + tmp = "Import"; + break; + case hl_export: + tmp = "Export"; + break; + case hl_undefined: + default: + tmp = "N/A"; + break; + } + Log("texture mode [ %7s ] [ %7s ]\n", tmp, "N/A"); + Log("texture parse [ %7s ] [ %7s ]\n", g_textureparse ? "on" : "off", DEFAULT_TEXTUREPARSE ? "on" : "off"); +#endif +#ifdef ZHLT_64BIT_FIX + Log("write extent file [ %7s ] [ %7s ]\n", g_writeextentfile ? "on" : "off", DEFAULT_WRITEEXTENTFILE ? "on" : "off"); +#endif +#ifdef ZHLT_EMBEDLIGHTMAP +#ifdef RIPENT_TEXTURE + Log("delete rad textures [ %7s ] [ %7s ]\n", g_deleteembeddedlightmaps ? "on" : "off", DEFAULT_DELETEEMBEDDEDLIGHTMAPS ? "on" : "off"); +#endif +#endif + + Log("\n\n"); +} + +/* + * ============ + * main + * ============ + */ +int main(int argc, char** argv) +{ + int i; + double start, end; + + g_Program = "ripent"; + +#ifdef RIPENT_PAUSE + atexit (&pause); +#endif +#ifdef ZHLT_PARAMFILE + int argcold = argc; + char ** argvold = argv; + { + int argc; + char ** argv; + ParseParamFile (argcold, argvold, argc, argv); + { +#endif +#ifdef ZHLT_CONSOLE + if (InitConsole (argc, argv) < 0) + Usage(); +#endif + if (argc == 1) + { + Usage(); + } + + for (i = 1; i < argc; i++) + { + if (!strcasecmp(argv[i], "-import")) + { + g_mode = hl_import; + } +#ifdef ZHLT_CONSOLE + else if (!strcasecmp(argv[i], "-console")) + { +#ifndef SYSTEM_WIN32 + Warning("The option '-console #' is only valid for Windows."); +#endif + if (i + 1 < argc) + ++i; + else + Usage(); + } +#endif + else if (!strcasecmp(argv[i], "-export")) + { + g_mode = hl_export; + } + // g_parse: command line switch (-parse). + // Added by: Ryan Gregg aka Nem + else if(!strcasecmp(argv[i], "-parse")) + { + g_parse = true; + } + else if (!strcasecmp(argv[i], "-texdata")) + { + if (i + 1 < argc) //added "1" .--vluzacn + { + int x = atoi(argv[++i]) * 1024; + + //if (x > g_max_map_miptex) //--vluzacn + { + g_max_map_miptex = x; + } + } + else + { + Usage(); + } + } + else if (!strcasecmp(argv[i], "-lightdata")) + { + if (i + 1 < argc) //added "1" .--vluzacn + { + int x = atoi(argv[++i]) * 1024; + + //if (x > g_max_map_lightdata) //--vluzacn + { + g_max_map_lightdata = x; + } + } + else + { + Usage(); + } + } + else if (!strcasecmp(argv[i], "-chart")) + { + g_chart = true; + } + else if (!strcasecmp(argv[i], "-noinfo")) + { + g_info = false; + } +#ifdef RIPENT_PAUSE + else if (!strcasecmp(argv[i], "-pause")) + { + g_pause = true; + } +#endif +#ifdef RIPENT_TEXTURE + else if (!strcasecmp(argv[i], "-textureimport")) + { + g_texturemode = hl_import; + } + else if (!strcasecmp(argv[i], "-textureexport")) + { + g_texturemode = hl_export; + } + else if (!strcasecmp(argv[i], "-textureparse")) + { + g_textureparse = true; + } +#endif +#ifdef ZHLT_64BIT_FIX + else if (!strcasecmp(argv[i], "-writeextentfile")) + { + g_writeextentfile = true; + } +#endif +#ifdef ZHLT_EMBEDLIGHTMAP +#ifdef RIPENT_TEXTURE + else if (!strcasecmp(argv[i], "-deleteembeddedlightmaps")) + { + g_deleteembeddedlightmaps = true; + } +#endif +#endif +#ifdef ZHLT_LANGFILE + else if (!strcasecmp (argv[i], "-lang")) + { + if (i + 1 < argc) + { + char tmp[_MAX_PATH]; +#ifdef SYSTEM_WIN32 + GetModuleFileName (NULL, tmp, _MAX_PATH); +#else + safe_strncpy (tmp, argv[0], _MAX_PATH); +#endif + LoadLangFile (argv[++i], tmp); + } + else + { + Usage(); + } + } +#endif + else if (argv[i][0] == '-') //--vluzacn + { + Log("Unknown option: '%s'\n", argv[i]); + Usage (); + } + else + { + safe_strncpy(g_Mapname, argv[i], _MAX_PATH); +#ifdef ZHLT_DEFAULTEXTENSION_FIX + FlipSlashes(g_Mapname); +#endif + StripExtension(g_Mapname); +#ifndef ZHLT_DEFAULTEXTENSION_FIX + DefaultExtension(g_Mapname, ".bsp"); +#endif + } + } + +#ifndef RIPENT_TEXTURE + if (g_mode == hl_undefined) + { + Log("Must specify either -import or -export\n"); //--vluzacn + Usage(); + } +#endif + +#ifdef ZHLT_DEFAULTEXTENSION_FIX + char source[_MAX_PATH]; + safe_snprintf(source, _MAX_PATH, "%s.bsp", g_Mapname); + if (!q_exists(source)) + { + Log("bspfile '%s' does not exist\n", source); //--vluzacn + Usage(); + } +#else + if (!q_exists(g_Mapname)) + { + Log("bspfile '%s' does not exist\n", g_Mapname); //--vluzacn + Usage(); + } +#endif + +#ifdef ZHLT_PARAMFILE + LogStart(argcold, argvold); + { + int i; + Log("Arguments: "); + for (i = 1; i < argc; i++) + { + if (strchr(argv[i], ' ')) + { + Log("\"%s\" ", argv[i]); + } + else + { + Log("%s ", argv[i]); + } + } + Log("\n"); + } +#else + LogStart(argc, argv); +#endif + atexit(LogEnd); + + Settings(); + + dtexdata_init(); + atexit(dtexdata_free); + + // BEGIN RipEnt + start = I_FloatTime(); + +#ifdef RIPENT_TEXTURE + ReadBSP(g_Mapname); + bool updatebsp = false; +#ifdef ZHLT_EMBEDLIGHTMAP + if (g_deleteembeddedlightmaps) + { + DeleteEmbeddedLightmaps (); + updatebsp = true; + } +#endif + switch (g_mode) + { + case hl_import: + ReadEntities(g_Mapname); + updatebsp = true; + break; + case hl_export: + WriteEntities(g_Mapname); + break; + case hl_undefined: + break; + } + switch (g_texturemode) + { + case hl_import: + ReadTextures(g_Mapname); + updatebsp = true; + break; + case hl_export: + WriteTextures(g_Mapname); + break; + case hl_undefined: + break; + } + if (g_chart) + { +#ifdef ZHLT_64BIT_FIX +#ifdef PLATFORM_CAN_CALC_EXTENT + if (!CalcFaceExtents_test ()) + { + Warning ("internal error: CalcFaceExtents_test failed."); + } +#endif +#endif + PrintBSPFileSizes(); + } + if (updatebsp) + { + WriteBSP(g_Mapname); + } +#else + switch (g_mode) + { + case hl_import: + ReadBSP(g_Mapname); + ReadEntities(g_Mapname); +#ifdef ZHLT_CHART_AllocBlock + // moved here because the bsp data should not be referenced again after WriteBSPFile + if (g_chart) + { +#ifdef ZHLT_64BIT_FIX +#ifdef PLATFORM_CAN_CALC_EXTENT + if (!CalcFaceExtents_test ()) + { + Warning ("internal error: CalcFaceExtents_test failed."); + } +#endif +#endif + PrintBSPFileSizes(); + } +#endif + WriteBSP(g_Mapname); + break; + case hl_export: + ReadBSP(g_Mapname); + WriteEntities(g_Mapname); + break; + } + +#ifndef ZHLT_CHART_AllocBlock + if (g_chart) + { +#ifdef ZHLT_64BIT_FIX +#ifdef PLATFORM_CAN_CALC_EXTENT + if (!CalcFaceExtents_test ()) + { + Warning ("internal error: CalcFaceExtents_test failed."); + } +#endif +#endif + PrintBSPFileSizes(); + } +#endif +#endif + + end = I_FloatTime(); + LogTimeElapsed(end - start); + // END RipEnt +#ifdef ZHLT_PARAMFILE + } + } +#endif + + return 0; +} + +// do nothing - we don't have params to fetch +void GetParamsFromEnt(entity_t* mapent) {} diff --git a/src/zhlt-vluzacn/ripent/ripent.h b/src/zhlt-vluzacn/ripent/ripent.h new file mode 100644 index 0000000..bb62c2c --- /dev/null +++ b/src/zhlt-vluzacn/ripent/ripent.h @@ -0,0 +1,31 @@ +#include "cmdlib.h" +#include "messages.h" +#include "win32fix.h" +#include "log.h" +#include "hlassert.h" +#include "mathlib.h" +#include "scriplib.h" +#include "winding.h" +#include "threads.h" +#include "bspfile.h" +#include "blockmem.h" +#include "filelib.h" +#ifdef ZHLT_PARAMFILE +#include "cmdlinecfg.h" +#endif + +#define DEFAULT_PARSE false +#ifdef RIPENT_TEXTURE +#define DEFAULT_TEXTUREPARSE false +#endif +#define DEFAULT_CHART false +#define DEFAULT_INFO true +#ifdef ZHLT_64BIT_FIX +#define DEFAULT_WRITEEXTENTFILE false +#endif +#ifdef ZHLT_EMBEDLIGHTMAP +#ifdef RIPENT_TEXTURE +#define DEFAULT_DELETEEMBEDDEDLIGHTMAPS false +#endif +#endif + diff --git a/src/zhlt-vluzacn/ripent/ripent.vcproj b/src/zhlt-vluzacn/ripent/ripent.vcproj new file mode 100644 index 0000000..a9453fc --- /dev/null +++ b/src/zhlt-vluzacn/ripent/ripent.vcproj @@ -0,0 +1,353 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/zhlt-vluzacn/ripent/ripent.vcxproj b/src/zhlt-vluzacn/ripent/ripent.vcxproj new file mode 100644 index 0000000..746030b --- /dev/null +++ b/src/zhlt-vluzacn/ripent/ripent.vcxproj @@ -0,0 +1,172 @@ + + + + + Release + Win32 + + + Release + x64 + + + + + + {B057E5AD-13AF-2277-D7E0-2A7A16A9340F} + + + + Application + false + MultiByte + v140 + + + Application + false + MultiByte + v140 + + + + + + + + + + + + + + + .\Release\ + .\Release\ + false + + + .\Release_x64\ + .\Release_x64\ + false + + + + MultiThreaded + Default + true + true + Disabled + true + Level3 + ..\common;..\template;%(AdditionalIncludeDirectories) + RIPENT;VERSION_32BIT;NDEBUG;WIN32;_CONSOLE;SYSTEM_WIN32;STDC_HEADERS;%(PreprocessorDefinitions) + .\Release\ + true + .\Release\ripent.pch + .\Release\ + .\Release\ + true + true + + + .\Release\ripent.tlb + + + 0x0409 + NDEBUG;%(PreprocessorDefinitions) + + + true + .\Release\ripent.bsc + + + true + Console + false + .\Release\ripent.exe + binmode.obj;%(AdditionalDependencies) + 4194304 + 1048576 + false + + + + + MultiThreaded + Default + true + true + MaxSpeed + true + Level3 + ..\common;..\template;%(AdditionalIncludeDirectories) + RIPENT;VERSION_64BIT;NDEBUG;WIN32;_CONSOLE;SYSTEM_WIN32;STDC_HEADERS;%(PreprocessorDefinitions) + .\Release_x64\ + true + .\Release_x64\ripent.pch + .\Release_x64\ + .\Release_x64\ + true + true + + + .\Release_x64\ripent.tlb + + + 0x0409 + NDEBUG;%(PreprocessorDefinitions) + + + true + .\Release_x64\ripent.bsc + + + true + Console + false + .\Release_x64\ripent.exe + binmode.obj;%(AdditionalDependencies) + 4194304 + 1048576 + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/zhlt-vluzacn/ripent/ripent.vcxproj.filters b/src/zhlt-vluzacn/ripent/ripent.vcxproj.filters new file mode 100644 index 0000000..d51b7ac --- /dev/null +++ b/src/zhlt-vluzacn/ripent/ripent.vcxproj.filters @@ -0,0 +1,114 @@ + + + + + {8c07a06a-b602-4153-9eb4-32a39074ef20} + cpp;c;cxx;rc;def;r;odl;idl;hpj;bat + + + {3566b363-ed7d-4789-9fac-9f56a2570beb} + + + {98aaae27-89e6-48db-838f-0216e11f3dec} + h;hpp;hxx;hm;inl + + + {d271b46d-7d17-44c9-a9aa-1f497e487ea7} + ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe + + + + + Source Files\common + + + Source Files\common + + + Source Files\common + + + Source Files\common + + + Source Files\common + + + Source Files\common + + + Source Files\common + + + Source Files\common + + + Source Files\common + + + Source Files\common + + + Source Files + + + Source Files\common + + + Source Files\common + + + + + 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 + + + \ No newline at end of file diff --git a/src/zhlt-vluzacn/template/BaseMath.h b/src/zhlt-vluzacn/template/BaseMath.h new file mode 100644 index 0000000..757c04c --- /dev/null +++ b/src/zhlt-vluzacn/template/BaseMath.h @@ -0,0 +1,183 @@ +// Copyright (C) 2000 Sean Cavanaugh +// This file is licensed under the terms of the Lesser GNU Public License +// (see LPGL.txt, or http://www.gnu.org/copyleft/lesser.txt) + +#ifndef BASEMATH_H__ +#define BASEMATH_H__ + +#if _MSC_VER >= 1000 +#pragma once +#endif // _MSC_VER >= 1000 + +#if defined(_WIN32) && !defined(FASTCALL) +#define FASTCALL __fastcall +#endif + +// +// MIN/MAX/AVG functions, work best with basic types +// + +template < typename T > +inline T FASTCALL MIN(const T a, const T b) +{ + return ((a < b) ? a : b); +} + +template < typename T > +inline T FASTCALL MIN(const T a, const T b, const T c) +{ + return (MIN(MIN(a, b), c)); +} + +template < typename T > +inline T FASTCALL MAX(const T a, const T b) +{ + return ((a > b) ? a : b); +} + +template < typename T > +inline T FASTCALL MAX(const T a, const T b, const T c) +{ + return (MAX(MAX(a, b), c)); +} + +template < typename T > +inline T FASTCALL AVG(const T a, const T b) +{ + return ((a + b) / 2); +} + +// +// MIN/MAX/AVG functions, work best with user-defined types +// (hopefully the compiler will choose the right one in most cases +// + + +template < typename T > +inline T FASTCALL MIN(const T& a, const T& b) +{ + return ((a < b) ? a : b); +} + +template < typename T > +inline T FASTCALL MIN(const T& a, const T& b, const T& c) +{ + return (MIN(MIN(a, b), c)); +} + +template < typename T > +inline T FASTCALL MAX(const T& a, const T& b) +{ + return ((a > b) ? a : b); +} + +template < typename T > +inline T FASTCALL MAX(const T& a, const T& b, const T& c) +{ + return (MAX(MAX(a, b), c)); +} + +template < typename T > +inline T FASTCALL AVG(const T& a, const T& b) +{ + return ((a + b) / 2); +} + + +// +// Generic Array Operations +// + +template < typename T > +inline T FASTCALL MIN(const T* array, const int size) +{ + assert(size); + T val = array[0]; + + for (int i = 1; i < size; i++) + { + if (val > array[i]) + { + val = array[i]; + } + } + return val; +} + +template < typename T > +inline T FASTCALL MAX(const T* array, const int size) +{ + assert(size); + T val = array[0]; + + for (int i = 1; i < size; i++) + { + if (val < array[i]) + { + val = array[i]; + } + } + return val; +} + +template < typename T > +inline T FASTCALL AVG(const T* array, const int size) +{ + assert(size); + T sum = array[0]; + + for (int i = 1; i < size; i++) + { + sum += array[i]; + } + return sum / num; +} + +template < typename T > +inline T FASTCALL SUM(const T* array, const int size) +{ + assert(size); + T sum = array[0]; + + for (int i = 1; i < size; i++) + { + sum += array[i]; + } + return sum; +} + + +// Uses one temp to swap, works best with user-defined types or doubles/long doubles +template < typename T > +inline void FASTCALL SWAP(T& a, T& b) +{ + T temp = a; + + a = b; + b = temp; +} + + +// XOR math to swap (no temps), works with integral types very well +template < typename T > +inline void FASTCALL XOR_SWAP(T& a, T& b) +{ + a ^= b; + b ^= a; + a ^= b; +} + + +// Uses two temps to swap, works very well with built-in types on pipelined CPUs +template < typename T > +inline void FASTCALL PIPE_SWAP(T& a, T& b) +{ + T tmpA = a; + T tmpB = b; + + b = tmpA; + a = tmpB; +} + + +#endif // BASEMATH_H__ diff --git a/src/zhlt-vluzacn/template/EndianMath.h b/src/zhlt-vluzacn/template/EndianMath.h new file mode 100644 index 0000000..b18fcc7 --- /dev/null +++ b/src/zhlt-vluzacn/template/EndianMath.h @@ -0,0 +1,185 @@ +// Copyright (C) 2000 Sean Cavanaugh +// This file is licensed under the terms of the Lesser GNU Public License +// (see LPGL.txt, or http://www.gnu.org/copyleft/lesser.txt) + +#ifndef ENDIAN_H__ +#define ENDIAN_H__ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#include "basictypes.h" +#include "BaseMath.h" + +class Endian +{ +public: + inline static INT16 FASTCALL Flip(const INT16 x) + { + INT16 a = (x >> 8) & 0x000FF; + INT16 b = (x << 8) & 0x0FF00; + INT16 rval = (a | b); + return rval; + } + + inline static UINT16 FASTCALL Flip(const UINT16 x) + { + UINT16 a = (x >> 8) & 0x000FF; + UINT16 b = (x << 8) & 0x0FF00; + UINT16 rval = (a | b); + return rval; + } + + inline static INT32 FASTCALL Flip(const INT32 x) + { + INT32 a = (x >> 24) & 0x0000000FF; + INT32 b = (x >> 8) & 0x00000FF00; + INT32 c = (x << 8) & 0x000FF0000; + INT32 d = (x << 24) & 0x0FF000000; + INT32 rval = (a | b | c | d); + return rval; + } + + inline static UINT32 FASTCALL Flip(const UINT32 x) + { + INT32 a = (x >> 24) & 0x0000000FF; + INT32 b = (x >> 8) & 0x00000FF00; + INT32 c = (x << 8) & 0x000FF0000; + INT32 d = (x << 24) & 0x0FF000000; + INT32 rval = (a | b | c | d); + return rval; + } +#if 0 + inline static INT64 FASTCALL Flip(const INT64 x) + { + INT64 a = (x >> 56) & 0x000000000000000FF; + INT64 b = (x >> 40) & 0x0000000000000FF00; + INT64 c = (x >> 24) & 0x00000000000FF0000; + INT64 d = (x >> 8 ) & 0x000000000FF000000; + INT64 e = (x << 8 ) & 0x0000000FF00000000; + INT64 f = (x << 24) & 0x00000FF0000000000; + INT64 g = (x << 40) & 0x000FF000000000000; + INT64 h = (x << 56) & 0x0FF00000000000000; + INT64 rval = (a | b | c | d | e | f | g | h); + return rval; + } + + inline static UINT64 FASTCALL Flip(const UINT64 x) + { + UINT64 a = (x >> 56) & 0x000000000000000FF; + UINT64 b = (x >> 40) & 0x0000000000000FF00; + UINT64 c = (x >> 24) & 0x00000000000FF0000; + UINT64 d = (x >> 8 ) & 0x000000000FF000000; + UINT64 e = (x << 8 ) & 0x0000000FF00000000; + UINT64 f = (x << 24) & 0x00000FF0000000000; + UINT64 g = (x << 40) & 0x000FF000000000000; + UINT64 h = (x << 56) & 0x0FF00000000000000; + UINT64 rval = (a | b | c | d | e | f | g | h); + return rval; + } +#endif + inline static float FASTCALL Flip(const float x) + { + union floatflipper + { + struct _x_t + { + BYTE _v[4]; + } _x; + float _f; + }; + + floatflipper tmp; + tmp._f = x; + SWAP(tmp._x._v[0], tmp._x._v[3]); + SWAP(tmp._x._v[1], tmp._x._v[2]); + return tmp._f; + } + + inline static double FASTCALL Flip(const double x) + { + union floatflipper + { + struct _x_t + { + BYTE _v[8]; + } _x; + double _d; + }; + + floatflipper tmp; + tmp._d = x; + SWAP(tmp._x._v[0], tmp._x._v[7]); + SWAP(tmp._x._v[1], tmp._x._v[6]); + SWAP(tmp._x._v[2], tmp._x._v[5]); + SWAP(tmp._x._v[3], tmp._x._v[4]); + return tmp._d; + } + + inline static void FlipArray(unsigned size, INT16* x) + { + for (unsigned i=0 ; i 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#ifdef WIN32 +#define WIN32_LEAN_AND_MEAN +#include "windows.h" +#endif +#include "assert.h" +#include "limits.h" + +#include "ReferenceCounter.h" + +/*! + \author Sean Cavanaugh + \email sean@dimensionalrift.com + \cvsauthor $Author: sean $ + \date $Date: 2000/09/11 20:28:24 $ + \version $Revision: 1.1 $ + \brief ReferenceArray is exactly the same as ReferencePtr, except it expects + the objects pointing to it to be allocated with new[] instead (and thusly + the object is deleted with delete[] when the reference count hits zero) + Additionally it provides a [] operator which returns a refernece to the + item in the list. No bounds checking is performed. + + Arrays of basic data types (char, int, float, etc) should use ReferencePtr, + as delete[] is not required for them. ReferencePtr can also handle void types + as a template parameter. +*/ +template +class ReferenceArrayBlock +{ +public: + DATA_T* pData; // User defined data block + mutable ReferenceCounter ReferenceCount; +}; + + +template +class ReferenceArray +{ +public: + // Construction + ReferenceArray(); + ReferenceArray(DATA_T* other); + ReferenceArray(const ReferenceArray& other); + virtual ~ReferenceArray(); + + // Assignment + ReferenceArray& operator=(const ReferenceArray& other); + ReferenceArray& operator=(DATA_T* other); + + // Dereferencing + operator DATA_T*() const {return m_pData->pData;} + DATA_T& operator[](unsigned int offset) const {return m_pData->pData[offset];} + DATA_T& operator[](int offset) const {return m_pData->pData[offset];} + +protected: + // Internal methods + void Alloc(); // Allocate the m_pData + void Release(); // Releases a reference count (possibly freeing memory) + +protected: + // Member data + ReferenceArrayBlock* m_pData; +}; + + +template +ReferenceArray::ReferenceArray() +{ + Alloc(); +} + +template +ReferenceArray::ReferenceArray(DATA_T* other) +{ + Alloc(); + m_pData->pData = other; + m_pData->ReferenceCount = 1; +} + +template +ReferenceArray::ReferenceArray(const ReferenceArray& other) +{ + m_pData = other.m_pData; + m_pData->ReferenceCount++; +} + +template +ReferenceArray::~ReferenceArray() +{ + Release(); +} + +template +ReferenceArray& ReferenceArray::operator=(const ReferenceArray& other) +{ + if (m_pData != other.m_pData) + { + Release(); + m_pData = other.m_pData; + m_pData->ReferenceCount++; + } + return *this; +} + +template +ReferenceArray& ReferenceArray::operator=(DATA_T* other) +{ + if (m_pData->ReferenceCount.dec() <= 0) + { + delete[] m_pData->pData; + m_pData->pData = other; + m_pData->ReferenceCount = 1; + } + else + { + Alloc(); + m_pData->pData = other; + } + return *this; +} + +template +void ReferenceArray::Alloc() +{ + m_pData = new ReferenceArrayBlock; + m_pData->ReferenceCount = 1; + m_pData->pData = NULL; +} + +template +void ReferenceArray::Release() +{ + assert(m_pData != NULL); + if (m_pData->ReferenceCount.dec() <= 0) + { + delete[] m_pData->pData; + m_pData->pData = NULL; + delete m_pData; + m_pData = NULL; + } +} + +#endif // !defined(AFX_ReferenceArray_H__BAEBCE9D_CD68_40AF_8A54_B23A0D14E807__INCLUDED_) diff --git a/src/zhlt-vluzacn/template/ReferenceCounter.h b/src/zhlt-vluzacn/template/ReferenceCounter.h new file mode 100644 index 0000000..a3184b3 --- /dev/null +++ b/src/zhlt-vluzacn/template/ReferenceCounter.h @@ -0,0 +1,233 @@ +// Copyright (C) 2000 Sean Cavanaugh +// This file is licensed under the terms of the Lesser GNU Public License +// (see LPGL.txt, or http://www.gnu.org/copyleft/lesser.txt) + +#ifndef ReferenceCounter_H__ +#define ReferenceCounter_H__ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#endif + +#ifdef HAVE_SYS_ATOMIC_H +#include +#endif + +#ifdef HAVE_ASM_ATOMIC_H +#include +#endif + +/*! + \author Sean Cavanaugh + \email sean@dimensionalrift.com + \cvsauthor $Author: sean $ + \date $Date: 2000/09/11 20:28:24 $ + \version $Revision: 1.1 $ + \brief ReferenceCounter abstracts a reference counted integer with proper thread safe access + The interface is not platform specific in any way, except the protected data + is platform specific, as well as the implementation details +*/ + +class ReferenceCounter +{ +// Construction +public: + ReferenceCounter(); + ReferenceCounter(int InitialValue); + virtual ~ReferenceCounter() {} // Should optimize to nothing except in the derived-class case + ReferenceCounter(const ReferenceCounter& other) {copy(other);} + ReferenceCounter& operator=(const ReferenceCounter& other) {copy(other); return *this;} + +public: + // User functions + inline int add(int amt);// increment the value by amt, returns the ORIGINAL value + inline int sub(int amt);// increment the value by amt, returns the ORIGINAL value + inline int inc();// increment the value, returns the NEW value + inline int dec();// decrement the value, returns the NEW value + inline int read() const;// read the current value + inline void write(int newvalue);// change the counter to a new value blindly + inline int swap(int newvalue);// change the counter to a new value, and return the ORIGINAL value + + // Convenient Operators + int operator++() {return inc();} + int operator--() {return dec();} + int operator++(int) {return inc() - 1;} + int operator--(int) {return dec() + 1;} + int operator+=(int amt) {return add(amt) + amt;} + int operator-=(int amt) {return sub(amt) - amt;} + int operator=(int value) {write(value); return value;} + operator int() const {return read();} + +// Internal Methods +protected: + inline void copy(const ReferenceCounter& other); + +// Data +protected: +#ifdef SINGLE_THREADED + int m_atom; +#else //SINGLE_THREADED + +#ifdef _WIN32 + long m_atom; +#endif +#ifdef HAVE_ATOMIC + atomic_t m_atom; +#endif + +#endif//SINGLE_THREADED +}; + + +#ifdef SINGLE_THREADED +inline ReferenceCounter::ReferenceCounter() +{ + m_atom = 0; +} +inline ReferenceCounter::ReferenceCounter(int InitialValue) +{ + m_atom = InitialValue; +} +inline int ReferenceCounter::add(int amt) +{ + m_atom += amt; + return m_atom; +} +inline int ReferenceCounter::sub(int amt) +{ + m_atom -= amt; + return m_atom; +} +inline int ReferenceCounter::inc() +{ + m_atom++; + return m_atom; +} +inline int ReferenceCounter::dec() +{ + m_atom--; + return m_atom; +} +inline int ReferenceCounter::swap(int newvalue) +{ + int rval = m_atom; + m_atom = newvalue; + return rval; +} +inline void ReferenceCounter::write(int newvalue) +{ + m_atom = newvalue; +} +inline int ReferenceCounter::read() const +{ + return m_atom; +} +inline void ReferenceCounter::copy(const ReferenceCounter& other) +{ + m_atom = other.m_atom; +} +#else // SINGLE_THREADED + +#ifdef _WIN32 +inline ReferenceCounter::ReferenceCounter() +{ + m_atom = 0; +} +inline ReferenceCounter::ReferenceCounter(int InitialValue) +{ + m_atom = InitialValue; +} +inline int ReferenceCounter::add(int amt) +{ + return InterlockedExchangeAdd(&m_atom, amt); +} +inline int ReferenceCounter::sub(int amt) +{ + return InterlockedExchangeAdd(&m_atom, -amt); +} +inline int ReferenceCounter::inc() +{ + return InterlockedIncrement(&m_atom); +} +inline int ReferenceCounter::dec() +{ + return InterlockedDecrement(&m_atom); +} +inline int ReferenceCounter::swap(int newvalue) +{ + return InterlockedExchange(&m_atom, newvalue); +} +inline void ReferenceCounter::write(int newvalue) +{ + InterlockedExchange(&m_atom, newvalue); +} +inline int ReferenceCounter::read() const +{ + return m_atom; +} +inline void ReferenceCounter::copy(const ReferenceCounter& other) +{ + m_atom = other.m_atom; +} +#endif//_WIN32 + +#ifdef HAVE_ATOMIC +inline ReferenceCounter::ReferenceCounter() +{ + m_atom.counter = 0; +} +inline ReferenceCounter::ReferenceCounter(int InitialValue) +{ + m_atom.counter = InitialValue; +} +inline int ReferenceCounter::add(int amt) +{ + int rval = atomic_read(&m_atom); + atomic_add(amt, &m_atom); + return rval; +} +inline int ReferenceCounter::sub(int amt) +{ + int rval = atomic_read(&m_atom); + atomic_sub(amt, &m_atom); + return rval; +} +inline int ReferenceCounter::inc() +{ + int rval = atomic_read(&m_atom); + atomic_inc(&m_atom); + return rval + 1; +} +inline int ReferenceCounter::dec() +{ + int rval = atomic_read(&m_atom); + atomic_dec(&m_atom); + return rval - 1; +} +inline int ReferenceCounter::swap(int newvalue) +{ + int rval = atomic_read(&m_atom); + atomic_set(&m_atom, newvalue); + return rval; +} +inline void ReferenceCounter::write(int newvalue) +{ + atomic_set(&m_atom, newvalue); +} +inline int ReferenceCounter::read() const +{ + return atomic_read(&m_atom); +} +inline void ReferenceCounter::copy(const ReferenceCounter& other) +{ + m_atom.counter = other.read(); +} +#endif//HAVE_ATOMIC +#endif//SINGLE_THREADED + +#endif//ReferenceCounter_H__ diff --git a/src/zhlt-vluzacn/template/ReferenceObject.h b/src/zhlt-vluzacn/template/ReferenceObject.h new file mode 100644 index 0000000..e7319d4 --- /dev/null +++ b/src/zhlt-vluzacn/template/ReferenceObject.h @@ -0,0 +1,232 @@ +// Copyright (C) 2000 Sean Cavanaugh +// This file is licensed under the terms of the Lesser GNU Public License +// (see LPGL.txt, or http://www.gnu.org/copyleft/lesser.txt) + +#if !defined(AFX_REFERENCEOBJECT_H__BAEBCE9D_CD68_40AF_8A54_B23A0D14E807__INCLUDED_) +#define AFX_REFERENCEOBJECT_H__BAEBCE9D_CD68_40AF_8A54_B23A0D14E807__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#ifdef WIN32 +#define WIN32_LEAN_AND_MEAN +#include "windows.h" +#endif +#include "assert.h" +#include "limits.h" + +#include "ReferenceCounter.h" + +template +class ReferenceObjectBlock +{ +public: + DATA_T* pData; // User defined data block + mutable ReferenceCounter ReferenceCount; + unsigned int AllocLength; // Current size of array, in T sized units. if AllocLength = 0, pData is const and needs CopyOnWrite + HEADER_T Header; // User defined header block (must be safe to copy with operator=) +}; + + + +/*! + \author Sean Cavanaugh + \email sean@dimensionalrift.com + \cvsauthor $Author: sean $ + \date $Date: 2000/09/11 20:28:24 $ + \version $Revision: 1.1 $ + \brief ReferenceObject is a template class designed to be used a base class for pass-by-reference objects, to that things such as returning objects up the stack efficiently are possible. + \bug EVERY non const function needs its very first operation to be CopyForWrite() + \bug EVERY derived class should define its operator=() to call the ReferenceObject base class version (notice this operator is protected,) + \bug HEADER_T will probably need an operator=() defined for all but the most trivial types + \bug Store objects or simple data types, NO POINTERS (use ReferencePtrObject for that) (they will leak like mad) + +*/ + + + +template +class ReferenceObject +{ +public: + ReferenceObject(); + ReferenceObject(unsigned int size); + ReferenceObject(const ReferenceObject& other); + virtual ~ReferenceObject(); + +public: + HEADER_T& getHeader(); + DATA_T* getData(); + + ReferenceObject& operator=(const ReferenceObject& other); + +protected: + void AllocBlock(unsigned int size); // Allocate Header+Data Block (size in T units) + void AllocHeader(); // Allocate Header Block + void AllocData(unsigned int size); // Alocate Data Block (size in T units) + + virtual void Release(); // Releases a reference count (possibly freeing memory) + virtual void InitHeader(); // User defined Header Initialization function + virtual void FreeHeader(); // User defined Header destructor function + virtual void CopyForWrite(); // Make unique copy for writing, must first instruction in all non const-functions in derived classes + virtual void CopyForWrite(unsigned int size); // same as CopyForWrite() except takes a resize parameter + + ReferenceObjectBlock* m_pData; +}; + + +template +ReferenceObject::ReferenceObject() +{ + m_pData = NULL; + AllocHeader(); + InitHeader(); +} + +template +ReferenceObject::ReferenceObject(unsigned int size) +{ + m_pData = NULL; + AllocBlock(size); +} + +template +ReferenceObject::ReferenceObject(const ReferenceObject& other) +{ + m_pData = other.m_pData; + m_pData->ReferenceCount++; +} + +template +ReferenceObject::~ReferenceObject() +{ + Release(); +} + +template +ReferenceObject& ReferenceObject::operator=(const ReferenceObject& other) +{ + if (m_pData != other.m_pData) + { + Release(); + m_pData = other.m_pData; + m_pData->ReferenceCount++; + } + return *this; +} + +template +void ReferenceObject::Release() +{ + assert(m_pData != NULL); + if (m_pData->ReferenceCount.dec() <= 0) + { + FreeHeader(); + delete[] m_pData->pData; + delete m_pData; + m_pData = NULL; + } +} + +template +void ReferenceObject::AllocBlock(unsigned int size) +{ + AllocHeader(); + AllocData(size); + InitHeader(); // Initialize user defined header +} + +template +void ReferenceObject::AllocHeader() +{ + m_pData = new ReferenceObjectBlock; + + m_pData->ReferenceCount = 1; + m_pData->pData = NULL; + m_pData->AllocLength = 0; +} + +template +void ReferenceObject::AllocData(unsigned int size) +{ + assert(m_pData != NULL); + assert((size * sizeof(DATA_T)) < INT_MAX); + + if (size) + { + m_pData->pData = new DATA_T[size]; + } + else + { + m_pData->pData = NULL; + } + m_pData->AllocLength = size; +} + +template +void ReferenceObject::InitHeader() +{ + // NOTE: Derive this function to initialize the Header object(s) +} + +template +void ReferenceObject::FreeHeader() +{ + // NOTE: Derive this function to clean up the Header object(s) +} + +template +void ReferenceObject::CopyForWrite() +{ + CopyForWrite(m_pData->AllocLength); +} + +template +void ReferenceObject::CopyForWrite(unsigned int size) +{ + unsigned int oldsize = m_pData->AllocLength; + + if (size) + { + if ((m_pData->ReferenceCount > 1) || (size != oldsize)) + { + ReferenceObjectBlock* pTmp = m_pData; + AllocBlock(size); + m_pData->Header = pTmp->Header; + unsigned int sizetocopy = min(size,oldsize); + // memcpy(m_pData->pData, pTmp->pData, min(size,oldsize) * sizeof(DATA_T)); // memcpy not safe for objects + for (unsigned int x=0;xpData[x] = pTmp->pData[x]; + } + if (pTmp->ReferenceCount.dec() <= 0) + { + delete[] pTmp->pData; + delete pTmp; + } + } + } + else // Replace reference to a null object (since size is zero) + { + Release(); + AllocHeader(); + InitHeader(); + } +} + +template +HEADER_T& ReferenceObject::getHeader() +{ + CopyForWrite(); + return m_pData->Header; +} + +template +DATA_T* ReferenceObject::getData() +{ + CopyForWrite(); + return m_pData->pData; +} + +#endif // !defined(AFX_REFERENCEOBJECT_H__BAEBCE9D_CD68_40AF_8A54_B23A0D14E807__INCLUDED_) diff --git a/src/zhlt-vluzacn/template/ReferencePtr.h b/src/zhlt-vluzacn/template/ReferencePtr.h new file mode 100644 index 0000000..1c8a20b --- /dev/null +++ b/src/zhlt-vluzacn/template/ReferencePtr.h @@ -0,0 +1,155 @@ +// Copyright (C) 2000 Sean Cavanaugh +// This file is licensed under the terms of the Lesser GNU Public License +// (see LPGL.txt, or http://www.gnu.org/copyleft/lesser.txt) + +#if !defined(AFX_ReferencePtr_H__BAEBCE9D_CD68_40AF_8A54_B23A0D14E807__INCLUDED_) +#define AFX_ReferencePtr_H__BAEBCE9D_CD68_40AF_8A54_B23A0D14E807__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#ifdef WIN32 +#define WIN32_LEAN_AND_MEAN +#include "windows.h" +#endif +#include "assert.h" +#include "limits.h" + +#ifdef SYSTEM_POSIX +#ifdef HAVE_STDDEF_H +// For NULL +#include +#endif +#endif + +#include "ReferenceCounter.h" + +/*! + \author Sean Cavanaugh + \email sean@dimensionalrift.com + \cvsauthor $Author: sean $ + \date $Date: 2000/09/11 20:28:24 $ + \version $Revision: 1.1 $ + \brief ReferencePtr is a simplified ReferencePtr, primiarily for use with reference counted pointers. + Its purpose is solely for simplified garbage collection, and not any kind of advanced + copy on write functionality. Passing a normal pointer to this class effectively starts + its reference count at one and should not be manually deleted. (But it may be referenced + as long as you know the object still exists in some scope somewhere) +*/ +template +class ReferencePtrBlock +{ +public: + DATA_T* pData; // User defined data block + mutable ReferenceCounter ReferenceCount; +}; + + +template +class ReferencePtr +{ +public: + // Construction + ReferencePtr(); + ReferencePtr(DATA_T* other); + ReferencePtr(const ReferencePtr& other); + virtual ~ReferencePtr(); + + // Assignment + ReferencePtr& operator=(const ReferencePtr& other); + ReferencePtr& operator=(DATA_T* other); + + // Dereferencing + operator DATA_T*() const {return m_pData->pData;} + DATA_T* operator->() const {return m_pData->pData;} + +protected: + // Internal methods + void Alloc(); // Allocate the m_pData + void Release(); // Releases a reference count (possibly freeing memory) + +protected: + // Member data + ReferencePtrBlock* m_pData; +}; + + +template +ReferencePtr::ReferencePtr() +{ + Alloc(); +} + +template +ReferencePtr::ReferencePtr(DATA_T* other) +{ + Alloc(); + m_pData->pData = other; + m_pData->ReferenceCount = 1; +} + +template +ReferencePtr::ReferencePtr(const ReferencePtr& other) +{ + m_pData = other.m_pData; + m_pData->ReferenceCount++; +} + +template +ReferencePtr::~ReferencePtr() +{ + Release(); +} + +template +ReferencePtr& ReferencePtr::operator=(const ReferencePtr& other) +{ + if (m_pData != other.m_pData) + { + Release(); + m_pData = other.m_pData; + m_pData->ReferenceCount++; + } + return *this; +} + +template +ReferencePtr& ReferencePtr::operator=(DATA_T* other) +{ + if (m_pData->ReferenceCount.dec() <= 0) + { + delete m_pData->pData; + m_pData->pData = other; + m_pData->ReferenceCount = 1; + } + else + { + Alloc(); + m_pData->pData = other; + } + return *this; +} + +template +void ReferencePtr::Alloc() +{ + m_pData = new ReferencePtrBlock; + m_pData->ReferenceCount = 1; + m_pData->pData = NULL; +} + +template +void ReferencePtr::Release() +{ + assert(m_pData != NULL); + if (m_pData->ReferenceCount.dec() <= 0) + { + delete m_pData->pData; + m_pData->pData = NULL; + delete m_pData; + m_pData = NULL; + } +} + +#endif // !defined(AFX_ReferencePtr_H__BAEBCE9D_CD68_40AF_8A54_B23A0D14E807__INCLUDED_) diff --git a/src/zhlt-vluzacn/template/basictypes.h b/src/zhlt-vluzacn/template/basictypes.h new file mode 100644 index 0000000..755bda1 --- /dev/null +++ b/src/zhlt-vluzacn/template/basictypes.h @@ -0,0 +1,77 @@ +// Copyright (C) 2000 Sean Cavanaugh +// This file is licensed under the terms of the Lesser GNU Public License +// (see LPGL.txt, or http://www.gnu.org/copyleft/lesser.txt) + +#if 0 // linux fix --vluzacn +// AJM GNU +#ifdef __GNUC__ +#define __int64 long long +#endif +#endif + +#ifndef BASICTYPES_H__ +#define BASICTYPES_H__ + +#if _MSC_VER > 1000 +#pragma once +#endif /* _MSC_VER > 1000 */ + +#if defined(_WIN32) || defined(SYSTEM_WIN32) + +#undef CHAR +#undef BYTE +#undef INT +#undef UINT +#undef INT8 +#undef UINT8 +#undef INT16 +#undef UINT16 +#undef INT32 +#undef UINT32 +#undef INT64 +#undef UINT64 +typedef char CHAR; +typedef unsigned char BYTE; +typedef signed int INT; +typedef unsigned int UINT; +typedef signed char INT8; +typedef unsigned char UINT8; +typedef signed short INT16; +typedef unsigned short UINT16; +typedef signed int INT32; +typedef unsigned int UINT32; +typedef signed __int64 INT64; +typedef unsigned __int64 UINT64; + +#endif /* SYSTEM_WIN32 */ + +#ifdef SYSTEM_POSIX + +#undef CHAR +#undef BYTE +#undef INT +#undef UINT +#undef INT8 +#undef UINT8 +#undef INT16 +#undef UINT16 +#undef INT32 +#undef UINT32 +#undef INT64 +#undef UINT64 +typedef char CHAR; +typedef unsigned char BYTE; +typedef signed int INT; +typedef unsigned int UINT; +typedef signed char INT8; +typedef unsigned char UINT8; +typedef signed short INT16; +typedef unsigned short UINT16; +typedef signed int INT32; +typedef unsigned int UINT32; +/* typedef __int64 INT64; */ +/* typedef unsigned __int64 UINT64; */ + +#endif /* SYSTEM_POSIX */ + +#endif /* BASICTYPES_H__ */ diff --git a/src/zhlt-vluzacn/zhlt.sln b/src/zhlt-vluzacn/zhlt.sln new file mode 100644 index 0000000..c8f9f14 --- /dev/null +++ b/src/zhlt-vluzacn/zhlt.sln @@ -0,0 +1,44 @@ + +Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual Studio 2010 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "hlbsp", "hlbsp\hlbsp.vcxproj", "{E75CEB5E-EBAE-1E7D-A626-81604E140A5F}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "hlcsg", "hlcsg\hlcsg.vcxproj", "{505681C2-3E57-300B-D330-46DD50C147D2}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "hlrad", "hlrad\hlrad.vcxproj", "{3B5F6C9B-1238-1ECF-14D8-01E4C3AB0EE1}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "hlvis", "hlvis\hlvis.vcxproj", "{76051CAC-5741-AF85-0C95-3A214F58D9AD}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ripent", "ripent\ripent.vcxproj", "{B057E5AD-13AF-2277-D7E0-2A7A16A9340F}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {E75CEB5E-EBAE-1E7D-A626-81604E140A5F}.Release|Win32.ActiveCfg = Release|Win32 + {E75CEB5E-EBAE-1E7D-A626-81604E140A5F}.Release|Win32.Build.0 = Release|Win32 + {E75CEB5E-EBAE-1E7D-A626-81604E140A5F}.Release|x64.ActiveCfg = Release|x64 + {E75CEB5E-EBAE-1E7D-A626-81604E140A5F}.Release|x64.Build.0 = Release|x64 + {505681C2-3E57-300B-D330-46DD50C147D2}.Release|Win32.ActiveCfg = Release|Win32 + {505681C2-3E57-300B-D330-46DD50C147D2}.Release|Win32.Build.0 = Release|Win32 + {505681C2-3E57-300B-D330-46DD50C147D2}.Release|x64.ActiveCfg = Release|x64 + {505681C2-3E57-300B-D330-46DD50C147D2}.Release|x64.Build.0 = Release|x64 + {3B5F6C9B-1238-1ECF-14D8-01E4C3AB0EE1}.Release|Win32.ActiveCfg = Release|Win32 + {3B5F6C9B-1238-1ECF-14D8-01E4C3AB0EE1}.Release|Win32.Build.0 = Release|Win32 + {3B5F6C9B-1238-1ECF-14D8-01E4C3AB0EE1}.Release|x64.ActiveCfg = Release|x64 + {3B5F6C9B-1238-1ECF-14D8-01E4C3AB0EE1}.Release|x64.Build.0 = Release|x64 + {76051CAC-5741-AF85-0C95-3A214F58D9AD}.Release|Win32.ActiveCfg = Release|Win32 + {76051CAC-5741-AF85-0C95-3A214F58D9AD}.Release|Win32.Build.0 = Release|Win32 + {76051CAC-5741-AF85-0C95-3A214F58D9AD}.Release|x64.ActiveCfg = Release|x64 + {76051CAC-5741-AF85-0C95-3A214F58D9AD}.Release|x64.Build.0 = Release|x64 + {B057E5AD-13AF-2277-D7E0-2A7A16A9340F}.Release|Win32.ActiveCfg = Release|Win32 + {B057E5AD-13AF-2277-D7E0-2A7A16A9340F}.Release|Win32.Build.0 = Release|Win32 + {B057E5AD-13AF-2277-D7E0-2A7A16A9340F}.Release|x64.ActiveCfg = Release|x64 + {B057E5AD-13AF-2277-D7E0-2A7A16A9340F}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/src/zhlt-vluzacn/zhlt_vc2005.sln b/src/zhlt-vluzacn/zhlt_vc2005.sln new file mode 100644 index 0000000..d489e65 --- /dev/null +++ b/src/zhlt-vluzacn/zhlt_vc2005.sln @@ -0,0 +1,48 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.27703.2042 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "hlbsp", "hlbsp\hlbsp.vcxproj", "{E75CEB5E-EBAE-1E7D-A626-81604E140A5F}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "hlcsg", "hlcsg\hlcsg.vcxproj", "{505681C2-3E57-300B-D330-46DD50C147D2}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "hlrad", "hlrad\hlrad.vcxproj", "{3B5F6C9B-1238-1ECF-14D8-01E4C3AB0EE1}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "hlvis", "hlvis\hlvis.vcxproj", "{76051CAC-5741-AF85-0C95-3A214F58D9AD}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ripent", "ripent\ripent.vcxproj", "{B057E5AD-13AF-2277-D7E0-2A7A16A9340F}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {E75CEB5E-EBAE-1E7D-A626-81604E140A5F}.Release|Win32.ActiveCfg = Release|Win32 + {E75CEB5E-EBAE-1E7D-A626-81604E140A5F}.Release|Win32.Build.0 = Release|Win32 + {E75CEB5E-EBAE-1E7D-A626-81604E140A5F}.Release|x64.ActiveCfg = Release|x64 + {E75CEB5E-EBAE-1E7D-A626-81604E140A5F}.Release|x64.Build.0 = Release|x64 + {505681C2-3E57-300B-D330-46DD50C147D2}.Release|Win32.ActiveCfg = Release|Win32 + {505681C2-3E57-300B-D330-46DD50C147D2}.Release|Win32.Build.0 = Release|Win32 + {505681C2-3E57-300B-D330-46DD50C147D2}.Release|x64.ActiveCfg = Release|x64 + {505681C2-3E57-300B-D330-46DD50C147D2}.Release|x64.Build.0 = Release|x64 + {3B5F6C9B-1238-1ECF-14D8-01E4C3AB0EE1}.Release|Win32.ActiveCfg = Release|Win32 + {3B5F6C9B-1238-1ECF-14D8-01E4C3AB0EE1}.Release|Win32.Build.0 = Release|Win32 + {3B5F6C9B-1238-1ECF-14D8-01E4C3AB0EE1}.Release|x64.ActiveCfg = Release|x64 + {3B5F6C9B-1238-1ECF-14D8-01E4C3AB0EE1}.Release|x64.Build.0 = Release|x64 + {76051CAC-5741-AF85-0C95-3A214F58D9AD}.Release|Win32.ActiveCfg = Release|Win32 + {76051CAC-5741-AF85-0C95-3A214F58D9AD}.Release|Win32.Build.0 = Release|Win32 + {76051CAC-5741-AF85-0C95-3A214F58D9AD}.Release|x64.ActiveCfg = Release|x64 + {76051CAC-5741-AF85-0C95-3A214F58D9AD}.Release|x64.Build.0 = Release|x64 + {B057E5AD-13AF-2277-D7E0-2A7A16A9340F}.Release|Win32.ActiveCfg = Release|Win32 + {B057E5AD-13AF-2277-D7E0-2A7A16A9340F}.Release|Win32.Build.0 = Release|Win32 + {B057E5AD-13AF-2277-D7E0-2A7A16A9340F}.Release|x64.ActiveCfg = Release|x64 + {B057E5AD-13AF-2277-D7E0-2A7A16A9340F}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {F816C76C-2392-4013-89F9-F16BFF5D9210} + EndGlobalSection +EndGlobal diff --git a/tools/lights.rad b/tools/lights.rad new file mode 100644 index 0000000..d2f7f6d --- /dev/null +++ b/tools/lights.rad @@ -0,0 +1,48 @@ ++0~GENERIC65 255 255 255 750 ++0~GENERIC85 255 255 255 20000 ++0~GENERIC86 255 255 255 10000 ++0~GENERIC86B 255 255 255 20000 ++0~GENERIC86R 255 255 255 60000 +GENERIC87A 255 255 255 1000 +GENERIC88A 255 255 255 1000 +GENERIC89A 255 255 255 1000 +GENERIC90A 255 255 255 1000 +GENERIC105 255 255 255 1000 +GENERIC106 255 255 255 1000 +GENERIC107 255 255 255 1000 +GEN_VEND1 255 255 255 1000 +EMERGLIGHT 255 255 255 50000 ++0~FIFTS_LGHT01 255 255 255 4000 ++0~FIFTIES_LGT2 255 255 255 5000 ++0~FIFTS_LGHT4 255 255 255 4000 ++0~LIGHT1 255 255 255 3000 ++0~LIGHT3A 255 255 255 10000 ++0~LIGHT4A 255 255 255 11000 ++0~LIGHT5A 255 255 255 10000 ++0~LIGHT6A 255 255 255 25000 ++0~TNNL_LGT1 255 255 255 10000 ++0~TNNL_LGT2 255 255 255 12000 ++0~TNNL_LGT3 255 255 255 17000 ++0~TNNL_LGT4 255 255 255 10000 ++0LAB1_W6D 255 255 255 4000 ++0LAB1_W6 255 255 255 8800 ++0LAB1_W7 255 255 255 4000 +SKKYLITE 255 255 255 1000 ++0~DRKMTLS1 255 255 255 6000 ++0~DRKMTLGT1 255 255 255 6000 ++0~DRKMTLS2 255 255 255 30000 ++0~DRKMTLS2C 255 255 255 50000 ++0DRKMTL_SCRN 255 255 255 10000 +~LAB_CRT9A 255 255 255 100 +~LAB_CRT9B 255 255 255 100 +~LAB_CRT9C 255 255 255 100 +~LIGHT3A 255 255 255 3000 +~LIGHT3B 255 255 255 2000 +~LIGHT3C 255 255 255 2500 +~LIGHT3E 255 255 255 6000 +C1A3C_MAP 255 255 255 100 +FIFTIES_MON1B 255 255 255 30 ++0~LAB_CRT8 255 255 255 100 +ELEV2_CIEL 255 255 255 800 +YELLOW 255 255 255 2000 +RED 255 255 255 1000 diff --git a/tools/settings.txt b/tools/settings.txt new file mode 100644 index 0000000..a03566e --- /dev/null +++ b/tools/settings.txt @@ -0,0 +1,119 @@ + +// This file is loaded each time the compiler start. It can modify the command line parameters. +// This file can be used to alter default parameters without changing the binaries. + + +// How does this file work : +// Basically, it directly modifies the command line when the program starts. +// The command line is treated as a sequence of parameters. +// For example, the command line +// tools\hlrad.exe "My Maps\mymap" -extra -texchop 64 +// is converted into 5 parameters: +// "" (the first parameter will always be replaced by the program name) +// "My Maps\mymap" +// "-extra" +// "-texchop" +// "64" +// . +// This file is composed of commands. Each word that begins with '#' is a command. Some commands can be followed with a sequence of parameters (denote as A B C). +// If a parameter has space in itself, it should be quoted by '"'. +// List of commands: +// #ifdef A B C If there is a sequence of parameters in the command line that matches A B C, execute the following commands till an '#else' or '#endif'. +// #ifndef A B C If there is not a sequence of parameters in the command line that matches A B C, execute the following commands till an '#else' or '#endif'. +// #else If previous commands has been skipped, execute the following commands till an '#else' or '#endif'. +// #define A B C Add parameters A B C to the end of the command line. +// #undef A B C Remove any sequence of patamenters in the command line that matches A B C. +// Special kinds of parameter: +// First character is '*' Match any parameter that ends with certain word. +// Last character is '*' Match any parameter that begins with certain word. + + + +// List of frequently used parameters: +// +// Shared: +// -low +// -estimate +// -chart +// +// HLCSG: +// -onlyents +// -wadautodetect +// -nowadtextures +// -wadinclude # +// -wadcfgfile # +// +// HLBSP: +// -maxnodesize # +// +// HLVIS: +// -fast +// -full +// +// HLRAD: +// -limiter # +// -fast +// -extra +// -blur # +// -smooth # +// -smooth2 # +// -texreflectscale # +// -chop # +// -texchop # +// -drawoverload +// -gamma # +// -minlight # +// +// RIPENT: +// -export +// -import +// -parse +// -textureexport +// -textureimport +// -textureparse + + + +#ifndef // CSG\BSP\VIS\RAD + #ifndef -high + #ifndef -low + #define -low + #endif + #endif + #ifndef -console 0 + //#define -estimate + #endif +#endif + +#ifdef // CSG + #ifndef -wadautodetect + #define -wadautodetect + #endif +#endif + +#ifdef // BSP + #ifndef -chart + #define -chart + #endif +#endif + +#ifdef // VIS + #ifndef -full + //#define -full + #endif +#endif + +#ifdef // RAD + #ifdef -sparse + #undef -sparse + #define -vismatrix sparse + #endif + #ifdef -nomatrix + #undef -nomatrix + #define -vismatrix off + #endif +#endif + +#ifdef // RIPENT +#endif + diff --git a/tools/wad.cfg b/tools/wad.cfg new file mode 100644 index 0000000..764c5a9 --- /dev/null +++ b/tools/wad.cfg @@ -0,0 +1,56 @@ +// GAME TEXTURE WAD CONFIGURATION FILE for 1.7 + +// use this file to set the different .wad files to be written into +// the .bsp file by CSG regardless of the .wad files you might have +// configured in Worldcraft. + +// if you want to use the configurations in this file, you MUST +// specify the apropriate configuration with the +// -wadconfig configration_name +// parameter on CSG. otherwise, the wadfile paths in the mapfile +// will be used. + +// if you DO specify a configuration, the wadfile paths specified +// in the map file will be ignored. basically, its either this file +// or the map file, not a mixture of both. + +// if you want to wadinclude a specific file, you may do so using +// the "include" prefix. the valve configuration below has an +// example commented out. all 3 examples in this file are perfectly +// valid, use whichever method you are comfortable with. + +// make sure you change these paths to the real path to your +// Half-Life directory. note, the syntax of this file has changed +// significantly since version 1.4 + +valve +{ +c:\Sierra\Half-Life\valve\halflife.wad +c:\Sierra\Half-Life\valve\liquids.wad +c:\Sierra\Half-Life\valve\xeno.wad +c:\Sierra\Half-Life\valve\decals.wad +//include c:\Sierra\Half-Life\valve\mywad.wad +} + +tfc +{ +// standard half-life wads +c:\Sierra\Half-Life\valve\halflife.wad c:\Sierra\Half-Life\valve\liquids.wad +c:\Sierra\Half-Life\valve\decals.wad c:\Sierra\Half-Life\valve\xeno.wad + +// tfc specific wads +c:\Sierra\Half-Life\tfc\tfc.wad c:\Sierra\Half-Life\tfc\tfc2.wad +} + +cs +{ + // counter-strike specific wads + c:\Sierra\Half-Life\cstrike\n0th1ng.wad + c:\Sierra\Half-Life\cstrike\cstrike.wad + + // standard half-life wads + c:\Sierra\Half-Life\valve\halflife.wad + c:\Sierra\Half-Life\valve\liquids.wad + c:\Sierra\Half-Life\valve\xeno.wad + c:\Sierra\Half-Life\valve\decals.wad +} diff --git a/tools/zhlt.fgd b/tools/zhlt.fgd new file mode 100644 index 0000000..4971e84 --- /dev/null +++ b/tools/zhlt.fgd @@ -0,0 +1,247 @@ + +// light_shadow +// It creates toggleable shadow for func_door, func_breakable, ... +@PointClass color(255 255 0) = light_shadow : "Dynamic shadow control" +[ + // Control the shadow of this solid entity. + // The solid entity must have 'Opaque' set in its lightflags. + target(target_destination) : "Target solid entity" + // Switch the light_shadow on/off will cause shadow to disappear/appear. + targetname(target_source) : "Name" + style(choices) : "Appearance (no name allowed)" : "" = + [ + "" : "Normal" + 10: "Fluorescent flicker" + 2 : "Slow, strong pulse" + 11: "Slow pulse, noblack" + 5 : "Gentle pulse" + 1 : "Flicker A" + 6 : "Flicker B" + 3 : "Candle A" + 7 : "Candle B" + 8 : "Candle C" + 4 : "Fast strobe" + 9 : "Slow strobe" + 12 : "Underwater mutation" + ] + pattern(string) : "Custom Appearance" + convertto(choices) : "Classname in game" : "light" = + [ + "light" : "light" + "light_spot" : "light_spot" + ] + spawnflags(flags) = + [ + 1 : "Initially dark" : 0 + 2048 : "Not in Deathmatch" : 0 + ] +] + +// light_bounce +// Use as complement for light_shadow. +@PointClass color(255 255 0) = light_bounce : "Bounce light control" +[ + // Control the light bounced from this solid entity. + target(target_destination) : "Target solid entity" + // When the targeted entity disappear, switch on the light_shadow and switch off the light_bounce at the same time. + targetname(target_source) : "Name" + style(choices) : "Appearance (no name allowed)" : "" = + [ + "" : "Normal" + 10: "Fluorescent flicker" + 2 : "Slow, strong pulse" + 11: "Slow pulse, noblack" + 5 : "Gentle pulse" + 1 : "Flicker A" + 6 : "Flicker B" + 3 : "Candle A" + 7 : "Candle B" + 8 : "Candle C" + 4 : "Fast strobe" + 9 : "Slow strobe" + 12 : "Underwater mutation" + ] + pattern(string) : "Custom Appearance" + convertto(choices) : "Classname in game" : "light" = + [ + "light" : "light" + "light_spot" : "light_spot" + ] + spawnflags(flags) = + [ + 1 : "Initially dark" : 0 + 2048 : "Not in Deathmatch" : 0 + ] +] + +// info_overview_point +// It makes all entities visible from this place. This is useful for overview mode (dev_overview 1). +// If "Reversed" is selected, this place will become visible from the entire map. This is useful for large skybox model. +@PointClass color(255 0 0) = info_overview_point : "Disable VIS here for overview" +[ + reverse(choices) : "Reversed" : "" = + [ + "": "No" + 1: "Yes" + ] +] + +// info_sunlight +// It generates a fake light_environment which defines sv_skycolor and sv_skyvec in game. +// If you are using multiple light_environments, you will probably need this entity. +@PointClass color(255 255 0) = info_sunlight : "light_environment information that affects model brightness" +[ + target(target_destination) : "Target" + angles(string) : "Pitch Yaw Roll (Y Z X)" : "0 0 0" + pitch(integer) : "Pitch" : -90 + _light(color255) : "Brightness" : "0 0 0 100" +] + +// func_group +// It is not a real entity. Brushes in this entity are still world brushes. +@SolidClass = func_group : "Solid brushes" +[ + zhlt_coplanarpriority(integer) : "Priority when faces overlap" : 0 + zhlt_noclip(choices) : "Passable" : "" = + [ + "": "No" + 1: "Yes" + ] +] + +// info_texlights +// It defines texture lights. +// Add any texture name as a key and their brightness as the value. +// Don't need to set colors because hlrad knows texture colors. +@PointClass color(255 0 0) = info_texlights : "Texture name : Brightness" +[ +] + +// info_smoothvalue +// It specifies smoothing threshold angle for each texture. +@PointClass color(255 0 0) = info_smoothvalue : "Texture name : Threshold of smooth angle" +[ +] + +// info_translucent +// It defines translucent effect for textures. +// 0.0 = normal (only receive light from front), 1.0 = receive 100% light from back and 0% from front. +// Can be used to simulate materials like fabric, coarse glass, plastic. +// The thickness of brush with translucent textures can not exceed 2 units. +@PointClass color(255 0 0) = info_translucent : "Texture name : translucent amount (0.0-1.0)" +[ +] + +// info_angularfade +// It gives textures metal like look. +// 1.0 = normal; higher value = brightness decrease more quickly when the angle increases +// Do not use this effect too much, because it looks unnatural and exaggerated. +@PointClass color(255 0 0) = info_angularfade : "Texture name : the speed at which light fade as its direction becomes parellel to the texture (default 1.0)" +[ +] + +// light_surface +// It defines texture lights. +// It is recommended to replace lights.rad and info_texlights with this entity. +@PointClass color(255 255 0) = light_surface : "Advanced texture light" +[ + _tex(string) : "Texture name" : "" // texture name (not case sensitive) + _frange(string) : " Filter max distance" : "" // max distance from face center to this entity + _fdist(string) : " Filter max dist to plane" : "" // max distance from face plane to this entity + _fclass(string) : " Filter entity classname" : "" + _fname(string) : " Filter entity name" : "" + _light(color255) : "Texture brightness" : "255 255 255 80" // value >= 80 will ensure full brightness. Colored brightness is not recommended. + _texcolor(color255) : " Color(replace texture color)" : "" // emit light as if the texture is in this color + // Note: + // If you want to set cone angle or any other value to 0, + // '0.0' should be used instead of '0'. + // This is a bug in Hammer. + _cone(string) : " Inner(bright) angle(90default)" : "" // should be 90 for conventional texlights + _cone2(string) : " Outer(fading) angle(90default)" : "" // should be 90 for conventional texlights + _scale(string) : " Adjust emit scale(1.0default)" : "" // 0.0 = no emitting + _chop(string) : " Grid size of sampling" : "" // in inch; not affected by texture scale + _texlightgap(choices) : " Dark gap in front of texlight" : "" = // in texture pixels; size of dark area near the light source + [ + "": "Default (no gap)" + "0.0": "0.0 - no gap" + "3.0": "3.0 - small gap" + "12.0": "12.0 - large gap" + ] + _fast(choices) : " Fast" : "" = + [ + "": "Auto" + 1: "Yes" + 2: "No" + ] + // 'light_surface' will not be recognized by the game if we don't change its classname. + convertto(choices) : "Classname in game" : "light" = + [ + "light" : "light" + "light_spot" : "light_spot" + ] + targetname(target_source) : " Name" : "" // create a new light style with this name + style(choices) : " Appearance (no name allowed)" : "" = // use predefined light styles which have predefined patterns + [ + "" : "Normal" + 10: "Fluorescent flicker" + 2 : "Slow, strong pulse" + 11: "Slow pulse, noblack" + 5 : "Gentle pulse" + 1 : "Flicker A" + 6 : "Flicker B" + 3 : "Candle A" + 7 : "Candle B" + 8 : "Candle C" + 4 : "Fast strobe" + 9 : "Slow strobe" + 12 : "Underwater mutation" + ] + // Light of the same style share the same pattern. + pattern(string) : " Custom Appearance" : "" // pattern defined by a sequence of letters + spawnflags(flags) = + [ + 1 : "Initially dark" : 0 + 2048 : "Not in Deathmatch" : 0 + ] +] + +// func_detail +// Similar in function to the func_detail in Source, though it is still subject to the bsp file format. +@SolidClass = func_detail : "Detail brushes" +[ + // You can leave the detail level to 1. For tiny objects, you might set to 2, so that they won't chop faces of other func_details. + zhlt_detaillevel(integer) : "Detail level" : 1 + // For large shapes such as terrain and walls, set this to no less than their detail level, so that they can chop the faces of adjacent world brushes. + zhlt_chopdown(integer) : "Lower its level to chop others" : 0 + // Usually you don't have to use this. + zhlt_chopup(integer) : "Raise its level to get chopped" : 0 + // For brushes in the same detail level and have overlapping faces (which are on the same plane), this priority determines which one will appear. + zhlt_coplanarpriority(integer) : "Priority when faces overlap" : 0 + // Setting this to 0 will reduce clipnode count, but will lose the benefit of func_detail's better content deciding method which is designed to prevent "Ambiguous leafnode contents" problem. + zhlt_clipnodedetaillevel(integer) : "Detail level of cliphulls" : 1 + // Very useful option which can reduce clipnode count. + zhlt_noclip(choices) : "Passable" : "" = + [ + "": "No" + 1: "Yes" + ] +] + +// info_hullshape +// It replaces the default cuboid shape of the player when generating collision hulls for certain brushes. +@SolidClass = info_hullshape : "Hull shape definition" +[ + targetname(target_source) : "Name" + defaulthulls(choices) : "Set as default shape" : "" = + [ + "": "No" + 2: "for hull 1" + 4: "for hull 2" + 8: "for hull 3" + ] + disabled(choices) : "Disable this entity" : "" = + [ + "": "No" + 1: "Yes" + ] +] diff --git a/tools/zhlt.wad b/tools/zhlt.wad new file mode 100644 index 0000000000000000000000000000000000000000..1b9b4536c35ea90e35dea786e5a4dca12a471a42 GIT binary patch literal 73508 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 literal 0 HcmV?d00001