Compare commits

...
10 Commits
Author SHA1 Message Date
tresabhi d485a463dd orange ground 2026-08-11 14:07:57 -05:00
tresabhi 3da1ed4d6c ground and sky color 2026-08-11 11:30:36 -05:00
tresabhi 051e9ec74a ground with shadows 2026-08-11 10:58:35 -05:00
tresabhi 486171f4db shadow focus and res 2026-08-11 10:52:33 -05:00
tresabhi 72de42a9ab performant ground 2026-08-11 10:38:36 -05:00
tresabhi 404e6625c0 remmove lghts 2026-08-11 10:35:41 -05:00
tresabhi 32e4bf3fd2 fading out fragment 2026-08-11 10:16:14 -05:00
tresabhi 0d1088fefb non pbr lihging 2026-08-11 10:01:34 -05:00
tresabhi 1a17d607fb move light target up 2026-08-11 09:56:58 -05:00
tresabhi dcda627518 dvpl free dir 2026-08-11 09:45:17 -05:00
9 changed files with 98 additions and 19 deletions

No files matched your search

+1 -1
View File
@@ -30,7 +30,7 @@
"@octokit/plugin-throttling": "^11.0.1",
"@octokit/rest": "^22.0.0",
"@types/adm-zip": "^0.5.7",
"@types/bun": "^1.3.1",
"@types/bun": "^1.3.14",
"@types/lodash-es": "^4.17.12",
"@types/lz4js": "^0.2.1",
"@types/three": "^0.180.0",
+1 -1
View File
@@ -9,7 +9,7 @@
"@octokit/plugin-throttling": "^11.0.1",
"@octokit/rest": "^22.0.0",
"@types/adm-zip": "^0.5.7",
"@types/bun": "^1.3.1",
"@types/bun": "^1.3.14",
"@types/lodash-es": "^4.17.12",
"@types/lz4js": "^0.2.1",
"@types/three": "^0.180.0",
+10 -2
View File
@@ -46,7 +46,15 @@ export abstract class AbstractVFS {
return buffer;
}
abstract dir(path: string): Promise<string[]>;
protected abstract _dir(path: string): Promise<string[]>;
async dir(path: string) {
const files = await this._dir(path);
return files.map((file) =>
file.endsWith(".dvpl") ? file.slice(0, -5) : file,
);
}
async text(path: string) {
const file = await this.file(path);
@@ -63,7 +71,7 @@ export abstract class AbstractVFS {
return this.xmlParser.parse(file) as Type;
}
async _csv(text: string, options: Options = {}) {
protected async _csv(text: string, options: Options = {}) {
const parsed = parseCSV(text, options);
return parsed;
}
+1 -1
View File
@@ -23,7 +23,7 @@ export class LocalVFS extends AbstractVFS {
return array;
}
async dir(path: string) {
async _dir(path: string) {
try {
return await readdir(`${this.base}/${path}`);
} catch (_) {
+1 -1
View File
@@ -35,7 +35,7 @@ export class MixedVFS extends AbstractVFS {
throw new Error(`File not found: ${path}`);
}
async dir(path: string) {
async _dir(path: string) {
const files = new Set<string>();
for (const vfs of this.vfses) {
+1 -1
View File
@@ -89,7 +89,7 @@ export class SteamVFS extends AbstractVFS {
return new Uint8Array(downloaded.file);
}
async dir(path: string) {
async _dir(path: string) {
const parentSegments = path.split("/").length;
const children = new Set<string>();
+1 -1
View File
@@ -33,7 +33,7 @@ export class ZipVFS extends AbstractVFS {
return new Uint8Array(arrayBuffer);
}
async dir(path: string) {
async _dir(path: string) {
const parentSegments = path.split("/").length;
const children = new Set<string>();
@@ -1,3 +1,4 @@
import { blueDark, orangeDark } from "@radix-ui/colors";
import { ContactShadows } from "@react-three/drei";
import { useFrame } from "@react-three/fiber";
import { clamp, times } from "lodash-es";
@@ -11,8 +12,11 @@ import {
} from "react";
import { HemisphereLight, SpotLight, type Group } from "three";
import { degToRad, lerp } from "three/src/math/MathUtils.js";
import { useModel } from "../../../../../../hooks/useModel";
import { Duel } from "../../../../../../stores/duel";
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;
@@ -20,12 +24,18 @@ const TRANSITION_ANIMATION_TIME = 0.5;
const LIGHTS_COUNT = 3;
const THETA_OFFSET = degToRad(180 - 45);
const LIGHT_DISTANCE = 20;
const LIGHT_HEIGHT_0 = 7;
const LIGHT_HEIGHT_1 = 10;
const INTENSITY_0 = 2 ** 6;
const INTENSITY_1 = 2 ** 3;
const HEMISPHERE_INTENSITY = 2 ** 0.7;
const NON_PBR_FACTOR = 2;
const SHADOW_RESOLUTION = 2 ** 9;
const SHADOW_FOCUS = 2 ** 0;
export const transitionEvent = new Quicklime<number>(0);
@@ -75,7 +85,11 @@ export function Lighting() {
)}
<group ref={wrapper}>
<hemisphereLight intensity={0} color="#d2e9ff" groundColor="#85490c" />
<hemisphereLight
intensity={0}
color={blueDark.blue12}
groundColor={orangeDark.orange8}
/>
{times(LIGHTS_COUNT, (index) => {
const x = index / (LIGHTS_COUNT - 1);
@@ -87,8 +101,13 @@ export function Lighting() {
] as const;
const intensity = lerp(INTENSITY_0, INTENSITY_1, x);
const light = useRef<SpotLight>(null!);
// useHelper(light, SpotLightHelper);
return (
<spotLight
ref={light}
key={index}
position={position}
intensity={intensity}
@@ -97,6 +116,9 @@ export function Lighting() {
decay={1}
color="#ffffff"
angle={0}
target-position={[0, 1.5, 0]}
shadow-mapSize={[SHADOW_RESOLUTION, SHADOW_RESOLUTION]}
shadow-focus={SHADOW_FOCUS}
/>
);
})}
@@ -116,14 +138,21 @@ interface AnimatorProps {
function Animator({ stop, t0, animationTime, wrapper }: AnimatorProps) {
const requestedDisplay = Tankopedia.use((state) => state.requestedDisplay);
const id = Duel.use((state) => state.protagonist.tank.id);
const display = Tankopedia.use((state) => state.display);
const { hasPbr } = useModel(id);
const factor =
!hasPbr && display !== TankopediaDisplay.StaticArmor ? NON_PBR_FACTOR : 1;
const apply = useCallback((t: number) => {
if (wrapper.current === null) return;
for (const child of wrapper.current.children) {
if (child instanceof SpotLight) {
child.angle = ANGLE * t;
child.angle = ANGLE * t * factor;
} else if (child instanceof HemisphereLight) {
child.intensity = HEMISPHERE_INTENSITY * t;
child.intensity = HEMISPHERE_INTENSITY * t * factor;
}
}
@@ -1,15 +1,15 @@
import { mauveDark } from "@radix-ui/colors";
import { invalidate } from "@react-three/fiber";
import { Quicklime, type QuicklimeEvent } from "quicklime";
import { useEffect, useRef } from "react";
import { Mesh, MeshStandardMaterial } from "three";
import { useCallback, useEffect, useRef } from "react";
import { Mesh, type WebGLProgramParametersWithUniforms } from "three";
const SIZE = 2 ** 5;
const SIZE = 2 ** 4;
export const screenshotReadyEvent = new Quicklime(false);
export function SceneProps() {
const mesh = useRef<Mesh>(null!);
const material = useRef<MeshStandardMaterial>(null);
useEffect(() => {
function handleScreenshotReady(event: QuicklimeEvent<boolean>) {
@@ -24,20 +24,62 @@ export function SceneProps() {
};
}, []);
const handleBeforeCompile = useCallback(
(shader: WebGLProgramParametersWithUniforms) => {
shader.uniforms.size = { value: SIZE / 2 };
shader.vertexShader = `
varying vec3 vWorldPosition;
${shader.vertexShader}
`.replace(
"#include <worldpos_vertex>",
`
#include <worldpos_vertex>
vWorldPosition = (modelMatrix * vec4(transformed, 1.0)).xyz;
`,
);
shader.fragmentShader = `
varying vec3 vWorldPosition;
uniform float size;
${shader.fragmentShader}
`.replace(
"#include <color_fragment>",
`
#include <color_fragment>
vec3 scaled = vWorldPosition / size;
float radius = length(scaled.xz);
if (radius >= 1.0) {
discard;
}
diffuseColor.a *= (1.0 - radius);
`,
);
},
[],
);
return (
<mesh
position={[0, -(2 ** -8), 0]}
rotation={[-Math.PI / 2, 0, 0]}
receiveShadow
visible={!screenshotReadyEvent.last!}
ref={mesh}
receiveShadow
>
<planeGeometry args={[SIZE, SIZE]} />
<meshStandardMaterial
ref={material}
color="black"
color={mauveDark.mauve6}
roughness={1}
metalness={0}
transparent
onBeforeCompile={handleBeforeCompile}
/>
</mesh>
);