mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-24 03:59:50 +02:00
feat: new lighting
This commit is contained in:
1 parent
2559782a28
commit
b67fd5b58c
5 files changed
+39
-153
No files matched your search
@@ -1,3 +1,5 @@
|
|||||||
|
import { Box, Text } from "@radix-ui/themes";
|
||||||
|
import { Html } from "@react-three/drei";
|
||||||
import { memo, type ComponentProps } from "react";
|
import { memo, type ComponentProps } from "react";
|
||||||
|
|
||||||
interface Props extends ComponentProps<"spotLight"> {
|
interface Props extends ComponentProps<"spotLight"> {
|
||||||
@@ -8,10 +10,20 @@ export const HelpingSpotLight = memo<Props>(({ debug, ...props }) => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{debug && (
|
{debug && (
|
||||||
<mesh position={props.position}>
|
<group position={props.position}>
|
||||||
|
<mesh>
|
||||||
<icosahedronGeometry args={[0.1, 2]} />
|
<icosahedronGeometry args={[0.1, 2]} />
|
||||||
<meshBasicMaterial color={props.color} />
|
<meshBasicMaterial color={props.color} />
|
||||||
</mesh>
|
</mesh>
|
||||||
|
|
||||||
|
<Html center>
|
||||||
|
<Box pb="9">
|
||||||
|
<Text wrap="nowrap">
|
||||||
|
{props.intensity} @ {JSON.stringify(props.userData)}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
</Html>
|
||||||
|
</group>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<spotLight {...props} />
|
<spotLight {...props} />
|
||||||
|
|||||||
-40
@@ -1,40 +0,0 @@
|
|||||||
import { invalidate, useThree } from "@react-three/fiber";
|
|
||||||
import { useEffect } from "react";
|
|
||||||
import type { Fog } from "three";
|
|
||||||
import { fogAnimationTime, fogFar0, fogFar1, forNear0, forNear1 } from "..";
|
|
||||||
import { Tankopedia } from "../../../../../../stores/tankopedia";
|
|
||||||
|
|
||||||
export function InitialFogReveal() {
|
|
||||||
const fog = useThree((state) => state.scene.fog as Fog | null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
Tankopedia.mutate((draft) => {
|
|
||||||
draft.revealed = true;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!fog) return;
|
|
||||||
|
|
||||||
const t0 = Date.now();
|
|
||||||
|
|
||||||
const interval = setInterval(() => {
|
|
||||||
const t = (Date.now() - t0) / 1000;
|
|
||||||
const x = t / fogAnimationTime;
|
|
||||||
const near = forNear1 + (forNear0 - forNear1) * x;
|
|
||||||
const far = fogFar1 + (fogFar0 - fogFar1) * x;
|
|
||||||
|
|
||||||
fog.near = near;
|
|
||||||
fog.far = far;
|
|
||||||
invalidate();
|
|
||||||
|
|
||||||
if (x >= 1) {
|
|
||||||
fog.near = forNear0;
|
|
||||||
fog.far = fogFar0;
|
|
||||||
clearInterval(interval);
|
|
||||||
}
|
|
||||||
}, 1000 / 60);
|
|
||||||
|
|
||||||
return () => clearInterval(interval);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
+22
-70
@@ -1,82 +1,34 @@
|
|||||||
import { useHelper } from "@react-three/drei";
|
|
||||||
import { useFrame } from "@react-three/fiber";
|
|
||||||
import { times } from "lodash-es";
|
|
||||||
import { useRef, useState } from "react";
|
|
||||||
import { Color, SpotLight, SpotLightHelper } from "three";
|
|
||||||
import { degToRad } from "three/src/math/MathUtils.js";
|
|
||||||
import { isHalloween } from "../../../../../../core/blitzkit/isHalloween";
|
|
||||||
import { HelpingSpotLight } from "../../../../../HelpingSpotLight";
|
import { HelpingSpotLight } from "../../../../../HelpingSpotLight";
|
||||||
|
|
||||||
const LIGHTS_COUNT = 5;
|
|
||||||
const LIGHT_DISTANCE = 13;
|
|
||||||
const LIGHT_HEIGHT_0 = 4;
|
|
||||||
const LIGHT_HEIGHT_1 = 2;
|
|
||||||
const INTENSITY = 50;
|
|
||||||
|
|
||||||
const N = 128;
|
|
||||||
const TARGET_FRAME_RATE = 30;
|
|
||||||
|
|
||||||
export function Lighting() {
|
export function Lighting() {
|
||||||
const [cast, setCast] = useState(true);
|
|
||||||
|
|
||||||
const t0 = useRef(0);
|
|
||||||
const topLight = useRef<SpotLight>(null!);
|
|
||||||
const frontLight = useRef<SpotLight>(null!);
|
|
||||||
|
|
||||||
useHelper(topLight, SpotLightHelper, "red");
|
|
||||||
useHelper(frontLight, SpotLightHelper, "green");
|
|
||||||
|
|
||||||
const last = useRef<number>(0);
|
|
||||||
const samples = useRef<number[]>([]);
|
|
||||||
|
|
||||||
useFrame(({ clock, frameloop }) => {
|
|
||||||
if (frameloop === "never") {
|
|
||||||
t0.current = clock.elapsedTime;
|
|
||||||
samples.current = [];
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!cast || clock.elapsedTime - t0.current < TARGET_FRAME_RATE * N) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const dt = clock.elapsedTime - last.current;
|
|
||||||
last.current = clock.elapsedTime;
|
|
||||||
|
|
||||||
const fps = 1 / dt;
|
|
||||||
samples.current.push(fps);
|
|
||||||
|
|
||||||
if (samples.current.length > N) samples.current.shift();
|
|
||||||
|
|
||||||
const avg =
|
|
||||||
samples.current.reduce((a, b) => a + b, 0) / samples.current.length;
|
|
||||||
|
|
||||||
if (avg < TARGET_FRAME_RATE) setCast(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{times(LIGHTS_COUNT, (index) => {
|
<hemisphereLight
|
||||||
const x = index / (LIGHTS_COUNT - 1);
|
position={[0, 1, 0]}
|
||||||
const theta = 2 * Math.PI * (index / LIGHTS_COUNT) - degToRad(152);
|
color="#dadaff"
|
||||||
const position = [
|
groundColor="#856331"
|
||||||
LIGHT_DISTANCE * Math.sin(theta),
|
intensity={4}
|
||||||
LIGHT_HEIGHT_0 * (1 - x) + LIGHT_HEIGHT_1 * x,
|
/>
|
||||||
LIGHT_DISTANCE * Math.cos(theta),
|
|
||||||
] as const;
|
|
||||||
const color = new Color();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<HelpingSpotLight
|
<HelpingSpotLight
|
||||||
key={index}
|
debug
|
||||||
position={position}
|
position={[-6, 6, -10]}
|
||||||
intensity={INTENSITY}
|
intensity={4}
|
||||||
penumbra={1}
|
penumbra={1}
|
||||||
castShadow={cast}
|
castShadow
|
||||||
decay={1}
|
color="#ffffff"
|
||||||
color={color}
|
decay={0}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<HelpingSpotLight
|
||||||
|
debug
|
||||||
|
position={[6, 6, -10]}
|
||||||
|
intensity={1}
|
||||||
|
penumbra={1}
|
||||||
|
castShadow
|
||||||
|
color="#ffffff"
|
||||||
|
decay={0}
|
||||||
/>
|
/>
|
||||||
);
|
|
||||||
})}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
+3
-3
@@ -4,7 +4,7 @@ import { Quicklime, type QuicklimeEvent } from "quicklime";
|
|||||||
import { useEffect, useRef } from "react";
|
import { useEffect, useRef } from "react";
|
||||||
import { Mesh, MeshStandardMaterial } from "three";
|
import { Mesh, MeshStandardMaterial } from "three";
|
||||||
|
|
||||||
const SIZE = 2 ** 5;
|
const SIZE = 2 ** 6;
|
||||||
|
|
||||||
export const screenshotReadyEvent = new Quicklime(false);
|
export const screenshotReadyEvent = new Quicklime(false);
|
||||||
|
|
||||||
@@ -14,7 +14,7 @@ export function SceneProps() {
|
|||||||
|
|
||||||
useFrame(({ camera }) => {
|
useFrame(({ camera }) => {
|
||||||
if (!material.current) return;
|
if (!material.current) return;
|
||||||
material.current.opacity = clamp(0.5 * camera.position.y, 0, 0.25);
|
material.current.opacity = clamp(0.5 * camera.position.y, 0, 0.5);
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -44,7 +44,7 @@ export function SceneProps() {
|
|||||||
ref={material}
|
ref={material}
|
||||||
color="black"
|
color="black"
|
||||||
roughness={1}
|
roughness={1}
|
||||||
metalness={1}
|
metalness={0}
|
||||||
/>
|
/>
|
||||||
</mesh>
|
</mesh>
|
||||||
);
|
);
|
||||||
|
|||||||
+1
-39
@@ -1,5 +1,4 @@
|
|||||||
import { BLITZKIT_TANK_ICON_SIZE } from "@blitzkit/core";
|
import { BLITZKIT_TANK_ICON_SIZE } from "@blitzkit/core";
|
||||||
import { invalidate } from "@react-three/fiber";
|
|
||||||
import {
|
import {
|
||||||
forwardRef,
|
forwardRef,
|
||||||
Suspense,
|
Suspense,
|
||||||
@@ -29,7 +28,6 @@ import { SmartCanvas } from "../../../../SmartCanvas";
|
|||||||
import { AutoClear } from "./components/AutoClear";
|
import { AutoClear } from "./components/AutoClear";
|
||||||
import { Controls } from "./components/Control";
|
import { Controls } from "./components/Control";
|
||||||
import { InitialAligner } from "./components/InitialAligner";
|
import { InitialAligner } from "./components/InitialAligner";
|
||||||
import { InitialFogReveal } from "./components/InitialFogReveal";
|
|
||||||
import { Lighting } from "./components/Lighting";
|
import { Lighting } from "./components/Lighting";
|
||||||
import { SceneProps } from "./components/SceneProps";
|
import { SceneProps } from "./components/SceneProps";
|
||||||
import { TankModel } from "./components/TankModel";
|
import { TankModel } from "./components/TankModel";
|
||||||
@@ -40,15 +38,8 @@ interface TankSandboxProps {
|
|||||||
naked?: boolean;
|
naked?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const forNear0 = 30;
|
|
||||||
export const fogFar0 = 50;
|
|
||||||
export const forNear1 = 0;
|
|
||||||
export const fogFar1 = 0;
|
|
||||||
export const fogAnimationTime = 2.5;
|
|
||||||
|
|
||||||
export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
|
export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
|
||||||
({ thicknessRange, naked }, ref) => {
|
({ thicknessRange, naked }, ref) => {
|
||||||
const fog = useRef(new Fog("black", forNear1, fogFar1));
|
|
||||||
const canvas = useRef<HTMLCanvasElement>(null);
|
const canvas = useRef<HTMLCanvasElement>(null);
|
||||||
const hasImprovedVerticalStabilizer = useEquipment(122);
|
const hasImprovedVerticalStabilizer = useEquipment(122);
|
||||||
const hasDownImprovedVerticalStabilizer = useEquipment(124);
|
const hasDownImprovedVerticalStabilizer = useEquipment(124);
|
||||||
@@ -64,34 +55,6 @@ export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
|
|||||||
(state) => state.hideTankModelUnderArmor
|
(state) => state.hideTankModelUnderArmor
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (rawDisplay === display) return;
|
|
||||||
|
|
||||||
const t0 = Date.now();
|
|
||||||
const interval = setInterval(() => {
|
|
||||||
const t = (Date.now() - t0) / 1000;
|
|
||||||
const x = t / fogAnimationTime;
|
|
||||||
const y = Math.sin(Math.PI * x);
|
|
||||||
|
|
||||||
const near = forNear0 + (forNear1 - forNear0) * y;
|
|
||||||
const far = fogFar0 + (fogFar1 - fogFar0) * y;
|
|
||||||
|
|
||||||
fog.current.near = near;
|
|
||||||
fog.current.far = far;
|
|
||||||
|
|
||||||
invalidate();
|
|
||||||
|
|
||||||
if (rawDisplay !== display && x >= 0.5) setDisplay(rawDisplay);
|
|
||||||
if (x >= 1) {
|
|
||||||
fog.current.near = forNear0;
|
|
||||||
fog.current.far = fogFar0;
|
|
||||||
clearInterval(interval);
|
|
||||||
}
|
|
||||||
}, 1000 / 60);
|
|
||||||
|
|
||||||
return () => clearInterval(interval);
|
|
||||||
}, [rawDisplay]);
|
|
||||||
|
|
||||||
useImperativeHandle(ref, () => canvas.current!, []);
|
useImperativeHandle(ref, () => canvas.current!, []);
|
||||||
|
|
||||||
function handlePointerDown() {
|
function handlePointerDown() {
|
||||||
@@ -191,7 +154,7 @@ export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
|
|||||||
return (
|
return (
|
||||||
<SmartCanvas
|
<SmartCanvas
|
||||||
ref={canvas}
|
ref={canvas}
|
||||||
scene={{ fog: naked ? undefined : fog.current }}
|
scene={{ fog: naked ? undefined : new Fog("black", 30, 50) }}
|
||||||
gl={{
|
gl={{
|
||||||
clippingPlanes: [],
|
clippingPlanes: [],
|
||||||
localClippingEnabled: true,
|
localClippingEnabled: true,
|
||||||
@@ -224,7 +187,6 @@ export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
|
|||||||
<Suspense>
|
<Suspense>
|
||||||
{/* Controls within Suspense to allow for frame-perfect start of camera auto-rotate */}
|
{/* Controls within Suspense to allow for frame-perfect start of camera auto-rotate */}
|
||||||
<Controls naked={naked} />
|
<Controls naked={naked} />
|
||||||
<InitialFogReveal />
|
|
||||||
|
|
||||||
{display === TankopediaDisplay.DynamicArmor && <Armor />}
|
{display === TankopediaDisplay.DynamicArmor && <Armor />}
|
||||||
{display === TankopediaDisplay.StaticArmor && (
|
{display === TankopediaDisplay.StaticArmor && (
|
||||||
|
|||||||
Reference in new issue
Block a user