dsh-drawai 0.3.3 → 0.4.0
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/LICENSE +4142 -0
- package/README.md +43 -10
- package/lib/audio-media.js +147 -0
- package/lib/audio-service.js +66 -0
- package/lib/canvas-format.js +134 -0
- package/lib/client.js +5283 -223
- package/lib/comfyui-default-workflow.mjs +1825 -0
- package/lib/comfyui-workflow.mjs +426 -0
- package/lib/document-commands.js +12 -0
- package/lib/document-core.js +29 -0
- package/lib/drawing-tasks.js +26 -0
- package/lib/imagegen.mjs +596 -0
- package/lib/index.js +1758 -129
- package/lib/media.js +26 -13
- package/lib/mgstyle-core.js +75 -0
- package/lib/motion/apply.js +180 -0
- package/lib/motion/asset-pack-lock.js +10 -0
- package/lib/motion/asset-pack.js +335 -0
- package/lib/motion/assets.js +290 -0
- package/lib/motion/audio.js +103 -0
- package/lib/motion/audiolevel.js +63 -0
- package/lib/motion/audiomix.js +144 -0
- package/lib/motion/beats.js +337 -0
- package/lib/motion/builtin-assets.js +62 -0
- package/lib/motion/canvas-spec.js +442 -0
- package/lib/motion/capture.js +95 -0
- package/lib/motion/cards.js +80 -0
- package/lib/motion/collect.js +447 -0
- package/lib/motion/delivery.js +112 -0
- package/lib/motion/edge-speech.py +90 -0
- package/lib/motion/entry.jsx +73 -0
- package/lib/motion/export.js +200 -0
- package/lib/motion/ffmpeg.js +42 -0
- package/lib/motion/fonts.js +71 -0
- package/lib/motion/jobs.js +113 -0
- package/lib/motion/json.js +4 -0
- package/lib/motion/media-catalog.js +59 -0
- package/lib/motion/mgstyles/BRIEF-TEMPLATE.md +63 -0
- package/lib/motion/mgstyles/briefs/01-flat-vector.md +104 -0
- package/lib/motion/mgstyles/briefs/02-line-art.md +97 -0
- package/lib/motion/mgstyles/briefs/03-isometric.md +108 -0
- package/lib/motion/mgstyles/briefs/05-cel-boil.md +145 -0
- package/lib/motion/mgstyles/briefs/06-collage.md +149 -0
- package/lib/motion/mgstyles/briefs/07-liquid.md +106 -0
- package/lib/motion/mgstyles/briefs/08-morph.md +111 -0
- package/lib/motion/mgstyles/briefs/09-bauhaus.md +150 -0
- package/lib/motion/mgstyles/briefs/10-synthwave.md +151 -0
- package/lib/motion/mgstyles/briefs/12-aurora-glass.md +147 -0
- package/lib/motion/mgstyles/briefs/18-hanazi.md +140 -0
- package/lib/motion/mgstyles/briefs/19-paperclip.md +156 -0
- package/lib/motion/mgstyles/briefs/20-pixel.md +118 -0
- package/lib/motion/mgstyles/briefs/22-hud.md +118 -0
- package/lib/motion/mgstyles/catalog.d/01-flat-vector.json +34 -0
- package/lib/motion/mgstyles/catalog.d/02-line-art.json +34 -0
- package/lib/motion/mgstyles/catalog.d/03-isometric.json +35 -0
- package/lib/motion/mgstyles/catalog.d/05-cel-boil.json +28 -0
- package/lib/motion/mgstyles/catalog.d/06-collage.json +28 -0
- package/lib/motion/mgstyles/catalog.d/07-liquid.json +28 -0
- package/lib/motion/mgstyles/catalog.d/08-morph.json +28 -0
- package/lib/motion/mgstyles/catalog.d/09-bauhaus.json +28 -0
- package/lib/motion/mgstyles/catalog.d/10-synthwave.json +28 -0
- package/lib/motion/mgstyles/catalog.d/12-aurora-glass.json +28 -0
- package/lib/motion/mgstyles/catalog.d/18-hanazi.json +28 -0
- package/lib/motion/mgstyles/catalog.d/19-paperclip.json +28 -0
- package/lib/motion/mgstyles/catalog.d/20-pixel.json +28 -0
- package/lib/motion/mgstyles/catalog.d/22-hud.json +28 -0
- package/lib/motion/mgstyles/catalog.json +1356 -0
- package/lib/motion/mgstyles/upstream/README.md +147 -0
- package/lib/motion/mgstyles/upstream/README.zh-CN.md +147 -0
- package/lib/motion/mgstyles/upstream/demos/01-flat-vector/cues.json +1 -0
- package/lib/motion/mgstyles/upstream/demos/01-flat-vector/index.html +712 -0
- package/lib/motion/mgstyles/upstream/demos/02-line-art/cues.json +331 -0
- package/lib/motion/mgstyles/upstream/demos/02-line-art/film.js +676 -0
- package/lib/motion/mgstyles/upstream/demos/02-line-art/index.html +18 -0
- package/lib/motion/mgstyles/upstream/demos/02-line-art/tools/dump_timing.mjs +26 -0
- package/lib/motion/mgstyles/upstream/demos/03-isometric/cues.json +109 -0
- package/lib/motion/mgstyles/upstream/demos/03-isometric/index.html +25 -0
- package/lib/motion/mgstyles/upstream/demos/03-isometric/js/anim.js +82 -0
- package/lib/motion/mgstyles/upstream/demos/03-isometric/js/main.js +219 -0
- package/lib/motion/mgstyles/upstream/demos/03-isometric/js/night5.js +180 -0
- package/lib/motion/mgstyles/upstream/demos/03-isometric/js/render.js +202 -0
- package/lib/motion/mgstyles/upstream/demos/03-isometric/js/title.js +167 -0
- package/lib/motion/mgstyles/upstream/demos/03-isometric/js/title5.js +294 -0
- package/lib/motion/mgstyles/upstream/demos/03-isometric/js/ui.js +141 -0
- package/lib/motion/mgstyles/upstream/demos/03-isometric/js/world.js +1399 -0
- package/lib/motion/mgstyles/upstream/demos/05-cel-boil/cues.json +157 -0
- package/lib/motion/mgstyles/upstream/demos/05-cel-boil/index.html +34 -0
- package/lib/motion/mgstyles/upstream/demos/05-cel-boil/js/engine.js +326 -0
- package/lib/motion/mgstyles/upstream/demos/05-cel-boil/js/scene.js +2036 -0
- package/lib/motion/mgstyles/upstream/demos/06-collage/assets/gen/layout.json +157 -0
- package/lib/motion/mgstyles/upstream/demos/06-collage/cues.json +80 -0
- package/lib/motion/mgstyles/upstream/demos/06-collage/index.html +889 -0
- package/lib/motion/mgstyles/upstream/demos/07-liquid/cues.json +100 -0
- package/lib/motion/mgstyles/upstream/demos/07-liquid/index.html +108 -0
- package/lib/motion/mgstyles/upstream/demos/07-liquid/js/engine.js +153 -0
- package/lib/motion/mgstyles/upstream/demos/07-liquid/js/links.js +64 -0
- package/lib/motion/mgstyles/upstream/demos/07-liquid/js/lookdev.js +46 -0
- package/lib/motion/mgstyles/upstream/demos/07-liquid/js/scene.js +963 -0
- package/lib/motion/mgstyles/upstream/demos/07-liquid/js/shaders.js +647 -0
- package/lib/motion/mgstyles/upstream/demos/07-liquid/js/textsdf.js +114 -0
- package/lib/motion/mgstyles/upstream/demos/07-liquid/js/type.js +50 -0
- package/lib/motion/mgstyles/upstream/demos/08-morph/cues.json +131 -0
- package/lib/motion/mgstyles/upstream/demos/08-morph/glyphs.json +1 -0
- package/lib/motion/mgstyles/upstream/demos/08-morph/index.html +901 -0
- package/lib/motion/mgstyles/upstream/demos/09-bauhaus/cues.json +270 -0
- package/lib/motion/mgstyles/upstream/demos/09-bauhaus/film.js +697 -0
- package/lib/motion/mgstyles/upstream/demos/09-bauhaus/index.html +12 -0
- package/lib/motion/mgstyles/upstream/demos/10-synthwave/cues.json +44 -0
- package/lib/motion/mgstyles/upstream/demos/10-synthwave/index.html +1028 -0
- package/lib/motion/mgstyles/upstream/demos/12-aurora-glass/cues.json +67 -0
- package/lib/motion/mgstyles/upstream/demos/12-aurora-glass/index.html +16 -0
- package/lib/motion/mgstyles/upstream/demos/12-aurora-glass/main.js +967 -0
- package/lib/motion/mgstyles/upstream/demos/12-aurora-glass/shaders.js +468 -0
- package/lib/motion/mgstyles/upstream/demos/18-hanazi/cues.json +117 -0
- package/lib/motion/mgstyles/upstream/demos/18-hanazi/index.html +541 -0
- package/lib/motion/mgstyles/upstream/demos/19-paperclip/assets/countries-110m.json +1 -0
- package/lib/motion/mgstyles/upstream/demos/19-paperclip/assets/stickers/meta.json +34 -0
- package/lib/motion/mgstyles/upstream/demos/19-paperclip/cues.json +285 -0
- package/lib/motion/mgstyles/upstream/demos/19-paperclip/index.html +599 -0
- package/lib/motion/mgstyles/upstream/demos/20-pixel/cues.json +42 -0
- package/lib/motion/mgstyles/upstream/demos/20-pixel/film.js +914 -0
- package/lib/motion/mgstyles/upstream/demos/20-pixel/index.html +18 -0
- package/lib/motion/mgstyles/upstream/demos/22-hud/cues.json +108 -0
- package/lib/motion/mgstyles/upstream/demos/22-hud/index.html +125 -0
- package/lib/motion/mgstyles/upstream/demos/22-hud/js/holo.js +275 -0
- package/lib/motion/mgstyles/upstream/demos/22-hud/js/hud.js +855 -0
- package/lib/motion/mgstyles/upstream/demos/22-hud/js/post.js +150 -0
- package/lib/motion/mgstyles/upstream/demos/22-hud/js/util.js +65 -0
- package/lib/motion/mgstyles/upstream/demos/22-hud/js/v5.js +142 -0
- package/lib/motion/mgstyles/upstream/harness/render.mjs +319 -0
- package/lib/motion/mgstyles/upstream/manifest.json +269 -0
- package/lib/motion/mgstyles/upstream/prompts/01-flat-vector.md +98 -0
- package/lib/motion/mgstyles/upstream/prompts/02-line-art.md +95 -0
- package/lib/motion/mgstyles/upstream/prompts/03-isometric.md +97 -0
- package/lib/motion/mgstyles/upstream/prompts/05-cel-boil.md +95 -0
- package/lib/motion/mgstyles/upstream/prompts/06-collage.md +95 -0
- package/lib/motion/mgstyles/upstream/prompts/07-liquid.md +98 -0
- package/lib/motion/mgstyles/upstream/prompts/08-morph.md +95 -0
- package/lib/motion/mgstyles/upstream/prompts/09-bauhaus.md +96 -0
- package/lib/motion/mgstyles/upstream/prompts/10-synthwave.md +95 -0
- package/lib/motion/mgstyles/upstream/prompts/12-aurora-glass.md +96 -0
- package/lib/motion/mgstyles/upstream/prompts/18-hanazi.md +95 -0
- package/lib/motion/mgstyles/upstream/prompts/19-paperclip.md +96 -0
- package/lib/motion/mgstyles/upstream/prompts/20-pixel.md +96 -0
- package/lib/motion/mgstyles/upstream/prompts/22-hud.md +95 -0
- package/lib/motion/mgstyles/upstream/rubric.md +51 -0
- package/lib/motion/mgstyles/upstream/template.md +36 -0
- package/lib/motion/pixelqa.js +198 -0
- package/lib/motion/preview.js +95 -0
- package/lib/motion/project.js +395 -0
- package/lib/motion/qa.js +290 -0
- package/lib/motion/reference-media.jsx +10 -0
- package/lib/motion/render-settings.js +112 -0
- package/lib/motion/render.js +159 -0
- package/lib/motion/scene-kit.js +183 -0
- package/lib/motion/sfx.js +232 -0
- package/lib/motion/shotcraft/ClipCard.tsx +204 -0
- package/lib/motion/shotcraft/catalog.json +2461 -0
- package/lib/motion/shotcraft/upstream/SKILL.md +286 -0
- package/lib/motion/shotcraft/upstream/assets/lib/Caption.tsx +54 -0
- package/lib/motion/shotcraft/upstream/assets/lib/ClipCard.tsx +200 -0
- package/lib/motion/shotcraft/upstream/assets/lib/DigitRoll.tsx +45 -0
- package/lib/motion/shotcraft/upstream/assets/lib/FlashCut.tsx +20 -0
- package/lib/motion/shotcraft/upstream/assets/lib/FlatPanel.tsx +65 -0
- package/lib/motion/shotcraft/upstream/assets/lib/PageCam.tsx +151 -0
- package/lib/motion/shotcraft/upstream/assets/lib/VerticalTicker.tsx +129 -0
- package/lib/motion/shotcraft/upstream/assets/lib/helpers/camera.tsx +75 -0
- package/lib/motion/shotcraft/upstream/assets/lib/helpers/motion.ts +40 -0
- package/lib/motion/shotcraft/upstream/assets/lib/helpers/rand.ts +11 -0
- package/lib/motion/shotcraft/upstream/assets/lib/helpers/shake.ts +10 -0
- package/lib/motion/shotcraft/upstream/assets/scripts/__pycache__/sfx-source.cpython-313.pyc +0 -0
- package/lib/motion/shotcraft/upstream/assets/scripts/build-readme-cover.mjs +49 -0
- package/lib/motion/shotcraft/upstream/assets/scripts/capture-template.mjs +210 -0
- package/lib/motion/shotcraft/upstream/assets/scripts/sfx-source.py +423 -0
- package/lib/motion/shotcraft/upstream/assets/scripts/smoke-render-demos.py +170 -0
- package/lib/motion/shotcraft/upstream/assets/scripts/test_sfx_source.py +157 -0
- package/lib/motion/shotcraft/upstream/demos/README.md +120 -0
- package/lib/motion/shotcraft/upstream/demos/_fixtures/Brand.tsx +112 -0
- package/lib/motion/shotcraft/upstream/demos/_fixtures/Fixtures.tsx +647 -0
- package/lib/motion/shotcraft/upstream/demos/_fixtures/Look.tsx +316 -0
- package/lib/motion/shotcraft/upstream/demos/_fixtures/Motion.tsx +82 -0
- package/lib/motion/shotcraft/upstream/demos/_fixtures/PageCam2D.tsx +115 -0
- package/lib/motion/shotcraft/upstream/demos/_fixtures/Polish.tsx +243 -0
- package/lib/motion/shotcraft/upstream/demos/_textures/live-layout.json +389 -0
- package/lib/motion/shotcraft/upstream/demos/camera/basic-3d-scene/Basic3DScene.tsx +250 -0
- package/lib/motion/shotcraft/upstream/demos/camera/crash-zoom-punch/CrashImpactReal.tsx +305 -0
- package/lib/motion/shotcraft/upstream/demos/camera/crash-zoom-punch/CrashZoomReal.tsx +246 -0
- package/lib/motion/shotcraft/upstream/demos/camera/cursor-flyover/CursorFlyover.tsx +390 -0
- package/lib/motion/shotcraft/upstream/demos/camera/depth-layer-moves/MultiplaneReal.tsx +138 -0
- package/lib/motion/shotcraft/upstream/demos/camera/graze-face-tour/GrazeFaceTour.tsx +542 -0
- package/lib/motion/shotcraft/upstream/demos/camera/overhead-camera-moves/OverheadTabletopDrop.tsx +315 -0
- package/lib/motion/shotcraft/upstream/demos/camera/overhead-camera-moves/TiltReveal.tsx +299 -0
- package/lib/motion/shotcraft/upstream/demos/camera/space-camera-moves/DroneDiveLanding.tsx +173 -0
- package/lib/motion/shotcraft/upstream/demos/camera/space-camera-moves/ExplodedView.tsx +348 -0
- package/lib/motion/shotcraft/upstream/demos/camera/steep-tilt-glide/SteepTiltGlide.tsx +366 -0
- package/lib/motion/shotcraft/upstream/demos/camera/tension-camera-moves/BulletTimeFreezeOrbit.tsx +279 -0
- package/lib/motion/shotcraft/upstream/demos/camera/tension-camera-moves/PullBackIsolation.tsx +184 -0
- package/lib/motion/shotcraft/upstream/demos/camera/terminal-3d/Terminal3D.tsx +390 -0
- package/lib/motion/shotcraft/upstream/demos/data/before-after-slider-scrub/BeforeAfterSliderScrub.tsx +155 -0
- package/lib/motion/shotcraft/upstream/demos/data/chart-live-moves/AxisRescaleShockV2.tsx +303 -0
- package/lib/motion/shotcraft/upstream/demos/data/chart-live-moves/OscilloscopeStreamV2.tsx +290 -0
- package/lib/motion/shotcraft/upstream/demos/data/chart-live-moves/UnitDotSwarmRegroupV2.tsx +278 -0
- package/lib/motion/shotcraft/upstream/demos/data/gauge-readout-moves/NeedleSweepSelftest.tsx +307 -0
- package/lib/motion/shotcraft/upstream/demos/data/gauge-readout-moves/TapeScrollFixedPointer.tsx +215 -0
- package/lib/motion/shotcraft/upstream/demos/data/scroll-brake-moves/BrakeReticleLock.tsx +299 -0
- package/lib/motion/shotcraft/upstream/demos/data/scroll-brake-moves/ChangelogScrollBrake.tsx +232 -0
- package/lib/motion/shotcraft/upstream/demos/data/timeline-travel/TimelineTravel.tsx +290 -0
- package/lib/motion/shotcraft/upstream/demos/effects/assemble-then-type-flyin/AssembleThenTypeFlyin.tsx +273 -0
- package/lib/motion/shotcraft/upstream/demos/effects/aurora-bloom-bg-flip/AuroraBloomBgFlip.tsx +167 -0
- package/lib/motion/shotcraft/upstream/demos/effects/brand-frame-snap/BrandFrameSnap.tsx +536 -0
- package/lib/motion/shotcraft/upstream/demos/effects/fui-hud-moves/ReticleLockOn.tsx +164 -0
- package/lib/motion/shotcraft/upstream/demos/effects/glow-flyline-moves/FlylineArc.tsx +175 -0
- package/lib/motion/shotcraft/upstream/demos/effects/glow-flyline-moves/GlowOrbAmbient.tsx +197 -0
- package/lib/motion/shotcraft/upstream/demos/effects/glow-flyline-moves/OrbFlylineRelay.tsx +241 -0
- package/lib/motion/shotcraft/upstream/demos/effects/icon-performance-moves/AttentionBounce.tsx +279 -0
- package/lib/motion/shotcraft/upstream/demos/effects/impact-feedback/AnimeImpact.tsx +230 -0
- package/lib/motion/shotcraft/upstream/demos/effects/impact-feedback/HitCounter.tsx +345 -0
- package/lib/motion/shotcraft/upstream/demos/effects/light-play-moves/SheenSweepRetry.tsx +275 -0
- package/lib/motion/shotcraft/upstream/demos/effects/light-play-moves/SpotlightSweepReveal.tsx +229 -0
- package/lib/motion/shotcraft/upstream/demos/effects/radial-ripple-phone-chips/RadialRipplePhoneChips.tsx +312 -0
- package/lib/motion/shotcraft/upstream/demos/effects/riso-print-hits/RisoMisregistrationHit.tsx +222 -0
- package/lib/motion/shotcraft/upstream/demos/effects/scan-bracket-sweep/ScanBracketSweep.tsx +295 -0
- package/lib/motion/shotcraft/upstream/demos/effects/scanline-annotate-focus/ScanlineAnnotateFocus.tsx +329 -0
- package/lib/motion/shotcraft/upstream/demos/effects/scanline-assemble-flyin/ScanlineAssembleFlyin.tsx +315 -0
- package/lib/motion/shotcraft/upstream/demos/effects/slam-entrance-moves/ImpactBurstKit.tsx +264 -0
- package/lib/motion/shotcraft/upstream/demos/effects/slam-entrance-moves/KanadaPerspectiveSnap.tsx +260 -0
- package/lib/motion/shotcraft/upstream/demos/effects/slam-entrance-moves/ScoreSlam.tsx +263 -0
- package/lib/motion/shotcraft/upstream/demos/effects/spotlight-sweep-moves/CornerSpotlightReveal.tsx +123 -0
- package/lib/motion/shotcraft/upstream/demos/effects/spotlight-sweep-moves/GlowWakeSleepPanel.tsx +187 -0
- package/lib/motion/shotcraft/upstream/demos/effects/spotlight-sweep-moves/SlideSpotlightPan.tsx +175 -0
- package/lib/motion/shotcraft/upstream/demos/interaction/autolayout-gap-dial/AutolayoutGapDial.tsx +305 -0
- package/lib/motion/shotcraft/upstream/demos/interaction/canvas-materialize-moves/DiagramCascadeBuild.tsx +350 -0
- package/lib/motion/shotcraft/upstream/demos/interaction/canvas-materialize-moves/PanelToCanvasMaterialize.tsx +352 -0
- package/lib/motion/shotcraft/upstream/demos/interaction/chip-grid-single-select-blackout/ChipGridSingleSelectBlackout.tsx +218 -0
- package/lib/motion/shotcraft/upstream/demos/interaction/collab-cursor-moves/CursorCastEnsemble.tsx +297 -0
- package/lib/motion/shotcraft/upstream/demos/interaction/glass-pill-dictation-typing/GlassPillDictationTyping.tsx +157 -0
- package/lib/motion/shotcraft/upstream/demos/interaction/hashtag-to-pill-materialize/HashtagToPillMaterialize.tsx +218 -0
- package/lib/motion/shotcraft/upstream/demos/interaction/picker-carousel-feature-cycle/PickerCarouselFeatureCycle.tsx +202 -0
- package/lib/motion/shotcraft/upstream/demos/interaction/segmented-thumb-hero/SegmentedThumbHero.tsx +265 -0
- package/lib/motion/shotcraft/upstream/demos/interaction/theme-switch-moves/PaletteThemeRipple.tsx +457 -0
- package/lib/motion/shotcraft/upstream/demos/interaction/theme-switch-moves/ThemeSweepToggle.tsx +446 -0
- package/lib/motion/shotcraft/upstream/demos/interaction/type-and-filter/TypeAndFilter.tsx +249 -0
- package/lib/motion/shotcraft/upstream/demos/interaction/voice-waveform-live/VoiceWaveformLive.tsx +278 -0
- package/lib/motion/shotcraft/upstream/demos/opening/crane-rise-reveal/CraneRiseReveal.tsx +332 -0
- package/lib/motion/shotcraft/upstream/demos/opening/dataviz-landscape-open/DatavizLandscapeOpen.tsx +358 -0
- package/lib/motion/shotcraft/upstream/demos/opening/fracture/Fracture.tsx +201 -0
- package/lib/motion/shotcraft/upstream/demos/opening/icon-field-colorize/IconFieldColorize.tsx +200 -0
- package/lib/motion/shotcraft/upstream/demos/opening/letterspace-materialize/LetterspaceMaterialize.tsx +208 -0
- package/lib/motion/shotcraft/upstream/demos/opening/magician-card-flourish/MagicianCardFlourish.tsx +341 -0
- package/lib/motion/shotcraft/upstream/demos/opening/orbit-ring-title-open/OrbitRingTitleOpen.tsx +389 -0
- package/lib/motion/shotcraft/upstream/demos/opening/spotlight-hero-card/SpotlightHeroCard.tsx +293 -0
- package/lib/motion/shotcraft/upstream/demos/opening/stroke-segment-build/StrokeSegmentBuild.tsx +183 -0
- package/lib/motion/shotcraft/upstream/demos/opening/text-as-mask/TextAsMask.tsx +245 -0
- package/lib/motion/shotcraft/upstream/demos/outro/grain-dissolve/GrainDissolve.tsx +222 -0
- package/lib/motion/shotcraft/upstream/demos/outro/logo-shrink-wordmark-lockup/LogoShrinkWordmarkLockup.tsx +223 -0
- package/lib/motion/shotcraft/upstream/demos/outro/neon-triple-marquee/NeonTripleMarquee.tsx +217 -0
- package/lib/motion/shotcraft/upstream/demos/outro/outro-group-photo-launch/OutroGroupPhotoLaunch.tsx +343 -0
- package/lib/motion/shotcraft/upstream/demos/outro/ui-strip-away-outro/UiStripAwayOutro.tsx +357 -0
- package/lib/motion/shotcraft/upstream/demos/outro/ui-to-brand-morph/IconFlipBloomLogo.tsx +187 -0
- package/lib/motion/shotcraft/upstream/demos/outro/ui-to-brand-morph/InputMorphsIntoLogo.tsx +333 -0
- package/lib/motion/shotcraft/upstream/demos/rhythm/beat-cut-moves/PaparazziFlash.tsx +274 -0
- package/lib/motion/shotcraft/upstream/demos/rhythm/beat-step-list-theme-cycle/BeatStepListThemeCycle.tsx +270 -0
- package/lib/motion/shotcraft/upstream/demos/rhythm/panel-grid-moves/ComicPanelSplit.tsx +430 -0
- package/lib/motion/shotcraft/upstream/demos/rhythm/quad-split-parallel-scenes/QuadSplitParallelScenes.tsx +252 -0
- package/lib/motion/shotcraft/upstream/demos/rhythm/rhythm-interrupt-moves/JumpCutPunchIn.tsx +106 -0
- package/lib/motion/shotcraft/upstream/demos/rhythm/sakuga-timing-shift/SakugaTimingShift.tsx +288 -0
- package/lib/motion/shotcraft/upstream/demos/rhythm/speed-ramp-freeze/FreezeAnnotateReal.tsx +362 -0
- package/lib/motion/shotcraft/upstream/demos/rhythm/trailer-grammar-moves/TrailerBumper.tsx +168 -0
- package/lib/motion/shotcraft/upstream/demos/transition/bottom-push-stack-wipe/BottomPushStackWipe.tsx +310 -0
- package/lib/motion/shotcraft/upstream/demos/transition/card-flip-reveal/CardFlipReveal.tsx +261 -0
- package/lib/motion/shotcraft/upstream/demos/transition/card-flock-tumble/CardFlockTumble.tsx +459 -0
- package/lib/motion/shotcraft/upstream/demos/transition/circle-match-iris/CircleMatchIris.tsx +294 -0
- package/lib/motion/shotcraft/upstream/demos/transition/gradient-transition/GradientTransition.tsx +161 -0
- package/lib/motion/shotcraft/upstream/demos/transition/line-carry-transition/LineCarryTransition.tsx +248 -0
- package/lib/motion/shotcraft/upstream/demos/transition/mosaic-reframe/MosaicReframe.tsx +359 -0
- package/lib/motion/shotcraft/upstream/demos/transition/page-turn-transitions/BarnDoorSplit.tsx +227 -0
- package/lib/motion/shotcraft/upstream/demos/transition/print-texture-transitions/InkBleedReveal.tsx +162 -0
- package/lib/motion/shotcraft/upstream/demos/transition/shot-transitions/PortalWipeV2.tsx +248 -0
- package/lib/motion/shotcraft/upstream/demos/transition/shot-transitions/WhipBrakeReal.tsx +186 -0
- package/lib/motion/shotcraft/upstream/demos/transition/tear-streak-transitions/GlitchDisplace.tsx +355 -0
- package/lib/motion/shotcraft/upstream/demos/transition/transition-hidden-cut/InvisibleCut.tsx +272 -0
- package/lib/motion/shotcraft/upstream/demos/transition/transition-hidden-cut/LightLeakBurn.tsx +262 -0
- package/lib/motion/shotcraft/upstream/demos/transition/transition-hidden-cut/VersusSlam.tsx +217 -0
- package/lib/motion/shotcraft/upstream/demos/transition/transition-travel/LetterformZoom.tsx +285 -0
- package/lib/motion/shotcraft/upstream/demos/transition/white-flash-logo-simplify-cut/WhiteFlashLogoSimplifyCut.tsx +205 -0
- package/lib/motion/shotcraft/upstream/demos/transition/wipe-transitions/BlindsSlice.tsx +178 -0
- package/lib/motion/shotcraft/upstream/demos/typography/blur-slide/BlurSlide.tsx +147 -0
- package/lib/motion/shotcraft/upstream/demos/typography/brace-expand/BraceExpand.tsx +143 -0
- package/lib/motion/shotcraft/upstream/demos/typography/brand-ink-open/BrandInkOpen.tsx +134 -0
- package/lib/motion/shotcraft/upstream/demos/typography/countdown-arc-scatter/CountdownArcScatter.tsx +217 -0
- package/lib/motion/shotcraft/upstream/demos/typography/flying-words/FlyingWords.tsx +198 -0
- package/lib/motion/shotcraft/upstream/demos/typography/glitch-cycle/GlitchCycle.tsx +221 -0
- package/lib/motion/shotcraft/upstream/demos/typography/gradient-word-sweep/GradientWordSweep.tsx +312 -0
- package/lib/motion/shotcraft/upstream/demos/typography/lead-word-zoom-assemble/LeadWordZoomAssemble.tsx +261 -0
- package/lib/motion/shotcraft/upstream/demos/typography/marker-underline-title/MarkerUnderlineTitle.tsx +121 -0
- package/lib/motion/shotcraft/upstream/demos/typography/outline-word-fill/OutlineWordFill.tsx +153 -0
- package/lib/motion/shotcraft/upstream/demos/typography/paper-title-card/PaperTitleCard.tsx +179 -0
- package/lib/motion/shotcraft/upstream/demos/typography/pill-chip-slot-cycle-handled/PillChipSlotCycleHandled.tsx +140 -0
- package/lib/motion/shotcraft/upstream/demos/typography/pill-slot-cycle/PillSlotCycle.tsx +309 -0
- package/lib/motion/shotcraft/upstream/demos/typography/text-column-converge/TextColumnConverge.tsx +191 -0
- package/lib/motion/shotcraft/upstream/demos/typography/title-demote-to-label/TitleDemoteToLabel.tsx +292 -0
- package/lib/motion/shotcraft/upstream/demos/typography/type-assembly-moves/LetterformDriftAssembly.tsx +135 -0
- package/lib/motion/shotcraft/upstream/demos/typography/type-assembly-moves/SplitTextStagger.tsx +124 -0
- package/lib/motion/shotcraft/upstream/demos/typography/type-assembly-moves/TextOnPath.tsx +255 -0
- package/lib/motion/shotcraft/upstream/demos/typography/type-entrance-moves/LetterDropPhysics.tsx +173 -0
- package/lib/motion/shotcraft/upstream/demos/typography/type-rhythm-sync/FontWeightPump.tsx +225 -0
- package/lib/motion/shotcraft/upstream/demos/typography/type-rhythm-sync/KaraokeFillSync.tsx +141 -0
- package/lib/motion/shotcraft/upstream/demos/typography/typewriter-moves/TypewriterErrorRetype.tsx +137 -0
- package/lib/motion/shotcraft/upstream/demos/typography/vertical-word-roll-blur-cycle/VerticalWordRollBlurCycle.tsx +147 -0
- package/lib/motion/shotcraft/upstream/demos/typography/word-relay-filmstrip/WordRelayFilmstrip.tsx +409 -0
- package/lib/motion/shotcraft/upstream/demos/typography/word-relay-geometry/WordRelayGeometry.tsx +280 -0
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/avatar-bracket-carousel/AvatarBracketCarousel.tsx +271 -0
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/bezier-source-converge-merge/BezierSourceConvergeMerge.tsx +385 -0
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/card-stack/CardStack.tsx +312 -0
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/carousel-3d/Carousel3D.tsx +303 -0
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/clipcard-looping/ClipCardLooping.tsx +77 -0
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/cloner-depth-echo/ClonerDepthEcho.tsx +311 -0
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/deck-deal-flyin/DeckDealFlyin.tsx +307 -0
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/doc-park-left-pill-deal/DocParkLeftPillDeal.tsx +349 -0
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/document-typewriter-reveal/DocumentTypewriterReveal.tsx +255 -0
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/draw-svg-trace/DrawSvgTrace.tsx +244 -0
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/element-body-moves/ContactShadowLift.tsx +217 -0
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/integration-hub-map/IntegrationHubMap.tsx +652 -0
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/list-reveal/ListReveal.tsx +153 -0
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/list-stack-press/ListStackPress.tsx +238 -0
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/neon-frame-forerun/NeonFrameForerun.tsx +324 -0
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/neon-frame-orbit-drop/NeonFrameForerunOrbit.tsx +354 -0
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/page-waterfall-wall/PageWaterfallWall.tsx +101 -0
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/page-waterfall-wall/VerticalTicker.tsx +129 -0
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/product-card-progressive-assemble/ProductCardProgressiveAssemble.tsx +297 -0
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/radial-wave/RadialWave.tsx +57 -0
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/research-card-stack-scroll/ResearchCardStackScroll.tsx +155 -0
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/row-embed/RowEmbed.tsx +175 -0
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/value-stagger-gradient/ValueStaggerGradient.tsx +153 -0
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/wall-reveal-moves/BentoLightUp.tsx +146 -0
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/wall-reveal-moves/WireframeDrawOn.tsx +226 -0
- package/lib/motion/shotcraft/upstream/gallery/api/library.json +1 -0
- package/lib/motion/shotcraft/upstream/manifest.json +2715 -0
- package/lib/motion/shotcraft/upstream/references/aesthetic-rules.md +193 -0
- package/lib/motion/shotcraft/upstream/references/final-review.md +121 -0
- package/lib/motion/shotcraft/upstream/references/guided-free-creation.md +199 -0
- package/lib/motion/shotcraft/upstream/references/jianying-export.md +183 -0
- package/lib/motion/shotcraft/upstream/references/music-beat-sync.md +257 -0
- package/lib/motion/shotcraft/upstream/references/pipeline.md +382 -0
- package/lib/motion/shotcraft/upstream/references/publish-upload-post.md +111 -0
- package/lib/motion/shotcraft/upstream/references/sequences/promo-energy-arc.md +50 -0
- package/lib/motion/shotcraft/upstream/references/sfx-sourcing.md +95 -0
- package/lib/motion/shotcraft/upstream/references/shots/camera/basic-3d-scene.md +41 -0
- package/lib/motion/shotcraft/upstream/references/shots/camera/crash-zoom-punch.md +52 -0
- package/lib/motion/shotcraft/upstream/references/shots/camera/cursor-flyover.md +39 -0
- package/lib/motion/shotcraft/upstream/references/shots/camera/depth-layer-moves.md +39 -0
- package/lib/motion/shotcraft/upstream/references/shots/camera/graze-face-tour.md +57 -0
- package/lib/motion/shotcraft/upstream/references/shots/camera/overhead-camera-moves.md +51 -0
- package/lib/motion/shotcraft/upstream/references/shots/camera/space-camera-moves.md +46 -0
- package/lib/motion/shotcraft/upstream/references/shots/camera/steep-tilt-glide.md +52 -0
- package/lib/motion/shotcraft/upstream/references/shots/camera/tension-camera-moves.md +43 -0
- package/lib/motion/shotcraft/upstream/references/shots/camera/terminal-3d.md +42 -0
- package/lib/motion/shotcraft/upstream/references/shots/data/before-after-slider-scrub.md +48 -0
- package/lib/motion/shotcraft/upstream/references/shots/data/chart-live-moves.md +52 -0
- package/lib/motion/shotcraft/upstream/references/shots/data/gauge-readout-moves.md +45 -0
- package/lib/motion/shotcraft/upstream/references/shots/data/scroll-brake-moves.md +46 -0
- package/lib/motion/shotcraft/upstream/references/shots/data/timeline-travel.md +44 -0
- package/lib/motion/shotcraft/upstream/references/shots/effects/assemble-then-type-flyin.md +63 -0
- package/lib/motion/shotcraft/upstream/references/shots/effects/aurora-bloom-bg-flip.md +54 -0
- package/lib/motion/shotcraft/upstream/references/shots/effects/brand-frame-snap.md +58 -0
- package/lib/motion/shotcraft/upstream/references/shots/effects/fui-hud-moves.md +35 -0
- package/lib/motion/shotcraft/upstream/references/shots/effects/glow-flyline-moves.md +53 -0
- package/lib/motion/shotcraft/upstream/references/shots/effects/icon-performance-moves.md +42 -0
- package/lib/motion/shotcraft/upstream/references/shots/effects/impact-feedback.md +50 -0
- package/lib/motion/shotcraft/upstream/references/shots/effects/light-play-moves.md +46 -0
- package/lib/motion/shotcraft/upstream/references/shots/effects/radial-ripple-phone-chips.md +56 -0
- package/lib/motion/shotcraft/upstream/references/shots/effects/riso-print-hits.md +49 -0
- package/lib/motion/shotcraft/upstream/references/shots/effects/scan-bracket-sweep.md +50 -0
- package/lib/motion/shotcraft/upstream/references/shots/effects/scanline-annotate-focus.md +53 -0
- package/lib/motion/shotcraft/upstream/references/shots/effects/scanline-assemble-flyin.md +53 -0
- package/lib/motion/shotcraft/upstream/references/shots/effects/slam-entrance-moves.md +55 -0
- package/lib/motion/shotcraft/upstream/references/shots/effects/spotlight-sweep-moves.md +52 -0
- package/lib/motion/shotcraft/upstream/references/shots/interaction/autolayout-gap-dial.md +60 -0
- package/lib/motion/shotcraft/upstream/references/shots/interaction/canvas-materialize-moves.md +57 -0
- package/lib/motion/shotcraft/upstream/references/shots/interaction/chip-grid-single-select-blackout.md +51 -0
- package/lib/motion/shotcraft/upstream/references/shots/interaction/collab-cursor-moves.md +41 -0
- package/lib/motion/shotcraft/upstream/references/shots/interaction/glass-pill-dictation-typing.md +53 -0
- package/lib/motion/shotcraft/upstream/references/shots/interaction/hashtag-to-pill-materialize.md +60 -0
- package/lib/motion/shotcraft/upstream/references/shots/interaction/picker-carousel-feature-cycle.md +55 -0
- package/lib/motion/shotcraft/upstream/references/shots/interaction/segmented-thumb-hero.md +57 -0
- package/lib/motion/shotcraft/upstream/references/shots/interaction/theme-switch-moves.md +47 -0
- package/lib/motion/shotcraft/upstream/references/shots/interaction/type-and-filter.md +37 -0
- package/lib/motion/shotcraft/upstream/references/shots/interaction/voice-waveform-live.md +54 -0
- package/lib/motion/shotcraft/upstream/references/shots/opening/brand-ink-open.md +36 -0
- package/lib/motion/shotcraft/upstream/references/shots/opening/crane-rise-reveal.md +56 -0
- package/lib/motion/shotcraft/upstream/references/shots/opening/dataviz-landscape-open.md +56 -0
- package/lib/motion/shotcraft/upstream/references/shots/opening/fracture.md +40 -0
- package/lib/motion/shotcraft/upstream/references/shots/opening/icon-field-colorize.md +60 -0
- package/lib/motion/shotcraft/upstream/references/shots/opening/letterspace-materialize.md +57 -0
- package/lib/motion/shotcraft/upstream/references/shots/opening/magician-card-flourish.md +73 -0
- package/lib/motion/shotcraft/upstream/references/shots/opening/orbit-ring-title-open.md +87 -0
- package/lib/motion/shotcraft/upstream/references/shots/opening/spotlight-hero-card.md +55 -0
- package/lib/motion/shotcraft/upstream/references/shots/opening/stroke-segment-build.md +50 -0
- package/lib/motion/shotcraft/upstream/references/shots/opening/text-as-mask.md +59 -0
- package/lib/motion/shotcraft/upstream/references/shots/outro/grain-dissolve.md +38 -0
- package/lib/motion/shotcraft/upstream/references/shots/outro/logo-shrink-wordmark-lockup.md +38 -0
- package/lib/motion/shotcraft/upstream/references/shots/outro/neon-triple-marquee.md +59 -0
- package/lib/motion/shotcraft/upstream/references/shots/outro/outro-group-photo-launch.md +39 -0
- package/lib/motion/shotcraft/upstream/references/shots/outro/ui-strip-away-outro.md +61 -0
- package/lib/motion/shotcraft/upstream/references/shots/outro/ui-to-brand-morph.md +52 -0
- package/lib/motion/shotcraft/upstream/references/shots/rhythm/beat-cut-moves.md +50 -0
- package/lib/motion/shotcraft/upstream/references/shots/rhythm/beat-step-list-theme-cycle.md +66 -0
- package/lib/motion/shotcraft/upstream/references/shots/rhythm/panel-grid-moves.md +43 -0
- package/lib/motion/shotcraft/upstream/references/shots/rhythm/quad-split-parallel-scenes.md +38 -0
- package/lib/motion/shotcraft/upstream/references/shots/rhythm/rhythm-interrupt-moves.md +38 -0
- package/lib/motion/shotcraft/upstream/references/shots/rhythm/sakuga-timing-shift.md +56 -0
- package/lib/motion/shotcraft/upstream/references/shots/rhythm/speed-ramp-freeze.md +44 -0
- package/lib/motion/shotcraft/upstream/references/shots/rhythm/trailer-grammar-moves.md +38 -0
- package/lib/motion/shotcraft/upstream/references/shots/transition/bottom-push-stack-wipe.md +63 -0
- package/lib/motion/shotcraft/upstream/references/shots/transition/card-flip-reveal.md +57 -0
- package/lib/motion/shotcraft/upstream/references/shots/transition/card-flock-tumble.md +63 -0
- package/lib/motion/shotcraft/upstream/references/shots/transition/circle-match-iris.md +58 -0
- package/lib/motion/shotcraft/upstream/references/shots/transition/gradient-transition.md +45 -0
- package/lib/motion/shotcraft/upstream/references/shots/transition/line-carry-transition.md +55 -0
- package/lib/motion/shotcraft/upstream/references/shots/transition/mosaic-reframe.md +49 -0
- package/lib/motion/shotcraft/upstream/references/shots/transition/page-turn-transitions.md +42 -0
- package/lib/motion/shotcraft/upstream/references/shots/transition/print-texture-transitions.md +42 -0
- package/lib/motion/shotcraft/upstream/references/shots/transition/shot-transitions.md +45 -0
- package/lib/motion/shotcraft/upstream/references/shots/transition/tear-streak-transitions.md +48 -0
- package/lib/motion/shotcraft/upstream/references/shots/transition/transition-hidden-cut.md +55 -0
- package/lib/motion/shotcraft/upstream/references/shots/transition/transition-travel.md +43 -0
- package/lib/motion/shotcraft/upstream/references/shots/transition/white-flash-logo-simplify-cut.md +41 -0
- package/lib/motion/shotcraft/upstream/references/shots/transition/wipe-transitions.md +42 -0
- package/lib/motion/shotcraft/upstream/references/shots/typography/blur-slide.md +59 -0
- package/lib/motion/shotcraft/upstream/references/shots/typography/brace-expand.md +55 -0
- package/lib/motion/shotcraft/upstream/references/shots/typography/countdown-arc-scatter.md +65 -0
- package/lib/motion/shotcraft/upstream/references/shots/typography/document-typewriter-reveal.md +42 -0
- package/lib/motion/shotcraft/upstream/references/shots/typography/flying-words.md +64 -0
- package/lib/motion/shotcraft/upstream/references/shots/typography/glitch-cycle.md +64 -0
- package/lib/motion/shotcraft/upstream/references/shots/typography/gradient-word-sweep.md +54 -0
- package/lib/motion/shotcraft/upstream/references/shots/typography/lead-word-zoom-assemble.md +91 -0
- package/lib/motion/shotcraft/upstream/references/shots/typography/marker-underline-title.md +51 -0
- package/lib/motion/shotcraft/upstream/references/shots/typography/outline-word-fill.md +66 -0
- package/lib/motion/shotcraft/upstream/references/shots/typography/paper-title-card.md +38 -0
- package/lib/motion/shotcraft/upstream/references/shots/typography/pill-chip-slot-cycle-handled.md +66 -0
- package/lib/motion/shotcraft/upstream/references/shots/typography/pill-slot-cycle.md +58 -0
- package/lib/motion/shotcraft/upstream/references/shots/typography/text-column-converge.md +61 -0
- package/lib/motion/shotcraft/upstream/references/shots/typography/title-demote-to-label.md +64 -0
- package/lib/motion/shotcraft/upstream/references/shots/typography/type-assembly-moves.md +45 -0
- package/lib/motion/shotcraft/upstream/references/shots/typography/type-entrance-moves.md +41 -0
- package/lib/motion/shotcraft/upstream/references/shots/typography/type-rhythm-sync.md +51 -0
- package/lib/motion/shotcraft/upstream/references/shots/typography/typewriter-moves.md +42 -0
- package/lib/motion/shotcraft/upstream/references/shots/typography/vertical-word-roll-blur-cycle.md +63 -0
- package/lib/motion/shotcraft/upstream/references/shots/typography/word-relay-filmstrip.md +57 -0
- package/lib/motion/shotcraft/upstream/references/shots/typography/word-relay-geometry.md +75 -0
- package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/avatar-bracket-carousel.md +59 -0
- package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/bezier-source-converge-merge.md +67 -0
- package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/card-stack.md +60 -0
- package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/carousel-3d.md +66 -0
- package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/cloner-depth-echo.md +40 -0
- package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/deck-deal-flyin.md +45 -0
- package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/doc-park-left-pill-deal.md +62 -0
- package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/draw-svg-trace.md +55 -0
- package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/element-body-moves.md +37 -0
- package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/integration-hub-map.md +55 -0
- package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/list-reveal.md +50 -0
- package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/list-stack-press.md +43 -0
- package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/neon-frame-forerun.md +55 -0
- package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/neon-frame-orbit-drop.md +51 -0
- package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/page-waterfall-wall.md +51 -0
- package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/product-card-progressive-assemble.md +56 -0
- package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/radial-wave.md +49 -0
- package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/research-card-stack-scroll.md +57 -0
- package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/row-embed.md +36 -0
- package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/value-stagger-gradient.md +64 -0
- package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/wall-reveal-moves.md +47 -0
- package/lib/motion/shotcraft/upstream/references/sound-design.md +170 -0
- package/lib/motion/shotcraft/upstream/references/workbench.md +157 -0
- package/lib/motion/shotcraft/upstream/template/TEMPLATE.md +103 -0
- package/lib/motion/shotcraft/upstream/template/THEMES.md +44 -0
- package/lib/motion/shotcraft/upstream/template/package.json +20 -0
- package/lib/motion/shotcraft/upstream/template/remotion.config.ts +6 -0
- package/lib/motion/shotcraft/upstream/template/src/Root.tsx +17 -0
- package/lib/motion/shotcraft/upstream/template/src/aifl/Caption.tsx +65 -0
- package/lib/motion/shotcraft/upstream/template/src/aifl/DigitRoll.tsx +48 -0
- package/lib/motion/shotcraft/upstream/template/src/aifl/FlashCut.tsx +24 -0
- package/lib/motion/shotcraft/upstream/template/src/aifl/Main.tsx +192 -0
- package/lib/motion/shotcraft/upstream/template/src/aifl/PaperTitleCard.tsx +111 -0
- package/lib/motion/shotcraft/upstream/template/src/aifl/live/PageCam.tsx +150 -0
- package/lib/motion/shotcraft/upstream/template/src/aifl/live/SceneDetail.tsx +158 -0
- package/lib/motion/shotcraft/upstream/template/src/aifl/live/SceneFlyIn.tsx +468 -0
- package/lib/motion/shotcraft/upstream/template/src/aifl/live/SceneOpen.tsx +552 -0
- package/lib/motion/shotcraft/upstream/template/src/aifl/live/SceneOutroLive.tsx +411 -0
- package/lib/motion/shotcraft/upstream/template/src/aifl/live/ScenePapers.tsx +200 -0
- package/lib/motion/shotcraft/upstream/template/src/aifl/live/SceneWbr.tsx +324 -0
- package/lib/motion/shotcraft/upstream/template/src/aifl/live-layout.json +389 -0
- package/lib/motion/shotcraft/upstream/template/src/index.ts +4 -0
- package/lib/motion/shotcraft/upstream/template/src/themes/ThemedFilm.tsx +7 -0
- package/lib/motion/shotcraft/upstream/template/src/themes/manifest.tsx +68 -0
- package/lib/motion/shotcraft/upstream/template/src/themes/palette-assets.json +1 -0
- package/lib/motion/shotcraft/upstream/template/src/themes/palette-assets.ts +24 -0
- package/lib/motion/shotcraft/upstream/template/src/themes/palettes.json +74 -0
- package/lib/motion/shotcraft/upstream/template/src/themes/visual-theme.tsx +91 -0
- package/lib/motion/shotcraft/upstream/template/src/workbench.ts +213 -0
- package/lib/motion/shotcraft/upstream/template/tsconfig.json +15 -0
- package/lib/motion/spec.js +113 -0
- package/lib/motion/style-engine.js +153 -0
- package/lib/motion/style-frames.js +268 -0
- package/lib/motion/sync.js +214 -0
- package/lib/motion/theme.js +121 -0
- package/lib/motion/voice.js +417 -0
- package/lib/motion-agent-files.js +82 -0
- package/lib/motion-agent-protocol.js +66 -0
- package/lib/motion-agent-tools.js +196 -0
- package/lib/motion-agent.js +171 -0
- package/lib/motion-capture.js +92 -0
- package/lib/motion-core.js +238 -0
- package/lib/motion-film-review.js +71 -0
- package/lib/motion-library.js +210 -0
- package/lib/motion-planner.js +254 -0
- package/lib/motion-project.js +80 -0
- package/lib/motion-render-worker.js +145 -0
- package/lib/motion-session.js +32 -0
- package/lib/motion-source.js +49 -0
- package/lib/motion-tasks.js +452 -0
- package/lib/motion-vision.js +43 -0
- package/lib/mxfile.js +27 -7
- package/lib/routing/node-runtime.js +14 -0
- package/lib/routing/router.js +211 -0
- package/lib/routing/vendor/README.md +96 -0
- package/lib/routing/vendor/libavoid-routing.js +753 -0
- package/lib/routing/vendor/libavoid.min.js +59 -0
- package/lib/shotcard-core.js +39 -0
- package/lib/shotcard-reference.js +56 -0
- package/lib/style-kernel.js +37 -3
- package/lib/task-patches.js +26 -2
- package/lib/video-export.js +17 -2
- package/package.json +29 -4
|
@@ -0,0 +1,1399 @@
|
|
|
1
|
+
// ISOPOLIS — scene construction + pure-time update.
|
|
2
|
+
import * as THREE from 'three';
|
|
3
|
+
import { RoundedBoxGeometry } from 'three/addons/geometries/RoundedBoxGeometry.js';
|
|
4
|
+
import { mergeGeometries } from 'three/addons/utils/BufferGeometryUtils.js';
|
|
5
|
+
import { RoomEnvironment } from 'three/addons/environments/RoomEnvironment.js';
|
|
6
|
+
import * as A from './anim.js';
|
|
7
|
+
import { LAYER_GLOW, LAYER_BG } from './render.js';
|
|
8
|
+
|
|
9
|
+
export const PAL = {
|
|
10
|
+
bgTop: '#EEEAFC', bgMid: '#DAD3F7', bgLow: '#C4BAEF', dot: '#A79EE2',
|
|
11
|
+
grass: ['#94DFBD', '#8CDBB6', '#9DE3C4', '#86D6B0'], grassDeep: '#74CBA3',
|
|
12
|
+
plaza: '#F4F1FD', plaza2: '#ECE8FB', road: '#8781D0', mark: '#FBFAFF', water: '#8CC6FF',
|
|
13
|
+
white: '#FBFAFF', peach: '#FFC4AC', sky: '#A8D2FF', mint: '#B7ECD6', lilac: '#D9D1FA', butter: '#FFE6A1',
|
|
14
|
+
coral: '#FF9E86', deepSky: '#74A9F2', soil1: '#FFFFFF', soil2: '#CBC3F3', soil3: '#B2A8EC', soil4: '#9F95E1',
|
|
15
|
+
clay: '#F1EEFA', clayWin: '#D0D4EE',
|
|
16
|
+
clayGrass: '#E6E2F2', clayRoad: '#CFCBE6', clayWater: '#DDE3F4', clayPlaza: '#D9D1FA', clayPlaza2: '#D2C9F7',
|
|
17
|
+
voidK: ['#EDE9F9', '#E0DBF5', '#D5CEF0'], voidOn: ['#E4D8FF', '#C5B2FC', '#A58CF0'],
|
|
18
|
+
winOff: '#98ACEE', winOn: '#FFF0C8', data: '#62F0F2', ink: '#2C2960',
|
|
19
|
+
leaf: ['#7FD3A6', '#93DDB5', '#6CC79A', '#A9E6C4'], blossom: '#FFB9A3', lilacLeaf: '#C9BDF6', trunk: '#C9A48F',
|
|
20
|
+
};
|
|
21
|
+
const C = (h) => new THREE.Color(h);
|
|
22
|
+
|
|
23
|
+
// 12 x 12 layout, row = j (+Z, screen left-down), column = i (+X, screen right-down)
|
|
24
|
+
const MAP = [
|
|
25
|
+
'o..t.f..t...', // round 2: turbine (0,0) removed ('o' = bare grass): it sat on the CORE's screen column
|
|
26
|
+
'.f.....f..ww',
|
|
27
|
+
'..rrrrrrrrww',
|
|
28
|
+
't.rbbbbbbrww',
|
|
29
|
+
'..rbbppbbrw.',
|
|
30
|
+
'f.rbpccpbr.f',
|
|
31
|
+
'..rbpccpbr.h',
|
|
32
|
+
'ssrbbppbbrh.',
|
|
33
|
+
'ssrbbbbbbr.h',
|
|
34
|
+
'..rrrrrrrrhf',
|
|
35
|
+
'h.hfh.hfh..f',
|
|
36
|
+
'.hfh.hfh.hf.',
|
|
37
|
+
];
|
|
38
|
+
const N = 12;
|
|
39
|
+
const tx = (i) => i - 5.5, tz = (j) => j - 5.5;
|
|
40
|
+
|
|
41
|
+
// ------------------------------------------------------------------ geometry / material helpers
|
|
42
|
+
const gcache = new Map();
|
|
43
|
+
function rbox(w, h, d, r = 0.05, seg = 2) {
|
|
44
|
+
const k = `rb${w.toFixed(3)}_${h.toFixed(3)}_${d.toFixed(3)}_${r}_${seg}`;
|
|
45
|
+
if (!gcache.has(k)) { const g = new RoundedBoxGeometry(w, h, d, seg, r); g.translate(0, h / 2, 0); gcache.set(k, g); }
|
|
46
|
+
return gcache.get(k);
|
|
47
|
+
}
|
|
48
|
+
function cyl(rt, rb, h, seg = 32) {
|
|
49
|
+
const k = `cy${rt}_${rb}_${h}_${seg}`;
|
|
50
|
+
if (!gcache.has(k)) { const g = new THREE.CylinderGeometry(rt, rb, h, seg, 1); g.translate(0, h / 2, 0); gcache.set(k, g); }
|
|
51
|
+
return gcache.get(k);
|
|
52
|
+
}
|
|
53
|
+
const mcache = new Map();
|
|
54
|
+
function std(hex, o = {}) {
|
|
55
|
+
const k = `${hex}_${o.rough ?? 0.72}_${o.env ?? 0.35}_${o.emissive ?? ''}_${o.ei ?? 1}_${o.metal ?? 0}`;
|
|
56
|
+
if (!mcache.has(k)) {
|
|
57
|
+
mcache.set(k, new THREE.MeshStandardMaterial({
|
|
58
|
+
color: C(hex), roughness: o.rough ?? 0.72, metalness: o.metal ?? 0, envMapIntensity: o.env ?? 0.35,
|
|
59
|
+
emissive: o.emissive ? C(o.emissive) : new THREE.Color(0), emissiveIntensity: o.ei ?? 1,
|
|
60
|
+
}));
|
|
61
|
+
}
|
|
62
|
+
return mcache.get(k);
|
|
63
|
+
}
|
|
64
|
+
function mesh(geo, mat, shadow = true) {
|
|
65
|
+
const m = new THREE.Mesh(geo, mat);
|
|
66
|
+
m.castShadow = shadow; m.receiveShadow = true;
|
|
67
|
+
return m;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ------------------------------------------------------------------ window shader (object-space facade)
|
|
71
|
+
const WIN_VERT_PARS = 'varying vec3 vObj; varying vec3 vObjN;\n';
|
|
72
|
+
const WIN_FRAG_PARS = /* glsl */`
|
|
73
|
+
uniform float uTime, uLit0, uFlash, uStyle, uFloorH, uColW, uSeed, uLitFrac, uEmit, uBaseY;
|
|
74
|
+
uniform vec3 uSize, uWinOff, uWinOn, uClay, uClayWin; uniform float uGrow, uCyl, uSeam;
|
|
75
|
+
varying vec3 vObj; varying vec3 vObjN;
|
|
76
|
+
float hsh(vec3 p){ return fract(sin(dot(p, vec3(12.9898, 78.233, 37.719))) * 43758.5453); }
|
|
77
|
+
`;
|
|
78
|
+
const WIN_FRAG_MAIN = /* glsl */`
|
|
79
|
+
float wMask = 0.0; vec3 wEmit = vec3(0.0);
|
|
80
|
+
// clay -> colour: when the ignition pulse reaches the building, colour climbs the facade with a scan line
|
|
81
|
+
float fy = vObj.y + uBaseY;
|
|
82
|
+
float fAge = uTime - uFlash;
|
|
83
|
+
float ff = fAge * 20.0;
|
|
84
|
+
float fill = fAge < 0.0 ? 0.0 : 1.0 - smoothstep(ff - 0.12, ff, fy);
|
|
85
|
+
float fline = fAge < 0.0 ? 0.0 : exp(-pow((fy - ff) * 12.0, 2.0)) * (1.0 - smoothstep(0.0, 0.2, fAge - (uBaseY + uSize.y) / 20.0));
|
|
86
|
+
diffuseColor.rgb = mix(uClay, diffuseColor.rgb, fill);
|
|
87
|
+
wEmit += fline * vec3(0.55, 1.0, 1.0) * 1.1;
|
|
88
|
+
{
|
|
89
|
+
// blueprint edge: 1-2 px cyan outline on freshly grown walls, held while they grow, gone ~0.4 s later
|
|
90
|
+
float gA = uTime - uGrow - 0.033;
|
|
91
|
+
if (gA > 0.0 && fAge < 0.4) {
|
|
92
|
+
float edy = min(vObj.y, uSize.y - vObj.y);
|
|
93
|
+
float edv = uCyl > 0.5 ? 1e3 : max(uSize.x * 0.5 - abs(vObj.x), uSize.z * 0.5 - abs(vObj.z));
|
|
94
|
+
// round 2: top faces only get their outline (before, the whole cap face read as 'edge' while the walls grew)
|
|
95
|
+
float ee = abs(vObjN.y) > 0.6 ? (uCyl > 0.5 ? uSize.x * 0.5 - length(vObj.xz) : min(uSize.x * 0.5 - abs(vObj.x), uSize.z * 0.5 - abs(vObj.z))) : min(edy, edv);
|
|
96
|
+
float ew = fwidth(ee);
|
|
97
|
+
float eline = 1.0 - smoothstep(ew * 0.55, ew * 1.45, ee);
|
|
98
|
+
float eenv = (gA < 0.35 ? 1.0 : 0.25 + 0.75 * exp(-(gA - 0.35) / 0.14)) * (1.0 - fill);
|
|
99
|
+
diffuseColor.rgb = mix(diffuseColor.rgb, vec3(0.38, 0.94, 0.95), eline * eenv * 0.7);
|
|
100
|
+
wEmit += eline * eenv * vec3(0.38, 0.94, 0.95) * 1.2;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
{
|
|
104
|
+
vec3 an = abs(vObjN);
|
|
105
|
+
if (an.y < 0.6) {
|
|
106
|
+
bool fx = an.x > an.z;
|
|
107
|
+
float u = fx ? vObj.z : vObj.x;
|
|
108
|
+
float fw = fx ? uSize.z : uSize.x;
|
|
109
|
+
float v = vObj.y;
|
|
110
|
+
float face = fx ? (vObjN.x > 0. ? 0. : 1.) : (vObjN.z > 0. ? 2. : 3.);
|
|
111
|
+
float mx = min(0.11, fw * 0.12), my0 = 0.18, my1 = 0.12;
|
|
112
|
+
float uu = u + fw * 0.5 - mx; float span = fw - 2. * mx;
|
|
113
|
+
float nc = max(1.0, floor(span / uColW + 0.5));
|
|
114
|
+
float cw = span / nc;
|
|
115
|
+
float cu = uu / cw; float ci = floor(cu); float fu = cu - ci;
|
|
116
|
+
float nr = max(1.0, floor((uSize.y - my0 - my1) / uFloorH));
|
|
117
|
+
float fh = (uSize.y - my0 - my1) / nr;
|
|
118
|
+
float rv = (v - my0) / fh; float ri = floor(rv); float fv = rv - ri;
|
|
119
|
+
float insideU = uStyle > 0.5 && uStyle < 1.5 ? 1.0 : step(0., uu) * step(uu, span);
|
|
120
|
+
float inside = insideU * step(0., rv) * step(rv, nr);
|
|
121
|
+
vec2 dq = vec2(fwidth(cu), fwidth(rv)) * 0.9;
|
|
122
|
+
float wx, wy;
|
|
123
|
+
if (uStyle < 0.5) {
|
|
124
|
+
wx = smoothstep(0.24 - dq.x, 0.24 + dq.x, fu) * (1. - smoothstep(0.76 - dq.x, 0.76 + dq.x, fu));
|
|
125
|
+
wy = smoothstep(0.3 - dq.y, 0.3 + dq.y, fv) * (1. - smoothstep(0.84 - dq.y, 0.84 + dq.y, fv));
|
|
126
|
+
} else if (uStyle < 1.5) {
|
|
127
|
+
wx = 1.;
|
|
128
|
+
ci = floor(atan(vObj.z, vObj.x) * 1.9);
|
|
129
|
+
wy = smoothstep(0.34 - dq.y, 0.34 + dq.y, fv) * (1. - smoothstep(0.8 - dq.y, 0.8 + dq.y, fv));
|
|
130
|
+
// round 2 (CORE): once the colour scan passes, the thick dormant band narrows to a 3-4 px lit seam on the ink body
|
|
131
|
+
if (uSeam > 0.5) wy = mix(wy, smoothstep(0.47 - dq.y, 0.47 + dq.y, fv) * (1. - smoothstep(0.61 - dq.y, 0.61 + dq.y, fv)), fill);
|
|
132
|
+
} else {
|
|
133
|
+
wx = smoothstep(0.32 - dq.x, 0.32 + dq.x, fu) * (1. - smoothstep(0.68 - dq.x, 0.68 + dq.x, fu));
|
|
134
|
+
wy = smoothstep(0.1 - dq.y, 0.1 + dq.y, fv) * (1. - smoothstep(0.9 - dq.y, 0.9 + dq.y, fv));
|
|
135
|
+
}
|
|
136
|
+
wMask = wx * wy * inside;
|
|
137
|
+
float h = hsh(vec3(ci, ri, face + uSeed));
|
|
138
|
+
float tOn = uLit0 + ri * 0.045 + h * 0.3;
|
|
139
|
+
float on = smoothstep(tOn, tOn + 0.04, uTime) * step(h, uLitFrac);
|
|
140
|
+
float pulse = step(uFlash, uTime);
|
|
141
|
+
float tP = uFlash + ri * 0.012 + h * 0.05;
|
|
142
|
+
on = max(on, smoothstep(tP, tP + 0.03, uTime) * step(h, max(uLitFrac, 0.86)));
|
|
143
|
+
float flash = pulse * exp(-max(uTime - tP, 0.0) * 8.0) * step(tP, uTime);
|
|
144
|
+
vec3 wc = mix(mix(uClayWin, uWinOn * 0.9, on), mix(uWinOff, uWinOn * 0.9, on), fill);
|
|
145
|
+
diffuseColor.rgb = mix(diffuseColor.rgb, wc, wMask);
|
|
146
|
+
float chase = uSeam > 0.5 ? 0.35 + 0.65 * pow(0.5 + 0.5 * cos(6.2832 * (fy * 0.4 - uTime * 0.5)), 3.0) : 1.0; // slow 2 s upward chase
|
|
147
|
+
wEmit += wMask * (uWinOn * on * uEmit * chase + vec3(0.75, 1.0, 1.0) * flash * 0.75);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
`;
|
|
151
|
+
function winMat(U, hex, size, style = 0, o = {}) {
|
|
152
|
+
const m = new THREE.MeshStandardMaterial({ color: C(hex), roughness: o.rough ?? 0.68, metalness: 0, envMapIntensity: o.env ?? 0.4 });
|
|
153
|
+
const u = {
|
|
154
|
+
uLit0: { value: 99 }, uFlash: { value: 99 }, uSize: { value: new THREE.Vector3(...size) }, uStyle: { value: style },
|
|
155
|
+
uFloorH: { value: o.floorH ?? 0.3 }, uColW: { value: o.colW ?? 0.22 }, uSeed: { value: o.seed ?? 0.5 },
|
|
156
|
+
uWinOff: { value: C(o.winOff ?? PAL.winOff) }, uWinOn: { value: C(o.winOn ?? PAL.winOn) }, uLitFrac: { value: o.litFrac ?? 0.7 }, uEmit: { value: o.emit ?? 0.28 }, uBaseY: { value: o.baseY ?? 0 },
|
|
157
|
+
uClay: { value: C(o.clay ?? PAL.clay) }, uClayWin: { value: C(o.clayWin ?? PAL.clayWin) }, uGrow: { value: o.grow ?? 99 }, uCyl: { value: o.cyl ?? 0 }, uSeam: { value: o.seam ?? 0 },
|
|
158
|
+
};
|
|
159
|
+
m.userData.u = u;
|
|
160
|
+
m.onBeforeCompile = (sh) => {
|
|
161
|
+
Object.assign(sh.uniforms, u, { uTime: U.uTime });
|
|
162
|
+
sh.vertexShader = WIN_VERT_PARS + sh.vertexShader.replace('#include <begin_vertex>', '#include <begin_vertex>\nvObj = position; vObjN = normal;');
|
|
163
|
+
sh.fragmentShader = WIN_FRAG_PARS + sh.fragmentShader
|
|
164
|
+
.replace('#include <color_fragment>', '#include <color_fragment>\n' + WIN_FRAG_MAIN)
|
|
165
|
+
.replace('#include <roughnessmap_fragment>', '#include <roughnessmap_fragment>\nroughnessFactor = mix(roughnessFactor, 0.28, wMask);')
|
|
166
|
+
.replace('#include <emissivemap_fragment>', '#include <emissivemap_fragment>\ntotalEmissiveRadiance += wEmit;');
|
|
167
|
+
};
|
|
168
|
+
m.customProgramCacheKey = () => 'win-v6';
|
|
169
|
+
return m;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// plain material that stays clay-white until the ignition pulse reaches it (+ delay)
|
|
173
|
+
function clayMat(U, hex, o = {}) {
|
|
174
|
+
const m = new THREE.MeshStandardMaterial({ color: C(hex), roughness: o.rough ?? 0.6, metalness: 0, envMapIntensity: o.env ?? 0.35 });
|
|
175
|
+
const u = { uFlash: { value: 99 }, uDelay: { value: o.delay ?? 0 }, uClay: { value: C(PAL.clay) } };
|
|
176
|
+
m.userData.u = u;
|
|
177
|
+
m.onBeforeCompile = (sh) => {
|
|
178
|
+
Object.assign(sh.uniforms, u, { uTime: U.uTime });
|
|
179
|
+
sh.fragmentShader = 'uniform float uTime, uFlash, uDelay; uniform vec3 uClay;\n' + sh.fragmentShader.replace('#include <color_fragment>',
|
|
180
|
+
'#include <color_fragment>\ndiffuseColor.rgb = mix(uClay, diffuseColor.rgb, smoothstep(uFlash + uDelay, uFlash + uDelay + 0.05, uTime));');
|
|
181
|
+
};
|
|
182
|
+
m.customProgramCacheKey = () => 'clay-v1';
|
|
183
|
+
return m;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// soil with strata bands (object space y)
|
|
187
|
+
function soilMat(U) {
|
|
188
|
+
const m = new THREE.MeshStandardMaterial({ color: 0xffffff, roughness: 0.85, envMapIntensity: 0.3 });
|
|
189
|
+
m.onBeforeCompile = (sh) => {
|
|
190
|
+
sh.uniforms.c1 = { value: C(PAL.soil1) }; sh.uniforms.c2 = { value: C(PAL.soil2) };
|
|
191
|
+
sh.uniforms.c3 = { value: C(PAL.soil3) }; sh.uniforms.c4 = { value: C(PAL.soil4) };
|
|
192
|
+
sh.vertexShader = 'varying vec3 vObj;\n' + sh.vertexShader.replace('#include <begin_vertex>', '#include <begin_vertex>\nvObj = position;');
|
|
193
|
+
sh.fragmentShader = 'uniform vec3 c1,c2,c3,c4; varying vec3 vObj;\n' + sh.fragmentShader.replace('#include <color_fragment>', `#include <color_fragment>
|
|
194
|
+
float y = vObj.y; float aa = fwidth(y) * 0.8;
|
|
195
|
+
vec3 col = c4;
|
|
196
|
+
col = mix(col, c3, smoothstep(-0.95 - aa, -0.95 + aa, y));
|
|
197
|
+
col = mix(col, c2, smoothstep(-0.62 - aa, -0.62 + aa, y));
|
|
198
|
+
col = mix(col, c1, smoothstep(-0.2 - aa, -0.2 + aa, y));
|
|
199
|
+
diffuseColor.rgb = col;`);
|
|
200
|
+
};
|
|
201
|
+
m.customProgramCacheKey = () => 'soil-v1';
|
|
202
|
+
return m;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// ------------------------------------------------------------------ path along a rounded rectangle loop
|
|
206
|
+
function loopPath(a, rc) {
|
|
207
|
+
const st = 2 * (a - rc), arc = Math.PI / 2 * rc, seg = st + arc, L = 4 * seg;
|
|
208
|
+
// counter-clockwise when seen from above (+Y): start at (a, 0, -(a-rc)) heading +z
|
|
209
|
+
const corners = [[a - rc, a - rc], [-(a - rc), a - rc], [-(a - rc), -(a - rc)], [a - rc, -(a - rc)]];
|
|
210
|
+
const starts = [[a, -(a - rc)], [a - rc, a], [-a, a - rc], [-(a - rc), -a]];
|
|
211
|
+
const dirs = [[0, 1], [-1, 0], [0, -1], [1, 0]];
|
|
212
|
+
return {
|
|
213
|
+
L,
|
|
214
|
+
at(s) {
|
|
215
|
+
s = ((s % L) + L) % L;
|
|
216
|
+
const k = Math.floor(s / seg), r = s - k * seg;
|
|
217
|
+
if (r < st) {
|
|
218
|
+
const [sx, sz] = starts[k], [dx, dz] = dirs[k];
|
|
219
|
+
return { x: sx + dx * r, z: sz + dz * r, ang: Math.atan2(dx, dz) };
|
|
220
|
+
}
|
|
221
|
+
const th = (r - st) / rc; // angle travelled on the corner
|
|
222
|
+
const [cx, cz] = corners[k];
|
|
223
|
+
const a0 = [0, Math.PI / 2, Math.PI, -Math.PI / 2][k]; // start angle (from +x toward +z)
|
|
224
|
+
const ang = a0 + th;
|
|
225
|
+
const dxz = [-Math.sin(ang), Math.cos(ang)];
|
|
226
|
+
return { x: cx + rc * Math.cos(ang), z: cz + rc * Math.sin(ang), ang: Math.atan2(dxz[0], dxz[1]) };
|
|
227
|
+
},
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// ------------------------------------------------------------------ build
|
|
232
|
+
export function buildWorld(renderer, cues, opts = {}) {
|
|
233
|
+
const scene = new THREE.Scene();
|
|
234
|
+
const U = { uTime: { value: 0 } };
|
|
235
|
+
// growth front: screen-x (iso) -> time, piecewise linear (cues.sweep)
|
|
236
|
+
const SW = cues.sweep;
|
|
237
|
+
const sxOf = (x, z) => (x - z) / Math.SQRT2;
|
|
238
|
+
const front = (sx) => {
|
|
239
|
+
for (let k = 1; k < SW.length; k++) if (sx <= SW[k][0] || k === SW.length - 1) {
|
|
240
|
+
const [a, ta] = SW[k - 1], [b, tb] = SW[k];
|
|
241
|
+
return ta + (tb - ta) * A.clamp((sx - a) / (b - a), 0, 1.2);
|
|
242
|
+
}
|
|
243
|
+
return SW[SW.length - 1][1];
|
|
244
|
+
};
|
|
245
|
+
const updaters = [];
|
|
246
|
+
const onUpdate = (f) => updaters.push(f);
|
|
247
|
+
|
|
248
|
+
// environment for soft specular
|
|
249
|
+
const pmrem = new THREE.PMREMGenerator(renderer);
|
|
250
|
+
scene.environment = pmrem.fromScene(new RoomEnvironment(), 0.04).texture;
|
|
251
|
+
scene.environmentIntensity = 0.3;
|
|
252
|
+
|
|
253
|
+
// --- camera: true isometric
|
|
254
|
+
const VH = 18, aspect = 16 / 9;
|
|
255
|
+
const camera = new THREE.OrthographicCamera(-VH * aspect / 2, VH * aspect / 2, VH / 2, -VH / 2, 1, 400);
|
|
256
|
+
const DIR = new THREE.Vector3(1, 1, 1).normalize();
|
|
257
|
+
const camRight = new THREE.Vector3(1, 0, -1).normalize();
|
|
258
|
+
const camUp = new THREE.Vector3(-1, 2, -1).normalize();
|
|
259
|
+
|
|
260
|
+
// --- lights
|
|
261
|
+
const hemi = new THREE.HemisphereLight(C('#F6F3FF'), C('#C9BFF0'), 0.75);
|
|
262
|
+
scene.add(hemi);
|
|
263
|
+
const sunDir0 = new THREE.Vector3(-0.42, 1.0, 0.62).normalize();
|
|
264
|
+
const sun = new THREE.DirectionalLight(C('#FFF3E6'), 2.25);
|
|
265
|
+
sun.castShadow = true;
|
|
266
|
+
sun.shadow.mapSize.set(4096, 4096);
|
|
267
|
+
const sc = sun.shadow.camera; sc.left = -20; sc.right = 20; sc.top = 20; sc.bottom = -20; sc.near = 1; sc.far = 90;
|
|
268
|
+
sun.shadow.bias = -0.0004; sun.shadow.normalBias = 0.018;
|
|
269
|
+
scene.add(sun); scene.add(sun.target);
|
|
270
|
+
const fill = new THREE.DirectionalLight(C('#DCE4FF'), 0.3);
|
|
271
|
+
fill.position.set(6, 3, -2); scene.add(fill);
|
|
272
|
+
// round 2: clay-phase light (warm key x1.2, denser contact shadows, lavender fill) eases into the hero light after the wave
|
|
273
|
+
const SUN_H = C('#FFF3E6'), SUN_K = C('#FFF6EC'), HEMI_G = C('#C9BFF0'), HEMI_GK = C('#B6A7EE'), FILL_H = C('#DCE4FF'), FILL_K = C('#CDBFFF');
|
|
274
|
+
|
|
275
|
+
// --- background (screen quad: gradient + iso dot floor + island floor shadow)
|
|
276
|
+
const bgU = {
|
|
277
|
+
uInvVP: { value: new THREE.Matrix4() }, uFwd: { value: DIR.clone().negate() },
|
|
278
|
+
uTop: { value: C(PAL.voidOn[0]) }, uMid: { value: C(PAL.voidOn[1]) }, uLow: { value: C(PAL.voidOn[2]) }, uDot: { value: C(PAL.dot) },
|
|
279
|
+
uTopK: { value: C(PAL.voidK[0]) }, uMidK: { value: C(PAL.voidK[1]) }, uLowK: { value: C(PAL.voidK[2]) }, uVoidK: { value: 0 }, uWaveT: { value: 99 }, uWaveC: { value: new THREE.Vector2() },
|
|
280
|
+
uFloorY: { value: -7.5 }, uOff: { value: new THREE.Vector2() }, uShadowC: { value: new THREE.Vector2() },
|
|
281
|
+
uShadow: { value: 0 }, uDots: { value: 1 }, uTime: U.uTime, uZoom: { value: 1 }, uRipC: { value: new THREE.Vector2(0, 0) }, uRipT: { value: 99 }, uRipV: { value: 12 }, uRipD: { value: 8 }, uHot: { value: C(PAL.data) },
|
|
282
|
+
};
|
|
283
|
+
const bgMat = new THREE.ShaderMaterial({
|
|
284
|
+
uniforms: bgU, depthTest: false, depthWrite: false,
|
|
285
|
+
vertexShader: 'varying vec2 vNdc; void main(){ vNdc = position.xy; gl_Position = vec4(position.xy, 0.9999, 1.0); }',
|
|
286
|
+
fragmentShader: /* glsl */`
|
|
287
|
+
uniform mat4 uInvVP; uniform vec3 uFwd; uniform vec3 uTop, uMid, uLow, uDot, uHot; uniform float uFloorY, uShadow, uDots, uZoom, uTime, uRipT, uRipV, uRipD; uniform vec2 uRipC;
|
|
288
|
+
uniform vec2 uOff, uShadowC; varying vec2 vNdc;
|
|
289
|
+
uniform vec3 uTopK, uMidK, uLowK; uniform float uVoidK, uWaveT; uniform vec2 uWaveC;
|
|
290
|
+
float sdRoundBox(vec2 p, vec2 b, float r){ vec2 q = abs(p) - b + r; return length(max(q, 0.0)) + min(max(q.x, q.y), 0.0) - r; }
|
|
291
|
+
void main(){
|
|
292
|
+
vec2 uv = vNdc * 0.5 + 0.5;
|
|
293
|
+
float wa = uTime - uWaveT;
|
|
294
|
+
float wr = length((vNdc - uWaveC) * vec2(1.7778, 1.0));
|
|
295
|
+
float front = wa > 0.0 ? 0.15 + wa * 5.5 : -1.0;
|
|
296
|
+
float on = 1.0 - smoothstep(front - 0.35, front, wr);
|
|
297
|
+
float kk = uVoidK * (1.0 - on);
|
|
298
|
+
vec3 cOn = mix(mix(uLow, uMid, smoothstep(0.0, 0.55, uv.y)), uTop, smoothstep(0.5, 1.0, uv.y));
|
|
299
|
+
vec3 cK = mix(mix(uLowK, uMidK, smoothstep(0.0, 0.55, uv.y)), uTopK, smoothstep(0.5, 1.0, uv.y));
|
|
300
|
+
vec3 col = mix(cOn, cK, kk);
|
|
301
|
+
vec3 topC = mix(uTop, uTopK, kk);
|
|
302
|
+
// soft light pool behind the island
|
|
303
|
+
vec2 q = vNdc; q.x *= 1.7778;
|
|
304
|
+
col = mix(col, topC * 1.03, 0.35 * exp(-dot(q - vec2(0.15, 0.1), q - vec2(0.15, 0.1)) * 0.9));
|
|
305
|
+
col += uHot * exp(-pow((wr - front + 0.12) * 6.0, 2.0)) * 0.16 * exp(-max(wa, 0.0) * 2.2) * step(0.0, wa);
|
|
306
|
+
// iso floor
|
|
307
|
+
vec4 p = uInvVP * vec4(vNdc, -1.0, 1.0); p.xyz /= p.w;
|
|
308
|
+
float tt = (uFloorY - p.y) / uFwd.y; vec3 w = p.xyz + uFwd * tt;
|
|
309
|
+
vec2 g = w.xz + uOff;
|
|
310
|
+
vec2 cc = g - floor(g + 0.5);
|
|
311
|
+
float dd = length(cc);
|
|
312
|
+
float aa = fwidth(dd) * 1.2 + 0.004;
|
|
313
|
+
float ra = uTime - uRipT;
|
|
314
|
+
vec2 rq = abs(floor(g + 0.5) - uRipC);
|
|
315
|
+
float rr = max(rq.x, rq.y);
|
|
316
|
+
// round 2: one cyan ping from the title to the island's floor shadow: 5 px dots at 70 % on the front, 0.5 s wake
|
|
317
|
+
float dR = rr - ra * uRipV;
|
|
318
|
+
float ring = ra > 0.0 ? (dR > 0.0 ? exp(-pow(dR * 1.4, 2.0)) : exp(dR / (uRipV * 0.5))) * (1.0 - smoothstep(uRipD + 1.0, uRipD + 4.0, ra * uRipV)) : 0.0;
|
|
319
|
+
float r = mix(0.032 / sqrt(max(uZoom, 0.5)), 2.5 / (60.0 * uZoom), clamp(ring, 0.0, 1.0));
|
|
320
|
+
float dotm = 1.0 - smoothstep(r - aa, r + aa, dd);
|
|
321
|
+
float fade = exp(-dot(w.xz, w.xz) * 0.0035);
|
|
322
|
+
col = mix(col, mix(uDot, uHot, clamp(ring * 1.5, 0.0, 1.0)), dotm * max(0.55 * fade, ring * 0.7) * uDots);
|
|
323
|
+
// r2s2 bookend: the ping re-draws the opening's cyan blueprint grid (1.2 px lines) on its front only, so the end frame
|
|
324
|
+
// answers the first frame. Lines run through the dots (integer g), like the opening blueprint crosses.
|
|
325
|
+
float lineRing = ra > 0.0 ? exp(-pow(dR * 0.9, 2.0)) * (1.0 - smoothstep(uRipD + 0.5, uRipD + 3.0, ra * uRipV)) : 0.0;
|
|
326
|
+
vec2 ce = abs(fract(g) - 0.5);
|
|
327
|
+
float ld = 0.5 - max(ce.x, ce.y);
|
|
328
|
+
float lw = 0.6 / (60.0 * uZoom); float la = fwidth(ld) * 1.0 + 1e-4;
|
|
329
|
+
float lm = 1.0 - smoothstep(lw - la, lw + la, ld);
|
|
330
|
+
col = mix(col, uHot, lm * lineRing * 0.5 * uDots);
|
|
331
|
+
// floor shadow of the floating island
|
|
332
|
+
float sd = sdRoundBox(w.xz - uShadowC, vec2(5.0), 1.8);
|
|
333
|
+
float sh = 1.0 - smoothstep(-2.2, 2.8, sd);
|
|
334
|
+
float core = 1.0 - smoothstep(-2.6, 0.9, sd);
|
|
335
|
+
col = mix(col, col * vec3(0.70, 0.68, 0.88), (0.55 * sh + 0.4 * core) * uShadow);
|
|
336
|
+
gl_FragColor = vec4(col, 1.0);
|
|
337
|
+
}`,
|
|
338
|
+
});
|
|
339
|
+
const bgQuad = new THREE.Mesh(new THREE.PlaneGeometry(2, 2), bgMat);
|
|
340
|
+
bgQuad.frustumCulled = false; bgQuad.renderOrder = -1000; bgQuad.layers.set(LAYER_BG);
|
|
341
|
+
scene.add(bgQuad);
|
|
342
|
+
|
|
343
|
+
// ================================================================= ISLAND
|
|
344
|
+
const island = new THREE.Group(); scene.add(island);
|
|
345
|
+
const ringT = cues.rings;
|
|
346
|
+
const tiles = [];
|
|
347
|
+
const R = A.rng(7);
|
|
348
|
+
const cell = (i, j) => (j >= 0 && j < N && i >= 0 && i < N ? MAP[j][i] : ' ');
|
|
349
|
+
for (let j = 0; j < N; j++) for (let i = 0; i < N; i++) {
|
|
350
|
+
const ch = cell(i, j);
|
|
351
|
+
const dx = i - 5.5, dz = j - 5.5;
|
|
352
|
+
const ring = Math.max(Math.abs(dx), Math.abs(dz)) - 0.5;
|
|
353
|
+
const ang = (Math.atan2(dz, dx) / (2 * Math.PI) + 1) % 1;
|
|
354
|
+
const tl = ringT[ring] + ang * 0.07 + (R() - 0.5) * 0.02;
|
|
355
|
+
let radial;
|
|
356
|
+
if (Math.abs(Math.abs(dx) - Math.abs(dz)) < 0.01) radial = new THREE.Vector3(Math.sign(dx), 0, Math.sign(dz)).normalize();
|
|
357
|
+
else if (Math.abs(dx) > Math.abs(dz)) radial = new THREE.Vector3(Math.sign(dx), 0, 0);
|
|
358
|
+
else radial = new THREE.Vector3(0, 0, Math.sign(dz));
|
|
359
|
+
const axis = new THREE.Vector3(0, 1, 0).cross(radial).normalize();
|
|
360
|
+
let type = 'grass';
|
|
361
|
+
if (ch === 'r') type = 'road'; else if ('pbc'.includes(ch)) type = 'plaza'; else if (ch === 'w') type = 'water';
|
|
362
|
+
tiles.push({ i, j, ch, type, x: tx(i), z: tz(j), tl, axis, ring, rot0: -(1.25 + R() * 0.3) });
|
|
363
|
+
}
|
|
364
|
+
// blueprint: the OS draws the 12x12 plan first; tiles snap onto it
|
|
365
|
+
const bpMat = new THREE.ShaderMaterial({
|
|
366
|
+
uniforms: { uTime: U.uTime, uA: { value: cues.blueprint[0] }, uB: { value: cues.blueprint[1] }, uCol: { value: C(PAL.data) }, uHot: { value: C('#FFFFFF') } },
|
|
367
|
+
transparent: true, depthWrite: false,
|
|
368
|
+
vertexShader: 'varying vec3 vW; void main(){ vec4 w = modelMatrix * vec4(position,1.); vW = w.xyz; gl_Position = projectionMatrix * viewMatrix * w; }',
|
|
369
|
+
fragmentShader: /* glsl */`
|
|
370
|
+
uniform float uTime, uA, uB; uniform vec3 uCol, uHot; varying vec3 vW;
|
|
371
|
+
void main(){
|
|
372
|
+
float r = max(abs(vW.x), abs(vW.z));
|
|
373
|
+
float u = clamp((uTime - uA) / (uB - uA), 0.0, 1.0);
|
|
374
|
+
float R = 1.9 + 4.3 * (1.0 - pow(1.0 - u, 2.0));
|
|
375
|
+
vec2 g = abs(fract(vW.xz + 0.5) - 0.5);
|
|
376
|
+
float lw = fwidth(vW.x);
|
|
377
|
+
float line = max(1.0 - smoothstep(lw * 0.8, lw * 1.8, g.x), 1.0 - smoothstep(lw * 0.8, lw * 1.8, g.y));
|
|
378
|
+
// cross ticks at intersections
|
|
379
|
+
vec2 c = abs(vW.xz - floor(vW.xz + 0.5));
|
|
380
|
+
float tick = (1.0 - smoothstep(lw * 1.3, lw * 2.4, min(c.x, c.y))) * step(max(c.x, c.y), 0.11);
|
|
381
|
+
float border = 1.0 - smoothstep(lw * 1.4, lw * 2.6, abs(r - 6.0));
|
|
382
|
+
float inside = 1.0 - smoothstep(R - 0.05, R, r);
|
|
383
|
+
float frontGlow = exp(-pow((r - R) * 3.0, 2.0)) * (1.0 - u * 0.6);
|
|
384
|
+
float head = exp(-pow((r - R) * 6.0, 2.0)) * (1.0 - u * 0.7);
|
|
385
|
+
float a = (line * 0.8 + tick * 1.0 + border * 1.0) * inside * (r < 6.02 ? 1.0 : 0.0);
|
|
386
|
+
a += head * 0.22 * (r < 6.02 ? 1.0 : 0.0);
|
|
387
|
+
a *= 1.0 - smoothstep(1.55, 1.85, uTime);
|
|
388
|
+
vec3 col = mix(uCol, uHot, clamp(frontGlow * 1.2 + head, 0.0, 1.0));
|
|
389
|
+
gl_FragColor = vec4(col, clamp(a, 0.0, 1.0) * 0.95);
|
|
390
|
+
}`,
|
|
391
|
+
});
|
|
392
|
+
const blueprint = new THREE.Mesh(new THREE.PlaneGeometry(12.6, 12.6).rotateX(-Math.PI / 2), bpMat);
|
|
393
|
+
blueprint.position.y = -0.338; blueprint.layers.set(LAYER_GLOW); island.add(blueprint);
|
|
394
|
+
onUpdate((T) => { blueprint.visible = T < 1.9; });
|
|
395
|
+
|
|
396
|
+
const tileGeo = new RoundedBoxGeometry(0.955, 0.34, 0.955, 2, 0.075);
|
|
397
|
+
const tileMat = new THREE.MeshStandardMaterial({ color: 0xffffff, roughness: 0.82, metalness: 0, envMapIntensity: 0.3 });
|
|
398
|
+
const waterMat = new THREE.MeshStandardMaterial({ color: C(PAL.water), roughness: 0.14, metalness: 0, envMapIntensity: 1.0 });
|
|
399
|
+
// stylised pond: drifting broken wave dashes + slow tonal swell on the top faces (island-local coords, pure in uTime)
|
|
400
|
+
waterMat.onBeforeCompile = (sh) => {
|
|
401
|
+
sh.uniforms.uTime = U.uTime;
|
|
402
|
+
sh.vertexShader = 'varying vec3 vWq; varying float vNy;\n' + sh.vertexShader.replace('#include <begin_vertex>',
|
|
403
|
+
'#include <begin_vertex>\nvec4 wq4 = vec4(transformed, 1.0);\n#ifdef USE_INSTANCING\nwq4 = instanceMatrix * wq4;\n#endif\nvWq = wq4.xyz; vNy = normal.y;');
|
|
404
|
+
sh.fragmentShader = 'uniform float uTime; varying vec3 vWq; varying float vNy;\n' + sh.fragmentShader.replace('#include <color_fragment>', `#include <color_fragment>
|
|
405
|
+
{
|
|
406
|
+
vec2 q = vWq.xz; float t = uTime;
|
|
407
|
+
float top = smoothstep(0.75, 0.97, -vNy) * step(0.001, vFK.y); // round 2: the water face is the tile's underside
|
|
408
|
+
float w1 = sin((q.x * 0.8 + q.y * 1.0) * 7.0 - t * 2.2 + sin(q.x * 2.1 - q.y * 1.7 + t * 0.9) * 1.4);
|
|
409
|
+
float br = smoothstep(0.42, 0.78, sin(q.x * 3.1 - q.y * 2.3 + t * 0.6 + 1.7) * 0.5 + 0.5);
|
|
410
|
+
float w2 = sin((q.x * 1.1 - q.y * 0.35) * 11.0 - t * 3.1 + 2.0);
|
|
411
|
+
float br2 = smoothstep(0.6, 0.9, sin(q.x * 4.3 + q.y * 3.7 - t * 0.8) * 0.5 + 0.5);
|
|
412
|
+
float dash = max(smoothstep(0.88, 0.97, w1) * br, smoothstep(0.93, 0.99, w2) * br2 * 0.6);
|
|
413
|
+
float swell = sin(q.x * 1.3 + q.y * 0.9 + t * 0.5) * 0.5 + 0.5;
|
|
414
|
+
diffuseColor.rgb *= mix(1.0, 0.93 + 0.1 * swell, top);
|
|
415
|
+
diffuseColor.rgb = mix(diffuseColor.rgb, vec3(0.97, 0.99, 1.0), dash * top * 0.75);
|
|
416
|
+
}`);
|
|
417
|
+
};
|
|
418
|
+
waterMat.customProgramCacheKey = () => 'water-v1';
|
|
419
|
+
const land = tiles.filter((t) => t.type !== 'water'), wet = tiles.filter((t) => t.type === 'water');
|
|
420
|
+
const tileGeoW = tileGeo.clone();
|
|
421
|
+
const tileIM = new THREE.InstancedMesh(tileGeo, tileMat, land.length);
|
|
422
|
+
const waterIM = new THREE.InstancedMesh(tileGeoW, waterMat, wet.length);
|
|
423
|
+
// round 2: two-faced tiles. The top (+y) is clay, the underside (-y) carries the colour; the hero wave flips empty tiles
|
|
424
|
+
// onto their coloured face, the rest repaint under the hop. Per instance: clay colour, [flip mode, colour k], landing time.
|
|
425
|
+
for (const [g, n] of [[tileGeo, land.length], [tileGeoW, wet.length]]) {
|
|
426
|
+
g.setAttribute('aClay', new THREE.InstancedBufferAttribute(new Float32Array(n * 3), 3));
|
|
427
|
+
g.setAttribute('aFK', new THREE.InstancedBufferAttribute(new Float32Array(n * 2), 2));
|
|
428
|
+
g.setAttribute('aLand', new THREE.InstancedBufferAttribute(new Float32Array(n), 1));
|
|
429
|
+
}
|
|
430
|
+
const TILE_FACE_GLSL = /* glsl */`#include <color_fragment>
|
|
431
|
+
vec3 tEmit = vec3(0.0);
|
|
432
|
+
{
|
|
433
|
+
vec3 cF = diffuseColor.rgb;
|
|
434
|
+
float up = smoothstep(0.3, 0.75, vTN.y), dn = smoothstep(0.3, 0.75, -vTN.y);
|
|
435
|
+
vec3 side = mix(vClay, cF, vFK.y);
|
|
436
|
+
vec3 fl = mix(mix(side, vClay, up), mix(vClay, cF, step(0.001, vFK.y)), dn);
|
|
437
|
+
diffuseColor.rgb = vFK.x > 0.5 ? fl : mix(vClay, cF, vFK.y);
|
|
438
|
+
// landing rim: 2 px cyan outline on the clay face, decays over ~6 frames after contact
|
|
439
|
+
float ed = abs(0.385 - max(abs(vTP.x), abs(vTP.z)));
|
|
440
|
+
float ew = fwidth(ed);
|
|
441
|
+
float la = uTimeT - vLand;
|
|
442
|
+
float rim = (1.0 - smoothstep(ew * 0.8, ew * 1.9, ed)) * smoothstep(0.9, 0.99, vTN.y) * step(0.0, la) * exp(-max(la, 0.0) / 0.075);
|
|
443
|
+
diffuseColor.rgb = mix(diffuseColor.rgb, vec3(0.38, 0.94, 0.95), rim * 0.85);
|
|
444
|
+
tEmit = rim * vec3(0.38, 0.94, 0.95) * 1.3;
|
|
445
|
+
}`;
|
|
446
|
+
for (const m of [tileMat, waterMat]) {
|
|
447
|
+
const prev = m.onBeforeCompile, k0 = m.customProgramCacheKey.call(m);
|
|
448
|
+
m.onBeforeCompile = (sh, r) => {
|
|
449
|
+
prev.call(m, sh, r);
|
|
450
|
+
sh.uniforms.uTimeT = U.uTime;
|
|
451
|
+
sh.vertexShader = 'attribute vec3 aClay; attribute vec2 aFK; attribute float aLand;\nvarying vec3 vClay; varying vec2 vFK; varying float vLand; varying vec3 vTN; varying vec3 vTP;\n' +
|
|
452
|
+
sh.vertexShader.replace('#include <begin_vertex>', '#include <begin_vertex>\nvClay = aClay; vFK = aFK; vLand = aLand; vTN = normal; vTP = position;');
|
|
453
|
+
sh.fragmentShader = 'uniform float uTimeT; varying vec3 vClay; varying vec2 vFK; varying float vLand; varying vec3 vTN; varying vec3 vTP;\n' + sh.fragmentShader
|
|
454
|
+
.replace('#include <color_fragment>', TILE_FACE_GLSL)
|
|
455
|
+
.replace('#include <emissivemap_fragment>', '#include <emissivemap_fragment>\ntotalEmissiveRadiance += tEmit;');
|
|
456
|
+
};
|
|
457
|
+
m.customProgramCacheKey = () => k0 + '|faces2';
|
|
458
|
+
}
|
|
459
|
+
for (const im of [tileIM, waterIM]) { im.castShadow = true; im.receiveShadow = true; im.frustumCulled = false; island.add(im); }
|
|
460
|
+
land.forEach((t, k) => {
|
|
461
|
+
let col;
|
|
462
|
+
if (t.type === 'grass') col = PAL.grass[Math.floor(A.hash(t.i * 13 + t.j * 7) * 4)];
|
|
463
|
+
else if (t.type === 'road') col = PAL.road;
|
|
464
|
+
else col = (t.i + t.j) % 2 ? PAL.plaza : PAL.plaza2;
|
|
465
|
+
let clay = t.type === 'grass' ? PAL.clayGrass : t.type === 'road' ? PAL.clayRoad : ((t.i + t.j) % 2 ? PAL.clayPlaza : PAL.clayPlaza2);
|
|
466
|
+
if (t.type === 'plaza') col = (t.i + t.j) % 2 ? '#FFF0DA' : '#FFE7CB';
|
|
467
|
+
if (t.ch === 'c') { col = PAL.ink; clay = PAL.ink; }
|
|
468
|
+
t.col = C(col); t.clay = C(clay);
|
|
469
|
+
tileIM.setColorAt(k, t.col);
|
|
470
|
+
t.im = tileIM; t.k = k;
|
|
471
|
+
});
|
|
472
|
+
wet.forEach((t, k) => { t.col = C(PAL.water); t.clay = C(PAL.clayWater); waterIM.setColorAt(k, t.col); t.im = waterIM; t.k = k; });
|
|
473
|
+
waterMat.color.set('#ffffff');
|
|
474
|
+
const _m = new THREE.Matrix4(), _q = new THREE.Quaternion(), _s = new THREE.Vector3(), _p = new THREE.Vector3(), _off = new THREE.Matrix4().makeTranslation(0, 0.17, 0);
|
|
475
|
+
const FLIP_D = 5 / 30;
|
|
476
|
+
function tileMatrix(t, T) {
|
|
477
|
+
if (t.flip && T > t.ta) {
|
|
478
|
+
// hero flip: 180 deg about the axis parallel to the CORE-facing edge (inner edge rises first), 5 frames,
|
|
479
|
+
// easeOutBack 1.4 (the overshoot is the micro-bounce), 0.15 u lift at mid-flip; pivot = tile centre
|
|
480
|
+
const u = Math.min((T - t.ta) / FLIP_D, 1);
|
|
481
|
+
_q.setFromAxisAngle(t.faxis, Math.PI * A.easeOutBack(u, 1.4) * t.fsgn);
|
|
482
|
+
_s.set(1, 1, 1);
|
|
483
|
+
_p.set(t.x, -0.17 + 0.15 * Math.sin(Math.PI * u), t.z);
|
|
484
|
+
return _m.compose(_p, _q, _s);
|
|
485
|
+
}
|
|
486
|
+
const d = A.drop(T - t.tl, 0.3, 2.2, t.rot0, 0.075);
|
|
487
|
+
if (!d.vis) { _m.makeScale(0, 0, 0); return _m; }
|
|
488
|
+
_q.setFromAxisAngle(t.axis, d.rot);
|
|
489
|
+
_s.set(d.sxz, d.sy, d.sxz);
|
|
490
|
+
_p.set(t.x, -0.34 + d.y + pulseBump(t.x, t.z, T), t.z);
|
|
491
|
+
_m.compose(_p, _q, _s).multiply(_off);
|
|
492
|
+
return _m;
|
|
493
|
+
}
|
|
494
|
+
// tiny bump of the tiles when the hero pulse passes
|
|
495
|
+
const PULSE_T = cues.drop - 0.015, PULSE_V = cues.pulse_speed, PULSE_R0 = 1.0; // wave is born at the CORE footprint edge
|
|
496
|
+
function pulseBump(x, z, T) {
|
|
497
|
+
const r = Math.max(Math.abs(x), Math.abs(z));
|
|
498
|
+
const ta = PULSE_T + Math.max(r - PULSE_R0, 0) / PULSE_V;
|
|
499
|
+
const u = T - ta;
|
|
500
|
+
if (u < 0 || u > 0.35) return 0;
|
|
501
|
+
return 0; // the hero hop now lives in the vertex shader (hopPatch) so every object on a tile rides the wave
|
|
502
|
+
}
|
|
503
|
+
for (const t of tiles) {
|
|
504
|
+
const r = Math.max(Math.abs(t.x), Math.abs(t.z));
|
|
505
|
+
t.ta = PULSE_T + Math.max(r - PULSE_R0, 0) / PULSE_V;
|
|
506
|
+
const xd = Math.abs(t.x) >= Math.abs(t.z);
|
|
507
|
+
t.faxis = xd ? new THREE.Vector3(0, 0, 1) : new THREE.Vector3(1, 0, 0);
|
|
508
|
+
t.fsgn = xd ? -Math.sign(t.x) : Math.sign(t.z);
|
|
509
|
+
t.flip = false; // decided once everything standing on the island exists (see "which tiles flip")
|
|
510
|
+
const ga = t.im.geometry.attributes;
|
|
511
|
+
ga.aClay.setXYZ(t.k, t.clay.r, t.clay.g, t.clay.b); ga.aLand.setX(t.k, t.tl);
|
|
512
|
+
}
|
|
513
|
+
onUpdate((T) => {
|
|
514
|
+
// round 2: tiles land clay-side up (no drain); at the hero wave empty tiles flip onto their coloured face,
|
|
515
|
+
// tiles carrying objects repaint as they hop
|
|
516
|
+
for (const t of tiles) {
|
|
517
|
+
t.im.setMatrixAt(t.k, tileMatrix(t, T));
|
|
518
|
+
const k = t.flip ? A.clamp((T - t.ta) / FLIP_D) : A.smooth(A.inv(t.ta, t.ta + 0.06, T));
|
|
519
|
+
t.im.geometry.attributes.aFK.setXY(t.k, t.flip ? 1 : 0, k);
|
|
520
|
+
}
|
|
521
|
+
tileIM.instanceMatrix.needsUpdate = true; waterIM.instanceMatrix.needsUpdate = true;
|
|
522
|
+
tileGeo.attributes.aFK.needsUpdate = true; tileGeoW.attributes.aFK.needsUpdate = true;
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
// --- soil body (extrudes downward once the tiles are in)
|
|
526
|
+
const soil = new THREE.Group(); island.add(soil);
|
|
527
|
+
const sm = soilMat(U);
|
|
528
|
+
const s1 = mesh(new RoundedBoxGeometry(12.0, 1.3, 12.0, 4, 0.16).translate(0, -0.65, 0), sm);
|
|
529
|
+
const s2 = mesh(new RoundedBoxGeometry(9.2, 0.9, 9.2, 4, 0.2).translate(0, -0.45, 0), std(PAL.soil4, { rough: 0.9 }));
|
|
530
|
+
s2.position.y = -1.3;
|
|
531
|
+
const s3 = mesh(new RoundedBoxGeometry(6.0, 0.8, 6.0, 4, 0.22).translate(0, -0.4, 0), std('#8F85D6', { rough: 0.9 }));
|
|
532
|
+
s3.position.y = -2.2;
|
|
533
|
+
soil.add(s1, s2, s3);
|
|
534
|
+
soil.position.y = -0.345;
|
|
535
|
+
onUpdate((T) => {
|
|
536
|
+
const u = A.easeOutCubic(A.inv(cues.soil, cues.soil + 0.45, T));
|
|
537
|
+
const u2 = A.easeOutCubic(A.inv(cues.soil + 0.15, cues.soil + 0.6, T));
|
|
538
|
+
const u3 = A.easeOutCubic(A.inv(cues.soil + 0.3, cues.soil + 0.75, T));
|
|
539
|
+
s1.scale.set(1, Math.max(u, 1e-4), 1); s1.visible = u > 0;
|
|
540
|
+
s2.position.y = -1.3 * u; s2.scale.set(1, Math.max(u2, 1e-4), 1); s2.visible = u2 > 0;
|
|
541
|
+
s3.position.y = -1.3 * u - 0.9 * u2; s3.scale.set(1, Math.max(u3, 1e-4), 1); s3.visible = u3 > 0;
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
// --- road markings (dashes along the loop, drawn on as a sweep)
|
|
545
|
+
const roadLoop = loopPath(3.5, 0.5);
|
|
546
|
+
const dashGeo = new THREE.BoxGeometry(0.2, 0.012, 0.045).translate(0, 0.006, 0);
|
|
547
|
+
const nD = 44;
|
|
548
|
+
const dashIM = new THREE.InstancedMesh(dashGeo, std(PAL.mark, { rough: 0.6 }), nD);
|
|
549
|
+
dashIM.receiveShadow = true; dashIM.frustumCulled = false; island.add(dashIM);
|
|
550
|
+
onUpdate((T) => {
|
|
551
|
+
for (let k = 0; k < nD; k++) {
|
|
552
|
+
const s = (k + 0.5) / nD * roadLoop.L;
|
|
553
|
+
const p = roadLoop.at(s);
|
|
554
|
+
const u = A.easeOutCubic(A.inv(cues.roads + k * 0.008, cues.roads + k * 0.008 + 0.18, T));
|
|
555
|
+
_q.setFromAxisAngle(new THREE.Vector3(0, 1, 0), p.ang + Math.PI / 2);
|
|
556
|
+
_s.set(u, u > 0.001 ? 1 : 0, u > 0.001 ? 1 : 0); _p.set(p.x, 0.001, p.z);
|
|
557
|
+
_m.compose(_p, _q, _s); dashIM.setMatrixAt(k, _m);
|
|
558
|
+
}
|
|
559
|
+
dashIM.instanceMatrix.needsUpdate = true;
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
// ================================================================= growth helpers
|
|
563
|
+
const WALL = (tau) => A.spring(tau, 14.8, 0.61, 0); // round 2: peak at ~8 frames, 9 % overshoot
|
|
564
|
+
let TGv = 0; // growth time: 90 deg shutter for the scale channels (set in update)
|
|
565
|
+
function growState(T, t0) {
|
|
566
|
+
const tau = T - t0;
|
|
567
|
+
// base slab drops in 0.1 s before t0
|
|
568
|
+
let bd;
|
|
569
|
+
if (tau < -0.1) bd = { y: 0, sy: 1, sxz: 0, vis: 0 };
|
|
570
|
+
else if (tau < 0) { const u = (tau + 0.1) / 0.1; bd = { y: 0, sy: 1, sxz: 0.12 + 0.88 * A.easeOutCubic(u), vis: 1 }; }
|
|
571
|
+
else bd = A.drop(tau, 0.1, 0.3, 0, 0.03);
|
|
572
|
+
const wall = tau > 1 / 30 ? WALL(tau - 1 / 30) : 0;
|
|
573
|
+
const rl = cues.roof_lag;
|
|
574
|
+
const rd = A.drop(tau - rl, 0.1, 0.3, 0, 0.025);
|
|
575
|
+
return { bd, wall, rd, tau };
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// generic building: sections [{w,d,h,col,style,colW,floorH}], roof {col, h, details}
|
|
579
|
+
const buildings = [];
|
|
580
|
+
const flashDelay = (x, z) => PULSE_T + Math.max(Math.max(Math.abs(x), Math.abs(z)) - PULSE_R0, 0) / PULSE_V;
|
|
581
|
+
function makeBuilding(def) {
|
|
582
|
+
const g = new THREE.Group();
|
|
583
|
+
g.position.set(def.x, 0, def.z);
|
|
584
|
+
island.add(g);
|
|
585
|
+
const base = mesh(rbox(def.bw, 0.07, def.bd, 0.03), std(def.baseCol || '#E6E1F7', { rough: 0.8 }));
|
|
586
|
+
g.add(base);
|
|
587
|
+
const walls = new THREE.Group(); walls.position.y = 0.07; g.add(walls);
|
|
588
|
+
let y = 0; const mats = []; const clayMats = [];
|
|
589
|
+
for (const s of def.sections) {
|
|
590
|
+
let m;
|
|
591
|
+
if (s.shape === 'cyl') {
|
|
592
|
+
m = mesh(cyl(s.w / 2, s.w / 2, s.h, 40), winMat(U, s.col, [s.w, s.h, s.w], 1, { floorH: s.floorH ?? 0.28, seed: def.seed + y, litFrac: 0.45, baseY: y, cyl: 1, grow: def.t0 }));
|
|
593
|
+
} else {
|
|
594
|
+
m = mesh(rbox(s.w, s.h, s.d, s.r ?? 0.05), winMat(U, s.col, [s.w, s.h, s.d], s.style ?? 0, { floorH: s.floorH ?? 0.3, colW: s.colW ?? 0.22, seed: def.seed + y, litFrac: s.litFrac ?? 0.7, baseY: y, grow: def.t0 }));
|
|
595
|
+
}
|
|
596
|
+
m.position.y = y;
|
|
597
|
+
mats.push(m.material);
|
|
598
|
+
walls.add(m);
|
|
599
|
+
if (s.ledge) {
|
|
600
|
+
const lm = clayMat(U, s.ledge, { rough: 0.7, delay: (y + s.h) / 9 }); clayMats.push(lm);
|
|
601
|
+
const l = mesh(rbox(s.w + 0.06, 0.05, (s.d ?? s.w) + 0.06, 0.02), lm);
|
|
602
|
+
l.position.y = y + s.h - 0.02; walls.add(l);
|
|
603
|
+
}
|
|
604
|
+
y += s.h;
|
|
605
|
+
}
|
|
606
|
+
const H = y;
|
|
607
|
+
const roof = new THREE.Group(); g.add(roof);
|
|
608
|
+
const top = def.sections[def.sections.length - 1];
|
|
609
|
+
if (def.roofShape !== 'none') {
|
|
610
|
+
const rm = clayMat(U, def.roofCol, { rough: 0.6, delay: H / 9 }); clayMats.push(rm);
|
|
611
|
+
if (top.shape === 'cyl') roof.add(mesh(cyl(top.w / 2 + 0.04, top.w / 2 + 0.04, 0.08, 40), rm));
|
|
612
|
+
else roof.add(mesh(rbox(top.w + 0.06, 0.08, top.d + 0.06, 0.035), rm));
|
|
613
|
+
}
|
|
614
|
+
const details = new THREE.Group(); details.position.y = 0.08; roof.add(details);
|
|
615
|
+
(def.details || []).forEach((d) => details.add(d));
|
|
616
|
+
const lit0 = def.t0 + 0.42;
|
|
617
|
+
for (const m of mats) { m.userData.u.uLit0.value = lit0; m.userData.u.uFlash.value = flashDelay(def.x, def.z); }
|
|
618
|
+
for (const m of clayMats) m.userData.u.uFlash.value = flashDelay(def.x, def.z);
|
|
619
|
+
const b = { ...def, g, base, walls, roof, details, H, mats };
|
|
620
|
+
buildings.push(b);
|
|
621
|
+
return b;
|
|
622
|
+
}
|
|
623
|
+
onUpdate((T) => {
|
|
624
|
+
for (const b of buildings) {
|
|
625
|
+
if (b.custom) continue;
|
|
626
|
+
const st = growState(TGv, b.t0);
|
|
627
|
+
const vis = st.bd.vis;
|
|
628
|
+
b.g.visible = !!vis;
|
|
629
|
+
if (!vis) continue;
|
|
630
|
+
b.base.position.y = st.bd.y; b.base.scale.set(st.bd.sxz, st.bd.sy, st.bd.sxz);
|
|
631
|
+
b.walls.visible = st.wall > 0.002;
|
|
632
|
+
const w = Math.max(st.wall, 1e-4);
|
|
633
|
+
const pulse = pulseBump(b.x, b.z, T) * 0.5;
|
|
634
|
+
b.walls.scale.set(1 - 0.06 * Math.max(0, st.wall - 1), w * (1 + pulse), 1 - 0.06 * Math.max(0, st.wall - 1));
|
|
635
|
+
b.walls.visible = st.wall > 0.002;
|
|
636
|
+
b.walls.position.y = 0.07 + st.bd.y;
|
|
637
|
+
const topY = 0.07 + b.H * w * (1 + pulse);
|
|
638
|
+
b.roof.visible = st.rd.vis > 0;
|
|
639
|
+
b.roof.position.y = topY + st.rd.y;
|
|
640
|
+
b.roof.scale.set(st.rd.sxz, st.rd.sy, st.rd.sxz);
|
|
641
|
+
// rooftop details pop after the cap
|
|
642
|
+
const dp = A.spring(TGv - b.t0 - cues.roof_lag - 0.05, 24, 0.5, 10);
|
|
643
|
+
b.details.scale.setScalar(Math.max(dp, 1e-4)); b.details.visible = dp > 0.001;
|
|
644
|
+
}
|
|
645
|
+
});
|
|
646
|
+
|
|
647
|
+
// rooftop detail factories
|
|
648
|
+
const acUnit = (x, z, s = 1) => { const m = mesh(rbox(0.16 * s, 0.09 * s, 0.12 * s, 0.025), std('#E7E3F6', { rough: 0.6 })); m.position.set(x, 0, z); return m; };
|
|
649
|
+
const mast = (x, z, h = 0.5) => {
|
|
650
|
+
const g = new THREE.Group(); g.position.set(x, 0, z);
|
|
651
|
+
g.add(mesh(cyl(0.012, 0.018, h, 8), std('#E9E6F7', { rough: 0.5 })));
|
|
652
|
+
const l = mesh(new THREE.SphereGeometry(0.03, 12, 8), std('#FF7A6A', { emissive: '#FF6B5B', ei: 2.2 }), false);
|
|
653
|
+
l.position.y = h; g.add(l);
|
|
654
|
+
return g;
|
|
655
|
+
};
|
|
656
|
+
const garden = (w, d) => {
|
|
657
|
+
const g = new THREE.Group();
|
|
658
|
+
g.add(mesh(rbox(w, 0.04, d, 0.015), std(PAL.grassDeep, { rough: 0.9 })));
|
|
659
|
+
for (let k = 0; k < 3; k++) {
|
|
660
|
+
const s = mesh(new THREE.SphereGeometry(0.07, 14, 10), std(PAL.leaf[k % 4], { rough: 0.9 }));
|
|
661
|
+
s.position.set((k - 1) * w * 0.28, 0.08, (k % 2 ? 0.1 : -0.08) * d); g.add(s);
|
|
662
|
+
}
|
|
663
|
+
return g;
|
|
664
|
+
};
|
|
665
|
+
const solarRoof = (w, d) => {
|
|
666
|
+
const g = new THREE.Group();
|
|
667
|
+
for (let a = 0; a < 2; a++) for (let c = 0; c < 2; c++) {
|
|
668
|
+
const p = mesh(rbox(w * 0.4, 0.02, d * 0.36, 0.008), std('#5E7BD6', { rough: 0.3, env: 0.9 }));
|
|
669
|
+
p.position.set((a - 0.5) * w * 0.46, 0.06, (c - 0.5) * d * 0.44); p.rotation.x = -0.35; g.add(p);
|
|
670
|
+
}
|
|
671
|
+
return g;
|
|
672
|
+
};
|
|
673
|
+
const helipad = (r) => {
|
|
674
|
+
const g = new THREE.Group();
|
|
675
|
+
g.add(mesh(cyl(r, r, 0.03, 40), std('#6E6AB0', { rough: 0.7 })));
|
|
676
|
+
const ring = mesh(new THREE.TorusGeometry(r * 0.72, 0.012, 6, 48), std('#FFFFFF', { rough: 0.5 }), false);
|
|
677
|
+
ring.rotation.x = Math.PI / 2; ring.position.y = 0.032; g.add(ring);
|
|
678
|
+
const hb1 = mesh(new THREE.BoxGeometry(0.03, 0.01, 0.2), std('#FFFFFF'), false); hb1.position.set(-0.06, 0.035, 0);
|
|
679
|
+
const hb2 = hb1.clone(); hb2.position.x = 0.06;
|
|
680
|
+
const hb3 = mesh(new THREE.BoxGeometry(0.12, 0.01, 0.03), std('#FFFFFF'), false); hb3.position.y = 0.035;
|
|
681
|
+
g.add(hb1, hb2, hb3);
|
|
682
|
+
return g;
|
|
683
|
+
};
|
|
684
|
+
|
|
685
|
+
// ================================================================= DOWNTOWN
|
|
686
|
+
const dt = cues.downtown;
|
|
687
|
+
const lot = (i0, j0, wi, dj) => ({ x: tx(i0) + (wi - 1) / 2, z: tz(j0) + (dj - 1) / 2, fw: wi, fd: dj });
|
|
688
|
+
const defs = [];
|
|
689
|
+
const add = (id, l, o) => defs.push({ id, t0: dt[id], seed: defs.length * 3.1, ...l, bw: l.fw - 0.1, bd: l.fd - 0.1, ...o });
|
|
690
|
+
// A: stepped landmark, back corner
|
|
691
|
+
add('A', lot(3, 3, 2, 2), {
|
|
692
|
+
sections: [
|
|
693
|
+
{ w: 1.62, d: 1.62, h: 1.25, col: PAL.white, ledge: PAL.lilac, colW: 0.2 },
|
|
694
|
+
{ w: 1.28, d: 1.28, h: 0.7, col: '#EAF3FF', style: 1, floorH: 0.25, ledge: PAL.white, litFrac: 0.45 },
|
|
695
|
+
{ w: 0.9, d: 0.9, h: 0.45, col: PAL.white, colW: 0.18 },
|
|
696
|
+
], roofCol: PAL.deepSky, details: [mast(0.15, -0.1, 0.35), acUnit(-0.2, 0.18)],
|
|
697
|
+
});
|
|
698
|
+
add('B', lot(5, 3, 1, 1), { sections: [{ w: 0.78, d: 0.78, h: 2.6, col: '#EAF3FF', style: 1, floorH: 0.24, litFrac: 0.45 }], roofCol: PAL.white, details: [acUnit(0.12, 0.1), mast(-0.18, -0.15, 0.4)] });
|
|
699
|
+
add('C', lot(6, 3, 1, 1), { sections: [{ w: 0.78, d: 0.78, h: 2.6, col: PAL.peach }], roofCol: PAL.coral, details: [garden(0.5, 0.5)] });
|
|
700
|
+
add('D', lot(7, 3, 2, 2), {
|
|
701
|
+
sections: [{ shape: 'cyl', w: 1.5, h: 1.2, col: PAL.sky }, { shape: 'cyl', w: 1.16, h: 2.4, col: '#EAF3FF' }],
|
|
702
|
+
roofCol: PAL.white, details: [helipad(0.34)],
|
|
703
|
+
});
|
|
704
|
+
add('E', lot(3, 5, 1, 1), { sections: [{ w: 0.78, d: 0.78, h: 2.5, col: PAL.peach, ledge: PAL.white }], roofCol: PAL.white, details: [solarRoof(0.66, 0.66)] });
|
|
705
|
+
add('F', lot(3, 6, 1, 1), { sections: [{ w: 0.78, d: 0.78, h: 1.9, col: PAL.mint }], roofCol: PAL.white, details: [acUnit(0, 0), acUnit(0.2, -0.18, 0.8)] });
|
|
706
|
+
add('G', lot(8, 5, 1, 1), { sections: [{ w: 0.78, d: 0.78, h: 2.3, col: PAL.mint, ledge: PAL.peach }], roofCol: PAL.coral, details: [garden(0.52, 0.52)] });
|
|
707
|
+
add('H', lot(8, 6, 1, 1), { sections: [{ w: 0.78, d: 0.78, h: 1.7, col: PAL.butter }], roofCol: PAL.white, details: [solarRoof(0.66, 0.66)] });
|
|
708
|
+
add('I', lot(3, 7, 2, 2), {
|
|
709
|
+
sections: [{ w: 1.62, d: 1.62, h: 1.25, col: PAL.white, colW: 0.24, ledge: PAL.peach }, { w: 1.1, d: 1.1, h: 0.6, col: PAL.lilac }],
|
|
710
|
+
roofCol: PAL.peach, details: [garden(0.8, 0.8)],
|
|
711
|
+
});
|
|
712
|
+
add('J', lot(5, 8, 1, 1), { sections: [{ w: 0.78, d: 0.78, h: 1.15, col: PAL.sky, floorH: 0.26 }], roofCol: PAL.white, details: [acUnit(0.1, 0.1)] });
|
|
713
|
+
add('K', lot(6, 8, 1, 1), { sections: [{ w: 0.78, d: 0.78, h: 1.45, col: PAL.peach }], roofCol: PAL.white, details: [acUnit(-0.1, 0.12), acUnit(0.15, -0.12, 0.8)] });
|
|
714
|
+
// L: data center + dome
|
|
715
|
+
const dome = mesh(new THREE.SphereGeometry(0.52, 40, 20, 0, Math.PI * 2, 0, Math.PI / 2), std(PAL.white, { rough: 0.35, env: 0.8 }));
|
|
716
|
+
dome.position.set(0.25, 0, 0.25);
|
|
717
|
+
const domeRing = mesh(new THREE.TorusGeometry(0.52, 0.03, 8, 48), std(PAL.deepSky, { rough: 0.5 }));
|
|
718
|
+
domeRing.rotation.x = Math.PI / 2; domeRing.position.set(0.25, 0.01, 0.25);
|
|
719
|
+
add('L', lot(7, 7, 2, 2), {
|
|
720
|
+
sections: [{ w: 1.66, d: 1.66, h: 0.72, col: PAL.lilac, style: 2, colW: 0.12, floorH: 0.5, litFrac: 0.9 }],
|
|
721
|
+
roofCol: PAL.white, details: [dome, domeRing, acUnit(-0.5, -0.45), acUnit(-0.5, -0.15), acUnit(-0.2, -0.5)],
|
|
722
|
+
});
|
|
723
|
+
defs.forEach((d) => makeBuilding(d));
|
|
724
|
+
|
|
725
|
+
// ================================================================= HOUSES (grow in symmetric pairs)
|
|
726
|
+
const housesTiles = [];
|
|
727
|
+
for (let j = 0; j < N; j++) for (let i = 0; i < N; i++) if (MAP[j][i] === 'h') housesTiles.push([i, j]);
|
|
728
|
+
// order by distance from centre (outermost first), paired
|
|
729
|
+
housesTiles.sort((a, b) => (Math.hypot(tx(b[0]), tz(b[1])) - Math.hypot(tx(a[0]), tz(a[1]))));
|
|
730
|
+
const roofCols = [PAL.coral, PAL.deepSky, PAL.peach, '#B9A8F3'];
|
|
731
|
+
const wallCols = [PAL.white, PAL.butter, PAL.lilac, PAL.mint, PAL.peach, PAL.sky];
|
|
732
|
+
housesTiles.forEach(([i, j], k) => {
|
|
733
|
+
const t0 = cues.houses[`${i},${j}`];
|
|
734
|
+
const rot = (A.hash(i * 3 + j * 11) > 0.5) ? 0 : Math.PI / 2;
|
|
735
|
+
const w = 0.52, d = 0.42, h = 0.36;
|
|
736
|
+
const g = new THREE.Group();
|
|
737
|
+
g.position.set(tx(i) + (A.hash(i + j) - 0.5) * 0.12, 0, tz(j) + (A.hash(i * j + 3) - 0.5) * 0.12);
|
|
738
|
+
island.add(g);
|
|
739
|
+
const base = mesh(rbox(0.66, 0.05, 0.56, 0.02), std(PAL.plaza, { rough: 0.8 }));
|
|
740
|
+
base.rotation.y = rot; g.add(base);
|
|
741
|
+
const walls = new THREE.Group(); walls.position.y = 0.05; walls.rotation.y = rot; g.add(walls);
|
|
742
|
+
const wm = winMat(U, wallCols[k % wallCols.length], [w, h, d], 0, { floorH: 0.16, colW: 0.17, seed: k * 1.7, litFrac: 0.8, grow: t0 });
|
|
743
|
+
walls.add(mesh(rbox(w, h, d, 0.03), wm));
|
|
744
|
+
// gable roof
|
|
745
|
+
const sh = new THREE.Shape(); sh.moveTo(-d / 2 - 0.05, 0); sh.lineTo(d / 2 + 0.05, 0); sh.lineTo(0, 0.24); sh.closePath();
|
|
746
|
+
const rg = new THREE.ExtrudeGeometry(sh, { depth: w + 0.08, bevelEnabled: true, bevelThickness: 0.012, bevelSize: 0.012, bevelSegments: 2 });
|
|
747
|
+
rg.translate(0, 0, -(w + 0.08) / 2); rg.rotateY(Math.PI / 2);
|
|
748
|
+
const roof = new THREE.Group(); roof.rotation.y = rot; g.add(roof);
|
|
749
|
+
const hrm = clayMat(U, roofCols[k % 4], { rough: 0.6, delay: 0.4 / 9 });
|
|
750
|
+
roof.add(mesh(rg, hrm));
|
|
751
|
+
const chim = mesh(rbox(0.06, 0.12, 0.06, 0.01), std(PAL.white)); chim.position.set(0.12, 0.1, 0.06); roof.add(chim);
|
|
752
|
+
wm.userData.u.uLit0.value = t0 + 0.4; wm.userData.u.uFlash.value = flashDelay(g.position.x, g.position.z);
|
|
753
|
+
hrm.userData.u.uFlash.value = flashDelay(g.position.x, g.position.z);
|
|
754
|
+
buildings.push({ id: 'h' + k, x: g.position.x, z: g.position.z, t0, g, base, walls, roof, details: new THREE.Group(), H: h, mats: [wm], house: true });
|
|
755
|
+
});
|
|
756
|
+
|
|
757
|
+
// ================================================================= TREES (instanced, pop in)
|
|
758
|
+
const treeSpots = [];
|
|
759
|
+
const TR = A.rng(99);
|
|
760
|
+
for (let j = 0; j < N; j++) for (let i = 0; i < N; i++) {
|
|
761
|
+
const ch = MAP[j][i]; const x = tx(i), z = tz(j);
|
|
762
|
+
if (ch === 'f') {
|
|
763
|
+
const n = 3 + Math.floor(TR() * 2);
|
|
764
|
+
for (let k = 0; k < n; k++) treeSpots.push({ x: x + (TR() - 0.5) * 0.62, z: z + (TR() - 0.5) * 0.62, s: 0.8 + TR() * 0.45, kind: TR() < 0.35 ? 1 : 0 });
|
|
765
|
+
} else if (ch === 'p') {
|
|
766
|
+
TR(); TR(); TR(); // round 2: plaza trees removed so the plaza tiles can flip at the hit (RNG calls kept: other trees stay put)
|
|
767
|
+
} else if (ch === '.' && TR() < 0.55) {
|
|
768
|
+
const n = 1 + Math.floor(TR() * 2);
|
|
769
|
+
for (let k = 0; k < n; k++) treeSpots.push({ x: x + (TR() - 0.5) * 0.6, z: z + (TR() - 0.5) * 0.6, s: 0.65 + TR() * 0.4, kind: TR() < 0.3 ? 1 : 0 });
|
|
770
|
+
} else if (ch === 'h' && TR() < 0.6) {
|
|
771
|
+
treeSpots.push({ x: x + 0.34 * (TR() > 0.5 ? 1 : -1), z: z + 0.34 * (TR() > 0.5 ? 1 : -1), s: 0.6, kind: 0 });
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
treeSpots.forEach((s, k) => {
|
|
775
|
+
s.t0 = front(sxOf(s.x, s.z)) + 0.05 + A.hash(k * 5.3) * 0.12; // pops as the growth front passes
|
|
776
|
+
const r = A.hash(k * 2.1);
|
|
777
|
+
s.col = r < 0.1 ? PAL.blossom : r < 0.18 ? PAL.lilacLeaf : PAL.leaf[k % 4];
|
|
778
|
+
});
|
|
779
|
+
const roundT = treeSpots.filter((s) => s.kind === 0), coneT = treeSpots.filter((s) => s.kind === 1);
|
|
780
|
+
const foliageGeo = new THREE.IcosahedronGeometry(0.16, 3).translate(0, 0.3, 0);
|
|
781
|
+
const coneGeo = mergeGeometries([new THREE.ConeGeometry(0.15, 0.3, 20).translate(0, 0.3, 0), new THREE.ConeGeometry(0.12, 0.24, 20).translate(0, 0.44, 0)]);
|
|
782
|
+
const trunkGeo = cyl(0.022, 0.03, 0.2, 8);
|
|
783
|
+
const leafMat = std('#ffffff', { rough: 0.85, env: 0.3 });
|
|
784
|
+
const mkIM = (geo, list, mat) => { const im = new THREE.InstancedMesh(geo, mat, Math.max(1, list.length)); im.castShadow = true; im.receiveShadow = true; im.frustumCulled = false; island.add(im); return im; };
|
|
785
|
+
const roundIM = mkIM(foliageGeo, roundT, leafMat), coneIM = mkIM(coneGeo, coneT, leafMat);
|
|
786
|
+
const trunkIM = mkIM(trunkGeo, treeSpots, std(PAL.trunk, { rough: 0.9 }));
|
|
787
|
+
roundT.forEach((s, k) => roundIM.setColorAt(k, C(s.col)));
|
|
788
|
+
coneT.forEach((s, k) => coneIM.setColorAt(k, C(s.kind ? PAL.leaf[(k + 2) % 4] : s.col)));
|
|
789
|
+
onUpdate((T) => {
|
|
790
|
+
const place = (im, list) => list.forEach((s, k) => {
|
|
791
|
+
const p = A.spring(T - s.t0, 22, 0.45, 13) * s.s;
|
|
792
|
+
const wob = Math.sin((T - s.t0) * 9) * Math.exp(-(T - s.t0) * 5) * 0.12 * (T > s.t0 ? 1 : 0);
|
|
793
|
+
_q.setFromEuler(new THREE.Euler(wob, 0, wob * 0.6));
|
|
794
|
+
_s.setScalar(Math.max(p, 1e-4)); _p.set(s.x, pulseBump(s.x, s.z, T), s.z);
|
|
795
|
+
_m.compose(_p, _q, _s); im.setMatrixAt(k, _m);
|
|
796
|
+
});
|
|
797
|
+
place(roundIM, roundT); place(coneIM, coneT); place(trunkIM, treeSpots);
|
|
798
|
+
roundIM.instanceMatrix.needsUpdate = coneIM.instanceMatrix.needsUpdate = trunkIM.instanceMatrix.needsUpdate = true;
|
|
799
|
+
});
|
|
800
|
+
|
|
801
|
+
// ================================================================= WIND TURBINES
|
|
802
|
+
const turbines = [];
|
|
803
|
+
const tSpots = [];
|
|
804
|
+
for (let j = 0; j < N; j++) for (let i = 0; i < N; i++) if (MAP[j][i] === 't') tSpots.push([i, j]);
|
|
805
|
+
tSpots.forEach(([i, j], k) => {
|
|
806
|
+
const g = new THREE.Group(); g.position.set(tx(i), 0, tz(j)); island.add(g);
|
|
807
|
+
const H = 2.3 + (k % 2) * 0.35;
|
|
808
|
+
const mast = mesh(cyl(0.035, 0.06, H, 16), std(PAL.white, { rough: 0.5 }));
|
|
809
|
+
g.add(mast);
|
|
810
|
+
const head = new THREE.Group(); head.position.y = H; g.add(head);
|
|
811
|
+
head.rotation.y = Math.PI / 4; // rotor faces the camera diagonal
|
|
812
|
+
head.add(mesh(rbox(0.1, 0.1, 0.24, 0.04).translate(0, -0.05, -0.05), std(PAL.white, { rough: 0.5 })));
|
|
813
|
+
const rotor = new THREE.Group(); rotor.position.z = 0.1; head.add(rotor);
|
|
814
|
+
rotor.add(mesh(new THREE.SphereGeometry(0.055, 16, 12), std(PAL.coral, { rough: 0.5 })));
|
|
815
|
+
for (let b = 0; b < 3; b++) {
|
|
816
|
+
const bl = mesh(rbox(0.07, 0.95, 0.02, 0.01).translate(0, 0.02, 0), std(PAL.white, { rough: 0.45 }));
|
|
817
|
+
bl.rotation.z = b * Math.PI * 2 / 3; rotor.add(bl);
|
|
818
|
+
}
|
|
819
|
+
turbines.push({ g, mast, head, rotor, H, t0: Math.round(front(sxOf(tx(i), tz(j))) / 0.125) * 0.125, ph: k * 0.9 });
|
|
820
|
+
});
|
|
821
|
+
onUpdate((T) => {
|
|
822
|
+
for (const tb of turbines) {
|
|
823
|
+
const tau = T - tb.t0;
|
|
824
|
+
const s = A.spring(tau, 16, 0.6, 5);
|
|
825
|
+
tb.g.visible = tau > 0;
|
|
826
|
+
tb.mast.scale.set(1, Math.max(s, 1e-4), 1);
|
|
827
|
+
tb.head.position.y = tb.H * s;
|
|
828
|
+
const hp = A.spring(tau - 0.25, 20, 0.5, 10);
|
|
829
|
+
tb.head.scale.setScalar(Math.max(hp, 1e-4));
|
|
830
|
+
// spin: accelerates in, then constant (closed form: integral of speed ramp)
|
|
831
|
+
const w = 4.2; const ramp = 0.8; const u = Math.max(0, tau - 0.3);
|
|
832
|
+
const ang = u < ramp ? w * u * u / (2 * ramp) : w * (u - ramp / 2);
|
|
833
|
+
tb.rotor.rotation.z = -ang - tb.ph;
|
|
834
|
+
}
|
|
835
|
+
});
|
|
836
|
+
|
|
837
|
+
// ================================================================= SOLAR FIELD (panels flip up)
|
|
838
|
+
const solar = [];
|
|
839
|
+
for (let j = 0; j < N; j++) for (let i = 0; i < N; i++) if (MAP[j][i] === 's') {
|
|
840
|
+
for (let a = 0; a < 2; a++) for (let c = 0; c < 2; c++) {
|
|
841
|
+
const g = new THREE.Group(); g.position.set(tx(i) + (a - 0.5) * 0.46, 0, tz(j) + (c - 0.5) * 0.44); island.add(g);
|
|
842
|
+
g.add(mesh(cyl(0.015, 0.015, 0.1, 6), std('#DCD7F2')));
|
|
843
|
+
const p = new THREE.Group(); p.position.y = 0.1; g.add(p);
|
|
844
|
+
p.add(mesh(rbox(0.38, 0.025, 0.3, 0.01).translate(0, -0.012, 0), std('#5E7BD6', { rough: 0.25, env: 1.0 })));
|
|
845
|
+
const fr = mesh(rbox(0.4, 0.012, 0.32, 0.006).translate(0, -0.022, 0), std(PAL.white)); p.add(fr);
|
|
846
|
+
solar.push({ g, p, t0: front(sxOf(g.position.x, g.position.z)) + a * 0.04 + c * 0.03 });
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
onUpdate((T) => {
|
|
850
|
+
for (const s of solar) {
|
|
851
|
+
const u = A.spring(T - s.t0, 20, 0.5, 8);
|
|
852
|
+
s.g.visible = T > s.t0; s.g.scale.setScalar(Math.max(Math.min(u * 1.2, 1), 1e-4));
|
|
853
|
+
s.p.rotation.z = -0.5 * u; // tilt toward the sun
|
|
854
|
+
}
|
|
855
|
+
});
|
|
856
|
+
|
|
857
|
+
// ================================================================= POND details (dock + ripples)
|
|
858
|
+
const dock = mesh(rbox(0.9, 0.05, 0.22, 0.02), std('#E9D9CF', { rough: 0.8 }));
|
|
859
|
+
dock.position.set(tx(10) - 0.1, 0.0, tz(3)); island.add(dock);
|
|
860
|
+
onUpdate((T) => { const u = A.spring(T - 2.2, 22, 0.5, 9); dock.visible = T > 2.2; dock.scale.setScalar(Math.max(u, 1e-4)); });
|
|
861
|
+
const rippleMat = new THREE.MeshBasicMaterial({ color: C('#FFFFFF'), transparent: true, opacity: 0.0, depthWrite: false });
|
|
862
|
+
const ripples = [];
|
|
863
|
+
for (let k = 0; k < 3; k++) {
|
|
864
|
+
const r = new THREE.Mesh(new THREE.RingGeometry(0.9, 1.0, 48).rotateX(-Math.PI / 2), rippleMat.clone());
|
|
865
|
+
r.position.set(tx(10) + 0.5, 0.004, tz(2) + 0.6 + k * 0.4); r.layers.set(LAYER_GLOW); island.add(r); ripples.push(r);
|
|
866
|
+
}
|
|
867
|
+
onUpdate((T) => {
|
|
868
|
+
ripples.forEach((r, k) => {
|
|
869
|
+
const ph = ((T * 0.45 + k / 3) % 1);
|
|
870
|
+
const on = A.smooth(A.inv(1.8, 2.3, T));
|
|
871
|
+
r.scale.setScalar(0.08 + ph * 0.32);
|
|
872
|
+
r.material.opacity = 0.5 * (1 - ph) * on;
|
|
873
|
+
});
|
|
874
|
+
});
|
|
875
|
+
|
|
876
|
+
// ================================================================= MONORAIL (pillars chain-grow, beam, train)
|
|
877
|
+
const railOff = -0.42; // over the inner curb
|
|
878
|
+
const rail = loopPath(3.5 + railOff, 0.5 + railOff);
|
|
879
|
+
const RAIL_H = 1.25;
|
|
880
|
+
const pillars = [];
|
|
881
|
+
const nP = 20;
|
|
882
|
+
for (let k = 0; k < nP; k++) {
|
|
883
|
+
const s = k / nP * rail.L;
|
|
884
|
+
const p = rail.at(s);
|
|
885
|
+
const g = new THREE.Group(); g.position.set(p.x, 0, p.z); island.add(g);
|
|
886
|
+
g.add(mesh(cyl(0.035, 0.05, RAIL_H, 14), std('#ECE8FA', { rough: 0.55 })));
|
|
887
|
+
pillars.push({ g, t0: cues.rail + k * 0.022 });
|
|
888
|
+
}
|
|
889
|
+
// beam: build from many short segments so it can be drawn on
|
|
890
|
+
const nB = 96;
|
|
891
|
+
const beamIM = new THREE.InstancedMesh(new RoundedBoxGeometry(1, 0.075, 0.12, 1, 0.025).translate(0, -0.04, 0), std('#E4DFF7', { rough: 0.5 }), nB);
|
|
892
|
+
beamIM.castShadow = true; beamIM.receiveShadow = true; beamIM.frustumCulled = false; island.add(beamIM);
|
|
893
|
+
const segL = rail.L / nB;
|
|
894
|
+
onUpdate((T) => {
|
|
895
|
+
for (const p of pillars) {
|
|
896
|
+
const u = A.spring(T - p.t0, 20, 0.6, 7);
|
|
897
|
+
p.g.visible = T > p.t0; p.g.scale.set(1, Math.max(u, 1e-4), 1);
|
|
898
|
+
}
|
|
899
|
+
for (let k = 0; k < nB; k++) {
|
|
900
|
+
const s = (k + 0.5) * segL;
|
|
901
|
+
const p = rail.at(s);
|
|
902
|
+
const t0 = cues.rail + 0.15 + k * 0.0048;
|
|
903
|
+
const u = A.easeOutCubic(A.inv(t0, t0 + 0.12, T));
|
|
904
|
+
_q.setFromAxisAngle(new THREE.Vector3(0, 1, 0), p.ang - Math.PI / 2);
|
|
905
|
+
_s.set(Math.max(u, 1e-4) * segL * 1.04, u > 0.001 ? 1 : 0, u > 0.001 ? 1 : 0); _p.set(p.x, RAIL_H + (1 - u) * 0.3, p.z);
|
|
906
|
+
_m.compose(_p, _q, _s); beamIM.setMatrixAt(k, _m);
|
|
907
|
+
}
|
|
908
|
+
beamIM.instanceMatrix.needsUpdate = true;
|
|
909
|
+
});
|
|
910
|
+
// train
|
|
911
|
+
const train = [];
|
|
912
|
+
const carLen = 0.78;
|
|
913
|
+
for (let k = 0; k < 3; k++) {
|
|
914
|
+
const g = new THREE.Group(); island.add(g);
|
|
915
|
+
const body = new THREE.Group(); g.add(body);
|
|
916
|
+
body.add(mesh(rbox(carLen, 0.22, 0.24, 0.09).translate(0, 0.02, 0), std(PAL.white, { rough: 0.35, env: 0.8 })));
|
|
917
|
+
const band = mesh(rbox(carLen - 0.1, 0.07, 0.25, 0.03).translate(0, 0.12, 0), std('#6D83DA', { rough: 0.2, env: 1.0, emissive: '#FFE39C', ei: 0 }));
|
|
918
|
+
body.add(band);
|
|
919
|
+
const stripe = mesh(rbox(carLen - 0.04, 0.025, 0.245, 0.01).translate(0, 0.05, 0), std(PAL.coral, { rough: 0.5 }));
|
|
920
|
+
body.add(stripe);
|
|
921
|
+
train.push({ g, body, band, k });
|
|
922
|
+
}
|
|
923
|
+
onUpdate((T) => {
|
|
924
|
+
const t0 = cues.train;
|
|
925
|
+
const tau = T - t0;
|
|
926
|
+
// closed-form motion: ramp to cruise speed v over 0.8 s
|
|
927
|
+
const v = 3.2, ramp = 0.8;
|
|
928
|
+
const u = Math.max(0, tau);
|
|
929
|
+
const dist = u < ramp ? v * u * u / (2 * ramp) : v * (u - ramp / 2);
|
|
930
|
+
const s0 = rail.L * 0.62;
|
|
931
|
+
const appear = A.spring(tau + 0.15, 22, 0.55, 8);
|
|
932
|
+
train.forEach((c) => {
|
|
933
|
+
const s = s0 + dist - c.k * (carLen + 0.05);
|
|
934
|
+
const p = rail.at(s);
|
|
935
|
+
c.g.position.set(p.x, RAIL_H + 0.0, p.z);
|
|
936
|
+
c.g.rotation.y = p.ang - Math.PI / 2;
|
|
937
|
+
c.g.visible = tau > -0.15;
|
|
938
|
+
c.g.scale.setScalar(Math.max(appear, 1e-4));
|
|
939
|
+
c.band.material.emissiveIntensity = T > PULSE_T ? 0.8 : 0;
|
|
940
|
+
});
|
|
941
|
+
});
|
|
942
|
+
|
|
943
|
+
// ================================================================= CARS on the ring road
|
|
944
|
+
const cars = [];
|
|
945
|
+
const carCols = [PAL.coral, PAL.white, PAL.deepSky, PAL.butter, PAL.peach, '#B9A8F3', PAL.white, PAL.mint];
|
|
946
|
+
const lanes = [loopPath(3.5 + 0.2, 0.5 + 0.2), loopPath(3.5 - 0.2, 0.5 - 0.2)];
|
|
947
|
+
for (let k = 0; k < 10; k++) {
|
|
948
|
+
const g = new THREE.Group(); island.add(g);
|
|
949
|
+
const lane = k % 2;
|
|
950
|
+
const col = carCols[k % carCols.length];
|
|
951
|
+
const body = new THREE.Group(); g.add(body);
|
|
952
|
+
body.add(mesh(rbox(0.3, 0.085, 0.15, 0.035).translate(0, 0.02, 0), std(col, { rough: 0.35, env: 0.7 })));
|
|
953
|
+
body.add(mesh(rbox(0.16, 0.07, 0.13, 0.03).translate(-0.02, 0.1, 0), std('#E8EEFF', { rough: 0.15, env: 1.0 })));
|
|
954
|
+
const hl = mesh(rbox(0.012, 0.022, 0.12, 0.006).translate(0.152, 0.055, 0), std('#FFFFFF', { emissive: '#FFF2C4', ei: 1.5 }), false);
|
|
955
|
+
body.add(hl);
|
|
956
|
+
cars.push({ g, body, lane, s0: (k / 10) * lanes[lane].L + A.hash(k) * 1.2, v: (lane ? -1 : 1) * (1.7 + A.hash(k * 3) * 0.5), t0: cues.cars + (k % 5) * 0.05 });
|
|
957
|
+
}
|
|
958
|
+
onUpdate((T) => {
|
|
959
|
+
for (const c of cars) {
|
|
960
|
+
const tau = T - c.t0;
|
|
961
|
+
const ramp = 0.7, u = Math.max(0, tau);
|
|
962
|
+
const dist = u < ramp ? c.v * u * u / (2 * ramp) : c.v * (u - ramp / 2);
|
|
963
|
+
const p = lanes[c.lane].at(c.s0 + dist);
|
|
964
|
+
c.g.position.set(p.x, pulseBump(p.x, p.z, T), p.z);
|
|
965
|
+
c.g.rotation.y = p.ang - Math.PI / 2 + (c.v < 0 ? Math.PI : 0);
|
|
966
|
+
const ap = A.spring(tau, 24, 0.5, 10);
|
|
967
|
+
c.g.visible = tau > 0; c.g.scale.setScalar(Math.max(ap, 1e-4));
|
|
968
|
+
}
|
|
969
|
+
});
|
|
970
|
+
|
|
971
|
+
// ================================================================= CORE TOWER (telescopes up)
|
|
972
|
+
const tw = cues.tower;
|
|
973
|
+
const core = new THREE.Group(); island.add(core);
|
|
974
|
+
const coreBase = mesh(cyl(0.92, 0.95, 0.2, 48), std(PAL.white, { rough: 0.5 }));
|
|
975
|
+
core.add(coreBase);
|
|
976
|
+
const coreBaseRing = mesh(new THREE.TorusGeometry(0.93, 0.025, 8, 64), std('#FFFFFF', { emissive: PAL.data, ei: 0 }), false);
|
|
977
|
+
coreBaseRing.rotation.x = Math.PI / 2; coreBaseRing.position.y = 0.2; core.add(coreBaseRing);
|
|
978
|
+
const SH1 = 2.3, SH2 = 2.0, SH3 = 1.6, SHA = 1.2;
|
|
979
|
+
const coreWin = (seed) => ({ floorH: 0.24, seed, litFrac: 0.0, winOn: '#7DF6F7', winOff: '#4A45A0', emit: 2.5, seam: 1, clay: PAL.ink, clayWin: '#6F69C8', rough: 0.34, env: 0.9, cyl: 1, grow: tw.seg1 });
|
|
980
|
+
const seg1 = mesh(cyl(0.6, 0.66, SH1, 48), winMat(U, PAL.ink, [1.2, SH1, 1.2], 1, coreWin(91)));
|
|
981
|
+
const seg2 = mesh(cyl(0.44, 0.44, SH2, 48), winMat(U, '#34307A', [0.88, SH2, 0.88], 1, { ...coreWin(92), clay: '#34307A', grow: tw.seg2 }));
|
|
982
|
+
const seg3 = mesh(cyl(0.3, 0.3, SH3, 40), winMat(U, PAL.ink, [0.6, SH3, 0.6], 1, { ...coreWin(93), grow: tw.seg3 }));
|
|
983
|
+
const cap3 = mesh(cyl(0.34, 0.34, 0.07, 40), std('#FFFFFF', { rough: 0.4, emissive: PAL.data, ei: 0.6 }));
|
|
984
|
+
const antenna = mesh(cyl(0.02, 0.035, SHA, 12), std(PAL.white, { rough: 0.4 }));
|
|
985
|
+
const beacon = mesh(new THREE.SphereGeometry(0.07, 20, 14), std('#FFFFFF', { emissive: PAL.data, ei: 0 }), false);
|
|
986
|
+
const halo = mesh(new THREE.TorusGeometry(0.78, 0.022, 12, 96), std('#FFFFFF', { rough: 0.3, emissive: PAL.data, ei: 0 }), false);
|
|
987
|
+
halo.rotation.x = Math.PI / 2;
|
|
988
|
+
const halo2 = mesh(new THREE.TorusGeometry(0.62, 0.013, 10, 96), std('#FFFFFF', { rough: 0.3, emissive: PAL.data, ei: 0 }), false);
|
|
989
|
+
halo2.rotation.x = Math.PI / 2;
|
|
990
|
+
core.add(seg1, seg2, seg3, cap3, antenna, beacon, halo, halo2);
|
|
991
|
+
for (const s of [seg1, seg2, seg3]) { s.material.userData.u.uLit0.value = tw.ring; s.material.userData.u.uFlash.value = PULSE_T; }
|
|
992
|
+
onUpdate((T) => {
|
|
993
|
+
const bd = A.drop(T - tw.base, 0.1, 0.35, 0, 0.04);
|
|
994
|
+
core.visible = bd.vis > 0;
|
|
995
|
+
coreBase.position.y = bd.y; coreBase.scale.set(bd.sxz, bd.sy, bd.sxz);
|
|
996
|
+
const s1 = A.spring(T - tw.seg1, 17, 0.6, 5);
|
|
997
|
+
const s2 = A.spring(T - tw.seg2, 17, 0.6, 5);
|
|
998
|
+
const s3 = A.spring(T - tw.seg3, 17, 0.6, 5);
|
|
999
|
+
const sa = A.spring(T - tw.antenna, 18, 0.55, 6);
|
|
1000
|
+
const y0 = 0.2 + bd.y;
|
|
1001
|
+
seg1.position.y = y0; seg1.scale.set(1, Math.max(s1, 1e-4), 1); seg1.visible = s1 > 0.002;
|
|
1002
|
+
const top1 = y0 + SH1 * s1;
|
|
1003
|
+
// segment 2 slides up out of segment 1
|
|
1004
|
+
seg2.position.y = top1 - SH2 + SH2 * s2 - 0.02; seg2.visible = s2 > 0.002 && s1 > 0.9;
|
|
1005
|
+
const top2 = seg2.position.y + SH2;
|
|
1006
|
+
seg3.position.y = top2 - SH3 + SH3 * s3 - 0.02; seg3.visible = s3 > 0.002;
|
|
1007
|
+
const top3 = seg3.position.y + SH3;
|
|
1008
|
+
cap3.position.y = top3; cap3.visible = s3 > 0.2;
|
|
1009
|
+
antenna.position.y = top3 + 0.07 - SHA + SHA * sa; antenna.visible = sa > 0.002;
|
|
1010
|
+
beacon.position.y = top3 + 0.07 + SHA * sa; beacon.visible = sa > 0.3;
|
|
1011
|
+
// halo rings assemble
|
|
1012
|
+
const hr = A.spring(T - tw.ring, 16, 0.5, 6);
|
|
1013
|
+
halo.visible = hr > 0.002; halo.scale.setScalar(Math.max(hr, 1e-4));
|
|
1014
|
+
halo.position.y = top1 + 0.35 + Math.sin(T * 2.2) * 0.04;
|
|
1015
|
+
const hr2 = A.spring(T - tw.ring - 0.1, 16, 0.5, 6);
|
|
1016
|
+
halo2.visible = hr2 > 0.002; halo2.scale.setScalar(Math.max(hr2, 1e-4));
|
|
1017
|
+
halo2.position.y = top2 + 0.25 + Math.sin(T * 2.2 + 1) * 0.04;
|
|
1018
|
+
// ignite at the drop
|
|
1019
|
+
const ig = T >= PULSE_T ? 1 : 0;
|
|
1020
|
+
const fl = ig * Math.exp(-(T - PULSE_T) * 3.0);
|
|
1021
|
+
beacon.material.emissiveIntensity = ig * (2.5 + 6 * fl) * (0.8 + 0.2 * Math.sin(T * 9));
|
|
1022
|
+
halo.material.emissiveIntensity = ig * (1.0 + 2.2 * fl);
|
|
1023
|
+
halo2.material.emissiveIntensity = ig * (0.8 + 1.8 * fl);
|
|
1024
|
+
coreBaseRing.material.emissiveIntensity = ig * (1.2 + 3 * fl);
|
|
1025
|
+
// anticipation: the CORE crouches (-6 % Y, +3 % XZ) 5.90-5.967, holds, then releases into a stretch on the hit
|
|
1026
|
+
const crouch = T < PULSE_T ? A.smooth(A.inv(5.90, 5.967, T)) : 0;
|
|
1027
|
+
const punch = ig * Math.exp(-(T - PULSE_T) * 6) * Math.cos((T - PULSE_T) * 26) * 0.07;
|
|
1028
|
+
core.scale.set(1 + 0.03 * crouch - punch * 0.45, 1 - 0.06 * crouch + punch, 1 + 0.03 * crouch - punch * 0.45);
|
|
1029
|
+
});
|
|
1030
|
+
const coreTop = () => new THREE.Vector3(0, 0.2 + SH1 + 0.35, 0);
|
|
1031
|
+
// reel v5 (read-only helper, unused by the default path): local-space top of the telescoping CORE stack as it is
|
|
1032
|
+
// posed for the last update(T) -- what a ball resting on the tower touches (base -> seg1 -> seg2 -> seg3 cap -> antenna tip).
|
|
1033
|
+
const coreStackTop = () => {
|
|
1034
|
+
let h = coreBase.position.y + 0.2 * coreBase.scale.y;
|
|
1035
|
+
if (seg1.visible) h = Math.max(h, seg1.position.y + SH1 * seg1.scale.y);
|
|
1036
|
+
if (seg2.visible) h = Math.max(h, seg2.position.y + SH2);
|
|
1037
|
+
if (seg3.visible) h = Math.max(h, seg3.position.y + SH3 + (cap3.visible ? 0.07 : 0));
|
|
1038
|
+
if (antenna.visible) h = Math.max(h, beacon.position.y);
|
|
1039
|
+
return h;
|
|
1040
|
+
};
|
|
1041
|
+
|
|
1042
|
+
// ---- ignition: light column + air shockwave + halo nodes
|
|
1043
|
+
const glowMatBase = (col, extra = {}) => new THREE.ShaderMaterial({
|
|
1044
|
+
uniforms: { uI: { value: 0 }, uCol: { value: C(col) }, ...extra },
|
|
1045
|
+
transparent: true, depthWrite: false, blending: THREE.AdditiveBlending, side: THREE.DoubleSide,
|
|
1046
|
+
vertexShader: 'varying vec2 vUv; varying vec3 vN; void main(){ vUv = uv; vN = normalize(normalMatrix * normal); gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.); }',
|
|
1047
|
+
fragmentShader: 'uniform float uI; uniform vec3 uCol; varying vec2 vUv; varying vec3 vN; void main(){ float f = pow(abs(vN.z), 1.6) * pow(1.0 - vUv.y, 1.3); float a = uI * f; gl_FragColor = vec4(uCol * a, a); }',
|
|
1048
|
+
});
|
|
1049
|
+
const beamGeo = new THREE.CylinderGeometry(0.1, 0.16, 16, 32, 1, true).translate(0, 8, 0);
|
|
1050
|
+
const beam = new THREE.Mesh(beamGeo, glowMatBase('#F2FFFF')); beam.layers.set(LAYER_GLOW); beam.frustumCulled = false;
|
|
1051
|
+
const beam2 = new THREE.Mesh(new THREE.CylinderGeometry(0.17, 0.26, 16, 32, 1, true).translate(0, 8, 0), glowMatBase(PAL.data)); beam2.layers.set(LAYER_GLOW); beam2.frustumCulled = false;
|
|
1052
|
+
core.add(beam, beam2);
|
|
1053
|
+
const shockMat = new THREE.MeshBasicMaterial({ color: C('#9FFBFF'), transparent: true, opacity: 0, blending: THREE.AdditiveBlending, depthWrite: false, side: THREE.DoubleSide });
|
|
1054
|
+
const shock = new THREE.Mesh(new THREE.RingGeometry(0.978, 1.0, 128).rotateX(-Math.PI / 2), shockMat); shock.layers.set(LAYER_GLOW); core.add(shock);
|
|
1055
|
+
const shock2 = new THREE.Mesh(new THREE.RingGeometry(0.96, 1.0, 128).rotateX(-Math.PI / 2), shockMat.clone()); shock2.layers.set(LAYER_GLOW); core.add(shock2);
|
|
1056
|
+
const nodes = new THREE.Group(); halo.add(nodes);
|
|
1057
|
+
for (let k = 0; k < 6; k++) {
|
|
1058
|
+
const a = k / 6 * Math.PI * 2;
|
|
1059
|
+
const nd = mesh(new THREE.SphereGeometry(0.06, 14, 10), std('#FFFFFF', { rough: 0.3, emissive: PAL.data, ei: 1.6 }), false);
|
|
1060
|
+
nd.position.set(Math.cos(a) * 0.78, Math.sin(a) * 0.78, 0); nodes.add(nd);
|
|
1061
|
+
}
|
|
1062
|
+
onUpdate((T) => {
|
|
1063
|
+
const u = T - PULSE_T;
|
|
1064
|
+
const env = u < 0 ? 0 : (u < 0.012 ? u / 0.012 : Math.exp(-(u - 0.012) * 5.0));
|
|
1065
|
+
const topY = beacon.position.y;
|
|
1066
|
+
beam.position.y = topY; beam2.position.y = topY;
|
|
1067
|
+
const grow = A.easeOutCubic(A.inv(0, 0.05, u));
|
|
1068
|
+
beam.scale.set(1, Math.max(grow, 1e-3), 1); beam2.scale.set(1, Math.max(grow, 1e-3), 1);
|
|
1069
|
+
beam.material.uniforms.uI.value = env * 2.8 + (u > 0 ? 0.1 : 0);
|
|
1070
|
+
beam2.material.uniforms.uI.value = env * 0.4;
|
|
1071
|
+
beam.visible = beam2.visible = u > 0;
|
|
1072
|
+
const su = A.easeOutCubic(A.inv(0, 0.45, u));
|
|
1073
|
+
shock.visible = u > 0 && u < 0.45;
|
|
1074
|
+
shock.position.y = halo.position.y; shock.scale.setScalar(0.9 + su * 9.5);
|
|
1075
|
+
shock.material.opacity = Math.pow(1 - su, 1.8) * 0.95;
|
|
1076
|
+
const su2 = A.easeOutCubic(A.inv(0.06, 0.55, u));
|
|
1077
|
+
shock2.visible = u > 0.06 && u < 0.55;
|
|
1078
|
+
shock2.position.y = halo2.position.y; shock2.scale.setScalar(0.7 + su2 * 7.0);
|
|
1079
|
+
shock2.material.opacity = Math.pow(1 - su2, 1.8) * 0.55;
|
|
1080
|
+
// halo spin (closed form) + pre-glow before the drop
|
|
1081
|
+
halo.rotation.z = T * 1.2 + A.smooth(A.inv(5.7, 6.1, T)) * 2.5;
|
|
1082
|
+
const pre = A.smooth(A.inv(tw.ring + 0.05, PULSE_T, T));
|
|
1083
|
+
if (T < PULSE_T) { halo.material.emissiveIntensity = pre * 1.1; halo2.material.emissiveIntensity = pre * 0.8; }
|
|
1084
|
+
nodes.visible = halo.visible;
|
|
1085
|
+
// beacon pre-blinks
|
|
1086
|
+
if (T < PULSE_T && T > tw.antenna) {
|
|
1087
|
+
const bl = Math.max(Math.exp(-Math.pow((T - 5.625) * 30, 2)), Math.exp(-Math.pow((T - 5.875) * 30, 2)));
|
|
1088
|
+
beacon.material.emissiveIntensity = bl * 4;
|
|
1089
|
+
}
|
|
1090
|
+
});
|
|
1091
|
+
|
|
1092
|
+
// ================================================================= HERO PULSE (ground light wave)
|
|
1093
|
+
const pulseU = { uTime: U.uTime, uT0: { value: PULSE_T }, uV: { value: PULSE_V }, uCol: { value: C(PAL.data) } };
|
|
1094
|
+
const pulseMat = new THREE.ShaderMaterial({
|
|
1095
|
+
uniforms: pulseU, transparent: true, depthWrite: false, blending: THREE.AdditiveBlending,
|
|
1096
|
+
vertexShader: 'varying vec3 vW; void main(){ vec4 w = modelMatrix * vec4(position,1.); vW = w.xyz; gl_Position = projectionMatrix * viewMatrix * w; }',
|
|
1097
|
+
fragmentShader: /* glsl */`
|
|
1098
|
+
uniform float uTime, uT0, uV; uniform vec3 uCol; varying vec3 vW;
|
|
1099
|
+
void main(){
|
|
1100
|
+
float r = max(abs(vW.x), abs(vW.z));
|
|
1101
|
+
float age = uTime - uT0;
|
|
1102
|
+
float rt = 1.0 + age * uV;
|
|
1103
|
+
float d = r - rt;
|
|
1104
|
+
vec2 g = abs(fract(vW.xz + 0.5) - 0.5);
|
|
1105
|
+
float lw = fwidth(vW.x) * 1.5;
|
|
1106
|
+
float line = max(1.0 - smoothstep(0.018, 0.018 + lw, g.x), 1.0 - smoothstep(0.018, 0.018 + lw, g.y));
|
|
1107
|
+
float band = exp(-d * d * 5.0);
|
|
1108
|
+
float edge = exp(-d * d * 70.0);
|
|
1109
|
+
float after = step(d, 0.0) * exp(d * 0.7) * exp(-age * 1.4);
|
|
1110
|
+
float a = edge * 1.0 + band * 0.08 + line * (band * 2.0 + after * 0.35);
|
|
1111
|
+
a *= step(0.0, age) * (1.0 - smoothstep(5.96, 6.08, r)) * (1.0 - smoothstep(0.7, 1.15, age));
|
|
1112
|
+
gl_FragColor = vec4(uCol * a, a);
|
|
1113
|
+
}`,
|
|
1114
|
+
});
|
|
1115
|
+
const pulse = new THREE.Mesh(new THREE.PlaneGeometry(12.2, 12.2).rotateX(-Math.PI / 2), pulseMat);
|
|
1116
|
+
pulse.position.y = 0.012; pulse.layers.set(LAYER_GLOW); island.add(pulse);
|
|
1117
|
+
onUpdate((T) => { pulse.visible = T > PULSE_T - 0.01 && T < PULSE_T + 1.75; });
|
|
1118
|
+
|
|
1119
|
+
// ================================================================= DATA ARCS (core -> roofs)
|
|
1120
|
+
const arcs = [];
|
|
1121
|
+
const arcMatBase = {
|
|
1122
|
+
transparent: true, depthWrite: false, blending: THREE.AdditiveBlending,
|
|
1123
|
+
vertexShader: 'varying vec2 vUv; void main(){ vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.); }',
|
|
1124
|
+
fragmentShader: /* glsl */`
|
|
1125
|
+
uniform float uTime, uT0, uDur, uSpeed, uPh; uniform vec3 uCol; varying vec2 vUv;
|
|
1126
|
+
void main(){
|
|
1127
|
+
float x = vUv.x;
|
|
1128
|
+
float grow = clamp((uTime - uT0) / uDur, 0.0, 1.0);
|
|
1129
|
+
grow = 1.0 - pow(1.0 - grow, 3.0);
|
|
1130
|
+
if (x > grow) discard;
|
|
1131
|
+
float head = exp(-pow((grow - x) * 14.0, 2.0)) * step(grow, 0.999);
|
|
1132
|
+
float p = fract(x * 3.0 - (uTime - uT0) * uSpeed + uPh);
|
|
1133
|
+
float dash = smoothstep(0.0, 0.08, p) * (1.0 - smoothstep(0.12, 0.3, p));
|
|
1134
|
+
float a = 0.28 + dash * 1.5 + head * 2.6;
|
|
1135
|
+
gl_FragColor = vec4(uCol * a, a);
|
|
1136
|
+
}`,
|
|
1137
|
+
};
|
|
1138
|
+
const arcTargets = buildings.filter((b) => !b.house);
|
|
1139
|
+
arcTargets.forEach((b, k) => {
|
|
1140
|
+
const top = new THREE.Vector3(b.x, 0.07 + b.H + 0.12, b.z);
|
|
1141
|
+
const from = new THREE.Vector3(0, 0.2 + 1.7 + 0.35, 0);
|
|
1142
|
+
const mid = from.clone().lerp(top, 0.5); mid.y = Math.max(from.y, top.y) + 1.2 + A.hash(k) * 0.6;
|
|
1143
|
+
const curve = new THREE.QuadraticBezierCurve3(from, mid, top);
|
|
1144
|
+
const geo = new THREE.TubeGeometry(curve, 72, 0.021, 8, false);
|
|
1145
|
+
const m = new THREE.ShaderMaterial({
|
|
1146
|
+
...arcMatBase,
|
|
1147
|
+
uniforms: { uTime: U.uTime, uT0: { value: PULSE_T + 0.05 + k * 0.035 }, uDur: { value: 0.45 }, uSpeed: { value: 1.3 + A.hash(k * 7) * 0.6 }, uPh: { value: A.hash(k * 3) }, uCol: { value: C(PAL.data) } },
|
|
1148
|
+
});
|
|
1149
|
+
const mm = new THREE.Mesh(geo, m); mm.layers.set(LAYER_GLOW); mm.frustumCulled = false; island.add(mm);
|
|
1150
|
+
// landing ring on the roof
|
|
1151
|
+
const ringM = new THREE.Mesh(new THREE.RingGeometry(0.1, 0.13, 32).rotateX(-Math.PI / 2), new THREE.MeshBasicMaterial({ color: C(PAL.data), transparent: true, opacity: 0, blending: THREE.AdditiveBlending, depthWrite: false }));
|
|
1152
|
+
ringM.position.copy(top).y -= 0.1; ringM.layers.set(LAYER_GLOW); island.add(ringM);
|
|
1153
|
+
arcs.push({ mesh: mm, ring: ringM, t0: m.uniforms.uT0.value, dur: 0.45, b });
|
|
1154
|
+
});
|
|
1155
|
+
onUpdate((T) => {
|
|
1156
|
+
for (const a of arcs) {
|
|
1157
|
+
a.mesh.visible = T > a.t0;
|
|
1158
|
+
const tl = a.t0 + a.dur;
|
|
1159
|
+
const u = T - tl;
|
|
1160
|
+
a.ring.visible = u > 0;
|
|
1161
|
+
if (u > 0) {
|
|
1162
|
+
const ph = (u * 0.9) % 1;
|
|
1163
|
+
a.ring.scale.setScalar(1 + ph * 2.2);
|
|
1164
|
+
a.ring.material.opacity = (1 - ph) * 0.9;
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
});
|
|
1168
|
+
|
|
1169
|
+
// ================================================================= DRONES with packets (hop between roofs)
|
|
1170
|
+
const drones = [];
|
|
1171
|
+
const byId = Object.fromEntries(buildings.filter((b) => !b.house).map((b) => [b.id, b]));
|
|
1172
|
+
const routes = [
|
|
1173
|
+
{ t0: 6.12, hops: ['L', 'G', 'C', 'K', 'E'] },
|
|
1174
|
+
{ t0: 6.28, hops: ['L', 'E', 'J', 'H', 'F'] },
|
|
1175
|
+
{ t0: 6.44, hops: ['I', 'H', 'F', 'G', 'J'] },
|
|
1176
|
+
{ t0: 6.6, hops: ['D', 'K', 'I', 'C', 'H'] },
|
|
1177
|
+
{ t0: 6.76, hops: ['L', 'F', 'G', 'J', 'K'] },
|
|
1178
|
+
];
|
|
1179
|
+
const HOP = 1.05, DWELL = 0.32;
|
|
1180
|
+
routes.forEach((rt, k) => {
|
|
1181
|
+
const g = new THREE.Group(); island.add(g);
|
|
1182
|
+
const body = new THREE.Group(); body.scale.setScalar(1.9); g.add(body);
|
|
1183
|
+
body.add(mesh(rbox(0.14, 0.045, 0.14, 0.02), std(PAL.white, { rough: 0.4 })));
|
|
1184
|
+
for (let a = 0; a < 4; a++) {
|
|
1185
|
+
const ax = (a < 2 ? 1 : -1) * 0.1, az = (a % 2 ? 1 : -1) * 0.1;
|
|
1186
|
+
const arm = mesh(new THREE.BoxGeometry(0.1, 0.012, 0.012), std('#DAD5F0'));
|
|
1187
|
+
arm.position.set(ax / 2, 0.03, az / 2); arm.rotation.y = Math.atan2(-az, ax); body.add(arm);
|
|
1188
|
+
const r = mesh(new THREE.CylinderGeometry(0.055, 0.055, 0.006, 20), std('#CFC8F0', { rough: 0.4 }), false);
|
|
1189
|
+
r.position.set(ax, 0.045, az); body.add(r);
|
|
1190
|
+
}
|
|
1191
|
+
const eye = mesh(new THREE.SphereGeometry(0.02, 10, 8), std('#FFFFFF', { emissive: PAL.data, ei: 2.2 }), false);
|
|
1192
|
+
eye.position.set(0.075, 0.0, 0.0); body.add(eye);
|
|
1193
|
+
const line = mesh(new THREE.CylinderGeometry(0.004, 0.004, 0.1, 4).translate(0, -0.05, 0), std('#8E86C8'), false);
|
|
1194
|
+
body.add(line);
|
|
1195
|
+
const pkt = mesh(rbox(0.1, 0.1, 0.1, 0.022).translate(0, -0.1, 0), std([PAL.coral, PAL.butter, PAL.deepSky, PAL.peach, PAL.mint][k % 5], { rough: 0.6 }));
|
|
1196
|
+
pkt.position.y = -0.1; body.add(pkt);
|
|
1197
|
+
drones.push({ g, body, pkt, line, rt, k });
|
|
1198
|
+
});
|
|
1199
|
+
const roofPt = (id, dy) => { const b = byId[id]; return new THREE.Vector3(b.x, 0.07 + b.H + 0.08 + dy, b.z); };
|
|
1200
|
+
onUpdate((T) => {
|
|
1201
|
+
for (const d of drones) {
|
|
1202
|
+
const tau = T - d.rt.t0;
|
|
1203
|
+
d.g.visible = tau > -0.02;
|
|
1204
|
+
if (!d.g.visible) continue;
|
|
1205
|
+
const cyc = HOP + DWELL;
|
|
1206
|
+
const n = Math.min(d.rt.hops.length - 2, Math.floor(Math.max(0, tau) / cyc));
|
|
1207
|
+
const ph = Math.max(0, tau) - n * cyc;
|
|
1208
|
+
const a = roofPt(d.rt.hops[n], 0.34), b = roofPt(d.rt.hops[n + 1], 0.34);
|
|
1209
|
+
const u = A.easeInOutSine(A.clamp(ph / HOP));
|
|
1210
|
+
const p = a.clone().lerp(b, u);
|
|
1211
|
+
const arch = 0.55 + a.distanceTo(b) * 0.12;
|
|
1212
|
+
p.y += arch * Math.sin(Math.PI * u);
|
|
1213
|
+
// first take-off rises from the roof
|
|
1214
|
+
if (n === 0) p.y -= 0.25 * (1 - A.easeOutCubic(A.inv(0, 0.3, tau)));
|
|
1215
|
+
const dw = Math.max(0, ph - HOP);
|
|
1216
|
+
p.y += Math.sin(dw * 7 + d.k) * 0.025 * Math.min(1, dw * 4) + Math.sin(T * 3.1 + d.k * 2) * 0.012;
|
|
1217
|
+
d.g.position.copy(p);
|
|
1218
|
+
const heading = Math.atan2(b.x - a.x, b.z - a.z);
|
|
1219
|
+
d.g.rotation.y = heading - Math.PI / 2;
|
|
1220
|
+
// bank: pitch forward accelerating, back when braking
|
|
1221
|
+
d.body.rotation.z = -Math.sin(Math.PI * 2 * A.clamp(ph / HOP)) * 0.28 * (ph < HOP ? 1 : 0);
|
|
1222
|
+
// packet: carried during the hop, dropped on arrival, re-picked at the next take-off
|
|
1223
|
+
const dropT = Math.max(0, ph - HOP + 0.02);
|
|
1224
|
+
d.pkt.position.y = -0.1 - Math.min(dropT * dropT * 10, 0.3);
|
|
1225
|
+
d.pkt.visible = ph < HOP + 0.16;
|
|
1226
|
+
const pickup = A.spring(ph, 26, 0.5, 10);
|
|
1227
|
+
d.pkt.scale.setScalar(Math.max(n === 0 ? 1 : pickup, 1e-4));
|
|
1228
|
+
d.g.scale.setScalar(Math.max(A.spring(tau, 22, 0.55, 9), 1e-4));
|
|
1229
|
+
}
|
|
1230
|
+
});
|
|
1231
|
+
|
|
1232
|
+
// ================================================================= CLOUDS (foreground, parallax 1.0)
|
|
1233
|
+
const cloudGeo = (() => {
|
|
1234
|
+
const parts = [[0, 0, 0, 0.55], [0.55, -0.08, 0.1, 0.42], [-0.55, -0.1, -0.05, 0.4], [0.2, 0.25, -0.1, 0.42], [-0.25, 0.18, 0.15, 0.38], [0.9, -0.2, 0, 0.28], [-0.9, -0.22, 0.05, 0.26]];
|
|
1235
|
+
const gs = parts.map(([x, y, z, r]) => new THREE.IcosahedronGeometry(r, 4).translate(x, y, z));
|
|
1236
|
+
const g = mergeGeometries(gs);
|
|
1237
|
+
const pos = g.attributes.position; // flatten the bottom
|
|
1238
|
+
for (let i = 0; i < pos.count; i++) if (pos.getY(i) < -0.2) pos.setY(i, -0.2 + (pos.getY(i) + 0.2) * 0.35);
|
|
1239
|
+
g.computeVertexNormals();
|
|
1240
|
+
return g;
|
|
1241
|
+
})();
|
|
1242
|
+
const cloudMat = new THREE.MeshStandardMaterial({ color: C('#FFFFFF'), roughness: 0.95, envMapIntensity: 0.4, emissive: C('#F1EEFF'), emissiveIntensity: 0.25 });
|
|
1243
|
+
const clouds = [];
|
|
1244
|
+
// authored in camera-plane coordinates (sx right, sy up, world units); depth toward the camera
|
|
1245
|
+
const cloudDefs = opts.clouds;
|
|
1246
|
+
const fg = new THREE.Group(); scene.add(fg);
|
|
1247
|
+
const cR = new THREE.Vector3(1, 0, -1).normalize(), cU = new THREE.Vector3(-1, 2, -1).normalize();
|
|
1248
|
+
cloudDefs.forEach((c) => {
|
|
1249
|
+
const m = new THREE.Mesh(cloudGeo, cloudMat); m.castShadow = false; m.receiveShadow = false;
|
|
1250
|
+
m.scale.setScalar(c.s); fg.add(m); clouds.push({ m, ...c });
|
|
1251
|
+
});
|
|
1252
|
+
onUpdate((T) => {
|
|
1253
|
+
for (const c of clouds) {
|
|
1254
|
+
const sx = c.sx + T * c.v, sy = c.sy + Math.sin(T * 0.7 + c.sx) * 0.06;
|
|
1255
|
+
c.m.position.copy(cR).multiplyScalar(sx).addScaledVector(cU, sy).addScaledVector(DIR, 16);
|
|
1256
|
+
}
|
|
1257
|
+
});
|
|
1258
|
+
|
|
1259
|
+
// ================================================================= BACKGROUND CUBES (parallax 0.6)
|
|
1260
|
+
const bgGroup = new THREE.Group(); scene.add(bgGroup);
|
|
1261
|
+
const bgCubes = [];
|
|
1262
|
+
const BR = A.rng(31);
|
|
1263
|
+
const bgCols = [PAL.lilac, PAL.white, PAL.sky, PAL.peach, PAL.mint];
|
|
1264
|
+
const spots = opts.bgCubes || [[-10, -3, -6], [9, -2, 8], [-8, 1.5, 9], [11, 0.5, -2], [-12, -1, 2], [6, -4, -11], [-3, -4, 12], [13, -3, 5], [-6, 2.5, -12], [2, -5, -13]];
|
|
1265
|
+
spots.forEach((p, k) => {
|
|
1266
|
+
const s = p[3] ?? (0.35 + BR() * 0.5);
|
|
1267
|
+
const m = mesh(rbox(s, s, s, s * 0.18).translate(0, -s / 2, 0), std(p[4] ?? bgCols[k % bgCols.length], { rough: 0.7 }), false);
|
|
1268
|
+
m.receiveShadow = false;
|
|
1269
|
+
bgGroup.add(m); bgCubes.push({ m, p, ph: BR() * 6, t0: 0.3 + BR() * 1.4 });
|
|
1270
|
+
});
|
|
1271
|
+
onUpdate((T) => {
|
|
1272
|
+
for (const c of bgCubes) {
|
|
1273
|
+
const a = A.spring(T - c.t0, 14, 0.6, 3);
|
|
1274
|
+
c.m.visible = T > c.t0;
|
|
1275
|
+
c.m.scale.setScalar(Math.max(a, 1e-4));
|
|
1276
|
+
c.m.position.set(c.p[0], c.p[1] + Math.sin(T * 1.1 + c.ph) * 0.12, c.p[2]);
|
|
1277
|
+
c.m.rotation.y = c.ph + T * 0.15;
|
|
1278
|
+
}
|
|
1279
|
+
});
|
|
1280
|
+
|
|
1281
|
+
// ================================================================= HERO HOP (vertex shader, pure in uTime)
|
|
1282
|
+
const hopU = { uHopT: { value: PULSE_T }, uHopV: { value: PULSE_V } };
|
|
1283
|
+
const HOP_GLSL = `uniform float uTime; uniform float uHopT, uHopV;
|
|
1284
|
+
vec2 hopAt(vec2 o) {
|
|
1285
|
+
float r = max(abs(o.x), abs(o.y));
|
|
1286
|
+
if (r < 1.02 || r > 6.6) return vec2(0.0);
|
|
1287
|
+
float u = uTime - (uHopT + (r - 1.0) / uHopV);
|
|
1288
|
+
if (u < 0.0 || u > 0.6) return vec2(0.0);
|
|
1289
|
+
float y; float q = 0.0;
|
|
1290
|
+
if (u < 0.1) { float a = u / 0.1; y = 1.0 - (1.0 - a) * (1.0 - a); q = -0.03 * sin(3.14159 * a); }
|
|
1291
|
+
else { float b = min((u - 0.1) / 0.1667, 1.0); float x = b - 1.0; y = 1.0 - (1.0 + 2.7 * x * x * x + 1.7 * x * x); }
|
|
1292
|
+
if (u > 0.16) q = 0.04 * sin(3.14159 * clamp((u - 0.16) / 0.26, 0.0, 1.0));
|
|
1293
|
+
return vec2(0.25 * y, q);
|
|
1294
|
+
}
|
|
1295
|
+
`;
|
|
1296
|
+
const HOP_PROJECT = `vec4 mvPosition = vec4( transformed, 1.0 );
|
|
1297
|
+
vec4 hopO = vec4(0.0, 0.0, 0.0, 1.0);
|
|
1298
|
+
#ifdef USE_INSTANCING
|
|
1299
|
+
mvPosition = instanceMatrix * mvPosition; hopO = instanceMatrix * hopO;
|
|
1300
|
+
#endif
|
|
1301
|
+
vec4 hopW = modelMatrix * mvPosition; hopO = modelMatrix * hopO;
|
|
1302
|
+
vec2 hq = hopAt(hopO.xz);
|
|
1303
|
+
hopW.y = hopW.y * (1.0 - hq.y) + hq.x;
|
|
1304
|
+
mvPosition = viewMatrix * hopW;
|
|
1305
|
+
gl_Position = projectionMatrix * mvPosition;`;
|
|
1306
|
+
function hopPatch(m) {
|
|
1307
|
+
if (m.userData.hop) return; m.userData.hop = true;
|
|
1308
|
+
const prev = m.onBeforeCompile;
|
|
1309
|
+
const baseKey = m.customProgramCacheKey.call(m);
|
|
1310
|
+
m.onBeforeCompile = (sh, r) => {
|
|
1311
|
+
prev.call(m, sh, r);
|
|
1312
|
+
sh.uniforms.uTime = U.uTime; sh.uniforms.uHopT = hopU.uHopT; sh.uniforms.uHopV = hopU.uHopV;
|
|
1313
|
+
sh.vertexShader = HOP_GLSL + sh.vertexShader.replace('#include <project_vertex>', HOP_PROJECT);
|
|
1314
|
+
};
|
|
1315
|
+
m.customProgramCacheKey = () => baseKey + '|hop1';
|
|
1316
|
+
m.needsUpdate = true;
|
|
1317
|
+
}
|
|
1318
|
+
{
|
|
1319
|
+
const excluded = new Set();
|
|
1320
|
+
[core, ...drones.map((d) => d.g)].forEach((r) => r.traverse((o) => excluded.add(o)));
|
|
1321
|
+
const incl = new Set();
|
|
1322
|
+
island.traverse((o) => { if (o.isMesh && !excluded.has(o) && o.material && o.material.isMeshStandardMaterial) incl.add(o.material); });
|
|
1323
|
+
excluded.forEach((o) => { if (o.isMesh && incl.has(o.material)) o.material = o.material.clone(); });
|
|
1324
|
+
incl.forEach(hopPatch);
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
// round 2: which tiles flip at the hit: bare grass / plaza / pond tiles with nothing standing on them
|
|
1328
|
+
{
|
|
1329
|
+
const occ = new Set();
|
|
1330
|
+
const mark = (x, z) => occ.add(Math.round(x + 5.5) + ',' + Math.round(z + 5.5));
|
|
1331
|
+
treeSpots.forEach((q) => mark(q.x, q.z));
|
|
1332
|
+
pillars.forEach((q) => mark(q.g.position.x, q.g.position.z));
|
|
1333
|
+
mark(dock.position.x, dock.position.z); mark(dock.position.x - 0.45, dock.position.z); mark(dock.position.x + 0.45, dock.position.z);
|
|
1334
|
+
ripples.forEach((q) => mark(q.position.x, q.position.z));
|
|
1335
|
+
for (const t of tiles) t.flip = '.opw'.includes(t.ch) && !occ.has(t.i + ',' + t.j);
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
// ================================================================= update
|
|
1339
|
+
const pan = new THREE.Vector3();
|
|
1340
|
+
function setCamera(target, zoom) {
|
|
1341
|
+
camera.position.copy(target).addScaledVector(DIR, 120);
|
|
1342
|
+
camera.up.set(0, 1, 0);
|
|
1343
|
+
camera.lookAt(target);
|
|
1344
|
+
camera.zoom = zoom;
|
|
1345
|
+
camera.updateProjectionMatrix();
|
|
1346
|
+
camera.updateMatrixWorld();
|
|
1347
|
+
}
|
|
1348
|
+
// camera path: screen-space pan (sx, sy in world units along camRight / camUp), zoom
|
|
1349
|
+
const camKeys = opts.camKeys;
|
|
1350
|
+
function camAt(T) {
|
|
1351
|
+
const k = camKeys(T);
|
|
1352
|
+
return k;
|
|
1353
|
+
}
|
|
1354
|
+
function update(T, sub = { u: 0, v: 0 }, Tc, Tg) {
|
|
1355
|
+
U.uTime.value = T;
|
|
1356
|
+
TGv = Tg ?? T;
|
|
1357
|
+
const kc = 1 - A.smooth(A.inv(PULSE_T, PULSE_T + 0.4, T));
|
|
1358
|
+
sun.intensity = 2.25 * (1 + 0.2 * kc); sun.color.copy(SUN_H).lerp(SUN_K, kc);
|
|
1359
|
+
hemi.intensity = 0.75 * (1 - 0.3 * kc); hemi.groundColor.copy(HEMI_G).lerp(HEMI_GK, kc);
|
|
1360
|
+
fill.color.copy(FILL_H).lerp(FILL_K, kc); fill.intensity = 0.3 + 0.12 * kc;
|
|
1361
|
+
for (const f of updaters) f(T);
|
|
1362
|
+
// camera
|
|
1363
|
+
const k = camAt(Tc ?? T);
|
|
1364
|
+
const target = new THREE.Vector3().addScaledVector(camRight, k.x).addScaledVector(camUp, k.y);
|
|
1365
|
+
// project target onto the ground plane along DIR so the look-at stays on y=0 (pure translation anyway)
|
|
1366
|
+
setCamera(target, k.zoom);
|
|
1367
|
+
// parallax: fg (clouds) 1.0, city 0.8, bg 0.6 -> offsets relative to the city
|
|
1368
|
+
pan.copy(target);
|
|
1369
|
+
const flat = (v) => v.addScaledVector(DIR, -v.y / DIR.y); // same screen offset, but with y = 0
|
|
1370
|
+
fg.position.copy(pan).multiplyScalar(-0.25);
|
|
1371
|
+
bgGroup.position.copy(flat(pan.clone().multiplyScalar(0.25)));
|
|
1372
|
+
// bg floor offset (moves at 0.6)
|
|
1373
|
+
const pOff = bgGroup.position.clone().negate();
|
|
1374
|
+
bgU.uOff.value.set(pOff.x, pOff.z);
|
|
1375
|
+
bgU.uZoom.value = k.zoom;
|
|
1376
|
+
const invVP = new THREE.Matrix4().multiplyMatrices(camera.matrixWorld, camera.projectionMatrixInverse);
|
|
1377
|
+
bgU.uInvVP.value.copy(invVP);
|
|
1378
|
+
// island floor shadow (projected along the sun)
|
|
1379
|
+
const fy = bgU.uFloorY.value;
|
|
1380
|
+
const sy = -1.5 - fy;
|
|
1381
|
+
bgU.uShadowC.value.set(0.9 - pOff.x, -0.9 - pOff.z);
|
|
1382
|
+
bgU.uShadow.value = A.smooth(A.inv(1.6, 2.4, T));
|
|
1383
|
+
bgU.uVoidK.value = 1; // round 2: lavender void (about 60 % of the final saturation) until the hero wave repaints it
|
|
1384
|
+
bgU.uWaveT.value = PULSE_T;
|
|
1385
|
+
const wc = new THREE.Vector3(0, 1.5, 0).project(camera); bgU.uWaveC.value.set(wc.x, wc.y);
|
|
1386
|
+
// island bob in the end
|
|
1387
|
+
island.position.y = Math.sin(Math.max(0, T - 8.6) * 1.6) * 0.06 * A.smooth(A.inv(8.6, 9.2, T));
|
|
1388
|
+
// sun jitter (soft shadows)
|
|
1389
|
+
const j = new THREE.Vector3(sub.u, 0, sub.v).multiplyScalar(0.045);
|
|
1390
|
+
const L = sunDir0.clone().add(new THREE.Vector3().addScaledVector(camRight, j.x).addScaledVector(new THREE.Vector3(0.62, 0.3, 0.42).normalize(), j.z)).normalize();
|
|
1391
|
+
sun.target.position.set(0, 0, 0);
|
|
1392
|
+
sun.position.copy(L).multiplyScalar(40);
|
|
1393
|
+
sun.target.updateMatrixWorld();
|
|
1394
|
+
sun.updateMatrixWorld();
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
return { hopPatch, scene, camera, update, U, buildings, byId, island, camRight, camUp, DIR, sunDir0, PAL, bgGroup, fg, onUpdate, sun, bgU,
|
|
1398
|
+
v5: { core, beacon, beam, coreStackTop, PULSE_T } };
|
|
1399
|
+
}
|