chore: add terrain surface config

This commit is contained in:
Tom Boullay
2026-05-25 15:51:02 +02:00
parent a52d57ae6c
commit 1f6d9659ed
6 changed files with 118 additions and 142 deletions
+1 -1
View File
@@ -7,7 +7,7 @@ import tseslint from "typescript-eslint";
import { defineConfig, globalIgnores } from "eslint/config"; import { defineConfig, globalIgnores } from "eslint/config";
export default defineConfig([ export default defineConfig([
globalIgnores(["dist", "POC-grass"]), globalIgnores(["dist"]),
{ {
files: ["**/*.{ts,tsx}"], files: ["**/*.{ts,tsx}"],
extends: [ extends: [
+21 -71
View File
@@ -40201,7 +40201,7 @@
"scale": [1, 1, 1], "scale": [1, 1, 1],
"children": [ "children": [
{ {
"name": "panneauxcentre", "name": "panneauclassique",
"type": "Object3D", "type": "Object3D",
"role": "group", "role": "group",
"position": [0, 0, 0], "position": [0, 0, 0],
@@ -40209,40 +40209,30 @@
"scale": [1, 1, 1], "scale": [1, 1, 1],
"children": [ "children": [
{ {
"name": "panneauxcentre", "name": "panneauclassique",
"type": "Object3D", "type": "Object3D",
"position": [-35.2895, 3.8515, 40.1864], "position": [-35.2895, 3.8515, 40.1864],
"rotation": [-3.0988, 0.9934, -3.1367], "rotation": [-3.0988, 0.9934, -3.1367],
"scale": [1, 1, 1], "scale": [1, 1, 1],
"children": [ "children": [
{ {
"name": "panneauxcentre", "name": "panneauclassique",
"type": "Mesh", "type": "Mesh",
"position": [-35.2895, 3.8515, 40.1864], "position": [-35.2895, 3.8515, 40.1864],
"rotation": [-3.0988, 0.9934, -3.1367], "rotation": [-3.0988, 0.9934, -3.1367],
"scale": [1, 1, 1] "scale": [1, 1, 1]
} }
] ]
} },
]
},
{
"name": "panneauxdomaine",
"type": "Object3D",
"role": "group",
"position": [0, 0, 0],
"rotation": [0, 0, 0],
"scale": [1, 1, 1],
"children": [
{ {
"name": "panneauxdomaine", "name": "panneauclassique",
"type": "Object3D", "type": "Object3D",
"position": [-2.4347, 3.7803, -56.1439], "position": [-2.4347, 3.7803, -56.1439],
"rotation": [0.0474, 0.2148, -0.0027], "rotation": [0.0474, 0.2148, -0.0027],
"scale": [1, 1, 1], "scale": [1, 1, 1],
"children": [ "children": [
{ {
"name": "panneauxdomaine", "name": "panneauclassique",
"type": "Mesh", "type": "Mesh",
"position": [-2.4347, 3.7803, -56.1439], "position": [-2.4347, 3.7803, -56.1439],
"rotation": [0.0474, 0.2148, -0.0027], "rotation": [0.0474, 0.2148, -0.0027],
@@ -40253,7 +40243,7 @@
] ]
}, },
{ {
"name": "panneaudirresidences2", "name": "panneaufleche",
"type": "Object3D", "type": "Object3D",
"role": "group", "role": "group",
"position": [0, 0, 0], "position": [0, 0, 0],
@@ -40261,118 +40251,78 @@
"scale": [1, 1, 1], "scale": [1, 1, 1],
"children": [ "children": [
{ {
"name": "panneaudirresidences2", "name": "panneaufleche",
"type": "Object3D", "type": "Object3D",
"position": [30.1743, 7.1944, -13.5781], "position": [30.1743, 7.1944, -13.5781],
"rotation": [0.0374, -1.297, -0.0099], "rotation": [0.0374, -1.297, -0.0099],
"scale": [1, 1, 1], "scale": [1, 1, 1],
"children": [ "children": [
{ {
"name": "panneaudirresidences2", "name": "panneaufleche",
"type": "Mesh", "type": "Mesh",
"position": [30.1743, 7.1944, -13.5781], "position": [30.1743, 7.1944, -13.5781],
"rotation": [0.0374, -1.297, -0.0099], "rotation": [0.0374, -1.297, -0.0099],
"scale": [1, 1, 1] "scale": [1, 1, 1]
} }
] ]
} },
]
},
{
"name": "panneaudirdomaine",
"type": "Object3D",
"role": "group",
"position": [0, 0, 0],
"rotation": [0, 0, 0],
"scale": [1, 1, 1],
"children": [
{ {
"name": "panneaudirdomaine", "name": "panneaufleche",
"type": "Object3D", "type": "Object3D",
"position": [8.1032, 7.1918, -23.0004], "position": [8.1032, 7.1918, -23.0004],
"rotation": [0.0462, -0.2443, -0.0027], "rotation": [0.0462, -0.2443, -0.0027],
"scale": [1, 1, 1], "scale": [1, 1, 1],
"children": [ "children": [
{ {
"name": "panneaudirdomaine", "name": "panneaufleche",
"type": "Mesh", "type": "Mesh",
"position": [8.1032, 7.1918, -23.0004], "position": [8.1032, 7.1918, -23.0004],
"rotation": [0.0462, -0.2443, -0.0027], "rotation": [0.0462, -0.2443, -0.0027],
"scale": [1, 1, 1] "scale": [1, 1, 1]
} }
] ]
} },
]
},
{
"name": "panneaudirresidences1",
"type": "Object3D",
"role": "group",
"position": [0, 0, 0],
"rotation": [0, 0, 0],
"scale": [1, 1, 1],
"children": [
{ {
"name": "panneaudirresidences1", "name": "panneaufleche",
"type": "Object3D", "type": "Object3D",
"position": [-10.1125, 7.2582, -11.4511], "position": [-10.1125, 7.2582, -11.4511],
"rotation": [0.0478, 0.3436, -0.0028], "rotation": [0.0478, 0.3436, -0.0028],
"scale": [1, 1, 1], "scale": [1, 1, 1],
"children": [ "children": [
{ {
"name": "panneaudirresidences1", "name": "panneaufleche",
"type": "Mesh", "type": "Mesh",
"position": [-10.1125, 7.2582, -11.4511], "position": [-10.1125, 7.2582, -11.4511],
"rotation": [0.0478, 0.3436, -0.0028], "rotation": [0.0478, 0.3436, -0.0028],
"scale": [1, 1, 1] "scale": [1, 1, 1]
} }
] ]
} },
]
},
{
"name": "panneaudircentre",
"type": "Object3D",
"role": "group",
"position": [0, 0, 0],
"rotation": [0, 0, 0],
"scale": [1, 1, 1],
"children": [
{ {
"name": "panneaudircentre", "name": "panneaufleche",
"type": "Object3D", "type": "Object3D",
"position": [-10.5207, 7.0874, 19.3819], "position": [-10.5207, 7.0874, 19.3819],
"rotation": [-3.1416, 1.5139, -3.0947], "rotation": [-3.1416, 1.5139, -3.0947],
"scale": [1, 1, 1], "scale": [1, 1, 1],
"children": [ "children": [
{ {
"name": "panneaudircentre", "name": "panneaufleche",
"type": "Mesh", "type": "Mesh",
"position": [-10.5207, 7.0874, 19.3819], "position": [-10.5207, 7.0874, 19.3819],
"rotation": [-3.1416, 1.5139, -3.0947], "rotation": [-3.1416, 1.5139, -3.0947],
"scale": [1, 1, 1] "scale": [1, 1, 1]
} }
] ]
} },
]
},
{
"name": "panneaudirfabrik",
"type": "Object3D",
"role": "group",
"position": [0, 0, 0],
"rotation": [0, 0, 0],
"scale": [1, 1, 1],
"children": [
{ {
"name": "panneaudirfabrik", "name": "panneaufleche",
"type": "Object3D", "type": "Object3D",
"position": [29.0899, 6.9811, 23.4988], "position": [29.0899, 6.9811, 23.4988],
"rotation": [3.1416, -0.8852, 3.1253], "rotation": [3.1416, -0.8852, 3.1253],
"scale": [1, 1, 1], "scale": [1, 1, 1],
"children": [ "children": [
{ {
"name": "panneaudirfabrik", "name": "panneaufleche",
"type": "Mesh", "type": "Mesh",
"position": [29.0899, 6.9811, 23.4988], "position": [29.0899, 6.9811, 23.4988],
"rotation": [3.1416, -0.8852, 3.1253], "rotation": [3.1416, -0.8852, 3.1253],
+7
View File
@@ -10,6 +10,13 @@ const MESH_NAME_MAPPINGS = {
eoliennes: "eolienne", eoliennes: "eolienne",
immeuble_1: "immeuble1", immeuble_1: "immeuble1",
buissons: "buisson", buissons: "buisson",
panneauxcentre: "panneauclassique",
panneauxdomaine: "panneauclassique",
panneaudircentre: "panneaufleche",
panneaudirdomaine: "panneaufleche",
panneaudirfabrik: "panneaufleche",
panneaudirresidences1: "panneaufleche",
panneaudirresidences2: "panneaufleche",
panneauxquartier: "panneauaffichage", panneauxquartier: "panneauaffichage",
}; };
+20 -70
View File
@@ -1,108 +1,58 @@
import type { TerrainSurfaceColorConfig } from "@/types/world/terrainSurface";
export const TERRAIN_SURFACE_COLOR_TOLERANCE = 15;
export const TERRAIN_WATER_HEIGHT = 0;
export const TERRAIN_TILE_SIZE = 1;
export const GRASS_BASE_COLOR = "#1a3a1a";
export const TERRAIN_COLORS = { export const TERRAIN_COLORS = {
grass1: { grass1: {
hex: "#84C66B", hex: "#84C66B",
rgb: [132, 198, 107] as const, rgb: [132, 198, 107] as const,
type: "grass" as const, kind: "grass",
grassTipColor: "#84C66B", grassTipColor: "#84C66B",
}, },
grass2: { grass2: {
hex: "#67B058", hex: "#67B058",
rgb: [103, 176, 88] as const, rgb: [103, 176, 88] as const,
type: "grass" as const, kind: "grass",
grassTipColor: "#67B058", grassTipColor: "#67B058",
}, },
grass3: { grass3: {
hex: "#A3CA5B", hex: "#A3CA5B",
rgb: [163, 202, 91] as const, rgb: [163, 202, 91] as const,
type: "grass" as const, kind: "grass",
grassTipColor: "#A3CA5B", grassTipColor: "#A3CA5B",
}, },
potager: { potager: {
hex: "#342420", hex: "#342420",
rgb: [52, 36, 32] as const, rgb: [52, 36, 32] as const,
type: "tile" as const, kind: "garden",
tileModel: "/models/potager/potager.gltf", modelPath: "/models/potager/potager.gltf",
tileSize: 1, tileSize: TERRAIN_TILE_SIZE,
}, },
terre: { terre: {
hex: "#513E2C", hex: "#513E2C",
rgb: [81, 62, 44] as const, rgb: [81, 62, 44] as const,
type: "none" as const, kind: "dirt",
}, },
chemin: { chemin: {
hex: "#F5D896", hex: "#F5D896",
rgb: [245, 216, 150] as const, rgb: [245, 216, 150] as const,
type: "tile" as const, kind: "path",
tileModel: "/models/chemins/model.gltf", modelPath: "/models/chemins/model.gltf",
tileSize: 1, tileSize: TERRAIN_TILE_SIZE,
}, },
eau: { eau: {
hex: "#91DAF5", hex: "#91DAF5",
rgb: [145, 218, 245] as const, rgb: [145, 218, 245] as const,
type: "water" as const, kind: "water",
}, },
cailloux: { cailloux: {
hex: "#B6D3DE", hex: "#B6D3DE",
rgb: [182, 211, 222] as const, rgb: [182, 211, 222] as const,
type: "none" as const, kind: "rock",
}, },
} as const; } satisfies Record<string, TerrainSurfaceColorConfig>;
export type TerrainColorKey = keyof typeof TERRAIN_COLORS; export type TerrainColorKey = keyof typeof TERRAIN_COLORS;
export type TerrainType = "grass" | "tile" | "water" | "none";
export const GRASS_BASE_COLOR = "#1a3a1a";
export const COLOR_TOLERANCE = 15;
export function colorMatchesTerrain(
r: number,
g: number,
b: number,
targetRgb: readonly [number, number, number],
tolerance: number = COLOR_TOLERANCE,
): boolean {
return (
Math.abs(r - targetRgb[0]) <= tolerance &&
Math.abs(g - targetRgb[1]) <= tolerance &&
Math.abs(b - targetRgb[2]) <= tolerance
);
}
export function getTerrainTypeFromColor(
r: number,
g: number,
b: number,
): TerrainColorKey | null {
for (const [key, config] of Object.entries(TERRAIN_COLORS)) {
if (colorMatchesTerrain(r, g, b, config.rgb)) {
return key as TerrainColorKey;
}
}
return null;
}
export function isGrassZone(r: number, g: number, b: number): boolean {
return (
colorMatchesTerrain(r, g, b, TERRAIN_COLORS.grass1.rgb) ||
colorMatchesTerrain(r, g, b, TERRAIN_COLORS.grass2.rgb) ||
colorMatchesTerrain(r, g, b, TERRAIN_COLORS.grass3.rgb)
);
}
export function getGrassTipColor(
r: number,
g: number,
b: number,
): string | null {
if (colorMatchesTerrain(r, g, b, TERRAIN_COLORS.grass1.rgb)) {
return TERRAIN_COLORS.grass1.grassTipColor;
}
if (colorMatchesTerrain(r, g, b, TERRAIN_COLORS.grass2.rgb)) {
return TERRAIN_COLORS.grass2.grassTipColor;
}
if (colorMatchesTerrain(r, g, b, TERRAIN_COLORS.grass3.rgb)) {
return TERRAIN_COLORS.grass3.grassTipColor;
}
return null;
}
+18
View File
@@ -0,0 +1,18 @@
export type TerrainSurfaceKind =
| "grass"
| "path"
| "water"
| "garden"
| "dirt"
| "rock";
export type TerrainSurfaceRgb = readonly [number, number, number];
export interface TerrainSurfaceColorConfig {
hex: string;
rgb: TerrainSurfaceRgb;
kind: TerrainSurfaceKind;
grassTipColor?: string;
modelPath?: string;
tileSize?: number;
}
+51
View File
@@ -0,0 +1,51 @@
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;
}