mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-24 20:19:49 +02:00
feat: movable tracks
This commit is contained in:
1 parent
b032de4a62
commit
5b833bab11
4 files changed
+64
-17
No files matched your search
Vendored
+1
@@ -89,6 +89,7 @@
|
|||||||
"lossrate",
|
"lossrate",
|
||||||
"mantlets",
|
"mantlets",
|
||||||
"metallicness",
|
"metallicness",
|
||||||
|
"metalness",
|
||||||
"minitelrose",
|
"minitelrose",
|
||||||
"Minotauro",
|
"Minotauro",
|
||||||
"MIPMAPCOUNT",
|
"MIPMAPCOUNT",
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { modelDefinitions } from '../../../../../core/blitzkit/modelDefinitions'
|
|||||||
import { Pose, poseEvent } from '../../../../../core/blitzkit/pose';
|
import { Pose, poseEvent } from '../../../../../core/blitzkit/pose';
|
||||||
import { useAwait } from '../../../../../hooks/useAwait';
|
import { useAwait } from '../../../../../hooks/useAwait';
|
||||||
import { useDuel } from '../../../../../stores/duel';
|
import { useDuel } from '../../../../../stores/duel';
|
||||||
import { useTankopediaPersistent } from '../../../../../stores/tankopedia';
|
import { useTankopediaTemporary } from '../../../../../stores/tankopedia';
|
||||||
|
|
||||||
const poseDistances: Record<Pose, number> = {
|
const poseDistances: Record<Pose, number> = {
|
||||||
[Pose.HullDown]: 15,
|
[Pose.HullDown]: 15,
|
||||||
@@ -62,8 +62,8 @@ export function Controls() {
|
|||||||
}, [camera]);
|
}, [camera]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const unsubscribeTankopedia = useTankopediaPersistent.subscribe(
|
const unsubscribeTankopedia = useTankopediaTemporary.subscribe(
|
||||||
(state) => state.model.visual.controlsEnabled,
|
(state) => state.controlsEnabled,
|
||||||
(enabled) => {
|
(enabled) => {
|
||||||
if (orbitControls.current) orbitControls.current.enabled = enabled;
|
if (orbitControls.current) orbitControls.current.enabled = enabled;
|
||||||
},
|
},
|
||||||
@@ -182,7 +182,7 @@ export function Controls() {
|
|||||||
maxDistance={20}
|
maxDistance={20}
|
||||||
minDistance={5}
|
minDistance={5}
|
||||||
ref={orbitControls}
|
ref={orbitControls}
|
||||||
enabled={useTankopediaPersistent.getState().model.visual.controlsEnabled}
|
enabled={useTankopediaTemporary.getState().controlsEnabled}
|
||||||
rotateSpeed={0.25}
|
rotateSpeed={0.25}
|
||||||
enableDamping={false}
|
enableDamping={false}
|
||||||
autoRotate={autoRotate}
|
autoRotate={autoRotate}
|
||||||
|
|||||||
@@ -1,6 +1,13 @@
|
|||||||
import { ThreeEvent, useThree } from '@react-three/fiber';
|
import { ThreeEvent, useThree } from '@react-three/fiber';
|
||||||
import { memo, useEffect, useRef } from 'react';
|
import { memo, useEffect, useRef } from 'react';
|
||||||
import { Euler, Group, Vector2, Vector3 } from 'three';
|
import {
|
||||||
|
Euler,
|
||||||
|
Group,
|
||||||
|
Mesh,
|
||||||
|
MeshStandardMaterial,
|
||||||
|
Vector2,
|
||||||
|
Vector3,
|
||||||
|
} from 'three';
|
||||||
import { degToRad } from 'three/src/math/MathUtils';
|
import { degToRad } from 'three/src/math/MathUtils';
|
||||||
import { I_HAT, J_HAT, K_HAT } from '../../../../../../../constants/axis';
|
import { I_HAT, J_HAT, K_HAT } from '../../../../../../../constants/axis';
|
||||||
import { applyPitchYawLimits } from '../../../../../../../core/blitz/applyPitchYawLimits';
|
import { applyPitchYawLimits } from '../../../../../../../core/blitz/applyPitchYawLimits';
|
||||||
@@ -15,9 +22,7 @@ import { normalizeAngleRad } from '../../../../../../../core/math/normalizeAngle
|
|||||||
import { useAwait } from '../../../../../../../hooks/useAwait';
|
import { useAwait } from '../../../../../../../hooks/useAwait';
|
||||||
import { useModel } from '../../../../../../../hooks/useModel';
|
import { useModel } from '../../../../../../../hooks/useModel';
|
||||||
import { mutateDuel, useDuel } from '../../../../../../../stores/duel';
|
import { mutateDuel, useDuel } from '../../../../../../../stores/duel';
|
||||||
import mutateTankopediaPersistent, {
|
import { mutateTankopediaTemporary } from '../../../../../../../stores/tankopedia';
|
||||||
mutateTankopediaTemporary,
|
|
||||||
} from '../../../../../../../stores/tankopedia';
|
|
||||||
|
|
||||||
export const TankModel = memo(() => {
|
export const TankModel = memo(() => {
|
||||||
const awaitedModelDefinitions = useAwait(modelDefinitions);
|
const awaitedModelDefinitions = useAwait(modelDefinitions);
|
||||||
@@ -117,11 +122,51 @@ export const TankModel = memo(() => {
|
|||||||
const isWheel = node.name.startsWith('chassis_wheel_');
|
const isWheel = node.name.startsWith('chassis_wheel_');
|
||||||
const isTrack = node.name.startsWith('chassis_track_');
|
const isTrack = node.name.startsWith('chassis_track_');
|
||||||
const isVisible = isHull || isWheel || isTrack;
|
const isVisible = isHull || isWheel || isTrack;
|
||||||
|
const position = new Vector2();
|
||||||
|
const delta = new Vector2();
|
||||||
|
|
||||||
if (!isVisible) return null;
|
if (!isVisible) return null;
|
||||||
|
|
||||||
|
function translateTexture(offset: number) {
|
||||||
|
const mesh = node.children[0] as Mesh;
|
||||||
|
const material = mesh?.material as MeshStandardMaterial;
|
||||||
|
|
||||||
|
if (!material) return;
|
||||||
|
|
||||||
|
if (material.map) material.map.offset.y += offset;
|
||||||
|
if (material.aoMap) material.aoMap.offset.y += offset;
|
||||||
|
if (material.normalMap) material.normalMap.offset.y += offset;
|
||||||
|
if (material.roughnessMap) material.roughnessMap.offset.y += offset;
|
||||||
|
if (material.metalnessMap) material.metalnessMap.offset.y += offset;
|
||||||
|
}
|
||||||
|
|
||||||
function onPointerDown(event: ThreeEvent<PointerEvent>) {
|
function onPointerDown(event: ThreeEvent<PointerEvent>) {
|
||||||
|
if (!isTrack) return;
|
||||||
|
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
|
||||||
|
mutateTankopediaTemporary((draft) => {
|
||||||
|
draft.controlsEnabled = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener('pointermove', handlePointerMove);
|
||||||
|
window.addEventListener('pointerup', handlePointerUp);
|
||||||
|
}
|
||||||
|
function handlePointerMove(event: PointerEvent) {
|
||||||
|
delta.set(event.clientX, event.clientY).sub(position);
|
||||||
|
position.set(event.clientX, event.clientY);
|
||||||
|
const deltaX = delta.x / window.innerWidth;
|
||||||
|
const deltaY = delta.y / window.innerHeight;
|
||||||
|
|
||||||
|
translateTexture(deltaX + deltaY);
|
||||||
|
}
|
||||||
|
function handlePointerUp() {
|
||||||
|
mutateTankopediaTemporary((draft) => {
|
||||||
|
draft.controlsEnabled = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
window.removeEventListener('pointermove', handlePointerMove);
|
||||||
|
window.removeEventListener('pointerup', handlePointerUp);
|
||||||
}
|
}
|
||||||
|
|
||||||
return jsxTree(
|
return jsxTree(
|
||||||
@@ -158,8 +203,8 @@ export const TankModel = memo(() => {
|
|||||||
yaw = protagonist.yaw;
|
yaw = protagonist.yaw;
|
||||||
pitch = protagonist.pitch;
|
pitch = protagonist.pitch;
|
||||||
|
|
||||||
mutateTankopediaPersistent((draft) => {
|
mutateTankopediaTemporary((draft) => {
|
||||||
draft.model.visual.controlsEnabled = false;
|
draft.controlsEnabled = false;
|
||||||
});
|
});
|
||||||
mutateTankopediaTemporary((draft) => {
|
mutateTankopediaTemporary((draft) => {
|
||||||
draft.shot = undefined;
|
draft.shot = undefined;
|
||||||
@@ -194,8 +239,8 @@ export const TankModel = memo(() => {
|
|||||||
draft.protagonist!.pitch = normalizeAngleRad(pitch);
|
draft.protagonist!.pitch = normalizeAngleRad(pitch);
|
||||||
draft.protagonist!.yaw = normalizeAngleRad(yaw);
|
draft.protagonist!.yaw = normalizeAngleRad(yaw);
|
||||||
});
|
});
|
||||||
mutateTankopediaPersistent((draft) => {
|
mutateTankopediaTemporary((draft) => {
|
||||||
draft.model.visual.controlsEnabled = true;
|
draft.controlsEnabled = true;
|
||||||
});
|
});
|
||||||
window.removeEventListener('pointermove', handlePointerMove);
|
window.removeEventListener('pointermove', handlePointerMove);
|
||||||
window.removeEventListener('pointerup', handlePointerUp);
|
window.removeEventListener('pointerup', handlePointerUp);
|
||||||
@@ -234,8 +279,8 @@ export const TankModel = memo(() => {
|
|||||||
function onPointerDown(event: ThreeEvent<PointerEvent>) {
|
function onPointerDown(event: ThreeEvent<PointerEvent>) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
|
||||||
mutateTankopediaPersistent((draft) => {
|
mutateTankopediaTemporary((draft) => {
|
||||||
draft.model.visual.controlsEnabled = false;
|
draft.controlsEnabled = false;
|
||||||
});
|
});
|
||||||
mutateTankopediaTemporary((draft) => {
|
mutateTankopediaTemporary((draft) => {
|
||||||
draft.shot = undefined;
|
draft.shot = undefined;
|
||||||
@@ -269,8 +314,8 @@ export const TankModel = memo(() => {
|
|||||||
modelTransformEvent.emit({ pitch, yaw });
|
modelTransformEvent.emit({ pitch, yaw });
|
||||||
}
|
}
|
||||||
function handlePointerUp() {
|
function handlePointerUp() {
|
||||||
mutateTankopediaPersistent((draft) => {
|
mutateTankopediaTemporary((draft) => {
|
||||||
draft.model.visual.controlsEnabled = true;
|
draft.controlsEnabled = true;
|
||||||
});
|
});
|
||||||
mutateDuel((draft) => {
|
mutateDuel((draft) => {
|
||||||
draft.protagonist!.pitch = normalizeAngleRad(pitch);
|
draft.protagonist!.pitch = normalizeAngleRad(pitch);
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ export interface TankopediaPersistent {
|
|||||||
wireframe: boolean;
|
wireframe: boolean;
|
||||||
opaque: boolean;
|
opaque: boolean;
|
||||||
environment: (typeof ENVIRONMENTS)[number];
|
environment: (typeof ENVIRONMENTS)[number];
|
||||||
controlsEnabled: boolean;
|
|
||||||
showGrid: boolean;
|
showGrid: boolean;
|
||||||
greenPenetration: boolean;
|
greenPenetration: boolean;
|
||||||
showEnvironment: boolean;
|
showEnvironment: boolean;
|
||||||
@@ -45,6 +44,7 @@ export interface TankopediaPersistent {
|
|||||||
interface TankopediaTemporary {
|
interface TankopediaTemporary {
|
||||||
shot?: Shot;
|
shot?: Shot;
|
||||||
skills: Record<string, number>;
|
skills: Record<string, number>;
|
||||||
|
controlsEnabled: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ShotLayerBase {
|
export interface ShotLayerBase {
|
||||||
@@ -199,6 +199,7 @@ export const useTankopediaPersistent = create<TankopediaPersistent>()(
|
|||||||
export const useTankopediaTemporary = create<TankopediaTemporary>()(
|
export const useTankopediaTemporary = create<TankopediaTemporary>()(
|
||||||
subscribeWithSelector<TankopediaTemporary>(() => ({
|
subscribeWithSelector<TankopediaTemporary>(() => ({
|
||||||
skills: {},
|
skills: {},
|
||||||
|
controlsEnabled: true,
|
||||||
})),
|
})),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user