@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,390 @@
|
|
|
1
|
+
// 角色骨架:17 岁欧洲金发少女(侧身朝左坐)+ 橘白猫(朝右坐)。
|
|
2
|
+
// 每个部位输出 Path2D,时代代码自己决定填色/描边/剪影/渲染器——同一副骨架、同一套动作,只换画法。
|
|
3
|
+
// 坐标基于标准构图(分镜表 §2.5):人 (1120,200)-(1500,910),猫 (390,610)-(690,905),桌 (810,620)-(1200,905)。
|
|
4
|
+
(() => {
|
|
5
|
+
const { clamp, lerp } = U;
|
|
6
|
+
const R = window.RIG = {};
|
|
7
|
+
|
|
8
|
+
// 二骨 IK:肩 S,目标 T,上臂 a,前臂 b,bendSign 决定肘向
|
|
9
|
+
R.ik2 = (S, T, a, b, bendSign = 1) => {
|
|
10
|
+
let dx = T[0] - S[0], dy = T[1] - S[1], d = Math.hypot(dx, dy);
|
|
11
|
+
d = Math.min(d, a + b - 0.01);
|
|
12
|
+
const ang = Math.atan2(dy, dx), cosA = clamp((a * a + d * d - b * b) / (2 * a * d), -1, 1);
|
|
13
|
+
const e = ang + bendSign * Math.acos(cosA);
|
|
14
|
+
const E = [S[0] + Math.cos(e) * a, S[1] + Math.sin(e) * a];
|
|
15
|
+
const H = [S[0] + Math.cos(ang) * d, S[1] + Math.sin(ang) * d];
|
|
16
|
+
return { E, H };
|
|
17
|
+
};
|
|
18
|
+
// 粗细变化的肢体:沿 A→B 画一个圆头胶囊。两端半圆帽朝外(逆时针从 n 走到 n-π)。
|
|
19
|
+
// 旧版 arc 方向反了,帽子凹进胶囊、路径自交,粗描边/平涂时关节处露洞(迁移测试 C:哈林小人被画成奶牛)。
|
|
20
|
+
R.limb = (A, B, wA, wB) => {
|
|
21
|
+
const p = new Path2D(), a = Math.atan2(B[1] - A[1], B[0] - A[0]), n = a + Math.PI / 2;
|
|
22
|
+
p.moveTo(A[0] + Math.cos(n) * wA / 2, A[1] + Math.sin(n) * wA / 2);
|
|
23
|
+
p.lineTo(B[0] + Math.cos(n) * wB / 2, B[1] + Math.sin(n) * wB / 2);
|
|
24
|
+
p.arc(B[0], B[1], wB / 2, n, n - Math.PI, true);
|
|
25
|
+
p.lineTo(A[0] - Math.cos(n) * wA / 2, A[1] - Math.sin(n) * wA / 2);
|
|
26
|
+
p.arc(A[0], A[1], wA / 2, n - Math.PI, n - 2 * Math.PI, true);
|
|
27
|
+
p.closePath(); return p;
|
|
28
|
+
};
|
|
29
|
+
// 非等比拉长(毕加索蓝色时期、El Greco 式的瘦长人物):以 anchor 为轴把整副骨架按 (sx, sy) 缩放。
|
|
30
|
+
// 所有 Path2D(含 .pts 点列)、锚点、杯子、眼睛、HP/T 坐标函数一起变换;线宽不变(不像 setTransform 那样把描边也压扁)。
|
|
31
|
+
R.stretch = (O, sx = 1, sy = 1, anchor = [0, 0]) => {
|
|
32
|
+
if (sx === 1 && sy === 1) return O;
|
|
33
|
+
const m = new DOMMatrix().translate(anchor[0], anchor[1]).scale(sx, sy).translate(-anchor[0], -anchor[1]);
|
|
34
|
+
const mp = q => { const r = m.transformPoint(new DOMPoint(q[0], q[1])); return [r.x, r.y]; };
|
|
35
|
+
const isPt = v => Array.isArray(v) && v.length === 2 && typeof v[0] === 'number' && typeof v[1] === 'number';
|
|
36
|
+
const map = v => {
|
|
37
|
+
if (v instanceof Path2D) { const p = new Path2D(); p.addPath(v, m); if (v.pts) { p.pts = v.pts.map(mp); p.closed = v.closed; } return p; }
|
|
38
|
+
if (isPt(v)) return mp(v);
|
|
39
|
+
if (Array.isArray(v)) return v.map(map);
|
|
40
|
+
if (v && typeof v === 'object' && 'x' in v && 'y' in v) { const q = mp([v.x, v.y]), o = { ...v, x: q[0], y: q[1] }; if ('w' in v) { o.w = v.w * sx; o.h = v.h * sy; } if ('r' in v) o.r = v.r * Math.sqrt(sx * sy); if ('rx' in v) { o.rx = v.rx * sx; o.ry = v.ry * sy; } return o; }
|
|
41
|
+
if (v && typeof v === 'object' && v.constructor === Object) { const o = {}; for (const k in v) o[k] = map(v[k]); return o; }
|
|
42
|
+
return v;
|
|
43
|
+
};
|
|
44
|
+
const out = {};
|
|
45
|
+
for (const k in O) {
|
|
46
|
+
if (typeof O[k] === 'function') { const f = O[k]; out[k] = (...a) => mp(f(...a)); }
|
|
47
|
+
else if (k === 'rigid' || k === 'tilt' || k === 'blink' || k === 'tailW' || k === 'eyeR' || k === 'headR') out[k] = O[k];
|
|
48
|
+
else out[k] = map(O[k]);
|
|
49
|
+
}
|
|
50
|
+
out.stretch = { sx, sy, anchor, matrix: m };
|
|
51
|
+
return out;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
// ---------------- 少女(v2:按设定稿逐部位量点,Catmull-Rom 平滑) ----------------
|
|
55
|
+
// 关键点取自 生图/plates/09_postimp.png(AI 设定稿)叠网格读数;参考姿势 = 端杯在嘴边。
|
|
56
|
+
R.smooth = (pts, closed = true, k = 0.5) => { // Catmull-Rom → 三次贝塞尔
|
|
57
|
+
const p = new Path2D(), n = pts.length; if (n < 2) return p;
|
|
58
|
+
const P_ = i => pts[closed ? (i + n) % n : Math.max(0, Math.min(n - 1, i))];
|
|
59
|
+
p.moveTo(...pts[0]);
|
|
60
|
+
for (let i = 0; i < (closed ? n : n - 1); i++) {
|
|
61
|
+
const p0 = P_(i - 1), p1 = P_(i), p2 = P_(i + 1), p3 = P_(i + 2);
|
|
62
|
+
p.bezierCurveTo(p1[0] + (p2[0] - p0[0]) * k / 3, p1[1] + (p2[1] - p0[1]) * k / 3, p2[0] - (p3[0] - p1[0]) * k / 3, p2[1] - (p3[1] - p1[1]) * k / 3, p2[0], p2[1]);
|
|
63
|
+
}
|
|
64
|
+
if (closed) p.closePath(); return p;
|
|
65
|
+
};
|
|
66
|
+
R.taper = (A, B, wA, wB, bulge = 0) => { // 锥形肢体(袖子/手臂),bulge 让中段鼓起
|
|
67
|
+
const a = Math.atan2(B[1] - A[1], B[0] - A[0]), nx = Math.cos(a + Math.PI / 2), ny = Math.sin(a + Math.PI / 2);
|
|
68
|
+
const M = [(A[0] + B[0]) / 2, (A[1] + B[1]) / 2], wM = (wA + wB) / 2 + bulge;
|
|
69
|
+
return R.smooth([[A[0] + nx * wA / 2, A[1] + ny * wA / 2], [M[0] + nx * wM / 2, M[1] + ny * wM / 2], [B[0] + nx * wB / 2, B[1] + ny * wB / 2],
|
|
70
|
+
[B[0] + Math.cos(a) * wB * 0.35, B[1] + Math.sin(a) * wB * 0.35], [B[0] - nx * wB / 2, B[1] - ny * wB / 2], [M[0] - nx * wM / 2, M[1] - ny * wM / 2], [A[0] - nx * wA / 2, A[1] - ny * wA / 2], [A[0] - Math.cos(a) * wA * 0.3, A[1] - Math.sin(a) * wA * 0.3]]);
|
|
71
|
+
};
|
|
72
|
+
// opt: { cup:0..1, sip, dx, dy, s, hat:'straw'|null, hair:'bun'|'long'|'braid'|'down', breathe,
|
|
73
|
+
// sx, sy, anchor:[x,y](非等比拉长,默认以脚底 (1240,932) 为轴,见 R.stretch) }
|
|
74
|
+
// 返回 G:每个部件一个 Path2D(RIG.smooth 生成的带 .pts 点列,可沿轮廓逐点改线宽/重剪);G.A 锚点;
|
|
75
|
+
// G.pivots 关节枢轴+G.rigid 刚体分组(皮影/剪纸偶:每组只绕自己的枢轴转,见文件末 R.rigidPose)。
|
|
76
|
+
R.girl = (opt = {}) => {
|
|
77
|
+
const { cup = 0, sip = 0, dx = 0, dy = 0, s = 1, breathe = 0 } = opt;
|
|
78
|
+
const T = (x, y) => [dx + 1300 + (x - 1300) * s, dy + 620 + (y - 620) * s];
|
|
79
|
+
const neck = T(1300, 425), tilt = -0.07 * cup - sip * 0.6;
|
|
80
|
+
const HP = (x, y) => { const p = T(x, y), c = Math.cos(tilt), si = Math.sin(tilt), X = p[0] - neck[0], Y = p[1] - neck[1]; return [neck[0] + X * c - Y * si, neck[1] + X * si + Y * c]; };
|
|
81
|
+
const TB = (x, y) => T(x, y + (y < 600 ? breathe * 150 * (600 - y) / 170 : 0)); // 呼吸只动上身
|
|
82
|
+
const sm = (f, pts, closed = true) => { const q = pts.map(p => f(...p)); return Object.assign(R.smooth(q, closed), { pts: q, closed }); }; // Path2D 带点列 .pts
|
|
83
|
+
const G = {};
|
|
84
|
+
// —— 头部组 ——
|
|
85
|
+
G.hairBack = sm(HP, [[1262, 312], [1300, 290], [1350, 293], [1392, 322], [1408, 368], [1398, 420], [1372, 448], [1346, 442], [1332, 412], [1318, 380], [1292, 340]]);
|
|
86
|
+
const style = opt.hair || 'bun';
|
|
87
|
+
G.bun = style === 'bun' ? (() => { const c0 = HP(1390, 372), p = new Path2D(); p.arc(c0[0], c0[1], 31 * s, 0, Math.PI * 2); return p; })() : null;
|
|
88
|
+
G.bunSpiral = HP(1390, 372);
|
|
89
|
+
G.hairDown = (style === 'long' || style === 'down') ? sm(HP, [[1340, 420], [1385, 430], [1418, 480], [1425, 560], [1405, 600], [1385, 540], [1360, 470]]) : null;
|
|
90
|
+
G.braid = style === 'braid' ? [0, 1, 2, 3, 4].map(k => HP(1392 - k * 2, 440 + k * 30)) : null;
|
|
91
|
+
G.face = sm(HP, [[1258, 318], [1252, 335], [1250, 352], [1242, 366], [1249, 373], [1256, 378], [1253, 386], [1259, 391], [1255, 398], [1263, 406], [1276, 413], [1296, 412], [1316, 398], [1326, 370], [1320, 335], [1296, 314]]);
|
|
92
|
+
G.neck = sm(T, [[1286, 400], [1314, 400], [1324, 445], [1290, 448]]);
|
|
93
|
+
G.bangs = sm(HP, [[1248, 322], [1260, 300], [1290, 293], [1322, 304], [1338, 330], [1332, 364], [1320, 346], [1302, 328], [1282, 322], [1266, 330], [1257, 346]]);
|
|
94
|
+
G.locks = [sm(HP, [[1318, 345], [1308, 372], [1314, 398], [1306, 420], [1314, 440]], false), sm(HP, [[1326, 352], [1322, 380], [1330, 405], [1324, 430]], false)];
|
|
95
|
+
G.hairLines = [sm(HP, [[1262, 312], [1300, 300], [1345, 312], [1380, 340]], false), sm(HP, [[1275, 322], [1315, 318], [1350, 338], [1372, 372]], false), sm(HP, [[1300, 330], [1340, 352], [1360, 395]], false)];
|
|
96
|
+
const ec = HP(1268, 350); G.eye = { x: ec[0], y: ec[1], r: 5.5 * s };
|
|
97
|
+
G.lid = sm(HP, [[1259, 348], [1268, 343], [1279, 348]], false);
|
|
98
|
+
G.lashes = [[HP(1259, 348), HP(1254, 343)], [HP(1262, 345), HP(1258, 339)]];
|
|
99
|
+
G.brow = sm(HP, [[1257, 335], [1268, 330], [1281, 334]], false);
|
|
100
|
+
G.nostril = HP(1250, 382);
|
|
101
|
+
G.lips = [HP(1252, 389), HP(1259, 390), HP(1254, 397)];
|
|
102
|
+
G.cheek = HP(1283, 380); G.ear = HP(1318, 372);
|
|
103
|
+
if (opt.hat === 'straw' || opt.hat === 'bonnet') {
|
|
104
|
+
G.brim = (() => { const c0 = HP(1326, 300), p = new Path2D(); p.ellipse(c0[0], c0[1], 130 * s, 25 * s, tilt + 0.19, 0, Math.PI * 2); return p; })();
|
|
105
|
+
G.crown = (() => { const c0 = HP(1336, 290), p = new Path2D(); p.ellipse(c0[0], c0[1], 62 * s, 44 * s, tilt + 0.14, Math.PI, 0); p.closePath(); return p; })();
|
|
106
|
+
G.band = sm(HP, [[1276, 283], [1336, 296], [1398, 306], [1396, 318], [1336, 309], [1276, 296]]);
|
|
107
|
+
G.bow = HP(1404, 318);
|
|
108
|
+
}
|
|
109
|
+
// —— 身体 ——
|
|
110
|
+
G.torso = sm(TB, [[1290, 432], [1318, 428], [1350, 434], [1380, 446], [1404, 482], [1416, 542], [1424, 600], [1412, 622], [1300, 618], [1285, 560], [1282, 500], [1286, 460]]);
|
|
111
|
+
G.bib = sm(TB, [[1291, 438], [1313, 436], [1307, 500], [1303, 560], [1305, 612], [1286, 612], [1283, 520]]);
|
|
112
|
+
G.skirt = sm(T, [[1300, 605], [1420, 612], [1446, 650], [1452, 722], [1440, 762], [1362, 772], [1342, 802], [1352, 926], [1220, 932], [1104, 926], [1118, 820], [1150, 722], [1190, 656], [1242, 630]]);
|
|
113
|
+
G.apronSkirt = sm(T, [[1300, 612], [1352, 642], [1332, 702], [1316, 822], [1200, 824], [1192, 702], [1200, 656], [1252, 630]]);
|
|
114
|
+
G.folds = [sm(T, [[1230, 670], [1222, 760], [1226, 815]], false), sm(T, [[1268, 668], [1262, 760], [1268, 818]], false), sm(T, [[1160, 760], [1150, 860], [1146, 920]], false), sm(T, [[1300, 820], [1306, 880], [1300, 925]], false)];
|
|
115
|
+
G.apronBow = [T(1428, 616), T(1450, 600), T(1452, 634), T(1440, 690)];
|
|
116
|
+
G.shoe = sm(T, [[1098, 918], [1120, 910], [1146, 914], [1150, 928], [1100, 930]]);
|
|
117
|
+
// 远侧手臂:搭在桌上(在躯干后面画)
|
|
118
|
+
G.farSleeve = sm(TB, [[1332, 468], [1364, 520], [1346, 600], [1250, 632], [1212, 626], [1220, 604], [1300, 584], [1318, 520]]);
|
|
119
|
+
G.farCuff = sm(T, [[1222, 604], [1236, 604], [1238, 630], [1220, 632]]);
|
|
120
|
+
G.farHand = sm(T, [[1218, 606], [1180, 604], [1150, 609], [1146, 618], [1180, 626], [1218, 630]]);
|
|
121
|
+
// 近侧手臂:IK,杯子从桌面(1196,598)举到嘴边(1226,380)
|
|
122
|
+
const S = TB(1352, 456);
|
|
123
|
+
const cupTable = T(1190, 596), cupMouth = HP(1226, 376);
|
|
124
|
+
const cp = [lerp(cupTable[0], cupMouth[0], cup), lerp(cupTable[1], cupMouth[1], cup)];
|
|
125
|
+
const handT = [cp[0] + 28 * s, cp[1] + 16 * s];
|
|
126
|
+
const { E, H } = R.ik2(S, handT, 165 * s, 122 * s, -1);
|
|
127
|
+
G.upperArm = R.taper(S, E, 54 * s, 40 * s, 10 * s);
|
|
128
|
+
const W_ = [lerp(E[0], H[0], 0.82), lerp(E[1], H[1], 0.82)];
|
|
129
|
+
G.foreArm = R.taper(E, W_, 40 * s, 30 * s, 2 * s);
|
|
130
|
+
G.cuff = R.taper([lerp(E[0], H[0], 0.76), lerp(E[1], H[1], 0.76)], W_, 32 * s, 31 * s);
|
|
131
|
+
const fa = Math.atan2(H[1] - E[1], H[0] - E[0]);
|
|
132
|
+
G.hand = (() => { const p = new Path2D(), r = 15 * s; p.ellipse(H[0], H[1], r * 1.15, r, fa, 0, Math.PI * 2); return p; })();
|
|
133
|
+
G.thumb = [H[0] + Math.cos(fa - 1.2) * 14 * s, H[1] + Math.sin(fa - 1.2) * 14 * s];
|
|
134
|
+
G.cup = { x: cp[0], y: cp[1] - 18 * s, w: 44 * s, h: 38 * s, tilt: -0.45 * cup };
|
|
135
|
+
G.hipSeat = T(1400, 700);
|
|
136
|
+
// 锚点:给时代代码加服饰/头饰/光环用(都已跟随缩放、头部点跟随头部倾斜)
|
|
137
|
+
G.A = { headTop: HP(1320, 288), headC: HP(1318, 360), forehead: HP(1262, 318), nape: HP(1360, 420), ear: HP(1318, 372), chin: HP(1270, 410),
|
|
138
|
+
neck: T(1305, 435), chest: TB(1300, 500), shoulder: S, back: TB(1410, 520), waist: T(1350, 612), lap: T(1250, 640), knee: T(1190, 660), hemL: T(1104, 926), hemR: T(1352, 926), elbow: E, hand: H };
|
|
139
|
+
G.tilt = tilt;
|
|
140
|
+
G.HP = HP; G.T = T; // 头部局部坐标(随仰头旋转)与身体坐标,加头饰/服饰用
|
|
141
|
+
G.A.wrist = W_; G.A.cupBottom = [G.cup.x, G.cup.y + G.cup.h];
|
|
142
|
+
G.box = [T(1100, 240), T(1460, 935)];
|
|
143
|
+
// 刚体部件+枢轴:head 组绕 neck 转(仰头就是这么转的),upperArm 绕 shoulder,foreArm/cuff 绕 elbow,hand 绕 wrist
|
|
144
|
+
G.pivots = { neck, shoulder: S, elbow: E, wrist: W_, hip: T(1400, 700), farShoulder: TB(1332, 468), knee: G.A.knee };
|
|
145
|
+
G.rigid = { head: ['hairBack', 'bun', 'hairDown', 'braid', 'face', 'bangs', 'locks', 'hairLines', 'lid', 'brow', 'brim', 'crown', 'band'], neck: ['neck'],
|
|
146
|
+
torso: ['torso', 'bib'], skirt: ['skirt', 'apronSkirt', 'folds', 'shoe'], farArm: ['farSleeve', 'farCuff', 'farHand'],
|
|
147
|
+
upperArm: ['upperArm'], foreArm: ['foreArm', 'cuff'], hand: ['hand'] };
|
|
148
|
+
G.rigidPivot = { head: 'neck', neck: 'neck', torso: 'hip', skirt: 'hip', farArm: 'farShoulder', upperArm: 'shoulder', foreArm: 'elbow', hand: 'wrist' };
|
|
149
|
+
if (opt.sx || opt.sy) return R.stretch(G, opt.sx || 1, opt.sy || 1, opt.anchor || [1240, 932]);
|
|
150
|
+
return G;
|
|
151
|
+
};
|
|
152
|
+
// ---------- 分层绘制的钩子(迁移测试 C 的 X.girl/X.cat 思路,正式并入) ----------
|
|
153
|
+
// pal.before = {部件名: (c, G, pal) => {}}、pal.after = {部件名: fn}:在任意部件前/后插画(领子、裙撑、刺绣、发饰、项圈……)。
|
|
154
|
+
// pal.hooks = {别名: fn}:常用插槽的简写,别名表见 GIRL_SLOTS / CAT_SLOTS。
|
|
155
|
+
// 钩子画的东西和部件一样参与遮挡:在 mode:'line'(visibleLines)下也只露出看得见的线——钩子里描线请用 pal.line,别写死颜色。
|
|
156
|
+
const GIRL_SLOTS = { back: ['before', 'farSleeve'], skirt: ['after', 'skirt'], torso: ['after', 'torso'], head: ['after', 'bangs'], face: ['after', 'features'], arm: ['after', 'cuff'], end: ['after', 'thumb'] };
|
|
157
|
+
const CAT_SLOTS = { back: ['before', 'tail'], body: ['after', 'white'], head: ['after', 'stripes'], end: ['after', 'whiskers'] };
|
|
158
|
+
const hooker = (c, O, pal, slots) => {
|
|
159
|
+
const B = { ...(pal.before || {}) }, A = { ...(pal.after || {}) };
|
|
160
|
+
if (pal.hooks) for (const k in pal.hooks) { const sl = slots[k]; if (!sl) throw new Error(`RIG 钩子别名不存在:${k}(可用 ${Object.keys(slots).join('/')},或用 before/after:{部件名})`); (sl[0] === 'before' ? B : A)[sl[1]] = pal.hooks[k]; }
|
|
161
|
+
return (name, fn) => { if (B[name]) B[name](c, O, pal); fn(); if (A[name]) A[name](c, O, pal); };
|
|
162
|
+
};
|
|
163
|
+
// 少女部件画序(远→近)。自己逐部件上色时照这个顺序画,遮挡就是对的。
|
|
164
|
+
R.GIRL_ORDER = ['farSleeve', 'farHand', 'farCuff', 'hairDown', 'skirt', 'apronSkirt', 'folds', 'shoe', 'torso', 'bib', 'apronBow', 'neck', 'hairBack', 'bun', 'bunSpiral', 'face', 'cheek', 'ear',
|
|
165
|
+
'bangs', 'hairLines', 'locks', 'features', 'hat', 'upperArm', 'foreArm', 'cuff', 'hand', 'cup', 'thumb'];
|
|
166
|
+
// 端杯手与杯子的前后:杯子在手前面(侧身朝左时杯子靠观众这侧)
|
|
167
|
+
// pal 颜色:dress(skirt/torso/farSleeve 可单独覆盖)sleeve skin hair hairLine cuff apron fold shoe iris lip cheek browC hat hatBand cupBody cupRim
|
|
168
|
+
// pal 线:line lw lwOf:{部件名:线宽} mode:'both'|'fill'|'line'(line 内部走 visibleLines)lineKey
|
|
169
|
+
// pal 分层:before/after/hooks(见上);paint(c, path, color, name) 接管每个部件的「填+描」(赛璐珞、网点、剪纸边……);
|
|
170
|
+
// features(c, G, pal) 接管五官;cup(c, G, pal) 接管杯子;blink 闭眼;locks:false 不画鬓角发绺。
|
|
171
|
+
R.drawGirl = (c, G, pal) => {
|
|
172
|
+
if (pal.mode === 'line') { // 线稿模式:内部走 visibleLines,被遮住的轮廓(远侧袖子、帽檐后半圈、被手臂挡住的躯干线)不会画穿
|
|
173
|
+
const [a, b] = G.box, box = [a[0] - 60, a[1] - 90, b[0] + 60, b[1] + 30];
|
|
174
|
+
c.drawImage(R.visibleLines(pal.lineKey || 'girl', (g, lc) => R.drawGirl(g, G, { ...pal, mode: 'both', line: lc }), pal.line || '#1b1d3a', box), 0, 0); return;
|
|
175
|
+
}
|
|
176
|
+
const L = pal.line || '#1b1d3a', lw = pal.lw ?? 3, mode = pal.mode || 'both';
|
|
177
|
+
const step = hooker(c, G, pal, GIRL_SLOTS);
|
|
178
|
+
const LWof = (name, d = lw) => pal.lwOf && pal.lwOf[name] != null ? pal.lwOf[name] : d;
|
|
179
|
+
const F = (name, p, col, line = true) => step(name, () => {
|
|
180
|
+
if (!p || !col) return;
|
|
181
|
+
if (pal.paint) { pal.paint(c, p, col, name); return; }
|
|
182
|
+
if (mode !== 'line') { c.fillStyle = col; c.fill(p); }
|
|
183
|
+
const w = LWof(name); if (w && line && mode !== 'fill') { c.strokeStyle = L; c.lineWidth = w; c.stroke(p); }
|
|
184
|
+
});
|
|
185
|
+
const S_ = (p, col, w) => { if (!p) return; c.strokeStyle = col; c.lineWidth = w; c.stroke(p); };
|
|
186
|
+
c.save(); c.lineJoin = 'round'; c.lineCap = 'round';
|
|
187
|
+
F('farSleeve', G.farSleeve, pal.farSleeve || pal.dress); F('farHand', G.farHand, pal.skin); F('farCuff', G.farCuff, pal.cuff || pal.apron || '#f3eee2');
|
|
188
|
+
F('hairDown', G.hairDown, pal.hair);
|
|
189
|
+
F('skirt', G.skirt, pal.skirt || pal.dress); if (pal.apron) F('apronSkirt', G.apronSkirt, pal.apron);
|
|
190
|
+
step('folds', () => { if (pal.fold) G.folds.forEach(f => S_(f, pal.fold, 2.5)); });
|
|
191
|
+
F('shoe', G.shoe, pal.shoe || '#4a2c1a');
|
|
192
|
+
F('torso', G.torso, pal.torso || pal.dress); if (pal.apron) F('bib', G.bib, pal.apron);
|
|
193
|
+
step('apronBow', () => { if (!pal.apron) return; c.strokeStyle = L; c.lineWidth = lw; c.fillStyle = pal.apron; const b = G.apronBow; c.beginPath(); c.ellipse(b[0][0] + 10, b[0][1] - 6, 16, 10, -0.5, 0, Math.PI * 2); c.fill(); c.stroke(); c.beginPath(); c.moveTo(...b[0]); c.lineTo(...b[3]); c.lineWidth = 9; c.strokeStyle = pal.apron; c.stroke(); });
|
|
194
|
+
F('neck', G.neck, pal.skin);
|
|
195
|
+
F('hairBack', G.hairBack, pal.hair); F('bun', G.bun, pal.hair);
|
|
196
|
+
step('bunSpiral', () => { if (G.bun && pal.hairLine) { c.strokeStyle = pal.hairLine; c.lineWidth = 2.5; c.beginPath(); for (let a = 0; a < 9; a += 0.2) { const r = 4 + a * 2.6; const x = G.bunSpiral[0] + Math.cos(a) * r, y = G.bunSpiral[1] + Math.sin(a) * r; a ? c.lineTo(x, y) : c.moveTo(x, y); } c.stroke(); } });
|
|
197
|
+
F('face', G.face, pal.skin);
|
|
198
|
+
step('cheek', () => { c.save(); c.clip(G.face); if (pal.cheek) { c.fillStyle = pal.cheek; c.beginPath(); c.arc(G.cheek[0], G.cheek[1], 13, 0, Math.PI * 2); c.fill(); } c.restore(); });
|
|
199
|
+
step('ear', () => { if (G.ear && pal.skin) { c.fillStyle = pal.skin; c.beginPath(); c.ellipse(G.ear[0], G.ear[1], 8, 12, 0.2, 0, Math.PI * 2); c.fill(); const w = LWof('ear', lw * 0.7); if (w) { c.strokeStyle = L; c.lineWidth = w; c.stroke(); } } });
|
|
200
|
+
F('bangs', G.bangs, pal.hair);
|
|
201
|
+
step('hairLines', () => { if (pal.hairLine) { G.hairLines.forEach(h => S_(h, pal.hairLine, 2.2)); } });
|
|
202
|
+
step('locks', () => { if (pal.locks !== false) G.locks.forEach(h => { S_(h, L, 9); S_(h, pal.hair, 6); }); });
|
|
203
|
+
// 五官
|
|
204
|
+
step('features', () => {
|
|
205
|
+
if (pal.features) { pal.features(c, G, pal); return; }
|
|
206
|
+
if (pal.blink) { c.strokeStyle = L; c.lineWidth = 2.8; c.beginPath(); c.arc(G.eye.x, G.eye.y - 1, 7, 0.25, Math.PI - 0.25); c.stroke(); }
|
|
207
|
+
else {
|
|
208
|
+
S_(G.lid, L, 2.6); G.lashes.forEach(l => { c.beginPath(); c.moveTo(...l[0]); c.lineTo(...l[1]); c.strokeStyle = L; c.lineWidth = 2; c.stroke(); });
|
|
209
|
+
c.fillStyle = pal.iris || '#2b4d8a'; c.beginPath(); c.ellipse(G.eye.x - 1, G.eye.y + 1.5, G.eye.r * 0.8, G.eye.r, 0, 0, Math.PI * 2); c.fill();
|
|
210
|
+
c.fillStyle = '#fff'; c.beginPath(); c.arc(G.eye.x - 2.5, G.eye.y - 0.5, 1.6, 0, Math.PI * 2); c.fill();
|
|
211
|
+
}
|
|
212
|
+
S_(G.brow, pal.browC || pal.hairLine || L, 2.4);
|
|
213
|
+
c.fillStyle = pal.lip || '#cf5a58'; c.beginPath(); c.moveTo(...G.lips[0]); c.lineTo(...G.lips[1]); c.lineTo(...G.lips[2]); c.closePath(); c.fill();
|
|
214
|
+
});
|
|
215
|
+
// 帽
|
|
216
|
+
step('hat', () => { if (G.brim && pal.hat) { F('brim', G.brim, pal.hat); F('crown', G.crown, pal.hat); F('band', G.band, pal.hatBand || '#2d4fa0', false); c.fillStyle = pal.hatBand || '#2d4fa0'; c.beginPath(); c.ellipse(G.bow[0], G.bow[1], 14, 9, 0.6, 0, Math.PI * 2); c.fill(); } });
|
|
217
|
+
// 近侧手臂、杯、手
|
|
218
|
+
F('upperArm', G.upperArm, pal.sleeve || pal.dress); F('foreArm', G.foreArm, pal.sleeve || pal.dress); F('cuff', G.cuff, pal.cuff || pal.apron || '#f3eee2');
|
|
219
|
+
F('hand', G.hand, pal.skin);
|
|
220
|
+
step('cup', () => { if (pal.cup) pal.cup(c, G, pal); else R.drawCup(c, G.cup, { body: pal.cupBody || '#2a4fa8', rim: pal.cupRim || '#5a3216', line: L, lw: lw * 0.8, hw: 7 }); });
|
|
221
|
+
step('thumb', () => { c.fillStyle = pal.skin; c.beginPath(); c.arc(G.thumb[0], G.thumb[1], 7, 0, Math.PI * 2); c.fill(); const w = LWof('thumb', lw * 0.7); if (w) { c.strokeStyle = L; c.lineWidth = w; c.stroke(); } });
|
|
222
|
+
c.restore();
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
// 只画五官(迁移测试 C 的 X.features):「先渲染器、后补五官」的画法用——平涂进笔触/点彩后五官会糊,最后单独补一遍。
|
|
226
|
+
// s: { line, featureW(线宽倍数), iris, brow, lip, cheek, blink }
|
|
227
|
+
R.drawFeatures = (c, G, s = {}) => {
|
|
228
|
+
const L = s.line || '#111', k = s.featureW || 1;
|
|
229
|
+
c.save(); c.lineCap = 'round'; c.lineJoin = 'round';
|
|
230
|
+
if (s.blink) { c.strokeStyle = L; c.lineWidth = 3 * k; c.beginPath(); c.arc(G.eye.x, G.eye.y - 1, 7, 0.25, Math.PI - 0.25); c.stroke(); }
|
|
231
|
+
else {
|
|
232
|
+
c.strokeStyle = L; c.lineWidth = 2.8 * k; c.stroke(G.lid);
|
|
233
|
+
G.lashes.forEach(l => { c.beginPath(); c.moveTo(...l[0]); c.lineTo(...l[1]); c.lineWidth = 2 * k; c.stroke(); });
|
|
234
|
+
c.fillStyle = s.iris || '#2b4d8a'; c.beginPath(); c.ellipse(G.eye.x - 1, G.eye.y + 1.5, G.eye.r * 0.8, G.eye.r, 0, 0, Math.PI * 2); c.fill();
|
|
235
|
+
c.fillStyle = '#fff'; c.beginPath(); c.arc(G.eye.x - 2.5, G.eye.y - 0.5, 1.6, 0, Math.PI * 2); c.fill();
|
|
236
|
+
}
|
|
237
|
+
c.strokeStyle = s.brow || L; c.lineWidth = 2.6 * k; c.stroke(G.brow);
|
|
238
|
+
c.fillStyle = s.lip || '#cf5a58'; c.beginPath(); c.moveTo(...G.lips[0]); c.lineTo(...G.lips[1]); c.lineTo(...G.lips[2]); c.closePath(); c.fill();
|
|
239
|
+
if (s.cheek) { c.save(); c.clip(G.face); c.fillStyle = s.cheek; c.beginPath(); c.arc(G.cheek[0], G.cheek[1], 13, 0, Math.PI * 2); c.fill(); c.restore(); }
|
|
240
|
+
c.restore();
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
// 画杯子(通用):c.translate 到杯口中心再画,style: {body, rim, line, handle:true}
|
|
244
|
+
R.drawCup = (c, cup, st = {}) => {
|
|
245
|
+
c.save(); c.translate(cup.x, cup.y); c.rotate(cup.tilt || 0);
|
|
246
|
+
const w = cup.w, h = cup.h;
|
|
247
|
+
c.beginPath(); c.moveTo(-w / 2, 0); c.lineTo(-w * 0.42, h); c.quadraticCurveTo(0, h * 1.12, w * 0.42, h); c.lineTo(w / 2, 0); c.closePath();
|
|
248
|
+
c.fillStyle = st.body || '#2a4fa8'; c.fill();
|
|
249
|
+
if (st.line) { c.strokeStyle = st.line; c.lineWidth = st.lw || 3; c.stroke(); }
|
|
250
|
+
if (st.handle !== false) { c.beginPath(); c.arc(w * 0.55, h * 0.4, h * 0.26, -1.2, 1.4); c.strokeStyle = st.body || '#2a4fa8'; c.lineWidth = st.hw || 7; c.stroke(); }
|
|
251
|
+
c.beginPath(); c.ellipse(0, 0, w / 2, h * 0.14, 0, 0, Math.PI * 2); c.fillStyle = st.rim || '#6b3f1f'; c.fill();
|
|
252
|
+
if (st.line) { c.strokeStyle = st.line; c.lineWidth = st.lw || 3; c.stroke(); }
|
|
253
|
+
c.restore();
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
// ---------------- 猫(橘白,朝右坐) ----------------
|
|
257
|
+
// opt: { tail(弧度), blink, ear, breathe, dx, dy, s, look, sx, sy, anchor }
|
|
258
|
+
// 返回 K:K.body 已平滑(Catmull-Rom,带 .pts;旧折线在 K.bodyPoly);白区拆成 K.chest / K.muzzle / K.paws,
|
|
259
|
+
// K.white = 三者合在一条 Path2D(兼容旧代码),K.whitePts 给点列/椭圆参数;K.pivots / K.rigid 同少女。
|
|
260
|
+
R.cat = (opt = {}) => {
|
|
261
|
+
const { tail = 0, blink = 0, breathe = 0, dx = 0, dy = 0, s = 1, look = 0 } = opt;
|
|
262
|
+
const T = (x, y) => [dx + 540 + (x - 540) * s, dy + 905 + (y - 905) * s];
|
|
263
|
+
const P = {};
|
|
264
|
+
// 身体:梨形
|
|
265
|
+
const body = new Path2D();
|
|
266
|
+
P.bodyPts = [[470, 900], [430, 860], [425, 790], [450, 720], [500, 680], [570, 668], [625, 690], [650, 750], [660, 830], [650, 900]].map(p => T(p[0], p[1] - (p[1] < 800 ? breathe * 300 : 0)));
|
|
267
|
+
P.bodyPts.forEach((p, i) => i ? body.lineTo(...p) : body.moveTo(...p)); body.closePath(); P.bodyPoly = body;
|
|
268
|
+
P.body = Object.assign(R.smooth(P.bodyPts), { pts: P.bodyPts, closed: true }); // 卡通/赛璐珞/不描线的画法里折线身体会露直边(迁移测试 B、D)
|
|
269
|
+
// 头
|
|
270
|
+
const hc = T(600 + look * 6, 625); P.headC = hc; P.headR = 64 * s;
|
|
271
|
+
const head = new Path2D(); head.ellipse(hc[0], hc[1], 66 * s, 58 * s, 0, 0, Math.PI * 2);
|
|
272
|
+
// 耳朵
|
|
273
|
+
const ear = (bx, by, tx, ty, ex, ey) => { head.moveTo(...T(bx, by)); head.lineTo(...T(tx, ty)); head.lineTo(...T(ex, ey)); head.closePath(); };
|
|
274
|
+
ear(555, 590, 560, 535, 595, 575); ear(615, 575, 652, 540, 650, 600);
|
|
275
|
+
P.head = head;
|
|
276
|
+
P.earInner = [[T(563, 585), T(565, 550), T(588, 578)], [T(622, 578), T(646, 553), T(644, 596)]];
|
|
277
|
+
// 白色部分:口鼻、胸口、爪子(橘白猫的标志)。胸口用平滑闭合曲线:旧版直线多边形在不描线的画法里像一块「盾牌」(迁移测试 B)
|
|
278
|
+
const mz = T(628, 650), chestPts = [[560, 700], [610, 690], [640, 740], [630, 820], [590, 860], [560, 800]].map(p => T(...p));
|
|
279
|
+
P.chest = Object.assign(R.smooth(chestPts), { pts: chestPts, closed: true });
|
|
280
|
+
P.muzzle = new Path2D(); P.muzzle.ellipse(mz[0], mz[1], 34 * s, 24 * s, 0, 0, Math.PI * 2);
|
|
281
|
+
P.paws = new Path2D(); const pawC = [585, 635].map(x => T(x, 893));
|
|
282
|
+
pawC.forEach(c0 => { P.paws.moveTo(c0[0] + 22 * s, c0[1]); P.paws.ellipse(c0[0], c0[1], 22 * s, 13 * s, 0, 0, Math.PI * 2); });
|
|
283
|
+
const white = new Path2D(); white.addPath(P.muzzle); white.addPath(P.chest); white.addPath(P.paws);
|
|
284
|
+
P.white = white;
|
|
285
|
+
P.whitePts = { chest: chestPts, muzzle: { x: mz[0], y: mz[1], rx: 34 * s, ry: 24 * s }, paws: pawC.map(c0 => ({ x: c0[0], y: c0[1], rx: 22 * s, ry: 13 * s })) };
|
|
286
|
+
// 前腿
|
|
287
|
+
P.legs = [R.limb(T(588, 740), T(584, 888), 34 * s, 28 * s), R.limb(T(630, 742), T(634, 888), 34 * s, 28 * s)];
|
|
288
|
+
// 眼睛(朝右看)、鼻、嘴、胡须
|
|
289
|
+
P.eyes = [{ x: T(612, 615)[0], y: T(612, 615)[1] }, { x: T(648, 612)[0], y: T(648, 612)[1] }]; P.eyeR = 9 * s; P.blink = blink;
|
|
290
|
+
P.nose = T(655, 640); P.mouth = [T(645, 655), T(655, 650), T(664, 655)];
|
|
291
|
+
P.whiskers = [[T(660, 645), T(712, 632)], [T(660, 650), T(715, 655)], [T(598, 648), T(552, 640)]];
|
|
292
|
+
// 虎斑条纹(头顶、背)
|
|
293
|
+
P.stripes = [[T(585, 572), T(590, 595)], [T(605, 568), T(607, 592)], [T(625, 572), T(622, 594)],
|
|
294
|
+
[T(452, 740), T(490, 750)], [T(440, 780), T(482, 790)], [T(436, 822), T(478, 830)], [T(500, 690), T(520, 715)]];
|
|
295
|
+
// 尾巴:从臀部绕到身前,末端随 tail 摆
|
|
296
|
+
const t0 = T(455, 885), tip = T(380 + Math.sin(tail) * 40, 830 - Math.cos(tail) * 30);
|
|
297
|
+
P.tailPts = P_bez(t0, T(360, 900), T(330, 860 + tail * 40), tip);
|
|
298
|
+
P.tail = (() => { const p = new Path2D(); const pts = P.tailPts; pts.forEach((q, i) => i ? p.lineTo(...q) : p.moveTo(...q)); return p; })();
|
|
299
|
+
P.tailW = 26 * s;
|
|
300
|
+
P.box = [T(390, 540), T(700, 905)];
|
|
301
|
+
P.pivots = { neck: T(588 + look * 6, 680), tailBase: t0, shoulder: T(600, 735), hip: T(470, 870) };
|
|
302
|
+
P.rigid = { head: ['head', 'earInner', 'eyes', 'nose', 'mouth', 'whiskers', 'muzzle'], body: ['body', 'chest', 'stripes'], legs: ['legs', 'paws'], tail: ['tail'] };
|
|
303
|
+
P.rigidPivot = { head: 'neck', body: 'hip', legs: 'shoulder', tail: 'tailBase' };
|
|
304
|
+
if (opt.sx || opt.sy) return R.stretch(P, opt.sx || 1, opt.sy || 1, opt.anchor || [540, 905]);
|
|
305
|
+
return P;
|
|
306
|
+
};
|
|
307
|
+
function P_bez(p0, p1, p2, p3, n = 20) { 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; }
|
|
308
|
+
|
|
309
|
+
// 猫只描线(给「先渲染器、后补线」的时代用)
|
|
310
|
+
R.drawCatLines = (c, K, pal) => {
|
|
311
|
+
const L = pal.line || '#1b1d3a', lw = pal.lw ?? 3; c.save(); c.lineJoin = 'round'; c.lineCap = 'round'; c.strokeStyle = L; c.lineWidth = lw;
|
|
312
|
+
c.stroke(K.body); c.stroke(K.head); K.legs.forEach(l => c.stroke(l));
|
|
313
|
+
K.eyes.forEach(e => { if (K.blink) { c.beginPath(); c.arc(e.x, e.y, K.eyeR, 0.2, Math.PI - 0.2); c.stroke(); } else { c.fillStyle = pal.eye || '#6aa84f'; c.beginPath(); c.arc(e.x, e.y, K.eyeR, 0, Math.PI * 2); c.fill(); c.stroke(); c.fillStyle = L; c.beginPath(); c.ellipse(e.x + 2, e.y, K.eyeR * 0.35, K.eyeR * 0.8, 0, 0, Math.PI * 2); c.fill(); } });
|
|
314
|
+
c.fillStyle = pal.noseC || '#d8746a'; c.beginPath(); c.arc(K.nose[0], K.nose[1], 6, 0, Math.PI * 2); c.fill();
|
|
315
|
+
c.lineWidth = 2.5; c.beginPath(); c.moveTo(...K.mouth[0]); c.quadraticCurveTo(...K.mouth[1], ...K.mouth[2]); c.stroke();
|
|
316
|
+
c.lineWidth = 1.5; K.whiskers.forEach(w => { c.beginPath(); c.moveTo(...w[0]); c.lineTo(...w[1]); c.stroke(); });
|
|
317
|
+
c.restore();
|
|
318
|
+
};
|
|
319
|
+
// 只取「看得见的线」:同一个完整画法(填色+描线,按部件顺序所以遮挡正确)画两遍,一遍用真线色、一遍用白色当哨兵;
|
|
320
|
+
// 两遍不同的像素 = 没被遮住的线(含用线色画的眼睑、眉、胡须、嘴)。取第一遍的颜色输出。
|
|
321
|
+
// 用于「平涂→风格肌理→补线」:直接 mode:'line' 叠最上层会把被遮住的轮廓(远侧袖子、帽檐后半圈)画穿。
|
|
322
|
+
// drawWith(g, lineColor) 由调用方提供,box=[x0,y0,x1,y1] 限定计算范围。
|
|
323
|
+
R._lc = {};
|
|
324
|
+
R.visibleLines = (key, drawWith, lineColor, box, thresh = 90) => {
|
|
325
|
+
const [x0, y0, x1, y1] = box.map(Math.round), w = x1 - x0, h = y1 - y0;
|
|
326
|
+
const mk = n => R._lc[key + n] || (R._lc[key + n] = (() => { const c = document.createElement('canvas'); c.width = window.STAGE.W; c.height = window.STAGE.H; return c; })());
|
|
327
|
+
const A = mk('a'), B = mk('b'), O = mk('o');
|
|
328
|
+
const ga = A.getContext('2d', { willReadFrequently: true }), gb = B.getContext('2d', { willReadFrequently: true }), go = O.getContext('2d');
|
|
329
|
+
ga.clearRect(0, 0, ga.canvas.width, ga.canvas.height); gb.clearRect(0, 0, gb.canvas.width, gb.canvas.height); go.clearRect(0, 0, go.canvas.width, go.canvas.height);
|
|
330
|
+
drawWith(ga, lineColor); drawWith(gb, '#ffffff');
|
|
331
|
+
const da = ga.getImageData(x0, y0, w, h), db = gb.getImageData(x0, y0, w, h).data, d = da.data;
|
|
332
|
+
for (let i = 0; i < d.length; i += 4) {
|
|
333
|
+
const diff = Math.abs(d[i] - db[i]) + Math.abs(d[i + 1] - db[i + 1]) + Math.abs(d[i + 2] - db[i + 2]);
|
|
334
|
+
if (diff < thresh) d[i + 3] = 0; else d[i + 3] = Math.min(255, d[i + 3] * Math.min(1, diff / 300));
|
|
335
|
+
}
|
|
336
|
+
go.putImageData(da, x0, y0); return O;
|
|
337
|
+
};
|
|
338
|
+
// 一站式默认画法见上方 R.drawGirl;猫:
|
|
339
|
+
// pal:orange white stripe stripeW eye earInner noseC(=nose) whisker line lw lwOf mode('line' 走 visibleLines) lineKey
|
|
340
|
+
// 分层:before/after/hooks(别名 back/body/head/end);paint(c, path, color, name, stroke) 接管部件填描;
|
|
341
|
+
// face(c, K, pal) 接管眼鼻嘴须;tail(c, K, pal) 接管尾巴;noStripes 不画条纹。
|
|
342
|
+
// 部件画序:tail body legs white head muzzleWhite earInner stripes eyes nose mouth whiskers
|
|
343
|
+
R.drawCat = (c, K, pal) => {
|
|
344
|
+
const L = pal.line || '#1b1d3a', lw = pal.lw ?? 3;
|
|
345
|
+
if (pal.mode === 'line') { const [a, b] = K.box, box = [a[0] - 60, a[1] - 40, b[0] + 70, b[1] + 30];
|
|
346
|
+
c.drawImage(R.visibleLines(pal.lineKey || 'cat', (g, lc) => R.drawCat(g, K, { ...pal, mode: 'both', line: lc, whisker: lc }), pal.line || '#1b1d3a', box), 0, 0); return; }
|
|
347
|
+
const step = hooker(c, K, pal, CAT_SLOTS), O = pal.orange || '#e8913a', Wt = pal.white || '#fff6ea';
|
|
348
|
+
const LWof = name => pal.lwOf && pal.lwOf[name] != null ? pal.lwOf[name] : lw;
|
|
349
|
+
const F = (name, p, col, stroke = true) => { if (pal.paint) { pal.paint(c, p, col, name, stroke); return; } c.fillStyle = col; c.fill(p); const w = LWof(name); if (w && stroke) { c.strokeStyle = L; c.lineWidth = w; c.stroke(p); } };
|
|
350
|
+
c.save(); c.lineJoin = 'round'; c.lineCap = 'round';
|
|
351
|
+
// 尾巴
|
|
352
|
+
step('tail', () => { if (pal.tail) { pal.tail(c, K, pal); return; }
|
|
353
|
+
c.strokeStyle = L; c.lineWidth = K.tailW + lw * 2; c.stroke(K.tail); c.strokeStyle = O; c.lineWidth = K.tailW; c.stroke(K.tail);
|
|
354
|
+
const tip = K.tailPts[K.tailPts.length - 1]; c.fillStyle = Wt; c.beginPath(); c.arc(tip[0], tip[1], K.tailW / 2, 0, Math.PI * 2); c.fill(); });
|
|
355
|
+
// 身体、腿、头
|
|
356
|
+
step('body', () => F('body', K.body, O));
|
|
357
|
+
step('legs', () => K.legs.forEach(l => F('leg', l, Wt)));
|
|
358
|
+
step('white', () => F('white', K.white, Wt, false));
|
|
359
|
+
step('head', () => F('head', K.head, O));
|
|
360
|
+
step('muzzleWhite', () => { c.save(); c.clip(K.head); c.fillStyle = Wt; c.beginPath(); c.ellipse(K.nose[0] - 22, K.nose[1] + 12, 36, 26, 0, 0, Math.PI * 2); c.fill(); c.restore(); });
|
|
361
|
+
step('earInner', () => { c.fillStyle = pal.earInner || '#f2a7a0'; K.earInner.forEach(e => { c.beginPath(); e.forEach((p, i) => i ? c.lineTo(...p) : c.moveTo(...p)); c.fill(); }); });
|
|
362
|
+
// 条纹
|
|
363
|
+
step('stripes', () => { if (pal.noStripes) return; c.strokeStyle = pal.stripe || '#c0621a'; c.lineWidth = pal.stripeW || 7; K.stripes.forEach(s => { c.beginPath(); c.moveTo(...s[0]); c.lineTo(...s[1]); c.stroke(); }); });
|
|
364
|
+
if (pal.face) step('face', () => pal.face(c, K, pal));
|
|
365
|
+
else {
|
|
366
|
+
// 眼
|
|
367
|
+
step('eyes', () => K.eyes.forEach(e => { if (K.blink) { c.strokeStyle = L; c.lineWidth = 3; c.beginPath(); c.arc(e.x, e.y, K.eyeR, 0.2, Math.PI - 0.2); c.stroke(); }
|
|
368
|
+
else { c.fillStyle = pal.eye || '#6aa84f'; c.beginPath(); c.arc(e.x, e.y, K.eyeR, 0, Math.PI * 2); c.fill(); c.fillStyle = L; c.beginPath(); c.ellipse(e.x + 2, e.y, K.eyeR * 0.35, K.eyeR * 0.8, 0, 0, Math.PI * 2); c.fill(); } }));
|
|
369
|
+
step('nose', () => { c.fillStyle = pal.noseC || pal.nose || '#d8746a'; c.beginPath(); c.arc(K.nose[0], K.nose[1], 6, 0, Math.PI * 2); c.fill(); });
|
|
370
|
+
step('mouth', () => { c.strokeStyle = L; c.lineWidth = 2.5; c.beginPath(); c.moveTo(...K.mouth[0]); c.quadraticCurveTo(...K.mouth[1], ...K.mouth[2]); c.stroke(); });
|
|
371
|
+
step('whiskers', () => { c.lineWidth = 1.5; c.strokeStyle = pal.whisker || L; K.whiskers.forEach(w => { c.beginPath(); c.moveTo(...w[0]); c.lineTo(...w[1]); c.stroke(); }); });
|
|
372
|
+
}
|
|
373
|
+
c.restore();
|
|
374
|
+
};
|
|
375
|
+
// 刚体姿态(皮影、剪纸偶、提线木偶):把 rigid 分组里的部件绕各自枢轴旋转。angles = {组名: 弧度},
|
|
376
|
+
// 返回新对象(Path2D 与点都转好),子组不自动跟随父组——需要链式时自己把父组的角度加到子组上(如 foreArm += upperArm)。
|
|
377
|
+
R.rigidPose = (O, angles) => {
|
|
378
|
+
const out = { ...O };
|
|
379
|
+
for (const grp in angles) {
|
|
380
|
+
const a = angles[grp], piv = O.pivots[O.rigidPivot[grp]]; if (!a || !piv) continue;
|
|
381
|
+
const m = new DOMMatrix().translate(piv[0], piv[1]).rotate(a * 180 / Math.PI).translate(-piv[0], -piv[1]);
|
|
382
|
+
const mp = q => { const r = m.transformPoint(new DOMPoint(q[0], q[1])); return [r.x, r.y]; };
|
|
383
|
+
const map = v => { if (v instanceof Path2D) { const p = new Path2D(); p.addPath(v, m); if (v.pts) { p.pts = v.pts.map(mp); p.closed = v.closed; } return p; }
|
|
384
|
+
if (Array.isArray(v) && v.length === 2 && typeof v[0] === 'number') return mp(v); if (Array.isArray(v)) return v.map(map);
|
|
385
|
+
if (v && typeof v === 'object' && 'x' in v) { const q = mp([v.x, v.y]); return { ...v, x: q[0], y: q[1] }; } return v; };
|
|
386
|
+
O.rigid[grp].forEach(name => { if (out[name] != null) out[name] = map(out[name]); });
|
|
387
|
+
}
|
|
388
|
+
return out;
|
|
389
|
+
};
|
|
390
|
+
})();
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
// 花叔卡通形象骨架(侧面,FK 关节)。来源:花叔形象的 superflat 三视图设定稿(未随仓库附带),侧视图叠网格读点。
|
|
2
|
+
// 角色定规:纯白渔夫帽、圆框黑眼镜、白 T、卡其短裤、白洞洞鞋、近侧手腕黑手表、大头小身体(头≈全身 38%)。
|
|
3
|
+
// 坐标:本地单位 1 = 设定图侧视 1px;原点 = 双脚之间的地面;y 向上为负;原生朝左(-x 为前)。全身高 740 单位。
|
|
4
|
+
// 用法:
|
|
5
|
+
// const H = HUASHU.build({ x, y, s: 0.6, face: 1 /*1朝右 -1朝左*/, pose: HUASHU.pose('walk', phase) });
|
|
6
|
+
// HUASHU.draw(c, H, pal) // 默认扁平画法;pal.mode: 'fill' | 'line' | 'both'
|
|
7
|
+
// H.parts = [{ name, role, path, z }] // 按画序;role ∈ skin/shirt/shorts/hat/hair/shoe/watch/glass/line
|
|
8
|
+
// H.J = { hip, knee1, ankle1, toe1, knee2, ..., shoulder1, elbow1, wrist1, ..., neck, head, hatTop, mouth, eye }
|
|
9
|
+
// 角度约定:0 = 自然下垂;正 = 往脸朝的方向(前)转;大头小身体手够不到头顶:举手=近侧手臂向前上(~1.9)、远侧向后上(~-1.9),侧视里成 V 字,不要直上。1 = 近侧肢体(画在最前),2 = 远侧。
|
|
10
|
+
(() => {
|
|
11
|
+
const HS = window.HUASHU = {};
|
|
12
|
+
const { lerp, clamp } = U;
|
|
13
|
+
const D = (a, b, c, d, e, f) => new DOMMatrix([a, b, c, d, e, f]);
|
|
14
|
+
const P2 = (pts, closed = true) => (RIG && RIG.smooth) ? RIG.smooth(pts, closed) : (() => { const p = new Path2D(); pts.forEach((q, i) => i ? p.lineTo(...q) : p.moveTo(...q)); if (closed) p.closePath(); return p; })();
|
|
15
|
+
const add = (dst, path, m) => { dst.addPath(path, m); return dst; };
|
|
16
|
+
const tf = (m, x, y) => { const p = m.transformPoint(new DOMPoint(x, y)); return [p.x, p.y]; };
|
|
17
|
+
|
|
18
|
+
// ---------- 局部形状(各自骨骼坐标系,原点 = 该骨骼起点关节,沿 +y 向下) ----------
|
|
19
|
+
const SHAPE = {
|
|
20
|
+
// 头:原点 = 颈点;脸朝 -x
|
|
21
|
+
face: P2([[-30, -158], [-78, -150], [-108, -128], [-122, -98], [-124, -66], [-118, -38], [-102, -14], [-80, 2], [-50, 8], [-18, 2], [8, -14], [22, -40], [24, -78], [10, -120]]),
|
|
22
|
+
hairBack: P2([[-70, -168], [-10, -176], [56, -168], [102, -140], [118, -96], [112, -58], [86, -30], [52, -18], [26, -28], [20, -62], [8, -110], [-30, -140], [-62, -140]]),
|
|
23
|
+
bangs: P2([[-122, -120], [-110, -146], [-76, -164], [-34, -166], [-10, -150], [-28, -138], [-52, -132], [-70, -118], [-86, -132], [-102, -116]]),
|
|
24
|
+
ear: (() => { const p = new Path2D(); p.ellipse(16, -66, 17, 22, 0.15, 0, Math.PI * 2); return p; })(),
|
|
25
|
+
crown: P2([[-100, -170], [-94, -232], [-64, -262], [-4, -274], [62, -266], [100, -236], [116, -180], [124, -110], [64, -118], [-10, -150], [-60, -166]]),
|
|
26
|
+
brim: P2([[-160, -166], [-112, -186], [-30, -190], [52, -164], [116, -112], [134, -62], [116, -52], [64, -86], [-16, -128], [-100, -150], [-156, -150]]),
|
|
27
|
+
band: P2([[-100, -186], [-30, -196], [60, -176], [118, -128], [120, -112], [60, -158], [-30, -180], [-100, -172]]),
|
|
28
|
+
lens: (() => { const p = new Path2D(); p.roundRect(-128, -118, 44, 62, 16); return p; })(),
|
|
29
|
+
// 躯干(T 恤):原点 = 胯;肩约 (0,-250)
|
|
30
|
+
shirt: P2([[-18, -272], [-40, -258], [-60, -200], [-72, -120], [-82, -42], [-84, -32], [74, -34], [72, -40], [70, -140], [62, -230], [44, -262], [14, -274]]),
|
|
31
|
+
collar: P2([[-18, -272], [0, -262], [14, -274]], false),
|
|
32
|
+
shorts: P2([[-74, -44], [76, -42], [80, 18], [70, 44], [-70, 46], [-78, 16]]),
|
|
33
|
+
// 大腿裤管:原点 = 胯侧关节;长 62
|
|
34
|
+
thigh: P2([[-40, -6], [38, -6], [42, 58], [-40, 62]]),
|
|
35
|
+
// 小腿:原点 = 膝;长 66
|
|
36
|
+
shin: P2([[-24, 0], [22, 0], [21, 66], [-21, 66]]),
|
|
37
|
+
// 洞洞鞋:原点 = 踝;鞋尖朝 -x
|
|
38
|
+
shoe: P2([[-104, 26], [-100, 6], [-80, -4], [-50, -8], [-20, -12], [18, -10], [46, -2], [58, 18], [56, 46], [40, 60], [-60, 62], [-96, 54], [-106, 40]]),
|
|
39
|
+
// 袖子:原点 = 肩;长 112
|
|
40
|
+
sleeve: P2([[-40, -10], [36, -14], [44, 100], [-34, 112]]),
|
|
41
|
+
upper: P2([[-19, 0], [19, 0], [18, 126], [-18, 126]]),
|
|
42
|
+
fore: P2([[-18, 0], [18, 0], [16, 74], [-16, 74]]),
|
|
43
|
+
hand: (() => { const p = new Path2D(); p.ellipse(0, 18, 22, 25, 0, 0, Math.PI * 2); return p; })(),
|
|
44
|
+
watch: (() => { const p = new Path2D(); p.roundRect(-24, 50, 48, 22, 6); return p; })(),
|
|
45
|
+
watchFace: (() => { const p = new Path2D(); p.roundRect(-16, 46, 32, 30, 7); return p; })(),
|
|
46
|
+
};
|
|
47
|
+
const SHOE_HOLES = [[-80, 18], [-58, 12], [-34, 10], [-10, 12], [-70, 32], [-46, 30], [-22, 30]];
|
|
48
|
+
|
|
49
|
+
// ---------- 姿势库 ----------
|
|
50
|
+
// 每个姿势返回关节角(弧度)与根偏移。phase 用于循环动作(0..1)。
|
|
51
|
+
HS.pose = (name, ph = 0, k = {}) => {
|
|
52
|
+
const s = Math.sin(2 * Math.PI * ph), c = Math.cos(2 * Math.PI * ph);
|
|
53
|
+
const P = { lean: 0, head: 0, bob: 0, dx: 0, th1: 0, sh1: 0, ft1: 0, th2: 0, sh2: 0, ft2: 0, ua1: 0, fa1: 0, ua2: 0, fa2: 0, pocket1: 0, pocket2: 0, mouth: 0.3, blink: 0 };
|
|
54
|
+
switch (name) {
|
|
55
|
+
case 'idle': Object.assign(P, { pocket1: 1, pocket2: 1, ua1: -0.05, fa1: 0.25, ua2: -0.05, fa2: 0.25, bob: 2 * s, head: 0.03 * s }); break;
|
|
56
|
+
case 'walk': Object.assign(P, { lean: 0.05, bob: -6 * Math.abs(c), th1: 0.42 * s, sh1: -0.55 * Math.max(0, -s) - 0.15, ft1: 0.1 * s, th2: -0.42 * s, sh2: -0.55 * Math.max(0, s) - 0.15, ft2: -0.1 * s,
|
|
57
|
+
ua1: -0.45 * s, fa1: 0.35, ua2: 0.45 * s, fa2: 0.35, head: 0.03 * c }); break;
|
|
58
|
+
case 'run': Object.assign(P, { lean: 0.22, bob: -14 * Math.abs(c), th1: 0.8 * s + 0.1, sh1: -1.1 * Math.max(0, -s) - 0.3, th2: -0.8 * s + 0.1, sh2: -1.1 * Math.max(0, s) - 0.3,
|
|
59
|
+
ua1: -0.9 * s, fa1: 1.4, ua2: 0.9 * s, fa2: 1.4, head: -0.06, mouth: 0.6 }); break;
|
|
60
|
+
case 'crouch': Object.assign(P, { lean: 0.35, bob: 52, th1: 1.25, sh1: -1.9, ft1: 0.5, th2: 1.05, sh2: -1.8, ft2: 0.6, ua1: 0.5, fa1: 0.4, ua2: 0.3, fa2: 0.5, head: -0.2 }); break;
|
|
61
|
+
case 'jump': Object.assign(P, { lean: 0.05, th1: 0.7, sh1: -1.2, th2: 0.2, sh2: -0.6, ua1: 1.95, fa1: 0.7, ua2: -1.95, fa2: -0.7, head: -0.15, mouth: 1 }); break;
|
|
62
|
+
case 'wave': Object.assign(P, { pocket2: 1, ua2: -0.05, fa2: 0.25, ua1: 2.15, fa1: 0.15 + 0.5 * (0.5 + 0.5 * s), head: 0.08, mouth: 0.8, bob: 2 * s }); break;
|
|
63
|
+
case 'sit': Object.assign(P, { bob: 62, th1: 1.55, sh1: -1.55, th2: 1.5, sh2: -1.5, ua1: 0.5, fa1: 0.9, ua2: 0.4, fa2: 0.9, lean: -0.05 }); break;
|
|
64
|
+
case 'point': Object.assign(P, { pocket2: 1, ua2: -0.05, fa2: 0.25, ua1: 1.45, fa1: 0.05, head: -0.05, mouth: 0.7 }); break;
|
|
65
|
+
case 'think': Object.assign(P, { pocket2: 1, ua2: -0.05, fa2: 0.25, ua1: 0.6, fa1: 2.3, head: -0.1, mouth: 0.1 }); break;
|
|
66
|
+
case 'cheer': Object.assign(P, { ua1: 2.05, fa1: 0.55, ua2: -2.05, fa2: -0.55, bob: -10 * Math.abs(s), head: -0.12, mouth: 1 }); break;
|
|
67
|
+
// 侧身甩臂扔(打水漂):ph 0→1 = 蓄力 → 出手 → 随挥
|
|
68
|
+
case 'throw': { const w = ph < 0.45 ? ph / 0.45 : 1, r = ph < 0.45 ? 0 : clamp((ph - 0.45) / 0.25), f = clamp((ph - 0.7) / 0.3);
|
|
69
|
+
Object.assign(P, { lean: lerp(lerp(0, -0.12, w), 0.32, r), bob: lerp(0, 34, w) - 10 * r, th1: lerp(0, 0.65, w) - 0.3 * r, sh1: lerp(0, -0.9, w) + 0.4 * r, th2: lerp(0, -0.35, w) + 0.25 * r, sh2: lerp(-0.1, -0.5, w),
|
|
70
|
+
ua1: lerp(lerp(0, -1.3, w), 1.1, r) + 0.3 * f, fa1: lerp(lerp(0.3, 0.9, w), 0.15, r), ua2: lerp(0, 0.9, w) - 0.5 * r, fa2: 0.6, head: lerp(0, -0.05, w), mouth: 0.5 + 0.5 * r }); break; }
|
|
71
|
+
}
|
|
72
|
+
return Object.assign(P, k);
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
// ---------- 组装 ----------
|
|
76
|
+
// o: { x, y, s, face, pose, squash } squash = 竖向挤压(落地 0.85、起跳 1.1)
|
|
77
|
+
HS.build = (o = {}) => {
|
|
78
|
+
const { x = 960, y = 900, s = 0.6, face = 1, pose = HS.pose('idle'), squash = 1, rot = 0 } = o;
|
|
79
|
+
const P = pose;
|
|
80
|
+
// 根:先挪到世界坐标,按朝向镜像(原生朝左,face=1 朝右要翻),挤压以脚底为轴
|
|
81
|
+
const root = new DOMMatrix().translate(x, y).rotate(rot * 180 / Math.PI).scale(-face * s, s * squash);
|
|
82
|
+
const hip = root.translate(P.dx, -185 + P.bob).rotate(-P.lean * 180 / Math.PI); // lean>0 往前倾;本地前=-x,rotate 负角让上身朝 -x 倒
|
|
83
|
+
const deg = a => a * 180 / Math.PI;
|
|
84
|
+
const parts = [], J = {};
|
|
85
|
+
const push = (name, role, shape, m, z) => parts.push({ name, role, path: add(new Path2D(), shape, m), z, m });
|
|
86
|
+
const leg = (i, th, sh, ft, zBase) => {
|
|
87
|
+
const hipJ = hip.translate(i === 1 ? -8 : 10, 0);
|
|
88
|
+
const mT = hipJ.rotate(deg(th)); // 大腿往前(-x):正角 rotate → (0,1) 转向 -x ✓
|
|
89
|
+
const knee = mT.translate(0, 60), mS = knee.rotate(deg(sh)), ankle = mS.translate(0, 64), mF = ankle.rotate(deg(ft));
|
|
90
|
+
push('thigh' + i, 'shorts', SHAPE.thigh, mT, zBase + 2);
|
|
91
|
+
push('shin' + i, 'skin', SHAPE.shin, mS, zBase);
|
|
92
|
+
push('shoe' + i, 'shoe', SHAPE.shoe, mF, zBase + 1);
|
|
93
|
+
J['hip' + i] = tf(hipJ, 0, 0); J['knee' + i] = tf(knee, 0, 0); J['ankle' + i] = tf(ankle, 0, 0); J['toe' + i] = tf(mF, -104, 40); J['heel' + i] = tf(mF, 56, 46);
|
|
94
|
+
J['shoeM' + i] = mF;
|
|
95
|
+
};
|
|
96
|
+
const arm = (i, ua, fa, pocket, zBase) => {
|
|
97
|
+
const sh = hip.translate(i === 1 ? -4 : 14, -250);
|
|
98
|
+
const mU = sh.rotate(deg(ua)), elbow = mU.translate(0, 124), mFo = elbow.rotate(deg(fa)), wrist = mFo.translate(0, 70);
|
|
99
|
+
push('sleeve' + i, 'shirt', SHAPE.sleeve, mU, zBase + 3);
|
|
100
|
+
push('upper' + i, 'skin', SHAPE.upper, mU, zBase);
|
|
101
|
+
if (!pocket) { push('fore' + i, 'skin', SHAPE.fore, mFo, zBase + 1); push('hand' + i, 'skin', SHAPE.hand, wrist, zBase + 2); }
|
|
102
|
+
else push('fore' + i, 'skin', SHAPE.fore, mFo, zBase - 30); // 插兜:前臂藏到裤子后面
|
|
103
|
+
if (i === 1) { push('watch', 'watch', SHAPE.watch, mFo, zBase + (pocket ? -29 : 2)); push('watchFace', 'watch', SHAPE.watchFace, mFo, zBase + (pocket ? -28 : 3)); }
|
|
104
|
+
J['shoulder' + i] = tf(sh, 0, 0); J['elbow' + i] = tf(elbow, 0, 0); J['wrist' + i] = tf(wrist, 0, 0); J['hand' + i] = tf(wrist, 0, 18); J['handM' + i] = wrist;
|
|
105
|
+
};
|
|
106
|
+
// 远侧在后
|
|
107
|
+
leg(2, P.th2, P.sh2, P.ft2, 0);
|
|
108
|
+
arm(2, P.ua2, P.fa2, P.pocket2, 10);
|
|
109
|
+
push('shorts', 'shorts', SHAPE.shorts, hip, 40);
|
|
110
|
+
push('shirt', 'shirt', SHAPE.shirt, hip, 50);
|
|
111
|
+
leg(1, P.th1, P.sh1, P.ft1, 60);
|
|
112
|
+
push('shortsFront', 'shorts', SHAPE.shorts, hip, 64);
|
|
113
|
+
push('shirtFront', 'shirt', SHAPE.shirt, hip, 66);
|
|
114
|
+
// 头
|
|
115
|
+
const neck = hip.translate(6, -272), mH = neck.rotate(deg(-P.head));
|
|
116
|
+
push('hairBack', 'hair', SHAPE.hairBack, mH, 80);
|
|
117
|
+
push('face', 'skin', SHAPE.face, mH, 81);
|
|
118
|
+
push('ear', 'skin', SHAPE.ear, mH, 82);
|
|
119
|
+
push('bangs', 'hair', SHAPE.bangs, mH, 83);
|
|
120
|
+
push('crown', 'hat', SHAPE.crown, mH, 85);
|
|
121
|
+
push('band', 'hatBand', SHAPE.band, mH, 86);
|
|
122
|
+
push('brim', 'hat', SHAPE.brim, mH, 87);
|
|
123
|
+
arm(1, P.ua1, P.fa1, P.pocket1, 70);
|
|
124
|
+
parts.sort((a, b) => a.z - b.z);
|
|
125
|
+
J.hip = tf(hip, 0, 0); J.neck = tf(neck, 0, 0); J.head = tf(mH, -40, -80); J.hatTop = tf(mH, 0, -275); J.mouth = tf(mH, -104, -34); J.eye = tf(mH, -104, -88);
|
|
126
|
+
J.chest = tf(hip, -40, -180); J.face = face; J.s = s;
|
|
127
|
+
return { parts, J, mH, root, hip, P, s, face };
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
// 五官与细节线(在头部坐标里画)
|
|
131
|
+
HS.details = (c, H, pal) => {
|
|
132
|
+
const L = pal.line || '#151515', lw = (pal.lw ?? 5);
|
|
133
|
+
c.save(); c.setTransform(c.getTransform().multiply(H.mH)); c.lineCap = 'round'; c.lineJoin = 'round';
|
|
134
|
+
// 眼镜:前镜片+镜腿;镜片里一只黑眼睛
|
|
135
|
+
c.fillStyle = pal.lensFill || 'rgba(255,255,255,0.0)'; c.fill(SHAPE.lens);
|
|
136
|
+
if (!H.P.blink) { c.fillStyle = pal.eye || L; c.beginPath(); c.ellipse(-104, -86, 9, 13, 0, 0, Math.PI * 2); c.fill(); c.fillStyle = '#fff'; c.beginPath(); c.arc(-107, -91, 3, 0, Math.PI * 2); c.fill(); }
|
|
137
|
+
else { c.strokeStyle = pal.eye || L; c.lineWidth = lw * 0.8; c.beginPath(); c.arc(-104, -90, 9, 0.2, Math.PI - 0.2); c.stroke(); }
|
|
138
|
+
c.strokeStyle = pal.glass || L; c.lineWidth = lw * 1.6; c.stroke(SHAPE.lens);
|
|
139
|
+
c.lineWidth = lw * 1.3; c.beginPath(); c.moveTo(-84, -96); c.lineTo(8, -84); c.stroke();
|
|
140
|
+
// 嘴:mouth 0 闭嘴微笑 → 1 张嘴
|
|
141
|
+
const m = H.P.mouth;
|
|
142
|
+
c.strokeStyle = L; c.lineWidth = lw * 0.8;
|
|
143
|
+
if (m < 0.5) { c.beginPath(); c.moveTo(-112, -40); c.quadraticCurveTo(-100, -28 - 8 * m, -88, -38); c.stroke(); }
|
|
144
|
+
else { c.fillStyle = pal.mouthFill || '#7a2e2e'; c.beginPath(); c.ellipse(-102, -34, 9, 6 + 6 * m, 0, 0, Math.PI * 2); c.fill(); c.stroke(); }
|
|
145
|
+
// 腮红、耳廓
|
|
146
|
+
if (pal.cheek) { c.fillStyle = pal.cheek; c.beginPath(); c.ellipse(-70, -48, 14, 9, 0, 0, Math.PI * 2); c.fill(); }
|
|
147
|
+
c.strokeStyle = pal.earLine || 'rgba(190,110,90,.8)'; c.lineWidth = lw * 0.6; c.beginPath(); c.arc(16, -66, 8, -1.2, 1.6); c.stroke();
|
|
148
|
+
c.restore();
|
|
149
|
+
// 鞋洞
|
|
150
|
+
for (const i of [1, 2]) { const m2 = H.J['shoeM' + i]; c.save(); c.setTransform(c.getTransform().multiply(m2)); c.fillStyle = pal.shoeHole || '#2a2a2a'; SHOE_HOLES.forEach(([x, y]) => { c.beginPath(); c.ellipse(x, y, 4.5, 3.5, 0, 0, Math.PI * 2); c.fill(); }); c.strokeStyle = pal.shoeHole || '#2a2a2a'; c.lineWidth = 3; c.beginPath(); c.arc(40, 24, 9, 0, Math.PI * 2); c.stroke(); c.restore(); }
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
HS.PAL = { skin: '#fbd9bf', hair: '#151515', shirt: '#ffffff', shorts: '#d8cdb8', hat: '#ffffff', hatBand: '#e9e9e9', shoe: '#fbfaf6', watch: '#1d1f22', line: '#151515', lw: 5, cheek: 'rgba(245,140,120,.35)' };
|
|
154
|
+
|
|
155
|
+
// 默认画法:逐部件「填→描」,天然只剩看得见的线(遮挡正确)
|
|
156
|
+
HS.draw = (c, H, pal = HS.PAL) => {
|
|
157
|
+
pal = { ...HS.PAL, ...pal };
|
|
158
|
+
const mode = pal.mode || 'both', L = pal.line, lw = pal.lw / H.s * H.s; // 线宽按世界像素
|
|
159
|
+
c.save(); c.lineJoin = 'round'; c.lineCap = 'round';
|
|
160
|
+
for (const p of H.parts) {
|
|
161
|
+
const col = pal[p.role] ?? pal.skin;
|
|
162
|
+
if (mode !== 'line') { c.fillStyle = col; c.fill(p.path); }
|
|
163
|
+
if (mode !== 'fill' && lw) {
|
|
164
|
+
if (mode === 'line') { c.save(); c.globalCompositeOperation = 'destination-out'; c.fill(p.path); c.restore(); } // 线稿模式:后画的部件先挖掉前面的线
|
|
165
|
+
c.strokeStyle = L; c.lineWidth = lw; c.stroke(p.path);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
HS.details(c, H, pal);
|
|
169
|
+
c.restore();
|
|
170
|
+
};
|
|
171
|
+
})();
|