diff --git a/neo/Doom3.sln b/neo/Doom3.sln
index 9754b8fd..9dff0276 100644
--- a/neo/Doom3.sln
+++ b/neo/Doom3.sln
@@ -1,6 +1,6 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 18
-VisualStudioVersion = 18.4.11626.88 stable
+VisualStudioVersion = 18.4.11626.88
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "libs", "libs", "{347D107C-D787-4408-A60D-86FA45997F9B}"
EndProject
@@ -20,95 +20,81 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "MayaImport", "MayaImport.vc
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "opengl", "opengl\opengl.vcxproj", "{58320C19-5139-47C5-AAFE-4CAD2C8ED14D}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "preydlls", "preydlls", "{F65E2305-0208-4A25-BEB0-038C92484211}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PreyGame", "prey\PreyGame.vcxproj", "{6CD8E46D-E28E-493F-BB06-537A637A68C2}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
- Debug with inlines and memory log|x64 = Debug with inlines and memory log|x64
- Debug with inlines|x64 = Debug with inlines|x64
Debug|x64 = Debug|x64
- Dedicated Debug with inlines|x64 = Dedicated Debug with inlines|x64
Dedicated Debug|x64 = Dedicated Debug|x64
Dedicated Release|x64 = Dedicated Release|x64
+ Prey Debug|x64 = Prey Debug|x64
Release|x64 = Release|x64
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {49BEC5C6-B964-417A-851E-808886B57430}.Debug with inlines and memory log|x64.ActiveCfg = Debug with inlines and memory log|x64
- {49BEC5C6-B964-417A-851E-808886B57430}.Debug with inlines and memory log|x64.Build.0 = Debug with inlines and memory log|x64
- {49BEC5C6-B964-417A-851E-808886B57430}.Debug with inlines|x64.ActiveCfg = Debug with inlines|x64
- {49BEC5C6-B964-417A-851E-808886B57430}.Debug with inlines|x64.Build.0 = Debug with inlines|x64
{49BEC5C6-B964-417A-851E-808886B57430}.Debug|x64.ActiveCfg = Debug|x64
{49BEC5C6-B964-417A-851E-808886B57430}.Debug|x64.Build.0 = Debug|x64
- {49BEC5C6-B964-417A-851E-808886B57430}.Dedicated Debug with inlines|x64.ActiveCfg = Dedicated Debug with inlines|x64
- {49BEC5C6-B964-417A-851E-808886B57430}.Dedicated Debug with inlines|x64.Build.0 = Dedicated Debug with inlines|x64
{49BEC5C6-B964-417A-851E-808886B57430}.Dedicated Debug|x64.ActiveCfg = Dedicated Debug|x64
{49BEC5C6-B964-417A-851E-808886B57430}.Dedicated Debug|x64.Build.0 = Dedicated Debug|x64
{49BEC5C6-B964-417A-851E-808886B57430}.Dedicated Release|x64.ActiveCfg = Dedicated Release|x64
{49BEC5C6-B964-417A-851E-808886B57430}.Dedicated Release|x64.Build.0 = Dedicated Release|x64
+ {49BEC5C6-B964-417A-851E-808886B57430}.Prey Debug|x64.ActiveCfg = Prey Debug|x64
{49BEC5C6-B964-417A-851E-808886B57430}.Release|x64.ActiveCfg = Release|x64
{49BEC5C6-B964-417A-851E-808886B57430}.Release|x64.Build.0 = Release|x64
- {49BEC5C6-B964-417A-851E-808886B57400}.Debug with inlines and memory log|x64.ActiveCfg = Debug with inlines and memory log|x64
- {49BEC5C6-B964-417A-851E-808886B57400}.Debug with inlines and memory log|x64.Build.0 = Debug with inlines and memory log|x64
- {49BEC5C6-B964-417A-851E-808886B57400}.Debug with inlines|x64.ActiveCfg = Debug with inlines|x64
- {49BEC5C6-B964-417A-851E-808886B57400}.Debug with inlines|x64.Build.0 = Debug with inlines|x64
{49BEC5C6-B964-417A-851E-808886B57400}.Debug|x64.ActiveCfg = Debug|x64
{49BEC5C6-B964-417A-851E-808886B57400}.Debug|x64.Build.0 = Debug|x64
- {49BEC5C6-B964-417A-851E-808886B57400}.Dedicated Debug with inlines|x64.ActiveCfg = Dedicated Debug with inlines|x64
- {49BEC5C6-B964-417A-851E-808886B57400}.Dedicated Debug with inlines|x64.Build.0 = Dedicated Debug with inlines|x64
{49BEC5C6-B964-417A-851E-808886B57400}.Dedicated Debug|x64.ActiveCfg = Dedicated Debug|x64
{49BEC5C6-B964-417A-851E-808886B57400}.Dedicated Debug|x64.Build.0 = Dedicated Debug|x64
{49BEC5C6-B964-417A-851E-808886B57400}.Dedicated Release|x64.ActiveCfg = Dedicated Release|x64
{49BEC5C6-B964-417A-851E-808886B57400}.Dedicated Release|x64.Build.0 = Dedicated Release|x64
+ {49BEC5C6-B964-417A-851E-808886B57400}.Prey Debug|x64.ActiveCfg = Prey Debug|x64
+ {49BEC5C6-B964-417A-851E-808886B57400}.Prey Debug|x64.Build.0 = Prey Debug|x64
{49BEC5C6-B964-417A-851E-808886B57400}.Release|x64.ActiveCfg = Release|x64
{49BEC5C6-B964-417A-851E-808886B57400}.Release|x64.Build.0 = Release|x64
- {F46F5D4E-C1D4-4ADE-9FAA-5F0CE3AA07F1}.Debug with inlines and memory log|x64.ActiveCfg = Debug|x64
- {F46F5D4E-C1D4-4ADE-9FAA-5F0CE3AA07F1}.Debug with inlines and memory log|x64.Build.0 = Debug|x64
- {F46F5D4E-C1D4-4ADE-9FAA-5F0CE3AA07F1}.Debug with inlines|x64.ActiveCfg = Debug|x64
- {F46F5D4E-C1D4-4ADE-9FAA-5F0CE3AA07F1}.Debug with inlines|x64.Build.0 = Debug|x64
{F46F5D4E-C1D4-4ADE-9FAA-5F0CE3AA07F1}.Debug|x64.ActiveCfg = Debug|x64
{F46F5D4E-C1D4-4ADE-9FAA-5F0CE3AA07F1}.Debug|x64.Build.0 = Debug|x64
- {F46F5D4E-C1D4-4ADE-9FAA-5F0CE3AA07F1}.Dedicated Debug with inlines|x64.ActiveCfg = Debug|x64
- {F46F5D4E-C1D4-4ADE-9FAA-5F0CE3AA07F1}.Dedicated Debug with inlines|x64.Build.0 = Debug|x64
{F46F5D4E-C1D4-4ADE-9FAA-5F0CE3AA07F1}.Dedicated Debug|x64.ActiveCfg = Debug|x64
{F46F5D4E-C1D4-4ADE-9FAA-5F0CE3AA07F1}.Dedicated Debug|x64.Build.0 = Debug|x64
{F46F5D4E-C1D4-4ADE-9FAA-5F0CE3AA07F1}.Dedicated Release|x64.ActiveCfg = Release|x64
{F46F5D4E-C1D4-4ADE-9FAA-5F0CE3AA07F1}.Dedicated Release|x64.Build.0 = Release|x64
+ {F46F5D4E-C1D4-4ADE-9FAA-5F0CE3AA07F1}.Prey Debug|x64.ActiveCfg = Prey Debug|x64
+ {F46F5D4E-C1D4-4ADE-9FAA-5F0CE3AA07F1}.Prey Debug|x64.Build.0 = Prey Debug|x64
{F46F5D4E-C1D4-4ADE-9FAA-5F0CE3AA07F1}.Release|x64.ActiveCfg = Release|x64
{F46F5D4E-C1D4-4ADE-9FAA-5F0CE3AA07F1}.Release|x64.Build.0 = Release|x64
- {49BEC5C6-B964-417A-851E-808886B57420}.Debug with inlines and memory log|x64.ActiveCfg = Debug with inlines and memory log|x64
- {49BEC5C6-B964-417A-851E-808886B57420}.Debug with inlines and memory log|x64.Build.0 = Debug with inlines and memory log|x64
- {49BEC5C6-B964-417A-851E-808886B57420}.Debug with inlines|x64.ActiveCfg = Debug with inlines|x64
- {49BEC5C6-B964-417A-851E-808886B57420}.Debug with inlines|x64.Build.0 = Debug with inlines|x64
{49BEC5C6-B964-417A-851E-808886B57420}.Debug|x64.ActiveCfg = Debug|x64
{49BEC5C6-B964-417A-851E-808886B57420}.Debug|x64.Build.0 = Debug|x64
- {49BEC5C6-B964-417A-851E-808886B57420}.Dedicated Debug with inlines|x64.ActiveCfg = Dedicated Debug with inlines|x64
- {49BEC5C6-B964-417A-851E-808886B57420}.Dedicated Debug with inlines|x64.Build.0 = Dedicated Debug with inlines|x64
{49BEC5C6-B964-417A-851E-808886B57420}.Dedicated Debug|x64.ActiveCfg = Dedicated Debug|x64
{49BEC5C6-B964-417A-851E-808886B57420}.Dedicated Debug|x64.Build.0 = Dedicated Debug|x64
{49BEC5C6-B964-417A-851E-808886B57420}.Dedicated Release|x64.ActiveCfg = Dedicated Release|x64
{49BEC5C6-B964-417A-851E-808886B57420}.Dedicated Release|x64.Build.0 = Dedicated Release|x64
+ {49BEC5C6-B964-417A-851E-808886B57420}.Prey Debug|x64.ActiveCfg = Prey Debug|x64
+ {49BEC5C6-B964-417A-851E-808886B57420}.Prey Debug|x64.Build.0 = Prey Debug|x64
{49BEC5C6-B964-417A-851E-808886B57420}.Release|x64.ActiveCfg = Release|x64
{49BEC5C6-B964-417A-851E-808886B57420}.Release|x64.Build.0 = Release|x64
- {49BEC5C6-B964-417A-851E-808886B574F1}.Debug with inlines and memory log|x64.ActiveCfg = Debug with inlines and memory log|x64
- {49BEC5C6-B964-417A-851E-808886B574F1}.Debug with inlines|x64.ActiveCfg = Debug with inlines|x64
{49BEC5C6-B964-417A-851E-808886B574F1}.Debug|x64.ActiveCfg = Debug|x64
{49BEC5C6-B964-417A-851E-808886B574F1}.Debug|x64.Build.0 = Debug|x64
- {49BEC5C6-B964-417A-851E-808886B574F1}.Dedicated Debug with inlines|x64.ActiveCfg = Dedicated Debug with inlines|x64
{49BEC5C6-B964-417A-851E-808886B574F1}.Dedicated Debug|x64.ActiveCfg = Dedicated Debug|x64
{49BEC5C6-B964-417A-851E-808886B574F1}.Dedicated Release|x64.ActiveCfg = Dedicated Release|x64
+ {49BEC5C6-B964-417A-851E-808886B574F1}.Prey Debug|x64.ActiveCfg = Prey Debug|x64
+ {49BEC5C6-B964-417A-851E-808886B574F1}.Prey Debug|x64.Build.0 = Prey Debug|x64
{49BEC5C6-B964-417A-851E-808886B574F1}.Release|x64.ActiveCfg = Release|x64
- {58320C19-5139-47C5-AAFE-4CAD2C8ED14D}.Debug with inlines and memory log|x64.ActiveCfg = Debug|x64
- {58320C19-5139-47C5-AAFE-4CAD2C8ED14D}.Debug with inlines and memory log|x64.Build.0 = Debug|x64
- {58320C19-5139-47C5-AAFE-4CAD2C8ED14D}.Debug with inlines|x64.ActiveCfg = Debug|x64
- {58320C19-5139-47C5-AAFE-4CAD2C8ED14D}.Debug with inlines|x64.Build.0 = Debug|x64
{58320C19-5139-47C5-AAFE-4CAD2C8ED14D}.Debug|x64.ActiveCfg = Debug|x64
{58320C19-5139-47C5-AAFE-4CAD2C8ED14D}.Debug|x64.Build.0 = Debug|x64
- {58320C19-5139-47C5-AAFE-4CAD2C8ED14D}.Dedicated Debug with inlines|x64.ActiveCfg = Debug|x64
- {58320C19-5139-47C5-AAFE-4CAD2C8ED14D}.Dedicated Debug with inlines|x64.Build.0 = Debug|x64
{58320C19-5139-47C5-AAFE-4CAD2C8ED14D}.Dedicated Debug|x64.ActiveCfg = Debug|x64
{58320C19-5139-47C5-AAFE-4CAD2C8ED14D}.Dedicated Debug|x64.Build.0 = Debug|x64
{58320C19-5139-47C5-AAFE-4CAD2C8ED14D}.Dedicated Release|x64.ActiveCfg = Release|x64
{58320C19-5139-47C5-AAFE-4CAD2C8ED14D}.Dedicated Release|x64.Build.0 = Release|x64
+ {58320C19-5139-47C5-AAFE-4CAD2C8ED14D}.Prey Debug|x64.ActiveCfg = Prey Debug|x64
+ {58320C19-5139-47C5-AAFE-4CAD2C8ED14D}.Prey Debug|x64.Build.0 = Prey Debug|x64
{58320C19-5139-47C5-AAFE-4CAD2C8ED14D}.Release|x64.ActiveCfg = Release|x64
{58320C19-5139-47C5-AAFE-4CAD2C8ED14D}.Release|x64.Build.0 = Release|x64
+ {6CD8E46D-E28E-493F-BB06-537A637A68C2}.Debug|x64.ActiveCfg = Debug|x64
+ {6CD8E46D-E28E-493F-BB06-537A637A68C2}.Dedicated Debug|x64.ActiveCfg = Dedicated Debug with Inlines|x64
+ {6CD8E46D-E28E-493F-BB06-537A637A68C2}.Dedicated Release|x64.ActiveCfg = Dedicated Release|x64
+ {6CD8E46D-E28E-493F-BB06-537A637A68C2}.Prey Debug|x64.ActiveCfg = Prey Debug|x64
+ {6CD8E46D-E28E-493F-BB06-537A637A68C2}.Prey Debug|x64.Build.0 = Prey Debug|x64
+ {6CD8E46D-E28E-493F-BB06-537A637A68C2}.Release|x64.ActiveCfg = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -120,6 +106,7 @@ Global
{49BEC5C6-B964-417A-851E-808886B57420} = {003B01AB-152D-45C8-BF45-E5A035042D7F}
{49BEC5C6-B964-417A-851E-808886B574F1} = {1E2B3940-65F8-4D8F-9EEE-85E94EBBC6DF}
{58320C19-5139-47C5-AAFE-4CAD2C8ED14D} = {003B01AB-152D-45C8-BF45-E5A035042D7F}
+ {6CD8E46D-E28E-493F-BB06-537A637A68C2} = {F65E2305-0208-4A25-BEB0-038C92484211}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {822AE68E-37FD-4947-AC78-29CFA29E613F}
diff --git a/neo/MayaImport.vcxproj b/neo/MayaImport.vcxproj
index bd1a02c4..87b5cc6c 100644
--- a/neo/MayaImport.vcxproj
+++ b/neo/MayaImport.vcxproj
@@ -25,6 +25,10 @@
Dedicated Release
x64
+
+ Prey Debug
+ x64
+
Release
x64
@@ -86,6 +90,12 @@
MultiByte
v145
+
+ DynamicLibrary
+ false
+ MultiByte
+ v145
+
@@ -138,6 +148,12 @@
+
+
+
+
+
+
<_ProjectFileVersion>10.0.40219.1
@@ -148,8 +164,11 @@
AllRules.ruleset
+ AllRules.ruleset
+
+
AllRules.ruleset
@@ -171,6 +190,14 @@
../$(TargetName)$(TargetExt)
+
+
+ MayaImport/fbxsdk/include;%(AdditionalIncludeDirectories)
+
+
+ ../$(TargetName)$(TargetExt)
+
+
MayaImport/fbxsdk/include;%(AdditionalIncludeDirectories)
@@ -229,6 +256,7 @@
Create
Create
Create
+ Create
Create
Create
Create
diff --git a/neo/cm/PreyCM/CollisionModel.h b/neo/cm/PreyCM/CollisionModel.h
new file mode 100644
index 00000000..870d67ba
--- /dev/null
+++ b/neo/cm/PreyCM/CollisionModel.h
@@ -0,0 +1,146 @@
+// Copyright (C) 2004 Id Software, Inc.
+//
+
+#ifndef __COLLISIONMODELMANAGER_H__
+#define __COLLISIONMODELMANAGER_H__
+
+/*
+===============================================================================
+
+ Trace model vs. polygonal model collision detection.
+
+ Short translations are the least expensive. Retrieving contact points is
+ about as cheap as a short translation. Position tests are more expensive
+ and rotations are most expensive.
+
+ There is no position test at the start of a translation or rotation. In other
+ words if a translation with start != end or a rotation with angle != 0 starts
+ in solid, this goes unnoticed and the collision result is undefined.
+
+ A translation with start == end or a rotation with angle == 0 performs
+ a position test and fills in the trace_t structure accordingly.
+
+===============================================================================
+*/
+
+// contact type
+typedef enum {
+ CONTACT_NONE, // no contact
+ CONTACT_EDGE, // trace model edge hits model edge
+ CONTACT_MODELVERTEX, // model vertex hits trace model polygon
+ CONTACT_TRMVERTEX // trace model vertex hits model polygon
+} contactType_t;
+
+// contact info
+typedef struct {
+ contactType_t type; // contact type
+ idVec3 point; // point of contact
+ idVec3 normal; // contact plane normal
+ float dist; // contact plane distance
+ int contents; // contents at other side of surface
+ const idMaterial * material; // surface material
+ int modelFeature; // contact feature on model
+ int trmFeature; // contact feature on trace model
+ int entityNum; // entity the contact surface is a part of
+ int id; // id of clip model the contact surface is part of
+} contactInfo_t;
+
+// trace result
+typedef struct trace_s {
+ float fraction; // fraction of movement completed, 1.0 = didn't hit anything
+ idVec3 endpos; // final position of trace model
+ idMat3 endAxis; // final axis of trace model
+ contactInfo_t c; // contact information, only valid if fraction < 1.0
+} trace_t;
+
+typedef int cmHandle_t;
+
+#define CM_CLIP_EPSILON 0.25f // always stay this distance away from any model
+#define CM_BOX_EPSILON 1.0f // should always be larger than clip epsilon
+#define CM_MAX_TRACE_DIST 4096.0f // maximum distance a trace model may be traced, point traces are unlimited
+
+//HUMANHEAD rww
+#if _HH_INLINED_PROC_CLIPMODELS
+#define PROC_CLIPMODEL_INDEX_START 1
+#define PROC_CLIPMODEL_STRING_PRFX "inlined_proc_clip_"
+#endif
+//HUMANHEAD END
+
+class idCollisionModelManager {
+public:
+ virtual ~idCollisionModelManager( void ) {}
+
+ // Loads collision models from a map file.
+ virtual void LoadMap( const idMapFile *mapFile ) = 0;
+
+// HUMANHEAD pdm: Support for level appending
+ virtual const char * ContentsName(const int contents) const = 0;
+ const char * StringFromContents( const int contents ) const;
+#if DEATHWALK_AUTOLOAD
+ virtual void AppendMap( const idMapFile *mapFile ) = 0;
+ virtual bool WillUseAlreadyLoadedCollisionMap( const idMapFile *mapFile) = 0;
+#endif
+// HUMANHEAD END
+
+ // Frees all the collision models.
+ virtual void FreeMap( void ) = 0;
+
+ // Gets the clip handle for a model.
+ virtual cmHandle_t LoadModel( const char *modelName, const bool precache ) = 0;
+ // Sets up a trace model for collision with other trace models.
+ virtual cmHandle_t SetupTrmModel( const idTraceModel &trm, const idMaterial *material ) = 0;
+ // Creates a trace model from a collision model, returns true if succesfull.
+ virtual bool TrmFromModel( const char *modelName, idTraceModel &trm ) = 0;
+
+ // Gets the name of a model.
+ virtual const char * GetModelName( cmHandle_t model ) const = 0;
+ // Gets the bounds of a model.
+ virtual bool GetModelBounds( cmHandle_t model, idBounds &bounds ) const = 0;
+ // Gets all contents flags of brushes and polygons of a model ored together.
+ virtual bool GetModelContents( cmHandle_t model, int &contents ) const = 0;
+ // Gets a vertex of a model.
+ virtual bool GetModelVertex( cmHandle_t model, int vertexNum, idVec3 &vertex ) const = 0;
+ // Gets an edge of a model.
+ virtual bool GetModelEdge( cmHandle_t model, int edgeNum, idVec3 &start, idVec3 &end ) const = 0;
+ // Gets a polygon of a model.
+ virtual bool GetModelPolygon( cmHandle_t model, int polygonNum, idFixedWinding &winding ) const = 0;
+
+ // Translates a trace model and reports the first collision if any.
+ virtual void Translation( trace_t *results, const idVec3 &start, const idVec3 &end,
+ const idTraceModel *trm, const idMat3 &trmAxis, int contentMask,
+ cmHandle_t model, const idVec3 &modelOrigin, const idMat3 &modelAxis ) = 0;
+ // Rotates a trace model and reports the first collision if any.
+ virtual void Rotation( trace_t *results, const idVec3 &start, const idRotation &rotation,
+ const idTraceModel *trm, const idMat3 &trmAxis, int contentMask,
+ cmHandle_t model, const idVec3 &modelOrigin, const idMat3 &modelAxis ) = 0;
+ // Returns the contents touched by the trace model or 0 if the trace model is in free space.
+ virtual int Contents( const idVec3 &start,
+ const idTraceModel *trm, const idMat3 &trmAxis, int contentMask,
+ cmHandle_t model, const idVec3 &modelOrigin, const idMat3 &modelAxis ) = 0;
+ // Stores all contact points of the trace model with the model, returns the number of contacts.
+ virtual int Contacts( contactInfo_t *contacts, const int maxContacts, const idVec3 &start, const idVec6 &dir, const float depth,
+ const idTraceModel *trm, const idMat3 &trmAxis, int contentMask,
+ cmHandle_t model, const idVec3 &modelOrigin, const idMat3 &modelAxis ) = 0;
+
+ // Tests collision detection.
+ virtual void DebugOutput( const idVec3 &origin ) = 0;
+ // Draws a model.
+ virtual void DrawModel( cmHandle_t model, const idVec3 &modelOrigin, const idMat3 &modelAxis,
+ const idVec3 &viewOrigin, const float radius ) = 0;
+ // Prints model information, use -1 handle for accumulated model info.
+ virtual void ModelInfo( cmHandle_t model ) = 0;
+ // Lists all loaded models.
+ virtual void ListModels( void ) = 0;
+ // Writes a collision model file for the given map entity.
+ virtual bool WriteCollisionModelForMapEntity( const idMapEntity *mapEnt, const char *filename, const bool testTraceModel = true ) = 0;
+
+ //HUMANHEAD rww
+#if _HH_INLINED_PROC_CLIPMODELS
+ virtual int GetNumInlinedProcClipModels(void) = 0;
+#endif
+ //HUMANHEAD END
+};
+
+extern idCollisionModelManager * collisionModelManager;
+
+#endif /* !__COLLISIONMODELMANAGER_H__ */
diff --git a/neo/cm/PreyCM/CollisionModel_contacts.cpp b/neo/cm/PreyCM/CollisionModel_contacts.cpp
new file mode 100644
index 00000000..047a879d
--- /dev/null
+++ b/neo/cm/PreyCM/CollisionModel_contacts.cpp
@@ -0,0 +1,48 @@
+
+/*
+===============================================================================
+
+ Trace model vs. polygonal model collision detection.
+
+===============================================================================
+*/
+
+#include "precompiled.h"
+#pragma hdrstop
+
+#include "CollisionModel_local.h"
+
+/*
+===============================================================================
+
+Retrieving contacts
+
+===============================================================================
+*/
+
+/*
+==================
+idCollisionModelManagerLocal::Contacts
+==================
+*/
+int idCollisionModelManagerLocal::Contacts( contactInfo_t *contacts, const int maxContacts, const idVec3 &start, const idVec6 &dir, const float depth,
+ const idTraceModel *trm, const idMat3 &trmAxis, int contentMask,
+ cmHandle_t model, const idVec3 &origin, const idMat3 &modelAxis ) {
+ trace_t results;
+ idVec3 end;
+
+ // same as Translation but instead of storing the first collision we store all collisions as contacts
+ idCollisionModelManagerLocal::getContacts = true;
+ idCollisionModelManagerLocal::contacts = contacts;
+ idCollisionModelManagerLocal::maxContacts = maxContacts;
+ idCollisionModelManagerLocal::numContacts = 0;
+ end = start + dir.SubVec3(0) * depth;
+ idCollisionModelManagerLocal::Translation( &results, start, end, trm, trmAxis, contentMask, model, origin, modelAxis );
+ if ( dir.SubVec3(1).LengthSqr() != 0.0f ) {
+ // FIXME: rotational contacts
+ }
+ idCollisionModelManagerLocal::getContacts = false;
+ idCollisionModelManagerLocal::maxContacts = 0;
+
+ return idCollisionModelManagerLocal::numContacts;
+}
diff --git a/neo/cm/PreyCM/CollisionModel_contents.cpp b/neo/cm/PreyCM/CollisionModel_contents.cpp
new file mode 100644
index 00000000..bffec167
--- /dev/null
+++ b/neo/cm/PreyCM/CollisionModel_contents.cpp
@@ -0,0 +1,614 @@
+
+/*
+===============================================================================
+
+ Trace model vs. polygonal model collision detection.
+
+===============================================================================
+*/
+
+#include "precompiled.h"
+#pragma hdrstop
+
+#include "CollisionModel_local.h"
+
+/*
+===============================================================================
+
+Contents test
+
+===============================================================================
+*/
+
+/*
+================
+idCollisionModelManagerLocal::TestTrmVertsInBrush
+
+ returns true if any of the trm vertices is inside the brush
+================
+*/
+bool idCollisionModelManagerLocal::TestTrmVertsInBrush( cm_traceWork_t *tw, cm_brush_t *b ) {
+ int i, j, numVerts, bestPlane;
+ float d, bestd;
+ idVec3 *p;
+
+ if ( b->checkcount == idCollisionModelManagerLocal::checkCount ) {
+ return false;
+ }
+ b->checkcount = idCollisionModelManagerLocal::checkCount;
+
+ if ( !(b->contents & tw->contents) ) {
+ return false;
+ }
+
+ // if the brush bounds don't intersect the trace bounds
+ if ( !b->bounds.IntersectsBounds( tw->bounds ) ) {
+ return false;
+ }
+
+ if ( tw->pointTrace ) {
+ numVerts = 1;
+ }
+ else {
+ numVerts = tw->numVerts;
+ }
+
+ for ( j = 0; j < numVerts; j++ ) {
+ p = &tw->vertices[j].p;
+
+ // see if the point is inside the brush
+ bestPlane = 0;
+ bestd = -idMath::INFINITY;
+ for ( i = 0; i < b->numPlanes; i++ ) {
+ d = b->planes[i].Distance( *p );
+ if ( d >= 0.0f ) {
+ break;
+ }
+ if ( d > bestd ) {
+ bestd = d;
+ bestPlane = i;
+ }
+ }
+ if ( i >= b->numPlanes ) {
+ tw->trace.fraction = 0.0f;
+ tw->trace.c.type = CONTACT_TRMVERTEX;
+ tw->trace.c.normal = b->planes[bestPlane].Normal();
+ tw->trace.c.dist = b->planes[bestPlane].Dist();
+ tw->trace.c.contents = b->contents;
+ tw->trace.c.material = b->material;
+ tw->trace.c.point = *p;
+ tw->trace.c.modelFeature = 0;
+ tw->trace.c.trmFeature = j;
+ return true;
+ }
+ }
+ return false;
+}
+
+/*
+================
+CM_SetTrmEdgeSidedness
+================
+*/
+#define CM_SetTrmEdgeSidedness( edge, bpl, epl, bitNum ) { \
+ if ( !(edge->sideSet & (1<side = (edge->side & ~(1<sideSet |= (1 << bitNum); \
+ } \
+}
+
+/*
+================
+CM_SetTrmPolygonSidedness
+================
+*/
+#define CM_SetTrmPolygonSidedness( v, plane, bitNum ) { \
+ if ( !((v)->sideSet & (1<p ); \
+ /* cannot use float sign bit because it is undetermined when fl == 0.0f */ \
+ if ( fl < 0.0f ) { \
+ (v)->side |= (1 << bitNum); \
+ } \
+ else { \
+ (v)->side &= ~(1 << bitNum); \
+ } \
+ (v)->sideSet |= (1 << bitNum); \
+ } \
+}
+
+/*
+================
+idCollisionModelManagerLocal::TestTrmInPolygon
+
+ returns true if the trm intersects the polygon
+================
+*/
+bool idCollisionModelManagerLocal::TestTrmInPolygon( cm_traceWork_t *tw, cm_polygon_t *p ) {
+ int i, j, k, edgeNum, flip, trmEdgeNum, bitNum, bestPlane;
+ int sides[MAX_TRACEMODEL_VERTS];
+ float d, bestd;
+ cm_trmEdge_t *trmEdge;
+ cm_edge_t *edge;
+ cm_vertex_t *v, *v1, *v2;
+
+ // if already checked this polygon
+ if ( p->checkcount == idCollisionModelManagerLocal::checkCount ) {
+ return false;
+ }
+ p->checkcount = idCollisionModelManagerLocal::checkCount;
+
+ // if this polygon does not have the right contents behind it
+ if ( !(p->contents & tw->contents) ) {
+ return false;
+ }
+
+ // if the polygon bounds don't intersect the trace bounds
+ if ( !p->bounds.IntersectsBounds( tw->bounds ) ) {
+ return false;
+ }
+
+ // bounds should cross polygon plane
+ switch( tw->bounds.PlaneSide( p->plane ) ) {
+ case PLANESIDE_CROSS:
+ break;
+ case PLANESIDE_FRONT:
+ if ( tw->model->isConvex ) {
+ tw->quickExit = true;
+ return true;
+ }
+ default:
+ return false;
+ }
+
+ // if the trace model is convex
+ if ( tw->isConvex ) {
+ // test if any polygon vertices are inside the trm
+ for ( i = 0; i < p->numEdges; i++ ) {
+ edgeNum = p->edges[i];
+ edge = tw->model->edges + abs(edgeNum);
+ // if this edge is already tested
+ if ( edge->checkcount == idCollisionModelManagerLocal::checkCount ) {
+ continue;
+ }
+
+ for ( j = 0; j < 2; j++ ) {
+ v = &tw->model->vertices[edge->vertexNum[j]];
+ // if this vertex is already tested
+ if ( v->checkcount == idCollisionModelManagerLocal::checkCount ) {
+ continue;
+ }
+
+ bestPlane = 0;
+ bestd = -idMath::INFINITY;
+ for ( k = 0; k < tw->numPolys; k++ ) {
+ d = tw->polys[k].plane.Distance( v->p );
+ if ( d >= 0.0f ) {
+ break;
+ }
+ if ( d > bestd ) {
+ bestd = d;
+ bestPlane = k;
+ }
+ }
+ if ( k >= tw->numPolys ) {
+ tw->trace.fraction = 0.0f;
+ tw->trace.c.type = CONTACT_MODELVERTEX;
+ tw->trace.c.normal = -tw->polys[bestPlane].plane.Normal();
+ tw->trace.c.dist = -tw->polys[bestPlane].plane.Dist();
+ tw->trace.c.contents = p->contents;
+ tw->trace.c.material = p->material;
+ tw->trace.c.point = v->p;
+ tw->trace.c.modelFeature = edge->vertexNum[j];
+ tw->trace.c.trmFeature = 0;
+ return true;
+ }
+ }
+ }
+ }
+
+ for ( i = 0; i < p->numEdges; i++ ) {
+ edgeNum = p->edges[i];
+ edge = tw->model->edges + abs(edgeNum);
+ // reset sidedness cache if this is the first time we encounter this edge
+ if ( edge->checkcount != idCollisionModelManagerLocal::checkCount ) {
+ edge->sideSet = 0;
+ }
+ // pluecker coordinate for edge
+ tw->polygonEdgePlueckerCache[i].FromLine( tw->model->vertices[edge->vertexNum[0]].p,
+ tw->model->vertices[edge->vertexNum[1]].p );
+ v = &tw->model->vertices[edge->vertexNum[INTSIGNBITSET(edgeNum)]];
+ // reset sidedness cache if this is the first time we encounter this vertex
+ if ( v->checkcount != idCollisionModelManagerLocal::checkCount ) {
+ v->sideSet = 0;
+ }
+ v->checkcount = idCollisionModelManagerLocal::checkCount;
+ }
+
+ // get side of polygon for each trm vertex
+ for ( i = 0; i < tw->numVerts; i++ ) {
+ d = p->plane.Distance( tw->vertices[i].p );
+ sides[i] = d < 0.0f ? -1 : 1;
+ }
+
+ // test if any trm edges go through the polygon
+ for ( i = 1; i <= tw->numEdges; i++ ) {
+ // if the trm edge does not cross the polygon plane
+ if ( sides[tw->edges[i].vertexNum[0]] == sides[tw->edges[i].vertexNum[1]] ) {
+ continue;
+ }
+ // check from which side to which side the trm edge goes
+ flip = INTSIGNBITSET( sides[tw->edges[i].vertexNum[0]] );
+ // test if trm edge goes through the polygon between the polygon edges
+ for ( j = 0; j < p->numEdges; j++ ) {
+ edgeNum = p->edges[j];
+ edge = tw->model->edges + abs(edgeNum);
+#if 1
+ CM_SetTrmEdgeSidedness( edge, tw->edges[i].pl, tw->polygonEdgePlueckerCache[j], i );
+ if ( INTSIGNBITSET(edgeNum) ^ ((edge->side >> i) & 1) ^ flip ) {
+ break;
+ }
+#else
+ d = tw->edges[i].pl.PermutedInnerProduct( tw->polygonEdgePlueckerCache[j] );
+ if ( flip ) {
+ d = -d;
+ }
+ if ( edgeNum > 0 ) {
+ if ( d <= 0.0f ) {
+ break;
+ }
+ }
+ else {
+ if ( d >= 0.0f ) {
+ break;
+ }
+ }
+#endif
+ }
+ if ( j >= p->numEdges ) {
+ tw->trace.fraction = 0.0f;
+ tw->trace.c.type = CONTACT_EDGE;
+ tw->trace.c.normal = p->plane.Normal();
+ tw->trace.c.dist = p->plane.Dist();
+ tw->trace.c.contents = p->contents;
+ tw->trace.c.material = p->material;
+ tw->trace.c.point = tw->vertices[tw->edges[i].vertexNum[ !flip ]].p;
+ tw->trace.c.modelFeature = *reinterpret_cast(&p);
+ tw->trace.c.trmFeature = i;
+ return true;
+ }
+ }
+
+ // test if any polygon edges go through the trm polygons
+ for ( i = 0; i < p->numEdges; i++ ) {
+ edgeNum = p->edges[i];
+ edge = tw->model->edges + abs(edgeNum);
+ if ( edge->checkcount == idCollisionModelManagerLocal::checkCount ) {
+ continue;
+ }
+ edge->checkcount = idCollisionModelManagerLocal::checkCount;
+
+ for ( j = 0; j < tw->numPolys; j++ ) {
+#if 1
+ v1 = tw->model->vertices + edge->vertexNum[0];
+ CM_SetTrmPolygonSidedness( v1, tw->polys[j].plane, j );
+ v2 = tw->model->vertices + edge->vertexNum[1];
+ CM_SetTrmPolygonSidedness( v2, tw->polys[j].plane, j );
+ // if the polygon edge does not cross the trm polygon plane
+ if ( !(((v1->side ^ v2->side) >> j) & 1) ) {
+ continue;
+ }
+ flip = (v1->side >> j) & 1;
+#else
+ float d1, d2;
+
+ v1 = tw->model->vertices + edge->vertexNum[0];
+ d1 = tw->polys[j].plane.Distance( v1->p );
+ v2 = tw->model->vertices + edge->vertexNum[1];
+ d2 = tw->polys[j].plane.Distance( v2->p );
+ // if the polygon edge does not cross the trm polygon plane
+ if ( (d1 >= 0.0f && d2 >= 0.0f) || (d1 <= 0.0f && d2 <= 0.0f) ) {
+ continue;
+ }
+ flip = false;
+ if ( d1 < 0.0f ) {
+ flip = true;
+ }
+#endif
+ // test if polygon edge goes through the trm polygon between the trm polygon edges
+ for ( k = 0; k < tw->polys[j].numEdges; k++ ) {
+ trmEdgeNum = tw->polys[j].edges[k];
+ trmEdge = tw->edges + abs(trmEdgeNum);
+#if 1
+ bitNum = abs(trmEdgeNum);
+ CM_SetTrmEdgeSidedness( edge, trmEdge->pl, tw->polygonEdgePlueckerCache[i], bitNum );
+ if ( INTSIGNBITSET(trmEdgeNum) ^ ((edge->side >> bitNum) & 1) ^ flip ) {
+ break;
+ }
+#else
+ d = trmEdge->pl.PermutedInnerProduct( tw->polygonEdgePlueckerCache[i] );
+ if ( flip ) {
+ d = -d;
+ }
+ if ( trmEdgeNum > 0 ) {
+ if ( d <= 0.0f ) {
+ break;
+ }
+ }
+ else {
+ if ( d >= 0.0f ) {
+ break;
+ }
+ }
+#endif
+ }
+ if ( k >= tw->polys[j].numEdges ) {
+ tw->trace.fraction = 0.0f;
+ tw->trace.c.type = CONTACT_EDGE;
+ tw->trace.c.normal = -tw->polys[j].plane.Normal();
+ tw->trace.c.dist = -tw->polys[j].plane.Dist();
+ tw->trace.c.contents = p->contents;
+ tw->trace.c.material = p->material;
+ tw->trace.c.point = tw->model->vertices[edge->vertexNum[ !flip ]].p;
+ tw->trace.c.modelFeature = edgeNum;
+ tw->trace.c.trmFeature = j;
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+/*
+================
+idCollisionModelManagerLocal::PointNode
+================
+*/
+cm_node_t *idCollisionModelManagerLocal::PointNode( const idVec3 &p, cm_model_t *model ) {
+ cm_node_t *node;
+
+ node = model->node;
+ while ( node->planeType != -1 ) {
+ if (p[node->planeType] > node->planeDist) {
+ node = node->children[0];
+ }
+ else {
+ node = node->children[1];
+ }
+
+ assert( node != NULL );
+ }
+ return node;
+}
+
+/*
+================
+idCollisionModelManagerLocal::PointContents
+================
+*/
+int idCollisionModelManagerLocal::PointContents( const idVec3 p, cmHandle_t model ) {
+ int i;
+ float d;
+ cm_node_t *node;
+ cm_brushRef_t *bref;
+ cm_brush_t *b;
+ idPlane *plane;
+
+ node = idCollisionModelManagerLocal::PointNode( p, idCollisionModelManagerLocal::models[model] );
+ for ( bref = node->brushes; bref; bref = bref->next ) {
+ b = bref->b;
+ // test if the point is within the brush bounds
+ for ( i = 0; i < 3; i++ ) {
+ if ( p[i] < b->bounds[0][i] ) {
+ break;
+ }
+ if ( p[i] > b->bounds[1][i] ) {
+ break;
+ }
+ }
+ if ( i < 3 ) {
+ continue;
+ }
+ // test if the point is inside the brush
+ plane = b->planes;
+ for ( i = 0; i < b->numPlanes; i++, plane++ ) {
+ d = plane->Distance( p );
+ if ( d >= 0 ) {
+ break;
+ }
+ }
+ if ( i >= b->numPlanes ) {
+ return b->contents;
+ }
+ }
+ return 0;
+}
+
+/*
+==================
+idCollisionModelManagerLocal::TransformedPointContents
+==================
+*/
+int idCollisionModelManagerLocal::TransformedPointContents( const idVec3 &p, cmHandle_t model, const idVec3 &origin, const idMat3 &modelAxis ) {
+ idVec3 p_l;
+
+ // subtract origin offset
+ p_l = p - origin;
+ if ( modelAxis.IsRotated() ) {
+ p_l *= modelAxis;
+ }
+ return idCollisionModelManagerLocal::PointContents( p_l, model );
+}
+
+
+/*
+==================
+idCollisionModelManagerLocal::ContentsTrm
+==================
+*/
+int idCollisionModelManagerLocal::ContentsTrm( trace_t *results, const idVec3 &start,
+ const idTraceModel *trm, const idMat3 &trmAxis, int contentMask,
+ cmHandle_t model, const idVec3 &modelOrigin, const idMat3 &modelAxis ) {
+ int i;
+ bool model_rotated, trm_rotated;
+ idMat3 invModelAxis, tmpAxis;
+ idVec3 dir;
+ ALIGN16( cm_traceWork_t tw; )
+
+ // fast point case
+ if ( !trm || ( trm->bounds[1][0] - trm->bounds[0][0] <= 0.0f &&
+ trm->bounds[1][1] - trm->bounds[0][1] <= 0.0f &&
+ trm->bounds[1][2] - trm->bounds[0][2] <= 0.0f ) ) {
+
+ results->c.contents = idCollisionModelManagerLocal::TransformedPointContents( start, model, modelOrigin, modelAxis );
+ results->fraction = ( results->c.contents == 0 );
+ results->endpos = start;
+ results->endAxis = trmAxis;
+
+ return results->c.contents;
+ }
+
+ idCollisionModelManagerLocal::checkCount++;
+
+ tw.trace.fraction = 1.0f;
+ tw.trace.c.contents = 0;
+ tw.trace.c.type = CONTACT_NONE;
+ tw.contents = contentMask;
+ tw.isConvex = true;
+ tw.rotation = false;
+ tw.positionTest = true;
+ tw.pointTrace = false;
+ tw.quickExit = false;
+ tw.numContacts = 0;
+ tw.model = idCollisionModelManagerLocal::models[model];
+ tw.start = start - modelOrigin;
+ tw.end = tw.start;
+
+ model_rotated = modelAxis.IsRotated();
+ if ( model_rotated ) {
+ invModelAxis = modelAxis.Transpose();
+ }
+
+ // setup trm structure
+ idCollisionModelManagerLocal::SetupTrm( &tw, trm );
+
+ trm_rotated = trmAxis.IsRotated();
+
+ // calculate vertex positions
+ if ( trm_rotated ) {
+ for ( i = 0; i < tw.numVerts; i++ ) {
+ // rotate trm around the start position
+ tw.vertices[i].p *= trmAxis;
+ }
+ }
+ for ( i = 0; i < tw.numVerts; i++ ) {
+ // set trm at start position
+ tw.vertices[i].p += tw.start;
+ }
+ if ( model_rotated ) {
+ for ( i = 0; i < tw.numVerts; i++ ) {
+ // rotate trm around model instead of rotating the model
+ tw.vertices[i].p *= invModelAxis;
+ }
+ }
+
+ // add offset to start point
+ if ( trm_rotated ) {
+ dir = trm->offset * trmAxis;
+ tw.start += dir;
+ tw.end += dir;
+ } else {
+ tw.start += trm->offset;
+ tw.end += trm->offset;
+ }
+ if ( model_rotated ) {
+ // rotate trace instead of model
+ tw.start *= invModelAxis;
+ tw.end *= invModelAxis;
+ }
+
+
+ // setup trm vertices
+ tw.size.Clear();
+ for ( i = 0; i < tw.numVerts; i++ ) {
+ // get axial trm size after rotations
+ tw.size.AddPoint( tw.vertices[i].p - tw.start );
+ }
+
+ // setup trm edges
+ for ( i = 1; i <= tw.numEdges; i++ ) {
+ // edge start, end and pluecker coordinate
+ tw.edges[i].start = tw.vertices[tw.edges[i].vertexNum[0]].p;
+ tw.edges[i].end = tw.vertices[tw.edges[i].vertexNum[1]].p;
+ tw.edges[i].pl.FromLine( tw.edges[i].start, tw.edges[i].end );
+ }
+
+ // setup trm polygons
+ if ( trm_rotated & model_rotated ) {
+ tmpAxis = trmAxis * invModelAxis;
+ for ( i = 0; i < tw.numPolys; i++ ) {
+ tw.polys[i].plane *= tmpAxis;
+ }
+ } else if ( trm_rotated ) {
+ for ( i = 0; i < tw.numPolys; i++ ) {
+ tw.polys[i].plane *= trmAxis;
+ }
+ } else if ( model_rotated ) {
+ for ( i = 0; i < tw.numPolys; i++ ) {
+ tw.polys[i].plane *= invModelAxis;
+ }
+ }
+ for ( i = 0; i < tw.numPolys; i++ ) {
+ tw.polys[i].plane.FitThroughPoint( tw.edges[abs(tw.polys[i].edges[0])].start );
+ }
+
+ // bounds for full trace, a little bit larger for epsilons
+ for ( i = 0; i < 3; i++ ) {
+ if ( tw.start[i] < tw.end[i] ) {
+ tw.bounds[0][i] = tw.start[i] + tw.size[0][i] - CM_BOX_EPSILON;
+ tw.bounds[1][i] = tw.end[i] + tw.size[1][i] + CM_BOX_EPSILON;
+ } else {
+ tw.bounds[0][i] = tw.end[i] + tw.size[0][i] - CM_BOX_EPSILON;
+ tw.bounds[1][i] = tw.start[i] + tw.size[1][i] + CM_BOX_EPSILON;
+ }
+ if ( idMath::Fabs(tw.size[0][i]) > idMath::Fabs(tw.size[1][i]) ) {
+ tw.extents[i] = idMath::Fabs( tw.size[0][i] ) + CM_BOX_EPSILON;
+ } else {
+ tw.extents[i] = idMath::Fabs( tw.size[1][i] ) + CM_BOX_EPSILON;
+ }
+ }
+
+ // trace through the model
+ idCollisionModelManagerLocal::TraceThroughModel( &tw );
+
+ *results = tw.trace;
+ results->fraction = ( results->c.contents == 0 );
+ results->endpos = start;
+ results->endAxis = trmAxis;
+
+ return results->c.contents;
+}
+
+/*
+==================
+idCollisionModelManagerLocal::Contents
+==================
+*/
+int idCollisionModelManagerLocal::Contents( const idVec3 &start,
+ const idTraceModel *trm, const idMat3 &trmAxis, int contentMask,
+ cmHandle_t model, const idVec3 &modelOrigin, const idMat3 &modelAxis ) {
+ trace_t results;
+
+ if ( model < 0 || model > idCollisionModelManagerLocal::maxModels || model > MAX_SUBMODELS ) {
+ common->Printf("idCollisionModelManagerLocal::Contents: invalid model handle\n");
+ return 0;
+ }
+ if ( !idCollisionModelManagerLocal::models || !idCollisionModelManagerLocal::models[model] ) {
+ common->Printf("idCollisionModelManagerLocal::Contents: invalid model\n");
+ return 0;
+ }
+
+ return ContentsTrm( &results, start, trm, trmAxis, contentMask, model, modelOrigin, modelAxis );
+}
diff --git a/neo/cm/PreyCM/CollisionModel_debug.cpp b/neo/cm/PreyCM/CollisionModel_debug.cpp
new file mode 100644
index 00000000..025772c8
--- /dev/null
+++ b/neo/cm/PreyCM/CollisionModel_debug.cpp
@@ -0,0 +1,498 @@
+
+/*
+===============================================================================
+
+ Trace model vs. polygonal model collision detection.
+
+===============================================================================
+*/
+
+#include "precompiled.h"
+#pragma hdrstop
+
+#include "CollisionModel_local.h"
+
+
+/*
+===============================================================================
+
+Visualisation code
+
+===============================================================================
+*/
+
+const char *cm_contentsNameByIndex[] = {
+// HUMANHEAD: Redid these as they were out of sync
+ "none", //
+ "solid", // bit 0
+ "opaque", // 1
+ "water", // 2
+ "playerclip", // 3
+ "monsterclip", // 4
+ "moveableclip", // 5
+ "ikclip", // 6
+ "blood", // 7
+ "body", // 8
+ "projectile", // 9
+ "corpse", // 10
+ "rendermodel", // 11
+ "trigger", // 12
+ "aas_solid", // 13
+ "aas_obstacle", // 14
+ "flashlight_trigger", // 15
+
+ // HUMANHEAD pdm: added our contents
+ "forcefield", // 16
+ "spiritbridge", // 17
+ "areaportal", // 18
+ "nocsg", // 19
+ "block_radiusdamage", // 20
+ "shootable", // 21
+ "deathvolume", // 22
+ "vehicleclip", // 23
+ "owner_to_owner", // 24
+ "game_portal", // 25
+ "shootablebyarrow", // 26
+ "hunterclip", // 27
+
+ // END HUMANHEAD
+ NULL
+};
+
+int cm_contentsFlagByIndex[] = {
+// HUMANHEAD: Redid these as they were out of sync
+ -1, // -1
+ CONTENTS_SOLID, // bit 0
+ CONTENTS_OPAQUE, // 1
+ CONTENTS_WATER, // 2
+ CONTENTS_PLAYERCLIP, // 3
+ CONTENTS_MONSTERCLIP, // 4
+ CONTENTS_MOVEABLECLIP, // 5
+ CONTENTS_IKCLIP, // 6
+ CONTENTS_BLOOD, // 7
+ CONTENTS_BODY, // 8
+ CONTENTS_PROJECTILE, // 9
+ CONTENTS_CORPSE, // 10
+ CONTENTS_RENDERMODEL, // 11
+ CONTENTS_TRIGGER, // 12
+ CONTENTS_AAS_SOLID, // 13
+ CONTENTS_AAS_OBSTACLE, // 14
+ CONTENTS_FLASHLIGHT_TRIGGER, // 15
+
+ // HUMANHEAD pdm: added our contents
+ CONTENTS_FORCEFIELD, // 16
+ CONTENTS_SPIRITBRIDGE, // 17
+ CONTENTS_AREAPORTAL, // 18
+ CONTENTS_NOCSG, // 19
+ CONTENTS_BLOCK_RADIUSDAMAGE, // 20
+ CONTENTS_SHOOTABLE, // 21
+ CONTENTS_DEATHVOLUME, // 22
+ CONTENTS_VEHICLECLIP, // 23
+ CONTENTS_OWNER_TO_OWNER, // 24
+ CONTENTS_GAME_PORTAL, // 25
+ CONTENTS_SHOOTABLEBYARROW, // 26
+ CONTENTS_HUNTERCLIP, // 27
+ // END HUMANHEAD
+ 0
+};
+
+idCVar cm_drawMask( "cm_drawMask", "none", CVAR_GAME, "collision mask", cm_contentsNameByIndex, idCmdSystem::ArgCompletion_String );
+idCVar cm_drawColor( "cm_drawColor", "1 0 0 .5", CVAR_GAME, "color used to draw the collision models" );
+idCVar cm_drawFilled( "cm_drawFilled", "0", CVAR_GAME | CVAR_BOOL, "draw filled polygons" );
+idCVar cm_drawInternal( "cm_drawInternal", "1", CVAR_GAME | CVAR_BOOL, "draw internal edges green" );
+idCVar cm_drawNormals( "cm_drawNormals", "0", CVAR_GAME | CVAR_BOOL, "draw polygon and edge normals" );
+idCVar cm_backFaceCull( "cm_backFaceCull", "0", CVAR_GAME | CVAR_BOOL, "cull back facing polygons" );
+idCVar cm_debugCollision( "cm_debugCollision", "0", CVAR_GAME | CVAR_BOOL, "debug the collision detection" );
+
+static idVec4 cm_color;
+
+/*
+================
+idCollisionModelManagerLocal::ContentsFromString
+================
+*/
+int idCollisionModelManagerLocal::ContentsFromString( const char *string ) const {
+ int i, contents = 0;
+ idLexer src( string, idStr::Length( string ), "ContentsFromString" );
+ idToken token;
+
+ while( src.ReadToken( &token ) ) {
+ if ( token == "," ) {
+ continue;
+ }
+ for ( i = 1; cm_contentsNameByIndex[i] != NULL; i++ ) {
+ if ( token.Icmp( cm_contentsNameByIndex[i] ) == 0 ) {
+ contents |= cm_contentsFlagByIndex[i];
+ break;
+ }
+ }
+ }
+
+ return contents;
+}
+
+/*
+================
+idCollisionModelManagerLocal::StringFromContents
+================
+*/
+const char *idCollisionModelManagerLocal::StringFromContents( const int contents ) const {
+ int i, length = 0;
+ static char contentsString[MAX_STRING_CHARS];
+
+ contentsString[0] = '\0';
+
+ for ( i = 1; cm_contentsFlagByIndex[i] != 0; i++ ) {
+ if ( contents & cm_contentsFlagByIndex[i] ) {
+ if ( length != 0 ) {
+ length += idStr::snPrintf( contentsString + length, sizeof( contentsString ) - length, "," );
+ }
+ length += idStr::snPrintf( contentsString + length, sizeof( contentsString ) - length, cm_contentsNameByIndex[i] );
+ }
+ }
+
+ return contentsString;
+}
+
+/*
+================
+idCollisionModelManagerLocal::DrawEdge
+================
+*/
+void idCollisionModelManagerLocal::DrawEdge( cm_model_t *model, int edgeNum, const idVec3 &origin, const idMat3 &axis ) {
+ int side;
+ cm_edge_t *edge;
+ idVec3 start, end, mid;
+ bool isRotated;
+
+ isRotated = axis.IsRotated();
+
+ edge = model->edges + abs(edgeNum);
+ side = edgeNum < 0;
+
+ start = model->vertices[edge->vertexNum[side]].p;
+ end = model->vertices[edge->vertexNum[!side]].p;
+ if ( isRotated ) {
+ start *= axis;
+ end *= axis;
+ }
+ start += origin;
+ end += origin;
+
+ if ( edge->internal ) {
+ if ( cm_drawInternal.GetBool() ) {
+ session->rw->DebugArrow( colorGreen, start, end, 1 );
+ }
+ } else {
+ if ( edge->numUsers > 2 ) {
+ session->rw->DebugArrow( colorBlue, start, end, 1 );
+ } else {
+ session->rw->DebugArrow( cm_color, start, end, 1 );
+ }
+ }
+
+ if ( cm_drawNormals.GetBool() ) {
+ mid = (start + end) * 0.5f;
+ if ( isRotated ) {
+ end = mid + 5 * (axis * edge->normal);
+ } else {
+ end = mid + 5 * edge->normal;
+ }
+ session->rw->DebugArrow( colorCyan, mid, end, 1 );
+ }
+}
+
+/*
+================
+idCollisionModelManagerLocal::DrawPolygon
+================
+*/
+void idCollisionModelManagerLocal::DrawPolygon( cm_model_t *model, cm_polygon_t *p, const idVec3 &origin, const idMat3 &axis, const idVec3 &viewOrigin ) {
+ int i, edgeNum;
+ cm_edge_t *edge;
+ idVec3 center, end, dir;
+
+ if ( cm_backFaceCull.GetBool() ) {
+ edgeNum = p->edges[0];
+ edge = model->edges + abs(edgeNum);
+ dir = model->vertices[edge->vertexNum[0]].p - viewOrigin;
+ if ( dir * p->plane.Normal() > 0.0f ) {
+ return;
+ }
+ }
+
+ if ( cm_drawNormals.GetBool() ) {
+ center = vec3_origin;
+ for ( i = 0; i < p->numEdges; i++ ) {
+ edgeNum = p->edges[i];
+ edge = model->edges + abs(edgeNum);
+ center += model->vertices[edge->vertexNum[edgeNum < 0]].p;
+ }
+ center *= (1.0f / p->numEdges);
+ if ( axis.IsRotated() ) {
+ center = center * axis + origin;
+ end = center + 5 * (axis * p->plane.Normal());
+ } else {
+ center += origin;
+ end = center + 5 * p->plane.Normal();
+ }
+ session->rw->DebugArrow( colorMagenta, center, end, 1 );
+ }
+
+ if ( cm_drawFilled.GetBool() ) {
+ idFixedWinding winding;
+ for ( i = p->numEdges - 1; i >= 0; i-- ) {
+ edgeNum = p->edges[i];
+ edge = model->edges + abs(edgeNum);
+ winding += origin + model->vertices[edge->vertexNum[INTSIGNBITSET(edgeNum)]].p * axis;
+ }
+ session->rw->DebugPolygon( cm_color, winding );
+ } else {
+ for ( i = 0; i < p->numEdges; i++ ) {
+ edgeNum = p->edges[i];
+ edge = model->edges + abs(edgeNum);
+ if ( edge->checkcount == checkCount ) {
+ continue;
+ }
+ edge->checkcount = checkCount;
+ DrawEdge( model, edgeNum, origin, axis );
+ }
+ }
+}
+
+/*
+================
+idCollisionModelManagerLocal::DrawNodePolygons
+================
+*/
+void idCollisionModelManagerLocal::DrawNodePolygons( cm_model_t *model, cm_node_t *node,
+ const idVec3 &origin, const idMat3 &axis,
+ const idVec3 &viewOrigin, const float radius ) {
+ int i;
+ cm_polygon_t *p;
+ cm_polygonRef_t *pref;
+
+ while (1) {
+ for ( pref = node->polygons; pref; pref = pref->next ) {
+ p = pref->p;
+ if ( radius ) {
+ // polygon bounds should overlap with trace bounds
+ for ( i = 0; i < 3; i++ ) {
+ if ( p->bounds[0][i] > viewOrigin[i] + radius ) {
+ break;
+ }
+ if ( p->bounds[1][i] < viewOrigin[i] - radius ) {
+ break;
+ }
+ }
+ if ( i < 3 ) {
+ continue;
+ }
+ }
+ if ( p->checkcount == checkCount ) {
+ continue;
+ }
+ if ( !( p->contents & cm_contentsFlagByIndex[cm_drawMask.GetInteger()] ) ) {
+ continue;
+ }
+
+ DrawPolygon( model, p, origin, axis, viewOrigin );
+ p->checkcount = checkCount;
+ }
+ if ( node->planeType == -1 ) {
+ break;
+ }
+ if ( radius && viewOrigin[node->planeType] > node->planeDist + radius ) {
+ node = node->children[0];
+ } else if ( radius && viewOrigin[node->planeType] < node->planeDist - radius ) {
+ node = node->children[1];
+ } else {
+ DrawNodePolygons( model, node->children[1], origin, axis, viewOrigin, radius );
+ node = node->children[0];
+ }
+ }
+}
+
+/*
+================
+idCollisionModelManagerLocal::DrawModel
+================
+*/
+void idCollisionModelManagerLocal::DrawModel( cmHandle_t handle, const idVec3 &modelOrigin, const idMat3 &modelAxis,
+ const idVec3 &viewOrigin, const float radius ) {
+
+ cm_model_t *model;
+ idVec3 viewPos;
+
+ if ( handle < 0 && handle >= numModels ) {
+ return;
+ }
+
+ if ( cm_drawColor.IsModified() ) {
+ sscanf( cm_drawColor.GetString(), "%f %f %f %f", &cm_color.x, &cm_color.y, &cm_color.z, &cm_color.w );
+ cm_drawColor.ClearModified();
+ }
+
+ model = models[ handle ];
+ viewPos = (viewOrigin - modelOrigin) * modelAxis.Transpose();
+ checkCount++;
+ DrawNodePolygons( model, model->node, modelOrigin, modelAxis, viewPos, radius );
+}
+
+/*
+===============================================================================
+
+Speed test code
+
+===============================================================================
+*/
+
+static idCVar cm_testCollision( "cm_testCollision", "0", CVAR_GAME | CVAR_BOOL, "" );
+static idCVar cm_testRotation( "cm_testRotation", "1", CVAR_GAME | CVAR_BOOL, "" );
+static idCVar cm_testModel( "cm_testModel", "0", CVAR_GAME | CVAR_INTEGER, "" );
+static idCVar cm_testTimes( "cm_testTimes", "1000", CVAR_GAME | CVAR_INTEGER, "" );
+static idCVar cm_testRandomMany( "cm_testRandomMany", "0", CVAR_GAME | CVAR_BOOL, "" );
+static idCVar cm_testOrigin( "cm_testOrigin", "0 0 0", CVAR_GAME, "" );
+static idCVar cm_testReset( "cm_testReset", "0", CVAR_GAME | CVAR_BOOL, "" );
+static idCVar cm_testBox( "cm_testBox", "-16 -16 0 16 16 64", CVAR_GAME, "" );
+static idCVar cm_testBoxRotation( "cm_testBoxRotation", "0 0 0", CVAR_GAME, "" );
+static idCVar cm_testWalk( "cm_testWalk", "1", CVAR_GAME | CVAR_BOOL, "" );
+static idCVar cm_testLength( "cm_testLength", "1024", CVAR_GAME | CVAR_FLOAT, "" );
+static idCVar cm_testRadius( "cm_testRadius", "64", CVAR_GAME | CVAR_FLOAT, "" );
+static idCVar cm_testAngle( "cm_testAngle", "60", CVAR_GAME | CVAR_FLOAT, "" );
+
+static int total_translation;
+static int min_translation = 999999;
+static int max_translation = -999999;
+static int num_translation = 0;
+static int total_rotation;
+static int min_rotation = 999999;
+static int max_rotation = -999999;
+static int num_rotation = 0;
+static idVec3 start;
+static idVec3 *testend;
+
+#include "../../sys/sys_public.h"
+
+void idCollisionModelManagerLocal::DebugOutput( const idVec3 &origin ) {
+ int i, k, t;
+ char buf[128];
+ idVec3 end;
+ idAngles boxAngles;
+ idMat3 modelAxis, boxAxis;
+ idBounds bounds;
+ trace_t trace;
+
+ if ( !cm_testCollision.GetBool() ) {
+ return;
+ }
+
+ testend = (idVec3 *) Mem_Alloc( cm_testTimes.GetInteger() * sizeof(idVec3) );
+
+ if ( cm_testReset.GetBool() || ( cm_testWalk.GetBool() && !start.Compare( start ) ) ) {
+ total_translation = total_rotation = 0;
+ min_translation = min_rotation = 999999;
+ max_translation = max_rotation = -999999;
+ num_translation = num_rotation = 0;
+ cm_testReset.SetBool( false );
+ }
+
+ if ( cm_testWalk.GetBool() ) {
+ start = origin;
+ cm_testOrigin.SetString( va( "%1.2f %1.2f %1.2f", start[0], start[1], start[2] ) );
+ } else {
+ sscanf( cm_testOrigin.GetString(), "%f %f %f", &start[0], &start[1], &start[2] );
+ }
+
+ sscanf( cm_testBox.GetString(), "%f %f %f %f %f %f", &bounds[0][0], &bounds[0][1], &bounds[0][2],
+ &bounds[1][0], &bounds[1][1], &bounds[1][2] );
+ sscanf( cm_testBoxRotation.GetString(), "%f %f %f", &boxAngles[0], &boxAngles[1], &boxAngles[2] );
+ boxAxis = boxAngles.ToMat3();
+ modelAxis.Identity();
+
+ idTraceModel itm( bounds );
+ idRandom random( 0 );
+ idTimer timer;
+
+ if ( cm_testRandomMany.GetBool() ) {
+ // if many traces in one random direction
+ for ( i = 0; i < 3; i++ ) {
+ testend[0][i] = start[i] + random.CRandomFloat() * cm_testLength.GetFloat();
+ }
+ for ( k = 1; k < cm_testTimes.GetInteger(); k++ ) {
+ testend[k] = testend[0];
+ }
+ } else {
+ // many traces each in a different random direction
+ for ( k = 0; k < cm_testTimes.GetInteger(); k++ ) {
+ for ( i = 0; i < 3; i++ ) {
+ testend[k][i] = start[i] + random.CRandomFloat() * cm_testLength.GetFloat();
+ }
+ }
+ }
+
+ // translational collision detection
+ timer.Clear();
+ timer.Start();
+ for ( i = 0; i < cm_testTimes.GetInteger(); i++ ) {
+ Translation( &trace, start, testend[i], &itm, boxAxis, CONTENTS_SOLID|CONTENTS_PLAYERCLIP, cm_testModel.GetInteger(), vec3_origin, modelAxis );
+ }
+ timer.Stop();
+ t = timer.Milliseconds();
+ if ( t < min_translation ) min_translation = t;
+ if ( t > max_translation ) max_translation = t;
+ num_translation++;
+ total_translation += t;
+ if ( cm_testTimes.GetInteger() > 9999 ) {
+ sprintf( buf, "%3dK", (int ) ( cm_testTimes.GetInteger() / 1000 ) );
+ } else {
+ sprintf( buf, "%4d", cm_testTimes.GetInteger() );
+ }
+ common->Printf("%s translations: %4d milliseconds, (min = %d, max = %d, av = %1.1f)\n", buf, t, min_translation, max_translation, (float) total_translation / num_translation );
+
+ if ( cm_testRandomMany.GetBool() ) {
+ // if many traces in one random direction
+ for ( i = 0; i < 3; i++ ) {
+ testend[0][i] = start[i] + random.CRandomFloat() * cm_testRadius.GetFloat();
+ }
+ for ( k = 1; k < cm_testTimes.GetInteger(); k++ ) {
+ testend[k] = testend[0];
+ }
+ } else {
+ // many traces each in a different random direction
+ for ( k = 0; k < cm_testTimes.GetInteger(); k++ ) {
+ for ( i = 0; i < 3; i++ ) {
+ testend[k][i] = start[i] + random.CRandomFloat() * cm_testRadius.GetFloat();
+ }
+ }
+ }
+
+ if ( cm_testRotation.GetBool() ) {
+ // rotational collision detection
+ idVec3 vec( random.CRandomFloat(), random.CRandomFloat(), random.RandomFloat() );
+ vec.Normalize();
+ idRotation rotation( vec3_origin, vec, cm_testAngle.GetFloat() );
+
+ timer.Clear();
+ timer.Start();
+ for ( i = 0; i < cm_testTimes.GetInteger(); i++ ) {
+ rotation.SetOrigin( testend[i] );
+ Rotation( &trace, start, rotation, &itm, boxAxis, CONTENTS_SOLID|CONTENTS_PLAYERCLIP, cm_testModel.GetInteger(), vec3_origin, modelAxis );
+ }
+ timer.Stop();
+ t = timer.Milliseconds();
+ if ( t < min_rotation ) min_rotation = t;
+ if ( t > max_rotation ) max_rotation = t;
+ num_rotation++;
+ total_rotation += t;
+ if ( cm_testTimes.GetInteger() > 9999 ) {
+ sprintf( buf, "%3dK", (int ) ( cm_testTimes.GetInteger() / 1000 ) );
+ } else {
+ sprintf( buf, "%4d", cm_testTimes.GetInteger() );
+ }
+ common->Printf("%s rotation: %4d milliseconds, (min = %d, max = %d, av = %1.1f)\n", buf, t, min_rotation, max_rotation, (float) total_rotation / num_rotation );
+ }
+
+ Mem_Free( testend );
+ testend = NULL;
+}
diff --git a/neo/cm/PreyCM/CollisionModel_files.cpp b/neo/cm/PreyCM/CollisionModel_files.cpp
new file mode 100644
index 00000000..5950ab39
--- /dev/null
+++ b/neo/cm/PreyCM/CollisionModel_files.cpp
@@ -0,0 +1,598 @@
+
+/*
+===============================================================================
+
+ Trace model vs. polygonal model collision detection.
+
+===============================================================================
+*/
+
+#include "precompiled.h"
+#pragma hdrstop
+
+#include "CollisionModel_local.h"
+
+#define CM_FILE_EXT "cm"
+#define CM_FILEID "CM"
+#define CM_FILEVERSION "1.00"
+
+
+/*
+===============================================================================
+
+Writing of collision model file
+
+===============================================================================
+*/
+
+void CM_GetNodeBounds( idBounds *bounds, cm_node_t *node );
+int CM_GetNodeContents( cm_node_t *node );
+
+
+/*
+================
+idCollisionModelManagerLocal::WriteNodes
+================
+*/
+void idCollisionModelManagerLocal::WriteNodes( idFile *fp, cm_node_t *node ) {
+ fp->WriteFloatString( "\t( %d %f )\n", node->planeType, node->planeDist );
+ if ( node->planeType != -1 ) {
+ WriteNodes( fp, node->children[0] );
+ WriteNodes( fp, node->children[1] );
+ }
+}
+
+/*
+================
+idCollisionModelManagerLocal::CountPolygonMemory
+================
+*/
+int idCollisionModelManagerLocal::CountPolygonMemory( cm_node_t *node ) const {
+ cm_polygonRef_t *pref;
+ cm_polygon_t *p;
+ int memory;
+
+ memory = 0;
+ for ( pref = node->polygons; pref; pref = pref->next ) {
+ p = pref->p;
+ if ( p->checkcount == checkCount ) {
+ continue;
+ }
+ p->checkcount = checkCount;
+
+ memory += sizeof( cm_polygon_t ) + ( p->numEdges - 1 ) * sizeof( p->edges[0] );
+ }
+ if ( node->planeType != -1 ) {
+ memory += CountPolygonMemory( node->children[0] );
+ memory += CountPolygonMemory( node->children[1] );
+ }
+ return memory;
+}
+
+/*
+================
+idCollisionModelManagerLocal::WritePolygons
+================
+*/
+void idCollisionModelManagerLocal::WritePolygons( idFile *fp, cm_node_t *node ) {
+ cm_polygonRef_t *pref;
+ cm_polygon_t *p;
+ int i;
+
+ for ( pref = node->polygons; pref; pref = pref->next ) {
+ p = pref->p;
+ if ( p->checkcount == checkCount ) {
+ continue;
+ }
+ p->checkcount = checkCount;
+ fp->WriteFloatString( "\t%d (", p->numEdges );
+ for ( i = 0; i < p->numEdges; i++ ) {
+ fp->WriteFloatString( " %d", p->edges[i] );
+ }
+ fp->WriteFloatString( " ) ( %f %f %f ) %f", p->plane.Normal()[0], p->plane.Normal()[1], p->plane.Normal()[2], p->plane.Dist() );
+ fp->WriteFloatString( " ( %f %f %f )", p->bounds[0][0], p->bounds[0][1], p->bounds[0][2] );
+ fp->WriteFloatString( " ( %f %f %f )", p->bounds[1][0], p->bounds[1][1], p->bounds[1][2] );
+ fp->WriteFloatString( " \"%s\"\n", p->material->GetName() );
+ }
+ if ( node->planeType != -1 ) {
+ WritePolygons( fp, node->children[0] );
+ WritePolygons( fp, node->children[1] );
+ }
+}
+
+/*
+================
+idCollisionModelManagerLocal::CountBrushMemory
+================
+*/
+int idCollisionModelManagerLocal::CountBrushMemory( cm_node_t *node ) const {
+ cm_brushRef_t *bref;
+ cm_brush_t *b;
+ int memory;
+
+ memory = 0;
+ for ( bref = node->brushes; bref; bref = bref->next ) {
+ b = bref->b;
+ if ( b->checkcount == checkCount ) {
+ continue;
+ }
+ b->checkcount = checkCount;
+
+ memory += sizeof( cm_brush_t ) + ( b->numPlanes - 1 ) * sizeof( b->planes[0] );
+ }
+ if ( node->planeType != -1 ) {
+ memory += CountBrushMemory( node->children[0] );
+ memory += CountBrushMemory( node->children[1] );
+ }
+ return memory;
+}
+
+/*
+================
+idCollisionModelManagerLocal::WriteBrushes
+================
+*/
+void idCollisionModelManagerLocal::WriteBrushes( idFile *fp, cm_node_t *node ) {
+ cm_brushRef_t *bref;
+ cm_brush_t *b;
+ int i;
+
+ for ( bref = node->brushes; bref; bref = bref->next ) {
+ b = bref->b;
+ if ( b->checkcount == checkCount ) {
+ continue;
+ }
+ b->checkcount = checkCount;
+ fp->WriteFloatString( "\t%d {\n", b->numPlanes );
+ for ( i = 0; i < b->numPlanes; i++ ) {
+ fp->WriteFloatString( "\t\t( %f %f %f ) %f\n", b->planes[i].Normal()[0], b->planes[i].Normal()[1], b->planes[i].Normal()[2], b->planes[i].Dist() );
+ }
+ fp->WriteFloatString( "\t} ( %f %f %f )", b->bounds[0][0], b->bounds[0][1], b->bounds[0][2] );
+ fp->WriteFloatString( " ( %f %f %f ) \"%s\"\n", b->bounds[1][0], b->bounds[1][1], b->bounds[1][2], StringFromContents( b->contents ) );
+ }
+ if ( node->planeType != -1 ) {
+ WriteBrushes( fp, node->children[0] );
+ WriteBrushes( fp, node->children[1] );
+ }
+}
+
+/*
+================
+idCollisionModelManagerLocal::WriteCollisionModel
+================
+*/
+void idCollisionModelManagerLocal::WriteCollisionModel( idFile *fp, cm_model_t *model ) {
+ int i, polygonMemory, brushMemory;
+
+ fp->WriteFloatString( "collisionModel \"%s\" {\n", model->name.c_str() );
+ // vertices
+ fp->WriteFloatString( "\tvertices { /* numVertices = */ %d\n", model->numVertices );
+ for ( i = 0; i < model->numVertices; i++ ) {
+ fp->WriteFloatString( "\t/* %d */ ( %f %f %f )\n", i, model->vertices[i].p[0], model->vertices[i].p[1], model->vertices[i].p[2] );
+ }
+ fp->WriteFloatString( "\t}\n" );
+ // edges
+ fp->WriteFloatString( "\tedges { /* numEdges = */ %d\n", model->numEdges );
+ for ( i = 0; i < model->numEdges; i++ ) {
+ fp->WriteFloatString( "\t/* %d */ ( %d %d ) %d %d\n", i, model->edges[i].vertexNum[0], model->edges[i].vertexNum[1], model->edges[i].internal, model->edges[i].numUsers );
+ }
+ fp->WriteFloatString( "\t}\n" );
+ // nodes
+ fp->WriteFloatString( "\tnodes {\n" );
+ WriteNodes( fp, model->node );
+ fp->WriteFloatString( "\t}\n" );
+ // polygons
+ checkCount++;
+ polygonMemory = CountPolygonMemory( model->node );
+ fp->WriteFloatString( "\tpolygons /* polygonMemory = */ %d {\n", polygonMemory );
+ checkCount++;
+ WritePolygons( fp, model->node );
+ fp->WriteFloatString( "\t}\n" );
+ // brushes
+ checkCount++;
+ brushMemory = CountBrushMemory( model->node );
+ fp->WriteFloatString( "\tbrushes /* brushMemory = */ %d {\n", brushMemory );
+ checkCount++;
+ WriteBrushes( fp, model->node );
+ fp->WriteFloatString( "\t}\n" );
+ // closing brace
+ fp->WriteFloatString( "}\n" );
+}
+
+/*
+================
+idCollisionModelManagerLocal::WriteCollisionModelsToFile
+================
+*/
+void idCollisionModelManagerLocal::WriteCollisionModelsToFile( const char *filename, int firstModel, int lastModel, unsigned int mapFileCRC ) {
+ int i;
+ idFile *fp;
+ idStr name;
+
+ name = filename;
+ name.SetFileExtension( CM_FILE_EXT );
+
+ common->Printf( "writing %s\n", name.c_str() );
+ // _D3XP was saving to fs_cdpath
+ fp = fileSystem->OpenFileWrite( name, "fs_devpath" );
+ if ( !fp ) {
+ common->Warning( "idCollisionModelManagerLocal::WriteCollisionModelsToFile: Error opening file %s\n", name.c_str() );
+ return;
+ }
+
+ // write file id and version
+ fp->WriteFloatString( "%s \"%s\"\n\n", CM_FILEID, CM_FILEVERSION );
+ // write the map file crc
+ fp->WriteFloatString( "%u\n\n", mapFileCRC );
+
+ // write the collision models
+ for ( i = firstModel; i < lastModel; i++ ) {
+ WriteCollisionModel( fp, models[ i ] );
+ }
+
+ fileSystem->CloseFile( fp );
+}
+
+/*
+================
+idCollisionModelManagerLocal::WriteCollisionModelForMapEntity
+================
+*/
+bool idCollisionModelManagerLocal::WriteCollisionModelForMapEntity( const idMapEntity *mapEnt, const char *filename, const bool testTraceModel ) {
+ idFile *fp;
+ idStr name;
+ cm_model_t *model;
+
+ SetupHash();
+ model = CollisionModelForMapEntity( mapEnt );
+ model->name = filename;
+
+ name = filename;
+ name.SetFileExtension( CM_FILE_EXT );
+
+ common->Printf( "writing %s\n", name.c_str() );
+ fp = fileSystem->OpenFileWrite( name, "fs_devpath" );
+ if ( !fp ) {
+ common->Printf( "idCollisionModelManagerLocal::WriteCollisionModelForMapEntity: Error opening file %s\n", name.c_str() );
+ FreeModel( model );
+ return false;
+ }
+
+ // write file id and version
+ fp->WriteFloatString( "%s \"%s\"\n\n", CM_FILEID, CM_FILEVERSION );
+ // write the map file crc
+ fp->WriteFloatString( "%u\n\n", 0 );
+
+ // write the collision model
+ WriteCollisionModel( fp, model );
+
+ fileSystem->CloseFile( fp );
+
+ if ( testTraceModel ) {
+ idTraceModel trm;
+ TrmFromModel( model, trm );
+ }
+
+ FreeModel( model );
+
+ return true;
+}
+
+
+/*
+===============================================================================
+
+Loading of collision model file
+
+===============================================================================
+*/
+
+/*
+================
+idCollisionModelManagerLocal::ParseVertices
+================
+*/
+void idCollisionModelManagerLocal::ParseVertices( idLexer *src, cm_model_t *model ) {
+ int i;
+
+ src->ExpectTokenString( "{" );
+ model->numVertices = src->ParseInt();
+ model->maxVertices = model->numVertices;
+ model->vertices = (cm_vertex_t *) Mem_Alloc( model->maxVertices * sizeof( cm_vertex_t ) );
+ for ( i = 0; i < model->numVertices; i++ ) {
+ src->Parse1DMatrix( 3, model->vertices[i].p.ToFloatPtr() );
+ model->vertices[i].side = 0;
+ model->vertices[i].sideSet = 0;
+ model->vertices[i].checkcount = 0;
+ }
+ src->ExpectTokenString( "}" );
+}
+
+/*
+================
+idCollisionModelManagerLocal::ParseEdges
+================
+*/
+void idCollisionModelManagerLocal::ParseEdges( idLexer *src, cm_model_t *model ) {
+ int i;
+
+ src->ExpectTokenString( "{" );
+ model->numEdges = src->ParseInt();
+ model->maxEdges = model->numEdges;
+ model->edges = (cm_edge_t *) Mem_Alloc( model->maxEdges * sizeof( cm_edge_t ) );
+ for ( i = 0; i < model->numEdges; i++ ) {
+ src->ExpectTokenString( "(" );
+ model->edges[i].vertexNum[0] = src->ParseInt();
+ model->edges[i].vertexNum[1] = src->ParseInt();
+ src->ExpectTokenString( ")" );
+ model->edges[i].side = 0;
+ model->edges[i].sideSet = 0;
+ model->edges[i].internal = src->ParseInt();
+ model->edges[i].numUsers = src->ParseInt();
+ model->edges[i].normal = vec3_origin;
+ model->edges[i].checkcount = 0;
+ model->numInternalEdges += model->edges[i].internal;
+ }
+ src->ExpectTokenString( "}" );
+}
+
+/*
+================
+idCollisionModelManagerLocal::ParseNodes
+================
+*/
+cm_node_t *idCollisionModelManagerLocal::ParseNodes( idLexer *src, cm_model_t *model, cm_node_t *parent ) {
+ cm_node_t *node;
+
+ model->numNodes++;
+ node = AllocNode( model, model->numNodes < NODE_BLOCK_SIZE_SMALL ? NODE_BLOCK_SIZE_SMALL : NODE_BLOCK_SIZE_LARGE );
+ node->brushes = NULL;
+ node->polygons = NULL;
+ node->parent = parent;
+ src->ExpectTokenString( "(" );
+ node->planeType = src->ParseInt();
+ node->planeDist = src->ParseFloat();
+ src->ExpectTokenString( ")" );
+ if ( node->planeType != -1 ) {
+ node->children[0] = ParseNodes( src, model, node );
+ node->children[1] = ParseNodes( src, model, node );
+ }
+ return node;
+}
+
+/*
+================
+idCollisionModelManagerLocal::ParsePolygons
+================
+*/
+void idCollisionModelManagerLocal::ParsePolygons( idLexer *src, cm_model_t *model ) {
+ cm_polygon_t *p;
+ int i, numEdges;
+ idVec3 normal;
+ idToken token;
+
+ if ( src->CheckTokenType( TT_NUMBER, 0, &token ) ) {
+ model->polygonBlock = (cm_polygonBlock_t *) Mem_Alloc( sizeof( cm_polygonBlock_t ) + token.GetIntValue() );
+ model->polygonBlock->bytesRemaining = token.GetIntValue();
+ model->polygonBlock->next = ( (byte *) model->polygonBlock ) + sizeof( cm_polygonBlock_t );
+ }
+
+ src->ExpectTokenString( "{" );
+ while ( !src->CheckTokenString( "}" ) ) {
+ // parse polygon
+ numEdges = src->ParseInt();
+ p = AllocPolygon( model, numEdges );
+ p->numEdges = numEdges;
+ src->ExpectTokenString( "(" );
+ for ( i = 0; i < p->numEdges; i++ ) {
+ p->edges[i] = src->ParseInt();
+ }
+ src->ExpectTokenString( ")" );
+ src->Parse1DMatrix( 3, normal.ToFloatPtr() );
+ p->plane.SetNormal( normal );
+ p->plane.SetDist( src->ParseFloat() );
+ src->Parse1DMatrix( 3, p->bounds[0].ToFloatPtr() );
+ src->Parse1DMatrix( 3, p->bounds[1].ToFloatPtr() );
+ src->ExpectTokenType( TT_STRING, 0, &token );
+ // get material
+ p->material = declManager->FindMaterial( token );
+ p->contents = p->material->GetContentFlags();
+ p->checkcount = 0;
+ // filter polygon into tree
+ R_FilterPolygonIntoTree( model, model->node, NULL, p );
+ }
+}
+
+/*
+================
+idCollisionModelManagerLocal::ParseBrushes
+================
+*/
+void idCollisionModelManagerLocal::ParseBrushes( idLexer *src, cm_model_t *model ) {
+ cm_brush_t *b;
+ int i, numPlanes;
+ idVec3 normal;
+ idToken token;
+
+ if ( src->CheckTokenType( TT_NUMBER, 0, &token ) ) {
+ model->brushBlock = (cm_brushBlock_t *) Mem_Alloc( sizeof( cm_brushBlock_t ) + token.GetIntValue() );
+ model->brushBlock->bytesRemaining = token.GetIntValue();
+ model->brushBlock->next = ( (byte *) model->brushBlock ) + sizeof( cm_brushBlock_t );
+ }
+
+ src->ExpectTokenString( "{" );
+ while ( !src->CheckTokenString( "}" ) ) {
+ // parse brush
+ numPlanes = src->ParseInt();
+ b = AllocBrush( model, numPlanes );
+ b->numPlanes = numPlanes;
+ src->ExpectTokenString( "{" );
+ for ( i = 0; i < b->numPlanes; i++ ) {
+ src->Parse1DMatrix( 3, normal.ToFloatPtr() );
+ b->planes[i].SetNormal( normal );
+ b->planes[i].SetDist( src->ParseFloat() );
+ }
+ src->ExpectTokenString( "}" );
+ src->Parse1DMatrix( 3, b->bounds[0].ToFloatPtr() );
+ src->Parse1DMatrix( 3, b->bounds[1].ToFloatPtr() );
+ src->ReadToken( &token );
+ if ( token.type == TT_NUMBER ) {
+ b->contents = token.GetIntValue(); // old .cm files use a single integer
+ } else {
+ b->contents = ContentsFromString( token );
+ }
+ b->checkcount = 0;
+ b->primitiveNum = 0;
+ // filter brush into tree
+ R_FilterBrushIntoTree( model, model->node, NULL, b );
+ }
+}
+
+/*
+================
+idCollisionModelManagerLocal::ParseCollisionModel
+================
+*/
+bool idCollisionModelManagerLocal::ParseCollisionModel( idLexer *src ) {
+ cm_model_t *model;
+ idToken token;
+
+ if ( numModels >= MAX_SUBMODELS ) {
+ common->Error( "LoadModel: no free slots" );
+ return false;
+ }
+ model = AllocModel();
+ models[numModels ] = model;
+ numModels++;
+ // parse the file
+ src->ExpectTokenType( TT_STRING, 0, &token );
+ model->name = token;
+ //HUMANHEAD rww
+#if _HH_INLINED_PROC_CLIPMODELS
+ if (anyInlinedProcClipMats) {
+ if (token.Cmpn(PROC_CLIPMODEL_STRING_PRFX, strlen(PROC_CLIPMODEL_STRING_PRFX)) == 0) {
+ numInlinedProcClipModels++;
+ }
+ }
+#endif
+ //HUMANHEAD END
+ src->ExpectTokenString( "{" );
+ while ( !src->CheckTokenString( "}" ) ) {
+
+ src->ReadToken( &token );
+
+ if ( token == "vertices" ) {
+ ParseVertices( src, model );
+ continue;
+ }
+
+ if ( token == "edges" ) {
+ ParseEdges( src, model );
+ continue;
+ }
+
+ if ( token == "nodes" ) {
+ src->ExpectTokenString( "{" );
+ model->node = ParseNodes( src, model, NULL );
+ src->ExpectTokenString( "}" );
+ continue;
+ }
+
+ if ( token == "polygons" ) {
+ ParsePolygons( src, model );
+ continue;
+ }
+
+ if ( token == "brushes" ) {
+ ParseBrushes( src, model );
+ continue;
+ }
+
+ src->Error( "ParseCollisionModel: bad token \"%s\"", token.c_str() );
+ }
+ // calculate edge normals
+ checkCount++;
+ CalculateEdgeNormals( model, model->node );
+ // get model bounds from brush and polygon bounds
+ CM_GetNodeBounds( &model->bounds, model->node );
+ // get model contents
+ model->contents = CM_GetNodeContents( model->node );
+ // total memory used by this model
+ model->usedMemory = model->numVertices * sizeof(cm_vertex_t) +
+ model->numEdges * sizeof(cm_edge_t) +
+ model->polygonMemory +
+ model->brushMemory +
+ model->numNodes * sizeof(cm_node_t) +
+ model->numPolygonRefs * sizeof(cm_polygonRef_t) +
+ model->numBrushRefs * sizeof(cm_brushRef_t);
+
+ return true;
+}
+
+/*
+================
+idCollisionModelManagerLocal::LoadCollisionModelFile
+================
+*/
+bool idCollisionModelManagerLocal::LoadCollisionModelFile( const char *name, unsigned int mapFileCRC ) {
+ idStr fileName;
+ idToken token;
+ idLexer *src;
+ unsigned int crc;
+
+ // load it
+ fileName = name;
+ fileName.SetFileExtension( CM_FILE_EXT );
+ src = new idLexer( fileName );
+ src->SetFlags( LEXFL_NOSTRINGCONCAT | LEXFL_NODOLLARPRECOMPILE );
+ if ( !src->IsLoaded() ) {
+ delete src;
+ return false;
+ }
+
+ if ( !src->ExpectTokenString( CM_FILEID ) ) {
+ common->Warning( "%s is not an CM file.", fileName.c_str() );
+ delete src;
+ return false;
+ }
+
+ if ( !src->ReadToken( &token ) || token != CM_FILEVERSION ) {
+ common->Warning( "%s has version %s instead of %s", fileName.c_str(), token.c_str(), CM_FILEVERSION );
+ delete src;
+ return false;
+ }
+
+ if ( !src->ExpectTokenType( TT_NUMBER, TT_INTEGER, &token ) ) {
+ common->Warning( "%s has no map file CRC", fileName.c_str() );
+ delete src;
+ return false;
+ }
+
+ crc = token.GetUnsignedLongValue();
+ if ( mapFileCRC && crc != mapFileCRC ) {
+ common->Printf( "%s is out of date\n", fileName.c_str() );
+ delete src;
+ return false;
+ }
+
+ // parse the file
+ while ( 1 ) {
+ if ( !src->ReadToken( &token ) ) {
+ break;
+ }
+
+ if ( token == "collisionModel" ) {
+ if ( !ParseCollisionModel( src ) ) {
+ delete src;
+ return false;
+ }
+ continue;
+ }
+
+ src->Error( "idCollisionModelManagerLocal::LoadCollisionModelFile: bad token \"%s\"", token.c_str() );
+ }
+
+ delete src;
+
+ return true;
+}
diff --git a/neo/cm/PreyCM/CollisionModel_load.cpp b/neo/cm/PreyCM/CollisionModel_load.cpp
new file mode 100644
index 00000000..ef8f1003
--- /dev/null
+++ b/neo/cm/PreyCM/CollisionModel_load.cpp
@@ -0,0 +1,3955 @@
+
+/*
+===============================================================================
+
+ Trace model vs. polygonal model collision detection.
+
+ It is more important to minimize the number of collision polygons
+ than it is to minimize the number of edges used for collision
+ detection (total edges - internal edges).
+
+ Stitching the world tends to minimize the number of edges used
+ for collision detection (more internal edges). However stitching
+ also results in more collision polygons which usually makes a
+ stitched world slower.
+
+ In an average map over 30% of all edges is internal.
+
+===============================================================================
+*/
+
+#include "precompiled.h"
+#pragma hdrstop
+
+#include "CollisionModel_local.h"
+
+
+idCollisionModelManagerLocal collisionModelManagerLocal;
+idCollisionModelManager * collisionModelManager = &collisionModelManagerLocal;
+
+cm_windingList_t * cm_windingList;
+cm_windingList_t * cm_outList;
+cm_windingList_t * cm_tmpList;
+
+idHashIndex * cm_vertexHash;
+idHashIndex * cm_edgeHash;
+
+idBounds cm_modelBounds;
+int cm_vertexShift;
+
+
+/*
+===============================================================================
+
+Proc BSP tree for data pruning
+
+===============================================================================
+*/
+
+/*
+================
+idCollisionModelManagerLocal::ParseProcNodes
+================
+*/
+void idCollisionModelManagerLocal::ParseProcNodes( idLexer *src ) {
+ int i;
+
+ src->ExpectTokenString( "{" );
+
+ numProcNodes = src->ParseInt();
+ if ( numProcNodes < 0 ) {
+ src->Error( "ParseProcNodes: bad numProcNodes" );
+ }
+ procNodes = (cm_procNode_t *)Mem_ClearedAlloc( numProcNodes * sizeof( cm_procNode_t ) );
+
+ for ( i = 0; i < numProcNodes; i++ ) {
+ cm_procNode_t *node;
+
+ node = &procNodes[i];
+
+ src->Parse1DMatrix( 4, node->plane.ToFloatPtr() );
+ node->children[0] = src->ParseInt();
+ node->children[1] = src->ParseInt();
+ }
+
+ src->ExpectTokenString( "}" );
+}
+
+//HUMANHEAD rww
+#if _HH_INLINED_PROC_CLIPMODELS
+typedef struct basicSurf_s {
+ idDrawVert *verts;
+ int numVerts;
+ glIndex_t *indices;
+ int numIndices;
+ const idMaterial *mat;
+} basicSurf_t;
+/*
+================
+idCollisionModelManagerLocal::CheckProcModelSurfClip
+================
+*/
+void idCollisionModelManagerLocal::CheckProcModelSurfClip(idLexer *src) {
+ idToken token;
+ int i, j;
+ idList basicSurfs;
+ bool parseOver = false;
+
+ src->ExpectTokenString( "{" );
+
+ // parse the name
+ //unfortunately, this isn't designer-recognizable and tends to bunch things up, so can't be used instead of material name
+ //however, we can expect only _area* models to contain inlined geometry
+ src->ExpectAnyToken( &token );
+ if (token.Cmpn("_area", strlen("_area")) != 0) {
+ parseOver = true;
+ }
+
+ int numSurfaces = src->ParseInt();
+ if ( numSurfaces < 0 ) {
+ src->Error( "idCollisionModelManagerLocal::CheckProcModelSurfClip: bad numSurfaces" );
+ }
+
+ cm_node_t *node;
+ cm_model_t *model = NULL;
+ idBounds totalBounds;
+
+ totalBounds.Zero();
+
+ for ( i = 0 ; i < numSurfaces ; i++ ) {
+ src->ExpectTokenString( "{" );
+
+ src->ExpectAnyToken( &token );
+
+ //check if this material is in the material list
+ if (!parseOver) {
+ for (j = 0; j < inlinedProcClipModelMats.Num(); j++) {
+ if (inlinedProcClipModelMats[j] == token) {
+ break;
+ }
+ }
+ }
+ if (parseOver || j == inlinedProcClipModelMats.Num()) { //just parse over this surf
+ int nV, nI;
+ nV = src->ParseInt();
+ nI = src->ParseInt();
+ for ( j = 0 ; j < nV ; j++ ) {
+ float vec[8];
+ src->Parse1DMatrix( 8, vec );
+ }
+ for ( j = 0 ; j < nI ; j++ ) {
+ src->ParseInt();
+ }
+ src->ExpectTokenString( "}" );
+ continue;
+ }
+
+ const idMaterial *mat = declManager->FindMaterial( token );
+
+ //we have a surface we want to use
+ if (!model) {
+ model = AllocModel();
+
+ model->name = va("%s%i", PROC_CLIPMODEL_STRING_PRFX, numInlinedProcClipModels);
+ node = AllocNode( model, NODE_BLOCK_SIZE_SMALL );
+ node->planeType = -1;
+ model->node = node;
+
+ model->maxVertices = 0;
+ model->numVertices = 0;
+ model->maxEdges = 0;
+ model->numEdges = 0;
+ }
+
+ int numVerts = src->ParseInt();
+ int numIndices = src->ParseInt();
+
+ model->maxVertices += numVerts;
+ model->maxEdges += numIndices;
+
+ idDrawVert *verts = (idDrawVert *)Mem_Alloc(numVerts*sizeof(idDrawVert));
+ glIndex_t *indices = (glIndex_t *)Mem_Alloc(numIndices*sizeof(glIndex_t));
+
+ //parse the actual verts and tris
+ for ( j = 0 ; j < numVerts ; j++ ) {
+ float vec[8];
+
+ src->Parse1DMatrix( 8, vec );
+
+ verts[j].xyz[0] = vec[0];
+ verts[j].xyz[1] = vec[1];
+ verts[j].xyz[2] = vec[2];
+ verts[j].st[0] = vec[3];
+ verts[j].st[1] = vec[4];
+ verts[j].normal[0] = vec[5];
+ verts[j].normal[1] = vec[6];
+ verts[j].normal[2] = vec[7];
+ }
+
+ for ( j = 0 ; j < numIndices ; j++ ) {
+ indices[j] = src->ParseInt();
+ }
+ src->ExpectTokenString( "}" );
+
+ //calculate a bounds for the surface
+ idBounds surfBounds;
+ surfBounds.Zero();
+ SIMDProcessor->MinMax(surfBounds[0], surfBounds[1], verts, numVerts);
+ totalBounds.AddBounds(surfBounds);
+
+ basicSurf_t bs;
+ bs.verts = verts;
+ bs.numVerts = numVerts;
+ bs.indices = indices;
+ bs.numIndices = numIndices;
+ bs.mat = mat;
+ basicSurfs.Append(bs);
+ }
+
+ src->ExpectTokenString( "}" );
+
+ if (model) { //we put together a model from the proc surfs
+ idFixedWinding w;
+ idPlane plane;
+
+ assert(basicSurfs.Num() > 0);
+
+ model->vertices = (cm_vertex_t *) Mem_ClearedAlloc( model->maxVertices * sizeof(cm_vertex_t) );
+ model->edges = (cm_edge_t *) Mem_ClearedAlloc( model->maxEdges * sizeof(cm_edge_t) );
+
+ assert(cm_vertexHash && cm_edgeHash); //after all, this should only be called while building models
+ cm_vertexHash->ResizeIndex( model->maxVertices );
+ cm_edgeHash->ResizeIndex( model->maxEdges );
+ ClearHash(totalBounds);
+
+ for (i = 0; i < basicSurfs.Num(); i++) {
+ for ( j = 0; j < basicSurfs[i].numIndices; j += 3 ) {
+ w.Clear();
+ w += basicSurfs[i].verts[ basicSurfs[i].indices[ j + 2 ] ].xyz;
+ w += basicSurfs[i].verts[ basicSurfs[i].indices[ j + 1 ] ].xyz;
+ w += basicSurfs[i].verts[ basicSurfs[i].indices[ j + 0 ] ].xyz;
+ w.GetPlane( plane );
+ plane = -plane;
+ PolygonFromWinding( model, &w, plane, basicSurfs[i].mat, 1 );
+ }
+ //free up the temp proc surf memory now that we're done with it
+ Mem_Free(basicSurfs[i].verts);
+ Mem_Free(basicSurfs[i].indices);
+ }
+
+ // create a BSP tree for the model
+ model->node = CreateAxialBSPTree( model, model->node );
+
+ model->isConvex = false;
+
+ FinishModel( model );
+
+ //add our new clipmodel to the list
+ models[PROC_CLIPMODEL_INDEX_START+numInlinedProcClipModels] = model;
+ numInlinedProcClipModels++;
+ }
+}
+#endif
+//HUMANHEAD END
+
+/*
+================
+idCollisionModelManagerLocal::LoadProcBSP
+
+ FIXME: if the nodes would be at the start of the .proc file it would speed things up considerably
+================
+*/
+void idCollisionModelManagerLocal::LoadProcBSP( const char *name ) {
+ idStr filename;
+ idToken token;
+ idLexer *src;
+
+ // load it
+ filename = name;
+ filename.SetFileExtension( PROC_FILE_EXT );
+ src = new idLexer( filename, LEXFL_NOSTRINGCONCAT | LEXFL_NODOLLARPRECOMPILE );
+ if ( !src->IsLoaded() ) {
+ common->Warning( "idCollisionModelManagerLocal::LoadProcBSP: couldn't load %s", filename.c_str() );
+ delete src;
+ return;
+ }
+
+ if ( !src->ReadToken( &token ) || token.Icmp( PROC_FILE_ID ) ) {
+ common->Warning( "idCollisionModelManagerLocal::LoadProcBSP: bad id '%s' instead of '%s'", token.c_str(), PROC_FILE_ID );
+ delete src;
+ return;
+ }
+
+ // parse the file
+ while ( 1 ) {
+ if ( !src->ReadToken( &token ) ) {
+ break;
+ }
+
+ if ( token == "model" ) {
+ //HUMANHEAD rww - parse model surfs to create clip models
+#if _HH_INLINED_PROC_CLIPMODELS
+ if (inlinedProcClipModelMats.Num() > 0) {
+ CheckProcModelSurfClip(src);
+ }
+ //HUMANHEAD END
+ else {
+ src->SkipBracedSection();
+ }
+#else
+ src->SkipBracedSection();
+#endif
+ continue;
+ }
+
+ if ( token == "shadowModel" ) {
+ src->SkipBracedSection();
+ continue;
+ }
+
+ if ( token == "interAreaPortals" ) {
+ src->SkipBracedSection();
+ continue;
+ }
+
+ if ( token == "nodes" ) {
+ ParseProcNodes( src );
+ break;
+ }
+
+ src->Error( "idCollisionModelManagerLocal::LoadProcBSP: bad token \"%s\"", token.c_str() );
+ }
+
+ delete src;
+}
+
+/*
+===============================================================================
+
+Free map
+
+===============================================================================
+*/
+
+/*
+================
+idCollisionModelManagerLocal::Clear
+================
+*/
+void idCollisionModelManagerLocal::Clear( void ) {
+ mapName.Clear();
+ mapFileTime = 0;
+ loaded = 0;
+ checkCount = 0;
+ maxModels = 0;
+ numModels = 0;
+ models = NULL;
+ memset( trmPolygons, 0, sizeof( trmPolygons ) );
+ trmBrushes[0] = NULL;
+ trmMaterial = NULL;
+ numProcNodes = 0;
+ procNodes = NULL;
+ getContacts = false;
+ contacts = NULL;
+ maxContacts = 0;
+ numContacts = 0;
+ //HUMANHEAD rww
+#if _HH_INLINED_PROC_CLIPMODELS
+ inlinedProcClipModelMats.Clear();
+ numInlinedProcClipModels = 0;
+ anyInlinedProcClipMats = false;
+#endif
+ //HUMANHEAD END
+}
+
+/*
+================
+idCollisionModelManagerLocal::RemovePolygonReferences_r
+================
+*/
+void idCollisionModelManagerLocal::RemovePolygonReferences_r( cm_node_t *node, cm_polygon_t *p ) {
+ cm_polygonRef_t *pref;
+
+ while( node ) {
+ for ( pref = node->polygons; pref; pref = pref->next ) {
+ if ( pref->p == p ) {
+ pref->p = NULL;
+ // cannot return here because we can have links down the tree due to polygon merging
+ //return;
+ }
+ }
+ // if leaf node
+ if ( node->planeType == -1 ) {
+ break;
+ }
+ if ( p->bounds[0][node->planeType] > node->planeDist ) {
+ node = node->children[0];
+ }
+ else if ( p->bounds[1][node->planeType] < node->planeDist ) {
+ node = node->children[1];
+ }
+ else {
+ RemovePolygonReferences_r( node->children[1], p );
+ node = node->children[0];
+ }
+ }
+}
+
+/*
+================
+idCollisionModelManagerLocal::RemoveBrushReferences_r
+================
+*/
+void idCollisionModelManagerLocal::RemoveBrushReferences_r( cm_node_t *node, cm_brush_t *b ) {
+ cm_brushRef_t *bref;
+
+ while( node ) {
+ for ( bref = node->brushes; bref; bref = bref->next ) {
+ if ( bref->b == b ) {
+ bref->b = NULL;
+ return;
+ }
+ }
+ // if leaf node
+ if ( node->planeType == -1 ) {
+ break;
+ }
+ if ( b->bounds[0][node->planeType] > node->planeDist ) {
+ node = node->children[0];
+ }
+ else if ( b->bounds[1][node->planeType] < node->planeDist ) {
+ node = node->children[1];
+ }
+ else {
+ RemoveBrushReferences_r( node->children[1], b );
+ node = node->children[0];
+ }
+ }
+}
+
+/*
+================
+idCollisionModelManagerLocal::FreeNode
+================
+*/
+void idCollisionModelManagerLocal::FreeNode( cm_node_t *node ) {
+ // don't free the node here
+ // the nodes are allocated in blocks which are freed when the model is freed
+}
+
+/*
+================
+idCollisionModelManagerLocal::FreePolygonReference
+================
+*/
+void idCollisionModelManagerLocal::FreePolygonReference( cm_polygonRef_t *pref ) {
+ // don't free the polygon reference here
+ // the polygon references are allocated in blocks which are freed when the model is freed
+}
+
+/*
+================
+idCollisionModelManagerLocal::FreeBrushReference
+================
+*/
+void idCollisionModelManagerLocal::FreeBrushReference( cm_brushRef_t *bref ) {
+ // don't free the brush reference here
+ // the brush references are allocated in blocks which are freed when the model is freed
+}
+
+/*
+================
+idCollisionModelManagerLocal::FreePolygon
+================
+*/
+void idCollisionModelManagerLocal::FreePolygon( cm_model_t *model, cm_polygon_t *poly ) {
+ model->numPolygons--;
+ model->polygonMemory -= sizeof( cm_polygon_t ) + ( poly->numEdges - 1 ) * sizeof( poly->edges[0] );
+ if ( model->polygonBlock == NULL ) {
+ Mem_Free( poly );
+ }
+}
+
+/*
+================
+idCollisionModelManagerLocal::FreeBrush
+================
+*/
+void idCollisionModelManagerLocal::FreeBrush( cm_model_t *model, cm_brush_t *brush ) {
+ model->numBrushes--;
+ model->brushMemory -= sizeof( cm_brush_t ) + ( brush->numPlanes - 1 ) * sizeof( brush->planes[0] );
+ if ( model->brushBlock == NULL ) {
+ Mem_Free( brush );
+ }
+}
+
+/*
+================
+idCollisionModelManagerLocal::FreeTree_r
+================
+*/
+void idCollisionModelManagerLocal::FreeTree_r( cm_model_t *model, cm_node_t *headNode, cm_node_t *node ) {
+ cm_polygonRef_t *pref;
+ cm_polygon_t *p;
+ cm_brushRef_t *bref;
+ cm_brush_t *b;
+
+ // free all polygons at this node
+ for ( pref = node->polygons; pref; pref = node->polygons ) {
+ p = pref->p;
+ if ( p ) {
+ // remove all other references to this polygon
+ RemovePolygonReferences_r( headNode, p );
+ FreePolygon( model, p );
+ }
+ node->polygons = pref->next;
+ FreePolygonReference( pref );
+ }
+ // free all brushes at this node
+ for ( bref = node->brushes; bref; bref = node->brushes ) {
+ b = bref->b;
+ if ( b ) {
+ // remove all other references to this brush
+ RemoveBrushReferences_r( headNode, b );
+ FreeBrush( model, b );
+ }
+ node->brushes = bref->next;
+ FreeBrushReference( bref );
+ }
+ // recurse down the tree
+ if ( node->planeType != -1 ) {
+ FreeTree_r( model, headNode, node->children[0] );
+ node->children[0] = NULL;
+ FreeTree_r( model, headNode, node->children[1] );
+ node->children[1] = NULL;
+ }
+ FreeNode( node );
+}
+
+/*
+================
+idCollisionModelManagerLocal::FreeModel
+================
+*/
+void idCollisionModelManagerLocal::FreeModel( cm_model_t *model ) {
+ cm_polygonRefBlock_t *polygonRefBlock, *nextPolygonRefBlock;
+ cm_brushRefBlock_t *brushRefBlock, *nextBrushRefBlock;
+ cm_nodeBlock_t *nodeBlock, *nextNodeBlock;
+
+ // free the tree structure
+ if ( model->node ) {
+ FreeTree_r( model, model->node, model->node );
+ }
+ // free blocks with polygon references
+ for ( polygonRefBlock = model->polygonRefBlocks; polygonRefBlock; polygonRefBlock = nextPolygonRefBlock ) {
+ nextPolygonRefBlock = polygonRefBlock->next;
+ Mem_Free( polygonRefBlock );
+ }
+ // free blocks with brush references
+ for ( brushRefBlock = model->brushRefBlocks; brushRefBlock; brushRefBlock = nextBrushRefBlock ) {
+ nextBrushRefBlock = brushRefBlock->next;
+ Mem_Free( brushRefBlock );
+ }
+ // free blocks with nodes
+ for ( nodeBlock = model->nodeBlocks; nodeBlock; nodeBlock = nextNodeBlock ) {
+ nextNodeBlock = nodeBlock->next;
+ Mem_Free( nodeBlock );
+ }
+ // free block allocated polygons
+ Mem_Free( model->polygonBlock );
+ // free block allocated brushes
+ Mem_Free( model->brushBlock );
+ // free edges
+ Mem_Free( model->edges );
+ // free vertices
+ Mem_Free( model->vertices );
+ // free the model
+ delete model;
+}
+
+/*
+================
+idCollisionModelManagerLocal::FreeMap
+================
+*/
+void idCollisionModelManagerLocal::FreeMap( void ) {
+ int i;
+
+ if ( !loaded ) {
+ Clear();
+ return;
+ }
+
+ for ( i = 0; i < maxModels; i++ ) {
+ if ( !models[i] ) {
+ continue;
+ }
+ FreeModel( models[i] );
+ }
+
+ FreeTrmModelStructure();
+
+ Mem_Free( models );
+
+ Clear();
+
+ ShutdownHash();
+}
+
+/*
+================
+idCollisionModelManagerLocal::FreeTrmModelStructure
+================
+*/
+void idCollisionModelManagerLocal::FreeTrmModelStructure( void ) {
+ int i;
+
+ assert( models );
+ if ( !models[MAX_SUBMODELS] ) {
+ return;
+ }
+
+ for ( i = 0; i < MAX_TRACEMODEL_POLYS; i++ ) {
+ FreePolygon( models[MAX_SUBMODELS], trmPolygons[i]->p );
+ }
+ FreeBrush( models[MAX_SUBMODELS], trmBrushes[0]->b );
+
+ models[MAX_SUBMODELS]->node->polygons = NULL;
+ models[MAX_SUBMODELS]->node->brushes = NULL;
+ FreeModel( models[MAX_SUBMODELS] );
+}
+
+
+/*
+===============================================================================
+
+Edge normals
+
+===============================================================================
+*/
+
+/*
+================
+idCollisionModelManagerLocal::CalculateEdgeNormals
+================
+*/
+#define SHARP_EDGE_DOT -0.7f
+
+void idCollisionModelManagerLocal::CalculateEdgeNormals( cm_model_t *model, cm_node_t *node ) {
+ cm_polygonRef_t *pref;
+ cm_polygon_t *p;
+ cm_edge_t *edge;
+ float dot, s;
+ int i, edgeNum;
+ idVec3 dir;
+
+ while( 1 ) {
+ for ( pref = node->polygons; pref; pref = pref->next ) {
+ p = pref->p;
+ // if we checked this polygon already
+ if ( p->checkcount == checkCount ) {
+ continue;
+ }
+ p->checkcount = checkCount;
+
+ for ( i = 0; i < p->numEdges; i++ ) {
+ edgeNum = p->edges[i];
+ edge = model->edges + abs( edgeNum );
+ if ( edge->normal[0] == 0.0f && edge->normal[1] == 0.0f && edge->normal[2] == 0.0f ) {
+ // if the edge is only used by this polygon
+ if ( edge->numUsers == 1 ) {
+ dir = model->vertices[ edge->vertexNum[edgeNum < 0]].p - model->vertices[ edge->vertexNum[edgeNum > 0]].p;
+ edge->normal = p->plane.Normal().Cross( dir );
+ edge->normal.Normalize();
+ } else {
+ // the edge is used by more than one polygon
+ edge->normal = p->plane.Normal();
+ }
+ } else {
+ dot = edge->normal * p->plane.Normal();
+ // if the two planes make a very sharp edge
+ if ( dot < SHARP_EDGE_DOT ) {
+ // max length normal pointing outside both polygons
+ dir = model->vertices[ edge->vertexNum[edgeNum > 0]].p - model->vertices[ edge->vertexNum[edgeNum < 0]].p;
+ edge->normal = edge->normal.Cross( dir ) + p->plane.Normal().Cross( -dir );
+ edge->normal *= ( 0.5f / ( 0.5f + 0.5f * SHARP_EDGE_DOT ) ) / edge->normal.Length();
+ model->numSharpEdges++;
+ } else {
+ s = 0.5f / ( 0.5f + 0.5f * dot );
+ edge->normal = s * ( edge->normal + p->plane.Normal() );
+ }
+ }
+ }
+ }
+ // if leaf node
+ if ( node->planeType == -1 ) {
+ break;
+ }
+ CalculateEdgeNormals( model, node->children[1] );
+ node = node->children[0];
+ }
+}
+
+/*
+===============================================================================
+
+Trace model to general collision model
+
+===============================================================================
+*/
+
+/*
+================
+idCollisionModelManagerLocal::AllocModel
+================
+*/
+cm_model_t *idCollisionModelManagerLocal::AllocModel( void ) {
+ cm_model_t *model;
+
+ model = new cm_model_t;
+ model->contents = 0;
+ model->isConvex = false;
+ model->maxVertices = 0;
+ model->numVertices = 0;
+ model->vertices = NULL;
+ model->maxEdges = 0;
+ model->numEdges = 0;
+ model->edges= NULL;
+ model->node = NULL;
+ model->nodeBlocks = NULL;
+ model->polygonRefBlocks = NULL;
+ model->brushRefBlocks = NULL;
+ model->polygonBlock = NULL;
+ model->brushBlock = NULL;
+ model->numPolygons = model->polygonMemory =
+ model->numBrushes = model->brushMemory =
+ model->numNodes = model->numBrushRefs =
+ model->numPolygonRefs = model->numInternalEdges =
+ model->numSharpEdges = model->numRemovedPolys =
+ model->numMergedPolys = model->usedMemory = 0;
+
+ return model;
+}
+
+/*
+================
+idCollisionModelManagerLocal::AllocNode
+================
+*/
+cm_node_t *idCollisionModelManagerLocal::AllocNode( cm_model_t *model, int blockSize ) {
+ int i;
+ cm_node_t *node;
+ cm_nodeBlock_t *nodeBlock;
+
+ if ( !model->nodeBlocks || !model->nodeBlocks->nextNode ) {
+ nodeBlock = (cm_nodeBlock_t *) Mem_ClearedAlloc( sizeof( cm_nodeBlock_t ) + blockSize * sizeof(cm_node_t) );
+ nodeBlock->nextNode = (cm_node_t *) ( ( (byte *) nodeBlock ) + sizeof( cm_nodeBlock_t ) );
+ nodeBlock->next = model->nodeBlocks;
+ model->nodeBlocks = nodeBlock;
+ node = nodeBlock->nextNode;
+ for ( i = 0; i < blockSize - 1; i++ ) {
+ node->parent = node + 1;
+ node = node->parent;
+ }
+ node->parent = NULL;
+ }
+
+ node = model->nodeBlocks->nextNode;
+ model->nodeBlocks->nextNode = node->parent;
+ node->parent = NULL;
+
+ return node;
+}
+
+/*
+================
+idCollisionModelManagerLocal::AllocPolygonReference
+================
+*/
+cm_polygonRef_t *idCollisionModelManagerLocal::AllocPolygonReference( cm_model_t *model, int blockSize ) {
+ int i;
+ cm_polygonRef_t *pref;
+ cm_polygonRefBlock_t *prefBlock;
+
+ if ( !model->polygonRefBlocks || !model->polygonRefBlocks->nextRef ) {
+ prefBlock = (cm_polygonRefBlock_t *) Mem_Alloc( sizeof( cm_polygonRefBlock_t ) + blockSize * sizeof(cm_polygonRef_t) );
+ prefBlock->nextRef = (cm_polygonRef_t *) ( ( (byte *) prefBlock ) + sizeof( cm_polygonRefBlock_t ) );
+ prefBlock->next = model->polygonRefBlocks;
+ model->polygonRefBlocks = prefBlock;
+ pref = prefBlock->nextRef;
+ for ( i = 0; i < blockSize - 1; i++ ) {
+ pref->next = pref + 1;
+ pref = pref->next;
+ }
+ pref->next = NULL;
+ }
+
+ pref = model->polygonRefBlocks->nextRef;
+ model->polygonRefBlocks->nextRef = pref->next;
+
+ return pref;
+}
+
+/*
+================
+idCollisionModelManagerLocal::AllocBrushReference
+================
+*/
+cm_brushRef_t *idCollisionModelManagerLocal::AllocBrushReference( cm_model_t *model, int blockSize ) {
+ int i;
+ cm_brushRef_t *bref;
+ cm_brushRefBlock_t *brefBlock;
+
+ if ( !model->brushRefBlocks || !model->brushRefBlocks->nextRef ) {
+ brefBlock = (cm_brushRefBlock_t *) Mem_Alloc( sizeof(cm_brushRefBlock_t) + blockSize * sizeof(cm_brushRef_t) );
+ brefBlock->nextRef = (cm_brushRef_t *) ( ( (byte *) brefBlock ) + sizeof(cm_brushRefBlock_t) );
+ brefBlock->next = model->brushRefBlocks;
+ model->brushRefBlocks = brefBlock;
+ bref = brefBlock->nextRef;
+ for ( i = 0; i < blockSize - 1; i++ ) {
+ bref->next = bref + 1;
+ bref = bref->next;
+ }
+ bref->next = NULL;
+ }
+
+ bref = model->brushRefBlocks->nextRef;
+ model->brushRefBlocks->nextRef = bref->next;
+
+ return bref;
+}
+
+/*
+================
+idCollisionModelManagerLocal::AllocPolygon
+================
+*/
+cm_polygon_t *idCollisionModelManagerLocal::AllocPolygon( cm_model_t *model, int numEdges ) {
+ cm_polygon_t *poly;
+ int size;
+
+ size = sizeof( cm_polygon_t ) + ( numEdges - 1 ) * sizeof( poly->edges[0] );
+ model->numPolygons++;
+ model->polygonMemory += size;
+ if ( model->polygonBlock && model->polygonBlock->bytesRemaining >= size ) {
+ poly = (cm_polygon_t *) model->polygonBlock->next;
+ model->polygonBlock->next += size;
+ model->polygonBlock->bytesRemaining -= size;
+ } else {
+ poly = (cm_polygon_t *) Mem_Alloc( size );
+ }
+ return poly;
+}
+
+/*
+================
+idCollisionModelManagerLocal::AllocBrush
+================
+*/
+cm_brush_t *idCollisionModelManagerLocal::AllocBrush( cm_model_t *model, int numPlanes ) {
+ cm_brush_t *brush;
+ int size;
+
+ size = sizeof( cm_brush_t ) + ( numPlanes - 1 ) * sizeof( brush->planes[0] );
+ model->numBrushes++;
+ model->brushMemory += size;
+ if ( model->brushBlock && model->brushBlock->bytesRemaining >= size ) {
+ brush = (cm_brush_t *) model->brushBlock->next;
+ model->brushBlock->next += size;
+ model->brushBlock->bytesRemaining -= size;
+ } else {
+ brush = (cm_brush_t *) Mem_Alloc( size );
+ }
+ return brush;
+}
+
+/*
+================
+idCollisionModelManagerLocal::AddPolygonToNode
+================
+*/
+void idCollisionModelManagerLocal::AddPolygonToNode( cm_model_t *model, cm_node_t *node, cm_polygon_t *p ) {
+ cm_polygonRef_t *pref;
+
+ pref = AllocPolygonReference( model, model->numPolygonRefs < REFERENCE_BLOCK_SIZE_SMALL ? REFERENCE_BLOCK_SIZE_SMALL : REFERENCE_BLOCK_SIZE_LARGE );
+ pref->p = p;
+ pref->next = node->polygons;
+ node->polygons = pref;
+ model->numPolygonRefs++;
+}
+
+/*
+================
+idCollisionModelManagerLocal::AddBrushToNode
+================
+*/
+void idCollisionModelManagerLocal::AddBrushToNode( cm_model_t *model, cm_node_t *node, cm_brush_t *b ) {
+ cm_brushRef_t *bref;
+
+ bref = AllocBrushReference( model, model->numBrushRefs < REFERENCE_BLOCK_SIZE_SMALL ? REFERENCE_BLOCK_SIZE_SMALL : REFERENCE_BLOCK_SIZE_LARGE );
+ bref->b = b;
+ bref->next = node->brushes;
+ node->brushes = bref;
+ model->numBrushRefs++;
+}
+
+/*
+================
+idCollisionModelManagerLocal::SetupTrmModelStructure
+================
+*/
+void idCollisionModelManagerLocal::SetupTrmModelStructure( void ) {
+ int i;
+ cm_node_t *node;
+ cm_model_t *model;
+
+ // setup model
+ model = AllocModel();
+
+ assert( models );
+ models[MAX_SUBMODELS] = model;
+ // create node to hold the collision data
+ node = (cm_node_t *) AllocNode( model, 1 );
+ node->planeType = -1;
+ model->node = node;
+ // allocate vertex and edge arrays
+ model->numVertices = 0;
+ model->maxVertices = MAX_TRACEMODEL_VERTS;
+ model->vertices = (cm_vertex_t *) Mem_ClearedAlloc( model->maxVertices * sizeof(cm_vertex_t) );
+ model->numEdges = 0;
+ model->maxEdges = MAX_TRACEMODEL_EDGES+1;
+ model->edges = (cm_edge_t *) Mem_ClearedAlloc( model->maxEdges * sizeof(cm_edge_t) );
+ // create a material for the trace model polygons
+ trmMaterial = declManager->FindMaterial( "_tracemodel", false );
+ if ( !trmMaterial ) {
+ common->FatalError( "_tracemodel material not found" );
+ }
+
+ // allocate polygons
+ for ( i = 0; i < MAX_TRACEMODEL_POLYS; i++ ) {
+ trmPolygons[i] = AllocPolygonReference( model, MAX_TRACEMODEL_POLYS );
+ trmPolygons[i]->p = AllocPolygon( model, MAX_TRACEMODEL_POLYEDGES );
+ trmPolygons[i]->p->bounds.Clear();
+ trmPolygons[i]->p->plane.Zero();
+ trmPolygons[i]->p->checkcount = 0;
+ trmPolygons[i]->p->contents = -1; // all contents
+ trmPolygons[i]->p->material = trmMaterial;
+ trmPolygons[i]->p->numEdges = 0;
+ }
+ // allocate brush for position test
+ trmBrushes[0] = AllocBrushReference( model, 1 );
+ trmBrushes[0]->b = AllocBrush( model, MAX_TRACEMODEL_POLYS );
+ trmBrushes[0]->b->primitiveNum = 0;
+ trmBrushes[0]->b->bounds.Clear();
+ trmBrushes[0]->b->checkcount = 0;
+ trmBrushes[0]->b->contents = -1; // all contents
+ trmBrushes[0]->b->material = trmMaterial; //HUMANHEAD rww
+ trmBrushes[0]->b->numPlanes = 0;
+}
+
+/*
+================
+idCollisionModelManagerLocal::SetupTrmModel
+
+Trace models (item boxes, etc) are converted to collision models on the fly, using the last model slot
+as a reusable temporary buffer
+================
+*/
+cmHandle_t idCollisionModelManagerLocal::SetupTrmModel( const idTraceModel &trm, const idMaterial *material ) {
+ int i, j;
+ cm_vertex_t *vertex;
+ cm_edge_t *edge;
+ cm_polygon_t *poly;
+ cm_model_t *model;
+ const traceModelVert_t *trmVert;
+ const traceModelEdge_t *trmEdge;
+ const traceModelPoly_t *trmPoly;
+
+ assert( models );
+
+ if ( material == NULL ) {
+ material = trmMaterial;
+ }
+
+ model = models[MAX_SUBMODELS];
+ model->node->brushes = NULL;
+ model->node->polygons = NULL;
+ // if not a valid trace model
+ if ( trm.type == TRM_INVALID || !trm.numPolys ) {
+ return TRACE_MODEL_HANDLE;
+ }
+ // vertices
+ model->numVertices = trm.numVerts;
+ vertex = model->vertices;
+ trmVert = trm.verts;
+ for ( i = 0; i < trm.numVerts; i++, vertex++, trmVert++ ) {
+ vertex->p = *trmVert;
+ vertex->sideSet = 0;
+ }
+ // edges
+ model->numEdges = trm.numEdges;
+ edge = model->edges + 1;
+ trmEdge = trm.edges + 1;
+ for ( i = 0; i < trm.numEdges; i++, edge++, trmEdge++ ) {
+ edge->vertexNum[0] = trmEdge->v[0];
+ edge->vertexNum[1] = trmEdge->v[1];
+ edge->normal = trmEdge->normal;
+ edge->internal = false;
+ edge->sideSet = 0;
+ }
+ // polygons
+ model->numPolygons = trm.numPolys;
+ trmPoly = trm.polys;
+ for ( i = 0; i < trm.numPolys; i++, trmPoly++ ) {
+ poly = trmPolygons[i]->p;
+ poly->numEdges = trmPoly->numEdges;
+ for ( j = 0; j < trmPoly->numEdges; j++ ) {
+ poly->edges[j] = trmPoly->edges[j];
+ }
+ poly->plane.SetNormal( trmPoly->normal );
+ poly->plane.SetDist( trmPoly->dist );
+ poly->bounds = trmPoly->bounds;
+ poly->material = material;
+ // link polygon at node
+ trmPolygons[i]->next = model->node->polygons;
+ model->node->polygons = trmPolygons[i];
+ }
+ // if the trace model is convex
+ if ( trm.isConvex ) {
+ // setup brush for position test
+ trmBrushes[0]->b->numPlanes = trm.numPolys;
+ for ( i = 0; i < trm.numPolys; i++ ) {
+ trmBrushes[0]->b->planes[i] = trmPolygons[i]->p->plane;
+ }
+ trmBrushes[0]->b->bounds = trm.bounds;
+ // link brush at node
+ trmBrushes[0]->next = model->node->brushes;
+ model->node->brushes = trmBrushes[0];
+ }
+ // model bounds
+ model->bounds = trm.bounds;
+ // convex
+ model->isConvex = trm.isConvex;
+
+ return TRACE_MODEL_HANDLE;
+}
+
+/*
+===============================================================================
+
+Optimisation, removal of polygons contained within brushes or solid
+
+===============================================================================
+*/
+
+/*
+============
+idCollisionModelManagerLocal::R_ChoppedAwayByProcBSP
+============
+*/
+int idCollisionModelManagerLocal::R_ChoppedAwayByProcBSP( int nodeNum, idFixedWinding *w, const idVec3 &normal, const idVec3 &origin, const float radius ) {
+ int res;
+ idFixedWinding back;
+ cm_procNode_t *node;
+ float dist;
+
+ do {
+ node = procNodes + nodeNum;
+ dist = node->plane.Normal() * origin + node->plane[3];
+ if ( dist > radius ) {
+ res = SIDE_FRONT;
+ }
+ else if ( dist < -radius ) {
+ res = SIDE_BACK;
+ }
+ else {
+ res = w->Split( &back, node->plane, CHOP_EPSILON );
+ }
+ if ( res == SIDE_FRONT ) {
+ nodeNum = node->children[0];
+ }
+ else if ( res == SIDE_BACK ) {
+ nodeNum = node->children[1];
+ }
+ else if ( res == SIDE_ON ) {
+ // continue with the side the winding faces
+ if ( node->plane.Normal() * normal > 0.0f ) {
+ nodeNum = node->children[0];
+ }
+ else {
+ nodeNum = node->children[1];
+ }
+ }
+ else {
+ // if either node is not solid
+ if ( node->children[0] < 0 || node->children[1] < 0 ) {
+ return false;
+ }
+ // only recurse if the node is not solid
+ if ( node->children[1] > 0 ) {
+ if ( !R_ChoppedAwayByProcBSP( node->children[1], &back, normal, origin, radius ) ) {
+ return false;
+ }
+ }
+ nodeNum = node->children[0];
+ }
+ } while ( nodeNum > 0 );
+ if ( nodeNum < 0 ) {
+ return false;
+ }
+ return true;
+}
+
+/*
+============
+idCollisionModelManagerLocal::ChoppedAwayByProcBSP
+============
+*/
+int idCollisionModelManagerLocal::ChoppedAwayByProcBSP( const idFixedWinding &w, const idPlane &plane, int contents ) {
+ idFixedWinding neww;
+ idBounds bounds;
+ float radius;
+ idVec3 origin;
+
+ // if the .proc file has no BSP tree
+ if ( procNodes == NULL ) {
+ return false;
+ }
+ // don't chop if the polygon is not solid
+ if ( !(contents & CONTENTS_SOLID) ) {
+ return false;
+ }
+ // make a local copy of the winding
+ neww = w;
+ neww.GetBounds( bounds );
+ origin = (bounds[1] - bounds[0]) * 0.5f;
+ radius = origin.Length() + CHOP_EPSILON;
+ origin = bounds[0] + origin;
+ //
+ return R_ChoppedAwayByProcBSP( 0, &neww, plane.Normal(), origin, radius );
+}
+
+/*
+=============
+idCollisionModelManagerLocal::ChopWindingWithBrush
+
+ returns the least number of winding fragments outside the brush
+=============
+*/
+void idCollisionModelManagerLocal::ChopWindingListWithBrush( cm_windingList_t *list, cm_brush_t *b ) {
+ int i, k, res, startPlane, planeNum, bestNumWindings;
+ idFixedWinding back, front;
+ idPlane plane;
+ bool chopped;
+ int sidedness[MAX_POINTS_ON_WINDING];
+ float dist;
+
+ if ( b->numPlanes > MAX_POINTS_ON_WINDING ) {
+ return;
+ }
+
+ // get sidedness for the list of windings
+ for ( i = 0; i < b->numPlanes; i++ ) {
+ plane = -b->planes[i];
+
+ dist = plane.Distance( list->origin );
+ if ( dist > list->radius ) {
+ sidedness[i] = SIDE_FRONT;
+ }
+ else if ( dist < -list->radius ) {
+ sidedness[i] = SIDE_BACK;
+ }
+ else {
+ sidedness[i] = list->bounds.PlaneSide( plane );
+ if ( sidedness[i] == PLANESIDE_FRONT ) {
+ sidedness[i] = SIDE_FRONT;
+ }
+ else if ( sidedness[i] == PLANESIDE_BACK ) {
+ sidedness[i] = SIDE_BACK;
+ }
+ else {
+ sidedness[i] = SIDE_CROSS;
+ }
+ }
+ }
+
+ cm_outList->numWindings = 0;
+ for ( k = 0; k < list->numWindings; k++ ) {
+ //
+ startPlane = 0;
+ bestNumWindings = 1 + b->numPlanes;
+ chopped = false;
+ do {
+ front = list->w[k];
+ cm_tmpList->numWindings = 0;
+ for ( planeNum = startPlane, i = 0; i < b->numPlanes; i++, planeNum++ ) {
+
+ if ( planeNum >= b->numPlanes ) {
+ planeNum = 0;
+ }
+
+ res = sidedness[planeNum];
+
+ if ( res == SIDE_CROSS ) {
+ plane = -b->planes[planeNum];
+ res = front.Split( &back, plane, CHOP_EPSILON );
+ }
+
+ // NOTE: disabling this can create gaps at places where Z-fighting occurs
+ // Z-fighting should not occur but what if there is a decal brush side
+ // with exactly the same size as another brush side ?
+ // only leave windings on a brush if the winding plane and brush side plane face the same direction
+ if ( res == SIDE_ON && list->primitiveNum >= 0 && (list->normal * b->planes[planeNum].Normal()) > 0 ) {
+ // return because all windings in the list will be on this brush side plane
+ return;
+ }
+
+ if ( res == SIDE_BACK ) {
+ if ( cm_outList->numWindings >= MAX_WINDING_LIST ) {
+ common->Warning( "idCollisionModelManagerLocal::ChopWindingWithBrush: primitive %d more than %d windings", list->primitiveNum, MAX_WINDING_LIST );
+ return;
+ }
+ // winding and brush didn't intersect, store the original winding
+ cm_outList->w[cm_outList->numWindings] = list->w[k];
+ cm_outList->numWindings++;
+ chopped = false;
+ break;
+ }
+
+ if ( res == SIDE_CROSS ) {
+ if ( cm_tmpList->numWindings >= MAX_WINDING_LIST ) {
+ common->Warning( "idCollisionModelManagerLocal::ChopWindingWithBrush: primitive %d more than %d windings", list->primitiveNum, MAX_WINDING_LIST );
+ return;
+ }
+ // store the front winding in the temporary list
+ cm_tmpList->w[cm_tmpList->numWindings] = back;
+ cm_tmpList->numWindings++;
+ chopped = true;
+ }
+
+ // if already found a start plane which generates less fragments
+ if ( cm_tmpList->numWindings >= bestNumWindings ) {
+ break;
+ }
+ }
+
+ // find the best start plane to get the least number of fragments outside the brush
+ if ( cm_tmpList->numWindings < bestNumWindings ) {
+ bestNumWindings = cm_tmpList->numWindings;
+ // store windings from temporary list in the out list
+ for ( i = 0; i < cm_tmpList->numWindings; i++ ) {
+ if ( cm_outList->numWindings + i >= MAX_WINDING_LIST ) {
+ common->Warning( "idCollisionModelManagerLocal::ChopWindingWithBrush: primitive %d more than %d windings", list->primitiveNum, MAX_WINDING_LIST );
+ return;
+ }
+ cm_outList->w[cm_outList->numWindings+i] = cm_tmpList->w[i];
+ }
+ // if only one winding left then we can't do any better
+ if ( bestNumWindings == 1 ) {
+ break;
+ }
+ }
+
+ // try the next start plane
+ startPlane++;
+
+ } while ( chopped && startPlane < b->numPlanes );
+ //
+ if ( chopped ) {
+ cm_outList->numWindings += bestNumWindings;
+ }
+ }
+ for ( k = 0; k < cm_outList->numWindings; k++ ) {
+ list->w[k] = cm_outList->w[k];
+ }
+ list->numWindings = cm_outList->numWindings;
+}
+
+/*
+============
+idCollisionModelManagerLocal::R_ChopWindingListWithTreeBrushes
+============
+*/
+void idCollisionModelManagerLocal::R_ChopWindingListWithTreeBrushes( cm_windingList_t *list, cm_node_t *node ) {
+ int i;
+ cm_brushRef_t *bref;
+ cm_brush_t *b;
+
+ while( 1 ) {
+ for ( bref = node->brushes; bref; bref = bref->next ) {
+ b = bref->b;
+ // if we checked this brush already
+ if ( b->checkcount == checkCount ) {
+ continue;
+ }
+ b->checkcount = checkCount;
+ // if the windings in the list originate from this brush
+ if ( b->primitiveNum == list->primitiveNum ) {
+ continue;
+ }
+ // if brush has a different contents
+ if ( b->contents != list->contents ) {
+ continue;
+ }
+ // brush bounds and winding list bounds should overlap
+ for ( i = 0; i < 3; i++ ) {
+ if ( list->bounds[0][i] > b->bounds[1][i] ) {
+ break;
+ }
+ if ( list->bounds[1][i] < b->bounds[0][i] ) {
+ break;
+ }
+ }
+ if ( i < 3 ) {
+ continue;
+ }
+ // chop windings in the list with brush
+ ChopWindingListWithBrush( list, b );
+ // if all windings are chopped away we're done
+ if ( !list->numWindings ) {
+ return;
+ }
+ }
+ // if leaf node
+ if ( node->planeType == -1 ) {
+ break;
+ }
+ if ( list->bounds[0][node->planeType] > node->planeDist ) {
+ node = node->children[0];
+ }
+ else if ( list->bounds[1][node->planeType] < node->planeDist ) {
+ node = node->children[1];
+ }
+ else {
+ R_ChopWindingListWithTreeBrushes( list, node->children[1] );
+ if ( !list->numWindings ) {
+ return;
+ }
+ node = node->children[0];
+ }
+ }
+}
+
+/*
+============
+idCollisionModelManagerLocal::WindingOutsideBrushes
+
+ Returns one winding which is not fully contained in brushes.
+ We always favor less polygons over a stitched world.
+ If the winding is partly contained and the contained pieces can be chopped off
+ without creating multiple winding fragments then the chopped winding is returned.
+============
+*/
+idFixedWinding *idCollisionModelManagerLocal::WindingOutsideBrushes( idFixedWinding *w, const idPlane &plane, int contents, int primitiveNum, cm_node_t *headNode ) {
+ int i, windingLeft;
+
+ cm_windingList->bounds.Clear();
+ for ( i = 0; i < w->GetNumPoints(); i++ ) {
+ cm_windingList->bounds.AddPoint( (*w)[i].ToVec3() );
+ }
+
+ cm_windingList->origin = (cm_windingList->bounds[1] - cm_windingList->bounds[0]) * 0.5;
+ cm_windingList->radius = cm_windingList->origin.Length() + CHOP_EPSILON;
+ cm_windingList->origin = cm_windingList->bounds[0] + cm_windingList->origin;
+ cm_windingList->bounds[0] -= idVec3( CHOP_EPSILON, CHOP_EPSILON, CHOP_EPSILON );
+ cm_windingList->bounds[1] += idVec3( CHOP_EPSILON, CHOP_EPSILON, CHOP_EPSILON );
+
+ cm_windingList->w[0] = *w;
+ cm_windingList->numWindings = 1;
+ cm_windingList->normal = plane.Normal();
+ cm_windingList->contents = contents;
+ cm_windingList->primitiveNum = primitiveNum;
+ //
+ checkCount++;
+ R_ChopWindingListWithTreeBrushes( cm_windingList, headNode );
+ //
+ if ( !cm_windingList->numWindings ) {
+ return NULL;
+ }
+ if ( cm_windingList->numWindings == 1 ) {
+ return &cm_windingList->w[0];
+ }
+ // if not the world model
+ if ( numModels != 0 ) {
+ return w;
+ }
+ // check if winding fragments would be chopped away by the proc BSP tree
+ windingLeft = -1;
+ for ( i = 0; i < cm_windingList->numWindings; i++ ) {
+ if ( !ChoppedAwayByProcBSP( cm_windingList->w[i], plane, contents ) ) {
+ if ( windingLeft >= 0 ) {
+ return w;
+ }
+ windingLeft = i;
+ }
+ }
+ if ( windingLeft >= 0 ) {
+ return &cm_windingList->w[windingLeft];
+ }
+ return NULL;
+}
+
+/*
+===============================================================================
+
+Merging polygons
+
+===============================================================================
+*/
+
+/*
+=============
+idCollisionModelManagerLocal::ReplacePolygons
+
+ does not allow for a node to have multiple references to the same polygon
+=============
+*/
+void idCollisionModelManagerLocal::ReplacePolygons( cm_model_t *model, cm_node_t *node, cm_polygon_t *p1, cm_polygon_t *p2, cm_polygon_t *newp ) {
+ cm_polygonRef_t *pref, *lastpref, *nextpref;
+ cm_polygon_t *p;
+ bool linked;
+
+ while( 1 ) {
+ linked = false;
+ lastpref = NULL;
+ for ( pref = node->polygons; pref; pref = nextpref ) {
+ nextpref = pref->next;
+ //
+ p = pref->p;
+ // if this polygon reference should change
+ if ( p == p1 || p == p2 ) {
+ // if the new polygon is already linked at this node
+ if ( linked ) {
+ if ( lastpref ) {
+ lastpref->next = nextpref;
+ }
+ else {
+ node->polygons = nextpref;
+ }
+ FreePolygonReference( pref );
+ model->numPolygonRefs--;
+ }
+ else {
+ pref->p = newp;
+ linked = true;
+ lastpref = pref;
+ }
+ }
+ else {
+ lastpref = pref;
+ }
+ }
+ // if leaf node
+ if ( node->planeType == -1 ) {
+ break;
+ }
+ if ( p1->bounds[0][node->planeType] > node->planeDist && p2->bounds[0][node->planeType] > node->planeDist ) {
+ node = node->children[0];
+ }
+ else if ( p1->bounds[1][node->planeType] < node->planeDist && p2->bounds[1][node->planeType] < node->planeDist ) {
+ node = node->children[1];
+ }
+ else {
+ ReplacePolygons( model, node->children[1], p1, p2, newp );
+ node = node->children[0];
+ }
+ }
+}
+
+/*
+=============
+idCollisionModelManagerLocal::TryMergePolygons
+=============
+*/
+#define CONTINUOUS_EPSILON 0.005f
+#define NORMAL_EPSILON 0.01f
+
+cm_polygon_t *idCollisionModelManagerLocal::TryMergePolygons( cm_model_t *model, cm_polygon_t *p1, cm_polygon_t *p2 ) {
+ int i, j, nexti, prevj;
+ int p1BeforeShare, p1AfterShare, p2BeforeShare, p2AfterShare;
+ int newEdges[CM_MAX_POLYGON_EDGES], newNumEdges;
+ int edgeNum, edgeNum1, edgeNum2, newEdgeNum1, newEdgeNum2;
+ cm_edge_t *edge;
+ cm_polygon_t *newp;
+ idVec3 delta, normal;
+ float dot;
+ bool keep1, keep2;
+
+ if ( p1->material != p2->material ) {
+ return NULL;
+ }
+ if ( idMath::Fabs( p1->plane.Dist() - p2->plane.Dist() ) > NORMAL_EPSILON ) {
+ return NULL;
+ }
+ for ( i = 0; i < 3; i++ ) {
+ if ( idMath::Fabs( p1->plane.Normal()[i] - p2->plane.Normal()[i] ) > NORMAL_EPSILON ) {
+ return NULL;
+ }
+ if ( p1->bounds[0][i] > p2->bounds[1][i] ) {
+ return NULL;
+ }
+ if ( p1->bounds[1][i] < p2->bounds[0][i] ) {
+ return NULL;
+ }
+ }
+ // this allows for merging polygons with multiple shared edges
+ // polygons with multiple shared edges probably never occur tho ;)
+ p1BeforeShare = p1AfterShare = p2BeforeShare = p2AfterShare = -1;
+ for ( i = 0; i < p1->numEdges; i++ ) {
+ nexti = (i+1)%p1->numEdges;
+ for ( j = 0; j < p2->numEdges; j++ ) {
+ prevj = (j+p2->numEdges-1)%p2->numEdges;
+ //
+ if ( abs(p1->edges[i]) != abs(p2->edges[j]) ) {
+ // if the next edge of p1 and the previous edge of p2 are the same
+ if ( abs(p1->edges[nexti]) == abs(p2->edges[prevj]) ) {
+ // if both polygons don't use the edge in the same direction
+ if ( p1->edges[nexti] != p2->edges[prevj] ) {
+ p1BeforeShare = i;
+ p2AfterShare = j;
+ }
+ break;
+ }
+ }
+ // if both polygons don't use the edge in the same direction
+ else if ( p1->edges[i] != p2->edges[j] ) {
+ // if the next edge of p1 and the previous edge of p2 are not the same
+ if ( abs(p1->edges[nexti]) != abs(p2->edges[prevj]) ) {
+ p1AfterShare = nexti;
+ p2BeforeShare = prevj;
+ break;
+ }
+ }
+ }
+ }
+ if ( p1BeforeShare < 0 || p1AfterShare < 0 || p2BeforeShare < 0 || p2AfterShare < 0 ) {
+ return NULL;
+ }
+
+ // check if the new polygon would still be convex
+ edgeNum = p1->edges[p1BeforeShare];
+ edge = model->edges + abs(edgeNum);
+ delta = model->vertices[edge->vertexNum[INTSIGNBITNOTSET(edgeNum)]].p -
+ model->vertices[edge->vertexNum[INTSIGNBITSET(edgeNum)]].p;
+ normal = p1->plane.Normal().Cross( delta );
+ normal.Normalize();
+
+ edgeNum = p2->edges[p2AfterShare];
+ edge = model->edges + abs(edgeNum);
+ delta = model->vertices[edge->vertexNum[INTSIGNBITNOTSET(edgeNum)]].p -
+ model->vertices[edge->vertexNum[INTSIGNBITSET(edgeNum)]].p;
+
+ dot = delta * normal;
+ if (dot < -CONTINUOUS_EPSILON)
+ return NULL; // not a convex polygon
+ keep1 = (bool)(dot > CONTINUOUS_EPSILON);
+
+ edgeNum = p2->edges[p2BeforeShare];
+ edge = model->edges + abs(edgeNum);
+ delta = model->vertices[edge->vertexNum[INTSIGNBITNOTSET(edgeNum)]].p -
+ model->vertices[edge->vertexNum[INTSIGNBITSET(edgeNum)]].p;
+ normal = p1->plane.Normal().Cross( delta );
+ normal.Normalize();
+
+ edgeNum = p1->edges[p1AfterShare];
+ edge = model->edges + abs(edgeNum);
+ delta = model->vertices[edge->vertexNum[INTSIGNBITNOTSET(edgeNum)]].p -
+ model->vertices[edge->vertexNum[INTSIGNBITSET(edgeNum)]].p;
+
+ dot = delta * normal;
+ if (dot < -CONTINUOUS_EPSILON)
+ return NULL; // not a convex polygon
+ keep2 = (bool)(dot > CONTINUOUS_EPSILON);
+
+ newEdgeNum1 = newEdgeNum2 = 0;
+ // get new edges if we need to replace colinear ones
+ if ( !keep1 ) {
+ edgeNum1 = p1->edges[p1BeforeShare];
+ edgeNum2 = p2->edges[p2AfterShare];
+ GetEdge( model, model->vertices[model->edges[abs(edgeNum1)].vertexNum[INTSIGNBITSET(edgeNum1)]].p,
+ model->vertices[model->edges[abs(edgeNum2)].vertexNum[INTSIGNBITNOTSET(edgeNum2)]].p,
+ &newEdgeNum1, -1 );
+ if ( newEdgeNum1 == 0 ) {
+ keep1 = true;
+ }
+ }
+ if ( !keep2 ) {
+ edgeNum1 = p2->edges[p2BeforeShare];
+ edgeNum2 = p1->edges[p1AfterShare];
+ GetEdge( model, model->vertices[model->edges[abs(edgeNum1)].vertexNum[INTSIGNBITSET(edgeNum1)]].p,
+ model->vertices[model->edges[abs(edgeNum2)].vertexNum[INTSIGNBITNOTSET(edgeNum2)]].p,
+ &newEdgeNum2, -1 );
+ if ( newEdgeNum2 == 0 ) {
+ keep2 = true;
+ }
+ }
+ // set edges for new polygon
+ newNumEdges = 0;
+ if ( !keep2 ) {
+ newEdges[newNumEdges++] = newEdgeNum2;
+ }
+ if ( p1AfterShare < p1BeforeShare ) {
+ for ( i = p1AfterShare + (!keep2); i <= p1BeforeShare - (!keep1); i++ ) {
+ newEdges[newNumEdges++] = p1->edges[i];
+ }
+ }
+ else {
+ for ( i = p1AfterShare + (!keep2); i < p1->numEdges; i++ ) {
+ newEdges[newNumEdges++] = p1->edges[i];
+ }
+ for ( i = 0; i <= p1BeforeShare - (!keep1); i++ ) {
+ newEdges[newNumEdges++] = p1->edges[i];
+ }
+ }
+ if ( !keep1 ) {
+ newEdges[newNumEdges++] = newEdgeNum1;
+ }
+ if ( p2AfterShare < p2BeforeShare ) {
+ for ( i = p2AfterShare + (!keep1); i <= p2BeforeShare - (!keep2); i++ ) {
+ newEdges[newNumEdges++] = p2->edges[i];
+ }
+ }
+ else {
+ for ( i = p2AfterShare + (!keep1); i < p2->numEdges; i++ ) {
+ newEdges[newNumEdges++] = p2->edges[i];
+ }
+ for ( i = 0; i <= p2BeforeShare - (!keep2); i++ ) {
+ newEdges[newNumEdges++] = p2->edges[i];
+ }
+ }
+
+ newp = AllocPolygon( model, newNumEdges );
+ memcpy( newp, p1, sizeof(cm_polygon_t) );
+ memcpy( newp->edges, newEdges, newNumEdges * sizeof(int) );
+ newp->numEdges = newNumEdges;
+ newp->checkcount = 0;
+ // increase usage count for the edges of this polygon
+ for ( i = 0; i < newp->numEdges; i++ ) {
+ if ( !keep1 && newp->edges[i] == newEdgeNum1 ) {
+ continue;
+ }
+ if ( !keep2 && newp->edges[i] == newEdgeNum2 ) {
+ continue;
+ }
+ model->edges[abs(newp->edges[i])].numUsers++;
+ }
+ // create new bounds from the merged polygons
+ newp->bounds = p1->bounds + p2->bounds;
+
+ return newp;
+}
+
+/*
+=============
+idCollisionModelManagerLocal::MergePolygonWithTreePolygons
+=============
+*/
+bool idCollisionModelManagerLocal::MergePolygonWithTreePolygons( cm_model_t *model, cm_node_t *node, cm_polygon_t *polygon ) {
+ int i;
+ cm_polygonRef_t *pref;
+ cm_polygon_t *p, *newp;
+
+ while( 1 ) {
+ for ( pref = node->polygons; pref; pref = pref->next ) {
+ p = pref->p;
+ //
+ if ( p == polygon ) {
+ continue;
+ }
+ //
+ newp = TryMergePolygons( model, polygon, p );
+ // if polygons were merged
+ if ( newp ) {
+ model->numMergedPolys++;
+ // replace links to the merged polygons with links to the new polygon
+ ReplacePolygons( model, model->node, polygon, p, newp );
+ // decrease usage count for edges of both merged polygons
+ for ( i = 0; i < polygon->numEdges; i++ ) {
+ model->edges[abs(polygon->edges[i])].numUsers--;
+ }
+ for ( i = 0; i < p->numEdges; i++ ) {
+ model->edges[abs(p->edges[i])].numUsers--;
+ }
+ // free merged polygons
+ FreePolygon( model, polygon );
+ FreePolygon( model, p );
+
+ return true;
+ }
+ }
+ // if leaf node
+ if ( node->planeType == -1 ) {
+ break;
+ }
+ if ( polygon->bounds[0][node->planeType] > node->planeDist ) {
+ node = node->children[0];
+ }
+ else if ( polygon->bounds[1][node->planeType] < node->planeDist ) {
+ node = node->children[1];
+ }
+ else {
+ if ( MergePolygonWithTreePolygons( model, node->children[1], polygon ) ) {
+ return true;
+ }
+ node = node->children[0];
+ }
+ }
+ return false;
+}
+
+/*
+=============
+idCollisionModelManagerLocal::MergeTreePolygons
+
+ try to merge any two polygons with the same surface flags and the same contents
+=============
+*/
+void idCollisionModelManagerLocal::MergeTreePolygons( cm_model_t *model, cm_node_t *node ) {
+ cm_polygonRef_t *pref;
+ cm_polygon_t *p;
+ bool merge;
+
+ while( 1 ) {
+ do {
+ merge = false;
+ for ( pref = node->polygons; pref; pref = pref->next ) {
+ p = pref->p;
+ // if we checked this polygon already
+ if ( p->checkcount == checkCount ) {
+ continue;
+ }
+ p->checkcount = checkCount;
+ // try to merge this polygon with other polygons in the tree
+ if ( MergePolygonWithTreePolygons( model, model->node, p ) ) {
+ merge = true;
+ break;
+ }
+ }
+ } while (merge);
+ // if leaf node
+ if ( node->planeType == -1 ) {
+ break;
+ }
+ MergeTreePolygons( model, node->children[1] );
+ node = node->children[0];
+ }
+}
+
+/*
+===============================================================================
+
+Find internal edges
+
+===============================================================================
+*/
+
+/*
+
+ if (two polygons have the same contents)
+ if (the normals of the two polygon planes face towards each other)
+ if (an edge is shared between the polygons)
+ if (the edge is not shared in the same direction)
+ then this is an internal edge
+ else
+ if (this edge is on the plane of the other polygon)
+ if (this edge if fully inside the winding of the other polygon)
+ then this edge is an internal edge
+
+*/
+
+/*
+=============
+idCollisionModelManagerLocal::PointInsidePolygon
+=============
+*/
+bool idCollisionModelManagerLocal::PointInsidePolygon( cm_model_t *model, cm_polygon_t *p, idVec3 &v ) {
+ int i, edgeNum;
+ idVec3 *v1, *v2, dir1, dir2, vec;
+ cm_edge_t *edge;
+
+ for ( i = 0; i < p->numEdges; i++ ) {
+ edgeNum = p->edges[i];
+ edge = model->edges + abs(edgeNum);
+ //
+ v1 = &model->vertices[edge->vertexNum[INTSIGNBITSET(edgeNum)]].p;
+ v2 = &model->vertices[edge->vertexNum[INTSIGNBITNOTSET(edgeNum)]].p;
+ dir1 = (*v2) - (*v1);
+ vec = v - (*v1);
+ dir2 = dir1.Cross( p->plane.Normal() );
+ if ( vec * dir2 > VERTEX_EPSILON ) {
+ return false;
+ }
+ }
+ return true;
+}
+
+/*
+=============
+idCollisionModelManagerLocal::FindInternalEdgesOnPolygon
+=============
+*/
+void idCollisionModelManagerLocal::FindInternalEdgesOnPolygon( cm_model_t *model, cm_polygon_t *p1, cm_polygon_t *p2 ) {
+ int i, j, k, edgeNum;
+ cm_edge_t *edge;
+ idVec3 *v1, *v2, dir1, dir2;
+ float d;
+
+ // bounds of polygons should overlap or touch
+ for ( i = 0; i < 3; i++ ) {
+ if ( p1->bounds[0][i] > p2->bounds[1][i] ) {
+ return;
+ }
+ if ( p1->bounds[1][i] < p2->bounds[0][i] ) {
+ return;
+ }
+ }
+ //
+ // FIXME: doubled geometry causes problems
+ //
+ for ( i = 0; i < p1->numEdges; i++ ) {
+ edgeNum = p1->edges[i];
+ edge = model->edges + abs(edgeNum);
+ // if already an internal edge
+ if ( edge->internal ) {
+ continue;
+ }
+ //
+ v1 = &model->vertices[edge->vertexNum[INTSIGNBITSET(edgeNum)]].p;
+ v2 = &model->vertices[edge->vertexNum[INTSIGNBITNOTSET(edgeNum)]].p;
+ // if either of the two vertices is outside the bounds of the other polygon
+ for ( k = 0; k < 3; k++ ) {
+ d = p2->bounds[1][k] + VERTEX_EPSILON;
+ if ( (*v1)[k] > d || (*v2)[k] > d ) {
+ break;
+ }
+ d = p2->bounds[0][k] - VERTEX_EPSILON;
+ if ( (*v1)[k] < d || (*v2)[k] < d ) {
+ break;
+ }
+ }
+ if ( k < 3 ) {
+ continue;
+ }
+ //
+ k = abs(edgeNum);
+ for ( j = 0; j < p2->numEdges; j++ ) {
+ if ( k == abs(p2->edges[j]) ) {
+ break;
+ }
+ }
+ // if the edge is shared between the two polygons
+ if ( j < p2->numEdges ) {
+ // if the edge is used by more than 2 polygons
+ if ( edge->numUsers > 2 ) {
+ // could still be internal but we'd have to test all polygons using the edge
+ continue;
+ }
+ // if the edge goes in the same direction for both polygons
+ if ( edgeNum == p2->edges[j] ) {
+ // the polygons can lay ontop of each other or one can obscure the other
+ continue;
+ }
+ }
+ // the edge was not shared
+ else {
+ // both vertices should be on the plane of the other polygon
+ d = p2->plane.Distance( *v1 );
+ if ( idMath::Fabs(d) > VERTEX_EPSILON ) {
+ continue;
+ }
+ d = p2->plane.Distance( *v2 );
+ if ( idMath::Fabs(d) > VERTEX_EPSILON ) {
+ continue;
+ }
+ }
+ // the two polygon plane normals should face towards each other
+ dir1 = (*v2) - (*v1);
+ dir2 = p1->plane.Normal().Cross( dir1 );
+ if ( p2->plane.Normal() * dir2 < 0 ) {
+ //continue;
+ break;
+ }
+ // if the edge was not shared
+ if ( j >= p2->numEdges ) {
+ // both vertices of the edge should be inside the winding of the other polygon
+ if ( !PointInsidePolygon( model, p2, *v1 ) ) {
+ continue;
+ }
+ if ( !PointInsidePolygon( model, p2, *v2 ) ) {
+ continue;
+ }
+ }
+ // we got another internal edge
+ edge->internal = true;
+ model->numInternalEdges++;
+ }
+}
+
+/*
+=============
+idCollisionModelManagerLocal::FindInternalPolygonEdges
+=============
+*/
+void idCollisionModelManagerLocal::FindInternalPolygonEdges( cm_model_t *model, cm_node_t *node, cm_polygon_t *polygon ) {
+ cm_polygonRef_t *pref;
+ cm_polygon_t *p;
+
+ if ( polygon->material->GetCullType() == CT_TWO_SIDED || polygon->material->ShouldCreateBackSides() ) {
+ return;
+ }
+
+ while( 1 ) {
+ for ( pref = node->polygons; pref; pref = pref->next ) {
+ p = pref->p;
+ //
+ // FIXME: use some sort of additional checkcount because currently
+ // polygons can be checked multiple times
+ //
+ // if the polygons don't have the same contents
+ if ( p->contents != polygon->contents ) {
+ continue;
+ }
+ if ( p == polygon ) {
+ continue;
+ }
+ FindInternalEdgesOnPolygon( model, polygon, p );
+ }
+ // if leaf node
+ if ( node->planeType == -1 ) {
+ break;
+ }
+ if ( polygon->bounds[0][node->planeType] > node->planeDist ) {
+ node = node->children[0];
+ }
+ else if ( polygon->bounds[1][node->planeType] < node->planeDist ) {
+ node = node->children[1];
+ }
+ else {
+ FindInternalPolygonEdges( model, node->children[1], polygon );
+ node = node->children[0];
+ }
+ }
+}
+
+/*
+=============
+idCollisionModelManagerLocal::FindContainedEdges
+=============
+*/
+void idCollisionModelManagerLocal::FindContainedEdges( cm_model_t *model, cm_polygon_t *p ) {
+ int i, edgeNum;
+ cm_edge_t *edge;
+ idFixedWinding w;
+
+ for ( i = 0; i < p->numEdges; i++ ) {
+ edgeNum = p->edges[i];
+ edge = model->edges + abs(edgeNum);
+ if ( edge->internal ) {
+ continue;
+ }
+ w.Clear();
+ w += model->vertices[edge->vertexNum[INTSIGNBITSET(edgeNum)]].p;
+ w += model->vertices[edge->vertexNum[INTSIGNBITNOTSET(edgeNum)]].p;
+ if ( ChoppedAwayByProcBSP( w, p->plane, p->contents ) ) {
+ edge->internal = true;
+ }
+ }
+}
+
+/*
+=============
+idCollisionModelManagerLocal::FindInternalEdges
+=============
+*/
+void idCollisionModelManagerLocal::FindInternalEdges( cm_model_t *model, cm_node_t *node ) {
+ cm_polygonRef_t *pref;
+ cm_polygon_t *p;
+
+ while( 1 ) {
+ for ( pref = node->polygons; pref; pref = pref->next ) {
+ p = pref->p;
+ // if we checked this polygon already
+ if ( p->checkcount == checkCount ) {
+ continue;
+ }
+ p->checkcount = checkCount;
+
+ FindInternalPolygonEdges( model, model->node, p );
+
+ //FindContainedEdges( model, p );
+ }
+ // if leaf node
+ if ( node->planeType == -1 ) {
+ break;
+ }
+ FindInternalEdges( model, node->children[1] );
+ node = node->children[0];
+ }
+}
+
+/*
+===============================================================================
+
+Spatial subdivision
+
+===============================================================================
+*/
+
+/*
+================
+CM_FindSplitter
+================
+*/
+static int CM_FindSplitter( const cm_node_t *node, const idBounds &bounds, int *planeType, float *planeDist ) {
+ int i, j, type, axis[3], polyCount;
+ float dist, t, bestt, size[3];
+ cm_brushRef_t *bref;
+ cm_polygonRef_t *pref;
+ const cm_node_t *n;
+ bool forceSplit = false;
+
+ for ( i = 0; i < 3; i++ ) {
+ size[i] = bounds[1][i] - bounds[0][i];
+ axis[i] = i;
+ }
+ // sort on largest axis
+ for ( i = 0; i < 2; i++ ) {
+ if ( size[i] < size[i+1] ) {
+ t = size[i];
+ size[i] = size[i+1];
+ size[i+1] = t;
+ j = axis[i];
+ axis[i] = axis[i+1];
+ axis[i+1] = j;
+ i = -1;
+ }
+ }
+ // if the node is too small for further splits
+ if ( size[0] < MIN_NODE_SIZE ) {
+ polyCount = 0;
+ for ( pref = node->polygons; pref; pref = pref->next) {
+ polyCount++;
+ }
+ if ( polyCount > MAX_NODE_POLYGONS ) {
+ forceSplit = true;
+ }
+ }
+ // find an axial aligned splitter
+ for ( i = 0; i < 3; i++ ) {
+ // start with the largest axis first
+ type = axis[i];
+ bestt = size[i];
+ // if the node is small anough in this axis direction
+ if ( !forceSplit && bestt < MIN_NODE_SIZE ) {
+ break;
+ }
+ // find an axial splitter from the brush bounding boxes
+ // also try brushes from parent nodes
+ for ( n = node; n; n = n->parent ) {
+ for ( bref = n->brushes; bref; bref = bref->next) {
+ for ( j = 0; j < 2; j++ ) {
+ dist = bref->b->bounds[j][type];
+ // if the splitter is already used or outside node bounds
+ if ( dist >= bounds[1][type] || dist <= bounds[0][type] ) {
+ continue;
+ }
+ // find the most centered splitter
+ t = abs((bounds[1][type] - dist) - (dist - bounds[0][type]));
+ if ( t < bestt ) {
+ bestt = t;
+ *planeType = type;
+ *planeDist = dist;
+ }
+ }
+ }
+ }
+ // find an axial splitter from the polygon bounding boxes
+ // also try brushes from parent nodes
+ for ( n = node; n; n = n->parent ) {
+ for ( pref = n->polygons; pref; pref = pref->next) {
+ for ( j = 0; j < 2; j++ ) {
+ dist = pref->p->bounds[j][type];
+ // if the splitter is already used or outside node bounds
+ if ( dist >= bounds[1][type] || dist <= bounds[0][type] ) {
+ continue;
+ }
+ // find the most centered splitter
+ t = abs((bounds[1][type] - dist) - (dist - bounds[0][type]));
+ if ( t < bestt ) {
+ bestt = t;
+ *planeType = type;
+ *planeDist = dist;
+ }
+ }
+ }
+ }
+ // if we found a splitter on the largest axis
+ if ( bestt < size[i] ) {
+ // if forced split due to lots of polygons
+ if ( forceSplit ) {
+ return true;
+ }
+ // don't create splitters real close to the bounds
+ if ( bounds[1][type] - *planeDist > (MIN_NODE_SIZE*0.5f) &&
+ *planeDist - bounds[0][type] > (MIN_NODE_SIZE*0.5f) ) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+/*
+================
+CM_R_InsideAllChildren
+================
+*/
+static int CM_R_InsideAllChildren( cm_node_t *node, const idBounds &bounds ) {
+ assert(node != NULL);
+ if ( node->planeType != -1 ) {
+ if ( bounds[0][node->planeType] >= node->planeDist ) {
+ return false;
+ }
+ if ( bounds[1][node->planeType] <= node->planeDist ) {
+ return false;
+ }
+ if ( !CM_R_InsideAllChildren( node->children[0], bounds ) ) {
+ return false;
+ }
+ if ( !CM_R_InsideAllChildren( node->children[1], bounds ) ) {
+ return false;
+ }
+ }
+ return true;
+}
+
+/*
+================
+idCollisionModelManagerLocal::R_FilterPolygonIntoTree
+================
+*/
+void idCollisionModelManagerLocal::R_FilterPolygonIntoTree( cm_model_t *model, cm_node_t *node, cm_polygonRef_t *pref, cm_polygon_t *p ) {
+ assert(node != NULL);
+ while ( node->planeType != -1 ) {
+ if ( CM_R_InsideAllChildren( node, p->bounds ) ) {
+ break;
+ }
+ if ( p->bounds[0][node->planeType] >= node->planeDist ) {
+ node = node->children[0];
+ }
+ else if ( p->bounds[1][node->planeType] <= node->planeDist ) {
+ node = node->children[1];
+ }
+ else {
+ R_FilterPolygonIntoTree( model, node->children[1], NULL, p );
+ node = node->children[0];
+ }
+ }
+ if ( pref ) {
+ pref->next = node->polygons;
+ node->polygons = pref;
+ }
+ else {
+ AddPolygonToNode( model, node, p );
+ }
+}
+
+/*
+================
+idCollisionModelManagerLocal::R_FilterBrushIntoTree
+================
+*/
+void idCollisionModelManagerLocal::R_FilterBrushIntoTree( cm_model_t *model, cm_node_t *node, cm_brushRef_t *pref, cm_brush_t *b ) {
+ assert(node != NULL);
+ while ( node->planeType != -1 ) {
+ if ( CM_R_InsideAllChildren( node, b->bounds ) ) {
+ break;
+ }
+ if ( b->bounds[0][node->planeType] >= node->planeDist ) {
+ node = node->children[0];
+ }
+ else if ( b->bounds[1][node->planeType] <= node->planeDist ) {
+ node = node->children[1];
+ }
+ else {
+ R_FilterBrushIntoTree( model, node->children[1], NULL, b );
+ node = node->children[0];
+ }
+ }
+ if ( pref ) {
+ pref->next = node->brushes;
+ node->brushes = pref;
+ }
+ else {
+ AddBrushToNode( model, node, b );
+ }
+}
+
+/*
+================
+idCollisionModelManagerLocal::R_CreateAxialBSPTree
+
+ a brush or polygon is linked in the node closest to the root where
+ the brush or polygon is inside all children
+================
+*/
+cm_node_t *idCollisionModelManagerLocal::R_CreateAxialBSPTree( cm_model_t *model, cm_node_t *node, const idBounds &bounds ) {
+ int planeType;
+ float planeDist;
+ cm_polygonRef_t *pref, *nextpref, *prevpref;
+ cm_brushRef_t *bref, *nextbref, *prevbref;
+ cm_node_t *frontNode, *backNode, *n;
+ idBounds frontBounds, backBounds;
+
+ if ( !CM_FindSplitter( node, bounds, &planeType, &planeDist ) ) {
+ node->planeType = -1;
+ return node;
+ }
+ // create two child nodes
+ frontNode = AllocNode( model, NODE_BLOCK_SIZE_LARGE );
+ memset( frontNode, 0, sizeof(cm_node_t) );
+ frontNode->parent = node;
+ frontNode->planeType = -1;
+ //
+ backNode = AllocNode( model, NODE_BLOCK_SIZE_LARGE );
+ memset( backNode, 0, sizeof(cm_node_t) );
+ backNode->parent = node;
+ backNode->planeType = -1;
+ //
+ model->numNodes += 2;
+ // set front node bounds
+ frontBounds = bounds;
+ frontBounds[0][planeType] = planeDist;
+ // set back node bounds
+ backBounds = bounds;
+ backBounds[1][planeType] = planeDist;
+ //
+ node->planeType = planeType;
+ node->planeDist = planeDist;
+ node->children[0] = frontNode;
+ node->children[1] = backNode;
+ // filter polygons and brushes down the tree if necesary
+ for ( n = node; n; n = n->parent ) {
+ prevpref = NULL;
+ for ( pref = n->polygons; pref; pref = nextpref) {
+ nextpref = pref->next;
+ // if polygon is not inside all children
+ if ( !CM_R_InsideAllChildren( n, pref->p->bounds ) ) {
+ // filter polygon down the tree
+ R_FilterPolygonIntoTree( model, n, pref, pref->p );
+ if ( prevpref ) {
+ prevpref->next = nextpref;
+ }
+ else {
+ n->polygons = nextpref;
+ }
+ }
+ else {
+ prevpref = pref;
+ }
+ }
+ prevbref = NULL;
+ for ( bref = n->brushes; bref; bref = nextbref) {
+ nextbref = bref->next;
+ // if brush is not inside all children
+ if ( !CM_R_InsideAllChildren( n, bref->b->bounds ) ) {
+ // filter brush down the tree
+ R_FilterBrushIntoTree( model, n, bref, bref->b );
+ if ( prevbref ) {
+ prevbref->next = nextbref;
+ }
+ else {
+ n->brushes = nextbref;
+ }
+ }
+ else {
+ prevbref = bref;
+ }
+ }
+ }
+ R_CreateAxialBSPTree( model, frontNode, frontBounds );
+ R_CreateAxialBSPTree( model, backNode, backBounds );
+ return node;
+}
+
+/*
+int cm_numSavedPolygonLinks;
+int cm_numSavedBrushLinks;
+
+int CM_R_CountChildren( cm_node_t *node ) {
+ if ( node->planeType == -1 ) {
+ return 0;
+ }
+ return 2 + CM_R_CountChildren(node->children[0]) + CM_R_CountChildren(node->children[1]);
+}
+
+void CM_R_TestOptimisation( cm_node_t *node ) {
+ int polyCount, brushCount, numChildren;
+ cm_polygonRef_t *pref;
+ cm_brushRef_t *bref;
+
+ if ( node->planeType == -1 ) {
+ return;
+ }
+ polyCount = 0;
+ for ( pref = node->polygons; pref; pref = pref->next) {
+ polyCount++;
+ }
+ brushCount = 0;
+ for ( bref = node->brushes; bref; bref = bref->next) {
+ brushCount++;
+ }
+ if ( polyCount || brushCount ) {
+ numChildren = CM_R_CountChildren( node );
+ cm_numSavedPolygonLinks += (numChildren - 1) * polyCount;
+ cm_numSavedBrushLinks += (numChildren - 1) * brushCount;
+ }
+ CM_R_TestOptimisation( node->children[0] );
+ CM_R_TestOptimisation( node->children[1] );
+}
+*/
+
+/*
+================
+idCollisionModelManagerLocal::CreateAxialBSPTree
+================
+*/
+cm_node_t *idCollisionModelManagerLocal::CreateAxialBSPTree( cm_model_t *model, cm_node_t *node ) {
+ cm_polygonRef_t *pref;
+ cm_brushRef_t *bref;
+ idBounds bounds;
+
+ // get head node bounds
+ bounds.Clear();
+ for ( pref = node->polygons; pref; pref = pref->next) {
+ bounds += pref->p->bounds;
+ }
+ for ( bref = node->brushes; bref; bref = bref->next) {
+ bounds += bref->b->bounds;
+ }
+
+ // create axial BSP tree from head node
+ node = R_CreateAxialBSPTree( model, node, bounds );
+
+ return node;
+}
+
+/*
+===============================================================================
+
+Raw polygon and brush data
+
+===============================================================================
+*/
+
+/*
+================
+idCollisionModelManagerLocal::SetupHash
+================
+*/
+void idCollisionModelManagerLocal::SetupHash( void ) {
+ if ( !cm_vertexHash ) {
+ cm_vertexHash = new idHashIndex( VERTEX_HASH_SIZE, 1024 );
+ }
+ if ( !cm_edgeHash ) {
+ cm_edgeHash = new idHashIndex( EDGE_HASH_SIZE, 1024 );
+ }
+ // init variables used during loading and optimization
+ if ( !cm_windingList ) {
+ cm_windingList = new cm_windingList_t;
+ }
+ if ( !cm_outList ) {
+ cm_outList = new cm_windingList_t;
+ }
+ if ( !cm_tmpList ) {
+ cm_tmpList = new cm_windingList_t;
+ }
+}
+
+/*
+================
+idCollisionModelManagerLocal::ShutdownHash
+================
+*/
+void idCollisionModelManagerLocal::ShutdownHash( void ) {
+ delete cm_vertexHash;
+ cm_vertexHash = NULL;
+ delete cm_edgeHash;
+ cm_edgeHash = NULL;
+ delete cm_tmpList;
+ cm_tmpList = NULL;
+ delete cm_outList;
+ cm_outList = NULL;
+ delete cm_windingList;
+ cm_windingList = NULL;
+}
+
+/*
+================
+idCollisionModelManagerLocal::ClearHash
+================
+*/
+void idCollisionModelManagerLocal::ClearHash( idBounds &bounds ) {
+ int i;
+ float f, max;
+
+ cm_vertexHash->Clear();
+ cm_edgeHash->Clear();
+
+ cm_modelBounds = bounds;
+ max = bounds[1].x - bounds[0].x;
+ f = bounds[1].y - bounds[0].y;
+ if ( f > max ) {
+ max = f;
+ }
+ cm_vertexShift = (float) max / VERTEX_HASH_BOXSIZE;
+ for ( i = 0; (1<> cm_vertexShift) & (VERTEX_HASH_BOXSIZE-1);
+ y = (((int)(vec[1] - cm_modelBounds[0].y + 0.5 )) >> cm_vertexShift) & (VERTEX_HASH_BOXSIZE-1);
+
+ assert (x >= 0 && x < VERTEX_HASH_BOXSIZE && y >= 0 && y < VERTEX_HASH_BOXSIZE);
+
+ return y * VERTEX_HASH_BOXSIZE + x;
+ */
+ int x, y, z;
+
+ x = (((int) (vec[0] - cm_modelBounds[0].x + 0.5)) + 2) >> 2;
+ y = (((int) (vec[1] - cm_modelBounds[0].y + 0.5)) + 2) >> 2;
+ z = (((int) (vec[2] - cm_modelBounds[0].z + 0.5)) + 2) >> 2;
+ return (x + y * VERTEX_HASH_BOXSIZE + z) & (VERTEX_HASH_SIZE-1);
+}
+
+/*
+================
+idCollisionModelManagerLocal::GetVertex
+================
+*/
+int idCollisionModelManagerLocal::GetVertex( cm_model_t *model, const idVec3 &v, int *vertexNum ) {
+ int i, hashKey, vn;
+ idVec3 vert, *p;
+
+ for (i = 0; i < 3; i++) {
+ if ( idMath::Fabs(v[i] - idMath::Rint(v[i])) < INTEGRAL_EPSILON )
+ vert[i] = idMath::Rint(v[i]);
+ else
+ vert[i] = v[i];
+ }
+
+ hashKey = HashVec( vert );
+
+ for (vn = cm_vertexHash->First( hashKey ); vn >= 0; vn = cm_vertexHash->Next( vn ) ) {
+ p = &model->vertices[vn].p;
+ // first compare z-axis because hash is based on x-y plane
+ if (idMath::Fabs(vert[2] - (*p)[2]) < VERTEX_EPSILON &&
+ idMath::Fabs(vert[0] - (*p)[0]) < VERTEX_EPSILON &&
+ idMath::Fabs(vert[1] - (*p)[1]) < VERTEX_EPSILON )
+ {
+ *vertexNum = vn;
+ return true;
+ }
+ }
+
+ if ( model->numVertices >= model->maxVertices ) {
+ cm_vertex_t *oldVertices;
+
+ // resize vertex array
+ model->maxVertices = (float) model->maxVertices * 1.5f + 1;
+ oldVertices = model->vertices;
+ model->vertices = (cm_vertex_t *) Mem_ClearedAlloc( model->maxVertices * sizeof(cm_vertex_t) );
+ memcpy( model->vertices, oldVertices, model->numVertices * sizeof(cm_vertex_t) );
+ Mem_Free( oldVertices );
+
+ cm_vertexHash->ResizeIndex( model->maxVertices );
+ }
+ model->vertices[model->numVertices].p = vert;
+ model->vertices[model->numVertices].checkcount = 0;
+ *vertexNum = model->numVertices;
+ // add vertice to hash
+ cm_vertexHash->Add( hashKey, model->numVertices );
+ //
+ model->numVertices++;
+ return false;
+}
+
+/*
+================
+idCollisionModelManagerLocal::GetEdge
+================
+*/
+int idCollisionModelManagerLocal::GetEdge( cm_model_t *model, const idVec3 &v1, const idVec3 &v2, int *edgeNum, int v1num ) {
+ int v2num, hashKey, e;
+ int found, *vertexNum;
+
+ // the first edge is a dummy
+ if ( model->numEdges == 0 ) {
+ model->numEdges = 1;
+ }
+
+ if ( v1num != -1 ) {
+ found = 1;
+ }
+ else {
+ found = GetVertex( model, v1, &v1num );
+ }
+ found &= GetVertex( model, v2, &v2num );
+ // if both vertices are the same or snapped onto each other
+ if ( v1num == v2num ) {
+ *edgeNum = 0;
+ return true;
+ }
+ hashKey = cm_edgeHash->GenerateKey( v1num, v2num );
+ // if both vertices where already stored
+ if (found) {
+ for (e = cm_edgeHash->First( hashKey ); e >= 0; e = cm_edgeHash->Next( e ) )
+ {
+ // NOTE: only allow at most two users that use the edge in opposite direction
+ if ( model->edges[e].numUsers != 1 ) {
+ continue;
+ }
+
+ vertexNum = model->edges[e].vertexNum;
+ if ( vertexNum[0] == v2num ) {
+ if ( vertexNum[1] == v1num ) {
+ // negative for a reversed edge
+ *edgeNum = -e;
+ break;
+ }
+ }
+ /*
+ else if ( vertexNum[0] == v1num ) {
+ if ( vertexNum[1] == v2num ) {
+ *edgeNum = e;
+ break;
+ }
+ }
+ */
+ }
+ // if edge found in hash
+ if ( e >= 0 ) {
+ model->edges[e].numUsers++;
+ return true;
+ }
+ }
+ if ( model->numEdges >= model->maxEdges ) {
+ cm_edge_t *oldEdges;
+
+ // resize edge array
+ model->maxEdges = (float) model->maxEdges * 1.5f + 1;
+ oldEdges = model->edges;
+ model->edges = (cm_edge_t *) Mem_ClearedAlloc( model->maxEdges * sizeof(cm_edge_t) );
+ memcpy( model->edges, oldEdges, model->numEdges * sizeof(cm_edge_t) );
+ Mem_Free( oldEdges );
+
+ cm_edgeHash->ResizeIndex( model->maxEdges );
+ }
+ // setup edge
+ model->edges[model->numEdges].vertexNum[0] = v1num;
+ model->edges[model->numEdges].vertexNum[1] = v2num;
+ model->edges[model->numEdges].internal = false;
+ model->edges[model->numEdges].checkcount = 0;
+ model->edges[model->numEdges].numUsers = 1; // used by one polygon atm
+ model->edges[model->numEdges].normal.Zero();
+ //
+ *edgeNum = model->numEdges;
+ // add edge to hash
+ cm_edgeHash->Add( hashKey, model->numEdges );
+
+ model->numEdges++;
+
+ return false;
+}
+
+/*
+================
+idCollisionModelManagerLocal::CreatePolygon
+================
+*/
+void idCollisionModelManagerLocal::CreatePolygon( cm_model_t *model, idFixedWinding *w, const idPlane &plane, const idMaterial *material, int primitiveNum ) {
+ int i, j, edgeNum, v1num;
+ int numPolyEdges, polyEdges[MAX_POINTS_ON_WINDING];
+ idBounds bounds;
+ cm_polygon_t *p;
+
+ // turn the winding into a sequence of edges
+ numPolyEdges = 0;
+ v1num = -1; // first vertex unknown
+ for ( i = 0, j = 1; i < w->GetNumPoints(); i++, j++ ) {
+ if ( j >= w->GetNumPoints() ) {
+ j = 0;
+ }
+ GetEdge( model, (*w)[i].ToVec3(), (*w)[j].ToVec3(), &polyEdges[numPolyEdges], v1num );
+ if ( polyEdges[numPolyEdges] ) {
+ // last vertex of this edge is the first vertex of the next edge
+ v1num = model->edges[ abs(polyEdges[numPolyEdges]) ].vertexNum[ INTSIGNBITNOTSET(polyEdges[numPolyEdges]) ];
+ // this edge is valid so keep it
+ numPolyEdges++;
+ }
+ }
+ // should have at least 3 edges
+ if ( numPolyEdges < 3 ) {
+ return;
+ }
+ // the polygon is invalid if some edge is found twice
+ for ( i = 0; i < numPolyEdges; i++ ) {
+ for ( j = i+1; j < numPolyEdges; j++ ) {
+ if ( abs(polyEdges[i]) == abs(polyEdges[j]) ) {
+ return;
+ }
+ }
+ }
+ // don't overflow max edges
+ if ( numPolyEdges > CM_MAX_POLYGON_EDGES ) {
+ common->Warning( "idCollisionModelManagerLocal::CreatePolygon: polygon has more than %d edges", numPolyEdges );
+ numPolyEdges = CM_MAX_POLYGON_EDGES;
+ }
+
+ w->GetBounds( bounds );
+
+ p = AllocPolygon( model, numPolyEdges );
+ p->numEdges = numPolyEdges;
+ p->contents = material->GetContentFlags();
+ p->material = material;
+ p->checkcount = 0;
+ p->plane = plane;
+ p->bounds = bounds;
+ for ( i = 0; i < numPolyEdges; i++ ) {
+ edgeNum = polyEdges[i];
+ p->edges[i] = edgeNum;
+ }
+ R_FilterPolygonIntoTree( model, model->node, NULL, p );
+}
+
+/*
+================
+idCollisionModelManagerLocal::PolygonFromWinding
+
+ NOTE: for patches primitiveNum < 0 and abs(primitiveNum) is the real number
+================
+*/
+void idCollisionModelManagerLocal::PolygonFromWinding( cm_model_t *model, idFixedWinding *w, const idPlane &plane, const idMaterial *material, int primitiveNum ) {
+ int contents;
+
+ contents = material->GetContentFlags();
+
+ // if this polygon is part of the world model
+ if ( numModels == 0 ) {
+ // if the polygon is fully chopped away by the proc bsp tree
+ if ( ChoppedAwayByProcBSP( *w, plane, contents ) ) {
+ model->numRemovedPolys++;
+ return;
+ }
+ }
+
+ // get one winding that is not or only partly contained in brushes
+ w = WindingOutsideBrushes( w, plane, contents, primitiveNum, model->node );
+
+ // if the polygon is fully contained within a brush
+ if ( !w ) {
+ model->numRemovedPolys++;
+ return;
+ }
+
+ if ( w->IsHuge() ) {
+ common->Warning( "idCollisionModelManagerLocal::PolygonFromWinding: model %s primitive %d is degenerate", model->name.c_str(), abs(primitiveNum) );
+ return;
+ }
+
+ CreatePolygon( model, w, plane, material, primitiveNum );
+
+ if ( material->GetCullType() == CT_TWO_SIDED || material->ShouldCreateBackSides() ) {
+ w->ReverseSelf();
+ CreatePolygon( model, w, -plane, material, primitiveNum );
+ }
+}
+
+/*
+=================
+idCollisionModelManagerLocal::CreatePatchPolygons
+=================
+*/
+void idCollisionModelManagerLocal::CreatePatchPolygons( cm_model_t *model, idSurface_Patch &mesh, const idMaterial *material, int primitiveNum ) {
+ int i, j;
+ float dot;
+ int v1, v2, v3, v4;
+ idFixedWinding w;
+ idPlane plane;
+ idVec3 d1, d2;
+
+ for ( i = 0; i < mesh.GetWidth() - 1; i++ ) {
+ for ( j = 0; j < mesh.GetHeight() - 1; j++ ) {
+
+ v1 = j * mesh.GetWidth() + i;
+ v2 = v1 + 1;
+ v3 = v1 + mesh.GetWidth() + 1;
+ v4 = v1 + mesh.GetWidth();
+
+ d1 = mesh[v2].xyz - mesh[v1].xyz;
+ d2 = mesh[v3].xyz - mesh[v1].xyz;
+ plane.SetNormal( d1.Cross(d2) );
+ if ( plane.Normalize() != 0.0f ) {
+ plane.FitThroughPoint( mesh[v1].xyz );
+ dot = plane.Distance( mesh[v4].xyz );
+ // if we can turn it into a quad
+ if ( idMath::Fabs(dot) < 0.1f ) {
+ w.Clear();
+ w += mesh[v1].xyz;
+ w += mesh[v2].xyz;
+ w += mesh[v3].xyz;
+ w += mesh[v4].xyz;
+
+ PolygonFromWinding( model, &w, plane, material, -primitiveNum );
+ continue;
+ }
+ else {
+ // create one of the triangles
+ w.Clear();
+ w += mesh[v1].xyz;
+ w += mesh[v2].xyz;
+ w += mesh[v3].xyz;
+
+ PolygonFromWinding( model, &w, plane, material, -primitiveNum );
+ }
+ }
+ // create the other triangle
+ d1 = mesh[v3].xyz - mesh[v1].xyz;
+ d2 = mesh[v4].xyz - mesh[v1].xyz;
+ plane.SetNormal( d1.Cross(d2) );
+ if ( plane.Normalize() != 0.0f ) {
+ plane.FitThroughPoint( mesh[v1].xyz );
+
+ w.Clear();
+ w += mesh[v1].xyz;
+ w += mesh[v3].xyz;
+ w += mesh[v4].xyz;
+
+ PolygonFromWinding( model, &w, plane, material, -primitiveNum );
+ }
+ }
+ }
+}
+
+/*
+=================
+CM_EstimateVertsAndEdges
+=================
+*/
+static void CM_EstimateVertsAndEdges( const idMapEntity *mapEnt, int *numVerts, int *numEdges ) {
+ int j, width, height;
+
+ *numVerts = *numEdges = 0;
+ for ( j = 0; j < mapEnt->GetNumPrimitives(); j++ ) {
+ const idMapPrimitive *mapPrim;
+ mapPrim = mapEnt->GetPrimitive(j);
+ if ( mapPrim->GetType() == idMapPrimitive::TYPE_PATCH ) {
+ // assume maximum tesselation without adding verts
+ width = static_cast(mapPrim)->GetWidth();
+ height = static_cast(mapPrim)->GetHeight();
+ *numVerts += width * height;
+ *numEdges += (width-1) * height + width * (height-1) + (width-1) * (height-1);
+ continue;
+ }
+ if ( mapPrim->GetType() == idMapPrimitive::TYPE_BRUSH ) {
+ // assume cylinder with a polygon with (numSides - 2) edges ontop and on the bottom
+ *numVerts += (static_cast(mapPrim)->GetNumSides() - 2) * 2;
+ *numEdges += (static_cast(mapPrim)->GetNumSides() - 2) * 3;
+ continue;
+ }
+ }
+}
+
+/*
+=================
+idCollisionModelManagerLocal::ConverPatch
+=================
+*/
+void idCollisionModelManagerLocal::ConvertPatch( cm_model_t *model, const idMapPatch *patch, int primitiveNum ) {
+ const idMaterial *material;
+ idSurface_Patch *cp;
+
+ material = declManager->FindMaterial( patch->GetMaterial() );
+ if ( !( material->GetContentFlags() & CONTENTS_REMOVE_UTIL ) ) {
+ return;
+ }
+ //HUMANHEAD PCF rww 05/11/06 - can be used explicitly by surfaces which use alpha coverage but do not want collision anyway
+ if ( material->TestMaterialFlag(MF_SKIPCLIP) ) {
+ return;
+ }
+ //HUMANHEAD END
+
+ // copy the patch
+ cp = new idSurface_Patch( *patch );
+
+ // if the patch has an explicit number of subdivisions use it to avoid cracks
+ if ( patch->GetExplicitlySubdivided() ) {
+ cp->SubdivideExplicit( patch->GetHorzSubdivisions(), patch->GetVertSubdivisions(), false, true );
+ } else {
+ cp->Subdivide( DEFAULT_CURVE_MAX_ERROR_CD, DEFAULT_CURVE_MAX_ERROR_CD, DEFAULT_CURVE_MAX_LENGTH_CD, false );
+ }
+
+ // create collision polygons for the patch
+ CreatePatchPolygons( model, *cp, material, primitiveNum );
+
+ delete cp;
+}
+
+/*
+================
+idCollisionModelManagerLocal::ConvertBrushSides
+================
+*/
+void idCollisionModelManagerLocal::ConvertBrushSides( cm_model_t *model, const idMapBrush *mapBrush, int primitiveNum ) {
+ int i, j;
+ idMapBrushSide *mapSide;
+ idFixedWinding w;
+ idPlane *planes;
+ const idMaterial *material;
+
+ // fix degenerate planes
+ planes = (idPlane *) _alloca16( mapBrush->GetNumSides() * sizeof( planes[0] ) );
+ for ( i = 0; i < mapBrush->GetNumSides(); i++ ) {
+ planes[i] = mapBrush->GetSide(i)->GetPlane();
+ planes[i].FixDegeneracies( DEGENERATE_DIST_EPSILON );
+ }
+
+ // create a collision polygon for each brush side
+ for ( i = 0; i < mapBrush->GetNumSides(); i++ ) {
+ mapSide = mapBrush->GetSide(i);
+ material = declManager->FindMaterial( mapSide->GetMaterial() );
+ if ( !( material->GetContentFlags() & CONTENTS_REMOVE_UTIL ) ) {
+ continue;
+ }
+ w.BaseForPlane( -planes[i] );
+ for ( j = 0; j < mapBrush->GetNumSides() && w.GetNumPoints(); j++ ) {
+ if ( i == j ) {
+ continue;
+ }
+ w.ClipInPlace( -planes[j], 0 );
+ }
+
+ if ( w.GetNumPoints() ) {
+ PolygonFromWinding( model, &w, planes[i], material, primitiveNum );
+ }
+ }
+}
+
+/*
+================
+idCollisionModelManagerLocal::ConvertBrush
+================
+*/
+void idCollisionModelManagerLocal::ConvertBrush( cm_model_t *model, const idMapBrush *mapBrush, int primitiveNum ) {
+ int i, j, contents;
+ idBounds bounds;
+ idMapBrushSide *mapSide;
+ cm_brush_t *brush;
+ idPlane *planes;
+ idFixedWinding w;
+ const idMaterial *material = NULL;
+
+ contents = 0;
+ bounds.Clear();
+
+ // fix degenerate planes
+ planes = (idPlane *) _alloca16( mapBrush->GetNumSides() * sizeof( planes[0] ) );
+ for ( i = 0; i < mapBrush->GetNumSides(); i++ ) {
+ planes[i] = mapBrush->GetSide(i)->GetPlane();
+ planes[i].FixDegeneracies( DEGENERATE_DIST_EPSILON );
+ }
+
+ // we are only getting the bounds for the brush so there's no need
+ // to create a winding for the last brush side
+ for ( i = 0; i < mapBrush->GetNumSides() - 1; i++ ) {
+ mapSide = mapBrush->GetSide(i);
+ material = declManager->FindMaterial( mapSide->GetMaterial() );
+ contents |= ( material->GetContentFlags() & CONTENTS_REMOVE_UTIL );
+ w.BaseForPlane( -planes[i] );
+ for ( j = 0; j < mapBrush->GetNumSides() && w.GetNumPoints(); j++ ) {
+ if ( i == j ) {
+ continue;
+ }
+ w.ClipInPlace( -planes[j], 0 );
+ }
+
+ for ( j = 0; j < w.GetNumPoints(); j++ ) {
+ bounds.AddPoint( w[j].ToVec3() );
+ }
+ }
+ if ( !contents ) {
+ return;
+ }
+ // create brush for position test
+ brush = AllocBrush( model, mapBrush->GetNumSides() );
+ brush->checkcount = 0;
+ brush->contents = contents;
+ brush->material = material;
+ brush->primitiveNum = primitiveNum;
+ brush->bounds = bounds;
+ brush->numPlanes = mapBrush->GetNumSides();
+ for (i = 0; i < mapBrush->GetNumSides(); i++) {
+ brush->planes[i] = planes[i];
+ }
+ AddBrushToNode( model, model->node, brush );
+}
+
+/*
+================
+CM_CountNodeBrushes
+================
+*/
+static int CM_CountNodeBrushes( const cm_node_t *node ) {
+ int count;
+ cm_brushRef_t *bref;
+
+ count = 0;
+ for ( bref = node->brushes; bref; bref = bref->next ) {
+ count++;
+ }
+ return count;
+}
+
+/*
+================
+CM_R_GetModelBounds
+================
+*/
+static void CM_R_GetNodeBounds( idBounds *bounds, cm_node_t *node ) {
+ cm_polygonRef_t *pref;
+ cm_brushRef_t *bref;
+
+ while ( 1 ) {
+ for ( pref = node->polygons; pref; pref = pref->next ) {
+ bounds->AddPoint( pref->p->bounds[0] );
+ bounds->AddPoint( pref->p->bounds[1] );
+ }
+ for ( bref = node->brushes; bref; bref = bref->next ) {
+ bounds->AddPoint( bref->b->bounds[0] );
+ bounds->AddPoint( bref->b->bounds[1] );
+ }
+ if ( node->planeType == -1 ) {
+ break;
+ }
+ CM_R_GetNodeBounds( bounds, node->children[1] );
+ node = node->children[0];
+ }
+}
+
+/*
+================
+CM_GetNodeBounds
+================
+*/
+void CM_GetNodeBounds( idBounds *bounds, cm_node_t *node ) {
+ bounds->Clear();
+ CM_R_GetNodeBounds( bounds, node );
+ if ( bounds->IsCleared() ) {
+ bounds->Zero();
+ }
+}
+
+/*
+================
+CM_GetNodeContents
+================
+*/
+int CM_GetNodeContents( cm_node_t *node ) {
+ int contents;
+ cm_polygonRef_t *pref;
+ cm_brushRef_t *bref;
+
+ contents = 0;
+ while ( 1 ) {
+ for ( pref = node->polygons; pref; pref = pref->next ) {
+ contents |= pref->p->contents;
+ }
+ for ( bref = node->brushes; bref; bref = bref->next ) {
+ contents |= bref->b->contents;
+ }
+ if ( node->planeType == -1 ) {
+ break;
+ }
+ contents |= CM_GetNodeContents( node->children[1] );
+ node = node->children[0];
+ }
+ return contents;
+}
+
+/*
+==================
+idCollisionModelManagerLocal::RemapEdges
+==================
+*/
+void idCollisionModelManagerLocal::RemapEdges( cm_node_t *node, int *edgeRemap ) {
+ cm_polygonRef_t *pref;
+ cm_polygon_t *p;
+ int i;
+
+ while ( 1 ) {
+ for ( pref = node->polygons; pref; pref = pref->next ) {
+ p = pref->p;
+ // if we checked this polygon already
+ if ( p->checkcount == checkCount ) {
+ continue;
+ }
+ p->checkcount = checkCount;
+ for ( i = 0; i < p->numEdges; i++ ) {
+ if ( p->edges[i] < 0 ) {
+ p->edges[i] = -edgeRemap[ abs(p->edges[i]) ];
+ }
+ else {
+ p->edges[i] = edgeRemap[ p->edges[i] ];
+ }
+ }
+ }
+ if ( node->planeType == -1 ) {
+ break;
+ }
+
+ RemapEdges( node->children[1], edgeRemap );
+ node = node->children[0];
+ }
+}
+
+/*
+==================
+idCollisionModelManagerLocal::OptimizeArrays
+
+ due to polygon merging and polygon removal the vertex and edge array
+ can have a lot of unused entries.
+==================
+*/
+void idCollisionModelManagerLocal::OptimizeArrays( cm_model_t *model ) {
+ int i, newNumVertices, newNumEdges, *v;
+ int *remap;
+ cm_edge_t *oldEdges;
+ cm_vertex_t *oldVertices;
+
+ remap = (int *) Mem_ClearedAlloc( Max( model->numVertices, model->numEdges ) * sizeof( int ) );
+ // get all used vertices
+ for ( i = 0; i < model->numEdges; i++ ) {
+ remap[ model->edges[i].vertexNum[0] ] = true;
+ remap[ model->edges[i].vertexNum[1] ] = true;
+ }
+ // create remap index and move vertices
+ newNumVertices = 0;
+ for ( i = 0; i < model->numVertices; i++ ) {
+ if ( remap[ i ] ) {
+ remap[ i ] = newNumVertices;
+ model->vertices[ newNumVertices ] = model->vertices[ i ];
+ newNumVertices++;
+ }
+ }
+ model->numVertices = newNumVertices;
+ // change edge vertex indexes
+ for ( i = 1; i < model->numEdges; i++ ) {
+ v = model->edges[i].vertexNum;
+ v[0] = remap[ v[0] ];
+ v[1] = remap[ v[1] ];
+ }
+
+ // create remap index and move edges
+ newNumEdges = 1;
+ for ( i = 1; i < model->numEdges; i++ ) {
+ // if the edge is used
+ if ( model->edges[ i ].numUsers ) {
+ remap[ i ] = newNumEdges;
+ model->edges[ newNumEdges ] = model->edges[ i ];
+ newNumEdges++;
+ }
+ }
+ // change polygon edge indexes
+ checkCount++;
+ RemapEdges( model->node, remap );
+ model->numEdges = newNumEdges;
+
+ Mem_Free( remap );
+
+ // realloc vertices
+ oldVertices = model->vertices;
+ if ( oldVertices ) {
+ model->vertices = (cm_vertex_t *) Mem_ClearedAlloc( model->numVertices * sizeof(cm_vertex_t) );
+ memcpy( model->vertices, oldVertices, model->numVertices * sizeof(cm_vertex_t) );
+ Mem_Free( oldVertices );
+ }
+
+ // realloc edges
+ oldEdges = model->edges;
+ if ( oldEdges ) {
+ model->edges = (cm_edge_t *) Mem_ClearedAlloc( model->numEdges * sizeof(cm_edge_t) );
+ memcpy( model->edges, oldEdges, model->numEdges * sizeof(cm_edge_t) );
+ Mem_Free( oldEdges );
+ }
+}
+
+/*
+================
+idCollisionModelManagerLocal::FinishModel
+================
+*/
+void idCollisionModelManagerLocal::FinishModel( cm_model_t *model ) {
+ // try to merge polygons
+ checkCount++;
+ MergeTreePolygons( model, model->node );
+ // find internal edges (no mesh can ever collide with internal edges)
+ checkCount++;
+ FindInternalEdges( model, model->node );
+ // calculate edge normals
+ checkCount++;
+ CalculateEdgeNormals( model, model->node );
+
+ //common->Printf( "%s vertex hash spread is %d\n", model->name.c_str(), cm_vertexHash->GetSpread() );
+ //common->Printf( "%s edge hash spread is %d\n", model->name.c_str(), cm_edgeHash->GetSpread() );
+
+ // remove all unused vertices and edges
+ OptimizeArrays( model );
+ // get model bounds from brush and polygon bounds
+ CM_GetNodeBounds( &model->bounds, model->node );
+ // get model contents
+ model->contents = CM_GetNodeContents( model->node );
+ // total memory used by this model
+ model->usedMemory = model->numVertices * sizeof(cm_vertex_t) +
+ model->numEdges * sizeof(cm_edge_t) +
+ model->polygonMemory +
+ model->brushMemory +
+ model->numNodes * sizeof(cm_node_t) +
+ model->numPolygonRefs * sizeof(cm_polygonRef_t) +
+ model->numBrushRefs * sizeof(cm_brushRef_t);
+}
+
+/*
+================
+idCollisionModelManagerLocal::LoadRenderModel
+================
+*/
+cm_model_t *idCollisionModelManagerLocal::LoadRenderModel( const char *fileName ) {
+ int i, j;
+ idRenderModel *renderModel;
+ const modelSurface_t *surf;
+ idFixedWinding w;
+ cm_node_t *node;
+ cm_model_t *model;
+ idPlane plane;
+ idBounds bounds;
+ bool collisionSurface;
+ idStr extension;
+
+ // only load ASE and LWO models
+ idStr( fileName ).ExtractFileExtension( extension );
+ if ( ( extension.Icmp( "ase" ) != 0 ) && ( extension.Icmp( "lwo" ) != 0 ) && ( extension.Icmp( "ma" ) != 0 ) ) {
+ return NULL;
+ }
+
+ if ( !renderModelManager->CheckModel( fileName ) ) {
+ return NULL;
+ }
+
+ renderModel = renderModelManager->FindModel( fileName );
+
+ model = AllocModel();
+ model->name = fileName;
+ node = AllocNode( model, NODE_BLOCK_SIZE_SMALL );
+ node->planeType = -1;
+ model->node = node;
+
+ model->maxVertices = 0;
+ model->numVertices = 0;
+ model->maxEdges = 0;
+ model->numEdges = 0;
+
+ bounds = renderModel->Bounds( NULL );
+
+ collisionSurface = false;
+ for ( i = 0; i < renderModel->NumSurfaces(); i++ ) {
+ surf = renderModel->Surface( i );
+ if ( surf->shader->GetSurfaceFlags() & SURF_COLLISION ) {
+ collisionSurface = true;
+ }
+ }
+
+ for ( i = 0; i < renderModel->NumSurfaces(); i++ ) {
+ surf = renderModel->Surface( i );
+ // if this surface has no contents
+ if ( ! ( surf->shader->GetContentFlags() & CONTENTS_REMOVE_UTIL ) ) {
+ continue;
+ }
+ // if the model has a collision surface and this surface is not a collision surface
+ if ( collisionSurface && !( surf->shader->GetSurfaceFlags() & SURF_COLLISION ) ) {
+ continue;
+ }
+ // get max verts and edges
+ model->maxVertices += surf->geometry->numVerts;
+ model->maxEdges += surf->geometry->numIndexes;
+ }
+
+ model->vertices = (cm_vertex_t *) Mem_ClearedAlloc( model->maxVertices * sizeof(cm_vertex_t) );
+ model->edges = (cm_edge_t *) Mem_ClearedAlloc( model->maxEdges * sizeof(cm_edge_t) );
+
+ // setup hash to speed up finding shared vertices and edges
+ SetupHash();
+
+ cm_vertexHash->ResizeIndex( model->maxVertices );
+ cm_edgeHash->ResizeIndex( model->maxEdges );
+
+ ClearHash( bounds );
+
+ for ( i = 0; i < renderModel->NumSurfaces(); i++ ) {
+ surf = renderModel->Surface( i );
+ // if this surface has no contents
+ if ( ! ( surf->shader->GetContentFlags() & CONTENTS_REMOVE_UTIL ) ) {
+ continue;
+ }
+ // if the model has a collision surface and this surface is not a collision surface
+ if ( collisionSurface && !( surf->shader->GetSurfaceFlags() & SURF_COLLISION ) ) {
+ continue;
+ }
+
+ for ( j = 0; j < surf->geometry->numIndexes; j += 3 ) {
+ w.Clear();
+ w += surf->geometry->verts[ surf->geometry->indexes[ j + 2 ] ].xyz;
+ w += surf->geometry->verts[ surf->geometry->indexes[ j + 1 ] ].xyz;
+ w += surf->geometry->verts[ surf->geometry->indexes[ j + 0 ] ].xyz;
+ w.GetPlane( plane );
+ plane = -plane;
+ PolygonFromWinding( model, &w, plane, surf->shader, 1 );
+ }
+ }
+
+ // create a BSP tree for the model
+ model->node = CreateAxialBSPTree( model, model->node );
+
+ model->isConvex = false;
+
+ FinishModel( model );
+
+ // shutdown the hash
+ ShutdownHash();
+
+ common->Printf( "loaded collision model %s\n", model->name.c_str() );
+
+ return model;
+}
+
+/*
+================
+idCollisionModelManagerLocal::CollisionModelForMapEntity
+================
+*/
+cm_model_t *idCollisionModelManagerLocal::CollisionModelForMapEntity( const idMapEntity *mapEnt ) {
+ cm_model_t *model;
+ idBounds bounds;
+ const char *name;
+ int i, brushCount;
+
+ // if the entity has no primitives
+ if ( mapEnt->GetNumPrimitives() < 1 ) {
+ return NULL;
+ }
+
+ // get a name for the collision model
+ mapEnt->epairs.GetString( "model", "", &name );
+ if ( !name[0] ) {
+ mapEnt->epairs.GetString( "name", "", &name );
+ if ( !name[0] ) {
+ if ( !numModels ) {
+ // first model is always the world
+ name = "worldMap";
+ }
+ else {
+ name = "unnamed inline model";
+ }
+ }
+ }
+
+ model = AllocModel();
+ model->node = AllocNode( model, NODE_BLOCK_SIZE_SMALL );
+
+ CM_EstimateVertsAndEdges( mapEnt, &model->maxVertices, &model->maxEdges );
+ model->numVertices = 0;
+ model->numEdges = 0;
+ model->vertices = (cm_vertex_t *) Mem_ClearedAlloc( model->maxVertices * sizeof(cm_vertex_t) );
+ model->edges = (cm_edge_t *) Mem_ClearedAlloc( model->maxEdges * sizeof(cm_edge_t) );
+
+ cm_vertexHash->ResizeIndex( model->maxVertices );
+ cm_edgeHash->ResizeIndex( model->maxEdges );
+
+ model->name = name;
+ model->isConvex = false;
+
+ // convert brushes
+ for ( i = 0; i < mapEnt->GetNumPrimitives(); i++ ) {
+ idMapPrimitive *mapPrim;
+
+ mapPrim = mapEnt->GetPrimitive(i);
+ if ( mapPrim->GetType() == idMapPrimitive::TYPE_BRUSH ) {
+ ConvertBrush( model, static_cast(mapPrim), i );
+ continue;
+ }
+ }
+
+ // create an axial bsp tree for the model if it has more than just a bunch brushes
+ brushCount = CM_CountNodeBrushes( model->node );
+ if ( brushCount > 4 ) {
+ model->node = CreateAxialBSPTree( model, model->node );
+ } else {
+ model->node->planeType = -1;
+ }
+
+ // get bounds for hash
+ if ( brushCount ) {
+ CM_GetNodeBounds( &bounds, model->node );
+ } else {
+ bounds[0].Set( -256, -256, -256 );
+ bounds[1].Set( 256, 256, 256 );
+ }
+
+ // different models do not share edges and vertices with each other, so clear the hash
+ ClearHash( bounds );
+
+ // create polygons from patches and brushes
+ for ( i = 0; i < mapEnt->GetNumPrimitives(); i++ ) {
+ idMapPrimitive *mapPrim;
+
+ mapPrim = mapEnt->GetPrimitive(i);
+ if ( mapPrim->GetType() == idMapPrimitive::TYPE_PATCH ) {
+ ConvertPatch( model, static_cast(mapPrim), i );
+ continue;
+ }
+ if ( mapPrim->GetType() == idMapPrimitive::TYPE_BRUSH ) {
+ ConvertBrushSides( model, static_cast(mapPrim), i );
+ continue;
+ }
+ }
+
+ FinishModel( model );
+
+ return model;
+}
+
+/*
+================
+idCollisionModelManagerLocal::FindModel
+================
+*/
+cmHandle_t idCollisionModelManagerLocal::FindModel( const char *name ) {
+ int i;
+
+ // check if this model is already loaded
+ for ( i = 0; i < numModels; i++ ) {
+ if ( !models[i]->name.Icmp( name ) ) {
+ break;
+ }
+ }
+ // if the model is already loaded
+ if ( i < numModels ) {
+ return i;
+ }
+ return -1;
+}
+
+/*
+==================
+idCollisionModelManagerLocal::PrintModelInfo
+==================
+*/
+void idCollisionModelManagerLocal::PrintModelInfo( const cm_model_t *model ) {
+ common->Printf( "%6i vertices (%i KB)\n", model->numVertices, (model->numVertices * sizeof(cm_vertex_t))>>10 );
+ common->Printf( "%6i edges (%i KB)\n", model->numEdges, (model->numEdges * sizeof(cm_edge_t))>>10 );
+ common->Printf( "%6i polygons (%i KB)\n", model->numPolygons, model->polygonMemory>>10 );
+ common->Printf( "%6i brushes (%i KB)\n", model->numBrushes, model->brushMemory>>10 );
+ common->Printf( "%6i nodes (%i KB)\n", model->numNodes, (model->numNodes * sizeof(cm_node_t))>>10 );
+ common->Printf( "%6i polygon refs (%i KB)\n", model->numPolygonRefs, (model->numPolygonRefs * sizeof(cm_polygonRef_t))>>10 );
+ common->Printf( "%6i brush refs (%i KB)\n", model->numBrushRefs, (model->numBrushRefs * sizeof(cm_brushRef_t))>>10 );
+ common->Printf( "%6i internal edges\n", model->numInternalEdges );
+ common->Printf( "%6i sharp edges\n", model->numSharpEdges );
+ common->Printf( "%6i contained polygons removed\n", model->numRemovedPolys );
+ common->Printf( "%6i polygons merged\n", model->numMergedPolys );
+ common->Printf( "%6i KB total memory used\n", model->usedMemory>>10 );
+}
+
+/*
+================
+idCollisionModelManagerLocal::AccumulateModelInfo
+================
+*/
+void idCollisionModelManagerLocal::AccumulateModelInfo( cm_model_t *model ) {
+ int i;
+
+ memset( model, 0, sizeof( *model ) );
+ // accumulate statistics of all loaded models
+ for ( i = 0; i < numModels; i++ ) {
+ model->numVertices += models[i]->numVertices;
+ model->numEdges += models[i]->numEdges;
+ model->numPolygons += models[i]->numPolygons;
+ model->polygonMemory += models[i]->polygonMemory;
+ model->numBrushes += models[i]->numBrushes;
+ model->brushMemory += models[i]->brushMemory;
+ model->numNodes += models[i]->numNodes;
+ model->numBrushRefs += models[i]->numBrushRefs;
+ model->numPolygonRefs += models[i]->numPolygonRefs;
+ model->numInternalEdges += models[i]->numInternalEdges;
+ model->numSharpEdges += models[i]->numSharpEdges;
+ model->numRemovedPolys += models[i]->numRemovedPolys;
+ model->numMergedPolys += models[i]->numMergedPolys;
+ model->usedMemory += models[i]->usedMemory;
+ }
+}
+
+/*
+================
+idCollisionModelManagerLocal::ModelInfo
+================
+*/
+void idCollisionModelManagerLocal::ModelInfo( cmHandle_t model ) {
+ cm_model_t modelInfo;
+
+ if ( model == -1 ) {
+ AccumulateModelInfo( &modelInfo );
+ PrintModelInfo( &modelInfo );
+ return;
+ }
+ if ( model < 0 || model > MAX_SUBMODELS || model > maxModels ) {
+ common->Printf( "idCollisionModelManagerLocal::ModelInfo: invalid model handle\n" );
+ return;
+ }
+ if ( !models[model] ) {
+ common->Printf( "idCollisionModelManagerLocal::ModelInfo: invalid model\n" );
+ return;
+ }
+
+ PrintModelInfo( models[model] );
+}
+
+/*
+================
+idCollisionModelManagerLocal::ListModels
+================
+*/
+void idCollisionModelManagerLocal::ListModels( void ) {
+ int i, totalMemory;
+
+ totalMemory = 0;
+ for ( i = 0; i < numModels; i++ ) {
+ common->Printf( "%4d: %5d KB %s\n", i, (models[i]->usedMemory>>10), models[i]->name.c_str() );
+ totalMemory += models[i]->usedMemory;
+ }
+ common->Printf( "%4d KB in %d models\n", (totalMemory>>10), numModels );
+}
+
+/*
+================
+idCollisionModelManagerLocal::BuildModels
+================
+*/
+void idCollisionModelManagerLocal::BuildModels( const idMapFile *mapFile ) {
+ int i;
+ const idMapEntity *mapEnt;
+ //HUMANHEAD rww
+#if _HH_INLINED_PROC_CLIPMODELS
+ const char *clipMat;
+ anyInlinedProcClipMats = false;
+ mapEnt = mapFile->GetEntity(0);
+ if (mapEnt) {
+ mapEnt->epairs.GetString("clipStaticMaterials1", "", &clipMat);
+ if (clipMat[0]) {
+ anyInlinedProcClipMats = true;
+ }
+ }
+#endif
+ //HUMANHEAD END
+
+ idTimer timer;
+ timer.Start();
+
+ if ( !LoadCollisionModelFile( mapFile->GetName(), mapFile->GetGeometryCRC() ) ) {
+
+// HUMANHEAD pdm: Support for level appending
+#if DEATHWALK_AUTOLOAD
+ // Keep track of the first model added
+ // (there may already be collision models from the regular map loaded if this is a deathwalk map)
+ int firstModel = idCollisionModelManagerLocal::numModels;
+#endif
+// HUMANHEAD END
+
+ if ( !mapFile->GetNumEntities() ) {
+ return;
+ }
+
+ //HUMANHEAD rww
+#if _HH_INLINED_PROC_CLIPMODELS
+ if (anyInlinedProcClipMats) {
+ assert(numInlinedProcClipModels == 0 && inlinedProcClipModelMats.Num() == 0);
+ numInlinedProcClipModels = 0;
+ mapEnt = mapFile->GetEntity(0);
+ inlinedProcClipModelMats.Clear();
+ if (mapEnt) {
+ for (i = 0; 1; i++) {
+ mapEnt->epairs.GetString(va("clipStaticMaterials%i", i+1), "", &clipMat);
+ if (clipMat[0]) { //a list of materials specifying proc surfaces to be turned into clip
+ inlinedProcClipModelMats.Append(clipMat);
+ }
+ else {
+ break;
+ }
+ }
+ }
+ }
+#endif
+ //HUMANHEAD END
+
+ // load the .proc file bsp for data optimisation
+ LoadProcBSP( mapFile->GetName() );
+
+ // convert brushes and patches to collision data
+ for ( i = 0; i < mapFile->GetNumEntities(); i++ ) {
+ mapEnt = mapFile->GetEntity(i);
+
+ if ( numModels >= MAX_SUBMODELS ) {
+ common->Error( "idCollisionModelManagerLocal::BuildModels: more than %d collision models", MAX_SUBMODELS );
+ break;
+ }
+ models[numModels] = CollisionModelForMapEntity( mapEnt );
+ if ( models[ numModels] ) {
+ numModels++;
+ }
+ //HUMANHEAD rww
+#if _HH_INLINED_PROC_CLIPMODELS
+ if (numInlinedProcClipModels && numModels == PROC_CLIPMODEL_INDEX_START) {
+ numModels += numInlinedProcClipModels;
+ }
+#endif
+ //HUMANHEAD END
+ }
+
+ // free the proc bsp which is only used for data optimization
+ Mem_Free( procNodes );
+ procNodes = NULL;
+
+ // write the collision models to a file
+// HUMANHEAD pdm: Support for level appending
+#if DEATHWALK_AUTOLOAD
+ bool bAppending = true; // jmarshall session->ShouldAppendLevel() && !idStr::Icmp(mapFile->GetName(), session->GetDeathwalkMapName());
+ if (bAppending) {
+ common->Printf("Appending collision data\n");
+ // Rename the world collision model
+ if (models[firstModel]) {
+ models[firstModel]->name = "dw_worldMap";
+ }
+ }
+ WriteCollisionModelsToFile( mapFile->GetName(), firstModel, numModels, mapFile->GetGeometryCRC() );
+#else
+ WriteCollisionModelsToFile( mapFile->GetName(), 0, numModels, mapFile->GetGeometryCRC() );
+#endif
+ }
+
+ timer.Stop();
+
+ // print statistics on collision data
+ cm_model_t model;
+ AccumulateModelInfo( &model );
+ common->Printf( "collision data:\n" );
+ common->Printf( "%6i models\n", numModels );
+ PrintModelInfo( &model );
+ common->Printf( "%.0f msec to load collision data.\n", timer.Milliseconds() );
+}
+
+
+/*
+================
+idCollisionModelManagerLocal::LoadMap
+================
+*/
+void idCollisionModelManagerLocal::LoadMap( const idMapFile *mapFile ) {
+
+ if ( mapFile == NULL ) {
+ common->Error( "idCollisionModelManagerLocal::LoadMap: NULL mapFile" );
+ }
+
+ // check whether we can keep the current collision map based on the mapName and mapFileTime
+ if ( loaded ) {
+ if ( mapName.Icmp( mapFile->GetName() ) == 0 ) {
+ if ( mapFile->GetFileTime() == mapFileTime ) {
+ common->DPrintf( "Using loaded version\n" );
+ return;
+ }
+ common->DPrintf( "Reloading modified map\n" );
+ }
+ FreeMap();
+ }
+
+ // clear the collision map
+ Clear();
+
+ // models
+ maxModels = MAX_SUBMODELS;
+ numModels = 0;
+ models = (cm_model_t **) Mem_ClearedAlloc( (maxModels+1) * sizeof(cm_model_t *) );
+
+ // setup hash to speed up finding shared vertices and edges
+ SetupHash();
+
+ // setup trace model structure
+ SetupTrmModelStructure();
+
+ // build collision models
+ BuildModels( mapFile );
+
+ // save name and time stamp
+ mapName = mapFile->GetName();
+ mapFileTime = mapFile->GetFileTime();
+ loaded = true;
+
+ // shutdown the hash
+ ShutdownHash();
+}
+
+/*
+===================
+idCollisionModelManagerLocal::GetModelName
+===================
+*/
+const char *idCollisionModelManagerLocal::GetModelName( cmHandle_t model ) const {
+ if ( model < 0 || model > MAX_SUBMODELS || model >= numModels || !models[model] ) {
+ common->Printf( "idCollisionModelManagerLocal::GetModelBounds: invalid model handle\n" );
+ return "";
+ }
+ return models[model]->name.c_str();
+}
+
+/*
+===================
+idCollisionModelManagerLocal::GetModelBounds
+===================
+*/
+bool idCollisionModelManagerLocal::GetModelBounds( cmHandle_t model, idBounds &bounds ) const {
+
+ if ( model < 0 || model > MAX_SUBMODELS || model >= numModels || !models[model] ) {
+ common->Printf( "idCollisionModelManagerLocal::GetModelBounds: invalid model handle\n" );
+ return false;
+ }
+
+ bounds = models[model]->bounds;
+ return true;
+}
+
+/*
+===================
+idCollisionModelManagerLocal::GetModelContents
+===================
+*/
+bool idCollisionModelManagerLocal::GetModelContents( cmHandle_t model, int &contents ) const {
+ if ( model < 0 || model > MAX_SUBMODELS || model >= numModels || !models[model] ) {
+ common->Printf( "idCollisionModelManagerLocal::GetModelContents: invalid model handle\n" );
+ return false;
+ }
+
+ contents = models[model]->contents;
+
+ return true;
+}
+
+/*
+===================
+idCollisionModelManagerLocal::GetModelVertex
+===================
+*/
+bool idCollisionModelManagerLocal::GetModelVertex( cmHandle_t model, int vertexNum, idVec3 &vertex ) const {
+ if ( model < 0 || model > MAX_SUBMODELS || model >= numModels || !models[model] ) {
+ common->Printf( "idCollisionModelManagerLocal::GetModelVertex: invalid model handle\n" );
+ return false;
+ }
+
+ if ( vertexNum < 0 || vertexNum >= models[model]->numVertices ) {
+ common->Printf( "idCollisionModelManagerLocal::GetModelVertex: invalid vertex number\n" );
+ return false;
+ }
+
+ vertex = models[model]->vertices[vertexNum].p;
+
+ return true;
+}
+
+/*
+===================
+idCollisionModelManagerLocal::GetModelEdge
+===================
+*/
+bool idCollisionModelManagerLocal::GetModelEdge( cmHandle_t model, int edgeNum, idVec3 &start, idVec3 &end ) const {
+ if ( model < 0 || model > MAX_SUBMODELS || model >= numModels || !models[model] ) {
+ common->Printf( "idCollisionModelManagerLocal::GetModelEdge: invalid model handle\n" );
+ return false;
+ }
+
+ edgeNum = abs( edgeNum );
+ if ( edgeNum >= models[model]->numEdges ) {
+ common->Printf( "idCollisionModelManagerLocal::GetModelEdge: invalid edge number\n" );
+ return false;
+ }
+
+ start = models[model]->vertices[models[model]->edges[edgeNum].vertexNum[0]].p;
+ end = models[model]->vertices[models[model]->edges[edgeNum].vertexNum[1]].p;
+
+ return true;
+}
+
+/*
+===================
+idCollisionModelManagerLocal::GetModelPolygon
+===================
+*/
+bool idCollisionModelManagerLocal::GetModelPolygon( cmHandle_t model, int polygonNum, idFixedWinding &winding ) const {
+ int i, edgeNum;
+ cm_polygon_t *poly;
+
+ if ( model < 0 || model > MAX_SUBMODELS || model >= numModels || !models[model] ) {
+ common->Printf( "idCollisionModelManagerLocal::GetModelPolygon: invalid model handle\n" );
+ return false;
+ }
+
+ poly = *reinterpret_cast(&polygonNum);
+ winding.Clear();
+ for ( i = 0; i < poly->numEdges; i++ ) {
+ edgeNum = poly->edges[i];
+ winding += models[model]->vertices[ models[model]->edges[abs(edgeNum)].vertexNum[INTSIGNBITSET(edgeNum)] ].p;
+ }
+
+ return true;
+}
+
+/*
+==================
+idCollisionModelManagerLocal::LoadModel
+==================
+*/
+cmHandle_t idCollisionModelManagerLocal::LoadModel( const char *modelName, const bool precache ) {
+ int handle;
+
+ handle = FindModel( modelName );
+ if ( handle >= 0 ) {
+ return handle;
+ }
+
+ if ( numModels >= MAX_SUBMODELS ) {
+ common->Error( "idCollisionModelManagerLocal::LoadModel: no free slots\n" );
+ return 0;
+ }
+
+ // try to load a .cm file
+ if ( LoadCollisionModelFile( modelName, 0 ) ) {
+ handle = FindModel( modelName );
+ if ( handle >= 0 ) {
+ return handle;
+ } else {
+ common->Warning( "idCollisionModelManagerLocal::LoadModel: collision file for '%s' contains different model", modelName );
+ }
+ }
+
+ // if only precaching .cm files do not waste memory converting render models
+ if ( precache ) {
+ return 0;
+ }
+
+ // try to load a .ASE or .LWO model and convert it to a collision model
+ models[numModels] = LoadRenderModel( modelName );
+ if ( models[numModels] != NULL ) {
+ numModels++;
+ return ( numModels - 1 );
+ }
+
+ return 0;
+}
+
+/*
+==================
+idCollisionModelManagerLocal::TrmFromModel_r
+==================
+*/
+bool idCollisionModelManagerLocal::TrmFromModel_r( idTraceModel &trm, cm_node_t *node ) {
+ cm_polygonRef_t *pref;
+ cm_polygon_t *p;
+ int i;
+
+ while ( 1 ) {
+ for ( pref = node->polygons; pref; pref = pref->next ) {
+ p = pref->p;
+
+ if ( p->checkcount == checkCount ) {
+ continue;
+ }
+
+ p->checkcount = checkCount;
+
+ if ( trm.numPolys >= MAX_TRACEMODEL_POLYS ) {
+ return false;
+ }
+ // copy polygon properties
+ trm.polys[ trm.numPolys ].bounds = p->bounds;
+ trm.polys[ trm.numPolys ].normal = p->plane.Normal();
+ trm.polys[ trm.numPolys ].dist = p->plane.Dist();
+ trm.polys[ trm.numPolys ].numEdges = p->numEdges;
+ // copy edge index
+ for ( i = 0; i < p->numEdges; i++ ) {
+ trm.polys[ trm.numPolys ].edges[ i ] = p->edges[ i ];
+ }
+ trm.numPolys++;
+ }
+ if ( node->planeType == -1 ) {
+ break;
+ }
+ if ( !TrmFromModel_r( trm, node->children[1] ) ) {
+ return false;
+ }
+ node = node->children[0];
+ }
+ return true;
+}
+
+/*
+==================
+idCollisionModelManagerLocal::TrmFromModel
+
+ NOTE: polygon merging can merge colinear edges and as such might cause dangling edges.
+==================
+*/
+bool idCollisionModelManagerLocal::TrmFromModel( const cm_model_t *model, idTraceModel &trm ) {
+ int i, j, numEdgeUsers[MAX_TRACEMODEL_EDGES+1];
+
+ // if the model has too many vertices to fit in a trace model
+ if ( model->numVertices > MAX_TRACEMODEL_VERTS ) {
+ common->Printf( "idCollisionModelManagerLocal::TrmFromModel: model %s has too many vertices.\n", model->name.c_str() );
+ PrintModelInfo( model );
+ return false;
+ }
+
+ // plus one because the collision model accounts for the first unused edge
+ if ( model->numEdges > MAX_TRACEMODEL_EDGES+1 ) {
+ common->Printf( "idCollisionModelManagerLocal::TrmFromModel: model %s has too many edges.\n", model->name.c_str() );
+ PrintModelInfo( model );
+ return false;
+ }
+
+ trm.type = TRM_CUSTOM;
+ trm.numVerts = 0;
+ trm.numEdges = 1;
+ trm.numPolys = 0;
+ trm.bounds.Clear();
+
+ // copy polygons
+ checkCount++;
+ if ( !TrmFromModel_r( trm, model->node ) ) {
+ common->Printf( "idCollisionModelManagerLocal::TrmFromModel: model %s has too many polygons.\n", model->name.c_str() );
+ PrintModelInfo( model );
+ return false;
+ }
+
+ // copy vertices
+ for ( i = 0; i < model->numVertices; i++ ) {
+ trm.verts[ i ] = model->vertices[ i ].p;
+ trm.bounds.AddPoint( trm.verts[ i ] );
+ }
+ trm.numVerts = model->numVertices;
+
+ // copy edges
+ for ( i = 0; i < model->numEdges; i++ ) {
+ trm.edges[ i ].v[0] = model->edges[ i ].vertexNum[0];
+ trm.edges[ i ].v[1] = model->edges[ i ].vertexNum[1];
+ }
+ // minus one because the collision model accounts for the first unused edge
+ trm.numEdges = model->numEdges - 1;
+
+ // each edge should be used exactly twice
+ memset( numEdgeUsers, 0, sizeof(numEdgeUsers) );
+ for ( i = 0; i < trm.numPolys; i++ ) {
+ for ( j = 0; j < trm.polys[i].numEdges; j++ ) {
+ numEdgeUsers[ abs( trm.polys[i].edges[j] ) ]++;
+ }
+ }
+ for ( i = 1; i <= trm.numEdges; i++ ) {
+ if ( numEdgeUsers[i] != 2 ) {
+ common->Printf( "idCollisionModelManagerLocal::TrmFromModel: model %s has dangling edges, the model has to be an enclosed hull.\n", model->name.c_str() );
+ PrintModelInfo( model );
+ return false;
+ }
+ }
+
+ // assume convex
+ trm.isConvex = true;
+ // check if really convex
+ for ( i = 0; i < trm.numPolys; i++ ) {
+ // to be convex no vertices should be in front of any polygon plane
+ for ( j = 0; j < trm.numVerts; j++ ) {
+ if ( trm.polys[ i ].normal * trm.verts[ j ] - trm.polys[ i ].dist > 0.01f ) {
+ trm.isConvex = false;
+ break;
+ }
+ }
+ if ( j < trm.numVerts ) {
+ break;
+ }
+ }
+
+ // offset to center of model
+ trm.offset = trm.bounds.GetCenter();
+
+ trm.GenerateEdgeNormals();
+
+ return true;
+}
+
+/*
+==================
+idCollisionModelManagerLocal::TrmFromModel
+==================
+*/
+bool idCollisionModelManagerLocal::TrmFromModel( const char *modelName, idTraceModel &trm ) {
+ cmHandle_t handle;
+
+ handle = LoadModel( modelName, false );
+ if ( !handle ) {
+ common->Printf( "idCollisionModelManagerLocal::TrmFromModel: model %s not found.\n", modelName );
+ return false;
+ }
+
+ return TrmFromModel( models[ handle ], trm );
+}
+
+// HUMANHEAD pdm: Support for level appending
+#if DEATHWALK_AUTOLOAD
+bool idCollisionModelManagerLocal::WillUseAlreadyLoadedCollisionMap(const idMapFile *mapFile) {
+ // Based on some code from idCollisionModelManagerLocal::LoadMap
+ return (idCollisionModelManagerLocal::loaded &&
+ idCollisionModelManagerLocal::mapName.Icmp( mapFile->GetName() ) == 0 &&
+ mapFile->GetFileTime() == idCollisionModelManagerLocal::mapFileTime);
+}
+void idCollisionModelManagerLocal::AppendMap( const idMapFile *mapFile ) {
+ // build collision models for our appended map
+ if (mapFile) {
+ idCollisionModelManagerLocal::BuildModels( mapFile );
+ }
+}
+#endif
+// HUMANHEAD END
+
+//HUMANHEAD rww
+#if _HH_INLINED_PROC_CLIPMODELS
+int idCollisionModelManagerLocal::GetNumInlinedProcClipModels(void) {
+ return numInlinedProcClipModels;
+}
+#endif
+//HUMANHEAD END
diff --git a/neo/cm/PreyCM/CollisionModel_local.h b/neo/cm/PreyCM/CollisionModel_local.h
new file mode 100644
index 00000000..f9bc0b5b
--- /dev/null
+++ b/neo/cm/PreyCM/CollisionModel_local.h
@@ -0,0 +1,527 @@
+
+/*
+===============================================================================
+
+ Trace model vs. polygonal model collision detection.
+
+===============================================================================
+*/
+
+#include "CollisionModel.h"
+
+#define MIN_NODE_SIZE 64.0f
+#define MAX_NODE_POLYGONS 128
+#define CM_MAX_POLYGON_EDGES 64
+#define CIRCLE_APPROXIMATION_LENGTH 64.0f
+
+#define MAX_SUBMODELS 2048
+#define TRACE_MODEL_HANDLE MAX_SUBMODELS
+
+#define VERTEX_HASH_BOXSIZE (1<<6) // must be power of 2
+#define VERTEX_HASH_SIZE (VERTEX_HASH_BOXSIZE*VERTEX_HASH_BOXSIZE)
+#define EDGE_HASH_SIZE (1<<14)
+
+#define NODE_BLOCK_SIZE_SMALL 8
+#define NODE_BLOCK_SIZE_LARGE 256
+#define REFERENCE_BLOCK_SIZE_SMALL 8
+#define REFERENCE_BLOCK_SIZE_LARGE 256
+
+#define MAX_WINDING_LIST 128 // quite a few are generated at times
+#define INTEGRAL_EPSILON 0.01f
+#define VERTEX_EPSILON 0.1f
+#define CHOP_EPSILON 0.1f
+
+
+typedef struct cm_windingList_s {
+ int numWindings; // number of windings
+ idFixedWinding w[MAX_WINDING_LIST]; // windings
+ idVec3 normal; // normal for all windings
+ idBounds bounds; // bounds of all windings in list
+ idVec3 origin; // origin for radius
+ float radius; // radius relative to origin for all windings
+ int contents; // winding surface contents
+ int primitiveNum; // number of primitive the windings came from
+} cm_windingList_t;
+
+/*
+===============================================================================
+
+Collision model
+
+===============================================================================
+*/
+
+typedef struct cm_vertex_s {
+ idVec3 p; // vertex point
+ int checkcount; // for multi-check avoidance
+ unsigned long side; // each bit tells at which side this vertex passes one of the trace model edges
+ unsigned long sideSet; // each bit tells if sidedness for the trace model edge has been calculated yet
+} cm_vertex_t;
+
+typedef struct cm_edge_s {
+ int checkcount; // for multi-check avoidance
+ unsigned short internal; // a trace model can never collide with internal edges
+ unsigned short numUsers; // number of polygons using this edge
+ unsigned long side; // each bit tells at which side of this edge one of the trace model vertices passes
+ unsigned long sideSet; // each bit tells if sidedness for the trace model vertex has been calculated yet
+ int vertexNum[2]; // start and end point of edge
+ idVec3 normal; // edge normal
+} cm_edge_t;
+
+typedef struct cm_polygonBlock_s {
+ int bytesRemaining;
+ byte * next;
+} cm_polygonBlock_t;
+
+typedef struct cm_polygon_s {
+ idBounds bounds; // polygon bounds
+ int checkcount; // for multi-check avoidance
+ int contents; // contents behind polygon
+ const idMaterial * material; // material
+ idPlane plane; // polygon plane
+ int numEdges; // number of edges
+ int edges[1]; // variable sized, indexes into cm_edge_t list
+} cm_polygon_t;
+
+typedef struct cm_polygonRef_s {
+ cm_polygon_t * p; // pointer to polygon
+ struct cm_polygonRef_s *next; // next polygon in chain
+} cm_polygonRef_t;
+
+typedef struct cm_polygonRefBlock_s {
+ cm_polygonRef_t * nextRef; // next polygon reference in block
+ struct cm_polygonRefBlock_s *next; // next block with polygon references
+} cm_polygonRefBlock_t;
+
+typedef struct cm_brushBlock_s {
+ int bytesRemaining;
+ byte * next;
+} cm_brushBlock_t;
+
+typedef struct cm_brush_s {
+ int checkcount; // for multi-check avoidance
+ idBounds bounds; // brush bounds
+ int contents; // contents of brush
+ const idMaterial * material; // material
+ int primitiveNum; // number of brush primitive
+ int numPlanes; // number of bounding planes
+ idPlane planes[1]; // variable sized
+} cm_brush_t;
+
+typedef struct cm_brushRef_s {
+ cm_brush_t * b; // pointer to brush
+ struct cm_brushRef_s * next; // next brush in chain
+} cm_brushRef_t;
+
+typedef struct cm_brushRefBlock_s {
+ cm_brushRef_t * nextRef; // next brush reference in block
+ struct cm_brushRefBlock_s *next; // next block with brush references
+} cm_brushRefBlock_t;
+
+typedef struct cm_node_s {
+ int planeType; // node axial plane type
+ float planeDist; // node plane distance
+ cm_polygonRef_t * polygons; // polygons in node
+ cm_brushRef_t * brushes; // brushes in node
+ struct cm_node_s * parent; // parent of this node
+ struct cm_node_s * children[2]; // node children
+} cm_node_t;
+
+typedef struct cm_nodeBlock_s {
+ cm_node_t * nextNode; // next node in block
+ struct cm_nodeBlock_s *next; // next block with nodes
+} cm_nodeBlock_t;
+
+typedef struct cm_model_s {
+ idStr name; // model name
+ idBounds bounds; // model bounds
+ int contents; // all contents of the model ored together
+ bool isConvex; // set if model is convex
+ // model geometry
+ int maxVertices; // size of vertex array
+ int numVertices; // number of vertices
+ cm_vertex_t * vertices; // array with all vertices used by the model
+ int maxEdges; // size of edge array
+ int numEdges; // number of edges
+ cm_edge_t * edges; // array with all edges used by the model
+ cm_node_t * node; // first node of spatial subdivision
+ // blocks with allocated memory
+ cm_nodeBlock_t * nodeBlocks; // list with blocks of nodes
+ cm_polygonRefBlock_t * polygonRefBlocks; // list with blocks of polygon references
+ cm_brushRefBlock_t * brushRefBlocks; // list with blocks of brush references
+ cm_polygonBlock_t * polygonBlock; // memory block with all polygons
+ cm_brushBlock_t * brushBlock; // memory block with all brushes
+ // statistics
+ int numPolygons;
+ int polygonMemory;
+ int numBrushes;
+ int brushMemory;
+ int numNodes;
+ int numBrushRefs;
+ int numPolygonRefs;
+ int numInternalEdges;
+ int numSharpEdges;
+ int numRemovedPolys;
+ int numMergedPolys;
+ int usedMemory;
+} cm_model_t;
+
+/*
+===============================================================================
+
+Data used during collision detection calculations
+
+===============================================================================
+*/
+
+typedef struct cm_trmVertex_s {
+ int used; // true if this vertex is used for collision detection
+ idVec3 p; // vertex position
+ idVec3 endp; // end point of vertex after movement
+ int polygonSide; // side of polygon this vertex is on (rotational collision)
+ idPluecker pl; // pluecker coordinate for vertex movement
+ idVec3 rotationOrigin; // rotation origin for this vertex
+ idBounds rotationBounds; // rotation bounds for this vertex
+} cm_trmVertex_t;
+
+typedef struct cm_trmEdge_s {
+ int used; // true when vertex is used for collision detection
+ idVec3 start; // start of edge
+ idVec3 end; // end of edge
+ int vertexNum[2]; // indexes into cm_traceWork_t->vertices
+ idPluecker pl; // pluecker coordinate for edge
+ idVec3 cross; // (z,-y,x) of cross product between edge dir and movement dir
+ idBounds rotationBounds; // rotation bounds for this edge
+ idPluecker plzaxis; // pluecker coordinate for rotation about the z-axis
+ unsigned short bitNum; // vertex bit number
+} cm_trmEdge_t;
+
+typedef struct cm_trmPolygon_s {
+ int used;
+ idPlane plane; // polygon plane
+ int numEdges; // number of edges
+ int edges[MAX_TRACEMODEL_POLYEDGES]; // index into cm_traceWork_t->edges
+ idBounds rotationBounds; // rotation bounds for this polygon
+} cm_trmPolygon_t;
+
+typedef struct cm_traceWork_s {
+ int numVerts;
+ cm_trmVertex_t vertices[MAX_TRACEMODEL_VERTS]; // trm vertices
+ int numEdges;
+ cm_trmEdge_t edges[MAX_TRACEMODEL_EDGES+1]; // trm edges
+ int numPolys;
+ cm_trmPolygon_t polys[MAX_TRACEMODEL_POLYS]; // trm polygons
+ cm_model_t *model; // model colliding with
+ idVec3 start; // start of trace
+ idVec3 end; // end of trace
+ idVec3 dir; // trace direction
+ idBounds bounds; // bounds of full trace
+ idBounds size; // bounds of transformed trm relative to start
+ idVec3 extents; // largest of abs(size[0]) and abs(size[1]) for BSP trace
+ int contents; // ignore polygons that do not have any of these contents flags
+ trace_t trace; // collision detection result
+
+ bool rotation; // true if calculating rotational collision
+ bool pointTrace; // true if only tracing a point
+ bool positionTest; // true if not tracing but doing a position test
+ bool isConvex; // true if the trace model is convex
+ bool axisIntersectsTrm; // true if the rotation axis intersects the trace model
+ bool getContacts; // true if retrieving contacts
+ bool quickExit; // set to quickly stop the collision detection calculations
+
+ idVec3 origin; // origin of rotation in model space
+ idVec3 axis; // rotation axis in model space
+ idMat3 matrix; // rotates axis of rotation to the z-axis
+ float angle; // angle for rotational collision
+ float maxTan; // max tangent of half the positive angle used instead of fraction
+ float radius; // rotation radius of trm start
+ idRotation modelVertexRotation; // inverse rotation for model vertices
+
+ contactInfo_t *contacts; // array with contacts
+ int maxContacts; // max size of contact array
+ int numContacts; // number of contacts found
+
+ idPlane heartPlane1; // polygons should be near anough the trace heart planes
+ float maxDistFromHeartPlane1;
+ idPlane heartPlane2;
+ float maxDistFromHeartPlane2;
+ idPluecker polygonEdgePlueckerCache[CM_MAX_POLYGON_EDGES];
+ idPluecker polygonVertexPlueckerCache[CM_MAX_POLYGON_EDGES];
+ idVec3 polygonRotationOriginCache[CM_MAX_POLYGON_EDGES];
+} cm_traceWork_t;
+
+/*
+===============================================================================
+
+Collision Map
+
+===============================================================================
+*/
+
+typedef struct cm_procNode_s {
+ idPlane plane;
+ int children[2]; // negative numbers are (-1 - areaNumber), 0 = solid
+} cm_procNode_t;
+
+class idCollisionModelManagerLocal : public idCollisionModelManager {
+public:
+ // load collision models from a map file
+ void LoadMap( const idMapFile *mapFile );
+
+// HUMANHEAD pdm: Support for level appending
+ virtual const char * ContentsName(const int contents) const { return StringFromContents(contents); }
+#if DEATHWALK_AUTOLOAD
+ virtual void AppendMap( const idMapFile *mapFile );
+ virtual bool WillUseAlreadyLoadedCollisionMap(const idMapFile *mapFile);
+#endif
+// HUMANHEAD END
+
+ // frees all the collision models
+ void FreeMap( void );
+
+ // get clip handle for model
+ cmHandle_t LoadModel( const char *modelName, const bool precache );
+ // sets up a trace model for collision with other trace models
+ cmHandle_t SetupTrmModel( const idTraceModel &trm, const idMaterial *material );
+ // create trace model from a collision model, returns true if succesfull
+ bool TrmFromModel( const char *modelName, idTraceModel &trm );
+
+ // name of the model
+ const char * GetModelName( cmHandle_t model ) const;
+ // bounds of the model
+ bool GetModelBounds( cmHandle_t model, idBounds &bounds ) const;
+ // all contents flags of brushes and polygons ored together
+ bool GetModelContents( cmHandle_t model, int &contents ) const;
+ // get the vertex of a model
+ bool GetModelVertex( cmHandle_t model, int vertexNum, idVec3 &vertex ) const;
+ // get the edge of a model
+ bool GetModelEdge( cmHandle_t model, int edgeNum, idVec3 &start, idVec3 &end ) const;
+ // get the polygon of a model
+ bool GetModelPolygon( cmHandle_t model, int polygonNum, idFixedWinding &winding ) const;
+
+ // translates a trm and reports the first collision if any
+ void Translation( trace_t *results, const idVec3 &start, const idVec3 &end,
+ const idTraceModel *trm, const idMat3 &trmAxis, int contentMask,
+ cmHandle_t model, const idVec3 &modelOrigin, const idMat3 &modelAxis );
+ // rotates a trm and reports the first collision if any
+ void Rotation( trace_t *results, const idVec3 &start, const idRotation &rotation,
+ const idTraceModel *trm, const idMat3 &trmAxis, int contentMask,
+ cmHandle_t model, const idVec3 &modelOrigin, const idMat3 &modelAxis );
+ // returns the contents the trm is stuck in or 0 if the trm is in free space
+ int Contents( const idVec3 &start,
+ const idTraceModel *trm, const idMat3 &trmAxis, int contentMask,
+ cmHandle_t model, const idVec3 &modelOrigin, const idMat3 &modelAxis );
+ // stores all contact points of the trm with the model, returns the number of contacts
+ int Contacts( contactInfo_t *contacts, const int maxContacts, const idVec3 &start, const idVec6 &dir, const float depth,
+ const idTraceModel *trm, const idMat3 &trmAxis, int contentMask,
+ cmHandle_t model, const idVec3 &modelOrigin, const idMat3 &modelAxis );
+ // test collision detection
+ void DebugOutput( const idVec3 &origin );
+ // draw a model
+ void DrawModel( cmHandle_t model, const idVec3 &origin, const idMat3 &axis,
+ const idVec3 &viewOrigin, const float radius );
+ // print model information, use -1 handle for accumulated model info
+ void ModelInfo( cmHandle_t model );
+ // list all loaded models
+ void ListModels( void );
+ // write a collision model file for the map entity
+ bool WriteCollisionModelForMapEntity( const idMapEntity *mapEnt, const char *filename, const bool testTraceModel = true );
+
+ //HUMANHEAD rww
+#if _HH_INLINED_PROC_CLIPMODELS
+ int GetNumInlinedProcClipModels(void);
+#endif
+ //HUMANHEAD END
+
+private: // CollisionMap_translate.cpp
+ int TranslateEdgeThroughEdge( idVec3 &cross, idPluecker &l1, idPluecker &l2, float *fraction );
+ void TranslateTrmEdgeThroughPolygon( cm_traceWork_t *tw, cm_polygon_t *poly, cm_trmEdge_t *trmEdge );
+ void TranslateTrmVertexThroughPolygon( cm_traceWork_t *tw, cm_polygon_t *poly, cm_trmVertex_t *v, int bitNum );
+ void TranslatePointThroughPolygon( cm_traceWork_t *tw, cm_polygon_t *poly, cm_trmVertex_t *v );
+ void TranslateVertexThroughTrmPolygon( cm_traceWork_t *tw, cm_trmPolygon_t *trmpoly, cm_polygon_t *poly, cm_vertex_t *v, idVec3 &endp, idPluecker &pl );
+ bool TranslateTrmThroughPolygon( cm_traceWork_t *tw, cm_polygon_t *p );
+ void SetupTranslationHeartPlanes( cm_traceWork_t *tw );
+ void SetupTrm( cm_traceWork_t *tw, const idTraceModel *trm );
+
+private: // CollisionMap_rotate.cpp
+ int CollisionBetweenEdgeBounds( cm_traceWork_t *tw, const idVec3 &va, const idVec3 &vb,
+ const idVec3 &vc, const idVec3 &vd, float tanHalfAngle,
+ idVec3 &collisionPoint, idVec3 &collisionNormal );
+ int RotateEdgeThroughEdge( cm_traceWork_t *tw, const idPluecker &pl1,
+ const idVec3 &vc, const idVec3 &vd,
+ const float minTan, float &tanHalfAngle );
+ int EdgeFurthestFromEdge( cm_traceWork_t *tw, const idPluecker &pl1,
+ const idVec3 &vc, const idVec3 &vd,
+ float &tanHalfAngle, float &dir );
+ void RotateTrmEdgeThroughPolygon( cm_traceWork_t *tw, cm_polygon_t *poly, cm_trmEdge_t *trmEdge );
+ int RotatePointThroughPlane( const cm_traceWork_t *tw, const idVec3 &point, const idPlane &plane,
+ const float angle, const float minTan, float &tanHalfAngle );
+ int PointFurthestFromPlane( const cm_traceWork_t *tw, const idVec3 &point, const idPlane &plane,
+ const float angle, float &tanHalfAngle, float &dir );
+ int RotatePointThroughEpsilonPlane( const cm_traceWork_t *tw, const idVec3 &point, const idVec3 &endPoint,
+ const idPlane &plane, const float angle, const idVec3 &origin,
+ float &tanHalfAngle, idVec3 &collisionPoint, idVec3 &endDir );
+ void RotateTrmVertexThroughPolygon( cm_traceWork_t *tw, cm_polygon_t *poly, cm_trmVertex_t *v, int vertexNum);
+ void RotateVertexThroughTrmPolygon( cm_traceWork_t *tw, cm_trmPolygon_t *trmpoly, cm_polygon_t *poly,
+ cm_vertex_t *v, idVec3 &rotationOrigin );
+ bool RotateTrmThroughPolygon( cm_traceWork_t *tw, cm_polygon_t *p );
+ void BoundsForRotation( const idVec3 &origin, const idVec3 &axis, const idVec3 &start, const idVec3 &end, idBounds &bounds );
+ void Rotation180( trace_t *results, const idVec3 &rorg, const idVec3 &axis,
+ const float startAngle, const float endAngle, const idVec3 &start,
+ const idTraceModel *trm, const idMat3 &trmAxis, int contentMask,
+ cmHandle_t model, const idVec3 &origin, const idMat3 &modelAxis );
+
+private: // CollisionMap_contents.cpp
+ bool TestTrmVertsInBrush( cm_traceWork_t *tw, cm_brush_t *b );
+ bool TestTrmInPolygon( cm_traceWork_t *tw, cm_polygon_t *p );
+ cm_node_t * PointNode( const idVec3 &p, cm_model_t *model );
+ int PointContents( const idVec3 p, cmHandle_t model );
+ int TransformedPointContents( const idVec3 &p, cmHandle_t model, const idVec3 &origin, const idMat3 &modelAxis );
+ int ContentsTrm( trace_t *results, const idVec3 &start,
+ const idTraceModel *trm, const idMat3 &trmAxis, int contentMask,
+ cmHandle_t model, const idVec3 &modelOrigin, const idMat3 &modelAxis );
+
+private: // CollisionMap_trace.cpp
+ void TraceTrmThroughNode( cm_traceWork_t *tw, cm_node_t *node );
+ void TraceThroughAxialBSPTree_r( cm_traceWork_t *tw, cm_node_t *node, float p1f, float p2f, idVec3 &p1, idVec3 &p2);
+ void TraceThroughModel( cm_traceWork_t *tw );
+ void RecurseProcBSP_r( trace_t *results, int parentNodeNum, int nodeNum, float p1f, float p2f, const idVec3 &p1, const idVec3 &p2 );
+
+private: // CollisionMap_load.cpp
+ void Clear( void );
+ void FreeTrmModelStructure( void );
+ // model deallocation
+ void RemovePolygonReferences_r( cm_node_t *node, cm_polygon_t *p );
+ void RemoveBrushReferences_r( cm_node_t *node, cm_brush_t *b );
+ void FreeNode( cm_node_t *node );
+ void FreePolygonReference( cm_polygonRef_t *pref );
+ void FreeBrushReference( cm_brushRef_t *bref );
+ void FreePolygon( cm_model_t *model, cm_polygon_t *poly );
+ void FreeBrush( cm_model_t *model, cm_brush_t *brush );
+ void FreeTree_r( cm_model_t *model, cm_node_t *headNode, cm_node_t *node );
+ void FreeModel( cm_model_t *model );
+ // merging polygons
+ void ReplacePolygons( cm_model_t *model, cm_node_t *node, cm_polygon_t *p1, cm_polygon_t *p2, cm_polygon_t *newp );
+ cm_polygon_t * TryMergePolygons( cm_model_t *model, cm_polygon_t *p1, cm_polygon_t *p2 );
+ bool MergePolygonWithTreePolygons( cm_model_t *model, cm_node_t *node, cm_polygon_t *polygon );
+ void MergeTreePolygons( cm_model_t *model, cm_node_t *node );
+ // finding internal edges
+ bool PointInsidePolygon( cm_model_t *model, cm_polygon_t *p, idVec3 &v );
+ void FindInternalEdgesOnPolygon( cm_model_t *model, cm_polygon_t *p1, cm_polygon_t *p2 );
+ void FindInternalPolygonEdges( cm_model_t *model, cm_node_t *node, cm_polygon_t *polygon );
+ void FindInternalEdges( cm_model_t *model, cm_node_t *node );
+ void FindContainedEdges( cm_model_t *model, cm_polygon_t *p );
+ // loading of proc BSP tree
+ void ParseProcNodes( idLexer *src );
+ //HUMANHEAD rww
+#if _HH_INLINED_PROC_CLIPMODELS
+ void CheckProcModelSurfClip(idLexer *src);
+#endif
+ //HUMANHEAD END
+ void LoadProcBSP( const char *name );
+ // removal of contained polygons
+ int R_ChoppedAwayByProcBSP( int nodeNum, idFixedWinding *w, const idVec3 &normal, const idVec3 &origin, const float radius );
+ int ChoppedAwayByProcBSP( const idFixedWinding &w, const idPlane &plane, int contents );
+ void ChopWindingListWithBrush( cm_windingList_t *list, cm_brush_t *b );
+ void R_ChopWindingListWithTreeBrushes( cm_windingList_t *list, cm_node_t *node );
+ idFixedWinding *WindingOutsideBrushes( idFixedWinding *w, const idPlane &plane, int contents, int patch, cm_node_t *headNode );
+ // creation of axial BSP tree
+ cm_model_t * AllocModel( void );
+ cm_node_t * AllocNode( cm_model_t *model, int blockSize );
+ cm_polygonRef_t*AllocPolygonReference( cm_model_t *model, int blockSize );
+ cm_brushRef_t * AllocBrushReference( cm_model_t *model, int blockSize );
+ cm_polygon_t * AllocPolygon( cm_model_t *model, int numEdges );
+ cm_brush_t * AllocBrush( cm_model_t *model, int numPlanes );
+ void AddPolygonToNode( cm_model_t *model, cm_node_t *node, cm_polygon_t *p );
+ void AddBrushToNode( cm_model_t *model, cm_node_t *node, cm_brush_t *b );
+ void SetupTrmModelStructure( void );
+ void R_FilterPolygonIntoTree( cm_model_t *model, cm_node_t *node, cm_polygonRef_t *pref, cm_polygon_t *p );
+ void R_FilterBrushIntoTree( cm_model_t *model, cm_node_t *node, cm_brushRef_t *pref, cm_brush_t *b );
+ cm_node_t * R_CreateAxialBSPTree( cm_model_t *model, cm_node_t *node, const idBounds &bounds );
+ cm_node_t * CreateAxialBSPTree( cm_model_t *model, cm_node_t *node );
+ // creation of raw polygons
+ void SetupHash(void);
+ void ShutdownHash(void);
+ void ClearHash( idBounds &bounds );
+ int HashVec(const idVec3 &vec);
+ int GetVertex( cm_model_t *model, const idVec3 &v, int *vertexNum );
+ int GetEdge( cm_model_t *model, const idVec3 &v1, const idVec3 &v2, int *edgeNum, int v1num );
+ void CreatePolygon( cm_model_t *model, idFixedWinding *w, const idPlane &plane, const idMaterial *material, int primitiveNum );
+ void PolygonFromWinding( cm_model_t *model, idFixedWinding *w, const idPlane &plane, const idMaterial *material, int primitiveNum );
+ void CalculateEdgeNormals( cm_model_t *model, cm_node_t *node );
+ void CreatePatchPolygons( cm_model_t *model, idSurface_Patch &mesh, const idMaterial *material, int primitiveNum );
+ void ConvertPatch( cm_model_t *model, const idMapPatch *patch, int primitiveNum );
+ void ConvertBrushSides( cm_model_t *model, const idMapBrush *mapBrush, int primitiveNum );
+ void ConvertBrush( cm_model_t *model, const idMapBrush *mapBrush, int primitiveNum );
+ void PrintModelInfo( const cm_model_t *model );
+ void AccumulateModelInfo( cm_model_t *model );
+ void RemapEdges( cm_node_t *node, int *edgeRemap );
+ void OptimizeArrays( cm_model_t *model );
+ void FinishModel( cm_model_t *model );
+ void BuildModels( const idMapFile *mapFile );
+ cmHandle_t FindModel( const char *name );
+ cm_model_t * CollisionModelForMapEntity( const idMapEntity *mapEnt ); // brush/patch model from .map
+ cm_model_t * LoadRenderModel( const char *fileName ); // ASE/LWO models
+ bool TrmFromModel_r( idTraceModel &trm, cm_node_t *node );
+ bool TrmFromModel( const cm_model_t *model, idTraceModel &trm );
+
+private: // CollisionMap_files.cpp
+ // writing
+ void WriteNodes( idFile *fp, cm_node_t *node );
+ int CountPolygonMemory( cm_node_t *node ) const;
+ void WritePolygons( idFile *fp, cm_node_t *node );
+ int CountBrushMemory( cm_node_t *node ) const;
+ void WriteBrushes( idFile *fp, cm_node_t *node );
+ void WriteCollisionModel( idFile *fp, cm_model_t *model );
+ void WriteCollisionModelsToFile( const char *filename, int firstModel, int lastModel, unsigned int mapFileCRC );
+ // loading
+ cm_node_t * ParseNodes( idLexer *src, cm_model_t *model, cm_node_t *parent );
+ void ParseVertices( idLexer *src, cm_model_t *model );
+ void ParseEdges( idLexer *src, cm_model_t *model );
+ void ParsePolygons( idLexer *src, cm_model_t *model );
+ void ParseBrushes( idLexer *src, cm_model_t *model );
+ bool ParseCollisionModel( idLexer *src );
+ bool LoadCollisionModelFile( const char *name, unsigned int mapFileCRC );
+
+private: // CollisionMap_debug
+ int ContentsFromString( const char *string ) const;
+ const char * StringFromContents( const int contents ) const;
+ void DrawEdge( cm_model_t *model, int edgeNum, const idVec3 &origin, const idMat3 &axis );
+ void DrawPolygon( cm_model_t *model, cm_polygon_t *p, const idVec3 &origin, const idMat3 &axis,
+ const idVec3 &viewOrigin );
+ void DrawNodePolygons( cm_model_t *model, cm_node_t *node, const idVec3 &origin, const idMat3 &axis,
+ const idVec3 &viewOrigin, const float radius );
+
+private: // collision map data
+ idStr mapName;
+ unsigned int mapFileTime;
+ int loaded;
+ // for multi-check avoidance
+ int checkCount;
+ // models
+ int maxModels;
+ int numModels;
+ cm_model_t ** models;
+ // polygons and brush for trm model
+ cm_polygonRef_t*trmPolygons[MAX_TRACEMODEL_POLYS];
+ cm_brushRef_t * trmBrushes[1];
+ const idMaterial *trmMaterial;
+ // for data pruning
+ int numProcNodes;
+ cm_procNode_t * procNodes;
+ // for retrieving contact points
+ bool getContacts;
+ contactInfo_t * contacts;
+ int maxContacts;
+ int numContacts;
+ //HUMANHEAD rww
+#if _HH_INLINED_PROC_CLIPMODELS
+ idList inlinedProcClipModelMats;
+ int numInlinedProcClipModels;
+ bool anyInlinedProcClipMats;
+#endif
+ //HUMANHEAD END
+};
+
+// for debugging
+extern idCVar cm_debugCollision;
diff --git a/neo/cm/PreyCM/CollisionModel_rotate.cpp b/neo/cm/PreyCM/CollisionModel_rotate.cpp
new file mode 100644
index 00000000..f8115890
--- /dev/null
+++ b/neo/cm/PreyCM/CollisionModel_rotate.cpp
@@ -0,0 +1,1672 @@
+
+/*
+===============================================================================
+
+ Trace model vs. polygonal model collision detection.
+
+===============================================================================
+*/
+
+#include "precompiled.h"
+#pragma hdrstop
+
+#include "CollisionModel_local.h"
+
+/*
+===============================================================================
+
+Collision detection for rotational motion
+
+===============================================================================
+*/
+
+// epsilon for round-off errors in epsilon calculations
+#define CM_PL_RANGE_EPSILON 1e-4f
+// if the collision point is this close to the rotation axis it is not considered a collision
+#define ROTATION_AXIS_EPSILON (CM_CLIP_EPSILON*0.25f)
+
+
+/*
+================
+CM_RotatePoint
+
+ rotates a point about an arbitrary axis using the tangent of half the rotation angle
+================
+*/
+void CM_RotatePoint( idVec3 &point, const idVec3 &origin, const idVec3 &axis, const float tanHalfAngle ) {
+ double d, t, s, c;
+ idVec3 proj, v1, v2;
+
+ point -= origin;
+ proj = axis * ( point * axis );
+ v1 = point - proj;
+ v2 = axis.Cross( v1 );
+
+ // r = tan( a / 2 );
+ // sin(a) = 2*r/(1+r*r);
+ // cos(a) = (1-r*r)/(1+r*r);
+ t = tanHalfAngle * tanHalfAngle;
+ d = 1.0f / ( 1.0f + t );
+ s = 2.0f * tanHalfAngle * d;
+ c = ( 1.0f - t ) * d;
+
+ point = v1 * c - v2 * s + proj + origin;
+}
+
+/*
+================
+CM_RotateEdge
+
+ rotates an edge about an arbitrary axis using the tangent of half the rotation angle
+================
+*/
+void CM_RotateEdge( idVec3 &start, idVec3 &end, const idVec3 &origin, const idVec3 &axis, const float tanHalfAngle ) {
+ double d, t, s, c;
+ idVec3 proj, v1, v2;
+
+ // r = tan( a / 2 );
+ // sin(a) = 2*r/(1+r*r);
+ // cos(a) = (1-r*r)/(1+r*r);
+ t = tanHalfAngle * tanHalfAngle;
+ d = 1.0f / ( 1.0f + t );
+ s = 2.0f * tanHalfAngle * d;
+ c = ( 1.0f - t ) * d;
+
+ start -= origin;
+ proj = axis * ( start * axis );
+ v1 = start - proj;
+ v2 = axis.Cross( v1 );
+ start = v1 * c - v2 * s + proj + origin;
+
+ end -= origin;
+ proj = axis * ( end * axis );
+ v1 = end - proj;
+ v2 = axis.Cross( v1 );
+ end = v1 * c - v2 * s + proj + origin;
+}
+
+/*
+================
+idCollisionModelManagerLocal::CollisionBetweenEdgeBounds
+
+ verifies if the collision of two edges occurs between the edge bounds
+ also calculates the collision point and collision plane normal if the collision occurs between the bounds
+================
+*/
+int idCollisionModelManagerLocal::CollisionBetweenEdgeBounds( cm_traceWork_t *tw, const idVec3 &va, const idVec3 &vb,
+ const idVec3 &vc, const idVec3 &vd, float tanHalfAngle,
+ idVec3 &collisionPoint, idVec3 &collisionNormal ) {
+ float d1, d2, d;
+ idVec3 at, bt, dir, dir1, dir2;
+ idPluecker pl1, pl2;
+
+ at = va;
+ bt = vb;
+ if ( tanHalfAngle != 0.0f ) {
+ CM_RotateEdge( at, bt, tw->origin, tw->axis, tanHalfAngle );
+ }
+
+ dir1 = (at - tw->origin).Cross( tw->axis );
+ dir2 = (bt - tw->origin).Cross( tw->axis );
+ if ( dir1 * dir1 > dir2 * dir2 ) {
+ dir = dir1;
+ }
+ else {
+ dir = dir2;
+ }
+ if ( tw->angle < 0.0f ) {
+ dir = -dir;
+ }
+
+ pl1.FromLine( at, bt );
+ pl2.FromRay( vc, dir );
+ d1 = pl1.PermutedInnerProduct( pl2 );
+ pl2.FromRay( vd, dir );
+ d2 = pl1.PermutedInnerProduct( pl2 );
+ if ( ( d1 > 0.0f && d2 > 0.0f ) || ( d1 < 0.0f && d2 < 0.0f ) ) {
+ return false;
+ }
+
+ pl1.FromLine( vc, vd );
+ pl2.FromRay( at, dir );
+ d1 = pl1.PermutedInnerProduct( pl2 );
+ pl2.FromRay( bt, dir );
+ d2 = pl1.PermutedInnerProduct( pl2 );
+ if ( ( d1 > 0.0f && d2 > 0.0f ) || ( d1 < 0.0f && d2 < 0.0f ) ) {
+ return false;
+ }
+
+ // collision point on the edge at-bt
+ dir1 = (vd - vc).Cross( dir );
+ d = dir1 * vc;
+ d1 = dir1 * at - d;
+ d2 = dir1 * bt - d;
+ if ( d1 == d2 ) {
+ return false;
+ }
+ collisionPoint = at + ( d1 / (d1 - d2) ) * ( bt - at );
+
+ // normal is cross product of the rotated edge va-vb and the edge vc-vd
+ collisionNormal.Cross( bt-at, vd-vc );
+
+ return true;
+}
+
+/*
+================
+idCollisionModelManagerLocal::RotateEdgeThroughEdge
+
+ calculates the tangent of half the rotation angle at which the edges collide
+================
+*/
+int idCollisionModelManagerLocal::RotateEdgeThroughEdge( cm_traceWork_t *tw, const idPluecker &pl1,
+ const idVec3 &vc, const idVec3 &vd,
+ const float minTan, float &tanHalfAngle ) {
+ double v0, v1, v2, a, b, c, d, sqrtd, q, frac1, frac2;
+ idVec3 ct, dt;
+ idPluecker pl2;
+
+ /*
+
+ a = start of line being rotated
+ b = end of line being rotated
+ pl1 = pluecker coordinate for line (a - b)
+ pl2 = pluecker coordinate for edge we might collide with (c - d)
+ t = rotation angle around the z-axis
+ solve pluecker inner product for t of rotating line a-b and line l2
+
+ // start point of rotated line during rotation
+ an[0] = a[0] * cos(t) + a[1] * sin(t)
+ an[1] = a[0] * -sin(t) + a[1] * cos(t)
+ an[2] = a[2];
+ // end point of rotated line during rotation
+ bn[0] = b[0] * cos(t) + b[1] * sin(t)
+ bn[1] = b[0] * -sin(t) + b[1] * cos(t)
+ bn[2] = b[2];
+
+ pl1[0] = a[0] * b[1] - b[0] * a[1];
+ pl1[1] = a[0] * b[2] - b[0] * a[2];
+ pl1[2] = a[0] - b[0];
+ pl1[3] = a[1] * b[2] - b[1] * a[2];
+ pl1[4] = a[2] - b[2];
+ pl1[5] = b[1] - a[1];
+
+ v[0] = (a[0] * cos(t) + a[1] * sin(t)) * (b[0] * -sin(t) + b[1] * cos(t)) - (b[0] * cos(t) + b[1] * sin(t)) * (a[0] * -sin(t) + a[1] * cos(t));
+ v[1] = (a[0] * cos(t) + a[1] * sin(t)) * b[2] - (b[0] * cos(t) + b[1] * sin(t)) * a[2];
+ v[2] = (a[0] * cos(t) + a[1] * sin(t)) - (b[0] * cos(t) + b[1] * sin(t));
+ v[3] = (a[0] * -sin(t) + a[1] * cos(t)) * b[2] - (b[0] * -sin(t) + b[1] * cos(t)) * a[2];
+ v[4] = a[2] - b[2];
+ v[5] = (b[0] * -sin(t) + b[1] * cos(t)) - (a[0] * -sin(t) + a[1] * cos(t));
+
+ pl2[0] * v[4] + pl2[1] * v[5] + pl2[2] * v[3] + pl2[4] * v[0] + pl2[5] * v[1] + pl2[3] * v[2] = 0;
+
+ v[0] = (a[0] * cos(t) + a[1] * sin(t)) * (b[0] * -sin(t) + b[1] * cos(t)) - (b[0] * cos(t) + b[1] * sin(t)) * (a[0] * -sin(t) + a[1] * cos(t));
+ v[0] = (a[1] * b[1] - a[0] * b[0]) * cos(t) * sin(t) + (a[0] * b[1] + a[1] * b[0] * cos(t)^2) - (a[1] * b[0]) - ((b[1] * a[1] - b[0] * a[0]) * cos(t) * sin(t) + (b[0] * a[1] + b[1] * a[0]) * cos(t)^2 - (b[1] * a[0]))
+ v[0] = - (a[1] * b[0]) - ( - (b[1] * a[0]))
+ v[0] = (b[1] * a[0]) - (a[1] * b[0])
+
+ v[0] = (a[0]*b[1]) - (a[1]*b[0]);
+ v[1] = (a[0]*b[2] - b[0]*a[2]) * cos(t) + (a[1]*b[2] - b[1]*a[2]) * sin(t);
+ v[2] = (a[0]-b[0]) * cos(t) + (a[1]-b[1]) * sin(t);
+ v[3] = (b[0]*a[2] - a[0]*b[2]) * sin(t) + (a[1]*b[2] - b[1]*a[2]) * cos(t);
+ v[4] = a[2] - b[2];
+ v[5] = (a[0]-b[0]) * sin(t) + (b[1]-a[1]) * cos(t);
+
+ v[0] = (a[0]*b[1]) - (a[1]*b[0]);
+ v[1] = (a[0]*b[2] - b[0]*a[2]) * cos(t) + (a[1]*b[2] - b[1]*a[2]) * sin(t);
+ v[2] = (a[0]-b[0]) * cos(t) - (b[1]-a[1]) * sin(t);
+ v[3] = (a[0]*b[2] - b[0]*a[2]) * -sin(t) + (a[1]*b[2] - b[1]*a[2]) * cos(t);
+ v[4] = a[2] - b[2];
+ v[5] = (a[0]-b[0]) * sin(t) + (b[1]-a[1]) * cos(t);
+
+ v[0] = pl1[0];
+ v[1] = pl1[1] * cos(t) + pl1[3] * sin(t);
+ v[2] = pl1[2] * cos(t) - pl1[5] * sin(t);
+ v[3] = pl1[3] * cos(t) - pl1[1] * sin(t);
+ v[4] = pl1[4];
+ v[5] = pl1[5] * cos(t) + pl1[2] * sin(t);
+
+ pl2[0] * v[4] + pl2[1] * v[5] + pl2[2] * v[3] + pl2[4] * v[0] + pl2[5] * v[1] + pl2[3] * v[2] = 0;
+
+ 0 = pl2[0] * pl1[4] +
+ pl2[1] * (pl1[5] * cos(t) + pl1[2] * sin(t)) +
+ pl2[2] * (pl1[3] * cos(t) - pl1[1] * sin(t)) +
+ pl2[4] * pl1[0] +
+ pl2[5] * (pl1[1] * cos(t) + pl1[3] * sin(t)) +
+ pl2[3] * (pl1[2] * cos(t) - pl1[5] * sin(t));
+
+ v2 * cos(t) + v1 * sin(t) + v0 = 0;
+
+ // rotation about the z-axis
+ v0 = pl2[0] * pl1[4] + pl2[4] * pl1[0];
+ v1 = pl2[1] * pl1[2] - pl2[2] * pl1[1] + pl2[5] * pl1[3] - pl2[3] * pl1[5];
+ v2 = pl2[1] * pl1[5] + pl2[2] * pl1[3] + pl2[5] * pl1[1] + pl2[3] * pl1[2];
+
+ // rotation about the x-axis
+ //v0 = pl2[3] * pl1[2] + pl2[2] * pl1[3];
+ //v1 = -pl2[5] * pl1[0] + pl2[4] * pl1[1] - pl2[1] * pl1[4] + pl2[0] * pl1[5];
+ //v2 = pl2[4] * pl1[0] + pl2[5] * pl1[1] + pl2[0] * pl1[4] + pl2[1] * pl1[5];
+
+ r = tan(t / 2);
+ sin(t) = 2*r/(1+r*r);
+ cos(t) = (1-r*r)/(1+r*r);
+
+ v1 * 2 * r / (1 + r*r) + v2 * (1 - r*r) / (1 + r*r) + v0 = 0
+ (v1 * 2 * r + v2 * (1 - r*r)) / (1 + r*r) = -v0
+ (v1 * 2 * r + v2 - v2 * r*r) / (1 + r*r) = -v0
+ v1 * 2 * r + v2 - v2 * r*r = -v0 * (1 + r*r)
+ v1 * 2 * r + v2 - v2 * r*r = -v0 + -v0 * r*r
+ (v0 - v2) * r * r + (2 * v1) * r + (v0 + v2) = 0;
+
+ MrE gives Pluecker a banana.. good monkey
+
+ */
+
+ tanHalfAngle = tw->maxTan;
+
+ // transform rotation axis to z-axis
+ ct = (vc - tw->origin) * tw->matrix;
+ dt = (vd - tw->origin) * tw->matrix;
+
+ pl2.FromLine( ct, dt );
+
+ v0 = pl2[0] * pl1[4] + pl2[4] * pl1[0];
+ v1 = pl2[1] * pl1[2] - pl2[2] * pl1[1] + pl2[5] * pl1[3] - pl2[3] * pl1[5];
+ v2 = pl2[1] * pl1[5] + pl2[2] * pl1[3] + pl2[5] * pl1[1] + pl2[3] * pl1[2];
+
+ a = v0 - v2;
+ b = v1;
+ c = v0 + v2;
+ if ( a == 0.0f ) {
+ if ( b == 0.0f ) {
+ return false;
+ }
+ frac1 = -c / ( 2.0f * b );
+ frac2 = 1e10; // = tan( idMath::HALF_PI )
+ }
+ else {
+ d = b * b - c * a;
+ if ( d <= 0.0f ) {
+ return false;
+ }
+ sqrtd = sqrt( d );
+ if ( b > 0.0f ) {
+ q = - b + sqrtd;
+ }
+ else {
+ q = - b - sqrtd;
+ }
+ frac1 = q / a;
+ if (q == 0.0f) { //HUMANHEAD rww - CUFPF
+ frac2 = 0.0f;
+ }
+ else {
+ frac2 = c / q;
+ }
+ }
+
+ if ( tw->angle < 0.0f ) {
+ frac1 = -frac1;
+ frac2 = -frac2;
+ }
+
+ // get smallest tangent for which a collision occurs
+ if ( frac1 >= minTan && frac1 < tanHalfAngle ) {
+ tanHalfAngle = frac1;
+ }
+ if ( frac2 >= minTan && frac2 < tanHalfAngle ) {
+ tanHalfAngle = frac2;
+ }
+
+ if ( tw->angle < 0.0f ) {
+ tanHalfAngle = -tanHalfAngle;
+ }
+
+ return true;
+}
+
+/*
+================
+idCollisionModelManagerLocal::EdgeFurthestFromEdge
+
+ calculates the direction of motion at the initial position, where dir < 0 means the edges move towards each other
+ if the edges move away from each other the tangent of half the rotation angle at which
+ the edges are furthest apart is also calculated
+================
+*/
+int idCollisionModelManagerLocal::EdgeFurthestFromEdge( cm_traceWork_t *tw, const idPluecker &pl1,
+ const idVec3 &vc, const idVec3 &vd,
+ float &tanHalfAngle, float &dir ) {
+ double v0, v1, v2, a, b, c, d, sqrtd, q, frac1, frac2;
+ idVec3 ct, dt;
+ idPluecker pl2;
+
+ /*
+
+ v2 * cos(t) + v1 * sin(t) + v0 = 0;
+
+ // rotation about the z-axis
+ v0 = pl2[0] * pl1[4] + pl2[4] * pl1[0];
+ v1 = pl2[1] * pl1[2] - pl2[2] * pl1[1] + pl2[5] * pl1[3] - pl2[3] * pl1[5];
+ v2 = pl2[1] * pl1[5] + pl2[2] * pl1[3] + pl2[5] * pl1[1] + pl2[3] * pl1[2];
+
+ derivative:
+ v1 * cos(t) - v2 * sin(t) = 0;
+
+ r = tan(t / 2);
+ sin(t) = 2*r/(1+r*r);
+ cos(t) = (1-r*r)/(1+r*r);
+
+ -v2 * 2 * r / (1 + r*r) + v1 * (1 - r*r)/(1+r*r);
+ -v2 * 2 * r + v1 * (1 - r*r) / (1 + r*r) = 0;
+ -v2 * 2 * r + v1 * (1 - r*r) = 0;
+ (-v1) * r * r + (-2 * v2) * r + (v1) = 0;
+
+ */
+
+ tanHalfAngle = 0.0f;
+
+ // transform rotation axis to z-axis
+ ct = (vc - tw->origin) * tw->matrix;
+ dt = (vd - tw->origin) * tw->matrix;
+
+ pl2.FromLine( ct, dt );
+
+ v0 = pl2[0] * pl1[4] + pl2[4] * pl1[0];
+ v1 = pl2[1] * pl1[2] - pl2[2] * pl1[1] + pl2[5] * pl1[3] - pl2[3] * pl1[5];
+ v2 = pl2[1] * pl1[5] + pl2[2] * pl1[3] + pl2[5] * pl1[1] + pl2[3] * pl1[2];
+
+ // get the direction of motion at the initial position
+ c = v0 + v2;
+ if ( tw->angle > 0.0f ) {
+ if ( c > 0.0f ) {
+ dir = v1;
+ }
+ else {
+ dir = -v1;
+ }
+ }
+ else {
+ if ( c > 0.0f ) {
+ dir = -v1;
+ }
+ else {
+ dir = v1;
+ }
+ }
+ // negative direction means the edges move towards each other at the initial position
+ if ( dir <= 0.0f ) {
+ return true;
+ }
+
+ a = -v1;
+ b = -v2;
+ c = v1;
+ if ( a == 0.0f ) {
+ if ( b == 0.0f ) {
+ return false;
+ }
+ frac1 = -c / ( 2.0f * b );
+ frac2 = 1e10; // = tan( idMath::HALF_PI )
+ }
+ else {
+ d = b * b - c * a;
+ if ( d <= 0.0f ) {
+ return false;
+ }
+ sqrtd = sqrt( d );
+ if ( b > 0.0f ) {
+ q = - b + sqrtd;
+ }
+ else {
+ q = - b - sqrtd;
+ }
+ frac1 = q / a;
+ frac2 = c / q;
+ }
+
+ if ( tw->angle < 0.0f ) {
+ frac1 = -frac1;
+ frac2 = -frac2;
+ }
+
+ if ( frac1 < 0.0f && frac2 < 0.0f ) {
+ return false;
+ }
+
+ if ( frac1 > frac2 ) {
+ tanHalfAngle = frac1;
+ }
+ else {
+ tanHalfAngle = frac2;
+ }
+
+ if ( tw->angle < 0.0f ) {
+ tanHalfAngle = -tanHalfAngle;
+ }
+
+ return true;
+}
+
+/*
+================
+idCollisionModelManagerLocal::RotateTrmEdgeThroughPolygon
+================
+*/
+void idCollisionModelManagerLocal::RotateTrmEdgeThroughPolygon( cm_traceWork_t *tw, cm_polygon_t *poly, cm_trmEdge_t *trmEdge ) {
+ int i, j, edgeNum;
+ float f1, f2, startTan, dir, tanHalfAngle;
+ cm_edge_t *edge;
+ cm_vertex_t *v1, *v2;
+ idVec3 collisionPoint, collisionNormal, origin, epsDir;
+ idPluecker epsPl;
+ idBounds bounds;
+
+ // if the trm is convex and the rotation axis intersects the trm
+ if ( tw->isConvex && tw->axisIntersectsTrm ) {
+ // if both points are behind the polygon the edge cannot collide within a 180 degrees rotation
+ if ( tw->vertices[trmEdge->vertexNum[0]].polygonSide & tw->vertices[trmEdge->vertexNum[1]].polygonSide ) {
+ return;
+ }
+ }
+
+ // if the trace model edge rotation bounds do not intersect the polygon bounds
+ if ( !trmEdge->rotationBounds.IntersectsBounds( poly->bounds ) ) {
+ return;
+ }
+
+ // edge rotation bounds should cross polygon plane
+ if ( trmEdge->rotationBounds.PlaneSide( poly->plane ) != SIDE_CROSS ) {
+ return;
+ }
+
+ // check edges for a collision
+ for ( i = 0; i < poly->numEdges; i++ ) {
+ edgeNum = poly->edges[i];
+ edge = tw->model->edges + abs(edgeNum);
+
+ // if this edge is already checked
+ if ( edge->checkcount == idCollisionModelManagerLocal::checkCount ) {
+ continue;
+ }
+
+ // can never collide with internal edges
+ if ( edge->internal ) {
+ continue;
+ }
+
+ v1 = tw->model->vertices + edge->vertexNum[INTSIGNBITSET(edgeNum)];
+ v2 = tw->model->vertices + edge->vertexNum[INTSIGNBITNOTSET(edgeNum)];
+
+ // edge bounds
+ for ( j = 0; j < 3; j++ ) {
+ if ( v1->p[j] > v2->p[j] ) {
+ bounds[0][j] = v2->p[j];
+ bounds[1][j] = v1->p[j];
+ }
+ else {
+ bounds[0][j] = v1->p[j];
+ bounds[1][j] = v2->p[j];
+ }
+ }
+
+ // if the trace model edge rotation bounds do not intersect the polygon edge bounds
+ if ( !trmEdge->rotationBounds.IntersectsBounds( bounds ) ) {
+ continue;
+ }
+
+ f1 = trmEdge->pl.PermutedInnerProduct( tw->polygonEdgePlueckerCache[i] );
+
+ // pluecker coordinate for epsilon expanded edge
+ epsDir = edge->normal * (CM_CLIP_EPSILON+CM_PL_RANGE_EPSILON);
+ epsPl.FromLine( tw->model->vertices[edge->vertexNum[0]].p + epsDir,
+ tw->model->vertices[edge->vertexNum[1]].p + epsDir );
+
+ f2 = trmEdge->pl.PermutedInnerProduct( epsPl );
+
+ // if the rotating edge is inbetween the polygon edge and the epsilon expanded edge
+ if ( ( f1 < 0.0f && f2 > 0.0f ) || ( f1 > 0.0f && f2 < 0.0f ) ) {
+
+ if ( !EdgeFurthestFromEdge( tw, trmEdge->plzaxis, v1->p, v2->p, startTan, dir ) ) {
+ continue;
+ }
+
+ if ( dir <= 0.0f ) {
+ // moving towards the polygon edge so stop immediately
+ tanHalfAngle = 0.0f;
+ }
+ else if ( idMath::Fabs( startTan ) >= tw->maxTan ) {
+ // never going to get beyond the start tangent during the current rotation
+ continue;
+ }
+ else {
+ // collide with the epsilon expanded edge
+ if ( !RotateEdgeThroughEdge(tw, trmEdge->plzaxis, v1->p + epsDir, v2->p + epsDir, idMath::Fabs( startTan ), tanHalfAngle ) ) {
+ tanHalfAngle = startTan;
+ }
+ }
+ }
+ else {
+ // collide with the epsilon expanded edge
+ epsDir = edge->normal * CM_CLIP_EPSILON;
+ if ( !RotateEdgeThroughEdge(tw, trmEdge->plzaxis, v1->p + epsDir, v2->p + epsDir, 0.0f, tanHalfAngle ) ) {
+ continue;
+ }
+ }
+
+ if ( idMath::Fabs( tanHalfAngle ) >= tw->maxTan ) {
+ continue;
+ }
+
+ // check if the collision is between the edge bounds
+ if ( !CollisionBetweenEdgeBounds( tw, trmEdge->start, trmEdge->end, v1->p, v2->p,
+ tanHalfAngle, collisionPoint, collisionNormal ) ) {
+ continue;
+ }
+
+ // allow rotation if the rotation axis goes through the collisionPoint
+ origin = tw->origin + tw->axis * ( tw->axis * ( collisionPoint - tw->origin ) );
+ if ( ( collisionPoint - origin ).LengthSqr() < ROTATION_AXIS_EPSILON * ROTATION_AXIS_EPSILON ) {
+ continue;
+ }
+
+ // fill in trace structure
+ tw->maxTan = idMath::Fabs( tanHalfAngle );
+ tw->trace.c.normal = collisionNormal;
+ tw->trace.c.normal.Normalize();
+ tw->trace.c.dist = tw->trace.c.normal * v1->p;
+ // make sure the collision plane faces the trace model
+ if ( (tw->trace.c.normal * trmEdge->start) - tw->trace.c.dist < 0 ) {
+ tw->trace.c.normal = -tw->trace.c.normal;
+ tw->trace.c.dist = -tw->trace.c.dist;
+ }
+ tw->trace.c.contents = poly->contents;
+ tw->trace.c.material = poly->material;
+ tw->trace.c.type = CONTACT_EDGE;
+ tw->trace.c.modelFeature = edgeNum;
+ tw->trace.c.trmFeature = trmEdge - tw->edges;
+ tw->trace.c.point = collisionPoint;
+ // if no collision can be closer
+ if ( tw->maxTan == 0.0f ) {
+ break;
+ }
+ }
+}
+
+/*
+================
+idCollisionModelManagerLocal::RotatePointThroughPlane
+
+ calculates the tangent of half the rotation angle at which the point collides with the plane
+================
+*/
+int idCollisionModelManagerLocal::RotatePointThroughPlane( const cm_traceWork_t *tw, const idVec3 &point, const idPlane &plane,
+ const float angle, const float minTan, float &tanHalfAngle ) {
+ double v0, v1, v2, a, b, c, d, sqrtd, q, frac1, frac2;
+ idVec3 p, normal;
+
+ /*
+
+ p[0] = point[0] * cos(t) + point[1] * sin(t)
+ p[1] = point[0] * -sin(t) + point[1] * cos(t)
+ p[2] = point[2];
+
+ normal[0] * (p[0] * cos(t) + p[1] * sin(t)) +
+ normal[1] * (p[0] * -sin(t) + p[1] * cos(t)) +
+ normal[2] * p[2] + dist = 0
+
+ normal[0] * p[0] * cos(t) + normal[0] * p[1] * sin(t) +
+ -normal[1] * p[0] * sin(t) + normal[1] * p[1] * cos(t) +
+ normal[2] * p[2] + dist = 0
+
+ v2 * cos(t) + v1 * sin(t) + v0
+
+ // rotation about the z-axis
+ v0 = normal[2] * p[2] + dist
+ v1 = normal[0] * p[1] - normal[1] * p[0]
+ v2 = normal[0] * p[0] + normal[1] * p[1]
+
+ r = tan(t / 2);
+ sin(t) = 2*r/(1+r*r);
+ cos(t) = (1-r*r)/(1+r*r);
+
+ v1 * 2 * r / (1 + r*r) + v2 * (1 - r*r) / (1 + r*r) + v0 = 0
+ (v1 * 2 * r + v2 * (1 - r*r)) / (1 + r*r) = -v0
+ (v1 * 2 * r + v2 - v2 * r*r) / (1 + r*r) = -v0
+ v1 * 2 * r + v2 - v2 * r*r = -v0 * (1 + r*r)
+ v1 * 2 * r + v2 - v2 * r*r = -v0 + -v0 * r*r
+ (v0 - v2) * r * r + (2 * v1) * r + (v0 + v2) = 0;
+
+ */
+
+ tanHalfAngle = tw->maxTan;
+
+ // transform rotation axis to z-axis
+ p = (point - tw->origin) * tw->matrix;
+ d = plane[3] + plane.Normal() * tw->origin;
+ normal = plane.Normal() * tw->matrix;
+
+ v0 = normal[2] * p[2] + d;
+ v1 = normal[0] * p[1] - normal[1] * p[0];
+ v2 = normal[0] * p[0] + normal[1] * p[1];
+
+ a = v0 - v2;
+ b = v1;
+ c = v0 + v2;
+ if ( a == 0.0f ) {
+ if ( b == 0.0f ) {
+ return false;
+ }
+ frac1 = -c / ( 2.0f * b );
+ frac2 = 1e10; // = tan( idMath::HALF_PI )
+ }
+ else {
+ d = b * b - c * a;
+ if ( d <= 0.0f ) {
+ return false;
+ }
+ sqrtd = sqrt( d );
+ if ( b > 0.0f ) {
+ q = - b + sqrtd;
+ }
+ else {
+ q = - b - sqrtd;
+ }
+ frac1 = q / a;
+ frac2 = c / q;
+ }
+
+ if ( angle < 0.0f ) {
+ frac1 = -frac1;
+ frac2 = -frac2;
+ }
+
+ // get smallest tangent for which a collision occurs
+ if ( frac1 >= minTan && frac1 < tanHalfAngle ) {
+ tanHalfAngle = frac1;
+ }
+ if ( frac2 >= minTan && frac2 < tanHalfAngle ) {
+ tanHalfAngle = frac2;
+ }
+
+ if ( angle < 0.0f ) {
+ tanHalfAngle = -tanHalfAngle;
+ }
+
+ return true;
+}
+
+/*
+================
+idCollisionModelManagerLocal::PointFurthestFromPlane
+
+ calculates the direction of motion at the initial position, where dir < 0 means the point moves towards the plane
+ if the point moves away from the plane the tangent of half the rotation angle at which
+ the point is furthest away from the plane is also calculated
+================
+*/
+int idCollisionModelManagerLocal::PointFurthestFromPlane( const cm_traceWork_t *tw, const idVec3 &point, const idPlane &plane,
+ const float angle, float &tanHalfAngle, float &dir ) {
+
+ double v1, v2, a, b, c, d, sqrtd, q, frac1, frac2;
+ idVec3 p, normal;
+
+ /*
+
+ v2 * cos(t) + v1 * sin(t) + v0 = 0;
+
+ // rotation about the z-axis
+ v0 = normal[2] * p[2] + dist
+ v1 = normal[0] * p[1] - normal[1] * p[0]
+ v2 = normal[0] * p[0] + normal[1] * p[1]
+
+ derivative:
+ v1 * cos(t) - v2 * sin(t) = 0;
+
+ r = tan(t / 2);
+ sin(t) = 2*r/(1+r*r);
+ cos(t) = (1-r*r)/(1+r*r);
+
+ -v2 * 2 * r / (1 + r*r) + v1 * (1 - r*r)/(1+r*r);
+ -v2 * 2 * r + v1 * (1 - r*r) / (1 + r*r) = 0;
+ -v2 * 2 * r + v1 * (1 - r*r) = 0;
+ (-v1) * r * r + (-2 * v2) * r + (v1) = 0;
+
+ */
+
+ tanHalfAngle = 0.0f;
+
+ // transform rotation axis to z-axis
+ p = (point - tw->origin) * tw->matrix;
+ normal = plane.Normal() * tw->matrix;
+
+ v1 = normal[0] * p[1] - normal[1] * p[0];
+ v2 = normal[0] * p[0] + normal[1] * p[1];
+
+ // the point will always start at the front of the plane, therefore v0 + v2 > 0 is always true
+ if ( angle < 0.0f ) {
+ dir = -v1;
+ }
+ else {
+ dir = v1;
+ }
+ // negative direction means the point moves towards the plane at the initial position
+ if ( dir <= 0.0f ) {
+ return true;
+ }
+
+ a = -v1;
+ b = -v2;
+ c = v1;
+ if ( a == 0.0f ) {
+ if ( b == 0.0f ) {
+ return false;
+ }
+ frac1 = -c / ( 2.0f * b );
+ frac2 = 1e10; // = tan( idMath::HALF_PI )
+ }
+ else {
+ d = b * b - c * a;
+ if ( d <= 0.0f ) {
+ return false;
+ }
+ sqrtd = sqrt( d );
+ if ( b > 0.0f ) {
+ q = - b + sqrtd;
+ }
+ else {
+ q = - b - sqrtd;
+ }
+ frac1 = q / a;
+ frac2 = c / q;
+ }
+
+ if ( angle < 0.0f ) {
+ frac1 = -frac1;
+ frac2 = -frac2;
+ }
+
+ if ( frac1 < 0.0f && frac2 < 0.0f ) {
+ return false;
+ }
+
+ if ( frac1 > frac2 ) {
+ tanHalfAngle = frac1;
+ }
+ else {
+ tanHalfAngle = frac2;
+ }
+
+ if ( angle < 0.0f ) {
+ tanHalfAngle = -tanHalfAngle;
+ }
+
+ return true;
+}
+
+/*
+================
+idCollisionModelManagerLocal::RotatePointThroughEpsilonPlane
+================
+*/
+int idCollisionModelManagerLocal::RotatePointThroughEpsilonPlane( const cm_traceWork_t *tw, const idVec3 &point, const idVec3 &endPoint,
+ const idPlane &plane, const float angle, const idVec3 &origin,
+ float &tanHalfAngle, idVec3 &collisionPoint, idVec3 &endDir ) {
+ float d, dir, startTan;
+ idVec3 vec, startDir;
+ idPlane epsPlane;
+
+ // epsilon expanded plane
+ epsPlane = plane;
+ epsPlane.SetDist( epsPlane.Dist() + CM_CLIP_EPSILON );
+
+ // if the rotation sphere at the rotation origin is too far away from the polygon plane
+ d = epsPlane.Distance( origin );
+ vec = point - origin;
+ if ( d * d > vec * vec ) {
+ return false;
+ }
+
+ // calculate direction of motion at vertex start position
+ startDir = ( point - origin ).Cross( tw->axis );
+ if ( angle < 0.0f ) {
+ startDir = -startDir;
+ }
+ // if moving away from plane at start position
+ if ( startDir * epsPlane.Normal() >= 0.0f ) {
+ // if end position is outside epsilon range
+ d = epsPlane.Distance( endPoint );
+ if ( d >= 0.0f ) {
+ return false; // no collision
+ }
+ // calculate direction of motion at vertex end position
+ endDir = ( endPoint - origin ).Cross( tw->axis );
+ if ( angle < 0.0f ) {
+ endDir = -endDir;
+ }
+ // if also moving away from plane at end position
+ if ( endDir * epsPlane.Normal() > 0.0f ) {
+ return false; // no collision
+ }
+ }
+
+ // if the start position is in the epsilon range
+ d = epsPlane.Distance( point );
+ if ( d <= CM_PL_RANGE_EPSILON ) {
+
+ // calculate tangent of half the rotation for which the vertex is furthest away from the plane
+ if ( !PointFurthestFromPlane( tw, point, plane, angle, startTan, dir ) ) {
+ return false;
+ }
+
+ if ( dir <= 0.0f ) {
+ // moving towards the polygon plane so stop immediately
+ tanHalfAngle = 0.0f;
+ }
+ else if ( idMath::Fabs( startTan ) >= tw->maxTan ) {
+ // never going to get beyond the start tangent during the current rotation
+ return false;
+ }
+ else {
+ // calculate collision with epsilon expanded plane
+ if ( !RotatePointThroughPlane( tw, point, epsPlane, angle, idMath::Fabs( startTan ), tanHalfAngle ) ) {
+ tanHalfAngle = startTan;
+ }
+ }
+ }
+ else {
+ // calculate collision with epsilon expanded plane
+ if ( !RotatePointThroughPlane( tw, point, epsPlane, angle, 0.0f, tanHalfAngle ) ) {
+ return false;
+ }
+ }
+
+ // calculate collision point
+ collisionPoint = point;
+ if ( tanHalfAngle != 0.0f ) {
+ CM_RotatePoint( collisionPoint, tw->origin, tw->axis, tanHalfAngle );
+ }
+ // calculate direction of motion at collision point
+ endDir = ( collisionPoint - origin ).Cross( tw->axis );
+ if ( angle < 0.0f ) {
+ endDir = -endDir;
+ }
+ return true;
+}
+
+/*
+================
+idCollisionModelManagerLocal::RotateTrmVertexThroughPolygon
+================
+*/
+void idCollisionModelManagerLocal::RotateTrmVertexThroughPolygon( cm_traceWork_t *tw, cm_polygon_t *poly, cm_trmVertex_t *v, int vertexNum ) {
+ int i;
+ float tanHalfAngle;
+ idVec3 endDir, collisionPoint;
+ idPluecker pl;
+
+ // if the trm vertex is behind the polygon plane it cannot collide with the polygon within a 180 degrees rotation
+ if ( tw->isConvex && tw->axisIntersectsTrm && v->polygonSide ) {
+ return;
+ }
+
+ // if the trace model vertex rotation bounds do not intersect the polygon bounds
+ if ( !v->rotationBounds.IntersectsBounds( poly->bounds ) ) {
+ return;
+ }
+
+ // vertex rotation bounds should cross polygon plane
+ if ( v->rotationBounds.PlaneSide( poly->plane ) != SIDE_CROSS ) {
+ return;
+ }
+
+ // rotate the vertex through the epsilon plane
+ if ( !RotatePointThroughEpsilonPlane( tw, v->p, v->endp, poly->plane, tw->angle, v->rotationOrigin,
+ tanHalfAngle, collisionPoint, endDir ) ) {
+ return;
+ }
+
+ if ( idMath::Fabs( tanHalfAngle ) < tw->maxTan ) {
+ // verify if 'collisionPoint' moving along 'endDir' moves between polygon edges
+ pl.FromRay( collisionPoint, endDir );
+ for ( i = 0; i < poly->numEdges; i++ ) {
+ if ( poly->edges[i] < 0 ) {
+ if ( pl.PermutedInnerProduct( tw->polygonEdgePlueckerCache[i] ) > 0.0f ) {
+ return;
+ }
+ }
+ else {
+ if ( pl.PermutedInnerProduct( tw->polygonEdgePlueckerCache[i] ) < 0.0f ) {
+ return;
+ }
+ }
+ }
+ tw->maxTan = idMath::Fabs( tanHalfAngle );
+ // collision plane is the polygon plane
+ tw->trace.c.normal = poly->plane.Normal();
+ tw->trace.c.dist = poly->plane.Dist();
+ tw->trace.c.contents = poly->contents;
+ tw->trace.c.material = poly->material;
+ tw->trace.c.type = CONTACT_TRMVERTEX;
+ tw->trace.c.modelFeature = *reinterpret_cast(&poly);
+ tw->trace.c.trmFeature = v - tw->vertices;
+ tw->trace.c.point = collisionPoint;
+ }
+}
+
+/*
+================
+idCollisionModelManagerLocal::RotateVertexThroughTrmPolygon
+================
+*/
+void idCollisionModelManagerLocal::RotateVertexThroughTrmPolygon( cm_traceWork_t *tw, cm_trmPolygon_t *trmpoly, cm_polygon_t *poly, cm_vertex_t *v, idVec3 &rotationOrigin ) {
+ int i, edgeNum;
+ float tanHalfAngle;
+ idVec3 dir, endp, endDir, collisionPoint;
+ idPluecker pl;
+ cm_trmEdge_t *edge;
+
+ // if the polygon vertex is behind the trm plane it cannot collide with the trm polygon within a 180 degrees rotation
+ if ( tw->isConvex && tw->axisIntersectsTrm && trmpoly->plane.Distance( v->p ) < 0.0f ) {
+ return;
+ }
+
+ // if the model vertex is outside the trm polygon rotation bounds
+ if ( !trmpoly->rotationBounds.ContainsPoint( v->p ) ) {
+ return;
+ }
+
+ // if the rotation axis goes through the polygon vertex
+ dir = v->p - rotationOrigin;
+ if ( dir * dir < ROTATION_AXIS_EPSILON * ROTATION_AXIS_EPSILON ) {
+ return;
+ }
+
+ // calculate vertex end position
+ endp = v->p;
+ tw->modelVertexRotation.RotatePoint( endp );
+
+ // rotate the vertex through the epsilon plane
+ if ( !RotatePointThroughEpsilonPlane( tw, v->p, endp, trmpoly->plane, -tw->angle, rotationOrigin,
+ tanHalfAngle, collisionPoint, endDir ) ) {
+ return;
+ }
+
+ if ( idMath::Fabs( tanHalfAngle ) < tw->maxTan ) {
+ // verify if 'collisionPoint' moving along 'endDir' moves between polygon edges
+ pl.FromRay( collisionPoint, endDir );
+ for ( i = 0; i < trmpoly->numEdges; i++ ) {
+ edgeNum = trmpoly->edges[i];
+ edge = tw->edges + abs(edgeNum);
+ if ( edgeNum < 0 ) {
+ if ( pl.PermutedInnerProduct( edge->pl ) > 0.0f ) {
+ return;
+ }
+ }
+ else {
+ if ( pl.PermutedInnerProduct( edge->pl ) < 0.0f ) {
+ return;
+ }
+ }
+ }
+ tw->maxTan = idMath::Fabs( tanHalfAngle );
+ // collision plane is the flipped trm polygon plane
+ tw->trace.c.normal = -trmpoly->plane.Normal();
+ tw->trace.c.dist = tw->trace.c.normal * v->p;
+ tw->trace.c.contents = poly->contents;
+ tw->trace.c.material = poly->material;
+ tw->trace.c.type = CONTACT_MODELVERTEX;
+ tw->trace.c.modelFeature = v - tw->model->vertices;
+ tw->trace.c.trmFeature = trmpoly - tw->polys;
+ tw->trace.c.point = v->p;
+ }
+}
+
+/*
+================
+idCollisionModelManagerLocal::RotateTrmThroughPolygon
+
+ returns true if the polygon blocks the complete rotation
+================
+*/
+bool idCollisionModelManagerLocal::RotateTrmThroughPolygon( cm_traceWork_t *tw, cm_polygon_t *p ) {
+ int i, j, k, edgeNum;
+ float d;
+ cm_trmVertex_t *bv;
+ cm_trmEdge_t *be;
+ cm_trmPolygon_t *bp;
+ cm_vertex_t *v;
+ cm_edge_t *e;
+ idVec3 *rotationOrigin;
+
+ // if already checked this polygon
+ if ( p->checkcount == idCollisionModelManagerLocal::checkCount ) {
+ return false;
+ }
+ p->checkcount = idCollisionModelManagerLocal::checkCount;
+
+ // if this polygon does not have the right contents behind it
+ if ( !(p->contents & tw->contents) ) {
+ return false;
+ }
+
+ // if the the trace bounds do not intersect the polygon bounds
+ if ( !tw->bounds.IntersectsBounds( p->bounds ) ) {
+ return false;
+ }
+
+ // back face culling
+ if ( tw->isConvex ) {
+ // if the center of the convex trm is behind the polygon plane
+ if ( p->plane.Distance( tw->start ) < 0.0f ) {
+ // if the rotation axis intersects the trace model
+ if ( tw->axisIntersectsTrm ) {
+ return false;
+ }
+ else {
+ // if the direction of motion at the start and end position of the
+ // center of the trm both go towards or away from the polygon plane
+ // or if the intersections of the rotation axis with the expanded heart planes
+ // are both in front of the polygon plane
+ }
+ }
+ }
+
+ // if the polygon is too far from the first heart plane
+ d = p->bounds.PlaneDistance( tw->heartPlane1 );
+ if ( idMath::Fabs(d) > tw->maxDistFromHeartPlane1 ) {
+ return false;
+ }
+
+ // rotation bounds should cross polygon plane
+ switch( tw->bounds.PlaneSide( p->plane ) ) {
+ case PLANESIDE_CROSS:
+ break;
+ case PLANESIDE_FRONT:
+ if ( tw->model->isConvex ) {
+ tw->quickExit = true;
+ return true;
+ }
+ default:
+ return false;
+ }
+
+ for ( i = 0; i < tw->numVerts; i++ ) {
+ bv = tw->vertices + i;
+ // calculate polygon side this vertex is on
+ d = p->plane.Distance( bv->p );
+ bv->polygonSide = FLOATSIGNBITSET( d );
+ }
+
+ for ( i = 0; i < p->numEdges; i++ ) {
+ edgeNum = p->edges[i];
+ e = tw->model->edges + abs(edgeNum);
+ v = tw->model->vertices + e->vertexNum[INTSIGNBITSET(edgeNum)];
+
+ // pluecker coordinate for edge
+ tw->polygonEdgePlueckerCache[i].FromLine( tw->model->vertices[e->vertexNum[0]].p,
+ tw->model->vertices[e->vertexNum[1]].p );
+
+ // calculate rotation origin projected into rotation plane through the vertex
+ tw->polygonRotationOriginCache[i] = tw->origin + tw->axis * ( tw->axis * ( v->p - tw->origin ) );
+ }
+ // copy first to last so we can easily cycle through
+ tw->polygonRotationOriginCache[p->numEdges] = tw->polygonRotationOriginCache[0];
+
+ // fast point rotation
+ if ( tw->pointTrace ) {
+ RotateTrmVertexThroughPolygon( tw, p, &tw->vertices[0], 0 );
+ }
+ else {
+ // rotate trm vertices through polygon
+ for ( i = 0; i < tw->numVerts; i++ ) {
+ bv = tw->vertices + i;
+ if ( bv->used ) {
+ RotateTrmVertexThroughPolygon( tw, p, bv, i );
+ }
+ }
+
+ // rotate trm edges through polygon
+ for ( i = 1; i <= tw->numEdges; i++ ) {
+ be = tw->edges + i;
+ if ( be->used ) {
+ RotateTrmEdgeThroughPolygon( tw, p, be );
+ }
+ }
+
+ // rotate all polygon vertices through the trm
+ for ( i = 0; i < p->numEdges; i++ ) {
+ edgeNum = p->edges[i];
+ e = tw->model->edges + abs(edgeNum);
+
+ if ( e->checkcount == idCollisionModelManagerLocal::checkCount ) {
+ continue;
+ }
+ // set edge check count
+ e->checkcount = idCollisionModelManagerLocal::checkCount;
+ // can never collide with internal edges
+ if ( e->internal ) {
+ continue;
+ }
+ // got to check both vertices because we skip internal edges
+ for ( k = 0; k < 2; k++ ) {
+
+ v = tw->model->vertices + e->vertexNum[k ^ INTSIGNBITSET(edgeNum)];
+
+ // if this vertex is already checked
+ if ( v->checkcount == idCollisionModelManagerLocal::checkCount ) {
+ continue;
+ }
+ // set vertex check count
+ v->checkcount = idCollisionModelManagerLocal::checkCount;
+
+ // if the vertex is outside the trm rotation bounds
+ if ( !tw->bounds.ContainsPoint( v->p ) ) {
+ continue;
+ }
+
+ rotationOrigin = &tw->polygonRotationOriginCache[i+k];
+
+ for ( j = 0; j < tw->numPolys; j++ ) {
+ bp = tw->polys + j;
+ if ( bp->used ) {
+ RotateVertexThroughTrmPolygon( tw, bp, p, v, *rotationOrigin );
+ }
+ }
+ }
+ }
+ }
+
+ return ( tw->maxTan == 0.0f );
+}
+
+/*
+================
+idCollisionModelManagerLocal::BoundsForRotation
+
+ only for rotations < 180 degrees
+================
+*/
+void idCollisionModelManagerLocal::BoundsForRotation( const idVec3 &origin, const idVec3 &axis, const idVec3 &start, const idVec3 &end, idBounds &bounds ) {
+ int i;
+ float radiusSqr;
+ idVec3 v1, v2;
+
+ radiusSqr = ( start - origin ).LengthSqr();
+ v1 = ( start - origin ).Cross( axis );
+ v2 = ( end - origin ).Cross( axis );
+
+ for ( i = 0; i < 3; i++ ) {
+ // if the derivative changes sign along this axis during the rotation from start to end
+ if ( ( v1[i] > 0.0f && v2[i] < 0.0f ) || ( v1[i] < 0.0f && v2[i] > 0.0f ) ) {
+ if ( ( 0.5f * (start[i] + end[i]) - origin[i] ) > 0.0f ) {
+ bounds[0][i] = Min( start[i], end[i] );
+ bounds[1][i] = origin[i] + idMath::Sqrt( radiusSqr * ( 1.0f - axis[i] * axis[i] ) );
+ }
+ else {
+ bounds[0][i] = origin[i] - idMath::Sqrt( radiusSqr * ( 1.0f - axis[i] * axis[i] ) );
+ bounds[1][i] = Max( start[i], end[i] );
+ }
+ }
+ else if ( start[i] > end[i] ) {
+ bounds[0][i] = end[i];
+ bounds[1][i] = start[i];
+ }
+ else {
+ bounds[0][i] = start[i];
+ bounds[1][i] = end[i];
+ }
+ // expand for epsilons
+ bounds[0][i] -= CM_BOX_EPSILON;
+ bounds[1][i] += CM_BOX_EPSILON;
+ }
+}
+
+/*
+================
+idCollisionModelManagerLocal::Rotation180
+================
+*/
+void idCollisionModelManagerLocal::Rotation180( trace_t *results, const idVec3 &rorg, const idVec3 &axis,
+ const float startAngle, const float endAngle, const idVec3 &start,
+ const idTraceModel *trm, const idMat3 &trmAxis, int contentMask,
+ cmHandle_t model, const idVec3 &modelOrigin, const idMat3 &modelAxis ) {
+ int i, j, edgeNum;
+ float d, maxErr, initialTan;
+ bool model_rotated, trm_rotated;
+ idVec3 dir, dir1, dir2, tmp, vr, vup, org, at, bt;
+ idMat3 invModelAxis, endAxis, tmpAxis;
+ idRotation startRotation, endRotation;
+ idPluecker plaxis;
+ cm_trmPolygon_t *poly;
+ cm_trmEdge_t *edge;
+ cm_trmVertex_t *vert;
+ ALIGN16( static cm_traceWork_t tw; )
+
+ if ( model < 0 || model > MAX_SUBMODELS || model > idCollisionModelManagerLocal::maxModels ) {
+ common->Printf("idCollisionModelManagerLocal::Rotation180: invalid model handle\n");
+ return;
+ }
+ if ( !idCollisionModelManagerLocal::models[model] ) {
+ common->Printf("idCollisionModelManagerLocal::Rotation180: invalid model\n");
+ return;
+ }
+
+ idCollisionModelManagerLocal::checkCount++;
+
+ tw.trace.fraction = 1.0f;
+ tw.trace.c.contents = 0;
+ tw.trace.c.type = CONTACT_NONE;
+ tw.contents = contentMask;
+ tw.isConvex = true;
+ tw.rotation = true;
+ tw.positionTest = false;
+ tw.axisIntersectsTrm = false;
+ tw.quickExit = false;
+ tw.angle = endAngle - startAngle;
+ assert( tw.angle > -180.0f && tw.angle < 180.0f );
+ tw.maxTan = initialTan = idMath::Fabs( tan( ( idMath::PI / 360.0f ) * tw.angle ) );
+ tw.model = idCollisionModelManagerLocal::models[model];
+ tw.start = start - modelOrigin;
+ // rotation axis, axis is assumed to be normalized
+ tw.axis = axis;
+ assert( tw.axis[0] * tw.axis[0] + tw.axis[1] * tw.axis[1] + tw.axis[2] * tw.axis[2] > 0.99f );
+ // rotation origin projected into rotation plane through tw.start
+ tw.origin = rorg - modelOrigin;
+ d = (tw.axis * tw.origin) - ( tw.axis * tw.start );
+ tw.origin = tw.origin - d * tw.axis;
+ // radius of rotation
+ tw.radius = ( tw.start - tw.origin ).Length();
+ // maximum error of the circle approximation traced through the axial BSP tree
+ d = tw.radius * tw.radius - (CIRCLE_APPROXIMATION_LENGTH*CIRCLE_APPROXIMATION_LENGTH*0.25f);
+ if ( d > 0.0f ) {
+ maxErr = tw.radius - idMath::Sqrt( d );
+ } else {
+ maxErr = tw.radius;
+ }
+
+ model_rotated = modelAxis.IsRotated();
+ if ( model_rotated ) {
+ invModelAxis = modelAxis.Transpose();
+ tw.axis *= invModelAxis;
+ tw.origin *= invModelAxis;
+ }
+
+ startRotation.Set( tw.origin, tw.axis, startAngle );
+ endRotation.Set( tw.origin, tw.axis, endAngle );
+
+ // create matrix which rotates the rotation axis to the z-axis
+ tw.axis.NormalVectors( vr, vup );
+ tw.matrix[0][0] = vr[0];
+ tw.matrix[1][0] = vr[1];
+ tw.matrix[2][0] = vr[2];
+ tw.matrix[0][1] = -vup[0];
+ tw.matrix[1][1] = -vup[1];
+ tw.matrix[2][1] = -vup[2];
+ tw.matrix[0][2] = tw.axis[0];
+ tw.matrix[1][2] = tw.axis[1];
+ tw.matrix[2][2] = tw.axis[2];
+
+ // if optimized point trace
+ if ( !trm || ( trm->bounds[1][0] - trm->bounds[0][0] <= 0.0f &&
+ trm->bounds[1][1] - trm->bounds[0][1] <= 0.0f &&
+ trm->bounds[1][2] - trm->bounds[0][2] <= 0.0f ) ) {
+
+ if ( model_rotated ) {
+ // rotate trace instead of model
+ tw.start *= invModelAxis;
+ }
+ tw.end = tw.start;
+ // if we start at a specific angle
+ if ( startAngle != 0.0f ) {
+ startRotation.RotatePoint( tw.start );
+ }
+ // calculate end position of rotation
+ endRotation.RotatePoint( tw.end );
+
+ // calculate rotation origin projected into rotation plane through the vertex
+ tw.numVerts = 1;
+ tw.vertices[0].p = tw.start;
+ tw.vertices[0].endp = tw.end;
+ tw.vertices[0].used = true;
+ tw.vertices[0].rotationOrigin = tw.origin + tw.axis * ( tw.axis * ( tw.vertices[0].p - tw.origin ) );
+ BoundsForRotation( tw.vertices[0].rotationOrigin, tw.axis, tw.start, tw.end, tw.vertices[0].rotationBounds );
+ // rotation bounds
+ tw.bounds = tw.vertices[0].rotationBounds;
+ tw.numEdges = tw.numPolys = 0;
+
+ // collision with single point
+ tw.pointTrace = true;
+
+ // extents is set to maximum error of the circle approximation traced through the axial BSP tree
+ tw.extents[0] = tw.extents[1] = tw.extents[2] = maxErr + CM_BOX_EPSILON;
+
+ // setup rotation heart plane
+ tw.heartPlane1.SetNormal( tw.axis );
+ tw.heartPlane1.FitThroughPoint( tw.start );
+ tw.maxDistFromHeartPlane1 = CM_BOX_EPSILON;
+
+ // trace through the model
+ idCollisionModelManagerLocal::TraceThroughModel( &tw );
+
+ // store results
+ *results = tw.trace;
+ results->endpos = start;
+ if ( tw.maxTan == initialTan ) {
+ results->fraction = 1.0f;
+ } else {
+ results->fraction = idMath::Fabs( atan( tw.maxTan ) * ( 2.0f * 180.0f / idMath::PI ) / tw.angle );
+ }
+ assert( results->fraction <= 1.0f );
+ endRotation.Set( rorg, axis, startAngle + (endAngle-startAngle) * results->fraction );
+ endRotation.RotatePoint( results->endpos );
+ results->endAxis.Identity();
+
+ if ( results->fraction < 1.0f ) {
+ // rotate trace plane normal if there was a collision with a rotated model
+ if ( model_rotated ) {
+ results->c.normal *= modelAxis;
+ results->c.point *= modelAxis;
+ }
+ results->c.point += modelOrigin;
+ results->c.dist += modelOrigin * results->c.normal;
+ }
+ return;
+ }
+
+ tw.pointTrace = false;
+
+ // setup trm structure
+ idCollisionModelManagerLocal::SetupTrm( &tw, trm );
+
+ trm_rotated = trmAxis.IsRotated();
+
+ // calculate vertex positions
+ if ( trm_rotated ) {
+ for ( i = 0; i < tw.numVerts; i++ ) {
+ // rotate trm around the start position
+ tw.vertices[i].p *= trmAxis;
+ }
+ }
+ for ( i = 0; i < tw.numVerts; i++ ) {
+ // set trm at start position
+ tw.vertices[i].p += tw.start;
+ }
+ if ( model_rotated ) {
+ for ( i = 0; i < tw.numVerts; i++ ) {
+ tw.vertices[i].p *= invModelAxis;
+ }
+ }
+ for ( i = 0; i < tw.numVerts; i++ ) {
+ tw.vertices[i].endp = tw.vertices[i].p;
+ }
+ // if we start at a specific angle
+ if ( startAngle != 0.0f ) {
+ for ( i = 0; i < tw.numVerts; i++ ) {
+ startRotation.RotatePoint( tw.vertices[i].p );
+ }
+ }
+ for ( i = 0; i < tw.numVerts; i++ ) {
+ // end position of vertex
+ endRotation.RotatePoint( tw.vertices[i].endp );
+ }
+
+ // add offset to start point
+ if ( trm_rotated ) {
+ tw.start += trm->offset * trmAxis;
+ } else {
+ tw.start += trm->offset;
+ }
+ // if the model is rotated
+ if ( model_rotated ) {
+ // rotate trace instead of model
+ tw.start *= invModelAxis;
+ }
+ tw.end = tw.start;
+ // if we start at a specific angle
+ if ( startAngle != 0.0f ) {
+ startRotation.RotatePoint( tw.start );
+ }
+ // calculate end position of rotation
+ endRotation.RotatePoint( tw.end );
+
+ // setup trm vertices
+ for ( vert = tw.vertices, i = 0; i < tw.numVerts; i++, vert++ ) {
+ // calculate rotation origin projected into rotation plane through the vertex
+ vert->rotationOrigin = tw.origin + tw.axis * ( tw.axis * ( vert->p - tw.origin ) );
+ // calculate rotation bounds for this vertex
+ BoundsForRotation( vert->rotationOrigin, tw.axis, vert->p, vert->endp, vert->rotationBounds );
+ // if the rotation axis goes through the vertex then the vertex is not used
+ d = ( vert->p - vert->rotationOrigin ).LengthSqr();
+ if ( d > ROTATION_AXIS_EPSILON * ROTATION_AXIS_EPSILON ) {
+ vert->used = true;
+ }
+ }
+
+ // setup trm edges
+ for ( edge = tw.edges + 1, i = 1; i <= tw.numEdges; i++, edge++ ) {
+ // if the rotation axis goes through both the edge vertices then the edge is not used
+ if ( tw.vertices[edge->vertexNum[0]].used | tw.vertices[edge->vertexNum[1]].used ) {
+ edge->used = true;
+ }
+ // edge start, end and pluecker coordinate
+ edge->start = tw.vertices[edge->vertexNum[0]].p;
+ edge->end = tw.vertices[edge->vertexNum[1]].p;
+ edge->pl.FromLine( edge->start, edge->end );
+ // pluecker coordinate for edge being rotated about the z-axis
+ at = ( edge->start - tw.origin ) * tw.matrix;
+ bt = ( edge->end - tw.origin ) * tw.matrix;
+ edge->plzaxis.FromLine( at, bt );
+ // get edge rotation bounds from the rotation bounds of both vertices
+ edge->rotationBounds = tw.vertices[edge->vertexNum[0]].rotationBounds;
+ edge->rotationBounds.AddBounds( tw.vertices[edge->vertexNum[1]].rotationBounds );
+ // used to calculate if the rotation axis intersects the trm
+ edge->bitNum = 0;
+ }
+
+ tw.bounds.Clear();
+
+ // rotate trm polygon planes
+ if ( trm_rotated & model_rotated ) {
+ tmpAxis = trmAxis * invModelAxis;
+ for ( poly = tw.polys, i = 0; i < tw.numPolys; i++, poly++ ) {
+ poly->plane *= tmpAxis;
+ }
+ } else if ( trm_rotated ) {
+ for ( poly = tw.polys, i = 0; i < tw.numPolys; i++, poly++ ) {
+ poly->plane *= trmAxis;
+ }
+ } else if ( model_rotated ) {
+ for ( poly = tw.polys, i = 0; i < tw.numPolys; i++, poly++ ) {
+ poly->plane *= invModelAxis;
+ }
+ }
+
+ // setup trm polygons
+ for ( poly = tw.polys, i = 0; i < tw.numPolys; i++, poly++ ) {
+ poly->used = true;
+ // set trm polygon plane distance
+ poly->plane.FitThroughPoint( tw.edges[abs(poly->edges[0])].start );
+ // get polygon bounds from edge bounds
+ poly->rotationBounds.Clear();
+ for ( j = 0; j < poly->numEdges; j++ ) {
+ // add edge rotation bounds to polygon rotation bounds
+ edge = &tw.edges[abs( poly->edges[j] )];
+ poly->rotationBounds.AddBounds( edge->rotationBounds );
+ }
+ // get trace bounds from polygon bounds
+ tw.bounds.AddBounds( poly->rotationBounds );
+ }
+
+ // extents including the maximum error of the circle approximation traced through the axial BSP tree
+ for ( i = 0; i < 3; i++ ) {
+ tw.size[0][i] = tw.bounds[0][i] - tw.start[i];
+ tw.size[1][i] = tw.bounds[1][i] - tw.start[i];
+ if ( idMath::Fabs( tw.size[0][i] ) > idMath::Fabs( tw.size[1][i] ) ) {
+ tw.extents[i] = idMath::Fabs( tw.size[0][i] ) + maxErr + CM_BOX_EPSILON;
+ } else {
+ tw.extents[i] = idMath::Fabs( tw.size[1][i] ) + maxErr + CM_BOX_EPSILON;
+ }
+ }
+
+ // for back-face culling
+ if ( tw.isConvex ) {
+ if ( tw.start == tw.origin ) {
+ tw.axisIntersectsTrm = true;
+ } else {
+ // determine if the rotation axis intersects the trm
+ plaxis.FromRay( tw.origin, tw.axis );
+ for ( poly = tw.polys, i = 0; i < tw.numPolys; i++, poly++ ) {
+ // back face cull polygons
+ if ( poly->plane.Normal() * tw.axis > 0.0f ) {
+ continue;
+ }
+ // test if the axis goes between the polygon edges
+ for ( j = 0; j < poly->numEdges; j++ ) {
+ edgeNum = poly->edges[j];
+ edge = tw.edges + abs(edgeNum);
+ if ( !(edge->bitNum & 2) ) {
+ d = plaxis.PermutedInnerProduct( edge->pl );
+ edge->bitNum = FLOATSIGNBITSET( d ) | 2;
+ }
+ if ( ( edge->bitNum ^ INTSIGNBITSET( edgeNum ) ) & 1 ) {
+ break;
+ }
+ }
+ if ( j >= poly->numEdges ) {
+ tw.axisIntersectsTrm = true;
+ break;
+ }
+ }
+ }
+ }
+
+ // setup rotation heart plane
+ tw.heartPlane1.SetNormal( tw.axis );
+ tw.heartPlane1.FitThroughPoint( tw.start );
+ tw.maxDistFromHeartPlane1 = 0.0f;
+ for ( i = 0; i < tw.numVerts; i++ ) {
+ d = idMath::Fabs( tw.heartPlane1.Distance( tw.vertices[i].p ) );
+ if ( d > tw.maxDistFromHeartPlane1 ) {
+ tw.maxDistFromHeartPlane1 = d;
+ }
+ }
+ tw.maxDistFromHeartPlane1 += CM_BOX_EPSILON;
+
+ // inverse rotation to rotate model vertices towards trace model
+ tw.modelVertexRotation.Set( tw.origin, tw.axis, -tw.angle );
+
+ // trace through the model
+ idCollisionModelManagerLocal::TraceThroughModel( &tw );
+
+ // store results
+ *results = tw.trace;
+ results->endpos = start;
+ if ( tw.maxTan == initialTan ) {
+ results->fraction = 1.0f;
+ } else {
+ results->fraction = idMath::Fabs( atan( tw.maxTan ) * ( 2.0f * 180.0f / idMath::PI ) / tw.angle );
+ }
+ assert( results->fraction <= 1.0f );
+ endRotation.Set( rorg, axis, startAngle + (endAngle-startAngle) * results->fraction );
+ endRotation.RotatePoint( results->endpos );
+ results->endAxis = trmAxis * endRotation.ToMat3();
+
+ if ( results->fraction < 1.0f ) {
+ // rotate trace plane normal if there was a collision with a rotated model
+ if ( model_rotated ) {
+ results->c.normal *= modelAxis;
+ results->c.point *= modelAxis;
+ }
+ results->c.point += modelOrigin;
+ results->c.dist += modelOrigin * results->c.normal;
+ }
+}
+
+/*
+================
+idCollisionModelManagerLocal::Rotation
+================
+*/
+#ifdef _DEBUG
+static int entered = 0;
+#endif
+
+void idCollisionModelManagerLocal::Rotation( trace_t *results, const idVec3 &start, const idRotation &rotation,
+ const idTraceModel *trm, const idMat3 &trmAxis, int contentMask,
+ cmHandle_t model, const idVec3 &modelOrigin, const idMat3 &modelAxis ) {
+ idVec3 tmp;
+ float maxa, stepa, a, lasta;
+
+ assert( ((byte *)&start) < ((byte *)results) || ((byte *)&start) > (((byte *)results) + sizeof( trace_t )) );
+ assert( ((byte *)&trmAxis) < ((byte *)results) || ((byte *)&trmAxis) > (((byte *)results) + sizeof( trace_t )) );
+
+ memset( results, 0, sizeof( *results ) );
+
+ // if special position test
+ if ( rotation.GetAngle() == 0.0f ) {
+ idCollisionModelManagerLocal::ContentsTrm( results, start, trm, trmAxis, contentMask, model, modelOrigin, modelAxis );
+ return;
+ }
+
+#ifdef _DEBUG
+ bool startsolid = false;
+ // test whether or not stuck to begin with
+ if ( cm_debugCollision.GetBool() ) {
+ if ( !entered ) {
+ entered = 1;
+ // if already fucked up to begin with
+ if ( idCollisionModelManagerLocal::Contents( start, trm, trmAxis, -1, model, modelOrigin, modelAxis ) & contentMask ) {
+ startsolid = true;
+ }
+ entered = 0;
+ }
+ }
+#endif
+
+ if ( rotation.GetAngle() >= 180.0f || rotation.GetAngle() <= -180.0f) {
+ if ( rotation.GetAngle() >= 360.0f ) {
+ maxa = 360.0f;
+ stepa = 120.0f; // three steps strictly < 180 degrees
+ } else if ( rotation.GetAngle() <= -360.0f ) {
+ maxa = -360.0f;
+ stepa = -120.0f; // three steps strictly < 180 degrees
+ } else {
+ maxa = rotation.GetAngle();
+ stepa = rotation.GetAngle() * 0.5f; // two steps strictly < 180 degrees
+ }
+ for ( lasta = 0.0f, a = stepa; fabs( a ) < fabs( maxa ) + 1.0f; lasta = a, a += stepa ) {
+ // partial rotation
+ idCollisionModelManagerLocal::Rotation180( results, rotation.GetOrigin(), rotation.GetVec(), lasta, a, start, trm, trmAxis, contentMask, model, modelOrigin, modelAxis );
+ // if there is a collision
+ if ( results->fraction < 1.0f ) {
+ // fraction of total rotation
+ results->fraction = (lasta + stepa * results->fraction) / rotation.GetAngle();
+ return;
+ }
+ }
+ results->fraction = 1.0f;
+ return;
+ }
+
+ idCollisionModelManagerLocal::Rotation180( results, rotation.GetOrigin(), rotation.GetVec(), 0.0f, rotation.GetAngle(), start, trm, trmAxis, contentMask, model, modelOrigin, modelAxis );
+
+#ifdef _DEBUG
+ // test for missed collisions
+ if ( cm_debugCollision.GetBool() ) {
+ if ( !entered ) {
+ entered = 1;
+ // if the trm is stuck in the model
+ if ( idCollisionModelManagerLocal::Contents( results->endpos, trm, results->endAxis, -1, model, modelOrigin, modelAxis ) & contentMask ) {
+ trace_t tr;
+
+ // test where the trm is stuck in the model
+ idCollisionModelManagerLocal::Contents( results->endpos, trm, results->endAxis, -1, model, modelOrigin, modelAxis );
+ // re-run collision detection to find out where it failed
+ idCollisionModelManagerLocal::Rotation( &tr, start, rotation, trm, trmAxis, contentMask, model, modelOrigin, modelAxis );
+ }
+ entered = 0;
+ }
+ }
+#endif
+}
diff --git a/neo/cm/PreyCM/CollisionModel_trace.cpp b/neo/cm/PreyCM/CollisionModel_trace.cpp
new file mode 100644
index 00000000..1471f97a
--- /dev/null
+++ b/neo/cm/PreyCM/CollisionModel_trace.cpp
@@ -0,0 +1,229 @@
+
+/*
+===============================================================================
+
+ Trace model vs. polygonal model collision detection.
+
+===============================================================================
+*/
+
+#include "precompiled.h"
+#pragma hdrstop
+
+#include "CollisionModel_local.h"
+
+/*
+===============================================================================
+
+Trace through the spatial subdivision
+
+===============================================================================
+*/
+
+/*
+================
+idCollisionModelManagerLocal::TraceTrmThroughNode
+================
+*/
+void idCollisionModelManagerLocal::TraceTrmThroughNode( cm_traceWork_t *tw, cm_node_t *node ) {
+ cm_polygonRef_t *pref;
+ cm_brushRef_t *bref;
+
+ // position test
+ if ( tw->positionTest ) {
+ // if already stuck in solid
+ if ( tw->trace.fraction == 0.0f ) {
+ return;
+ }
+ // test if any of the trm vertices is inside a brush
+ for ( bref = node->brushes; bref; bref = bref->next ) {
+ if ( idCollisionModelManagerLocal::TestTrmVertsInBrush( tw, bref->b ) ) {
+ return;
+ }
+ }
+ // if just testing a point we're done
+ if ( tw->pointTrace ) {
+ return;
+ }
+ // test if the trm is stuck in any polygons
+ for ( pref = node->polygons; pref; pref = pref->next ) {
+ if ( idCollisionModelManagerLocal::TestTrmInPolygon( tw, pref->p ) ) {
+ return;
+ }
+ }
+ }
+ else if ( tw->rotation ) {
+ // rotate through all polygons in this leaf
+ for ( pref = node->polygons; pref; pref = pref->next ) {
+ if ( idCollisionModelManagerLocal::RotateTrmThroughPolygon( tw, pref->p ) ) {
+ return;
+ }
+ }
+ }
+ else {
+ // trace through all polygons in this leaf
+ for ( pref = node->polygons; pref; pref = pref->next ) {
+ if ( idCollisionModelManagerLocal::TranslateTrmThroughPolygon( tw, pref->p ) ) {
+ return;
+ }
+ }
+ }
+}
+
+/*
+================
+idCollisionModelManagerLocal::TraceThroughAxialBSPTree_r
+================
+*/
+//#define NO_SPATIAL_SUBDIVISION
+
+void idCollisionModelManagerLocal::TraceThroughAxialBSPTree_r( cm_traceWork_t *tw, cm_node_t *node, float p1f, float p2f, idVec3 &p1, idVec3 &p2) {
+ float t1, t2, offset;
+ float frac, frac2;
+ float idist;
+ idVec3 mid;
+ int side;
+ float midf;
+
+ if ( !node ) {
+ return;
+ }
+
+ if ( tw->quickExit ) {
+ return; // stop immediately
+ }
+
+ if ( tw->trace.fraction <= p1f ) {
+ return; // already hit something nearer
+ }
+
+ // if we need to test this node for collisions
+ if ( node->polygons || (tw->positionTest && node->brushes) ) {
+ // trace through node with collision data
+ idCollisionModelManagerLocal::TraceTrmThroughNode( tw, node );
+ }
+ // if already stuck in solid
+ if ( tw->positionTest && tw->trace.fraction == 0.0f ) {
+ return;
+ }
+ // if this is a leaf node
+ if ( node->planeType == -1 ) {
+ return;
+ }
+#ifdef NO_SPATIAL_SUBDIVISION
+ idCollisionModelManagerLocal::TraceThroughAxialBSPTree_r( tw, node->children[0], p1f, p2f, p1, p2 );
+ idCollisionModelManagerLocal::TraceThroughAxialBSPTree_r( tw, node->children[1], p1f, p2f, p1, p2 );
+ return;
+#endif
+ // distance from plane for trace start and end
+ t1 = p1[node->planeType] - node->planeDist;
+ t2 = p2[node->planeType] - node->planeDist;
+ // adjust the plane distance appropriately for mins/maxs
+ offset = tw->extents[node->planeType];
+ // see which sides we need to consider
+ if ( t1 >= offset && t2 >= offset ) {
+ idCollisionModelManagerLocal::TraceThroughAxialBSPTree_r( tw, node->children[0], p1f, p2f, p1, p2 );
+ return;
+ }
+
+ if ( t1 < -offset && t2 < -offset ) {
+ idCollisionModelManagerLocal::TraceThroughAxialBSPTree_r( tw, node->children[1], p1f, p2f, p1, p2 );
+ return;
+ }
+
+ if ( t1 < t2 ) {
+ idist = 1.0f / (t1-t2);
+ side = 1;
+ frac2 = (t1 + offset) * idist;
+ frac = (t1 - offset) * idist;
+ } else if (t1 > t2) {
+ idist = 1.0f / (t1-t2);
+ side = 0;
+ frac2 = (t1 - offset) * idist;
+ frac = (t1 + offset) * idist;
+ } else {
+ side = 0;
+ frac = 1.0f;
+ frac2 = 0.0f;
+ }
+
+ // move up to the node
+ if ( frac < 0.0f ) {
+ frac = 0.0f;
+ }
+ else if ( frac > 1.0f ) {
+ frac = 1.0f;
+ }
+
+ midf = p1f + (p2f - p1f)*frac;
+
+ mid[0] = p1[0] + frac*(p2[0] - p1[0]);
+ mid[1] = p1[1] + frac*(p2[1] - p1[1]);
+ mid[2] = p1[2] + frac*(p2[2] - p1[2]);
+
+ idCollisionModelManagerLocal::TraceThroughAxialBSPTree_r( tw, node->children[side], p1f, midf, p1, mid );
+
+
+ // go past the node
+ if ( frac2 < 0.0f ) {
+ frac2 = 0.0f;
+ }
+ else if ( frac2 > 1.0f ) {
+ frac2 = 1.0f;
+ }
+
+ midf = p1f + (p2f - p1f)*frac2;
+
+ mid[0] = p1[0] + frac2*(p2[0] - p1[0]);
+ mid[1] = p1[1] + frac2*(p2[1] - p1[1]);
+ mid[2] = p1[2] + frac2*(p2[2] - p1[2]);
+
+ idCollisionModelManagerLocal::TraceThroughAxialBSPTree_r( tw, node->children[side^1], midf, p2f, mid, p2 );
+}
+
+/*
+================
+idCollisionModelManagerLocal::TraceThroughModel
+================
+*/
+void idCollisionModelManagerLocal::TraceThroughModel( cm_traceWork_t *tw ) {
+ float d;
+ int i, numSteps;
+ idVec3 start, end;
+ idRotation rot;
+
+ if ( !tw->rotation ) {
+ // trace through spatial subdivision and then through leafs
+ idCollisionModelManagerLocal::TraceThroughAxialBSPTree_r( tw, tw->model->node, 0, 1, tw->start, tw->end );
+ }
+ else {
+ // approximate the rotation with a series of straight line movements
+ // total length covered along circle
+ d = tw->radius * DEG2RAD( tw->angle );
+ // if more than one step
+ if ( d > CIRCLE_APPROXIMATION_LENGTH ) {
+ // number of steps for the approximation
+ numSteps = (int) (CIRCLE_APPROXIMATION_LENGTH / d);
+ // start of approximation
+ start = tw->start;
+ // trace circle approximation steps through the BSP tree
+ for ( i = 0; i < numSteps; i++ ) {
+ // calculate next point on approximated circle
+ rot.Set( tw->origin, tw->axis, tw->angle * ((float) (i+1) / numSteps) );
+ end = start * rot;
+ // trace through spatial subdivision and then through leafs
+ idCollisionModelManagerLocal::TraceThroughAxialBSPTree_r( tw, tw->model->node, 0, 1, start, end );
+ // no need to continue if something was hit already
+ if ( tw->trace.fraction < 1.0f ) {
+ return;
+ }
+ start = end;
+ }
+ }
+ else {
+ start = tw->start;
+ }
+ // last step of the approximation
+ idCollisionModelManagerLocal::TraceThroughAxialBSPTree_r( tw, tw->model->node, 0, 1, start, tw->end );
+ }
+}
diff --git a/neo/cm/PreyCM/CollisionModel_translate.cpp b/neo/cm/PreyCM/CollisionModel_translate.cpp
new file mode 100644
index 00000000..b0d5a134
--- /dev/null
+++ b/neo/cm/PreyCM/CollisionModel_translate.cpp
@@ -0,0 +1,1100 @@
+
+/*
+===============================================================================
+
+ Trace model vs. polygonal model collision detection.
+
+===============================================================================
+*/
+
+#include "precompiled.h"
+#pragma hdrstop
+
+#include "CollisionModel_local.h"
+
+/*
+===============================================================================
+
+Collision detection for translational motion
+
+===============================================================================
+*/
+
+/*
+================
+idCollisionModelManagerLocal::TranslateEdgeThroughEdge
+
+ calculates fraction of the translation completed at which the edges collide
+================
+*/
+ID_INLINE int idCollisionModelManagerLocal::TranslateEdgeThroughEdge( idVec3 &cross, idPluecker &l1, idPluecker &l2, float *fraction ) {
+
+ float d, t;
+
+ /*
+
+ a = start of line
+ b = end of line
+ dir = movement direction
+ l1 = pluecker coordinate for line
+ l2 = pluecker coordinate for edge we might collide with
+ a+dir = start of line after movement
+ b+dir = end of line after movement
+ t = scale factor
+ solve pluecker inner product for t of line (a+t*dir : b+t*dir) and line l2
+
+ v[0] = (a[0]+t*dir[0]) * (b[1]+t*dir[1]) - (b[0]+t*dir[0]) * (a[1]+t*dir[1]);
+ v[1] = (a[0]+t*dir[0]) * (b[2]+t*dir[2]) - (b[0]+t*dir[0]) * (a[2]+t*dir[2]);
+ v[2] = (a[0]+t*dir[0]) - (b[0]+t*dir[0]);
+ v[3] = (a[1]+t*dir[1]) * (b[2]+t*dir[2]) - (b[1]+t*dir[1]) * (a[2]+t*dir[2]);
+ v[4] = (a[2]+t*dir[2]) - (b[2]+t*dir[2]);
+ v[5] = (b[1]+t*dir[1]) - (a[1]+t*dir[1]);
+
+ l2[0] * v[4] + l2[1] * v[5] + l2[2] * v[3] + l2[4] * v[0] + l2[5] * v[1] + l2[3] * v[2] = 0;
+
+ solve t
+
+ v[0] = (a[0]+t*dir[0]) * (b[1]+t*dir[1]) - (b[0]+t*dir[0]) * (a[1]+t*dir[1]);
+ v[0] = (a[0]*b[1]) + a[0]*t*dir[1] + b[1]*t*dir[0] + (t*t*dir[0]*dir[1]) -
+ ((b[0]*a[1]) + b[0]*t*dir[1] + a[1]*t*dir[0] + (t*t*dir[0]*dir[1]));
+ v[0] = a[0]*b[1] + a[0]*t*dir[1] + b[1]*t*dir[0] - b[0]*a[1] - b[0]*t*dir[1] - a[1]*t*dir[0];
+
+ v[1] = (a[0]+t*dir[0]) * (b[2]+t*dir[2]) - (b[0]+t*dir[0]) * (a[2]+t*dir[2]);
+ v[1] = (a[0]*b[2]) + a[0]*t*dir[2] + b[2]*t*dir[0] + (t*t*dir[0]*dir[2]) -
+ ((b[0]*a[2]) + b[0]*t*dir[2] + a[2]*t*dir[0] + (t*t*dir[0]*dir[2]));
+ v[1] = a[0]*b[2] + a[0]*t*dir[2] + b[2]*t*dir[0] - b[0]*a[2] - b[0]*t*dir[2] - a[2]*t*dir[0];
+
+ v[2] = (a[0]+t*dir[0]) - (b[0]+t*dir[0]);
+ v[2] = a[0] - b[0];
+
+ v[3] = (a[1]+t*dir[1]) * (b[2]+t*dir[2]) - (b[1]+t*dir[1]) * (a[2]+t*dir[2]);
+ v[3] = (a[1]*b[2]) + a[1]*t*dir[2] + b[2]*t*dir[1] + (t*t*dir[1]*dir[2]) -
+ ((b[1]*a[2]) + b[1]*t*dir[2] + a[2]*t*dir[1] + (t*t*dir[1]*dir[2]));
+ v[3] = a[1]*b[2] + a[1]*t*dir[2] + b[2]*t*dir[1] - b[1]*a[2] - b[1]*t*dir[2] - a[2]*t*dir[1];
+
+ v[4] = (a[2]+t*dir[2]) - (b[2]+t*dir[2]);
+ v[4] = a[2] - b[2];
+
+ v[5] = (b[1]+t*dir[1]) - (a[1]+t*dir[1]);
+ v[5] = b[1] - a[1];
+
+
+ v[0] = a[0]*b[1] + a[0]*t*dir[1] + b[1]*t*dir[0] - b[0]*a[1] - b[0]*t*dir[1] - a[1]*t*dir[0];
+ v[1] = a[0]*b[2] + a[0]*t*dir[2] + b[2]*t*dir[0] - b[0]*a[2] - b[0]*t*dir[2] - a[2]*t*dir[0];
+ v[2] = a[0] - b[0];
+ v[3] = a[1]*b[2] + a[1]*t*dir[2] + b[2]*t*dir[1] - b[1]*a[2] - b[1]*t*dir[2] - a[2]*t*dir[1];
+ v[4] = a[2] - b[2];
+ v[5] = b[1] - a[1];
+
+ v[0] = (a[0]*dir[1] + b[1]*dir[0] - b[0]*dir[1] - a[1]*dir[0]) * t + a[0]*b[1] - b[0]*a[1];
+ v[1] = (a[0]*dir[2] + b[2]*dir[0] - b[0]*dir[2] - a[2]*dir[0]) * t + a[0]*b[2] - b[0]*a[2];
+ v[2] = a[0] - b[0];
+ v[3] = (a[1]*dir[2] + b[2]*dir[1] - b[1]*dir[2] - a[2]*dir[1]) * t + a[1]*b[2] - b[1]*a[2];
+ v[4] = a[2] - b[2];
+ v[5] = b[1] - a[1];
+
+ l2[4] * (a[0]*dir[1] + b[1]*dir[0] - b[0]*dir[1] - a[1]*dir[0]) * t + l2[4] * (a[0]*b[1] - b[0]*a[1])
+ + l2[5] * (a[0]*dir[2] + b[2]*dir[0] - b[0]*dir[2] - a[2]*dir[0]) * t + l2[5] * (a[0]*b[2] - b[0]*a[2])
+ + l2[3] * (a[0] - b[0])
+ + l2[2] * (a[1]*dir[2] + b[2]*dir[1] - b[1]*dir[2] - a[2]*dir[1]) * t + l2[2] * (a[1]*b[2] - b[1]*a[2])
+ + l2[0] * (a[2] - b[2])
+ + l2[1] * (b[1] - a[1]) = 0
+
+ t = (- l2[4] * (a[0]*b[1] - b[0]*a[1]) -
+ l2[5] * (a[0]*b[2] - b[0]*a[2]) -
+ l2[3] * (a[0] - b[0]) -
+ l2[2] * (a[1]*b[2] - b[1]*a[2]) -
+ l2[0] * (a[2] - b[2]) -
+ l2[1] * (b[1] - a[1])) /
+ (l2[4] * (a[0]*dir[1] + b[1]*dir[0] - b[0]*dir[1] - a[1]*dir[0]) +
+ l2[5] * (a[0]*dir[2] + b[2]*dir[0] - b[0]*dir[2] - a[2]*dir[0]) +
+ l2[2] * (a[1]*dir[2] + b[2]*dir[1] - b[1]*dir[2] - a[2]*dir[1]));
+
+ d = l2[4] * (a[0]*dir[1] + b[1]*dir[0] - b[0]*dir[1] - a[1]*dir[0]) +
+ l2[5] * (a[0]*dir[2] + b[2]*dir[0] - b[0]*dir[2] - a[2]*dir[0]) +
+ l2[2] * (a[1]*dir[2] + b[2]*dir[1] - b[1]*dir[2] - a[2]*dir[1]);
+
+ t = - ( l2[4] * (a[0]*b[1] - b[0]*a[1]) +
+ l2[5] * (a[0]*b[2] - b[0]*a[2]) +
+ l2[3] * (a[0] - b[0]) +
+ l2[2] * (a[1]*b[2] - b[1]*a[2]) +
+ l2[0] * (a[2] - b[2]) +
+ l2[1] * (b[1] - a[1]));
+ t /= d;
+
+ MrE pats Pluecker on the head.. good monkey
+
+ edgeDir = a - b;
+ d = l2[4] * (edgeDir[0]*dir[1] - edgeDir[1]*dir[0]) +
+ l2[5] * (edgeDir[0]*dir[2] - edgeDir[2]*dir[0]) +
+ l2[2] * (edgeDir[1]*dir[2] - edgeDir[2]*dir[1]);
+ */
+
+ d = l2[4] * cross[0] + l2[5] * cross[1] + l2[2] * cross[2];
+
+ if ( d == 0.0f ) {
+ *fraction = 1.0f;
+ // no collision ever
+ return false;
+ }
+
+ t = -l1.PermutedInnerProduct( l2 );
+ // if the lines cross each other to begin with
+ if ( t == 0.0f ) {
+ *fraction = 0.0f;
+ return true;
+ }
+ // fraction of movement at the time the lines cross each other
+ *fraction = t / d;
+ return true;
+}
+
+/*
+================
+CM_AddContact
+================
+*/
+ID_INLINE void CM_AddContact( cm_traceWork_t *tw ) {
+
+ if ( tw->numContacts >= tw->maxContacts ) {
+ return;
+ }
+ // copy contact information from trace_t
+ tw->contacts[tw->numContacts] = tw->trace.c;
+ tw->numContacts++;
+ // set fraction back to 1 to find all other contacts
+ tw->trace.fraction = 1.0f;
+}
+
+/*
+================
+CM_SetVertexSidedness
+
+ stores for the given model vertex at which side of one of the trm edges it passes
+================
+*/
+ID_INLINE void CM_SetVertexSidedness( cm_vertex_t *v, const idPluecker &vpl, const idPluecker &epl, const int bitNum ) {
+ if ( !(v->sideSet & (1<side = (v->side & ~(1<sideSet |= (1 << bitNum);
+ }
+}
+
+/*
+================
+CM_SetEdgeSidedness
+
+ stores for the given model edge at which side one of the trm vertices
+================
+*/
+ID_INLINE void CM_SetEdgeSidedness( cm_edge_t *edge, const idPluecker &vpl, const idPluecker &epl, const int bitNum ) {
+ if ( !(edge->sideSet & (1<side = (edge->side & ~(1<sideSet |= (1 << bitNum);
+ }
+}
+
+/*
+================
+idCollisionModelManagerLocal::TranslateTrmEdgeThroughPolygon
+================
+*/
+void idCollisionModelManagerLocal::TranslateTrmEdgeThroughPolygon( cm_traceWork_t *tw, cm_polygon_t *poly, cm_trmEdge_t *trmEdge ) {
+ int i, edgeNum;
+ float f1, f2, dist, d1, d2;
+ idVec3 start, end, normal;
+ cm_edge_t *edge;
+ cm_vertex_t *v1, *v2;
+ idPluecker *pl, epsPl;
+
+ // check edges for a collision
+ for ( i = 0; i < poly->numEdges; i++) {
+ edgeNum = poly->edges[i];
+ edge = tw->model->edges + abs(edgeNum);
+ // if this edge is already checked
+ if ( edge->checkcount == idCollisionModelManagerLocal::checkCount ) {
+ continue;
+ }
+ // can never collide with internal edges
+ if ( edge->internal ) {
+ continue;
+ }
+ pl = &tw->polygonEdgePlueckerCache[i];
+ // get the sides at which the trm edge vertices pass the polygon edge
+ CM_SetEdgeSidedness( edge, *pl, tw->vertices[trmEdge->vertexNum[0]].pl, trmEdge->vertexNum[0] );
+ CM_SetEdgeSidedness( edge, *pl, tw->vertices[trmEdge->vertexNum[1]].pl, trmEdge->vertexNum[1] );
+ // if the trm edge start and end vertex do not pass the polygon edge at different sides
+ if ( !(((edge->side >> trmEdge->vertexNum[0]) ^ (edge->side >> trmEdge->vertexNum[1])) & 1) ) {
+ continue;
+ }
+ // get the sides at which the polygon edge vertices pass the trm edge
+ v1 = tw->model->vertices + edge->vertexNum[INTSIGNBITSET(edgeNum)];
+ CM_SetVertexSidedness( v1, tw->polygonVertexPlueckerCache[i], trmEdge->pl, trmEdge->bitNum );
+ v2 = tw->model->vertices + edge->vertexNum[INTSIGNBITNOTSET(edgeNum)];
+ CM_SetVertexSidedness( v2, tw->polygonVertexPlueckerCache[i+1], trmEdge->pl, trmEdge->bitNum );
+ // if the polygon edge start and end vertex do not pass the trm edge at different sides
+ if ( !((v1->side ^ v2->side) & (1<bitNum)) ) {
+ continue;
+ }
+ // if there is no possible collision between the trm edge and the polygon edge
+ if ( !idCollisionModelManagerLocal::TranslateEdgeThroughEdge( trmEdge->cross, trmEdge->pl, *pl, &f1 ) ) {
+ continue;
+ }
+ // if moving away from edge
+ if ( f1 < 0.0f ) {
+ continue;
+ }
+
+ // pluecker coordinate for epsilon expanded edge
+ epsPl.FromLine( tw->model->vertices[edge->vertexNum[0]].p + edge->normal * CM_CLIP_EPSILON,
+ tw->model->vertices[edge->vertexNum[1]].p + edge->normal * CM_CLIP_EPSILON );
+ // calculate collision fraction with epsilon expanded edge
+ if ( !idCollisionModelManagerLocal::TranslateEdgeThroughEdge( trmEdge->cross, trmEdge->pl, epsPl, &f2 ) ) {
+ continue;
+ }
+ // if no collision with epsilon edge or moving away from edge
+ if ( f2 > 1.0f || f1 < f2 ) {
+ continue;
+ }
+
+ if ( f2 < 0.0f ) {
+ f2 = 0.0f;
+ }
+
+ if ( f2 < tw->trace.fraction ) {
+ tw->trace.fraction = f2;
+ // create plane with normal vector orthogonal to both the polygon edge and the trm edge
+ start = tw->model->vertices[edge->vertexNum[0]].p;
+ end = tw->model->vertices[edge->vertexNum[1]].p;
+ tw->trace.c.normal = ( end - start ).Cross( trmEdge->end - trmEdge->start );
+ // FIXME: do this normalize when we know the first collision
+ tw->trace.c.normal.Normalize();
+ tw->trace.c.dist = tw->trace.c.normal * start;
+ // make sure the collision plane faces the trace model
+ if ( tw->trace.c.normal * trmEdge->start - tw->trace.c.dist < 0.0f ) {
+ tw->trace.c.normal = -tw->trace.c.normal;
+ tw->trace.c.dist = -tw->trace.c.dist;
+ }
+ tw->trace.c.contents = poly->contents;
+ tw->trace.c.material = poly->material;
+ tw->trace.c.type = CONTACT_EDGE;
+ tw->trace.c.modelFeature = edgeNum;
+ tw->trace.c.trmFeature = trmEdge - tw->edges;
+ // calculate collision point
+ normal[0] = trmEdge->cross[2];
+ normal[1] = -trmEdge->cross[1];
+ normal[2] = trmEdge->cross[0];
+ dist = normal * trmEdge->start;
+ d1 = normal * start - dist;
+ d2 = normal * end - dist;
+ if (d1 == d2) { //HUMANHEAD rww - CUFPF
+ f1 = 0.0f;
+ }
+ else {
+ f1 = d1 / ( d1 - d2 );
+ }
+ //assert( f1 >= 0.0f && f1 <= 1.0f );
+ tw->trace.c.point = start + f1 * ( end - start );
+ // if retrieving contacts
+ if ( tw->getContacts ) {
+ CM_AddContact( tw );
+ }
+ }
+ }
+}
+
+/*
+================
+CM_TranslationPlaneFraction
+================
+*/
+
+#if 0
+
+float CM_TranslationPlaneFraction( idPlane &plane, idVec3 &start, idVec3 &end ) {
+ float d1, d2;
+
+ d2 = plane.Distance( end );
+ // if the end point is closer to the plane than an epsilon we still take it for a collision
+ if ( d2 >= CM_CLIP_EPSILON ) {
+ return 1.0f;
+ }
+ d1 = plane.Distance( start );
+
+ // if completely behind the polygon
+ if ( d1 <= 0.0f ) {
+ return 1.0f;
+ }
+ // leaves polygon
+ if ( d1 <= d2 ) {
+ return 1.0f;
+ }
+ return (d1-CM_CLIP_EPSILON) / (d1-d2);
+}
+
+#else
+
+float CM_TranslationPlaneFraction( idPlane &plane, idVec3 &start, idVec3 &end ) {
+ float d1, d2, d2eps;
+
+ d2 = plane.Distance( end );
+ // if the end point is closer to the plane than an epsilon we still take it for a collision
+ // if ( d2 >= CM_CLIP_EPSILON ) {
+ d2eps = d2 - CM_CLIP_EPSILON;
+ if ( FLOATSIGNBITNOTSET(d2eps) ) {
+ return 1.0f;
+ }
+ d1 = plane.Distance( start );
+
+ // if completely behind the polygon
+ if ( FLOATSIGNBITSET(d1) ) {
+ return 1.0f;
+ }
+ // if going towards the front of the plane and
+ // the start and end point are not at equal distance from the plane
+ // if ( d1 > d2 )
+ d2 = d1 - d2;
+ if ( d2 <= 0.0f ) {
+ return 1.0f;
+ }
+ return (d1-CM_CLIP_EPSILON) / d2;
+}
+
+#endif
+
+/*
+================
+idCollisionModelManagerLocal::TranslateTrmVertexThroughPolygon
+================
+*/
+void idCollisionModelManagerLocal::TranslateTrmVertexThroughPolygon( cm_traceWork_t *tw, cm_polygon_t *poly, cm_trmVertex_t *v, int bitNum ) {
+ int i, edgeNum;
+ float f;
+ cm_edge_t *edge;
+
+ f = CM_TranslationPlaneFraction( poly->plane, v->p, v->endp );
+ if ( f < tw->trace.fraction ) {
+
+ for ( i = 0; i < poly->numEdges; i++ ) {
+ edgeNum = poly->edges[i];
+ edge = tw->model->edges + abs(edgeNum);
+ CM_SetEdgeSidedness( edge, tw->polygonEdgePlueckerCache[i], v->pl, bitNum );
+ if ( INTSIGNBITSET(edgeNum) ^ ((edge->side >> bitNum) & 1) ) {
+ return;
+ }
+ }
+ if ( f < 0.0f ) {
+ f = 0.0f;
+ }
+ tw->trace.fraction = f;
+ // collision plane is the polygon plane
+ tw->trace.c.normal = poly->plane.Normal();
+ tw->trace.c.dist = poly->plane.Dist();
+ tw->trace.c.contents = poly->contents;
+ tw->trace.c.material = poly->material;
+ tw->trace.c.type = CONTACT_TRMVERTEX;
+ tw->trace.c.modelFeature = *reinterpret_cast(&poly);
+ tw->trace.c.trmFeature = v - tw->vertices;
+ tw->trace.c.point = v->p + tw->trace.fraction * ( v->endp - v->p );
+ // if retrieving contacts
+ if ( tw->getContacts ) {
+ CM_AddContact( tw );
+ // no need to store the trm vertex more than once as a contact
+ v->used = false;
+ }
+ }
+}
+
+/*
+================
+idCollisionModelManagerLocal::TranslatePointThroughPolygon
+================
+*/
+void idCollisionModelManagerLocal::TranslatePointThroughPolygon( cm_traceWork_t *tw, cm_polygon_t *poly, cm_trmVertex_t *v ) {
+ int i, edgeNum;
+ float f;
+ cm_edge_t *edge;
+ idPluecker pl;
+
+ f = CM_TranslationPlaneFraction( poly->plane, v->p, v->endp );
+ if ( f < tw->trace.fraction ) {
+
+ for ( i = 0; i < poly->numEdges; i++ ) {
+ edgeNum = poly->edges[i];
+ edge = tw->model->edges + abs(edgeNum);
+ // if we didn't yet calculate the sidedness for this edge
+ if ( edge->checkcount != idCollisionModelManagerLocal::checkCount ) {
+ float fl;
+ edge->checkcount = idCollisionModelManagerLocal::checkCount;
+ pl.FromLine(tw->model->vertices[edge->vertexNum[0]].p, tw->model->vertices[edge->vertexNum[1]].p);
+ fl = v->pl.PermutedInnerProduct( pl );
+ edge->side = FLOATSIGNBITSET(fl);
+ }
+ // if the point passes the edge at the wrong side
+ //if ( (edgeNum > 0) == edge->side ) {
+ if ( INTSIGNBITSET(edgeNum) ^ edge->side ) {
+ return;
+ }
+ }
+ if ( f < 0.0f ) {
+ f = 0.0f;
+ }
+ tw->trace.fraction = f;
+ // collision plane is the polygon plane
+ tw->trace.c.normal = poly->plane.Normal();
+ tw->trace.c.dist = poly->plane.Dist();
+ tw->trace.c.contents = poly->contents;
+ tw->trace.c.material = poly->material;
+ tw->trace.c.type = CONTACT_TRMVERTEX;
+ tw->trace.c.modelFeature = *reinterpret_cast(&poly);
+ tw->trace.c.trmFeature = v - tw->vertices;
+ tw->trace.c.point = v->p + tw->trace.fraction * ( v->endp - v->p );
+ // if retrieving contacts
+ if ( tw->getContacts ) {
+ CM_AddContact( tw );
+ // no need to store the trm vertex more than once as a contact
+ v->used = false;
+ }
+ }
+}
+
+/*
+================
+idCollisionModelManagerLocal::TranslateVertexThroughTrmPolygon
+================
+*/
+void idCollisionModelManagerLocal::TranslateVertexThroughTrmPolygon( cm_traceWork_t *tw, cm_trmPolygon_t *trmpoly, cm_polygon_t *poly, cm_vertex_t *v, idVec3 &endp, idPluecker &pl ) {
+ int i, edgeNum;
+ float f;
+ cm_trmEdge_t *edge;
+
+ f = CM_TranslationPlaneFraction( trmpoly->plane, v->p, endp );
+ if ( f < tw->trace.fraction ) {
+
+ for ( i = 0; i < trmpoly->numEdges; i++ ) {
+ edgeNum = trmpoly->edges[i];
+ edge = tw->edges + abs(edgeNum);
+
+ CM_SetVertexSidedness( v, pl, edge->pl, edge->bitNum );
+ if ( INTSIGNBITSET(edgeNum) ^ ((v->side >> edge->bitNum) & 1) ) {
+ return;
+ }
+ }
+ if ( f < 0.0f ) {
+ f = 0.0f;
+ }
+ tw->trace.fraction = f;
+ // collision plane is the inverse trm polygon plane
+ tw->trace.c.normal = -trmpoly->plane.Normal();
+ tw->trace.c.dist = -trmpoly->plane.Dist();
+ tw->trace.c.contents = poly->contents;
+ tw->trace.c.material = poly->material;
+ tw->trace.c.type = CONTACT_MODELVERTEX;
+ tw->trace.c.modelFeature = v - tw->model->vertices;
+ tw->trace.c.trmFeature = trmpoly - tw->polys;
+ tw->trace.c.point = v->p + tw->trace.fraction * ( endp - v->p );
+ // if retrieving contacts
+ if ( tw->getContacts ) {
+ CM_AddContact( tw );
+ }
+ }
+}
+
+/*
+================
+idCollisionModelManagerLocal::TranslateTrmThroughPolygon
+
+ returns true if the polygon blocks the complete translation
+================
+*/
+bool idCollisionModelManagerLocal::TranslateTrmThroughPolygon( cm_traceWork_t *tw, cm_polygon_t *p ) {
+ int i, j, k, edgeNum;
+ float fraction, d;
+ idVec3 endp;
+ idPluecker *pl;
+ cm_trmVertex_t *bv;
+ cm_trmEdge_t *be;
+ cm_trmPolygon_t *bp;
+ cm_vertex_t *v;
+ cm_edge_t *e;
+
+ // if already checked this polygon
+ if ( p->checkcount == idCollisionModelManagerLocal::checkCount ) {
+ return false;
+ }
+ p->checkcount = idCollisionModelManagerLocal::checkCount;
+
+ // if this polygon does not have the right contents behind it
+ if ( !(p->contents & tw->contents) ) {
+ return false;
+ }
+
+ // if the the trace bounds do not intersect the polygon bounds
+ if ( !tw->bounds.IntersectsBounds( p->bounds ) ) {
+ return false;
+ }
+
+ // only collide with the polygon if approaching at the front
+ if ( ( p->plane.Normal() * tw->dir ) > 0.0f ) {
+ return false;
+ }
+
+ // if the polygon is too far from the first heart plane
+ d = p->bounds.PlaneDistance( tw->heartPlane1 );
+ if ( idMath::Fabs(d) > tw->maxDistFromHeartPlane1 ) {
+ return false;
+ }
+
+ // if the polygon is too far from the second heart plane
+ d = p->bounds.PlaneDistance( tw->heartPlane2 );
+ if ( idMath::Fabs(d) > tw->maxDistFromHeartPlane2 ) {
+ return false;
+ }
+ fraction = tw->trace.fraction;
+
+ // fast point trace
+ if ( tw->pointTrace ) {
+ idCollisionModelManagerLocal::TranslatePointThroughPolygon( tw, p, &tw->vertices[0] );
+ }
+ else {
+
+ // trace bounds should cross polygon plane
+ switch ( tw->bounds.PlaneSide( p->plane ) ) {
+ case PLANESIDE_CROSS:
+ break;
+ case PLANESIDE_FRONT:
+ if ( tw->model->isConvex ) {
+ tw->quickExit = true;
+ return true;
+ }
+ default:
+ return false;
+ }
+
+ // calculate pluecker coordinates for the polygon edges and polygon vertices
+ for ( i = 0; i < p->numEdges; i++ ) {
+ edgeNum = p->edges[i];
+ e = tw->model->edges + abs(edgeNum);
+ // reset sidedness cache if this is the first time we encounter this edge during this trace
+ if ( e->checkcount != idCollisionModelManagerLocal::checkCount ) {
+ e->sideSet = 0;
+ }
+ // pluecker coordinate for edge
+ tw->polygonEdgePlueckerCache[i].FromLine( tw->model->vertices[e->vertexNum[0]].p,
+ tw->model->vertices[e->vertexNum[1]].p );
+
+ v = &tw->model->vertices[e->vertexNum[INTSIGNBITSET(edgeNum)]];
+ // reset sidedness cache if this is the first time we encounter this vertex during this trace
+ if ( v->checkcount != idCollisionModelManagerLocal::checkCount ) {
+ v->sideSet = 0;
+ }
+ // pluecker coordinate for vertex movement vector
+ tw->polygonVertexPlueckerCache[i].FromRay( v->p, -tw->dir );
+ }
+ // copy first to last so we can easily cycle through for the edges
+ tw->polygonVertexPlueckerCache[p->numEdges] = tw->polygonVertexPlueckerCache[0];
+
+ // trace trm vertices through polygon
+ for ( i = 0; i < tw->numVerts; i++ ) {
+ bv = tw->vertices + i;
+ if ( bv->used ) {
+ idCollisionModelManagerLocal::TranslateTrmVertexThroughPolygon( tw, p, bv, i );
+ }
+ }
+
+ // trace trm edges through polygon
+ for ( i = 1; i <= tw->numEdges; i++ ) {
+ be = tw->edges + i;
+ if ( be->used ) {
+ idCollisionModelManagerLocal::TranslateTrmEdgeThroughPolygon( tw, p, be);
+ }
+ }
+
+ // trace all polygon vertices through the trm
+ for ( i = 0; i < p->numEdges; i++ ) {
+ edgeNum = p->edges[i];
+ e = tw->model->edges + abs(edgeNum);
+
+ if ( e->checkcount == idCollisionModelManagerLocal::checkCount ) {
+ continue;
+ }
+ // set edge check count
+ e->checkcount = idCollisionModelManagerLocal::checkCount;
+ // can never collide with internal edges
+ if ( e->internal ) {
+ continue;
+ }
+ // got to check both vertices because we skip internal edges
+ for ( k = 0; k < 2; k++ ) {
+
+ v = tw->model->vertices + e->vertexNum[k ^ INTSIGNBITSET(edgeNum)];
+ // if this vertex is already checked
+ if ( v->checkcount == idCollisionModelManagerLocal::checkCount ) {
+ continue;
+ }
+ // set vertex check count
+ v->checkcount = idCollisionModelManagerLocal::checkCount;
+
+ // if the vertex is outside the trace bounds
+ if ( !tw->bounds.ContainsPoint( v->p ) ) {
+ continue;
+ }
+
+ // vertex end point after movement
+ endp = v->p - tw->dir;
+ // pluecker coordinate for vertex movement vector
+ pl = &tw->polygonVertexPlueckerCache[i+k];
+
+ for ( j = 0; j < tw->numPolys; j++ ) {
+ bp = tw->polys + j;
+ if ( bp->used ) {
+ idCollisionModelManagerLocal::TranslateVertexThroughTrmPolygon( tw, bp, p, v, endp, *pl );
+ }
+ }
+ }
+ }
+ }
+
+ // if there was a collision with this polygon and we are not retrieving contacts
+ if ( tw->trace.fraction < fraction && !tw->getContacts ) {
+ fraction = tw->trace.fraction;
+ endp = tw->start + fraction * tw->dir;
+ // decrease bounds
+ for ( i = 0; i < 3; i++ ) {
+ if ( tw->start[i] < endp[i] ) {
+ tw->bounds[0][i] = tw->start[i] + tw->size[0][i] - CM_BOX_EPSILON;
+ tw->bounds[1][i] = endp[i] + tw->size[1][i] + CM_BOX_EPSILON;
+ }
+ else {
+ tw->bounds[0][i] = endp[i] + tw->size[0][i] - CM_BOX_EPSILON;
+ tw->bounds[1][i] = tw->start[i] + tw->size[1][i] + CM_BOX_EPSILON;
+ }
+ }
+ }
+
+ return ( tw->trace.fraction == 0.0f );
+}
+
+/*
+================
+idCollisionModelManagerLocal::SetupTrm
+================
+*/
+void idCollisionModelManagerLocal::SetupTrm( cm_traceWork_t *tw, const idTraceModel *trm ) {
+ int i, j;
+
+ // vertices
+ tw->numVerts = trm->numVerts;
+ for ( i = 0; i < trm->numVerts; i++ ) {
+ tw->vertices[i].p = trm->verts[i];
+ tw->vertices[i].used = false;
+ }
+ // edges
+ tw->numEdges = trm->numEdges;
+ for ( i = 1; i <= trm->numEdges; i++ ) {
+ tw->edges[i].vertexNum[0] = trm->edges[i].v[0];
+ tw->edges[i].vertexNum[1] = trm->edges[i].v[1];
+ tw->edges[i].used = false;
+ }
+ // polygons
+ tw->numPolys = trm->numPolys;
+ for ( i = 0; i < trm->numPolys; i++ ) {
+ tw->polys[i].numEdges = trm->polys[i].numEdges;
+ for ( j = 0; j < trm->polys[i].numEdges; j++ ) {
+ tw->polys[i].edges[j] = trm->polys[i].edges[j];
+ }
+ tw->polys[i].plane.SetNormal( trm->polys[i].normal );
+ tw->polys[i].used = false;
+ }
+ // is the trace model convex or not
+ tw->isConvex = trm->isConvex;
+}
+
+/*
+================
+idCollisionModelManagerLocal::SetupTranslationHeartPlanes
+================
+*/
+void idCollisionModelManagerLocal::SetupTranslationHeartPlanes( cm_traceWork_t *tw ) {
+ idVec3 dir, normal1, normal2;
+
+ // calculate trace heart planes
+ dir = tw->dir;
+ dir.Normalize();
+ dir.NormalVectors( normal1, normal2 );
+ tw->heartPlane1.SetNormal( normal1 );
+ tw->heartPlane1.FitThroughPoint( tw->start );
+ tw->heartPlane2.SetNormal( normal2 );
+ tw->heartPlane2.FitThroughPoint( tw->start );
+}
+
+/*
+================
+idCollisionModelManagerLocal::Translation
+================
+*/
+#ifdef _DEBUG
+static int entered = 0;
+#endif
+
+void idCollisionModelManagerLocal::Translation( trace_t *results, const idVec3 &start, const idVec3 &end,
+ const idTraceModel *trm, const idMat3 &trmAxis, int contentMask,
+ cmHandle_t model, const idVec3 &modelOrigin, const idMat3 &modelAxis ) {
+
+ int i, j;
+ float dist;
+ bool model_rotated, trm_rotated;
+ idVec3 dir1, dir2, dir;
+ idMat3 invModelAxis, tmpAxis;
+ cm_trmPolygon_t *poly;
+ cm_trmEdge_t *edge;
+ cm_trmVertex_t *vert;
+ ALIGN16( static cm_traceWork_t tw; )
+
+ assert( ((byte *)&start) < ((byte *)results) || ((byte *)&start) > (((byte *)results) + sizeof( trace_t )) );
+ assert( ((byte *)&end) < ((byte *)results) || ((byte *)&end) > (((byte *)results) + sizeof( trace_t )) );
+ assert( ((byte *)&trmAxis) < ((byte *)results) || ((byte *)&trmAxis) > (((byte *)results) + sizeof( trace_t )) );
+
+ memset( results, 0, sizeof( *results ) );
+
+ if ( model < 0 || model > MAX_SUBMODELS || model > idCollisionModelManagerLocal::maxModels ) {
+ common->Printf("idCollisionModelManagerLocal::Translation: invalid model handle\n");
+ return;
+ }
+ if ( !idCollisionModelManagerLocal::models[model] ) {
+ common->Printf("idCollisionModelManagerLocal::Translation: invalid model\n");
+ return;
+ }
+
+ // if case special position test
+ if ( start[0] == end[0] && start[1] == end[1] && start[2] == end[2] ) {
+ idCollisionModelManagerLocal::ContentsTrm( results, start, trm, trmAxis, contentMask, model, modelOrigin, modelAxis );
+ return;
+ }
+
+#ifdef _DEBUG
+ bool startsolid = false;
+ // test whether or not stuck to begin with
+ if ( cm_debugCollision.GetBool() ) {
+ if ( !entered && !idCollisionModelManagerLocal::getContacts ) {
+ entered = 1;
+ // if already fucked up to begin with
+ if ( idCollisionModelManagerLocal::Contents( start, trm, trmAxis, -1, model, modelOrigin, modelAxis ) & contentMask ) {
+ startsolid = true;
+ }
+ entered = 0;
+ }
+ }
+#endif
+
+ idCollisionModelManagerLocal::checkCount++;
+
+ tw.trace.fraction = 1.0f;
+ tw.trace.c.contents = 0;
+ tw.trace.c.type = CONTACT_NONE;
+ tw.trace.c.id = 0; // HUMANHEAD pdm: initialize so we don't get bogus values back
+ tw.contents = contentMask;
+ tw.isConvex = true;
+ tw.rotation = false;
+ tw.positionTest = false;
+ tw.quickExit = false;
+ tw.getContacts = idCollisionModelManagerLocal::getContacts;
+ tw.contacts = idCollisionModelManagerLocal::contacts;
+ tw.maxContacts = idCollisionModelManagerLocal::maxContacts;
+ tw.numContacts = 0;
+ tw.model = idCollisionModelManagerLocal::models[model];
+ tw.start = start - modelOrigin;
+ tw.end = end - modelOrigin;
+ tw.dir = end - start;
+
+ model_rotated = modelAxis.IsRotated();
+ if ( model_rotated ) {
+ invModelAxis = modelAxis.Transpose();
+ }
+
+ // if optimized point trace
+ if ( !trm || ( trm->bounds[1][0] - trm->bounds[0][0] <= 0.0f &&
+ trm->bounds[1][1] - trm->bounds[0][1] <= 0.0f &&
+ trm->bounds[1][2] - trm->bounds[0][2] <= 0.0f ) ) {
+
+ if ( model_rotated ) {
+ // rotate trace instead of model
+ tw.start *= invModelAxis;
+ tw.end *= invModelAxis;
+ tw.dir *= invModelAxis;
+ }
+
+ // trace bounds
+ for ( i = 0; i < 3; i++ ) {
+ if ( tw.start[i] < tw.end[i] ) {
+ tw.bounds[0][i] = tw.start[i] - CM_BOX_EPSILON;
+ tw.bounds[1][i] = tw.end[i] + CM_BOX_EPSILON;
+ }
+ else {
+ tw.bounds[0][i] = tw.end[i] - CM_BOX_EPSILON;
+ tw.bounds[1][i] = tw.start[i] + CM_BOX_EPSILON;
+ }
+ }
+ tw.extents[0] = tw.extents[1] = tw.extents[2] = CM_BOX_EPSILON;
+ tw.size.Zero();
+
+ // setup trace heart planes
+ idCollisionModelManagerLocal::SetupTranslationHeartPlanes( &tw );
+ tw.maxDistFromHeartPlane1 = CM_BOX_EPSILON;
+ tw.maxDistFromHeartPlane2 = CM_BOX_EPSILON;
+ // collision with single point
+ tw.numVerts = 1;
+ tw.vertices[0].p = tw.start;
+ tw.vertices[0].endp = tw.vertices[0].p + tw.dir;
+ tw.vertices[0].pl.FromRay( tw.vertices[0].p, tw.dir );
+ tw.numEdges = tw.numPolys = 0;
+ tw.pointTrace = true;
+ // trace through the model
+ idCollisionModelManagerLocal::TraceThroughModel( &tw );
+ // store results
+ *results = tw.trace;
+ results->endpos = start + results->fraction * (end - start);
+ results->endAxis = mat3_identity;
+
+ if ( results->fraction < 1.0f ) {
+ // rotate trace plane normal if there was a collision with a rotated model
+ if ( model_rotated ) {
+ results->c.normal *= modelAxis;
+ results->c.point *= modelAxis;
+ }
+ results->c.point += modelOrigin;
+ results->c.dist += modelOrigin * results->c.normal;
+ }
+ idCollisionModelManagerLocal::numContacts = tw.numContacts;
+ return;
+ }
+
+ // the trace fraction is too inaccurate to describe translations over huge distances
+ if ( tw.dir.LengthSqr() > Square( CM_MAX_TRACE_DIST ) ) {
+ results->fraction = 0.0f;
+ results->endpos = start;
+ results->endAxis = trmAxis;
+ results->c.normal = vec3_origin;
+ results->c.material = NULL;
+ results->c.point = start;
+ if ( session->rw ) {
+ session->rw->DebugArrow( colorRed, start, end, 1 );
+ }
+ common->Printf( "idCollisionModelManagerLocal::Translation: huge translation\n" );
+ common->Printf( " of entity: %s\n", tw.model->name.c_str() ); // HUMANHEAD
+ return;
+ }
+
+ tw.pointTrace = false;
+ tw.size.Clear();
+
+ // setup trm structure
+ idCollisionModelManagerLocal::SetupTrm( &tw, trm );
+
+ trm_rotated = trmAxis.IsRotated();
+
+ // calculate vertex positions
+ if ( trm_rotated ) {
+ for ( i = 0; i < tw.numVerts; i++ ) {
+ // rotate trm around the start position
+ tw.vertices[i].p *= trmAxis;
+ }
+ }
+ for ( i = 0; i < tw.numVerts; i++ ) {
+ // set trm at start position
+ tw.vertices[i].p += tw.start;
+ }
+ if ( model_rotated ) {
+ for ( i = 0; i < tw.numVerts; i++ ) {
+ // rotate trm around model instead of rotating the model
+ tw.vertices[i].p *= invModelAxis;
+ }
+ }
+
+ // add offset to start point
+ if ( trm_rotated ) {
+ dir = trm->offset * trmAxis;
+ tw.start += dir;
+ tw.end += dir;
+ } else {
+ tw.start += trm->offset;
+ tw.end += trm->offset;
+ }
+ if ( model_rotated ) {
+ // rotate trace instead of model
+ tw.start *= invModelAxis;
+ tw.end *= invModelAxis;
+ tw.dir *= invModelAxis;
+ }
+
+ // rotate trm polygon planes
+ if ( trm_rotated & model_rotated ) {
+ tmpAxis = trmAxis * invModelAxis;
+ for ( poly = tw.polys, i = 0; i < tw.numPolys; i++, poly++ ) {
+ poly->plane *= tmpAxis;
+ }
+ } else if ( trm_rotated ) {
+ for ( poly = tw.polys, i = 0; i < tw.numPolys; i++, poly++ ) {
+ poly->plane *= trmAxis;
+ }
+ } else if ( model_rotated ) {
+ for ( poly = tw.polys, i = 0; i < tw.numPolys; i++, poly++ ) {
+ poly->plane *= invModelAxis;
+ }
+ }
+
+ // setup trm polygons
+ for ( poly = tw.polys, i = 0; i < tw.numPolys; i++, poly++ ) {
+ // if the trm poly plane is facing in the movement direction
+ dist = poly->plane.Normal() * tw.dir;
+ if ( dist > 0.0f || ( !trm->isConvex && dist == 0.0f ) ) {
+ // this trm poly and it's edges and vertices need to be used for collision
+ poly->used = true;
+ for ( j = 0; j < poly->numEdges; j++ ) {
+ edge = &tw.edges[abs( poly->edges[j] )];
+ edge->used = true;
+ tw.vertices[edge->vertexNum[0]].used = true;
+ tw.vertices[edge->vertexNum[1]].used = true;
+ }
+ }
+ }
+
+ // setup trm vertices
+ for ( vert = tw.vertices, i = 0; i < tw.numVerts; i++, vert++ ) {
+ if ( !vert->used ) {
+ continue;
+ }
+ // get axial trm size after rotations
+ tw.size.AddPoint( vert->p - tw.start );
+ // calculate the end position of each vertex for a full trace
+ vert->endp = vert->p + tw.dir;
+ // pluecker coordinate for vertex movement line
+ vert->pl.FromRay( vert->p, tw.dir );
+ }
+
+ // setup trm edges
+ for ( edge = tw.edges + 1, i = 1; i <= tw.numEdges; i++, edge++ ) {
+ if ( !edge->used ) {
+ continue;
+ }
+ // edge start, end and pluecker coordinate
+ edge->start = tw.vertices[edge->vertexNum[0]].p;
+ edge->end = tw.vertices[edge->vertexNum[1]].p;
+ edge->pl.FromLine( edge->start, edge->end );
+ // calculate normal of plane through movement plane created by the edge
+ dir = edge->start - edge->end;
+ edge->cross[0] = dir[0] * tw.dir[1] - dir[1] * tw.dir[0];
+ edge->cross[1] = dir[0] * tw.dir[2] - dir[2] * tw.dir[0];
+ edge->cross[2] = dir[1] * tw.dir[2] - dir[2] * tw.dir[1];
+ // bit for vertex sidedness bit cache
+ edge->bitNum = i;
+ }
+
+ // set trm plane distances
+ for ( poly = tw.polys, i = 0; i < tw.numPolys; i++, poly++ ) {
+ if ( poly->used ) {
+ poly->plane.FitThroughPoint( tw.edges[abs(poly->edges[0])].start );
+ }
+ }
+
+ // bounds for full trace, a little bit larger for epsilons
+ for ( i = 0; i < 3; i++ ) {
+ if ( tw.start[i] < tw.end[i] ) {
+ tw.bounds[0][i] = tw.start[i] + tw.size[0][i] - CM_BOX_EPSILON;
+ tw.bounds[1][i] = tw.end[i] + tw.size[1][i] + CM_BOX_EPSILON;
+ } else {
+ tw.bounds[0][i] = tw.end[i] + tw.size[0][i] - CM_BOX_EPSILON;
+ tw.bounds[1][i] = tw.start[i] + tw.size[1][i] + CM_BOX_EPSILON;
+ }
+ if ( idMath::Fabs( tw.size[0][i] ) > idMath::Fabs( tw.size[1][i] ) ) {
+ tw.extents[i] = idMath::Fabs( tw.size[0][i] ) + CM_BOX_EPSILON;
+ } else {
+ tw.extents[i] = idMath::Fabs( tw.size[1][i] ) + CM_BOX_EPSILON;
+ }
+ }
+
+ // setup trace heart planes
+ idCollisionModelManagerLocal::SetupTranslationHeartPlanes( &tw );
+ tw.maxDistFromHeartPlane1 = 0;
+ tw.maxDistFromHeartPlane2 = 0;
+ // calculate maximum trm vertex distance from both heart planes
+ for ( vert = tw.vertices, i = 0; i < tw.numVerts; i++, vert++ ) {
+ if ( !vert->used ) {
+ continue;
+ }
+ dist = idMath::Fabs( tw.heartPlane1.Distance( vert->p ) );
+ if ( dist > tw.maxDistFromHeartPlane1 ) {
+ tw.maxDistFromHeartPlane1 = dist;
+ }
+ dist = idMath::Fabs( tw.heartPlane2.Distance( vert->p ) );
+ if ( dist > tw.maxDistFromHeartPlane2 ) {
+ tw.maxDistFromHeartPlane2 = dist;
+ }
+ }
+ // for epsilons
+ tw.maxDistFromHeartPlane1 += CM_BOX_EPSILON;
+ tw.maxDistFromHeartPlane2 += CM_BOX_EPSILON;
+
+ // trace through the model
+ idCollisionModelManagerLocal::TraceThroughModel( &tw );
+
+ // if we're getting contacts
+ if ( tw.getContacts ) {
+ // move all contacts to world space
+ if ( model_rotated ) {
+ for ( i = 0; i < tw.numContacts; i++ ) {
+ tw.contacts[i].normal *= modelAxis;
+ tw.contacts[i].point *= modelAxis;
+ }
+ }
+ if ( modelOrigin != vec3_origin ) {
+ for ( i = 0; i < tw.numContacts; i++ ) {
+ tw.contacts[i].point += modelOrigin;
+ tw.contacts[i].dist += modelOrigin * tw.contacts[i].normal;
+ }
+ }
+ idCollisionModelManagerLocal::numContacts = tw.numContacts;
+ } else {
+ // store results
+ *results = tw.trace;
+ results->endpos = start + results->fraction * ( end - start );
+ results->endAxis = trmAxis;
+
+ if ( results->fraction < 1.0f ) {
+ // if the fraction is tiny the actual movement could end up zero
+ if ( results->fraction > 0.0f && results->endpos.Compare( start ) ) {
+ results->fraction = 0.0f;
+ }
+ // rotate trace plane normal if there was a collision with a rotated model
+ if ( model_rotated ) {
+ results->c.normal *= modelAxis;
+ results->c.point *= modelAxis;
+ }
+ results->c.point += modelOrigin;
+ results->c.dist += modelOrigin * results->c.normal;
+ }
+ }
+
+#ifdef _DEBUG
+ // test for missed collisions
+ if ( cm_debugCollision.GetBool() ) {
+ if ( !entered && !idCollisionModelManagerLocal::getContacts ) {
+ entered = 1;
+ // if the trm is stuck in the model
+ if ( idCollisionModelManagerLocal::Contents( results->endpos, trm, trmAxis, -1, model, modelOrigin, modelAxis ) & contentMask ) {
+ trace_t tr;
+
+ // test where the trm is stuck in the model
+ idCollisionModelManagerLocal::Contents( results->endpos, trm, trmAxis, -1, model, modelOrigin, modelAxis );
+ // re-run collision detection to find out where it failed
+ idCollisionModelManagerLocal::Translation( &tr, start, end, trm, trmAxis, contentMask, model, modelOrigin, modelAxis );
+ }
+ entered = 0;
+ }
+ }
+#endif
+}
diff --git a/neo/curllib.vcxproj b/neo/curllib.vcxproj
index 7762ffbd..7276455e 100644
--- a/neo/curllib.vcxproj
+++ b/neo/curllib.vcxproj
@@ -5,6 +5,10 @@
Debug
x64
+
+ Prey Debug
+ x64
+
Release
x64
@@ -33,6 +37,11 @@
MultiByte
v145
+
+ StaticLibrary
+ MultiByte
+ v145
+
@@ -48,12 +57,21 @@
+
+
+
+
+
+
<_ProjectFileVersion>10.0.40219.1
AllRules.ruleset
+ AllRules.ruleset
+
+
AllRules.ruleset
@@ -64,6 +82,12 @@
_USRDLL;CURLLIB_EXPORTS;%(PreprocessorDefinitions)
+
+
+ .\curl\include;%(AdditionalIncludeDirectories)
+ _USRDLL;CURLLIB_EXPORTS;%(PreprocessorDefinitions)
+
+
.\curl\include;%(AdditionalIncludeDirectories)
diff --git a/neo/doomdll.vcxproj b/neo/doomdll.vcxproj
index 26e18ec1..62f7b68a 100644
--- a/neo/doomdll.vcxproj
+++ b/neo/doomdll.vcxproj
@@ -25,6 +25,10 @@
Dedicated Release
x64
+
+ Prey Debug
+ x64
+
Release
x64
@@ -88,6 +92,12 @@
MultiByte
v145
+
+ Application
+ false
+ MultiByte
+ v145
+
@@ -140,6 +150,12 @@
+
+
+
+
+
+
<_ProjectFileVersion>10.0.40219.1
@@ -150,8 +166,11 @@
AllRules.ruleset
+ AllRules.ruleset
+
+
AllRules.ruleset
@@ -204,6 +223,20 @@
precompiled.h
+
+
+ openal\lib;sys\win32\dongle;%(AdditionalLibraryDirectories)
+ ../Prey.exe
+ d3d12.lib;nafxcwd.lib;libcmtd.lib;%(AdditionalDependencies)
+
+
+ Use
+
+
+ precompiled.h
+ PREY;_DEBUG;%(PreprocessorDefinitions)
+
+
openal\lib;sys\win32\dongle;%(AdditionalLibraryDirectories)
@@ -260,6 +293,8 @@
+
+
@@ -637,6 +672,7 @@
true
true
true
+ true
true
true
true
@@ -646,6 +682,7 @@
true
true
true
+ true
true
true
true
@@ -655,6 +692,7 @@
true
true
true
+ true
true
true
true
@@ -664,6 +702,7 @@
true
true
true
+ true
true
true
true
@@ -673,6 +712,7 @@
true
true
true
+ true
true
true
true
@@ -682,6 +722,7 @@
true
true
true
+ true
true
true
true
@@ -691,6 +732,7 @@
true
true
true
+ true
true
true
true
@@ -700,6 +742,7 @@
true
true
true
+ true
true
true
true
@@ -709,6 +752,7 @@
true
true
true
+ true
true
true
true
@@ -1261,14 +1305,94 @@
-
-
-
-
-
-
-
-
+
+ false
+ false
+ false
+ false
+ false
+ true
+ false
+ false
+
+
+ false
+ false
+ false
+ false
+ false
+ true
+ false
+ false
+
+
+ false
+ false
+ false
+ false
+ false
+ true
+ false
+ false
+
+
+ false
+ false
+ false
+ false
+ false
+ true
+ false
+ false
+
+
+ false
+ false
+ false
+ false
+ false
+ true
+ false
+ false
+
+
+ false
+ false
+ false
+ false
+ false
+ true
+ false
+ false
+
+
+ false
+ false
+ false
+ false
+ false
+ true
+ false
+ false
+
+
+ false
+ false
+ false
+ false
+ false
+ true
+ false
+ false
+
+
+
+
+
+
+
+
+
@@ -1300,6 +1424,7 @@
Create
+ Create
Create
Create
Create
@@ -1355,6 +1480,7 @@
NotUsing
+ NotUsing
NotUsing
NotUsing
NotUsing
@@ -1364,6 +1490,7 @@
NotUsing
+ NotUsing
NotUsing
NotUsing
NotUsing
@@ -1373,6 +1500,7 @@
NotUsing
+ NotUsing
NotUsing
NotUsing
NotUsing
@@ -1382,6 +1510,7 @@
NotUsing
+ NotUsing
NotUsing
NotUsing
NotUsing
@@ -1391,6 +1520,7 @@
NotUsing
+ NotUsing
NotUsing
NotUsing
NotUsing
@@ -1400,6 +1530,7 @@
NotUsing
+ NotUsing
NotUsing
NotUsing
NotUsing
@@ -1409,6 +1540,7 @@
NotUsing
+ NotUsing
NotUsing
NotUsing
NotUsing
@@ -1418,6 +1550,7 @@
NotUsing
+ NotUsing
NotUsing
NotUsing
NotUsing
@@ -1427,6 +1560,7 @@
NotUsing
+ NotUsing
NotUsing
NotUsing
NotUsing
@@ -1436,6 +1570,7 @@
NotUsing
+ NotUsing
NotUsing
NotUsing
NotUsing
@@ -1445,6 +1580,7 @@
NotUsing
+ NotUsing
NotUsing
NotUsing
NotUsing
@@ -1454,6 +1590,7 @@
NotUsing
+ NotUsing
NotUsing
NotUsing
NotUsing
@@ -1463,6 +1600,7 @@
NotUsing
+ NotUsing
NotUsing
NotUsing
NotUsing
@@ -1472,6 +1610,7 @@
NotUsing
+ NotUsing
NotUsing
NotUsing
NotUsing
@@ -1481,6 +1620,7 @@
NotUsing
+ NotUsing
NotUsing
NotUsing
NotUsing
@@ -1490,6 +1630,7 @@
NotUsing
+ NotUsing
NotUsing
NotUsing
NotUsing
@@ -1499,6 +1640,7 @@
NotUsing
+ NotUsing
NotUsing
NotUsing
NotUsing
@@ -1508,6 +1650,7 @@
NotUsing
+ NotUsing
NotUsing
NotUsing
NotUsing
@@ -1517,6 +1660,7 @@
NotUsing
+ NotUsing
NotUsing
NotUsing
NotUsing
@@ -1526,6 +1670,7 @@
NotUsing
+ NotUsing
NotUsing
NotUsing
NotUsing
@@ -1535,6 +1680,7 @@
NotUsing
+ NotUsing
NotUsing
NotUsing
NotUsing
@@ -1544,6 +1690,7 @@
NotUsing
+ NotUsing
NotUsing
NotUsing
NotUsing
@@ -1553,6 +1700,7 @@
NotUsing
+ NotUsing
NotUsing
NotUsing
NotUsing
@@ -1562,6 +1710,7 @@
NotUsing
+ NotUsing
NotUsing
NotUsing
NotUsing
@@ -1571,6 +1720,7 @@
NotUsing
+ NotUsing
NotUsing
NotUsing
NotUsing
@@ -1580,6 +1730,7 @@
NotUsing
+ NotUsing
NotUsing
NotUsing
NotUsing
@@ -1589,6 +1740,7 @@
NotUsing
+ NotUsing
NotUsing
NotUsing
NotUsing
@@ -1598,6 +1750,7 @@
NotUsing
+ NotUsing
NotUsing
NotUsing
NotUsing
@@ -1607,6 +1760,7 @@
NotUsing
+ NotUsing
NotUsing
NotUsing
NotUsing
@@ -1616,6 +1770,7 @@
NotUsing
+ NotUsing
NotUsing
NotUsing
NotUsing
@@ -1625,6 +1780,7 @@
NotUsing
+ NotUsing
NotUsing
NotUsing
NotUsing
@@ -1634,6 +1790,7 @@
NotUsing
+ NotUsing
NotUsing
NotUsing
NotUsing
@@ -1643,6 +1800,7 @@
NotUsing
+ NotUsing
NotUsing
NotUsing
NotUsing
@@ -1652,6 +1810,7 @@
NotUsing
+ NotUsing
NotUsing
NotUsing
NotUsing
@@ -1661,6 +1820,7 @@
NotUsing
+ NotUsing
NotUsing
NotUsing
NotUsing
@@ -1670,6 +1830,7 @@
NotUsing
+ NotUsing
NotUsing
NotUsing
NotUsing
@@ -1679,6 +1840,7 @@
NotUsing
+ NotUsing
NotUsing
NotUsing
NotUsing
@@ -1688,6 +1850,7 @@
NotUsing
+ NotUsing
NotUsing
NotUsing
NotUsing
@@ -1697,6 +1860,7 @@
NotUsing
+ NotUsing
NotUsing
NotUsing
NotUsing
@@ -1706,6 +1870,7 @@
NotUsing
+ NotUsing
NotUsing
NotUsing
NotUsing
@@ -1715,6 +1880,7 @@
NotUsing
+ NotUsing
NotUsing
NotUsing
NotUsing
@@ -1724,6 +1890,7 @@
NotUsing
+ NotUsing
NotUsing
NotUsing
NotUsing
@@ -1733,6 +1900,7 @@
NotUsing
+ NotUsing
NotUsing
NotUsing
NotUsing
@@ -1742,6 +1910,7 @@
NotUsing
+ NotUsing
NotUsing
NotUsing
NotUsing
@@ -1759,6 +1928,7 @@
NotUsing
+ NotUsing
NotUsing
NotUsing
NotUsing
@@ -1768,6 +1938,7 @@
NotUsing
+ NotUsing
NotUsing
NotUsing
NotUsing
@@ -1777,6 +1948,7 @@
NotUsing
+ NotUsing
NotUsing
NotUsing
NotUsing
@@ -1786,6 +1958,7 @@
NotUsing
+ NotUsing
NotUsing
NotUsing
NotUsing
@@ -1795,6 +1968,7 @@
NotUsing
+ NotUsing
NotUsing
NotUsing
NotUsing
@@ -1804,6 +1978,7 @@
NotUsing
+ NotUsing
NotUsing
NotUsing
NotUsing
@@ -1813,6 +1988,7 @@
NotUsing
+ NotUsing
NotUsing
NotUsing
NotUsing
@@ -1822,6 +1998,7 @@
NotUsing
+ NotUsing
NotUsing
NotUsing
NotUsing
@@ -1831,6 +2008,7 @@
NotUsing
+ NotUsing
NotUsing
NotUsing
NotUsing
@@ -1840,6 +2018,7 @@
NotUsing
+ NotUsing
NotUsing
NotUsing
NotUsing
@@ -1849,6 +2028,7 @@
NotUsing
+ NotUsing
NotUsing
NotUsing
NotUsing
@@ -1858,6 +2038,7 @@
NotUsing
+ NotUsing
NotUsing
NotUsing
NotUsing
@@ -1867,6 +2048,7 @@
NotUsing
+ NotUsing
NotUsing
NotUsing
NotUsing
@@ -1876,6 +2058,7 @@
NotUsing
+ NotUsing
NotUsing
NotUsing
NotUsing
@@ -1885,6 +2068,7 @@
NotUsing
+ NotUsing
NotUsing
NotUsing
NotUsing
@@ -1894,6 +2078,7 @@
NotUsing
+ NotUsing
NotUsing
NotUsing
NotUsing
@@ -1903,6 +2088,7 @@
NotUsing
+ NotUsing
NotUsing
NotUsing
NotUsing
@@ -1912,6 +2098,7 @@
NotUsing
+ NotUsing
NotUsing
NotUsing
NotUsing
@@ -1921,6 +2108,7 @@
NotUsing
+ NotUsing
NotUsing
NotUsing
NotUsing
@@ -1930,6 +2118,7 @@
NotUsing
+ NotUsing
NotUsing
NotUsing
NotUsing
@@ -1939,6 +2128,7 @@
NotUsing
+ NotUsing
NotUsing
NotUsing
NotUsing
@@ -1948,6 +2138,7 @@
NotUsing
+ NotUsing
NotUsing
NotUsing
NotUsing
@@ -1957,6 +2148,7 @@
NotUsing
+ NotUsing
NotUsing
NotUsing
NotUsing
@@ -1966,6 +2158,7 @@
NotUsing
+ NotUsing
NotUsing
NotUsing
NotUsing
@@ -1975,6 +2168,7 @@
NotUsing
+ NotUsing
NotUsing
NotUsing
NotUsing
@@ -1991,6 +2185,7 @@
C:\Program Files %28x86%29\Microsoft DirectX SDK %28June 2010%29\Include;%(AdditionalIncludeDirectories)
+ C:\Program Files %28x86%29\Microsoft DirectX SDK %28June 2010%29\Include;%(AdditionalIncludeDirectories)
C:\Program Files %28x86%29\Microsoft DirectX SDK %28June 2010%29\Include;%(AdditionalIncludeDirectories)
C:\Program Files %28x86%29\Microsoft DirectX SDK %28June 2010%29\Include;%(AdditionalIncludeDirectories)
C:\Program Files %28x86%29\Microsoft DirectX SDK %28June 2010%29\Include;%(AdditionalIncludeDirectories)
@@ -2247,6 +2442,7 @@
true
true
true
+ true
true
true
true
@@ -2256,6 +2452,7 @@
true
true
true
+ true
true
true
true
@@ -2265,6 +2462,7 @@
true
true
true
+ true
true
true
true
@@ -2274,6 +2472,7 @@
true
true
true
+ true
true
true
true
@@ -2283,6 +2482,7 @@
true
true
true
+ true
true
true
true
@@ -2292,6 +2492,7 @@
true
true
true
+ true
true
true
true
@@ -2301,6 +2502,7 @@
true
true
true
+ true
true
true
true
@@ -2310,6 +2512,7 @@
true
true
true
+ true
true
true
true
@@ -2319,6 +2522,7 @@
true
true
true
+ true
true
true
true
@@ -2998,6 +3202,7 @@
true
true
true
+ true
true
true
true
@@ -3007,6 +3212,7 @@
true
true
true
+ true
true
true
true
@@ -3016,6 +3222,7 @@
true
true
true
+ true
true
true
true
@@ -3025,6 +3232,7 @@
true
true
true
+ true
true
true
true
@@ -3035,6 +3243,7 @@
true
true
true
+ true
true
true
true
@@ -3044,6 +3253,7 @@
true
true
true
+ true
true
true
true
@@ -3053,6 +3263,7 @@
true
true
true
+ true
true
true
true
@@ -3062,6 +3273,7 @@
true
true
true
+ true
true
true
true
@@ -3071,6 +3283,7 @@
true
true
true
+ true
true
true
true
@@ -3080,6 +3293,7 @@
true
true
true
+ true
true
true
true
@@ -3089,6 +3303,7 @@
true
true
true
+ true
true
true
true
@@ -3098,6 +3313,7 @@
true
true
true
+ true
true
true
true
@@ -3119,6 +3335,12 @@
{58320c19-5139-47c5-aafe-4cad2c8ed14d}
+
+ {6cd8e46d-e28e-493f-bb06-537a637a68c2}
+
+
+
+
diff --git a/neo/doomdll.vcxproj.filters b/neo/doomdll.vcxproj.filters
index b1e622c3..7fb6cf83 100644
--- a/neo/doomdll.vcxproj.filters
+++ b/neo/doomdll.vcxproj.filters
@@ -106,6 +106,9 @@
{b3166d25-0562-49da-bc8d-961006fe1785}
+
+ {f7ebd0db-2189-45da-a492-76ab782e337a}
+
@@ -603,6 +606,12 @@
Renderer
+
+ CM\PreyCM
+
+
+ CM\PreyCM
+
@@ -1766,6 +1775,30 @@
Renderer
+
+ CM\PreyCM
+
+
+ CM\PreyCM
+
+
+ CM\PreyCM
+
+
+ CM\PreyCM
+
+
+ CM\PreyCM
+
+
+ CM\PreyCM
+
+
+ CM\PreyCM
+
+
+ CM\PreyCM
+
@@ -2484,4 +2517,9 @@
Tools\Script
+
+
+ Sys\RC\res
+
+
\ No newline at end of file
diff --git a/neo/doomdll.vcxproj.user b/neo/doomdll.vcxproj.user
index 785d20f2..609e7c7b 100644
--- a/neo/doomdll.vcxproj.user
+++ b/neo/doomdll.vcxproj.user
@@ -19,6 +19,13 @@
D:\projects\Doom3
+set r_fullscreen 0 +set r_mode 6|+set r_fullscreen 1 +set r_mode 6|+set r_fullscreen 1 +set r_mode 1|+set r_fullscreen 0 +set r_mode 1|
+
+ D:\projects\Doom3\Prey.exe
+ WindowsLocalDebugger
+ +set r_fullscreen 0 +set r_mode 1
+ D:\projects\Doom3
+ +set r_fullscreen 0 +set r_mode 6|+set r_fullscreen 1 +set r_mode 6|+set r_fullscreen 1 +set r_mode 1|+set r_fullscreen 0 +set r_mode 1|
+
D:\projects\Doom3\Doom3.exe
WindowsLocalDebugger
@@ -36,6 +43,7 @@
WindowsLocalDebugger
+set r_fullscreen 0 +set r_mode 6
D:\projects\Doom3
+ +set r_fullscreen 0 +set r_mode 6|
D:\projects\Doom3\Doom3.exe
diff --git a/neo/framework/BuildDefines.h b/neo/framework/BuildDefines.h
index 11f9c7c0..96c94521 100644
--- a/neo/framework/BuildDefines.h
+++ b/neo/framework/BuildDefines.h
@@ -133,3 +133,8 @@ If you have questions concerning this license or the applicable additional terms
# endif
#endif
+// jmarshall - have to make this public outside of preygame for the collision model code.
+#ifdef PREY
+#define DEATHWALK_AUTOLOAD 1 // Append deathwalk map to all maps loaded
+#endif
+// jmarshall end
\ No newline at end of file
diff --git a/neo/framework/Common.cpp b/neo/framework/Common.cpp
index e16179a5..869f2027 100644
--- a/neo/framework/Common.cpp
+++ b/neo/framework/Common.cpp
@@ -146,7 +146,9 @@ public:
virtual void Error( const char *fmt, ... ) id_attribute((format(printf,2,3)));
virtual void FatalError( const char *fmt, ... ) id_attribute((format(printf,2,3)));
virtual const idLangDict * GetLanguageDict( void );
-
+#ifdef PREY
+ virtual void MaterialKeyForBinding(const char* binding, char* materialName, char* keyText, bool* wideKey);
+#endif
virtual const char * KeysFromBinding( const char *bind );
virtual const char * BindingFromKey( const char *key );
@@ -2645,8 +2647,11 @@ void idCommonLocal::LoadGameDLL( void ) {
gameImport_t gameImport;
gameExport_t gameExport;
GetGameAPI_t GetGameAPI;
-
+#ifdef PREY
+ fileSystem->FindDLL( "preygame", dllPath, true);
+#else
fileSystem->FindDLL( "game", dllPath, true );
+#endif
if ( !dllPath[ 0 ] ) {
common->FatalError( "couldn't find game dynamic library" );
@@ -3122,4 +3127,145 @@ int64_t idCommonLocal::Get_com_engineHz_numerator(void) {
int64_t idCommonLocal::Get_com_engineHz_denominator(void) {
return com_engineHz_denominator;
-}
\ No newline at end of file
+}
+
+#ifdef PREY
+void idCommonLocal::MaterialKeyForBinding(const char* binding, char* materialName, char* keyText, bool* wideKey) {
+ bool unused1 = false;
+ bool unused2 = false;
+
+ idStr keyName;
+ idStr displayName;
+
+ const char* rawKey = idKeyInput::SingleKeyFromBinding(binding, unused1);
+ if (rawKey != NULL) {
+ keyName = rawKey;
+ }
+
+ const char* localizedKey = idKeyInput::SingleKeyFromBinding(binding, unused2);
+ if (localizedKey != NULL) {
+ displayName = localizedKey;
+ }
+
+ keyText[0] = '\0';
+ *wideKey = false;
+
+ if (keyName.Length() <= 0) {
+ strcpy(materialName, "textures/interface/tips/keywide");
+
+ const idLangDict* lang = GetLanguageDict();
+ strcpy(keyText, lang->GetString("#str_07133"));
+
+ *wideKey = true;
+ return;
+ }
+
+ if (!keyName.Icmp("mouse1")) {
+ strcpy(materialName, "textures/interface/tips/mouse1");
+ return;
+ }
+
+ if (!keyName.Icmp("mouse2")) {
+ strcpy(materialName, "textures/interface/tips/mouse2");
+ return;
+ }
+
+ if (!keyName.Icmp("mouse3")) {
+ strcpy(materialName, "textures/interface/tips/mouse3");
+ return;
+ }
+
+ if (!keyName.Icmp("mwheelup")) {
+ strcpy(materialName, "textures/interface/tips/mouseup");
+ return;
+ }
+
+ if (!keyName.Icmp("mwheeldown")) {
+ strcpy(materialName, "textures/interface/tips/mousedn");
+ return;
+ }
+
+ if (!keyName.Icmp("uparrow")) {
+ strcpy(materialName, "textures/interface/tips/uparrow");
+ return;
+ }
+
+ if (!keyName.Icmp("downarrow")) {
+ strcpy(materialName, "textures/interface/tips/downarrow");
+ return;
+ }
+
+ if (!keyName.Icmp("leftarrow")) {
+ strcpy(materialName, "textures/interface/tips/leftarrow");
+ return;
+ }
+
+ if (!keyName.Icmp("rightarrow")) {
+ strcpy(materialName, "textures/interface/tips/rightarrow");
+ return;
+ }
+
+ if (!keyName.Icmp("enter")) {
+ strcpy(materialName, "textures/interface/tips/enter");
+ *wideKey = true;
+ return;
+ }
+
+ if (!keyName.Icmp("backspace")) {
+ strcpy(materialName, "textures/interface/tips/backspace");
+ *wideKey = true;
+ return;
+ }
+
+ if (!keyName.Icmp("tab")) {
+ strcpy(materialName, "textures/interface/tips/tab");
+ *wideKey = true;
+ return;
+ }
+
+ if (!keyName.Icmp("menu")) {
+ strcpy(materialName, "textures/interface/tips/menu");
+ return;
+ }
+
+ if (!keyName.Icmp("shift")) {
+ strcpy(materialName, "textures/interface/tips/shift");
+ *wideKey = true;
+ return;
+ }
+
+ /*
+ Generic short printable keys use the normal key material and put the
+ localized/display key text on top of it.
+
+ The original excludes large/special keys from this path:
+ printscreen, keypad keys, pause, capslock, windows keys,
+ modifiers, and space.
+ */
+ if (keyName.Icmp("printscreen") &&
+ keyName.Icmpn("kp_", 3) &&
+ keyName.Icmp("pause") &&
+ keyName.Icmp("capslock") &&
+ keyName.Icmp("lwin") &&
+ keyName.Icmp("rwin") &&
+ keyName.Icmp("shift") &&
+ keyName.Icmp("ctrl") &&
+ keyName.Icmp("alt") &&
+ keyName.Icmp("space") &&
+ displayName.Length() <= 3) {
+
+ strcpy(materialName, "textures/interface/tips/key");
+ strcpy(keyText, displayName.c_str());
+ return;
+ }
+
+ /*
+ Everything else falls back to the wide key material with text.
+ This handles longer key names or special keys that do not have
+ dedicated art.
+ */
+ strcpy(materialName, "textures/interface/tips/keywide");
+ strcpy(keyText, displayName.c_str());
+ *wideKey = true;
+}
+#endif
\ No newline at end of file
diff --git a/neo/framework/Common.h b/neo/framework/Common.h
index 5941dd67..9f2483cd 100644
--- a/neo/framework/Common.h
+++ b/neo/framework/Common.h
@@ -37,6 +37,10 @@ If you have questions concerning this license or the applicable additional terms
==============================================================
*/
+#ifdef PREY
+class idKeyInput;
+#endif
+
typedef enum {
EDITOR_NONE = 0,
EDITOR_RADIANT = BIT(1),
@@ -205,6 +209,10 @@ public:
// Directly sample a keystate.
virtual int KeyState( int key ) = 0;
+#ifdef PREY
+ virtual void MaterialKeyForBinding(const char* binding, char* materialName, char* keyText, bool* wideKey) = 0;
+#endif
+
virtual float Get_com_engineHz_latched(void) = 0;
virtual int64_t Get_com_engineHz_numerator(void) = 0;
virtual int64_t Get_com_engineHz_denominator(void) = 0;
diff --git a/neo/framework/DeclFX.cpp b/neo/framework/DeclFX.cpp
index 8f8712f6..e80e3a85 100644
--- a/neo/framework/DeclFX.cpp
+++ b/neo/framework/DeclFX.cpp
@@ -189,6 +189,16 @@ void idDeclFX::ParseSingleFXAction( idLexer &src, idFXSingleAction& FXAction ) {
continue;
}
+#ifdef PREY
+ if (!token.Icmp("useAxis")) {
+ if (!src.ReadToken(&token)) {
+ break;
+ }
+ ParseUseAxis(token, FXAction);
+ continue;
+ }
+#endif
+
if ( !token.Icmp( "rotate" ) ) {
FXAction.rotate = src.ParseFloat();
continue;
@@ -471,3 +481,23 @@ idDeclFX::FreeData
void idDeclFX::FreeData( void ) {
events.Clear();
}
+
+#ifdef PREY
+void idDeclFX::ParseUseAxis(idStr& text, idFXSingleAction& action) const {
+ if (!idStr::Icmp(text, "normal")) {
+ action.useAxis = AXIS_NORMAL;
+ }
+ else if (!idStr::Icmp(text, "bounce")) {
+ action.useAxis = AXIS_BOUNCE;
+ }
+ else if (!idStr::Icmp(text, "incoming")) {
+ action.useAxis = AXIS_INCOMING;
+ }
+ else if (!idStr::Icmp(text, "customlocal")) {
+ action.useAxis = AXIS_CUSTOMLOCAL;
+ }
+ else {
+ action.useAxis = AXIS_CURRENT;
+ }
+}
+#endif
\ No newline at end of file
diff --git a/neo/framework/DeclFX.h b/neo/framework/DeclFX.h
index 076eb3f4..db6f91ca 100644
--- a/neo/framework/DeclFX.h
+++ b/neo/framework/DeclFX.h
@@ -60,7 +60,12 @@ typedef struct {
idStr data;
idStr name;
idStr fire;
-
+#ifdef PREY
+ // HUMANHEAD nla
+ int useAxis;
+ idVec3 dir;
+ // HUMANHEAD END
+#endif
float delay;
float duration;
float restart;
@@ -91,6 +96,11 @@ typedef struct {
bool trackOrigin;
} idFXSingleAction;
+
+#ifdef PREY
+enum fxAxis { AXIS_CURRENT, AXIS_NORMAL, AXIS_BOUNCE, AXIS_INCOMING, AXIS_CUSTOMLOCAL };
+#endif
+
//
// grouped fx structures
//
@@ -108,6 +118,12 @@ public:
private:
void ParseSingleFXAction( idLexer &src, idFXSingleAction& FXAction );
+#ifdef PREY
+ // HUMANHEAD nla
+ void ParseUseAxis(idStr& text, idFXSingleAction& action) const;
+ // HUMANHEAD END
+#endif
};
+
#endif /* !__DECLFX_H__ */
diff --git a/neo/framework/DeclManager.h b/neo/framework/DeclManager.h
index 25dac07f..ccce8a51 100644
--- a/neo/framework/DeclManager.h
+++ b/neo/framework/DeclManager.h
@@ -78,6 +78,9 @@ typedef enum {
DECL_EMAIL,
DECL_MODELEXPORT,
DECL_MAPDEF,
+#ifdef PREY
+ DECL_BEAM,
+#endif
// new decl types can be added here
diff --git a/neo/framework/DeclSkin.h b/neo/framework/DeclSkin.h
index deba5823..59a82272 100644
--- a/neo/framework/DeclSkin.h
+++ b/neo/framework/DeclSkin.h
@@ -50,7 +50,7 @@ public:
virtual bool Parse( const char *text, const int textLength );
virtual void FreeData( void );
- const idMaterial * RemapShaderBySkin( const idMaterial *shader ) const;
+ virtual const idMaterial * RemapShaderBySkin( const idMaterial *shader ) const;
// model associations are just for the preview dialog in the editor
const int GetNumModelAssociations() const;
diff --git a/neo/framework/DeclTable.h b/neo/framework/DeclTable.h
index 6097920e..50d130a4 100644
--- a/neo/framework/DeclTable.h
+++ b/neo/framework/DeclTable.h
@@ -45,7 +45,7 @@ public:
virtual bool Parse( const char *text, const int textLength );
virtual void FreeData( void );
- float TableLookup( float index ) const;
+ virtual float TableLookup( float index ) const;
private:
bool clamp;
diff --git a/neo/framework/KeyInput.cpp b/neo/framework/KeyInput.cpp
index 2a8279fb..f7ae2e3d 100644
--- a/neo/framework/KeyInput.cpp
+++ b/neo/framework/KeyInput.cpp
@@ -454,6 +454,39 @@ int idKeyInput::GetUsercmdAction( int keynum ) {
return keys[ keynum ].usercmdAction;
}
+#ifdef PREY
+/*
+===================
+idKeyInput::SingleKeyFromBinding
+===================
+*/
+const char* idKeyInput::SingleKeyFromBinding(const char* binding, bool localized) {
+ static idStr keyName;
+
+ keyName.Clear();
+
+ if (binding == NULL || binding[0] == '\0') {
+ return keyName.c_str();
+ }
+
+ int keyNum;
+
+ for (keyNum = 0; keyNum < 256; keyNum++) {
+ if (!idStr::Icmp(keys[keyNum].binding, binding)) {
+ break;
+ }
+ }
+
+ if (keyNum == 256) {
+ return keyName.c_str();
+ }
+
+ keyName.Append(KeyNumToString(keyNum, localized));
+
+ return keyName.c_str();
+}
+#endif
+
/*
===================
Key_Unbind_f
diff --git a/neo/framework/KeyInput.h b/neo/framework/KeyInput.h
index e8b278c9..01b93ae7 100644
--- a/neo/framework/KeyInput.h
+++ b/neo/framework/KeyInput.h
@@ -207,6 +207,10 @@ public:
static int StringToKeyNum( const char *str );
static const char * KeyNumToString( int keyNum, bool localized );
+#ifdef PREY
+ static const char* SingleKeyFromBinding(const char* binding, bool localized);
+#endif
+
static void SetBinding( int keyNum, const char *binding );
static const char * GetBinding( int keyNum );
static bool UnbindBinding( const char *bind );
diff --git a/neo/framework/Licensee.h b/neo/framework/Licensee.h
index cbbd7461..8bc1ddb1 100644
--- a/neo/framework/Licensee.h
+++ b/neo/framework/Licensee.h
@@ -34,21 +34,34 @@ If you have questions concerning this license or the applicable additional terms
===============================================================================
*/
-#define GAME_NAME "DOOM 3" // appears on window titles and errors
+#ifdef PREY
+#define GAME_NAME "PREY" // appears on window titles and errors
+#else
+#define GAME_NAME "DOOM 3" // appears on window titles and errors
+#endif
-#define ENGINE_VERSION "DOOM 1.3.1" // printed in console
+#define ENGINE_VERSION GAME_NAME " " __DATE__ " " __TIME__ // printed in console
// paths
#define CD_BASEDIR "Doom"
#ifdef ID_DEMO_BUILD
#define BASE_GAMEDIR "demo"
+#else
+#ifdef PREY
+ #define BASE_GAMEDIR "preybase"
#else
#define BASE_GAMEDIR "base"
#endif
+#endif
// filenames
#define CD_EXE "doom.exe"
+
+#ifdef PREY
+#define CONFIG_FILE "PreyConfig.cfg"
+#else
#define CONFIG_FILE "DoomConfig.cfg"
+#endif
// base folder where the source code lives
#define SOURCE_CODE_BASE_FOLDER "neo"
@@ -90,7 +103,7 @@ If you have questions concerning this license or the applicable additional terms
// editor info
#define EDITOR_DEFAULT_PROJECT "doom.qe4"
#define EDITOR_REGISTRY_KEY "DOOMRadiant"
-#define EDITOR_WINDOWTEXT "IceEdit2"
+#define EDITOR_WINDOWTEXT "IceEdit2(for " GAME_NAME " )"
// win32 info
#define WIN32_CONSOLE_CLASS "DOOM 3 WinConsole"
diff --git a/neo/framework/Session.cpp b/neo/framework/Session.cpp
index b709e3b7..630b7b8c 100644
--- a/neo/framework/Session.cpp
+++ b/neo/framework/Session.cpp
@@ -1444,7 +1444,9 @@ void idSessionLocal::LoadLoadingGui( const char *mapName ) {
char guiMap[ MAX_STRING_CHARS ];
strncpy( guiMap, va( "guis/map/%s.gui", stripped.c_str() ), MAX_STRING_CHARS );
// give the gamecode a chance to override
+#ifndef PREY
game->GetMapLoadingGUI( guiMap );
+#endif
if ( uiManager->CheckGui( guiMap ) ) {
guiLoading = uiManager->FindGui( guiMap, true, false, true );
diff --git a/neo/framework/Session_menu.cpp b/neo/framework/Session_menu.cpp
index efeccf45..dc6384e8 100644
--- a/neo/framework/Session_menu.cpp
+++ b/neo/framework/Session_menu.cpp
@@ -576,9 +576,11 @@ void idSessionLocal::HandleMainMenuCommands( const char *menuCommand ) {
}
// always let the game know the command is being run
+#ifndef PREY
if ( game ) {
game->HandleMainMenuCommands( cmd, guiActive );
}
+#endif
if ( !idStr::Icmp( cmd, "startGame" ) ) {
cvarSystem->SetCVarInteger( "g_skill", guiMainMenu->State().GetInt( "skill" ) );
diff --git a/neo/framework/UsercmdGen.h b/neo/framework/UsercmdGen.h
index d248bd44..a0e8b4b0 100644
--- a/neo/framework/UsercmdGen.h
+++ b/neo/framework/UsercmdGen.h
@@ -44,9 +44,15 @@ const int USERCMD_MSEC = 1000 / USERCMD_HZ;
const int BUTTON_ATTACK = BIT(0);
const int BUTTON_RUN = BIT(1);
const int BUTTON_ZOOM = BIT(2);
+#ifdef PREY
+const int BUTTON_ATTACK_ALT = BIT(3);
+const int BUTTON_SCORES = BIT(4);
+const int BUTTON_MLOOK = BIT(5);
+#else
const int BUTTON_SCORES = BIT(3);
const int BUTTON_MLOOK = BIT(4);
const int BUTTON_5 = BIT(5);
+#endif
const int BUTTON_6 = BIT(6);
const int BUTTON_7 = BIT(7);
diff --git a/neo/framework/async/AsyncClient.cpp b/neo/framework/async/AsyncClient.cpp
index 776d895d..7f721b3d 100644
--- a/neo/framework/async/AsyncClient.cpp
+++ b/neo/framework/async/AsyncClient.cpp
@@ -1880,7 +1880,11 @@ void idAsyncClient::RunFrame( void ) {
bool lastPredictFrame = ( snapshotGameFrame + 1 >= gameFrame && gameTimeResidual + clientPredictTime < USERCMD_MSEC );
// run client prediction
+#ifdef PREY
+ gameReturn_t ret = game->ClientPrediction(clientNum, userCmds[snapshotGameFrame & (MAX_USERCMD_BACKUP - 1)]);
+#else
gameReturn_t ret = game->ClientPrediction( clientNum, userCmds[ snapshotGameFrame & ( MAX_USERCMD_BACKUP - 1 ) ], lastPredictFrame );
+#endif
idAsyncNetwork::ExecuteSessionCommand( ret.sessionCommand );
diff --git a/neo/framework/async/AsyncServer.cpp b/neo/framework/async/AsyncServer.cpp
index 31ada75b..95be59fd 100644
--- a/neo/framework/async/AsyncServer.cpp
+++ b/neo/framework/async/AsyncServer.cpp
@@ -255,7 +255,11 @@ void idAsyncServer::ExecuteMapChange( void ) {
fileSystem->ClearPureChecksums();
// make sure the map/gametype combo is good
+#ifdef PREY
+ strcpy(bestGameType, game->GetBestGameType(cvarSystem->GetCVarString("si_map"), cvarSystem->GetCVarString("si_gametype")));
+#else
game->GetBestGameType( cvarSystem->GetCVarString("si_map"), cvarSystem->GetCVarString("si_gametype"), bestGameType );
+#endif
cvarSystem->SetCVarString("si_gametype", bestGameType );
// initialize map settings
@@ -695,7 +699,11 @@ void idAsyncServer::InitClient( int clientNum, int clientId, int clientRate ) {
}
// let the game know a player connected
+#ifdef PREY
+ game->ServerClientConnect(clientNum);
+#else
game->ServerClientConnect( clientNum, client.guid );
+#endif
}
/*
diff --git a/neo/game.vcxproj b/neo/game.vcxproj
index 569583fa..c6e0dbe8 100644
--- a/neo/game.vcxproj
+++ b/neo/game.vcxproj
@@ -25,6 +25,10 @@
Dedicated Release
x64
+
+ Prey Debug
+ x64
+
Release
x64
@@ -79,6 +83,11 @@
MultiByte
v145
+
+ DynamicLibrary
+ MultiByte
+ v145
+
@@ -131,6 +140,12 @@
+
+
+
+
+
+
<_ProjectFileVersion>10.0.40219.1
@@ -141,8 +156,11 @@
AllRules.ruleset
+ AllRules.ruleset
+
+
AllRules.ruleset
@@ -171,6 +189,21 @@
precompiled.h
+
+
+
+
+
+
+ ../gamex64.dll
+
+
+ Use
+
+
+ precompiled.h
+
+
@@ -333,6 +366,7 @@
Create
+ Create
Create
Create
Create
diff --git a/neo/idlib.vcxproj b/neo/idlib.vcxproj
index 9fd0d007..c9b75af3 100644
--- a/neo/idlib.vcxproj
+++ b/neo/idlib.vcxproj
@@ -25,6 +25,10 @@
Dedicated Release
x64
+
+ Prey Debug
+ x64
+
Release
x64
@@ -79,6 +83,11 @@
MultiByte
v145
+
+ StaticLibrary
+ MultiByte
+ v145
+
@@ -131,6 +140,12 @@
+
+
+
+
+
+
<_ProjectFileVersion>10.0.40219.1
@@ -141,8 +156,11 @@
AllRules.ruleset
+ AllRules.ruleset
+
+
AllRules.ruleset
@@ -156,7 +174,10 @@
-
+
+
+ PREY;_DEBUG;%(PreprocessorDefinitions)
+
@@ -206,8 +227,11 @@
Create
Create
Create
+ Create
+
+
Create
Create
Create
diff --git a/neo/idlib/BitMsg.h b/neo/idlib/BitMsg.h
index 8fc7be05..67382719 100644
--- a/neo/idlib/BitMsg.h
+++ b/neo/idlib/BitMsg.h
@@ -119,6 +119,14 @@ public:
int ReadData( void *data, int length ) const;
void ReadNetadr( netadr_t *adr ) const;
+ void WriteVec3(const idVec3& vector);
+ void WriteMat3(const idMat3& axis);
+ void WriteBool(bool boolean);
+
+ idVec3 ReadVec3() const;
+ idMat3 ReadMat3() const;
+ bool ReadBool() const;
+
int ReadDeltaChar( int oldValue ) const;
int ReadDeltaByte( int oldValue ) const;
int ReadDeltaShort( int oldValue ) const;
@@ -427,6 +435,42 @@ ID_INLINE float idBitMsg::ReadDeltaFloat( float oldValue, int exponentBits, int
return idMath::BitsToFloat( newBits, exponentBits, mantissaBits );
}
+ID_INLINE void idBitMsg::WriteVec3(const idVec3& vector) {
+ WriteFloat(vector[0]);
+ WriteFloat(vector[1]);
+ WriteFloat(vector[2]);
+}
+
+ID_INLINE void idBitMsg::WriteMat3(const idMat3& axis) {
+ WriteVec3(axis[0]);
+ WriteVec3(axis[1]);
+ WriteVec3(axis[2]);
+}
+
+ID_INLINE void idBitMsg::WriteBool(bool boolean) {
+ WriteBits(boolean, 1);
+}
+
+ID_INLINE idVec3 idBitMsg::ReadVec3() const {
+ idVec3 vector;
+ vector[0] = ReadFloat();
+ vector[1] = ReadFloat();
+ vector[2] = ReadFloat();
+ return vector;
+}
+
+ID_INLINE idMat3 idBitMsg::ReadMat3() const {
+ idMat3 axis;
+ axis[0] = ReadVec3();
+ axis[1] = ReadVec3();
+ axis[2] = ReadVec3();
+ return axis;
+}
+
+ID_INLINE bool idBitMsg::ReadBool() const {
+ int boolean = ReadBits(1);
+ return *(bool*)&boolean;//We seem to get a performance warning unless I do this. Ick!
+}
/*
===============================================================================
diff --git a/neo/idlib/containers/List.h b/neo/idlib/containers/List.h
index 59a550fd..c0c22f88 100644
--- a/neo/idlib/containers/List.h
+++ b/neo/idlib/containers/List.h
@@ -131,7 +131,7 @@ public:
void Swap( idList &other ); // swap the contents of the lists
void DeleteContents( bool clear ); // delete the contents of the list
-private:
+protected:
int num;
int size;
int granularity;
diff --git a/neo/idlib/math/Vector.cpp b/neo/idlib/math/Vector.cpp
index 469b5628..f1c168d2 100644
--- a/neo/idlib/math/Vector.cpp
+++ b/neo/idlib/math/Vector.cpp
@@ -213,6 +213,42 @@ idMat3 idVec3::ToMat3( void ) const {
return mat;
}
+#ifdef PREY
+// HUMANHEAD nla
+/*
+=============
+idVec3::hhToMat3
+=============
+*/
+idMat3 idVec3::hhToMat3(void) const {
+ idVec3 left;
+ idVec3 down;
+
+ // NormalVectors actually returns left and down
+ NormalVectors(left, down);
+ return idMat3(*this, left, -down);
+}
+
+// HUMANHEAD PDM: Vector conversion to/from axis aligned direction masks (6-bit vector compression)
+int idVec3::DirectionMask() const {
+ int mask = 0;
+ for (int term = 0; term < 3; term++) {
+ mask += (*this)[term] < 0 ? 1 << (term << 1) : (*this)[term]>0 ? 1 << ((term << 1) + 1) : 0;
+ }
+ return mask;
+}
+
+idVec3::idVec3(int directionMask) {
+ int negativeMask, positiveMask;
+ for (int term = 0; term < 3; term++) {
+ negativeMask = 1 << (term << 1);
+ positiveMask = 1 << ((term << 1) + 1);
+ (*this)[term] = (directionMask & negativeMask) ? -1 : (directionMask & positiveMask) ? 1 : 0;
+ }
+}
+// HUMANHEAD END
+#endif
+
/*
=============
idVec3::ToString
diff --git a/neo/idlib/math/Vector.h b/neo/idlib/math/Vector.h
index 8ef454dc..4ac114ca 100644
--- a/neo/idlib/math/Vector.h
+++ b/neo/idlib/math/Vector.h
@@ -313,6 +313,18 @@ ID_INLINE float *idVec2::ToFloatPtr( void ) {
//
//===============================================================
+#ifdef PREY
+// HUMANHEAD pdm: support for 6 bit vector compression, direction masks
+#define INDEX_IN_MASK(mask, index) ( ((mask) & (1<<(index)) ) != 0 )
+#define MASK_NEGX 0x0001
+#define MASK_POSX 0x0002
+#define MASK_NEGY 0x0004
+#define MASK_POSY 0x0008
+#define MASK_NEGZ 0x0010
+#define MASK_POSZ 0x0020
+// HUMANHEAD END
+#endif
+
class idVec3 {
public:
float x;
@@ -384,11 +396,29 @@ public:
void Lerp( const idVec3 &v1, const idVec3 &v2, const float l );
void SLerp( const idVec3 &v1, const idVec3 &v2, const float l );
+
+ idVec3 ToNormal() const;
+#ifdef PREY
+ // HUMANHEAD nla - Returns a Mat3 with Z up instead of down
+ idMat3 hhToMat3(void) const; // vector should be normalized
+ int DirectionMask() const; // calculate direction bitmask
+ idVec3(int directionMask); // construct from direction bitmask
+ // HUMANHEAD END
+#endif
};
extern idVec3 vec3_origin;
#define vec3_zero vec3_origin
+
+ID_INLINE idVec3 idVec3::ToNormal() const {
+ float sqrLength, invLength;
+
+ sqrLength = x * x + y * y + z * z;
+ invLength = idMath::InvSqrt(sqrLength);
+ return *this * invLength;
+}
+
ID_INLINE idVec3::idVec3( void ) {
}
diff --git a/neo/idlib/precompiled.h b/neo/idlib/precompiled.h
index 9ba6f56a..d0c57a9a 100644
--- a/neo/idlib/precompiled.h
+++ b/neo/idlib/precompiled.h
@@ -163,30 +163,41 @@ const int MAX_EXPRESSION_REGISTERS = 4096;
#include "../ui/UserInterface.h"
// collision detection system
+#if defined(PREY)
+#include "../prey/cm/CollisionModel.h"
+#else
#include "../cm/CollisionModel.h"
+#endif
// AAS files and manager
#include "../tools/compilers/aas/AASFile.h"
#include "../tools/compilers/aas/AASFileManager.h"
// game
+#if defined(PREY)
+#include "../prey/game/Game.h"
+#else
#if defined(_D3XP)
#include "../d3xp/Game.h"
#else
#include "../game/Game.h"
#endif
-
+#endif
//-----------------------------------------------------
#ifndef _D3SDK
#ifdef GAME_DLL
+#if defined(PREY)
+#include "../prey/game/Game_local.h"
+#else
#if defined(_D3XP)
#include "../d3xp/Game_local.h"
#else
#include "../game/Game_local.h"
#endif
+#endif
#else
diff --git a/neo/opengl/opengl.vcxproj b/neo/opengl/opengl.vcxproj
index 5e8e6e5c..01da4388 100644
--- a/neo/opengl/opengl.vcxproj
+++ b/neo/opengl/opengl.vcxproj
@@ -5,6 +5,14 @@
Debug
Win32
+
+ Prey Debug
+ Win32
+
+
+ Prey Debug
+ x64
+
Release
Win32
@@ -22,69 +30,106 @@
Disabled
+ Disabled
MaxSpeed
+ MaxSpeed
Speed
+ Speed
Default
+ Default
MaxSpeed
+ MaxSpeed
Speed
+ Speed
Default
+ Default
MaxSpeed
+ MaxSpeed
Speed
+ Speed
Default
+ Default
MaxSpeed
+ MaxSpeed
Speed
+ Speed
Default
+ Default
MaxSpeed
+ MaxSpeed
Speed
+ Speed
Default
+ Default
MaxSpeed
+ MaxSpeed
Speed
+ Speed
Default
+ Default
MaxSpeed
+ MaxSpeed
Speed
+ Speed
Default
+ Default
MaxSpeed
+ MaxSpeed
Speed
+ Speed
Default
+ Default
MaxSpeed
+ MaxSpeed
Speed
+ Speed
Default
+ Default
MaxSpeed
+ MaxSpeed
Speed
+ Speed
Default
+ Default
MaxSpeed
+ MaxSpeed
Speed
+ Speed
Default
+ Default
MaxSpeed
+ MaxSpeed
Speed
+ Speed
Default
+ Default
@@ -127,6 +172,12 @@
v145
Unicode
+
+ StaticLibrary
+ true
+ v145
+ Unicode
+
StaticLibrary
false
@@ -140,6 +191,12 @@
v145
Unicode
+
+ StaticLibrary
+ true
+ v145
+ Unicode
+
StaticLibrary
false
@@ -155,12 +212,18 @@
+
+
+
+
+
+
@@ -181,6 +244,22 @@
true
+
+
+ Level3
+ true
+ WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions)
+ true
+ stdcpp20
+ NotUsing
+ pch.h
+
+
+
+
+ true
+
+
Level3
@@ -218,6 +297,25 @@
true
+
+
+ Level3
+ true
+ _DEBUG;_LIB;%(PreprocessorDefinitions)
+ true
+ stdcpp20
+ NotUsing
+ pch.h
+ MultiThreadedDebug
+ streamline/include
+ 4996
+
+
+
+
+ true
+
+
Level3
diff --git a/neo/prey/MayaImport/Maya4.5/maya.h b/neo/prey/MayaImport/Maya4.5/maya.h
new file mode 100644
index 00000000..91ad4ba4
--- /dev/null
+++ b/neo/prey/MayaImport/Maya4.5/maya.h
@@ -0,0 +1,47 @@
+#ifdef _WIN32
+
+#define _BOOL
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#undef _BOOL
+
+#endif // _WIN32
diff --git a/neo/prey/MayaImport/Maya6.0/maya.h b/neo/prey/MayaImport/Maya6.0/maya.h
new file mode 100644
index 00000000..91ad4ba4
--- /dev/null
+++ b/neo/prey/MayaImport/Maya6.0/maya.h
@@ -0,0 +1,47 @@
+#ifdef _WIN32
+
+#define _BOOL
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#undef _BOOL
+
+#endif // _WIN32
diff --git a/neo/prey/MayaImport/exporter.h b/neo/prey/MayaImport/exporter.h
new file mode 100644
index 00000000..d3ec82df
--- /dev/null
+++ b/neo/prey/MayaImport/exporter.h
@@ -0,0 +1,434 @@
+#define MAYA_DEFAULT_CAMERA "camera1"
+
+#define ANIM_TX BIT( 0 )
+#define ANIM_TY BIT( 1 )
+#define ANIM_TZ BIT( 2 )
+#define ANIM_QX BIT( 3 )
+#define ANIM_QY BIT( 4 )
+#define ANIM_QZ BIT( 5 )
+
+typedef enum {
+ WRITE_MESH,
+ WRITE_ANIM,
+ WRITE_CAMERA
+} exportType_t;
+
+typedef struct {
+ idCQuat q;
+ idVec3 t;
+} jointFrame_t;
+
+typedef struct {
+ idCQuat q;
+ idVec3 t;
+ float fov;
+} cameraFrame_t;
+
+/*
+==============================================================================================
+
+ idTokenizer
+
+==============================================================================================
+*/
+
+class idTokenizer {
+private:
+ int currentToken;
+ idStrList tokens;
+
+public:
+ idTokenizer() { Clear(); };
+ void Clear( void ) { currentToken = 0; tokens.Clear(); };
+
+ int SetTokens( const char *buffer );
+ const char *NextToken( const char *errorstring = NULL );
+
+ bool TokenAvailable( void ) { return currentToken < tokens.Num(); };
+ int Num( void ) { return tokens.Num(); };
+ void UnGetToken( void ) { if ( currentToken > 0 ) { currentToken--; } };
+ const char *GetToken( int index ) { if ( ( index >= 0 ) && ( index < tokens.Num() ) ) { return tokens[ index ]; } else { return NULL; } };
+ const char *CurrentToken( void ) { return GetToken( currentToken ); };
+};
+
+/*
+==============================================================================================
+
+ idExportOptions
+
+==============================================================================================
+*/
+
+class idNamePair {
+public:
+ idStr from;
+ idStr to;
+};
+
+class idAnimGroup {
+public:
+ idStr name;
+ idStrList joints;
+};
+
+class idExportOptions {
+private:
+ idTokenizer tokens;
+
+ void Reset( const char *commandline );
+
+public:
+ idStr commandLine;
+ idStr src;
+ idStr dest;
+ idStr game;
+ idStr prefix;
+ float scale;
+ exportType_t type;
+ bool ignoreMeshes;
+ bool clearOrigin;
+ bool clearOriginAxis;
+ bool ignoreScale;
+ int startframe;
+ int endframe;
+ int framerate;
+ float xyzPrecision;
+ float quatPrecision;
+ idStr align;
+ idList renamejoints;
+ idList remapjoints;
+ idStrList keepjoints;
+ idStrList skipmeshes;
+ idStrList keepmeshes;
+ idList exportgroups;
+ idList groups;
+ float rotate;
+ float jointThreshold;
+ int cycleStart;
+
+ // HUMANHEAD pdm: Allow bounds expansion (shared animations calculate bounds based on a different mesh, so give some slop)
+ float boundsExpansion;
+ // HUMANHEAD END
+
+ idExportOptions( const char *commandline, const char *ospath );
+
+ bool jointInExportGroup( const char *jointname );
+};
+
+/*
+==============================================================================
+
+idExportJoint
+
+==============================================================================
+*/
+
+class idExportJoint {
+public:
+ idStr name;
+ idStr realname;
+ idStr longname;
+ int index;
+ int exportNum;
+ bool keep;
+
+ float scale;
+ float invscale;
+
+ MFnDagNode *dagnode;
+
+ idHierarchy mayaNode;
+ idHierarchy exportNode;
+
+ idVec3 t;
+ idMat3 wm;
+
+ idVec3 idt;
+ idMat3 idwm;
+
+ idVec3 bindpos;
+ idMat3 bindmat;
+
+ int animBits;
+ int firstComponent;
+ jointFrame_t baseFrame;
+ int depth;
+
+ idExportJoint();
+ idExportJoint &operator=( const idExportJoint &other );
+};
+
+/*
+==============================================================================
+
+misc structures
+
+==============================================================================
+*/
+
+typedef struct {
+ idExportJoint *joint;
+ float jointWeight;
+ idVec3 offset;
+} exportWeight_t;
+
+typedef struct {
+ idVec3 pos;
+ idVec2 texCoords;
+ int startweight;
+ int numWeights;
+} exportVertex_t;
+
+typedef struct {
+ int indexes[ 3 ];
+} exportTriangle_t;
+
+typedef struct {
+ idVec2 uv[ 3 ];
+} exportUV_t;
+
+ID_INLINE int operator==( exportVertex_t a, exportVertex_t b ) {
+ if ( a.pos != b.pos ) {
+ return false;
+ }
+
+ if ( ( a.texCoords[ 0 ] != b.texCoords[ 0 ] ) || ( a.texCoords[ 1 ] != b.texCoords[ 1 ] ) ) {
+ return false;
+ }
+
+ if ( ( a.startweight != b.startweight ) || ( a.numWeights != b.numWeights ) ) {
+ return false;
+ }
+
+ return true;
+}
+
+/*
+========================================================================
+
+.MD3 triangle model file format
+
+========================================================================
+*/
+
+#define MD3_IDENT (('3'<<24)+('P'<<16)+('D'<<8)+'I')
+#define MD3_VERSION 15
+
+// limits
+#define MD3_MAX_LODS 4
+#define MD3_MAX_TRIANGLES 8192 // per surface
+#define MD3_MAX_VERTS 4096 // per surface
+#define MD3_MAX_SHADERS 256 // per surface
+#define MD3_MAX_FRAMES 1024 // per model
+#define MD3_MAX_SURFACES 32 // per model
+#define MD3_MAX_TAGS 16 // per frame
+
+// vertex scales
+#define MD3_XYZ_SCALE (1.0/64)
+
+// surface geometry should not exceed these limits
+#define SHADER_MAX_VERTEXES 1000
+#define SHADER_MAX_INDEXES (6*SHADER_MAX_VERTEXES)
+
+
+// the maximum size of game reletive pathnames
+#define MAX_Q3PATH 64
+
+typedef struct md3Frame_s {
+ idVec3 bounds[2];
+ idVec3 localOrigin;
+ float radius;
+ char name[16];
+} md3Frame_t;
+
+typedef struct md3Tag_s {
+ char name[MAX_Q3PATH]; // tag name
+ idVec3 origin;
+ idVec3 axis[3];
+} md3Tag_t;
+
+/*
+** md3Surface_t
+**
+** CHUNK SIZE
+** header sizeof( md3Surface_t )
+** shaders sizeof( md3Shader_t ) * numShaders
+** triangles[0] sizeof( md3Triangle_t ) * numTriangles
+** st sizeof( md3St_t ) * numVerts
+** XyzNormals sizeof( md3XyzNormal_t ) * numVerts * numFrames
+*/
+typedef struct {
+ int ident; //
+
+ char name[MAX_Q3PATH]; // polyset name
+
+ int flags;
+ int numFrames; // all surfaces in a model should have the same
+
+ int numShaders; // all surfaces in a model should have the same
+ int numVerts;
+
+ int numTriangles;
+ int ofsTriangles;
+
+ int ofsShaders; // offset from start of md3Surface_t
+ int ofsSt; // texture coords are common for all frames
+ int ofsXyzNormals; // numVerts * numFrames
+
+ int ofsEnd; // next surface follows
+} md3Surface_t;
+
+typedef struct {
+ char name[MAX_Q3PATH];
+ int shaderIndex; // for in-game use
+} md3Shader_t;
+
+typedef struct {
+ int indexes[3];
+} md3Triangle_t;
+
+typedef struct {
+ float st[2];
+} md3St_t;
+
+typedef struct {
+ short xyz[3];
+ short normal;
+} md3XyzNormal_t;
+
+typedef struct {
+ int ident;
+ int version;
+
+ char name[MAX_Q3PATH]; // model name
+
+ int flags;
+
+ int numFrames;
+ int numTags;
+ int numSurfaces;
+
+ int numSkins;
+
+ int ofsFrames; // offset for first frame
+ int ofsTags; // numFrames * numTags
+ int ofsSurfaces; // first surface, others follow
+
+ int ofsEnd; // end of file
+} md3Header_t;
+
+/*
+==============================================================================
+
+idExportMesh
+
+==============================================================================
+*/
+
+class idExportMesh {
+public:
+
+ idStr name;
+ idStr shader;
+
+ bool keep;
+
+ idList verts;
+ idList tris;
+ idList weights;
+ idList uv;
+
+ idExportMesh() { keep = true; };
+ void ShareVerts( void );
+ void GetBounds( idBounds &bounds ) const;
+ void Merge( idExportMesh *mesh );
+};
+
+/*
+==============================================================================
+
+idExportModel
+
+==============================================================================
+*/
+
+class idExportModel {
+public:
+ idExportJoint *exportOrigin;
+ idList joints;
+ idHierarchy mayaHead;
+ idHierarchy exportHead;
+ idList cameraCuts;
+ idList camera;
+ idList bounds;
+ idList jointFrames;
+ idList frames;
+ int frameRate;
+ int numFrames;
+ int skipjoints;
+ int export_joints;
+ idList meshes;
+
+ idExportModel();
+ ~idExportModel();
+ idExportJoint *FindJointReal( const char *name );
+ idExportJoint *FindJoint( const char *name );
+ bool WriteMesh( const char *filename, idExportOptions &options );
+ bool WriteAnim( const char *filename, idExportOptions &options );
+ bool WriteCamera( const char *filename, idExportOptions &options );
+};
+
+/*
+==============================================================================
+
+Maya
+
+==============================================================================
+*/
+
+class idMayaExport {
+private:
+ idExportModel model;
+ idExportOptions &options;
+
+ void FreeDagNodes( void );
+
+ float TimeForFrame( int num ) const;
+ int GetMayaFrameNum( int num ) const;
+ void SetFrame( int num );
+
+
+ void GetBindPose( MObject &jointNode, idExportJoint *joint, float scale );
+ void GetLocalTransform( idExportJoint *joint, idVec3 &pos, idMat3 &mat );
+ void GetWorldTransform( idExportJoint *joint, idVec3 &pos, idMat3 &mat, float scale );
+
+ void CreateJoints( float scale );
+ void PruneJoints( idStrList &keepjoints, idStr &prefix );
+ void RenameJoints( idList &renamejoints, idStr &prefix );
+ bool RemapParents( idList &remapjoints );
+
+ MObject FindShader( MObject& setNode );
+ void GetTextureForMesh( idExportMesh *mesh, MFnDagNode &dagNode );
+
+ idExportMesh *CopyMesh( MFnSkinCluster &skinCluster, float scale );
+ void CreateMesh( float scale );
+ void CombineMeshes( void );
+
+ void GetAlignment( idStr &alignName, idMat3 &align, float rotate, int startframe );
+
+ const char *GetObjectType( MObject object );
+
+ float GetCameraFov( idExportJoint *joint );
+ void GetCameraFrame( idExportJoint *camera, idMat3 &align, cameraFrame_t *cam );
+ void CreateCameraAnim( idMat3 &align );
+
+ void GetDefaultPose( idMat3 &align );
+ void CreateAnimation( idMat3 &align );
+
+public:
+ idMayaExport( idExportOptions &exportOptions ) : options( exportOptions ) { };
+ ~idMayaExport();
+
+ void ConvertModel( void );
+ void ConvertToMD3( void );
+};
diff --git a/neo/prey/MayaImport/maya5.0/maya.h b/neo/prey/MayaImport/maya5.0/maya.h
new file mode 100644
index 00000000..cd007cb7
--- /dev/null
+++ b/neo/prey/MayaImport/maya5.0/maya.h
@@ -0,0 +1,47 @@
+#ifdef _WIN32
+
+#define _BOOL
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#undef _BOOL
+
+#endif // _WIN32
diff --git a/neo/prey/MayaImport/maya_main.cpp b/neo/prey/MayaImport/maya_main.cpp
new file mode 100644
index 00000000..3794fc37
--- /dev/null
+++ b/neo/prey/MayaImport/maya_main.cpp
@@ -0,0 +1,3152 @@
+
+#include "../idlib/precompiled.h"
+#pragma hdrstop
+
+// HUMANHEAD CJR : Set-up for Maya 6.0
+#define USE_MAYA_6 1
+
+#if USE_MAYA_6
+#undef _GLOBAL_USING
+#define _GLOBAL_USING 0
+#include "Maya6.0/maya.h" // must also change include directory in project from "MayaImport\Maya4.5\include" to "MayaImport\Maya6.0\include" (requires MSDev 7.1)
+#else
+#include "Maya5.0/maya.h"
+#endif
+// HUMANHEAD END
+
+#include "exporter.h"
+#include "maya_main.h"
+
+idStr errorMessage;
+bool initialized = false;
+
+#define DEFAULT_ANIM_EPSILON 0.125f
+#define DEFAULT_QUAT_EPSILON ( 1.0f / 8192.0f )
+
+#define SLOP_VERTEX 0.01f // merge xyz coordinates this far apart
+#define SLOP_TEXCOORD 0.001f // merge texture coordinates this far apart
+
+const char *componentNames[ 6 ] = { "Tx", "Ty", "Tz", "Qx", "Qy", "Qz" };
+
+idSys * sys = NULL;
+idCommon * common = NULL;
+idCVarSystem * cvarSystem = NULL;
+
+idCVar * idCVar::staticVars = NULL;
+
+// HUMANHEAD pdm: Output all the animation text with Green color
+#define Printf APrintf
+// HUMANHEAD END
+
+/*
+=================
+MayaError
+=================
+*/
+void MayaError( const char *fmt, ... ) {
+ va_list argptr;
+ char text[ 8192 ];
+
+ va_start( argptr, fmt );
+ idStr::vsnPrintf( text, sizeof( text ), fmt, argptr );
+ va_end( argptr );
+
+ throw idException( text );
+}
+
+/*
+=================
+FS_WriteFloatString
+=================
+*/
+#define MAX_PRINT_MSG 4096
+static int WriteFloatString( FILE *file, const char *fmt, ... ) {
+ long i;
+ unsigned long u;
+ double f;
+ char *str;
+ int index;
+ idStr tmp, format;
+ va_list argPtr;
+
+ va_start( argPtr, fmt );
+
+ index = 0;
+
+ while( *fmt ) {
+ switch( *fmt ) {
+ case '%':
+ format = "";
+ format += *fmt++;
+ while ( (*fmt >= '0' && *fmt <= '9') ||
+ *fmt == '.' || *fmt == '-' || *fmt == '+' || *fmt == '#') {
+ format += *fmt++;
+ }
+ format += *fmt;
+ switch( *fmt ) {
+ case 'f':
+ case 'e':
+ case 'E':
+ case 'g':
+ case 'G':
+ f = va_arg( argPtr, double );
+ if ( format.Length() <= 2 ) {
+ // high precision floating point number without trailing zeros
+ sprintf( tmp, "%1.10f", f );
+ tmp.StripTrailing( '0' );
+ tmp.StripTrailing( '.' );
+ index += fprintf( file, "%s", tmp.c_str() );
+ }
+ else {
+ index += fprintf( file, format.c_str(), f );
+ }
+ break;
+ case 'd':
+ case 'i':
+ i = va_arg( argPtr, long );
+ index += fprintf( file, format.c_str(), i );
+ break;
+ case 'u':
+ u = va_arg( argPtr, unsigned long );
+ index += fprintf( file, format.c_str(), u );
+ break;
+ case 'o':
+ u = va_arg( argPtr, unsigned long );
+ index += fprintf( file, format.c_str(), u );
+ break;
+ case 'x':
+ u = va_arg( argPtr, unsigned long );
+ index += fprintf( file, format.c_str(), u );
+ break;
+ case 'X':
+ u = va_arg( argPtr, unsigned long );
+ index += fprintf( file, format.c_str(), u );
+ break;
+ case 'c':
+ i = va_arg( argPtr, long );
+ index += fprintf( file, format.c_str(), (char) i );
+ break;
+ case 's':
+ str = va_arg( argPtr, char * );
+ index += fprintf( file, format.c_str(), str );
+ break;
+ case '%':
+ index += fprintf( file, format.c_str() );
+ break;
+ default:
+ MayaError( "WriteFloatString: invalid format %s", format.c_str() );
+ break;
+ }
+ fmt++;
+ break;
+ case '\\':
+ fmt++;
+ switch( *fmt ) {
+ case 't':
+ index += fprintf( file, "\t" );
+ break;
+ case 'n':
+ index += fprintf( file, "\n" );
+ default:
+ MayaError( "WriteFloatString: unknown escape character \'%c\'", *fmt );
+ break;
+ }
+ fmt++;
+ break;
+ default:
+ index += fprintf( file, "%c", *fmt );
+ fmt++;
+ break;
+ }
+ }
+
+ va_end( argPtr );
+
+ return index;
+}
+
+/*
+================
+OSPathToRelativePath
+
+takes a full OS path, as might be found in data from a media creation
+program, and converts it to a qpath by stripping off directories
+
+Returns false if the osPath tree doesn't match any of the existing
+search paths.
+================
+*/
+bool OSPathToRelativePath( const char *osPath, idStr &qpath, const char *game ) {
+ char *s, *base;
+
+ // skip a drive letter?
+
+ // search for anything with BASE_GAMEDIR in it
+ // Ase files from max may have the form of:
+ // "//Purgatory/purgatory/doom/base/models/mapobjects/bitch/hologirl.tga"
+ // which won't match any of our drive letter based search paths
+ base = (char *)strstr( osPath, BASE_GAMEDIR ); //HUMANHEAD rww vs2005 - strstr now returns const char
+
+ // _D3XP added mod support
+ if ( base == NULL && strlen(game) > 0 ) {
+
+ base = s = (char *)strstr( osPath, game ); //HUMANHEAD rww vs2005 - strstr now returns const char
+
+ while( s = strstr( s, game ) ) {
+ s += strlen( game );
+ if ( s[0] == '/' || s[0] == '\\' ) {
+ base = s;
+ }
+ }
+ }
+
+ if ( base ) {
+ s = strstr( base, "/" );
+ if ( !s ) {
+ s = strstr( base, "\\" );
+ }
+ if ( s ) {
+ qpath = s + 1;
+ return true;
+ }
+ }
+
+ common->Printf( "OSPathToRelativePath failed on %s\n", osPath );
+ qpath = osPath;
+
+ return false;
+}
+
+/*
+===============
+ConvertFromIdSpace
+===============
+*/
+idMat3 ConvertFromIdSpace( const idMat3 &idmat ) {
+ idMat3 mat;
+
+ mat[ 0 ][ 0 ] = idmat[ 0 ][ 0 ];
+ mat[ 0 ][ 2 ] = -idmat[ 0 ][ 1 ];
+ mat[ 0 ][ 1 ] = idmat[ 0 ][ 2 ];
+
+ mat[ 1 ][ 0 ] = idmat[ 1 ][ 0 ];
+ mat[ 1 ][ 2 ] = -idmat[ 1 ][ 1 ];
+ mat[ 1 ][ 1 ] = idmat[ 1 ][ 2 ];
+
+ mat[ 2 ][ 0 ] = idmat[ 2 ][ 0 ];
+ mat[ 2 ][ 2 ] = -idmat[ 2 ][ 1 ];
+ mat[ 2 ][ 1 ] = idmat[ 2 ][ 2 ];
+
+ return mat;
+}
+
+/*
+===============
+ConvertFromIdSpace
+===============
+*/
+idVec3 ConvertFromIdSpace( const idVec3 &idpos ) {
+ idVec3 pos;
+
+ pos.x = idpos.x;
+ pos.z = -idpos.y;
+ pos.y = idpos.z;
+
+ return pos;
+}
+
+/*
+===============
+ConvertToIdSpace
+===============
+*/
+idMat3 ConvertToIdSpace( const idMat3 &mat ) {
+ idMat3 idmat;
+
+ idmat[ 0 ][ 0 ] = mat[ 0 ][ 0 ];
+ idmat[ 0 ][ 1 ] = -mat[ 0 ][ 2 ];
+ idmat[ 0 ][ 2 ] = mat[ 0 ][ 1 ];
+
+ idmat[ 1 ][ 0 ] = mat[ 1 ][ 0 ];
+ idmat[ 1 ][ 1 ] = -mat[ 1 ][ 2 ];
+ idmat[ 1 ][ 2 ] = mat[ 1 ][ 1 ];
+
+ idmat[ 2 ][ 0 ] = mat[ 2 ][ 0 ];
+ idmat[ 2 ][ 1 ] = -mat[ 2 ][ 2 ];
+ idmat[ 2 ][ 2 ] = mat[ 2 ][ 1 ];
+
+ return idmat;
+}
+
+/*
+===============
+ConvertToIdSpace
+===============
+*/
+idVec3 ConvertToIdSpace( const idVec3 &pos ) {
+ idVec3 idpos;
+
+ idpos.x = pos.x;
+ idpos.y = -pos.z;
+ idpos.z = pos.y;
+
+ return idpos;
+}
+
+/*
+===============
+idVec
+===============
+*/
+idVec3 idVec( const MFloatPoint &point ) {
+ return idVec3( point[ 0 ], point[ 1 ], point[ 2 ] );
+}
+
+/*
+===============
+idVec
+===============
+*/
+idVec3 idVec( const MMatrix &matrix ) {
+ return idVec3( matrix[ 3 ][ 0 ], matrix[ 3 ][ 1 ], matrix[ 3 ][ 2 ] );
+}
+
+/*
+===============
+idMat
+===============
+*/
+idMat3 idMat( const MMatrix &matrix ) {
+ int j, k;
+ idMat3 mat;
+
+ for( j = 0; j < 3; j++ ) {
+ for( k = 0; k < 3; k++ ) {
+ mat[ j ][ k ] = matrix[ j ][ k ];
+ }
+ }
+
+ return mat;
+}
+
+/*
+===============
+GetParent
+===============
+*/
+MFnDagNode *GetParent( MFnDagNode *joint ) {
+ MStatus status;
+ MObject parentObject;
+
+ parentObject = joint->parent( 0, &status );
+ if ( !status && status.statusCode() == MStatus::kInvalidParameter ) {
+ return NULL;
+ }
+
+ while( !parentObject.hasFn( MFn::kTransform ) ) {
+ MFnDagNode parentNode( parentObject, &status );
+ if ( !status ) {
+ return NULL;
+ }
+
+ parentObject = parentNode.parent( 0, &status );
+ if ( !status && status.statusCode() == MStatus::kInvalidParameter ) {
+ return NULL;
+ }
+ }
+
+ MFnDagNode *parentNode;
+
+ parentNode = new MFnDagNode( parentObject, &status );
+ if ( !status ) {
+ delete parentNode;
+ return NULL;
+ }
+
+ return parentNode;
+}
+
+/*
+==============================================================================================
+
+ idTokenizer
+
+==============================================================================================
+*/
+
+/*
+====================
+idTokenizer::SetTokens
+====================
+*/
+int idTokenizer::SetTokens( const char *buffer ) {
+ const char *cmd;
+
+ Clear();
+
+ // tokenize commandline
+ cmd = buffer;
+ while ( *cmd ) {
+ // skip whitespace
+ while( *cmd && isspace( *cmd ) ) {
+ cmd++;
+ }
+
+ if ( !*cmd ) {
+ break;
+ }
+
+ idStr ¤t = tokens.Alloc();
+ while( *cmd && !isspace( *cmd ) ) {
+ current += *cmd;
+ cmd++;
+ }
+ }
+
+ return tokens.Num();
+}
+
+/*
+====================
+idTokenizer::NextToken
+====================
+*/
+const char *idTokenizer::NextToken( const char *errorstring ) {
+ if ( currentToken < tokens.Num() ) {
+ return tokens[ currentToken++ ];
+ }
+
+ if ( errorstring ) {
+ MayaError( "Error: %s", errorstring );
+ }
+
+ return NULL;
+}
+
+/*
+==============================================================================================
+
+ idExportOptions
+
+==============================================================================================
+*/
+
+/*
+====================
+idExportOptions::Reset
+====================
+*/
+void idExportOptions::Reset( const char *commandline ) {
+ scale = 1.0f;
+ type = WRITE_MESH;
+ startframe = -1;
+ endframe = -1;
+ ignoreMeshes = false;
+ clearOrigin = false;
+ clearOriginAxis = false;
+ framerate = 24;
+ align = "";
+ rotate = 0.0f;
+ commandLine = commandline;
+ prefix = "";
+ jointThreshold = 0.05f;
+ ignoreScale = false;
+ xyzPrecision = DEFAULT_ANIM_EPSILON;
+ quatPrecision = DEFAULT_QUAT_EPSILON;
+ cycleStart = -1;
+
+ // HUMANHEAD pdm: Allow bounds expansion (shared animations calculate bounds based on a different mesh, so give some slop)
+ boundsExpansion = 0.0f;
+ // HUMANHEAD END
+
+ src.Clear();
+ dest.Clear();
+
+ tokens.SetTokens( commandline );
+
+ keepjoints.Clear();
+ renamejoints.Clear();
+ remapjoints.Clear();
+ exportgroups.Clear();
+ skipmeshes.Clear();
+ keepmeshes.Clear();
+ groups.Clear();
+}
+
+/*
+====================
+idExportOptions::idExportOptions
+====================
+*/
+idExportOptions::idExportOptions( const char *commandline, const char *ospath ) {
+ idStr token;
+ idNamePair joints;
+ int i;
+ idAnimGroup *group;
+ idStr sourceDir;
+ idStr destDir;
+
+ Reset( commandline );
+
+ token = tokens.NextToken( "Missing export command" );
+ if ( token == "mesh" ) {
+ type = WRITE_MESH;
+ } else if ( token == "anim" ) {
+ type = WRITE_ANIM;
+ } else if ( token == "camera" ) {
+ type = WRITE_CAMERA;
+ } else {
+ MayaError( "Unknown export command '%s'", token.c_str() );
+ }
+
+ src = tokens.NextToken( "Missing source filename" );
+ dest = src;
+
+ for( token = tokens.NextToken(); token != ""; token = tokens.NextToken() ) {
+ if ( token == "-force" ) {
+ // skip
+ } else if ( token == "-game" ) {
+ // parse game name
+ game = tokens.NextToken( "Expecting game name after -game" );
+
+ } else if ( token == "-rename" ) {
+ // parse joint to rename
+ joints.from = tokens.NextToken( "Missing joint name for -rename. Usage: -rename [joint name] [new name]" );
+ joints.to = tokens.NextToken( "Missing new name for -rename. Usage: -rename [joint name] [new name]" );
+ renamejoints.Append( joints );
+
+ } else if ( token == "-prefix" ) {
+ prefix = tokens.NextToken( "Missing name for -prefix. Usage: -prefix [joint prefix]" );
+
+ } else if ( token == "-parent" ) {
+ // parse joint to reparent
+ joints.from = tokens.NextToken( "Missing joint name for -parent. Usage: -parent [joint name] [new parent]" );
+ joints.to = tokens.NextToken( "Missing new parent for -parent. Usage: -parent [joint name] [new parent]" );
+ remapjoints.Append( joints );
+
+ } else if ( !token.Icmp( "-sourcedir" ) ) {
+ // parse source directory
+ sourceDir = tokens.NextToken( "Missing filename for -sourcedir. Usage: -sourcedir [directory]" );
+
+ } else if ( !token.Icmp( "-destdir" ) ) {
+ // parse destination directory
+ destDir = tokens.NextToken( "Missing filename for -destdir. Usage: -destdir [directory]" );
+
+ } else if ( token == "-dest" ) {
+ // parse destination filename
+ dest = tokens.NextToken( "Missing filename for -dest. Usage: -dest [filename]" );
+
+ } else if ( token == "-range" ) {
+ // parse frame range to export
+ token = tokens.NextToken( "Missing start frame for -range. Usage: -range [start frame] [end frame]" );
+ startframe = atoi( token );
+ token = tokens.NextToken( "Missing end frame for -range. Usage: -range [start frame] [end frame]" );
+ endframe = atoi( token );
+
+ if ( startframe > endframe ) {
+ MayaError( "Start frame is greater than end frame." );
+ }
+
+ } else if ( !token.Icmp( "-cycleStart" ) ) {
+ // parse start frame of cycle
+ token = tokens.NextToken( "Missing cycle start frame for -cycleStart. Usage: -cycleStart [first frame of cycle]" );
+ cycleStart = atoi( token );
+
+ } else if ( token == "-scale" ) {
+ // parse scale
+ token = tokens.NextToken( "Missing scale amount for -scale. Usage: -scale [scale amount]" );
+ scale = atof( token );
+
+ } else if ( token == "-align" ) {
+ // parse align joint
+ align = tokens.NextToken( "Missing joint name for -align. Usage: -align [joint name]" );
+
+ // HUMANHEAD pdm: Allow bounds expansion (shared animations calculate bounds based on a different mesh, so give some slop)
+ } else if ( token == "-expandbounds" ) {
+ // parse boundsexpansion
+ token = tokens.NextToken( "Missing expansion amount for -expandbounds. Usage: -expandbounds [expansion amount]" );
+ boundsExpansion = atof( token );
+ // HUMANHEAD END
+
+ } else if ( token == "-rotate" ) {
+ // parse angle rotation
+ token = tokens.NextToken( "Missing value for -rotate. Usage: -rotate [yaw]" );
+ rotate = -atof( token );
+
+ } else if ( token == "-nomesh" ) {
+ ignoreMeshes = true;
+
+ } else if ( token == "-clearorigin" ) {
+ clearOrigin = true;
+ clearOriginAxis = true;
+
+ } else if ( token == "-clearoriginaxis" ) {
+ clearOriginAxis = true;
+
+ } else if ( token == "-ignorescale" ) {
+ ignoreScale = true;
+
+ } else if ( token == "-xyzprecision" ) {
+ // parse quaternion precision
+ token = tokens.NextToken( "Missing value for -xyzprecision. Usage: -xyzprecision [precision]" );
+ xyzPrecision = atof( token );
+ if ( xyzPrecision < 0.0f ) {
+ MayaError( "Invalid value for -xyzprecision. Must be >= 0" );
+ }
+
+ } else if ( token == "-quatprecision" ) {
+ // parse quaternion precision
+ token = tokens.NextToken( "Missing value for -quatprecision. Usage: -quatprecision [precision]" );
+ quatPrecision = atof( token );
+ if ( quatPrecision < 0.0f ) {
+ MayaError( "Invalid value for -quatprecision. Must be >= 0" );
+ }
+
+ } else if ( token == "-jointthreshold" ) {
+ // parse joint threshold
+ token = tokens.NextToken( "Missing weight for -jointthreshold. Usage: -jointthreshold [minimum joint weight]" );
+ jointThreshold = atof( token );
+
+ } else if ( token == "-skipmesh" ) {
+ token = tokens.NextToken( "Missing name for -skipmesh. Usage: -skipmesh [name of mesh to skip]" );
+ skipmeshes.AddUnique( token );
+
+ } else if ( token == "-keepmesh" ) {
+ token = tokens.NextToken( "Missing name for -keepmesh. Usage: -keepmesh [name of mesh to keep]" );
+ keepmeshes.AddUnique( token );
+
+ } else if ( token == "-jointgroup" ) {
+ token = tokens.NextToken( "Missing name for -jointgroup. Usage: -jointgroup [group name] [joint1] [joint2]...[joint n]" );
+ group = groups.Ptr();
+ for( i = 0; i < groups.Num(); i++, group++ ) {
+ if ( group->name == token ) {
+ break;
+ }
+ }
+
+ if ( i >= groups.Num() ) {
+ // create a new group
+ group = &groups.Alloc();
+ group->name = token;
+ }
+
+ while( tokens.TokenAvailable() ) {
+ token = tokens.NextToken();
+ if ( token[ 0 ] == '-' ) {
+ tokens.UnGetToken();
+ break;
+ }
+
+ group->joints.AddUnique( token );
+ }
+ } else if ( token == "-group" ) {
+ // add the list of groups to export (these don't affect the hierarchy)
+ while( tokens.TokenAvailable() ) {
+ token = tokens.NextToken();
+ if ( token[ 0 ] == '-' ) {
+ tokens.UnGetToken();
+ break;
+ }
+
+ group = groups.Ptr();
+ for( i = 0; i < groups.Num(); i++, group++ ) {
+ if ( group->name == token ) {
+ break;
+ }
+ }
+
+ if ( i >= groups.Num() ) {
+ MayaError( "Unknown group '%s'", token.c_str() );
+ }
+
+ exportgroups.AddUnique( group );
+ }
+ } else if ( token == "-keep" ) {
+ // add joints that are kept whether they're used by a mesh or not
+ while( tokens.TokenAvailable() ) {
+ token = tokens.NextToken();
+ if ( token[ 0 ] == '-' ) {
+ tokens.UnGetToken();
+ break;
+ }
+ keepjoints.AddUnique( token );
+ }
+ } else {
+ MayaError( "Unknown option '%s'", token.c_str() );
+ }
+ }
+
+ token = src;
+ src = ospath;
+ src.BackSlashesToSlashes();
+ src.AppendPath( sourceDir );
+ src.AppendPath( token );
+
+ token = dest;
+ dest = ospath;
+ dest.BackSlashesToSlashes();
+ dest.AppendPath( destDir );
+ dest.AppendPath( token );
+
+ // Maya only accepts unix style path separators
+ src.BackSlashesToSlashes();
+ dest.BackSlashesToSlashes();
+
+ if ( skipmeshes.Num() && keepmeshes.Num() ) {
+ MayaError( "Can't use -keepmesh and -skipmesh together." );
+ }
+}
+
+/*
+====================
+idExportOptions::jointInExportGroup
+====================
+*/
+bool idExportOptions::jointInExportGroup( const char *jointname ) {
+ int i;
+ int j;
+ idAnimGroup *group;
+
+ if ( !exportgroups.Num() ) {
+ // if we don't have any groups specified as export then export every joint
+ return true;
+ }
+
+ // search through all exported groups to see if this joint is exported
+ for( i = 0; i < exportgroups.Num(); i++ ) {
+ group = exportgroups[ i ];
+ for( j = 0; j < group->joints.Num(); j++ ) {
+ if ( group->joints[ j ] == jointname ) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+/*
+==============================================================================
+
+idExportJoint
+
+==============================================================================
+*/
+
+idExportJoint::idExportJoint() {
+ index = 0;
+ exportNum = 0;
+
+ mayaNode.SetOwner( this );
+ exportNode.SetOwner( this );
+
+ dagnode = NULL;
+
+ t = vec3_zero;
+ wm = mat3_default;
+ bindpos = vec3_zero;
+ bindmat = mat3_default;
+ keep = false;
+ scale = 1.0f;
+ invscale = 1.0f;
+ animBits = 0;
+ firstComponent = 0;
+ baseFrame.q.Set( 0.0f, 0.0f, 0.0f );
+ baseFrame.t.Zero();
+}
+
+idExportJoint &idExportJoint::operator=( const idExportJoint &other ) {
+ name = other.name;
+ realname = other.realname;
+ longname = other.longname;
+ index = other.index;
+ exportNum = other.exportNum;
+ keep = other.keep;
+
+ scale = other.scale;
+ invscale = other.invscale;
+
+ dagnode = other.dagnode;
+
+ mayaNode = other.mayaNode;
+ exportNode = other.exportNode;
+
+ t = other.t;
+ idt = other.idt;
+ wm = other.wm;
+ idwm = other.idwm;
+ bindpos = other.bindpos;
+ bindmat = other.bindmat;
+
+ animBits = other.animBits;
+ firstComponent = other.firstComponent;
+ baseFrame = other.baseFrame;
+
+ mayaNode.SetOwner( this );
+ exportNode.SetOwner( this );
+
+ return *this;
+}
+
+/*
+==============================================================================
+
+idExportMesh
+
+==============================================================================
+*/
+
+void idExportMesh::ShareVerts( void ) {
+ int i, j, k;
+ exportVertex_t vert;
+ idList v;
+
+ v = verts;
+ verts.Clear();
+ for( i = 0; i < tris.Num(); i++ ) {
+ for( j = 0; j < 3; j++ ) {
+ vert = v[ tris[ i ].indexes[ j ] ];
+ vert.texCoords[ 0 ] = uv[ i ].uv[ j ][ 0 ];
+ vert.texCoords[ 1 ] = 1.0f - uv[ i ].uv[ j ][ 1 ];
+
+ for( k = 0; k < verts.Num(); k++ ) {
+ if ( vert.numWeights != verts[ k ].numWeights ) {
+ continue;
+ }
+ if ( vert.startweight != verts[ k ].startweight ) {
+ continue;
+ }
+ if ( !vert.pos.Compare( verts[ k ].pos, SLOP_VERTEX ) ) {
+ continue;
+ }
+ if ( !vert.texCoords.Compare( verts[ k ].texCoords, SLOP_TEXCOORD ) ) {
+ continue;
+ }
+
+ break;
+ }
+
+ if ( k < verts.Num() ) {
+ tris[ i ].indexes[ j ] = k;
+ } else {
+ tris[ i ].indexes[ j ] = verts.Append( vert );
+ }
+ }
+ }
+}
+
+void idExportMesh::Merge( idExportMesh *mesh ) {
+ int i;
+ int numverts;
+ int numtris;
+ int numweights;
+ int numuvs;
+
+ // merge name
+ sprintf( name, "%s, %s", name.c_str(), mesh->name.c_str() );
+
+ // merge verts
+ numverts = verts.Num();
+ verts.SetNum( numverts + mesh->verts.Num() );
+ for( i = 0; i < mesh->verts.Num(); i++ ) {
+ verts[ numverts + i ] = mesh->verts[ i ];
+ verts[ numverts + i ].startweight += weights.Num();
+ }
+
+ // merge triangles
+ numtris = tris.Num();
+ tris.SetNum( numtris + mesh->tris.Num() );
+ for( i = 0; i < mesh->tris.Num(); i++ ) {
+ tris[ numtris + i ].indexes[ 0 ] = mesh->tris[ i ].indexes[ 0 ] + numverts;
+ tris[ numtris + i ].indexes[ 1 ] = mesh->tris[ i ].indexes[ 1 ] + numverts;
+ tris[ numtris + i ].indexes[ 2 ] = mesh->tris[ i ].indexes[ 2 ] + numverts;
+ }
+
+ // merge weights
+ numweights = weights.Num();
+ weights.SetNum( numweights + mesh->weights.Num() );
+ for( i = 0; i < mesh->weights.Num(); i++ ) {
+ weights[ numweights + i ] = mesh->weights[ i ];
+ }
+
+ // merge uvs
+ numuvs = uv.Num();
+ uv .SetNum( numuvs + mesh->uv.Num() );
+ for( i = 0; i < mesh->uv.Num(); i++ ) {
+ uv[ numuvs + i ] = mesh->uv[ i ];
+ }
+}
+
+void idExportMesh::GetBounds( idBounds &bounds ) const {
+ int i;
+ int j;
+ idVec3 pos;
+ const exportWeight_t *weight;
+ const exportVertex_t *vert;
+
+ bounds.Clear();
+
+ weight = weights.Ptr();
+ vert = verts.Ptr();
+ for( i = 0; i < verts.Num(); i++, vert++ ) {
+ pos.Zero();
+ weight = &weights[ vert->startweight ];
+ for( j = 0; j < vert->numWeights; j++, weight++ ) {
+ pos += weight->jointWeight * ( weight->joint->idwm * weight->offset + weight->joint->idt );
+ }
+ bounds.AddPoint( pos );
+ }
+}
+
+/*
+==============================================================================
+
+idExportModel
+
+==============================================================================
+*/
+
+/*
+====================
+idExportModel::idExportModel
+====================
+*/
+ID_INLINE idExportModel::idExportModel() {
+ export_joints = 0;
+ skipjoints = 0;
+ frameRate = 24;
+ numFrames = 0;
+ exportOrigin = NULL;
+}
+
+/*
+====================
+idExportModel::~idExportModel
+====================
+*/
+ID_INLINE idExportModel::~idExportModel() {
+ meshes.DeleteContents( true );
+}
+
+idExportJoint *idExportModel::FindJointReal( const char *name ) {
+ idExportJoint *joint;
+ int i;
+
+ joint = joints.Ptr();
+ for( i = 0; i < joints.Num(); i++, joint++ ) {
+ if ( joint->realname == name ) {
+ return joint;
+ }
+ }
+
+ return NULL;
+}
+
+idExportJoint *idExportModel::FindJoint( const char *name ) {
+ idExportJoint *joint;
+ int i;
+
+ joint = joints.Ptr();
+ for( i = 0; i < joints.Num(); i++, joint++ ) {
+ if ( joint->name == name ) {
+ return joint;
+ }
+ }
+
+ return NULL;
+}
+
+bool idExportModel::WriteMesh( const char *filename, idExportOptions &options ) {
+ int i, j;
+ int numMeshes;
+ idExportMesh *mesh;
+ idExportJoint *joint;
+ idExportJoint *parent;
+ idExportJoint *sibling;
+ FILE *file;
+ const char *parentName;
+ int parentNum;
+ idList jointList;
+
+ file = fopen( filename, "w" );
+ if ( !file ) {
+ return false;
+ }
+
+ for( joint = exportHead.GetNext(); joint != NULL; joint = joint->exportNode.GetNext() ) {
+ jointList.Append( joint );
+ }
+
+ for( i = 0; i < jointList.Num(); i++ ) {
+ joint = jointList[ i ];
+ sibling = joint->exportNode.GetSibling();
+ while( sibling ) {
+ if ( idStr::Cmp( joint->name, sibling->name ) > 0 ) {
+ joint->exportNode.MakeSiblingAfter( sibling->exportNode );
+ sibling = joint->exportNode.GetSibling();
+ } else {
+ sibling = sibling->exportNode.GetSibling();
+ }
+ }
+ }
+
+ jointList.Clear();
+ for( joint = exportHead.GetNext(); joint != NULL; joint = joint->exportNode.GetNext() ) {
+ joint->exportNum = jointList.Append( joint );
+ }
+
+ numMeshes = 0;
+ if ( !options.ignoreMeshes ) {
+ for( i = 0; i < meshes.Num(); i++ ) {
+ if ( meshes[ i ]->keep ) {
+ numMeshes++;
+ }
+ }
+ }
+
+ // write version info
+ WriteFloatString( file, MD5_VERSION_STRING " %d\n", MD5_VERSION );
+ WriteFloatString( file, "commandline \"%s\"\n\n", options.commandLine.c_str() );
+
+ // write joints
+ WriteFloatString( file, "numJoints %d\n", jointList.Num() );
+ WriteFloatString( file, "numMeshes %d\n\n", numMeshes );
+
+ WriteFloatString( file, "joints {\n" );
+ for( i = 0; i < jointList.Num(); i++ ) {
+ joint = jointList[ i ];
+ parent = joint->exportNode.GetParent();
+ if ( parent ) {
+ parentNum = parent->exportNum;
+ parentName = parent->name.c_str();
+ } else {
+ parentNum = -1;
+ parentName = "";
+ }
+
+ idCQuat bindQuat = joint->bindmat.ToQuat().ToCQuat();
+ WriteFloatString( file, "\t\"%s\"\t%d ( %f %f %f ) ( %f %f %f )\t\t// %s\n", joint->name.c_str(), parentNum,
+ joint->bindpos.x, joint->bindpos.y, joint->bindpos.z, bindQuat[ 0 ], bindQuat[ 1 ], bindQuat[ 2 ], parentName );
+ }
+ WriteFloatString( file, "}\n" );
+
+ // write meshes
+ for( i = 0; i < meshes.Num(); i++ ) {
+ mesh = meshes[ i ];
+ if ( !mesh->keep ) {
+ continue;
+ }
+
+ WriteFloatString( file, "\nmesh {\n" );
+ WriteFloatString( file, "\t// meshes: %s\n", mesh->name.c_str() );
+ WriteFloatString( file, "\tshader \"%s\"\n", mesh->shader.c_str() );
+
+ WriteFloatString( file, "\n\tnumverts %d\n", mesh->verts.Num() );
+ for( j = 0; j < mesh->verts.Num(); j++ ) {
+ WriteFloatString( file, "\tvert %d ( %f %f ) %d %d\n", j, mesh->verts[ j ].texCoords[ 0 ], mesh->verts[ j ].texCoords[ 1 ],
+ mesh->verts[ j ].startweight, mesh->verts[ j ].numWeights );
+ }
+
+ WriteFloatString( file, "\n\tnumtris %d\n", mesh->tris.Num() );
+ for( j = 0; j < mesh->tris.Num(); j++ ) {
+ WriteFloatString( file, "\ttri %d %d %d %d\n", j, mesh->tris[ j ].indexes[ 2 ], mesh->tris[ j ].indexes[ 1 ], mesh->tris[ j ].indexes[ 0 ] );
+ }
+
+ WriteFloatString( file, "\n\tnumweights %d\n", mesh->weights.Num() );
+ for( j = 0; j < mesh->weights.Num(); j++ ) {
+ exportWeight_t *weight;
+
+ weight = &mesh->weights[ j ];
+ WriteFloatString( file, "\tweight %d %d %f ( %f %f %f )\n", j,
+ weight->joint->exportNum, weight->jointWeight, weight->offset.x, weight->offset.y, weight->offset.z );
+ }
+
+ WriteFloatString( file, "}\n" );
+ }
+
+ fclose( file );
+
+ return true;
+}
+
+bool idExportModel::WriteAnim( const char *filename, idExportOptions &options ) {
+ int i, j;
+ idExportJoint *joint;
+ idExportJoint *parent;
+ idExportJoint *sibling;
+ jointFrame_t *frame;
+ FILE *file;
+ int numAnimatedComponents;
+ idList jointList;
+
+ file = fopen( filename, "w" );
+ if ( !file ) {
+ return false;
+ }
+
+ for( joint = exportHead.GetNext(); joint != NULL; joint = joint->exportNode.GetNext() ) {
+ jointList.Append( joint );
+ }
+
+ for( i = 0; i < jointList.Num(); i++ ) {
+ joint = jointList[ i ];
+ sibling = joint->exportNode.GetSibling();
+ while( sibling ) {
+ if ( idStr::Cmp( joint->name, sibling->name ) > 0 ) {
+ joint->exportNode.MakeSiblingAfter( sibling->exportNode );
+ sibling = joint->exportNode.GetSibling();
+ } else {
+ sibling = sibling->exportNode.GetSibling();
+ }
+ }
+ }
+
+ jointList.Clear();
+ for( joint = exportHead.GetNext(); joint != NULL; joint = joint->exportNode.GetNext() ) {
+ joint->exportNum = jointList.Append( joint );
+ }
+
+ numAnimatedComponents = 0;
+ for( i = 0; i < jointList.Num(); i++ ) {
+ joint = jointList[ i ];
+ joint->exportNum = i;
+ joint->baseFrame = frames[ 0 ][ joint->index ];
+ joint->animBits = 0;
+ for( j = 1; j < numFrames; j++ ) {
+ frame = &frames[ j ][ joint->index ];
+ if ( fabs( frame->t[ 0 ] - joint->baseFrame.t[ 0 ] ) > options.xyzPrecision ) {
+ joint->animBits |= ANIM_TX;
+ }
+ if ( fabs( frame->t[ 1 ] - joint->baseFrame.t[ 1 ] ) > options.xyzPrecision ) {
+ joint->animBits |= ANIM_TY;
+ }
+ if ( fabs( frame->t[ 2 ] - joint->baseFrame.t[ 2 ] ) > options.xyzPrecision ) {
+ joint->animBits |= ANIM_TZ;
+ }
+ if ( fabs( frame->q[ 0 ] - joint->baseFrame.q[ 0 ] ) > options.quatPrecision ) {
+ joint->animBits |= ANIM_QX;
+ }
+ if ( fabs( frame->q[ 1 ] - joint->baseFrame.q[ 1 ] ) > options.quatPrecision ) {
+ joint->animBits |= ANIM_QY;
+ }
+ if ( fabs( frame->q[ 2 ] - joint->baseFrame.q[ 2 ] ) > options.quatPrecision ) {
+ joint->animBits |= ANIM_QZ;
+ }
+ if ( ( joint->animBits & 63 ) == 63 ) {
+ break;
+ }
+ }
+ if ( joint->animBits ) {
+ joint->firstComponent = numAnimatedComponents;
+ for( j = 0; j < 6; j++ ) {
+ if ( joint->animBits & BIT( j ) ) {
+ numAnimatedComponents++;
+ }
+ }
+ }
+ }
+
+ // write version info
+ WriteFloatString( file, MD5_VERSION_STRING " %d\n", MD5_VERSION );
+ WriteFloatString( file, "commandline \"%s\"\n\n", options.commandLine.c_str() );
+
+ WriteFloatString( file, "numFrames %d\n", numFrames );
+ WriteFloatString( file, "numJoints %d\n", jointList.Num() );
+ WriteFloatString( file, "frameRate %d\n", frameRate );
+ WriteFloatString( file, "numAnimatedComponents %d\n", numAnimatedComponents );
+
+ // write out the hierarchy
+ WriteFloatString( file, "\nhierarchy {\n" );
+ for( i = 0; i < jointList.Num(); i++ ) {
+ joint = jointList[ i ];
+ parent = joint->exportNode.GetParent();
+ if ( parent ) {
+ WriteFloatString( file, "\t\"%s\"\t%d %d %d\t// %s", joint->name.c_str(), parent->exportNum, joint->animBits, joint->firstComponent, parent->name.c_str() );
+ } else {
+ WriteFloatString( file, "\t\"%s\"\t-1 %d %d\t//", joint->name.c_str(), joint->animBits, joint->firstComponent );
+ }
+
+ if ( !joint->animBits ) {
+ WriteFloatString( file, "\n" );
+ } else {
+ WriteFloatString( file, " ( " );
+ for( j = 0; j < 6; j++ ) {
+ if ( joint->animBits & BIT( j ) ) {
+ WriteFloatString( file, "%s ", componentNames[ j ] );
+ }
+ }
+ WriteFloatString( file, ")\n" );
+ }
+ }
+ WriteFloatString( file, "}\n" );
+
+ // write the frame bounds
+ WriteFloatString( file, "\nbounds {\n" );
+ for( i = 0; i < numFrames; i++ ) {
+#if 1 // HUMANHEAD pdm: Allow bounds expansion (shared animations calculate bounds based on a different mesh, so give some slop)
+ idBounds expandedBounds = bounds[i].Expand( options.boundsExpansion );
+ WriteFloatString( file, "\t( %f %f %f ) ( %f %f %f )\n", expandedBounds[ 0 ].x, expandedBounds[ 0 ].y, expandedBounds[ 0 ].z, expandedBounds[ 1 ].x, expandedBounds[ 1 ].y, expandedBounds[ 1 ].z );
+#else // HUMANHEAD END
+ WriteFloatString( file, "\t( %f %f %f ) ( %f %f %f )\n", bounds[ i ][ 0 ].x, bounds[ i ][ 0 ].y, bounds[ i ][ 0 ].z, bounds[ i ][ 1 ].x, bounds[ i ][ 1 ].y, bounds[ i ][ 1 ].z );
+#endif
+ }
+ WriteFloatString( file, "}\n" );
+
+ // write the base frame
+ WriteFloatString( file, "\nbaseframe {\n" );
+ for( i = 0; i < jointList.Num(); i++ ) {
+ joint = jointList[ i ];
+ WriteFloatString( file, "\t( %f %f %f ) ( %f %f %f )\n", joint->baseFrame.t[ 0 ], joint->baseFrame.t[ 1 ], joint->baseFrame.t[ 2 ],
+ joint->baseFrame.q[ 0 ], joint->baseFrame.q[ 1 ], joint->baseFrame.q[ 2 ] );
+ }
+ WriteFloatString( file, "}\n" );
+
+ // write the frames
+ for( i = 0; i < numFrames; i++ ) {
+ WriteFloatString( file, "\nframe %d {\n", i );
+ for( j = 0; j < jointList.Num(); j++ ) {
+ joint = jointList[ j ];
+ frame = &frames[ i ][ joint->index ];
+ if ( joint->animBits ) {
+ WriteFloatString( file, "\t" );
+ if ( joint->animBits & ANIM_TX ) {
+ WriteFloatString( file, " %f", frame->t[ 0 ] );
+ }
+ if ( joint->animBits & ANIM_TY ) {
+ WriteFloatString( file, " %f", frame->t[ 1 ] );
+ }
+ if ( joint->animBits & ANIM_TZ ) {
+ WriteFloatString( file, " %f", frame->t[ 2 ] );
+ }
+ if ( joint->animBits & ANIM_QX ) {
+ WriteFloatString( file, " %f", frame->q[ 0 ] );
+ }
+ if ( joint->animBits & ANIM_QY ) {
+ WriteFloatString( file, " %f", frame->q[ 1 ] );
+ }
+ if ( joint->animBits & ANIM_QZ ) {
+ WriteFloatString( file, " %f", frame->q[ 2 ] );
+ }
+ WriteFloatString( file, "\n" );
+ }
+ }
+ WriteFloatString( file, "}\n" );
+ }
+
+ fclose( file );
+
+ return true;
+}
+
+bool idExportModel::WriteCamera( const char *filename, idExportOptions &options ) {
+ int i;
+ FILE *file;
+
+ file = fopen( filename, "w" );
+ if ( !file ) {
+ return false;
+ }
+
+ // write version info
+ WriteFloatString( file, MD5_VERSION_STRING " %d\n", MD5_VERSION );
+ WriteFloatString( file, "commandline \"%s\"\n\n", options.commandLine.c_str() );
+
+ WriteFloatString( file, "numFrames %d\n", camera.Num() );
+ WriteFloatString( file, "frameRate %d\n", frameRate );
+ WriteFloatString( file, "numCuts %d\n", cameraCuts.Num() );
+
+ // write out the cuts
+ WriteFloatString( file, "\ncuts {\n" );
+ for( i = 0; i < cameraCuts.Num(); i++ ) {
+ WriteFloatString( file, "\t%d\n", cameraCuts[ i ] );
+ }
+ WriteFloatString( file, "}\n" );
+
+ // write out the frames
+ WriteFloatString( file, "\ncamera {\n" );
+ cameraFrame_t *frame = camera.Ptr();
+ for( i = 0; i < camera.Num(); i++, frame++ ) {
+ WriteFloatString( file, "\t( %f %f %f ) ( %f %f %f ) %f\n", frame->t.x, frame->t.y, frame->t.z, frame->q[ 0 ], frame->q[ 1 ], frame->q[ 2 ], frame->fov );
+ }
+ WriteFloatString( file, "}\n" );
+
+ fclose( file );
+
+ return true;
+}
+
+/*
+==============================================================================
+
+Maya
+
+==============================================================================
+*/
+
+/*
+===============
+idMayaExport::~idMayaExport
+
+===============
+*/
+idMayaExport::~idMayaExport() {
+ FreeDagNodes();
+
+ // free up the file in Maya
+ MFileIO::newFile( true );
+}
+
+/*
+===============
+idMayaExport::TimeForFrame
+===============
+*/
+float idMayaExport::TimeForFrame( int num ) const {
+ MTime time;
+
+ // set time unit to 24 frames per second
+ time.setUnit( MTime::kFilm );
+ time.setValue( num );
+ return time.as( MTime::kSeconds );
+}
+
+/*
+===============
+idMayaExport::GetMayaFrameNum
+===============
+*/
+int idMayaExport::GetMayaFrameNum( int num ) const {
+ int frameNum;
+
+ if ( options.cycleStart > options.startframe ) {
+ // in cycles, the last frame is a duplicate of the first frame, so with cycleStart we need to
+ // duplicate one of the interior frames instead and chop off the first frame.
+ frameNum = options.cycleStart + num;
+ if ( frameNum > options.endframe ) {
+ frameNum -= options.endframe - options.startframe;
+ }
+ if ( frameNum < options.startframe ) {
+ frameNum = options.startframe + 1;
+ }
+ } else {
+ frameNum = options.startframe + num;
+ if ( frameNum > options.endframe ) {
+ frameNum -= options.endframe + 1 - options.startframe;
+ }
+ if ( frameNum < options.startframe ) {
+ frameNum = options.startframe;
+ }
+ }
+
+ return frameNum;
+}
+
+/*
+===============
+idMayaExport::SetFrame
+===============
+*/
+void idMayaExport::SetFrame( int num ) {
+ MTime time;
+ int frameNum;
+
+ frameNum = GetMayaFrameNum( num );
+
+ // set time unit to 24 frames per second
+ time.setUnit( MTime::kFilm );
+ time.setValue( frameNum );
+ MGlobal::viewFrame( time );
+}
+
+/*
+===============
+idMayaExport::PruneJoints
+===============
+*/
+void idMayaExport::PruneJoints( idStrList &keepjoints, idStr &prefix ) {
+ int i;
+ int j;
+ idExportMesh *mesh;
+ idExportJoint *joint;
+ idExportJoint *joint2;
+ idExportJoint *parent;
+ int num_weights;
+
+ // if we don't have any joints specified to keep, mark the ones used by the meshes as keep
+ if ( !keepjoints.Num() && !prefix.Length() ) {
+ if ( !model.meshes.Num() || options.ignoreMeshes ) {
+ // export all joints
+ joint = model.joints.Ptr();
+ for( i = 0; i < model.joints.Num(); i++, joint++ ) {
+ joint->keep = true;
+ }
+ } else {
+ for( i = 0; i < model.meshes.Num(); i++, mesh++ ) {
+ mesh = model.meshes[ i ];
+ for( j = 0; j < mesh->weights.Num(); j++ ) {
+ mesh->weights[ j ].joint->keep = true;
+ }
+ }
+ }
+ } else {
+ // mark the joints to keep
+ for( i = 0; i < keepjoints.Num(); i++ ) {
+ joint = model.FindJoint( keepjoints[ i ] );
+ if ( joint ) {
+ joint->keep = true;
+ }
+ }
+
+ // count valid meshes
+ for( i = 0; i < model.meshes.Num(); i++ ) {
+ mesh = model.meshes[ i ];
+ num_weights = 0;
+ for( j = 0; j < mesh->weights.Num(); j++ ) {
+ if ( mesh->weights[ j ].joint->keep ) {
+ num_weights++;
+ } else if ( prefix.Length() && !mesh->weights[ j ].joint->realname.Cmpn( prefix, prefix.Length() ) ) {
+ // keep the joint if it's used by the mesh and it has the right prefix
+ mesh->weights[ j ].joint->keep = true;
+ num_weights++;
+ }
+ }
+
+ if ( num_weights != mesh->weights.Num() ) {
+ mesh->keep = false;
+ }
+ }
+ }
+
+ // find all joints aren't exported and reparent joint's children
+ model.export_joints = 0;
+ joint = model.joints.Ptr();
+ for( i = 0; i < model.joints.Num(); i++, joint++ ) {
+ if ( !joint->keep ) {
+ joint->exportNode.RemoveFromHierarchy();
+ } else {
+ joint->index = model.export_joints;
+ model.export_joints++;
+
+ // make sure we are parented to an exported joint
+ for( parent = joint->exportNode.GetParent(); parent != NULL; parent = parent->exportNode.GetParent() ) {
+ if ( parent->keep ) {
+ break;
+ }
+ }
+
+ if ( parent != NULL ) {
+ joint->exportNode.ParentTo( parent->exportNode );
+ } else {
+ joint->exportNode.ParentTo( model.exportHead );
+ }
+ }
+ }
+
+ // check if we have any duplicate joint names
+ for( joint = model.exportHead.GetNext(); joint != NULL; joint = joint->exportNode.GetNext() ) {
+ if ( !joint->keep ) {
+ MayaError( "Non-kept joint in export tree ('%s')", joint->name.c_str() );
+ }
+
+ for( joint2 = model.exportHead.GetNext(); joint2 != NULL; joint2 = joint2->exportNode.GetNext() ) {
+ if ( ( joint2 != joint ) && ( joint2->name == joint->name ) ) {
+ MayaError( "Two joints found with the same name ('%s')", joint->name.c_str() );
+ }
+ }
+ }
+}
+
+/*
+===============
+idMayaExport::FreeDagNodes
+===============
+*/
+void idMayaExport::FreeDagNodes( void ) {
+ int i;
+
+ for( i = 0; i < model.joints.Num(); i++ ) {
+ delete model.joints[ i ].dagnode;
+ model.joints[ i ].dagnode = NULL;
+ }
+}
+
+/*
+===============
+idMayaExport::GetBindPose
+===============
+*/
+void idMayaExport::GetBindPose( MObject &jointNode, idExportJoint *joint, float scale ) {
+ MStatus status;
+ MFnDependencyNode fnJoint( jointNode );
+ MObject aBindPose = fnJoint.attribute( "bindPose", &status );
+
+ joint->bindpos = vec3_zero;
+ joint->bindmat = mat3_default;
+
+ if ( MS::kSuccess == status ) {
+ unsigned ii;
+ unsigned jointIndex;
+ unsigned connLength;
+ MPlugArray connPlugs;
+ MPlug pBindPose( jointNode, aBindPose );
+
+ pBindPose.connectedTo( connPlugs, false, true );
+ connLength = connPlugs.length();
+ for( ii = 0; ii < connLength; ++ii ) {
+ if ( connPlugs[ ii ].node().apiType() == MFn::kDagPose ) {
+ MObject aMember = connPlugs[ ii ].attribute();
+ MFnAttribute fnAttr( aMember );
+
+ if ( fnAttr.name() == "worldMatrix" ) {
+ jointIndex = connPlugs[ ii ].logicalIndex();
+
+ MFnDependencyNode nDagPose( connPlugs[ ii ].node() );
+
+ // construct plugs for this joint's world matrix
+ MObject aWorldMatrix = nDagPose.attribute( "worldMatrix" );
+ MPlug pWorldMatrix( connPlugs[ ii ].node(), aWorldMatrix );
+
+ pWorldMatrix.selectAncestorLogicalIndex( jointIndex, aWorldMatrix );
+
+ // get the world matrix data
+ MObject worldMatrix;
+ MStatus status = pWorldMatrix.getValue( worldMatrix );
+ if ( MS::kSuccess != status ) {
+ // Problem retrieving world matrix
+ return;
+ }
+
+ MFnMatrixData dMatrix( worldMatrix );
+ MMatrix wMatrix = dMatrix.matrix( &status );
+
+ joint->bindmat = ConvertToIdSpace( idMat( wMatrix ) );
+ joint->bindpos = ConvertToIdSpace( idVec( wMatrix ) ) * scale;
+ if ( !options.ignoreScale ) {
+ joint->bindpos *= joint->scale;
+ } else {
+ joint->bindmat[ 0 ].Normalize();
+ joint->bindmat[ 1 ].Normalize();
+ joint->bindmat[ 2 ].Normalize();
+ }
+
+ return;
+ }
+ }
+ }
+ }
+}
+
+/*
+===============
+idMayaExport::GetLocalTransform
+===============
+*/
+void idMayaExport::GetLocalTransform( idExportJoint *joint, idVec3 &pos, idMat3 &mat ) {
+ MStatus status;
+ MDagPath dagPath;
+
+ pos.Zero();
+ mat.Identity();
+
+ if ( !joint->dagnode ) {
+ return;
+ }
+
+ status = joint->dagnode->getPath( dagPath );
+ if ( !status ) {
+ return;
+ }
+
+ MObject transformNode = dagPath.transform( &status );
+ if ( !status && ( status.statusCode () == MStatus::kInvalidParameter ) ) {
+ return;
+ }
+
+ MFnDagNode transform( transformNode, &status );
+ if ( !status ) {
+ return;
+ }
+
+ pos = idVec( transform.transformationMatrix() );
+ mat = idMat( transform.transformationMatrix() );
+}
+
+/*
+===============
+idMayaExport::GetWorldTransform
+===============
+*/
+void idMayaExport::GetWorldTransform( idExportJoint *joint, idVec3 &pos, idMat3 &mat, float scale ) {
+ idExportJoint *parent;
+
+ GetLocalTransform( joint, pos, mat );
+ mat.OrthoNormalizeSelf();
+ pos *= scale;
+
+ parent = joint->mayaNode.GetParent();
+ if ( parent ) {
+ idVec3 parentpos;
+ idMat3 parentmat;
+
+ GetWorldTransform( parent, parentpos, parentmat, scale );
+
+ pos = parentpos + ( parentmat * ( pos * parent->scale ) );
+ mat = mat * parentmat;
+ }
+}
+
+/*
+===============
+idMayaExport::CreateJoints
+===============
+*/
+void idMayaExport::CreateJoints( float scale ) {
+ int i;
+ int j;
+ idExportJoint *joint;
+ idExportJoint *parent;
+ MStatus status;
+ MDagPath dagPath;
+ MFnDagNode *parentNode;
+
+ SetFrame( 0 );
+
+ // create an initial list with all of the transformable nodes in the scene
+ MItDag dagIterator( MItDag::kDepthFirst, MFn::kTransform, &status );
+ for ( ; !dagIterator.isDone(); dagIterator.next() ) {
+ status = dagIterator.getPath( dagPath );
+ if ( !status ) {
+ MayaError( "CreateJoints: MItDag::getPath failed (%s)", status.errorString().asChar() );
+ continue;
+ }
+
+ joint = &model.joints.Alloc();
+ joint->index = model.joints.Num() - 1;
+ joint->dagnode = new MFnDagNode( dagPath, &status );
+ if ( !status ) {
+ MayaError( "CreateJoints: MFnDagNode constructor failed (%s)", status.errorString().asChar() );
+ continue;
+ }
+
+ joint->name = joint->dagnode->name().asChar();
+ joint->realname = joint->name;
+ }
+
+ // allocate an extra joint in case we need to add an origin later
+ model.exportOrigin = &model.joints.Alloc();
+ model.exportOrigin->index = model.joints.Num() - 1;
+
+ // create scene hierarchy
+ joint = model.joints.Ptr();
+ for( i = 0; i < model.joints.Num(); i++, joint++ ) {
+ if ( !joint->dagnode ) {
+ continue;
+ }
+ joint->mayaNode.ParentTo( model.mayaHead );
+ joint->exportNode.ParentTo( model.exportHead );
+
+ parentNode = GetParent( joint->dagnode );
+ if ( parentNode ) {
+ // find the parent joint in our jointlist
+ for( j = 0; j < model.joints.Num(); j++ ) {
+ if ( !model.joints[ j ].dagnode ) {
+ continue;
+ }
+ if ( model.joints[ j ].dagnode->name() == parentNode->name() ) {
+ joint->mayaNode.ParentTo( model.joints[ j ].mayaNode );
+ joint->exportNode.ParentTo( model.joints[ j ].exportNode );
+ break;
+ }
+ }
+
+ delete parentNode;
+ }
+
+ // create long name
+ parent = joint->mayaNode.GetParent();
+ if ( parent ) {
+ joint->longname = parent->longname + "/" + joint->name;
+ } else {
+ joint->longname = joint->name;
+ }
+
+ // get the joint's scale
+ GetLocalTransform( &model.joints[ i ], joint->t, joint->wm );
+ joint->scale = joint->wm[ 0 ].Length();
+
+ if ( parent ) {
+ joint->scale *= parent->scale;
+ if ( joint->scale != 0 ) {
+ joint->invscale = 1.0f / joint->scale;
+ } else {
+ joint->invscale = 0;
+ }
+ }
+
+ joint->dagnode->getPath( dagPath );
+ GetBindPose( dagPath.node( &status ), joint, scale );
+ }
+}
+
+/*
+===============
+idMayaExport::RenameJoints
+===============
+*/
+void idMayaExport::RenameJoints( idList &renamejoints, idStr &prefix ) {
+ int i;
+ idExportJoint *joint;
+
+ // rename joints that match the prefix
+ if ( prefix.Length() ) {
+ joint = model.joints.Ptr();
+ for( i = 0; i < model.joints.Num(); i++, joint++ ) {
+ if ( !joint->name.Cmpn( prefix, prefix.Length() ) ) {
+ // remove the prefix from the name
+ joint->name = joint->name.Right( joint->name.Length() - prefix.Length() );
+ }
+ }
+ }
+
+ // rename joints if necessary
+ for( i = 0; i < renamejoints.Num(); i++ ) {
+ joint = model.FindJoint( renamejoints[ i ].from );
+ if ( joint ) {
+ joint->name = renamejoints[ i ].to;
+ }
+ }
+}
+
+/*
+===============
+idMayaExport::RemapParents
+===============
+*/
+bool idMayaExport::RemapParents( idList &remapjoints ) {
+ int i;
+ idExportJoint *joint;
+ idExportJoint *parent;
+ idExportJoint *origin;
+ idExportJoint *sibling;
+
+ for( i = 0; i < remapjoints.Num(); i++ ) {
+ // find joint to reparent
+ joint = model.FindJoint( remapjoints[ i ].from );
+ if ( !joint ) {
+ // couldn't find joint, fail
+ MayaError( "Couldn't find joint '%s' to reparent\n", remapjoints[ i ].from.c_str() );
+ }
+
+ // find new parent joint
+ parent = model.FindJoint( remapjoints[ i ].to );
+ if ( !parent ) {
+ // couldn't find parent, fail
+ MayaError( "Couldn't find joint '%s' to be new parent for '%s'\n", remapjoints[ i ].to.c_str(), remapjoints[ i ].from.c_str() );
+ }
+
+ if ( parent->exportNode.ParentedBy( joint->exportNode ) ) {
+ MayaError( "Joint '%s' is a child of joint '%s' and can't become the parent.", joint->name.c_str(), parent->name.c_str() );
+ }
+ joint->exportNode.ParentTo( parent->exportNode );
+ }
+
+ // if we have an origin, make it the first node in the export list, otherwise add one
+ origin = model.FindJoint( "origin" );
+ if ( !origin ) {
+ origin = model.exportOrigin;
+ origin->dagnode = NULL;
+ origin->name = "origin";
+ origin->realname = "origin";
+ origin->bindmat.Identity();
+ origin->bindpos.Zero();
+ }
+
+ origin->exportNode.ParentTo( model.exportHead );
+
+ // force the joint to be kept
+ origin->keep = true;
+
+ // make all root joints children of the origin joint
+ joint = model.exportHead.GetChild();
+ while( joint ) {
+ sibling = joint->exportNode.GetSibling();
+ if ( joint != origin ) {
+ joint->exportNode.ParentTo( origin->exportNode );
+ }
+ joint = sibling;
+ }
+
+ return true;
+}
+
+/*
+===============
+idMayaExport::FindShader
+
+Find the shading node for the given shading group set node.
+===============
+*/
+MObject idMayaExport::FindShader( MObject& setNode ) {
+ MStatus status;
+ MFnDependencyNode fnNode( setNode );
+ MPlug shaderPlug;
+
+ shaderPlug = fnNode.findPlug( "surfaceShader" );
+ if ( !shaderPlug.isNull() ) {
+ MPlugArray connectedPlugs;
+ bool asSrc = false;
+ bool asDst = true;
+ shaderPlug.connectedTo( connectedPlugs, asDst, asSrc, &status );
+
+ if ( connectedPlugs.length() != 1 ) {
+ MayaError( "FindShader: Error getting shader (%s)", status.errorString().asChar() );
+ } else {
+ return connectedPlugs[ 0 ].node();
+ }
+ }
+
+ return MObject::kNullObj;
+}
+
+/*
+===============
+idMayaExport::GetTextureForMesh
+
+Find the texture files that apply to the color of each polygon of
+a selected shape if the shape has its polygons organized into sets.
+===============
+*/
+void idMayaExport::GetTextureForMesh( idExportMesh *mesh, MFnDagNode &dagNode ) {
+ MStatus status;
+ MDagPath path;
+ int i;
+ int instanceNum;
+
+ status = dagNode.getPath( path );
+ if ( !status ) {
+ return;
+ }
+
+ path.extendToShape();
+
+ // If the shape is instanced then we need to determine which
+ // instance this path refers to.
+ //
+ instanceNum = 0;
+ if ( path.isInstanced() ) {
+ instanceNum = path.instanceNumber();
+ }
+
+ // Get a list of all sets pertaining to the selected shape and the
+ // members of those sets.
+ //
+ MFnMesh fnMesh( path );
+ MObjectArray sets;
+ MObjectArray comps;
+ status = fnMesh.getConnectedSetsAndMembers( instanceNum, sets, comps, true );
+ if ( !status ) {
+ MayaError( "GetTextureForMesh: MFnMesh::getConnectedSetsAndMembers failed (%s)", status.errorString().asChar() );
+ }
+
+ // Loop through all the sets. If the set is a polygonal set, find the
+ // shader attached to the and print out the texture file name for the
+ // set along with the polygons in the set.
+ //
+ for ( i = 0; i < ( int )sets.length(); i++ ) {
+ MObject set = sets[i];
+ MObject comp = comps[i];
+
+ MFnSet fnSet( set, &status );
+ if ( status == MS::kFailure ) {
+ MayaError( "GetTextureForMesh: MFnSet constructor failed (%s)", status.errorString().asChar() );
+ continue;
+ }
+
+ // Make sure the set is a polygonal set. If not, continue.
+ MItMeshPolygon piter(path, comp, &status);
+ if (status == MS::kFailure) {
+ continue;
+ }
+
+ // Find the texture that is applied to this set. First, get the
+ // shading node connected to the set. Then, if there is an input
+ // attribute called "color", search upstream from it for a texture
+ // file node.
+ //
+ MObject shaderNode = FindShader( set );
+ if ( shaderNode == MObject::kNullObj ) {
+ continue;
+ }
+
+ MPlug colorPlug = MFnDependencyNode(shaderNode).findPlug("color", &status);
+ if ( status == MS::kFailure ) {
+ continue;
+ }
+
+ MItDependencyGraph dgIt(colorPlug, MFn::kFileTexture,
+ MItDependencyGraph::kUpstream,
+ MItDependencyGraph::kBreadthFirst,
+ MItDependencyGraph::kNodeLevel,
+ &status);
+
+ if ( status == MS::kFailure ) {
+ continue;
+ }
+
+ dgIt.disablePruningOnFilter();
+
+ // If no texture file node was found, just continue.
+ //
+ if ( dgIt.isDone() ) {
+ continue;
+ }
+
+ // Print out the texture node name and texture file that it references.
+ //
+ MObject textureNode = dgIt.thisNode();
+ MPlug filenamePlug = MFnDependencyNode( textureNode ).findPlug( "fileTextureName" );
+ MString textureName;
+ filenamePlug.getValue( textureName );
+
+ // remove the OS path and save it in the mesh
+ OSPathToRelativePath( textureName.asChar(), mesh->shader, options.game );
+ mesh->shader.StripFileExtension();
+
+ return;
+ }
+}
+
+/*
+===============
+idMayaExport::CopyMesh
+===============
+*/
+idExportMesh *idMayaExport::CopyMesh( MFnSkinCluster &skinCluster, float scale ) {
+ MStatus status;
+ MObjectArray objarray;
+ MObjectArray outputarray;
+ int nGeom;
+ int i, j, k;
+ idExportMesh *mesh;
+ float uv_u, uv_v;
+ idStr name, altname;
+ int pos;
+
+ status = skinCluster.getInputGeometry( objarray );
+ if ( !status ) {
+ MayaError( "CopyMesh: Error getting input geometry (%s)", status.errorString().asChar() );
+ return NULL;
+ }
+
+ nGeom = objarray.length();
+ for( i = 0; i < nGeom; i++ ) {
+ MFnDagNode dagNode( objarray[ i ], &status );
+ if ( !status ) {
+ common->Printf( "CopyMesh: MFnDagNode Constructor failed (%s)", status.errorString().asChar() );
+ continue;
+ }
+
+ MFnMesh fnmesh( objarray[ i ], &status );
+ if ( !status ) {
+ // object isn't an MFnMesh
+ continue;
+ }
+
+ status = skinCluster.getOutputGeometry( outputarray );
+ if ( !status ) {
+ common->Printf( "CopyMesh: Error getting output geometry (%s)", status.errorString().asChar() );
+ return NULL;
+ }
+
+ if ( outputarray.length() < 1 ) {
+ return NULL;
+ }
+
+ name = fnmesh.name().asChar();
+ if ( options.prefix.Length() ) {
+ if ( !name.Cmpn( options.prefix, options.prefix.Length() ) ) {
+ // remove the prefix from the name
+ name = name.Right( name.Length() - options.prefix.Length() );
+ } else {
+ // name doesn't match prefix, so don't use this mesh
+ //return NULL;
+ }
+ }
+
+ pos = name.Find( "ShapeOrig" );
+ if ( pos >= 0 ) {
+ name.CapLength( pos );
+ }
+
+ MFnDagNode dagNode2( outputarray[ 0 ], &status );
+ if ( !status ) {
+ common->Printf( "CopyMesh: MFnDagNode Constructor failed (%s)", status.errorString().asChar() );
+ continue;
+ }
+
+ altname = name;
+ MObject parent = fnmesh.parent( 0, &status );
+ if ( status ) {
+ MFnDagNode parentNode( parent, &status );
+ if ( status ) {
+ altname = parentNode.name().asChar();
+ }
+ }
+
+ name.StripLeadingOnce( options.prefix );
+ altname.StripLeadingOnce( options.prefix );
+ if ( options.keepmeshes.Num() ) {
+ if ( !options.keepmeshes.Find( name ) && !options.keepmeshes.Find( altname ) ) {
+ if ( altname != name ) {
+ common->Printf( "Skipping mesh '%s' ('%s')\n", name.c_str(), altname.c_str() );
+ } else {
+ common->Printf( "Skipping mesh '%s'\n", name.c_str() );
+ }
+ return NULL;
+ }
+ }
+
+ if ( options.skipmeshes.Find( name ) || options.skipmeshes.Find( altname ) ) {
+ common->Printf( "Skipping mesh '%s' ('%s')\n", name.c_str(), altname.c_str() );
+ return NULL;
+ }
+
+ mesh = new idExportMesh();
+ model.meshes.Append( mesh );
+
+ if ( altname.Length() ) {
+ mesh->name = altname;
+ } else {
+ mesh->name = name;
+ }
+ GetTextureForMesh( mesh, dagNode2 );
+
+ int v = fnmesh.numVertices( &status );
+ mesh->verts.SetNum( v );
+
+ MFloatPointArray vertexArray;
+
+ fnmesh.getPoints( vertexArray, MSpace::kPreTransform );
+
+ for( j = 0; j < v; j++ ) {
+ memset( &mesh->verts[ j ], 0, sizeof( mesh->verts[ j ] ) );
+ mesh->verts[ j ].pos = ConvertToIdSpace( idVec( vertexArray[ j ] ) ) * scale;
+ }
+
+ MIntArray vertexList;
+ int p;
+
+ p = fnmesh.numPolygons( &status );
+ mesh->tris.SetNum( p );
+ mesh->uv.SetNum( p );
+
+ MString setName;
+
+ status = fnmesh.getCurrentUVSetName( setName );
+ if ( !status ) {
+ MayaError( "CopyMesh: MFnMesh::getCurrentUVSetName failed (%s)", status.errorString().asChar() );
+ }
+
+ for( j = 0; j < p; j++ ) {
+ fnmesh.getPolygonVertices( j, vertexList );
+ if ( vertexList.length() != 3 ) {
+ MayaError( "CopyMesh: Too many vertices on a face (%d)\n", vertexList.length() );
+ }
+
+ for( k = 0; k < 3; k++ ) {
+ mesh->tris[ j ].indexes[ k ] = vertexList[ k ];
+
+ status = fnmesh.getPolygonUV( j, k, uv_u, uv_v, &setName );
+ if ( !status ) {
+ MayaError( "CopyMesh: MFnMesh::getPolygonUV failed (%s)", status.errorString().asChar() );
+ }
+
+ mesh->uv[ j ].uv[ k ][ 0 ] = uv_u;
+ mesh->uv[ j ].uv[ k ][ 1 ] = uv_v;
+ }
+ }
+
+ return mesh;
+ }
+
+ return NULL;
+}
+
+/*
+===============
+idMayaExport::CreateMesh
+===============
+*/
+void idMayaExport::CreateMesh( float scale ) {
+ size_t count;
+ idExportMesh *mesh;
+ MStatus status;
+ exportWeight_t weight;
+ unsigned int nGeoms;
+
+ // Iterate through graph and search for skinCluster nodes
+ MItDependencyNodes iter( MFn::kSkinClusterFilter );
+ count = 0;
+ for ( ; !iter.isDone(); iter.next() ) {
+ MObject object = iter.item();
+
+ count++;
+
+ // For each skinCluster node, get the list of influence objects
+ MFnSkinCluster skinCluster( object, &status );
+ if ( !status ) {
+ MayaError( "%s: Error getting skin cluster (%s)", object.apiTypeStr(), status.errorString().asChar() );
+ }
+
+ mesh = CopyMesh( skinCluster, scale );
+ if ( !mesh ) {
+ continue;
+ }
+
+ MDagPathArray infs;
+ unsigned int nInfs = skinCluster.influenceObjects(infs, &status);
+ if ( !status ) {
+ MayaError( "Mesh '%s': Error getting influence objects (%s)", mesh->name.c_str(), status.errorString().asChar() );
+ }
+
+ if ( 0 == nInfs ) {
+ MayaError( "Mesh '%s': No influence objects found", mesh->name.c_str() );
+ }
+
+ // loop through the geometries affected by this cluster
+ nGeoms = skinCluster.numOutputConnections();
+ for (size_t ii = 0; ii < nGeoms; ++ii) {
+ unsigned int index = skinCluster.indexForOutputConnection(ii,&status);
+
+ if ( !status ) {
+ MayaError( "Mesh '%s': Error getting geometry index (%s)", mesh->name.c_str(), status.errorString().asChar() );
+ }
+
+ // get the dag path of the ii'th geometry
+ MDagPath skinPath;
+ status = skinCluster.getPathAtIndex(index,skinPath);
+ if ( !status ) {
+ MayaError( "Mesh '%s': Error getting geometry path (%s)", mesh->name.c_str(), status.errorString().asChar() );
+ }
+
+ // iterate through the components of this geometry
+ MItGeometry gIter( skinPath );
+
+ // print out the influence objects
+ idList joints;
+ idExportJoint *joint;
+ exportVertex_t *vert;
+
+ joints.Resize( nInfs );
+ for (size_t kk = 0; kk < nInfs; ++kk) {
+ const char *c;
+ MString s;
+
+ s = infs[kk].partialPathName();
+ c = s.asChar();
+ joint = model.FindJointReal( c );
+ if ( !joint ) {
+ MayaError( "Mesh '%s': joint %s not found", mesh->name.c_str(), c );
+ }
+
+ joints.Append( joint );
+ }
+
+ for ( /* nothing */ ; !gIter.isDone(); gIter.next() ) {
+ MObject comp = gIter.component( &status );
+ if ( !status ) {
+ MayaError( "Mesh '%s': Error getting component (%s)", mesh->name.c_str(), status.errorString().asChar() );
+ }
+
+ // Get the weights for this vertex (one per influence object)
+ MFloatArray wts;
+ unsigned infCount;
+ status = skinCluster.getWeights(skinPath,comp,wts,infCount);
+ if ( !status ) {
+ MayaError( "Mesh '%s': Error getting weights (%s)", mesh->name.c_str(), status.errorString().asChar() );
+ }
+ if (0 == infCount) {
+ MayaError( "Mesh '%s': Error: 0 influence objects.", mesh->name.c_str() );
+ }
+
+ int num = gIter.index();
+ vert = &mesh->verts[ num ];
+ vert->startweight = mesh->weights.Num();
+
+ float totalweight = 0.0f;
+
+ // copy the weight data for this vertex
+ int numNonZeroWeights = 0;
+ int jj;
+ for ( jj = 0; jj < (int)infCount ; ++jj ) {
+ float w = ( float )wts[ jj ];
+ if ( w > 0.0f ) {
+ numNonZeroWeights++;
+ }
+ if ( w > options.jointThreshold ) {
+ weight.joint = joints[ jj ];
+ weight.jointWeight = wts[ jj ];
+
+ if ( !options.ignoreScale ) {
+ weight.joint->bindmat.ProjectVector( vert->pos - ( weight.joint->bindpos * weight.joint->invscale ), weight.offset );
+ weight.offset *= weight.joint->scale;
+ } else {
+ weight.joint->bindmat.ProjectVector( vert->pos - weight.joint->bindpos, weight.offset );
+ }
+ mesh->weights.Append( weight );
+ totalweight += weight.jointWeight;
+ }
+ }
+
+ vert->numWeights = mesh->weights.Num() - vert->startweight;
+ if ( !vert->numWeights ) {
+ if ( numNonZeroWeights ) {
+ MayaError( "Error on mesh '%s': Vertex %d doesn't have any joint weights exceeding jointThreshold (%f).", mesh->name.c_str(), num, options.jointThreshold );
+ } else {
+ MayaError( "Error on mesh '%s': Vertex %d doesn't have any joint weights.", mesh->name.c_str(), num );
+ }
+ } else if ( !totalweight ) {
+ MayaError( "Error on mesh '%s': Combined weight of 0 on vertex %d.", mesh->name.c_str(), num );
+ }
+ //if ( numNonZeroWeights ) {
+ // common->Printf( "Mesh '%s': skipped %d out of %d weights on vertex %d\n", mesh->name.c_str(), numNonZeroWeights, numNonZeroWeights + vert->numWeights, num );
+ //}
+
+ // normalize the joint weights
+ for( jj = 0; jj < vert->numWeights; jj++ ) {
+ mesh->weights[ vert->startweight + jj ].jointWeight /= totalweight;
+ }
+ }
+ break;
+ }
+ }
+
+ if ( !count && !options.ignoreMeshes ) {
+ MayaError( "CreateMesh: No skinClusters found in this scene.\n" );
+ }
+}
+
+/*
+===============
+idMayaExport::CombineMeshes
+
+combine surfaces with the same shader.
+===============
+*/
+void idMayaExport::CombineMeshes( void ) {
+ int i, j;
+ int count;
+ idExportMesh *mesh;
+ idExportMesh *combine;
+ idList oldmeshes;
+
+ oldmeshes = model.meshes;
+ model.meshes.Clear();
+
+ count = 0;
+ for( i = 0; i < oldmeshes.Num(); i++ ) {
+ mesh = oldmeshes[ i ];
+ if ( !mesh->keep ) {
+ delete mesh;
+ continue;
+ }
+
+ combine = NULL;
+ for( j = 0; j < model.meshes.Num(); j++ ) {
+ if ( model.meshes[ j ]->shader == mesh->shader ) {
+ combine = model.meshes[ j ];
+ break;
+ }
+ }
+
+ if ( combine ) {
+ combine->Merge( mesh );
+ delete mesh;
+ count++;
+ } else {
+ model.meshes.Append( mesh );
+ }
+ }
+
+ // share verts
+ for( i = 0; i < model.meshes.Num(); i++ ) {
+ model.meshes[ i ]->ShareVerts();
+ }
+
+ common->Printf( "Merged %d meshes\n", count );
+}
+
+/*
+===============
+idMayaExport::GetAlignment
+===============
+*/
+void idMayaExport::GetAlignment( idStr &alignName, idMat3 &align, float rotate, int startframe ) {
+ idVec3 pos;
+ idExportJoint *joint;
+ idAngles ang( 0, rotate, 0 );
+ idMat3 mat;
+
+ align.Identity();
+
+ if ( alignName.Length() ) {
+ SetFrame( 0 );
+
+ joint = model.FindJoint( alignName );
+ if ( !joint ) {
+ MayaError( "could not find joint '%s' to align model to.\n", alignName.c_str() );
+ }
+
+ // found it
+ GetWorldTransform( joint, pos, mat, 1.0f );
+ align[ 0 ][ 0 ] = mat[ 2 ][ 0 ];
+ align[ 0 ][ 1 ] = -mat[ 2 ][ 2 ];
+ align[ 0 ][ 2 ] = mat[ 2 ][ 1 ];
+
+ align[ 1 ][ 0 ] = mat[ 0 ][ 0 ];
+ align[ 1 ][ 1 ] = -mat[ 0 ][ 2 ];
+ align[ 1 ][ 2 ] = mat[ 0 ][ 1 ];
+
+ align[ 2 ][ 0 ] = mat[ 1 ][ 0 ];
+ align[ 2 ][ 1 ] = -mat[ 1 ][ 2 ];
+ align[ 2 ][ 2 ] = mat[ 1 ][ 1 ];
+
+ if ( rotate ) {
+ align *= ang.ToMat3();
+ }
+ } else if ( rotate ) {
+ align = ang.ToMat3();
+ }
+
+ align.TransposeSelf();
+}
+
+/*
+===============
+idMayaExport::GetObjectType
+
+return the type of the object
+===============
+*/
+const char *idMayaExport::GetObjectType( MObject object ) {
+ if( object.isNull() ) {
+ return "(Null)";
+ }
+
+ MStatus stat;
+ MFnDependencyNode dgNode;
+ MString typeName;
+
+ stat = dgNode.setObject( object );
+ typeName = dgNode.typeName( &stat );
+ if( MS::kSuccess != stat ) {
+ // can not get the type name of this object
+ return "(Unknown)";
+ }
+
+ return typeName.asChar();
+}
+
+/*
+===============
+idMayaExport::GetCameraFov
+===============
+*/
+float idMayaExport::GetCameraFov( idExportJoint *joint ) {
+ int childCount;
+ int j;
+ double horiz;
+ double focal;
+ MStatus status;
+ const char *n1, *n2;
+ MFnDagNode *dagnode;
+ float fov;
+
+ dagnode = joint->dagnode;
+
+ MObject cameraNode = dagnode->object();
+ childCount = dagnode->childCount();
+
+ fov = 90.0f;
+ for( j = 0; j < childCount; j++ ) {
+ MObject childNode = dagnode->child( j );
+
+ n1 = GetObjectType( cameraNode );
+ n2 = GetObjectType( childNode );
+ if ( ( !strcmp( "transform", n1 ) ) && ( !strcmp( "camera", n2 ) ) ) {
+ MFnCamera camera( childNode );
+ focal = camera.focalLength();
+ horiz = camera.horizontalFilmAperture();
+ fov = RAD2DEG( 2 * atan( ( horiz * 0.5 ) / ( focal / 25.4 ) ) );
+ break;
+ }
+ }
+
+ return fov;
+}
+
+/*
+===============
+idMayaExport::GetCameraFrame
+===============
+*/
+void idMayaExport::GetCameraFrame( idExportJoint *camera, idMat3 &align, cameraFrame_t *cam ) {
+ idMat3 mat;
+ idMat3 axis;
+ idVec3 pos;
+
+ // get the worldspace positions of the joint
+ GetWorldTransform( camera, pos, axis, 1.0f );
+
+ // convert to id coordinates
+ cam->t = ConvertToIdSpace( pos ) * align;
+
+ // correct the orientation for the game
+ axis = ConvertToIdSpace( axis ) * align;
+ mat[ 0 ] = -axis[ 2 ];
+ mat[ 1 ] = -axis[ 0 ];
+ mat[ 2 ] = axis[ 1 ];
+ cam->q = mat.ToQuat().ToCQuat();
+
+ // get it's fov
+ cam->fov = GetCameraFov( camera );
+}
+
+/*
+===============
+idMayaExport::CreateCameraAnim
+===============
+*/
+void idMayaExport::CreateCameraAnim( idMat3 &align ) {
+ float start, end;
+ MDagPath dagPath;
+ int frameNum;
+ short v;
+ MStatus status;
+ cameraFrame_t cam;
+ idExportJoint *refCam;
+ idExportJoint *camJoint;
+ idStr currentCam;
+ idStr newCam;
+ MPlug plug;
+ MFnEnumAttribute cameraAttribute;
+
+ start = TimeForFrame( options.startframe );
+ end = TimeForFrame( options.endframe );
+
+#if 0
+ options.framerate = 60;
+ model.numFrames = ( int )( ( end - start ) * ( float )options.framerate ) + 1;
+ model.frameRate = options.framerate;
+#else
+ model.numFrames = options.endframe + 1 - options.startframe;
+ model.frameRate = options.framerate;
+#endif
+
+ common->Printf( "start frame = %d\n end frame = %d\n", options.startframe, options.endframe );
+ common->Printf( " start time = %f\n end time = %f\n total time = %f\n", start, end, end - start );
+
+ if ( start > end ) {
+ MayaError( "Start frame is greater than end frame." );
+ }
+
+ refCam = model.FindJoint( "refcam" );
+ if ( refCam == NULL ) {
+ currentCam = MAYA_DEFAULT_CAMERA;
+ } else {
+ MObject cameraNode = refCam->dagnode->object();
+ MFnDependencyNode cameraDG( cameraNode, &status );
+ if( MS::kSuccess != status ) {
+ MayaError( "Can't find 'refcam' dependency node." );
+ return;
+ }
+
+ MObject attr = cameraDG.attribute( MString( "Camera" ), &status );
+ if( MS::kSuccess != status ) {
+ MayaError( "Can't find 'Camera' attribute on 'refcam'." );
+ return;
+ }
+
+ plug = MPlug( cameraNode, attr );
+ status = cameraAttribute.setObject( attr );
+ if( MS::kSuccess != status ) {
+ MayaError( "Bad 'Camera' attribute on 'refcam'." );
+ return;
+ }
+
+ model.camera.Clear();
+ model.cameraCuts.Clear();
+
+ SetFrame( 0 );
+ status = plug.getValue( v );
+ currentCam = cameraAttribute.fieldName( v, &status ).asChar();
+ if( MS::kSuccess != status ) {
+ MayaError( "Error getting camera name on frame %d", GetMayaFrameNum( 0 ) );
+ }
+ }
+
+ camJoint = model.FindJoint( currentCam );
+ if ( !camJoint ) {
+ MayaError( "Couldn't find camera '%s'", currentCam.c_str() );
+ }
+
+ for( frameNum = 0; frameNum < model.numFrames; frameNum++ ) {
+ common->Printf( "\rFrame %d/%d...", options.startframe + frameNum, options.endframe );
+
+#if 0
+ MTime time;
+ time.setUnit( MTime::kSeconds );
+ time.setValue( start + ( ( float )frameNum / ( float )options.framerate ) );
+ MGlobal::viewFrame( time );
+#else
+ SetFrame( frameNum );
+#endif
+
+ // get the position for this frame
+ GetCameraFrame( camJoint, align, &model.camera.Alloc() );
+
+ if ( refCam != NULL ) {
+ status = plug.getValue( v );
+ newCam = cameraAttribute.fieldName( v, &status ).asChar();
+ if( MS::kSuccess != status ) {
+ MayaError( "Error getting camera name on frame %d", GetMayaFrameNum( frameNum ) );
+ }
+
+ if ( newCam != currentCam ) {
+ // place a cut at our last frame
+ model.cameraCuts.Append( model.camera.Num() - 1 );
+
+ currentCam = newCam;
+ camJoint = model.FindJoint( currentCam );
+ if ( !camJoint ) {
+ MayaError( "Couldn't find camera '%s'", currentCam.c_str() );
+ }
+
+ // get the position for this frame
+ GetCameraFrame( camJoint, align, &model.camera.Alloc() );
+ }
+ }
+ }
+
+ common->Printf( "\n" );
+}
+
+/*
+===============
+idMayaExport::GetDefaultPose
+===============
+*/
+void idMayaExport::GetDefaultPose( idMat3 &align ) {
+ float start;
+ MDagPath dagPath;
+ idMat3 jointaxis;
+ idVec3 jointpos;
+ idExportJoint *joint, *parent;
+ idBounds bnds;
+ idBounds meshBounds;
+ idList frame;
+
+ start = TimeForFrame( options.startframe );
+
+ common->Printf( "default pose frame = %d\n", options.startframe );
+ common->Printf( " default pose time = %f\n", start );
+
+ frame.SetNum( model.joints.Num() );
+ SetFrame( 0 );
+
+ // convert joints into local coordinates and save in channels
+ for( joint = model.exportHead.GetNext(); joint != NULL; joint = joint->exportNode.GetNext() ) {
+ if ( !joint->dagnode ) {
+ // custom origin joint
+ joint->idwm.Identity();
+ joint->idt.Zero();
+ frame[ joint->index ].t.Zero();
+ frame[ joint->index ].q.Set( 0.0f, 0.0f, 0.0f );
+ continue;
+ }
+
+ // get the worldspace positions of the joint
+ GetWorldTransform( joint, jointpos, jointaxis, options.scale );
+
+ // convert to id coordinates
+ jointaxis = ConvertToIdSpace( jointaxis ) * align;
+ jointpos = ConvertToIdSpace( jointpos ) * align;
+
+ // save worldspace position of joint for children
+ joint->idwm = jointaxis;
+ joint->idt = jointpos;
+
+ parent = joint->exportNode.GetParent();
+ if ( parent ) {
+ // convert to local coordinates
+ jointpos = ( jointpos - parent->idt ) * parent->idwm.Transpose();
+ jointaxis = jointaxis * parent->idwm.Transpose();
+ } else if ( joint->name == "origin" ) {
+ if ( options.clearOrigin ) {
+ jointpos.Zero();
+ }
+ if ( options.clearOriginAxis ) {
+ jointaxis.Identity();
+ }
+ }
+
+ frame[ joint->index ].t = jointpos;
+ frame[ joint->index ].q = jointaxis.ToQuat().ToCQuat();
+ }
+
+ // relocate origin to start at 0, 0, 0 for first frame
+ joint = model.FindJoint( "origin" );
+ if ( joint ) {
+ frame[ joint->index ].t.Zero();
+ }
+
+ // transform the hierarchy
+ for( joint = model.exportHead.GetNext(); joint != NULL; joint = joint->exportNode.GetNext() ) {
+ jointpos = frame[ joint->index ].t;
+ jointaxis = frame[ joint->index ].q.ToQuat().ToMat3();
+
+ parent = joint->exportNode.GetParent();
+ if ( parent ) {
+ joint->idwm = jointaxis * parent->idwm;
+ joint->idt = parent->idt + jointpos * parent->idwm;
+ } else {
+ joint->idwm = jointaxis;
+ joint->idt = jointpos;
+ }
+
+ joint->bindmat = joint->idwm;
+ joint->bindpos = joint->idt;
+ }
+
+ common->Printf( "\n" );
+}
+
+/*
+===============
+idMayaExport::CreateAnimation
+===============
+*/
+void idMayaExport::CreateAnimation( idMat3 &align ) {
+ int i;
+ float start, end;
+ MDagPath dagPath;
+ idMat3 jointaxis;
+ idVec3 jointpos;
+ int frameNum;
+ idExportJoint *joint, *parent;
+ idBounds bnds;
+ idBounds meshBounds;
+ jointFrame_t *frame;
+ int cycleStart;
+ idVec3 totalDelta;
+ idList copyFrames;
+
+ start = TimeForFrame( options.startframe );
+ end = TimeForFrame( options.endframe );
+
+ model.numFrames = options.endframe + 1 - options.startframe;
+ model.frameRate = options.framerate;
+
+ common->Printf( "start frame = %d\n end frame = %d\n", options.startframe, options.endframe );
+ common->Printf( " start time = %f\n end time = %f\n total time = %f\n", start, end, end - start );
+
+ if ( start > end ) {
+ MayaError( "Start frame is greater than end frame." );
+ }
+
+ model.bounds.SetNum( model.numFrames );
+ model.jointFrames.SetNum( model.numFrames * model.joints.Num() );
+ model.frames.SetNum( model.numFrames );
+ for( i = 0; i < model.numFrames; i++ ) {
+ model.frames[ i ] = &model.jointFrames[ model.joints.Num() * i ];
+ }
+
+ // *sigh*. cyclestart doesn't work nicely with the anims.
+ // may just want to not do it in SetTime anymore.
+ cycleStart = options.cycleStart;
+ options.cycleStart = options.startframe;
+
+ for( frameNum = 0; frameNum < model.numFrames; frameNum++ ) {
+ common->Printf( "\rFrame %d/%d...", options.startframe + frameNum, options.endframe );
+
+ frame = model.frames[ frameNum ];
+ SetFrame( frameNum );
+
+ // convert joints into local coordinates and save in channels
+ for( joint = model.exportHead.GetNext(); joint != NULL; joint = joint->exportNode.GetNext() ) {
+ if ( !joint->dagnode ) {
+ // custom origin joint
+ joint->idwm.Identity();
+ joint->idt.Zero();
+ frame[ joint->index ].t.Zero();
+ frame[ joint->index ].q.Set( 0.0f, 0.0f, 0.0f );
+ continue;
+ }
+
+ // get the worldspace positions of the joint
+ GetWorldTransform( joint, jointpos, jointaxis, options.scale );
+
+ // convert to id coordinates
+ jointaxis = ConvertToIdSpace( jointaxis ) * align;
+ jointpos = ConvertToIdSpace( jointpos ) * align;
+
+ // save worldspace position of joint for children
+ joint->idwm = jointaxis;
+ joint->idt = jointpos;
+
+ parent = joint->exportNode.GetParent();
+ if ( parent ) {
+ // convert to local coordinates
+ jointpos = ( jointpos - parent->idt ) * parent->idwm.Transpose();
+ jointaxis = jointaxis * parent->idwm.Transpose();
+ } else if ( joint->name == "origin" ) {
+ if ( options.clearOrigin ) {
+ jointpos.Zero();
+ }
+ if ( options.clearOriginAxis ) {
+ jointaxis.Identity();
+ }
+ }
+
+ frame[ joint->index ].t = jointpos;
+ frame[ joint->index ].q = jointaxis.ToQuat().ToCQuat();
+ }
+ }
+
+ options.cycleStart = cycleStart;
+ totalDelta.Zero();
+
+ joint = model.FindJoint( "origin" );
+ if ( joint ) {
+ frame = model.frames[ 0 ];
+ idVec3 origin = frame[ joint->index ].t;
+
+ frame = model.frames[ model.numFrames - 1 ];
+ totalDelta = frame[ joint->index ].t - origin;
+ }
+
+ // shift the frames when cycleStart is used
+ if ( options.cycleStart > options.startframe ) {
+ copyFrames = model.jointFrames;
+ for( i = 0; i < model.numFrames; i++ ) {
+ bool shiftorigin = false;
+ frameNum = i + ( options.cycleStart - options.startframe );
+ if ( frameNum >= model.numFrames ) {
+ // wrap around, skipping the first frame, since it's a dupe of the last frame
+ frameNum -= model.numFrames - 1;
+ shiftorigin = true;
+ }
+
+ memcpy( &model.jointFrames[ model.joints.Num() * i ], ©Frames[ model.joints.Num() * frameNum ], model.joints.Num() * sizeof( copyFrames[ 0 ] ) );
+
+ if ( joint && shiftorigin ) {
+ model.frames[ i ][ joint->index ].t += totalDelta;
+ }
+ }
+ }
+
+ if ( joint ) {
+ // relocate origin to start at 0, 0, 0 for first frame
+ frame = model.frames[ 0 ];
+ idVec3 origin = frame[ joint->index ].t;
+ for( i = 0; i < model.numFrames; i++ ) {
+ frame = model.frames[ i ];
+ frame[ joint->index ].t -= origin;
+ }
+ }
+
+ // get the bounds for each frame
+ for( frameNum = 0; frameNum < model.numFrames; frameNum++ ) {
+ frame = model.frames[ frameNum ];
+
+ // transform the hierarchy
+ for( joint = model.exportHead.GetNext(); joint != NULL; joint = joint->exportNode.GetNext() ) {
+ jointpos = frame[ joint->index ].t;
+ jointaxis = frame[ joint->index ].q.ToQuat().ToMat3();
+
+ parent = joint->exportNode.GetParent();
+ if ( parent ) {
+ joint->idwm = jointaxis * parent->idwm;
+ joint->idt = parent->idt + jointpos * parent->idwm;
+ } else {
+ joint->idwm = jointaxis;
+ joint->idt = jointpos;
+ }
+ }
+
+ // get bounds for this frame
+ bnds.Clear();
+ for( i = 0; i < model.meshes.Num(); i++ ) {
+ if ( model.meshes[ i ]->keep ) {
+ model.meshes[ i ]->GetBounds( meshBounds );
+ bnds.AddBounds( meshBounds );
+ }
+ }
+ model.bounds[ frameNum ][ 0 ] = bnds[ 0 ];
+ model.bounds[ frameNum ][ 1 ] = bnds[ 1 ];
+ }
+
+ common->Printf( "\n" );
+}
+
+/*
+===============
+idMayaExport::ConvertModel
+===============
+*/
+void idMayaExport::ConvertModel( void ) {
+ MStatus status;
+ idMat3 align;
+
+ common->Printf( "Converting %s to %s...\n", options.src.c_str(), options.dest.c_str() );
+
+ // see if the destination file exists
+ FILE *file = fopen( options.dest, "r" );
+ if ( file ) {
+ fclose( file );
+
+ // make sure we can write to the destination
+ FILE *file = fopen( options.dest, "r+" );
+ if ( !file ) {
+ MayaError( "Unable to write to the file '%s'", options.dest.c_str() );
+ }
+ fclose( file );
+ }
+
+ MString filename( options.src );
+ MFileIO::newFile( true );
+
+ // Load the file into Maya
+ common->Printf( "Loading file...\n" );
+ status = MFileIO::open( filename, NULL, true );
+ if ( !status ) {
+ MayaError( "Error loading '%s': '%s'\n", filename.asChar(), status.errorString().asChar() );
+ }
+
+ // force Maya to update the frame. When using references, sometimes
+ // the model is posed the way it is in the source. Since Maya only
+ // updates it when the frame time changes to a value other than the
+ // current, just setting the time to the min time doesn't guarantee
+ // that the model gets updated.
+ MGlobal::viewFrame( MAnimControl::maxTime() );
+ MGlobal::viewFrame( MAnimControl::minTime() );
+
+ if ( options.startframe < 0 ) {
+ options.startframe = MAnimControl::minTime().as( MTime::kFilm );
+ }
+
+ if ( options.endframe < 0 ) {
+ options.endframe = MAnimControl::maxTime().as( MTime::kFilm );
+ }
+ if ( options.cycleStart < 0 ) {
+ options.cycleStart = options.startframe;
+ } else if ( ( options.cycleStart < options.startframe ) || ( options.cycleStart > options.endframe ) ) {
+ MayaError( "cycleStart (%d) out of frame range (%d to %d)\n", options.cycleStart, options.startframe, options.endframe );
+ } else if ( options.cycleStart == options.endframe ) {
+ // end frame is a duplicate of the first frame in cycles, so just disable cycleStart
+ options.cycleStart = options.startframe;
+ }
+
+ // create a list of the transform nodes that will make up our heirarchy
+ common->Printf( "Creating joints...\n" );
+ CreateJoints( options.scale );
+ if ( options.type != WRITE_CAMERA ) {
+ common->Printf( "Creating meshes...\n" );
+ CreateMesh( options.scale );
+ common->Printf( "Renaming joints...\n" );
+ RenameJoints( options.renamejoints, options.prefix );
+ common->Printf( "Remapping parents...\n" );
+ RemapParents( options.remapjoints );
+ common->Printf( "Pruning joints...\n" );
+ PruneJoints( options.keepjoints, options.prefix );
+ common->Printf( "Combining meshes...\n" );
+ CombineMeshes();
+ }
+
+ common->Printf( "Align model...\n" );
+ GetAlignment( options.align, align, options.rotate, 0 );
+
+ switch( options.type ) {
+ case WRITE_MESH :
+ common->Printf( "Grabbing default pose:\n" );
+ GetDefaultPose( align );
+ common->Printf( "Writing file...\n" );
+ if ( !model.WriteMesh( options.dest, options ) ) {
+ MayaError( "error writing to '%s'", options.dest.c_str() );
+ }
+ break;
+
+ case WRITE_ANIM :
+ common->Printf( "Creating animation frames:\n" );
+ CreateAnimation( align );
+ common->Printf( "Writing file...\n" );
+ if ( !model.WriteAnim( options.dest, options ) ) {
+ MayaError( "error writing to '%s'", options.dest.c_str() );
+ }
+ break;
+
+ case WRITE_CAMERA :
+ common->Printf( "Creating camera frames:\n" );
+ CreateCameraAnim( align );
+
+ common->Printf( "Writing file...\n" );
+ if ( !model.WriteCamera( options.dest, options ) ) {
+ MayaError( "error writing to '%s'", options.dest.c_str() );
+ }
+ break;
+ }
+
+ common->Printf( "done\n\n" );
+}
+
+/*
+===============
+idMayaExport::ConvertToMD3
+===============
+*/
+void idMayaExport::ConvertToMD3( void ) {
+#if 0
+ int i, j;
+ md3Header_t *pinmodel;
+ md3Frame_t *frame;
+ md3Surface_t *surf;
+ md3Shader_t *shader;
+ md3Triangle_t *tri;
+ md3St_t *st;
+ md3XyzNormal_t *xyz;
+ md3Tag_t *tag;
+ int version;
+ int size;
+
+ //model_t *mod, int lod, void *buffer, const char *mod_name
+
+ pinmodel = (md3Header_t *)buffer;
+
+ version = LittleLong (pinmodel->version);
+ if (version != MD3_VERSION) {
+ common->Printf( "R_LoadMD3: %s has wrong version (%i should be %i)\n",
+ mod_name, version, MD3_VERSION);
+ return qfalse;
+ }
+
+ mod->type = MOD_MESH;
+ size = LittleLong(pinmodel->ofsEnd);
+ mod->dataSize += size;
+ mod->md3[lod] = ri.Hunk_Alloc( size );
+
+ memcpy (mod->md3[lod], buffer, LittleLong(pinmodel->ofsEnd) );
+
+ LL(mod->md3[lod]->ident);
+ LL(mod->md3[lod]->version);
+ LL(mod->md3[lod]->numFrames);
+ LL(mod->md3[lod]->numTags);
+ LL(mod->md3[lod]->numSurfaces);
+ LL(mod->md3[lod]->ofsFrames);
+ LL(mod->md3[lod]->ofsTags);
+ LL(mod->md3[lod]->ofsSurfaces);
+ LL(mod->md3[lod]->ofsEnd);
+
+ if ( mod->md3[lod]->numFrames < 1 ) {
+ common->Printf( "R_LoadMD3: %s has no frames\n", mod_name );
+ return qfalse;
+ }
+
+ // swap all the frames
+ frame = (md3Frame_t *) ( (byte *)mod->md3[lod] + mod->md3[lod]->ofsFrames );
+ for ( i = 0 ; i < mod->md3[lod]->numFrames ; i++, frame++) {
+ frame->radius = LittleFloat( frame->radius );
+ for ( j = 0 ; j < 3 ; j++ ) {
+ frame->bounds[0][j] = LittleFloat( frame->bounds[0][j] );
+ frame->bounds[1][j] = LittleFloat( frame->bounds[1][j] );
+ frame->localOrigin[j] = LittleFloat( frame->localOrigin[j] );
+ }
+ }
+
+ // swap all the tags
+ tag = (md3Tag_t *) ( (byte *)mod->md3[lod] + mod->md3[lod]->ofsTags );
+ for ( i = 0 ; i < mod->md3[lod]->numTags * mod->md3[lod]->numFrames ; i++, tag++) {
+ for ( j = 0 ; j < 3 ; j++ ) {
+ tag->origin[j] = LittleFloat( tag->origin[j] );
+ tag->axis[0][j] = LittleFloat( tag->axis[0][j] );
+ tag->axis[1][j] = LittleFloat( tag->axis[1][j] );
+ tag->axis[2][j] = LittleFloat( tag->axis[2][j] );
+ }
+ }
+
+ // swap all the surfaces
+ surf = (md3Surface_t *) ( (byte *)mod->md3[lod] + mod->md3[lod]->ofsSurfaces );
+ for ( i = 0 ; i < mod->md3[lod]->numSurfaces ; i++) {
+
+ LL(surf->ident);
+ LL(surf->flags);
+ LL(surf->numFrames);
+ LL(surf->numShaders);
+ LL(surf->numTriangles);
+ LL(surf->ofsTriangles);
+ LL(surf->numVerts);
+ LL(surf->ofsShaders);
+ LL(surf->ofsSt);
+ LL(surf->ofsXyzNormals);
+ LL(surf->ofsEnd);
+
+ if ( surf->numVerts > SHADER_MAX_VERTEXES ) {
+ ri.Error (ERR_DROP, "R_LoadMD3: %s has more than %i verts on a surface (%i)",
+ mod_name, SHADER_MAX_VERTEXES, surf->numVerts );
+ }
+ if ( surf->numTriangles*3 > SHADER_MAX_INDEXES ) {
+ ri.Error (ERR_DROP, "R_LoadMD3: %s has more than %i triangles on a surface (%i)",
+ mod_name, SHADER_MAX_INDEXES / 3, surf->numTriangles );
+ }
+
+ // change to surface identifier
+ surf->ident = SF_MD3;
+
+ // lowercase the surface name so skin compares are faster
+ Q_strlwr( surf->name );
+
+ // strip off a trailing _1 or _2
+ // this is a crutch for q3data being a mess
+ j = strlen( surf->name );
+ if ( j > 2 && surf->name[j-2] == '_' ) {
+ surf->name[j-2] = 0;
+ }
+
+ // register the shaders
+ shader = (md3Shader_t *) ( (byte *)surf + surf->ofsShaders );
+ for ( j = 0 ; j < surf->numShaders ; j++, shader++ ) {
+ shader_t *sh;
+
+ sh = R_FindShader( shader->name, LIGHTMAP_NONE, qtrue );
+ if ( sh->defaultShader ) {
+ shader->shaderIndex = 0;
+ } else {
+ shader->shaderIndex = sh->index;
+ }
+ }
+
+ // swap all the triangles
+ tri = (md3Triangle_t *) ( (byte *)surf + surf->ofsTriangles );
+ for ( j = 0 ; j < surf->numTriangles ; j++, tri++ ) {
+ LL(tri->indexes[0]);
+ LL(tri->indexes[1]);
+ LL(tri->indexes[2]);
+ }
+
+ // swap all the ST
+ st = (md3St_t *) ( (byte *)surf + surf->ofsSt );
+ for ( j = 0 ; j < surf->numVerts ; j++, st++ ) {
+ st->st[0] = LittleFloat( st->st[0] );
+ st->st[1] = LittleFloat( st->st[1] );
+ }
+
+ // swap all the XyzNormals
+ xyz = (md3XyzNormal_t *) ( (byte *)surf + surf->ofsXyzNormals );
+ for ( j = 0 ; j < surf->numVerts * surf->numFrames ; j++, xyz++ )
+ {
+ xyz->xyz[0] = LittleShort( xyz->xyz[0] );
+ xyz->xyz[1] = LittleShort( xyz->xyz[1] );
+ xyz->xyz[2] = LittleShort( xyz->xyz[2] );
+
+ xyz->normal = LittleShort( xyz->normal );
+ }
+
+
+ // find the next surface
+ surf = (md3Surface_t *)( (byte *)surf + surf->ofsEnd );
+ }
+ return true;
+#endif
+}
+
+/*
+==============================================================================
+
+dll setup
+
+==============================================================================
+*/
+
+/*
+===============
+Maya_Shutdown
+===============
+*/
+void Maya_Shutdown( void ) {
+ if ( initialized ) {
+ errorMessage.Clear();
+ initialized = false;
+
+ // This shuts down the entire app somehow, so just ignore it.
+ //MLibrary::cleanup();
+ }
+}
+
+/*
+===============
+Maya_ConvertModel
+===============
+*/
+const char *Maya_ConvertModel( const char *ospath, const char *commandline ) {
+
+ errorMessage = "Ok";
+
+ try {
+ idExportOptions options( commandline, ospath );
+ idMayaExport export( options );
+
+ export.ConvertModel();
+ }
+
+ catch( idException &exception ) {
+ errorMessage = exception.error;
+ }
+
+ return errorMessage;
+}
+
+/*
+===============
+dllEntry
+===============
+*/
+bool dllEntry( int version, idCommon *common, idSys *sys ) {
+
+ if ( !common || !sys ) {
+ return false;
+ }
+
+ ::common = common;
+ ::sys = sys;
+ ::cvarSystem = NULL;
+
+ idLib::sys = sys;
+ idLib::common = common;
+ idLib::cvarSystem = NULL;
+ idLib::fileSystem = NULL;
+
+ idLib::Init();
+
+ if ( version != MD5_VERSION ) {
+ common->Printf( "Error initializing Maya exporter: DLL version %d different from .exe version %d\n", MD5_VERSION, version );
+ return false;
+ }
+
+ if ( !initialized ) {
+ MStatus status;
+
+ status = MLibrary::initialize( GAME_NAME, true );
+ if ( !status ) {
+ common->Printf( "Error calling MLibrary::initialize (%s)\n", status.errorString().asChar() );
+ return false;
+ }
+
+ initialized = true;
+ }
+
+ return true;
+}
+
+// Force type checking on the interface functions to help ensure that they match the ones in the .exe
+const exporterDLLEntry_t ValidateEntry = &dllEntry;
+const exporterInterface_t ValidateConvert = &Maya_ConvertModel;
+const exporterShutdown_t ValidateShutdown = &Maya_Shutdown;
diff --git a/neo/prey/MayaImport/maya_main.h b/neo/prey/MayaImport/maya_main.h
new file mode 100644
index 00000000..df375dc3
--- /dev/null
+++ b/neo/prey/MayaImport/maya_main.h
@@ -0,0 +1,18 @@
+
+#ifndef __MAYA_MAIN_H__
+#define __MAYA_MAIN_H__
+
+/*
+==============================================================
+
+ Maya Import
+
+==============================================================
+*/
+
+
+typedef bool ( *exporterDLLEntry_t )( int version, idCommon *common, idSys *sys );
+typedef const char *( *exporterInterface_t )( const char *ospath, const char *commandline );
+typedef void ( *exporterShutdown_t )( void );
+
+#endif /* !__MAYA_MAIN_H__ */
diff --git a/neo/prey/MayaImport/mayaimport.def b/neo/prey/MayaImport/mayaimport.def
new file mode 100644
index 00000000..9748f40c
--- /dev/null
+++ b/neo/prey/MayaImport/mayaimport.def
@@ -0,0 +1,4 @@
+EXPORTS
+ dllEntry
+ Maya_ConvertModel
+ Maya_Shutdown
diff --git a/neo/prey/Prey/ai_Navigator.cpp b/neo/prey/Prey/ai_Navigator.cpp
new file mode 100644
index 00000000..747a5b4e
--- /dev/null
+++ b/neo/prey/Prey/ai_Navigator.cpp
@@ -0,0 +1,268 @@
+
+#include "../idlib/precompiled.h"
+#pragma hdrstop
+
+#include "prey_local.h"
+
+
+
+/*
+=================
+hhNavigator::hhNavigator()
+=================
+*/
+hhNavigator::hhNavigator(void) {
+
+
+ hhSelf = NULL;
+
+// followStateFunction = FollowAllyStay;
+
+} //. hhNavigator::hhNavigator(void)
+
+
+/*
+========================
+hhNavigator::Spawn(void)
+========================
+*/
+
+// Required for CLASS_DECLARATION
+void hhNavigator::Spawn(void) {
+
+
+} //. hhNavigator::hhNavigator(void)
+
+
+/*
+=======================================
+hhNavigator::SetOwner(idAI *owner)
+=======================================
+*/
+void hhNavigator::SetOwner(idAI *owner) {
+
+ idNavigator::SetOwner(owner);
+
+ if (owner->IsType(hhAI::Type)) {
+ hhSelf = static_cast(owner);
+ }
+
+} //. hhNavigator::SetOwner(idAI *)
+
+/* JRM- Removed because brain stuff handles this logic now
+//
+//===============================
+//hhNavigator::SetAlly(idActor *)
+//===============================
+//
+void hhNavigator::SetAlly(idActor *ally) {
+
+ if (ally != NULL) {
+ FollowAlly(ally);
+ }
+
+} //. hhNavigator::SetAlly(idActor *)
+
+
+//
+//=============================
+//hhNavigator::FollowAlly(ally)
+//=============================
+//
+void hhNavigator::FollowAlly(idActor *ally) {
+
+ // If we aren't on an hhAI, we can't follow, so leave
+ if (hhSelf == NULL) {
+ return;
+ }
+
+
+
+ (this->*followStateFunction)(ally);
+
+
+
+} //. hhNavigator::FollowAlly(idActor *ally)
+*/
+
+
+
+
+
+/* HUMANHEAD JRM - REMOVED
+//
+//===================================
+//hhNavigator::FollowAllyStay(void)
+//===================================
+//
+void hhNavigator::FollowAllyStay(idActor *ally) {
+ static bool first = true;
+
+
+ if (first) {
+ if (ai_debug->integer > 0) {
+ gameLocal.Printf("in FollowAllyStay\n");
+ }
+ first = false;
+ }
+
+ if (hhSelf->AI_ALLY_FAR ||
+ !hhSelf->AI_ALLY_VISIBLE) {
+ followStateFunction = FollowAllyFollow;
+ first = true;
+ }
+ else if (hhSelf->AI_ALLY_TOUCHED) {
+ followStateFunction = FollowAllyLead;
+ first = true;
+ }
+ else { // Just stay still
+ moveType = MOVE_NONE;
+ }
+
+
+} //. hhNavigator::FollowAllyStay(void)
+
+
+//
+//===================================
+//hhNavigator::FollowAllyFollow(void)
+//===================================
+//
+void hhNavigator::FollowAllyFollow(idActor *ally) {
+ static bool first = true;
+
+
+ hhSelf->AI_FOLLOW_ALLY = true;
+
+ if (first) {
+ if (ai_debug->integer > 0) {
+ gameLocal.Printf("in FollowAllyFollow\n");
+ }
+ first = false;
+ }
+
+ if (hhSelf->AI_ALLY_NEAR) {
+ StopMove();
+
+ followStateFunction = FollowAllyStay;
+ hhSelf->AI_FOLLOW_ALLY = false;
+ first = true;
+ }
+ // In case the min_dist is too close
+ else if (hhSelf->AI_ALLY_TOUCHED) {
+ followStateFunction = FollowAllyLead;
+ hhSelf->AI_FOLLOW_ALLY = false;
+ first = true;
+ }
+ else {
+ moveDest = ally->GetFloorPos();
+ goal = ally;
+ moveType = MOVE_TO_ALLY;
+
+ if (aas) {
+ toAreaNum = aas->PointReachableAreaNum( moveDest );
+ toAreaEnemy = false;
+ }
+ } //. Follow the player
+
+} //. hhNavigator::FollowAllyFollow(void)
+
+
+//
+//===================================
+//hhNavigator::FollowAllyLead(void)
+//===================================
+//
+void hhNavigator::FollowAllyLead(idActor *ally) {
+ static bool first = true;
+ static int nextUpdateTime;
+ idVec3 leadPosition;
+
+
+ hhSelf->AI_LEAD_ALLY = true;
+
+ if (first) {
+ if (ai_debug->integer > 0) {
+ gameLocal.Printf("in FollowAllyLead\n");
+ }
+ first = false;
+ }
+
+ // Reset this variable, as it was used to get here.
+ if (hhSelf->AI_ALLY_TOUCHED) {
+ hhSelf->AI_ALLY_TOUCHED = false;
+ nextUpdateTime = 0;
+ }
+
+ if (gameLocal.time * 1000.0f >= nextUpdateTime) {
+ //if (nextUpdateTime == 0) {
+
+ leadPosition = FindNewLeadPosition(ally);
+
+ if (leadPosition != vec3_origin) {
+ moveDest = leadPosition;
+ moveType = MOVE_TO_ALLY;
+ goal = NULL;
+
+ if (aas) {
+ toAreaNum = aas->PointReachableAreaNum(moveDest);
+ toAreaEnemy = false;
+ }
+
+ nextUpdateTime = gameLocal.time * 1000.0f +
+ hhSelf->follow_lead_update_time;
+ }
+
+ } //. Time to update position
+
+ if (!hhSelf->AI_ALLY_NEAR) {
+ followStateFunction = FollowAllyStay;
+ hhSelf->AI_LEAD_ALLY = false;
+ first = true;
+ }
+
+
+} //. hhNavigator::FollowAllyLead(void)
+
+
+//
+//================================
+//hhNavigator::FindNewLeadPosition
+//================================
+//
+idVec3 hhNavigator::FindNewLeadPosition(idActor *ally) {
+ idVec3 direction;
+ aasTrace_t trace;
+
+
+ direction = hhSelf->GetFloorPos() - ally->GetFloorPos();
+ direction.Normalize();
+ direction *= hhSelf->follow_min_dist;
+
+ if (aas) {
+ idVec3 bestPos;
+
+ bestPos = aas->FindNearestPoint(ally->GetFloorPos(),
+ hhSelf->GetFloorPos(),
+ hhSelf->follow_min_dist);
+ if (bestPos != vec3_origin) {
+ return(bestPos);
+ }
+
+ // Didn't find a good point
+
+ // Just move as far as we can in the direction nudged
+ aas->Trace(trace, hhSelf->GetPhysics()->GetOrigin(),
+ hhSelf->GetPhysics()->GetOrigin() + direction);
+
+ //? How does this work on slopes?
+ //! Do we want to use the floor position?
+ return(ally->GetFloorPos() + direction * trace.fraction);
+ }
+
+ return(ally->GetFloorPos() + direction);
+
+
+
+} //. hhNavigator::FindNewLeadPosition(idActor *)
+*/
diff --git a/neo/prey/Prey/ai_Navigator.h b/neo/prey/Prey/ai_Navigator.h
new file mode 100644
index 00000000..4a50a367
--- /dev/null
+++ b/neo/prey/Prey/ai_Navigator.h
@@ -0,0 +1,41 @@
+
+#ifndef __PREY_AI_NAVIGATOR_H__
+#define __PREY_AI_NAVIGATOR_H__
+
+// Forward declaration for hhAI
+class hhAI;
+
+class hhNavigator : public idNavigator {
+
+public:
+
+ hhNavigator(void);
+
+ void Spawn(void);
+
+ //virtual void SetAlly(idActor *ally); JRM removed
+
+ virtual void SetOwner(idAI *owner);
+
+ virtual boolean IsNearDest( void );
+
+protected:
+
+ hhAI * hhSelf;
+
+ //virtual void FollowAlly(idActor *ally); JRM removed
+
+ /* JRM removed
+ void FollowAllyStay(idActor *ally);
+ void FollowAllyFollow(idActor *ally);
+ void FollowAllyLead(idActor *ally);
+ */
+
+// void (hhNavigator::* followStateFunction) (idActor *ally); JRM removed
+
+ //idVec3 FindNewLeadPosition(idActor *ally); JRM removed
+
+};
+
+
+#endif /* __PREY_AI_NAVIGATOR_H__ */
diff --git a/neo/prey/Prey/ai_centurion.cpp b/neo/prey/Prey/ai_centurion.cpp
new file mode 100644
index 00000000..2fc8d403
--- /dev/null
+++ b/neo/prey/Prey/ai_centurion.cpp
@@ -0,0 +1,529 @@
+#include "precompiled.h"
+#pragma hdrstop
+
+#include "prey_local.h"
+
+const idEventDef AI_CenturionLaunch("centurionFire", "d"); //called from anims to handle firing for centurion
+const idEventDef AI_CenturionRoar("centurionRoar"); //called from level-script to cause centurion to roar
+const idEventDef AI_CenturionArmChop("centurionArmChop"); //called from level-script to actually cause centurion to loose arm
+const idEventDef AI_CenturionLooseArm("centurionLooseArm"); //internal: used to actually 'loose' arm
+const idEventDef AI_CenturionForceFieldNotify("preForcefieldNotify");
+const idEventDef AI_CenturionForceFieldToggle("forcefieldToggle", "d");
+const idEventDef AI_CenturionInTunnel("playerInBox", "dE");
+const idEventDef AI_CenturionReachedTunnel("reachedTunnel");
+const idEventDef AI_CenturionMoveToTunnel("moveToTunnel", NULL, 'd');
+const idEventDef AI_CheckForObstructions("checkForObstructions", "d", 'd');
+const idEventDef AI_DestroyObstruction("destroyObstruction");
+const idEventDef AI_MoveToObstruction("moveToObstruction");
+const idEventDef AI_ReachedObstruction("reachedObstruction");
+const idEventDef AI_CloseToObstruction("closeToObstruction", 0, 'd');
+const idEventDef AI_BackhandImpulse("", "e");
+const idEventDef AI_EnemyCloseToObstruction("enemyCloseToObstruction", NULL, 'd');
+const idEventDef AI_TakeDamage("takeDamage", "d");
+const idEventDef AI_FindNearbyEnemy("findNearbyEnemy", "f", 'e');
+
+CLASS_DECLARATION( hhMonsterAI, hhCenturion )
+ EVENT( AI_CenturionLaunch, hhCenturion::Event_CenturionLaunch )
+ EVENT( AI_CenturionRoar, hhCenturion::Event_ScriptedRoar )
+ EVENT( AI_CenturionArmChop, hhCenturion::Event_ScriptedArmChop )
+ EVENT( AI_CenturionLooseArm, hhCenturion::Event_CenturionLooseArm )
+ EVENT( AI_CenturionInTunnel, hhCenturion::Event_PlayerInTunnel )
+ EVENT( AI_CenturionReachedTunnel, hhCenturion::Event_ReachedTunnel )
+ EVENT( AI_CenturionMoveToTunnel, hhCenturion::Event_MoveToTunnel )
+ EVENT( AI_CenturionForceFieldNotify, hhCenturion::Event_ForceFieldNotify )
+ EVENT( AI_CenturionForceFieldToggle, hhCenturion::Event_ForceFieldToggle )
+ EVENT( AI_CheckForObstructions, hhCenturion::Event_CheckForObstruction )
+ EVENT( AI_MoveToObstruction, hhCenturion::Event_MoveToObstruction )
+ EVENT( AI_DestroyObstruction, hhCenturion::Event_DestroyObstruction )
+ EVENT( AI_ReachedObstruction, hhCenturion::Event_ReachedObstruction )
+ EVENT( AI_CloseToObstruction, hhCenturion::Event_CloseToObstruction )
+ EVENT( AI_BackhandImpulse, hhCenturion::Event_BackhandImpulse )
+ EVENT( AI_EnemyCloseToObstruction, hhCenturion::Event_EnemyCloseToObstruction )
+ EVENT( AI_TakeDamage, hhCenturion::Event_TakeDamage )
+ EVENT( AI_FindNearbyEnemy, hhCenturion::Event_FindNearbyEnemy )
+END_CLASS
+
+#ifndef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build
+
+void hhCenturion::Spawn() {
+ AI_CENTURION_ARM_MISSING = false;
+ AI_CENTURION_REQUIRE_ROAR = false;
+ AI_CENTURION_ARM_TUNNEL = false;
+ AI_CENTURION_SCRIPTED_ROAR = 0;
+ AI_CENTURION_FORCEFIELD_WAIT = false;
+ AI_CENTURION_SCRIPTED_TUNNEL = 0;
+}
+
+void hhCenturion::Event_PostSpawn( void ) {
+ hhMonsterAI::Event_PostSpawn();
+
+ const idKeyValue *kv = spawnArgs.MatchPrefix( "aasObstacle" );
+ idEntityPtr ent;
+ while ( kv ) {
+ ent = gameLocal.FindEntity( kv->GetValue() );
+ if ( ent.IsValid() ) {
+ obstacles.AddUnique( ent );
+ }
+ kv = spawnArgs.MatchPrefix( "aasObstacle", kv );
+ }
+}
+
+#define LinkScriptVariable( name ) name.LinkTo( scriptObject, #name )
+void hhCenturion::LinkScriptVariables( void ) {
+ hhMonsterAI::LinkScriptVariables();
+
+ LinkScriptVariable(AI_CENTURION_ARM_MISSING);
+ LinkScriptVariable(AI_CENTURION_REQUIRE_ROAR);
+ LinkScriptVariable(AI_CENTURION_ARM_TUNNEL);
+ LinkScriptVariable(AI_CENTURION_SCRIPTED_ROAR);
+ LinkScriptVariable(AI_CENTURION_SCRIPTED_TUNNEL);
+ LinkScriptVariable(AI_CENTURION_FORCEFIELD_WAIT);
+}
+
+
+void hhCenturion::Event_ForceFieldNotify() {
+ if( !armchop_Target.GetEntity() ) {
+ return; //must not be in arm-chop mode, so we don't care yet
+ }
+ AI_CENTURION_REQUIRE_ROAR = true;
+ AI_CENTURION_FORCEFIELD_WAIT = true;
+}
+
+void hhCenturion::Event_ForceFieldToggle( int toggle ) {
+ if( !armchop_Target.GetEntity() ) {
+ return;
+ }
+ AI_CENTURION_FORCEFIELD_WAIT = false;
+ if (!toggle) {
+ AI_CENTURION_REQUIRE_ROAR = false;
+ } else if( AI_CENTURION_ARM_TUNNEL ) {
+ Event_ScriptedArmChop();
+ return;
+ }
+}
+
+void hhCenturion::Event_PlayerInTunnel( int toggle, idEntity* ent ) {
+ if( AI_CENTURION_ARM_MISSING ) { //don't care if the player goes back in tunnel once we already lost our arm
+ return;
+ }
+
+ if( toggle ) {
+ if( !ent ) {
+ gameLocal.DWarning( "entity needs to be present for tunnel event" );
+ return;
+ }
+ armchop_Target = ent;
+ AI_CENTURION_SCRIPTED_TUNNEL = 1;
+ }
+ else {
+ armchop_Target = NULL;
+ AI_CENTURION_SCRIPTED_TUNNEL = 0;
+ }
+}
+
+void hhCenturion::Event_MoveToTunnel() {
+ if (!armchop_Target.IsValid()) {
+ idThread::ReturnInt(0);
+ return;
+ }
+
+ StopMove(MOVE_STATUS_DONE);
+ MoveToPosition(armchop_Target->GetOrigin());
+ idThread::ReturnInt(1);
+}
+
+void hhCenturion::Event_ReachedTunnel() {
+ if( armchop_Target.IsValid() ) {
+ idAngles faceAngles = armchop_Target->GetAxis()[0].ToAngles();
+ ideal_yaw = faceAngles.yaw;
+ current_yaw = faceAngles.yaw;
+ SetAxis( armchop_Target->GetAxis() );
+ }
+}
+
+void hhCenturion::Event_ScriptedRoar() {
+ if ( AI_CENTURION_SCRIPTED_ROAR > 0 ) {
+ gameLocal.Warning( "centurionRoar() called more than once!\n");
+ } else {
+ AI_CENTURION_SCRIPTED_ROAR = 1;
+ }
+}
+
+void hhCenturion::Event_ScriptedArmChop() {
+ const function_t* newstate = NULL;
+ newstate = GetScriptFunction( "state_ScriptedArmChop" );
+ if( newstate ) {
+ SetState( newstate );
+ }
+ else {
+ gameLocal.Warning( "Unable to find 'state_ScriptedArmChop' on centurion" );
+ }
+}
+
+void hhCenturion::Event_CenturionLooseArm() {
+ idDict args;
+ idEntity* ent = NULL;
+ idVec3 jointLoc;
+ idMat3 jointAxis;
+
+ animator.GetJointTransform( spawnArgs.GetString("arm_severjoint", ""), gameLocal.time, jointLoc, jointAxis );
+ jointLoc = renderEntity.origin + jointLoc * renderEntity.axis;
+ args.SetVector( "origin", jointLoc );
+ args.SetMatrix( "rotation", renderEntity.axis );
+ args.SetBool( "spin", 0 );
+ args.SetFloat( "triggersize", 48.f );
+ args.SetBool( "enablePickup", true );
+ args.SetFloat( "respawn", 0.f );
+ ent = gameLocal.SpawnObject( spawnArgs.GetString("def_arm_weaponclass", ""), &args );
+
+ SetSkinByName( spawnArgs.GetString( "skin_arm_gone" ) );
+
+ hhFxInfo fx;
+ fx.SetEntity( this );
+ fx.RemoveWhenDone( true );
+ SpawnFxLocal( spawnArgs.GetString( "fx_armchop" ), jointLoc, mat3_identity, &fx );
+
+ //drop any stuck arrows
+ idEntity *next;
+ for( ent = teamChain; ent != NULL; ent = next ) {
+ next = ent->GetTeamChain();
+ if ( ent && ent->IsType( hhProjectile::Type ) ) {
+ ent->Unbind();
+ ent->Hide();
+ ent->PostEventSec( &EV_Remove, 5 );
+ next = teamChain;
+ }
+ }
+
+ AI_CENTURION_ARM_MISSING = true;
+}
+
+void hhCenturion::Event_CenturionLaunch( const idList* parmList ) {
+ const idDict* projDef = NULL;
+ const idSoundShader* soundShader = NULL;
+
+ // parms: joint, projectileDef, sound, fx
+ if( !parmList || parmList->Num() != 5 ) {
+ gameLocal.Warning( "Incorrect paramater number" );
+ return;
+ }
+//Rbarrel_A projectile_centurion_autocannon snd_fire fx_muzzleFlash
+ const char* jointName = (*parmList)[ 0 ].c_str();
+ const char* projectileDefName = (*parmList)[ 1 ].c_str();
+ const char* soundName = (*parmList)[ 2 ].c_str();
+ const char* fxName = (*parmList)[ 3 ].c_str();
+ int autoAim = atoi( (*parmList)[ 4 ].c_str() );
+
+ if( AI_CENTURION_ARM_MISSING ) { //skip firing if joint is on the severed arm
+ if( !idStr::Icmp(jointName, spawnArgs.GetString("severed_jointA", "")) || !idStr::Icmp(jointName, spawnArgs.GetString("severed_jointB", "")) ) {
+ return;
+ }
+ }
+
+ projDef = gameLocal.FindEntityDefDict( projectileDefName, false );
+ HH_ASSERT( !shootTarget.IsValid() );
+ // If autoAim is true and we're not blending, and we're facing the enemy, auto-aim
+ if ( ( autoAim == 1 || ( autoAim == 2 && gameLocal.random.RandomInt(100) < 50 ) ) && torsoAnim.animBlendFrames == 0 && FacingEnemy( 5.0f ) ) {
+ AimedAttackMissile( jointName, projDef );
+ } else { // Otherwise do a normal missile attack
+ Event_AttackMissile( jointName, projDef, 1 );
+ }
+
+ if(idStr::Cmpn( soundName, "snd_", 4)) {
+ soundShader = declManager->FindSound( soundName );
+ if( soundShader->GetState() == DS_DEFAULTED ) {
+ gameLocal.Warning( "Sound '%s' not found", soundName );
+ }
+ StartSoundShader( soundShader, SND_CHANNEL_WEAPON, 0, false, NULL );
+ }
+ else {
+ if( !StartSound(soundName, SND_CHANNEL_WEAPON, 0, false, NULL) ) {
+ gameLocal.Warning( "Framecommand 'centurionFire' on entity '%s' could not find sound '%s'", GetName(), soundName );
+ }
+ }
+ BroadcastFxInfoAlongBone( spawnArgs.GetString(fxName), jointName );
+}
+
+
+void hhCenturion::Event_DestroyObstruction() {
+ if( pillarEntity.IsValid() ) {
+ pillarEntity->PostEventMS( &EV_Activate, 0.f, this );
+ }
+}
+
+void hhCenturion::Event_CheckForObstruction( int checkPathToPillar ) {
+ bool obstacle = false;
+ predictedPath_t path;
+ if( enemy.IsValid() ) {
+ idVec3 end = enemy->GetPhysics()->GetOrigin();
+ if ( !checkPathToPillar ) {
+ trace_t tr;
+ idVec3 toPos, eye = GetEyePosition();
+
+ if ( enemy->IsType( idActor::Type ) ) {
+ toPos = ( ( idActor * )enemy.GetEntity() )->GetEyePosition();
+ } else {
+ toPos = enemy->GetPhysics()->GetOrigin();
+ }
+
+ gameLocal.clip.TracePoint( tr, eye, toPos, MASK_SHOT_BOUNDINGBOX, this );
+
+ idEntity *traceEnt = gameLocal.GetTraceEntity( tr );
+ if ( traceEnt && ( tr.fraction < 1.0f || traceEnt != enemy.GetEntity() ) ) {
+ //check to see if the other object is an pillar...
+ if( traceEnt->spawnArgs.GetInt("centurion_pillar", "0") == 1 ) {
+ pillarEntity = traceEnt;
+ obstacle = true;
+ }
+ }
+
+ } else if ( pillarEntity.IsValid() ) {
+ idAI::PredictPath( this, this->aas, physicsObj.GetOrigin(), enemy->GetPhysics()->GetOrigin() - physicsObj.GetOrigin(), 1000, 1000, ( move.moveType == MOVETYPE_FLY ) ? SE_BLOCKED : (SE_ENTER_OBSTACLE | SE_BLOCKED | SE_ENTER_LEDGE_AREA ), path );
+ if( path.endEvent != 0 && path.blockingEntity ) {
+ //check to see if the other object is an pillar...
+ if( path.blockingEntity->spawnArgs.GetInt("centurion_pillar", "0") == 1 ) {
+ //check to see if we can path to the obstacle clearly
+ pillarEntity = path.blockingEntity;
+ //idAI::PredictPath( this, this->aas, physicsObj.GetOrigin(), pillarEntity->GetPhysics()->GetOrigin() - physicsObj.GetOrigin(), 1000, 1000, (SE_ENTER_OBSTACLE | SE_BLOCKED | SE_ENTER_LEDGE_AREA), path );
+ //if( path.endEvent != 0 && path.blockingEntity == pillarEntity.GetEntity() ) {
+ obstacle = true;
+ //}
+ }
+ }
+ }
+ }
+ idThread::ReturnInt( (int)obstacle );
+}
+
+void hhCenturion::Event_CloseToObstruction() {
+ if( physicsObj.GetAbsBounds().IntersectsBounds(pillarEntity->GetPhysics()->GetAbsBounds()) ) {
+ idThread::ReturnInt( 1 );
+ return;
+ }
+ idThread::ReturnInt( 0 );
+}
+
+void hhCenturion::Event_ReachedObstruction() {
+ FaceEntity( pillarEntity.GetEntity() );
+}
+
+void hhCenturion::Event_MoveToObstruction() {
+ idVec3 temp;
+ if ( aas ) {
+ int toAreaNum = PointReachableAreaNum( pillarEntity.GetEntity()->GetOrigin() );
+ temp = pillarEntity.GetEntity()->GetOrigin();
+ aas->PushPointIntoAreaNum( toAreaNum, temp );
+ MoveToPosition( temp );
+ } else {
+ gameLocal.Warning( "Centurion has no aas for MoveToObstruction\n" );
+ }
+}
+
+void hhCenturion::Think() {
+ PROFILE_SCOPE("AI", PROFMASK_NORMAL|PROFMASK_AI);
+ if (ai_skipThink.GetBool()) {
+ return;
+ }
+
+ hhMonsterAI::Think();
+
+ if(ai_debugBrain.GetInteger() > 0 && state) {
+ if ( enemy.IsValid() && enemy->GetHealth() > 0 ) {
+ float dist = ( GetPhysics()->GetOrigin() - enemy->GetPhysics()->GetOrigin() ).LengthFast();
+ gameRenderWorld->DrawText( va("%f", dist), this->GetEyePosition() + idVec3(0.0f, 0.0f, 40.0f), 0.75f, colorYellow, gameLocal.GetLocalPlayer()->viewAngles.ToMat3());
+ }
+ gameRenderWorld->DrawText(state->Name(), this->GetEyePosition() + idVec3(0.0f, 0.0f, 40.0f), 0.75f, colorYellow, gameLocal.GetLocalPlayer()->viewAngles.ToMat3());
+ gameRenderWorld->DrawText(torsoAnim.state, this->GetEyePosition() + idVec3(0.0f, 0.0f, 20.0f), 0.75f, colorYellow, gameLocal.GetLocalPlayer()->viewAngles.ToMat3());
+ gameRenderWorld->DrawText(legsAnim.state, this->GetEyePosition() + idVec3(0.0f, 0.0f, 0.0f), 0.75f, colorYellow, gameLocal.GetLocalPlayer()->viewAngles.ToMat3());
+ }
+}
+
+bool hhCenturion::AttackMelee( const char *meleeDefName ) {
+ const idDict *meleeDef;
+ idActor *enemyEnt = enemy.GetEntity();
+ const char *p;
+ const idSoundShader *shader;
+
+ meleeDef = gameLocal.FindEntityDefDict( meleeDefName, false );
+ if ( !meleeDef ) {
+ gameLocal.Error( "Unknown melee '%s'", meleeDefName );
+ }
+
+ if ( !enemyEnt ) {
+ p = meleeDef->GetString( "snd_miss" );
+ if ( p && *p ) {
+ shader = declManager->FindSound( p );
+ StartSoundShader( shader, SND_CHANNEL_DAMAGE, 0, false, NULL );
+ }
+ return false;
+ }
+
+ // make sure the trace can actually hit the enemy
+ if ( !TestMelee() ) {
+ // missed
+ p = meleeDef->GetString( "snd_miss" );
+ if ( p && *p ) {
+ shader = declManager->FindSound( p );
+ StartSoundShader( shader, SND_CHANNEL_DAMAGE, 0, false, NULL );
+ }
+ return false;
+ }
+
+ //
+ // do the damage
+ //
+ p = meleeDef->GetString( "snd_hit" );
+ if ( p && *p ) {
+ shader = declManager->FindSound( p );
+ StartSoundShader( shader, SND_CHANNEL_DAMAGE, 0, false, NULL );
+ }
+
+ idVec3 kickDir;
+ meleeDef->GetVector( "kickDir", "0 0 0", kickDir );
+
+ idVec3 globalKickDir;
+ globalKickDir = ( viewAxis * physicsObj.GetGravityAxis() ) * kickDir;
+
+ enemyEnt->Damage( this, this, globalKickDir, meleeDefName, 1.0f, INVALID_JOINT );
+ if ( enemyEnt->IsActiveAF() && !enemyEnt->IsType( idPlayer::Type ) ) {
+ PostEventMS( &AI_BackhandImpulse, 0, enemyEnt );
+ }
+
+ lastAttackTime = gameLocal.time;
+
+ return true;
+}
+
+void hhCenturion::Event_BackhandImpulse( idEntity* ent ) {
+ const idDict *meleeDef = gameLocal.FindEntityDefDict( spawnArgs.GetString("def_impulse_damage"), false );
+ if ( !meleeDef || !ent ) {
+ return;
+ }
+ idVec3 kickDir;
+ meleeDef->GetVector( "kickDir", "0 0 0", kickDir );
+ idVec3 globalKickDir = ( viewAxis * physicsObj.GetGravityAxis() ) * kickDir;
+ globalKickDir *= ent->spawnArgs.GetFloat( "kick_scale", "1.0" );
+ ent->ApplyImpulse( this, 0, ent->GetOrigin(), meleeDef->GetFloat( "push_ragdoll" ) * globalKickDir );
+}
+
+/*
+=====================
+hhCenturion::Save
+=====================
+*/
+void hhCenturion::Save( idSaveGame *savefile ) const {
+ armchop_Target.Save( savefile );
+ pillarEntity.Save( savefile );
+}
+
+/*
+=====================
+hhCenturion::Restore
+=====================
+*/
+void hhCenturion::Restore( idRestoreGame *savefile ) {
+ armchop_Target.Restore( savefile );
+ pillarEntity.Restore( savefile );
+
+ // Restore the obstacle list
+ const idKeyValue *kv = spawnArgs.MatchPrefix( "aasObstacle" );
+ idEntityPtr ent;
+ while ( kv ) {
+ ent = gameLocal.FindEntity( kv->GetValue() );
+ if ( ent.IsValid() ) {
+ obstacles.AddUnique( ent );
+ }
+ kv = spawnArgs.MatchPrefix( "aasObstacle", kv );
+ }
+}
+
+void hhCenturion::Event_EnemyCloseToObstruction(void) {
+ bool close = false;
+ if ( pillarEntity.IsValid() && enemy.IsValid() ) {
+ float obs_dist = ( pillarEntity->GetOrigin() - GetOrigin() ).Length();
+ float dist = ( enemy->GetOrigin() - GetOrigin() ).Length();
+
+ // If the enemy is further from me than the pillar...
+ if ( dist > obs_dist) {
+ dist = ( pillarEntity->GetOrigin() - enemy->GetOrigin() ).Length();
+ // If the enemy is less than 400 units from the pillar, then he's 'close'
+ if ( dist < 400.0f ) {
+ close = true;
+ }
+ }
+ }
+ idThread::ReturnInt( (int) close );
+}
+
+void hhCenturion::AimedAttackMissile( const char *jointname, const idDict *projDef) {
+ idProjectile *proj;
+ idVec3 target, origin = GetOrigin();
+ bool inShuttle = false;
+
+ if ( shootTarget.IsValid() ) {
+ target = shootTarget->GetOrigin();
+ } else if ( enemy.IsValid() ) {
+ target = enemy->GetOrigin();
+ if ( enemy->IsType( idActor::Type ) ) {
+ target.z += enemy->EyeHeight() / 4.0f;
+ }
+ } else {
+ // No target? Do the default attack
+ Event_AttackMissile( jointname, projDef, 1 );
+ return;
+ }
+
+ // If target is too close do a non-aimed attack
+ if ( fabsf( origin.x - target.x ) < 256 &&
+ fabsf( origin.y - target.y ) < 256 ) {
+ Event_AttackMissile( jointname, projDef, 1 );
+ return;
+ }
+
+ idVec3 dist = origin - target;
+
+ if ( shootTarget.IsValid() ) {
+ proj = LaunchProjectile( jointname, shootTarget.GetEntity(), true, projDef );
+ } else {
+ proj = LaunchProjectile( jointname, enemy.GetEntity(), true, projDef );
+ }
+}
+
+void hhCenturion::Event_TakeDamage(int takeDamage) {
+ // Set the takedamage flag
+ fl.takedamage = (takeDamage != 0);
+}
+
+void hhCenturion::Event_FindNearbyEnemy( float distance ) {
+ // Search for the monster nearest to us
+ idAI *nearest = NULL;
+ float dist, nearDist = idMath::INFINITY;
+ idAI *ai = reinterpret_cast (gameLocal.FindEntityOfType( idAI::Type, NULL ));
+ while (ai) {
+ if (ai != this) { // Don't target yourself
+ dist = (ai->GetOrigin() - GetOrigin()).Length();
+ if (dist < nearDist) {
+ nearDist = dist;
+ nearest = ai;
+ }
+ }
+ ai = reinterpret_cast (gameLocal.FindEntityOfType( idAI::Type, ai ));
+ }
+
+ // If we found one near us and it's near enough, return it
+ if (nearest && nearDist < distance) {
+ idThread::ReturnEntity(nearest);
+ } else {
+ idThread::ReturnEntity(NULL);
+ }
+}
+
+void hhCenturion::Event_Touch( idEntity *other, trace_t *trace ) {
+ if ( (!enemy.GetEntity() || other->IsType( hhPlayer::Type )) && !other->fl.notarget && ( ReactionTo( other ) & ATTACK_ON_ACTIVATE ) ) {
+ Activate( other );
+ SetEnemy( static_cast ( other ) );
+ }
+ AI_PUSHED = true;
+}
+
+#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build
\ No newline at end of file
diff --git a/neo/prey/Prey/ai_centurion.h b/neo/prey/Prey/ai_centurion.h
new file mode 100644
index 00000000..3175c9f8
--- /dev/null
+++ b/neo/prey/Prey/ai_centurion.h
@@ -0,0 +1,84 @@
+#ifndef __PREY_AI_CENTURION_H__
+#define __PREY_AI_CENTURION_H__
+
+/***********************************************************************
+ hhCenturion.
+ Centurion AI.
+***********************************************************************/
+class hhCenturion : public hhMonsterAI {
+
+public:
+ CLASS_PROTOTYPE(hhCenturion);
+
+#ifdef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build
+ void Event_CenturionLaunch( const idList* parmList ) {};
+ void Event_ScriptedRoar() {};
+ void Event_ScriptedArmChop() {};
+ void Event_CenturionLooseArm() {};
+ void Event_ForceFieldNotify() {};
+ void Event_ForceFieldToggle( int toggle ) {};
+ void Event_PlayerInTunnel( int toggle, idEntity* ent ) {};
+ void Event_ReachedTunnel() {};
+ void Event_MoveToTunnel() {};
+
+ bool AttackMelee( const char *meleeDefName ) { return true; };
+ void Event_CheckForObstruction( int checkPathToPillar ) {};
+ void Event_MoveToObstruction() {};
+ void Event_DestroyObstruction() {};
+ void Event_ReachedObstruction() {};
+ void Event_CloseToObstruction() {};
+ void Event_EnemyCloseToObstruction() {};
+ void Event_BackhandImpulse( idEntity* ent ) {};
+ virtual void Event_PostSpawn( void ) {};
+ void Event_TakeDamage( int takeDamage ) {};
+ void Event_FindNearbyEnemy( float distance ) {};
+ virtual void Event_Touch( idEntity *other, trace_t *trace ) {};
+#else
+
+public:
+ void Spawn();
+ void Think();
+ void Save( idSaveGame *savefile ) const;
+ void Restore( idRestoreGame *savefile );
+protected:
+ void Event_CenturionLaunch( const idList* parmList );
+ void Event_ScriptedRoar();
+ void Event_ScriptedArmChop();
+ void Event_CenturionLooseArm();
+ void Event_ForceFieldNotify();
+ void Event_ForceFieldToggle( int toggle );
+ void Event_PlayerInTunnel( int toggle, idEntity* ent );
+ void Event_ReachedTunnel();
+ void Event_MoveToTunnel();
+
+ void LinkScriptVariables( void );
+ bool AttackMelee( const char *meleeDefName );
+ void Event_CheckForObstruction( int checkPathToPillar );
+ void Event_MoveToObstruction();
+ void Event_DestroyObstruction();
+ void Event_ReachedObstruction();
+ void Event_CloseToObstruction();
+ void Event_EnemyCloseToObstruction();
+ void Event_BackhandImpulse( idEntity* ent );
+ virtual void Event_PostSpawn( void );
+ void Event_TakeDamage( int takeDamage );
+ void Event_FindNearbyEnemy( float distance );
+ virtual void Event_Touch( idEntity *other, trace_t *trace );
+
+ void AimedAttackMissile( const char *jointname, const idDict *projDef );
+
+ idEntityPtr armchop_Target;
+ idEntityPtr pillarEntity;
+
+ idScriptBool AI_CENTURION_ARM_MISSING;
+ idScriptBool AI_CENTURION_REQUIRE_ROAR;
+ idScriptBool AI_CENTURION_ARM_TUNNEL;
+ idScriptBool AI_CENTURION_FORCEFIELD_WAIT;
+ idScriptFloat AI_CENTURION_SCRIPTED_ROAR;
+ idScriptFloat AI_CENTURION_SCRIPTED_TUNNEL;
+
+ idList< idEntityPtr > obstacles;
+#endif
+};
+
+#endif //__PREY_AI_CENTURION_H__
\ No newline at end of file
diff --git a/neo/prey/Prey/ai_crawler.cpp b/neo/prey/Prey/ai_crawler.cpp
new file mode 100644
index 00000000..c6df719f
--- /dev/null
+++ b/neo/prey/Prey/ai_crawler.cpp
@@ -0,0 +1,115 @@
+#include "precompiled.h"
+#pragma hdrstop
+
+#include "prey_local.h"
+
+
+CLASS_DECLARATION( hhMonsterAI, hhCrawler )
+ EVENT( EV_Touch, hhCrawler::Event_Touch )
+END_CLASS
+
+
+void hhCrawler::Spawn() {
+ if( spawnArgs.FindKey("def_pickup") ) {
+ if ( gameLocal.isMultiplayer ) {
+ GetPhysics()->SetContents( CONTENTS_TRIGGER | CONTENTS_MONSTERCLIP );
+ } else {
+ GetPhysics()->SetContents( CONTENTS_TRIGGER );
+ }
+ }
+}
+
+void hhCrawler::Event_Touch( idEntity *other, trace_t *trace ) {
+ hhPlayer *player;
+
+ if( !spawnArgs.FindKey("def_pickup") || !other->IsType(idPlayer::Type) ) {
+ return;
+ }
+
+ player = static_cast(other);
+
+ Pickup( player );
+}
+
+void hhCrawler::Pickup( hhPlayer *player ) {
+
+ if( !GiveToPlayer( player ) ) {
+ return;
+ }
+
+ // play pickup sound
+ StartSoundShader( refSound.shader, SND_CHANNEL_ITEM, false ); // play what's defined in the entity
+
+ // trigger our targets
+ ActivateTargets( player );
+
+ // clear our contents so the object isn't picked up twice
+ GetPhysics()->SetContents( 0 );
+
+ // hide the model
+ Hide();
+
+ if (player->hud) {
+ player->hud->SetStateInt("item", 1);
+ player->hud->SetStateString("itemtext", spawnArgs.GetString("inv_name"));
+ player->hud->SetStateString("itemicon", spawnArgs.GetString("inv_icon"));
+ }
+
+ idStr str;
+ spawnArgs.GetString("inv_name", "Item", str);
+
+ if (player == gameLocal.GetClientByNum(gameLocal.localClientNum)) {
+ gameLocal.Printf("Picked up a %s\n", str.c_str());
+ }
+
+
+ PostEventMS( &EV_Remove, 2000 );
+}
+
+bool hhCrawler::GiveToPlayer( hhPlayer* player ) {
+ const char *pickupName = spawnArgs.GetString("def_pickup", NULL);
+ bool pickedUp = false;
+
+ if ( player && pickupName ) {
+ idEntity *ent = gameLocal.SpawnObject(pickupName, NULL);
+ if (ent->IsType(hhItem::Type)) {
+ pickedUp = player->GiveItem( static_cast(ent) );
+ }
+ ent->Hide();
+ ent->PostEventMS(&EV_Remove, 2000);
+ }
+
+ return pickedUp;
+}
+
+void hhCrawler::Think( void ) {
+ PROFILE_SCOPE("AI", PROFMASK_NORMAL|PROFMASK_AI);
+ if (ai_skipThink.GetBool()) { //HUMANHEAD rww
+ return;
+ }
+
+ if ( thinkFlags & TH_THINK ) {
+ current_yaw += deltaViewAngles.yaw;
+ ideal_yaw = idMath::AngleNormalize180( ideal_yaw + deltaViewAngles.yaw );
+ deltaViewAngles.Zero();
+ viewAxis = idAngles( 0, current_yaw, 0 ).ToMat3();
+
+ // HUMANHEAD NLA
+ physicsObj.ResetNumTouchEnt(0);
+ // HUMANHEAD END
+ // animation based movement
+ UpdateAIScript();
+ AnimMove();
+ } else if ( thinkFlags & TH_PHYSICS ) {
+ RunPhysics();
+ }
+
+ UpdateAnimation();
+ Present();
+ LinkCombat();
+}
+
+bool hhCrawler::UpdateAnimationControllers() {
+ //do nothing
+ return false;
+}
\ No newline at end of file
diff --git a/neo/prey/Prey/ai_crawler.h b/neo/prey/Prey/ai_crawler.h
new file mode 100644
index 00000000..d3dbaec8
--- /dev/null
+++ b/neo/prey/Prey/ai_crawler.h
@@ -0,0 +1,24 @@
+#ifndef __PREY_AI_CRAWLER_H__
+#define __PREY_AI_CRAWLER_H__
+
+/***********************************************************************
+ hhCrawler.
+ Crawler AI.
+***********************************************************************/
+class hhCrawler : public hhMonsterAI {
+
+public:
+ CLASS_PROTOTYPE(hhCrawler);
+
+public:
+ void Spawn();
+
+protected:
+ void Event_Touch( idEntity *other, trace_t *trace );
+ void Pickup( hhPlayer *player );
+ bool GiveToPlayer( hhPlayer* player );
+ void Think();
+ bool UpdateAnimationControllers();
+};
+
+#endif //__PREY_AI_CRAWLER_H__
\ No newline at end of file
diff --git a/neo/prey/Prey/ai_creaturex.cpp b/neo/prey/Prey/ai_creaturex.cpp
new file mode 100644
index 00000000..6863ea90
--- /dev/null
+++ b/neo/prey/Prey/ai_creaturex.cpp
@@ -0,0 +1,1669 @@
+
+#include "precompiled.h"
+#pragma hdrstop
+
+#include "prey_local.h"
+
+const idEventDef CX_LaserOn("sunBeamOn", NULL, 'd');
+const idEventDef CX_LaserOff("sunBeamOff");
+const idEventDef MA_UpdateLasers("");
+const idEventDef MA_AssignLeftMuzzleFx( "", "e" );
+const idEventDef MA_AssignRightMuzzleFx( "", "e" );
+const idEventDef MA_AssignLeftImpactFx( "", "e" );
+const idEventDef MA_AssignRightImpactFx( "", "e" );
+const idEventDef MA_AssignLeftRechargeFx( "", "e" );
+const idEventDef MA_AssignRightRechargeFx( "", "e" );
+const idEventDef MA_SetGunOffset( "setGunOffset", "v" );
+const idEventDef MA_EndLeftBeams( "" );
+const idEventDef MA_EndRightBeams( "" );
+const idEventDef MA_HudEvent( "hudEvent", "s" );
+const idEventDef MA_GunRecharge( "gunRecharge", "d" );
+const idEventDef MA_EndRecharge( "endRecharge" );
+const idEventDef MA_ResetRechargeBeam( "" );
+const idEventDef MA_SparkLeft( "" );
+const idEventDef MA_SparkRight( "" );
+const idEventDef MA_LeftGunDeath( "" );
+const idEventDef MA_RightGunDeath( "" );
+const idEventDef MA_StartRechargeBeams( "" );
+
+CLASS_DECLARATION(hhMonsterAI, hhCreatureX)
+ EVENT(CX_LaserOn, hhCreatureX::Event_LaserOn )
+ EVENT(CX_LaserOff, hhCreatureX::Event_LaserOff )
+ EVENT(MA_UpdateLasers, hhCreatureX::Event_UpdateLasers )
+ EVENT(MA_AssignRightMuzzleFx, hhCreatureX::Event_AssignRightMuzzleFx )
+ EVENT(MA_AssignLeftMuzzleFx, hhCreatureX::Event_AssignLeftMuzzleFx )
+ EVENT(MA_AssignRightImpactFx, hhCreatureX::Event_AssignRightImpactFx )
+ EVENT(MA_AssignLeftImpactFx, hhCreatureX::Event_AssignLeftImpactFx )
+ EVENT(MA_SetGunOffset, hhCreatureX::Event_SetGunOffset )
+ EVENT(MA_EndLeftBeams, hhCreatureX::Event_EndLeftBeams )
+ EVENT(MA_EndRightBeams, hhCreatureX::Event_EndRightBeams )
+ EVENT(MA_HudEvent, hhCreatureX::Event_HudEvent )
+ EVENT(MA_GunRecharge, hhCreatureX::Event_GunRecharge )
+ EVENT(MA_EndRecharge, hhCreatureX::Event_EndRecharge )
+ EVENT(MA_ResetRechargeBeam, hhCreatureX::Event_ResetRechargeBeam )
+ EVENT(MA_SparkLeft, hhCreatureX::Event_SparkLeft )
+ EVENT(MA_SparkRight, hhCreatureX::Event_SparkRight )
+ EVENT(MA_LeftGunDeath, hhCreatureX::Event_LeftGunDeath )
+ EVENT(MA_RightGunDeath, hhCreatureX::Event_RightGunDeath )
+ EVENT(MA_StartRechargeBeams, hhCreatureX::Event_StartRechargeBeams )
+END_CLASS
+
+#ifndef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build
+void hhCreatureX::Spawn() {
+ idVec3 boneOrigin;
+ idMat3 boneAxis;
+ targetStart_L = vec3_zero;
+ targetStart_R = vec3_zero;
+ targetEnd_L = vec3_zero;
+ targetEnd_R = vec3_zero;
+ targetCurrent_L = vec3_zero;
+ targetCurrent_R = vec3_zero;
+ bLaserLeftActive = false;
+ bLaserRightActive = false;
+ nextBeamTime = 0;
+ nextLeftZapTime = 0;
+ nextRightZapTime = 0;
+ nextLaserLeft = 0;
+ nextLaserRight = 0;
+ nextHealthTick = 0;
+ rightGunLives = spawnArgs.GetInt( "gun_lives", "3" );
+ leftGunLives = spawnArgs.GetInt( "gun_lives", "3" );
+ rightGunHealth = spawnArgs.GetInt( "gun_health", "20" );
+ leftGunHealth = spawnArgs.GetInt( "gun_health", "20" );
+ bScripted = spawnArgs.GetBool( "scripted", "0" );
+ numBurstBeams = 0;
+
+ if ( bScripted ) {
+ //don't spawn any combat related entities if scripted
+ return;
+ }
+
+ laserRight = hhBeamSystem::SpawnBeam( vec3_origin, spawnArgs.GetString( "def_beamLaser" ) );
+ if( laserRight.IsValid() ) {
+ GetJointWorldTransform( spawnArgs.GetString("laser_bone_right"), boneOrigin, boneAxis );
+
+ idVec3 junkOrigin = boneOrigin + viewAxis[0] * 60; //Test for bad origin
+ HH_ASSERT(!FLOAT_IS_NAN(junkOrigin.x)); //Test for bad origin
+ HH_ASSERT(!FLOAT_IS_NAN(junkOrigin.y)); //Test for bad origin
+ HH_ASSERT(!FLOAT_IS_NAN(junkOrigin.z)); //Test for bad origin
+
+ laserRight->SetOrigin( boneOrigin + viewAxis[0] * 60 );
+ laserRight->Activate( false );
+ }
+ laserLeft = hhBeamSystem::SpawnBeam( vec3_origin, spawnArgs.GetString( "def_beamLaser" ) );
+ if( laserLeft.IsValid() ) {
+ GetJointWorldTransform( spawnArgs.GetString("laser_bone_left"), boneOrigin, boneAxis );
+
+ idVec3 junkOrigin = boneOrigin + viewAxis[0] * 60; //Test for bad origin
+ HH_ASSERT(!FLOAT_IS_NAN(junkOrigin.x)); //Test for bad origin
+ HH_ASSERT(!FLOAT_IS_NAN(junkOrigin.y)); //Test for bad origin
+ HH_ASSERT(!FLOAT_IS_NAN(junkOrigin.z)); //Test for bad origin
+
+ laserLeft->SetOrigin( boneOrigin + viewAxis[0] * 60 );
+ laserLeft->Activate( false );
+ }
+ preLaserRight = hhBeamSystem::SpawnBeam( vec3_origin, spawnArgs.GetString( "def_beamLaser" ) );
+ if( preLaserRight.IsValid() ) {
+ GetJointWorldTransform( spawnArgs.GetString("laser_bone_right"), boneOrigin, boneAxis );
+
+ idVec3 junkOrigin = boneOrigin; //Test for bad origin
+ HH_ASSERT(!FLOAT_IS_NAN(junkOrigin.x)); //Test for bad origin
+ HH_ASSERT(!FLOAT_IS_NAN(junkOrigin.y)); //Test for bad origin
+ HH_ASSERT(!FLOAT_IS_NAN(junkOrigin.z)); //Test for bad origin
+
+ preLaserRight->SetOrigin( boneOrigin );
+ preLaserRight->BindToJoint( this, spawnArgs.GetString("laser_bone_right"), false );
+ preLaserRight->Activate( false );
+ }
+ preLaserLeft = hhBeamSystem::SpawnBeam( vec3_origin, spawnArgs.GetString( "def_beamLaser" ) );
+ if( preLaserLeft.IsValid() ) {
+ GetJointWorldTransform( spawnArgs.GetString("laser_bone_left"), boneOrigin, boneAxis );
+
+ idVec3 junkOrigin = boneOrigin; //Test for bad origin
+ HH_ASSERT(!FLOAT_IS_NAN(junkOrigin.x)); //Test for bad origin
+ HH_ASSERT(!FLOAT_IS_NAN(junkOrigin.y)); //Test for bad origin
+ HH_ASSERT(!FLOAT_IS_NAN(junkOrigin.z)); //Test for bad origin
+
+ preLaserLeft->SetOrigin( boneOrigin );
+ preLaserLeft->BindToJoint( this, spawnArgs.GetString("laser_bone_left"), false );
+ preLaserLeft->Activate( false );
+ }
+ PostEventSec( &MA_UpdateLasers, spawnArgs.GetFloat( "laser_freq", "0.1" ) );
+
+ leftBeamList.Clear();
+ rightBeamList.Clear();
+ leftRechargeBeam.Clear();
+ rightRechargeBeam.Clear();
+ leftRecharger.Clear();
+ rightRecharger.Clear();
+ if ( spawnArgs.GetBool( "use_recharge" ) ) {
+ numBurstBeams = spawnArgs.GetInt( "num_burst_beams", "4" );
+ for ( int i = 0; i < numBurstBeams; i ++ ) {
+ leftBeamList.Append( hhBeamSystem::SpawnBeam( vec3_origin, spawnArgs.GetString( "def_beamBurst" ) ) );
+ if( leftBeamList[i].IsValid() ) {
+ leftBeamList[i]->Activate( false );
+ GetJointWorldTransform( spawnArgs.GetString("damage_bone_left"), boneOrigin, boneAxis );
+ leftBeamList[i]->SetOrigin( boneOrigin );
+ leftBeamList[i]->BindToJoint( this, spawnArgs.GetString("damage_bone_left"), false );
+ }
+ rightBeamList.Append( hhBeamSystem::SpawnBeam( vec3_origin, spawnArgs.GetString( "def_beamBurst" ) ) );
+ if( rightBeamList[i].IsValid() ) {
+ rightBeamList[i]->Activate( false );
+ GetJointWorldTransform( spawnArgs.GetString("damage_bone_right"), boneOrigin, boneAxis );
+ rightBeamList[i]->SetOrigin( boneOrigin );
+ rightBeamList[i]->BindToJoint( this, spawnArgs.GetString("damage_bone_right"), false );
+ }
+ }
+
+ leftRechargeBeam = hhBeamSystem::SpawnBeam( vec3_origin, spawnArgs.GetString( "def_beamRecharge" ) );
+ if( leftRechargeBeam.IsValid() ) {
+ leftRechargeBeam->Activate( false );
+ GetJointWorldTransform( spawnArgs.GetString("laser_bone_left"), boneOrigin, boneAxis );
+ leftRechargeBeam->SetOrigin( boneOrigin );
+ leftRechargeBeam->BindToJoint( this, spawnArgs.GetString("laser_bone_left"), false );
+ leftRechargeBeam->Hide();
+ }
+ rightRechargeBeam = hhBeamSystem::SpawnBeam( vec3_origin, spawnArgs.GetString( "def_beamRecharge" ) );
+ if( rightRechargeBeam.IsValid() ) {
+ rightRechargeBeam->Activate( false );
+ GetJointWorldTransform( spawnArgs.GetString("laser_bone_right"), boneOrigin, boneAxis );
+ rightRechargeBeam->SetOrigin( boneOrigin );
+ rightRechargeBeam->BindToJoint( this, spawnArgs.GetString("laser_bone_right"), false );
+ rightRechargeBeam->Hide();
+ }
+ leftDamageBeam = hhBeamSystem::SpawnBeam( vec3_origin, spawnArgs.GetString( "def_beamDamage" ) );
+ if( leftDamageBeam.IsValid() ) {
+ leftDamageBeam->Activate( false );
+ GetJointWorldTransform( spawnArgs.GetString("laser_bone_left"), boneOrigin, boneAxis );
+ leftDamageBeam->SetOrigin( boneOrigin );
+ leftDamageBeam->BindToJoint( this, spawnArgs.GetString("laser_bone_left"), false );
+ leftDamageBeam->Hide();
+ }
+ rightDamageBeam = hhBeamSystem::SpawnBeam( vec3_origin, spawnArgs.GetString( "def_beamDamage" ) );
+ if( rightDamageBeam.IsValid() ) {
+ rightDamageBeam->Activate( false );
+ GetJointWorldTransform( spawnArgs.GetString("laser_bone_right"), boneOrigin, boneAxis );
+ rightDamageBeam->SetOrigin( boneOrigin );
+ rightDamageBeam->BindToJoint( this, spawnArgs.GetString("laser_bone_right"), false );
+ rightDamageBeam->Hide();
+ }
+ leftRetractBeam = hhBeamSystem::SpawnBeam( vec3_origin, spawnArgs.GetString( "def_beamRetract" ) );
+ if( leftRetractBeam.IsValid() ) {
+ leftRetractBeam->Activate( false );
+ GetJointWorldTransform( spawnArgs.GetString("laser_bone_left"), boneOrigin, boneAxis );
+ leftRetractBeam->SetOrigin( boneOrigin );
+ leftRetractBeam->BindToJoint( this, spawnArgs.GetString("laser_bone_left"), false );
+ leftRetractBeam->Hide();
+ }
+ rightRetractBeam = hhBeamSystem::SpawnBeam( vec3_origin, spawnArgs.GetString( "def_beamRetract" ) );
+ if( rightRetractBeam.IsValid() ) {
+ rightRetractBeam->Activate( false );
+ GetJointWorldTransform( spawnArgs.GetString("laser_bone_right"), boneOrigin, boneAxis );
+ rightRetractBeam->SetOrigin( boneOrigin );
+ rightRetractBeam->BindToJoint( this, spawnArgs.GetString("laser_bone_right"), false );
+ rightRetractBeam->Hide();
+ }
+
+ idEntity *ent;
+ idDict dict;
+ const idDict *rechargeDict = gameLocal.FindEntityDefDict(spawnArgs.GetString("def_recharger"));
+ if ( !rechargeDict ) {
+ gameLocal.Error( "Unknown def_recharger: %s\n", spawnArgs.GetString("def_recharger") );
+ }
+ dict.Copy(*rechargeDict);
+ gameLocal.SpawnEntityDef( dict, &ent );
+ if ( ent ) {
+ leftRecharger.Assign( ent );
+ leftRecharger->GetPhysics()->DisableClip();
+ leftRecharger->GetPhysics()->SetClipMask( 0 );
+ leftRecharger->HideNoDormant();
+ leftRecharger->SetOrigin( GetOrigin() );
+ leftRecharger->SetEnemy( this );
+ leftRecharger->spawnArgs.Set( "left_recharger", "1" ); //set key for left or right
+ }
+ ent = NULL;
+ gameLocal.SpawnEntityDef( dict, &ent );
+ if ( ent ) {
+ rightRecharger.Assign( ent );
+ rightRecharger->GetPhysics()->DisableClip();
+ rightRecharger->GetPhysics()->SetClipMask( 0 );
+ rightRecharger->HideNoDormant();
+ rightRecharger->SetOrigin( GetOrigin() );
+ rightRecharger->SetEnemy( this );
+ rightRecharger->spawnArgs.Set( "right_recharger", "1" ); //set key for left or right
+ }
+ }
+}
+
+hhCreatureX::~hhCreatureX() {
+ MuzzleLeftOff();
+ MuzzleRightOff();
+
+ SAFE_REMOVE( laserRight );
+ SAFE_REMOVE( laserLeft );
+ SAFE_REMOVE( preLaserLeft );
+ SAFE_REMOVE( preLaserRight );
+}
+
+void hhCreatureX::Event_LaserOn() {
+ if ( !enemy.IsValid() ) {
+ idThread::ReturnInt( false );
+ return;
+ }
+ if ( !AI_RIGHT_DAMAGED ) {
+ bLaserRightActive = true;
+ }
+ if ( !AI_LEFT_DAMAGED ) {
+ bLaserLeftActive = true;
+ }
+ idVec3 toEnemy = GetEnemy()->GetOrigin() - GetOrigin();
+ targetStart_L = GetOrigin() + spawnArgs.GetFloat( "test_1", "0.5" ) * toEnemy;
+ targetStart_L.z = GetOrigin().z;
+ float distToStart = (targetStart_L - GetOrigin()).LengthFast();
+ if ( distToStart < 200 ) {
+ targetStart_L = GetOrigin() + toEnemy;
+ targetStart_L.z = GetOrigin().z;
+ }
+ targetStart_R = GetOrigin() + spawnArgs.GetFloat( "test_1", "0.5" ) * toEnemy;
+ targetStart_R.z = GetOrigin().z;
+ distToStart = (targetStart_R - GetOrigin()).LengthFast();
+ if ( distToStart < 200 ) {
+ targetStart_R = GetOrigin() + toEnemy;
+ targetStart_R.z = GetOrigin().z;
+ }
+ targetEnd_L = GetOrigin() + spawnArgs.GetFloat( "test_2", "1.4" ) * toEnemy;
+ targetEnd_L.z = GetOrigin().z + spawnArgs.GetFloat( "test_4", "50" );
+ targetEnd_R = GetOrigin() + spawnArgs.GetFloat( "test_2", "1.4" ) * toEnemy;
+ targetEnd_R.z = GetOrigin().z + spawnArgs.GetFloat( "test_4", "50" );
+ if ( ai_debugBrain.GetBool() ) {
+ gameRenderWorld->DebugArrow( colorRed, targetStart_L, targetEnd_L, 10, 10000 );
+ }
+ targetAlpha_L = 0.0f;
+ targetAlpha_R = 0.0f;
+ if ( !bLaserLeftActive && !bLaserRightActive ) {
+ idThread::ReturnInt( false );
+ }
+ idThread::ReturnInt( true );
+}
+
+void hhCreatureX::Event_LaserOff() {
+ bLaserLeftActive = false;
+ bLaserRightActive = false;
+ targetStart_L = vec3_zero;
+ targetStart_R = vec3_zero;
+ targetEnd_L = vec3_zero;
+ targetEnd_R = vec3_zero;
+ MuzzleLeftOff();
+ MuzzleRightOff();
+}
+
+void hhCreatureX::Event_UpdateLasers() {
+ if ( !enemy.IsValid() ) {
+ PostEventSec( &MA_UpdateLasers, spawnArgs.GetFloat( "laser_freq", "0.1" ) );
+ return;
+ }
+ trace_t trace;
+ float dist = 0;
+ idVec3 boneOrigin, traceEnd;
+ idMat3 boneAxis;
+ idEntity *hitEntity = NULL;
+ int hitCount = 0;
+ if ( laserRight.IsValid() && bLaserRightActive ) {
+ MuzzleRightOn();
+ traceEnd = targetCurrent_R + 2000 * (targetCurrent_R - laserRight->GetOrigin()).ToNormal();
+ GetJointWorldTransform( spawnArgs.GetString("laser_bone_right"), boneOrigin, boneAxis );
+ gameLocal.clip.TracePoint( trace, laserRight->GetOrigin(), traceEnd, MASK_SHOT_RENDERMODEL, this );
+ if ( ai_debugBrain.GetBool() ) {
+ gameRenderWorld->DebugArrow( colorRed, laserRight->GetOrigin(), traceEnd, 10, 200 );
+ }
+ if ( trace.fraction < 1.0f ) {
+ dist = (trace.endpos - laserRight->GetOrigin()).Length();
+ hitEntity = gameLocal.GetTraceEntity( trace );
+ if ( hitEntity ) {
+ hitEntity->Damage( this, this, hitEntity->GetOrigin() - GetOrigin().ToNormal(), spawnArgs.GetString( "def_laserDamage" ), 1.0, 0 );
+ }
+ }
+ } else {
+ MuzzleRightOff();
+ }
+ if ( laserLeft.IsValid() && bLaserLeftActive ) {
+ MuzzleLeftOn();
+ traceEnd = targetCurrent_L + 2000 * (targetCurrent_L - laserLeft->GetOrigin()).ToNormal();
+ GetJointWorldTransform( spawnArgs.GetString("laser_bone_left"), boneOrigin, boneAxis );
+ gameLocal.clip.TracePoint( trace, laserLeft->GetOrigin(), traceEnd, MASK_SHOT_RENDERMODEL, this );
+ if ( ai_debugBrain.GetBool() ) {
+ gameRenderWorld->DebugArrow( colorRed, laserLeft->GetOrigin(), traceEnd, 10, 200 );
+ }
+ if ( trace.fraction < 1.0f ) {
+ dist = (trace.endpos - laserLeft->GetOrigin()).Length();
+ hitEntity = gameLocal.GetTraceEntity( trace );
+ if ( hitEntity ) {
+ hitEntity->Damage( this, this, hitEntity->GetOrigin() - GetOrigin().ToNormal(), spawnArgs.GetString( "def_laserDamage" ), 1.0, 0 );
+ }
+ }
+ } else {
+ MuzzleLeftOff();
+ }
+ PostEventSec( &MA_UpdateLasers, spawnArgs.GetFloat( "laser_freq", "0.1" ) );
+}
+
+void hhCreatureX::Event_ResetRechargeBeam() {
+ if ( !AI_RECHARGING ) {
+ return;
+ }
+ if ( leftRecharger.IsValid() ) {
+ leftRecharger->SetShaderParm( 6, 0.0f );
+ }
+ if ( rightRecharger.IsValid() ) {
+ rightRecharger->SetShaderParm( 6, 0.0f );
+ }
+ if ( rechargeLeftFx.IsValid() ) {
+ rechargeLeftFx->SetParticleShaderParm( 6, 0.0f );
+ }
+ if ( rechargeRightFx.IsValid() ) {
+ rechargeRightFx->SetParticleShaderParm( 6, 0.0f );
+ }
+ if ( leftRechargeBeam.IsValid() ) {
+ leftRechargeBeam->Activate( true );
+ leftRechargeBeam->Show();
+ }
+ if ( rightRechargeBeam.IsValid() ) {
+ rightRechargeBeam->Activate( true );
+ rightRechargeBeam->Show();
+ }
+ if ( leftDamageBeam.IsValid() ) {
+ leftDamageBeam->Activate( false );
+ }
+ if ( rightDamageBeam.IsValid() ) {
+ rightDamageBeam->Activate( false );
+ }
+ if ( leftRetractBeam.IsValid() ) {
+ leftRetractBeam->Activate( false );
+ }
+ if ( rightRetractBeam.IsValid() ) {
+ rightRetractBeam->Activate( false );
+ }
+}
+
+void hhCreatureX::Think() {
+ PROFILE_SCOPE("AI", PROFMASK_NORMAL|PROFMASK_AI);
+ if (ai_skipThink.GetBool()) {
+ return;
+ }
+
+ idVec3 pastEnemy;
+ hhMonsterAI::Think();
+ if ( AI_RECHARGING ) {
+ bool damaged = false;
+ if ( leftRecharger.IsValid() ) {
+ //recharger damage. switch out beams
+ if ( leftRechargeBeam.IsValid() && leftRecharger->GetHealth() < leftRechargerHealth ) {
+ if ( leftRechargerHealth > 0 && !leftRecharger->IsDead() ) {
+ damaged = true;
+ }
+ leftRechargeBeam->Activate( false );
+ leftDamageBeam->Show();
+ leftDamageBeam->Activate( true );
+ leftRecharger->SetShaderParm( 6, 1.0f );
+ if ( rechargeLeftFx.IsValid() ) {
+ rechargeLeftFx->SetParticleShaderParm( 6, 1.0f );
+ }
+ }
+ leftRechargerHealth = leftRecharger->GetHealth();
+ }
+ if ( rightRecharger.IsValid() ) {
+ //recharger damage. switch out beams
+ if ( rightRechargeBeam.IsValid() && rightRecharger->GetHealth() < rightRechargerHealth ) {
+ if ( rightRechargerHealth > 0 && !rightRecharger->IsDead() ) {
+ damaged = true;
+ }
+ rightRechargeBeam->Activate( false );
+ rightDamageBeam->Show();
+ rightDamageBeam->Activate( true );
+ rightRecharger->SetShaderParm( 6, 1.0f );
+ if ( rechargeRightFx.IsValid() ) {
+ rechargeRightFx->SetParticleShaderParm( 6, 1.0f );
+ }
+ }
+ rightRechargerHealth = rightRecharger->GetHealth();
+ }
+ if ( damaged ) {
+ PostEventSec( &MA_ResetRechargeBeam, spawnArgs.GetFloat( "damage_beam_duration", "0.8" ) );
+ }
+ if ( !AI_LEFT_DAMAGED && leftRecharger.IsValid() && leftRecharger->GetHealth() <= 0 ) {
+ //recharger has died so start retracting the beams and schedule the gun's death
+ StartSound( "snd_gun_predeath", SND_CHANNEL_ANY );
+ SAFE_REMOVE( leftRechargeBeam );
+ PostEventSec( &MA_LeftGunDeath, spawnArgs.GetFloat("retract_delay", "0.9") );
+ const char *defName = spawnArgs.GetString( "fx_retract" );
+ if ( defName && defName[0] && leftDamageBeam.IsValid() ) {
+ hhFxInfo fxInfo;
+ fxInfo.RemoveWhenDone( true );
+ retractLeftFx = SpawnFxLocal( defName, leftDamageBeam->GetTargetLocation(), mat3_identity, &fxInfo, gameLocal.isClient );
+ }
+ if ( leftRetractBeam.IsValid() ) {
+ leftRetractBeam->Show();
+ leftRetractBeam->Activate( true );
+ }
+ if ( leftDamageBeam.IsValid() ) {
+ leftDamageBeam->Activate( false );
+ leftDamageBeam->Hide();
+ }
+ AI_LEFT_DAMAGED = true;
+ }
+ if ( !AI_RIGHT_DAMAGED && rightRecharger.IsValid() && rightRecharger->GetHealth() <= 0 ) {
+ //recharger has died so start retracting the beams and schedule the gun's death
+ StartSound( "snd_gun_predeath", SND_CHANNEL_ANY );
+ SAFE_REMOVE( rightRechargeBeam );
+ PostEventSec( &MA_RightGunDeath, spawnArgs.GetFloat("retract_delay", "1") );
+ const char *defName = spawnArgs.GetString( "fx_retract" );
+ if ( defName && defName[0] && leftDamageBeam.IsValid() ) {
+ hhFxInfo fxInfo;
+ fxInfo.RemoveWhenDone( true );
+ retractRightFx = SpawnFxLocal( defName, rightDamageBeam->GetTargetLocation(), mat3_identity, &fxInfo, gameLocal.isClient );
+ }
+ if ( rightRetractBeam.IsValid() ) {
+ rightRetractBeam->Show();
+ rightRetractBeam->Activate( true );
+ }
+ if ( rightDamageBeam.IsValid() ) {
+ rightDamageBeam->Activate( false );
+ rightDamageBeam->Hide();
+ }
+ AI_RIGHT_DAMAGED = true;
+ }
+ if ( AI_HEALTH_TICK && gameLocal.time >= nextHealthTick ) {
+ health += spawnArgs.GetInt( "recharge_delta", "1" );
+ nextHealthTick += spawnArgs.GetInt( "recharge_period" );
+ if ( health > spawnArgs.GetInt( "health" ) ) {
+ AI_HEALTH_TICK = false;
+ health = spawnArgs.GetInt( "health" );
+ }
+ }
+ } else {
+ if ( leftRechargeBeam.IsValid() ) {
+ leftRechargeBeam->Activate( false );
+ }
+ if ( rightRechargeBeam.IsValid() ) {
+ rightRechargeBeam->Activate( false );
+ }
+ }
+
+ if ( AI_LEFT_DAMAGED && gameLocal.time > nextLeftZapTime ) {
+ nextLeftZapTime = gameLocal.time + SEC2MS(spawnArgs.GetFloat( "zap_period", "0.3" ));
+ for ( int i = 0; i < numBurstBeams; i ++ ) {
+ if ( leftBeamList[i].IsValid() && !leftBeamList[i]->IsActivated() ) {
+ leftBeamList[i]->Activate( true );
+ leftBeamList[i]->SetTargetLocation( leftBeamList[i]->GetOrigin() + spawnArgs.GetFloat( "zap_length", "50" ) * hhUtils::RandomSpreadDir( hhUtils::RandomVector().ToMat3(), 16.0f ) );
+ PostEventSec(&MA_EndLeftBeams, spawnArgs.GetFloat( "beam_duration_1", "0.2" ) );
+ break;
+ }
+ }
+ }
+ if ( AI_RIGHT_DAMAGED && gameLocal.time > nextRightZapTime ) {
+ nextRightZapTime = gameLocal.time + SEC2MS(spawnArgs.GetFloat( "zap_period", "0.3" ));
+ for ( int i = 0; i < numBurstBeams; i ++ ) {
+ if ( rightBeamList[i].IsValid() && !rightBeamList[i]->IsActivated() ) {
+ rightBeamList[i]->Activate( true );
+ rightBeamList[i]->SetTargetLocation( rightBeamList[i]->GetOrigin() + spawnArgs.GetFloat( "zap_length", "50" ) * hhUtils::RandomSpreadDir( hhUtils::RandomVector().ToMat3(), 16.0f ) );
+ PostEventSec(&MA_EndRightBeams, spawnArgs.GetFloat( "beam_duration_1", "0.2" ) );
+ break;
+ }
+ }
+ }
+
+ if ( enemy.IsValid() ) {
+ //left laser
+ //HUMANHEAD jsh PCF 5/12/06 30hz issue with beam movement
+ targetAlpha_L += spawnArgs.GetFloat( "laser_move_delta", "0.1" ) * (60.0f * USERCMD_ONE_OVER_HZ);
+ if ( targetAlpha_L > 1.0f ) {
+ targetAlpha_L = 1.0f;
+ }
+ targetCurrent_L = targetStart_L + targetAlpha_L * ( targetEnd_L - targetStart_L );
+ pastEnemy = GetEnemy()->GetOrigin() + idVec3( 0,0,30 );
+ //HUMANHEAD jsh PCF 5/12/06 30hz issue with beam movement
+ targetEnd_L += spawnArgs.GetFloat( "laser_track_delta", "0.1" ) * ( pastEnemy - targetEnd_L ) * (60.0f * USERCMD_ONE_OVER_HZ);
+
+ //right laser
+ //HUMANHEAD jsh PCF 5/12/06 30hz issue with beam movement
+ targetAlpha_R += spawnArgs.GetFloat( "laser_move_delta", "0.1" ) * (60.0f * USERCMD_ONE_OVER_HZ);
+ if ( targetAlpha_R > 1.0f ) {
+ targetAlpha_R = 1.0f;
+ }
+ targetCurrent_R = targetStart_R + targetAlpha_R * ( targetEnd_R - targetStart_R );
+ pastEnemy = GetEnemy()->GetOrigin() + idVec3( 0,0,30 );
+ //HUMANHEAD jsh PCF 5/12/06 30hz issue with beam movement
+ targetEnd_R += spawnArgs.GetFloat( "laser_track_delta", "0.1" ) * ( pastEnemy - targetEnd_R ) * (60.0f * USERCMD_ONE_OVER_HZ);
+ }
+
+ if ( gameLocal.time > nextBeamTime ) {
+ nextBeamTime = gameLocal.time + SEC2MS(spawnArgs.GetFloat( "beam_period", "0.3" ));
+ for ( int i = 0; i < leftBeamList.Num(); i ++ ) {
+ if ( leftBeamList[i].IsValid() && leftBeamList[i]->IsActivated() ) {
+ leftBeamList[i]->SetTargetLocation( leftBeamList[i]->GetOrigin() + spawnArgs.GetFloat( "beam_length", "100" ) * hhUtils::RandomSpreadDir( hhUtils::RandomVector().ToMat3(), 16.0f ) );
+ }
+ }
+ for ( int i = 0; i < rightBeamList.Num(); i ++ ) {
+ if ( rightBeamList[i].IsValid() && rightBeamList[i]->IsActivated() ) {
+ rightBeamList[i]->SetTargetLocation( rightBeamList[i]->GetOrigin() + spawnArgs.GetFloat( "beam_length", "100" ) * hhUtils::RandomSpreadDir( hhUtils::RandomVector().ToMat3(), 16.0f ) );
+ }
+ }
+ }
+
+ idVec3 boneOrigin, traceEnd;
+ idMat3 boneAxis;
+ trace_t trace;
+ memset(&trace, 0, sizeof(trace));
+ if ( !enemy.IsValid() ) {
+ return;
+ }
+
+ if ( laserRight.IsValid() && bLaserRightActive ) {
+ traceEnd = targetCurrent_R + 2000 * (targetCurrent_R - laserRight->GetOrigin()).ToNormal();
+ GetJointWorldTransform( spawnArgs.GetString("laser_bone_right"), boneOrigin, boneAxis );
+ gameLocal.clip.TracePoint( trace, laserRight->GetOrigin(), traceEnd, MASK_SHOT_RENDERMODEL, this );
+ if ( trace.fraction < 1.0f ) {
+ if ( preLaserRight.IsValid() ) {
+ if ( preLaserRight->IsHidden() ) {
+ preLaserRight->Show();
+ }
+ preLaserRight->Activate( true );
+ preLaserRight->SetTargetEntity( laserRight.GetEntity() );
+ }
+ if ( laserRight->IsHidden() ) {
+ laserRight->Show();
+ }
+ laserRight->SetOrigin( boneOrigin + viewAxis[0] * 60 );
+ laserRight->Activate( true );
+ laserRight->SetTargetLocation( trace.endpos - viewAxis[1] * 10 );
+ }
+ float dist = (laserRight->GetTargetLocation() - laserRight->GetOrigin()).Length();
+ if ( dist > 100.0f && impactRightFx.IsValid() ) {
+ impactRightFx->SetOrigin( laserRight->GetTargetLocation() );
+ }
+ laserEndRight = trace.endpos;
+ } else {
+ if ( preLaserRight.IsValid() ) {
+ preLaserRight->Activate( false );
+ }
+ if ( laserRight.IsValid() ) {
+ laserRight->Activate( false );
+ }
+ }
+
+ if ( laserLeft.IsValid() && bLaserLeftActive ) {
+ traceEnd = targetCurrent_L + 2000 * (targetCurrent_L - laserLeft->GetOrigin()).ToNormal();
+ GetJointWorldTransform( spawnArgs.GetString("laser_bone_left"), boneOrigin, boneAxis );
+ gameLocal.clip.TracePoint( trace, laserLeft->GetOrigin(), traceEnd, MASK_SHOT_RENDERMODEL, this );
+ if ( trace.fraction < 1.0f ) {
+ if ( preLaserLeft.IsValid() ) {
+ if ( preLaserLeft->IsHidden() ) {
+ preLaserLeft->Show();
+ }
+ preLaserLeft->Activate( true );
+ preLaserLeft->SetTargetEntity( laserLeft.GetEntity() );
+ }
+ if ( laserLeft->IsHidden() ) {
+ laserLeft->Show();
+ }
+ laserLeft->SetOrigin( boneOrigin + viewAxis[0] * 60 );
+ laserLeft->Activate( true );
+ laserLeft->SetTargetLocation( trace.endpos + viewAxis[1] * 10 );
+ }
+ float dist = (laserLeft->GetTargetLocation() - laserLeft->GetOrigin()).Length();
+ if ( dist > 100.0f && impactLeftFx.IsValid() ) {
+ impactLeftFx->SetOrigin( laserLeft->GetTargetLocation() );
+ }
+ laserEndLeft = trace.endpos;
+ } else {
+ if ( preLaserLeft.IsValid() ) {
+ preLaserLeft->Activate( false );
+ }
+ if ( laserLeft.IsValid() ) {
+ laserLeft->Activate( false );
+ }
+ }
+}
+
+#define LinkScriptVariable( name ) name.LinkTo( scriptObject, #name )
+void hhCreatureX::LinkScriptVariables() {
+ hhMonsterAI::LinkScriptVariables();
+ LinkScriptVariable( AI_GUN_TRACKING );
+ LinkScriptVariable( AI_RECHARGING );
+ LinkScriptVariable( AI_LEFT_FIRE );
+ LinkScriptVariable( AI_RIGHT_FIRE );
+ LinkScriptVariable( AI_LEFT_DAMAGED );
+ LinkScriptVariable( AI_RIGHT_DAMAGED );
+ LinkScriptVariable( AI_FIRING_LASER );
+ LinkScriptVariable( AI_HEALTH_TICK );
+ LinkScriptVariable( AI_GUN_EXPLODE );
+}
+
+void hhCreatureX::Event_RightGunDeath() {
+ bool explodeGun = false;
+ if ( rightRetractBeam.IsValid() ) {
+ rightRetractBeam->Show();
+ rightRetractBeam->Activate( true );
+
+ //retract the damage beam a little bit
+ idVec3 bonePos;
+ idMat3 boneAxis;
+ GetJointWorldTransform( spawnArgs.GetString( "gun_bone_right" ), bonePos, boneAxis );
+ idVec3 lastTargetLocation = rightRetractBeam->GetTargetLocation();
+ rightRetractBeam->SetTargetEntity( NULL );
+ if ( ai_debugBrain.GetBool() ) {
+ gameRenderWorld->DebugArrow( colorRed, lastTargetLocation, bonePos, 10, 1 );
+ }
+ rightRetractBeam->SetTargetLocation( lastTargetLocation + spawnArgs.GetFloat("retract_speed", "50") * (bonePos - lastTargetLocation).ToNormal() );
+ if ( retractRightFx.IsValid() ) {
+ retractRightFx->SetOrigin( rightRetractBeam->GetTargetLocation() );
+ }
+ if ( (lastTargetLocation - bonePos).LengthFast() < 30.0f ) {
+ explodeGun = true;
+ rightRetractBeam->Activate( false );
+ } else {
+ PostEventSec( &MA_RightGunDeath, 0.01f );
+ }
+ }
+
+ if ( explodeGun || !rightRetractBeam.IsValid() ) {
+ CancelEvents( &MA_ResetRechargeBeam );
+ SAFE_REMOVE( rightRechargeBeam );
+ SAFE_REMOVE( retractRightFx );
+ for ( int i=0;iActivate( true );
+ rightBeamList[i]->SetTargetLocation( rightBeamList[i]->GetOrigin() + spawnArgs.GetFloat( "beam_length", "100" ) * hhUtils::RandomSpreadDir( GetEnemy()->GetOrigin().ToMat3(), 16.0f ) );
+ }
+ }
+ StartSound( "snd_jenny_scream", SND_CHANNEL_ANY );
+ PostEventSec(&MA_EndRightBeams, spawnArgs.GetFloat( "beam_duration_3", "1.0" ) );
+ BroadcastFxInfoAlongBone( spawnArgs.GetString("fx_smoke"), spawnArgs.GetString("damage_bone_right"), NULL );
+ BroadcastFxInfoAlongBone( spawnArgs.GetString("fx_gun_death"), spawnArgs.GetString("damage_bone_right"), NULL );
+ AI_RIGHT_DAMAGED = true;
+ PostEventSec(&MA_SparkLeft, spawnArgs.GetFloat( "spark_freq", "1" ) + gameLocal.random.RandomFloat() );
+ if ( AI_LEFT_DAMAGED ) {
+ SetSkinByName( spawnArgs.GetString( "skin_nogun_both" ) );
+ } else {
+ SetSkinByName( spawnArgs.GetString( "skin_nogun_right" ) );
+ }
+ }
+}
+
+void hhCreatureX::Event_LeftGunDeath() {
+ bool explodeGun = false;
+ if ( leftRetractBeam.IsValid() ) {
+ leftRetractBeam->Show();
+ leftRetractBeam->Activate( true );
+
+ //retract the damage beam a little bit
+ idVec3 bonePos;
+ idMat3 boneAxis;
+ GetJointWorldTransform( spawnArgs.GetString( "gun_bone_left" ), bonePos, boneAxis );
+ idVec3 lastTargetLocation = leftRetractBeam->GetTargetLocation();
+ leftRetractBeam->SetTargetEntity( NULL );
+ if ( ai_debugBrain.GetBool() ) {
+ gameRenderWorld->DebugArrow( colorRed, lastTargetLocation, bonePos, 10, 1 );
+ }
+ leftRetractBeam->SetTargetLocation( lastTargetLocation + spawnArgs.GetFloat("retract_speed", "50") * (bonePos - lastTargetLocation).ToNormal() );
+ if ( retractLeftFx.IsValid() ) {
+ retractLeftFx->SetOrigin( leftRetractBeam->GetTargetLocation() );
+ }
+ if ( (lastTargetLocation - bonePos).LengthFast() < 30.0f ) {
+ explodeGun = true;
+ leftRetractBeam->Activate( false );
+ } else {
+ PostEventSec( &MA_LeftGunDeath, 0.01f );
+ }
+ }
+
+ if ( explodeGun || !leftRetractBeam.IsValid() ) {
+ CancelEvents( &MA_ResetRechargeBeam );
+ SAFE_REMOVE( leftRechargeBeam );
+ SAFE_REMOVE( retractLeftFx );
+ for ( int i=0;iActivate( true );
+ leftBeamList[i]->SetTargetLocation( leftBeamList[i]->GetOrigin() + spawnArgs.GetFloat( "beam_length", "100" ) * hhUtils::RandomSpreadDir( GetEnemy()->GetOrigin().ToMat3(), 16.0f ) );
+ }
+ }
+ StartSound( "snd_jenny_scream", SND_CHANNEL_ANY );
+ PostEventSec(&MA_EndLeftBeams, spawnArgs.GetFloat( "beam_duration_3", "1.0" ) );
+ BroadcastFxInfoAlongBone( spawnArgs.GetString("fx_smoke"), spawnArgs.GetString("damage_bone_left"), NULL );
+ BroadcastFxInfoAlongBone( spawnArgs.GetString("fx_gun_death"), spawnArgs.GetString("damage_bone_left"), NULL );
+ AI_LEFT_DAMAGED = true;
+ PostEventSec(&MA_SparkLeft, spawnArgs.GetFloat( "spark_freq", "1" ) + gameLocal.random.RandomFloat() );
+ if ( AI_RIGHT_DAMAGED ) {
+ SetSkinByName( spawnArgs.GetString( "skin_nogun_both" ) );
+ } else {
+ SetSkinByName( spawnArgs.GetString( "skin_nogun_left" ) );
+ }
+ }
+}
+
+
+bool hhCreatureX::UpdateAnimationControllers( void ) {
+ idVec3 local;
+ idVec3 focusPos;
+ idVec3 left;
+ idVec3 dir;
+ idVec3 orientationJointPos;
+ idVec3 localDir;
+ idAngles newLookAng;
+ idAngles diff;
+ idMat3 mat;
+ idMat3 axis;
+ idMat3 orientationJointAxis;
+ idAFAttachment *headEnt = head.GetEntity();
+ idVec3 eyepos;
+ idVec3 pos;
+ int i;
+ idAngles jointAng;
+ float orientationJointYaw;
+
+ if ( AI_DEAD ) {
+
+ for (int i = 0; i < jawFlapList.Num(); i++) {
+ jawFlapInfo_t &flapInfo = jawFlapList[i];
+ animator.ClearJoint( flapInfo.bone );
+ }
+ animator.ClearJoint( leftEyeJoint );
+ animator.ClearJoint( rightEyeJoint );
+
+ return idActor::UpdateAnimationControllers();
+ }
+
+ if ( orientationJoint == INVALID_JOINT ) {
+ orientationJointAxis = viewAxis;
+ orientationJointPos = physicsObj.GetOrigin();
+ orientationJointYaw = current_yaw;
+ } else {
+ GetJointWorldTransform( orientationJoint, gameLocal.time, orientationJointPos, orientationJointAxis );
+ orientationJointYaw = orientationJointAxis[ 2 ].ToYaw();
+ orientationJointAxis = idAngles( 0.0f, orientationJointYaw, 0.0f ).ToMat3();
+ }
+
+ if ( ai_debugBrain.GetBool() ) {
+ gameRenderWorld->DebugArrow( colorCyan, orientationJointPos, orientationJointPos + orientationJointAxis[0] * 64.0, 10, 1 );
+ }
+
+ if ( focusJoint != INVALID_JOINT ) {
+ if ( headEnt ) {
+ headEnt->GetJointWorldTransform( focusJoint, gameLocal.time, eyepos, axis );
+ } else {
+ // JRMMERGE_GRAVAXIS - What about GetGravAxis() are we still using/needing that?
+ GetJointWorldTransform( focusJoint, gameLocal.time, eyepos, axis );
+ }
+ eyeOffset.z = eyepos.z - physicsObj.GetOrigin().z;
+ } else {
+ eyepos = GetEyePosition();
+ }
+
+ if ( headEnt ) {
+ CopyJointsFromBodyToHead();
+ }
+
+ // Update the IK after we've gotten all the joint positions we need, but before we set any joint positions.
+ // Getting the joint positions causes the joints to be updated. The IK gets joint positions itself (which
+ // are already up to date because of getting the joints in this function) and then sets their positions, which
+ // forces the heirarchy to be updated again next time we get a joint or present the model. If IK is enabled,
+ // or if we have a seperate head, we end up transforming the joints twice per frame. Characters with no
+ // head entity and no ik will only transform their joints once. Set g_debuganim to the current entity number
+ // in order to see how many times an entity transforms the joints per frame.
+ idActor::UpdateAnimationControllers();
+
+ idEntity *focusEnt = focusEntity.GetEntity();
+ //HUMANHEAD jsh allow eyefocus independent from allowJointMod
+ if ( ( !allowJointMod && !allowEyeFocus ) || ( gameLocal.time >= focusTime && focusTime != -1 ) || GetPhysics()->GetGravityNormal() != idVec3( 0,0,-1) ) {
+ focusPos = GetEyePosition() + orientationJointAxis[ 0 ] * 512.0f;
+ } else if ( focusEnt == NULL ) {
+ // keep looking at last position until focusTime is up
+ focusPos = currentFocusPos;
+ } else if ( focusEnt == enemy.GetEntity() ) {
+ focusPos = lastVisibleEnemyPos + lastVisibleEnemyEyeOffset - eyeVerticalOffset * enemy.GetEntity()->GetPhysics()->GetGravityNormal();
+ } else if ( focusEnt->IsType( idActor::Type ) ) {
+ focusPos = static_cast( focusEnt )->GetEyePosition() - eyeVerticalOffset * focusEnt->GetPhysics()->GetGravityNormal();
+ } else {
+ focusPos = focusEnt->GetPhysics()->GetOrigin();
+ }
+
+ currentFocusPos = currentFocusPos + ( focusPos - currentFocusPos ) * eyeFocusRate;
+
+ // determine yaw from origin instead of from focus joint since joint may be offset, which can cause us to bounce between two angles
+ dir = focusPos - orientationJointPos;
+ newLookAng.yaw = idMath::AngleNormalize180( dir.ToYaw() - orientationJointYaw );
+ newLookAng.roll = 0.0f;
+ newLookAng.pitch = 0.0f;
+
+ newLookAng += lookOffset;
+
+#if 0
+ gameRenderWorld->DebugLine( colorRed, orientationJointPos, focusPos, gameLocal.msec );
+ gameRenderWorld->DebugLine( colorYellow, orientationJointPos, orientationJointPos + orientationJointAxis[ 0 ] * 32.0f, gameLocal.msec );
+ gameRenderWorld->DebugLine( colorGreen, orientationJointPos, orientationJointPos + newLookAng.ToForward() * 48.0f, gameLocal.msec );
+#endif
+
+//JRMMERGE_GRAVAXIS: This changed to much to merge, see if you can get your monsters on planets changes back in here. I'll leave both versions
+#if OLD_CODE
+ GetGravViewAxis().ProjectVector( dir, localDir ); // HUMANHEAD JRM: VIEWAXIS_TO_GETGRAVVIEWAXIS
+ lookAng.yaw = idMath::AngleNormalize180( localDir.ToYaw() );
+ lookAng.pitch = -idMath::AngleNormalize180( localDir.ToPitch() );
+ lookAng.roll = 0.0f;
+#else
+ // determine pitch from joint position
+ dir = focusPos - eyepos;
+ if ( ai_debugBrain.GetBool() ) {
+ gameRenderWorld->DebugArrow( colorYellow, eyepos, eyepos + dir, 10, 1 );
+ }
+ dir.NormalizeFast();
+ orientationJointAxis.ProjectVector( dir, localDir );
+ newLookAng.pitch = -idMath::AngleNormalize180( localDir.ToPitch() ) + lookOffset.pitch;
+ newLookAng.roll = 0.0f;
+#endif
+
+ diff = newLookAng - lookAng;
+
+ if ( eyeAng != diff ) {
+ eyeAng = diff;
+ eyeAng.Clamp( eyeMin, eyeMax );
+ idAngles angDelta = diff - eyeAng;
+ if ( !angDelta.Compare( ang_zero, 0.1f ) ) {
+ alignHeadTime = gameLocal.time;
+ } else {
+ alignHeadTime = gameLocal.time + ( 0.5f + 0.5f * gameLocal.random.RandomFloat() ) * focusAlignTime;
+ }
+ }
+
+ if ( idMath::Fabs( newLookAng.yaw ) < 0.1f ) {
+ alignHeadTime = gameLocal.time;
+ }
+
+ if ( ( gameLocal.time >= alignHeadTime ) || ( gameLocal.time < forceAlignHeadTime ) ) {
+ alignHeadTime = gameLocal.time + ( 0.5f + 0.5f * gameLocal.random.RandomFloat() ) * focusAlignTime;
+ destLookAng = newLookAng;
+ destLookAng.Clamp( lookMin, lookMax );
+ }
+
+ diff = destLookAng - lookAng;
+ if ( ( lookMin.pitch == -180.0f ) && ( lookMax.pitch == 180.0f ) ) {
+ if ( ( diff.pitch > 180.0f ) || ( diff.pitch <= -180.0f ) ) {
+ diff.pitch = 360.0f - diff.pitch;
+ }
+ }
+ if ( ( lookMin.yaw == -180.0f ) && ( lookMax.yaw == 180.0f ) ) {
+ if ( diff.yaw > 180.0f ) {
+ diff.yaw -= 360.0f;
+ } else if ( diff.yaw <= -180.0f ) {
+ diff.yaw += 360.0f;
+ }
+ }
+ lookAng = lookAng + diff * headFocusRate;
+ lookAng.Normalize180();
+
+ jointAng.roll = 0.0f;
+ if ( allowJointMod ) {
+ for( i = 0; i < lookJoints.Num(); i++ ) {
+ jointAng.pitch = lookAng.pitch * lookJointAngles[ i ].pitch;
+ jointAng.yaw = lookAng.yaw * lookJointAngles[ i ].yaw;
+ animator.SetJointAxis( lookJoints[ i ], JOINTMOD_WORLD, jointAng.ToMat3() );
+ }
+ }
+
+ if ( move.moveType == MOVETYPE_FLY ) {
+ // lean into turns
+ AdjustFlyingAngles();
+ }
+
+ if ( headEnt ) {
+ idAnimator *headAnimator = headEnt->GetAnimator();
+
+ // HUMANHEAD pdm: Added support for look joints in head entities
+ if ( allowJointMod ) {
+ for( i = 0; i < headLookJoints.Num(); i++ ) {
+ jointAng.pitch = lookAng.pitch * headLookJointAngles[ i ].pitch;
+ jointAng.yaw = lookAng.yaw * headLookJointAngles[ i ].yaw;
+ headAnimator->SetJointAxis( headLookJoints[ i ], JOINTMOD_WORLD, jointAng.ToMat3() );
+ }
+ }
+ // HUMANHEAD END
+
+ if ( allowEyeFocus ) {
+ idMat3 eyeAxis = ( lookAng + eyeAng ).ToMat3(); idMat3 headTranspose = headEnt->GetPhysics()->GetAxis().Transpose();
+ axis = eyeAxis * orientationJointAxis;
+ left = axis[ 1 ] * eyeHorizontalOffset;
+ eyepos -= headEnt->GetPhysics()->GetOrigin();
+ headAnimator->SetJointPos( leftEyeJoint, JOINTMOD_WORLD_OVERRIDE, eyepos + ( axis[ 0 ] * 64.0f + left ) * headTranspose );
+ headAnimator->SetJointPos( rightEyeJoint, JOINTMOD_WORLD_OVERRIDE, eyepos + ( axis[ 0 ] * 64.0f - left ) * headTranspose );
+
+ //if ( ai_debugMove.GetBool() ) {
+ // gameRenderWorld->DebugLine( colorRed, orientationJointPos, eyepos + ( axis[ 0 ] * 64.0f + left ) * headTranspose, gameLocal.msec );
+ //}
+ } else {
+ headAnimator->ClearJoint( leftEyeJoint );
+ headAnimator->ClearJoint( rightEyeJoint );
+ }
+ } else {
+ if ( allowEyeFocus ) {
+ idMat3 eyeAxis = ( lookAng + eyeAng ).ToMat3();
+ axis = eyeAxis * orientationJointAxis;
+ left = axis[ 1 ] * eyeHorizontalOffset;
+ eyepos += axis[ 0 ] * 64.0f - physicsObj.GetOrigin();
+ animator.SetJointPos( leftEyeJoint, JOINTMOD_WORLD_OVERRIDE, eyepos + left );
+ animator.SetJointPos( rightEyeJoint, JOINTMOD_WORLD_OVERRIDE, eyepos - left );
+ } else {
+ animator.ClearJoint( leftEyeJoint );
+ animator.ClearJoint( rightEyeJoint );
+ }
+ }
+
+ //HUMANHEAD pdm jawflap
+ hhAnimator *theAnimator;
+ if (head.IsValid()) {
+ theAnimator = head->GetAnimator();
+ }
+ else {
+ theAnimator = GetAnimator();
+ }
+ JawFlap(theAnimator);
+ //END HUMANHEAD
+
+ //update guns
+ //HUMANHEAD jsh PCF 4/27/06 no gun tracking
+ //if ( enemy.IsValid() ) {
+ // if ( AI_GUN_TRACKING && !AI_DEAD && !AI_RECHARGING ) {
+ // idVec3 focusPos, bonePos;
+ // idMat3 boneAxis;
+
+ // idAngles ang = ang_zero;
+ // ang.pitch = (bonePos - enemy->GetOrigin()).ToPitch();
+ // ang.pitch += gunShake.pitch;
+ // ang.yaw = (targetCurrent_L-bonePos).ToYaw() - viewAxis.ToAngles().yaw;
+ // ang.yaw += gunShake.yaw;
+ // GetJointWorldTransform( spawnArgs.GetString( "gun_bone_left" ), bonePos, boneAxis );
+ // animator.SetJointAxis( animator.GetJointHandle( spawnArgs.GetString( "gun_bone_left" ) ), JOINTMOD_WORLD, idAngles( (bonePos - targetCurrent_L).ToPitch(), (targetCurrent_L-bonePos).ToYaw() - viewAxis.ToAngles().yaw, 0.0f ).ToMat3() );
+
+ // ang.yaw = (targetCurrent_R-bonePos).ToYaw() - viewAxis.ToAngles().yaw;
+ // ang.yaw += gunShake.yaw;
+ // GetJointWorldTransform( spawnArgs.GetString( "gun_bone_right" ), bonePos, boneAxis );
+ // animator.SetJointAxis( animator.GetJointHandle( spawnArgs.GetString( "gun_bone_right" ) ), JOINTMOD_WORLD, idAngles( (bonePos - targetCurrent_R).ToPitch(), (targetCurrent_R-bonePos).ToYaw() - viewAxis.ToAngles().yaw, 0.0f ).ToMat3() );
+ // } else {
+ // animator.SetJointAxis( animator.GetJointHandle( spawnArgs.GetString( "gun_bone_left" ) ), JOINTMOD_WORLD, mat3_identity );
+ // animator.SetJointAxis( animator.GetJointHandle( spawnArgs.GetString( "gun_bone_right" ) ), JOINTMOD_WORLD, mat3_identity );
+ // }
+ //}
+
+ return true;
+}
+
+void hhCreatureX::Killed(idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) {
+ HandleNoGore();
+
+ //stop sparking upon death
+ CancelEvents( &MA_SparkLeft );
+ CancelEvents( &MA_SparkRight );
+ SAFE_REMOVE( preLaserLeft );
+ SAFE_REMOVE( preLaserRight );
+ AI_LEFT_DAMAGED = false;
+ AI_RIGHT_DAMAGED = false;
+
+ if ( laserLeft.IsValid() ) {
+ laserLeft->Hide();
+ }
+ if ( laserRight.IsValid() ) {
+ laserRight->Hide();
+ }
+
+ if ( spawnArgs.GetBool( "use_death_point", "0" ) ) {
+ if ( !AI_DEAD ) {
+ fl.takedamage = false;
+ AI_DEAD = true;
+ state = GetScriptFunction( "state_Predeath" );
+ SetState( state );
+ SetWaitState( "" );
+ return;
+ }
+ }
+
+ if ( AI_DEAD ) {
+ AI_DAMAGE = true;
+ return;
+ }
+
+ fl.takedamage = false;
+ idAngles ang;
+ const char *modelDeath;
+
+ // make sure the monster is activated
+ EndAttack();
+
+ if ( g_debugDamage.GetBool() ) {
+ gameLocal.Printf( "Damage: joint: '%s', zone '%s'\n", animator.GetJointName( ( jointHandle_t )location ),
+ GetDamageGroup( location ) );
+ }
+
+ if ( inflictor ) {
+ AI_SPECIAL_DAMAGE = inflictor->spawnArgs.GetInt( "special_damage" );
+ } else {
+ AI_SPECIAL_DAMAGE = 0;
+ }
+
+ if ( AI_DEAD ) {
+ AI_PAIN = true;
+ AI_DAMAGE = true;
+ return;
+ }
+
+ // stop all voice sounds
+ StopSound( SND_CHANNEL_VOICE, false );
+ if ( head.GetEntity() ) {
+ head.GetEntity()->StopSound( SND_CHANNEL_VOICE, false );
+ head.GetEntity()->GetAnimator()->ClearAllAnims( gameLocal.time, 100 );
+ }
+
+ disableGravity = false;
+ move.moveType = MOVETYPE_DEAD;
+ af_push_moveables = false;
+
+ physicsObj.UseFlyMove( false );
+ physicsObj.ForceDeltaMove( false );
+
+ // end our looping ambient sound
+ StopSound( SND_CHANNEL_AMBIENT, false );
+
+ if ( attacker && attacker->IsType( idActor::Type ) ) {
+ gameLocal.AlertAI( ( idActor * )attacker );
+ }
+
+ // activate targets
+ ActivateTargets( attacker );
+
+ RemoveAttachments();
+ RemoveProjectile();
+ StopMove( MOVE_STATUS_DONE );
+
+ ClearEnemy();
+ AI_DEAD = true;
+
+ // HUMANHEAD jsh commented out for girlfriendx
+ // make monster nonsolid
+ if ( spawnArgs.GetBool( "boss" ) ) {
+ physicsObj.SetContents( 0 );
+ physicsObj.GetClipModel()->Unlink();
+ }
+
+ Unbind();
+
+ // spawn death clip model
+ if ( spawnArgs.GetBool( "boss" ) ) {
+ idDict dict;
+ const idDict *torsoDict = gameLocal.FindEntityDefDict(spawnArgs.GetString("def_deathclip"));
+ dict.Copy(*torsoDict);
+ dict.SetVector("origin", GetOrigin() + modelOffset * GetAxis() );
+ dict.SetMatrix("rotation", GetAxis());
+ idEntity *e;
+ gameLocal.SpawnEntityDef(dict, &e);
+ if ( e ) {
+ e->GetPhysics()->SetContents( CONTENTS_PLAYERCLIP );
+ }
+ }
+
+ if ( StartRagdoll() ) {
+ StartSound( "snd_death", SND_CHANNEL_VOICE, 0, false, NULL );
+ // HUMANHEAD JRM - some monsters are removed, but always need to play sound
+ } else if(spawnArgs.GetBool("death_sound_always","0")) {
+ StartSound( "snd_death", SND_CHANNEL_VOICE, 0, false, NULL );
+ }
+ // HUMANHEAD JRM - end
+
+ if ( spawnArgs.GetString( "model_death", "", &modelDeath ) ) {
+ // lost soul is only case that does not use a ragdoll and has a model_death so get the death sound in here
+ StartSound( "snd_death", SND_CHANNEL_VOICE, 0, false, NULL );
+ renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time );
+ SetModel( modelDeath );
+ physicsObj.SetLinearVelocity( vec3_zero );
+ physicsObj.PutToRest();
+ physicsObj.DisableImpact();
+ }
+
+ restartParticles = false;
+
+ state = GetScriptFunction( "state_Killed" );
+ SetState( state );
+ SetWaitState( "" );
+
+ if ( attacker && attacker->IsType( idPlayer::Type ) ) {
+ static_cast< idPlayer* >( attacker )->AddAIKill();
+ }
+
+ // General non-item dropping (for monsters, souls, etc.)
+ const idKeyValue *kv = NULL;
+ kv = spawnArgs.MatchPrefix( "def_drops", NULL );
+ while ( kv ) {
+
+ idStr drops = kv->GetValue();
+ idDict args;
+
+ idStr last5 = kv->GetKey().Right(5);
+ if ( drops.Length() && idStr::Icmp( last5, "Joint" ) != 0) {
+ args.Set( "classname", drops );
+
+ // HUMANHEAD pdm: specify monster so souls can call back to remove body when picked up
+ args.Set("monsterSpawnedBy", name.c_str());
+
+ idVec3 origin;
+ idMat3 axis;
+ idStr jointKey = kv->GetKey() + idStr("Joint");
+ idStr jointName = spawnArgs.GetString( jointKey );
+ idStr joint2JointKey = kv->GetKey() + idStr("Joint2Joint");
+ idStr j2jName = spawnArgs.GetString( joint2JointKey );
+
+ idEntity *newEnt = NULL;
+ gameLocal.SpawnEntityDef( args, &newEnt );
+ HH_ASSERT(newEnt != NULL);
+
+ if(jointName.Length()) {
+ jointHandle_t joint = GetAnimator()->GetJointHandle( jointName );
+ if (!GetAnimator()->GetJointTransform( joint, gameLocal.time, origin, axis ) ) {
+ gameLocal.Printf( "%s refers to invalid joint '%s' on entity '%s'\n", (const char*)jointKey.c_str(), (const char*)jointName, (const char*)name );
+ origin = renderEntity.origin;
+ axis = renderEntity.axis;
+ }
+ axis *= renderEntity.axis;
+ origin = renderEntity.origin + origin * renderEntity.axis;
+ newEnt->SetAxis(axis);
+ newEnt->SetOrigin(origin);
+ }
+ else {
+
+ newEnt->SetAxis(viewAxis);
+ newEnt->SetOrigin(GetOrigin());
+ }
+
+ }
+
+ kv = spawnArgs.MatchPrefix( "def_drops", kv );
+ }
+}
+
+/*
+=====================
+hhCreatureX::Save
+=====================
+*/
+void hhCreatureX::Save( idSaveGame *savefile ) const {
+ laserRight.Save( savefile );
+ laserLeft.Save( savefile );
+ savefile->WriteBool( bLaserLeftActive );
+ savefile->WriteBool( bLaserRightActive );
+
+ int i, num = leftBeamList.Num();
+ savefile->WriteInt( num );
+ for ( i = 0; i < num; i++ ) {
+ leftBeamList[i].Save( savefile );
+ }
+
+ num = rightBeamList.Num();
+ savefile->WriteInt( num );
+ for ( i = 0; i < num; i++ ) {
+ rightBeamList[i].Save( savefile );
+ }
+
+ savefile->WriteInt( numBurstBeams );
+ savefile->WriteInt( leftGunHealth );
+ savefile->WriteInt( rightGunHealth );
+ savefile->WriteAngles( gunShake );
+ savefile->WriteInt( nextBeamTime );
+ savefile->WriteInt( nextLeftZapTime );
+ savefile->WriteInt( nextRightZapTime );
+ savefile->WriteInt( leftGunLives );
+ savefile->WriteInt( rightGunLives );
+
+ savefile->WriteVec3( targetStart_L );
+ savefile->WriteVec3( targetEnd_L );
+ savefile->WriteVec3( targetCurrent_L );
+ savefile->WriteVec3( targetStart_R );
+ savefile->WriteVec3( targetEnd_R );
+ savefile->WriteVec3( targetCurrent_R );
+
+ savefile->WriteFloat( targetAlpha_L );
+ savefile->WriteFloat( targetAlpha_R );
+
+ savefile->WriteVec3( laserEndLeft );
+ savefile->WriteVec3( laserEndRight );
+
+ savefile->WriteInt( nextLaserLeft );
+ savefile->WriteInt( nextLaserRight );
+ savefile->WriteInt( nextHealthTick );
+
+ leftRecharger.Save( savefile );
+ rightRecharger.Save( savefile );
+
+ leftRechargeBeam.Save( savefile );
+ rightRechargeBeam.Save( savefile );
+
+ leftDamageBeam.Save( savefile );
+ rightDamageBeam.Save( savefile );
+
+ leftRetractBeam.Save( savefile );
+ rightRetractBeam.Save( savefile );
+
+ preLaserLeft.Save( savefile );
+ preLaserRight.Save( savefile );
+
+ muzzleLeftFx.Save( savefile );
+ muzzleRightFx.Save( savefile );
+ impactLeftFx.Save( savefile );
+ impactRightFx.Save( savefile );
+ rechargeLeftFx.Save( savefile );
+ rechargeRightFx.Save( savefile );
+ retractLeftFx.Save( savefile );
+ retractRightFx.Save( savefile );
+
+ savefile->WriteInt( leftRechargerHealth );
+ savefile->WriteInt( rightRechargerHealth );
+ savefile->WriteBool( bScripted );
+}
+
+/*
+=====================
+hhCreatureX::Restore
+=====================
+*/
+void hhCreatureX::Restore( idRestoreGame *savefile ) {
+ laserRight.Restore( savefile );
+ laserLeft.Restore( savefile );
+ savefile->ReadBool( bLaserLeftActive );
+ savefile->ReadBool( bLaserRightActive );
+
+ int i, num;
+ savefile->ReadInt( num );
+ leftBeamList.SetNum( num );
+ for ( i = 0; i < num; i++ ) {
+ leftBeamList[i].Restore( savefile );
+ }
+
+ savefile->ReadInt( num );
+ rightBeamList.SetNum( num );
+ for ( int i = 0; i < num; i++ ) {
+ rightBeamList[i].Restore( savefile );
+ }
+
+ savefile->ReadInt( numBurstBeams );
+ savefile->ReadInt( leftGunHealth );
+ savefile->ReadInt( rightGunHealth );
+ savefile->ReadAngles( gunShake );
+ savefile->ReadInt( nextBeamTime );
+ savefile->ReadInt( nextLeftZapTime );
+ savefile->ReadInt( nextRightZapTime );
+ savefile->ReadInt( leftGunLives );
+ savefile->ReadInt( rightGunLives );
+
+ savefile->ReadVec3( targetStart_L );
+ savefile->ReadVec3( targetEnd_L );
+ savefile->ReadVec3( targetCurrent_L );
+ savefile->ReadVec3( targetStart_R );
+ savefile->ReadVec3( targetEnd_R );
+ savefile->ReadVec3( targetCurrent_R );
+
+ savefile->ReadFloat( targetAlpha_L );
+ savefile->ReadFloat( targetAlpha_R );
+
+ savefile->ReadVec3( laserEndLeft );
+ savefile->ReadVec3( laserEndRight );
+
+ savefile->ReadInt( nextLaserLeft );
+ savefile->ReadInt( nextLaserRight );
+ savefile->ReadInt( nextHealthTick );
+
+ leftRecharger.Restore( savefile );
+ rightRecharger.Restore( savefile );
+
+ leftRechargeBeam.Restore( savefile );
+ rightRechargeBeam.Restore( savefile );
+
+ leftDamageBeam.Restore( savefile );
+ rightDamageBeam.Restore( savefile );
+
+ leftRetractBeam.Restore( savefile );
+ rightRetractBeam.Restore( savefile );
+
+ preLaserLeft.Restore( savefile );
+ preLaserRight.Restore( savefile );
+
+ muzzleLeftFx.Restore( savefile );
+ muzzleRightFx.Restore( savefile );
+ impactLeftFx.Restore( savefile );
+ impactRightFx.Restore( savefile );
+ rechargeLeftFx.Restore( savefile );
+ rechargeRightFx.Restore( savefile );
+ retractLeftFx.Restore( savefile );
+ retractRightFx.Restore( savefile );
+
+ savefile->ReadInt( leftRechargerHealth );
+ savefile->ReadInt( rightRechargerHealth );
+ savefile->ReadBool( bScripted );
+}
+
+
+void hhCreatureX::MuzzleLeftOn() {
+ if ( !muzzleLeftFx.IsValid() ) {
+ BroadcastFxInfoAlongBone( spawnArgs.GetString("fx_muzzle"), spawnArgs.GetString("muzzle_bone_left"), NULL, &MA_AssignLeftMuzzleFx, false );
+ }
+ if ( !impactLeftFx.IsValid() ) {
+ BroadcastFxInfo( spawnArgs.GetString("fx_impact"), GetOrigin(), GetAxis(), NULL, &MA_AssignLeftImpactFx, false );
+ }
+}
+
+void hhCreatureX::MuzzleLeftOff() {
+ SAFE_REMOVE( muzzleLeftFx );
+ SAFE_REMOVE( impactLeftFx );
+}
+
+void hhCreatureX::Event_AssignLeftMuzzleFx( hhEntityFx* fx ) {
+ muzzleLeftFx = fx;
+}
+
+void hhCreatureX::Event_AssignRightMuzzleFx( hhEntityFx* fx ) {
+ muzzleRightFx = fx;
+}
+
+void hhCreatureX::Event_AssignRightImpactFx( hhEntityFx* fx ) {
+ impactRightFx = fx;
+}
+
+void hhCreatureX::Event_AssignLeftImpactFx( hhEntityFx* fx ) {
+ impactLeftFx = fx;
+}
+
+
+void hhCreatureX::Event_AssignLeftRechargeFx( hhEntityFx* fx ) {
+ rechargeLeftFx = fx;
+}
+
+void hhCreatureX::Event_AssignRightRechargeFx( hhEntityFx* fx ) {
+ rechargeRightFx= fx;
+}
+
+void hhCreatureX::MuzzleRightOn() {
+ if ( !muzzleRightFx.IsValid() ) {
+ BroadcastFxInfoAlongBone( spawnArgs.GetString("fx_muzzle"), spawnArgs.GetString("muzzle_bone_right"), NULL, &MA_AssignRightMuzzleFx, false );
+ }
+ if ( !impactRightFx.IsValid() ) {
+ BroadcastFxInfo( spawnArgs.GetString("fx_impact"), GetOrigin(), GetAxis(), NULL, &MA_AssignRightImpactFx, false );
+ }
+}
+
+void hhCreatureX::MuzzleRightOff() {
+ SAFE_REMOVE( muzzleRightFx );
+ SAFE_REMOVE( impactRightFx );
+}
+
+void hhCreatureX::Event_SetGunOffset( const idAngles &ang ) {
+ gunShake = ang;
+}
+
+void hhCreatureX::Event_EndLeftBeams() {
+ for ( int i = 0; i < numBurstBeams; i ++ ) {
+ if( leftBeamList[i].IsValid() ) {
+ leftBeamList[i]->Activate( false );
+ }
+ }
+}
+
+void hhCreatureX::Event_EndRightBeams() {
+ for ( int i = 0; i < numBurstBeams; i ++ ) {
+ if( rightBeamList[i].IsValid() ) {
+ rightBeamList[i]->Activate( false );
+ }
+ }
+}
+
+void hhCreatureX::Event_StartRechargeBeams() {
+ StartSound( "snd_recharge_beam_start", SND_CHANNEL_ANY );
+ if ( leftRecharger.IsValid() ) {
+ const char *defName = spawnArgs.GetString( "fx_recharge_beam_start" );
+ if (defName && defName[0]) {
+ hhFxInfo fxInfo;
+ fxInfo.RemoveWhenDone( true );
+ idEntityFx *rechargeFx = SpawnFxLocal( defName, leftRecharger->GetOrigin(), leftRecharger->GetAxis(), &fxInfo, gameLocal.isClient );
+ if ( rechargeFx ) {
+ rechargeFx->Bind( leftRecharger.GetEntity(), true );
+ }
+ }
+ if ( leftRechargeBeam.IsValid() ) {
+ leftRechargeBeam->Activate( true );
+ leftRechargeBeam->SetTargetEntity( leftRecharger.GetEntity(), 0, leftRecharger->spawnArgs.GetVector( "offsetModel" ) );
+ }
+ }
+ if ( rightRecharger.IsValid() ) {
+ const char *defName = spawnArgs.GetString( "fx_recharge_beam_start" );
+ if (defName && defName[0]) {
+ hhFxInfo fxInfo;
+ fxInfo.RemoveWhenDone( true );
+ idEntityFx *rechargeFx = SpawnFxLocal( defName, rightRecharger->GetOrigin(), rightRecharger->GetAxis(), &fxInfo, gameLocal.isClient );
+ if ( rechargeFx ) {
+ rechargeFx->Bind( rightRecharger.GetEntity(), true );
+ }
+ }
+
+ if ( rightRechargeBeam.IsValid() ) {
+ rightRechargeBeam->Activate( true );
+ rightRechargeBeam->SetTargetEntity( rightRecharger.GetEntity(), 0, rightRecharger->spawnArgs.GetVector( "offsetModel" ) );
+ }
+ }
+}
+
+void hhCreatureX::Event_GunRecharge( int onOff ) {
+ if ( onOff ) {
+ idVec3 boneOrigin;
+ idMat3 boneAxis;
+ GetJointWorldTransform( spawnArgs.GetString("laser_bone_right"), boneOrigin, boneAxis );
+ BroadcastFxInfo( spawnArgs.GetString("fx_recharger_enter"), boneOrigin, boneAxis, NULL, NULL, false );
+
+ GetJointWorldTransform( spawnArgs.GetString("laser_bone_left"), boneOrigin, boneAxis );
+ BroadcastFxInfo( spawnArgs.GetString("fx_recharger_enter"), boneOrigin, boneAxis, NULL, NULL, false );
+
+ PostEventSec( &MA_StartRechargeBeams, spawnArgs.GetFloat( "recharge_delay", "0.9" ) );
+ if ( leftRecharger.IsValid() ) {
+ if ( leftRetractBeam.IsValid() ) {
+ leftRetractBeam->SetTargetEntity( leftRecharger.GetEntity(), 0, leftRecharger->spawnArgs.GetVector( "offsetModel" ) );
+ }
+ leftRechargerHealth = leftRecharger->GetHealth();
+ if ( leftDamageBeam.IsValid() ) {
+ leftDamageBeam->SetTargetEntity( leftRecharger.GetEntity(), 0, leftRecharger->spawnArgs.GetVector( "offsetModel" ) );
+ }
+ leftRecharger->Show();
+ leftRecharger->SetOrigin( GetOrigin() );
+ idVec3 offset = spawnArgs.GetVector( "healer_offset", "0 80 60" );
+ leftRecharger->MoveToPosition( GetOrigin() + viewAxis * offset );
+ leftRecharger->SetState( leftRecharger->GetScriptFunction( "state_Healer" ) );
+ leftRecharger->SetWaitState( "" );
+ }
+ if ( rightRecharger.IsValid() ) {
+ if ( rightRetractBeam.IsValid() ) {
+ rightRetractBeam->SetTargetEntity( rightRecharger.GetEntity(), 0, rightRecharger->spawnArgs.GetVector( "offsetModel" ) );
+ }
+ rightRechargerHealth = rightRecharger->GetHealth();
+ if ( rightDamageBeam.IsValid() ) {
+ rightDamageBeam->SetTargetEntity( rightRecharger.GetEntity(), 0, rightRecharger->spawnArgs.GetVector( "offsetModel" ) );
+ }
+ rightRecharger->Show();
+ idVec3 offset = spawnArgs.GetVector( "healer_offset", "0 80 60" );
+ offset.y *= -1;
+ rightRecharger->SetOrigin( GetOrigin() );
+ rightRecharger->MoveToPosition( GetOrigin() + viewAxis * offset );
+ rightRecharger->SetState( rightRecharger->GetScriptFunction( "state_Healer" ) );
+ rightRecharger->SetWaitState( "" );
+ }
+ if ( !AI_LEFT_DAMAGED ) {
+ BroadcastFxInfoAlongBone( spawnArgs.GetString("fx_weakpoint"), spawnArgs.GetString("recharge_bone_left"), NULL, &MA_AssignLeftRechargeFx, false );
+ }
+ if ( !AI_RIGHT_DAMAGED ) {
+ BroadcastFxInfoAlongBone( spawnArgs.GetString("fx_weakpoint"), spawnArgs.GetString("recharge_bone_right"), NULL, &MA_AssignRightRechargeFx, false );
+ }
+ } else {
+ SAFE_REMOVE( rechargeLeftFx );
+ SAFE_REMOVE( rechargeRightFx );
+ }
+}
+
+void hhCreatureX::Event_EndRecharge() {
+ if ( leftRechargeBeam.IsValid() ) {
+ leftRechargeBeam->Activate( false );
+ }
+ if ( leftRecharger.IsValid() ) {
+ leftRecharger->SetState( leftRecharger->GetScriptFunction( "state_Return" ) );
+ }
+ if ( rightRechargeBeam.IsValid() ) {
+ rightRechargeBeam->Activate( false );
+ }
+ if ( rightRecharger.IsValid() ) {
+ rightRecharger->SetState( rightRecharger->GetScriptFunction( "state_Return" ) );
+ }
+ if ( rightDamageBeam.IsValid() ) {
+ rightDamageBeam->Activate( false );
+ }
+ if ( leftDamageBeam.IsValid() ) {
+ leftDamageBeam->Activate( false );
+ }
+ CancelEvents( &MA_ResetRechargeBeam );
+ CancelEvents( &MA_StartRechargeBeams );
+}
+
+//HUMANHEAD jsh PCF 4/27/06 initialized proj and made sure ReturnEntity is called
+void hhCreatureX::Event_AttackMissile( const char *jointname, const idDict *projDef, int boneDir ) {
+ idProjectile *proj = NULL;
+
+ // Bonedir launch?
+ if((BOOL)boneDir) {
+ proj = hhProjectile::SpawnProjectile(projDef);
+ if ( proj ) {
+ idMat3 axis;
+ idVec3 muzzle;
+ GetMuzzle( jointname, muzzle, axis );
+ proj->Create(this, muzzle, axis);
+ proj->Launch(muzzle, axis, vec3_zero);
+ }
+ }
+ else {
+ if ( shootTarget.IsValid() ) {
+ proj = LaunchProjectile( jointname, shootTarget.GetEntity(), false, projDef ); //HUMANHEAD mdc - pass projDef on for multiple proj support
+ } else {
+ proj = LaunchProjectile( jointname, enemy.GetEntity(), false, projDef ); //HUMANHEAD mdc - pass projDef on for multiple proj support
+ }
+ }
+
+ idThread::ReturnEntity( proj );
+}
+
+void hhCreatureX::Event_SparkLeft() {
+ BroadcastFxInfoAlongBone( spawnArgs.GetString("fx_spark"), spawnArgs.GetString("damage_bone_left"), NULL );
+ PostEventSec(&MA_SparkLeft, spawnArgs.GetFloat( "spark_freq", "1" ) + gameLocal.random.RandomFloat() );
+}
+
+void hhCreatureX::Event_SparkRight() {
+ BroadcastFxInfoAlongBone( spawnArgs.GetString("fx_spark"), spawnArgs.GetString("damage_bone_right"), NULL );
+ PostEventSec(&MA_SparkRight, spawnArgs.GetFloat( "spark_freq", "1" ) + gameLocal.random.RandomFloat() );
+}
+
+void hhCreatureX::Event_HudEvent( const char* eventName ) {
+ idPlayer* player = gameLocal.GetLocalPlayer();
+ if ( player && player->hud ) {
+ gameLocal.GetLocalPlayer()->hud->HandleNamedEvent( eventName );
+ gameLocal.GetLocalPlayer()->hud->StateChanged(gameLocal.time);
+ }
+}
+
+void hhCreatureX::Activate( idEntity *activator ) {
+ if ( preLaserLeft.IsValid() ) {
+ HH_ASSERT(!FLOAT_IS_NAN(preLaserLeft->GetOrigin().x)); //Test for bad origin
+ HH_ASSERT(!FLOAT_IS_NAN(preLaserLeft->GetOrigin().y)); //Test for bad origin
+ HH_ASSERT(!FLOAT_IS_NAN(preLaserLeft->GetOrigin().z)); //Test for bad origin
+ }
+ if ( preLaserRight.IsValid() ) {
+ HH_ASSERT(!FLOAT_IS_NAN(preLaserRight->GetOrigin().x)); //Test for bad origin
+ HH_ASSERT(!FLOAT_IS_NAN(preLaserRight->GetOrigin().y)); //Test for bad origin
+ HH_ASSERT(!FLOAT_IS_NAN(preLaserRight->GetOrigin().z)); //Test for bad origin
+ }
+ if ( laserLeft.IsValid() ) {
+ HH_ASSERT(!FLOAT_IS_NAN(laserLeft->GetOrigin().x)); //Test for bad origin
+ HH_ASSERT(!FLOAT_IS_NAN(laserLeft->GetOrigin().y)); //Test for bad origin
+ HH_ASSERT(!FLOAT_IS_NAN(laserLeft->GetOrigin().z)); //Test for bad origin
+ }
+ if ( laserRight.IsValid() ) {
+ HH_ASSERT(!FLOAT_IS_NAN(laserRight->GetOrigin().x)); //Test for bad origin
+ HH_ASSERT(!FLOAT_IS_NAN(laserRight->GetOrigin().y)); //Test for bad origin
+ HH_ASSERT(!FLOAT_IS_NAN(laserRight->GetOrigin().z)); //Test for bad origin
+ }
+
+ hhMonsterAI::Activate( activator );
+}
+
+void hhCreatureX::Show() {
+ hhMonsterAI::Show();
+ if ( preLaserLeft.IsValid() ) {
+ preLaserLeft->Hide();
+ preLaserLeft->Activate( false );
+ }
+ if ( preLaserRight.IsValid() ) {
+ preLaserRight->Hide();
+ preLaserRight->Activate( false );
+ }
+ if ( laserLeft.IsValid() ) {
+ laserLeft->Hide();
+ laserLeft->Activate( false );
+ }
+ if ( laserRight.IsValid() ) {
+ laserRight->Hide();
+ laserRight->Activate( false );
+ }
+ if ( leftRechargeBeam.IsValid() ) {
+ leftRechargeBeam->Hide();
+ leftRechargeBeam->Activate( false );
+ }
+ if ( rightRechargeBeam.IsValid() ) {
+ rightRechargeBeam->Hide();
+ rightRechargeBeam->Activate( false );
+ }
+ if ( leftDamageBeam.IsValid() ) {
+ leftDamageBeam->Hide();
+ leftDamageBeam->Activate( false );
+ }
+ if ( rightDamageBeam.IsValid() ) {
+ rightDamageBeam->Hide();
+ rightDamageBeam->Activate( false );
+ }
+ if ( leftRetractBeam.IsValid() ) {
+ leftRetractBeam->Hide();
+ leftRetractBeam->Activate( false );
+ }
+ if ( rightRetractBeam.IsValid() ) {
+ rightRetractBeam->Hide();
+ rightRetractBeam->Activate( false );
+ }
+}
+
+void hhCreatureX::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) {
+ if ( spawnArgs.GetBool( "boss" ) ) {
+ if ( attacker && attacker->IsType( idPlayer::Type ) && idStr::Icmp( damageDefName, spawnArgs.GetString( "def_damageTelefrag", "damage_telefrag" ) ) == 0 ) {
+ //telefragged so move somewhere nice
+ SetState( GetScriptFunction( "state_Telefragged" ) );
+ return;
+ }
+ }
+
+ if ( !fl.takedamage ) {
+ return;
+ }
+ //check for splash damage or jenny-specific damage based on location
+ if ( AI_DEAD && spawnArgs.GetBool( "boss" ) ) {
+ const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName );
+ if ( ( damageDef && damageDef->GetFloat( "radius" ) > 0 ) ||
+ ( location && strcmp( GetDamageGroup( location ), "jenny" ) == 0 ) ) {
+ SetState( GetScriptFunction( "state_RealDeath" ) );
+ fl.takedamage = false;
+ }
+ }
+ hhMonsterAI::Damage( inflictor, attacker, dir, damageDefName, damageScale, location );
+}
+#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build
\ No newline at end of file
diff --git a/neo/prey/Prey/ai_creaturex.h b/neo/prey/Prey/ai_creaturex.h
new file mode 100644
index 00000000..827173ad
--- /dev/null
+++ b/neo/prey/Prey/ai_creaturex.h
@@ -0,0 +1,139 @@
+
+#ifndef __PREY_AI_CREATURE_H__
+#define __PREY_AI_CREATURE_H__
+
+
+class hhCreatureX : public hhMonsterAI {
+
+public:
+
+ CLASS_PROTOTYPE(hhCreatureX);
+#ifdef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build
+ void Event_GunRecharge( int onOff ) {};
+ void Event_LaserOn() {};
+ void Event_LaserOff() {};
+ void Event_UpdateLasers() {};
+ void Event_SetGunOffset( const idAngles &ang ) {};
+ void Event_EndLeftBeams() {};
+ void Event_EndRightBeams() {};
+ void Event_AssignRightMuzzleFx( hhEntityFx* fx ) {};
+ void Event_AssignLeftMuzzleFx( hhEntityFx* fx ) {};
+ void Event_AssignRightImpactFx( hhEntityFx* fx ) {};
+ void Event_AssignLeftImpactFx( hhEntityFx* fx ) {};
+ void Event_AssignLeftRechargeFx( hhEntityFx* fx ) {};
+ void Event_AssignRightRechargeFx( hhEntityFx* fx ) {};
+ void Event_AttackMissile( const char *jointname, const idDict *projDef, int boneDir ) {};
+ void Event_HudEvent( const char *eventName ) {};
+ void Event_RechargeHealth() {};
+ void Event_EndRecharge() {};
+ void Event_ResetRechargeBeam() {};
+ void Event_SparkLeft() {};
+ void Event_SparkRight() {};
+ void Event_LeftGunDeath() {};
+ void Event_RightGunDeath() {};
+ void Event_StartRechargeBeams() {};
+#else
+
+ ~hhCreatureX();
+ void Spawn();
+ void LinkScriptVariables( void );
+ void Think();
+ void Save( idSaveGame *savefile ) const;
+ void Restore( idRestoreGame *savefile );
+ void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location );
+
+ idScriptBool AI_GUN_TRACKING;
+ idScriptBool AI_RECHARGING;
+ idScriptBool AI_LEFT_FIRE;
+ idScriptBool AI_RIGHT_FIRE;
+ idScriptBool AI_LEFT_DAMAGED;
+ idScriptBool AI_RIGHT_DAMAGED;
+ idScriptBool AI_FIRING_LASER;
+ idScriptBool AI_HEALTH_TICK;
+ idScriptBool AI_GUN_EXPLODE;
+protected:
+ void Event_GunRecharge( int onOff );
+ void Event_LaserOn();
+ void Event_LaserOff();
+ void Event_UpdateLasers();
+ void Event_SetGunOffset( const idAngles &ang );
+ void Event_EndLeftBeams();
+ void Event_EndRightBeams();
+ void Event_AssignRightMuzzleFx( hhEntityFx* fx );
+ void Event_AssignLeftMuzzleFx( hhEntityFx* fx );
+ void Event_AssignRightImpactFx( hhEntityFx* fx );
+ void Event_AssignLeftImpactFx( hhEntityFx* fx );
+ void Event_AssignLeftRechargeFx( hhEntityFx* fx );
+ void Event_AssignRightRechargeFx( hhEntityFx* fx );
+ void Event_AttackMissile( const char *jointname, const idDict *projDef, int boneDir );
+ void Event_HudEvent( const char *eventName );
+ void Event_RechargeHealth();
+ void Event_EndRecharge();
+ void Event_ResetRechargeBeam();
+ void Event_SparkLeft();
+ void Event_SparkRight();
+ void Event_LeftGunDeath();
+ void Event_RightGunDeath();
+ void Event_StartRechargeBeams();
+
+ void MuzzleLeftOn();
+ void MuzzleRightOn();
+ void MuzzleLeftOff();
+ void MuzzleRightOff();
+ bool UpdateAnimationControllers( void );
+ virtual void Killed(idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location );
+ void Show();
+ void Activate( idEntity *activator );
+ idEntityPtr laserRight;
+ idEntityPtr laserLeft;
+ idEntityPtr preLaserRight;
+ idEntityPtr preLaserLeft;
+ idList< idEntityPtr > leftBeamList;
+ idList< idEntityPtr > rightBeamList;
+ idEntityPtr leftRechargeBeam;
+ idEntityPtr rightRechargeBeam;
+ idEntityPtr leftDamageBeam;
+ idEntityPtr rightDamageBeam;
+ idEntityPtr leftRetractBeam;
+ idEntityPtr rightRetractBeam;
+ idEntityPtr muzzleLeftFx;
+ idEntityPtr muzzleRightFx;
+ idEntityPtr impactLeftFx;
+ idEntityPtr impactRightFx;
+ idEntityPtr rechargeLeftFx;
+ idEntityPtr rechargeRightFx;
+ idEntityPtr retractLeftFx;
+ idEntityPtr retractRightFx;
+ int numBurstBeams;
+ bool bLaserLeftActive;
+ bool bLaserRightActive;
+ idVec3 targetStart_L;
+ idVec3 targetEnd_L;
+ idVec3 targetCurrent_L;
+ idVec3 targetStart_R;
+ idVec3 targetEnd_R;
+ idVec3 targetCurrent_R;
+ float targetAlpha_L;
+ float targetAlpha_R;
+ int leftGunHealth;
+ int rightGunHealth;
+ int leftGunLives;
+ int rightGunLives;
+ idAngles gunShake;
+ int nextBeamTime;
+ int nextLeftZapTime;
+ int nextRightZapTime;
+ idVec3 laserEndLeft;
+ idVec3 laserEndRight;
+ int nextLaserLeft;
+ int nextLaserRight;
+ int nextHealthTick;
+ int leftRechargerHealth;
+ int rightRechargerHealth;
+ idEntityPtr leftRecharger;
+ idEntityPtr rightRecharger;
+ bool bScripted;
+#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build
+};
+
+#endif /* __PREY_AI_CREATURE_H__ */
diff --git a/neo/prey/Prey/ai_droid.cpp b/neo/prey/Prey/ai_droid.cpp
new file mode 100644
index 00000000..bb9d7cf8
--- /dev/null
+++ b/neo/prey/Prey/ai_droid.cpp
@@ -0,0 +1,660 @@
+
+
+#include "precompiled.h"
+#pragma hdrstop
+
+#include "prey_local.h"
+
+const idEventDef EV_StartBurst("startBurst");
+const idEventDef EV_EndBurst("endBurst");
+const idEventDef EV_StartStaticBeam("startStaticBeam");
+const idEventDef EV_EndStaticBeam("endStaticBeam");
+const idEventDef EV_StartChargeShot("startChargeShot");
+const idEventDef EV_EndChargeShot("endChargeShot");
+const idEventDef EV_StartPathing("startPathing");
+const idEventDef EV_EndPathing("endPathing");
+const idEventDef EV_EndZipBeams("");
+const idEventDef EV_HealerReset("");
+
+CLASS_DECLARATION(hhMonsterAI, hhDroid)
+ EVENT(EV_StartStaticBeam, hhDroid::Event_StartStaticBeam)
+ EVENT(EV_EndStaticBeam, hhDroid::Event_EndStaticBeam)
+ EVENT(EV_StartBurst, hhDroid::Event_StartBurst)
+ EVENT(EV_EndBurst, hhDroid::Event_EndBurst)
+ EVENT(EV_StartChargeShot, hhDroid::Event_StartChargeShot)
+ EVENT(EV_EndChargeShot, hhDroid::Event_EndChargeShot)
+ EVENT(EV_StartPathing, hhDroid::Event_StartPathing)
+ EVENT(EV_EndPathing, hhDroid::Event_EndPathing)
+ EVENT(EV_EndZipBeams, hhDroid::Event_EndZipBeams)
+ EVENT(EV_HealerReset, hhDroid::Event_HealerReset)
+END_CLASS
+
+#ifndef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build
+hhDroid::hhDroid() {
+ burstLength = 0.0;
+ burstSpread = 0.0;
+ burstDuration = 0.0;
+ staticDuration = 0.0;
+ staticRange = 0.0;
+ numBurstBeams = 0;
+ beamOffset = vec3_zero;
+ chargeShotSize = 0.0;
+ old_fly_bob_strength = 0.0;
+ spinAngle = 0.0f;
+}
+
+hhDroid::~hhDroid() {
+ for ( int i = 0; i < numBurstBeams; i ++ ) {
+ SAFE_REMOVE( beamBurstList[i] );
+ }
+}
+
+void hhDroid::Show( void ) {
+ hhMonsterAI::Show();
+
+ PostEventSec(&EV_StartStaticBeam, gameLocal.random.RandomFloat() * spawnArgs.GetFloat( "staticFreq", "2.5" ) );
+}
+
+void hhDroid::Spawn(void)
+{
+ Event_SetMoveType(MOVETYPE_FLY);
+ lives = spawnArgs.GetInt( "lives", "3" );
+ numBurstBeams = spawnArgs.GetInt( "numBurstBeams", "5" );
+ burstLength = spawnArgs.GetFloat( "burstLength", "250" );
+ staticDuration = spawnArgs.GetFloat( "staticDuration", "2.0" );
+ burstDuration = spawnArgs.GetFloat( "burstDuration", "2.0" );
+ beamOffset = spawnArgs.GetVector( "beamOffset", "0 0 0" );
+ burstSpread = spawnArgs.GetFloat( "burstSpread", "1.0" );
+ enemyRange = spawnArgs.GetFloat( "enemy_range", "3000" );
+ flyDampening = spawnArgs.GetFloat( "fly_dampening", "0.01" );
+ bHealer = spawnArgs.GetBool( "healer" );
+
+ beamZip = hhBeamSystem::SpawnBeam( vec3_origin, spawnArgs.GetString( "def_beamZip" ) );
+ if( beamZip.IsValid() ) {
+ beamZip->Activate( false );
+ beamZip->SetOrigin( GetPhysics()->GetOrigin() + beamOffset );
+ beamZip->Bind( this, false );
+ }
+
+ for ( int i = 0; i < numBurstBeams; i ++ ) {
+ if ( gameLocal.random.RandomFloat() > 0.5f ) {
+ beamBurstList.Append( hhBeamSystem::SpawnBeam( vec3_origin, spawnArgs.GetString( "def_beamBurst1" ) ) );
+ } else {
+ beamBurstList.Append( hhBeamSystem::SpawnBeam( vec3_origin, spawnArgs.GetString( "def_beamBurst2" ) ) );
+ }
+ if( beamBurstList[i].IsValid() ) {
+ beamBurstList[i]->Activate( false );
+ beamBurstList[i]->SetOrigin( GetPhysics()->GetOrigin() + beamOffset );
+ beamBurstList[i]->Bind( this, false );
+ }
+ }
+
+ PostEventSec(&EV_StartStaticBeam, gameLocal.random.RandomFloat() * spawnArgs.GetFloat( "staticFreq", "2.5" ) );
+}
+
+bool hhDroid::Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) {
+ bool bPain = hhMonsterAI::Pain( inflictor, attacker, damage, dir, location );
+ if ( !bPain ) {
+ return bPain;
+ }
+
+ //activate some beams for damage fx
+ for ( int i = 0; i < beamBurstList.Num(); i ++ ) {
+ if ( beamBurstList[i].IsValid() ) {
+ beamBurstList[i]->Activate( true );
+ beamBurstList[i]->SetTargetLocation( beamBurstList[i]->GetOrigin() + burstLength * hhUtils::RandomSpreadDir( dir.ToMat3(), burstSpread ) );
+ PostEventSec(&EV_EndBurst, staticDuration);
+ }
+ }
+ return bPain;
+}
+
+void hhDroid::Think() {
+ PROFILE_SCOPE("AI", PROFMASK_NORMAL|PROFMASK_AI);
+ if (ai_skipThink.GetBool()) {
+ return;
+ }
+
+ hhMonsterAI::Think();
+
+ //update burst beams
+ idMat3 dir;
+ if ( enemy.IsValid() ) {
+ dir = (enemy->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin()).ToMat3();
+ } else {
+ dir = GetPhysics()->GetAxis();
+ }
+ for ( int i = 0; i < beamBurstList.Num(); i ++ ) {
+ if ( beamBurstList[i].IsValid() && beamBurstList[i]->IsActivated() ) {
+ beamBurstList[i]->SetTargetLocation( beamBurstList[i]->GetOrigin() + burstLength * hhUtils::RandomSpreadDir( hhUtils::RandomVector().ToMat3(), burstSpread ) );
+ }
+ }
+
+ if ( staticPoint.IsValid() && beamBurstList[0].IsValid() ) {
+ beamBurstList[0]->SetTargetLocation( staticPoint->GetPhysics()->GetOrigin() );
+ }
+
+ if ( chargeShot.IsValid() ) {
+ chargeShot->SetShaderParm( 9, 1 );
+ chargeShot->SetShaderParm( 10, chargeShotSize );
+ chargeShotSize += spawnArgs.GetFloat( "chargeSizeDelta" );
+ chargeShot->GetPhysics()->DisableClip();
+ }
+}
+
+void hhDroid::Event_StartChargeShot(void) {
+ //spawn chargeshot projectile
+ const idDict *projectileDef = gameLocal.FindEntityDefDict( spawnArgs.GetString("def_chargeshot") );
+ if ( projectileDef ) {
+ chargeShot = hhProjectile::SpawnProjectile( projectileDef );
+ if ( chargeShot.IsValid() ) {
+ chargeShotSize = spawnArgs.GetFloat( "chargeSizeStart", "1.0" );
+ idVec3 launchStart = GetPhysics()->GetOrigin() + spawnArgs.GetVector( "chargeOffset", "15 0 0" ) * GetRenderEntity()->axis;
+ chargeShot->Create(this, launchStart, GetRenderEntity()->axis);
+ chargeShot->Launch(chargeShot->GetPhysics()->GetOrigin(), chargeShot->GetPhysics()->GetAxis(), vec3_zero );
+ chargeShot->Bind(this, true);
+ }
+ }
+}
+
+void hhDroid::Event_EndChargeShot(void) {
+ //launch projectile
+ if ( chargeShot.IsValid() ) {
+ chargeShot->Unbind();
+ chargeShot->StartTracking();
+ chargeShot.Clear();
+ }
+}
+
+void hhDroid::Event_StartBurst(void) {
+ gameLocal.RadiusDamage( GetPhysics()->GetOrigin(), this, this, this, this, spawnArgs.GetString("def_burstdamage") );
+
+ //burst effect
+ idMat3 dir;
+ if ( enemy.IsValid() ) {
+ dir = (enemy->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin()).ToMat3();
+ } else {
+ dir = GetPhysics()->GetAxis();
+ }
+ for ( int i = 0; i < beamBurstList.Num(); i++ ) {
+ if ( beamBurstList[i].IsValid() ) {
+ beamBurstList[i]->Activate( true );
+ beamBurstList[i]->SetTargetLocation( beamBurstList[i]->GetOrigin() + burstLength * hhUtils::RandomSpreadDir( hhUtils::RandomVector().ToMat3(), burstSpread ) );
+ }
+ }
+ PostEventSec(&EV_EndBurst, burstDuration);
+}
+
+void hhDroid::Event_EndBurst(void) {
+ for ( int i = 0; i < beamBurstList.Num(); i ++ ) {
+ if ( beamBurstList[i].IsValid() ) {
+ beamBurstList[i]->Activate( false );
+ }
+ }
+}
+
+void hhDroid::Event_EndStaticBeam(void) {
+ if ( beamBurstList[0].IsValid() ) {
+ beamBurstList[0]->Activate( false );
+ }
+ if ( staticPoint.IsValid() ) {
+ staticPoint.Clear();
+ }
+
+ //wait random time until next static beam
+ if ( !IsHidden() && !AI_DEAD ) {
+ PostEventSec(&EV_StartStaticBeam, gameLocal.random.RandomFloat() * spawnArgs.GetFloat( "staticFreq", "2.5" ) );
+ }
+}
+
+void hhDroid::Event_StartStaticBeam(void) {
+ idVec3 targetPoint;
+ idEntity *entityList[ MAX_GENTITIES ];
+
+ if ( IsHidden() || AI_DEAD ) {
+ return;
+ }
+
+ //find nearest staticPoint
+ float radius = spawnArgs.GetFloat( "staticRange" );
+ int listedEntities = gameLocal.EntitiesWithinRadius( GetPhysics()->GetOrigin(), radius, entityList, MAX_GENTITIES );
+ for( int i = 0; i < listedEntities; i++ ) {
+ if ( entityList[ i ] && entityList[ i ]->spawnArgs.GetInt( "droidBeam", "0" ) ) {
+ staticPoint = entityList[ i ];
+ break;
+ }
+ }
+
+ //activate one beam
+ if ( staticPoint.IsValid() && beamBurstList[0].IsValid() ) {
+ StartSound( "snd_staticBeam", SND_CHANNEL_BODY, 0, true, NULL );
+ beamBurstList[0]->Activate( true );
+ beamBurstList[0]->SetTargetLocation( beamBurstList[0]->GetOrigin() + burstLength * hhUtils::RandomVector() );
+ staticPoint->ActivateTargets( this );
+ PostEventSec(&EV_EndStaticBeam, staticDuration);
+ }
+}
+
+void hhDroid::FlyTurn( void ) {
+ if ( AI_PATHING ) {
+ hhMonsterAI::FlyTurn();
+ return;
+ }
+ if ( AI_ENEMY_VISIBLE || move.moveCommand == MOVE_FACE_ENEMY ) {
+ TurnToward( lastVisibleEnemyPos );
+ } else if ( ( move.moveCommand == MOVE_FACE_ENTITY ) && move.goalEntity.GetEntity() ) {
+ TurnToward( move.goalEntity.GetEntity()->GetPhysics()->GetOrigin() );
+ } else if ( move.speed > 0.0f ) {
+ const idVec3 &vel = physicsObj.GetLinearVelocity();
+ if ( vel.ToVec2().LengthSqr() > 0.1f ) {
+ TurnToward( vel.ToYaw() );
+ }
+ }
+ Turn();
+}
+
+void hhDroid::Event_StartPathing(void) {
+ AI_PATHING = true;
+ old_fly_bob_strength = fly_bob_strength;
+ fly_bob_strength = 0.0;
+ ignore_obstacles = true;
+}
+
+void hhDroid::Event_EndPathing(void) {
+ AI_PATHING = false;
+ fly_bob_strength = old_fly_bob_strength;
+ ignore_obstacles = false;
+}
+
+void hhDroid::FlyMove( void ) {
+ idVec3 goalPos;
+ idVec3 oldorigin;
+ idVec3 newDest;
+
+ AI_BLOCKED = false;
+ if ( ( move.moveCommand != MOVE_NONE ) && ReachedPos( move.moveDest, move.moveCommand ) ) {
+ if ( AI_PATHING ) {
+ physicsObj.SetLinearVelocity( idVec3(0,0,0) );
+ }
+ StopMove( MOVE_STATUS_DONE );
+ }
+
+ if ( move.moveCommand != MOVE_TO_POSITION_DIRECT ) {
+ idVec3 vel = physicsObj.GetLinearVelocity();
+
+ if ( GetMovePos( goalPos ) ) {
+ move.obstacle = NULL;
+ }
+
+ if ( move.speed ) {
+ FlySeekGoal( vel, goalPos );
+ }
+
+ // add in bobbing
+ AddFlyBob( vel );
+
+ if ( enemy.GetEntity() && ( move.moveCommand != MOVE_TO_POSITION ) ) {
+ float dist = ( GetPhysics()->GetOrigin() - enemy->GetPhysics()->GetOrigin() ).LengthFast();
+ if ( dist < enemyRange && enemyRange > 0 ) {
+ AdjustFlyHeight( vel, goalPos );
+ }
+ }
+
+ AdjustFlySpeed( vel );
+
+ physicsObj.SetLinearVelocity( vel );
+ }
+
+ // turn
+ FlyTurn();
+
+ // run the physics for this frame
+ oldorigin = physicsObj.GetOrigin();
+ physicsObj.UseFlyMove( true );
+ physicsObj.UseVelocityMove( false );
+ physicsObj.SetDelta( vec3_zero );
+ physicsObj.ForceDeltaMove( disableGravity );
+ RunPhysics();
+
+ monsterMoveResult_t moveResult = physicsObj.GetMoveResult();
+ if ( !af_push_moveables && attack.Length() && TestMelee() ) {
+ DirectDamage( attack, enemy.GetEntity() );
+ } else {
+ idEntity *blockEnt = physicsObj.GetSlideMoveEntity();
+ if ( blockEnt && blockEnt->IsType( idMoveable::Type ) && blockEnt->GetPhysics()->IsPushable() ) {
+ KickObstacles( viewAxis[ 0 ], kickForce, blockEnt );
+ } else if ( moveResult == MM_BLOCKED ) {
+ move.blockTime = gameLocal.time + 500;
+ AI_BLOCKED = true;
+ }
+ }
+
+ idVec3 org = physicsObj.GetOrigin();
+ if ( oldorigin != org ) {
+ TouchTriggers();
+ }
+
+ if ( ai_debugMove.GetBool() ) {
+ gameRenderWorld->DebugLine( colorCyan, oldorigin, physicsObj.GetOrigin(), 4000 );
+ gameRenderWorld->DebugBounds( colorOrange, physicsObj.GetBounds(), org, gameLocal.msec );
+ gameRenderWorld->DebugBounds( colorMagenta, physicsObj.GetBounds(), move.moveDest, gameLocal.msec );
+ gameRenderWorld->DebugLine( colorRed, org, org + physicsObj.GetLinearVelocity(), gameLocal.msec, true );
+ gameRenderWorld->DebugLine( colorBlue, org, goalPos, gameLocal.msec, true );
+ gameRenderWorld->DebugLine( colorYellow, org + EyeOffset(), org + EyeOffset() + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 16.0f, gameLocal.msec, true );
+ DrawRoute();
+ }
+}
+
+idProjectile *hhDroid::LaunchProjectile( const char *jointname, idEntity *target, bool clampToAttackCone, const idDict* desiredProjectileDef ) {
+ //jsh overridden to allow per-projectile accuracy
+ idVec3 muzzle;
+ idVec3 dir;
+ idVec3 start;
+ trace_t tr;
+ idBounds projBounds;
+ float distance;
+ const idClipModel *projClip;
+ float attack_accuracy;
+ float attack_cone;
+ float projectile_spread;
+ float diff;
+ float angle;
+ float spin;
+ idAngles ang;
+ int num_projectiles;
+ int i;
+ idMat3 axis;
+ idVec3 tmp;
+ idProjectile *lastProjectile;
+
+ //HUMANHEAD mdc - added to support multiple projectiles
+ if( desiredProjectileDef ) { //try to set our projectile to the desiredProjectile
+ int projIndex = FindProjectileInfo( desiredProjectileDef );
+ if( projIndex >= 0 ) {
+ SetCurrentProjectile( projIndex );
+ }
+ }
+ //HUMANHEAD END
+
+ if ( !projectileDef ) {
+ gameLocal.Warning( "%s (%s) doesn't have a projectile specified", name.c_str(), GetEntityDefName() );
+ return NULL;
+ }
+
+ if ( projectileDef->GetFloat( "attack_accuracy" ) ) {
+ attack_accuracy = projectileDef->GetFloat( "attack_accuracy", "7" );
+ } else {
+ attack_accuracy = spawnArgs.GetFloat( "attack_accuracy", "7" );
+ }
+ attack_cone = spawnArgs.GetFloat( "attack_cone", "70" );
+ if ( projectileDef->GetFloat( "projectile_spread" ) ) {
+ projectile_spread = projectileDef->GetFloat( "projectile_spread", "0" );
+ } else {
+ projectile_spread = spawnArgs.GetFloat( "projectile_spread", "0" );
+ }
+ if ( projectileDef->GetFloat( "num_projectiles" ) ) {
+ num_projectiles = projectileDef->GetFloat( "num_projectiles", "1" );
+ } else {
+ num_projectiles = spawnArgs.GetFloat( "num_projectiles", "1" );
+ }
+
+
+ GetMuzzle( jointname, muzzle, axis );
+
+ if ( !projectile.GetEntity() ) {
+ CreateProjectile( muzzle, axis[ 0 ] );
+ }
+
+ lastProjectile = projectile.GetEntity();
+
+ if ( target != NULL ) {
+ tmp = target->GetPhysics()->GetAbsBounds().GetCenter() - muzzle;
+ tmp.Normalize();
+ axis = tmp.ToMat3();
+ } else {
+ axis = viewAxis;
+ }
+
+ // rotate it because the cone points up by default
+ tmp = axis[2];
+ axis[2] = axis[0];
+ axis[0] = -tmp;
+
+ // make sure the projectile starts inside the monster bounding box
+ const idBounds &ownerBounds = physicsObj.GetAbsBounds();
+ projClip = lastProjectile->GetPhysics()->GetClipModel();
+ projBounds = projClip->GetBounds().Rotate( axis );
+
+ // check if the owner bounds is bigger than the projectile bounds
+ if ( ( ( ownerBounds[1][0] - ownerBounds[0][0] ) > ( projBounds[1][0] - projBounds[0][0] ) ) &&
+ ( ( ownerBounds[1][1] - ownerBounds[0][1] ) > ( projBounds[1][1] - projBounds[0][1] ) ) &&
+ ( ( ownerBounds[1][2] - ownerBounds[0][2] ) > ( projBounds[1][2] - projBounds[0][2] ) ) ) {
+ if ( (ownerBounds - projBounds).RayIntersection( muzzle, viewAxis[ 0 ], distance ) ) {
+ start = muzzle + distance * viewAxis[ 0 ];
+ } else {
+ start = ownerBounds.GetCenter();
+ }
+ } else {
+ // projectile bounds bigger than the owner bounds, so just start it from the center
+ start = ownerBounds.GetCenter();
+ }
+
+ gameLocal.clip.Translation( tr, start, muzzle, projClip, axis, MASK_SHOT_RENDERMODEL, this );
+ muzzle = tr.endpos;
+
+ // set aiming direction
+ GetAimDir( muzzle, target, this, dir );
+ ang = dir.ToAngles();
+
+ // adjust his aim so it's not perfect. uses sine based movement so the tracers appear less random in their spread.
+ float t = MS2SEC( gameLocal.time + entityNumber * 497 );
+ ang.pitch += idMath::Sin16( t * 5.1 ) * attack_accuracy;
+ ang.yaw += idMath::Sin16( t * 6.7 ) * attack_accuracy;
+
+ if ( !AI_WALLWALK && clampToAttackCone ) {
+ // clamp the attack direction to be within monster's attack cone so he doesn't do
+ // things like throw the missile backwards if you're behind him
+ diff = idMath::AngleDelta( ang.yaw, current_yaw );
+ if ( diff > attack_cone ) {
+ ang.yaw = current_yaw + attack_cone;
+ } else if ( diff < -attack_cone ) {
+ ang.yaw = current_yaw - attack_cone;
+ }
+ }
+
+ axis = ang.ToMat3();
+
+ float spreadRad = DEG2RAD( projectile_spread );
+ for( i = 0; i < num_projectiles; i++ ) {
+ // spread the projectiles out
+ angle = idMath::Sin( spreadRad * gameLocal.random.RandomFloat() );
+ spin = (float)DEG2RAD( 360.0f ) * gameLocal.random.RandomFloat();
+ dir = axis[ 0 ] + axis[ 2 ] * ( angle * idMath::Sin( spin ) ) - axis[ 1 ] * ( angle * idMath::Cos( spin ) );
+ dir.Normalize();
+
+ // launch the projectile
+ if ( !projectile.GetEntity() ) {
+ CreateProjectile( muzzle, dir );
+ }
+ lastProjectile = projectile.GetEntity();
+ lastProjectile->Launch( muzzle, dir, vec3_origin );
+ projectile = NULL;
+ }
+
+ TriggerWeaponEffects( muzzle, axis );
+
+ lastAttackTime = gameLocal.time;
+
+//HUMANHEAD mdc - added to support multiple projectiles
+ projectile = NULL;
+ SetCurrentProjectile( projectileDefaultDefIndex ); //set back to our default projectile to be on the safe side
+//HUMANHEAD END
+
+ return lastProjectile;
+}
+
+void hhDroid::Event_FlyZip() {
+ idVec3 old_origin = GetOrigin();
+ hhMonsterAI::Event_FlyZip();
+ if ( old_origin != GetOrigin() ) {
+ if ( beamZip.IsValid() ) {
+ beamZip->SetTargetLocation( old_origin );
+ }
+ if ( beamZip.IsValid() ) {
+ beamZip->Activate( true );
+ }
+ PostEventSec( &EV_EndZipBeams, spawnArgs.GetFloat( "zip_beam_duration", "1.0" ) );
+ }
+}
+
+void hhDroid::Event_EndZipBeams() {
+ if ( beamZip.IsValid() ) {
+ beamZip->Activate( false );
+ }
+}
+
+void hhDroid::Event_HealerReset() {
+ fl.takedamage = true;
+}
+
+void hhDroid::Killed(idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) {
+ if ( bHealer ) {
+ lives--;
+ if ( lives <= 0 ) {
+ hhMonsterAI::Killed( inflictor, attacker, damage, dir, location );
+ } else {
+ const char *defName = spawnArgs.GetString( "fx_pain" );
+ if (defName && defName[0]) {
+ hhFxInfo fxInfo;
+ fxInfo.RemoveWhenDone( true );
+ idEntityFx *painFx = SpawnFxLocal( defName, GetOrigin(), GetAxis(), &fxInfo, gameLocal.isClient );
+ if ( painFx ) {
+ painFx->Bind( this, true );
+ }
+ }
+ health = spawnArgs.GetInt( "health" );
+ fl.takedamage = false;
+ PostEventSec( &EV_HealerReset, spawnArgs.GetFloat( "reset_delay", "0.8" ) );
+ }
+ return;
+ }
+ if ( AI_DEAD ) {
+ AI_DAMAGE = true;
+ return;
+ }
+ hhMonsterAI::Killed( inflictor, attacker, damage, dir, location );
+ for ( int i = 0; i < beamBurstList.Num(); i ++ ) {
+ if ( beamBurstList[i].IsValid() ) {
+ beamBurstList[i]->Activate( true );
+ beamBurstList[i]->SetTargetLocation( beamBurstList[i]->GetOrigin() + burstLength * hhUtils::RandomSpreadDir( dir.ToMat3(), burstSpread ) );
+ PostEventSec(&EV_EndBurst, staticDuration);
+ }
+ }
+}
+
+void hhDroid::AdjustFlySpeed( idVec3 &vel ) {
+ float speed;
+
+ // apply dampening
+ vel -= vel * flyDampening * MS2SEC( gameLocal.msec );
+
+ // gradually speed up/slow down to desired speed
+ speed = vel.Normalize();
+ speed += ( move.speed - speed ) * MS2SEC( gameLocal.msec );
+ if ( speed < 0.0f ) {
+ speed = 0.0f;
+ } else if ( move.speed && ( speed > move.speed ) ) {
+ speed = move.speed;
+ }
+
+ vel *= speed;
+}
+
+void hhDroid::UpdateModelTransform( void ) {
+ idVec3 origin;
+ idMat3 axis;
+
+ if ( GetPhysicsToVisualTransform( origin, axis ) ) {
+ renderEntity.axis = axis * GetPhysics()->GetAxis();
+ renderEntity.origin = GetPhysics()->GetOrigin() + origin * renderEntity.axis;
+
+ if ( bHealer ) {
+ renderEntity.axis = idAngles( 0, 0, spinAngle ).ToMat3() * renderEntity.axis;
+ spinAngle += 10;
+ if ( spinAngle > 360.0f ) {
+ spinAngle = 0.0f;
+ }
+ }
+ } else {
+ renderEntity.axis = GetPhysics()->GetAxis();
+ renderEntity.origin = GetPhysics()->GetOrigin();
+ }
+}
+
+/*
+=====================
+hhDroid::Save
+=====================
+*/
+void hhDroid::Save( idSaveGame *savefile ) const {
+ int num = beamBurstList.Num();
+ savefile->WriteInt( num );
+ for ( int i = 0; i < num; i++ ) {
+ beamBurstList[i].Save( savefile );
+ }
+
+ staticPoint.Save( savefile );
+ chargeShot.Save( savefile );
+ beamZip.Save( savefile );
+
+ savefile->WriteVec3( savedGravity );
+ savefile->WriteFloat( chargeShotSize );
+ savefile->WriteFloat( burstLength );
+ savefile->WriteFloat( burstSpread );
+ savefile->WriteFloat( burstDuration );
+ savefile->WriteFloat( staticDuration );
+ savefile->WriteFloat( staticRange );
+ savefile->WriteInt( numBurstBeams );
+ savefile->WriteVec3( beamOffset );
+ savefile->WriteFloat( old_fly_bob_strength );
+ savefile->WriteFloat( enemyRange );
+ savefile->WriteFloat( flyDampening );
+ savefile->WriteFloat( spinAngle );
+ savefile->WriteBool( bHealer );
+ savefile->WriteInt( lives );
+}
+
+/*
+=====================
+hhDroid::Restore
+=====================
+*/
+void hhDroid::Restore( idRestoreGame *savefile ) {
+ int num;
+ savefile->ReadInt( num );
+ beamBurstList.SetNum( num );
+ for ( int i = 0; i < num; i++ ) {
+ beamBurstList[i].Restore( savefile );
+ }
+
+ staticPoint.Restore( savefile );
+ chargeShot.Restore( savefile );
+ beamZip.Restore( savefile );
+
+ savefile->ReadVec3( savedGravity );
+ savefile->ReadFloat( chargeShotSize );
+ savefile->ReadFloat( burstLength );
+ savefile->ReadFloat( burstSpread );
+ savefile->ReadFloat( burstDuration );
+ savefile->ReadFloat( staticDuration );
+ savefile->ReadFloat( staticRange );
+ savefile->ReadInt( numBurstBeams );
+ savefile->ReadVec3( beamOffset );
+ savefile->ReadFloat( old_fly_bob_strength );
+ savefile->ReadFloat( enemyRange );
+ savefile->ReadFloat( flyDampening );
+ savefile->ReadFloat( spinAngle );
+ savefile->ReadBool( bHealer );
+ savefile->ReadInt( lives );
+}
+#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build
\ No newline at end of file
diff --git a/neo/prey/Prey/ai_droid.h b/neo/prey/Prey/ai_droid.h
new file mode 100644
index 00000000..427a9856
--- /dev/null
+++ b/neo/prey/Prey/ai_droid.h
@@ -0,0 +1,73 @@
+
+#ifndef __PREY_AI_DROID_H__
+#define __PREY_AI_DROID_H__
+
+class hhDroid : public hhMonsterAI {
+
+public:
+
+ CLASS_PROTOTYPE(hhDroid);
+
+#ifdef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build
+ void Event_StartBurst(void) {};
+ void Event_EndBurst(void) {};
+ void Event_StartStaticBeam(void) {};
+ void Event_EndStaticBeam(void) {};
+ void Event_StartChargeShot(void) {};
+ void Event_EndChargeShot(void) {};
+ void Event_StartPathing(void) {};
+ void Event_EndPathing(void) {};
+ void Event_FlyZip() {};
+ void Event_EndZipBeams() {};
+ void Event_HealerReset() {}
+#else
+ hhDroid();
+ ~hhDroid();
+ void Spawn( void );
+ virtual void Think( void );
+ void FlyTurn( void );
+ void Save( idSaveGame *savefile ) const;
+ void Restore( idRestoreGame *savefile );
+ void AdjustFlySpeed( idVec3 &vel );
+protected:
+ idList< idEntityPtr > beamBurstList;
+ idEntityPtr beamZip;
+ idEntityPtr staticPoint;
+ idEntityPtr chargeShot;
+ void Event_StartBurst(void);
+ void Event_EndBurst(void);
+ void Event_StartStaticBeam(void);
+ void Event_EndStaticBeam(void);
+ void Event_StartChargeShot(void);
+ void Event_EndChargeShot(void);
+ void Event_StartPathing(void);
+ void Event_EndPathing(void);
+ void Event_FlyZip();
+ void Event_EndZipBeams();
+ void Event_HealerReset();
+ idProjectile *LaunchProjectile( const char *jointname, idEntity *target, bool clampToAttackCone, const idDict* desiredProjectileDef );
+ virtual bool Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location );
+ virtual void Show( void );
+ void FlyMove( void );
+ void Killed(idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location );
+ void UpdateModelTransform();
+
+ idVec3 savedGravity;
+ float chargeShotSize;
+ float burstLength;
+ float burstSpread;
+ float burstDuration;
+ float staticDuration; //how long staticbeam lasts
+ float staticRange; //distance to check for staticbeam points
+ int numBurstBeams;
+ idVec3 beamOffset;
+ float old_fly_bob_strength;
+ float enemyRange;
+ float flyDampening;
+ int lives;
+ float spinAngle;
+ bool bHealer;
+#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build
+};
+
+#endif
\ No newline at end of file
diff --git a/neo/prey/Prey/ai_gasbag_simple.cpp b/neo/prey/Prey/ai_gasbag_simple.cpp
new file mode 100644
index 00000000..ccb9f828
--- /dev/null
+++ b/neo/prey/Prey/ai_gasbag_simple.cpp
@@ -0,0 +1,801 @@
+
+
+#include "precompiled.h"
+#pragma hdrstop
+
+#include "prey_local.h"
+
+const idEventDef EV_AcidBlast("acidBlast");
+const idEventDef EV_AcidDrip("");
+const idEventDef EV_DeathCloud("");
+const idEventDef EV_LaunchPod("launchPod");
+const idEventDef EV_NewPod("", "e");
+const idEventDef EV_SpawnBlastDebris("");
+const idEventDef EV_ChargeEnemy("chargeEnemy");
+const idEventDef EV_GrabEnemy("grabEnemy", NULL, 'f');
+const idEventDef EV_BiteEnemy("biteEnemy");
+const idEventDef EV_DirectMoveToPosition("directMoveToPosition", "v");
+const idEventDef EV_BindUnfroze("", "e");
+const idEventDef EV_GrabCheck("grabCheck", "", 'd');
+const idEventDef EV_MoveToGrabPosition("moveToGrabPosition");
+const idEventDef EV_CheckRange("checkRange", "", 'd');
+const idEventDef EV_EnemyRangeZ("enemyRangeZ", "", 'f');
+
+CLASS_DECLARATION(hhMonsterAI, hhGasbagSimple)
+ EVENT(EV_AcidBlast, hhGasbagSimple::Event_AcidBlast)
+ EVENT(EV_AcidDrip, hhGasbagSimple::Event_AcidDrip)
+ EVENT(EV_DeathCloud, hhGasbagSimple::Event_DeathCloud)
+ EVENT(EV_LaunchPod, hhGasbagSimple::Event_LaunchPod)
+ EVENT(EV_NewPod, hhGasbagSimple::Event_NewPod)
+ EVENT(EV_SpawnBlastDebris, hhGasbagSimple::Event_SpawnBlastDebris)
+ EVENT(EV_ChargeEnemy, hhGasbagSimple::Event_ChargeEnemy)
+ EVENT(EV_GrabEnemy, hhGasbagSimple::Event_GrabEnemy)
+ EVENT(EV_BiteEnemy, hhGasbagSimple::Event_BiteEnemy)
+ EVENT(EV_DirectMoveToPosition, hhGasbagSimple::Event_DirectMoveToPosition)
+ EVENT(EV_BindUnfroze, hhGasbagSimple::Event_BindUnfroze)
+ EVENT(EV_GrabCheck, hhGasbagSimple::Event_GrabCheck)
+ EVENT(EV_MoveToGrabPosition, hhGasbagSimple::Event_MoveToGrabPosition)
+ EVENT(EV_CheckRange, hhGasbagSimple::Event_CheckRange)
+ EVENT(EV_EnemyRangeZ, hhGasbagSimple::Event_EnemyRangeZ)
+END_CLASS
+
+static const idEventDef EV_Unfreeze( "" );
+
+#ifndef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build
+//
+// ~hhGasbagSimple()
+//
+hhGasbagSimple::~hhGasbagSimple(void) {
+ // Make sure to unbind the player if we've grabbed him to prevent deleting him
+ if (enemy.IsValid() && enemy->IsBoundTo(this) && enemy->IsType(hhPlayer::Type)) {
+ enemy->Unbind();
+ enemy->PostEventMS(&EV_Unfreeze, 0);
+ }
+}
+
+//
+// Spawn()
+//
+void hhGasbagSimple::Spawn(void) {
+ Event_SetMoveType(MOVETYPE_FLY); // So we ignore gravity zones from the beginning
+ podOffset = spawnArgs.GetVector("pod_offset", "0 0 64");
+ podRange = spawnArgs.GetFloat("podRange", "250");
+ dripCount = 0;
+ nextWoundTime = 0;
+
+ bindController = static_cast( gameLocal.SpawnClientObject(spawnArgs.GetString("def_bindController"), NULL) );
+ HH_ASSERT( bindController.IsValid() );
+ bindController->fl.networkSync = false;
+
+ float yawLimit = spawnArgs.GetFloat("yawlimit", "180");
+ const char *handName = spawnArgs.GetString("def_tractorhand");
+ const char *animName = spawnArgs.GetString("boundanim");
+
+ bindController->SetRiderParameters(animName, handName, yawLimit, 0.0f);
+ bindController->Bind(this, true);
+ bindController->SetOrigin(spawnArgs.GetVector("grab_offset"));
+ bindController->fl.neverDormant = true;
+}
+
+//
+// Ticker()
+//
+void hhGasbagSimple::Ticker(void) {
+ for (int i = 0; i < podList.Num(); i++) {
+ if (!podList[i].IsValid()) {
+ podList.RemoveIndex(i);
+ i--; // This index is gone, so don't increment
+ continue;
+ }
+ }
+
+ // Update the podcount
+ AI_PODCOUNT = podList.Num();
+
+ if (podList.Num() == 0) {
+ BecomeInactive(TH_TICKER);
+ }
+}
+
+//
+// Save()
+//
+void hhGasbagSimple::Save(idSaveGame *savefile) const {
+ savefile->WriteInt(dripCount);
+
+ savefile->WriteInt(podList.Num());
+ for (int i = 0; i < podList.Num(); i++) {
+ podList[i].Save(savefile);
+ }
+ bindController.Save(savefile);
+}
+
+//
+// Restore()
+//
+void hhGasbagSimple::Restore(idRestoreGame *savefile) {
+ Spawn();
+ savefile->ReadInt(dripCount);
+
+ int num;
+ savefile->ReadInt(num);
+ podList.SetNum(num);
+ for (int i = 0; i < num; i++) {
+ podList[i].Restore(savefile);
+ }
+ bindController.Restore(savefile);
+
+ nextWoundTime = 0;
+}
+
+//
+// Event_AcidBlast()
+//
+void hhGasbagSimple::Event_AcidBlast(void) {
+ if (health <= 0) { // Don't launch a pod if we're dead
+ return;
+ }
+
+ if (!enemy.IsValid()) {
+ return;
+ }
+
+ idEntity *target = enemy.GetEntity();
+ idVec3 targetOrigin = target->GetOrigin();
+ float len, nearest = podRange;
+
+ // See if we have a pod near the enemy. If we do, target it instead.
+ idEntity *entList[100];
+ int num = gameLocal.EntitiesWithinRadius(targetOrigin, podRange, entList, 100);
+ for (int i = 0; i < num; i++) {
+ if (!entList[i]->IsType(hhPod::Type)) {
+ continue;
+ }
+
+ len = (entList[i]->GetOrigin() - targetOrigin).Length();
+
+ // Always target the pod nearest to the enemy
+ if (len < nearest) {
+ target = entList[i];
+ nearest = len;
+ }
+ }
+
+ // Spawn a "muzzle flash"-like fx
+ hhFxInfo fx;
+ fx.SetEntity(this);
+ fx.RemoveWhenDone(true);
+ SpawnFxLocal(spawnArgs.GetString("fx_fire"), GetOrigin() + idVec3(0, 0, -400), enemy->GetPhysics()->GetGravityNormal().ToMat3(), &fx);
+
+ // Spawn an acid blob in the player's general direction
+ const idDict *projDef = gameLocal.FindEntityDefDict(spawnArgs.GetString("def_projectile"), false);
+ LaunchProjectile(spawnArgs.GetString("acidbone", "LmbRt"), target, false, projDef);
+
+ PostEventMS(&EV_SpawnBlastDebris, 100 + gameLocal.random.RandomInt(100));
+
+ dripCount = gameLocal.random.RandomInt(3) + 1;
+ PostEventMS(&EV_AcidDrip, 200 + gameLocal.random.RandomInt(100)); // Drip acid after firing
+}
+
+//
+// Event_SpawnBlastDebris()
+//
+void hhGasbagSimple::Event_SpawnBlastDebris(void) {
+ idDict args;
+ args.Clear();
+ args.Set("origin", (GetPhysics()->GetOrigin() + spawnArgs.GetVector("blast_debris_offset", "0 0 128")).ToString());
+ gameLocal.SpawnObject("debrisSpawner_gasbag_fire", &args);
+}
+
+//
+// Event_AcidDrip()
+//
+void hhGasbagSimple::Event_AcidDrip(void) {
+ assert(dripCount > 0);
+ dripCount--;
+
+ idVec3 target = GetPhysics()->GetOrigin();
+ target.z -= 128;
+
+ idVec3 rndVec = hhUtils::RandomVector() * gameLocal.random.RandomInt(20);
+ const idDict *projDef = gameLocal.FindEntityDefDict(spawnArgs.GetString("def_drip_projectile", "projectile_acidspray_gasbag"), false);
+ LaunchProjectileAtVec(spawnArgs.GetString("acidbone", "LmbRt"), target + rndVec, false, projDef);
+
+ if (dripCount > 0) {
+ PostEventMS(&EV_AcidDrip, 200 + gameLocal.random.RandomInt(100)); // Fire again in 200-300ms
+ }
+}
+
+//
+// Event_DeathCloud()
+//
+void hhGasbagSimple::Event_DeathCloud(void) {
+ const char *fx = spawnArgs.GetString("fx_death");
+ if (!fx || !fx[0]) {
+ return;
+ }
+ hhFxInfo fxInfo;
+
+ fxInfo.RemoveWhenDone(true);
+ BroadcastFxInfo(fx, GetOrigin(), GetAxis(), &fxInfo);
+}
+
+//
+// Event_LaunchPod()
+//
+void hhGasbagSimple::Event_LaunchPod() {
+ if (health <= 0) { // Don't launch a pod if we're dying
+ return;
+ }
+
+ idVec3 target, origin, offset = vec3_origin;
+ if (enemy.IsValid()) {
+ // Target a random area around the enemy
+ origin = enemy->GetEyePosition();
+ if (gameLocal.random.RandomInt(100) > 30) { // Possibly target the enemy exactly
+ offset.x = (gameLocal.random.RandomFloat() * 100.0f) - 50.0f;
+ offset.y = (gameLocal.random.RandomFloat() * 100.0f) - 50.0f;
+ }
+ } else {
+ // Target a random area beneath us
+ origin = GetOrigin();
+ offset.x = gameLocal.random.RandomFloat() * 10.0f;
+ offset.y = gameLocal.random.RandomFloat() * 10.0f;
+ offset.z = -10.0f;
+ }
+ target = origin + offset;
+
+ const idDict *projDef = gameLocal.FindEntityDefDict(spawnArgs.GetString("def_pod_projectile", "projectile_gasbag_pod"), false);
+ LaunchProjectileAtVec(spawnArgs.GetString("acidbone", "LmbRt"), target, false, projDef);
+}
+
+//
+// Event_NewPod
+//
+void hhGasbagSimple::Event_NewPod(hhPod *pod) {
+ HH_ASSERT(pod && pod->IsType(hhPod::Type));
+
+ podList.Append(idEntityPtr(pod));
+
+ // Update the podcount
+ AI_PODCOUNT = podList.Num();
+
+ BecomeActive(TH_TICKER);
+}
+
+//
+// Killed
+//
+void hhGasbagSimple::Killed(idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location) {
+ bool wasDead = (AI_DEAD != 0);
+
+ hhMonsterAI::Killed(inflictor, attacker, damage, dir, location);
+
+ if (wasDead) {
+ return;
+ }
+
+ CancelEvents(&EV_AcidDrip);
+ CancelEvents(&EV_AcidBlast);
+ CancelEvents(&EV_LaunchPod);
+ dripCount = 0;
+
+ // Release any grabbed enemy
+ idEntity *rider;
+ if ((rider = bindController->GetRider()) != NULL) {
+ bindController->Detach();
+ if (rider->IsType(hhPlayer::Type)) {
+ rider->ProcessEvent(&EV_Unfreeze);
+ }
+ }
+
+ int deathAnim = GetAnimator()->GetAnim("death");
+ GetAnimator()->PlayAnim(ANIMCHANNEL_TORSO, deathAnim, gameLocal.time, 300.0f);
+ int ttl = GetAnimator()->AnimLength(deathAnim);
+ // Freeze
+ physicsObj.EnableGravity(false);
+ physicsObj.PutToRest();
+
+ SetSkinByName( spawnArgs.GetString( "skin_death" ) );
+ SetDeformation(DEFORMTYPE_DEATHEFFECT, gameLocal.time + 2500, 8000); // starttime, duration
+ PostEventSec( &EV_StartSound, 1.5f, "snd_acid", SND_CHANNEL_ANY, 1 );
+
+ // Spawn gas cloud
+ PostEventMS(&EV_DeathCloud, ttl);
+ PostEventMS(&EV_Dispose, 0);
+}
+
+//
+// LaunchProjectile
+//
+idProjectile *hhGasbagSimple::LaunchProjectileAtVec( const char *jointname, const idVec3 &target, bool clampToAttackCone, const idDict* desiredProjectileDef ) { //HUMANHEAD mdc - added desiredProjectileDef for supporting multiple projs.
+ idVec3 muzzle;
+ idVec3 dir;
+ idVec3 start;
+ trace_t tr;
+ idBounds projBounds;
+ float distance;
+ const idClipModel *projClip;
+ float attack_accuracy;
+ float attack_cone;
+ float projectile_spread;
+ float diff;
+ float angle;
+ float spin;
+ idAngles ang;
+ int num_projectiles;
+ int i;
+ idMat3 axis;
+ idVec3 tmp;
+ idProjectile *lastProjectile;
+
+ //HUMANHEAD mdc - added to support multiple projectiles
+ if( desiredProjectileDef ) { //try to set our projectile to the desiredProjectile
+ int projIndex = FindProjectileInfo( desiredProjectileDef );
+ if( projIndex >= 0 ) {
+ SetCurrentProjectile( projIndex );
+ }
+ }
+ //HUMANHEAD END
+
+
+ if ( !projectileDef ) {
+ gameLocal.Warning( "%s (%s) doesn't have a projectile specified", name.c_str(), GetEntityDefName() );
+ return NULL;
+ }
+
+ attack_accuracy = spawnArgs.GetFloat( "attack_accuracy", "7" );
+ attack_cone = spawnArgs.GetFloat( "attack_cone", "70" );
+ projectile_spread = spawnArgs.GetFloat( "projectile_spread", "0" );
+ num_projectiles = spawnArgs.GetInt( "num_projectiles", "1" );
+
+ GetMuzzle( jointname, muzzle, axis );
+
+ if ( !projectile.GetEntity() ) {
+ CreateProjectile( muzzle, axis[ 0 ] );
+ }
+
+ lastProjectile = projectile.GetEntity();
+
+ tmp = target - muzzle;
+ tmp.Normalize();
+ axis = tmp.ToMat3();
+
+ // rotate it because the cone points up by default
+ tmp = axis[2];
+ axis[2] = axis[0];
+ axis[0] = -tmp;
+
+ // make sure the projectile starts inside the monster bounding box
+ const idBounds &ownerBounds = physicsObj.GetAbsBounds();
+ projClip = lastProjectile->GetPhysics()->GetClipModel();
+ projBounds = projClip->GetBounds().Rotate( axis );
+
+ // check if the owner bounds is bigger than the projectile bounds
+ if ( ( ( ownerBounds[1][0] - ownerBounds[0][0] ) > ( projBounds[1][0] - projBounds[0][0] ) ) &&
+ ( ( ownerBounds[1][1] - ownerBounds[0][1] ) > ( projBounds[1][1] - projBounds[0][1] ) ) &&
+ ( ( ownerBounds[1][2] - ownerBounds[0][2] ) > ( projBounds[1][2] - projBounds[0][2] ) ) ) {
+ if ( (ownerBounds - projBounds).RayIntersection( muzzle, viewAxis[ 0 ], distance ) ) {
+ start = muzzle + distance * viewAxis[ 0 ];
+ } else {
+ start = ownerBounds.GetCenter();
+ }
+ } else {
+ // projectile bounds bigger than the owner bounds, so just start it from the center
+ start = ownerBounds.GetCenter();
+ }
+
+ gameLocal.clip.Translation( tr, start, muzzle, projClip, axis, MASK_SHOT_RENDERMODEL, this );
+ muzzle = tr.endpos;
+
+ // set aiming direction
+ //GetAimDir( muzzle, target, this, dir );
+ dir = target - muzzle;
+ ang = dir.ToAngles();
+
+ // adjust his aim so it's not perfect. uses sine based movement so the tracers appear less random in their spread.
+ float t = MS2SEC( gameLocal.time + entityNumber * 497 );
+ ang.pitch += idMath::Sin16( t * 5.1 ) * attack_accuracy;
+ ang.yaw += idMath::Sin16( t * 6.7 ) * attack_accuracy;
+
+ if ( clampToAttackCone ) {
+ // clamp the attack direction to be within monster's attack cone so he doesn't do
+ // things like throw the missile backwards if you're behind him
+ diff = idMath::AngleDelta( ang.yaw, current_yaw );
+ if ( diff > attack_cone ) {
+ ang.yaw = current_yaw + attack_cone;
+ } else if ( diff < -attack_cone ) {
+ ang.yaw = current_yaw - attack_cone;
+ }
+ }
+
+ axis = ang.ToMat3();
+
+ float spreadRad = DEG2RAD( projectile_spread );
+ for( i = 0; i < num_projectiles; i++ ) {
+ // spread the projectiles out
+ angle = idMath::Sin( spreadRad * gameLocal.random.RandomFloat() );
+ spin = (float)DEG2RAD( 360.0f ) * gameLocal.random.RandomFloat();
+ dir = axis[ 0 ] + axis[ 2 ] * ( angle * idMath::Sin( spin ) ) - axis[ 1 ] * ( angle * idMath::Cos( spin ) );
+ dir.Normalize();
+
+ // launch the projectile
+ if ( !projectile.GetEntity() ) {
+ CreateProjectile( muzzle, dir );
+ }
+ lastProjectile = projectile.GetEntity();
+ lastProjectile->Launch( muzzle, dir, vec3_origin );
+ projectile = NULL;
+ }
+
+ TriggerWeaponEffects( muzzle, axis );
+
+ lastAttackTime = gameLocal.time;
+
+//HUMANHEAD mdc - added to support multiple projectiles
+ projectile = NULL;
+ SetCurrentProjectile( projectileDefaultDefIndex ); //set back to our default projectile to be on the safe side
+//HUMANHEAD END
+
+ return lastProjectile;
+}
+
+//
+// LaunchProjectile
+//
+idProjectile *hhGasbagSimple::LaunchProjectileAtVec( const idVec3 &startOrigin, const idMat3 &startAxis, const idVec3 &target, bool clampToAttackCone, const idDict* desiredProjectileDef ) { //HUMANHEAD mdc - added desiredProjectileDef for supporting multiple projs.
+ idVec3 muzzle;
+ idVec3 dir;
+ idVec3 start;
+ trace_t tr;
+ idBounds projBounds;
+ float distance;
+ const idClipModel *projClip;
+ //float attack_accuracy;
+ //float attack_cone;
+ float projectile_spread;
+ float angle;
+ float spin;
+ idAngles ang;
+ int num_projectiles;
+ int i;
+ idMat3 axis;
+ idVec3 tmp;
+ idProjectile *lastProjectile;
+
+ //HUMANHEAD mdc - added to support multiple projectiles
+ if( desiredProjectileDef ) { //try to set our projectile to the desiredProjectile
+ int projIndex = FindProjectileInfo( desiredProjectileDef );
+ if( projIndex >= 0 ) {
+ SetCurrentProjectile( projIndex );
+ }
+ }
+ //HUMANHEAD END
+
+
+ if ( !projectileDef ) {
+ gameLocal.Warning( "%s (%s) doesn't have a projectile specified", name.c_str(), GetEntityDefName() );
+ return NULL;
+ }
+
+ //attack_accuracy = spawnArgs.GetFloat( "attack_accuracy", "7" );
+ //attack_cone = spawnArgs.GetFloat( "attack_cone", "70" );
+ projectile_spread = spawnArgs.GetFloat( "projectile_spread", "0" );
+ num_projectiles = spawnArgs.GetInt( "num_projectiles", "1" );
+
+ muzzle = startOrigin;
+ axis = startAxis;
+
+ if ( !projectile.GetEntity() ) {
+ CreateProjectile( muzzle, axis[ 0 ] );
+ }
+
+ lastProjectile = projectile.GetEntity();
+
+ // make sure the projectile starts inside the monster bounding box
+ const idBounds &ownerBounds = physicsObj.GetAbsBounds();
+ projClip = lastProjectile->GetPhysics()->GetClipModel();
+ projBounds = projClip->GetBounds().Rotate( axis );
+
+ // check if the owner bounds is bigger than the projectile bounds
+ if ( ( ( ownerBounds[1][0] - ownerBounds[0][0] ) > ( projBounds[1][0] - projBounds[0][0] ) ) &&
+ ( ( ownerBounds[1][1] - ownerBounds[0][1] ) > ( projBounds[1][1] - projBounds[0][1] ) ) &&
+ ( ( ownerBounds[1][2] - ownerBounds[0][2] ) > ( projBounds[1][2] - projBounds[0][2] ) ) ) {
+ if ( (ownerBounds - projBounds).RayIntersection( muzzle, viewAxis[ 0 ], distance ) ) {
+ start = muzzle + distance * viewAxis[ 0 ];
+ } else {
+ start = ownerBounds.GetCenter();
+ }
+ } else {
+ // projectile bounds bigger than the owner bounds, so just start it from the center
+ start = ownerBounds.GetCenter();
+ }
+
+ gameLocal.clip.Translation( tr, start, muzzle, projClip, axis, MASK_SHOT_RENDERMODEL, this );
+ muzzle = tr.endpos;
+
+ float spreadRad = DEG2RAD( projectile_spread );
+ for( i = 0; i < num_projectiles; i++ ) {
+ // spread the projectiles out
+ angle = idMath::Sin( spreadRad * gameLocal.random.RandomFloat() );
+ spin = (float)DEG2RAD( 360.0f ) * gameLocal.random.RandomFloat();
+ dir = axis[ 0 ] + axis[ 2 ] * ( angle * idMath::Sin( spin ) ) - axis[ 1 ] * ( angle * idMath::Cos( spin ) );
+ dir.Normalize();
+
+ // launch the projectile
+ if ( !projectile.GetEntity() ) {
+ CreateProjectile( muzzle, dir );
+ }
+ lastProjectile = projectile.GetEntity();
+ lastProjectile->Launch( muzzle, dir, vec3_origin );
+ projectile = NULL;
+ }
+
+ TriggerWeaponEffects( muzzle, axis );
+
+ lastAttackTime = gameLocal.time;
+
+//HUMANHEAD mdc - added to support multiple projectiles
+ projectile = NULL;
+ SetCurrentProjectile( projectileDefaultDefIndex ); //set back to our default projectile to be on the safe side
+//HUMANHEAD END
+
+ return lastProjectile;
+}
+
+#define LinkScriptVariable( name ) name.LinkTo( scriptObject, #name )
+void hhGasbagSimple::LinkScriptVariables(void) {
+ hhMonsterAI::LinkScriptVariables();
+
+ LinkScriptVariable(AI_PODCOUNT);
+ LinkScriptVariable(AI_CHARGEDONE);
+ LinkScriptVariable(AI_SWOOP);
+ LinkScriptVariable(AI_DODGEDAMAGE);
+}
+
+//
+// Event_GrabEnemy()
+//
+void hhGasbagSimple::Event_GrabEnemy(void) {
+ idThread::ReturnFloat( GrabEnemy() );
+}
+
+//
+// GrabEnemy()
+//
+bool hhGasbagSimple::GrabEnemy(void) {
+ if (!enemy.IsValid() || enemy->IsBound() || bindController->GetRider()) {
+ return false;
+ }
+
+ // Don't grab Talon
+ if (enemy->IsType(hhTalon::Type)) {
+ return false;
+ }
+
+ if (enemy->IsType(hhPlayer::Type)) {
+ hhPlayer *player = reinterpret_cast (enemy.GetEntity());
+ if (player->IsSpiritOrDeathwalking() || player->InGravityZone()) { // Refuse to grab spirit players, or players affected by a gravity zone
+ return false;
+ }
+ player->Freeze();
+ }
+
+ bindController->Attach(enemy.GetEntity());
+ enemy->SetOrigin(vec3_origin);
+ return true;
+}
+
+//
+// Event_BiteEnemy()
+//
+void hhGasbagSimple::Event_BiteEnemy(void) {
+ if (!enemy.IsValid() || bindController->GetRider() != enemy.GetEntity()) {
+ return;
+ }
+
+ if (enemy->IsType(hhSpiritProxy::Type)) {
+ float diff = gameLocal.GetTime() - reinterpret_cast (enemy.GetEntity())->GetActivationTime();
+ if (diff < 1150.0f) {
+ // Don't allow the spirit proxy to ignore damage
+ scriptThread->WaitMS(diff + 250.0f);
+ CancelEvents(&EV_BiteEnemy);
+ PostEventMS(&EV_BiteEnemy, diff + 100.0f);
+ return;
+ }
+ }
+
+ // Direct damage
+ bindController->Detach();
+ enemy->Damage(this, this, vec3_origin, spawnArgs.GetString("def_damage_bite"), 1.0f, INVALID_JOINT);
+}
+
+//
+// Damage()
+//
+void hhGasbagSimple::Damage(idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location) {
+ int curHealth = health;
+ idEntity *enemyEnt = enemy.GetEntity();
+ hhMonsterAI::Damage(inflictor, attacker, dir, damageDefName, damageScale, location);
+
+ if (health < curHealth) {
+ if (gameLocal.time > nextWoundTime && location != INVALID_JOINT) {
+ hhFxInfo fx;
+ fx.SetEntity(this);
+ fx.RemoveWhenDone(true);
+ SpawnFxLocal(spawnArgs.GetString("fx_damage"), inflictor->GetOrigin(), dir.ToMat3(), &fx);
+
+ nextWoundTime = gameLocal.time + 250;
+ }
+
+ // Only release enemy if we actually took damage
+ idEntity *rider;
+ if ((rider = bindController->GetRider()) != NULL) {
+ bindController->Detach();
+ if (rider->IsType(hhPlayer::Type)) {
+ rider->PostEventMS(&EV_Unfreeze, 0);
+ }
+ }
+
+ AI_DODGEDAMAGE = AI_DODGEDAMAGE + (curHealth - health);
+ }
+}
+
+//
+// Event_DirectMoveToPosition()
+//
+void hhGasbagSimple::Event_DirectMoveToPosition(const idVec3 &pos) {
+ StopMove(MOVE_STATUS_DONE);
+ DirectMoveToPosition(pos);
+}
+
+//
+// Event_ChargeEnemy
+//
+void hhGasbagSimple::Event_ChargeEnemy(void) {
+ AI_CHARGEDONE = true;
+ StopMove(MOVE_STATUS_DEST_NOT_FOUND);
+ if (!enemy.IsValid() || enemy->IsBound()) {
+ return;
+ }
+
+ idVec3 enemyOrg;
+
+ fly_offset = 0.0f;
+
+ // position destination so that we're in the enemy's view
+ enemyOrg = enemy->GetEyePosition();
+ enemyOrg -= enemy->GetPhysics()->GetGravityNormal() * fly_offset;
+
+ DirectMoveToPosition(enemyOrg);
+ HH_ASSERT(move.bEnemyBlocks == false);
+}
+
+void hhGasbagSimple::Event_EnemyIsSpirit( hhPlayer *player, hhSpiritProxy *proxy ) {
+ hhMonsterAI::Event_EnemyIsSpirit(player, proxy);
+ if (bindController->GetRider() == player) {
+ bindController->Detach();
+ player->Unfreeze();
+ GrabEnemy();
+ }
+}
+
+void hhGasbagSimple::Event_EnemyIsPhysical( hhPlayer *player, hhSpiritProxy *proxy ) {
+ hhMonsterAI::Event_EnemyIsPhysical(player, proxy);
+ if (bindController->GetRider() == player) {
+ player->Freeze();
+ }
+}
+
+//
+// Event_BindUnfroze
+//
+void hhGasbagSimple::Event_BindUnfroze(idEntity *unfrozenBind) {
+ if (!enemy.IsValid() || unfrozenBind != enemy.GetEntity() || bindController->GetRider() != enemy.GetEntity()) {
+ gameLocal.Warning("Gasbag received BindUnfroze event but enemy is not bound to it.\n");
+ return;
+ }
+
+ bindController->Detach();
+}
+
+//
+// FlyTurn
+//
+void hhGasbagSimple::FlyTurn(void) {
+ if (!AI_SWOOP && (AI_ENEMY_VISIBLE || move.moveCommand == MOVE_FACE_ENEMY)) {
+ TurnToward( lastVisibleEnemyPos );
+ } else if ((move.moveCommand == MOVE_FACE_ENTITY) && move.goalEntity.GetEntity()) {
+ TurnToward(move.goalEntity.GetEntity()->GetPhysics()->GetOrigin());
+ } else if (move.speed > 0.1f) {
+ const idVec3 &vel = physicsObj.GetLinearVelocity();
+ if (vel.ToVec2().LengthSqr() > 0.1f) {
+ TurnToward(vel.ToYaw());
+ }
+ }
+ Turn();
+}
+
+//
+// Event_GrabCheck()
+//
+void hhGasbagSimple::Event_GrabCheck(void) {
+ idThread::ReturnInt(enemy.IsValid() && enemy->IsBoundTo(this));
+}
+
+
+void hhGasbagSimple::Event_MoveToGrabPosition(void) {
+ StopMove(MOVE_STATUS_DEST_NOT_FOUND);
+ if (!enemy.IsValid() || enemy->IsBound()) {
+ return;
+ }
+
+ fly_offset = 0.0f;
+
+ // position destination so that we're right on the enemy when we grab him
+ DirectMoveToPosition(enemy->GetOrigin() - spawnArgs.GetVector( "grab_offset" ));
+ HH_ASSERT(move.bEnemyBlocks == false);
+}
+
+void hhGasbagSimple::SetEnemy(idActor *newEnemy) {
+ idEntity *rider;
+ if ((rider = bindController->GetRider())) { // This is usually caused by Talon
+ if (rider->IsType(hhPlayer::Type)) {
+ rider->PostEventMS(&EV_Unfreeze, 0);
+ }
+ bindController->Detach();
+ }
+
+ hhMonsterAI::SetEnemy(newEnemy);
+}
+
+void hhGasbagSimple::Event_CheckRange(void) {
+ if (!enemy.IsValid()) {
+ idThread::ReturnInt(0);
+ return;
+ }
+
+ int retval;
+ float dist = ( enemy->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin() ).Length();
+
+ if (enemy->IsType(hhPlayer::Type)) {
+ float min = spawnArgs.GetFloat( "dda_range_min" );
+ float max = spawnArgs.GetFloat( "dda_range_max" );
+ float player_dist = min + (max - min) * gameLocal.GetDDAValue();
+
+ retval = (int) (dist <= player_dist);
+ } else {
+ retval = (int) (dist <= 100.0f);
+ }
+
+ idThread::ReturnInt(retval);
+}
+
+void hhGasbagSimple::Gib( const idVec3 &dir, const char *damageDefName ) {
+ // Bypass idActor::Gib()
+ idAFEntity_Gibbable::Gib( dir, damageDefName );
+}
+
+int hhGasbagSimple::ReactionTo( const idEntity *ent ) {
+ //gasbags skip the spirit stuff in hhMonsterAI::ReactionTo()
+ if ( bNoCombat ) {
+ return ATTACK_IGNORE;
+ }
+ const idActor *actor = static_cast( ent );
+ if( actor && actor->IsType(hhDeathProxy::Type) ) {
+ return ATTACK_IGNORE;
+ }
+ if ( ent->IsType( hhMonsterAI::Type ) ) {
+ const hhMonsterAI *entAI = static_cast( ent );
+ if ( entAI && entAI->bNeverTarget ) {
+ return ATTACK_IGNORE;
+ }
+ }
+
+ return idAI::ReactionTo( ent );
+}
+
+void hhGasbagSimple::Event_EnemyRangeZ( void ) {
+ idThread::ReturnFloat( lastVisibleEnemyPos.z - GetOrigin().z );
+}
+#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build
\ No newline at end of file
diff --git a/neo/prey/Prey/ai_gasbag_simple.h b/neo/prey/Prey/ai_gasbag_simple.h
new file mode 100644
index 00000000..e684865b
--- /dev/null
+++ b/neo/prey/Prey/ai_gasbag_simple.h
@@ -0,0 +1,90 @@
+#ifndef __PREY_AI_GASBAG_SIMPLE_H__
+#define __PREY_AI_GASBAG_SIMPLE_H__
+
+extern const idEventDef EV_NewPod;
+
+class hhPod;
+
+class hhGasbagSimple : public hhMonsterAI {
+public:
+ CLASS_PROTOTYPE(hhGasbagSimple);
+
+#ifdef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build
+ void Event_AcidBlast(void) {};
+ void Event_AcidDrip(void) {};
+ void Event_DeathCloud(void) {};
+ void Event_NewPod(hhPod *pod) {};
+ void Event_LaunchPod(void) {};
+ void Event_SpawnBlastDebris(void) {};
+ void Event_ChargeEnemy(void) {};
+ void Event_GrabEnemy(void) {};
+ void Event_BiteEnemy(void) {};
+ void Event_DirectMoveToPosition(const idVec3 &pos) {};
+ void Event_BindUnfroze(idEntity *unfrozenBind) {};
+ virtual void Event_EnemyIsSpirit( hhPlayer *player, hhSpiritProxy *proxy ) {};
+ virtual void Event_EnemyIsPhysical( hhPlayer *player, hhSpiritProxy *proxy ) {};
+ void Event_GrabCheck(void) {};
+ void Event_MoveToGrabPosition(void) {};
+ void Event_CheckRange(void) {};
+ void Event_EnemyRangeZ(void) {};
+#else
+ virtual ~hhGasbagSimple(void);
+
+ void Spawn(void);
+ void Save(idSaveGame *savefile) const;
+ void Restore(idRestoreGame *savefile);
+
+ virtual void FlyTurn(void);
+
+ virtual void Gib(const idVec3 &dir, const char *damageDefName);
+ int ReactionTo( const idEntity *ent );
+
+protected:
+ void Event_AcidBlast(void);
+ void Event_AcidDrip(void);
+ void Event_DeathCloud(void);
+ void Event_NewPod(hhPod *pod);
+ void Event_LaunchPod(void);
+ void Event_SpawnBlastDebris(void);
+ void Event_ChargeEnemy(void);
+ void Event_GrabEnemy(void);
+ void Event_BiteEnemy(void);
+ void Event_DirectMoveToPosition(const idVec3 &pos);
+ void Event_BindUnfroze(idEntity *unfrozenBind);
+ virtual void Event_EnemyIsSpirit( hhPlayer *player, hhSpiritProxy *proxy );
+ virtual void Event_EnemyIsPhysical( hhPlayer *player, hhSpiritProxy *proxy );
+ void Event_GrabCheck(void);
+ void Event_MoveToGrabPosition(void);
+ void Event_CheckRange(void);
+ void Event_EnemyRangeZ(void);
+
+ bool GrabEnemy(void);
+
+ virtual void LinkScriptVariables(void);
+
+ virtual void Ticker(void);
+ virtual idProjectile *LaunchProjectileAtVec(const char *jointname, const idVec3 &target, bool clampToAttackCone, const idDict* desiredProjectileDef);
+ virtual idProjectile *LaunchProjectileAtVec(const idVec3 &startOrigin, const idMat3 &startAxis, const idVec3 &target, bool clampToAttackCone, const idDict* desiredProjectileDef);
+ virtual void Killed(idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location);
+ virtual void Damage(idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location);
+ virtual void SetEnemy(idActor *newEnemy);
+
+ idVec3 podOffset;
+ float podRange;
+ int dripCount;
+
+ idList< idEntityPtr > podList;
+
+ idScriptInt AI_PODCOUNT;
+ idScriptBool AI_CHARGEDONE;
+ idScriptBool AI_SWOOP;
+ idScriptInt AI_DODGEDAMAGE;
+
+ int nextWoundTime;
+
+ idEntityPtr bindController; // Bind controller for tractor beam
+#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build
+};
+
+#endif
+
diff --git a/neo/prey/Prey/ai_harvester_simple.cpp b/neo/prey/Prey/ai_harvester_simple.cpp
new file mode 100644
index 00000000..f132ad03
--- /dev/null
+++ b/neo/prey/Prey/ai_harvester_simple.cpp
@@ -0,0 +1,838 @@
+#include "precompiled.h"
+#pragma hdrstop
+
+#include "prey_local.h"
+
+const idEventDef MA_AntiProjectileAttack("", "e");
+const idEventDef MA_StartPreDeath("" );
+const idEventDef MA_HandlePassageway("", NULL);
+const idEventDef MA_EnterPassageway("", "e",NULL);
+const idEventDef MA_ExitPassageway("", "e",NULL);
+const idEventDef MA_UseThisPassageway("useThisPassageway", "ed",NULL);
+const idEventDef MA_GibOnDeath("gibOnDeath", "d");
+
+CLASS_DECLARATION( hhMonsterAI, hhHarvesterSimple )
+ EVENT( MA_OnProjectileLaunch, hhHarvesterSimple::Event_OnProjectileLaunch )
+ EVENT( MA_AntiProjectileAttack, hhHarvesterSimple::Event_AntiProjectileAttack )
+ EVENT( MA_StartPreDeath, hhHarvesterSimple::Event_StartPreDeath )
+ EVENT( MA_HandlePassageway, hhHarvesterSimple::Event_HandlePassageway )
+ EVENT( MA_EnterPassageway, hhHarvesterSimple::Event_EnterPassageway )
+ EVENT( MA_ExitPassageway, hhHarvesterSimple::Event_ExitPassageway )
+ EVENT( MA_UseThisPassageway, hhHarvesterSimple::Event_UseThisPassageway )
+ EVENT( EV_Broadcast_AppendFxToList, hhHarvesterSimple::Event_AppendFxToList )
+ EVENT( MA_GibOnDeath, hhHarvesterSimple::Event_GibOnDeath )
+END_CLASS
+
+#ifndef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build
+//
+// Spawn()
+//
+void hhHarvesterSimple::Spawn(void) {
+ allowPreDeath = true;
+ lastAntiProjectileAttack = 0;
+ lastPassagewayTime = 0;
+ passageCount = spawnArgs.GetInt("passage_count", "0"); // Used for passing to and from torso
+ bSmokes = spawnArgs.GetBool("smokes", "0");
+ bGibOnDeath = false;
+}
+
+//
+// Event_OnProjectileLaunch()
+//
+void hhHarvesterSimple::Event_OnProjectileLaunch(hhProjectile *proj) {
+
+ // Can't launch again yet
+ float min = spawnArgs.GetFloat( "dda_delay_min" );
+ float max = spawnArgs.GetFloat( "dda_delay_max" );
+ float delay = min + (max - min) * (1.0f - gameLocal.GetDDAValue());
+
+ if(gameLocal.GetTime() - lastAntiProjectileAttack < delay)
+ return;
+
+ // The person who launched this projectile wasn't someone to worry about
+ if(proj->GetOwner() && !(ReactionTo(proj->GetOwner()) & (ATTACK_ON_SIGHT | ATTACK_ON_DAMAGE)))
+ return;
+
+ // TODO: more intelligent checks for if we should launch the anti-projectile chaff
+ idVec3 fw = viewAxis[0];
+ idVec3 projFw = proj->GetAxis()[0];
+ if(proj->GetOwner())
+ projFw = proj->GetOwner()->GetAxis()[0];
+ float dot = fw * projFw;
+ if(dot > -.7f)
+ return;
+
+ ProcessEvent(&MA_AntiProjectileAttack, proj);
+}
+
+
+//
+// Event_AntiProjectileAttack()
+//
+void hhHarvesterSimple::Event_AntiProjectileAttack(hhProjectile *proj) {
+
+ if(!spawnArgs.GetBool("block_projectiles", "1")) {
+ return;
+ }
+
+ const idDict *projectileDef = gameLocal.FindEntityDefDict( spawnArgs.GetString("def_chaff") );
+ if ( !projectileDef ) {
+ gameLocal.Error( "Unknown def_chaff: %s\n", spawnArgs.GetString("def_chaff") );
+ }
+ hhProjectile* projectile = NULL;
+
+ //bjk: new parms
+ int numProj = spawnArgs.GetInt("shieldNumProj", "10");
+ float spread = DEG2RAD( spawnArgs.GetFloat("shieldSpread", "10") );
+ float yaw = DEG2RAD( spawnArgs.GetFloat("shieldYawSpread", "10") );
+
+ //StartSound("snd_fire_chaff", SND_CHANNEL_ANY, 0, true, NULL);
+ torsoAnim.PlayAnim( GetAnimator()->GetAnim( "antiprojectile_attack" ) );
+
+ idVec3 dir = proj->GetOrigin() - GetOrigin();
+ dir.Normalize();
+ dir = dir+idVec3(0.f, 0.f, -.3f); // bjk: bias towards ground
+ dir.Normalize();
+ idMat3 muzzleAxis = dir.ToMat3();
+ // todo: read this in from a bone
+ idVec3 launchPos = GetOrigin() + idVec3(0.0f, 0.0f, 64.0f);
+
+ for(int i=0; iCreate(this, launchPos, dir);
+ projectile->Launch(launchPos, dir, idVec3(0.0f, 0.0f, 0.0f));
+ }
+
+ lastAntiProjectileAttack = gameLocal.GetTime();
+}
+
+bool hhHarvesterSimple::Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) {
+ int preDeathThresh = spawnArgs.GetInt("predeath_thresh", "-1");
+ if(allowPreDeath && !bGibOnDeath && preDeathThresh > 0 && health < preDeathThresh) {
+ allowPreDeath = false; // We've tried, cannot try anymore
+ float preDeathFreq = spawnArgs.GetFloat("predeath_freq", "1");
+ if(gameLocal.random.RandomFloat() < preDeathFreq) {
+ ProcessEvent(&MA_StartPreDeath);
+ }
+ }
+
+ if ( bSmokes ) {
+ if ( !fxSmoke[0].IsValid() && health <= AI_PASSAGEWAY_HEALTH ) {
+ SpawnSmoke();
+ } else if (fxSmoke[0].IsValid() && health > AI_PASSAGEWAY_HEALTH) {
+ ClearSmoke();
+ }
+ }
+
+ return( idAI::Pain(inflictor, attacker, damage, dir, location) );
+}
+
+void hhHarvesterSimple::Event_StartPreDeath(void) {
+ int i;
+
+ // No torso? Just kill myself .....
+ if(!spawnArgs.GetString("def_torso")) {
+ ProcessEvent(&AI_Kill);
+ return;
+ }
+
+ // Spawn death fx, if any
+ const char *fx = spawnArgs.GetString("fx_death");
+ if (fx && fx[0]) {
+ hhFxInfo fxInfo;
+
+ fxInfo.RemoveWhenDone(true);
+ BroadcastFxInfo(fx, GetOrigin(), GetAxis(), &fxInfo);
+ }
+
+ // do blood splats
+ float size = spawnArgs.GetFloat("decal_torso_pop_size","96");
+
+ // copy my target keys to the keys that will spawn the torso
+ idDict dict;
+ const idDict *torsoDict = gameLocal.FindEntityDefDict(spawnArgs.GetString("def_torso"));
+ if ( !torsoDict ) {
+ gameLocal.Error("Unknown def_torso: %s\n", spawnArgs.GetString("def_torso"));
+ }
+ dict.Copy(*torsoDict);
+ for(i=0;i 0) {
+ sprintf(key, "target%i", i);
+ }
+
+ dict.Set((const char*)key, (const char*)targets[i].GetEntity()->name);
+ }
+
+ // Passageway variables
+ dict.SetInt("passage_health", AI_PASSAGEWAY_HEALTH);
+ dict.SetInt("passage_count", passageCount);
+ dict.SetVector("origin", GetOrigin());
+ dict.SetMatrix("rotation", GetAxis());
+
+ // remove my target keys (so they don't get fired)
+ targets.Clear();
+
+ // Spawn the torso
+ idEntity *e;
+ hhMonsterAI *aiTorso;
+ if(!gameLocal.SpawnEntityDef(dict, &e))
+ gameLocal.Error("Failed to spawn ai torso.");
+ HH_ASSERT(e && e->IsType(hhMonsterAI::Type));
+ aiTorso = static_cast(e);
+ HH_ASSERT(aiTorso != NULL);
+
+ // Throw gibs
+ idStr debrisDef = spawnArgs.GetString("def_gibDebrisSpawnerPreDeath");
+ if(debrisDef.Length() > 0) {
+ hhUtils::SpawnDebrisMass(debrisDef.c_str(), this, 1.0f);
+ }
+
+ health = 0;
+
+ // remove myself
+ Hide();
+ PostEventMS(&EV_Remove, 3000);
+}
+
+void hhHarvesterSimple::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) {
+ if ( attacker == this ) {
+ return;
+ }
+ const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName );
+ if ( !AI_DEAD ) {
+ if ( damageDef && damageDef->GetBool( "ice" ) && spawnArgs.GetBool( "can_freeze", "0" ) ) {
+ spawnArgs.Set( "bAlwaysGib", "0" );
+ allowPreDeath = false;
+ } else {
+ allowPreDeath = true;
+ }
+ if ( damageDef && damageDef->GetBool( "no_special_death" ) ) {
+ allowPreDeath = false;
+ }
+ }
+ hhMonsterAI::Damage( inflictor, attacker, dir, damageDefName, damageScale, location );
+}
+
+int hhHarvesterSimple::EvaluateReaction( const hhReaction *react ) {
+ // Let the script limit the distance of Climb reactions we consider
+ if ( react->desc->effect == hhReactionDesc::Effect_Climb && AI_CLIMB_RANGE >= 0.0f && ( react->causeEntity->GetOrigin() - GetOrigin()).LengthSqr() > AI_CLIMB_RANGE ) {
+ return 0;
+ }
+
+ // If we're supposed to use a specific passageway, only accept it and reject the others
+ if ( nextPassageway.IsValid() && react->desc->effect == hhReactionDesc::Effect_Passageway ) {
+ if (react->causeEntity.GetEntity() == nextPassageway.GetEntity()) {
+ return 100;
+ } else {
+ return 0;
+ }
+ }
+
+ int rank = hhMonsterAI::EvaluateReaction( react );
+ if ( react->desc->effect == hhReactionDesc::Effect_Passageway &&
+ react->causeEntity.GetEntity() == lastPassageway.GetEntity() ) {
+ if ( lastPassagewayTime + 10000 > gameLocal.time ) {
+ if (rank > 10) {
+ // Discourage entering the same passageway we just left
+ return 10;
+ }
+ } else {
+ // Ignore previous passageway for 10 seconds
+ return 0;
+ }
+ }
+ return rank;
+}
+
+#define CheckPassageFreqInitial 6000
+void hhHarvesterSimple::Event_EnterPassageway(hhAIPassageway *pn) {
+ CancelEvents( &MA_OnProjectileLaunch ); // Clear any anti-projectile attacks
+ CancelEvents( &MA_AntiProjectileAttack ); // Clear any anti-projectile attacks
+ if (nextPassageway.IsValid()) {
+ if (nextPassageway.GetEntity() != pn) {
+ gameLocal.Warning("hhAIPassageway entered was not the specified one!\n");
+ }
+ nextPassageway.Clear();
+ }
+
+ if(currPassageway != NULL) {
+ gameLocal.Error("%s tried to ENTER a passageway but already had a passageway!", (const char*)name);
+ }
+
+ HH_ASSERT(currPassageway == NULL);
+ Hide(); // Should already be hidden but just to make sure
+
+ // Only consider passageCount if damaged (ignores scripted passage sequences) or if it's the torso
+ if (health < spawnHealth || !idStr::Icmp(spawnArgs.GetString("classname"), "monster_harvester_torso") ) {
+ passageCount++;
+ if (passageCount > 1) {
+ //TODO make this affected by the DDA system
+ AI_PASSAGEWAY_HEALTH = AI_PASSAGEWAY_HEALTH * 0.75f; // Reduce passageway seeking health level to 3/4 of it's original value
+ }
+ }
+
+ GetAnimator()->ClearAllAnims(gameLocal.GetTime(), 0);
+ Event_AnimState(ANIMCHANNEL_LEGS, "Legs_Idle", (int)0);
+ Event_AnimState(ANIMCHANNEL_TORSO, "Torso_Idle", (int)0);
+ torsoAnim.UpdateState();
+ legsAnim.UpdateState();
+
+ StartSound("snd_inside_passage", SND_CHANNEL_VOICE, SSF_LOOPING, true, NULL);
+
+ currPassageway = pn;
+ if(GetPhysics())
+ GetPhysics()->SetContents(0);
+
+ // Does this passage way give us health?
+ int h = 0;
+ pn->spawnArgs.GetInt("give_health", "0", h);
+ health += h;
+ health = idMath::ClampInt(0, spawnArgs.GetInt("health"), health);
+
+
+ // Should we transform to another monster when entering?
+ idStr transTo;
+ if(spawnArgs.GetString("passage_transform_to", "", transTo)) {
+ const idDict *def = gameLocal.FindEntityDefDict( transTo );
+ if(!def) {
+ gameLocal.Error("Unknown def : %s", (const char*)transTo);
+ }
+
+ // copy my target keys to the keys of the transformed-to entity
+ idDict dict;
+ dict.Copy(*def);
+ for(int i=0;i 0)
+ sprintf(key, "target%i", i);
+
+ dict.Set((const char*)key, (const char*)targets[i].GetEntity()->name);
+ }
+
+ // Passageway variables
+ dict.SetInt("passage_health", AI_PASSAGEWAY_HEALTH);
+ dict.SetInt("passage_count", passageCount);
+
+ idEntity *e = NULL;
+ gameLocal.SpawnEntityDef(dict, &e);
+ HH_ASSERT( e->IsType( hhHarvesterSimple::Type ) );
+ hhHarvesterSimple *ai = static_cast(e);
+ ai->SetOrigin(GetOrigin());
+ ai->SetAxis(GetAxis());
+ ai->Event_EnterPassageway(pn);
+ targets.Clear();
+ PostEventSec(&EV_Remove, 0.1f);
+ return;
+ }
+
+
+ PostEventMS(&MA_HandlePassageway, CheckPassageFreqInitial);
+}
+
+void hhHarvesterSimple::Event_ExitPassageway(hhAIPassageway *pn) {
+ if(currPassageway == NULL) {
+ gameLocal.Error("%s tried to EXIT a passageway but did NOT have a curr passageway!", (const char*)name);
+ }
+
+ HH_ASSERT(currPassageway != NULL);
+
+ // Store this passageway so we don't jump right back into it.
+ lastPassageway = idEntityPtr (pn);
+ lastPassagewayTime = gameLocal.time;
+
+ if(GetPhysics())
+ GetPhysics()->SetContents(CONTENTS_BODY);
+ currPassageway = NULL;
+ AI_ACTIVATED = true; // Sometimes this is NOT true?!?!
+ StopSound(SND_CHANNEL_VOICE, true);
+
+ // Teleport to new pos
+ idAngles angs = pn->GetAxis().ToAngles();
+ idVec3 pos = pn->GetExitPos();
+ Teleport( pos, angs, pn );
+ Show();
+
+ current_yaw = angs.yaw;
+ ideal_yaw = angs.yaw;
+ turnVel = 0.0f;
+ HH_ASSERT(FacingIdeal());
+
+ // Exit anim to play?
+ idStr exitAnim = pn->spawnArgs.GetString("exit_anim");
+ if(exitAnim.Length() && GetAnimator()->HasAnim(exitAnim)) {
+ GetAnimator()->ClearAllAnims(gameLocal.GetTime(), 0);
+ torsoAnim.UpdateState();
+ legsAnim.UpdateState();
+ torsoAnim.PlayAnim( GetAnimator()->GetAnim( exitAnim ) );
+ legsAnim.PlayAnim( GetAnimator()->GetAnim( exitAnim ) );
+ }
+}
+
+//
+// Event_HandlePassageway()
+//
+#define MaxExits 32
+#define CheckPassageFreq 3000
+void hhHarvesterSimple::Event_HandlePassageway(void) {
+
+ if(currPassageway == NULL) {
+ gameLocal.Error("%s tried to handle a passageway but did NOT have a passageway!", (const char*)name);
+ }
+
+ HH_ASSERT(currPassageway != NULL);
+ hhAIPassageway *exitNode = NULL;
+
+ // CATCH 'went inside hole - never came out' bug
+ if(currPassageway.GetEntity()->lastEntered == this && gameLocal.GetTime() - currPassageway.GetEntity()->timeLastEntered > 30000) {
+ gameLocal.Warning("%s has been stuck in a passageway for >30 secs, killing it off.", (const char*)name);
+ fl.takedamage = true;
+ // Use hhMonsterAI::Damage because our Damage returns if we're the attacker
+ hhMonsterAI::Damage(this, this, vec3_origin, "damage_gib", 1.0f, INVALID_JOINT);
+ return;
+ }
+
+ // No other way out - gotta go out the way we came in
+ if(currPassageway->targets.Num() <= 0) {
+ exitNode = currPassageway.GetEntity();
+ }
+ // We can go out another way
+ else {
+ hhAIPassageway *possibleExits[MaxExits];
+ hhAIPassageway *tmp;
+ int count = 0;
+
+ bool isRandom;
+ if (gameLocal.GetTime() - currPassageway.GetEntity()->timeLastEntered > SEC2MS(10)) {
+ // Try to prevent us from being stuck after 10 seconds
+ isRandom = true;
+ } else {
+ // Choose possibleExits[] randomly if we have no enemy or based on chance. Otherwise choose based on line-of-sight to the player
+ isRandom = !enemy.IsValid() || gameLocal.random.RandomInt(100) > 50;
+ }
+ trace_t tr;
+ idVec3 toPos;
+ if (!isRandom) {
+ toPos = enemy->GetOrigin();
+ }
+
+ // We've tried once, now we can use our entrance as an exit
+ if(gameLocal.GetTime() - currPassageway.GetEntity()->timeLastEntered > CheckPassageFreqInitial + 1000) {
+ count = 1;
+ possibleExits[0] = currPassageway.GetEntity(); // We can always go out the same way we came in
+ }
+ for(int i=0;itargets.Num();i++) {
+ if(currPassageway->targets[i] != NULL && currPassageway->targets[i].GetEntity()->IsType(hhAIPassageway::Type)) {
+ if (isRandom) {
+ possibleExits[count++] = static_cast(currPassageway->targets[i].GetEntity());
+ } else {
+ tmp = static_cast(currPassageway->targets[i].GetEntity());
+ // Choose based on visibility to our enemy
+ gameLocal.clip.TracePoint( tr, tmp->GetOrigin(), toPos, MASK_SHOT_BOUNDINGBOX, tmp );
+ if ( tr.fraction >= 1.0f || ( gameLocal.GetTraceEntity( tr ) == enemy.GetEntity() ) ) {
+ lastVisibleEnemyPos = toPos; // Can now see enemy
+ possibleExits[count++] = tmp;
+ }
+ }
+ }
+
+ // We've got enough
+ if(count >= MaxExits)
+ break;
+ }
+
+ if(count == 0) {
+ exitNode = currPassageway.GetEntity();
+ } else if ( enemy.IsValid() && gameLocal.random.RandomInt(100) > 70 ) {
+ // Find the one closest to the last known enemy pos
+ if (!exitNode) {
+ float bestDistance = -1.0f, distSq;
+ for (int i = 0; i < count; i++) {
+ distSq = ( possibleExits[i]->GetOrigin() - lastVisibleEnemyPos ).LengthSqr();
+ if ( bestDistance == -1.0f || distSq < bestDistance ) {
+ exitNode = possibleExits[i];
+ bestDistance = distSq;
+ }
+ }
+ }
+ } else {
+ // Pick a random one
+ int randExit = gameLocal.random.RandomInt(count); // TODO: Randomness isn't very random! Fix!
+ HH_ASSERT(randExit >= 0 && randExit < count);
+ exitNode = possibleExits[randExit];
+ }
+ }
+
+ if(!exitNode)
+ exitNode = currPassageway.GetEntity();
+
+ // The one we picked is disabled, so go out where we came in
+ // TODO: make this time based, try a few more times before going back out
+ if(!exitNode->IsPassagewayEnabled()) {
+ exitNode = currPassageway.GetEntity();
+ }
+
+ // Special case: The passageway is never meant to be exited from, use it's specified exit point instead
+ const char *altPassage = exitNode->spawnArgs.GetString("force_passageway", "");
+ if (altPassage && altPassage[0]) {
+ idEntity *node = gameLocal.FindEntity(altPassage);
+ if (node && node->IsType(hhAIPassageway::Type)) {
+ exitNode = reinterpret_cast (node);
+ }
+ }
+
+ // Make sure no one is blocking where we want to go out
+ idBounds myBnds = GetPhysics()->GetBounds();
+ myBnds.Expand(12.0f);
+ myBnds.TranslateSelf(exitNode->GetExitPos());
+ idEntity *ents[MAX_GENTITIES];
+ int count = gameLocal.clip.EntitiesTouchingBounds(myBnds, -1, ents, MAX_GENTITIES);
+ for(int i=0;iIsHidden() && (ents[i]->IsType(idActor::Type) || ents[i]->IsType(hhMoveable::Type))) {
+ // Chunk moveables
+ if ( ents[i]->IsType(idMoveable::Type) ) {
+ ents[i]->Damage( this, this, vec3_origin, "damage_gib", 1.0f, INVALID_JOINT );
+ ents[i]->SquishedByDoor(this);
+ continue;
+ }
+ gameLocal.Warning("Passageway exit blocked.\n");
+ PostEventMS(&MA_HandlePassageway, CheckPassageFreq);
+ if(ai_debugBrain.GetInteger() != 0) {
+ gameRenderWorld->DebugBounds(colorYellow, myBnds, vec3_origin, 2000);
+ }
+ return;
+ }
+ }
+
+ // Else we can just exit now - its safe
+ exitNode->ProcessEvent(&EV_Activate, this);
+}
+
+#define LinkScriptVariable( name ) name.LinkTo(scriptObject, #name)
+void hhHarvesterSimple::LinkScriptVariables() {
+ hhMonsterAI::LinkScriptVariables();
+ LinkScriptVariable(AI_CLIMB_RANGE);
+ LinkScriptVariable(AI_PASSAGEWAY_HEALTH);
+}
+
+void hhHarvesterSimple::Event_UseThisPassageway(hhAIPassageway *pn, bool force) {
+ nextPassageway.Assign(pn);
+
+ if (force) { // Snap to position
+ Event_FindReaction("passageway");
+ hhReaction *reaction = targetReaction.GetReaction();
+ if (!reaction) {
+ gameLocal.Warning("useThisPassageway could not find a valid reaction!\n");
+ } else {
+ idVec3 tp = GetTouchPos( reaction->causeEntity.GetEntity(), reaction->desc );
+ SetOrigin(tp);
+ }
+ }
+}
+
+/*
+=====================
+hhHarvesterSimple::Save
+=====================
+*/
+void hhHarvesterSimple::Save( idSaveGame *savefile ) const {
+ savefile->WriteBool( allowPreDeath );
+ savefile->WriteInt( lastAntiProjectileAttack );
+ savefile->WriteInt( passageCount );
+ savefile->WriteBool( bGibOnDeath );
+ savefile->WriteInt( lastPassagewayTime );
+
+ lastPassageway.Save( savefile );
+ nextPassageway.Save( savefile );
+}
+
+/*
+=====================
+hhHarvesterSimple::Restore
+=====================
+*/
+void hhHarvesterSimple::Restore( idRestoreGame *savefile ) {
+ savefile->ReadBool( allowPreDeath );
+ savefile->ReadInt( lastAntiProjectileAttack );
+ savefile->ReadInt( passageCount );
+ savefile->ReadBool( bGibOnDeath );
+ savefile->ReadInt( lastPassagewayTime );
+
+ lastPassageway.Restore( savefile );
+ nextPassageway.Restore( savefile );
+
+ bSmokes = spawnArgs.GetBool("smokes", "0");
+}
+
+void hhHarvesterSimple::SpawnSmoke(void) {
+ const char *bones[MAX_HARVESTER_LEGS];
+ bones[0] = spawnArgs.GetString( "bone_smoke_front_left" );
+ bones[1] = spawnArgs.GetString( "bone_smoke_front_right" );
+ bones[2] = spawnArgs.GetString( "bone_smoke_rear_left" );
+ bones[3] = spawnArgs.GetString( "bone_smoke_rear_right" );
+
+ const char *psystem = spawnArgs.GetString( "fx_smoke" );
+ if ( psystem && *psystem ) {
+ idVec3 bonePos;
+ idMat3 boneAxis;
+
+ for(int i = 0; i < MAX_HARVESTER_LEGS; i++) {
+ if( fxSmoke[i] != NULL ) {
+ gameLocal.Warning("Harvester already had smoke particles for leg %i.\n", i);
+ continue;
+ }
+
+ hhFxInfo fxInfo;
+
+ this->GetJointWorldTransform( bones[i], bonePos, boneAxis );
+ fxInfo.SetEntity( this );
+ fxInfo.SetBindBone( bones[i] );
+ fxInfo.Toggle();
+ fxInfo.RemoveWhenDone( false );
+
+ BroadcastFxInfo( psystem, bonePos, boneAxis, &fxInfo, &EV_Broadcast_AppendFxToList );
+ }
+ }
+}
+
+void hhHarvesterSimple::ClearSmoke(void) {
+ for(int i=0; i < MAX_HARVESTER_LEGS; i++) {
+ if(fxSmoke[i].IsValid()) {
+ fxSmoke[i]->Nozzle(false);
+ }
+ }
+
+ for(int i = 0; i < MAX_HARVESTER_LEGS; i++) {
+ SAFE_REMOVE(fxSmoke[MAX_HARVESTER_LEGS]);
+ }
+}
+
+void hhHarvesterSimple::Event_AppendFxToList(hhEntityFx *fx) {
+ for(int i = 0; i < MAX_HARVESTER_LEGS; ++i) {
+ if(fxSmoke[i] != NULL) {
+ continue;
+ }
+
+ fxSmoke[i] = fx;
+ return;
+ }
+}
+
+void hhHarvesterSimple::Event_GibOnDeath(const idList* parmList) {
+ bGibOnDeath = (atoi((*parmList)[0].c_str()) != 0);
+}
+
+void hhHarvesterSimple::Killed(idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location) {
+ ClearSmoke();
+ hhMonsterAI::Killed(inflictor, attacker, damage, dir, location);
+
+ if (bGibOnDeath) {
+ Gib(vec3_origin, "damage_gib");
+ }
+}
+
+void hhHarvesterSimple::Event_Activate(idEntity *activator) {
+ if (fl.hidden) {
+ // If we're hidden, we're probably birthing out of passageway. Figure out which one.
+ hhAIPassageway *nearest = NULL;
+ float dist, nearDist = idMath::INFINITY;
+ hhAIPassageway *passage = reinterpret_cast (gameLocal.FindEntityOfType( hhAIPassageway::Type, NULL ));
+ while (passage) {
+ dist = (passage->GetOrigin() - GetOrigin()).Length();
+ if (dist < nearDist) {
+ nearDist = dist;
+ nearest = passage;
+ }
+ passage = reinterpret_cast (gameLocal.FindEntityOfType( hhAIPassageway::Type, passage ));
+ }
+
+ // If we found one near us and it's near enough, mark it as our last passageway
+ if (nearest && nearDist < 250.0f) {
+ lastPassageway = nearest;
+ lastPassagewayTime = gameLocal.time;
+ }
+ }
+ hhMonsterAI::Event_Activate(activator);
+}
+
+void hhHarvesterSimple::AnimMove( void ) {
+ idVec3 goalPos;
+ idVec3 delta;
+ idVec3 goalDelta;
+ float goalDist;
+ monsterMoveResult_t moveResult;
+ idVec3 newDest;
+
+ // Turn on physics to prevent flying harvesters
+ BecomeActive(TH_PHYSICS);
+
+ idVec3 oldorigin = physicsObj.GetOrigin();
+#ifdef HUMANHEAD //jsh wallwalk
+ idMat3 oldaxis = GetGravViewAxis();
+#else
+ idMat3 oldaxis = viewAxis;
+#endif
+
+ AI_BLOCKED = false;
+
+ if ( move.moveCommand < NUM_NONMOVING_COMMANDS ){
+ move.lastMoveOrigin.Zero();
+ move.lastMoveTime = gameLocal.time;
+ }
+
+ move.obstacle = NULL;
+ if ( ( move.moveCommand == MOVE_FACE_ENEMY ) && enemy.GetEntity() ) {
+ TurnToward( lastVisibleEnemyPos );
+ goalPos = oldorigin;
+ } else if ( ( move.moveCommand == MOVE_FACE_ENTITY ) && move.goalEntity.GetEntity() ) {
+ TurnToward( move.goalEntity.GetEntity()->GetPhysics()->GetOrigin() );
+ goalPos = oldorigin;
+ } else if ( GetMovePos( goalPos ) ) {
+ if ( move.moveCommand != MOVE_WANDER ) {
+ CheckObstacleAvoidance( goalPos, newDest );
+ TurnToward( newDest );
+ } else {
+ TurnToward( goalPos );
+ }
+ }
+
+ Turn();
+
+ if ( move.moveCommand == MOVE_SLIDE_TO_POSITION ) {
+ if ( gameLocal.time < move.startTime + move.duration ) {
+ goalPos = move.moveDest - move.moveDir * MS2SEC( move.startTime + move.duration - gameLocal.time );
+ delta = goalPos - oldorigin;
+ delta.z = 0.0f;
+ } else {
+ delta = move.moveDest - oldorigin;
+ delta.z = 0.0f;
+ StopMove( MOVE_STATUS_DONE );
+ }
+ } else if ( allowMove ) {
+#ifdef HUMANHEAD //jsh wallwalk
+ GetMoveDelta( oldaxis, GetGravViewAxis(), delta );
+#else
+ GetMoveDelta( oldaxis, viewAxis, delta );
+#endif
+ } else {
+ delta.Zero();
+ }
+
+ if ( move.moveCommand == MOVE_TO_POSITION ) {
+ goalDelta = move.moveDest - oldorigin;
+ goalDist = goalDelta.LengthFast();
+ if ( goalDist < delta.LengthFast() ) {
+ delta = goalDelta;
+ }
+ }
+
+#ifdef HUMANHEAD //shrink functionality
+ float scale = renderEntity.shaderParms[SHADERPARM_ANY_DEFORM_PARM1];
+ if ( scale > 0.0f && scale < 2.0f ) {
+ delta *= scale;
+ }
+#endif
+ physicsObj.SetDelta( delta );
+ physicsObj.ForceDeltaMove( disableGravity );
+
+ RunPhysics();
+
+ if ( ai_debugMove.GetBool() ) {
+ // HUMANHEAD JRM - so we can see if grav is on or off
+ if(disableGravity) {
+ gameRenderWorld->DebugLine( colorRed, oldorigin, physicsObj.GetOrigin(), 5000 );
+ } else {
+ gameRenderWorld->DebugLine( colorCyan, oldorigin, physicsObj.GetOrigin(), 5000 );
+ }
+ }
+
+ moveResult = physicsObj.GetMoveResult();
+ if ( !af_push_moveables && attack.Length() && TestMelee() ) {
+ DirectDamage( attack, enemy.GetEntity() );
+ } else {
+ idEntity *blockEnt = physicsObj.GetSlideMoveEntity();
+ if ( blockEnt && blockEnt->IsType( hhHarvesterSimple::Type ) ) {
+ StopMove( MOVE_STATUS_BLOCKED_BY_MONSTER );
+ return;
+ }
+ if ( blockEnt && blockEnt->IsType( idMoveable::Type ) && blockEnt->GetPhysics()->IsPushable() ) {
+ KickObstacles( viewAxis[ 0 ], kickForce, blockEnt );
+ }
+ }
+
+ BlockedFailSafe();
+
+ AI_ONGROUND = physicsObj.OnGround();
+
+ idVec3 org = physicsObj.GetOrigin();
+ if ( oldorigin != org ) {
+ TouchTriggers();
+ }
+
+ if ( ai_debugMove.GetBool() ) {
+ gameRenderWorld->DebugBounds( colorMagenta, physicsObj.GetBounds(), org, gameLocal.msec );
+ gameRenderWorld->DebugBounds( colorMagenta, physicsObj.GetBounds(), move.moveDest, gameLocal.msec );
+ gameRenderWorld->DebugLine( colorYellow, org + EyeOffset(), org + EyeOffset() + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 16.0f, gameLocal.msec, true );
+ DrawRoute();
+ }
+}
+
+void hhHarvesterSimple::Event_CanBecomeSolid( void ) {
+ int i;
+ int num;
+ idEntity * hit;
+ idClipModel *cm;
+ idClipModel *clipModels[ MAX_GENTITIES ];
+
+ num = gameLocal.clip.ClipModelsTouchingBounds( physicsObj.GetAbsBounds(), MASK_MONSTERSOLID, clipModels, MAX_GENTITIES );
+ for ( i = 0; i < num; i++ ) {
+ cm = clipModels[ i ];
+
+ // don't check render entities
+ if ( cm->IsRenderModel() ) {
+ continue;
+ }
+
+ hit = cm->GetEntity();
+ if ( hit == this ) {
+ continue;
+ }
+
+ // Special case, crush any moveables blocking our start point
+ if ( hit->IsType( idMoveable::Type ) ) {
+ hit->Damage( this, this, vec3_origin, "damage_gib", 1.0f, INVALID_JOINT );
+ hit->SquishedByDoor(this);
+ }
+
+ if ( !hit->fl.takedamage ) {
+ continue;
+ }
+
+ if ( physicsObj.ClipContents( cm ) ) {
+ idThread::ReturnFloat( false );
+ return;
+ }
+ }
+
+ idThread::ReturnFloat( true );
+}
+
+#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build
\ No newline at end of file
diff --git a/neo/prey/Prey/ai_harvester_simple.h b/neo/prey/Prey/ai_harvester_simple.h
new file mode 100644
index 00000000..e5da0879
--- /dev/null
+++ b/neo/prey/Prey/ai_harvester_simple.h
@@ -0,0 +1,79 @@
+
+#ifndef __PREY_AI_HARVESTERSIMPLE_H__
+#define __PREY_AI_HARVESTERSIMPLE_H__
+
+extern const idEventDef AI_OnProjectileLaunch;
+extern const idEventDef MA_EnterPassageway;
+extern const idEventDef MA_ExitPassageway;
+
+#define MAX_HARVESTER_LEGS 4
+
+class hhHarvesterSimple : public hhMonsterAI {
+
+public:
+ CLASS_PROTOTYPE(hhHarvesterSimple);
+
+#ifdef ID_DEMO_BUILD //HUMANHEAD jsh PCF 5/26/06: code removed for demo build
+ virtual void Event_StartPreDeath(void) {};
+ void Event_OnProjectileLaunch(hhProjectile *proj) {};
+ void Event_AntiProjectileAttack(hhProjectile *proj) {};
+ void Event_EnterPassageway(hhAIPassageway *pn) {};
+ void Event_ExitPassageway(hhAIPassageway *pn) {};
+ void Event_HandlePassageway(void) {};
+ void Event_UseThisPassageway(hhAIPassageway *pn, bool force) {};
+ void Event_AppendFxToList(hhEntityFx *fx) {};
+ void Event_GibOnDeath(const idList* parmList) {};
+ virtual void Event_Activate(idEntity *activator) {};
+ virtual void Event_CanBecomeSolid(void) {};
+#else
+ virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location );
+ virtual void LinkScriptVariables(void);
+ void Save( idSaveGame *savefile ) const;
+ void Restore( idRestoreGame *savefile );
+
+ virtual void Killed(idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location);
+
+protected:
+
+ void Spawn(void);
+
+ virtual int EvaluateReaction( const hhReaction *react );
+
+ void SpawnSmoke(void);
+ void ClearSmoke(void);
+
+ //predeath torso code
+ virtual void Event_StartPreDeath(void);
+ virtual bool Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location );
+ bool allowPreDeath; // TRUE if we will allow predeath
+
+ virtual void AnimMove(void);
+
+ //anti-projectile chaff
+ void Event_OnProjectileLaunch(hhProjectile *proj);
+ void Event_AntiProjectileAttack(hhProjectile *proj);
+ void Event_EnterPassageway(hhAIPassageway *pn);
+ void Event_ExitPassageway(hhAIPassageway *pn);
+ void Event_HandlePassageway(void);
+ void Event_UseThisPassageway(hhAIPassageway *pn, bool force);
+ void Event_AppendFxToList(hhEntityFx *fx);
+ void Event_GibOnDeath(const idList* parmList);
+ virtual void Event_Activate(idEntity *activator);
+ virtual void Event_CanBecomeSolid(void);
+
+ int lastAntiProjectileAttack;
+ int lastPassagewayTime;
+ idEntityPtr lastPassageway;
+ idEntityPtr nextPassageway;
+ idScriptFloat AI_CLIMB_RANGE;
+ idScriptFloat AI_PASSAGEWAY_HEALTH;
+
+ idEntityPtr fxSmoke[MAX_HARVESTER_LEGS];
+
+ int passageCount;
+ bool bSmokes;
+ bool bGibOnDeath;
+#endif //HUMANHEAD jsh PCF 5/26/06: code removed for demo build
+};
+
+#endif
\ No newline at end of file
diff --git a/neo/prey/Prey/ai_hunter_simple.cpp b/neo/prey/Prey/ai_hunter_simple.cpp
new file mode 100644
index 00000000..0cdbebbb
--- /dev/null
+++ b/neo/prey/Prey/ai_hunter_simple.cpp
@@ -0,0 +1,2141 @@
+
+#include "precompiled.h"
+#pragma hdrstop
+
+#include "prey_local.h"
+
+const idEventDef MA_LaserOn("laserOn", NULL);
+const idEventDef MA_LaserOff("laserOff", NULL);
+const idEventDef MA_EscapePortal( "escapePortal" );
+const idEventDef MA_AssignSniperFx( "", "e" );
+const idEventDef MA_KickAngle( "kickAngle", "v" );
+const idEventDef MA_AssignFlashLightFx( "", "e" );
+const idEventDef MA_FlashLightOn( "flashLightOn" );
+const idEventDef MA_FlashLightOff( "flashLightOff" );
+const idEventDef MA_EnemyCanSee( "enemyCanSee", NULL, 'd' );
+const idEventDef MA_PrintAction( "printAction", "s" );
+const idEventDef MA_GetAlly( "getAlly", NULL, 'e' );
+const idEventDef MA_TriggerDelay( "triggerDelay", "Ef" );
+const idEventDef MA_CallBackup( "callBackup", "f" );
+const idEventDef MA_SaySound( "saySound", "s", 'f' );
+const idEventDef MA_CheckRush( "" );
+const idEventDef MA_CheckRetreat( "