feat(web): add audio speaker and particle authoring
Some checks failed
M6 deployable RC / quick (push) Has been cancelled
M6 deployable RC / chromium (push) Has been cancelled
M6 deployable RC / release (push) Has been cancelled

This commit is contained in:
mes123456
2026-08-29 06:30:42 +08:00
parent 81113159dc
commit 43d0c437bb
44 changed files with 2228 additions and 54 deletions

View File

@@ -420,6 +420,33 @@ function assertFutureCapability(payload: Extract<WebEngineRequest["command"], {
if (typeof payload.source !== "string" || new TextEncoder().encode(payload.source).byteLength > 1_048_576) throw report("NON_MESH_PROPERTY_INVALID", "Text source must be valid UTF-8 within the 1 MiB budget");
return;
}
case "setSoundProperties": {
const sound = currentSnapshot?.sounds?.find((candidate) => candidate.id === payload.dataId);
if (!sound) throw report("NON_MESH_DATA_UNSUPPORTED", `Sound data block is unavailable: ${payload.dataId}`);
if (!validPropertyKeys(payload.properties, ["volume", "attenuation", "pitch", "minGain", "maxGain", "distance", "offsetTime"])) throw report("NON_MESH_PROPERTY_INVALID", "Sound properties are empty or contain unsupported fields");
for (const value of Object.values(payload.properties)) if (!boundedNumber(value, 0, 1_000_000)) throw report("NON_MESH_PROPERTY_INVALID", "Sound properties are outside the bounded range");
return;
}
case "setSpeakerProperties": {
const speaker = currentSnapshot?.speakers?.find((candidate) => candidate.id === payload.dataId);
if (!speaker) throw report("NON_MESH_DATA_UNSUPPORTED", `Speaker data block is unavailable: ${payload.dataId}`);
if (!validPropertyKeys(payload.properties, ["volumeMax", "volumeMin", "distanceMax", "distanceReference", "attenuation", "coneAngleOuter", "coneAngleInner", "coneVolumeOuter", "volume", "pitch"])) throw report("NON_MESH_PROPERTY_INVALID", "Speaker properties are empty or contain unsupported fields");
for (const [key, value] of Object.entries(payload.properties)) {
const maximum = key.startsWith("coneAngle") ? 360 : key === "coneVolumeOuter" ? 1 : 1_000_000;
if (!boundedNumber(value, 0, maximum)) throw report("NON_MESH_PROPERTY_INVALID", "Speaker properties are outside the bounded range");
}
return;
}
case "setParticleSettings": {
const settings = currentSnapshot?.particleSettings?.find((candidate) => candidate.id === payload.dataId);
if (!settings) throw report("NON_MESH_DATA_UNSUPPORTED", `ParticleSettings data block is unavailable: ${payload.dataId}`);
if (!validPropertyKeys(payload.properties, ["type", "from", "distribution", "physicsType", "totalParticles", "start", "end", "lifetime", "size", "drawSize"])) throw report("NON_MESH_PROPERTY_INVALID", "Particle settings are empty or contain unsupported fields");
for (const [key, value] of Object.entries(payload.properties)) {
if (typeof value !== "number" || !Number.isFinite(value) || value < 0 || value > 1_000_000) throw report("NON_MESH_PROPERTY_INVALID", `Particle setting ${key} is outside the bounded range`);
if (["type", "from", "distribution", "physicsType", "totalParticles"].includes(key) && !Number.isSafeInteger(value)) throw report("NON_MESH_PROPERTY_INVALID", `Particle setting ${key} must be an integer`);
}
return;
}
case "setVolumeProperties": {
const data = currentSnapshot?.nonMeshData?.find((candidate) => candidate.id === payload.dataId);
if (!data || data.type !== "VOLUME") throw report("NON_MESH_DATA_UNSUPPORTED", `N-015 Volume data block is unavailable: ${payload.dataId}`);