cozyclay 3.0.3 → 3.1.1
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/CHANGELOG.md +93 -0
- package/INSTALL_FOR_AGENTS.md +146 -0
- package/README.md +74 -192
- package/THIRD_PARTY_NOTICES.md +0 -21
- package/bin/agent/agent-routes.mjs +29 -62
- package/bin/agent/agent-runner.mjs +5 -3
- package/bin/agent/main-agent.mjs +70 -0
- package/bin/agent/main-prompt.mjs +17 -0
- package/bin/agent/plan-tool.mjs +114 -0
- package/bin/agent/script-tool.mjs +2 -0
- package/bin/agent/sculpt-sketch.mjs +190 -0
- package/bin/agent/session-store.mjs +4 -3
- package/bin/agent/sketch-code.mjs +151 -0
- package/bin/agent/sketch-sandbox.mjs +102 -0
- package/bin/agent/studio-prompt.mjs +1 -1
- package/bin/agent/studio-task-routes.mjs +185 -0
- package/bin/agent/studio-tasks.mjs +156 -0
- package/bin/agent/studio-tools.mjs +3 -2
- package/bin/agent/studio-turn-transaction.mjs +76 -0
- package/bin/agent/turn-frames.mjs +4 -0
- package/bin/cozyclay.mjs +5 -7
- package/dist/app/index.html +7 -6
- package/dist/assets/TaskBoard-ngdqiu7u.js +1 -0
- package/dist/assets/app-BlwOpest.css +1 -0
- package/dist/assets/app-DmR7jBkz.js +5144 -0
- package/dist/assets/compile-worker-D0pKioa0.js +5 -0
- package/dist/assets/landing-y3EqDgSZ.js +1 -0
- package/dist/assets/modulepreload-polyfill-P2Xu9kJm.js +1 -0
- package/dist/assets/motion3-compile-BB3_1-EB.js +1 -0
- package/dist/assets/scene-asset-sync-CBx7XRti.css +1 -0
- package/dist/assets/scene-asset-sync-Cgdtr1Yw.js +13 -0
- package/dist/assets/task-board-ErQvfoaz.js +14 -0
- package/dist/assets/workflow-zQM-EKW1.js +179 -0
- package/dist/assets/x-bot-tpose-body-BusyR3eg.json +1 -0
- package/dist/assets/y-bot-tpose-body-Dy1VKChZ.json +1 -0
- package/dist/cozyclay-package.json +1 -1
- package/dist/greybox-to-video/index.html +2 -3
- package/dist/index.html +7 -57
- package/dist/llms.txt +2 -1
- package/dist/previs-software/index.html +2 -2
- package/dist/privacy/index.html +3 -7
- package/dist/seedance-camera-control/index.html +2 -2
- package/dist/sitemap.xml +5 -5
- package/dist/sw.js +5 -1
- package/dist/thumbs/blank-stage-dark.webp +0 -0
- package/dist/thumbs/blank-stage-light.webp +0 -0
- package/dist/workflow/index.html +5 -4
- package/mcp/mesh-file.mjs +3 -1
- package/mcp/server.mjs +3 -0
- package/mcp/tool-handlers.mjs +2 -2
- package/package.json +5 -3
- package/src/App.jsx +51 -282
- package/src/analytics.js +3 -148
- package/src/app-stage.jsx +1 -1
- package/src/asset-pane.jsx +5 -10
- package/src/auto-flat.js +10 -0
- package/src/bus-tasks.js +11 -5
- package/src/command-bus.js +1 -1
- package/src/commands/elements.js +4 -3
- package/src/commands/motion3-compile.js +51 -27
- package/src/commands/motion3.js +3 -3
- package/src/commands/objects.js +1 -1
- package/src/commands/project.js +1 -1
- package/src/commands/shot.js +16 -4
- package/src/controls.jsx +0 -11
- package/src/cuts.js +11 -0
- package/src/domains/cast.js +3 -2
- package/src/domains/motion.js +3 -10
- package/src/domains/playback.js +32 -0
- package/src/domains/scenes.js +3 -22
- package/src/domains/shots.js +5 -5
- package/src/dualview.jsx +11 -15
- package/src/hierarchy-panel.css +18 -4
- package/src/hierarchy-panel.jsx +12 -11
- package/src/kits/held.js +3 -2
- package/src/light-blockers.js +2 -2
- package/src/main.jsx +5 -0
- package/src/material-look.js +189 -0
- package/src/material-presets.js +78 -0
- package/src/material-textures.js +157 -0
- package/src/motion/ground.js +5 -14
- package/src/motion/playback-frame-store.js +79 -0
- package/src/motion/playback-probe.js +16 -0
- package/src/motion/playback-subject.js +7 -0
- package/src/motion/timeline.css +43 -5
- package/src/motion/timeline.jsx +4 -3
- package/src/motion3/body.js +37 -5
- package/src/motion3/carried.js +11 -3
- package/src/motion3/compile-job.js +1 -1
- package/src/motion3/compiler/compile.js +7 -1
- package/src/motion3/compiler/finger-close.js +137 -0
- package/src/motion3/compiler/limbs.js +18 -3
- package/src/motion3/compiler/pose.js +6 -0
- package/src/motion3/compiler/validate-objects.js +17 -2
- package/src/motion3/compiler/validate-parts.js +2 -1
- package/src/motion3/compiler/vocab.js +4 -4
- package/src/motion3/describe.js +2 -2
- package/src/motion3/fix/index.js +5 -3
- package/src/motion3/flight.js +38 -0
- package/src/motion3/free-motion.js +10 -7
- package/src/motion3/load.js +125 -0
- package/src/motion3/mass.js +23 -0
- package/src/motion3/object-keys.js +11 -6
- package/src/motion3/object-pose.js +11 -3
- package/src/motion3/pipeline.js +3 -0
- package/src/motion3/qa/explain.js +3 -1
- package/src/motion3/qa/grasp-checks.js +123 -0
- package/src/motion3/qa/hints-grasp.js +71 -0
- package/src/motion3/qa/index.js +3 -1
- package/src/motion3/qa/object-checks.js +7 -2
- package/src/motion3/qa/patch.js +2 -1
- package/src/motion3/qa/spec-checks.js +10 -1
- package/src/motion3/schema/fields.js +3 -3
- package/src/motion3/schema/topics.js +9 -8
- package/src/motion3/schema/types.js +4 -1
- package/src/motion3/seat.js +84 -0
- package/src/motion3/settle.js +11 -6
- package/src/motion3/shapes.js +70 -0
- package/src/motion3/skills/catch.js +71 -0
- package/src/motion3/skills/index.js +10 -3
- package/src/motion3/skills/info.js +13 -0
- package/src/motion3/skills/throw.js +72 -0
- package/src/object-catalog.jsx +0 -4
- package/src/object-gizmo.jsx +2 -1
- package/src/object-look.js +9 -5
- package/src/object-path.js +1 -1
- package/src/panels/ProjectPanel.jsx +42 -21
- package/src/panels/details.css +15 -4
- package/src/popover.js +37 -0
- package/src/posestudio.jsx +15 -10
- package/src/primitive-look.js +38 -0
- package/src/project-browser-art.jsx +19 -0
- package/src/project-browser.css +74 -121
- package/src/project-browser.jsx +36 -60
- package/src/props.jsx +24 -35
- package/src/render-passes.js +7 -1
- package/src/retired-kinds.js +37 -0
- package/src/scene-objects.js +5 -9
- package/src/scenes.js +4 -5
- package/src/sculpt-colours.js +65 -0
- package/src/sculpt-compare.js +13 -50
- package/src/sculpt-csg.js +112 -0
- package/src/sculpt-curve.js +123 -0
- package/src/sculpt-cutters.js +43 -0
- package/src/sculpt-detail.js +207 -0
- package/src/sculpt-geometry-loft-convex.js +100 -0
- package/src/sculpt-geometry-shapes.js +250 -0
- package/src/sculpt-geometry.js +33 -15
- package/src/sculpt-loft-rings.js +225 -0
- package/src/sculpt-noise.js +65 -0
- package/src/sculpt-part-fields.js +27 -0
- package/src/sculpt-recipe-read.js +65 -0
- package/src/sculpt-recipe.js +33 -75
- package/src/sculpt-run.js +1 -1
- package/src/sculpt-scatter-items.js +183 -0
- package/src/sculpt-scatter-shape.js +57 -0
- package/src/sculpt-scatter.js +124 -0
- package/src/sculpt-shapes-curved.js +211 -0
- package/src/sculpt-shapes-loft-convex.js +218 -0
- package/src/sculpt-shapes.js +138 -0
- package/src/sculpt-sketch-state.js +76 -0
- package/src/sculpt-sketch.jsx +124 -0
- package/src/sculpt-surface-fields.js +65 -0
- package/src/settings-menu.jsx +1 -20
- package/src/shell/BottomDock.jsx +9 -9
- package/src/shell/MenuBar.jsx +1 -9
- package/src/shell/PreferencesDialog.jsx +3 -4
- package/src/shell/ViewportToolbar.jsx +3 -3
- package/src/shell/add-camera-shot.js +11 -0
- package/src/shell/glass-regions.css +3 -2
- package/src/shell/glass.css +0 -5
- package/src/shell/lens-scrub-math.js +51 -0
- package/src/shell/lens-scrub.css +73 -0
- package/src/shell/lens-scrub.jsx +98 -0
- package/src/shell/mode.css +6 -5
- package/src/shell/preferences.css +1 -32
- package/src/shell/shell.css +18 -1
- package/src/shell/studio-shell-context.js +5 -0
- package/src/shot-frame.js +26 -0
- package/src/stage-canvas-rigs.jsx +7 -8
- package/src/stage-characters.jsx +26 -0
- package/src/stage-gizmo.js +43 -0
- package/src/stage-labels-ko.js +0 -6
- package/src/stage-startup-defaults.js +3 -9
- package/src/starter-scenes.js +5 -11
- package/src/studio-agent-commands.js +2 -2
- package/src/studio-agent-protocol.js +2 -7
- package/src/studio-app-binding.js +1 -1
- package/src/studio-elements.js +6 -2
- package/src/styles/themes.css +13 -0
- package/src/styles.css +6 -674
- package/src/text/text-object.jsx +1 -1
- package/src/ui.jsx +8 -16
- package/src/use-embed-bridge.js +3 -6
- package/src/use-render-activity.js +3 -1
- package/src/view-ghosts.jsx +5 -3
- package/src/viewport-visible-rect.js +37 -0
- package/src/workflow/AgentPanel.jsx +79 -46
- package/src/workflow/AgentPlanCard.jsx +41 -0
- package/src/workflow/AgentProgressCard.jsx +1 -1
- package/src/workflow/TaskBoard.jsx +102 -0
- package/src/workflow/TaskInline.jsx +114 -0
- package/src/workflow/agent-client.js +25 -36
- package/src/workflow/agent-panel.css +14 -1
- package/src/workflow/agent-plan.css +93 -0
- package/src/workflow/agent-plan.js +59 -0
- package/src/workflow/attachment-image.js +10 -3
- package/src/workflow/attachment-video.js +91 -0
- package/src/workflow/task-board.css +98 -0
- package/src/workflow/task-board.js +169 -0
- package/tools/check-eslint.mjs +2 -1
- package/tools/check-knip.mjs +2 -1
- package/tools/dev/pages/greybox-to-video.html +2 -3
- package/tools/dev/pages/previs-software.html +2 -2
- package/tools/dev/pages/privacy.html +3 -7
- package/tools/dev/pages/seedance-camera-control.html +2 -2
- package/tools/dev-full.mjs +11 -2
- package/tools/eslint-baseline.json +6 -6
- package/tools/knip-baseline.json +0 -11
- package/tools/loc-baseline.json +30 -31
- package/tools/metrics-snapshot.mjs +43 -5
- package/tools/render-start-thumbs.mjs +175 -0
- package/tools/run-tests.mjs +10 -17
- package/tools/sitemap.mjs +1 -1
- package/dist/assets/analytics-BibyE5ar.js +0 -6
- package/dist/assets/app-DhRJ-PBz.js +0 -5088
- package/dist/assets/app-f-fkV5uP.css +0 -1
- package/dist/assets/compile-worker-DqiA4AaN.js +0 -5
- package/dist/assets/first-shot-handoff-Qx7SKl38.js +0 -1
- package/dist/assets/landing-DYg87MDY.js +0 -1
- package/dist/assets/scene-asset-sync-DBYQBR0T.js +0 -20
- package/dist/assets/scene-asset-sync-_S8NQl5B.css +0 -1
- package/dist/assets/workflow-BSA22Ofr.js +0 -179
- package/dist/scenes/city-block.cclayproject +0 -1
- package/src/burn-in.js +0 -91
- package/src/camera-tutorial.jsx +0 -520
- package/src/facing-marks.js +0 -88
- package/src/first-shot-handoff.js +0 -44
- package/src/panels/motion.css +0 -197
- package/src/set-pieces.jsx +0 -183
- package/src/tutorial-analytics.js +0 -86
- package/src/tutorial-walk.js +0 -17
- package/src/use-camera-tutorial.js +0 -50
- package/src/use-semantic-state.js +0 -12
- package/tools/metrics/archive-traffic.mjs +0 -138
- package/tools/perf/ik-camera-drag.mjs +0 -138
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// Start-screen art: the brand mark and the template thumbnail that fills both
|
|
2
|
+
// the card and the larger preview. Thumbnails are real studio renders, one per
|
|
3
|
+
// theme, made by tools/render-start-thumbs.mjs into public/thumbs/. A template
|
|
4
|
+
// without a render (a starter added since the last run) keeps the plain tile
|
|
5
|
+
// its wrapper already paints.
|
|
6
|
+
import { useState } from "react";
|
|
7
|
+
import { useUiTheme } from "./theme.js";
|
|
8
|
+
|
|
9
|
+
export function TemplateThumb({ id }) {
|
|
10
|
+
const theme = useUiTheme();
|
|
11
|
+
const src = `/thumbs/${id}-${theme}.webp`;
|
|
12
|
+
const [failed, setFailed] = useState(null);
|
|
13
|
+
if (failed === src) return null;
|
|
14
|
+
return <img className="v2-start-art" src={src} alt="" aria-hidden="true" draggable="false" decoding="async" onError={() => setFailed(src)} />;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function BrandMark() {
|
|
18
|
+
return <img className="v2-start-brand-mark" src="/icons/icon-192.png" alt="" aria-hidden="true" draggable="false" width="26" height="26" />;
|
|
19
|
+
}
|
package/src/project-browser.css
CHANGED
|
@@ -102,11 +102,17 @@
|
|
|
102
102
|
}
|
|
103
103
|
|
|
104
104
|
.v2-start-screen .v2-start-brand-mark {
|
|
105
|
+
display: block;
|
|
106
|
+
box-sizing: border-box;
|
|
105
107
|
width: var(--start-brand-mark, 26px);
|
|
106
108
|
height: var(--start-brand-mark, 26px);
|
|
107
109
|
flex: 0 0 var(--start-brand-mark, 26px);
|
|
108
|
-
|
|
109
|
-
|
|
110
|
+
padding: 1px;
|
|
111
|
+
border-radius: 7px;
|
|
112
|
+
/* The frog keeps its own blue on its own white tile in both themes. */
|
|
113
|
+
background: #fff;
|
|
114
|
+
box-shadow: 0 0 0 var(--start-border-width, 1px) color-mix(in srgb, var(--text-1, #e4e4e7) 10%, transparent);
|
|
115
|
+
object-fit: contain;
|
|
110
116
|
}
|
|
111
117
|
|
|
112
118
|
.v2-start-screen .v2-start-nav-list {
|
|
@@ -196,30 +202,7 @@
|
|
|
196
202
|
color: var(--text-1, #e4e4e7);
|
|
197
203
|
}
|
|
198
204
|
|
|
199
|
-
|
|
200
|
-
display: flex;
|
|
201
|
-
align-items: center;
|
|
202
|
-
gap: var(--start-tab-gap, 20px);
|
|
203
|
-
min-height: var(--start-tab-height, 24px);
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
.v2-start-screen .v2-start-tab {
|
|
207
|
-
height: var(--start-tab-height, 24px);
|
|
208
|
-
padding: 0 0 var(--start-tab-underline-space, 8px);
|
|
209
|
-
border: 0;
|
|
210
|
-
border-bottom: var(--start-active-width, 1.5px) solid var(--transparent, transparent);
|
|
211
|
-
background: var(--transparent, transparent);
|
|
212
|
-
color: var(--text-3, #636369);
|
|
213
|
-
white-space: nowrap;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
.v2-start-screen .v2-start-tab:hover,
|
|
217
|
-
.v2-start-screen .v2-start-tab.active {
|
|
218
|
-
border-bottom-color: var(--select, #e8a33d);
|
|
219
|
-
color: var(--text-selected, #fff);
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
/* Project mode (previs flag): a two-up segmented switch above the tabs. Each
|
|
205
|
+
/* Project mode (previs flag): a two-up segmented switch above the cards. Each
|
|
223
206
|
segment carries its label and the one-line promise of what the mode makes. */
|
|
224
207
|
.v2-start-screen .v2-start-mode {
|
|
225
208
|
display: grid;
|
|
@@ -291,69 +274,27 @@
|
|
|
291
274
|
}
|
|
292
275
|
|
|
293
276
|
.v2-start-screen .v2-start-template-art {
|
|
294
|
-
|
|
277
|
+
display: block;
|
|
278
|
+
overflow: hidden;
|
|
295
279
|
width: var(--start-template-art-width, 100%);
|
|
296
280
|
aspect-ratio: 16 / 10;
|
|
297
|
-
overflow: hidden;
|
|
298
281
|
border-radius: var(--r-2, 6px);
|
|
282
|
+
outline: var(--start-border-width, 1px) solid color-mix(in srgb, var(--text-1, #e4e4e7) 9%, transparent);
|
|
283
|
+
outline-offset: -1px;
|
|
299
284
|
background: var(--surface-raised, #1e1e21);
|
|
300
|
-
box-shadow: inset 0 0 0 var(--start-border-width, 1px) var(--transparent, transparent);
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
.v2-start-screen .v2-start-template-art::before,
|
|
304
|
-
.v2-start-screen .v2-start-template-art::after {
|
|
305
|
-
content: "";
|
|
306
|
-
position: absolute;
|
|
307
|
-
display: block;
|
|
308
|
-
background: var(--surface-active, #26262a);
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
.v2-start-screen .v2-start-template-art::before {
|
|
312
|
-
left: var(--start-art-block-left, 12%);
|
|
313
|
-
bottom: var(--start-art-block-bottom, 16%);
|
|
314
|
-
width: var(--start-art-block-width, 36%);
|
|
315
|
-
height: var(--start-art-block-height, 48%);
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
.v2-start-screen .v2-start-template-art::after {
|
|
319
|
-
right: var(--start-art-block-right, 12%);
|
|
320
|
-
bottom: var(--start-art-block-bottom, 16%);
|
|
321
|
-
width: var(--start-art-small-width, 20%);
|
|
322
|
-
height: var(--start-art-small-height, 24%);
|
|
323
|
-
background: var(--field, #0f0f11);
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
.v2-start-screen .v2-start-template-art.sample::before {
|
|
327
|
-
width: var(--start-sample-block-width, 54%);
|
|
328
|
-
height: var(--start-sample-block-height, 28%);
|
|
329
|
-
background: var(--primary, #7258a0);
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
.v2-start-screen .v2-start-template-art.sample::after {
|
|
333
|
-
width: var(--start-sample-small-width, 10%);
|
|
334
|
-
height: var(--start-sample-small-height, 52%);
|
|
335
|
-
background: var(--camera, #a78bfa);
|
|
336
285
|
}
|
|
337
286
|
|
|
338
287
|
.v2-start-screen .v2-start-template.selected .v2-start-template-art {
|
|
339
|
-
|
|
288
|
+
outline: var(--start-selected-width, 1.5px) solid var(--select, #e8a33d);
|
|
289
|
+
outline-offset: -1.5px;
|
|
340
290
|
}
|
|
341
291
|
|
|
342
292
|
.v2-start-screen .v2-start-template:hover .v2-start-template-art {
|
|
343
|
-
|
|
293
|
+
outline-color: var(--text-3, #636369);
|
|
344
294
|
}
|
|
345
295
|
|
|
346
296
|
.v2-start-screen .v2-start-template.selected:hover .v2-start-template-art {
|
|
347
|
-
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
.v2-start-screen .v2-start-template-code {
|
|
351
|
-
position: absolute;
|
|
352
|
-
left: var(--sp-1, 8px);
|
|
353
|
-
bottom: var(--sp-1, 8px);
|
|
354
|
-
color: var(--text-off, #4f4f55);
|
|
355
|
-
font-family: var(--mono, 'JetBrains Mono'), ui-monospace, monospace;
|
|
356
|
-
font-size: var(--start-code-size, 10px);
|
|
297
|
+
outline-color: var(--select, #e8a33d);
|
|
357
298
|
}
|
|
358
299
|
|
|
359
300
|
.v2-start-screen .v2-start-template-copy {
|
|
@@ -382,6 +323,43 @@
|
|
|
382
323
|
white-space: nowrap;
|
|
383
324
|
}
|
|
384
325
|
|
|
326
|
+
/* Open-project card: same slot and caption as a template, dashed art. */
|
|
327
|
+
.v2-start-screen .v2-start-add-card {
|
|
328
|
+
min-width: var(--start-template-min-width, 0);
|
|
329
|
+
display: flex;
|
|
330
|
+
flex-direction: column;
|
|
331
|
+
gap: var(--sp-2, 12px);
|
|
332
|
+
padding: 0;
|
|
333
|
+
border: 0;
|
|
334
|
+
background: var(--transparent, transparent);
|
|
335
|
+
color: var(--text-1, #e4e4e7);
|
|
336
|
+
text-align: left;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
.v2-start-screen .v2-start-add-art {
|
|
340
|
+
display: grid;
|
|
341
|
+
place-items: center;
|
|
342
|
+
width: var(--start-template-art-width, 100%);
|
|
343
|
+
aspect-ratio: 16 / 10;
|
|
344
|
+
border: var(--start-border-width, 1px) dashed var(--text-3, #636369);
|
|
345
|
+
border-radius: var(--r-2, 6px);
|
|
346
|
+
color: var(--text-3, #636369);
|
|
347
|
+
transition: border-color 120ms ease, color 120ms ease;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
.v2-start-screen .v2-start-add-card:hover .v2-start-add-art,
|
|
351
|
+
.v2-start-screen .v2-start-add-card:focus-visible .v2-start-add-art {
|
|
352
|
+
border-color: var(--select, #e8a33d);
|
|
353
|
+
color: var(--select, #e8a33d);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
.v2-start-screen .v2-start-add-card.selected .v2-start-add-art {
|
|
357
|
+
border-style: solid;
|
|
358
|
+
border-color: var(--select, #e8a33d);
|
|
359
|
+
box-shadow: inset 0 0 0 0.5px var(--select, #e8a33d);
|
|
360
|
+
color: var(--select, #e8a33d);
|
|
361
|
+
}
|
|
362
|
+
|
|
385
363
|
/* --------------------------------------------------------------- recent --- */
|
|
386
364
|
|
|
387
365
|
.v2-start-screen .v2-start-recent-block,
|
|
@@ -510,11 +488,6 @@
|
|
|
510
488
|
padding: var(--sp-1, 8px) 0;
|
|
511
489
|
}
|
|
512
490
|
|
|
513
|
-
.v2-start-screen .v2-start-file-actions {
|
|
514
|
-
margin-top: auto;
|
|
515
|
-
padding-top: var(--sp-1, 8px);
|
|
516
|
-
}
|
|
517
|
-
|
|
518
491
|
.v2-start-screen .v2-start-learn {
|
|
519
492
|
max-width: var(--start-learn-max-width, 520px);
|
|
520
493
|
display: flex;
|
|
@@ -537,6 +510,11 @@
|
|
|
537
510
|
line-height: 1.55;
|
|
538
511
|
}
|
|
539
512
|
|
|
513
|
+
.v2-start-screen .v2-start-file-actions {
|
|
514
|
+
margin-top: auto;
|
|
515
|
+
padding-top: var(--sp-1, 8px);
|
|
516
|
+
}
|
|
517
|
+
|
|
540
518
|
/* --------------------------------------------------------------- preview --- */
|
|
541
519
|
|
|
542
520
|
.v2-start-screen .v2-start-preview {
|
|
@@ -550,51 +528,26 @@
|
|
|
550
528
|
}
|
|
551
529
|
|
|
552
530
|
.v2-start-screen .v2-start-preview-art {
|
|
553
|
-
|
|
554
|
-
|
|
531
|
+
display: block;
|
|
532
|
+
flex: 0 0 auto;
|
|
533
|
+
/* A framed card inset to the copy column below it, not an edge-to-edge band. */
|
|
534
|
+
width: auto;
|
|
535
|
+
margin: var(--start-preview-copy-pad-x, 20px) var(--start-preview-copy-pad-x, 20px) 0;
|
|
555
536
|
aspect-ratio: 16 / 9;
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
537
|
+
overflow: hidden;
|
|
538
|
+
border-radius: var(--r-2, 6px);
|
|
539
|
+
outline: var(--start-border-width, 1px) solid color-mix(in srgb, var(--text-1, #e4e4e7) 9%, transparent);
|
|
540
|
+
outline-offset: -1px;
|
|
560
541
|
background: var(--surface-raised, #1e1e21);
|
|
561
542
|
}
|
|
562
543
|
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
position: absolute;
|
|
544
|
+
/* Template art: a real studio render (public/thumbs, one per theme). The
|
|
545
|
+
wrapper paints a plain tile underneath for a template without one. */
|
|
546
|
+
.v2-start-screen .v2-start-art {
|
|
567
547
|
display: block;
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
.v2-start-screen .v2-start-preview-art::before {
|
|
572
|
-
left: var(--preview-block-left, 14%);
|
|
573
|
-
bottom: var(--preview-block-bottom, 16%);
|
|
574
|
-
width: var(--preview-block-width, 34%);
|
|
575
|
-
height: var(--preview-block-height, 50%);
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
.v2-start-screen .v2-start-preview-art::after {
|
|
579
|
-
right: var(--preview-block-right, 14%);
|
|
580
|
-
bottom: var(--preview-block-bottom, 16%);
|
|
581
|
-
width: var(--preview-small-width, 18%);
|
|
582
|
-
height: var(--preview-small-height, 28%);
|
|
583
|
-
background: var(--primary, #7258a0);
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
.v2-start-screen .v2-start-preview-art.sample::before {
|
|
587
|
-
width: var(--preview-sample-width, 58%);
|
|
588
|
-
height: var(--preview-sample-height, 30%);
|
|
589
|
-
background: var(--camera, #a78bfa);
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
.v2-start-screen .v2-start-preview-art span {
|
|
593
|
-
position: relative;
|
|
594
|
-
z-index: var(--start-art-label-layer, 1);
|
|
595
|
-
color: var(--text-off, #4f4f55);
|
|
596
|
-
font-family: var(--mono, 'JetBrains Mono'), ui-monospace, monospace;
|
|
597
|
-
font-size: var(--start-preview-label-size, 10.5px);
|
|
548
|
+
width: 100%;
|
|
549
|
+
height: 100%;
|
|
550
|
+
object-fit: cover;
|
|
598
551
|
}
|
|
599
552
|
|
|
600
553
|
.v2-start-screen .v2-start-preview-copy {
|
package/src/project-browser.jsx
CHANGED
|
@@ -14,21 +14,16 @@ import {
|
|
|
14
14
|
storeProjectsDirectory,
|
|
15
15
|
DEFAULT_PREVIS_MODE,
|
|
16
16
|
} from "./project.js";
|
|
17
|
+
import { BrandMark, TemplateThumb } from "./project-browser-art.jsx";
|
|
17
18
|
import "./project-browser.css";
|
|
18
19
|
|
|
19
20
|
const APP_VERSION = packageInfo.version;
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
name: "Blank Stage",
|
|
24
|
-
category: "All",
|
|
25
|
-
description: "An empty stage for blocking a new shot.",
|
|
26
|
-
kind: "blank",
|
|
27
|
-
code: "NEW",
|
|
28
|
-
});
|
|
21
|
+
// The "+" card is the one blank entry: an empty stage under a fresh name (no
|
|
22
|
+
// starterId, so Create takes the blank path). Its preview is the blank-stage render.
|
|
23
|
+
const NEW_PROJECT = Object.freeze({ id: "new-project", thumbId: "blank-stage", kind: "blank", defaultName: "Untitled Project", get name() { return ko("New project", "새 프로젝트"); }, get description() { return ko("Start from an empty stage.", "빈 무대에서 시작합니다."); } });
|
|
29
24
|
|
|
30
25
|
// The two project modes the New view offers.
|
|
31
|
-
// Storyboard starts from an empty stage only; Animation
|
|
26
|
+
// Storyboard starts from an empty stage only; Animation also offers the starters.
|
|
32
27
|
const MODE_OPTIONS = [
|
|
33
28
|
{
|
|
34
29
|
id: "storyboard",
|
|
@@ -46,6 +41,9 @@ const MODE_OPTIONS = [
|
|
|
46
41
|
// path. The v2 chooser already has the name, so this short-lived handoff seeds
|
|
47
42
|
// the existing ProjectNameDialog and lets its normal submit path commit it.
|
|
48
43
|
let pendingProjectName = null;
|
|
44
|
+
function stagePendingProjectName(name) {
|
|
45
|
+
pendingProjectName = name;
|
|
46
|
+
}
|
|
49
47
|
|
|
50
48
|
function formatDate(value) {
|
|
51
49
|
const date = new Date(value);
|
|
@@ -58,10 +56,8 @@ function sampleTemplate(scene) {
|
|
|
58
56
|
id: `sample-${scene.id}`,
|
|
59
57
|
starterId: scene.id,
|
|
60
58
|
name: scene.name,
|
|
61
|
-
category: "Action",
|
|
62
59
|
description: scene.blurb,
|
|
63
60
|
kind: "sample",
|
|
64
|
-
code: "SAMPLE",
|
|
65
61
|
};
|
|
66
62
|
}
|
|
67
63
|
|
|
@@ -103,20 +99,17 @@ export function ProjectNameDialog({ open, initialName = "My Project", onCancel,
|
|
|
103
99
|
|
|
104
100
|
/**
|
|
105
101
|
* v2 start / project screen. Recent projects still come from the browser-local
|
|
106
|
-
* project store, while
|
|
102
|
+
* project store, while the bundled starter scenes (none today) become template
|
|
103
|
+
* cards before the "+" card.
|
|
107
104
|
*/
|
|
108
105
|
export default function ProjectBrowser({ currentName, onOpen, onOpenFile, onNew, onClose, startup = false, starters = [], onStarter }) {
|
|
109
106
|
const starterTemplates = useMemo(() => starters.map(sampleTemplate), [starters]);
|
|
110
107
|
const [previsMode, setPrevisMode] = useState(DEFAULT_PREVIS_MODE);
|
|
111
|
-
const templates =
|
|
112
|
-
|
|
113
|
-
[previsMode, starterTemplates],
|
|
114
|
-
);
|
|
115
|
-
const defaultTemplate = starterTemplates[0] ?? BLANK_TEMPLATE;
|
|
108
|
+
const templates = previsMode === "storyboard" ? [] : starterTemplates;
|
|
109
|
+
const defaultTemplate = starterTemplates[0] ?? NEW_PROJECT;
|
|
116
110
|
const [activeNav, setActiveNav] = useState(startup ? "new" : "recent");
|
|
117
|
-
const [category, setCategory] = useState("All");
|
|
118
111
|
const [selectedTemplateId, setSelectedTemplateId] = useState(defaultTemplate.id);
|
|
119
|
-
const [name, setName] = useState(currentName || defaultTemplate.name || "My Project");
|
|
112
|
+
const [name, setName] = useState(currentName || defaultTemplate.defaultName || defaultTemplate.name || "My Project");
|
|
120
113
|
const [frameRate, setFrameRate] = useState("24");
|
|
121
114
|
const [units, setUnits] = useState("Meters");
|
|
122
115
|
const [recents, setRecents] = useState([]);
|
|
@@ -156,15 +149,14 @@ export default function ProjectBrowser({ currentName, onOpen, onOpenFile, onNew,
|
|
|
156
149
|
|
|
157
150
|
// File › New reaches this screen with unsaved work, so Create can be refused
|
|
158
151
|
// by the discard confirmation. A name left behind then must not seed a later
|
|
159
|
-
// Save's name dialog once the screen closes.
|
|
152
|
+
// Save's name dialog once the screen closes. The clear waits for the end of
|
|
153
|
+
// the commit: a Create that closes this screen opens the name dialog in the
|
|
154
|
+
// same commit, and its effect takes the name first.
|
|
160
155
|
useEffect(() => () => {
|
|
161
|
-
|
|
156
|
+
queueMicrotask(() => stagePendingProjectName(null));
|
|
162
157
|
}, []);
|
|
163
158
|
|
|
164
|
-
const selectedTemplate = templates.find((template) => template.id === selectedTemplateId) ??
|
|
165
|
-
const visibleTemplates = category === "All"
|
|
166
|
-
? templates
|
|
167
|
-
: templates.filter((template) => template.category === "All" || template.category === category);
|
|
159
|
+
const selectedTemplate = templates.find((template) => template.id === selectedTemplateId) ?? NEW_PROJECT;
|
|
168
160
|
const canCreate = name.trim().length > 0;
|
|
169
161
|
const createReason = ko("Enter a project name to create a project", "프로젝트를 만들려면 이름을 입력하세요");
|
|
170
162
|
const locationLabel = folder?.name || ko("Browser project store", "브라우저 프로젝트 저장소");
|
|
@@ -201,17 +193,17 @@ export default function ProjectBrowser({ currentName, onOpen, onOpenFile, onNew,
|
|
|
201
193
|
const selectTemplate = (template) => {
|
|
202
194
|
setSelectedTemplateId(template.id);
|
|
203
195
|
// A name the previous card (or the open project) filled in follows the new card; a typed one stays.
|
|
204
|
-
setName((current) => !current.trim() ||
|
|
196
|
+
setName((current) => !current.trim() || [selectedTemplate?.name, selectedTemplate?.defaultName, currentName].includes(current.trim()) ? template.defaultName ?? template.name : current);
|
|
205
197
|
};
|
|
206
198
|
|
|
207
199
|
const choosePrevisMode = (mode) => {
|
|
208
200
|
setPrevisMode(mode);
|
|
209
201
|
// Storyboard offers the blank stage only, so a starter selection cannot
|
|
210
202
|
// survive the switch.
|
|
211
|
-
if (mode !== "storyboard" || selectedTemplate?.
|
|
212
|
-
setSelectedTemplateId(
|
|
203
|
+
if (mode !== "storyboard" || !selectedTemplate?.starterId) return;
|
|
204
|
+
setSelectedTemplateId(NEW_PROJECT.id);
|
|
213
205
|
// A name the starter card filled in would now describe the wrong stage.
|
|
214
|
-
setName((current) => (current.trim() === selectedTemplate?.name ?
|
|
206
|
+
setName((current) => (current.trim() === selectedTemplate?.name ? NEW_PROJECT.defaultName : current));
|
|
215
207
|
};
|
|
216
208
|
|
|
217
209
|
const createProject = (event) => {
|
|
@@ -222,7 +214,7 @@ export default function ProjectBrowser({ currentName, onOpen, onOpenFile, onNew,
|
|
|
222
214
|
onStarter?.(selectedTemplate.starterId, projectName, { previsMode: "animation" });
|
|
223
215
|
return;
|
|
224
216
|
}
|
|
225
|
-
|
|
217
|
+
stagePendingProjectName(projectName);
|
|
226
218
|
onNew(projectName, { previsMode });
|
|
227
219
|
};
|
|
228
220
|
|
|
@@ -234,7 +226,6 @@ export default function ProjectBrowser({ currentName, onOpen, onOpenFile, onNew,
|
|
|
234
226
|
const navItems = [
|
|
235
227
|
{ id: "recent", label: ko("Recent", "최근") },
|
|
236
228
|
{ id: "new", label: ko("New Project", "새 프로젝트") },
|
|
237
|
-
{ id: "samples", label: ko("Samples", "샘플") },
|
|
238
229
|
{ id: "learn", label: ko("Learn", "배우기") },
|
|
239
230
|
];
|
|
240
231
|
|
|
@@ -243,7 +234,7 @@ export default function ProjectBrowser({ currentName, onOpen, onOpenFile, onNew,
|
|
|
243
234
|
<div className={`v2-start-window project-browser${startup ? " startup" : ""}`} role="dialog" aria-modal="true" aria-label={ko("Choose project", "프로젝트 선택")}>
|
|
244
235
|
<nav className="v2-start-nav" aria-label={ko("Project navigation", "프로젝트 탐색")}>
|
|
245
236
|
<div className="v2-start-brand">
|
|
246
|
-
<
|
|
237
|
+
<BrandMark />
|
|
247
238
|
<strong>CozyClay</strong>
|
|
248
239
|
</div>
|
|
249
240
|
<div className="v2-start-nav-list">
|
|
@@ -265,7 +256,7 @@ export default function ProjectBrowser({ currentName, onOpen, onOpenFile, onNew,
|
|
|
265
256
|
<main className="v2-start-main">
|
|
266
257
|
<div className="v2-start-main-head">
|
|
267
258
|
<div className="v2-start-title-row">
|
|
268
|
-
<h1>{activeNav === "recent" ? ko("Recent", "최근") : activeNav === "
|
|
259
|
+
<h1>{activeNav === "recent" ? ko("Recent", "최근") : activeNav === "learn" ? ko("Learn", "배우기") : ko("New Project", "새 프로젝트")}</h1>
|
|
269
260
|
{activeNav !== "new" && <button type="button" className="v2-start-close x" onClick={onClose} aria-label={ko("Close", "닫기")}>×</button>}
|
|
270
261
|
</div>
|
|
271
262
|
{activeNav === "new" && (
|
|
@@ -286,22 +277,6 @@ export default function ProjectBrowser({ currentName, onOpen, onOpenFile, onNew,
|
|
|
286
277
|
))}
|
|
287
278
|
</div>
|
|
288
279
|
)}
|
|
289
|
-
{(activeNav === "new" || activeNav === "samples") && (
|
|
290
|
-
<div className="v2-start-tabs" role="tablist" aria-label={ko("Template categories", "템플릿 카테고리")}>
|
|
291
|
-
{CATEGORIES.map((entry) => (
|
|
292
|
-
<button
|
|
293
|
-
type="button"
|
|
294
|
-
key={entry}
|
|
295
|
-
role="tab"
|
|
296
|
-
aria-selected={category === entry}
|
|
297
|
-
className={`v2-start-tab${category === entry ? " active" : ""}`}
|
|
298
|
-
onClick={() => setCategory(entry)}
|
|
299
|
-
>
|
|
300
|
-
{ko(entry, entry === "All" ? "전체" : entry === "Film" ? "영화" : entry === "Dialogue" ? "대화" : entry === "Action" ? "액션" : "차량")}
|
|
301
|
-
</button>
|
|
302
|
-
))}
|
|
303
|
-
</div>
|
|
304
|
-
)}
|
|
305
280
|
</div>
|
|
306
281
|
|
|
307
282
|
{activeNav === "learn" ? (
|
|
@@ -327,7 +302,7 @@ export default function ProjectBrowser({ currentName, onOpen, onOpenFile, onNew,
|
|
|
327
302
|
) : (
|
|
328
303
|
<>
|
|
329
304
|
<div className="v2-start-template-grid" role="list" aria-label={ko("Project templates", "프로젝트 템플릿")}>
|
|
330
|
-
{
|
|
305
|
+
{templates.map((template) => (
|
|
331
306
|
<button
|
|
332
307
|
type="button"
|
|
333
308
|
role="listitem"
|
|
@@ -337,15 +312,20 @@ export default function ProjectBrowser({ currentName, onOpen, onOpenFile, onNew,
|
|
|
337
312
|
onClick={() => selectTemplate(template)}
|
|
338
313
|
onDoubleClick={() => template.starterId && onStarter?.(template.starterId)}
|
|
339
314
|
>
|
|
340
|
-
<span className={`v2-start-template-art ${template.kind}`}
|
|
341
|
-
<span className="v2-start-template-code">{template.code}</span>
|
|
342
|
-
</span>
|
|
315
|
+
<span className={`v2-start-template-art ${template.kind}`}><TemplateThumb id={template.starterId ?? template.id} /></span>
|
|
343
316
|
<span className="v2-start-template-copy">
|
|
344
317
|
<strong>{template.name}</strong>
|
|
345
318
|
<span>{template.description}</span>
|
|
346
319
|
</span>
|
|
347
320
|
</button>
|
|
348
321
|
))}
|
|
322
|
+
<button type="button" role="listitem" className={`v2-start-add-card${selectedTemplate?.id === NEW_PROJECT.id ? " selected" : ""}`} data-testid="start-new-project-card" onClick={() => { selectTemplate(NEW_PROJECT); requestAnimationFrame(() => nameInputRef.current?.select()); }}>
|
|
323
|
+
<span className="v2-start-add-art" aria-hidden="true"><svg viewBox="0 0 20 20" width="20" height="20"><path d="M10 3.5v13M3.5 10h13" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" /></svg></span>
|
|
324
|
+
<span className="v2-start-template-copy">
|
|
325
|
+
<strong>{ko("New project", "새 프로젝트")}</strong>
|
|
326
|
+
<span>{ko("Start from an empty stage", "빈 무대에서 시작")}</span>
|
|
327
|
+
</span>
|
|
328
|
+
</button>
|
|
349
329
|
</div>
|
|
350
330
|
|
|
351
331
|
<div className="v2-start-recent-block">
|
|
@@ -357,15 +337,11 @@ export default function ProjectBrowser({ currentName, onOpen, onOpenFile, onNew,
|
|
|
357
337
|
</div>
|
|
358
338
|
</>
|
|
359
339
|
)}
|
|
360
|
-
{activeNav !== "recent" && activeNav !== "learn" && (
|
|
361
|
-
<div className="v2-start-file-actions">
|
|
362
|
-
<button type="button" className="v2-start-file-button" onClick={onOpenFile}>{ko("Open project file…", "프로젝트 파일 열기…")}</button>
|
|
363
|
-
</div>
|
|
364
|
-
)}
|
|
340
|
+
{activeNav !== "recent" && activeNav !== "learn" && <div className="v2-start-file-actions"><button type="button" className="v2-start-file-button" onClick={onOpenFile}>{ko("Open project file…", "프로젝트 파일 열기…")}</button></div>}
|
|
365
341
|
</main>
|
|
366
342
|
|
|
367
343
|
<aside className="v2-start-preview" data-testid="start-project-preview" aria-label={ko("Project preview", "프로젝트 미리보기")}>
|
|
368
|
-
<div className={`v2-start-preview-art ${selectedTemplate?.kind || "blank"}`}
|
|
344
|
+
<div className={`v2-start-preview-art ${selectedTemplate?.kind || "blank"}`}><TemplateThumb id={selectedTemplate.starterId ?? selectedTemplate.thumbId} /></div>
|
|
369
345
|
<div className="v2-start-preview-copy">
|
|
370
346
|
<strong>{selectedTemplate?.name}</strong>
|
|
371
347
|
<span>{selectedTemplate?.description}</span>
|
package/src/props.jsx
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
* Set props: low-poly clay maquettes to block against.
|
|
3
3
|
*
|
|
4
4
|
* The set is a bare room and a figure; a shot that needs "something in the
|
|
5
|
-
* world" (a
|
|
5
|
+
* world" (a wall to lean on, to walk past, to frame behind) has nothing
|
|
6
6
|
* to work with. These props are built from primitives in the same clay style
|
|
7
7
|
* as the Room and the character tint, so the whole frame stays one
|
|
8
|
-
* consistent maquette.
|
|
9
|
-
*
|
|
8
|
+
* consistent maquette. Practical lights and glow live in scene-lights.jsx
|
|
9
|
+
* and object-glow.js.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { useEffect, useMemo, useRef, useState } from "react";
|
|
@@ -19,40 +19,35 @@ import { CUTOUT_KIND, MESH_KIND, EMPTY_KIND, EMPTY_MARKER_SIZE, SCULPT_KIND } fr
|
|
|
19
19
|
import { TEXT_KIND } from "./text/text-record.js";
|
|
20
20
|
import { TextObject } from "./text/text-object.jsx";
|
|
21
21
|
import { buildSculptGroup, disposeSculptGroup } from "./sculpt-geometry.js";
|
|
22
|
+
import { depthRankProps, usePrimitiveLook } from "./primitive-look.js";
|
|
22
23
|
import { subscribeToAssetTexture } from "./scene-asset-cache.js";
|
|
23
24
|
import { ImportedMesh } from "./imported-mesh.jsx";
|
|
24
25
|
import { EffectMarker, EffectsLayer } from "./effects/EffectsLayer.jsx";
|
|
25
26
|
import { EFFECT_KIND } from "./effects/effect-record.js";
|
|
26
27
|
import { applyPoseFade } from "./pose-fade.js";
|
|
27
28
|
import { coplanarDepthRanks } from "./coplanar-depth.js";
|
|
28
|
-
import { autoFlat
|
|
29
|
+
import { autoFlat } from "./auto-flat.js";
|
|
29
30
|
import { LIGHT_KIND, capPracticalShadows } from "./object-look.js";
|
|
30
31
|
import { SceneLight, useLookRefresh } from "./scene-lights.jsx";
|
|
31
32
|
import { placeObjectLook } from "./scene-light-place.js";
|
|
32
33
|
|
|
33
|
-
// Parts of an assembly can share a plane with an earlier part (hood top and
|
|
34
|
-
// grille top). The depth test can't order them and the winner flips as the
|
|
35
|
-
// camera moves, so a ranked part is pulled toward the camera by `rank` steps
|
|
36
|
-
// (negative = nearer). Rank 0 gets nothing: those materials stay untouched.
|
|
37
|
-
function depthRankProps(rank) {
|
|
38
|
-
return rank > 0 ? { polygonOffset: true, polygonOffsetFactor: -rank, polygonOffsetUnits: -rank } : null;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
34
|
/**
|
|
42
35
|
* The creatable primitives, Unity's 3D Object menu in clay. Each one is built
|
|
43
36
|
* with its base on the local floor (y = 0), so an object's `y` reads as height
|
|
44
|
-
* above the deck rather than "half of me is underground".
|
|
37
|
+
* above the deck rather than "half of me is underground". A surface preset
|
|
38
|
+
* (primitive-look.js) replaces the clay; glass casts no shadow.
|
|
45
39
|
*/
|
|
46
|
-
function Primitive({ kind, color, autoColor, depthRank = 0 }) {
|
|
40
|
+
function Primitive({ kind, object, color, autoColor, depthRank = 0 }) {
|
|
47
41
|
const side = kind === "plane" ? THREE.DoubleSide : THREE.FrontSide;
|
|
48
|
-
const
|
|
42
|
+
const look = usePrimitiveLook(object, kind, side, depthRank), cast = !(look?.transmission > 0);
|
|
43
|
+
const material = look ? <primitive object={look} attach="material" /> : autoColor ? (
|
|
49
44
|
<meshStandardMaterial {...autoFlat(autoColor)} side={side} {...depthRankProps(depthRank)} />
|
|
50
45
|
) : (
|
|
51
46
|
<meshStandardMaterial color={color} roughness={0.82} side={side} {...depthRankProps(depthRank)} />
|
|
52
47
|
);
|
|
53
48
|
if (kind === "sphere") {
|
|
54
49
|
return (
|
|
55
|
-
<mesh position={[0, 0.5, 0]} castShadow receiveShadow>
|
|
50
|
+
<mesh position={[0, 0.5, 0]} castShadow={cast} receiveShadow>
|
|
56
51
|
<sphereGeometry args={[0.5, 28, 18]} />
|
|
57
52
|
{material}
|
|
58
53
|
</mesh>
|
|
@@ -60,7 +55,7 @@ function Primitive({ kind, color, autoColor, depthRank = 0 }) {
|
|
|
60
55
|
}
|
|
61
56
|
if (kind === "capsule") {
|
|
62
57
|
return (
|
|
63
|
-
<mesh position={[0, 0.7, 0]} castShadow receiveShadow>
|
|
58
|
+
<mesh position={[0, 0.7, 0]} castShadow={cast} receiveShadow>
|
|
64
59
|
<capsuleGeometry args={[0.35, 0.7, 6, 18]} />
|
|
65
60
|
{material}
|
|
66
61
|
</mesh>
|
|
@@ -68,7 +63,7 @@ function Primitive({ kind, color, autoColor, depthRank = 0 }) {
|
|
|
68
63
|
}
|
|
69
64
|
if (kind === "cylinder") {
|
|
70
65
|
return (
|
|
71
|
-
<mesh position={[0, 0.5, 0]} castShadow receiveShadow>
|
|
66
|
+
<mesh position={[0, 0.5, 0]} castShadow={cast} receiveShadow>
|
|
72
67
|
<cylinderGeometry args={[0.5, 0.5, 1, 26]} />
|
|
73
68
|
{material}
|
|
74
69
|
</mesh>
|
|
@@ -76,7 +71,7 @@ function Primitive({ kind, color, autoColor, depthRank = 0 }) {
|
|
|
76
71
|
}
|
|
77
72
|
if (kind === "cone") {
|
|
78
73
|
return (
|
|
79
|
-
<mesh position={[0, 0.5, 0]} castShadow receiveShadow>
|
|
74
|
+
<mesh position={[0, 0.5, 0]} castShadow={cast} receiveShadow>
|
|
80
75
|
<coneGeometry args={[0.5, 1, 26]} />
|
|
81
76
|
{material}
|
|
82
77
|
</mesh>
|
|
@@ -91,7 +86,7 @@ function Primitive({ kind, color, autoColor, depthRank = 0 }) {
|
|
|
91
86
|
);
|
|
92
87
|
}
|
|
93
88
|
return (
|
|
94
|
-
<mesh position={[0, 0.5, 0]} castShadow receiveShadow>
|
|
89
|
+
<mesh position={[0, 0.5, 0]} castShadow={cast} receiveShadow>
|
|
95
90
|
<boxGeometry args={[1, 1, 1]} />
|
|
96
91
|
{material}
|
|
97
92
|
</mesh>
|
|
@@ -162,20 +157,17 @@ function Cutout({ object }) {
|
|
|
162
157
|
|
|
163
158
|
/**
|
|
164
159
|
* A sculpt: the clay parts its recipe describes, rebuilt only when the recipe
|
|
165
|
-
* or the look changes.
|
|
166
|
-
*
|
|
160
|
+
* or the look changes. The shaded look is buildSculptGroup's own clay (part
|
|
161
|
+
* colours, gradients, scatter tints); auto-colour paints the whole sculpt flat
|
|
162
|
+
* like any other prop, because it is one object. Depth rank rides on either.
|
|
167
163
|
*/
|
|
168
164
|
function Sculpt({ object, depthRank = 0 }) {
|
|
169
165
|
const { recipe, autoColor } = object;
|
|
170
|
-
const group = useMemo(
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
}),
|
|
176
|
-
}),
|
|
177
|
-
[recipe, autoColor, depthRank],
|
|
178
|
-
);
|
|
166
|
+
const group = useMemo(() => {
|
|
167
|
+
const built = buildSculptGroup(recipe, autoColor ? { materialFor: () => new THREE.MeshStandardMaterial(autoFlat(autoColor)) } : {});
|
|
168
|
+
built.traverse((node) => node.material && Object.assign(node.material, depthRankProps(depthRank)));
|
|
169
|
+
return built;
|
|
170
|
+
}, [recipe, autoColor, depthRank]);
|
|
179
171
|
useEffect(() => () => disposeSculptGroup(group), [group]);
|
|
180
172
|
return <primitive object={group} />;
|
|
181
173
|
}
|
|
@@ -242,10 +234,7 @@ function SceneObjectContent({ object, depthRank = 0 }) {
|
|
|
242
234
|
if (renderer === MESH_KIND) return <ImportedMesh object={object} />;
|
|
243
235
|
if (renderer === SCULPT_KIND) return <Sculpt object={object} depthRank={depthRank} />;
|
|
244
236
|
if (renderer === TEXT_KIND) return <TextObject object={object} />;
|
|
245
|
-
if (renderer
|
|
246
|
-
if (renderer === "small-plane") return <SmallPlane autoColor={autoColor} />;
|
|
247
|
-
if (renderer === "chair") return <Chair autoColor={autoColor} />;
|
|
248
|
-
if (PRIMITIVE_KINDS.has(renderer)) return <Primitive kind={renderer} color={color} autoColor={autoColor} depthRank={depthRank} />;
|
|
237
|
+
if (PRIMITIVE_KINDS.has(renderer)) return <Primitive kind={renderer} object={object} color={color} autoColor={autoColor} depthRank={depthRank} />;
|
|
249
238
|
return null;
|
|
250
239
|
}
|
|
251
240
|
|
package/src/render-passes.js
CHANGED
|
@@ -47,10 +47,16 @@ export function passFileName(kind) {
|
|
|
47
47
|
// it spreads the value over four channels and reads as colour noise). This
|
|
48
48
|
// shader is the plain depth-to-grey the deliverable wants, and the range it
|
|
49
49
|
// normalises over is the stage's, not the projection's.
|
|
50
|
+
// An InstancedMesh (a sculpt's scatter) draws every instance through its
|
|
51
|
+
// instanceMatrix; without it all of them would land on the mesh's origin.
|
|
50
52
|
const DEPTH_VERTEX = /* glsl */ `
|
|
51
53
|
varying float vViewDepth;
|
|
52
54
|
void main() {
|
|
53
|
-
vec4
|
|
55
|
+
vec4 local = vec4(position, 1.0);
|
|
56
|
+
#ifdef USE_INSTANCING
|
|
57
|
+
local = instanceMatrix * local;
|
|
58
|
+
#endif
|
|
59
|
+
vec4 viewPosition = modelViewMatrix * local;
|
|
54
60
|
vViewDepth = -viewPosition.z;
|
|
55
61
|
gl_Position = projectionMatrix * viewPosition;
|
|
56
62
|
}
|