feat: i tried my best to implement dynamic armor :(

This commit is contained in:
TrèsAbhi committed 2024-09-04 22:15:19 -05:00
1 parent 952523d3f9
commit 46f1461de6
9 files changed
+182 -92

No files matched your search

@@ -6,7 +6,6 @@ import {
useImperativeHandle,
useRef,
} from 'react';
import { Armor } from '../../../../../../components/Armor';
import { ArmorPlateDisplay } from '../../../../../../components/Armor/components/ArmorPlateDisplay';
import { ShotDisplay } from '../../../../../../components/Armor/components/ShotDisplay';
import {
@@ -185,7 +184,7 @@ export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
{/* idk why the shot display doesn't work without suspense here lol */}
<Suspense fallback={<ModelLoader />}>
{display === TankopediaDisplay.DynamicArmor && <Armor />}
{/* {display === TankopediaDisplay.DynamicArmor && <Armor />} */}
{display === TankopediaDisplay.StaticArmor && (
<StaticArmor thicknessRange={thicknessRange} />
)}
@@ -31,6 +31,9 @@ export const TankModel = memo(() => {
const gunModelDefinition = turretModelDefinition.guns[protagonist.gun.id];
const { gltf } = useModel(protagonist.tank.id);
const nodes = Object.values(gltf.nodes);
const isDynamicArmorActive = Duel.use((state) =>
state.protagonist.consumables.includes(73),
);
useTankTransform(protagonist, turretContainer, gunContainer);
@@ -100,9 +103,28 @@ export const TankModel = memo(() => {
return jsxTree(
node,
{
castShadow: true,
receiveShadow: true,
onPointerDown,
mesh: {
castShadow: true,
receiveShadow: true,
onPointerDown,
},
// group(group) {
// console.log(group.name);
// 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'))),
// };
// },
},
node.uuid,
);
@@ -178,9 +200,28 @@ export const TankModel = memo(() => {
return jsxTree(
node,
{
castShadow: true,
receiveShadow: true,
onPointerDown,
mesh: {
castShadow: true,
receiveShadow: true,
onPointerDown,
},
// 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'))),
// };
// },
},
node.uuid,
);
@@ -258,9 +299,28 @@ export const TankModel = memo(() => {
return jsxTree(
node,
{
castShadow: true,
receiveShadow: true,
onPointerDown,
mesh: {
castShadow: true,
receiveShadow: true,
onPointerDown,
},
// 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'))),
// };
// },
},
node.uuid,
);
@@ -156,10 +156,14 @@ export function PrimaryArmorSceneComponent({
<>
{jsxTree(
node,
{ renderOrder: 0, material: excludeMaterial },
{ mesh: { renderOrder: 0, material: excludeMaterial } },
`${node.uuid}-exclude`,
)}
{jsxTree(node, { renderOrder: 1, material }, `${node.uuid}-include`)}
{jsxTree(
node,
{ mesh: { renderOrder: 1, material } },
`${node.uuid}-include`,
)}
</>
);
}
@@ -113,24 +113,30 @@ export function SpacedArmorSubExternal({
{jsxTree(
node,
{
renderOrder: 3,
material: new MeshBasicMaterial({
colorWrite: false,
depthTest: true,
depthWrite: true,
...(clip ? { clippingPlanes: [clip] } : {}),
}),
onClick() {},
userData: {
type: ArmorType.External,
thickness,
variant,
} satisfies ArmorUserData,
mesh: {
renderOrder: 3,
material: new MeshBasicMaterial({
colorWrite: false,
depthTest: true,
depthWrite: true,
...(clip ? { clippingPlanes: [clip] } : {}),
}),
onClick() {},
userData: {
type: ArmorType.External,
thickness,
variant,
} satisfies ArmorUserData,
},
},
`${node.uuid}-exclude`,
)}
{jsxTree(node, { renderOrder: 4, material }, `${node.uuid}-include`)}
{jsxTree(
node,
{ mesh: { renderOrder: 4, material } },
`${node.uuid}-include`,
)}
</>
);
}
@@ -123,13 +123,15 @@ export function SpacedArmorSubSpaced({
{jsxTree(
node,
{
renderOrder: 2,
material,
onClick() {},
userData: {
type: ArmorType.Spaced,
thickness,
} satisfies ArmorUserData,
mesh: {
renderOrder: 2,
material,
onClick() {},
userData: {
type: ArmorType.Spaced,
thickness,
} satisfies ArmorUserData,
},
},
`${node.uuid}-exclude`,
)}
@@ -137,8 +139,10 @@ export function SpacedArmorSubSpaced({
{jsxTree(
node,
{
renderOrder: 5,
material: depthWriteMaterial,
mesh: {
renderOrder: 5,
material: depthWriteMaterial,
},
},
`${node.uuid}-include`,
)}
@@ -371,20 +371,22 @@ export function SpacedArmorSceneComponent({
jsxTree(
node,
{
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 });
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 });
},
},
},
node.uuid,
@@ -215,46 +215,50 @@ export function StaticArmorSceneComponent({
return (
<>
{jsxTree(node, {
material: surfaceMaterial,
onPointerDown,
userData: {
type: props.type,
variant: props.type === ArmorType.External ? props.variant : 'gun',
thickness,
} satisfies ArmorUserData,
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;
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,
};
});
},
},
})}
+18 -4
View File
@@ -1,7 +1,14 @@
import { MeshProps } from '@react-three/fiber';
import { GroupProps, MeshProps } from '@react-three/fiber';
import { Mesh, Object3D } from 'three';
export function jsxTree(node: Object3D, mesh?: MeshProps, key?: string) {
export function jsxTree(
node: Object3D,
mergers?: {
mesh?: MeshProps | ((mesh: Mesh) => MeshProps);
group?: GroupProps | ((object3d: Object3D) => GroupProps);
},
key?: string,
) {
if (node instanceof Mesh) {
return (
<mesh
@@ -11,7 +18,10 @@ export function jsxTree(node: Object3D, mesh?: MeshProps, key?: string) {
position={node.position}
rotation={node.rotation}
scale={node.scale}
{...mesh}
{...(mergers?.mesh &&
(typeof mergers.mesh === 'function'
? mergers.mesh(node)
: mergers.mesh))}
/>
);
} else if (node instanceof Object3D) {
@@ -21,8 +31,12 @@ export function jsxTree(node: Object3D, mesh?: MeshProps, key?: string) {
position={node.position}
rotation={node.rotation}
scale={node.scale}
{...(mergers?.group &&
(typeof mergers.group === 'function'
? mergers.group(node)
: mergers.group))}
>
{node.children.map((child) => jsxTree(child, mesh, child.uuid))}
{node.children.map((child) => jsxTree(child, mergers, child.uuid))}
</group>
);
}
-3
View File
@@ -9,9 +9,6 @@ const cache: Record<
export function useArmor(id: number) {
const gltf = useLoader(GLTFLoader, asset(`3d/tanks/armor/${id}.glb`));
console.log(Object.values(gltf.nodes).map((node) => node.name));
if (!cache[id]) {
cache[id] = {
gltf,