From f5e05733f617ea72ca910656251a422bb43140ad Mon Sep 17 00:00:00 2001 From: Alexey Dmitriev <157652245+AlexD717@users.noreply.github.com> Date: Wed, 13 Aug 2025 09:53:10 -0700 Subject: [PATCH 01/10] Simple Refactor --- .../scoring/ManageProtectedZonesInterface.tsx | 141 ++------- .../scoring/ManageScoringZonesInterface.tsx | 146 ++------- .../scoring/ProtectedZoneConfigInterface.tsx | 294 ++---------------- .../scoring/ScoringZoneConfigInterface.tsx | 273 ++-------------- .../interfaces/zones/ManageZonesBase.tsx | 117 +++++++ .../interfaces/zones/ZoneConfigBase.tsx | 238 ++++++++++++++ 6 files changed, 459 insertions(+), 750 deletions(-) create mode 100644 fission/src/ui/panels/configuring/assembly-config/interfaces/zones/ManageZonesBase.tsx create mode 100644 fission/src/ui/panels/configuring/assembly-config/interfaces/zones/ZoneConfigBase.tsx diff --git a/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ManageProtectedZonesInterface.tsx b/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ManageProtectedZonesInterface.tsx index 47151ecaf8..2d712de70f 100644 --- a/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ManageProtectedZonesInterface.tsx +++ b/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ManageProtectedZonesInterface.tsx @@ -1,60 +1,15 @@ -import { Stack } from "@mui/material" -import { useCallback, useEffect, useState } from "react" -import { ConfigurationSavedEvent } from "@/events/ConfigurationSavedEvent" import type MirabufSceneObject from "@/mirabuf/MirabufSceneObject" import { ContactType } from "@/mirabuf/ZoneTypes" import { MatchModeType } from "@/systems/match_mode/MatchModeTypes" -import { PAUSE_REF_ASSEMBLY_CONFIG } from "@/systems/physics/PhysicsTypes" -import PreferencesSystem from "@/systems/preferences/PreferencesSystem" import type { ProtectedZonePreferences } 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" - -const saveZones = (zones: ProtectedZonePreferences[] | undefined, field: MirabufSceneObject | undefined) => { - if (!zones || !field) return +import ManageZonesBase, { type ManageZonesBaseProps } from "../zones/ManageZonesBase" +function persistProtectedZones(zones: ProtectedZonePreferences[], field: MirabufSceneObject) { const fieldPrefs = field.fieldPreferences if (fieldPrefs) fieldPrefs.protectedZones = zones - - PreferencesSystem.savePreferences() field.updateProtectedZones() } -type ProtectedZoneRowProps = { - zone: ProtectedZonePreferences - save: () => void - deleteZone: () => void - selectZone: (zone: ProtectedZonePreferences) => void -} - -const ProtectedZoneRow: React.FC = ({ zone, save, deleteZone, selectZone }) => { - return ( - - -
- - - - - - - {EditButton(() => { - selectZone(zone) - save() - })} - - {DeleteButton(() => { - deleteZone() - })} - - - ) -} - interface ProtectedZonesProps { selectedField: MirabufSceneObject initialZones: ProtectedZonePreferences[] @@ -62,76 +17,28 @@ interface ProtectedZonesProps { } const ManageZonesInterface: React.FC = ({ selectedField, initialZones, selectZone }) => { - const [zones, setZones] = useState(initialZones) - - const saveEvent = useCallback(() => { - saveZones(zones, selectedField) - }, [zones, selectedField]) - - useEffect(() => { - ConfigurationSavedEvent.listen(saveEvent) - - return () => { - ConfigurationSavedEvent.removeListener(saveEvent) - } - }, [saveEvent]) - - useEffect(() => { - saveZones(zones, selectedField) - - World.physicsSystem.holdPause(PAUSE_REF_ASSEMBLY_CONFIG) - - return () => { - World.physicsSystem.releasePause(PAUSE_REF_ASSEMBLY_CONFIG) - } - }, [selectedField, zones]) - - return ( - <> - {zones?.length > 0 ? ( - - - {zones.map((zonePrefs: ProtectedZonePreferences, i: number) => ( - { - return zonePrefs - })()} - save={() => saveZones(zones, selectedField)} - deleteZone={() => { - setZones(zones.filter((_, idx) => idx !== i)) - saveZones( - zones.filter((_, idx) => idx !== i), - selectedField - ) - }} - selectZone={selectZone} - /> - ))} - - - ) : ( - - )} - {AddButton(() => { - if (zones === undefined) return - - const newZone: ProtectedZonePreferences = { - name: "New Protected Zone", - alliance: "blue", - penaltyPoints: 5, - parentNode: undefined, - contactType: ContactType.ROBOT_ENTERS, - activeDuring: [MatchModeType.AUTONOMOUS, MatchModeType.TELEOP, MatchModeType.ENDGAME], - deltaTransformation: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], - } - - saveZones(zones, selectedField) - - selectZone(newZone) - })} - - ) + const baseProps: ManageZonesBaseProps = { + selectedField, + initialZones, + selectZone, + getListItem: zone => ({ + name: zone.name, + alliance: zone.alliance, + pointsLabel: `${zone.penaltyPoints} ${zone.penaltyPoints === 1 ? "penalty point" : "penalty points"}`, + }), + persistZones: persistProtectedZones, + createNewZone: () => ({ + name: "New Protected Zone", + alliance: "blue", + penaltyPoints: 5, + parentNode: undefined, + contactType: ContactType.ROBOT_ENTERS, + activeDuring: [MatchModeType.AUTONOMOUS, MatchModeType.TELEOP, MatchModeType.ENDGAME], + deltaTransformation: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], + }), + } + + return } export default ManageZonesInterface diff --git a/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ManageScoringZonesInterface.tsx b/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ManageScoringZonesInterface.tsx index e6c22c6933..d913e4e986 100644 --- a/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ManageScoringZonesInterface.tsx +++ b/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ManageScoringZonesInterface.tsx @@ -1,63 +1,13 @@ -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 { ScoringZonePreferences } 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" - -const saveZones = (zones: ScoringZonePreferences[] | undefined, field: MirabufSceneObject | undefined) => { - if (!zones || !field) return +import ManageZonesBase, { type ManageZonesBaseProps } from "../zones/ManageZonesBase" +function persistScoringZones(zones: ScoringZonePreferences[], field: MirabufSceneObject) { const fieldPrefs = field.fieldPreferences if (fieldPrefs) fieldPrefs.scoringZones = zones - - PreferencesSystem.savePreferences() field.updateScoringZones() } -type ScoringZoneRowProps = { - zone: ScoringZonePreferences - save: () => void - deleteZone: () => void - selectZone: (zone: ScoringZonePreferences) => void -} - -const ScoringZoneRow: React.FC = ({ zone, save, deleteZone, selectZone }) => { - return ( - - - - - - - - - - {EditButton(() => { - selectZone(zone) - save() - })} - - {DeleteButton(() => { - deleteZone() - })} - - - ) -} - interface ScoringZonesProps { selectedField: MirabufSceneObject initialZones: ScoringZonePreferences[] @@ -65,76 +15,28 @@ interface ScoringZonesProps { } const ManageZonesInterface: React.FC = ({ selectedField, initialZones, selectZone }) => { - const [zones, setZones] = useState(initialZones) - - const saveEvent = useCallback(() => { - saveZones(zones, selectedField) - }, [zones, selectedField]) - - useEffect(() => { - ConfigurationSavedEvent.listen(saveEvent) - - return () => { - ConfigurationSavedEvent.removeListener(saveEvent) - } - }, [saveEvent]) - - useEffect(() => { - saveZones(zones, selectedField) - - World.physicsSystem.holdPause(PAUSE_REF_ASSEMBLY_CONFIG) - - return () => { - World.physicsSystem.releasePause(PAUSE_REF_ASSEMBLY_CONFIG) - } - }, [selectedField, zones]) - - return ( - <> - {zones?.length > 0 ? ( - - - {zones.map((zonePrefs: ScoringZonePreferences, i: number) => ( - { - return zonePrefs - })()} - save={() => saveZones(zones, selectedField)} - deleteZone={() => { - setZones(zones.filter((_, idx) => idx !== i)) - saveZones( - zones.filter((_, idx) => idx !== i), - selectedField - ) - }} - selectZone={selectZone} - /> - ))} - - - ) : ( - - )} - {AddButton(() => { - if (zones === undefined) return - - const newZone: ScoringZonePreferences = { - name: "New Scoring Zone", - alliance: "blue", - parentNode: undefined, - points: 0, - destroyGamepiece: false, - persistentPoints: false, - deltaTransformation: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], - } - - saveZones(zones, selectedField) - - selectZone(newZone) - })} - - ) + const baseProps: ManageZonesBaseProps = { + selectedField, + initialZones, + selectZone, + getListItem: zone => ({ + name: zone.name, + alliance: zone.alliance, + pointsLabel: `${zone.points} ${zone.points === 1 ? "point" : "points"}`, + }), + persistZones: persistScoringZones, + createNewZone: () => ({ + name: "New Scoring Zone", + alliance: "blue", + parentNode: undefined, + points: 5, + destroyGamepiece: false, + persistentPoints: false, + deltaTransformation: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], + }), + } + + return } export default ManageZonesInterface diff --git a/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ProtectedZoneConfigInterface.tsx b/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ProtectedZoneConfigInterface.tsx index 2354155797..f7319c4bde 100644 --- a/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ProtectedZoneConfigInterface.tsx +++ b/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ProtectedZoneConfigInterface.tsx @@ -1,38 +1,21 @@ -import type Jolt from "@azaleacolburn/jolt-physics" import { - Button, - Checkbox, + Checkbox as MuiCheckbox, FormControl, InputLabel, ListItemText, MenuItem, OutlinedInput, Select, - 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 { useState } from "react" +import type * as THREE from "three" import ProtectedZoneSceneObject from "@/mirabuf/ProtectedZoneSceneObject" import { ContactType } from "@/mirabuf/ZoneTypes" import { MatchModeType } from "@/systems/match_mode/MatchModeTypes" -import { PAUSE_REF_ASSEMBLY_CONFIG } from "@/systems/physics/PhysicsTypes" -import PreferencesSystem from "@/systems/preferences/PreferencesSystem" -import type { Alliance, ProtectedZonePreferences } 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" +import type MirabufSceneObject from "@/mirabuf/MirabufSceneObject" +import type { ProtectedZonePreferences } from "@/systems/preferences/PreferenceTypes" +import ZoneConfigBase from "../zones/ZoneConfigBase" const MATCH_MODE_OPTIONS: MatchModeType[] = [ MatchModeType.SANDBOX, @@ -43,80 +26,9 @@ const MATCH_MODE_OPTIONS: MatchModeType[] = [ const CONTACT_TYPE_OPTIONS = Object.values(ContactType) -/** - * 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 protected zone by the user. - * @param alliance protected zone alliance. - * @param points Number of points to penalize. - * @param requireRobotContact Do you need to contact a robot for the penalty to apply. - * @param activeDuring Array of match mode types during which the zone is active. - * @param gizmo Reference to the transform gizmo object. - * @param selectedNode Selected node that configuration is relative to. - */ -function save( - field: MirabufSceneObject, - zone: ProtectedZonePreferences, - name: string, - alliance: Alliance, - points: number, - contactType: ContactType, - activeDuring: MatchModeType[], - 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.penaltyPoints = points - zone.contactType = contactType - zone.activeDuring = activeDuring - +function attachAndPersistZone(zone: ProtectedZonePreferences, field: MirabufSceneObject) { + if (!field?.fieldPreferences) return if (!field.fieldPreferences.protectedZones.includes(zone)) field.fieldPreferences.protectedZones.push(zone) - - PreferencesSystem.savePreferences() } interface ZoneConfigProps { @@ -126,173 +38,37 @@ interface ZoneConfigProps { } const ZoneConfigInterface: React.FC = ({ selectedField, selectedZone, saveAllZones }) => { - // TODO: Do we want to eventually make these editable? - const redMaterial = useMemo(() => { - return ProtectedZoneSceneObject.redMaterial.clone() as THREE.MeshPhongMaterial - }, []) - - const blueMaterial = useMemo(() => { - return ProtectedZoneSceneObject.blueMaterial.clone() as THREE.MeshPhongMaterial - }, []) - - const [name, setName] = useState(selectedZone.name) - const [alliance, setAlliance] = useState(selectedZone.alliance) - const [selectedNode, setSelectedNode] = useState(selectedZone.parentNode) - const [points, setPoints] = useState(selectedZone.penaltyPoints) + const [penaltyPoints, setPenaltyPoints] = useState(selectedZone.penaltyPoints) const [contactType, setContactType] = useState(selectedZone.contactType || ContactType.ROBOT_ENTERS) const [activeDuring, setActiveDuring] = useState(selectedZone.activeDuring) - const gizmoRef = useRef(undefined) - - const saveEvent = useCallback(() => { - if (gizmoRef.current && selectedField) { - save( - selectedField, - selectedZone, - name, - alliance, - points, - contactType, - activeDuring, - gizmoRef.current, - selectedNode - ) - saveAllZones() - } - }, [selectedField, selectedZone, name, alliance, points, contactType, activeDuring, 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 = 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) - selectedField.removeProtectedZoneObject(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 - }, - [selectedField] - ) + // Use the cloned FIRST materials like before + const materials = { + red: ProtectedZoneSceneObject.redMaterial.clone() as THREE.MeshPhongMaterial, + blue: ProtectedZoneSceneObject.blueMaterial.clone() as THREE.MeshPhongMaterial, + } return ( - - {/** Set the zone name */} - setName(e.target.value)} - /> - - {/** Set the alliance color */} - - - {/** Select a parent node */} - trySetSelectedNode(body.GetID())} - /> - - {/** Set the point value */} + { + zone.penaltyPoints = penaltyPoints + zone.contactType = contactType + zone.activeDuring = activeDuring + }} + removeZoneObject={(field, zone) => field.removeProtectedZoneObject(zone)} + saveAllZones={saveAllZones} + materials={materials} + > 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} - + ) } 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 ae57f6e878..cf0ff6e653 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,25 +1,9 @@ -import type Jolt from "@azaleacolburn/jolt-physics" -import { Button, 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 { TextField } from "@mui/material" +import { useState } from "react" import Checkbox from "@/ui/components/Checkbox" -import SelectButton from "@/ui/components/SelectButton" -import TransformGizmoControl from "@/ui/components/TransformGizmoControl" -import { - convertArrayToThreeMatrix4, - convertJoltMat44ToThreeMatrix4, - convertThreeMatrix4ToArray, -} from "@/util/TypeConversions" -import { deltaFieldTransformsPhysicalProp as deltaFieldTransformsVisualProperties } from "@/util/threejs/MeshCreation" +import type MirabufSceneObject from "@/mirabuf/MirabufSceneObject" +import type { ScoringZonePreferences } from "@/systems/preferences/PreferenceTypes" +import ZoneConfigBase from "../zones/ZoneConfigBase" /** * Saves ejector configuration to selected field. @@ -47,54 +31,9 @@ import { deltaFieldTransformsPhysicalProp as deltaFieldTransformsVisualPropertie * @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,197 +43,33 @@ interface ZoneConfigProps { } 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 [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 - }, - [selectedField] - ) - return ( -
- {/** Set the zone name */} - setName(e.target.value)} - /> - - {/** Set the alliance color */} - - - {/** Select a parent node */} - trySetSelectedNode(body.GetID())} - /> - - {/** Set the point value */} + { + zone.points = zone.points ?? selectedZone.points + zone.destroyGamepiece = selectedZone.destroyGamepiece + zone.persistentPoints = persistent + }} + removeZoneObject={(field, zone) => field.removeScoringZoneObject(zone)} + saveAllZones={saveAllZones} + > setPoints(parseInt(v.target.value) || 1)} + onChange={v => { + const parsed = parseInt(v.target.value) + selectedZone.points = Number.isNaN(parsed) ? 1 : parsed + }} /> - - {/** 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} -
+ ) } 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..510fd46e0d --- /dev/null +++ b/fission/src/ui/panels/configuring/assembly-config/interfaces/zones/ZoneConfigBase.tsx @@ -0,0 +1,238 @@ +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" + +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 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) { + 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) + } + + return ( + + ) + } else { + gizmoRef.current = undefined + return <> + } + }, [selectedField, selectedZone, defaultGizmoMesh, removeZoneObject]) + + 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} + + ) +} From 2d7c70c7a6d1cdf4624d8ac2ea7db1f59674c9da Mon Sep 17 00:00:00 2001 From: Alexey Dmitriev <157652245+AlexD717@users.noreply.github.com> Date: Fri, 15 Aug 2025 08:59:17 -0700 Subject: [PATCH 02/10] Comments --- .../scoring/ProtectedZoneConfigInterface.tsx | 6 +++++ .../scoring/ScoringZoneConfigInterface.tsx | 20 +-------------- .../interfaces/zones/ZoneConfigBase.tsx | 25 +++++++++++++++++++ 3 files changed, 32 insertions(+), 19 deletions(-) diff --git a/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ProtectedZoneConfigInterface.tsx b/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ProtectedZoneConfigInterface.tsx index f7319c4bde..e4165fa5bf 100644 --- a/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ProtectedZoneConfigInterface.tsx +++ b/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ProtectedZoneConfigInterface.tsx @@ -17,6 +17,12 @@ import type MirabufSceneObject from "@/mirabuf/MirabufSceneObject" import type { ProtectedZonePreferences } from "@/systems/preferences/PreferenceTypes" import ZoneConfigBase from "../zones/ZoneConfigBase" +/** + * @param penaltyPoints Number of points the zone is worth. + * @param contactType Contact type of the zone. + * @param activeDuring Match modes during which the zone is active. + */ + const MATCH_MODE_OPTIONS: MatchModeType[] = [ MatchModeType.SANDBOX, MatchModeType.AUTONOMOUS, 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 2317bec5ad..5abf4a2955 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 @@ -6,29 +6,11 @@ import type { ScoringZonePreferences } from "@/systems/preferences/PreferenceTyp import ZoneConfigBase from "../zones/ZoneConfigBase" /** - * 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. */ + function attachAndPersistZone(zone: ScoringZonePreferences, field: MirabufSceneObject) { if (!field?.fieldPreferences) return if (!field.fieldPreferences.scoringZones.includes(zone)) field.fieldPreferences.scoringZones.push(zone) 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 index 510fd46e0d..8f03404707 100644 --- a/fission/src/ui/panels/configuring/assembly-config/interfaces/zones/ZoneConfigBase.tsx +++ b/fission/src/ui/panels/configuring/assembly-config/interfaces/zones/ZoneConfigBase.tsx @@ -20,6 +20,31 @@ import { } 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 From b258fdf698d4d9374d978f853d02d02cbe014cf0 Mon Sep 17 00:00:00 2001 From: Alexey Dmitriev <157652245+AlexD717@users.noreply.github.com> Date: Fri, 15 Aug 2025 09:15:15 -0700 Subject: [PATCH 03/10] Formatting --- .../assembly-config/interfaces/zones/ZoneConfigBase.tsx | 1 - 1 file changed, 1 deletion(-) 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 index 8f03404707..abf242b2ca 100644 --- a/fission/src/ui/panels/configuring/assembly-config/interfaces/zones/ZoneConfigBase.tsx +++ b/fission/src/ui/panels/configuring/assembly-config/interfaces/zones/ZoneConfigBase.tsx @@ -20,7 +20,6 @@ import { } from "@/util/TypeConversions" import { deltaFieldTransformsPhysicalProp } from "@/util/threejs/MeshCreation" - /** * Saves zone configuration to selected field. * From 606686742f88fc1b3439d6154c364202e07c8d2e Mon Sep 17 00:00:00 2001 From: Alexey Dmitriev <157652245+AlexD717@users.noreply.github.com> Date: Fri, 15 Aug 2025 12:14:40 -0700 Subject: [PATCH 04/10] PR Feedback --- fission/src/mirabuf/MirabufSceneObject.ts | 71 +++++++++++-------- .../ConfigureProtectedZonesInterface.tsx | 8 +-- .../ConfigureScoringZonesInterface.tsx | 4 +- .../scoring/ManageProtectedZonesInterface.tsx | 4 +- .../scoring/ManageScoringZonesInterface.tsx | 4 +- .../scoring/ProtectedZoneConfigInterface.tsx | 37 +++++----- .../scoring/ScoringZoneConfigInterface.tsx | 29 ++++---- .../interfaces/zones/ZoneConfigBase.tsx | 44 ++++++------ 8 files changed, 112 insertions(+), 89 deletions(-) diff --git a/fission/src/mirabuf/MirabufSceneObject.ts b/fission/src/mirabuf/MirabufSceneObject.ts index be780b0436..868862aed6 100644 --- a/fission/src/mirabuf/MirabufSceneObject.ts +++ b/fission/src/mirabuf/MirabufSceneObject.ts @@ -600,22 +600,28 @@ class MirabufSceneObject extends SceneObject implements ContextSupplier { return true } - public updateScoringZones(render?: boolean) { - this._scoringZones.filter(zone => zone.id != -1).forEach(zone => World.sceneRenderer.removeSceneObject(zone.id)) - this._scoringZones = [] - - if (this._fieldPreferences && this._fieldPreferences.scoringZones) { - for (let i = 0; i < this._fieldPreferences.scoringZones.length; i++) { - const newZone = new ScoringZoneSceneObject( - this, - i, - render ?? PreferencesSystem.getGlobalPreference("RenderScoringZones") - ) - this._scoringZones.push(newZone) - World.sceneRenderer.registerSceneObject(newZone) - } - } - } + public updateScoringZones(render?: boolean) { + this._scoringZones.filter(zone => zone.id != -1).forEach(zone => World.sceneRenderer.removeSceneObject(zone.id)) + this._scoringZones = [] + + if (this._fieldPreferences && this._fieldPreferences.scoringZones) { + // Auto-sync devtool data so scoring zones persist across reloads + const parts = this._mirabufInstance.parser.assembly.data?.parts + if (parts) { + const editor = new FieldMiraEditor(parts) + editor.setUserData("devtool:scoring_zones", this._fieldPreferences.scoringZones) + } + for (let i = 0; i < this._fieldPreferences.scoringZones.length; i++) { + const newZone = new ScoringZoneSceneObject( + this, + i, + render ?? PreferencesSystem.getGlobalPreference("RenderScoringZones") + ) + this._scoringZones.push(newZone) + World.sceneRenderer.registerSceneObject(newZone) + } + } + } public updateProtectedZones(render?: boolean) { this._protectedZones @@ -809,18 +815,27 @@ class MirabufSceneObject extends SceneObject implements ContextSupplier { this._fieldPreferences = PreferencesSystem.getFieldPreferences(this.assemblyName) - // For fields, sync devtool data with field preferences - if (this.miraType === MiraType.FIELD) { - const parts = this._mirabufInstance.parser.assembly.data?.parts - if (parts) { - const editor = new FieldMiraEditor(parts) - devtoolKeys.forEach(key => { - devtoolHandlers[key].set(this, editor.getUserData(key)) - }) - PreferencesSystem.setFieldPreferences(this.assemblyName, this._fieldPreferences) - PreferencesSystem.savePreferences() - } - } + // For fields, sync devtool data with field preferences + if (this.miraType === MiraType.FIELD) { + const parts = this._mirabufInstance.parser.assembly.data?.parts + if (parts) { + const editor = new FieldMiraEditor(parts) + // First, push current preferences into devtool so handlers don't overwrite saved prefs + if (this._fieldPreferences) { + if (this._fieldPreferences.scoringZones && this._fieldPreferences.scoringZones.length > 0) { + editor.setUserData("devtool:scoring_zones", this._fieldPreferences.scoringZones) + } + if (this._fieldPreferences.spawnLocations?.hasConfiguredLocations) { + editor.setUserData("devtool:spawn_locations", this._fieldPreferences.spawnLocations) + } + } + devtoolKeys.forEach(key => { + devtoolHandlers[key].set(this, editor.getUserData(key)) + }) + PreferencesSystem.setFieldPreferences(this.assemblyName, this._fieldPreferences) + PreferencesSystem.savePreferences() + } + } } public updateSimConfig(config: SimConfigData | undefined) { 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 9b5ea175d2..961fe0ad09 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 @@ -9,7 +9,7 @@ import type { ScoringZonePreferences } from "@/systems/preferences/PreferenceTyp import Label from "@/ui/components/Label" import { 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) => { if (!zones || !field) return @@ -60,7 +60,7 @@ const ConfigureScoringZonesInterface: React.FC = ({ selecte - { diff --git a/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ManageProtectedZonesInterface.tsx b/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ManageProtectedZonesInterface.tsx index 2d712de70f..e96220e87d 100644 --- a/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ManageProtectedZonesInterface.tsx +++ b/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ManageProtectedZonesInterface.tsx @@ -16,7 +16,7 @@ interface ProtectedZonesProps { selectZone: (zone: ProtectedZonePreferences) => void } -const ManageZonesInterface: React.FC = ({ selectedField, initialZones, selectZone }) => { +const ManageProtectedZonesInterface: React.FC = ({ selectedField, initialZones, selectZone }) => { const baseProps: ManageZonesBaseProps = { selectedField, initialZones, @@ -41,4 +41,4 @@ const ManageZonesInterface: React.FC = ({ selectedField, in return } -export default ManageZonesInterface +export default ManageProtectedZonesInterface diff --git a/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ManageScoringZonesInterface.tsx b/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ManageScoringZonesInterface.tsx index d913e4e986..344d74c310 100644 --- a/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ManageScoringZonesInterface.tsx +++ b/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ManageScoringZonesInterface.tsx @@ -14,7 +14,7 @@ interface ScoringZonesProps { selectZone: (zone: ScoringZonePreferences) => void } -const ManageZonesInterface: React.FC = ({ selectedField, initialZones, selectZone }) => { +const ManageScoringZonesInterface: React.FC = ({ selectedField, initialZones, selectZone }) => { const baseProps: ManageZonesBaseProps = { selectedField, initialZones, @@ -39,4 +39,4 @@ const ManageZonesInterface: React.FC = ({ selectedField, init return } -export default ManageZonesInterface +export default ManageScoringZonesInterface diff --git a/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ProtectedZoneConfigInterface.tsx b/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ProtectedZoneConfigInterface.tsx index e4165fa5bf..8c956a2c2f 100644 --- a/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ProtectedZoneConfigInterface.tsx +++ b/fission/src/ui/panels/configuring/assembly-config/interfaces/scoring/ProtectedZoneConfigInterface.tsx @@ -8,8 +8,7 @@ import { Select, TextField, } from "@mui/material" -import { useState } from "react" -import type * as THREE from "three" +import { useState, useCallback, useMemo } from "react" import ProtectedZoneSceneObject from "@/mirabuf/ProtectedZoneSceneObject" import { ContactType } from "@/mirabuf/ZoneTypes" import { MatchModeType } from "@/systems/match_mode/MatchModeTypes" @@ -43,28 +42,34 @@ interface ZoneConfigProps { saveAllZones: () => void } -const ZoneConfigInterface: React.FC = ({ selectedField, selectedZone, saveAllZones }) => { +const ProtectedZoneConfigInterface: React.FC = ({ selectedField, selectedZone, saveAllZones }) => { const [penaltyPoints, setPenaltyPoints] = useState(selectedZone.penaltyPoints) const [contactType, setContactType] = useState(selectedZone.contactType || ContactType.ROBOT_ENTERS) const [activeDuring, setActiveDuring] = useState(selectedZone.activeDuring) // Use the cloned FIRST materials like before - const materials = { - red: ProtectedZoneSceneObject.redMaterial.clone() as THREE.MeshPhongMaterial, - blue: ProtectedZoneSceneObject.blueMaterial.clone() as THREE.MeshPhongMaterial, - } + const materials = useMemo(() => ({ + red: ProtectedZoneSceneObject.redMaterial.clone(), + blue: ProtectedZoneSceneObject.blueMaterial.clone(), + }), []) + + const applyExtrasOnSave = useCallback((zone: ProtectedZonePreferences) => { + zone.penaltyPoints = penaltyPoints + zone.contactType = contactType + zone.activeDuring = activeDuring + }, [penaltyPoints, contactType, activeDuring]) + + const removeZoneObject = useCallback((field: MirabufSceneObject, zone: ProtectedZonePreferences) => { + field.removeProtectedZoneObject(zone) + }, []) return ( { - zone.penaltyPoints = penaltyPoints - zone.contactType = contactType - zone.activeDuring = activeDuring - }} - removeZoneObject={(field, zone) => field.removeProtectedZoneObject(zone)} + applyExtrasOnSave={applyExtrasOnSave} + removeZoneObject={removeZoneObject} saveAllZones={saveAllZones} materials={materials} > @@ -85,12 +90,12 @@ const ZoneConfigInterface: React.FC = ({ selectedField, selecte target: { value }, } = e setActiveDuring( - (typeof value === "string" ? (value as string).split(",") : value) as MatchModeType[] + typeof value === "string" ? value.split(",") as MatchModeType[] : value ) }} value={activeDuring} input={} - renderValue={selected => (selected as MatchModeType[]).join(", ")} + renderValue={selected => selected.join(", ")} multiple > {MATCH_MODE_OPTIONS.map(opt => ( @@ -119,4 +124,4 @@ const ZoneConfigInterface: React.FC = ({ selectedField, selecte ) } -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 5abf4a2955..53dfc9c444 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,5 +1,5 @@ import { TextField } from "@mui/material" -import { useState } from "react" +import { useState, useCallback } from "react" import Checkbox from "@/ui/components/Checkbox" import type MirabufSceneObject from "@/mirabuf/MirabufSceneObject" import type { ScoringZonePreferences } from "@/systems/preferences/PreferenceTypes" @@ -22,20 +22,26 @@ interface ZoneConfigProps { saveAllZones: () => void } -const ZoneConfigInterface: React.FC = ({ selectedField, selectedZone, saveAllZones }) => { +const ScoringZoneConfigInterface: React.FC = ({ selectedField, selectedZone, saveAllZones }) => { + const [points, setPoints] = useState(selectedZone.points) const [persistent, setPersistent] = useState(selectedZone.persistentPoints) + const applyExtrasOnSave = useCallback((zone: ScoringZonePreferences) => { + zone.points = points + zone.persistentPoints = persistent + }, [points, persistent]) + + const removeZoneObject = useCallback((field: MirabufSceneObject, zone: ScoringZonePreferences) => { + field.removeScoringZoneObject(zone) + }, []) + return ( { - zone.points = zone.points ?? selectedZone.points - zone.destroyGamepiece = selectedZone.destroyGamepiece - zone.persistentPoints = persistent - }} - removeZoneObject={(field, zone) => field.removeScoringZoneObject(zone)} + applyExtrasOnSave={applyExtrasOnSave} + removeZoneObject={removeZoneObject} saveAllZones={saveAllZones} > = ({ selectedField, selecte label="Points" placeholder="Zone points" defaultValue={selectedZone.points} - onChange={v => { - const parsed = parseInt(v.target.value) - selectedZone.points = Number.isNaN(parsed) ? 1 : parsed - }} + onChange={v => setPoints(parseInt(v.target.value) || 1)} /> setPersistent(checked)} /> ) } -export default ZoneConfigInterface +export default ScoringZoneConfigInterface 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 index abf242b2ca..6f4f15c68c 100644 --- a/fission/src/ui/panels/configuring/assembly-config/interfaces/zones/ZoneConfigBase.tsx +++ b/fission/src/ui/panels/configuring/assembly-config/interfaces/zones/ZoneConfigBase.tsx @@ -178,32 +178,32 @@ export default function ZoneConfigBase(props: return new THREE.Mesh(new THREE.BoxGeometry(1, 1, 1), getAllianceMaterial(selectedZone.alliance, materials)) }, [selectedZone, selectedZone?.alliance, materials]) - 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 postGizmoCreation = useCallback((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) { - nodeBodyId = selectedField.mechanism.nodeToBody.get(selectedField.rootNodeId)! - } + 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) + 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) + 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) - } + removeZoneObject(selectedField, selectedZone) + }, [selectedField, selectedZone, removeZoneObject]) + const gizmoComponent = useMemo(() => { + if (selectedField && selectedZone) { return ( (props: gizmoRef.current = undefined return <> } - }, [selectedField, selectedZone, defaultGizmoMesh, removeZoneObject]) + }, [selectedField, selectedZone, defaultGizmoMesh, postGizmoCreation]) const trySetSelectedNode = useCallback( (body: Jolt.BodyID) => { From 5ef37d2863feecdbc29183a0adca9494a9f67ecf Mon Sep 17 00:00:00 2001 From: Alexey Dmitriev <157652245+AlexD717@users.noreply.github.com> Date: Fri, 15 Aug 2025 12:42:40 -0700 Subject: [PATCH 05/10] Proper Material & Unnecessary Button Removed --- .../scoring/ConfigureScoringZonesInterface.tsx | 17 ++--------------- .../interfaces/zones/ZoneConfigBase.tsx | 2 +- 2 files changed, 3 insertions(+), 16 deletions(-) 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 961fe0ad09..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,17 +1,14 @@ import { Box, Divider, Stack } from "@mui/material" -import { Button } from "@/ui/components/StyledComponents" 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 { SynthesisIcons } from "@/ui/components/StyledComponents" import ManageScoringZonesInterface from "./ManageScoringZonesInterface" 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 @@ -41,16 +38,6 @@ const ConfigureScoringZonesInterface: React.FC = ({ selecte <> - - {/** Back arrow button when an option is selected */} -