diff --git a/packages/website/src/components/Armor/components/PrimaryArmorSceneComponent/index.tsx b/packages/website/src/components/Armor/components/PrimaryArmorSceneComponent/index.tsx index 69830885a..dae2f4d05 100644 --- a/packages/website/src/components/Armor/components/PrimaryArmorSceneComponent/index.tsx +++ b/packages/website/src/components/Armor/components/PrimaryArmorSceneComponent/index.tsx @@ -211,16 +211,25 @@ export function PrimaryArmorSceneComponent({ return ( <> - {jsxTree( - node, - { mesh: { renderOrder: 0, material: excludeMaterial } }, - `${node.uuid}-exclude`, - )} - {jsxTree( - node, - { mesh: { renderOrder: 1, material } }, - `${node.uuid}-include`, - )} + {jsxTree(node, { + mesh(_, props, key) { + return ( + + ); + }, + })} + {jsxTree(node, { + mesh(_, props, key) { + return ( + + ); + }, + })} ); } diff --git a/packages/website/src/components/Armor/components/SpacedArmorSceneComponent/components/SpacedArmorSubExternal/index.tsx b/packages/website/src/components/Armor/components/SpacedArmorSceneComponent/components/SpacedArmorSubExternal/index.tsx index 983692e2a..6974393ce 100644 --- a/packages/website/src/components/Armor/components/SpacedArmorSceneComponent/components/SpacedArmorSubExternal/index.tsx +++ b/packages/website/src/components/Armor/components/SpacedArmorSceneComponent/components/SpacedArmorSubExternal/index.tsx @@ -114,33 +114,40 @@ export function SpacedArmorSubExternal({ return ( <> - {jsxTree( - node, - { - mesh: { - renderOrder: 3, - material: new MeshBasicMaterial({ - colorWrite: false, - depthTest: true, - depthWrite: true, - ...(clip ? { clippingPlanes: [clip] } : {}), - }), - onClick() {}, - userData: { - type: ArmorType.External, - thickness, - variant, - } satisfies ArmorUserData, - }, + {jsxTree(node, { + mesh(_, props, key) { + return ( + + ); }, - `${node.uuid}-exclude`, - )} + })} - {jsxTree( - node, - { mesh: { renderOrder: 4, material } }, - `${node.uuid}-include`, - )} + {jsxTree(node, { + mesh(_, props, key) { + return ( + + ); + }, + })} ); } diff --git a/packages/website/src/components/Armor/components/SpacedArmorSceneComponent/components/SpacedArmorSubSpaced/index.tsx b/packages/website/src/components/Armor/components/SpacedArmorSceneComponent/components/SpacedArmorSubSpaced/index.tsx index 3c3904e71..3420523ce 100644 --- a/packages/website/src/components/Armor/components/SpacedArmorSceneComponent/components/SpacedArmorSubSpaced/index.tsx +++ b/packages/website/src/components/Armor/components/SpacedArmorSceneComponent/components/SpacedArmorSubSpaced/index.tsx @@ -124,32 +124,38 @@ export function SpacedArmorSubSpaced({ return ( <> - {jsxTree( - node, - { - mesh: { - renderOrder: 2, - material, - onClick() {}, - userData: { - type: ArmorType.Spaced, - thickness, - } satisfies ArmorUserData, - }, + {jsxTree(node, { + mesh(_, props, key) { + return ( + {}} + userData={ + { + type: ArmorType.Spaced, + thickness, + } satisfies ArmorUserData + } + /> + ); }, - `${node.uuid}-exclude`, - )} + })} - {jsxTree( - node, - { - mesh: { - renderOrder: 5, - material: depthWriteMaterial, - }, + {jsxTree(node, { + mesh(_, props, key) { + return ( + + ); }, - `${node.uuid}-include`, - )} + })} ); } diff --git a/packages/website/src/components/Armor/components/SpacedArmorSceneComponent/index.tsx b/packages/website/src/components/Armor/components/SpacedArmorSceneComponent/index.tsx index 2d8666347..c04024ec0 100644 --- a/packages/website/src/components/Armor/components/SpacedArmorSceneComponent/index.tsx +++ b/packages/website/src/components/Armor/components/SpacedArmorSceneComponent/index.tsx @@ -372,29 +372,33 @@ export function SpacedArmorSceneComponent({ return ( <> {props.type === ArmorType.Primary && - jsxTree( - node, - { - mesh: { - renderOrder: 0, - material: omitMaterial, - userData: { - type: ArmorType.Primary, - thickness, - } satisfies ArmorUserData, - async onClick(event) { - event.stopPropagation(); - const shot = (await shoot( - event.point, - event.intersections, - true, - ))!; - tankopediaEphemeralStore.setState({ shot }); - }, - }, + jsxTree(node, { + mesh(mesh, props, key) { + return ( + { + event.stopPropagation(); + const shot = (await shoot( + event.point, + event.intersections, + true, + ))!; + tankopediaEphemeralStore.setState({ shot }); + }} + /> + ); }, - node.uuid, - )} + })} {props.type === ArmorType.Spaced && ( diff --git a/packages/website/src/components/Armor/components/StaticArmorSceneComponent.tsx b/packages/website/src/components/Armor/components/StaticArmorSceneComponent.tsx index cc32af091..e1e74654f 100644 --- a/packages/website/src/components/Armor/components/StaticArmorSceneComponent.tsx +++ b/packages/website/src/components/Armor/components/StaticArmorSceneComponent.tsx @@ -215,50 +215,61 @@ export function StaticArmorSceneComponent({ return ( <> {jsxTree(node, { - mesh: { - material: surfaceMaterial, - onPointerDown, - userData: { - type: props.type, - variant: props.type === ArmorType.External ? props.variant : 'gun', - thickness, - } satisfies ArmorUserData, + mesh(_, meshProps, key) { + return ( + { + if (discardClippingPlane(event.object, event.point)) return; - onClick(event) { - if (discardClippingPlane(event.object, event.point)) return; + event.stopPropagation(); - event.stopPropagation(); + const { editStatic } = tankopediaEphemeralStore.getState(); - const { editStatic } = tankopediaEphemeralStore.getState(); + 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 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, - point, - thickness, - thicknessAngled, - angle, - color: `#${color.getHexString()}`, - editingPlate: editStatic, - }; - }); - }, + mutateTankopediaEphemeralStore((draft) => { + draft.highlightArmor = { + type: props.type, + name, + point, + thickness, + thicknessAngled, + angle, + color: `#${color.getHexString()}`, + editingPlate: editStatic, + }; + }); + }} + /> + ); }, })} diff --git a/packages/website/src/components/Compare/Controls.tsx b/packages/website/src/components/Compare/Controls.tsx index b17f8745e..2a7c984a1 100644 --- a/packages/website/src/components/Compare/Controls.tsx +++ b/packages/website/src/components/Compare/Controls.tsx @@ -1,5 +1,6 @@ import { PlusIcon, TrashIcon } from '@radix-ui/react-icons'; import { Button, Dialog, Flex, SegmentedControl } from '@radix-ui/themes'; +import { awaitableModelDefinitions } from '../../core/awaitables/modelDefinitions'; import { awaitableProvisionDefinitions } from '../../core/awaitables/provisionDefinitions'; import { tankToCompareMember } from '../../core/blitzkit/tankToCompareMember'; import { useLocale } from '../../hooks/useLocale'; @@ -15,7 +16,10 @@ interface ControlsProps { onAddTankDialogOpenChange: (open: boolean) => void; } -const provisionDefinitions = await awaitableProvisionDefinitions; +const [provisionDefinitions, modelDefinitions] = await Promise.all([ + awaitableProvisionDefinitions, + awaitableModelDefinitions, +]); export function Controls({ addTankDialogOpen, @@ -56,9 +60,11 @@ export function Controls({ { + const model = modelDefinitions.models[tank.id]; + mutateCompareEphemeral((draft) => { draft.members.push( - tankToCompareMember(tank, provisionDefinitions), + tankToCompareMember(tank, model, provisionDefinitions), ); draft.sorting = undefined; }); @@ -67,9 +73,15 @@ export function Controls({ onSelectAll={(tanks) => { mutateCompareEphemeral((draft) => { draft.members.push( - ...tanks.map((tank) => - tankToCompareMember(tank, provisionDefinitions), - ), + ...tanks.map((tank) => { + const model = modelDefinitions.models[tank.id]; + + return tankToCompareMember( + tank, + model, + provisionDefinitions, + ); + }), ); draft.sorting = undefined; }); diff --git a/packages/website/src/components/Compare/TankControl.tsx b/packages/website/src/components/Compare/TankControl.tsx index 96aa5840f..310fc4426 100644 --- a/packages/website/src/components/Compare/TankControl.tsx +++ b/packages/website/src/components/Compare/TankControl.tsx @@ -1,6 +1,7 @@ import { ExternalLinkIcon, LoopIcon, TrashIcon } from '@radix-ui/react-icons'; import { Dialog, Flex, IconButton } from '@radix-ui/themes'; import { useState } from 'react'; +import { awaitableModelDefinitions } from '../../core/awaitables/modelDefinitions'; import { awaitableProvisionDefinitions } from '../../core/awaitables/provisionDefinitions'; import { tankToCompareMember } from '../../core/blitzkit/tankToCompareMember'; import { useLocale } from '../../hooks/useLocale'; @@ -13,7 +14,10 @@ interface TankControlProps { id: number; } -const provisionDefinitions = await awaitableProvisionDefinitions; +const [provisionDefinitions, modelDefinitions] = await Promise.all([ + awaitableProvisionDefinitions, + awaitableModelDefinitions, +]); export function TankControl({ index, id }: TankControlProps) { const { locale } = useLocale(); @@ -57,9 +61,12 @@ export function TankControl({ index, id }: TankControlProps) { { + const model = modelDefinitions.models[tank.id]; + mutateCompareEphemeral((draft) => { draft.members[index] = tankToCompareMember( tank, + model, provisionDefinitions, ); draft.sorting = undefined; diff --git a/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/TankModel.tsx b/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/TankModel.tsx index b91ee66b6..f8df92760 100644 --- a/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/TankModel.tsx +++ b/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/TankModel.tsx @@ -114,29 +114,35 @@ export function TankModel() { window.removeEventListener('pointerup', handlePointerUp); } - return jsxTree( - node, - { - mesh: { onPointerDown, castShadow: true }, - // group(group) { - // return { - // visible: - // group.name !== 'ST_base' && - // group.name !== 'MR_Base2' && - // (!group.name.includes('state_entity') || - // (isDynamicArmorActive && - // group.name.includes('state_entity_01')) || - // (!isDynamicArmorActive && - // group.name.includes('state_entity_00'))) && - // (!group.name.includes('shields') || - // (isDynamicArmorActive && - // group.name.includes('shields_rev')) || - // (!isDynamicArmorActive && group.name.endsWith('shields'))), - // }; - // }, + return jsxTree(node, { + mesh(_, props, key) { + return ( + + ); }, - node.uuid, - ); + + // group(group) { + // return { + // visible: + // group.name !== 'ST_base' && + // group.name !== 'MR_Base2' && + // (!group.name.includes('state_entity') || + // (isDynamicArmorActive && + // group.name.includes('state_entity_01')) || + // (!isDynamicArmorActive && + // group.name.includes('state_entity_00'))) && + // (!group.name.includes('shields') || + // (isDynamicArmorActive && + // group.name.includes('shields_rev')) || + // (!isDynamicArmorActive && group.name.endsWith('shields'))), + // }; + // }, + }); })} @@ -217,30 +223,36 @@ export function TankModel() { window.removeEventListener('pointerup', handlePointerUp); } - return jsxTree( - node, - { - mesh: { onPointerDown, castShadow: true }, - // group(group) { - // return { - // visible: - // group.name !== 'ST_base' && - // group.name !== 'MR_Base2' && - // (!group.name.includes('state_entity') || - // (isDynamicArmorActive && - // group.name.includes('state_entity_01')) || - // (!isDynamicArmorActive && - // group.name.includes('state_entity_00'))) && - // (!group.name.includes('shields') || - // (isDynamicArmorActive && - // group.name.includes('shields_rev')) || - // (!isDynamicArmorActive && - // group.name.endsWith('shields'))), - // }; - // }, + return jsxTree(node, { + mesh(_, props, key) { + return ( + + ); }, - node.uuid, - ); + + // group(group) { + // return { + // visible: + // group.name !== 'ST_base' && + // group.name !== 'MR_Base2' && + // (!group.name.includes('state_entity') || + // (isDynamicArmorActive && + // group.name.includes('state_entity_01')) || + // (!isDynamicArmorActive && + // group.name.includes('state_entity_00'))) && + // (!group.name.includes('shields') || + // (isDynamicArmorActive && + // group.name.includes('shields_rev')) || + // (!isDynamicArmorActive && + // group.name.endsWith('shields'))), + // }; + // }, + }); })} @@ -324,30 +336,36 @@ export function TankModel() { window.removeEventListener('pointerup', handlePointerUp); } - return jsxTree( - node, - { - mesh: { onPointerDown, castShadow: true }, - // group(group) { - // return { - // visible: - // group.name !== 'ST_base' && - // group.name !== 'MR_Base2' && - // (!group.name.includes('state_entity') || - // (isDynamicArmorActive && - // group.name.includes('state_entity_01')) || - // (!isDynamicArmorActive && - // group.name.includes('state_entity_00'))) && - // (!group.name.includes('shields') || - // (isDynamicArmorActive && - // group.name.includes('shields_rev')) || - // (!isDynamicArmorActive && - // group.name.endsWith('shields'))), - // }; - // }, + return jsxTree(node, { + mesh(_, props, key) { + return ( + + ); }, - node.uuid, - ); + + // group(group) { + // return { + // visible: + // group.name !== 'ST_base' && + // group.name !== 'MR_Base2' && + // (!group.name.includes('state_entity') || + // (isDynamicArmorActive && + // group.name.includes('state_entity_01')) || + // (!isDynamicArmorActive && + // group.name.includes('state_entity_00'))) && + // (!group.name.includes('shields') || + // (isDynamicArmorActive && + // group.name.includes('shields_rev')) || + // (!isDynamicArmorActive && + // group.name.endsWith('shields'))), + // }; + // }, + }); })} diff --git a/packages/website/src/core/blitzkit/jsxTree.tsx b/packages/website/src/core/blitzkit/jsxTree.tsx index 4ae843b6c..253527522 100644 --- a/packages/website/src/core/blitzkit/jsxTree.tsx +++ b/packages/website/src/core/blitzkit/jsxTree.tsx @@ -1,43 +1,44 @@ -import type { GroupProps, MeshProps } from '@react-three/fiber'; +import type { MeshProps, Object3DProps } from '@react-three/fiber'; +import type { ReactNode } from 'react'; import { Mesh, Object3D } from 'three'; export function jsxTree( node: Object3D, mergers?: { - mesh?: MeshProps | ((mesh: Mesh) => MeshProps); - group?: GroupProps | ((object3d: Object3D) => GroupProps); + mesh?: (mesh: Mesh, props: MeshProps, key: string) => ReactNode; + group?: ( + object3d: Object3D, + props: Object3DProps, + key: string, + ) => ReactNode; }, - key?: string, -) { +): ReactNode { if (node instanceof Mesh) { - return ( - + const props = { + geometry: node.geometry, + material: node.material, + position: node.position, + rotation: node.rotation, + scale: node.scale, + }; + + return mergers?.mesh ? ( + mergers.mesh(node, props, node.uuid) + ) : ( + ); } else if (node instanceof Object3D) { - return ( - - {node.children.map((child) => jsxTree(child, mergers, child.uuid))} - + const props = { + position: node.position, + rotation: node.rotation, + scale: node.scale, + children: node.children.map((child) => jsxTree(child, mergers)), + }; + + return mergers?.group ? ( + mergers.group(node, props, node.uuid) + ) : ( + ); } diff --git a/packages/website/src/pages/[...locale]/tools/compare/_index.tsx b/packages/website/src/pages/[...locale]/tools/compare/_index.tsx index 59d3106a8..7361e0211 100644 --- a/packages/website/src/pages/[...locale]/tools/compare/_index.tsx +++ b/packages/website/src/pages/[...locale]/tools/compare/_index.tsx @@ -113,7 +113,11 @@ function Content() { if (tank === undefined) return; - draft.members.push(tankToCompareMember(tank, provisionDefinitions)); + const model = modelDefinitions.models[tank.id]; + + draft.members.push( + tankToCompareMember(tank, model, provisionDefinitions), + ); }); }); }, []);