create-aura3d 1.1.1 → 1.1.2
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/index.js +1 -1
- package/package.json +1 -1
- package/templates/cartoon-channel/README.md +11 -1
- package/templates/cartoon-channel/src/experimental/README.md +24 -0
- package/templates/cartoon-channel/src/{concept-episode-2-5d.ts → experimental/concept-episode-2-5d.ts} +2 -2
- package/templates/cartoon-channel/src/{image-puppet-episode.ts → experimental/image-puppet-episode.ts} +2 -2
- package/templates/cartoon-channel/src/{puppet-episode-2d.ts → experimental/puppet-episode-2d.ts} +2 -2
- package/templates/cartoon-studio/ASSET-LICENSES.md +41 -0
- package/templates/cartoon-studio/aura.assets.json +321 -78
- package/templates/cartoon-studio/live-route.html +12 -0
- package/templates/cartoon-studio/package.json +1 -0
- package/templates/cartoon-studio/public/aura-assets/luma.authored.glb +0 -0
- package/templates/cartoon-studio/public/aura-assets/miko.authored.glb +0 -0
- package/templates/cartoon-studio/scripts/build-characters.ts +738 -0
- package/templates/cartoon-studio/scripts/build-dialogue-audio.ts +262 -0
- package/templates/cartoon-studio/scripts/episode.ts +48 -1
- package/templates/cartoon-studio/scripts/render-live.ts +873 -0
- package/templates/cartoon-studio/scripts/validate-characters.ts +0 -0
- package/templates/cartoon-studio/src/aura-assets.ts +15 -12
- package/templates/cartoon-studio/src/main.ts +84 -17
- package/templates/cartoon-studio/src/render-live-route.ts +913 -0
- package/templates/cartoon-studio/tsconfig.json +3 -1
- package/templates/cartoon-studio/vite.config.ts +13 -0
- package/templates/episode-builder/README.md +4 -0
- package/templates/fighting-game/README.md +16 -0
- package/templates/prompt-cartoon-channel/README.md +4 -0
- package/templates/cartoon-studio/public/aura-assets/luma.047f5e5f.glb +0 -0
- package/templates/cartoon-studio/public/aura-assets/luma.humanoid-fixture.glb +0 -0
- package/templates/cartoon-studio/public/aura-assets/miko.047f5e5f.glb +0 -0
- /package/templates/cartoon-channel/src/{concept-episode-2-5d.css → experimental/concept-episode-2-5d.css} +0 -0
- /package/templates/cartoon-channel/src/{image-puppet-episode.css → experimental/image-puppet-episode.css} +0 -0
- /package/templates/cartoon-channel/src/{puppet-episode-2d.css → experimental/puppet-episode-2d.css} +0 -0
|
Binary file
|
|
@@ -16,31 +16,34 @@ export const assets = defineAuraAssets({
|
|
|
16
16
|
miko: {
|
|
17
17
|
type: "model",
|
|
18
18
|
format: "glb",
|
|
19
|
-
url: "/aura-assets/miko.
|
|
20
|
-
bounds: [0.
|
|
21
|
-
hash: "sha256-
|
|
19
|
+
url: "/aura-assets/miko.authored.glb",
|
|
20
|
+
bounds: [0.66, 1.47, 0.43],
|
|
21
|
+
hash: "sha256-15d37c49f79d3f0e7d70f199e058935605802dcb61434cc458684e512486f705",
|
|
22
22
|
metadata: {
|
|
23
23
|
contractId: assetContractId,
|
|
24
24
|
role: "cartoon-character",
|
|
25
25
|
license: "CC0",
|
|
26
|
-
source: "Aura3D
|
|
27
|
-
animationClips: ["Idle", "
|
|
28
|
-
|
|
26
|
+
source: "Aura3D-authored procedural cartoon robot (scripts/build-characters.ts)",
|
|
27
|
+
animationClips: ["Idle", "Wave", "Walk"],
|
|
28
|
+
// Authored GLB ships ONE real face blendshape, `mouthOpen`, driven by viseme
|
|
29
|
+
// mouthOpenness for real geometric lip-sync (no primitive mouth-card).
|
|
30
|
+
mouthReadiness: "facial-morph-targets"
|
|
29
31
|
}
|
|
30
32
|
},
|
|
31
33
|
luma: {
|
|
32
34
|
type: "model",
|
|
33
35
|
format: "glb",
|
|
34
|
-
url: "/aura-assets/luma.
|
|
35
|
-
bounds: [
|
|
36
|
-
hash: "sha256-
|
|
36
|
+
url: "/aura-assets/luma.authored.glb",
|
|
37
|
+
bounds: [0.62, 1.94, 0.34],
|
|
38
|
+
hash: "sha256-ccfcb4accdf58127337bd0b8f9ae10a160b6365b3a92f85b777476c524c92989",
|
|
37
39
|
metadata: {
|
|
38
40
|
contractId: assetContractId,
|
|
39
41
|
role: "cartoon-character",
|
|
40
42
|
license: "CC0",
|
|
41
|
-
source: "Aura3D
|
|
42
|
-
animationClips: ["Idle", "
|
|
43
|
-
|
|
43
|
+
source: "Aura3D-authored procedural cartoon robot (scripts/build-characters.ts)",
|
|
44
|
+
animationClips: ["Idle", "Wave", "Walk"],
|
|
45
|
+
// Authored GLB ships the same real `mouthOpen` face blendshape as miko.
|
|
46
|
+
mouthReadiness: "facial-morph-targets"
|
|
44
47
|
}
|
|
45
48
|
},
|
|
46
49
|
moonGarden: {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
applyShotPlaybackFrame,
|
|
2
3
|
camera,
|
|
3
4
|
captionCueAtTime,
|
|
4
5
|
createAuraApp,
|
|
@@ -6,7 +7,6 @@ import {
|
|
|
6
7
|
effects,
|
|
7
8
|
game,
|
|
8
9
|
group,
|
|
9
|
-
installShotPlayback,
|
|
10
10
|
labels,
|
|
11
11
|
lights,
|
|
12
12
|
material,
|
|
@@ -144,6 +144,34 @@ document.body.dataset.cartoonStoryBibleProps = String(storyBible.props.length);
|
|
|
144
144
|
document.body.dataset.cartoonStudioPanels = cartoonStudioSupport.panels.join(",");
|
|
145
145
|
if (!captureMode) installCartoonStudioPanel(document.body);
|
|
146
146
|
|
|
147
|
+
// Ground the cartoon characters on the moon-garden path and normalize their
|
|
148
|
+
// wildly different native scales (miko bounds ~0.066m tall, luma ~1.8m) to a
|
|
149
|
+
// single on-screen height so neither floats nor dwarfs the other. Bounds come
|
|
150
|
+
// from the generated typed assets (`boundsMetadata` in aura.assets.json):
|
|
151
|
+
// miko -> min/max y [-0.013, 0.013], center origin
|
|
152
|
+
// luma -> min/max y [0, 1.8], foot origin
|
|
153
|
+
// The path box sits at y=0.05 with a 0.04-thick top, so its walkable surface is
|
|
154
|
+
// at y = 0.05 + 0.02 = 0.07; both characters rest their lowest point there.
|
|
155
|
+
// Local grounding helper (kept in-template so the scaffold builds against the
|
|
156
|
+
// published @aura3d/engine). Grounds a scaled asset's lowest point on `floorY`
|
|
157
|
+
// and normalizes its height to `targetHeight`.
|
|
158
|
+
function groundedPlacement(
|
|
159
|
+
bounds: { readonly min: readonly [number, number, number]; readonly max: readonly [number, number, number] },
|
|
160
|
+
opts: { readonly targetHeight: number; readonly x: number; readonly z: number; readonly floorY: number }
|
|
161
|
+
): { position: [number, number, number]; scale: number } {
|
|
162
|
+
const height = bounds.max[1] - bounds.min[1];
|
|
163
|
+
const scale = height > 0 && opts.targetHeight > 0 ? opts.targetHeight / height : 1;
|
|
164
|
+
const y = opts.floorY + -bounds.min[1] * scale;
|
|
165
|
+
return { position: [opts.x, y, opts.z], scale };
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const PATH_FLOOR_Y = 0.07;
|
|
169
|
+
const CHARACTER_TARGET_HEIGHT = 1.5;
|
|
170
|
+
const mikoBounds = { min: [-0.033, -0.013, -0.009], max: [0.033, 0.013, 0.008] } as const;
|
|
171
|
+
const lumaBounds = { min: [-0.55, 0, -0.4], max: [0.55, 1.8, 0.4] } as const;
|
|
172
|
+
const mikoPlacement = groundedPlacement(mikoBounds, { targetHeight: CHARACTER_TARGET_HEIGHT, x: -0.8, z: 0, floorY: PATH_FLOOR_Y });
|
|
173
|
+
const lumaPlacement = groundedPlacement(lumaBounds, { targetHeight: CHARACTER_TARGET_HEIGHT, x: 0.8, z: 0, floorY: PATH_FLOOR_Y });
|
|
174
|
+
|
|
147
175
|
const app = createAuraApp("#app", {
|
|
148
176
|
scene: scene()
|
|
149
177
|
.background("#081b2a")
|
|
@@ -155,18 +183,18 @@ const app = createAuraApp("#app", {
|
|
|
155
183
|
)
|
|
156
184
|
.add(
|
|
157
185
|
model(assets.miko, { name: "Miko typed cartoon character" })
|
|
158
|
-
.position(
|
|
159
|
-
.scale(
|
|
186
|
+
.position(mikoPlacement.position[0], mikoPlacement.position[1], mikoPlacement.position[2])
|
|
187
|
+
.scale(mikoPlacement.scale)
|
|
160
188
|
.runtime(game.runtimeNode("miko", { tags: ["character", "miko", "typed-asset"] }))
|
|
161
189
|
)
|
|
162
|
-
.add(createMouthCard("miko:mouth", "Miko", [
|
|
190
|
+
.add(createMouthCard("miko:mouth", "Miko", [mikoPlacement.position[0], mikoPlacement.position[1] + CHARACTER_TARGET_HEIGHT * 0.92, 0.32], "#f8fff2"))
|
|
163
191
|
.add(
|
|
164
192
|
model(assets.luma, { name: "Luma typed cartoon character" })
|
|
165
|
-
.position(0
|
|
166
|
-
.scale(
|
|
193
|
+
.position(lumaPlacement.position[0], lumaPlacement.position[1], lumaPlacement.position[2])
|
|
194
|
+
.scale(lumaPlacement.scale)
|
|
167
195
|
.runtime(game.runtimeNode("luma", { tags: ["character", "luma", "typed-asset"] }))
|
|
168
196
|
)
|
|
169
|
-
.add(createMouthCard("luma:mouth", "Luma", [0
|
|
197
|
+
.add(createMouthCard("luma:mouth", "Luma", [lumaPlacement.position[0], lumaPlacement.position[1] + CHARACTER_TARGET_HEIGHT * 0.92, 0.32], "#40ffbf"))
|
|
170
198
|
.add(
|
|
171
199
|
primitives
|
|
172
200
|
.box({ name: "moon garden path", material: material.pbr({ color: "#182d3f", roughness: 0.78 }) })
|
|
@@ -239,24 +267,63 @@ const cartoonEpisodeProof: CartoonEpisodeRouteProof = {
|
|
|
239
267
|
};
|
|
240
268
|
window.__AURA3D_CARTOON_EPISODE_PROOF__ = cartoonEpisodeProof;
|
|
241
269
|
window.__AURA3D_CARTOON_TEMPLATE__ = cartoonEpisodeProof;
|
|
242
|
-
|
|
243
|
-
const sample = sampleCartoonRouteAt(time);
|
|
244
|
-
captionOverlay.textContent = sample.captionText ?? firstCaption?.text ?? "";
|
|
245
|
-
captionOverlay.dataset.shotId = sample.shotId ?? "";
|
|
246
|
-
captionOverlay.dataset.captionId = sample.captionId ?? "";
|
|
247
|
-
};
|
|
248
|
-
|
|
249
|
-
installShotPlayback(app, shotPlaybackPlan, {
|
|
270
|
+
const shotPlaybackApplyOptions = {
|
|
250
271
|
primitiveMouthNodeByCharacterId: {
|
|
251
272
|
miko: "miko:mouth",
|
|
252
273
|
luma: "luma:mouth"
|
|
253
274
|
},
|
|
254
|
-
|
|
275
|
+
playAnimationClips: true,
|
|
276
|
+
onCaption(caption: { readonly text?: string; readonly captionId?: string } | undefined, framePlan: { readonly shotId?: string }) {
|
|
255
277
|
captionOverlay.textContent = caption?.text ?? firstCaption?.text ?? "";
|
|
256
278
|
captionOverlay.dataset.shotId = framePlan.shotId ?? "";
|
|
257
279
|
captionOverlay.dataset.captionId = caption?.captionId ?? "";
|
|
258
280
|
}
|
|
259
|
-
}
|
|
281
|
+
} as const;
|
|
282
|
+
|
|
283
|
+
// Map a director action to an actual GLB clip so the characters play real skeletal
|
|
284
|
+
// animation instead of standing in their imported bind (T) pose.
|
|
285
|
+
function clipForAction(action: string | undefined): string {
|
|
286
|
+
switch (action) {
|
|
287
|
+
case "walk":
|
|
288
|
+
case "move":
|
|
289
|
+
return "Walking";
|
|
290
|
+
case "wave":
|
|
291
|
+
case "greet":
|
|
292
|
+
case "point":
|
|
293
|
+
case "react":
|
|
294
|
+
case "celebrate":
|
|
295
|
+
case "happy":
|
|
296
|
+
return "Wave";
|
|
297
|
+
case "jump":
|
|
298
|
+
return "Jump";
|
|
299
|
+
case "punch":
|
|
300
|
+
case "action":
|
|
301
|
+
case "clean":
|
|
302
|
+
return "Punch";
|
|
303
|
+
default:
|
|
304
|
+
return "Idle"; // idle / speak / listen
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Deterministically pose every character (position, rotation, animation clip, mouth)
|
|
309
|
+
// at the requested time, injecting a real GLB clip per action so nobody is T-posed.
|
|
310
|
+
function applyCartoonFrameAt(time: number) {
|
|
311
|
+
const frame = sampleShotPlaybackPlan(shotPlaybackPlan, time);
|
|
312
|
+
const nodeUpdates = frame.nodeUpdates.map((update) => ({
|
|
313
|
+
...update,
|
|
314
|
+
animationClip: update.animationClip ?? clipForAction(update.action)
|
|
315
|
+
}));
|
|
316
|
+
applyShotPlaybackFrame(app, { ...frame, nodeUpdates }, shotPlaybackApplyOptions);
|
|
317
|
+
}
|
|
318
|
+
window.__AURA3D_CARTOON_EPISODE_SEEK__ = applyCartoonFrameAt;
|
|
319
|
+
|
|
320
|
+
if (captureMode) {
|
|
321
|
+
// Deterministic capture: the seek hook drives the rendered frame.
|
|
322
|
+
applyCartoonFrameAt(1);
|
|
323
|
+
} else {
|
|
324
|
+
// Live preview: play the episode timeline on the runtime clock with real clips.
|
|
325
|
+
app.onFrame((frame) => applyCartoonFrameAt(frame.time));
|
|
326
|
+
}
|
|
260
327
|
|
|
261
328
|
function createMouthCard(id: "miko:mouth" | "luma:mouth", name: string, position: readonly [number, number, number], color: string) {
|
|
262
329
|
return primitives
|