From 78c6f8ab256850c78f9ef65d3a73747a292a5bf8 Mon Sep 17 00:00:00 2001 From: Ryan Zhang Date: Tue, 15 Jul 2025 14:10:52 -0700 Subject: [PATCH 01/10] Add animation duration slider --- .../ConfigureGamepiecePickupInterface.tsx | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/fission/src/ui/panels/configuring/assembly-config/interfaces/ConfigureGamepiecePickupInterface.tsx b/fission/src/ui/panels/configuring/assembly-config/interfaces/ConfigureGamepiecePickupInterface.tsx index 6df4278f78..91d722ce25 100644 --- a/fission/src/ui/panels/configuring/assembly-config/interfaces/ConfigureGamepiecePickupInterface.tsx +++ b/fission/src/ui/panels/configuring/assembly-config/interfaces/ConfigureGamepiecePickupInterface.tsx @@ -23,10 +23,14 @@ import { PAUSE_REF_ASSEMBLY_CONFIG } from "@/systems/physics/PhysicsSystem" import { Box } from "@mui/material" import { Switch } from "@mui/base/Switch" import Label, { LabelSize } from "@/ui/components/Label" +import EjectableSceneObject from "@/mirabuf/EjectableSceneObject" // slider constants const MIN_ZONE_SIZE = 0.1 const MAX_ZONE_SIZE = 1.0 +const MIN_ANIMATION_DURATION = 0.1 +const MAX_ANIMATION_DURATION = 2.0 +const ANIMATION_DURATION_STEP = 0.05 /** * Saves ejector configuration to selected robot. @@ -106,6 +110,7 @@ const ConfigureGamepiecePickupInterface: React.FC = ({ select const [zoneSize, setZoneSize] = useState((MIN_ZONE_SIZE + MAX_ZONE_SIZE) / 2.0) const [showZoneAlways, setShowZoneAlways] = useState(false) const [maxPieces, setMaxPieces] = useState(selectedRobot.intakePreferences?.maxPieces || 1) + const [animationDuration, setAnimationDuration] = useState(EjectableSceneObject.getAnimationDuration()) const gizmoRef = useRef(undefined) @@ -248,12 +253,22 @@ const ConfigureGamepiecePickupInterface: React.FC = ({ select min={MIN_ZONE_SIZE} max={MAX_ZONE_SIZE} value={zoneSize} - label="Zone Size" - format={{ minimumFractionDigits: 2, maximumFractionDigits: 2 }} - onChange={(_, vel: number | number[]) => { - setZoneSize(vel as number) - }} + onChange={(_, v) => setZoneSize(typeof v === "number" ? v : v[0])} step={0.01} + label="Intake Zone Diameter (m)" + /> + { + const val = typeof v === "number" ? v : v[0] + setAnimationDuration(val) + EjectableSceneObject.setAnimationDuration(val) + }} + step={ANIMATION_DURATION_STEP} + label="Intake Animation Duration (s)" + format={{ maximumFractionDigits: 2 }} /> {/* Slider for adjusting max pieces the robot can intake */} From bc8a88e619b655b08991a8e390f88f06bb13c121 Mon Sep 17 00:00:00 2001 From: Ryan Zhang Date: Tue, 15 Jul 2025 14:11:04 -0700 Subject: [PATCH 02/10] Add lerping logic --- fission/src/mirabuf/EjectableSceneObject.ts | 76 +++++++++++++++++++-- 1 file changed, 69 insertions(+), 7 deletions(-) diff --git a/fission/src/mirabuf/EjectableSceneObject.ts b/fission/src/mirabuf/EjectableSceneObject.ts index 6c7dfe10a8..20797b7edf 100644 --- a/fission/src/mirabuf/EjectableSceneObject.ts +++ b/fission/src/mirabuf/EjectableSceneObject.ts @@ -9,6 +9,7 @@ import { convertThreeQuaternionToJoltQuat, convertThreeVector3ToJoltRVec3, convertThreeVector3ToJoltVec3, + convertJoltVec3ToThreeVector3, } from "@/util/TypeConversions" import * as THREE from "three" import ScoringZoneSceneObject from "./ScoringZoneSceneObject" @@ -21,6 +22,24 @@ class EjectableSceneObject extends SceneObject { private _deltaTransformation?: THREE.Matrix4 private _ejectVelocity?: number + // Animation state + private _isAnimating = false + private _animationStartTime = 0 + private _animationDuration = EjectableSceneObject._defaultAnimationDuration + private _startPosition?: THREE.Vector3 + private _endPosition?: THREE.Vector3 + private _startQuaternion?: THREE.Quaternion + private _endQuaternion?: THREE.Quaternion + + private static _defaultAnimationDuration = 0.5 + + public static setAnimationDuration(duration: number) { + EjectableSceneObject._defaultAnimationDuration = duration + } + public static getAnimationDuration() { + return EjectableSceneObject._defaultAnimationDuration + } + public get gamePieceBodyId() { return this._gamePieceBodyId } @@ -49,15 +68,40 @@ class EjectableSceneObject extends SceneObject { ) this._ejectVelocity = this._parentAssembly.ejectorPreferences.ejectorVelocity + // Animation start at game piece + const gpBody = World.physicsSystem.getBody(this._gamePieceBodyId) + this._startPosition = convertJoltVec3ToThreeVector3(gpBody.GetPosition()) + this._startQuaternion = convertJoltQuatToThreeQuaternion(gpBody.GetRotation()) + + // Compute the ejectable position/rotation + if (this._parentBodyId && this._deltaTransformation) { + const posToCOM = convertJoltMat44ToThreeMatrix4(gpBody.GetCenterOfMassTransform()).premultiply( + convertJoltMat44ToThreeMatrix4(gpBody.GetWorldTransform()).invert() + ) + const parentBody = World.physicsSystem.getBody(this._parentBodyId) + const bodyTransform = posToCOM + .invert() + .premultiply( + this._deltaTransformation + .clone() + .premultiply(convertJoltMat44ToThreeMatrix4(parentBody.GetWorldTransform())) + ) + const endPos = new THREE.Vector3() + const endQuat = new THREE.Quaternion() + bodyTransform.decompose(endPos, endQuat, new THREE.Vector3(1, 1, 1)) + this._endPosition = endPos + this._endQuaternion = endQuat + } + + this._animationDuration = EjectableSceneObject._defaultAnimationDuration + this._isAnimating = true + this._animationStartTime = performance.now() + World.physicsSystem.disablePhysicsForBody(this._gamePieceBodyId) - // Checks if the gamepiece comes from a zone for persistent point score updates - // because gamepieces removed by intake are not detected in the collision listener + // Remove from scoring zones const zones = [...World.sceneRenderer.sceneObjects.entries()] - .filter(x => { - const y = x[1] instanceof ScoringZoneSceneObject - return y - }) + .filter(x => x[1] instanceof ScoringZoneSceneObject) .map(x => x[1]) as ScoringZoneSceneObject[] zones.forEach(x => { @@ -69,13 +113,31 @@ class EjectableSceneObject extends SceneObject { } public update(): void { + // Animation logic: lerp from start to held position + if (this._isAnimating && this._gamePieceBodyId && this._startPosition && this._endPosition && this._startQuaternion && this._endQuaternion) { + const now = performance.now() + const elapsed = (now - this._animationStartTime) / 1000 + const t = Math.min(elapsed / this._animationDuration, 1) + + const pos = new THREE.Vector3().lerpVectors(this._startPosition, this._endPosition, t) + const quat = new THREE.Quaternion().copy(this._startQuaternion).slerp(this._endQuaternion, t) + + World.physicsSystem.setBodyPosition(this._gamePieceBodyId, convertThreeVector3ToJoltRVec3(pos), false) + World.physicsSystem.setBodyRotation(this._gamePieceBodyId, convertThreeQuaternionToJoltQuat(quat), false) + + if (t >= 1) { + this._isAnimating = false + } + return + } + + // After animation, keep gamepiece at ejectable position if (this._parentBodyId && this._deltaTransformation && this._gamePieceBodyId) { if (!World.physicsSystem.isBodyAdded(this._gamePieceBodyId)) { this._gamePieceBodyId = undefined return } - // I had a think and free wrote this matrix math on a whim. It worked first try and I honestly can't quite remember how it works... -Hunter const gpBody = World.physicsSystem.getBody(this._gamePieceBodyId) const posToCOM = convertJoltMat44ToThreeMatrix4(gpBody.GetCenterOfMassTransform()).premultiply( convertJoltMat44ToThreeMatrix4(gpBody.GetWorldTransform()).invert() From c496556acfbdbbfac15128753f591c40f9604692 Mon Sep 17 00:00:00 2001 From: Ryan Zhang Date: Mon, 21 Jul 2025 08:19:48 -0700 Subject: [PATCH 03/10] Update interpolation to be separate --- fission/src/mirabuf/EjectableSceneObject.ts | 47 +++++++++++++++------ 1 file changed, 35 insertions(+), 12 deletions(-) diff --git a/fission/src/mirabuf/EjectableSceneObject.ts b/fission/src/mirabuf/EjectableSceneObject.ts index 20797b7edf..63c7a0b158 100644 --- a/fission/src/mirabuf/EjectableSceneObject.ts +++ b/fission/src/mirabuf/EjectableSceneObject.ts @@ -26,10 +26,8 @@ class EjectableSceneObject extends SceneObject { private _isAnimating = false private _animationStartTime = 0 private _animationDuration = EjectableSceneObject._defaultAnimationDuration - private _startPosition?: THREE.Vector3 - private _endPosition?: THREE.Vector3 - private _startQuaternion?: THREE.Quaternion - private _endQuaternion?: THREE.Quaternion + private _startMatrix?: THREE.Matrix4 + private _endMatrix?: THREE.Matrix4 private static _defaultAnimationDuration = 0.5 @@ -70,8 +68,6 @@ class EjectableSceneObject extends SceneObject { // Animation start at game piece const gpBody = World.physicsSystem.getBody(this._gamePieceBodyId) - this._startPosition = convertJoltVec3ToThreeVector3(gpBody.GetPosition()) - this._startQuaternion = convertJoltQuatToThreeQuaternion(gpBody.GetRotation()) // Compute the ejectable position/rotation if (this._parentBodyId && this._deltaTransformation) { @@ -86,11 +82,20 @@ class EjectableSceneObject extends SceneObject { .clone() .premultiply(convertJoltMat44ToThreeMatrix4(parentBody.GetWorldTransform())) ) + // Compose start and end matrices directly + this._startMatrix = new THREE.Matrix4().compose( + convertJoltVec3ToThreeVector3(gpBody.GetPosition()), + convertJoltQuatToThreeQuaternion(gpBody.GetRotation()), + new THREE.Vector3(1, 1, 1) + ) const endPos = new THREE.Vector3() const endQuat = new THREE.Quaternion() bodyTransform.decompose(endPos, endQuat, new THREE.Vector3(1, 1, 1)) - this._endPosition = endPos - this._endQuaternion = endQuat + this._endMatrix = new THREE.Matrix4().compose( + endPos, + endQuat, + new THREE.Vector3(1, 1, 1) + ) } this._animationDuration = EjectableSceneObject._defaultAnimationDuration @@ -113,15 +118,33 @@ class EjectableSceneObject extends SceneObject { } public update(): void { - // Animation logic: lerp from start to held position - if (this._isAnimating && this._gamePieceBodyId && this._startPosition && this._endPosition && this._startQuaternion && this._endQuaternion) { + // Animation logic: interpolate full transform (matrix) + if (this._isAnimating && this._gamePieceBodyId && this._startMatrix && this._endMatrix) { const now = performance.now() const elapsed = (now - this._animationStartTime) / 1000 const t = Math.min(elapsed / this._animationDuration, 1) - const pos = new THREE.Vector3().lerpVectors(this._startPosition, this._endPosition, t) - const quat = new THREE.Quaternion().copy(this._startQuaternion).slerp(this._endQuaternion, t) + // Decompose start and end + const startPos = new THREE.Vector3() + const startQuat = new THREE.Quaternion() + const startScale = new THREE.Vector3() + this._startMatrix.decompose(startPos, startQuat, startScale) + + const endPos = new THREE.Vector3() + const endQuat = new THREE.Quaternion() + const endScale = new THREE.Vector3() + this._endMatrix.decompose(endPos, endQuat, endScale) + + // Interpolate + const pos = new THREE.Vector3().lerpVectors(startPos, endPos, t) + const quat = new THREE.Quaternion().copy(startQuat).slerp(endQuat, t) + const scale = new THREE.Vector3().lerpVectors(startScale, endScale, t) + + // Compose interpolated matrix + const interpMatrix = new THREE.Matrix4().compose(pos, quat, scale) + // Decompose to set position/rotation + interpMatrix.decompose(pos, quat, scale) World.physicsSystem.setBodyPosition(this._gamePieceBodyId, convertThreeVector3ToJoltRVec3(pos), false) World.physicsSystem.setBodyRotation(this._gamePieceBodyId, convertThreeQuaternionToJoltQuat(quat), false) From 551cc12338a57c53931c515094cc6092fe262d0e Mon Sep 17 00:00:00 2001 From: Ryan Zhang Date: Mon, 21 Jul 2025 11:20:09 -0700 Subject: [PATCH 04/10] Add quaterion debug --- fission/src/util/debug/DebugPrint.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/fission/src/util/debug/DebugPrint.ts b/fission/src/util/debug/DebugPrint.ts index 5cb6d83e36..be63f01bc0 100644 --- a/fission/src/util/debug/DebugPrint.ts +++ b/fission/src/util/debug/DebugPrint.ts @@ -39,6 +39,10 @@ export function threeVector3ToString(v: THREE.Vector3, units: number = 3) { return `(${v.x.toFixed(units)}, ${v.y.toFixed(units)}, ${v.z.toFixed(units)})` } +export function threeQuaternionToString(v: THREE.Quaternion, units: number = 3) { + return `(${v.x.toFixed(units)}, ${v.y.toFixed(units)}, ${v.z.toFixed(units)}, ${v.w.toFixed(units)})` +} + export function joltVec3ToString(v: Jolt.Vec3 | Jolt.RVec3, units: number = 3) { return `(${v.GetX().toFixed(units)}, ${v.GetY().toFixed(units)}, ${v.GetZ().toFixed(units)})` } From aa992b51b2cf3b0baeaa569311759a1e0b18be16 Mon Sep 17 00:00:00 2001 From: Ryan Zhang Date: Mon, 21 Jul 2025 11:22:25 -0700 Subject: [PATCH 05/10] Update animation logic with Hunter --- fission/src/mirabuf/EjectableSceneObject.ts | 112 +++++--------------- 1 file changed, 29 insertions(+), 83 deletions(-) diff --git a/fission/src/mirabuf/EjectableSceneObject.ts b/fission/src/mirabuf/EjectableSceneObject.ts index 63c7a0b158..32ab9b2edc 100644 --- a/fission/src/mirabuf/EjectableSceneObject.ts +++ b/fission/src/mirabuf/EjectableSceneObject.ts @@ -9,7 +9,6 @@ import { convertThreeQuaternionToJoltQuat, convertThreeVector3ToJoltRVec3, convertThreeVector3ToJoltVec3, - convertJoltVec3ToThreeVector3, } from "@/util/TypeConversions" import * as THREE from "three" import ScoringZoneSceneObject from "./ScoringZoneSceneObject" @@ -23,11 +22,10 @@ class EjectableSceneObject extends SceneObject { private _ejectVelocity?: number // Animation state - private _isAnimating = false private _animationStartTime = 0 private _animationDuration = EjectableSceneObject._defaultAnimationDuration - private _startMatrix?: THREE.Matrix4 - private _endMatrix?: THREE.Matrix4 + private _startTranslation?: THREE.Vector3 + private _startRotation?: THREE.Quaternion private static _defaultAnimationDuration = 0.5 @@ -69,37 +67,11 @@ class EjectableSceneObject extends SceneObject { // Animation start at game piece const gpBody = World.physicsSystem.getBody(this._gamePieceBodyId) - // Compute the ejectable position/rotation - if (this._parentBodyId && this._deltaTransformation) { - const posToCOM = convertJoltMat44ToThreeMatrix4(gpBody.GetCenterOfMassTransform()).premultiply( - convertJoltMat44ToThreeMatrix4(gpBody.GetWorldTransform()).invert() - ) - const parentBody = World.physicsSystem.getBody(this._parentBodyId) - const bodyTransform = posToCOM - .invert() - .premultiply( - this._deltaTransformation - .clone() - .premultiply(convertJoltMat44ToThreeMatrix4(parentBody.GetWorldTransform())) - ) - // Compose start and end matrices directly - this._startMatrix = new THREE.Matrix4().compose( - convertJoltVec3ToThreeVector3(gpBody.GetPosition()), - convertJoltQuatToThreeQuaternion(gpBody.GetRotation()), - new THREE.Vector3(1, 1, 1) - ) - const endPos = new THREE.Vector3() - const endQuat = new THREE.Quaternion() - bodyTransform.decompose(endPos, endQuat, new THREE.Vector3(1, 1, 1)) - this._endMatrix = new THREE.Matrix4().compose( - endPos, - endQuat, - new THREE.Vector3(1, 1, 1) - ) - } + this._startTranslation = new THREE.Vector3(0, 0, 0) + this._startRotation = new THREE.Quaternion(0, 0, 0, 1) + convertJoltMat44ToThreeMatrix4(gpBody.GetCenterOfMassTransform()).decompose(this._startTranslation, this._startRotation, new THREE.Vector3(1, 1, 1)) this._animationDuration = EjectableSceneObject._defaultAnimationDuration - this._isAnimating = true this._animationStartTime = performance.now() World.physicsSystem.disablePhysicsForBody(this._gamePieceBodyId) @@ -118,43 +90,10 @@ class EjectableSceneObject extends SceneObject { } public update(): void { - // Animation logic: interpolate full transform (matrix) - if (this._isAnimating && this._gamePieceBodyId && this._startMatrix && this._endMatrix) { - const now = performance.now() - const elapsed = (now - this._animationStartTime) / 1000 - const t = Math.min(elapsed / this._animationDuration, 1) - - // Decompose start and end - const startPos = new THREE.Vector3() - const startQuat = new THREE.Quaternion() - const startScale = new THREE.Vector3() - this._startMatrix.decompose(startPos, startQuat, startScale) - - const endPos = new THREE.Vector3() - const endQuat = new THREE.Quaternion() - const endScale = new THREE.Vector3() - this._endMatrix.decompose(endPos, endQuat, endScale) - - // Interpolate - const pos = new THREE.Vector3().lerpVectors(startPos, endPos, t) - const quat = new THREE.Quaternion().copy(startQuat).slerp(endQuat, t) - const scale = new THREE.Vector3().lerpVectors(startScale, endScale, t) - - // Compose interpolated matrix - const interpMatrix = new THREE.Matrix4().compose(pos, quat, scale) - - // Decompose to set position/rotation - interpMatrix.decompose(pos, quat, scale) - World.physicsSystem.setBodyPosition(this._gamePieceBodyId, convertThreeVector3ToJoltRVec3(pos), false) - World.physicsSystem.setBodyRotation(this._gamePieceBodyId, convertThreeQuaternionToJoltQuat(quat), false) - - if (t >= 1) { - this._isAnimating = false - } - return - } + const now = performance.now() + const elapsed = (now - this._animationStartTime) / 1000 + const t = Math.min(elapsed / this._animationDuration, 1) - // After animation, keep gamepiece at ejectable position if (this._parentBodyId && this._deltaTransformation && this._gamePieceBodyId) { if (!World.physicsSystem.isBodyAdded(this._gamePieceBodyId)) { this._gamePieceBodyId = undefined @@ -167,23 +106,30 @@ class EjectableSceneObject extends SceneObject { ) const body = World.physicsSystem.getBody(this._parentBodyId) - const bodyTransform = posToCOM - .invert() - .premultiply( - this._deltaTransformation - .clone() - .premultiply(convertJoltMat44ToThreeMatrix4(body.GetWorldTransform())) - ) + let desiredPosition = new THREE.Vector3(0, 0, 0) + let desiredRotation = new THREE.Quaternion(0, 0, 0, 1) + + const desiredTransform = this._deltaTransformation + .clone() + .premultiply(convertJoltMat44ToThreeMatrix4(body.GetWorldTransform())) + + desiredTransform.decompose(desiredPosition, desiredRotation, new THREE.Vector3(1, 1, 1)) + + if (t < 1 && this._startTranslation && this._startRotation) { + desiredPosition = new THREE.Vector3().lerpVectors(this._startTranslation, desiredPosition, t) + desiredRotation = new THREE.Quaternion().copy(this._startRotation).slerp(desiredRotation, t) + } + + desiredTransform.identity().compose(desiredPosition, desiredRotation, new THREE.Vector3(1, 1, 1)) + + const bodyTransform = posToCOM.clone().invert().premultiply(desiredTransform) + const position = new THREE.Vector3(0, 0, 0) const rotation = new THREE.Quaternion(0, 0, 0, 1) bodyTransform.decompose(position, rotation, new THREE.Vector3(1, 1, 1)) World.physicsSystem.setBodyPosition(this._gamePieceBodyId, convertThreeVector3ToJoltRVec3(position), false) - World.physicsSystem.setBodyRotation( - this._gamePieceBodyId, - convertThreeQuaternionToJoltQuat(rotation), - false - ) + World.physicsSystem.setBodyRotation(this._gamePieceBodyId, convertThreeQuaternionToJoltQuat(rotation), false) } } @@ -206,8 +152,8 @@ class EjectableSceneObject extends SceneObject { World.physicsSystem.enablePhysicsForBody(this._gamePieceBodyId) gpBody.SetLinearVelocity( parentBody - .GetLinearVelocity() - .Add(convertThreeVector3ToJoltVec3(ejectDir.multiplyScalar(this._ejectVelocity))) + .GetLinearVelocity() + .Add(convertThreeVector3ToJoltVec3(ejectDir.multiplyScalar(this._ejectVelocity))) ) gpBody.SetAngularVelocity(parentBody.GetAngularVelocity()) From 23f772302c098744f01f15b0f0c4f5b97d1f61d7 Mon Sep 17 00:00:00 2001 From: Ryan Zhang Date: Mon, 21 Jul 2025 11:23:00 -0700 Subject: [PATCH 06/10] chore: formatting --- fission/src/mirabuf/EjectableSceneObject.ts | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/fission/src/mirabuf/EjectableSceneObject.ts b/fission/src/mirabuf/EjectableSceneObject.ts index 32ab9b2edc..a48d8f15cc 100644 --- a/fission/src/mirabuf/EjectableSceneObject.ts +++ b/fission/src/mirabuf/EjectableSceneObject.ts @@ -69,7 +69,11 @@ class EjectableSceneObject extends SceneObject { this._startTranslation = new THREE.Vector3(0, 0, 0) this._startRotation = new THREE.Quaternion(0, 0, 0, 1) - convertJoltMat44ToThreeMatrix4(gpBody.GetCenterOfMassTransform()).decompose(this._startTranslation, this._startRotation, new THREE.Vector3(1, 1, 1)) + convertJoltMat44ToThreeMatrix4(gpBody.GetCenterOfMassTransform()).decompose( + this._startTranslation, + this._startRotation, + new THREE.Vector3(1, 1, 1) + ) this._animationDuration = EjectableSceneObject._defaultAnimationDuration this._animationStartTime = performance.now() @@ -129,7 +133,11 @@ class EjectableSceneObject extends SceneObject { bodyTransform.decompose(position, rotation, new THREE.Vector3(1, 1, 1)) World.physicsSystem.setBodyPosition(this._gamePieceBodyId, convertThreeVector3ToJoltRVec3(position), false) - World.physicsSystem.setBodyRotation(this._gamePieceBodyId, convertThreeQuaternionToJoltQuat(rotation), false) + World.physicsSystem.setBodyRotation( + this._gamePieceBodyId, + convertThreeQuaternionToJoltQuat(rotation), + false + ) } } @@ -152,8 +160,8 @@ class EjectableSceneObject extends SceneObject { World.physicsSystem.enablePhysicsForBody(this._gamePieceBodyId) gpBody.SetLinearVelocity( parentBody - .GetLinearVelocity() - .Add(convertThreeVector3ToJoltVec3(ejectDir.multiplyScalar(this._ejectVelocity))) + .GetLinearVelocity() + .Add(convertThreeVector3ToJoltVec3(ejectDir.multiplyScalar(this._ejectVelocity))) ) gpBody.SetAngularVelocity(parentBody.GetAngularVelocity()) From 088abf6dab77735dae530acec8f4f48c88251a8a Mon Sep 17 00:00:00 2001 From: Ryan Zhang Date: Mon, 21 Jul 2025 12:54:45 -0700 Subject: [PATCH 07/10] Update animationDuration globally --- .../src/systems/preferences/PreferenceTypes.ts | 2 ++ fission/src/test/PreferencesSystem.test.ts | 2 ++ .../src/test/mirabuf/MirabufSceneObject.test.ts | 2 ++ .../ConfigureGamepiecePickupInterface.tsx | 17 ++++++++++++----- 4 files changed, 18 insertions(+), 5 deletions(-) diff --git a/fission/src/systems/preferences/PreferenceTypes.ts b/fission/src/systems/preferences/PreferenceTypes.ts index ef12cbf4f4..38fcfe71cf 100644 --- a/fission/src/systems/preferences/PreferenceTypes.ts +++ b/fission/src/systems/preferences/PreferenceTypes.ts @@ -92,6 +92,7 @@ export type IntakePreferences = { parentNode: string | undefined showZoneAlways: boolean maxPieces: number + animationDuration: number } export type EjectorPreferences = { @@ -181,6 +182,7 @@ export function defaultRobotPreferences(): RobotPreferences { parentNode: undefined, showZoneAlways: false, maxPieces: 1, + animationDuration: 0.5, }, ejector: { deltaTransformation: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], diff --git a/fission/src/test/PreferencesSystem.test.ts b/fission/src/test/PreferencesSystem.test.ts index abaebd8da0..57c8e6ef55 100644 --- a/fission/src/test/PreferencesSystem.test.ts +++ b/fission/src/test/PreferencesSystem.test.ts @@ -123,6 +123,7 @@ describe("Preference System Robot/Field", () => { parentNode: undefined, showZoneAlways: true, maxPieces: 3, + animationDuration: 0.5, }, ejector: { deltaTransformation: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], @@ -142,6 +143,7 @@ describe("Preference System Robot/Field", () => { parentNode: undefined, showZoneAlways: false, maxPieces: 1, + animationDuration: 0.5, }, ejector: { deltaTransformation: [1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], diff --git a/fission/src/test/mirabuf/MirabufSceneObject.test.ts b/fission/src/test/mirabuf/MirabufSceneObject.test.ts index 90eba3c66b..e3b1cb0011 100644 --- a/fission/src/test/mirabuf/MirabufSceneObject.test.ts +++ b/fission/src/test/mirabuf/MirabufSceneObject.test.ts @@ -188,6 +188,7 @@ describe("MirabufSceneObject", () => { zoneDiameter: 1, showZoneAlways: false, maxPieces: 0, + animationDuration: 0.5, }) expect(instance.setEjectable(bodyId)).toBe(false) }) @@ -205,6 +206,7 @@ describe("MirabufSceneObject", () => { zoneDiameter: 1, showZoneAlways: false, maxPieces: 2, + animationDuration: 0.5, }) setPrivate(instance, "_ejectables", []) const bodyId = mockBodyId() diff --git a/fission/src/ui/panels/configuring/assembly-config/interfaces/ConfigureGamepiecePickupInterface.tsx b/fission/src/ui/panels/configuring/assembly-config/interfaces/ConfigureGamepiecePickupInterface.tsx index 91d722ce25..d755d16ea1 100644 --- a/fission/src/ui/panels/configuring/assembly-config/interfaces/ConfigureGamepiecePickupInterface.tsx +++ b/fission/src/ui/panels/configuring/assembly-config/interfaces/ConfigureGamepiecePickupInterface.tsx @@ -61,7 +61,8 @@ function save( selectedRobot: MirabufSceneObject, selectedNode?: RigidNodeId, showZoneAlways?: boolean, - maxPieces?: number + maxPieces?: number, + animationDuration?: number ) { if (!selectedRobot?.intakePreferences || !gizmo) { return @@ -92,6 +93,7 @@ function save( } selectedRobot.intakePreferences.maxPieces = maxPieces! + selectedRobot.intakePreferences.animationDuration = animationDuration! PreferencesSystem.savePreferences() } @@ -110,16 +112,18 @@ const ConfigureGamepiecePickupInterface: React.FC = ({ select const [zoneSize, setZoneSize] = useState((MIN_ZONE_SIZE + MAX_ZONE_SIZE) / 2.0) const [showZoneAlways, setShowZoneAlways] = useState(false) const [maxPieces, setMaxPieces] = useState(selectedRobot.intakePreferences?.maxPieces || 1) - const [animationDuration, setAnimationDuration] = useState(EjectableSceneObject.getAnimationDuration()) + const [animationDuration, setAnimationDuration] = useState( + selectedRobot.intakePreferences?.animationDuration || 0.5 + ) const gizmoRef = useRef(undefined) const saveEvent = useCallback(() => { if (gizmoRef.current && selectedRobot) { - save(zoneSize, gizmoRef.current, selectedRobot, selectedNode, showZoneAlways, maxPieces) + save(zoneSize, gizmoRef.current, selectedRobot, selectedNode, showZoneAlways, maxPieces, animationDuration) selectedRobot.updateIntakeSensor() } - }, [selectedRobot, selectedNode, zoneSize, showZoneAlways, maxPieces]) + }, [selectedRobot, selectedNode, zoneSize, showZoneAlways, maxPieces, animationDuration]) useEffect(() => { ConfigurationSavedEvent.listen(saveEvent) @@ -198,9 +202,11 @@ const ConfigureGamepiecePickupInterface: React.FC = ({ select setSelectedNode(selectedRobot.intakePreferences.parentNode) setMaxPieces(selectedRobot.intakePreferences.maxPieces) setShowZoneAlways(selectedRobot.intakePreferences.showZoneAlways ?? false) + setAnimationDuration(selectedRobot.intakePreferences.animationDuration ?? 0.5) } else { setSelectedNode(undefined) setShowZoneAlways(false) + setAnimationDuration(0.5) } }, [selectedRobot]) @@ -260,7 +266,7 @@ const ConfigureGamepiecePickupInterface: React.FC = ({ select { const val = typeof v === "number" ? v : v[0] setAnimationDuration(val) @@ -353,6 +359,7 @@ const ConfigureGamepiecePickupInterface: React.FC = ({ select setZoneSize(0.5) setSelectedNode(selectedRobot?.rootNodeId) setMaxPieces(selectedRobot.intakePreferences?.maxPieces ?? 1) + setAnimationDuration(0.5) }} /> From da6155d8191a068b48dbb95ec109b3cc1b649d20 Mon Sep 17 00:00:00 2001 From: Ryan Zhang Date: Tue, 22 Jul 2025 08:47:27 -0700 Subject: [PATCH 08/10] Add cubic easing + comments for future --- fission/src/mirabuf/EjectableSceneObject.ts | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/fission/src/mirabuf/EjectableSceneObject.ts b/fission/src/mirabuf/EjectableSceneObject.ts index a48d8f15cc..26321dfaee 100644 --- a/fission/src/mirabuf/EjectableSceneObject.ts +++ b/fission/src/mirabuf/EjectableSceneObject.ts @@ -64,9 +64,8 @@ class EjectableSceneObject extends SceneObject { ) this._ejectVelocity = this._parentAssembly.ejectorPreferences.ejectorVelocity - // Animation start at game piece + // Record start transform at the game piece center of mass const gpBody = World.physicsSystem.getBody(this._gamePieceBodyId) - this._startTranslation = new THREE.Vector3(0, 0, 0) this._startRotation = new THREE.Quaternion(0, 0, 0, 1) convertJoltMat44ToThreeMatrix4(gpBody.GetCenterOfMassTransform()).decompose( @@ -80,7 +79,7 @@ class EjectableSceneObject extends SceneObject { World.physicsSystem.disablePhysicsForBody(this._gamePieceBodyId) - // Remove from scoring zones + // Remove from any scoring zones const zones = [...World.sceneRenderer.sceneObjects.entries()] .filter(x => x[1] instanceof ScoringZoneSceneObject) .map(x => x[1]) as ScoringZoneSceneObject[] @@ -96,7 +95,11 @@ class EjectableSceneObject extends SceneObject { public update(): void { const now = performance.now() const elapsed = (now - this._animationStartTime) / 1000 - const t = Math.min(elapsed / this._animationDuration, 1) + const tRaw = elapsed / this._animationDuration + const t = Math.min(tRaw, 1) + + // ease-in curve for gradual acceleration + const easedT = t * t * t if (this._parentBodyId && this._deltaTransformation && this._gamePieceBodyId) { if (!World.physicsSystem.isBodyAdded(this._gamePieceBodyId)) { @@ -113,6 +116,7 @@ class EjectableSceneObject extends SceneObject { let desiredPosition = new THREE.Vector3(0, 0, 0) let desiredRotation = new THREE.Quaternion(0, 0, 0, 1) + // Compute target world transform const desiredTransform = this._deltaTransformation .clone() .premultiply(convertJoltMat44ToThreeMatrix4(body.GetWorldTransform())) @@ -120,10 +124,15 @@ class EjectableSceneObject extends SceneObject { desiredTransform.decompose(desiredPosition, desiredRotation, new THREE.Vector3(1, 1, 1)) if (t < 1 && this._startTranslation && this._startRotation) { - desiredPosition = new THREE.Vector3().lerpVectors(this._startTranslation, desiredPosition, t) - desiredRotation = new THREE.Quaternion().copy(this._startRotation).slerp(desiredRotation, t) + // gradual acceleration via easedT + desiredPosition = new THREE.Vector3().lerpVectors(this._startTranslation, desiredPosition, easedT) + desiredRotation = new THREE.Quaternion().copy(this._startRotation).slerp(desiredRotation, easedT) + } else if (t >= 1) { + // snap instantly and re-enable physics + World.physicsSystem.enablePhysicsForBody(this._gamePieceBodyId) } + // apply the transform desiredTransform.identity().compose(desiredPosition, desiredRotation, new THREE.Vector3(1, 1, 1)) const bodyTransform = posToCOM.clone().invert().premultiply(desiredTransform) From ba71d9a4666a798bc25709df79a06e90cda39949 Mon Sep 17 00:00:00 2001 From: Ryan Zhang Date: Tue, 22 Jul 2025 15:53:25 -0700 Subject: [PATCH 09/10] Revert back to quadratic easing --- fission/src/mirabuf/EjectableSceneObject.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fission/src/mirabuf/EjectableSceneObject.ts b/fission/src/mirabuf/EjectableSceneObject.ts index 26321dfaee..b63331d254 100644 --- a/fission/src/mirabuf/EjectableSceneObject.ts +++ b/fission/src/mirabuf/EjectableSceneObject.ts @@ -99,7 +99,7 @@ class EjectableSceneObject extends SceneObject { const t = Math.min(tRaw, 1) // ease-in curve for gradual acceleration - const easedT = t * t * t + const easedT = t * t if (this._parentBodyId && this._deltaTransformation && this._gamePieceBodyId) { if (!World.physicsSystem.isBodyAdded(this._gamePieceBodyId)) { From 50b923d4cf093604707356187f95e9e5155dbc1f Mon Sep 17 00:00:00 2001 From: Ryan Zhang Date: Tue, 22 Jul 2025 16:21:46 -0700 Subject: [PATCH 10/10] fix: gamepiece dropping + random collisions --- fission/src/mirabuf/EjectableSceneObject.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/fission/src/mirabuf/EjectableSceneObject.ts b/fission/src/mirabuf/EjectableSceneObject.ts index b63331d254..68418201c4 100644 --- a/fission/src/mirabuf/EjectableSceneObject.ts +++ b/fission/src/mirabuf/EjectableSceneObject.ts @@ -127,10 +127,11 @@ class EjectableSceneObject extends SceneObject { // gradual acceleration via easedT desiredPosition = new THREE.Vector3().lerpVectors(this._startTranslation, desiredPosition, easedT) desiredRotation = new THREE.Quaternion().copy(this._startRotation).slerp(desiredRotation, easedT) - } else if (t >= 1) { - // snap instantly and re-enable physics - World.physicsSystem.enablePhysicsForBody(this._gamePieceBodyId) } + // } else if (t >= 1) { + // // snap instantly and re-enable physics + // World.physicsSystem.enablePhysicsForBody(this._gamePieceBodyId) + // } // apply the transform desiredTransform.identity().compose(desiredPosition, desiredRotation, new THREE.Vector3(1, 1, 1))