File size: 10,294 Bytes
970fc3e | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 | import {languagePattern, numberWords, termWords} from "./language.mjs";
import { validateMotionPlan } from "./authoring-motion.mjs";
import { CAST_SIZE, castSlots } from "./actor-slots.mjs";
import { clearEntityTargets } from "./entity-targets.mjs";
const finite = (x, min, max) => typeof x === "number" && Number.isFinite(x) && x >= min && x <= max;
const idOK = (id) => typeof id === "string" && /^[a-zA-Z0-9_-]{1,80}$/.test(id);
const MAX_ACTORS = 7;
const actorMotionId = (actor) => actor.motionId === void 0 ? actor.assetId : actor.motionId;
function validateActor(actor) {
if (!actor || !idOK(actor.id) || !idOK(actor.assetId) || typeof actor.name !== "string" || !actor.name.trim() || actor.name.length > 80) throw new Error("Invalid actor identifier");
if (!Array.isArray(actor.position) || actor.position.length !== 3 || !actor.position.every((x) => finite(x, -100, 100)) || !finite(actor.yaw, -Math.PI * 8, Math.PI * 8)) throw new Error("Invalid actor position or orientation");
if (!finite(actor.delay, 0, 60) || actor.trigger !== "start" || !/^#[0-9a-fA-F]{6}$/.test(actor.color)) throw new Error("Invalid actor entry settings");
if (actor.motionId !== void 0 && actor.motionId !== null && !idOK(actor.motionId)) throw new Error("Invalid actor motion identifier");
if (actor.castSlot !== void 0 && (!Number.isInteger(actor.castSlot) || actor.castSlot < 1 || actor.castSlot > CAST_SIZE)) throw new Error("Invalid actor motion slot");
if (actor.motionOffset !== void 0 && (!Array.isArray(actor.motionOffset) || actor.motionOffset.length !== 3 || !actor.motionOffset.every((x) => finite(x, -100, 100)))) throw new Error("Invalid motion transition offset");
if (actor.motionPlan != null) validateMotionPlan(actor.motionPlan);
return actor;
}
function validateActors(actors) {
if (!Array.isArray(actors) || actors.length > MAX_ACTORS) throw new Error(`A maximum of ${MAX_ACTORS} actors is supported`);
const ids = /* @__PURE__ */ new Set(), slots = /* @__PURE__ */ new Set();
for (const actor of actors) {
validateActor(actor);
if (ids.has(actor.id)) throw new Error("Duplicate actor ID");
ids.add(actor.id);
if (actor.castSlot !== void 0) {
if (slots.has(actor.castSlot)) throw new Error("Duplicate actor motion slot");
slots.add(actor.castSlot);
}
}
return actors;
}
function updateActors(scene, command, assetIds) {
const previous = structuredClone(scene.actors || []), actors = previous.length <= CAST_SIZE ? castSlots(previous) : previous, allowed = new Set(assetIds);
if (command.type === "create") {
if (actors.length >= CAST_SIZE) throw new Error(`This demo supports up to ${CAST_SIZE} actors`);
validateActor(command.actor);
if (!allowed.has(command.actor.assetId) || actorMotionId(command.actor) && !allowed.has(actorMotionId(command.actor))) throw new Error("Motion assets are not ready");
if (actors.some((a) => a.id === command.actor.id) || scene.objects.some((o) => o.id === command.actor.id)) throw new Error("Actor ID already exists");
actors.push(command.actor);
} else {
const index = actors.findIndex((a) => a.id === command.id);
if (index < 0) throw new Error("Select an actor first");
if (command.type === "remove") actors.splice(index, 1);
else if (command.type === "update") {
const permitted = ["position", "yaw", "delay", "color", "name", "assetId", "motionId", "motionPlan"];
if (!command.changes || Object.keys(command.changes).some((k) => !permitted.includes(k))) throw new Error("Invalid actor edit field");
if (command.changes.assetId !== void 0 && !allowed.has(command.changes.assetId)) throw new Error("Motion assets are not ready");
if (command.changes.motionId != null && !allowed.has(command.changes.motionId)) throw new Error("Motion assets are not ready");
actors[index] = { ...actors[index], ...command.changes, ...command.changes.motionId !== void 0 && command.changes.motionPlan === void 0 ? { motionPlan: null } : {} };
validateActor(actors[index]);
} else throw new Error("Invalid actor action");
}
const next = actors.length <= CAST_SIZE ? castSlots(actors) : actors;
validateActors(next);
const result = { ...scene, actors: next };
if (command.type === "remove") result.objects = clearEntityTargets(scene.objects, command.id);
if (scene.doorPerformance) {
const ids = scene.doorPerformance.actorIds.filter((id) => next.some((a) => a.id === id));
if (ids.length) result.doorPerformance = { ...scene.doorPerformance, actorIds: ids, anchorActorId: ids.includes(scene.doorPerformance.anchorActorId) ? scene.doorPerformance.anchorActorId : ids[0] };
else delete result.doorPerformance;
}
return result;
}
function createPerformance() {
let mode = "editing", time = 0, runId = 0, actors = [], schedule = /* @__PURE__ */ new Map();
const prepare = (ids, nextMode) => {
runId++;
time = 0;
mode = nextMode;
const chosen = ids ? new Set(ids) : null;
schedule = new Map(actors.filter((a) => !chosen || chosen.has(a.id)).map((a) => [a.id, { delay: a.delay, assetId: a.assetId, motionId: actorMotionId(a), motionPlan: a.motionPlan }]));
};
return {
sync(next) {
actors = structuredClone(next);
},
start(ids) {
prepare(ids, "running");
},
arm(ids) {
prepare(ids, "paused");
},
switchMotions(offsets = {}, playback = "play") {
if (playback === "assign") return;
const oldMode = mode, oldTime = time, oldSchedule = schedule;
if (playback === "preserve" && oldMode === "editing") return;
runId++;
time = 0;
mode = playback === "preserve" && oldMode === "paused" ? "paused" : "running";
schedule = new Map(actors.map((a) => [a.id, { delay: oldMode === "editing" ? a.delay : Math.max(0, (oldSchedule.get(a.id)?.delay ?? a.delay) - oldTime), assetId: a.assetId, motionId: actorMotionId(a), motionOffset: [...offsets[a.id] || [0, 0, 0]] }]));
},
stop() {
if (mode === "running") mode = "paused";
},
resume() {
if (mode === "paused") mode = "running";
},
edit() {
mode = "editing";
time = 0;
},
advance(dt) {
if (mode === "running" && Number.isFinite(dt) && dt > 0) time += dt;
},
snapshot() {
return { mode, time, runId };
},
completed(assets) {
const active = actors.map((a) => schedule.get(a.id)).filter((c) => c?.motionPlan || c?.motionId);
return mode !== "editing" && active.length > 0 && active.every((c) => time >= c.delay + (c.motionPlan?.duration ?? assets.get(c.motionId)?.duration ?? Infinity));
},
frames(assets) {
return actors.map((a) => {
const cue = schedule.get(a.id), preview = mode === "editing" || !cue;
const visible = preview || !!cue && time >= cue.delay;
const motionId = preview ? actorMotionId(a) : cue.motionId, assetId = motionId || (preview ? a.assetId : cue.assetId), rest = preview || !motionId;
const motionPlan = preview ? a.motionPlan : cue.motionPlan;
if (motionPlan) return { ...a, motionPlan, assetId: preview ? a.assetId : cue.assetId, motionId: null, pose: "generated", preview, visible, runId, clipTime: preview ? motionPlan.duration : Math.min(motionPlan.duration, Math.max(0, time - (cue?.delay || 0))) };
const duration = assets.get(motionId)?.duration || 0;
return { ...a, assetId, motionId, motionOffset: preview ? [0, 0, 0] : [...cue?.motionOffset || [0, 0, 0]], pose: rest ? "rest" : "motion", preview, visible, runId, clipTime: rest ? 0 : Math.min(duration, Math.max(0, time - (cue?.delay || 0))) };
});
}
};
}
function delayValue(text) {
const values = numberWords.actorDelays;
const match = text.match(languagePattern("actor.delaySeconds"));
return match ? Number(values[match[1]] ?? match[1]) : null;
}
function parseDirectorCommand(text) {
const t = text.trim().replace(languagePattern("punctuation.actors.1"), " ").trim();
const groupStyle = t.match(languagePattern("actor.groupStyle"));
if (groupStyle && !languagePattern("punctuation.actors.2").test(text)) return { type: "style", style: groupStyle[1] === termWords.cute ? "cute" : "zombie" };
if (languagePattern("actor.stop").test(t)) return { type: "stop" };
if (languagePattern("actor.start").test(t)) return { type: "start" };
if (languagePattern("actor.resume").test(t)) return { type: "resume" };
if (languagePattern("actor.edit").test(t)) return { type: "edit" };
if (languagePattern("actor.saveScene").test(t)) return { type: "save" };
const style = t.match(languagePattern("actor.style"));
if (style && !languagePattern("punctuation.actors.3").test(text)) return { type: "style", style: [termWords.cute, termWords.birthday].includes(style[1]) ? "cute" : "zombie" };
const actor = languagePattern("actor.subject").test(t), delay = delayValue(t);
if (languagePattern("actor.clearMotion").test(t)) return { type: "clearMotion" };
const numberedMotion = t.match(languagePattern("actor.numberedMotion"));
if (numberedMotion) return { type: "motion", number: Number(numberWords.actorMotionNumbers[numberedMotion[1]] ?? numberedMotion[1]) };
if (languagePattern("actor.motionSubject").test(t) && languagePattern("actor.generatedMotion").test(t)) return { type: "unavailable" };
if (languagePattern("actor.spatialSubject").test(t) && languagePattern("actor.moveVerb").test(t) && languagePattern("actor.here").test(t)) return { type: "move" };
if (actor && languagePattern("actor.createVerb").test(t)) return { type: "create", delay: delay ?? 0 };
if (languagePattern("legacy.lungeMotion").test(t) && languagePattern("legacy.motionAssignmentSubject").test(t) || languagePattern("legacy.assignLungeMotion").test(t)) return { type: "motion" };
if (delay !== null && languagePattern("actor.delayVerb").test(t)) return { type: "delay", delay };
if (languagePattern("actor.spatialSubject").test(t) && languagePattern("actor.faceViewer").test(t)) return { type: "face" };
if (languagePattern("actor.spatialSubject").test(t) && languagePattern("actor.removeVerb").test(t)) return { type: "remove" };
return null;
}
export {
MAX_ACTORS,
actorMotionId,
createPerformance,
parseDirectorCommand,
updateActors,
validateActor,
validateActors
};
|