diff --git a/remotion/src/baukasten/helfer.tsx b/remotion/src/baukasten/helfer.tsx index 3ca5c66..14efb1f 100644 --- a/remotion/src/baukasten/helfer.tsx +++ b/remotion/src/baukasten/helfer.tsx @@ -71,3 +71,23 @@ export const hopfStauch = (frame: number, start: number, dauer = 12) => { if (frame >= start && frame < start + dauer) return 1.04; return 1; }; + +// Lulus Maul im Clip lulu_C9_hals-runter (gemessen an der Nase, Leinwand-Koordinaten), +// je [Clip-Bild (24 fps), cx, cy]. Für Dinge, die Lulu beim Halssenken im Maul trägt. +const C9 = {x: 294.7, y: 24.6, s: 709.43 / 662}; +const C9_NASE: [number, number, number][] = [ + [0, 276, 143], [17, 276, 143], [21, 260, 151], [25, 246, 158], [29, 231, 167], [33, 215, 179], + [37, 196, 197], [41, 180, 217], [45, 165, 245], [49, 151, 277], [53, 143, 301], [57, 134, 341], + [61, 130, 371], [65, 128, 403], [69, 126, 435], [73, 127, 461], [77, 129, 489], [81, 135, 520], + [85, 142, 542], [89, 147, 563], [93, 155, 577], [97, 166, 587], [101, 176, 593], [105, 181, 597], [120, 179, 596], +]; +export const luluMaulC9 = (clipBild: number) => { + const b = Math.min(120, Math.max(0, clipBild)); + let i = 0; + while (i < C9_NASE.length - 2 && C9_NASE[i + 1][0] < b) i++; + const [b0, x0, y0] = C9_NASE[i]; + const [b1, x1, y1] = C9_NASE[i + 1]; + const t = b1 === b0 ? 0 : (b - b0) / (b1 - b0); + // Maul knapp über der Unterkante der Nase + return {cx: C9.x + (x0 + (x1 - x0) * t) * C9.s, cy: C9.y + (y0 + (y1 - y0) * t - 6) * C9.s}; +}; diff --git a/remotion/src/folge01/szene3.tsx b/remotion/src/folge01/szene3.tsx index 301f664..0ddd25d 100644 --- a/remotion/src/folge01/szene3.tsx +++ b/remotion/src/folge01/szene3.tsx @@ -12,7 +12,7 @@ import {APFEL, BLUME_X, BLUME_Y, HAUFEN_X} from './szene2'; // Szene 3: Der Apfel ist zu hoch. Pino hüpft, Noa streckt sich – beide zu klein. // Pino-Bilder schauen nach rechts (zum Apfel); zu Noa (links) wird er gespiegelt. -export const PINO_BAUM_X = 500; // Pino unter dem Apfel +export const PINO_BAUM_X = 470; // Pino unter dem Apfel export const NOA_BAUM_X = 330; // Noa am untersten Ast export const S3 = plane('s3', [ diff --git a/remotion/src/folge01/szene4.tsx b/remotion/src/folge01/szene4.tsx index 8822579..2469716 100644 --- a/remotion/src/folge01/szene4.tsx +++ b/remotion/src/folge01/szene4.tsx @@ -1,8 +1,8 @@ import React from 'react'; import {useCurrentFrame} from 'remotion'; import {kurve} from '../bewegung'; -import {Figur, Geher, Kamera} from '../baukasten/figur'; -import {Blende, Vignette, atmen, hopfStauch, huepfer, kamFahrt} from '../baukasten/helfer'; +import {ClipFigur, Figur, Geher, Kamera} from '../baukasten/figur'; +import {Blende, Vignette, atmen, hopfStauch, huepfer, kamFahrt, luluMaulC9, punkt} from '../baukasten/helfer'; import {Apfel, Blaetterwind, Blume, Laubhaufen} from '../baukasten/natur'; import {BODEN, Ort} from '../baukasten/orte'; import {Untertitel} from '../baukasten/untertitel'; @@ -16,11 +16,13 @@ import {NOA_BAUM_X, PINO_BAUM_X} from './szene3'; // Lulus Maul mit dem Apfel (lulu_A_apfel) liegt 149 px links und 542 px über dem Fußpunkt: // so steht sie, dass sie den Apfel am Baum erreicht. export const LULU_APFEL_X = APFEL.x + 149; -// Mit gesenktem Hals (lulu_A_hals-unten) liegt das Maul 244 px links vom Fußpunkt, knapp über dem Boden. -const LULU_UNTEN_X = 840; +// Den Hals senkt sie mit dem Clip lulu_C9_hals-runter; der Apfel bleibt dabei im Maul +// (Maul-Bahn gemessen, luluMaulC9). Unten liegt das Maul direkt neben Pino. const LULU_WARTE_X = 930; // bis zum Pflücken etwas rechts vom Apfel -const MAUL_UNTEN = {x: LULU_UNTEN_X - 244, y: BODEN - 22}; -const APFEL_BODEN = {x: 578, y: BODEN - 14}; +const C9_TEMPO = 1.3; +const C9_START = 17; // vorher steht sie im Clip still +const C9_UNTEN = 105; // Kopf unten +const c9Frames = (bilder: number) => (bilder * 30) / 24 / C9_TEMPO; export const S4 = plane('s4', [ {id: 'bild0', dauer: 3.4}, @@ -33,55 +35,64 @@ export const S4 = plane('s4', [ {id: 'p2', wer: 'PINO'}, {id: 'l2', wer: 'LULU'}, {id: 'l3', wer: 'LULU'}, - {id: 'bild2', dauer: 3.4}, + {id: 'bild2', dauer: 5.0}, {id: 'p3', wer: 'PINO'}, {id: 'l4', wer: 'LULU'}, {id: 'p4', wer: 'PINO'}, + {id: 'bild3', dauer: 3.6}, ]); export const Szene4: React.FC = () => { const f = useCurrentFrame(); const {z, dauer} = S4; - // Lulu stakst langsam von rechts herein (tapp, tapp) + // Lulu stakst von rechts herein (tapp, tapp), wartet, tritt zum Apfel und frisst ihn const luluAn = z.bild0.ende + 10; const essen = z.bild2.start + 10; - const unten = z.bild2.start + 52; - const ablegen = z.bild2.ende - 6; + const runter = essen + 30; // Hals senken (Clip) + const unten = runter + c9Frames(C9_UNTEN - C9_START); + const ablegen = unten + 10; + const hoch = z.p4.ende - 4; // Hals wieder heben (Clip rückwärts) + const oben = hoch + c9Frames(C9_UNTEN); const luluX = kurve(f, [ [0, 1650], [luluAn, LULU_WARTE_X], [essen - 16, LULU_WARTE_X], [essen + 2, LULU_APFEL_X], - [unten, LULU_APFEL_X], - [unten + 14, LULU_UNTEN_X], ]); + const luluY = BODEN - 6; const luluGeht = f < luluAn; + const imClip = f >= runter && f < oben; const luluPose: [number, string][] = [ [0, 'lulu_A4_staunen'], [z.l1.start, 'lulu_A3_lachen'], [z.l2.start, 'lulu_A5_blick-links'], [z.l3.start + 20, 'lulu_A4_staunen'], [essen, 'lulu_A_apfel'], - [unten, 'lulu_A_hals-unten'], - [z.p4.ende - 4, 'lulu_A3_lachen'], + [oben - 1, 'lulu_A1_augen-zu'], + [oben + 6, 'lulu_A3_lachen'], ]; - // Der Apfel: am Baum → in Lulus Maul (im Bild lulu_A_apfel) → am Maul nach unten → vor Pino + // Der Apfel: am Baum → im Maul (im Bild lulu_A_apfel) → im Maul mit nach unten → neben Pino const apfelAmBaum = f < essen + 6; - const apfelAmMaul = f >= unten + 8; - const apfel = apfelAmMaul - ? { - x: kurve(f, [ - [ablegen, MAUL_UNTEN.x], - [ablegen + 12, APFEL_BODEN.x], - ]), - y: kurve(f, [ - [ablegen, MAUL_UNTEN.y], - [ablegen + 12, APFEL_BODEN.y], - ]), - } - : APFEL; + const clipBild = Math.min(C9_UNTEN, C9_START + ((f - runter) * C9_TEMPO * 24) / 30); + const maul = luluMaulC9(clipBild); + const amMaul = punkt('lulu', luluX, luluY, maul.cx, maul.cy); + const APFEL_BODEN = {x: amMaul.x + 8, y: BODEN - 12}; + const apfelSichtbar = f >= runter && f < 1e9; + const apfel = + f < ablegen + ? amMaul + : { + x: kurve(f, [ + [ablegen, amMaul.x], + [ablegen + 12, APFEL_BODEN.x], + ]), + y: kurve(f, [ + [ablegen, amMaul.y], + [ablegen + 12, APFEL_BODEN.y], + ]), + }; // Pino staunt, freut sich; Stacheln halb hoch bei „noch viel größer“ (kurz aufplustern) const pinoPose: [number, string][] = [ @@ -116,10 +127,10 @@ export const Szene4: React.FC = () => { [z.p2.start + 10, 520, 800, 1.8], [z.l2.start + 10, 640, 430, 1.6], [z.l3.ende, 620, 420, 1.7], - [unten - 10, 620, 440, 1.6], - [unten + 30, 570, 800, 1.8], - [z.p3.start, 560, 780, 1.7], - [dauer, 600, 700, 1.4], + [essen + 20, 640, 470, 1.6], + [unten, 650, 640, 1.2], + [z.p4.ende, 650, 640, 1.2], + [dauer, 680, 600, 1.15], ]); return ( @@ -129,14 +140,22 @@ export const Szene4: React.FC = () => { {apfelAmBaum && } {luluGeht ? ( - + + ) : imClip ? ( + f < hoch ? ( + + ) : ( + + ) ) : ( - + )} + {/* Übergang Apfel-Bild → Clip: das Apfel-Bild blendet kurz aus */} + {f >= runter && f < runter + 8 && } - {apfelAmMaul && } + {apfelSichtbar && } diff --git a/remotion/src/folge01/szene5.tsx b/remotion/src/folge01/szene5.tsx index 1d988b2..89236da 100644 --- a/remotion/src/folge01/szene5.tsx +++ b/remotion/src/folge01/szene5.tsx @@ -1,7 +1,7 @@ import React from 'react'; import {useCurrentFrame} from 'remotion'; import {kurve} from '../bewegung'; -import {Figur, Kamera} from '../baukasten/figur'; +import {ClipFigur, Figur, Kamera} from '../baukasten/figur'; import {Blende, Vignette, atmen, hopfStauch, huepfer, kamFahrt} from '../baukasten/helfer'; import {Blaetterwind, Blume, Laubhaufen} from '../baukasten/natur'; import {BODEN, Ort} from '../baukasten/orte'; @@ -44,23 +44,29 @@ export const Szene5: React.FC = () => { const f = useCurrentFrame(); const {z, dauer} = S5; - // Lulu neigt sich zur Blume (nach links), wackelt, richtet sich wieder auf + // Lulu senkt den Hals (Clip) bis zur Hälfte, bleibt wackelnd stecken, hebt ihn wieder (Clip rückwärts) + const C9_TEMPO = 1.1; + const C9_START = 17; + const C9_HALB = 52; // so weit kommt sie + const c9 = (bilder: number) => (bilder * 30) / 24 / C9_TEMPO; const beugA = z.bild1.start; - const beugE = z.l4.ende; - const neigen = kurve(f, [ - [beugA, 0], - [beugA + 36, 1], - [beugE - 10, 1], - [beugE + 18, 0], + const halb = beugA + c9(C9_HALB - C9_START); + const wieder = z.l4.ende - 6; + const aufrecht = wieder + c9(C9_HALB); + const imClip = f >= beugA && f < aufrecht; + const steckt = kurve(f, [ + [halb - 6, 0], + [halb + 6, 1], + [wieder - 6, 1], + [wieder + 4, 0], ]); - const wackeln = neigen * (Math.sin(f * 0.55) * 3 + Math.sin(f * 0.23) * 2); - const luluDreh = -24 * neigen + wackeln * 1.4; + // angestrengtes Wackeln: kleine Kipp- und Federbewegung um die Füße + const luluDreh = steckt * (Math.sin(f * 0.6) * 1.6 + Math.sin(f * 0.27) * 1.2); + const luluFeder = 1 - steckt * 0.012 * (1 + Math.sin(f * 0.9)); const luluPose: [number, string][] = [ [0, 'lulu_A4_staunen'], [z.l1.start + 10, 'lulu_A3_lachen'], - [beugA + 10, 'lulu_A1_augen-zu'], - [z.l3.start, 'lulu_B2_erschrocken'], - [z.bild2.start, 'lulu_B1_traurig'], + [aufrecht - 1, 'lulu_B1_traurig'], [z.n1.start + 20, 'lulu_A4_staunen'], ]; @@ -101,7 +107,15 @@ export const Szene5: React.FC = () => { - + {imClip ? ( + f < wieder ? ( + + ) : ( + + ) + ) : ( + + )} diff --git a/remotion/src/folge01/szene6.tsx b/remotion/src/folge01/szene6.tsx index f89fbcc..ab73203 100644 --- a/remotion/src/folge01/szene6.tsx +++ b/remotion/src/folge01/szene6.tsx @@ -46,7 +46,7 @@ export const Szene6: React.FC = () => { [da, PINO_AN_BLUME.x], [zurueck, PINO_AN_BLUME.x], [mitteZurueck, VORN.x], - [wieder, NOA_BLUME_X + 120], + [wieder, NOA_BLUME_X + 80], ]); const pinoBoden = kurve(f, [ [hin, BODEN], @@ -67,30 +67,43 @@ export const Szene6: React.FC = () => { const s = 136 / 571; // Pino: Bildschirm-Pixel je Leinwand-Pixel const pfoten = {x: pinoX + 4, y: pinoBoden - 210 * s}; - // Die Blume: am Zaun → in Pinos Pfoten → zu Noa, die sie hochhält - const geben = z.bild1.start + 6; - const hoch = geben + 26; - // rechte Hand in noa_A_arme-hoch: Leinwand (800, 190), Fuß (675, 700), Maßstab 340/535 - const NOA_HAENDE = {x: NOA_BLUME_X + 79, y: BODEN - 310}; + // Die Blume: am Zaun → in Pinos Pfoten → Noa hockt sich hin und nimmt sie → steht auf und hält sie hoch + const hocke = wieder - 12; + const nehmen = wieder + 14; + const aufstehen = nehmen + 14; + const BLENDE = 14; + // Hände: noa_B_hocken auf den Knien (Leinwand ca. 700, 520), noa_A_arme-hoch rechte Hand (800, 190); Fuß (675, 700), Maßstab 340/535 + const m = 340 / 535; + const HAND_HOCKE = {x: NOA_BLUME_X + (700 - 675) * m, y: BODEN + (520 - 700) * m}; + const HAND_OBEN = {x: NOA_BLUME_X + (800 - 675) * m, y: BODEN + (190 - 700) * m}; let blume: {x: number; y: number; s: number} = {x: BLUME_X, y: BLUME_Y, s: 0.8}; - if (f >= pflueck && f < geben) blume = {x: pfoten.x, y: pfoten.y + 20, s: 0.55}; - if (f >= geben) + if (f >= pflueck) blume = {x: pfoten.x, y: pfoten.y + 20, s: 0.55}; + if (f >= nehmen) blume = { x: kurve(f, [ - [geben, NOA_BLUME_X + 120], - [hoch, NOA_HAENDE.x], + [nehmen, pfoten.x], + [nehmen + 10, HAND_HOCKE.x], + [aufstehen, HAND_HOCKE.x], + [aufstehen + BLENDE, HAND_OBEN.x], ]), y: kurve(f, [ - [geben, BODEN - 20], - [hoch, NOA_HAENDE.y], + [nehmen, pfoten.y + 20], + [nehmen + 10, HAND_HOCKE.y + 12], + [aufstehen, HAND_HOCKE.y + 12], + [aufstehen + BLENDE, HAND_OBEN.y + 12], + ]), + s: kurve(f, [ + [nehmen, 0.55], + [aufstehen + BLENDE, 0.62], ]), - s: 0.62, }; + const wechsel = (t: number) => (f >= t && f < t + BLENDE ? Math.sin(((f - t) / BLENDE) * Math.PI) : 0); + const noaStauch = 1 - 0.05 * wechsel(hocke) + 0.03 * wechsel(aufstehen); const noaPose: [number, string][] = [ [0, 'noa_A3_lachen'], - [geben, 'noa_A_arme-hoch'], - [z.e1.start, 'noa_A_arme-hoch'], + [hocke, 'noa_B_hocken'], + [aufstehen, 'noa_A_arme-hoch'], [z.e3.start, 'noa_A3_lachen'], ]; const blumeImBild = f < z.e3.start; @@ -114,7 +127,7 @@ export const Szene6: React.FC = () => { [da, 1450, 780, 1.7], [zurueck, 1350, 780, 1.6], [wieder, 960, 740, 1.6], - [hoch, 960, 660, 1.5], + [aufstehen + BLENDE, 960, 680, 1.5], [nahA - 1, 960, 660, 1.5], [nahA, LULU_NASE.x - 20, LULU_NASE.y + 60, 3.2], [nahE - 1, LULU_NASE.x - 20, LULU_NASE.y + 60, 3.0], @@ -133,7 +146,7 @@ export const Szene6: React.FC = () => { {f < pflueck && } - + {pinoGehtHin ? ( ) : pinoGehtZurueck ? ( diff --git a/remotion/src/folge01/szene7.tsx b/remotion/src/folge01/szene7.tsx index 46f4c6a..accd96f 100644 --- a/remotion/src/folge01/szene7.tsx +++ b/remotion/src/folge01/szene7.tsx @@ -63,7 +63,7 @@ export const Szene7: React.FC = () => { <> - +