Public Functions
ConsoleDocClass(Item , "@brief Base <a href="/coding/class/classitem/">Item</a> class. Uses the <a href="/coding/class/structitemdata/">ItemData</a> datablock <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> common <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">properties.\n\n</a>" "Items represent an object in the world)
ConsoleDocClass(ItemData , "@brief Stores properties <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> an individual <a href="/coding/class/classitem/">Item</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">type.\n\n</a>" "Items represent an object in the world, usually one that the player will interact with. " "One example is <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> health kit on the group that is automatically picked up when the player " "comes into contact with <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">it.\n\n</a>" "<a href="/coding/class/structitemdata/">ItemData</a> provides the common properties <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> set of Items. These properties include <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> " "DTS or DAE model used <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> <a href="/coding/file/editortool_8cpp/#editortool_8cpp_1a4cb041169a32ea3d4cacadbb955e06b4">render</a> the <a href="/coding/class/classitem/">Item</a> in the world, its physical properties <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> when the " "<a href="/coding/class/classitem/">Item</a> interacts with the world, and any lights that emit " "from the <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">Item.\n\n</a>" " @<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">tsexample\n</a>" "datablock <a href="/coding/class/structitemdata/">ItemData</a>(HealthKitSmall)\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "{\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " category=\"Health\";\n" " className = \"HealthPatch\";\n" " shapeFile = \"art/shapes/items/kit/healthkit.dts\";\n" " gravityMod = \"1.0\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " mass = 2;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " friction = 1;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " elasticity = 0.3;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " density = 2;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " drag = 0.5;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " maxVelocity = \"10.0\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " emap = true;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " sticky = false;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " dynamicType = \"0\"\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>;" " lightOnlyStatic = false;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " lightType = \"NoLight\";\n" " lightColor = \"1.0 1.0 1.0 1.0\";\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " lightTime = 1000;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " lightRadius = 10.0;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" " simpleServerCollision = true;" " // Dynamic properties used by the <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">scripts\n\n</a>" " pickupName = \"a small health kit\";\n" " repairAmount = 50;\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "};\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n</a>" "@<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">endtsexample\n</a>" "@ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">gameObjects\n</a>" )
DefineEngineMethod(Item , getLastStickyNormal , const char * , () )
DefineEngineMethod(Item , getLastStickyPos , const char * , () )
DefineEngineMethod(Item , isAtRest , bool , () , "@brief Is the object at rest (ie, no longer moving)?\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n\n</a>" "@return True <a href="/coding/file/tsmeshintrinsics_8cpp/#tsmeshintrinsics_8cpp_1a4e4fa7e3399708e0777b5308db01278c">if</a> the object is at rest)
DefineEngineMethod(Item , isRotating , bool , () , "@brief Is the object still rotating?\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n\n</a>" "@return True <a href="/coding/file/tsmeshintrinsics_8cpp/#tsmeshintrinsics_8cpp_1a4e4fa7e3399708e0777b5308db01278c">if</a> the object is still rotating)
DefineEngineMethod(Item , isStatic , bool , () , "@brief Is the object static (ie, non-movable)?\<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">n\n</a>" "@return True <a href="/coding/file/tsmeshintrinsics_8cpp/#tsmeshintrinsics_8cpp_1a4e4fa7e3399708e0777b5308db01278c">if</a> the object is static)
DefineEngineMethod(Item , setCollisionTimeout , bool , (S32 ignoreColObj) , "@brief Temporarily disable collisions against <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> specific <a href="/coding/class/classshapebase/">ShapeBase</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">object.\n\n</a>" "This is useful <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> prevent <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> player from immediately picking up an <a href="/coding/class/classitem/">Item</a> they have " "just thrown. Only one object may be on the timeout list at <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> time. The timeout is " "defined as 15 <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">ticks.\n\n</a>" "@param objectID <a href="/coding/class/classshapebase/">ShapeBase</a> object ID <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> disable collisions <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">against.\n</a>" "@return Returns true <a href="/coding/file/tsmeshintrinsics_8cpp/#tsmeshintrinsics_8cpp_1a4e4fa7e3399708e0777b5308db01278c">if</a> the <a href="/coding/class/classshapebase/">ShapeBase</a> object requested could be found)
IMPLEMENT_CALLBACK(Item , onEnterLiquid , void , (const char *objID, F32 waterCoverage, const char *liquidType) , (objID, waterCoverage, liquidType) , "Informs an <a href="/coding/class/classitem/">Item</a> object that it has entered liquid, along with information about the liquid <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">type.\n</a>" " @param objID <a href="/coding/file/gizmo_8h/#gizmo_8h_1a10fcd3bee2ea25191e31795e36bdeba1a5df911aaca43421a25e32c3002befbc4">Object</a> ID <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> this <a href="/coding/class/classitem/">Item</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">object.\n</a>" " @param waterCoverage How much coverage of water this <a href="/coding/class/classitem/">Item</a> object <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">has.\n</a>" " @param liquidType The type of liquid that this <a href="/coding/class/classitem/">Item</a> object has <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">entered.\n</a>" " @note Server side <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">only.\n</a>" " @see Item, <a href="/coding/class/structitemdata/">ItemData</a> , <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">WaterObject\n</a>" )
IMPLEMENT_CALLBACK(Item , onLeaveLiquid , void , (const char *objID, const char *liquidType) , (objID, liquidType) , "Informs an <a href="/coding/class/classitem/">Item</a> object that it has left <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> liquid, along with information about the liquid <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">type.\n</a>" " @param objID <a href="/coding/file/gizmo_8h/#gizmo_8h_1a10fcd3bee2ea25191e31795e36bdeba1a5df911aaca43421a25e32c3002befbc4">Object</a> ID <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> this <a href="/coding/class/classitem/">Item</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">object.\n</a>" " @param liquidType The type of liquid that this <a href="/coding/class/classitem/">Item</a> object has <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">left.\n</a>" " @note Server side <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">only.\n</a>" " @see Item, <a href="/coding/class/structitemdata/">ItemData</a> , <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">WaterObject\n</a>" )
IMPLEMENT_CALLBACK(Item , onStickyCollision , void , (const char *objID) , (objID) , "@brief Informs the <a href="/coding/class/classitem/">Item</a> object that it is now sticking <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> another <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">object.\n\n</a>" "This callback is only called <a href="/coding/file/tsmeshintrinsics_8cpp/#tsmeshintrinsics_8cpp_1a4e4fa7e3399708e0777b5308db01278c">if</a> the <a href="/coding/class/structitemdata/#structitemdata_1a351749d1f39ac3c5342334188c44c8c3">ItemData::sticky</a> property <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> this <a href="/coding/class/classitem/">Item</a> is <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">true.\n</a>" "@param objID <a href="/coding/file/gizmo_8h/#gizmo_8h_1a10fcd3bee2ea25191e31795e36bdeba1a5df911aaca43421a25e32c3002befbc4">Object</a> ID this <a href="/coding/class/classitem/">Item</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">object.\n</a>" "@note Server side <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">only.\n</a>" "@see Item, <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">ItemData\n</a>" )
IMPLEMENT_CO_DATABLOCK_V1(ItemData )
IMPLEMENT_CO_NETOBJECT_V1(Item )
ImplementEnumType(ItemLightType , "@brief The type of light the <a href="/coding/class/classitem/">Item</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">has\n\n</a>" "@ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">gameObjects\n\n</a>" )
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/item.h"
26
27#include "core/stream/bitStream.h"
28#include "math/mMath.h"
29#include "console/console.h"
30#include "console/consoleTypes.h"
31#include "sim/netConnection.h"
32#include "collision/boxConvex.h"
33#include "collision/earlyOutPolyList.h"
34#include "collision/extrudedPolyList.h"
35#include "math/mPolyhedron.h"
36#include "math/mathIO.h"
37#include "lighting/lightInfo.h"
38#include "lighting/lightManager.h"
39#include "T3D/physics/physicsPlugin.h"
40#include "T3D/physics/physicsBody.h"
41#include "T3D/physics/physicsCollision.h"
42#include "ts/tsShapeInstance.h"
43#include "console/engineAPI.h"
44
45
46const F32 sRotationSpeed = 6.0f; // Secs/Rotation
47const F32 sAtRestVelocity = 0.15f; // Min speed after collision
48const S32 sCollisionTimeout = 15; // Timout value in ticks
49
50// Client prediction
51static F32 sMinWarpTicks = 0.5 ; // Fraction of tick at which instant warp occures
52static S32 sMaxWarpTicks = 3; // Max warp duration in ticks
53
54const U32 sClientCollisionMask = (TerrainObjectType |
55 StaticShapeObjectType |
56 VehicleObjectType |
57 PlayerObjectType);
58
59const U32 sServerCollisionMask = (sClientCollisionMask);
60
61const S32 Item::csmAtRestTimer = 64;
62
63//----------------------------------------------------------------------------
64
65IMPLEMENT_CO_DATABLOCK_V1(ItemData);
66
67ConsoleDocClass( ItemData,
68 "@brief Stores properties for an individual Item type.\n\n"
69
70 "Items represent an object in the world, usually one that the player will interact with. "
71 "One example is a health kit on the group that is automatically picked up when the player "
72 "comes into contact with it.\n\n"
73
74 "ItemData provides the common properties for a set of Items. These properties include a "
75 "DTS or DAE model used to render the Item in the world, its physical properties for when the "
76 "Item interacts with the world (such as being tossed by the player), and any lights that emit "
77 "from the Item.\n\n"
78
79 "@tsexample\n"
80 "datablock ItemData(HealthKitSmall)\n"
81 "{\n"
82 " category =\"Health\";\n"
83 " className = \"HealthPatch\";\n"
84 " shapeFile = \"art/shapes/items/kit/healthkit.dts\";\n"
85 " gravityMod = \"1.0\";\n"
86 " mass = 2;\n"
87 " friction = 1;\n"
88 " elasticity = 0.3;\n"
89 " density = 2;\n"
90 " drag = 0.5;\n"
91 " maxVelocity = \"10.0\";\n"
92 " emap = true;\n"
93 " sticky = false;\n"
94 " dynamicType = \"0\"\n;"
95 " lightOnlyStatic = false;\n"
96 " lightType = \"NoLight\";\n"
97 " lightColor = \"1.0 1.0 1.0 1.0\";\n"
98 " lightTime = 1000;\n"
99 " lightRadius = 10.0;\n"
100 " simpleServerCollision = true;"
101 " // Dynamic properties used by the scripts\n\n"
102 " pickupName = \"a small health kit\";\n"
103 " repairAmount = 50;\n"
104 "};\n"
105 "@endtsexample\n"
106
107 "@ingroup gameObjects\n"
108);
109
110
111ItemData::ItemData()
112{
113 shadowEnable = true;
114
115
116 friction = 0;
117 elasticity = 0;
118
119 sticky = false;
120 gravityMod = 1.0;
121 maxVelocity = 25.0f;
122
123 density = 2;
124 drag = 0.5;
125
126 lightOnlyStatic = false;
127 lightType = Item::NoLight;
128 lightColor.set(1.f,1.f,1.f,1.f);
129 lightTime = 1000;
130 lightRadius = 10.f;
131
132 simpleServerCollision = true;
133}
134
135ImplementEnumType( ItemLightType,
136 "@brief The type of light the Item has\n\n"
137 "@ingroup gameObjects\n\n")
138 { Item::NoLight, "NoLight", "The item has no light attached.\n" },
139 { Item::ConstantLight, "ConstantLight", "The item has a constantly emitting light attached.\n" },
140 { Item::PulsingLight, "PulsingLight", "The item has a pulsing light attached.\n" }
141EndImplementEnumType;
142
143void ItemData::initPersistFields()
144{
145 addField("friction", TypeF32, Offset(friction, ItemData), "A floating-point value specifying how much velocity is lost to impact and sliding friction.");
146 addField("elasticity", TypeF32, Offset(elasticity, ItemData), "A floating-point value specifying how 'bouncy' this ItemData is.");
147 addField("sticky", TypeBool, Offset(sticky, ItemData),
148 "@brief If true, ItemData will 'stick' to any surface it collides with.\n\n"
149 "When an item does stick to a surface, the Item::onStickyCollision() callback is called. The Item has methods to retrieve "
150 "the world position and normal the Item is stuck to.\n"
151 "@note Valid objects to stick to must be of StaticShapeObjectType.\n");
152 addField("gravityMod", TypeF32, Offset(gravityMod, ItemData), "Floating point value to multiply the existing gravity with, just for this ItemData.");
153 addField("maxVelocity", TypeF32, Offset(maxVelocity, ItemData), "Maximum velocity that this ItemData is able to move.");
154
155 addField("lightType", TYPEID< Item::LightType >(), Offset(lightType, ItemData), "Type of light to apply to this ItemData. Options are NoLight, ConstantLight, PulsingLight. Default is NoLight." );
156 addField("lightColor", TypeColorF, Offset(lightColor, ItemData),
157 "@brief Color value to make this light. Example: \"1.0,1.0,1.0\"\n\n"
158 "@see lightType\n");
159 addField("lightTime", TypeS32, Offset(lightTime, ItemData),
160 "@brief Time value for the light of this ItemData, used to control the pulse speed of the PulsingLight LightType.\n\n"
161 "@see lightType\n");
162 addField("lightRadius", TypeF32, Offset(lightRadius, ItemData),
163 "@brief Distance from the center point of this ItemData for the light to affect\n\n"
164 "@see lightType\n");
165 addField("lightOnlyStatic", TypeBool, Offset(lightOnlyStatic, ItemData),
166 "@brief If true, this ItemData will only cast a light if the Item for this ItemData has a static value of true.\n\n"
167 "@see lightType\n");
168
169 addField("simpleServerCollision", TypeBool, Offset(simpleServerCollision, ItemData),
170 "@brief Determines if only simple server-side collision will be used (for pick ups).\n\n"
171 "If set to true then only simple, server-side collision detection will be used. This is often the case "
172 "if the item is used for a pick up object, such as ammo. If set to false then a full collision volume "
173 "will be used as defined by the shape. The default is true.\n"
174 "@note Only applies when using a physics library.\n"
175 "@see TurretShape and ProximityMine for examples that should set this to false to allow them to be "
176 "shot by projectiles.\n");
177
178 Parent::initPersistFields();
179}
180
181void ItemData::packData(BitStream* stream)
182{
183 Parent::packData(stream);
184 stream->writeFloat(friction, 10);
185 stream->writeFloat(elasticity, 10);
186 stream->writeFlag(sticky);
187 if(stream->writeFlag(gravityMod != 1.0))
188 stream->writeFloat(gravityMod, 10);
189 if(stream->writeFlag(maxVelocity != -1))
190 stream->write(maxVelocity);
191
192 if(stream->writeFlag(lightType != Item::NoLight))
193 {
194 AssertFatal(Item::NumLightTypes < (1 << 2), "ItemData: light type needs more bits");
195 stream->writeInt(lightType, 2);
196 stream->writeFloat(lightColor.red, 7);
197 stream->writeFloat(lightColor.green, 7);
198 stream->writeFloat(lightColor.blue, 7);
199 stream->writeFloat(lightColor.alpha, 7);
200 stream->write(lightTime);
201 stream->write(lightRadius);
202 stream->writeFlag(lightOnlyStatic);
203 }
204
205 stream->writeFlag(simpleServerCollision);
206}
207
208void ItemData::unpackData(BitStream* stream)
209{
210 Parent::unpackData(stream);
211 friction = stream->readFloat(10);
212 elasticity = stream->readFloat(10);
213 sticky = stream->readFlag();
214 if(stream->readFlag())
215 gravityMod = stream->readFloat(10);
216 else
217 gravityMod = 1.0;
218
219 if(stream->readFlag())
220 stream->read(&maxVelocity);
221 else
222 maxVelocity = -1;
223
224 if(stream->readFlag())
225 {
226 lightType = stream->readInt(2);
227 lightColor.red = stream->readFloat(7);
228 lightColor.green = stream->readFloat(7);
229 lightColor.blue = stream->readFloat(7);
230 lightColor.alpha = stream->readFloat(7);
231 stream->read(&lightTime);
232 stream->read(&lightRadius);
233 lightOnlyStatic = stream->readFlag();
234 }
235 else
236 lightType = Item::NoLight;
237
238 simpleServerCollision = stream->readFlag();
239}
240
241
242//----------------------------------------------------------------------------
243
244IMPLEMENT_CO_NETOBJECT_V1(Item);
245
246ConsoleDocClass( Item,
247 "@brief Base Item class. Uses the ItemData datablock for common properties.\n\n"
248
249 "Items represent an object in the world, usually one that the player will interact with. "
250 "One example is a health kit on the group that is automatically picked up when the player "
251 "comes into contact with it.\n\n"
252
253 "@tsexample\n"
254 "// This is the \"health patch\" dropped by a dying player.\n"
255 "datablock ItemData(HealthKitPatch)\n"
256 "{\n"
257 " // Mission editor category, this datablock will show up in the\n"
258 " // specified category under the \"shapes\" root category.\n"
259 " category = \"Health\";\n\n"
260 " className = \"HealthPatch\";\n\n"
261 " // Basic Item properties\n"
262 " shapeFile = \"art/shapes/items/patch/healthpatch.dts\";\n"
263 " mass = 2;\n"
264 " friction = 1;\n"
265 " elasticity = 0.3;\n"
266 " emap = true;\n\n"
267 " // Dynamic properties used by the scripts\n"
268 " pickupName = \"a health patch\";\n"
269 " repairAmount = 50;\n"
270 "};\n\n"
271
272 "%obj = new Item()\n"
273 "{\n"
274 " dataBlock = HealthKitSmall;\n"
275 " parentGroup = EWCreatorWindow.objectGroup;\n"
276 " static = true;\n"
277 " rotate = true;\n"
278 "};\n"
279 "@endtsexample\n\n"
280
281 "@see ItemData\n"
282
283 "@ingroup gameObjects\n"
284);
285
286IMPLEMENT_CALLBACK( Item, onStickyCollision, void, ( const char* objID ),( objID ),
287 "@brief Informs the Item object that it is now sticking to another object.\n\n"
288 "This callback is only called if the ItemData::sticky property for this Item is true.\n"
289 "@param objID Object ID this Item object.\n"
290 "@note Server side only.\n"
291 "@see Item, ItemData\n"
292);
293
294IMPLEMENT_CALLBACK( Item, onEnterLiquid, void, ( const char* objID, F32 waterCoverage, const char* liquidType ),( objID, waterCoverage, liquidType ),
295 "Informs an Item object that it has entered liquid, along with information about the liquid type.\n"
296 "@param objID Object ID for this Item object.\n"
297 "@param waterCoverage How much coverage of water this Item object has.\n"
298 "@param liquidType The type of liquid that this Item object has entered.\n"
299 "@note Server side only.\n"
300 "@see Item, ItemData, WaterObject\n"
301);
302
303IMPLEMENT_CALLBACK( Item, onLeaveLiquid, void, ( const char* objID, const char* liquidType ),( objID, liquidType ),
304 "Informs an Item object that it has left a liquid, along with information about the liquid type.\n"
305 "@param objID Object ID for this Item object.\n"
306 "@param liquidType The type of liquid that this Item object has left.\n"
307 "@note Server side only.\n"
308 "@see Item, ItemData, WaterObject\n"
309);
310
311
312Item::Item()
313{
314 mTypeMask |= ItemObjectType | DynamicShapeObjectType;
315 mDataBlock = 0;
316 mStatic = false;
317 mRotate = false;
318 mVelocity = VectorF(0,0,0);
319 mAtRest = true;
320 mAtRestCounter = 0;
321 mInLiquid = false;
322 mDelta.warpTicks = 0;
323 mDelta.dt = 1;
324 mCollisionObject = 0;
325 mCollisionTimeout = 0;
326 mPhysicsRep = NULL;
327
328 mConvex.init(this);
329 mWorkingQueryBox.minExtents.set(-1e9, -1e9, -1e9);
330 mWorkingQueryBox.maxExtents.set(-1e9, -1e9, -1e9);
331
332 mLight = NULL;
333
334 mSubclassItemHandlesScene = false;
335}
336
337Item::~Item()
338{
339 SAFE_DELETE(mLight);
340}
341
342
343//----------------------------------------------------------------------------
344
345bool Item::onAdd()
346{
347 if (!Parent::onAdd() || !mDataBlock)
348 return false;
349
350 if (mStatic)
351 mAtRest = true;
352 mObjToWorld.getColumn(3,&mDelta.pos);
353
354 // Setup the box for our convex object...
355 mObjBox.getCenter(&mConvex.mCenter);
356 mConvex.mSize.x = mObjBox.len_x() / 2.0;
357 mConvex.mSize.y = mObjBox.len_y() / 2.0;
358 mConvex.mSize.z = mObjBox.len_z() / 2.0;
359 mWorkingQueryBox.minExtents.set(-1e9, -1e9, -1e9);
360 mWorkingQueryBox.maxExtents.set(-1e9, -1e9, -1e9);
361
362 if( !isHidden() && !mSubclassItemHandlesScene )
363 addToScene();
364
365 if (isServerObject())
366 {
367 if (!mSubclassItemHandlesScene)
368 scriptOnAdd();
369 }
370 else if (mDataBlock->lightType != NoLight)
371 {
372 mDropTime = Sim::getCurrentTime();
373 }
374
375 _updatePhysics();
376
377 return true;
378}
379
380void Item::_updatePhysics()
381{
382 SAFE_DELETE( mPhysicsRep );
383
384 if ( !PHYSICSMGR )
385 return;
386
387 if (mDataBlock->simpleServerCollision)
388 {
389 // We only need the trigger on the server.
390 if ( isServerObject() )
391 {
392 PhysicsCollision *colShape = PHYSICSMGR->createCollision();
393 colShape->addBox( mObjBox.getExtents() * 0.5f, MatrixF::Identity );
394
395 PhysicsWorld *world = PHYSICSMGR->getWorld( isServerObject() ? "server" : "client" );
396 mPhysicsRep = PHYSICSMGR->createBody();
397 mPhysicsRep->init( colShape, 0, PhysicsBody::BF_TRIGGER | PhysicsBody::BF_KINEMATIC, this, world );
398 mPhysicsRep->setTransform( getTransform() );
399 }
400 }
401 else
402 {
403 if ( !mShapeInstance )
404 return;
405
406 PhysicsCollision* colShape = mShapeInstance->getShape()->buildColShape( false, getScale() );
407
408 if ( colShape )
409 {
410 PhysicsWorld *world = PHYSICSMGR->getWorld( isServerObject() ? "server" : "client" );
411 mPhysicsRep = PHYSICSMGR->createBody();
412 mPhysicsRep->init( colShape, 0, PhysicsBody::BF_KINEMATIC, this, world );
413 mPhysicsRep->setTransform( getTransform() );
414 }
415 }
416}
417
418bool Item::onNewDataBlock( GameBaseData *dptr, bool reload )
419{
420 mDataBlock = dynamic_cast<ItemData*>(dptr);
421 if (!mDataBlock || !Parent::onNewDataBlock(dptr,reload))
422 return false;
423
424 if (!mSubclassItemHandlesScene)
425 scriptOnNewDataBlock();
426
427 if ( isProperlyAdded() )
428 _updatePhysics();
429
430 return true;
431}
432
433void Item::onRemove()
434{
435 mWorkingQueryBox.minExtents.set(-1e9, -1e9, -1e9);
436 mWorkingQueryBox.maxExtents.set(-1e9, -1e9, -1e9);
437
438 SAFE_DELETE( mPhysicsRep );
439
440 if (!mSubclassItemHandlesScene)
441 {
442 scriptOnRemove();
443 removeFromScene();
444 }
445
446 Parent::onRemove();
447}
448
449void Item::onDeleteNotify( SimObject *obj )
450{
451 if ( obj == mCollisionObject )
452 {
453 mCollisionObject = NULL;
454 mCollisionTimeout = 0;
455 }
456
457 Parent::onDeleteNotify( obj );
458}
459
460// Lighting: -----------------------------------------------------------------
461
462void Item::registerLights(LightManager * lightManager, bool lightingScene)
463{
464 if(lightingScene)
465 return;
466
467 if(mDataBlock->lightOnlyStatic && !mStatic)
468 return;
469
470 F32 intensity;
471 switch(mDataBlock->lightType)
472 {
473 case ConstantLight:
474 intensity = mFadeVal;
475 break;
476
477 case PulsingLight:
478 {
479 S32 delta = Sim::getCurrentTime() - mDropTime;
480 intensity = 0.5f + 0.5f * mSin(M_PI_F * F32(delta) / F32(mDataBlock->lightTime));
481 intensity = 0.15f + intensity * 0.85f;
482 intensity *= mFadeVal; // fade out light on flags
483 break;
484 }
485
486 default:
487 return;
488 }
489
490 // Create a light if needed
491 if (!mLight)
492 {
493 mLight = lightManager->createLightInfo();
494 }
495 mLight->setColor( mDataBlock->lightColor * intensity );
496 mLight->setType( LightInfo::Point );
497 mLight->setRange( mDataBlock->lightRadius );
498 mLight->setPosition( getBoxCenter() );
499
500 lightManager->registerGlobalLight( mLight, this );
501}
502
503
504//----------------------------------------------------------------------------
505
506Point3F Item::getVelocity() const
507{
508 return mVelocity;
509}
510
511void Item::setVelocity(const VectorF& vel)
512{
513 mVelocity = vel;
514
515 // Clamp against the maximum velocity.
516 if ( mDataBlock->maxVelocity > 0 )
517 {
518 F32 len = mVelocity.magnitudeSafe();
519 if ( len > mDataBlock->maxVelocity )
520 {
521 Point3F excess = mVelocity * ( 1.0f - (mDataBlock->maxVelocity / len ) );
522 mVelocity -= excess;
523 }
524 }
525
526 setMaskBits(PositionMask);
527 mAtRest = false;
528 mAtRestCounter = 0;
529}
530
531void Item::applyImpulse(const Point3F&,const VectorF& vec)
532{
533 // Items ignore angular velocity
534 VectorF vel;
535 vel.x = vec.x / mDataBlock->mass;
536 vel.y = vec.y / mDataBlock->mass;
537 vel.z = vec.z / mDataBlock->mass;
538 setVelocity(vel);
539}
540
541void Item::setCollisionTimeout(ShapeBase* obj)
542{
543 if (mCollisionObject)
544 clearNotify(mCollisionObject);
545 deleteNotify(obj);
546 mCollisionObject = obj;
547 mCollisionTimeout = sCollisionTimeout;
548 setMaskBits(ThrowSrcMask);
549}
550
551
552//----------------------------------------------------------------------------
553
554void Item::processTick(const Move* move)
555{
556 Parent::processTick(move);
557
558 if ( isMounted() )
559 return;
560
561 //
562 if (mCollisionObject && !--mCollisionTimeout)
563 mCollisionObject = 0;
564
565 // Warp to catch up to server
566 if (mDelta.warpTicks > 0)
567 {
568 mDelta.warpTicks--;
569
570 // Set new pos.
571 MatrixF mat = mObjToWorld;
572 mat.getColumn(3,&mDelta.pos);
573 mDelta.pos += mDelta.warpOffset;
574 mat.setColumn(3, mDelta.pos);
575 Parent::setTransform(mat);
576
577 // Backstepping
578 mDelta.posVec.x = -mDelta.warpOffset.x;
579 mDelta.posVec.y = -mDelta.warpOffset.y;
580 mDelta.posVec.z = -mDelta.warpOffset.z;
581 }
582 else
583 {
584 if (isServerObject() && mAtRest && (mStatic == false && mDataBlock->sticky == false))
585 {
586 if (++mAtRestCounter > csmAtRestTimer)
587 {
588 mAtRest = false;
589 mAtRestCounter = 0;
590 setMaskBits(PositionMask);
591 }
592 }
593
594 if (!mStatic && !mAtRest && isHidden() == false)
595 {
596 updateVelocity(TickSec);
597 updateWorkingCollisionSet(isGhost() ? sClientCollisionMask : sServerCollisionMask, TickSec);
598 updatePos(isGhost() ? sClientCollisionMask : sServerCollisionMask, TickSec);
599 }
600 else
601 {
602 // Need to clear out last updatePos or warp interpolation
603 mDelta.posVec.set(0,0,0);
604 }
605 }
606}
607
608void Item::interpolateTick(F32 dt)
609{
610 Parent::interpolateTick(dt);
611 if ( isMounted() )
612 return;
613
614 // Client side interpolation
615 Point3F pos = mDelta.pos + mDelta.posVec * dt;
616 MatrixF mat = mRenderObjToWorld;
617 mat.setColumn(3,pos);
618 setRenderTransform(mat);
619 mDelta.dt = dt;
620// PATHSHAPE
621 updateRenderChangesByParent();
622// PATHSHAPE END
623}
624
625
626//----------------------------------------------------------------------------
627
628void Item::setTransform(const MatrixF& mat)
629{
630 Point3F pos;
631 mat.getColumn(3,&pos);
632 MatrixF tmat;
633 if (!mRotate) {
634 // Forces all rotation to be around the z axis
635 VectorF vec;
636 mat.getColumn(1,&vec);
637 tmat.set(EulerF(0,0,-mAtan2(-vec.x,vec.y)));
638 }
639 else
640 tmat.identity();
641 tmat.setColumn(3,pos);
642 Parent::setTransform(tmat);
643 if (!mStatic)
644 {
645 mAtRest = false;
646 mAtRestCounter = 0;
647 }
648
649 if ( mPhysicsRep )
650 mPhysicsRep->setTransform( getTransform() );
651
652 setMaskBits(RotationMask | PositionMask | NoWarpMask);
653}
654
655
656//----------------------------------------------------------------------------
657void Item::updateWorkingCollisionSet(const U32 mask, const F32 dt)
658{
659 // It is assumed that we will never accelerate more than 10 m/s for gravity...
660 //
661 Point3F scaledVelocity = mVelocity * dt;
662 F32 len = scaledVelocity.len();
663 F32 newLen = len + (10 * dt);
664
665 // Check to see if it is actually necessary to construct the new working list,
666 // or if we can use the cached version from the last query. We use the x
667 // component of the min member of the mWorkingQueryBox, which is lame, but
668 // it works ok.
669 bool updateSet = false;
670
671 Box3F convexBox = mConvex.getBoundingBox(getTransform(), getScale());
672 F32 l = (newLen * 1.1) + 0.1; // from Convex::updateWorkingList
673 convexBox.minExtents -= Point3F(l, l, l);
674 convexBox.maxExtents += Point3F(l, l, l);
675
676 // Check containment
677 {
678 if (mWorkingQueryBox.minExtents.x != -1e9)
679 {
680 if (mWorkingQueryBox.isContained(convexBox) == false)
681 {
682 // Needed region is outside the cached region. Update it.
683 updateSet = true;
684 }
685 else
686 {
687 // We can leave it alone, we're still inside the cached region
688 }
689 }
690 else
691 {
692 // Must update
693 updateSet = true;
694 }
695 }
696
697 // Actually perform the query, if necessary
698 if (updateSet == true)
699 {
700 mWorkingQueryBox = convexBox;
701 mWorkingQueryBox.minExtents -= Point3F(2 * l, 2 * l, 2 * l);
702 mWorkingQueryBox.maxExtents += Point3F(2 * l, 2 * l, 2 * l);
703
704 disableCollision();
705 if (mCollisionObject)
706 mCollisionObject->disableCollision();
707
708 mConvex.updateWorkingList(mWorkingQueryBox, mask);
709
710 if (mCollisionObject)
711 mCollisionObject->enableCollision();
712 enableCollision();
713 }
714}
715
716void Item::updateVelocity(const F32 dt)
717{
718 // Container buoyancy & drag
719 // Acceleration due to gravity
720 mVelocity.z += (mNetGravity * mDataBlock->gravityMod) * dt;
721 mVelocity -= mVelocity * mDrag * dt;
722
723 // Add in physical zone force
724 mVelocity += mAppliedForce;
725
726 F32 len;
727 if (mDataBlock->maxVelocity > 0 && (len = mVelocity.len()) > (mDataBlock->maxVelocity * 1.05)) {
728 Point3F excess = mVelocity * (1.0 - (mDataBlock->maxVelocity / len ));
729 excess *= 0.1f;
730 mVelocity -= excess;
731 }
732}
733
734
735void Item::updatePos(const U32 /*mask*/, const F32 dt)
736{
737 // Try and move
738 Point3F pos;
739 mObjToWorld.getColumn(3,&pos);
740 mDelta.posVec = pos;
741
742 bool contact = false;
743 bool nonStatic = false;
744 bool stickyNotify = false;
745 CollisionList collisionList;
746 F32 time = dt;
747
748 static Polyhedron sBoxPolyhedron;
749 static ExtrudedPolyList sExtrudedPolyList;
750 static EarlyOutPolyList sEarlyOutPolyList;
751 MatrixF collisionMatrix(true);
752 Point3F end = pos + mVelocity * time;
753 U32 mask = isServerObject() ? sServerCollisionMask : sClientCollisionMask;
754
755 // Part of our speed problem here is that we don't track contact surfaces, like we do
756 // with the player. In order to handle the most common and performance impacting
757 // instance of this problem, we'll use a ray cast to detect any contact surfaces below
758 // us. This won't be perfect, but it only needs to catch a few of these to make a
759 // big difference. We'll cast from the top center of the bounding box at the tick's
760 // beginning to the bottom center of the box at the end.
761 Point3F startCast((mObjBox.minExtents.x + mObjBox.maxExtents.x) * 0.5,
762 (mObjBox.minExtents.y + mObjBox.maxExtents.y) * 0.5,
763 mObjBox.maxExtents.z);
764 Point3F endCast((mObjBox.minExtents.x + mObjBox.maxExtents.x) * 0.5,
765 (mObjBox.minExtents.y + mObjBox.maxExtents.y) * 0.5,
766 mObjBox.minExtents.z);
767 collisionMatrix.setColumn(3, pos);
768 collisionMatrix.mulP(startCast);
769 collisionMatrix.setColumn(3, end);
770 collisionMatrix.mulP(endCast);
771 RayInfo rinfo;
772 bool doToughCollision = true;
773 disableCollision();
774 if (mCollisionObject)
775 mCollisionObject->disableCollision();
776 if (getContainer()->castRay(startCast, endCast, mask, &rinfo))
777 {
778 F32 bd = -mDot(mVelocity, rinfo.normal);
779
780 if (bd >= 0.0)
781 {
782 // Contact!
783 if (mDataBlock->sticky && rinfo.object->getTypeMask() & (STATIC_COLLISION_TYPEMASK)) {
784 mVelocity.set(0, 0, 0);
785 mAtRest = true;
786 mAtRestCounter = 0;
787 stickyNotify = true;
788 mStickyCollisionPos = rinfo.point;
789 mStickyCollisionNormal = rinfo.normal;
790 doToughCollision = false;;
791 } else {
792 // Subtract out velocity into surface and friction
793 VectorF fv = mVelocity + rinfo.normal * bd;
794 F32 fvl = fv.len();
795 if (fvl) {
796 F32 ff = bd * mDataBlock->friction;
797 if (ff < fvl) {
798 fv *= ff / fvl;
799 fvl = ff;
800 }
801 }
802 bd *= 1 + mDataBlock->elasticity;
803 VectorF dv = rinfo.normal * (bd + 0.002);
804 mVelocity += dv;
805 mVelocity -= fv;
806
807 // Keep track of what we hit
808 contact = true;
809 U32 typeMask = rinfo.object->getTypeMask();
810 if (!(typeMask & StaticObjectType))
811 nonStatic = true;
812 if (isServerObject() && (typeMask & ShapeBaseObjectType)) {
813 ShapeBase* col = static_cast<ShapeBase*>(rinfo.object);
814 queueCollision(col,mVelocity - col->getVelocity());
815 }
816 }
817 }
818 }
819 enableCollision();
820 if (mCollisionObject)
821 mCollisionObject->enableCollision();
822
823 if (doToughCollision)
824 {
825 U32 count;
826 for (count = 0; count < 3; count++)
827 {
828 // Build list from convex states here...
829 end = pos + mVelocity * time;
830
831
832 collisionMatrix.setColumn(3, end);
833 Box3F wBox = getObjBox();
834 collisionMatrix.mul(wBox);
835 Box3F testBox = wBox;
836 Point3F oldMin = testBox.minExtents;
837 Point3F oldMax = testBox.maxExtents;
838 testBox.minExtents.setMin(oldMin + (mVelocity * time));
839 testBox.maxExtents.setMin(oldMax + (mVelocity * time));
840
841 sEarlyOutPolyList.clear();
842 sEarlyOutPolyList.mNormal.set(0,0,0);
843 sEarlyOutPolyList.mPlaneList.setSize(6);
844 sEarlyOutPolyList.mPlaneList[0].set(wBox.minExtents,VectorF(-1,0,0));
845 sEarlyOutPolyList.mPlaneList[1].set(wBox.maxExtents,VectorF(0,1,0));
846 sEarlyOutPolyList.mPlaneList[2].set(wBox.maxExtents,VectorF(1,0,0));
847 sEarlyOutPolyList.mPlaneList[3].set(wBox.minExtents,VectorF(0,-1,0));
848 sEarlyOutPolyList.mPlaneList[4].set(wBox.minExtents,VectorF(0,0,-1));
849 sEarlyOutPolyList.mPlaneList[5].set(wBox.maxExtents,VectorF(0,0,1));
850
851 CollisionWorkingList& eorList = mConvex.getWorkingList();
852 CollisionWorkingList* eopList = eorList.wLink.mNext;
853 while (eopList != &eorList) {
854 if ((eopList->mConvex->getObject()->getTypeMask() & mask) != 0)
855 {
856 Box3F convexBox = eopList->mConvex->getBoundingBox();
857 if (testBox.isOverlapped(convexBox))
858 {
859 eopList->mConvex->getPolyList(&sEarlyOutPolyList);
860 if (sEarlyOutPolyList.isEmpty() == false)
861 break;
862 }
863 }
864 eopList = eopList->wLink.mNext;
865 }
866 if (sEarlyOutPolyList.isEmpty())
867 {
868 pos = end;
869 break;
870 }
871
872 collisionMatrix.setColumn(3, pos);
873 sBoxPolyhedron.buildBox(collisionMatrix, mObjBox, true);
874
875 // Build extruded polyList...
876 VectorF vector = end - pos;
877 sExtrudedPolyList.extrude(sBoxPolyhedron, vector);
878 sExtrudedPolyList.setVelocity(mVelocity);
879 sExtrudedPolyList.setCollisionList(&collisionList);
880
881 CollisionWorkingList& rList = mConvex.getWorkingList();
882 CollisionWorkingList* pList = rList.wLink.mNext;
883 while (pList != &rList) {
884 if ((pList->mConvex->getObject()->getTypeMask() & mask) != 0)
885 {
886 Box3F convexBox = pList->mConvex->getBoundingBox();
887 if (testBox.isOverlapped(convexBox))
888 {
889 pList->mConvex->getPolyList(&sExtrudedPolyList);
890 }
891 }
892 pList = pList->wLink.mNext;
893 }
894
895 if (collisionList.getTime() < 1.0)
896 {
897 // Set to collision point
898 F32 cdt = time * collisionList.getTime();
899 pos += mVelocity * cdt;
900 time -= cdt;
901
902 // Pick the most resistant surface
903 F32 bd = 0;
904 const Collision* collision = 0;
905 for (S32 c = 0; c < collisionList.getCount(); c++) {
906 const Collision &cp = collisionList[c];
907 F32 dot = -mDot(mVelocity,cp.normal);
908 if (dot > bd) {
909 bd = dot;
910 collision = &cp;
911 }
912 }
913
914 if (collision && mDataBlock->sticky && collision->object->getTypeMask() & (STATIC_COLLISION_TYPEMASK)) {
915 mVelocity.set(0, 0, 0);
916 mAtRest = true;
917 mAtRestCounter = 0;
918 stickyNotify = true;
919 mStickyCollisionPos = collision->point;
920 mStickyCollisionNormal = collision->normal;
921 break;
922 } else {
923 // Subtract out velocity into surface and friction
924 if (collision) {
925 VectorF fv = mVelocity + collision->normal * bd;
926 F32 fvl = fv.len();
927 if (fvl) {
928 F32 ff = bd * mDataBlock->friction;
929 if (ff < fvl) {
930 fv *= ff / fvl;
931 fvl = ff;
932 }
933 }
934 bd *= 1 + mDataBlock->elasticity;
935 VectorF dv = collision->normal * (bd + 0.002);
936 mVelocity += dv;
937 mVelocity -= fv;
938
939 // Keep track of what we hit
940 contact = true;
941 U32 typeMask = collision->object->getTypeMask();
942 if (!(typeMask & StaticObjectType))
943 nonStatic = true;
944 if (isServerObject() && (typeMask & ShapeBaseObjectType)) {
945 ShapeBase* col = static_cast<ShapeBase*>(collision->object);
946 queueCollision(col,mVelocity - col->getVelocity());
947 }
948 }
949 }
950 }
951 else
952 {
953 pos = end;
954 break;
955 }
956 }
957 if (count == 3)
958 {
959 // Couldn't move...
960 mVelocity.set(0, 0, 0);
961 }
962 }
963
964 // If on the client, calculate delta for backstepping
965 if (isGhost()) {
966 mDelta.pos = pos;
967 mDelta.posVec -= pos;
968 mDelta.dt = 1;
969 }
970
971 // Update transform
972 MatrixF mat = mObjToWorld;
973 mat.setColumn(3,pos);
974 Parent::setTransform(mat);
975 enableCollision();
976 if (mCollisionObject)
977 mCollisionObject->enableCollision();
978 updateContainer();
979
980 if ( mPhysicsRep )
981 mPhysicsRep->setTransform( mat );
982
983 //
984 if (contact) {
985 // Check for rest condition
986 if (!nonStatic && mVelocity.len() < sAtRestVelocity) {
987 mVelocity.x = mVelocity.y = mVelocity.z = 0;
988 mAtRest = true;
989 mAtRestCounter = 0;
990 }
991
992 // Only update the client if we hit a non-static shape or
993 // if this is our final rest pos.
994 if (nonStatic || mAtRest)
995 setMaskBits(PositionMask);
996 }
997
998 // Collision callbacks. These need to be processed whether we hit
999 // anything or not.
1000 if (!isGhost())
1001 {
1002 SimObjectPtr<Item> safePtr(this);
1003 if (stickyNotify)
1004 {
1005 notifyCollision();
1006 if(bool(safePtr))
1007 onStickyCollision_callback( getIdString() );
1008 }
1009 else
1010 notifyCollision();
1011
1012 // water
1013 if(bool(safePtr))
1014 {
1015 if(!mInLiquid && mWaterCoverage != 0.0f)
1016 {
1017 onEnterLiquid_callback( getIdString(), mWaterCoverage, mLiquidType.c_str() );
1018 mInLiquid = true;
1019 }
1020 else if(mInLiquid && mWaterCoverage == 0.0f)
1021 {
1022 onLeaveLiquid_callback(getIdString(), mLiquidType.c_str());
1023 mInLiquid = false;
1024 }
1025 }
1026 }
1027}
1028
1029
1030//----------------------------------------------------------------------------
1031
1032static MatrixF IMat(1);
1033
1034bool Item::buildPolyList(PolyListContext context, AbstractPolyList* polyList, const Box3F&, const SphereF&)
1035{
1036 if ( context == PLC_Decal )
1037 return false;
1038
1039 // Collision with the item is always against the item's object
1040 // space bounding box axis aligned in world space.
1041 Point3F pos;
1042 mObjToWorld.getColumn(3,&pos);
1043 IMat.setColumn(3,pos);
1044 polyList->setTransform(&IMat, mObjScale);
1045 polyList->setObject(this);
1046 polyList->addBox(mObjBox);
1047 return true;
1048}
1049
1050
1051//----------------------------------------------------------------------------
1052
1053U32 Item::packUpdate(NetConnection *connection, U32 mask, BitStream *stream)
1054{
1055 U32 retMask = Parent::packUpdate(connection,mask,stream);
1056
1057 if (stream->writeFlag(mask & InitialUpdateMask)) {
1058 stream->writeFlag(mRotate);
1059 stream->writeFlag(mStatic);
1060 if (stream->writeFlag(getScale() != Point3F(1, 1, 1)))
1061 mathWrite(*stream, getScale());
1062 }
1063
1064 if (mask & ThrowSrcMask && mCollisionObject) {
1065 S32 gIndex = connection->getGhostIndex(mCollisionObject);
1066 if (stream->writeFlag(gIndex != -1))
1067 stream->writeInt(gIndex,NetConnection::GhostIdBitSize);
1068 }
1069 else
1070 stream->writeFlag(false);
1071
1072 if (stream->writeFlag(mask & RotationMask && !mRotate)) {
1073 // Assumes rotation is about the Z axis
1074 AngAxisF aa(mObjToWorld);
1075 stream->writeFlag(aa.axis.z < 0);
1076 stream->write(aa.angle);
1077 }
1078
1079 if (stream->writeFlag(mask & PositionMask)) {
1080 Point3F pos;
1081 mObjToWorld.getColumn(3,&pos);
1082 mathWrite(*stream, pos);
1083 if (!stream->writeFlag(mAtRest)) {
1084 mathWrite(*stream, mVelocity);
1085 }
1086 stream->writeFlag(!(mask & NoWarpMask));
1087 }
1088 return retMask;
1089}
1090
1091void Item::unpackUpdate(NetConnection *connection, BitStream *stream)
1092{
1093 Parent::unpackUpdate(connection,stream);
1094
1095 // InitialUpdateMask
1096 if (stream->readFlag()) {
1097 mRotate = stream->readFlag();
1098 mStatic = stream->readFlag();
1099 if (stream->readFlag())
1100 mathRead(*stream, &mObjScale);
1101 else
1102 mObjScale.set(1, 1, 1);
1103 }
1104
1105 // ThrowSrcMask && mCollisionObject
1106 if (stream->readFlag()) {
1107 S32 gIndex = stream->readInt(NetConnection::GhostIdBitSize);
1108 setCollisionTimeout(static_cast<ShapeBase*>(connection->resolveGhost(gIndex)));
1109 }
1110
1111 MatrixF mat = mObjToWorld;
1112
1113 // RotationMask && !mRotate
1114 if (stream->readFlag()) {
1115 // Assumes rotation is about the Z axis
1116 AngAxisF aa;
1117 aa.axis.set(0.0f, 0.0f, stream->readFlag() ? -1.0f : 1.0f);
1118 stream->read(&aa.angle);
1119 aa.setMatrix(&mat);
1120 Point3F pos;
1121 mObjToWorld.getColumn(3,&pos);
1122 mat.setColumn(3,pos);
1123 }
1124
1125 // PositionMask
1126 if (stream->readFlag()) {
1127 Point3F pos;
1128 mathRead(*stream, &pos);
1129 F32 speed = mVelocity.len();
1130 if ((mAtRest = stream->readFlag()) == true)
1131 mVelocity.set(0.0f, 0.0f, 0.0f);
1132 else
1133 mathRead(*stream, &mVelocity);
1134
1135 if (stream->readFlag() && isProperlyAdded()) {
1136 // Determin number of ticks to warp based on the average
1137 // of the client and server velocities.
1138 mDelta.warpOffset = pos - mDelta.pos;
1139 F32 as = (speed + mVelocity.len()) * 0.5f * TickSec;
1140 F32 dt = (as > 0.00001f) ? mDelta.warpOffset.len() / as: sMaxWarpTicks;
1141 mDelta.warpTicks = (S32)((dt > sMinWarpTicks)? getMax(mFloor(dt + 0.5f), 1.0f): 0.0f);
1142
1143 if (mDelta.warpTicks)
1144 {
1145 // Setup the warp to start on the next tick, only the
1146 // object's position is warped.
1147 if (mDelta.warpTicks > sMaxWarpTicks)
1148 mDelta.warpTicks = sMaxWarpTicks;
1149 mDelta.warpOffset /= (F32)mDelta.warpTicks;
1150 }
1151 else {
1152 // Going to skip the warp, server and client are real close.
1153 // Adjust the frame interpolation to move smoothly to the
1154 // new position within the current tick.
1155 Point3F cp = mDelta.pos + mDelta.posVec * mDelta.dt;
1156 VectorF vec = mDelta.pos - cp;
1157 F32 vl = vec.len();
1158 if (vl) {
1159 F32 s = mDelta.posVec.len() / vl;
1160 mDelta.posVec = (cp - pos) * s;
1161 }
1162 mDelta.pos = pos;
1163 mat.setColumn(3,pos);
1164 }
1165 }
1166 else {
1167 // Set the item to the server position
1168 mDelta.warpTicks = 0;
1169 mDelta.posVec.set(0,0,0);
1170 mDelta.pos = pos;
1171 mDelta.dt = 0;
1172 mat.setColumn(3,pos);
1173 }
1174 }
1175 Parent::setTransform(mat);
1176}
1177
1178DefineEngineMethod( Item, isStatic, bool, (),,
1179 "@brief Is the object static (ie, non-movable)?\n\n"
1180 "@return True if the object is static, false if it is not.\n"
1181 "@tsexample\n"
1182 "// Query the item on if it is or is not static.\n"
1183 "%isStatic = %itemData.isStatic();\n\n"
1184 "@endtsexample\n\n"
1185 "@see static\n"
1186 )
1187{
1188 return object->isStatic();
1189}
1190
1191DefineEngineMethod( Item, isAtRest, bool, (),,
1192 "@brief Is the object at rest (ie, no longer moving)?\n\n"
1193 "@return True if the object is at rest, false if it is not.\n"
1194 "@tsexample\n"
1195 "// Query the item on if it is or is not at rest.\n"
1196 "%isAtRest = %item.isAtRest();\n\n"
1197 "@endtsexample\n\n"
1198 )
1199{
1200 return object->isAtRest();
1201}
1202
1203DefineEngineMethod( Item, isRotating, bool, (),,
1204 "@brief Is the object still rotating?\n\n"
1205 "@return True if the object is still rotating, false if it is not.\n"
1206 "@tsexample\n"
1207 "// Query the item on if it is or is not rotating.\n"
1208 "%isRotating = %itemData.isRotating();\n\n"
1209 "@endtsexample\n\n"
1210 "@see rotate\n"
1211 )
1212{
1213 return object->isRotating();
1214}
1215
1216DefineEngineMethod( Item, setCollisionTimeout, bool, (S32 ignoreColObj),,
1217 "@brief Temporarily disable collisions against a specific ShapeBase object.\n\n"
1218
1219 "This is useful to prevent a player from immediately picking up an Item they have "
1220 "just thrown. Only one object may be on the timeout list at a time. The timeout is "
1221 "defined as 15 ticks.\n\n"
1222
1223 "@param objectID ShapeBase object ID to disable collisions against.\n"
1224 "@return Returns true if the ShapeBase object requested could be found, false if it could not.\n"
1225
1226 "@tsexample\n"
1227 "// Set the ShapeBase Object ID to disable collisions against\n"
1228 "%ignoreColObj = %player.getID();\n\n"
1229 "// Inform this Item object to ignore collisions temproarily against the %ignoreColObj.\n"
1230 "%item.setCollisionTimeout(%ignoreColObj);\n\n"
1231 "@endtsexample\n\n"
1232 )
1233{
1234 ShapeBase* source = NULL;
1235 if (Sim::findObject(ignoreColObj,source)) {
1236 object->setCollisionTimeout(source);
1237 return true;
1238 }
1239 return false;
1240}
1241
1242
1243DefineEngineMethod( Item, getLastStickyPos, const char*, (),,
1244 "@brief Get the position on the surface on which this Item is stuck.\n\n"
1245 "@return Returns The XYZ position of where this Item is stuck.\n"
1246 "@tsexample\n"
1247 "// Acquire the position where this Item is currently stuck\n"
1248 "%stuckPosition = %item.getLastStickPos();\n\n"
1249 "@endtsexample\n\n"
1250 "@note Server side only.\n"
1251 )
1252{
1253 static const U32 bufSize = 256;
1254 char* ret = Con::getReturnBuffer(bufSize);
1255 if (object->isServerObject())
1256 dSprintf(ret, bufSize, "%g %g %g",
1257 object->mStickyCollisionPos.x,
1258 object->mStickyCollisionPos.y,
1259 object->mStickyCollisionPos.z);
1260 else
1261 dStrcpy(ret, "0 0 0", bufSize);
1262
1263 return ret;
1264}
1265
1266DefineEngineMethod( Item, getLastStickyNormal, const char *, (),,
1267 "@brief Get the normal of the surface on which the object is stuck.\n\n"
1268 "@return Returns The XYZ normal from where this Item is stuck.\n"
1269 "@tsexample\n"
1270 "// Acquire the position where this Item is currently stuck\n"
1271 "%stuckPosition = %item.getLastStickPos();\n\n"
1272 "@endtsexample\n\n"
1273 "@note Server side only.\n"
1274 )
1275{
1276 static const U32 bufSize = 256;
1277 char* ret = Con::getReturnBuffer(bufSize);
1278 if (object->isServerObject())
1279 dSprintf(ret, bufSize, "%g %g %g",
1280 object->mStickyCollisionNormal.x,
1281 object->mStickyCollisionNormal.y,
1282 object->mStickyCollisionNormal.z);
1283 else
1284 dStrcpy(ret, "0 0 0", bufSize);
1285
1286 return ret;
1287}
1288
1289//----------------------------------------------------------------------------
1290
1291bool Item::_setStatic(void *object, const char *index, const char *data)
1292{
1293 Item *i = static_cast<Item*>(object);
1294 i->mAtRest = dAtob(data);
1295 i->setMaskBits(InitialUpdateMask | PositionMask);
1296 return true;
1297}
1298
1299bool Item::_setRotate(void *object, const char *index, const char *data)
1300{
1301 Item *i = static_cast<Item*>(object);
1302 i->setMaskBits(InitialUpdateMask | RotationMask);
1303 return true;
1304}
1305
1306void Item::initPersistFields()
1307{
1308 addGroup("Misc");
1309 addProtectedField("static", TypeBool, Offset(mStatic, Item), &_setStatic, &defaultProtectedGetFn, "If true, the object is not moving in the world.\n");
1310 addProtectedField("rotate", TypeBool, Offset(mRotate, Item), &_setRotate, &defaultProtectedGetFn, "If true, the object will automatically rotate around its Z axis.\n");
1311 endGroup("Misc");
1312
1313 Parent::initPersistFields();
1314}
1315
1316void Item::consoleInit()
1317{
1318 Con::addVariable("Item::minWarpTicks",TypeF32,&sMinWarpTicks,
1319 "@brief Fraction of tick at which instant warp occures on the client.\n\n"
1320 "@ingroup GameObjects");
1321 Con::addVariable("Item::maxWarpTicks",TypeS32,&sMaxWarpTicks,
1322 "@brief When a warp needs to occur due to the client being too far off from the server, this is the "
1323 "maximum number of ticks we'll allow the client to warp to catch up.\n\n"
1324 "@ingroup GameObjects");
1325}
1326
1327//----------------------------------------------------------------------------
1328
1329void Item::prepRenderImage( SceneRenderState* state )
1330{
1331 // Items do NOT render if destroyed
1332 if (getDamageState() == Destroyed)
1333 return;
1334
1335 Parent::prepRenderImage( state );
1336}
1337
1338void Item::buildConvex(const Box3F& box, Convex* convex)
1339{
1340 if (mShapeInstance == NULL)
1341 return;
1342
1343 // These should really come out of a pool
1344 mConvexList->collectGarbage();
1345
1346 if (box.isOverlapped(getWorldBox()) == false)
1347 return;
1348
1349 // Just return a box convex for the entire shape...
1350 Convex* cc = 0;
1351 CollisionWorkingList& wl = convex->getWorkingList();
1352 for (CollisionWorkingList* itr = wl.wLink.mNext; itr != &wl; itr = itr->wLink.mNext) {
1353 if (itr->mConvex->getType() == BoxConvexType &&
1354 itr->mConvex->getObject() == this) {
1355 cc = itr->mConvex;
1356 break;
1357 }
1358 }
1359 if (cc)
1360 return;
1361
1362 // Create a new convex.
1363 BoxConvex* cp = new BoxConvex;
1364 mConvexList->registerObject(cp);
1365 convex->addToWorkingList(cp);
1366 cp->init(this);
1367
1368 mObjBox.getCenter(&cp->mCenter);
1369 cp->mSize.x = mObjBox.len_x() / 2.0f;
1370 cp->mSize.y = mObjBox.len_y() / 2.0f;
1371 cp->mSize.z = mObjBox.len_z() / 2.0f;
1372}
1373
1374void Item::advanceTime(F32 dt)
1375{
1376 Parent::advanceTime(dt);
1377 if ( isMounted() )
1378 return;
1379
1380 if( mRotate )
1381 {
1382 F32 r = (dt / sRotationSpeed) * M_2PI;
1383 Point3F pos = mRenderObjToWorld.getPosition();
1384 MatrixF rotMatrix;
1385 if( mRotate )
1386 {
1387 rotMatrix.set( EulerF( 0.0, 0.0, r ) );
1388 }
1389 else
1390 {
1391 rotMatrix.set( EulerF( r * 0.5, 0.0, r ) );
1392 }
1393 MatrixF mat = mRenderObjToWorld;
1394 mat.setPosition( pos );
1395 mat.mul( rotMatrix );
1396 setRenderTransform(mat);
1397 }
1398
1399}
1400