Feat/map-environment #6

Merged
math-pixel merged 116 commits from feat/map-environment into develop 2026-05-29 00:00:51 +00:00
2 changed files with 124 additions and 0 deletions
Showing only changes of commit b4a3545460 - Show all commits
+18
View File
@@ -8,6 +8,18 @@ export type TerrainSurfaceKind =
export type TerrainSurfaceRgb = readonly [number, number, number];
export interface TerrainSurfaceUv {
u: number;
v: number;
}
export interface TerrainSurfaceBounds {
minX: number;
maxX: number;
minZ: number;
maxZ: number;
}
export interface TerrainSurfaceColorConfig {
hex: string;
rgb: TerrainSurfaceRgb;
@@ -16,3 +28,9 @@ export interface TerrainSurfaceColorConfig {
modelPath?: string;
tileSize?: number;
}
export interface TerrainSurfaceSample {
rgb: TerrainSurfaceRgb;
key: string | null;
config: TerrainSurfaceColorConfig | null;
}
+106
View File
@@ -0,0 +1,106 @@
import type * as THREE from "three";
import { TERRAIN_COLORS } from "@/data/world/terrainConfig";
import type {
TerrainSurfaceBounds,
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 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,
): TerrainSurfaceUv {
const width = bounds.maxX - bounds.minX;
const depth = bounds.maxZ - bounds.minZ;
return {
u: width === 0 ? 0 : (x - bounds.minX) / width,
v: depth === 0 ? 0 : (z - bounds.minZ) / depth,
};
}
export function sampleTerrainSurfaceAtXZ(
imageData: ImageData,
x: number,
z: number,
bounds: TerrainSurfaceBounds,
): TerrainSurfaceSample {
return sampleTerrainSurfaceAtUv(
imageData,
terrainSurfaceUvFromXZ(x, z, bounds),
);
}