feat: add terrain surface sampler
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user