import React, { useMemo } from 'react'; import { useCurrentFrame, useVideoConfig, interpolate, Easing, AbsoluteFill, } from 'remotion'; import * as THREE from 'three'; // ============================================================================= // COMPOSITION CONFIG // ============================================================================= export const compositionConfig = { id: 'WindmillMorning', durationInSeconds: 5, fps: 60, width: 1920, height: 1080, }; // ============================================================================= // STYLE CONSTANTS - MORNING COLORS // ============================================================================= const COLORS = { // Morning sky gradient skyTop: 0x1e90ff, // Deep blue skyMiddle: 0x87ceeb, // Light sky blue skyHorizon: 0xfff8dc, // Warm cream horizon // Bright morning sun sun: 0xfffacd, // Lemon chiffon sunGlow: 0xffd700, // Golden glow // Fresh green ground ground: 0x3cb371, // Medium sea green groundDark: 0x2e8b57, // Sea green for hills // Windmill windmillBody: 0xfaf0e6, // Linen white windmillRoof: 0x8b4513, // Saddle brown windmillBlade: 0x5c4033, // Dark wood // Vibrant tulips tulipRed: 0xe63946, tulipOrange: 0xf4a261, tulipYellow: 0xf9c74f, tulipPink: 0xf72585, tulipStem: 0x228b22, // Forest green // Morning clouds cloud: 0xffffff, cloudShadow: 0xe8e8e8, } as const; const EASINGS = { gentle: Easing.bezier(0.25, 0.1, 0.25, 1), easeOut: Easing.bezier(0.33, 1, 0.68, 1), easeInOut: Easing.bezier(0.37, 0, 0.63, 1), }; // ============================================================================= // SEEDED RANDOM // ============================================================================= const seededRandom = (seed: number): number => { const x = Math.sin(seed * 9999) * 10000; return x - Math.floor(x); }; // ============================================================================= // TYPE DEFINITIONS // ============================================================================= interface TulipData { x: number; z: number; color: number; height: number; rotation: number; swayOffset: number; swaySpeed: number; } interface CloudData { x: number; y: number; z: number; scale: number; speed: number; sphereOffsets: { cx: number; cy: number; cz: number; cr: number }[]; } interface BirdData { startX: number; y: number; z: number; speed: number; wingOffset: number; } // ============================================================================= // THREE.JS SCENE COMPONENT // ============================================================================= const ThreeScene: React.FC<{ frame: number; fps: number; durationInFrames: number }> = ({ frame, fps, durationInFrames, }) => { const canvasRef = React.useRef(null); const time = frame / fps; const sceneData = useMemo(() => { const tulipColors = [COLORS.tulipRed, COLORS.tulipOrange, COLORS.tulipYellow, COLORS.tulipPink]; const tulips: TulipData[] = []; for (let i = 0; i < 3000; i++) { const x = seededRandom(i * 1.1) * 60 - 30; const z = seededRandom(i * 2.2) * 25 + 5; const distFromWindmill = Math.sqrt(x * x + (z - 15) * (z - 15)); if (distFromWindmill < 6) continue; tulips.push({ x, z, color: tulipColors[Math.floor(seededRandom(i * 3.3) * 4)], height: 0.3 + seededRandom(i * 4.4) * 0.15, rotation: seededRandom(i * 5.5) * Math.PI * 2, swayOffset: seededRandom(i * 6.6) * Math.PI * 2, swaySpeed: 1.5 + seededRandom(i * 7.7) * 1, }); } // More fluffy morning clouds with varied shapes const clouds: CloudData[] = []; for (let i = 0; i < 12; i++) { const numSpheres = 5 + Math.floor(seededRandom(i * 50) * 5); const sphereOffsets: { cx: number; cy: number; cz: number; cr: number }[] = []; for (let j = 0; j < numSpheres; j++) { sphereOffsets.push({ cx: (seededRandom(i * 100 + j * 1) - 0.5) * 3, cy: seededRandom(i * 100 + j * 2) * 0.6, cz: (seededRandom(i * 100 + j * 3) - 0.5) * 1.5, cr: 0.4 + seededRandom(i * 100 + j * 4) * 0.8, }); } clouds.push({ x: seededRandom(i * 10.1) * 140 - 70, y: 22 + seededRandom(i * 11.2) * 18, z: -35 - seededRandom(i * 12.3) * 40, scale: 2.5 + seededRandom(i * 13.4) * 4, speed: 1.5 + seededRandom(i * 14.5) * 2, sphereOffsets, }); } // Birds flying in the morning sky const birds: BirdData[] = []; for (let i = 0; i < 5; i++) { birds.push({ startX: -60 + seededRandom(i * 200) * 20, y: 18 + seededRandom(i * 201) * 12, z: -20 - seededRandom(i * 202) * 25, speed: 8 + seededRandom(i * 203) * 4, wingOffset: seededRandom(i * 204) * Math.PI * 2, }); } return { tulips, clouds, birds }; }, []); React.useEffect(() => { if (!canvasRef.current) return; const canvas = canvasRef.current; const width = 1920; const height = 1080; const renderer = new THREE.WebGLRenderer({ canvas, antialias: true, powerPreference: 'high-performance', }); renderer.setSize(width, height); renderer.shadowMap.enabled = true; renderer.shadowMap.type = THREE.PCFSoftShadowMap; renderer.toneMapping = THREE.ACESFilmicToneMapping; renderer.toneMappingExposure = 1.2; const scene = new THREE.Scene(); // Light morning haze scene.fog = new THREE.FogExp2(0xb0d4f1, 0.008); const camera = new THREE.PerspectiveCamera(50, width / height, 0.1, 500); // ========================================================================= // LIGHTING - BRIGHT MORNING SUN // ========================================================================= const ambientLight = new THREE.AmbientLight(0xadd8e6, 0.5); scene.add(ambientLight); // Main sun light from the right (east - morning sun position) const sunLight = new THREE.DirectionalLight(0xfffaf0, 1.4); sunLight.position.set(50, 40, -30); sunLight.castShadow = true; sunLight.shadow.mapSize.width = 2048; sunLight.shadow.mapSize.height = 2048; sunLight.shadow.camera.near = 1; sunLight.shadow.camera.far = 150; sunLight.shadow.camera.left = -50; sunLight.shadow.camera.right = 50; sunLight.shadow.camera.top = 50; sunLight.shadow.camera.bottom = -50; sunLight.shadow.bias = -0.0002; scene.add(sunLight); // Sky light (hemisphere) const fillLight = new THREE.HemisphereLight(0x87ceeb, 0x3cb371, 0.4); scene.add(fillLight); // Soft back fill const backLight = new THREE.DirectionalLight(0xe6f3ff, 0.3); backLight.position.set(-30, 20, 20); scene.add(backLight); // ========================================================================= // SKY - BEAUTIFUL MORNING BLUE // ========================================================================= const skyGeo = new THREE.SphereGeometry(150, 64, 64); const skyMat = new THREE.ShaderMaterial({ uniforms: { topColor: { value: new THREE.Color(COLORS.skyTop) }, middleColor: { value: new THREE.Color(COLORS.skyMiddle) }, bottomColor: { value: new THREE.Color(COLORS.skyHorizon) }, }, vertexShader: ` varying vec3 vWorldPosition; void main() { vec4 worldPosition = modelMatrix * vec4(position, 1.0); vWorldPosition = worldPosition.xyz; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); } `, fragmentShader: ` uniform vec3 topColor; uniform vec3 middleColor; uniform vec3 bottomColor; varying vec3 vWorldPosition; void main() { float h = normalize(vWorldPosition).y; vec3 color; if (h > 0.4) { color = mix(middleColor, topColor, (h - 0.4) / 0.6); } else if (h > 0.0) { color = mix(bottomColor, middleColor, h / 0.4); } else { color = bottomColor; } gl_FragColor = vec4(color, 1.0); } `, side: THREE.BackSide, }); scene.add(new THREE.Mesh(skyGeo, skyMat)); // ========================================================================= // SUN - BRIGHT MORNING SUN // ========================================================================= const sunGeo = new THREE.SphereGeometry(6, 32, 32); const sunMat = new THREE.MeshBasicMaterial({ color: COLORS.sun, }); const sun = new THREE.Mesh(sunGeo, sunMat); sun.position.set(60, 35, -70); scene.add(sun); // Sun glow layers const glowSizes = [9, 12, 16]; const glowOpacities = [0.4, 0.2, 0.1]; glowSizes.forEach((size, i) => { const glowGeo = new THREE.SphereGeometry(size, 32, 32); const glowMat = new THREE.MeshBasicMaterial({ color: COLORS.sunGlow, transparent: true, opacity: glowOpacities[i], }); const glow = new THREE.Mesh(glowGeo, glowMat); glow.position.copy(sun.position); scene.add(glow); }); // Sun rays (subtle light beams) for (let i = 0; i < 8; i++) { const rayAngle = (i / 8) * Math.PI * 2; const rayLength = 15 + seededRandom(i * 500) * 10; const rayGeo = new THREE.PlaneGeometry(0.5, rayLength); const rayMat = new THREE.MeshBasicMaterial({ color: 0xfffacd, transparent: true, opacity: 0.15, side: THREE.DoubleSide, }); const ray = new THREE.Mesh(rayGeo, rayMat); ray.position.copy(sun.position); ray.position.x += Math.cos(rayAngle) * rayLength * 0.3; ray.position.y += Math.sin(rayAngle) * rayLength * 0.3; ray.rotation.z = rayAngle + Math.PI / 2; scene.add(ray); } // ========================================================================= // GROUND // ========================================================================= const groundGeo = new THREE.PlaneGeometry(200, 200); const groundMat = new THREE.MeshStandardMaterial({ color: COLORS.ground, roughness: 0.85, }); const ground = new THREE.Mesh(groundGeo, groundMat); ground.rotation.x = -Math.PI / 2; ground.position.y = 0; ground.receiveShadow = true; scene.add(ground); // ========================================================================= // DISTANT HILLS - MORNING COLORS // ========================================================================= const createHill = (x: number, z: number, w: number, h: number, d: number, color: number) => { const hillGeo = new THREE.SphereGeometry(1, 32, 16); hillGeo.scale(w, h, d); const hillMat = new THREE.MeshStandardMaterial({ color, roughness: 0.85, }); const hill = new THREE.Mesh(hillGeo, hillMat); hill.position.set(x, h * 0.2, z); hill.receiveShadow = true; return hill; }; scene.add(createHill(-50, -60, 40, 15, 25, 0x2e8b57)); scene.add(createHill(40, -70, 50, 18, 30, 0x228b22)); scene.add(createHill(0, -55, 35, 12, 20, 0x32cd32)); // ========================================================================= // WINDMILL // ========================================================================= const windmillGroup = new THREE.Group(); const bodyMat = new THREE.MeshStandardMaterial({ color: COLORS.windmillBody, roughness: 0.75, }); const baseGeo = new THREE.CylinderGeometry(2.5, 3.5, 10, 8); const base = new THREE.Mesh(baseGeo, bodyMat); base.position.y = 5; base.castShadow = true; base.receiveShadow = true; windmillGroup.add(base); const topGeo = new THREE.CylinderGeometry(1.8, 2.5, 4, 8); const top = new THREE.Mesh(topGeo, bodyMat); top.position.y = 12; top.castShadow = true; windmillGroup.add(top); const roofGeo = new THREE.ConeGeometry(2.5, 3, 8); const roofMat = new THREE.MeshStandardMaterial({ color: COLORS.windmillRoof, roughness: 0.7, }); const roof = new THREE.Mesh(roofGeo, roofMat); roof.position.y = 15.5; roof.castShadow = true; windmillGroup.add(roof); const doorGeo = new THREE.BoxGeometry(1.2, 2.5, 0.2); const doorMat = new THREE.MeshStandardMaterial({ color: 0x5c4033, roughness: 0.7, }); const door = new THREE.Mesh(doorGeo, doorMat); door.position.set(0, 1.25, 3.4); windmillGroup.add(door); // Windows with morning light reflection for (let i = 0; i < 3; i++) { const windowGeo = new THREE.BoxGeometry(0.8, 1, 0.2); const windowMat = new THREE.MeshStandardMaterial({ color: 0x87ceeb, emissive: 0x4a90d9, emissiveIntensity: 0.2, roughness: 0.1, metalness: 0.3, }); const windowMesh = new THREE.Mesh(windowGeo, windowMat); windowMesh.position.set(0, 4 + i * 3.5, 3.3 - i * 0.3); windmillGroup.add(windowMesh); } // Windmill blades const bladesGroup = new THREE.Group(); const bladeMat = new THREE.MeshStandardMaterial({ color: COLORS.windmillBlade, roughness: 0.6, side: THREE.DoubleSide, }); const hubGeo = new THREE.CylinderGeometry(0.5, 0.5, 0.8, 16); const hub = new THREE.Mesh(hubGeo, bladeMat); hub.rotation.x = Math.PI / 2; bladesGroup.add(hub); for (let i = 0; i < 4; i++) { const bladeGroup = new THREE.Group(); const armGeo = new THREE.BoxGeometry(0.15, 6, 0.1); const arm = new THREE.Mesh(armGeo, bladeMat); arm.position.y = 3; arm.castShadow = true; bladeGroup.add(arm); const sailGeo = new THREE.PlaneGeometry(1.2, 5); const sailMat = new THREE.MeshStandardMaterial({ color: 0xfaf0e6, roughness: 0.9, side: THREE.DoubleSide, transparent: true, opacity: 0.92, }); const sail = new THREE.Mesh(sailGeo, sailMat); sail.position.set(0.7, 3.5, 0); sail.rotation.y = 0.1; sail.castShadow = true; bladeGroup.add(sail); bladeGroup.rotation.z = (i / 4) * Math.PI * 2; bladesGroup.add(bladeGroup); } bladesGroup.position.set(0, 13, 2.8); const bladeRotation = time * 0.5; // Gentle morning breeze bladesGroup.rotation.z = bladeRotation; windmillGroup.add(bladesGroup); windmillGroup.position.set(0, 0, -8); scene.add(windmillGroup); // ========================================================================= // TULIP FIELD (INSTANCED) // ========================================================================= const tulipHeadGeo = new THREE.SphereGeometry(0.12, 8, 6); tulipHeadGeo.scale(1, 1.4, 1); const tulipStemGeo = new THREE.CylinderGeometry(0.02, 0.025, 0.4, 6); const tulipColors = [COLORS.tulipRed, COLORS.tulipOrange, COLORS.tulipYellow, COLORS.tulipPink]; const tulipMeshes: THREE.InstancedMesh[] = []; const stemMesh = new THREE.InstancedMesh( tulipStemGeo, new THREE.MeshStandardMaterial({ color: COLORS.tulipStem, roughness: 0.75 }), sceneData.tulips.length ); stemMesh.castShadow = true; scene.add(stemMesh); tulipColors.forEach((color) => { const mesh = new THREE.InstancedMesh( tulipHeadGeo, new THREE.MeshStandardMaterial({ color, roughness: 0.65 }), sceneData.tulips.filter((t) => t.color === color).length ); mesh.castShadow = true; tulipMeshes.push(mesh); scene.add(mesh); }); const dummy = new THREE.Object3D(); const colorIndices: number[] = [0, 0, 0, 0]; sceneData.tulips.forEach((tulip, i) => { const windWave = Math.sin(time * tulip.swaySpeed + tulip.swayOffset + tulip.x * 0.2) * 0.12; const windWave2 = Math.sin(time * tulip.swaySpeed * 0.7 + tulip.swayOffset + tulip.z * 0.15) * 0.08; dummy.position.set(tulip.x, tulip.height * 0.5, tulip.z); dummy.rotation.set(windWave, tulip.rotation, windWave2); dummy.scale.set(1, tulip.height / 0.3, 1); dummy.updateMatrix(); stemMesh.setMatrixAt(i, dummy.matrix); const headY = tulip.height + 0.08; dummy.position.set( tulip.x + Math.sin(windWave) * tulip.height * 0.3, headY, tulip.z + Math.sin(windWave2) * tulip.height * 0.2 ); dummy.rotation.set(windWave * 0.5, tulip.rotation, windWave2 * 0.5); dummy.scale.setScalar(1); dummy.updateMatrix(); const colorIndex = tulipColors.indexOf(tulip.color); tulipMeshes[colorIndex].setMatrixAt(colorIndices[colorIndex], dummy.matrix); colorIndices[colorIndex]++; }); stemMesh.instanceMatrix.needsUpdate = true; tulipMeshes.forEach((mesh) => { mesh.instanceMatrix.needsUpdate = true; }); // ========================================================================= // FLUFFY WHITE CLOUDS - MOVING ACROSS SKY // ========================================================================= const createCloud = (cloudData: CloudData, timeOffset: number) => { const group = new THREE.Group(); const cloudMat = new THREE.MeshStandardMaterial({ color: COLORS.cloud, roughness: 1, transparent: true, opacity: 0.95, }); const shadowMat = new THREE.MeshStandardMaterial({ color: COLORS.cloudShadow, roughness: 1, transparent: true, opacity: 0.9, }); cloudData.sphereOffsets.forEach((sphere, idx) => { const part = new THREE.Mesh( new THREE.SphereGeometry(sphere.cr, 12, 12), idx < 2 ? shadowMat : cloudMat // Bottom spheres slightly darker ); part.position.set(sphere.cx, sphere.cy, sphere.cz); group.add(part); }); // Clouds move from right to left (west wind in morning) const wrappedX = ((cloudData.x - timeOffset * cloudData.speed) % 160) - 80; group.position.set(wrappedX, cloudData.y, cloudData.z); group.scale.setScalar(cloudData.scale); // Subtle bobbing motion group.position.y += Math.sin(timeOffset * 0.3 + cloudData.x * 0.1) * 0.5; return group; }; sceneData.clouds.forEach((c) => { scene.add(createCloud(c, time)); }); // ========================================================================= // BIRDS FLYING // ========================================================================= const createBird = (bird: BirdData) => { const birdGroup = new THREE.Group(); const birdMat = new THREE.MeshBasicMaterial({ color: 0x2f2f2f }); // Simple bird shape - body const bodyGeo = new THREE.SphereGeometry(0.15, 6, 6); bodyGeo.scale(2, 0.8, 1); const body = new THREE.Mesh(bodyGeo, birdMat); birdGroup.add(body); // Wings const wingFlap = Math.sin(time * 12 + bird.wingOffset) * 0.5; const leftWing = new THREE.Mesh( new THREE.PlaneGeometry(0.6, 0.2), birdMat ); leftWing.position.set(-0.3, 0.1, 0); leftWing.rotation.z = wingFlap + 0.3; birdGroup.add(leftWing); const rightWing = new THREE.Mesh( new THREE.PlaneGeometry(0.6, 0.2), birdMat ); rightWing.position.set(0.3, 0.1, 0); rightWing.rotation.z = -wingFlap - 0.3; birdGroup.add(rightWing); // Position with movement const birdX = bird.startX + time * bird.speed; const bobY = Math.sin(time * 3 + bird.wingOffset) * 0.3; birdGroup.position.set(birdX, bird.y + bobY, bird.z); birdGroup.rotation.y = -Math.PI / 2; // Face direction of movement return birdGroup; }; sceneData.birds.forEach((bird) => { if (bird.startX + time * bird.speed < 80) { scene.add(createBird(bird)); } }); // ========================================================================= // FLOATING PARTICLES (MORNING DEW / POLLEN) // ========================================================================= for (let i = 0; i < 60; i++) { const px = seededRandom(i * 100) * 40 - 20; const py = 0.5 + seededRandom(i * 101) * 10; const pz = seededRandom(i * 102) * 30; const floatY = Math.sin(time * 1.2 + seededRandom(i * 103) * Math.PI * 2) * 0.4; const driftX = Math.sin(time * 0.6 + seededRandom(i * 104) * Math.PI * 2) * 0.25; const particle = new THREE.Mesh( new THREE.SphereGeometry(0.025, 4, 4), new THREE.MeshBasicMaterial({ color: 0xffffff, transparent: true, opacity: 0.5, }) ); particle.position.set(px + driftX, py + floatY, pz); scene.add(particle); } // ========================================================================= // CAMERA ANIMATION // ========================================================================= const cameraX = interpolate(time, [0, 5], [-8, 8], { easing: EASINGS.gentle, extrapolateRight: 'clamp', }); const wobbleY = Math.sin(time * 0.8) * 0.05; const wobbleX = Math.sin(time * 0.6) * 0.03; camera.position.set(cameraX + wobbleX, 4 + wobbleY, 22); camera.lookAt(0, 6, -8); // ========================================================================= // RENDER // ========================================================================= renderer.render(scene, camera); return () => { renderer.dispose(); }; }, [frame, fps, durationInFrames, time, sceneData]); return ( ); }; // ============================================================================= // MAIN COMPONENT // ============================================================================= const WindmillMorning: React.FC = () => { const frame = useCurrentFrame(); const { durationInFrames, fps } = useVideoConfig(); return ( ); }; export default WindmillMorning;