From 2c0f9184dbaeb7ede37617ae97445599370a07a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Wed, 31 Jul 2024 13:11:25 -0500 Subject: [PATCH] feat: better plate editing integration --- .../tankopedia/[id]/components/Prompts.tsx | 96 +++++++++++++++++++ .../[id]/components/Thicknesses.tsx | 37 ++++++- src/app/tools/tankopedia/[id]/page.tsx | 3 + .../Armor/components/ArmorPlateDisplay.tsx | 60 +----------- .../SpacedArmorSceneComponent/index.tsx | 70 ++++++++------ src/stores/tankopediaEphemeral.ts | 3 + 6 files changed, 181 insertions(+), 88 deletions(-) create mode 100644 src/app/tools/tankopedia/[id]/components/Prompts.tsx diff --git a/src/app/tools/tankopedia/[id]/components/Prompts.tsx b/src/app/tools/tankopedia/[id]/components/Prompts.tsx new file mode 100644 index 000000000..118a83594 --- /dev/null +++ b/src/app/tools/tankopedia/[id]/components/Prompts.tsx @@ -0,0 +1,96 @@ +import { Button, Dialog, Flex, Text, TextField } from '@radix-ui/themes'; +import { useRef } from 'react'; +import { ModelArmor } from '../../../../../core/blitzkit/modelDefinitions'; +import { resolveArmorIndex } from '../../../../../core/blitzkit/resolveArmorIndex'; +import * as Duel from '../../../../../stores/duel'; +import * as TankopediaEphemeral from '../../../../../stores/tankopediaEphemeral'; + +export function Prompts() { + const input = useRef(null); + const mutateTankopediaEphemeral = TankopediaEphemeral.useMutation(); + const editingPlate = TankopediaEphemeral.use((state) => state.editingPlate); + const duelStore = Duel.useStore(); + + return ( + { + mutateTankopediaEphemeral((draft) => { + draft.editingPlate = undefined; + }); + }} + > + + + Edit thickness + + { + input.current?.select(); + }} + onKeyDown={(event) => { + if (!editingPlate) return; + + if (event.key === 'Enter') { + input.current?.blur(); + mutateTankopediaEphemeral((draft) => { + draft.editingPlate = undefined; + draft.highlightArmor = undefined; + const { protagonist } = duelStore.getState(); + let armor: ModelArmor; + + if (editingPlate.name.startsWith('hull_')) { + armor = draft.model.armor; + } else if (editingPlate.name.startsWith('turret_')) { + armor = draft.model.turrets[protagonist.turret.id].armor; + } else if (editingPlate.name.startsWith('gun_')) { + armor = + draft.model.turrets[protagonist.turret.id].guns[ + protagonist.gun.id + ].armor; + } else return; + + const index = resolveArmorIndex(editingPlate.name); + + if (index === undefined) return; + + const input = event.currentTarget.valueAsNumber; + + if (!isNaN(input)) { + armor.thickness[index] = input; + } + }); + } else if (event.key === 'Escape') { + input.current?.blur(); + mutateTankopediaEphemeral((draft) => { + draft.highlightArmor = undefined; + }); + } + }} + onClick={(event) => { + event.stopPropagation(); + event.preventDefault(); + }} + defaultValue={editingPlate?.default.toFixed(0)} + > + mm + + + + + + + + + + + + + + ); +} diff --git a/src/app/tools/tankopedia/[id]/components/Thicknesses.tsx b/src/app/tools/tankopedia/[id]/components/Thicknesses.tsx index a80ac6ef3..f80741e32 100644 --- a/src/app/tools/tankopedia/[id]/components/Thicknesses.tsx +++ b/src/app/tools/tankopedia/[id]/components/Thicknesses.tsx @@ -1,5 +1,5 @@ -import { EyeClosedIcon, EyeOpenIcon } from '@radix-ui/react-icons'; -import { Box, Flex, Text } from '@radix-ui/themes'; +import { EyeClosedIcon, EyeOpenIcon, ReloadIcon } from '@radix-ui/react-icons'; +import { Box, Button, Checkbox, Flex, Text } from '@radix-ui/themes'; import { ThicknessRange } from '../../../../../components/StaticArmor/components/StaticArmorScene'; import * as TankopediaEphemeral from '../../../../../stores/tankopediaEphemeral'; import * as TankopediaPersistent from '../../../../../stores/tankopediaPersistent'; @@ -20,6 +20,8 @@ export function Thicknesses({ thicknessRange }: ThicknessesProps) { ); const mutateTankopediaPersistent = TankopediaPersistent.useMutation(); const mutateTankopediaEphemeral = TankopediaEphemeral.useMutation(); + const editStatic = TankopediaEphemeral.use((state) => state.editStatic); + const tankopediaEphemeralStore = TankopediaEphemeral.useStore(); return ( + + + + + { + mutateTankopediaEphemeral((draft) => { + draft.editStatic = !draft.editStatic; + draft.highlightArmor = undefined; + }); + }} + > + Edit + + + ); } diff --git a/src/app/tools/tankopedia/[id]/page.tsx b/src/app/tools/tankopedia/[id]/page.tsx index 56033181f..54f16dffc 100644 --- a/src/app/tools/tankopedia/[id]/page.tsx +++ b/src/app/tools/tankopedia/[id]/page.tsx @@ -10,6 +10,7 @@ import { HistorySection } from './components/HistorySection'; import { CharacteristicsSection } from './components/Model/CharacteristicsSection'; import { HeroSection } from './components/Model/HeroSection'; import { TechTreeSection } from './components/Model/TechTreeSection'; +import { Prompts } from './components/Prompts'; import { ShotDisplaySection } from './components/ShotDisplaySection'; import { VideoSection } from './components/VideoSection'; @@ -74,6 +75,8 @@ export default function Page({ params }: { params: { id: string } }) { return ( + + {!exempt && } diff --git a/src/components/Armor/components/ArmorPlateDisplay.tsx b/src/components/Armor/components/ArmorPlateDisplay.tsx index 6917ae284..53f35989c 100644 --- a/src/components/Armor/components/ArmorPlateDisplay.tsx +++ b/src/components/Armor/components/ArmorPlateDisplay.tsx @@ -1,9 +1,7 @@ -import { Card, Code, Flex, Text, TextField } from '@radix-ui/themes'; +import { Card, Code, Flex, Text } from '@radix-ui/themes'; import { Html } from '@react-three/drei'; -import { useEffect, useRef } from 'react'; +import { useRef } from 'react'; import { radToDeg } from 'three/src/math/MathUtils'; -import { ModelArmor } from '../../../core/blitzkit/modelDefinitions'; -import { resolveArmorIndex } from '../../../core/blitzkit/resolveArmorIndex'; import * as App from '../../../stores/app'; import * as Duel from '../../../stores/duel'; import * as TankopediaEphemeral from '../../../stores/tankopediaEphemeral'; @@ -19,10 +17,6 @@ export function ArmorPlateDisplay() { const devThicknessInput = useRef(null); const duelStore = Duel.useStore(); - useEffect(() => { - devThicknessInput.current?.focus(); - }); - if (highlightArmor === undefined) return null; return ( @@ -52,60 +46,12 @@ export function ArmorPlateDisplay() { )} + {developerMode && ( <> DEV: {highlightArmor.name} - { - if (event.key === 'Enter') { - devThicknessInput.current?.blur(); - mutateTankopediaEphemeral((draft) => { - draft.highlightArmor = undefined; - const { protagonist } = duelStore.getState(); - let armor: ModelArmor; - - if (highlightArmor.name.startsWith('hull_')) { - armor = draft.model.armor; - } else if (highlightArmor.name.startsWith('turret_')) { - armor = - draft.model.turrets[protagonist.turret.id].armor; - } else if (highlightArmor.name.startsWith('gun_')) { - armor = - draft.model.turrets[protagonist.turret.id].guns[ - protagonist.gun.id - ].armor; - } else return; - - const index = resolveArmorIndex(highlightArmor.name); - - if (index === undefined) return; - - const input = event.currentTarget.valueAsNumber; - - if (!isNaN(input)) { - armor.thickness[index] = input; - } - }); - } else if (event.key === 'Escape') { - devThicknessInput.current?.blur(); - mutateTankopediaEphemeral((draft) => { - draft.highlightArmor = undefined; - }); - } - }} - key={highlightArmor.name} - defaultValue={highlightArmor.thickness.toFixed(0)} - mt="1" - autoFocus - ref={devThicknessInput} - size="1" - > - mm - )} diff --git a/src/components/Armor/components/SpacedArmorSceneComponent/index.tsx b/src/components/Armor/components/SpacedArmorSceneComponent/index.tsx index 62213fe6a..2275b069c 100644 --- a/src/components/Armor/components/SpacedArmorSceneComponent/index.tsx +++ b/src/components/Armor/components/SpacedArmorSceneComponent/index.tsx @@ -104,7 +104,6 @@ export function SpacedArmorSceneComponent({ let color: Color; let opacity: number; - let renderOrder = 1; let depthWrite = true; switch (props.type) { @@ -121,7 +120,6 @@ export function SpacedArmorSceneComponent({ case ArmorType.External: color = externalModuleColor; opacity = 1 / 8; - renderOrder = 0; depthWrite = false; break; } @@ -142,7 +140,7 @@ export function SpacedArmorSceneComponent({ const outlineMaterial = useMemo( () => new LineBasicMaterial({ - color: color.clone().multiplyScalar(1 - 2 ** -3), + color: color.clone().multiplyScalar(2 ** 2), }), [thickness], ); @@ -210,37 +208,51 @@ export function SpacedArmorSceneComponent({ onClick(event) { event.stopPropagation(); - const bounds = new Box3().setFromObject(event.object); - const point = bounds.min - .clone() - .add(bounds.max) - .divideScalar(2) - .setY(bounds.max.y); - const cameraNormal = camera.position.clone().sub(point).normalize(); - const surfaceNormal = event - .normal!.clone() - .applyQuaternion( - event.object.getWorldQuaternion(new Quaternion()), - ); - const angle = surfaceNormal.angleTo(cameraNormal); - const thicknessAngled = thickness / Math.sin(Math.PI / 2 - angle); + const { editStatic } = tankopediaEphemeralStore.getState(); - mutateTankopediaEphemeralStore((draft) => { - draft.highlightArmor = { - type: props.type, - name: props.name, - point, - thickness, - thicknessAngled, - angle, - color: `#${color.getHexString()}`, - }; - }); + if (editStatic) { + mutateTankopediaEphemeralStore((draft) => { + draft.editingPlate = { + name: props.name, + default: thickness, + }; + }); + } else { + const bounds = new Box3().setFromObject(event.object); + const point = bounds.min + .clone() + .add(bounds.max) + .divideScalar(2) + .setY(bounds.max.y); + const cameraNormal = camera.position + .clone() + .sub(point) + .normalize(); + const surfaceNormal = event + .normal!.clone() + .applyQuaternion( + event.object.getWorldQuaternion(new Quaternion()), + ); + const angle = surfaceNormal.angleTo(cameraNormal); + const thicknessAngled = thickness / Math.sin(Math.PI / 2 - angle); + + mutateTankopediaEphemeralStore((draft) => { + draft.highlightArmor = { + type: props.type, + name: props.name, + point, + thickness, + thicknessAngled, + angle, + color: `#${color.getHexString()}`, + }; + }); + } }, })} - + ); diff --git a/src/stores/tankopediaEphemeral.ts b/src/stores/tankopediaEphemeral.ts index a80f6c443..708021b93 100644 --- a/src/stores/tankopediaEphemeral.ts +++ b/src/stores/tankopediaEphemeral.ts @@ -13,6 +13,8 @@ interface TankopediaEphemeral { skills: Record; controlsEnabled: boolean; model: ModelDefinition; + editStatic: boolean; + editingPlate?: { name: string; default: number }; highlightArmor?: { name: string; point: Vector3; @@ -91,6 +93,7 @@ export const { Provider, use, useMutation, useStore } = createNextSafeStore( (model: ModelDefinition) => create()( subscribeWithSelector(() => ({ + editStatic: false, skills: {}, model, controlsEnabled: true,