116 lines
6.2 KiB
TypeScript
116 lines
6.2 KiB
TypeScript
import React from 'react';
|
||
import {random, useCurrentFrame} from 'remotion';
|
||
import {ClipFigur, Figur, GROESSE, NOA_HOEHE} from '../baukasten/figur';
|
||
import {Drachen, type DrachenLage, drachenHalt} from '../baukasten/drachen';
|
||
import {Drachenschnur, spulenPunkt} from '../baukasten/drachenschnur';
|
||
import {luluMaulC9, punkt} from '../baukasten/helfer';
|
||
import {Blaetterwind} from '../baukasten/natur';
|
||
import {BODEN, Ort} from '../baukasten/orte';
|
||
|
||
// Bildteile für Folge 9 „Lulu und der Drachen“. Der Wind weht die ganze Folge von links nach rechts:
|
||
// Blätter, Grashalme, Drachenschwanz und Lulus Schal (Clip lulu_C11_schal-wind) zeigen alle nach rechts.
|
||
// Feste Plätze mitten auf der Wiese, weit weg vom Apfelbaum (links bei x ≈ 340).
|
||
|
||
// Lulu steht links (windaufwärts), Noa rechts: Steigt der Drachen nach rechts (mit dem Wind), läuft die
|
||
// Schnur von Noa nach rechts oben – nie an Lulus Hals vorbei. Pino ganz links am Boden.
|
||
export const PLATZ = {noa: 1340, lulu: 1000, pino: 600};
|
||
export const LULU_Y = BODEN - 6;
|
||
// Lulu senkt den Hals (Clip C9) bis Clipbild 105: dann ist ihr Maul hier, knapp über dem Drachen im Gras
|
||
const C9_TEMPO = 1.25;
|
||
export const C9_START = 17;
|
||
export const C9_UNTEN = 105;
|
||
export const c9 = (bilder: number) => (bilder * 30) / 24 / C9_TEMPO;
|
||
const MAUL_UNTEN = luluMaulC9(C9_UNTEN);
|
||
export const DRACHEN_IM_GRAS = {x: punkt('lulu', PLATZ.lulu, LULU_Y, MAUL_UNTEN.cx, MAUL_UNTEN.cy).x, y: BODEN - 40};
|
||
// In der Pose noa_A_haende-oben sind die Fäustlinge um 674/218 – der Drachen liegt dazwischen
|
||
export const drachenUeberNoa = (noaX: number) => punkt('noa', noaX, BODEN, 674.5, 80); // über dem Kopf, nicht vor dem Gesicht
|
||
|
||
// Windiger Herbstgarten: Blätter wehen nach rechts, Grashalme biegen sich im Takt der Böen
|
||
export const WindGarten: React.FC<{abend?: boolean; wind?: number; boeen?: number}> = ({abend = false, wind = 1, boeen = 0}) => {
|
||
const f = useCurrentFrame();
|
||
const boe = 0.6 + 0.4 * Math.sin(f * 0.05) + boeen;
|
||
return (
|
||
<>
|
||
<Ort name={abend ? 'garten_herbst_abend' : 'garten_herbst'} />
|
||
{wind > 0 && <Blaetterwind anzahl={Math.round(8 * wind + 10 * boeen)} seed="drachen" tempo={wind * (1 + boeen)} yBereich={[380, 900]} />}
|
||
<svg viewBox="0 0 1920 1080" style={{position: 'absolute', inset: 0, width: 1920, height: 1080}}>
|
||
{new Array(70).fill(0).map((_, i) => {
|
||
const x = random(`gh${i}`) * 1960 - 20;
|
||
const y = BODEN + 10 + random(`ghy${i}`) * 130;
|
||
const l = 22 + random(`ghl${i}`) * 26;
|
||
const biegen = wind * (14 + 10 * boe) * (0.8 + random(`ghb${i}`) * 0.4) + Math.sin(f * 0.2 + i) * 3 * wind;
|
||
return <path key={i} d={`M ${x} ${y} q ${biegen * 0.4} ${-l * 0.6} ${biegen} ${-l}`} stroke={random(`ghf${i}`) < 0.5 ? '#7f9a4c' : '#93ad5c'} strokeWidth={4} fill="none" strokeLinecap="round" />;
|
||
})}
|
||
</svg>
|
||
</>
|
||
);
|
||
};
|
||
|
||
// Lulu im Wind: Clip mit wehendem Schal (Schleife), sonst eine Pose
|
||
export const LuluImWind: React.FC<{pose?: string; stauchY?: number}> = ({pose, stauchY}) =>
|
||
pose ? (
|
||
<Figur pose={pose} x={PLATZ.lulu} y={LULU_Y} stauchY={stauchY} />
|
||
) : (
|
||
<ClipFigur name="lulu_C11_schal-wind" start={0} schleife tempo={1.25} x={PLATZ.lulu} y={LULU_Y} stauchY={stauchY} ersatz="lulu_A3_lachen" />
|
||
);
|
||
|
||
// Drachen samt Schnur zu Noas Spule (Pose noa_A_drachenschnur). Schaut Noa nach links (spiegeln),
|
||
// läuft die Schnur hinter ihr – deshalb vor ihr zeichnen lassen (vorNoa = false).
|
||
export const SchnurUndDrachen: React.FC<{lage: DrachenLage; noaX: number; spiegeln: boolean; schnur?: boolean; flattern?: number; leuchten?: [number, number, number]}> = ({
|
||
lage,
|
||
noaX,
|
||
spiegeln,
|
||
schnur = true,
|
||
flattern,
|
||
leuchten,
|
||
}) => {
|
||
const hoch = lage.y < BODEN - 160;
|
||
const schwanz = lage.liegt ? 0 : hoch ? 30 : 12;
|
||
return (
|
||
<>
|
||
{schnur && (
|
||
<Drachenschnur
|
||
von={spulenPunkt(noaX, BODEN, NOA_HOEHE * GROESSE.noa, spiegeln)}
|
||
nach={drachenHalt(lage.x, lage.y, 1, lage.drehung)}
|
||
durchhang={hoch ? 0.05 : 0.12}
|
||
/>
|
||
)}
|
||
<Drachen {...lage} schwanz={schwanz} flattern={flattern ?? (lage.liegt ? 0 : 14)} boden={BODEN} leuchten={leuchten} />
|
||
</>
|
||
);
|
||
};
|
||
|
||
// Lulu holt den Drachen aus dem Gras und hält ihn mit dem Maul hoch: Hals runter (Clip C9 vorwärts),
|
||
// Drachen ins Maul, Hals hoch (Clip rückwärts, bleibt auf dem ersten Clipbild stehen). Der Drachen hängt
|
||
// die ganze Zeit an seiner oberen Spitze im Maul. Keine Pose lulu_A_apfel: dort ist der Apfel gemalt.
|
||
// Die Schnur hält immer Noa.
|
||
export const hebenPlan = (start: number) => {
|
||
const unten = start + c9(C9_UNTEN - C9_START);
|
||
const hoch = unten + 10;
|
||
const oben = hoch + c9(C9_UNTEN);
|
||
return {start, unten, hoch, oben};
|
||
};
|
||
|
||
export const drachenBeiLulu = (f: number, plan: ReturnType<typeof hebenPlan>): DrachenLage => {
|
||
if (f < plan.unten) return {x: DRACHEN_IM_GRAS.x, y: BODEN - 36, drehung: 90, liegt: true};
|
||
const bild = f < plan.hoch ? C9_UNTEN : Math.max(0, C9_UNTEN - ((f - plan.hoch) * 1.25 * 24) / 30);
|
||
const m = luluMaulC9(bild);
|
||
const maul = punkt('lulu', PLATZ.lulu, LULU_Y, m.cx, m.cy);
|
||
// Die obere Spitze (42 px über dem Leistenkreuz) steckt im Maul. Beim Hochheben kippt der Drachen
|
||
// nach vorn (Lulu schaut nach links), damit er ihr Gesicht nicht verdeckt.
|
||
const w = 35 * Math.min(1, Math.max(0, (f - plan.hoch) / 40));
|
||
const r = (w * Math.PI) / 180;
|
||
return {x: maul.x - 40 * Math.sin(r), y: maul.y + 40 * Math.cos(r), drehung: w};
|
||
};
|
||
|
||
// `danach`: was Lulu nach dem Loslassen zeigt ('wind' = Schal-Clip) – nie die Apfel-Pose ohne Drachen!
|
||
export const LuluHebt: React.FC<{plan: ReturnType<typeof hebenPlan>; danach?: string}> = ({plan, danach}) => {
|
||
const f = useCurrentFrame();
|
||
if (f < plan.start) return <LuluImWind />;
|
||
if (f < plan.hoch) return <ClipFigur name="lulu_C9_hals-runter" start={plan.start - c9(C9_START)} tempo={1.25} bis={C9_UNTEN / 24} x={PLATZ.lulu} y={LULU_Y} />;
|
||
if (f < plan.oben) return <ClipFigur name="lulu_C9_hals-runter" start={plan.hoch} tempo={1.25} bis={C9_UNTEN / 24} rueckwaerts x={PLATZ.lulu} y={LULU_Y} />;
|
||
if (danach === 'wind') return <LuluImWind />;
|
||
if (danach) return <LuluImWind pose={danach} />;
|
||
// solange sie den Drachen hält: Hals oben, letztes Bild des rückwärts laufenden Clips
|
||
return <ClipFigur name="lulu_C9_hals-runter" start={plan.hoch} tempo={1.25} bis={C9_UNTEN / 24} rueckwaerts x={PLATZ.lulu} y={LULU_Y} />;
|
||
};
|