@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,555 @@
|
|
|
1
|
+
// 公元79年 庞贝马赛克(opus tessellatum / vermiculatum)——纯代码。
|
|
2
|
+
// 管线:①平涂底稿(静态部分缓存,少女/猫/火山烟/边框卷涡每帧重画)
|
|
3
|
+
// →②自写镶嵌渲染器:init 时一次性算好「石块布局」——距离场等值线上走线放石块(沿轮廓排,andamento)、
|
|
4
|
+
// 远处规则成行;每块石头是带四边独立抖动的旋转方块,用「加权 L∞ Voronoi」光栅成 ID 图,相邻石块之间自然留灰浆缝;
|
|
5
|
+
// 每像素预存斜面明暗(左上受光)+石纹颗粒
|
|
6
|
+
// →③每帧只在动态矩形(少女、猫、火山画、边框带)里按石块中心去底稿取色重填——石块不动,颜色在石块间流动。
|
|
7
|
+
SCENES['04_roman'] = (() => {
|
|
8
|
+
const W = 1920, H = 1080, { clamp, lerp, rng } = U, P = PAINT;
|
|
9
|
+
const BW = 84; // 外框波浪纹带宽
|
|
10
|
+
const C = {
|
|
11
|
+
wall: '#e4ddcd', halo: '#f5f1e8', outline: '#46322a',
|
|
12
|
+
floor: '#dac9a0', floorLine: '#8f6c40',
|
|
13
|
+
bW: '#ece6d8', bK: '#24201d',
|
|
14
|
+
red: '#a8322a', red2: '#7c221d', gold: '#d0a23c', gold2: '#9f7420',
|
|
15
|
+
sky: '#9ec0d8', sky2: '#d3e3e4', cloud: '#88847d', cloud2: '#a9a49b',
|
|
16
|
+
cone: '#4e5796', coneL: '#7c84b8', coneD: '#2f3672', ridge: '#373e7c', green: '#6f9341', green2: '#92b254', ground: '#cba344', ground2: '#a67d2a',
|
|
17
|
+
cyp: '#2b5a2f', cyp2: '#4b7c3c', lava: '#d64a22', lava2: '#f2952c',
|
|
18
|
+
table: '#7f95ab', tableHi: '#aebfcd', tableD: '#5d7288',
|
|
19
|
+
chair: '#c69c3c', amph: '#b4472e', amphHi: '#d66c4a', amphD: '#7e2c1e',
|
|
20
|
+
stola: '#efe7d5', stolaF: '#cbbd9c', skin: '#efc4a0', hair: '#dcaa42', hairD: '#9c6c1c', lip: '#b8433a', eye: '#2a2420',
|
|
21
|
+
catO: '#dc8530', catO2: '#c06a24', catS: '#9c4818', catW: '#f7f2e8', catEye: '#e9c22c', collar: '#b02a22',
|
|
22
|
+
};
|
|
23
|
+
// 石块类别:尺寸 / 沿轮廓排几圈(99=全部沿轮廓)/ 颜色抖动幅度
|
|
24
|
+
const K_WALL = 0, K_HALO = 1, K_FIG = 2, K_FACE = 3, K_HAIR = 4, K_PIC = 5, K_FRAME = 6, K_BORDER = 7, K_TEXT = 8, K_FLOOR = 9, K_OBJ = 10, K_CAT = 11;
|
|
25
|
+
const SZ = [12, 11, 10, 5, 8, 9, 9, 7.6, 7, 13, 10, 9];
|
|
26
|
+
const KR = [1, 9, 99, 99, 99, 2, 99, 0, 99, 1, 99, 99];
|
|
27
|
+
const JIT = [.08, .05, .05, .04, .05, .06, .05, .05, .05, .065, .05, .05];
|
|
28
|
+
|
|
29
|
+
// ---------- 静态物件的路径 ----------
|
|
30
|
+
const rectP = (x, y, w, h) => { const p = new Path2D(); p.rect(x, y, w, h); return p; };
|
|
31
|
+
const FRAME = rectP(360, 120, 440, 440);
|
|
32
|
+
const TABLE_TOP = (() => { const p = new Path2D(); p.roundRect(820, 634, 380, 30, 15); return p; })();
|
|
33
|
+
const TABLE_PED = (() => { const p = new Path2D(); [[994, 664], [1026, 664], [1022, 700], [1034, 738], [1026, 780], [1040, 840], [1076, 882], [1080, 898], [940, 898], [944, 882], [980, 840], [994, 780], [986, 738], [998, 700]].forEach((q, i) => i ? p.lineTo(...q) : p.moveTo(...q)); p.closePath(); return p; })();
|
|
34
|
+
const CHAIR = (() => { const p = new Path2D(); p.rect(1460, 462, 14, 440); p.rect(1296, 724, 184, 14); p.rect(1306, 738, 12, 164); p.rect(1306, 818, 168, 9); p.moveTo(1476, 454); p.arc(1467, 454, 9, 0, Math.PI * 2); return p; })();
|
|
35
|
+
const AMPH = RIG.smooth([[1712, 616], [1744, 616], [1746, 652], [1772, 690], [1768, 760], [1752, 830], [1734, 884], [1729, 900], [1724, 884], [1704, 830], [1688, 760], [1686, 690], [1710, 652]]);
|
|
36
|
+
const AMPH_LIP = rectP(1702, 604, 52, 13);
|
|
37
|
+
const AMPH_H = (() => { const p = new Path2D(); p.moveTo(1714, 628); p.bezierCurveTo(1684, 628, 1682, 660, 1692, 690); p.moveTo(1742, 628); p.bezierCurveTo(1772, 628, 1774, 660, 1764, 690); return p; })();
|
|
38
|
+
// 少女的罗马服饰:披肩 palla(背后斜披+盖过膝+前襟垂下)
|
|
39
|
+
const PALLA_BACK = RIG.smooth([[1350, 436], [1384, 440], [1408, 472], [1422, 532], [1432, 600], [1448, 662], [1454, 722], [1440, 766], [1392, 772], [1398, 700], [1394, 620], [1380, 540], [1362, 482]]);
|
|
40
|
+
const PALLA_LAP = RIG.smooth([[1418, 612], [1448, 652], [1454, 722], [1440, 766], [1362, 778], [1290, 768], [1226, 744], [1200, 722], [1236, 712], [1300, 716], [1360, 700], [1392, 660]]);
|
|
41
|
+
const PALLA_HANG = RIG.smooth([[1322, 768], [1366, 774], [1360, 850], [1356, 912], [1324, 914], [1330, 840]]);
|
|
42
|
+
const PALLA_FOLDS = [[[1372, 470], [1398, 560], [1412, 660], [1418, 740]], [[1236, 730], [1300, 742], [1370, 744], [1424, 734]]].map(q => RIG.smooth(q, false));
|
|
43
|
+
|
|
44
|
+
// ---------- 平涂底稿:静态部分 ----------
|
|
45
|
+
function picture(g) {
|
|
46
|
+
g.save(); g.beginPath(); g.rect(392, 152, 376, 376); g.clip();
|
|
47
|
+
const sg = g.createLinearGradient(0, 152, 0, 470); sg.addColorStop(0, C.sky); sg.addColorStop(1, C.sky2); g.fillStyle = sg; g.fillRect(392, 152, 376, 376);
|
|
48
|
+
g.strokeStyle = '#e4eeee'; g.lineWidth = 5; [[410, 250, 500], [620, 262, 760], [400, 300, 470], [640, 318, 750]].forEach(([a, y, b]) => { g.beginPath(); g.moveTo(a, y); g.lineTo(b, y); g.stroke(); });
|
|
49
|
+
// 火山:三面
|
|
50
|
+
const poly = (pts, col) => { g.fillStyle = col; g.beginPath(); pts.forEach((q, i) => i ? g.lineTo(...q) : g.moveTo(...q)); g.closePath(); g.fill(); };
|
|
51
|
+
poly([[402, 472], [535, 222], [560, 222], [520, 472]], C.coneL);
|
|
52
|
+
poly([[520, 472], [560, 222], [586, 222], [646, 472]], C.cone);
|
|
53
|
+
poly([[646, 472], [586, 222], [738, 472]], C.coneD);
|
|
54
|
+
g.strokeStyle = C.ridge; g.lineWidth = 5; [[[548, 226], [500, 330], [470, 420]], [[574, 226], [600, 330], [620, 440]]].forEach(l => { g.beginPath(); l.forEach((q, i) => i ? g.lineTo(...q) : g.moveTo(...q)); g.stroke(); });
|
|
55
|
+
g.fillStyle = '#2a2c48'; g.beginPath(); g.ellipse(560, 222, 27, 6, 0, 0, Math.PI * 2); g.fill();
|
|
56
|
+
// 熔岩
|
|
57
|
+
g.lineCap = 'round'; g.lineJoin = 'round';
|
|
58
|
+
g.strokeStyle = C.lava; g.lineWidth = 8; g.beginPath(); g.moveTo(566, 226); g.lineTo(580, 262); g.lineTo(596, 298); g.lineTo(614, 334); g.stroke();
|
|
59
|
+
g.strokeStyle = C.lava2; g.lineWidth = 5; g.beginPath(); g.moveTo(552, 228); g.lineTo(544, 262); g.lineTo(538, 290); g.stroke();
|
|
60
|
+
// 山坡、地面、柏树
|
|
61
|
+
g.fillStyle = C.green; g.beginPath(); g.moveTo(392, 462); g.quadraticCurveTo(470, 430, 560, 456); g.quadraticCurveTo(650, 482, 768, 448); g.lineTo(768, 528); g.lineTo(392, 528); g.fill();
|
|
62
|
+
g.fillStyle = C.green2; g.beginPath(); g.moveTo(392, 482); g.quadraticCurveTo(480, 462, 580, 484); g.quadraticCurveTo(680, 502, 768, 476); g.lineTo(768, 528); g.lineTo(392, 528); g.fill();
|
|
63
|
+
g.fillStyle = C.ground; g.beginPath(); g.moveTo(392, 502); g.quadraticCurveTo(560, 488, 768, 504); g.lineTo(768, 528); g.lineTo(392, 528); g.fill();
|
|
64
|
+
g.strokeStyle = C.ground2; g.lineWidth = 4; g.beginPath(); g.moveTo(400, 515); for (let x = 400; x <= 768; x += 24) g.quadraticCurveTo(x + 6, 510, x + 12, 516); g.stroke();
|
|
65
|
+
[[702, 258, 500, 17], [740, 276, 498, 14]].forEach(([x, top, bot, w]) => {
|
|
66
|
+
g.fillStyle = C.cyp; g.beginPath(); g.moveTo(x, top); g.quadraticCurveTo(x + w * 1.1, top + 110, x + w * 0.75, bot); g.lineTo(x - w * 0.75, bot); g.quadraticCurveTo(x - w * 1.1, top + 110, x, top); g.fill();
|
|
67
|
+
g.strokeStyle = C.cyp2; g.lineWidth = 5; g.beginPath(); g.moveTo(x - 3, top + 30); g.lineTo(x - 6, bot - 10); g.stroke();
|
|
68
|
+
});
|
|
69
|
+
g.restore();
|
|
70
|
+
}
|
|
71
|
+
function staticScene(g) {
|
|
72
|
+
g.fillStyle = C.wall; g.fillRect(0, 0, W, H);
|
|
73
|
+
g.fillStyle = C.floor; g.fillRect(BW, 905, W - 2 * BW, H - BW - 905);
|
|
74
|
+
// 物件晕圈(白石沿轮廓)
|
|
75
|
+
g.lineJoin = 'round'; g.strokeStyle = C.halo;
|
|
76
|
+
g.lineWidth = 44; g.stroke(FRAME); g.stroke(TABLE_TOP); g.stroke(TABLE_PED); g.stroke(AMPH); g.stroke(AMPH_LIP); g.stroke(AMPH_H);
|
|
77
|
+
g.lineWidth = 34; g.stroke(CHAIR);
|
|
78
|
+
g.strokeStyle = C.floorLine; g.lineWidth = 6; g.beginPath(); g.moveTo(BW, 905); g.lineTo(W - BW, 905); g.stroke();
|
|
79
|
+
// 画框:红框 → 深线 → 金线 → 深线 → 画
|
|
80
|
+
g.fillStyle = C.red; g.fillRect(360, 120, 440, 440);
|
|
81
|
+
g.fillStyle = C.red2; g.fillRect(376, 136, 408, 408);
|
|
82
|
+
g.fillStyle = C.gold; g.fillRect(381, 141, 398, 398);
|
|
83
|
+
g.fillStyle = '#3a2a22'; g.fillRect(387, 147, 386, 386);
|
|
84
|
+
picture(g);
|
|
85
|
+
g.strokeStyle = C.outline; g.lineWidth = 5; g.stroke(FRAME);
|
|
86
|
+
// 椅子(在少女后面)
|
|
87
|
+
g.fillStyle = C.chair; g.fill(CHAIR); g.strokeStyle = '#7d5c1c'; g.lineWidth = 5; g.stroke(CHAIR);
|
|
88
|
+
// 桌子:蓝灰圆桌面+喇叭形单柱
|
|
89
|
+
g.fillStyle = C.table; g.fill(TABLE_PED);
|
|
90
|
+
g.save(); g.clip(TABLE_PED); g.fillStyle = '#93a8bb'; g.fillRect(940, 660, 52, 240); g.fillStyle = C.tableD; g.fillRect(1030, 660, 60, 240);
|
|
91
|
+
g.fillStyle = '#c33a2c'; g.fillRect(940, 736, 140, 9); g.fillStyle = '#3d5a9a'; g.fillRect(940, 748, 140, 7); g.restore();
|
|
92
|
+
g.strokeStyle = C.outline; g.lineWidth = 6; g.stroke(TABLE_PED);
|
|
93
|
+
g.fillStyle = C.table; g.fill(TABLE_TOP); g.fillStyle = C.tableHi; g.fillRect(834, 638, 352, 9); g.stroke(TABLE_TOP);
|
|
94
|
+
// 陶罐
|
|
95
|
+
g.strokeStyle = C.amphD; g.lineWidth = 9; g.lineCap = 'round'; g.stroke(AMPH_H);
|
|
96
|
+
g.fillStyle = C.amph; g.fill(AMPH); g.fill(AMPH_LIP);
|
|
97
|
+
g.save(); g.clip(AMPH); g.fillStyle = C.amphHi; g.fillRect(1698, 640, 14, 200); g.fillStyle = C.amphD; g.fillRect(1748, 640, 30, 260); g.restore();
|
|
98
|
+
g.strokeStyle = C.outline; g.lineWidth = 6; g.stroke(AMPH); g.stroke(AMPH_LIP);
|
|
99
|
+
// CAVE CATTVM:碑刻大写,黑石
|
|
100
|
+
// 字母自己写笔画(等宽粗笔画最像石块拼字;字体的细横笔在 7px 石块下会断):60px 高,笔画 12px
|
|
101
|
+
const GL = { C: [44, g2 => g2.ellipse(22, 30, 19, 26, 0, Math.PI * 0.27, Math.PI * 1.73)], A: [46, [[[0, 60], [23, 0], [46, 60]], [[11, 38], [35, 38]]]], V: [46, [[[0, 0], [23, 60], [46, 0]]]],
|
|
102
|
+
E: [38, [[[38, 6], [6, 6], [6, 54], [38, 54]], [[6, 30], [32, 30]]]], T: [46, [[[0, 6], [46, 6]], [[23, 6], [23, 60]]]], M: [52, [[[4, 60], [6, 0], [26, 46], [46, 0], [48, 60]]]] };
|
|
103
|
+
g.save(); g.strokeStyle = '#1d1a18'; g.lineWidth = 14; g.lineJoin = 'miter'; g.lineCap = 'butt';
|
|
104
|
+
const TXT = 'CAVE CATTVM', gap = 16; let tx = 0; const lay = [];
|
|
105
|
+
for (const ch of TXT) { if (ch === ' ') { tx += 30; continue; } lay.push([ch, tx]); tx += GL[ch][0] + gap; }
|
|
106
|
+
const sc = 600 / (tx - gap); g.translate(270, 922); g.scale(sc, 1);
|
|
107
|
+
for (const [ch, x0] of lay) { const gl = GL[ch][1]; g.beginPath(); if (typeof gl === 'function') { g.save(); g.translate(x0, 0); gl(g); g.restore(); } else gl.forEach(l => l.forEach((q, k) => k ? g.lineTo(x0 + q[0], q[1]) : g.moveTo(x0 + q[0], q[1]))); g.stroke(); }
|
|
108
|
+
g.restore();
|
|
109
|
+
// 鱼与老鼠(小石块)
|
|
110
|
+
g.fillStyle = '#5f5b57'; g.beginPath(); g.ellipse(1446, 950, 40, 8, 0, 0, Math.PI * 2); g.fill();
|
|
111
|
+
g.beginPath(); g.moveTo(1408, 950); g.lineTo(1386, 938); g.lineTo(1386, 962); g.closePath(); g.fill();
|
|
112
|
+
g.strokeStyle = '#d8ccb0'; g.lineWidth = 2.5; for (let k = 0; k < 5; k++) { g.beginPath(); g.moveTo(1420 + k * 11, 944); g.lineTo(1426 + k * 11, 956); g.stroke(); }
|
|
113
|
+
g.fillStyle = '#86807a'; g.beginPath(); g.ellipse(1784, 960, 22, 10, 0, 0, Math.PI * 2); g.fill();
|
|
114
|
+
g.beginPath(); g.moveTo(1800, 952); g.lineTo(1824, 962); g.lineTo(1800, 968); g.fill(); g.beginPath(); g.arc(1798, 950, 5, 0, Math.PI * 2); g.fill();
|
|
115
|
+
g.strokeStyle = '#86807a'; g.lineWidth = 3; g.beginPath(); g.moveTo(1764, 962); g.quadraticCurveTo(1748, 950, 1738, 966); g.stroke();
|
|
116
|
+
// 内框黑线
|
|
117
|
+
g.fillStyle = C.bK; g.fillRect(BW - 2, BW - 2, W - 2 * BW + 4, 6); g.fillRect(BW - 2, H - BW - 4, W - 2 * BW + 4, 6); g.fillRect(BW - 2, BW - 2, 6, H - 2 * BW + 4); g.fillRect(W - BW - 4, BW - 2, 6, H - 2 * BW + 4);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// ---------- 平涂底稿:会动的部分 ----------
|
|
121
|
+
// 四边波浪纹(维特鲁威卷涡),每条边在自己的局部坐标里画同一个图案:u 沿顺时针方向,v 从外往里
|
|
122
|
+
const PER = 81;
|
|
123
|
+
const SIDES = [[1, 0, 0, 1, 0, 0, W], [0, 1, -1, 0, W, 0, H], [-1, 0, 0, -1, W, H, W], [0, -1, 1, 0, 0, H, H]];
|
|
124
|
+
function border(g, phase) {
|
|
125
|
+
for (const [a, b, c2, d, e, f, len] of SIDES) {
|
|
126
|
+
g.save(); g.setTransform(a, b, c2, d, e, f);
|
|
127
|
+
g.beginPath(); g.rect(BW, 0, len - 2 * BW, BW); g.clip();
|
|
128
|
+
g.fillStyle = C.bW; g.fillRect(0, 0, len, BW);
|
|
129
|
+
g.fillStyle = C.bK; g.fillRect(0, 0, len, 8); g.fillRect(0, 66, len, BW - 66);
|
|
130
|
+
g.strokeStyle = C.bK; g.lineWidth = 13; g.lineCap = 'round'; g.lineJoin = 'round';
|
|
131
|
+
const off = ((phase % PER) + PER) % PER;
|
|
132
|
+
for (let u0 = BW - 2 * PER + off; u0 < len + PER; u0 += PER) {
|
|
133
|
+
const cx = u0 + PER * 0.52, cy = 38, r0 = 24;
|
|
134
|
+
g.beginPath(); g.moveTo(u0 - 6, 68); g.quadraticCurveTo(u0 + 2, 42, cx - r0, cy);
|
|
135
|
+
for (let th = Math.PI; th <= Math.PI * 3.25; th += 0.14) { const r = r0 - (th - Math.PI) / (Math.PI * 2.25) * (r0 - 5); g.lineTo(cx + Math.cos(th) * r, cy + Math.sin(th) * r); }
|
|
136
|
+
g.stroke();
|
|
137
|
+
}
|
|
138
|
+
g.restore();
|
|
139
|
+
}
|
|
140
|
+
// 四角方块饰
|
|
141
|
+
for (const [x, y] of [[0, 0], [W - BW, 0], [0, H - BW], [W - BW, H - BW]]) {
|
|
142
|
+
g.fillStyle = C.bK; g.fillRect(x, y, BW, BW); g.fillStyle = C.bW; g.fillRect(x + 10, y + 10, BW - 20, BW - 20);
|
|
143
|
+
g.fillStyle = C.bK; g.fillRect(x + 22, y + 22, BW - 44, BW - 44); g.fillStyle = C.red; g.fillRect(x + 33, y + 33, BW - 66, BW - 66);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
// 火山烟团+火星(全局时间驱动,连续)
|
|
147
|
+
function volcanoFX(g, t) {
|
|
148
|
+
g.save(); g.beginPath(); g.rect(392, 152, 376, 376); g.clip();
|
|
149
|
+
// 云带(火山灰云)向右漂:周期 380px,一秒 55px
|
|
150
|
+
const cx0 = (t * 55) % 380;
|
|
151
|
+
for (const ox of [-380, 0]) [[28, 168, 46, 18, C.cloud], [108, 162, 52, 16, C.cloud2], [218, 166, 50, 18, C.cloud], [308, 170, 58, 17, C.cloud2], [63, 192, 36, 10, C.cloud2], [278, 195, 40, 10, C.cloud]]
|
|
152
|
+
.forEach(([x, y, rx, ry, col]) => { g.fillStyle = col; g.beginPath(); g.ellipse(392 + x + ox + cx0, y, rx, ry, 0, 0, Math.PI * 2); g.fill(); });
|
|
153
|
+
const N = 9;
|
|
154
|
+
for (let i = 0; i < N; i++) {
|
|
155
|
+
const q = ((t * 0.85 + i / N) % 1), e = 1 - Math.pow(1 - q, 1.6);
|
|
156
|
+
const x = 560 + e * 175 + Math.sin(t * 2 + i * 2.1) * 10, y = 212 - e * 62 - Math.sin(e * 3.1) * 18, r = 13 + q * 40; // 烟柱往右压低飘(留在画框天空里才看得见)
|
|
157
|
+
const col = P.mix(P.hex('#3f3c3a'), P.hex('#a29c94'), Math.min(1, q * 1.1));
|
|
158
|
+
g.globalAlpha = q > 0.8 ? (1 - q) / 0.2 : 1;
|
|
159
|
+
g.fillStyle = P.rgb(col); g.beginPath(); g.arc(x, y, r, 0, Math.PI * 2); g.fill();
|
|
160
|
+
g.fillStyle = P.rgb(P.mix(col, [255, 255, 255], 0.3)); g.beginPath(); g.arc(x - r * 0.3, y - r * 0.3, r * 0.45, 0, Math.PI * 2); g.fill();
|
|
161
|
+
}
|
|
162
|
+
g.globalAlpha = 1;
|
|
163
|
+
const r = rng(79);
|
|
164
|
+
for (let i = 0; i < 12; i++) {
|
|
165
|
+
const ang = -Math.PI / 2 + (r() - 0.5) * 2.0, sp = 130 + r() * 110, ph = r();
|
|
166
|
+
const q = (t * 1.25 + ph) % 1, tt = q * 0.9;
|
|
167
|
+
const x = 560 + Math.cos(ang) * sp * tt, y = 216 + Math.sin(ang) * sp * tt + 260 * tt * tt;
|
|
168
|
+
g.fillStyle = r() < 0.5 ? '#f6d23c' : '#f08a22'; g.fillRect(x - 5, y - 5, 10, 10);
|
|
169
|
+
}
|
|
170
|
+
// 火山口熔岩脉动
|
|
171
|
+
g.fillStyle = `rgba(246,160,40,${0.55 + 0.45 * Math.sin(t * 9)})`; g.beginPath(); g.ellipse(560, 220, 20, 6, 0, 0, Math.PI * 2); g.fill();
|
|
172
|
+
g.restore();
|
|
173
|
+
}
|
|
174
|
+
// 少女:罗马斯托拉+红色披肩+金发罗马发髻
|
|
175
|
+
const rot = (G, x, y) => { const c = Math.cos(G.tilt), s = Math.sin(G.tilt), X = x - 1300, Y = y - 425; return [1300 + X * c - Y * s, 425 + X * s + Y * c]; };
|
|
176
|
+
// 近侧手臂:不用骨架原举杯路线(终点杯子戳在鼻子上、手臂挡脸)。杯沿右端贴到唇高、倾角 ≤8.6°,手托在杯脚下,
|
|
177
|
+
// 杯子走「桌面 → 前上方 → 唇前」的二次贝塞尔;IK 用骨架同一副臂长(165/122)与肘向。
|
|
178
|
+
function armPose(G, k) {
|
|
179
|
+
const S = G.A.shoulder, lip = G.lips[0];
|
|
180
|
+
const A0 = [1186, 584], A2 = [lip[0] - 31, lip[1] - 3], A1 = [1150, 470], u = 1 - k;
|
|
181
|
+
const cp = [u * u * A0[0] + 2 * u * k * A1[0] + k * k * A2[0], u * u * A0[1] + 2 * u * k * A1[1] + k * k * A2[1]];
|
|
182
|
+
const cup = { x: cp[0], y: cp[1], w: 44, h: 38, tilt: 0.15 * k };
|
|
183
|
+
const hT = [cp[0] + lerp(40, 3, k), cp[1] + lerp(32, 56, k)];
|
|
184
|
+
const { E, H } = RIG.ik2(S, hT, 165, 122, -1);
|
|
185
|
+
const W_ = [lerp(E[0], H[0], 0.82), lerp(E[1], H[1], 0.82)], fa = Math.atan2(H[1] - E[1], H[0] - E[0]);
|
|
186
|
+
const hand = new Path2D(); hand.ellipse(H[0], H[1], 17, 14, fa, 0, Math.PI * 2);
|
|
187
|
+
return { S, E, H, cup, upperArm: RIG.taper(S, E, 54, 40, 10), foreArm: RIG.taper(E, W_, 40, 30, 2), cuff: RIG.taper([lerp(E[0], H[0], 0.74), lerp(E[1], H[1], 0.74)], W_, 32, 31), hand };
|
|
188
|
+
}
|
|
189
|
+
function girl(g, G, { nearArm = true, steam = 0, t = 0, blink = 0, cup = 0 } = {}) {
|
|
190
|
+
const AP = armPose(G, cup);
|
|
191
|
+
g.save(); g.lineJoin = 'round'; g.lineCap = 'round';
|
|
192
|
+
const sil = [G.skirt, G.torso, G.farSleeve, G.farHand, G.hairBack, G.bun, G.neck, G.face, G.bangs, PALLA_BACK, PALLA_LAP, PALLA_HANG, G.shoe];
|
|
193
|
+
const arm = nearArm ? [AP.upperArm, AP.foreArm, AP.hand] : [];
|
|
194
|
+
g.strokeStyle = C.halo; g.lineWidth = 50; sil.concat(arm).forEach(p => p && g.stroke(p));
|
|
195
|
+
if (nearArm) { g.fillStyle = C.halo; g.beginPath(); g.arc(AP.cup.x, AP.cup.y + 26, 60, 0, Math.PI * 2); g.fill(); }
|
|
196
|
+
const F = (p, col, lw = 8) => { if (!p) return; g.fillStyle = col; g.fill(p); if (lw) { g.strokeStyle = C.outline; g.lineWidth = lw; g.stroke(p); } };
|
|
197
|
+
F(G.farSleeve, C.stola); F(G.farHand, C.skin, 6); F(G.farCuff, C.gold, 0);
|
|
198
|
+
F(G.skirt, C.stola);
|
|
199
|
+
g.save(); g.clip(G.skirt); g.fillStyle = C.red; g.fillRect(1080, 878, 400, 10); g.fillRect(1080, 897, 400, 9);
|
|
200
|
+
g.strokeStyle = C.stolaF; g.lineWidth = 6; G.folds.forEach(f => g.stroke(f)); g.restore();
|
|
201
|
+
g.strokeStyle = C.outline; g.lineWidth = 8; g.stroke(G.skirt);
|
|
202
|
+
F(G.shoe, '#6b4428', 6);
|
|
203
|
+
F(G.torso, C.stola);
|
|
204
|
+
g.save(); g.clip(G.torso); g.strokeStyle = C.stolaF; g.lineWidth = 6; [[1300, 470, 1306, 600], [1330, 480, 1340, 605]].forEach(([a, b, c2, d]) => { g.beginPath(); g.moveTo(a, b); g.quadraticCurveTo(a + 8, (b + d) / 2, c2, d); g.stroke(); }); g.restore();
|
|
205
|
+
F(PALLA_BACK, C.red); F(PALLA_LAP, C.red); F(PALLA_HANG, C.red, 7);
|
|
206
|
+
g.strokeStyle = C.red2; g.lineWidth = 7; PALLA_FOLDS.forEach(f => g.stroke(f));
|
|
207
|
+
g.fillStyle = C.gold; g.beginPath(); g.arc(1372, 446, 10, 0, Math.PI * 2); g.fill(); g.strokeStyle = C.gold2; g.lineWidth = 3; g.stroke();
|
|
208
|
+
F(G.neck, C.skin, 6);
|
|
209
|
+
F(G.hairBack, C.hair, 7); F(G.bun, C.hair, 7);
|
|
210
|
+
g.strokeStyle = C.hairD; g.lineWidth = 4; g.beginPath(); for (let a = 0; a < 8; a += 0.25) { const r = 4 + a * 3, x = G.bunSpiral[0] + Math.cos(a) * r, y = G.bunSpiral[1] + Math.sin(a) * r; a ? g.lineTo(x, y) : g.moveTo(x, y); } g.stroke();
|
|
211
|
+
F(G.face, C.skin, 10);
|
|
212
|
+
g.fillStyle = C.skin; g.beginPath(); g.ellipse(G.ear[0], G.ear[1], 8, 12, 0.2, 0, Math.PI * 2); g.fill(); g.strokeStyle = C.outline; g.lineWidth = 4; g.stroke();
|
|
213
|
+
g.fillStyle = C.gold; g.beginPath(); g.arc(G.ear[0] - 1, G.ear[1] + 16, 5, 0, Math.PI * 2); g.fill();
|
|
214
|
+
F(G.bangs, C.hair, 6);
|
|
215
|
+
g.strokeStyle = C.hairD; g.lineWidth = 4; G.hairLines.forEach(h => g.stroke(h));
|
|
216
|
+
// 红色发带
|
|
217
|
+
const f0 = rot(G, 1256, 322), f1 = rot(G, 1282, 294), f2 = rot(G, 1342, 286), f3 = rot(G, 1378, 334);
|
|
218
|
+
g.strokeStyle = C.red; g.lineWidth = 8; g.beginPath(); g.moveTo(...f0); g.bezierCurveTo(...f1, ...f2, ...f3); g.stroke();
|
|
219
|
+
// 五官(小石块尺度:眼一块深色,眉、唇)
|
|
220
|
+
if (blink) { g.strokeStyle = C.eye; g.lineWidth = 4; g.stroke(G.lid); }
|
|
221
|
+
else { g.fillStyle = '#fbf8f0'; g.beginPath(); g.ellipse(G.eye.x + 3, G.eye.y + 1, 13, 7, 0, 0, Math.PI * 2); g.fill(); g.fillStyle = C.eye; g.beginPath(); g.ellipse(G.eye.x - 1, G.eye.y + 1, 8, 7, 0, 0, Math.PI * 2); g.fill(); g.strokeStyle = C.eye; g.lineWidth = 5; g.stroke(G.lid); }
|
|
222
|
+
g.strokeStyle = '#4a3020'; g.lineWidth = 6; g.stroke(G.brow);
|
|
223
|
+
{ const l0 = G.lips[0], l2 = G.lips[2]; g.fillStyle = '#a8282a'; g.beginPath(); g.ellipse((l0[0] + l2[0]) / 2 + 4, (l0[1] + l2[1]) / 2, 8.5, 6, -0.3, 0, Math.PI * 2); g.fill(); }
|
|
224
|
+
if (nearArm) {
|
|
225
|
+
F(AP.upperArm, C.skin, 6); F(AP.foreArm, C.skin, 6);
|
|
226
|
+
g.save(); g.clip(AP.upperArm); const sh0 = G.A.shoulder; g.fillStyle = C.stola; g.beginPath(); g.arc(sh0[0], sh0[1], 62, 0, Math.PI * 2); g.fill(); g.strokeStyle = C.red; g.lineWidth = 9; g.beginPath(); g.arc(sh0[0], sh0[1], 62, 0, Math.PI * 2); g.stroke(); g.restore(); g.strokeStyle = C.outline; g.lineWidth = 6; g.stroke(AP.upperArm);
|
|
227
|
+
F(AP.cuff, C.gold, 0);
|
|
228
|
+
F(AP.hand, C.skin, 6); // 手托杯脚;杯子本身由 cupSprite 叠在马赛克上
|
|
229
|
+
}
|
|
230
|
+
g.restore();
|
|
231
|
+
}
|
|
232
|
+
// 猫:用 RIG.cat 的锚点重画——平滑梨形身体、虎斑、白胸白爪、大黄眼、红项圈
|
|
233
|
+
const CATS = 0.85, catT = (g) => { g.translate(540, 905); g.scale(CATS, CATS); g.translate(-540, -905); };
|
|
234
|
+
const catPt = ([x, y]) => [540 + (x - 540) * CATS, 905 + (y - 905) * CATS];
|
|
235
|
+
function cat(g, K) {
|
|
236
|
+
g.save(); catT(g); g.lineJoin = 'round'; g.lineCap = 'round';
|
|
237
|
+
const body = RIG.smooth(K.bodyPts);
|
|
238
|
+
const tail = new Path2D(); K.tailPts.forEach((q, i) => i ? tail.lineTo(...q) : tail.moveTo(...q));
|
|
239
|
+
g.strokeStyle = C.halo; g.lineWidth = 50; g.stroke(body); g.stroke(K.head); K.legs.forEach(l => g.stroke(l)); g.lineWidth = K.tailW + 50; g.stroke(tail);
|
|
240
|
+
// 尾巴:深描边 → 橘 → 深橘环纹 → 白尖
|
|
241
|
+
g.strokeStyle = C.outline; g.lineWidth = K.tailW + 12; g.stroke(tail);
|
|
242
|
+
g.strokeStyle = C.catO; g.lineWidth = K.tailW; g.stroke(tail);
|
|
243
|
+
g.strokeStyle = C.catS; g.setLineDash([9, 13]); g.lineDashOffset = 4; g.lineWidth = K.tailW - 2; g.lineCap = 'butt'; g.stroke(tail); g.setLineDash([]); g.lineCap = 'round';
|
|
244
|
+
const tip = K.tailPts[K.tailPts.length - 1]; g.fillStyle = C.catW; g.beginPath(); g.arc(tip[0], tip[1], K.tailW / 2, 0, Math.PI * 2); g.fill();
|
|
245
|
+
// 身体
|
|
246
|
+
g.fillStyle = C.catO; g.fill(body);
|
|
247
|
+
g.save(); g.clip(body); g.fillStyle = C.catO2; g.beginPath(); g.ellipse(K.bodyPts[2][0] + 10, K.bodyPts[2][1] + 40, 52, 110, 0.1, 0, Math.PI * 2); g.fill();
|
|
248
|
+
g.strokeStyle = C.catS; g.lineWidth = 10; K.stripes.slice(3).forEach(s => { g.beginPath(); g.moveTo(...s[0]); g.lineTo(...s[1]); g.stroke(); });
|
|
249
|
+
const hx = K.bodyPts[1][0], hy = K.bodyPts[1][1];
|
|
250
|
+
g.lineWidth = 8; g.strokeStyle = C.catS; g.beginPath(); g.arc(hx + 62, hy - 6, 50, Math.PI * 0.95, Math.PI * 1.6); g.stroke(); // 后腿轮廓
|
|
251
|
+
g.beginPath(); g.arc(hx + 62, hy - 6, 30, Math.PI * 1.0, Math.PI * 1.55); g.stroke();
|
|
252
|
+
g.restore();
|
|
253
|
+
g.strokeStyle = C.outline; g.lineWidth = 8; g.stroke(body);
|
|
254
|
+
K.legs.forEach(l => { g.fillStyle = C.catW; g.fill(l); });
|
|
255
|
+
g.fillStyle = C.catW; g.fill(K.white);
|
|
256
|
+
K.legs.forEach(l => { g.strokeStyle = C.outline; g.lineWidth = 5; g.stroke(l); });
|
|
257
|
+
// 红项圈+金坠
|
|
258
|
+
const hc = K.headC;
|
|
259
|
+
g.strokeStyle = C.collar; g.lineWidth = 13; g.beginPath(); g.moveTo(hc[0] - 46, hc[1] + 52); g.quadraticCurveTo(hc[0] + 2, hc[1] + 80, hc[0] + 52, hc[1] + 52); g.stroke();
|
|
260
|
+
g.fillStyle = C.gold; g.beginPath(); g.arc(hc[0] + 6, hc[1] + 76, 7, 0, Math.PI * 2); g.fill();
|
|
261
|
+
// 头
|
|
262
|
+
// 头:正脸(庞贝猫马赛克是正面瞪眼)。脸中线 x≈604(两耳尖中点)
|
|
263
|
+
const fx = hc[0] + 4, fy = hc[1];
|
|
264
|
+
g.fillStyle = C.catO; g.fill(K.head);
|
|
265
|
+
g.save(); g.clip(K.head); g.fillStyle = C.catW; g.beginPath(); g.ellipse(fx, fy + 30, 34, 24, 0, 0, Math.PI * 2); g.fill();
|
|
266
|
+
g.strokeStyle = C.catS; g.lineWidth = 7; K.stripes.slice(0, 3).forEach(s => { g.beginPath(); g.moveTo(...s[0]); g.lineTo(...s[1]); g.stroke(); });
|
|
267
|
+
[[-1], [1]].forEach(([d]) => { g.beginPath(); g.moveTo(fx + d * 66, fy + 4); g.lineTo(fx + d * 44, fy + 8); g.stroke(); g.beginPath(); g.moveTo(fx + d * 64, fy + 20); g.lineTo(fx + d * 46, fy + 20); g.stroke(); });
|
|
268
|
+
g.restore();
|
|
269
|
+
g.strokeStyle = C.outline; g.lineWidth = 11; g.stroke(K.head);
|
|
270
|
+
g.fillStyle = '#e9a08e'; K.earInner.forEach(e => { g.beginPath(); e.forEach((p, i) => i ? g.lineTo(...p) : g.moveTo(...p)); g.fill(); });
|
|
271
|
+
// 大黄圆眼+黑竖瞳+深色眼眶(庞贝猫标志)
|
|
272
|
+
[[fx - 24, fy - 8], [fx + 24, fy - 10]].forEach(([ex, ey]) => {
|
|
273
|
+
const R = 17;
|
|
274
|
+
if (K.blink) { g.fillStyle = C.catO; g.beginPath(); g.arc(ex, ey, R, 0, Math.PI * 2); g.fill(); g.strokeStyle = C.eye; g.lineWidth = 7; g.beginPath(); g.moveTo(ex - R, ey + 2); g.quadraticCurveTo(ex, ey + 8, ex + R, ey + 2); g.stroke(); }
|
|
275
|
+
else { g.fillStyle = C.catEye; g.beginPath(); g.arc(ex, ey, R, 0, Math.PI * 2); g.fill(); g.strokeStyle = C.eye; g.lineWidth = 7; g.stroke(); g.fillStyle = C.eye; g.beginPath(); g.ellipse(ex, ey, 4.5, 11, 0, 0, Math.PI * 2); g.fill(); }
|
|
276
|
+
});
|
|
277
|
+
g.fillStyle = '#c96a5c'; g.beginPath(); g.moveTo(fx - 8, fy + 16); g.lineTo(fx + 8, fy + 16); g.lineTo(fx, fy + 25); g.closePath(); g.fill();
|
|
278
|
+
g.strokeStyle = C.eye; g.lineWidth = 4; g.beginPath(); g.moveTo(fx, fy + 25); g.lineTo(fx, fy + 31); g.moveTo(fx - 12, fy + 36); g.quadraticCurveTo(fx - 5, fy + 38, fx, fy + 31); g.quadraticCurveTo(fx + 5, fy + 38, fx + 12, fy + 36); g.stroke();
|
|
279
|
+
g.restore();
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// ---------- 编舞 ----------
|
|
283
|
+
const ease = U.ease.inOut;
|
|
284
|
+
const borderPhase = (lt) => 15 * lt + PER * ease(clamp((lt - 0.485) / 0.365)); // 匀速漂移+原片 226–248 帧走一个周期
|
|
285
|
+
const poses = (lt, t) => {
|
|
286
|
+
const ch = P.choreo(lt, t);
|
|
287
|
+
return { ch, G: RIG.girl({ cup: ch.cup, sip: ch.sip, breathe: ch.breathe }), K: RIG.cat({ tail: ch.tail * 1.6, blink: ch.blink, breathe: ch.breathe }) };
|
|
288
|
+
};
|
|
289
|
+
function fullBase(g, sb, lt, t) {
|
|
290
|
+
const { ch, G, K } = poses(lt, t);
|
|
291
|
+
g.drawImage(sb, 0, 0);
|
|
292
|
+
border(g, borderPhase(lt));
|
|
293
|
+
volcanoFX(g, t);
|
|
294
|
+
cat(g, K);
|
|
295
|
+
girl(g, G, { steam: 1 - ch.cup * 0.85, t, blink: ch.blink, cup: ch.cup });
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// ---------- 镶嵌渲染器:布局(只算一次) ----------
|
|
299
|
+
let S = null;
|
|
300
|
+
function build() {
|
|
301
|
+
const t0 = performance.now();
|
|
302
|
+
const sb = P.cached('rm_static', W, H, g => staticScene(g));
|
|
303
|
+
const mk = () => { const cv = P.canvas(); return [cv, cv.getContext('2d', { willReadFrequently: true })]; };
|
|
304
|
+
// 结构图(决定石块怎么排):静态+少女(不含会动的近侧手臂)+猫
|
|
305
|
+
const [sc, sg] = mk(); sg.drawImage(sb, 0, 0);
|
|
306
|
+
const G0 = RIG.girl({ cup: 0 }), K0 = RIG.cat({});
|
|
307
|
+
cat(sg, K0); girl(sg, G0, { nearArm: false, steam: 0 });
|
|
308
|
+
// 类别图
|
|
309
|
+
const [cc, cg] = mk(); const cs = k => `rgb(${k * 16},0,0)`;
|
|
310
|
+
cg.fillStyle = cs(K_WALL); cg.fillRect(0, 0, W, H);
|
|
311
|
+
cg.fillStyle = cs(K_FLOOR); cg.fillRect(BW, 905, W - 2 * BW, H - BW - 905);
|
|
312
|
+
cg.lineJoin = 'round'; cg.strokeStyle = cs(K_HALO); cg.lineWidth = 44; [FRAME, TABLE_TOP, TABLE_PED, AMPH, AMPH_LIP].forEach(p => cg.stroke(p)); cg.lineWidth = 34; cg.stroke(CHAIR);
|
|
313
|
+
cg.fillStyle = cs(K_FRAME); cg.fill(FRAME); cg.fillStyle = cs(K_PIC); cg.fillRect(388, 148, 384, 384);
|
|
314
|
+
cg.fillStyle = cs(K_OBJ); [TABLE_TOP, TABLE_PED, AMPH, AMPH_LIP, CHAIR].forEach(p => cg.fill(p));
|
|
315
|
+
const gs = [G0.skirt, G0.torso, G0.farSleeve, G0.farHand, G0.neck, PALLA_BACK, PALLA_LAP, PALLA_HANG, G0.shoe], gh = [G0.hairBack, G0.bun, G0.bangs];
|
|
316
|
+
const kb = [RIG.smooth(K0.bodyPts), K0.head].concat(K0.legs), ktail = new Path2D(); K0.tailPts.forEach((q, i) => i ? ktail.lineTo(...q) : ktail.moveTo(...q));
|
|
317
|
+
cg.strokeStyle = cs(K_HALO); cg.lineWidth = 50; gs.concat(gh, [G0.face]).forEach(p => cg.stroke(p)); cg.save(); catT(cg); kb.forEach(p => cg.stroke(p)); cg.lineWidth = K0.tailW + 50; cg.stroke(ktail); cg.restore();
|
|
318
|
+
cg.lineWidth = 9; cg.fillStyle = cg.strokeStyle = cs(K_FIG); gs.forEach(p => { cg.fill(p); cg.stroke(p); }); cg.fillStyle = cg.strokeStyle = cs(K_HAIR); gh.forEach(p => { cg.fill(p); cg.stroke(p); }); cg.fillStyle = cg.strokeStyle = cs(K_FACE); cg.fill(G0.face); cg.stroke(G0.face);
|
|
319
|
+
cg.save(); catT(cg); cg.lineWidth = 9; cg.fillStyle = cg.strokeStyle = cs(K_CAT); kb.forEach(p => { cg.fill(p); cg.stroke(p); }); cg.strokeStyle = cs(K_CAT); cg.lineWidth = K0.tailW + 12; cg.stroke(ktail); cg.fillStyle = cg.strokeStyle = cs(K_FACE); cg.lineWidth = 13; cg.fill(K0.head); cg.stroke(K0.head); cg.restore();
|
|
320
|
+
cg.fillStyle = cs(K_BORDER); cg.fillRect(0, 0, W, BW); cg.fillRect(0, H - BW, W, BW); cg.fillRect(0, 0, BW, H); cg.fillRect(W - BW, 0, BW, H);
|
|
321
|
+
const cd = cg.getImageData(0, 0, W, H).data, CL = new Uint8Array(W * H);
|
|
322
|
+
for (let i = 0; i < W * H; i++) CL[i] = Math.round(cd[i * 4] / 16);
|
|
323
|
+
const sdat = sg.getImageData(0, 0, W, H).data;
|
|
324
|
+
for (let y = 905; y < H - BW; y++) for (let x = 255; x < 885; x++) { const i = y * W + x; if (sdat[i * 4] + sdat[i * 4 + 1] + sdat[i * 4 + 2] < 240) CL[i] = K_TEXT; }
|
|
325
|
+
// 半分辨率:边缘 → 精确欧氏距离变换(Felzenszwalb)
|
|
326
|
+
const HW = 960, HH = 540, N = HW * HH;
|
|
327
|
+
const hc = P.canvas(HW, HH), hg = hc.getContext('2d', { willReadFrequently: true }); hg.drawImage(sc, 0, 0, HW, HH);
|
|
328
|
+
const hd = hg.getImageData(0, 0, HW, HH).data;
|
|
329
|
+
const CH = new Uint8Array(N); for (let y = 0; y < HH; y++) for (let x = 0; x < HW; x++) CH[y * HW + x] = CL[(2 * y) * W + 2 * x];
|
|
330
|
+
const F = new Float64Array(N);
|
|
331
|
+
for (let y = 0; y < HH; y++) for (let x = 0; x < HW; x++) {
|
|
332
|
+
const i = y * HW + x, o = i * 4; let e = false;
|
|
333
|
+
if (x < HW - 1) { const q = o + 4; e = Math.abs(hd[o] - hd[q]) + Math.abs(hd[o + 1] - hd[q + 1]) + Math.abs(hd[o + 2] - hd[q + 2]) > 34; }
|
|
334
|
+
if (!e && y < HH - 1) { const q = o + HW * 4; e = Math.abs(hd[o] - hd[q]) + Math.abs(hd[o + 1] - hd[q + 1]) + Math.abs(hd[o + 2] - hd[q + 2]) > 34; }
|
|
335
|
+
if (!e && x < HW - 1 && CH[i] !== CH[i + 1] && (CH[i] === K_BORDER || CH[i + 1] === K_BORDER)) e = true;
|
|
336
|
+
if (!e && y < HH - 1 && CH[i] !== CH[i + HW] && (CH[i] === K_BORDER || CH[i + HW] === K_BORDER)) e = true;
|
|
337
|
+
F[i] = e ? 0 : 1e20;
|
|
338
|
+
}
|
|
339
|
+
const D = edt(F, HW, HH); // 半分辨率像素的平方距离 → 全分辨率像素距离
|
|
340
|
+
for (let i = 0; i < N; i++) D[i] = Math.sqrt(D[i]) * 2;
|
|
341
|
+
const tangent = (i) => { const x = i % HW, y = (i / HW) | 0; const gx = D[i + (x < HW - 1 ? 1 : 0)] - D[i - (x > 0 ? 1 : 0)], gy = D[i + (y < HH - 1 ? HW : 0)] - D[i - (y > 0 ? HW : 0)]; return Math.atan2(gy, gx) + Math.PI / 2; };
|
|
342
|
+
// 石块种子:空间哈希保证最小间距
|
|
343
|
+
const sx = [], sy = [], ss = [], sa = [], scl = [];
|
|
344
|
+
const CS = 24, GWc = Math.ceil(W / CS), GHc = Math.ceil(H / CS), cell = Array.from({ length: GWc * GHc }, () => []);
|
|
345
|
+
const ok = (x, y, s, f) => { const cx = (x / CS) | 0, cy = (y / CS) | 0; for (let j = cy - 1; j <= cy + 1; j++) for (let i = cx - 1; i <= cx + 1; i++) { if (i < 0 || j < 0 || i >= GWc || j >= GHc) continue; for (const k of cell[j * GWc + i]) { const m = f * (s + ss[k]) / 2; const dx = sx[k] - x, dy = sy[k] - y; if (dx * dx + dy * dy < m * m) return false; } } return true; };
|
|
346
|
+
const add = (x, y, s, a, c) => { const k = sx.length; sx.push(x); sy.push(y); ss.push(s); sa.push(a); scl.push(c); cell[((y / CS) | 0) * GWc + ((x / CS) | 0)].push(k); return k; };
|
|
347
|
+
// ① 外框:沿边成行
|
|
348
|
+
const r = rng(2079), NR = 11, bs = BW / NR;
|
|
349
|
+
for (const [a, b, c2, d, e, f, len] of SIDES) {
|
|
350
|
+
for (let j = 0; j < NR; j++) { let u = BW + (j % 2) * bs * 0.5 + r() * 2; while (u < len - BW) { const w = bs * (0.86 + r() * 0.28), uc = u + w / 2, v = (j + 0.5) * bs; add(a * uc + c2 * v + e, b * uc + d * v + f, bs, Math.atan2(b, a) + (r() - .5) * 0.06, K_BORDER); u += w; } }
|
|
351
|
+
}
|
|
352
|
+
for (const [x0, y0] of [[0, 0], [W - BW, 0], [0, H - BW], [W - BW, H - BW]]) for (let j = 0; j < NR; j++) for (let i = 0; i < NR; i++) add(x0 + (i + 0.5) * bs, y0 + (j + 0.5) * bs, bs, (r() - .5) * 0.06, K_BORDER);
|
|
353
|
+
// ② 沿轮廓:在距离场等值线 d=(k+½)s 上走线,每走一个石块宽放一块
|
|
354
|
+
const cand = new Uint8Array(N), vis = new Uint8Array(N);
|
|
355
|
+
for (let i = 0; i < N; i++) { const c = CH[i]; if (c === K_BORDER) continue; const s = SZ[c], d = D[i]; if (d < 1) continue; const k = Math.floor(d / s); if (k >= KR[c]) continue; if (Math.abs(d - (k + 0.5) * s) < 1.5) cand[i] = 1; }
|
|
356
|
+
const NB = [[1, 0], [1, 1], [0, 1], [-1, 1], [-1, 0], [-1, -1], [0, -1], [1, -1]];
|
|
357
|
+
for (let i0 = 0; i0 < N; i0++) {
|
|
358
|
+
if (!cand[i0] || vis[i0]) continue;
|
|
359
|
+
let cur = i0, px = -1e9, py = -1e9, dx = 1, dy = 0;
|
|
360
|
+
for (;;) {
|
|
361
|
+
vis[cur] = 1; const hx = cur % HW, hy = (cur / HW) | 0, x = 2 * hx + 1, y = 2 * hy + 1, c = CH[cur], s = SZ[c];
|
|
362
|
+
if ((x - px) ** 2 + (y - py) ** 2 >= s * s * 0.98 && ok(x, y, s, 0.8)) { add(x, y, s, tangent(cur), c); px = x; py = y; }
|
|
363
|
+
let best = -1, bd = -9, bx = 0, by = 0;
|
|
364
|
+
for (const [ox, oy] of NB) { const nx = hx + ox, ny = hy + oy; if (nx < 0 || ny < 0 || nx >= HW || ny >= HH) continue; const n = ny * HW + nx; if (!cand[n] || vis[n]) continue; const l = Math.hypot(ox, oy), dt = (ox * dx + oy * dy) / l; if (dt > bd) { bd = dt; best = n; bx = ox / l; by = oy / l; } }
|
|
365
|
+
if (best < 0) break;
|
|
366
|
+
dx = dx * 0.6 + bx * 0.4; dy = dy * 0.6 + by * 0.4; const l = Math.hypot(dx, dy) || 1; dx /= l; dy /= l; cur = best;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
// ③ 其余:规则成行(砖缝错半块)+补洞
|
|
370
|
+
for (let c = 0; c < SZ.length; c++) {
|
|
371
|
+
if (c === K_BORDER) continue; const s = SZ[c];
|
|
372
|
+
for (let j = 0; (j + 0.5) * s < H; j++) for (let i = -1; (i + 0.5) * s < W; i++) {
|
|
373
|
+
const x = (i + 0.5 + (j % 2) * 0.5) * s + (r() - .5) * 0.18 * s, y = (j + 0.5) * s + (r() - .5) * 0.1 * s;
|
|
374
|
+
if (x < 0 || y < 0 || x >= W || y >= H) continue;
|
|
375
|
+
if (CL[(y | 0) * W + (x | 0)] !== c) continue;
|
|
376
|
+
const hi = Math.min(HH - 1, (y / 2) | 0) * HW + Math.min(HW - 1, (x / 2) | 0);
|
|
377
|
+
const contour = KR[c] >= 99 || D[hi] < KR[c] * s;
|
|
378
|
+
if (ok(x, y, s, 0.86)) add(x, y, s, contour ? tangent(hi) : (r() - .5) * 0.07, c);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
// ④ 光栅化:加权 L∞ Voronoi(每块石头四边独立抖动 → 不规则方块),D2−D1 太小处是灰浆缝
|
|
382
|
+
const n = sx.length;
|
|
383
|
+
const HUP = new Float32Array(n), HUM = new Float32Array(n), HVP = new Float32Array(n), HVM = new Float32Array(n), CA = new Float32Array(n), SA = new Float32Array(n);
|
|
384
|
+
const JL = new Float32Array(n), TR = new Float32Array(n), TG = new Float32Array(n), TB = new Float32Array(n), SIDX = new Int32Array(n);
|
|
385
|
+
for (let k = 0; k < n; k++) {
|
|
386
|
+
const h = ss[k] / 2, c = scl[k];
|
|
387
|
+
HUP[k] = h * (1.08 + r() * 0.2); HUM[k] = h * (1.08 + r() * 0.2); HVP[k] = h * (1.08 + r() * 0.2); HVM[k] = h * (1.08 + r() * 0.2);
|
|
388
|
+
const a = sa[k] + (r() - .5) * 0.12; CA[k] = Math.cos(a); SA[k] = Math.sin(a);
|
|
389
|
+
JL[k] = (r() - .5) * 2 * JIT[c]; const add_ = (r() - .5) * 14; TR[k] = add_; TG[k] = add_; TB[k] = add_;
|
|
390
|
+
const q = r(); if (q < 0.09) { TR[k] += 12; TG[k] += 4; TB[k] -= 8; } else if (q < 0.16) { TR[k] -= 6; TB[k] += 9; }
|
|
391
|
+
if (c === K_WALL && r() < 0.07) JL[k] -= 0.12;
|
|
392
|
+
SIDX[k] = clamp(Math.round(sy[k]), 0, H - 1) * W + clamp(Math.round(sx[k]), 0, W - 1);
|
|
393
|
+
}
|
|
394
|
+
const D1 = new Float32Array(W * H).fill(9), D2 = new Float32Array(W * H).fill(9), ID = new Int32Array(W * H).fill(-1);
|
|
395
|
+
for (let k = 0; k < n; k++) {
|
|
396
|
+
const cx = sx[k], cy = sy[k], ca = CA[k], sn = SA[k], hm = Math.max(HUP[k], HUM[k], HVP[k], HVM[k]), R = Math.ceil(hm * 1.5) + 1;
|
|
397
|
+
const x0 = Math.max(0, (cx - R) | 0), x1 = Math.min(W - 1, (cx + R) | 0), y0 = Math.max(0, (cy - R) | 0), y1 = Math.min(H - 1, (cy + R) | 0);
|
|
398
|
+
const hup = HUP[k], hum = HUM[k], hvp = HVP[k], hvm = HVM[k];
|
|
399
|
+
for (let y = y0; y <= y1; y++) { const ddy = y + 0.5 - cy; let p = y * W + x0; for (let x = x0; x <= x1; x++, p++) {
|
|
400
|
+
const ddx = x + 0.5 - cx, u = ddx * ca + ddy * sn, v = -ddx * sn + ddy * ca;
|
|
401
|
+
const du = u > 0 ? u / hup : -u / hum, dv = v > 0 ? v / hvp : -v / hvm, Dd = du > dv ? du : dv;
|
|
402
|
+
if (Dd < D1[p]) { D2[p] = D1[p]; D1[p] = Dd; ID[p] = k; } else if (Dd < D2[p]) D2[p] = Dd;
|
|
403
|
+
} }
|
|
404
|
+
}
|
|
405
|
+
// ⑤ 每像素:石块 or 灰浆;石面斜面明暗(左上受光)+颗粒
|
|
406
|
+
const SH = new Uint8Array(W * H), LX = -0.6, LY = -0.8;
|
|
407
|
+
const gr = rng(7);
|
|
408
|
+
for (let y = 0; y < H; y++) for (let x = 0; x < W; x++) {
|
|
409
|
+
const p = y * W + x, k = ID[p]; if (k < 0) continue;
|
|
410
|
+
const h = (HUP[k] + HUM[k] + HVP[k] + HVM[k]) / 4, g = clamp(0.1 * ss[k], 0.75, 1.25), d1 = D1[p];
|
|
411
|
+
const gap = (D2[p] - d1) * h / 2;
|
|
412
|
+
if (d1 > 1 || gap < g) { ID[p] = -1; continue; }
|
|
413
|
+
const e = Math.min((1 - d1) * h, gap - g);
|
|
414
|
+
let sh = 1 - 0.05 * d1 * d1 + (gr() - 0.5) * 0.06;
|
|
415
|
+
if (e < 2.2) {
|
|
416
|
+
const ddx = x + 0.5 - sx[k], ddy = y + 0.5 - sy[k], ca = CA[k], sn = SA[k], u = ddx * ca + ddy * sn, v = -ddx * sn + ddy * ca;
|
|
417
|
+
const du = u > 0 ? u / HUP[k] : -u / HUM[k], dv = v > 0 ? v / HVP[k] : -v / HVM[k];
|
|
418
|
+
const nx = du > dv ? Math.sign(u) * ca : -Math.sign(v) * sn, ny = du > dv ? Math.sign(u) * sn : Math.sign(v) * ca;
|
|
419
|
+
sh += 0.16 * (1 - e / 2.2) * (nx * LX + ny * LY);
|
|
420
|
+
}
|
|
421
|
+
SH[p] = clamp(Math.round(sh * 128), 0, 255);
|
|
422
|
+
}
|
|
423
|
+
// ⑥ 第 0 帧全幅上色 → 静态马赛克
|
|
424
|
+
const [bc, bg] = mk();
|
|
425
|
+
fullBase(bg, sb, 0, 3.28125);
|
|
426
|
+
const bd = bg.getImageData(0, 0, W, H).data;
|
|
427
|
+
const TCr = new Uint8ClampedArray(n), TCg = new Uint8ClampedArray(n), TCb = new Uint8ClampedArray(n);
|
|
428
|
+
const color = (k, d) => { const o = SIDX[k] * 4, f = 1 + JL[k]; TCr[k] = d[o] * f + TR[k]; TCg[k] = d[o + 1] * f + TG[k]; TCb[k] = d[o + 2] * f + TB[k]; };
|
|
429
|
+
for (let k = 0; k < n; k++) color(k, bd);
|
|
430
|
+
const [oc, og] = mk(), oimg = og.createImageData(W, H), SO = oimg.data, grr = rng(13);
|
|
431
|
+
for (let p = 0; p < W * H; p++) {
|
|
432
|
+
const k = ID[p], o = p * 4;
|
|
433
|
+
if (k < 0) { const nz = (grr() - 0.5) * 16; SO[o] = 112 + nz; SO[o + 1] = 103 + nz; SO[o + 2] = 92 + nz; }
|
|
434
|
+
else { const sh = SH[p]; SO[o] = (TCr[k] * sh) >> 7; SO[o + 1] = (TCg[k] * sh) >> 7; SO[o + 2] = (TCb[k] * sh) >> 7; }
|
|
435
|
+
SO[o + 3] = 255;
|
|
436
|
+
}
|
|
437
|
+
og.putImageData(oimg, 0, 0);
|
|
438
|
+
// ⑦ 动态矩形:由各姿态的包围盒算出
|
|
439
|
+
const bbox = (pts, m) => { let x0 = 1e9, y0 = 1e9, x1 = -1e9, y1 = -1e9; pts.forEach(([x, y]) => { x0 = Math.min(x0, x); y0 = Math.min(y0, y); x1 = Math.max(x1, x); y1 = Math.max(y1, y); }); return [Math.max(0, (x0 - m) | 0), Math.max(0, (y0 - m) | 0), Math.min(W, Math.ceil(x1 + m)), Math.min(H, Math.ceil(y1 + m))]; };
|
|
440
|
+
const gp = [[1180, 270], [1460, 720]];
|
|
441
|
+
for (let cu = 0; cu <= 1.001; cu += 0.05) for (const sip of [0, 0.15]) { const G = RIG.girl({ cup: cu, sip }), A = armPose(G, cu); gp.push(A.H, A.E, G.A.headTop, G.A.forehead, G.A.chin, G.A.nape, [A.cup.x - 40, A.cup.y - 95], [A.cup.x + 40, A.cup.y + 60], [G.bunSpiral[0] + 36, G.bunSpiral[1] - 36]); }
|
|
442
|
+
const kp = [[420, 540], [700, 912]];
|
|
443
|
+
for (let tl = -0.75; tl <= 0.75; tl += 0.05) { const K = RIG.cat({ tail: tl }); kp.push(...K.tailPts.map(catPt)); }
|
|
444
|
+
const rects = [[392, 152, 768, 528], bbox(gp, 58), bbox(kp, 58)]
|
|
445
|
+
.map(([x0, y0, x1, y1]) => ({ x: x0, y: y0, w: x1 - x0, h: y1 - y0, img: og.createImageData(x1 - x0, y1 - y0) }));
|
|
446
|
+
const mark = new Uint8Array(n), dyn = [];
|
|
447
|
+
for (const R of rects) for (let y = R.y; y < R.y + R.h; y++) for (let x = R.x; x < R.x + R.w; x++) { const k = ID[y * W + x]; if (k >= 0 && !mark[k]) { mark[k] = 1; dyn.push(k); } }
|
|
448
|
+
S = { sb, bc, bg, oc, og, ID, SH, SO, TCr, TCg, TCb, color, rects, dyn: Int32Array.from(dyn), n, strip: borderStrip(), cupSp: cupSprite(), ms: performance.now() - t0 };
|
|
449
|
+
window.__rm_stats = { tiles: n, dynTiles: dyn.length, buildMs: Math.round(S.ms), rects: rects.map(R => [R.x, R.y, R.w, R.h]) };
|
|
450
|
+
}
|
|
451
|
+
// 外框:一条「周期 = 卷涡周期 81px」的石块带(石块布局也按 81px 循环),每帧整条平移相位——
|
|
452
|
+
// 石块跟着图案一起走(像传送带),比「石块不动只换色」明显得多(7.6px 石块会把 1–3px/帧的相位移动量化掉)。
|
|
453
|
+
function borderStrip() {
|
|
454
|
+
const NP = 26, SL = PER * NP, NR = 11, bs = BW / NR;
|
|
455
|
+
const pc = P.canvas(SL, BW), pg = pc.getContext('2d', { willReadFrequently: true });
|
|
456
|
+
pg.fillStyle = C.bW; pg.fillRect(0, 0, SL, BW); pg.fillStyle = C.bK; pg.fillRect(0, 0, SL, 8); pg.fillRect(0, 66, SL, BW - 66);
|
|
457
|
+
pg.strokeStyle = C.bK; pg.lineWidth = 13; pg.lineCap = 'round'; pg.lineJoin = 'round';
|
|
458
|
+
for (let k = -1; k <= NP; k++) { const u0 = k * PER, cx = u0 + PER * 0.52, cy = 38, r0 = 24;
|
|
459
|
+
pg.beginPath(); pg.moveTo(u0 - 6, 68); pg.quadraticCurveTo(u0 + 2, 42, cx - r0, cy);
|
|
460
|
+
for (let th = Math.PI; th <= Math.PI * 3.25; th += 0.14) { const r = r0 - (th - Math.PI) / (Math.PI * 2.25) * (r0 - 5); pg.lineTo(cx + Math.cos(th) * r, cy + Math.sin(th) * r); } pg.stroke(); }
|
|
461
|
+
const pd = pg.getImageData(0, 0, SL, BW).data;
|
|
462
|
+
const sc = P.canvas(SL, BW), g = sc.getContext('2d');
|
|
463
|
+
g.fillStyle = 'rgb(112,103,92)'; g.fillRect(0, 0, SL, BW);
|
|
464
|
+
for (let j = 0; j < NR; j++) {
|
|
465
|
+
const r = rng(500 + j), n = Math.round(PER / bs), ws = Array.from({ length: n }, () => 0.84 + r() * 0.32), sum = ws.reduce((a, b) => a + b, 0);
|
|
466
|
+
const tiles = []; let u = (j % 2) * bs * 0.5;
|
|
467
|
+
ws.forEach(w0 => { const w = w0 / sum * PER; tiles.push([u, w, Array.from({ length: 8 }, () => (r() - .5) * 1.4), (r() - .5) * 0.12, (r() - .5) * 14]); u += w; });
|
|
468
|
+
for (let k = -1; k <= NP; k++) for (const [u0, w, jt, jl, ja] of tiles) {
|
|
469
|
+
const x0 = k * PER + u0, y0 = j * bs, cx = x0 + w / 2, cy = y0 + bs / 2;
|
|
470
|
+
if (cx < -w || cx > SL + w) continue;
|
|
471
|
+
const o = (Math.min(BW - 1, cy | 0) * SL + Math.max(0, Math.min(SL - 1, cx | 0))) * 4, f = 1 + jl;
|
|
472
|
+
const col = [pd[o] * f + ja, pd[o + 1] * f + ja, pd[o + 2] * f + ja], q = [[x0 + 0.9, y0 + 0.9], [x0 + w - 0.9, y0 + 0.9], [x0 + w - 0.9, y0 + bs - 0.9], [x0 + 0.9, y0 + bs - 0.9]].map((p, i) => [p[0] + jt[i * 2] * 0.6, p[1] + jt[i * 2 + 1] * 0.6]);
|
|
473
|
+
g.fillStyle = P.rgb(col); g.beginPath(); q.forEach((p, i) => i ? g.lineTo(...p) : g.moveTo(...p)); g.closePath(); g.fill();
|
|
474
|
+
g.strokeStyle = P.rgb(P.mix(col, [255, 255, 255], 0.22)); g.lineWidth = 1; g.beginPath(); g.moveTo(...q[0]); g.lineTo(...q[1]); g.stroke();
|
|
475
|
+
g.strokeStyle = P.rgb(P.mix(col, [0, 0, 0], 0.18)); g.beginPath(); g.moveTo(...q[3]); g.lineTo(...q[2]); g.stroke();
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
return sc;
|
|
479
|
+
}
|
|
480
|
+
// 杯子:4.4px 小石块拼片(深色描边石块+金色杯身+亮杯口+双耳+杯脚),整块随手移动
|
|
481
|
+
const CSC = 1.3, CSP = [80, 24];
|
|
482
|
+
function cupSprite() {
|
|
483
|
+
const w = 170, h = 110, fc = P.canvas(w, h), fg = fc.getContext('2d', { willReadFrequently: true });
|
|
484
|
+
fg.translate(CSP[0], CSP[1]); fg.scale(CSC, CSC);
|
|
485
|
+
const cw = 46, ch = 38;
|
|
486
|
+
fg.lineJoin = 'round'; fg.lineCap = 'round';
|
|
487
|
+
fg.strokeStyle = C.outline; fg.lineWidth = 11; [-1, 1].forEach(d => { fg.beginPath(); fg.arc(d * cw * 0.5, ch * 0.28, ch * 0.27, d < 0 ? Math.PI * 0.5 : -Math.PI * 0.5, d < 0 ? Math.PI * 1.5 : Math.PI * 0.5); fg.stroke(); });
|
|
488
|
+
fg.strokeStyle = C.gold; fg.lineWidth = 5; [-1, 1].forEach(d => { fg.beginPath(); fg.arc(d * cw * 0.5, ch * 0.28, ch * 0.27, d < 0 ? Math.PI * 0.5 : -Math.PI * 0.5, d < 0 ? Math.PI * 1.5 : Math.PI * 0.5); fg.stroke(); });
|
|
489
|
+
const bowl = new Path2D(); bowl.moveTo(-cw / 2, 0); bowl.quadraticCurveTo(-cw * 0.46, ch * 0.74, 0, ch * 0.74); bowl.quadraticCurveTo(cw * 0.46, ch * 0.74, cw / 2, 0); bowl.closePath();
|
|
490
|
+
const foot = new Path2D(); foot.rect(-4, ch * 0.7, 8, ch * 0.3); foot.ellipse(0, ch * 1.02, cw * 0.3, 5, 0, 0, Math.PI * 2);
|
|
491
|
+
fg.lineWidth = 6; fg.strokeStyle = C.outline; fg.stroke(bowl); fg.stroke(foot);
|
|
492
|
+
fg.fillStyle = C.gold; fg.fill(bowl); fg.fill(foot);
|
|
493
|
+
fg.save(); fg.clip(bowl); fg.fillStyle = C.gold2; fg.fillRect(cw * 0.12, 0, cw, ch); fg.fillStyle = '#f4d77a'; fg.fillRect(-cw * 0.36, 6, 6, ch * 0.5); fg.restore();
|
|
494
|
+
fg.fillStyle = '#f2d77c'; fg.beginPath(); fg.ellipse(0, 0, cw / 2, 5.5, 0, 0, Math.PI * 2); fg.fill(); fg.strokeStyle = C.outline; fg.lineWidth = 4; fg.stroke();
|
|
495
|
+
fg.fillStyle = '#7a5418'; fg.beginPath(); fg.ellipse(0, 0.5, cw / 2 - 6, 2.6, 0, 0, Math.PI * 2); fg.fill();
|
|
496
|
+
const fd = fg.getImageData(0, 0, w, h).data, sp = P.canvas(w, h), sg = sp.getContext('2d'), ts = 4.4, r = rng(91);
|
|
497
|
+
// 灰浆底:只在杯形(alpha)里
|
|
498
|
+
sg.drawImage(fc, 0, 0); sg.globalCompositeOperation = 'source-in'; sg.fillStyle = 'rgb(96,86,74)'; sg.fillRect(0, 0, w, h); sg.globalCompositeOperation = 'source-over';
|
|
499
|
+
for (let y = 0; y < h; y += ts) for (let x = ((y / ts) % 2) * ts * 0.5 - ts; x < w; x += ts) {
|
|
500
|
+
const cx = x + ts / 2, cy = y + ts / 2; if (cx < 0 || cx >= w || cy >= h) continue;
|
|
501
|
+
const o = ((cy | 0) * w + (cx | 0)) * 4; if (fd[o + 3] < 110) continue;
|
|
502
|
+
const f = 1 + (r() - .5) * 0.12; sg.fillStyle = P.rgb([fd[o] * f, fd[o + 1] * f, fd[o + 2] * f]);
|
|
503
|
+
sg.fillRect(x + 0.6 + (r() - .5) * 0.5, y + 0.6 + (r() - .5) * 0.5, ts - 1.2, ts - 1.2);
|
|
504
|
+
}
|
|
505
|
+
return sp;
|
|
506
|
+
}
|
|
507
|
+
function steamTiles(c, cup, t, a) { // 白石热气:两列方石沿正弦上行
|
|
508
|
+
if (a < 0.03) return;
|
|
509
|
+
c.save(); c.globalAlpha = a;
|
|
510
|
+
for (let k = 0; k < 2; k++) for (let i = 0; i < 7; i++) {
|
|
511
|
+
const q = ((i + (t * 2.2) % 1) / 7), x = cup.x - 9 + k * 18 + Math.sin(t * 6 + k * 2.2 - q * 6) * 9 * q, y = cup.y - 12 - q * 76, s = 7 - q * 2;
|
|
512
|
+
c.globalAlpha = a * (1 - q * 0.7); c.fillStyle = 'rgb(96,86,74)'; c.fillRect(x - s / 2 - 1, y - s / 2 - 1, s + 2, s + 2); c.fillStyle = '#fbf8f0'; c.fillRect(x - s / 2, y - s / 2, s, s);
|
|
513
|
+
}
|
|
514
|
+
c.restore();
|
|
515
|
+
}
|
|
516
|
+
function edt(F, w, h) { // 2D 平方欧氏距离变换(Felzenszwalb–Huttenlocher)
|
|
517
|
+
const n = Math.max(w, h), f = new Float64Array(n), d = new Float64Array(n), v = new Int32Array(n), z = new Float64Array(n + 1);
|
|
518
|
+
const pass = (len) => { let k = 0; v[0] = 0; z[0] = -1e30; z[1] = 1e30;
|
|
519
|
+
for (let q = 1; q < len; q++) { let s = ((f[q] + q * q) - (f[v[k]] + v[k] * v[k])) / (2 * q - 2 * v[k]); while (s <= z[k]) { k--; s = ((f[q] + q * q) - (f[v[k]] + v[k] * v[k])) / (2 * q - 2 * v[k]); } k++; v[k] = q; z[k] = s; z[k + 1] = 1e30; }
|
|
520
|
+
k = 0; for (let q = 0; q < len; q++) { while (z[k + 1] < q) k++; d[q] = (q - v[k]) * (q - v[k]) + f[v[k]]; } };
|
|
521
|
+
const out = new Float64Array(w * h);
|
|
522
|
+
for (let x = 0; x < w; x++) { for (let y = 0; y < h; y++) f[y] = F[y * w + x]; pass(h); for (let y = 0; y < h; y++) out[y * w + x] = d[y]; }
|
|
523
|
+
for (let y = 0; y < h; y++) { for (let x = 0; x < w; x++) f[x] = out[y * w + x]; pass(w); for (let x = 0; x < w; x++) out[y * w + x] = d[x]; }
|
|
524
|
+
return out;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
return {
|
|
528
|
+
init() { if (!S) build(); },
|
|
529
|
+
draw(c, lt, t) {
|
|
530
|
+
if (!S) build();
|
|
531
|
+
const { bg, og, ID, SH, SO, TCr, TCg, TCb } = S;
|
|
532
|
+
fullBase(bg, S.sb, lt, t);
|
|
533
|
+
const bd = bg.getImageData(0, 0, W, H).data;
|
|
534
|
+
const dyn = S.dyn; for (let i = 0; i < dyn.length; i++) S.color(dyn[i], bd);
|
|
535
|
+
for (const R of S.rects) {
|
|
536
|
+
const dd = R.img.data; let o = 0;
|
|
537
|
+
for (let y = R.y; y < R.y + R.h; y++) { let p = y * W + R.x; for (let x = 0; x < R.w; x++, p++, o += 4) {
|
|
538
|
+
const k = ID[p];
|
|
539
|
+
if (k < 0) { const q = p * 4; dd[o] = SO[q]; dd[o + 1] = SO[q + 1]; dd[o + 2] = SO[q + 2]; }
|
|
540
|
+
else { const sh = SH[p]; dd[o] = (TCr[k] * sh) >> 7; dd[o + 1] = (TCg[k] * sh) >> 7; dd[o + 2] = (TCb[k] * sh) >> 7; }
|
|
541
|
+
dd[o + 3] = 255;
|
|
542
|
+
} }
|
|
543
|
+
og.putImageData(R.img, R.x, R.y);
|
|
544
|
+
}
|
|
545
|
+
c.drawImage(S.oc, 0, 0);
|
|
546
|
+
// 外框传送带:同一条周期石块带,四边各自局部坐标(顺时针),按相位平移
|
|
547
|
+
const off = ((borderPhase(lt) % PER) + PER) % PER;
|
|
548
|
+
for (const [a, b, c2, d, e, f, len] of SIDES) { c.save(); c.transform(a, b, c2, d, e, f); c.beginPath(); c.rect(BW, 0, len - 2 * BW, BW); c.clip(); c.drawImage(S.strip, BW - 2 * PER + off, 0); c.restore(); }
|
|
549
|
+
// 杯子拼片与热气(跟着手走)
|
|
550
|
+
const { ch, G } = poses(lt, t), AP = armPose(G, ch.cup);
|
|
551
|
+
c.save(); c.translate(AP.cup.x, AP.cup.y); c.rotate(AP.cup.tilt); c.drawImage(S.cupSp, -CSP[0], -CSP[1]); c.restore();
|
|
552
|
+
steamTiles(c, AP.cup, t, 1 - ch.cup * 0.85);
|
|
553
|
+
},
|
|
554
|
+
};
|
|
555
|
+
})();
|