90 lines
4.4 KiB
TypeScript
90 lines
4.4 KiB
TypeScript
import {
|
|
validateNanoVDBBundleManifest,
|
|
type NanoVDBBundleManifestIR,
|
|
type NanoVDBGridSemantic,
|
|
type NanoVDBMaterialIR,
|
|
} from "../../../protocol/volume-vdb";
|
|
|
|
export interface PrincipledVolumeMappingInputIR {
|
|
densityGrid: string;
|
|
densityScale: number;
|
|
color?: [number, number, number];
|
|
colorGrid?: string;
|
|
temperatureGrid?: string;
|
|
temperatureScale?: number;
|
|
blackbodyEnabled?: boolean;
|
|
emissionGrid?: string;
|
|
emissionColor?: [number, number, number];
|
|
emissionScale?: number;
|
|
velocityGrid?: string;
|
|
anisotropy?: number;
|
|
interpolation?: "NEAREST" | "LINEAR";
|
|
}
|
|
|
|
export interface VolumeMaterialMappingLossIR {
|
|
code:
|
|
| "VOLUME_VELOCITY_RENDER_UNSUPPORTED";
|
|
field: "velocityGrid";
|
|
fallback: string;
|
|
}
|
|
|
|
export interface VolumeMaterialMappingResultIR {
|
|
material: NanoVDBMaterialIR;
|
|
losses: VolumeMaterialMappingLossIR[];
|
|
supportedSemantics: Array<"DENSITY_GRID" | "COLOR_GRID" | "TEMPERATURE_GRID_BLACKBODY" | "EMISSION_GRID" | "CONSTANT_COLOR" | "CONSTANT_EMISSION" | "ANISOTROPY" | "INTERPOLATION">;
|
|
}
|
|
|
|
function finite(value: number, minimum: number, maximum: number, name: string): number {
|
|
if (!Number.isFinite(value) || value < minimum || value > maximum) throw new Error(`NANOVDB_MANIFEST_INVALID: ${name}`);
|
|
return value;
|
|
}
|
|
|
|
function color(value: [number, number, number] | undefined, fallback: [number, number, number], name: string): [number, number, number] {
|
|
const result = value ?? fallback;
|
|
if (!Array.isArray(result) || result.length !== 3 || result.some((channel) => !Number.isFinite(channel) || channel < 0 || channel > 1_000_000)) throw new Error(`NANOVDB_MANIFEST_INVALID: ${name}`);
|
|
return [...result];
|
|
}
|
|
|
|
function requireGrid(manifest: NanoVDBBundleManifestIR, name: string | undefined, semantic: NanoVDBGridSemantic, field: string, valueType: "FLOAT32" | "VEC3F32"): string | undefined {
|
|
if (name === undefined) return undefined;
|
|
const grid = manifest.grids.find((candidate) => candidate.name === name);
|
|
if (!grid || grid.semantic !== semantic) throw new Error(`NANOVDB_MANIFEST_INVALID: ${field} must reference a ${semantic} grid`);
|
|
if (grid.valueType !== valueType) throw new Error(`NANOVDB_GRID_UNSUPPORTED: ${field} requires ${valueType}`);
|
|
return name;
|
|
}
|
|
|
|
export function mapPrincipledVolumeToNanoVDB(
|
|
sourceManifest: NanoVDBBundleManifestIR,
|
|
input: PrincipledVolumeMappingInputIR,
|
|
): VolumeMaterialMappingResultIR {
|
|
const manifest = validateNanoVDBBundleManifest(sourceManifest);
|
|
const densityGrid = requireGrid(manifest, input.densityGrid, "DENSITY", "densityGrid", "FLOAT32");
|
|
if (!densityGrid) throw new Error("NANOVDB_MANIFEST_INVALID: a density grid is required");
|
|
const colorGrid = requireGrid(manifest, input.colorGrid, "COLOR", "colorGrid", "VEC3F32");
|
|
const temperatureGrid = requireGrid(manifest, input.temperatureGrid, "TEMPERATURE", "temperatureGrid", "FLOAT32");
|
|
const emissionGrid = requireGrid(manifest, input.emissionGrid, "EMISSION", "emissionGrid", "FLOAT32");
|
|
const velocityGrid = requireGrid(manifest, input.velocityGrid, "VELOCITY", "velocityGrid", "VEC3F32");
|
|
const material: NanoVDBMaterialIR = {
|
|
densityGrid,
|
|
...(colorGrid ? { colorGrid } : {}),
|
|
...(temperatureGrid && input.blackbodyEnabled ? { temperatureGrid } : {}),
|
|
...(emissionGrid ? { emissionGrid } : {}),
|
|
...(velocityGrid ? { velocityGrid } : {}),
|
|
densityScale: finite(input.densityScale, 0, 1_000_000, "densityScale"),
|
|
emissionScale: finite(input.emissionScale ?? 0, 0, 1_000_000, "emissionScale"),
|
|
temperatureScale: finite(input.temperatureScale ?? 1, 0, 1_000_000, "temperatureScale"),
|
|
anisotropy: finite(input.anisotropy ?? 0, -0.99, 0.99, "anisotropy"),
|
|
interpolation: input.interpolation ?? "LINEAR",
|
|
color: color(input.color, [0.72, 0.78, 0.86], "color"),
|
|
emissionColor: color(input.emissionColor, [1, 1, 1], "emissionColor"),
|
|
};
|
|
if (material.interpolation !== "NEAREST" && material.interpolation !== "LINEAR") throw new Error("NANOVDB_MANIFEST_INVALID: interpolation");
|
|
const losses: VolumeMaterialMappingLossIR[] = [];
|
|
if (velocityGrid) losses.push({ code: "VOLUME_VELOCITY_RENDER_UNSUPPORTED", field: "velocityGrid", fallback: "velocity metadata retained without motion rendering" });
|
|
return {
|
|
material,
|
|
losses,
|
|
supportedSemantics: ["DENSITY_GRID", "COLOR_GRID", "TEMPERATURE_GRID_BLACKBODY", "EMISSION_GRID", "CONSTANT_COLOR", "CONSTANT_EMISSION", "ANISOTROPY", "INTERPOLATION"],
|
|
};
|
|
}
|