@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
|
@@ -0,0 +1,696 @@
|
|
|
1
|
+
// The transform gizmo in the working pane, on whatever the author is moving.
|
|
2
|
+
//
|
|
3
|
+
// ── ONE GIZMO, TWO SUBJECTS, AND THE FORK IS `modalTarget` ──────────────
|
|
4
|
+
//
|
|
5
|
+
// With a selection it hangs on the selection's centre and moves layers. With
|
|
6
|
+
// NOTHING selected it hangs on the shot camera's apex and moves the camera —
|
|
7
|
+
// which is not a second feature bolted on, it is the SAME rule `G` and `S`
|
|
8
|
+
// already follow (modalState.ts: "layers when the selection is non-empty, else
|
|
9
|
+
// the CAMERA's framing"). Importing `modalTarget` rather than restating it is
|
|
10
|
+
// what keeps the drawn gizmo and the keyboard from ever disagreeing about whose
|
|
11
|
+
// transform the author is about to change.
|
|
12
|
+
//
|
|
13
|
+
// The camera's arms mean something slightly different, and `framingArmDelta`
|
|
14
|
+
// owns that: it has no free z, so its Z arm is the DOLLY. See that function.
|
|
15
|
+
//
|
|
16
|
+
// ── THE HANDLES, AND WHY THE CAMERA GETS FEWER ──────────────────────────
|
|
17
|
+
//
|
|
18
|
+
// Layers get three ARROWS (one design axis each), three PLANE PADS (two axes at
|
|
19
|
+
// once) and a view-plane DISC at the centre. The camera gets the arrows and ONE
|
|
20
|
+
// arc, and that is honesty rather than economy: a `Framing` is (x, y, zoom,
|
|
21
|
+
// roll) with no free z, so the xz and yz pads would be pads for a motion it
|
|
22
|
+
// cannot perform, and the xy pad would be an exact duplicate of the camera
|
|
23
|
+
// body's own drag that already pans it. The arms start clear of the body's
|
|
24
|
+
// grab box — see `GIZMO_PX.armStart` — and they outrank it in the pointer
|
|
25
|
+
// lane, so both stay reachable.
|
|
26
|
+
//
|
|
27
|
+
// Layers also get three ROTATE ARCS, one per design axis, which is every
|
|
28
|
+
// rotation the document model HAS: `arc:z` is `rollDeg` (and takes the
|
|
29
|
+
// selection's median as its pivot, the way the camera pane's ring already
|
|
30
|
+
// does), `arc:x` and `arc:y` are the two `tilt` channels (which are pivot-free,
|
|
31
|
+
// because `applyGesture` can only orbit a pivot in the design z plane). There
|
|
32
|
+
// is no fourth arc because there is no fourth channel. The camera keeps `arc:z`
|
|
33
|
+
// alone: a roll is the one rotation its framing has, and the arc is the grip
|
|
34
|
+
// the drawn camera never had for it (the `R` modal is the keyboard path).
|
|
35
|
+
//
|
|
36
|
+
// ── AND WHY THERE ARE NO SCALE HANDLES ──────────────────────────────────
|
|
37
|
+
//
|
|
38
|
+
// Because there already are. A selection quad in this pane draws the same
|
|
39
|
+
// eight resize handles the camera pane does (Overlays' `SelectionQuad`), and a
|
|
40
|
+
// multi-select draws the union box, whose corner IS a uniform scale about the
|
|
41
|
+
// opposite corner (`unionScale`). Cubes on these arms would be a third way to
|
|
42
|
+
// write `size`, competing for the same pixels as the arrowheads. `S` and its
|
|
43
|
+
// axis constraints remain the keyboard path.
|
|
44
|
+
//
|
|
45
|
+
// ── WHY AN ARM DRAG SOLVES A LINE AND A PAD SOLVES A PLANE ──────────────
|
|
46
|
+
//
|
|
47
|
+
// An arm the author has grabbed must stay under the cursor, and only the
|
|
48
|
+
// closest-approach solve between the axis line and the pointer ray does that
|
|
49
|
+
// (`axisParam`). Unprojecting onto a plane and zeroing two components — what
|
|
50
|
+
// the modal lane does, because it has no drawn arm to stay under — drifts away
|
|
51
|
+
// from the pointer as the view turns and runs away entirely at a grazing angle.
|
|
52
|
+
// A PAD has no line to stay on: the grabbed point stays under the cursor
|
|
53
|
+
// exactly when both rays are unprojected onto the pad's own plane, which is
|
|
54
|
+
// `designDelta`, the same solve a layer body drag uses.
|
|
55
|
+
//
|
|
56
|
+
// ── EDITOR-ONLY, THE SAME THREE WAYS THE FRUSTUM IS ─────────────────────
|
|
57
|
+
//
|
|
58
|
+
// `layers.set(EDITOR_LAYER)`, SplitRenderer's post pass pinned to bit 0, and no
|
|
59
|
+
// `SHOT_LAYER_KEY` userData. It also writes no document: the DRAG does, through
|
|
60
|
+
// the same `onGestureStart`/`onGesture`/`onGestureEnd` scrub every other
|
|
61
|
+
// transform opens.
|
|
62
|
+
//
|
|
63
|
+
// ── AND WHY IT IS MESHES AND NOT LINES ──────────────────────────────────
|
|
64
|
+
//
|
|
65
|
+
// `LineSegments` has no line width on either backend, so the arms were 1px
|
|
66
|
+
// hairlines with a 14px invisible grab slop around them: the affordance was
|
|
67
|
+
// four times thinner than its own hit area, and nothing said which end of an
|
|
68
|
+
// axis was positive. Cylinders and cones are `MeshBasicNodeMaterial`, which is
|
|
69
|
+
// a node material like everything else in this lane and therefore correct on
|
|
70
|
+
// the WebGPU backend as well as the WebGL2 one this stage pins.
|
|
71
|
+
import { useEffect, useMemo } from "react";
|
|
72
|
+
import { useFrame } from "@react-three/fiber";
|
|
73
|
+
import {
|
|
74
|
+
BufferAttribute,
|
|
75
|
+
BufferGeometry as ThreeBufferGeometry,
|
|
76
|
+
CircleGeometry,
|
|
77
|
+
ConeGeometry,
|
|
78
|
+
CylinderGeometry,
|
|
79
|
+
Line,
|
|
80
|
+
Matrix4,
|
|
81
|
+
Mesh,
|
|
82
|
+
PlaneGeometry,
|
|
83
|
+
Quaternion,
|
|
84
|
+
Vector3,
|
|
85
|
+
} from "three";
|
|
86
|
+
import type { BufferGeometry, Camera, Object3D } from "three";
|
|
87
|
+
import { LineBasicNodeMaterial, MeshBasicNodeMaterial } from "three/webgpu";
|
|
88
|
+
import { EDITOR_LAYER } from "./StageTools.js";
|
|
89
|
+
import {
|
|
90
|
+
ARC_SEGMENTS,
|
|
91
|
+
DESIGN_AXES,
|
|
92
|
+
GIZMO_PX,
|
|
93
|
+
MIN_ARM_SCREEN_PX,
|
|
94
|
+
MIN_PAD_SCREEN_AREA,
|
|
95
|
+
alongAxis,
|
|
96
|
+
arcPoints,
|
|
97
|
+
armGrabbable,
|
|
98
|
+
gizmoAxisOf,
|
|
99
|
+
gizmoHitAt,
|
|
100
|
+
padAxes,
|
|
101
|
+
padCentre,
|
|
102
|
+
padCorners,
|
|
103
|
+
screenArea,
|
|
104
|
+
} from "./gizmoMath.js";
|
|
105
|
+
import type { DesignAxis, GizmoHandleId, GizmoTarget, Vec3 } from "./gizmoMath.js";
|
|
106
|
+
import { modalTarget } from "./modalState.js";
|
|
107
|
+
import { toScreen, worldPerPixel } from "./project.js";
|
|
108
|
+
import { tokenColor } from "./tokenColor.js";
|
|
109
|
+
import type { StageTheme } from "./stageTheme.js";
|
|
110
|
+
import type { StageFeed } from "./feed.js";
|
|
111
|
+
import type { PaneRect } from "./panes.js";
|
|
112
|
+
|
|
113
|
+
/** Over everything, and over the frustum too: a handle that a camera gizmo
|
|
114
|
+
* could hide is a handle the author cannot grab. */
|
|
115
|
+
const GIZMO_RENDER_ORDER = 2_000_000;
|
|
116
|
+
|
|
117
|
+
const AXIS_FALLBACK: Readonly<Record<DesignAxis, string>> = {
|
|
118
|
+
x: "#ef4444",
|
|
119
|
+
y: "#22c55e",
|
|
120
|
+
z: "#3b82f6",
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
/** How far a hovered handle blends towards the theme's contrast ink. Enough to
|
|
124
|
+
* read as hot at a glance, little enough that red / green / blue still name
|
|
125
|
+
* the axis. */
|
|
126
|
+
const HOVER_TINT = 0.45;
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* What a handle that cannot answer a press is drawn at.
|
|
130
|
+
*
|
|
131
|
+
* `armGrabbable` has always had the answer and only the HIT TEST asked it, so
|
|
132
|
+
* a foreshortened arm — the design y at Numpad7, the design z under isolation's
|
|
133
|
+
* orthographic rig — was painted at full strength and then silently ignored
|
|
134
|
+
* every press. Hiding it would lie about the geometry; dimming it does not. The
|
|
135
|
+
* axis is still there and still that colour, it just stops claiming to be a
|
|
136
|
+
* handle.
|
|
137
|
+
*
|
|
138
|
+
* The same value dims every OTHER handle while one is being dragged, which is
|
|
139
|
+
* the same statement about the same thing: those cannot be grabbed either while
|
|
140
|
+
* a scrub is open.
|
|
141
|
+
*/
|
|
142
|
+
const DEAD_OPACITY = 0.3;
|
|
143
|
+
|
|
144
|
+
/** A pad's fill is a wash, not a solid: it sits over the geometry the author is
|
|
145
|
+
* placing, and a solid quad there hides exactly what they are aiming at. */
|
|
146
|
+
const PAD_OPACITY = 0.45;
|
|
147
|
+
|
|
148
|
+
/** The view-plane disc is the quietest handle on the widget — it does the least
|
|
149
|
+
* constrained thing, so it makes the least visual claim. */
|
|
150
|
+
const DISC_OPACITY = 0.32;
|
|
151
|
+
|
|
152
|
+
/** An arc is a hairline (line width has no setting on either backend), so it
|
|
153
|
+
* sits closer to full strength than the filled handles do or it would read as
|
|
154
|
+
* already dimmed. */
|
|
155
|
+
const ARC_OPACITY = 0.8;
|
|
156
|
+
|
|
157
|
+
/** three's cylinder and cone are authored along +y, so an arm's orientation is
|
|
158
|
+
* one `setFromUnitVectors` off this. (A pad's is NOT — it needs an explicit
|
|
159
|
+
* basis, see where it is built.) */
|
|
160
|
+
const UP = new Vector3(0, 1, 0);
|
|
161
|
+
const _origin = new Vector3();
|
|
162
|
+
const _point = new Vector3();
|
|
163
|
+
const _view = new Vector3();
|
|
164
|
+
|
|
165
|
+
/** Is a world point behind the camera's own plane? View-space z runs NEGATIVE
|
|
166
|
+
* in front, so anything at or past zero has no honest projection. */
|
|
167
|
+
function behind(point: Vector3, camera: Camera): boolean {
|
|
168
|
+
return _view.copy(point).applyMatrix4(camera.matrixWorldInverse).z >= 0;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export interface TransformGizmoProps {
|
|
172
|
+
/** Resolved editor theme. It rebuilds only token-backed GL materials. */
|
|
173
|
+
theme: StageTheme;
|
|
174
|
+
feed: StageFeed;
|
|
175
|
+
panes: { working: PaneRect | null; camera: PaneRect | null };
|
|
176
|
+
selection: readonly string[];
|
|
177
|
+
/** The shot camera's own position at this frame, or null when no scene is on
|
|
178
|
+
* screen. Passed in rather than read off the feed: `FrustumGizmo` writes its
|
|
179
|
+
* own copy at priority 0 too, and two priority-0 subscribers have no defined
|
|
180
|
+
* order — so this comes from `rig`, which `StageApp` already memoizes. */
|
|
181
|
+
/** Where the camera IS at the clock's frame, read at FRAME time (StageApp's
|
|
182
|
+
* `playhead`): the arms hang on it when nothing is selected. */
|
|
183
|
+
cameraApexAt: () => Vec3 | null;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* One drawable piece of the widget: an object, the handle it belongs to, and
|
|
188
|
+
* the cached appearance so the frame loop can skip writes that change nothing.
|
|
189
|
+
*
|
|
190
|
+
* `base` is the handle's own colour at rest, and `restAlpha` its own opacity at
|
|
191
|
+
* rest — a pad and an arrow on the same axis are the same hue and deliberately
|
|
192
|
+
* not the same weight.
|
|
193
|
+
*
|
|
194
|
+
* `kind` is carried rather than sniffed off the geometry's `type`, because two
|
|
195
|
+
* pieces share one handle (an arm is a shaft AND a head) and the placement
|
|
196
|
+
* differs between them.
|
|
197
|
+
*/
|
|
198
|
+
type PieceKind = "shaft" | "head" | "pad" | "disc" | "arc";
|
|
199
|
+
|
|
200
|
+
interface Piece {
|
|
201
|
+
handle: GizmoHandleId;
|
|
202
|
+
kind: PieceKind;
|
|
203
|
+
object: Object3D;
|
|
204
|
+
material: MeshBasicNodeMaterial | LineBasicNodeMaterial;
|
|
205
|
+
geometry: BufferGeometry;
|
|
206
|
+
base: string;
|
|
207
|
+
fallback: string;
|
|
208
|
+
restAlpha: number;
|
|
209
|
+
tint: number;
|
|
210
|
+
alpha: number;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export function TransformGizmo({ theme, feed, panes, selection, cameraApexAt }: TransformGizmoProps) {
|
|
214
|
+
const hotColor = useMemo(
|
|
215
|
+
() => tokenColor("--gizmo-hot", theme === "light" ? "#1c1c1c" : "#ffffff"),
|
|
216
|
+
[theme],
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
// ── ONE MESH PER PIECE, ONE MATERIAL PER PIECE ────────────────────────
|
|
220
|
+
//
|
|
221
|
+
// Not one buffer with vertex colours: vertex colours on a node material would
|
|
222
|
+
// be a second thing to keep working across the WebGPU and WebGL2 backends,
|
|
223
|
+
// and the per-piece opacity the dimming needs is a uniform either way. Ten
|
|
224
|
+
// meshes is the whole cost, and nothing here allocates per frame.
|
|
225
|
+
const pieces = useMemo(() => {
|
|
226
|
+
const built: Piece[] = [];
|
|
227
|
+
/** Shared by every piece: the editor-only flags, the token colour, and the
|
|
228
|
+
* transparency the dimming needs. */
|
|
229
|
+
const dress = (
|
|
230
|
+
handle: GizmoHandleId,
|
|
231
|
+
kind: PieceKind,
|
|
232
|
+
object: Object3D,
|
|
233
|
+
material: MeshBasicNodeMaterial | LineBasicNodeMaterial,
|
|
234
|
+
geometry: BufferGeometry,
|
|
235
|
+
base: string,
|
|
236
|
+
fallback: string,
|
|
237
|
+
restAlpha: number,
|
|
238
|
+
): void => {
|
|
239
|
+
material.color.copy(tokenColor(base, fallback));
|
|
240
|
+
material.depthTest = false;
|
|
241
|
+
material.depthWrite = false;
|
|
242
|
+
material.toneMapped = false;
|
|
243
|
+
// Always transparent, so a handle going dead or being dimmed under a drag
|
|
244
|
+
// is one `opacity` write rather than a material recompile mid-gesture.
|
|
245
|
+
material.transparent = true;
|
|
246
|
+
material.opacity = restAlpha;
|
|
247
|
+
// The working pane renders under a clay override in solid/wireframe mode,
|
|
248
|
+
// and a grey gizmo is the one thing this must not be.
|
|
249
|
+
material.allowOverride = false;
|
|
250
|
+
object.layers.set(EDITOR_LAYER);
|
|
251
|
+
object.renderOrder = GIZMO_RENDER_ORDER;
|
|
252
|
+
// Position and scale are rewritten every frame from the screen-constant
|
|
253
|
+
// sizing (and an arc's vertices in place), so the bounding sphere the
|
|
254
|
+
// culler would test is never the one the geometry holds.
|
|
255
|
+
object.frustumCulled = false;
|
|
256
|
+
object.matrixAutoUpdate = false;
|
|
257
|
+
built.push({
|
|
258
|
+
handle,
|
|
259
|
+
kind,
|
|
260
|
+
object,
|
|
261
|
+
material,
|
|
262
|
+
geometry,
|
|
263
|
+
base,
|
|
264
|
+
fallback,
|
|
265
|
+
restAlpha,
|
|
266
|
+
tint: 0,
|
|
267
|
+
alpha: restAlpha,
|
|
268
|
+
});
|
|
269
|
+
};
|
|
270
|
+
const add = (
|
|
271
|
+
handle: GizmoHandleId,
|
|
272
|
+
kind: PieceKind,
|
|
273
|
+
geometry: BufferGeometry,
|
|
274
|
+
quaternion: Quaternion,
|
|
275
|
+
base: string,
|
|
276
|
+
fallback: string,
|
|
277
|
+
restAlpha: number,
|
|
278
|
+
): void => {
|
|
279
|
+
const material = new MeshBasicNodeMaterial();
|
|
280
|
+
const mesh = new Mesh(geometry, material);
|
|
281
|
+
mesh.quaternion.copy(quaternion);
|
|
282
|
+
dress(handle, kind, mesh, material, geometry, base, fallback, restAlpha);
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
for (const { axis, world } of DESIGN_AXES) {
|
|
286
|
+
// The arm's direction is FIXED (a design axis in world), so its rotation
|
|
287
|
+
// is computed once here and never again — only position and scale move.
|
|
288
|
+
const along = new Quaternion().setFromUnitVectors(UP, new Vector3(world[0], world[1], world[2]));
|
|
289
|
+
// Unit primitives: height 1 and radius 1, centred on the origin along +y.
|
|
290
|
+
// Every real dimension is a `scale`, which is what makes the per-frame
|
|
291
|
+
// update three number writes and no geometry rebuild.
|
|
292
|
+
add(`axis:${axis}`, "shaft", new CylinderGeometry(1, 1, 1, 8, 1), along, `--axis-${axis}`, AXIS_FALLBACK[axis], 1);
|
|
293
|
+
add(`axis:${axis}`, "head", new ConeGeometry(1, 1, 16, 1), along, `--axis-${axis}`, AXIS_FALLBACK[axis], 1);
|
|
294
|
+
// The PAD normal to this axis, drawn in the plane of the other two. Its
|
|
295
|
+
// colour is the NORMAL's, matching Blender: the pad you grab to move in
|
|
296
|
+
// x and y is the one that leaves z alone, and naming it "z" is the only
|
|
297
|
+
// label that survives an orbit.
|
|
298
|
+
//
|
|
299
|
+
// ── ITS BASIS IS EXPLICIT, NOT THE SHORTEST ROTATION ───────────────
|
|
300
|
+
//
|
|
301
|
+
// `setFromUnitVectors(FORWARD, normal)` lands the square at whatever
|
|
302
|
+
// in-plane angle the shortest rotation happens to give, while the HIT
|
|
303
|
+
// QUAD is built from `padAxes` — same plane, different square, and a
|
|
304
|
+
// handle whose drawn corners answer no press. Building the rotation from
|
|
305
|
+
// the same two axes the hit test uses is what makes the two one shape.
|
|
306
|
+
const [pu, pv] = padAxes(axis);
|
|
307
|
+
const basis = new Matrix4().makeBasis(
|
|
308
|
+
new Vector3(pu[0], pu[1], pu[2]),
|
|
309
|
+
new Vector3(pv[0], pv[1], pv[2]),
|
|
310
|
+
new Vector3(world[0], world[1], world[2]),
|
|
311
|
+
);
|
|
312
|
+
const facing = new Quaternion().setFromRotationMatrix(basis);
|
|
313
|
+
add(`plane:${axis}`, "pad", new PlaneGeometry(1, 1), facing, `--axis-${axis}`, AXIS_FALLBACK[axis], PAD_OPACITY);
|
|
314
|
+
// ── THE ROTATE ARC ────────────────────────────────────────────────
|
|
315
|
+
//
|
|
316
|
+
// A `Line` rather than a mesh, and its vertices are WORLD points written
|
|
317
|
+
// in place every frame — a torus would have to be posed AND scaled
|
|
318
|
+
// anisotropically to stay screen-constant, and a tube's own thickness
|
|
319
|
+
// would then be wrong. Same construction the frustum uses.
|
|
320
|
+
const geometry = new ThreeBufferGeometry();
|
|
321
|
+
geometry.setAttribute(
|
|
322
|
+
"position",
|
|
323
|
+
new BufferAttribute(new Float32Array((ARC_SEGMENTS + 1) * 3), 3),
|
|
324
|
+
);
|
|
325
|
+
const material = new LineBasicNodeMaterial();
|
|
326
|
+
const line = new Line(geometry, material);
|
|
327
|
+
dress(
|
|
328
|
+
`arc:${axis}`,
|
|
329
|
+
"arc",
|
|
330
|
+
line,
|
|
331
|
+
material,
|
|
332
|
+
geometry,
|
|
333
|
+
`--axis-${axis}`,
|
|
334
|
+
AXIS_FALLBACK[axis],
|
|
335
|
+
ARC_OPACITY,
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
// The view-plane disc faces the camera, so its rotation is the ONE that has
|
|
339
|
+
// to be rewritten per frame. Accent rather than an axis colour: it is the
|
|
340
|
+
// handle that belongs to no axis.
|
|
341
|
+
add("screen", "disc", new CircleGeometry(1, 24), new Quaternion(), "--accent", "#0099ff", DISC_OPACITY);
|
|
342
|
+
return built;
|
|
343
|
+
}, [theme]);
|
|
344
|
+
|
|
345
|
+
useEffect(
|
|
346
|
+
() => () => {
|
|
347
|
+
for (const piece of pieces) {
|
|
348
|
+
piece.geometry.dispose();
|
|
349
|
+
piece.material.dispose();
|
|
350
|
+
}
|
|
351
|
+
},
|
|
352
|
+
[pieces],
|
|
353
|
+
);
|
|
354
|
+
|
|
355
|
+
useFrame(() => {
|
|
356
|
+
const working = panes.working;
|
|
357
|
+
const camera = feed.workingCamera;
|
|
358
|
+
// `modalTarget` and not `selection.length` written out again: this is the
|
|
359
|
+
// same fork `G` takes, and one of them drifting is a gizmo that moves
|
|
360
|
+
// something other than what the pill says it will.
|
|
361
|
+
//
|
|
362
|
+
// A selection whose layers are all off-frame at this playhead anchors
|
|
363
|
+
// NOWHERE rather than falling through to the camera — silently retargeting
|
|
364
|
+
// a live selection's gizmo at the camera is the worst answer available.
|
|
365
|
+
const subject = modalTarget(selection.length > 0);
|
|
366
|
+
// A DRIVEN camera has no rig to hang arms on: a component is writing the
|
|
367
|
+
// camera the picture shows, and dragging the rig would move nothing in it.
|
|
368
|
+
// `FrustumGizmo` writes the bit one frame behind, which is invisible.
|
|
369
|
+
const centre =
|
|
370
|
+
subject === "camera"
|
|
371
|
+
? feed.cameraDriven
|
|
372
|
+
? null
|
|
373
|
+
: cameraApexAt()
|
|
374
|
+
: selectionCentre(feed, selection);
|
|
375
|
+
if (working === null || camera === null || centre === null) {
|
|
376
|
+
for (const piece of pieces) piece.object.visible = false;
|
|
377
|
+
feed.gizmoHit = null;
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
_origin.set(centre[0], centre[1], centre[2]);
|
|
382
|
+
const perPixel = worldPerPixel(camera, _origin, working.height);
|
|
383
|
+
// The anchor ON SCREEN, which is what every arm's own screen length is
|
|
384
|
+
// measured against below. An anchor behind the camera projects to garbage,
|
|
385
|
+
// so nothing is grabbable at all — the same refusal each arm makes.
|
|
386
|
+
const anchorVisible = !behind(_origin, camera);
|
|
387
|
+
const anchor = toScreen(_origin, camera, working.width, working.height);
|
|
388
|
+
// THE HIT TABLE, built innermost-first. The order IS the priority — see
|
|
389
|
+
// `gizmoHitAt`, which takes the first target that contains the point rather
|
|
390
|
+
// than the nearest, because a pad sits on top of the two arms bounding it.
|
|
391
|
+
const disc: GizmoTarget[] = [];
|
|
392
|
+
const pads: GizmoTarget[] = [];
|
|
393
|
+
const arms: GizmoTarget[] = [];
|
|
394
|
+
const arcs: GizmoTarget[] = [];
|
|
395
|
+
/** Each arc's projected polyline, kept so the DRAW pass writes the very
|
|
396
|
+
* points the HIT test measured — one curve, not two that agree. */
|
|
397
|
+
const arcWorld = new Map<GizmoHandleId, readonly Vec3[]>();
|
|
398
|
+
// Which handles turned out to be grabbable this frame, so the draw pass can
|
|
399
|
+
// dim the ones that are not without asking the geometry a second time.
|
|
400
|
+
const live = new Set<GizmoHandleId>();
|
|
401
|
+
// THE CAMERA GETS ARROWS ONLY — see the header. Its framing has no free z,
|
|
402
|
+
// and the pads would either duplicate the body drag or promise a motion the
|
|
403
|
+
// document cannot store.
|
|
404
|
+
const full = subject === "layers";
|
|
405
|
+
|
|
406
|
+
for (const { axis, world } of DESIGN_AXES) {
|
|
407
|
+
const root = alongAxis(centre, world, GIZMO_PX.armStart * perPixel);
|
|
408
|
+
const tip = alongAxis(centre, world, GIZMO_PX.arm * perPixel);
|
|
409
|
+
// An arm with EITHER end behind the working camera projects to garbage,
|
|
410
|
+
// so it is drawn but not grabbable — the same refusal the frustum's body
|
|
411
|
+
// makes, and for the same reason: there is no screen segment to measure
|
|
412
|
+
// against.
|
|
413
|
+
let grabbable = anchorVisible && !isBehind(root, camera) && !isBehind(tip, camera);
|
|
414
|
+
if (grabbable) {
|
|
415
|
+
const a = toScreen(_point.set(root[0], root[1], root[2]), camera, working.width, working.height);
|
|
416
|
+
const b = toScreen(_point.set(tip[0], tip[1], tip[2]), camera, working.width, working.height);
|
|
417
|
+
// AN ARM THAT COLLAPSED ON SCREEN IS DRAWN BUT NOT GRABBABLE. The gate
|
|
418
|
+
// lives in `armGrabbable`, which is pure so a decision about whether
|
|
419
|
+
// the whole widget works can be checked without a GL context.
|
|
420
|
+
grabbable = armGrabbable(a, b, MIN_ARM_SCREEN_PX);
|
|
421
|
+
if (grabbable) {
|
|
422
|
+
arms.push({
|
|
423
|
+
handle: `axis:${axis}`,
|
|
424
|
+
origin: centre,
|
|
425
|
+
direction: world,
|
|
426
|
+
shape: { type: "path", points: [a, b], slop: GIZMO_PX.grab },
|
|
427
|
+
});
|
|
428
|
+
live.add(`axis:${axis}`);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
// The camera's widget stops here on x and y; it keeps the z ARC below.
|
|
432
|
+
if (!full && axis !== "z") continue;
|
|
433
|
+
if (full) {
|
|
434
|
+
// The pad NORMAL to this axis, spanning the other two — the SAME four
|
|
435
|
+
// world corners the mesh is drawn at, out of the module that owns them.
|
|
436
|
+
const corners: [number, number][] = [];
|
|
437
|
+
let padVisible = anchorVisible;
|
|
438
|
+
for (const p of padCorners(
|
|
439
|
+
centre,
|
|
440
|
+
axis,
|
|
441
|
+
GIZMO_PX.padAt * perPixel,
|
|
442
|
+
(GIZMO_PX.pad / 2) * perPixel,
|
|
443
|
+
)) {
|
|
444
|
+
if (isBehind(p, camera)) {
|
|
445
|
+
padVisible = false;
|
|
446
|
+
break;
|
|
447
|
+
}
|
|
448
|
+
corners.push(toScreen(_point.set(p[0], p[1], p[2]), camera, working.width, working.height));
|
|
449
|
+
}
|
|
450
|
+
// A pad seen EDGE-ON projects to a sliver nothing can be inside, exactly
|
|
451
|
+
// as a collapsed arm does — and `designDelta` on its plane would then be
|
|
452
|
+
// a division by a grazing angle. `screenArea` is the same claim
|
|
453
|
+
// `armGrabbable` makes, in the units a quad has.
|
|
454
|
+
if (padVisible && screenArea(corners) >= MIN_PAD_SCREEN_AREA) {
|
|
455
|
+
pads.push({
|
|
456
|
+
handle: `plane:${axis}`,
|
|
457
|
+
origin: centre,
|
|
458
|
+
// The pad's own NORMAL: what its drag unprojects onto. See
|
|
459
|
+
// `GizmoGrab.direction`.
|
|
460
|
+
direction: world,
|
|
461
|
+
shape: { type: "quad", corners },
|
|
462
|
+
});
|
|
463
|
+
live.add(`plane:${axis}`);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// ── THE ROTATE ARC ──────────────────────────────────────────────────
|
|
468
|
+
//
|
|
469
|
+
// The SAME polyline is drawn and hit-tested, so an arc seen at an angle
|
|
470
|
+
// cannot have a grab shape that is a different curve from its picture.
|
|
471
|
+
// The whole arc is dropped when any point falls behind the camera, for
|
|
472
|
+
// the arm's reason: a projected ghost is not a curve anyone can aim at.
|
|
473
|
+
const points = arcPoints(centre, axis, GIZMO_PX.arc * perPixel, ARC_SEGMENTS);
|
|
474
|
+
arcWorld.set(`arc:${axis}`, points);
|
|
475
|
+
const screen: [number, number][] = [];
|
|
476
|
+
let arcVisible = anchorVisible;
|
|
477
|
+
for (const p of points) {
|
|
478
|
+
if (isBehind(p, camera)) {
|
|
479
|
+
arcVisible = false;
|
|
480
|
+
break;
|
|
481
|
+
}
|
|
482
|
+
screen.push(toScreen(_point.set(p[0], p[1], p[2]), camera, working.width, working.height));
|
|
483
|
+
}
|
|
484
|
+
// An arc seen EDGE-ON collapses to a straight line lying along its own
|
|
485
|
+
// chord — `ringAngle` still answers, but the projected curve gives the
|
|
486
|
+
// author nothing to aim along and every press near the anchor would grab
|
|
487
|
+
// it. `screenArea` of the closed polyline is the same claim `armGrabbable`
|
|
488
|
+
// and the pads make, in the units a curve has.
|
|
489
|
+
if (arcVisible && screenArea(screen) >= MIN_PAD_SCREEN_AREA) {
|
|
490
|
+
arcs.push({
|
|
491
|
+
handle: `arc:${axis}`,
|
|
492
|
+
origin: centre,
|
|
493
|
+
// The arc's own NORMAL: the axis the rotation turns ABOUT, and the
|
|
494
|
+
// plane its drag unprojects onto. See `GizmoGrab.direction`.
|
|
495
|
+
direction: world,
|
|
496
|
+
shape: { type: "path", points: screen, slop: GIZMO_PX.arcGrab },
|
|
497
|
+
});
|
|
498
|
+
live.add(`arc:${axis}`);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
if (full && anchorVisible) {
|
|
503
|
+
// The disc's normal is the camera's own forward, so a drag on it moves
|
|
504
|
+
// the selection in the plane of the screen — the one placement gesture
|
|
505
|
+
// that needs no axis at all.
|
|
506
|
+
const forward = cameraForward(camera);
|
|
507
|
+
disc.push({
|
|
508
|
+
handle: "screen",
|
|
509
|
+
origin: centre,
|
|
510
|
+
direction: forward,
|
|
511
|
+
shape: { type: "disc", at: anchor, radius: GIZMO_PX.screen },
|
|
512
|
+
});
|
|
513
|
+
live.add("screen");
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// ORDER IS PRIORITY: innermost first, which is the only order in which every
|
|
517
|
+
// handle stays reachable — and the ARMS outrank the ARCS deliberately, so
|
|
518
|
+
// the two points where each quarter arc crosses the axes that bound it
|
|
519
|
+
// belong to the arms.
|
|
520
|
+
const targets = [...disc, ...pads, ...arms, ...arcs];
|
|
521
|
+
feed.gizmoHit = (x, y) => {
|
|
522
|
+
const hit = gizmoHitAt(targets, x, y);
|
|
523
|
+
return hit === null
|
|
524
|
+
? null
|
|
525
|
+
: { subject, handle: hit.handle, origin: hit.origin, direction: hit.direction };
|
|
526
|
+
};
|
|
527
|
+
|
|
528
|
+
// ── DRAW ──────────────────────────────────────────────────────────────
|
|
529
|
+
const held = feed.activeHandle;
|
|
530
|
+
for (const piece of pieces) {
|
|
531
|
+
const axis = gizmoAxisOf(piece.handle);
|
|
532
|
+
// ARROWS AND THE ROLL ARC ARE THE CAMERA'S WHOLE WIDGET — see the header
|
|
533
|
+
// for why its framing gets no pads, no disc and no other arc.
|
|
534
|
+
const shown =
|
|
535
|
+
piece.kind === "shaft" || piece.kind === "head" || piece.handle === "arc:z" ? true : full;
|
|
536
|
+
piece.object.visible = shown;
|
|
537
|
+
if (!shown) continue;
|
|
538
|
+
if (piece.kind === "disc") {
|
|
539
|
+
// The only piece whose ROTATION moves: it faces the camera, which turns
|
|
540
|
+
// under the author's orbit while everything else is pinned to a design
|
|
541
|
+
// axis.
|
|
542
|
+
piece.object.quaternion.copy(camera.quaternion);
|
|
543
|
+
const r = GIZMO_PX.screen * perPixel;
|
|
544
|
+
placeAt(piece.object, centre, r, r, 1);
|
|
545
|
+
} else if (piece.kind === "pad" && axis !== null) {
|
|
546
|
+
const side = GIZMO_PX.pad * perPixel;
|
|
547
|
+
placeAt(piece.object, padCentre(centre, axis, GIZMO_PX.padAt * perPixel), side, side, 1);
|
|
548
|
+
} else if (piece.kind === "arc") {
|
|
549
|
+
// WORLD vertices, rewritten in place from the very points the hit test
|
|
550
|
+
// measured — the object stays at the origin with an identity matrix,
|
|
551
|
+
// which is the same construction the frustum gizmo uses.
|
|
552
|
+
const points = arcWorld.get(piece.handle);
|
|
553
|
+
if (points !== undefined) writeArc(piece.geometry, points);
|
|
554
|
+
} else if (axis !== null) {
|
|
555
|
+
const world = DESIGN_AXES.find((a) => a.axis === axis)!.world;
|
|
556
|
+
const shaftEnd = (GIZMO_PX.arm - GIZMO_PX.arrowHead) * perPixel;
|
|
557
|
+
const start = GIZMO_PX.armStart * perPixel;
|
|
558
|
+
// Two pieces share `axis:*`, and `kind` is what tells them apart: the
|
|
559
|
+
// SHAFT is the cylinder, the HEAD is the cone. Neither is a handle of
|
|
560
|
+
// its own — a press on either is a press on the arm.
|
|
561
|
+
if (piece.kind === "shaft") {
|
|
562
|
+
placeAt(
|
|
563
|
+
piece.object,
|
|
564
|
+
alongAxis(centre, world, (start + shaftEnd) / 2),
|
|
565
|
+
GIZMO_PX.shaft * perPixel,
|
|
566
|
+
Math.max(shaftEnd - start, 0),
|
|
567
|
+
GIZMO_PX.shaft * perPixel,
|
|
568
|
+
);
|
|
569
|
+
} else {
|
|
570
|
+
placeAt(
|
|
571
|
+
piece.object,
|
|
572
|
+
alongAxis(centre, world, shaftEnd + (GIZMO_PX.arrowHead * perPixel) / 2),
|
|
573
|
+
GIZMO_PX.arrowWidth * perPixel,
|
|
574
|
+
GIZMO_PX.arrowHead * perPixel,
|
|
575
|
+
GIZMO_PX.arrowWidth * perPixel,
|
|
576
|
+
);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
// ── HOT, DIM, OR AT REST ────────────────────────────────────────────
|
|
581
|
+
//
|
|
582
|
+
// Blender highlights the gizmo element under the cursor and dims the rest
|
|
583
|
+
// once a transform is running, and this pane did neither — so "is this
|
|
584
|
+
// grabbable" was something the author learned by trying, and a live drag
|
|
585
|
+
// kept three full-strength lines across the geometry being judged.
|
|
586
|
+
//
|
|
587
|
+
// Here, in the frame loop, off two plain fields the pointer lane wrote. A
|
|
588
|
+
// hover therefore costs one uniform write and zero React renders, which
|
|
589
|
+
// is the whole reason `feed.hoverHandle` is a field and not state.
|
|
590
|
+
const hot = held === null ? feed.hoverHandle === piece.handle : held === piece.handle;
|
|
591
|
+
const dim = held === null ? !live.has(piece.handle) : held !== piece.handle;
|
|
592
|
+
const wantTint = hot ? HOVER_TINT : 0;
|
|
593
|
+
const wantAlpha = dim ? DEAD_OPACITY * piece.restAlpha : piece.restAlpha;
|
|
594
|
+
if (piece.tint !== wantTint) {
|
|
595
|
+
piece.tint = wantTint;
|
|
596
|
+
piece.material.color.copy(tokenColor(piece.base, piece.fallback)).lerp(hotColor, wantTint);
|
|
597
|
+
}
|
|
598
|
+
if (piece.alpha !== wantAlpha) {
|
|
599
|
+
piece.alpha = wantAlpha;
|
|
600
|
+
piece.material.opacity = wantAlpha;
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
}, 0);
|
|
604
|
+
|
|
605
|
+
useEffect(
|
|
606
|
+
() => () => {
|
|
607
|
+
feed.gizmoHit = null;
|
|
608
|
+
},
|
|
609
|
+
[feed],
|
|
610
|
+
);
|
|
611
|
+
|
|
612
|
+
return (
|
|
613
|
+
<>
|
|
614
|
+
{pieces.map((piece, i) => (
|
|
615
|
+
<primitive key={`${piece.handle}-${i}`} object={piece.object} />
|
|
616
|
+
))}
|
|
617
|
+
</>
|
|
618
|
+
);
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
/** An arc's world points into its position buffer, past the frustum gizmo's own
|
|
622
|
+
* half-pixel gate: this runs on every committed frame, and a parked playhead
|
|
623
|
+
* with a selection must not re-upload three buffers per seek. */
|
|
624
|
+
function writeArc(geometry: BufferGeometry, points: readonly Vec3[]): void {
|
|
625
|
+
const attribute = geometry.getAttribute("position") as BufferAttribute;
|
|
626
|
+
const buffer = attribute.array as Float32Array;
|
|
627
|
+
let moved = false;
|
|
628
|
+
for (let i = 0; i < points.length; i++) {
|
|
629
|
+
const p = points[i]!;
|
|
630
|
+
for (let c = 0; c < 3; c++) {
|
|
631
|
+
const at = i * 3 + c;
|
|
632
|
+
if (Math.abs(buffer[at]! - p[c]!) > 0.5) moved = true;
|
|
633
|
+
buffer[at] = p[c]!;
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
if (moved) attribute.needsUpdate = true;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
function isBehind(point: Vec3, camera: Camera): boolean {
|
|
640
|
+
return behind(_point.set(point[0], point[1], point[2]), camera);
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
/** The camera's own forward, in world. The third basis column of its world
|
|
644
|
+
* matrix points BACK from the eye, so the forward is its negation. */
|
|
645
|
+
function cameraForward(camera: Camera): Vec3 {
|
|
646
|
+
const e = camera.matrixWorld.elements;
|
|
647
|
+
return [-e[8]!, -e[9]!, -e[10]!];
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
/** One piece's per-frame update: three position writes, three scale writes and
|
|
651
|
+
* one matrix compose. No allocation, because a parked playhead with a
|
|
652
|
+
* selection has to cost nothing. */
|
|
653
|
+
function placeAt(object: Object3D, at: Vec3, sx: number, sy: number, sz: number): void {
|
|
654
|
+
object.position.set(at[0], at[1], at[2]);
|
|
655
|
+
object.scale.set(sx, sy, sz);
|
|
656
|
+
object.updateMatrix();
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
/**
|
|
660
|
+
* Where the gizmo sits: the mean of the selected layers' own origins.
|
|
661
|
+
*
|
|
662
|
+
* The GROUP origin out of `feed.frames`, never the mesh's centre — a paint
|
|
663
|
+
* layer's plane is grown by the rasterizer's shadow padding, so the mesh would
|
|
664
|
+
* put the gizmo a blur radius away from the thing it moves (the same reason
|
|
665
|
+
* `collectFrames` reads userData).
|
|
666
|
+
*
|
|
667
|
+
* Null when nothing selected is on screen this frame, which includes a
|
|
668
|
+
* selection whose lifetime has ended: a handle drawn for a layer that is not
|
|
669
|
+
* there would drag something the author cannot see.
|
|
670
|
+
*/
|
|
671
|
+
function selectionCentre(feed: StageFeed, selection: readonly string[]): Vec3 | null {
|
|
672
|
+
const frames = feed.frames;
|
|
673
|
+
if (frames === null || selection.length === 0) return null;
|
|
674
|
+
let x = 0;
|
|
675
|
+
let y = 0;
|
|
676
|
+
let z = 0;
|
|
677
|
+
let count = 0;
|
|
678
|
+
// ONE FRAME PER ID, the first — the same rule `StageTools`' `byId` follows.
|
|
679
|
+
// A cloner stamps its children N times under one id, and averaging all the
|
|
680
|
+
// clones put the gizmo in the empty space between them rather than on the
|
|
681
|
+
// layer the author selected.
|
|
682
|
+
const seen = new Set<string>();
|
|
683
|
+
for (const frame of frames) {
|
|
684
|
+
if (!selection.includes(frame.id) || seen.has(frame.id)) continue;
|
|
685
|
+
seen.add(frame.id);
|
|
686
|
+
const e = frame.matrixWorld.elements;
|
|
687
|
+
x += e[12]!;
|
|
688
|
+
y += e[13]!;
|
|
689
|
+
z += e[14]!;
|
|
690
|
+
count += 1;
|
|
691
|
+
}
|
|
692
|
+
return count === 0 ? null : [x / count, y / count, z / count];
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
// `worldPerPixel` lives in project.ts now: the camera body (FrustumGizmo) sizes
|
|
696
|
+
// itself with the same function, and one copy is how the two stay one rule.
|