mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-25 19:49:50 +01:00
feat: start of new lighting
This commit is contained in:
1 parent
f3762e356f
commit
94cbd95509
7 files changed
+40
-238
No files matched your search
Binary file not shown.
|
Before Width: | Height: | Size: 490 KiB After Width: | Height: | Size: 669 KiB |
@@ -1,4 +1,3 @@
|
|||||||
import { Vector3 } from 'three';
|
|
||||||
import { awaitableModelDefinitions } from '../../../core/awaitables/modelDefinitions';
|
import { awaitableModelDefinitions } from '../../../core/awaitables/modelDefinitions';
|
||||||
import { jsxTree } from '../../../core/blitzkit/jsxTree';
|
import { jsxTree } from '../../../core/blitzkit/jsxTree';
|
||||||
import { useModel } from '../../../hooks/useModel';
|
import { useModel } from '../../../hooks/useModel';
|
||||||
@@ -32,17 +31,6 @@ export function Model() {
|
|||||||
const gunTurretModel = gunTankModel.turrets[gun.turret.id];
|
const gunTurretModel = gunTankModel.turrets[gun.turret.id];
|
||||||
const gunModel = gunTurretModel.guns[gun.gun.id];
|
const gunModel = gunTurretModel.guns[gun.gun.id];
|
||||||
|
|
||||||
const turretDelta = new Vector3(
|
|
||||||
tankModel.turret_origin.x - turretTankModel.turret_origin.x,
|
|
||||||
tankModel.turret_origin.y - turretTankModel.turret_origin.y,
|
|
||||||
tankModel.turret_origin.z - turretTankModel.turret_origin.z,
|
|
||||||
);
|
|
||||||
const gunDelta = new Vector3(
|
|
||||||
turretModel.gun_origin.x - gunTurretModel.gun_origin.x,
|
|
||||||
turretModel.gun_origin.z - gunTurretModel.gun_origin.z,
|
|
||||||
turretModel.gun_origin.y - gunTurretModel.gun_origin.y,
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<group rotation={[-Math.PI / 2, 0, 0]}>
|
<group rotation={[-Math.PI / 2, 0, 0]}>
|
||||||
{hullNodes.map((node) => {
|
{hullNodes.map((node) => {
|
||||||
@@ -55,7 +43,7 @@ export function Model() {
|
|||||||
|
|
||||||
return jsxTree(node, {
|
return jsxTree(node, {
|
||||||
mesh(_, props, key) {
|
mesh(_, props, key) {
|
||||||
return <mesh {...props} key={key} />;
|
return <mesh {...props} key={key} castShadow receiveShadow />;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
})}
|
})}
|
||||||
@@ -86,7 +74,7 @@ export function Model() {
|
|||||||
|
|
||||||
return jsxTree(node, {
|
return jsxTree(node, {
|
||||||
mesh(_, props, key) {
|
mesh(_, props, key) {
|
||||||
return <mesh {...props} key={key} />;
|
return <mesh {...props} key={key} castShadow receiveShadow />;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
})}
|
})}
|
||||||
@@ -126,7 +114,7 @@ export function Model() {
|
|||||||
|
|
||||||
return jsxTree(node, {
|
return jsxTree(node, {
|
||||||
mesh(_, props, key) {
|
mesh(_, props, key) {
|
||||||
return <mesh {...props} key={key} />;
|
return <mesh {...props} key={key} castShadow receiveShadow />;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Environment, OrbitControls } from '@react-three/drei';
|
import { OrbitControls } from '@react-three/drei';
|
||||||
import { Canvas } from '@react-three/fiber';
|
import { Canvas } from '@react-three/fiber';
|
||||||
import { Suspense } from 'react';
|
import { Suspense } from 'react';
|
||||||
import { Model } from './components/Model';
|
import { Model } from './components/Model';
|
||||||
@@ -6,16 +6,16 @@ import { Model } from './components/Model';
|
|||||||
export function MixerScene() {
|
export function MixerScene() {
|
||||||
return (
|
return (
|
||||||
<Canvas
|
<Canvas
|
||||||
|
shadows
|
||||||
camera={{
|
camera={{
|
||||||
fov: 65,
|
fov: 45,
|
||||||
position: [5, 5, -5],
|
position: [5, 5, -5],
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<OrbitControls />
|
<OrbitControls />
|
||||||
|
|
||||||
<Environment preset="lobby" />
|
<directionalLight position={[10, 10, 10]} intensity={5} castShadow />
|
||||||
{/* <ambientLight intensity={6} />
|
<directionalLight position={[-10, -10, -10]} intensity={3} castShadow />
|
||||||
<pointLight position={[10, 10, 10]} /> */}
|
|
||||||
|
|
||||||
<Suspense fallback={null}>
|
<Suspense fallback={null}>
|
||||||
<Model />
|
<Model />
|
||||||
|
|||||||
+26
-1
@@ -1,11 +1,13 @@
|
|||||||
import { Environment } from '@react-three/drei';
|
import { Environment } from '@react-three/drei';
|
||||||
import { Euler } from 'three';
|
import { useEffect, useRef } from 'react';
|
||||||
|
import { DirectionalLight, Euler } from 'three';
|
||||||
import { degToRad } from 'three/src/math/MathUtils.js';
|
import { degToRad } from 'three/src/math/MathUtils.js';
|
||||||
import { useModel } from '../../../../../../hooks/useModel';
|
import { useModel } from '../../../../../../hooks/useModel';
|
||||||
import { Duel } from '../../../../../../stores/duel';
|
import { Duel } from '../../../../../../stores/duel';
|
||||||
import { TankopediaDisplay } from '../../../../../../stores/tankopediaPersistent/constants';
|
import { TankopediaDisplay } from '../../../../../../stores/tankopediaPersistent/constants';
|
||||||
|
|
||||||
const LIGHTS_COUNT = 5;
|
const LIGHTS_COUNT = 5;
|
||||||
|
const SIZE = 2 ** 11;
|
||||||
|
|
||||||
interface LightingProps {
|
interface LightingProps {
|
||||||
display: TankopediaDisplay;
|
display: TankopediaDisplay;
|
||||||
@@ -17,6 +19,29 @@ export function Lighting({ display }: LightingProps) {
|
|||||||
const isBrighterLighting =
|
const isBrighterLighting =
|
||||||
!hasPbr && display !== TankopediaDisplay.StaticArmor;
|
!hasPbr && display !== TankopediaDisplay.StaticArmor;
|
||||||
|
|
||||||
|
const light = useRef<DirectionalLight>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!light.current) return;
|
||||||
|
|
||||||
|
light.current.shadow.mapSize.width = SIZE;
|
||||||
|
light.current.shadow.mapSize.height = SIZE;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<directionalLight
|
||||||
|
position={[-2, 0.5, -1]}
|
||||||
|
intensity={5}
|
||||||
|
castShadow
|
||||||
|
ref={light}
|
||||||
|
/>
|
||||||
|
<directionalLight position={[2, -0.5, 0]} intensity={1} />
|
||||||
|
<directionalLight position={[0, -0.5, -2]} intensity={0.5} />
|
||||||
|
<ambientLight intensity={1} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{display !== TankopediaDisplay.StaticArmor && (
|
{display !== TankopediaDisplay.StaticArmor && (
|
||||||
|
|||||||
+2
-215
@@ -1,108 +1,14 @@
|
|||||||
import { asset, I_HAT, J_HAT } from '@blitzkit/core';
|
|
||||||
import { useFrame, useLoader } from '@react-three/fiber';
|
import { useFrame, useLoader } from '@react-three/fiber';
|
||||||
import { clamp } from 'lodash-es';
|
import { clamp } from 'lodash-es';
|
||||||
import { useRef } from 'react';
|
import { useRef } from 'react';
|
||||||
import {
|
import { MeshStandardMaterial, TextureLoader } from 'three';
|
||||||
Group,
|
|
||||||
MeshStandardMaterial,
|
|
||||||
PerspectiveCamera,
|
|
||||||
TextureLoader,
|
|
||||||
Vector2,
|
|
||||||
Vector3,
|
|
||||||
} from 'three';
|
|
||||||
import { GLTFLoader } from 'three/examples/jsm/Addons.js';
|
|
||||||
import { degToRad, radToDeg } from 'three/src/math/MathUtils.js';
|
|
||||||
import { awaitableEquipmentDefinitions } from '../../../../../../../core/awaitables/equipmentDefinitions';
|
|
||||||
import { awaitableModelDefinitions } from '../../../../../../../core/awaitables/modelDefinitions';
|
|
||||||
import { awaitableProvisionDefinitions } from '../../../../../../../core/awaitables/provisionDefinitions';
|
|
||||||
import { applyPitchYawLimits } from '../../../../../../../core/blitz/applyPitchYawLimits';
|
|
||||||
import { modelTransformEvent } from '../../../../../../../core/blitzkit/modelTransform';
|
|
||||||
import { tankCharacteristics } from '../../../../../../../core/blitzkit/tankCharacteristics';
|
|
||||||
import { Duel } from '../../../../../../../stores/duel';
|
|
||||||
import { TankopediaEphemeral } from '../../../../../../../stores/tankopediaEphemeral';
|
import { TankopediaEphemeral } from '../../../../../../../stores/tankopediaEphemeral';
|
||||||
import { aimTarget } from './constants';
|
|
||||||
|
|
||||||
const [modelDefinitions, equipmentDefinitions, provisionDefinitions] =
|
|
||||||
await Promise.all([
|
|
||||||
awaitableModelDefinitions,
|
|
||||||
awaitableEquipmentDefinitions,
|
|
||||||
awaitableProvisionDefinitions,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const emptyVector = new Vector2();
|
|
||||||
|
|
||||||
export function SceneProps() {
|
export function SceneProps() {
|
||||||
const disturbed = TankopediaEphemeral.use((state) => state.disturbed);
|
const disturbed = TankopediaEphemeral.use((state) => state.disturbed);
|
||||||
const targetCircleWrapper = useRef<Group>(null);
|
|
||||||
const clientTargetCircle = useRef<HTMLDivElement>(null);
|
|
||||||
const serverTargetCircle = useRef<HTMLDivElement>(null);
|
|
||||||
const material = useRef<MeshStandardMaterial>(null);
|
const material = useRef<MeshStandardMaterial>(null);
|
||||||
const playground = useRef<Group>(null);
|
|
||||||
|
|
||||||
const turret = Duel.use((state) => state.protagonist.turret);
|
|
||||||
const track = Duel.use((state) => state.protagonist.track);
|
|
||||||
const tank = Duel.use((state) => state.protagonist.tank);
|
|
||||||
const gun = Duel.use((state) => state.protagonist.gun);
|
|
||||||
const shell = Duel.use((state) => state.protagonist.shell);
|
|
||||||
const engine = Duel.use((state) => state.protagonist.engine);
|
|
||||||
const tankModelDefinition = modelDefinitions.models[tank.id];
|
|
||||||
const trackModelDefinition =
|
|
||||||
modelDefinitions.models[tank.id].tracks[track.id];
|
|
||||||
const gunModelDefinition =
|
|
||||||
tankModelDefinition.turrets[turret.id].guns[gun.id];
|
|
||||||
const turretModelDefinition = tankModelDefinition.turrets[turret.id];
|
|
||||||
const hullOrigin = new Vector3(
|
|
||||||
trackModelDefinition.origin.x,
|
|
||||||
trackModelDefinition.origin.y,
|
|
||||||
-trackModelDefinition.origin.z,
|
|
||||||
);
|
|
||||||
const turretOrigin = new Vector3(
|
|
||||||
tankModelDefinition.turret_origin.x,
|
|
||||||
tankModelDefinition.turret_origin.y,
|
|
||||||
-tankModelDefinition.turret_origin.z,
|
|
||||||
);
|
|
||||||
const gunOriginOnlyY = new Vector3(0, turretModelDefinition.gun_origin.y, 0);
|
|
||||||
const shellOrigin = hullOrigin.clone().add(turretOrigin).add(gunOriginOnlyY);
|
|
||||||
const camouflage = Duel.use((state) => state.protagonist.camouflage);
|
|
||||||
const crewSkills = TankopediaEphemeral.use((state) => state.skills);
|
|
||||||
const consumables = Duel.use((state) => state.protagonist.consumables);
|
|
||||||
const provisions = Duel.use((state) => state.protagonist.provisions);
|
|
||||||
const assaultDistance = Duel.use(
|
|
||||||
(state) => state.protagonist.assaultDistance,
|
|
||||||
);
|
|
||||||
const equipmentMatrix = Duel.use(
|
|
||||||
(state) => state.protagonist.equipmentMatrix,
|
|
||||||
);
|
|
||||||
const characteristics = tankCharacteristics(
|
|
||||||
{
|
|
||||||
tank,
|
|
||||||
turret,
|
|
||||||
gun,
|
|
||||||
shell,
|
|
||||||
consumables,
|
|
||||||
applyDynamicArmor: false,
|
|
||||||
applyReactiveArmor: false,
|
|
||||||
applySpallLiner: false,
|
|
||||||
camouflage,
|
|
||||||
crewSkills,
|
|
||||||
engine,
|
|
||||||
equipmentMatrix,
|
|
||||||
provisions,
|
|
||||||
stockEngine: tank.engines[0],
|
|
||||||
stockGun: tank.turrets[0].guns[0],
|
|
||||||
stockTurret: tank.turrets[0],
|
|
||||||
stockTrack: tank.tracks[0],
|
|
||||||
track,
|
|
||||||
assaultDistance,
|
|
||||||
},
|
|
||||||
{ equipmentDefinitions, provisionDefinitions, tankModelDefinition },
|
|
||||||
);
|
|
||||||
let { dispersion } = characteristics;
|
|
||||||
const gunRotationSpeed = degToRad(characteristics.gunTraverseSpeed);
|
|
||||||
const mockTank = useLoader(GLTFLoader, asset(`3d/tanks/models/6929.glb`));
|
|
||||||
const texture = useLoader(TextureLoader, '/assets/images/3d/grid.png');
|
const texture = useLoader(TextureLoader, '/assets/images/3d/grid.png');
|
||||||
const path = new Vector3();
|
|
||||||
const direction = new Vector3();
|
|
||||||
texture.anisotropy = 2;
|
texture.anisotropy = 2;
|
||||||
|
|
||||||
useFrame(({ camera }) => {
|
useFrame(({ camera }) => {
|
||||||
@@ -110,129 +16,10 @@ export function SceneProps() {
|
|||||||
material.current.opacity = clamp(0.5 * camera.position.y, 0, 1);
|
material.current.opacity = clamp(0.5 * camera.position.y, 0, 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
let lastTime = 0;
|
|
||||||
let DEBUG_disable = false;
|
|
||||||
|
|
||||||
useFrame(({ raycaster, camera, clock, gl }) => {
|
|
||||||
if (
|
|
||||||
!playground.current ||
|
|
||||||
!targetCircleWrapper.current ||
|
|
||||||
DEBUG_disable ||
|
|
||||||
!clientTargetCircle.current ||
|
|
||||||
!serverTargetCircle.current
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
raycaster.setFromCamera(emptyVector, camera);
|
|
||||||
|
|
||||||
const cameraIntersections = raycaster.intersectObjects(
|
|
||||||
playground.current.children,
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
|
|
||||||
let length: number;
|
|
||||||
|
|
||||||
if (cameraIntersections.length === 0) {
|
|
||||||
length = characteristics.shellRange;
|
|
||||||
direction.set(0, 0, -1).applyQuaternion(camera.quaternion);
|
|
||||||
aimTarget.copy(shellOrigin).addScaledVector(direction, length);
|
|
||||||
} else {
|
|
||||||
aimTarget.copy(cameraIntersections[0].point);
|
|
||||||
path.copy(aimTarget).sub(shellOrigin);
|
|
||||||
length = path.length();
|
|
||||||
direction.copy(path).normalize();
|
|
||||||
}
|
|
||||||
|
|
||||||
const deltaT = clock.elapsedTime - lastTime;
|
|
||||||
lastTime = clock.elapsedTime;
|
|
||||||
|
|
||||||
const desiredPitch = Math.asin(direction.y);
|
|
||||||
const desiredYaw = Math.atan2(-direction.x, -direction.z);
|
|
||||||
const deltaPitch = desiredPitch - modelTransformEvent.last!.pitch;
|
|
||||||
const deltaYaw = desiredYaw - modelTransformEvent.last!.yaw;
|
|
||||||
const deltaAngle = Math.sqrt(deltaPitch ** 2 + deltaYaw ** 2);
|
|
||||||
const maxPossibleRotationInTime = gunRotationSpeed * deltaT;
|
|
||||||
const clampedDeltaAngle = Math.min(maxPossibleRotationInTime, deltaAngle);
|
|
||||||
const clampedDeltaPitch =
|
|
||||||
deltaAngle === 0 ? 0 : deltaPitch * (clampedDeltaAngle / deltaAngle);
|
|
||||||
const clampedDeltaYaw =
|
|
||||||
deltaAngle === 0 ? 0 : deltaYaw * (clampedDeltaAngle / deltaAngle);
|
|
||||||
|
|
||||||
const [pitch, yaw] = applyPitchYawLimits(
|
|
||||||
modelTransformEvent.last!.pitch + clampedDeltaPitch,
|
|
||||||
modelTransformEvent.last!.yaw + clampedDeltaYaw,
|
|
||||||
gunModelDefinition.pitch,
|
|
||||||
turretModelDefinition.yaw,
|
|
||||||
);
|
|
||||||
|
|
||||||
const actualDeltaPitch = pitch - modelTransformEvent.last!.pitch;
|
|
||||||
const actualDeltaYaw = yaw - modelTransformEvent.last!.yaw;
|
|
||||||
const deltaRotation = Math.abs(actualDeltaPitch) + Math.abs(actualDeltaYaw);
|
|
||||||
const rotationSpeed = deltaRotation / deltaT;
|
|
||||||
const turretTraversePenalty =
|
|
||||||
characteristics.dispersionTurretTraversing * radToDeg(rotationSpeed);
|
|
||||||
|
|
||||||
const penalty = Math.sqrt(1 + turretTraversePenalty ** 2);
|
|
||||||
|
|
||||||
if (penalty === 1) {
|
|
||||||
const dispersionMod = dispersion - characteristics.dispersion;
|
|
||||||
const dispersionModDischarged =
|
|
||||||
dispersionMod * Math.exp(-deltaT / characteristics.aimTime) -
|
|
||||||
dispersionMod;
|
|
||||||
|
|
||||||
dispersion += dispersionModDischarged;
|
|
||||||
} else {
|
|
||||||
dispersion = Math.max(characteristics.dispersion * penalty, dispersion);
|
|
||||||
}
|
|
||||||
|
|
||||||
modelTransformEvent.dispatch({ pitch, yaw });
|
|
||||||
|
|
||||||
const gunDirection = new Vector3(0, 0, -1)
|
|
||||||
.applyAxisAngle(I_HAT, pitch)
|
|
||||||
.applyAxisAngle(J_HAT, yaw);
|
|
||||||
|
|
||||||
raycaster.set(shellOrigin, gunDirection);
|
|
||||||
|
|
||||||
const gunIntersections = raycaster.intersectObjects(
|
|
||||||
playground.current.children,
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
|
|
||||||
let gunTarget: Vector3;
|
|
||||||
|
|
||||||
if (gunIntersections.length === 0) {
|
|
||||||
gunTarget = shellOrigin
|
|
||||||
.clone()
|
|
||||||
.add(gunDirection.multiplyScalar(characteristics.shellRange));
|
|
||||||
} else {
|
|
||||||
gunTarget = gunIntersections[0].point;
|
|
||||||
}
|
|
||||||
|
|
||||||
targetCircleWrapper.current.position.copy(gunTarget);
|
|
||||||
|
|
||||||
const distanceToTarget = gunTarget.distanceTo(shellOrigin);
|
|
||||||
const dispersionRadius = dispersion * (distanceToTarget / 100);
|
|
||||||
const dispersionSize = 2 * dispersionRadius;
|
|
||||||
const targetDistanceFromCamera = gunTarget.distanceTo(camera.position);
|
|
||||||
const angularSize =
|
|
||||||
2 * Math.atan(dispersionSize / (2 * targetDistanceFromCamera));
|
|
||||||
const verticalScreenSize =
|
|
||||||
2 * Math.tan(degToRad((camera as PerspectiveCamera).fov) / 2);
|
|
||||||
const screenSpaceSize =
|
|
||||||
gl.domElement.clientHeight * (angularSize / verticalScreenSize);
|
|
||||||
const targetCircleDotsSize = `${screenSpaceSize}px`;
|
|
||||||
|
|
||||||
clientTargetCircle.current.style.width = targetCircleDotsSize;
|
|
||||||
clientTargetCircle.current.style.height = targetCircleDotsSize;
|
|
||||||
serverTargetCircle.current.style.width = targetCircleDotsSize;
|
|
||||||
serverTargetCircle.current.style.height = targetCircleDotsSize;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!disturbed) return null;
|
if (!disturbed) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<mesh rotation={[-Math.PI / 2, 0, 0]}>
|
<mesh rotation={[-Math.PI / 2, 0, 0]} receiveShadow>
|
||||||
<planeGeometry args={[10, 10]} />
|
<planeGeometry args={[10, 10]} />
|
||||||
<meshStandardMaterial ref={material} map={texture} transparent />
|
<meshStandardMaterial ref={material} map={texture} transparent />
|
||||||
</mesh>
|
</mesh>
|
||||||
|
|||||||
+3
@@ -105,6 +105,7 @@ export function TankModel() {
|
|||||||
key={key}
|
key={key}
|
||||||
onPointerDown={onPointerDown}
|
onPointerDown={onPointerDown}
|
||||||
castShadow
|
castShadow
|
||||||
|
receiveShadow
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -184,6 +185,7 @@ export function TankModel() {
|
|||||||
key={key}
|
key={key}
|
||||||
onPointerDown={onPointerDown}
|
onPointerDown={onPointerDown}
|
||||||
castShadow
|
castShadow
|
||||||
|
receiveShadow
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -269,6 +271,7 @@ export function TankModel() {
|
|||||||
key={key}
|
key={key}
|
||||||
onPointerDown={onPointerDown}
|
onPointerDown={onPointerDown}
|
||||||
castShadow
|
castShadow
|
||||||
|
receiveShadow
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
+1
-2
@@ -55,7 +55,6 @@ export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
|
|||||||
const hasDownImprovedVerticalStabilizer = useEquipment(124);
|
const hasDownImprovedVerticalStabilizer = useEquipment(124);
|
||||||
const protagonistGun = Duel.use((state) => state.protagonist.gun);
|
const protagonistGun = Duel.use((state) => state.protagonist.gun);
|
||||||
const protagonistTurret = Duel.use((state) => state.protagonist.turret);
|
const protagonistTurret = Duel.use((state) => state.protagonist.turret);
|
||||||
const mutateDuel = Duel.useMutation();
|
|
||||||
const tankModelDefinition = useTankModelDefinition();
|
const tankModelDefinition = useTankModelDefinition();
|
||||||
const turretModelDefinition =
|
const turretModelDefinition =
|
||||||
tankModelDefinition.turrets[protagonistTurret.id];
|
tankModelDefinition.turrets[protagonistTurret.id];
|
||||||
@@ -199,7 +198,7 @@ export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
|
|||||||
localClippingEnabled: true,
|
localClippingEnabled: true,
|
||||||
preserveDrawingBuffer: true,
|
preserveDrawingBuffer: true,
|
||||||
}}
|
}}
|
||||||
shadows
|
shadows="soft"
|
||||||
onPointerDown={handlePointerDown}
|
onPointerDown={handlePointerDown}
|
||||||
onPointerMissed={() => {
|
onPointerMissed={() => {
|
||||||
mutateTankopediaEphemeral((draft) => {
|
mutateTankopediaEphemeral((draft) => {
|
||||||
|
|||||||
Reference in new issue
Block a user