Add Chromium-only Blender WebEngine parity work
This commit is contained in:
62
web/protocol/skin.ts
Normal file
62
web/protocol/skin.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import type { ShapeKeyIR, SkinWeightsIR } from "./scene-ir";
|
||||
import type { SkinSimplifyPolicy } from "./simplify";
|
||||
|
||||
export interface NormalizedSkinWeights {
|
||||
indices: number[];
|
||||
weights: number[];
|
||||
droppedInfluences: number;
|
||||
}
|
||||
|
||||
export interface SkinSimplifyGate {
|
||||
allowed: boolean;
|
||||
reason?: "MISSING_SKIN_ATTRIBUTES" | "INVALID_SKIN_ATTRIBUTES" | "SHAPE_KEYS_REJECTED" | "SHAPE_KEY_DATA_INVALID";
|
||||
message?: string;
|
||||
normalized?: NormalizedSkinWeights;
|
||||
}
|
||||
|
||||
function finiteWeight(value: number): boolean {
|
||||
return Number.isFinite(value) && value >= 0;
|
||||
}
|
||||
|
||||
export function normalizeSkinWeights(vertexCount: number, skin: SkinWeightsIR, policy: SkinSimplifyPolicy): NormalizedSkinWeights {
|
||||
if (!Number.isInteger(vertexCount) || vertexCount <= 0 || skin.indices.length !== vertexCount * 4 || skin.weights.length !== vertexCount * 4) {
|
||||
throw new Error("skin arrays must contain four influences per vertex");
|
||||
}
|
||||
const indices: number[] = [];
|
||||
const weights: number[] = [];
|
||||
let droppedInfluences = 0;
|
||||
for (let vertex = 0; vertex < vertexCount; vertex++) {
|
||||
const influences = [0, 1, 2, 3].map((slot) => ({
|
||||
index: skin.indices[vertex * 4 + slot],
|
||||
weight: skin.weights[vertex * 4 + slot],
|
||||
})).filter((influence) => finiteWeight(influence.weight) && influence.weight >= policy.minWeight)
|
||||
.sort((left, right) => right.weight - left.weight)
|
||||
.slice(0, policy.maxInfluences);
|
||||
droppedInfluences += 4 - influences.length;
|
||||
const total = influences.reduce((sum, influence) => sum + influence.weight, 0);
|
||||
if (total <= 0) throw new Error(`vertex ${vertex} has no surviving skin influence`);
|
||||
for (let slot = 0; slot < 4; slot++) {
|
||||
const influence = influences[slot];
|
||||
indices.push(influence?.index ?? 0);
|
||||
weights.push(influence ? influence.weight / total : 0);
|
||||
}
|
||||
}
|
||||
return { indices, weights, droppedInfluences };
|
||||
}
|
||||
|
||||
export function evaluateSkinSimplifyGate(vertexCount: number, skin: SkinWeightsIR | undefined, shapeKeys: readonly ShapeKeyIR[] | undefined, policy: SkinSimplifyPolicy): SkinSimplifyGate {
|
||||
if (!skin) return { allowed: false, reason: "MISSING_SKIN_ATTRIBUTES", message: "Skin simplification requires exported bone indices, weights and bind matrix" };
|
||||
try {
|
||||
const normalized = normalizeSkinWeights(vertexCount, skin, policy);
|
||||
if (shapeKeys && shapeKeys.length > 0 && policy.shapeKeys === "REJECT") {
|
||||
return { allowed: false, reason: "SHAPE_KEYS_REJECTED", message: "Shape keys must be explicitly rejected before skin simplification" };
|
||||
}
|
||||
if (shapeKeys?.some((shape) => shape.positions.length !== vertexCount * 3)) {
|
||||
return { allowed: false, reason: "SHAPE_KEY_DATA_INVALID", message: "Shape key positions do not match the source vertex count" };
|
||||
}
|
||||
return { allowed: true, normalized };
|
||||
}
|
||||
catch (error) {
|
||||
return { allowed: false, reason: "INVALID_SKIN_ATTRIBUTES", message: error instanceof Error ? error.message : "Invalid skin attributes" };
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user