1 //
2 // Copyright (c) 2008-2017 the Urho3D project.
3 //
4 // Permission is hereby granted, free of charge, to any person obtaining a copy
5 // of this software and associated documentation files (the "Software"), to deal
6 // in the Software without restriction, including without limitation the rights
7 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 // copies of the Software, and to permit persons to whom the Software is
9 // furnished to do so, subject to the following conditions:
10 //
11 // The above copyright notice and this permission notice shall be included in
12 // all copies or substantial portions of the Software.
13 //
14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 // THE SOFTWARE.
21 //
22
23 #include "../Precompiled.h"
24
25 #include "../Core/Context.h"
26 #include "../Core/Profiler.h"
27 #include "../Graphics/CustomGeometry.h"
28 #include "../Graphics/DebugRenderer.h"
29 #include "../Graphics/DrawableEvents.h"
30 #include "../Graphics/Geometry.h"
31 #include "../Graphics/IndexBuffer.h"
32 #include "../Graphics/Model.h"
33 #include "../Graphics/Terrain.h"
34 #include "../Graphics/VertexBuffer.h"
35 #include "../IO/Log.h"
36 #include "../Physics/CollisionShape.h"
37 #include "../Physics/PhysicsUtils.h"
38 #include "../Physics/PhysicsWorld.h"
39 #include "../Physics/RigidBody.h"
40 #include "../Resource/ResourceCache.h"
41 #include "../Resource/ResourceEvents.h"
42 #include "../Scene/Scene.h"
43
44 #include <Bullet/BulletCollision/CollisionDispatch/btInternalEdgeUtility.h>
45 #include <Bullet/BulletCollision/CollisionShapes/btBoxShape.h>
46 #include <Bullet/BulletCollision/CollisionShapes/btCapsuleShape.h>
47 #include <Bullet/BulletCollision/CollisionShapes/btCompoundShape.h>
48 #include <Bullet/BulletCollision/CollisionShapes/btConeShape.h>
49 #include <Bullet/BulletCollision/CollisionShapes/btConvexHullShape.h>
50 #include <Bullet/BulletCollision/CollisionShapes/btCylinderShape.h>
51 #include <Bullet/BulletCollision/CollisionShapes/btHeightfieldTerrainShape.h>
52 #include <Bullet/BulletCollision/CollisionShapes/btScaledBvhTriangleMeshShape.h>
53 #include <Bullet/BulletCollision/CollisionShapes/btSphereShape.h>
54 #include <Bullet/BulletCollision/CollisionShapes/btTriangleIndexVertexArray.h>
55 #include <Bullet/BulletDynamics/Dynamics/btDiscreteDynamicsWorld.h>
56 #include <Bullet/BulletCollision/CollisionShapes/btStaticPlaneShape.h>
57 #include <StanHull/hull.h>
58
59 namespace Urho3D
60 {
61
62 static const float DEFAULT_COLLISION_MARGIN = 0.04f;
63 static const unsigned QUANTIZE_MAX_TRIANGLES = 1000000;
64
65 static const btVector3 WHITE(1.0f, 1.0f, 1.0f);
66 static const btVector3 GREEN(0.0f, 1.0f, 0.0f);
67
68 static const char* typeNames[] =
69 {
70 "Box",
71 "Sphere",
72 "StaticPlane",
73 "Cylinder",
74 "Capsule",
75 "Cone",
76 "TriangleMesh",
77 "ConvexHull",
78 "Terrain",
79 0
80 };
81
82 extern const char* PHYSICS_CATEGORY;
83
84 class TriangleMeshInterface : public btTriangleIndexVertexArray
85 {
86 public:
TriangleMeshInterface(Model * model,unsigned lodLevel)87 TriangleMeshInterface(Model* model, unsigned lodLevel) :
88 btTriangleIndexVertexArray()
89 {
90 unsigned numGeometries = model->GetNumGeometries();
91 unsigned totalTriangles = 0;
92
93 for (unsigned i = 0; i < numGeometries; ++i)
94 {
95 Geometry* geometry = model->GetGeometry(i, lodLevel);
96 if (!geometry)
97 {
98 URHO3D_LOGWARNING("Skipping null geometry for triangle mesh collision");
99 continue;
100 }
101
102 SharedArrayPtr<unsigned char> vertexData;
103 SharedArrayPtr<unsigned char> indexData;
104 unsigned vertexSize;
105 unsigned indexSize;
106 const PODVector<VertexElement>* elements;
107
108 geometry->GetRawDataShared(vertexData, vertexSize, indexData, indexSize, elements);
109 if (!vertexData || !indexData || !elements || VertexBuffer::GetElementOffset(*elements, TYPE_VECTOR3, SEM_POSITION) != 0)
110 {
111 URHO3D_LOGWARNING("Skipping geometry with no or unsuitable CPU-side geometry data for triangle mesh collision");
112 continue;
113 }
114
115 // Keep shared pointers to the vertex/index data so that if it's unloaded or changes size, we don't crash
116 dataArrays_.Push(vertexData);
117 dataArrays_.Push(indexData);
118
119 unsigned indexStart = geometry->GetIndexStart();
120 unsigned indexCount = geometry->GetIndexCount();
121
122 btIndexedMesh meshIndex;
123 meshIndex.m_numTriangles = indexCount / 3;
124 meshIndex.m_triangleIndexBase = &indexData[indexStart * indexSize];
125 meshIndex.m_triangleIndexStride = 3 * indexSize;
126 meshIndex.m_numVertices = 0;
127 meshIndex.m_vertexBase = vertexData;
128 meshIndex.m_vertexStride = vertexSize;
129 meshIndex.m_indexType = (indexSize == sizeof(unsigned short)) ? PHY_SHORT : PHY_INTEGER;
130 meshIndex.m_vertexType = PHY_FLOAT;
131 m_indexedMeshes.push_back(meshIndex);
132
133 totalTriangles += meshIndex.m_numTriangles;
134 }
135
136 // Bullet will not work properly with quantized AABB compression, if the triangle count is too large. Use a conservative
137 // threshold value
138 useQuantize_ = totalTriangles <= QUANTIZE_MAX_TRIANGLES;
139 }
140
TriangleMeshInterface(CustomGeometry * custom)141 TriangleMeshInterface(CustomGeometry* custom) :
142 btTriangleIndexVertexArray()
143 {
144 const Vector<PODVector<CustomGeometryVertex> >& srcVertices = custom->GetVertices();
145 unsigned totalVertexCount = 0;
146 unsigned totalTriangles = 0;
147
148 for (unsigned i = 0; i < srcVertices.Size(); ++i)
149 totalVertexCount += srcVertices[i].Size();
150
151 if (totalVertexCount)
152 {
153 // CustomGeometry vertex data is unindexed, so build index data here
154 SharedArrayPtr<unsigned char> vertexData(new unsigned char[totalVertexCount * sizeof(Vector3)]);
155 SharedArrayPtr<unsigned char> indexData(new unsigned char[totalVertexCount * sizeof(unsigned)]);
156 dataArrays_.Push(vertexData);
157 dataArrays_.Push(indexData);
158
159 Vector3* destVertex = reinterpret_cast<Vector3*>(&vertexData[0]);
160 unsigned* destIndex = reinterpret_cast<unsigned*>(&indexData[0]);
161 unsigned k = 0;
162
163 for (unsigned i = 0; i < srcVertices.Size(); ++i)
164 {
165 for (unsigned j = 0; j < srcVertices[i].Size(); ++j)
166 {
167 *destVertex++ = srcVertices[i][j].position_;
168 *destIndex++ = k++;
169 }
170 }
171
172 btIndexedMesh meshIndex;
173 meshIndex.m_numTriangles = totalVertexCount / 3;
174 meshIndex.m_triangleIndexBase = indexData;
175 meshIndex.m_triangleIndexStride = 3 * sizeof(unsigned);
176 meshIndex.m_numVertices = totalVertexCount;
177 meshIndex.m_vertexBase = vertexData;
178 meshIndex.m_vertexStride = sizeof(Vector3);
179 meshIndex.m_indexType = PHY_INTEGER;
180 meshIndex.m_vertexType = PHY_FLOAT;
181 m_indexedMeshes.push_back(meshIndex);
182
183 totalTriangles += meshIndex.m_numTriangles;
184 }
185
186 useQuantize_ = totalTriangles <= QUANTIZE_MAX_TRIANGLES;
187 }
188
189 /// OK to use quantization flag.
190 bool useQuantize_;
191
192 private:
193 /// Shared vertex/index data used in the collision
194 Vector<SharedArrayPtr<unsigned char> > dataArrays_;
195 };
196
TriangleMeshData(Model * model,unsigned lodLevel)197 TriangleMeshData::TriangleMeshData(Model* model, unsigned lodLevel)
198 {
199 meshInterface_ = new TriangleMeshInterface(model, lodLevel);
200 shape_ = new btBvhTriangleMeshShape(meshInterface_.Get(), meshInterface_->useQuantize_, true);
201
202 infoMap_ = new btTriangleInfoMap();
203 btGenerateInternalEdgeInfo(shape_.Get(), infoMap_.Get());
204 }
205
TriangleMeshData(CustomGeometry * custom)206 TriangleMeshData::TriangleMeshData(CustomGeometry* custom)
207 {
208 meshInterface_ = new TriangleMeshInterface(custom);
209 shape_ = new btBvhTriangleMeshShape(meshInterface_.Get(), meshInterface_->useQuantize_, true);
210
211 infoMap_ = new btTriangleInfoMap();
212 btGenerateInternalEdgeInfo(shape_.Get(), infoMap_.Get());
213 }
214
~TriangleMeshData()215 TriangleMeshData::~TriangleMeshData()
216 {
217 }
218
ConvexData(Model * model,unsigned lodLevel)219 ConvexData::ConvexData(Model* model, unsigned lodLevel)
220 {
221 PODVector<Vector3> vertices;
222 unsigned numGeometries = model->GetNumGeometries();
223
224 for (unsigned i = 0; i < numGeometries; ++i)
225 {
226 Geometry* geometry = model->GetGeometry(i, lodLevel);
227 if (!geometry)
228 {
229 URHO3D_LOGWARNING("Skipping null geometry for convex hull collision");
230 continue;
231 };
232
233 const unsigned char* vertexData;
234 const unsigned char* indexData;
235 unsigned vertexSize;
236 unsigned indexSize;
237 const PODVector<VertexElement>* elements;
238
239 geometry->GetRawData(vertexData, vertexSize, indexData, indexSize, elements);
240 if (!vertexData || VertexBuffer::GetElementOffset(*elements, TYPE_VECTOR3, SEM_POSITION) != 0)
241 {
242 URHO3D_LOGWARNING("Skipping geometry with no or unsuitable CPU-side geometry data for convex hull collision");
243 continue;
244 }
245
246 unsigned vertexStart = geometry->GetVertexStart();
247 unsigned vertexCount = geometry->GetVertexCount();
248
249 // Copy vertex data
250 for (unsigned j = 0; j < vertexCount; ++j)
251 {
252 const Vector3& v = *((const Vector3*)(&vertexData[(vertexStart + j) * vertexSize]));
253 vertices.Push(v);
254 }
255 }
256
257 BuildHull(vertices);
258 }
259
ConvexData(CustomGeometry * custom)260 ConvexData::ConvexData(CustomGeometry* custom)
261 {
262 const Vector<PODVector<CustomGeometryVertex> >& srcVertices = custom->GetVertices();
263 PODVector<Vector3> vertices;
264
265 for (unsigned i = 0; i < srcVertices.Size(); ++i)
266 {
267 for (unsigned j = 0; j < srcVertices[i].Size(); ++j)
268 vertices.Push(srcVertices[i][j].position_);
269 }
270
271 BuildHull(vertices);
272 }
273
BuildHull(const PODVector<Vector3> & vertices)274 void ConvexData::BuildHull(const PODVector<Vector3>& vertices)
275 {
276 if (vertices.Size())
277 {
278 // Build the convex hull from the raw geometry
279 StanHull::HullDesc desc;
280 desc.SetHullFlag(StanHull::QF_TRIANGLES);
281 desc.mVcount = vertices.Size();
282 desc.mVertices = vertices[0].Data();
283 desc.mVertexStride = 3 * sizeof(float);
284 desc.mSkinWidth = 0.0f;
285
286 StanHull::HullLibrary lib;
287 StanHull::HullResult result;
288 lib.CreateConvexHull(desc, result);
289
290 vertexCount_ = result.mNumOutputVertices;
291 vertexData_ = new Vector3[vertexCount_];
292
293 indexCount_ = result.mNumIndices;
294 indexData_ = new unsigned[indexCount_];
295
296 // Copy vertex data & index data
297 memcpy(vertexData_.Get(), result.mOutputVertices, vertexCount_ * sizeof(Vector3));
298 memcpy(indexData_.Get(), result.mIndices, indexCount_ * sizeof(unsigned));
299
300 lib.ReleaseResult(result);
301 }
302 else
303 {
304 vertexCount_ = 0;
305 indexCount_ = 0;
306 }
307 }
308
~ConvexData()309 ConvexData::~ConvexData()
310 {
311 }
312
HeightfieldData(Terrain * terrain,unsigned lodLevel)313 HeightfieldData::HeightfieldData(Terrain* terrain, unsigned lodLevel) :
314 heightData_(terrain->GetHeightData()),
315 spacing_(terrain->GetSpacing()),
316 size_(terrain->GetNumVertices()),
317 minHeight_(0.0f),
318 maxHeight_(0.0f)
319 {
320 if (heightData_)
321 {
322 if (lodLevel > 0)
323 {
324 IntVector2 lodSize = size_;
325 Vector3 lodSpacing = spacing_;
326 unsigned skip = 1;
327
328 for (unsigned i = 0; i < lodLevel; ++i)
329 {
330 skip *= 2;
331 lodSpacing.x_ *= 2.0f;
332 lodSpacing.z_ *= 2.0f;
333 int rX = lodSize.x_ & 1;
334 int rY = lodSize.y_ & 1;
335 lodSize.x_ >>= 1;
336 lodSize.y_ >>= 1;
337 lodSize.x_ += rX;
338 lodSize.y_ += rY;
339 if (lodSize.x_ <= 2 || lodSize.y_ <= 2)
340 break;
341 }
342
343 SharedArrayPtr<float> lodHeightData(new float[lodSize.x_ * lodSize.y_]);
344 for (int y = 0, dY = 0; y < size_.y_ && dY < lodSize.y_; y += skip, ++dY)
345 {
346 for (int x = 0, dX = 0; x < size_.x_ && dX < lodSize.x_; x += skip, ++dX)
347 lodHeightData[dY * lodSize.x_ + dX] = heightData_[y * size_.x_ + x];
348 }
349
350 size_ = lodSize;
351 spacing_ = lodSpacing;
352 heightData_ = lodHeightData;
353 }
354
355 unsigned points = (unsigned)(size_.x_ * size_.y_);
356 float* data = heightData_.Get();
357
358 minHeight_ = maxHeight_ = data[0];
359 for (unsigned i = 1; i < points; ++i)
360 {
361 minHeight_ = Min(minHeight_, data[i]);
362 maxHeight_ = Max(maxHeight_, data[i]);
363 }
364 }
365 }
366
~HeightfieldData()367 HeightfieldData::~HeightfieldData()
368 {
369 }
370
HasDynamicBuffers(Model * model,unsigned lodLevel)371 bool HasDynamicBuffers(Model* model, unsigned lodLevel)
372 {
373 unsigned numGeometries = model->GetNumGeometries();
374
375 for (unsigned i = 0; i < numGeometries; ++i)
376 {
377 Geometry* geometry = model->GetGeometry(i, lodLevel);
378 if (!geometry)
379 continue;
380 unsigned numVertexBuffers = geometry->GetNumVertexBuffers();
381 for (unsigned j = 0; j < numVertexBuffers; ++j)
382 {
383 VertexBuffer* buffer = geometry->GetVertexBuffer(j);
384 if (!buffer)
385 continue;
386 if (buffer->IsDynamic())
387 return true;
388 }
389 IndexBuffer* buffer = geometry->GetIndexBuffer();
390 if (buffer && buffer->IsDynamic())
391 return true;
392 }
393
394 return false;
395 }
396
CollisionShape(Context * context)397 CollisionShape::CollisionShape(Context* context) :
398 Component(context),
399 shapeType_(SHAPE_BOX),
400 position_(Vector3::ZERO),
401 rotation_(Quaternion::IDENTITY),
402 size_(Vector3::ONE),
403 cachedWorldScale_(Vector3::ONE),
404 lodLevel_(0),
405 customGeometryID_(0),
406 margin_(DEFAULT_COLLISION_MARGIN),
407 recreateShape_(true),
408 retryCreation_(false)
409 {
410 }
411
~CollisionShape()412 CollisionShape::~CollisionShape()
413 {
414 ReleaseShape();
415
416 if (physicsWorld_)
417 physicsWorld_->RemoveCollisionShape(this);
418 }
419
RegisterObject(Context * context)420 void CollisionShape::RegisterObject(Context* context)
421 {
422 context->RegisterFactory<CollisionShape>(PHYSICS_CATEGORY);
423
424 URHO3D_ACCESSOR_ATTRIBUTE("Is Enabled", IsEnabled, SetEnabled, bool, true, AM_DEFAULT);
425 URHO3D_ENUM_ATTRIBUTE("Shape Type", shapeType_, typeNames, SHAPE_BOX, AM_DEFAULT);
426 URHO3D_ATTRIBUTE("Size", Vector3, size_, Vector3::ONE, AM_DEFAULT);
427 URHO3D_ACCESSOR_ATTRIBUTE("Offset Position", GetPosition, SetPosition, Vector3, Vector3::ZERO, AM_DEFAULT);
428 URHO3D_ACCESSOR_ATTRIBUTE("Offset Rotation", GetRotation, SetRotation, Quaternion, Quaternion::IDENTITY, AM_DEFAULT);
429 URHO3D_MIXED_ACCESSOR_ATTRIBUTE("Model", GetModelAttr, SetModelAttr, ResourceRef, ResourceRef(Model::GetTypeStatic()), AM_DEFAULT);
430 URHO3D_ATTRIBUTE("LOD Level", int, lodLevel_, 0, AM_DEFAULT);
431 URHO3D_ATTRIBUTE("Collision Margin", float, margin_, DEFAULT_COLLISION_MARGIN, AM_DEFAULT);
432 URHO3D_ATTRIBUTE("CustomGeometry ComponentID", unsigned, customGeometryID_, 0, AM_DEFAULT | AM_COMPONENTID);
433 }
434
OnSetAttribute(const AttributeInfo & attr,const Variant & src)435 void CollisionShape::OnSetAttribute(const AttributeInfo& attr, const Variant& src)
436 {
437 Serializable::OnSetAttribute(attr, src);
438
439 // Change of any non-accessor attribute requires recreation of the collision shape
440 if (!attr.accessor_)
441 recreateShape_ = true;
442 }
443
ApplyAttributes()444 void CollisionShape::ApplyAttributes()
445 {
446 if (recreateShape_)
447 {
448 UpdateShape();
449 NotifyRigidBody();
450 }
451 }
452
OnSetEnabled()453 void CollisionShape::OnSetEnabled()
454 {
455 NotifyRigidBody();
456 }
457
DrawDebugGeometry(DebugRenderer * debug,bool depthTest)458 void CollisionShape::DrawDebugGeometry(DebugRenderer* debug, bool depthTest)
459 {
460 if (debug && physicsWorld_ && shape_ && node_ && IsEnabledEffective())
461 {
462 // Use the rigid body's world transform if possible, as it may be different from the rendering transform
463 Matrix3x4 worldTransform;
464 RigidBody* body = GetComponent<RigidBody>();
465 bool bodyActive = false;
466 if (body)
467 {
468 worldTransform = Matrix3x4(body->GetPosition(), body->GetRotation(), node_->GetWorldScale());
469 bodyActive = body->IsActive();
470 }
471 else
472 worldTransform = node_->GetWorldTransform();
473
474 // Special case code for convex hull: bypass Bullet's own rendering to draw triangles correctly, not just edges
475 if (shapeType_ == SHAPE_CONVEXHULL)
476 {
477 ConvexData *convexData = static_cast<ConvexData*>(GetGeometryData());
478 RigidBody* body = GetComponent<RigidBody>();
479 Color color = bodyActive ? Color::WHITE : Color::GREEN;
480 Matrix3x4 shapeTransform(worldTransform * position_, worldTransform.Rotation() * rotation_, worldTransform.Scale());
481
482 if (convexData)
483 {
484 for (unsigned i = 0; i < convexData->indexCount_; i += 3)
485 {
486 Vector3 a = shapeTransform * convexData->vertexData_[convexData->indexData_[i + 0]];
487 Vector3 b = shapeTransform * convexData->vertexData_[convexData->indexData_[i + 1]];
488 Vector3 c = shapeTransform * convexData->vertexData_[convexData->indexData_[i + 2]];
489 debug->AddLine(a, b, color, depthTest);
490 debug->AddLine(b, c, color, depthTest);
491 debug->AddLine(a, c, color, depthTest);
492 }
493 }
494 }
495 else
496 {
497 physicsWorld_->SetDebugRenderer(debug);
498 physicsWorld_->SetDebugDepthTest(depthTest);
499
500 Vector3 position = position_;
501 // For terrains, undo the height centering performed automatically by Bullet
502 if (shapeType_ == SHAPE_TERRAIN && geometry_)
503 {
504 HeightfieldData* heightfield = static_cast<HeightfieldData*>(geometry_.Get());
505 position.y_ += (heightfield->minHeight_ + heightfield->maxHeight_) * 0.5f;
506 }
507
508 Vector3 worldPosition(worldTransform * position);
509 Quaternion worldRotation(worldTransform.Rotation() * rotation_);
510
511 btDiscreteDynamicsWorld* world = physicsWorld_->GetWorld();
512 world->debugDrawObject(btTransform(ToBtQuaternion(worldRotation), ToBtVector3(worldPosition)), shape_.Get(), bodyActive ?
513 WHITE : GREEN);
514
515 physicsWorld_->SetDebugRenderer(0);
516 }
517 }
518 }
519
SetBox(const Vector3 & size,const Vector3 & position,const Quaternion & rotation)520 void CollisionShape::SetBox(const Vector3& size, const Vector3& position, const Quaternion& rotation)
521 {
522 if (model_)
523 UnsubscribeFromEvent(model_, E_RELOADFINISHED);
524
525 shapeType_ = SHAPE_BOX;
526 size_ = size;
527 position_ = position;
528 rotation_ = rotation;
529 model_.Reset();
530 customGeometryID_ = 0;
531
532 UpdateShape();
533 NotifyRigidBody();
534 MarkNetworkUpdate();
535 }
536
SetSphere(float diameter,const Vector3 & position,const Quaternion & rotation)537 void CollisionShape::SetSphere(float diameter, const Vector3& position, const Quaternion& rotation)
538 {
539 if (model_)
540 UnsubscribeFromEvent(model_, E_RELOADFINISHED);
541
542 shapeType_ = SHAPE_SPHERE;
543 size_ = Vector3(diameter, diameter, diameter);
544 position_ = position;
545 rotation_ = rotation;
546 model_.Reset();
547 customGeometryID_ = 0;
548
549 UpdateShape();
550 NotifyRigidBody();
551 MarkNetworkUpdate();
552 }
553
SetStaticPlane(const Vector3 & position,const Quaternion & rotation)554 void CollisionShape::SetStaticPlane(const Vector3& position, const Quaternion& rotation)
555 {
556 if (model_)
557 UnsubscribeFromEvent(model_, E_RELOADFINISHED);
558
559 shapeType_ = SHAPE_STATICPLANE;
560 position_ = position;
561 rotation_ = rotation;
562 model_.Reset();
563 customGeometryID_ = 0;
564
565 UpdateShape();
566 NotifyRigidBody();
567 MarkNetworkUpdate();
568 }
569
SetCylinder(float diameter,float height,const Vector3 & position,const Quaternion & rotation)570 void CollisionShape::SetCylinder(float diameter, float height, const Vector3& position, const Quaternion& rotation)
571 {
572 if (model_)
573 UnsubscribeFromEvent(model_, E_RELOADFINISHED);
574
575 shapeType_ = SHAPE_CYLINDER;
576 size_ = Vector3(diameter, height, diameter);
577 position_ = position;
578 rotation_ = rotation;
579 model_.Reset();
580 customGeometryID_ = 0;
581
582 UpdateShape();
583 NotifyRigidBody();
584 MarkNetworkUpdate();
585 }
586
SetCapsule(float diameter,float height,const Vector3 & position,const Quaternion & rotation)587 void CollisionShape::SetCapsule(float diameter, float height, const Vector3& position, const Quaternion& rotation)
588 {
589 if (model_)
590 UnsubscribeFromEvent(model_, E_RELOADFINISHED);
591
592 shapeType_ = SHAPE_CAPSULE;
593 size_ = Vector3(diameter, height, diameter);
594 position_ = position;
595 rotation_ = rotation;
596 model_.Reset();
597 customGeometryID_ = 0;
598
599 UpdateShape();
600 NotifyRigidBody();
601 MarkNetworkUpdate();
602 }
603
SetCone(float diameter,float height,const Vector3 & position,const Quaternion & rotation)604 void CollisionShape::SetCone(float diameter, float height, const Vector3& position, const Quaternion& rotation)
605 {
606 if (model_)
607 UnsubscribeFromEvent(model_, E_RELOADFINISHED);
608
609 shapeType_ = SHAPE_CONE;
610 size_ = Vector3(diameter, height, diameter);
611 position_ = position;
612 rotation_ = rotation;
613 model_.Reset();
614 customGeometryID_ = 0;
615
616 UpdateShape();
617 NotifyRigidBody();
618 MarkNetworkUpdate();
619 }
620
SetTriangleMesh(Model * model,unsigned lodLevel,const Vector3 & scale,const Vector3 & position,const Quaternion & rotation)621 void CollisionShape::SetTriangleMesh(Model* model, unsigned lodLevel, const Vector3& scale, const Vector3& position,
622 const Quaternion& rotation)
623 {
624 if (!model)
625 {
626 URHO3D_LOGERROR("Null model, can not set triangle mesh");
627 return;
628 }
629
630 if (model_)
631 UnsubscribeFromEvent(model_, E_RELOADFINISHED);
632
633 shapeType_ = SHAPE_TRIANGLEMESH;
634 model_ = model;
635 lodLevel_ = lodLevel;
636 size_ = scale;
637 position_ = position;
638 rotation_ = rotation;
639 customGeometryID_ = 0;
640
641 UpdateShape();
642 NotifyRigidBody();
643 MarkNetworkUpdate();
644 }
645
SetCustomTriangleMesh(CustomGeometry * custom,const Vector3 & scale,const Vector3 & position,const Quaternion & rotation)646 void CollisionShape::SetCustomTriangleMesh(CustomGeometry* custom, const Vector3& scale, const Vector3& position,
647 const Quaternion& rotation)
648 {
649 if (!custom)
650 {
651 URHO3D_LOGERROR("Null custom geometry, can not set triangle mesh");
652 return;
653 }
654 if (custom->GetScene() != GetScene())
655 {
656 URHO3D_LOGERROR("Custom geometry is not in the same scene as the collision shape, can not set triangle mesh");
657 return;
658 }
659
660 if (model_)
661 UnsubscribeFromEvent(model_, E_RELOADFINISHED);
662
663 shapeType_ = SHAPE_TRIANGLEMESH;
664 model_.Reset();
665 lodLevel_ = 0;
666 size_ = scale;
667 position_ = position;
668 rotation_ = rotation;
669 customGeometryID_ = custom->GetID();
670
671 UpdateShape();
672 NotifyRigidBody();
673 MarkNetworkUpdate();
674 }
675
SetConvexHull(Model * model,unsigned lodLevel,const Vector3 & scale,const Vector3 & position,const Quaternion & rotation)676 void CollisionShape::SetConvexHull(Model* model, unsigned lodLevel, const Vector3& scale, const Vector3& position,
677 const Quaternion& rotation)
678 {
679 if (!model)
680 {
681 URHO3D_LOGERROR("Null model, can not set convex hull");
682 return;
683 }
684
685 if (model_)
686 UnsubscribeFromEvent(model_, E_RELOADFINISHED);
687
688 shapeType_ = SHAPE_CONVEXHULL;
689 model_ = model;
690 lodLevel_ = lodLevel;
691 size_ = scale;
692 position_ = position;
693 rotation_ = rotation;
694 customGeometryID_ = 0;
695
696 UpdateShape();
697 NotifyRigidBody();
698 MarkNetworkUpdate();
699 }
700
SetCustomConvexHull(CustomGeometry * custom,const Vector3 & scale,const Vector3 & position,const Quaternion & rotation)701 void CollisionShape::SetCustomConvexHull(CustomGeometry* custom, const Vector3& scale, const Vector3& position,
702 const Quaternion& rotation)
703 {
704 if (!custom)
705 {
706 URHO3D_LOGERROR("Null custom geometry, can not set convex hull");
707 return;
708 }
709 if (custom->GetScene() != GetScene())
710 {
711 URHO3D_LOGERROR("Custom geometry is not in the same scene as the collision shape, can not set convex hull");
712 return;
713 }
714
715 if (model_)
716 UnsubscribeFromEvent(model_, E_RELOADFINISHED);
717
718 shapeType_ = SHAPE_CONVEXHULL;
719 model_.Reset();
720 lodLevel_ = 0;
721 size_ = scale;
722 position_ = position;
723 rotation_ = rotation;
724 customGeometryID_ = custom->GetID();
725
726 UpdateShape();
727 NotifyRigidBody();
728 MarkNetworkUpdate();
729 }
730
SetTerrain(unsigned lodLevel)731 void CollisionShape::SetTerrain(unsigned lodLevel)
732 {
733 Terrain* terrain = GetComponent<Terrain>();
734 if (!terrain)
735 {
736 URHO3D_LOGERROR("No terrain component, can not set terrain shape");
737 return;
738 }
739
740 if (model_)
741 UnsubscribeFromEvent(model_, E_RELOADFINISHED);
742
743 shapeType_ = SHAPE_TERRAIN;
744 lodLevel_ = lodLevel;
745
746 UpdateShape();
747 NotifyRigidBody();
748 MarkNetworkUpdate();
749 }
750
SetShapeType(ShapeType type)751 void CollisionShape::SetShapeType(ShapeType type)
752 {
753 if (type != shapeType_)
754 {
755 shapeType_ = type;
756 UpdateShape();
757 NotifyRigidBody();
758 MarkNetworkUpdate();
759 }
760 }
761
SetSize(const Vector3 & size)762 void CollisionShape::SetSize(const Vector3& size)
763 {
764 if (size != size_)
765 {
766 size_ = size;
767 UpdateShape();
768 NotifyRigidBody();
769 MarkNetworkUpdate();
770 }
771 }
772
SetPosition(const Vector3 & position)773 void CollisionShape::SetPosition(const Vector3& position)
774 {
775 if (position != position_)
776 {
777 position_ = position;
778 NotifyRigidBody();
779 MarkNetworkUpdate();
780 }
781 }
782
SetRotation(const Quaternion & rotation)783 void CollisionShape::SetRotation(const Quaternion& rotation)
784 {
785 if (rotation != rotation_)
786 {
787 rotation_ = rotation;
788 NotifyRigidBody();
789 MarkNetworkUpdate();
790 }
791 }
792
SetTransform(const Vector3 & position,const Quaternion & rotation)793 void CollisionShape::SetTransform(const Vector3& position, const Quaternion& rotation)
794 {
795 if (position != position_ || rotation != rotation_)
796 {
797 position_ = position;
798 rotation_ = rotation;
799 NotifyRigidBody();
800 MarkNetworkUpdate();
801 }
802 }
803
SetMargin(float margin)804 void CollisionShape::SetMargin(float margin)
805 {
806 margin = Max(margin, 0.0f);
807
808 if (margin != margin_)
809 {
810 if (shape_)
811 shape_->setMargin(margin);
812 margin_ = margin;
813 MarkNetworkUpdate();
814 }
815 }
816
SetModel(Model * model)817 void CollisionShape::SetModel(Model* model)
818 {
819 if (model != model_)
820 {
821 if (model_)
822 UnsubscribeFromEvent(model_, E_RELOADFINISHED);
823
824 model_ = model;
825 if (shapeType_ >= SHAPE_TRIANGLEMESH)
826 {
827 UpdateShape();
828 NotifyRigidBody();
829 }
830 MarkNetworkUpdate();
831 }
832 }
833
SetLodLevel(unsigned lodLevel)834 void CollisionShape::SetLodLevel(unsigned lodLevel)
835 {
836 if (lodLevel != lodLevel_)
837 {
838 lodLevel_ = lodLevel;
839 if (shapeType_ >= SHAPE_TRIANGLEMESH)
840 {
841 UpdateShape();
842 NotifyRigidBody();
843 }
844 MarkNetworkUpdate();
845 }
846 }
847
GetWorldBoundingBox() const848 BoundingBox CollisionShape::GetWorldBoundingBox() const
849 {
850 if (shape_ && node_)
851 {
852 // Use the rigid body's world transform if possible, as it may be different from the rendering transform
853 RigidBody* body = GetComponent<RigidBody>();
854 Matrix3x4 worldTransform = body ? Matrix3x4(body->GetPosition(), body->GetRotation(), node_->GetWorldScale()) :
855 node_->GetWorldTransform();
856
857 Vector3 worldPosition(worldTransform * position_);
858 Quaternion worldRotation(worldTransform.Rotation() * rotation_);
859 btTransform shapeWorldTransform(ToBtQuaternion(worldRotation), ToBtVector3(worldPosition));
860 btVector3 aabbMin, aabbMax;
861 shape_->getAabb(shapeWorldTransform, aabbMin, aabbMax);
862
863 return BoundingBox(ToVector3(aabbMin), ToVector3(aabbMax));
864 }
865 else
866 return BoundingBox();
867 }
868
NotifyRigidBody(bool updateMass)869 void CollisionShape::NotifyRigidBody(bool updateMass)
870 {
871 btCompoundShape* compound = GetParentCompoundShape();
872 if (node_ && shape_ && compound)
873 {
874 // Remove the shape first to ensure it is not added twice
875 compound->removeChildShape(shape_.Get());
876
877 if (IsEnabledEffective())
878 {
879 // Then add with updated offset
880 Vector3 position = position_;
881 // For terrains, undo the height centering performed automatically by Bullet
882 if (shapeType_ == SHAPE_TERRAIN && geometry_)
883 {
884 HeightfieldData* heightfield = static_cast<HeightfieldData*>(geometry_.Get());
885 position.y_ += (heightfield->minHeight_ + heightfield->maxHeight_) * 0.5f;
886 }
887
888 btTransform offset;
889 offset.setOrigin(ToBtVector3(node_->GetWorldScale() * position));
890 offset.setRotation(ToBtQuaternion(rotation_));
891 compound->addChildShape(offset, shape_.Get());
892 }
893
894 // Finally tell the rigid body to update its mass
895 if (updateMass)
896 rigidBody_->UpdateMass();
897 }
898 }
899
SetModelAttr(const ResourceRef & value)900 void CollisionShape::SetModelAttr(const ResourceRef& value)
901 {
902 ResourceCache* cache = GetSubsystem<ResourceCache>();
903 model_ = cache->GetResource<Model>(value.name_);
904 recreateShape_ = true;
905 MarkNetworkUpdate();
906 }
907
GetModelAttr() const908 ResourceRef CollisionShape::GetModelAttr() const
909 {
910 return GetResourceRef(model_, Model::GetTypeStatic());
911 }
912
ReleaseShape()913 void CollisionShape::ReleaseShape()
914 {
915 btCompoundShape* compound = GetParentCompoundShape();
916 if (shape_ && compound)
917 {
918 compound->removeChildShape(shape_.Get());
919 rigidBody_->UpdateMass();
920 }
921
922 shape_.Reset();
923
924 geometry_.Reset();
925
926 if (physicsWorld_)
927 physicsWorld_->CleanupGeometryCache();
928 }
929
OnNodeSet(Node * node)930 void CollisionShape::OnNodeSet(Node* node)
931 {
932 if (node)
933 {
934 node->AddListener(this);
935 cachedWorldScale_ = node->GetWorldScale();
936
937 // Terrain collision shape depends on the terrain component's geometry updates. Subscribe to them
938 SubscribeToEvent(node, E_TERRAINCREATED, URHO3D_HANDLER(CollisionShape, HandleTerrainCreated));
939 }
940 }
941
OnSceneSet(Scene * scene)942 void CollisionShape::OnSceneSet(Scene* scene)
943 {
944 if (scene)
945 {
946 if (scene == node_)
947 URHO3D_LOGWARNING(GetTypeName() + " should not be created to the root scene node");
948
949 physicsWorld_ = scene->GetOrCreateComponent<PhysicsWorld>();
950 physicsWorld_->AddCollisionShape(this);
951
952 // Create shape now if necessary (attributes modified before adding to scene)
953 if (retryCreation_)
954 {
955 UpdateShape();
956 NotifyRigidBody();
957 }
958 }
959 else
960 {
961 ReleaseShape();
962
963 if (physicsWorld_)
964 physicsWorld_->RemoveCollisionShape(this);
965
966 // Recreate when moved to a scene again
967 retryCreation_ = true;
968 }
969 }
970
OnMarkedDirty(Node * node)971 void CollisionShape::OnMarkedDirty(Node* node)
972 {
973 Vector3 newWorldScale = node_->GetWorldScale();
974 if (HasWorldScaleChanged(cachedWorldScale_, newWorldScale) && shape_)
975 {
976 // Physics operations are not safe from worker threads
977 Scene* scene = GetScene();
978 if (scene && scene->IsThreadedUpdate())
979 {
980 scene->DelayedMarkedDirty(this);
981 return;
982 }
983
984 switch (shapeType_)
985 {
986 case SHAPE_BOX:
987 case SHAPE_SPHERE:
988 case SHAPE_CYLINDER:
989 case SHAPE_CAPSULE:
990 case SHAPE_CONE:
991 shape_->setLocalScaling(ToBtVector3(newWorldScale));
992 break;
993
994 case SHAPE_TRIANGLEMESH:
995 case SHAPE_CONVEXHULL:
996 shape_->setLocalScaling(ToBtVector3(newWorldScale * size_));
997 break;
998
999 case SHAPE_TERRAIN:
1000 {
1001 HeightfieldData* heightfield = static_cast<HeightfieldData*>(geometry_.Get());
1002 shape_->setLocalScaling(ToBtVector3(Vector3(heightfield->spacing_.x_, 1.0f, heightfield->spacing_.z_) *
1003 newWorldScale * size_));
1004 }
1005 break;
1006
1007 default:
1008 break;
1009 }
1010
1011 NotifyRigidBody();
1012
1013 cachedWorldScale_ = newWorldScale;
1014 }
1015 }
1016
GetParentCompoundShape()1017 btCompoundShape* CollisionShape::GetParentCompoundShape()
1018 {
1019 if (!rigidBody_)
1020 rigidBody_ = GetComponent<RigidBody>();
1021
1022 return rigidBody_ ? rigidBody_->GetCompoundShape() : 0;
1023 }
1024
UpdateShape()1025 void CollisionShape::UpdateShape()
1026 {
1027 URHO3D_PROFILE(UpdateCollisionShape);
1028
1029 ReleaseShape();
1030
1031 // If no physics world available now mark for retry later
1032 if (!physicsWorld_)
1033 {
1034 retryCreation_ = true;
1035 return;
1036 }
1037
1038 if (node_)
1039 {
1040 Scene* scene = GetScene();
1041 Vector3 newWorldScale = node_->GetWorldScale();
1042
1043 switch (shapeType_)
1044 {
1045 case SHAPE_BOX:
1046 shape_ = new btBoxShape(ToBtVector3(size_ * 0.5f));
1047 shape_->setLocalScaling(ToBtVector3(newWorldScale));
1048 break;
1049
1050 case SHAPE_SPHERE:
1051 shape_ = new btSphereShape(size_.x_ * 0.5f);
1052 shape_->setLocalScaling(ToBtVector3(newWorldScale));
1053 break;
1054
1055 case SHAPE_STATICPLANE:
1056 shape_ = new btStaticPlaneShape(btVector3(0.0f, 1.0f, 0.0f), 0.0f);
1057 break;
1058
1059 case SHAPE_CYLINDER:
1060 shape_ = new btCylinderShape(btVector3(size_.x_ * 0.5f, size_.y_ * 0.5f, size_.x_ * 0.5f));
1061 shape_->setLocalScaling(ToBtVector3(newWorldScale));
1062 break;
1063
1064 case SHAPE_CAPSULE:
1065 shape_ = new btCapsuleShape(size_.x_ * 0.5f, Max(size_.y_ - size_.x_, 0.0f));
1066 shape_->setLocalScaling(ToBtVector3(newWorldScale));
1067 break;
1068
1069 case SHAPE_CONE:
1070 shape_ = new btConeShape(size_.x_ * 0.5f, size_.y_);
1071 shape_->setLocalScaling(ToBtVector3(newWorldScale));
1072 break;
1073
1074 case SHAPE_TRIANGLEMESH:
1075 size_ = size_.Abs();
1076 if (customGeometryID_ && scene)
1077 {
1078 CustomGeometry* custom = dynamic_cast<CustomGeometry*>(scene->GetComponent(customGeometryID_));
1079 if (custom)
1080 {
1081 geometry_ = new TriangleMeshData(custom);
1082 TriangleMeshData* triMesh = static_cast<TriangleMeshData*>(geometry_.Get());
1083 shape_ = new btScaledBvhTriangleMeshShape(triMesh->shape_.Get(), ToBtVector3(newWorldScale * size_));
1084 }
1085 else
1086 URHO3D_LOGWARNING("Could not find custom geometry component ID " + String(customGeometryID_) +
1087 " for triangle mesh shape creation");
1088 }
1089 else if (model_ && model_->GetNumGeometries())
1090 {
1091 // Check the geometry cache
1092 Pair<Model*, unsigned> id = MakePair(model_.Get(), lodLevel_);
1093 HashMap<Pair<Model*, unsigned>, SharedPtr<CollisionGeometryData> >& cache = physicsWorld_->GetTriMeshCache();
1094 HashMap<Pair<Model*, unsigned>, SharedPtr<CollisionGeometryData> >::Iterator j = cache.Find(id);
1095 if (j != cache.End())
1096 geometry_ = j->second_;
1097 else
1098 {
1099 geometry_ = new TriangleMeshData(model_, lodLevel_);
1100 // Check if model has dynamic buffers, do not cache in that case
1101 if (!HasDynamicBuffers(model_, lodLevel_))
1102 cache[id] = geometry_;
1103 }
1104
1105 TriangleMeshData* triMesh = static_cast<TriangleMeshData*>(geometry_.Get());
1106 shape_ = new btScaledBvhTriangleMeshShape(triMesh->shape_.Get(), ToBtVector3(newWorldScale * size_));
1107 // Watch for live reloads of the collision model to reload the geometry if necessary
1108 SubscribeToEvent(model_, E_RELOADFINISHED, URHO3D_HANDLER(CollisionShape, HandleModelReloadFinished));
1109 }
1110 break;
1111
1112 case SHAPE_CONVEXHULL:
1113 size_ = size_.Abs();
1114 if (customGeometryID_ && scene)
1115 {
1116 CustomGeometry* custom = dynamic_cast<CustomGeometry*>(scene->GetComponent(customGeometryID_));
1117 if (custom)
1118 {
1119 geometry_ = new ConvexData(custom);
1120 ConvexData* convex = static_cast<ConvexData*>(geometry_.Get());
1121 shape_ = new btConvexHullShape((btScalar*)convex->vertexData_.Get(), convex->vertexCount_, sizeof(Vector3));
1122 shape_->setLocalScaling(ToBtVector3(newWorldScale * size_));
1123 }
1124 else
1125 URHO3D_LOGWARNING("Could not find custom geometry component ID " + String(customGeometryID_) +
1126 " for convex shape creation");
1127 }
1128 else if (model_ && model_->GetNumGeometries())
1129 {
1130 // Check the geometry cache
1131 Pair<Model*, unsigned> id = MakePair(model_.Get(), lodLevel_);
1132 HashMap<Pair<Model*, unsigned>, SharedPtr<CollisionGeometryData> >& cache = physicsWorld_->GetConvexCache();
1133 HashMap<Pair<Model*, unsigned>, SharedPtr<CollisionGeometryData> >::Iterator j = cache.Find(id);
1134 if (j != cache.End())
1135 geometry_ = j->second_;
1136 else
1137 {
1138 geometry_ = new ConvexData(model_, lodLevel_);
1139 // Check if model has dynamic buffers, do not cache in that case
1140 if (!HasDynamicBuffers(model_, lodLevel_))
1141 cache[id] = geometry_;
1142 }
1143
1144 ConvexData* convex = static_cast<ConvexData*>(geometry_.Get());
1145 shape_ = new btConvexHullShape((btScalar*)convex->vertexData_.Get(), convex->vertexCount_, sizeof(Vector3));
1146 shape_->setLocalScaling(ToBtVector3(newWorldScale * size_));
1147 SubscribeToEvent(model_, E_RELOADFINISHED, URHO3D_HANDLER(CollisionShape, HandleModelReloadFinished));
1148 }
1149 break;
1150
1151 case SHAPE_TERRAIN:
1152 size_ = size_.Abs();
1153 {
1154 Terrain* terrain = GetComponent<Terrain>();
1155 if (terrain && terrain->GetHeightData())
1156 {
1157 geometry_ = new HeightfieldData(terrain, lodLevel_);
1158 HeightfieldData* heightfield = static_cast<HeightfieldData*>(geometry_.Get());
1159
1160 shape_ =
1161 new btHeightfieldTerrainShape(heightfield->size_.x_, heightfield->size_.y_, heightfield->heightData_.Get(),
1162 1.0f, heightfield->minHeight_, heightfield->maxHeight_, 1, PHY_FLOAT, false);
1163 shape_->setLocalScaling(
1164 ToBtVector3(Vector3(heightfield->spacing_.x_, 1.0f, heightfield->spacing_.z_) * newWorldScale * size_));
1165 }
1166 }
1167 break;
1168
1169 default:
1170 shape_ = this->UpdateDerivedShape(shapeType_, newWorldScale);
1171 break;
1172 }
1173
1174 if (shape_)
1175 {
1176 shape_->setUserPointer(this);
1177 shape_->setMargin(margin_);
1178 }
1179
1180 cachedWorldScale_ = newWorldScale;
1181 }
1182
1183 if (physicsWorld_)
1184 physicsWorld_->CleanupGeometryCache();
1185
1186 recreateShape_ = false;
1187 retryCreation_ = false;
1188 }
1189
UpdateDerivedShape(int shapeType,const Vector3 & newWorldScale)1190 btCollisionShape* CollisionShape::UpdateDerivedShape(int shapeType, const Vector3& newWorldScale)
1191 {
1192 // To be overridden in derived classes.
1193 return 0;
1194 }
1195
1196
HandleTerrainCreated(StringHash eventType,VariantMap & eventData)1197 void CollisionShape::HandleTerrainCreated(StringHash eventType, VariantMap& eventData)
1198 {
1199 if (shapeType_ == SHAPE_TERRAIN)
1200 {
1201 UpdateShape();
1202 NotifyRigidBody();
1203 }
1204 }
1205
HandleModelReloadFinished(StringHash eventType,VariantMap & eventData)1206 void CollisionShape::HandleModelReloadFinished(StringHash eventType, VariantMap& eventData)
1207 {
1208 if (physicsWorld_)
1209 physicsWorld_->RemoveCachedGeometry(model_);
1210 if (shapeType_ == SHAPE_TRIANGLEMESH || shapeType_ == SHAPE_CONVEXHULL)
1211 {
1212 UpdateShape();
1213 NotifyRigidBody();
1214 }
1215 }
1216
1217 }
1218