cannon-es-control
Version:
A lightweight 3D physics engine written in JavaScript with control system tools
219 lines (194 loc) • 6.2 kB
text/typescript
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()