OpenGothic
Open source reimplementation of Gothic I and II
Loading...
Searching...
No Matches
dynamicworld.cpp
Go to the documentation of this file.
1#include "dynamicworld.h"
2
3#include "collisionworld.h"
4#include "physicmeshshape.h"
5#include "physicvbo.h"
7
8#include <algorithm>
9#include <cmath>
10#include <cassert>
11
13#include "world/objects/item.h"
14#include "world/bullet.h"
15#include "world/world.h"
16
17const float DynamicWorld::ghostPadding=50-22.5f;
18const float DynamicWorld::ghostHeight =140;
19const float DynamicWorld::worldHeight =20000;
20
21struct DynamicWorld::HumShape:btCapsuleShape {
22 HumShape(btScalar radius, btScalar height):btCapsuleShape(
23 CollisionWorld::toMeters(height<=0.f ? 0.f : radius),
24 CollisionWorld::toMeters(height)) {}
25
26 // "human" object mush have identyty scale/rotation matrix. Only translation allowed.
27 void getAabb(const btTransform& t, btVector3& aabbMin, btVector3& aabbMax) const override {
28 const btScalar rad = getRadius();
29 btVector3 extent(rad,rad,rad);
30 extent[m_upAxis] = rad + getHalfHeight();
31 btVector3 center = t.getOrigin();
32
33 aabbMin = center - extent;
34 aabbMax = center + extent;
35 }
36 };
37
38struct DynamicWorld::NpcBody : btRigidBody {
39 NpcBody(btCollisionShape* shape):btRigidBody(0,nullptr,shape){}
41 delete m_collisionShape;
42 }
43
44 Tempest::Vec3 pos={};
45 float r=0, h=0, rX=0, rZ=0;
46 bool enable=true;
47 size_t frozen=size_t(-1);
48 uint64_t lastMove=0;
49
51 return reinterpret_cast<Npc*>(getUserPointer());
52 }
53
54 void setPosition(const Tempest::Vec3& p) {
55 auto m = CollisionWorld::toMeters(p+Tempest::Vec3(0,(h-r-ghostPadding)*0.5f+r+ghostPadding,0));
56 pos = p;
57 btTransform trans;
58 trans.setIdentity();
59 trans.setOrigin(m);
60 setWorldTransform(trans);
61 }
62 };
63
65 struct Record final {
66 NpcBody* body = nullptr;
67 float x = 0.f;
68 };
69
71 body .reserve(1024);
72 frozen.reserve(1024);
73 }
74
75 NpcBody* create(const Tempest::Vec3 &min, const Tempest::Vec3 &max) {
76 static const float dimMax = 45.f;
77
78 float dx = max.x-min.x;
79 float dz = max.z-min.z;
80 float dim = (dx+dz)*0.5f; // npc-to-landscape collision size
81 float height = max.y-min.y;
82
83 if(dim>dimMax)
84 dim = dimMax;
85
86 btCollisionShape* shape = new HumShape(dim*0.5f, std::max(height-ghostPadding,0.f)*0.5f);
87 NpcBody* obj = new NpcBody(shape);
88
89 btTransform trans;
90 trans.setIdentity();
91 obj->setWorldTransform(trans);
92 obj->setUserIndex(C_Ghost);
93 obj->setCollisionFlags(btCollisionObject::CF_NO_CONTACT_RESPONSE);
94
95 add(obj);
96 resize(*obj,height,dx,dz);
97 return obj;
98 }
99
100 void add(NpcBody* b){
101 Record r;
102 r.body = b;
103 r.x = b->pos.x;
104 body.push_back(r);
105 }
106
107 bool del(void* b){
108 if(del(b,body))
109 return true;
110 if(del(b,frozen)){
111 srt=false;
112 return true;
113 }
114 return false;
115 }
116
117 bool del(void* b,std::vector<Record>& arr){
118 for(size_t i=0;i<arr.size();++i){
119 if(arr[i].body!=b)
120 continue;
121 arr[i]=arr.back();
122 arr.pop_back();
123 return true;
124 }
125 return false;
126 }
127
128 bool delMisordered(NpcBody* b,std::vector<Record>& arr){
129 auto& fr = arr[b->frozen];
130 const float x = fr.x;
131 if((b->frozen==0 || arr[b->frozen-1].x<x) &&
132 (b->frozen+1==arr.size() || x<arr[b->frozen+1].x)) {
133 fr.x = fr.body->pos.x;
134 return false;
135 } else {
136 fr.body = nullptr;
137 return true;
138 }
139 }
140
141 void resize(NpcBody& n, float h, float dx, float dz){
142 n.rX = dx;
143 n.rZ = dz;
144
145 // n.r = (dx+dz)*0.25f;
146 n.r = std::max((dx+dz)*0.5f, dz)*0.5f;
147 n.h = h;
148
149 maxR = std::max(maxR,n.r);
150 }
151
152 void onMove(NpcBody& n){
153 if(n.frozen!=size_t(-1)) {
154 if(delMisordered(&n,frozen)){
155 n.lastMove = tick;
156 n.frozen = size_t(-1);
157
158 Record r;
159 r.body = &n;
160 body.push_back(r);
161 }
162 } else {
163 n.lastMove = tick;
164 }
165 }
166
167 bool rayTest(NpcBody& npc, const Tempest::Vec3& s, const Tempest::Vec3& e, float extR, float& proj) {
168 if(!npc.enable)
169 return false;
170 auto ln = e - s;
171 auto at = npc.pos - s;
172
173 float lenL = ln.length();
174
175 float dot = Tempest::Vec3::dotProduct(ln,at);
176
177 proj = dot/(lenL<=0 ? 1.f : (lenL*lenL));
178 proj = std::max(0.f,std::min(proj,1.f));
179
180 // TODO: ray-box intersection
181 auto& tr = npc.getWorldTransform();
182 auto pos = CollisionWorld::toCentimeters(tr.getOrigin());
183
184 auto nr = ln*proj + s;
185 auto dp = nr - pos;
186 float R = 0.5f*(npc.rX + npc.rZ) + extR;
187 if(dp.x*dp.x+dp.z*dp.z > R*R)
188 return false;
189 if(npc.h<abs(dp.y))
190 return false;
191 return true;
192 }
193
194 NpcBody* rayTest(const Tempest::Vec3& s, const Tempest::Vec3& e, float extR, const Npc* except) {
195 NpcBody* ret = nullptr;
196 float minProj = 2;
197
198 for(auto i:body) {
199 float proj = 0;
200 if(i.body->toNpc()!=except && rayTest(*i.body, s, e, extR, proj)) {
201 if(proj<minProj) {
202 ret = i.body;
203 minProj = proj;
204 }
205 }
206 }
207 for(auto i:frozen) {
208 float proj = 0;
209 if(i.body!=nullptr && i.body->toNpc()!=except && rayTest(*i.body, s, e, extR, proj)) {
210 if(proj<minProj) {
211 ret = i.body;
212 minProj = proj;
213 }
214 }
215 }
216 return ret;
217 }
218
219 bool hasCollision(const DynamicWorld::NpcItem& obj, Tempest::Vec3& normal, Npc*& npc) {
220 static bool disable=false;
221 if(disable)
222 return false;
223
224 const NpcBody* pn = dynamic_cast<const NpcBody*>(obj.obj);
225 if(pn==nullptr)
226 return false;
227 const NpcBody& n = *pn;
228
229 if(srt){
230 if(hasCollision(n,frozen,normal,npc,true))
231 return true;
232 return hasCollision(n,body,normal,npc,false);
233 } else {
234 if(hasCollision(n,body,normal,npc,false))
235 return true;
236 //adjustSort();
237 return hasCollision(n,frozen,normal,npc,false);
238 }
239 }
240
241 bool hasCollision(const NpcBody& n, const std::vector<Record>& arr, Tempest::Vec3& normal, Npc*& npc, bool sorted) {
242 auto l = arr.begin();
243 auto r = arr.end();
244
245 if(sorted) {
246 const float dX = maxR+n.r;
247 l = std::lower_bound(arr.begin(),arr.end(),n.pos.x-dX,[](const Record& b,float x){ return b.x<x; });
248 r = std::upper_bound(arr.begin(),arr.end(),n.pos.x+dX,[](float x,const Record& b){ return x<b.x; });
249 }
250
251 const auto dist = std::distance(l,r);
252 if(dist<=1)
253 return false;
254
255 bool ret=false;
256 for(;l!=r;++l){
257 auto& v = (*l);
258 if(v.body!=nullptr && v.body->enable && hasCollision(n,*v.body,normal)) {
259 npc = v.body->toNpc();
260 ret = true;
261 }
262 }
263 return ret;
264 }
265
266 bool hasCollision(const NpcBody& a, const NpcBody& b, Tempest::Vec3& normal){
267 if(&a==&b)
268 return false;
269 auto dx = a.pos.x-b.pos.x, dy = a.pos.y-b.pos.y, dz = a.pos.z-b.pos.z;
270 auto r = a.r+b.r;
271
272 if(dx*dx+dz*dz>r*r)
273 return false;
274 if(dy>b.h || dy<-a.h)
275 return false;
276
277 normal.x += dx;
278 normal.y += dy;
279 normal.z += dz;
280 return true;
281 }
282
283 void adjustSort() {
284 srt=true;
285 std::sort(frozen.begin(),frozen.end(),[](Record& a,Record& b){
286 return a.x < b.x;
287 });
288 for(size_t i=0; i<frozen.size(); ++i)
289 frozen[i].body->frozen = i;
290 }
291
292 void tickAabbs() {
293 for(size_t i=0;i<body.size();) {
294 if(body[i].body->lastMove!=tick){
295 auto b = body[i];
296 body[i]=body.back();
297 body.pop_back();
298
299 b.x = b.body->pos.x;
300 frozen.push_back(b);
301 } else {
302 ++i;
303 }
304 }
305
306 for(size_t i=0;i<frozen.size();) {
307 if(frozen[i].body==nullptr) {
308 frozen[i]=frozen.back();
309 frozen.pop_back();
310 }
311 else if(frozen[i].body->lastMove==tick){
312 frozen[i].body->frozen=size_t(-1);
313 body.push_back(frozen[i]);
314 frozen[i]=frozen.back();
315 frozen.pop_back();
316 }
317 else {
318 ++i;
319 }
320 }
321
322 adjustSort();
323 tick++;
324 }
325
327 std::vector<Record> body, frozen;
328 bool srt=false;
329 uint64_t tick=0;
330 float maxR=0;
331 };
332
336
338 BulletBody b(&wrld,cb);
339 body.push_front(std::move(b));
340 return &body.front();
341 }
342
343 void del(BulletBody* b) {
344 for(auto i=body.begin(), e=body.end();i!=e;++i){
345 if(&(*i)==b) {
346 body.erase(i);
347 return;
348 }
349 }
350 }
351
352 void tick(uint64_t dt) {
353 for(auto& i:body) {
354 wrld.moveBullet(i,i.dir,dt);
355 if(i.cb!=nullptr)
356 i.cb->onMove();
357 }
358 }
359
360 void onMoveNpc(NpcBody& npc, NpcBodyList& list){
361 for(auto& i:body) {
362 float proj = 0;
363 if(i.cb!=nullptr && list.rayTest(npc,i.lastPos,i.pos,i.tgRange,proj)) {
364 i.cb->onCollide(*npc.toNpc());
365 }
366 }
367 }
368
369 std::list<BulletBody> body;
371 };
372
376
377 void add(BBoxBody* b) {
378 body.emplace_back(b);
379 }
380
381 void del(BBoxBody* b) {
382 for(auto i=body.begin(), e=body.end();i!=e;++i){
383 if(*i==b) {
384 body.erase(i);
385 return;
386 }
387 }
388 }
389
390 BBoxBody* rayTest(const btVector3& s, const btVector3& e) {
391 struct CallBack:btCollisionWorld::ClosestRayResultCallback {
392 using ClosestRayResultCallback::ClosestRayResultCallback;
393 };
394 CallBack callback{s,e};
395
396 btTransform rayFromTrans,rayToTrans;
397 rayFromTrans.setIdentity();
398 rayFromTrans.setOrigin(s);
399 rayToTrans.setIdentity();
400 rayToTrans.setOrigin(e);
401 for(auto& i:body)
402 if(rayTestSingle(rayFromTrans, rayToTrans, *i, callback))
403 return i;
404 return nullptr;
405 }
406
407 bool rayTestSingle(const btTransform& s,
408 const btTransform& e, BBoxBody& npc,
409 btCollisionWorld::RayResultCallback& callback){
410 wrld.world->rayTestSingle(s, e, npc.obj,
411 npc.shape,
412 npc.obj->getWorldTransform(),
413 callback);
414 return callback.hasHit();
415 }
416
417 std::vector<BBoxBody*> body;
419 };
420
421DynamicWorld::DynamicWorld(World& owner,const zenkit::Mesh& worldMesh) {
422 world.reset(new CollisionWorld());
423
424 {
425 PackedMesh pkg(worldMesh,PackedMesh::PK_Physic);
426 sectors.resize(pkg.subMeshes.size());
427 for(size_t i=0;i<sectors.size();++i)
428 sectors[i] = pkg.subMeshes[i].material.name;
429
430 landVbo.resize(pkg.vertices.size());
431 for(size_t i=0;i<pkg.vertices.size();++i) {
432 auto v = pkg.vertices[i];
433 landVbo[i] = CollisionWorld::toMeters(Tempest::Vec3(v.pos[0],v.pos[1],v.pos[2]));
434 }
435
436 landMesh .reset(new PhysicVbo(&landVbo));
437 waterMesh.reset(new PhysicVbo(&landVbo));
438
439 for(size_t i=0;i<pkg.subMeshes.size();++i) {
440 auto& sm = pkg.subMeshes[i];
441 if(!sm.material.disable_collision && sm.iboLength>0) {
442 if(sm.material.group==zenkit::MaterialGroup::WATER) {
443 waterMesh->addIndex(pkg.indices,sm.iboOffset,sm.iboLength,sm.material.group);
444 } else {
445 landMesh ->addIndex(pkg.indices,sm.iboOffset,sm.iboLength,sm.material.group,sectors[i].c_str());
446 }
447 }
448 }
449 }
450
451 btVector3 bbox[2] = {btVector3(0,0,0), btVector3(0,0,0)};
452 if(!landMesh->isEmpty()) {
453 Tempest::Matrix4x4 mt;
454 mt.identity();
455 landShape.reset(new btMultimaterialTriangleMeshShape(landMesh.get(),landMesh->useQuantization(),true));
456 landBody = world->addCollisionBody(*landShape,mt,DynamicWorld::materialFriction(zenkit::MaterialGroup::NONE));
457 landBody->setUserIndex(C_Landscape);
458
459 btVector3 b[2] = {btVector3(0,0,0), btVector3(0,0,0)};
460 landBody->getAabb(b[0],b[1]);
461 bbox[0].setMin(b[0]);
462 bbox[1].setMax(b[1]);
463 }
464
465 if(!waterMesh->isEmpty()) {
466 Tempest::Matrix4x4 mt;
467 mt.identity();
468 waterShape.reset(new btMultimaterialTriangleMeshShape(waterMesh.get(),waterMesh->useQuantization(),true));
469 waterBody = world->addCollisionBody(*waterShape,mt,0);
470 waterBody->setUserIndex(C_Water);
471 waterBody->setCollisionFlags(btCollisionObject::CF_STATIC_OBJECT | btCollisionObject::CF_NO_CONTACT_RESPONSE);
472 // waterBody->setCollisionFlags(btCollisionObject::CO_HF_FLUID);
473
474 btVector3 b[2] = {btVector3(0,0,0), btVector3(0,0,0)};
475 landBody->getAabb(b[0],b[1]);
476 bbox[0].setMin(b[0]);
477 bbox[1].setMax(b[1]);
478 }
479
480 world->setBBox(bbox[0],bbox[1]);
481 npcList .reset(new NpcBodyList(*this));
482 bulletList.reset(new BulletsList(*this));
483 bboxList .reset(new BBoxList (*this));
484
485 world->setItemHitCallback([&](::Item& itm, zenkit::MaterialGroup mat, float impulse, float mass) {
486 auto snd = owner.addLandHitEffect(ItemMaterial(itm.handle().material),mat,itm.transform());
487 float v = impulse/mass;
488 float vol = snd.volume()*std::min(v/10.f,1.f);
489 snd.setVolume(vol);
490 snd.play();
491 });
492 }
493
496
497DynamicWorld::RayLandResult DynamicWorld::landRay(const Tempest::Vec3& from, float maxDy) const {
498 world->updateAabbs();
499 if(maxDy==0)
500 maxDy = worldHeight;
501 return ray(Tempest::Vec3(from.x,from.y+ghostPadding,from.z), Tempest::Vec3(from.x,from.y-maxDy,from.z));
502 }
503
504DynamicWorld::RayWaterResult DynamicWorld::waterRay(const Tempest::Vec3& from) const {
505 world->updateAabbs();
506 return implWaterRay(from, Tempest::Vec3(from.x,from.y+worldHeight,from.z));
507 }
508
509DynamicWorld::RayWaterResult DynamicWorld::waterRay(const Tempest::Vec3& from, const Tempest::Vec3& to) const {
510 world->updateAabbs();
511 return implWaterRay(from, to);
512 }
513
514DynamicWorld::RayWaterResult DynamicWorld::implWaterRay(const Tempest::Vec3& from, const Tempest::Vec3& to) const {
515 struct CallBack:btCollisionWorld::ClosestRayResultCallback {
516 using ClosestRayResultCallback::ClosestRayResultCallback;
517
518 btScalar addSingleResult(btCollisionWorld::LocalRayResult& rayResult, bool normalInWorldSpace) override {
519 return ClosestRayResultCallback::addSingleResult(rayResult,normalInWorldSpace);
520 }
521 };
522
523 CallBack callback{CollisionWorld::toMeters(from), CollisionWorld::toMeters(to)};
524 callback.m_flags = btTriangleRaycastCallback::kF_KeepUnflippedNormal | btTriangleRaycastCallback::kF_FilterBackfaces;
525
526 if(waterBody!=nullptr) {
527 btTransform rayFromTrans,rayToTrans;
528 rayFromTrans.setIdentity();
529 rayFromTrans.setOrigin(callback.m_rayFromWorld);
530 rayToTrans.setIdentity();
531 rayToTrans.setOrigin(callback.m_rayToWorld);
532 world->rayTestSingle(rayFromTrans, rayToTrans,
533 waterBody.get(),
534 waterBody->getCollisionShape(),
535 waterBody->getWorldTransform(),
536 callback);
537 }
538
539 RayWaterResult ret;
540 if(callback.hasHit()) {
541 float waterY = callback.m_hitPointWorld.y()*100.f;
542 auto cave = ray(from,Tempest::Vec3(to.x,waterY,to.z));
543 if(cave.hasCol && cave.v.y<waterY) {
544 ret.wdepth = from.y-worldHeight;
545 ret.hasCol = false;
546 } else {
547 ret.wdepth = waterY;
548 ret.hasCol = true;
549 }
550 return ret;
551 }
552
553 ret.wdepth = from.y-worldHeight;
554 ret.hasCol = false;
555 return ret;
556 }
557
558DynamicWorld::RayCamResult DynamicWorld::cameraRay(const Tempest::Vec3& from, const Tempest::Vec3& to) const {
559 struct CallBack:btCollisionWorld::AllHitsRayResultCallback {
560 using AllHitsRayResultCallback::AllHitsRayResultCallback;
561
562 btScalar addSingleResult(btCollisionWorld::LocalRayResult& rayResult, bool normalInWorldSpace) override {
563 hits++;
564 return AllHitsRayResultCallback::addSingleResult(rayResult,normalInWorldSpace);
565 }
566
567 uint32_t hits = 0;
568 };
569
570 CallBack callback{CollisionWorld::toMeters(from), CollisionWorld::toMeters(to)};
571 callback.m_flags = btTriangleRaycastCallback::kF_KeepUnflippedNormal | btTriangleRaycastCallback::kF_FilterBackfaces;
572
573 if(waterBody!=nullptr) {
574 btTransform rayFromTrans,rayToTrans;
575 rayFromTrans.setIdentity();
576 rayFromTrans.setOrigin(callback.m_rayFromWorld);
577 rayToTrans.setIdentity();
578 rayToTrans.setOrigin(callback.m_rayToWorld);
579 world->rayTestSingle(rayFromTrans, rayToTrans,
580 waterBody.get(),
581 waterBody->getCollisionShape(),
582 waterBody->getWorldTransform(),
583 callback);
584 }
585
586 RayCamResult ret;
587 ret.waterCol = int(callback.m_collisionObjects.size());
588 return ret;
589 }
590
591DynamicWorld::RayLandResult DynamicWorld::ray(const Tempest::Vec3& from, const Tempest::Vec3& to) const {
592 struct CallBack:btCollisionWorld::ClosestRayResultCallback {
593 using ClosestRayResultCallback::ClosestRayResultCallback;
594 zenkit::MaterialGroup matId = zenkit::MaterialGroup::UNDEFINED;
595 const char* sector = nullptr;
596 Category colCat = C_Null;
597
598 bool needsCollision(btBroadphaseProxy* proxy0) const override {
599 auto obj=reinterpret_cast<btCollisionObject*>(proxy0->m_clientObject);
600 if(obj->getUserIndex()==C_Landscape || obj->getUserIndex()==C_Object)
601 return ClosestRayResultCallback::needsCollision(proxy0);
602 return false;
603 }
604
605 btScalar addSingleResult(btCollisionWorld::LocalRayResult& rayResult, bool normalInWorldSpace) override {
606 auto shape = rayResult.m_collisionObject->getCollisionShape();
607 if(shape!=nullptr) {
608 auto s = reinterpret_cast<const btMultimaterialTriangleMeshShape*>(shape);
609 auto mt = reinterpret_cast<const PhysicVbo*>(s->getMeshInterface());
610
611 size_t id = size_t(rayResult.m_localShapeInfo->m_shapePart);
612 matId = mt->materialId(id);
613 sector = mt->sectorName(id);
614 }
615 colCat = Category(rayResult.m_collisionObject->getUserIndex());
616 return ClosestRayResultCallback::addSingleResult(rayResult,normalInWorldSpace);
617 }
618 };
619
620 CallBack callback{CollisionWorld::toMeters(from), CollisionWorld::toMeters(to)};
621 callback.m_flags = btTriangleRaycastCallback::kF_KeepUnflippedNormal | btTriangleRaycastCallback::kF_FilterBackfaces;
622
623 world->rayCast(from,to,callback);
624
625 Tempest::Vec3 hitPos = to, hitNorm;
626 if(callback.hasHit()){
627 hitPos = CollisionWorld::toCentimeters(callback.m_hitPointWorld);
628 if(callback.colCat==DynamicWorld::C_Landscape) {
629 hitNorm.x = callback.m_hitNormalWorld.x();
630 hitNorm.y = callback.m_hitNormalWorld.y();
631 hitNorm.z = callback.m_hitNormalWorld.z();
632 }
633 }
634 RayLandResult ret;
635 ret.v = hitPos;
636 ret.n = hitNorm;
637 ret.mat = callback.matId;
638 ret.hasCol = callback.hasHit();
639 ret.hitFraction = callback.m_closestHitFraction;
640 ret.sector = callback.sector;
641 return ret;
642 }
643
644DynamicWorld::RayQueryResult DynamicWorld::rayNpc(const Tempest::Vec3& from, const Tempest::Vec3& to, const Npc* except) const {
646 static_cast<RayLandResult&>(r) = ray(from,to);
647 if(auto ptr = npcList->rayTest(from, (r.hasCol ? r.v : to), 1, except)) {
648 r.npcHit = ptr->toNpc();
649 r.hasCol = true;
650 }
651 return r;
652 }
653
654float DynamicWorld::soundOclusion(const Tempest::Vec3& from, const Tempest::Vec3& to) const {
655 struct CallBack:btCollisionWorld::AllHitsRayResultCallback {
656 using AllHitsRayResultCallback::AllHitsRayResultCallback;
657
658 enum { FRAC_MAX=16 };
659 uint32_t cnt = 0;
660 float frac[FRAC_MAX] = {};
661
662 bool needsCollision(btBroadphaseProxy* proxy0) const override {
663 auto obj=reinterpret_cast<btCollisionObject*>(proxy0->m_clientObject);
664 int id = obj->getUserIndex();
665 if(id==C_Landscape || id==C_Water || id==C_Object)
666 return AllHitsRayResultCallback::needsCollision(proxy0);
667 return false;
668 }
669
670 btScalar addSingleResult(btCollisionWorld::LocalRayResult& rayResult, bool normalInWorldSpace) override {
671 if(cnt<FRAC_MAX)
672 frac[cnt] = rayResult.m_hitFraction;
673 cnt++;
674 return AllHitsRayResultCallback::addSingleResult(rayResult,normalInWorldSpace);
675 }
676 };
677
678 CallBack callback(CollisionWorld::toMeters(from), CollisionWorld::toMeters(to));
679 callback.m_flags = btTriangleRaycastCallback::kF_KeepUnflippedNormal;
680
681 world->rayCast(from,to,callback);
682 if(callback.cnt<2)
683 return 0;
684
685 if(callback.cnt>=CallBack::FRAC_MAX)
686 return 1;
687
688 float fr=0;
689 std::sort(callback.frac,callback.frac+callback.cnt);
690 for(size_t i=1;i<callback.cnt;i+=2) {
691 fr += (callback.frac[i]-callback.frac[i-1]);
692 }
693
694 float tlen = (callback.m_rayFromWorld-callback.m_rayToWorld).length();
695 // let's say: 1.5 meter wall blocks sound completely :)
696 return (tlen*fr)/1.5f;
697 }
698
700 Tempest::Vec3 min={0,0,0}, max={0,0,0};
701 if(auto sk=Resources::loadSkeleton(visual)) {
702 min = sk->bboxCol[0];
703 max = sk->bboxCol[1];
704 }
705 auto obj = npcList->create(min,max);
706 float dim = std::max(obj->rX,obj->rZ);
707 return NpcItem(this,obj,dim*0.5f);
708 }
709
710DynamicWorld::Item DynamicWorld::staticObj(const PhysicMeshShape *shape, const Tempest::Matrix4x4 &m) {
711 if(shape==nullptr)
712 return Item();
713 return createObj(&shape->shape,false,m,0,shape->friction(),IT_Static);
714 }
715
716DynamicWorld::Item DynamicWorld::movableObj(const PhysicMeshShape* shape, const Tempest::Matrix4x4& m) {
717 if(shape==nullptr)
718 return Item();
719 return createObj(&shape->shape,false,m,0,shape->friction(),IT_Movable);
720 }
721
722DynamicWorld::Item DynamicWorld::createObj(btCollisionShape* shape, bool ownShape, const Tempest::Matrix4x4& m, float mass, float friction, ItemType type) {
723 std::unique_ptr<CollisionWorld::CollisionBody> obj;
724 switch(type) {
725 case IT_Movable:
726 case IT_Static:
727 obj = world->addCollisionBody(*shape,m,friction);
728 obj->setUserIndex(C_Object);
729 break;
730 case IT_Dynamic:
731 obj = world->addDynamicBody(*shape,m,friction,mass);
732 obj->setUserIndex(C_Item);
733 break;
734 }
735 return Item(this,obj.release(),ownShape ? shape : nullptr);
736 }
737
738DynamicWorld::Item DynamicWorld::dynamicObj(const Tempest::Matrix4x4& pos, const Bounds& b, zenkit::MaterialGroup mat) {
739 btVector3 hExt = {b.bbox[1].x-b.bbox[0].x, b.bbox[1].y-b.bbox[0].y, b.bbox[1].z-b.bbox[0].z};
740 hExt *= 0.01f;
741
742 float density = DynamicWorld::materialDensity(mat);
743 float mass = density*(hExt[0])*(hExt[1])*(hExt[2]);
744 //mass = 0.1f;
745 for(int i=0;i<3;++i)
746 hExt[i] = std::max(hExt[i]*0.5f,0.15f);
747
748 std::unique_ptr<btCollisionShape> shape { new btBoxShape(hExt) };
749 return createObj(shape.release(),true,pos,mass,materialFriction(mat),IT_Dynamic);
750 }
751
753 return bulletList->add(cb);
754 }
755
756DynamicWorld::BBoxBody DynamicWorld::bboxObj(BBoxCallback* cb, const zenkit::AxisAlignedBoundingBox& bbox) {
757 return BBoxBody(this,cb,bbox);
758 }
759
760DynamicWorld::BBoxBody DynamicWorld::bboxObj(BBoxCallback* cb, const Tempest::Vec3& pos, float R) {
761 return BBoxBody(this,cb,pos,R);
762 }
763
764void DynamicWorld::moveBullet(BulletBody &b, const Tempest::Vec3& dir, uint64_t dt) {
765 const float dtF = float(dt);
766 const bool isSpell = b.isSpell();
767
768 auto pos = b.pos;
769 auto to = pos + dir*dtF - Tempest::Vec3(0,(isSpell ? 0 : gravity*dtF*dtF),0);
770
771 struct CallBack:btCollisionWorld::ClosestRayResultCallback {
772 using ClosestRayResultCallback::ClosestRayResultCallback;
773 zenkit::MaterialGroup matId = zenkit::MaterialGroup::NONE;
774
775 bool needsCollision(btBroadphaseProxy* proxy0) const override {
776 auto obj=reinterpret_cast<btCollisionObject*>(proxy0->m_clientObject);
777 if(obj->getUserIndex()==C_Landscape || obj->getUserIndex()==C_Object)
778 return ClosestRayResultCallback::needsCollision(proxy0);
779 return false;
780 }
781
782 btScalar addSingleResult(btCollisionWorld::LocalRayResult& rayResult, bool normalInWorldSpace) override {
783 auto shape = rayResult.m_collisionObject->getCollisionShape();
784 if(shape) {
785 auto s = reinterpret_cast<const btMultimaterialTriangleMeshShape*>(shape);
786 auto mt = reinterpret_cast<const PhysicVbo*>(s->getMeshInterface());
787
788 size_t id = size_t(rayResult.m_localShapeInfo->m_shapePart);
789 matId = mt->materialId(id);
790 }
791 return ClosestRayResultCallback::addSingleResult(rayResult,normalInWorldSpace);
792 }
793 };
794
796
797 CallBack callback{s,e};
798 callback.m_flags = btTriangleRaycastCallback::kF_KeepUnflippedNormal | btTriangleRaycastCallback::kF_FilterBackfaces;
799
800 if(auto ptr = bboxList->rayTest(s,e)) {
801 if(ptr->cb!=nullptr) {
802 ptr->cb->onCollide(b);
803 }
804 }
805
806 world->rayCast(pos, to, callback);
807
808 bool stopBullet = false;
809 if(callback.matId != zenkit::MaterialGroup::NONE) {
810 if(isSpell){
811 if(b.cb!=nullptr)
812 b.cb->onCollide(callback.matId);
813 stopBullet = true;
814 } else {
815 if(callback.matId==zenkit::MaterialGroup::METAL ||
816 callback.matId==zenkit::MaterialGroup::STONE) {
817 auto d = b.dir;
818 btVector3 m = {d.x,d.y,d.z};
819 btVector3 n = callback.m_hitNormalWorld;
820
821 n.normalize();
822 const float l = b.speed();
823 m/=l;
824
825 btVector3 dir = m - 2*m.dot(n)*n;
826 dir*=(l*0.5f); //slow-down
827
828 float a = callback.m_closestHitFraction;
829 b.move(pos + (to-pos)*a);
830 if(l*a>0.1f) {
831 b.setDirection({dir.x(),dir.y(),dir.z()});
832 b.addPathLen(l*a);
833 b.addHit();
834 if(b.cb!=nullptr)
835 b.cb->onCollide(callback.matId);
836 }
837 } else {
838 float a = callback.m_closestHitFraction;
839 b.move(pos + (to-pos)*a);
840 b.addPathLen((to-pos).length()*a);
841 b.addHit();
842 if(b.cb!=nullptr)
843 b.cb->onCollide(callback.matId);
844 stopBullet = true;
845 }
846 }
847 } else {
848 if(auto ptr = npcList->rayTest(pos,to,b.targetRange(),nullptr)) {
849 if(b.cb!=nullptr)
850 stopBullet |= b.cb->onCollide(*ptr->toNpc()); else
851 stopBullet = true;
852 }
853 const float l = b.speed();
854 auto d = b.direction();
855 if(!isSpell)
856 d.y -= (gravity)*dtF;
857 b.move(to);
858 b.setDirection(d);
859 b.addPathLen(l*dtF);
860 }
861
862 if(stopBullet || b.hitCount()>3 || b.pathLength()>10000) {
863 if(b.cb!=nullptr)
864 b.cb->onStop();
865 }
866 }
867
868void DynamicWorld::tick(uint64_t dt) {
869 npcList ->tickAabbs();
870 bulletList->tick(dt);
871 world ->tick(dt);
872 }
873
875 bulletList->del(obj);
876 }
877
878float DynamicWorld::materialFriction(zenkit::MaterialGroup mat) {
879 // https://www.thoughtspike.com/friction-coefficients-for-bullet-physics/
880 switch(mat) {
881 case zenkit::MaterialGroup::UNDEFINED:
882 return 0.5f;
883 case zenkit::MaterialGroup::METAL:
884 return 1.1f;
885 case zenkit::MaterialGroup::STONE:
886 return 0.65f;
887 case zenkit::MaterialGroup::WOOD:
888 return 0.4f;
889 case zenkit::MaterialGroup::EARTH:
890 return 0.4f;
891 case zenkit::MaterialGroup::WATER:
892 return 0.01f;
893 case zenkit::MaterialGroup::SNOW:
894 return 0.2f;
895 case zenkit::MaterialGroup::NONE:
896 break;
897 }
898 return 0.75f;
899 }
900
901float DynamicWorld::materialDensity(zenkit::MaterialGroup mat) {
902 switch (mat) {
903 case zenkit::MaterialGroup::UNDEFINED:
904 case zenkit::MaterialGroup::NONE:
905 return 2000.0f;
906 case zenkit::MaterialGroup::METAL:
907 return 7800.f;
908 case zenkit::MaterialGroup::STONE:
909 return 2200.f;
910 case zenkit::MaterialGroup::WOOD:
911 return 700.f;
912 case zenkit::MaterialGroup::EARTH:
913 return 1500.f;
914 case zenkit::MaterialGroup::WATER:
915 return 1000.f;
916 case zenkit::MaterialGroup::SNOW:
917 return 1000.f;
918 }
919 return 2000.f;
920 }
921
922std::string_view DynamicWorld::validateSectorName(std::string_view name) const {
923 return landMesh->validateSectorName(name);
924 }
925
926bool DynamicWorld::hasCollision(const NpcItem& it, CollisionTest& out) {
927 if(npcList->hasCollision(it,out.normal,out.npc)){
928 out.normal /= out.normal.length();
929 out.npcCol = true;
930 return true;
931 }
932 return world->hasCollision(*it.obj,out.normal,out.vob);
933 }
934
936 if(owner==nullptr)
937 return;
938 owner->world->touchAabbs();
939 if(!owner->npcList->del(obj))
940 owner->world->removeCollisionObject(obj);
941 }
942
943void DynamicWorld::NpcItem::setPosition(const Tempest::Vec3& pos) {
944 if(obj) {
945 implSetPosition(pos);
946 owner->npcList->onMove(*obj);
947 owner->bulletList->onMoveNpc(*obj,*owner->npcList);
948 }
949 }
950
951void DynamicWorld::NpcItem::implSetPosition(const Tempest::Vec3& pos) {
952 obj->setPosition(pos);
953 }
954
956 if(obj==nullptr || obj->enable==e)
957 return;
958 obj->enable = e;
959 owner->world->touchAabbs();
960 }
961
963 obj->setUserPointer(p);
964 }
965
967 if(obj) {
968 const btTransform& tr = obj->getWorldTransform();
969 return tr.getOrigin().y()*100.f;
970 //return obj->h*0.5f;
971 }
972 return 0;
973 }
974
975const Tempest::Vec3& DynamicWorld::NpcItem::position() const {
976 return obj->pos;
977 }
978
979bool DynamicWorld::NpcItem::testMove(const Tempest::Vec3& to, CollisionTest& out) {
980 if(!obj)
981 return false;
982 return testMove(to,obj->pos,out);
983 }
984
985bool DynamicWorld::NpcItem::testMove(const Tempest::Vec3& to, const Tempest::Vec3& pos0, CollisionTest& out) {
986 if(!obj)
987 return false;
988 auto prev = obj->pos;
989 auto code = implTryMove(to,pos0,out);
990 implSetPosition(prev);
991 return code==MoveCode::MC_OK;
992 }
993
995 // 2-santimeters
996 static const float eps = 2;
997
998 if(!obj)
999 return MoveCode::MC_Fail;
1000
1001 auto dp = to - obj->pos;
1002 if(std::abs(dp.x)<eps && std::abs(dp.y)<eps && std::abs(dp.z)<eps) {
1003 // skip-move
1004 return MoveCode::MC_Skip;
1005 }
1006
1007 auto code = implTryMove(to,obj->pos,out);
1008 switch(code) {
1009 case MoveCode::MC_Fail:
1010 case MoveCode::MC_Skip:
1011 return code;
1013 setPosition(out.partial);
1014 return MoveCode::MC_Partial;
1015 case MoveCode::MC_OK:
1016 owner->npcList->onMove(*obj);
1017 owner->bulletList->onMoveNpc(*obj,*owner->npcList);
1018 return MoveCode::MC_OK;
1019 }
1020 return code;
1021 }
1022
1023DynamicWorld::MoveCode DynamicWorld::NpcItem::implTryMove(const Tempest::Vec3& to, const Tempest::Vec3& pos0, CollisionTest& out) {
1024 auto initial = pos0;
1025 auto r = obj->r;
1026 int count = 1;
1027 auto dp = to-initial;
1028
1029 if((dp.x*dp.x+dp.z*dp.z)>r*r || dp.y>obj->h*0.5f) {
1030 const int countXZ = int(std::ceil(std::sqrt(dp.x*dp.x+dp.z*dp.z)/r));
1031 const int countY = int(std::ceil(std::abs(dp.y)/(obj->h*0.5f)));
1032
1033 count = std::max(countXZ,countY);
1034 }
1035
1036 auto prev = initial;
1037 for(int i=1; i<=count; ++i) {
1038 auto pos = initial+(dp*float(i))/float(count);
1039 implSetPosition(pos);
1040 if(owner->hasCollision(*this,out)) {
1041 if(i>1) {
1042 // moved a bit
1043 out.partial = prev;
1044 return MoveCode::MC_Partial;
1045 }
1046 implSetPosition(initial);
1047 if(owner->hasCollision(*this,out)) {
1048 // was in collision from the start
1049 implSetPosition(to);
1050 return MoveCode::MC_OK;
1051 }
1052 return MoveCode::MC_Fail;
1053 }
1054 }
1055
1056 return MoveCode::MC_OK;
1057 }
1058
1060 if(!obj)
1061 return false;
1062 CollisionTest info;
1063 return owner->hasCollision(*this,info);
1064 }
1065
1067 delete obj;
1068 delete shp;
1069 }
1070
1071void DynamicWorld::Item::setObjMatrix(const Tempest::Matrix4x4 &m) {
1072 if(obj!=nullptr) {
1073 btTransform trans;
1074 trans.setFromOpenGLMatrix(reinterpret_cast<const btScalar*>(&m));
1075 trans.getOrigin()*=0.01f;
1076 if(obj->getWorldTransform()==trans)
1077 return;
1078 obj->setWorldTransform(trans);
1079 //owner->world->touchAabbs(); // TOO SLOW!
1080 owner->world->updateSingleAabb(obj);
1081 }
1082 }
1083
1085 assert(obj->getUserIndex()==DynamicWorld::C_Item);
1086 obj->setUserPointer(it);
1087 obj->setUserIndex2(1);
1088 }
1089
1091 assert(obj->getUserIndex()==DynamicWorld::C_Object);
1092 obj->setUserPointer(it);
1093 obj->setUserIndex2(2);
1094 }
1095
1099
1101 : pos(other.pos), lastPos(other.lastPos),
1102 dir(other.dir), dirL(other.dirL), totalL(other.totalL), spl(other.spl){
1103 std::swap(owner,other.owner);
1104 std::swap(cb,other.cb);
1105 }
1106
1108 spl = s;
1109 }
1110
1112 tgRange = t;
1113 }
1114
1115void DynamicWorld::BulletBody::move(const Tempest::Vec3& to) {
1116 lastPos = pos;
1117 pos = to;
1118 }
1119
1120void DynamicWorld::BulletBody::setPosition(const Tempest::Vec3& p) {
1121 lastPos = p;
1122 pos = p;
1123 }
1124
1125void DynamicWorld::BulletBody::setDirection(const Tempest::Vec3& d) {
1126 dir = d;
1127 dirL = d.length();
1128 }
1129
1131 return totalL;
1132 }
1133
1135 totalL += v;
1136 }
1137
1138Tempest::Matrix4x4 DynamicWorld::BulletBody::matrix() const {
1139 const float dx = dir.x/dirL;
1140 const float dy = dir.y/dirL;
1141 const float dz = dir.z/dirL;
1142
1143 float a2 = std::asin(dy)*float(180/M_PI);
1144 float ang = std::atan2(dz,dx)*float(180/M_PI)+180.f;
1145
1146 Tempest::Matrix4x4 mat;
1147 mat.identity();
1148 mat.translate(pos.x,pos.y,pos.z);
1149 mat.rotateOY(-ang);
1150 mat.rotateOZ(-a2);
1151 return mat;
1152 }
1153
1154DynamicWorld::BBoxBody::BBoxBody(DynamicWorld* ow, BBoxCallback* cb, const zenkit::AxisAlignedBoundingBox& bbox)
1155 : owner(ow), cb(cb) {
1156 btVector3 hExt = CollisionWorld::toMeters(Tempest::Vec3{bbox.max.x-bbox.min.x, bbox.max.y-bbox.min.y, bbox.max.z-bbox.min.z})*0.5f;
1157 btVector3 pos = CollisionWorld::toMeters(Tempest::Vec3{bbox.max.x+bbox.min.x, bbox.max.y+bbox.min.y, bbox.max.z+bbox.min.z})*0.5f;
1158
1159 shape = new btBoxShape(hExt);
1160 obj = new btRigidBody(btRigidBody::btRigidBodyConstructionInfo(0,nullptr,shape));
1161
1162 btTransform trans;
1163 trans.setIdentity();
1164 trans.setOrigin(pos);
1165
1166 obj->setWorldTransform(trans);
1167 obj->setUserIndex(C_Ghost);
1168 obj->setCollisionFlags(btCollisionObject::CF_NO_CONTACT_RESPONSE);
1169
1170 owner->bboxList->add(this);
1171}
1172
1173DynamicWorld::BBoxBody::BBoxBody(DynamicWorld* ow, BBoxCallback* cb, const Tempest::Vec3& p, float R)
1174 : owner(ow), cb(cb) {
1175 btVector3 pos = CollisionWorld::toMeters(p);
1176
1177 shape = new btCapsuleShape(R,4.f);
1178 obj = new btRigidBody(btRigidBody::btRigidBodyConstructionInfo(0,nullptr,shape));
1179
1180 btTransform trans;
1181 trans.setIdentity();
1182 trans.setOrigin(pos);
1183
1184 obj->setWorldTransform(trans);
1185 obj->setUserIndex(C_Ghost);
1186 obj->setCollisionFlags(btCollisionObject::CF_NO_CONTACT_RESPONSE);
1187
1188 owner->bboxList->add(this);
1189 }
1190
1192 :owner(other.owner),cb(other.cb),shape(other.shape),obj(other.obj) {
1193 other.owner = nullptr;
1194 other.cb = nullptr;
1195 other.shape = nullptr;
1196 other.obj = nullptr;
1197
1198 if(owner!=nullptr) {
1199 owner->bboxList->del(&other);
1200 owner->bboxList->add(this);
1201 }
1202 }
1203
1205 if(other.owner!=nullptr)
1206 other.owner->bboxList->del(&other);
1207 if(owner!=nullptr)
1208 owner->bboxList->del(this);
1209
1210 std::swap(owner,other.owner);
1211 std::swap(cb, other.cb);
1212 std::swap(shape,other.shape);
1213 std::swap(obj, other.obj);
1214
1215 if(other.owner!=nullptr)
1216 other.owner->bboxList->add(&other);
1217 if(owner!=nullptr)
1218 owner->bboxList->add(this);
1219
1220 return *this;
1221 }
1222
1224 if(owner==nullptr)
1225 return;
1226 owner->bboxList->del(this);
1227 delete obj;
1228 delete shape;
1229 }
Tempest::Vec3 bbox[2]
Definition bounds.h:22
static const Tempest::Vec3 toCentimeters(const btVector3 &v)
static float toMeters(const float v)
Item movableObj(const PhysicMeshShape *src, const Tempest::Matrix4x4 &m)
RayWaterResult waterRay(const Tempest::Vec3 &from) const
RayCamResult cameraRay(const Tempest::Vec3 &from, const Tempest::Vec3 &to) const
static const float ghostPadding
static constexpr float gravity
static float materialFriction(zenkit::MaterialGroup mat)
BBoxBody bboxObj(BBoxCallback *cb, const zenkit::AxisAlignedBoundingBox &bbox)
void deleteObj(BulletBody *obj)
float soundOclusion(const Tempest::Vec3 &from, const Tempest::Vec3 &to) const
RayLandResult landRay(const Tempest::Vec3 &from, float maxDy=0) const
Item dynamicObj(const Tempest::Matrix4x4 &pos, const Bounds &bbox, zenkit::MaterialGroup mat)
RayLandResult ray(const Tempest::Vec3 &from, const Tempest::Vec3 &to) const
std::string_view validateSectorName(std::string_view name) const
static float materialDensity(zenkit::MaterialGroup mat)
void tick(uint64_t dt)
Item staticObj(const PhysicMeshShape *src, const Tempest::Matrix4x4 &m)
BulletBody * bulletObj(BulletCallback *cb)
DynamicWorld(World &world, const zenkit::Mesh &mesh)
NpcItem ghostObj(std::string_view visual)
RayQueryResult rayNpc(const Tempest::Vec3 &from, const Tempest::Vec3 &to, const Npc *except) const
Definition item.h:14
const zenkit::IItem & handle() const
Definition item.h:83
Definition npc.h:25
std::vector< SubMesh > subMeshes
Definition packedmesh.h:73
std::vector< uint32_t > indices
Definition packedmesh.h:70
std::vector< Vertex > vertices
Definition packedmesh.h:68
float friction() const
static const Skeleton * loadSkeleton(std::string_view name)
auto transform() const -> const Tempest::Matrix4x4 &
Definition vob.h:34
Definition world.h:31
Sound addLandHitEffect(ItemMaterial src, zenkit::MaterialGroup reciver, const Tempest::Matrix4x4 &pos)
Definition world.cpp:752
ItemMaterial
Definition constants.h:242
BBoxBody & operator=(BBoxBody &&other)
BBoxList(DynamicWorld &wrld)
void add(BBoxBody *b)
bool rayTestSingle(const btTransform &s, const btTransform &e, BBoxBody &npc, btCollisionWorld::RayResultCallback &callback)
void del(BBoxBody *b)
BBoxBody * rayTest(const btVector3 &s, const btVector3 &e)
std::vector< BBoxBody * > body
void setPosition(const Tempest::Vec3 &pos)
BulletBody(DynamicWorld *wrld, BulletCallback *cb)
void setTargetRange(float tgRange)
void move(const Tempest::Vec3 &to)
Tempest::Matrix4x4 matrix() const
void setDirection(const Tempest::Vec3 &dir)
void del(BulletBody *b)
void onMoveNpc(NpcBody &npc, NpcBodyList &list)
BulletsList(DynamicWorld &wrld)
BulletBody * add(BulletCallback *cb)
std::list< BulletBody > body
HumShape(btScalar radius, btScalar height)
void getAabb(const btTransform &t, btVector3 &aabbMin, btVector3 &aabbMax) const override
void setInteractive(Interactive *it)
void setItem(::Item *it)
void setObjMatrix(const Tempest::Matrix4x4 &m)
NpcBody * create(const Tempest::Vec3 &min, const Tempest::Vec3 &max)
void resize(NpcBody &n, float h, float dx, float dz)
std::vector< Record > frozen
bool delMisordered(NpcBody *b, std::vector< Record > &arr)
std::vector< Record > body
NpcBodyList(DynamicWorld &wrld)
NpcBody * rayTest(const Tempest::Vec3 &s, const Tempest::Vec3 &e, float extR, const Npc *except)
bool rayTest(NpcBody &npc, const Tempest::Vec3 &s, const Tempest::Vec3 &e, float extR, float &proj)
bool hasCollision(const NpcBody &a, const NpcBody &b, Tempest::Vec3 &normal)
bool del(void *b, std::vector< Record > &arr)
bool hasCollision(const NpcBody &n, const std::vector< Record > &arr, Tempest::Vec3 &normal, Npc *&npc, bool sorted)
bool hasCollision(const DynamicWorld::NpcItem &obj, Tempest::Vec3 &normal, Npc *&npc)
void setPosition(const Tempest::Vec3 &p)
NpcBody(btCollisionShape *shape)
bool testMove(const Tempest::Vec3 &to, CollisionTest &out)
void setPosition(const Tempest::Vec3 &pos)
void setUserPointer(void *p)
const Tempest::Vec3 & position() const
auto tryMove(const Tempest::Vec3 &to, CollisionTest &out) -> DynamicWorld::MoveCode
zenkit::MaterialGroup mat