feat: more rendering

This commit is contained in:
tresabhi committed 2025-03-20 22:07:37 -05:00
1 parent 5b16e22750
commit cbd2c2a719
10 files changed
+315 -379

No files matched your search

@@ -5,16 +5,13 @@ import { Color, Material, MeshBasicMaterial } from 'three';
import { jsxTree } from '../../../core/blitzkit/jsxTree';
import { Duel } from '../../../stores/duel';
import { TankopediaEphemeral } from '../../../stores/tankopediaEphemeral';
interface ArmorSceneProps {
thicknessRange: ThicknessRange;
}
import { StaticArmorSceneComponents } from './StaticArmorSceneComponents';
export interface ThicknessRange {
value: number;
}
export const StaticArmor = memo<ArmorSceneProps>(({ thicknessRange }) => {
export const StaticArmor = memo(() => {
// const wrapper = useRef<Group>(null);
// const turretContainer = useRef<Group>(null);
// const gunContainer = useRef<Group>(null);
@@ -58,44 +55,11 @@ export const StaticArmor = memo<ArmorSceneProps>(({ thicknessRange }) => {
// useTankTransform(track, turret, turretContainer, gunContainer);
const tank = Duel.use((state) => state.protagonist.tank);
const armor = TankopediaEphemeral.use((state) => state.armor);
const hullArmor = armor.groups.hull;
const hullGltf = useGLTF(asset(`tanks/${tank.id}/collision/hull.glb`));
return (
<group>
{jsxTree(hullGltf.scene, {
mesh(mesh) {
if (!(mesh.material instanceof Material)) {
return { visible: false };
}
const armorName = mesh.material.name;
const material = new MeshBasicMaterial({});
const armorPlate = hullArmor.armors[armorName];
switch (armorPlate.type) {
case 'Armor':
material.color = new Color('#ec6142');
material.transparent = true;
material.opacity = 0.5;
break;
case 'ArmorScreen':
material.color = new Color('#ff8dcc');
material.transparent = true;
material.opacity = 0.5;
break;
default:
material.color = new Color('#70b8ff');
break;
}
return { material };
},
})}
<StaticArmorSceneComponents group="hull" />
</group>
);
});
@@ -1,272 +0,0 @@
import { I_HAT, J_HAT } from '@blitzkit/core';
import { type MeshProps, useThree } from '@react-three/fiber';
import { clamp } from 'lodash-es';
import { useEffect, useMemo } from 'react';
import {
Box3,
Color,
DoubleSide,
FrontSide,
LineBasicMaterial,
Mesh,
MeshStandardMaterial,
Object3D,
Plane,
Quaternion,
Vector3,
} from 'three';
import { unrotateDavaVector } from '../../../core/blitz/unrotateDavaVector';
import { jsxTree } from '../../../core/blitzkit/jsxTree';
import {
type ModelTransformEventData,
modelTransformEvent,
} from '../../../core/blitzkit/modelTransform';
import { discardClippingPlane } from '../../../core/three/discardClippingPlane';
import { TankopediaEphemeral } from '../../../stores/tankopediaEphemeral';
import { ArmorType } from './SpacedArmorScene';
import type {
ArmorUserData,
ExternalModuleVariant,
} from './SpacedArmorSceneComponent';
import type { ThicknessRange } from './StaticArmor';
type StaticArmorSceneComponentProps = {
name: string;
thickness: number;
thicknessRange: ThicknessRange;
node: Object3D;
onPointerDown?: MeshProps['onPointerDown'];
} & (
| {
type: Exclude<ArmorType, ArmorType.External>;
}
| {
type: ArmorType.External;
variant: ExternalModuleVariant;
}
) &
(
| {
clip?: undefined;
}
| {
clip: Plane;
hullOrigin: Vector3;
turretOrigin: Vector3;
gunOrigin: Vector3;
}
);
const unselectedColor = new Color(0x404040);
const externalModuleColor = new Color(192 / 255, 192 / 255, 192 / 255);
export function StaticArmorSceneComponent({
name,
thickness,
thicknessRange,
node,
onPointerDown,
...props
}: StaticArmorSceneComponentProps) {
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 && (
<lineSegments material={outlineMaterial}>
<edgesGeometry args={[node.geometry, 45]} />
</lineSegments>
)}
</>
);
}
@@ -0,0 +1,259 @@
import { asset } from '@blitzkit/core';
import { useGLTF } from '@react-three/drei';
import { Color, 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 = {
group: string;
};
const unselectedColor = new Color(0x404040);
const externalModuleColor = new Color(192 / 255, 192 / 255, 192 / 255);
export function StaticArmorSceneComponents({
group,
}: StaticArmorSceneComponentsProps) {
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 thicknessRange = TankopediaEphemeral.use(
(state) => state.thicknessRange,
);
return jsxTree(gltf.scene, {
mesh(mesh) {
if (!(mesh.material instanceof Material)) {
return { visible: false };
}
const armorName = mesh.material.name;
const armorPlate = groupArmor.armors[armorName];
const x = armorPlate.thickness / thicknessRange;
const xClamped = clamp(x, 0, 1);
const material = new MeshStandardMaterial();
switch (armorPlate.type) {
case 'Armor':
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':
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;
break;
}
material.opacity = clamp(material.opacity, 0, 1);
material.transparent = material.opacity < 1;
return { material };
},
});
// 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 && (
// <lineSegments material={outlineMaterial}>
// <edgesGeometry args={[node.geometry, 45]} />
// </lineSegments>
// )}
// </>
// );
}
@@ -4,27 +4,21 @@ import { Button, Checkbox, Flex, Text } from '@radix-ui/themes';
import { useLocale } from '../../../../../../hooks/useLocale';
import { TankopediaEphemeral } from '../../../../../../stores/tankopediaEphemeral';
import { TankopediaPersistent } from '../../../../../../stores/tankopediaPersistent';
import type { ThicknessRange } from '../../../../../Armor/components/StaticArmor';
interface ThicknessesProps {
thicknessRange: ThicknessRange;
}
export function Thicknesses({ thicknessRange }: ThicknessesProps) {
const showExternalModules = TankopediaPersistent.use(
(state) => state.showExternalModules,
);
const showSpacedArmor = TankopediaPersistent.use(
(state) => state.showSpacedArmor,
);
const showPrimaryArmor = TankopediaPersistent.use(
(state) => state.showPrimaryArmor,
export function Thicknesses() {
const showModules = TankopediaPersistent.use((state) => state.showModules);
const showArmorScreen = TankopediaPersistent.use(
(state) => state.showArmorScreen,
);
const showArmor = TankopediaPersistent.use((state) => state.showArmor);
const mutateTankopediaPersistent = TankopediaPersistent.useMutation();
const mutateTankopediaEphemeral = TankopediaEphemeral.useMutation();
const editStatic = TankopediaEphemeral.use((state) => state.editStatic);
const tankopediaEphemeralStore = TankopediaEphemeral.useStore();
const { strings } = useLocale();
const thicknessRange = TankopediaEphemeral.use(
(state) => state.thicknessRange,
);
return (
<Flex
@@ -41,10 +35,10 @@ export function Thicknesses({ thicknessRange }: ThicknessesProps) {
<Flex
height="64px"
gap="2"
style={{ opacity: showPrimaryArmor ? 1 : 0.5, cursor: 'pointer' }}
style={{ opacity: showArmor ? 1 : 0.5, cursor: 'pointer' }}
onClick={() => {
mutateTankopediaPersistent((draft) => {
draft.showPrimaryArmor = !draft.showPrimaryArmor;
draft.showArmor = !draft.showArmor;
});
mutateTankopediaEphemeral((draft) => {
draft.highlightArmor = undefined;
@@ -54,7 +48,7 @@ export function Thicknesses({ thicknessRange }: ThicknessesProps) {
<Flex direction="column" align="end" justify="between">
<Text color="gray" size="1">
{literals(strings.common.units.mm, [
(thicknessRange.value * 1.5).toFixed(0),
(thicknessRange * 1.5).toFixed(0),
])}
</Text>
<Text size="1">
@@ -75,17 +69,17 @@ export function Thicknesses({ thicknessRange }: ThicknessesProps) {
color: 'black',
}}
>
{showPrimaryArmor ? <EyeOpenIcon /> : <EyeClosedIcon />}
{showArmor ? <EyeOpenIcon /> : <EyeClosedIcon />}
</Flex>
</Flex>
<Flex
height="64px"
gap="2"
style={{ opacity: showSpacedArmor ? 1 : 0.5, cursor: 'pointer' }}
style={{ opacity: showArmorScreen ? 1 : 0.5, cursor: 'pointer' }}
onClick={() => {
mutateTankopediaPersistent((draft) => {
draft.showSpacedArmor = !draft.showSpacedArmor;
draft.showArmorScreen = !draft.showArmorScreen;
});
mutateTankopediaEphemeral((draft) => {
draft.highlightArmor = undefined;
@@ -94,9 +88,7 @@ export function Thicknesses({ thicknessRange }: ThicknessesProps) {
>
<Flex direction="column" align="end" justify="between">
<Text color="gray" size="1">
{literals(strings.common.units.mm, [
thicknessRange.value.toFixed(0),
])}
{literals(strings.common.units.mm, [thicknessRange.toFixed(0)])}
</Text>
<Text size="1">
{strings.website.tools.tanks.sandbox.static.spaced}
@@ -115,16 +107,16 @@ export function Thicknesses({ thicknessRange }: ThicknessesProps) {
background: `linear-gradient(rgb(32, 0, 225), rgb(255, 0, 255))`,
}}
>
{showSpacedArmor ? <EyeOpenIcon /> : <EyeClosedIcon />}
{showArmorScreen ? <EyeOpenIcon /> : <EyeClosedIcon />}
</Flex>
</Flex>
<Flex
gap="2"
style={{ opacity: showExternalModules ? 1 : 0.5, cursor: 'pointer' }}
style={{ opacity: showModules ? 1 : 0.5, cursor: 'pointer' }}
onClick={() => {
mutateTankopediaPersistent((draft) => {
draft.showExternalModules = !draft.showExternalModules;
draft.showModules = !draft.showModules;
});
mutateTankopediaEphemeral((draft) => {
draft.highlightArmor = undefined;
@@ -148,7 +140,7 @@ export function Thicknesses({ thicknessRange }: ThicknessesProps) {
color: 'black',
}}
>
{showExternalModules ? <EyeOpenIcon /> : <EyeClosedIcon />}
{showModules ? <EyeOpenIcon /> : <EyeClosedIcon />}
</Flex>
</Flex>
</Flex>
@@ -179,7 +171,7 @@ export function Thicknesses({ thicknessRange }: ThicknessesProps) {
onClick={() => {
mutateTankopediaEphemeral((draft) => {
draft.editStatic = false;
draft.model = tankopediaEphemeralStore.getInitialState().model;
draft.armor = tankopediaEphemeralStore.getInitialState().armor;
});
}}
>
@@ -12,15 +12,13 @@ import {
} from '../../../../../stores/tankopediaEphemeral';
import { TankopediaPersistent } from '../../../../../stores/tankopediaPersistent';
import { TankopediaDisplay } from '../../../../../stores/tankopediaPersistent/constants';
import type { ThicknessRange } from '../../../../Armor/components/StaticArmor';
import { Thicknesses } from './components/Thicknesses';
interface OptionsProps {
thicknessRange: ThicknessRange;
canvas: RefObject<HTMLCanvasElement>;
}
export function Options({ thicknessRange, canvas }: OptionsProps) {
export function Options({ canvas }: OptionsProps) {
const hasCustomShell = TankopediaEphemeral.use(
(state) => state.customShell !== undefined,
);
@@ -75,9 +73,7 @@ export function Options({ thicknessRange, canvas }: OptionsProps) {
/>
)}
{display === TankopediaDisplay.StaticArmor && (
<Thicknesses thicknessRange={thicknessRange} />
)}
{display === TankopediaDisplay.StaticArmor && <Thicknesses />}
{/* {display === TankopediaDisplay.DynamicArmor && (
<Flex
@@ -8,10 +8,7 @@ import { TankopediaDisplay } from '../../../../../stores/tankopediaPersistent/co
import { Armor } from '../../../../Armor';
import { ArmorPlateDisplay } from '../../../../Armor/components/ArmorPlateDisplay';
import { ShotDisplay } from '../../../../Armor/components/ShotDisplay';
import {
StaticArmor,
type ThicknessRange,
} from '../../../../Armor/components/StaticArmor';
import { StaticArmor } from '../../../../Armor/components/StaticArmor';
import { AutoClear } from './components/AutoClear';
import { Controls } from './components/Control';
import { Lighting } from './components/Lighting';
@@ -20,12 +17,11 @@ import { SceneProps } from './components/SceneProps';
import { TankModel } from './components/TankModel';
interface TankSandboxProps {
thicknessRange: ThicknessRange;
naked?: boolean;
}
export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
({ thicknessRange, naked }, ref) => {
({ naked }, ref) => {
const mutateTankopediaEphemeral = TankopediaEphemeral.useMutation();
const canvas = useRef<HTMLCanvasElement>(null);
// const hasImprovedVerticalStabilizer = useEquipment(122);
@@ -193,9 +189,7 @@ export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
<Suspense fallback={<ModelLoader />}>
{display === TankopediaDisplay.DynamicArmor && <Armor />}
{display === TankopediaDisplay.StaticArmor && (
<StaticArmor thicknessRange={thicknessRange} />
)}
{display === TankopediaDisplay.StaticArmor && <StaticArmor />}
<Lighting />
</Suspense>
</Canvas>
@@ -42,7 +42,6 @@ export function HeroSection({ skeleton }: MaybeSkeletonComponentProps) {
// return { value } satisfies ThicknessRange;
// }, [protagonist]);
const thicknessRange = { value: 300 };
// const duelStore = Duel.useStore();
// const mutateDuel = Duel.useMutation();
// const mutateTankopediaEphemeral = TankopediaEphemeral.useMutation();
@@ -140,11 +139,11 @@ export function HeroSection({ skeleton }: MaybeSkeletonComponentProps) {
{skeleton && <TankSandboxLoader id={protagonist.id} />}
<Suspense fallback={<TankSandboxLoader id={protagonist.id} />}>
<TankSandbox ref={canvas} thicknessRange={thicknessRange} />
<TankSandbox ref={canvas} />
</Suspense>
</Box>
<Options canvas={canvas} thicknessRange={thicknessRange} />
<Options canvas={canvas} />
</Box>
</Box>
</Flex>
@@ -20,7 +20,7 @@ type PageProps = MaybeSkeletonComponentProps &
export function Page({ tank, armor, skeleton, locale }: PageProps) {
return (
<LocaleProvider locale={locale}>
<TankopediaEphemeral.Provider data={armor}>
<TankopediaEphemeral.Provider data={{ armor, thicknessRange: 193 }}>
<App.Provider>
<TankopediaPersistent.Provider>
<Duel.Provider
@@ -96,6 +96,7 @@ export const SHOOTING_RANGE_ZOOM_COEFFICIENTS: Record<
};
interface TankopediaEphemeral {
thicknessRange: number;
shot?: Shot;
shootingRangeZoom: ShootingRangeZoom;
skills: Record<string, number>;
@@ -124,17 +125,20 @@ interface TankopediaEphemeral {
const skillDefinitions = await awaitableSkillDefinitions;
export const TankopediaEphemeral = createContextualStore((armor: TankArmor) => {
return create<TankopediaEphemeral>()(
subscribeWithSelector<TankopediaEphemeral>(() => ({
shootingRangeZoom: ShootingRangeZoom.Arcade,
relativeAgainst: TankopediaRelativeAgainst.Class,
editStatic: false,
skills: createDefaultSkills(skillDefinitions),
armor,
controlsEnabled: true,
xpMultiplier: 1,
display: TankopediaDisplay.StaticArmor,
})),
);
});
export const TankopediaEphemeral = createContextualStore(
({ armor, thicknessRange }: { armor: TankArmor; thicknessRange: number }) => {
return create<TankopediaEphemeral>()(
subscribeWithSelector<TankopediaEphemeral>(() => ({
thicknessRange,
shootingRangeZoom: ShootingRangeZoom.Arcade,
relativeAgainst: TankopediaRelativeAgainst.Class,
editStatic: false,
skills: createDefaultSkills(skillDefinitions),
armor,
controlsEnabled: true,
xpMultiplier: 1,
display: TankopediaDisplay.StaticArmor,
})),
);
},
);
@@ -14,9 +14,9 @@ export interface TankopediaPersistentStore {
advancedHighlighting: boolean;
showGrid: boolean;
greenPenetration: boolean;
showSpacedArmor: boolean;
showExternalModules: boolean;
showPrimaryArmor: boolean;
showArmorScreen: boolean;
showModules: boolean;
showArmor: boolean;
recentlyViewed: number[];
hideTankModelUnderArmor: boolean;
@@ -36,9 +36,9 @@ export const TankopediaPersistent = createContextualStore(() =>
advancedHighlighting: false,
showGrid: true,
greenPenetration: false,
showSpacedArmor: true,
showExternalModules: true,
showPrimaryArmor: true,
showArmorScreen: true,
showModules: true,
showArmor: true,
recentlyViewed: [],
sort: {
by: 'meta.none',