From c7017a7a153c64fee976f180feae5ebb6a16a38f Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Sun, 23 Mar 2025 19:07:37 -0500 Subject: [PATCH] feat: cards --- .../components/Armor/components/Dissector.tsx | 21 +++-- .../Armor/components/DissectorModuleCard.tsx | 32 +++++++ .../components/DissectorSceneComponent.tsx | 84 ++++++++++++++----- .../components/TankSandbox/index.tsx | 8 ++ packages/website/src/core/blitzkit/dissect.ts | 6 +- .../website/src/core/blitzkit/moduleSelect.ts | 19 +++++ 6 files changed, 142 insertions(+), 28 deletions(-) create mode 100644 packages/website/src/components/Armor/components/DissectorModuleCard.tsx create mode 100644 packages/website/src/core/blitzkit/moduleSelect.ts diff --git a/packages/website/src/components/Armor/components/Dissector.tsx b/packages/website/src/components/Armor/components/Dissector.tsx index f7a49a326..06b7460d1 100644 --- a/packages/website/src/components/Armor/components/Dissector.tsx +++ b/packages/website/src/components/Armor/components/Dissector.tsx @@ -1,17 +1,22 @@ import { memo } from 'react'; +import { DissectorModuleCard } from './DissectorModuleCard'; import { DissectorSceneComponent } from './DissectorSceneComponent'; export const Dissector = memo(() => { return ( - - - - - - - + <> + + + + + + + + + + - + ); }); diff --git a/packages/website/src/components/Armor/components/DissectorModuleCard.tsx b/packages/website/src/components/Armor/components/DissectorModuleCard.tsx new file mode 100644 index 000000000..9cc452848 --- /dev/null +++ b/packages/website/src/components/Armor/components/DissectorModuleCard.tsx @@ -0,0 +1,32 @@ +import { Card, Text } from '@radix-ui/themes'; +import { Html } from '@react-three/drei'; +import { useEffect, useState } from 'react'; +import { + lastModuleSelect, + moduleSelectEvent, + type ModuleSelectEventData, +} from '../../../core/blitzkit/moduleSelect'; + +export function DissectorModuleCard() { + const [moduleSelection, setModuleSelection] = useState(lastModuleSelect); + + useEffect(() => { + function handleModuleSelectEvent(event: ModuleSelectEventData) { + setModuleSelection(event); + } + + return moduleSelectEvent.on(handleModuleSelectEvent); + }, []); + + if (!moduleSelection.selected) return null; + + return ( + + + + {moduleSelection.module} + + + + ); +} diff --git a/packages/website/src/components/Armor/components/DissectorSceneComponent.tsx b/packages/website/src/components/Armor/components/DissectorSceneComponent.tsx index 9266aa8e4..daa4f71b0 100644 --- a/packages/website/src/components/Armor/components/DissectorSceneComponent.tsx +++ b/packages/website/src/components/Armor/components/DissectorSceneComponent.tsx @@ -18,9 +18,11 @@ import { type CSGGeometryRef, } from '@react-three/csg'; import { useGLTF } from '@react-three/drei'; +import { useThree } from '@react-three/fiber'; import { Fragment, useEffect, useRef } from 'react'; import type { Mesh } from 'three'; import { + Box3, Color, DoubleSide, Group, @@ -37,6 +39,11 @@ import { type DissectEventData, } from '../../../core/blitzkit/dissect'; import { jsxTree } from '../../../core/blitzkit/jsxTree'; +import { + lastModuleSelect, + moduleSelectEvent, + type ModuleSelectEventData, +} from '../../../core/blitzkit/moduleSelect'; import { Duel } from '../../../stores/duel'; import { TankopediaEphemeral } from '../../../stores/tankopediaEphemeral'; import { groupNameRegex } from './StaticArmorSceneComponent'; @@ -88,6 +95,7 @@ export function DissectorSceneComponent({ const armor = TankopediaEphemeral.use((state) => state.armor); const groupName = group.match(groupNameRegex)![1]; const groupArmor = armor.groups[groupName]; + const camera = useThree((state) => state.camera); return jsxTree(gltf.scene, { mesh(mesh, props) { @@ -115,7 +123,7 @@ export function DissectorSceneComponent({ const material = useRef( new MeshStandardMaterial({ transparent: isTrack, - opacity: 2 ** -1, + opacity: 2 ** -5, side: DoubleSide, color: moduleColor[armorPlate.type], clippingPlanes: isModule ? [] : [clippingPlane.current], @@ -141,6 +149,8 @@ export function DissectorSceneComponent({ }), ); const worldPosition = new Vector3(); + const boundsTop = new Vector3(); + const bounds = new Box3(); useEffect(() => { const offset = new Vector3(); @@ -169,11 +179,28 @@ export function DissectorSceneComponent({ csg.current.update(); } + function handleModuleSelectEvent(event: ModuleSelectEventData) { + if (!isModule) return; + + const isThisComponentSelected = + event.selected && + event.group === groupName && + event.module === armorName; + const shouldBeVisible = !event.selected || isThisComponentSelected; + + material.current.transparent = !shouldBeVisible; + outlineMaterial.current.transparent = !shouldBeVisible; + } + dissectEvent.on(handleDissectEvent); + moduleSelectEvent.on(handleModuleSelectEvent); + handleDissectEvent(lastDissection); + handleModuleSelectEvent(lastModuleSelect); return () => { dissectEvent.off(handleDissectEvent); + moduleSelectEvent.off(handleModuleSelectEvent); }; }, []); @@ -184,25 +211,44 @@ export function DissectorSceneComponent({ {isModule && ( - { - event.stopPropagation(); - }} - > - - + <> + { + if (!baseWrapper.current) return; - - - - - - - + const isAlreadySelected = + lastModuleSelect.selected && + lastModuleSelect.group === groupName && + lastModuleSelect.module === armorName; + + if (!isAlreadySelected) { + moduleSelectEvent.emit({ + selected: true, + group: groupName, + module: armorName, + position: event.point.toArray(), + }); + } + }} + onPointerDown={(event) => { + event.stopPropagation(); + }} + > + + + + + + + + + + + )} {!isModule && ( diff --git a/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/index.tsx b/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/index.tsx index 8d8d86edd..7da3ce7a1 100644 --- a/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/index.tsx +++ b/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/index.tsx @@ -1,6 +1,10 @@ import { BLITZKIT_TANK_ICON_SIZE } from '@blitzkit/core'; import { Canvas } from '@react-three/fiber'; import { forwardRef, Suspense, useImperativeHandle, useRef } from 'react'; +import { + lastModuleSelect, + moduleSelectEvent, +} from '../../../../../core/blitzkit/moduleSelect'; import { useOnScreen } from '../../../../../hooks/useOnScreen'; import { TankopediaEphemeral } from '../../../../../stores/tankopediaEphemeral'; import { TankopediaPersistent } from '../../../../../stores/tankopediaPersistent'; @@ -172,6 +176,10 @@ export const TankSandbox = forwardRef( draft.shot = undefined; draft.highlightArmor = undefined; }); + + if (lastModuleSelect.selected) { + moduleSelectEvent.emit({ selected: false }); + } }} style={{ userSelect: 'none', diff --git a/packages/website/src/core/blitzkit/dissect.ts b/packages/website/src/core/blitzkit/dissect.ts index 5bf368adb..f18ec362f 100644 --- a/packages/website/src/core/blitzkit/dissect.ts +++ b/packages/website/src/core/blitzkit/dissect.ts @@ -1,5 +1,6 @@ import { EventManager } from '@blitzkit/core'; import { degToRad } from 'three/src/math/MathUtils.js'; +import { lastModuleSelect, moduleSelectEvent } from './moduleSelect'; export interface DissectEventData { rotation: number; @@ -13,4 +14,7 @@ export const lastDissection: DissectEventData = { offset: 0.8, }; -dissectEvent.on((data) => Object.assign(lastDissection, data)); +dissectEvent.on((data) => { + if (lastModuleSelect.selected) moduleSelectEvent.emit({ selected: false }); + Object.assign(lastDissection, data); +}); diff --git a/packages/website/src/core/blitzkit/moduleSelect.ts b/packages/website/src/core/blitzkit/moduleSelect.ts new file mode 100644 index 000000000..ca034dcf3 --- /dev/null +++ b/packages/website/src/core/blitzkit/moduleSelect.ts @@ -0,0 +1,19 @@ +import { EventManager } from '@blitzkit/core'; +import type { Vector3Tuple } from 'three'; + +export type ModuleSelectEventData = + | { + selected: false; + } + | { + selected: true; + group: string; + module: string; + position: Vector3Tuple; + }; + +export const moduleSelectEvent = new EventManager(); + +export const lastModuleSelect: ModuleSelectEventData = { selected: false }; + +moduleSelectEvent.on((data) => Object.assign(lastModuleSelect, data));