Download src/shared/actors.mjs from xlt99/EmboDi: direct link, hf CLI and curl.
- Browser
- Download file 10.3 kB
-
https://huggingface.co/spaces/xlt99/EmboDi/resolve/main/src/shared/actors.mjs
- Command line
-
hf download hf://spaces/xlt99/EmboDi/src/shared/actors.mjs
-
curl -L -o actors.mjs https://huggingface.co/spaces/xlt99/EmboDi/resolve/main/src/shared/actors.mjs
10.3 kB
| 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 | |
| }; | |