@biunow/agent-biuart 0.0.0-stage → 0.1.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/bin/server.mjs +575 -0
- package/data/styles.json +533 -0
- package/engine/audit_clip.py +46 -0
- package/engine/clip.html +24 -0
- package/engine/clip.js +104 -0
- package/engine/clip_schema.json +19 -0
- package/engine/clips/t1_3b1b.js +203 -0
- package/engine/clips/t2_keynote_ui.js +347 -0
- package/engine/clips/t3_finance_chart.js +206 -0
- package/engine/clips/y1_kurzgesagt.js +183 -0
- package/engine/clips/y2_vox.js +150 -0
- package/engine/clips/y3_whiteboard.js +209 -0
- package/engine/clips/y4_storytime.js +194 -0
- package/engine/clips/y5_kinetic_type.js +134 -0
- package/engine/compare.py +19 -0
- package/engine/demos/_shared/hero/c1_0.png +0 -0
- package/engine/demos/_shared/hero/c1_1.png +0 -0
- package/engine/demos/_shared/hero/c1_2.png +0 -0
- package/engine/demos/_shared/hero/c3_line.png +0 -0
- package/engine/demos/_shared/hero.js +96 -0
- package/engine/demos/long_scroll/eras.js +23 -0
- package/engine/demos/long_scroll/frames/8bit_act/01.png +0 -0
- package/engine/demos/long_scroll/frames/8bit_act/02.png +0 -0
- package/engine/demos/long_scroll/frames/8bit_act/03.png +0 -0
- package/engine/demos/long_scroll/frames/8bit_act/04.png +0 -0
- package/engine/demos/long_scroll/frames/8bit_act/05.png +0 -0
- package/engine/demos/long_scroll/frames/8bit_act/06.png +0 -0
- package/engine/demos/long_scroll/frames/8bit_act/07.png +0 -0
- package/engine/demos/long_scroll/frames/8bit_act/08.png +0 -0
- package/engine/demos/long_scroll/frames/8bit_act/meta.json +69 -0
- package/engine/demos/long_scroll/frames/8bit_walk/01.png +0 -0
- package/engine/demos/long_scroll/frames/8bit_walk/02.png +0 -0
- package/engine/demos/long_scroll/frames/8bit_walk/03.png +0 -0
- package/engine/demos/long_scroll/frames/8bit_walk/04.png +0 -0
- package/engine/demos/long_scroll/frames/8bit_walk/05.png +0 -0
- package/engine/demos/long_scroll/frames/8bit_walk/06.png +0 -0
- package/engine/demos/long_scroll/frames/8bit_walk/07.png +0 -0
- package/engine/demos/long_scroll/frames/8bit_walk/08.png +0 -0
- package/engine/demos/long_scroll/frames/8bit_walk/meta.json +69 -0
- package/engine/demos/long_scroll/frames/egypt_dodge/01.png +0 -0
- package/engine/demos/long_scroll/frames/egypt_dodge/02.png +0 -0
- package/engine/demos/long_scroll/frames/egypt_dodge/03.png +0 -0
- package/engine/demos/long_scroll/frames/egypt_dodge/04.png +0 -0
- package/engine/demos/long_scroll/frames/egypt_dodge/05.png +0 -0
- package/engine/demos/long_scroll/frames/egypt_dodge/06.png +0 -0
- package/engine/demos/long_scroll/frames/egypt_dodge/07.png +0 -0
- package/engine/demos/long_scroll/frames/egypt_dodge/08.png +0 -0
- package/engine/demos/long_scroll/frames/egypt_dodge/meta.json +69 -0
- package/engine/demos/long_scroll/frames/egypt_walk8/01.png +0 -0
- package/engine/demos/long_scroll/frames/egypt_walk8/02.png +0 -0
- package/engine/demos/long_scroll/frames/egypt_walk8/03.png +0 -0
- package/engine/demos/long_scroll/frames/egypt_walk8/04.png +0 -0
- package/engine/demos/long_scroll/frames/egypt_walk8/05.png +0 -0
- package/engine/demos/long_scroll/frames/egypt_walk8/06.png +0 -0
- package/engine/demos/long_scroll/frames/egypt_walk8/07.png +0 -0
- package/engine/demos/long_scroll/frames/egypt_walk8/08.png +0 -0
- package/engine/demos/long_scroll/frames/egypt_walk8/meta.json +69 -0
- package/engine/demos/long_scroll/frames/monet_throw/01.png +0 -0
- package/engine/demos/long_scroll/frames/monet_throw/02.png +0 -0
- package/engine/demos/long_scroll/frames/monet_throw/03.png +0 -0
- package/engine/demos/long_scroll/frames/monet_throw/04.png +0 -0
- package/engine/demos/long_scroll/frames/monet_throw/05.png +0 -0
- package/engine/demos/long_scroll/frames/monet_throw/06.png +0 -0
- package/engine/demos/long_scroll/frames/monet_throw/07.png +0 -0
- package/engine/demos/long_scroll/frames/monet_throw/08.png +0 -0
- package/engine/demos/long_scroll/frames/monet_throw/meta.json +71 -0
- package/engine/demos/long_scroll/frames/monet_walk/01.png +0 -0
- package/engine/demos/long_scroll/frames/monet_walk/02.png +0 -0
- package/engine/demos/long_scroll/frames/monet_walk/03.png +0 -0
- package/engine/demos/long_scroll/frames/monet_walk/04.png +0 -0
- package/engine/demos/long_scroll/frames/monet_walk/05.png +0 -0
- package/engine/demos/long_scroll/frames/monet_walk/06.png +0 -0
- package/engine/demos/long_scroll/frames/monet_walk/07.png +0 -0
- package/engine/demos/long_scroll/frames/monet_walk/08.png +0 -0
- package/engine/demos/long_scroll/frames/monet_walk/meta.json +71 -0
- package/engine/demos/long_scroll/lib/acts.js +87 -0
- package/engine/demos/long_scroll/lib/crossing.js +463 -0
- package/engine/demos/long_scroll/lib/world.js +143 -0
- package/engine/demos/long_scroll/lib/xing2_ding.js +42 -0
- package/engine/demos/long_scroll/lib/xing2_jia.js +114 -0
- package/engine/demos/long_scroll/lib/xing2_yi.js +133 -0
- package/engine/demos/long_scroll/segments/02_egypt.js +234 -0
- package/engine/demos/long_scroll/segments/14_8bit.js +118 -0
- package/engine/demos/long_scroll/segments/28_monet.js +86 -0
- package/engine/demos/t1_3b1b/eras.js +11 -0
- package/engine/demos/t1_3b1b/t1_s1.js +101 -0
- package/engine/demos/t1_3b1b/t1_s2.js +74 -0
- package/engine/demos/t1_3b1b/t1_s3.js +65 -0
- package/engine/demos/t2_keynote_ui/eras.js +10 -0
- package/engine/demos/t2_keynote_ui/t2_s1.js +57 -0
- package/engine/demos/t2_keynote_ui/t2_s2.js +70 -0
- package/engine/demos/t2_keynote_ui/t2_s3.js +61 -0
- package/engine/demos/t3_finance_chart/eras.js +11 -0
- package/engine/demos/t3_finance_chart/t3_s1.js +48 -0
- package/engine/demos/t3_finance_chart/t3_s2.js +42 -0
- package/engine/demos/t3_finance_chart/t3_s3.js +51 -0
- package/engine/demos/y1_kurzgesagt/eras.js +10 -0
- package/engine/demos/y1_kurzgesagt/kurzgesagt.js +347 -0
- package/engine/demos/y2_vox/eras.js +10 -0
- package/engine/demos/y2_vox/vox.js +240 -0
- package/engine/demos/y3_whiteboard/eras.js +10 -0
- package/engine/demos/y3_whiteboard/whiteboard.js +162 -0
- package/engine/demos/y4_storytime/eras.js +14 -0
- package/engine/demos/y4_storytime/s1_desk.js +18 -0
- package/engine/demos/y4_storytime/s2_screen.js +82 -0
- package/engine/demos/y4_storytime/s3_react.js +39 -0
- package/engine/demos/y4_storytime/s4_wide.js +16 -0
- package/engine/demos/y4_storytime/s5_close.js +18 -0
- package/engine/demos/y4_storytime/story.js +25 -0
- package/engine/demos/y4_storytime/vo_env.js +2 -0
- package/engine/demos/y4_storytime/world.js +73 -0
- package/engine/demos/y5_kinetic_type/eras.js +16 -0
- package/engine/demos/y5_kinetic_type/k1_title.js +38 -0
- package/engine/demos/y5_kinetic_type/k2_complete.js +55 -0
- package/engine/demos/y5_kinetic_type/k3_chat.js +47 -0
- package/engine/demos/y5_kinetic_type/k4_agent.js +49 -0
- package/engine/demos/y5_kinetic_type/kinetic.js +52 -0
- package/engine/engine.js +277 -0
- package/engine/eras.js +60 -0
- package/engine/eras_gallery.js +115 -0
- package/engine/eras_style_studies.js +20 -0
- package/engine/examples/assets/K/347/272/277/345/233/276_/346/250/252/345/261/217.png +0 -0
- package/engine/examples/assets//347/273/210/347/253/257_for/345/276/252/347/216/257/346/261/202/345/222/214.png +0 -0
- package/engine/examples/assets//347/273/210/347/253/257_sum/346/261/202/345/222/214.png +0 -0
- package/engine/examples/assets//350/220/245/346/224/266/345/233/276_/347/253/226/345/261/217.png +0 -0
- package/engine/examples/assets//350/264/242/347/273/217/345/233/276/350/241/250_/346/250/252/345/261/217.png +0 -0
- package/engine/examples/t1_3b1b.json +19 -0
- package/engine/examples/t2_fullbleed_pulse.json +15 -0
- package/engine/examples/t2_keynote_ui.json +15 -0
- package/engine/examples/t3_finance_chart.json +29 -0
- package/engine/examples/y1_kurzgesagt.json +16 -0
- package/engine/examples/y2_vox.json +14 -0
- package/engine/examples/y3_whiteboard.json +17 -0
- package/engine/examples/y4_storytime.json +52 -0
- package/engine/examples/y5_kinetic_type.json +14 -0
- package/engine/index.html +42 -0
- package/engine/lib/THREE_LICENSE.txt +21 -0
- package/engine/lib/brush.js +149 -0
- package/engine/lib/camera.js +99 -0
- package/engine/lib/chart.js +152 -0
- package/engine/lib/collage.js +97 -0
- package/engine/lib/diagram.js +264 -0
- package/engine/lib/finish.js +70 -0
- package/engine/lib/fonts/AlfaSlabOne-400.woff +0 -0
- package/engine/lib/fonts/Anton-400.woff +0 -0
- package/engine/lib/fonts/ArchivoBlack-400.woff +0 -0
- package/engine/lib/fonts/Bangers-400.woff +0 -0
- package/engine/lib/fonts/Caveat-600.woff +0 -0
- package/engine/lib/fonts/Cinzel-400.woff +0 -0
- package/engine/lib/fonts/Cinzel-700.woff +0 -0
- package/engine/lib/fonts/CormorantGaramond-500.woff +0 -0
- package/engine/lib/fonts/CormorantGaramond-500i.woff +0 -0
- package/engine/lib/fonts/CormorantGaramond-700.woff +0 -0
- package/engine/lib/fonts/EBGaramond-400.woff +0 -0
- package/engine/lib/fonts/EBGaramond-400i.woff +0 -0
- package/engine/lib/fonts/EBGaramond-600.woff +0 -0
- package/engine/lib/fonts/Exo2-800i.woff +0 -0
- package/engine/lib/fonts/GochiHand-400.woff +0 -0
- package/engine/lib/fonts/IMFellEnglish-400.woff +0 -0
- package/engine/lib/fonts/IMFellEnglish-400i.woff +0 -0
- package/engine/lib/fonts/Inter-var.woff +0 -0
- package/engine/lib/fonts/JosefinSans-700.woff +0 -0
- package/engine/lib/fonts/Kalam-700.woff +0 -0
- package/engine/lib/fonts/LICENSES.md +40 -0
- package/engine/lib/fonts/LXGWWenKai-500.woff +0 -0
- package/engine/lib/fonts/LibreBaskerville-400.woff +0 -0
- package/engine/lib/fonts/LibreBaskerville-700.woff +0 -0
- package/engine/lib/fonts/LilitaOne-400.woff +0 -0
- package/engine/lib/fonts/Marcellus-400.woff +0 -0
- package/engine/lib/fonts/NotoSansSC-500.woff +0 -0
- package/engine/lib/fonts/NotoSansSC-700.woff +0 -0
- package/engine/lib/fonts/NotoSansSC-800.woff +0 -0
- package/engine/lib/fonts/NotoSansSC-900.woff +0 -0
- package/engine/lib/fonts/NotoSansSC-sub.woff +0 -0
- package/engine/lib/fonts/NotoSerifJP-600.woff +0 -0
- package/engine/lib/fonts/NotoSerifSC-sub.woff +0 -0
- package/engine/lib/fonts/OFL.txt +94 -0
- package/engine/lib/fonts/PlayfairDisplay-400i.woff +0 -0
- package/engine/lib/fonts/PlayfairDisplay-700.woff +0 -0
- package/engine/lib/fonts/PlayfairDisplay-700i.woff +0 -0
- package/engine/lib/fonts/Poppins-600.woff +0 -0
- package/engine/lib/fonts/Poppins-700.woff +0 -0
- package/engine/lib/fonts/Poppins-800.woff +0 -0
- package/engine/lib/fonts/PressStart2P-400.woff +0 -0
- package/engine/lib/fonts/Righteous-400.woff +0 -0
- package/engine/lib/fonts/RobotoCondensed-var.woff +0 -0
- package/engine/lib/fonts/Rye-400.woff +0 -0
- package/engine/lib/fonts/UnifrakturMaguntia-400.woff +0 -0
- package/engine/lib/fonts/cmunrm.woff +0 -0
- package/engine/lib/fonts/cmunti.woff +0 -0
- package/engine/lib/fonts/index.json +1 -0
- package/engine/lib/fonts.js +56 -0
- package/engine/lib/kit.js +91 -0
- package/engine/lib/media.js +118 -0
- package/engine/lib/medium.js +258 -0
- package/engine/lib/motion.js +111 -0
- package/engine/lib/paint.js +200 -0
- package/engine/lib/palettes.js +57 -0
- package/engine/lib/post.js +108 -0
- package/engine/lib/render.js +189 -0
- package/engine/lib/rig.js +390 -0
- package/engine/lib/rig_huashu.js +171 -0
- package/engine/lib/three.min.js +7 -0
- package/engine/lib/toon.js +186 -0
- package/engine/lib/typo.js +136 -0
- package/engine/lib/ui.js +141 -0
- package/engine/lib/util.js +92 -0
- package/engine/render.py +157 -0
- package/engine/scenes/01_cave.js +364 -0
- package/engine/scenes/02_egypt.js +524 -0
- package/engine/scenes/03_greek.js +448 -0
- package/engine/scenes/04_roman.js +555 -0
- package/engine/scenes/05_gothic.js +522 -0
- package/engine/scenes/06_renaissance.js +338 -0
- package/engine/scenes/08_impressionism.js +206 -0
- package/engine/scenes/09_postimp.js +183 -0
- package/engine/scenes/10_nouveau.js +334 -0
- package/engine/scenes/11_cubism.js +265 -0
- package/engine/scenes/12_bauhaus.js +214 -0
- package/engine/scenes/13_pop.js +269 -0
- package/engine/scenes/14_8bit.js +238 -0
- package/engine/scenes/15_raytrace.js +300 -0
- package/engine/scenes/16_2026.js +565 -0
- package/engine/scenes/17_ink.js +273 -0
- package/engine/scenes/18_klimt.js +227 -0
- package/engine/scenes/19_munch.js +153 -0
- package/engine/scenes/20_dunhuang.js +215 -0
- package/engine/scenes/21_kusama.js +124 -0
- package/engine/scenes/22_constructivism.js +205 -0
- package/engine/scenes/23_dali.js +221 -0
- package/engine/scenes/24_hopper.js +150 -0
- package/engine/scenes/25_ghibli.js +251 -0
- package/engine/scenes/26_vaporwave.js +191 -0
- package/engine/scenes/27_kirby.js +255 -0
- package/engine/scenes/28_monet.js +176 -0
- package/engine/scenes/29_seurat.js +104 -0
- package/engine/scenes/30_matisse.js +151 -0
- package/engine/scenes/31_haring.js +109 -0
- package/engine/scenes/32_rembrandt.js +217 -0
- package/engine/scenes/33_rubberhose.js +219 -0
- package/engine/scenes/34_shadowpuppet.js +195 -0
- package/engine/scenes/35_shinkai.js +197 -0
- package/engine/scenes/36_picasso_blue.js +123 -0
- package/engine/scenes/38_giotto.js +133 -0
- package/engine/scenes/39_piero.js +53 -0
- package/engine/scenes/40_raphael.js +96 -0
- package/engine/scenes/41_michelangelo.js +112 -0
- package/engine/scenes/42_guardi.js +70 -0
- package/engine/scenes/43_lorenzetti.js +145 -0
- package/engine/scenes/44_cortona.js +86 -0
- package/engine/scenes/45_renoir.js +86 -0
- package/engine/scenes/46_pissarro.js +82 -0
- package/engine/scenes/47_sisley.js +94 -0
- package/engine/scenes/48_gauguin.js +107 -0
- package/engine/scenes/49_chagall.js +60 -0
- package/engine/scenes/50_ma_yuan.js +131 -0
- package/engine/scenes/51_persian_miniature.js +149 -0
- package/engine/scenes/52_south_park.js +105 -0
- package/engine/scenes/53_pixar.js +60 -0
- package/engine/scenes/54_ghibli_lab.js +68 -0
- package/engine/scenes/55_dunhuang.js +161 -0
- package/engine/scenes/index.js +28 -0
- package/engine/spec_assets.py +76 -0
- package/engine/transitions.js +929 -0
- package/engine//346/227/266/344/273/243/344/275/234/350/200/205/350/247/204/350/214/203.md +49 -0
- package/lib/host.mjs +754 -0
- package/lib/render-bridge.mjs +543 -0
- package/lib/render-cli.mjs +98 -0
- package/lib/storyboard-lint.mjs +54 -0
- package/manifest.json +52 -0
- package/package.json +8 -4
- package/runtime/package.json +8 -0
- package/skills/biuart/SKILL.md +154 -0
- package/skills/biuart/references/01-/346/213/206/350/247/243.md +55 -0
- package/skills/biuart/references/02-/346/234/272/345/210/266.md +41 -0
- package/skills/biuart/references/03-/344/270/200/345/270/247/345/205/210/350/241/214/345/233/233/346/235/241/350/267/257/347/272/277.md +39 -0
- package/skills/biuart/references/04-/347/272/257/344/273/243/347/240/201/347/273/230/345/210/266.md +92 -0
- package/skills/biuart/references/05-/350/212/202/345/245/217/344/270/216/351/205/215/344/271/220.md +19 -0
- package/skills/biuart/references/06-/345/217/243/346/222/255/351/251/261/345/212/250/347/232/204/350/211/272/346/234/257/347/237/255/347/211/207.md +31 -0
- package/skills/biuart/references/07-/346/255/243/351/235/242/347/273/217/351/252/214.md +196 -0
- package/skills/biuart/references/08-/351/243/216/346/240/274/344/275/234/350/200/205/350/247/204/350/214/203.md +61 -0
- package/skills/biuart/references/09-/350/247/206/351/242/221/345/212/250/347/224/273/350/257/255/346/263/225.md +382 -0
- package/skills/biuart/references/10-/350/247/222/350/211/262.md +34 -0
- package/skills/biuart/references/11-/351/225/277/345/215/267/347/251/277/350/266/212/347/211/207.md +43 -0
- package/skills/biuart/references/12-/345/217/243/346/222/255/346/225/264/347/211/207/344/270/216/347/273/217/351/252/214/345/233/236/346/265/201.md +25 -0
- package/skills/biuart/references/13-/345/217/243/346/222/255/344/270/216/350/257/255/351/237/263/345/244/215/345/210/273.md +7 -0
- package/skills/biuart/references/15-/350/267/250/345/234/272/346/231/257/351/243/216/346/240/274/346/217/220/347/202/274.md +48 -0
- package/skills/biuart/references/16-/351/243/216/346/240/274/346/274/224/347/244/27295/345/210/206/345/215/217/350/256/256.md +103 -0
- package/skills/biuart/references/about-author.md +99 -0
- package/skills/biuart/references/capabilities.md +91 -0
- package/skills/biuart/references/compatibility.md +34 -0
- package/skills/biuart/references/images.md +97 -0
- package/skills/biuart/references/voice-model-review.md +18 -0
- package/skills/biuart/references//345/212/250/347/224/273/350/257/255/346/263/225/t1_3b1b.md +122 -0
- package/skills/biuart/references//345/212/250/347/224/273/350/257/255/346/263/225/t2_keynote_ui.md +117 -0
- package/skills/biuart/references//345/212/250/347/224/273/350/257/255/346/263/225/t3_finance_chart.md +116 -0
- package/skills/biuart/references//345/212/250/347/224/273/350/257/255/346/263/225/y1_kurzgesagt.md +193 -0
- package/skills/biuart/references//345/212/250/347/224/273/350/257/255/346/263/225/y2_vox.md +195 -0
- package/skills/biuart/references//345/212/250/347/224/273/350/257/255/346/263/225/y3_whiteboard.md +190 -0
- package/skills/biuart/references//345/212/250/347/224/273/350/257/255/346/263/225/y4_storytime.md +166 -0
- package/skills/biuart/references//345/212/250/347/224/273/350/257/255/346/263/225/y5_kinetic_type.md +158 -0
- package/skills/biuart/references//345/212/250/347/224/273/350/257/255/346/263/225/y6_presenter_explainer.md +24 -0
- package/skills/biuart/references//345/217/243/346/222/255API.md +125 -0
- package/skills/biuart/references//350/211/262/346/235/277.md +38 -0
- package/skills/biuart/references//350/247/222/350/211/262/347/262/276/347/273/206/345/272/246.md +61 -0
- package/skills/biuart/references//351/225/234/345/244/264/350/241/250/346/250/241/346/235/277.md +80 -0
- package/skills/biuart/references//351/225/234/345/244/264/350/257/255/350/250/200.md +73 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/01_cave.md +46 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/02_egypt.md +64 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/03_greek.md +50 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/04_roman.md +99 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/05_gothic.md +32 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/06_renaissance.md +41 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/08_impressionism.md +34 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/09_postimp.md +17 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/10_nouveau.md +31 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/11_cubism.md +30 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/12_bauhaus.md +30 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/13_pop.md +29 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/14_8bit.md +27 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/15_raytrace.md +32 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/16_2026.md +55 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/17_ink.md +50 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/18_klimt.md +37 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/19_munch.md +42 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/20_dunhuang.md +49 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/21_kusama.md +32 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/22_constructivism.md +35 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/23_dali.md +29 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/24_hopper.md +30 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/25_ghibli.md +55 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/26_vaporwave.md +27 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/27_kirby.md +30 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/28_monet.md +39 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/29_seurat.md +31 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/30_matisse.md +26 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/31_haring.md +22 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/32_rembrandt.md +29 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/33_rubberhose.md +31 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/34_shadowpuppet.md +40 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/35_shinkai.md +34 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/36_picasso_blue.md +25 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/37_rick_morty.md +48 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/38_giotto.md +67 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/39_piero.md +59 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/40_raphael.md +65 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/41_michelangelo.md +65 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/42_guardi.md +65 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/43_lorenzetti.md +69 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/44_cortona.md +66 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/45_renoir.md +66 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/46_pissarro.md +66 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/47_sisley.md +67 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/48_gauguin.md +66 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/49_chagall.md +59 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/50_ma_yuan.md +68 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/51_persian_miniature.md +66 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/52_south_park.md +65 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/53_pixar.md +61 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/55_dunhuang.md +41 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/INDEX.md +89 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/_/350/275/254/345/234/272_/350/277/201/347/247/273/346/265/213/350/257/225.md +53 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/_/351/207/215/345/201/232/350/277/233/345/272/246.md +37 -0
- package/skills/biuart/references//351/243/216/346/240/274/351/205/215/346/226/271/_/351/237/263/350/275/250_/350/211/272/346/234/257/345/217/262/351/200/237/351/200/232.md +135 -0
- package/studio/app.js +380 -0
- package/studio/assert-panel-permit.mjs +67 -0
- package/studio/composer.css +465 -0
- package/studio/composer.js +480 -0
- package/studio/index.html +97 -0
- package/studio/optimize-prompt.mjs +84 -0
- package/studio/optimize-prompt.test.mjs +47 -0
- package/studio/recipes.mjs +87 -0
- package/studio/select-menu.js +450 -0
- package/studio/styles.css +71 -0
- package/studio/to-english-prompt.mjs +149 -0
- package/studio/to-english-prompt.test.mjs +72 -0
- package/README.md +0 -3
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
// 媒介层:把「干净的形」过一遍真实的画材,而不是在扁平矢量上盖噪点。2026-10-16 新增,新风格演示(38–54)用。
|
|
2
|
+
// MEDIUM.figure(c, o) 有体量的着衣人物:钟形袍+披风褶+袖+手+简化脸(视线是主角)。只给原创平面/壁画/细密画一类明确选择的风格用
|
|
3
|
+
// MEDIUM.plaster(key, o) 灰泥墙底:低频湿干色差+颗粒+针孔+接缝(壁画「一天的活」边),缓存
|
|
4
|
+
// MEDIUM.fresco(c, plate, o) 壁画通道:色粉被石灰吃进去(轻糊+粉白罩)+灰泥相乘+针孔提亮+接缝
|
|
5
|
+
// MEDIUM.silk(key, o) 绢/纸底:经纬丝纹+泛黄晕边,缓存
|
|
6
|
+
// MEDIUM.outline(c, path, o) 轮廓线:可变线宽、颜色可偏(赭褐、群青、墨),不是纯黑
|
|
7
|
+
// MEDIUM.flat(c, path, fill, o) 掐丝珐琅式平涂:同色块里有顺笔向的短条+粗而不均的深群青轮廓(高更)
|
|
8
|
+
// MEDIUM.broken(src, o) 破碎色笔触重绘:把干净的画布重画成一笔一笔的印象派(明处偏暖、暗处偏冷的互补抖动),返回新画布,调用方缓存
|
|
9
|
+
// MEDIUM.hatchFill(c, path, o) 在路径内按方向排线(木刻/铜版/细密画暗部)
|
|
10
|
+
// 约定:所有随机走种子;plate 是画好的 1920×1080 离屏画布;通道函数不改 plate。
|
|
11
|
+
(() => {
|
|
12
|
+
let W = 1920, H = 1080; U.onStage((w, h) => { W = w; H = h; });
|
|
13
|
+
const P = window.PAINT, { clamp, lerp, rng, ss } = U, TAU = Math.PI * 2;
|
|
14
|
+
const M = window.MEDIUM = {};
|
|
15
|
+
|
|
16
|
+
const hex = h => P.hex(h);
|
|
17
|
+
const mixc = (a, b, t) => P.mix(typeof a === 'string' ? hex(a) : a, typeof b === 'string' ? hex(b) : b, t);
|
|
18
|
+
const css = (c, a = 1) => P.rgb(typeof c === 'string' ? hex(c) : c, a);
|
|
19
|
+
M.css = css; M.mix = mixc;
|
|
20
|
+
|
|
21
|
+
// ---------- 灰泥 ----------
|
|
22
|
+
// 壁画不是纸:石灰层有低频的湿干色差、细颗粒、针孔;一天的活(giornata)之间有一道几乎看不见的接缝。
|
|
23
|
+
M.plaster = (key, { base = '#d9cdb4', seams = [], seed = 7, pits = 1400, mottle = 0.07 } = {}) => P.cached('plaster_' + key, W, H, (g) => {
|
|
24
|
+
const r = rng(seed), im = g.createImageData(W, H), d = im.data, b = hex(base);
|
|
25
|
+
for (let y = 0; y < H; y++) for (let x = 0; x < W; x++) {
|
|
26
|
+
const i = (y * W + x) * 4;
|
|
27
|
+
const m = P.fbm(x * 0.0035 + seed, y * 0.0035, 4) * mottle * 255 + P.fbm(x * 0.02, y * 0.02 + seed, 3) * 0.035 * 255;
|
|
28
|
+
const f = (r() - 0.5) * 16; // 细颗粒
|
|
29
|
+
d[i] = clamp(b[0] + m + f, 0, 255); d[i + 1] = clamp(b[1] + m + f, 0, 255); d[i + 2] = clamp(b[2] + m * 0.9 + f, 0, 255); d[i + 3] = 255;
|
|
30
|
+
}
|
|
31
|
+
g.putImageData(im, 0, 0);
|
|
32
|
+
for (let i = 0; i < pits; i++) { // 针孔:暗点+右下一侧的亮边
|
|
33
|
+
const x = r() * W, y = r() * H, s = 0.6 + r() * 1.9;
|
|
34
|
+
g.fillStyle = `rgba(70,55,40,${0.12 + r() * 0.22})`; g.beginPath(); g.arc(x, y, s, 0, TAU); g.fill();
|
|
35
|
+
g.fillStyle = 'rgba(255,250,235,.28)'; g.beginPath(); g.arc(x + s * 0.7, y + s * 0.7, s * 0.55, 0, TAU); g.fill();
|
|
36
|
+
}
|
|
37
|
+
g.lineCap = 'round';
|
|
38
|
+
for (const sm of seams) { // 接缝:[[x,y],...] 折线;一条亮脊+一条暗线,两侧底色略差
|
|
39
|
+
g.strokeStyle = 'rgba(80,62,45,.22)'; g.lineWidth = 1.6; g.beginPath(); sm.forEach((p, k) => k ? g.lineTo(p[0], p[1]) : g.moveTo(p[0], p[1])); g.stroke();
|
|
40
|
+
g.strokeStyle = 'rgba(255,248,230,.34)'; g.lineWidth = 1.4; g.beginPath(); sm.forEach((p, k) => k ? g.lineTo(p[0], p[1] + 2) : g.moveTo(p[0], p[1] + 2)); g.stroke();
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// 壁画通道:plate(干净的形)→ 画面。
|
|
45
|
+
// ① 轻糊(色粉被湿灰泥吃进去,边缘不锐)② 石灰粉白罩(screen 低 α,整体粉、哑)③ 灰泥相乘(底色差、颗粒)④ 针孔和接缝提亮
|
|
46
|
+
M.fresco = (c, plate, { ground, chalk = 0.10, soften = 0.8, plasterAlpha = 0.55, sat = 0.9, tone = null } = {}) => {
|
|
47
|
+
c.save();
|
|
48
|
+
c.filter = `blur(${soften}px) saturate(${sat})`; c.drawImage(plate, 0, 0); c.filter = 'none';
|
|
49
|
+
c.globalCompositeOperation = 'screen'; c.fillStyle = `rgba(236,226,206,${chalk})`; c.fillRect(0, 0, W, H);
|
|
50
|
+
c.globalCompositeOperation = 'multiply'; c.globalAlpha = plasterAlpha; c.drawImage(ground, 0, 0);
|
|
51
|
+
c.globalCompositeOperation = 'soft-light'; c.globalAlpha = 0.5; c.drawImage(ground, 0, 0);
|
|
52
|
+
if (tone) { c.globalCompositeOperation = 'multiply'; c.globalAlpha = 1; c.fillStyle = tone; c.fillRect(0, 0, W, H); }
|
|
53
|
+
c.restore();
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
// ---------- 绢 / 纸底 ----------
|
|
57
|
+
M.silk = (key, { base = '#c9a85e', seed = 3, warp = 0.5, vig = 0.5, fleck = 1800 } = {}) => P.cached('silk_' + key, W, H, (g) => {
|
|
58
|
+
const r = rng(seed), b = hex(base);
|
|
59
|
+
const gr = g.createLinearGradient(0, 0, 0, H); gr.addColorStop(0, css(mixc(b, [255, 240, 200], 0.18))); gr.addColorStop(1, css(mixc(b, [120, 80, 30], 0.12)));
|
|
60
|
+
g.fillStyle = gr; g.fillRect(0, 0, W, H);
|
|
61
|
+
g.globalAlpha = 0.5; g.drawImage(P.texture('silk' + key, base, { scale: 0.003, amt: 14, grain: 5, seed }), 0, 0); g.globalAlpha = 1;
|
|
62
|
+
for (let x = 0; x < W; x += 3) { g.fillStyle = `rgba(90,60,20,${(0.02 + r() * 0.045) * warp})`; g.fillRect(x, 0, 1, H); } // 经线
|
|
63
|
+
for (let y = 0; y < H; y += 3) { g.fillStyle = `rgba(255,236,170,${(0.015 + r() * 0.04) * warp})`; g.fillRect(0, y, W, 1); } // 纬线
|
|
64
|
+
for (let i = 0; i < fleck; i++) { g.fillStyle = r() < 0.5 ? 'rgba(80,55,25,.10)' : 'rgba(255,240,200,.14)'; g.fillRect(r() * W, r() * H, 1 + r() * 4, 1); }
|
|
65
|
+
const v = g.createRadialGradient(W / 2, H / 2, H * 0.45, W / 2, H / 2, W * 0.75); v.addColorStop(0, 'rgba(80,50,15,0)'); v.addColorStop(1, `rgba(80,50,15,${vig * 0.5})`);
|
|
66
|
+
g.fillStyle = v; g.fillRect(0, 0, W, H);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// ---------- 线 ----------
|
|
70
|
+
// 沿 pts 画变宽线:w 为 [起,中,尾] 宽度,col 颜色。不是纯黑——给素描红土/群青/墨色。
|
|
71
|
+
M.outline = (c, pts, { w = [1, 3, 1], col = '#4a3322', a = 0.9, closed = false, jitter = 0, seed = 1 } = {}) => {
|
|
72
|
+
const r = rng(seed), n = pts.length;
|
|
73
|
+
c.save(); c.lineCap = 'round'; c.lineJoin = 'round'; c.strokeStyle = typeof col === 'string' ? col : css(col, a); c.globalAlpha = a;
|
|
74
|
+
const seg = closed ? n : n - 1;
|
|
75
|
+
for (let i = 0; i < seg; i++) {
|
|
76
|
+
const A = pts[i], B = pts[(i + 1) % n], q = i / Math.max(1, seg - 1);
|
|
77
|
+
c.lineWidth = q < 0.5 ? lerp(w[0], w[1], q * 2) : lerp(w[1], w[2], (q - 0.5) * 2);
|
|
78
|
+
c.beginPath(); c.moveTo(A[0] + (r() - .5) * jitter, A[1] + (r() - .5) * jitter); c.lineTo(B[0] + (r() - .5) * jitter, B[1] + (r() - .5) * jitter); c.stroke();
|
|
79
|
+
}
|
|
80
|
+
c.restore();
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
M.hatchFill = (c, path, { ang = 0.9, gap = 5, col = 'rgba(40,30,20,.35)', lw = 1, bounds = [0, 0, W, H], seed = 1 } = {}) => {
|
|
84
|
+
const r = rng(seed); c.save(); c.clip(path); c.strokeStyle = col; c.lineWidth = lw; c.beginPath();
|
|
85
|
+
const [bx, by, bw, bh] = bounds, R = Math.hypot(bw, bh), cx = bx + bw / 2, cy = by + bh / 2, ca = Math.cos(ang), sa = Math.sin(ang);
|
|
86
|
+
for (let k = -R; k < R; k += gap) { const j = (r() - .5) * 1.2; c.moveTo(cx + ca * -R - sa * (k + j), cy + sa * -R + ca * (k + j)); c.lineTo(cx + ca * R - sa * (k + j), cy + sa * R + ca * (k + j)); }
|
|
87
|
+
c.stroke(); c.restore();
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
// ---------- 掐丝珐琅式平涂(高更/综合主义)----------
|
|
91
|
+
// path: Path2D;fill: 色;o.streak: 笔向(弧度),o.vari: 色差幅度;o.line: 轮廓色(深群青/紫黑,不是纯黑),o.lw: 轮廓宽
|
|
92
|
+
// 平涂不等于平:同一色块里有顺笔向的明暗短条(看得见刷子),轮廓粗而不均。每块用 o.seed 固定笔触,确定性。
|
|
93
|
+
M.flat = (c, path, fill, { streak = 0.7, vari = 18, line = '#1d2a52', lw = 6, seed = 1, bounds = [0, 0, W, H], n = 46, la = 0.95 } = {}) => {
|
|
94
|
+
const r = rng(seed * 131), f = typeof fill === 'string' ? hex(fill) : fill;
|
|
95
|
+
c.save(); c.fillStyle = css(f); c.fill(path); c.clip(path);
|
|
96
|
+
const [bx, by, bw, bh] = bounds;
|
|
97
|
+
c.lineCap = 'round';
|
|
98
|
+
for (let i = 0; i < n; i++) {
|
|
99
|
+
const x = bx + r() * bw, y = by + r() * bh, L = 40 + r() * 120, a = streak + (r() - .5) * 0.25, d = (r() - .5) * 2 * vari;
|
|
100
|
+
c.strokeStyle = css([f[0] + d, f[1] + d * 0.8, f[2] + d * 0.7], 0.55); c.lineWidth = 8 + r() * 22;
|
|
101
|
+
c.beginPath(); c.moveTo(x, y); c.lineTo(x + Math.cos(a) * L, y + Math.sin(a) * L); c.stroke();
|
|
102
|
+
}
|
|
103
|
+
c.restore();
|
|
104
|
+
if (lw) { c.save(); c.strokeStyle = line; c.globalAlpha = la; c.lineWidth = lw; c.lineJoin = 'round'; c.lineCap = 'round'; c.stroke(path); c.restore(); }
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
// ---------- 破碎色笔触重绘(印象派:毕沙罗/西斯莱/瓜尔迪/雷诺阿)----------
|
|
108
|
+
// src:画好的干净画布(可比舞台宽,如拖动的天空);返回新画布(调用方缓存)。
|
|
109
|
+
// 每个采样点画一小笔:沿 angleFn(x,y) 方向、两头尖的弯笔,颜色=该点颜色按「明暗+冷暖互补」抖动——
|
|
110
|
+
// 亮处偏暖黄/粉、暗处偏冷紫/蓝(不是单纯明度噪声);随机跳过一些点让底色透出;笔序确定性打乱,所以厚笔压薄笔。
|
|
111
|
+
// o: cell 采样间距, len 笔长, wid 笔宽, passes 遍数(第二遍更小更碎), angle(x,y)→弧度, seed, warm/cool 互补抖动色, sat 饱和度增益
|
|
112
|
+
M.broken = (src, { cell = 9, len = 26, wid = 8, passes = 2, angle = () => 0, seed = 1, warm = [255, 226, 160], cool = [120, 120, 190], jit = 22, skip = 0.1, sat = 1.06, base = true } = {}) => {
|
|
113
|
+
const w = src.width, h = src.height, out = P.canvas(w, h), g = out.getContext('2d');
|
|
114
|
+
const sg = src.getContext('2d', { willReadFrequently: true }), d = sg.getImageData(0, 0, w, h).data, r = rng(seed * 977);
|
|
115
|
+
if (base) { g.filter = 'blur(6px)'; g.drawImage(src, -14, -14, w + 28, h + 28); g.filter = 'none'; } // 外扩 14px 再糊:边缘不留半透明圈(左缘一排缺口小圆点就是这么来的)
|
|
116
|
+
g.lineCap = 'round';
|
|
117
|
+
for (let pass = 0; pass < passes; pass++) {
|
|
118
|
+
const cs = cell * (pass ? 0.62 : 1), L = len * (pass ? 0.6 : 1), Wd = wid * (pass ? 0.6 : 1), pts = [];
|
|
119
|
+
for (let y = 0; y < h + cs; y += cs) for (let x = 0; x < w + cs; x += cs) pts.push([x + (r() - .5) * cs, y + (r() - .5) * cs]);
|
|
120
|
+
for (let i = pts.length - 1; i > 0; i--) { const j = (r() * (i + 1)) | 0; [pts[i], pts[j]] = [pts[j], pts[i]]; }
|
|
121
|
+
for (const [x, y] of pts) {
|
|
122
|
+
if (r() < skip) continue;
|
|
123
|
+
const xi = clamp(x | 0, 0, w - 1), yi = clamp(y | 0, 0, h - 1), k = (yi * w + xi) * 4;
|
|
124
|
+
if (d[k + 3] < 8) continue;
|
|
125
|
+
let R = d[k], G = d[k + 1], B = d[k + 2];
|
|
126
|
+
const lum = (R * 0.3 + G * 0.59 + B * 0.11) / 255, t2 = (r() - 0.5) * 2;
|
|
127
|
+
const tint = lum > 0.55 ? warm : cool, tm = Math.abs(t2) * 0.22 * (lum > 0.55 ? lum : 1 - lum);
|
|
128
|
+
R = lerp(R, tint[0], tm) + t2 * jit * 0.5; G = lerp(G, tint[1], tm) + t2 * jit * 0.45; B = lerp(B, tint[2], tm) + t2 * jit * 0.4;
|
|
129
|
+
const mx = (R + G + B) / 3; R = mx + (R - mx) * sat; G = mx + (G - mx) * sat; B = mx + (B - mx) * sat;
|
|
130
|
+
const a = angle(x, y) + (r() - .5) * 0.35, l = L * (0.6 + r() * 0.8), wd = Wd * (0.6 + r() * 0.7), bend = (r() - .5) * 0.5 * l;
|
|
131
|
+
const cx = Math.cos(a), sx = Math.sin(a);
|
|
132
|
+
g.strokeStyle = `rgb(${clamp(R, 0, 255) | 0},${clamp(G, 0, 255) | 0},${clamp(B, 0, 255) | 0})`; g.globalAlpha = 0.9; g.lineWidth = wd;
|
|
133
|
+
g.beginPath(); g.moveTo(x - cx * l / 2, y - sx * l / 2); g.quadraticCurveTo(x - sx * bend, y + cx * bend, x + cx * l / 2, y + sx * l / 2); g.stroke();
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
g.globalAlpha = 1; return out;
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
// ---------- 有体量的着衣人物 ----------
|
|
140
|
+
// o: x,y(脚底中心), h(全高), robe:[亮,中,暗], under(内衣露出色), mantle(披风色组或 null), skin, hair,
|
|
141
|
+
// face(-1 朝左/1 朝右), lean(髋以上前倾,弧度,朝 face 方向为正), bow(低头 0..1), gaze:[x,y](看向哪,画眼珠),
|
|
142
|
+
// hands:[[x,y],[x,y]](两只手的目标点,null 则自然下垂), sway(衣摆摆动 0..1), halo(金色光环,默认无), line(轮廓色)
|
|
143
|
+
// 画法:袍=钟形多边形+沿宽度的明暗渐变+数条褶线(暗线+相邻亮线);袖=两段式(肩→肘→手)的变宽带;头=椭圆+发+眼(视线)。
|
|
144
|
+
// 不画五官细节:吉卜力式大眼、乔托式「杏仁眼斜视」都靠眼珠偏向 gaze 表达关系。
|
|
145
|
+
M.figure = (c, o) => {
|
|
146
|
+
const { x, y, h, face = 1, lean = 0, bow = 0, sway = 0, hands = [null, null], gaze = null, halo = false } = o;
|
|
147
|
+
const robe = (o.robe || ['#d98a78', '#b85a4c', '#7a3a34']).map(hex), under = hex(o.under || '#e7d9b0'), skin = hex(o.skin || '#e0b48c'), hairC = hex(o.hair || '#5a3a24'), line = o.line || '#4a2f22';
|
|
148
|
+
const mantle = o.mantle ? o.mantle.map(hex) : null, seed = o.seed || 1, r = rng(seed * 97);
|
|
149
|
+
const hipY = y - h * 0.42, sh = h * 0.2;
|
|
150
|
+
const cs = Math.cos(lean * face), sn = Math.sin(lean * face);
|
|
151
|
+
const up = (dx, dy) => [x + dx * cs - dy * sn * face, hipY + dx * sn * face + dy * cs]; // 髋为原点、绕髋前倾
|
|
152
|
+
const shL = up(-h * 0.15, -h * 0.30), shR = up(h * 0.15, -h * 0.30), neck = up(0, -h * 0.335);
|
|
153
|
+
const head = [neck[0] + face * h * 0.014 * bow, neck[1] - h * 0.088 + h * 0.025 * bow];
|
|
154
|
+
const hw = h * 0.078, hh = h * 0.094;
|
|
155
|
+
c.save(); c.lineJoin = 'round'; c.lineCap = 'round';
|
|
156
|
+
|
|
157
|
+
// 地面影
|
|
158
|
+
c.fillStyle = 'rgba(40,28,18,.22)'; c.beginPath(); c.ellipse(x + face * h * 0.03, y + 3, h * 0.22, h * 0.03, 0, 0, TAU); c.fill();
|
|
159
|
+
|
|
160
|
+
// --- 袍身 ---
|
|
161
|
+
const hemW = h * (0.25 * (o.hemK || 1) + 0.012 * sway), waistW = h * 0.115, sw = Math.sin(o.t * 2.3 + seed) * sway * h * 0.012 || 0;
|
|
162
|
+
const body = new Path2D();
|
|
163
|
+
body.moveTo(shL[0], shL[1]); body.lineTo(neck[0] - h * 0.03, neck[1] + 4); body.lineTo(neck[0] + h * 0.03, neck[1] + 4); body.lineTo(shR[0], shR[1]);
|
|
164
|
+
body.quadraticCurveTo(shR[0] + h * 0.02, hipY - h * 0.1, x + waistW, hipY + h * 0.05);
|
|
165
|
+
body.quadraticCurveTo(x + hemW * 0.9 + sw, y - h * 0.18, x + hemW + sw, y);
|
|
166
|
+
body.quadraticCurveTo(x, y + h * 0.018, x - hemW + sw, y);
|
|
167
|
+
body.quadraticCurveTo(x - hemW * 0.9 + sw, y - h * 0.18, x - waistW, hipY + h * 0.05);
|
|
168
|
+
body.quadraticCurveTo(shL[0] - h * 0.02, hipY - h * 0.1, shL[0], shL[1]); body.closePath();
|
|
169
|
+
const lx = x - face * h * 0.2, gr = c.createLinearGradient(lx, 0, x + face * h * 0.26, 0); // 光从背后一侧来:亮在 -face 侧
|
|
170
|
+
gr.addColorStop(0, css(robe[0])); gr.addColorStop(0.3, css(robe[0])); gr.addColorStop(0.52, css(robe[1])); gr.addColorStop(0.82, css(robe[2])); gr.addColorStop(1, css(robe[2]));
|
|
171
|
+
c.fillStyle = gr; c.fill(body);
|
|
172
|
+
// 褶:先铺软的明暗大面(模糊),再压一条细硬折痕——乔托的衣褶是「大面渐变+少数硬边」,不是排线
|
|
173
|
+
const nF = 5, folds = [];
|
|
174
|
+
for (let i = 0; i < nF; i++) { const q = (i + 0.5) / nF - 0.5; folds.push({ x0: x + q * h * 0.22 + (r() - .5) * h * 0.02, x1: x + q * hemW * 2 * 0.92 + sw * (0.4 + Math.abs(q)), cy: y - h * (0.22 + r() * 0.12), k: 0.7 + r() * 0.6 }); }
|
|
175
|
+
c.save(); c.clip(body);
|
|
176
|
+
c.filter = `blur(${Math.max(2, h * 0.012)}px)`;
|
|
177
|
+
for (const f of folds) { c.strokeStyle = css(robe[2], 0.34 * f.k); c.lineWidth = h * 0.05 * f.k; c.beginPath(); c.moveTo(f.x0, hipY - h * 0.1); c.quadraticCurveTo(f.x0 + (f.x1 - f.x0) * 0.2 + face * h * 0.015, f.cy, f.x1, y - h * 0.01); c.stroke();
|
|
178
|
+
c.strokeStyle = css(robe[0], 0.3 * f.k); c.lineWidth = h * 0.035 * f.k; c.beginPath(); c.moveTo(f.x0 - face * h * 0.04, hipY - h * 0.1); c.quadraticCurveTo(f.x0 + (f.x1 - f.x0) * 0.2 - face * h * 0.03, f.cy, f.x1 - face * h * 0.045, y - h * 0.01); c.stroke(); }
|
|
179
|
+
c.filter = 'none';
|
|
180
|
+
for (const f of folds) { c.strokeStyle = css(robe[2], 0.5); c.lineWidth = h * 0.0065; c.beginPath(); c.moveTo(f.x0, hipY - h * 0.1); c.quadraticCurveTo(f.x0 + (f.x1 - f.x0) * 0.2 + face * h * 0.015, f.cy, f.x1, y - h * 0.01); c.stroke(); }
|
|
181
|
+
// 膝/腰的受力:两处弧形暗影(衣料被撑起的地方)
|
|
182
|
+
c.filter = `blur(${h * 0.02}px)`; c.fillStyle = css(robe[2], 0.22); c.beginPath(); c.ellipse(x + face * h * 0.04, hipY + h * 0.12, h * 0.07, h * 0.16, 0.2 * face, 0, TAU); c.fill(); c.filter = 'none';
|
|
183
|
+
// 腰带/内衣下摆
|
|
184
|
+
c.fillStyle = css(under, 0.95); c.beginPath(); c.moveTo(x - hemW * 0.5 + sw, y); c.quadraticCurveTo(x + sw, y - h * 0.05, x + hemW * 0.5 + sw, y); c.quadraticCurveTo(x + sw, y + h * 0.015, x - hemW * 0.5 + sw, y); c.fill();
|
|
185
|
+
c.restore();
|
|
186
|
+
c.strokeStyle = line; c.globalAlpha = 0.75; c.lineWidth = Math.max(1.6, h * 0.007); c.stroke(body); c.globalAlpha = 1;
|
|
187
|
+
|
|
188
|
+
// --- 披风:从背侧肩斜过身体盖到对侧髋,厚重的大褶 ---
|
|
189
|
+
if (mantle) {
|
|
190
|
+
const back = face > 0 ? shL : shR, front = face > 0 ? shR : shL, sideX = face;
|
|
191
|
+
const mp = new Path2D();
|
|
192
|
+
mp.moveTo(back[0] - sideX * h * 0.03, back[1] - h * 0.03);
|
|
193
|
+
mp.quadraticCurveTo(neck[0], neck[1] - h * 0.015, front[0] + sideX * h * 0.015, front[1] + h * 0.02);
|
|
194
|
+
mp.quadraticCurveTo(front[0] - sideX * h * 0.02, hipY, x - sideX * hemW * 0.05 + sw, y - h * 0.01);
|
|
195
|
+
mp.quadraticCurveTo(x - sideX * hemW * 0.5 + sw, y + h * 0.022, x - sideX * hemW * 1.02 + sw, y);
|
|
196
|
+
mp.quadraticCurveTo(back[0] - sideX * h * 0.16, hipY, back[0] - sideX * h * 0.03, back[1] - h * 0.03); mp.closePath();
|
|
197
|
+
const mg = c.createLinearGradient(x - h * 0.2, 0, x + h * 0.26, 0); mg.addColorStop(0, css(mantle[0])); mg.addColorStop(0.5, css(mantle[1])); mg.addColorStop(1, css(mantle[2]));
|
|
198
|
+
c.fillStyle = mg; c.fill(mp);
|
|
199
|
+
c.save(); c.clip(mp);
|
|
200
|
+
for (let i = 0; i < 5; i++) {
|
|
201
|
+
const q = i / 4, sx = lerp(back[0], front[0], q), ex = x + (q - 0.45) * hemW * 1.8 + sw;
|
|
202
|
+
c.strokeStyle = css(mantle[2], 0.5); c.lineWidth = h * 0.011; c.beginPath(); c.moveTo(sx, lerp(back[1], front[1], q) + h * 0.03); c.quadraticCurveTo(sx + sideX * h * 0.03, hipY + h * 0.03, ex, y - h * 0.01); c.stroke();
|
|
203
|
+
c.strokeStyle = css(mantle[0], 0.4); c.lineWidth = h * 0.006; c.beginPath(); c.moveTo(sx - sideX * h * 0.012, lerp(back[1], front[1], q) + h * 0.03); c.quadraticCurveTo(sx + sideX * h * 0.02, hipY + h * 0.03, ex - sideX * h * 0.014, y - h * 0.01); c.stroke();
|
|
204
|
+
}
|
|
205
|
+
c.restore(); c.strokeStyle = line; c.globalAlpha = 0.7; c.lineWidth = Math.max(1.6, h * 0.007); c.stroke(mp); c.globalAlpha = 1;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// --- 袖与手:肩→肘→手,肘按「向外下方」弯 ---
|
|
209
|
+
const sleeveCol = mantle ? mantle : robe;
|
|
210
|
+
const arm = (S, T, k) => {
|
|
211
|
+
if (!T) T = [S[0] + (k ? 1 : -1) * h * 0.05, S[1] + h * 0.26];
|
|
212
|
+
const L1 = h * 0.17, L2 = h * 0.16, dx = T[0] - S[0], dy = T[1] - S[1], dist = Math.min(Math.hypot(dx, dy), L1 + L2 - 1), a = Math.atan2(dy, dx);
|
|
213
|
+
const ca = Math.acos(clamp((L1 * L1 + dist * dist - L2 * L2) / (2 * L1 * dist), -1, 1)), side = (T[0] > x ? 1 : -1) * (k ? 1 : -1) || 1;
|
|
214
|
+
const el = [S[0] + Math.cos(a + ca * -side) * L1, S[1] + Math.sin(a + ca * -side) * L1];
|
|
215
|
+
const hd = [S[0] + Math.cos(a) * dist, S[1] + Math.sin(a) * dist];
|
|
216
|
+
const w0 = h * 0.078, w1 = h * 0.058, w2 = h * 0.04;
|
|
217
|
+
const pts = [S, el, hd], nx = i => { const A = pts[Math.max(0, i - 1)], B = pts[Math.min(2, i + 1)], d = Math.hypot(B[0] - A[0], B[1] - A[1]) || 1; return [-(B[1] - A[1]) / d, (B[0] - A[0]) / d]; };
|
|
218
|
+
const W3 = [w0, w1, w2], L = [], R = [];
|
|
219
|
+
for (let i = 0; i < 3; i++) { const n = nx(i); L.push([pts[i][0] + n[0] * W3[i] / 2, pts[i][1] + n[1] * W3[i] / 2]); R.push([pts[i][0] - n[0] * W3[i] / 2, pts[i][1] - n[1] * W3[i] / 2]); }
|
|
220
|
+
const sp = new Path2D(); sp.moveTo(...L[0]); sp.quadraticCurveTo(...L[1], ...L[2]); sp.lineTo(...R[2]); sp.quadraticCurveTo(...R[1], ...R[0]); sp.closePath();
|
|
221
|
+
const sg = c.createLinearGradient(S[0] - h * 0.05, S[1], hd[0] + h * 0.05, hd[1]); sg.addColorStop(0, css(sleeveCol[0])); sg.addColorStop(1, css(sleeveCol[1]));
|
|
222
|
+
c.fillStyle = sg; c.fill(sp); c.strokeStyle = line; c.globalAlpha = 0.7; c.lineWidth = Math.max(1.4, h * 0.006); c.stroke(sp); c.globalAlpha = 1;
|
|
223
|
+
c.strokeStyle = css(sleeveCol[2], 0.4); c.lineWidth = h * 0.007; c.beginPath(); c.moveTo(el[0], el[1]); c.quadraticCurveTo(el[0] + h * 0.01, el[1] + h * 0.03, hd[0] - h * 0.005, hd[1] - h * 0.04); c.stroke();
|
|
224
|
+
c.fillStyle = css(skin); c.strokeStyle = line; c.globalAlpha = 0.8; c.lineWidth = Math.max(1.2, h * 0.005);
|
|
225
|
+
// 手:掌+拇指+并拢的三指,朝向沿前臂方向(托物时看得出是手,不是圆片)
|
|
226
|
+
c.save(); c.translate(hd[0], hd[1]); c.rotate(a);
|
|
227
|
+
c.beginPath(); c.ellipse(h * 0.012, 0, h * 0.028, h * 0.022, 0, 0, TAU); c.fill(); c.stroke();
|
|
228
|
+
c.beginPath(); c.ellipse(h * 0.04, h * 0.002, h * 0.026, h * 0.012, 0.08, 0, TAU); c.fill(); c.stroke();
|
|
229
|
+
c.beginPath(); c.ellipse(h * 0.012, -h * 0.022 * (k ? 1 : -1), h * 0.016, h * 0.008, 0.5 * (k ? 1 : -1), 0, TAU); c.fill(); c.stroke();
|
|
230
|
+
c.restore(); c.globalAlpha = 1;
|
|
231
|
+
};
|
|
232
|
+
arm(face > 0 ? shL : shR, hands[0], 0); arm(face > 0 ? shR : shL, hands[1], 1);
|
|
233
|
+
|
|
234
|
+
// --- 头 ---
|
|
235
|
+
if (halo) { c.fillStyle = 'rgba(214,170,70,.95)'; c.beginPath(); c.arc(head[0], head[1], hh * 1.55, 0, TAU); c.fill(); c.strokeStyle = 'rgba(120,80,30,.8)'; c.lineWidth = 2; c.stroke(); }
|
|
236
|
+
c.fillStyle = css(hairC); c.beginPath(); c.ellipse(head[0] - face * hw * 0.1, head[1] - hh * 0.12, hw * 1.12, hh * 1.06, 0, 0, TAU); c.fill();
|
|
237
|
+
const fg = c.createLinearGradient(head[0] - hw, head[1], head[0] + hw, head[1]); fg.addColorStop(0, css(mixc(skin, [255, 235, 205], 0.25))); fg.addColorStop(1, css(mixc(skin, [90, 50, 30], 0.35)));
|
|
238
|
+
c.fillStyle = fg; c.beginPath(); c.ellipse(head[0] + face * hw * 0.1, head[1] + hh * 0.1 + h * 0.008 * bow, hw * 0.92, hh * 0.92, 0, 0, TAU); c.fill();
|
|
239
|
+
c.strokeStyle = line; c.globalAlpha = 0.65; c.lineWidth = Math.max(1.2, h * 0.005); c.stroke(); c.globalAlpha = 1;
|
|
240
|
+
// 眼:杏仁形,眼珠偏向 gaze;鼻:一道暗线;口:短线
|
|
241
|
+
const ey = head[1] + hh * 0.12 + h * 0.01 * bow, ex = head[0] + face * hw * 0.22;
|
|
242
|
+
let gx = 0, gy = 0; if (gaze) { const dd = Math.hypot(gaze[0] - ex, gaze[1] - ey) || 1; gx = (gaze[0] - ex) / dd; gy = (gaze[1] - ey) / dd; }
|
|
243
|
+
[-1, 1].forEach(sd => {
|
|
244
|
+
const px = ex + sd * hw * 0.38 * (face > 0 ? 1 : 1) - 0, ew = hw * 0.26;
|
|
245
|
+
c.fillStyle = '#f4ead4'; c.beginPath(); c.ellipse(px, ey, ew, ew * 0.5, 0, 0, TAU); c.fill();
|
|
246
|
+
c.fillStyle = '#2a1c14'; c.beginPath(); c.arc(px + gx * ew * 0.45, ey + gy * ew * 0.25, ew * 0.42, 0, TAU); c.fill();
|
|
247
|
+
c.strokeStyle = line; c.lineWidth = Math.max(1, h * 0.0045); c.beginPath(); c.ellipse(px, ey, ew, ew * 0.5, 0, Math.PI * 1.05, Math.PI * 1.95); c.stroke();
|
|
248
|
+
});
|
|
249
|
+
c.strokeStyle = css(hairC, 0.85); c.lineWidth = Math.max(1.4, h * 0.006); [-1, 1].forEach(sd => { const px = ex + sd * hw * 0.38; c.beginPath(); c.moveTo(px - hw * 0.26, ey - hh * 0.2 + (sd * face > 0 ? 1 : 0) * 2); c.quadraticCurveTo(px, ey - hh * 0.34, px + hw * 0.26, ey - hh * 0.18); c.stroke(); });
|
|
250
|
+
c.fillStyle = css(mixc(skin, [90, 40, 25], 0.28), 0.5); c.beginPath(); c.ellipse(head[0] + face * hw * 0.5, head[1] + hh * 0.55, hw * 0.3, hh * 0.2, 0, 0, TAU); c.fill(); // 颧红/阴影
|
|
251
|
+
c.fillStyle = css(skin); c.beginPath(); c.ellipse(head[0] - face * hw * 0.82, head[1] + hh * 0.2, hw * 0.14, hh * 0.24, 0, 0, TAU); c.fill();
|
|
252
|
+
if (o.beard) { c.fillStyle = css(mixc(hairC, [190, 180, 165], 0.45), 0.96); c.beginPath(); c.moveTo(head[0] - hw * 0.8, head[1] + hh * 0.28); c.quadraticCurveTo(head[0] - hw * 0.7, head[1] + hh * 1.35, head[0], head[1] + hh * 1.55); c.quadraticCurveTo(head[0] + hw * 0.7, head[1] + hh * 1.35, head[0] + hw * 0.8, head[1] + hh * 0.28); c.quadraticCurveTo(head[0] + hw * 0.35, head[1] + hh * 0.62, head[0], head[1] + hh * 0.6); c.quadraticCurveTo(head[0] - hw * 0.35, head[1] + hh * 0.62, head[0] - hw * 0.8, head[1] + hh * 0.28); c.fill(); }
|
|
253
|
+
c.strokeStyle = css(mixc(skin, [60, 30, 20], 0.5), 0.8); c.lineWidth = Math.max(1, h * 0.0045);
|
|
254
|
+
c.beginPath(); c.moveTo(ex + face * hw * 0.04, ey + hh * 0.08); c.lineTo(ex + face * hw * 0.08, ey + hh * 0.42); c.lineTo(ex - face * hw * 0.05, ey + hh * 0.46); c.stroke();
|
|
255
|
+
c.beginPath(); c.moveTo(ex - hw * 0.14, ey + hh * 0.7); c.quadraticCurveTo(ex, ey + hh * 0.74, ex + hw * 0.14, ey + hh * 0.68); c.stroke();
|
|
256
|
+
c.restore();
|
|
257
|
+
};
|
|
258
|
+
})();
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
// 动效时间库 MO:缓动、弹簧、步进、错开、拍号。全部是「时间 → 数值」的纯函数,不读时钟,确定性。
|
|
2
|
+
// 来源:YouTube 解说动画语法 8 支示范片(references/09-视频动画语法.md)。约定:p 是 0..1 的进度,t/lt 是秒。
|
|
3
|
+
// 选缓动的一句话:讲解(3b1b/白板)用 smooth/sineInOut 两端都停稳;发布会用 appleOut/弹簧;动态文字用 expoOut+弹簧;
|
|
4
|
+
// 拼贴(Vox)相机用 sineInOut 或长尾 longTail,纸片按 12fps 步进;Kurzgesagt 动作用 k75,循环用 easyEase。
|
|
5
|
+
(() => {
|
|
6
|
+
const { clamp, lerp } = U;
|
|
7
|
+
const MO = window.MO = {};
|
|
8
|
+
|
|
9
|
+
// 画布尺寸 U.setStage / U.onStage 在 util.js(比本文件先加载的 paint/brush/render/post 也要登记)。
|
|
10
|
+
|
|
11
|
+
// ---------- 时间片 ----------
|
|
12
|
+
MO.seg = (t, a, b) => clamp((t - a) / (b - a)); // [a,b] → 0..1
|
|
13
|
+
MO.at = (t, t0, dur) => clamp((t - t0) / dur); // [t0, t0+dur] → 0..1
|
|
14
|
+
MO.anim = (lt, start, dur, ease = MO.smooth) => ease(clamp((lt - start) / dur));
|
|
15
|
+
// 步进时间:把连续时间量化成 fps 步。拼贴纸片/打字/荧光笔「一拍二」= 12fps;故事型角色 24fps;相机仍按 60fps 平滑。
|
|
16
|
+
MO.step = (t, fps = 12) => Math.floor(t * fps + 1e-6) / fps;
|
|
17
|
+
|
|
18
|
+
// ---------- manim 缓动(3b1b/manim rate_functions.py) ----------
|
|
19
|
+
// smooth = 6t⁵−15t⁴+10t³:两端速度、加速度都为 0。讲解类片子的默认缓动。
|
|
20
|
+
MO.smooth = t => { t = clamp(t); return t * t * t * (10 - 15 * t + 6 * t * t); };
|
|
21
|
+
const sig = x => 1 / (1 + Math.exp(-x));
|
|
22
|
+
MO.smoothCE = (t, inflection = 10) => { const e = sig(-inflection / 2); return clamp((sig(inflection * (t - 0.5)) - e) / (1 - 2 * e)); }; // ManimCE 版
|
|
23
|
+
MO.thereAndBack = t => MO.smooth(t < 0.5 ? 2 * t : 2 * (1 - t)); // Indicate 用:去了又回
|
|
24
|
+
MO.rushInto = t => 2 * MO.smooth(t / 2); // 慢起、冲进终点
|
|
25
|
+
MO.rushFrom = t => 2 * MO.smooth(t / 2 + 0.5) - 1; // 冲出、慢收
|
|
26
|
+
MO.doubleSmooth = t => t < 0.5 ? 0.5 * MO.smooth(2 * t) : 0.5 * (1 + MO.smooth(2 * t - 1));
|
|
27
|
+
MO.wiggle = (t, wiggles = 2) => MO.thereAndBack(t) * Math.sin(wiggles * Math.PI * t);
|
|
28
|
+
// manim LaggedStart(lag_ratio=r):n 个子动画里第 i 个在总进度 p 下的局部进度。总长 = 1+(n-1)·r 个单位,第 i 个占 [i·r, i·r+1]。
|
|
29
|
+
MO.lagged = (i, n, p, r = 0.05) => clamp(p * (1 + (n - 1) * r) - i * r);
|
|
30
|
+
|
|
31
|
+
// ---------- 常用解析缓动(AE / CSS 名字) ----------
|
|
32
|
+
MO.linear = p => p;
|
|
33
|
+
MO.sineInOut = p => -(Math.cos(Math.PI * p) - 1) / 2;
|
|
34
|
+
MO.cubicIn = p => p * p * p;
|
|
35
|
+
MO.cubicOut = p => 1 - Math.pow(1 - p, 3);
|
|
36
|
+
MO.cubicInOut = p => p < .5 ? 4 * p * p * p : 1 - Math.pow(-2 * p + 2, 3) / 2;
|
|
37
|
+
MO.quartOut = p => 1 - Math.pow(1 - p, 4);
|
|
38
|
+
MO.quintOut = p => 1 - Math.pow(1 - p, 5);
|
|
39
|
+
MO.quintInOut = p => p < .5 ? 16 * p ** 5 : 1 - Math.pow(-2 * p + 2, 5) / 2;
|
|
40
|
+
MO.expoIn = p => p <= 0 ? 0 : Math.pow(2, 10 * p - 10);
|
|
41
|
+
MO.expoOut = p => p >= 1 ? 1 : 1 - Math.pow(2, -10 * p); // 动效设计最常用的「快出慢停」
|
|
42
|
+
MO.expoInOut = p => p <= 0 ? 0 : p >= 1 ? 1 : p < .5 ? Math.pow(2, 20 * p - 10) / 2 : (2 - Math.pow(2, -20 * p + 10)) / 2;
|
|
43
|
+
// backOut 过冲:s=1.70158 约 10%;2.2 约 13%;2.6 约 17%;3.5 约 23%
|
|
44
|
+
MO.backOut = (p, s = 1.70158) => { const q = p - 1; return 1 + (s + 1) * q * q * q + s * q * q; };
|
|
45
|
+
// 弹性落定:overshoot 一次再回(贴纸「啪」地落下)
|
|
46
|
+
MO.elasticOut = p => p <= 0 ? 0 : p >= 1 ? 1 : Math.pow(2, -10 * p) * Math.sin((p * 10 - 0.75) * (2 * Math.PI / 3)) + 1;
|
|
47
|
+
|
|
48
|
+
// ---------- CSS cubic-bezier(x1,y1,x2,y2):牛顿法由 x 求参数,再取 y ----------
|
|
49
|
+
MO.bezier = (x1, y1, x2, y2) => {
|
|
50
|
+
const cx = 3 * x1, bx = 3 * (x2 - x1) - cx, ax = 1 - cx - bx;
|
|
51
|
+
const cy = 3 * y1, by = 3 * (y2 - y1) - cy, ay = 1 - cy - by;
|
|
52
|
+
const X = s => ((ax * s + bx) * s + cx) * s, Y = s => ((ay * s + by) * s + cy) * s, dX = s => (3 * ax * s + 2 * bx) * s + cx;
|
|
53
|
+
return p => {
|
|
54
|
+
if (p <= 0) return 0; if (p >= 1) return 1;
|
|
55
|
+
let s = p; for (let k = 0; k < 8; k++) { const e = X(s) - p, d = dX(s); if (Math.abs(e) < 1e-6 || Math.abs(d) < 1e-6) break; s -= e / d; }
|
|
56
|
+
s = clamp(s); return Y(s);
|
|
57
|
+
};
|
|
58
|
+
};
|
|
59
|
+
MO.appleOut = MO.bezier(0.25, 0.1, 0.25, 1); // CSS `ease`:苹果官网/发布会过渡
|
|
60
|
+
MO.emphasized = MO.bezier(0.2, 0, 0, 1); // Material 3 emphasized:长尾减速
|
|
61
|
+
MO.k75 = MO.bezier(0.75, 0, 0.25, 1); // Kurzgesagt「其他动作用 75% influence」(y1 调研 §6.2)
|
|
62
|
+
MO.easyEase = MO.bezier(0.333, 0, 0.667, 1); // AE Easy Ease:循环动作
|
|
63
|
+
MO.longTail = MO.bezier(0.33, 0, 0.2, 1); // Vox「沿线追过去」「拉出揭示」:起步快、长尾落定(y2 调研)
|
|
64
|
+
|
|
65
|
+
// ---------- 弹簧 ----------
|
|
66
|
+
// SwiftUI 参数化(WWDC23 Animate with springs):stiffness=(2π/duration)²,damping=4π(1-bounce)/duration → ζ=1-bounce,ω=2π/duration。
|
|
67
|
+
// 返回 0→1 的位移(可 >1 = 过冲)。t 单位秒。发布会 UI 用它:.smooth(bounce 0) / .snappy(0.15) / .bouncy(0.3)。
|
|
68
|
+
MO.spring = (t, { duration = 0.5, bounce = 0, v0 = 0 } = {}) => {
|
|
69
|
+
if (t <= 0) return 0;
|
|
70
|
+
const w = 2 * Math.PI / duration, z = 1 - bounce;
|
|
71
|
+
let x; // x = 剩余位移(1→0)
|
|
72
|
+
if (Math.abs(z - 1) < 1e-4) x = Math.exp(-w * t) * (1 + (w - v0) * t);
|
|
73
|
+
else if (z < 1) { const wd = w * Math.sqrt(1 - z * z); x = Math.exp(-z * w * t) * (Math.cos(wd * t) + ((z * w - v0) / wd) * Math.sin(wd * t)); }
|
|
74
|
+
else { const r = Math.sqrt(z * z - 1), a = -w * (z - r), b = -w * (z + r); const A = (-v0 - b) / (a - b); x = A * Math.exp(a * t) + (1 - A) * Math.exp(b * t); }
|
|
75
|
+
return 1 - x;
|
|
76
|
+
};
|
|
77
|
+
MO.smoothSpring = t => MO.spring(t, { duration: 0.5, bounce: 0 });
|
|
78
|
+
MO.snappy = t => MO.spring(t, { duration: 0.5, bounce: 0.15 });
|
|
79
|
+
MO.bouncy = t => MO.spring(t, { duration: 0.5, bounce: 0.3 });
|
|
80
|
+
// 频率/衰减参数化的欠阻尼弹簧阶跃响应(动态文字用):freq Hz、decay 1/s。
|
|
81
|
+
// freq 3 / decay 6 第一次过冲 ≈37%(砸进来的大字);2.5 / 9 ≈16%(常规弹入);2.2 / 10 ≈8%(主词落定)
|
|
82
|
+
MO.springHz = (t, freq = 2.5, decay = 9) => t <= 0 ? 0 : 1 - Math.exp(-decay * t) * Math.cos(2 * Math.PI * freq * t);
|
|
83
|
+
// 到位后的余振(Dan Ebberts 惯性回弹):amp·sin(2πft)/e^(decay·t)。换姿势的身体挤压、屏幕震动都用它。
|
|
84
|
+
MO.settle = (t, amp, freq = 3, decay = 5) => t <= 0 ? 0 : amp * Math.sin(2 * Math.PI * freq * t) / Math.exp(decay * t);
|
|
85
|
+
|
|
86
|
+
// ---------- 镜头动作时长(什么时候用哪一种,见 references/镜头语言.md) ----------
|
|
87
|
+
// 两类:过渡(缓推 push、找下一件 glide、拉远 pull,0.6–1.5s 缓入缓出)是默认;重音(快推 punch、快移 pan、砸入 slam)留给揭晓、数字、反转。
|
|
88
|
+
// 过渡的数取自参考片实测:Vox 推进 1.0–1.5s、找卡 0.5–0.9s、拉出约 1s;3b1b 推镜约 1s。
|
|
89
|
+
// 重音的数:一支「每镜匀速推近 7.5%」被判像 PPT 的片子,改成停住→0.28s 快推→停住后被认可;但 2026-10-12 诺奖短片每 2 秒一次快推,被判「太多、太快、不 smooth」。
|
|
90
|
+
// 时长常量集中在这里,CAM.track / MD / 各片段都读它;要改节奏改这里,别在片段里另写一套。
|
|
91
|
+
MO.PULSE = { punch: 0.28, pan: 0.30, slam: 0.17, k: 0.25, kMin: 0.18, kMax: 0.32, push: 0.9, glide: 0.7, pull: 1.1 };
|
|
92
|
+
// 砸入曲线:k = 0..1(砸入进度)→ 缩放。1.55 → 0.94(前 60%,3 帧砸下)→ 1.0(后 40%,2 帧回弹)。k≤0 返回 1.55(还没落下)。
|
|
93
|
+
MO.slamK = (k, from = 1.55, under = 0.94) => k <= 0 ? from : k < 0.6 ? from - (from - under) * (k / 0.6) : k < 1 ? under + (1 - under) * ((k - 0.6) / 0.4) : 1;
|
|
94
|
+
// 某个元素在 at 这一帧砸进来:返回它此刻的缩放(at 之前 = 0,元素还不该画)。o: {dur=0.17, from=1.55, under=0.94}
|
|
95
|
+
MO.slam = (t, at, o = {}) => t < at ? 0 : MO.slamK((t - at) / (o.dur || MO.PULSE.slam), o.from, o.under);
|
|
96
|
+
// 动作进度:at 起 dur 秒内按 ease 从 0 走到 1,之后停在 1(默认快推的时长和曲线;缓推写 MO.pulse(t, at, MO.PULSE.push, MO.sineInOut))
|
|
97
|
+
MO.pulse = (t, at, dur = MO.PULSE.punch, ease = MO.cubicInOut) => ease(clamp((t - at) / dur));
|
|
98
|
+
|
|
99
|
+
// ---------- 持续微动 ----------
|
|
100
|
+
// 只给环境层(星闪、粒子、远景云)和艺术风格画面用。解说片的主体、标签、卡片落定后停住,不加浮动、呼吸。
|
|
101
|
+
// 漂浮:两频正弦叠加。周期避开 5s 左右(HIG:~0.2Hz 的持续摆动让人不适)
|
|
102
|
+
MO.float = (t, amp = 6, period = 4, ph = 0) => amp * (0.75 * Math.sin(2 * Math.PI * t / period + ph) + 0.25 * Math.sin(2 * Math.PI * t / (period * 0.53) + ph * 1.7));
|
|
103
|
+
|
|
104
|
+
// ---------- 反查与拍号 ----------
|
|
105
|
+
// 缓动的反函数(二分):「线头/计数在什么时刻到达 y」。例:折线按 smooth 画,求画到某个 x 的时刻(t3 镜 2 的事件标注)。
|
|
106
|
+
MO.invert = (ease, y, iters = 30) => { let a = 0, b = 1; for (let i = 0; i < iters; i++) { const m = (a + b) / 2; if (ease(m) < y) a = m; else b = m; } return (a + b) / 2; };
|
|
107
|
+
// 拍 → 秒
|
|
108
|
+
MO.beat = (n, bpm = 120) => n * 60 / bpm;
|
|
109
|
+
// 揭开那一帧落在拍上:段起点 = 小节线 − 揭开点 × 转场时长(y5:zoom 揭开点 .55、色带 .6、push 到 90% 位移在 .58)
|
|
110
|
+
MO.onBeat = (barTime, revealP, trDur) => barTime - revealP * trDur;
|
|
111
|
+
})();
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
// 纯代码绘画工具库:噪声、纹理、手绘线、风格渲染器(笔触/色点/马赛克/网点/像素/切面)、共享编舞。
|
|
2
|
+
// 约定:所有随机都用种子;「抖动线条」按 boilFps 换种子(手绘动画的 boiling line),其余保持帧间稳定。
|
|
3
|
+
(() => {
|
|
4
|
+
let W = 1920, H = 1080; U.onStage((w, h) => { W = w; H = h; }); // 画布尺寸跟 U.setStage 走(默认 1920×1080)
|
|
5
|
+
const P = window.PAINT = {};
|
|
6
|
+
const { clamp, lerp, ease, rng } = U;
|
|
7
|
+
|
|
8
|
+
// ---------- 画布 ----------
|
|
9
|
+
P.canvas = (w = W, h = H) => { const c = document.createElement('canvas'); c.width = w; c.height = h; return c; };
|
|
10
|
+
const CACHE = {};
|
|
11
|
+
P.cached = (key, w, h, fn) => { if (!CACHE[key]) { const c = P.canvas(w, h); fn(c.getContext('2d'), c); CACHE[key] = c; } return CACHE[key]; };
|
|
12
|
+
P.scratch = (key) => { if (!CACHE['s_' + key]) CACHE['s_' + key] = P.canvas(); return CACHE['s_' + key]; };
|
|
13
|
+
|
|
14
|
+
// ---------- 噪声 ----------
|
|
15
|
+
const perm = new Uint8Array(512); { const r = rng(1337); const p = [...Array(256).keys()]; for (let i = 255; i > 0; i--) { const j = (r() * (i + 1)) | 0; [p[i], p[j]] = [p[j], p[i]]; } for (let i = 0; i < 512; i++) perm[i] = p[i & 255]; }
|
|
16
|
+
const fade = t => t * t * t * (t * (t * 6 - 15) + 10);
|
|
17
|
+
const grad = (h, x, y) => { const u = (h & 1) ? x : -x, v = (h & 2) ? y : -y; return (h & 4) ? u + v * 0.5 : u * 0.5 + v; };
|
|
18
|
+
P.noise = (x, y) => { // Perlin 2D,约 [-1,1]
|
|
19
|
+
const X = Math.floor(x) & 255, Y = Math.floor(y) & 255; x -= Math.floor(x); y -= Math.floor(y);
|
|
20
|
+
const u = fade(x), v = fade(y), a = perm[X] + Y, b = perm[X + 1] + Y;
|
|
21
|
+
return lerp(lerp(grad(perm[a], x, y), grad(perm[b], x - 1, y), u), lerp(grad(perm[a + 1], x, y - 1), grad(perm[b + 1], x - 1, y - 1), u), v);
|
|
22
|
+
};
|
|
23
|
+
P.fbm = (x, y, o = 4) => { let s = 0, a = 0.5, f = 1; for (let i = 0; i < o; i++) { s += a * P.noise(x * f, y * f); a *= 0.5; f *= 2; } return s; };
|
|
24
|
+
|
|
25
|
+
// ---------- 颜色 ----------
|
|
26
|
+
P.hex = h => { h = h.replace('#', ''); if (h.length === 3) h = h.split('').map(x => x + x).join(''); const n = parseInt(h, 16); return [n >> 16 & 255, n >> 8 & 255, n & 255]; };
|
|
27
|
+
P.rgb = (c, a = 1) => `rgba(${c[0] | 0},${c[1] | 0},${c[2] | 0},${a})`;
|
|
28
|
+
P.mix = (a, b, t) => [lerp(a[0], b[0], t), lerp(a[1], b[1], t), lerp(a[2], b[2], t)];
|
|
29
|
+
P.jitter = (c, r, amt) => [c[0] + (r() - .5) * amt, c[1] + (r() - .5) * amt, c[2] + (r() - .5) * amt];
|
|
30
|
+
|
|
31
|
+
// ---------- 纹理(缓存) ----------
|
|
32
|
+
// 纸/墙/岩石:底色 + 多层噪声斑驳 + 细颗粒
|
|
33
|
+
P.texture = (key, base, opt = {}) => P.cached('tex_' + key, W, H, (g) => {
|
|
34
|
+
const { scale = 0.004, amt = 26, grain = 14, dark = null, seed = 1 } = opt;
|
|
35
|
+
const img = g.createImageData(W, H), d = img.data, b = P.hex(base), r = rng(seed);
|
|
36
|
+
const dk = dark ? P.hex(dark) : null;
|
|
37
|
+
for (let y = 0; y < H; y++) for (let x = 0; x < W; x++) {
|
|
38
|
+
const n = P.fbm(x * scale + seed, y * scale, 5), gr = (r() - .5) * grain;
|
|
39
|
+
const i = (y * W + x) * 4;
|
|
40
|
+
let c = b;
|
|
41
|
+
if (dk) c = P.mix(b, dk, clamp(n * 1.2 + 0.25));
|
|
42
|
+
d[i] = c[0] + n * amt + gr; d[i + 1] = c[1] + n * amt + gr; d[i + 2] = c[2] + n * amt * 0.8 + gr; d[i + 3] = 255;
|
|
43
|
+
}
|
|
44
|
+
g.putImageData(img, 0, 0);
|
|
45
|
+
});
|
|
46
|
+
// 颗粒噪点叠层(扁平插画的 grain、旧印刷):透明底上的随机点
|
|
47
|
+
P.grain = (key, density = 0.08, col = [0, 0, 0], alpha = 0.18) => P.cached('grain_' + key, W, H, (g) => {
|
|
48
|
+
const img = g.createImageData(W, H), d = img.data, r = rng(key.length * 97 + 3);
|
|
49
|
+
for (let i = 0; i < W * H; i++) if (r() < density) { d[i * 4] = col[0]; d[i * 4 + 1] = col[1]; d[i * 4 + 2] = col[2]; d[i * 4 + 3] = 255 * alpha * (0.4 + r() * 0.6); }
|
|
50
|
+
g.putImageData(img, 0, 0);
|
|
51
|
+
});
|
|
52
|
+
// 龟裂纹(古典油画):随机游走的细裂线网
|
|
53
|
+
P.craquelure = (key, alpha = 0.35) => P.cached('crack_' + key, W, H, (g) => {
|
|
54
|
+
const r = rng(77); g.strokeStyle = `rgba(40,25,10,${alpha})`; g.lineWidth = 0.8;
|
|
55
|
+
for (let k = 0; k < 900; k++) {
|
|
56
|
+
let x = r() * W, y = r() * H, a = r() * Math.PI * 2; g.beginPath(); g.moveTo(x, y);
|
|
57
|
+
for (let s = 0; s < 6 + r() * 10; s++) { a += (r() - .5) * 1.6; x += Math.cos(a) * (6 + r() * 14); y += Math.sin(a) * (6 + r() * 14); g.lineTo(x, y); }
|
|
58
|
+
g.stroke();
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// ---------- 手绘线 ----------
|
|
63
|
+
// 把折线/曲线采样后加噪声抖动;boil = 当前时间换种子的频率(0 = 不抖)
|
|
64
|
+
P.boilSeed = (t, fps = 12) => Math.floor(t * fps);
|
|
65
|
+
P.roughPath = (c, pts, { amp = 2, seed = 1, closed = false, step = 6 } = {}) => {
|
|
66
|
+
const r = rng(seed);
|
|
67
|
+
// 先按 step 重采样
|
|
68
|
+
const out = [];
|
|
69
|
+
const N = closed ? pts.length : pts.length - 1;
|
|
70
|
+
for (let i = 0; i < N; i++) {
|
|
71
|
+
const a = pts[i], b = pts[(i + 1) % pts.length], L = Math.hypot(b[0] - a[0], b[1] - a[1]), n = Math.max(1, Math.round(L / step));
|
|
72
|
+
for (let k = 0; k < n; k++) { const q = k / n; out.push([lerp(a[0], b[0], q) + (r() - .5) * amp, lerp(a[1], b[1], q) + (r() - .5) * amp]); }
|
|
73
|
+
}
|
|
74
|
+
if (!closed) out.push([pts[pts.length - 1][0] + (r() - .5) * amp, pts[pts.length - 1][1] + (r() - .5) * amp]);
|
|
75
|
+
c.beginPath(); out.forEach((p, i) => i ? c.lineTo(p[0], p[1]) : c.moveTo(p[0], p[1])); if (closed) c.closePath();
|
|
76
|
+
};
|
|
77
|
+
// 三次贝塞尔采样成点列(便于 roughPath / 变形)
|
|
78
|
+
P.bez = (p0, p1, p2, p3, n = 24) => { const o = []; for (let i = 0; i <= n; i++) { const t = i / n, u = 1 - t; o.push([u * u * u * p0[0] + 3 * u * u * t * p1[0] + 3 * u * t * t * p2[0] + t * t * t * p3[0], u * u * u * p0[1] + 3 * u * u * t * p1[1] + 3 * u * t * t * p2[1] + t * t * t * p3[1]]); } return o; };
|
|
79
|
+
// SVG path 字符串 → Path2D(写复杂轮廓最省事)
|
|
80
|
+
P.path = (d) => new Path2D(d);
|
|
81
|
+
|
|
82
|
+
// ---------- 风格渲染器(对一张底稿 canvas 做「重画」) ----------
|
|
83
|
+
// 1) 流场笔触(梵高/印象派):在抖动网格上取底稿颜色,沿 angle(x,y,t) 画短笔触,带深色描边
|
|
84
|
+
// cap:宽笔(width ≥ 30)密排时圆头会连成一排排「人头/鹅卵石」(迁移测试 D 蓝色时期),改 'butt'。
|
|
85
|
+
// boil:只给母题区域沸腾,静止区(地板、道具、角色)boil=0 种子固定——全屏 8fps 换种子读成满屏闪烁(迁移测试 C 莫奈)。
|
|
86
|
+
P.strokes = (dst, src, { cell = 12, len = 22, width = 7, angle, seed = 1, t = 0, boil = 8, outline = 0.35, outlineCol = [20, 25, 60], outlineMix = 0.55, jitterCol = 18, mask, alphaMask = false, palette, cap = 'round' } = {}) => {
|
|
87
|
+
const sd = src.getContext('2d').getImageData(0, 0, W, H).data;
|
|
88
|
+
const r = rng(seed * 1000 + P.boilSeed(t, boil));
|
|
89
|
+
dst.lineCap = cap;
|
|
90
|
+
for (let y = 0; y < H + cell; y += cell) for (let x = 0; x < W + cell; x += cell) {
|
|
91
|
+
const px = x + (r() - .5) * cell, py = y + (r() - .5) * cell;
|
|
92
|
+
const ix = clamp(px | 0, 0, W - 1), iy = clamp(py | 0, 0, H - 1), i = (iy * W + ix) * 4;
|
|
93
|
+
if (mask && !mask(px, py)) continue;
|
|
94
|
+
if (alphaMask && sd[i + 3] < 200) continue;
|
|
95
|
+
let col = [sd[i], sd[i + 1], sd[i + 2]];
|
|
96
|
+
const pc = palette ? palette(px, py, col, r) : null; if (pc) col = pc; // 区域色板:从色组里抽一个颜色
|
|
97
|
+
const a = angle ? angle(px, py, t) : 0, l = len * (0.7 + r() * 0.6);
|
|
98
|
+
const dx = Math.cos(a) * l / 2, dy = Math.sin(a) * l / 2;
|
|
99
|
+
if (outline > 0) { dst.strokeStyle = P.rgb(P.mix(col, outlineCol, outlineMix), outline); dst.lineWidth = width + 2.5; dst.beginPath(); dst.moveTo(px - dx, py - dy); dst.lineTo(px + dx, py + dy); dst.stroke(); }
|
|
100
|
+
dst.strokeStyle = P.rgb(pc ? col : P.jitter(col, r, jitterCol)); dst.lineWidth = width;
|
|
101
|
+
dst.beginPath(); dst.moveTo(px - dx, py - dy); dst.lineTo(px + dx, py + dy); dst.stroke();
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
// 色组工具:给一组 hex 和权重,返回按随机数抽色的函数(配 strokes 的 palette 用);mixBase 把底稿色混进来保留明暗
|
|
105
|
+
P.swatch = (hexes, mixBase = 0.35) => { const cs = hexes.map(P.hex); return (col, r) => { const k = cs[(r() * cs.length) | 0]; return P.mix(k, col, mixBase); }; };
|
|
106
|
+
// 2) 色点(印象派):椭圆短点,颜色带冷暖偏移、大小不一。注意这不是点彩——修拉的点彩是「等大点+纯色板视觉混色」,用 P.pointillism(lib/render.js)
|
|
107
|
+
P.dabs = (dst, src, { cell = 10, size = 9, seed = 2, t = 0, boil = 8, angle = -0.6, shift = 22 } = {}) => {
|
|
108
|
+
const sd = src.getContext('2d').getImageData(0, 0, W, H).data, r = rng(seed * 1000 + P.boilSeed(t, boil));
|
|
109
|
+
for (let y = 0; y < H + cell; y += cell) for (let x = 0; x < W + cell; x += cell) {
|
|
110
|
+
const px = x + (r() - .5) * cell * 1.2, py = y + (r() - .5) * cell * 1.2;
|
|
111
|
+
const i = (clamp(py | 0, 0, H - 1) * W + clamp(px | 0, 0, W - 1)) * 4;
|
|
112
|
+
const warm = r() < 0.5 ? [shift, shift * 0.5, -shift] : [-shift * 0.6, 0, shift];
|
|
113
|
+
dst.fillStyle = `rgb(${sd[i] + warm[0]},${sd[i + 1] + warm[1]},${sd[i + 2] + warm[2]})`;
|
|
114
|
+
dst.beginPath(); dst.ellipse(px, py, size * (0.8 + r() * 0.6), size * 0.45, angle + (r() - .5) * 0.5, 0, Math.PI * 2); dst.fill();
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
// 3) 马赛克:带缝隙的小石块,颜色取中心,略有抖动与倒角
|
|
118
|
+
P.mosaic = (dst, src, { tile = 14, grout = '#3b352e', seed = 3, jitter = 0.25 } = {}) => {
|
|
119
|
+
const sd = src.getContext('2d').getImageData(0, 0, W, H).data, r = rng(seed);
|
|
120
|
+
dst.fillStyle = grout; dst.fillRect(0, 0, W, H);
|
|
121
|
+
for (let y = 0; y < H; y += tile) { const off = (y / tile) % 2 ? tile * 0.5 * jitter : 0; for (let x = -tile; x < W; x += tile) {
|
|
122
|
+
const cx = x + off + tile / 2, cy = y + tile / 2, i = (clamp(cy | 0, 0, H - 1) * W + clamp(cx | 0, 0, W - 1)) * 4;
|
|
123
|
+
const c = P.jitter([sd[i], sd[i + 1], sd[i + 2]], r, 14), s = tile * (0.82 + r() * 0.1);
|
|
124
|
+
dst.fillStyle = P.rgb(c); dst.fillRect(cx - s / 2 + (r() - .5) * 1.5, cy - s / 2 + (r() - .5) * 1.5, s, s);
|
|
125
|
+
dst.fillStyle = 'rgba(255,255,255,.12)'; dst.fillRect(cx - s / 2, cy - s / 2, s, 2);
|
|
126
|
+
} }
|
|
127
|
+
};
|
|
128
|
+
// 4) 网点(本戴点):颜色取中心,半径随暗度
|
|
129
|
+
P.halftone = (dst, src, { cell = 14, paper = '#f4ecd8', seed = 4 } = {}) => {
|
|
130
|
+
const sd = src.getContext('2d').getImageData(0, 0, W, H).data;
|
|
131
|
+
dst.fillStyle = paper; dst.fillRect(0, 0, W, H);
|
|
132
|
+
for (let y = 0; y < H + cell; y += cell) for (let x = 0; x < W + cell; x += cell) {
|
|
133
|
+
const cx = x + ((y / cell) % 2) * cell / 2, i = (clamp(y | 0, 0, H - 1) * W + clamp(cx | 0, 0, W - 1)) * 4;
|
|
134
|
+
const L = (sd[i] * .3 + sd[i + 1] * .59 + sd[i + 2] * .11) / 255;
|
|
135
|
+
dst.fillStyle = `rgb(${sd[i]},${sd[i + 1]},${sd[i + 2]})`;
|
|
136
|
+
dst.beginPath(); dst.arc(cx, y, cell * 0.72 * (0.3 + (1 - L) * 0.9), 0, Math.PI * 2); dst.fill();
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
// 5) 像素:最近邻降采样,可选调色板量化
|
|
140
|
+
P.pixelate = (dst, src, { size = 8, palette } = {}) => {
|
|
141
|
+
const w = Math.ceil(W / size), h = Math.ceil(H / size), s = P.scratch('px'), g = s.getContext('2d');
|
|
142
|
+
g.imageSmoothingEnabled = false; g.clearRect(0, 0, w, h); g.drawImage(src, 0, 0, w, h);
|
|
143
|
+
if (palette) { const pal = palette.map(P.hex), im = g.getImageData(0, 0, w, h), d = im.data;
|
|
144
|
+
for (let i = 0; i < d.length; i += 4) { let best = 0, bd = 1e9; for (let k = 0; k < pal.length; k++) { const q = pal[k], dd = (d[i] - q[0]) ** 2 + (d[i + 1] - q[1]) ** 2 + (d[i + 2] - q[2]) ** 2; if (dd < bd) { bd = dd; best = k; } } d[i] = pal[best][0]; d[i + 1] = pal[best][1]; d[i + 2] = pal[best][2]; }
|
|
145
|
+
g.putImageData(im, 0, 0); }
|
|
146
|
+
dst.imageSmoothingEnabled = false; dst.drawImage(s, 0, 0, w, h, 0, 0, w * size, h * size); dst.imageSmoothingEnabled = true;
|
|
147
|
+
};
|
|
148
|
+
// 6) 立体主义切面:抖动三角网,每块取「偏移视角」的颜色做线性渐变,描暗边
|
|
149
|
+
P.facets = (dst, src, { nx = 22, ny = 13, seed = 5, t = 0, shiftAmt = 26, edge = 'rgba(40,30,20,.45)' } = {}) => {
|
|
150
|
+
const sd = src.getContext('2d').getImageData(0, 0, W, H).data, r = rng(seed);
|
|
151
|
+
const pts = []; for (let j = 0; j <= ny; j++) for (let i = 0; i <= nx; i++) { const e = i === 0 || j === 0 || i === nx || j === ny; pts.push([i / nx * W + (e ? 0 : (r() - .5) * W / nx * .9), j / ny * H + (e ? 0 : (r() - .5) * H / ny * .9)]); }
|
|
152
|
+
const col = (x, y) => { const i = (clamp(y | 0, 0, H - 1) * W + clamp(x | 0, 0, W - 1)) * 4; return [sd[i], sd[i + 1], sd[i + 2]]; };
|
|
153
|
+
for (let j = 0; j < ny; j++) for (let i = 0; i < nx; i++) {
|
|
154
|
+
const a = pts[j * (nx + 1) + i], b = pts[j * (nx + 1) + i + 1], c2 = pts[(j + 1) * (nx + 1) + i], d2 = pts[(j + 1) * (nx + 1) + i + 1];
|
|
155
|
+
for (const tri of (r() < .5 ? [[a, b, d2], [a, d2, c2]] : [[a, b, c2], [b, d2, c2]])) {
|
|
156
|
+
const cx = (tri[0][0] + tri[1][0] + tri[2][0]) / 3, cy = (tri[0][1] + tri[1][1] + tri[2][1]) / 3;
|
|
157
|
+
const sh = Math.sin(t * 3 + cx * 0.01) * shiftAmt * r();
|
|
158
|
+
const c0 = col(cx + sh, cy), c1 = P.mix(c0, [60, 45, 30], 0.35);
|
|
159
|
+
const g = dst.createLinearGradient(tri[0][0], tri[0][1], tri[2][0], tri[2][1]); g.addColorStop(0, P.rgb(P.mix(c0, [255, 250, 235], 0.12))); g.addColorStop(1, P.rgb(c1));
|
|
160
|
+
dst.fillStyle = g; dst.beginPath(); tri.forEach((p, k) => k ? dst.lineTo(p[0], p[1]) : dst.moveTo(p[0], p[1])); dst.closePath(); dst.fill();
|
|
161
|
+
dst.strokeStyle = edge; dst.lineWidth = 1.5; dst.stroke();
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
// ---------- 共享编舞:16 个时代用同一套动作曲线,各自用自己的画法画出来 ----------
|
|
167
|
+
// lt = 本段局部时间(秒)。返回 0..1 或角度,时代代码按需取用。
|
|
168
|
+
// 端杯循环(全片时间):2.4s 一轮——0.9s 举到嘴边、停 0.7s 喝、0.8s 放回
|
|
169
|
+
P.cupCycle = (t, period = 2.4) => { const q = ((t % period) + period) % period;
|
|
170
|
+
if (q < 0.9) return 0.5 - 0.5 * Math.cos(q / 0.9 * Math.PI); if (q < 1.6) return 1; return 0.5 + 0.5 * Math.cos((q - 1.6) / 0.8 * Math.PI); };
|
|
171
|
+
P.choreo = (lt, t) => ({
|
|
172
|
+
cupG: P.cupCycle(t), // 按全片时间的端杯(跨段连续:速通片换段时姿势不重置)
|
|
173
|
+
cup: 0.5 - 0.5 * Math.cos(Math.min(1, lt / 0.9) * Math.PI), // 端杯:0=在桌上 1=在嘴边(约 0.9s 举起)
|
|
174
|
+
sip: Math.max(0, Math.sin(lt * 6)) * 0.15, // 喝时头部微仰
|
|
175
|
+
tail: Math.sin(t * 5.2) * 0.35 + Math.sin(t * 11) * 0.08, // 猫尾摆(弧度)
|
|
176
|
+
blink: (t % 2.3) > 2.18 ? 1 : 0, // 眨眼
|
|
177
|
+
ear: Math.max(0, Math.sin(t * 9)) > 0.97 ? 1 : 0,
|
|
178
|
+
breathe: Math.sin(t * 4) * 0.012,
|
|
179
|
+
steam: t, // 热气用全局时间
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
// ---------- 通用元素 ----------
|
|
183
|
+
// 热气:几条随时间上行的正弦曲线(风格化的 S 形可传 width/color)
|
|
184
|
+
P.steam = (c, t, x, y, { h = 80, n = 3, color = 'rgba(255,255,255,.8)', width = 4, spread = 16, wobble = 10, seed = 1 } = {}) => {
|
|
185
|
+
c.save(); c.lineCap = 'round'; c.lineWidth = width;
|
|
186
|
+
for (let k = 0; k < n; k++) {
|
|
187
|
+
const ox = (k - (n - 1) / 2) * spread, ph = t * 3 + k * 2.1 + seed;
|
|
188
|
+
const g = c.createLinearGradient(0, y, 0, y - h); g.addColorStop(0, color); g.addColorStop(1, 'rgba(255,255,255,0)'); c.strokeStyle = g;
|
|
189
|
+
c.beginPath(); for (let s = 0; s <= 20; s++) { const q = s / 20; const xx = x + ox + Math.sin(ph - q * 5) * wobble * q, yy = y - q * h; s ? c.lineTo(xx, yy) : c.moveTo(xx, yy); } c.stroke();
|
|
190
|
+
}
|
|
191
|
+
c.restore();
|
|
192
|
+
};
|
|
193
|
+
// 粒子:给定种子与时间,返回确定的粒子位置(飘落/上浮)
|
|
194
|
+
P.particles = (n, seed, t, { x0 = 0, x1 = W, y0 = -40, y1 = H + 40, speed = 80, drift = 30, life = 6 } = {}) => {
|
|
195
|
+
const r = rng(seed), out = [];
|
|
196
|
+
for (let i = 0; i < n; i++) { const bx = lerp(x0, x1, r()), ph = r() * life, sp = speed * (0.6 + r() * 0.8), q = ((t + ph) % life) / life;
|
|
197
|
+
out.push({ x: bx + Math.sin((t + ph) * 1.7 + i) * drift, y: lerp(y0, y1, q), a: r() * Math.PI * 2 + t * (r() - .5) * 3, s: 0.6 + r() * 0.8, q, i }); }
|
|
198
|
+
return out;
|
|
199
|
+
};
|
|
200
|
+
})();
|