diff --git a/packages/editor/src/components/toolbar/Toolbar.tsx b/packages/editor/src/components/toolbar/Toolbar.tsx
index 62c8229608..316da9c6c9 100644
--- a/packages/editor/src/components/toolbar/Toolbar.tsx
+++ b/packages/editor/src/components/toolbar/Toolbar.tsx
@@ -80,8 +80,6 @@ const onClickNewScene = async () => {
if (!confirm) return
}
- onNewScene()
-
const newSceneUIAddons = getState(EditorState).uiAddons.newScene
if (Object.keys(newSceneUIAddons).length > 0) {
PopoverState.showPopupover()
diff --git a/packages/editor/src/functions/assetFunctions.ts b/packages/editor/src/functions/assetFunctions.ts
index fb0c7f040d..4f979bbe9f 100644
--- a/packages/editor/src/functions/assetFunctions.ts
+++ b/packages/editor/src/functions/assetFunctions.ts
@@ -35,7 +35,7 @@ import { modelResourcesPath } from '@etherealengine/engine/src/assets/functions/
import { pathJoin } from '@etherealengine/common/src/utils/miscUtils'
-const handleUploadFiles = (projectName: string, directoryPath: string, files: FileList) => {
+export const handleUploadFiles = (projectName: string, directoryPath: string, files: FileList | File[]) => {
return Promise.all(
Array.from(files).map((file) => {
const fileDirectory = file.webkitRelativePath || file.name
diff --git a/packages/spatial/src/physics/classes/Physics.test.tsx b/packages/spatial/src/physics/classes/Physics.test.tsx
index da0b14adcb..8fd2154c72 100644
--- a/packages/spatial/src/physics/classes/Physics.test.tsx
+++ b/packages/spatial/src/physics/classes/Physics.test.tsx
@@ -1,2667 +1,2667 @@
-// /*
-// CPAL-1.0 License
-
-// The contents of this file are subject to the Common Public Attribution License
-// Version 1.0. (the "License"); you may not use this file except in compliance
-// with the License. You may obtain a copy of the License at
-// https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE.
-// The License is based on the Mozilla Public License Version 1.1, but Sections 14
-// and 15 have been added to cover use of software over a computer network and
-// provide for limited attribution for the Original Developer. In addition,
-// Exhibit A has been modified to be consistent with Exhibit B.
-
-// Software distributed under the License is distributed on an "AS IS" basis,
-// WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the
-// specific language governing rights and limitations under the License.
-
-// The Original Code is Ethereal Engine.
-
-// The Original Developer is the Initial Developer. The Initial Developer of the
-// Original Code is the Ethereal Engine team.
-
-// All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023
-// Ethereal Engine. All Rights Reserved.
-// */
-// import '../../..'
-
-// import { RigidBodyType, ShapeType, TempContactForceEvent, Vector, World } from '@dimforge/rapier3d-compat'
-// import assert from 'assert'
-// import sinon from 'sinon'
-// import { BoxGeometry, Mesh, Quaternion, Vector3 } from 'three'
-
-// import {
-// getComponent,
-// getMutableComponent,
-// getOptionalComponent,
-// hasComponent,
-// removeComponent,
-// setComponent
-// } from '@etherealengine/ecs/src/ComponentFunctions'
-// import { destroyEngine } from '@etherealengine/ecs/src/Engine'
-// import { createEntity } from '@etherealengine/ecs/src/EntityFunctions'
-// import { getState } from '@etherealengine/hyperflux'
-
-// import { createEngine } from '@etherealengine/ecs/src/Engine'
-// import { ObjectDirection, Vector3_Zero } from '../../common/constants/MathConstants'
-// import { TransformComponent } from '../../transform/components/TransformComponent'
-// import { computeTransformMatrix } from '../../transform/systems/TransformSystem'
-// import { ColliderComponent } from '../components/ColliderComponent'
-// import { CollisionComponent } from '../components/CollisionComponent'
-// import {
-// RigidBodyComponent,
-// RigidBodyFixedTagComponent,
-// getTagComponentForRigidBody
-// } from '../components/RigidBodyComponent'
-// import { TriggerComponent } from '../components/TriggerComponent'
-// import { AllCollisionMask, CollisionGroups, DefaultCollisionMask } from '../enums/CollisionGroups'
-// import { getInteractionGroups } from '../functions/getInteractionGroups'
-
-// import {
-// Entity,
-// EntityUUID,
-// SystemDefinitions,
-// UUIDComponent,
-// UndefinedEntity,
-// removeEntity
-// } from '@etherealengine/ecs'
-// import { act, render } from '@testing-library/react'
-// import React from 'react'
-// import { MeshComponent } from '../../renderer/components/MeshComponent'
-// import { SceneComponent } from '../../renderer/components/SceneComponents'
-// import { EntityTreeComponent } from '../../transform/components/EntityTree'
-// import { PhysicsSystem } from '../PhysicsModule'
-// import {
-// BodyTypes,
-// ColliderDescOptions,
-// ColliderHitEvent,
-// CollisionEvents,
-// SceneQueryType,
-// Shapes
-// } from '../types/PhysicsTypes'
-// import { Physics, PhysicsWorld, RapierWorldState } from './Physics'
-
-// const Rotation_Zero = { x: 0, y: 0, z: 0, w: 1 }
-
-// const Epsilon = 0.001
-// function floatApproxEq(A: number, B: number, epsilon = Epsilon): boolean {
-// return Math.abs(A - B) < epsilon
-// }
-// export function assertFloatApproxEq(A: number, B: number, epsilon = Epsilon) {
-// assert.ok(floatApproxEq(A, B, epsilon), `Numbers are not approximately equal: ${A} : ${B} : ${A - B}`)
-// }
-
-// export function assertFloatApproxNotEq(A: number, B: number, epsilon = Epsilon) {
-// assert.ok(!floatApproxEq(A, B, epsilon), `Numbers are approximately equal: ${A} : ${B} : ${A - B}`)
-// }
-
-// export function assertVecApproxEq(A, B, elems: number, epsilon = Epsilon) {
-// // @note Also used by RigidBodyComponent.test.ts
-// assertFloatApproxEq(A.x, B.x, epsilon)
-// assertFloatApproxEq(A.y, B.y, epsilon)
-// assertFloatApproxEq(A.z, B.z, epsilon)
-// if (elems > 3) assertFloatApproxEq(A.w, B.w, epsilon)
-// }
-
-// /**
-// * @description
-// * Triggers an assert if one or many of the (x,y,z,w) members of `@param A` is not equal to `@param B`.
-// * Does nothing for members that are equal */
-// export function assertVecAnyApproxNotEq(A, B, elems: number, epsilon = Epsilon) {
-// // @note Also used by PhysicsSystem.test.ts
-// !floatApproxEq(A.x, B.x, epsilon) && assertFloatApproxNotEq(A.x, B.x, epsilon)
-// !floatApproxEq(A.y, B.y, epsilon) && assertFloatApproxNotEq(A.y, B.y, epsilon)
-// !floatApproxEq(A.z, B.z, epsilon) && assertFloatApproxNotEq(A.z, B.z, epsilon)
-// if (elems > 3) !floatApproxEq(A.w, B.w, epsilon) && assertFloatApproxEq(A.w, B.w, epsilon)
-// }
-
-// export function assertVecAllApproxNotEq(A, B, elems: number, epsilon = Epsilon) {
-// // @note Also used by RigidBodyComponent.test.ts
-// assertFloatApproxNotEq(A.x, B.x, epsilon)
-// assertFloatApproxNotEq(A.y, B.y, epsilon)
-// assertFloatApproxNotEq(A.z, B.z, epsilon)
-// if (elems > 3) assertFloatApproxNotEq(A.w, B.w, epsilon)
-// }
-
-// export const boxDynamicConfig = {
-// shapeType: ShapeType.Cuboid,
-// bodyType: RigidBodyType.Fixed,
-// collisionLayer: CollisionGroups.Default,
-// collisionMask: DefaultCollisionMask | CollisionGroups.Avatars | CollisionGroups.Ground,
-// friction: 1,
-// restitution: 0,
-// isTrigger: false,
-// spawnPosition: new Vector3(0, 0.25, 5),
-// spawnScale: new Vector3(0.5, 0.25, 0.5)
-// } as ColliderDescOptions
-
-// describe('Physics : External API', () => {
-// let physicsWorld: PhysicsWorld
-// let physicsWorldEntity: Entity
-
-// beforeEach(async () => {
-// createEngine()
-// await Physics.load()
-// physicsWorldEntity = createEntity()
-// setComponent(physicsWorldEntity, UUIDComponent, UUIDComponent.generateUUID())
-// setComponent(physicsWorldEntity, SceneComponent)
-// setComponent(physicsWorldEntity, TransformComponent)
-// setComponent(physicsWorldEntity, EntityTreeComponent)
-// physicsWorld = Physics.createWorld(getComponent(physicsWorldEntity, UUIDComponent))
-// physicsWorld.timestep = 1 / 60
-// })
-
-// afterEach(() => {
-// return destroyEngine()
-// })
-
-// it('should create & remove rigidBody', async () => {
-// const entity = createEntity()
-// setComponent(entity, TransformComponent)
-// setComponent(entity, EntityTreeComponent, { parentEntity: physicsWorldEntity })
-// setComponent(entity, RigidBodyComponent, { type: BodyTypes.Dynamic })
-// setComponent(entity, ColliderComponent, { shape: Shapes.Sphere })
-
-// assert.deepEqual(physicsWorld.bodies.len(), 1)
-// assert.deepEqual(physicsWorld.colliders.len(), 1)
-
-// removeComponent(entity, RigidBodyComponent)
-
-// assert.deepEqual(physicsWorld.bodies.len(), 0)
-// })
-
-// it('component type should match rigid body type', async () => {
-// const entity = createEntity()
-
-// setComponent(entity, TransformComponent)
-// setComponent(entity, EntityTreeComponent, { parentEntity: physicsWorldEntity })
-// setComponent(entity, RigidBodyComponent, { type: BodyTypes.Fixed })
-// setComponent(entity, ColliderComponent, { shape: Shapes.Sphere })
-
-// const rigidBodyComponent = getTagComponentForRigidBody(BodyTypes.Fixed)
-// assert.deepEqual(rigidBodyComponent, RigidBodyFixedTagComponent)
-// })
-
-// /**
-// // @todo External API test for `setRigidBodyType`
-// it("should change the entity's RigidBody type", async () => {})
-// */
-
-// it('should create accurate InteractionGroups', async () => {
-// const collisionGroup = 0x0001
-// const collisionMask = 0x0003
-// const interactionGroups = getInteractionGroups(collisionGroup, collisionMask)
-
-// assert.deepEqual(interactionGroups, 65539)
-// })
-
-// it('should generate a collision event', async () => {
-// const entity1 = createEntity()
-// const entity2 = createEntity()
-// setComponent(entity1, TransformComponent)
-// setComponent(entity1, EntityTreeComponent, { parentEntity: physicsWorldEntity })
-// setComponent(entity2, TransformComponent)
-// setComponent(entity2, EntityTreeComponent, { parentEntity: physicsWorldEntity })
-
-// setComponent(entity1, RigidBodyComponent, { type: BodyTypes.Dynamic })
-// setComponent(entity2, RigidBodyComponent, { type: BodyTypes.Dynamic })
-// setComponent(entity1, ColliderComponent, {
-// shape: Shapes.Sphere,
-// collisionLayer: CollisionGroups.Default,
-// collisionMask: DefaultCollisionMask
-// })
-// setComponent(entity2, ColliderComponent, {
-// shape: Shapes.Sphere,
-// collisionLayer: CollisionGroups.Default,
-// collisionMask: DefaultCollisionMask
-// })
-
-// const collisionEventQueue = Physics.createCollisionEventQueue()
-// const drainCollisions = Physics.drainCollisionEventQueue(physicsWorld)
-
-// physicsWorld.step(collisionEventQueue)
-// collisionEventQueue.drainCollisionEvents(drainCollisions)
-
-// const rigidBody1 = physicsWorld.Rigidbodies.get(entity1)!
-// const rigidBody2 = physicsWorld.Rigidbodies.get(entity2)!
-
-// assert.equal(getComponent(entity1, CollisionComponent).get(entity2)?.bodySelf, rigidBody1)
-// assert.equal(getComponent(entity1, CollisionComponent).get(entity2)?.bodyOther, rigidBody2)
-// assert.equal(getComponent(entity1, CollisionComponent).get(entity2)?.shapeSelf, rigidBody1.collider(0))
-// assert.equal(getComponent(entity1, CollisionComponent).get(entity2)?.shapeOther, rigidBody2.collider(0))
-// assert.equal(getComponent(entity1, CollisionComponent).get(entity2)?.type, CollisionEvents.COLLISION_START)
-
-// assert.equal(getComponent(entity2, CollisionComponent).get(entity1)?.bodySelf, rigidBody2)
-// assert.equal(getComponent(entity2, CollisionComponent).get(entity1)?.bodyOther, rigidBody1)
-// assert.equal(getComponent(entity2, CollisionComponent).get(entity1)?.shapeSelf, rigidBody2.collider(0))
-// assert.equal(getComponent(entity2, CollisionComponent).get(entity1)?.shapeOther, rigidBody1.collider(0))
-// assert.equal(getComponent(entity2, CollisionComponent).get(entity1)?.type, CollisionEvents.COLLISION_START)
-
-// rigidBody2.setTranslation({ x: 0, y: 0, z: 15 }, true)
-
-// physicsWorld.step(collisionEventQueue)
-// collisionEventQueue.drainCollisionEvents(drainCollisions)
-
-// assert.equal(getComponent(entity1, CollisionComponent).get(entity2)?.bodySelf, rigidBody1)
-// assert.equal(getComponent(entity1, CollisionComponent).get(entity2)?.bodyOther, rigidBody2)
-// assert.equal(getComponent(entity1, CollisionComponent).get(entity2)?.shapeSelf, rigidBody1.collider(0))
-// assert.equal(getComponent(entity1, CollisionComponent).get(entity2)?.shapeOther, rigidBody2.collider(0))
-// assert.equal(getComponent(entity1, CollisionComponent).get(entity2)?.type, CollisionEvents.COLLISION_END)
-
-// assert.equal(getComponent(entity2, CollisionComponent).get(entity1)?.bodySelf, rigidBody2)
-// assert.equal(getComponent(entity2, CollisionComponent).get(entity1)?.bodyOther, rigidBody1)
-// assert.equal(getComponent(entity2, CollisionComponent).get(entity1)?.shapeSelf, rigidBody2.collider(0))
-// assert.equal(getComponent(entity2, CollisionComponent).get(entity1)?.shapeOther, rigidBody1.collider(0))
-// assert.equal(getComponent(entity2, CollisionComponent).get(entity1)?.type, CollisionEvents.COLLISION_END)
-// })
-
-// it('should generate a trigger event', async () => {
-// //force nested reactors to run
-// const { rerender, unmount } = render(<>>)
-
-// const entity1 = createEntity()
-// const entity2 = createEntity()
-
-// setComponent(entity1, CollisionComponent)
-// setComponent(entity2, CollisionComponent)
-
-// setComponent(entity1, EntityTreeComponent, { parentEntity: physicsWorldEntity })
-// setComponent(entity1, TransformComponent)
-// setComponent(entity2, EntityTreeComponent, { parentEntity: physicsWorldEntity })
-// setComponent(entity2, TransformComponent)
-
-// setComponent(entity1, RigidBodyComponent, { type: BodyTypes.Dynamic })
-// setComponent(entity2, RigidBodyComponent, { type: BodyTypes.Dynamic })
-// setComponent(entity1, ColliderComponent, {
-// shape: Shapes.Sphere,
-// collisionLayer: CollisionGroups.Default,
-// collisionMask: AllCollisionMask
-// })
-// setComponent(entity2, ColliderComponent, {
-// shape: Shapes.Sphere,
-// collisionLayer: CollisionGroups.Default,
-// collisionMask: AllCollisionMask
-// })
-// setComponent(entity2, TriggerComponent)
-
-// await act(() => rerender(<>>))
-
-// const collisionEventQueue = Physics.createCollisionEventQueue()
-// const drainCollisions = Physics.drainCollisionEventQueue(physicsWorld)
-
-// physicsWorld.step(collisionEventQueue)
-// collisionEventQueue.drainCollisionEvents(drainCollisions)
-
-// const rigidBody1 = physicsWorld.Rigidbodies.get(entity1)!
-// const rigidBody2 = physicsWorld.Rigidbodies.get(entity2)!
-
-// assert.equal(getComponent(entity1, CollisionComponent).get(entity2)?.bodySelf, rigidBody1)
-// assert.equal(getComponent(entity1, CollisionComponent).get(entity2)?.bodyOther, rigidBody2)
-// assert.equal(getComponent(entity1, CollisionComponent).get(entity2)?.shapeSelf, rigidBody1.collider(0))
-// assert.equal(getComponent(entity1, CollisionComponent).get(entity2)?.shapeOther, rigidBody2.collider(0))
-// assert.equal(getComponent(entity1, CollisionComponent).get(entity2)?.type, CollisionEvents.TRIGGER_START)
-
-// assert.equal(getComponent(entity2, CollisionComponent).get(entity1)?.bodySelf, rigidBody2)
-// assert.equal(getComponent(entity2, CollisionComponent).get(entity1)?.bodyOther, rigidBody1)
-// assert.equal(getComponent(entity2, CollisionComponent).get(entity1)?.shapeSelf, rigidBody2.collider(0))
-// assert.equal(getComponent(entity2, CollisionComponent).get(entity1)?.shapeOther, rigidBody1.collider(0))
-// assert.equal(getComponent(entity2, CollisionComponent).get(entity1)?.type, CollisionEvents.TRIGGER_START)
-
-// rigidBody2.setTranslation({ x: 0, y: 0, z: 15 }, true)
-
-// physicsWorld.step(collisionEventQueue)
-// collisionEventQueue.drainCollisionEvents(drainCollisions)
-
-// assert.equal(getComponent(entity1, CollisionComponent).get(entity2)?.bodySelf, rigidBody1)
-// assert.equal(getComponent(entity1, CollisionComponent).get(entity2)?.bodyOther, rigidBody2)
-// assert.equal(getComponent(entity1, CollisionComponent).get(entity2)?.shapeSelf, rigidBody1.collider(0))
-// assert.equal(getComponent(entity1, CollisionComponent).get(entity2)?.shapeOther, rigidBody2.collider(0))
-// assert.equal(getComponent(entity1, CollisionComponent).get(entity2)?.type, CollisionEvents.TRIGGER_END)
-
-// assert.equal(getComponent(entity2, CollisionComponent).get(entity1)?.bodySelf, rigidBody2)
-// assert.equal(getComponent(entity2, CollisionComponent).get(entity1)?.bodyOther, rigidBody1)
-// assert.equal(getComponent(entity2, CollisionComponent).get(entity1)?.shapeSelf, rigidBody2.collider(0))
-// assert.equal(getComponent(entity2, CollisionComponent).get(entity1)?.shapeOther, rigidBody1.collider(0))
-// assert.equal(getComponent(entity2, CollisionComponent).get(entity1)?.type, CollisionEvents.TRIGGER_END)
-// })
-// })
-
-// describe('Physics : Rapier->ECS API', () => {
-// describe('createWorld', () => {
-// beforeEach(async () => {
-// createEngine()
-// await Physics.load()
-// })
-
-// afterEach(() => {
-// return destroyEngine()
-// })
-
-// it('should create a world object with the default gravity when not specified', () => {
-// const world = Physics.createWorld('world' as EntityUUID)
-// assert(getState(RapierWorldState)['world'])
-// assert.ok(world instanceof World, 'The create world has an incorrect type.')
-// const Expected = new Vector3(0.0, -9.81, 0.0)
-// assertVecApproxEq(world.gravity, Expected, 3)
-// Physics.destroyWorld('world' as EntityUUID)
-// assert(!getState(RapierWorldState)['world'])
-// })
-
-// it('should create a world object with a different gravity value when specified', () => {
-// const expected = { x: 0.0, y: -5.0, z: 0.0 }
-// const world = Physics.createWorld('world' as EntityUUID, { gravity: expected, substeps: 2 })
-// assertVecApproxEq(world.gravity, expected, 3)
-// assert.equal(world.substeps, 2)
-// })
-// })
-
-// describe('Rigidbodies', () => {
-// describe('createRigidBody', () => {
-// const position = new Vector3(1, 2, 3)
-// const rotation = new Quaternion(0.2, 0.3, 0.5, 0.0).normalize()
-
-// const scale = new Vector3(10, 10, 10)
-// let testEntity = UndefinedEntity
-// let physicsWorld: PhysicsWorld
-// let physicsWorldEntity: Entity
-
-// beforeEach(async () => {
-// createEngine()
-// await Physics.load()
-// physicsWorldEntity = createEntity()
-// setComponent(physicsWorldEntity, UUIDComponent, UUIDComponent.generateUUID())
-// physicsWorld = Physics.createWorld(getComponent(physicsWorldEntity, UUIDComponent))
-// setComponent(physicsWorldEntity, SceneComponent)
-// setComponent(physicsWorldEntity, TransformComponent)
-// setComponent(physicsWorldEntity, EntityTreeComponent)
-// physicsWorld!.timestep = 1 / 60
-
-// // Create the entity
-// testEntity = createEntity()
-// setComponent(testEntity, EntityTreeComponent, { parentEntity: physicsWorldEntity })
-// setComponent(testEntity, TransformComponent, { position: position, scale: scale, rotation: rotation })
-// setComponent(testEntity, RigidBodyComponent, { type: BodyTypes.Dynamic, canSleep: true, gravityScale: 0 })
-// RigidBodyComponent.reactorMap.get(testEntity)!.stop()
-// })
-
-// afterEach(() => {
-// removeEntity(testEntity)
-// return destroyEngine()
-// })
-
-// it('should create a rigidBody successfully', () => {
-// Physics.createRigidBody(physicsWorld, testEntity)
-// const body = physicsWorld.Rigidbodies.get(testEntity)
-// assert.ok(body)
-// })
-
-// it("shouldn't mark the entity transform as dirty", () => {
-// Physics.createRigidBody(physicsWorld, testEntity)
-// assert.ok(TransformComponent.dirtyTransforms[testEntity] == false)
-// })
-
-// it('should assign the correct RigidBodyType enum', () => {
-// Physics.createRigidBody(physicsWorld, testEntity)
-// const body = physicsWorld.Rigidbodies.get(testEntity)!
-// assert.equal(body.bodyType(), RigidBodyType.Dynamic)
-// })
-
-// it("should assign the entity's position to the rigidBody.translation property", () => {
-// Physics.createRigidBody(physicsWorld, testEntity)
-// const body = physicsWorld.Rigidbodies.get(testEntity)!
-// assertVecApproxEq(body.translation(), position, 3)
-// })
-
-// it("should assign the entity's rotation to the rigidBody.rotation property", () => {
-// Physics.createRigidBody(physicsWorld, testEntity)
-// const body = physicsWorld.Rigidbodies.get(testEntity)!
-// assertVecApproxEq(body!.rotation(), rotation, 4)
-// })
-
-// it('should create a body with no Linear Velocity', () => {
-// Physics.createRigidBody(physicsWorld, testEntity)
-// const body = physicsWorld.Rigidbodies.get(testEntity)!
-// assertVecApproxEq(body.linvel(), Vector3_Zero, 3)
-// })
-
-// it('should create a body with no Angular Velocity', () => {
-// Physics.createRigidBody(physicsWorld, testEntity)
-// const body = physicsWorld.Rigidbodies.get(testEntity)!
-// assertVecApproxEq(body.angvel(), Vector3_Zero, 3)
-// })
-
-// it("should store the entity in the body's userData property", () => {
-// Physics.createRigidBody(physicsWorld, testEntity)
-// const body = physicsWorld.Rigidbodies.get(testEntity)!
-// assert.deepEqual(body.userData, { entity: testEntity })
-// })
-// })
-
-// describe('removeRigidbody', () => {
-// let testEntity = UndefinedEntity
-// let physicsWorld: PhysicsWorld
-
-// beforeEach(async () => {
-// createEngine()
-// await Physics.load()
-// const entity = createEntity()
-// setComponent(entity, UUIDComponent, UUIDComponent.generateUUID())
-// physicsWorld = Physics.createWorld(getComponent(entity, UUIDComponent))
-// setComponent(entity, SceneComponent)
-// setComponent(entity, TransformComponent)
-// setComponent(entity, EntityTreeComponent)
-// physicsWorld!.timestep = 1 / 60
-
-// // Create the entity
-// testEntity = createEntity()
-// setComponent(testEntity, EntityTreeComponent, { parentEntity: entity })
-// setComponent(testEntity, TransformComponent)
-// setComponent(testEntity, RigidBodyComponent, { type: BodyTypes.Dynamic })
-// RigidBodyComponent.reactorMap.get(testEntity)!.stop()
-// Physics.createRigidBody(physicsWorld, testEntity)
-// })
-
-// afterEach(() => {
-// removeEntity(testEntity)
-// return destroyEngine()
-// })
-
-// it('should successfully remove the body from the RigidBodies map', () => {
-// let body = physicsWorld.Rigidbodies.get(testEntity)
-// assert.ok(body)
-// Physics.removeRigidbody(physicsWorld, testEntity)
-// body = physicsWorld.Rigidbodies.get(testEntity)
-// assert.equal(body, undefined)
-// })
-// })
-
-// describe('isSleeping', () => {
-// let testEntity = UndefinedEntity
-// let physicsWorld: PhysicsWorld
-
-// beforeEach(async () => {
-// createEngine()
-// await Physics.load()
-// const entity = createEntity()
-// setComponent(entity, UUIDComponent, UUIDComponent.generateUUID())
-// setComponent(entity, SceneComponent)
-// setComponent(entity, TransformComponent)
-// setComponent(entity, EntityTreeComponent)
-// physicsWorld = Physics.createWorld(getComponent(entity, UUIDComponent))
-// physicsWorld!.timestep = 1 / 60
-
-// // Create the entity
-// testEntity = createEntity()
-// setComponent(testEntity, EntityTreeComponent, { parentEntity: entity })
-// setComponent(testEntity, TransformComponent)
-// setComponent(testEntity, RigidBodyComponent, { type: BodyTypes.Dynamic })
-// RigidBodyComponent.reactorMap.get(testEntity)!.stop()
-// Physics.createRigidBody(physicsWorld, testEntity)
-// })
-
-// afterEach(() => {
-// removeEntity(testEntity)
-// return destroyEngine()
-// })
-
-// it('should return the correct values', () => {
-// const noBodyEntity = createEntity()
-// assert.equal(
-// Physics.isSleeping(physicsWorld, noBodyEntity),
-// true,
-// 'Returns true when the entity does not have a RigidBody'
-// )
-// assert.equal(
-// Physics.isSleeping(physicsWorld, testEntity),
-// false,
-// "Returns false when the entity is first created and physics haven't been simulated yet"
-// )
-// })
-// })
-
-// describe('setRigidBodyType', () => {
-// let testEntity = UndefinedEntity
-// let physicsWorld: PhysicsWorld
-
-// beforeEach(async () => {
-// createEngine()
-// await Physics.load()
-// const entity = createEntity()
-// setComponent(entity, UUIDComponent, UUIDComponent.generateUUID())
-// setComponent(entity, SceneComponent)
-// setComponent(entity, TransformComponent)
-// setComponent(entity, EntityTreeComponent)
-// physicsWorld = Physics.createWorld(getComponent(entity, UUIDComponent))
-// physicsWorld!.timestep = 1 / 60
-
-// // Create the entity
-// testEntity = createEntity()
-// setComponent(testEntity, EntityTreeComponent, { parentEntity: entity })
-// setComponent(testEntity, TransformComponent)
-// setComponent(testEntity, RigidBodyComponent, { type: BodyTypes.Dynamic })
-// RigidBodyComponent.reactorMap.get(testEntity)!.stop()
-// Physics.createRigidBody(physicsWorld, testEntity)
-// })
-
-// afterEach(() => {
-// removeEntity(testEntity)
-// return destroyEngine()
-// })
-
-// it("should assign the correct RigidBodyType to the entity's body", () => {
-// let body = physicsWorld.Rigidbodies.get(testEntity)!
-// assert.equal(body.bodyType(), RigidBodyType.Dynamic)
-// // Check change to fixed
-// Physics.setRigidBodyType(physicsWorld, testEntity, BodyTypes.Fixed)
-// body = physicsWorld.Rigidbodies.get(testEntity)!
-// assert.notEqual(body.bodyType(), RigidBodyType.Dynamic, "The RigidBody's type was not changed")
-// assert.equal(body.bodyType(), RigidBodyType.Fixed, "The RigidBody's type was not changed to Fixed")
-// // Check change to dynamic
-// Physics.setRigidBodyType(physicsWorld, testEntity, BodyTypes.Dynamic)
-// body = physicsWorld.Rigidbodies.get(testEntity)!
-// assert.notEqual(body.bodyType(), RigidBodyType.Fixed, "The RigidBody's type was not changed")
-// assert.equal(body.bodyType(), RigidBodyType.Dynamic, "The RigidBody's type was not changed to Dynamic")
-// // Check change to kinematic
-// Physics.setRigidBodyType(physicsWorld, testEntity, BodyTypes.Kinematic)
-// body = physicsWorld.Rigidbodies.get(testEntity)!
-// assert.notEqual(body.bodyType(), RigidBodyType.Dynamic, "The RigidBody's type was not changed")
-// assert.equal(
-// body.bodyType(),
-// RigidBodyType.KinematicPositionBased,
-// "The RigidBody's type was not changed to KinematicPositionBased"
-// )
-// })
-// })
-
-// describe('setRigidbodyPose', () => {
-// const position = new Vector3(1, 2, 3)
-// const rotation = new Quaternion(0.1, 0.3, 0.7, 0.0).normalize()
-// const linVel = new Vector3(7, 8, 9)
-// const angVel = new Vector3(0, 1, 2)
-// let testEntity = UndefinedEntity
-// let physicsWorld: PhysicsWorld
-
-// beforeEach(async () => {
-// createEngine()
-// await Physics.load()
-// const entity = createEntity()
-// setComponent(entity, UUIDComponent, UUIDComponent.generateUUID())
-// setComponent(entity, SceneComponent)
-// setComponent(entity, TransformComponent)
-// setComponent(entity, EntityTreeComponent)
-// physicsWorld = Physics.createWorld(getComponent(entity, UUIDComponent))
-// physicsWorld!.timestep = 1 / 60
-
-// // Create the entity
-// testEntity = createEntity()
-// setComponent(testEntity, EntityTreeComponent, { parentEntity: entity })
-// setComponent(testEntity, TransformComponent)
-// setComponent(testEntity, RigidBodyComponent, { type: BodyTypes.Dynamic })
-// RigidBodyComponent.reactorMap.get(testEntity)!.stop()
-// Physics.createRigidBody(physicsWorld, testEntity)
-// })
-
-// afterEach(() => {
-// removeEntity(testEntity)
-// return destroyEngine()
-// })
-
-// it("should set the body's Translation to the given Position", () => {
-// Physics.setRigidbodyPose(physicsWorld, testEntity, position, rotation, linVel, angVel)
-// const body = physicsWorld.Rigidbodies.get(testEntity)!
-// assertVecApproxEq(body.translation(), position, 3)
-// })
-
-// it("should set the body's Rotation to the given value", () => {
-// Physics.setRigidbodyPose(physicsWorld, testEntity, position, rotation, linVel, angVel)
-// const body = physicsWorld.Rigidbodies.get(testEntity)!
-// assertVecApproxEq(body.rotation(), rotation, 4)
-// })
-
-// it("should set the body's Linear Velocity to the given value", () => {
-// Physics.setRigidbodyPose(physicsWorld, testEntity, position, rotation, linVel, angVel)
-// const body = physicsWorld.Rigidbodies.get(testEntity)!
-// assertVecApproxEq(body.linvel(), linVel, 3)
-// })
-
-// it("should set the body's Angular Velocity to the given value", () => {
-// Physics.setRigidbodyPose(physicsWorld, testEntity, position, rotation, linVel, angVel)
-// const body = physicsWorld.Rigidbodies.get(testEntity)!
-// assertVecApproxEq(body.angvel(), angVel, 3)
-// })
-// })
-
-// describe('enabledCcd', () => {
-// let testEntity = UndefinedEntity
-// let physicsWorld: PhysicsWorld
-
-// beforeEach(async () => {
-// createEngine()
-// await Physics.load()
-// const entity = createEntity()
-// setComponent(entity, UUIDComponent, UUIDComponent.generateUUID())
-// setComponent(entity, SceneComponent)
-// setComponent(entity, TransformComponent)
-// setComponent(entity, EntityTreeComponent)
-// physicsWorld = Physics.createWorld(getComponent(entity, UUIDComponent))
-// physicsWorld!.timestep = 1 / 60
-
-// // Create the entity
-// testEntity = createEntity()
-// setComponent(testEntity, EntityTreeComponent, { parentEntity: entity })
-// setComponent(testEntity, TransformComponent)
-// setComponent(testEntity, RigidBodyComponent, { type: BodyTypes.Dynamic })
-// RigidBodyComponent.reactorMap.get(testEntity)!.stop()
-// Physics.createRigidBody(physicsWorld, testEntity)
-// })
-
-// afterEach(() => {
-// removeEntity(testEntity)
-// return destroyEngine()
-// })
-
-// it('should enable Continuous Collision Detection on the entity', () => {
-// const body = physicsWorld.Rigidbodies.get(testEntity)!
-// assert.equal(body.isCcdEnabled(), false)
-// Physics.enabledCcd(physicsWorld, testEntity, true)
-// assert.equal(body.isCcdEnabled(), true)
-// })
-
-// it('should disable CCD on the entity when passing `false` to the `enabled` property', () => {
-// const body = physicsWorld.Rigidbodies.get(testEntity)!
-// Physics.enabledCcd(physicsWorld, testEntity, true)
-// assert.equal(body.isCcdEnabled(), true)
-// Physics.enabledCcd(physicsWorld, testEntity, false)
-// assert.equal(body.isCcdEnabled(), false)
-// })
-// })
-
-// describe('applyImpulse', () => {
-// let testEntity = UndefinedEntity
-// let physicsWorld: PhysicsWorld
-
-// beforeEach(async () => {
-// createEngine()
-// await Physics.load()
-// const entity = createEntity()
-// setComponent(entity, UUIDComponent, UUIDComponent.generateUUID())
-// setComponent(entity, SceneComponent)
-// setComponent(entity, TransformComponent)
-// setComponent(entity, EntityTreeComponent)
-// physicsWorld = Physics.createWorld(getComponent(entity, UUIDComponent))
-// physicsWorld!.timestep = 1 / 60
-
-// // Create the entity
-// testEntity = createEntity()
-// setComponent(testEntity, EntityTreeComponent, { parentEntity: entity })
-// setComponent(testEntity, TransformComponent)
-// setComponent(testEntity, RigidBodyComponent, { type: BodyTypes.Dynamic })
-// setComponent(testEntity, ColliderComponent)
-// })
-
-// afterEach(() => {
-// removeEntity(testEntity)
-// return destroyEngine()
-// })
-
-// const physicsSystemExecute = SystemDefinitions.get(PhysicsSystem)!.execute
-
-// it('should apply the impulse to the RigidBody of the entity', () => {
-// const testImpulse = new Vector3(1, 2, 3)
-// const beforeBody = physicsWorld.Rigidbodies.get(testEntity)
-// assert.ok(beforeBody)
-// const before = beforeBody.linvel()
-// assertVecApproxEq(before, Vector3_Zero, 3)
-// Physics.applyImpulse(physicsWorld, testEntity, testImpulse)
-// physicsSystemExecute()
-// const afterBody = physicsWorld.Rigidbodies.get(testEntity)
-// assert.ok(afterBody)
-// const after = afterBody.linvel()
-// assertVecAllApproxNotEq(after, before, 3)
-// })
-// })
-
-// describe('lockRotations', () => {
-// let testEntity = UndefinedEntity
-// let physicsWorld: PhysicsWorld
-
-// beforeEach(async () => {
-// createEngine()
-// await Physics.load()
-// const entity = createEntity()
-// setComponent(entity, UUIDComponent, UUIDComponent.generateUUID())
-// setComponent(entity, SceneComponent)
-// setComponent(entity, TransformComponent)
-// setComponent(entity, EntityTreeComponent)
-// physicsWorld = Physics.createWorld(getComponent(entity, UUIDComponent))
-// physicsWorld!.timestep = 1 / 60
-
-// // Create the entity
-// testEntity = createEntity()
-// setComponent(testEntity, EntityTreeComponent, { parentEntity: entity })
-// setComponent(testEntity, TransformComponent)
-// setComponent(testEntity, RigidBodyComponent, { type: BodyTypes.Dynamic })
-// setComponent(testEntity, ColliderComponent)
-// })
-
-// afterEach(() => {
-// removeEntity(testEntity)
-// return destroyEngine()
-// })
-
-// it('should lock rotations on the entity', () => {
-// const impulse = new Vector3(1, 2, 3)
-// const body = physicsWorld.Rigidbodies.get(testEntity)!
-// const before = { x: body.angvel().x, y: body.angvel().y, z: body.angvel().z }
-// assertVecApproxEq(before, Vector3_Zero, 3)
-
-// body.applyTorqueImpulse(impulse, false)
-// const dummy = { x: body.angvel().x, y: body.angvel().y, z: body.angvel().z }
-// assertVecAllApproxNotEq(before, dummy, 3)
-
-// Physics.lockRotations(physicsWorld, testEntity, true)
-// body.applyTorqueImpulse(impulse, false)
-// const after = { x: body.angvel().x, y: body.angvel().y, z: body.angvel().z }
-// assertVecApproxEq(dummy, after, 3)
-// })
-
-// /**
-// // @todo Fix this test when we update to Rapier >= v0.12
-// it('should disable locked rotations on the entity', () => {
-// const ExpectedValue = new Quaternion(0.5, 0.3, 0.2, 0.0).normalize()
-// const body = physicsWorld.Rigidbodies.get(testEntity)!
-// assert.notDeepEqual(body.rotation(), ExpectedValue)
-
-// Physics.lockRotations(testEntity, true)
-// body.setRotation(ExpectedValue, false)
-// console.log(JSON.stringify(body.rotation()), "BEFORE")
-// console.log(JSON.stringify(ExpectedValue), "Expected")
-// assertVecAllApproxNotEq(body.rotation(), ExpectedValue, 3)
-// // assert.notDeepEqual(body.rotation(), ExpectedValue)
-
-// Physics.lockRotations(testEntity, true)
-// console.log(JSON.stringify(body.rotation()), "AFTEr")
-// assertVecApproxEq(body.rotation(), ExpectedValue, 4)
-// })
-// */
-// })
-
-// describe('setEnabledRotations', () => {
-// let testEntity = UndefinedEntity
-// let physicsWorld: PhysicsWorld
-
-// beforeEach(async () => {
-// createEngine()
-// await Physics.load()
-// const entity = createEntity()
-// setComponent(entity, UUIDComponent, UUIDComponent.generateUUID())
-// setComponent(entity, SceneComponent)
-// setComponent(entity, TransformComponent)
-// setComponent(entity, EntityTreeComponent)
-// physicsWorld = Physics.createWorld(getComponent(entity, UUIDComponent))
-// physicsWorld!.timestep = 1 / 60
-
-// // Create the entity
-// testEntity = createEntity()
-// setComponent(testEntity, EntityTreeComponent, { parentEntity: entity })
-// setComponent(testEntity, TransformComponent)
-// setComponent(testEntity, RigidBodyComponent, { type: BodyTypes.Dynamic })
-// setComponent(testEntity, ColliderComponent)
-// })
-
-// afterEach(() => {
-// removeEntity(testEntity)
-// return destroyEngine()
-// })
-
-// it('should disable rotations on the X axis for the rigidBody of the entity', () => {
-// const testImpulse = new Vector3(1, 2, 3)
-// const enabledRotation = [false, true, true] as [boolean, boolean, boolean]
-// const body = physicsWorld.Rigidbodies.get(testEntity)!
-// const before = body.angvel()
-// assertVecApproxEq(before, Vector3_Zero, 3)
-// Physics.setEnabledRotations(physicsWorld, testEntity, enabledRotation)
-// body.applyTorqueImpulse(testImpulse, false)
-// physicsWorld!.step()
-// const after = body.angvel()
-// assertFloatApproxEq(after.x, before.x)
-// assertFloatApproxNotEq(after.y, before.y)
-// assertFloatApproxNotEq(after.z, before.z)
-// })
-
-// it('should disable rotations on the Y axis for the rigidBody of the entity', () => {
-// const testImpulse = new Vector3(1, 2, 3)
-// const enabledRotation = [true, false, true] as [boolean, boolean, boolean]
-// const body = physicsWorld.Rigidbodies.get(testEntity)!
-// const before = body.angvel()
-// assertVecApproxEq(before, Vector3_Zero, 3)
-// Physics.setEnabledRotations(physicsWorld, testEntity, enabledRotation)
-// body.applyTorqueImpulse(testImpulse, false)
-// physicsWorld!.step()
-// const after = body.angvel()
-// assertFloatApproxNotEq(after.x, before.x)
-// assertFloatApproxEq(after.y, before.y)
-// assertFloatApproxNotEq(after.z, before.z)
-// })
-
-// it('should disable rotations on the Z axis for the rigidBody of the entity', () => {
-// const testImpulse = new Vector3(1, 2, 3)
-// const enabledRotation = [true, true, false] as [boolean, boolean, boolean]
-// const body = physicsWorld.Rigidbodies.get(testEntity)!
-// const before = body.angvel()
-// assertVecApproxEq(before, Vector3_Zero, 3)
-// Physics.setEnabledRotations(physicsWorld, testEntity, enabledRotation)
-// body.applyTorqueImpulse(testImpulse, false)
-// physicsWorld!.step()
-// const after = body.angvel()
-// assertFloatApproxNotEq(after.x, before.x)
-// assertFloatApproxNotEq(after.y, before.y)
-// assertFloatApproxEq(after.z, before.z)
-// })
-// })
-
-// describe('updatePreviousRigidbodyPose', () => {
-// let testEntity = UndefinedEntity
-// let physicsWorld: PhysicsWorld
-
-// beforeEach(async () => {
-// createEngine()
-// await Physics.load()
-// const entity = createEntity()
-// setComponent(entity, UUIDComponent, UUIDComponent.generateUUID())
-// setComponent(entity, SceneComponent)
-// setComponent(entity, TransformComponent)
-// setComponent(entity, EntityTreeComponent)
-// physicsWorld = Physics.createWorld(getComponent(entity, UUIDComponent))
-// physicsWorld!.timestep = 1 / 60
-
-// // Create the entity
-// testEntity = createEntity()
-// setComponent(testEntity, EntityTreeComponent, { parentEntity: entity })
-// setComponent(testEntity, TransformComponent)
-// setComponent(testEntity, RigidBodyComponent, { type: BodyTypes.Dynamic })
-// setComponent(testEntity, ColliderComponent)
-// })
-
-// afterEach(() => {
-// removeEntity(testEntity)
-// return destroyEngine()
-// })
-
-// it("should set the previous position of the entity's RigidBodyComponent", () => {
-// const Expected = new Vector3(1, 2, 3)
-// const body = physicsWorld.Rigidbodies.get(testEntity)!
-// body.setTranslation(Expected, false)
-// const before = {
-// x: RigidBodyComponent.previousPosition.x[testEntity],
-// y: RigidBodyComponent.previousPosition.y[testEntity],
-// z: RigidBodyComponent.previousPosition.z[testEntity]
-// }
-// Physics.updatePreviousRigidbodyPose([testEntity])
-// const after = {
-// x: RigidBodyComponent.previousPosition.x[testEntity],
-// y: RigidBodyComponent.previousPosition.y[testEntity],
-// z: RigidBodyComponent.previousPosition.z[testEntity]
-// }
-// assertVecAllApproxNotEq(before, after, 3)
-// })
-
-// it("should set the previous rotation of the entity's RigidBodyComponent", () => {
-// const Expected = new Quaternion(0.5, 0.3, 0.2, 0.0).normalize()
-// const body = physicsWorld.Rigidbodies.get(testEntity)!
-// body.setRotation(Expected, false)
-// const before = {
-// x: RigidBodyComponent.previousRotation.x[testEntity],
-// y: RigidBodyComponent.previousRotation.y[testEntity],
-// z: RigidBodyComponent.previousRotation.z[testEntity],
-// w: RigidBodyComponent.previousRotation.w[testEntity]
-// }
-// Physics.updatePreviousRigidbodyPose([testEntity])
-// const after = {
-// x: RigidBodyComponent.previousRotation.x[testEntity],
-// y: RigidBodyComponent.previousRotation.y[testEntity],
-// z: RigidBodyComponent.previousRotation.z[testEntity],
-// w: RigidBodyComponent.previousRotation.w[testEntity]
-// }
-// assertVecAllApproxNotEq(before, after, 4)
-// })
-// })
-
-// describe('updateRigidbodyPose', () => {
-// let testEntity = UndefinedEntity
-// let physicsWorld: PhysicsWorld
-
-// beforeEach(async () => {
-// createEngine()
-// await Physics.load()
-// const entity = createEntity()
-// setComponent(entity, UUIDComponent, UUIDComponent.generateUUID())
-// setComponent(entity, SceneComponent)
-// setComponent(entity, TransformComponent)
-// setComponent(entity, EntityTreeComponent)
-// physicsWorld = Physics.createWorld(getComponent(entity, UUIDComponent))
-// physicsWorld!.timestep = 1 / 60
-
-// // Create the entity
-// testEntity = createEntity()
-// setComponent(testEntity, EntityTreeComponent, { parentEntity: entity })
-// setComponent(testEntity, TransformComponent)
-// setComponent(testEntity, RigidBodyComponent, { type: BodyTypes.Dynamic })
-// setComponent(testEntity, ColliderComponent)
-// })
-
-// afterEach(() => {
-// removeEntity(testEntity)
-// return destroyEngine()
-// })
-
-// it("should set the position of the entity's RigidBodyComponent", () => {
-// const position = new Vector3(1, 2, 3)
-// const body = physicsWorld.Rigidbodies.get(testEntity)!
-// body.setTranslation(position, false)
-// const before = {
-// x: RigidBodyComponent.position.x[testEntity],
-// y: RigidBodyComponent.position.y[testEntity],
-// z: RigidBodyComponent.position.z[testEntity]
-// }
-// Physics.updateRigidbodyPose([testEntity])
-// const after = {
-// x: RigidBodyComponent.position.x[testEntity],
-// y: RigidBodyComponent.position.y[testEntity],
-// z: RigidBodyComponent.position.z[testEntity]
-// }
-// assertVecAllApproxNotEq(before, after, 3)
-// })
-
-// it("should set the rotation of the entity's RigidBodyComponent", () => {
-// const rotation = new Quaternion(0.5, 0.3, 0.2, 0.0).normalize()
-// const body = physicsWorld.Rigidbodies.get(testEntity)!
-// body.setRotation(rotation, false)
-// const before = {
-// x: RigidBodyComponent.rotation.x[testEntity],
-// y: RigidBodyComponent.rotation.y[testEntity],
-// z: RigidBodyComponent.rotation.z[testEntity],
-// w: RigidBodyComponent.rotation.w[testEntity]
-// }
-// Physics.updateRigidbodyPose([testEntity])
-// const after = {
-// x: RigidBodyComponent.rotation.x[testEntity],
-// y: RigidBodyComponent.rotation.y[testEntity],
-// z: RigidBodyComponent.rotation.z[testEntity],
-// w: RigidBodyComponent.rotation.w[testEntity]
-// }
-// assertVecAllApproxNotEq(before, after, 4)
-// })
-
-// it("should set the linearVelocity of the entity's RigidBodyComponent", () => {
-// const impulse = new Vector3(1, 2, 3)
-// const body = physicsWorld.Rigidbodies.get(testEntity)!
-// body.applyImpulse(impulse, false)
-// const before = {
-// x: RigidBodyComponent.linearVelocity.x[testEntity],
-// y: RigidBodyComponent.linearVelocity.y[testEntity],
-// z: RigidBodyComponent.linearVelocity.z[testEntity]
-// }
-// Physics.updateRigidbodyPose([testEntity])
-// const after = {
-// x: RigidBodyComponent.linearVelocity.x[testEntity],
-// y: RigidBodyComponent.linearVelocity.y[testEntity],
-// z: RigidBodyComponent.linearVelocity.z[testEntity]
-// }
-// assertVecAllApproxNotEq(before, after, 3)
-// })
-
-// it("should set the angularVelocity of the entity's RigidBodyComponent", () => {
-// const impulse = new Vector3(1, 2, 3)
-// const body = physicsWorld.Rigidbodies.get(testEntity)!
-// body.applyTorqueImpulse(impulse, false)
-// const before = {
-// x: RigidBodyComponent.angularVelocity.x[testEntity],
-// y: RigidBodyComponent.angularVelocity.y[testEntity],
-// z: RigidBodyComponent.angularVelocity.z[testEntity]
-// }
-// Physics.updateRigidbodyPose([testEntity])
-// const after = {
-// x: RigidBodyComponent.angularVelocity.x[testEntity],
-// y: RigidBodyComponent.angularVelocity.y[testEntity],
-// z: RigidBodyComponent.angularVelocity.z[testEntity]
-// }
-// assertVecAllApproxNotEq(before, after, 3)
-// })
-// })
-
-// describe('setKinematicRigidbodyPose', () => {
-// const position = new Vector3(1, 2, 3)
-// const rotation = new Quaternion(0.5, 0.3, 0.2, 0.0).normalize()
-// let testEntity = UndefinedEntity
-// let physicsWorld: PhysicsWorld
-
-// beforeEach(async () => {
-// createEngine()
-// await Physics.load()
-// const entity = createEntity()
-// setComponent(entity, UUIDComponent, UUIDComponent.generateUUID())
-// setComponent(entity, SceneComponent)
-// setComponent(entity, TransformComponent)
-// setComponent(entity, EntityTreeComponent)
-// physicsWorld = Physics.createWorld(getComponent(entity, UUIDComponent))
-// physicsWorld!.timestep = 1 / 60
-
-// // Create the entity
-// testEntity = createEntity()
-// setComponent(testEntity, EntityTreeComponent, { parentEntity: entity })
-// setComponent(testEntity, TransformComponent)
-// setComponent(testEntity, RigidBodyComponent, { type: BodyTypes.Kinematic })
-// setComponent(testEntity, ColliderComponent)
-// })
-
-// afterEach(() => {
-// removeEntity(testEntity)
-// return destroyEngine()
-// })
-
-// it("should set the nextTranslation property of the entity's Kinematic RigidBody", () => {
-// const body = physicsWorld.Rigidbodies.get(testEntity)!
-// const before = body.nextTranslation()
-// Physics.setKinematicRigidbodyPose(physicsWorld, testEntity, position, rotation)
-// const after = body.nextTranslation()
-// assertVecAllApproxNotEq(before, after, 3)
-// })
-
-// it("should set the nextRotation property of the entity's Kinematic RigidBody", () => {
-// const body = physicsWorld.Rigidbodies.get(testEntity)!
-// const before = body.nextRotation()
-// Physics.setKinematicRigidbodyPose(physicsWorld, testEntity, position, rotation)
-// const after = body.nextRotation()
-// assertVecAllApproxNotEq(before, after, 4)
-// })
-// })
-// }) // << Rigidbodies
-
-// describe('Colliders', () => {
-// describe('setTrigger', () => {
-// let testEntity = UndefinedEntity
-// let physicsWorld: PhysicsWorld
-
-// beforeEach(async () => {
-// createEngine()
-// await Physics.load()
-// const entity = createEntity()
-// setComponent(entity, UUIDComponent, UUIDComponent.generateUUID())
-// setComponent(entity, SceneComponent)
-// setComponent(entity, TransformComponent)
-// setComponent(entity, EntityTreeComponent)
-// physicsWorld = Physics.createWorld(getComponent(entity, UUIDComponent))
-// physicsWorld!.timestep = 1 / 60
-
-// // Create the entity
-// testEntity = createEntity()
-// setComponent(testEntity, EntityTreeComponent, { parentEntity: entity })
-// setComponent(testEntity, TransformComponent)
-// setComponent(testEntity, RigidBodyComponent, { type: BodyTypes.Dynamic })
-// setComponent(testEntity, ColliderComponent, { shape: Shapes.Sphere })
-// })
-
-// afterEach(() => {
-// removeEntity(testEntity)
-// return destroyEngine()
-// })
-
-// it('should mark the collider of the entity as a sensor', () => {
-// const collider = physicsWorld.Colliders.get(testEntity)!
-// Physics.setTrigger(physicsWorld, testEntity, true)
-// assert.ok(collider.isSensor())
-// })
-
-// it('should add CollisionGroup.trigger to the interaction groups of the collider when `isTrigger` is passed as true', () => {
-// const collider = physicsWorld.Colliders.get(testEntity)!
-// Physics.setTrigger(physicsWorld, testEntity, true)
-// const triggerInteraction = getInteractionGroups(CollisionGroups.Trigger, 0) // Shift the Trigger bits into the interaction bits, so they don't match with the mask
-// const hasTriggerInteraction = Boolean(collider.collisionGroups() & triggerInteraction) // If interactionGroups contains the triggerInteraction bits
-// assert.ok(hasTriggerInteraction)
-// })
-
-// it('should not add CollisionGroup.trigger to the interaction groups of the collider when `isTrigger` is passed as false', () => {
-// const collider = physicsWorld.Colliders.get(testEntity)!
-// Physics.setTrigger(physicsWorld, testEntity, false)
-// const triggerInteraction = getInteractionGroups(CollisionGroups.Trigger, 0) // Shift the Trigger bits into the interaction bits, so they don't match with the mask
-// const notTriggerInteraction = !(collider.collisionGroups() & triggerInteraction) // If interactionGroups does not contain the triggerInteraction bits
-// assert.ok(notTriggerInteraction)
-// })
-// }) // << setTrigger
-
-// describe('setCollisionLayer', () => {
-// let testEntity = UndefinedEntity
-// let physicsWorld: PhysicsWorld
-
-// beforeEach(async () => {
-// createEngine()
-// await Physics.load()
-// const entity = createEntity()
-// setComponent(entity, UUIDComponent, UUIDComponent.generateUUID())
-// setComponent(entity, SceneComponent)
-// setComponent(entity, TransformComponent)
-// setComponent(entity, EntityTreeComponent)
-// physicsWorld = Physics.createWorld(getComponent(entity, UUIDComponent))
-// physicsWorld!.timestep = 1 / 60
-
-// // Create the entity
-// testEntity = createEntity()
-// setComponent(testEntity, EntityTreeComponent, { parentEntity: entity })
-// setComponent(testEntity, TransformComponent)
-// setComponent(testEntity, RigidBodyComponent, { type: BodyTypes.Dynamic })
-// setComponent(testEntity, ColliderComponent, { shape: Shapes.Sphere })
-// })
-
-// afterEach(() => {
-// removeEntity(testEntity)
-// return destroyEngine()
-// })
-
-// it('should set the collider interaction groups to the given value', () => {
-// const data = getComponent(testEntity, ColliderComponent)
-// const ExpectedLayer = CollisionGroups.Avatars | data.collisionLayer
-// const Expected = getInteractionGroups(ExpectedLayer, data.collisionMask)
-// const before = physicsWorld.Colliders.get(testEntity)!.collisionGroups()
-// Physics.setCollisionLayer(physicsWorld, testEntity, ExpectedLayer)
-// const after = physicsWorld.Colliders.get(testEntity)!.collisionGroups()
-// assert.notEqual(before, Expected)
-// assert.equal(after, Expected)
-// })
-
-// it('should not modify the collision mask of the collider', () => {
-// const data = getComponent(testEntity, ColliderComponent)
-// const newLayer = CollisionGroups.Avatars
-// const Expected = getInteractionGroups(newLayer, data.collisionMask)
-// Physics.setCollisionLayer(physicsWorld, testEntity, newLayer)
-// const after = physicsWorld.Colliders.get(testEntity)!.collisionGroups()
-// assert.equal(after, Expected)
-// })
-
-// it('should not add CollisionGroups.Trigger to the collider interaction groups if the entity does not have a TriggerComponent', () => {
-// Physics.setCollisionLayer(physicsWorld, testEntity, CollisionGroups.Avatars)
-// const after = physicsWorld.Colliders.get(testEntity)!.collisionGroups()
-// const noTriggerBit = !(after & getInteractionGroups(CollisionGroups.Trigger, 0)) // not collisionLayer contains Trigger
-// assert.ok(noTriggerBit)
-// })
-
-// it('should not modify the CollisionGroups.Trigger bit in the collider interaction groups if the entity has a TriggerComponent', () => {
-// const triggerLayer = getInteractionGroups(CollisionGroups.Trigger, 0) // Create the triggerLayer groups bitmask
-// setComponent(testEntity, TriggerComponent)
-// const beforeGroups = physicsWorld.Colliders.get(testEntity)!.collisionGroups()
-// const before = getInteractionGroups(beforeGroups & triggerLayer, 0) === triggerLayer // beforeGroups.collisionLayer contains Trigger
-// Physics.setCollisionLayer(physicsWorld, testEntity, CollisionGroups.Avatars)
-// const afterGroups = physicsWorld.Colliders.get(testEntity)!.collisionGroups()
-// const after = getInteractionGroups(afterGroups & triggerLayer, 0) === triggerLayer // afterGroups.collisionLayer contains Trigger
-// assert.equal(before, after)
-// })
-// }) // setCollisionLayer
-
-// describe('setCollisionMask', () => {
-// let testEntity = UndefinedEntity
-// let physicsWorld: PhysicsWorld
-
-// beforeEach(async () => {
-// createEngine()
-// await Physics.load()
-// const entity = createEntity()
-// setComponent(entity, UUIDComponent, UUIDComponent.generateUUID())
-// setComponent(entity, SceneComponent)
-// setComponent(entity, TransformComponent)
-// setComponent(entity, EntityTreeComponent)
-// physicsWorld = Physics.createWorld(getComponent(entity, UUIDComponent))
-// physicsWorld!.timestep = 1 / 60
-
-// // Create the entity
-// testEntity = createEntity()
-// setComponent(testEntity, EntityTreeComponent, { parentEntity: entity })
-// setComponent(testEntity, TransformComponent)
-// setComponent(testEntity, RigidBodyComponent, { type: BodyTypes.Dynamic })
-// setComponent(testEntity, ColliderComponent, { shape: Shapes.Sphere })
-// })
-
-// afterEach(() => {
-// removeEntity(testEntity)
-// return destroyEngine()
-// })
-
-// it('should set the collider mask to the given value', () => {
-// const before = getComponent(testEntity, ColliderComponent)
-// const Expected = CollisionGroups.Avatars | before.collisionMask
-// Physics.setCollisionMask(physicsWorld, testEntity, Expected)
-// const after = getComponent(testEntity, ColliderComponent)
-// assert.equal(after.collisionMask, Expected)
-// })
-
-// it('should not modify the collision layer of the collider', () => {
-// const before = getComponent(testEntity, ColliderComponent)
-// Physics.setCollisionMask(physicsWorld, testEntity, CollisionGroups.Avatars)
-// const after = getComponent(testEntity, ColliderComponent)
-// assert.equal(before.collisionLayer, after.collisionLayer)
-// })
-
-// it('should not add CollisionGroups.Trigger to the collider mask if the entity does not have a TriggerComponent', () => {
-// Physics.setCollisionMask(physicsWorld, testEntity, CollisionGroups.Avatars)
-// const after = getComponent(testEntity, ColliderComponent)
-// const noTriggerBit = !(after.collisionMask & CollisionGroups.Trigger) // not collisionMask contains Trigger
-// assert.ok(noTriggerBit)
-// })
-
-// it('should not modify the CollisionGroups.Trigger bit in the collider mask if the entity has a TriggerComponent', () => {
-// setComponent(testEntity, TriggerComponent)
-// const beforeData = getComponent(testEntity, ColliderComponent)
-// const before = beforeData.collisionMask & CollisionGroups.Trigger // collisionMask contains Trigger
-// Physics.setCollisionMask(physicsWorld, testEntity, CollisionGroups.Avatars)
-
-// const afterData = getComponent(testEntity, ColliderComponent)
-// const after = afterData.collisionMask & CollisionGroups.Trigger // collisionMask contains Trigger
-// assert.equal(before, after)
-// })
-// }) // setCollisionMask
-
-// describe('setFriction', () => {
-// let testEntity = UndefinedEntity
-// let physicsWorld: PhysicsWorld
-
-// beforeEach(async () => {
-// createEngine()
-// await Physics.load()
-// const entity = createEntity()
-// setComponent(entity, UUIDComponent, UUIDComponent.generateUUID())
-// setComponent(entity, SceneComponent)
-// setComponent(entity, TransformComponent)
-// setComponent(entity, EntityTreeComponent)
-// physicsWorld = Physics.createWorld(getComponent(entity, UUIDComponent))
-// physicsWorld!.timestep = 1 / 60
-
-// // Create the entity
-// testEntity = createEntity()
-// setComponent(testEntity, EntityTreeComponent, { parentEntity: entity })
-// setComponent(testEntity, TransformComponent)
-// setComponent(testEntity, RigidBodyComponent, { type: BodyTypes.Dynamic })
-// setComponent(testEntity, ColliderComponent, { shape: Shapes.Sphere })
-// })
-
-// afterEach(() => {
-// removeEntity(testEntity)
-// return destroyEngine()
-// })
-
-// it('should set the friction value on the entity', () => {
-// const ExpectedValue = 42
-// const collider = physicsWorld.Colliders.get(testEntity)!
-// assert.notEqual(collider.friction(), ExpectedValue)
-// Physics.setFriction(physicsWorld, testEntity, ExpectedValue)
-// assert.equal(collider.friction(), ExpectedValue)
-// })
-// }) // << setFriction
-
-// describe('setRestitution', () => {
-// let testEntity = UndefinedEntity
-// let physicsWorld: PhysicsWorld
-
-// beforeEach(async () => {
-// createEngine()
-// await Physics.load()
-// const entity = createEntity()
-// setComponent(entity, UUIDComponent, UUIDComponent.generateUUID())
-// setComponent(entity, SceneComponent)
-// setComponent(entity, TransformComponent)
-// setComponent(entity, EntityTreeComponent)
-// physicsWorld = Physics.createWorld(getComponent(entity, UUIDComponent))
-// physicsWorld!.timestep = 1 / 60
-
-// // Create the entity
-// testEntity = createEntity()
-// setComponent(testEntity, EntityTreeComponent, { parentEntity: entity })
-// setComponent(testEntity, TransformComponent)
-// setComponent(testEntity, RigidBodyComponent, { type: BodyTypes.Dynamic })
-// setComponent(testEntity, ColliderComponent, { shape: Shapes.Sphere })
-// })
-
-// afterEach(() => {
-// removeEntity(testEntity)
-// return destroyEngine()
-// })
-
-// it('should set the restitution value on the entity', () => {
-// const ExpectedValue = 42
-// const collider = physicsWorld.Colliders.get(testEntity)!
-// assert.notEqual(collider.restitution(), ExpectedValue)
-// Physics.setRestitution(physicsWorld, testEntity, ExpectedValue)
-// assert.equal(collider.restitution(), ExpectedValue)
-// })
-// }) // << setRestitution
-
-// describe('setMass', () => {
-// let testEntity = UndefinedEntity
-// let physicsWorld: PhysicsWorld
-
-// beforeEach(async () => {
-// createEngine()
-// await Physics.load()
-// const entity = createEntity()
-// setComponent(entity, UUIDComponent, UUIDComponent.generateUUID())
-// setComponent(entity, SceneComponent)
-// setComponent(entity, TransformComponent)
-// setComponent(entity, EntityTreeComponent)
-// physicsWorld = Physics.createWorld(getComponent(entity, UUIDComponent))
-// physicsWorld!.timestep = 1 / 60
-
-// // Create the entity
-// testEntity = createEntity()
-// setComponent(testEntity, EntityTreeComponent, { parentEntity: entity })
-// setComponent(testEntity, TransformComponent)
-// setComponent(testEntity, RigidBodyComponent, { type: BodyTypes.Dynamic })
-// setComponent(testEntity, ColliderComponent, { shape: Shapes.Sphere })
-// })
-
-// afterEach(() => {
-// removeEntity(testEntity)
-// return destroyEngine()
-// })
-
-// it('should set the mass value on the entity', () => {
-// const ExpectedValue = 42
-// const collider = physicsWorld.Colliders.get(testEntity)!
-// assert.notEqual(collider.mass(), ExpectedValue)
-// Physics.setMass(physicsWorld, testEntity, ExpectedValue)
-// assert.equal(collider.mass(), ExpectedValue)
-// })
-// }) // << setMass
-
-// describe('getShape', () => {
-// let testEntity = UndefinedEntity
-// let physicsWorld: PhysicsWorld
-
-// beforeEach(async () => {
-// createEngine()
-// await Physics.load()
-// const entity = createEntity()
-// setComponent(entity, UUIDComponent, UUIDComponent.generateUUID())
-// setComponent(entity, SceneComponent)
-// setComponent(entity, TransformComponent)
-// setComponent(entity, EntityTreeComponent)
-// physicsWorld = Physics.createWorld(getComponent(entity, UUIDComponent))
-// physicsWorld!.timestep = 1 / 60
-
-// // Create the entity
-// testEntity = createEntity()
-// setComponent(testEntity, EntityTreeComponent, { parentEntity: entity })
-// setComponent(testEntity, TransformComponent)
-// setComponent(testEntity, RigidBodyComponent, { type: BodyTypes.Dynamic })
-// })
-
-// afterEach(() => {
-// removeEntity(testEntity)
-// return destroyEngine()
-// })
-
-// it('should return a sphere shape', () => {
-// setComponent(testEntity, ColliderComponent, { shape: Shapes.Sphere })
-// Physics.createRigidBody(physicsWorld, testEntity)
-// assert.equal(Physics.getShape(physicsWorld, testEntity), Shapes.Sphere)
-// })
-
-// it('should return a capsule shape', () => {
-// setComponent(testEntity, ColliderComponent, { shape: Shapes.Capsule })
-// Physics.createRigidBody(physicsWorld, testEntity)
-// assert.equal(Physics.getShape(physicsWorld, testEntity), Shapes.Capsule)
-// })
-
-// it('should return a cylinder shape', () => {
-// setComponent(testEntity, ColliderComponent, { shape: Shapes.Cylinder })
-// Physics.createRigidBody(physicsWorld, testEntity)
-// assert.equal(Physics.getShape(physicsWorld, testEntity), Shapes.Cylinder)
-// })
-
-// it('should return a box shape', () => {
-// setComponent(testEntity, ColliderComponent, { shape: Shapes.Box })
-// Physics.createRigidBody(physicsWorld, testEntity)
-// assert.equal(Physics.getShape(physicsWorld, testEntity), Shapes.Box)
-// })
-
-// it('should return a plane shape', () => {
-// setComponent(testEntity, ColliderComponent, { shape: Shapes.Plane })
-// Physics.createRigidBody(physicsWorld, testEntity)
-// assert.equal(Physics.getShape(physicsWorld, testEntity), Shapes.Box) // The Shapes.Plane case is implemented as a box in the engine
-// })
-
-// it('should return undefined for the convex_hull case', () => {
-// setComponent(testEntity, ColliderComponent, { shape: Shapes.ConvexHull })
-// Physics.createRigidBody(physicsWorld, testEntity)
-// assert.equal(Physics.getShape(physicsWorld, testEntity), undefined /** @todo Shapes.ConvexHull */)
-// })
-
-// it('should return undefined for the mesh case', () => {
-// setComponent(testEntity, ColliderComponent, { shape: Shapes.Mesh })
-// Physics.createRigidBody(physicsWorld, testEntity)
-// assert.equal(Physics.getShape(physicsWorld, testEntity), undefined /** @todo Shapes.Mesh */)
-// })
-
-// /**
-// // @todo Heightfield is not supported yet. Triggers an Error exception
-// it("should return undefined for the heightfield case", () => {
-// setComponent(testEntity, ColliderComponent, { shape: Shapes.Heightfield })
-// Physics.createRigidBody(physicsWorld, testEntity)
-// assert.equal(Physics.getShape(physicsWorld, testEntity), Shapes.Heightfield)
-// })
-// */
-// }) // << getShape
-
-// describe('removeCollider', () => {
-// let testEntity = UndefinedEntity
-// let physicsWorld: PhysicsWorld
-
-// beforeEach(async () => {
-// createEngine()
-// await Physics.load()
-// const entity = createEntity()
-// setComponent(entity, UUIDComponent, UUIDComponent.generateUUID())
-// setComponent(entity, SceneComponent)
-// setComponent(entity, TransformComponent)
-// setComponent(entity, EntityTreeComponent)
-// physicsWorld = Physics.createWorld(getComponent(entity, UUIDComponent))
-// physicsWorld!.timestep = 1 / 60
-
-// // Create the entity
-// testEntity = createEntity()
-// setComponent(testEntity, EntityTreeComponent, { parentEntity: entity })
-// setComponent(testEntity, TransformComponent)
-// setComponent(testEntity, RigidBodyComponent, { type: BodyTypes.Dynamic })
-// setComponent(testEntity, ColliderComponent, { shape: Shapes.Box })
-// })
-
-// afterEach(() => {
-// removeEntity(testEntity)
-// return destroyEngine()
-// })
-
-// it("should remove the entity's collider", () => {
-// const before = physicsWorld.Colliders.get(testEntity)
-// assert.notEqual(before, undefined)
-// Physics.removeCollider(physicsWorld!, testEntity)
-// const after = physicsWorld.Colliders.get(testEntity)
-// assert.equal(after, undefined)
-// })
-// }) // << removeCollider
-
-// describe('removeCollidersFromRigidBody', () => {
-// let testEntity = UndefinedEntity
-// let physicsWorld: PhysicsWorld
-
-// beforeEach(async () => {
-// createEngine()
-// await Physics.load()
-// const entity = createEntity()
-// setComponent(entity, UUIDComponent, UUIDComponent.generateUUID())
-// setComponent(entity, SceneComponent)
-// setComponent(entity, TransformComponent)
-// setComponent(entity, EntityTreeComponent)
-// physicsWorld = Physics.createWorld(getComponent(entity, UUIDComponent))
-// physicsWorld!.timestep = 1 / 60
-
-// // Create the entity
-// testEntity = createEntity()
-// setComponent(testEntity, EntityTreeComponent, { parentEntity: entity })
-// setComponent(testEntity, TransformComponent)
-// setComponent(testEntity, RigidBodyComponent, { type: BodyTypes.Dynamic })
-// setComponent(testEntity, ColliderComponent)
-// })
-
-// afterEach(() => {
-// removeEntity(testEntity)
-// return destroyEngine()
-// })
-
-// it('should remove all Colliders from the RigidBody when called', () => {
-// const before = physicsWorld.Rigidbodies.get(testEntity)!
-// assert.notEqual(before.numColliders(), 0)
-// Physics.removeCollidersFromRigidBody(testEntity, physicsWorld!)
-// assert.equal(before.numColliders(), 0)
-// })
-// }) // << removeCollidersFromRigidBody
-
-// describe('createColliderDesc', () => {
-// const Default = {
-// // Default values returned by `createColliderDesc` when the default values of the components are not changed
-// enabled: true,
-// shape: { type: 1, halfExtents: { x: 0.5, y: 0.5, z: 0.5 } },
-// massPropsMode: 0,
-// density: 1,
-// friction: 0.5,
-// restitution: 0.5,
-// rotation: { x: 0, y: 0, z: 0, w: 1 },
-// translation: { x: 0, y: 0, z: 0 },
-// isSensor: false,
-// collisionGroups: 65543,
-// solverGroups: 4294967295,
-// frictionCombineRule: 0,
-// restitutionCombineRule: 0,
-// activeCollisionTypes: 60943,
-// activeEvents: 1,
-// activeHooks: 0,
-// mass: 0,
-// centerOfMass: { x: 0, y: 0, z: 0 },
-// contactForceEventThreshold: 0,
-// principalAngularInertia: { x: 0, y: 0, z: 0 },
-// angularInertiaLocalFrame: { x: 0, y: 0, z: 0, w: 1 }
-// }
-
-// let physicsWorld: PhysicsWorld
-// let testEntity = UndefinedEntity
-// let rootEntity = UndefinedEntity
-
-// beforeEach(async () => {
-// createEngine()
-// await Physics.load()
-// const entity = createEntity()
-// setComponent(entity, UUIDComponent, UUIDComponent.generateUUID())
-// setComponent(entity, SceneComponent)
-// setComponent(entity, TransformComponent)
-// setComponent(entity, EntityTreeComponent)
-// physicsWorld = Physics.createWorld(getComponent(entity, UUIDComponent))
-// physicsWorld!.timestep = 1 / 60
-
-// // Create the entity
-// testEntity = createEntity()
-// setComponent(testEntity, EntityTreeComponent, { parentEntity: rootEntity })
-// setComponent(testEntity, TransformComponent)
-// setComponent(testEntity, RigidBodyComponent)
-// setComponent(testEntity, ColliderComponent)
-// rootEntity = createEntity()
-// setComponent(rootEntity, EntityTreeComponent, { parentEntity: entity })
-// setComponent(rootEntity, TransformComponent)
-// setComponent(rootEntity, RigidBodyComponent)
-// setComponent(rootEntity, ColliderComponent)
-// })
-
-// afterEach(() => {
-// removeEntity(testEntity)
-// removeEntity(rootEntity)
-// return destroyEngine()
-// })
-
-// it('should return early if the given `rootEntity` does not have a RigidBody', () => {
-// removeComponent(rootEntity, RigidBodyComponent)
-// const result = Physics.createColliderDesc(physicsWorld, testEntity, rootEntity)
-// assert.equal(result, undefined)
-// })
-
-// it('should return a descriptor with the expected default values', () => {
-// const result = Physics.createColliderDesc(physicsWorld, testEntity, rootEntity)
-// assert.deepEqual(result, Default)
-// })
-
-// it('should set the friction to the same value as the ColliderComponent', () => {
-// const result = Physics.createColliderDesc(physicsWorld, testEntity, rootEntity)
-// assert.equal(result.friction, getComponent(testEntity, ColliderComponent).friction)
-// })
-
-// it('should set the restitution to the same value as the ColliderComponent', () => {
-// const result = Physics.createColliderDesc(physicsWorld, testEntity, rootEntity)
-// assert.equal(result.restitution, getComponent(testEntity, ColliderComponent).restitution)
-// })
-
-// it('should set the collisionGroups to the same value as the ColliderComponent layer and mask', () => {
-// const result = Physics.createColliderDesc(physicsWorld, testEntity, rootEntity)
-// const data = getComponent(testEntity, ColliderComponent)
-// assert.equal(result.collisionGroups, getInteractionGroups(data.collisionLayer, data.collisionMask))
-// })
-
-// it('should set the sensor property according to whether the entity has a TriggerComponent or not', () => {
-// const noTriggerDesc = Physics.createColliderDesc(physicsWorld, testEntity, rootEntity)
-// assert.equal(noTriggerDesc.isSensor, hasComponent(testEntity, TriggerComponent))
-// setComponent(testEntity, TriggerComponent)
-// const triggerDesc = Physics.createColliderDesc(physicsWorld, testEntity, rootEntity)
-// assert.equal(triggerDesc.isSensor, hasComponent(testEntity, TriggerComponent))
-// })
-
-// it('should set the shape to a Ball when the ColliderComponent shape is a Sphere', () => {
-// setComponent(testEntity, ColliderComponent, { shape: Shapes.Sphere })
-// const result = Physics.createColliderDesc(physicsWorld, testEntity, rootEntity)
-// assert.equal(result.shape.type, ShapeType.Ball)
-// })
-
-// it('should set the shape to a Cuboid when the ColliderComponent shape is a Box', () => {
-// setComponent(testEntity, ColliderComponent, { shape: Shapes.Box })
-// const result = Physics.createColliderDesc(physicsWorld, testEntity, rootEntity)
-// assert.equal(result.shape.type, ShapeType.Cuboid)
-// })
-
-// it('should set the shape to a Cuboid when the ColliderComponent shape is a Plane', () => {
-// setComponent(testEntity, ColliderComponent, { shape: Shapes.Plane })
-// const result = Physics.createColliderDesc(physicsWorld, testEntity, rootEntity)
-// assert.equal(result.shape.type, ShapeType.Cuboid)
-// })
-
-// it('should set the shape to a TriMesh when the ColliderComponent shape is a Mesh', () => {
-// setComponent(testEntity, MeshComponent, new Mesh(new BoxGeometry()))
-// setComponent(testEntity, ColliderComponent, { shape: Shapes.Mesh })
-// const result = Physics.createColliderDesc(physicsWorld, testEntity, rootEntity)
-// assert.equal(result.shape.type, ShapeType.TriMesh)
-// })
-
-// it('should set the shape to a ConvexPolyhedron when the ColliderComponent shape is a ConvexHull', () => {
-// setComponent(testEntity, MeshComponent, new Mesh(new BoxGeometry()))
-// setComponent(testEntity, ColliderComponent, { shape: Shapes.ConvexHull })
-// const result = Physics.createColliderDesc(physicsWorld, testEntity, rootEntity)
-// assert.equal(result.shape.type, ShapeType.ConvexPolyhedron)
-// })
-
-// it('should set the shape to a Cylinder when the ColliderComponent shape is a Cylinder', () => {
-// setComponent(testEntity, ColliderComponent, { shape: Shapes.Cylinder })
-// const result = Physics.createColliderDesc(physicsWorld, testEntity, rootEntity)
-// assert.equal(result.shape.type, ShapeType.Cylinder)
-// })
-
-// it('should set the position relative to the parent entity', () => {
-// const Expected = new Vector3(1, 2, 3)
-// const result = Physics.createColliderDesc(physicsWorld, testEntity, rootEntity)
-// console.log(JSON.stringify(result))
-// console.log(JSON.stringify(result.translation))
-// assertVecApproxEq(result.translation, Vector3_Zero, 3)
-// })
-
-// it('should set the rotation relative to the parent entity', () => {
-// const Expected = new Quaternion(0.5, 0.3, 0.2, 0.0).normalize()
-// const result = Physics.createColliderDesc(physicsWorld, testEntity, rootEntity)
-// console.log(JSON.stringify(result.rotation))
-// assertVecApproxEq(result.rotation, Rotation_Zero, 4)
-// })
-// })
-
-// describe('attachCollider', () => {
-// let testEntity = UndefinedEntity
-// let rigidbodyEntity = UndefinedEntity
-// let physicsWorld: PhysicsWorld
-
-// beforeEach(async () => {
-// createEngine()
-// await Physics.load()
-// const entity = createEntity()
-// setComponent(entity, UUIDComponent, UUIDComponent.generateUUID())
-// setComponent(entity, SceneComponent)
-// setComponent(entity, TransformComponent)
-// setComponent(entity, EntityTreeComponent)
-// physicsWorld = Physics.createWorld(getComponent(entity, UUIDComponent))
-// physicsWorld!.timestep = 1 / 60
-
-// // Create the entity
-// testEntity = createEntity()
-// setComponent(testEntity, EntityTreeComponent, { parentEntity: entity })
-// setComponent(testEntity, TransformComponent)
-// setComponent(testEntity, RigidBodyComponent, { type: BodyTypes.Dynamic })
-// setComponent(testEntity, ColliderComponent, { shape: Shapes.Box })
-// rigidbodyEntity = createEntity()
-// setComponent(rigidbodyEntity, EntityTreeComponent, { parentEntity: entity })
-// setComponent(rigidbodyEntity, TransformComponent)
-// setComponent(rigidbodyEntity, RigidBodyComponent, { type: BodyTypes.Dynamic })
-// setComponent(rigidbodyEntity, ColliderComponent, { shape: Shapes.Box })
-// })
-
-// afterEach(() => {
-// removeEntity(testEntity)
-// removeEntity(rigidbodyEntity)
-// return destroyEngine()
-// })
-
-// it("should return undefined when rigidBodyEntity doesn't have a RigidBodyComponent", () => {
-// removeComponent(rigidbodyEntity, RigidBodyComponent)
-// const colliderDesc = Physics.createColliderDesc(physicsWorld, testEntity, rigidbodyEntity)
-// const result = Physics.attachCollider(physicsWorld!, colliderDesc, rigidbodyEntity, testEntity)
-// assert.equal(result, undefined)
-// })
-
-// it('should add the collider to the physicsWorld.Colliders map', () => {
-// ColliderComponent.reactorMap.get(testEntity)!.stop()
-// const colliderDesc = Physics.createColliderDesc(physicsWorld, testEntity, rigidbodyEntity)
-// const result = Physics.attachCollider(physicsWorld!, colliderDesc, rigidbodyEntity, testEntity)!
-// const expected = physicsWorld.Colliders.get(testEntity)
-// assert.ok(result)
-// assert.ok(expected)
-// assert.deepEqual(result.handle, expected.handle)
-// })
-// })
-
-// describe('setColliderPose', () => {
-// let testEntity = UndefinedEntity
-// let physicsWorld: PhysicsWorld
-// const position = new Vector3(1, 2, 3)
-// const rotation = new Quaternion(0.5, 0.4, 0.1, 0.0).normalize()
-
-// beforeEach(async () => {
-// createEngine()
-// await Physics.load()
-// const entity = createEntity()
-// setComponent(entity, UUIDComponent, UUIDComponent.generateUUID())
-// setComponent(entity, SceneComponent)
-// setComponent(entity, TransformComponent)
-// setComponent(entity, EntityTreeComponent)
-// physicsWorld = Physics.createWorld(getComponent(entity, UUIDComponent))
-// physicsWorld!.timestep = 1 / 60
-
-// // Create the entity
-// testEntity = createEntity()
-// setComponent(testEntity, EntityTreeComponent, { parentEntity: entity })
-// setComponent(testEntity, TransformComponent)
-// setComponent(testEntity, RigidBodyComponent, { type: BodyTypes.Dynamic })
-// setComponent(testEntity, ColliderComponent, { shape: Shapes.Box })
-// })
-
-// afterEach(() => {
-// removeEntity(testEntity)
-// return destroyEngine()
-// })
-
-// it("should assign the entity's position to the collider.translation property", () => {
-// Physics.setColliderPose(physicsWorld, testEntity, position, rotation)
-// const collider = physicsWorld.Colliders.get(testEntity)!
-// // need to step to update the collider's position
-// physicsWorld.step()
-// assertVecApproxEq(collider.translation(), position, 3, 0.01)
-// })
-
-// it("should assign the entity's rotation to the collider.rotation property", () => {
-// Physics.setColliderPose(physicsWorld, testEntity, position, rotation)
-// const collider = physicsWorld.Colliders.get(testEntity)!
-// // need to step to update the collider's position
-// physicsWorld.step()
-// assertVecApproxEq(collider.rotation(), rotation, 4)
-// })
-// })
-
-// describe('setMassCenter', () => {}) /** @todo The function is not implemented. It is annotated with a todo tag */
-// }) // << Colliders
-
-// describe('CharacterControllers', () => {
-// describe('createCharacterController', () => {
-// const Default = {
-// offset: 0.01,
-// maxSlopeClimbAngle: (60 * Math.PI) / 180,
-// minSlopeSlideAngle: (30 * Math.PI) / 180,
-// autoStep: { maxHeight: 0.5, minWidth: 0.01, stepOverDynamic: true },
-// enableSnapToGround: 0.1 as number | false
-// }
-
-// let testEntity = UndefinedEntity
-// let physicsWorld: PhysicsWorld
-
-// beforeEach(async () => {
-// createEngine()
-// await Physics.load()
-// const entity = createEntity()
-// setComponent(entity, UUIDComponent, UUIDComponent.generateUUID())
-// setComponent(entity, SceneComponent)
-// setComponent(entity, TransformComponent)
-// setComponent(entity, EntityTreeComponent)
-// physicsWorld = Physics.createWorld(getComponent(entity, UUIDComponent))
-// physicsWorld!.timestep = 1 / 60
-
-// // Create the entity
-// testEntity = createEntity()
-// setComponent(testEntity, EntityTreeComponent, { parentEntity: entity })
-// setComponent(testEntity, TransformComponent)
-// setComponent(testEntity, RigidBodyComponent, { type: BodyTypes.Dynamic })
-// setComponent(testEntity, ColliderComponent, { shape: Shapes.Mesh })
-// })
-
-// afterEach(() => {
-// removeEntity(testEntity)
-// return destroyEngine()
-// })
-
-// it('should store a character controller in the Controllers map', () => {
-// const before = physicsWorld.Controllers.get(testEntity)
-// assert.equal(before, undefined)
-// Physics.createCharacterController(physicsWorld, testEntity, {})
-// const after = physicsWorld.Controllers.get(testEntity)
-// assert.ok(after)
-// })
-
-// it('should create a the character controller with the expected defaults when they are omitted', () => {
-// Physics.createCharacterController(physicsWorld, testEntity, {})
-// const controller = physicsWorld.Controllers.get(testEntity)
-// assert.ok(controller)
-// assertFloatApproxEq(controller.offset(), Default.offset)
-// assertFloatApproxEq(controller.maxSlopeClimbAngle(), Default.maxSlopeClimbAngle)
-// assertFloatApproxEq(controller.minSlopeSlideAngle(), Default.minSlopeSlideAngle)
-// assertFloatApproxEq(controller.autostepMaxHeight()!, Default.autoStep.maxHeight)
-// assertFloatApproxEq(controller.autostepMinWidth()!, Default.autoStep.minWidth)
-// assert.equal(controller.autostepEnabled(), Default.autoStep.stepOverDynamic)
-// assert.equal(controller.snapToGroundEnabled(), !!Default.enableSnapToGround)
-// })
-
-// it('should create a the character controller with values different than the defaults when they are specified', () => {
-// const Expected = {
-// offset: 0.05,
-// maxSlopeClimbAngle: (20 * Math.PI) / 180,
-// minSlopeSlideAngle: (60 * Math.PI) / 180,
-// autoStep: { maxHeight: 0.1, minWidth: 0.05, stepOverDynamic: false },
-// enableSnapToGround: false as number | false
-// }
-// Physics.createCharacterController(physicsWorld, testEntity, Expected)
-// const controller = physicsWorld.Controllers.get(testEntity)
-// assert.ok(controller)
-// // Compare against the specified values
-// assertFloatApproxEq(controller.offset(), Expected.offset)
-// assertFloatApproxEq(controller.maxSlopeClimbAngle(), Expected.maxSlopeClimbAngle)
-// assertFloatApproxEq(controller.minSlopeSlideAngle(), Expected.minSlopeSlideAngle)
-// assertFloatApproxEq(controller.autostepMaxHeight()!, Expected.autoStep.maxHeight)
-// assertFloatApproxEq(controller.autostepMinWidth()!, Expected.autoStep.minWidth)
-// assert.equal(controller.autostepIncludesDynamicBodies(), Expected.autoStep.stepOverDynamic)
-// assert.equal(controller.snapToGroundEnabled(), !!Expected.enableSnapToGround)
-// // Compare against the defaults
-// assertFloatApproxNotEq(controller.offset(), Default.offset)
-// assertFloatApproxNotEq(controller.maxSlopeClimbAngle(), Default.maxSlopeClimbAngle)
-// assertFloatApproxNotEq(controller.minSlopeSlideAngle(), Default.minSlopeSlideAngle)
-// assertFloatApproxNotEq(controller.autostepMaxHeight()!, Default.autoStep.maxHeight)
-// assertFloatApproxNotEq(controller.autostepMinWidth()!, Default.autoStep.minWidth)
-// assert.notEqual(controller.autostepIncludesDynamicBodies(), Default.autoStep.stepOverDynamic)
-// assert.notEqual(controller.snapToGroundEnabled(), !!Default.enableSnapToGround)
-// })
-// })
-
-// describe('removeCharacterController', () => {
-// let testEntity = UndefinedEntity
-// let physicsWorld: PhysicsWorld
-
-// beforeEach(async () => {
-// createEngine()
-// await Physics.load()
-// const entity = createEntity()
-// setComponent(entity, UUIDComponent, UUIDComponent.generateUUID())
-// setComponent(entity, SceneComponent)
-// setComponent(entity, TransformComponent)
-// setComponent(entity, EntityTreeComponent)
-// physicsWorld = Physics.createWorld(getComponent(entity, UUIDComponent))
-// physicsWorld!.timestep = 1 / 60
-
-// // Create the entity
-// testEntity = createEntity()
-// setComponent(testEntity, EntityTreeComponent, { parentEntity: entity })
-// setComponent(testEntity, TransformComponent)
-// setComponent(testEntity, RigidBodyComponent, { type: BodyTypes.Dynamic })
-// setComponent(testEntity, ColliderComponent, { shape: Shapes.Mesh })
-// })
-
-// afterEach(() => {
-// removeEntity(testEntity)
-// return destroyEngine()
-// })
-
-// it('should remove the character controller from the Controllers map', () => {
-// const before = physicsWorld.Controllers.get(testEntity)
-// assert.equal(before, undefined)
-// Physics.createCharacterController(physicsWorld, testEntity, {})
-// const created = physicsWorld.Controllers.get(testEntity)
-// assert.ok(created)
-// Physics.removeCharacterController(physicsWorld, testEntity)
-// const after = physicsWorld.Controllers.get(testEntity)
-// assert.equal(after, undefined)
-// })
-// })
-
-// describe('computeColliderMovement', () => {
-// let testEntity = UndefinedEntity
-// let physicsWorld: PhysicsWorld
-
-// beforeEach(async () => {
-// createEngine()
-// await Physics.load()
-// const entity = createEntity()
-// setComponent(entity, UUIDComponent, UUIDComponent.generateUUID())
-// setComponent(entity, SceneComponent)
-// setComponent(entity, TransformComponent)
-// setComponent(entity, EntityTreeComponent)
-// physicsWorld = Physics.createWorld(getComponent(entity, UUIDComponent))
-// physicsWorld!.timestep = 1 / 60
-
-// // Create the entity
-// testEntity = createEntity()
-// setComponent(testEntity, EntityTreeComponent, { parentEntity: entity })
-// setComponent(testEntity, TransformComponent)
-// setComponent(testEntity, RigidBodyComponent, { type: BodyTypes.Dynamic })
-// setComponent(testEntity, ColliderComponent, { shape: Shapes.Box })
-// Physics.createCharacterController(physicsWorld, testEntity, {})
-// })
-
-// afterEach(() => {
-// removeEntity(testEntity)
-// return destroyEngine()
-// })
-
-// it("should change the `computedMovement` value for the entity's Character Controller", () => {
-// const movement = new Vector3(1, 2, 3)
-// const controller = physicsWorld.Controllers.get(testEntity)!
-// const before = controller.computedMovement()
-// Physics.computeColliderMovement(
-// physicsWorld,
-// testEntity, // entity: Entity,
-// testEntity, // colliderEntity: Entity,
-// movement // desiredTranslation: Vector3,
-// // filterGroups?: InteractionGroups,
-// // filterPredicate?: (collider: Collider) => boolean
-// )
-// const after = controller.computedMovement()
-// assertVecAllApproxNotEq(before, after, 3)
-// })
-// }) // << computeColliderMovement
-
-// describe('getComputedMovement', () => {
-// let testEntity = UndefinedEntity
-// let physicsWorld: PhysicsWorld
-
-// beforeEach(async () => {
-// createEngine()
-// await Physics.load()
-// const entity = createEntity()
-// setComponent(entity, UUIDComponent, UUIDComponent.generateUUID())
-// setComponent(entity, SceneComponent)
-// setComponent(entity, TransformComponent)
-// setComponent(entity, EntityTreeComponent)
-// physicsWorld = Physics.createWorld(getComponent(entity, UUIDComponent))
-// physicsWorld!.timestep = 1 / 60
-
-// // Create the entity
-// testEntity = createEntity()
-// setComponent(testEntity, EntityTreeComponent, { parentEntity: entity })
-// setComponent(testEntity, TransformComponent)
-// setComponent(testEntity, RigidBodyComponent, { type: BodyTypes.Dynamic })
-// setComponent(testEntity, ColliderComponent, { shape: Shapes.Box })
-// })
-
-// afterEach(() => {
-// removeEntity(testEntity)
-// return destroyEngine()
-// })
-
-// it('should return (0,0,0) when the entity does not have a CharacterController', () => {
-// const result = new Vector3(1, 2, 3)
-// Physics.getComputedMovement(physicsWorld, testEntity, result)
-// assertVecApproxEq(result, Vector3_Zero, 3)
-// })
-
-// it("should return the same value contained in the `computedMovement` value of the entity's Character Controller", () => {
-// Physics.createCharacterController(physicsWorld, testEntity, {})
-// const movement = new Vector3(1, 2, 3)
-// const controller = physicsWorld.Controllers.get(testEntity)!
-// const before = controller.computedMovement()
-// Physics.computeColliderMovement(
-// physicsWorld,
-// testEntity, // entity: Entity,
-// testEntity, // colliderEntity: Entity,
-// movement // desiredTranslation: Vector3,
-// // filterGroups?: InteractionGroups,
-// // filterPredicate?: (collider: Collider) => boolean
-// )
-// const after = controller.computedMovement()
-// assertVecAllApproxNotEq(before, after, 3)
-// const result = new Vector3()
-// Physics.getComputedMovement(physicsWorld, testEntity, result)
-// assertVecAllApproxNotEq(before, result, 3)
-// assertVecApproxEq(after, result, 3)
-// })
-// }) // << getComputedMovement
-// }) // << CharacterControllers
-
-// describe('Raycasts', () => {
-// describe('castRay', () => {
-// let testEntity = UndefinedEntity
-// let physicsWorld: PhysicsWorld
-
-// beforeEach(async () => {
-// createEngine()
-// await Physics.load()
-// const entity = createEntity()
-// setComponent(entity, UUIDComponent, UUIDComponent.generateUUID())
-// setComponent(entity, SceneComponent)
-// setComponent(entity, TransformComponent)
-// setComponent(entity, EntityTreeComponent)
-// physicsWorld = Physics.createWorld(getComponent(entity, UUIDComponent))
-// physicsWorld!.timestep = 1 / 60
-
-// // Create the entity
-// testEntity = createEntity()
-// setComponent(testEntity, EntityTreeComponent, { parentEntity: entity })
-// setComponent(testEntity, TransformComponent, {
-// position: new Vector3(10, 0, 0),
-// scale: new Vector3(10, 10, 10)
-// })
-// computeTransformMatrix(testEntity)
-// setComponent(testEntity, RigidBodyComponent, { type: BodyTypes.Fixed })
-// setComponent(testEntity, ColliderComponent, {
-// shape: Shapes.Box,
-// collisionLayer: CollisionGroups.Default,
-// collisionMask: DefaultCollisionMask
-// })
-// })
-
-// afterEach(() => {
-// removeEntity(testEntity)
-// return destroyEngine()
-// })
-
-// it('should cast a ray and hit a rigidbody', async () => {
-// physicsWorld!.step()
-
-// const raycastComponentData = {
-// type: SceneQueryType.Closest,
-// origin: new Vector3().set(0, 0, 0),
-// direction: ObjectDirection.Right,
-// maxDistance: 20,
-// groups: getInteractionGroups(CollisionGroups.Default, CollisionGroups.Default)
-// }
-// const hits = Physics.castRay(physicsWorld!, raycastComponentData)
-
-// assert.deepEqual(hits.length, 1)
-// assert.deepEqual(hits[0].normal.x, -1)
-// assert.deepEqual(hits[0].distance, 5)
-// assert.deepEqual((hits[0].body.userData as any)['entity'], testEntity)
-// })
-// })
-
-// describe('castRayFromCamera', () => {
-// let testEntity = UndefinedEntity
-// let physicsWorld: PhysicsWorld
-
-// beforeEach(async () => {
-// createEngine()
-// await Physics.load()
-// const entity = createEntity()
-// setComponent(entity, UUIDComponent, UUIDComponent.generateUUID())
-// setComponent(entity, SceneComponent)
-// setComponent(entity, TransformComponent)
-// setComponent(entity, EntityTreeComponent)
-// physicsWorld = Physics.createWorld(getComponent(entity, UUIDComponent))
-// physicsWorld!.timestep = 1 / 60
-
-// // Create the entity
-// testEntity = createEntity()
-// setComponent(testEntity, EntityTreeComponent, { parentEntity: entity })
-// setComponent(testEntity, TransformComponent, {
-// position: new Vector3(10, 0, 0),
-// scale: new Vector3(10, 10, 10)
-// })
-// computeTransformMatrix(testEntity)
-// setComponent(testEntity, RigidBodyComponent, { type: BodyTypes.Fixed })
-// setComponent(testEntity, ColliderComponent, {
-// shape: Shapes.Box,
-// collisionLayer: CollisionGroups.Default,
-// collisionMask: DefaultCollisionMask
-// })
-// })
-
-// afterEach(() => {
-// removeEntity(testEntity)
-// return destroyEngine()
-// })
-
-// /*
-// it('should cast a ray from a camera and hit a rigidbody', async () => {
-// physicsWorld!.step()
-// assert.ok(1)
-// })
-// */
-// }) // << castRayFromCamera
-
-// /**
-// // @todo Double check the `castShape` implementation before implementing this test
-// describe('castShape', () => {
-// let testEntity = UndefinedEntity
-// let physicsWorld: PhysicsWorld
-
-// beforeEach(async () => {
-// createEngine()
-// await Physics.load()
-// const entity = createEntity()
-// setComponent(entity, UUIDComponent, UUIDComponent.generateUUID())
-// setComponent(entity, SceneComponent)
-// setComponent(entity, TransformComponent)
-// setComponent(entity, EntityTreeComponent)
-// physicsWorld = Physics.createWorld(getComponent(entity, UUIDComponent))
-// physicsWorld!.timestep = 1 / 60
-
-// // Create the entity
-// testEntity = createEntity()
-// setComponent(testEntity, EntityTreeComponent, { parentEntity: entity })
-// setComponent(testEntity, TransformComponent, {
-// position: new Vector3(10, 0, 0),
-// scale: new Vector3(10, 10, 10)
-// })
-// computeTransformMatrix(testEntity)
-// setComponent(testEntity, RigidBodyComponent, { type: BodyTypes.Fixed })
-// setComponent(testEntity, ColliderComponent, {
-// shape: Shapes.Box,
-// collisionLayer: CollisionGroups.Default,
-// collisionMask: DefaultCollisionMask
-// })
-// })
-
-// afterEach(() => {
-// removeEntity(testEntity)
-// return destroyEngine()
-// })
-
-// // @todo This setup is not hitting. Double check the `castShape` implementation before implementing this test
-// it('should cast a shape and hit a rigidbody', () => {
-// physicsWorld!.step()
-
-// const collider = physicsWorld.Colliders.get(testEntity)!
-// const hits = [] as RaycastHit[]
-// const shapecastComponentData :ShapecastArgs= {
-// type: SceneQueryType.Closest, // type: SceneQueryType
-// hits: hits, // hits: RaycastHit[]
-// collider: collider, // collider: Collider
-// direction: ObjectDirection.Right, // direction: Vector3
-// maxDistance: 20, // maxDistance: number
-// collisionGroups: getInteractionGroups(CollisionGroups.Default, CollisionGroups.Default), // collisionGroups: InteractionGroups
-// }
-// Physics.castShape(physicsWorld!, shapecastComponentData)
-
-// assert.deepEqual(hits.length, 1, "The length of the hits array is incorrect.")
-// assert.deepEqual(hits[0].normal.x, -1)
-// assert.deepEqual(hits[0].distance, 5)
-// assert.deepEqual((hits[0].body.userData as any)['entity'], testEntity)
-// })
-// }) // << castShape
-// */
-// }) // << Raycasts
-
-// describe('Collisions', () => {
-// describe('createCollisionEventQueue', () => {
-// beforeEach(async () => {
-// createEngine()
-// await Physics.load()
-// })
-
-// afterEach(() => {
-// return destroyEngine()
-// })
-
-// it('should create a collision event queue successfully', () => {
-// const queue = Physics.createCollisionEventQueue()
-// assert(queue)
-// })
-// })
-
-// describe('drainCollisionEventQueue', () => {
-// const InvalidHandle = 8198123
-// let physicsWorld: PhysicsWorld
-// let testEntity1 = UndefinedEntity
-// let testEntity2 = UndefinedEntity
-
-// beforeEach(async () => {
-// createEngine()
-// await Physics.load()
-// const entity = createEntity()
-// setComponent(entity, UUIDComponent, UUIDComponent.generateUUID())
-// setComponent(entity, SceneComponent)
-// setComponent(entity, TransformComponent)
-// setComponent(entity, EntityTreeComponent)
-// physicsWorld = Physics.createWorld(getComponent(entity, UUIDComponent))
-// physicsWorld.timestep = 1 / 60
-
-// testEntity1 = createEntity()
-// setComponent(testEntity1, EntityTreeComponent, { parentEntity: entity })
-// setComponent(testEntity1, TransformComponent)
-// setComponent(testEntity1, RigidBodyComponent)
-// setComponent(testEntity1, ColliderComponent)
-
-// testEntity2 = createEntity()
-// setComponent(testEntity2, EntityTreeComponent, { parentEntity: entity })
-// setComponent(testEntity2, TransformComponent)
-// setComponent(testEntity2, RigidBodyComponent)
-// setComponent(testEntity2, ColliderComponent)
-// })
-
-// afterEach(() => {
-// return destroyEngine()
-// })
-
-// function assertCollisionEventClosure(closure: any) {
-// type CollisionEventClosure = (handle1: number, handle2: number, started: boolean) => void
-// function hasCollisionEventClosureShape(closure: any): closure is CollisionEventClosure {
-// return typeof closure === 'function' && closure.length === 3
-// }
-// assert.ok(closure)
-// assert.ok(hasCollisionEventClosureShape(closure))
-// }
-
-// it('should return a function with the correct shape (handle1: number, handle2: number, started: boolean) => void', () => {
-// assert.ok(physicsWorld)
-// const event = Physics.drainCollisionEventQueue(physicsWorld)
-// assertCollisionEventClosure(event)
-// })
-
-// it('should do nothing if any of the collider handles are not found', () => {
-// assert.ok(physicsWorld)
-// const event = Physics.drainCollisionEventQueue(physicsWorld)
-// assertCollisionEventClosure(event)
-// physicsWorld.step()
-// const collider1 = physicsWorld.Colliders.get(testEntity1)
-// const collider2 = physicsWorld.Colliders.get(testEntity2)
-// assert.ok(collider1)
-// assert.ok(collider2)
-
-// assert.ok(!hasComponent(testEntity1, CollisionComponent))
-// event(collider1.handle, InvalidHandle, true)
-// assert.ok(!hasComponent(testEntity1, CollisionComponent))
-
-// assert.ok(!hasComponent(testEntity2, CollisionComponent))
-// event(collider2!.handle, InvalidHandle, true)
-// assert.ok(!hasComponent(testEntity2, CollisionComponent))
-// })
-
-// it('should add a CollisionComponent to the entities contained in the userData of the parent rigidBody of each collider (collider.parent())', () => {
-// assert.ok(physicsWorld)
-// const event = Physics.drainCollisionEventQueue(physicsWorld)
-// assertCollisionEventClosure(event)
-// physicsWorld.step()
-
-// // Get the colliders from the API
-// const collider1 = physicsWorld.Colliders.get(testEntity1)
-// const collider2 = physicsWorld.Colliders.get(testEntity2)
-// assert.ok(collider1)
-// assert.ok(collider2)
-// // Get the parents from the API
-// const colliderParent1 = collider1.parent()
-// const colliderParent2 = collider2.parent()
-// assert.ok(colliderParent1)
-// assert.ok(colliderParent2)
-// // Get the entities from parent.userData
-// const entity1 = (colliderParent1.userData as any)['entity']
-// const entity2 = (colliderParent2.userData as any)['entity']
-// assert.equal(testEntity1, entity1)
-// assert.equal(testEntity2, entity2)
-// // Check before
-// assert.ok(!hasComponent(entity1, CollisionComponent))
-// assert.ok(!hasComponent(entity2, CollisionComponent))
-
-// // Run and Check after
-// event(collider1.handle, collider2.handle, true)
-// assert.ok(hasComponent(entity1, CollisionComponent))
-// assert.ok(hasComponent(entity2, CollisionComponent))
-// })
-
-// describe('when `started` is set to `true` ...', () => {
-// it('... should create a CollisionEvents.COLLISION_START when neither of the colliders is a sensor (aka has a TriggerComponent)', () => {
-// const Started = true
-
-// assert.ok(physicsWorld)
-// const event = Physics.drainCollisionEventQueue(physicsWorld)
-// assertCollisionEventClosure(event)
-// // Get the colliders from the API
-// const collider1 = physicsWorld.Colliders.get(testEntity1)
-// const collider2 = physicsWorld.Colliders.get(testEntity2)
-// assert.ok(collider1)
-// assert.ok(collider2)
-// // Check before
-// const before1 = getComponent(testEntity1, CollisionComponent)?.get(testEntity2)
-// const before2 = getComponent(testEntity2, CollisionComponent)?.get(testEntity1)
-// assert.equal(before1, undefined)
-// assert.equal(before2, undefined)
-// // setComponent(testEntity1, TriggerComponent) // DONT set the trigger component (testEntity1.body.isSensor() is false)
-
-// // Run and Check after
-// event(collider1.handle, collider2.handle, Started)
-// const after1 = getComponent(testEntity1, CollisionComponent).get(testEntity2)
-// const after2 = getComponent(testEntity2, CollisionComponent).get(testEntity1)
-// assert.ok(after1)
-// assert.ok(after2)
-// assert.equal(after1.type, CollisionEvents.COLLISION_START)
-// assert.equal(after2.type, CollisionEvents.COLLISION_START)
-// })
-
-// it('... should create a CollisionEvents.TRIGGER_START when either one of the colliders is a sensor (aka has a TriggerComponent)', async () => {
-// //force nested reactors to run
-// const { rerender, unmount } = render(<>>)
-
-// const Started = true
-
-// assert.ok(physicsWorld)
-// const event = Physics.drainCollisionEventQueue(physicsWorld)
-// assertCollisionEventClosure(event)
-// // Get the colliders from the API
-// const collider1 = physicsWorld.Colliders.get(testEntity1)
-// const collider2 = physicsWorld.Colliders.get(testEntity2)
-// assert.ok(collider1)
-// assert.ok(collider2)
-// // Check before
-// const before1 = getComponent(testEntity1, CollisionComponent)?.get(testEntity2)
-// const before2 = getComponent(testEntity2, CollisionComponent)?.get(testEntity1)
-// assert.equal(before1, undefined)
-// assert.equal(before2, undefined)
-// setComponent(testEntity1, TriggerComponent) // Set the trigger component (marks testEntity1.body.isSensor() as true)
-// await act(() => rerender(<>>))
-
-// event(collider1.handle, collider2.handle, Started)
-
-// // Run and Check after
-// const after1 = getComponent(testEntity1, CollisionComponent).get(testEntity2)
-// const after2 = getComponent(testEntity2, CollisionComponent).get(testEntity1)
-// assert.ok(after1)
-// assert.ok(after2)
-// assert.equal(after1.type, CollisionEvents.TRIGGER_START)
-// assert.equal(after2.type, CollisionEvents.TRIGGER_START)
-// })
-
-// it('... should set entity2 in the CollisionComponent of entity1, and entity1 in the CollisionComponent of entity2', () => {
-// assert.ok(physicsWorld)
-// const event = Physics.drainCollisionEventQueue(physicsWorld)
-// assertCollisionEventClosure(event)
-// // Get the colliders from the API
-// const collider1 = physicsWorld.Colliders.get(testEntity1)
-// const collider2 = physicsWorld.Colliders.get(testEntity2)
-// assert.ok(collider1)
-// assert.ok(collider2)
-// // Check before
-// const before1 = getComponent(testEntity1, CollisionComponent)?.get(testEntity2)
-// const before2 = getComponent(testEntity2, CollisionComponent)?.get(testEntity1)
-// assert.equal(before1, undefined)
-// assert.equal(before2, undefined)
-
-// // Run and Check after
-// event(collider1.handle, collider2.handle, true)
-// const after1 = getComponent(testEntity1, CollisionComponent).get(testEntity2)
-// const after2 = getComponent(testEntity2, CollisionComponent).get(testEntity1)
-// assert.ok(after1)
-// assert.ok(after2)
-// })
-// })
-
-// describe('when `started` is set to `false` ...', () => {
-// it('... should create a CollisionEvents.TRIGGER_END when either one of the colliders is a sensor', async () => {
-// //force nested reactors to run
-// const { rerender, unmount } = render(<>>)
-
-// const Started = false
-
-// assert.ok(physicsWorld)
-// const event = Physics.drainCollisionEventQueue(physicsWorld)
-// assertCollisionEventClosure(event)
-// // Get the colliders from the API
-// const collider1 = physicsWorld.Colliders.get(testEntity1)
-// const collider2 = physicsWorld.Colliders.get(testEntity2)
-// assert.ok(collider1)
-// assert.ok(collider2)
-// // Check before
-// const before1 = getComponent(testEntity1, CollisionComponent)?.get(testEntity2)
-// const before2 = getComponent(testEntity2, CollisionComponent)?.get(testEntity1)
-// assert.equal(before1, undefined)
-// assert.equal(before2, undefined)
-// setComponent(testEntity1, TriggerComponent) // Set the trigger component (marks testEntity1.body.isSensor() as true)
-// await act(() => rerender(<>>))
-
-// // Run and Check after
-// event(collider1.handle, collider2.handle, true) // Run the even twice, so that the entities get each other in their collision components
-// event(collider1.handle, collider2.handle, Started)
-// const after1 = getComponent(testEntity1, CollisionComponent).get(testEntity2)
-// const after2 = getComponent(testEntity2, CollisionComponent).get(testEntity1)
-// assert.ok(after1)
-// assert.ok(after2)
-// assert.equal(after1.type, CollisionEvents.TRIGGER_END)
-// assert.equal(after2.type, CollisionEvents.TRIGGER_END)
-// })
-
-// it('... should create a CollisionEvents.COLLISION_END when neither of the colliders is a sensor', () => {
-// const Started = false
-
-// assert.ok(physicsWorld)
-// const event = Physics.drainCollisionEventQueue(physicsWorld)
-// assertCollisionEventClosure(event)
-// // Get the colliders from the API
-// const collider1 = physicsWorld.Colliders.get(testEntity1)
-// const collider2 = physicsWorld.Colliders.get(testEntity2)
-// assert.ok(collider1)
-// assert.ok(collider2)
-// // Check before
-// const before1 = getComponent(testEntity1, CollisionComponent)?.get(testEntity2)
-// const before2 = getComponent(testEntity2, CollisionComponent)?.get(testEntity1)
-// assert.equal(before1, undefined)
-// assert.equal(before2, undefined)
-// // setComponent(testEntity1, TriggerComponent) // DONT set the trigger component (testEntity1.body.isSensor() is false)
-
-// // Run and Check after
-// event(collider1.handle, collider2.handle, true) // Run the even twice, so that the entities get each other in their collision components
-// event(collider1.handle, collider2.handle, Started)
-// const after1 = getComponent(testEntity1, CollisionComponent).get(testEntity2)
-// const after2 = getComponent(testEntity2, CollisionComponent).get(testEntity1)
-// assert.ok(after1)
-// assert.ok(after2)
-// assert.equal(after1.type, CollisionEvents.COLLISION_END)
-// assert.equal(after2.type, CollisionEvents.COLLISION_END)
-// })
-// })
-// }) // << drainCollisionEventQueue
-
-// describe('drainContactEventQueue', () => {
-// let physicsWorld: PhysicsWorld
-// let testEntity1 = UndefinedEntity
-// let testEntity2 = UndefinedEntity
-
-// beforeEach(async () => {
-// createEngine()
-// await Physics.load()
-// const entity = createEntity()
-// setComponent(entity, UUIDComponent, UUIDComponent.generateUUID())
-// setComponent(entity, SceneComponent)
-// setComponent(entity, TransformComponent)
-// setComponent(entity, EntityTreeComponent)
-// physicsWorld = Physics.createWorld(getComponent(entity, UUIDComponent))
-// physicsWorld.timestep = 1 / 60
-
-// testEntity1 = createEntity()
-// setComponent(testEntity1, EntityTreeComponent, { parentEntity: entity })
-// setComponent(testEntity1, TransformComponent)
-// setComponent(testEntity1, RigidBodyComponent, { type: BodyTypes.Dynamic })
-// setComponent(testEntity1, ColliderComponent)
-// testEntity2 = createEntity()
-// setComponent(testEntity2, EntityTreeComponent, { parentEntity: entity })
-// setComponent(testEntity2, TransformComponent)
-// setComponent(testEntity2, RigidBodyComponent, { type: BodyTypes.Dynamic })
-// setComponent(testEntity2, ColliderComponent)
-// })
-
-// afterEach(() => {
-// removeEntity(testEntity1)
-// removeEntity(testEntity2)
-// return destroyEngine()
-// })
-
-// function assertContactEventClosure(closure: any) {
-// type ContactEventClosure = (handle1: number, handle2: number, started: boolean) => void
-// function hasContactEventClosureShape(closure: any): closure is ContactEventClosure {
-// return typeof closure === 'function' && closure.length === 1
-// }
-// assert.ok(closure)
-// assert.ok(hasContactEventClosureShape(closure))
-// }
-
-// it('should return a function with the correct shape (event: TempContactForceEvent) => void', () => {
-// assert.ok(physicsWorld)
-// const closure = Physics.drainContactEventQueue(physicsWorld)
-// assertContactEventClosure(closure)
-// })
-
-// describe('if the collision exists ...', () => {
-// const DummyMaxForce = { x: 42, y: 43, z: 44 }
-// const DummyTotalForce = { x: 45, y: 46, z: 47 }
-// const DummyHit = {
-// maxForceDirection: DummyMaxForce,
-// totalForce: DummyTotalForce
-// } as ColliderHitEvent
-// function setDummyCollisionBetween(ent1: Entity, ent2: Entity, hit = DummyHit): void {
-// const hits = new Map()
-// hits.set(ent2, hit)
-// setComponent(ent1, CollisionComponent)
-// getMutableComponent(ent1, CollisionComponent).set(hits)
-// }
-
-// const ExpectedMaxForce = { x: 4, y: 5, z: 6 }
-// const ExpectedTotalForce = { x: 7, y: 8, z: 9 }
-
-// it('should store event.maxForceDirection() into the CollisionComponent.maxForceDirection of entity1.collision.get(entity2) if the collision exists', () => {
-// // Setup the function spies
-// const collider1Spy = sinon.spy((): number => {
-// return physicsWorld.Colliders.get(testEntity1)!.handle
-// })
-// const collider2Spy = sinon.spy((): number => {
-// return physicsWorld.Colliders.get(testEntity2)!.handle
-// })
-// const totalForceSpy = sinon.spy((): Vector => {
-// return ExpectedTotalForce
-// })
-// const maxForceSpy = sinon.spy((): Vector => {
-// return ExpectedMaxForce
-// })
-
-// // Check before
-// assert.ok(physicsWorld)
-// const event = Physics.drainContactEventQueue(physicsWorld)
-// assertContactEventClosure(event)
-// assert.equal(getOptionalComponent(testEntity1, CollisionComponent), undefined)
-// assert.equal(getOptionalComponent(testEntity2, CollisionComponent), undefined)
-
-// // Run and Check after
-// setDummyCollisionBetween(testEntity1, testEntity2)
-// setDummyCollisionBetween(testEntity2, testEntity1)
-// event({
-// collider1: collider1Spy as any,
-// collider2: collider2Spy as any,
-// totalForce: totalForceSpy as any,
-// maxForceDirection: maxForceSpy as any
-// } as TempContactForceEvent)
-// sinon.assert.called(collider1Spy)
-// sinon.assert.called(collider2Spy)
-// sinon.assert.called(maxForceSpy)
-// const after = getComponent(testEntity1, CollisionComponent).get(testEntity2)?.maxForceDirection
-// assertVecApproxEq(after, ExpectedMaxForce, 3)
-// })
-
-// it('should store event.maxForceDirection() into the CollisionComponent.maxForceDirection of entity2.collision.get(entity1) if the collision exists', () => {
-// // Setup the function spies
-// const collider1Spy = sinon.spy((): number => {
-// return physicsWorld.Colliders.get(testEntity1)!.handle
-// })
-// const collider2Spy = sinon.spy((): number => {
-// return physicsWorld.Colliders.get(testEntity2)!.handle
-// })
-// const totalForceSpy = sinon.spy((): Vector => {
-// return ExpectedTotalForce
-// })
-// const maxForceSpy = sinon.spy((): Vector => {
-// return ExpectedMaxForce
-// })
-
-// // Check before
-// assert.ok(physicsWorld)
-// const event = Physics.drainContactEventQueue(physicsWorld)
-// assertContactEventClosure(event)
-// assert.equal(getOptionalComponent(testEntity1, CollisionComponent), undefined)
-// assert.equal(getOptionalComponent(testEntity2, CollisionComponent), undefined)
-
-// // Run and Check after
-// setDummyCollisionBetween(testEntity1, testEntity2)
-// setDummyCollisionBetween(testEntity2, testEntity1)
-
-// event({
-// collider1: collider1Spy as any,
-// collider2: collider2Spy as any,
-// totalForce: totalForceSpy as any,
-// maxForceDirection: maxForceSpy as any
-// } as TempContactForceEvent)
-
-// sinon.assert.called(collider1Spy)
-// sinon.assert.called(collider2Spy)
-// sinon.assert.called(maxForceSpy)
-// const after = getComponent(testEntity2, CollisionComponent).get(testEntity1)?.maxForceDirection
-// assertVecApproxEq(after, ExpectedMaxForce, 3)
-// })
-
-// it('should store event.totalForce() into the CollisionComponent.totalForce of entity1.collision.get(entity2) if the collision exists', () => {
-// // Setup the function spies
-// const collider1Spy = sinon.spy((): number => {
-// return physicsWorld.Colliders.get(testEntity1)!.handle
-// })
-// const collider2Spy = sinon.spy((): number => {
-// return physicsWorld.Colliders.get(testEntity2)!.handle
-// })
-// const totalForceSpy = sinon.spy((): Vector => {
-// return ExpectedTotalForce
-// })
-// const maxForceSpy = sinon.spy((): Vector => {
-// return ExpectedMaxForce
-// })
-
-// // Check before
-// assert.ok(physicsWorld)
-// const event = Physics.drainContactEventQueue(physicsWorld)
-// assertContactEventClosure(event)
-// assert.equal(getOptionalComponent(testEntity1, CollisionComponent), undefined)
-// assert.equal(getOptionalComponent(testEntity2, CollisionComponent), undefined)
-// // Run and Check after
-// setDummyCollisionBetween(testEntity1, testEntity2)
-// setDummyCollisionBetween(testEntity2, testEntity1)
-
-// event({
-// collider1: collider1Spy as any,
-// collider2: collider2Spy as any,
-// totalForce: totalForceSpy as any,
-// maxForceDirection: maxForceSpy as any
-// } as TempContactForceEvent)
-
-// sinon.assert.called(collider1Spy)
-// sinon.assert.called(collider2Spy)
-// sinon.assert.called(totalForceSpy)
-// const after = getComponent(testEntity1, CollisionComponent).get(testEntity2)?.totalForce
-// assertVecApproxEq(after, ExpectedTotalForce, 3)
-// })
-
-// it('should store event.totalForce() into the CollisionComponent.totalForce of entity2.collision.get(entity1) if the collision exists', () => {
-// // Setup the function spies
-// const collider1Spy = sinon.spy((): number => {
-// return physicsWorld.Colliders.get(testEntity1)!.handle
-// })
-// const collider2Spy = sinon.spy((): number => {
-// return physicsWorld.Colliders.get(testEntity2)!.handle
-// })
-// const totalForceSpy = sinon.spy((): Vector => {
-// return ExpectedTotalForce
-// })
-// const maxForceSpy = sinon.spy((): Vector => {
-// return ExpectedMaxForce
-// })
-
-// // Check before
-// assert.ok(physicsWorld)
-// const event = Physics.drainContactEventQueue(physicsWorld)
-// assertContactEventClosure(event)
-// assert.equal(getOptionalComponent(testEntity1, CollisionComponent), undefined)
-// assert.equal(getOptionalComponent(testEntity2, CollisionComponent), undefined)
-
-// // Run and Check after
-// setDummyCollisionBetween(testEntity1, testEntity2)
-// setDummyCollisionBetween(testEntity2, testEntity1)
-// event({
-// collider1: collider1Spy as any,
-// collider2: collider2Spy as any,
-// totalForce: totalForceSpy as any,
-// maxForceDirection: maxForceSpy as any
-// } as TempContactForceEvent)
-
-// sinon.assert.called(collider1Spy)
-// sinon.assert.called(collider2Spy)
-// sinon.assert.called(totalForceSpy)
-// const after = getComponent(testEntity2, CollisionComponent).get(testEntity1)?.totalForce
-// assertVecApproxEq(after, ExpectedTotalForce, 3)
-// })
-// })
-// }) // << drainContactEventQueue
-// }) // << Collisions
-// })
-
-// /** TODO:
-// describe("load", () => {}) // @todo Is there a way to check that the wasmInit() call from rapier.js has been run?
-// // Character Controller
-// describe("getControllerOffset", () => {}) // @deprecated
-// */
+/*
+CPAL-1.0 License
+
+The contents of this file are subject to the Common Public Attribution License
+Version 1.0. (the "License"); you may not use this file except in compliance
+with the License. You may obtain a copy of the License at
+https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE.
+The License is based on the Mozilla Public License Version 1.1, but Sections 14
+and 15 have been added to cover use of software over a computer network and
+provide for limited attribution for the Original Developer. In addition,
+Exhibit A has been modified to be consistent with Exhibit B.
+
+Software distributed under the License is distributed on an "AS IS" basis,
+WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the
+specific language governing rights and limitations under the License.
+
+The Original Code is Ethereal Engine.
+
+The Original Developer is the Initial Developer. The Initial Developer of the
+Original Code is the Ethereal Engine team.
+
+All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023
+Ethereal Engine. All Rights Reserved.
+*/
+import '../../..'
+
+import { RigidBodyType, ShapeType, TempContactForceEvent, Vector, World } from '@dimforge/rapier3d-compat'
+import assert from 'assert'
+import sinon from 'sinon'
+import { BoxGeometry, Mesh, Quaternion, Vector3 } from 'three'
+
+import {
+ getComponent,
+ getMutableComponent,
+ getOptionalComponent,
+ hasComponent,
+ removeComponent,
+ setComponent
+} from '@etherealengine/ecs/src/ComponentFunctions'
+import { destroyEngine } from '@etherealengine/ecs/src/Engine'
+import { createEntity } from '@etherealengine/ecs/src/EntityFunctions'
+import { getState } from '@etherealengine/hyperflux'
+
+import { createEngine } from '@etherealengine/ecs/src/Engine'
+import { ObjectDirection, Vector3_Zero } from '../../common/constants/MathConstants'
+import { TransformComponent } from '../../transform/components/TransformComponent'
+import { computeTransformMatrix } from '../../transform/systems/TransformSystem'
+import { ColliderComponent } from '../components/ColliderComponent'
+import { CollisionComponent } from '../components/CollisionComponent'
+import {
+ RigidBodyComponent,
+ RigidBodyFixedTagComponent,
+ getTagComponentForRigidBody
+} from '../components/RigidBodyComponent'
+import { TriggerComponent } from '../components/TriggerComponent'
+import { AllCollisionMask, CollisionGroups, DefaultCollisionMask } from '../enums/CollisionGroups'
+import { getInteractionGroups } from '../functions/getInteractionGroups'
+
+import {
+ Entity,
+ EntityUUID,
+ SystemDefinitions,
+ UUIDComponent,
+ UndefinedEntity,
+ removeEntity
+} from '@etherealengine/ecs'
+import { act, render } from '@testing-library/react'
+import React from 'react'
+import { MeshComponent } from '../../renderer/components/MeshComponent'
+import { SceneComponent } from '../../renderer/components/SceneComponents'
+import { EntityTreeComponent } from '../../transform/components/EntityTree'
+import { PhysicsSystem } from '../PhysicsModule'
+import {
+ BodyTypes,
+ ColliderDescOptions,
+ ColliderHitEvent,
+ CollisionEvents,
+ SceneQueryType,
+ Shapes
+} from '../types/PhysicsTypes'
+import { Physics, PhysicsWorld, RapierWorldState } from './Physics'
+
+const Rotation_Zero = { x: 0, y: 0, z: 0, w: 1 }
+
+const Epsilon = 0.001
+function floatApproxEq(A: number, B: number, epsilon = Epsilon): boolean {
+ return Math.abs(A - B) < epsilon
+}
+export function assertFloatApproxEq(A: number, B: number, epsilon = Epsilon) {
+ assert.ok(floatApproxEq(A, B, epsilon), `Numbers are not approximately equal: ${A} : ${B} : ${A - B}`)
+}
+
+export function assertFloatApproxNotEq(A: number, B: number, epsilon = Epsilon) {
+ assert.ok(!floatApproxEq(A, B, epsilon), `Numbers are approximately equal: ${A} : ${B} : ${A - B}`)
+}
+
+export function assertVecApproxEq(A, B, elems: number, epsilon = Epsilon) {
+ // @note Also used by RigidBodyComponent.test.ts
+ assertFloatApproxEq(A.x, B.x, epsilon)
+ assertFloatApproxEq(A.y, B.y, epsilon)
+ assertFloatApproxEq(A.z, B.z, epsilon)
+ if (elems > 3) assertFloatApproxEq(A.w, B.w, epsilon)
+}
+
+/**
+ * @description
+ * Triggers an assert if one or many of the (x,y,z,w) members of `@param A` is not equal to `@param B`.
+ * Does nothing for members that are equal */
+export function assertVecAnyApproxNotEq(A, B, elems: number, epsilon = Epsilon) {
+ // @note Also used by PhysicsSystem.test.ts
+ !floatApproxEq(A.x, B.x, epsilon) && assertFloatApproxNotEq(A.x, B.x, epsilon)
+ !floatApproxEq(A.y, B.y, epsilon) && assertFloatApproxNotEq(A.y, B.y, epsilon)
+ !floatApproxEq(A.z, B.z, epsilon) && assertFloatApproxNotEq(A.z, B.z, epsilon)
+ if (elems > 3) !floatApproxEq(A.w, B.w, epsilon) && assertFloatApproxEq(A.w, B.w, epsilon)
+}
+
+export function assertVecAllApproxNotEq(A, B, elems: number, epsilon = Epsilon) {
+ // @note Also used by RigidBodyComponent.test.ts
+ assertFloatApproxNotEq(A.x, B.x, epsilon)
+ assertFloatApproxNotEq(A.y, B.y, epsilon)
+ assertFloatApproxNotEq(A.z, B.z, epsilon)
+ if (elems > 3) assertFloatApproxNotEq(A.w, B.w, epsilon)
+}
+
+export const boxDynamicConfig = {
+ shapeType: ShapeType.Cuboid,
+ bodyType: RigidBodyType.Fixed,
+ collisionLayer: CollisionGroups.Default,
+ collisionMask: DefaultCollisionMask | CollisionGroups.Avatars | CollisionGroups.Ground,
+ friction: 1,
+ restitution: 0,
+ isTrigger: false,
+ spawnPosition: new Vector3(0, 0.25, 5),
+ spawnScale: new Vector3(0.5, 0.25, 0.5)
+} as ColliderDescOptions
+
+describe('Physics : External API', () => {
+ let physicsWorld: PhysicsWorld
+ let physicsWorldEntity: Entity
+
+ beforeEach(async () => {
+ createEngine()
+ await Physics.load()
+ physicsWorldEntity = createEntity()
+ setComponent(physicsWorldEntity, UUIDComponent, UUIDComponent.generateUUID())
+ setComponent(physicsWorldEntity, SceneComponent)
+ setComponent(physicsWorldEntity, TransformComponent)
+ setComponent(physicsWorldEntity, EntityTreeComponent)
+ physicsWorld = Physics.createWorld(getComponent(physicsWorldEntity, UUIDComponent))
+ physicsWorld.timestep = 1 / 60
+ })
+
+ afterEach(() => {
+ return destroyEngine()
+ })
+
+ it('should create & remove rigidBody', async () => {
+ const entity = createEntity()
+ setComponent(entity, TransformComponent)
+ setComponent(entity, EntityTreeComponent, { parentEntity: physicsWorldEntity })
+ setComponent(entity, RigidBodyComponent, { type: BodyTypes.Dynamic })
+ setComponent(entity, ColliderComponent, { shape: Shapes.Sphere })
+
+ assert.deepEqual(physicsWorld.bodies.len(), 1)
+ assert.deepEqual(physicsWorld.colliders.len(), 1)
+
+ removeComponent(entity, RigidBodyComponent)
+
+ assert.deepEqual(physicsWorld.bodies.len(), 0)
+ })
+
+ it('component type should match rigid body type', async () => {
+ const entity = createEntity()
+
+ setComponent(entity, TransformComponent)
+ setComponent(entity, EntityTreeComponent, { parentEntity: physicsWorldEntity })
+ setComponent(entity, RigidBodyComponent, { type: BodyTypes.Fixed })
+ setComponent(entity, ColliderComponent, { shape: Shapes.Sphere })
+
+ const rigidBodyComponent = getTagComponentForRigidBody(BodyTypes.Fixed)
+ assert.deepEqual(rigidBodyComponent, RigidBodyFixedTagComponent)
+ })
+
+ /**
+ // @todo External API test for `setRigidBodyType`
+ it("should change the entity's RigidBody type", async () => {})
+ */
+
+ it('should create accurate InteractionGroups', async () => {
+ const collisionGroup = 0x0001
+ const collisionMask = 0x0003
+ const interactionGroups = getInteractionGroups(collisionGroup, collisionMask)
+
+ assert.deepEqual(interactionGroups, 65539)
+ })
+
+ it('should generate a collision event', async () => {
+ const entity1 = createEntity()
+ const entity2 = createEntity()
+ setComponent(entity1, TransformComponent)
+ setComponent(entity1, EntityTreeComponent, { parentEntity: physicsWorldEntity })
+ setComponent(entity2, TransformComponent)
+ setComponent(entity2, EntityTreeComponent, { parentEntity: physicsWorldEntity })
+
+ setComponent(entity1, RigidBodyComponent, { type: BodyTypes.Dynamic })
+ setComponent(entity2, RigidBodyComponent, { type: BodyTypes.Dynamic })
+ setComponent(entity1, ColliderComponent, {
+ shape: Shapes.Sphere,
+ collisionLayer: CollisionGroups.Default,
+ collisionMask: DefaultCollisionMask
+ })
+ setComponent(entity2, ColliderComponent, {
+ shape: Shapes.Sphere,
+ collisionLayer: CollisionGroups.Default,
+ collisionMask: DefaultCollisionMask
+ })
+
+ const collisionEventQueue = Physics.createCollisionEventQueue()
+ const drainCollisions = Physics.drainCollisionEventQueue(physicsWorld)
+
+ physicsWorld.step(collisionEventQueue)
+ collisionEventQueue.drainCollisionEvents(drainCollisions)
+
+ const rigidBody1 = physicsWorld.Rigidbodies.get(entity1)!
+ const rigidBody2 = physicsWorld.Rigidbodies.get(entity2)!
+
+ assert.equal(getComponent(entity1, CollisionComponent).get(entity2)?.bodySelf, rigidBody1)
+ assert.equal(getComponent(entity1, CollisionComponent).get(entity2)?.bodyOther, rigidBody2)
+ assert.equal(getComponent(entity1, CollisionComponent).get(entity2)?.shapeSelf, rigidBody1.collider(0))
+ assert.equal(getComponent(entity1, CollisionComponent).get(entity2)?.shapeOther, rigidBody2.collider(0))
+ assert.equal(getComponent(entity1, CollisionComponent).get(entity2)?.type, CollisionEvents.COLLISION_START)
+
+ assert.equal(getComponent(entity2, CollisionComponent).get(entity1)?.bodySelf, rigidBody2)
+ assert.equal(getComponent(entity2, CollisionComponent).get(entity1)?.bodyOther, rigidBody1)
+ assert.equal(getComponent(entity2, CollisionComponent).get(entity1)?.shapeSelf, rigidBody2.collider(0))
+ assert.equal(getComponent(entity2, CollisionComponent).get(entity1)?.shapeOther, rigidBody1.collider(0))
+ assert.equal(getComponent(entity2, CollisionComponent).get(entity1)?.type, CollisionEvents.COLLISION_START)
+
+ rigidBody2.setTranslation({ x: 0, y: 0, z: 15 }, true)
+
+ physicsWorld.step(collisionEventQueue)
+ collisionEventQueue.drainCollisionEvents(drainCollisions)
+
+ assert.equal(getComponent(entity1, CollisionComponent).get(entity2)?.bodySelf, rigidBody1)
+ assert.equal(getComponent(entity1, CollisionComponent).get(entity2)?.bodyOther, rigidBody2)
+ assert.equal(getComponent(entity1, CollisionComponent).get(entity2)?.shapeSelf, rigidBody1.collider(0))
+ assert.equal(getComponent(entity1, CollisionComponent).get(entity2)?.shapeOther, rigidBody2.collider(0))
+ assert.equal(getComponent(entity1, CollisionComponent).get(entity2)?.type, CollisionEvents.COLLISION_END)
+
+ assert.equal(getComponent(entity2, CollisionComponent).get(entity1)?.bodySelf, rigidBody2)
+ assert.equal(getComponent(entity2, CollisionComponent).get(entity1)?.bodyOther, rigidBody1)
+ assert.equal(getComponent(entity2, CollisionComponent).get(entity1)?.shapeSelf, rigidBody2.collider(0))
+ assert.equal(getComponent(entity2, CollisionComponent).get(entity1)?.shapeOther, rigidBody1.collider(0))
+ assert.equal(getComponent(entity2, CollisionComponent).get(entity1)?.type, CollisionEvents.COLLISION_END)
+ })
+
+ it('should generate a trigger event', async () => {
+ //force nested reactors to run
+ const { rerender, unmount } = render(<>>)
+
+ const entity1 = createEntity()
+ const entity2 = createEntity()
+
+ setComponent(entity1, CollisionComponent)
+ setComponent(entity2, CollisionComponent)
+
+ setComponent(entity1, EntityTreeComponent, { parentEntity: physicsWorldEntity })
+ setComponent(entity1, TransformComponent)
+ setComponent(entity2, EntityTreeComponent, { parentEntity: physicsWorldEntity })
+ setComponent(entity2, TransformComponent)
+
+ setComponent(entity1, RigidBodyComponent, { type: BodyTypes.Dynamic })
+ setComponent(entity2, RigidBodyComponent, { type: BodyTypes.Dynamic })
+ setComponent(entity1, ColliderComponent, {
+ shape: Shapes.Sphere,
+ collisionLayer: CollisionGroups.Default,
+ collisionMask: AllCollisionMask
+ })
+ setComponent(entity2, ColliderComponent, {
+ shape: Shapes.Sphere,
+ collisionLayer: CollisionGroups.Default,
+ collisionMask: AllCollisionMask
+ })
+ setComponent(entity2, TriggerComponent)
+
+ await act(() => rerender(<>>))
+
+ const collisionEventQueue = Physics.createCollisionEventQueue()
+ const drainCollisions = Physics.drainCollisionEventQueue(physicsWorld)
+
+ physicsWorld.step(collisionEventQueue)
+ collisionEventQueue.drainCollisionEvents(drainCollisions)
+
+ const rigidBody1 = physicsWorld.Rigidbodies.get(entity1)!
+ const rigidBody2 = physicsWorld.Rigidbodies.get(entity2)!
+
+ assert.equal(getComponent(entity1, CollisionComponent).get(entity2)?.bodySelf, rigidBody1)
+ assert.equal(getComponent(entity1, CollisionComponent).get(entity2)?.bodyOther, rigidBody2)
+ assert.equal(getComponent(entity1, CollisionComponent).get(entity2)?.shapeSelf, rigidBody1.collider(0))
+ assert.equal(getComponent(entity1, CollisionComponent).get(entity2)?.shapeOther, rigidBody2.collider(0))
+ assert.equal(getComponent(entity1, CollisionComponent).get(entity2)?.type, CollisionEvents.TRIGGER_START)
+
+ assert.equal(getComponent(entity2, CollisionComponent).get(entity1)?.bodySelf, rigidBody2)
+ assert.equal(getComponent(entity2, CollisionComponent).get(entity1)?.bodyOther, rigidBody1)
+ assert.equal(getComponent(entity2, CollisionComponent).get(entity1)?.shapeSelf, rigidBody2.collider(0))
+ assert.equal(getComponent(entity2, CollisionComponent).get(entity1)?.shapeOther, rigidBody1.collider(0))
+ assert.equal(getComponent(entity2, CollisionComponent).get(entity1)?.type, CollisionEvents.TRIGGER_START)
+
+ rigidBody2.setTranslation({ x: 0, y: 0, z: 15 }, true)
+
+ physicsWorld.step(collisionEventQueue)
+ collisionEventQueue.drainCollisionEvents(drainCollisions)
+
+ assert.equal(getComponent(entity1, CollisionComponent).get(entity2)?.bodySelf, rigidBody1)
+ assert.equal(getComponent(entity1, CollisionComponent).get(entity2)?.bodyOther, rigidBody2)
+ assert.equal(getComponent(entity1, CollisionComponent).get(entity2)?.shapeSelf, rigidBody1.collider(0))
+ assert.equal(getComponent(entity1, CollisionComponent).get(entity2)?.shapeOther, rigidBody2.collider(0))
+ assert.equal(getComponent(entity1, CollisionComponent).get(entity2)?.type, CollisionEvents.TRIGGER_END)
+
+ assert.equal(getComponent(entity2, CollisionComponent).get(entity1)?.bodySelf, rigidBody2)
+ assert.equal(getComponent(entity2, CollisionComponent).get(entity1)?.bodyOther, rigidBody1)
+ assert.equal(getComponent(entity2, CollisionComponent).get(entity1)?.shapeSelf, rigidBody2.collider(0))
+ assert.equal(getComponent(entity2, CollisionComponent).get(entity1)?.shapeOther, rigidBody1.collider(0))
+ assert.equal(getComponent(entity2, CollisionComponent).get(entity1)?.type, CollisionEvents.TRIGGER_END)
+ })
+})
+
+describe('Physics : Rapier->ECS API', () => {
+ describe('createWorld', () => {
+ beforeEach(async () => {
+ createEngine()
+ await Physics.load()
+ })
+
+ afterEach(() => {
+ return destroyEngine()
+ })
+
+ it('should create a world object with the default gravity when not specified', () => {
+ const world = Physics.createWorld('world' as EntityUUID)
+ assert(getState(RapierWorldState)['world'])
+ assert.ok(world instanceof World, 'The create world has an incorrect type.')
+ const Expected = new Vector3(0.0, -9.81, 0.0)
+ assertVecApproxEq(world.gravity, Expected, 3)
+ Physics.destroyWorld('world' as EntityUUID)
+ assert(!getState(RapierWorldState)['world'])
+ })
+
+ it('should create a world object with a different gravity value when specified', () => {
+ const expected = { x: 0.0, y: -5.0, z: 0.0 }
+ const world = Physics.createWorld('world' as EntityUUID, { gravity: expected, substeps: 2 })
+ assertVecApproxEq(world.gravity, expected, 3)
+ assert.equal(world.substeps, 2)
+ })
+ })
+
+ describe('Rigidbodies', () => {
+ describe('createRigidBody', () => {
+ const position = new Vector3(1, 2, 3)
+ const rotation = new Quaternion(0.2, 0.3, 0.5, 0.0).normalize()
+
+ const scale = new Vector3(10, 10, 10)
+ let testEntity = UndefinedEntity
+ let physicsWorld: PhysicsWorld
+ let physicsWorldEntity: Entity
+
+ beforeEach(async () => {
+ createEngine()
+ await Physics.load()
+ physicsWorldEntity = createEntity()
+ setComponent(physicsWorldEntity, UUIDComponent, UUIDComponent.generateUUID())
+ physicsWorld = Physics.createWorld(getComponent(physicsWorldEntity, UUIDComponent))
+ setComponent(physicsWorldEntity, SceneComponent)
+ setComponent(physicsWorldEntity, TransformComponent)
+ setComponent(physicsWorldEntity, EntityTreeComponent)
+ physicsWorld!.timestep = 1 / 60
+
+ // Create the entity
+ testEntity = createEntity()
+ setComponent(testEntity, EntityTreeComponent, { parentEntity: physicsWorldEntity })
+ setComponent(testEntity, TransformComponent, { position: position, scale: scale, rotation: rotation })
+ setComponent(testEntity, RigidBodyComponent, { type: BodyTypes.Dynamic, canSleep: true, gravityScale: 0 })
+ RigidBodyComponent.reactorMap.get(testEntity)!.stop()
+ })
+
+ afterEach(() => {
+ removeEntity(testEntity)
+ return destroyEngine()
+ })
+
+ it('should create a rigidBody successfully', () => {
+ Physics.createRigidBody(physicsWorld, testEntity)
+ const body = physicsWorld.Rigidbodies.get(testEntity)
+ assert.ok(body)
+ })
+
+ it("shouldn't mark the entity transform as dirty", () => {
+ Physics.createRigidBody(physicsWorld, testEntity)
+ assert.ok(TransformComponent.dirtyTransforms[testEntity] == false)
+ })
+
+ it('should assign the correct RigidBodyType enum', () => {
+ Physics.createRigidBody(physicsWorld, testEntity)
+ const body = physicsWorld.Rigidbodies.get(testEntity)!
+ assert.equal(body.bodyType(), RigidBodyType.Dynamic)
+ })
+
+ it("should assign the entity's position to the rigidBody.translation property", () => {
+ Physics.createRigidBody(physicsWorld, testEntity)
+ const body = physicsWorld.Rigidbodies.get(testEntity)!
+ assertVecApproxEq(body.translation(), position, 3)
+ })
+
+ it("should assign the entity's rotation to the rigidBody.rotation property", () => {
+ Physics.createRigidBody(physicsWorld, testEntity)
+ const body = physicsWorld.Rigidbodies.get(testEntity)!
+ assertVecApproxEq(body!.rotation(), rotation, 4)
+ })
+
+ it('should create a body with no Linear Velocity', () => {
+ Physics.createRigidBody(physicsWorld, testEntity)
+ const body = physicsWorld.Rigidbodies.get(testEntity)!
+ assertVecApproxEq(body.linvel(), Vector3_Zero, 3)
+ })
+
+ it('should create a body with no Angular Velocity', () => {
+ Physics.createRigidBody(physicsWorld, testEntity)
+ const body = physicsWorld.Rigidbodies.get(testEntity)!
+ assertVecApproxEq(body.angvel(), Vector3_Zero, 3)
+ })
+
+ it("should store the entity in the body's userData property", () => {
+ Physics.createRigidBody(physicsWorld, testEntity)
+ const body = physicsWorld.Rigidbodies.get(testEntity)!
+ assert.deepEqual(body.userData, { entity: testEntity })
+ })
+ })
+
+ describe('removeRigidbody', () => {
+ let testEntity = UndefinedEntity
+ let physicsWorld: PhysicsWorld
+
+ beforeEach(async () => {
+ createEngine()
+ await Physics.load()
+ const entity = createEntity()
+ setComponent(entity, UUIDComponent, UUIDComponent.generateUUID())
+ physicsWorld = Physics.createWorld(getComponent(entity, UUIDComponent))
+ setComponent(entity, SceneComponent)
+ setComponent(entity, TransformComponent)
+ setComponent(entity, EntityTreeComponent)
+ physicsWorld!.timestep = 1 / 60
+
+ // Create the entity
+ testEntity = createEntity()
+ setComponent(testEntity, EntityTreeComponent, { parentEntity: entity })
+ setComponent(testEntity, TransformComponent)
+ setComponent(testEntity, RigidBodyComponent, { type: BodyTypes.Dynamic })
+ RigidBodyComponent.reactorMap.get(testEntity)!.stop()
+ Physics.createRigidBody(physicsWorld, testEntity)
+ })
+
+ afterEach(() => {
+ removeEntity(testEntity)
+ return destroyEngine()
+ })
+
+ it('should successfully remove the body from the RigidBodies map', () => {
+ let body = physicsWorld.Rigidbodies.get(testEntity)
+ assert.ok(body)
+ Physics.removeRigidbody(physicsWorld, testEntity)
+ body = physicsWorld.Rigidbodies.get(testEntity)
+ assert.equal(body, undefined)
+ })
+ })
+
+ describe('isSleeping', () => {
+ let testEntity = UndefinedEntity
+ let physicsWorld: PhysicsWorld
+
+ beforeEach(async () => {
+ createEngine()
+ await Physics.load()
+ const entity = createEntity()
+ setComponent(entity, UUIDComponent, UUIDComponent.generateUUID())
+ setComponent(entity, SceneComponent)
+ setComponent(entity, TransformComponent)
+ setComponent(entity, EntityTreeComponent)
+ physicsWorld = Physics.createWorld(getComponent(entity, UUIDComponent))
+ physicsWorld!.timestep = 1 / 60
+
+ // Create the entity
+ testEntity = createEntity()
+ setComponent(testEntity, EntityTreeComponent, { parentEntity: entity })
+ setComponent(testEntity, TransformComponent)
+ setComponent(testEntity, RigidBodyComponent, { type: BodyTypes.Dynamic })
+ RigidBodyComponent.reactorMap.get(testEntity)!.stop()
+ Physics.createRigidBody(physicsWorld, testEntity)
+ })
+
+ afterEach(() => {
+ removeEntity(testEntity)
+ return destroyEngine()
+ })
+
+ it('should return the correct values', () => {
+ const noBodyEntity = createEntity()
+ assert.equal(
+ Physics.isSleeping(physicsWorld, noBodyEntity),
+ true,
+ 'Returns true when the entity does not have a RigidBody'
+ )
+ assert.equal(
+ Physics.isSleeping(physicsWorld, testEntity),
+ false,
+ "Returns false when the entity is first created and physics haven't been simulated yet"
+ )
+ })
+ })
+
+ describe('setRigidBodyType', () => {
+ let testEntity = UndefinedEntity
+ let physicsWorld: PhysicsWorld
+
+ beforeEach(async () => {
+ createEngine()
+ await Physics.load()
+ const entity = createEntity()
+ setComponent(entity, UUIDComponent, UUIDComponent.generateUUID())
+ setComponent(entity, SceneComponent)
+ setComponent(entity, TransformComponent)
+ setComponent(entity, EntityTreeComponent)
+ physicsWorld = Physics.createWorld(getComponent(entity, UUIDComponent))
+ physicsWorld!.timestep = 1 / 60
+
+ // Create the entity
+ testEntity = createEntity()
+ setComponent(testEntity, EntityTreeComponent, { parentEntity: entity })
+ setComponent(testEntity, TransformComponent)
+ setComponent(testEntity, RigidBodyComponent, { type: BodyTypes.Dynamic })
+ RigidBodyComponent.reactorMap.get(testEntity)!.stop()
+ Physics.createRigidBody(physicsWorld, testEntity)
+ })
+
+ afterEach(() => {
+ removeEntity(testEntity)
+ return destroyEngine()
+ })
+
+ it("should assign the correct RigidBodyType to the entity's body", () => {
+ let body = physicsWorld.Rigidbodies.get(testEntity)!
+ assert.equal(body.bodyType(), RigidBodyType.Dynamic)
+ // Check change to fixed
+ Physics.setRigidBodyType(physicsWorld, testEntity, BodyTypes.Fixed)
+ body = physicsWorld.Rigidbodies.get(testEntity)!
+ assert.notEqual(body.bodyType(), RigidBodyType.Dynamic, "The RigidBody's type was not changed")
+ assert.equal(body.bodyType(), RigidBodyType.Fixed, "The RigidBody's type was not changed to Fixed")
+ // Check change to dynamic
+ Physics.setRigidBodyType(physicsWorld, testEntity, BodyTypes.Dynamic)
+ body = physicsWorld.Rigidbodies.get(testEntity)!
+ assert.notEqual(body.bodyType(), RigidBodyType.Fixed, "The RigidBody's type was not changed")
+ assert.equal(body.bodyType(), RigidBodyType.Dynamic, "The RigidBody's type was not changed to Dynamic")
+ // Check change to kinematic
+ Physics.setRigidBodyType(physicsWorld, testEntity, BodyTypes.Kinematic)
+ body = physicsWorld.Rigidbodies.get(testEntity)!
+ assert.notEqual(body.bodyType(), RigidBodyType.Dynamic, "The RigidBody's type was not changed")
+ assert.equal(
+ body.bodyType(),
+ RigidBodyType.KinematicPositionBased,
+ "The RigidBody's type was not changed to KinematicPositionBased"
+ )
+ })
+ })
+
+ describe('setRigidbodyPose', () => {
+ const position = new Vector3(1, 2, 3)
+ const rotation = new Quaternion(0.1, 0.3, 0.7, 0.0).normalize()
+ const linVel = new Vector3(7, 8, 9)
+ const angVel = new Vector3(0, 1, 2)
+ let testEntity = UndefinedEntity
+ let physicsWorld: PhysicsWorld
+
+ beforeEach(async () => {
+ createEngine()
+ await Physics.load()
+ const entity = createEntity()
+ setComponent(entity, UUIDComponent, UUIDComponent.generateUUID())
+ setComponent(entity, SceneComponent)
+ setComponent(entity, TransformComponent)
+ setComponent(entity, EntityTreeComponent)
+ physicsWorld = Physics.createWorld(getComponent(entity, UUIDComponent))
+ physicsWorld!.timestep = 1 / 60
+
+ // Create the entity
+ testEntity = createEntity()
+ setComponent(testEntity, EntityTreeComponent, { parentEntity: entity })
+ setComponent(testEntity, TransformComponent)
+ setComponent(testEntity, RigidBodyComponent, { type: BodyTypes.Dynamic })
+ RigidBodyComponent.reactorMap.get(testEntity)!.stop()
+ Physics.createRigidBody(physicsWorld, testEntity)
+ })
+
+ afterEach(() => {
+ removeEntity(testEntity)
+ return destroyEngine()
+ })
+
+ it("should set the body's Translation to the given Position", () => {
+ Physics.setRigidbodyPose(physicsWorld, testEntity, position, rotation, linVel, angVel)
+ const body = physicsWorld.Rigidbodies.get(testEntity)!
+ assertVecApproxEq(body.translation(), position, 3)
+ })
+
+ it("should set the body's Rotation to the given value", () => {
+ Physics.setRigidbodyPose(physicsWorld, testEntity, position, rotation, linVel, angVel)
+ const body = physicsWorld.Rigidbodies.get(testEntity)!
+ assertVecApproxEq(body.rotation(), rotation, 4)
+ })
+
+ it("should set the body's Linear Velocity to the given value", () => {
+ Physics.setRigidbodyPose(physicsWorld, testEntity, position, rotation, linVel, angVel)
+ const body = physicsWorld.Rigidbodies.get(testEntity)!
+ assertVecApproxEq(body.linvel(), linVel, 3)
+ })
+
+ it("should set the body's Angular Velocity to the given value", () => {
+ Physics.setRigidbodyPose(physicsWorld, testEntity, position, rotation, linVel, angVel)
+ const body = physicsWorld.Rigidbodies.get(testEntity)!
+ assertVecApproxEq(body.angvel(), angVel, 3)
+ })
+ })
+
+ describe('enabledCcd', () => {
+ let testEntity = UndefinedEntity
+ let physicsWorld: PhysicsWorld
+
+ beforeEach(async () => {
+ createEngine()
+ await Physics.load()
+ const entity = createEntity()
+ setComponent(entity, UUIDComponent, UUIDComponent.generateUUID())
+ setComponent(entity, SceneComponent)
+ setComponent(entity, TransformComponent)
+ setComponent(entity, EntityTreeComponent)
+ physicsWorld = Physics.createWorld(getComponent(entity, UUIDComponent))
+ physicsWorld!.timestep = 1 / 60
+
+ // Create the entity
+ testEntity = createEntity()
+ setComponent(testEntity, EntityTreeComponent, { parentEntity: entity })
+ setComponent(testEntity, TransformComponent)
+ setComponent(testEntity, RigidBodyComponent, { type: BodyTypes.Dynamic })
+ RigidBodyComponent.reactorMap.get(testEntity)!.stop()
+ Physics.createRigidBody(physicsWorld, testEntity)
+ })
+
+ afterEach(() => {
+ removeEntity(testEntity)
+ return destroyEngine()
+ })
+
+ it('should enable Continuous Collision Detection on the entity', () => {
+ const body = physicsWorld.Rigidbodies.get(testEntity)!
+ assert.equal(body.isCcdEnabled(), false)
+ Physics.enabledCcd(physicsWorld, testEntity, true)
+ assert.equal(body.isCcdEnabled(), true)
+ })
+
+ it('should disable CCD on the entity when passing `false` to the `enabled` property', () => {
+ const body = physicsWorld.Rigidbodies.get(testEntity)!
+ Physics.enabledCcd(physicsWorld, testEntity, true)
+ assert.equal(body.isCcdEnabled(), true)
+ Physics.enabledCcd(physicsWorld, testEntity, false)
+ assert.equal(body.isCcdEnabled(), false)
+ })
+ })
+
+ describe('applyImpulse', () => {
+ let testEntity = UndefinedEntity
+ let physicsWorld: PhysicsWorld
+
+ beforeEach(async () => {
+ createEngine()
+ await Physics.load()
+ const entity = createEntity()
+ setComponent(entity, UUIDComponent, UUIDComponent.generateUUID())
+ setComponent(entity, SceneComponent)
+ setComponent(entity, TransformComponent)
+ setComponent(entity, EntityTreeComponent)
+ physicsWorld = Physics.createWorld(getComponent(entity, UUIDComponent))
+ physicsWorld!.timestep = 1 / 60
+
+ // Create the entity
+ testEntity = createEntity()
+ setComponent(testEntity, EntityTreeComponent, { parentEntity: entity })
+ setComponent(testEntity, TransformComponent)
+ setComponent(testEntity, RigidBodyComponent, { type: BodyTypes.Dynamic })
+ setComponent(testEntity, ColliderComponent)
+ })
+
+ afterEach(() => {
+ removeEntity(testEntity)
+ return destroyEngine()
+ })
+
+ const physicsSystemExecute = SystemDefinitions.get(PhysicsSystem)!.execute
+
+ it('should apply the impulse to the RigidBody of the entity', () => {
+ const testImpulse = new Vector3(1, 2, 3)
+ const beforeBody = physicsWorld.Rigidbodies.get(testEntity)
+ assert.ok(beforeBody)
+ const before = beforeBody.linvel()
+ assertVecApproxEq(before, Vector3_Zero, 3)
+ Physics.applyImpulse(physicsWorld, testEntity, testImpulse)
+ physicsSystemExecute()
+ const afterBody = physicsWorld.Rigidbodies.get(testEntity)
+ assert.ok(afterBody)
+ const after = afterBody.linvel()
+ assertVecAllApproxNotEq(after, before, 3)
+ })
+ })
+
+ describe('lockRotations', () => {
+ let testEntity = UndefinedEntity
+ let physicsWorld: PhysicsWorld
+
+ beforeEach(async () => {
+ createEngine()
+ await Physics.load()
+ const entity = createEntity()
+ setComponent(entity, UUIDComponent, UUIDComponent.generateUUID())
+ setComponent(entity, SceneComponent)
+ setComponent(entity, TransformComponent)
+ setComponent(entity, EntityTreeComponent)
+ physicsWorld = Physics.createWorld(getComponent(entity, UUIDComponent))
+ physicsWorld!.timestep = 1 / 60
+
+ // Create the entity
+ testEntity = createEntity()
+ setComponent(testEntity, EntityTreeComponent, { parentEntity: entity })
+ setComponent(testEntity, TransformComponent)
+ setComponent(testEntity, RigidBodyComponent, { type: BodyTypes.Dynamic })
+ setComponent(testEntity, ColliderComponent)
+ })
+
+ afterEach(() => {
+ removeEntity(testEntity)
+ return destroyEngine()
+ })
+
+ it('should lock rotations on the entity', () => {
+ const impulse = new Vector3(1, 2, 3)
+ const body = physicsWorld.Rigidbodies.get(testEntity)!
+ const before = { x: body.angvel().x, y: body.angvel().y, z: body.angvel().z }
+ assertVecApproxEq(before, Vector3_Zero, 3)
+
+ body.applyTorqueImpulse(impulse, false)
+ const dummy = { x: body.angvel().x, y: body.angvel().y, z: body.angvel().z }
+ assertVecAllApproxNotEq(before, dummy, 3)
+
+ Physics.lockRotations(physicsWorld, testEntity, true)
+ body.applyTorqueImpulse(impulse, false)
+ const after = { x: body.angvel().x, y: body.angvel().y, z: body.angvel().z }
+ assertVecApproxEq(dummy, after, 3)
+ })
+
+ /**
+ // @todo Fix this test when we update to Rapier >= v0.12
+ it('should disable locked rotations on the entity', () => {
+ const ExpectedValue = new Quaternion(0.5, 0.3, 0.2, 0.0).normalize()
+ const body = physicsWorld.Rigidbodies.get(testEntity)!
+ assert.notDeepEqual(body.rotation(), ExpectedValue)
+
+ Physics.lockRotations(testEntity, true)
+ body.setRotation(ExpectedValue, false)
+ console.log(JSON.stringify(body.rotation()), "BEFORE")
+ console.log(JSON.stringify(ExpectedValue), "Expected")
+ assertVecAllApproxNotEq(body.rotation(), ExpectedValue, 3)
+ // assert.notDeepEqual(body.rotation(), ExpectedValue)
+
+ Physics.lockRotations(testEntity, true)
+ console.log(JSON.stringify(body.rotation()), "AFTEr")
+ assertVecApproxEq(body.rotation(), ExpectedValue, 4)
+ })
+ */
+ })
+
+ describe('setEnabledRotations', () => {
+ let testEntity = UndefinedEntity
+ let physicsWorld: PhysicsWorld
+
+ beforeEach(async () => {
+ createEngine()
+ await Physics.load()
+ const entity = createEntity()
+ setComponent(entity, UUIDComponent, UUIDComponent.generateUUID())
+ setComponent(entity, SceneComponent)
+ setComponent(entity, TransformComponent)
+ setComponent(entity, EntityTreeComponent)
+ physicsWorld = Physics.createWorld(getComponent(entity, UUIDComponent))
+ physicsWorld!.timestep = 1 / 60
+
+ // Create the entity
+ testEntity = createEntity()
+ setComponent(testEntity, EntityTreeComponent, { parentEntity: entity })
+ setComponent(testEntity, TransformComponent)
+ setComponent(testEntity, RigidBodyComponent, { type: BodyTypes.Dynamic })
+ setComponent(testEntity, ColliderComponent)
+ })
+
+ afterEach(() => {
+ removeEntity(testEntity)
+ return destroyEngine()
+ })
+
+ it('should disable rotations on the X axis for the rigidBody of the entity', () => {
+ const testImpulse = new Vector3(1, 2, 3)
+ const enabledRotation = [false, true, true] as [boolean, boolean, boolean]
+ const body = physicsWorld.Rigidbodies.get(testEntity)!
+ const before = body.angvel()
+ assertVecApproxEq(before, Vector3_Zero, 3)
+ Physics.setEnabledRotations(physicsWorld, testEntity, enabledRotation)
+ body.applyTorqueImpulse(testImpulse, false)
+ physicsWorld!.step()
+ const after = body.angvel()
+ assertFloatApproxEq(after.x, before.x)
+ assertFloatApproxNotEq(after.y, before.y)
+ assertFloatApproxNotEq(after.z, before.z)
+ })
+
+ it('should disable rotations on the Y axis for the rigidBody of the entity', () => {
+ const testImpulse = new Vector3(1, 2, 3)
+ const enabledRotation = [true, false, true] as [boolean, boolean, boolean]
+ const body = physicsWorld.Rigidbodies.get(testEntity)!
+ const before = body.angvel()
+ assertVecApproxEq(before, Vector3_Zero, 3)
+ Physics.setEnabledRotations(physicsWorld, testEntity, enabledRotation)
+ body.applyTorqueImpulse(testImpulse, false)
+ physicsWorld!.step()
+ const after = body.angvel()
+ assertFloatApproxNotEq(after.x, before.x)
+ assertFloatApproxEq(after.y, before.y)
+ assertFloatApproxNotEq(after.z, before.z)
+ })
+
+ it('should disable rotations on the Z axis for the rigidBody of the entity', () => {
+ const testImpulse = new Vector3(1, 2, 3)
+ const enabledRotation = [true, true, false] as [boolean, boolean, boolean]
+ const body = physicsWorld.Rigidbodies.get(testEntity)!
+ const before = body.angvel()
+ assertVecApproxEq(before, Vector3_Zero, 3)
+ Physics.setEnabledRotations(physicsWorld, testEntity, enabledRotation)
+ body.applyTorqueImpulse(testImpulse, false)
+ physicsWorld!.step()
+ const after = body.angvel()
+ assertFloatApproxNotEq(after.x, before.x)
+ assertFloatApproxNotEq(after.y, before.y)
+ assertFloatApproxEq(after.z, before.z)
+ })
+ })
+
+ describe('updatePreviousRigidbodyPose', () => {
+ let testEntity = UndefinedEntity
+ let physicsWorld: PhysicsWorld
+
+ beforeEach(async () => {
+ createEngine()
+ await Physics.load()
+ const entity = createEntity()
+ setComponent(entity, UUIDComponent, UUIDComponent.generateUUID())
+ setComponent(entity, SceneComponent)
+ setComponent(entity, TransformComponent)
+ setComponent(entity, EntityTreeComponent)
+ physicsWorld = Physics.createWorld(getComponent(entity, UUIDComponent))
+ physicsWorld!.timestep = 1 / 60
+
+ // Create the entity
+ testEntity = createEntity()
+ setComponent(testEntity, EntityTreeComponent, { parentEntity: entity })
+ setComponent(testEntity, TransformComponent)
+ setComponent(testEntity, RigidBodyComponent, { type: BodyTypes.Dynamic })
+ setComponent(testEntity, ColliderComponent)
+ })
+
+ afterEach(() => {
+ removeEntity(testEntity)
+ return destroyEngine()
+ })
+
+ it("should set the previous position of the entity's RigidBodyComponent", () => {
+ const Expected = new Vector3(1, 2, 3)
+ const body = physicsWorld.Rigidbodies.get(testEntity)!
+ body.setTranslation(Expected, false)
+ const before = {
+ x: RigidBodyComponent.previousPosition.x[testEntity],
+ y: RigidBodyComponent.previousPosition.y[testEntity],
+ z: RigidBodyComponent.previousPosition.z[testEntity]
+ }
+ Physics.updatePreviousRigidbodyPose([testEntity])
+ const after = {
+ x: RigidBodyComponent.previousPosition.x[testEntity],
+ y: RigidBodyComponent.previousPosition.y[testEntity],
+ z: RigidBodyComponent.previousPosition.z[testEntity]
+ }
+ assertVecAllApproxNotEq(before, after, 3)
+ })
+
+ it("should set the previous rotation of the entity's RigidBodyComponent", () => {
+ const Expected = new Quaternion(0.5, 0.3, 0.2, 0.0).normalize()
+ const body = physicsWorld.Rigidbodies.get(testEntity)!
+ body.setRotation(Expected, false)
+ const before = {
+ x: RigidBodyComponent.previousRotation.x[testEntity],
+ y: RigidBodyComponent.previousRotation.y[testEntity],
+ z: RigidBodyComponent.previousRotation.z[testEntity],
+ w: RigidBodyComponent.previousRotation.w[testEntity]
+ }
+ Physics.updatePreviousRigidbodyPose([testEntity])
+ const after = {
+ x: RigidBodyComponent.previousRotation.x[testEntity],
+ y: RigidBodyComponent.previousRotation.y[testEntity],
+ z: RigidBodyComponent.previousRotation.z[testEntity],
+ w: RigidBodyComponent.previousRotation.w[testEntity]
+ }
+ assertVecAllApproxNotEq(before, after, 4)
+ })
+ })
+
+ describe('updateRigidbodyPose', () => {
+ let testEntity = UndefinedEntity
+ let physicsWorld: PhysicsWorld
+
+ beforeEach(async () => {
+ createEngine()
+ await Physics.load()
+ const entity = createEntity()
+ setComponent(entity, UUIDComponent, UUIDComponent.generateUUID())
+ setComponent(entity, SceneComponent)
+ setComponent(entity, TransformComponent)
+ setComponent(entity, EntityTreeComponent)
+ physicsWorld = Physics.createWorld(getComponent(entity, UUIDComponent))
+ physicsWorld!.timestep = 1 / 60
+
+ // Create the entity
+ testEntity = createEntity()
+ setComponent(testEntity, EntityTreeComponent, { parentEntity: entity })
+ setComponent(testEntity, TransformComponent)
+ setComponent(testEntity, RigidBodyComponent, { type: BodyTypes.Dynamic })
+ setComponent(testEntity, ColliderComponent)
+ })
+
+ afterEach(() => {
+ removeEntity(testEntity)
+ return destroyEngine()
+ })
+
+ it("should set the position of the entity's RigidBodyComponent", () => {
+ const position = new Vector3(1, 2, 3)
+ const body = physicsWorld.Rigidbodies.get(testEntity)!
+ body.setTranslation(position, false)
+ const before = {
+ x: RigidBodyComponent.position.x[testEntity],
+ y: RigidBodyComponent.position.y[testEntity],
+ z: RigidBodyComponent.position.z[testEntity]
+ }
+ Physics.updateRigidbodyPose([testEntity])
+ const after = {
+ x: RigidBodyComponent.position.x[testEntity],
+ y: RigidBodyComponent.position.y[testEntity],
+ z: RigidBodyComponent.position.z[testEntity]
+ }
+ assertVecAllApproxNotEq(before, after, 3)
+ })
+
+ it("should set the rotation of the entity's RigidBodyComponent", () => {
+ const rotation = new Quaternion(0.5, 0.3, 0.2, 0.0).normalize()
+ const body = physicsWorld.Rigidbodies.get(testEntity)!
+ body.setRotation(rotation, false)
+ const before = {
+ x: RigidBodyComponent.rotation.x[testEntity],
+ y: RigidBodyComponent.rotation.y[testEntity],
+ z: RigidBodyComponent.rotation.z[testEntity],
+ w: RigidBodyComponent.rotation.w[testEntity]
+ }
+ Physics.updateRigidbodyPose([testEntity])
+ const after = {
+ x: RigidBodyComponent.rotation.x[testEntity],
+ y: RigidBodyComponent.rotation.y[testEntity],
+ z: RigidBodyComponent.rotation.z[testEntity],
+ w: RigidBodyComponent.rotation.w[testEntity]
+ }
+ assertVecAllApproxNotEq(before, after, 4)
+ })
+
+ it("should set the linearVelocity of the entity's RigidBodyComponent", () => {
+ const impulse = new Vector3(1, 2, 3)
+ const body = physicsWorld.Rigidbodies.get(testEntity)!
+ body.applyImpulse(impulse, false)
+ const before = {
+ x: RigidBodyComponent.linearVelocity.x[testEntity],
+ y: RigidBodyComponent.linearVelocity.y[testEntity],
+ z: RigidBodyComponent.linearVelocity.z[testEntity]
+ }
+ Physics.updateRigidbodyPose([testEntity])
+ const after = {
+ x: RigidBodyComponent.linearVelocity.x[testEntity],
+ y: RigidBodyComponent.linearVelocity.y[testEntity],
+ z: RigidBodyComponent.linearVelocity.z[testEntity]
+ }
+ assertVecAllApproxNotEq(before, after, 3)
+ })
+
+ it("should set the angularVelocity of the entity's RigidBodyComponent", () => {
+ const impulse = new Vector3(1, 2, 3)
+ const body = physicsWorld.Rigidbodies.get(testEntity)!
+ body.applyTorqueImpulse(impulse, false)
+ const before = {
+ x: RigidBodyComponent.angularVelocity.x[testEntity],
+ y: RigidBodyComponent.angularVelocity.y[testEntity],
+ z: RigidBodyComponent.angularVelocity.z[testEntity]
+ }
+ Physics.updateRigidbodyPose([testEntity])
+ const after = {
+ x: RigidBodyComponent.angularVelocity.x[testEntity],
+ y: RigidBodyComponent.angularVelocity.y[testEntity],
+ z: RigidBodyComponent.angularVelocity.z[testEntity]
+ }
+ assertVecAllApproxNotEq(before, after, 3)
+ })
+ })
+
+ describe('setKinematicRigidbodyPose', () => {
+ const position = new Vector3(1, 2, 3)
+ const rotation = new Quaternion(0.5, 0.3, 0.2, 0.0).normalize()
+ let testEntity = UndefinedEntity
+ let physicsWorld: PhysicsWorld
+
+ beforeEach(async () => {
+ createEngine()
+ await Physics.load()
+ const entity = createEntity()
+ setComponent(entity, UUIDComponent, UUIDComponent.generateUUID())
+ setComponent(entity, SceneComponent)
+ setComponent(entity, TransformComponent)
+ setComponent(entity, EntityTreeComponent)
+ physicsWorld = Physics.createWorld(getComponent(entity, UUIDComponent))
+ physicsWorld!.timestep = 1 / 60
+
+ // Create the entity
+ testEntity = createEntity()
+ setComponent(testEntity, EntityTreeComponent, { parentEntity: entity })
+ setComponent(testEntity, TransformComponent)
+ setComponent(testEntity, RigidBodyComponent, { type: BodyTypes.Kinematic })
+ setComponent(testEntity, ColliderComponent)
+ })
+
+ afterEach(() => {
+ removeEntity(testEntity)
+ return destroyEngine()
+ })
+
+ it("should set the nextTranslation property of the entity's Kinematic RigidBody", () => {
+ const body = physicsWorld.Rigidbodies.get(testEntity)!
+ const before = body.nextTranslation()
+ Physics.setKinematicRigidbodyPose(physicsWorld, testEntity, position, rotation)
+ const after = body.nextTranslation()
+ assertVecAllApproxNotEq(before, after, 3)
+ })
+
+ it("should set the nextRotation property of the entity's Kinematic RigidBody", () => {
+ const body = physicsWorld.Rigidbodies.get(testEntity)!
+ const before = body.nextRotation()
+ Physics.setKinematicRigidbodyPose(physicsWorld, testEntity, position, rotation)
+ const after = body.nextRotation()
+ assertVecAllApproxNotEq(before, after, 4)
+ })
+ })
+ }) // << Rigidbodies
+
+ describe('Colliders', () => {
+ describe('setTrigger', () => {
+ let testEntity = UndefinedEntity
+ let physicsWorld: PhysicsWorld
+
+ beforeEach(async () => {
+ createEngine()
+ await Physics.load()
+ const entity = createEntity()
+ setComponent(entity, UUIDComponent, UUIDComponent.generateUUID())
+ setComponent(entity, SceneComponent)
+ setComponent(entity, TransformComponent)
+ setComponent(entity, EntityTreeComponent)
+ physicsWorld = Physics.createWorld(getComponent(entity, UUIDComponent))
+ physicsWorld!.timestep = 1 / 60
+
+ // Create the entity
+ testEntity = createEntity()
+ setComponent(testEntity, EntityTreeComponent, { parentEntity: entity })
+ setComponent(testEntity, TransformComponent)
+ setComponent(testEntity, RigidBodyComponent, { type: BodyTypes.Dynamic })
+ setComponent(testEntity, ColliderComponent, { shape: Shapes.Sphere })
+ })
+
+ afterEach(() => {
+ removeEntity(testEntity)
+ return destroyEngine()
+ })
+
+ it('should mark the collider of the entity as a sensor', () => {
+ const collider = physicsWorld.Colliders.get(testEntity)!
+ Physics.setTrigger(physicsWorld, testEntity, true)
+ assert.ok(collider.isSensor())
+ })
+
+ it('should add CollisionGroup.trigger to the interaction groups of the collider when `isTrigger` is passed as true', () => {
+ const collider = physicsWorld.Colliders.get(testEntity)!
+ Physics.setTrigger(physicsWorld, testEntity, true)
+ const triggerInteraction = getInteractionGroups(CollisionGroups.Trigger, 0) // Shift the Trigger bits into the interaction bits, so they don't match with the mask
+ const hasTriggerInteraction = Boolean(collider.collisionGroups() & triggerInteraction) // If interactionGroups contains the triggerInteraction bits
+ assert.ok(hasTriggerInteraction)
+ })
+
+ it('should not add CollisionGroup.trigger to the interaction groups of the collider when `isTrigger` is passed as false', () => {
+ const collider = physicsWorld.Colliders.get(testEntity)!
+ Physics.setTrigger(physicsWorld, testEntity, false)
+ const triggerInteraction = getInteractionGroups(CollisionGroups.Trigger, 0) // Shift the Trigger bits into the interaction bits, so they don't match with the mask
+ const notTriggerInteraction = !(collider.collisionGroups() & triggerInteraction) // If interactionGroups does not contain the triggerInteraction bits
+ assert.ok(notTriggerInteraction)
+ })
+ }) // << setTrigger
+
+ describe('setCollisionLayer', () => {
+ let testEntity = UndefinedEntity
+ let physicsWorld: PhysicsWorld
+
+ beforeEach(async () => {
+ createEngine()
+ await Physics.load()
+ const entity = createEntity()
+ setComponent(entity, UUIDComponent, UUIDComponent.generateUUID())
+ setComponent(entity, SceneComponent)
+ setComponent(entity, TransformComponent)
+ setComponent(entity, EntityTreeComponent)
+ physicsWorld = Physics.createWorld(getComponent(entity, UUIDComponent))
+ physicsWorld!.timestep = 1 / 60
+
+ // Create the entity
+ testEntity = createEntity()
+ setComponent(testEntity, EntityTreeComponent, { parentEntity: entity })
+ setComponent(testEntity, TransformComponent)
+ setComponent(testEntity, RigidBodyComponent, { type: BodyTypes.Dynamic })
+ setComponent(testEntity, ColliderComponent, { shape: Shapes.Sphere })
+ })
+
+ afterEach(() => {
+ removeEntity(testEntity)
+ return destroyEngine()
+ })
+
+ it('should set the collider interaction groups to the given value', () => {
+ const data = getComponent(testEntity, ColliderComponent)
+ const ExpectedLayer = CollisionGroups.Avatars | data.collisionLayer
+ const Expected = getInteractionGroups(ExpectedLayer, data.collisionMask)
+ const before = physicsWorld.Colliders.get(testEntity)!.collisionGroups()
+ Physics.setCollisionLayer(physicsWorld, testEntity, ExpectedLayer)
+ const after = physicsWorld.Colliders.get(testEntity)!.collisionGroups()
+ assert.notEqual(before, Expected)
+ assert.equal(after, Expected)
+ })
+
+ it('should not modify the collision mask of the collider', () => {
+ const data = getComponent(testEntity, ColliderComponent)
+ const newLayer = CollisionGroups.Avatars
+ const Expected = getInteractionGroups(newLayer, data.collisionMask)
+ Physics.setCollisionLayer(physicsWorld, testEntity, newLayer)
+ const after = physicsWorld.Colliders.get(testEntity)!.collisionGroups()
+ assert.equal(after, Expected)
+ })
+
+ it('should not add CollisionGroups.Trigger to the collider interaction groups if the entity does not have a TriggerComponent', () => {
+ Physics.setCollisionLayer(physicsWorld, testEntity, CollisionGroups.Avatars)
+ const after = physicsWorld.Colliders.get(testEntity)!.collisionGroups()
+ const noTriggerBit = !(after & getInteractionGroups(CollisionGroups.Trigger, 0)) // not collisionLayer contains Trigger
+ assert.ok(noTriggerBit)
+ })
+
+ it('should not modify the CollisionGroups.Trigger bit in the collider interaction groups if the entity has a TriggerComponent', () => {
+ const triggerLayer = getInteractionGroups(CollisionGroups.Trigger, 0) // Create the triggerLayer groups bitmask
+ setComponent(testEntity, TriggerComponent)
+ const beforeGroups = physicsWorld.Colliders.get(testEntity)!.collisionGroups()
+ const before = getInteractionGroups(beforeGroups & triggerLayer, 0) === triggerLayer // beforeGroups.collisionLayer contains Trigger
+ Physics.setCollisionLayer(physicsWorld, testEntity, CollisionGroups.Avatars)
+ const afterGroups = physicsWorld.Colliders.get(testEntity)!.collisionGroups()
+ const after = getInteractionGroups(afterGroups & triggerLayer, 0) === triggerLayer // afterGroups.collisionLayer contains Trigger
+ assert.equal(before, after)
+ })
+ }) // setCollisionLayer
+
+ describe('setCollisionMask', () => {
+ let testEntity = UndefinedEntity
+ let physicsWorld: PhysicsWorld
+
+ beforeEach(async () => {
+ createEngine()
+ await Physics.load()
+ const entity = createEntity()
+ setComponent(entity, UUIDComponent, UUIDComponent.generateUUID())
+ setComponent(entity, SceneComponent)
+ setComponent(entity, TransformComponent)
+ setComponent(entity, EntityTreeComponent)
+ physicsWorld = Physics.createWorld(getComponent(entity, UUIDComponent))
+ physicsWorld!.timestep = 1 / 60
+
+ // Create the entity
+ testEntity = createEntity()
+ setComponent(testEntity, EntityTreeComponent, { parentEntity: entity })
+ setComponent(testEntity, TransformComponent)
+ setComponent(testEntity, RigidBodyComponent, { type: BodyTypes.Dynamic })
+ setComponent(testEntity, ColliderComponent, { shape: Shapes.Sphere })
+ })
+
+ afterEach(() => {
+ removeEntity(testEntity)
+ return destroyEngine()
+ })
+
+ it('should set the collider mask to the given value', () => {
+ const before = getComponent(testEntity, ColliderComponent)
+ const Expected = CollisionGroups.Avatars | before.collisionMask
+ Physics.setCollisionMask(physicsWorld, testEntity, Expected)
+ const after = getComponent(testEntity, ColliderComponent)
+ assert.equal(after.collisionMask, Expected)
+ })
+
+ it('should not modify the collision layer of the collider', () => {
+ const before = getComponent(testEntity, ColliderComponent)
+ Physics.setCollisionMask(physicsWorld, testEntity, CollisionGroups.Avatars)
+ const after = getComponent(testEntity, ColliderComponent)
+ assert.equal(before.collisionLayer, after.collisionLayer)
+ })
+
+ it('should not add CollisionGroups.Trigger to the collider mask if the entity does not have a TriggerComponent', () => {
+ Physics.setCollisionMask(physicsWorld, testEntity, CollisionGroups.Avatars)
+ const after = getComponent(testEntity, ColliderComponent)
+ const noTriggerBit = !(after.collisionMask & CollisionGroups.Trigger) // not collisionMask contains Trigger
+ assert.ok(noTriggerBit)
+ })
+
+ it('should not modify the CollisionGroups.Trigger bit in the collider mask if the entity has a TriggerComponent', () => {
+ setComponent(testEntity, TriggerComponent)
+ const beforeData = getComponent(testEntity, ColliderComponent)
+ const before = beforeData.collisionMask & CollisionGroups.Trigger // collisionMask contains Trigger
+ Physics.setCollisionMask(physicsWorld, testEntity, CollisionGroups.Avatars)
+
+ const afterData = getComponent(testEntity, ColliderComponent)
+ const after = afterData.collisionMask & CollisionGroups.Trigger // collisionMask contains Trigger
+ assert.equal(before, after)
+ })
+ }) // setCollisionMask
+
+ describe('setFriction', () => {
+ let testEntity = UndefinedEntity
+ let physicsWorld: PhysicsWorld
+
+ beforeEach(async () => {
+ createEngine()
+ await Physics.load()
+ const entity = createEntity()
+ setComponent(entity, UUIDComponent, UUIDComponent.generateUUID())
+ setComponent(entity, SceneComponent)
+ setComponent(entity, TransformComponent)
+ setComponent(entity, EntityTreeComponent)
+ physicsWorld = Physics.createWorld(getComponent(entity, UUIDComponent))
+ physicsWorld!.timestep = 1 / 60
+
+ // Create the entity
+ testEntity = createEntity()
+ setComponent(testEntity, EntityTreeComponent, { parentEntity: entity })
+ setComponent(testEntity, TransformComponent)
+ setComponent(testEntity, RigidBodyComponent, { type: BodyTypes.Dynamic })
+ setComponent(testEntity, ColliderComponent, { shape: Shapes.Sphere })
+ })
+
+ afterEach(() => {
+ removeEntity(testEntity)
+ return destroyEngine()
+ })
+
+ it('should set the friction value on the entity', () => {
+ const ExpectedValue = 42
+ const collider = physicsWorld.Colliders.get(testEntity)!
+ assert.notEqual(collider.friction(), ExpectedValue)
+ Physics.setFriction(physicsWorld, testEntity, ExpectedValue)
+ assert.equal(collider.friction(), ExpectedValue)
+ })
+ }) // << setFriction
+
+ describe('setRestitution', () => {
+ let testEntity = UndefinedEntity
+ let physicsWorld: PhysicsWorld
+
+ beforeEach(async () => {
+ createEngine()
+ await Physics.load()
+ const entity = createEntity()
+ setComponent(entity, UUIDComponent, UUIDComponent.generateUUID())
+ setComponent(entity, SceneComponent)
+ setComponent(entity, TransformComponent)
+ setComponent(entity, EntityTreeComponent)
+ physicsWorld = Physics.createWorld(getComponent(entity, UUIDComponent))
+ physicsWorld!.timestep = 1 / 60
+
+ // Create the entity
+ testEntity = createEntity()
+ setComponent(testEntity, EntityTreeComponent, { parentEntity: entity })
+ setComponent(testEntity, TransformComponent)
+ setComponent(testEntity, RigidBodyComponent, { type: BodyTypes.Dynamic })
+ setComponent(testEntity, ColliderComponent, { shape: Shapes.Sphere })
+ })
+
+ afterEach(() => {
+ removeEntity(testEntity)
+ return destroyEngine()
+ })
+
+ it('should set the restitution value on the entity', () => {
+ const ExpectedValue = 42
+ const collider = physicsWorld.Colliders.get(testEntity)!
+ assert.notEqual(collider.restitution(), ExpectedValue)
+ Physics.setRestitution(physicsWorld, testEntity, ExpectedValue)
+ assert.equal(collider.restitution(), ExpectedValue)
+ })
+ }) // << setRestitution
+
+ describe('setMass', () => {
+ let testEntity = UndefinedEntity
+ let physicsWorld: PhysicsWorld
+
+ beforeEach(async () => {
+ createEngine()
+ await Physics.load()
+ const entity = createEntity()
+ setComponent(entity, UUIDComponent, UUIDComponent.generateUUID())
+ setComponent(entity, SceneComponent)
+ setComponent(entity, TransformComponent)
+ setComponent(entity, EntityTreeComponent)
+ physicsWorld = Physics.createWorld(getComponent(entity, UUIDComponent))
+ physicsWorld!.timestep = 1 / 60
+
+ // Create the entity
+ testEntity = createEntity()
+ setComponent(testEntity, EntityTreeComponent, { parentEntity: entity })
+ setComponent(testEntity, TransformComponent)
+ setComponent(testEntity, RigidBodyComponent, { type: BodyTypes.Dynamic })
+ setComponent(testEntity, ColliderComponent, { shape: Shapes.Sphere })
+ })
+
+ afterEach(() => {
+ removeEntity(testEntity)
+ return destroyEngine()
+ })
+
+ it('should set the mass value on the entity', () => {
+ const ExpectedValue = 42
+ const collider = physicsWorld.Colliders.get(testEntity)!
+ assert.notEqual(collider.mass(), ExpectedValue)
+ Physics.setMass(physicsWorld, testEntity, ExpectedValue)
+ assert.equal(collider.mass(), ExpectedValue)
+ })
+ }) // << setMass
+
+ describe('getShape', () => {
+ let testEntity = UndefinedEntity
+ let physicsWorld: PhysicsWorld
+
+ beforeEach(async () => {
+ createEngine()
+ await Physics.load()
+ const entity = createEntity()
+ setComponent(entity, UUIDComponent, UUIDComponent.generateUUID())
+ setComponent(entity, SceneComponent)
+ setComponent(entity, TransformComponent)
+ setComponent(entity, EntityTreeComponent)
+ physicsWorld = Physics.createWorld(getComponent(entity, UUIDComponent))
+ physicsWorld!.timestep = 1 / 60
+
+ // Create the entity
+ testEntity = createEntity()
+ setComponent(testEntity, EntityTreeComponent, { parentEntity: entity })
+ setComponent(testEntity, TransformComponent)
+ setComponent(testEntity, RigidBodyComponent, { type: BodyTypes.Dynamic })
+ })
+
+ afterEach(() => {
+ removeEntity(testEntity)
+ return destroyEngine()
+ })
+
+ it('should return a sphere shape', () => {
+ setComponent(testEntity, ColliderComponent, { shape: Shapes.Sphere })
+ Physics.createRigidBody(physicsWorld, testEntity)
+ assert.equal(Physics.getShape(physicsWorld, testEntity), Shapes.Sphere)
+ })
+
+ it('should return a capsule shape', () => {
+ setComponent(testEntity, ColliderComponent, { shape: Shapes.Capsule })
+ Physics.createRigidBody(physicsWorld, testEntity)
+ assert.equal(Physics.getShape(physicsWorld, testEntity), Shapes.Capsule)
+ })
+
+ it('should return a cylinder shape', () => {
+ setComponent(testEntity, ColliderComponent, { shape: Shapes.Cylinder })
+ Physics.createRigidBody(physicsWorld, testEntity)
+ assert.equal(Physics.getShape(physicsWorld, testEntity), Shapes.Cylinder)
+ })
+
+ it('should return a box shape', () => {
+ setComponent(testEntity, ColliderComponent, { shape: Shapes.Box })
+ Physics.createRigidBody(physicsWorld, testEntity)
+ assert.equal(Physics.getShape(physicsWorld, testEntity), Shapes.Box)
+ })
+
+ it('should return a plane shape', () => {
+ setComponent(testEntity, ColliderComponent, { shape: Shapes.Plane })
+ Physics.createRigidBody(physicsWorld, testEntity)
+ assert.equal(Physics.getShape(physicsWorld, testEntity), Shapes.Box) // The Shapes.Plane case is implemented as a box in the engine
+ })
+
+ it('should return undefined for the convex_hull case', () => {
+ setComponent(testEntity, ColliderComponent, { shape: Shapes.ConvexHull })
+ Physics.createRigidBody(physicsWorld, testEntity)
+ assert.equal(Physics.getShape(physicsWorld, testEntity), undefined /** @todo Shapes.ConvexHull */)
+ })
+
+ it('should return undefined for the mesh case', () => {
+ setComponent(testEntity, ColliderComponent, { shape: Shapes.Mesh })
+ Physics.createRigidBody(physicsWorld, testEntity)
+ assert.equal(Physics.getShape(physicsWorld, testEntity), undefined /** @todo Shapes.Mesh */)
+ })
+
+ /**
+ // @todo Heightfield is not supported yet. Triggers an Error exception
+ it("should return undefined for the heightfield case", () => {
+ setComponent(testEntity, ColliderComponent, { shape: Shapes.Heightfield })
+ Physics.createRigidBody(physicsWorld, testEntity)
+ assert.equal(Physics.getShape(physicsWorld, testEntity), Shapes.Heightfield)
+ })
+ */
+ }) // << getShape
+
+ describe('removeCollider', () => {
+ let testEntity = UndefinedEntity
+ let physicsWorld: PhysicsWorld
+
+ beforeEach(async () => {
+ createEngine()
+ await Physics.load()
+ const entity = createEntity()
+ setComponent(entity, UUIDComponent, UUIDComponent.generateUUID())
+ setComponent(entity, SceneComponent)
+ setComponent(entity, TransformComponent)
+ setComponent(entity, EntityTreeComponent)
+ physicsWorld = Physics.createWorld(getComponent(entity, UUIDComponent))
+ physicsWorld!.timestep = 1 / 60
+
+ // Create the entity
+ testEntity = createEntity()
+ setComponent(testEntity, EntityTreeComponent, { parentEntity: entity })
+ setComponent(testEntity, TransformComponent)
+ setComponent(testEntity, RigidBodyComponent, { type: BodyTypes.Dynamic })
+ setComponent(testEntity, ColliderComponent, { shape: Shapes.Box })
+ })
+
+ afterEach(() => {
+ removeEntity(testEntity)
+ return destroyEngine()
+ })
+
+ it("should remove the entity's collider", () => {
+ const before = physicsWorld.Colliders.get(testEntity)
+ assert.notEqual(before, undefined)
+ Physics.removeCollider(physicsWorld!, testEntity)
+ const after = physicsWorld.Colliders.get(testEntity)
+ assert.equal(after, undefined)
+ })
+ }) // << removeCollider
+
+ describe('removeCollidersFromRigidBody', () => {
+ let testEntity = UndefinedEntity
+ let physicsWorld: PhysicsWorld
+
+ beforeEach(async () => {
+ createEngine()
+ await Physics.load()
+ const entity = createEntity()
+ setComponent(entity, UUIDComponent, UUIDComponent.generateUUID())
+ setComponent(entity, SceneComponent)
+ setComponent(entity, TransformComponent)
+ setComponent(entity, EntityTreeComponent)
+ physicsWorld = Physics.createWorld(getComponent(entity, UUIDComponent))
+ physicsWorld!.timestep = 1 / 60
+
+ // Create the entity
+ testEntity = createEntity()
+ setComponent(testEntity, EntityTreeComponent, { parentEntity: entity })
+ setComponent(testEntity, TransformComponent)
+ setComponent(testEntity, RigidBodyComponent, { type: BodyTypes.Dynamic })
+ setComponent(testEntity, ColliderComponent)
+ })
+
+ afterEach(() => {
+ removeEntity(testEntity)
+ return destroyEngine()
+ })
+
+ it('should remove all Colliders from the RigidBody when called', () => {
+ const before = physicsWorld.Rigidbodies.get(testEntity)!
+ assert.notEqual(before.numColliders(), 0)
+ Physics.removeCollidersFromRigidBody(testEntity, physicsWorld!)
+ assert.equal(before.numColliders(), 0)
+ })
+ }) // << removeCollidersFromRigidBody
+
+ describe('createColliderDesc', () => {
+ const Default = {
+ // Default values returned by `createColliderDesc` when the default values of the components are not changed
+ enabled: true,
+ shape: { type: 1, halfExtents: { x: 0.5, y: 0.5, z: 0.5 } },
+ massPropsMode: 0,
+ density: 1,
+ friction: 0.5,
+ restitution: 0.5,
+ rotation: { x: 0, y: 0, z: 0, w: 1 },
+ translation: { x: 0, y: 0, z: 0 },
+ isSensor: false,
+ collisionGroups: 65543,
+ solverGroups: 4294967295,
+ frictionCombineRule: 0,
+ restitutionCombineRule: 0,
+ activeCollisionTypes: 60943,
+ activeEvents: 1,
+ activeHooks: 0,
+ mass: 0,
+ centerOfMass: { x: 0, y: 0, z: 0 },
+ contactForceEventThreshold: 0,
+ principalAngularInertia: { x: 0, y: 0, z: 0 },
+ angularInertiaLocalFrame: { x: 0, y: 0, z: 0, w: 1 }
+ }
+
+ let physicsWorld: PhysicsWorld
+ let testEntity = UndefinedEntity
+ let rootEntity = UndefinedEntity
+
+ beforeEach(async () => {
+ createEngine()
+ await Physics.load()
+ const entity = createEntity()
+ setComponent(entity, UUIDComponent, UUIDComponent.generateUUID())
+ setComponent(entity, SceneComponent)
+ setComponent(entity, TransformComponent)
+ setComponent(entity, EntityTreeComponent)
+ physicsWorld = Physics.createWorld(getComponent(entity, UUIDComponent))
+ physicsWorld!.timestep = 1 / 60
+
+ // Create the entity
+ testEntity = createEntity()
+ setComponent(testEntity, EntityTreeComponent, { parentEntity: rootEntity })
+ setComponent(testEntity, TransformComponent)
+ setComponent(testEntity, RigidBodyComponent)
+ setComponent(testEntity, ColliderComponent)
+ rootEntity = createEntity()
+ setComponent(rootEntity, EntityTreeComponent, { parentEntity: entity })
+ setComponent(rootEntity, TransformComponent)
+ setComponent(rootEntity, RigidBodyComponent)
+ setComponent(rootEntity, ColliderComponent)
+ })
+
+ afterEach(() => {
+ removeEntity(testEntity)
+ removeEntity(rootEntity)
+ return destroyEngine()
+ })
+
+ it('should return early if the given `rootEntity` does not have a RigidBody', () => {
+ removeComponent(rootEntity, RigidBodyComponent)
+ const result = Physics.createColliderDesc(physicsWorld, testEntity, rootEntity)
+ assert.equal(result, undefined)
+ })
+
+ it('should return a descriptor with the expected default values', () => {
+ const result = Physics.createColliderDesc(physicsWorld, testEntity, rootEntity)
+ assert.deepEqual(result, Default)
+ })
+
+ it('should set the friction to the same value as the ColliderComponent', () => {
+ const result = Physics.createColliderDesc(physicsWorld, testEntity, rootEntity)
+ assert.equal(result.friction, getComponent(testEntity, ColliderComponent).friction)
+ })
+
+ it('should set the restitution to the same value as the ColliderComponent', () => {
+ const result = Physics.createColliderDesc(physicsWorld, testEntity, rootEntity)
+ assert.equal(result.restitution, getComponent(testEntity, ColliderComponent).restitution)
+ })
+
+ it('should set the collisionGroups to the same value as the ColliderComponent layer and mask', () => {
+ const result = Physics.createColliderDesc(physicsWorld, testEntity, rootEntity)
+ const data = getComponent(testEntity, ColliderComponent)
+ assert.equal(result.collisionGroups, getInteractionGroups(data.collisionLayer, data.collisionMask))
+ })
+
+ it('should set the sensor property according to whether the entity has a TriggerComponent or not', () => {
+ const noTriggerDesc = Physics.createColliderDesc(physicsWorld, testEntity, rootEntity)
+ assert.equal(noTriggerDesc.isSensor, hasComponent(testEntity, TriggerComponent))
+ setComponent(testEntity, TriggerComponent)
+ const triggerDesc = Physics.createColliderDesc(physicsWorld, testEntity, rootEntity)
+ assert.equal(triggerDesc.isSensor, hasComponent(testEntity, TriggerComponent))
+ })
+
+ it('should set the shape to a Ball when the ColliderComponent shape is a Sphere', () => {
+ setComponent(testEntity, ColliderComponent, { shape: Shapes.Sphere })
+ const result = Physics.createColliderDesc(physicsWorld, testEntity, rootEntity)
+ assert.equal(result.shape.type, ShapeType.Ball)
+ })
+
+ it('should set the shape to a Cuboid when the ColliderComponent shape is a Box', () => {
+ setComponent(testEntity, ColliderComponent, { shape: Shapes.Box })
+ const result = Physics.createColliderDesc(physicsWorld, testEntity, rootEntity)
+ assert.equal(result.shape.type, ShapeType.Cuboid)
+ })
+
+ it('should set the shape to a Cuboid when the ColliderComponent shape is a Plane', () => {
+ setComponent(testEntity, ColliderComponent, { shape: Shapes.Plane })
+ const result = Physics.createColliderDesc(physicsWorld, testEntity, rootEntity)
+ assert.equal(result.shape.type, ShapeType.Cuboid)
+ })
+
+ it('should set the shape to a TriMesh when the ColliderComponent shape is a Mesh', () => {
+ setComponent(testEntity, MeshComponent, new Mesh(new BoxGeometry()))
+ setComponent(testEntity, ColliderComponent, { shape: Shapes.Mesh })
+ const result = Physics.createColliderDesc(physicsWorld, testEntity, rootEntity)
+ assert.equal(result.shape.type, ShapeType.TriMesh)
+ })
+
+ it('should set the shape to a ConvexPolyhedron when the ColliderComponent shape is a ConvexHull', () => {
+ setComponent(testEntity, MeshComponent, new Mesh(new BoxGeometry()))
+ setComponent(testEntity, ColliderComponent, { shape: Shapes.ConvexHull })
+ const result = Physics.createColliderDesc(physicsWorld, testEntity, rootEntity)
+ assert.equal(result.shape.type, ShapeType.ConvexPolyhedron)
+ })
+
+ it('should set the shape to a Cylinder when the ColliderComponent shape is a Cylinder', () => {
+ setComponent(testEntity, ColliderComponent, { shape: Shapes.Cylinder })
+ const result = Physics.createColliderDesc(physicsWorld, testEntity, rootEntity)
+ assert.equal(result.shape.type, ShapeType.Cylinder)
+ })
+
+ it('should set the position relative to the parent entity', () => {
+ const Expected = new Vector3(1, 2, 3)
+ const result = Physics.createColliderDesc(physicsWorld, testEntity, rootEntity)
+ console.log(JSON.stringify(result))
+ console.log(JSON.stringify(result.translation))
+ assertVecApproxEq(result.translation, Vector3_Zero, 3)
+ })
+
+ it('should set the rotation relative to the parent entity', () => {
+ const Expected = new Quaternion(0.5, 0.3, 0.2, 0.0).normalize()
+ const result = Physics.createColliderDesc(physicsWorld, testEntity, rootEntity)
+ console.log(JSON.stringify(result.rotation))
+ assertVecApproxEq(result.rotation, Rotation_Zero, 4)
+ })
+ })
+
+ describe('attachCollider', () => {
+ let testEntity = UndefinedEntity
+ let rigidbodyEntity = UndefinedEntity
+ let physicsWorld: PhysicsWorld
+
+ beforeEach(async () => {
+ createEngine()
+ await Physics.load()
+ const entity = createEntity()
+ setComponent(entity, UUIDComponent, UUIDComponent.generateUUID())
+ setComponent(entity, SceneComponent)
+ setComponent(entity, TransformComponent)
+ setComponent(entity, EntityTreeComponent)
+ physicsWorld = Physics.createWorld(getComponent(entity, UUIDComponent))
+ physicsWorld!.timestep = 1 / 60
+
+ // Create the entity
+ testEntity = createEntity()
+ setComponent(testEntity, EntityTreeComponent, { parentEntity: entity })
+ setComponent(testEntity, TransformComponent)
+ setComponent(testEntity, RigidBodyComponent, { type: BodyTypes.Dynamic })
+ setComponent(testEntity, ColliderComponent, { shape: Shapes.Box })
+ rigidbodyEntity = createEntity()
+ setComponent(rigidbodyEntity, EntityTreeComponent, { parentEntity: entity })
+ setComponent(rigidbodyEntity, TransformComponent)
+ setComponent(rigidbodyEntity, RigidBodyComponent, { type: BodyTypes.Dynamic })
+ setComponent(rigidbodyEntity, ColliderComponent, { shape: Shapes.Box })
+ })
+
+ afterEach(() => {
+ removeEntity(testEntity)
+ removeEntity(rigidbodyEntity)
+ return destroyEngine()
+ })
+
+ it("should return undefined when rigidBodyEntity doesn't have a RigidBodyComponent", () => {
+ removeComponent(rigidbodyEntity, RigidBodyComponent)
+ const colliderDesc = Physics.createColliderDesc(physicsWorld, testEntity, rigidbodyEntity)
+ const result = Physics.attachCollider(physicsWorld!, colliderDesc, rigidbodyEntity, testEntity)
+ assert.equal(result, undefined)
+ })
+
+ it('should add the collider to the physicsWorld.Colliders map', () => {
+ ColliderComponent.reactorMap.get(testEntity)!.stop()
+ const colliderDesc = Physics.createColliderDesc(physicsWorld, testEntity, rigidbodyEntity)
+ const result = Physics.attachCollider(physicsWorld!, colliderDesc, rigidbodyEntity, testEntity)!
+ const expected = physicsWorld.Colliders.get(testEntity)
+ assert.ok(result)
+ assert.ok(expected)
+ assert.deepEqual(result.handle, expected.handle)
+ })
+ })
+
+ describe('setColliderPose', () => {
+ let testEntity = UndefinedEntity
+ let physicsWorld: PhysicsWorld
+ const position = new Vector3(1, 2, 3)
+ const rotation = new Quaternion(0.5, 0.4, 0.1, 0.0).normalize()
+
+ beforeEach(async () => {
+ createEngine()
+ await Physics.load()
+ const entity = createEntity()
+ setComponent(entity, UUIDComponent, UUIDComponent.generateUUID())
+ setComponent(entity, SceneComponent)
+ setComponent(entity, TransformComponent)
+ setComponent(entity, EntityTreeComponent)
+ physicsWorld = Physics.createWorld(getComponent(entity, UUIDComponent))
+ physicsWorld!.timestep = 1 / 60
+
+ // Create the entity
+ testEntity = createEntity()
+ setComponent(testEntity, EntityTreeComponent, { parentEntity: entity })
+ setComponent(testEntity, TransformComponent)
+ setComponent(testEntity, RigidBodyComponent, { type: BodyTypes.Dynamic })
+ setComponent(testEntity, ColliderComponent, { shape: Shapes.Box })
+ })
+
+ afterEach(() => {
+ removeEntity(testEntity)
+ return destroyEngine()
+ })
+
+ it("should assign the entity's position to the collider.translation property", () => {
+ Physics.setColliderPose(physicsWorld, testEntity, position, rotation)
+ const collider = physicsWorld.Colliders.get(testEntity)!
+ // need to step to update the collider's position
+ physicsWorld.step()
+ assertVecApproxEq(collider.translation(), position, 3, 0.01)
+ })
+
+ it("should assign the entity's rotation to the collider.rotation property", () => {
+ Physics.setColliderPose(physicsWorld, testEntity, position, rotation)
+ const collider = physicsWorld.Colliders.get(testEntity)!
+ // need to step to update the collider's position
+ physicsWorld.step()
+ assertVecApproxEq(collider.rotation(), rotation, 4)
+ })
+ })
+
+ describe('setMassCenter', () => {}) /** @todo The function is not implemented. It is annotated with a todo tag */
+ }) // << Colliders
+
+ describe('CharacterControllers', () => {
+ describe('createCharacterController', () => {
+ const Default = {
+ offset: 0.01,
+ maxSlopeClimbAngle: (60 * Math.PI) / 180,
+ minSlopeSlideAngle: (30 * Math.PI) / 180,
+ autoStep: { maxHeight: 0.5, minWidth: 0.01, stepOverDynamic: true },
+ enableSnapToGround: 0.1 as number | false
+ }
+
+ let testEntity = UndefinedEntity
+ let physicsWorld: PhysicsWorld
+
+ beforeEach(async () => {
+ createEngine()
+ await Physics.load()
+ const entity = createEntity()
+ setComponent(entity, UUIDComponent, UUIDComponent.generateUUID())
+ setComponent(entity, SceneComponent)
+ setComponent(entity, TransformComponent)
+ setComponent(entity, EntityTreeComponent)
+ physicsWorld = Physics.createWorld(getComponent(entity, UUIDComponent))
+ physicsWorld!.timestep = 1 / 60
+
+ // Create the entity
+ testEntity = createEntity()
+ setComponent(testEntity, EntityTreeComponent, { parentEntity: entity })
+ setComponent(testEntity, TransformComponent)
+ setComponent(testEntity, RigidBodyComponent, { type: BodyTypes.Dynamic })
+ setComponent(testEntity, ColliderComponent, { shape: Shapes.Mesh })
+ })
+
+ afterEach(() => {
+ removeEntity(testEntity)
+ return destroyEngine()
+ })
+
+ it('should store a character controller in the Controllers map', () => {
+ const before = physicsWorld.Controllers.get(testEntity)
+ assert.equal(before, undefined)
+ Physics.createCharacterController(physicsWorld, testEntity, {})
+ const after = physicsWorld.Controllers.get(testEntity)
+ assert.ok(after)
+ })
+
+ it('should create a the character controller with the expected defaults when they are omitted', () => {
+ Physics.createCharacterController(physicsWorld, testEntity, {})
+ const controller = physicsWorld.Controllers.get(testEntity)
+ assert.ok(controller)
+ assertFloatApproxEq(controller.offset(), Default.offset)
+ assertFloatApproxEq(controller.maxSlopeClimbAngle(), Default.maxSlopeClimbAngle)
+ assertFloatApproxEq(controller.minSlopeSlideAngle(), Default.minSlopeSlideAngle)
+ assertFloatApproxEq(controller.autostepMaxHeight()!, Default.autoStep.maxHeight)
+ assertFloatApproxEq(controller.autostepMinWidth()!, Default.autoStep.minWidth)
+ assert.equal(controller.autostepEnabled(), Default.autoStep.stepOverDynamic)
+ assert.equal(controller.snapToGroundEnabled(), !!Default.enableSnapToGround)
+ })
+
+ it('should create a the character controller with values different than the defaults when they are specified', () => {
+ const Expected = {
+ offset: 0.05,
+ maxSlopeClimbAngle: (20 * Math.PI) / 180,
+ minSlopeSlideAngle: (60 * Math.PI) / 180,
+ autoStep: { maxHeight: 0.1, minWidth: 0.05, stepOverDynamic: false },
+ enableSnapToGround: false as number | false
+ }
+ Physics.createCharacterController(physicsWorld, testEntity, Expected)
+ const controller = physicsWorld.Controllers.get(testEntity)
+ assert.ok(controller)
+ // Compare against the specified values
+ assertFloatApproxEq(controller.offset(), Expected.offset)
+ assertFloatApproxEq(controller.maxSlopeClimbAngle(), Expected.maxSlopeClimbAngle)
+ assertFloatApproxEq(controller.minSlopeSlideAngle(), Expected.minSlopeSlideAngle)
+ assertFloatApproxEq(controller.autostepMaxHeight()!, Expected.autoStep.maxHeight)
+ assertFloatApproxEq(controller.autostepMinWidth()!, Expected.autoStep.minWidth)
+ assert.equal(controller.autostepIncludesDynamicBodies(), Expected.autoStep.stepOverDynamic)
+ assert.equal(controller.snapToGroundEnabled(), !!Expected.enableSnapToGround)
+ // Compare against the defaults
+ assertFloatApproxNotEq(controller.offset(), Default.offset)
+ assertFloatApproxNotEq(controller.maxSlopeClimbAngle(), Default.maxSlopeClimbAngle)
+ assertFloatApproxNotEq(controller.minSlopeSlideAngle(), Default.minSlopeSlideAngle)
+ assertFloatApproxNotEq(controller.autostepMaxHeight()!, Default.autoStep.maxHeight)
+ assertFloatApproxNotEq(controller.autostepMinWidth()!, Default.autoStep.minWidth)
+ assert.notEqual(controller.autostepIncludesDynamicBodies(), Default.autoStep.stepOverDynamic)
+ assert.notEqual(controller.snapToGroundEnabled(), !!Default.enableSnapToGround)
+ })
+ })
+
+ describe('removeCharacterController', () => {
+ let testEntity = UndefinedEntity
+ let physicsWorld: PhysicsWorld
+
+ beforeEach(async () => {
+ createEngine()
+ await Physics.load()
+ const entity = createEntity()
+ setComponent(entity, UUIDComponent, UUIDComponent.generateUUID())
+ setComponent(entity, SceneComponent)
+ setComponent(entity, TransformComponent)
+ setComponent(entity, EntityTreeComponent)
+ physicsWorld = Physics.createWorld(getComponent(entity, UUIDComponent))
+ physicsWorld!.timestep = 1 / 60
+
+ // Create the entity
+ testEntity = createEntity()
+ setComponent(testEntity, EntityTreeComponent, { parentEntity: entity })
+ setComponent(testEntity, TransformComponent)
+ setComponent(testEntity, RigidBodyComponent, { type: BodyTypes.Dynamic })
+ setComponent(testEntity, ColliderComponent, { shape: Shapes.Mesh })
+ })
+
+ afterEach(() => {
+ removeEntity(testEntity)
+ return destroyEngine()
+ })
+
+ it('should remove the character controller from the Controllers map', () => {
+ const before = physicsWorld.Controllers.get(testEntity)
+ assert.equal(before, undefined)
+ Physics.createCharacterController(physicsWorld, testEntity, {})
+ const created = physicsWorld.Controllers.get(testEntity)
+ assert.ok(created)
+ Physics.removeCharacterController(physicsWorld, testEntity)
+ const after = physicsWorld.Controllers.get(testEntity)
+ assert.equal(after, undefined)
+ })
+ })
+
+ describe('computeColliderMovement', () => {
+ let testEntity = UndefinedEntity
+ let physicsWorld: PhysicsWorld
+
+ beforeEach(async () => {
+ createEngine()
+ await Physics.load()
+ const entity = createEntity()
+ setComponent(entity, UUIDComponent, UUIDComponent.generateUUID())
+ setComponent(entity, SceneComponent)
+ setComponent(entity, TransformComponent)
+ setComponent(entity, EntityTreeComponent)
+ physicsWorld = Physics.createWorld(getComponent(entity, UUIDComponent))
+ physicsWorld!.timestep = 1 / 60
+
+ // Create the entity
+ testEntity = createEntity()
+ setComponent(testEntity, EntityTreeComponent, { parentEntity: entity })
+ setComponent(testEntity, TransformComponent)
+ setComponent(testEntity, RigidBodyComponent, { type: BodyTypes.Dynamic })
+ setComponent(testEntity, ColliderComponent, { shape: Shapes.Box })
+ Physics.createCharacterController(physicsWorld, testEntity, {})
+ })
+
+ afterEach(() => {
+ removeEntity(testEntity)
+ return destroyEngine()
+ })
+
+ it("should change the `computedMovement` value for the entity's Character Controller", () => {
+ const movement = new Vector3(1, 2, 3)
+ const controller = physicsWorld.Controllers.get(testEntity)!
+ const before = controller.computedMovement()
+ Physics.computeColliderMovement(
+ physicsWorld,
+ testEntity, // entity: Entity,
+ testEntity, // colliderEntity: Entity,
+ movement // desiredTranslation: Vector3,
+ // filterGroups?: InteractionGroups,
+ // filterPredicate?: (collider: Collider) => boolean
+ )
+ const after = controller.computedMovement()
+ assertVecAllApproxNotEq(before, after, 3)
+ })
+ }) // << computeColliderMovement
+
+ describe('getComputedMovement', () => {
+ let testEntity = UndefinedEntity
+ let physicsWorld: PhysicsWorld
+
+ beforeEach(async () => {
+ createEngine()
+ await Physics.load()
+ const entity = createEntity()
+ setComponent(entity, UUIDComponent, UUIDComponent.generateUUID())
+ setComponent(entity, SceneComponent)
+ setComponent(entity, TransformComponent)
+ setComponent(entity, EntityTreeComponent)
+ physicsWorld = Physics.createWorld(getComponent(entity, UUIDComponent))
+ physicsWorld!.timestep = 1 / 60
+
+ // Create the entity
+ testEntity = createEntity()
+ setComponent(testEntity, EntityTreeComponent, { parentEntity: entity })
+ setComponent(testEntity, TransformComponent)
+ setComponent(testEntity, RigidBodyComponent, { type: BodyTypes.Dynamic })
+ setComponent(testEntity, ColliderComponent, { shape: Shapes.Box })
+ })
+
+ afterEach(() => {
+ removeEntity(testEntity)
+ return destroyEngine()
+ })
+
+ it('should return (0,0,0) when the entity does not have a CharacterController', () => {
+ const result = new Vector3(1, 2, 3)
+ Physics.getComputedMovement(physicsWorld, testEntity, result)
+ assertVecApproxEq(result, Vector3_Zero, 3)
+ })
+
+ it("should return the same value contained in the `computedMovement` value of the entity's Character Controller", () => {
+ Physics.createCharacterController(physicsWorld, testEntity, {})
+ const movement = new Vector3(1, 2, 3)
+ const controller = physicsWorld.Controllers.get(testEntity)!
+ const before = controller.computedMovement()
+ Physics.computeColliderMovement(
+ physicsWorld,
+ testEntity, // entity: Entity,
+ testEntity, // colliderEntity: Entity,
+ movement // desiredTranslation: Vector3,
+ // filterGroups?: InteractionGroups,
+ // filterPredicate?: (collider: Collider) => boolean
+ )
+ const after = controller.computedMovement()
+ assertVecAllApproxNotEq(before, after, 3)
+ const result = new Vector3()
+ Physics.getComputedMovement(physicsWorld, testEntity, result)
+ assertVecAllApproxNotEq(before, result, 3)
+ assertVecApproxEq(after, result, 3)
+ })
+ }) // << getComputedMovement
+ }) // << CharacterControllers
+
+ describe('Raycasts', () => {
+ describe('castRay', () => {
+ let testEntity = UndefinedEntity
+ let physicsWorld: PhysicsWorld
+
+ beforeEach(async () => {
+ createEngine()
+ await Physics.load()
+ const entity = createEntity()
+ setComponent(entity, UUIDComponent, UUIDComponent.generateUUID())
+ setComponent(entity, SceneComponent)
+ setComponent(entity, TransformComponent)
+ setComponent(entity, EntityTreeComponent)
+ physicsWorld = Physics.createWorld(getComponent(entity, UUIDComponent))
+ physicsWorld!.timestep = 1 / 60
+
+ // Create the entity
+ testEntity = createEntity()
+ setComponent(testEntity, EntityTreeComponent, { parentEntity: entity })
+ setComponent(testEntity, TransformComponent, {
+ position: new Vector3(10, 0, 0),
+ scale: new Vector3(10, 10, 10)
+ })
+ computeTransformMatrix(testEntity)
+ setComponent(testEntity, RigidBodyComponent, { type: BodyTypes.Fixed })
+ setComponent(testEntity, ColliderComponent, {
+ shape: Shapes.Box,
+ collisionLayer: CollisionGroups.Default,
+ collisionMask: DefaultCollisionMask
+ })
+ })
+
+ afterEach(() => {
+ removeEntity(testEntity)
+ return destroyEngine()
+ })
+
+ it('should cast a ray and hit a rigidbody', async () => {
+ physicsWorld!.step()
+
+ const raycastComponentData = {
+ type: SceneQueryType.Closest,
+ origin: new Vector3().set(0, 0, 0),
+ direction: ObjectDirection.Right,
+ maxDistance: 20,
+ groups: getInteractionGroups(CollisionGroups.Default, CollisionGroups.Default)
+ }
+ const hits = Physics.castRay(physicsWorld!, raycastComponentData)
+
+ assert.deepEqual(hits.length, 1)
+ assert.deepEqual(hits[0].normal.x, -1)
+ assert.deepEqual(hits[0].distance, 5)
+ assert.deepEqual((hits[0].body.userData as any)['entity'], testEntity)
+ })
+ })
+
+ describe('castRayFromCamera', () => {
+ let testEntity = UndefinedEntity
+ let physicsWorld: PhysicsWorld
+
+ beforeEach(async () => {
+ createEngine()
+ await Physics.load()
+ const entity = createEntity()
+ setComponent(entity, UUIDComponent, UUIDComponent.generateUUID())
+ setComponent(entity, SceneComponent)
+ setComponent(entity, TransformComponent)
+ setComponent(entity, EntityTreeComponent)
+ physicsWorld = Physics.createWorld(getComponent(entity, UUIDComponent))
+ physicsWorld!.timestep = 1 / 60
+
+ // Create the entity
+ testEntity = createEntity()
+ setComponent(testEntity, EntityTreeComponent, { parentEntity: entity })
+ setComponent(testEntity, TransformComponent, {
+ position: new Vector3(10, 0, 0),
+ scale: new Vector3(10, 10, 10)
+ })
+ computeTransformMatrix(testEntity)
+ setComponent(testEntity, RigidBodyComponent, { type: BodyTypes.Fixed })
+ setComponent(testEntity, ColliderComponent, {
+ shape: Shapes.Box,
+ collisionLayer: CollisionGroups.Default,
+ collisionMask: DefaultCollisionMask
+ })
+ })
+
+ afterEach(() => {
+ removeEntity(testEntity)
+ return destroyEngine()
+ })
+
+ /*
+ it('should cast a ray from a camera and hit a rigidbody', async () => {
+ physicsWorld!.step()
+ assert.ok(1)
+ })
+ */
+ }) // << castRayFromCamera
+
+ /**
+ // @todo Double check the `castShape` implementation before implementing this test
+ describe('castShape', () => {
+ let testEntity = UndefinedEntity
+ let physicsWorld: PhysicsWorld
+
+ beforeEach(async () => {
+ createEngine()
+ await Physics.load()
+ const entity = createEntity()
+ setComponent(entity, UUIDComponent, UUIDComponent.generateUUID())
+ setComponent(entity, SceneComponent)
+ setComponent(entity, TransformComponent)
+ setComponent(entity, EntityTreeComponent)
+ physicsWorld = Physics.createWorld(getComponent(entity, UUIDComponent))
+ physicsWorld!.timestep = 1 / 60
+
+ // Create the entity
+ testEntity = createEntity()
+ setComponent(testEntity, EntityTreeComponent, { parentEntity: entity })
+ setComponent(testEntity, TransformComponent, {
+ position: new Vector3(10, 0, 0),
+ scale: new Vector3(10, 10, 10)
+ })
+ computeTransformMatrix(testEntity)
+ setComponent(testEntity, RigidBodyComponent, { type: BodyTypes.Fixed })
+ setComponent(testEntity, ColliderComponent, {
+ shape: Shapes.Box,
+ collisionLayer: CollisionGroups.Default,
+ collisionMask: DefaultCollisionMask
+ })
+ })
+
+ afterEach(() => {
+ removeEntity(testEntity)
+ return destroyEngine()
+ })
+
+ // @todo This setup is not hitting. Double check the `castShape` implementation before implementing this test
+ it('should cast a shape and hit a rigidbody', () => {
+ physicsWorld!.step()
+
+ const collider = physicsWorld.Colliders.get(testEntity)!
+ const hits = [] as RaycastHit[]
+ const shapecastComponentData :ShapecastArgs= {
+ type: SceneQueryType.Closest, // type: SceneQueryType
+ hits: hits, // hits: RaycastHit[]
+ collider: collider, // collider: Collider
+ direction: ObjectDirection.Right, // direction: Vector3
+ maxDistance: 20, // maxDistance: number
+ collisionGroups: getInteractionGroups(CollisionGroups.Default, CollisionGroups.Default), // collisionGroups: InteractionGroups
+ }
+ Physics.castShape(physicsWorld!, shapecastComponentData)
+
+ assert.deepEqual(hits.length, 1, "The length of the hits array is incorrect.")
+ assert.deepEqual(hits[0].normal.x, -1)
+ assert.deepEqual(hits[0].distance, 5)
+ assert.deepEqual((hits[0].body.userData as any)['entity'], testEntity)
+ })
+ }) // << castShape
+ */
+ }) // << Raycasts
+
+ describe('Collisions', () => {
+ describe('createCollisionEventQueue', () => {
+ beforeEach(async () => {
+ createEngine()
+ await Physics.load()
+ })
+
+ afterEach(() => {
+ return destroyEngine()
+ })
+
+ it('should create a collision event queue successfully', () => {
+ const queue = Physics.createCollisionEventQueue()
+ assert(queue)
+ })
+ })
+
+ describe('drainCollisionEventQueue', () => {
+ const InvalidHandle = 8198123
+ let physicsWorld: PhysicsWorld
+ let testEntity1 = UndefinedEntity
+ let testEntity2 = UndefinedEntity
+
+ beforeEach(async () => {
+ createEngine()
+ await Physics.load()
+ const entity = createEntity()
+ setComponent(entity, UUIDComponent, UUIDComponent.generateUUID())
+ setComponent(entity, SceneComponent)
+ setComponent(entity, TransformComponent)
+ setComponent(entity, EntityTreeComponent)
+ physicsWorld = Physics.createWorld(getComponent(entity, UUIDComponent))
+ physicsWorld.timestep = 1 / 60
+
+ testEntity1 = createEntity()
+ setComponent(testEntity1, EntityTreeComponent, { parentEntity: entity })
+ setComponent(testEntity1, TransformComponent)
+ setComponent(testEntity1, RigidBodyComponent)
+ setComponent(testEntity1, ColliderComponent)
+
+ testEntity2 = createEntity()
+ setComponent(testEntity2, EntityTreeComponent, { parentEntity: entity })
+ setComponent(testEntity2, TransformComponent)
+ setComponent(testEntity2, RigidBodyComponent)
+ setComponent(testEntity2, ColliderComponent)
+ })
+
+ afterEach(() => {
+ return destroyEngine()
+ })
+
+ function assertCollisionEventClosure(closure: any) {
+ type CollisionEventClosure = (handle1: number, handle2: number, started: boolean) => void
+ function hasCollisionEventClosureShape(closure: any): closure is CollisionEventClosure {
+ return typeof closure === 'function' && closure.length === 3
+ }
+ assert.ok(closure)
+ assert.ok(hasCollisionEventClosureShape(closure))
+ }
+
+ it('should return a function with the correct shape (handle1: number, handle2: number, started: boolean) => void', () => {
+ assert.ok(physicsWorld)
+ const event = Physics.drainCollisionEventQueue(physicsWorld)
+ assertCollisionEventClosure(event)
+ })
+
+ it('should do nothing if any of the collider handles are not found', () => {
+ assert.ok(physicsWorld)
+ const event = Physics.drainCollisionEventQueue(physicsWorld)
+ assertCollisionEventClosure(event)
+ physicsWorld.step()
+ const collider1 = physicsWorld.Colliders.get(testEntity1)
+ const collider2 = physicsWorld.Colliders.get(testEntity2)
+ assert.ok(collider1)
+ assert.ok(collider2)
+
+ assert.ok(!hasComponent(testEntity1, CollisionComponent))
+ event(collider1.handle, InvalidHandle, true)
+ assert.ok(!hasComponent(testEntity1, CollisionComponent))
+
+ assert.ok(!hasComponent(testEntity2, CollisionComponent))
+ event(collider2!.handle, InvalidHandle, true)
+ assert.ok(!hasComponent(testEntity2, CollisionComponent))
+ })
+
+ it('should add a CollisionComponent to the entities contained in the userData of the parent rigidBody of each collider (collider.parent())', () => {
+ assert.ok(physicsWorld)
+ const event = Physics.drainCollisionEventQueue(physicsWorld)
+ assertCollisionEventClosure(event)
+ physicsWorld.step()
+
+ // Get the colliders from the API
+ const collider1 = physicsWorld.Colliders.get(testEntity1)
+ const collider2 = physicsWorld.Colliders.get(testEntity2)
+ assert.ok(collider1)
+ assert.ok(collider2)
+ // Get the parents from the API
+ const colliderParent1 = collider1.parent()
+ const colliderParent2 = collider2.parent()
+ assert.ok(colliderParent1)
+ assert.ok(colliderParent2)
+ // Get the entities from parent.userData
+ const entity1 = (colliderParent1.userData as any)['entity']
+ const entity2 = (colliderParent2.userData as any)['entity']
+ assert.equal(testEntity1, entity1)
+ assert.equal(testEntity2, entity2)
+ // Check before
+ assert.ok(!hasComponent(entity1, CollisionComponent))
+ assert.ok(!hasComponent(entity2, CollisionComponent))
+
+ // Run and Check after
+ event(collider1.handle, collider2.handle, true)
+ assert.ok(hasComponent(entity1, CollisionComponent))
+ assert.ok(hasComponent(entity2, CollisionComponent))
+ })
+
+ describe('when `started` is set to `true` ...', () => {
+ it('... should create a CollisionEvents.COLLISION_START when neither of the colliders is a sensor (aka has a TriggerComponent)', () => {
+ const Started = true
+
+ assert.ok(physicsWorld)
+ const event = Physics.drainCollisionEventQueue(physicsWorld)
+ assertCollisionEventClosure(event)
+ // Get the colliders from the API
+ const collider1 = physicsWorld.Colliders.get(testEntity1)
+ const collider2 = physicsWorld.Colliders.get(testEntity2)
+ assert.ok(collider1)
+ assert.ok(collider2)
+ // Check before
+ const before1 = getComponent(testEntity1, CollisionComponent)?.get(testEntity2)
+ const before2 = getComponent(testEntity2, CollisionComponent)?.get(testEntity1)
+ assert.equal(before1, undefined)
+ assert.equal(before2, undefined)
+ // setComponent(testEntity1, TriggerComponent) // DONT set the trigger component (testEntity1.body.isSensor() is false)
+
+ // Run and Check after
+ event(collider1.handle, collider2.handle, Started)
+ const after1 = getComponent(testEntity1, CollisionComponent).get(testEntity2)
+ const after2 = getComponent(testEntity2, CollisionComponent).get(testEntity1)
+ assert.ok(after1)
+ assert.ok(after2)
+ assert.equal(after1.type, CollisionEvents.COLLISION_START)
+ assert.equal(after2.type, CollisionEvents.COLLISION_START)
+ })
+
+ it('... should create a CollisionEvents.TRIGGER_START when either one of the colliders is a sensor (aka has a TriggerComponent)', async () => {
+ //force nested reactors to run
+ const { rerender, unmount } = render(<>>)
+
+ const Started = true
+
+ assert.ok(physicsWorld)
+ const event = Physics.drainCollisionEventQueue(physicsWorld)
+ assertCollisionEventClosure(event)
+ // Get the colliders from the API
+ const collider1 = physicsWorld.Colliders.get(testEntity1)
+ const collider2 = physicsWorld.Colliders.get(testEntity2)
+ assert.ok(collider1)
+ assert.ok(collider2)
+ // Check before
+ const before1 = getComponent(testEntity1, CollisionComponent)?.get(testEntity2)
+ const before2 = getComponent(testEntity2, CollisionComponent)?.get(testEntity1)
+ assert.equal(before1, undefined)
+ assert.equal(before2, undefined)
+ setComponent(testEntity1, TriggerComponent) // Set the trigger component (marks testEntity1.body.isSensor() as true)
+ await act(() => rerender(<>>))
+
+ event(collider1.handle, collider2.handle, Started)
+
+ // Run and Check after
+ const after1 = getComponent(testEntity1, CollisionComponent).get(testEntity2)
+ const after2 = getComponent(testEntity2, CollisionComponent).get(testEntity1)
+ assert.ok(after1)
+ assert.ok(after2)
+ assert.equal(after1.type, CollisionEvents.TRIGGER_START)
+ assert.equal(after2.type, CollisionEvents.TRIGGER_START)
+ })
+
+ it('... should set entity2 in the CollisionComponent of entity1, and entity1 in the CollisionComponent of entity2', () => {
+ assert.ok(physicsWorld)
+ const event = Physics.drainCollisionEventQueue(physicsWorld)
+ assertCollisionEventClosure(event)
+ // Get the colliders from the API
+ const collider1 = physicsWorld.Colliders.get(testEntity1)
+ const collider2 = physicsWorld.Colliders.get(testEntity2)
+ assert.ok(collider1)
+ assert.ok(collider2)
+ // Check before
+ const before1 = getComponent(testEntity1, CollisionComponent)?.get(testEntity2)
+ const before2 = getComponent(testEntity2, CollisionComponent)?.get(testEntity1)
+ assert.equal(before1, undefined)
+ assert.equal(before2, undefined)
+
+ // Run and Check after
+ event(collider1.handle, collider2.handle, true)
+ const after1 = getComponent(testEntity1, CollisionComponent).get(testEntity2)
+ const after2 = getComponent(testEntity2, CollisionComponent).get(testEntity1)
+ assert.ok(after1)
+ assert.ok(after2)
+ })
+ })
+
+ describe('when `started` is set to `false` ...', () => {
+ it('... should create a CollisionEvents.TRIGGER_END when either one of the colliders is a sensor', async () => {
+ //force nested reactors to run
+ const { rerender, unmount } = render(<>>)
+
+ const Started = false
+
+ assert.ok(physicsWorld)
+ const event = Physics.drainCollisionEventQueue(physicsWorld)
+ assertCollisionEventClosure(event)
+ // Get the colliders from the API
+ const collider1 = physicsWorld.Colliders.get(testEntity1)
+ const collider2 = physicsWorld.Colliders.get(testEntity2)
+ assert.ok(collider1)
+ assert.ok(collider2)
+ // Check before
+ const before1 = getComponent(testEntity1, CollisionComponent)?.get(testEntity2)
+ const before2 = getComponent(testEntity2, CollisionComponent)?.get(testEntity1)
+ assert.equal(before1, undefined)
+ assert.equal(before2, undefined)
+ setComponent(testEntity1, TriggerComponent) // Set the trigger component (marks testEntity1.body.isSensor() as true)
+ await act(() => rerender(<>>))
+
+ // Run and Check after
+ event(collider1.handle, collider2.handle, true) // Run the even twice, so that the entities get each other in their collision components
+ event(collider1.handle, collider2.handle, Started)
+ const after1 = getComponent(testEntity1, CollisionComponent).get(testEntity2)
+ const after2 = getComponent(testEntity2, CollisionComponent).get(testEntity1)
+ assert.ok(after1)
+ assert.ok(after2)
+ assert.equal(after1.type, CollisionEvents.TRIGGER_END)
+ assert.equal(after2.type, CollisionEvents.TRIGGER_END)
+ })
+
+ it('... should create a CollisionEvents.COLLISION_END when neither of the colliders is a sensor', () => {
+ const Started = false
+
+ assert.ok(physicsWorld)
+ const event = Physics.drainCollisionEventQueue(physicsWorld)
+ assertCollisionEventClosure(event)
+ // Get the colliders from the API
+ const collider1 = physicsWorld.Colliders.get(testEntity1)
+ const collider2 = physicsWorld.Colliders.get(testEntity2)
+ assert.ok(collider1)
+ assert.ok(collider2)
+ // Check before
+ const before1 = getComponent(testEntity1, CollisionComponent)?.get(testEntity2)
+ const before2 = getComponent(testEntity2, CollisionComponent)?.get(testEntity1)
+ assert.equal(before1, undefined)
+ assert.equal(before2, undefined)
+ // setComponent(testEntity1, TriggerComponent) // DONT set the trigger component (testEntity1.body.isSensor() is false)
+
+ // Run and Check after
+ event(collider1.handle, collider2.handle, true) // Run the even twice, so that the entities get each other in their collision components
+ event(collider1.handle, collider2.handle, Started)
+ const after1 = getComponent(testEntity1, CollisionComponent).get(testEntity2)
+ const after2 = getComponent(testEntity2, CollisionComponent).get(testEntity1)
+ assert.ok(after1)
+ assert.ok(after2)
+ assert.equal(after1.type, CollisionEvents.COLLISION_END)
+ assert.equal(after2.type, CollisionEvents.COLLISION_END)
+ })
+ })
+ }) // << drainCollisionEventQueue
+
+ describe('drainContactEventQueue', () => {
+ let physicsWorld: PhysicsWorld
+ let testEntity1 = UndefinedEntity
+ let testEntity2 = UndefinedEntity
+
+ beforeEach(async () => {
+ createEngine()
+ await Physics.load()
+ const entity = createEntity()
+ setComponent(entity, UUIDComponent, UUIDComponent.generateUUID())
+ setComponent(entity, SceneComponent)
+ setComponent(entity, TransformComponent)
+ setComponent(entity, EntityTreeComponent)
+ physicsWorld = Physics.createWorld(getComponent(entity, UUIDComponent))
+ physicsWorld.timestep = 1 / 60
+
+ testEntity1 = createEntity()
+ setComponent(testEntity1, EntityTreeComponent, { parentEntity: entity })
+ setComponent(testEntity1, TransformComponent)
+ setComponent(testEntity1, RigidBodyComponent, { type: BodyTypes.Dynamic })
+ setComponent(testEntity1, ColliderComponent)
+ testEntity2 = createEntity()
+ setComponent(testEntity2, EntityTreeComponent, { parentEntity: entity })
+ setComponent(testEntity2, TransformComponent)
+ setComponent(testEntity2, RigidBodyComponent, { type: BodyTypes.Dynamic })
+ setComponent(testEntity2, ColliderComponent)
+ })
+
+ afterEach(() => {
+ removeEntity(testEntity1)
+ removeEntity(testEntity2)
+ return destroyEngine()
+ })
+
+ function assertContactEventClosure(closure: any) {
+ type ContactEventClosure = (handle1: number, handle2: number, started: boolean) => void
+ function hasContactEventClosureShape(closure: any): closure is ContactEventClosure {
+ return typeof closure === 'function' && closure.length === 1
+ }
+ assert.ok(closure)
+ assert.ok(hasContactEventClosureShape(closure))
+ }
+
+ it('should return a function with the correct shape (event: TempContactForceEvent) => void', () => {
+ assert.ok(physicsWorld)
+ const closure = Physics.drainContactEventQueue(physicsWorld)
+ assertContactEventClosure(closure)
+ })
+
+ describe('if the collision exists ...', () => {
+ const DummyMaxForce = { x: 42, y: 43, z: 44 }
+ const DummyTotalForce = { x: 45, y: 46, z: 47 }
+ const DummyHit = {
+ maxForceDirection: DummyMaxForce,
+ totalForce: DummyTotalForce
+ } as ColliderHitEvent
+ function setDummyCollisionBetween(ent1: Entity, ent2: Entity, hit = DummyHit): void {
+ const hits = new Map()
+ hits.set(ent2, hit)
+ setComponent(ent1, CollisionComponent)
+ getMutableComponent(ent1, CollisionComponent).set(hits)
+ }
+
+ const ExpectedMaxForce = { x: 4, y: 5, z: 6 }
+ const ExpectedTotalForce = { x: 7, y: 8, z: 9 }
+
+ it('should store event.maxForceDirection() into the CollisionComponent.maxForceDirection of entity1.collision.get(entity2) if the collision exists', () => {
+ // Setup the function spies
+ const collider1Spy = sinon.spy((): number => {
+ return physicsWorld.Colliders.get(testEntity1)!.handle
+ })
+ const collider2Spy = sinon.spy((): number => {
+ return physicsWorld.Colliders.get(testEntity2)!.handle
+ })
+ const totalForceSpy = sinon.spy((): Vector => {
+ return ExpectedTotalForce
+ })
+ const maxForceSpy = sinon.spy((): Vector => {
+ return ExpectedMaxForce
+ })
+
+ // Check before
+ assert.ok(physicsWorld)
+ const event = Physics.drainContactEventQueue(physicsWorld)
+ assertContactEventClosure(event)
+ assert.equal(getOptionalComponent(testEntity1, CollisionComponent), undefined)
+ assert.equal(getOptionalComponent(testEntity2, CollisionComponent), undefined)
+
+ // Run and Check after
+ setDummyCollisionBetween(testEntity1, testEntity2)
+ setDummyCollisionBetween(testEntity2, testEntity1)
+ event({
+ collider1: collider1Spy as any,
+ collider2: collider2Spy as any,
+ totalForce: totalForceSpy as any,
+ maxForceDirection: maxForceSpy as any
+ } as TempContactForceEvent)
+ sinon.assert.called(collider1Spy)
+ sinon.assert.called(collider2Spy)
+ sinon.assert.called(maxForceSpy)
+ const after = getComponent(testEntity1, CollisionComponent).get(testEntity2)?.maxForceDirection
+ assertVecApproxEq(after, ExpectedMaxForce, 3)
+ })
+
+ it('should store event.maxForceDirection() into the CollisionComponent.maxForceDirection of entity2.collision.get(entity1) if the collision exists', () => {
+ // Setup the function spies
+ const collider1Spy = sinon.spy((): number => {
+ return physicsWorld.Colliders.get(testEntity1)!.handle
+ })
+ const collider2Spy = sinon.spy((): number => {
+ return physicsWorld.Colliders.get(testEntity2)!.handle
+ })
+ const totalForceSpy = sinon.spy((): Vector => {
+ return ExpectedTotalForce
+ })
+ const maxForceSpy = sinon.spy((): Vector => {
+ return ExpectedMaxForce
+ })
+
+ // Check before
+ assert.ok(physicsWorld)
+ const event = Physics.drainContactEventQueue(physicsWorld)
+ assertContactEventClosure(event)
+ assert.equal(getOptionalComponent(testEntity1, CollisionComponent), undefined)
+ assert.equal(getOptionalComponent(testEntity2, CollisionComponent), undefined)
+
+ // Run and Check after
+ setDummyCollisionBetween(testEntity1, testEntity2)
+ setDummyCollisionBetween(testEntity2, testEntity1)
+
+ event({
+ collider1: collider1Spy as any,
+ collider2: collider2Spy as any,
+ totalForce: totalForceSpy as any,
+ maxForceDirection: maxForceSpy as any
+ } as TempContactForceEvent)
+
+ sinon.assert.called(collider1Spy)
+ sinon.assert.called(collider2Spy)
+ sinon.assert.called(maxForceSpy)
+ const after = getComponent(testEntity2, CollisionComponent).get(testEntity1)?.maxForceDirection
+ assertVecApproxEq(after, ExpectedMaxForce, 3)
+ })
+
+ it('should store event.totalForce() into the CollisionComponent.totalForce of entity1.collision.get(entity2) if the collision exists', () => {
+ // Setup the function spies
+ const collider1Spy = sinon.spy((): number => {
+ return physicsWorld.Colliders.get(testEntity1)!.handle
+ })
+ const collider2Spy = sinon.spy((): number => {
+ return physicsWorld.Colliders.get(testEntity2)!.handle
+ })
+ const totalForceSpy = sinon.spy((): Vector => {
+ return ExpectedTotalForce
+ })
+ const maxForceSpy = sinon.spy((): Vector => {
+ return ExpectedMaxForce
+ })
+
+ // Check before
+ assert.ok(physicsWorld)
+ const event = Physics.drainContactEventQueue(physicsWorld)
+ assertContactEventClosure(event)
+ assert.equal(getOptionalComponent(testEntity1, CollisionComponent), undefined)
+ assert.equal(getOptionalComponent(testEntity2, CollisionComponent), undefined)
+ // Run and Check after
+ setDummyCollisionBetween(testEntity1, testEntity2)
+ setDummyCollisionBetween(testEntity2, testEntity1)
+
+ event({
+ collider1: collider1Spy as any,
+ collider2: collider2Spy as any,
+ totalForce: totalForceSpy as any,
+ maxForceDirection: maxForceSpy as any
+ } as TempContactForceEvent)
+
+ sinon.assert.called(collider1Spy)
+ sinon.assert.called(collider2Spy)
+ sinon.assert.called(totalForceSpy)
+ const after = getComponent(testEntity1, CollisionComponent).get(testEntity2)?.totalForce
+ assertVecApproxEq(after, ExpectedTotalForce, 3)
+ })
+
+ it('should store event.totalForce() into the CollisionComponent.totalForce of entity2.collision.get(entity1) if the collision exists', () => {
+ // Setup the function spies
+ const collider1Spy = sinon.spy((): number => {
+ return physicsWorld.Colliders.get(testEntity1)!.handle
+ })
+ const collider2Spy = sinon.spy((): number => {
+ return physicsWorld.Colliders.get(testEntity2)!.handle
+ })
+ const totalForceSpy = sinon.spy((): Vector => {
+ return ExpectedTotalForce
+ })
+ const maxForceSpy = sinon.spy((): Vector => {
+ return ExpectedMaxForce
+ })
+
+ // Check before
+ assert.ok(physicsWorld)
+ const event = Physics.drainContactEventQueue(physicsWorld)
+ assertContactEventClosure(event)
+ assert.equal(getOptionalComponent(testEntity1, CollisionComponent), undefined)
+ assert.equal(getOptionalComponent(testEntity2, CollisionComponent), undefined)
+
+ // Run and Check after
+ setDummyCollisionBetween(testEntity1, testEntity2)
+ setDummyCollisionBetween(testEntity2, testEntity1)
+ event({
+ collider1: collider1Spy as any,
+ collider2: collider2Spy as any,
+ totalForce: totalForceSpy as any,
+ maxForceDirection: maxForceSpy as any
+ } as TempContactForceEvent)
+
+ sinon.assert.called(collider1Spy)
+ sinon.assert.called(collider2Spy)
+ sinon.assert.called(totalForceSpy)
+ const after = getComponent(testEntity2, CollisionComponent).get(testEntity1)?.totalForce
+ assertVecApproxEq(after, ExpectedTotalForce, 3)
+ })
+ })
+ }) // << drainContactEventQueue
+ }) // << Collisions
+})
+
+/** TODO:
+ describe("load", () => {}) // @todo Is there a way to check that the wasmInit() call from rapier.js has been run?
+ // Character Controller
+ describe("getControllerOffset", () => {}) // @deprecated
+ */
diff --git a/packages/spatial/src/physics/classes/Physics.ts b/packages/spatial/src/physics/classes/Physics.ts
index 55e9a26034..be6091dd76 100644
--- a/packages/spatial/src/physics/classes/Physics.ts
+++ b/packages/spatial/src/physics/classes/Physics.ts
@@ -91,9 +91,9 @@ export type PhysicsWorld = World & {
id: EntityUUID
substeps: number
cameraAttachedRigidbodyEntity: Entity
- Colliders: Record
- Rigidbodies: Record
- Controllers: Record
+ Colliders: Map
+ Rigidbodies: Map
+ Controllers: Map
collisionEventQueue: EventQueue
drainCollisions: ReturnType
drainContacts: ReturnType
@@ -115,9 +115,9 @@ function createWorld(id: EntityUUID, args = { gravity: { x: 0.0, y: -9.81, z: 0.
world.substeps = args.substeps
world.cameraAttachedRigidbodyEntity = UndefinedEntity
- const Colliders = {} as Record
- const Rigidbodies = {} as Record
- const Controllers = {} as Record
+ const Colliders = new Map()
+ const Rigidbodies = new Map()
+ const Controllers = new Map()
world.Colliders = Colliders
world.Rigidbodies = Rigidbodies
@@ -133,13 +133,13 @@ function createWorld(id: EntityUUID, args = { gravity: { x: 0.0, y: -9.81, z: 0.
}
function destroyWorld(id: EntityUUID) {
- const world = getMutableState(RapierWorldState)[id]
+ const world = getState(RapierWorldState)[id]
if (!world) throw new Error('Physics world not found')
- world.Colliders.set({})
- world.Rigidbodies.set({})
- world.Controllers.set({})
getMutableState(RapierWorldState)[id].set(none)
- world.value.free()
+ world.Colliders.clear()
+ world.Rigidbodies.clear()
+ world.Controllers.clear()
+ world.free()
}
function getWorld(entity: Entity) {
@@ -193,7 +193,7 @@ function simulate(simulationTimestep: number, kinematicEntities: Entity[]) {
// smooth kinematic pose changes
const substep = (i + 1) / substeps
for (const entity of kinematicEntities) {
- if (world.Rigidbodies[entity]) smoothKinematicBody(world, entity, timestep, substep)
+ if (world.Rigidbodies.has(entity)) smoothKinematicBody(world, entity, timestep, substep)
}
world.step(collisionEventQueue)
collisionEventQueue.drainCollisionEvents(drainCollisions)
@@ -259,16 +259,16 @@ function createRigidBody(world: PhysicsWorld, entity: Entity) {
const rigidBodyUserdata = { entity: entity }
body.userData = rigidBodyUserdata
- getMutableState(RapierWorldState)[world.id].Rigidbodies[entity].set(body)
+ world.Rigidbodies.set(entity, body)
}
function isSleeping(world: PhysicsWorld, entity: Entity) {
- const rigidBody = world.Rigidbodies[entity]
+ const rigidBody = world.Rigidbodies.get(entity)
return !rigidBody || rigidBody.isSleeping()
}
const setRigidBodyType = (world: PhysicsWorld, entity: Entity, type: Body) => {
- const rigidbody = world.Rigidbodies[entity]
+ const rigidbody = world.Rigidbodies.get(entity)
if (!rigidbody) return
let typeEnum: RigidBodyType = undefined!
@@ -298,7 +298,7 @@ function setRigidbodyPose(
linearVelocity: Vector3,
angularVelocity: Vector3
) {
- const rigidBody = world.Rigidbodies[entity]
+ const rigidBody = world.Rigidbodies.get(entity)
if (!rigidBody) return
rigidBody.setTranslation(position, false)
rigidBody.setRotation(rotation, false)
@@ -307,14 +307,14 @@ function setRigidbodyPose(
}
function setKinematicRigidbodyPose(world: PhysicsWorld, entity: Entity, position: Vector3, rotation: Quaternion) {
- const rigidBody = world.Rigidbodies[entity]
+ const rigidBody = world.Rigidbodies.get(entity)
if (!rigidBody) return
rigidBody.setNextKinematicTranslation(position)
rigidBody.setNextKinematicRotation(rotation)
}
function enabledCcd(world: PhysicsWorld, entity: Entity, enabled: boolean) {
- const rigidBody = world.Rigidbodies[entity]
+ const rigidBody = world.Rigidbodies.get(entity)
if (!rigidBody) return
rigidBody.enableCcd(enabled)
}
@@ -326,7 +326,7 @@ function enabledCcd(world: PhysicsWorld, entity: Entity, enabled: boolean) {
* https://github.com/dimforge/rapier.js/issues/282#issuecomment-2177426589
*/
function lockRotations(world: PhysicsWorld, entity: Entity, lock: boolean) {
- const rigidBody = world.Rigidbodies[entity]
+ const rigidBody = world.Rigidbodies.get(entity)
if (!rigidBody) return
rigidBody.lockRotations(lock, false)
}
@@ -335,7 +335,7 @@ function lockRotations(world: PhysicsWorld, entity: Entity, lock: boolean) {
* @note `setEnabledRotations(entity, [ true, true, true ])` is the exact same as `lockRotations(entity, true)`
*/
function setEnabledRotations(world: PhysicsWorld, entity: Entity, enabledRotations: [boolean, boolean, boolean]) {
- const rigidBody = world.Rigidbodies[entity]
+ const rigidBody = world.Rigidbodies.get(entity)
if (!rigidBody) return
rigidBody.setEnabledRotations(enabledRotations[0], enabledRotations[1], enabledRotations[2], false)
}
@@ -344,7 +344,7 @@ function updatePreviousRigidbodyPose(entities: Entity[]) {
for (const entity of entities) {
const world = getWorld(entity)
if (!world) continue
- const body = world.Rigidbodies[entity]
+ const body = world.Rigidbodies.get(entity)
if (!body) continue
const translation = body.translation() as Vector3
const rotation = body.rotation() as Quaternion
@@ -362,7 +362,7 @@ function updateRigidbodyPose(entities: Entity[]) {
for (const entity of entities) {
const world = getWorld(entity)
if (!world) continue
- const body = world.Rigidbodies[entity]
+ const body = world.Rigidbodies.get(entity)
if (!body) continue
const translation = body.translation() as Vector3
const rotation = body.rotation() as Quaternion
@@ -385,21 +385,21 @@ function updateRigidbodyPose(entities: Entity[]) {
}
function removeRigidbody(world: PhysicsWorld, entity: Entity) {
- const rigidBody = world.Rigidbodies[entity]
+ const rigidBody = world.Rigidbodies.get(entity)
if (rigidBody && world.bodies.contains(rigidBody.handle)) {
world.removeRigidBody(rigidBody)
- getMutableState(RapierWorldState)[world.id].Rigidbodies[entity].set(none)
+ world.Rigidbodies.delete(entity)
}
}
function applyImpulse(world: PhysicsWorld, entity: Entity, impulse: Vector3) {
- const rigidBody = world.Rigidbodies[entity]
+ const rigidBody = world.Rigidbodies.get(entity)
if (!rigidBody) return
rigidBody.applyImpulse(impulse, true)
}
function createColliderDesc(world: PhysicsWorld, entity: Entity, rootEntity: Entity) {
- if (!world.Rigidbodies[rootEntity]) return
+ if (!world.Rigidbodies.has(rootEntity)) return
const mesh = getOptionalComponent(entity, MeshComponent)
@@ -538,30 +538,30 @@ function attachCollider(
rigidBodyEntity: Entity,
colliderEntity: Entity
) {
- if (world.Colliders[colliderEntity]) return
- const rigidBody = world.Rigidbodies[rigidBodyEntity] // guaranteed will exist
+ if (world.Colliders.has(colliderEntity)) return
+ const rigidBody = world.Rigidbodies.get(rigidBodyEntity) // guaranteed will exist
if (!rigidBody) return console.error('Rigidbody not found for entity ' + rigidBodyEntity)
const collider = world.createCollider(colliderDesc, rigidBody)
- getMutableState(RapierWorldState)[world.id].Colliders[colliderEntity].set(collider)
+ world.Colliders.set(colliderEntity, collider)
return collider
}
function setColliderPose(world: PhysicsWorld, entity: Entity, position: Vector3, rotation: Quaternion) {
- const collider = world.Colliders[entity]
+ const collider = world.Colliders.get(entity)
if (!collider) return
collider.setTranslationWrtParent(position)
collider.setRotationWrtParent(rotation)
}
function removeCollider(world: PhysicsWorld, entity: Entity) {
- const collider = world.Colliders[entity]
+ const collider = world.Colliders.get(entity)
if (!collider) return
world.removeCollider(collider, false)
- getMutableState(RapierWorldState)[world.id].Colliders[entity].set(none)
+ world.Colliders.delete(entity)
}
function setTrigger(world: PhysicsWorld, entity: Entity, isTrigger: boolean) {
- const collider = world.Colliders[entity]
+ const collider = world.Colliders.get(entity)
if (!collider) return
collider.setSensor(isTrigger)
const colliderComponent = getComponent(entity, ColliderComponent)
@@ -571,32 +571,32 @@ function setTrigger(world: PhysicsWorld, entity: Entity, isTrigger: boolean) {
}
function setFriction(world: PhysicsWorld, entity: Entity, friction: number) {
- const collider = world.Colliders[entity]
+ const collider = world.Colliders.get(entity)
if (!collider) return
collider.setFriction(friction)
}
function setRestitution(world: PhysicsWorld, entity: Entity, restitution: number) {
- const collider = world.Colliders[entity]
+ const collider = world.Colliders.get(entity)
if (!collider) return
collider.setRestitution(restitution)
}
function setMass(world: PhysicsWorld, entity: Entity, mass: number) {
- const collider = world.Colliders[entity]
+ const collider = world.Colliders.get(entity)
if (!collider) return
collider.setMass(mass)
}
function setMassCenter(world: PhysicsWorld, entity: Entity, massCenter: Vector3) {
- const collider = world.Colliders[entity]
+ const collider = world.Colliders.get(entity)
if (!collider) return
/** @todo */
// collider.setMassProperties(massCenter, collider.mass())
}
function setCollisionLayer(world: PhysicsWorld, entity: Entity, collisionLayer: InteractionGroups) {
- const collider = world.Colliders[entity]
+ const collider = world.Colliders.get(entity)
if (!collider) return
const colliderComponent = getComponent(entity, ColliderComponent)
const _collisionLayer = hasComponent(entity, TriggerComponent)
@@ -606,7 +606,7 @@ function setCollisionLayer(world: PhysicsWorld, entity: Entity, collisionLayer:
}
function setCollisionMask(world: PhysicsWorld, entity: Entity, collisionMask: InteractionGroups) {
- const collider = world.Colliders[entity]
+ const collider = world.Colliders.get(entity)
if (!collider) return
const colliderComponent = getComponent(entity, ColliderComponent)
const collisionLayer = hasComponent(entity, TriggerComponent)
@@ -616,13 +616,13 @@ function setCollisionMask(world: PhysicsWorld, entity: Entity, collisionMask: In
}
function getShape(world: PhysicsWorld, entity: Entity): Shape | undefined {
- const collider = world.Colliders[entity]
+ const collider = world.Colliders.get(entity)
if (!collider) return
return RapierShapeToString[collider.shape.type]
}
function removeCollidersFromRigidBody(entity: Entity, world: PhysicsWorld) {
- const rigidBody = world.Rigidbodies[entity]
+ const rigidBody = world.Rigidbodies.get(entity)
if (!rigidBody) return
const numColliders = rigidBody.numColliders()
for (let index = 0; index < numColliders; index++) {
@@ -648,21 +648,21 @@ function createCharacterController(
if (autoStep) characterController.enableAutostep(autoStep.maxHeight, autoStep.minWidth, autoStep.stepOverDynamic)
if (enableSnapToGround) characterController.enableSnapToGround(enableSnapToGround)
else characterController.disableSnapToGround()
- getMutableState(RapierWorldState)[world.id].Controllers[entity].set(characterController)
+ world.Controllers.set(entity, characterController)
}
function removeCharacterController(world: PhysicsWorld, entity: Entity) {
- const controller = world.Controllers[entity]
+ const controller = world.Controllers.get(entity)
if (!controller) return
world.removeCharacterController(controller)
- getMutableState(RapierWorldState)[world.id].Controllers[entity].set(none)
+ world.Controllers.delete(entity)
}
/**
* @deprecated - will be populated on AvatarControllerComponent
*/
function getControllerOffset(world: PhysicsWorld, entity: Entity) {
- const controller = world.Controllers[entity]
+ const controller = world.Controllers.get(entity)
if (!controller) return 0
return controller.offset()
}
@@ -677,9 +677,9 @@ function computeColliderMovement(
filterGroups?: InteractionGroups,
filterPredicate?: (collider: Collider) => boolean
) {
- const controller = world.Controllers[entity]
+ const controller = world.Controllers.get(entity)
if (!controller) return
- const collider = world.Colliders[colliderEntity]
+ const collider = world.Colliders.get(colliderEntity)
if (!collider) return
controller.computeColliderMovement(
collider,
@@ -691,7 +691,7 @@ function computeColliderMovement(
}
function getComputedMovement(world: PhysicsWorld, entity: Entity, out: Vector3) {
- const controller = world.Controllers[entity]
+ const controller = world.Controllers.get(entity)
if (!controller) return out.set(0, 0, 0)
return out.copy(controller.computedMovement() as Vector3)
}
@@ -732,8 +732,8 @@ function castRay(world: PhysicsWorld, raycastQuery: RaycastArgs, filterPredicate
const groups = raycastQuery.groups
const flags = raycastQuery.flags
- const excludeCollider = raycastQuery.excludeCollider && world.Colliders[raycastQuery.excludeCollider]
- const excludeRigidBody = raycastQuery.excludeRigidBody && world.Rigidbodies[raycastQuery.excludeRigidBody]
+ const excludeCollider = raycastQuery.excludeCollider && world.Colliders.get(raycastQuery.excludeCollider)
+ const excludeRigidBody = raycastQuery.excludeRigidBody && world.Rigidbodies.get(raycastQuery.excludeRigidBody)
const hits = [] as RaycastHit[]
const hitWithNormal = world.castRayAndGetNormal(
diff --git a/packages/spatial/src/physics/components/ColliderComponent.test.ts b/packages/spatial/src/physics/components/ColliderComponent.test.ts
index 9809199ea0..2eae254445 100644
--- a/packages/spatial/src/physics/components/ColliderComponent.test.ts
+++ b/packages/spatial/src/physics/components/ColliderComponent.test.ts
@@ -1,406 +1,406 @@
-// /*
-// CPAL-1.0 License
-
-// The contents of this file are subject to the Common Public Attribution License
-// Version 1.0. (the "License"); you may not use this file except in compliance
-// with the License. You may obtain a copy of the License at
-// https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE.
-// The License is based on the Mozilla Public License Version 1.1, but Sections 14
-// and 15 have been added to cover use of software over a computer network and
-// provide for limited attribution for the Original Developer. In addition,
-// Exhibit A has been modified to be consistent with Exhibit B.
-
-// Software distributed under the License is distributed on an "AS IS" basis,
-// WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the
-// specific language governing rights and limitations under the License.
-
-// The Original Code is Ethereal Engine.
-
-// The Original Developer is the Initial Developer. The Initial Developer of the
-// Original Code is the Ethereal Engine team.
-
-// All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023
-// Ethereal Engine. All Rights Reserved.
-// */
-
-// import assert from 'assert'
-
-// import {
-// Entity,
-// UUIDComponent,
-// UndefinedEntity,
-// createEntity,
-// destroyEngine,
-// getComponent,
-// removeComponent,
-// removeEntity,
-// serializeComponent,
-// setComponent
-// } from '@etherealengine/ecs'
-
-// import { createEngine } from '@etherealengine/ecs/src/Engine'
-// import { Vector3 } from 'three'
-// import { TransformComponent } from '../../SpatialModule'
-// import { SceneComponent } from '../../renderer/components/SceneComponents'
-// import { EntityTreeComponent, getAncestorWithComponent } from '../../transform/components/EntityTree'
-// import { Physics, PhysicsWorld } from '../classes/Physics'
-// import { assertVecAllApproxNotEq, assertVecApproxEq } from '../classes/Physics.test'
-// import { CollisionGroups, DefaultCollisionMask } from '../enums/CollisionGroups'
-// import { BodyTypes, Shapes } from '../types/PhysicsTypes'
-// import { ColliderComponent } from './ColliderComponent'
-// import { RigidBodyComponent } from './RigidBodyComponent'
-// import { TriggerComponent } from './TriggerComponent'
-
-// export const ColliderComponentDefaults = {
-// // also used in TriggerComponent.test.ts
-// shape: Shapes.Box,
-// mass: 1,
-// massCenter: new Vector3(),
-// friction: 0.5,
-// restitution: 0.5,
-// collisionLayer: CollisionGroups.Default,
-// collisionMask: DefaultCollisionMask
-// }
-
-// export function assertColliderComponentEquals(data, expected, testShape = true) {
-// // also used in TriggerComponent.test.ts
-// testShape && assert.equal(data.shape.type, expected.shape.type)
-// assert.equal(data.mass, expected.mass)
-// assert.deepEqual(data.massCenter, expected.massCenter)
-// assert.equal(data.friction, expected.friction)
-// assert.equal(data.restitution, expected.restitution)
-// assert.equal(data.collisionLayer, expected.collisionLayer)
-// assert.equal(data.collisionMask, expected.collisionMask)
-// }
-
-// function getLayerFromCollisionGroups(groups: number): number {
-// return (groups & 0xffff_0000) >> 16
-// }
-// function getMaskFromCollisionGroups(groups: number): number {
-// return groups & 0x0000_ffff
-// }
-
-// describe('ColliderComponent', () => {
-// describe('general functionality', () => {
-// let entity = UndefinedEntity
-// let physicsWorld: PhysicsWorld
-// let physicsWorldEntity = UndefinedEntity
-
-// beforeEach(async () => {
-// createEngine()
-// await Physics.load()
-// physicsWorldEntity = createEntity()
-// setComponent(physicsWorldEntity, UUIDComponent, UUIDComponent.generateUUID())
-// physicsWorld = Physics.createWorld(getComponent(physicsWorldEntity, UUIDComponent))
-// setComponent(physicsWorldEntity, SceneComponent)
-// setComponent(physicsWorldEntity, TransformComponent)
-// setComponent(physicsWorldEntity, EntityTreeComponent)
-// entity = createEntity()
-// setComponent(entity, EntityTreeComponent, { parentEntity: physicsWorldEntity })
-// })
-
-// afterEach(() => {
-// removeEntity(entity)
-// return destroyEngine()
-// })
-
-// it('should add collider to rigidbody', () => {
-// setComponent(entity, TransformComponent)
-// setComponent(entity, RigidBodyComponent, { type: BodyTypes.Fixed })
-// setComponent(entity, ColliderComponent)
-
-// const body = physicsWorld.Rigidbodies.get(entity)!
-// const collider = physicsWorld.Colliders.get(entity)!
-
-// assert.equal(body.numColliders(), 1)
-// assert(collider)
-// assert.equal(collider, body.collider(0))
-// })
-
-// it('should remove collider from rigidbody', () => {
-// setComponent(entity, TransformComponent)
-// setComponent(entity, RigidBodyComponent, { type: BodyTypes.Fixed })
-// setComponent(entity, ColliderComponent)
-
-// const body = physicsWorld.Rigidbodies.get(entity)!
-// const collider = physicsWorld.Colliders.get(entity)!
-
-// assert.equal(body.numColliders(), 1)
-// assert(collider)
-// assert.equal(collider, body.collider(0))
-
-// removeComponent(entity, ColliderComponent)
-
-// assert.equal(body.numColliders(), 0)
-// })
-
-// it('should add trigger collider', () => {
-// setComponent(entity, TransformComponent)
-
-// setComponent(entity, RigidBodyComponent, { type: BodyTypes.Fixed })
-// setComponent(entity, TriggerComponent)
-// setComponent(entity, ColliderComponent)
-
-// const collider = physicsWorld.Colliders.get(entity)!
-// assert.equal(collider!.isSensor(), true)
-// })
-// })
-
-// describe('IDs', () => {
-// it('should initialize the ColliderComponent.name field with the expected value', () => {
-// assert.equal(ColliderComponent.name, 'ColliderComponent')
-// })
-// it('should initialize the ColliderComponent.jsonID field with the expected value', () => {
-// assert.equal(ColliderComponent.jsonID, 'EE_collider')
-// })
-// })
-
-// describe('onInit', () => {
-// let testEntity = UndefinedEntity
-
-// beforeEach(async () => {
-// createEngine()
-// testEntity = createEntity()
-// setComponent(testEntity, ColliderComponent)
-// })
-
-// afterEach(() => {
-// removeEntity(testEntity)
-// return destroyEngine()
-// })
-
-// it('should initialize the component with the expected default values', () => {
-// const data = getComponent(testEntity, ColliderComponent)
-// assertColliderComponentEquals(data, ColliderComponentDefaults)
-// })
-// }) // << onInit
-
-// describe('onSet', () => {
-// let testEntity = UndefinedEntity
-
-// beforeEach(async () => {
-// createEngine()
-// testEntity = createEntity()
-// setComponent(testEntity, ColliderComponent)
-// })
-
-// afterEach(() => {
-// removeEntity(testEntity)
-// return destroyEngine()
-// })
-
-// it('should change the values of an initialized ColliderComponent', () => {
-// const Expected = {
-// shape: Shapes.Sphere,
-// mass: 2,
-// massCenter: new Vector3(1, 2, 3),
-// friction: 4.0,
-// restitution: 5.0,
-// collisionLayer: CollisionGroups.Ground,
-// collisionMask: CollisionGroups.Avatars | CollisionGroups.Trigger
-// }
-// const before = getComponent(testEntity, ColliderComponent)
-// assertColliderComponentEquals(before, ColliderComponentDefaults)
-// setComponent(testEntity, ColliderComponent, Expected)
-
-// const data = getComponent(testEntity, ColliderComponent)
-// assertColliderComponentEquals(data, Expected)
-// })
-
-// it('should not change values of an initialized ColliderComponent when the data passed had incorrect types', () => {
-// const Incorrect = {
-// shape: 1,
-// mass: 'mass.incorrect',
-// massCenter: 2,
-// friction: 'friction.incorrect',
-// restitution: 'restitution.incorrect',
-// collisionLayer: 'collisionLayer.incorrect',
-// collisionMask: 'trigger.incorrect'
-// }
-// const before = getComponent(testEntity, ColliderComponent)
-// assertColliderComponentEquals(before, ColliderComponentDefaults)
-
-// // @ts-ignore
-// setComponent(testEntity, ColliderComponent, Incorrect)
-// const data = getComponent(testEntity, ColliderComponent)
-// assertColliderComponentEquals(data, ColliderComponentDefaults)
-// })
-// }) // << onSet
-
-// describe('toJSON', () => {
-// let testEntity = UndefinedEntity
-
-// beforeEach(async () => {
-// createEngine()
-// await Physics.load()
-// testEntity = createEntity()
-// setComponent(testEntity, ColliderComponent)
-// })
-
-// afterEach(() => {
-// removeEntity(testEntity)
-// return destroyEngine()
-// })
-
-// it("should serialize the component's data correctly", () => {
-// const json = serializeComponent(testEntity, ColliderComponent)
-// assert.deepEqual(json, ColliderComponentDefaults)
-// })
-// }) // << toJson
-
-// describe('reactor', () => {
-// let testEntity = UndefinedEntity
-// let parentEntity = UndefinedEntity
-// let physicsWorld: PhysicsWorld
-// let physicsWorldEntity = UndefinedEntity
-
-// function createValidAncestor(colliderData = ColliderComponentDefaults as any): Entity {
-// const result = createEntity()
-// setComponent(result, TransformComponent)
-// setComponent(result, ColliderComponent, colliderData)
-// setComponent(result, RigidBodyComponent)
-// return result
-// }
-
-// beforeEach(async () => {
-// createEngine()
-// await Physics.load()
-// physicsWorldEntity = createEntity()
-// setComponent(physicsWorldEntity, UUIDComponent, UUIDComponent.generateUUID())
-// physicsWorld = Physics.createWorld(getComponent(physicsWorldEntity, UUIDComponent))
-// setComponent(physicsWorldEntity, SceneComponent)
-// setComponent(physicsWorldEntity, TransformComponent)
-// setComponent(physicsWorldEntity, EntityTreeComponent)
-// physicsWorld!.timestep = 1 / 60
-
-// parentEntity = createValidAncestor()
-// testEntity = createEntity()
-// setComponent(parentEntity, EntityTreeComponent, { parentEntity: physicsWorldEntity })
-// setComponent(testEntity, EntityTreeComponent, { parentEntity: parentEntity })
-// setComponent(testEntity, TransformComponent)
-// setComponent(testEntity, RigidBodyComponent)
-// setComponent(testEntity, ColliderComponent)
-// })
-
-// afterEach(() => {
-// Physics.destroyWorld(physicsWorld.id)
-// removeEntity(testEntity)
-// return destroyEngine()
-// })
-
-// describe('should attach and/or remove a collider to the physicsWorld based on the entity and its closest ancestor with a RigidBodyComponent ...', () => {
-// it("... when the shape of the entity's collider changes", () => {
-// assert.ok(ColliderComponent.reactorMap.get(testEntity)!.isRunning)
-// const beforeCollider = physicsWorld.Colliders.get(testEntity)
-// assert.ok(beforeCollider)
-// const before = beforeCollider.shape
-// assert.equal(getComponent(testEntity, ColliderComponent).shape, ColliderComponentDefaults.shape)
-
-// setComponent(testEntity, ColliderComponent, { shape: Shapes.Sphere })
-// assert.notEqual(getComponent(testEntity, ColliderComponent).shape, ColliderComponentDefaults.shape)
-// const after1Collider = physicsWorld.Colliders.get(testEntity)!
-// const after1 = after1Collider.shape
-// assert.notEqual(beforeCollider.handle, after1Collider.handle)
-// assert.notDeepEqual(after1, before)
-
-// removeComponent(testEntity, ColliderComponent)
-// assert.notEqual(getComponent(testEntity, ColliderComponent)?.shape, ColliderComponentDefaults.shape)
-// const after2Collider = physicsWorld.Colliders.get(testEntity)!
-// assert.equal(after2Collider, undefined)
-// })
-
-// it("... when the scale of the entity's transform changes", () => {
-// assert.ok(ColliderComponent.reactorMap.get(testEntity)!.isRunning)
-// const TransformScaleDefault = new Vector3(1, 1, 1)
-// const Expected = new Vector3(42, 42, 42)
-// const beforeCollider = physicsWorld.Colliders.get(testEntity)
-// assert.ok(beforeCollider)
-// const before = getComponent(testEntity, TransformComponent).scale.clone()
-// assertVecApproxEq(before, TransformScaleDefault, 3)
-
-// // Apply and check on changes
-// setComponent(testEntity, TransformComponent, { scale: Expected })
-// const after1 = getComponent(testEntity, TransformComponent).scale.clone()
-// assertVecAllApproxNotEq(before, after1, 3)
-
-// // Apply and check on component removal
-// removeComponent(testEntity, ColliderComponent)
-// const after2 = getComponent(testEntity, TransformComponent).scale.clone()
-// assert.notEqual(after1, after2)
-// const afterCollider = physicsWorld.Colliders.get(testEntity)
-// assert.equal(afterCollider, undefined)
-// })
-
-// it('... when the closest ancestor to the entity, with a RigidBodyComponent, changes', () => {
-// assert.ok(ColliderComponent.reactorMap.get(testEntity)!.isRunning)
-// const newParent = createValidAncestor()
-// assert.notEqual(parentEntity, newParent)
-
-// removeComponent(testEntity, EntityTreeComponent)
-// setComponent(testEntity, EntityTreeComponent, { parentEntity: newParent })
-// const ancestor = getAncestorWithComponent(
-// testEntity,
-// RigidBodyComponent,
-// /*closest*/ true,
-// /*includeSelf*/ false
-// )
-// assert.equal(ancestor, newParent)
-// })
-// })
-
-// it('should set the mass of the API data based on the component.mass.value when it changes', () => {
-// assert.ok(ColliderComponent.reactorMap.get(testEntity)!.isRunning)
-// const Expected = 42
-// const before = physicsWorld.Colliders.get(testEntity)!.mass()
-// setComponent(testEntity, ColliderComponent, { mass: Expected })
-// const after = physicsWorld.Colliders.get(testEntity)!.mass()
-// assert.notEqual(before, after, 'Before and After should not be equal')
-// assert.notEqual(before, Expected, 'Before and Expected should not be equal')
-// assert.equal(after, Expected, 'After and Expected should be equal')
-// })
-
-// it('should set the friction of the API data based on the component.friction.value when it changes', () => {
-// assert.ok(ColliderComponent.reactorMap.get(testEntity)!.isRunning)
-// const Expected = 42
-// const before = physicsWorld.Colliders.get(testEntity)!.friction()
-// setComponent(testEntity, ColliderComponent, { friction: Expected })
-// const after = physicsWorld.Colliders.get(testEntity)!.friction()
-// assert.notEqual(before, after, 'Before and After should not be equal')
-// assert.notEqual(before, Expected, 'Before and Expected should not be equal')
-// assert.equal(after, Expected, 'After and Expected should be equal')
-// })
-
-// it('should set the restitution of the API data based on the component.restitution.value when it changes', () => {
-// assert.ok(ColliderComponent.reactorMap.get(testEntity)!.isRunning)
-// const Expected = 42
-// const before = physicsWorld.Colliders.get(testEntity)!.restitution()
-// setComponent(testEntity, ColliderComponent, { restitution: Expected })
-// const after = physicsWorld.Colliders.get(testEntity)!.restitution()
-// assert.notEqual(before, after, 'Before and After should not be equal')
-// assert.notEqual(before, Expected, 'Before and Expected should not be equal')
-// assert.equal(after, Expected, 'After and Expected should be equal')
-// })
-
-// it('should set the collisionLayer of the API data based on the component.collisionLayer.value when it changes', () => {
-// assert.ok(ColliderComponent.reactorMap.get(testEntity)!.isRunning)
-// const Expected = CollisionGroups.Avatars
-// const before = getLayerFromCollisionGroups(physicsWorld.Colliders.get(testEntity)!.collisionGroups())
-// setComponent(testEntity, ColliderComponent, { collisionLayer: Expected })
-// const after = getLayerFromCollisionGroups(physicsWorld.Colliders.get(testEntity)!.collisionGroups())
-// assert.notEqual(before, after, 'Before and After should not be equal')
-// assert.notEqual(before, Expected, 'Before and Expected should not be equal')
-// assert.equal(after, Expected, 'After and Expected should be equal')
-// })
-
-// it('should set the collisionMask of the API data based on the component.collisionMask.value when it changes', () => {
-// assert.ok(ColliderComponent.reactorMap.get(testEntity)!.isRunning)
-// const Expected = CollisionGroups.Avatars
-// const before = getMaskFromCollisionGroups(physicsWorld.Colliders.get(testEntity)!.collisionGroups())
-// setComponent(testEntity, ColliderComponent, { collisionMask: Expected })
-// const after = getMaskFromCollisionGroups(physicsWorld.Colliders.get(testEntity)!.collisionGroups())
-// assert.notEqual(before, after, 'Before and After should not be equal')
-// assert.notEqual(before, Expected, 'Before and Expected should not be equal')
-// assert.equal(after, Expected, 'After and Expected should be equal')
-// })
-// }) // << reactor
-// })
+/*
+CPAL-1.0 License
+
+The contents of this file are subject to the Common Public Attribution License
+Version 1.0. (the "License"); you may not use this file except in compliance
+with the License. You may obtain a copy of the License at
+https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE.
+The License is based on the Mozilla Public License Version 1.1, but Sections 14
+and 15 have been added to cover use of software over a computer network and
+provide for limited attribution for the Original Developer. In addition,
+Exhibit A has been modified to be consistent with Exhibit B.
+
+Software distributed under the License is distributed on an "AS IS" basis,
+WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the
+specific language governing rights and limitations under the License.
+
+The Original Code is Ethereal Engine.
+
+The Original Developer is the Initial Developer. The Initial Developer of the
+Original Code is the Ethereal Engine team.
+
+All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023
+Ethereal Engine. All Rights Reserved.
+*/
+
+import assert from 'assert'
+
+import {
+ Entity,
+ UUIDComponent,
+ UndefinedEntity,
+ createEntity,
+ destroyEngine,
+ getComponent,
+ removeComponent,
+ removeEntity,
+ serializeComponent,
+ setComponent
+} from '@etherealengine/ecs'
+
+import { createEngine } from '@etherealengine/ecs/src/Engine'
+import { Vector3 } from 'three'
+import { TransformComponent } from '../../SpatialModule'
+import { SceneComponent } from '../../renderer/components/SceneComponents'
+import { EntityTreeComponent, getAncestorWithComponent } from '../../transform/components/EntityTree'
+import { Physics, PhysicsWorld } from '../classes/Physics'
+import { assertVecAllApproxNotEq, assertVecApproxEq } from '../classes/Physics.test'
+import { CollisionGroups, DefaultCollisionMask } from '../enums/CollisionGroups'
+import { BodyTypes, Shapes } from '../types/PhysicsTypes'
+import { ColliderComponent } from './ColliderComponent'
+import { RigidBodyComponent } from './RigidBodyComponent'
+import { TriggerComponent } from './TriggerComponent'
+
+export const ColliderComponentDefaults = {
+ // also used in TriggerComponent.test.ts
+ shape: Shapes.Box,
+ mass: 1,
+ massCenter: new Vector3(),
+ friction: 0.5,
+ restitution: 0.5,
+ collisionLayer: CollisionGroups.Default,
+ collisionMask: DefaultCollisionMask
+}
+
+export function assertColliderComponentEquals(data, expected, testShape = true) {
+ // also used in TriggerComponent.test.ts
+ testShape && assert.equal(data.shape.type, expected.shape.type)
+ assert.equal(data.mass, expected.mass)
+ assert.deepEqual(data.massCenter, expected.massCenter)
+ assert.equal(data.friction, expected.friction)
+ assert.equal(data.restitution, expected.restitution)
+ assert.equal(data.collisionLayer, expected.collisionLayer)
+ assert.equal(data.collisionMask, expected.collisionMask)
+}
+
+function getLayerFromCollisionGroups(groups: number): number {
+ return (groups & 0xffff_0000) >> 16
+}
+function getMaskFromCollisionGroups(groups: number): number {
+ return groups & 0x0000_ffff
+}
+
+describe('ColliderComponent', () => {
+ describe('general functionality', () => {
+ let entity = UndefinedEntity
+ let physicsWorld: PhysicsWorld
+ let physicsWorldEntity = UndefinedEntity
+
+ beforeEach(async () => {
+ createEngine()
+ await Physics.load()
+ physicsWorldEntity = createEntity()
+ setComponent(physicsWorldEntity, UUIDComponent, UUIDComponent.generateUUID())
+ physicsWorld = Physics.createWorld(getComponent(physicsWorldEntity, UUIDComponent))
+ setComponent(physicsWorldEntity, SceneComponent)
+ setComponent(physicsWorldEntity, TransformComponent)
+ setComponent(physicsWorldEntity, EntityTreeComponent)
+ entity = createEntity()
+ setComponent(entity, EntityTreeComponent, { parentEntity: physicsWorldEntity })
+ })
+
+ afterEach(() => {
+ removeEntity(entity)
+ return destroyEngine()
+ })
+
+ it('should add collider to rigidbody', () => {
+ setComponent(entity, TransformComponent)
+ setComponent(entity, RigidBodyComponent, { type: BodyTypes.Fixed })
+ setComponent(entity, ColliderComponent)
+
+ const body = physicsWorld.Rigidbodies.get(entity)!
+ const collider = physicsWorld.Colliders.get(entity)!
+
+ assert.equal(body.numColliders(), 1)
+ assert(collider)
+ assert.equal(collider, body.collider(0))
+ })
+
+ it('should remove collider from rigidbody', () => {
+ setComponent(entity, TransformComponent)
+ setComponent(entity, RigidBodyComponent, { type: BodyTypes.Fixed })
+ setComponent(entity, ColliderComponent)
+
+ const body = physicsWorld.Rigidbodies.get(entity)!
+ const collider = physicsWorld.Colliders.get(entity)!
+
+ assert.equal(body.numColliders(), 1)
+ assert(collider)
+ assert.equal(collider, body.collider(0))
+
+ removeComponent(entity, ColliderComponent)
+
+ assert.equal(body.numColliders(), 0)
+ })
+
+ it('should add trigger collider', () => {
+ setComponent(entity, TransformComponent)
+
+ setComponent(entity, RigidBodyComponent, { type: BodyTypes.Fixed })
+ setComponent(entity, TriggerComponent)
+ setComponent(entity, ColliderComponent)
+
+ const collider = physicsWorld.Colliders.get(entity)!
+ assert.equal(collider!.isSensor(), true)
+ })
+ })
+
+ describe('IDs', () => {
+ it('should initialize the ColliderComponent.name field with the expected value', () => {
+ assert.equal(ColliderComponent.name, 'ColliderComponent')
+ })
+ it('should initialize the ColliderComponent.jsonID field with the expected value', () => {
+ assert.equal(ColliderComponent.jsonID, 'EE_collider')
+ })
+ })
+
+ describe('onInit', () => {
+ let testEntity = UndefinedEntity
+
+ beforeEach(async () => {
+ createEngine()
+ testEntity = createEntity()
+ setComponent(testEntity, ColliderComponent)
+ })
+
+ afterEach(() => {
+ removeEntity(testEntity)
+ return destroyEngine()
+ })
+
+ it('should initialize the component with the expected default values', () => {
+ const data = getComponent(testEntity, ColliderComponent)
+ assertColliderComponentEquals(data, ColliderComponentDefaults)
+ })
+ }) // << onInit
+
+ describe('onSet', () => {
+ let testEntity = UndefinedEntity
+
+ beforeEach(async () => {
+ createEngine()
+ testEntity = createEntity()
+ setComponent(testEntity, ColliderComponent)
+ })
+
+ afterEach(() => {
+ removeEntity(testEntity)
+ return destroyEngine()
+ })
+
+ it('should change the values of an initialized ColliderComponent', () => {
+ const Expected = {
+ shape: Shapes.Sphere,
+ mass: 2,
+ massCenter: new Vector3(1, 2, 3),
+ friction: 4.0,
+ restitution: 5.0,
+ collisionLayer: CollisionGroups.Ground,
+ collisionMask: CollisionGroups.Avatars | CollisionGroups.Trigger
+ }
+ const before = getComponent(testEntity, ColliderComponent)
+ assertColliderComponentEquals(before, ColliderComponentDefaults)
+ setComponent(testEntity, ColliderComponent, Expected)
+
+ const data = getComponent(testEntity, ColliderComponent)
+ assertColliderComponentEquals(data, Expected)
+ })
+
+ it('should not change values of an initialized ColliderComponent when the data passed had incorrect types', () => {
+ const Incorrect = {
+ shape: 1,
+ mass: 'mass.incorrect',
+ massCenter: 2,
+ friction: 'friction.incorrect',
+ restitution: 'restitution.incorrect',
+ collisionLayer: 'collisionLayer.incorrect',
+ collisionMask: 'trigger.incorrect'
+ }
+ const before = getComponent(testEntity, ColliderComponent)
+ assertColliderComponentEquals(before, ColliderComponentDefaults)
+
+ // @ts-ignore
+ setComponent(testEntity, ColliderComponent, Incorrect)
+ const data = getComponent(testEntity, ColliderComponent)
+ assertColliderComponentEquals(data, ColliderComponentDefaults)
+ })
+ }) // << onSet
+
+ describe('toJSON', () => {
+ let testEntity = UndefinedEntity
+
+ beforeEach(async () => {
+ createEngine()
+ await Physics.load()
+ testEntity = createEntity()
+ setComponent(testEntity, ColliderComponent)
+ })
+
+ afterEach(() => {
+ removeEntity(testEntity)
+ return destroyEngine()
+ })
+
+ it("should serialize the component's data correctly", () => {
+ const json = serializeComponent(testEntity, ColliderComponent)
+ assert.deepEqual(json, ColliderComponentDefaults)
+ })
+ }) // << toJson
+
+ describe('reactor', () => {
+ let testEntity = UndefinedEntity
+ let parentEntity = UndefinedEntity
+ let physicsWorld: PhysicsWorld
+ let physicsWorldEntity = UndefinedEntity
+
+ function createValidAncestor(colliderData = ColliderComponentDefaults as any): Entity {
+ const result = createEntity()
+ setComponent(result, TransformComponent)
+ setComponent(result, ColliderComponent, colliderData)
+ setComponent(result, RigidBodyComponent)
+ return result
+ }
+
+ beforeEach(async () => {
+ createEngine()
+ await Physics.load()
+ physicsWorldEntity = createEntity()
+ setComponent(physicsWorldEntity, UUIDComponent, UUIDComponent.generateUUID())
+ physicsWorld = Physics.createWorld(getComponent(physicsWorldEntity, UUIDComponent))
+ setComponent(physicsWorldEntity, SceneComponent)
+ setComponent(physicsWorldEntity, TransformComponent)
+ setComponent(physicsWorldEntity, EntityTreeComponent)
+ physicsWorld!.timestep = 1 / 60
+
+ parentEntity = createValidAncestor()
+ testEntity = createEntity()
+ setComponent(parentEntity, EntityTreeComponent, { parentEntity: physicsWorldEntity })
+ setComponent(testEntity, EntityTreeComponent, { parentEntity: parentEntity })
+ setComponent(testEntity, TransformComponent)
+ setComponent(testEntity, RigidBodyComponent)
+ setComponent(testEntity, ColliderComponent)
+ })
+
+ afterEach(() => {
+ Physics.destroyWorld(physicsWorld.id)
+ removeEntity(testEntity)
+ return destroyEngine()
+ })
+
+ describe('should attach and/or remove a collider to the physicsWorld based on the entity and its closest ancestor with a RigidBodyComponent ...', () => {
+ it("... when the shape of the entity's collider changes", () => {
+ assert.ok(ColliderComponent.reactorMap.get(testEntity)!.isRunning)
+ const beforeCollider = physicsWorld.Colliders.get(testEntity)
+ assert.ok(beforeCollider)
+ const before = beforeCollider.shape
+ assert.equal(getComponent(testEntity, ColliderComponent).shape, ColliderComponentDefaults.shape)
+
+ setComponent(testEntity, ColliderComponent, { shape: Shapes.Sphere })
+ assert.notEqual(getComponent(testEntity, ColliderComponent).shape, ColliderComponentDefaults.shape)
+ const after1Collider = physicsWorld.Colliders.get(testEntity)!
+ const after1 = after1Collider.shape
+ assert.notEqual(beforeCollider.handle, after1Collider.handle)
+ assert.notDeepEqual(after1, before)
+
+ removeComponent(testEntity, ColliderComponent)
+ assert.notEqual(getComponent(testEntity, ColliderComponent)?.shape, ColliderComponentDefaults.shape)
+ const after2Collider = physicsWorld.Colliders.get(testEntity)!
+ assert.equal(after2Collider, undefined)
+ })
+
+ it("... when the scale of the entity's transform changes", () => {
+ assert.ok(ColliderComponent.reactorMap.get(testEntity)!.isRunning)
+ const TransformScaleDefault = new Vector3(1, 1, 1)
+ const Expected = new Vector3(42, 42, 42)
+ const beforeCollider = physicsWorld.Colliders.get(testEntity)
+ assert.ok(beforeCollider)
+ const before = getComponent(testEntity, TransformComponent).scale.clone()
+ assertVecApproxEq(before, TransformScaleDefault, 3)
+
+ // Apply and check on changes
+ setComponent(testEntity, TransformComponent, { scale: Expected })
+ const after1 = getComponent(testEntity, TransformComponent).scale.clone()
+ assertVecAllApproxNotEq(before, after1, 3)
+
+ // Apply and check on component removal
+ removeComponent(testEntity, ColliderComponent)
+ const after2 = getComponent(testEntity, TransformComponent).scale.clone()
+ assert.notEqual(after1, after2)
+ const afterCollider = physicsWorld.Colliders.get(testEntity)
+ assert.equal(afterCollider, undefined)
+ })
+
+ it('... when the closest ancestor to the entity, with a RigidBodyComponent, changes', () => {
+ assert.ok(ColliderComponent.reactorMap.get(testEntity)!.isRunning)
+ const newParent = createValidAncestor()
+ assert.notEqual(parentEntity, newParent)
+
+ removeComponent(testEntity, EntityTreeComponent)
+ setComponent(testEntity, EntityTreeComponent, { parentEntity: newParent })
+ const ancestor = getAncestorWithComponent(
+ testEntity,
+ RigidBodyComponent,
+ /*closest*/ true,
+ /*includeSelf*/ false
+ )
+ assert.equal(ancestor, newParent)
+ })
+ })
+
+ it('should set the mass of the API data based on the component.mass.value when it changes', () => {
+ assert.ok(ColliderComponent.reactorMap.get(testEntity)!.isRunning)
+ const Expected = 42
+ const before = physicsWorld.Colliders.get(testEntity)!.mass()
+ setComponent(testEntity, ColliderComponent, { mass: Expected })
+ const after = physicsWorld.Colliders.get(testEntity)!.mass()
+ assert.notEqual(before, after, 'Before and After should not be equal')
+ assert.notEqual(before, Expected, 'Before and Expected should not be equal')
+ assert.equal(after, Expected, 'After and Expected should be equal')
+ })
+
+ it('should set the friction of the API data based on the component.friction.value when it changes', () => {
+ assert.ok(ColliderComponent.reactorMap.get(testEntity)!.isRunning)
+ const Expected = 42
+ const before = physicsWorld.Colliders.get(testEntity)!.friction()
+ setComponent(testEntity, ColliderComponent, { friction: Expected })
+ const after = physicsWorld.Colliders.get(testEntity)!.friction()
+ assert.notEqual(before, after, 'Before and After should not be equal')
+ assert.notEqual(before, Expected, 'Before and Expected should not be equal')
+ assert.equal(after, Expected, 'After and Expected should be equal')
+ })
+
+ it('should set the restitution of the API data based on the component.restitution.value when it changes', () => {
+ assert.ok(ColliderComponent.reactorMap.get(testEntity)!.isRunning)
+ const Expected = 42
+ const before = physicsWorld.Colliders.get(testEntity)!.restitution()
+ setComponent(testEntity, ColliderComponent, { restitution: Expected })
+ const after = physicsWorld.Colliders.get(testEntity)!.restitution()
+ assert.notEqual(before, after, 'Before and After should not be equal')
+ assert.notEqual(before, Expected, 'Before and Expected should not be equal')
+ assert.equal(after, Expected, 'After and Expected should be equal')
+ })
+
+ it('should set the collisionLayer of the API data based on the component.collisionLayer.value when it changes', () => {
+ assert.ok(ColliderComponent.reactorMap.get(testEntity)!.isRunning)
+ const Expected = CollisionGroups.Avatars
+ const before = getLayerFromCollisionGroups(physicsWorld.Colliders.get(testEntity)!.collisionGroups())
+ setComponent(testEntity, ColliderComponent, { collisionLayer: Expected })
+ const after = getLayerFromCollisionGroups(physicsWorld.Colliders.get(testEntity)!.collisionGroups())
+ assert.notEqual(before, after, 'Before and After should not be equal')
+ assert.notEqual(before, Expected, 'Before and Expected should not be equal')
+ assert.equal(after, Expected, 'After and Expected should be equal')
+ })
+
+ it('should set the collisionMask of the API data based on the component.collisionMask.value when it changes', () => {
+ assert.ok(ColliderComponent.reactorMap.get(testEntity)!.isRunning)
+ const Expected = CollisionGroups.Avatars
+ const before = getMaskFromCollisionGroups(physicsWorld.Colliders.get(testEntity)!.collisionGroups())
+ setComponent(testEntity, ColliderComponent, { collisionMask: Expected })
+ const after = getMaskFromCollisionGroups(physicsWorld.Colliders.get(testEntity)!.collisionGroups())
+ assert.notEqual(before, after, 'Before and After should not be equal')
+ assert.notEqual(before, Expected, 'Before and Expected should not be equal')
+ assert.equal(after, Expected, 'After and Expected should be equal')
+ })
+ }) // << reactor
+})
diff --git a/packages/spatial/src/physics/components/ColliderComponent.tsx b/packages/spatial/src/physics/components/ColliderComponent.tsx
index d77fd317cd..ad84c9889b 100644
--- a/packages/spatial/src/physics/components/ColliderComponent.tsx
+++ b/packages/spatial/src/physics/components/ColliderComponent.tsx
@@ -23,12 +23,12 @@ All portions of the code written by the Ethereal Engine team are Copyright © 20
Ethereal Engine. All Rights Reserved.
*/
-import { useEffect, useLayoutEffect } from 'react'
import { Vector3 } from 'three'
import { defineComponent, useComponent, useEntityContext, useOptionalComponent } from '@etherealengine/ecs'
import { useState } from '@etherealengine/hyperflux'
+import { useLayoutEffect } from 'react'
import { useAncestorWithComponent } from '../../transform/components/EntityTree'
import { TransformComponent } from '../../transform/components/TransformComponent'
import { Physics } from '../classes/Physics'
@@ -83,15 +83,16 @@ export const ColliderComponent = defineComponent({
const component = useComponent(entity, ColliderComponent)
const transform = useComponent(entity, TransformComponent)
const rigidbodyEntity = useAncestorWithComponent(entity, RigidBodyComponent)
+ const rigidbodyComponent = useOptionalComponent(rigidbodyEntity, RigidBodyComponent)
const physicsWorld = Physics.useWorld(entity)
const triggerComponent = useOptionalComponent(entity, TriggerComponent)
const hasCollider = useState(false)
- const physicsWorldRigidbody = Physics.useWorld(entity)?.Rigidbodies[entity]
- useEffect(() => {
- if (!rigidbodyEntity || !physicsWorld) return
+ useLayoutEffect(() => {
+ if (!rigidbodyComponent?.initialized?.value || !physicsWorld) return
const colliderDesc = Physics.createColliderDesc(physicsWorld, entity, rigidbodyEntity)
+
if (!colliderDesc) return
Physics.attachCollider(physicsWorld, colliderDesc, rigidbodyEntity, entity)
@@ -101,7 +102,7 @@ export const ColliderComponent = defineComponent({
Physics.removeCollider(physicsWorld, entity)
hasCollider.set(false)
}
- }, [physicsWorld, component.shape, rigidbodyEntity, !!physicsWorldRigidbody, transform.scale])
+ }, [physicsWorld, component.shape, !!rigidbodyComponent?.initialized?.value, transform.scale])
useLayoutEffect(() => {
if (!physicsWorld) return
@@ -132,7 +133,7 @@ export const ColliderComponent = defineComponent({
Physics.setCollisionMask(physicsWorld, entity, component.collisionMask.value)
}, [physicsWorld, component.collisionMask])
- useEffect(() => {
+ useLayoutEffect(() => {
if (!physicsWorld || !triggerComponent?.value || !hasCollider.value) return
Physics.setTrigger(physicsWorld, entity, true)
diff --git a/packages/spatial/src/physics/components/RigidBodyComponent.test.tsx b/packages/spatial/src/physics/components/RigidBodyComponent.test.tsx
index 368d08095e..f8ad4a9048 100644
--- a/packages/spatial/src/physics/components/RigidBodyComponent.test.tsx
+++ b/packages/spatial/src/physics/components/RigidBodyComponent.test.tsx
@@ -1,503 +1,503 @@
-// /*
-// CPAL-1.0 License
-
-// The contents of this file are subject to the Common Public Attribution License
-// Version 1.0. (the "License"); you may not use this file except in compliance
-// with the License. You may obtain a copy of the License at
-// https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE.
-// The License is based on the Mozilla Public License Version 1.1, but Sections 14
-// and 15 have been added to cover use of software over a computer network and
-// provide for limited attribution for the Original Developer. In addition,
-// Exhibit A has been modified to be consistent with Exhibit B.
-
-// Software distributed under the License is distributed on an "AS IS" basis,
-// WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the
-// specific language governing rights and limitations under the License.
-
-// The Original Code is Ethereal Engine.
-
-// The Original Developer is the Initial Developer. The Initial Developer of the
-// Original Code is the Ethereal Engine team.
-
-// All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023
-// Ethereal Engine. All Rights Reserved.
-// */
-
-// import { act, render } from '@testing-library/react'
-// import assert from 'assert'
-
-// import { RigidBodyType } from '@dimforge/rapier3d-compat'
-// import {
-// SystemDefinitions,
-// UUIDComponent,
-// UndefinedEntity,
-// createEngine,
-// createEntity,
-// destroyEngine,
-// getComponent,
-// hasComponent,
-// removeComponent,
-// removeEntity,
-// serializeComponent,
-// setComponent
-// } from '@etherealengine/ecs'
-// import React from 'react'
-// import { Vector3 } from 'three'
-// import { PhysicsSystem, TransformComponent } from '../../SpatialModule'
-// import { Vector3_Zero } from '../../common/constants/MathConstants'
-// import { SceneComponent } from '../../renderer/components/SceneComponents'
-// import { EntityTreeComponent } from '../../transform/components/EntityTree'
-// import { Physics, PhysicsWorld } from '../classes/Physics'
-// import {
-// assertFloatApproxEq,
-// assertFloatApproxNotEq,
-// assertVecAllApproxNotEq,
-// assertVecApproxEq
-// } from '../classes/Physics.test'
-// import { BodyTypes } from '../types/PhysicsTypes'
-// import { ColliderComponent } from './ColliderComponent'
-// import {
-// RigidBodyComponent,
-// RigidBodyDynamicTagComponent,
-// RigidBodyFixedTagComponent,
-// RigidBodyKinematicTagComponent,
-// getTagComponentForRigidBody
-// } from './RigidBodyComponent'
-
-// const RigidBodyComponentDefaults = {
-// type: BodyTypes.Fixed,
-// ccd: false,
-// allowRolling: true,
-// enabledRotations: [true, true, true] as [boolean, boolean, boolean],
-// canSleep: true,
-// gravityScale: 1,
-// previousPosition: 3,
-// previousRotation: 4,
-// position: 3,
-// rotation: 4,
-// targetKinematicPosition: 3,
-// targetKinematicRotation: 4,
-// linearVelocity: 3,
-// angularVelocity: 3,
-// targetKinematicLerpMultiplier: 0
-// }
-
-// export function assertArrayEqual(A: Array, B: Array, err = 'Arrays are not equal') {
-// assert.equal(A.length, B.length, err + ': Their lenght is not the same')
-// for (let id = 0; id < A.length && id < B.length; id++) {
-// assert.deepEqual(A[id], B[id], err + `: Their item[${id}] is not the same`)
-// }
-// }
-
-// export function assertArrayNotEqual(A: Array, B: Array, err = 'Arrays are equal') {
-// for (let id = 0; id < A.length && id < B.length; id++) {
-// assert.notDeepEqual(A[id], B[id], err)
-// }
-// }
-
-// export function assertRigidBodyComponentEqual(data, expected = RigidBodyComponentDefaults) {
-// assert.equal(data.type, expected.type)
-// assert.equal(data.ccd, expected.ccd)
-// assert.equal(data.allowRolling, expected.allowRolling)
-// assert.equal(data.enabledRotations.length, expected.enabledRotations.length)
-// assert.equal(data.enabledRotations[0], expected.enabledRotations[0])
-// assert.equal(data.enabledRotations[1], expected.enabledRotations[1])
-// assert.equal(data.enabledRotations[2], expected.enabledRotations[2])
-// assert.equal(data.canSleep, expected.canSleep)
-// assert.equal(data.gravityScale, expected.gravityScale)
-// /**
-// // @todo Not serialized by the component
-// assertVecApproxEq(data.previousPosition, expected.previousPosition, 3)
-// assertVecApproxEq(data.previousRotation, expected.previousRotation, 4)
-// assertVecApproxEq(data.position, expected.position, 3)
-// assertVecApproxEq(data.rotation, expected.rotation, 4)
-// assertVecApproxEq(data.targetKinematicPosition, expected.targetKinematicPosition, 3)
-// assertVecApproxEq(data.targetKinematicRotation, expected.targetKinematicRotation, 4)
-// assertVecApproxEq(data.linearVelocity, expected.linearVelocity, 3)
-// assertVecApproxEq(data.angularVelocity, expected.angularVelocity, 3)
-// assert.equal(data.targetKinematicLerpMultiplier, expected.targetKinematicLerpMultiplier)
-// */
-// }
-
-// describe('RigidBodyComponent', () => {
-// describe('IDs', () => {
-// it('should initialize the RigidBodyComponent.name field with the expected value', () => {
-// assert.equal(RigidBodyComponent.name, 'RigidBodyComponent')
-// })
-// it('should initialize the RigidBodyComponent.jsonID field with the expected value', () => {
-// assert.equal(RigidBodyComponent.jsonID, 'EE_rigidbody')
-// })
-// })
-
-// describe('onInit', () => {
-// let testEntity = UndefinedEntity
-
-// beforeEach(async () => {
-// createEngine()
-// await Physics.load()
-// testEntity = createEntity()
-// setComponent(testEntity, RigidBodyComponent)
-// })
-
-// afterEach(() => {
-// removeEntity(testEntity)
-// return destroyEngine()
-// })
-
-// it('should initialize the component with the expected default values', () => {
-// const data = getComponent(testEntity, RigidBodyComponent)
-// assertRigidBodyComponentEqual(data, RigidBodyComponentDefaults)
-// })
-// }) // << onInit
-
-// describe('onSet', () => {
-// let testEntity = UndefinedEntity
-
-// beforeEach(async () => {
-// createEngine()
-// await Physics.load()
-// testEntity = createEntity()
-// setComponent(testEntity, RigidBodyComponent)
-// })
-
-// afterEach(() => {
-// removeEntity(testEntity)
-// return destroyEngine()
-// })
-
-// it('should change the values of an initialized RigidBodyComponent', () => {
-// const Expected = {
-// type: BodyTypes.Dynamic,
-// ccd: true,
-// allowRolling: false,
-// canSleep: false,
-// gravityScale: 2,
-// enabledRotations: [false, false, false] as [boolean, boolean, boolean]
-// }
-// const before = getComponent(testEntity, RigidBodyComponent)
-// assertRigidBodyComponentEqual(before, RigidBodyComponentDefaults)
-
-// setComponent(testEntity, RigidBodyComponent, Expected)
-// const after = getComponent(testEntity, RigidBodyComponent)
-// assert.equal(after.type, Expected.type)
-// assert.equal(after.ccd, Expected.ccd)
-// assert.equal(after.allowRolling, Expected.allowRolling)
-// assert.equal(after.canSleep, Expected.canSleep)
-// assert.equal(after.gravityScale, Expected.gravityScale)
-// assert.equal(after.enabledRotations.length, Expected.enabledRotations.length)
-// assert.equal(after.enabledRotations[0], Expected.enabledRotations[0])
-// assert.equal(after.enabledRotations[1], Expected.enabledRotations[1])
-// assert.equal(after.enabledRotations[2], Expected.enabledRotations[2])
-// })
-
-// it('should not change values of an initialized RigidBodyComponent when the data passed had incorrect types', () => {
-// const Incorrect = {
-// type: 1,
-// ccd: 'ccd',
-// allowRolling: 2,
-// canSleep: 3,
-// gravityScale: false,
-// enabledRotations: [4, 5, 6]
-// }
-// const before = getComponent(testEntity, RigidBodyComponent)
-// assertRigidBodyComponentEqual(before, RigidBodyComponentDefaults)
-
-// // @ts-ignore Pass an incorrect type to setComponent
-// setComponent(testEntity, RigidBodyComponent, Incorrect)
-// const after = getComponent(testEntity, RigidBodyComponent)
-// assertRigidBodyComponentEqual(after, RigidBodyComponentDefaults)
-// })
-// }) // << onSet
-
-// describe('toJSON', () => {
-// let testEntity = UndefinedEntity
-
-// beforeEach(async () => {
-// createEngine()
-// await Physics.load()
-// testEntity = createEntity()
-// setComponent(testEntity, RigidBodyComponent)
-// })
-
-// afterEach(() => {
-// removeEntity(testEntity)
-// return destroyEngine()
-// })
-
-// it("should serialize the component's data correctly", () => {
-// const Expected = {
-// type: 'fixed',
-// ccd: false,
-// allowRolling: true,
-// enabledRotations: [true, true, true],
-// canSleep: true,
-// gravityScale: 1
-// }
-// const json = serializeComponent(testEntity, RigidBodyComponent)
-// assert.deepEqual(json, Expected)
-// })
-// }) // << toJSON
-
-// describe('reactor', () => {
-// let testEntity = UndefinedEntity
-// let physicsWorld: PhysicsWorld
-// let newPhysicsWorld: PhysicsWorld
-// let physicsWorldEntity = UndefinedEntity
-
-// beforeEach(async () => {
-// createEngine()
-// await Physics.load()
-// physicsWorldEntity = createEntity()
-// setComponent(physicsWorldEntity, UUIDComponent, UUIDComponent.generateUUID())
-// setComponent(physicsWorldEntity, SceneComponent)
-// setComponent(physicsWorldEntity, TransformComponent)
-// setComponent(physicsWorldEntity, EntityTreeComponent)
-// physicsWorld = Physics.createWorld(getComponent(physicsWorldEntity, UUIDComponent))
-// physicsWorld!.timestep = 1 / 60
-
-// testEntity = createEntity()
-// setComponent(testEntity, EntityTreeComponent, { parentEntity: physicsWorldEntity })
-// setComponent(testEntity, TransformComponent)
-// setComponent(testEntity, RigidBodyComponent)
-// setComponent(testEntity, ColliderComponent)
-// })
-
-// afterEach(() => {
-// Physics.destroyWorld(physicsWorld.id)
-// // if (newPhysicsWorld) Physics.destroyWorld(newPhysicsWorld.id)
-// removeEntity(testEntity)
-// return destroyEngine()
-// })
-
-// const physicsSystemExecute = SystemDefinitions.get(PhysicsSystem)!.execute
-
-// it('should create a RigidBody for the entity in the new physicsWorld when the world is changed', async () => {
-// assert.ok(RigidBodyComponent.reactorMap.get(testEntity)!.isRunning)
-// const before = physicsWorld.Rigidbodies.get(testEntity)!.handle
-// assert.ok(physicsWorld!.bodies.contains(before))
-
-// const newPhysicsEntity = createEntity()
-// setComponent(newPhysicsEntity, UUIDComponent, UUIDComponent.generateUUID())
-// setComponent(newPhysicsEntity, SceneComponent)
-// setComponent(newPhysicsEntity, TransformComponent)
-// setComponent(newPhysicsEntity, EntityTreeComponent)
-// newPhysicsWorld = Physics.createWorld(getComponent(newPhysicsEntity, UUIDComponent))
-// newPhysicsWorld!.timestep = 1 / 60
-
-// // Change the world
-// setComponent(testEntity, EntityTreeComponent, { parentEntity: newPhysicsEntity })
-
-// // Force react lifecycle to update Physics.useWorld
-// const { rerender, unmount } = render(<>>)
-// await act(() => rerender(<>>))
-
-// // Check the changes
-// RigidBodyComponent.reactorMap.get(testEntity)!.run() // Reactor is already running. But force-run it so changes are applied immediately
-// const after = newPhysicsWorld.Rigidbodies.get(testEntity)!.handle
-// assert.ok(newPhysicsWorld!.bodies.contains(after))
-// })
-
-// it('should set the correct RigidBody type on the API data when component.type changes', () => {
-// assert.ok(RigidBodyComponent.reactorMap.get(testEntity)!.isRunning)
-// setComponent(testEntity, RigidBodyComponent, { type: BodyTypes.Dynamic })
-// const one = physicsWorld.Rigidbodies.get(testEntity)!.bodyType()
-// assert.equal(one, RigidBodyType.Dynamic)
-// setComponent(testEntity, RigidBodyComponent, { type: BodyTypes.Fixed })
-// const two = physicsWorld.Rigidbodies.get(testEntity)!.bodyType()
-// assert.equal(two, RigidBodyType.Fixed)
-// setComponent(testEntity, RigidBodyComponent, { type: BodyTypes.Kinematic })
-// const three = physicsWorld.Rigidbodies.get(testEntity)!.bodyType()
-// assert.equal(three, RigidBodyType.KinematicPositionBased)
-// })
-
-// it('should set and remove a RigidBodyDynamicTagComponent on the entity when the component.type changes to dynamic', () => {
-// assert.ok(RigidBodyComponent.reactorMap.get(testEntity)!.isRunning)
-// const tag = RigidBodyDynamicTagComponent
-// removeComponent(testEntity, RigidBodyComponent)
-// assert.equal(hasComponent(testEntity, tag), false)
-// setComponent(testEntity, RigidBodyComponent, { type: BodyTypes.Dynamic })
-// assert.equal(hasComponent(testEntity, tag), true)
-// setComponent(testEntity, RigidBodyComponent, { type: BodyTypes.Fixed })
-// assert.equal(hasComponent(testEntity, tag), false)
-// })
-
-// it('should set and remove a RigidBodyFixedTagComponent on the entity when the component.type changes to fixed', () => {
-// assert.ok(RigidBodyComponent.reactorMap.get(testEntity)!.isRunning)
-// const tag = RigidBodyFixedTagComponent
-// removeComponent(testEntity, RigidBodyComponent)
-// assert.equal(hasComponent(testEntity, tag), false)
-// setComponent(testEntity, RigidBodyComponent, { type: BodyTypes.Fixed })
-// assert.equal(hasComponent(testEntity, tag), true)
-// setComponent(testEntity, RigidBodyComponent, { type: BodyTypes.Dynamic })
-// assert.equal(hasComponent(testEntity, tag), false)
-// })
-
-// it('should set and remove a RigidBodyKinematicTagComponent on the entity when the component.type changes to kinematic', () => {
-// assert.ok(RigidBodyComponent.reactorMap.get(testEntity)!.isRunning)
-// const tag = RigidBodyKinematicTagComponent
-// removeComponent(testEntity, RigidBodyComponent)
-// assert.equal(hasComponent(testEntity, tag), false)
-// setComponent(testEntity, RigidBodyComponent, { type: BodyTypes.Kinematic })
-// assert.equal(hasComponent(testEntity, tag), true)
-// setComponent(testEntity, RigidBodyComponent, { type: BodyTypes.Fixed })
-// assert.equal(hasComponent(testEntity, tag), false)
-// })
-
-// it('should enable CCD for the RigidBody on the API data when component.ccd changes', () => {
-// assert.ok(RigidBodyComponent.reactorMap.get(testEntity)!.isRunning)
-// const Expected = !RigidBodyComponentDefaults.ccd
-// const beforeBody = physicsWorld.Rigidbodies.get(testEntity)!
-// assert.ok(beforeBody)
-// const beforeAPI = beforeBody.isCcdEnabled()
-// assert.equal(beforeAPI, RigidBodyComponentDefaults.ccd)
-// const beforeECS = getComponent(testEntity, RigidBodyComponent).ccd
-// assert.equal(beforeECS, RigidBodyComponentDefaults.ccd)
-
-// setComponent(testEntity, RigidBodyComponent, { ccd: Expected })
-// const afterBody = physicsWorld.Rigidbodies.get(testEntity)!
-// assert.ok(afterBody)
-// const afterAPI = afterBody.isCcdEnabled()
-// assert.equal(afterAPI, Expected)
-// const afterECS = getComponent(testEntity, RigidBodyComponent).ccd
-// assert.equal(afterECS, Expected)
-// })
-
-// it('should lock/unlock rotations for the RigidBody on the API data when component.allowRolling changes', () => {
-// setComponent(testEntity, RigidBodyComponent, { type: BodyTypes.Dynamic })
-
-// assert.ok(RigidBodyComponent.reactorMap.get(testEntity)!.isRunning)
-// const TorqueImpulse = new Vector3(10, 20, 30)
-// const body = physicsWorld.Rigidbodies.get(testEntity)!
-
-// // Defaults
-// const one = getComponent(testEntity, RigidBodyComponent).angularVelocity
-// const before = { x: one.x, y: one.y, z: one.z }
-// assertVecApproxEq(before, Vector3_Zero, 3)
-// const Expected = !RigidBodyComponentDefaults.allowRolling
-// assert.notEqual(getComponent(testEntity, RigidBodyComponent).allowRolling, Expected) // Should still be the default
-
-// // Locked
-// setComponent(testEntity, RigidBodyComponent, { allowRolling: Expected })
-// assert.equal(getComponent(testEntity, RigidBodyComponent).allowRolling, Expected)
-// body.applyTorqueImpulse(TorqueImpulse, false)
-// physicsSystemExecute()
-// const two = getComponent(testEntity, RigidBodyComponent).angularVelocity
-// const after = { x: two.x, y: two.y, z: two.z }
-// assertVecApproxEq(before, after, 3)
-
-// // Unlocked
-// setComponent(testEntity, RigidBodyComponent, { allowRolling: !Expected })
-// assert.equal(getComponent(testEntity, RigidBodyComponent).allowRolling, !Expected)
-// body.applyTorqueImpulse(TorqueImpulse, false)
-// physicsSystemExecute()
-// const three = getComponent(testEntity, RigidBodyComponent).angularVelocity
-// const unlocked = { x: three.x, y: three.y, z: three.z }
-// assertVecAllApproxNotEq(before, unlocked, 3)
-// })
-
-// it('should enable/disable rotations for each axis for the RigidBody on the API data when component.enabledRotations changes', () => {
-// setComponent(testEntity, RigidBodyComponent, { type: BodyTypes.Dynamic })
-
-// const reactor = RigidBodyComponent.reactorMap.get(testEntity)!
-// assert.ok(reactor.isRunning)
-// const TorqueImpulse = new Vector3(10, 20, 30)
-// const body = physicsWorld.Rigidbodies.get(testEntity)!
-
-// // Defaults
-// const one = getComponent(testEntity, RigidBodyComponent).angularVelocity.clone()
-// assertFloatApproxEq(one.x, Vector3_Zero.x)
-// assertFloatApproxEq(one.y, Vector3_Zero.y)
-// assertFloatApproxEq(one.z, Vector3_Zero.z)
-
-// // Locked
-// const AllLocked = [false, false, false] as [boolean, boolean, boolean]
-// assertArrayNotEqual(getComponent(testEntity, RigidBodyComponent).enabledRotations, AllLocked) // Should still be the default
-// setComponent(testEntity, RigidBodyComponent, { enabledRotations: AllLocked })
-// assertArrayEqual(getComponent(testEntity, RigidBodyComponent).enabledRotations, AllLocked)
-// reactor.run()
-// body.applyTorqueImpulse(TorqueImpulse, false)
-// physicsSystemExecute()
-// const two = getComponent(testEntity, RigidBodyComponent).angularVelocity.clone()
-// assertFloatApproxEq(one.x, two.x)
-// assertFloatApproxEq(one.y, two.y)
-// assertFloatApproxEq(one.z, two.z)
-
-// // Unlock X
-// const XUnlocked = [true, false, false] as [boolean, boolean, boolean]
-// setComponent(testEntity, RigidBodyComponent, { enabledRotations: XUnlocked })
-// assertArrayEqual(getComponent(testEntity, RigidBodyComponent).enabledRotations, XUnlocked)
-// body.applyTorqueImpulse(TorqueImpulse, false)
-// physicsSystemExecute()
-// const three = getComponent(testEntity, RigidBodyComponent).angularVelocity.clone()
-// assertFloatApproxNotEq(two.x, three.x)
-// assertFloatApproxEq(two.y, three.y)
-// assertFloatApproxEq(two.z, three.z)
-
-// // Unlock Y
-// const YUnlocked = [false, true, false] as [boolean, boolean, boolean]
-// setComponent(testEntity, RigidBodyComponent, { enabledRotations: YUnlocked })
-// assertArrayEqual(getComponent(testEntity, RigidBodyComponent).enabledRotations, YUnlocked)
-// body.applyTorqueImpulse(TorqueImpulse, false)
-// physicsSystemExecute()
-// const four = getComponent(testEntity, RigidBodyComponent).angularVelocity.clone()
-// assertFloatApproxEq(three.x, four.x)
-// assertFloatApproxNotEq(three.y, four.y)
-// assertFloatApproxEq(three.z, four.z)
-
-// // Unlock Z
-// const ZUnlocked = [false, false, true] as [boolean, boolean, boolean]
-// setComponent(testEntity, RigidBodyComponent, { enabledRotations: ZUnlocked })
-// assertArrayEqual(getComponent(testEntity, RigidBodyComponent).enabledRotations, ZUnlocked)
-// body.applyTorqueImpulse(TorqueImpulse, false)
-// physicsSystemExecute()
-// const five = getComponent(testEntity, RigidBodyComponent).angularVelocity.clone()
-// assertFloatApproxEq(four.x, five.x)
-// assertFloatApproxEq(four.y, five.y)
-// assertFloatApproxNotEq(four.z, five.z)
-
-// // Unlock All
-// const AllUnlocked = [true, true, true] as [boolean, boolean, boolean]
-// setComponent(testEntity, RigidBodyComponent, { enabledRotations: AllUnlocked })
-// assertArrayEqual(getComponent(testEntity, RigidBodyComponent).enabledRotations, AllUnlocked)
-// body.applyTorqueImpulse(TorqueImpulse, false)
-// physicsSystemExecute()
-// const six = getComponent(testEntity, RigidBodyComponent).angularVelocity.clone()
-// assertFloatApproxNotEq(five.x, six.x)
-// assertFloatApproxNotEq(five.y, six.y)
-// assertFloatApproxNotEq(five.z, six.z)
-// })
-// }) // << reactor
-
-// describe('getTagComponentForRigidBody', () => {
-// let testEntity = UndefinedEntity
-
-// beforeEach(async () => {
-// createEngine()
-// await Physics.load()
-// testEntity = createEntity()
-// })
-
-// afterEach(() => {
-// removeEntity(testEntity)
-// return destroyEngine()
-// })
-
-// it('should return the expected tag components', () => {
-// setComponent(testEntity, RigidBodyComponent, { type: BodyTypes.Dynamic })
-// assert.equal(
-// getTagComponentForRigidBody(getComponent(testEntity, RigidBodyComponent).type),
-// RigidBodyDynamicTagComponent
-// )
-// setComponent(testEntity, RigidBodyComponent, { type: BodyTypes.Fixed })
-// assert.equal(
-// getTagComponentForRigidBody(getComponent(testEntity, RigidBodyComponent).type),
-// RigidBodyFixedTagComponent
-// )
-// setComponent(testEntity, RigidBodyComponent, { type: BodyTypes.Kinematic })
-// assert.equal(
-// getTagComponentForRigidBody(getComponent(testEntity, RigidBodyComponent).type),
-// RigidBodyKinematicTagComponent
-// )
-// })
-// }) // getTagComponentForRigidBody
-// })
+/*
+CPAL-1.0 License
+
+The contents of this file are subject to the Common Public Attribution License
+Version 1.0. (the "License"); you may not use this file except in compliance
+with the License. You may obtain a copy of the License at
+https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE.
+The License is based on the Mozilla Public License Version 1.1, but Sections 14
+and 15 have been added to cover use of software over a computer network and
+provide for limited attribution for the Original Developer. In addition,
+Exhibit A has been modified to be consistent with Exhibit B.
+
+Software distributed under the License is distributed on an "AS IS" basis,
+WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the
+specific language governing rights and limitations under the License.
+
+The Original Code is Ethereal Engine.
+
+The Original Developer is the Initial Developer. The Initial Developer of the
+Original Code is the Ethereal Engine team.
+
+All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023
+Ethereal Engine. All Rights Reserved.
+*/
+
+import { act, render } from '@testing-library/react'
+import assert from 'assert'
+
+import { RigidBodyType } from '@dimforge/rapier3d-compat'
+import {
+ SystemDefinitions,
+ UUIDComponent,
+ UndefinedEntity,
+ createEngine,
+ createEntity,
+ destroyEngine,
+ getComponent,
+ hasComponent,
+ removeComponent,
+ removeEntity,
+ serializeComponent,
+ setComponent
+} from '@etherealengine/ecs'
+import React from 'react'
+import { Vector3 } from 'three'
+import { PhysicsSystem, TransformComponent } from '../../SpatialModule'
+import { Vector3_Zero } from '../../common/constants/MathConstants'
+import { SceneComponent } from '../../renderer/components/SceneComponents'
+import { EntityTreeComponent } from '../../transform/components/EntityTree'
+import { Physics, PhysicsWorld } from '../classes/Physics'
+import {
+ assertFloatApproxEq,
+ assertFloatApproxNotEq,
+ assertVecAllApproxNotEq,
+ assertVecApproxEq
+} from '../classes/Physics.test'
+import { BodyTypes } from '../types/PhysicsTypes'
+import { ColliderComponent } from './ColliderComponent'
+import {
+ RigidBodyComponent,
+ RigidBodyDynamicTagComponent,
+ RigidBodyFixedTagComponent,
+ RigidBodyKinematicTagComponent,
+ getTagComponentForRigidBody
+} from './RigidBodyComponent'
+
+const RigidBodyComponentDefaults = {
+ type: BodyTypes.Fixed,
+ ccd: false,
+ allowRolling: true,
+ enabledRotations: [true, true, true] as [boolean, boolean, boolean],
+ canSleep: true,
+ gravityScale: 1,
+ previousPosition: 3,
+ previousRotation: 4,
+ position: 3,
+ rotation: 4,
+ targetKinematicPosition: 3,
+ targetKinematicRotation: 4,
+ linearVelocity: 3,
+ angularVelocity: 3,
+ targetKinematicLerpMultiplier: 0
+}
+
+export function assertArrayEqual(A: Array, B: Array, err = 'Arrays are not equal') {
+ assert.equal(A.length, B.length, err + ': Their lenght is not the same')
+ for (let id = 0; id < A.length && id < B.length; id++) {
+ assert.deepEqual(A[id], B[id], err + `: Their item[${id}] is not the same`)
+ }
+}
+
+export function assertArrayNotEqual(A: Array, B: Array, err = 'Arrays are equal') {
+ for (let id = 0; id < A.length && id < B.length; id++) {
+ assert.notDeepEqual(A[id], B[id], err)
+ }
+}
+
+export function assertRigidBodyComponentEqual(data, expected = RigidBodyComponentDefaults) {
+ assert.equal(data.type, expected.type)
+ assert.equal(data.ccd, expected.ccd)
+ assert.equal(data.allowRolling, expected.allowRolling)
+ assert.equal(data.enabledRotations.length, expected.enabledRotations.length)
+ assert.equal(data.enabledRotations[0], expected.enabledRotations[0])
+ assert.equal(data.enabledRotations[1], expected.enabledRotations[1])
+ assert.equal(data.enabledRotations[2], expected.enabledRotations[2])
+ assert.equal(data.canSleep, expected.canSleep)
+ assert.equal(data.gravityScale, expected.gravityScale)
+ /**
+ // @todo Not serialized by the component
+ assertVecApproxEq(data.previousPosition, expected.previousPosition, 3)
+ assertVecApproxEq(data.previousRotation, expected.previousRotation, 4)
+ assertVecApproxEq(data.position, expected.position, 3)
+ assertVecApproxEq(data.rotation, expected.rotation, 4)
+ assertVecApproxEq(data.targetKinematicPosition, expected.targetKinematicPosition, 3)
+ assertVecApproxEq(data.targetKinematicRotation, expected.targetKinematicRotation, 4)
+ assertVecApproxEq(data.linearVelocity, expected.linearVelocity, 3)
+ assertVecApproxEq(data.angularVelocity, expected.angularVelocity, 3)
+ assert.equal(data.targetKinematicLerpMultiplier, expected.targetKinematicLerpMultiplier)
+ */
+}
+
+describe('RigidBodyComponent', () => {
+ describe('IDs', () => {
+ it('should initialize the RigidBodyComponent.name field with the expected value', () => {
+ assert.equal(RigidBodyComponent.name, 'RigidBodyComponent')
+ })
+ it('should initialize the RigidBodyComponent.jsonID field with the expected value', () => {
+ assert.equal(RigidBodyComponent.jsonID, 'EE_rigidbody')
+ })
+ })
+
+ describe('onInit', () => {
+ let testEntity = UndefinedEntity
+
+ beforeEach(async () => {
+ createEngine()
+ await Physics.load()
+ testEntity = createEntity()
+ setComponent(testEntity, RigidBodyComponent)
+ })
+
+ afterEach(() => {
+ removeEntity(testEntity)
+ return destroyEngine()
+ })
+
+ it('should initialize the component with the expected default values', () => {
+ const data = getComponent(testEntity, RigidBodyComponent)
+ assertRigidBodyComponentEqual(data, RigidBodyComponentDefaults)
+ })
+ }) // << onInit
+
+ describe('onSet', () => {
+ let testEntity = UndefinedEntity
+
+ beforeEach(async () => {
+ createEngine()
+ await Physics.load()
+ testEntity = createEntity()
+ setComponent(testEntity, RigidBodyComponent)
+ })
+
+ afterEach(() => {
+ removeEntity(testEntity)
+ return destroyEngine()
+ })
+
+ it('should change the values of an initialized RigidBodyComponent', () => {
+ const Expected = {
+ type: BodyTypes.Dynamic,
+ ccd: true,
+ allowRolling: false,
+ canSleep: false,
+ gravityScale: 2,
+ enabledRotations: [false, false, false] as [boolean, boolean, boolean]
+ }
+ const before = getComponent(testEntity, RigidBodyComponent)
+ assertRigidBodyComponentEqual(before, RigidBodyComponentDefaults)
+
+ setComponent(testEntity, RigidBodyComponent, Expected)
+ const after = getComponent(testEntity, RigidBodyComponent)
+ assert.equal(after.type, Expected.type)
+ assert.equal(after.ccd, Expected.ccd)
+ assert.equal(after.allowRolling, Expected.allowRolling)
+ assert.equal(after.canSleep, Expected.canSleep)
+ assert.equal(after.gravityScale, Expected.gravityScale)
+ assert.equal(after.enabledRotations.length, Expected.enabledRotations.length)
+ assert.equal(after.enabledRotations[0], Expected.enabledRotations[0])
+ assert.equal(after.enabledRotations[1], Expected.enabledRotations[1])
+ assert.equal(after.enabledRotations[2], Expected.enabledRotations[2])
+ })
+
+ it('should not change values of an initialized RigidBodyComponent when the data passed had incorrect types', () => {
+ const Incorrect = {
+ type: 1,
+ ccd: 'ccd',
+ allowRolling: 2,
+ canSleep: 3,
+ gravityScale: false,
+ enabledRotations: [4, 5, 6]
+ }
+ const before = getComponent(testEntity, RigidBodyComponent)
+ assertRigidBodyComponentEqual(before, RigidBodyComponentDefaults)
+
+ // @ts-ignore Pass an incorrect type to setComponent
+ setComponent(testEntity, RigidBodyComponent, Incorrect)
+ const after = getComponent(testEntity, RigidBodyComponent)
+ assertRigidBodyComponentEqual(after, RigidBodyComponentDefaults)
+ })
+ }) // << onSet
+
+ describe('toJSON', () => {
+ let testEntity = UndefinedEntity
+
+ beforeEach(async () => {
+ createEngine()
+ await Physics.load()
+ testEntity = createEntity()
+ setComponent(testEntity, RigidBodyComponent)
+ })
+
+ afterEach(() => {
+ removeEntity(testEntity)
+ return destroyEngine()
+ })
+
+ it("should serialize the component's data correctly", () => {
+ const Expected = {
+ type: 'fixed',
+ ccd: false,
+ allowRolling: true,
+ enabledRotations: [true, true, true],
+ canSleep: true,
+ gravityScale: 1
+ }
+ const json = serializeComponent(testEntity, RigidBodyComponent)
+ assert.deepEqual(json, Expected)
+ })
+ }) // << toJSON
+
+ describe('reactor', () => {
+ let testEntity = UndefinedEntity
+ let physicsWorld: PhysicsWorld
+ let newPhysicsWorld: PhysicsWorld
+ let physicsWorldEntity = UndefinedEntity
+
+ beforeEach(async () => {
+ createEngine()
+ await Physics.load()
+ physicsWorldEntity = createEntity()
+ setComponent(physicsWorldEntity, UUIDComponent, UUIDComponent.generateUUID())
+ setComponent(physicsWorldEntity, SceneComponent)
+ setComponent(physicsWorldEntity, TransformComponent)
+ setComponent(physicsWorldEntity, EntityTreeComponent)
+ physicsWorld = Physics.createWorld(getComponent(physicsWorldEntity, UUIDComponent))
+ physicsWorld!.timestep = 1 / 60
+
+ testEntity = createEntity()
+ setComponent(testEntity, EntityTreeComponent, { parentEntity: physicsWorldEntity })
+ setComponent(testEntity, TransformComponent)
+ setComponent(testEntity, RigidBodyComponent)
+ setComponent(testEntity, ColliderComponent)
+ })
+
+ afterEach(() => {
+ Physics.destroyWorld(physicsWorld.id)
+ // if (newPhysicsWorld) Physics.destroyWorld(newPhysicsWorld.id)
+ removeEntity(testEntity)
+ return destroyEngine()
+ })
+
+ const physicsSystemExecute = SystemDefinitions.get(PhysicsSystem)!.execute
+
+ it('should create a RigidBody for the entity in the new physicsWorld when the world is changed', async () => {
+ assert.ok(RigidBodyComponent.reactorMap.get(testEntity)!.isRunning)
+ const before = physicsWorld.Rigidbodies.get(testEntity)!.handle
+ assert.ok(physicsWorld!.bodies.contains(before))
+
+ const newPhysicsEntity = createEntity()
+ setComponent(newPhysicsEntity, UUIDComponent, UUIDComponent.generateUUID())
+ setComponent(newPhysicsEntity, SceneComponent)
+ setComponent(newPhysicsEntity, TransformComponent)
+ setComponent(newPhysicsEntity, EntityTreeComponent)
+ newPhysicsWorld = Physics.createWorld(getComponent(newPhysicsEntity, UUIDComponent))
+ newPhysicsWorld!.timestep = 1 / 60
+
+ // Change the world
+ setComponent(testEntity, EntityTreeComponent, { parentEntity: newPhysicsEntity })
+
+ // Force react lifecycle to update Physics.useWorld
+ const { rerender, unmount } = render(<>>)
+ await act(() => rerender(<>>))
+
+ // Check the changes
+ RigidBodyComponent.reactorMap.get(testEntity)!.run() // Reactor is already running. But force-run it so changes are applied immediately
+ const after = newPhysicsWorld.Rigidbodies.get(testEntity)!.handle
+ assert.ok(newPhysicsWorld!.bodies.contains(after))
+ })
+
+ it('should set the correct RigidBody type on the API data when component.type changes', () => {
+ assert.ok(RigidBodyComponent.reactorMap.get(testEntity)!.isRunning)
+ setComponent(testEntity, RigidBodyComponent, { type: BodyTypes.Dynamic })
+ const one = physicsWorld.Rigidbodies.get(testEntity)!.bodyType()
+ assert.equal(one, RigidBodyType.Dynamic)
+ setComponent(testEntity, RigidBodyComponent, { type: BodyTypes.Fixed })
+ const two = physicsWorld.Rigidbodies.get(testEntity)!.bodyType()
+ assert.equal(two, RigidBodyType.Fixed)
+ setComponent(testEntity, RigidBodyComponent, { type: BodyTypes.Kinematic })
+ const three = physicsWorld.Rigidbodies.get(testEntity)!.bodyType()
+ assert.equal(three, RigidBodyType.KinematicPositionBased)
+ })
+
+ it('should set and remove a RigidBodyDynamicTagComponent on the entity when the component.type changes to dynamic', () => {
+ assert.ok(RigidBodyComponent.reactorMap.get(testEntity)!.isRunning)
+ const tag = RigidBodyDynamicTagComponent
+ removeComponent(testEntity, RigidBodyComponent)
+ assert.equal(hasComponent(testEntity, tag), false)
+ setComponent(testEntity, RigidBodyComponent, { type: BodyTypes.Dynamic })
+ assert.equal(hasComponent(testEntity, tag), true)
+ setComponent(testEntity, RigidBodyComponent, { type: BodyTypes.Fixed })
+ assert.equal(hasComponent(testEntity, tag), false)
+ })
+
+ it('should set and remove a RigidBodyFixedTagComponent on the entity when the component.type changes to fixed', () => {
+ assert.ok(RigidBodyComponent.reactorMap.get(testEntity)!.isRunning)
+ const tag = RigidBodyFixedTagComponent
+ removeComponent(testEntity, RigidBodyComponent)
+ assert.equal(hasComponent(testEntity, tag), false)
+ setComponent(testEntity, RigidBodyComponent, { type: BodyTypes.Fixed })
+ assert.equal(hasComponent(testEntity, tag), true)
+ setComponent(testEntity, RigidBodyComponent, { type: BodyTypes.Dynamic })
+ assert.equal(hasComponent(testEntity, tag), false)
+ })
+
+ it('should set and remove a RigidBodyKinematicTagComponent on the entity when the component.type changes to kinematic', () => {
+ assert.ok(RigidBodyComponent.reactorMap.get(testEntity)!.isRunning)
+ const tag = RigidBodyKinematicTagComponent
+ removeComponent(testEntity, RigidBodyComponent)
+ assert.equal(hasComponent(testEntity, tag), false)
+ setComponent(testEntity, RigidBodyComponent, { type: BodyTypes.Kinematic })
+ assert.equal(hasComponent(testEntity, tag), true)
+ setComponent(testEntity, RigidBodyComponent, { type: BodyTypes.Fixed })
+ assert.equal(hasComponent(testEntity, tag), false)
+ })
+
+ it('should enable CCD for the RigidBody on the API data when component.ccd changes', () => {
+ assert.ok(RigidBodyComponent.reactorMap.get(testEntity)!.isRunning)
+ const Expected = !RigidBodyComponentDefaults.ccd
+ const beforeBody = physicsWorld.Rigidbodies.get(testEntity)!
+ assert.ok(beforeBody)
+ const beforeAPI = beforeBody.isCcdEnabled()
+ assert.equal(beforeAPI, RigidBodyComponentDefaults.ccd)
+ const beforeECS = getComponent(testEntity, RigidBodyComponent).ccd
+ assert.equal(beforeECS, RigidBodyComponentDefaults.ccd)
+
+ setComponent(testEntity, RigidBodyComponent, { ccd: Expected })
+ const afterBody = physicsWorld.Rigidbodies.get(testEntity)!
+ assert.ok(afterBody)
+ const afterAPI = afterBody.isCcdEnabled()
+ assert.equal(afterAPI, Expected)
+ const afterECS = getComponent(testEntity, RigidBodyComponent).ccd
+ assert.equal(afterECS, Expected)
+ })
+
+ it('should lock/unlock rotations for the RigidBody on the API data when component.allowRolling changes', () => {
+ setComponent(testEntity, RigidBodyComponent, { type: BodyTypes.Dynamic })
+
+ assert.ok(RigidBodyComponent.reactorMap.get(testEntity)!.isRunning)
+ const TorqueImpulse = new Vector3(10, 20, 30)
+ const body = physicsWorld.Rigidbodies.get(testEntity)!
+
+ // Defaults
+ const one = getComponent(testEntity, RigidBodyComponent).angularVelocity
+ const before = { x: one.x, y: one.y, z: one.z }
+ assertVecApproxEq(before, Vector3_Zero, 3)
+ const Expected = !RigidBodyComponentDefaults.allowRolling
+ assert.notEqual(getComponent(testEntity, RigidBodyComponent).allowRolling, Expected) // Should still be the default
+
+ // Locked
+ setComponent(testEntity, RigidBodyComponent, { allowRolling: Expected })
+ assert.equal(getComponent(testEntity, RigidBodyComponent).allowRolling, Expected)
+ body.applyTorqueImpulse(TorqueImpulse, false)
+ physicsSystemExecute()
+ const two = getComponent(testEntity, RigidBodyComponent).angularVelocity
+ const after = { x: two.x, y: two.y, z: two.z }
+ assertVecApproxEq(before, after, 3)
+
+ // Unlocked
+ setComponent(testEntity, RigidBodyComponent, { allowRolling: !Expected })
+ assert.equal(getComponent(testEntity, RigidBodyComponent).allowRolling, !Expected)
+ body.applyTorqueImpulse(TorqueImpulse, false)
+ physicsSystemExecute()
+ const three = getComponent(testEntity, RigidBodyComponent).angularVelocity
+ const unlocked = { x: three.x, y: three.y, z: three.z }
+ assertVecAllApproxNotEq(before, unlocked, 3)
+ })
+
+ it('should enable/disable rotations for each axis for the RigidBody on the API data when component.enabledRotations changes', () => {
+ setComponent(testEntity, RigidBodyComponent, { type: BodyTypes.Dynamic })
+
+ const reactor = RigidBodyComponent.reactorMap.get(testEntity)!
+ assert.ok(reactor.isRunning)
+ const TorqueImpulse = new Vector3(10, 20, 30)
+ const body = physicsWorld.Rigidbodies.get(testEntity)!
+
+ // Defaults
+ const one = getComponent(testEntity, RigidBodyComponent).angularVelocity.clone()
+ assertFloatApproxEq(one.x, Vector3_Zero.x)
+ assertFloatApproxEq(one.y, Vector3_Zero.y)
+ assertFloatApproxEq(one.z, Vector3_Zero.z)
+
+ // Locked
+ const AllLocked = [false, false, false] as [boolean, boolean, boolean]
+ assertArrayNotEqual(getComponent(testEntity, RigidBodyComponent).enabledRotations, AllLocked) // Should still be the default
+ setComponent(testEntity, RigidBodyComponent, { enabledRotations: AllLocked })
+ assertArrayEqual(getComponent(testEntity, RigidBodyComponent).enabledRotations, AllLocked)
+ reactor.run()
+ body.applyTorqueImpulse(TorqueImpulse, false)
+ physicsSystemExecute()
+ const two = getComponent(testEntity, RigidBodyComponent).angularVelocity.clone()
+ assertFloatApproxEq(one.x, two.x)
+ assertFloatApproxEq(one.y, two.y)
+ assertFloatApproxEq(one.z, two.z)
+
+ // Unlock X
+ const XUnlocked = [true, false, false] as [boolean, boolean, boolean]
+ setComponent(testEntity, RigidBodyComponent, { enabledRotations: XUnlocked })
+ assertArrayEqual(getComponent(testEntity, RigidBodyComponent).enabledRotations, XUnlocked)
+ body.applyTorqueImpulse(TorqueImpulse, false)
+ physicsSystemExecute()
+ const three = getComponent(testEntity, RigidBodyComponent).angularVelocity.clone()
+ assertFloatApproxNotEq(two.x, three.x)
+ assertFloatApproxEq(two.y, three.y)
+ assertFloatApproxEq(two.z, three.z)
+
+ // Unlock Y
+ const YUnlocked = [false, true, false] as [boolean, boolean, boolean]
+ setComponent(testEntity, RigidBodyComponent, { enabledRotations: YUnlocked })
+ assertArrayEqual(getComponent(testEntity, RigidBodyComponent).enabledRotations, YUnlocked)
+ body.applyTorqueImpulse(TorqueImpulse, false)
+ physicsSystemExecute()
+ const four = getComponent(testEntity, RigidBodyComponent).angularVelocity.clone()
+ assertFloatApproxEq(three.x, four.x)
+ assertFloatApproxNotEq(three.y, four.y)
+ assertFloatApproxEq(three.z, four.z)
+
+ // Unlock Z
+ const ZUnlocked = [false, false, true] as [boolean, boolean, boolean]
+ setComponent(testEntity, RigidBodyComponent, { enabledRotations: ZUnlocked })
+ assertArrayEqual(getComponent(testEntity, RigidBodyComponent).enabledRotations, ZUnlocked)
+ body.applyTorqueImpulse(TorqueImpulse, false)
+ physicsSystemExecute()
+ const five = getComponent(testEntity, RigidBodyComponent).angularVelocity.clone()
+ assertFloatApproxEq(four.x, five.x)
+ assertFloatApproxEq(four.y, five.y)
+ assertFloatApproxNotEq(four.z, five.z)
+
+ // Unlock All
+ const AllUnlocked = [true, true, true] as [boolean, boolean, boolean]
+ setComponent(testEntity, RigidBodyComponent, { enabledRotations: AllUnlocked })
+ assertArrayEqual(getComponent(testEntity, RigidBodyComponent).enabledRotations, AllUnlocked)
+ body.applyTorqueImpulse(TorqueImpulse, false)
+ physicsSystemExecute()
+ const six = getComponent(testEntity, RigidBodyComponent).angularVelocity.clone()
+ assertFloatApproxNotEq(five.x, six.x)
+ assertFloatApproxNotEq(five.y, six.y)
+ assertFloatApproxNotEq(five.z, six.z)
+ })
+ }) // << reactor
+
+ describe('getTagComponentForRigidBody', () => {
+ let testEntity = UndefinedEntity
+
+ beforeEach(async () => {
+ createEngine()
+ await Physics.load()
+ testEntity = createEntity()
+ })
+
+ afterEach(() => {
+ removeEntity(testEntity)
+ return destroyEngine()
+ })
+
+ it('should return the expected tag components', () => {
+ setComponent(testEntity, RigidBodyComponent, { type: BodyTypes.Dynamic })
+ assert.equal(
+ getTagComponentForRigidBody(getComponent(testEntity, RigidBodyComponent).type),
+ RigidBodyDynamicTagComponent
+ )
+ setComponent(testEntity, RigidBodyComponent, { type: BodyTypes.Fixed })
+ assert.equal(
+ getTagComponentForRigidBody(getComponent(testEntity, RigidBodyComponent).type),
+ RigidBodyFixedTagComponent
+ )
+ setComponent(testEntity, RigidBodyComponent, { type: BodyTypes.Kinematic })
+ assert.equal(
+ getTagComponentForRigidBody(getComponent(testEntity, RigidBodyComponent).type),
+ RigidBodyKinematicTagComponent
+ )
+ })
+ }) // getTagComponentForRigidBody
+})
diff --git a/packages/spatial/src/physics/components/RigidBodyComponent.ts b/packages/spatial/src/physics/components/RigidBodyComponent.ts
index e868092bc9..2c0706dd38 100644
--- a/packages/spatial/src/physics/components/RigidBodyComponent.ts
+++ b/packages/spatial/src/physics/components/RigidBodyComponent.ts
@@ -67,6 +67,8 @@ export const RigidBodyComponent = defineComponent({
canSleep: true,
gravityScale: 1,
// internal
+ /** @deprecated @todo make the physics api properly reactive to remove this property */
+ initialized: false,
previousPosition: proxifyVector3(this.previousPosition, entity),
previousRotation: proxifyQuaternion(this.previousRotation, entity),
position: proxifyVector3(this.position, entity),
@@ -118,8 +120,10 @@ export const RigidBodyComponent = defineComponent({
useEffect(() => {
if (!physicsWorld) return
Physics.createRigidBody(physicsWorld, entity)
+ component.initialized.set(true)
return () => {
Physics.removeRigidbody(physicsWorld, entity)
+ component.initialized.set(false)
}
}, [physicsWorld])
diff --git a/packages/spatial/src/physics/components/TriggerComponent.test.ts b/packages/spatial/src/physics/components/TriggerComponent.test.ts
index 8cd88afc08..cb45e3ebb7 100644
--- a/packages/spatial/src/physics/components/TriggerComponent.test.ts
+++ b/packages/spatial/src/physics/components/TriggerComponent.test.ts
@@ -1,226 +1,226 @@
-// /*
-// CPAL-1.0 License
-
-// The contents of this file are subject to the Common Public Attribution License
-// Version 1.0. (the "License"); you may not use this file except in compliance
-// with the License. You may obtain a copy of the License at
-// https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE.
-// The License is based on the Mozilla Public License Version 1.1, but Sections 14
-// and 15 have been added to cover use of software over a computer network and
-// provide for limited attribution for the Original Developer. In addition,
-// Exhibit A has been modified to be consistent with Exhibit B.
-
-// Software distributed under the License is distributed on an "AS IS" basis,
-// WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the
-// specific language governing rights and limitations under the License.
-
-// The Original Code is Ethereal Engine.
-
-// The Original Developer is the Initial Developer. The Initial Developer of the
-// Original Code is the Ethereal Engine team.
-
-// All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023
-// Ethereal Engine. All Rights Reserved.
-// */
-
-// import {
-// EntityUUID,
-// UUIDComponent,
-// UndefinedEntity,
-// createEngine,
-// createEntity,
-// destroyEngine,
-// getComponent,
-// removeComponent,
-// removeEntity,
-// serializeComponent,
-// setComponent
-// } from '@etherealengine/ecs'
-// import assert from 'assert'
-// import { Vector3 } from 'three'
-// import { TransformComponent } from '../../SpatialModule'
-// import { SceneComponent } from '../../renderer/components/SceneComponents'
-// import { EntityTreeComponent } from '../../transform/components/EntityTree'
-// import { Physics, PhysicsWorld } from '../classes/Physics'
-// import { CollisionGroups, DefaultCollisionMask } from '../enums/CollisionGroups'
-// import { Shapes } from '../types/PhysicsTypes'
-// import { ColliderComponent } from './ColliderComponent'
-// import { ColliderComponentDefaults, assertColliderComponentEquals } from './ColliderComponent.test'
-// import { RigidBodyComponent } from './RigidBodyComponent'
-// import { TriggerComponent } from './TriggerComponent'
-
-// const TriggerComponentDefaults = {
-// triggers: [] as Array<{
-// onEnter: null | string
-// onExit: null | string
-// target: null | EntityUUID
-// }>
-// }
-
-// function assertArrayEqual(A: Array, B: Array, err = 'Arrays are not equal') {
-// assert.equal(A.length, B.length, err)
-// for (let id = 0; id < A.length && id < B.length; id++) {
-// assert.deepEqual(A[id], B[id], err)
-// }
-// }
-
-// function assertArrayNotEqual(A: Array, B: Array, err = 'Arrays are equal') {
-// for (let id = 0; id < A.length && id < B.length; id++) {
-// assert.notDeepEqual(A[id], B[id], err)
-// }
-// }
-
-// function assertTriggerComponentEqual(data, expected) {
-// assertArrayEqual(data.triggers, expected.triggers)
-// }
-
-// function assertTriggerComponentNotEqual(data, expected) {
-// assertArrayNotEqual(data.triggers, expected.triggers)
-// }
-
-// describe('TriggerComponent', () => {
-// describe('IDs', () => {
-// it('should initialize the TriggerComponent.name field with the expected value', () => {
-// assert.equal(TriggerComponent.name, 'TriggerComponent')
-// })
-// it('should initialize the TriggerComponent.jsonID field with the expected value', () => {
-// assert.equal(TriggerComponent.jsonID, 'EE_trigger')
-// })
-// })
-
-// describe('onInit', () => {
-// let testEntity = UndefinedEntity
-
-// beforeEach(async () => {
-// createEngine()
-// testEntity = createEntity()
-// setComponent(testEntity, TriggerComponent)
-// })
-
-// afterEach(() => {
-// removeEntity(testEntity)
-// return destroyEngine()
-// })
-
-// it('should initialize the component with the expected default values', () => {
-// const data = getComponent(testEntity, TriggerComponent)
-// assertTriggerComponentEqual(data, TriggerComponentDefaults)
-// })
-// }) // << onInit
-
-// describe('onSet', () => {
-// let testEntity = UndefinedEntity
-
-// beforeEach(async () => {
-// createEngine()
-// testEntity = createEntity()
-// setComponent(testEntity, TriggerComponent)
-// })
-
-// afterEach(() => {
-// removeEntity(testEntity)
-// return destroyEngine()
-// })
-
-// it('should change the values of an initialized TriggerComponent', () => {
-// const Expected = {
-// triggers: [
-// {
-// onEnter: 'onEnter.Expected',
-// onExit: 'onExit.Expected',
-// target: 'target' as EntityUUID
-// }
-// ]
-// }
-// const before = getComponent(testEntity, TriggerComponent)
-// assertTriggerComponentEqual(before, TriggerComponentDefaults)
-// setComponent(testEntity, TriggerComponent, Expected)
-
-// const data = getComponent(testEntity, TriggerComponent)
-// assertTriggerComponentEqual(data, Expected)
-// })
-
-// it('should not change values of an initialized TriggerComponent when the data passed had incorrect types', () => {
-// const Incorrect = { triggers: 'triggers' }
-// const before = getComponent(testEntity, TriggerComponent)
-// assertTriggerComponentEqual(before, TriggerComponentDefaults)
-
-// // @ts-ignore
-// setComponent(testEntity, TriggerComponent, Incorrect)
-// const data = getComponent(testEntity, TriggerComponent)
-// assertTriggerComponentEqual(data, TriggerComponentDefaults)
-// })
-// }) // << onSet
-
-// describe('toJSON', () => {
-// let testEntity = UndefinedEntity
-
-// beforeEach(async () => {
-// createEngine()
-// await Physics.load()
-// testEntity = createEntity()
-// setComponent(testEntity, TriggerComponent)
-// })
-
-// afterEach(() => {
-// removeEntity(testEntity)
-// return destroyEngine()
-// })
-
-// it("should serialize the component's data correctly", () => {
-// const json = serializeComponent(testEntity, TriggerComponent)
-// assert.deepEqual(json, TriggerComponentDefaults)
-// })
-// }) // << toJson
-
-// describe('reactor', () => {
-// let testEntity = UndefinedEntity
-// let physicsWorld: PhysicsWorld
-// let physicsWorldEntity = UndefinedEntity
-
-// beforeEach(async () => {
-// createEngine()
-// await Physics.load()
-// physicsWorldEntity = createEntity()
-// setComponent(physicsWorldEntity, UUIDComponent, UUIDComponent.generateUUID())
-// setComponent(physicsWorldEntity, SceneComponent)
-// setComponent(physicsWorldEntity, TransformComponent)
-// setComponent(physicsWorldEntity, EntityTreeComponent)
-// physicsWorld = Physics.createWorld(getComponent(physicsWorldEntity, UUIDComponent))
-// physicsWorld!.timestep = 1 / 60
-
-// testEntity = createEntity()
-// setComponent(testEntity, EntityTreeComponent, { parentEntity: physicsWorldEntity })
-// setComponent(testEntity, TransformComponent)
-// setComponent(testEntity, RigidBodyComponent)
-// setComponent(testEntity, ColliderComponent)
-// setComponent(testEntity, TriggerComponent)
-// })
-
-// afterEach(() => {
-// removeEntity(testEntity)
-// return destroyEngine()
-// })
-
-// it("should call Physics.setTrigger on the entity's collider when a new ColliderComponent is set", () => {
-// assertColliderComponentEquals(getComponent(testEntity, ColliderComponent), ColliderComponentDefaults)
-// removeComponent(testEntity, ColliderComponent)
-// const ColliderComponentData = {
-// shape: Shapes.Sphere,
-// mass: 3,
-// massCenter: new Vector3(1, 2, 3),
-// friction: 1.0,
-// restitution: 0.1,
-// collisionLayer: CollisionGroups.Default,
-// collisionMask: DefaultCollisionMask
-// }
-// setComponent(testEntity, ColliderComponent, ColliderComponentData)
-// assertColliderComponentEquals(getComponent(testEntity, ColliderComponent), ColliderComponentData)
-// const reactor = ColliderComponent.reactorMap.get(testEntity)!
-// assert.ok(reactor.isRunning)
-// const collider = physicsWorld.Colliders.get(testEntity)!
-// assert.ok(collider)
-// assert.ok(collider.isSensor())
-// })
-// }) // << reactor
-// })
+/*
+CPAL-1.0 License
+
+The contents of this file are subject to the Common Public Attribution License
+Version 1.0. (the "License"); you may not use this file except in compliance
+with the License. You may obtain a copy of the License at
+https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE.
+The License is based on the Mozilla Public License Version 1.1, but Sections 14
+and 15 have been added to cover use of software over a computer network and
+provide for limited attribution for the Original Developer. In addition,
+Exhibit A has been modified to be consistent with Exhibit B.
+
+Software distributed under the License is distributed on an "AS IS" basis,
+WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the
+specific language governing rights and limitations under the License.
+
+The Original Code is Ethereal Engine.
+
+The Original Developer is the Initial Developer. The Initial Developer of the
+Original Code is the Ethereal Engine team.
+
+All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023
+Ethereal Engine. All Rights Reserved.
+*/
+
+import {
+ EntityUUID,
+ UUIDComponent,
+ UndefinedEntity,
+ createEngine,
+ createEntity,
+ destroyEngine,
+ getComponent,
+ removeComponent,
+ removeEntity,
+ serializeComponent,
+ setComponent
+} from '@etherealengine/ecs'
+import assert from 'assert'
+import { Vector3 } from 'three'
+import { TransformComponent } from '../../SpatialModule'
+import { SceneComponent } from '../../renderer/components/SceneComponents'
+import { EntityTreeComponent } from '../../transform/components/EntityTree'
+import { Physics, PhysicsWorld } from '../classes/Physics'
+import { CollisionGroups, DefaultCollisionMask } from '../enums/CollisionGroups'
+import { Shapes } from '../types/PhysicsTypes'
+import { ColliderComponent } from './ColliderComponent'
+import { ColliderComponentDefaults, assertColliderComponentEquals } from './ColliderComponent.test'
+import { RigidBodyComponent } from './RigidBodyComponent'
+import { TriggerComponent } from './TriggerComponent'
+
+const TriggerComponentDefaults = {
+ triggers: [] as Array<{
+ onEnter: null | string
+ onExit: null | string
+ target: null | EntityUUID
+ }>
+}
+
+function assertArrayEqual(A: Array, B: Array, err = 'Arrays are not equal') {
+ assert.equal(A.length, B.length, err)
+ for (let id = 0; id < A.length && id < B.length; id++) {
+ assert.deepEqual(A[id], B[id], err)
+ }
+}
+
+function assertArrayNotEqual(A: Array, B: Array, err = 'Arrays are equal') {
+ for (let id = 0; id < A.length && id < B.length; id++) {
+ assert.notDeepEqual(A[id], B[id], err)
+ }
+}
+
+function assertTriggerComponentEqual(data, expected) {
+ assertArrayEqual(data.triggers, expected.triggers)
+}
+
+function assertTriggerComponentNotEqual(data, expected) {
+ assertArrayNotEqual(data.triggers, expected.triggers)
+}
+
+describe('TriggerComponent', () => {
+ describe('IDs', () => {
+ it('should initialize the TriggerComponent.name field with the expected value', () => {
+ assert.equal(TriggerComponent.name, 'TriggerComponent')
+ })
+ it('should initialize the TriggerComponent.jsonID field with the expected value', () => {
+ assert.equal(TriggerComponent.jsonID, 'EE_trigger')
+ })
+ })
+
+ describe('onInit', () => {
+ let testEntity = UndefinedEntity
+
+ beforeEach(async () => {
+ createEngine()
+ testEntity = createEntity()
+ setComponent(testEntity, TriggerComponent)
+ })
+
+ afterEach(() => {
+ removeEntity(testEntity)
+ return destroyEngine()
+ })
+
+ it('should initialize the component with the expected default values', () => {
+ const data = getComponent(testEntity, TriggerComponent)
+ assertTriggerComponentEqual(data, TriggerComponentDefaults)
+ })
+ }) // << onInit
+
+ describe('onSet', () => {
+ let testEntity = UndefinedEntity
+
+ beforeEach(async () => {
+ createEngine()
+ testEntity = createEntity()
+ setComponent(testEntity, TriggerComponent)
+ })
+
+ afterEach(() => {
+ removeEntity(testEntity)
+ return destroyEngine()
+ })
+
+ it('should change the values of an initialized TriggerComponent', () => {
+ const Expected = {
+ triggers: [
+ {
+ onEnter: 'onEnter.Expected',
+ onExit: 'onExit.Expected',
+ target: 'target' as EntityUUID
+ }
+ ]
+ }
+ const before = getComponent(testEntity, TriggerComponent)
+ assertTriggerComponentEqual(before, TriggerComponentDefaults)
+ setComponent(testEntity, TriggerComponent, Expected)
+
+ const data = getComponent(testEntity, TriggerComponent)
+ assertTriggerComponentEqual(data, Expected)
+ })
+
+ it('should not change values of an initialized TriggerComponent when the data passed had incorrect types', () => {
+ const Incorrect = { triggers: 'triggers' }
+ const before = getComponent(testEntity, TriggerComponent)
+ assertTriggerComponentEqual(before, TriggerComponentDefaults)
+
+ // @ts-ignore
+ setComponent(testEntity, TriggerComponent, Incorrect)
+ const data = getComponent(testEntity, TriggerComponent)
+ assertTriggerComponentEqual(data, TriggerComponentDefaults)
+ })
+ }) // << onSet
+
+ describe('toJSON', () => {
+ let testEntity = UndefinedEntity
+
+ beforeEach(async () => {
+ createEngine()
+ await Physics.load()
+ testEntity = createEntity()
+ setComponent(testEntity, TriggerComponent)
+ })
+
+ afterEach(() => {
+ removeEntity(testEntity)
+ return destroyEngine()
+ })
+
+ it("should serialize the component's data correctly", () => {
+ const json = serializeComponent(testEntity, TriggerComponent)
+ assert.deepEqual(json, TriggerComponentDefaults)
+ })
+ }) // << toJson
+
+ describe('reactor', () => {
+ let testEntity = UndefinedEntity
+ let physicsWorld: PhysicsWorld
+ let physicsWorldEntity = UndefinedEntity
+
+ beforeEach(async () => {
+ createEngine()
+ await Physics.load()
+ physicsWorldEntity = createEntity()
+ setComponent(physicsWorldEntity, UUIDComponent, UUIDComponent.generateUUID())
+ setComponent(physicsWorldEntity, SceneComponent)
+ setComponent(physicsWorldEntity, TransformComponent)
+ setComponent(physicsWorldEntity, EntityTreeComponent)
+ physicsWorld = Physics.createWorld(getComponent(physicsWorldEntity, UUIDComponent))
+ physicsWorld!.timestep = 1 / 60
+
+ testEntity = createEntity()
+ setComponent(testEntity, EntityTreeComponent, { parentEntity: physicsWorldEntity })
+ setComponent(testEntity, TransformComponent)
+ setComponent(testEntity, RigidBodyComponent)
+ setComponent(testEntity, ColliderComponent)
+ setComponent(testEntity, TriggerComponent)
+ })
+
+ afterEach(() => {
+ removeEntity(testEntity)
+ return destroyEngine()
+ })
+
+ it("should call Physics.setTrigger on the entity's collider when a new ColliderComponent is set", () => {
+ assertColliderComponentEquals(getComponent(testEntity, ColliderComponent), ColliderComponentDefaults)
+ removeComponent(testEntity, ColliderComponent)
+ const ColliderComponentData = {
+ shape: Shapes.Sphere,
+ mass: 3,
+ massCenter: new Vector3(1, 2, 3),
+ friction: 1.0,
+ restitution: 0.1,
+ collisionLayer: CollisionGroups.Default,
+ collisionMask: DefaultCollisionMask
+ }
+ setComponent(testEntity, ColliderComponent, ColliderComponentData)
+ assertColliderComponentEquals(getComponent(testEntity, ColliderComponent), ColliderComponentData)
+ const reactor = ColliderComponent.reactorMap.get(testEntity)!
+ assert.ok(reactor.isRunning)
+ const collider = physicsWorld.Colliders.get(testEntity)!
+ assert.ok(collider)
+ assert.ok(collider.isSensor())
+ })
+ }) // << reactor
+})
diff --git a/packages/spatial/src/physics/systems/PhysicsPreTransformSystem.ts b/packages/spatial/src/physics/systems/PhysicsPreTransformSystem.ts
index 00e139c75f..98f379f3dd 100644
--- a/packages/spatial/src/physics/systems/PhysicsPreTransformSystem.ts
+++ b/packages/spatial/src/physics/systems/PhysicsPreTransformSystem.ts
@@ -30,7 +30,6 @@ import { ECSState } from '@etherealengine/ecs/src/ECSState'
import { getState } from '@etherealengine/hyperflux'
import { Vector3_One, Vector3_Zero } from '../../common/constants/MathConstants'
-import { SceneComponent } from '../../renderer/components/SceneComponents'
import { EntityTreeComponent, getAncestorWithComponent, iterateEntityNode } from '../../transform/components/EntityTree'
import { TransformComponent } from '../../transform/components/TransformComponent'
import { computeTransformMatrix, isDirty, TransformDirtyUpdateSystem } from '../../transform/systems/TransformSystem'
@@ -79,13 +78,13 @@ export const lerpTransformFromRigidbody = (entity: Entity, alpha: number) => {
const transform = getComponent(entity, TransformComponent)
- const sceneEntity = getAncestorWithComponent(entity, SceneComponent)
- const sceneTransform = getComponent(sceneEntity, TransformComponent)
- parentMatrixInverse.copy(sceneTransform.matrixWorld).invert()
+ const rigidBodyEntity = getAncestorWithComponent(entity, RigidBodyComponent)
+ const rigidBodyTransform = getComponent(rigidBodyEntity, TransformComponent)
+ parentMatrixInverse.copy(rigidBodyTransform.matrixWorld).invert()
localMatrix.compose(position, rotation, Vector3_One).premultiply(parentMatrixInverse)
localMatrix.decompose(position, rotation, scale)
transform.matrix.compose(position, rotation, transform.scale)
- transform.matrixWorld.multiplyMatrices(sceneTransform.matrixWorld, transform.matrix)
+ transform.matrixWorld.multiplyMatrices(rigidBodyTransform.matrixWorld, transform.matrix)
/** set all children dirty deeply, but set this entity to clean */
iterateEntityNode(entity, setDirty)
diff --git a/packages/spatial/src/physics/systems/PhysicsSystem.test.ts b/packages/spatial/src/physics/systems/PhysicsSystem.test.ts
index 428438a6c6..f5c696ba24 100644
--- a/packages/spatial/src/physics/systems/PhysicsSystem.test.ts
+++ b/packages/spatial/src/physics/systems/PhysicsSystem.test.ts
@@ -1,440 +1,440 @@
-// /*
-// CPAL-1.0 License
-
-// The contents of this file are subject to the Common Public Attribution License
-// Version 1.0. (the "License"); you may not use this file except in compliance
-// with the License. You may obtain a copy of the License at
-// https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE.
-// The License is based on the Mozilla Public License Version 1.1, but Sections 14
-// and 15 have been added to cover use of software over a computer network and
-// provide for limited attribution for the Original Developer. In addition,
-// Exhibit A has been modified to be consistent with Exhibit B.
-
-// Software distributed under the License is distributed on an "AS IS" basis,
-// WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the
-// specific language governing rights and limitations under the License.
-
-// The Original Code is Ethereal Engine.
-
-// The Original Developer is the Initial Developer. The Initial Developer of the
-// Original Code is the Ethereal Engine team.
-
-// All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023
-// Ethereal Engine. All Rights Reserved.
-// */
-
-// import { destroyEngine } from '@etherealengine/ecs/src/Engine'
-
-// import {
-// Entity,
-// SystemDefinitions,
-// SystemUUID,
-// UUIDComponent,
-// UndefinedEntity,
-// createEntity,
-// getComponent,
-// getMutableComponent,
-// hasComponent,
-// removeEntity,
-// setComponent
-// } from '@etherealengine/ecs'
-// import { createEngine } from '@etherealengine/ecs/src/Engine'
-// import assert from 'assert'
-// import { Quaternion, Vector3 } from 'three'
-// import { TransformComponent } from '../../SpatialModule'
-// import { Vector3_Zero } from '../../common/constants/MathConstants'
-// import { smootheLerpAlpha } from '../../common/functions/MathLerpFunctions'
-// import { SceneComponent } from '../../renderer/components/SceneComponents'
-// import { EntityTreeComponent } from '../../transform/components/EntityTree'
-// import { Physics, PhysicsWorld } from '../classes/Physics'
-// import { assertVecAllApproxNotEq, assertVecAnyApproxNotEq, assertVecApproxEq } from '../classes/Physics.test'
-// import { ColliderComponent } from '../components/ColliderComponent'
-// import { CollisionComponent } from '../components/CollisionComponent'
-// import { RigidBodyComponent } from '../components/RigidBodyComponent'
-// import { BodyTypes } from '../types/PhysicsTypes'
-// import { PhysicsSystem } from './PhysicsSystem'
-
-// // Epsilon Constants for Interpolation
-// const LerpEpsilon = 0.000001
-// /** @note three.js Quat.slerp fails tests at 6 significant figures, but passes at 5 */
-// const SLerpEpsilon = 0.00001
-
-// const Quaternion_Zero = new Quaternion(0, 0, 0, 1).normalize()
-
-// describe('smoothKinematicBody', () => {
-// /** @description Pair of `deltaTime` and `substep` values that will be used during an interpolation test */
-// type Step = { dt: number; substep: number }
-// /** @description Creates a Step object. @note Just a clarity/readability alias */
-// function createStep(dt: number, substep: number): Step {
-// return { dt, substep }
-// }
-
-// const DeltaTime = 1 / 60
-// const Start = {
-// position: new Vector3(1, 2, 3),
-// rotation: new Quaternion(0.5, 0.3, 0.2, 0.0).normalize()
-// }
-// const Final = {
-// position: new Vector3(4, 5, 6),
-// rotation: new Quaternion(0.0, 0.2, 0.8, 0.0).normalize()
-// }
-
-// /** @description List of steps that will be tested against for both the linear and smoooth interpolation tests */
-// const Step = {
-// Tenth: createStep(DeltaTime, 0.1),
-// Quarter: createStep(DeltaTime, 0.25),
-// Half: createStep(DeltaTime, 0.5),
-// One: createStep(DeltaTime, 1),
-// Two: createStep(DeltaTime, 2)
-// }
-
-// /** @description {@link Step} list, in array form */
-// const Steps = [Step.Tenth, Step.Quarter, Step.Half, Step.One, Step.Two]
-
-// /** @description List of non-zero values that {@link RigidbodyComponent.targetKinematicLerpMultiplier} will be set to during the gradual smoothing tests */
-// const KinematicMultiplierCases = [0.5, 0.25, 0.1, 0.01, 0.001, 0.0001, 2, 3, 4, 5]
-
-// /**
-// * @section Initialize/Terminate the engine, entities and physics
-// */
-// let testEntity = UndefinedEntity
-// let physicsWorld: PhysicsWorld
-// let physicsWorldEntity = UndefinedEntity
-
-// beforeEach(async () => {
-// createEngine()
-// await Physics.load()
-// physicsWorldEntity = createEntity()
-// setComponent(physicsWorldEntity, UUIDComponent, UUIDComponent.generateUUID())
-// setComponent(physicsWorldEntity, SceneComponent)
-// setComponent(physicsWorldEntity, TransformComponent)
-// setComponent(physicsWorldEntity, EntityTreeComponent)
-// physicsWorld = Physics.createWorld(getComponent(physicsWorldEntity, UUIDComponent))
-
-// testEntity = createEntity()
-// setComponent(testEntity, EntityTreeComponent, { parentEntity: physicsWorldEntity })
-// setComponent(testEntity, TransformComponent)
-// setComponent(testEntity, RigidBodyComponent)
-// // Set the Start..Final values for interpolation
-// const body = getComponent(testEntity, RigidBodyComponent)
-// body.previousPosition.set(Start.position.x, Start.position.y, Start.position.z)
-// body.previousRotation.set(Start.rotation.x, Start.rotation.y, Start.rotation.z, Start.rotation.w)
-// body.targetKinematicPosition.set(Final.position.x, Final.position.y, Final.position.z)
-// body.targetKinematicRotation.set(Final.rotation.x, Final.rotation.y, Final.rotation.z, Final.rotation.w)
-// })
-
-// afterEach(() => {
-// removeEntity(testEntity)
-// return destroyEngine()
-// })
-
-// describe('when RigidbodyComponent.targetKinematicLerpMultiplier is set to 0 ...', () => {
-// /** @description Calculates the Deterministic Lerp value for the `@param entity`, as expected by the tests, based on the given {@link Step.substep} value */
-// function computeLerp(entity: Entity, step: Step) {
-// const body = getComponent(entity, RigidBodyComponent)
-// const result = {
-// position: body.previousPosition.clone().lerp(body.targetKinematicPosition.clone(), step.substep).clone(),
-// rotation: body.previousRotation.clone().slerp(body.targetKinematicRotation.clone(), step.substep).clone()
-// }
-// return result
-// }
-// /** @description Set the {@link RigidBodyComponent.targetKinematicLerpMultiplier} to 0 for all of the linear interpolation tests */
-// beforeEach(() => {
-// getMutableComponent(testEntity, RigidBodyComponent).targetKinematicLerpMultiplier.set(0)
-// })
-
-// it('... should apply deterministic linear interpolation to the position of the KinematicBody of the given entity', () => {
-// // Check data before
-// const body = getComponent(testEntity, RigidBodyComponent)
-// const before = body.position.clone()
-// assertVecApproxEq(before, Vector3_Zero, 3, LerpEpsilon)
-
-// // Run and Check resulting data
-// Physics.smoothKinematicBody(physicsWorld, testEntity, Step.Quarter.dt, Step.Quarter.substep)
-// const after = body.position.clone()
-// assertVecAllApproxNotEq(before, after, 3, LerpEpsilon)
-// assertVecApproxEq(after, computeLerp(testEntity, Step.Quarter).position, 3, LerpEpsilon)
-// // Check the other Step cases
-// getComponent(testEntity, RigidBodyComponent).position.set(0, 0, 0) // reset for next case
-// Physics.smoothKinematicBody(physicsWorld, testEntity, Step.Tenth.dt, Step.Tenth.substep)
-// assertVecApproxEq(body.position.clone(), computeLerp(testEntity, Step.Tenth).position, 3, LerpEpsilon)
-// getComponent(testEntity, RigidBodyComponent).position.set(0, 0, 0) // reset for next case
-// Physics.smoothKinematicBody(physicsWorld, testEntity, Step.Half.dt, Step.Half.substep)
-// assertVecApproxEq(body.position.clone(), computeLerp(testEntity, Step.Half).position, 3, LerpEpsilon)
-// getComponent(testEntity, RigidBodyComponent).position.set(0, 0, 0) // reset for next case
-// Physics.smoothKinematicBody(physicsWorld, testEntity, Step.One.dt, Step.One.substep)
-// assertVecApproxEq(body.position.clone(), computeLerp(testEntity, Step.One).position, 3, LerpEpsilon)
-// getComponent(testEntity, RigidBodyComponent).position.set(0, 0, 0) // reset for next case
-// Physics.smoothKinematicBody(physicsWorld, testEntity, Step.Two.dt, Step.Two.substep)
-// assertVecApproxEq(body.position.clone(), computeLerp(testEntity, Step.Two).position, 3, LerpEpsilon)
-// // Check substep precision Step cases
-// const TestCount = 1_000_000
-// for (let divider = 1; divider <= TestCount; divider += 1_000) {
-// const step = createStep(DeltaTime, 1 / divider)
-// getComponent(testEntity, RigidBodyComponent).position.set(0, 0, 0) // reset for next case
-// Physics.smoothKinematicBody(physicsWorld, testEntity, step.dt, step.substep)
-// assertVecApproxEq(body.position.clone(), computeLerp(testEntity, step).position, 3, LerpEpsilon)
-// }
-// })
-
-// it('... should apply deterministic spherical linear interpolation to the rotation of the KinematicBody of the given entity', () => {
-// // Check data before
-// const body = getComponent(testEntity, RigidBodyComponent)
-// const before = body.rotation.clone()
-// assertVecApproxEq(before, new Quaternion(0, 0, 0, 1), 3, SLerpEpsilon)
-
-// // Run and Check resulting data
-// Physics.smoothKinematicBody(physicsWorld, testEntity, Step.Quarter.dt, Step.Quarter.substep)
-// const after = body.rotation.clone()
-// assertVecAllApproxNotEq(before, after, 4, SLerpEpsilon)
-// assertVecApproxEq(after, computeLerp(testEntity, Step.Quarter).rotation, 4, SLerpEpsilon)
-// // Check the other Step cases
-// getComponent(testEntity, RigidBodyComponent).rotation.set(0, 0, 0, 1) // reset for next case
-// Physics.smoothKinematicBody(physicsWorld, testEntity, Step.Tenth.dt, Step.Tenth.substep)
-// assertVecApproxEq(body.rotation.clone(), computeLerp(testEntity, Step.Tenth).rotation, 4, SLerpEpsilon)
-// getComponent(testEntity, RigidBodyComponent).rotation.set(0, 0, 0, 1) // reset for next case
-// Physics.smoothKinematicBody(physicsWorld, testEntity, Step.Half.dt, Step.Half.substep)
-// assertVecApproxEq(body.rotation.clone(), computeLerp(testEntity, Step.Half).rotation, 4, SLerpEpsilon)
-// getComponent(testEntity, RigidBodyComponent).rotation.set(0, 0, 0, 1) // reset for next case
-// Physics.smoothKinematicBody(physicsWorld, testEntity, Step.One.dt, Step.One.substep)
-// assertVecApproxEq(body.rotation.clone(), computeLerp(testEntity, Step.One).rotation, 4, SLerpEpsilon)
-// getComponent(testEntity, RigidBodyComponent).rotation.set(0, 0, 0, 1) // reset for next case
-// Physics.smoothKinematicBody(physicsWorld, testEntity, Step.Two.dt, Step.Two.substep)
-// assertVecApproxEq(body.rotation.clone(), computeLerp(testEntity, Step.Two).rotation, 4, SLerpEpsilon)
-// // Check substep precision Step cases
-// const TestCount = 1_000_000
-// for (let divider = 1; divider <= TestCount; divider += 1_000) {
-// const step = createStep(DeltaTime, 1 / divider)
-// getComponent(testEntity, RigidBodyComponent).rotation.set(0, 0, 0, 1) // reset for next case
-// Physics.smoothKinematicBody(physicsWorld, testEntity, step.dt, step.substep)
-// assertVecApproxEq(body.rotation.clone(), computeLerp(testEntity, step).rotation, 4, SLerpEpsilon)
-// }
-// })
-// })
-
-// describe('when RigidbodyComponent.targetKinematicLerpMultiplier is set to a value other than 0 ...', () => {
-// type LerpData = {
-// position: { start: Vector3; final: Vector3 }
-// rotation: { start: Quaternion; final: Quaternion }
-// }
-
-// /**
-// * @description Sets the entity's {@link RigidBodyComponent.targetKinematicLerpMultiplier} property to `@param mult`
-// * @returns The `@param mult` itself */
-// function setMultiplier(entity: Entity, mult: number): number {
-// getMutableComponent(entity, RigidBodyComponent).targetKinematicLerpMultiplier.set(mult)
-// return mult
-// }
-// /**
-// * @description Sets the entity's {@link RigidBodyComponent.targetKinematicLerpMultiplier} property to `@param mult` and calculates its smooth lerp alpha
-// * @returns The exponentially smootheed Lerp Alpha value to use as `dt` in {@link smoothKinematicBody} */
-// function getAlphaWithMultiplier(entity: Entity, dt: number, mult: number): number {
-// return smootheLerpAlpha(setMultiplier(entity, mult), dt)
-// }
-
-// /** @description Computes the lerp of the (`@param start`,`@param final`) input Vectors without mutating their values */
-// function lerpNoRef(start: Vector3, final: Vector3, dt: number) {
-// return start.clone().lerp(final.clone(), dt).clone()
-// }
-// /** @description Computes the fastSlerp of the (`@param start`,`@param final`) input Quaternions without mutating their values */
-// function fastSlerpNoRef(start: Quaternion, final: Quaternion, dt: number) {
-// return start.clone().fastSlerp(final.clone(), dt).clone()
-// }
-
-// /** @description Calculates the Exponential Lerp value for the `@param data`, as expected by the tests, based on the given `@param dt` alpha value */
-// function computeELerp(data: LerpData, alpha: number) {
-// return {
-// position: lerpNoRef(data.position.start, data.position.final, alpha),
-// rotation: fastSlerpNoRef(data.rotation.start, data.rotation.final, alpha)
-// }
-// }
-
-// it('... should apply gradual smoothing (aka exponential interpolation) to the position of the KinematicBody of the given entity', () => {
-// // Check data before
-// const body = getComponent(testEntity, RigidBodyComponent)
-// const before = body.position.clone()
-// assertVecApproxEq(before, Vector3_Zero, 3, LerpEpsilon)
-
-// // Run and Check resulting data
-// // ... Infinite smoothing case
-// const MultInfinite = 1 // Multiplier 1 shouldn't change the position (aka. infinite smoothing)
-// setMultiplier(testEntity, MultInfinite)
-// Physics.smoothKinematicBody(physicsWorld, testEntity, DeltaTime, /*substep*/ 1)
-// assertVecApproxEq(before, body.position, 3, LerpEpsilon)
-
-// // ... Hardcoded case
-// setMultiplier(testEntity, 0.12345)
-// Physics.smoothKinematicBody(physicsWorld, testEntity, 1 / 60, 1)
-// const ExpectedHardcoded = { x: 0.1370581001805662, y: 0.17132262522570774, z: 0.20558715027084928 }
-// assertVecApproxEq(body.position.clone(), ExpectedHardcoded, 3)
-
-// // ... Check the other Step cases
-// for (const multiplier of KinematicMultiplierCases) {
-// for (const step of Steps) {
-// getComponent(testEntity, RigidBodyComponent).position.set(0, 0, 0) // reset for next case
-// const alpha = getAlphaWithMultiplier(testEntity, step.dt, multiplier)
-// const before = {
-// position: { start: body.position.clone(), final: body.targetKinematicPosition.clone() },
-// rotation: { start: body.rotation.clone(), final: body.targetKinematicRotation.clone() }
-// }
-// Physics.smoothKinematicBody(physicsWorld, testEntity, step.dt, step.substep)
-// assertVecApproxEq(body.position, computeELerp(before, alpha).position, 3, LerpEpsilon)
-// }
-// }
-// })
-
-// it('... should apply gradual smoothing (aka exponential interpolation) to the rotation of the KinematicBody of the given entity', () => {
-// // Check data before
-// const body = getComponent(testEntity, RigidBodyComponent)
-// const before = body.rotation.clone()
-// assertVecApproxEq(before, Quaternion_Zero, 4, SLerpEpsilon)
-
-// // Run and Check resulting data
-// // ... Infinite smoothing case
-// const MultInfinite = 1 // Multiplier 1 shouldn't change the rotation (aka. infinite smoothing)
-// setMultiplier(testEntity, MultInfinite)
-// Physics.smoothKinematicBody(physicsWorld, testEntity, DeltaTime, /*substep*/ 1)
-// assertVecApproxEq(before, body.rotation, 3, SLerpEpsilon)
-
-// // ... Hardcoded case
-// setMultiplier(testEntity, 0.12345)
-// Physics.smoothKinematicBody(physicsWorld, testEntity, 1 / 60, 1)
-// const ExpectedHardcoded = new Quaternion(0, 0.013047535062645674, 0.052190140250582696, 0.9985524073985961)
-// assertVecApproxEq(body.rotation.clone(), ExpectedHardcoded, 4)
-
-// // ... Check the other Step cases
-// for (const multiplier of KinematicMultiplierCases) {
-// for (const step of Steps) {
-// getComponent(testEntity, RigidBodyComponent).rotation.set(0, 0, 0, 1) // reset for next case
-// const alpha = getAlphaWithMultiplier(testEntity, step.dt, multiplier)
-// const before = {
-// position: { start: body.position.clone(), final: body.targetKinematicPosition.clone() },
-// rotation: { start: body.rotation.clone(), final: body.targetKinematicRotation.clone() }
-// } as LerpData
-// Physics.smoothKinematicBody(physicsWorld, testEntity, step.dt, step.substep)
-// assertVecApproxEq(body.rotation, computeELerp(before, alpha).rotation, 3, SLerpEpsilon)
-// }
-// }
-// })
-// })
-// })
-
-// describe('PhysicsSystem', () => {
-// describe('IDs', () => {
-// it("should define the PhysicsSystem's UUID with the expected value", () => {
-// assert.equal(PhysicsSystem, 'ee.engine.PhysicsSystem' as SystemUUID)
-// })
-// })
-
-// describe('execute', () => {
-// let testEntity = UndefinedEntity
-// let physicsWorld: PhysicsWorld
-// let physicsWorldEntity = UndefinedEntity
-
-// beforeEach(async () => {
-// createEngine()
-// await Physics.load()
-// physicsWorldEntity = createEntity()
-// setComponent(physicsWorldEntity, UUIDComponent, UUIDComponent.generateUUID())
-// setComponent(physicsWorldEntity, SceneComponent)
-// setComponent(physicsWorldEntity, TransformComponent)
-// setComponent(physicsWorldEntity, EntityTreeComponent)
-// physicsWorld = Physics.createWorld(getComponent(physicsWorldEntity, UUIDComponent))
-// physicsWorld.timestep = 1 / 60
-
-// testEntity = createEntity()
-// setComponent(testEntity, EntityTreeComponent, { parentEntity: physicsWorldEntity })
-// setComponent(testEntity, TransformComponent)
-// setComponent(testEntity, RigidBodyComponent, { type: BodyTypes.Dynamic })
-// setComponent(testEntity, ColliderComponent)
-// })
-
-// afterEach(() => {
-// removeEntity(testEntity)
-// return destroyEngine()
-// })
-
-// const physicsSystemExecute = SystemDefinitions.get(PhysicsSystem)!.execute
-
-// it('should step the physics', () => {
-// const testImpulse = new Vector3(1, 2, 3)
-// const beforeBody = physicsWorld.Rigidbodies.get(testEntity)
-// assert.ok(beforeBody)
-// const before = beforeBody.linvel()
-// assertVecApproxEq(before, Vector3_Zero, 3)
-// Physics.applyImpulse(physicsWorld, testEntity, testImpulse)
-// physicsSystemExecute()
-// const afterBody = physicsWorld.Rigidbodies.get(testEntity)
-// assert.ok(afterBody)
-// const after = afterBody.linvel()
-// assertVecAllApproxNotEq(after, before, 3)
-// })
-
-// function cloneRigidBodyPoseData(entity: Entity) {
-// const body = getComponent(testEntity, RigidBodyComponent)
-// return {
-// previousPosition: body.previousPosition.clone(),
-// previousRotation: body.previousRotation.clone(),
-// position: body.position.clone(),
-// rotation: body.rotation.clone(),
-// targetKinematicPosition: body.targetKinematicPosition.clone(),
-// targetKinematicRotation: body.targetKinematicRotation.clone(),
-// linearVelocity: body.linearVelocity.clone(),
-// angularVelocity: body.angularVelocity.clone()
-// }
-// }
-
-// it('should update poses on the ECS', () => {
-// const testImpulse = new Vector3(1, 2, 3)
-// const before = cloneRigidBodyPoseData(testEntity)
-// const body = getComponent(testEntity, RigidBodyComponent)
-// assertVecApproxEq(before.previousPosition, body.previousPosition.clone(), 3)
-// assertVecApproxEq(before.previousRotation, body.previousRotation.clone(), 3)
-// assertVecApproxEq(before.position, body.position.clone(), 3)
-// assertVecApproxEq(before.rotation, body.rotation.clone(), 4)
-// assertVecApproxEq(before.targetKinematicPosition, body.targetKinematicPosition.clone(), 3)
-// assertVecApproxEq(before.targetKinematicRotation, body.targetKinematicRotation.clone(), 4)
-// assertVecApproxEq(before.linearVelocity, body.linearVelocity.clone(), 3)
-// assertVecApproxEq(before.angularVelocity, body.angularVelocity.clone(), 3)
-
-// Physics.applyImpulse(physicsWorld, testEntity, testImpulse)
-// physicsSystemExecute()
-
-// const after = cloneRigidBodyPoseData(testEntity)
-// assertVecAnyApproxNotEq(after.previousPosition, before.previousPosition, 3)
-// assertVecAnyApproxNotEq(after.previousRotation, before.previousRotation, 3)
-// assertVecAnyApproxNotEq(after.position, before.position, 3)
-// assertVecAnyApproxNotEq(after.rotation, before.rotation, 4)
-// assertVecAnyApproxNotEq(after.targetKinematicPosition, before.targetKinematicPosition, 3)
-// assertVecAnyApproxNotEq(after.targetKinematicRotation, before.targetKinematicRotation, 4)
-// assertVecAnyApproxNotEq(after.linearVelocity, before.linearVelocity, 3)
-// assertVecAnyApproxNotEq(after.angularVelocity, before.angularVelocity, 3)
-// })
-
-// it('should update collisions on the ECS', () => {
-// const testImpulse = new Vector3(1, 2, 3)
-// const entity1 = createEntity()
-// setComponent(entity1, TransformComponent)
-// setComponent(entity1, RigidBodyComponent, { type: BodyTypes.Dynamic })
-// setComponent(entity1, ColliderComponent)
-// const entity2 = createEntity()
-// setComponent(entity2, TransformComponent)
-// setComponent(entity2, RigidBodyComponent, { type: BodyTypes.Dynamic })
-// setComponent(entity2, ColliderComponent)
-// // Check before
-// assert.ok(!hasComponent(entity1, CollisionComponent))
-// assert.ok(!hasComponent(entity2, CollisionComponent))
-
-// // Run and Check after
-// Physics.applyImpulse(physicsWorld, entity1, testImpulse)
-// physicsSystemExecute()
-// assert.ok(hasComponent(entity1, ColliderComponent))
-// assert.ok(hasComponent(entity2, ColliderComponent))
-// })
-// })
-
-// /**
-// // @note The reactor is currently just binding data onMount and onUnmount
-// // describe('reactor', () => {})
-// */
-// })
+/*
+CPAL-1.0 License
+
+The contents of this file are subject to the Common Public Attribution License
+Version 1.0. (the "License"); you may not use this file except in compliance
+with the License. You may obtain a copy of the License at
+https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE.
+The License is based on the Mozilla Public License Version 1.1, but Sections 14
+and 15 have been added to cover use of software over a computer network and
+provide for limited attribution for the Original Developer. In addition,
+Exhibit A has been modified to be consistent with Exhibit B.
+
+Software distributed under the License is distributed on an "AS IS" basis,
+WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the
+specific language governing rights and limitations under the License.
+
+The Original Code is Ethereal Engine.
+
+The Original Developer is the Initial Developer. The Initial Developer of the
+Original Code is the Ethereal Engine team.
+
+All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023
+Ethereal Engine. All Rights Reserved.
+*/
+
+import { destroyEngine } from '@etherealengine/ecs/src/Engine'
+
+import {
+ Entity,
+ SystemDefinitions,
+ SystemUUID,
+ UUIDComponent,
+ UndefinedEntity,
+ createEntity,
+ getComponent,
+ getMutableComponent,
+ hasComponent,
+ removeEntity,
+ setComponent
+} from '@etherealengine/ecs'
+import { createEngine } from '@etherealengine/ecs/src/Engine'
+import assert from 'assert'
+import { Quaternion, Vector3 } from 'three'
+import { TransformComponent } from '../../SpatialModule'
+import { Vector3_Zero } from '../../common/constants/MathConstants'
+import { smootheLerpAlpha } from '../../common/functions/MathLerpFunctions'
+import { SceneComponent } from '../../renderer/components/SceneComponents'
+import { EntityTreeComponent } from '../../transform/components/EntityTree'
+import { Physics, PhysicsWorld } from '../classes/Physics'
+import { assertVecAllApproxNotEq, assertVecAnyApproxNotEq, assertVecApproxEq } from '../classes/Physics.test'
+import { ColliderComponent } from '../components/ColliderComponent'
+import { CollisionComponent } from '../components/CollisionComponent'
+import { RigidBodyComponent } from '../components/RigidBodyComponent'
+import { BodyTypes } from '../types/PhysicsTypes'
+import { PhysicsSystem } from './PhysicsSystem'
+
+// Epsilon Constants for Interpolation
+const LerpEpsilon = 0.000001
+/** @note three.js Quat.slerp fails tests at 6 significant figures, but passes at 5 */
+const SLerpEpsilon = 0.00001
+
+const Quaternion_Zero = new Quaternion(0, 0, 0, 1).normalize()
+
+describe('smoothKinematicBody', () => {
+ /** @description Pair of `deltaTime` and `substep` values that will be used during an interpolation test */
+ type Step = { dt: number; substep: number }
+ /** @description Creates a Step object. @note Just a clarity/readability alias */
+ function createStep(dt: number, substep: number): Step {
+ return { dt, substep }
+ }
+
+ const DeltaTime = 1 / 60
+ const Start = {
+ position: new Vector3(1, 2, 3),
+ rotation: new Quaternion(0.5, 0.3, 0.2, 0.0).normalize()
+ }
+ const Final = {
+ position: new Vector3(4, 5, 6),
+ rotation: new Quaternion(0.0, 0.2, 0.8, 0.0).normalize()
+ }
+
+ /** @description List of steps that will be tested against for both the linear and smoooth interpolation tests */
+ const Step = {
+ Tenth: createStep(DeltaTime, 0.1),
+ Quarter: createStep(DeltaTime, 0.25),
+ Half: createStep(DeltaTime, 0.5),
+ One: createStep(DeltaTime, 1),
+ Two: createStep(DeltaTime, 2)
+ }
+
+ /** @description {@link Step} list, in array form */
+ const Steps = [Step.Tenth, Step.Quarter, Step.Half, Step.One, Step.Two]
+
+ /** @description List of non-zero values that {@link RigidbodyComponent.targetKinematicLerpMultiplier} will be set to during the gradual smoothing tests */
+ const KinematicMultiplierCases = [0.5, 0.25, 0.1, 0.01, 0.001, 0.0001, 2, 3, 4, 5]
+
+ /**
+ * @section Initialize/Terminate the engine, entities and physics
+ */
+ let testEntity = UndefinedEntity
+ let physicsWorld: PhysicsWorld
+ let physicsWorldEntity = UndefinedEntity
+
+ beforeEach(async () => {
+ createEngine()
+ await Physics.load()
+ physicsWorldEntity = createEntity()
+ setComponent(physicsWorldEntity, UUIDComponent, UUIDComponent.generateUUID())
+ setComponent(physicsWorldEntity, SceneComponent)
+ setComponent(physicsWorldEntity, TransformComponent)
+ setComponent(physicsWorldEntity, EntityTreeComponent)
+ physicsWorld = Physics.createWorld(getComponent(physicsWorldEntity, UUIDComponent))
+
+ testEntity = createEntity()
+ setComponent(testEntity, EntityTreeComponent, { parentEntity: physicsWorldEntity })
+ setComponent(testEntity, TransformComponent)
+ setComponent(testEntity, RigidBodyComponent)
+ // Set the Start..Final values for interpolation
+ const body = getComponent(testEntity, RigidBodyComponent)
+ body.previousPosition.set(Start.position.x, Start.position.y, Start.position.z)
+ body.previousRotation.set(Start.rotation.x, Start.rotation.y, Start.rotation.z, Start.rotation.w)
+ body.targetKinematicPosition.set(Final.position.x, Final.position.y, Final.position.z)
+ body.targetKinematicRotation.set(Final.rotation.x, Final.rotation.y, Final.rotation.z, Final.rotation.w)
+ })
+
+ afterEach(() => {
+ removeEntity(testEntity)
+ return destroyEngine()
+ })
+
+ describe('when RigidbodyComponent.targetKinematicLerpMultiplier is set to 0 ...', () => {
+ /** @description Calculates the Deterministic Lerp value for the `@param entity`, as expected by the tests, based on the given {@link Step.substep} value */
+ function computeLerp(entity: Entity, step: Step) {
+ const body = getComponent(entity, RigidBodyComponent)
+ const result = {
+ position: body.previousPosition.clone().lerp(body.targetKinematicPosition.clone(), step.substep).clone(),
+ rotation: body.previousRotation.clone().slerp(body.targetKinematicRotation.clone(), step.substep).clone()
+ }
+ return result
+ }
+ /** @description Set the {@link RigidBodyComponent.targetKinematicLerpMultiplier} to 0 for all of the linear interpolation tests */
+ beforeEach(() => {
+ getMutableComponent(testEntity, RigidBodyComponent).targetKinematicLerpMultiplier.set(0)
+ })
+
+ it('... should apply deterministic linear interpolation to the position of the KinematicBody of the given entity', () => {
+ // Check data before
+ const body = getComponent(testEntity, RigidBodyComponent)
+ const before = body.position.clone()
+ assertVecApproxEq(before, Vector3_Zero, 3, LerpEpsilon)
+
+ // Run and Check resulting data
+ Physics.smoothKinematicBody(physicsWorld, testEntity, Step.Quarter.dt, Step.Quarter.substep)
+ const after = body.position.clone()
+ assertVecAllApproxNotEq(before, after, 3, LerpEpsilon)
+ assertVecApproxEq(after, computeLerp(testEntity, Step.Quarter).position, 3, LerpEpsilon)
+ // Check the other Step cases
+ getComponent(testEntity, RigidBodyComponent).position.set(0, 0, 0) // reset for next case
+ Physics.smoothKinematicBody(physicsWorld, testEntity, Step.Tenth.dt, Step.Tenth.substep)
+ assertVecApproxEq(body.position.clone(), computeLerp(testEntity, Step.Tenth).position, 3, LerpEpsilon)
+ getComponent(testEntity, RigidBodyComponent).position.set(0, 0, 0) // reset for next case
+ Physics.smoothKinematicBody(physicsWorld, testEntity, Step.Half.dt, Step.Half.substep)
+ assertVecApproxEq(body.position.clone(), computeLerp(testEntity, Step.Half).position, 3, LerpEpsilon)
+ getComponent(testEntity, RigidBodyComponent).position.set(0, 0, 0) // reset for next case
+ Physics.smoothKinematicBody(physicsWorld, testEntity, Step.One.dt, Step.One.substep)
+ assertVecApproxEq(body.position.clone(), computeLerp(testEntity, Step.One).position, 3, LerpEpsilon)
+ getComponent(testEntity, RigidBodyComponent).position.set(0, 0, 0) // reset for next case
+ Physics.smoothKinematicBody(physicsWorld, testEntity, Step.Two.dt, Step.Two.substep)
+ assertVecApproxEq(body.position.clone(), computeLerp(testEntity, Step.Two).position, 3, LerpEpsilon)
+ // Check substep precision Step cases
+ const TestCount = 1_000_000
+ for (let divider = 1; divider <= TestCount; divider += 1_000) {
+ const step = createStep(DeltaTime, 1 / divider)
+ getComponent(testEntity, RigidBodyComponent).position.set(0, 0, 0) // reset for next case
+ Physics.smoothKinematicBody(physicsWorld, testEntity, step.dt, step.substep)
+ assertVecApproxEq(body.position.clone(), computeLerp(testEntity, step).position, 3, LerpEpsilon)
+ }
+ })
+
+ it('... should apply deterministic spherical linear interpolation to the rotation of the KinematicBody of the given entity', () => {
+ // Check data before
+ const body = getComponent(testEntity, RigidBodyComponent)
+ const before = body.rotation.clone()
+ assertVecApproxEq(before, new Quaternion(0, 0, 0, 1), 3, SLerpEpsilon)
+
+ // Run and Check resulting data
+ Physics.smoothKinematicBody(physicsWorld, testEntity, Step.Quarter.dt, Step.Quarter.substep)
+ const after = body.rotation.clone()
+ assertVecAllApproxNotEq(before, after, 4, SLerpEpsilon)
+ assertVecApproxEq(after, computeLerp(testEntity, Step.Quarter).rotation, 4, SLerpEpsilon)
+ // Check the other Step cases
+ getComponent(testEntity, RigidBodyComponent).rotation.set(0, 0, 0, 1) // reset for next case
+ Physics.smoothKinematicBody(physicsWorld, testEntity, Step.Tenth.dt, Step.Tenth.substep)
+ assertVecApproxEq(body.rotation.clone(), computeLerp(testEntity, Step.Tenth).rotation, 4, SLerpEpsilon)
+ getComponent(testEntity, RigidBodyComponent).rotation.set(0, 0, 0, 1) // reset for next case
+ Physics.smoothKinematicBody(physicsWorld, testEntity, Step.Half.dt, Step.Half.substep)
+ assertVecApproxEq(body.rotation.clone(), computeLerp(testEntity, Step.Half).rotation, 4, SLerpEpsilon)
+ getComponent(testEntity, RigidBodyComponent).rotation.set(0, 0, 0, 1) // reset for next case
+ Physics.smoothKinematicBody(physicsWorld, testEntity, Step.One.dt, Step.One.substep)
+ assertVecApproxEq(body.rotation.clone(), computeLerp(testEntity, Step.One).rotation, 4, SLerpEpsilon)
+ getComponent(testEntity, RigidBodyComponent).rotation.set(0, 0, 0, 1) // reset for next case
+ Physics.smoothKinematicBody(physicsWorld, testEntity, Step.Two.dt, Step.Two.substep)
+ assertVecApproxEq(body.rotation.clone(), computeLerp(testEntity, Step.Two).rotation, 4, SLerpEpsilon)
+ // Check substep precision Step cases
+ const TestCount = 1_000_000
+ for (let divider = 1; divider <= TestCount; divider += 1_000) {
+ const step = createStep(DeltaTime, 1 / divider)
+ getComponent(testEntity, RigidBodyComponent).rotation.set(0, 0, 0, 1) // reset for next case
+ Physics.smoothKinematicBody(physicsWorld, testEntity, step.dt, step.substep)
+ assertVecApproxEq(body.rotation.clone(), computeLerp(testEntity, step).rotation, 4, SLerpEpsilon)
+ }
+ })
+ })
+
+ describe('when RigidbodyComponent.targetKinematicLerpMultiplier is set to a value other than 0 ...', () => {
+ type LerpData = {
+ position: { start: Vector3; final: Vector3 }
+ rotation: { start: Quaternion; final: Quaternion }
+ }
+
+ /**
+ * @description Sets the entity's {@link RigidBodyComponent.targetKinematicLerpMultiplier} property to `@param mult`
+ * @returns The `@param mult` itself */
+ function setMultiplier(entity: Entity, mult: number): number {
+ getMutableComponent(entity, RigidBodyComponent).targetKinematicLerpMultiplier.set(mult)
+ return mult
+ }
+ /**
+ * @description Sets the entity's {@link RigidBodyComponent.targetKinematicLerpMultiplier} property to `@param mult` and calculates its smooth lerp alpha
+ * @returns The exponentially smootheed Lerp Alpha value to use as `dt` in {@link smoothKinematicBody} */
+ function getAlphaWithMultiplier(entity: Entity, dt: number, mult: number): number {
+ return smootheLerpAlpha(setMultiplier(entity, mult), dt)
+ }
+
+ /** @description Computes the lerp of the (`@param start`,`@param final`) input Vectors without mutating their values */
+ function lerpNoRef(start: Vector3, final: Vector3, dt: number) {
+ return start.clone().lerp(final.clone(), dt).clone()
+ }
+ /** @description Computes the fastSlerp of the (`@param start`,`@param final`) input Quaternions without mutating their values */
+ function fastSlerpNoRef(start: Quaternion, final: Quaternion, dt: number) {
+ return start.clone().fastSlerp(final.clone(), dt).clone()
+ }
+
+ /** @description Calculates the Exponential Lerp value for the `@param data`, as expected by the tests, based on the given `@param dt` alpha value */
+ function computeELerp(data: LerpData, alpha: number) {
+ return {
+ position: lerpNoRef(data.position.start, data.position.final, alpha),
+ rotation: fastSlerpNoRef(data.rotation.start, data.rotation.final, alpha)
+ }
+ }
+
+ it('... should apply gradual smoothing (aka exponential interpolation) to the position of the KinematicBody of the given entity', () => {
+ // Check data before
+ const body = getComponent(testEntity, RigidBodyComponent)
+ const before = body.position.clone()
+ assertVecApproxEq(before, Vector3_Zero, 3, LerpEpsilon)
+
+ // Run and Check resulting data
+ // ... Infinite smoothing case
+ const MultInfinite = 1 // Multiplier 1 shouldn't change the position (aka. infinite smoothing)
+ setMultiplier(testEntity, MultInfinite)
+ Physics.smoothKinematicBody(physicsWorld, testEntity, DeltaTime, /*substep*/ 1)
+ assertVecApproxEq(before, body.position, 3, LerpEpsilon)
+
+ // ... Hardcoded case
+ setMultiplier(testEntity, 0.12345)
+ Physics.smoothKinematicBody(physicsWorld, testEntity, 1 / 60, 1)
+ const ExpectedHardcoded = { x: 0.1370581001805662, y: 0.17132262522570774, z: 0.20558715027084928 }
+ assertVecApproxEq(body.position.clone(), ExpectedHardcoded, 3)
+
+ // ... Check the other Step cases
+ for (const multiplier of KinematicMultiplierCases) {
+ for (const step of Steps) {
+ getComponent(testEntity, RigidBodyComponent).position.set(0, 0, 0) // reset for next case
+ const alpha = getAlphaWithMultiplier(testEntity, step.dt, multiplier)
+ const before = {
+ position: { start: body.position.clone(), final: body.targetKinematicPosition.clone() },
+ rotation: { start: body.rotation.clone(), final: body.targetKinematicRotation.clone() }
+ }
+ Physics.smoothKinematicBody(physicsWorld, testEntity, step.dt, step.substep)
+ assertVecApproxEq(body.position, computeELerp(before, alpha).position, 3, LerpEpsilon)
+ }
+ }
+ })
+
+ it('... should apply gradual smoothing (aka exponential interpolation) to the rotation of the KinematicBody of the given entity', () => {
+ // Check data before
+ const body = getComponent(testEntity, RigidBodyComponent)
+ const before = body.rotation.clone()
+ assertVecApproxEq(before, Quaternion_Zero, 4, SLerpEpsilon)
+
+ // Run and Check resulting data
+ // ... Infinite smoothing case
+ const MultInfinite = 1 // Multiplier 1 shouldn't change the rotation (aka. infinite smoothing)
+ setMultiplier(testEntity, MultInfinite)
+ Physics.smoothKinematicBody(physicsWorld, testEntity, DeltaTime, /*substep*/ 1)
+ assertVecApproxEq(before, body.rotation, 3, SLerpEpsilon)
+
+ // ... Hardcoded case
+ setMultiplier(testEntity, 0.12345)
+ Physics.smoothKinematicBody(physicsWorld, testEntity, 1 / 60, 1)
+ const ExpectedHardcoded = new Quaternion(0, 0.013047535062645674, 0.052190140250582696, 0.9985524073985961)
+ assertVecApproxEq(body.rotation.clone(), ExpectedHardcoded, 4)
+
+ // ... Check the other Step cases
+ for (const multiplier of KinematicMultiplierCases) {
+ for (const step of Steps) {
+ getComponent(testEntity, RigidBodyComponent).rotation.set(0, 0, 0, 1) // reset for next case
+ const alpha = getAlphaWithMultiplier(testEntity, step.dt, multiplier)
+ const before = {
+ position: { start: body.position.clone(), final: body.targetKinematicPosition.clone() },
+ rotation: { start: body.rotation.clone(), final: body.targetKinematicRotation.clone() }
+ } as LerpData
+ Physics.smoothKinematicBody(physicsWorld, testEntity, step.dt, step.substep)
+ assertVecApproxEq(body.rotation, computeELerp(before, alpha).rotation, 3, SLerpEpsilon)
+ }
+ }
+ })
+ })
+})
+
+describe('PhysicsSystem', () => {
+ describe('IDs', () => {
+ it("should define the PhysicsSystem's UUID with the expected value", () => {
+ assert.equal(PhysicsSystem, 'ee.engine.PhysicsSystem' as SystemUUID)
+ })
+ })
+
+ describe('execute', () => {
+ let testEntity = UndefinedEntity
+ let physicsWorld: PhysicsWorld
+ let physicsWorldEntity = UndefinedEntity
+
+ beforeEach(async () => {
+ createEngine()
+ await Physics.load()
+ physicsWorldEntity = createEntity()
+ setComponent(physicsWorldEntity, UUIDComponent, UUIDComponent.generateUUID())
+ setComponent(physicsWorldEntity, SceneComponent)
+ setComponent(physicsWorldEntity, TransformComponent)
+ setComponent(physicsWorldEntity, EntityTreeComponent)
+ physicsWorld = Physics.createWorld(getComponent(physicsWorldEntity, UUIDComponent))
+ physicsWorld.timestep = 1 / 60
+
+ testEntity = createEntity()
+ setComponent(testEntity, EntityTreeComponent, { parentEntity: physicsWorldEntity })
+ setComponent(testEntity, TransformComponent)
+ setComponent(testEntity, RigidBodyComponent, { type: BodyTypes.Dynamic })
+ setComponent(testEntity, ColliderComponent)
+ })
+
+ afterEach(() => {
+ removeEntity(testEntity)
+ return destroyEngine()
+ })
+
+ const physicsSystemExecute = SystemDefinitions.get(PhysicsSystem)!.execute
+
+ it('should step the physics', () => {
+ const testImpulse = new Vector3(1, 2, 3)
+ const beforeBody = physicsWorld.Rigidbodies.get(testEntity)
+ assert.ok(beforeBody)
+ const before = beforeBody.linvel()
+ assertVecApproxEq(before, Vector3_Zero, 3)
+ Physics.applyImpulse(physicsWorld, testEntity, testImpulse)
+ physicsSystemExecute()
+ const afterBody = physicsWorld.Rigidbodies.get(testEntity)
+ assert.ok(afterBody)
+ const after = afterBody.linvel()
+ assertVecAllApproxNotEq(after, before, 3)
+ })
+
+ function cloneRigidBodyPoseData(entity: Entity) {
+ const body = getComponent(testEntity, RigidBodyComponent)
+ return {
+ previousPosition: body.previousPosition.clone(),
+ previousRotation: body.previousRotation.clone(),
+ position: body.position.clone(),
+ rotation: body.rotation.clone(),
+ targetKinematicPosition: body.targetKinematicPosition.clone(),
+ targetKinematicRotation: body.targetKinematicRotation.clone(),
+ linearVelocity: body.linearVelocity.clone(),
+ angularVelocity: body.angularVelocity.clone()
+ }
+ }
+
+ it('should update poses on the ECS', () => {
+ const testImpulse = new Vector3(1, 2, 3)
+ const before = cloneRigidBodyPoseData(testEntity)
+ const body = getComponent(testEntity, RigidBodyComponent)
+ assertVecApproxEq(before.previousPosition, body.previousPosition.clone(), 3)
+ assertVecApproxEq(before.previousRotation, body.previousRotation.clone(), 3)
+ assertVecApproxEq(before.position, body.position.clone(), 3)
+ assertVecApproxEq(before.rotation, body.rotation.clone(), 4)
+ assertVecApproxEq(before.targetKinematicPosition, body.targetKinematicPosition.clone(), 3)
+ assertVecApproxEq(before.targetKinematicRotation, body.targetKinematicRotation.clone(), 4)
+ assertVecApproxEq(before.linearVelocity, body.linearVelocity.clone(), 3)
+ assertVecApproxEq(before.angularVelocity, body.angularVelocity.clone(), 3)
+
+ Physics.applyImpulse(physicsWorld, testEntity, testImpulse)
+ physicsSystemExecute()
+
+ const after = cloneRigidBodyPoseData(testEntity)
+ assertVecAnyApproxNotEq(after.previousPosition, before.previousPosition, 3)
+ assertVecAnyApproxNotEq(after.previousRotation, before.previousRotation, 3)
+ assertVecAnyApproxNotEq(after.position, before.position, 3)
+ assertVecAnyApproxNotEq(after.rotation, before.rotation, 4)
+ assertVecAnyApproxNotEq(after.targetKinematicPosition, before.targetKinematicPosition, 3)
+ assertVecAnyApproxNotEq(after.targetKinematicRotation, before.targetKinematicRotation, 4)
+ assertVecAnyApproxNotEq(after.linearVelocity, before.linearVelocity, 3)
+ assertVecAnyApproxNotEq(after.angularVelocity, before.angularVelocity, 3)
+ })
+
+ it('should update collisions on the ECS', () => {
+ const testImpulse = new Vector3(1, 2, 3)
+ const entity1 = createEntity()
+ setComponent(entity1, TransformComponent)
+ setComponent(entity1, RigidBodyComponent, { type: BodyTypes.Dynamic })
+ setComponent(entity1, ColliderComponent)
+ const entity2 = createEntity()
+ setComponent(entity2, TransformComponent)
+ setComponent(entity2, RigidBodyComponent, { type: BodyTypes.Dynamic })
+ setComponent(entity2, ColliderComponent)
+ // Check before
+ assert.ok(!hasComponent(entity1, CollisionComponent))
+ assert.ok(!hasComponent(entity2, CollisionComponent))
+
+ // Run and Check after
+ Physics.applyImpulse(physicsWorld, entity1, testImpulse)
+ physicsSystemExecute()
+ assert.ok(hasComponent(entity1, ColliderComponent))
+ assert.ok(hasComponent(entity2, ColliderComponent))
+ })
+ })
+
+ /**
+ // @note The reactor is currently just binding data onMount and onUnmount
+ // describe('reactor', () => {})
+ */
+})
diff --git a/packages/spatial/src/physics/systems/TriggerSystem.test.ts b/packages/spatial/src/physics/systems/TriggerSystem.test.ts
index 13d0b72ff0..de73dcb2e9 100644
--- a/packages/spatial/src/physics/systems/TriggerSystem.test.ts
+++ b/packages/spatial/src/physics/systems/TriggerSystem.test.ts
@@ -1,268 +1,268 @@
-// /*
-// CPAL-1.0 License
+/*
+CPAL-1.0 License
-// The contents of this file are subject to the Common Public Attribution License
-// Version 1.0. (the "License"); you may not use this file except in compliance
-// with the License. You may obtain a copy of the License at
-// https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE.
-// The License is based on the Mozilla Public License Version 1.1, but Sections 14
-// and 15 have been added to cover use of software over a computer network and
-// provide for limited attribution for the Original Developer. In addition,
-// Exhibit A has been modified to be consistent with Exhibit B.
+The contents of this file are subject to the Common Public Attribution License
+Version 1.0. (the "License"); you may not use this file except in compliance
+with the License. You may obtain a copy of the License at
+https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE.
+The License is based on the Mozilla Public License Version 1.1, but Sections 14
+and 15 have been added to cover use of software over a computer network and
+provide for limited attribution for the Original Developer. In addition,
+Exhibit A has been modified to be consistent with Exhibit B.
-// Software distributed under the License is distributed on an "AS IS" basis,
-// WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the
-// specific language governing rights and limitations under the License.
+Software distributed under the License is distributed on an "AS IS" basis,
+WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the
+specific language governing rights and limitations under the License.
-// The Original Code is Ethereal Engine.
+The Original Code is Ethereal Engine.
-// The Original Developer is the Initial Developer. The Initial Developer of the
-// Original Code is the Ethereal Engine team.
+The Original Developer is the Initial Developer. The Initial Developer of the
+Original Code is the Ethereal Engine team.
-// All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023
-// Ethereal Engine. All Rights Reserved.
-// */
+All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023
+Ethereal Engine. All Rights Reserved.
+*/
-// import assert from 'assert'
+import assert from 'assert'
-// import {
-// EntityUUID,
-// SystemDefinitions,
-// SystemUUID,
-// UUIDComponent,
-// UndefinedEntity,
-// createEngine,
-// createEntity,
-// destroyEngine,
-// getComponent,
-// hasComponent,
-// removeComponent,
-// removeEntity,
-// setComponent
-// } from '@etherealengine/ecs'
-// import { TransformComponent } from '../../SpatialModule'
-// import { setCallback } from '../../common/CallbackComponent'
-// import { SceneComponent } from '../../renderer/components/SceneComponents'
-// import { EntityTreeComponent } from '../../transform/components/EntityTree'
-// import { Physics, PhysicsWorld } from '../classes/Physics'
-// import { ColliderComponent } from '../components/ColliderComponent'
-// import { CollisionComponent } from '../components/CollisionComponent'
-// import { RigidBodyComponent } from '../components/RigidBodyComponent'
-// import { TriggerComponent } from '../components/TriggerComponent'
-// import { ColliderHitEvent, CollisionEvents } from '../types/PhysicsTypes'
-// import { TriggerSystem, triggerEnter, triggerExit } from './TriggerSystem'
+import {
+ EntityUUID,
+ SystemDefinitions,
+ SystemUUID,
+ UUIDComponent,
+ UndefinedEntity,
+ createEngine,
+ createEntity,
+ destroyEngine,
+ getComponent,
+ hasComponent,
+ removeComponent,
+ removeEntity,
+ setComponent
+} from '@etherealengine/ecs'
+import { TransformComponent } from '../../SpatialModule'
+import { setCallback } from '../../common/CallbackComponent'
+import { SceneComponent } from '../../renderer/components/SceneComponents'
+import { EntityTreeComponent } from '../../transform/components/EntityTree'
+import { Physics, PhysicsWorld } from '../classes/Physics'
+import { ColliderComponent } from '../components/ColliderComponent'
+import { CollisionComponent } from '../components/CollisionComponent'
+import { RigidBodyComponent } from '../components/RigidBodyComponent'
+import { TriggerComponent } from '../components/TriggerComponent'
+import { ColliderHitEvent, CollisionEvents } from '../types/PhysicsTypes'
+import { TriggerSystem, triggerEnter, triggerExit } from './TriggerSystem'
-// describe('TriggerSystem', () => {
-// describe('IDs', () => {
-// it("should define the TriggerSystem's UUID with the expected value", () => {
-// assert.equal(TriggerSystem, 'ee.engine.TriggerSystem' as SystemUUID)
-// })
-// })
+describe('TriggerSystem', () => {
+ describe('IDs', () => {
+ it("should define the TriggerSystem's UUID with the expected value", () => {
+ assert.equal(TriggerSystem, 'ee.engine.TriggerSystem' as SystemUUID)
+ })
+ })
-// const InvalidEntityUUID = 'dummyID-123456' as EntityUUID
+ const InvalidEntityUUID = 'dummyID-123456' as EntityUUID
-// /** @todo Refactor: Simplify by using sinon.spy functions */
-// const EnterStartValue = 42 // Start testOnEnter at 42
-// let enterVal = EnterStartValue
-// const TestOnEnterName = 'test.onEnter'
-// function testOnEnter(ent1, ent2) {
-// ++enterVal
-// }
+ /** @todo Refactor: Simplify by using sinon.spy functions */
+ const EnterStartValue = 42 // Start testOnEnter at 42
+ let enterVal = EnterStartValue
+ const TestOnEnterName = 'test.onEnter'
+ function testOnEnter(ent1, ent2) {
+ ++enterVal
+ }
-// /** @todo Refactor: Simplify by using sinon.spy functions */
-// const ExitStartValue = 10_042 // Start testOnExit at 10_042
-// let exitVal = ExitStartValue
-// const TestOnExitName = 'test.onExit'
-// function testOnExit(ent1, ent2) {
-// ++exitVal
-// }
+ /** @todo Refactor: Simplify by using sinon.spy functions */
+ const ExitStartValue = 10_042 // Start testOnExit at 10_042
+ let exitVal = ExitStartValue
+ const TestOnExitName = 'test.onExit'
+ function testOnExit(ent1, ent2) {
+ ++exitVal
+ }
-// let triggerEntity = UndefinedEntity
-// let targetEntity = UndefinedEntity
-// let testEntity = UndefinedEntity
-// let targetEntityUUID = '' as EntityUUID
-// let physicsWorld: PhysicsWorld
-// let physicsWorldEntity = UndefinedEntity
+ let triggerEntity = UndefinedEntity
+ let targetEntity = UndefinedEntity
+ let testEntity = UndefinedEntity
+ let targetEntityUUID = '' as EntityUUID
+ let physicsWorld: PhysicsWorld
+ let physicsWorldEntity = UndefinedEntity
-// beforeEach(async () => {
-// createEngine()
-// await Physics.load()
-// physicsWorldEntity = createEntity()
-// setComponent(physicsWorldEntity, UUIDComponent, UUIDComponent.generateUUID())
-// setComponent(physicsWorldEntity, SceneComponent)
-// setComponent(physicsWorldEntity, TransformComponent)
-// setComponent(physicsWorldEntity, EntityTreeComponent)
-// physicsWorld = Physics.createWorld(getComponent(physicsWorldEntity, UUIDComponent))
-// physicsWorld.timestep = 1 / 60
+ beforeEach(async () => {
+ createEngine()
+ await Physics.load()
+ physicsWorldEntity = createEntity()
+ setComponent(physicsWorldEntity, UUIDComponent, UUIDComponent.generateUUID())
+ setComponent(physicsWorldEntity, SceneComponent)
+ setComponent(physicsWorldEntity, TransformComponent)
+ setComponent(physicsWorldEntity, EntityTreeComponent)
+ physicsWorld = Physics.createWorld(getComponent(physicsWorldEntity, UUIDComponent))
+ physicsWorld.timestep = 1 / 60
-// // Create the entity
-// testEntity = createEntity()
-// setComponent(testEntity, EntityTreeComponent, { parentEntity: physicsWorldEntity })
-// setComponent(testEntity, TransformComponent)
-// setComponent(testEntity, RigidBodyComponent)
-// setComponent(testEntity, ColliderComponent)
+ // Create the entity
+ testEntity = createEntity()
+ setComponent(testEntity, EntityTreeComponent, { parentEntity: physicsWorldEntity })
+ setComponent(testEntity, TransformComponent)
+ setComponent(testEntity, RigidBodyComponent)
+ setComponent(testEntity, ColliderComponent)
-// targetEntity = createEntity()
-// setComponent(targetEntity, UUIDComponent, UUIDComponent.generateUUID())
-// setCallback(targetEntity, TestOnEnterName, testOnEnter)
-// setCallback(targetEntity, TestOnExitName, testOnExit)
-// targetEntityUUID = getComponent(targetEntity, UUIDComponent)
+ targetEntity = createEntity()
+ setComponent(targetEntity, UUIDComponent, UUIDComponent.generateUUID())
+ setCallback(targetEntity, TestOnEnterName, testOnEnter)
+ setCallback(targetEntity, TestOnExitName, testOnExit)
+ targetEntityUUID = getComponent(targetEntity, UUIDComponent)
-// triggerEntity = createEntity()
-// setComponent(testEntity, EntityTreeComponent, { parentEntity: physicsWorldEntity })
-// setComponent(triggerEntity, TransformComponent)
-// setComponent(triggerEntity, RigidBodyComponent)
-// setComponent(triggerEntity, ColliderComponent)
-// setComponent(triggerEntity, TriggerComponent, {
-// triggers: [{ onEnter: TestOnEnterName, onExit: TestOnExitName, target: targetEntityUUID }]
-// })
-// })
+ triggerEntity = createEntity()
+ setComponent(testEntity, EntityTreeComponent, { parentEntity: physicsWorldEntity })
+ setComponent(triggerEntity, TransformComponent)
+ setComponent(triggerEntity, RigidBodyComponent)
+ setComponent(triggerEntity, ColliderComponent)
+ setComponent(triggerEntity, TriggerComponent, {
+ triggers: [{ onEnter: TestOnEnterName, onExit: TestOnExitName, target: targetEntityUUID }]
+ })
+ })
-// afterEach(() => {
-// removeEntity(testEntity)
-// removeEntity(triggerEntity)
-// removeEntity(targetEntity)
-// return destroyEngine()
-// })
+ afterEach(() => {
+ removeEntity(testEntity)
+ removeEntity(triggerEntity)
+ removeEntity(targetEntity)
+ return destroyEngine()
+ })
-// describe('triggerEnter', () => {
-// const Hit = {} as ColliderHitEvent // @todo The hitEvent argument is currently ignored in the function body
-// describe('for all entity.triggerComponent.triggers ...', () => {
-// it('... should only run if trigger.target defines the UUID of a valid entity', () => {
-// setComponent(triggerEntity, TriggerComponent, {
-// triggers: [{ onEnter: TestOnEnterName, onExit: TestOnExitName, target: InvalidEntityUUID }]
-// })
-// assert.equal(enterVal, EnterStartValue)
-// triggerEnter(triggerEntity, targetEntity, Hit)
-// assert.equal(enterVal, EnterStartValue)
-// })
+ describe('triggerEnter', () => {
+ const Hit = {} as ColliderHitEvent // @todo The hitEvent argument is currently ignored in the function body
+ describe('for all entity.triggerComponent.triggers ...', () => {
+ it('... should only run if trigger.target defines the UUID of a valid entity', () => {
+ setComponent(triggerEntity, TriggerComponent, {
+ triggers: [{ onEnter: TestOnEnterName, onExit: TestOnExitName, target: InvalidEntityUUID }]
+ })
+ assert.equal(enterVal, EnterStartValue)
+ triggerEnter(triggerEntity, targetEntity, Hit)
+ assert.equal(enterVal, EnterStartValue)
+ })
-// it('... should only run if trigger.onEnter callback has a value and is part of the target.CallbackComponent.callbacks map', () => {
-// const noEnterEntity = createEntity()
-// setComponent(noEnterEntity, UUIDComponent, UUIDComponent.generateUUID())
-// setCallback(noEnterEntity, TestOnExitName, testOnExit)
-// const noEnterEntityUUID = getComponent(noEnterEntity, UUIDComponent)
-// setComponent(triggerEntity, TriggerComponent, {
-// triggers: [{ onEnter: null, onExit: TestOnExitName, target: noEnterEntityUUID }]
-// })
-// assert.equal(enterVal, EnterStartValue)
-// triggerEnter(triggerEntity, targetEntity, Hit)
-// assert.equal(enterVal, EnterStartValue)
-// })
+ it('... should only run if trigger.onEnter callback has a value and is part of the target.CallbackComponent.callbacks map', () => {
+ const noEnterEntity = createEntity()
+ setComponent(noEnterEntity, UUIDComponent, UUIDComponent.generateUUID())
+ setCallback(noEnterEntity, TestOnExitName, testOnExit)
+ const noEnterEntityUUID = getComponent(noEnterEntity, UUIDComponent)
+ setComponent(triggerEntity, TriggerComponent, {
+ triggers: [{ onEnter: null, onExit: TestOnExitName, target: noEnterEntityUUID }]
+ })
+ assert.equal(enterVal, EnterStartValue)
+ triggerEnter(triggerEntity, targetEntity, Hit)
+ assert.equal(enterVal, EnterStartValue)
+ })
-// it('... should run the target.CallbackComponent.callbacks[trigger.onEnter] function', () => {
-// assert.equal(enterVal, EnterStartValue)
-// triggerEnter(triggerEntity, targetEntity, Hit)
-// assert.notEqual(enterVal, EnterStartValue)
-// })
-// })
-// })
+ it('... should run the target.CallbackComponent.callbacks[trigger.onEnter] function', () => {
+ assert.equal(enterVal, EnterStartValue)
+ triggerEnter(triggerEntity, targetEntity, Hit)
+ assert.notEqual(enterVal, EnterStartValue)
+ })
+ })
+ })
-// describe('triggerExit', () => {
-// const Hit = {} as ColliderHitEvent // @todo The hitEvent argument is currently ignored in the function body
-// describe('for all entity.triggerComponent.triggers ...', () => {
-// it('... should only run if trigger.target defines the UUID of a valid entity', () => {
-// setComponent(triggerEntity, TriggerComponent, {
-// triggers: [{ onEnter: TestOnEnterName, onExit: TestOnExitName, target: InvalidEntityUUID }]
-// })
-// assert.equal(exitVal, ExitStartValue)
-// triggerExit(triggerEntity, targetEntity, Hit)
-// assert.equal(exitVal, ExitStartValue)
-// })
+ describe('triggerExit', () => {
+ const Hit = {} as ColliderHitEvent // @todo The hitEvent argument is currently ignored in the function body
+ describe('for all entity.triggerComponent.triggers ...', () => {
+ it('... should only run if trigger.target defines the UUID of a valid entity', () => {
+ setComponent(triggerEntity, TriggerComponent, {
+ triggers: [{ onEnter: TestOnEnterName, onExit: TestOnExitName, target: InvalidEntityUUID }]
+ })
+ assert.equal(exitVal, ExitStartValue)
+ triggerExit(triggerEntity, targetEntity, Hit)
+ assert.equal(exitVal, ExitStartValue)
+ })
-// it('... should only run if trigger.onExit callback has a value and is part of the target.CallbackComponent.callbacks map', () => {
-// const noExitEntity = createEntity()
-// setComponent(noExitEntity, UUIDComponent, UUIDComponent.generateUUID())
-// setCallback(noExitEntity, TestOnExitName, testOnExit)
-// const noExitEntityUUID = getComponent(noExitEntity, UUIDComponent)
-// setComponent(triggerEntity, TriggerComponent, {
-// triggers: [{ onEnter: TestOnEnterName, onExit: null, target: noExitEntityUUID }]
-// })
-// assert.equal(exitVal, ExitStartValue)
-// triggerExit(triggerEntity, targetEntity, Hit)
-// assert.equal(exitVal, ExitStartValue)
-// })
+ it('... should only run if trigger.onExit callback has a value and is part of the target.CallbackComponent.callbacks map', () => {
+ const noExitEntity = createEntity()
+ setComponent(noExitEntity, UUIDComponent, UUIDComponent.generateUUID())
+ setCallback(noExitEntity, TestOnExitName, testOnExit)
+ const noExitEntityUUID = getComponent(noExitEntity, UUIDComponent)
+ setComponent(triggerEntity, TriggerComponent, {
+ triggers: [{ onEnter: TestOnEnterName, onExit: null, target: noExitEntityUUID }]
+ })
+ assert.equal(exitVal, ExitStartValue)
+ triggerExit(triggerEntity, targetEntity, Hit)
+ assert.equal(exitVal, ExitStartValue)
+ })
-// it('... should run the target.CallbackComponent.callbacks[trigger.onExit] function', () => {
-// assert.equal(exitVal, ExitStartValue)
-// triggerExit(triggerEntity, targetEntity, Hit)
-// assert.notEqual(exitVal, ExitStartValue)
-// })
-// })
-// })
+ it('... should run the target.CallbackComponent.callbacks[trigger.onExit] function', () => {
+ assert.equal(exitVal, ExitStartValue)
+ triggerExit(triggerEntity, targetEntity, Hit)
+ assert.notEqual(exitVal, ExitStartValue)
+ })
+ })
+ })
-// describe('execute', () => {
-// const triggerSystemExecute = SystemDefinitions.get(TriggerSystem)!.execute
+ describe('execute', () => {
+ const triggerSystemExecute = SystemDefinitions.get(TriggerSystem)!.execute
-// it('should only run for entities that have both a TriggerComponent and a CollisionComponent (aka. collisionQuery)', () => {
-// const triggerTestStartHit = {
-// type: CollisionEvents.TRIGGER_START,
-// bodySelf: physicsWorld.Rigidbodies.get(triggerEntity)!,
-// bodyOther: physicsWorld.Rigidbodies.get(testEntity)!,
-// shapeSelf: physicsWorld.Colliders.get(triggerEntity)!,
-// shapeOther: physicsWorld.Colliders.get(testEntity)!,
-// maxForceDirection: null,
-// totalForce: null
-// } as ColliderHitEvent
+ it('should only run for entities that have both a TriggerComponent and a CollisionComponent (aka. collisionQuery)', () => {
+ const triggerTestStartHit = {
+ type: CollisionEvents.TRIGGER_START,
+ bodySelf: physicsWorld.Rigidbodies.get(triggerEntity)!,
+ bodyOther: physicsWorld.Rigidbodies.get(testEntity)!,
+ shapeSelf: physicsWorld.Colliders.get(triggerEntity)!,
+ shapeOther: physicsWorld.Colliders.get(testEntity)!,
+ maxForceDirection: null,
+ totalForce: null
+ } as ColliderHitEvent
-// removeComponent(triggerEntity, TriggerComponent)
-// setComponent(triggerEntity, CollisionComponent)
-// const collision = getComponent(triggerEntity, CollisionComponent)
-// collision?.set(testEntity, triggerTestStartHit)
+ removeComponent(triggerEntity, TriggerComponent)
+ setComponent(triggerEntity, CollisionComponent)
+ const collision = getComponent(triggerEntity, CollisionComponent)
+ collision?.set(testEntity, triggerTestStartHit)
-// const beforeEnter = EnterStartValue + 1 // +1 because the system runs once before this test
-// const beforeExit = ExitStartValue + 1
-// assert.equal(enterVal, beforeEnter)
-// assert.equal(exitVal, beforeExit)
-// triggerSystemExecute()
-// assert.equal(enterVal, beforeEnter)
-// assert.equal(exitVal, beforeExit)
-// })
+ const beforeEnter = EnterStartValue + 1 // +1 because the system runs once before this test
+ const beforeExit = ExitStartValue + 1
+ assert.equal(enterVal, beforeEnter)
+ assert.equal(exitVal, beforeExit)
+ triggerSystemExecute()
+ assert.equal(enterVal, beforeEnter)
+ assert.equal(exitVal, beforeExit)
+ })
-// it('should run `triggerEnter` for all entities that match the collisionQuery and have a CollisionComponent', () => {
-// const triggerTestStartHit = {
-// type: CollisionEvents.TRIGGER_START,
-// bodySelf: physicsWorld.Rigidbodies.get(triggerEntity)!,
-// bodyOther: physicsWorld.Rigidbodies.get(testEntity)!,
-// shapeSelf: physicsWorld.Colliders.get(triggerEntity)!,
-// shapeOther: physicsWorld.Colliders.get(testEntity)!,
-// maxForceDirection: null,
-// totalForce: null
-// } as ColliderHitEvent
+ it('should run `triggerEnter` for all entities that match the collisionQuery and have a CollisionComponent', () => {
+ const triggerTestStartHit = {
+ type: CollisionEvents.TRIGGER_START,
+ bodySelf: physicsWorld.Rigidbodies.get(triggerEntity)!,
+ bodyOther: physicsWorld.Rigidbodies.get(testEntity)!,
+ shapeSelf: physicsWorld.Colliders.get(triggerEntity)!,
+ shapeOther: physicsWorld.Colliders.get(testEntity)!,
+ maxForceDirection: null,
+ totalForce: null
+ } as ColliderHitEvent
-// const beforeEnter = EnterStartValue + 1 // +1 because the system runs once before this test
-// assert.equal(enterVal, beforeEnter)
-// // Set a start collision and run the system
-// assert.ok(!hasComponent(triggerEntity, CollisionComponent))
-// setComponent(triggerEntity, CollisionComponent)
-// const collision = getComponent(triggerEntity, CollisionComponent)
-// collision?.set(testEntity, triggerTestStartHit)
-// triggerSystemExecute()
-// // Check after
-// assert.notEqual(enterVal, beforeEnter)
-// })
+ const beforeEnter = EnterStartValue + 1 // +1 because the system runs once before this test
+ assert.equal(enterVal, beforeEnter)
+ // Set a start collision and run the system
+ assert.ok(!hasComponent(triggerEntity, CollisionComponent))
+ setComponent(triggerEntity, CollisionComponent)
+ const collision = getComponent(triggerEntity, CollisionComponent)
+ collision?.set(testEntity, triggerTestStartHit)
+ triggerSystemExecute()
+ // Check after
+ assert.notEqual(enterVal, beforeEnter)
+ })
-// it('should run `triggerExit` for all entities that match the collisionQuery and have a CollisionComponent', () => {
-// const triggerTestEndHit = {
-// type: CollisionEvents.TRIGGER_END,
-// bodySelf: physicsWorld.Rigidbodies.get(triggerEntity)!,
-// bodyOther: physicsWorld.Rigidbodies.get(testEntity)!,
-// shapeSelf: physicsWorld.Colliders.get(triggerEntity)!,
-// shapeOther: physicsWorld.Colliders.get(testEntity)!,
-// maxForceDirection: null,
-// totalForce: null
-// } as ColliderHitEvent
+ it('should run `triggerExit` for all entities that match the collisionQuery and have a CollisionComponent', () => {
+ const triggerTestEndHit = {
+ type: CollisionEvents.TRIGGER_END,
+ bodySelf: physicsWorld.Rigidbodies.get(triggerEntity)!,
+ bodyOther: physicsWorld.Rigidbodies.get(testEntity)!,
+ shapeSelf: physicsWorld.Colliders.get(triggerEntity)!,
+ shapeOther: physicsWorld.Colliders.get(testEntity)!,
+ maxForceDirection: null,
+ totalForce: null
+ } as ColliderHitEvent
-// const beforeExit = ExitStartValue + 1 // +1 because the system runs once before this test
-// assert.equal(exitVal, beforeExit)
-// // Set an end collision and run the system
-// assert.ok(!hasComponent(triggerEntity, CollisionComponent))
-// setComponent(triggerEntity, CollisionComponent)
-// const collision = getComponent(triggerEntity, CollisionComponent)
-// collision?.set(testEntity, triggerTestEndHit)
-// triggerSystemExecute()
-// // Check after
-// assert.notEqual(exitVal, beforeExit)
-// })
-// })
-// })
+ const beforeExit = ExitStartValue + 1 // +1 because the system runs once before this test
+ assert.equal(exitVal, beforeExit)
+ // Set an end collision and run the system
+ assert.ok(!hasComponent(triggerEntity, CollisionComponent))
+ setComponent(triggerEntity, CollisionComponent)
+ const collision = getComponent(triggerEntity, CollisionComponent)
+ collision?.set(testEntity, triggerTestEndHit)
+ triggerSystemExecute()
+ // Check after
+ assert.notEqual(exitVal, beforeExit)
+ })
+ })
+})
diff --git a/packages/spatial/src/transform/components/EntityTree.tsx b/packages/spatial/src/transform/components/EntityTree.tsx
index c07a819c0b..41feecb691 100644
--- a/packages/spatial/src/transform/components/EntityTree.tsx
+++ b/packages/spatial/src/transform/components/EntityTree.tsx
@@ -384,7 +384,7 @@ export function useTreeQuery(entity: Entity) {
* @returns
*/
export function useAncestorWithComponent(entity: Entity, component: ComponentType) {
- const result = useHookstate(UndefinedEntity)
+ const result = useHookstate(() => getAncestorWithComponent(entity, component))
useImmediateEffect(() => {
let unmounted = false
diff --git a/packages/ui/src/components/editor/panels/Files/container/index.tsx b/packages/ui/src/components/editor/panels/Files/container/index.tsx
index 2f1078afb6..441eab9ae0 100644
--- a/packages/ui/src/components/editor/panels/Files/container/index.tsx
+++ b/packages/ui/src/components/editor/panels/Files/container/index.tsx
@@ -25,7 +25,6 @@ Ethereal Engine. All Rights Reserved.
import { FileThumbnailJobState } from '@etherealengine/client-core/src/common/services/FileThumbnailJobState'
import { NotificationService } from '@etherealengine/client-core/src/common/services/NotificationService'
import { PopoverState } from '@etherealengine/client-core/src/common/services/PopoverState'
-import { uploadToFeathersService } from '@etherealengine/client-core/src/util/upload'
import config from '@etherealengine/common/src/config'
import {
FileBrowserContentType,
@@ -33,12 +32,10 @@ import {
UserID,
archiverPath,
fileBrowserPath,
- fileBrowserUploadPath,
projectPath,
staticResourcePath
} from '@etherealengine/common/src/schema.type.module'
import { CommonKnownContentTypes } from '@etherealengine/common/src/utils/CommonKnownContentTypes'
-import { processFileName } from '@etherealengine/common/src/utils/processFileName'
import { Engine } from '@etherealengine/ecs'
import { AssetSelectionChangePropsType } from '@etherealengine/editor/src/components/assets/AssetsPreviewPanel'
import {
@@ -51,7 +48,11 @@ import ImageCompressionPanel from '@etherealengine/editor/src/components/assets/
import ModelCompressionPanel from '@etherealengine/editor/src/components/assets/ModelCompressionPanel'
import { DndWrapper } from '@etherealengine/editor/src/components/dnd/DndWrapper'
import { SupportedFileTypes } from '@etherealengine/editor/src/constants/AssetTypes'
-import { downloadBlobAsZip, inputFileWithAddToScene } from '@etherealengine/editor/src/functions/assetFunctions'
+import {
+ downloadBlobAsZip,
+ handleUploadFiles,
+ inputFileWithAddToScene
+} from '@etherealengine/editor/src/functions/assetFunctions'
import { bytesToSize, unique } from '@etherealengine/editor/src/functions/utils'
import { EditorState } from '@etherealengine/editor/src/services/EditorServices'
import { ClickPlacementState } from '@etherealengine/editor/src/systems/ClickPlacementSystem'
@@ -346,10 +347,8 @@ const FileBrowserContentPanel: React.FC = (props)
await moveContent(data.fullName, newName, data.path, destinationPath, false)
}
} else {
- const destinationPathCleaned = removeLeadingTrailingSlash(destinationPath)
- const folder = destinationPathCleaned //destinationPathCleaned.substring(0, destinationPathCleaned.lastIndexOf('/') + 1)
- const projectName = folder.split('/')[1]
- const relativePath = folder.replace('projects/' + projectName + '/', '')
+ const path = selectedDirectory.get(NO_PROXY).slice(1)
+ const toUpload = [] as File[]
await Promise.all(
data.files.map(async (file) => {
@@ -358,38 +357,23 @@ const FileBrowserContentPanel: React.FC = (props)
// creating directory
await fileService.create(`${destinationPath}${file.name}`)
} else {
- try {
- const name = processFileName(file.name)
- await uploadToFeathersService(fileBrowserUploadPath, [file], {
- args: [
- {
- project: projectName,
- path: relativePath + '/' + name,
- contentType: file.type
- }
- ]
- }).promise
- } catch (err) {
- NotificationService.dispatchNotify(err.message, { variant: 'error' })
- }
+ toUpload.push(file)
}
})
)
+
+ if (toUpload.length) {
+ try {
+ await handleUploadFiles(projectName, path, toUpload)
+ } catch (err) {
+ NotificationService.dispatchNotify(err.message, { variant: 'error' })
+ }
+ }
}
await refreshDirectory()
}
- function removeLeadingTrailingSlash(str) {
- if (str.startsWith('/')) {
- str = str.substring(1)
- }
- if (str.endsWith('/')) {
- str = str.substring(0, str.length - 1)
- }
- return str
- }
-
const onBackDirectory = () => {
const pattern = /([^/]+)/g
const result = selectedDirectory.value.match(pattern)