From 0c37550c05ae071bf86c2d2acf4ae084368c95ea Mon Sep 17 00:00:00 2001
From: tresabhi <43380238+tresabhi@users.noreply.github.com>
Date: Fri, 21 Mar 2025 18:55:24 -0500
Subject: [PATCH] feat: outlines
---
packages/i18n/strings/en.json | 4 +-
.../Armor/components/StaticArmor.tsx | 15 +-
.../components/StaticArmorSceneComponents.tsx | 249 ++++--------------
.../Options/components/Thicknesses.tsx | 2 +-
.../TankSandbox/components/Control.tsx | 226 ++++++++--------
.../website/src/core/blitzkit/jsxTree.tsx | 58 ++--
6 files changed, 204 insertions(+), 350 deletions(-)
diff --git a/packages/i18n/strings/en.json b/packages/i18n/strings/en.json
index 2ced1ebfc..6b4d23528 100644
--- a/packages/i18n/strings/en.json
+++ b/packages/i18n/strings/en.json
@@ -900,7 +900,7 @@
"element": {
"primary": "Primary",
"spaced": "Spaced",
- "external": "External",
+ "modules": "Modules",
"gap": "Gap",
"gun": "Gun",
"track": "Tracks"
@@ -922,7 +922,7 @@
"name": "Static armor",
"primary": "Primary",
"spaced": "Spaced",
- "external": "External",
+ "modules": "Modules",
"edit": {
"label": "Edit",
"reset": "Reset"
diff --git a/packages/website/src/components/Armor/components/StaticArmor.tsx b/packages/website/src/components/Armor/components/StaticArmor.tsx
index 9ed87f8ce..3191e7023 100644
--- a/packages/website/src/components/Armor/components/StaticArmor.tsx
+++ b/packages/website/src/components/Armor/components/StaticArmor.tsx
@@ -1,10 +1,4 @@
-import { asset } from '@blitzkit/core';
-import { useGLTF } from '@react-three/drei';
import { memo } from 'react';
-import { Color, Material, MeshBasicMaterial } from 'three';
-import { jsxTree } from '../../../core/blitzkit/jsxTree';
-import { Duel } from '../../../stores/duel';
-import { TankopediaEphemeral } from '../../../stores/tankopediaEphemeral';
import { StaticArmorSceneComponents } from './StaticArmorSceneComponents';
export interface ThicknessRange {
@@ -55,11 +49,16 @@ export const StaticArmor = memo(() => {
// useTankTransform(track, turret, turretContainer, gunContainer);
-
return (
-
+
+
+
+
+
+
+
);
});
diff --git a/packages/website/src/components/Armor/components/StaticArmorSceneComponents.tsx b/packages/website/src/components/Armor/components/StaticArmorSceneComponents.tsx
index 3fab91bb0..801880fed 100644
--- a/packages/website/src/components/Armor/components/StaticArmorSceneComponents.tsx
+++ b/packages/website/src/components/Armor/components/StaticArmorSceneComponents.tsx
@@ -1,39 +1,45 @@
import { asset } from '@blitzkit/core';
import { useGLTF } from '@react-three/drei';
-import { Color, Material, MeshStandardMaterial } from 'three';
+import {
+ Color,
+ LineBasicMaterial,
+ Material,
+ MeshStandardMaterial,
+} from 'three';
import { clamp } from 'three/src/math/MathUtils.js';
import { jsxTree } from '../../../core/blitzkit/jsxTree';
import { Duel } from '../../../stores/duel';
import { TankopediaEphemeral } from '../../../stores/tankopediaEphemeral';
import { TankopediaPersistent } from '../../../stores/tankopediaPersistent';
-type StaticArmorSceneComponentsProps = {
+interface StaticArmorSceneComponentsProps {
group: string;
-};
+}
-const unselectedColor = new Color(0x404040);
-const externalModuleColor = new Color(192 / 255, 192 / 255, 192 / 255);
+const moduleColor = new Color(192 / 255, 192 / 255, 192 / 255);
+const groupNameRegex = /([a-z]+)(_\d+)?/;
export function StaticArmorSceneComponents({
group,
}: StaticArmorSceneComponentsProps) {
+ const tank = Duel.use((state) => state.protagonist.tank);
const showModules = TankopediaPersistent.use((state) => state.showModules);
const showArmorScreen = TankopediaPersistent.use(
(state) => state.showArmorScreen,
);
const showArmor = TankopediaPersistent.use((state) => state.showArmor);
- const tank = Duel.use((state) => state.protagonist.tank);
const gltf = useGLTF(asset(`tanks/${tank.id}/collision/${group}.glb`));
const armor = TankopediaEphemeral.use((state) => state.armor);
- const groupArmor = armor.groups[group];
+ const groupName = group.match(groupNameRegex)![1];
+ const groupArmor = armor.groups[groupName];
const thicknessRange = TankopediaEphemeral.use(
(state) => state.thicknessRange,
);
return jsxTree(gltf.scene, {
- mesh(mesh) {
+ mesh(mesh, props) {
if (!(mesh.material instanceof Material)) {
- return { visible: false };
+ return null;
}
const armorName = mesh.material.name;
@@ -44,216 +50,67 @@ export function StaticArmorSceneComponents({
switch (armorPlate.type) {
case 'Armor':
+ if (!showArmor) return null;
+
if (x > 1) {
material.color = new Color(clamp(2 - x, 0.5, 1), 0, 0);
} else {
material.color = new Color(-((1 - x) ** 2) + 1, -(x ** 2) + 1, 0);
}
material.opacity = 1;
- material.visible = showArmor;
break;
case 'ArmorScreen':
+ if (!showArmorScreen) return null;
+
material.color = new Color(
clamp(1 - (7 / 8) * xClamped, 0, 1),
0,
clamp(1 - (1 / 8) * xClamped, 0, 1),
);
material.opacity = clamp(x + 1 / 2, 0, 1);
- material.visible = showArmorScreen;
+
break;
default:
- material.color = externalModuleColor;
- material.opacity = 7 / 8;
- material.depthWrite = false;
- material.visible = showModules;
+ if (!showModules) return null;
+
+ material.color = moduleColor;
+ material.opacity = 1 / 2;
+
break;
}
+
+ const outlineMaterial = new LineBasicMaterial({
+ color: material.color.clone(),
+ });
+
+ switch (armorPlate.type) {
+ case 'Armor':
+ outlineMaterial.color.multiplyScalar(0.5);
+ break;
+
+ case 'ArmorScreen':
+ outlineMaterial.color.multiplyScalar(4);
+ break;
+
+ default:
+ outlineMaterial.color.multiplyScalar(0.5);
+ break;
+ }
+
material.opacity = clamp(material.opacity, 0, 1);
material.transparent = material.opacity < 1;
- return { material };
+ return (
+ <>
+
+
+
+
+
+ >
+ );
},
});
-
- // const mutateTankopediaEphemeralStore = TankopediaEphemeral.useMutation();
- // const tankopediaEphemeralStore = TankopediaEphemeral.useStore();
- // const camera = useThree((state) => state.camera);
- // const x = thickness / thicknessRange.value;
- // const xClamped = clamp(x, 0, 1);
- // let color: Color;
- // let opacity: number;
- // let depthWrite = true;
- // switch (props.type) {
- // case ArmorType.Primary:
- // if (x > 1) {
- // color = new Color(clamp(2 - x, 0.5, 1), 0, 0);
- // } else {
- // color = new Color(-((1 - x) ** 2) + 1, -(x ** 2) + 1, 0);
- // }
- // opacity = 1;
- // break;
- // case ArmorType.Spaced:
- // color = new Color(
- // clamp(1 - (7 / 8) * xClamped, 0, 1),
- // 0,
- // clamp(1 - (1 / 8) * xClamped, 0, 1),
- // );
- // opacity = clamp(x + 1 / 2, 0, 1);
- // break;
- // case ArmorType.External:
- // color = externalModuleColor;
- // opacity = 1 / 8;
- // depthWrite = false;
- // break;
- // }
- // opacity = clamp(opacity, 0, 1);
- // const surfaceMaterial = useMemo(
- // () =>
- // new MeshStandardMaterial({
- // color,
- // opacity,
- // transparent: opacity < 1,
- // depthWrite,
- // ...(props.clip ? { clippingPlanes: [props.clip] } : {}),
- // }),
- // [thickness],
- // );
- // const outlineMaterial = useMemo(
- // () =>
- // new LineBasicMaterial({
- // color: color
- // .clone()
- // .multiplyScalar(props.type === ArmorType.Spaced ? 2 ** 2 : 2 ** -1),
- // }),
- // [thickness],
- // );
- // useEffect(() => {
- // function handleHighlightArmor(selectedName?: string) {
- // if (selectedName === undefined) {
- // // nothing selected, go back to defaults
- // surfaceMaterial.opacity = opacity;
- // surfaceMaterial.transparent = opacity < 1;
- // surfaceMaterial.color = color;
- // surfaceMaterial.depthWrite = props.type !== ArmorType.External;
- // surfaceMaterial.side = FrontSide;
- // outlineMaterial.visible = true;
- // } else if (
- // selectedName === name ||
- // (name.startsWith('chassis_') && selectedName.startsWith('chassis_')) ||
- // (name.startsWith('gun_') &&
- // selectedName.startsWith('gun_') &&
- // !name.includes('_armor_') &&
- // !selectedName.includes('_armor_'))
- // ) {
- // // this selected, stand out!
- // surfaceMaterial.opacity = 1;
- // surfaceMaterial.transparent = false;
- // surfaceMaterial.color = color;
- // surfaceMaterial.depthWrite = true;
- // surfaceMaterial.side = DoubleSide;
- // outlineMaterial.visible = true;
- // } else {
- // // something else selected, become background
- // surfaceMaterial.opacity = 1 / 4;
- // surfaceMaterial.transparent = true;
- // surfaceMaterial.color = unselectedColor;
- // surfaceMaterial.depthWrite = props.type !== ArmorType.External;
- // surfaceMaterial.side = FrontSide;
- // outlineMaterial.visible = false;
- // }
- // surfaceMaterial.needsUpdate = true;
- // }
- // const unsubscribes = [
- // tankopediaEphemeralStore.subscribe(
- // (state) => state.highlightArmor?.name,
- // handleHighlightArmor,
- // ),
- // ];
- // if (props.clip) {
- // /**
- // * hook inside an if statement?? don't panic! I assure you the clip prop
- // * never mutates :)
- // */
- // const { clip } = props;
- // const gunOrigin = unrotateDavaVector(props.gunOrigin.clone());
- // const neckOrigin = unrotateDavaVector(props.hullOrigin.clone()).add(
- // unrotateDavaVector(props.turretOrigin.clone()),
- // );
- // const barrelOrigin = neckOrigin.clone().add(gunOrigin);
- // const distanceToBarrel = clip.distanceToPoint(barrelOrigin);
- // const point = new Vector3();
- // function handleModelTransform(data: ModelTransformEventData) {
- // clip.normal
- // .set(0, 0, -1)
- // .applyAxisAngle(I_HAT, data.pitch)
- // .applyAxisAngle(J_HAT, data.yaw ?? 0);
- // clip.constant = 0;
- // point
- // .copy(gunOrigin)
- // .applyAxisAngle(J_HAT, data.yaw ?? 0)
- // .add(clip.normal.multiplyScalar(-distanceToBarrel))
- // .add(neckOrigin);
- // clip.normal.normalize();
- // clip.constant -= clip.distanceToPoint(point);
- // }
- // modelTransformEvent.on(handleModelTransform);
- // unsubscribes.push(() => modelTransformEvent.off(handleModelTransform));
- // }
- // return () => {
- // unsubscribes.forEach((unsubscribe) => unsubscribe());
- // };
- // }, [thickness]);
- // return (
- // <>
- // {jsxTree(node, {
- // mesh: {
- // material: surfaceMaterial,
- // onPointerDown,
- // userData: {
- // type: props.type,
- // variant: props.type === ArmorType.External ? props.variant : 'gun',
- // thickness,
- // } satisfies ArmorUserData,
- // onClick(event) {
- // if (discardClippingPlane(event.object, event.point)) return;
- // event.stopPropagation();
- // 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);
- // mutateTankopediaEphemeralStore((draft) => {
- // draft.highlightArmor = {
- // type: props.type,
- // name,
- // point,
- // thickness,
- // thicknessAngled,
- // angle,
- // color: `#${color.getHexString()}`,
- // editingPlate: editStatic,
- // };
- // });
- // },
- // },
- // })}
- // {node instanceof Mesh && (
- //
- //
- //
- // )}
- // >
- // );
}
diff --git a/packages/website/src/components/Tankopedia/HeroSection/components/Options/components/Thicknesses.tsx b/packages/website/src/components/Tankopedia/HeroSection/components/Options/components/Thicknesses.tsx
index 08d47a0ea..398a7996c 100644
--- a/packages/website/src/components/Tankopedia/HeroSection/components/Options/components/Thicknesses.tsx
+++ b/packages/website/src/components/Tankopedia/HeroSection/components/Options/components/Thicknesses.tsx
@@ -125,7 +125,7 @@ export function Thicknesses() {
>
- {strings.website.tools.tanks.sandbox.static.external}
+ {strings.website.tools.tanks.sandbox.static.modules}
diff --git a/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/Control.tsx b/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/Control.tsx
index 1b4bdc593..6d5004c3d 100644
--- a/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/Control.tsx
+++ b/packages/website/src/components/Tankopedia/HeroSection/components/TankSandbox/components/Control.tsx
@@ -1,13 +1,16 @@
import { OrbitControls } from '@react-three/drei';
import { useThree } from '@react-three/fiber';
-import { useRef, useState } from 'react';
+import { useEffect, useRef, useState } from 'react';
import { PerspectiveCamera } from 'three';
import { OrbitControls as OrbitControlsClass } from 'three-stdlib';
import { degToRad } from 'three/src/math/MathUtils.js';
import { awaitableModelDefinitions } from '../../../../../../core/awaitables/modelDefinitions';
import { Pose } from '../../../../../../core/blitzkit/pose';
import { Duel } from '../../../../../../stores/duel';
-import { TankopediaEphemeral } from '../../../../../../stores/tankopediaEphemeral';
+import {
+ SHOOTING_RANGE_ZOOM_COEFFICIENTS,
+ TankopediaEphemeral,
+} from '../../../../../../stores/tankopediaEphemeral';
import { TankopediaDisplay } from '../../../../../../stores/tankopediaPersistent/constants';
const poseDistances: Record = {
@@ -80,11 +83,12 @@ export function Controls({ naked }: ControlsProps) {
// protagonistHullOrigin.y +
// protagonistTurretOrigin.y +
// protagonistGunOrigin.y;
- // const inspectModeInitialPosition = [
- // -8,
- // gunHeight + (naked ? 10 : 4),
- // -13,
- // ] as const;
+ const gunHeight = 2;
+ const inspectModeInitialPosition = [
+ -8,
+ gunHeight + (naked ? 10 : 4),
+ -13,
+ ] as const;
// const protagonistGunOriginOnlyY = new Vector3(
// 0,
// protagonistTurretModelDefinition.gun_origin.y,
@@ -206,131 +210,131 @@ export function Controls({ naked }: ControlsProps) {
// };
// }, [camera, protagonistTank.id, antagonistTank.id]);
- // useEffect(() => {
- // let isInitialIteration = true;
+ useEffect(() => {
+ let isInitialIteration = true;
- // function handleDisturbance() {
- // setAutoRotate(false);
- // }
+ function handleDisturbance() {
+ setAutoRotate(false);
+ }
- // function handleKeyDown(event: KeyboardEvent) {
- // if (display !== TankopediaDisplay.ShootingRange || event.repeat) return;
+ // function handleKeyDown(event: KeyboardEvent) {
+ // if (display !== TankopediaDisplay.ShootingRange || event.repeat) return;
- // if (event.key === 'Shift') {
- // mutateTankopediaEphemeral((draft) => {
- // draft.shootingRangeZoom =
- // draft.shootingRangeZoom === ShootingRangeZoom.Arcade
- // ? ShootingRangeZoom.Zoom0
- // : ShootingRangeZoom.Arcade;
- // });
- // }
- // }
+ // if (event.key === 'Shift') {
+ // mutateTankopediaEphemeral((draft) => {
+ // draft.shootingRangeZoom =
+ // draft.shootingRangeZoom === ShootingRangeZoom.Arcade
+ // ? ShootingRangeZoom.Zoom0
+ // : ShootingRangeZoom.Arcade;
+ // });
+ // }
+ // }
- // function handleWheel(event: WheelEvent) {
- // if (display !== TankopediaDisplay.ShootingRange) return;
+ // function handleWheel(event: WheelEvent) {
+ // if (display !== TankopediaDisplay.ShootingRange) return;
- // event.preventDefault();
+ // event.preventDefault();
- // mutateTankopediaEphemeral((draft) => {
- // draft.shootingRangeZoom = clamp(
- // draft.shootingRangeZoom - Math.sign(event.deltaY),
- // ShootingRangeZoom.Arcade,
- // ShootingRangeZoom.Zoom2,
- // );
- // });
- // }
+ // mutateTankopediaEphemeral((draft) => {
+ // draft.shootingRangeZoom = clamp(
+ // draft.shootingRangeZoom - Math.sign(event.deltaY),
+ // ShootingRangeZoom.Arcade,
+ // ShootingRangeZoom.Zoom2,
+ // );
+ // });
+ // }
- // function handleScroll(event: Event) {
- // if (display !== TankopediaDisplay.ShootingRange) return;
+ // function handleScroll(event: Event) {
+ // if (display !== TankopediaDisplay.ShootingRange) return;
- // event.preventDefault();
- // }
+ // event.preventDefault();
+ // }
- // poseEvent.on(handleDisturbance);
- // canvas.addEventListener('pointerdown', handleDisturbance);
- // window.addEventListener('keydown', handleKeyDown);
- // canvas.addEventListener('wheel', handleWheel);
- // document.body.addEventListener('scroll', handleScroll);
+ // poseEvent.on(handleDisturbance);
+ canvas.addEventListener('pointerdown', handleDisturbance);
+ // window.addEventListener('keydown', handleKeyDown);
+ // canvas.addEventListener('wheel', handleWheel);
+ // document.body.addEventListener('scroll', handleScroll);
- // function updateCamera() {
- // if (!orbitControls.current) return;
+ function updateCamera() {
+ if (!orbitControls.current) return;
- // orbitControls.current.rotateSpeed = 0.25;
+ orbitControls.current.rotateSpeed = 0.25;
- // if (display === TankopediaDisplay.ShootingRange) {
- // const { shootingRangeZoom } = tankopediaEphemeralStore.getState();
+ if (display === TankopediaDisplay.ShootingRange) {
+ const { shootingRangeZoom } = tankopediaEphemeralStore.getState();
- // orbitControls.current.enablePan = false;
- // orbitControls.current.enableZoom = false;
- // orbitControls.current.rotateSpeed *=
- // SHOOTING_RANGE_ZOOM_COEFFICIENTS[shootingRangeZoom];
- // camera.fov =
- // ARCADE_MODE_FOV * SHOOTING_RANGE_ZOOM_COEFFICIENTS[shootingRangeZoom];
+ orbitControls.current.enablePan = false;
+ orbitControls.current.enableZoom = false;
+ orbitControls.current.rotateSpeed *=
+ SHOOTING_RANGE_ZOOM_COEFFICIENTS[shootingRangeZoom];
+ camera.fov =
+ ARCADE_MODE_FOV * SHOOTING_RANGE_ZOOM_COEFFICIENTS[shootingRangeZoom];
- // if (shootingRangeZoom === ShootingRangeZoom.Arcade) {
- // orbitControls.current.target.set(0, gunHeight + 3, 0);
+ // if (shootingRangeZoom === ShootingRangeZoom.Arcade) {
+ // orbitControls.current.target.set(0, gunHeight + 3, 0);
- // if (isInitialIteration) {
- // isInitialIteration = false;
- // camera.position.set(
- // 0,
- // gunHeight + ARCADE_MODE_DISTANCE * Math.sin(ARCADE_MODE_ANGLE),
- // ARCADE_MODE_DISTANCE * Math.cos(ARCADE_MODE_ANGLE),
- // );
- // } else {
- // camera.position
- // .copy(aimTarget)
- // .sub(orbitControls.current.target)
- // .normalize()
- // .multiplyScalar(-ARCADE_MODE_DISTANCE)
- // .add(orbitControls.current.target);
- // }
- // } else {
- // orbitControls.current.target.copy(shellOrigin);
+ // if (isInitialIteration) {
+ // isInitialIteration = false;
+ // camera.position.set(
+ // 0,
+ // gunHeight + ARCADE_MODE_DISTANCE * Math.sin(ARCADE_MODE_ANGLE),
+ // ARCADE_MODE_DISTANCE * Math.cos(ARCADE_MODE_ANGLE),
+ // );
+ // } else {
+ // camera.position
+ // .copy(aimTarget)
+ // .sub(orbitControls.current.target)
+ // .normalize()
+ // .multiplyScalar(-ARCADE_MODE_DISTANCE)
+ // .add(orbitControls.current.target);
+ // }
+ // } else {
+ // orbitControls.current.target.copy(shellOrigin);
- // camera.position
- // .copy(aimTarget)
- // .sub(shellOrigin)
- // .normalize()
- // .multiplyScalar(-Number.EPSILON)
- // .add(shellOrigin);
- // }
- // } else {
- // camera.position.set(...inspectModeInitialPosition);
- // orbitControls.current.target.set(0, gunHeight / (naked ? 4 : 2), 0);
- // orbitControls.current.enablePan = true;
- // orbitControls.current.enableZoom = true;
- // camera.fov = naked ? 20 : 25;
- // }
+ // camera.position
+ // .copy(aimTarget)
+ // .sub(shellOrigin)
+ // .normalize()
+ // .multiplyScalar(-Number.EPSILON)
+ // .add(shellOrigin);
+ // }
+ } else {
+ camera.position.set(...inspectModeInitialPosition);
+ orbitControls.current.target.set(0, gunHeight / (naked ? 4 : 2), 0);
+ orbitControls.current.enablePan = true;
+ orbitControls.current.enableZoom = true;
+ camera.fov = naked ? 20 : 25;
+ }
- // camera.updateProjectionMatrix();
- // }
+ camera.updateProjectionMatrix();
+ }
- // updateCamera();
+ updateCamera();
- // const unsubscribeDisplay = tankopediaEphemeralStore.subscribe(
- // (state) => state.display,
- // () => {
- // handleDisturbance();
- // updateCamera();
- // },
- // );
+ const unsubscribeDisplay = tankopediaEphemeralStore.subscribe(
+ (state) => state.display,
+ () => {
+ handleDisturbance();
+ updateCamera();
+ },
+ );
- // const unsubscribeShootingRangeZoom = tankopediaEphemeralStore.subscribe(
- // (state) => state.shootingRangeZoom,
- // updateCamera,
- // );
+ const unsubscribeShootingRangeZoom = tankopediaEphemeralStore.subscribe(
+ (state) => state.shootingRangeZoom,
+ updateCamera,
+ );
- // return () => {
- // canvas.removeEventListener('pointerdown', handleDisturbance);
- // poseEvent.off(handleDisturbance);
- // unsubscribeDisplay();
- // unsubscribeShootingRangeZoom();
- // window.removeEventListener('keydown', handleKeyDown);
- // canvas.removeEventListener('wheel', handleWheel);
- // document.body.removeEventListener('scroll', handleScroll);
- // };
- // }, [display === TankopediaDisplay.ShootingRange]);
+ return () => {
+ canvas.removeEventListener('pointerdown', handleDisturbance);
+ // poseEvent.off(handleDisturbance);
+ unsubscribeDisplay();
+ unsubscribeShootingRangeZoom();
+ // window.removeEventListener('keydown', handleKeyDown);
+ // canvas.removeEventListener('wheel', handleWheel);
+ // document.body.removeEventListener('scroll', handleScroll);
+ };
+ }, [display === TankopediaDisplay.ShootingRange]);
return (
MeshProps);
- group?: GroupProps | ((object3d: Object3D) => GroupProps);
+ mesh?: (mesh: Mesh, props: MeshProps) => ReactNode;
+ group?: (object3d: Object3D, props: Object3DProps) => ReactNode;
},
key?: string,
-) {
+): ReactNode {
if (node instanceof Mesh) {
- return (
-
- );
+ const props = {
+ key,
+ geometry: node.geometry,
+ material: node.material,
+ position: node.position,
+ rotation: node.rotation,
+ scale: node.scale,
+ };
+
+ return mergers?.mesh?.(node, props) ?? ;
} else if (node instanceof Object3D) {
- return (
-
- {node.children.map((child) => jsxTree(child, mergers, child.uuid))}
-
- );
+ const props = {
+ key,
+ position: node.position,
+ rotation: node.rotation,
+ scale: node.scale,
+ children: node.children.map((child) =>
+ jsxTree(child, mergers, child.uuid),
+ ),
+ };
+
+ return mergers?.group?.(node, props) ?? ;
}
return null;