diff --git a/.eslintrc.js b/.eslintrc.js index 6c8198a3..d119cce8 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -94,8 +94,6 @@ module.exports = { "dist", // Ignore webpack config files "/configs", - // Ignore included ammo.js library - "/src/ammo.js", // Ignore static files "/static", // Ignore dependencies diff --git a/.gitmodules b/.gitmodules index 30fa6438..95fbccbe 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,6 +7,4 @@ [submodule "dependencies/emsdk"] path = dependencies/emsdk url = https://github.com/emscripten-core/emsdk -[submodule "dependencies/ammo.js"] - path = dependencies/ammo.js - url = https://github.com/kipr/ammo.js + diff --git a/configs/webpack/common.js b/configs/webpack/common.js index 7223a326..804aca76 100644 --- a/configs/webpack/common.js +++ b/configs/webpack/common.js @@ -17,7 +17,6 @@ try { const modules = ['node_modules']; if (dependencies.cpython) modules.push(resolve(dependencies.cpython)); -if (dependencies.ammo) modules.push(resolve(dependencies.ammo)); let libkiprCDocumentation = undefined; if (dependencies.libkipr_c_documentation) { @@ -142,7 +141,6 @@ module.exports = { SIMULATOR_VERSION: JSON.stringify(require('../../package.json').version), SIMULATOR_GIT_HASH: JSON.stringify(commitHash), SIMULATOR_HAS_CPYTHON: JSON.stringify(dependencies.cpython !== undefined), - SIMULATOR_HAS_AMMO: JSON.stringify(dependencies.ammo !== undefined), SIMULATOR_LIBKIPR_C_DOCUMENTATION: JSON.stringify(libkiprCDocumentation), SIMULATOR_I18N: JSON.stringify(i18n), }), diff --git a/dependencies/ammo.js b/dependencies/ammo.js deleted file mode 160000 index a8b359df..00000000 --- a/dependencies/ammo.js +++ /dev/null @@ -1 +0,0 @@ -Subproject commit a8b359dfc5edb44f9cc1b8e3e79c2755ba20a54f diff --git a/dependencies/build.py b/dependencies/build.py index 8c72735f..50eb9623 100755 --- a/dependencies/build.py +++ b/dependencies/build.py @@ -7,27 +7,27 @@ import multiprocessing def is_tool(name): - """Check whether `name` is on PATH and marked as executable.""" - from shutil import which - return which(name) is not None + """Check whether `name` is on PATH and marked as executable.""" + from shutil import which + return which(name) is not None # Sanity checks if not is_tool('cmake'): - print('CMake is not installed. Please install CMake and try again.') - exit(1) + print('CMake is not installed. Please install CMake and try again.') + exit(1) if not is_tool('make'): - print('Make is not installed. Please install Make and try again.') - exit(1) + print('Make is not installed. Please install Make and try again.') + exit(1) if not is_tool('swig'): - print('SWIG is not installed. Please install SWIG and try again.') - exit(1) + print('SWIG is not installed. Please install SWIG and try again.') + exit(1) if not is_tool('doxygen'): - print('Doxygen is not installed. Please install Doxygen and try again.') - exit(1) + print('Doxygen is not installed. Please install Doxygen and try again.') + exit(1) working_dir = pathlib.Path(__file__).parent.absolute() @@ -116,31 +116,31 @@ def is_tool(name): print('Applying cpython patches...') for patch_file in (working_dir / 'cpython_patches').glob('*.patch'): - print('Applying patch:', patch_file) - with open(patch_file) as patch: - subprocess.run( - ['patch', '-p0', '--forward'], - stdin = patch, - cwd = working_dir - ) + print('Applying patch:', patch_file) + with open(patch_file) as patch: + subprocess.run( + ['patch', '-p0', '--forward'], + stdin = patch, + cwd = working_dir + ) print('Finding latest host python...') python = 'python3' if is_tool('python3.12'): - python = 'python3.12' + python = 'python3.12' elif is_tool('python3.11'): - python = 'python3.11' + python = 'python3.11' elif is_tool('python3.10'): - python = 'python3.10' + python = 'python3.10' elif is_tool('python3.9'): - python = 'python3.9' + python = 'python3.9' elif is_tool('python3.8'): - python = 'python3.8' + python = 'python3.8' elif is_tool('python3.7'): - python = 'python3.7' + python = 'python3.7' else: - print('Warning: Python 3.7+ could not be found. Using python3. This might not work.') + print('Warning: Python 3.7+ could not be found. Using python3. This might not work.') print(f'Building cpython with {python}...') subprocess.run( @@ -196,31 +196,31 @@ def is_tool(name): env = env ) -print('Configuring ammo.js...') -ammo_dir = working_dir / 'ammo.js' -ammo_build_dir = working_dir / 'ammo_build' -os.makedirs(ammo_build_dir, exist_ok=True) -subprocess.run( - [ - 'emcmake', - 'cmake', - '-DCLOSURE=1', - '-DTOTAL_MEMORY=268435456', - '-DALLOW_MEMORY_GROWTH=1', - ammo_dir - ], - cwd = ammo_build_dir, - check = True, - env = env -) - -print('Building ammo.js...') -subprocess.run( - [ 'emmake', 'make', f'-j{multiprocessing.cpu_count()}' ], - cwd = ammo_build_dir, - check = True, - env = env -) +# print('Configuring ammo.js...') +# ammo_dir = working_dir / 'ammo.js' +# ammo_build_dir = working_dir / 'ammo_build' +# os.makedirs(ammo_build_dir, exist_ok=True) +# subprocess.run( +# [ +# 'emcmake', +# 'cmake', +# '-DCLOSURE=1', +# '-DTOTAL_MEMORY=268435456', +# '-DALLOW_MEMORY_GROWTH=1', +# ammo_dir +# ], +# cwd = ammo_build_dir, +# check = True, +# env = env +# ) + +# print('Building ammo.js...') +# subprocess.run( +# [ 'emmake', 'make', f'-j{multiprocessing.cpu_count()}' ], +# cwd = ammo_build_dir, +# check = True, +# env = env +# ) print('Generating JSON documentation...') libkipr_c_documentation_json = f'{libkipr_build_c_dir}/documentation/json.json' @@ -244,9 +244,9 @@ def is_tool(name): 'libkipr_c': f'{libkipr_install_c_dir}', 'libkipr_python': f'{libkipr_build_python_dir}', 'cpython': f'{cpython_emscripten_build_dir}', - 'ammo': f'{ammo_build_dir}', + # 'ammo': f'{ammo_build_dir}', "libkipr_c_documentation": libkipr_c_documentation_json, }) with open(working_dir / 'dependencies.json', 'w') as f: - f.write(output) \ No newline at end of file + f.write(output) diff --git a/package.json b/package.json index ec0ca5b6..e0a9e1eb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "simulator", - "version": "1.3.0", + "version": "1.4.0", "sideEffects": false, "main": "dist/index.js", "author": "KISS Institute for Practical Robotics", @@ -53,14 +53,15 @@ "webpack-merge": "^5.7.3" }, "dependencies": { - "@babylonjs/core": "^5.22.1", - "@babylonjs/loaders": "^5.22.1", + "@babylonjs/core": "^6.14.0", + "@babylonjs/loaders": "^6.14.0", + "@babylonjs/havok": "^1.0.0", "@fortawesome/fontawesome-svg-core": "^6.2.0", "@fortawesome/free-brands-svg-icons": "^6.2.0", "@fortawesome/free-solid-svg-icons": "^6.2.0", "@fortawesome/react-fontawesome": "^0.2.0", "@types/react": "^16.9.25", - "babylonjs-gltf2interface": "^5.22.0", + "babylonjs-gltf2interface": "^6.14.0", "body-parser": "^1.19.0", "colorjs.io": "^0.4.2", "connected-react-router": "^6.9.3", diff --git a/src/RobotBinding.ts b/src/RobotBinding.ts index 13db7b8a..0cfb7d59 100644 --- a/src/RobotBinding.ts +++ b/src/RobotBinding.ts @@ -3,19 +3,22 @@ import { TransformNode as BabylonTransformNode } from '@babylonjs/core/Meshes/tr import { AbstractMesh as BabylonAbstractMesh } from '@babylonjs/core/Meshes/abstractMesh'; import { LinesMesh as BabylonLinesMesh } from '@babylonjs/core/Meshes/linesMesh'; import { PhysicsViewer as BabylonPhysicsViewer } from '@babylonjs/core/Debug/physicsViewer'; -import { BoxBuilder as BabylonBoxBuilder } from '@babylonjs/core/Meshes/Builders/boxBuilder'; -import { SphereBuilder as BabylonSphereBuilder } from '@babylonjs/core/Meshes/Builders/sphereBuilder'; +import { CreateBox as BabylonCreateBox } from '@babylonjs/core/Meshes/Builders/boxBuilder'; +import { CreateSphere as BabylonCreateSphere } from '@babylonjs/core/Meshes/Builders/sphereBuilder'; import { IcoSphereBuilder as BabylonIcoSphereBuilder } from '@babylonjs/core/Meshes/Builders/icoSphereBuilder'; import { CreateLines as BabylonCreateLines } from '@babylonjs/core/Meshes/Builders/linesBuilder'; import { Vector3 as BabylonVector3, Quaternion as BabylonQuaternion } from '@babylonjs/core/Maths/math.vector'; import { StandardMaterial as BabylonStandardMaterial } from '@babylonjs/core/Materials/standardMaterial'; -import { PhysicsImpostor as BabylonPhysicsImpostor, PhysicsImpostorParameters as BabylonPhysicsImpostorParameters, IPhysicsEnabledObject as BabylonIPhysicsEnabledObject } from '@babylonjs/core/Physics/physicsImpostor'; +import { IPhysicsEnabledObject as BabylonIPhysicsEnabledObject } from '@babylonjs/core/Physics/physicsImpostor'; +import { PhysicsBody, PhysicsConstraintAxis, Physics6DoFConstraint, PhysicsMotionType, PhysicsShape, PhysicsAggregate, + PhysicsShapeType, PhysicsConstraintMotorType, PhysicShapeOptions, PhysicsShapeParameters, LockConstraint, PhysicsShapeContainer, + HingeConstraint, PhysicsConstraintAxisLimitMode } from '@babylonjs/core'; import { SpotLight as BabylonSpotLight } from '@babylonjs/core/Lights/spotLight'; import { DirectionalLight as BabylonDirectionalLight } from '@babylonjs/core/Lights/directionalLight'; import { HemisphericLight as BabylonHemisphericLight } from '@babylonjs/core/Lights/hemisphericLight'; import { Mesh as BabylonMesh } from '@babylonjs/core/Meshes/mesh'; import { SceneLoader as BabylonSceneLoader } from '@babylonjs/core/Loading/sceneLoader'; -import { MotorEnabledJoint as BabylonMotorEnabledJoint, PhysicsJoint as BabylonPhysicsJoint, HingeJoint as BabylonHingeJoint } from '@babylonjs/core/Physics/physicsJoint'; +import { PhysicsJoint as BabylonPhysicsJoint } from '@babylonjs/core/Physics/physicsJoint'; import { Ray as BabylonRay } from '@babylonjs/core/Culling/ray'; import '@babylonjs/core/Physics/physicsEngineComponent'; @@ -33,9 +36,10 @@ import { RENDER_SCALE, RENDER_SCALE_METERS_MULTIPLIER } from './renderConstants' import WriteCommand from './AbstractRobot/WriteCommand'; import AbstractRobot from './AbstractRobot'; import Motor from './AbstractRobot/Motor'; +import { node } from 'prop-types'; interface BuiltGeometry { - mesh: BabylonMesh; + nonColliders: BabylonMesh[]; colliders?: BuiltGeometry.Collider[]; } @@ -65,8 +69,8 @@ class RobotBinding { private weights_: Dict = {}; private fixed_: Dict = {}; - private motors_: Dict = {}; - private servos_: Dict = {}; + private motors_: Dict = {}; + private servos_: Dict = {}; private motorPorts_ = new Array(4); private servoPorts_ = new Array(4); @@ -85,6 +89,7 @@ class RobotBinding { this.physicsViewer_ = physicsViewer; } + // Loads the geometry of a robot part and divides into the collider and noncollider pieces private buildGeometry_ = async (name: string, geometry: Geometry): Promise => { let ret: BuiltGeometry; @@ -100,8 +105,8 @@ class RobotBinding { const colliders: BuiltGeometry.Collider[] = []; for (const mesh of res.meshes.slice(1) as BabylonMesh[]) { - + // The robot mesh includes sub-meshes with the 'collider' name to indicate their use. if (mesh.name.startsWith('collider')) { const parts = mesh.name.split('-'); if (parts.length !== 3) throw new Error(`Invalid collider name: ${mesh.name}`); @@ -113,103 +118,131 @@ class RobotBinding { let bType: number; switch (type) { - case 'box': bType = BabylonPhysicsImpostor.BoxImpostor; break; - case 'sphere': bType = BabylonPhysicsImpostor.SphereImpostor; break; - case 'cylinder': bType = BabylonPhysicsImpostor.CylinderImpostor; break; - case 'capsule': bType = BabylonPhysicsImpostor.CapsuleImpostor; break; - case 'plane': bType = BabylonPhysicsImpostor.PlaneImpostor; break; - case 'mesh': bType = BabylonPhysicsImpostor.MeshImpostor; break; + case 'box': bType = PhysicsShapeType.BOX; break; + case 'sphere': bType = PhysicsShapeType.SPHERE; break; + case 'cylinder': bType = PhysicsShapeType.CYLINDER; break; + case 'capsule': bType = PhysicsShapeType.CAPSULE; break; + case 'plane': bType = PhysicsShapeType.HEIGHTFIELD; break; + case 'mesh': bType = PhysicsShapeType.MESH; break; default: throw new Error(`Invalid collider type: ${type}`); } - colliders.push({ mesh, type: bType, name, volume }); } else { nonColliders.push(mesh); } } - const mesh = BabylonMesh.MergeMeshes(nonColliders, true, true, undefined, false, true); - mesh.visibility = 0; - ret = { mesh, colliders }; + ret = { nonColliders, colliders }; break; } - default: { - throw new Error(`Unsupported geometry type: ${geometry.type}`); - } + default: { throw new Error(`Unsupported geometry type: ${geometry.type}`); } } - return ret; }; + // Creates a link and returns the root mesh. Links are wheels, chasis, arms, etc. + // Loads the geometry and adds the appropriate physics properties to the mesh private createLink_ = async (id: string, link: Node.Link) => { + let builtGeometry: BuiltGeometry; if (link.geometryId === undefined) { - builtGeometry = { mesh: new BabylonMesh(id, this.bScene_) }; + builtGeometry = { nonColliders: [new BabylonMesh(id, this.bScene_)] }; } else { const geometry = this.robot_.geometry[link.geometryId]; if (!geometry) throw new Error(`Missing geometry: ${link.geometryId}`); builtGeometry = await this.buildGeometry_(id, geometry); } + + const meshes = builtGeometry.nonColliders; + let myMesh: BabylonMesh; - const ret = builtGeometry.mesh; - - const physicsImposterParams: BabylonPhysicsImpostorParameters = { - mass: Mass.toGramsValue(link.mass || Mass.grams(0)), - restitution: link.restitution ?? 0, - friction: link.friction ?? 0.5, - }; - - // We assume the mesh is defined in meters. Bring it into our default scaling. - ret.scaling.scaleInPlace(RENDER_SCALE_METERS_MULTIPLIER); - + const inertiaScale = .5; + switch (link.collisionBody.type) { + // Notes on Links - the root link should have the highest mass and inertia and it should + // scale down further out the tree to prevent wild oscillations. + // body.setMassProperties can also help setting the inertia vector, + // body.setAngularDamping and body.setLinearDamping can help with oscillations + case Node.Link.CollisionBody.Type.Box: { - ret.physicsImpostor = new BabylonPhysicsImpostor( - ret, - BabylonPhysicsImpostor.BoxImpostor, - physicsImposterParams, - this.bScene_ - ); - this.colliders_.add(ret); + // alert("box collision body"); // Currently there are no box collision bodies in the robot model + myMesh = BabylonMesh.MergeMeshes(meshes, true, true, undefined, false, true); + myMesh.scaling.scaleInPlace(RENDER_SCALE_METERS_MULTIPLIER); + + const aggregate = new PhysicsAggregate(myMesh, PhysicsShapeType.BOX, { + mass: Mass.toGramsValue(link.mass || Mass.grams(10)), + friction: link.friction ?? 0.5, + restitution: link.restitution ?? 0, + startAsleep: true, + }, this.bScene_); + + this.colliders_.add(myMesh); break; } case Node.Link.CollisionBody.Type.Cylinder: { - ret.physicsImpostor = new BabylonPhysicsImpostor( - ret, - BabylonPhysicsImpostor.CylinderImpostor, - physicsImposterParams, - this.bScene_ - ); - this.colliders_.add(ret); + myMesh = BabylonMesh.MergeMeshes(meshes, true, true, undefined, false, true); + myMesh.scaling.scaleInPlace(RENDER_SCALE_METERS_MULTIPLIER); + + const aggregate = new PhysicsAggregate(myMesh, PhysicsShapeType.CYLINDER, { + mass: Mass.toGramsValue(link.mass || Mass.grams(10)), + friction: link.friction ?? 0.5, + restitution: link.restitution ?? 0, + startAsleep: true, + }, this.bScene_); + + this.colliders_.add(myMesh); break; } case Node.Link.CollisionBody.Type.Embedded: { + myMesh = BabylonMesh.MergeMeshes(meshes, true, true, undefined, false, true); + myMesh.scaling.scaleInPlace(RENDER_SCALE_METERS_MULTIPLIER); + + // As the embedded collision body consists of multiple meshes, we need to create a parent + // This mmeans we are unable to use the physics aggregate + const parentShape = new PhysicsShapeContainer(this.bScene_); + for (const collider of builtGeometry.colliders ?? []) { const bCollider = collider.mesh; - bCollider.parent = ret; - - bCollider.physicsImpostor = new BabylonPhysicsImpostor( - bCollider, - collider.type, - { - ...physicsImposterParams, - mass: 0, - }, - this.bScene_ - ); + bCollider.parent = myMesh; + + const parameters: PhysicsShapeParameters = { mesh: bCollider }; + const options: PhysicShapeOptions = { type: PhysicsShapeType.MESH, parameters: parameters }; + const shape = new PhysicsShape(options, this.bScene_); + shape.material = { + friction: link.friction ?? 0.5, + restitution: link.restitution ?? 0.1, + }; + + parentShape.addChild(shape, bCollider.absolutePosition, bCollider.absoluteRotationQuaternion); bCollider.visibility = 0; this.colliders_.add(bCollider); } - - ret.physicsImpostor = new BabylonPhysicsImpostor( - ret, - BabylonPhysicsImpostor.NoImpostor, - physicsImposterParams, - this.bScene_ - ); - this.colliders_.add(ret); + + const body = new PhysicsBody(myMesh, PhysicsMotionType.DYNAMIC, false, this.bScene_); + body.shape = parentShape; + if (link.geometryId.includes("chassis")) { + body.setMassProperties({ + mass: Mass.toGramsValue(link.mass || Mass.grams(100)), + inertia: new BabylonVector3(10 * inertiaScale,10 * inertiaScale,10 * inertiaScale) // (left/right, twist around, rock forward and backward) + }); + } + if (link.geometryId.includes("arm")) { + body.setMassProperties({ + mass: Mass.toGramsValue(link.mass || Mass.grams(80)), + inertia: new BabylonVector3(6 * inertiaScale,6 * inertiaScale,6 * inertiaScale) // (left/right, twist around, rock forward and backward) + }); + } + if (link.geometryId.includes("claw")) { + body.setMassProperties({ + mass: Mass.toGramsValue(link.mass || Mass.grams(10)), + inertia: new BabylonVector3(3 * inertiaScale,3 * inertiaScale,3 * inertiaScale) // (left/right, twist around, rock forward and backward) + }); + } + body.setAngularDamping(.5); + + this.colliders_.add(myMesh); break; } @@ -217,10 +250,8 @@ class RobotBinding { throw new Error(`Unsupported collision body type: ${link.collisionBody.type}`); } } - - if (this.physicsViewer_) this.physicsViewer_.showImpostor(ret.physicsImpostor, ret); - - return ret; + if (this.physicsViewer_ && myMesh.physicsBody) this.physicsViewer_.showBody(myMesh.physicsBody); + return myMesh; }; private createSensor_ = >(s: { new(parameters: RobotBinding.SensorParameters): S }) => (id: string, definition: T): S => { @@ -260,38 +291,34 @@ class RobotBinding { }; }; + // Adds an invisible weight to a parent link. private createWeight_ = (id: string, weight: Node.Weight) => { - const ret = BabylonSphereBuilder.CreateSphere(id, { diameter: 1 }, this.bScene_); + const ret = BabylonCreateSphere(id, { diameter: 1 }, this.bScene_); ret.visibility = 0; const parent = this.robot_.nodes[weight.parentId]; if (!parent) throw new Error(`Missing parent: "${weight.parentId}" for weight "${id}"`); if (parent.type !== Node.Type.Link) throw new Error(`Invalid parent type: "${parent.type}" for weight "${id}"`); - ret.physicsImpostor = new BabylonPhysicsImpostor( - ret, - BabylonPhysicsImpostor.NoImpostor, - { - mass: Mass.toGramsValue(weight.mass), - restitution: 0, - friction: 0, - }, - this.bScene_ - ); + const aggregate = new PhysicsAggregate(ret, PhysicsShapeType.CYLINDER, { + mass: Mass.toGramsValue(weight.mass), + friction: 0, + restitution: 0, + }, this.bScene_); const bParent = this.links_[weight.parentId]; if (!bParent) throw new Error(`Missing parent instantiation: "${weight.parentId}" for weight "${id}"`); const bOrigin = ReferenceFrame.toBabylon(weight.origin, RENDER_SCALE); - - const bJoint = new BabylonPhysicsJoint(BabylonPhysicsJoint.LockJoint, { - mainPivot: bOrigin.position, - mainAxis: BabylonVector3.Up(), - connectedPivot: BabylonVector3.Zero(), - connectedAxis: BabylonVector3.Up().applyRotationQuaternion(bOrigin.rotationQuaternion.invert()), - }); - bParent.physicsImpostor.addJoint(ret.physicsImpostor, bJoint); + const constraint = new LockConstraint( + bOrigin.position, + new BabylonVector3(0,0,0), // RawVector3.toBabylon(new BabylonVector3(-8,10,0)), // updown, frontback + BabylonVector3.Up(), + BabylonVector3.Up().applyRotationQuaternion(bOrigin.rotationQuaternion.invert()), + this.bScene_ + ); + bParent.physicsBody.addConstraint(ret.physicsBody, constraint); return ret; }; @@ -321,48 +348,85 @@ class RobotBinding { }; }; + // Adds a physics constraint between a parent and child link. private createHinge_ = (id: string, hinge: Node.HingeJoint & { parentId: string }) => { + + // Begin by moving the child in place (this prevents inertial snap as the physics engine applys the constraint) const { bParent, bChild } = this.bParentChild_(id, hinge.parentId); - - const childAxis = hinge.childAxis || hinge.parentAxis; - const bChildAxis = RawVector3.toBabylon(childAxis); + bChild.setParent(bParent); + bChild.position.x = Vector3.toBabylon(hinge.parentPivot, 'meters')._x; + bChild.position.y = Vector3.toBabylon(hinge.parentPivot, 'meters')._y; + bChild.position.z = Vector3.toBabylon(hinge.parentPivot, 'meters')._z; - if (hinge.childTwist) { - bChild.rotationQuaternion = BabylonQuaternion.RotationAxis(bChildAxis, Angle.toRadiansValue(hinge.childTwist)); - bChild.computeWorldMatrix(true); + bChild.rotationQuaternion = BabylonQuaternion.FromEulerAngles(hinge.parentAxis.z * 3.1415 / 2, 0, 0); + + // The 6DoF constraint is used for motorized joints. Unforunately, it is not possible to + // completely lock these joints as hinges, so we also apply a hinge constraint. + // Order appears to matter here, the hinge should come before the 6DoF constraint. + if (id.includes("claw")) { + const hingeJoint = new HingeConstraint( + Vector3.toBabylon(hinge.parentPivot, RENDER_SCALE), + Vector3.toBabylon(hinge.childPivot, RENDER_SCALE), + new BabylonVector3(0,0,1), + new BabylonVector3(0,1,0), + this.bScene_ + ); + bParent.physicsBody.addConstraint(bChild.physicsBody, hingeJoint); + } else if (id.includes("arm")) { + const hingeJoint = new HingeConstraint( + Vector3.toBabylon(hinge.parentPivot, RENDER_SCALE), + Vector3.toBabylon(hinge.childPivot, RENDER_SCALE), + new BabylonVector3(0,0,1), + new BabylonVector3(0,-1,0), + this.bScene_ + ); + bParent.physicsBody.addConstraint(bChild.physicsBody, hingeJoint); + } else if (id.includes("wheel")) { + const hingeJoint = new HingeConstraint( + Vector3.toBabylon(hinge.parentPivot, RENDER_SCALE), + Vector3.toBabylon(hinge.childPivot, RENDER_SCALE), + new BabylonVector3(0,1,0), + undefined, + this.bScene_ + ); + bParent.physicsBody.addConstraint(bChild.physicsBody, hingeJoint); } + const joint: Physics6DoFConstraint = new Physics6DoFConstraint({ + pivotA: Vector3.toBabylon(hinge.parentPivot, RENDER_SCALE), + pivotB: Vector3.toBabylon(hinge.childPivot, RENDER_SCALE), + axisA: new BabylonVector3(1,0,0), + axisB: new BabylonVector3(1,0,0), + perpAxisA: new BabylonVector3(0,-1,0), // bChildAxis, // + perpAxisB: RawVector3.toBabylon(hinge.parentAxis), + }, + [ + { + axis: PhysicsConstraintAxis.ANGULAR_Z, + minLimit: -30 * Math.PI / 180, maxLimit: -30 * Math.PI / 180, + } + ], + this.bScene_ + ); + + bParent.physicsBody.addConstraint(bChild.physicsBody, joint); + joint.setAxisMode(PhysicsConstraintAxis.LINEAR_X, PhysicsConstraintAxisLimitMode.LOCKED); + joint.setAxisMode(PhysicsConstraintAxis.LINEAR_Y, PhysicsConstraintAxisLimitMode.LOCKED); + joint.setAxisMode(PhysicsConstraintAxis.LINEAR_Z, PhysicsConstraintAxisLimitMode.LOCKED); + joint.setAxisMode(PhysicsConstraintAxis.ANGULAR_X, PhysicsConstraintAxisLimitMode.LOCKED); + joint.setAxisMode(PhysicsConstraintAxis.ANGULAR_Y, PhysicsConstraintAxisLimitMode.LOCKED); - const ret = new BabylonHingeJoint({ - mainPivot: Vector3.toBabylon(hinge.parentPivot, RENDER_SCALE), - mainAxis: RawVector3.toBabylon(hinge.parentAxis), - connectedAxis: bChildAxis, - connectedPivot: Vector3.toBabylon(hinge.childPivot, RENDER_SCALE) - }); - - bParent.physicsImpostor.addJoint(bChild.physicsImpostor, ret); - - ret.setMotor(0, 10); - return ret; + return joint; }; - private hingeAngle_ = (joint: BabylonMotorEnabledJoint): number => { - let currentAngle: number; - joint.executeNativeFunction((world, joint) => { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call - currentAngle = joint.getHingeAngle(); - }); + // Assumes the current hinge angle is the last target of the axis motor. + // TODO: implement a method using relative poses to get the actual angle. + private hingeAngle_ = (joint: Physics6DoFConstraint): number => { + const currentAngle: number = joint.getAxisMotorTarget(0); return currentAngle; }; - private setMotorVelocity_ = (joint: BabylonMotorEnabledJoint, velocity: number) => { - joint.executeNativeFunction((world, joint) => { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call - joint.enableAngularMotor(true, velocity, 100000); - }); - }; - - + private lastTick_ = 0; private lastMotorAngles_: [number, number, number, number] = [0, 0, 0, 0]; @@ -393,9 +457,6 @@ class RobotBinding { const delta = (now - this.lastTick_) / 1000; this.lastTick_ = now; - - - const abstractMotors: [Motor, Motor, Motor, Motor] = [ readable.getMotor(0), readable.getMotor(1), @@ -446,8 +507,6 @@ class RobotBinding { // Convert to ticks const positionDeltaRaw = plug * deltaAngle / (2 * Math.PI) * ticksPerRevolution + this.positionDeltaFracs_[port]; - - const positionDelta = Math.trunc(positionDeltaRaw); this.positionDeltaFracs_[port] = positionDeltaRaw - positionDelta; @@ -458,12 +517,10 @@ class RobotBinding { writeCommands.push(WriteCommand.addMotorPosition({ port, positionDelta })); - - let writePwm = true; if (mode === Motor.Mode.Pwm && direction === Motor.Direction.Idle) { - this.setMotorVelocity_(bMotor, 0); + bMotor.setAxisMotorTarget(PhysicsConstraintAxis.ANGULAR_Z, 0); continue; } @@ -523,12 +580,29 @@ class RobotBinding { if (writePwm) writeCommands.push(WriteCommand.motorPwm({ port, pwm })); const normalizedPwm = pwm / 400; - const nextAngularVelocity = normalizedPwm * velocityMax * 2 * Math.PI / ticksPerRevolution; - - this.setMotorVelocity_(bMotor, nextAngularVelocity); + let direction_mult = 2; + if (motorId.includes("left")) { + direction_mult *= -1; + } + const nextAngularVelocity = direction_mult * normalizedPwm * velocityMax * 1 * Math.PI / ticksPerRevolution; + const currentAngularVelocity = bMotor.getAxisMotorTarget(PhysicsConstraintAxis.ANGULAR_Z); + + if (currentAngularVelocity.toFixed(6) !== nextAngularVelocity.toFixed(6)) { // comparison is aproximately unequal to 5 decimals + const pid_aproximator = 20; + const intermediate_target = (nextAngularVelocity + pid_aproximator * currentAngularVelocity) / (pid_aproximator + 1); + // console.log(`Setting motor ${motorId} to ${intermediate_target} from (${currentAngularVelocity})`); + const zero = 0.0; + if (intermediate_target.toFixed(6) === zero.toFixed(6)) { + bMotor.setAxisMotorTarget(PhysicsConstraintAxis.ANGULAR_Z, 0); + bMotor.setAxisFriction(PhysicsConstraintAxis.ANGULAR_Z, 10000000); + // bMotor.setAxisMode(PhysicsConstraintAxis.ANGULAR_Z, PhysicsConstraintAxisLimitMode.LOCKED); + } else { + bMotor.setAxisMode(PhysicsConstraintAxis.ANGULAR_Z, PhysicsConstraintAxisLimitMode.FREE); + } + bMotor.setAxisMotorTarget(PhysicsConstraintAxis.ANGULAR_Z, intermediate_target); + } } - // Servos for (let i = 0; i < 4; i++) { const servoId = this.servoPorts_[i]; @@ -558,22 +632,37 @@ class RobotBinding { if (abstractServo.enabled) { const servoPosition = clamp(0, abstractServo.position, 2048); const desiredAngle = (servoPosition - 1024) / 2048 * RobotBinding.SERVO_LOGICAL_RANGE_RADS; - this.lastServoEnabledAngle_[i] = -desiredAngle + twist; + if (servoId.includes("claw")) { + this.lastServoEnabledAngle_[i] = -desiredAngle + twist; + + } else { + this.lastServoEnabledAngle_[i] = -1 * (-desiredAngle + twist); + + } } - bServo.executeNativeFunction((world, joint) => { - /* eslint-disable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call */ + + const angle = this.lastServoEnabledAngle_[i]; - joint.setLimit(physicalMinRads, physicalMaxRads, 0.9, 0.3, 1); - joint.setMaxMotorImpulse(10000); - const angle = this.lastServoEnabledAngle_[i]; - if (Math.abs(angle - joint.getHingeAngle()) > Math.PI / 8) { - joint.setMotorTarget(angle, 0.2); - } else { - joint.setMotorTarget(angle, 0.1); + let cur_angle = 0; + const cur_direction = bServo.getAxisMotorTarget(PhysicsConstraintAxis.ANGULAR_Z) > 0; + if (cur_direction) { + cur_angle = bServo.getAxisMaxLimit(PhysicsConstraintAxis.ANGULAR_Z); + } else { + cur_angle = bServo.getAxisMinLimit(PhysicsConstraintAxis.ANGULAR_Z); + } + + if (cur_angle.toFixed(5) !== angle.toFixed(5)) { + // console.log(`Setting servo ${servoId} to ${angle * 180 / Math.PI} from (${cur_angle * 180 / Math.PI})`); + if (cur_angle < angle) { + bServo.setAxisMaxLimit(PhysicsConstraintAxis.ANGULAR_Z, angle); + bServo.setAxisMotorTarget(PhysicsConstraintAxis.ANGULAR_Z, Math.PI * .4); } - /* eslint-enable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call */ - }); + if (cur_angle > angle) { + bServo.setAxisMinLimit(PhysicsConstraintAxis.ANGULAR_Z, angle); + bServo.setAxisMotorTarget(PhysicsConstraintAxis.ANGULAR_Z, Math.PI * -.4); + } + } } // Digital Sensors @@ -643,10 +732,10 @@ class RobotBinding { return linkOrigins; } - set linkOrigins(linkOrigins: Dict) { + set linkOrigins(newLinkOrigins: Dict) { for (const [linkId, link] of Object.entries(this.links_)) { - if (!(linkId in linkOrigins)) continue; - const rawLinkPosition = ReferenceFrame.toRaw(linkOrigins[linkId], RENDER_SCALE); + if (!(linkId in newLinkOrigins)) continue; + const rawLinkPosition = ReferenceFrame.toRaw(newLinkOrigins[linkId], RENDER_SCALE); link.position = RawVector3.toBabylon(rawLinkPosition.position); link.rotationQuaternion = Quaternion.toBabylon(rawLinkPosition.orientation); link.scaling = RawVector3.toBabylon(rawLinkPosition.scale); @@ -669,15 +758,32 @@ class RobotBinding { }; } - set origin(origin: ReferenceFrame) { + // To set the origin, a root node is set, the robot is parented to the node, and then the node is moved. + set origin(newOrigin: ReferenceFrame) { this.lastPErrs_ = [0, 0, 0, 0]; this.iErrs_ = [0, 0, 0, 0]; this.brakeAt_ = [undefined, undefined, undefined, undefined]; - const rawOrigin = ReferenceFrame.toRaw(origin, RENDER_SCALE); + const rawOrigin = ReferenceFrame.toRaw(newOrigin, RENDER_SCALE); const rawInternalOrigin = ReferenceFrame.toRaw(this.robot_.origin || ReferenceFrame.IDENTITY, RENDER_SCALE); + const newOriginE = Euler.fromQuaternion(rawOrigin.orientation); + const Robot_OriginE = Euler.fromQuaternion(rawInternalOrigin.orientation); + + const default_offset = -1 * Math.PI / 2; + + + const UpdatedEulerOrigin = Euler.create( + newOriginE.x + Robot_OriginE.x, + newOriginE.y + Robot_OriginE.y + default_offset, + newOriginE.z + Robot_OriginE.z, + "xyz" + ); + + console.log("Set origin orientation to:", UpdatedEulerOrigin); + + const rootLink = this.links_[this.rootId_]; const rootTransformNode = new BabylonTransformNode('root-transform-node', this.bScene_); @@ -685,22 +791,23 @@ class RobotBinding { rootTransformNode.rotationQuaternion = rootLink.absoluteRotationQuaternion; for (const link of Object.values(this.links_)) { + link.physicsBody.disablePreStep = false; link.setParent(rootTransformNode); - link.physicsImpostor.setAngularVelocity(BabylonVector3.Zero()); - link.physicsImpostor.setLinearVelocity(BabylonVector3.Zero()); + link.physicsBody.setAngularVelocity(BabylonVector3.Zero()); + link.physicsBody.setLinearVelocity(BabylonVector3.Zero()); } for (const weight of Object.values(this.weights_)) { + weight.physicsBody.disablePreStep = false; weight.setParent(rootTransformNode); - weight.physicsImpostor.setAngularVelocity(BabylonVector3.Zero()); - weight.physicsImpostor.setLinearVelocity(BabylonVector3.Zero()); + weight.physicsBody.setAngularVelocity(BabylonVector3.Zero()); + weight.physicsBody.setLinearVelocity(BabylonVector3.Zero()); } - + rootTransformNode.position = RawVector3.toBabylon(rawOrigin.position || RawVector3.ZERO) .add(RawVector3.toBabylon(rawInternalOrigin.position || RawVector3.ZERO)); - rootTransformNode.rotationQuaternion = Quaternion.toBabylon(rawInternalOrigin.orientation || Quaternion.IDENTITY) - .multiply(Quaternion.toBabylon(rawOrigin.orientation || Quaternion.IDENTITY)); - + + rootTransformNode.rotationQuaternion = Quaternion.toBabylon(Euler.toQuaternion(UpdatedEulerOrigin)); for (const link of Object.values(this.links_)) { link.setParent(null); @@ -730,9 +837,12 @@ class RobotBinding { } } + // Entry point for actually setting up a robot async setRobot(sceneRobot: SceneNode.Robot, robot: Robot, robotSceneId: string) { if (this.robot_) throw new Error('Robot already set'); this.robotSceneId_ = robotSceneId; + robot.origin = sceneRobot.origin; + this.robot_ = robot; const rootIds = Robot.rootNodeIds(robot); @@ -743,11 +853,15 @@ class RobotBinding { const nodeIds = Robot.breadthFirstNodeIds(robot); this.childrenNodeIds_ = Robot.childrenNodeIds(robot); + const rootNode = robot.nodes[this.rootId_]; + if (robot.nodes[this.rootId_].type !== Node.Type.Link) throw new Error('Root node must be a link'); + for (const nodeId of nodeIds) { const node = robot.nodes[nodeId]; if (node.type !== Node.Type.Link) continue; + const bNode = await this.createLink_(nodeId, node); bNode.metadata = { id: this.robotSceneId_, selected: false } as SceneMeshMetadata; this.links_[nodeId] = bNode; @@ -765,14 +879,32 @@ class RobotBinding { } case Node.Type.Motor: { const bJoint = this.createHinge_(nodeId, node); + bJoint.setAxisMotorMaxForce(PhysicsConstraintAxis.ANGULAR_Z, 1000000000); + bJoint.setAxisMaxLimit(PhysicsConstraintAxis.ANGULAR_Z, 1000000000000); + bJoint.setAxisMinLimit(PhysicsConstraintAxis.ANGULAR_Z, -1000000000000); + bJoint.setAxisMotorTarget(PhysicsConstraintAxis.ANGULAR_Z, 0); + bJoint.setAxisMotorType(PhysicsConstraintAxis.ANGULAR_Z, PhysicsConstraintMotorType.VELOCITY); // Position control + + bJoint.setAxisMode(PhysicsConstraintAxis.ANGULAR_Z, PhysicsConstraintAxisLimitMode.LOCKED); + this.motors_[nodeId] = bJoint; this.motorPorts_[node.motorPort] = nodeId; break; } case Node.Type.Servo: { + // minLimit: -30 * Math.PI / 180, maxLimit: -30 * Math.PI / 180, + // -90 is upright and closed; 0 is forward and open const bJoint = this.createHinge_(nodeId, node); + bJoint.setAxisMotorMaxForce(PhysicsConstraintAxis.ANGULAR_Z, 10000000); + bJoint.setAxisMotorTarget(PhysicsConstraintAxis.ANGULAR_Z, 2); + bJoint.setAxisMotorType(PhysicsConstraintAxis.ANGULAR_Z, PhysicsConstraintMotorType.VELOCITY); // Velocity control + + bJoint.setAxisMaxLimit(PhysicsConstraintAxis.ANGULAR_Z, Angle.toRadiansValue(Angle.degrees(0))); + bJoint.setAxisMinLimit(PhysicsConstraintAxis.ANGULAR_Z, Angle.toRadiansValue(Angle.degrees(-10))); + this.servos_[nodeId] = bJoint; this.servoPorts_[node.servoPort] = nodeId; + this.lastServoEnabledAngle_[node.servoPort] = Angle.toRadiansValue(Angle.degrees(-10)); break; } case Node.Type.TouchSensor: { @@ -934,7 +1066,7 @@ namespace RobotBinding { // The parent already has RENDER_SCALE applied, so we don't need to apply it again. const rawCollisionBox = Vector3.toRaw(collisionBox, 'meters'); - this.intersector_ = BabylonBoxBuilder.CreateBox(id, { + this.intersector_ = BabylonCreateBox(id, { depth: rawCollisionBox.z, height: rawCollisionBox.y, width: rawCollisionBox.x, @@ -956,7 +1088,7 @@ namespace RobotBinding { let hit = false; meshes.forEach(mesh => { if (hit || mesh === this.intersector_ || links.has(mesh as BabylonMesh)) return; - if (!mesh.physicsImpostor) return; + if (!mesh.physicsBody) return; hit = this.intersector_.intersectsMesh(mesh, true); }); @@ -1018,7 +1150,7 @@ namespace RobotBinding { mesh !== this.trace_ && !links.has(mesh as BabylonMesh) && !colliders.has(mesh as BabylonMesh) && - (!!mesh.physicsImpostor || metadata.selected) + (!!mesh.physicsBody || metadata.selected) ); }); @@ -1112,7 +1244,7 @@ namespace RobotBinding { for (let i = 0; i < meshes.length; i++) { const mesh = meshes.data[i]; if (mesh === this.trace_) continue; - if (!mesh.physicsImpostor) continue; + if (!mesh.physicsBody) continue; hit = ray.intersectsBox(mesh.getBoundingInfo().boundingBox); if (hit) break; } diff --git a/src/SceneBinding.ts b/src/SceneBinding.ts index 3b56042a..2384a4dd 100644 --- a/src/SceneBinding.ts +++ b/src/SceneBinding.ts @@ -5,18 +5,18 @@ import { Node as BabylonNode } from '@babylonjs/core/node'; import { PhysicsViewer as BabylonPhysicsViewer } from '@babylonjs/core/Debug/physicsViewer'; import { ShadowGenerator as BabylonShadowGenerator } from '@babylonjs/core/Lights/Shadows/shadowGenerator'; import { Camera as BabylonCamera } from '@babylonjs/core/Cameras/camera'; -import { AmmoJSPlugin as BabylonAmmoJSPlugin } from '@babylonjs/core/Physics/Plugins/ammoJSPlugin'; -import { BoxBuilder as BabylonBoxBuilder } from '@babylonjs/core/Meshes/Builders/boxBuilder'; -import { SphereBuilder as BabylonSphereBuilder } from '@babylonjs/core/Meshes/Builders/sphereBuilder'; -import { CylinderBuilder as BabylonCylinderBuilder } from '@babylonjs/core/Meshes/Builders/cylinderBuilder'; -import { PlaneBuilder as BabylonPlaneBuilder } from '@babylonjs/core/Meshes/Builders/planeBuilder'; +import { CreateBox as BabylonCreateBox } from '@babylonjs/core/Meshes/Builders/boxBuilder'; +import { CreateSphere as BabylonCreateSphere } from '@babylonjs/core/Meshes/Builders/sphereBuilder'; +import { CreateCylinder as BabylonCreateCylinder } from '@babylonjs/core/Meshes/Builders/cylinderBuilder'; +import { CreatePlane as BabylonCreatePlane } from '@babylonjs/core/Meshes/Builders/planeBuilder'; import { Vector3 as BabylonVector3, Vector4 as BabylonVector4 } from '@babylonjs/core/Maths/math.vector'; import { Texture as BabylonTexture } from '@babylonjs/core/Materials/Textures/texture'; +import { DynamicTexture as BabylonDynamicTexture } from '@babylonjs/core/Materials/Textures/dynamicTexture'; import { Material as BabylonMaterial } from '@babylonjs/core/Materials/material'; import { StandardMaterial as BabylonStandardMaterial } from '@babylonjs/core/Materials/standardMaterial'; import { GizmoManager as BabylonGizmoManager } from '@babylonjs/core/Gizmos/gizmoManager'; import { ArcRotateCamera as BabylonArcRotateCamera } from '@babylonjs/core/Cameras/arcRotateCamera'; -import { PhysicsImpostor as BabylonPhysicsImpostor } from '@babylonjs/core/Physics/physicsImpostor'; +import { PhysicsShapeType, IPhysicsCollisionEvent, IPhysicsEnginePluginV2, PhysicsAggregate } from '@babylonjs/core'; import { IShadowLight as BabylonIShadowLight } from '@babylonjs/core/Lights/shadowLight'; import { PointLight as BabylonPointLight } from '@babylonjs/core/Lights/pointLight'; import { SpotLight as BabylonSpotLight } from '@babylonjs/core/Lights/spotLight'; @@ -54,6 +54,7 @@ import WorkerInstance from "./WorkerInstance"; import LocalizedString from './util/LocalizedString'; import ScriptManager from './ScriptManager'; import { RENDER_SCALE } from './renderConstants'; +import { number } from 'prop-types'; export type FrameLike = BabylonTransformNode | BabylonAbstractMesh; @@ -81,7 +82,6 @@ class SceneBinding { private camera_: BabylonCamera; private engineView_: BabylonEngineView; - private ammo_: BabylonAmmoJSPlugin; private robots_: Dict; private robotBindings_: Dict = {}; @@ -116,13 +116,20 @@ class SceneBinding { private materialIdIter_ = 0; - constructor(bScene: BabylonScene, ammo: unknown) { + private seed_ = 0; + + constructor(bScene: BabylonScene, physics: IPhysicsEnginePluginV2) { this.bScene_ = bScene; this.scene_ = Scene.EMPTY; - this.ammo_ = new BabylonAmmoJSPlugin(true, ammo); - this.bScene_.enablePhysics(new BabylonVector3(0, -9.8 * 100, 0), this.ammo_); - this.bScene_.getPhysicsEngine().setSubTimeStep(2); + + // Gravity is currently set at 5x, which seems to be a sweet spot for realism and joint performance + const gravityVector = new BabylonVector3(0, -9.8 * 5, 0); // -9.81 + this.bScene_.enablePhysics(gravityVector, physics); + + // The sub time step is incredibly important for physics realism. 1 seems to work well. + this.bScene_.getPhysicsEngine().setSubTimeStep(1); + // Uncomment this to turn on the physics viewer for objects // this.physicsViewer_ = new BabylonPhysicsViewer(this.bScene_); this.root_ = new BabylonTransformNode('__scene_root__', this.bScene_); @@ -130,6 +137,7 @@ class SceneBinding { this.camera_ = this.createNoneCamera_(Camera.NONE); + // Gizmos are the little arrows that appear when you select an object this.gizmoManager_.positionGizmoEnabled = true; this.gizmoManager_.gizmos.positionGizmo.scaleRatio = 1.25; this.gizmoManager_.rotationGizmoEnabled = true; @@ -141,6 +149,7 @@ class SceneBinding { } private robotLinkOrigins_: Dict> = {}; + set robotLinkOrigins(robotLinkOrigins: Dict>) { this.robotLinkOrigins_ = robotLinkOrigins; } @@ -155,6 +164,7 @@ class SceneBinding { return ret; } + // apply_ is used to propogate the function f(m) on children of the specified mesh g private static apply_ = (g: BabylonNode, f: (m: BabylonAbstractMesh) => void) => { if (g instanceof BabylonAbstractMesh) { f(g); @@ -163,30 +173,62 @@ class SceneBinding { } }; + private random = (max: number, min: number) => { + let x = Math.sin(this.seed_++) * 10000; + x = x - Math.floor(x); + x = ((x - .5) * (max - min)) + ((max + min) / 2); + return x; + }; + private buildGeometry_ = async (name: string, geometry: Geometry, faceUvs?: RawVector2[]): Promise => { let ret: FrameLike; switch (geometry.type) { case 'box': { - ret = BabylonBoxBuilder.CreateBox(name, { + const rect = BabylonCreateBox(name, { + updatable:true, width: Distance.toCentimetersValue(geometry.size.x), height: Distance.toCentimetersValue(geometry.size.y), depth: Distance.toCentimetersValue(geometry.size.z), faceUV: this.buildGeometryFaceUvs_(faceUvs, 12), }, this.bScene_); + const verts = rect.getVerticesData("position"); + ret = rect; break; } case 'sphere': { const bFaceUvs = this.buildGeometryFaceUvs_(faceUvs, 2)?.[0]; - ret = BabylonSphereBuilder.CreateSphere(name, { - // Why?? Why is a sphere defined by its diameter? - diameter: Distance.toCentimetersValue(geometry.radius) * 2, + const segments = 4; + const rock = BabylonCreateSphere(name, { + segments: segments, + updatable:true, frontUVs: bFaceUvs, sideOrientation: bFaceUvs ? BabylonMesh.DOUBLESIDE : undefined, + diameterX:Distance.toCentimetersValue(geometry.radius) * 2, + diameterY:Distance.toCentimetersValue(geometry.radius) * 2 * geometry.squash, + diameterZ:Distance.toCentimetersValue(geometry.radius) * 2 * geometry.stretch, }, this.bScene_); + + const positions = rock.getVerticesData("position"); + // TODO: Replace with custom rocks from blender + if (name.includes('Rock')) { + const skip = [25,26,38,39,51,52,64,65]; + for (let i = 14; i < 65; i++) { + if (skip.includes(i)) { + continue; + } else { + positions[3 * i] = positions[3 * i] + this.random(geometry.noise, -1 * geometry.noise); + positions[1 + 3 * i] = positions[1 + 3 * i] + this.random(geometry.noise, -1 * geometry.noise); + positions[2 + 3 * i] = positions[2 + 3 * i] + this.random(geometry.noise, -1 * geometry.noise); + } + } + } + rock.updateVerticesData("position", positions); + + ret = rock; break; } case 'cylinder': { - ret = BabylonCylinderBuilder.CreateCylinder(name, { + ret = BabylonCreateCylinder(name, { height: Distance.toCentimetersValue(geometry.height), diameterTop: Distance.toCentimetersValue(geometry.radius) * 2, diameterBottom: Distance.toCentimetersValue(geometry.radius) * 2, @@ -195,7 +237,7 @@ class SceneBinding { break; } case 'cone': { - ret = BabylonCylinderBuilder.CreateCylinder(name, { + ret = BabylonCreateCylinder(name, { diameterTop: 0, height: Distance.toCentimetersValue(geometry.height), diameterBottom: Distance.toCentimetersValue(geometry.radius) * 2, @@ -204,7 +246,7 @@ class SceneBinding { break; } case 'plane': { - ret = BabylonPlaneBuilder.CreatePlane(name, { + ret = BabylonCreatePlane(name, { width: Distance.toCentimetersValue(geometry.size.x), height: Distance.toCentimetersValue(geometry.size.y), frontUVs: this.buildGeometryFaceUvs_(faceUvs, 2)?.[0], @@ -218,13 +260,17 @@ class SceneBinding { const res = await BabylonSceneLoader.ImportMeshAsync(geometry.include ?? '', baseName, fileName, this.bScene_); if (res.meshes.length === 1) return res.meshes[0]; + // const nonColliders: BabylonMesh[] = []; + ret = new BabylonTransformNode(geometry.uri, this.bScene_); - for (const mesh of res.meshes) { + for (const mesh of res.meshes as BabylonMesh[]) { // GLTF importer adds a __root__ mesh (always the first one) that we can ignore if (mesh.name === '__root__') continue; + // nonColliders.push(mesh); mesh.setParent(ret); } + // const mesh = BabylonMesh.MergeMeshes(nonColliders, true, true, undefined, false, true); break; } default: { @@ -235,10 +281,10 @@ class SceneBinding { if (ret instanceof BabylonAbstractMesh) { ret.visibility = 1; } else { - const children = ret.getChildren(c => c instanceof BabylonAbstractMesh) as BabylonAbstractMesh[]; - for (const child of children) { - child.visibility = 1; - } + const children = ret.getChildren(c => c instanceof BabylonAbstractMesh) as BabylonMesh[]; + const mesh = BabylonMesh.MergeMeshes(children, true, true, undefined, false, true); + mesh.visibility = 1; + ret = mesh; } return ret; @@ -282,22 +328,34 @@ class SceneBinding { if (!color.uri) { basic.diffuseColor = new BabylonColor3(0.5, 0, 0.5); } else { - basic.diffuseTexture = new BabylonTexture(color.uri, this.bScene_); + if (id.includes('Sky')) { + basic.reflectionTexture = new BabylonTexture(color.uri, this.bScene_); + basic.reflectionTexture.coordinatesMode = BabylonTexture.FIXED_EQUIRECTANGULAR_MODE; + basic.backFaceCulling = false; + basic.disableLighting = true; + } else if (id === 'Container') { + const myDynamicTexture = new BabylonDynamicTexture("dynamic texture", 1000, this.bScene_, true); + // myDynamicTexture.drawText(material.text, 130, 600, "18px Arial", "white", "gray", true); + myDynamicTexture.drawText(color.uri, 130, 600, "18px Arial", "white", "gray", true); + basic.diffuseTexture = myDynamicTexture; + } else { + basic.bumpTexture = new BabylonTexture(color.uri, this.bScene_, false, false); + basic.emissiveTexture = new BabylonTexture(color.uri, this.bScene_, false, false); + basic.diffuseTexture = new BabylonTexture(color.uri, this.bScene_, false, false); + basic.diffuseTexture.coordinatesMode = BabylonTexture.FIXED_EQUIRECTANGULAR_MODE; + basic.backFaceCulling = false; + } } - break; } } } - bMaterial = basic; - break; } case 'pbr': { const pbr = new BabylonPBRMaterial(id, this.bScene_); const { albedo, ambient, emissive, metalness, reflection } = material; - if (albedo) { switch (albedo.type) { case 'color3': { @@ -370,8 +428,6 @@ class SceneBinding { } } - - return bMaterial; }; @@ -390,6 +446,11 @@ class SceneBinding { if (!color.next.uri) { bMaterial.diffuseColor = new BabylonColor3(0.5, 0, 0.5); bMaterial.diffuseTexture = null; + } else if (color.next.uri[0] !== '/') { + const myDynamicTexture = new BabylonDynamicTexture("dynamic texture", 1000, this.bScene_, true); + // myDynamicTexture.drawText(material.text, 130, 600, "18px Arial", "white", "gray", true); + myDynamicTexture.drawText(color.next.uri, 130, 600, "18px Arial", "white", "gray", true); + bMaterial.diffuseTexture = myDynamicTexture; } else { bMaterial.diffuseColor = Color.toBabylon(Color.WHITE); bMaterial.diffuseTexture = new BabylonTexture(color.next.uri, this.bScene_); @@ -514,7 +575,6 @@ class SceneBinding { if (next) { return this.createMaterial_(id, next); } - return null; } case Patch.Type.InnerChange: { @@ -542,7 +602,6 @@ class SceneBinding { console.error(`node ${LocalizedString.lookup(node.name, LocalizedString.EN_US)} has invalid geometry ID: ${node.geometryId}`); return null; } - const ret = await this.buildGeometry_(node.name[LocalizedString.EN_US], geometry, node.faceUvs); if (!node.visible) { @@ -554,11 +613,7 @@ class SceneBinding { SceneBinding.apply_(ret, m => m.material = material); } - // Create physics impostor - SceneBinding.apply_(ret, m => this.restorePhysicsImpostor(m, node, null, nextScene)); - ret.setParent(parent); - return ret; }; @@ -619,21 +674,24 @@ class SceneBinding { return ret; }; + // Create Robot Binding private createRobot_ = async (id: string, node: Node.Robot): Promise => { // This should probably be somewhere else, but it ensures this is called during // initial instantiation and when a new scene is loaded. WorkerInstance.sync(node.state); + const robotBinding = new RobotBinding(this.bScene_, this.physicsViewer_); const robot = this.robots_[node.robotId]; if (!robot) throw new Error(`Robot by id "${node.robotId}" not found`); await robotBinding.setRobot(node, robot, id); robotBinding.linkOrigins = this.robotLinkOrigins_[id] || {}; + // console.log('robot linkOrigins', robotBinding.linkOrigins); + // Here the linkOrigins are all shown as 0,0,0, this may be why the initial kinematics are messed up. - // FIXME: For some reason this origin isn't respected immediately. We need to look into it. - robotBinding.visible = false; + robotBinding.visible = true; const observerObj: { observer: BabylonObserver } = { observer: null }; - let count = 0; + robotBinding.origin = node.origin; this.declineTicks_ = true; observerObj.observer = this.bScene_.onAfterRenderObservable.add((data, state) => { @@ -646,13 +704,9 @@ class SceneBinding { const { origin, visible } = node; - robotBinding.origin = origin || ReferenceFrame.IDENTITY; - const linkOrigins = this.robotLinkOrigins_[id]; if (linkOrigins) robotBinding.linkOrigins = linkOrigins; - if (count++ < 10) return; - robotBinding.visible = visible ?? false; observerObj.observer.unregisterOnNextCall = true; this.declineTicks_ = false; @@ -677,7 +731,7 @@ class SceneBinding { let nodeToCreate: Node = node; // Resolve template nodes into non-template nodes by looking up the template by ID - if (node.type === 'from-template') { + if (node.type === 'from-jbc-template' || node.type === 'from-rock-template' || node.type === 'from-space-template') { const nodeTemplate = preBuiltTemplates[node.templateId]; if (!nodeTemplate) { console.warn('template node has invalid template ID:', node.templateId); @@ -716,6 +770,7 @@ class SceneBinding { if (ret instanceof BabylonAbstractMesh || ret instanceof BabylonTransformNode) { SceneBinding.apply_(ret, m => { m.metadata = { id } as SceneMeshMetadata; + this.restorePhysicsToObject(m, nodeToCreate as Node.Obj, null, nextScene); }); } @@ -737,18 +792,6 @@ class SceneBinding { bNode.rotationQuaternion = Quaternion.toBabylon(Rotation.toRawQuaternion(orientation)); bNode.scaling.set(scale.x, scale.y, scale.z); - - // Physics impostor needs to be updated after scale changes - // TODO: Only do this if the scale actually changed in this update - // TODO: Need to consider the impact of this, since it may destroy joints - SceneBinding.apply_(bNode, m => { - if (m.physicsImpostor) { - const mParent = m.parent; - m.setParent(null); - m.physicsImpostor.setScalingUpdated(); - m.setParent(mParent); - } - }); } }; @@ -784,6 +827,7 @@ class SceneBinding { return null; }; + // Patch changes to an object private updateObject_ = async (id: string, node: Patch.InnerChange, nextScene: Scene): Promise => { const bNode = this.findBNode_(id) as FrameLike; @@ -809,15 +853,14 @@ class SceneBinding { }); // TODO: Handle changes to faceUvs when we fully support it - if (node.inner.origin.type === Patch.Type.OuterChange) { this.updateNodePosition_(node.next, bNode); } if (node.inner.physics.type === Patch.Type.OuterChange) { SceneBinding.apply_(bNode, m => { - this.removePhysicsImpostor(m); - this.restorePhysicsImpostor(m, node.next, id, nextScene); + this.removePhysicsFromObject(m); + this.restorePhysicsToObject(m, node.next, id, nextScene); }); } @@ -826,15 +869,14 @@ class SceneBinding { SceneBinding.apply_(bNode, m => { m.isVisible = nextVisible; - // Create/remove physics impostor for object becoming visible/invisible + // Create/remove physics for object becoming visible/invisible if (!nextVisible) { - this.removePhysicsImpostor(m); + this.removePhysicsFromObject(m); } else { - this.restorePhysicsImpostor(m, node.next, id, nextScene); + this.restorePhysicsToObject(m, node.next, id, nextScene); } }); } - return Promise.resolve(bNode); }; @@ -884,13 +926,16 @@ class SceneBinding { return robotBinding; }; - private updateFromTemplate_ = (id: string, node: Patch.InnerChange, nextScene: Scene): Promise => { + private updateFromTemplate_ = ( + id: string, + node: Patch.InnerChange | Patch.InnerChange | Patch.InnerChange, + nextScene: Scene + ): Promise => { // If the template ID changes, recreate the node entirely if (node.inner.templateId.type === Patch.Type.OuterChange) { this.destroyNode_(id); return this.createNode_(id, node.next, nextScene); } - const bNode = this.findBNode_(id); const nodeTemplate = preBuiltTemplates[node.next.templateId]; @@ -984,6 +1029,7 @@ class SceneBinding { }; private updateNode_ = async (id: string, node: Patch, geometryPatches: Dict>, nextScene: Scene): Promise => { + switch (node.type) { // The node hasn't changed type, but some fields have been changed case Patch.Type.InnerChange: { @@ -1012,7 +1058,11 @@ class SceneBinding { await this.updateRobot_(id, node as Patch.InnerChange); return null; } - case 'from-template': return this.updateFromTemplate_(id, node as Patch.InnerChange, nextScene); + case 'from-jbc-template': return this.updateFromTemplate_(id, node as Patch.InnerChange, nextScene); + case 'from-rock-template': return this.updateFromTemplate_(id, node as Patch.InnerChange, nextScene); + case 'from-space-template': { + return this.updateFromTemplate_(id, node as Patch.InnerChange, nextScene); + } default: { console.error('invalid node type for inner change:', (node.next as Node).type); return this.findBNode_(id); @@ -1022,7 +1072,6 @@ class SceneBinding { // The node has been wholesale replaced by another type of node case Patch.Type.OuterChange: { this.destroyNode_(id); - return this.createNode_(id, node.next, nextScene); } // The node was newly added to the scene @@ -1048,7 +1097,6 @@ class SceneBinding { } if (node.prev.type === 'robot') return null; - return this.findBNode_(id); } } @@ -1070,10 +1118,11 @@ class SceneBinding { private gizmoManager_: BabylonGizmoManager; private createArcRotateCamera_ = (camera: Camera.ArcRotate): BabylonArcRotateCamera => { - const ret = new BabylonArcRotateCamera('botcam', 10, 10, 10, Vector3.toBabylon(camera.target, 'centimeters'), this.bScene_); + const ret = new BabylonArcRotateCamera('botcam', 0, 0, 0, Vector3.toBabylon(camera.target, 'centimeters'), this.bScene_); ret.attachControl(this.bScene_.getEngine().getRenderingCanvas(), true); ret.position = Vector3.toBabylon(camera.position, 'centimeters'); ret.panningSensibility = 100; + // ret.checkCollisions = true; return ret; }; @@ -1119,45 +1168,49 @@ class SceneBinding { }; private cachedCollideCallbacks_: Dict<{ - callback: (collider: BabylonPhysicsImpostor, collidedWith: BabylonPhysicsImpostor, point: BabylonVector3) => void; - otherImpostors: BabylonPhysicsImpostor[]; + callback: (collisionEvent: IPhysicsCollisionEvent) => void; }[]> = {}; - private restorePhysicsImpostor = (mesh: BabylonAbstractMesh, objectNode: Node.Obj, nodeId: string, scene: Scene): void => { - // Physics impostors should only be added to physics-enabled, visible, non-selected objects + private restorePhysicsToObject = (mesh: BabylonAbstractMesh, objectNode: Node.Obj | Node.FromSpaceTemplate, nodeId: string, scene: Scene): void => { + // Physics should only be added to physics-enabled, visible, non-selected objects if ( !objectNode.physics || !objectNode.visible || (nodeId && scene.selectedNodeId === nodeId) || - (mesh.physicsImpostor && !mesh.physicsImpostor.isDisposed) - ) return; + (mesh.physicsBody) + ) { + return; + } const initialParent = mesh.parent; mesh.setParent(null); - - const type = IMPOSTER_TYPE_MAPPINGS[objectNode.physics.type]; - mesh.physicsImpostor = new BabylonPhysicsImpostor(mesh, type, { + const aggregate = new PhysicsAggregate(mesh, PHYSICS_SHAPE_TYPE_MAPPINGS[objectNode.physics.type], { mass: objectNode.physics.mass ? Mass.toGramsValue(objectNode.physics.mass) : 0, - restitution: objectNode.physics.restitution ?? 0.5, friction: objectNode.physics.friction ?? 5, - }); - - if (this.physicsViewer_) this.physicsViewer_.showImpostor(mesh.physicsImpostor); + restitution: objectNode.physics.restitution ?? 0.5, + }, this.bScene_); + if (this.physicsViewer_) { + this.physicsViewer_.showBody(mesh.physicsBody); + } mesh.setParent(initialParent); - this.syncCollisionFilters_(); }; - private removePhysicsImpostor = (mesh: BabylonAbstractMesh) => { - if (!mesh.physicsImpostor) return; + private removePhysicsFromObject = (mesh: BabylonAbstractMesh) => { + if (!mesh.physicsBody) return; const parent = mesh.parent; mesh.setParent(null); - if (!mesh.physicsImpostor.isDisposed) mesh.physicsImpostor.dispose(); - mesh.physicsImpostor = undefined; + if (this.physicsViewer_) { + this.physicsViewer_.hideBody(mesh.physicsBody); + } + mesh.physicsBody.shape.dispose(); + mesh.physicsBody.dispose(); + mesh.physicsBody = null; + mesh.setParent(parent); @@ -1172,25 +1225,23 @@ class SceneBinding { const meshes = this.nodeMeshes_(nodeId); if (meshes.length === 0) continue; - const impostors = meshes - .map(mesh => mesh.physicsImpostor) - .filter(impostor => impostor && !impostor.isDisposed); + const meshcopy = meshes + .map(mesh => mesh.physicsBody) + .filter(body => !body); - if (impostors.length === 0) continue; + if (meshcopy.length === 0) continue; const filterIds = this.collisionFilters_[nodeId]; - const otherImpostors = Array.from(filterIds) + const otherBodies = Array.from(filterIds) .map(id => this.nodeMeshes_(id)) .reduce((acc, val) => [...acc, ...val], []) - .filter(mesh => mesh && mesh.physicsImpostor) - .map(mesh => mesh.physicsImpostor); - - for (const impostor of impostors) { - impostor._onPhysicsCollideCallbacks = [{ - callback: this.onCollideEvent_, - otherImpostors, - }]; + .filter(mesh => mesh && mesh.physicsBody) + .map(mesh => mesh.physicsBody); + + for (const body of meshcopy) { + const observable = body.getCollisionObservable(); + observable.add(this.onCollideEvent_); } } }; @@ -1210,15 +1261,18 @@ class SceneBinding { }; private onCollideEvent_ = ( - collider: BabylonPhysicsImpostor, - collidedWith: BabylonPhysicsImpostor, - point: BabylonVector3 + collisionEvent: IPhysicsCollisionEvent, ) => { - if (!('metadata' in collider.object)) return; - if (!('metadata' in collidedWith.object)) return; - const colliderMetadata = collider.object['metadata'] as SceneMeshMetadata; - const collidedWithMetadata = collidedWith.object['metadata'] as SceneMeshMetadata; + const collider = collisionEvent.collider; + const collidedWith = collisionEvent.collidedAgainst; + const point = collisionEvent.point; + + if (!('metadata' in collider.transformNode)) return; + if (!('metadata' in collidedWith.transformNode)) return; + + const colliderMetadata = collider.transformNode.metadata as SceneMeshMetadata; + const collidedWithMetadata = collidedWith.transformNode.metadata as SceneMeshMetadata; if (!colliderMetadata) return; if (!collidedWithMetadata) return; @@ -1230,32 +1284,28 @@ class SceneBinding { })); }; - - + readonly setScene = async (scene: Scene, robots: Dict) => { this.robots_ = robots; const patch = Scene.diff(this.scene_, scene); - const nodeIds = Dict.keySet(patch.nodes); - const removedKeys: Set = new Set(); // We need to handle removals first for (const nodeId of nodeIds) { const node = patch.nodes[nodeId]; if (node.type !== Patch.Type.Remove) continue; - await this.updateNode_(nodeId, node, patch.geometry, scene); delete this.nodes_[nodeId]; delete this.shadowGenerators_[nodeId]; + delete this.intersectionFilters_[nodeId]; removedKeys.add(nodeId); } // Now get a breadth-first sort of the remaining nodes (we need to make sure we add parents first) const sortedNodeIds = Scene.nodeOrdering(scene); - for (const nodeId of sortedNodeIds) { if (removedKeys.has(nodeId)) continue; const node = patch.nodes[nodeId]; @@ -1275,14 +1325,21 @@ class SceneBinding { let prevNodeObj: Node.Obj; const prevNode = scene.nodes[prev]; if (prevNode.type === 'object') prevNodeObj = prevNode; - else if (prevNode.type === 'from-template') { + else if (prevNode.type === 'from-jbc-template') { + const nodeTemplate = preBuiltTemplates[prevNode.templateId]; + if (nodeTemplate?.type === 'object') prevNodeObj = { ...nodeTemplate, ...Node.Base.upcast(prevNode) }; + } else if (prevNode.type === 'from-rock-template') { + const nodeTemplate = preBuiltTemplates[prevNode.templateId]; + if (nodeTemplate?.type === 'object') prevNodeObj = { ...nodeTemplate, ...Node.Base.upcast(prevNode) }; + } else if (prevNode.type === 'from-space-template') { const nodeTemplate = preBuiltTemplates[prevNode.templateId]; if (nodeTemplate?.type === 'object') prevNodeObj = { ...nodeTemplate, ...Node.Base.upcast(prevNode) }; } - const prevBNode = this.bScene_.getNodeByID(prev); + + const prevBNode = this.bScene_.getNodeById(prev); if (prevNodeObj && (prevBNode instanceof BabylonAbstractMesh || prevBNode instanceof BabylonTransformNode)) { prevBNode.metadata = { ...(prevBNode.metadata as SceneMeshMetadata), selected: false }; - SceneBinding.apply_(prevBNode, m => this.restorePhysicsImpostor(m, prevNodeObj, prev, scene)); + SceneBinding.apply_(prevBNode, m => this.restorePhysicsToObject(m, prevNodeObj, prev, scene)); } this.gizmoManager_.attachToNode(null); @@ -1290,9 +1347,9 @@ class SceneBinding { // Disable physics on the now selected node if (next !== undefined) { - const node = this.bScene_.getNodeByID(next); + const node = this.bScene_.getNodeById(next); if (node instanceof BabylonAbstractMesh || node instanceof BabylonTransformNode) { - SceneBinding.apply_(node, m => this.removePhysicsImpostor(m)); + SceneBinding.apply_(node, m => this.removePhysicsFromObject(m)); node.metadata = { ...(node.metadata as SceneMeshMetadata), selected: true }; this.gizmoManager_.attachToNode(node); } @@ -1325,7 +1382,8 @@ class SceneBinding { } if (patch.gravity.type === Patch.Type.OuterChange) { - this.bScene_.getPhysicsEngine().setGravity(Vector3.toBabylon(patch.gravity.next, 'centimeters')); + const gravity_scalar = new BabylonVector3(1,10,1); // This seems to be somewhat realistic + this.bScene_.getPhysicsEngine().setGravity(Vector3.toBabylon(patch.gravity.next, 'meters').multiply(gravity_scalar)); } // Scripts **must** be initialized after the scene is fully loaded @@ -1353,7 +1411,6 @@ class SceneBinding { if (reinitializedScripts.has(scriptId)) this.scriptManager_.bind(scriptId, nodeId); } } - this.scene_ = scene; }; @@ -1361,7 +1418,6 @@ class SceneBinding { private nodeMeshes_ = (id: string): BabylonAbstractMesh[] => { if (id in this.robotBindings_) return Dict.values(this.robotBindings_[id].links); - const bNode = this.findBNode_(id); if (bNode && bNode instanceof BabylonAbstractMesh) return [bNode]; @@ -1397,40 +1453,44 @@ class SceneBinding { // Update intersections for (const nodeId in this.intersectionFilters_) { - const nodeBoundingBoxes = this.nodeBoundingBoxes_(nodeId); - const filterIds = this.intersectionFilters_[nodeId]; - for (const filterId of filterIds) { - const filterMinMaxes = this.nodeMinMaxes_(filterId); - - let intersection = false; - for (const nodeBoundingBox of nodeBoundingBoxes) { - for (const filterMinMax of filterMinMaxes) { - intersection = nodeBoundingBox.intersectsMinMax(filterMinMax.min, filterMinMax.max); + try { + const nodeBoundingBoxes = this.nodeBoundingBoxes_(nodeId); // + const filterIds = this.intersectionFilters_[nodeId]; + for (const filterId of filterIds) { + const filterMinMaxes = this.nodeMinMaxes_(filterId); + + let intersection = false; + for (const nodeBoundingBox of nodeBoundingBoxes) { + for (const filterMinMax of filterMinMaxes) { + intersection = nodeBoundingBox.intersectsMinMax(filterMinMax.min, filterMinMax.max); + if (intersection) break; + } if (intersection) break; } - if (intersection) break; - } - - if (intersection) { - if (!this.currentIntersections_[nodeId]) this.currentIntersections_[nodeId] = new Set(); - else if (this.currentIntersections_[nodeId].has(filterId)) continue; - - this.currentIntersections_[nodeId].add(filterId); - - this.scriptManager_.trigger(ScriptManager.Event.intersectionStart({ - nodeId, - otherNodeId: filterId, - })); - } else { - if (!this.currentIntersections_[nodeId] || !this.currentIntersections_[nodeId].has(filterId)) continue; - - this.currentIntersections_[nodeId].delete(filterId); - - this.scriptManager_.trigger(ScriptManager.Event.intersectionEnd({ - nodeId, - otherNodeId: filterId, - })); + + if (intersection) { + if (!this.currentIntersections_[nodeId]) this.currentIntersections_[nodeId] = new Set(); + else if (this.currentIntersections_[nodeId].has(filterId)) continue; + + this.currentIntersections_[nodeId].add(filterId); + + this.scriptManager_.trigger(ScriptManager.Event.intersectionStart({ + nodeId, + otherNodeId: filterId, + })); + } else { + if (!this.currentIntersections_[nodeId] || !this.currentIntersections_[nodeId].has(filterId)) continue; + + this.currentIntersections_[nodeId].delete(filterId); + + this.scriptManager_.trigger(ScriptManager.Event.intersectionEnd({ + nodeId, + otherNodeId: filterId, + })); + } } + } catch (e) { + delete this.intersectionFilters_[nodeId]; } } @@ -1450,12 +1510,12 @@ class SceneBinding { } } -const IMPOSTER_TYPE_MAPPINGS: { [key in Node.Physics.Type]: number } = { - 'box': BabylonPhysicsImpostor.BoxImpostor, - 'sphere': BabylonPhysicsImpostor.SphereImpostor, - 'cylinder': BabylonPhysicsImpostor.CylinderImpostor, - 'mesh': BabylonPhysicsImpostor.MeshImpostor, - 'none': BabylonPhysicsImpostor.NoImpostor, +const PHYSICS_SHAPE_TYPE_MAPPINGS: { [key in Node.Physics.Type]: number } = { + 'box': PhysicsShapeType.BOX, + 'sphere': PhysicsShapeType.SPHERE, + 'cylinder': PhysicsShapeType.CYLINDER, + 'mesh': PhysicsShapeType.MESH, + 'none': PhysicsShapeType.CONVEX_HULL, }; export default SceneBinding; \ No newline at end of file diff --git a/src/Sim.tsx b/src/Sim.tsx index abf85cdf..1dad9a63 100644 --- a/src/Sim.tsx +++ b/src/Sim.tsx @@ -11,6 +11,8 @@ import { HemisphericLight as BabylonHemisphericLight } from '@babylonjs/core/Lig import { EventState as BabylonEventState } from '@babylonjs/core/Misc/observable'; import { PointerEventTypes as BabylonPointerEventTypes, PointerInfo as BabylonPointerInfo } from '@babylonjs/core/Events/pointerEvents'; import { DracoCompression as BabylonDracoCompression } from '@babylonjs/core/Meshes/Compression/dracoCompression'; +import HavokPhysics from "@babylonjs/havok"; +import { HavokPlugin } from '@babylonjs/core'; import '@babylonjs/loaders/glTF'; import '@babylonjs/core/Physics/physicsEngineComponent'; @@ -31,12 +33,6 @@ import { Robots } from './state/State'; -let Ammo: unknown; -if (SIMULATOR_HAS_AMMO) { - // This is on a non-standard path specified in the webpack config. - // eslint-disable-next-line @typescript-eslint/no-var-requires - Ammo = require('ammo.js'); -} import WorkerInstance from './WorkerInstance'; import AbstractRobot from './AbstractRobot'; @@ -47,6 +43,10 @@ import Camera from './state/State/Scene/Camera'; export let ACTIVE_SPACE: Space; +async function getInitializedHavok() { + return await HavokPhysics(); +} + export class Space { private static instance: Space; @@ -88,13 +88,25 @@ export class Space { } get scene() { return this.scene_; } + set scene(scene: Scene) { this.scene_ = scene; - if (this.sceneBinding_) this.sceneBinding_.scriptManager.scene = this.scene_; - + if (this.sceneBinding_) { + this.sceneBinding_.scriptManager.scene = this.scene_; + } + + // this.sceneSetting_ is true if we are currently setting the scene + // this.debounceUpdate_ is true if we are currently updating the store + // console.log("Check scene status (setting, debounce, not binding", this.sceneSetting_, this.debounceUpdate_, !this.sceneBinding_); if (this.sceneSetting_ || this.debounceUpdate_ || !this.sceneBinding_) { - if (this.sceneBinding_ && !this.sceneSetting_) this.sceneBinding_.scene = scene; - if (this.sceneSetting_ && !this.debounceUpdate_) this.nextScene_ = scene; + if (this.sceneBinding_ && !this.sceneSetting_) { + // console.log("setting next scene in sim set scene binding", scene); + this.sceneBinding_.scene = scene; + } + if (this.sceneSetting_ && !this.debounceUpdate_) { + // console.log("setting next scene in sim set scene", scene); + this.nextScene_ = scene; + } return; } @@ -108,9 +120,9 @@ export class Space { const nextScene = this.nextScene_; this.nextScene_ = undefined; await this.sceneBinding_.setScene(nextScene, Robots.loaded(store.getState().robots)); - } this.bScene_.physicsEnabled = true; + })().finally(() => { this.sceneSetting_ = false; }); @@ -181,7 +193,6 @@ export class Space { resolve(); }) .catch((e) => { - console.error('The simulator meshes failed to load', e); reject(e); }); }); @@ -225,14 +236,15 @@ export class Space { // At 100x scale, gravity should be -9.8 * 100, but this causes weird jitter behavior // Full gravity will be -9.8 * 10 - const gravityVector = new BabylonVector3(0, -9.8 * 50, 0); + // const gravityVector = new BabylonVector3(0, -9.8 * 50, 0); const state = store.getState(); - + const havokInstance = await HavokPhysics(); + const havokPlugin = new HavokPlugin(true, havokInstance); + // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-explicit-any - const ammo: unknown = await (Ammo as any)(); - this.sceneBinding_ = new SceneBinding(this.bScene_, ammo); + this.sceneBinding_ = new SceneBinding(this.bScene_, havokPlugin); this.sceneBinding_.robotLinkOrigins = this.robotLinkOrigins_; const scriptManager = this.sceneBinding_.scriptManager; @@ -249,7 +261,7 @@ export class Space { this.sceneBinding_.scriptManager.scene = this.scene_; await this.sceneBinding_.setScene(this.scene_, Robots.loaded(state.robots)); - this.bScene_.getPhysicsEngine().setSubTimeStep(5); + this.bScene_.getPhysicsEngine().setSubTimeStep(1); // (x, z) coordinates of cans around the board } @@ -327,8 +339,17 @@ export class Space { } // Update state with significant changes, if needed + // These seems to also be necessary for sensors to update this.debounceUpdate_ = true; - if (setNodeBatch.nodeIds.length > 0) this.onSetNodeBatch?.(setNodeBatch); + if (setNodeBatch.nodeIds.length > 0) { + if (setNodeBatch.nodeIds[0].id === 'robot') { + this.onSetNodeBatch?.(setNodeBatch); + } + if (setNodeBatch.nodeIds.length > 1) { + this.onSetNodeBatch?.(setNodeBatch); + // console.log("setting node batch in sim updateStore_", setNodeBatch); + } + } this.debounceUpdate_ = false; }; @@ -384,8 +405,11 @@ export class Space { this.engine.runRenderLoop(() => { // Post updates to the store this.updateStore_(); + // console.log("store updated"); this.sceneBinding_.scriptManager.trigger(ScriptManager.Event.RENDER); + // console.log("script manager triggered"); this.bScene_.render(); + // console.log("scene rendered"); }); } diff --git a/src/WorkerInstance.ts b/src/WorkerInstance.ts index 475cd0fe..437704ac 100644 --- a/src/WorkerInstance.ts +++ b/src/WorkerInstance.ts @@ -38,7 +38,7 @@ class WorkerInstance implements AbstractRobot { } sync(stateless: AbstractRobot.Stateless) { - console.log('stateless', stateless); + // console.log('stateless', stateless); this.sharedRegistersRobot_.sync(stateless); } diff --git a/src/components/OpenSceneDialog.tsx b/src/components/OpenSceneDialog.tsx index 774a6ae5..309b40db 100644 --- a/src/components/OpenSceneDialog.tsx +++ b/src/components/OpenSceneDialog.tsx @@ -205,6 +205,7 @@ export default connect((state: ReduxState, ownProps) => }), (dispatch, b) => ({ onSceneChange: (sceneId: string) => { dispatch(push(`/scene/${sceneId}`)); + location.reload(); }, listUserScenes: () => dispatch(ScenesAction.LIST_USER_SCENES), }))(OpenSceneDialog) as React.ComponentType; \ No newline at end of file diff --git a/src/components/World/AddNodeDialog.tsx b/src/components/World/AddNodeDialog.tsx index 47b8933a..648e594b 100644 --- a/src/components/World/AddNodeDialog.tsx +++ b/src/components/World/AddNodeDialog.tsx @@ -78,7 +78,7 @@ class AddNodeDialog extends React.PureComponent { this.state = { id: uuid.v4(), node: { - type: 'from-template', + type: 'from-jbc-template', templateId: 'can', name: tr('Unnamed Object'), startingOrigin: origin, diff --git a/src/components/World/NodeSettings.tsx b/src/components/World/NodeSettings.tsx index ff8e36fe..25d3b8c7 100644 --- a/src/components/World/NodeSettings.tsx +++ b/src/components/World/NodeSettings.tsx @@ -147,21 +147,57 @@ class NodeSettings extends React.PureComponent { let transmutedNode = Node.transmute(node, selectedType); - const TEMPLATE_OPTIONS: ComboBox.Option[] = [ + const JBC_TEMPLATE_OPTIONS: ComboBox.Option[] = [ ComboBox.option(LocalizedString.lookup(tr('Can'), locale), 'can'), ComboBox.option(LocalizedString.lookup(tr('Paper Ream'), locale), 'ream'), ]; + const ROCK_TEMPLATE_OPTIONS: ComboBox.Option[] = [ + ComboBox.option(LocalizedString.lookup(tr('Basalt Rock'), locale), 'basalt'), + ComboBox.option(LocalizedString.lookup(tr('Anorthosite Rock'), locale), 'anorthosite'), + ComboBox.option(LocalizedString.lookup(tr('Breccia Rock'), locale), 'breccia'), + ComboBox.option(LocalizedString.lookup(tr('Meteorite Rock'), locale), 'meteorite'), + ]; + const SPACE_TEMPLATE_OPTIONS: ComboBox.Option[] = [ + ComboBox.option(LocalizedString.lookup(tr('Communication Tower'), locale), 'tower'), + ComboBox.option(LocalizedString.lookup(tr('Hab'), locale), 'hab'), + ComboBox.option(LocalizedString.lookup(tr('Science Pad'), locale), 'sciencepad'), + ComboBox.option(LocalizedString.lookup(tr('Life Science Pack'), locale), 'lifescience'), + ComboBox.option(LocalizedString.lookup(tr('Radiation Science Pack'), locale), 'radscience'), + ComboBox.option(LocalizedString.lookup(tr('Communication Tower 2'), locale), 'comstower'), + ComboBox.option(LocalizedString.lookup(tr('Hab 2'), locale), 'habitat'), + ComboBox.option(LocalizedString.lookup(tr('Walkway'), locale), 'walkway'), + ComboBox.option(LocalizedString.lookup(tr('Solar Panel'), locale), 'solarpanel'), + ComboBox.option(LocalizedString.lookup(tr('BotGuy Astronaut'), locale), 'botguy'), + ]; + + // If the new type is from a template, set the template ID to a default value // If the new type is from a template, set the template ID to a default value - if (transmutedNode.type === 'from-template') { - const defaultTemplateId = TEMPLATE_OPTIONS[0].data as string; + if (transmutedNode.type === 'from-jbc-template') { + const defaultTemplateId = JBC_TEMPLATE_OPTIONS[0].data as string; + + transmutedNode = { + ...transmutedNode, + templateId: defaultTemplateId, + }; + } + // If the new type is from a template, set the template ID to a default value + if (transmutedNode.type === 'from-rock-template') { + const defaultTemplateId = ROCK_TEMPLATE_OPTIONS[0].data as string; + + transmutedNode = { + ...transmutedNode, + templateId: defaultTemplateId, + }; + } + if (transmutedNode.type === 'from-space-template') { + const defaultTemplateId = SPACE_TEMPLATE_OPTIONS[0].data as string; transmutedNode = { ...transmutedNode, templateId: defaultTemplateId, }; } - // If the new type is an object, add a new geometry and reset the physics type if (transmutedNode.type === 'object') { const defaultGeometryType: Geometry.Type = 'box'; @@ -436,9 +472,10 @@ class NodeSettings extends React.PureComponent { private onMaterialBasicFieldTextureUriChange_ = (field: keyof Omit) => (event: React.SyntheticEvent) => { const { node, onNodeChange } = this.props; - if (node.type !== 'object') throw new Error('Node is not an object'); + if (node.type !== 'object' && node.type !== 'from-space-template') throw new Error('Node is not an object'); const material = node.material as Material.Basic; + console.log("prev", material); const nextMaterial = { ...material }; const member = material[field]; @@ -448,6 +485,7 @@ class NodeSettings extends React.PureComponent { ...member, uri: event.currentTarget.value }; + console.log("update", nextMaterial); onNodeChange({ ...node, @@ -461,7 +499,7 @@ class NodeSettings extends React.PureComponent { private onMaterialPbrAmbientTextureUriChange_ = this.onMaterialPbrFieldTextureUriChange_('ambient'); private onMaterialPbrMetalnessTextureUriChange_ = this.onMaterialPbrFieldTextureUriChange_('metalness'); - private onMaterialBasicColorTextureUriChange_ = this.onMaterialBasicFieldTextureUriChange_('color'); + private onMaterialBasicColorTextureUriChange2_ = this.onMaterialBasicFieldTextureUriChange_('color'); private static materialType = (material: Material) => { if (!material) return 'unset'; @@ -518,11 +556,26 @@ class NodeSettings extends React.PureComponent { } }; - private onTemplateSelect_ = (index: number, option: ComboBox.Option) => { + private onJBCTemplateSelect_ = (index: number, option: ComboBox.Option) => { + const { props } = this; + const { node } = props; + + if (node.type !== 'from-jbc-template') return; + + const templateId = option.data as string; + + this.props.onNodeChange({ + ...node, + templateId + }); + }; + + private onRockTemplateSelect_ = (index: number, option: ComboBox.Option) => { + console.log("onRockTemplateSelect_"); const { props } = this; const { node } = props; - if (node.type !== 'from-template') return; + if (node.type !== 'from-rock-template') return; const templateId = option.data as string; @@ -532,6 +585,20 @@ class NodeSettings extends React.PureComponent { }); }; + private onSpaceTemplateSelect_ = (index: number, option: ComboBox.Option) => { + console.log("onSpaceTemplateSelect_"); + const { props } = this; + const { node } = props; + + if (node.type !== 'from-space-template') return; + + const templateId = option.data as string; + + this.props.onNodeChange({ + ...node, + templateId + }); + }; private onCollapsedChange_ = (key: string) => (collapsed: boolean) => { this.setState({ collapsed: { @@ -895,13 +962,55 @@ class NodeSettings extends React.PureComponent { return dict; }, {}); - - const TEMPLATE_OPTIONS: ComboBox.Option[] = [ + + const JBC_TEMPLATE_OPTIONS: ComboBox.Option[] = [ ComboBox.option(LocalizedString.lookup(tr('Can'), locale), 'can'), ComboBox.option(LocalizedString.lookup(tr('Paper Ream'), locale), 'ream'), ]; + const ROCK_TEMPLATE_OPTIONS: ComboBox.Option[] = [ + ComboBox.option(LocalizedString.lookup(tr('Basalt Rock'), locale), 'basalt'), + ComboBox.option(LocalizedString.lookup(tr('Anorthosite Rock'), locale), 'anorthosite'), + ComboBox.option(LocalizedString.lookup(tr('Breccia Rock'), locale), 'breccia'), + ComboBox.option(LocalizedString.lookup(tr('Meteorite Rock'), locale), 'meteorite'), + ]; + const SPACE_TEMPLATE_OPTIONS: ComboBox.Option[] = [ + ComboBox.option(LocalizedString.lookup(tr('Communication Tower'), locale), 'tower'), + ComboBox.option(LocalizedString.lookup(tr('Hab'), locale), 'hab'), + ComboBox.option(LocalizedString.lookup(tr('Life Science Pack'), locale), 'lifescience'), + ComboBox.option(LocalizedString.lookup(tr('Radiation Science Pack'), locale), 'radscience'), + ComboBox.option(LocalizedString.lookup(tr('Radiation Science Pack'), locale), 'noradscience'), + ComboBox.option(LocalizedString.lookup(tr('Communication Tower 2'), locale), 'commstower'), + ComboBox.option(LocalizedString.lookup(tr('Science Pad'), locale), 'sciencepad'), + ComboBox.option(LocalizedString.lookup(tr('Living Habitat'), locale), 'habitat'), + ComboBox.option(LocalizedString.lookup(tr('Research Habitat'), locale), 'research_habitat'), + ComboBox.option(LocalizedString.lookup(tr('Controls Habitat'), locale), 'control_habitat'), + ComboBox.option(LocalizedString.lookup(tr('Walkway'), locale), 'walkway'), + ComboBox.option(LocalizedString.lookup(tr('Solar Panel'), locale), 'solarpanel'), + ComboBox.option(LocalizedString.lookup(tr('BotGuy Astronaut'), locale), 'botguy'), + ComboBox.option(LocalizedString.lookup(tr('Moon Rock Container'), locale), 'container'), + ]; + + const RADIATION_TEMPLATE_OPTIONS: ComboBox.Option[] = [ + ComboBox.option(LocalizedString.lookup(tr('Radiation Science Pack - Low'), locale), 'noradscience'), + ComboBox.option(LocalizedString.lookup(tr('Radiation Science Pack - High'), locale), 'radscience'), + ]; - const TEMPLATE_REVERSE_OPTIONS: Dict = TEMPLATE_OPTIONS.reduce((dict, option, i) => { + const JBC_TEMPLATE_REVERSE_OPTIONS: Dict = JBC_TEMPLATE_OPTIONS.reduce((dict, option, i) => { + dict[option.data as string] = i; + return dict; + }, {}); + + const ROCK_TEMPLATE_REVERSE_OPTIONS: Dict = ROCK_TEMPLATE_OPTIONS.reduce((dict, option, i) => { + dict[option.data as string] = i; + return dict; + }, {}); + + const SPACE_TEMPLATE_REVERSE_OPTIONS: Dict = SPACE_TEMPLATE_OPTIONS.reduce((dict, option, i) => { + dict[option.data as string] = i; + return dict; + }, {}); + + const RADIATION_TEMPLATE_REVERSE_OPTIONS: Dict = RADIATION_TEMPLATE_OPTIONS.reduce((dict, option, i) => { dict[option.data as string] = i; return dict; }, {}); @@ -921,13 +1030,23 @@ class NodeSettings extends React.PureComponent { ]; const NODE_TYPE_OPTIONS: ComboBox.Option[] = [ - ComboBox.option(LocalizedString.lookup(tr('Empty'), locale), 'empty'), - ComboBox.option(LocalizedString.lookup(tr('Standard Object'), locale), 'from-template'), + ComboBox.option(LocalizedString.lookup(tr('Space Base'), locale), 'from-space-template'), + ComboBox.option(LocalizedString.lookup(tr('JBC Pieces'), locale), 'from-jbc-template'), + ComboBox.option(LocalizedString.lookup(tr('Moon Rock'), locale), 'from-rock-template'), ComboBox.option(LocalizedString.lookup(tr('Custom Object'), locale), 'object'), // ComboBox.option('Directional Light', 'directional-light'), ComboBox.option(LocalizedString.lookup(tr('Point Light'), locale), 'point-light'), + ComboBox.option(LocalizedString.lookup(tr('Empty'), locale), 'empty'), + ComboBox.option(LocalizedString.lookup(tr('All'), locale), 'all'), // ComboBox.option('Spot Light', 'spot-light'), ]; + + const ROCK_DESCRIPTIONS: Dict = { + 'basalt': LocalizedString.lookup(tr('Basalt is an aphanitic (fine-grained) extrusive igneous rock formed from the rapid cooling of low-viscosity lava rich in magnesium and iron (mafic lava) exposed at or very near the surface of a rocky planet or moon.'), locale), + 'anorthosite': LocalizedString.lookup(tr('Anorthosite is a phaneritic, intrusive igneous rock characterized by its composition: mostly plagioclase feldspar (90–100%), with a minimal mafic component (0–10%).'), locale), + 'breccia': LocalizedString.lookup(tr('Breccia is a rock composed of large angular broken fragments of minerals or rocks cemented together by a fine-grained matrix.'), locale), + 'meteorite': LocalizedString.lookup(tr('Meteorite is a solid piece of debris from an object, such as a comet, asteroid, or meteoroid, that originates in outer space and survives its passage through the atmosphere to reach the surface of a planet or moon.'), locale), + }; const NODE_TYPE_OPTIONS_REV = (() => { const map: Record = {}; @@ -1026,17 +1145,59 @@ class NodeSettings extends React.PureComponent { /> )} - - {node.type === 'from-template' && ( + {node.type === 'from-jbc-template' && ( )} + {node.type === 'from-rock-template' && ( + + + + )} + {node.type === 'from-space-template' && ( + + + + )} + +
+ {node.type === 'from-rock-template' && ( + <> + <>{ROCK_DESCRIPTIONS[node.templateId]} + + )} + {/* {node.material && node.material.type === 'basic' && node.material.color && node.material.color.type === 'texture' && ( */} + {node.type === 'from-space-template' && (node.templateId === 'radscience' || node.templateId === 'noradscience') && ( + + + + )} + {node.type === 'from-space-template' && node.material && node.material.type === 'basic' && node.material.color.type === 'texture' && node.templateId === 'container' && ( + + + + )}
{(node.type === 'object' && geometry && geometry.type === 'box') ? (
{ />
) : undefined} - + {(node.type === 'object' && geometry.type === 'file') ? (
@@ -1177,7 +1338,7 @@ class NodeSettings extends React.PureComponent { )} {node.material && node.material.type === 'basic' && node.material.color && node.material.color.type === 'texture' && ( - + )} diff --git a/src/index.html.ejs b/src/index.html.ejs index 5725de75..f662c494 100644 --- a/src/index.html.ejs +++ b/src/index.html.ejs @@ -3,9 +3,9 @@ - - - + + + KISS IDE Simulator diff --git a/src/login/LoginPage.tsx b/src/login/LoginPage.tsx index f29160b8..08687582 100644 --- a/src/login/LoginPage.tsx +++ b/src/login/LoginPage.tsx @@ -46,7 +46,7 @@ const Container = styled('div', (props: ThemeProps) => ({ justifyContent: 'center', width: '100%', height: '100vh', - backgroundImage: 'url(../../static/Triangular_Background_Compressed.png)', + backgroundImage: 'url(../../static/backgrounds/Triangular_Background_Compressed.png)', backgroundSize: 'cover', })); diff --git a/src/login/login.html.ejs b/src/login/login.html.ejs index 9e90556b..aaffd56f 100644 --- a/src/login/login.html.ejs +++ b/src/login/login.html.ejs @@ -3,9 +3,9 @@ - - - + + + KISS IDE Simulator diff --git a/src/node-templates/index.ts b/src/node-templates/index.ts index 0b98211b..8abfca12 100644 --- a/src/node-templates/index.ts +++ b/src/node-templates/index.ts @@ -20,7 +20,64 @@ const canTemplate: Node.TemplatedNode = { type: 'basic', color: { type: 'texture', - uri: '/static/Can Texture.png' + uri: '/static/textures/Can_Texture.png' + }, + }, + faceUvs: [Vector2.ZERO, Vector2.ZERO, Vector2.create(1, 0), Vector2.create(0, 1), Vector2.ZERO, Vector2.ZERO], +}; + +const lifescienceTemplate: Node.TemplatedNode = { + type: 'object', + geometryId: 'can', + physics: { + type: 'cylinder', + mass: Mass.grams(5), + friction: 0.7, + restitution: 0.3, + }, + material: { + type: 'basic', + color: { + type: 'texture', + uri: '/static/textures/sciencepack/life_science_pack.png' + }, + }, + faceUvs: [Vector2.ZERO, Vector2.ZERO, Vector2.create(1, 0), Vector2.create(0, 1), Vector2.ZERO, Vector2.ZERO], +}; + +const radscienceTemplate: Node.TemplatedNode = { + type: 'object', + geometryId: 'can', + physics: { + type: 'cylinder', + mass: Mass.grams(5), + friction: 0.7, + restitution: 0.3, + }, + material: { + type: 'basic', + color: { + type: 'texture', + uri: '/static/textures/sciencepack/rad_science_pack.png' + }, + }, + faceUvs: [Vector2.ZERO, Vector2.ZERO, Vector2.create(1, 0), Vector2.create(0, 1), Vector2.ZERO, Vector2.ZERO], +}; + +const noradscienceTemplate: Node.TemplatedNode = { + type: 'object', + geometryId: 'can', + physics: { + type: 'cylinder', + mass: Mass.grams(5), + friction: 0.7, + restitution: 0.3, + }, + material: { + type: 'basic', + color: { + type: 'texture', + uri: '/static/textures/sciencepack/no_rad_science_pack.png' }, }, faceUvs: [Vector2.ZERO, Vector2.ZERO, Vector2.create(1, 0), Vector2.create(0, 1), Vector2.ZERO, Vector2.ZERO], @@ -31,7 +88,7 @@ const reamTemplate: Node.TemplatedNode = { geometryId: 'ream', physics: { type: 'box', - restitution: 0, + restitution: .3, friction: 1, mass: Mass.pounds(5), }, @@ -39,44 +96,267 @@ const reamTemplate: Node.TemplatedNode = { type: 'basic', color: { type: 'color3', - color: Color.Rgb.create(250, 249, 246), + color: Color.Rgb.create(250, 250, 250), }, }, }; -const jbcMatATemplate: Node.TemplatedNode = { +const matATemplate: Node.TemplatedNode = { type: 'object', - geometryId: 'jbc_mat_a', + geometryId: 'mat', physics: { type: 'box', - restitution: 0, - friction: 1 + restitution: .3, + friction: 1, + }, + material: { + type: 'basic', + color: { + type: "texture", + uri: "/static/textures/KIPR_Surface_A.png" + }, }, }; -const jbcMatBTemplate: Node.TemplatedNode = { +const matBTemplate: Node.TemplatedNode = { type: 'object', - geometryId: 'jbc_mat_b', + geometryId: 'mat', physics: { type: 'box', - restitution: 0, - friction: 1 + restitution: .3, + friction: 1, + }, + material: { + type: 'basic', + color: { + type: "texture", + uri: "/static/textures/KIPR_Surface_B.png" + }, + }, +}; + +const sciencePadTemplate: Node.TemplatedNode = { + type: 'object', + geometryId: 'sciencepad', + physics: { + type: 'box', + restitution: 1, + friction: 1, + }, + material: { + type: 'basic', + color: { + type: "texture", + uri: "/static/textures/science_pad2.png" + }, + }, +}; + +const basaltTemplate: Node.TemplatedNode = { + type: 'object', + geometryId: 'basalt', + physics: { + type: 'mesh', + restitution: .3, + friction: 1, + mass: Mass.grams(20), + }, + material: { + type: 'basic', + color: { + type: 'texture', + uri: '/static/textures/rocks/basalt_texture_m.png' + }, + }, +}; + +const anorthositeTemplate: Node.TemplatedNode = { + type: 'object', + geometryId: 'anorthosite', + physics: { + type: 'mesh', + restitution: .3, + friction: 1, + mass: Mass.grams(20), + }, + material: { + type: 'basic', + color: { + type: 'texture', + uri: '/static/textures/rocks/anorthosite_texture_m.png' + }, + }, +}; + +const brecciaTemplate: Node.TemplatedNode = { + type: 'object', + geometryId: 'breccia', + physics: { + type: 'mesh', + restitution: .3, + friction: 1, + mass: Mass.grams(20), + }, + material: { + type: 'basic', + color: { + type: 'texture', + uri: '/static/textures/rocks/breccia_texture_m.png' + }, + }, +}; + +const meteoriteTemplate: Node.TemplatedNode = { + type: 'object', + geometryId: 'meteorite', + physics: { + type: 'mesh', + restitution: .3, + friction: 1, + mass: Mass.grams(20), + }, + material: { + type: 'basic', + color: { + type: 'texture', + uri: '/static/textures/rocks/meteorite_texture_m.png' + }, + }, +}; + +const containerTemplate: Node.TemplatedNode = { + type: 'object', + geometryId: 'container', + physics: { + type: 'mesh', + restitution: .3, + friction: 1, + // mass: Mass.pounds(20), + }, +}; +const botguyTemplate: Node.TemplatedNode = { + type: 'object', + geometryId: 'botguy', + physics: { + type: 'mesh', + restitution: .3, + friction: 1, + mass: Mass.grams(5), + }, +}; +const solarpanelTemplate: Node.TemplatedNode = { + type: 'object', + geometryId: 'solarpanel', + physics: { + type: 'mesh', + restitution: .3, + friction: 1, + mass: Mass.pounds(.3), + }, +}; + +const walkwayTemplate: Node.TemplatedNode = { + type: 'object', + geometryId: 'walkway', + physics: { + type: 'mesh', + restitution: .3, + friction: 1, + mass: Mass.pounds(.3), + }, +}; +const commstowerTemplate: Node.TemplatedNode = { + type: 'object', + geometryId: 'commstower', + physics: { + type: 'mesh', + restitution: .3, + friction: 1, + mass: Mass.pounds(.3), + }, +}; + +const habitatTemplate: Node.TemplatedNode = { + type: 'object', + geometryId: 'habitat', + physics: { + type: 'mesh', + restitution: .3, + friction: 1, + mass: Mass.pounds(.3), + }, +}; + +const habitatResearchTemplate: Node.TemplatedNode = { + type: 'object', + geometryId: 'research_habitat', + physics: { + type: 'mesh', + restitution: .3, + friction: 1, + mass: Mass.pounds(.3), + }, +}; + +const habitatControlTemplate: Node.TemplatedNode = { + type: 'object', + geometryId: 'control_habitat', + physics: { + type: 'mesh', + restitution: .3, + friction: 1, + mass: Mass.pounds(.3), }, }; export const preBuiltTemplates = Object.freeze>>({ 'can': canTemplate, + 'sciencepad': sciencePadTemplate, + 'lifescience': lifescienceTemplate, + 'radscience': radscienceTemplate, + 'noradscience': noradscienceTemplate, 'ream': reamTemplate, - 'jbc_mat_a': jbcMatATemplate, - 'jbc_mat_b': jbcMatBTemplate, + 'matA': matATemplate, + 'matB': matBTemplate, + 'basalt': basaltTemplate, + 'anorthosite': anorthositeTemplate, + 'breccia': brecciaTemplate, + 'meteorite': meteoriteTemplate, + 'container': containerTemplate, + 'botguy': botguyTemplate, + 'solarpanel': solarpanelTemplate, + 'walkway': walkwayTemplate, + 'commstower': commstowerTemplate, + 'habitat': habitatTemplate, + 'research_habitat': habitatResearchTemplate, + 'control_habitat': habitatControlTemplate, }); + export const preBuiltGeometries = Object.freeze>({ 'can': { type: 'cylinder', height: Distance.centimeters(11.15), radius: Distance.centimeters(3), }, + 'lifescience': { + type: 'cylinder', + height: Distance.centimeters(7), + radius: Distance.centimeters(3), + }, + 'radscience': { + type: 'cylinder', + height: Distance.centimeters(7), + radius: Distance.centimeters(3), + }, + 'sciencepad': { + type: 'box', + size: { + x: Distance.feet(1), + y: Distance.centimeters(4), + z: Distance.feet(1), + } + }, 'ream': { type: 'box', size: { @@ -85,12 +365,72 @@ export const preBuiltGeometries = Object.freeze>({ z: Distance.centimeters(21.59), }, }, - 'jbc_mat_a': { + 'mat': { + type: 'box', + size: { + x: Distance.feet(2), + y: Distance.centimeters(.1), + z: Distance.feet(4), + } + }, + 'basalt': { + type: 'sphere', + radius: Distance.centimeters(5), + squash: 1, + stretch: 1, + noise: .5, + }, + 'anorthosite': { + type: 'sphere', + radius: Distance.centimeters(5), + squash: .8, + stretch: 1, + noise: 1, + }, + 'breccia': { + type: 'sphere', + radius: Distance.centimeters(5), + squash: 1, + stretch: 1, + noise: 1, + }, + 'meteorite': { + type: 'sphere', + radius: Distance.centimeters(5), + squash: 1, + stretch: 1, + noise: 1, + }, + 'container': { + type: 'file', + uri: '/static/object_binaries/container_with_lid.glb' + }, + 'botguy': { + type: 'file', + uri: '/static/object_binaries/ogBotguy2.glb' + }, + 'solarpanel': { + type: 'file', + uri: '/static/object_binaries/basic_solar_panel.glb' + }, + 'walkway': { + type: 'file', + uri: '/static/object_binaries/basic_walkway.glb' + }, + 'commstower': { + type: 'file', + uri: '/static/object_binaries/comm_dish.glb' + }, + 'habitat': { + type: 'file', + uri: '/static/object_binaries/basic_hab.glb' + }, + 'research_habitat': { type: 'file', - uri: '/static/jbcMatA.glb' + uri: '/static/object_binaries/manipulator_hab.glb' }, - 'jbc_mat_b': { + 'control_habitat': { type: 'file', - uri: '/static/jbcMatB.glb' + uri: '/static/object_binaries/com_hab.glb' }, }); \ No newline at end of file diff --git a/src/pages/Dashboard.tsx b/src/pages/Dashboard.tsx index c70c4bd6..aff3e1d2 100644 --- a/src/pages/Dashboard.tsx +++ b/src/pages/Dashboard.tsx @@ -78,14 +78,14 @@ class Dashboard extends React.PureComponent { title={LocalizedString.lookup(tr('Tutorials'), locale)} description={LocalizedString.lookup(tr('Learn how to get started with the simulator'), locale)} backgroundColor={'#6c6ca1'} - backgroundImage={'url(../../static/Laptop_Icon_Sunscreen.png)'} + backgroundImage={'url(../../static/icons/Laptop_Icon_Sunscreen.png)'} onClick={onTutorialsClick} /> @@ -93,7 +93,7 @@ class Dashboard extends React.PureComponent { theme={theme} title={LocalizedString.lookup(tr('About'), locale)} description={LocalizedString.lookup(tr('KIPR is a 501(c) 3 organization started to make the long-term educational benefits of robotics accessible to students.'), locale)} - backgroundImage={'linear-gradient(#3b3c3c, transparent), url(../../static/Botguy-Picture-Small.png)'} + backgroundImage={'linear-gradient(#3b3c3c, transparent), url(../../static/icons/Botguy-Picture-Small.png)'} backgroundColor={'#3b3c3c'} backgroundSize={'80%'} hoverBackgroundSize={'95%'} diff --git a/src/pages/tutorialList.ts b/src/pages/tutorialList.ts index d5acd7c2..baafd348 100644 --- a/src/pages/tutorialList.ts +++ b/src/pages/tutorialList.ts @@ -7,25 +7,25 @@ export const tutorialList: Tutorial [] = [ title: tr('Quick Start'), description: tr('Learn how to get started with the simulator'), backgroundColor: '#6c6ca1', - backgroundImage: 'url(../../static/Laptop_Icon_Sunscreen.png)', + backgroundImage: 'url(../../static/icons/Laptop_Icon_Sunscreen.png)', src: 'https://www.youtube.com/embed/7Szf-iQjNCw', }, { title: tr('Navigating in 3D'), description: tr('Learn the controls for navigating in 3D in the simulator'), - backgroundImage: 'linear-gradient(#3b3c3c, transparent), url(../../static/Simulator_Full_View.png)', + backgroundImage: 'linear-gradient(#3b3c3c, transparent), url(../../static/example_images/Simulator_Full_View.png)', src: 'https://www.youtube.com/embed/RBpWIpBlYK8', }, { title: tr('Robot Section'), description: tr('How to use the robot section'), - backgroundImage: 'url(../../static/Simulator-Robot-Closeup.png)', + backgroundImage: 'url(../../static/example_images/Simulator-Robot-Closeup.png)', src: 'https://www.youtube.com/embed/SmYR1esidcc', }, { title: tr('World Section'), description: tr('Learn how to create and manipulate items and scene in the simulator'), - backgroundImage: 'linear-gradient(#3b3c3c, transparent), url(../../static/Can_Ream.png)', + backgroundImage: 'linear-gradient(#3b3c3c, transparent), url(../../static/textures/Can_Ream.png)', src: 'https://www.youtube.com/embed/K7GsS8s3Rfg', }, ]; diff --git a/src/robots/demobot.ts b/src/robots/demobot.ts index 5cc88c61..2862d098 100644 --- a/src/robots/demobot.ts +++ b/src/robots/demobot.ts @@ -18,8 +18,9 @@ export const DEMOBOT: Robot = { chassis: Node.link({ collisionBody: Node.Link.CollisionBody.EMBEDDED, geometryId: 'chassis_link', - mass: grams(1160 - 800), - friction: 0.1, + mass: grams(400), + restitution: 0, + friction: 0.01, }), lightSensor: Node.lightSensor({ parentId: 'chassis', @@ -27,13 +28,14 @@ export const DEMOBOT: Robot = { position: Vector3.meters(0.3, 0, 0), orientation: Rotation.eulerDegrees(90, 0, 0), }, - analogPort: 3, + analogPort: 2, }), wombat: Node.weight({ parentId: 'chassis', - mass: grams(800), + mass: grams(200), origin: { - position: Vector3.meters(-0.08786, 0.063695, 0), + position: Vector3.meters(-0.06, 0.04, 0), + // position: Vector3.meters(-0.08786, 0.063695, 0), }, }), left_wheel: Node.motor({ @@ -48,8 +50,9 @@ export const DEMOBOT: Robot = { parentId: 'left_wheel', geometryId: 'wheel_link', collisionBody: Node.Link.CollisionBody.CYLINDER, - mass: grams(14), - friction: 25, + mass: grams(50), + friction: 100, + restitution: 0, }), right_wheel: Node.motor({ parentAxis: RawVector3.Z, @@ -62,8 +65,9 @@ export const DEMOBOT: Robot = { parentId: 'right_wheel', geometryId: 'wheel_link', collisionBody: Node.Link.CollisionBody.CYLINDER, - mass: grams(14), - friction: 25, + mass: grams(50), + friction: 100, + restitution: 0, }), arm: Node.servo({ parentAxis: RawVector3.NEGATIVE_Z, @@ -78,6 +82,7 @@ export const DEMOBOT: Robot = { geometryId: 'arm_link', mass: grams(14), friction: 50, + restitution: 0, collisionBody: Node.Link.CollisionBody.EMBEDDED, }), claw: Node.servo({ @@ -91,8 +96,9 @@ export const DEMOBOT: Robot = { claw_link: Node.link({ parentId: 'claw', geometryId: 'claw_link', - mass: grams(14), + mass: grams(7), friction: 50, + restitution: 0, collisionBody: Node.Link.CollisionBody.EMBEDDED, }), touch_sensor: Node.touchSensor({ @@ -137,12 +143,12 @@ export const DEMOBOT: Robot = { }), }, geometry: { - chassis_link: Geometry.remoteMesh({ uri: '/static/chassis.glb' }), - wheel_link: Geometry.remoteMesh({ uri: '/static/wheel.glb' }), - arm_link: Geometry.remoteMesh({ uri: '/static/arm.glb' }), - claw_link: Geometry.remoteMesh({ uri: '/static/claw.glb' }), + chassis_link: Geometry.remoteMesh({ uri: '/static/object_binaries/chassis.glb' }), + wheel_link: Geometry.remoteMesh({ uri: '/static/object_binaries/wheel.glb' }), + arm_link: Geometry.remoteMesh({ uri: '/static/object_binaries/arm.glb' }), + claw_link: Geometry.remoteMesh({ uri: '/static/object_binaries/claw.glb' }), }, origin: { - orientation: Rotation.eulerDegrees(0, -90, 0), + orientation: Rotation.eulerDegrees(0, 0, 0), } }; \ No newline at end of file diff --git a/src/scenes/index.ts b/src/scenes/index.ts index 3c7e4121..fabb64dd 100644 --- a/src/scenes/index.ts +++ b/src/scenes/index.ts @@ -34,4 +34,5 @@ export * from './jbc20'; export * from './jbc21'; export * from './jbc22'; export * from './scriptPlayground'; -export * from './lightSensorTest'; \ No newline at end of file +export * from './lightSensorTest'; +export * from './moonSandbox'; \ No newline at end of file diff --git a/src/scenes/jbc15b.ts b/src/scenes/jbc15b.ts index f73f5384..e296e41b 100644 --- a/src/scenes/jbc15b.ts +++ b/src/scenes/jbc15b.ts @@ -55,7 +55,7 @@ export const JBC_15B: Scene = { origin: ROBOT_ORIGIN, }, 'ream1': { - type: 'from-template', + type: 'from-jbc-template', templateId: 'ream', name: tr('Paper Ream 1'), startingOrigin: REAM1_ORIGIN, @@ -63,7 +63,7 @@ export const JBC_15B: Scene = { visible: true, }, 'ream2': { - type: 'from-template', + type: 'from-jbc-template', templateId: 'ream', name: tr('Paper Ream 2'), startingOrigin: REAM2_ORIGIN, diff --git a/src/scenes/jbc19.ts b/src/scenes/jbc19.ts index 76c0e769..e1933a5d 100644 --- a/src/scenes/jbc19.ts +++ b/src/scenes/jbc19.ts @@ -31,7 +31,7 @@ export const JBC_19: Scene = { 'can2': createCanNode(2, { x: Distance.centimeters(-10), y: Distance.centimeters(6), z: Distance.centimeters(91.6) }), 'can3': createCanNode(3, { x: Distance.centimeters(-17), y: Distance.centimeters(6), z: Distance.centimeters(84.6) }), 'ream': { - type: 'from-template', + type: 'from-jbc-template', templateId: 'ream', name: tr('Paper Ream'), startingOrigin: REAM_ORIGIN, diff --git a/src/scenes/jbc20.ts b/src/scenes/jbc20.ts index c07aa3e8..23ce1266 100644 --- a/src/scenes/jbc20.ts +++ b/src/scenes/jbc20.ts @@ -43,7 +43,7 @@ export const JBC_20: Scene = { 'can10': createCanNode(10), 'can12': createCanNode(12), 'ream': { - type: 'from-template', + type: 'from-jbc-template', templateId: 'ream', name: tr('Paper Ream'), startingOrigin: REAM_ORIGIN, diff --git a/src/scenes/jbcBase.ts b/src/scenes/jbcBase.ts index 3e328639..d4b0c036 100644 --- a/src/scenes/jbcBase.ts +++ b/src/scenes/jbcBase.ts @@ -12,7 +12,7 @@ import { sprintf } from 'sprintf-js'; import Dict from '../Dict'; const ROBOT_ORIGIN: ReferenceFrame = { - position: Vector3.centimeters(0, 5, 0), + position: Vector3.centimeters(0, 0, 0), orientation: Rotation.eulerDegrees(0, 0, 0), }; @@ -26,23 +26,20 @@ const ROBOT: Node.Robot = { origin: ROBOT_ORIGIN }; + const JBC_MAT_ORIGIN: ReferenceFrame = { position: { x: Distance.centimeters(0), y: Distance.centimeters(-7), z: Distance.centimeters(50), }, - scale: { - x: 100, - y: 100, - z: 100, - } + orientation: Rotation.eulerDegrees(0, 0, 0) }; const GROUND_ORIGIN: ReferenceFrame = { position: { x: Distance.centimeters(0), - y: Distance.centimeters(-7.2), + y: Distance.centimeters(-7.512), z: Distance.centimeters(50), }, orientation: { @@ -68,22 +65,32 @@ export function createBaseSceneSurfaceA(): Scene { author: Author.organization('kipr'), geometry: { 'ground': { - type: 'plane', + type: 'box', size: { x: Distance.meters(3.54), y: Distance.meters(3.54), + z: Distance.meters(0.01), }, }, + 'mat': { + type: 'box', + size: { + x: Distance.feet(2), + y: Distance.centimeters(.1), + z: Distance.feet(4), + } + }, }, nodes: { 'robot': ROBOT, - 'jbc_mat_a': { - type: 'from-template', - templateId: 'jbc_mat_a', - name: tr('JBC Surface A'), + 'matA': { + type: 'from-jbc-template', + templateId: 'matA', + name: tr('JBC Mat A'), startingOrigin: JBC_MAT_ORIGIN, origin: JBC_MAT_ORIGIN, visible: true, + editable: true, }, 'ground': { type: 'object', @@ -94,7 +101,7 @@ export function createBaseSceneSurfaceA(): Scene { visible: true, physics: { type: 'box', - restitution: 0, + restitution: .3, friction: 1, }, }, @@ -108,21 +115,21 @@ export function createBaseSceneSurfaceA(): Scene { } }, camera: Camera.arcRotate({ - radius: Distance.meters(5), + radius: Distance.meters(1), target: { x: Distance.meters(0), y: Distance.meters(0), - z: Distance.meters(0.5), + z: Distance.meters(0.25), }, position: { - x: Distance.meters(1), - y: Distance.meters(0.91), - z: Distance.meters(1.5), + x: Distance.meters(0.5), + y: Distance.meters(0.5), + z: Distance.meters(-.5), } }), gravity: { x: Distance.meters(0), - y: Distance.meters(-9.8 / 2), + y: Distance.meters(-9.8 * 0.4), z: Distance.meters(0), } }; @@ -137,22 +144,24 @@ export function createBaseSceneSurfaceB(): Scene { author: Author.organization('kipr'), geometry: { 'ground': { - type: 'plane', + type: 'box', size: { x: Distance.meters(3.54), y: Distance.meters(3.54), + z: Distance.meters(0.01), }, }, }, nodes: { 'robot': ROBOT, - 'jbc_mat_b': { - type: 'from-template', - templateId: 'jbc_mat_b', - name: tr('JBC Surface B'), + 'matB': { + type: 'from-jbc-template', + templateId: 'matB', + name: tr('JBC Mat B'), startingOrigin: JBC_MAT_ORIGIN, origin: JBC_MAT_ORIGIN, visible: true, + editable: true, }, 'ground': { type: 'object', @@ -163,8 +172,8 @@ export function createBaseSceneSurfaceB(): Scene { visible: true, physics: { type: 'box', - restitution: 0, - friction: 1, + restitution: 0.1, + friction: 10, }, }, 'light0': { @@ -177,7 +186,7 @@ export function createBaseSceneSurfaceB(): Scene { }, }, camera: Camera.arcRotate({ - radius: Distance.meters(5), + radius: Distance.meters(2), target: { x: Distance.meters(0), y: Distance.meters(0), @@ -191,7 +200,7 @@ export function createBaseSceneSurfaceB(): Scene { }), gravity: { x: Distance.meters(0), - y: Distance.meters(-9.8 / 2), + y: Distance.meters(-9.8 * .4), z: Distance.meters(0), } }; @@ -208,10 +217,11 @@ export function createBaseSceneSurfaceB(): Scene { export function createCanNode(canNumber: number, canPosition?: Vector3, editable?: boolean, visible?: boolean): Node { const origin: ReferenceFrame = { position: canPosition ?? canPositions[canNumber - 1], + orientation: Rotation.eulerDegrees(180, 0, 0), }; return { - type: 'from-template', + type: 'from-jbc-template', templateId: 'can', name: Dict.map(tr('Can %s'), (str: string) => sprintf(str, canNumber)), startingOrigin: origin, diff --git a/src/scenes/jbcSandboxA.ts b/src/scenes/jbcSandboxA.ts index fdfaf939..73363ab6 100644 --- a/src/scenes/jbcSandboxA.ts +++ b/src/scenes/jbcSandboxA.ts @@ -57,7 +57,7 @@ export const JBC_Sandbox_A: Scene = { 'can11': createCanNode(11, undefined, true, false), 'can12': createCanNode(12, undefined, true, false), 'ream1': { - type: 'from-template', + type: 'from-jbc-template', templateId: 'ream', name: tr('Paper Ream 1'), startingOrigin: REAM1_ORIGIN, @@ -66,7 +66,7 @@ export const JBC_Sandbox_A: Scene = { visible: false, }, 'ream2': { - type: 'from-template', + type: 'from-jbc-template', templateId: 'ream', name: tr('Paper Ream 2'), startingOrigin: REAM2_ORIGIN, diff --git a/src/scenes/moonBase.ts b/src/scenes/moonBase.ts new file mode 100644 index 00000000..f4f10465 --- /dev/null +++ b/src/scenes/moonBase.ts @@ -0,0 +1,167 @@ +import { ReferenceFrame, Rotation, Vector3 } from "../unit-math"; +import { Angle, Distance, Mass } from "../util"; +import Node from "../state/State/Scene/Node"; +import Camera from "../state/State/Scene/Camera"; +import Scene from "../state/State/Scene"; +import AbstractRobot from '../AbstractRobot'; +import LocalizedString from '../util/LocalizedString'; +import Author from '../db/Author'; +import { Color } from "../state/State/Scene/Color"; +import { StandardMaterial } from '@babylonjs/core/Materials/standardMaterial'; + +import tr from '@i18n'; +import { sprintf } from 'sprintf-js'; +import Dict from '../Dict'; + + +const ROBOT_ORIGIN: ReferenceFrame = { + position: Vector3.centimeters(0, 4, 0), + orientation: Rotation.eulerDegrees(0, -45, 0), +}; + +const GROUND_ORIGIN: ReferenceFrame = { + position: Vector3.centimeters(0, -.5, 50), + orientation: Rotation.eulerDegrees(0, 0, 0) +}; + +const START_ORIGIN: ReferenceFrame = { + position: Vector3.centimeters(0, 0, 0), + orientation: Rotation.eulerDegrees(0, 90, 0) +}; + +const SKY_ORIGIN: ReferenceFrame = { + position: Vector3.centimeters(0, -17.2, 50), + orientation: Rotation.eulerDegrees(90, 0, 0) +}; + +const LIGHT_ORIGIN: ReferenceFrame = { + position: Vector3.meters(0, 40.91, .5), +}; + +const ROBOT: Node.Robot = { + type: 'robot', + name: tr('Robot'), + robotId: 'demobot', + state: AbstractRobot.Stateless.NIL, + visible: true, + startingOrigin: ROBOT_ORIGIN, + origin: ROBOT_ORIGIN +}; + +export function createBaseSceneSurface(): Scene { + return { + name: tr('Base Scene - Moon Surface'), + description: tr('A base scene. Intended to be augmented to create full Moon scenes'), + author: Author.organization('kipr'), + geometry: { + 'moon ground': { + type: 'cylinder', + radius: Distance.feet(25), + height: Distance.centimeters(1), + }, + 'sky': { + type: 'box', + size: { + x: Distance.meters(15), + y: Distance.meters(15), + z: Distance.meters(15), + } + }, + 'start': { + type: 'box', + size: { + x: Distance.feet(2), + y: Distance.centimeters(.1), + z: Distance.feet(2), + } + }, + + }, + nodes: { + 'robot': ROBOT, + 'Moon ground': { + type: 'object', + geometryId: 'moon ground', + name: tr('1.1.1 Ground'), + startingOrigin: GROUND_ORIGIN, + origin: GROUND_ORIGIN, + visible: true, + physics: { + type: 'box', + restitution: .3, + friction: 1, + }, + material: { + type: 'basic', + color: { + type: "texture", + uri: "/static/textures/Moon-2d-Surface.png" + }, + }, + }, + 'start': { + type: 'object', + geometryId: 'start', + name: tr('1.1.6-8 Start Area'), + startingOrigin: START_ORIGIN, + origin: START_ORIGIN, + visible: true, + editable: true, + physics: { + type: 'box', + restitution: .3, + friction: 1, + }, + material: { + type: 'basic', + color: { + type: "texture", + uri: "/static/textures/start_texture_light.png" + }, + }, + }, + 'light0': { + type: 'point-light', + intensity: .25, + name: tr('Light'), + startingOrigin: LIGHT_ORIGIN, + origin: LIGHT_ORIGIN, + visible: true + }, + 'night_sky': { + type: 'object', + name: tr('1.1.2-4 Sky'), + geometryId: 'sky', + visible: true, + startingOrigin: SKY_ORIGIN, + origin: SKY_ORIGIN, + material: { + type: 'basic', + color: { + type: "texture", + uri: "/static/textures/earthrise.png" + }, + }, + } + }, + camera: Camera.arcRotate({ + radius: Distance.meters(5), + target: { + x: Distance.meters(0), + y: Distance.meters(0), + z: Distance.meters(0.05), + }, + position: { + x: Distance.meters(-1), + y: Distance.meters(0.51), + z: Distance.meters(-1.5), + } + }), + gravity: { + x: Distance.meters(0), + y: Distance.meters(-9.8 * 0.5), + z: Distance.meters(0), + } + }; +} + diff --git a/src/scenes/moonSandbox.ts b/src/scenes/moonSandbox.ts new file mode 100644 index 00000000..fcf35916 --- /dev/null +++ b/src/scenes/moonSandbox.ts @@ -0,0 +1,335 @@ +// In scenes we build upon the base, and add objects to the scene. +// The objects are set up in the node-templates index.ts file. +// Here we add the objects and their properties to the scene. + +import Scene from "../state/State/Scene"; +import Script from '../state/State/Scene/Script'; +import { ReferenceFrame, Rotation, Vector3 } from "../unit-math"; +import { Distance } from "../util"; +import { Color } from '../state/State/Scene/Color'; +import LocalizedString from '../util/LocalizedString'; + +import { createBaseSceneSurface } from './moonBase'; + +import tr from '@i18n'; + +const rockEvaluation = ` +const setNodeVisible = (nodeId, visible) => scene.setNode(nodeId, { + ...scene.nodes[nodeId], + visible: visible +}); + +// When the pad detects life a green light glows. + +scene.addOnIntersectionListener('meteorite', (type, otherNodeId) => { + console.log('meteorite may have life!', type, otherNodeId); + const visible = type === 'start'; + setNodeVisible('life indicator', visible); + if (visible) alert('You found a meteorite rock! It may have life! Bring this to the container for further study.'); +}, 'sciencepad'); + +scene.addOnIntersectionListener('basalt', (type, otherNodeId) => { + console.log('basalt may have life!', type, otherNodeId); + const visible = type === 'start'; + setNodeVisible('nolife indicator', visible); + if (visible) alert('You found a basalt rock! There does not appear to be life here. Try another rock.'); +}, 'sciencepad'); + +scene.addOnIntersectionListener('anorthosite', (type, otherNodeId) => { + console.log('anorthosite may have life!', type, otherNodeId); + const visible = type === 'start'; + setNodeVisible('nolife indicator', visible); + if (visible) alert('You found a anorthosite rock! There does not appear to be life here. Try another rock.'); +}, 'sciencepad'); + +scene.addOnIntersectionListener('breccia', (type, otherNodeId) => { + console.log('breccia may have life!', type, otherNodeId); + const visible = type === 'start'; + setNodeVisible('nolife indicator', visible); + if (visible) alert('You found a breccia rock! There does not appear to be life here. Try another rock.'); +}, 'sciencepad'); +`; + + +const baseScene = createBaseSceneSurface(); + +const SCIENCEPAD_ORIGIN: ReferenceFrame = { + position: Vector3.centimeters(0, 2, 100), + orientation: Rotation.eulerDegrees(0, 90, 0) +}; + +const CONTAINER_ORIGIN: ReferenceFrame = { + position: Vector3.centimeters(-60, 0, 100), + scale: { x: 15, y: 15, z: 15 }, + orientation: Rotation.eulerDegrees(0, 180, 0) +}; + +const BOTGUY_ORIGIN: ReferenceFrame = { + position: Vector3.centimeters(-50, 0, 30), + scale: { x: 70, y: 70, z: 70 } +}; + +const SOLARPANEL_ORIGIN: ReferenceFrame = { + position: Vector3.centimeters(20, 0, 135), + scale: { x: 4, y: 4, z: 4 }, + orientation: Rotation.eulerDegrees(0, 180, 0) +}; + + +const COMMSTOWER_ORIGIN: ReferenceFrame = { + position: Vector3.centimeters(80, 4, 0), + scale: { x: 10, y: 10, z: 10 } +}; + +const HABITAT_ORIGIN: ReferenceFrame = { + position: Vector3.centimeters(50, 17, 130), + scale: { x: 12, y: 12, z: 12 }, + orientation: Rotation.eulerDegrees(0, 180, 0) +}; + +const WALKWAY_ORIGIN: ReferenceFrame = { + position: Vector3.centimeters(50, 17, 80), + scale: { x: 12, y: 12, z: 12 } +}; + +const RESEARCH_HABITAT_ORIGIN: ReferenceFrame = { + position: Vector3.centimeters(50, 17, 25), + scale: { x: 12, y: 12, z: 12 }, + orientation: Rotation.eulerDegrees(0, 180, 0) +}; + +const CONTROL_HABITAT_ORIGIN: ReferenceFrame = { + position: Vector3.centimeters(50, 17, -35), + scale: { x: 12, y: 12, z: 12 }, + orientation: Rotation.eulerDegrees(0, 180, 0) +}; + +const LIFESCIENCE_ORIGIN: ReferenceFrame = { + position: Vector3.centimeters(-50, 6, 50.3), +}; + +const RADSCIENCE_ORIGIN: ReferenceFrame = { + position: Vector3.centimeters(-50, 6, 40.3), +}; + +const BASALT_ORIGIN: ReferenceFrame = { + position: Vector3.centimeters(-30, 4.5, 61.3), +}; +const ANORTHOSITE_ORIGIN: ReferenceFrame = { + position: Vector3.centimeters(-15, 4.5, 61.3), +}; +const BRECCIA_ORIGIN: ReferenceFrame = { + position: Vector3.centimeters(0, 4.5, 61.3), +}; +const METEORITE_ORIGIN: ReferenceFrame = { + position: Vector3.centimeters(15, 4.5, 61.3), +}; +export const Moon_Sandbox: Scene = { + ...baseScene, + name: tr('Moon Sandbox'), + description: tr('Lunar sandbox. Currently supports 4 types of rocks. Demo Ready.'), + scripts: { + 'rockEvaluation': Script.ecmaScript('Rock Evaluation Test', rockEvaluation), + }, + geometry: { + ...baseScene.geometry, + 'indicator': { + type: 'cylinder', + radius: Distance.centimeters(3), + height: Distance.centimeters(0.1), + }, + }, + nodes: { + ...baseScene.nodes, + 'robot': { + ...baseScene.nodes['robot'], + editable: true, + }, + 'basalt': { + type: 'from-rock-template', + templateId: 'basalt', + name: tr('Basalt Rock'), + startingOrigin: BASALT_ORIGIN, + origin: BASALT_ORIGIN, + editable: true, + visible: true, + }, + 'anorthosite': { + type: 'from-rock-template', + templateId: 'anorthosite', + name: tr('Anorthosite Rock'), + startingOrigin: ANORTHOSITE_ORIGIN, + origin: ANORTHOSITE_ORIGIN, + editable: true, + visible: true, + }, + 'breccia': { + type: 'from-rock-template', + templateId: 'breccia', + name: tr('Breccia Rock'), + startingOrigin: BRECCIA_ORIGIN, + origin: BRECCIA_ORIGIN, + editable: true, + visible: true, + }, + 'meteorite': { + type: 'from-rock-template', + templateId: 'meteorite', + name: tr('Meteorite Rock'), + startingOrigin: METEORITE_ORIGIN, + origin: METEORITE_ORIGIN, + editable: true, + visible: true, + }, + 'lifescience': { + type: 'from-space-template', + templateId: 'lifescience', + name: tr('Life Science Pack'), + startingOrigin: LIFESCIENCE_ORIGIN, + origin: LIFESCIENCE_ORIGIN, + editable: true, + visible: true, + }, + 'radscience': { + type: 'from-space-template', + templateId: 'radscience', + name: tr('Radiation Science Pack - High'), + startingOrigin: RADSCIENCE_ORIGIN, + origin: RADSCIENCE_ORIGIN, + editable: true, + visible: true, + }, + // 'noradscience': { + // type: 'from-space-template', + // templateId: 'noradscience', + // name: tr('Radiation Science Pack - Low'), + // startingOrigin: RADSCIENCE_ORIGIN, + // origin: RADSCIENCE_ORIGIN, + // editable: true, + // visible: true, + // }, + 'sciencepad': { + type: 'from-space-template', + templateId: 'sciencepad', + name: tr('Science Pad'), + startingOrigin: SCIENCEPAD_ORIGIN, + origin: SCIENCEPAD_ORIGIN, + editable: true, + visible: true, + }, + 'container': { + type: 'from-space-template', + templateId: 'container', + name: tr('Container'), + startingOrigin: CONTAINER_ORIGIN, + origin: CONTAINER_ORIGIN, + editable: true, + visible: true, + material: { + type: 'basic', + color: { + type: "texture", + uri: "Rocks with Possible Life" // default text to display + }, + }, + }, + 'botguy': { + type: 'from-space-template', + templateId: 'botguy', + name: tr('Space Bot Guy'), + startingOrigin: BOTGUY_ORIGIN, + origin: BOTGUY_ORIGIN, + visible: true, + editable: true, + }, + 'solarpanel': { + type: 'from-space-template', + templateId: 'solarpanel', + name: tr('Solar Panel'), + startingOrigin: SOLARPANEL_ORIGIN, + origin: SOLARPANEL_ORIGIN, + visible: true, + editable: true, + }, + 'walkway': { + type: 'from-space-template', + templateId: 'walkway', + name: tr('Walkway'), + startingOrigin: WALKWAY_ORIGIN, + origin: WALKWAY_ORIGIN, + visible: true, + editable: true, + }, + 'commstower': { + type: 'from-space-template', + templateId: 'commstower', + name: tr('Comms Tower'), + startingOrigin: COMMSTOWER_ORIGIN, + origin: COMMSTOWER_ORIGIN, + visible: true, + editable: true, + }, + 'habitat': { + type: 'from-space-template', + templateId: 'habitat', + name: tr('Human Living Habitat'), + startingOrigin: HABITAT_ORIGIN, + origin: HABITAT_ORIGIN, + visible: true, + editable: true, + }, + 'research_habitat': { + type: 'from-space-template', + templateId: 'research_habitat', + name: tr('Human Research Habitat'), + startingOrigin: RESEARCH_HABITAT_ORIGIN, + origin: RESEARCH_HABITAT_ORIGIN, + visible: true, + editable: true, + }, + 'control_habitat': { + type: 'from-space-template', + templateId: 'control_habitat', + name: tr('Human Control/Comms Station Habitat'), + startingOrigin: CONTROL_HABITAT_ORIGIN, + origin: CONTROL_HABITAT_ORIGIN, + visible: true, + editable: true, + }, + 'life indicator': { + type: 'object', + geometryId: 'indicator', + name: tr('Life Indicator Light'), + visible: false, + editable: true, + origin: { + position: Vector3.centimeters(-10, 4, 110) + }, + material: { + type: 'pbr', + emissive: { + type: 'color3', + color: Color.rgb(0, 255, 0), + }, + }, + }, + 'nolife indicator': { + type: 'object', + geometryId: 'indicator', + name: tr('No Life Indicator Light'), + visible: false, + editable: true, + origin: { + position: Vector3.centimeters(10, 4, 110) + }, + material: { + type: 'pbr', + emissive: { + type: 'color3', + color: Color.rgb(255, 0, 0), + }, + }, + }, + } + +}; \ No newline at end of file diff --git a/src/scenes/scriptPlayground.ts b/src/scenes/scriptPlayground.ts index e1decdbe..7d8f7353 100644 --- a/src/scenes/scriptPlayground.ts +++ b/src/scenes/scriptPlayground.ts @@ -118,7 +118,7 @@ export const scriptPlayground: Scene = { 'can11': createCanNode(11, undefined, true, false), 'can12': createCanNode(12, undefined, true, false), 'ream1': { - type: 'from-template', + type: 'from-jbc-template', templateId: 'ream', name: tr('Paper Ream 1'), origin: { @@ -136,7 +136,7 @@ export const scriptPlayground: Scene = { visible: false, }, 'ream2': { - type: 'from-template', + type: 'from-jbc-template', templateId: 'ream', name: tr('Paper Ream 2'), origin: { diff --git a/src/state/State/Scene/Geometry.ts b/src/state/State/Scene/Geometry.ts index b26aea68..0f72c992 100644 --- a/src/state/State/Scene/Geometry.ts +++ b/src/state/State/Scene/Geometry.ts @@ -33,6 +33,9 @@ namespace Geometry { export interface Sphere { type: 'sphere'; radius: Distance; + squash?: number; + stretch?: number; + noise?: number; } export namespace Sphere { diff --git a/src/state/State/Scene/Node.ts b/src/state/State/Scene/Node.ts index acdbc807..80c1eff9 100644 --- a/src/state/State/Scene/Node.ts +++ b/src/state/State/Scene/Node.ts @@ -247,25 +247,25 @@ namespace Node { }; } - export interface FromTemplate extends Base { - type: 'from-template'; + export interface FromJBCTemplate extends Base { + type: 'from-jbc-template'; parentId?: string; templateId: string; } - export namespace FromTemplate { - export const NIL: FromTemplate = { - type: 'from-template', + export namespace FromJBCTemplate { + export const NIL: FromJBCTemplate = { + type: 'from-jbc-template', ...Base.NIL, templateId: '', }; - export const from = (t: T): FromTemplate => ({ + export const from = (t: T): FromJBCTemplate => ({ ...NIL, ...Base.upcast(t) }); - export const diff = (prev: FromTemplate, next: FromTemplate): Patch => { + export const diff = (prev: FromJBCTemplate, next: FromJBCTemplate): Patch => { if (!deepNeq(prev, next)) return Patch.none(prev); return Patch.innerChange(prev, next, { @@ -277,6 +277,73 @@ namespace Node { }; } + export interface FromRockTemplate extends Base { + type: 'from-rock-template'; + parentId?: string; + material?: Material; + templateId: string; + } + + export namespace FromRockTemplate { + export const NIL: FromRockTemplate = { + type: 'from-rock-template', + ...Base.NIL, + templateId: '', + }; + + export const from = (t: T): FromRockTemplate => ({ + ...NIL, + ...Base.upcast(t) + }); + + export const diff = (prev: FromRockTemplate, next: FromRockTemplate): Patch => { + if (!deepNeq(prev, next)) return Patch.none(prev); + + return Patch.innerChange(prev, next, { + type: Patch.none(prev.type), + parentId: Patch.diff(prev.parentId, next.parentId), + templateId: Patch.diff(prev.templateId, next.templateId), + ...Base.partialDiff(prev, next), + }); + }; + } + + export interface FromSpaceTemplate extends Base { + type: 'from-space-template'; + templateId: string; + parentId?: string; + physics?: Physics; + material?: Material; + geometryId?: string; + } + + export namespace FromSpaceTemplate { + export const NIL: FromSpaceTemplate = { + type: 'from-space-template', + ...Base.NIL, + templateId: '', + }; + + export const from = (t: T): FromSpaceTemplate => ({ + ...NIL, + ...Base.upcast(t) + }); + + export const diff = (prev: FromSpaceTemplate, next: FromSpaceTemplate): Patch => { + if (!deepNeq(prev, next)) return Patch.none(prev); + + return Patch.innerChange(prev, next, { + type: Patch.none(prev.type), + parentId: Patch.diff(prev.parentId, next.parentId), + templateId: Patch.diff(prev.templateId, next.templateId), + physics: Patch.diff(prev.physics, next.physics), + material: Material.diff(prev.material, next.material), + geometryId: Patch.diff(prev.geometryId, next.geometryId), + ...Base.partialDiff(prev, next), + }); + }; + } + export interface Robot extends Base { type: 'robot'; robotId: string; @@ -317,12 +384,14 @@ namespace Node { case 'point-light': return PointLight.diff(prev, next as PointLight); case 'spot-light': return SpotLight.diff(prev, next as SpotLight); case 'directional-light': return DirectionalLight.diff(prev, next as DirectionalLight); - case 'from-template': return FromTemplate.diff(prev, next as FromTemplate); + case 'from-jbc-template': return FromJBCTemplate.diff(prev, next as FromJBCTemplate); + case 'from-rock-template': return FromRockTemplate.diff(prev, next as FromRockTemplate); + case 'from-space-template': return FromSpaceTemplate.diff(prev, next as FromSpaceTemplate); case 'robot': return Robot.diff(prev, next as Robot); } }; - export type Type = 'empty' | 'object' | 'point-light' | 'spot-light' | 'directional-light' | 'from-template' | 'robot'; + export type Type = 'empty' | 'object' | 'point-light' | 'spot-light' | 'directional-light' | 'from-jbc-template' | 'from-space-template' | 'from-rock-template' | 'robot' | 'all'; export const transmute = (node: Node, type: Type): Node => { switch (type) { @@ -331,7 +400,9 @@ namespace Node { case 'point-light': return PointLight.from(node); case 'spot-light': return SpotLight.from(node); case 'directional-light': return DirectionalLight.from(node); - case 'from-template': return FromTemplate.from(node); + case 'from-jbc-template': return FromJBCTemplate.from(node); + case 'from-rock-template': return FromRockTemplate.from(node); + case 'from-space-template': return FromSpaceTemplate.from(node); case 'robot': return Robot.from(node); } }; @@ -345,7 +416,9 @@ type Node = ( Node.PointLight | Node.SpotLight | Node.DirectionalLight | - Node.FromTemplate | + Node.FromJBCTemplate | + Node.FromRockTemplate | + Node.FromSpaceTemplate | Node.Robot ); diff --git a/src/state/reducer/scenes.ts b/src/state/reducer/scenes.ts index 228c9433..14724209 100644 --- a/src/state/reducer/scenes.ts +++ b/src/state/reducer/scenes.ts @@ -283,7 +283,9 @@ export type ScenesAction = ( ScenesAction.SetScript ); + const DEFAULT_SCENES: Scenes = { + moonSandbox: Async.loaded({ value: JBC_SCENES.Moon_Sandbox }), jbcSandboxA: Async.loaded({ value: JBC_SCENES.JBC_Sandbox_A }), jbcSandboxB: Async.loaded({ value: JBC_SCENES.JBC_Sandbox_B }), jbc1: Async.loaded({ value: JBC_SCENES.JBC_1 }), diff --git a/src/types/globals.d.ts b/src/types/globals.d.ts index 17e5227d..802c4d91 100644 --- a/src/types/globals.d.ts +++ b/src/types/globals.d.ts @@ -2,6 +2,5 @@ declare const SIMULATOR_VERSION: string; declare const SIMULATOR_GIT_HASH: string; declare const SIMULATOR_HAS_CPYTHON: boolean; -declare const SIMULATOR_HAS_AMMO: boolean; declare const SIMULATOR_LIBKIPR_C_DOCUMENTATION: unknown | undefined; declare const SIMULATOR_I18N: unknown | undefined; diff --git a/static/arm.blend b/static/arm.blend deleted file mode 100644 index 9953591b..00000000 --- a/static/arm.blend +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:680bcd7b444589d2bd61207c2dac4b08d3b65b132a40ea40f151e8ac0741e1a4 -size 14387852 diff --git a/static/arm.blend1 b/static/arm.blend1 deleted file mode 100644 index c780c312..00000000 Binary files a/static/arm.blend1 and /dev/null differ diff --git a/static/Triangular_Background_Compressed.png b/static/backgrounds/Triangular_Background_Compressed.png similarity index 100% rename from static/Triangular_Background_Compressed.png rename to static/backgrounds/Triangular_Background_Compressed.png diff --git a/static/gray-hex-background.png b/static/backgrounds/gray-hex-background.png similarity index 100% rename from static/gray-hex-background.png rename to static/backgrounds/gray-hex-background.png diff --git a/static/red-geometric.png b/static/backgrounds/red-geometric.png similarity index 100% rename from static/red-geometric.png rename to static/backgrounds/red-geometric.png diff --git a/static/cayley_interior_1k.hdr b/static/cayley_interior_1k.hdr deleted file mode 100644 index 58059201..00000000 Binary files a/static/cayley_interior_1k.hdr and /dev/null differ diff --git a/static/chassis.blend b/static/chassis.blend deleted file mode 100644 index fcc78e30..00000000 --- a/static/chassis.blend +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e1783b7b501ae07cc19956a95d2b4115feb7a2c28a4bf1fa86b8740104727bd3 -size 42695836 diff --git a/static/chassis.blend1 b/static/chassis.blend1 deleted file mode 100644 index db8884d5..00000000 Binary files a/static/chassis.blend1 and /dev/null differ diff --git a/static/chassis.glb b/static/chassis.glb deleted file mode 100644 index 01d9da40..00000000 --- a/static/chassis.glb +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5207a45422a7f4d7ad591a2b5d32236de89afc3c7240b18e8f9fea0a18919d2e -size 6313900 diff --git a/static/claw.blend b/static/claw.blend deleted file mode 100644 index e53a4c12..00000000 --- a/static/claw.blend +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:75f228951cc3926a1cfa8f2cdfcc4fc280e001a407ab55938d981d1fe04a3784 -size 9057012 diff --git a/static/Can_Ream.png b/static/example_images/Can_Ream.png similarity index 100% rename from static/Can_Ream.png rename to static/example_images/Can_Ream.png diff --git a/static/Simulator-Robot-Closeup.png b/static/example_images/Simulator-Robot-Closeup.png similarity index 100% rename from static/Simulator-Robot-Closeup.png rename to static/example_images/Simulator-Robot-Closeup.png diff --git a/static/Simulator_Full_View.png b/static/example_images/Simulator_Full_View.png similarity index 100% rename from static/Simulator_Full_View.png rename to static/example_images/Simulator_Full_View.png diff --git a/static/Botguy-Picture-Small.png b/static/icons/Botguy-Picture-Small.png similarity index 100% rename from static/Botguy-Picture-Small.png rename to static/icons/Botguy-Picture-Small.png diff --git a/static/KIPR-Logo-bk.jpg b/static/icons/KIPR-Logo-bk.jpg similarity index 100% rename from static/KIPR-Logo-bk.jpg rename to static/icons/KIPR-Logo-bk.jpg diff --git a/static/Laptop_Icon_Sunscreen.png b/static/icons/Laptop_Icon_Sunscreen.png similarity index 100% rename from static/Laptop_Icon_Sunscreen.png rename to static/icons/Laptop_Icon_Sunscreen.png diff --git a/static/apple-touch-icon.png b/static/icons/apple-touch-icon.png similarity index 100% rename from static/apple-touch-icon.png rename to static/icons/apple-touch-icon.png diff --git a/static/favicon-16x16.png b/static/icons/favicon-16x16.png similarity index 100% rename from static/favicon-16x16.png rename to static/icons/favicon-16x16.png diff --git a/static/favicon-32x32.png b/static/icons/favicon-32x32.png similarity index 100% rename from static/favicon-32x32.png rename to static/icons/favicon-32x32.png diff --git a/static/google_signin_dark_normal.png b/static/icons/google_signin_dark_normal.png similarity index 100% rename from static/google_signin_dark_normal.png rename to static/icons/google_signin_dark_normal.png diff --git a/static/arena.glb b/static/object_binaries/arena.glb similarity index 100% rename from static/arena.glb rename to static/object_binaries/arena.glb diff --git a/static/arm.glb b/static/object_binaries/arm.glb similarity index 100% rename from static/arm.glb rename to static/object_binaries/arm.glb diff --git a/static/object_binaries/basic_hab.glb b/static/object_binaries/basic_hab.glb new file mode 100755 index 00000000..24d65f88 --- /dev/null +++ b/static/object_binaries/basic_hab.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:56799454b2269dcca971a35c71698113e2e79976df4d3fd588e413ba80765294 +size 68948 diff --git a/static/object_binaries/basic_solar_panel.glb b/static/object_binaries/basic_solar_panel.glb new file mode 100644 index 00000000..4f541442 --- /dev/null +++ b/static/object_binaries/basic_solar_panel.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:653885018f1f19bb5c1a3103d21f4e0d647c8d18aa84b0f07d888890303da8a1 +size 166424 diff --git a/static/object_binaries/basic_walkway.glb b/static/object_binaries/basic_walkway.glb new file mode 100755 index 00000000..b7a9c284 --- /dev/null +++ b/static/object_binaries/basic_walkway.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7e2d4487703cb5c56369457b31f11ef0cde872f9f1e1a916a1e140f6a15b1bc8 +size 65004 diff --git a/static/object_binaries/botguy.glb b/static/object_binaries/botguy.glb new file mode 100644 index 00000000..a07689dd --- /dev/null +++ b/static/object_binaries/botguy.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:74af0b5a0216e0f4f288713d58f2e7ba148c7e04792fa4917317427ebbb3e5d9 +size 89364 diff --git a/static/object_binaries/chassis.glb b/static/object_binaries/chassis.glb new file mode 100644 index 00000000..9a27533e --- /dev/null +++ b/static/object_binaries/chassis.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5828874b778bd827d38e9ec89cc79efcbc31ef441d6340d60326509a4a0d82da +size 15930428 diff --git a/static/claw.glb b/static/object_binaries/claw.glb similarity index 100% rename from static/claw.glb rename to static/object_binaries/claw.glb diff --git a/static/object_binaries/com_hab.glb b/static/object_binaries/com_hab.glb new file mode 100755 index 00000000..76af03d0 --- /dev/null +++ b/static/object_binaries/com_hab.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6510248ed5adf9c6513f9b557646a8b9328df4961006f982874ffa7d84701e5b +size 456632 diff --git a/static/object_binaries/comm_dish.glb b/static/object_binaries/comm_dish.glb new file mode 100644 index 00000000..a5a3601b --- /dev/null +++ b/static/object_binaries/comm_dish.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a27de3606b7b9306156befd1897d342dd580cfe90e36c8c8dd265d1ea9182e43 +size 214620 diff --git a/static/object_binaries/comm_tower.glb b/static/object_binaries/comm_tower.glb new file mode 100644 index 00000000..bbf9ba9d --- /dev/null +++ b/static/object_binaries/comm_tower.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:64e2b0b52f3a23db62fcbb40655e7a34ff46df64a2c1a3fad2667ff7be6a4f12 +size 70376 diff --git a/static/object_binaries/container_with_lid.glb b/static/object_binaries/container_with_lid.glb new file mode 100644 index 00000000..bc754665 --- /dev/null +++ b/static/object_binaries/container_with_lid.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6a977e2f268040eb1653c5f80d7ee8f14027c4298acfa39b6fee3a7af07f5d3f +size 4536 diff --git a/static/demobot_v6.glb b/static/object_binaries/demobot_v6.glb similarity index 100% rename from static/demobot_v6.glb rename to static/object_binaries/demobot_v6.glb diff --git a/static/object_binaries/greenhouse_hab.glb b/static/object_binaries/greenhouse_hab.glb new file mode 100755 index 00000000..2f08753e --- /dev/null +++ b/static/object_binaries/greenhouse_hab.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:830c7eb2dea10bd00a1f879922e14eb19e45d7b1708e98c230b1bf4ea3114ee8 +size 69872 diff --git a/static/jbcMatA.glb b/static/object_binaries/jbcMatA.glb similarity index 100% rename from static/jbcMatA.glb rename to static/object_binaries/jbcMatA.glb diff --git a/static/jbcMatB.glb b/static/object_binaries/jbcMatB.glb similarity index 100% rename from static/jbcMatB.glb rename to static/object_binaries/jbcMatB.glb diff --git a/static/object_binaries/manipulator_hab.glb b/static/object_binaries/manipulator_hab.glb new file mode 100755 index 00000000..818013ba --- /dev/null +++ b/static/object_binaries/manipulator_hab.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e3cd5e76d370522cf2cba240d22c3ba9890df982cd992af94ebd93818b21b8d2 +size 457072 diff --git a/static/object_binaries/ogBotguy2.glb b/static/object_binaries/ogBotguy2.glb new file mode 100644 index 00000000..368312f6 --- /dev/null +++ b/static/object_binaries/ogBotguy2.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c45e34993d2b6335ac0c04eab4dd4fcfc28d17bc8e93456c1eea7517a2126333 +size 3796672 diff --git a/static/object_binaries/solar_panel2.glb b/static/object_binaries/solar_panel2.glb new file mode 100644 index 00000000..6eaf44f7 --- /dev/null +++ b/static/object_binaries/solar_panel2.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:aad47fe7de4ce9caee4948c8c98a7d3d1665636aa1e278da1dae883c780636ff +size 161488 diff --git a/static/suzanne.glb b/static/object_binaries/suzanne.glb similarity index 100% rename from static/suzanne.glb rename to static/object_binaries/suzanne.glb diff --git a/static/table.glb b/static/object_binaries/table.glb similarity index 100% rename from static/table.glb rename to static/object_binaries/table.glb diff --git a/static/wheel.glb b/static/object_binaries/wheel.glb similarity index 100% rename from static/wheel.glb rename to static/object_binaries/wheel.glb diff --git a/static/Can Texture.png b/static/textures/Can_Texture.png similarity index 100% rename from static/Can Texture.png rename to static/textures/Can_Texture.png diff --git a/static/textures/KIPR_Surface_A.png b/static/textures/KIPR_Surface_A.png new file mode 100644 index 00000000..46b2d18e Binary files /dev/null and b/static/textures/KIPR_Surface_A.png differ diff --git a/static/textures/KIPR_Surface_B.png b/static/textures/KIPR_Surface_B.png new file mode 100644 index 00000000..73d89628 Binary files /dev/null and b/static/textures/KIPR_Surface_B.png differ diff --git a/static/textures/Moon-2d-Surface.png b/static/textures/Moon-2d-Surface.png new file mode 100644 index 00000000..4cd8ab74 Binary files /dev/null and b/static/textures/Moon-2d-Surface.png differ diff --git a/static/textures/NightSkybox.png b/static/textures/NightSkybox.png new file mode 100644 index 00000000..05bc2712 Binary files /dev/null and b/static/textures/NightSkybox.png differ diff --git a/static/textures/earthrise.png b/static/textures/earthrise.png new file mode 100644 index 00000000..36075030 Binary files /dev/null and b/static/textures/earthrise.png differ diff --git a/static/textures/hab5.png b/static/textures/hab5.png new file mode 100644 index 00000000..efc2b9bd Binary files /dev/null and b/static/textures/hab5.png differ diff --git a/static/Surface-A.png b/static/textures/jbcmat/Surface-A.png similarity index 100% rename from static/Surface-A.png rename to static/textures/jbcmat/Surface-A.png diff --git a/static/Surface-B.png b/static/textures/jbcmat/Surface-B.png similarity index 100% rename from static/Surface-B.png rename to static/textures/jbcmat/Surface-B.png diff --git a/static/textures/rocks/anorthosite_texture.png b/static/textures/rocks/anorthosite_texture.png new file mode 100644 index 00000000..b40ab4fa Binary files /dev/null and b/static/textures/rocks/anorthosite_texture.png differ diff --git a/static/textures/rocks/anorthosite_texture_m.png b/static/textures/rocks/anorthosite_texture_m.png new file mode 100644 index 00000000..6c90509d Binary files /dev/null and b/static/textures/rocks/anorthosite_texture_m.png differ diff --git a/static/textures/rocks/anorthosite_texture_w.png b/static/textures/rocks/anorthosite_texture_w.png new file mode 100644 index 00000000..d1da449f Binary files /dev/null and b/static/textures/rocks/anorthosite_texture_w.png differ diff --git a/static/textures/rocks/anorthosite_texture_wn.png b/static/textures/rocks/anorthosite_texture_wn.png new file mode 100644 index 00000000..67df2012 Binary files /dev/null and b/static/textures/rocks/anorthosite_texture_wn.png differ diff --git a/static/textures/rocks/basalt_texture.png b/static/textures/rocks/basalt_texture.png new file mode 100644 index 00000000..687422b5 Binary files /dev/null and b/static/textures/rocks/basalt_texture.png differ diff --git a/static/textures/rocks/basalt_texture2.png b/static/textures/rocks/basalt_texture2.png new file mode 100644 index 00000000..128a460b Binary files /dev/null and b/static/textures/rocks/basalt_texture2.png differ diff --git a/static/textures/rocks/basalt_texture_m.png b/static/textures/rocks/basalt_texture_m.png new file mode 100644 index 00000000..a38b445c Binary files /dev/null and b/static/textures/rocks/basalt_texture_m.png differ diff --git a/static/textures/rocks/basalt_texture_w.png b/static/textures/rocks/basalt_texture_w.png new file mode 100644 index 00000000..b1ee123d Binary files /dev/null and b/static/textures/rocks/basalt_texture_w.png differ diff --git a/static/textures/rocks/basalt_texture_wn.png b/static/textures/rocks/basalt_texture_wn.png new file mode 100644 index 00000000..6d710760 Binary files /dev/null and b/static/textures/rocks/basalt_texture_wn.png differ diff --git a/static/textures/rocks/breccia_texture.png b/static/textures/rocks/breccia_texture.png new file mode 100644 index 00000000..8391a961 Binary files /dev/null and b/static/textures/rocks/breccia_texture.png differ diff --git a/static/textures/rocks/breccia_texture_m.png b/static/textures/rocks/breccia_texture_m.png new file mode 100644 index 00000000..74c77e18 Binary files /dev/null and b/static/textures/rocks/breccia_texture_m.png differ diff --git a/static/textures/rocks/breccia_texture_w.png b/static/textures/rocks/breccia_texture_w.png new file mode 100644 index 00000000..3a6c86b4 Binary files /dev/null and b/static/textures/rocks/breccia_texture_w.png differ diff --git a/static/textures/rocks/breccia_texture_wn.png b/static/textures/rocks/breccia_texture_wn.png new file mode 100644 index 00000000..0e326ff2 Binary files /dev/null and b/static/textures/rocks/breccia_texture_wn.png differ diff --git a/static/textures/rocks/meteorite_texture.png b/static/textures/rocks/meteorite_texture.png new file mode 100644 index 00000000..33b5be37 Binary files /dev/null and b/static/textures/rocks/meteorite_texture.png differ diff --git a/static/textures/rocks/meteorite_texture_m.png b/static/textures/rocks/meteorite_texture_m.png new file mode 100644 index 00000000..62e12a31 Binary files /dev/null and b/static/textures/rocks/meteorite_texture_m.png differ diff --git a/static/textures/rocks/meteorite_texture_w.png b/static/textures/rocks/meteorite_texture_w.png new file mode 100644 index 00000000..63f2484c Binary files /dev/null and b/static/textures/rocks/meteorite_texture_w.png differ diff --git a/static/textures/rocks/meteorite_texture_wn.png b/static/textures/rocks/meteorite_texture_wn.png new file mode 100644 index 00000000..3749c923 Binary files /dev/null and b/static/textures/rocks/meteorite_texture_wn.png differ diff --git a/static/textures/science_pad.png b/static/textures/science_pad.png new file mode 100644 index 00000000..cd69b8c9 Binary files /dev/null and b/static/textures/science_pad.png differ diff --git a/static/textures/science_pad2.png b/static/textures/science_pad2.png new file mode 100644 index 00000000..2d66de00 Binary files /dev/null and b/static/textures/science_pad2.png differ diff --git a/static/textures/sciencepack/life_science_pack.png b/static/textures/sciencepack/life_science_pack.png new file mode 100644 index 00000000..5ec767fb Binary files /dev/null and b/static/textures/sciencepack/life_science_pack.png differ diff --git a/static/textures/sciencepack/no_rad_science_pack.png b/static/textures/sciencepack/no_rad_science_pack.png new file mode 100644 index 00000000..8d42f344 Binary files /dev/null and b/static/textures/sciencepack/no_rad_science_pack.png differ diff --git a/static/textures/sciencepack/rad_science_pack.png b/static/textures/sciencepack/rad_science_pack.png new file mode 100644 index 00000000..df89f8f7 Binary files /dev/null and b/static/textures/sciencepack/rad_science_pack.png differ diff --git a/static/textures/solar.png b/static/textures/solar.png new file mode 100644 index 00000000..ac598eb1 Binary files /dev/null and b/static/textures/solar.png differ diff --git a/static/textures/start_texture_dark.png b/static/textures/start_texture_dark.png new file mode 100644 index 00000000..dd8ed5f7 Binary files /dev/null and b/static/textures/start_texture_dark.png differ diff --git a/static/textures/start_texture_light.png b/static/textures/start_texture_light.png new file mode 100644 index 00000000..e3a69fa9 Binary files /dev/null and b/static/textures/start_texture_light.png differ diff --git a/static/textures/tower/tall_tower_texture.png b/static/textures/tower/tall_tower_texture.png new file mode 100644 index 00000000..9b69aa54 Binary files /dev/null and b/static/textures/tower/tall_tower_texture.png differ diff --git a/static/textures/tower/tower_texture.png b/static/textures/tower/tower_texture.png new file mode 100644 index 00000000..acf542c5 Binary files /dev/null and b/static/textures/tower/tower_texture.png differ diff --git a/test/util/Patch.spec.ts b/test/util/Patch.spec.ts index 826d9402..3123eba1 100644 --- a/test/util/Patch.spec.ts +++ b/test/util/Patch.spec.ts @@ -43,7 +43,7 @@ const SCENE_B: Scene = { name: { [LocalizedString.EN_US]: 'Node 0' }, }, '1': { - type: 'from-template', + type: 'from-jbc-template', name: { [LocalizedString.EN_US]: 'Node 1' }, templateId: 'template', }, diff --git a/yarn.lock b/yarn.lock index 2dc3720d..181214c8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -526,15 +526,22 @@ "@babel/helper-validator-identifier" "^7.14.5" to-fast-properties "^2.0.0" -"@babylonjs/core@^5.22.1": - version "5.48.0" - resolved "https://registry.yarnpkg.com/@babylonjs/core/-/core-5.48.0.tgz#79e7b7595620ffde86e0d064ccbc1ca50d93f656" - integrity sha512-97d+stKdMGGXmmCuYEo9xhz0v0q5DbWSg2GI6mfa+P1qH9YPmWN+YNxwBs0A2H3HrhBrC/PQOTHUibfA867CoQ== +"@babylonjs/core@^6.14.0": + version "6.14.0" + resolved "https://registry.yarnpkg.com/@babylonjs/core/-/core-6.14.0.tgz#da90c52f75d4235b8a202e7f5852148aae09070c" + integrity sha512-ciIfWMMtV5jsnqxqTn+v/CS65yji6CXTP2drmvLlzk+k+IZjE8RfkpMqZgZozN/KNkOmIVn2Li7qRMjg4ZUGlw== -"@babylonjs/loaders@^5.22.1": - version "5.48.0" - resolved "https://registry.yarnpkg.com/@babylonjs/loaders/-/loaders-5.48.0.tgz#a16f69d03770fa8132b5df1dea51667072ad5e0e" - integrity sha512-BbZoceGDsUYis86OeAkFq9CQN1yJ59IOL0O0AtogOt8KQ5E8b0sH4MVdOwuV6h07yLVjo9Ir5RZsWKdGyuWHOw== +"@babylonjs/havok@^1.0.0": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@babylonjs/havok/-/havok-1.1.1.tgz#9b446105c2626826a3ee671dd7da442d6755d186" + integrity sha512-QIygEnKPYYPCE8QuJDxDLYPMj+W5Yha6NOdUfpvcVfZwtjVDE+eOa2Y5el7muQD563OdeJS+Kzmhyg+5qAZHBA== + dependencies: + "@types/emscripten" "^1.39.6" + +"@babylonjs/loaders@^6.14.0": + version "6.14.0" + resolved "https://registry.yarnpkg.com/@babylonjs/loaders/-/loaders-6.14.0.tgz#5e861748522dbf8da8b0b85140f64a04aa5c86ab" + integrity sha512-rGDMuyHEphpOHBO4JtY0vNIri2VMAmwUw3UmDJ+C00erQpiQDXyzG3IkJpXDHt7mWOJZNASRJjC76bPDu9zarg== "@bcoe/v8-coverage@^0.2.3": version "0.2.3" @@ -1443,6 +1450,11 @@ dependencies: "@babel/types" "^7.3.0" +"@types/emscripten@^1.39.6": + version "1.39.7" + resolved "https://registry.yarnpkg.com/@types/emscripten/-/emscripten-1.39.7.tgz#3025183ea56e12bf4d096aadc48ce74ca051233d" + integrity sha512-tLqYV94vuqDrXh515F/FOGtBcRMTPGvVV1LzLbtYDcQmmhtpf/gLYf+hikBbQk8MzOHNz37wpFfJbYAuSn8HqA== + "@types/eslint-scope@^3.7.0": version "3.7.1" resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.1.tgz#8dc390a7b4f9dd9f1284629efce982e41612116e" @@ -2271,10 +2283,10 @@ babel-preset-jest@^29.4.3: babel-plugin-jest-hoist "^29.4.3" babel-preset-current-node-syntax "^1.0.0" -babylonjs-gltf2interface@^5.22.0: - version "5.48.1" - resolved "https://registry.yarnpkg.com/babylonjs-gltf2interface/-/babylonjs-gltf2interface-5.48.1.tgz#8d649a46b667b6a7218a319f9b517a34b5112095" - integrity sha512-PfTbjyrPuosI0//ROGe3Yz4aHK+Ar7RFF8BD1+tlelV2j/mW/XPmkANG+AxchbL59dEB46hz6r8k15Aw6Ufy/A== +babylonjs-gltf2interface@^6.14.0: + version "6.14.0" + resolved "https://registry.yarnpkg.com/babylonjs-gltf2interface/-/babylonjs-gltf2interface-6.14.0.tgz#2a1c80e40090d2deccae8c5a49509ededf098e98" + integrity sha512-fCv0Mc2zGblSlY7DzLPEfYDbcd1h1+wJJmF1MskMl4H30PBPl5EslYeHaHyjjRvwcOLlq4Dd+SS0in8B1nKFuA== balanced-match@^1.0.0: version "1.0.2" @@ -2621,15 +2633,10 @@ camelcase@^6.2.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== -caniuse-lite@^1.0.30001219: - version "1.0.30001243" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001243.tgz#d9250155c91e872186671c523f3ae50cfc94a3aa" - integrity sha512-vNxw9mkTBtkmLFnJRv/2rhs1yufpDfCkBZexG3Y0xdOH2Z/eE/85E4Dl5j1YUN34nZVsSp6vVRFQRrez9wJMRA== - -caniuse-lite@^1.0.30001449: - version "1.0.30001458" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001458.tgz#871e35866b4654a7d25eccca86864f411825540c" - integrity sha512-lQ1VlUUq5q9ro9X+5gOEyH7i3vm+AYVT1WDCVB69XOZ17KZRhnZ9J0Sqz7wTHQaLBJccNCHq8/Ww5LlOIZbB0w== +caniuse-lite@^1.0.30001219, caniuse-lite@^1.0.30001449: + version "1.0.30001505" + resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001505.tgz" + integrity sha512-jaAOR5zVtxHfL0NjZyflVTtXm3D3J9P15zSJ7HmQF8dSKGA6tqzQq+0ZI3xkjyQj46I4/M0K2GbMpcAFOcbr3A== caw@^2.0.0, caw@^2.0.1: version "2.0.1"