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.
Files changed (32) hide show
  1. package/dist/index.js +1 -1
  2. package/package.json +1 -1
  3. package/templates/cartoon-channel/README.md +11 -1
  4. package/templates/cartoon-channel/src/experimental/README.md +24 -0
  5. package/templates/cartoon-channel/src/{concept-episode-2-5d.ts → experimental/concept-episode-2-5d.ts} +2 -2
  6. package/templates/cartoon-channel/src/{image-puppet-episode.ts → experimental/image-puppet-episode.ts} +2 -2
  7. package/templates/cartoon-channel/src/{puppet-episode-2d.ts → experimental/puppet-episode-2d.ts} +2 -2
  8. package/templates/cartoon-studio/ASSET-LICENSES.md +41 -0
  9. package/templates/cartoon-studio/aura.assets.json +321 -78
  10. package/templates/cartoon-studio/live-route.html +12 -0
  11. package/templates/cartoon-studio/package.json +1 -0
  12. package/templates/cartoon-studio/public/aura-assets/luma.authored.glb +0 -0
  13. package/templates/cartoon-studio/public/aura-assets/miko.authored.glb +0 -0
  14. package/templates/cartoon-studio/scripts/build-characters.ts +738 -0
  15. package/templates/cartoon-studio/scripts/build-dialogue-audio.ts +262 -0
  16. package/templates/cartoon-studio/scripts/episode.ts +48 -1
  17. package/templates/cartoon-studio/scripts/render-live.ts +873 -0
  18. package/templates/cartoon-studio/scripts/validate-characters.ts +0 -0
  19. package/templates/cartoon-studio/src/aura-assets.ts +15 -12
  20. package/templates/cartoon-studio/src/main.ts +84 -17
  21. package/templates/cartoon-studio/src/render-live-route.ts +913 -0
  22. package/templates/cartoon-studio/tsconfig.json +3 -1
  23. package/templates/cartoon-studio/vite.config.ts +13 -0
  24. package/templates/episode-builder/README.md +4 -0
  25. package/templates/fighting-game/README.md +16 -0
  26. package/templates/prompt-cartoon-channel/README.md +4 -0
  27. package/templates/cartoon-studio/public/aura-assets/luma.047f5e5f.glb +0 -0
  28. package/templates/cartoon-studio/public/aura-assets/luma.humanoid-fixture.glb +0 -0
  29. package/templates/cartoon-studio/public/aura-assets/miko.047f5e5f.glb +0 -0
  30. /package/templates/cartoon-channel/src/{concept-episode-2-5d.css → experimental/concept-episode-2-5d.css} +0 -0
  31. /package/templates/cartoon-channel/src/{image-puppet-episode.css → experimental/image-puppet-episode.css} +0 -0
  32. /package/templates/cartoon-channel/src/{puppet-episode-2d.css → experimental/puppet-episode-2d.css} +0 -0
@@ -16,31 +16,34 @@ export const assets = defineAuraAssets({
16
16
  miko: {
17
17
  type: "model",
18
18
  format: "glb",
19
- url: "/aura-assets/miko.047f5e5f.glb",
20
- bounds: [0.066, 0.026, 0.017],
21
- hash: "sha256-047f5e5fb3bb6d378bd1df16ca6137f2a596c99b3a1b5690b4020c05aaf6f319",
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 bundled humanoid starter fixture",
27
- animationClips: ["Idle", "Walking", "Wave", "Punch", "Jump"],
28
- mouthReadiness: "primitive-mouth-card"
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.humanoid-fixture.glb",
35
- bounds: [1.1, 1.8, 0.8],
36
- hash: "sha256-dfb230fc1f942f259dd00281a1186953ad602fc5d69067ce63e24b2aa439736b",
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 bundled humanoid starter fixture",
42
- animationClips: ["Idle", "Walking", "Wave", "Punch", "Jump"],
43
- mouthReadiness: "primitive-mouth-card"
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(-0.8, 0.75, 0)
159
- .scale(0.72)
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", [-0.8, 0.8, 0.32], "#f8fff2"))
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.8, 0.72, 0)
166
- .scale(0.68)
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.8, 0.77, 0.32], "#40ffbf"))
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
- window.__AURA3D_CARTOON_EPISODE_SEEK__ = (time: number) => {
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
- onCaption(caption, framePlan) {
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