58 lines
2.4 KiB
TypeScript
58 lines
2.4 KiB
TypeScript
import type { ModifierIR } from "./scene-ir";
|
|
|
|
function fnv1a(value: string): number {
|
|
let hash = 0x811c9dc5;
|
|
for (let index = 0; index < value.length; index++) {
|
|
hash ^= value.charCodeAt(index);
|
|
hash = Math.imul(hash, 0x01000193);
|
|
}
|
|
return hash >>> 0;
|
|
}
|
|
|
|
export type ModifierParameterMetadata = {
|
|
key: string;
|
|
value: unknown;
|
|
kind: "boolean" | "number" | "string" | "enum" | "object";
|
|
options?: string[];
|
|
};
|
|
|
|
/** Derives editable fields from the metadata carried by the authoritative modifier IR. */
|
|
export function modifierParameterMetadata(modifier: ModifierIR): ModifierParameterMetadata[] {
|
|
return Object.entries(modifier.parameters ?? {}).map(([key, rawValue]) => {
|
|
// Main may carry a descriptor when RNA exposes enum choices alongside the
|
|
// current value. Keep the descriptor opaque to callers while deriving the
|
|
// editable value and choices from it.
|
|
const descriptor = rawValue && typeof rawValue === "object" && !Array.isArray(rawValue) &&
|
|
"value" in rawValue && ("kind" in rawValue || "options" in rawValue)
|
|
? rawValue as { value: unknown; kind?: ModifierParameterMetadata["kind"]; options?: unknown[] }
|
|
: undefined;
|
|
const rawOptions = descriptor?.options ?? (Array.isArray(rawValue) && rawValue.every((entry) => typeof entry === "string") ? rawValue : undefined);
|
|
const options = rawOptions?.map(String);
|
|
const value = descriptor?.value ?? (options ? options[0] : rawValue);
|
|
const kind = descriptor?.kind ?? (
|
|
typeof value === "boolean" ? "boolean" :
|
|
typeof value === "number" ? "number" :
|
|
typeof value === "string" ? (options ? "enum" : "string") :
|
|
"object"
|
|
);
|
|
return { key, value, kind, options: options ?? undefined };
|
|
});
|
|
}
|
|
|
|
/** Hashes evaluated modifier identity/state, excluding display timestamps. */
|
|
export function modifierStackHash(stack: readonly ModifierIR[] | undefined): string {
|
|
const canonical = (stack ?? []).map((modifier) => ({
|
|
uuid: modifier.uuid,
|
|
type: modifier.type,
|
|
enabled: modifier.enabled,
|
|
showViewport: modifier.showViewport,
|
|
showRender: modifier.showRender,
|
|
showEditMode: modifier.showEditMode,
|
|
showOnCage: modifier.showOnCage,
|
|
parameters: modifier.parameters,
|
|
dependsOn: modifier.dependsOn,
|
|
evaluationStatus: modifier.evaluationStatus,
|
|
}));
|
|
return `mod-${fnv1a(JSON.stringify(canonical)).toString(16).padStart(8, "0")}`;
|
|
}
|