Advance WebGPU volume and bounded workflows
This commit is contained in:
94
web/app/src/volume/volume-material-mapping.ts
Normal file
94
web/app/src/volume/volume-material-mapping.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
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_COLOR_GRID_UNSUPPORTED"
|
||||
| "VOLUME_TEMPERATURE_BLACKBODY_UNSUPPORTED"
|
||||
| "VOLUME_EMISSION_GRID_UNSUPPORTED"
|
||||
| "VOLUME_VELOCITY_RENDER_UNSUPPORTED";
|
||||
field: "colorGrid" | "temperatureGrid" | "emissionGrid" | "velocityGrid";
|
||||
fallback: string;
|
||||
}
|
||||
|
||||
export interface VolumeMaterialMappingResultIR {
|
||||
material: NanoVDBMaterialIR;
|
||||
losses: VolumeMaterialMappingLossIR[];
|
||||
supportedSemantics: Array<"DENSITY_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): 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`);
|
||||
return name;
|
||||
}
|
||||
|
||||
export function mapPrincipledVolumeToNanoVDB(
|
||||
sourceManifest: NanoVDBBundleManifestIR,
|
||||
input: PrincipledVolumeMappingInputIR,
|
||||
): VolumeMaterialMappingResultIR {
|
||||
const manifest = validateNanoVDBBundleManifest(sourceManifest);
|
||||
const densityGrid = requireGrid(manifest, input.densityGrid, "DENSITY", "densityGrid");
|
||||
if (!densityGrid) throw new Error("NANOVDB_MANIFEST_INVALID: a density grid is required");
|
||||
const colorGrid = requireGrid(manifest, input.colorGrid, "COLOR", "colorGrid");
|
||||
const temperatureGrid = requireGrid(manifest, input.temperatureGrid, "TEMPERATURE", "temperatureGrid");
|
||||
const emissionGrid = requireGrid(manifest, input.emissionGrid, "EMISSION", "emissionGrid");
|
||||
const velocityGrid = requireGrid(manifest, input.velocityGrid, "VELOCITY", "velocityGrid");
|
||||
const material: NanoVDBMaterialIR = {
|
||||
densityGrid,
|
||||
...(colorGrid ? { colorGrid } : {}),
|
||||
...(temperatureGrid ? { 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 (colorGrid) losses.push({ code: "VOLUME_COLOR_GRID_UNSUPPORTED", field: "colorGrid", fallback: "constant color" });
|
||||
if (temperatureGrid && input.blackbodyEnabled) losses.push({ code: "VOLUME_TEMPERATURE_BLACKBODY_UNSUPPORTED", field: "temperatureGrid", fallback: "constant emission color" });
|
||||
if (emissionGrid) losses.push({ code: "VOLUME_EMISSION_GRID_UNSUPPORTED", field: "emissionGrid", fallback: "constant emission color and scale" });
|
||||
if (velocityGrid) losses.push({ code: "VOLUME_VELOCITY_RENDER_UNSUPPORTED", field: "velocityGrid", fallback: "velocity metadata retained without motion rendering" });
|
||||
return {
|
||||
material,
|
||||
losses,
|
||||
supportedSemantics: ["DENSITY_GRID", "CONSTANT_COLOR", "CONSTANT_EMISSION", "ANISOTROPY", "INTERPOLATION"],
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user