Public Functions
ConsoleDocClass(Trigger , "@brief A <a href="/coding/class/classtrigger/">Trigger</a> is <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> volume of space that initiates script callbacks " "when objects pass through the <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">Trigger.\n\n</a>" "<a href="/coding/class/structtriggerdata/">TriggerData</a> provides the callbacks <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> the <a href="/coding/class/classtrigger/">Trigger</a> when an object enters, stays inside " "or leaves the <a href="/coding/class/classtrigger/">Trigger</a> 's <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">volume.\n\n</a>" " @see <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">TriggerData\n</a>" " @ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">gameObjects\n</a>" )
ConsoleDocClass(TriggerData , "@brief Defines shared properties <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> <a href="/coding/class/classtrigger/">Trigger</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">objects.\n\n</a>" "The primary focus of the <a href="/coding/class/structtriggerdata/">TriggerData</a> datablock is the callbacks it provides when an object is " "within or leaves the <a href="/coding/class/classtrigger/">Trigger</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">bounds.\n</a>" "@see <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">Trigger.\n</a>" "@ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">gameObjects\n</a>" "@ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">Datablocks\n</a>" )
ConsoleSetType(TypeTriggerPolyhedron )
ConsoleType(floatList , TypeTriggerPolyhedron , Polyhedron , "" )
DECLARE_STRUCT(Polyhedron )
DefineEngineMethod(Trigger , getNumObjects , S32 , () , "@brief Get the number of objects that are within the <a href="/coding/class/classtrigger/">Trigger</a>'s <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">bounds.\n\n</a>" "@see getObject()\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" )
DefineEngineMethod(Trigger , getObject , S32 , (S32 index) , "@brief Retrieve the requested object that is within the <a href="/coding/class/classtrigger/">Trigger</a>'s <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">bounds.\n\n</a>" "@param index Index of the object <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> get (range is 0 <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> getNumObjects()-1)\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "@returns The SimObjectID of the object, or -1 <a href="/coding/file/tsmeshintrinsics_8cpp/#tsmeshintrinsics_8cpp_1a4e4fa7e3399708e0777b5308db01278c">if</a> the requested index is <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">invalid.\n</a>" " @see getNumObjects()\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" )
IMPLEMENT_CALLBACK(Trigger , onAdd , void , (U32 objectId) , (objectId) , "@brief Called when the <a href="/coding/class/classtrigger/">Trigger</a> is being <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">created.\n\n</a>" "@param objectId the object <a href="/coding/file/win32cursorcontroller_8cpp/#win32cursorcontroller_8cpp_1ab38592509822a5f4674447022cc62efe">id</a> of the <a href="/coding/class/classtrigger/">Trigger</a> being <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">created\n</a>" )
IMPLEMENT_CALLBACK(Trigger , onRemove , void , (U32 objectId) , (objectId) , "@brief Called just before the <a href="/coding/class/classtrigger/">Trigger</a> is <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">deleted.\n\n</a>" "@param objectId the object <a href="/coding/file/win32cursorcontroller_8cpp/#win32cursorcontroller_8cpp_1ab38592509822a5f4674447022cc62efe">id</a> of the <a href="/coding/class/classtrigger/">Trigger</a> being <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">deleted\n</a>" )
IMPLEMENT_CALLBACK(TriggerData , onEnterTrigger , void , (Trigger *trigger, GameBase *obj) , (trigger, obj) , "@brief Called when an object enters the volume of the <a href="/coding/class/classtrigger/">Trigger</a> instance using this <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">TriggerData.\n\n</a>" "@param trigger the <a href="/coding/class/classtrigger/">Trigger</a> instance whose volume the object <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">entered\n</a>" "@param obj the object that entered the volume of the <a href="/coding/class/classtrigger/">Trigger</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">instance\n</a>" )
IMPLEMENT_CALLBACK(TriggerData , onLeaveTrigger , void , (Trigger *trigger, GameBase *obj) , (trigger, obj) , "@brief Called when an object leaves the volume of the <a href="/coding/class/classtrigger/">Trigger</a> instance using this <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">TriggerData.\n\n</a>" "@param trigger the <a href="/coding/class/classtrigger/">Trigger</a> instance whose volume the object <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">left\n</a>" "@param obj the object that left the volume of the <a href="/coding/class/classtrigger/">Trigger</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">instance\n</a>" )
IMPLEMENT_CALLBACK(TriggerData , onTickTrigger , void , (Trigger *trigger) , (trigger) , "@brief Called every tickPeriodMS number of milliseconds (as specified in the <a href="/coding/class/structtriggerdata/">TriggerData</a>) whenever " "one or more objects are inside the volume of the <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">trigger.\n\n</a>" "The <a href="/coding/class/classtrigger/">Trigger</a> has methods <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> retrieve the objects that are within the <a href="/coding/class/classtrigger/">Trigger</a>'s bounds <a href="/coding/file/tsmeshintrinsics_8cpp/#tsmeshintrinsics_8cpp_1a4e4fa7e3399708e0777b5308db01278c">if</a> you " "want <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> do something with them in this <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">callback.\n</a>" "@param trigger the <a href="/coding/class/classtrigger/">Trigger</a> instance whose volume the object is <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">inside\n</a>" "@see <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">tickPeriodMS\n</a>" "@see Trigger::getNumObjects()\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "@see <a href="/coding/class/classtrigger/#classtrigger_1ad49a7d5affe8f08e522d634fce71b648">Trigger::getObject</a>()\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" )
IMPLEMENT_CO_DATABLOCK_V1(TriggerData )
IMPLEMENT_CO_NETOBJECT_V1(Trigger )
IMPLEMENT_STRUCT(Polyhedron , Polyhedron , "" )
1
2//-----------------------------------------------------------------------------
3// Copyright (c) 2012 GarageGames, LLC
4//
5// Permission is hereby granted, free of charge, to any person obtaining a copy
6// of this software and associated documentation files (the "Software"), to
7// deal in the Software without restriction, including without limitation the
8// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
9// sell copies of the Software, and to permit persons to whom the Software is
10// furnished to do so, subject to the following conditions:
11//
12// The above copyright notice and this permission notice shall be included in
13// all copies or substantial portions of the Software.
14//
15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21// IN THE SOFTWARE.
22//-----------------------------------------------------------------------------
23
24#include "platform/platform.h"
25#include "T3D/trigger.h"
26
27#include "scene/sceneRenderState.h"
28#include "console/consoleTypes.h"
29#include "console/engineAPI.h"
30#include "collision/boxConvex.h"
31
32#include "core/stream/bitStream.h"
33#include "math/mathIO.h"
34#include "gfx/gfxTransformSaver.h"
35#include "renderInstance/renderPassManager.h"
36#include "gfx/gfxDrawUtil.h"
37#include "T3D/physics/physicsPlugin.h"
38#include "T3D/physics/physicsBody.h"
39#include "T3D/physics/physicsCollision.h"
40
41
42bool Trigger::smRenderTriggers = false;
43
44//-----------------------------------------------------------------------------
45
46//----------------------------------------------------------------------------
47
48IMPLEMENT_CO_DATABLOCK_V1(TriggerData);
49
50ConsoleDocClass( TriggerData,
51 "@brief Defines shared properties for Trigger objects.\n\n"
52
53 "The primary focus of the TriggerData datablock is the callbacks it provides when an object is "
54 "within or leaves the Trigger bounds.\n"
55
56 "@see Trigger.\n"
57 "@ingroup gameObjects\n"
58 "@ingroup Datablocks\n"
59);
60
61IMPLEMENT_CALLBACK( TriggerData, onEnterTrigger, void, ( Trigger* trigger, GameBase* obj ), ( trigger, obj ),
62 "@brief Called when an object enters the volume of the Trigger instance using this TriggerData.\n\n"
63
64 "@param trigger the Trigger instance whose volume the object entered\n"
65 "@param obj the object that entered the volume of the Trigger instance\n" );
66
67IMPLEMENT_CALLBACK( TriggerData, onTickTrigger, void, ( Trigger* trigger ), ( trigger ),
68 "@brief Called every tickPeriodMS number of milliseconds (as specified in the TriggerData) whenever "
69 "one or more objects are inside the volume of the trigger.\n\n"
70
71 "The Trigger has methods to retrieve the objects that are within the Trigger's bounds if you "
72 "want to do something with them in this callback.\n"
73
74 "@param trigger the Trigger instance whose volume the object is inside\n"
75
76 "@see tickPeriodMS\n"
77 "@see Trigger::getNumObjects()\n"
78 "@see Trigger::getObject()\n");
79
80IMPLEMENT_CALLBACK( TriggerData, onLeaveTrigger, void, ( Trigger* trigger, GameBase* obj ), ( trigger, obj ),
81 "@brief Called when an object leaves the volume of the Trigger instance using this TriggerData.\n\n"
82
83 "@param trigger the Trigger instance whose volume the object left\n"
84 "@param obj the object that left the volume of the Trigger instance\n" );
85
86TriggerData::TriggerData()
87{
88 tickPeriodMS = 100;
89 isClientSide = false;
90}
91
92bool TriggerData::onAdd()
93{
94 if (!Parent::onAdd())
95 return false;
96
97 return true;
98}
99
100void TriggerData::initPersistFields()
101{
102 addGroup("Callbacks");
103
104 addField( "tickPeriodMS", TypeS32, Offset( tickPeriodMS, TriggerData ),
105 "@brief Time in milliseconds between calls to onTickTrigger() while at least one object is within a Trigger's bounds.\n\n"
106 "@see onTickTrigger()\n");
107 addField( "clientSide", TypeBool, Offset( isClientSide, TriggerData ),
108 "Forces Trigger callbacks to only be called on clients.");
109
110 endGroup("Callbacks");
111
112 Parent::initPersistFields();
113}
114
115
116//--------------------------------------------------------------------------
117void TriggerData::packData(BitStream* stream)
118{
119 Parent::packData(stream);
120 stream->write(tickPeriodMS);
121 stream->write(isClientSide);
122}
123
124void TriggerData::unpackData(BitStream* stream)
125{
126 Parent::unpackData(stream);
127 stream->read(&tickPeriodMS);
128 stream->read(&isClientSide);
129}
130
131
132//--------------------------------------------------------------------------
133
134IMPLEMENT_CO_NETOBJECT_V1(Trigger);
135
136ConsoleDocClass( Trigger,
137 "@brief A Trigger is a volume of space that initiates script callbacks "
138 "when objects pass through the Trigger.\n\n"
139
140 "TriggerData provides the callbacks for the Trigger when an object enters, stays inside "
141 "or leaves the Trigger's volume.\n\n"
142
143 "@see TriggerData\n"
144 "@ingroup gameObjects\n"
145);
146
147IMPLEMENT_CALLBACK( Trigger, onAdd, void, ( U32 objectId ), ( objectId ),
148 "@brief Called when the Trigger is being created.\n\n"
149 "@param objectId the object id of the Trigger being created\n" );
150
151IMPLEMENT_CALLBACK( Trigger, onRemove, void, ( U32 objectId ), ( objectId ),
152 "@brief Called just before the Trigger is deleted.\n\n"
153 "@param objectId the object id of the Trigger being deleted\n" );
154
155Trigger::Trigger()
156{
157 // Don't ghost by default.
158 mNetFlags.set(Ghostable | ScopeAlways);
159
160 mTypeMask |= TriggerObjectType;
161
162 mObjScale.set(1, 1, 1);
163 mObjToWorld.identity();
164 mWorldToObj.identity();
165
166 mDataBlock = NULL;
167
168 mLastThink = 0;
169 mCurrTick = 0;
170
171 mConvexList = new Convex;
172
173 mPhysicsRep = NULL;
174 mTripOnce = false;
175 mTrippedBy = 0xFFFFFFFF;
176 mTripCondition = "";
177}
178
179Trigger::~Trigger()
180{
181 delete mConvexList;
182 mConvexList = NULL;
183 SAFE_DELETE( mPhysicsRep );
184}
185
186bool Trigger::castRay(const Point3F &start, const Point3F &end, RayInfo* info)
187{
188 // Collide against bounding box
189 F32 st,et,fst = 0,fet = 1;
190 F32 *bmin = &mObjBox.minExtents.x;
191 F32 *bmax = &mObjBox.maxExtents.x;
192 F32 const *si = &start.x;
193 F32 const *ei = &end.x;
194
195 for (S32 i = 0; i < 3; i++)
196 {
197 if (*si < *ei)
198 {
199 if (*si > *bmax || *ei < *bmin)
200 return false;
201 F32 di = *ei - *si;
202 st = (*si < *bmin)? (*bmin - *si) / di: 0;
203 et = (*ei > *bmax)? (*bmax - *si) / di: 1;
204 }
205 else
206 {
207 if (*ei > *bmax || *si < *bmin)
208 return false;
209 F32 di = *ei - *si;
210 st = (*si > *bmax)? (*bmax - *si) / di: 0;
211 et = (*ei < *bmin)? (*bmin - *si) / di: 1;
212 }
213 if (st > fst) fst = st;
214 if (et < fet) fet = et;
215 if (fet < fst)
216 return false;
217 bmin++; bmax++;
218 si++; ei++;
219 }
220
221 info->normal = start - end;
222 info->normal.normalizeSafe();
223 getTransform().mulV( info->normal );
224
225 info->t = fst;
226 info->object = this;
227 info->point.interpolate(start,end,fst);
228 info->material = 0;
229 return true;
230}
231
232
233//--------------------------------------------------------------------------
234/* Console polyhedron data type exporter
235 The polyhedron type is really a quadrilateral and consists of a corner
236 point follow by three vectors representing the edges extending from the
237 corner.
238*/
239DECLARE_STRUCT( Polyhedron );
240IMPLEMENT_STRUCT( Polyhedron, Polyhedron,,
241 "" )
242
243 FIELD(mPointList, pointList, 1, "")
244 FIELD(mPlaneList, planeList, 1, "")
245 FIELD(mEdgeList, edgeList, 1, "")
246
247END_IMPLEMENT_STRUCT;
248ConsoleType(floatList, TypeTriggerPolyhedron, Polyhedron, "")
249
250
251ConsoleGetType( TypeTriggerPolyhedron )
252{
253 U32 i;
254 Polyhedron* pPoly = reinterpret_cast<Polyhedron*>(dptr);
255
256 // First point is corner, need to find the three vectors...`
257 Point3F origin = pPoly->mPointList[0];
258 U32 currVec = 0;
259 Point3F vecs[3];
260 for (i = 0; i < pPoly->mEdgeList.size(); i++) {
261 const U32 *vertex = pPoly->mEdgeList[i].vertex;
262 if (vertex[0] == 0)
263 vecs[currVec++] = pPoly->mPointList[vertex[1]] - origin;
264 else
265 if (vertex[1] == 0)
266 vecs[currVec++] = pPoly->mPointList[vertex[0]] - origin;
267 }
268 AssertFatal(currVec == 3, "Internal error: Bad trigger polyhedron");
269
270 // Build output string.
271 static const U32 bufSize = 1024;
272 char* retBuf = Con::getReturnBuffer(bufSize);
273 dSprintf(retBuf, bufSize, "%7.7f %7.7f %7.7f %7.7f %7.7f %7.7f %7.7f %7.7f %7.7f %7.7f %7.7f %7.7f",
274 origin.x, origin.y, origin.z,
275 vecs[0].x, vecs[0].y, vecs[0].z,
276 vecs[2].x, vecs[2].y, vecs[2].z,
277 vecs[1].x, vecs[1].y, vecs[1].z);
278
279
280 return retBuf;
281}
282
283/* Console polyhedron data type loader
284 The polyhedron type is really a quadrilateral and consists of an corner
285 point follow by three vectors representing the edges extending from the
286 corner.
287*/
288ConsoleSetType( TypeTriggerPolyhedron )
289{
290 if (argc != 1) {
291 Con::printf("(TypeTriggerPolyhedron) multiple args not supported for polyhedra");
292 return;
293 }
294
295 Point3F origin;
296 Point3F vecs[3];
297
298 U32 numArgs = dSscanf(argv[0], "%g %g %g %g %g %g %g %g %g %g %g %g",
299 &origin.x, &origin.y, &origin.z,
300 &vecs[0].x, &vecs[0].y, &vecs[0].z,
301 &vecs[1].x, &vecs[1].y, &vecs[1].z,
302 &vecs[2].x, &vecs[2].y, &vecs[2].z);
303 if (numArgs != 12) {
304 Con::printf("Bad polyhedron!");
305 return;
306 }
307
308 Polyhedron* pPoly = reinterpret_cast<Polyhedron*>(dptr);
309
310 // This setup goes against conventions for Polyhedrons in that it a) sets up
311 // edges with CCW instead of CW order for face[0] and that it b) lets plane
312 // normals face outwards rather than inwards.
313
314 pPoly->mPointList.setSize(8);
315 pPoly->mPointList[0] = origin;
316 pPoly->mPointList[1] = origin + vecs[0];
317 pPoly->mPointList[2] = origin + vecs[1];
318 pPoly->mPointList[3] = origin + vecs[2];
319 pPoly->mPointList[4] = origin + vecs[0] + vecs[1];
320 pPoly->mPointList[5] = origin + vecs[0] + vecs[2];
321 pPoly->mPointList[6] = origin + vecs[1] + vecs[2];
322 pPoly->mPointList[7] = origin + vecs[0] + vecs[1] + vecs[2];
323
324 Point3F normal;
325 pPoly->mPlaneList.setSize(6);
326
327 mCross(vecs[2], vecs[0], &normal);
328 pPoly->mPlaneList[0].set(origin, normal);
329 mCross(vecs[0], vecs[1], &normal);
330 pPoly->mPlaneList[1].set(origin, normal);
331 mCross(vecs[1], vecs[2], &normal);
332 pPoly->mPlaneList[2].set(origin, normal);
333 mCross(vecs[1], vecs[0], &normal);
334 pPoly->mPlaneList[3].set(pPoly->mPointList[7], normal);
335 mCross(vecs[2], vecs[1], &normal);
336 pPoly->mPlaneList[4].set(pPoly->mPointList[7], normal);
337 mCross(vecs[0], vecs[2], &normal);
338 pPoly->mPlaneList[5].set(pPoly->mPointList[7], normal);
339
340 pPoly->mEdgeList.setSize(12);
341 pPoly->mEdgeList[0].vertex[0] = 0; pPoly->mEdgeList[0].vertex[1] = 1; pPoly->mEdgeList[0].face[0] = 0; pPoly->mEdgeList[0].face[1] = 1;
342 pPoly->mEdgeList[1].vertex[0] = 1; pPoly->mEdgeList[1].vertex[1] = 5; pPoly->mEdgeList[1].face[0] = 0; pPoly->mEdgeList[1].face[1] = 4;
343 pPoly->mEdgeList[2].vertex[0] = 5; pPoly->mEdgeList[2].vertex[1] = 3; pPoly->mEdgeList[2].face[0] = 0; pPoly->mEdgeList[2].face[1] = 3;
344 pPoly->mEdgeList[3].vertex[0] = 3; pPoly->mEdgeList[3].vertex[1] = 0; pPoly->mEdgeList[3].face[0] = 0; pPoly->mEdgeList[3].face[1] = 2;
345 pPoly->mEdgeList[4].vertex[0] = 3; pPoly->mEdgeList[4].vertex[1] = 6; pPoly->mEdgeList[4].face[0] = 3; pPoly->mEdgeList[4].face[1] = 2;
346 pPoly->mEdgeList[5].vertex[0] = 6; pPoly->mEdgeList[5].vertex[1] = 2; pPoly->mEdgeList[5].face[0] = 2; pPoly->mEdgeList[5].face[1] = 5;
347 pPoly->mEdgeList[6].vertex[0] = 2; pPoly->mEdgeList[6].vertex[1] = 0; pPoly->mEdgeList[6].face[0] = 2; pPoly->mEdgeList[6].face[1] = 1;
348 pPoly->mEdgeList[7].vertex[0] = 1; pPoly->mEdgeList[7].vertex[1] = 4; pPoly->mEdgeList[7].face[0] = 4; pPoly->mEdgeList[7].face[1] = 1;
349 pPoly->mEdgeList[8].vertex[0] = 4; pPoly->mEdgeList[8].vertex[1] = 2; pPoly->mEdgeList[8].face[0] = 1; pPoly->mEdgeList[8].face[1] = 5;
350 pPoly->mEdgeList[9].vertex[0] = 4; pPoly->mEdgeList[9].vertex[1] = 7; pPoly->mEdgeList[9].face[0] = 4; pPoly->mEdgeList[9].face[1] = 5;
351 pPoly->mEdgeList[10].vertex[0] = 5; pPoly->mEdgeList[10].vertex[1] = 7; pPoly->mEdgeList[10].face[0] = 3; pPoly->mEdgeList[10].face[1] = 4;
352 pPoly->mEdgeList[11].vertex[0] = 7; pPoly->mEdgeList[11].vertex[1] = 6; pPoly->mEdgeList[11].face[0] = 3; pPoly->mEdgeList[11].face[1] = 5;
353}
354
355
356//-----------------------------------------------------------------------------
357void Trigger::consoleInit()
358{
359 Con::addVariable( "$Trigger::renderTriggers", TypeBool, &smRenderTriggers,
360 "@brief Forces all Trigger's to render.\n\n"
361 "Used by the Tools and debug render modes.\n"
362 "@ingroup gameObjects" );
363}
364
365void Trigger::initPersistFields()
366{
367 addField("polyhedron", TypeTriggerPolyhedron, Offset(mTriggerPolyhedron, Trigger),
368 "@brief Defines a non-rectangular area for the trigger.\n\n"
369 "Rather than the standard rectangular bounds, this optional parameter defines a quadrilateral "
370 "trigger area. The quadrilateral is defined as a corner point followed by three vectors "
371 "representing the edges extending from the corner.\n");
372
373 addField("TripOnce", TypeBool, Offset(mTripOnce, Trigger),"Do we trigger callacks just the once?");
374 addField("TripCondition", TypeRealString, Offset(mTripCondition, Trigger),"evaluation condition to trip callbacks (true/false)");
375 addField("TrippedBy", TypeS32, Offset(mTrippedBy, Trigger), "typemask filter");
376 addProtectedField("enterCommand", TypeCommand, Offset(mEnterCommand, Trigger), &setEnterCmd, &defaultProtectedGetFn,
377 "The command to execute when an object enters this trigger. Object id stored in %%obj. Maximum 1023 characters." );
378 addProtectedField("leaveCommand", TypeCommand, Offset(mLeaveCommand, Trigger), &setLeaveCmd, &defaultProtectedGetFn,
379 "The command to execute when an object leaves this trigger. Object id stored in %%obj. Maximum 1023 characters." );
380 addProtectedField("tickCommand", TypeCommand, Offset(mTickCommand, Trigger), &setTickCmd, &defaultProtectedGetFn,
381 "The command to execute while an object is inside this trigger. Maximum 1023 characters." );
382
383 Parent::initPersistFields();
384}
385
386bool Trigger::setEnterCmd( void *object, const char *index, const char *data )
387{
388 static_cast<Trigger*>(object)->setMaskBits(EnterCmdMask);
389 return true; // to update the actual field
390}
391
392bool Trigger::setLeaveCmd(void *object, const char *index, const char *data)
393{
394 static_cast<Trigger*>(object)->setMaskBits(LeaveCmdMask);
395 return true; // to update the actual field
396}
397
398bool Trigger::setTickCmd(void *object, const char *index, const char *data)
399{
400 static_cast<Trigger*>(object)->setMaskBits(TickCmdMask);
401 return true; // to update the actual field
402}
403
404//------------------------------------------------------------------------------
405
406void Trigger::testObjects()
407{
408 Vector<SceneObject*> foundobjs;
409 foundobjs.clear();
410 if (getSceneManager() && getSceneManager()->getContainer() && getSceneManager()->getZoneManager())
411 getSceneManager()->getContainer()->findObjectList(getWorldBox(), mTrippedBy, &foundobjs);
412 else return;
413
414 for (S32 i = 0; i < foundobjs.size(); i++)
415 {
416 GameBase* so = dynamic_cast<GameBase*>(foundobjs[i]);
417 if (so)
418 potentialEnterObject(so);
419 }
420}
421
422//--------------------------------------------------------------------------
423
424bool Trigger::onAdd()
425{
426 if(!Parent::onAdd())
427 return false;
428
429 onAdd_callback( getId() );
430
431 Polyhedron temp = mTriggerPolyhedron;
432 setTriggerPolyhedron(temp);
433 mTripped = false;
434 addToScene();
435
436 if (isServerObject())
437 scriptOnAdd();
438
439 testObjects();
440
441 return true;
442}
443
444void Trigger::onRemove()
445{
446 onRemove_callback( getId() );
447
448 mConvexList->nukeList();
449
450 removeFromScene();
451 Parent::onRemove();
452}
453
454bool Trigger::onNewDataBlock( GameBaseData *dptr, bool reload )
455{
456 mDataBlock = dynamic_cast<TriggerData*>( dptr );
457 if ( !mDataBlock || !Parent::onNewDataBlock( dptr, reload ) )
458 return false;
459
460 scriptOnNewDataBlock();
461 return true;
462}
463
464void Trigger::onDeleteNotify( SimObject *obj )
465{
466 GameBase* pScene = dynamic_cast<GameBase*>( obj );
467
468 if ( pScene != NULL && mDataBlock != NULL )
469 {
470 for ( U32 i = 0; i < mObjects.size(); i++ )
471 {
472 if ( pScene == mObjects[i] )
473 {
474 mObjects.erase(i);
475 if (mDataBlock)
476 mDataBlock->onLeaveTrigger_callback( this, NULL );
477 break;
478 }
479 }
480 }
481
482 Parent::onDeleteNotify( obj );
483}
484
485void Trigger::inspectPostApply()
486{
487 setTriggerPolyhedron(mTriggerPolyhedron);
488 setMaskBits(PolyMask);
489 Parent::inspectPostApply();
490}
491
492//--------------------------------------------------------------------------
493
494void Trigger::buildConvex(const Box3F& box, Convex* convex)
495{
496 // These should really come out of a pool
497 mConvexList->collectGarbage();
498
499 Box3F realBox = box;
500 mWorldToObj.mul(realBox);
501 realBox.minExtents.convolveInverse(mObjScale);
502 realBox.maxExtents.convolveInverse(mObjScale);
503
504 if (realBox.isOverlapped(getObjBox()) == false)
505 return;
506
507 // Just return a box convex for the entire shape...
508 Convex* cc = 0;
509 CollisionWorkingList& wl = convex->getWorkingList();
510 for (CollisionWorkingList* itr = wl.wLink.mNext; itr != &wl; itr = itr->wLink.mNext) {
511 if (itr->mConvex->getType() == BoxConvexType &&
512 itr->mConvex->getObject() == this) {
513 cc = itr->mConvex;
514 break;
515 }
516 }
517 if (cc)
518 return;
519
520 // Create a new convex.
521 BoxConvex* cp = new BoxConvex;
522 mConvexList->registerObject(cp);
523 convex->addToWorkingList(cp);
524 cp->init(this);
525
526 mObjBox.getCenter(&cp->mCenter);
527 cp->mSize.x = mObjBox.len_x() / 2.0f;
528 cp->mSize.y = mObjBox.len_y() / 2.0f;
529 cp->mSize.z = mObjBox.len_z() / 2.0f;
530}
531
532//------------------------------------------------------------------------------
533
534void Trigger::setTransform(const MatrixF & mat)
535{
536 Parent::setTransform(mat);
537
538 if ( mPhysicsRep )
539 mPhysicsRep->setTransform( mat );
540
541 if (isServerObject()) {
542 MatrixF base(true);
543 base.scale(Point3F(1.0/<a href="/coding/class/classsceneobject/#classsceneobject_1abf78914dea349b0201b66591567c6d91">mObjScale</a>.x,
544 1.0/<a href="/coding/class/classsceneobject/#classsceneobject_1abf78914dea349b0201b66591567c6d91">mObjScale</a>.y,
545 1.0/<a href="/coding/class/classsceneobject/#classsceneobject_1abf78914dea349b0201b66591567c6d91">mObjScale</a>.z));
546 base.mul(mWorldToObj);
547 mClippedList.setBaseTransform(base);
548
549 setMaskBits(TransformMask | ScaleMask);
550 }
551
552 testObjects();
553}
554
555void Trigger::onUnmount( SceneObject *obj, S32 node )
556{
557 Parent::onUnmount( obj, node );
558 // Make sure the client get's the final server pos.
559 setMaskBits(TransformMask | ScaleMask);
560}
561
562void Trigger::prepRenderImage( SceneRenderState *state )
563{
564 // only render if selected or render flag is set
565 if ( !smRenderTriggers && !isSelected() )
566 return;
567
568 ObjectRenderInst *ri = state->getRenderPass()->allocInst<ObjectRenderInst>();
569 ri->renderDelegate.bind( this, &Trigger::renderObject );
570 ri->type = RenderPassManager::RIT_Editor;
571 ri->translucentSort = true;
572 ri->defaultKey = 1;
573 state->getRenderPass()->addInst( ri );
574}
575
576void Trigger::renderObject( ObjectRenderInst *ri,
577 SceneRenderState *state,
578 BaseMatInstance *overrideMat )
579{
580 if(overrideMat)
581 return;
582
583 GFXStateBlockDesc desc;
584 desc.setZReadWrite( true, false );
585 desc.setBlend( true );
586
587 // Trigger polyhedrons are set up with outward facing normals and CCW ordering
588 // so can't enable backface culling.
589 desc.setCullMode( GFXCullNone );
590
591 GFXTransformSaver saver;
592
593 MatrixF mat = getRenderTransform();
594 mat.scale( getScale() );
595
596 GFX->multWorld( mat );
597
598 GFXDrawUtil *drawer = GFX->getDrawUtil();
599
600 drawer->drawPolyhedron( desc, mTriggerPolyhedron, ColorI( 255, 192, 0, 45 ) );
601
602 // Render wireframe.
603
604 desc.setFillModeWireframe();
605 drawer->drawPolyhedron( desc, mTriggerPolyhedron, ColorI::BLACK );
606}
607
608void Trigger::setTriggerPolyhedron(const Polyhedron& rPolyhedron)
609{
610 mTriggerPolyhedron = rPolyhedron;
611
612 if (mTriggerPolyhedron.mPointList.size() != 0) {
613 mObjBox.minExtents.set(1e10, 1e10, 1e10);
614 mObjBox.maxExtents.set(-1e10, -1e10, -1e10);
615 for (U32 i = 0; i < mTriggerPolyhedron.mPointList.size(); i++) {
616 mObjBox.minExtents.setMin(mTriggerPolyhedron.mPointList[i]);
617 mObjBox.maxExtents.setMax(mTriggerPolyhedron.mPointList[i]);
618 }
619 } else {
620 mObjBox.minExtents.set(-0.5, -0.5, -0.5);
621 mObjBox.maxExtents.set( 0.5, 0.5, 0.5);
622 }
623
624 MatrixF xform = getTransform();
625 setTransform(xform);
626
627 mClippedList.clear();
628 mClippedList.mPlaneList = mTriggerPolyhedron.mPlaneList;
629// for (U32 i = 0; i < mClippedList.mPlaneList.size(); i++)
630// mClippedList.mPlaneList[i].neg();
631
632 MatrixF base(true);
633 base.scale(Point3F(1.0/<a href="/coding/class/classsceneobject/#classsceneobject_1abf78914dea349b0201b66591567c6d91">mObjScale</a>.x,
634 1.0/<a href="/coding/class/classsceneobject/#classsceneobject_1abf78914dea349b0201b66591567c6d91">mObjScale</a>.y,
635 1.0/<a href="/coding/class/classsceneobject/#classsceneobject_1abf78914dea349b0201b66591567c6d91">mObjScale</a>.z));
636 base.mul(mWorldToObj);
637
638 mClippedList.setBaseTransform(base);
639
640 SAFE_DELETE( mPhysicsRep );
641
642 if ( PHYSICSMGR )
643 {
644 PhysicsCollision *colShape = PHYSICSMGR->createCollision();
645
646 MatrixF colMat( true );
647 colMat.displace( Point3F( 0, 0, mObjBox.getExtents().z * 0.5f * mObjScale.z ) );
648
649 colShape->addBox( mObjBox.getExtents() * 0.5f * mObjScale, colMat );
650 //MatrixF colMat( true );
651 //colMat.scale( mObjScale );
652 //colShape->addConvex( mTriggerPolyhedron.pointList.address(), mTriggerPolyhedron.pointList.size(), colMat );
653
654 PhysicsWorld *world = PHYSICSMGR->getWorld( isServerObject() ? "server" : "client" );
655 mPhysicsRep = PHYSICSMGR->createBody();
656 mPhysicsRep->init( colShape, 0, PhysicsBody::BF_TRIGGER | PhysicsBody::BF_KINEMATIC, this, world );
657 mPhysicsRep->setTransform( getTransform() );
658 }
659}
660
661
662//--------------------------------------------------------------------------
663
664bool Trigger::testObject(GameBase* enter)
665{
666 if (mTriggerPolyhedron.mPointList.size() == 0)
667 return false;
668
669 if (!(enter->getTypeMask() & mTrippedBy))
670 return false; //not the right type of object
671
672 mClippedList.clear();
673
674 SphereF sphere;
675 sphere.center = (mWorldBox.minExtents + mWorldBox.maxExtents) * 0.5;
676 VectorF bv = mWorldBox.maxExtents - sphere.center;
677 sphere.radius = bv.len();
678
679 enter->buildPolyList(PLC_Collision, &mClippedList, mWorldBox, sphere);
680 return mClippedList.isEmpty() == false;
681}
682
683bool Trigger::testTrippable()
684{
685 if ((mTripOnce == true) && (mTripped == true))
686 return false; // we've already fired the once
687 return true;
688}
689
690bool Trigger::testCondition()
691{
692 if (mTripCondition.isEmpty())
693 return true; //we've got no tests to run so just do it
694
695 //test the mapper plugged in condition line
696 String resVar = getIdString() + String(".result");
697 Con::setBoolVariable(resVar.c_str(), false);
698 String command = resVar + "=" + mTripCondition + ";";
699 Con::evaluatef(command.c_str());
700 if (Con::getBoolVariable(resVar.c_str()) == 1)
701 {
702 return true;
703 }
704 return false;
705}
706
707bool Trigger::evalCmD(String* cmd)
708{
709 if (!testTrippable()) return false;
710 if (cmd && cmd->isNotEmpty())//do we have a callback?
711 {
712 return testCondition();
713 }
714 return false;
715}
716
717void Trigger::potentialEnterObject(GameBase* enter)
718{
719 if( (!mDataBlock || mDataBlock->isClientSide) && isServerObject() )
720 return;
721 if( (mDataBlock && !mDataBlock->isClientSide) && isGhost() )
722 return;
723
724 for (U32 i = 0; i < mObjects.size(); i++) {
725 if (mObjects[i] == enter)
726 return;
727 }
728
729 if (testObject(enter) == true) {
730 mObjects.push_back(enter);
731 deleteNotify(enter);
732
733 if(evalCmD(&mEnterCommand))
734 {
735 String command = String("%obj = ") + enter->getIdString() + ";" + mEnterCommand;
736 Con::evaluate(command.c_str());
737 }
738
739 if( mDataBlock && testTrippable() && testCondition())
740 mDataBlock->onEnterTrigger_callback( this, enter );
741 mTripped = true;
742 }
743}
744
745
746void Trigger::processTick(const Move* move)
747{
748 Parent::processTick(move);
749
750 if (!mDataBlock)
751 return;
752 if (mDataBlock->isClientSide && isServerObject())
753 return;
754 if (!mDataBlock->isClientSide && isClientObject())
755 return;
756
757 if (isMounted()) {
758 MatrixF mat;
759 mMount.object->getMountTransform( mMount.node, mMount.xfm, &mat );
760 setTransform(mat);
761 setRenderTransform(mat);
762 }
763
764 //
765 if (mObjects.size() == 0)
766 return;
767
768 if (mLastThink + mDataBlock->tickPeriodMS < mCurrTick)
769 {
770 mCurrTick = 0;
771 mLastThink = 0;
772
773 for (S32 i = S32(mObjects.size() - 1); i >= 0; i--)
774 {
775 if (testObject(mObjects[i]) == false)
776 {
777 GameBase* remove = mObjects[i];
778 mObjects.erase(i);
779 clearNotify(remove);
780
781 if (evalCmD(&mLeaveCommand))
782 {
783 String command = String("%obj = ") + remove->getIdString() + ";" + mLeaveCommand;
784 Con::evaluate(command.c_str());
785 }
786 if (testTrippable() && testCondition())
787 mDataBlock->onLeaveTrigger_callback( this, remove );
788 mTripped = true;
789 }
790 }
791
792 if (evalCmD(&mTickCommand))
793 Con::evaluate(mTickCommand.c_str());
794
795 if (mObjects.size() != 0 && testTrippable() && testCondition())
796 mDataBlock->onTickTrigger_callback( this );
797 }
798 else
799 {
800 mCurrTick += TickMs;
801 }
802}
803
804void Trigger::interpolateTick(F32 delta)
805{
806 if (isMounted()) {
807 MatrixF mat;
808 mMount.object->getRenderMountTransform( delta, mMount.node, mMount.xfm, &mat );
809 setRenderTransform(mat);
810 }
811}
812
813//--------------------------------------------------------------------------
814
815U32 Trigger::packUpdate(NetConnection* con, U32 mask, BitStream* stream)
816{
817 U32 i;
818 U32 retMask = Parent::packUpdate(con, mask, stream);
819
820 if( stream->writeFlag( mask & TransformMask ) )
821 {
822 stream->writeAffineTransform(mObjToWorld);
823 }
824
825 // Write the polyhedron
826 if( stream->writeFlag( mask & PolyMask ) )
827 {
828 stream->write(mTriggerPolyhedron.mPointList.size());
829 for (i = 0; i < mTriggerPolyhedron.mPointList.size(); i++)
830 mathWrite(*stream, mTriggerPolyhedron.mPointList[i]);
831
832 stream->write(mTriggerPolyhedron.mPlaneList.size());
833 for (i = 0; i < mTriggerPolyhedron.mPlaneList.size(); i++)
834 mathWrite(*stream, mTriggerPolyhedron.mPlaneList[i]);
835
836 stream->write(mTriggerPolyhedron.mEdgeList.size());
837 for (i = 0; i < mTriggerPolyhedron.mEdgeList.size(); i++) {
838 const Polyhedron::Edge& rEdge = mTriggerPolyhedron.mEdgeList[i];
839
840 stream->write(rEdge.face[0]);
841 stream->write(rEdge.face[1]);
842 stream->write(rEdge.vertex[0]);
843 stream->write(rEdge.vertex[1]);
844 }
845 }
846
847 if( stream->writeFlag( mask & EnterCmdMask ) )
848 stream->writeLongString(CMD_SIZE-1, mEnterCommand.c_str());
849 if( stream->writeFlag( mask & LeaveCmdMask ) )
850 stream->writeLongString(CMD_SIZE-1, mLeaveCommand.c_str());
851 if( stream->writeFlag( mask & TickCmdMask ) )
852 stream->writeLongString(CMD_SIZE-1, mTickCommand.c_str());
853
854 return retMask;
855}
856
857void Trigger::unpackUpdate(NetConnection* con, BitStream* stream)
858{
859 Parent::unpackUpdate(con, stream);
860
861 U32 i, size;
862
863 // Transform
864 if( stream->readFlag() )
865 {
866 MatrixF temp;
867 stream->readAffineTransform(&temp);
868 setTransform(temp);
869 }
870
871 // Read the polyhedron
872 if( stream->readFlag() )
873 {
874 Polyhedron tempPH;
875 stream->read(&size);
876 tempPH.mPointList.setSize(size);
877 for (i = 0; i < tempPH.mPointList.size(); i++)
878 mathRead(*stream, &tempPH.mPointList[i]);
879
880 stream->read(&size);
881 tempPH.mPlaneList.setSize(size);
882 for (i = 0; i < tempPH.mPlaneList.size(); i++)
883 mathRead(*stream, &tempPH.mPlaneList[i]);
884
885 stream->read(&size);
886 tempPH.mEdgeList.setSize(size);
887 for (i = 0; i < tempPH.mEdgeList.size(); i++) {
888 Polyhedron::Edge& rEdge = tempPH.mEdgeList[i];
889
890 stream->read(&rEdge.face[0]);
891 stream->read(&rEdge.face[1]);
892 stream->read(&rEdge.vertex[0]);
893 stream->read(&rEdge.vertex[1]);
894 }
895 setTriggerPolyhedron(tempPH);
896 }
897
898 if( stream->readFlag() )
899 {
900 char buf[CMD_SIZE];
901 stream->readLongString(CMD_SIZE-1, buf);
902 mEnterCommand = buf;
903 }
904 if( stream->readFlag() )
905 {
906 char buf[CMD_SIZE];
907 stream->readLongString(CMD_SIZE-1, buf);
908 mLeaveCommand = buf;
909 }
910 if( stream->readFlag() )
911 {
912 char buf[CMD_SIZE];
913 stream->readLongString(CMD_SIZE-1, buf);
914 mTickCommand = buf;
915 }
916}
917
918//ConsoleMethod( Trigger, getNumObjects, S32, 2, 2, "")
919DefineEngineMethod( Trigger, getNumObjects, S32, (),,
920 "@brief Get the number of objects that are within the Trigger's bounds.\n\n"
921 "@see getObject()\n")
922{
923 return object->getNumTriggeringObjects();
924}
925
926//ConsoleMethod( Trigger, getObject, S32, 3, 3, "(int idx)")
927DefineEngineMethod( Trigger, getObject, S32, ( S32 index ),,
928 "@brief Retrieve the requested object that is within the Trigger's bounds.\n\n"
929 "@param index Index of the object to get (range is 0 to getNumObjects()-1)\n"
930 "@returns The SimObjectID of the object, or -1 if the requested index is invalid.\n"
931 "@see getNumObjects()\n")
932{
933 if (index >= object->getNumTriggeringObjects() || index < 0)
934 return -1;
935 else
936 return object->getObject(U32(index))->getId();
937}
938