2 * Copyright (C) 2003 Robert Kooima
4 * NEVERBALL is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published
6 * by the Free Software Foundation; either version 2 of the License,
7 * or (at your option) any later version.
9 * This program is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * General Public License for more details.
20 #include "solid_vary.h"
21 #include "solid_sim.h"
22 #include "solid_all.h"
23 #include "solid_cmd.h"
28 /*---------------------------------------------------------------------------*/
29 /* Solves (p + v * t) . (p + v * t) == r * r for smallest t. */
32 * Given vectors A = P, B = V * t, C = A + B, |C| = r, solve for
35 * Some useful dot product properties:
37 * 1) A . A = |A| * |A|
38 * 2) A . (B + C) = A . B + A . C
39 * 3) (r * A) . B = r * (A . B)
41 * Deriving a quadratic equation:
44 * (A + B) . (A + B) = r * r
45 * A . (A + B) + B . (A + B) = r * r (2)
46 * A . A + A . B + B . A + B . B = r * r (2)
47 * A . A + 2 * (A . B) + B . B = r * r
48 * P . P + 2 * (P . V * t) + (V * t . V * t) = r * r
49 * P . P + 2 * (P . V) * t + (V . V) * t * t = r * r (3)
50 * (V . V) * t * t + 2 * (P . V) * t + P . P - r * r = 0
52 * This equation is solved using the quadratic formula.
55 static float v_sol(const float p[3], const float v[3], float r)
57 float a = v_dot(v, v);
58 float b = v_dot(v, p) * 2.0f;
59 float c = v_dot(p, p) - r * r;
60 float d = b * b - 4.0f * a * c;
62 /* HACK: This seems to cause failures to detect low-velocity collision
63 Yet, the potential division by zero below seems fine.
64 if (fabsf(a) < SMALL) return LARGE;
67 if (d < 0.0f) return LARGE;
70 float t0 = 0.5f * (-b - fsqrtf(d)) / a;
71 float t1 = 0.5f * (-b + fsqrtf(d)) / a;
72 float t = (t0 < t1) ? t0 : t1;
74 return (t < 0.0f) ? LARGE : t;
76 else return -b * 0.5f / a;
79 /*---------------------------------------------------------------------------*/
82 * Compute the earliest time and position of the intersection of a
83 * sphere and a vertex.
85 * The sphere has radius R and moves along vector V from point P. The
86 * vertex moves along vector W from point Q in a coordinate system
89 static float v_vert(float Q[3],
94 const float v[3], float r)
96 float O[3], P[3], V[3];
103 if (v_dot(P, V) < 0.0f)
114 * Compute the earliest time and position of the intersection of a
115 * sphere and an edge.
117 * The sphere has radius R and moves along vector V from point P. The
118 * edge moves along vector W from point Q in a coordinate system based
119 * at O. The edge extends along the length of vector U.
121 static float v_edge(float Q[3],
127 const float v[3], float r)
131 float du, eu, uu, s, t;
138 * Think projections. Vectors D, extending from the edge vertex Q
139 * to the sphere, and E, the relative velocity of sphere wrt the
140 * edge, are made orthogonal to the edge vector U. Division of
141 * the dot products is required to obtain the true projection
142 * ratios since U does not have unit length.
149 v_mad(P, d, u, -du / uu);
151 /* First, test for intersection. */
153 if (v_dot(P, P) < r * r)
155 /* The sphere already intersects the line of the edge. */
157 if (du < 0 || du > uu)
160 * The sphere is behind the endpoints of the edge, and
161 * can't hit the edge without hitting the vertices first.
166 /* The sphere already intersects the edge. */
168 if (v_dot(P, e) >= 0)
180 v_mad(V, e, u, -eu / uu);
183 s = (du + eu * t) / uu; /* Projection of D + E * t on U. */
185 if (0.0f <= t && t < LARGE && 0.0f < s && s < 1.0f)
198 * Compute the earliest time and position of the intersection of a
199 * sphere and a plane.
201 * The sphere has radius R and moves along vector V from point P. The
202 * plane moves along vector W. The plane has normal N and is
203 * positioned at distance D from the origin O along that normal.
205 static float v_side(float Q[3],
208 const float n[3], float d,
210 const float v[3], float r)
212 float vn = v_dot(v, n);
213 float wn = v_dot(w, n);
218 float on = v_dot(o, n);
219 float pn = v_dot(p, n);
221 float u = (r + d + on - pn) / (vn - wn);
222 float a = ( d + on - pn) / (vn - wn);
242 /*---------------------------------------------------------------------------*/
245 * Compute the new linear and angular velocities of a bouncing ball.
246 * Q gives the position of the point of impact and W gives the
247 * velocity of the object being impacted.
249 static float sol_bounce(struct v_ball *up,
251 const float w[3], float dt)
253 float n[3], r[3], d[3], vn, wn;
257 /* Find the normal of the impact. */
263 /* Find the new angular velocity. */
266 v_scl(up->w, up->w, -1.0f / (up->r * up->r));
268 /* Find the new linear velocity. */
273 v_mad(v, v, n, 1.7 * (wn - vn));
275 v_mad(p, q, n, up->r);
277 /* Return the "energy" of the impact, to determine the sound amplitude. */
279 return fabsf(v_dot(n, d));
282 /*---------------------------------------------------------------------------*/
284 static float sol_test_vert(float dt,
286 const struct v_ball *up,
287 const struct b_vert *vp,
291 return v_vert(T, o, vp->p, w, up->p, up->v, up->r);
294 static float sol_test_edge(float dt,
296 const struct v_ball *up,
297 const struct s_base *base,
298 const struct b_edge *ep,
305 v_cpy(q, base->vv[ep->vi].p);
306 v_sub(u, base->vv[ep->vj].p, base->vv[ep->vi].p);
308 return v_edge(T, o, q, u, w, up->p, up->v, up->r);
311 static float sol_test_side(float dt,
313 const struct v_ball *up,
314 const struct s_base *base,
315 const struct b_lump *lp,
316 const struct b_side *sp,
320 float t = v_side(T, o, w, sp->n, sp->d, up->p, up->v, up->r);
324 for (i = 0; i < lp->sc; i++)
326 const struct b_side *sq = base->sv + base->iv[lp->s0 + i];
331 v_dot(w, sq->n) * t > sq->d)
337 /*---------------------------------------------------------------------------*/
339 static int sol_test_fore(float dt,
340 const struct v_ball *up,
341 const struct b_side *sp,
347 /* If the ball is not behind the plane, the test passes. */
352 if (v_dot(q, sp->n) - d + up->r >= 0)
355 /* If it's not behind the plane after DT seconds, the test passes. */
357 v_mad(q, q, up->v, dt);
358 d += v_dot(w, sp->n) * dt;
360 if (v_dot(q, sp->n) - d + up->r >= 0)
363 /* Else, test fails. */
368 static int sol_test_back(float dt,
369 const struct v_ball *up,
370 const struct b_side *sp,
376 /* If the ball is not in front of the plane, the test passes. */
381 if (v_dot(q, sp->n) - d - up->r <= 0)
384 /* If it's not in front of the plane after DT seconds, the test passes. */
386 v_mad(q, q, up->v, dt);
387 d += v_dot(w, sp->n) * dt;
389 if (v_dot(q, sp->n) - d - up->r <= 0)
392 /* Else, test fails. */
397 /*---------------------------------------------------------------------------*/
399 static float sol_test_lump(float dt,
401 const struct v_ball *up,
402 const struct s_base *base,
403 const struct b_lump *lp,
407 float U[3] = { 0.0f, 0.0f, 0.0f };
411 /* Short circuit a non-solid lump. */
413 if (lp->fl & L_DETAIL) return t;
418 for (i = 0; i < lp->vc; i++)
420 const struct b_vert *vp = base->vv + base->iv[lp->v0 + i];
422 if ((u = sol_test_vert(t, U, up, vp, o, w)) < t)
432 for (i = 0; i < lp->ec; i++)
434 const struct b_edge *ep = base->ev + base->iv[lp->e0 + i];
436 if ((u = sol_test_edge(t, U, up, base, ep, o, w)) < t)
445 for (i = 0; i < lp->sc; i++)
447 const struct b_side *sp = base->sv + base->iv[lp->s0 + i];
449 if ((u = sol_test_side(t, U, up, base, lp, sp, o, w)) < t)
458 static float sol_test_node(float dt,
460 const struct v_ball *up,
461 const struct s_base *base,
462 const struct b_node *np,
466 float U[3], u, t = dt;
471 for (i = 0; i < np->lc; i++)
473 const struct b_lump *lp = base->lv + np->l0 + i;
475 if ((u = sol_test_lump(t, U, up, base, lp, o, w)) < t)
482 /* Test in front of this node */
484 if (np->ni >= 0 && sol_test_fore(t, up, base->sv + np->si, o, w))
486 const struct b_node *nq = base->nv + np->ni;
488 if ((u = sol_test_node(t, U, up, base, nq, o, w)) < t)
495 /* Test behind this node */
497 if (np->nj >= 0 && sol_test_back(t, up, base->sv + np->si, o, w))
499 const struct b_node *nq = base->nv + np->nj;
501 if ((u = sol_test_node(t, U, up, base, nq, o, w)) < t)
511 static float sol_test_body(float dt,
512 float T[3], float V[3],
513 const struct v_ball *up,
514 const struct s_vary *vary,
515 const struct v_body *bp)
517 float U[3], O[3], E[4], W[3], A[3], u;
519 const struct b_node *np = vary->base->nv + bp->base->ni;
521 sol_body_p(O, vary, bp->pi, bp->t);
522 sol_body_v(W, vary, bp->pi, bp->t, dt);
523 sol_body_e(E, vary, bp, 0);
524 sol_body_w(A, vary, bp);
527 * For rotating bodies, rather than rotate every normal and vertex
528 * of the body, we temporarily pretend the ball is rotating and
529 * moving about a static body.
533 * Linear velocity of a point rotating about the origin:
537 if (E[0] != 1.0f || v_dot(A, A) != 0.0f)
539 /* The body has a non-identity orientation or it is rotating. */
542 float e[4], p0[3], p1[3];
543 const float z[3] = { 0 };
545 /* First, calculate position at start and end of time interval. */
552 v_mad(p1, p1, up->v, dt);
553 v_mad(p1, p1, W, -dt);
554 sol_body_e(e, vary, bp, dt);
558 /* Set up ball struct with values relative to body. */
564 /* Calculate velocity from start/end positions and time. */
566 v_sub(ball.v, p1, p0);
567 v_scl(ball.v, ball.v, 1.0f / dt);
569 if ((u = sol_test_node(dt, U, &ball, vary->base, np, z, z)) < dt)
573 /* Compute the final orientation. */
575 q_by_axisangle(d, A, v_len(A) * u);
579 /* Return world space coordinates. */
588 /* Express "non-ball" velocity. */
598 if ((u = sol_test_node(dt, U, up, vary->base, np, O, W)) < dt)
608 static float sol_test_file(float dt,
609 float T[3], float V[3],
610 const struct v_ball *up,
611 const struct s_vary *vary)
613 float U[3], W[3], u, t = dt;
616 for (i = 0; i < vary->bc; i++)
618 const struct v_body *bp = vary->bv + i;
620 if ((u = sol_test_body(t, U, W, up, vary, bp)) < t)
630 /*---------------------------------------------------------------------------*/
633 * Track simulation steps in integer milliseconds.
636 static float ms_accum;
638 static void ms_init(void)
643 static int ms_step(float dt)
649 while (ms_accum >= 0.001f)
658 static int ms_peek(float dt)
674 /*---------------------------------------------------------------------------*/
677 * Step the physics forward DT seconds under the influence of gravity
678 * vector G. If the ball gets pinched between two moving solids, this
679 * loop might not terminate. It is better to do something physically
680 * impossible than to lock up the game. So, if we make more than C
681 * iterations, punt it.
684 float sol_step(struct s_vary *vary, const float *g, float dt, int ui, int *m)
686 float P[3], V[3], v[3], r[3], a[3], d, e, nt, b = 0.0f, tt = dt;
693 struct v_ball *up = vary->uv + ui;
695 /* If the ball is in contact with a surface, apply friction. */
701 if (m && sol_test_file(tt, P, V, up, vary) < 0.0005f)
706 if ((d = v_dot(r, g) / (v_len(r) * v_len(g))) > 0.999f)
708 if ((e = (v_len(up->v) - dt)) > 0.0f)
710 /* Scale the linear velocity. */
713 v_scl(up->v, up->v, e);
715 /* Scale the angular velocity. */
719 v_scl(up->w, up->w, -1.0f / (up->r * up->r));
723 /* Friction has brought the ball to a stop. */
732 else v_mad(up->v, v, g, tt);
734 else v_mad(up->v, v, g, tt);
736 /* Test for collision. */
738 for (c = 16; c > 0 && tt > 0; c--)
743 /* HACK: avoid stepping across path changes. */
747 for (bi = 0; bi < vary->bc; bi++)
749 struct v_body *bp = vary->bv + bi;
753 struct v_path *pp = vary->pv + bp->pi;
758 if (bp->tm + ms_peek(st) > pp->base->tm)
759 st = MS_TO_TIME(pp->base->tm - bp->tm);
763 /* Miss collisions if we reach the iteration limit. */
766 nt = sol_test_file(st, P, V, up, vary);
770 cmd.type = CMD_STEP_SIMULATION;
776 sol_body_step(vary, nt, ms);
777 sol_swch_step(vary, nt, ms);
778 sol_ball_step(vary, nt);
781 if (b < (d = sol_bounce(up, P, V, nt)))
789 sol_pendulum(up, a, g, dt);
795 /*---------------------------------------------------------------------------*/
797 void sol_init_sim(struct s_vary *vary)
802 void sol_quit_sim(void)
807 /*---------------------------------------------------------------------------*/