more robust contorls

This commit is contained in:
tresabhi committed 2026-06-14 12:40:42 -05:00
1 parent 39aeba0766
commit 59ef527967
3 files changed
+37 -26

No files matched your search

@@ -1,26 +1,22 @@
import { J_HAT, K_HAT } from "@blitzkit/core"; import { I_HAT, J_HAT } from "@blitzkit/core";
import { invalidate, useThree } from "@react-three/fiber"; import { invalidate, useThree } from "@react-three/fiber";
import { useEffect } from "react"; import { useEffect } from "react";
import { Vector2, Vector3 } from "three"; import { Quaternion, Vector2, Vector3 } from "three";
import { degToRad } from "three/src/math/MathUtils.js"; import { degToRad } from "three/src/math/MathUtils.js";
const initialDistance = 8;
const initialRotation = new Vector2(degToRad(45), degToRad(45));
const clientPosition = new Vector2(); const clientPosition = new Vector2();
const workingVector2 = new Vector2();
const workingVector3 = new Vector3(); const workingVector3 = new Vector3();
const workingQuaternion = new Quaternion();
const cameraCenter = new Vector3(0, 1, 0); const cameraCenter = new Vector3(0, 1, 0);
const cameraRotation = initialRotation.clone();
let cameraDistance = initialDistance;
export function TankopediaControls() { export function TankopediaControls() {
const canvas = useThree((state) => state.gl.domElement); const canvas = useThree((state) => state.gl.domElement);
const camera = useThree((state) => state.camera); const camera = useThree((state) => state.camera);
useEffect(() => { useEffect(() => {
setCameraPosition(15, degToRad(90 - 20), degToRad(90 + 25));
function handlePointerDown(event: PointerEvent) { function handlePointerDown(event: PointerEvent) {
clientPosition.set(event.clientX, event.clientY); clientPosition.set(event.clientX, event.clientY);
@@ -29,20 +25,24 @@ export function TankopediaControls() {
} }
function handlePointerMove(event: PointerEvent) { function handlePointerMove(event: PointerEvent) {
workingVector2.set(event.clientX, event.clientY).sub(clientPosition); event.preventDefault();
workingVector2.x *= -(2 * Math.PI) / canvas.width; workingVector3.copy(camera.position).sub(cameraCenter);
workingVector2.y *= (2 * Math.PI) / canvas.height;
cameraRotation.x += workingVector2.x; const r = workingVector3.length();
const dx = event.movementX / window.innerWidth;
const dy = event.movementY / window.innerHeight;
if (cameraRotation.y + workingVector2.y < Math.PI / 2) { const dTheta = -dx * Math.PI;
cameraRotation.y += workingVector2.y; const dPhi = -dy * Math.PI;
}
clientPosition.set(event.clientX, event.clientY); const theta = Math.atan2(workingVector3.x, workingVector3.z) + dTheta;
const phi0 = Math.acos(workingVector3.y / r);
let phi = phi0 + dPhi;
updateCameraPosition(); if (phi < 0 || phi > Math.PI) phi = phi0;
setCameraPosition(r, phi, theta);
} }
function handlePointerUp() { function handlePointerUp() {
@@ -50,13 +50,20 @@ export function TankopediaControls() {
window.removeEventListener("pointerup", handlePointerUp); window.removeEventListener("pointerup", handlePointerUp);
} }
function updateCameraPosition() { function setCameraPosition(r: number, phi: number, theta: number) {
camera.position workingVector3
.set(cameraDistance, 0, 0) .set(
.applyAxisAngle(J_HAT, cameraRotation.x) Math.sin(phi) * Math.sin(theta),
.applyAxisAngle(K_HAT, cameraRotation.y) Math.cos(phi),
.add(cameraCenter); Math.sin(phi) * Math.cos(theta),
camera.lookAt(cameraCenter); )
.multiplyScalar(r);
camera.position.copy(cameraCenter).add(workingVector3);
workingQuaternion.setFromAxisAngle(I_HAT, phi - Math.PI / 2);
camera.quaternion
.setFromAxisAngle(J_HAT, theta)
.multiply(workingQuaternion);
invalidate(); invalidate();
} }
@@ -67,4 +74,6 @@ export function TankopediaControls() {
canvas.removeEventListener("pointerdown", handlePointerDown); canvas.removeEventListener("pointerdown", handlePointerDown);
}; };
}, []); }, []);
return null;
} }
@@ -1,5 +1,6 @@
import { type StageParameters } from "@protos/blitz_static_tank_upgrade_single_stage"; import { type StageParameters } from "@protos/blitz_static_tank_upgrade_single_stage";
import { Canvas } from "@react-three/fiber"; import { Canvas } from "@react-three/fiber";
import { fov } from "../../config/fov";
import { useTankChassisModel } from "../../hooks/useTankChassisModel"; import { useTankChassisModel } from "../../hooks/useTankChassisModel";
import { useTankGunModel } from "../../hooks/useTankGunModel"; import { useTankGunModel } from "../../hooks/useTankGunModel";
import { useTankHullModel } from "../../hooks/useTankHullModel"; import { useTankHullModel } from "../../hooks/useTankHullModel";
@@ -18,7 +19,7 @@ export function TankopediaSandbox({ parameters }: TankopediaSandboxProps) {
<div className={styles.wrapper}> <div className={styles.wrapper}>
<div className={styles.sandbox}> <div className={styles.sandbox}>
<Canvas <Canvas
camera={{ fov: 40 }} camera={{ fov }}
shadows shadows
className={styles.canvas} className={styles.canvas}
frameloop="demand" frameloop="demand"
+1
View File
@@ -0,0 +1 @@
export const fov = 20;