mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-27 04:29:50 +01:00
undistrubed animation
This commit is contained in:
1 parent
4248e19f68
commit
d424679257
4 files changed
+91
-28
No files matched your search
@@ -1,12 +1,17 @@
|
|||||||
import { I_HAT, J_HAT } from "@blitzkit/core";
|
import { I_HAT, J_HAT } from "@blitzkit/core";
|
||||||
import { invalidate, useThree } from "@react-three/fiber";
|
import { invalidate, useFrame, useThree } from "@react-three/fiber";
|
||||||
import { useEffect } from "react";
|
import { useCallback, useEffect, useRef, type RefObject } from "react";
|
||||||
import { Quaternion, Vector2, Vector3 } from "three";
|
import { Quaternion, Vector2, Vector3 } from "three";
|
||||||
import {
|
import {
|
||||||
|
cameraSwingHorizontalAmplitude,
|
||||||
|
cameraSwingHorizontalFrequency,
|
||||||
|
cameraSwingVerticalAmplitude,
|
||||||
|
cameraSwingVerticalFrequency,
|
||||||
initialCameraPhi,
|
initialCameraPhi,
|
||||||
initialCameraR,
|
initialCameraR,
|
||||||
initialCameraTheta,
|
initialCameraTheta,
|
||||||
} from "../config/camera";
|
} from "../config/camera";
|
||||||
|
import { Tankopedia } from "../stores/tankopedia";
|
||||||
|
|
||||||
const clientPosition = new Vector2();
|
const clientPosition = new Vector2();
|
||||||
const workingVector3 = new Vector3();
|
const workingVector3 = new Vector3();
|
||||||
@@ -20,10 +25,38 @@ 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);
|
||||||
|
|
||||||
|
const r = useRef(initialCameraR);
|
||||||
|
const phi = useRef(initialCameraPhi);
|
||||||
|
const theta = useRef(initialCameraTheta);
|
||||||
|
|
||||||
|
const disturbed = Tankopedia.use((state) => state.disturbed);
|
||||||
|
|
||||||
|
const updateCameraPosition = useCallback(() => {
|
||||||
|
workingVector3
|
||||||
|
.set(
|
||||||
|
Math.sin(phi.current) * Math.sin(theta.current),
|
||||||
|
Math.cos(phi.current),
|
||||||
|
Math.sin(phi.current) * Math.cos(theta.current),
|
||||||
|
)
|
||||||
|
.multiplyScalar(r.current);
|
||||||
|
camera.position.copy(cameraCenter).add(workingVector3);
|
||||||
|
|
||||||
|
workingQuaternion.setFromAxisAngle(I_HAT, phi.current - Math.PI / 2);
|
||||||
|
camera.quaternion
|
||||||
|
.setFromAxisAngle(J_HAT, theta.current)
|
||||||
|
.multiply(workingQuaternion);
|
||||||
|
|
||||||
|
invalidate();
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setCameraPosition(initialCameraR, initialCameraPhi, initialCameraTheta);
|
updateCameraPosition();
|
||||||
|
|
||||||
function handlePointerDown(event: PointerEvent) {
|
function handlePointerDown(event: PointerEvent) {
|
||||||
|
Tankopedia.mutate((draft) => {
|
||||||
|
draft.disturbed = true;
|
||||||
|
});
|
||||||
|
|
||||||
clientPosition.set(event.clientX, event.clientY);
|
clientPosition.set(event.clientX, event.clientY);
|
||||||
|
|
||||||
window.addEventListener("pointermove", handlePointerMove);
|
window.addEventListener("pointermove", handlePointerMove);
|
||||||
@@ -35,21 +68,22 @@ export function TankopediaControls() {
|
|||||||
|
|
||||||
workingVector3.copy(camera.position).sub(cameraCenter);
|
workingVector3.copy(camera.position).sub(cameraCenter);
|
||||||
|
|
||||||
const r = workingVector3.length();
|
r.current = workingVector3.length();
|
||||||
const dx = event.movementX / window.innerWidth;
|
const dx = event.movementX / window.innerWidth;
|
||||||
const dy = event.movementY / window.innerHeight;
|
const dy = event.movementY / window.innerHeight;
|
||||||
|
|
||||||
const dTheta = -dx * Math.PI;
|
const dTheta = -dx * Math.PI;
|
||||||
const dPhi = -dy * Math.PI;
|
const dPhi = -dy * Math.PI;
|
||||||
|
|
||||||
const theta = Math.atan2(workingVector3.x, workingVector3.z) + dTheta;
|
theta.current = Math.atan2(workingVector3.x, workingVector3.z) + dTheta;
|
||||||
const phi0 = Math.acos(workingVector3.y / r);
|
const phi0 = Math.acos(workingVector3.y / r.current);
|
||||||
let phi = phi0 + dPhi;
|
phi.current = phi0 + dPhi;
|
||||||
const maxPhi = Math.PI / 2 + Math.asin(cameraCenter.y / r) - PHI_EPSILON;
|
const maxPhi =
|
||||||
|
Math.PI / 2 + Math.asin(cameraCenter.y / r.current) - PHI_EPSILON;
|
||||||
|
|
||||||
if (phi < 0 || phi > maxPhi) phi = phi0;
|
if (phi.current < 0 || phi.current > maxPhi) phi.current = phi0;
|
||||||
|
|
||||||
setCameraPosition(r, phi, theta);
|
updateCameraPosition();
|
||||||
}
|
}
|
||||||
|
|
||||||
function handlePointerUp() {
|
function handlePointerUp() {
|
||||||
@@ -57,24 +91,6 @@ export function TankopediaControls() {
|
|||||||
window.removeEventListener("pointerup", handlePointerUp);
|
window.removeEventListener("pointerup", handlePointerUp);
|
||||||
}
|
}
|
||||||
|
|
||||||
function setCameraPosition(r: number, phi: number, theta: number) {
|
|
||||||
workingVector3
|
|
||||||
.set(
|
|
||||||
Math.sin(phi) * Math.sin(theta),
|
|
||||||
Math.cos(phi),
|
|
||||||
Math.sin(phi) * Math.cos(theta),
|
|
||||||
)
|
|
||||||
.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();
|
|
||||||
}
|
|
||||||
|
|
||||||
canvas.addEventListener("pointerdown", handlePointerDown);
|
canvas.addEventListener("pointerdown", handlePointerDown);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
@@ -82,5 +98,39 @@ export function TankopediaControls() {
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
!disturbed && (
|
||||||
|
<Swing
|
||||||
|
r={r}
|
||||||
|
phi={phi}
|
||||||
|
theta={theta}
|
||||||
|
updateCameraPosition={updateCameraPosition}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SwingProps {
|
||||||
|
r: RefObject<number>;
|
||||||
|
phi: RefObject<number>;
|
||||||
|
theta: RefObject<number>;
|
||||||
|
updateCameraPosition: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Swing({ r, phi, theta, updateCameraPosition }: SwingProps) {
|
||||||
|
useFrame(({ clock }) => {
|
||||||
|
const t = 2 * Math.PI * clock.getElapsedTime();
|
||||||
|
|
||||||
|
phi.current =
|
||||||
|
initialCameraPhi +
|
||||||
|
cameraSwingVerticalAmplitude * Math.cos(cameraSwingVerticalFrequency * t);
|
||||||
|
theta.current =
|
||||||
|
initialCameraTheta +
|
||||||
|
cameraSwingHorizontalAmplitude *
|
||||||
|
Math.sin(cameraSwingHorizontalFrequency * t);
|
||||||
|
|
||||||
|
updateCameraPosition();
|
||||||
|
});
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -5,3 +5,8 @@ export const fov = 20;
|
|||||||
export const initialCameraR = 15;
|
export const initialCameraR = 15;
|
||||||
export const initialCameraPhi = degToRad(90 - 10);
|
export const initialCameraPhi = degToRad(90 - 10);
|
||||||
export const initialCameraTheta = degToRad(90 + 15);
|
export const initialCameraTheta = degToRad(90 + 15);
|
||||||
|
|
||||||
|
export const cameraSwingVerticalAmplitude = degToRad(5);
|
||||||
|
export const cameraSwingVerticalFrequency = Math.E ** -6;
|
||||||
|
export const cameraSwingHorizontalAmplitude = degToRad(30);
|
||||||
|
export const cameraSwingHorizontalFrequency = Math.E ** -5;
|
||||||
@@ -5,6 +5,8 @@ import { type TankState } from "../tankopedia/tankState";
|
|||||||
import { characteristicsOrder } from "../tankopedia/characteristicsOrder";
|
import { characteristicsOrder } from "../tankopedia/characteristicsOrder";
|
||||||
|
|
||||||
interface Tankopedia {
|
interface Tankopedia {
|
||||||
|
disturbed: boolean;
|
||||||
|
|
||||||
compare: TankopediaCompare;
|
compare: TankopediaCompare;
|
||||||
groups: Record<string, boolean>;
|
groups: Record<string, boolean>;
|
||||||
|
|
||||||
@@ -24,6 +26,8 @@ for (const { name } of characteristicsOrder) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const Tankopedia = new Varuna<Tankopedia, Tank>((tank) => ({
|
export const Tankopedia = new Varuna<Tankopedia, Tank>((tank) => ({
|
||||||
|
disturbed: false,
|
||||||
|
|
||||||
compare: TankopediaCompare.TierAndClass,
|
compare: TankopediaCompare.TierAndClass,
|
||||||
groups,
|
groups,
|
||||||
|
|
||||||
|
|||||||
@@ -43,6 +43,10 @@ export function createTankState({ tank, id }: Tank) {
|
|||||||
speed: 0,
|
speed: 0,
|
||||||
terrainHardness: TerrainHardness.Hard,
|
terrainHardness: TerrainHardness.Hard,
|
||||||
|
|
||||||
|
isHullRotating: false,
|
||||||
|
isTurretRotating: false,
|
||||||
|
isGunRotating: false,
|
||||||
|
|
||||||
status: status as Record<VehicleStatusKey, boolean>,
|
status: status as Record<VehicleStatusKey, boolean>,
|
||||||
} satisfies TankState;
|
} satisfies TankState;
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user