diff --git a/.eslintrc.json b/.eslintrc.json index c6a9c09b..06b362bf 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -19,7 +19,41 @@ "@typescript-eslint/explicit-module-boundary-types": "off", "@typescript-eslint/member-ordering": [ "error", - { "default": { "order": "alphabetically-case-insensitive" } } + { + "default": { + "order": "alphabetically-case-insensitive" + }, + "classes": { + "order": "alphabetically-case-insensitive", + "memberTypes": [ + "public-static-field", + "protected-static-field", + "private-static-field", + "public-instance-field", + "public-decorated-field", + "public-abstract-field", + "protected-instance-field", + "protected-decorated-field", + "protected-abstract-field", + "private-instance-field", + "private-decorated-field", + "private-abstract-field", + "static-field", + "public-field", + "instance-field", + "protected-field", + "private-field", + "abstract-field", + "constructor", + "public-static-method", + "protected-static-method", + "private-static-method", + "public-method", + "protected-method", + "private-method" + ] + } + } ], "@typescript-eslint/no-namespace": ["error", { "allowDeclarations": true }], "@typescript-eslint/no-non-null-assertion": "off", diff --git a/examples/yarn.lock b/examples/yarn.lock index a9b682bf..770ab67a 100644 --- a/examples/yarn.lock +++ b/examples/yarn.lock @@ -350,10 +350,11 @@ integrity sha512-W/YMJMX35XgGGzX0gKORBTwnvQ+1loDOFN3XlZkW5fgpEY+7VkRUpPyqPWXQr3n6lHrsLmHIGdpznqZi54ACTQ== "@react-three/cannon@file:..": - version "4.4.0" + version "4.6.1" dependencies: cannon-es "^0.18.0" cannon-es-debugger "^1.0.0" + events "^3.3.0" "@react-three/drei@^8.3.1": version "8.3.1" @@ -854,6 +855,11 @@ estree-walker@^2.0.1: resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac" integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w== +events@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" + integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== + fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" diff --git a/package.json b/package.json index 59ac6929..afb68e0b 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,8 @@ }, "dependencies": { "cannon-es": "^0.18.0", - "cannon-es-debugger": "^1.0.0" + "cannon-es-debugger": "^1.0.0", + "events": "^3.3.0" }, "devDependencies": { "@babel/core": "^7.16.5", diff --git a/src/Provider.tsx b/src/Provider.tsx index 8df3f69c..a2b50b3f 100644 --- a/src/Provider.tsx +++ b/src/Provider.tsx @@ -1,149 +1,41 @@ import { useFrame, useThree } from '@react-three/fiber' -import type { ContactMaterial, Shape } from 'cannon-es' import type { PropsWithChildren } from 'react' import { useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react' import type { Object3D } from 'three' import { InstancedMesh, Matrix4, Quaternion, Vector3 } from 'three' -// @ts-expect-error Types are not setup for this yet -import CannonWorker from '../src/worker' -import type { Triplet } from './hooks' -import type { AtomicName, Buffers, PropValue, ProviderContext, Refs } from './setup' +import type { + ProviderContext, + Refs, + WorkerCollideBeginEvent, + WorkerCollideEndEvent, + WorkerCollideEvent, + WorkerFrameMessage, + WorkerRayhitEvent, +} from './setup' import { context } from './setup' import { useUpdateWorldPropsEffect } from './useUpdateWorldPropsEffect' - -function noop() { - /**/ -} - -export type Broadphase = 'Naive' | 'SAP' -export type Solver = 'GS' | 'Split' - -export type DefaultContactMaterial = Partial< - Pick< - ContactMaterial, - | 'contactEquationRelaxation' - | 'contactEquationStiffness' - | 'friction' - | 'frictionEquationRelaxation' - | 'frictionEquationStiffness' - | 'restitution' - > -> - -export type ProviderProps = PropsWithChildren<{ - allowSleep?: boolean - axisIndex?: number - broadphase?: Broadphase - defaultContactMaterial?: DefaultContactMaterial - gravity?: Triplet - iterations?: number - quatNormalizeFast?: boolean - quatNormalizeSkip?: number - shouldInvalidate?: boolean - size?: number - solver?: Solver - step?: number - tolerance?: number -}> - -type Observation = { [K in AtomicName]: [id: number, value: PropValue, type: K] }[AtomicName] - -type WorkerFrameMessage = { - data: Buffers & { - active: boolean - bodies?: string[] - observations: Observation[] - op: 'frame' - } -} - -export type WorkerCollideEvent = { - data: { - body: string - collisionFilters: { - bodyFilterGroup: number - bodyFilterMask: number - targetFilterGroup: number - targetFilterMask: number - } - contact: { - bi: string - bj: string - /** Normal of the contact, relative to the colliding body */ - contactNormal: number[] - /** Contact point in world space */ - contactPoint: number[] - id: string - impactVelocity: number - ni: number[] - ri: number[] - rj: number[] - } - op: 'event' - target: string - type: 'collide' - } -} - -export type WorkerRayhitEvent = { - data: { - body: string | null - distance: number - hasHit: boolean - hitFaceIndex: number - hitNormalWorld: number[] - hitPointWorld: number[] - op: 'event' - ray: { - collisionFilterGroup: number - collisionFilterMask: number - direction: number[] - from: number[] - to: number[] - uuid: string - } - rayFromWorld: number[] - rayToWorld: number[] - shape: (Omit & { body: string }) | null - shouldStop: boolean - type: 'rayhit' +import type { CannonWorkerProps } from './worker/cannon-worker' +import { CannonWorker } from './worker/cannon-worker' + +export type ProviderProps = PropsWithChildren< + CannonWorkerProps & { + maxSubSteps?: number + shouldInvalidate?: boolean + stepSize?: number } -} -export type WorkerCollideBeginEvent = { - data: { - bodyA: string - bodyB: string - op: 'event' - type: 'collideBegin' - } -} -export type WorkerCollideEndEvent = { - data: { - bodyA: string - bodyB: string - op: 'event' - type: 'collideEnd' - } -} -type WorkerEventMessage = - | WorkerCollideBeginEvent - | WorkerCollideEndEvent - | WorkerCollideEvent - | WorkerRayhitEvent - -type IncomingWorkerMessage = WorkerEventMessage | WorkerFrameMessage +> const v = new Vector3() const s = new Vector3(1, 1, 1) const q = new Quaternion() const m = new Matrix4() -function apply(index: number, buffers: Buffers, object?: Object3D) { +function apply(index: number, positions: Float32Array, quaternions: Float32Array, object?: Object3D) { if (index !== undefined) { m.compose( - v.fromArray(buffers.positions, index * 3), - q.fromArray(buffers.quaternions, index * 4), + v.fromArray(positions, index * 3), + q.fromArray(quaternions, index * 4), object ? object.scale : s, ) if (object) { @@ -168,34 +60,15 @@ export function Provider({ shouldInvalidate = true, size = 1000, solver = 'GS', - step = 1 / 60, + stepSize = 1 / 60, + maxSubSteps = 5, tolerance = 0.001, }: ProviderProps): JSX.Element { const { invalidate } = useThree() - const [worker] = useState(() => new CannonWorker() as Worker) - const [refs] = useState({}) - const [buffers] = useState(() => ({ - positions: new Float32Array(size * 3), - quaternions: new Float32Array(size * 4), - })) - const [events] = useState({}) - const [subscriptions] = useState({}) - - const bodies = useRef<{ [uuid: string]: number }>({}) - const loop = useCallback(() => { - if (buffers.positions.byteLength !== 0 && buffers.quaternions.byteLength !== 0) { - worker.postMessage({ op: 'step', ...buffers }, [buffers.positions.buffer, buffers.quaternions.buffer]) - } - }, []) - - // Run loop *after* all the physics objects have ran theirs! - // Otherwise the buffers will be invalidated by the browser - useFrame(loop) - useLayoutEffect(() => { - worker.postMessage({ - op: 'init', - props: { + const [worker] = useState( + () => + new CannonWorker({ allowSleep, axisIndex, broadphase, @@ -204,120 +77,150 @@ export function Provider({ iterations, quatNormalizeFast, quatNormalizeSkip, + size, solver, - step, tolerance, - }, + }), + ) + const [refs] = useState({}) + const [events] = useState({}) + const [subscriptions] = useState({}) + + const bodies = useRef<{ [uuid: string]: number }>({}) + + const loop = useCallback((_, delta) => { + worker.step(stepSize, delta, maxSubSteps) + }, []) + + const collideHandler = ({ + body, + contact: { bi, bj, ...contactRest }, + target, + ...rest + }: WorkerCollideEvent['data']) => { + const cb = events[target]?.collide + cb && + cb({ + body: refs[body], + contact: { + bi: refs[bi], + bj: refs[bj], + ...contactRest, + }, + target: refs[target], + ...rest, + }) + } + + const collideBeginHandler = ({ bodyA, bodyB }: WorkerCollideBeginEvent['data']) => { + const cbA = events[bodyA]?.collideBegin + cbA && + cbA({ + body: refs[bodyB], + op: 'event', + target: refs[bodyA], + type: 'collideBegin', + }) + const cbB = events[bodyB]?.collideBegin + cbB && + cbB({ + body: refs[bodyA], + op: 'event', + target: refs[bodyB], + type: 'collideBegin', + }) + } + + const collideEndHandler = ({ bodyA, bodyB }: WorkerCollideEndEvent['data']) => { + const cbA = events[bodyA]?.collideEnd + cbA && + cbA({ + body: refs[bodyB], + op: 'event', + target: refs[bodyA], + type: 'collideEnd', + }) + const cbB = events[bodyB]?.collideEnd + cbB && + cbB({ + body: refs[bodyA], + op: 'event', + target: refs[bodyB], + type: 'collideEnd', + }) + } + + const frameHandler = ({ + active, + bodies: uuids = [], + observations, + positions, + quaternions, + }: WorkerFrameMessage['data']) => { + for (let i = 0; i < uuids.length; i++) { + bodies.current[uuids[i]] = i + } + observations.forEach(([id, value, type]) => { + const subscription = subscriptions[id] || {} + const cb = subscription[type] + // HELP: We clearly know the type of the callback, but typescript can't deal with it + cb && cb(value as never) }) - let i = 0 - let body: string - let callback - worker.onmessage = (e: IncomingWorkerMessage) => { - switch (e.data.op) { - case 'frame': - buffers.positions = e.data.positions - buffers.quaternions = e.data.quaternions - if (e.data.bodies) { - for (i = 0; i < e.data.bodies.length; i++) { - body = e.data.bodies[i] - bodies.current[body] = e.data.bodies.indexOf(body) + if (active) { + for (const ref of Object.values(refs)) { + if (ref instanceof InstancedMesh) { + for (let i = 0; i < ref.count; i++) { + const index = bodies.current[`${ref.uuid}/${i}`] + if (index !== undefined) { + ref.setMatrixAt(i, apply(index, positions, quaternions)) } + ref.instanceMatrix.needsUpdate = true } + } else { + apply(bodies.current[ref.uuid], positions, quaternions, ref) + } + } + if (shouldInvalidate) { + invalidate() + } + } + } - e.data.observations.forEach(([id, value, type]) => { - const subscription = subscriptions[id] || {} - callback = subscription[type] || noop - // HELP: We clearly know the type of the callback, but typescript can't deal with it - callback(value as never) - }) + const rayhitHandler = ({ body, ray: { uuid, ...rayRest }, ...rest }: WorkerRayhitEvent['data']) => { + const cb = events[uuid]?.rayhit + cb && + cb({ + body: body ? refs[body] : null, + ray: { uuid, ...rayRest }, + ...rest, + }) + } - if (e.data.active) { - for (const ref of Object.values(refs)) { - if (ref instanceof InstancedMesh) { - for (let i = 0; i < ref.count; i++) { - const index = bodies.current[`${ref.uuid}/${i}`] - if (index !== undefined) { - ref.setMatrixAt(i, apply(index, buffers)) - } - ref.instanceMatrix.needsUpdate = true - } - } else { - apply(bodies.current[ref.uuid], buffers, ref) - } - } - if (shouldInvalidate) { - invalidate() - } - } + // Run loop *after* all the physics objects have ran theirs! + // Otherwise the buffers will be invalidated by the browser + useFrame(loop) - break - case 'event': - switch (e.data.type) { - case 'collide': - callback = events[e.data.target]?.collide || noop - callback({ - ...e.data, - body: refs[e.data.body], - contact: { - ...e.data.contact, - bi: refs[e.data.contact.bi], - bj: refs[e.data.contact.bj], - }, - target: refs[e.data.target], - }) - break - case 'collideBegin': - callback = events[e.data.bodyA]?.collideBegin || noop - callback({ - body: refs[e.data.bodyB], - op: 'event', - target: refs[e.data.bodyA], - type: 'collideBegin', - }) - callback = events[e.data.bodyB]?.collideBegin || noop - callback({ - body: refs[e.data.bodyA], - op: 'event', - target: refs[e.data.bodyB], - type: 'collideBegin', - }) - break - case 'collideEnd': - callback = events[e.data.bodyA]?.collideEnd || noop - callback({ - body: refs[e.data.bodyB], - op: 'event', - target: refs[e.data.bodyA], - type: 'collideEnd', - }) - callback = events[e.data.bodyB]?.collideEnd || noop - callback({ - body: refs[e.data.bodyA], - op: 'event', - target: refs[e.data.bodyB], - type: 'collideEnd', - }) - break - case 'rayhit': - callback = events[e.data.ray.uuid]?.rayhit || noop - callback({ - ...e.data, - body: e.data.body ? refs[e.data.body] : null, - }) - break - } - break - } + useLayoutEffect(() => { + worker.init() + + worker.on('collide', collideHandler) + worker.on('collideBegin', collideBeginHandler) + worker.on('collideEnd', collideEndHandler) + worker.on('frame', frameHandler) + worker.on('rayhit', rayhitHandler) + + return () => { + worker.terminate() + worker.removeAllListeners() } - return () => worker.terminate() }, []) - useUpdateWorldPropsEffect({ axisIndex, broadphase, gravity, iterations, step, tolerance, worker }) + useUpdateWorldPropsEffect({ axisIndex, broadphase, gravity, iterations, tolerance, worker }) - const api: ProviderContext = useMemo( - () => ({ bodies, buffers, events, refs, subscriptions, worker }), - [bodies, buffers, events, refs, subscriptions, worker], + const value: ProviderContext = useMemo( + () => ({ bodies, events, refs, subscriptions, worker }), + [bodies, events, refs, subscriptions, worker], ) - return {children} + return {children} } diff --git a/src/hooks.ts b/src/hooks.ts index e56da211..04269598 100644 --- a/src/hooks.ts +++ b/src/hooks.ts @@ -1,25 +1,26 @@ -import type { ContactMaterialOptions, MaterialOptions } from 'cannon-es' +import type { ContactMaterialOptions, MaterialOptions, RayOptions as RayOptionsImpl } from 'cannon-es' import type { DependencyList, MutableRefObject, Ref, RefObject } from 'react' import { useContext, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react' import { DynamicDrawUsage, Euler, InstancedMesh, MathUtils, Object3D, Quaternion, Vector3 } from 'three' import type { - AddRayMessage, AtomicName, - CannonWorker, CollideBeginEvent, CollideEndEvent, CollideEvent, PropValue, ProviderContext, + Quad, RayhitEvent, RayMode, SetOpName, SubscriptionName, SubscriptionTarget, + Triplet, VectorName, } from './setup' import { context, debugContext } from './setup' +import type { CannonWorker } from './worker/cannon-worker' export type AtomicProps = { allowSleep: boolean @@ -37,13 +38,9 @@ export type AtomicProps = { userData: {} } -export type Triplet = [x: number, y: number, z: number] - export type VectorProps = Record type VectorTypes = Vector3 | Triplet -export type Quad = [x: number, y: number, z: number, w: number] - export type BodyProps = Partial & Partial & { args?: T @@ -180,6 +177,34 @@ export interface SpringOptns { worldAnchorB?: Triplet } +export type RayOptions = { + from?: Triplet + mode: RayMode + to?: Triplet +} & Pick< + RayOptionsImpl, + 'checkCollisionResponse' | 'collisionFilterGroup' | 'collisionFilterMask' | 'skipBackfaces' +> + +export interface WheelInfoOptions { + axleLocal?: Triplet + chassisConnectionPointLocal?: Triplet + customSlidingRotationalSpeed?: number + dampingCompression?: number + dampingRelaxation?: number + directionLocal?: Triplet + frictionSlip?: number + isFrontWheel?: boolean + maxSuspensionForce?: number + maxSuspensionTravel?: number + radius?: number + rollInfluence?: number + sideAcceleration?: number + suspensionRestLength?: number + suspensionStiffness?: number + useCustomSlidingRotationalSpeed?: boolean +} + const temp = new Object3D() function useForwardedRef(ref: Ref): MutableRefObject { @@ -218,10 +243,10 @@ function subscribe( const id = incrementingId++ subscriptions[id] = { [type]: callback } const uuid = getUUID(ref, index) - uuid && worker.postMessage({ op: 'subscribe', props: { id, target, type }, uuid }) + uuid && worker.subscribe({ props: { id, target, type }, uuid }) return () => { delete subscriptions[id] - worker.postMessage({ op: 'unsubscribe', props: id }) + worker.unsubscribe({ props: id }) } } } @@ -299,8 +324,7 @@ function useBody>( }) // Register on mount, unregister on unmount - currentWorker.postMessage({ - op: 'addBodies', + currentWorker.addBodies({ props: props.map(({ onCollide, onCollideBegin, onCollideEnd, ...serializableProps }) => { return { onCollide: Boolean(onCollide), ...serializableProps } }), @@ -313,48 +337,51 @@ function useBody>( if (debugApi) debugApi.remove(id) delete events[id] }) - currentWorker.postMessage({ op: 'removeBodies', uuid }) + currentWorker.removeBodies({ uuid }) } }, deps) const api = useMemo(() => { const makeAtomic = (type: T, index?: number) => { const op: SetOpName = `set${capitalize(type)}` + return { set: (value: PropValue) => { const uuid = getUUID(ref, index) - uuid && worker.postMessage({ op, props: value, uuid }) + uuid && + worker[op]({ + props: value, + uuid, + } as never) }, subscribe: subscribe(ref, worker, subscriptions, type, index), } } const makeQuaternion = (index?: number) => { - const op = 'setQuaternion' const type = 'quaternion' return { copy: ({ w, x, y, z }: Quaternion) => { const uuid = getUUID(ref, index) - uuid && worker.postMessage({ op, props: [x, y, z, w], uuid }) + uuid && worker.setQuaternion({ props: [x, y, z, w], uuid }) }, set: (x: number, y: number, z: number, w: number) => { const uuid = getUUID(ref, index) - uuid && worker.postMessage({ op, props: [x, y, z, w], uuid }) + uuid && worker.setQuaternion({ props: [x, y, z, w], uuid }) }, subscribe: subscribe(ref, worker, subscriptions, type, index), } } const makeRotation = (index?: number) => { - const op = 'setRotation' return { copy: ({ x, y, z }: Vector3 | Euler) => { const uuid = getUUID(ref, index) - uuid && worker.postMessage({ op, props: [x, y, z], uuid }) + uuid && worker.setRotation({ props: [x, y, z], uuid }) }, set: (x: number, y: number, z: number) => { const uuid = getUUID(ref, index) - uuid && worker.postMessage({ op, props: [x, y, z], uuid }) + uuid && worker.setRotation({ props: [x, y, z], uuid }) }, subscribe: (callback: (value: Triplet) => void) => { const id = incrementingId++ @@ -363,10 +390,10 @@ function useBody>( const uuid = getUUID(ref, index) subscriptions[id] = { [type]: quaternionToRotation(callback) } - uuid && worker.postMessage({ op: 'subscribe', props: { id, target, type }, uuid }) + uuid && worker.subscribe({ props: { id, target, type }, uuid }) return () => { delete subscriptions[id] - worker.postMessage({ op: 'unsubscribe', props: id }) + worker.unsubscribe({ props: id }) } }, } @@ -377,11 +404,11 @@ function useBody>( return { copy: ({ x, y, z }: Vector3 | Euler) => { const uuid = getUUID(ref, index) - uuid && worker.postMessage({ op, props: [x, y, z], uuid }) + uuid && worker[op]({ props: [x, y, z], uuid }) }, set: (x: number, y: number, z: number) => { const uuid = getUUID(ref, index) - uuid && worker.postMessage({ op, props: [x, y, z], uuid }) + uuid && worker[op]({ props: [x, y, z], uuid }) }, subscribe: subscribe(ref, worker, subscriptions, type, index), } @@ -395,23 +422,23 @@ function useBody>( angularVelocity: makeVec('angularVelocity', index), applyForce(force: Triplet, worldPoint: Triplet) { const uuid = getUUID(ref, index) - uuid && worker.postMessage({ op: 'applyForce', props: [force, worldPoint], uuid }) + uuid && worker.applyForce({ props: [force, worldPoint], uuid }) }, applyImpulse(impulse: Triplet, worldPoint: Triplet) { const uuid = getUUID(ref, index) - uuid && worker.postMessage({ op: 'applyImpulse', props: [impulse, worldPoint], uuid }) + uuid && worker.applyImpulse({ props: [impulse, worldPoint], uuid }) }, applyLocalForce(force: Triplet, localPoint: Triplet) { const uuid = getUUID(ref, index) - uuid && worker.postMessage({ op: 'applyLocalForce', props: [force, localPoint], uuid }) + uuid && worker.applyLocalForce({ props: [force, localPoint], uuid }) }, applyLocalImpulse(impulse: Triplet, localPoint: Triplet) { const uuid = getUUID(ref, index) - uuid && worker.postMessage({ op: 'applyLocalImpulse', props: [impulse, localPoint], uuid }) + uuid && worker.applyLocalImpulse({ props: [impulse, localPoint], uuid }) }, applyTorque(torque: Triplet) { const uuid = getUUID(ref, index) - uuid && worker.postMessage({ op: 'applyTorque', props: [torque], uuid }) + uuid && worker.applyTorque({ props: [torque], uuid }) }, collisionFilterGroup: makeAtomic('collisionFilterGroup', index), collisionFilterMask: makeAtomic('collisionFilterMask', index), @@ -427,7 +454,7 @@ function useBody>( rotation: makeRotation(index), sleep() { const uuid = getUUID(ref, index) - uuid && worker.postMessage({ op: 'sleep', uuid }) + uuid && worker.sleep({ uuid }) }, sleepSpeedLimit: makeAtomic('sleepSpeedLimit', index), sleepTimeLimit: makeAtomic('sleepTimeLimit', index), @@ -435,7 +462,7 @@ function useBody>( velocity: makeVec('velocity', index), wakeUp() { const uuid = getUUID(ref, index) - uuid && worker.postMessage({ op: 'wakeUp', uuid }) + uuid && worker.wakeUp({ uuid }) }, } } @@ -568,7 +595,7 @@ function useConstraint( type: T, bodyA: Ref, bodyB: Ref, - optns: any = {}, + optns: ConstraintOptns | HingeConstraintOpts = {}, deps: DependencyList = [], ): ConstraintORHingeApi { const { worker } = useContext(context) @@ -579,31 +606,28 @@ function useConstraint( useEffect(() => { if (refA.current && refB.current) { - worker.postMessage({ - op: 'addConstraint', + worker.addConstraint({ props: [refA.current.uuid, refB.current.uuid, optns], type, uuid, }) - return () => worker.postMessage({ op: 'removeConstraint', uuid }) + return () => worker.removeConstraint({ uuid }) } }, deps) const api = useMemo(() => { const enableDisable = { - disable: () => worker.postMessage({ op: 'disableConstraint', uuid }), - enable: () => worker.postMessage({ op: 'enableConstraint', uuid }), + disable: () => worker.disableConstraint({ uuid }), + enable: () => worker.enableConstraint({ uuid }), } if (type === 'Hinge') { return { ...enableDisable, - disableMotor: () => worker.postMessage({ op: 'disableConstraintMotor', uuid }), - enableMotor: () => worker.postMessage({ op: 'enableConstraintMotor', uuid }), - setMotorMaxForce: (value: number) => - worker.postMessage({ op: 'setConstraintMotorMaxForce', props: value, uuid }), - setMotorSpeed: (value: number) => - worker.postMessage({ op: 'setConstraintMotorSpeed', props: value, uuid }), + disableMotor: () => worker.disableConstraintMotor({ uuid }), + enableMotor: () => worker.enableConstraintMotor({ uuid }), + setMotorMaxForce: (value: number) => worker.setConstraintMotorMaxForce({ props: value, uuid }), + setMotorSpeed: (value: number) => worker.setConstraintMotorSpeed({ props: value, uuid }), } } @@ -668,22 +692,21 @@ export function useSpring( useEffect(() => { if (refA.current && refB.current) { - worker.postMessage({ - op: 'addSpring', + worker.addSpring({ props: [refA.current.uuid, refB.current.uuid, optns], uuid, }) return () => { - worker.postMessage({ op: 'removeSpring', uuid }) + worker.removeSpring({ uuid }) } } }, deps) const api = useMemo( () => ({ - setDamping: (value: number) => worker.postMessage({ op: 'setSpringDamping', props: value, uuid }), - setRestLength: (value: number) => worker.postMessage({ op: 'setSpringRestLength', props: value, uuid }), - setStiffness: (value: number) => worker.postMessage({ op: 'setSpringStiffness', props: value, uuid }), + setDamping: (value: number) => worker.setSpringDamping({ props: value, uuid }), + setRestLength: (value: number) => worker.setSpringRestLength({ props: value, uuid }), + setStiffness: (value: number) => worker.setSpringStiffness({ props: value, uuid }), }), deps, ) @@ -691,8 +714,6 @@ export function useSpring( return [refA, refB, api] } -type RayOptions = Omit - function useRay( mode: RayMode, options: RayOptions, @@ -703,9 +724,9 @@ function useRay( const [uuid] = useState(() => MathUtils.generateUUID()) useEffect(() => { events[uuid] = { rayhit: callback } - worker.postMessage({ op: 'addRay', props: { mode, ...options }, uuid }) + worker.addRay({ props: { ...options, mode }, uuid }) return () => { - worker.postMessage({ op: 'removeRay', uuid }) + worker.removeRay({ uuid }) delete events[uuid] } }, deps) @@ -744,23 +765,8 @@ export interface RaycastVehiclePublicApi { } } -export interface WheelInfoOptions { - axleLocal?: Triplet - chassisConnectionPointLocal?: Triplet - customSlidingRotationalSpeed?: number - dampingCompression?: number - dampingRelaxation?: number - directionLocal?: Triplet - frictionSlip?: number - isFrontWheel?: boolean - maxSuspensionForce?: number - maxSuspensionTravel?: number - radius?: number - rollInfluence?: number - sideAcceleration?: number - suspensionRestLength?: number - suspensionStiffness?: number - useCustomSlidingRotationalSpeed?: boolean +function isString(v: unknown): v is string { + return typeof v === 'string' } export interface RaycastVehicleProps { @@ -772,10 +778,6 @@ export interface RaycastVehicleProps { wheels: Ref[] } -function isString(v: unknown): v is string { - return typeof v === 'string' -} - export function useRaycastVehicle( fn: () => RaycastVehicleProps, fwdRef: Ref = null, @@ -807,13 +809,12 @@ export function useRaycastVehicle( if (!chassisBodyUUID || !wheelUUIDs.every(isString)) return - currentWorker.postMessage({ - op: 'addRaycastVehicle', + currentWorker.addRaycastVehicle({ props: [chassisBodyUUID, wheelUUIDs, wheelInfos, indexForwardAxis, indexRightAxis, indexUpAxis], uuid, }) return () => { - currentWorker.postMessage({ op: 'removeRaycastVehicle', uuid }) + currentWorker.removeRaycastVehicle({ uuid }) } }, deps) @@ -821,15 +822,23 @@ export function useRaycastVehicle( return { applyEngineForce(value: number, wheelIndex: number) { const uuid = getUUID(ref) - uuid && worker.postMessage({ op: 'applyRaycastVehicleEngineForce', props: [value, wheelIndex], uuid }) + uuid && + worker.applyRaycastVehicleEngineForce({ + props: [value, wheelIndex], + uuid, + }) }, setBrake(brake: number, wheelIndex: number) { const uuid = getUUID(ref) - uuid && worker.postMessage({ op: 'setRaycastVehicleBrake', props: [brake, wheelIndex], uuid }) + uuid && worker.setRaycastVehicleBrake({ props: [brake, wheelIndex], uuid }) }, setSteeringValue(value: number, wheelIndex: number) { const uuid = getUUID(ref) - uuid && worker.postMessage({ op: 'setRaycastVehicleSteeringValue', props: [value, wheelIndex], uuid }) + uuid && + worker.setRaycastVehicleSteeringValue({ + props: [value, wheelIndex], + uuid, + }) }, sliding: { subscribe: subscribe(ref, worker, subscriptions, 'sliding', undefined, 'vehicles'), @@ -849,13 +858,12 @@ export function useContactMaterial( const [uuid] = useState(() => MathUtils.generateUUID()) useEffect(() => { - worker.postMessage({ - op: 'addContactMaterial', + worker.addContactMaterial({ props: [materialA, materialB, options], uuid, }) return () => { - worker.postMessage({ op: 'removeContactMaterial', uuid }) + worker.removeContactMaterial({ uuid }) } }, deps) } diff --git a/src/setup.ts b/src/setup.ts index ccb2020e..0d86cd3d 100644 --- a/src/setup.ts +++ b/src/setup.ts @@ -1,19 +1,24 @@ -import type { ContactMaterialOptions, MaterialOptions, RayOptions } from 'cannon-es' +import type { ContactMaterial, ContactMaterialOptions, MaterialOptions, Shape } from 'cannon-es' import type { MutableRefObject } from 'react' import { createContext } from 'react' import type { Object3D } from 'three' +import type { CannonWorker } from 'worker/cannon-worker' import type { AtomicProps, BodyProps, BodyShapeType, ConstraintTypes, - Quad, + RayOptions, SpringOptns, - Triplet, WheelInfoOptions, } from './hooks' -import type { ProviderProps, WorkerCollideEvent, WorkerRayhitEvent } from './Provider' + +export type Triplet = [x: number, y: number, z: number] +export type Quad = [x: number, y: number, z: number, w: number] + +export type Broadphase = 'Naive' | 'SAP' +export type Solver = 'GS' | 'Split' export type Buffers = { positions: Float32Array; quaternions: Float32Array } export type Refs = { [uuid: string]: Object3D } @@ -94,17 +99,16 @@ export type SubscriptionName = typeof subscriptionNames[number] export type SetOpName = `set${Capitalize}` -type Operation = { op: T } & (P extends void ? {} : { props: P }) -type WithUUID = Operation & { uuid: string } -type WithUUIDs = Operation & { uuid: string[] } +type Operation = { op: T } & (P extends null ? {} : { props: P }) +type WithUUID = Operation & { uuid: string } +type WithUUIDs = Operation & { uuid: string[] } -type AddConstraintMessage = WithUUID<'addConstraint', [uuidA: string, uuidB: string, options: {}]> & { +export type AddConstraintMessage = WithUUID<'addConstraint', [uuidA: string, uuidB: string, options: {}]> & { type: 'Hinge' | ConstraintTypes } - -type DisableConstraintMessage = WithUUID<'disableConstraint'> -type EnableConstraintMessage = WithUUID<'enableConstraint'> -type RemoveConstraintMessage = WithUUID<'removeConstraint'> +export type DisableConstraintMessage = WithUUID<'disableConstraint'> +export type EnableConstraintMessage = WithUUID<'enableConstraint'> +export type RemoveConstraintMessage = WithUUID<'removeConstraint'> type ConstraintMessage = | AddConstraintMessage @@ -112,10 +116,10 @@ type ConstraintMessage = | EnableConstraintMessage | RemoveConstraintMessage -type DisableConstraintMotorMessage = WithUUID<'disableConstraintMotor'> -type EnableConstraintMotorMessage = WithUUID<'enableConstraintMotor'> -type SetConstraintMotorMaxForce = WithUUID<'setConstraintMotorMaxForce', number> -type SetConstraintMotorSpeed = WithUUID<'setConstraintMotorSpeed', number> +export type DisableConstraintMotorMessage = WithUUID<'disableConstraintMotor'> +export type EnableConstraintMotorMessage = WithUUID<'enableConstraintMotor'> +export type SetConstraintMotorMaxForce = WithUUID<'setConstraintMotorMaxForce', number> +export type SetConstraintMotorSpeed = WithUUID<'setConstraintMotorSpeed', number> type ConstraintMotorMessage = | DisableConstraintMotorMessage @@ -123,12 +127,12 @@ type ConstraintMotorMessage = | SetConstraintMotorSpeed | SetConstraintMotorMaxForce -type AddSpringMessage = WithUUID<'addSpring', [uuidA: string, uuidB: string, options: SpringOptns]> -type RemoveSpringMessage = WithUUID<'removeSpring'> +export type AddSpringMessage = WithUUID<'addSpring', [uuidA: string, uuidB: string, options: SpringOptns]> +export type RemoveSpringMessage = WithUUID<'removeSpring'> -type SetSpringDampingMessage = WithUUID<'setSpringDamping', number> -type SetSpringRestLengthMessage = WithUUID<'setSpringRestLength', number> -type SetSpringStiffnessMessage = WithUUID<'setSpringStiffness', number> +export type SetSpringDampingMessage = WithUUID<'setSpringDamping', number> +export type SetSpringRestLengthMessage = WithUUID<'setSpringRestLength', number> +export type SetSpringStiffnessMessage = WithUUID<'setSpringStiffness', number> type SpringMessage = | AddSpringMessage @@ -141,7 +145,7 @@ export type AddContactMaterialMessage = WithUUID< 'addContactMaterial', [materialA: MaterialOptions, materialB: MaterialOptions, options: ContactMaterialOptions] > -type RemoveContactMaterialMessage = WithUUID<'removeContactMaterial'> +export type RemoveContactMaterialMessage = WithUUID<'removeContactMaterial'> type ContactMaterialMessage = AddContactMaterialMessage | RemoveContactMaterialMessage export type RayMode = 'Closest' | 'Any' | 'All' @@ -157,11 +161,11 @@ export type AddRayMessage = WithUUID< 'checkCollisionResponse' | 'collisionFilterGroup' | 'collisionFilterMask' | 'skipBackfaces' > > -type RemoveRayMessage = WithUUID<'removeRay'> +export type RemoveRayMessage = WithUUID<'removeRay'> type RayMessage = AddRayMessage | RemoveRayMessage -type AddRaycastVehicleMessage = WithUUIDs< +export type AddRaycastVehicleMessage = WithUUIDs< 'addRaycastVehicle', [ chassisBodyUUID: string, @@ -172,14 +176,17 @@ type AddRaycastVehicleMessage = WithUUIDs< indexUpAxis: number, ] > -type RemoveRaycastVehicleMessage = WithUUIDs<'removeRaycastVehicle'> +export type RemoveRaycastVehicleMessage = WithUUIDs<'removeRaycastVehicle'> -type ApplyRaycastVehicleEngineForceMessage = WithUUID< +export type ApplyRaycastVehicleEngineForceMessage = WithUUID< 'applyRaycastVehicleEngineForce', [value: number, wheelIndex: number] > -type SetRaycastVehicleBrakeMessage = WithUUID<'setRaycastVehicleBrake', [brake: number, wheelIndex: number]> -type SetRaycastVehicleSteeringValueMessage = WithUUID< +export type SetRaycastVehicleBrakeMessage = WithUUID< + 'setRaycastVehicleBrake', + [brake: number, wheelIndex: number] +> +export type SetRaycastVehicleSteeringValueMessage = WithUUID< 'setRaycastVehicleSteeringValue', [value: number, wheelIndex: number] > @@ -191,16 +198,19 @@ type RaycastVehicleMessage = | SetRaycastVehicleBrakeMessage | SetRaycastVehicleSteeringValueMessage -type AtomicMessage = WithUUID, any> -type QuaternionMessage = WithUUID, Quad> -type RotationMessage = WithUUID, Triplet> -type VectorMessage = WithUUID, Triplet> +export type AtomicMessage = WithUUID< + SetOpName, + T extends AtomicName ? PropValue : any +> +export type QuaternionMessage = WithUUID, Quad> +export type RotationMessage = WithUUID, Triplet> +export type VectorMessage = WithUUID, Triplet> -type ApplyForceMessage = WithUUID<'applyForce', [force: Triplet, worldPoint: Triplet]> -type ApplyImpulseMessage = WithUUID<'applyImpulse', [impulse: Triplet, worldPoint: Triplet]> -type ApplyLocalForceMessage = WithUUID<'applyLocalForce', [force: Triplet, localPoint: Triplet]> -type ApplyLocalImpulseMessage = WithUUID<'applyLocalImpulse', [impulse: Triplet, localPoint: Triplet]> -type ApplyTorque = WithUUID<'applyTorque', [torque: Triplet]> +export type ApplyForceMessage = WithUUID<'applyForce', [force: Triplet, worldPoint: Triplet]> +export type ApplyImpulseMessage = WithUUID<'applyImpulse', [impulse: Triplet, worldPoint: Triplet]> +export type ApplyLocalForceMessage = WithUUID<'applyLocalForce', [force: Triplet, localPoint: Triplet]> +export type ApplyLocalImpulseMessage = WithUUID<'applyLocalImpulse', [impulse: Triplet, localPoint: Triplet]> +export type ApplyTorque = WithUUID<'applyTorque', [torque: Triplet]> type ApplyMessage = | ApplyForceMessage @@ -213,17 +223,17 @@ type SerializableBodyProps = { onCollide: boolean } -type AddBodiesMessage = WithUUIDs<'addBodies', SerializableBodyProps[]> & { type: BodyShapeType } -type RemoveBodiesMessage = WithUUIDs<'removeBodies'> +export type AddBodiesMessage = WithUUIDs<'addBodies', SerializableBodyProps[]> & { type: BodyShapeType } +export type RemoveBodiesMessage = WithUUIDs<'removeBodies'> type BodiesMessage = AddBodiesMessage | RemoveBodiesMessage -type SleepMessage = WithUUID<'sleep'> -type WakeUpMessage = WithUUID<'wakeUp'> +export type SleepMessage = WithUUID<'sleep'> +export type WakeUpMessage = WithUUID<'wakeUp'> export type SubscriptionTarget = 'bodies' | 'vehicles' -type SubscribeMessage = WithUUID< +export type SubscribeMessage = WithUUID< 'subscribe', { id: number @@ -231,39 +241,171 @@ type SubscribeMessage = WithUUID< type: SubscriptionName } > -type UnsubscribeMessage = Operation<'unsubscribe', number> +export type UnsubscribeMessage = Operation<'unsubscribe', number> type SubscriptionMessage = SubscribeMessage | UnsubscribeMessage -export type WorldPropName = 'axisIndex' | 'broadphase' | 'gravity' | 'iterations' | 'step' | 'tolerance' +export type Observation = { [K in AtomicName]: [id: number, value: PropValue, type: K] }[AtomicName] + +export type WorkerFrameMessage = { + data: Buffers & { + active: boolean + bodies?: string[] + observations: Observation[] + op: 'frame' + } +} + +export type WorkerCollideEvent = { + data: { + body: string + collisionFilters: { + bodyFilterGroup: number + bodyFilterMask: number + targetFilterGroup: number + targetFilterMask: number + } + contact: { + bi: string + bj: string + /** Normal of the contact, relative to the colliding body */ + contactNormal: number[] + /** Contact point in world space */ + contactPoint: number[] + id: string + impactVelocity: number + ni: number[] + ri: number[] + rj: number[] + } + op: 'event' + target: string + type: 'collide' + } +} + +export type WorkerRayhitEvent = { + data: { + body: string | null + distance: number + hasHit: boolean + hitFaceIndex: number + hitNormalWorld: number[] + hitPointWorld: number[] + op: 'event' + ray: { + collisionFilterGroup: number + collisionFilterMask: number + direction: number[] + from: number[] + to: number[] + uuid: string + } + rayFromWorld: number[] + rayToWorld: number[] + shape: (Omit & { body: string }) | null + shouldStop: boolean + type: 'rayhit' + } +} +export type WorkerCollideBeginEvent = { + data: { + bodyA: string + bodyB: string + op: 'event' + type: 'collideBegin' + } +} +export type WorkerCollideEndEvent = { + data: { + bodyA: string + bodyB: string + op: 'event' + type: 'collideEnd' + } +} +export type WorkerEventMessage = + | WorkerCollideBeginEvent + | WorkerCollideEndEvent + | WorkerCollideEvent + | WorkerRayhitEvent + +export type IncomingWorkerMessage = WorkerEventMessage | WorkerFrameMessage + +export type WorldPropName = 'axisIndex' | 'broadphase' | 'gravity' | 'iterations' | 'tolerance' + +export type DefaultContactMaterial = Partial< + Pick< + ContactMaterial, + | 'contactEquationRelaxation' + | 'contactEquationStiffness' + | 'friction' + | 'frictionEquationRelaxation' + | 'frictionEquationStiffness' + | 'restitution' + > +> + +export type InitMessage = Operation< + 'init', + { + allowSleep: boolean + axisIndex: number + broadphase: Broadphase + defaultContactMaterial: DefaultContactMaterial + gravity: Triplet + iterations: number + quatNormalizeFast: boolean + quatNormalizeSkip: number + solver: Solver + tolerance: number + } +> + +export type StepMessage = Operation< + 'step', + { + dt?: number + maxSubSteps?: number + stepSize: number + } +> & { + positions: Float32Array + quaternions: Float32Array +} -type WorldMessage = Operation, Required> +type WorldMessage = Operation, Required> -type CannonMessage = +export type CannonMessage = | ApplyMessage | AtomicMessage | BodiesMessage | ConstraintMessage | ConstraintMotorMessage + | InitMessage | QuaternionMessage | RaycastVehicleMessage | RayMessage | RotationMessage | SleepMessage | SpringMessage + | StepMessage | ContactMaterialMessage | SubscriptionMessage | VectorMessage | WakeUpMessage | WorldMessage -export interface CannonWorker extends Worker { - postMessage: (message: CannonMessage) => void +export interface CannonWebWorker { + onmessage: (e: IncomingWorkerMessage) => void + postMessage: + | ((message: CannonMessage) => void) + | ((message: CannonMessage, transferables?: Transferable[]) => void) + terminate: () => void } export type ProviderContext = { bodies: MutableRefObject<{ [uuid: string]: number }> - buffers: Buffers events: CannonEvents refs: Refs subscriptions: Subscriptions diff --git a/src/useUpdateWorldPropsEffect.ts b/src/useUpdateWorldPropsEffect.ts index f6a83f46..52a393c5 100644 --- a/src/useUpdateWorldPropsEffect.ts +++ b/src/useUpdateWorldPropsEffect.ts @@ -1,7 +1,8 @@ import { useEffect } from 'react' import type { ProviderProps } from './Provider' -import type { CannonWorker, WorldPropName } from './setup' +import type { WorldPropName } from './setup' +import type { CannonWorker } from './worker/cannon-worker' type Props = Pick, WorldPropName> & { worker: CannonWorker } @@ -10,14 +11,22 @@ export function useUpdateWorldPropsEffect({ broadphase, gravity, iterations, - step, tolerance, worker, }: Props) { - useEffect(() => void worker.postMessage({ op: 'setAxisIndex', props: axisIndex }), [axisIndex]) - useEffect(() => void worker.postMessage({ op: 'setBroadphase', props: broadphase }), [broadphase]) - useEffect(() => void worker.postMessage({ op: 'setGravity', props: gravity }), [gravity]) - useEffect(() => void worker.postMessage({ op: 'setIterations', props: iterations }), [iterations]) - useEffect(() => void worker.postMessage({ op: 'setStep', props: step }), [step]) - useEffect(() => void worker.postMessage({ op: 'setTolerance', props: tolerance }), [tolerance]) + useEffect(() => { + worker.axisIndex = axisIndex + }, [axisIndex]) + useEffect(() => { + worker.broadphase = broadphase + }, [broadphase]) + useEffect(() => { + worker.gravity = gravity + }, [gravity]) + useEffect(() => { + worker.iterations = iterations + }, [iterations]) + useEffect(() => { + worker.tolerance = tolerance + }, [tolerance]) } diff --git a/src/worker/cannon-worker.ts b/src/worker/cannon-worker.ts new file mode 100644 index 00000000..a79d5272 --- /dev/null +++ b/src/worker/cannon-worker.ts @@ -0,0 +1,465 @@ +import EventEmitter from 'events' + +import type { + AddBodiesMessage, + AddConstraintMessage, + AddContactMaterialMessage, + AddRaycastVehicleMessage, + AddRayMessage, + AddSpringMessage, + ApplyForceMessage, + ApplyImpulseMessage, + ApplyLocalForceMessage, + ApplyLocalImpulseMessage, + ApplyRaycastVehicleEngineForceMessage, + ApplyTorque, + AtomicMessage, + Broadphase, + CannonMessage, + CannonWebWorker, + DefaultContactMaterial, + DisableConstraintMessage, + DisableConstraintMotorMessage, + EnableConstraintMessage, + EnableConstraintMotorMessage, + IncomingWorkerMessage, + QuaternionMessage, + RemoveBodiesMessage, + RemoveConstraintMessage, + RemoveContactMaterialMessage, + RemoveRaycastVehicleMessage, + RemoveRayMessage, + RemoveSpringMessage, + RotationMessage, + SetConstraintMotorMaxForce, + SetConstraintMotorSpeed, + SetRaycastVehicleBrakeMessage, + SetRaycastVehicleSteeringValueMessage, + SetSpringDampingMessage, + SetSpringRestLengthMessage, + SetSpringStiffnessMessage, + SleepMessage, + Solver, + SubscribeMessage, + Triplet, + VectorMessage, + WakeUpMessage, +} from '../setup' +// @ts-expect-error Types are not setup for this yet +import Worker from './worker' + +type WithoutOp = Omit + +export type CannonWorkerProps = { + allowSleep?: boolean + axisIndex?: number + broadphase?: Broadphase + defaultContactMaterial?: DefaultContactMaterial + gravity?: Triplet + iterations?: number + quatNormalizeFast?: boolean + quatNormalizeSkip?: number + size?: number + solver?: Solver + tolerance?: number +} + +export class CannonWorker extends EventEmitter { + get axisIndex(): number { + return this.config.axisIndex + } + + set axisIndex(value: number) { + this.config.axisIndex = value + this.worker.postMessage({ op: 'setAxisIndex', props: value }) + } + + get broadphase(): Broadphase { + return this.config.broadphase + } + + set broadphase(value: Broadphase) { + this.config.broadphase = value + this.worker.postMessage({ op: 'setBroadphase', props: value }) + } + + get gravity(): Triplet { + return this.config.gravity + } + + set gravity(value: Triplet) { + this.config.gravity = value + this.worker.postMessage({ op: 'setGravity', props: value }) + } + + get iterations(): number { + return this.config.iterations + } + + set iterations(value: number) { + this.config.iterations = value + this.worker.postMessage({ op: 'setIterations', props: value }) + } + + get tolerance(): number { + return this.config.tolerance + } + + set tolerance(value: number) { + this.config.tolerance = value + this.worker.postMessage({ op: 'setTolerance', props: value }) + } + + private buffers: { + positions: Float32Array + quaternions: Float32Array + } + + private config: Required + + private worker = new Worker() as CannonWebWorker + + constructor({ + allowSleep = false, + axisIndex = 0, + broadphase = 'Naive', + defaultContactMaterial = { contactEquationStiffness: 1e6 }, + gravity = [0, -9.81, 0], + iterations = 5, + quatNormalizeFast = false, + quatNormalizeSkip = 0, + size = 1000, + solver = 'GS', + tolerance = 0.001, + }: CannonWorkerProps) { + super() + + this.config = { + allowSleep, + axisIndex, + broadphase, + defaultContactMaterial, + gravity, + iterations, + quatNormalizeFast, + quatNormalizeSkip, + size, + solver, + tolerance, + } + + this.buffers = { + positions: new Float32Array(size * 3), + quaternions: new Float32Array(size * 4), + } + + this.worker.onmessage = (message: IncomingWorkerMessage) => { + if (message.data.op === 'frame') { + this.buffers.positions = message.data.positions + this.buffers.quaternions = message.data.quaternions + this.emit(message.data.op, message.data) + return + } + + this.emit(message.data.type, message.data) + } + } + + addBodies({ props, type, uuid }: WithoutOp): void { + this.worker.postMessage({ + op: 'addBodies', + props, + type, + uuid, + }) + } + + addConstraint({ props: [refA, refB, optns], type, uuid }: WithoutOp): void { + this.worker.postMessage({ + op: 'addConstraint', + props: [refA, refB, optns], + type, + uuid, + }) + } + + addContactMaterial({ props, uuid }: WithoutOp): void { + this.worker.postMessage({ + op: 'addContactMaterial', + props, + uuid, + }) + } + + addRay({ props, uuid }: WithoutOp): void { + this.worker.postMessage({ op: 'addRay', props, uuid }) + } + + addRaycastVehicle({ + props: [chassisBodyUUID, wheelUUIDs, wheelInfos, indexForwardAxis, indexRightAxis, indexUpAxis], + uuid, + }: WithoutOp): void { + this.worker.postMessage({ + op: 'addRaycastVehicle', + props: [chassisBodyUUID, wheelUUIDs, wheelInfos, indexForwardAxis, indexRightAxis, indexUpAxis], + uuid, + }) + } + + addSpring({ props: [refA, refB, optns], uuid }: WithoutOp): void { + this.worker.postMessage({ + op: 'addSpring', + props: [refA, refB, optns], + uuid, + }) + } + + applyForce({ props, uuid }: WithoutOp): void { + this.worker.postMessage({ op: 'applyForce', props, uuid }) + } + + applyImpulse({ props, uuid }: WithoutOp): void { + this.worker.postMessage({ op: 'applyImpulse', props, uuid }) + } + + applyLocalForce({ props, uuid }: WithoutOp): void { + this.worker.postMessage({ op: 'applyLocalForce', props, uuid }) + } + + applyLocalImpulse({ props, uuid }: WithoutOp): void { + this.worker.postMessage({ op: 'applyLocalImpulse', props, uuid }) + } + + applyRaycastVehicleEngineForce({ props, uuid }: WithoutOp): void { + this.worker.postMessage({ + op: 'applyRaycastVehicleEngineForce', + props, + uuid, + }) + } + + applyTorque({ props, uuid }: WithoutOp): void { + this.worker.postMessage({ op: 'applyTorque', props, uuid }) + } + + disableConstraint({ uuid }: WithoutOp): void { + this.worker.postMessage({ op: 'disableConstraint', uuid }) + } + + disableConstraintMotor({ uuid }: WithoutOp): void { + this.worker.postMessage({ op: 'disableConstraintMotor', uuid }) + } + + enableConstraint({ uuid }: WithoutOp): void { + this.worker.postMessage({ op: 'enableConstraint', uuid }) + } + + enableConstraintMotor({ uuid }: WithoutOp): void { + this.worker.postMessage({ op: 'enableConstraintMotor', uuid }) + } + + init(): void { + const { + allowSleep, + axisIndex, + broadphase, + defaultContactMaterial, + gravity, + iterations, + quatNormalizeFast, + quatNormalizeSkip, + solver, + tolerance, + } = this.config + + this.worker.postMessage({ + op: 'init', + props: { + allowSleep, + axisIndex, + broadphase, + defaultContactMaterial, + gravity, + iterations, + quatNormalizeFast, + quatNormalizeSkip, + solver, + tolerance, + }, + }) + } + + removeBodies({ uuid }: WithoutOp): void { + this.worker.postMessage({ op: 'removeBodies', uuid }) + } + + removeConstraint({ uuid }: WithoutOp): void { + this.worker.postMessage({ op: 'removeConstraint', uuid }) + } + + removeContactMaterial({ uuid }: WithoutOp): void { + this.worker.postMessage({ + op: 'removeContactMaterial', + uuid, + }) + } + + removeRay({ uuid }: WithoutOp): void { + this.worker.postMessage({ op: 'removeRay', uuid }) + } + + removeRaycastVehicle({ uuid }: WithoutOp): void { + this.worker.postMessage({ op: 'removeRaycastVehicle', uuid }) + } + + removeSpring({ uuid }: WithoutOp): void { + this.worker.postMessage({ op: 'removeSpring', uuid }) + } + + setAllowSleep({ props, uuid }: WithoutOp>): void { + this.worker.postMessage({ op: 'setAllowSleep', props, uuid }) + } + + setAngularDamping({ props, uuid }: WithoutOp>): void { + this.worker.postMessage({ op: 'setAngularDamping', props, uuid }) + } + + setAngularFactor({ props, uuid }: WithoutOp): void { + this.worker.postMessage({ op: 'setAngularFactor', props, uuid }) + } + + setAngularVelocity({ props, uuid }: WithoutOp): void { + this.worker.postMessage({ op: 'setAngularVelocity', props, uuid }) + } + + setCollisionFilterGroup({ props, uuid }: WithoutOp>): void { + this.worker.postMessage({ op: 'setCollisionFilterGroup', props, uuid }) + } + + setCollisionFilterMask({ props, uuid }: WithoutOp>): void { + this.worker.postMessage({ op: 'setCollisionFilterMask', props, uuid }) + } + + setCollisionResponse({ props, uuid }: WithoutOp>): void { + this.worker.postMessage({ op: 'setCollisionResponse', props, uuid }) + } + + setConstraintMotorMaxForce({ props, uuid }: WithoutOp): void { + this.worker.postMessage({ op: 'setConstraintMotorMaxForce', props, uuid }) + } + + setConstraintMotorSpeed({ props, uuid }: WithoutOp): void { + this.worker.postMessage({ op: 'setConstraintMotorSpeed', props, uuid }) + } + + setFixedRotation({ props, uuid }: WithoutOp>): void { + this.worker.postMessage({ op: 'setFixedRotation', props, uuid }) + } + + setIsTrigger({ props, uuid }: WithoutOp>): void { + this.worker.postMessage({ op: 'setIsTrigger', props, uuid }) + } + + setLinearDamping({ props, uuid }: WithoutOp>): void { + this.worker.postMessage({ op: 'setLinearDamping', props, uuid }) + } + + setLinearFactor({ props, uuid }: WithoutOp): void { + this.worker.postMessage({ op: 'setLinearFactor', props, uuid }) + } + + setMass({ props, uuid }: WithoutOp>): void { + this.worker.postMessage({ op: 'setMass', props, uuid }) + } + + setMaterial({ props, uuid }: WithoutOp>): void { + this.worker.postMessage({ op: 'setMaterial', props, uuid }) + } + + setPosition({ props, uuid }: WithoutOp): void { + this.worker.postMessage({ op: 'setPosition', props, uuid }) + } + + setQuaternion({ props: [x, y, z, w], uuid }: WithoutOp): void { + this.worker.postMessage({ op: 'setQuaternion', props: [x, y, z, w], uuid }) + } + + setRaycastVehicleBrake({ props, uuid }: WithoutOp): void { + this.worker.postMessage({ op: 'setRaycastVehicleBrake', props, uuid }) + } + + setRaycastVehicleSteeringValue({ props, uuid }: WithoutOp): void { + this.worker.postMessage({ + op: 'setRaycastVehicleSteeringValue', + props, + uuid, + }) + } + + setRotation({ props, uuid }: WithoutOp): void { + this.worker.postMessage({ op: 'setRotation', props, uuid }) + } + + setSleepSpeedLimit({ props, uuid }: WithoutOp>): void { + this.worker.postMessage({ op: 'setSleepSpeedLimit', props, uuid }) + } + + setSleepTimeLimit({ props, uuid }: WithoutOp>): void { + this.worker.postMessage({ op: 'setSleepTimeLimit', props, uuid }) + } + + setSpringDamping({ props, uuid }: WithoutOp): void { + this.worker.postMessage({ op: 'setSpringDamping', props, uuid }) + } + + setSpringRestLength({ props, uuid }: WithoutOp): void { + this.worker.postMessage({ op: 'setSpringRestLength', props, uuid }) + } + + setSpringStiffness({ props, uuid }: WithoutOp): void { + this.worker.postMessage({ op: 'setSpringStiffness', props, uuid }) + } + + setUserData({ props, uuid }: WithoutOp>): void { + this.worker.postMessage({ op: 'setUserData', props, uuid }) + } + + setVelocity({ props, uuid }: WithoutOp): void { + this.worker.postMessage({ op: 'setVelocity', props, uuid }) + } + + sleep({ uuid }: WithoutOp): void { + this.worker.postMessage({ op: 'sleep', uuid }) + } + + step(stepSize: number, dt?: number, maxSubSteps?: number): void { + const { + buffers: { positions, quaternions }, + } = this + + if (positions.byteLength === 0 || quaternions.byteLength === 0) { + return + } + + this.worker.postMessage({ op: 'step', positions, props: { dt, maxSubSteps, stepSize }, quaternions }, [ + positions.buffer, + quaternions.buffer, + ]) + } + + subscribe({ props: { id, target, type }, uuid }: WithoutOp): void { + this.worker.postMessage({ op: 'subscribe', props: { id, target, type }, uuid }) + } + + terminate(): void { + this.worker.terminate() + } + + unsubscribe({ props }: { props: number }): void { + this.worker.postMessage({ op: 'unsubscribe', props }) + } + + wakeUp({ uuid }: WithoutOp): void { + this.worker.postMessage({ op: 'wakeUp', uuid }) + } +} diff --git a/src/worker/index.js b/src/worker/worker.js similarity index 98% rename from src/worker/index.js rename to src/worker/worker.js index 8003a37f..e9013a78 100644 --- a/src/worker/index.js +++ b/src/worker/worker.js @@ -76,14 +76,11 @@ self.onmessage = ({ data: { op, positions, props, quaternions, type, uuid } }) = break } case 'step': { - const now = performance.now() / 1000 - if (!state.lastCallTime) { - state.world.step(state.config.step) + if (!props.dt) { + state.world.step(props.stepSize) } else { - const timeSinceLastCall = now - state.lastCallTime - state.world.step(state.config.step, timeSinceLastCall) + state.world.step(props.stepSize, props.stepSize.dt, props.maxSubSteps) } - state.lastCallTime = now const numberOfBodies = state.world.bodies.length for (let i = 0; i < numberOfBodies; i++) { diff --git a/yarn.lock b/yarn.lock index 607e1123..357a06e9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1863,6 +1863,11 @@ esutils@^2.0.2: resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== +events@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" + integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== + execa@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd"