mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-24 12:09:51 +02:00
feat: code split containers
This commit is contained in:
1 parent
39e4bce254
commit
56d8392f12
8 files changed
+395
-308
No files matched your search
Binary file not shown.
@@ -587,7 +587,8 @@ if (allTargets || targets?.includes('tankModels')) {
|
|||||||
|
|
||||||
// if (id !== 15697) continue; // chieftain TODO: investigate vertices stream over read
|
// if (id !== 15697) continue; // chieftain TODO: investigate vertices stream over read
|
||||||
// if (id !== 24609) continue; // concept 1b
|
// if (id !== 24609) continue; // concept 1b
|
||||||
if (id !== 7425) continue; // isu 152
|
// if (id !== 7425) continue; // isu 152
|
||||||
|
if (id !== 10369) continue; // mino
|
||||||
// if (id !== 4417) continue; // amx m4 mle
|
// if (id !== 4417) continue; // amx m4 mle
|
||||||
// if (id !== 7297) continue; // 60tp
|
// if (id !== 7297) continue; // 60tp
|
||||||
// if (id !== 1) continue; // t-34
|
// if (id !== 1) continue; // t-34
|
||||||
|
|||||||
@@ -0,0 +1,135 @@
|
|||||||
|
import { ThreeEvent, useThree } from '@react-three/fiber';
|
||||||
|
import { clamp } from 'lodash';
|
||||||
|
import { RefObject, useRef } from 'react';
|
||||||
|
import { Group, Mesh, Object3D, Vector3 } from 'three';
|
||||||
|
import {
|
||||||
|
PitchLimits,
|
||||||
|
YawLimits,
|
||||||
|
} from '../../../../../core/blitzkrieg/modelDefinitions';
|
||||||
|
import { resolveJsxTree } from '../../../../../core/blitzkrieg/resolveJsxTree';
|
||||||
|
import { normalizeAnglePI } from '../../../../../core/math/normalizeAngle180';
|
||||||
|
|
||||||
|
interface GunContainerProps {
|
||||||
|
turretOrigin: Vector3;
|
||||||
|
gunOrigin: Vector3;
|
||||||
|
gunPitch: number;
|
||||||
|
turretYaw: number;
|
||||||
|
objects: Object3D[];
|
||||||
|
model: number;
|
||||||
|
yawLimits?: YawLimits;
|
||||||
|
pitchLimits: PitchLimits;
|
||||||
|
onPitchStart: () => void;
|
||||||
|
onPitchEnd: (pitch: number, yaw: number) => void;
|
||||||
|
turretContainer: RefObject<Group>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function GunContainer({
|
||||||
|
gunOrigin,
|
||||||
|
turretOrigin,
|
||||||
|
gunPitch,
|
||||||
|
turretYaw,
|
||||||
|
objects,
|
||||||
|
yawLimits,
|
||||||
|
onPitchStart,
|
||||||
|
turretContainer,
|
||||||
|
pitchLimits,
|
||||||
|
model,
|
||||||
|
onPitchEnd,
|
||||||
|
}: GunContainerProps) {
|
||||||
|
const canvas = useThree((state) => state.gl.domElement);
|
||||||
|
const gunContainer = useRef<Group>(null);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<group
|
||||||
|
position={new Vector3()
|
||||||
|
.sub(turretOrigin)
|
||||||
|
.sub(gunOrigin)
|
||||||
|
.applyAxisAngle(new Vector3(1, 0, 0), gunPitch)
|
||||||
|
.add(turretOrigin)
|
||||||
|
.add(gunOrigin)}
|
||||||
|
rotation={[gunPitch, 0, 0]}
|
||||||
|
ref={gunContainer}
|
||||||
|
>
|
||||||
|
{objects.map((object) => {
|
||||||
|
const isCurrentMantlet =
|
||||||
|
object.name === `gun_${model.toString().padStart(2, '0')}_mask`;
|
||||||
|
const isCurrentGun =
|
||||||
|
object.name === `gun_${model.toString().padStart(2, '0')}`;
|
||||||
|
const isVisible = isCurrentGun || isCurrentMantlet;
|
||||||
|
let draftMantletPitch = 0;
|
||||||
|
let draftTurretYaw = 0;
|
||||||
|
|
||||||
|
function handlePointerDown(event: ThreeEvent<PointerEvent>) {
|
||||||
|
event.stopPropagation();
|
||||||
|
|
||||||
|
onPitchStart();
|
||||||
|
draftMantletPitch = gunPitch;
|
||||||
|
draftTurretYaw = turretYaw;
|
||||||
|
window.addEventListener('pointermove', handlePointerMove);
|
||||||
|
window.addEventListener('pointerup', handlePointerUp);
|
||||||
|
}
|
||||||
|
function handlePointerMove(event: PointerEvent) {
|
||||||
|
if (yawLimits) {
|
||||||
|
draftTurretYaw = clamp(
|
||||||
|
draftTurretYaw + event.movementX * (Math.PI / canvas.width),
|
||||||
|
-yawLimits.max * (Math.PI / 180),
|
||||||
|
-yawLimits.min * (Math.PI / 180),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
draftTurretYaw += event.movementX * ((2 * Math.PI) / canvas.width);
|
||||||
|
}
|
||||||
|
draftMantletPitch = clamp(
|
||||||
|
draftMantletPitch - event.movementY * (Math.PI / canvas.height),
|
||||||
|
-pitchLimits.max * (Math.PI / 180),
|
||||||
|
-pitchLimits.min * (Math.PI / 180),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (gunContainer.current) {
|
||||||
|
gunContainer.current.position
|
||||||
|
.set(0, 0, 0)
|
||||||
|
.sub(turretOrigin)
|
||||||
|
.sub(gunOrigin)
|
||||||
|
.applyAxisAngle(new Vector3(1, 0, 0), draftMantletPitch)
|
||||||
|
.add(turretOrigin)
|
||||||
|
.add(gunOrigin);
|
||||||
|
gunContainer.current.rotation.x = draftMantletPitch;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (turretContainer.current) {
|
||||||
|
turretContainer.current.position
|
||||||
|
.set(0, 0, 0)
|
||||||
|
.sub(turretOrigin)
|
||||||
|
.applyAxisAngle(new Vector3(0, 0, 1), draftTurretYaw)
|
||||||
|
.add(turretOrigin);
|
||||||
|
turretContainer.current.rotation.z = draftTurretYaw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function handlePointerUp() {
|
||||||
|
onPitchEnd(
|
||||||
|
normalizeAnglePI(draftMantletPitch),
|
||||||
|
normalizeAnglePI(draftTurretYaw),
|
||||||
|
);
|
||||||
|
window.removeEventListener('pointermove', handlePointerMove);
|
||||||
|
window.removeEventListener('pointerup', handlePointerUp);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isVisible) return null;
|
||||||
|
return (
|
||||||
|
<mesh
|
||||||
|
visible={isVisible}
|
||||||
|
onPointerDown={handlePointerDown}
|
||||||
|
children={resolveJsxTree(object)}
|
||||||
|
key={object.uuid}
|
||||||
|
castShadow
|
||||||
|
receiveShadow
|
||||||
|
geometry={(object as Mesh).geometry}
|
||||||
|
material={(object as Mesh).material}
|
||||||
|
position={object.position}
|
||||||
|
rotation={object.rotation}
|
||||||
|
scale={object.scale}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</group>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
import { ThreeEvent, useThree } from '@react-three/fiber';
|
||||||
|
import { ReactNode, forwardRef, useImperativeHandle, useRef } from 'react';
|
||||||
|
import { Group, Mesh, Object3D } from 'three';
|
||||||
|
import { resolveJsxTree } from '../../../../../core/blitzkrieg/resolveJsxTree';
|
||||||
|
import { normalizeAnglePI } from '../../../../../core/math/normalizeAngle180';
|
||||||
|
|
||||||
|
interface HullContainerProps {
|
||||||
|
children: ReactNode;
|
||||||
|
yaw: number;
|
||||||
|
objects: Object3D[];
|
||||||
|
onYawStart: () => void;
|
||||||
|
onYawEnd: (yaw: number) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const HullContainer = forwardRef<Group, HullContainerProps>(
|
||||||
|
({ children, yaw, objects, onYawStart, onYawEnd }, ref) => {
|
||||||
|
const canvas = useThree((state) => state.gl.domElement);
|
||||||
|
const hullContainer = useRef<Group>(null);
|
||||||
|
|
||||||
|
useImperativeHandle(ref, () => hullContainer.current!);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<group ref={hullContainer} rotation={[-Math.PI / 2, 0, yaw]}>
|
||||||
|
{objects.map((object) => {
|
||||||
|
const isHull = object.name === 'hull';
|
||||||
|
const isWheel = object.name.startsWith('chassis_wheel_');
|
||||||
|
const isTrack = object.name.startsWith('chassis_track_');
|
||||||
|
const isVisible = isHull || isWheel || isTrack;
|
||||||
|
let draftHullYaw = 0;
|
||||||
|
|
||||||
|
function handlePointerDown(event: ThreeEvent<PointerEvent>) {
|
||||||
|
event.stopPropagation();
|
||||||
|
|
||||||
|
draftHullYaw = yaw;
|
||||||
|
|
||||||
|
onYawStart();
|
||||||
|
window.addEventListener('pointermove', handlePointerMove);
|
||||||
|
window.addEventListener('pointerup', handlePointerUp);
|
||||||
|
}
|
||||||
|
function handlePointerMove(event: PointerEvent) {
|
||||||
|
draftHullYaw += event.movementX * ((2 * Math.PI) / canvas.width);
|
||||||
|
|
||||||
|
if (hullContainer.current) {
|
||||||
|
hullContainer.current.rotation.z = draftHullYaw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function handlePointerUp() {
|
||||||
|
onYawEnd(normalizeAnglePI(draftHullYaw));
|
||||||
|
window.removeEventListener('pointermove', handlePointerMove);
|
||||||
|
window.removeEventListener('pointerup', handlePointerUp);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isVisible) return null;
|
||||||
|
return (
|
||||||
|
<mesh
|
||||||
|
children={resolveJsxTree(object)}
|
||||||
|
key={object.uuid}
|
||||||
|
castShadow
|
||||||
|
receiveShadow
|
||||||
|
geometry={(object as Mesh).geometry}
|
||||||
|
material={(object as Mesh).material}
|
||||||
|
position={object.position}
|
||||||
|
rotation={object.rotation}
|
||||||
|
scale={object.scale}
|
||||||
|
onPointerDown={handlePointerDown}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
|
||||||
|
{children}
|
||||||
|
</group>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { Color } from 'three';
|
||||||
|
import InfiniteGridHelper from '../../../../../components/InfiniteGridHelper';
|
||||||
|
|
||||||
|
export function SceneProps() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<InfiniteGridHelper
|
||||||
|
size1={1 / 5}
|
||||||
|
size2={1}
|
||||||
|
distance={20}
|
||||||
|
color={new Color('#ffffff')}
|
||||||
|
/>
|
||||||
|
<InfiniteGridHelper
|
||||||
|
position={[0, 1e-4, 0]}
|
||||||
|
size1={0}
|
||||||
|
size2={100}
|
||||||
|
distance={25}
|
||||||
|
color={new Color('red')}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<directionalLight position={[1, 1, -1]} intensity={1} castShadow />
|
||||||
|
<directionalLight position={[-1, 1, 1]} intensity={2} castShadow />
|
||||||
|
<ambientLight intensity={0.25} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
import { ThreeEvent, useThree } from '@react-three/fiber';
|
||||||
|
import { ReactNode, forwardRef, useImperativeHandle, useRef } from 'react';
|
||||||
|
import { Group, Mesh, Object3D, Vector3 } from 'three';
|
||||||
|
import { resolveJsxTree } from '../../../../../core/blitzkrieg/resolveJsxTree';
|
||||||
|
import { normalizeAnglePI } from '../../../../../core/math/normalizeAngle180';
|
||||||
|
|
||||||
|
interface TurretContainerProps {
|
||||||
|
origin: Vector3;
|
||||||
|
objects: Object3D[];
|
||||||
|
children: ReactNode;
|
||||||
|
yaw: number;
|
||||||
|
model: number;
|
||||||
|
onYawStart: () => void;
|
||||||
|
onYawEnd: (yaw: number) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TurretContainer = forwardRef(
|
||||||
|
(
|
||||||
|
{
|
||||||
|
origin,
|
||||||
|
yaw,
|
||||||
|
children,
|
||||||
|
objects,
|
||||||
|
model,
|
||||||
|
onYawEnd,
|
||||||
|
onYawStart,
|
||||||
|
}: TurretContainerProps,
|
||||||
|
ref,
|
||||||
|
) => {
|
||||||
|
const canvas = useThree((state) => state.gl.domElement);
|
||||||
|
const turretContainer = useRef<Group>(null);
|
||||||
|
|
||||||
|
useImperativeHandle(ref, () => turretContainer.current!);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<group
|
||||||
|
position={new Vector3()
|
||||||
|
.sub(origin)
|
||||||
|
.applyAxisAngle(new Vector3(0, 0, 1), yaw)
|
||||||
|
.add(origin)}
|
||||||
|
rotation={[0, 0, yaw]}
|
||||||
|
ref={turretContainer}
|
||||||
|
>
|
||||||
|
{objects.map((object) => {
|
||||||
|
const isTurret = object.name.startsWith('turret_');
|
||||||
|
const isCurrentTurret =
|
||||||
|
object.name === `turret_${model.toString().padStart(2, '0')}`;
|
||||||
|
const isVisible = isCurrentTurret;
|
||||||
|
let draftYaw = 0;
|
||||||
|
|
||||||
|
function handlePointerDown(event: ThreeEvent<PointerEvent>) {
|
||||||
|
event.stopPropagation();
|
||||||
|
|
||||||
|
if (isTurret) {
|
||||||
|
draftYaw = yaw;
|
||||||
|
|
||||||
|
onYawStart();
|
||||||
|
window.addEventListener('pointermove', handlePointerMove);
|
||||||
|
window.addEventListener('pointerup', handlePointerUp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function handlePointerMove(event: PointerEvent) {
|
||||||
|
draftYaw += event.movementX * ((2 * Math.PI) / canvas.width);
|
||||||
|
|
||||||
|
if (turretContainer.current) {
|
||||||
|
turretContainer.current.position
|
||||||
|
.set(0, 0, 0)
|
||||||
|
.sub(origin)
|
||||||
|
.applyAxisAngle(new Vector3(0, 0, 1), draftYaw)
|
||||||
|
.add(origin);
|
||||||
|
turretContainer.current.rotation.z = draftYaw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function handlePointerUp() {
|
||||||
|
onYawEnd(normalizeAnglePI(draftYaw));
|
||||||
|
window.removeEventListener('pointermove', handlePointerMove);
|
||||||
|
window.removeEventListener('pointerup', handlePointerUp);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isVisible) return null;
|
||||||
|
return (
|
||||||
|
<mesh
|
||||||
|
visible={isVisible}
|
||||||
|
onPointerDown={handlePointerDown}
|
||||||
|
children={resolveJsxTree(object)}
|
||||||
|
key={object.uuid}
|
||||||
|
castShadow
|
||||||
|
receiveShadow
|
||||||
|
geometry={(object as Mesh).geometry}
|
||||||
|
material={(object as Mesh).material}
|
||||||
|
position={object.position}
|
||||||
|
rotation={object.rotation}
|
||||||
|
scale={object.scale}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
|
||||||
|
{children}
|
||||||
|
</group>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
@@ -19,20 +19,18 @@ import {
|
|||||||
Theme,
|
Theme,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
} from '@radix-ui/themes';
|
} from '@radix-ui/themes';
|
||||||
import { Canvas, ThreeEvent, useLoader } from '@react-three/fiber';
|
import { Canvas, useLoader } from '@react-three/fiber';
|
||||||
import { go } from 'fuzzysort';
|
import { go } from 'fuzzysort';
|
||||||
import { clamp, debounce } from 'lodash';
|
import { clamp, debounce } from 'lodash';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { use, useEffect, useRef, useState } from 'react';
|
import { use, useEffect, useRef, useState } from 'react';
|
||||||
import { Color, Group, Mesh, Vector3 } from 'three';
|
import { Group, Vector3 } from 'three';
|
||||||
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
|
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
|
||||||
import { Flag } from '../../../../components/Flag';
|
import { Flag } from '../../../../components/Flag';
|
||||||
import InfiniteGridHelper from '../../../../components/InfiniteGridHelper';
|
|
||||||
import { ModuleButtons } from '../../../../components/ModuleButton';
|
import { ModuleButtons } from '../../../../components/ModuleButton';
|
||||||
import PageWrapper from '../../../../components/PageWrapper';
|
import PageWrapper from '../../../../components/PageWrapper';
|
||||||
import { asset } from '../../../../core/blitzkrieg/asset';
|
import { asset } from '../../../../core/blitzkrieg/asset';
|
||||||
import { modelDefinitions } from '../../../../core/blitzkrieg/modelDefinitions';
|
import { modelDefinitions } from '../../../../core/blitzkrieg/modelDefinitions';
|
||||||
import { resolveJsxTree } from '../../../../core/blitzkrieg/resolveJsxTree';
|
|
||||||
import {
|
import {
|
||||||
TIER_ROMAN_NUMERALS,
|
TIER_ROMAN_NUMERALS,
|
||||||
tankDefinitions,
|
tankDefinitions,
|
||||||
@@ -41,6 +39,10 @@ import {
|
|||||||
import { normalizeAnglePI } from '../../../../core/math/normalizeAngle180';
|
import { normalizeAnglePI } from '../../../../core/math/normalizeAngle180';
|
||||||
import * as styles from '../page.css';
|
import * as styles from '../page.css';
|
||||||
import { Controls } from './components/Control';
|
import { Controls } from './components/Control';
|
||||||
|
import { GunContainer } from './components/GunContainer';
|
||||||
|
import { HullContainer } from './components/HullContainer';
|
||||||
|
import { SceneProps } from './components/SceneProps';
|
||||||
|
import { TurretContainer } from './components/TurretContainer';
|
||||||
|
|
||||||
const X_AXIS = new Vector3(1, 0, 0);
|
const X_AXIS = new Vector3(1, 0, 0);
|
||||||
|
|
||||||
@@ -147,308 +149,50 @@ export default function Page({ params }: { params: { id: string } }) {
|
|||||||
|
|
||||||
<div style={{ height: '50vh', maxHeight: 576 }}>
|
<div style={{ height: '50vh', maxHeight: 576 }}>
|
||||||
<Canvas shadows ref={canvas} camera={{ fov: 20 }}>
|
<Canvas shadows ref={canvas} camera={{ fov: 20 }}>
|
||||||
<InfiniteGridHelper
|
|
||||||
size1={1 / 5}
|
|
||||||
size2={1}
|
|
||||||
distance={20}
|
|
||||||
color={new Color('#ffffff')}
|
|
||||||
/>
|
|
||||||
<InfiniteGridHelper
|
|
||||||
position={[0, 1e-4, 0]}
|
|
||||||
size1={0}
|
|
||||||
size2={100}
|
|
||||||
distance={25}
|
|
||||||
color={new Color('red')}
|
|
||||||
/>
|
|
||||||
<Controls enabled={controlsEnabled} fit={hullContainer} />
|
<Controls enabled={controlsEnabled} fit={hullContainer} />
|
||||||
|
<SceneProps />
|
||||||
|
|
||||||
{/* I really like apartment, dawn, and sunset */}
|
<HullContainer
|
||||||
<directionalLight
|
objects={gltf.scene.children[0].children}
|
||||||
position={[1, 1, -1]}
|
yaw={hullYaw}
|
||||||
intensity={1}
|
|
||||||
castShadow
|
|
||||||
/>
|
|
||||||
<directionalLight
|
|
||||||
position={[-1, 1, 1]}
|
|
||||||
intensity={2}
|
|
||||||
castShadow
|
|
||||||
/>
|
|
||||||
<ambientLight intensity={0.25} />
|
|
||||||
|
|
||||||
<group
|
|
||||||
ref={hullContainer}
|
ref={hullContainer}
|
||||||
rotation={[-Math.PI / 2, 0, hullYaw]}
|
onYawStart={() => setControlsEnabled(false)}
|
||||||
|
onYawEnd={(yaw) => {
|
||||||
|
setControlsEnabled(true);
|
||||||
|
setHullYaw(yaw);
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{gltf.scene.children[0].children.map((child) => {
|
<TurretContainer
|
||||||
const isHull = child.name === 'hull';
|
|
||||||
const isWheel = child.name.startsWith('chassis_wheel_');
|
|
||||||
const isTrack = child.name.startsWith('chassis_track_');
|
|
||||||
const isVisible = isHull || isWheel || isTrack;
|
|
||||||
let draftHullYaw = 0;
|
|
||||||
|
|
||||||
function handlePointerDown(
|
|
||||||
event: ThreeEvent<PointerEvent>,
|
|
||||||
) {
|
|
||||||
event.stopPropagation();
|
|
||||||
draftHullYaw = hullYaw;
|
|
||||||
setControlsEnabled(false);
|
|
||||||
window.addEventListener(
|
|
||||||
'pointermove',
|
|
||||||
handlePointerMove,
|
|
||||||
);
|
|
||||||
window.addEventListener('pointerup', handlePointerUp);
|
|
||||||
}
|
|
||||||
function handlePointerMove(event: PointerEvent) {
|
|
||||||
draftHullYaw +=
|
|
||||||
event.movementX *
|
|
||||||
((2 * Math.PI) / canvas.current!.width);
|
|
||||||
|
|
||||||
if (hullContainer.current) {
|
|
||||||
hullContainer.current.rotation.z = draftHullYaw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function handlePointerUp() {
|
|
||||||
setControlsEnabled(true);
|
|
||||||
setHullYaw(normalizeAnglePI(draftHullYaw));
|
|
||||||
window.removeEventListener(
|
|
||||||
'pointermove',
|
|
||||||
handlePointerMove,
|
|
||||||
);
|
|
||||||
window.removeEventListener(
|
|
||||||
'pointerup',
|
|
||||||
handlePointerUp,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isVisible) return null;
|
|
||||||
return (
|
|
||||||
<mesh
|
|
||||||
children={resolveJsxTree(child as Mesh)}
|
|
||||||
key={child.uuid}
|
|
||||||
castShadow
|
|
||||||
receiveShadow
|
|
||||||
geometry={(child as Mesh).geometry}
|
|
||||||
material={(child as Mesh).material}
|
|
||||||
position={child.position}
|
|
||||||
rotation={child.rotation}
|
|
||||||
scale={child.scale}
|
|
||||||
onPointerDown={handlePointerDown}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
|
|
||||||
<group
|
|
||||||
position={new Vector3()
|
|
||||||
.sub(turretOrigin)
|
|
||||||
.applyAxisAngle(new Vector3(0, 0, 1), turretYaw)
|
|
||||||
.add(turretOrigin)}
|
|
||||||
rotation={[0, 0, turretYaw]}
|
|
||||||
ref={turretContainer}
|
ref={turretContainer}
|
||||||
|
objects={gltf.scene.children[0].children}
|
||||||
|
model={turretModelDefinition.model}
|
||||||
|
onYawStart={() => setControlsEnabled(false)}
|
||||||
|
onYawEnd={(yaw) => {
|
||||||
|
setControlsEnabled(true);
|
||||||
|
setTurretYaw(yaw);
|
||||||
|
}}
|
||||||
|
origin={turretOrigin}
|
||||||
|
yaw={turretYaw}
|
||||||
>
|
>
|
||||||
{gltf.scene.children[0].children.map((child) => {
|
<GunContainer
|
||||||
const isTurret = child.name.startsWith('turret_');
|
onPitchStart={() => setControlsEnabled(false)}
|
||||||
const isCurrentTurret =
|
pitchLimits={gunModelDefinition.pitch}
|
||||||
child.name ===
|
turretContainer={turretContainer}
|
||||||
`turret_${turretModelDefinition.model
|
turretYaw={turretYaw}
|
||||||
.toString()
|
onPitchEnd={(pitch, yaw) => {
|
||||||
.padStart(2, '0')}`;
|
|
||||||
const isVisible = isCurrentTurret;
|
|
||||||
let draftTurretYaw = 0;
|
|
||||||
|
|
||||||
function handlePointerDown(
|
|
||||||
event: ThreeEvent<PointerEvent>,
|
|
||||||
) {
|
|
||||||
event.stopPropagation();
|
|
||||||
|
|
||||||
if (isTurret) {
|
|
||||||
setControlsEnabled(false);
|
|
||||||
draftTurretYaw = turretYaw;
|
|
||||||
window.addEventListener(
|
|
||||||
'pointermove',
|
|
||||||
handlePointerMove,
|
|
||||||
);
|
|
||||||
window.addEventListener(
|
|
||||||
'pointerup',
|
|
||||||
handlePointerUp,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function handlePointerMove(event: PointerEvent) {
|
|
||||||
draftTurretYaw +=
|
|
||||||
event.movementX *
|
|
||||||
((2 * Math.PI) / canvas.current!.width);
|
|
||||||
|
|
||||||
if (turretContainer.current) {
|
|
||||||
turretContainer.current.position
|
|
||||||
.set(0, 0, 0)
|
|
||||||
.sub(turretOrigin)
|
|
||||||
.applyAxisAngle(
|
|
||||||
new Vector3(0, 0, 1),
|
|
||||||
draftTurretYaw,
|
|
||||||
)
|
|
||||||
.add(turretOrigin);
|
|
||||||
turretContainer.current.rotation.z = draftTurretYaw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function handlePointerUp() {
|
|
||||||
setTurretYaw(normalizeAnglePI(draftTurretYaw));
|
|
||||||
setControlsEnabled(true);
|
setControlsEnabled(true);
|
||||||
window.removeEventListener(
|
setGunPitch(pitch);
|
||||||
'pointermove',
|
setTurretYaw(yaw);
|
||||||
handlePointerMove,
|
}}
|
||||||
);
|
yawLimits={turretModelDefinition.yaw}
|
||||||
window.removeEventListener(
|
gunOrigin={gunOrigin}
|
||||||
'pointerup',
|
gunPitch={gunPitch}
|
||||||
handlePointerUp,
|
model={gunModelDefinition.model}
|
||||||
);
|
objects={gltf.scene.children[0].children}
|
||||||
}
|
turretOrigin={turretOrigin}
|
||||||
|
/>
|
||||||
if (!isVisible) return null;
|
</TurretContainer>
|
||||||
return (
|
</HullContainer>
|
||||||
<mesh
|
|
||||||
visible={isVisible}
|
|
||||||
onPointerDown={handlePointerDown}
|
|
||||||
children={resolveJsxTree(child as Mesh)}
|
|
||||||
key={child.uuid}
|
|
||||||
castShadow
|
|
||||||
receiveShadow
|
|
||||||
geometry={(child as Mesh).geometry}
|
|
||||||
material={(child as Mesh).material}
|
|
||||||
position={child.position}
|
|
||||||
rotation={child.rotation}
|
|
||||||
scale={child.scale}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
|
|
||||||
<group
|
|
||||||
position={new Vector3()
|
|
||||||
.sub(turretOrigin)
|
|
||||||
.sub(gunOrigin)
|
|
||||||
.applyAxisAngle(new Vector3(1, 0, 0), gunPitch)
|
|
||||||
.add(turretOrigin)
|
|
||||||
.add(gunOrigin)}
|
|
||||||
rotation={[gunPitch, 0, 0]}
|
|
||||||
ref={gunContainer}
|
|
||||||
>
|
|
||||||
{gltf.scene.children[0].children.map((child) => {
|
|
||||||
const isCurrentMantlet =
|
|
||||||
child.name ===
|
|
||||||
`gun_${gunModelDefinition.model
|
|
||||||
.toString()
|
|
||||||
.padStart(2, '0')}_mask`;
|
|
||||||
const isCurrentGun =
|
|
||||||
child.name ===
|
|
||||||
`gun_${gunModelDefinition.model
|
|
||||||
.toString()
|
|
||||||
.padStart(2, '0')}`;
|
|
||||||
const isVisible = isCurrentGun || isCurrentMantlet;
|
|
||||||
let draftMantletPitch = 0;
|
|
||||||
let draftTurretYaw = 0;
|
|
||||||
|
|
||||||
function handlePointerDown(
|
|
||||||
event: ThreeEvent<PointerEvent>,
|
|
||||||
) {
|
|
||||||
event.stopPropagation();
|
|
||||||
|
|
||||||
setControlsEnabled(false);
|
|
||||||
draftMantletPitch = gunPitch;
|
|
||||||
draftTurretYaw = turretYaw;
|
|
||||||
window.addEventListener(
|
|
||||||
'pointermove',
|
|
||||||
handlePointerMove,
|
|
||||||
);
|
|
||||||
window.addEventListener(
|
|
||||||
'pointerup',
|
|
||||||
handlePointerUp,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
function handlePointerMove(event: PointerEvent) {
|
|
||||||
if (turretModelDefinition.yaw) {
|
|
||||||
draftTurretYaw = clamp(
|
|
||||||
draftTurretYaw +
|
|
||||||
event.movementX *
|
|
||||||
(Math.PI / canvas.current!.width),
|
|
||||||
-turretModelDefinition.yaw.max *
|
|
||||||
(Math.PI / 180),
|
|
||||||
-turretModelDefinition.yaw.min *
|
|
||||||
(Math.PI / 180),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
draftTurretYaw +=
|
|
||||||
event.movementX *
|
|
||||||
((2 * Math.PI) / canvas.current!.width);
|
|
||||||
}
|
|
||||||
draftMantletPitch = clamp(
|
|
||||||
draftMantletPitch -
|
|
||||||
event.movementY *
|
|
||||||
(Math.PI / canvas.current!.height),
|
|
||||||
-gunModelDefinition.pitch.max * (Math.PI / 180),
|
|
||||||
-gunModelDefinition.pitch.min * (Math.PI / 180),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (gunContainer.current) {
|
|
||||||
gunContainer.current.position
|
|
||||||
.set(0, 0, 0)
|
|
||||||
.sub(turretOrigin)
|
|
||||||
.sub(gunOrigin)
|
|
||||||
.applyAxisAngle(
|
|
||||||
new Vector3(1, 0, 0),
|
|
||||||
draftMantletPitch,
|
|
||||||
)
|
|
||||||
.add(turretOrigin)
|
|
||||||
.add(gunOrigin);
|
|
||||||
gunContainer.current.rotation.x =
|
|
||||||
draftMantletPitch;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (turretContainer.current) {
|
|
||||||
turretContainer.current.position
|
|
||||||
.set(0, 0, 0)
|
|
||||||
.sub(turretOrigin)
|
|
||||||
.applyAxisAngle(
|
|
||||||
new Vector3(0, 0, 1),
|
|
||||||
draftTurretYaw,
|
|
||||||
)
|
|
||||||
.add(turretOrigin);
|
|
||||||
turretContainer.current.rotation.z =
|
|
||||||
draftTurretYaw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function handlePointerUp() {
|
|
||||||
setControlsEnabled(true);
|
|
||||||
setGunPitch(normalizeAnglePI(draftMantletPitch));
|
|
||||||
setTurretYaw(normalizeAnglePI(draftTurretYaw));
|
|
||||||
window.removeEventListener(
|
|
||||||
'pointermove',
|
|
||||||
handlePointerMove,
|
|
||||||
);
|
|
||||||
window.removeEventListener(
|
|
||||||
'pointerup',
|
|
||||||
handlePointerUp,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isVisible) return null;
|
|
||||||
return (
|
|
||||||
<mesh
|
|
||||||
visible={isVisible}
|
|
||||||
onPointerDown={handlePointerDown}
|
|
||||||
children={resolveJsxTree(child as Mesh)}
|
|
||||||
key={child.uuid}
|
|
||||||
castShadow
|
|
||||||
receiveShadow
|
|
||||||
geometry={(child as Mesh).geometry}
|
|
||||||
material={(child as Mesh).material}
|
|
||||||
position={child.position}
|
|
||||||
rotation={child.rotation}
|
|
||||||
scale={child.scale}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</group>
|
|
||||||
</group>
|
|
||||||
</group>
|
|
||||||
</Canvas>
|
</Canvas>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -10,18 +10,23 @@ interface TurretModelDefinition {
|
|||||||
model: number;
|
model: number;
|
||||||
gunOrigin: Vector3Tuple;
|
gunOrigin: Vector3Tuple;
|
||||||
guns: Record<number, GunModelDefinition>;
|
guns: Record<number, GunModelDefinition>;
|
||||||
yaw?: { min: number; max: number };
|
yaw?: YawLimits;
|
||||||
|
}
|
||||||
|
export interface YawLimits {
|
||||||
|
min: number;
|
||||||
|
max: number;
|
||||||
}
|
}
|
||||||
interface GunModelDefinition {
|
interface GunModelDefinition {
|
||||||
model: number;
|
model: number;
|
||||||
pitch: {
|
pitch: PitchLimits;
|
||||||
min: number;
|
}
|
||||||
max: number;
|
export interface PitchLimits {
|
||||||
|
min: number;
|
||||||
|
max: number;
|
||||||
|
|
||||||
front?: { min: number; max: number; range: number };
|
front?: { min: number; max: number; range: number };
|
||||||
back?: { min: number; max: number; range: number };
|
back?: { min: number; max: number; range: number };
|
||||||
transition?: number;
|
transition?: number;
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const modelDefinitions = fetch(asset('definitions/models.json'), {
|
export const modelDefinitions = fetch(asset('definitions/models.json'), {
|
||||||
|
|||||||
Reference in new issue
Block a user