File size: 15,017 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 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 | import { roomAlignmentAction } from "./workflow.mjs";
import { transparencyActions } from "./scene-appearance.mjs";
const AUTHORING_PAGES = ["global", "main", "createTools", "createLight", "lightSettings", "drawTools", "curveTools", "actorSettings", "sceneTools", "sceneMore", "roomTools", "sceneTransparency", "doorSettings", "brushSettings", "suggestions", "recordingTools"];
const item = (id, label, disabled = false) => ({ id, label, disabled });
function authoringMenu(c, page = "main") {
if (!c.authoring || c.phase !== "explore") return null;
if (!AUTHORING_PAGES.includes(page) && page !== "interaction") return null;
const back = item("authoringBack", "Back"), voice = item("voice", c.actorSelected ? "Describe a basic motion" : c.targetName ? "Describe an edit" : "Describe your idea");
const target = c.actorName || c.targetName || (c.objectSelection?.length ? `${c.objectSelection.length} objects selected` : "Current scene");
const door = c.objectSelection?.length === 1 && c.door?.id === c.objectSelection[0];
const regionTarget = c.regionTarget ?? (c.objectSelection?.length === 1 && !c.actorSelected);
const result = (title, entries, hint = "X Voice · Y Menu · A Confirm · B Cancel") => ({ title, entries, hint });
if (c.voiceDraft) return result("Review speech · Not sent", [item("voiceConfirm", "Send · A"), item("voiceRetry", "Speak again · X"), item("voiceCancel", "Cancel speech · B")], "Right stick: pages · A Send · X Retry · B Cancel");
if (c.recording && c.recording !== "idle") return result(c.recording === "recording" ? "Listening" : "Processing speech", [item("voice", c.recording === "recording" ? "Stop listening" : "Transcribing…", c.recording !== "recording"), item("voiceCancel", "Cancel speech · B")], "Release X to review · B Cancel");
const p = c.production;
if (p?.transitioning || p?.saving) return result("Saving changes", [item("cinemaSave", "Please wait…", true)]);
if (p?.requesting) return result("Camera / Lighting Agent", [item("cinemaCancel", "Cancel request")], "B Cancel");
if (p?.placing) return result("Choose a placement point", [item("cinemaCancel", "Cancel placement · B")], p.placement?.visible ? "Place at the ring · A Create · B Cancel" : "Point at visible ground · A Create");
if (p?.dirty) return result(p.tool === "transform" ? "Transform" : p.tool === "curve" ? "Edit curve" : "Preview changes", [item("cinemaSave", "Save · A"), item("cinemaCancel", "Revert · B")], p.tool === "transform" ? "L stick: Move · L grip + stick: Height\nR stick: Rotate · R grip: Grab" : p.tool === "curve" ? "Right grip: pull control points · Release, then A Save" : "A Save · B Discard");
if (page === "sceneTransparency") return result(`Scene transparency · ${Math.round((1 - (c.roomOpacity ?? 0.5)) * 100)}%`, [...transparencyActions(c.roomOpacity), back], "70% is the most transparent · Saved in this browser");
if (page === "roomTools" && c.roomMode && c.hasScene) {
const align = roomAlignmentAction(c), blocked = !!(c.editingBusy || c.job || c.saving || c.finishing || c.applying || c.actorPlacing || c.draft?.active || p?.monitor || p?.playing) || (c.videoState ?? "idle") !== "idle";
return result(
"Room / Alignment",
[align, item("sceneTransparency", "Scene transparency"), item("overview", "Scene overview", blocked), back],
blocked ? "Finish the current operation before adjusting the room." : !c.roomAlignmentAvailable ? "Build or load a reconstructed room to align it." : !c.xr || c.xrMode !== "immersive-ar" ? "Enter Quest MR to align the room with your real space." : "Align room opens a preview, even when a saved offset exists. · A Confirm · B Back"
);
}
if (p && !c.editing) {
if (page === "recordingTools") return result("Recording", [item("record", c.videoState === "recording" ? "Stop and save" : "Start recording", !c.productionReady && c.videoState !== "recording"), item("videoFiles", "View recordings"), back]);
return result(p.monitor ? `Camera monitor · ${p.cameraName || ""}` : p.previewLabel ? `Rehearse · ${p.previewLabel}` : "Explore mode", [
item(p.monitor ? "cinemaNext" : "cinemaPreview", p.monitor ? "Next camera" : "Camera Agent · View camera"),
item(p.playing ? "cinemaPause" : "cinemaPlay", p.playing ? "Pause rehearsal · Left grip" : p.activePreviewIds?.length ? "Resume rehearsal · Left grip" : "Rehearse selection · Left grip", !p.canPreview),
...p.monitor ? [item("cinemaClose", "Close monitor")] : [item("recordingTools", "Recording and export", !c.productionReady)],
...c.script?.agentText || c.script?.userText ? [item("agentReply", "View Agent reply")] : [],
...c.roomMode && c.hasScene ? [item("roomTools", "Room / Alignment")] : [],
item("edit", "Enter edit mode")
], p.previewIds?.length ? "Rehearse selection and linked interactions · Right stick click: edit" : "Right stick click: edit and select a target · X Voice");
}
if (c.saving || c.finishing || c.applying) return result("Saving", [item("save", "Saving…", true)]);
if (c.job === "ready") return result("Preview result", [
item("apply", "Apply changes"),
item("discard", "Discard preview"),
...c.script?.agentText || c.script?.userText ? [item("agentReply", "View Agent reply")] : [],
...c.generatedPreview ? [item("motionReplay", "Replay motion")] : c.floodPreview ? [item("floodPreview", "Replay interaction")] : []
], "A Apply · B Discard · X Voice");
if (c.job === "running") return result("Agent working", [item("cancel", "Stop request"), ...c.script?.agentText || c.script?.userText ? [item("agentReply", "View Agent reply")] : []], "B Stop · X Voice");
if (c.actorPlacing) return result("Place actor", [item("cancelOperation", "Cancel placement")], "Point at ground · A Place · B Cancel");
if (c.draft?.active || c.objectInteraction?.mode === "draft") {
const regions = c.draft?.kind === "regions", drawing = !!c.draft?.drawing;
if (page === "brushSettings") return result("Brush settings", [
item("draftSmooth", "Smoothing: " + (c.draft?.smoothingLabel || "Standard"), drawing),
...c.draft?.mode === "space3d" ? [item("brushNear", "Bring tip closer", drawing), item("brushFar", "Move tip farther", drawing)] : [],
item("draftClear", "Clear draft", drawing),
back
]);
return result(regions ? "Draw interaction regions" : c.draft?.mode === "space3d" ? "Spatial path · 3D" : "Ground path · 2D", [
item("draftApply", regions ? "Finish regions" : "Save curve", drawing || !(regions ? c.draft?.regionCount : c.draft?.pointCount)),
item("draftUndo", regions ? "Undo last region" : "Undo stroke", !c.draft?.canUndo || drawing),
...regions ? [item("regionSurface", c.draft?.forceFloor ? "Draw object sources" : "Draw floor trigger", drawing)] : [],
...!regions ? [item("brushSettings", "Brush settings")] : [],
item("draftCancel", "Cancel drawing")
], "Right trigger: draw · Release, then A Save · B Cancel");
}
if (p && (page === "global" || page === "main" && !c.objectSelection?.length)) return result("Edit mode", [
item("createTools", "Create"),
item("drawTools", "Sketches and paths"),
...c.roomMode && c.hasScene ? [item("roomTools", "Room / Alignment")] : [],
...c.script?.agentText || c.script?.userText ? [item("agentReply", "View Agent reply")] : [],
...c.suggestions?.length ? [item("suggestions", "Agent suggestions")] : [],
item("cinemaComplete", "Finish setup and explore"),
item("edit", "Return to explore")
], "Right stick: select · A Confirm · B / Y Hide menu");
if (p && page === "createTools") return result("Create", [item("createLight", "Light"), item("cinemaCamera", "Camera"), item("cinemaBox", "Object"), item("actorPlace", "Actor", !c.actorReady), item("cinemaCreateOther", "Other")], "Choose a category · B Back · Edit after placement");
if (p && page === "createLight") return result("Light type", [item("cinemaPoint", "Point · All directions"), item("cinemaSpot", "Spot · Directional"), back], "Choose a type, then a ground point · Type stays editable");
if (p && page === "lightSettings") return result("Light settings", [item("cinemaLightPoint", "Switch to point", c.selectedLightType === "point"), item("cinemaLightSpot", "Switch to spot", c.selectedLightType === "spot"), item("cinemaOther", "Voice: brightness and color"), back], "Review changes · A Save · B Revert");
if (p?.monitor && c.editing) return result(`Camera monitor · ${p.cameraName || ""}`, [item("cinemaAdjustCamera", "Adjust this camera"), item("cinemaNext", "Next camera"), item("cinemaClose", "Close monitor")], "B Close · After saving, say “preview camera”");
if (p && c.objectInteraction && c.selectedLightType && !["drawTools", "curveTools"].includes(page)) return result("Light · Interaction", [item("lightSettings", "Type and light settings"), item("cinemaBind", "Bind selected curve", !c.curveCount), item("cinemaPlay", "Rehearse this light", !p.canPreview), item("interactionClose", "Back")]);
if (p && c.objectInteraction && c.selectedCameraId && !["drawTools", "curveTools"].includes(page)) return result("Camera · Interaction", [item("cinemaPreview", "View this camera"), item("cinemaBind", "Bind selected curve", !c.curveCount), item("cinemaOther", "Voice: camera and movement"), item("interactionClose", "Back")]);
if (p && c.objectInteraction && page !== "drawTools" && page !== "curveTools") return result("Interaction", [
item("voice", "Describe an interaction"),
item("drawTools", "Draw a path or region"),
item("cinemaBind", "Bind selected curve", !c.curveCount),
item("cinemaPlay", "Rehearse selection · Left grip", !p.canPreview),
item("interactionClose", "Back")
]);
if (p && page === "main" && c.objectSelection?.length) return result(target, [item("cinemaTransform", "Transform"), item("objectInteraction", "Interaction"), item("cinemaOther", "Other")], "Right stick: select · A Confirm · Other starts voice");
if (page === "suggestions") return result("Agent suggestions", [
...(c.suggestions || []).map((s, i) => item("suggestion:" + i, s.label)),
back
], "Right stick: select · A Confirm · B Back");
if (page === "drawTools") return result("Spatial sketches", [
item("drawFloor", "Draw ground path · 2D"),
item("drawSpace", "Draw spatial path · 3D"),
...regionTarget ? [item("regionDraft", "Draw surface sources and trigger region")] : [],
...c.curveCount ? [item("curveTools", `Saved curves · ${c.curveCount}`)] : [],
back
]);
if (page === "curveTools") return result("Saved curves", [
item("curveNext", "Next curve · " + c.curveName, !c.curveCount),
...p ? [item("cinemaCurveEdit", "Pull control points", !c.curveCount), item("cinemaStraight", "Extreme · Straight endpoints", !c.curveCount), item("cinemaSmooth", "Smooth control points", !c.curveCount)] : [],
item("curveRemove", "Delete selected curve", !c.curveCount),
back
]);
if (page === "recordingTools") return result("Recording and export", [item("record", c.videoState === "recording" ? "Stop and save recording" : c.videoRetry ? "Retry recording save" : "Start recording", ["starting", "saving"].includes(c.videoState)), item("videoFiles", "Saved recordings"), back]);
if (page === "actorSettings") return result(target, [
item("objectTransform", "Move and rotate"),
item("actors", "Motion assets and entrance"),
item("actorClearMotion", "Clear motion"),
item("actorRemove", "Delete actor"),
item("sceneTools", "Scene tools"),
back
]);
if (page === "doorSettings") return result(target, [
item("doorToggle", c.door?.effect?.open ? "Close door" : "Open door"),
item("doorHinge", "Switch hinge side"),
item("doorDirection", "Change opening direction"),
item("objectTransform", "Move and rotate"),
item("sceneTools", "Scene tools"),
back
]);
if (page === "sceneTools") return result("Scene tools", [
item("actorPlace", "Add actor", !c.actorReady),
...c.actorCount ? [item("actorTransport", p?.playing ? "Pause rehearsal" : "Rehearse selection", p ? !p.canPreview : !c.actorMotionCount)] : [],
item("save", "Save scene"),
item("sceneMore", "Recording, display and more"),
back
]);
if (page === "sceneMore") return result("Scene settings", [
item("recordingTools", c.videoState === "recording" ? "Recording · Stop and save" : "Recording and export"),
item("display", "Display and alignment"),
item("overview", "Scene overview"),
item("authoringHelp", "Controls"),
item("selectionSettings", "Selection and materials"),
back
]);
if (c.suggestions?.length) return result("Agent suggestions", [
...c.suggestions.map((s, i) => item("suggestion:" + i, s.label)),
item("dismissSuggestions", "Dismiss suggestions")
], "Choose a suggestion to preview · Right stick: select · A Confirm");
if (c.actorSelected) return result(target, [item("actorAgent", "Generate a basic motion"), item("drawTools", "Draw a motion path"), item("actorPreview", "Rehearse this actor"), item("actorSettings", "Actor settings")]);
if (door) return result(target, [item("voice", "Describe an interaction"), item("regionDraft", "Draw surface sources and trigger region"), ...c.floodAvailable ? [item("floodPreview", "Rehearse interaction")] : [], item("doorSettings", "Door settings")]);
return result(target, [
voice,
item("drawTools", "Draw a spatial sketch"),
...c.objectSelection?.length ? [item("objectTransform", "Move and rotate")] : [item("edit", c.editing ? "Finish selection" : "Select objects")],
item("sceneTools", "Scene tools")
]);
}
function authoringParent(page) {
return { global: "global", main: "global", createTools: "global", sceneTools: "global", roomTools: "global", sceneTransparency: "roomTools", suggestions: "global", createLight: "createTools", lightSettings: "interaction", curveTools: "drawTools", sceneMore: "sceneTools", recordingTools: "sceneMore" }[page] || "main";
}
function createMenuNavigation() {
const states = /* @__PURE__ */ new WeakMap();
return { sample(source, active) {
const y = source.gamepad?.axes?.[3] ?? 0, old = states.get(source);
if (!active || source.handedness !== "right") {
states.set(source, { active: false, ready: false });
return 0;
}
const neutral = Math.abs(y) < 0.3;
if (!old?.active) {
states.set(source, { active: true, ready: neutral });
return 0;
}
if (neutral) {
old.ready = true;
return 0;
}
if (!old.ready || Math.abs(y) < 0.65) return 0;
old.ready = false;
return Math.sign(y);
} };
}
const suggestionScope = (c) => JSON.stringify([
c.revision,
c.spatialContext?.spatialKey || c.spatialKey || "",
[...c.targetIds || c.objectSelection || []].sort(),
c.curveId || null
]);
export {
AUTHORING_PAGES,
authoringMenu,
authoringParent,
createMenuNavigation,
suggestionScope
};
|