diff --git a/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ConfigureProtectedZonesInterface.tsx b/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ConfigureProtectedZonesInterface.tsx index 550cbb4c7b..4c51c06aad 100644 --- a/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ConfigureProtectedZonesInterface.tsx +++ b/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ConfigureProtectedZonesInterface.tsx @@ -6,9 +6,9 @@ import PreferencesSystem from "@/systems/preferences/PreferencesSystem" import type { ProtectedZonePreferences } from "@/systems/preferences/PreferenceTypes" import Label from "@/ui/components/Label" import ManageProtectedZonesInterface from "./ManageProtectedZonesInterface" -import ZoneConfigInterface from "./ProtectedZoneConfigInterface" +import ProtectedZoneConfigInterface from "./ProtectedZoneConfigInterface" -const protectedZones = (zones: ProtectedZonePreferences[] | undefined, field: MirabufSceneObject | undefined) => { +const saveProtectedZones = (zones: ProtectedZonePreferences[] | undefined, field: MirabufSceneObject | undefined) => { if (!zones || !field) return const fieldPrefs = field.fieldPreferences @@ -48,11 +48,11 @@ const ConfigureProtectedZonesInterface: React.FC = ({ selec - { - protectedZones(selectedField.fieldPreferences?.protectedZones, selectedField) + saveProtectedZones(selectedField.fieldPreferences?.protectedZones, selectedField) }} /> diff --git a/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ConfigureScoringZonesInterface.tsx b/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ConfigureScoringZonesInterface.tsx index fc9606106c..e732a819f4 100644 --- a/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ConfigureScoringZonesInterface.tsx +++ b/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ConfigureScoringZonesInterface.tsx @@ -1,16 +1,14 @@ import { Box, Divider, Stack } from "@mui/material" import type React from "react" import { useState } from "react" -import { ConfigurationSavedEvent } from "@/events/ConfigurationSavedEvent" import type MirabufSceneObject from "@/mirabuf/MirabufSceneObject" import PreferencesSystem from "@/systems/preferences/PreferencesSystem" import type { ScoringZonePreferences } from "@/systems/preferences/PreferenceTypes" import Label from "@/ui/components/Label" -import { Button, SynthesisIcons } from "@/ui/components/StyledComponents" import ManageScoringZonesInterface from "./ManageScoringZonesInterface" -import ZoneConfigInterface from "./ScoringZoneConfigInterface" +import ScoringZoneConfigInterface from "./ScoringZoneConfigInterface" -const saveZones = (zones: ScoringZonePreferences[] | undefined, field: MirabufSceneObject | undefined) => { +const saveScoringZones = (zones: ScoringZonePreferences[] | undefined, field: MirabufSceneObject | undefined) => { if (!zones || !field) return const fieldPrefs = field.fieldPreferences @@ -40,16 +38,6 @@ const ConfigureScoringZonesInterface: React.FC = ({ selecte <> - - {/** Back arrow button when an option is selected */} - - - {/** Select a parent node */} - trySetSelectedNode(body.GetID())} - /> + const removeZoneObject = useCallback((field: MirabufSceneObject, zone: ProtectedZonePreferences) => { + field.removeProtectedZoneObject(zone) + }, []) - {/** Set the point value */} + return ( + setPoints(parseInt(v.target.value) || 1)} + onChange={v => setPenaltyPoints(parseInt(v.target.value) || 1)} /> - - {/** Determines during what game state the protected zone is active */} Active During - - {/** Determines what type of contact is required for the penalty to apply */} Contact Type - - {gizmoComponent} - + ) } -export default ZoneConfigInterface +export default ProtectedZoneConfigInterface diff --git a/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ScoringZoneConfigInterface.tsx b/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ScoringZoneConfigInterface.tsx index 875022fc31..794b35f783 100644 --- a/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ScoringZoneConfigInterface.tsx +++ b/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ScoringZoneConfigInterface.tsx @@ -1,101 +1,19 @@ -import type Jolt from "@azaleacolburn/jolt-physics" -import { TextField } from "@mui/material" -import { useCallback, useEffect, useMemo, useRef, useState } from "react" -import * as THREE from "three" -import { ConfigurationSavedEvent } from "@/events/ConfigurationSavedEvent" -import type { RigidNodeId } from "@/mirabuf/MirabufParser" import type MirabufSceneObject from "@/mirabuf/MirabufSceneObject" -import type { RigidNodeAssociate } from "@/mirabuf/MirabufSceneObject" -import { PAUSE_REF_ASSEMBLY_CONFIG } from "@/systems/physics/PhysicsTypes" -import PreferencesSystem from "@/systems/preferences/PreferencesSystem" -import type { Alliance, ScoringZonePreferences } from "@/systems/preferences/PreferenceTypes" -import type GizmoSceneObject from "@/systems/scene/GizmoSceneObject" -import World from "@/systems/World" +import type { ScoringZonePreferences } from "@/systems/preferences/PreferenceTypes" +import ZoneConfigBase from "../zones/ZoneConfigBase" +import { TextField } from "@mui/material" +import { useState, useCallback } from "react" import Checkbox from "@/ui/components/Checkbox" -import SelectButton from "@/ui/components/SelectButton" -import { Button } from "@/ui/components/StyledComponents" -import TransformGizmoControl from "@/ui/components/TransformGizmoControl" -import { - convertArrayToThreeMatrix4, - convertJoltMat44ToThreeMatrix4, - convertThreeMatrix4ToArray, -} from "@/util/TypeConversions" -import { deltaFieldTransformsPhysicalProp as deltaFieldTransformsVisualProperties } from "@/util/threejs/MeshCreation" /** - * Saves ejector configuration to selected field. - * - * Math Explanation: - * Let W be the world transformation matrix of the gizmo. - * Let R be the world transformation matrix of the selected field node. - * Let L be the local transformation matrix of the gizmo, relative to the selected field node. - * - * We are given W and R, and want to save L with the field. This way when we create - * the ejection point afterwards, it will be relative to the selected field node. - * - * W = L R - * L = W R^(-1) - * - * ThreeJS sets the standard multiplication operation for matrices to be premultiply. I really - * don't like this terminology as it's thrown me off multiple times, but I suppose it does go - * against most other multiplication operations. - * - * @param name Name given to the scoring zone by the user. - * @param alliance Scoring zone alliance. * @param points Number of points the zone is worth. * @param destroy Destroy gamepiece setting. * @param persistent Persistent points setting. - * @param gizmo Reference to the transform gizmo object. - * @param selectedNode Selected node that configuration is relative to. */ -function save( - field: MirabufSceneObject, - zone: ScoringZonePreferences, - name: string, - alliance: Alliance, - points: number, - destroy: boolean, - persistent: boolean, - gizmo: GizmoSceneObject, - selectedNode?: RigidNodeId -) { - if (!field?.fieldPreferences || !gizmo) { - return - } - - selectedNode ??= field.rootNodeId - - const nodeBodyId = field.mechanism.nodeToBody.get(selectedNode) - if (!nodeBodyId) { - return - } - - // This step seems useless, but keeps the scale from messing up the rotation - const translation = new THREE.Vector3(0, 0, 0) - const rotation = new THREE.Quaternion(0, 0, 0, 1) - const scale = new THREE.Vector3(1, 1, 1) - gizmo.obj.matrixWorld.decompose(translation, rotation, scale) - scale.x = Math.abs(scale.x) - scale.y = Math.abs(scale.y) - scale.z = Math.abs(scale.z) - - const gizmoTransformation = new THREE.Matrix4().compose(translation, rotation, scale) - const fieldTransformation = convertJoltMat44ToThreeMatrix4( - World.physicsSystem.getBody(nodeBodyId).GetWorldTransform() - ) - const deltaTransformation = gizmoTransformation.premultiply(fieldTransformation.invert()) - - zone.deltaTransformation = convertThreeMatrix4ToArray(deltaTransformation) - zone.name = name - zone.alliance = alliance - zone.parentNode = selectedNode - zone.points = points - zone.destroyGamepiece = destroy - zone.persistentPoints = persistent +function attachAndPersistZone(zone: ScoringZonePreferences, field: MirabufSceneObject) { + if (!field?.fieldPreferences) return if (!field.fieldPreferences.scoringZones.includes(zone)) field.fieldPreferences.scoringZones.push(zone) - - PreferencesSystem.savePreferences() } interface ZoneConfigProps { @@ -104,176 +22,31 @@ interface ZoneConfigProps { saveAllZones: () => void } -const ZoneConfigInterface: React.FC = ({ selectedField, selectedZone, saveAllZones }) => { - //Official FIRST hex - // TODO: Do we want to eventually make these editable? - const redMaterial = useMemo(() => { - return new THREE.MeshPhongMaterial({ - color: 0xed1c24, - shininess: 0.0, - opacity: 0.7, - transparent: true, - }) - }, []) - - const blueMaterial = useMemo(() => { - return new THREE.MeshPhongMaterial({ - color: 0x0066b3, - shininess: 0.0, - opacity: 0.7, - transparent: true, - }) - }, []) - - const [name, setName] = useState(selectedZone.name) - const [alliance, setAlliance] = useState(selectedZone.alliance) - const [selectedNode, setSelectedNode] = useState(selectedZone.parentNode) +const ScoringZoneConfigInterface: React.FC = ({ selectedField, selectedZone, saveAllZones }) => { const [points, setPoints] = useState(selectedZone.points) - const [destroy] = useState(selectedZone.destroyGamepiece) const [persistent, setPersistent] = useState(selectedZone.persistentPoints) - const gizmoRef = useRef(undefined) - - const saveEvent = useCallback(() => { - if (gizmoRef.current && selectedField) { - save( - selectedField, - selectedZone, - name, - alliance, - points, - destroy, - persistent, - gizmoRef.current, - selectedNode - ) - saveAllZones() - } - }, [selectedField, selectedZone, name, alliance, points, destroy, persistent, selectedNode, saveAllZones]) - - useEffect(() => { - ConfigurationSavedEvent.listen(saveEvent) - - return () => { - ConfigurationSavedEvent.removeListener(saveEvent) - } - }, [saveEvent]) - - /** Holds a pause for the duration of the interface component */ - useEffect(() => { - World.physicsSystem.holdPause(PAUSE_REF_ASSEMBLY_CONFIG) - - return () => { - World.physicsSystem.releasePause(PAUSE_REF_ASSEMBLY_CONFIG) - } - }, []) - - /** Creates the default mesh for the gizmo */ - const defaultGizmoMesh = useMemo(() => { - console.debug("Default Gizmo Mesh Recreation") - - if (!selectedZone) { - console.debug("No zone selected") - return undefined - } - - return new THREE.Mesh( - new THREE.BoxGeometry(1, 1, 1), - selectedZone.alliance === "blue" ? blueMaterial : redMaterial - ) - }, [selectedZone, selectedZone.alliance, blueMaterial, redMaterial]) - - /** Creates TransformGizmoControl component and sets up target mesh. */ - const gizmoComponent = useMemo(() => { - if (selectedField && selectedZone) { - const postGizmoCreation = (gizmo: GizmoSceneObject) => { - const material = (gizmo.obj as THREE.Mesh).material as THREE.Material - material.depthTest = false - - const deltaTransformation = convertArrayToThreeMatrix4(selectedZone.deltaTransformation) - - let nodeBodyId = selectedField.mechanism.nodeToBody.get( - selectedZone.parentNode ?? selectedField.rootNodeId - ) - if (!nodeBodyId) { - // In the event that something about the id generation for the rigid nodes changes and parent node id is no longer in use - nodeBodyId = selectedField.mechanism.nodeToBody.get(selectedField.rootNodeId)! - } - - /** W = L x R. See save() for math details */ - const fieldTransformation = convertJoltMat44ToThreeMatrix4( - World.physicsSystem.getBody(nodeBodyId).GetWorldTransform() - ) - const props = deltaFieldTransformsVisualProperties(deltaTransformation, fieldTransformation) - - gizmo.obj.position.set(props.translation.x, props.translation.y, props.translation.z) - gizmo.obj.rotation.setFromQuaternion(props.rotation) - gizmo.obj.scale.set(props.scale.x, props.scale.y, props.scale.z) - selectedField.removeScoringZoneObject(selectedZone) // avoid rendering twice - } - - return ( - - ) - } else { - gizmoRef.current = undefined - return <> - } - }, [selectedField, selectedZone, defaultGizmoMesh]) - - /** Sets the selected node if it is a part of the currently loaded field */ - const trySetSelectedNode = useCallback( - (body: Jolt.BodyID) => { - if (!selectedField) { - return false - } - - const assoc = World.physicsSystem.getBodyAssociation(body) as RigidNodeAssociate - if (!assoc || assoc?.sceneObject !== selectedField) { - return false - } - - setSelectedNode(assoc.rigidNodeId) - return true + const applyExtrasOnSave = useCallback( + (zone: ScoringZonePreferences) => { + zone.points = points + zone.persistentPoints = persistent }, - [selectedField] + [points, persistent] ) - return ( -
- {/** Set the zone name */} - setName(e.target.value)} - /> - - {/** Set the alliance color */} - - - {/** Select a parent node */} - trySetSelectedNode(body.GetID())} - /> + const removeZoneObject = useCallback((field: MirabufSceneObject, zone: ScoringZonePreferences) => { + field.removeScoringZoneObject(zone) + }, []) - {/** Set the point value */} + return ( + = ({ selectedField, selecte defaultValue={selectedZone.points} onChange={v => setPoints(parseInt(v.target.value) || 1)} /> - - {/** When checked, the zone will destroy gamepieces it comes in contact with */} - {/** */} - - {/** When checked, points will stay even when a gamepiece leaves the zone */} setPersistent(checked)} /> - - {/** Switch between transform control modes */} - - {gizmoComponent} -
+ ) } -export default ZoneConfigInterface +export default ScoringZoneConfigInterface diff --git a/fission/src/ui/panels/configuring/assembly-config/interfaces/zones/ManageZonesBase.tsx b/fission/src/ui/panels/configuring/assembly-config/interfaces/zones/ManageZonesBase.tsx new file mode 100644 index 0000000000..7b5f4eb374 --- /dev/null +++ b/fission/src/ui/panels/configuring/assembly-config/interfaces/zones/ManageZonesBase.tsx @@ -0,0 +1,117 @@ +import { Box, Stack } from "@mui/material" +import { useCallback, useEffect, useState } from "react" +import { ConfigurationSavedEvent } from "@/events/ConfigurationSavedEvent" +import type MirabufSceneObject from "@/mirabuf/MirabufSceneObject" +import { PAUSE_REF_ASSEMBLY_CONFIG } from "@/systems/physics/PhysicsTypes" +import PreferencesSystem from "@/systems/preferences/PreferencesSystem" +import type { Alliance } from "@/systems/preferences/PreferenceTypes" +import World from "@/systems/World" +import Label from "@/ui/components/Label" +import ScrollView from "@/ui/components/ScrollView" +import { AddButton, DeleteButton, EditButton } from "@/ui/components/StyledComponents" + +export type ZoneListItem = { + name: string + alliance: Alliance + pointsLabel?: string +} + +export type ManageZonesBaseProps = { + selectedField: MirabufSceneObject + initialZones: TZone[] + selectZone: (zone: TZone) => void + /** Returns the props needed to render a row for a given zone */ + getListItem: (zone: TZone) => ZoneListItem + /** Apply the new list to the field preferences and persist, and trigger any field updates */ + persistZones: (zones: TZone[], field: MirabufSceneObject) => void + /** Create a sensible default new zone */ + createNewZone: () => TZone +} + +function saveZonesGeneric( + zones: TZone[] | undefined, + field: MirabufSceneObject | undefined, + persistZones: (zones: TZone[], field: MirabufSceneObject) => void +) { + if (!zones || !field) return + persistZones(zones, field) + PreferencesSystem.savePreferences() +} + +export default function ManageZonesBase(props: ManageZonesBaseProps) { + const { selectedField, initialZones, selectZone, getListItem, persistZones, createNewZone } = props + const [zones, setZones] = useState(initialZones) + + const saveEvent = useCallback(() => { + saveZonesGeneric(zones, selectedField, persistZones) + }, [zones, selectedField, persistZones]) + + useEffect(() => { + ConfigurationSavedEvent.listen(saveEvent) + return () => { + ConfigurationSavedEvent.removeListener(saveEvent) + } + }, [saveEvent]) + + useEffect(() => { + saveZonesGeneric(zones, selectedField, persistZones) + World.physicsSystem.holdPause(PAUSE_REF_ASSEMBLY_CONFIG) + return () => { + World.physicsSystem.releasePause(PAUSE_REF_ASSEMBLY_CONFIG) + } + }, [selectedField, zones, persistZones]) + + return ( + <> + {zones?.length > 0 ? ( + + + {zones.map((zonePrefs: TZone, i: number) => { + const item = getListItem(zonePrefs) + return ( + + + + + + {item.pointsLabel ? : null} + + + + {EditButton(() => { + selectZone(zonePrefs) + saveZonesGeneric(zones, selectedField, persistZones) + })} + {DeleteButton(() => { + const newZones = zones.filter((_, idx) => idx !== i) + setZones(newZones) + saveZonesGeneric(newZones, selectedField, persistZones) + })} + + + ) + })} + + + ) : ( + + )} + {AddButton(() => { + const newZone = createNewZone() + saveZonesGeneric(zones, selectedField, persistZones) + selectZone(newZone) + })} + + ) +} diff --git a/fission/src/ui/panels/configuring/assembly-config/interfaces/zones/ZoneConfigBase.tsx b/fission/src/ui/panels/configuring/assembly-config/interfaces/zones/ZoneConfigBase.tsx new file mode 100644 index 0000000000..f1f89cf2cc --- /dev/null +++ b/fission/src/ui/panels/configuring/assembly-config/interfaces/zones/ZoneConfigBase.tsx @@ -0,0 +1,263 @@ +import type Jolt from "@azaleacolburn/jolt-physics" +import { Button, Stack, TextField } from "@mui/material" +import { useCallback, useEffect, useMemo, useRef, useState } from "react" +import * as THREE from "three" +import { ConfigurationSavedEvent } from "@/events/ConfigurationSavedEvent" +import type { RigidNodeId } from "@/mirabuf/MirabufParser" +import type MirabufSceneObject from "@/mirabuf/MirabufSceneObject" +import type { RigidNodeAssociate } from "@/mirabuf/MirabufSceneObject" +import { PAUSE_REF_ASSEMBLY_CONFIG } from "@/systems/physics/PhysicsTypes" +import PreferencesSystem from "@/systems/preferences/PreferencesSystem" +import type { Alliance } from "@/systems/preferences/PreferenceTypes" +import type GizmoSceneObject from "@/systems/scene/GizmoSceneObject" +import World from "@/systems/World" +import SelectButton from "@/ui/components/SelectButton" +import TransformGizmoControl from "@/ui/components/TransformGizmoControl" +import { + convertArrayToThreeMatrix4, + convertJoltMat44ToThreeMatrix4, + convertThreeMatrix4ToArray, +} from "@/util/TypeConversions" +import { deltaFieldTransformsPhysicalProp } from "@/util/threejs/MeshCreation" + +/** + * Saves zone configuration to selected field. + * + * Math Explanation: + * Let W be the world transformation matrix of the gizmo. + * Let R be the world transformation matrix of the selected field node. + * Let L be the local transformation matrix of the gizmo, relative to the selected field node. + * + * We are given W and R, and want to save L with the field. This way when we create + * the ejection point afterwards, it will be relative to the selected field node. + * + * W = L R + * L = W R^(-1) + * + * ThreeJS sets the standard multiplication operation for matrices to be premultiply. I really + * don't like this terminology as it's thrown me off multiple times, but I suppose it does go + * against most other multiplication operations. + * + * @param name Name given to the zone by the user. + * @param alliance Zone alliance. + * @param parentNode Parent node of the zone. + * @param deltaTransformation Delta transformation of the zone. + */ + +export type BaseZonePreferences = { + name: string + alliance: Alliance + parentNode: string | undefined + deltaTransformation: number[] +} + +type AllianceMaterials = { + red: THREE.Material + blue: THREE.Material +} + +export type ZoneConfigBaseProps = { + selectedField: MirabufSceneObject + selectedZone: TZone + /** Called to ensure the zone exists in the correct preferences list and to persist. */ + attachAndPersistZone: (zone: TZone, field: MirabufSceneObject) => void + /** Called by the base right before save to allow updating zone-specific fields from local state. */ + applyExtrasOnSave: (zone: TZone) => void + /** Called after save to bubble up any UI updates, e.g. refreshing a list. */ + saveAllZones?: () => void + /** Removes any already-rendered zone object from the field to avoid double-rendering while gizmo is active. */ + removeZoneObject: (field: MirabufSceneObject, zone: TZone) => void + /** Materials used to visualize the gizmo by alliance. If omitted, defaults will be used. */ + materials?: AllianceMaterials + /** Optional additional inputs to render below the common fields. */ + children?: React.ReactNode +} + +const DEFAULT_RED_MATERIAL = new THREE.MeshPhongMaterial({ + color: 0xed1c24, + shininess: 0.0, + opacity: 0.7, + transparent: true, +}) +const DEFAULT_BLUE_MATERIAL = new THREE.MeshPhongMaterial({ + color: 0x0066b3, + shininess: 0.0, + opacity: 0.7, + transparent: true, +}) + +function computeDeltaFromGizmo( + field: MirabufSceneObject, + gizmo: GizmoSceneObject, + selectedNode?: RigidNodeId +): number[] | undefined { + selectedNode ??= field.rootNodeId + + const nodeBodyId = field.mechanism.nodeToBody.get(selectedNode) + if (!nodeBodyId) return undefined + + const translation = new THREE.Vector3(0, 0, 0) + const rotation = new THREE.Quaternion(0, 0, 0, 1) + const scale = new THREE.Vector3(1, 1, 1) + gizmo.obj.matrixWorld.decompose(translation, rotation, scale) + scale.x = Math.abs(scale.x) + scale.y = Math.abs(scale.y) + scale.z = Math.abs(scale.z) + + const gizmoTransformation = new THREE.Matrix4().compose(translation, rotation, scale) + const fieldTransformation = convertJoltMat44ToThreeMatrix4( + World.physicsSystem.getBody(nodeBodyId).GetWorldTransform() + ) + const deltaTransformation = gizmoTransformation.premultiply(fieldTransformation.invert()) + + return convertThreeMatrix4ToArray(deltaTransformation) +} + +function getAllianceMaterial(alliance: Alliance, materials?: AllianceMaterials): THREE.Material { + if (materials) return alliance === "blue" ? materials.blue : materials.red + return alliance === "blue" ? DEFAULT_BLUE_MATERIAL : DEFAULT_RED_MATERIAL +} + +export default function ZoneConfigBase(props: ZoneConfigBaseProps) { + const { + selectedField, + selectedZone, + attachAndPersistZone, + applyExtrasOnSave, + saveAllZones, + removeZoneObject, + materials, + } = props + + const [name, setName] = useState(selectedZone.name) + const [alliance, setAlliance] = useState(selectedZone.alliance) + const [selectedNode, setSelectedNode] = useState(selectedZone.parentNode) + + const gizmoRef = useRef(undefined) + + const saveEvent = useCallback(() => { + if (gizmoRef.current && selectedField && selectedZone) { + const delta = computeDeltaFromGizmo(selectedField, gizmoRef.current, selectedNode) + if (!delta) return + + selectedZone.deltaTransformation = delta + selectedZone.name = name + selectedZone.alliance = alliance + selectedZone.parentNode = selectedNode + + applyExtrasOnSave(selectedZone) + attachAndPersistZone(selectedZone, selectedField) + PreferencesSystem.savePreferences() + saveAllZones?.() + } + }, [ + selectedField, + selectedZone, + name, + alliance, + selectedNode, + applyExtrasOnSave, + attachAndPersistZone, + saveAllZones, + ]) + + useEffect(() => { + ConfigurationSavedEvent.listen(saveEvent) + return () => ConfigurationSavedEvent.removeListener(saveEvent) + }, [saveEvent]) + + useEffect(() => { + World.physicsSystem.holdPause(PAUSE_REF_ASSEMBLY_CONFIG) + return () => { + World.physicsSystem.releasePause(PAUSE_REF_ASSEMBLY_CONFIG) + } + }, []) + + const defaultGizmoMesh = useMemo(() => { + if (!selectedZone) return undefined + return new THREE.Mesh(new THREE.BoxGeometry(1, 1, 1), getAllianceMaterial(selectedZone.alliance, materials)) + }, [selectedZone, selectedZone?.alliance, materials]) + + const postGizmoCreation = useCallback( + (gizmo: GizmoSceneObject) => { + const material = (gizmo.obj as THREE.Mesh).material as THREE.Material + material.depthTest = true + + const deltaTransformation = convertArrayToThreeMatrix4(selectedZone.deltaTransformation) + let nodeBodyId = selectedField.mechanism.nodeToBody.get(selectedZone.parentNode ?? selectedField.rootNodeId) + if (!nodeBodyId) { + nodeBodyId = selectedField.mechanism.nodeToBody.get(selectedField.rootNodeId)! + } + + const fieldTransformation = convertJoltMat44ToThreeMatrix4( + World.physicsSystem.getBody(nodeBodyId).GetWorldTransform() + ) + const props = deltaFieldTransformsPhysicalProp(deltaTransformation, fieldTransformation) + + gizmo.obj.position.set(props.translation.x, props.translation.y, props.translation.z) + gizmo.obj.rotation.setFromQuaternion(props.rotation) + gizmo.obj.scale.set(props.scale.x, props.scale.y, props.scale.z) + + removeZoneObject(selectedField, selectedZone) + }, + [selectedField, selectedZone, removeZoneObject] + ) + + const gizmoComponent = useMemo(() => { + if (selectedField && selectedZone) { + return ( + + ) + } else { + gizmoRef.current = undefined + return <> + } + }, [selectedField, selectedZone, defaultGizmoMesh, postGizmoCreation]) + + const trySetSelectedNode = useCallback( + (body: Jolt.BodyID) => { + if (!selectedField) return false + const assoc = World.physicsSystem.getBodyAssociation(body) as RigidNodeAssociate + if (!assoc || assoc?.sceneObject !== selectedField) return false + setSelectedNode(assoc.rigidNodeId) + return true + }, + [selectedField] + ) + + return ( + + setName(e.target.value)} + /> + + trySetSelectedNode(body.GetID())} + /> + {props.children} + {gizmoComponent} + + ) +}