@dailies-studio/shot-editor 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.vite/manifest.json +382 -0
- package/dist/assets/AgentPanel-CQejLbiX.js +2 -0
- package/dist/assets/AgentPanel-DJEphah0.css +1 -0
- package/dist/assets/AgentQuestionDialog-BqkD0kXF.js +1 -0
- package/dist/assets/CodeStudio-Dv4ihHpz.js +23 -0
- package/dist/assets/CommentsPanel-B4yhhT1T.js +1 -0
- package/dist/assets/ComponentLibraryRail-Ch_dsAru.js +1 -0
- package/dist/assets/ComponentStage-BRhRotyH.js +1 -0
- package/dist/assets/CurvePane-BjFVabKX.css +1 -0
- package/dist/assets/CurvePane-R22WzllJ.js +1 -0
- package/dist/assets/ExportDialog-CWLQWTmG.js +2 -0
- package/dist/assets/FontPicker-DWKaYL7F.js +1 -0
- package/dist/assets/FontPicker-v0IOIArM.css +1 -0
- package/dist/assets/GenerateDrawer-Bh3SqDKt.js +2 -0
- package/dist/assets/InsertDrawer-Ce1fod1L.js +1 -0
- package/dist/assets/LibraryBrowser-D9sEGz5w.js +2 -0
- package/dist/assets/MakeItYoursSection-CMO4319_.js +2 -0
- package/dist/assets/MaterialGraphCanvas-BFzl4wWR.js +23 -0
- package/dist/assets/MaterialGraphCanvas-BIUD59ik.css +1 -0
- package/dist/assets/MaterialWorkspace-CqTWqaRA.js +14 -0
- package/dist/assets/PreferencesDialog-CBf9_OoD.js +1 -0
- package/dist/assets/PresetsSection-CUEaoCVU.js +1 -0
- package/dist/assets/SoundHistory-BeS8EW1h.js +1 -0
- package/dist/assets/SoundMode-BmcZru3i.js +3 -0
- package/dist/assets/StockSounds-DdAUGRYK.js +1 -0
- package/dist/assets/TemplateFieldSection-CaeXpLFo.js +1 -0
- package/dist/assets/VersionHistoryPanel-CxG6XhNP.js +1 -0
- package/dist/assets/board-B88EPuLO.css +1 -0
- package/dist/assets/board-CL-he7SL.js +3 -0
- package/dist/assets/components-0oOSPN0l.js +1 -0
- package/dist/assets/googleFontRanges-gp32wJMo.js +1 -0
- package/dist/assets/googleFonts-BNon6M7X.js +1 -0
- package/dist/assets/iconsAndAliases-DL7gXP5j.js +1863 -0
- package/dist/assets/index-4F5NQmI8.css +1 -0
- package/dist/assets/index-BGHQGV5z.js +117 -0
- package/dist/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
- package/dist/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
- package/dist/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
- package/dist/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
- package/dist/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
- package/dist/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
- package/dist/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
- package/dist/assets/libraryModel-CpIwfFha.js +1 -0
- package/dist/assets/lucide-6iK64tTB.js +9319 -0
- package/dist/assets/motionStamp-CH-Hew4E.js +1 -0
- package/dist/assets/takeMeasure-Tdg0TxEi.js +1 -0
- package/dist/assets/webmcp-BsB9gxWC.js +2 -0
- package/dist/index.html +34 -3
- package/package.json +12 -9
- package/src/addFamilies.ts +66 -0
- package/src/api.ts +1631 -55
- package/src/app/App.tsx +977 -198
- package/src/app/lazyPanel.tsx +137 -0
- package/src/app/lazySurfaces.tsx +203 -0
- package/src/audio/index.tsx +41 -10
- package/src/audio/meter.ts +485 -0
- package/src/audio/meterMath.ts +273 -0
- package/src/audio/meterStore.ts +155 -0
- package/src/audio/offlineMix.ts +289 -0
- package/src/audio/preview.ts +928 -118
- package/src/bridge.ts +15 -5
- package/src/bridgeTypes.ts +1611 -36
- package/src/channels.ts +17 -32
- package/src/fonts/googleFonts.ts +4 -0
- package/src/hostApi.ts +174 -0
- package/src/icons.tsx +770 -0
- package/src/layerWords.ts +61 -0
- package/src/main.tsx +56 -4
- package/src/panels/ResizeHandle.tsx +33 -43
- package/src/panels/agent/AgentDots.tsx +36 -0
- package/src/panels/agent/AgentPanel.tsx +119 -43
- package/src/panels/agent/AgentQuestionDialog.tsx +202 -0
- package/src/panels/agent/agent.css +113 -97
- package/src/panels/agent/agentGate.ts +35 -0
- package/src/panels/code/CodeDefaultsPanel.tsx +345 -0
- package/src/panels/code/CodeStudio.tsx +373 -0
- package/src/panels/code/FileTree.tsx +265 -0
- package/src/panels/code/codeBufferGuard.ts +52 -0
- package/src/panels/code/codeParts.tsx +321 -0
- package/src/panels/comments/CommentComposer.tsx +239 -0
- package/src/panels/comments/CommentThread.tsx +346 -0
- package/src/panels/comments/CommentsPanel.tsx +336 -0
- package/src/panels/comments/InboxButton.tsx +131 -0
- package/src/panels/comments/MentionMenu.tsx +98 -0
- package/src/panels/comments/parts.tsx +171 -0
- package/src/panels/export/ExportDialog.tsx +919 -96
- package/src/panels/export/ExportScrim.tsx +57 -0
- package/src/panels/files/AssetsTab.tsx +656 -0
- package/src/panels/files/FilePanel.tsx +1466 -216
- package/src/panels/files/LayerMarks.tsx +40 -0
- package/src/panels/files/LeftColumn.tsx +37 -0
- package/src/panels/files/McpSection.tsx +690 -47
- package/src/panels/files/MissingAssetsNotice.tsx +60 -0
- package/src/panels/files/assetDrag.ts +79 -0
- package/src/panels/files/resourceParts.tsx +255 -0
- package/src/panels/fonts/FontPicker.tsx +762 -0
- package/src/panels/fonts/faceCache.ts +207 -0
- package/src/panels/fonts/fontPicker.css +271 -0
- package/src/panels/fonts/pickerModel.ts +265 -0
- package/src/panels/fonts/pickerRequest.ts +46 -0
- package/src/panels/header/AccountAvatar.tsx +76 -0
- package/src/panels/header/AgentChip.tsx +77 -0
- package/src/panels/header/AppMenu.tsx +213 -0
- package/src/panels/header/Header.tsx +564 -84
- package/src/panels/header/HistoryControls.tsx +31 -5
- package/src/panels/header/PresenceStack.tsx +329 -0
- package/src/panels/header/ProjectTitle.tsx +232 -0
- package/src/panels/header/ShareButton.tsx +59 -0
- package/src/panels/header/StatusBadges.tsx +358 -27
- package/src/panels/header/SyncDeletedDialog.tsx +88 -0
- package/src/panels/header/ZoomControls.tsx +46 -15
- package/src/panels/header/agentHandoff.ts +52 -0
- package/src/panels/header/leave.ts +26 -0
- package/src/panels/help/HelpSection.tsx +189 -163
- package/src/panels/insert/InsertDrawer.tsx +723 -0
- package/src/panels/insert/iconSet.ts +109 -0
- package/src/panels/inspector/ExposeDot.tsx +117 -0
- package/src/panels/inspector/FramingRows.tsx +314 -44
- package/src/panels/inspector/InspectorFooter.tsx +91 -0
- package/src/panels/inspector/InspectorHead.tsx +425 -0
- package/src/panels/inspector/InspectorPanel.tsx +408 -124
- package/src/panels/inspector/KeyframeDot.tsx +373 -0
- package/src/panels/inspector/KeyframeSection.tsx +257 -0
- package/src/panels/inspector/MarkerSection.tsx +55 -11
- package/src/panels/inspector/ParamRow.tsx +84 -53
- package/src/panels/inspector/RightColumn.tsx +152 -0
- package/src/panels/inspector/TimeSection.tsx +866 -0
- package/src/panels/inspector/animate/EaseControl.tsx +244 -0
- package/src/panels/inspector/animate/PresetsSection.tsx +135 -0
- package/src/panels/inspector/animate/ShotSection.tsx +562 -48
- package/src/panels/inspector/design/AlignRow.tsx +95 -31
- package/src/panels/inspector/design/AppearanceSection.tsx +118 -9
- package/src/panels/inspector/design/ClonerSection.tsx +323 -62
- package/src/panels/inspector/design/ComponentSection.tsx +50 -43
- package/src/panels/inspector/design/CornerSection.tsx +45 -14
- package/src/panels/inspector/design/DefinitionProps.tsx +315 -0
- package/src/panels/inspector/design/DefinitionSection.tsx +136 -0
- package/src/panels/inspector/design/DeviceSection.tsx +210 -0
- package/src/panels/inspector/design/FillSection.tsx +83 -51
- package/src/panels/inspector/design/FontField.tsx +196 -0
- package/src/panels/inspector/design/HtmlSection.tsx +23 -12
- package/src/panels/inspector/design/InstanceSection.tsx +373 -154
- package/src/panels/inspector/design/MaskSection.tsx +84 -0
- package/src/panels/inspector/design/MaterialSection.tsx +177 -0
- package/src/panels/inspector/design/MediaSection.tsx +273 -120
- package/src/panels/inspector/design/MeshSection.tsx +910 -82
- package/src/panels/inspector/design/PaintRows.tsx +550 -0
- package/src/panels/inspector/design/PlateStyleSection.tsx +177 -0
- package/src/panels/inspector/design/ShadowSection.tsx +221 -48
- package/src/panels/inspector/design/SplineFollowSection.tsx +235 -0
- package/src/panels/inspector/design/StrokeSection.tsx +228 -43
- package/src/panels/inspector/design/TemplateFieldSection.tsx +57 -0
- package/src/panels/inspector/design/TransformSection.tsx +244 -50
- package/src/panels/inspector/design/TypographySection.tsx +679 -110
- package/src/panels/inspector/layout.ts +295 -0
- package/src/panels/inspector/propValue.ts +199 -0
- package/src/panels/inspector/scene/CameraSection.tsx +232 -22
- package/src/panels/inspector/scene/EffectsSection.tsx +722 -71
- package/src/panels/inspector/scene/MakeItYoursSection.tsx +180 -0
- package/src/panels/inspector/scene/SceneDocSection.tsx +191 -0
- package/src/panels/inspector/scene/SceneSection.tsx +219 -44
- package/src/panels/inspector/scene/TransitionSection.tsx +523 -72
- package/src/panels/inspector/storyboard/StoryboardReferenceSection.tsx +593 -0
- package/src/panels/inspector/storyboard/VideoStill.tsx +120 -0
- package/src/panels/inspector/timeModel.ts +190 -0
- package/src/panels/inspector/useDocWrite.ts +438 -146
- package/src/panels/library/LibraryBrowser.tsx +435 -0
- package/src/panels/library/LibraryGrid.tsx +190 -0
- package/src/panels/library/LibraryPreviewPanel.tsx +349 -0
- package/src/panels/library/LibraryRail.tsx +84 -0
- package/src/panels/library/LibrarySoundPanel.tsx +293 -0
- package/src/panels/library/SoundHistory.tsx +245 -0
- package/src/panels/library/StockSounds.tsx +390 -0
- package/src/panels/library/libraryModel.ts +147 -0
- package/src/panels/library/stockSearch.ts +77 -0
- package/src/panels/maskMarks.tsx +117 -0
- package/src/panels/material/MaterialCommentNode.tsx +54 -0
- package/src/panels/material/MaterialGraphCanvas.tsx +861 -0
- package/src/panels/material/MaterialGraphMenu.tsx +45 -0
- package/src/panels/material/MaterialGroupNode.tsx +56 -0
- package/src/panels/material/MaterialLibrary.tsx +253 -0
- package/src/panels/material/MaterialNode.tsx +453 -0
- package/src/panels/material/MaterialNodePalette.tsx +349 -0
- package/src/panels/material/MaterialParameterControls.tsx +525 -0
- package/src/panels/material/MaterialRerouteNode.tsx +42 -0
- package/src/panels/material/MaterialWorkspace.tsx +501 -0
- package/src/panels/material/NodePreview.tsx +70 -0
- package/src/panels/material/flowModel.ts +441 -0
- package/src/panels/material/parameterValues.ts +231 -0
- package/src/panels/material/previewScheduler.ts +235 -0
- package/src/panels/material/reactFlow.css +6 -0
- package/src/panels/material/socketGlyph.ts +85 -0
- package/src/panels/material/viewportMemory.ts +58 -0
- package/src/panels/preferences/PreferencesDialog.tsx +215 -0
- package/src/panels/share/ShareComponentDialog.tsx +247 -0
- package/src/panels/stage/StageMenu.tsx +35 -5
- package/src/panels/stage/StagePanel.tsx +338 -23
- package/src/panels/stage/cellSlots.tsx +70 -0
- package/src/panels/storyboard/BoardCanvas.tsx +739 -0
- package/src/panels/storyboard/BoardCursors.tsx +158 -0
- package/src/panels/storyboard/BoardDropDialog.tsx +161 -0
- package/src/panels/storyboard/BoardPlan.tsx +42 -0
- package/src/panels/storyboard/ComponentSheet.tsx +187 -0
- package/src/panels/storyboard/ComponentStage.tsx +235 -0
- package/src/panels/storyboard/MiniSketch.tsx +173 -0
- package/src/panels/storyboard/PartPicker.tsx +64 -0
- package/src/panels/storyboard/PlanPanel.tsx +617 -0
- package/src/panels/storyboard/ReferencePreview.tsx +217 -0
- package/src/panels/storyboard/ShotBrief.tsx +360 -0
- package/src/panels/storyboard/ShotCard.tsx +251 -0
- package/src/panels/storyboard/StagePreview.tsx +348 -0
- package/src/panels/storyboard/StillThumbs.tsx +162 -0
- package/src/panels/storyboard/StoryboardView.tsx +79 -0
- package/src/panels/storyboard/board.css +1065 -0
- package/src/panels/storyboard/board.ts +12 -0
- package/src/panels/storyboard/boardClipboard.ts +111 -0
- package/src/panels/storyboard/boardOrder.ts +68 -0
- package/src/panels/storyboard/boardRows.tsx +425 -0
- package/src/panels/storyboard/liveCap.ts +90 -0
- package/src/panels/storyboard/plan.css +295 -0
- package/src/panels/storyboard/posterCache.ts +133 -0
- package/src/panels/storyboard/shotboard/ComponentPicker.tsx +373 -0
- package/src/panels/storyboard/shotboard/ImagePicker.tsx +145 -0
- package/src/panels/storyboard/shotboard/ShotBoard.tsx +292 -0
- package/src/panels/storyboard/shotboard/SketchCanvas.tsx +443 -0
- package/src/panels/storyboard/shotboard/SketchInspector.tsx +471 -0
- package/src/panels/storyboard/shotboard/SketchItem.tsx +240 -0
- package/src/panels/storyboard/shotboard/SketchLayers.tsx +130 -0
- package/src/panels/storyboard/shotboard/SketchPlay.tsx +108 -0
- package/src/panels/storyboard/shotboard/SketchStrip.tsx +178 -0
- package/src/panels/storyboard/shotboard/SketchToolFooter.tsx +99 -0
- package/src/panels/storyboard/shotboard/sketch.css +1392 -0
- package/src/panels/storyboard/shotboard/sketchModel.ts +219 -0
- package/src/panels/storyboard/storyboardDrop.ts +164 -0
- package/src/panels/studio/ComponentLibraryRail.tsx +346 -0
- package/src/panels/studio/ComponentSidePanel.tsx +355 -0
- package/src/panels/studio/ComponentStudio.tsx +243 -0
- package/src/panels/studio/ComponentsHome.tsx +318 -0
- package/src/panels/studio/ComponentsPreview.tsx +179 -0
- package/src/panels/studio/EveryUse.tsx +244 -0
- package/src/panels/studio/ThisUsePanel.tsx +679 -0
- package/src/panels/studio/componentParts.tsx +341 -0
- package/src/panels/studio/components.ts +6 -0
- package/src/panels/studio/newCodeComponent.ts +66 -0
- package/src/panels/timeline/AudioFxButton.tsx +290 -0
- package/src/panels/timeline/AudioLane.tsx +674 -85
- package/src/panels/timeline/BlockRow.tsx +154 -85
- package/src/panels/timeline/ChannelRow.tsx +378 -129
- package/src/panels/timeline/CurvePane.tsx +508 -0
- package/src/panels/timeline/Filmstrip.tsx +361 -0
- package/src/panels/timeline/GenerateDrawer.tsx +407 -0
- package/src/panels/timeline/KeyStretch.tsx +115 -0
- package/src/panels/timeline/LifespanRow.tsx +730 -47
- package/src/panels/timeline/MarkerLane.tsx +46 -8
- package/src/panels/timeline/PlayButton.tsx +91 -0
- package/src/panels/timeline/PropertyPicker.tsx +254 -0
- package/src/panels/timeline/ShotRow.tsx +400 -0
- package/src/panels/timeline/SoundMode.tsx +1406 -0
- package/src/panels/timeline/TimelineDock.tsx +2338 -272
- package/src/panels/timeline/TransportVolume.tsx +163 -0
- package/src/panels/timeline/beatRuler.ts +74 -0
- package/src/panels/timeline/curveModel.ts +500 -0
- package/src/panels/timeline/duckUnderVoice.ts +68 -0
- package/src/panels/timeline/keyframeMenu.ts +138 -0
- package/src/panels/timeline/labelWidth.ts +49 -0
- package/src/panels/timeline/marquee.ts +155 -0
- package/src/panels/timeline/model.ts +1860 -199
- package/src/panels/timeline/snap.ts +125 -43
- package/src/panels/timeline/staggerPreview.tsx +92 -0
- package/src/panels/timeline/takeMeasure.ts +72 -0
- package/src/panels/timeline/useLive.ts +48 -0
- package/src/panels/timeline/viewport.ts +234 -0
- package/src/panels/toolbar/BackChip.tsx +116 -0
- package/src/panels/toolbar/CameraViewBar.tsx +118 -49
- package/src/panels/toolbar/CanvasBar.tsx +117 -0
- package/src/panels/toolbar/InsertButton.tsx +38 -0
- package/src/panels/toolbar/IsolationTrail.tsx +156 -0
- package/src/panels/toolbar/PanelToggles.tsx +86 -0
- package/src/panels/toolbar/ShadingBar.tsx +119 -26
- package/src/panels/toolbar/ToolDockBar.tsx +329 -80
- package/src/panels/toolbar/ToolFooter.tsx +102 -0
- package/src/panels/toolbar/ViewModeBar.tsx +247 -38
- package/src/panels/toolbar/WorkPlaneBar.tsx +94 -0
- package/src/panels/toolbar/useAddRows.tsx +326 -0
- package/src/panels/toolbar/useDismiss.ts +122 -0
- package/src/panels/versions/VersionHistoryPanel.tsx +274 -0
- package/src/panels/versions/versionModel.ts +103 -0
- package/src/panels/virtualWindow.ts +271 -0
- package/src/shotTokens.css +151 -60
- package/src/stage/FrustumGizmo.tsx +504 -84
- package/src/stage/GridPlane.tsx +225 -16
- package/src/stage/NodePreviews.tsx +628 -0
- package/src/stage/PerfHud.tsx +437 -47
- package/src/stage/SplitRenderer.tsx +816 -169
- package/src/stage/StageApp.tsx +4274 -486
- package/src/stage/StageRootBoundary.tsx +114 -0
- package/src/stage/StageTools.tsx +651 -151
- package/src/stage/TransformGizmo.tsx +696 -0
- package/src/stage/adaptivePresentation.ts +145 -0
- package/src/stage/bridgeClient.ts +2522 -41
- package/src/stage/exportWorker.ts +9 -0
- package/src/stage/feed.ts +305 -14
- package/src/stage/fontPreview.ts +176 -0
- package/src/stage/frustumGeometry.ts +319 -1
- package/src/stage/furnitureTokens.ts +33 -0
- package/src/stage/gizmoMath.ts +508 -16
- package/src/stage/gpuTimer.ts +276 -0
- package/src/stage/instanceModules.ts +194 -33
- package/src/stage/interactions/move.ts +24 -5
- package/src/stage/isolation.ts +291 -33
- package/src/stage/jsSelfProfiler.ts +325 -0
- package/src/stage/main.tsx +116 -16
- package/src/stage/modalState.ts +222 -23
- package/src/stage/moduleEpoch.ts +60 -0
- package/src/stage/motionPathStage.ts +279 -0
- package/src/stage/motionPreview.ts +74 -0
- package/src/stage/overlays/Overlays.tsx +1112 -159
- package/src/stage/overlays/Rulers.tsx +495 -0
- package/src/stage/panes.ts +159 -10
- package/src/stage/project.ts +68 -3
- package/src/stage/renderScaleController.ts +730 -0
- package/src/stage/rulers.ts +129 -0
- package/src/stage/sceneExtent.ts +75 -0
- package/src/stage/shading.ts +20 -0
- package/src/stage/splineStage.ts +632 -0
- package/src/stage/stagePerformanceTelemetry.ts +717 -0
- package/src/stage/stageTheme.ts +36 -0
- package/src/stage/stageWarmup.ts +11 -0
- package/src/stage/textAutoSize.ts +124 -0
- package/src/stage/tier1Export.ts +641 -0
- package/src/stage/tokenColor.ts +14 -9
- package/src/stage/view.ts +56 -9
- package/src/stage/workingView.ts +277 -33
- package/src/state/accountPrefs.ts +261 -0
- package/src/state/agentActions.ts +173 -15
- package/src/state/agentActivity.ts +198 -0
- package/src/state/agentQuestion.ts +239 -0
- package/src/state/bootOpen.ts +42 -0
- package/src/state/cameraVerbs.ts +139 -0
- package/src/state/clientId.ts +6 -1
- package/src/state/comments.ts +1099 -0
- package/src/state/context.tsx +19 -1
- package/src/state/editorPrefs.ts +220 -0
- package/src/state/errorReports.ts +121 -0
- package/src/state/fieldWrite.ts +161 -0
- package/src/state/fontRecents.ts +70 -0
- package/src/state/fonts.ts +656 -0
- package/src/state/graphClipboard.ts +414 -0
- package/src/state/imageThumbs.ts +216 -0
- package/src/state/insert.ts +444 -67
- package/src/state/isolationTrail.ts +92 -0
- package/src/state/keyMoves.ts +134 -0
- package/src/state/keyframeClipboard.ts +177 -0
- package/src/state/keymap.ts +1153 -117
- package/src/state/layoutPrefs.ts +272 -0
- package/src/state/layouts.ts +205 -0
- package/src/state/measureMedia.ts +167 -0
- package/src/state/menu.ts +2341 -28
- package/src/state/motionStamp.ts +7 -0
- package/src/state/panelSize.ts +375 -5
- package/src/state/pendingChanges.ts +119 -0
- package/src/state/playHold.ts +78 -0
- package/src/state/playbackPreparation.ts +562 -0
- package/src/state/posterCapture.ts +242 -0
- package/src/state/presence.ts +254 -0
- package/src/state/projectComponents.ts +160 -0
- package/src/state/renderEvents.ts +298 -18
- package/src/state/runtimeErrors.ts +46 -0
- package/src/state/selection.ts +12 -0
- package/src/state/shortcuts.ts +1606 -0
- package/src/state/sketchGuide.ts +51 -0
- package/src/state/splineRefs.ts +87 -0
- package/src/state/stageHealth.ts +259 -0
- package/src/state/stageWatchdog.ts +47 -0
- package/src/state/stillStage.ts +330 -0
- package/src/state/stillThumbs.ts +474 -0
- package/src/state/store.ts +23591 -2982
- package/src/state/storyboardModel.ts +528 -0
- package/src/state/sync.ts +1257 -0
- package/src/state/syncCache.ts +159 -0
- package/src/state/triangles.ts +13 -0
- package/src/state/types.ts +1698 -55
- package/src/state/unsavedGuard.ts +49 -0
- package/src/state/viewMode.ts +200 -0
- package/src/state/webmcp.ts +1128 -0
- package/src/state/workspace.ts +1147 -0
- package/src/styles/assets-tab.css +304 -0
- package/src/styles/canvas.css +545 -0
- package/src/styles/comments.css +1435 -0
- package/src/styles/controls.css +162 -0
- package/src/styles/dialogs.css +390 -0
- package/src/styles/insert-drawer.css +586 -0
- package/src/styles/inspector-board.css +207 -0
- package/src/styles/inspector-object.css +486 -0
- package/src/styles/inspector-shot.css +252 -0
- package/src/styles/inspector.css +360 -0
- package/src/styles/left-column.css +1628 -0
- package/src/styles/library-preview.css +575 -0
- package/src/styles/library.css +512 -0
- package/src/styles/modes/code.css +569 -0
- package/src/styles/modes/component.css +1561 -0
- package/src/styles/modes/material.css +1201 -0
- package/src/styles/modes/storyboard.css +518 -0
- package/src/styles/share-component.css +49 -0
- package/src/styles/shell.css +653 -0
- package/src/styles/shot-brief.css +360 -0
- package/src/styles/states.css +76 -0
- package/src/styles/sync.css +380 -0
- package/src/styles/timeline-graph.css +384 -0
- package/src/styles/timeline-meters.css +89 -0
- package/src/styles/timeline-rows.css +1959 -0
- package/src/styles/timeline-sound.css +1003 -0
- package/src/styles/timeline.css +754 -0
- package/src/styles/tool-footer.css +525 -0
- package/src/styles/topbar.css +886 -0
- package/src/styles/versions.css +262 -0
- package/src/styles.css +72 -2921
- package/src/tools.ts +108 -23
- package/dist/assets/index-B7notge7.css +0 -1
- package/dist/assets/index-BBE6Ta9L.js +0 -52
- package/src/panels/files/icons.tsx +0 -118
- package/src/panels/header/DocSwitcher.tsx +0 -85
- package/src/panels/header/TransportBar.tsx +0 -86
- package/src/panels/inspector/animate/BlockTiming.tsx +0 -341
- package/src/panels/inspector/animate/DuringBlocks.tsx +0 -85
- package/src/panels/inspector/animate/InOutPresets.tsx +0 -57
- package/src/panels/inspector/animate/ResolvedChannels.tsx +0 -150
- package/src/panels/inspector/animate/useBlocks.ts +0 -43
- package/src/panels/material/MaterialPicker.tsx +0 -172
- package/src/panels/timeline/SceneStrip.tsx +0 -145
- package/src/panels/toolbar/AddMenu.tsx +0 -230
- package/src/stage/MoveGizmo.tsx +0 -411
|
@@ -14,7 +14,22 @@
|
|
|
14
14
|
// it and layer picking cannot return it.
|
|
15
15
|
//
|
|
16
16
|
// It also touches no document. Everything it draws is a pure function of the
|
|
17
|
-
// resolved rig, which is a pure function
|
|
17
|
+
// resolved rig (or of the camera a component drove), which is a pure function
|
|
18
|
+
// of (document, frame).
|
|
19
|
+
//
|
|
20
|
+
// ── TWO BUFFERS: THE BODY AND THE REACH ─────────────────────────────────
|
|
21
|
+
//
|
|
22
|
+
// The BODY is the camera as an object: a short lens pyramid in front of the eye,
|
|
23
|
+
// a box behind it, the up-marker on the lens. Its size is fixed against the
|
|
24
|
+
// SCENE (frustumGeometry's `cameraDisplayLength`), clamped in screen px, so it
|
|
25
|
+
// reads as one more object in the world rather than a thing that grows as the
|
|
26
|
+
// author backs away. It is drawn for the rig and for a driven camera alike.
|
|
27
|
+
//
|
|
28
|
+
// The REACH is the document's model: the four long edges from the eye to the
|
|
29
|
+
// framed rectangle at the design plane, and that rectangle. It is what is in
|
|
30
|
+
// shot, from any angle, and the rig case alone has it (a driven camera has no
|
|
31
|
+
// plane to land on). Dimmer than the body, so the body reads as the camera and
|
|
32
|
+
// the reach as its sightline.
|
|
18
33
|
//
|
|
19
34
|
// ── WHY THE MATERIAL REFUSES THE OVERRIDE ───────────────────────────────
|
|
20
35
|
//
|
|
@@ -25,47 +40,42 @@
|
|
|
25
40
|
//
|
|
26
41
|
// ── AND WHY THE COLOUR IS READ FROM CSS ─────────────────────────────────
|
|
27
42
|
//
|
|
28
|
-
// It is a MATERIAL colour, not a GL clear colour, and it is resolved
|
|
29
|
-
//
|
|
30
|
-
// stays the single definition both documents share, and the
|
|
31
|
-
// the same yellow tomorrow without a second literal.
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
import {
|
|
35
|
-
import {
|
|
36
|
-
import {
|
|
37
|
-
BufferAttribute,
|
|
38
|
-
BufferGeometry,
|
|
39
|
-
LineSegments,
|
|
40
|
-
Vector3,
|
|
41
|
-
} from "three";
|
|
43
|
+
// It is a MATERIAL colour, not a GL clear colour, and it is resolved inside the
|
|
44
|
+
// stage document, which imports shotTokens.css, whenever appearance changes.
|
|
45
|
+
// So `--frustum` stays the single definition both documents share, and the
|
|
46
|
+
// overlay could draw the same yellow tomorrow without a second literal.
|
|
47
|
+
import { useEffect, useMemo, useRef } from "react";
|
|
48
|
+
import { useFrame, useThree } from "@react-three/fiber";
|
|
49
|
+
import { Box3, BufferAttribute, BufferGeometry, LineSegments, Vector3 } from "three";
|
|
50
|
+
import type { Quaternion } from "three";
|
|
42
51
|
import { LineBasicNodeMaterial } from "three/webgpu";
|
|
43
52
|
import { EDITOR_LAYER } from "./StageTools.js";
|
|
44
53
|
import { tokenColor } from "./tokenColor.js";
|
|
54
|
+
import type { StageTheme } from "./stageTheme.js";
|
|
45
55
|
import {
|
|
46
|
-
|
|
56
|
+
BODY_GRAB_PX,
|
|
57
|
+
CAMERA_BODY_SEGMENT_FLOATS,
|
|
58
|
+
FRAME_SEGMENT_FLOATS,
|
|
47
59
|
borderDistance,
|
|
48
|
-
|
|
60
|
+
cameraBodySegments,
|
|
61
|
+
cameraBodyShape,
|
|
62
|
+
cameraDisplayLength,
|
|
63
|
+
frameSegments,
|
|
49
64
|
frustumShape,
|
|
65
|
+
isDriven,
|
|
66
|
+
rigPose,
|
|
50
67
|
segmentsMoved,
|
|
68
|
+
waypointCrossSize,
|
|
69
|
+
waypointSegmentFloats,
|
|
70
|
+
waypointSegments,
|
|
51
71
|
} from "./frustumGeometry.js";
|
|
52
|
-
import type { CameraRig } from "./frustumGeometry.js";
|
|
53
|
-
import { toScreen } from "./project.js";
|
|
72
|
+
import type { CameraPose, CameraRig } from "./frustumGeometry.js";
|
|
73
|
+
import { toScreen, worldPerPixel } from "./project.js";
|
|
74
|
+
import { sceneExtent } from "./sceneExtent.js";
|
|
54
75
|
import type { StageFeed } from "./feed.js";
|
|
55
76
|
import type { PaneRect } from "./panes.js";
|
|
56
77
|
import type { FrameSize } from "./view.js";
|
|
57
78
|
|
|
58
|
-
/** How close a press has to land to the apex to grab it, in CSS px. Larger
|
|
59
|
-
* than the layer handles' 10: the apex is a single point with no box around
|
|
60
|
-
* it, and it is the only way to reach the zoom. */
|
|
61
|
-
export const APEX_GRAB_PX = 14;
|
|
62
|
-
|
|
63
|
-
/** How close a press has to land to the base rectangle's DRAWN border to grab
|
|
64
|
-
* the body. The interior is deliberately not a grab target — see
|
|
65
|
-
* `borderDistance` for why an interior claim takes the whole pane away — so
|
|
66
|
-
* the border is a line, and it gets the same slop the apex point does. */
|
|
67
|
-
const BODY_GRAB_PX = 14;
|
|
68
|
-
|
|
69
79
|
/** Over everything. Every material in this lane draws with `depthTest: false`,
|
|
70
80
|
* so paint order is `renderOrder` alone (ShotRuntime's header) — a gizmo that
|
|
71
81
|
* shared the layers' range would be hidden by whichever layer sorted after it. */
|
|
@@ -73,112 +83,522 @@ const FRUSTUM_RENDER_ORDER = 1_000_000;
|
|
|
73
83
|
|
|
74
84
|
const FRUSTUM_FALLBACK = "#eab308";
|
|
75
85
|
|
|
86
|
+
/** How far the reach sits below the body: the sightline reads as belonging to
|
|
87
|
+
* the camera without competing with it. */
|
|
88
|
+
const REACH_OPACITY = 0.55;
|
|
89
|
+
|
|
90
|
+
/** How far a hovered gizmo blends towards the theme's contrast ink — the move
|
|
91
|
+
* arms' own tint, so the two widgets in this pane light up by the same amount
|
|
92
|
+
* and neither reads as the more active one. */
|
|
93
|
+
const HOVER_TINT = 0.45;
|
|
94
|
+
|
|
95
|
+
/** How many committed frames a driven scene's bounds are trusted for before
|
|
96
|
+
* the walk runs again. A flythrough moves objects, but the ground and the
|
|
97
|
+
* architecture bound the world; a change between refreshes is a sub-pixel
|
|
98
|
+
* pop in the body's size, not a wrong drawing. */
|
|
99
|
+
const EXTENT_REFRESH_FRAMES = 60;
|
|
100
|
+
|
|
101
|
+
/** The lens rectangle stays in front of the design plane: this far along the
|
|
102
|
+
* rig's own distance at most. */
|
|
103
|
+
const REACH_CAP_FRACTION = 0.9;
|
|
104
|
+
|
|
76
105
|
/** Scratch for the border test's behind-camera check. */
|
|
77
106
|
const _view = new Vector3();
|
|
78
107
|
|
|
108
|
+
/**
|
|
109
|
+
* AFTER EVERY CAMERA WRITER. `ShotCamera` puts the render camera on the rig at
|
|
110
|
+
* priority 0, and an instance component that drives the camera itself writes
|
|
111
|
+
* below that (fiber sorts a phase's priorities DESCENDING, so lower runs
|
|
112
|
+
* later). The gizmo reads the camera after all of them, so it draws the
|
|
113
|
+
* camera the pane is about to render with and not the one the rig proposed.
|
|
114
|
+
*/
|
|
115
|
+
const AFTER_CAMERA_WRITERS = -1000;
|
|
116
|
+
|
|
117
|
+
/** Scratch for reading the render camera's basis. */
|
|
118
|
+
const _eye = new Vector3();
|
|
119
|
+
const _right = new Vector3();
|
|
120
|
+
const _up = new Vector3();
|
|
121
|
+
const _forward = new Vector3();
|
|
122
|
+
const _pose: CameraPose = { eye: [0, 0, 0], right: [1, 0, 0], up: [0, 1, 0], forward: [0, 0, -1], fovDeg: 35 };
|
|
123
|
+
/** Scratch for the bounds walk, and its centre. */
|
|
124
|
+
const _bounds = new Box3();
|
|
125
|
+
const _centre = new Vector3();
|
|
126
|
+
|
|
127
|
+
/** The render camera's pose, or null when it is not a perspective camera. */
|
|
128
|
+
function readPose(camera: object): CameraPose | null {
|
|
129
|
+
const cam = camera as {
|
|
130
|
+
isPerspectiveCamera?: boolean;
|
|
131
|
+
fov: number;
|
|
132
|
+
position: Vector3;
|
|
133
|
+
quaternion: Quaternion;
|
|
134
|
+
};
|
|
135
|
+
if (cam.isPerspectiveCamera !== true) return null;
|
|
136
|
+
_eye.copy(cam.position);
|
|
137
|
+
_right.set(1, 0, 0).applyQuaternion(cam.quaternion);
|
|
138
|
+
_up.set(0, 1, 0).applyQuaternion(cam.quaternion);
|
|
139
|
+
_forward.set(0, 0, -1).applyQuaternion(cam.quaternion);
|
|
140
|
+
_pose.eye = [_eye.x, _eye.y, _eye.z];
|
|
141
|
+
_pose.right = [_right.x, _right.y, _right.z];
|
|
142
|
+
_pose.up = [_up.x, _up.y, _up.z];
|
|
143
|
+
_pose.forward = [_forward.x, _forward.y, _forward.z];
|
|
144
|
+
_pose.fovDeg = cam.fov;
|
|
145
|
+
return _pose;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** How close a press has to land to a waypoint's centre to grab it, in CSS px.
|
|
149
|
+
* A point with no box around it gets the body's own slop. */
|
|
150
|
+
export const WAYPOINT_GRAB_PX = 14;
|
|
151
|
+
|
|
152
|
+
/** The eyes of the path move under the playhead, if there is one. */
|
|
153
|
+
export interface WaypointMarks {
|
|
154
|
+
shotId: string;
|
|
155
|
+
eyes: readonly [number, number, number][];
|
|
156
|
+
}
|
|
157
|
+
|
|
79
158
|
export interface FrustumGizmoProps {
|
|
159
|
+
/** Resolved editor theme. It rebuilds only the token-backed GL material. */
|
|
160
|
+
theme: StageTheme;
|
|
80
161
|
feed: StageFeed;
|
|
81
162
|
panes: { working: PaneRect | null; camera: PaneRect | null };
|
|
82
|
-
/** The camera
|
|
83
|
-
|
|
163
|
+
/** The camera rig at the clock's frame, read at FRAME time (StageApp's
|
|
164
|
+
* `playhead`), or null when nothing is on screen. */
|
|
165
|
+
rigAt: () => CameraRig | null;
|
|
84
166
|
frame: FrameSize;
|
|
167
|
+
/**
|
|
168
|
+
* The waypoints to draw and to make grabbable, or null.
|
|
169
|
+
*
|
|
170
|
+
* Null whenever the playhead is not inside a PATH move — the same rule the
|
|
171
|
+
* frustum follows about which camera it draws. Computed by the host because
|
|
172
|
+
* it holds the document; this component only ever draws what it is handed.
|
|
173
|
+
*/
|
|
174
|
+
waypointsAt: () => WaypointMarks | null;
|
|
175
|
+
/**
|
|
176
|
+
* The component that has CLAIMED the render camera (the runtime's
|
|
177
|
+
* `useClaimStageCamera`), read at frame time, or null. A claim beats the
|
|
178
|
+
* float comparison `offTheRig` makes, and it brings the component's own
|
|
179
|
+
* extent for the body's size.
|
|
180
|
+
*/
|
|
181
|
+
driverAt: () => { name: string; extent?: number } | null;
|
|
182
|
+
/**
|
|
183
|
+
* Who drives the camera, pushed ON CHANGE: the claim's name, "a component"
|
|
184
|
+
* for an unclaimed writer the float comparison caught, or null for the rig.
|
|
185
|
+
* The stage relays it to the chrome (`onCameraDriver`).
|
|
186
|
+
*/
|
|
187
|
+
onDriver: (name: string | null) => void;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/** One line buffer: allocated once, rewritten in place. `rest` is its opacity
|
|
191
|
+
* when nothing holds the camera; a held camera draws at a fraction of it. */
|
|
192
|
+
interface Lines {
|
|
193
|
+
lines: LineSegments;
|
|
194
|
+
geometry: BufferGeometry;
|
|
195
|
+
material: LineBasicNodeMaterial;
|
|
196
|
+
tint: number;
|
|
197
|
+
rest: number;
|
|
198
|
+
opacity: number;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/** How far a HELD camera fades: the fork would refuse a drag on it (a finished
|
|
202
|
+
* move with Animate off, a path), and the dimming says so before the press. */
|
|
203
|
+
const HELD_OPACITY = 0.4;
|
|
204
|
+
|
|
205
|
+
function newLines(floats: number, opacity: number): Lines {
|
|
206
|
+
const geometry = new BufferGeometry();
|
|
207
|
+
geometry.setAttribute("position", new BufferAttribute(new Float32Array(floats), 3));
|
|
208
|
+
const material = new LineBasicNodeMaterial();
|
|
209
|
+
material.color.copy(tokenColor("--frustum", FRUSTUM_FALLBACK));
|
|
210
|
+
material.depthTest = false;
|
|
211
|
+
material.depthWrite = false;
|
|
212
|
+
material.toneMapped = false;
|
|
213
|
+
// See the header: the working pane renders under a clay override.
|
|
214
|
+
material.allowOverride = false;
|
|
215
|
+
// Always transparent, so the held fade is one `opacity` write and not a
|
|
216
|
+
// material recompile the first time the playhead crosses into a move.
|
|
217
|
+
material.transparent = true;
|
|
218
|
+
material.opacity = opacity;
|
|
219
|
+
const lines = new LineSegments(geometry, material);
|
|
220
|
+
lines.layers.set(EDITOR_LAYER);
|
|
221
|
+
lines.renderOrder = FRUSTUM_RENDER_ORDER;
|
|
222
|
+
// The vertices are WORLD positions rewritten in place, so the object stays
|
|
223
|
+
// at the origin and the bounding sphere the culler would test is never the
|
|
224
|
+
// one the buffer holds. Culling it against a stale sphere makes the gizmo
|
|
225
|
+
// vanish at some orbits and not others.
|
|
226
|
+
lines.frustumCulled = false;
|
|
227
|
+
lines.matrixAutoUpdate = false;
|
|
228
|
+
return { lines, geometry, material, tint: 0, rest: opacity, opacity };
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function fadeTo(target: Lines, held: boolean): void {
|
|
232
|
+
const want = held ? target.rest * HELD_OPACITY : target.rest;
|
|
233
|
+
if (target.opacity === want) return;
|
|
234
|
+
target.opacity = want;
|
|
235
|
+
target.material.opacity = want;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/** Rewrite a buffer only when a vertex moved by more than `epsilon`: this runs
|
|
239
|
+
* on every committed frame, and a parked playhead must not re-upload. */
|
|
240
|
+
function write(target: Lines, next: number[], epsilon: number): void {
|
|
241
|
+
const attribute = target.geometry.getAttribute("position") as BufferAttribute;
|
|
242
|
+
const buffer = attribute.array as Float32Array;
|
|
243
|
+
if (segmentsMoved(buffer, next, epsilon)) {
|
|
244
|
+
buffer.set(next);
|
|
245
|
+
attribute.needsUpdate = true;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function tintTo(target: Lines, want: number, hot: ReturnType<typeof tokenColor>): void {
|
|
250
|
+
if (target.tint === want) return;
|
|
251
|
+
target.tint = want;
|
|
252
|
+
target.material.color.copy(tokenColor("--frustum", FRUSTUM_FALLBACK)).lerp(hot, want);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function newPoints(n: number): Vector3[] {
|
|
256
|
+
return Array.from({ length: n }, () => new Vector3());
|
|
85
257
|
}
|
|
86
258
|
|
|
87
|
-
export function FrustumGizmo({
|
|
88
|
-
|
|
259
|
+
export function FrustumGizmo({
|
|
260
|
+
theme,
|
|
261
|
+
feed,
|
|
262
|
+
panes,
|
|
263
|
+
rigAt,
|
|
264
|
+
frame,
|
|
265
|
+
waypointsAt,
|
|
266
|
+
driverAt,
|
|
267
|
+
onDriver,
|
|
268
|
+
}: FrustumGizmoProps) {
|
|
269
|
+
/** The last driver name pushed, so the relay fires on change only. */
|
|
270
|
+
const pushedDriver = useRef<string | null>(null);
|
|
271
|
+
// The camera the camera pane renders with. On the rig it is where `rigAt`
|
|
272
|
+
// says; a component that drives it moves it off the rig, and then THIS is
|
|
273
|
+
// the camera the picture came from.
|
|
274
|
+
const renderCamera = useThree((s) => s.camera);
|
|
275
|
+
const scene = useThree((s) => s.scene);
|
|
276
|
+
const hotColor = useMemo(
|
|
277
|
+
() => tokenColor("--gizmo-hot", theme === "light" ? "#1c1c1c" : "#ffffff"),
|
|
278
|
+
[theme],
|
|
279
|
+
);
|
|
280
|
+
const body = useMemo(() => newLines(CAMERA_BODY_SEGMENT_FLOATS, 1), [theme]);
|
|
281
|
+
const reach = useMemo(() => newLines(FRAME_SEGMENT_FLOATS, REACH_OPACITY), [theme]);
|
|
282
|
+
// The hit-test geometry, kept as three vectors so the screen test allocates
|
|
283
|
+
// nothing: the twelve body points (lens rectangle, then the box) and the
|
|
284
|
+
// framed rectangle's four corners.
|
|
285
|
+
const hit = useMemo(() => ({ body: newPoints(12), quad: newPoints(4) }), []);
|
|
286
|
+
|
|
287
|
+
// ── THE WAYPOINT MARKS ────────────────────────────────────────────────────
|
|
288
|
+
//
|
|
289
|
+
// A SECOND object rather than more segments in the body's buffer, and the
|
|
290
|
+
// reason is the count: the body has 23 segments always, so its buffer is
|
|
291
|
+
// allocated once and rewritten in place. A path has two to a dozen
|
|
292
|
+
// waypoints, and folding them in would mean reallocating the body's buffer
|
|
293
|
+
// whenever an author inserted a pose. Separate objects, one allocation each.
|
|
294
|
+
const marks = useMemo(() => {
|
|
89
295
|
const geometry = new BufferGeometry();
|
|
90
|
-
geometry.setAttribute(
|
|
91
|
-
"position",
|
|
92
|
-
new BufferAttribute(new Float32Array(FRUSTUM_SEGMENT_FLOATS), 3),
|
|
93
|
-
);
|
|
94
296
|
const material = new LineBasicNodeMaterial();
|
|
95
297
|
material.color.copy(tokenColor("--frustum", FRUSTUM_FALLBACK));
|
|
96
298
|
material.depthTest = false;
|
|
97
299
|
material.depthWrite = false;
|
|
98
300
|
material.toneMapped = false;
|
|
99
|
-
// See the header: the working pane renders under a clay override.
|
|
100
301
|
material.allowOverride = false;
|
|
101
|
-
|
|
102
302
|
const lines = new LineSegments(geometry, material);
|
|
103
303
|
lines.layers.set(EDITOR_LAYER);
|
|
104
304
|
lines.renderOrder = FRUSTUM_RENDER_ORDER;
|
|
105
|
-
// The vertices are WORLD positions rewritten in place, so the object stays
|
|
106
|
-
// at the origin and the bounding sphere the culler would test is never the
|
|
107
|
-
// one the buffer holds. Culling it against a stale sphere makes the gizmo
|
|
108
|
-
// vanish at some orbits and not others.
|
|
109
305
|
lines.frustumCulled = false;
|
|
110
306
|
lines.matrixAutoUpdate = false;
|
|
111
|
-
|
|
112
|
-
|
|
307
|
+
lines.visible = false;
|
|
308
|
+
return { lines, geometry, material, eyes: [] as Vector3[], sizes: [] as number[], count: -1, tint: 0 };
|
|
309
|
+
}, [theme]);
|
|
113
310
|
|
|
114
311
|
useEffect(
|
|
115
312
|
() => () => {
|
|
116
|
-
|
|
117
|
-
|
|
313
|
+
body.geometry.dispose();
|
|
314
|
+
body.material.dispose();
|
|
315
|
+
reach.geometry.dispose();
|
|
316
|
+
reach.material.dispose();
|
|
317
|
+
marks.geometry.dispose();
|
|
318
|
+
marks.material.dispose();
|
|
118
319
|
},
|
|
119
|
-
[
|
|
320
|
+
[body, reach, marks],
|
|
120
321
|
);
|
|
121
322
|
|
|
323
|
+
/**
|
|
324
|
+
* The extent of a DRIVEN camera's world: the largest axis of the live
|
|
325
|
+
* content's bounds, re-walked when the graph's membership changes
|
|
326
|
+
* (`feed.sceneEpoch`) or every `EXTENT_REFRESH_FRAMES`, whichever comes
|
|
327
|
+
* first. NaN when the walk found nothing, and the caller falls back to the
|
|
328
|
+
* frame.
|
|
329
|
+
*
|
|
330
|
+
* ponytail: a full traverse per refresh. A component that streams thousands
|
|
331
|
+
* of separate meshes pays it every 60 frames; a declared extent from the
|
|
332
|
+
* component (the camera claim) is the upgrade and skips the walk entirely.
|
|
333
|
+
*/
|
|
334
|
+
const extentCache = useRef({ epoch: -1, frames: 0, value: Number.NaN });
|
|
335
|
+
const drivenExtent = (): number => {
|
|
336
|
+
const cache = extentCache.current;
|
|
337
|
+
if (cache.epoch !== feed.sceneEpoch || cache.frames >= EXTENT_REFRESH_FRAMES) {
|
|
338
|
+
cache.epoch = feed.sceneEpoch;
|
|
339
|
+
cache.frames = 0;
|
|
340
|
+
cache.value = sceneExtent(scene, _bounds);
|
|
341
|
+
}
|
|
342
|
+
cache.frames += 1;
|
|
343
|
+
return cache.value;
|
|
344
|
+
};
|
|
345
|
+
const rigExtent = Math.max(frame.width, frame.height);
|
|
346
|
+
|
|
122
347
|
useFrame(() => {
|
|
348
|
+
const list = waypointsAt()?.eyes ?? [];
|
|
349
|
+
if (list.length === 0) {
|
|
350
|
+
marks.lines.visible = false;
|
|
351
|
+
feed.waypointHit = null;
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
marks.lines.visible = true;
|
|
355
|
+
const want = feed.hoverWaypoint === null ? 0 : HOVER_TINT;
|
|
356
|
+
if (marks.tint !== want) {
|
|
357
|
+
marks.tint = want;
|
|
358
|
+
marks.material.color.copy(tokenColor("--frustum", FRUSTUM_FALLBACK)).lerp(hotColor, want);
|
|
359
|
+
}
|
|
360
|
+
// REALLOCATE ONLY WHEN THE COUNT CHANGES, which is when a waypoint is
|
|
361
|
+
// inserted or removed. A drag moves vertices inside a buffer that is
|
|
362
|
+
// already the right size.
|
|
363
|
+
if (marks.count !== list.length) {
|
|
364
|
+
marks.count = list.length;
|
|
365
|
+
// ── DISPOSE, THEN REBUILD ──────────────────────────────────────────────
|
|
366
|
+
//
|
|
367
|
+
// `setAttribute` alone ORPHANS the outgoing attribute's GPU buffer: three
|
|
368
|
+
// frees an attribute only from the geometry's own dispose listener, which
|
|
369
|
+
// walks the attributes still ATTACHED at that moment. Replacing one in
|
|
370
|
+
// place leaves the old buffer alive with nothing pointing at it, and
|
|
371
|
+
// nothing draws wrong — the only symptom is `renderer.info.memory
|
|
372
|
+
// .attributes` climbing every time the playhead crosses between two path
|
|
373
|
+
// moves with different waypoint counts.
|
|
374
|
+
marks.geometry.dispose();
|
|
375
|
+
marks.geometry = new BufferGeometry();
|
|
376
|
+
marks.geometry.setAttribute(
|
|
377
|
+
"position",
|
|
378
|
+
new BufferAttribute(new Float32Array(waypointSegmentFloats(list.length)), 3),
|
|
379
|
+
);
|
|
380
|
+
marks.lines.geometry = marks.geometry;
|
|
381
|
+
marks.eyes = list.map(() => new Vector3());
|
|
382
|
+
marks.sizes = list.map(() => 0);
|
|
383
|
+
}
|
|
384
|
+
// The hit-test points follow every frame regardless of the redraw gate,
|
|
385
|
+
// the same rule the body follows and for the same reason — and each cross
|
|
386
|
+
// is sized against its OWN screen depth, so a path that runs towards the
|
|
387
|
+
// author does not end in a mark too small to grab.
|
|
388
|
+
const camera = feed.workingCamera;
|
|
123
389
|
const working = panes.working;
|
|
390
|
+
let epsilon = 0.5;
|
|
391
|
+
for (let i = 0; i < list.length; i++) {
|
|
392
|
+
const eye = marks.eyes[i]!.set(list[i]![0], list[i]![1], list[i]![2]);
|
|
393
|
+
const wpp = camera === null || working === null ? 0 : worldPerPixel(camera, eye, working.height);
|
|
394
|
+
marks.sizes[i] = waypointCrossSize(rigExtent, wpp);
|
|
395
|
+
if (i === 0 && wpp > 0) epsilon = 0.5 * wpp;
|
|
396
|
+
}
|
|
397
|
+
const next = waypointSegments(list, marks.sizes);
|
|
398
|
+
const attribute = marks.geometry.getAttribute("position") as BufferAttribute;
|
|
399
|
+
const buffer = attribute.array as Float32Array;
|
|
400
|
+
if (segmentsMoved(buffer, next, epsilon)) {
|
|
401
|
+
buffer.set(next);
|
|
402
|
+
attribute.needsUpdate = true;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
feed.waypointHit = (x, y) => {
|
|
406
|
+
if (camera === null || working === null) return null;
|
|
407
|
+
// NEAREST WINS, and it has to: a path that doubles back puts two eyes
|
|
408
|
+
// within a grab radius of each other, and answering with whichever came
|
|
409
|
+
// first in the list would move the one behind the pointer.
|
|
410
|
+
let best: number | null = null;
|
|
411
|
+
let bestDistance = WAYPOINT_GRAB_PX;
|
|
412
|
+
for (let i = 0; i < marks.eyes.length; i++) {
|
|
413
|
+
const eye = marks.eyes[i]!;
|
|
414
|
+
// A waypoint BEHIND the working camera projects to garbage, so it is
|
|
415
|
+
// not on screen to be grabbed.
|
|
416
|
+
_view.copy(eye).applyMatrix4(camera.matrixWorldInverse);
|
|
417
|
+
if (_view.z >= 0) continue;
|
|
418
|
+
const [px, py] = toScreen(eye, camera, working.width, working.height);
|
|
419
|
+
const distance = Math.hypot(px - x, py - y);
|
|
420
|
+
if (distance <= bestDistance) {
|
|
421
|
+
best = i;
|
|
422
|
+
bestDistance = distance;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
return best;
|
|
426
|
+
};
|
|
427
|
+
}, 0);
|
|
428
|
+
|
|
429
|
+
useEffect(
|
|
430
|
+
() => () => {
|
|
431
|
+
feed.waypointHit = null;
|
|
432
|
+
feed.hoverWaypoint = null;
|
|
433
|
+
},
|
|
434
|
+
[feed],
|
|
435
|
+
);
|
|
436
|
+
|
|
437
|
+
useFrame(() => {
|
|
438
|
+
const working = panes.working;
|
|
439
|
+
const rig = rigAt();
|
|
124
440
|
if (rig === null) {
|
|
125
|
-
|
|
441
|
+
body.lines.visible = false;
|
|
442
|
+
reach.lines.visible = false;
|
|
126
443
|
feed.frustumHit = null;
|
|
444
|
+
feed.cameraDriven = false;
|
|
445
|
+
feed.cameraWorld = null;
|
|
446
|
+
if (pushedDriver.current !== null) {
|
|
447
|
+
pushedDriver.current = null;
|
|
448
|
+
onDriver(null);
|
|
449
|
+
}
|
|
127
450
|
return;
|
|
128
451
|
}
|
|
129
|
-
|
|
452
|
+
const camera = feed.workingCamera;
|
|
130
453
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
//
|
|
136
|
-
//
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
454
|
+
// ── IT LIGHTS UP UNDER THE CURSOR, LIKE THE MOVE ARMS ─────────────────
|
|
455
|
+
//
|
|
456
|
+
// Off a plain field the pointer lane wrote (`feed.hoverFrustum`), read
|
|
457
|
+
// here in the frame loop: one uniform write per change and zero React
|
|
458
|
+
// renders, the same trade `feed.hoverArm` makes. Both grips pan, so both
|
|
459
|
+
// buffers light together: one tint is the honest one.
|
|
460
|
+
const want = feed.hoverFrustum === null ? 0 : HOVER_TINT;
|
|
461
|
+
tintTo(body, want, hotColor);
|
|
462
|
+
tintTo(reach, want, hotColor);
|
|
463
|
+
|
|
464
|
+
// ── WHOSE CAMERA ───────────────────────────────────────────────────────
|
|
465
|
+
//
|
|
466
|
+
// The rig's, unless a component drives the render camera: one that has
|
|
467
|
+
// CLAIMED it (by name, with its world's extent), or one the float
|
|
468
|
+
// comparison catches writing it off the rig this frame (a flythrough
|
|
469
|
+
// writes eye, target and lens itself). Then the body follows the camera
|
|
470
|
+
// the pane renders with, sized against the component's own world, and
|
|
471
|
+
// nothing is grabbable: dragging the rig would move nothing the picture
|
|
472
|
+
// shows.
|
|
473
|
+
const live = readPose(renderCamera);
|
|
474
|
+
const driver = driverAt();
|
|
475
|
+
const driven = isDriven(driver, rig, live);
|
|
476
|
+
feed.cameraDriven = driven;
|
|
477
|
+
const driverName = driver?.name ?? (driven ? "a component" : null);
|
|
478
|
+
if (driverName !== pushedDriver.current) {
|
|
479
|
+
pushedDriver.current = driverName;
|
|
480
|
+
onDriver(driverName);
|
|
481
|
+
}
|
|
482
|
+
const pose = driven && live !== null ? live : rigPose(rig);
|
|
483
|
+
// A HELD camera fades: the fork would refuse a drag on it, and the fade is
|
|
484
|
+
// the cue that says so before the press (the cursor and the pill say why).
|
|
485
|
+
const held = !driven && feed.cameraGate !== null && feed.cameraGate.kind !== "free";
|
|
486
|
+
fadeTo(body, held);
|
|
487
|
+
fadeTo(reach, held);
|
|
488
|
+
|
|
489
|
+
// The component's world: its declared extent, or the one the bounds walk
|
|
490
|
+
// measures; the walk runs either way for the CENTRE, which Home fits to.
|
|
491
|
+
let extent = rigExtent;
|
|
492
|
+
if (driven) {
|
|
493
|
+
const walked = drivenExtent();
|
|
494
|
+
const declared = driver?.extent;
|
|
495
|
+
const chosen = declared !== undefined && declared > 0 ? declared : walked;
|
|
496
|
+
extent = Number.isFinite(chosen) && chosen > 0 ? chosen : rigExtent;
|
|
497
|
+
if (!_bounds.isEmpty()) {
|
|
498
|
+
_bounds.getCenter(_centre);
|
|
499
|
+
feed.cameraWorld = { centre: [_centre.x, _centre.y, _centre.z], extent };
|
|
500
|
+
} else {
|
|
501
|
+
feed.cameraWorld = { centre: [pose.eye[0], pose.eye[1], pose.eye[2]], extent };
|
|
502
|
+
}
|
|
503
|
+
} else {
|
|
504
|
+
feed.cameraWorld = null;
|
|
140
505
|
}
|
|
506
|
+
_eye.set(pose.eye[0], pose.eye[1], pose.eye[2]);
|
|
507
|
+
const wpp = camera === null || working === null ? 0 : worldPerPixel(camera, _eye, working.height);
|
|
508
|
+
const length = cameraDisplayLength(
|
|
509
|
+
extent,
|
|
510
|
+
wpp,
|
|
511
|
+
working === null ? 0 : working.height,
|
|
512
|
+
driven ? Number.POSITIVE_INFINITY : REACH_CAP_FRACTION * rig.distance,
|
|
513
|
+
);
|
|
514
|
+
// Half a SCREEN pixel at the eye, both cases: the old half design px gate
|
|
515
|
+
// was a whole city block for a metre-scale component.
|
|
516
|
+
const epsilon = wpp > 0 ? 0.5 * wpp : 0.5;
|
|
141
517
|
|
|
142
|
-
|
|
518
|
+
const shape = cameraBodyShape(pose, length, frame);
|
|
519
|
+
write(body, cameraBodySegments(shape), epsilon);
|
|
520
|
+
body.lines.visible = true;
|
|
521
|
+
// The hit-test geometry follows every frame regardless of the gate: half a
|
|
143
522
|
// pixel of drift is invisible in the drawing and is still the difference
|
|
144
|
-
// between grabbing the
|
|
145
|
-
|
|
146
|
-
for (let i = 0; i <
|
|
147
|
-
|
|
523
|
+
// between grabbing the body and missing it.
|
|
524
|
+
for (let i = 0; i < 4; i++) hit.body[i]!.set(shape.near[i]![0], shape.near[i]![1], shape.near[i]![2]);
|
|
525
|
+
for (let i = 0; i < 8; i++) hit.body[4 + i]!.set(shape.box[i]![0], shape.box[i]![1], shape.box[i]![2]);
|
|
526
|
+
|
|
527
|
+
if (driven) {
|
|
528
|
+
reach.lines.visible = false;
|
|
529
|
+
feed.frustumHit = null;
|
|
530
|
+
return;
|
|
148
531
|
}
|
|
532
|
+
const far = frustumShape(rig, frame);
|
|
533
|
+
write(reach, frameSegments(far), epsilon);
|
|
534
|
+
reach.lines.visible = true;
|
|
535
|
+
for (let i = 0; i < 4; i++) hit.quad[i]!.set(far.corners[i]![0], far.corners[i]![1], far.corners[i]![2]);
|
|
149
536
|
|
|
150
537
|
feed.frustumHit = (x, y) => {
|
|
151
|
-
const camera = feed.workingCamera;
|
|
152
538
|
if (camera === null || working === null) return null;
|
|
153
|
-
// THE
|
|
154
|
-
//
|
|
155
|
-
//
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
539
|
+
// THE BODY FIRST, by the screen box its twelve points project to,
|
|
540
|
+
// inflated by the slop. A small object drawn over the layers wins over
|
|
541
|
+
// them (the pointer lane keeps its layer veto for the reach alone). A
|
|
542
|
+
// point behind the working camera projects to garbage, so a body that is
|
|
543
|
+
// not wholly in front is not on screen to be grabbed.
|
|
544
|
+
let minX = Number.POSITIVE_INFINITY;
|
|
545
|
+
let minY = Number.POSITIVE_INFINITY;
|
|
546
|
+
let maxX = Number.NEGATIVE_INFINITY;
|
|
547
|
+
let maxY = Number.NEGATIVE_INFINITY;
|
|
548
|
+
let inFront = true;
|
|
549
|
+
for (const point of hit.body) {
|
|
550
|
+
_view.copy(point).applyMatrix4(camera.matrixWorldInverse);
|
|
551
|
+
if (_view.z >= 0) {
|
|
552
|
+
inFront = false;
|
|
553
|
+
break;
|
|
554
|
+
}
|
|
555
|
+
const [sx, sy] = toScreen(point, camera, working.width, working.height);
|
|
556
|
+
if (sx < minX) minX = sx;
|
|
557
|
+
if (sx > maxX) maxX = sx;
|
|
558
|
+
if (sy < minY) minY = sy;
|
|
559
|
+
if (sy > maxY) maxY = sy;
|
|
560
|
+
}
|
|
561
|
+
if (
|
|
562
|
+
inFront &&
|
|
563
|
+
x >= minX - BODY_GRAB_PX &&
|
|
564
|
+
x <= maxX + BODY_GRAB_PX &&
|
|
565
|
+
y >= minY - BODY_GRAB_PX &&
|
|
566
|
+
y <= maxY + BODY_GRAB_PX
|
|
567
|
+
) {
|
|
568
|
+
return "body";
|
|
569
|
+
}
|
|
570
|
+
// THE FRAMED RECTANGLE BY ITS BORDER, in SCREEN px like the body and for
|
|
571
|
+
// the same reason. Its interior is deliberately not a target — see
|
|
572
|
+
// `borderDistance` for why an interior claim takes the whole pane away.
|
|
162
573
|
const corners: [number, number][] = [];
|
|
163
|
-
for (const corner of
|
|
574
|
+
for (const corner of hit.quad) {
|
|
164
575
|
_view.copy(corner).applyMatrix4(camera.matrixWorldInverse);
|
|
165
576
|
if (_view.z >= 0) return null;
|
|
166
577
|
corners.push(toScreen(corner, camera, working.width, working.height));
|
|
167
578
|
}
|
|
168
|
-
return borderDistance(x, y, corners) <= BODY_GRAB_PX ? "
|
|
579
|
+
return borderDistance(x, y, corners) <= BODY_GRAB_PX ? "frame" : null;
|
|
169
580
|
};
|
|
170
|
-
},
|
|
581
|
+
}, AFTER_CAMERA_WRITERS);
|
|
171
582
|
|
|
172
583
|
useEffect(
|
|
173
584
|
() => () => {
|
|
174
585
|
feed.frustumHit = null;
|
|
586
|
+
feed.hoverFrustum = null;
|
|
587
|
+
feed.cameraDriven = false;
|
|
588
|
+
feed.cameraWorld = null;
|
|
589
|
+
if (pushedDriver.current !== null) {
|
|
590
|
+
pushedDriver.current = null;
|
|
591
|
+
onDriver(null);
|
|
592
|
+
}
|
|
175
593
|
},
|
|
176
|
-
[feed],
|
|
594
|
+
[feed, onDriver],
|
|
177
595
|
);
|
|
178
596
|
|
|
179
|
-
return
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
597
|
+
return (
|
|
598
|
+
<>
|
|
599
|
+
<primitive object={body.lines} />
|
|
600
|
+
<primitive object={reach.lines} />
|
|
601
|
+
<primitive object={marks.lines} />
|
|
602
|
+
</>
|
|
603
|
+
);
|
|
184
604
|
}
|