refactor: nettoie l'architecture monde et les docs

This commit is contained in:
tom-boullay
2026-05-28 15:47:16 +02:00
parent 1a91b1d7ae
commit ba50224e6e
45 changed files with 89 additions and 726 deletions
-66
View File
@@ -1,66 +0,0 @@
import * as THREE from "three";
type TextureMaterialKey = Extract<
| keyof THREE.MeshBasicMaterial
| keyof THREE.MeshStandardMaterial
| keyof THREE.MeshPhysicalMaterial
| keyof THREE.MeshToonMaterial,
string
>;
type MaterialWithTextureSlots = THREE.Material &
Partial<Record<TextureMaterialKey, THREE.Texture | null>>;
const MATERIAL_TEXTURE_KEYS = [
"alphaMap",
"aoMap",
"bumpMap",
"clearcoatMap",
"clearcoatNormalMap",
"clearcoatRoughnessMap",
"displacementMap",
"emissiveMap",
"envMap",
"gradientMap",
"lightMap",
"map",
"metalnessMap",
"normalMap",
"roughnessMap",
"sheenColorMap",
"sheenRoughnessMap",
"specularColorMap",
"specularIntensityMap",
"specularMap",
"thicknessMap",
"transmissionMap",
] as const satisfies readonly TextureMaterialKey[];
export function disposeObject3D(object: THREE.Object3D): void {
object.traverse((child) => {
if (child instanceof THREE.Mesh) {
child.geometry?.dispose();
if (Array.isArray(child.material)) {
for (const material of child.material) {
disposeMaterial(material);
}
} else if (child.material) {
disposeMaterial(child.material);
}
}
});
}
function disposeMaterial(material: THREE.Material): void {
material.dispose();
const materialWithTextures = material as MaterialWithTextureSlots;
for (const key of MATERIAL_TEXTURE_KEYS) {
const value = materialWithTextures[key];
if (value instanceof THREE.Texture) {
value.dispose();
}
}
}
-51
View File
@@ -1,51 +0,0 @@
import {
TERRAIN_COLORS,
TERRAIN_SURFACE_COLOR_TOLERANCE,
type TerrainColorKey,
} from "@/data/world/terrainConfig";
import type { TerrainSurfaceRgb } from "@/types/world/terrainSurface";
export function colorMatchesTerrainSurface(
r: number,
g: number,
b: number,
targetRgb: TerrainSurfaceRgb,
tolerance: number = TERRAIN_SURFACE_COLOR_TOLERANCE,
): boolean {
return (
Math.abs(r - targetRgb[0]) <= tolerance &&
Math.abs(g - targetRgb[1]) <= tolerance &&
Math.abs(b - targetRgb[2]) <= tolerance
);
}
export function getTerrainColorKeyFromRgb(
r: number,
g: number,
b: number,
): TerrainColorKey | null {
for (const [key, config] of Object.entries(TERRAIN_COLORS)) {
if (colorMatchesTerrainSurface(r, g, b, config.rgb)) {
return key as TerrainColorKey;
}
}
return null;
}
export function isGrassTerrainColor(r: number, g: number, b: number): boolean {
const key = getTerrainColorKeyFromRgb(r, g, b);
return key !== null && TERRAIN_COLORS[key].kind === "grass";
}
export function getGrassTipColorFromRgb(
r: number,
g: number,
b: number,
): string | null {
const key = getTerrainColorKeyFromRgb(r, g, b);
if (key === null) return null;
const terrainColor = TERRAIN_COLORS[key];
return "grassTipColor" in terrainColor ? terrainColor.grassTipColor : null;
}
-125
View File
@@ -1,125 +0,0 @@
import * as THREE from "three";
import { TERRAIN_COLORS } from "@/data/world/terrainConfig";
import type {
TerrainSurfaceBounds,
TerrainSurfaceProjectionConfig,
TerrainSurfaceRgb,
TerrainSurfaceSample,
TerrainSurfaceUv,
} from "@/types/world/terrainSurface";
import { getTerrainColorKeyFromRgb } from "@/utils/world/terrainSurfaceColor";
type TerrainSurfaceImageSource =
| HTMLImageElement
| HTMLCanvasElement
| ImageBitmap;
const imageDataCache = new WeakMap<TerrainSurfaceImageSource, ImageData>();
function clamp01(value: number): number {
return Math.min(Math.max(value, 0), 1);
}
function wrap01(value: number): number {
return ((value % 1) + 1) % 1;
}
function isTerrainSurfaceImageSource(
value: unknown,
): value is TerrainSurfaceImageSource {
return (
value instanceof HTMLImageElement ||
value instanceof HTMLCanvasElement ||
(typeof ImageBitmap !== "undefined" && value instanceof ImageBitmap)
);
}
export function createTerrainSurfaceImageData(
texture: THREE.Texture,
): ImageData | null {
if (typeof document === "undefined") return null;
const image = texture.image as unknown;
if (!isTerrainSurfaceImageSource(image)) return null;
const cachedImageData = imageDataCache.get(image);
if (cachedImageData) return cachedImageData;
const width = image.width;
const height = image.height;
if (width <= 0 || height <= 0) return null;
const canvas = document.createElement("canvas");
const context = canvas.getContext("2d");
if (!context) return null;
canvas.width = width;
canvas.height = height;
context.drawImage(image, 0, 0, width, height);
const imageData = context.getImageData(0, 0, width, height);
imageDataCache.set(image, imageData);
return imageData;
}
export function sampleTerrainSurfaceAtUv(
imageData: ImageData,
uv: TerrainSurfaceUv,
): TerrainSurfaceSample {
const x = Math.round(clamp01(uv.u) * (imageData.width - 1));
const y = Math.round((1 - clamp01(uv.v)) * (imageData.height - 1));
const index = (y * imageData.width + x) * 4;
const rgb: TerrainSurfaceRgb = [
imageData.data[index] ?? 0,
imageData.data[index + 1] ?? 0,
imageData.data[index + 2] ?? 0,
];
const key = getTerrainColorKeyFromRgb(rgb[0], rgb[1], rgb[2]);
return {
rgb,
key,
config: key === null ? null : TERRAIN_COLORS[key],
};
}
export function terrainSurfaceUvFromXZ(
x: number,
z: number,
bounds: TerrainSurfaceBounds,
projection?: TerrainSurfaceProjectionConfig,
): TerrainSurfaceUv {
const width = bounds.maxX - bounds.minX;
const depth = bounds.maxZ - bounds.minZ;
let u = width === 0 ? 0 : x / width + 0.5;
let v = depth === 0 ? 0 : z / depth + 0.5;
if (projection?.flipX) {
u = 1 - u;
}
if (projection?.flipZ) {
v = 1 - v;
}
u = wrap01(u + (projection?.offsetX ?? 0));
v = wrap01(v + (projection?.offsetZ ?? 0));
return {
u,
v,
};
}
export function sampleTerrainSurfaceAtXZ(
imageData: ImageData,
x: number,
z: number,
bounds: TerrainSurfaceBounds,
projection?: TerrainSurfaceProjectionConfig,
): TerrainSurfaceSample {
return sampleTerrainSurfaceAtUv(
imageData,
terrainSurfaceUvFromXZ(x, z, bounds, projection),
);
}