import { JacobianElement } from '../math/JacobianElement' import { Vec3 } from '../math/Vec3' import type { Body } from '../objects/Body' import type { Shape } from '../shapes/Shape' /** * Equation base class. * * `a`, `b` and `eps` are {@link https://www8.cs.umu.se/kurser/5DV058/VT15/lectures/SPOOKlabnotes.pdf SPOOK} parameters that default to `0.0`. See {@link https://github.com/schteppe/cannon.js/issues/238#issuecomment-147172327 this exchange} for more details on Cannon's physics implementation. */ export class Equation { id: number /** * Minimum (read: negative max) force to be applied by the constraint. */ minForce: number /** * Maximum (read: positive max) force to be applied by the constraint. */ maxForce: number bi: Body bj: Body si!: Shape sj!: Shape /** * SPOOK parameter */ a: number /** * SPOOK parameter */ b: number /** * SPOOK parameter */ eps: number jacobianElementA: JacobianElement jacobianElementB: JacobianElement enabled: boolean /** * A number, proportional to the force added to the bodies. */ multiplier: number static idCounter = 0 constructor(bi: Body, bj: Body, minForce = -1e6, maxForce = 1e6) { this.id = Equation.idCounter++ this.minForce = minForce this.maxForce = maxForce this.bi = bi this.bj = bj this.a = 0.0 // SPOOK parameter this.b = 0.0 // SPOOK parameter this.eps = 0.0 // SPOOK parameter this.jacobianElementA = new JacobianElement() this.jacobianElementB = new JacobianElement() this.enabled = true this.multiplier = 0 this.setSpookParams(1e7, 4, 1 / 60) // Set typical spook params } /** * Recalculates a, b, and eps. * * The Equation constructor sets typical SPOOK parameters as such: * * `stiffness` = 1e7 * * `relaxation` = 4 * * `timeStep`= 1 / 60, _note the hardcoded refresh rate._ */ setSpookParams(stiffness: number, relaxation: number, timeStep: number): void { const d = relaxation const k = stiffness const h = timeStep this.a = 4.0 / (h * (1 + 4 * d)) this.b = (4.0 * d) / (1 + 4 * d) this.eps = 4.0 / (h * h * k * (1 + 4 * d)) } /** * Computes the right hand side of the SPOOK equation */ computeB(a: number, b: number, h: number): number { const GW = this.computeGW() const Gq = this.computeGq() const GiMf = this.computeGiMf() return -Gq * a - GW * b - GiMf * h } /** * Computes G*q, where q are the generalized body coordinates */ computeGq(): number { const GA = this.jacobianElementA const GB = this.jacobianElementB const bi = this.bi const bj = this.bj const xi = bi.position const xj = bj.position return GA.spatial.dot(xi) + GB.spatial.dot(xj) } /** * Computes G*W, where W are the body velocities */ computeGW(): number { const GA = this.jacobianElementA const GB = this.jacobianElementB const bi = this.bi const bj = this.bj const vi = bi.velocity const vj = bj.velocity const wi = bi.angularVelocity const wj = bj.angularVelocity return GA.multiplyVectors(vi, wi) + GB.multiplyVectors(vj, wj) } /** * Computes G*Wlambda, where W are the body velocities */ computeGWlambda(): number { const GA = this.jacobianElementA const GB = this.jacobianElementB const bi = this.bi const bj = this.bj const vi = bi.vlambda const vj = bj.vlambda const wi = bi.wlambda const wj = bj.wlambda return GA.multiplyVectors(vi, wi) + GB.multiplyVectors(vj, wj) } /** * Computes G*inv(M)*f, where M is the mass matrix with diagonal blocks for each body, and f are the forces on the bodies. */ computeGiMf(): number { const GA = this.jacobianElementA const GB = this.jacobianElementB const bi = this.bi const bj = this.bj const fi = bi.force const ti = bi.torque const fj = bj.force const tj = bj.torque const invMassi = bi.invMassSolve const invMassj = bj.invMassSolve fi.scale(invMassi, iMfi) fj.scale(invMassj, iMfj) bi.invInertiaWorldSolve.vmult(ti, invIi_vmult_taui) bj.invInertiaWorldSolve.vmult(tj, invIj_vmult_tauj) return GA.multiplyVectors(iMfi, invIi_vmult_taui) + GB.multiplyVectors(iMfj, invIj_vmult_tauj) } /** * Computes G*inv(M)*G' */ computeGiMGt(): number { const GA = this.jacobianElementA const GB = this.jacobianElementB const bi = this.bi const bj = this.bj const invMassi = bi.invMassSolve const invMassj = bj.invMassSolve const invIi = bi.invInertiaWorldSolve const invIj = bj.invInertiaWorldSolve let result = invMassi + invMassj invIi.vmult(GA.rotational, tmp) result += tmp.dot(GA.rotational) invIj.vmult(GB.rotational, tmp) result += tmp.dot(GB.rotational) return result } /** * Add constraint velocity to the bodies. */ addToWlambda(deltalambda: number): void { const GA = this.jacobianElementA const GB = this.jacobianElementB const bi = this.bi const bj = this.bj const temp = addToWlambda_temp // Add to linear velocity // v_lambda += inv(M) * delta_lamba * G bi.vlambda.addScaledVector(bi.invMassSolve * deltalambda, GA.spatial, bi.vlambda) bj.vlambda.addScaledVector(bj.invMassSolve * deltalambda, GB.spatial, bj.vlambda) // Add to angular velocity bi.invInertiaWorldSolve.vmult(GA.rotational, temp) bi.wlambda.addScaledVector(deltalambda, temp, bi.wlambda) bj.invInertiaWorldSolve.vmult(GB.rotational, temp) bj.wlambda.addScaledVector(deltalambda, temp, bj.wlambda) } /** * Compute the denominator part of the SPOOK equation: C = G*inv(M)*G' + eps */ computeC(): number { return this.computeGiMGt() + this.eps } } const iMfi = new Vec3() const iMfj = new Vec3() const invIi_vmult_taui = new Vec3() const invIj_vmult_tauj = new Vec3() const tmp = new Vec3() const addToWlambda_temp = new Vec3()