mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-25 03:29:51 +01:00
canvas
This commit is contained in:
1 parent
960ca4b0f4
commit
4247004b13
20 files changed
+279
-251
No files matched your search
+1
-1
@@ -22,7 +22,7 @@ import { jsxTree } from "../../../../core/blitzkit/jsxTree";
|
|||||||
import { Duel } from "../../../../stores/duel";
|
import { Duel } from "../../../../stores/duel";
|
||||||
import { Tankopedia } from "../../../../stores/tankopedia";
|
import { Tankopedia } from "../../../../stores/tankopedia";
|
||||||
import { TankopediaPersistent } from "../../../../stores/tankopediaPersistent";
|
import { TankopediaPersistent } from "../../../../stores/tankopediaPersistent";
|
||||||
import { transitionEvent } from "../../../Tankopedia/HeroSection/components/TankSandbox/components/Lighting";
|
import { transitionEvent } from "../../../Lighting";
|
||||||
import fragmentShader from "./shaders/fragment.glsl?raw";
|
import fragmentShader from "./shaders/fragment.glsl?raw";
|
||||||
import vertexShader from "./shaders/vertex.glsl?raw";
|
import vertexShader from "./shaders/vertex.glsl?raw";
|
||||||
import { spacedArmorRenderTarget } from "./target";
|
import { spacedArmorRenderTarget } from "./target";
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import { discardClippingPlane } from "../../../core/three/discardClippingPlane";
|
|||||||
import { useEquipment } from "../../../hooks/useEquipment";
|
import { useEquipment } from "../../../hooks/useEquipment";
|
||||||
import { Duel } from "../../../stores/duel";
|
import { Duel } from "../../../stores/duel";
|
||||||
import { Tankopedia } from "../../../stores/tankopedia";
|
import { Tankopedia } from "../../../stores/tankopedia";
|
||||||
import { transitionEvent } from "../../Tankopedia/HeroSection/components/TankSandbox/components/Lighting";
|
import { transitionEvent } from "../../Lighting";
|
||||||
import { ArmorType } from "./SpacedArmorScene";
|
import { ArmorType } from "./SpacedArmorScene";
|
||||||
import type {
|
import type {
|
||||||
ArmorUserData,
|
ArmorUserData,
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
import { useFrame } from "@react-three/fiber";
|
import { useFrame } from "@react-three/fiber";
|
||||||
|
|
||||||
export function AutoClear() {
|
export function CanvasAutoClear() {
|
||||||
useFrame(({ gl }) => {
|
useFrame(({ gl }) => {
|
||||||
gl.clear();
|
gl.clear();
|
||||||
}, 0);
|
}, 0);
|
||||||
+7
-7
@@ -5,13 +5,13 @@ import type { QuicklimeEvent } from "quicklime";
|
|||||||
import { useEffect, useRef } from "react";
|
import { useEffect, useRef } from "react";
|
||||||
import { PerspectiveCamera, Vector3 } from "three";
|
import { PerspectiveCamera, Vector3 } from "three";
|
||||||
import { OrbitControls as OrbitControlsClass } from "three-stdlib";
|
import { OrbitControls as OrbitControlsClass } from "three-stdlib";
|
||||||
import { api } from "../../../../../../api/dynamic";
|
|
||||||
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 { Duel } from "../../../../../../stores/duel";
|
||||||
import { Tankopedia } from "../../../../../../stores/tankopedia";
|
import { api } from "../api/dynamic";
|
||||||
|
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 { Tankopedia } from "../stores/tankopedia";
|
||||||
|
|
||||||
const poseDistances: Record<Pose, number> = {
|
const poseDistances: Record<Pose, number> = {
|
||||||
[Pose.HullDown]: 15,
|
[Pose.HullDown]: 15,
|
||||||
@@ -29,7 +29,7 @@ interface ControlsProps {
|
|||||||
enableRotate?: boolean;
|
enableRotate?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Controls({
|
export function CanvasControl({
|
||||||
autoRotate = true,
|
autoRotate = true,
|
||||||
zoomable = true,
|
zoomable = true,
|
||||||
enableRotate = true,
|
enableRotate = true,
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
import { useModel } from "../hooks/useModel";
|
import { useModel } from "../hooks/useModel";
|
||||||
import { Guess, GuessState } from "../stores/guess";
|
import { Guess, GuessState } from "../stores/guess";
|
||||||
|
import { Controls } from "./CanvasControl";
|
||||||
|
import { Lighting } from "./Lighting";
|
||||||
import { SmartCanvas } from "./SmartCanvas";
|
import { SmartCanvas } from "./SmartCanvas";
|
||||||
import { Controls } from "./Tankopedia/HeroSection/components/TankSandbox/components/Control";
|
import { TankModel } from "./TankModel";
|
||||||
import { Lighting } from "./Tankopedia/HeroSection/components/TankSandbox/components/Lighting";
|
|
||||||
import { TankModel } from "./Tankopedia/HeroSection/components/TankSandbox/components/TankModel";
|
|
||||||
|
|
||||||
const BRIGHTNESS = 2 ** -5.5;
|
const BRIGHTNESS = 2 ** -5.5;
|
||||||
const CONTRAST = 2 ** 2;
|
const CONTRAST = 2 ** 2;
|
||||||
|
|||||||
+4
-4
@@ -1,13 +1,13 @@
|
|||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { degToRad } from "three/src/math/MathUtils.js";
|
import { degToRad } from "three/src/math/MathUtils.js";
|
||||||
import { api } from "../../../../../../api/dynamic";
|
|
||||||
import { applyPitchYawLimits } from "../../../../../../core/blitz/applyPitchYawLimits";
|
|
||||||
import { modelTransformEvent } from "../../../../../../core/blitzkit/modelTransform";
|
|
||||||
import { Duel } from "../../../../../../stores/duel";
|
import { Duel } from "../../../../../../stores/duel";
|
||||||
|
import { api } from "../api/dynamic";
|
||||||
|
import { applyPitchYawLimits } from "../core/blitz/applyPitchYawLimits";
|
||||||
|
import { modelTransformEvent } from "../core/blitzkit/modelTransform";
|
||||||
|
|
||||||
const modelDefinitions = await api.models();
|
const modelDefinitions = await api.models();
|
||||||
|
|
||||||
export function InitialAligner() {
|
export function InitialCameraAligner() {
|
||||||
const tank = Duel.use((state) => state.protagonist.tank);
|
const tank = Duel.use((state) => state.protagonist.tank);
|
||||||
const turret = Duel.use((state) => state.protagonist.turret);
|
const turret = Duel.use((state) => state.protagonist.turret);
|
||||||
const gun = Duel.use((state) => state.protagonist.gun);
|
const gun = Duel.use((state) => state.protagonist.gun);
|
||||||
+3
-3
@@ -12,9 +12,9 @@ import {
|
|||||||
} from "react";
|
} from "react";
|
||||||
import { HemisphereLight, SpotLight, type Group } from "three";
|
import { HemisphereLight, SpotLight, type Group } from "three";
|
||||||
import { degToRad, lerp } from "three/src/math/MathUtils.js";
|
import { degToRad, lerp } from "three/src/math/MathUtils.js";
|
||||||
import { Tankopedia } from "../../../../../../stores/tankopedia";
|
import { Tankopedia } from "../stores/tankopedia";
|
||||||
import { TankopediaPersistent } from "../../../../../../stores/tankopediaPersistent";
|
import { TankopediaPersistent } from "../stores/tankopediaPersistent";
|
||||||
import { TankopediaDisplay } from "../../../../../../stores/tankopediaPersistent/constants";
|
import { TankopediaDisplay } from "../stores/tankopediaPersistent/constants";
|
||||||
|
|
||||||
const ANGLE = degToRad(15);
|
const ANGLE = degToRad(15);
|
||||||
const REVEAL_ANIMATION_TIME = 3;
|
const REVEAL_ANIMATION_TIME = 3;
|
||||||
@@ -4,8 +4,8 @@ import type { QuicklimeEvent } from "quicklime";
|
|||||||
import { forwardRef, Suspense, useEffect, useRef } from "react";
|
import { forwardRef, Suspense, useEffect, useRef } from "react";
|
||||||
import type { OrbitControls as OrbitControlsClass } from "three-stdlib";
|
import type { OrbitControls as OrbitControlsClass } from "three-stdlib";
|
||||||
import { controlsEnabledEvent } from "../../core/controlsEnabled";
|
import { controlsEnabledEvent } from "../../core/controlsEnabled";
|
||||||
import { Lighting } from "../Tankopedia/HeroSection/components/TankSandbox/components/Lighting";
|
import { Lighting } from "../Lighting";
|
||||||
import { SceneProps } from "../Tankopedia/HeroSection/components/TankSandbox/components/SceneProps";
|
import { SceneProps } from "../SceneProps";
|
||||||
import { MixerFallback } from "./components/MixerFallback";
|
import { MixerFallback } from "./components/MixerFallback";
|
||||||
import { Model } from "./components/Model";
|
import { Model } from "./components/Model";
|
||||||
|
|
||||||
|
|||||||
File renamed without changes.
@@ -9,7 +9,7 @@ import {
|
|||||||
import { Jimp } from "jimp";
|
import { Jimp } from "jimp";
|
||||||
import { useCallback, type RefObject } from "react";
|
import { useCallback, type RefObject } from "react";
|
||||||
import { useLocale } from "../hooks/useLocale";
|
import { useLocale } from "../hooks/useLocale";
|
||||||
import { screenshotReadyEvent } from "./Tankopedia/HeroSection/components/TankSandbox/components/SceneProps";
|
import { screenshotReadyEvent } from "./SceneProps";
|
||||||
|
|
||||||
interface Props extends ButtonProps {
|
interface Props extends ButtonProps {
|
||||||
canvas: RefObject<HTMLCanvasElement>;
|
canvas: RefObject<HTMLCanvasElement>;
|
||||||
|
|||||||
+10
-10
@@ -1,18 +1,18 @@
|
|||||||
import { invalidate, useThree, type ThreeEvent } from "@react-three/fiber";
|
import { invalidate, useThree, type ThreeEvent } from "@react-three/fiber";
|
||||||
import { useRef } from "react";
|
import { useRef } from "react";
|
||||||
import { Group, Mesh, MeshStandardMaterial, Vector2 } from "three";
|
import { Group, Mesh, MeshStandardMaterial, Vector2 } from "three";
|
||||||
import { applyPitchYawLimits } from "../../../../../../core/blitz/applyPitchYawLimits";
|
|
||||||
import { hasEquipment } from "../../../../../../core/blitzkit/hasEquipment";
|
|
||||||
import { jsxTree } from "../../../../../../core/blitzkit/jsxTree";
|
|
||||||
import { modelTransformEvent } from "../../../../../../core/blitzkit/modelTransform";
|
|
||||||
import { controlsEnabledEvent } from "../../../../../../core/controlsEnabled";
|
|
||||||
import { useModel } from "../../../../../../hooks/useModel";
|
|
||||||
import { useTankModelDefinition } from "../../../../../../hooks/useTankModelDefinition";
|
import { useTankModelDefinition } from "../../../../../../hooks/useTankModelDefinition";
|
||||||
import { useTankTransform } from "../../../../../../hooks/useTankTransform";
|
|
||||||
import { Duel } from "../../../../../../stores/duel";
|
import { Duel } from "../../../../../../stores/duel";
|
||||||
import { Tankopedia } from "../../../../../../stores/tankopedia";
|
import { applyPitchYawLimits } from "../core/blitz/applyPitchYawLimits";
|
||||||
import { TankopediaDisplay } from "../../../../../../stores/tankopediaPersistent/constants";
|
import { hasEquipment } from "../core/blitzkit/hasEquipment";
|
||||||
import { ModelTankWrapper } from "../../../../../Armor/components/ModelTankWrapper";
|
import { jsxTree } from "../core/blitzkit/jsxTree";
|
||||||
|
import { modelTransformEvent } from "../core/blitzkit/modelTransform";
|
||||||
|
import { controlsEnabledEvent } from "../core/controlsEnabled";
|
||||||
|
import { useModel } from "../hooks/useModel";
|
||||||
|
import { useTankTransform } from "../hooks/useTankTransform";
|
||||||
|
import { Tankopedia } from "../stores/tankopedia";
|
||||||
|
import { TankopediaDisplay } from "../stores/tankopediaPersistent/constants";
|
||||||
|
import { ModelTankWrapper } from "./Armor/components/ModelTankWrapper";
|
||||||
|
|
||||||
export function TankModel() {
|
export function TankModel() {
|
||||||
const protagonist = Duel.use((draft) => draft.protagonist);
|
const protagonist = Duel.use((draft) => draft.protagonist);
|
||||||
-200
@@ -1,200 +0,0 @@
|
|||||||
import {
|
|
||||||
forwardRef,
|
|
||||||
Suspense,
|
|
||||||
useEffect,
|
|
||||||
useImperativeHandle,
|
|
||||||
useRef,
|
|
||||||
} from "react";
|
|
||||||
import { applyPitchYawLimits } from "../../../../../core/blitz/applyPitchYawLimits";
|
|
||||||
import { modelTransformEvent } from "../../../../../core/blitzkit/modelTransform";
|
|
||||||
import { Pose, poseEvent } from "../../../../../core/blitzkit/pose";
|
|
||||||
import { useEquipment } from "../../../../../hooks/useEquipment";
|
|
||||||
import { useModel } from "../../../../../hooks/useModel";
|
|
||||||
import { useTankModelDefinition } from "../../../../../hooks/useTankModelDefinition";
|
|
||||||
import { Duel } from "../../../../../stores/duel";
|
|
||||||
import { Tankopedia } from "../../../../../stores/tankopedia";
|
|
||||||
import { TankopediaPersistent } from "../../../../../stores/tankopediaPersistent";
|
|
||||||
import { TankopediaDisplay } from "../../../../../stores/tankopediaPersistent/constants";
|
|
||||||
import { Armor } from "../../../../Armor";
|
|
||||||
import { ArmorPlateDisplay } from "../../../../Armor/components/ArmorPlateDisplay";
|
|
||||||
import { ShotDisplay } from "../../../../Armor/components/ShotDisplay";
|
|
||||||
import {
|
|
||||||
StaticArmor,
|
|
||||||
type ThicknessRange,
|
|
||||||
} from "../../../../Armor/components/StaticArmor";
|
|
||||||
import { SmartCanvas } from "../../../../SmartCanvas";
|
|
||||||
import { AutoClear } from "./components/AutoClear";
|
|
||||||
import { Controls } from "./components/Control";
|
|
||||||
import { InitialAligner } from "./components/InitialAligner";
|
|
||||||
import { Lighting } from "./components/Lighting";
|
|
||||||
import { SceneProps } from "./components/SceneProps";
|
|
||||||
import { TankModel } from "./components/TankModel";
|
|
||||||
|
|
||||||
interface TankSandboxProps {
|
|
||||||
thicknessRange: ThicknessRange;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
|
|
||||||
({ thicknessRange }, ref) => {
|
|
||||||
const canvas = useRef<HTMLCanvasElement>(null);
|
|
||||||
const hasImprovedVerticalStabilizer = useEquipment(122);
|
|
||||||
const hasDownImprovedVerticalStabilizer = useEquipment(124);
|
|
||||||
const protagonistGun = Duel.use((state) => state.protagonist.gun);
|
|
||||||
const protagonistTurret = Duel.use((state) => state.protagonist.turret);
|
|
||||||
const tankModelDefinition = useTankModelDefinition();
|
|
||||||
const turretModelDefinition =
|
|
||||||
tankModelDefinition.turrets[protagonistTurret.id];
|
|
||||||
const gunModelDefinition = turretModelDefinition.guns[protagonistGun.id];
|
|
||||||
const display = Tankopedia.use((state) => state.display);
|
|
||||||
const hideTankModelUnderArmor = TankopediaPersistent.use(
|
|
||||||
(state) => state.hideTankModelUnderArmor,
|
|
||||||
);
|
|
||||||
|
|
||||||
const id = Duel.use((state) => state.protagonist.tank.id);
|
|
||||||
const { hasPbr } = useModel(id);
|
|
||||||
|
|
||||||
useImperativeHandle(ref, () => canvas.current!, []);
|
|
||||||
|
|
||||||
function handlePointerDown() {
|
|
||||||
window.addEventListener("pointermove", handlePointerMove);
|
|
||||||
window.addEventListener("pointerup", handlePointerUp);
|
|
||||||
}
|
|
||||||
function handlePointerMove(event: PointerEvent) {
|
|
||||||
event.preventDefault();
|
|
||||||
}
|
|
||||||
function handlePointerUp(event: PointerEvent) {
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
window.removeEventListener("pointermove", handlePointerMove);
|
|
||||||
window.removeEventListener("pointerup", handlePointerUp);
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
function handlePoseEvent(pose: Pose) {
|
|
||||||
switch (pose) {
|
|
||||||
case Pose.HullDown: {
|
|
||||||
const [pitch, yaw] = applyPitchYawLimits(
|
|
||||||
-Infinity,
|
|
||||||
0,
|
|
||||||
gunModelDefinition.pitch!,
|
|
||||||
turretModelDefinition.yaw,
|
|
||||||
hasImprovedVerticalStabilizer,
|
|
||||||
hasDownImprovedVerticalStabilizer,
|
|
||||||
);
|
|
||||||
|
|
||||||
modelTransformEvent.dispatch({ pitch, yaw });
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case Pose.FaceHug: {
|
|
||||||
const [pitch, yaw] = applyPitchYawLimits(
|
|
||||||
Infinity,
|
|
||||||
0,
|
|
||||||
gunModelDefinition.pitch!,
|
|
||||||
turretModelDefinition.yaw,
|
|
||||||
hasImprovedVerticalStabilizer,
|
|
||||||
);
|
|
||||||
|
|
||||||
modelTransformEvent.dispatch({ pitch, yaw });
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case Pose.Default:
|
|
||||||
const [pitch, yaw] = applyPitchYawLimits(
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
gunModelDefinition.pitch!,
|
|
||||||
turretModelDefinition.yaw,
|
|
||||||
hasImprovedVerticalStabilizer,
|
|
||||||
);
|
|
||||||
|
|
||||||
modelTransformEvent.dispatch({ pitch, yaw });
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
poseEvent.on(handlePoseEvent);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
poseEvent.off(handlePoseEvent);
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const [pitch, yaw] = applyPitchYawLimits(
|
|
||||||
modelTransformEvent.last!.pitch,
|
|
||||||
modelTransformEvent.last!.yaw,
|
|
||||||
gunModelDefinition.pitch!,
|
|
||||||
turretModelDefinition.yaw,
|
|
||||||
hasImprovedVerticalStabilizer,
|
|
||||||
);
|
|
||||||
|
|
||||||
modelTransformEvent.dispatch({ pitch, yaw });
|
|
||||||
}, [protagonistGun, protagonistTurret]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (display !== TankopediaDisplay.DynamicArmor) {
|
|
||||||
Tankopedia.mutate((draft) => {
|
|
||||||
draft.shot = undefined;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (display !== TankopediaDisplay.StaticArmor) {
|
|
||||||
Tankopedia.mutate((draft) => {
|
|
||||||
draft.highlightArmor = undefined;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [display]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<SmartCanvas
|
|
||||||
resize={{ debounce: 0 }}
|
|
||||||
ref={canvas}
|
|
||||||
gl={{
|
|
||||||
clippingPlanes: [],
|
|
||||||
localClippingEnabled: true,
|
|
||||||
preserveDrawingBuffer: true,
|
|
||||||
}}
|
|
||||||
shadows="soft"
|
|
||||||
onPointerDown={handlePointerDown}
|
|
||||||
onPointerMissed={() => {
|
|
||||||
Tankopedia.mutate((draft) => {
|
|
||||||
draft.shot = undefined;
|
|
||||||
draft.highlightArmor = undefined;
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
style={{
|
|
||||||
userSelect: "none",
|
|
||||||
width: "100%",
|
|
||||||
height: "100%",
|
|
||||||
outline: undefined,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Lighting hasPbr={hasPbr} />
|
|
||||||
|
|
||||||
<SceneProps />
|
|
||||||
{(display === TankopediaDisplay.Model ||
|
|
||||||
(display === TankopediaDisplay.DynamicArmor &&
|
|
||||||
!hideTankModelUnderArmor)) && <TankModel />}
|
|
||||||
<ShotDisplay />
|
|
||||||
<ArmorPlateDisplay />
|
|
||||||
<AutoClear />
|
|
||||||
|
|
||||||
<Suspense>
|
|
||||||
{/* Controls within Suspense to allow for frame-perfect start of camera auto-rotate */}
|
|
||||||
<Controls />
|
|
||||||
|
|
||||||
{display === TankopediaDisplay.DynamicArmor && <Armor />}
|
|
||||||
{display === TankopediaDisplay.StaticArmor && (
|
|
||||||
<StaticArmor thicknessRange={thicknessRange} />
|
|
||||||
)}
|
|
||||||
</Suspense>
|
|
||||||
|
|
||||||
<InitialAligner />
|
|
||||||
</SmartCanvas>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
import type { Consumable } from "@blitzkit/core";
|
import type { Consumable } from "@blitzkit/core";
|
||||||
import { TrashIcon } from "@radix-ui/react-icons";
|
import { TrashIcon } from "@radix-ui/react-icons";
|
||||||
import { useTankCompatibility } from "../../hooks/useTankCompatibility";
|
|
||||||
import { useConsumables } from "../../hooks/useConsumables";
|
import { useConsumables } from "../../hooks/useConsumables";
|
||||||
import { useProtagonist } from "../../hooks/useProtagonist";
|
|
||||||
import { useProtagonistGun } from "../../hooks/useProtagonistGun";
|
import { useProtagonistGun } from "../../hooks/useProtagonistGun";
|
||||||
|
import { useProtagonistTank } from "../../hooks/useProtagonistTank";
|
||||||
import { useStrings } from "../../hooks/useStrings";
|
import { useStrings } from "../../hooks/useStrings";
|
||||||
|
import { useTankCompatibility } from "../../hooks/useTankCompatibility";
|
||||||
import { Tankopedia } from "../../stores/tankopedia";
|
import { Tankopedia } from "../../stores/tankopedia";
|
||||||
import { hasUpgrades } from "../../tankopedia/hasUpgrades";
|
import { hasUpgrades } from "../../tankopedia/hasUpgrades";
|
||||||
import { vehicleStatusKeys } from "../../tankopedia/tankState";
|
import { vehicleStatusKeys } from "../../tankopedia/tankState";
|
||||||
@@ -19,7 +19,7 @@ interface TankopediaLoadoutProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function TankopediaLoadout({ characteristics }: TankopediaLoadoutProps) {
|
export function TankopediaLoadout({ characteristics }: TankopediaLoadoutProps) {
|
||||||
const tank = useProtagonist();
|
const tank = useProtagonistTank();
|
||||||
const showModules = hasUpgrades(tank);
|
const showModules = hasUpgrades(tank);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -120,7 +120,7 @@ interface ConsumablesProps {
|
|||||||
function Consumables({ characteristics }: ConsumablesProps) {
|
function Consumables({ characteristics }: ConsumablesProps) {
|
||||||
const strings = useStrings();
|
const strings = useStrings();
|
||||||
const consumables = useConsumables();
|
const consumables = useConsumables();
|
||||||
const tank = useProtagonist();
|
const tank = useProtagonistTank();
|
||||||
const gun = useProtagonistGun();
|
const gun = useProtagonistGun();
|
||||||
const isCompatible = useTankCompatibility(tank, gun);
|
const isCompatible = useTankCompatibility(tank, gun);
|
||||||
|
|
||||||
@@ -165,7 +165,7 @@ function Consumable({ id, consumable }: ConsumableProps) {
|
|||||||
const isSelected = Tankopedia.use((state) =>
|
const isSelected = Tankopedia.use((state) =>
|
||||||
state.protagonist.consumables.includes(id),
|
state.protagonist.consumables.includes(id),
|
||||||
);
|
);
|
||||||
const tank = useProtagonist();
|
const tank = useProtagonistTank();
|
||||||
const unwrap = useUnwrap();
|
const unwrap = useUnwrap();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -214,7 +214,7 @@ function Consumable({ id, consumable }: ConsumableProps) {
|
|||||||
|
|
||||||
function Equipment() {
|
function Equipment() {
|
||||||
const strings = useStrings();
|
const strings = useStrings();
|
||||||
const tank = useProtagonist();
|
const tank = useProtagonistTank();
|
||||||
const equipment = useEquipment(tank.tank!);
|
const equipment = useEquipment(tank.tank!);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -273,7 +273,7 @@ interface EquipmentSlotProps {
|
|||||||
|
|
||||||
function EquipmentSlot({ slot, rowIndex, columnIndex }: EquipmentSlotProps) {
|
function EquipmentSlot({ slot, rowIndex, columnIndex }: EquipmentSlotProps) {
|
||||||
const equipmentIndex = rowIndex * equipmentColumns + columnIndex;
|
const equipmentIndex = rowIndex * equipmentColumns + columnIndex;
|
||||||
const tank = useProtagonist();
|
const tank = useProtagonistTank();
|
||||||
const equipment = useEquipment(tank.tank!);
|
const equipment = useEquipment(tank.tank!);
|
||||||
const { price } = equipment.price.unlock_slot_prices[equipmentIndex];
|
const { price } = equipment.price.unlock_slot_prices[equipmentIndex];
|
||||||
|
|
||||||
@@ -307,7 +307,7 @@ function EquipmentOption({
|
|||||||
columnIndex,
|
columnIndex,
|
||||||
}: EquipmentOptionProps) {
|
}: EquipmentOptionProps) {
|
||||||
const equipmentIndex = rowIndex * equipmentColumns + columnIndex;
|
const equipmentIndex = rowIndex * equipmentColumns + columnIndex;
|
||||||
const tank = useProtagonist();
|
const tank = useProtagonistTank();
|
||||||
const equipment = useEquipment(tank.tank!);
|
const equipment = useEquipment(tank.tank!);
|
||||||
const slot = equipment.preset.slots[equipmentIndex];
|
const slot = equipment.preset.slots[equipmentIndex];
|
||||||
const id = slot.options_catalog_i_ds[optionIndex];
|
const id = slot.options_catalog_i_ds[optionIndex];
|
||||||
@@ -346,7 +346,7 @@ function EquipmentOption({
|
|||||||
|
|
||||||
function Modules() {
|
function Modules() {
|
||||||
const strings = useStrings();
|
const strings = useStrings();
|
||||||
const tank = useProtagonist();
|
const tank = useProtagonistTank();
|
||||||
const upgrades = Tankopedia.use((state) => state.protagonist.upgrades);
|
const upgrades = Tankopedia.use((state) => state.protagonist.upgrades);
|
||||||
const alternates = Tankopedia.use((state) => state.protagonist.alternates);
|
const alternates = Tankopedia.use((state) => state.protagonist.alternates);
|
||||||
const isStock = useMemo(() => {
|
const isStock = useMemo(() => {
|
||||||
@@ -496,7 +496,7 @@ interface LineElementProps {
|
|||||||
function LineElement({ index, lineName, stage }: LineElementProps) {
|
function LineElement({ index, lineName, stage }: LineElementProps) {
|
||||||
const upgrades = Tankopedia.use((state) => state.protagonist.upgrades);
|
const upgrades = Tankopedia.use((state) => state.protagonist.upgrades);
|
||||||
const alternates = Tankopedia.use((state) => state.protagonist.alternates);
|
const alternates = Tankopedia.use((state) => state.protagonist.alternates);
|
||||||
const tank = useProtagonist();
|
const tank = useProtagonistTank();
|
||||||
const strings = useStrings();
|
const strings = useStrings();
|
||||||
const gameStrings = useGameStrings("TankEntity");
|
const gameStrings = useGameStrings("TankEntity");
|
||||||
const upgradePreset = useUpgradePreset(tank.tank!.tank_upgrade_preset);
|
const upgradePreset = useUpgradePreset(tank.tank!.tank_upgrade_preset);
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
.wrapper {
|
||||||
|
height: calc(100dvh - 16rem);
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.sandbox {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
.canvas {
|
||||||
|
background-color: black;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,202 @@
|
|||||||
|
import {
|
||||||
|
forwardRef,
|
||||||
|
Suspense,
|
||||||
|
useEffect,
|
||||||
|
useImperativeHandle,
|
||||||
|
useRef,
|
||||||
|
} from "react";
|
||||||
|
import { applyPitchYawLimits } from "../../core/blitz/applyPitchYawLimits";
|
||||||
|
import { modelTransformEvent } from "../../core/blitzkit/modelTransform";
|
||||||
|
import { Pose, poseEvent } from "../../core/blitzkit/pose";
|
||||||
|
import { useEquipment } from "../../hooks/useEquipment";
|
||||||
|
import { useModel } from "../../hooks/useModel";
|
||||||
|
import { useProtagonistGun } from "../../hooks/useProtagonistGun";
|
||||||
|
import { useProtagonistTank } from "../../hooks/useProtagonistTank";
|
||||||
|
import { useProtagonistTurret } from "../../hooks/useProtagonistTurret";
|
||||||
|
import { Tankopedia } from "../../stores/tankopedia";
|
||||||
|
import { TankopediaPersistent } from "../../stores/tankopediaPersistent";
|
||||||
|
import { TankopediaDisplay } from "../../stores/tankopediaPersistent/constants";
|
||||||
|
import { Armor } from "../Armor";
|
||||||
|
import { ArmorPlateDisplay } from "../Armor/components/ArmorPlateDisplay";
|
||||||
|
import { ShotDisplay } from "../Armor/components/ShotDisplay";
|
||||||
|
import {
|
||||||
|
StaticArmor,
|
||||||
|
type ThicknessRange,
|
||||||
|
} from "../Armor/components/StaticArmor";
|
||||||
|
import { CanvasAutoClear } from "../CanvasAutoClear";
|
||||||
|
import { CanvasControl } from "../CanvasControl";
|
||||||
|
import { InitialCameraAligner } from "../InitialCameraAligner";
|
||||||
|
import { Lighting } from "../Lighting";
|
||||||
|
import { SceneProps } from "../SceneProps";
|
||||||
|
import { SmartCanvas } from "../SmartCanvas";
|
||||||
|
import { TankModel } from "../TankModel";
|
||||||
|
import styles from "./index.module.css";
|
||||||
|
|
||||||
|
interface TankSandboxProps {
|
||||||
|
thicknessRange: ThicknessRange;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TankopediaSandbox = forwardRef<
|
||||||
|
HTMLCanvasElement,
|
||||||
|
TankSandboxProps
|
||||||
|
>(({ thicknessRange }, ref) => {
|
||||||
|
const canvas = useRef<HTMLCanvasElement>(null);
|
||||||
|
const hasImprovedVerticalStabilizer = useEquipment(122);
|
||||||
|
const hasDownImprovedVerticalStabilizer = useEquipment(124);
|
||||||
|
|
||||||
|
const tank = useProtagonistTank();
|
||||||
|
const gun = useProtagonistGun();
|
||||||
|
const turret = useProtagonistTurret();
|
||||||
|
|
||||||
|
const model = Tankopedia.use((state) => state.protagonist.model);
|
||||||
|
const turretModelDefinition = model.turrets[turret.id];
|
||||||
|
const gunModelDefinition = turretModelDefinition.guns[gun.id];
|
||||||
|
const display = Tankopedia.use((state) => state.display);
|
||||||
|
const hideTankModelUnderArmor = TankopediaPersistent.use(
|
||||||
|
(state) => state.hideTankModelUnderArmor,
|
||||||
|
);
|
||||||
|
|
||||||
|
const { hasPbr } = useModel(tank.id);
|
||||||
|
|
||||||
|
useImperativeHandle(ref, () => canvas.current!, []);
|
||||||
|
|
||||||
|
function handlePointerDown() {
|
||||||
|
window.addEventListener("pointermove", handlePointerMove);
|
||||||
|
window.addEventListener("pointerup", handlePointerUp);
|
||||||
|
}
|
||||||
|
function handlePointerMove(event: PointerEvent) {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
function handlePointerUp(event: PointerEvent) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
window.removeEventListener("pointermove", handlePointerMove);
|
||||||
|
window.removeEventListener("pointerup", handlePointerUp);
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
function handlePoseEvent(pose: Pose) {
|
||||||
|
switch (pose) {
|
||||||
|
case Pose.HullDown: {
|
||||||
|
const [pitch, yaw] = applyPitchYawLimits(
|
||||||
|
-Infinity,
|
||||||
|
0,
|
||||||
|
gunModelDefinition.pitch!,
|
||||||
|
turretModelDefinition.yaw,
|
||||||
|
hasImprovedVerticalStabilizer,
|
||||||
|
hasDownImprovedVerticalStabilizer,
|
||||||
|
);
|
||||||
|
|
||||||
|
modelTransformEvent.dispatch({ pitch, yaw });
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case Pose.FaceHug: {
|
||||||
|
const [pitch, yaw] = applyPitchYawLimits(
|
||||||
|
Infinity,
|
||||||
|
0,
|
||||||
|
gunModelDefinition.pitch!,
|
||||||
|
turretModelDefinition.yaw,
|
||||||
|
hasImprovedVerticalStabilizer,
|
||||||
|
);
|
||||||
|
|
||||||
|
modelTransformEvent.dispatch({ pitch, yaw });
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case Pose.Default:
|
||||||
|
const [pitch, yaw] = applyPitchYawLimits(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
gunModelDefinition.pitch!,
|
||||||
|
turretModelDefinition.yaw,
|
||||||
|
hasImprovedVerticalStabilizer,
|
||||||
|
);
|
||||||
|
|
||||||
|
modelTransformEvent.dispatch({ pitch, yaw });
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
poseEvent.on(handlePoseEvent);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
poseEvent.off(handlePoseEvent);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const [pitch, yaw] = applyPitchYawLimits(
|
||||||
|
modelTransformEvent.last!.pitch,
|
||||||
|
modelTransformEvent.last!.yaw,
|
||||||
|
gunModelDefinition.pitch!,
|
||||||
|
turretModelDefinition.yaw,
|
||||||
|
hasImprovedVerticalStabilizer,
|
||||||
|
);
|
||||||
|
|
||||||
|
modelTransformEvent.dispatch({ pitch, yaw });
|
||||||
|
}, [gun, turret]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (display !== TankopediaDisplay.DynamicArmor) {
|
||||||
|
Tankopedia.mutate((draft) => {
|
||||||
|
draft.shot = undefined;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (display !== TankopediaDisplay.StaticArmor) {
|
||||||
|
Tankopedia.mutate((draft) => {
|
||||||
|
draft.highlightArmor = undefined;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [display]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.wrapper}>
|
||||||
|
<SmartCanvas
|
||||||
|
className={styles.canvas}
|
||||||
|
resize={{ debounce: 0 }}
|
||||||
|
ref={canvas}
|
||||||
|
gl={{
|
||||||
|
clippingPlanes: [],
|
||||||
|
localClippingEnabled: true,
|
||||||
|
preserveDrawingBuffer: true,
|
||||||
|
}}
|
||||||
|
shadows="soft"
|
||||||
|
onPointerDown={handlePointerDown}
|
||||||
|
onPointerMissed={() => {
|
||||||
|
Tankopedia.mutate((draft) => {
|
||||||
|
draft.shot = undefined;
|
||||||
|
draft.highlightArmor = undefined;
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Lighting hasPbr={hasPbr} />
|
||||||
|
|
||||||
|
<SceneProps />
|
||||||
|
{(display === TankopediaDisplay.Model ||
|
||||||
|
(display === TankopediaDisplay.DynamicArmor &&
|
||||||
|
!hideTankModelUnderArmor)) && <TankModel />}
|
||||||
|
|
||||||
|
<ShotDisplay />
|
||||||
|
<ArmorPlateDisplay />
|
||||||
|
<CanvasAutoClear />
|
||||||
|
|
||||||
|
<Suspense>
|
||||||
|
{/* Controls within Suspense to allow for frame-perfect start of camera auto-rotate */}
|
||||||
|
<CanvasControl />
|
||||||
|
|
||||||
|
{display === TankopediaDisplay.DynamicArmor && <Armor />}
|
||||||
|
{display === TankopediaDisplay.StaticArmor && (
|
||||||
|
<StaticArmor thicknessRange={thicknessRange} />
|
||||||
|
)}
|
||||||
|
</Suspense>
|
||||||
|
|
||||||
|
<InitialCameraAligner />
|
||||||
|
</SmartCanvas>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import { degToRad } from "three/src/math/MathUtils.js";
|
||||||
|
|
||||||
|
export const fov = 20;
|
||||||
|
|
||||||
|
export const initialCameraR = 15;
|
||||||
|
export const initialCameraPhi = degToRad(90 - 10);
|
||||||
|
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;
|
||||||
|
|
||||||
|
export const cameraCenterCorrectionAmplitude = 0.8;
|
||||||
+1
-1
@@ -2,7 +2,7 @@ import { api } from "../api/dynamic";
|
|||||||
import { Tankopedia } from "../stores/tankopedia";
|
import { Tankopedia } from "../stores/tankopedia";
|
||||||
import { useAwait } from "./useAwait";
|
import { useAwait } from "./useAwait";
|
||||||
|
|
||||||
export function useProtagonist() {
|
export function useProtagonistTank() {
|
||||||
const id = Tankopedia.use((state) => state.protagonist.tank);
|
const id = Tankopedia.use((state) => state.protagonist.tank);
|
||||||
const tank = useAwait(() => api.tank(id), `tank-${id}`);
|
const tank = useAwait(() => api.tank(id), `tank-${id}`);
|
||||||
return tank;
|
return tank;
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import { Tankopedia } from "../stores/tankopedia";
|
import { Tankopedia } from "../stores/tankopedia";
|
||||||
import { useProtagonist } from "./useProtagonist";
|
import { useProtagonistTank } from "./useProtagonistTank";
|
||||||
|
|
||||||
export function useProtagonistTurret() {
|
export function useProtagonistTurret() {
|
||||||
const id = Tankopedia.use((state) => state.protagonist.gun);
|
const id = Tankopedia.use((state) => state.protagonist.gun);
|
||||||
const tank = useProtagonist();
|
const tank = useProtagonistTank();
|
||||||
return tank.turrets[id];
|
return tank.turrets[id];
|
||||||
}
|
}
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
import { Tankopedia } from "../stores/tankopedia";
|
|
||||||
|
|
||||||
export function useTankModelDefinition() {
|
|
||||||
return Tankopedia.use((state) => state.model);
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { api } from "../../../../../api/dynamic";
|
import { api } from "../../../../../api/dynamic";
|
||||||
import { TankopediaLoadout } from "../../../../../components/TankopediaLoadout";
|
import { TankopediaLoadout } from "../../../../../components/TankopediaLoadout";
|
||||||
|
import { TankopediaSandbox } from "../../../../../components/TankopediaSandbox";
|
||||||
import { withErrorWrapper } from "../../../../../hocs/withErrorWrapper";
|
import { withErrorWrapper } from "../../../../../hocs/withErrorWrapper";
|
||||||
import { withLocale } from "../../../../../hocs/withLocale";
|
import { withLocale } from "../../../../../hocs/withLocale";
|
||||||
import { useAwait } from "../../../../../hooks/useAwait";
|
import { useAwait } from "../../../../../hooks/useAwait";
|
||||||
|
|||||||
Reference in new issue
Block a user