feat: some model

This commit is contained in:
TrèsAbhi committed 2025-08-10 20:36:48 -05:00
1 parent c243ebd19a
commit 8ff26d57f6
11 files changed
+154 -67

No files matched your search

@@ -198,7 +198,7 @@ export const StaticArmor = memo<ArmorSceneProps>(({ thicknessRange }) => {
hasImprovedVerticalStabilizer, hasImprovedVerticalStabilizer,
hasDownImprovedVerticalStabilizer, hasDownImprovedVerticalStabilizer,
); );
modelTransformEvent.emit({ pitch, yaw }); modelTransformEvent.dispatch({ pitch, yaw });
} }
function handlePointerUp() { function handlePointerUp() {
mutateDuel((draft) => { mutateDuel((draft) => {
@@ -298,7 +298,7 @@ export const StaticArmor = memo<ArmorSceneProps>(({ thicknessRange }) => {
hasImprovedVerticalStabilizer, hasImprovedVerticalStabilizer,
hasDownImprovedVerticalStabilizer, hasDownImprovedVerticalStabilizer,
); );
modelTransformEvent.emit({ pitch, yaw }); modelTransformEvent.dispatch({ pitch, yaw });
} }
function handlePointerUp() { function handlePointerUp() {
mutateTankopediaEphemeral((draft) => { mutateTankopediaEphemeral((draft) => {
@@ -386,7 +386,7 @@ export const StaticArmor = memo<ArmorSceneProps>(({ thicknessRange }) => {
hasImprovedVerticalStabilizer, hasImprovedVerticalStabilizer,
hasDownImprovedVerticalStabilizer, hasDownImprovedVerticalStabilizer,
); );
modelTransformEvent.emit({ pitch, yaw }); modelTransformEvent.dispatch({ pitch, yaw });
} }
function handlePointerUp() { function handlePointerUp() {
mutateTankopediaEphemeral((draft) => { mutateTankopediaEphemeral((draft) => {
@@ -0,0 +1,44 @@
import { invalidate } from '@react-three/fiber';
import type { QuicklimeEvent } from 'quicklime';
import { useEffect, useRef } from 'react';
import type { Group } from 'three';
import {
modelTransformEvent,
type ModelTransformEventData,
} from '../../../../../../../../core/blitzkit/modelTransform';
import { ModelChunk } from './ModelChunk';
export function CanvasContent() {
const hull = useRef<Group>(null);
const gun = useRef<Group>(null);
useEffect(() => {
function handleModelTransformEvent(
event: QuicklimeEvent<ModelTransformEventData>,
) {
if (!gun.current) return;
gun.current.rotation.x = event.data.pitch;
invalidate();
}
modelTransformEvent.on(handleModelTransformEvent);
return () => {
modelTransformEvent.off(handleModelTransformEvent);
};
}, []);
return (
<>
<group ref={hull}>
<ModelChunk />
</group>
<group ref={gun}>
<ModelChunk onlyGun />
</group>
</>
);
}
@@ -1,46 +1,20 @@
import { Canvas } from '@react-three/fiber'; import { Canvas } from '@react-three/fiber';
import { awaitableModelDefinitions } from '../../../../../../../../core/awaitables/modelDefinitions'; import { CanvasContent } from './CanvasContent';
import { useModel } from '../../../../../../../../hooks/useModel';
import { Duel } from '../../../../../../../../stores/duel';
const modelDefinitions = await awaitableModelDefinitions; export const zoom = 60;
export function FlexibilityCanvas() { export function FlexibilityCanvas() {
const tank = Duel.use((state) => state.protagonist.tank);
const turret = Duel.use((state) => state.protagonist.turret);
const tankModel = modelDefinitions.models[tank.id];
const turretModel = tankModel.turrets[turret.id];
const { gltf } = useModel(tank.id);
const zoom = 60;
const originY = tankModel.turret_origin.z + turretModel.gun_origin.z + 1.5;
const originZ = tankModel.turret_origin.y + turretModel.gun_origin.y;
console.log(originY, originZ);
return ( return (
<Canvas <Canvas
frameloop="demand" // frameloop="demand"
orthographic orthographic
camera={{ camera={{
zoom, zoom,
position: [10, originZ, -originY], position: [10, 0, -2],
rotation: [0, Math.PI / 2, 0], rotation: [0, Math.PI / 2, 0],
}} }}
> >
<ambientLight intensity={2} /> <CanvasContent />
<directionalLight position={[1, 1, 1]} intensity={8} />
{/* <mesh>
<boxGeometry args={[1, 1, 1]} />
<meshNormalMaterial />
</mesh> */}
<group rotation={[-Math.PI / 2, 0, 0]}>
<primitive object={gltf.scene} />
</group>
</Canvas> </Canvas>
); );
} }
@@ -8,6 +8,7 @@ import {
applyPitchYawLimits, applyPitchYawLimits,
} from '../../../../../../../../core/blitz/applyPitchYawLimits'; } from '../../../../../../../../core/blitz/applyPitchYawLimits';
import { hasEquipment } from '../../../../../../../../core/blitzkit/hasEquipment'; import { hasEquipment } from '../../../../../../../../core/blitzkit/hasEquipment';
import { modelTransformEvent } from '../../../../../../../../core/blitzkit/modelTransform';
import { Var } from '../../../../../../../../core/radix/var'; import { Var } from '../../../../../../../../core/radix/var';
import { useLocale } from '../../../../../../../../hooks/useLocale'; import { useLocale } from '../../../../../../../../hooks/useLocale';
import { Duel } from '../../../../../../../../stores/duel'; import { Duel } from '../../../../../../../../stores/duel';
@@ -155,7 +156,7 @@ export function FlexibilityCard() {
const rect = container.current.getBoundingClientRect(); const rect = container.current.getBoundingClientRect();
const u = event.clientX - rect.left - rect.width / 2; const u = event.clientX - rect.left - rect.width / 2;
const v = event.clientY - rect.top - rect.height / 2; const v = event.clientY - rect.top - rect.height / 2;
const yaw = Math.atan2(u, -v); let yaw = Math.atan2(u, -v);
const { const {
equipmentMatrix, equipmentMatrix,
@@ -189,9 +190,14 @@ export function FlexibilityCard() {
hasDownImprovedVerticalStabilizer, hasDownImprovedVerticalStabilizer,
); );
setYaw(min[1]); yaw = min[1];
const pitch = min[0];
setYaw(yaw);
setMinPitch(min[0]); setMinPitch(min[0]);
setMaxPitch(max[0]); setMaxPitch(max[0]);
modelTransformEvent.dispatch({ pitch, yaw });
}} }}
mb="0" mb="0"
> >
@@ -229,7 +235,7 @@ export function FlexibilityCard() {
> >
<path <path
fillRule="evenodd" fillRule="evenodd"
fill="var(--gray-6)" fill="var(--gray-5)"
stroke="var(--gray-8)" stroke="var(--gray-8)"
strokeWidth="1px" strokeWidth="1px"
vectorEffect="non-scaling-stroke" vectorEffect="non-scaling-stroke"
@@ -262,7 +268,7 @@ export function FlexibilityCard() {
height="14rem" height="14rem"
style={{ style={{
borderRadius: Var('radius-1'), borderRadius: Var('radius-1'),
background: `linear-gradient(${Var('gray-a6')}, ${Var('gray-a11')})`, background: `linear-gradient(${Var('gray-a11')}, ${Var('gray-a6')})`,
transform: 'translateX(-50%)', transform: 'translateX(-50%)',
}} }}
/> />
@@ -0,0 +1,72 @@
import { mauveDark } from '@radix-ui/colors';
import { LineBasicMaterial, MeshBasicMaterial } from 'three';
import { awaitableModelDefinitions } from '../../../../../../../../core/awaitables/modelDefinitions';
import { jsxTree } from '../../../../../../../../core/blitzkit/jsxTree';
import { useModel } from '../../../../../../../../hooks/useModel';
import { Duel } from '../../../../../../../../stores/duel';
const modelDefinitions = await awaitableModelDefinitions;
const surfaceMaterial = new MeshBasicMaterial({
color: mauveDark.mauve6,
toneMapped: false,
});
const outlineMaterial = new LineBasicMaterial({
color: mauveDark.mauve8,
toneMapped: false,
});
interface Props {
onlyGun?: boolean;
}
export function ModelChunk({ onlyGun = false }: Props) {
const tank = Duel.use((state) => state.protagonist.tank);
const turret = Duel.use((state) => state.protagonist.turret);
const gun = Duel.use((state) => state.protagonist.gun);
const tankModel = modelDefinitions.models[tank.id];
const turretModel = tankModel.turrets[turret.id];
const gunModel = turretModel.guns[gun.gun_type!.value.base.id];
const originZ = tankModel.turret_origin.z + turretModel.gun_origin.z;
const originY = tankModel.turret_origin.y + turretModel.gun_origin.y;
const { gltf } = useModel(tank.id);
const nodes = Object.values(gltf.nodes);
return (
<group position={[0, -originY, originZ]} rotation={[-Math.PI / 2, 0, 0]}>
{nodes.map((node) => {
const isCurrentMantlet =
node.name ===
`gun_${gunModel.model_id.toString().padStart(2, '0')}_mask`;
const isCurrentGun =
node.name === `gun_${gunModel.model_id.toString().padStart(2, '0')}`;
const isVisible = isCurrentGun || isCurrentMantlet;
const isHull = node.name === 'hull';
const isWheel = node.name.startsWith('chassis_wheel_');
const isTrack = node.name.startsWith('chassis_track_');
const isCurrentTurret =
node.name ===
`turret_${turretModel.model_id.toString().padStart(2, '0')}`;
const isAnythingElse = isHull || isWheel || isTrack || isCurrentTurret;
if ((onlyGun && isVisible) || (!onlyGun && isAnythingElse)) {
return jsxTree(node, {
mesh(mesh, props, key) {
return (
<>
<mesh key={key} {...props} material={surfaceMaterial} />
<lineSegments material={outlineMaterial}>
<edgesGeometry args={[mesh.geometry, 30]} />
</lineSegments>
</>
);
},
});
}
})}
</group>
);
}
@@ -1,4 +1,4 @@
import { Box, Flex } from '@radix-ui/themes'; import { Box, Card, Flex } from '@radix-ui/themes';
import type { MaybeSkeletonComponentProps } from '../../../../../../../types/maybeSkeletonComponentProps'; import type { MaybeSkeletonComponentProps } from '../../../../../../../types/maybeSkeletonComponentProps';
import { FlexibilityCanvas } from './components/FlexibilityCanvas'; import { FlexibilityCanvas } from './components/FlexibilityCanvas';
import { FlexibilityCard } from './components/FlexibilityCard'; import { FlexibilityCard } from './components/FlexibilityCard';
@@ -7,25 +7,16 @@ export function GunFlexibilityVisualizer({
skeleton, skeleton,
}: MaybeSkeletonComponentProps) { }: MaybeSkeletonComponentProps) {
return ( return (
<Flex direction="column" gap="4"> <Flex direction="column-reverse" gap="0">
<FlexibilityCard /> <Box mt="-4" px="2">
<Card variant="classic" style={{ height: '10rem' }}>
<Box position="relative" style={{ aspectRatio: '2 / 1' }}> <Box position="absolute" width="100%" height="100%" top="0" left="0">
<Box position="absolute" width="100%" height="100%" top="0" left="0"> {!skeleton && <FlexibilityCanvas />}
{!skeleton && <FlexibilityCanvas />} </Box>
</Box> </Card>
<Box
position="absolute"
width="100%"
height="100%"
top="0"
left="0"
style={{
boxShadow: 'inset 0 -1rem 1rem var(--gray-1)',
}}
/>
</Box> </Box>
<FlexibilityCard />
</Flex> </Flex>
); );
} }
@@ -97,7 +97,7 @@ export function QuickInputs() {
hasImprovedVerticalStabilizer, hasImprovedVerticalStabilizer,
hasDownImprovedVerticalStabilizer, hasDownImprovedVerticalStabilizer,
); );
modelTransformEvent.emit({ pitch, yaw }); modelTransformEvent.dispatch({ pitch, yaw });
mutateDuel((state) => { mutateDuel((state) => {
state.protagonist.pitch = pitch; state.protagonist.pitch = pitch;
state.protagonist.yaw = yaw; state.protagonist.yaw = yaw;
@@ -140,7 +140,7 @@ export function QuickInputs() {
hasImprovedVerticalStabilizer, hasImprovedVerticalStabilizer,
hasDownImprovedVerticalStabilizer, hasDownImprovedVerticalStabilizer,
); );
modelTransformEvent.emit({ pitch, yaw }); modelTransformEvent.dispatch({ pitch, yaw });
mutateDuel((state) => { mutateDuel((state) => {
state.protagonist.pitch = pitch; state.protagonist.pitch = pitch;
state.protagonist.yaw = yaw; state.protagonist.yaw = yaw;
@@ -189,7 +189,7 @@ export function SceneProps() {
dispersion = Math.max(characteristics.dispersion * penalty, dispersion); dispersion = Math.max(characteristics.dispersion * penalty, dispersion);
} }
modelTransformEvent.emit({ pitch, yaw }); modelTransformEvent.dispatch({ pitch, yaw });
lastPitch = pitch; lastPitch = pitch;
lastYaw = yaw; lastYaw = yaw;
@@ -189,7 +189,7 @@ export function TankModel() {
hasImprovedVerticalStabilizer, hasImprovedVerticalStabilizer,
hasDownImprovedVerticalStabilizer, hasDownImprovedVerticalStabilizer,
); );
modelTransformEvent.emit({ pitch, yaw }); modelTransformEvent.dispatch({ pitch, yaw });
} }
function handlePointerUp() { function handlePointerUp() {
mutateDuel((draft) => { mutateDuel((draft) => {
@@ -296,7 +296,7 @@ export function TankModel() {
hasImprovedVerticalStabilizer, hasImprovedVerticalStabilizer,
hasDownImprovedVerticalStabilizer, hasDownImprovedVerticalStabilizer,
); );
modelTransformEvent.emit({ pitch, yaw }); modelTransformEvent.dispatch({ pitch, yaw });
} }
function handlePointerUp() { function handlePointerUp() {
mutateTankopediaEphemeral((draft) => { mutateTankopediaEphemeral((draft) => {
@@ -125,7 +125,7 @@ export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
hasDownImprovedVerticalStabilizer, hasDownImprovedVerticalStabilizer,
); );
modelTransformEvent.emit({ pitch, yaw }); modelTransformEvent.dispatch({ pitch, yaw });
draft.protagonist.pitch = pitch; draft.protagonist.pitch = pitch;
draft.protagonist.yaw = yaw; draft.protagonist.yaw = yaw;
}); });
@@ -143,7 +143,7 @@ export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
hasImprovedVerticalStabilizer, hasImprovedVerticalStabilizer,
); );
modelTransformEvent.emit({ pitch, yaw }); modelTransformEvent.dispatch({ pitch, yaw });
draft.protagonist.pitch = pitch; draft.protagonist.pitch = pitch;
draft.protagonist.yaw = yaw; draft.protagonist.yaw = yaw;
}); });
@@ -161,7 +161,7 @@ export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
hasImprovedVerticalStabilizer, hasImprovedVerticalStabilizer,
); );
modelTransformEvent.emit({ pitch, yaw }); modelTransformEvent.dispatch({ pitch, yaw });
draft.protagonist.pitch = pitch; draft.protagonist.pitch = pitch;
draft.protagonist.yaw = yaw; draft.protagonist.yaw = yaw;
}); });
@@ -1,8 +1,8 @@
import { EventManager } from '@blitzkit/core'; import { Quicklime } from 'quicklime';
export interface ModelTransformEventData { export interface ModelTransformEventData {
yaw?: number; yaw?: number;
pitch: number; pitch: number;
} }
export const modelTransformEvent = new EventManager<ModelTransformEventData>(); export const modelTransformEvent = new Quicklime<ModelTransformEventData>();