Download src/shared/group-path.mjs from xlt99/EmboDi: direct link, hf CLI and curl.
- Browser
- Download file 5.21 kB
-
https://huggingface.co/spaces/xlt99/EmboDi/resolve/main/src/shared/group-path.mjs
- Command line
-
hf download hf://spaces/xlt99/EmboDi/src/shared/group-path.mjs
-
curl -L -o group-path.mjs https://huggingface.co/spaces/xlt99/EmboDi/resolve/main/src/shared/group-path.mjs
5.21 kB
| import { actorMotionId } from "./actors.mjs"; | |
| import { floorAt } from "./cast-layout.mjs"; | |
| import { pathLength } from "./draft.mjs"; | |
| const point = (p) => Array.isArray(p) && p.length === 3 && p.every((n) => Number.isFinite(n) && Math.abs(n) <= 100); | |
| const turn = (p, y) => [Math.cos(y) * p[0] + Math.sin(y) * p[2], p[1], -Math.sin(y) * p[0] + Math.cos(y) * p[2]]; | |
| const angle = (a) => Math.atan2(Math.sin(a), Math.cos(a)); | |
| const actorDesign = (actors) => actors.map((a) => ({ id: a.id, position: [...a.position], yaw: a.yaw, motionId: actorMotionId(a) })); | |
| function makeGroupPath(scene, { actorIds, points, speed = 0.6, id }) { | |
| if (!Array.isArray(actorIds) || !actorIds.length || new Set(actorIds).size !== actorIds.length) throw Error("Select a group of actors"); | |
| const actors = actorIds.map((id2) => scene.actors?.find((a) => a.id === id2)); | |
| if (actors.some((a) => !a || !actorMotionId(a))) throw Error("Prepare motions for the selected actors first"); | |
| if (!Array.isArray(points) || points.length < 2 || points.length > 512 || !points.every(point)) throw Error("Invalid path point"); | |
| const lead = actors[0], rawPoints = structuredClone(points), clean = []; | |
| for (const p of points) { | |
| const y = floorAt(scene, p); | |
| if (!Number.isFinite(y) || Math.abs(p[1] - y) > 0.2 || Math.abs(y - lead.position[1]) > 0.2) throw Error("The path must stay on valid floor at one level"); | |
| const q = [p[0], lead.position[1], p[2]]; | |
| if (!clean.length || Math.hypot(q[0] - clean.at(-1)[0], q[2] - clean.at(-1)[2]) >= 0.025) clean.push(q); | |
| } | |
| if (clean.length < 2 || pathLength(clean) < 0.15 || pathLength(clean) > 30) throw Error("Draw a continuous path between 0.15 and 30 meters"); | |
| if (Math.hypot(clean[0][0] - lead.position[0], clean[0][2] - lead.position[2]) > 0.025) clean.unshift([...lead.position]); | |
| else clean[0] = [...lead.position]; | |
| if (pathLength(clean) > 35) throw Error("Path starts too far from the group. Start drawing near the actors."); | |
| const path = { id, groupId: "group-" + lead.id, actorIds: [...actorIds], rawPoints, points: clean, speed, initialYaw: lead.yaw, design: actorDesign(actors), offsets: actors.map((a) => ({ id: a.id, position: turn(a.position.map((v, i) => v - lead.position[i]), -lead.yaw), yaw: angle(a.yaw - lead.yaw) })), length: pathLength(clean) }; | |
| validateGroupPath(path, scene); | |
| return path; | |
| } | |
| function validateGroupPath(p, scene) { | |
| if (!p || typeof p.id !== "string" || !/^path-[\w-]{1,90}$/.test(p.id) || typeof p.groupId !== "string" || !Number.isFinite(p.speed) || p.speed < 0.1 || p.speed > 2 || !Number.isFinite(p.initialYaw) || !Array.isArray(p.points) || p.points.length < 2 || p.points.length > 513 || !p.points.every(point) || !Array.isArray(p.rawPoints) || p.rawPoints.length > 512 || !p.rawPoints.every(point) || !Number.isFinite(p.length) || Math.abs(pathLength(p.points) - p.length) > 1e-3 || p.length < 0.15 || p.length > 35) throw Error("Invalid group path configuration"); | |
| if (!Array.isArray(p.actorIds) || p.actorIds.length < 1 || p.actorIds.length > 5 || new Set(p.actorIds).size !== p.actorIds.length || p.actorIds.some((id) => !scene.actors?.some((a) => a.id === id && actorMotionId(a))) || !Array.isArray(p.offsets) || p.offsets.length !== p.actorIds.length || p.offsets.some((o, i) => o.id !== p.actorIds[i] || !point(o.position) || !Number.isFinite(o.yaw))) throw Error("Path references an invalid actor"); | |
| if (!Array.isArray(p.design) || p.design.length !== p.actorIds.length || p.design.some((a, i) => a.id !== p.actorIds[i] || !point(a.position) || !Number.isFinite(a.yaw) || typeof a.motionId !== "string")) throw Error("Invalid initial path position"); | |
| } | |
| function at(points, d) { | |
| for (let i = 1; i < points.length; i++) { | |
| const a = points[i - 1], b = points[i], length = Math.hypot(b[0] - a[0], b[2] - a[2]); | |
| if (d <= length || i === points.length - 1) { | |
| const t = Math.min(1, Math.max(0, d / Math.max(length, 1e-5))); | |
| return a.map((v, j) => v + (b[j] - v) * t); | |
| } | |
| d -= length; | |
| } | |
| return points[0]; | |
| } | |
| function groupPathPose(path, time) { | |
| const distance = Math.min(path.length, Math.max(0, time) * path.speed), p = at(path.points, distance), a = at(path.points, Math.max(0, distance - 0.25)), b = at(path.points, Math.min(path.length, distance + 0.25)); | |
| const target = Math.atan2(b[0] - a[0], b[2] - a[2]), blend = Math.min(1, distance / 0.5), yaw = path.initialYaw + angle(target - path.initialYaw) * blend; | |
| return { position: p, yaw, complete: distance >= path.length }; | |
| } | |
| function pathFrames(path, frames, time, assets) { | |
| const pose = groupPathPose(path, time), byId = new Map(path.offsets.map((o) => [o.id, o])), elapsed = Math.min(time, path.length / path.speed); | |
| return frames.map((f) => { | |
| const offset = byId.get(f.id); | |
| if (!offset) return f; | |
| const duration = assets.get(actorMotionId(f))?.duration || 1; | |
| return { ...f, position: turn(offset.position, pose.yaw).map((v, i) => v + pose.position[i]), yaw: pose.yaw + offset.yaw, pose: "motion", preview: false, visible: true, clipTime: elapsed % duration, inPlace: true, motionOffset: [0, 0, 0] }; | |
| }); | |
| } | |
| export { | |
| actorDesign, | |
| groupPathPose, | |
| makeGroupPath, | |
| pathFrames, | |
| validateGroupPath | |
| }; | |