Line data Source code
1 : /* ragdoll.cpp: ragdoll physics support
2 : *
3 : * Libprimis supports limited client-side ragdolls to simulate corpses. Ragdolls
4 : * are created when actors are killed and support basic physical interaction with
5 : * terrain (but not other players).
6 : *
7 : * This file contains implementation functions for the ragdoll class. For the
8 : * class definition, see ragdoll.h.
9 : */
10 : #include "../libprimis-headers/cube.h"
11 : #include "../../shared/geomexts.h"
12 :
13 : #include <memory>
14 : #include <optional>
15 :
16 : #include "interface/console.h"
17 : #include "interface/control.h"
18 :
19 : #include "render/rendergl.h"
20 :
21 : #include "world/entities.h"
22 : #include "world/octaworld.h"
23 : #include "world/physics.h"
24 : #include "world/bih.h"
25 :
26 : #include "model.h"
27 : #include "ragdoll.h"
28 :
29 : /* ragdollskel */
30 :
31 : //ragdollskel::tri
32 :
33 5 : bool ragdollskel::tri::shareverts(const tri &t) const
34 : {
35 15 : for(int i = 0; i < 3; ++i)
36 : {
37 43 : for(int j = 0; j < 3; ++j)
38 : {
39 33 : if(vert[i] == t.vert[j])
40 : {
41 3 : return true;
42 : }
43 : }
44 : }
45 2 : return false;
46 : }
47 :
48 : //ragdollskel
49 :
50 1 : void ragdollskel::setupjoints()
51 : {
52 4 : for(vert &i : verts)
53 : {
54 3 : i.weight = 0;
55 : }
56 2 : for(joint &j : joints)
57 : {
58 1 : j.weight = 0;
59 1 : vec pos(0, 0, 0);
60 4 : for(int k = 0; k < 3; ++k)
61 : {
62 3 : if(j.vert[k]>=0) //for each valid vertex (not -1) refered to by this joint (up to 3)
63 : {
64 3 : pos.add(verts[j.vert[k]].pos); //j.vert[k].pos is the positon of the vertex pointed to by the joint being used
65 3 : j.weight++;
66 3 : verts[j.vert[k]].weight++;
67 : }
68 : }
69 2 : for(int k = 0; k < j.weight; ++k)
70 : {
71 1 : j.weight = 1/j.weight;
72 : }
73 1 : pos.mul(j.weight);
74 :
75 1 : const tri &t = tris[j.tri];
76 1 : matrix4x3 &m = j.orient;
77 1 : const vec &v1 = verts[t.vert[0]].pos,
78 1 : &v2 = verts[t.vert[1]].pos,
79 1 : &v3 = verts[t.vert[2]].pos;
80 : /*
81 : * /|
82 : * /
83 : * m.c/
84 : * /
85 : * v1 / m.a v2
86 : * *---------->*
87 : * |
88 : * |
89 : * |
90 : * m.b|
91 : * v
92 : *
93 : *
94 : * *
95 : * v3
96 : *
97 : * ----→ ----→
98 : * m.c = v1 v2 x v1 v3
99 : * --→ ----→
100 : * m.b = m.c x v1 v2
101 : *
102 : */
103 1 : m.a = vec(v2).sub(v1).normalize(); //m.a has magnitude 1 at all times
104 1 : m.c.cross(m.a, vec(v3).sub(v1)).normalize(); //m.c will always have a magnitude <=1
105 1 : m.b.cross(m.c, m.a); //m.b will always have a magnitude <= m.c
106 1 : m.d = pos;
107 1 : m.transpose();
108 : }
109 4 : for(vert &i : verts)
110 : {
111 3 : if(i.weight)
112 : {
113 3 : i.weight = 1/i.weight;
114 : }
115 : }
116 1 : reljoints.clear();
117 1 : }
118 :
119 1 : void ragdollskel::setuprotfrictions()
120 : {
121 1 : rotfrictions.clear();
122 3 : for(uint i = 0; i < tris.size(); i++)
123 : {
124 3 : for(uint j = i+1; j < tris.size(); j++)
125 : {
126 1 : if(tris[i].shareverts(tris[j]))
127 : {
128 : rotfriction r;
129 1 : r.tri[0] = i;
130 1 : r.tri[1] = j;
131 1 : rotfrictions.push_back(r);
132 : }
133 : }
134 : }
135 1 : }
136 :
137 1 : void ragdollskel::setup()
138 : {
139 1 : setupjoints();
140 1 : setuprotfrictions();
141 :
142 1 : loaded = true;
143 1 : }
144 :
145 1 : void ragdollskel::addreljoint(int bone, int parent)
146 : {
147 1 : reljoints.emplace_back(reljoint{bone, parent});
148 1 : }
149 : /* ragdolldata */
150 :
151 3 : ragdolldata::ragdolldata(const ragdollskel *skel, float scale)
152 3 : : skel(skel),
153 3 : millis(lastmillis),
154 3 : collidemillis(0),
155 3 : lastmove(lastmillis),
156 3 : radius(0),
157 3 : tris(skel->tris.size()),
158 3 : animjoints(!skel->animjoints || skel->joints.empty() ? nullptr : new matrix4x3[skel->joints.size()]),
159 3 : reljoints(skel->reljoints.empty() ? nullptr : new dualquat[skel->reljoints.size()]),
160 3 : verts(skel->verts.size()),
161 3 : collisions(0),
162 3 : floating(0),
163 3 : unsticks(INT_MAX),
164 3 : timestep(0),
165 3 : scale(scale),
166 6 : rotfrictions(skel->rotfrictions.size())
167 : {
168 3 : }
169 :
170 3 : ragdolldata::~ragdolldata()
171 : {
172 3 : if(animjoints)
173 : {
174 1 : delete[] animjoints;
175 : }
176 3 : if(reljoints)
177 : {
178 0 : delete[] reljoints;
179 : }
180 3 : }
181 :
182 : /*
183 : seed particle position = avg(modelview * base2anim * spherepos)
184 : mapped transform = invert(curtri) * origtrig
185 : parented transform = parent{invert(curtri) * origtrig} * (invert(parent{base2anim}) * base2anim)
186 : */
187 :
188 1 : void ragdolldata::calcanimjoint(int i, const matrix4x3 &anim)
189 : {
190 1 : if(!animjoints)
191 : {
192 1 : return;
193 : }
194 0 : const ragdollskel::joint &j = skel->joints[i];
195 0 : vec pos(0, 0, 0);
196 0 : for(int k = 0; k < 3; ++k)
197 : {
198 0 : if(j.vert[k]>=0)
199 : {
200 0 : pos.add(verts[j.vert[k]].pos);
201 : }
202 : }
203 0 : pos.mul(j.weight);
204 :
205 0 : const ragdollskel::tri &t = skel->tris[j.tri];
206 0 : matrix4x3 m;
207 0 : const vec &v1 = verts[t.vert[0]].pos,
208 0 : &v2 = verts[t.vert[1]].pos,
209 0 : &v3 = verts[t.vert[2]].pos;
210 0 : m.a = vec(v2).sub(v1).normalize();
211 0 : m.c.cross(m.a, vec(v3).sub(v1)).normalize();
212 0 : m.b.cross(m.c, m.a);
213 0 : m.d = pos;
214 0 : animjoints[i].transposemul(m, anim);
215 : }
216 :
217 1 : void ragdolldata::calctris()
218 : {
219 1 : for(uint i = 0; i < skel->tris.size(); i++)
220 : {
221 0 : const ragdollskel::tri &t = skel->tris[i];
222 0 : matrix3 &m = tris[i];
223 0 : const vec &v1 = verts[t.vert[0]].pos,
224 0 : &v2 = verts[t.vert[1]].pos,
225 0 : &v3 = verts[t.vert[2]].pos;
226 0 : m.a = vec(v2).sub(v1).normalize();
227 0 : m.c.cross(m.a, vec(v3).sub(v1)).normalize();
228 0 : m.b.cross(m.c, m.a);
229 : }
230 1 : }
231 :
232 1 : void ragdolldata::calcboundsphere()
233 : {
234 1 : center = vec(0, 0, 0);
235 3 : for(const vert &v : verts)
236 : {
237 2 : center.add(v.pos);
238 : }
239 1 : center.div(verts.size());
240 1 : radius = 0;
241 3 : for(const vert &v : verts)
242 : {
243 2 : radius = std::max(radius, v.pos.dist(center));
244 : }
245 1 : }
246 :
247 : VAR(ragdolltimestepmin, 1, 5, 50);
248 : VAR(ragdolltimestepmax, 1, 10, 50);
249 :
250 1 : void ragdolldata::init(const dynent *d)
251 : {
252 1 : float ts = ragdolltimestepmin/1000.0f;
253 3 : for(vert &v : verts)
254 : {
255 2 : (v.oldpos = v.pos).sub(vec(d->vel).add(d->falling).mul(ts));
256 : }
257 1 : timestep = ts;
258 :
259 1 : calctris();
260 1 : calcboundsphere();
261 1 : offset = d->o;
262 1 : offset.sub(skel->eye >= 0 ? verts[skel->eye].pos : center);
263 1 : offset.z += (d->eyeheight + d->aboveeye)/2;
264 1 : }
265 :
266 0 : void ragdolldata::constraindist()
267 : {
268 0 : for(const ragdollskel::distlimit &d : skel->distlimits)
269 : {
270 0 : vert &v1 = verts[d.vert[0]],
271 0 : &v2 = verts[d.vert[1]];
272 0 : vec dir = vec(v2.pos).sub(v1.pos);
273 0 : const float dist = dir.magnitude()/scale;
274 : float cdist;
275 0 : if(dist < d.mindist)
276 : {
277 0 : cdist = d.mindist;
278 : }
279 0 : else if(dist > d.maxdist)
280 : {
281 0 : cdist = d.maxdist;
282 : }
283 : else
284 : {
285 0 : continue;
286 : }
287 0 : if(dist > 1e-4f)
288 : {
289 0 : dir.mul(cdist*0.5f/dist);
290 : }
291 : else
292 : {
293 0 : dir = vec(0, 0, cdist*0.5f*scale);
294 : }
295 0 : const vec center = vec(v1.pos).add(v2.pos).mul(0.5f);
296 0 : v1.newpos.add(vec(center).sub(dir));
297 0 : v1.weight++;
298 0 : v2.newpos.add(vec(center).add(dir));
299 0 : v2.weight++;
300 : }
301 0 : }
302 :
303 0 : void ragdolldata::applyrotlimit(const ragdollskel::tri &t1, const ragdollskel::tri &t2, float angle, const vec &axis)
304 : {
305 0 : vert &v1a = verts[t1.vert[0]],
306 0 : &v1b = verts[t1.vert[1]],
307 0 : &v1c = verts[t1.vert[2]],
308 0 : &v2a = verts[t2.vert[0]],
309 0 : &v2b = verts[t2.vert[1]],
310 0 : &v2c = verts[t2.vert[2]];
311 : // vec() copy constructor used below to deal with the fact that vec.add/sub() are destructive operations
312 0 : const vec m1 = vec(v1a.pos).add(v1b.pos).add(v1c.pos).div(3),
313 0 : m2 = vec(v2a.pos).add(v2b.pos).add(v2c.pos).div(3);
314 0 : vec q1a, q1b, q1c, q2a, q2b, q2c;
315 0 : const float w1 = q1a.cross(axis, vec(v1a.pos).sub(m1)).magnitude() +
316 0 : q1b.cross(axis, vec(v1b.pos).sub(m1)).magnitude() +
317 0 : q1c.cross(axis, vec(v1c.pos).sub(m1)).magnitude(),
318 0 : w2 = q2a.cross(axis, vec(v2a.pos).sub(m2)).magnitude() +
319 0 : q2b.cross(axis, vec(v2b.pos).sub(m2)).magnitude() +
320 0 : q2c.cross(axis, vec(v2c.pos).sub(m2)).magnitude();
321 0 : angle /= w1 + w2 + 1e-9f;
322 0 : const float a1 = angle*w2,
323 0 : a2 = -angle*w1,
324 0 : s1 = std::sin(a1),
325 0 : s2 = std::sin(a2);
326 0 : const vec c1 = vec(axis).mul(1 - std::cos(a1)),
327 0 : c2 = vec(axis).mul(1 - std::cos(a2));
328 0 : v1a.newpos.add(vec().cross(c1, q1a).madd(q1a, s1).add(v1a.pos));
329 0 : v1a.weight++;
330 0 : v1b.newpos.add(vec().cross(c1, q1b).madd(q1b, s1).add(v1b.pos));
331 0 : v1b.weight++;
332 0 : v1c.newpos.add(vec().cross(c1, q1c).madd(q1c, s1).add(v1c.pos));
333 0 : v1c.weight++;
334 0 : v2a.newpos.add(vec().cross(c2, q2a).madd(q2a, s2).add(v2a.pos));
335 0 : v2a.weight++;
336 0 : v2b.newpos.add(vec().cross(c2, q2b).madd(q2b, s2).add(v2b.pos));
337 0 : v2b.weight++;
338 0 : v2c.newpos.add(vec().cross(c2, q2c).madd(q2c, s2).add(v2c.pos));
339 0 : v2c.weight++;
340 0 : }
341 :
342 0 : void ragdolldata::constrainrot()
343 : {
344 0 : calctris();
345 0 : for(const ragdollskel::rotlimit &r : skel->rotlimits)
346 : {
347 0 : matrix3 rot;
348 0 : rot.mul(tris[r.tri[0]], r.middle);
349 0 : rot.multranspose(tris[r.tri[1]]);
350 :
351 0 : vec axis;
352 : float angle;
353 0 : const float tr = rot.trace();
354 0 : if(tr >= r.maxtrace || !rot.calcangleaxis(tr, angle, axis))
355 : {
356 0 : continue;
357 : }
358 0 : angle = r.maxangle - angle + 1e-3f;
359 0 : applyrotlimit(skel->tris[r.tri[0]], skel->tris[r.tri[1]], angle, axis);
360 : }
361 0 : }
362 :
363 0 : void ragdolldata::calcrotfriction()
364 : {
365 0 : for(size_t i = 0; i < skel->rotfrictions.size(); ++i)
366 : {
367 0 : rotfrictions[i].transposemul(tris[skel->rotfrictions[i].tri[0]], tris[skel->rotfrictions[i].tri[1]]);
368 : }
369 0 : }
370 :
371 0 : void ragdolldata::applyrotfriction(float ts)
372 : {
373 0 : static FVAR(ragdollrotfric, 0, 0.85f, 1);
374 0 : static FVAR(ragdollrotfricstop, 0, 0.1f, 1);
375 :
376 0 : calctris();
377 0 : float stopangle = 2*M_PI*ts*ragdollrotfricstop,
378 0 : rotfric = 1.0f - std::pow(ragdollrotfric, ts*1000.0f/ragdolltimestepmin);
379 0 : for(size_t i = 0; i < skel->rotfrictions.size(); ++i)
380 : {
381 0 : matrix3 rot;
382 0 : rot.mul(tris[ skel->rotfrictions[i].tri[0]], rotfrictions[i]);
383 0 : rot.multranspose(tris[ skel->rotfrictions[i].tri[1]]);
384 :
385 0 : vec axis;
386 : float angle;
387 0 : if(!rot.calcangleaxis(angle, axis))
388 : {
389 0 : continue;
390 : }
391 0 : angle *= -(std::fabs(angle) >= stopangle ? rotfric : 1.0f);
392 0 : applyrotlimit(skel->tris[skel->rotfrictions[i].tri[0]], skel->tris[ skel->rotfrictions[i].tri[1]], angle, axis);
393 : }
394 0 : for(vert &v : verts)
395 : {
396 0 : if(!v.weight)
397 : {
398 0 : continue;
399 : }
400 0 : v.pos = v.newpos.div(v.weight);
401 0 : v.newpos = vec(0, 0, 0);
402 0 : v.weight = 0;
403 : }
404 0 : }
405 :
406 0 : void ragdolldata::tryunstick(float speed)
407 : {
408 : /* vec `unstuck` is the average position of all vertices with the `stuck` flag
409 : * that are not found to collide with the world, as well as all vertices without
410 : * the `stuck` flag.
411 : */
412 0 : vec unstuck(0, 0, 0);
413 0 : size_t stuck = 0;
414 0 : for(uint i = 0; i < verts.size(); i++)
415 : {
416 0 : vert &v = verts[i];
417 0 : if(v.stuck)
418 : {
419 0 : if(collidevert(v.pos, vec(0, 0, 0), skel->verts[i].radius))
420 : {
421 0 : stuck++;
422 0 : continue;
423 : }
424 0 : v.stuck = false;
425 : }
426 0 : unstuck.add(v.pos);
427 : }
428 0 : unsticks = 0;
429 0 : if(!stuck || stuck >= verts.size())
430 : {
431 0 : return;
432 : }
433 0 : unstuck.div(verts.size() - stuck); //get average by dividing by # verts not stuck
434 :
435 0 : for(vert &v : verts)
436 : {
437 0 : if(v.stuck)
438 : {
439 0 : v.pos.add(vec(unstuck).sub(v.pos).rescale(speed));
440 0 : unsticks++;
441 : }
442 : }
443 : }
444 :
445 0 : void ragdolldata::constrain()
446 : {
447 0 : static VAR(ragdollconstrain, 1, 7, 100); //number of iterations to run ragdolldata::constrain() for
448 : //note: this for loop does not use the loop variable `i` anywhere
449 0 : for(int i = 0; i < ragdollconstrain; ++i)
450 : {
451 0 : constraindist();
452 0 : for(vert &v : verts)
453 : {
454 0 : v.undo = v.pos;
455 0 : if(v.weight)
456 : {
457 0 : v.pos = v.newpos.div(v.weight);
458 0 : v.newpos = vec(0, 0, 0);
459 0 : v.weight = 0;
460 : }
461 : }
462 :
463 0 : constrainrot();
464 0 : for(uint j = 0; j < verts.size(); j++)
465 : {
466 0 : vert &v = verts[j];
467 0 : if(v.weight)
468 : {
469 0 : v.pos = v.newpos.div(v.weight);
470 0 : v.newpos = vec(0, 0, 0);
471 0 : v.weight = 0;
472 : }
473 0 : if(v.pos != v.undo && collidevert(v.pos, vec(v.pos).sub(v.undo), skel->verts[j].radius))
474 : {
475 0 : vec dir = vec(v.pos).sub(v.oldpos);
476 0 : const float facing = dir.dot(collidewall);
477 0 : if(facing < 0)
478 : {
479 0 : v.oldpos = vec(v.undo).sub(dir.msub(collidewall, 2*facing));
480 : }
481 0 : v.pos = v.undo;
482 0 : v.collided = true;
483 : }
484 : }
485 : }
486 0 : }
487 :
488 0 : void ragdolldata::move(bool water, float ts)
489 : {
490 :
491 0 : static FVAR(ragdollbodyfric, 0, 0.95f, 1);
492 0 : static FVAR(ragdollbodyfricscale, 0, 2, 10);
493 0 : static FVAR(ragdollwaterfric, 0, 0.85f, 1);
494 0 : static FVAR(ragdollgroundfric, 0, 0.8f, 1);
495 0 : static FVAR(ragdollairfric, 0, 0.996f, 1);
496 0 : static FVAR(ragdollunstick, 0, 10, 1e3f);
497 0 : static VAR(ragdollexpireoffset, 0, 2500, 30000);
498 0 : static VAR(ragdollwaterexpireoffset, 0, 4000, 30000);
499 :
500 0 : if(collidemillis && lastmillis > collidemillis)
501 : {
502 0 : return;
503 : }
504 :
505 0 : calcrotfriction();
506 0 : const float tsfric = timestep ? ts/timestep : 1,
507 0 : airfric = ragdollairfric + std::min((ragdollbodyfricscale*collisions)/verts.size(), 1.0f)*(ragdollbodyfric - ragdollairfric);
508 0 : collisions = 0;
509 0 : for(uint i = 0; i < verts.size(); i++)
510 : {
511 0 : vert &v = verts[i];
512 0 : vec dpos = vec(v.pos).sub(v.oldpos);
513 0 : dpos.z -= 100*ts*ts;
514 0 : if(water)
515 : {
516 : //reinterpret cast pointer -> "random" seed value
517 0 : dpos.z += 0.25f*std::sin(detrnd(reinterpret_cast<size_t>(this)+i, 360)/RAD + lastmillis/10000.0f*M_PI)*ts;
518 : }
519 0 : dpos.mul(std::pow((water ? ragdollwaterfric : 1.0f) * (v.collided ? ragdollgroundfric : airfric), ts*1000.0f/ragdolltimestepmin)*tsfric);
520 0 : v.oldpos = v.pos;
521 0 : v.pos.add(dpos);
522 : }
523 0 : applyrotfriction(ts);
524 0 : for(uint i = 0; i < verts.size(); i++)
525 : {
526 0 : vert &v = verts[i];
527 0 : if(v.pos.z < 0)
528 : {
529 0 : v.pos.z = 0;
530 0 : v.oldpos = v.pos;
531 0 : collisions++;
532 : }
533 0 : vec dir = vec(v.pos).sub(v.oldpos);
534 0 : v.collided = collidevert(v.pos, dir, skel->verts[i].radius);
535 0 : if(v.collided)
536 : {
537 0 : v.pos = v.oldpos;
538 0 : v.oldpos.sub(dir.reflect(collidewall));
539 0 : collisions++;
540 : }
541 : }
542 0 : if(unsticks && ragdollunstick)
543 : {
544 0 : tryunstick(ts*ragdollunstick);
545 : }
546 0 : timestep = ts;
547 0 : if(collisions)
548 : {
549 0 : floating = 0;
550 0 : if(!collidemillis)
551 : {
552 0 : collidemillis = lastmillis + (water ? ragdollwaterexpireoffset : ragdollexpireoffset);
553 : }
554 : }
555 0 : else if(++floating > 1 && lastmillis < collidemillis)
556 : {
557 0 : collidemillis = 0;
558 : }
559 0 : constrain();
560 0 : calctris();
561 0 : calcboundsphere();
562 : }
563 :
564 0 : bool ragdolldata::collidevert(const vec &pos, const vec &dir, float radius)
565 : {
566 : static struct vertent : physent
567 : {
568 0 : vertent()
569 0 : {
570 0 : type = PhysEnt_Bounce;
571 0 : radius = xradius = yradius = eyeheight = aboveeye = 1;
572 0 : }
573 0 : } v;
574 0 : v.o = pos;
575 0 : if(v.radius != radius) //(radius param != 1)
576 : {
577 0 : v.radius = v.xradius = v.yradius = v.eyeheight = v.aboveeye = radius;
578 : }
579 : //collide generated vertent (point with sphere bounding of r = radius)
580 : //with dir parameter
581 0 : return collide(&v, dir, 0, false);
582 : }
583 :
584 : //used in iengine
585 0 : void moveragdoll(dynent *d)
586 : {
587 0 : static FVAR(ragdolleyesmooth, 0, 0.5f, 1);
588 0 : static VAR(ragdolleyesmoothmillis, 1, 250, 10000);
589 0 : if(!curtime || !d->ragdoll)
590 : {
591 0 : return;
592 : }
593 0 : if(!d->ragdoll->collidemillis || lastmillis < d->ragdoll->collidemillis)
594 : {
595 0 : const int lastmove = d->ragdoll->lastmove;
596 0 : while(d->ragdoll->lastmove + (lastmove == d->ragdoll->lastmove ? ragdolltimestepmin : ragdolltimestepmax) <= lastmillis)
597 : {
598 0 : const int timestep = std::min(ragdolltimestepmax, lastmillis - d->ragdoll->lastmove),
599 0 : material = rootworld.lookupmaterial(vec(d->ragdoll->center.x, d->ragdoll->center.y, d->ragdoll->center.z + d->ragdoll->radius/2));
600 0 : const bool water = (material&MatFlag_Volume) == Mat_Water;
601 0 : d->inwater = water ? material&MatFlag_Volume : Mat_Air;
602 0 : d->ragdoll->move(water, timestep/1000.0f);
603 0 : d->ragdoll->lastmove += timestep;
604 : }
605 : }
606 :
607 0 : vec eye = d->ragdoll->skel->eye >= 0 ? d->ragdoll->verts[d->ragdoll->skel->eye].pos : d->ragdoll->center;
608 0 : eye.add(d->ragdoll->offset);
609 0 : const float k = std::pow(ragdolleyesmooth, static_cast<float>(curtime)/ragdolleyesmoothmillis);
610 0 : d->o.lerp(eye, 1-k);
611 : }
612 :
613 1 : void cleanragdoll(dynent *d)
614 : {
615 1 : if(d->ragdoll)
616 : {
617 1 : delete d->ragdoll;
618 1 : d->ragdoll = nullptr;
619 : }
620 1 : }
|