feat: start of new lighting

This commit is contained in:
TrèsAbhi committed 2025-08-28 16:52:23 -05:00
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 { jsxTree } from '../../../core/blitzkit/jsxTree';
import { useModel } from '../../../hooks/useModel';
@@ -32,17 +31,6 @@ export function Model() {
const gunTurretModel = gunTankModel.turrets[gun.turret.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 (
<group rotation={[-Math.PI / 2, 0, 0]}>
{hullNodes.map((node) => {
@@ -55,7 +43,7 @@ export function Model() {
return jsxTree(node, {
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, {
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, {
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 { Suspense } from 'react';
import { Model } from './components/Model';
@@ -6,16 +6,16 @@ import { Model } from './components/Model';
export function MixerScene() {
return (
<Canvas
shadows
camera={{
fov: 65,
fov: 45,
position: [5, 5, -5],
}}
>
<OrbitControls />
<Environment preset="lobby" />
{/* <ambientLight intensity={6} />
<pointLight position={[10, 10, 10]} /> */}
<directionalLight position={[10, 10, 10]} intensity={5} castShadow />
<directionalLight position={[-10, -10, -10]} intensity={3} castShadow />
<Suspense fallback={null}>
<Model />
@@ -1,11 +1,13 @@
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 { useModel } from '../../../../../../hooks/useModel';
import { Duel } from '../../../../../../stores/duel';
import { TankopediaDisplay } from '../../../../../../stores/tankopediaPersistent/constants';
const LIGHTS_COUNT = 5;
const SIZE = 2 ** 11;
interface LightingProps {
display: TankopediaDisplay;
@@ -17,6 +19,29 @@ export function Lighting({ display }: LightingProps) {
const isBrighterLighting =
!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 (
<>
{display !== TankopediaDisplay.StaticArmor && (
@@ -1,108 +1,14 @@
import { asset, I_HAT, J_HAT } from '@blitzkit/core';
import { useFrame, useLoader } from '@react-three/fiber';
import { clamp } from 'lodash-es';
import { useRef } from 'react';
import {
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 { MeshStandardMaterial, TextureLoader } from 'three';
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() {
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 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 path = new Vector3();
const direction = new Vector3();
texture.anisotropy = 2;
useFrame(({ camera }) => {
@@ -110,129 +16,10 @@ export function SceneProps() {
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;
return (
<mesh rotation={[-Math.PI / 2, 0, 0]}>
<mesh rotation={[-Math.PI / 2, 0, 0]} receiveShadow>
<planeGeometry args={[10, 10]} />
<meshStandardMaterial ref={material} map={texture} transparent />
</mesh>
@@ -105,6 +105,7 @@ export function TankModel() {
key={key}
onPointerDown={onPointerDown}
castShadow
receiveShadow
/>
);
},
@@ -184,6 +185,7 @@ export function TankModel() {
key={key}
onPointerDown={onPointerDown}
castShadow
receiveShadow
/>
);
},
@@ -269,6 +271,7 @@ export function TankModel() {
key={key}
onPointerDown={onPointerDown}
castShadow
receiveShadow
/>
);
},
@@ -55,7 +55,6 @@ export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
const hasDownImprovedVerticalStabilizer = useEquipment(124);
const protagonistGun = Duel.use((state) => state.protagonist.gun);
const protagonistTurret = Duel.use((state) => state.protagonist.turret);
const mutateDuel = Duel.useMutation();
const tankModelDefinition = useTankModelDefinition();
const turretModelDefinition =
tankModelDefinition.turrets[protagonistTurret.id];
@@ -199,7 +198,7 @@ export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
localClippingEnabled: true,
preserveDrawingBuffer: true,
}}
shadows
shadows="soft"
onPointerDown={handlePointerDown}
onPointerMissed={() => {
mutateTankopediaEphemeral((draft) => {