feat: start of rotation

This commit is contained in:
TrèsAbhi committed 2025-09-30 16:34:08 -05:00
1 parent d60551d30a
commit 10942879a6
7 files changed
+121 -18

No files matched your search

@@ -1,11 +1,25 @@
import { useThree, type ThreeEvent } from "@react-three/fiber";
import type { QuicklimeEvent } from "quicklime";
import { useCallback, useEffect, useRef } from "react";
import { Group, Vector2 } from "three";
import { awaitableModelDefinitions } from "../../../core/awaitables/modelDefinitions";
import { jsxTree } from "../../../core/blitzkit/jsxTree";
import {
modelTransformEvent,
type ModelTransformEventData,
} from "../../../core/blitzkit/modelTransform";
import { controlsEnabledEvent } from "../../../core/controlsEnabled";
import { useModel } from "../../../hooks/useModel";
import { Mixer } from "../../../stores/mixer";
const modelDefinitions = await awaitableModelDefinitions;
export function Model() {
const canvas = useThree((state) => state.gl.domElement);
const turretGroup = useRef<Group>(null!);
const gunGroup = useRef<Group>(null!);
const hull = Mixer.use((state) => state.hull);
const turret = Mixer.use((state) => state.turret);
const gun = Mixer.use((state) => state.gun);
@@ -31,6 +45,54 @@ export function Model() {
const gunTurretModel = gunTankModel.turrets[gun.turret.id];
const gunModel = gunTurretModel.guns[gun.gun.id];
const pointer = useRef(new Vector2());
const delta = useRef(new Vector2());
const handlePointerDown = useCallback((event: ThreeEvent<PointerEvent>) => {
event.stopPropagation();
controlsEnabledEvent.dispatch(false);
pointer.current.set(event.clientX, event.clientY);
window.addEventListener("pointermove", handlePointerMove);
window.addEventListener("pointerup", handlePointerUp);
}, []);
const handlePointerMove = useCallback((event: PointerEvent) => {
const bounds = canvas.getBoundingClientRect();
delta.current.set(event.clientX, event.clientY).sub(pointer.current);
pointer.current.set(event.clientX, event.clientY);
modelTransformEvent.dispatch({
pitch:
modelTransformEvent.last!.pitch +
delta.current.y * (Math.PI / bounds.height),
yaw:
modelTransformEvent.last!.yaw +
delta.current.x * (Math.PI / bounds.width),
});
}, []);
const handlePointerUp = useCallback(() => {
controlsEnabledEvent.dispatch(true);
window.removeEventListener("pointermove", handlePointerMove);
window.removeEventListener("pointerup", handlePointerUp);
}, []);
useEffect(() => {
function handleModelTransform(
event: QuicklimeEvent<ModelTransformEventData>
) {
turretGroup.current.rotation.z = event.data.yaw;
}
modelTransformEvent.on(handleModelTransform);
return () => {
modelTransformEvent.off(handleModelTransform);
};
}, []);
return (
<group rotation={[-Math.PI / 2, 0, 0]}>
{hullNodes.map((node) => {
@@ -49,6 +111,8 @@ export function Model() {
})}
<group
ref={turretGroup}
rotation={[0, 0, modelTransformEvent.last!.yaw]}
position={[
tankModel.turret_origin.x +
trackModel.origin.x -
@@ -74,7 +138,15 @@ export function Model() {
return jsxTree(node, {
mesh(_, props, key) {
return <mesh {...props} key={key} castShadow receiveShadow />;
return (
<mesh
{...props}
key={key}
castShadow
receiveShadow
onPointerDown={handlePointerDown}
/>
);
},
});
})}
@@ -1,12 +1,30 @@
import { OrbitControls } from "@react-three/drei";
import { Canvas } from "@react-three/fiber";
import { forwardRef, Suspense } from "react";
import type { QuicklimeEvent } from "quicklime";
import { forwardRef, Suspense, useEffect, useRef } from "react";
import type { OrbitControls as OrbitControlsClass } from "three-stdlib";
import { controlsEnabledEvent } from "../../core/controlsEnabled";
import { Lighting } from "../Tankopedia/HeroSection/components/TankSandbox/components/Lighting";
import { SceneProps } from "../Tankopedia/HeroSection/components/TankSandbox/components/SceneProps";
import { MixerFallback } from "./components/MixerFallback";
import { Model } from "./components/Model";
export const MixerScene = forwardRef<HTMLCanvasElement>((_, ref) => {
const controls = useRef<OrbitControlsClass>(null!);
useEffect(() => {
function handleControlsEnabled(event: QuicklimeEvent<boolean>) {
if (!controls.current) return;
controls.current.enabled = event.data;
}
controlsEnabledEvent.on(handleControlsEnabled);
return () => {
controlsEnabledEvent.off(handleControlsEnabled);
};
}, []);
return (
<Canvas
ref={ref}
@@ -18,6 +36,7 @@ export const MixerScene = forwardRef<HTMLCanvasElement>((_, ref) => {
}}
>
<OrbitControls
ref={controls}
maxDistance={20}
minDistance={5}
enableDamping={false}
@@ -1,6 +1,7 @@
import { I_HAT, J_HAT } from "@blitzkit/core";
import { OrbitControls } from "@react-three/drei";
import { invalidate, useFrame, useThree } from "@react-three/fiber";
import type { QuicklimeEvent } from "quicklime";
import { useEffect, useRef } from "react";
import { PerspectiveCamera, Vector3 } from "three";
import { OrbitControls as OrbitControlsClass } from "three-stdlib";
@@ -8,6 +9,7 @@ import { awaitableModelDefinitions } from "../../../../../../core/awaitables/mod
import { applyPitchYawLimits } from "../../../../../../core/blitz/applyPitchYawLimits";
import { hasEquipment } from "../../../../../../core/blitzkit/hasEquipment";
import { Pose, poseEvent } from "../../../../../../core/blitzkit/pose";
import { controlsEnabledEvent } from "../../../../../../core/controlsEnabled";
import { Duel } from "../../../../../../stores/duel";
import { Tankopedia } from "../../../../../../stores/tankopedia";
@@ -99,12 +101,12 @@ export function Controls({
});
useEffect(() => {
const unsubscribeTankopediaEphemeral = Tankopedia.on(
(state) => state.controlsEnabled,
(enabled) => {
if (orbitControls.current) orbitControls.current.enabled = enabled;
}
);
function handleControlsEnabled(event: QuicklimeEvent<boolean>) {
if (!orbitControls.current) return;
orbitControls.current.enabled = event.data;
}
controlsEnabledEvent.on(handleControlsEnabled);
function handlePoseEvent(event: Pose) {
const hasImprovedVerticalStabilizer = hasEquipment(
@@ -203,7 +205,7 @@ export function Controls({
poseEvent.on(handlePoseEvent);
return () => {
unsubscribeTankopediaEphemeral();
controlsEnabledEvent.off(handleControlsEnabled);
poseEvent.off(handlePoseEvent);
};
}, [camera, protagonistTank.id, antagonistTank.id]);
@@ -264,7 +266,7 @@ export function Controls({
enableZoom={zoomable}
zoomSpeed={zoomable ? undefined : 0}
ref={orbitControls}
enabled={Tankopedia.state.controlsEnabled}
enabled={controlsEnabledEvent.last!}
enableDamping={false}
/>
);