mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-21 02:37:05 +02: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 { Tankopedia } from "../../../../stores/tankopedia";
|
||||
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 vertexShader from "./shaders/vertex.glsl?raw";
|
||||
import { spacedArmorRenderTarget } from "./target";
|
||||
|
||||
@@ -27,7 +27,7 @@ import { discardClippingPlane } from "../../../core/three/discardClippingPlane";
|
||||
import { useEquipment } from "../../../hooks/useEquipment";
|
||||
import { Duel } from "../../../stores/duel";
|
||||
import { Tankopedia } from "../../../stores/tankopedia";
|
||||
import { transitionEvent } from "../../Tankopedia/HeroSection/components/TankSandbox/components/Lighting";
|
||||
import { transitionEvent } from "../../Lighting";
|
||||
import { ArmorType } from "./SpacedArmorScene";
|
||||
import type {
|
||||
ArmorUserData,
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import { useFrame } from "@react-three/fiber";
|
||||
|
||||
export function AutoClear() {
|
||||
export function CanvasAutoClear() {
|
||||
useFrame(({ gl }) => {
|
||||
gl.clear();
|
||||
}, 0);
|
||||
+7
-7
@@ -5,13 +5,13 @@ import type { QuicklimeEvent } from "quicklime";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { PerspectiveCamera, Vector3 } from "three";
|
||||
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 { 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> = {
|
||||
[Pose.HullDown]: 15,
|
||||
@@ -29,7 +29,7 @@ interface ControlsProps {
|
||||
enableRotate?: boolean;
|
||||
}
|
||||
|
||||
export function Controls({
|
||||
export function CanvasControl({
|
||||
autoRotate = true,
|
||||
zoomable = true,
|
||||
enableRotate = true,
|
||||
@@ -1,9 +1,9 @@
|
||||
import { useModel } from "../hooks/useModel";
|
||||
import { Guess, GuessState } from "../stores/guess";
|
||||
import { Controls } from "./CanvasControl";
|
||||
import { Lighting } from "./Lighting";
|
||||
import { SmartCanvas } from "./SmartCanvas";
|
||||
import { Controls } from "./Tankopedia/HeroSection/components/TankSandbox/components/Control";
|
||||
import { Lighting } from "./Tankopedia/HeroSection/components/TankSandbox/components/Lighting";
|
||||
import { TankModel } from "./Tankopedia/HeroSection/components/TankSandbox/components/TankModel";
|
||||
import { TankModel } from "./TankModel";
|
||||
|
||||
const BRIGHTNESS = 2 ** -5.5;
|
||||
const CONTRAST = 2 ** 2;
|
||||
|
||||
+4
-4
@@ -1,13 +1,13 @@
|
||||
import { useEffect } from "react";
|
||||
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 { api } from "../api/dynamic";
|
||||
import { applyPitchYawLimits } from "../core/blitz/applyPitchYawLimits";
|
||||
import { modelTransformEvent } from "../core/blitzkit/modelTransform";
|
||||
|
||||
const modelDefinitions = await api.models();
|
||||
|
||||
export function InitialAligner() {
|
||||
export function InitialCameraAligner() {
|
||||
const tank = Duel.use((state) => state.protagonist.tank);
|
||||
const turret = Duel.use((state) => state.protagonist.turret);
|
||||
const gun = Duel.use((state) => state.protagonist.gun);
|
||||
+3
-3
@@ -12,9 +12,9 @@ import {
|
||||
} from "react";
|
||||
import { HemisphereLight, SpotLight, type Group } from "three";
|
||||
import { degToRad, lerp } from "three/src/math/MathUtils.js";
|
||||
import { Tankopedia } from "../../../../../../stores/tankopedia";
|
||||
import { TankopediaPersistent } from "../../../../../../stores/tankopediaPersistent";
|
||||
import { TankopediaDisplay } from "../../../../../../stores/tankopediaPersistent/constants";
|
||||
import { Tankopedia } from "../stores/tankopedia";
|
||||
import { TankopediaPersistent } from "../stores/tankopediaPersistent";
|
||||
import { TankopediaDisplay } from "../stores/tankopediaPersistent/constants";
|
||||
|
||||
const ANGLE = degToRad(15);
|
||||
const REVEAL_ANIMATION_TIME = 3;
|
||||
@@ -4,8 +4,8 @@ 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 { Lighting } from "../Lighting";
|
||||
import { SceneProps } from "../SceneProps";
|
||||
import { MixerFallback } from "./components/MixerFallback";
|
||||
import { Model } from "./components/Model";
|
||||
|
||||
|
||||
File renamed without changes.
@@ -9,7 +9,7 @@ import {
|
||||
import { Jimp } from "jimp";
|
||||
import { useCallback, type RefObject } from "react";
|
||||
import { useLocale } from "../hooks/useLocale";
|
||||
import { screenshotReadyEvent } from "./Tankopedia/HeroSection/components/TankSandbox/components/SceneProps";
|
||||
import { screenshotReadyEvent } from "./SceneProps";
|
||||
|
||||
interface Props extends ButtonProps {
|
||||
canvas: RefObject<HTMLCanvasElement>;
|
||||
|
||||
+10
-10
@@ -1,18 +1,18 @@
|
||||
import { invalidate, useThree, type ThreeEvent } from "@react-three/fiber";
|
||||
import { useRef } from "react";
|
||||
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 { useTankTransform } from "../../../../../../hooks/useTankTransform";
|
||||
import { Duel } from "../../../../../../stores/duel";
|
||||
import { Tankopedia } from "../../../../../../stores/tankopedia";
|
||||
import { TankopediaDisplay } from "../../../../../../stores/tankopediaPersistent/constants";
|
||||
import { ModelTankWrapper } from "../../../../../Armor/components/ModelTankWrapper";
|
||||
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 { useTankTransform } from "../hooks/useTankTransform";
|
||||
import { Tankopedia } from "../stores/tankopedia";
|
||||
import { TankopediaDisplay } from "../stores/tankopediaPersistent/constants";
|
||||
import { ModelTankWrapper } from "./Armor/components/ModelTankWrapper";
|
||||
|
||||
export function TankModel() {
|
||||
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 { TrashIcon } from "@radix-ui/react-icons";
|
||||
import { useTankCompatibility } from "../../hooks/useTankCompatibility";
|
||||
import { useConsumables } from "../../hooks/useConsumables";
|
||||
import { useProtagonist } from "../../hooks/useProtagonist";
|
||||
import { useProtagonistGun } from "../../hooks/useProtagonistGun";
|
||||
import { useProtagonistTank } from "../../hooks/useProtagonistTank";
|
||||
import { useStrings } from "../../hooks/useStrings";
|
||||
import { useTankCompatibility } from "../../hooks/useTankCompatibility";
|
||||
import { Tankopedia } from "../../stores/tankopedia";
|
||||
import { hasUpgrades } from "../../tankopedia/hasUpgrades";
|
||||
import { vehicleStatusKeys } from "../../tankopedia/tankState";
|
||||
@@ -19,7 +19,7 @@ interface TankopediaLoadoutProps {
|
||||
}
|
||||
|
||||
export function TankopediaLoadout({ characteristics }: TankopediaLoadoutProps) {
|
||||
const tank = useProtagonist();
|
||||
const tank = useProtagonistTank();
|
||||
const showModules = hasUpgrades(tank);
|
||||
|
||||
return (
|
||||
@@ -120,7 +120,7 @@ interface ConsumablesProps {
|
||||
function Consumables({ characteristics }: ConsumablesProps) {
|
||||
const strings = useStrings();
|
||||
const consumables = useConsumables();
|
||||
const tank = useProtagonist();
|
||||
const tank = useProtagonistTank();
|
||||
const gun = useProtagonistGun();
|
||||
const isCompatible = useTankCompatibility(tank, gun);
|
||||
|
||||
@@ -165,7 +165,7 @@ function Consumable({ id, consumable }: ConsumableProps) {
|
||||
const isSelected = Tankopedia.use((state) =>
|
||||
state.protagonist.consumables.includes(id),
|
||||
);
|
||||
const tank = useProtagonist();
|
||||
const tank = useProtagonistTank();
|
||||
const unwrap = useUnwrap();
|
||||
|
||||
return (
|
||||
@@ -214,7 +214,7 @@ function Consumable({ id, consumable }: ConsumableProps) {
|
||||
|
||||
function Equipment() {
|
||||
const strings = useStrings();
|
||||
const tank = useProtagonist();
|
||||
const tank = useProtagonistTank();
|
||||
const equipment = useEquipment(tank.tank!);
|
||||
|
||||
return (
|
||||
@@ -273,7 +273,7 @@ interface EquipmentSlotProps {
|
||||
|
||||
function EquipmentSlot({ slot, rowIndex, columnIndex }: EquipmentSlotProps) {
|
||||
const equipmentIndex = rowIndex * equipmentColumns + columnIndex;
|
||||
const tank = useProtagonist();
|
||||
const tank = useProtagonistTank();
|
||||
const equipment = useEquipment(tank.tank!);
|
||||
const { price } = equipment.price.unlock_slot_prices[equipmentIndex];
|
||||
|
||||
@@ -307,7 +307,7 @@ function EquipmentOption({
|
||||
columnIndex,
|
||||
}: EquipmentOptionProps) {
|
||||
const equipmentIndex = rowIndex * equipmentColumns + columnIndex;
|
||||
const tank = useProtagonist();
|
||||
const tank = useProtagonistTank();
|
||||
const equipment = useEquipment(tank.tank!);
|
||||
const slot = equipment.preset.slots[equipmentIndex];
|
||||
const id = slot.options_catalog_i_ds[optionIndex];
|
||||
@@ -346,7 +346,7 @@ function EquipmentOption({
|
||||
|
||||
function Modules() {
|
||||
const strings = useStrings();
|
||||
const tank = useProtagonist();
|
||||
const tank = useProtagonistTank();
|
||||
const upgrades = Tankopedia.use((state) => state.protagonist.upgrades);
|
||||
const alternates = Tankopedia.use((state) => state.protagonist.alternates);
|
||||
const isStock = useMemo(() => {
|
||||
@@ -496,7 +496,7 @@ interface LineElementProps {
|
||||
function LineElement({ index, lineName, stage }: LineElementProps) {
|
||||
const upgrades = Tankopedia.use((state) => state.protagonist.upgrades);
|
||||
const alternates = Tankopedia.use((state) => state.protagonist.alternates);
|
||||
const tank = useProtagonist();
|
||||
const tank = useProtagonistTank();
|
||||
const strings = useStrings();
|
||||
const gameStrings = useGameStrings("TankEntity");
|
||||
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 { useAwait } from "./useAwait";
|
||||
|
||||
export function useProtagonist() {
|
||||
export function useProtagonistTank() {
|
||||
const id = Tankopedia.use((state) => state.protagonist.tank);
|
||||
const tank = useAwait(() => api.tank(id), `tank-${id}`);
|
||||
return tank;
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Tankopedia } from "../stores/tankopedia";
|
||||
import { useProtagonist } from "./useProtagonist";
|
||||
import { useProtagonistTank } from "./useProtagonistTank";
|
||||
|
||||
export function useProtagonistTurret() {
|
||||
const id = Tankopedia.use((state) => state.protagonist.gun);
|
||||
const tank = useProtagonist();
|
||||
const tank = useProtagonistTank();
|
||||
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 { api } from "../../../../../api/dynamic";
|
||||
import { TankopediaLoadout } from "../../../../../components/TankopediaLoadout";
|
||||
import { TankopediaSandbox } from "../../../../../components/TankopediaSandbox";
|
||||
import { withErrorWrapper } from "../../../../../hocs/withErrorWrapper";
|
||||
import { withLocale } from "../../../../../hocs/withLocale";
|
||||
import { useAwait } from "../../../../../hooks/useAwait";
|
||||
|
||||
Reference in new issue
Block a user