mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-21 10:47:04 +02:00
feat: i tried my best to implement dynamic armor :(
This commit is contained in:
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`,
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
+20
-14
@@ -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`,
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
+13
-9
@@ -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,
|
||||
};
|
||||
});
|
||||
},
|
||||
},
|
||||
})}
|
||||
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in new issue
Block a user