gamesteel 0.2.0 → 0.3.1
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/README.md +6 -5
- package/dist/gamesteel.js +123159 -1612
- package/dist/pkgs/art/catalogue.json +153 -0
- package/dist/pkgs/art/library/new-platformer/License.txt +28 -0
- package/dist/pkgs/art/library/new-platformer/bee.png +0 -0
- package/dist/pkgs/art/library/new-platformer/block-bang.png +0 -0
- package/dist/pkgs/art/library/new-platformer/block-coin.png +0 -0
- package/dist/pkgs/art/library/new-platformer/block.png +0 -0
- package/dist/pkgs/art/library/new-platformer/bomb.png +0 -0
- package/dist/pkgs/art/library/new-platformer/bush.png +0 -0
- package/dist/pkgs/art/library/new-platformer/cactus.png +0 -0
- package/dist/pkgs/art/library/new-platformer/clouds.png +0 -0
- package/dist/pkgs/art/library/new-platformer/coin-bronze.png +0 -0
- package/dist/pkgs/art/library/new-platformer/coin-silver.png +0 -0
- package/dist/pkgs/art/library/new-platformer/coin.png +0 -0
- package/dist/pkgs/art/library/new-platformer/crate.png +0 -0
- package/dist/pkgs/art/library/new-platformer/desert.png +0 -0
- package/dist/pkgs/art/library/new-platformer/dirt.png +0 -0
- package/dist/pkgs/art/library/new-platformer/fireball.png +0 -0
- package/dist/pkgs/art/library/new-platformer/fish.png +0 -0
- package/dist/pkgs/art/library/new-platformer/flag.png +0 -0
- package/dist/pkgs/art/library/new-platformer/fly.png +0 -0
- package/dist/pkgs/art/library/new-platformer/frog.png +0 -0
- package/dist/pkgs/art/library/new-platformer/gem-blue.png +0 -0
- package/dist/pkgs/art/library/new-platformer/gem-green.png +0 -0
- package/dist/pkgs/art/library/new-platformer/gem-red.png +0 -0
- package/dist/pkgs/art/library/new-platformer/gem-yellow.png +0 -0
- package/dist/pkgs/art/library/new-platformer/grass.png +0 -0
- package/dist/pkgs/art/library/new-platformer/heart.png +0 -0
- package/dist/pkgs/art/library/new-platformer/hero-beige.png +0 -0
- package/dist/pkgs/art/library/new-platformer/hero-green.png +0 -0
- package/dist/pkgs/art/library/new-platformer/hero-pink.png +0 -0
- package/dist/pkgs/art/library/new-platformer/hero-purple.png +0 -0
- package/dist/pkgs/art/library/new-platformer/hero-yellow.png +0 -0
- package/dist/pkgs/art/library/new-platformer/hills.png +0 -0
- package/dist/pkgs/art/library/new-platformer/key-blue.png +0 -0
- package/dist/pkgs/art/library/new-platformer/key-yellow.png +0 -0
- package/dist/pkgs/art/library/new-platformer/ladder.png +0 -0
- package/dist/pkgs/art/library/new-platformer/ladybug.png +0 -0
- package/dist/pkgs/art/library/new-platformer/lever.png +0 -0
- package/dist/pkgs/art/library/new-platformer/lock-blue.png +0 -0
- package/dist/pkgs/art/library/new-platformer/mouse.png +0 -0
- package/dist/pkgs/art/library/new-platformer/mushroom.png +0 -0
- package/dist/pkgs/art/library/new-platformer/mushrooms.png +0 -0
- package/dist/pkgs/art/library/new-platformer/purple.png +0 -0
- package/dist/pkgs/art/library/new-platformer/rock.png +0 -0
- package/dist/pkgs/art/library/new-platformer/sand.png +0 -0
- package/dist/pkgs/art/library/new-platformer/saw.png +0 -0
- package/dist/pkgs/art/library/new-platformer/sign-exit.png +0 -0
- package/dist/pkgs/art/library/new-platformer/sign-left.png +0 -0
- package/dist/pkgs/art/library/new-platformer/sign-right.png +0 -0
- package/dist/pkgs/art/library/new-platformer/sky.png +0 -0
- package/dist/pkgs/art/library/new-platformer/slime-fire.png +0 -0
- package/dist/pkgs/art/library/new-platformer/slime-spike.png +0 -0
- package/dist/pkgs/art/library/new-platformer/slime.png +0 -0
- package/dist/pkgs/art/library/new-platformer/snail.png +0 -0
- package/dist/pkgs/art/library/new-platformer/snow.png +0 -0
- package/dist/pkgs/art/library/new-platformer/spikes.png +0 -0
- package/dist/pkgs/art/library/new-platformer/spring.png +0 -0
- package/dist/pkgs/art/library/new-platformer/star.png +0 -0
- package/dist/pkgs/art/library/new-platformer/stone.png +0 -0
- package/dist/pkgs/art/library/new-platformer/torch.png +0 -0
- package/dist/pkgs/art/library/new-platformer/trees.png +0 -0
- package/dist/pkgs/art/library/new-platformer/worm.png +0 -0
- package/dist/pkgs/art/library/pixel-platformer/License.txt +28 -0
- package/dist/pkgs/art/library/pixel-platformer/angry-block.png +0 -0
- package/dist/pkgs/art/library/pixel-platformer/astronaut-beige.png +0 -0
- package/dist/pkgs/art/library/pixel-platformer/astronaut-blue.png +0 -0
- package/dist/pkgs/art/library/pixel-platformer/astronaut-green.png +0 -0
- package/dist/pkgs/art/library/pixel-platformer/astronaut-pink.png +0 -0
- package/dist/pkgs/art/library/pixel-platformer/astronaut-yellow.png +0 -0
- package/dist/pkgs/art/library/pixel-platformer/bat.png +0 -0
- package/dist/pkgs/art/library/pixel-platformer/block-bang.png +0 -0
- package/dist/pkgs/art/library/pixel-platformer/block-coin.png +0 -0
- package/dist/pkgs/art/library/pixel-platformer/block-yellow.png +0 -0
- package/dist/pkgs/art/library/pixel-platformer/cactus.png +0 -0
- package/dist/pkgs/art/library/pixel-platformer/coin.png +0 -0
- package/dist/pkgs/art/library/pixel-platformer/crate.png +0 -0
- package/dist/pkgs/art/library/pixel-platformer/crawler-bot.png +0 -0
- package/dist/pkgs/art/library/pixel-platformer/desert.png +0 -0
- package/dist/pkgs/art/library/pixel-platformer/drill-bot.png +0 -0
- package/dist/pkgs/art/library/pixel-platformer/flag.png +0 -0
- package/dist/pkgs/art/library/pixel-platformer/forest.png +0 -0
- package/dist/pkgs/art/library/pixel-platformer/gem.png +0 -0
- package/dist/pkgs/art/library/pixel-platformer/gold-imp.png +0 -0
- package/dist/pkgs/art/library/pixel-platformer/grass.png +0 -0
- package/dist/pkgs/art/library/pixel-platformer/heart-empty.png +0 -0
- package/dist/pkgs/art/library/pixel-platformer/heart-half.png +0 -0
- package/dist/pkgs/art/library/pixel-platformer/heart.png +0 -0
- package/dist/pkgs/art/library/pixel-platformer/key.png +0 -0
- package/dist/pkgs/art/library/pixel-platformer/ladder.png +0 -0
- package/dist/pkgs/art/library/pixel-platformer/lever.png +0 -0
- package/dist/pkgs/art/library/pixel-platformer/mushroom.png +0 -0
- package/dist/pkgs/art/library/pixel-platformer/sand.png +0 -0
- package/dist/pkgs/art/library/pixel-platformer/sign-left.png +0 -0
- package/dist/pkgs/art/library/pixel-platformer/sign-right.png +0 -0
- package/dist/pkgs/art/library/pixel-platformer/sky.png +0 -0
- package/dist/pkgs/art/library/pixel-platformer/snow.png +0 -0
- package/dist/pkgs/art/library/pixel-platformer/snowman.png +0 -0
- package/dist/pkgs/art/library/pixel-platformer/spikes.png +0 -0
- package/dist/pkgs/art/library/pixel-platformer/spiky-ball.png +0 -0
- package/dist/pkgs/art/library/pixel-platformer/spring.png +0 -0
- package/dist/pkgs/art/library/pixel-platformer/sprout.png +0 -0
- package/dist/pkgs/art/library/pixel-platformer/tank-bot.png +0 -0
- package/dist/pkgs/art/library/pixel-platformer/tree.png +0 -0
- package/dist/pkgs/art/library/puzzle/License.txt +21 -0
- package/dist/pkgs/art/library/puzzle/ball-blue.png +0 -0
- package/dist/pkgs/art/library/puzzle/ball-grey.png +0 -0
- package/dist/pkgs/art/library/puzzle/brick-blue.png +0 -0
- package/dist/pkgs/art/library/puzzle/brick-green.png +0 -0
- package/dist/pkgs/art/library/puzzle/brick-grey.png +0 -0
- package/dist/pkgs/art/library/puzzle/brick-purple.png +0 -0
- package/dist/pkgs/art/library/puzzle/brick-red.png +0 -0
- package/dist/pkgs/art/library/puzzle/brick-yellow.png +0 -0
- package/dist/pkgs/art/library/puzzle/diamond-blue.png +0 -0
- package/dist/pkgs/art/library/puzzle/diamond-green.png +0 -0
- package/dist/pkgs/art/library/puzzle/diamond-grey.png +0 -0
- package/dist/pkgs/art/library/puzzle/diamond-purple.png +0 -0
- package/dist/pkgs/art/library/puzzle/diamond-red.png +0 -0
- package/dist/pkgs/art/library/puzzle/diamond-yellow.png +0 -0
- package/dist/pkgs/art/library/puzzle/paddle-blue.png +0 -0
- package/dist/pkgs/art/library/puzzle/paddle-red.png +0 -0
- package/dist/pkgs/art/library/puzzle/star.png +0 -0
- package/dist/pkgs/art/library/tiny-dungeon/License.txt +22 -0
- package/dist/pkgs/art/library/tiny-dungeon/adventurer.png +0 -0
- package/dist/pkgs/art/library/tiny-dungeon/axe.png +0 -0
- package/dist/pkgs/art/library/tiny-dungeon/barrel.png +0 -0
- package/dist/pkgs/art/library/tiny-dungeon/bat.png +0 -0
- package/dist/pkgs/art/library/tiny-dungeon/chest-open.png +0 -0
- package/dist/pkgs/art/library/tiny-dungeon/chest.png +0 -0
- package/dist/pkgs/art/library/tiny-dungeon/cyclops.png +0 -0
- package/dist/pkgs/art/library/tiny-dungeon/dagger.png +0 -0
- package/dist/pkgs/art/library/tiny-dungeon/dark-wizard.png +0 -0
- package/dist/pkgs/art/library/tiny-dungeon/demon.png +0 -0
- package/dist/pkgs/art/library/tiny-dungeon/elder.png +0 -0
- package/dist/pkgs/art/library/tiny-dungeon/ghost.png +0 -0
- package/dist/pkgs/art/library/tiny-dungeon/guard.png +0 -0
- package/dist/pkgs/art/library/tiny-dungeon/hammer.png +0 -0
- package/dist/pkgs/art/library/tiny-dungeon/knight.png +0 -0
- package/dist/pkgs/art/library/tiny-dungeon/mimic.png +0 -0
- package/dist/pkgs/art/library/tiny-dungeon/monk.png +0 -0
- package/dist/pkgs/art/library/tiny-dungeon/orc.png +0 -0
- package/dist/pkgs/art/library/tiny-dungeon/potion-blue.png +0 -0
- package/dist/pkgs/art/library/tiny-dungeon/potion-green.png +0 -0
- package/dist/pkgs/art/library/tiny-dungeon/potion-red.png +0 -0
- package/dist/pkgs/art/library/tiny-dungeon/princess.png +0 -0
- package/dist/pkgs/art/library/tiny-dungeon/shield.png +0 -0
- package/dist/pkgs/art/library/tiny-dungeon/slime.png +0 -0
- package/dist/pkgs/art/library/tiny-dungeon/snake.png +0 -0
- package/dist/pkgs/art/library/tiny-dungeon/soldier.png +0 -0
- package/dist/pkgs/art/library/tiny-dungeon/spider.png +0 -0
- package/dist/pkgs/art/library/tiny-dungeon/staff.png +0 -0
- package/dist/pkgs/art/library/tiny-dungeon/stone-wall.png +0 -0
- package/dist/pkgs/art/library/tiny-dungeon/sword.png +0 -0
- package/dist/pkgs/art/library/tiny-dungeon/viking.png +0 -0
- package/dist/pkgs/art/library/tiny-dungeon/villager.png +0 -0
- package/dist/pkgs/art/library/tiny-dungeon/wizard.png +0 -0
- package/dist/pkgs/art/library/tiny-dungeon/worm.png +0 -0
- package/dist/pkgs/artgen/dist/draw-worker.js +1574 -0
- package/dist/pkgs/audio/dist/worker.js +76965 -0
- package/dist/pkgs/fonts/files/bungee.ttf +0 -0
- package/dist/pkgs/fonts/files/cinzel.ttf +0 -0
- package/dist/pkgs/fonts/files/fredoka.ttf +0 -0
- package/dist/pkgs/fonts/files/inter.ttf +0 -0
- package/dist/pkgs/fonts/files/lilita-one.ttf +0 -0
- package/dist/pkgs/fonts/files/luckiest-guy.ttf +0 -0
- package/dist/pkgs/fonts/files/medievalsharp.ttf +0 -0
- package/dist/pkgs/fonts/files/orbitron.ttf +0 -0
- package/dist/pkgs/fonts/files/patrick-hand.ttf +0 -0
- package/dist/pkgs/fonts/files/pixelify.ttf +0 -0
- package/dist/pkgs/fonts/files/press-start.ttf +0 -0
- package/dist/pkgs/fonts/files/russo-one.ttf +0 -0
- package/dist/pkgs/fonts/files/silkscreen.ttf +0 -0
- package/dist/pkgs/fonts/files/varela-round.ttf +0 -0
- package/dist/pkgs/fonts/licences/bungee.txt +93 -0
- package/dist/pkgs/fonts/licences/cinzel.txt +94 -0
- package/dist/pkgs/fonts/licences/fredoka.txt +93 -0
- package/dist/pkgs/fonts/licences/inter.txt +92 -0
- package/dist/pkgs/fonts/licences/lilita-one.txt +94 -0
- package/dist/pkgs/fonts/licences/luckiest-guy.txt +202 -0
- package/dist/pkgs/fonts/licences/medievalsharp.txt +105 -0
- package/dist/pkgs/fonts/licences/orbitron.txt +105 -0
- package/dist/pkgs/fonts/licences/patrick-hand.txt +93 -0
- package/dist/pkgs/fonts/licences/pixelify.txt +93 -0
- package/dist/pkgs/fonts/licences/press-start.txt +93 -0
- package/dist/pkgs/fonts/licences/russo-one.txt +93 -0
- package/dist/pkgs/fonts/licences/silkscreen.txt +93 -0
- package/dist/pkgs/fonts/licences/varela-round.txt +93 -0
- package/dist/pkgs/instruments/licences/musescore-general.txt +92 -0
- package/dist/pkgs/model/blender/builder.py +741 -0
- package/dist/pkgs/model/dist/worker.js +58006 -0
- package/dist/pkgs/paint/dist/worker.js +56328 -0
- package/dist/pkgs/soundlib/catalogue.json +2606 -0
- package/dist/pkgs/soundlib/library/kenney-digital/high-down.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-digital/high-up.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-digital/laser-1.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-digital/laser-2.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-digital/laser-3.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-digital/low-down.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-digital/low-random.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-digital/low-three-tone.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-digital/pep-sound-1.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-digital/pep-sound-2.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-digital/pep-sound-3.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-digital/phase-jump-1.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-digital/phase-jump-2.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-digital/phase-jump-3.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-digital/phaser-down-1.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-digital/phaser-down-2.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-digital/phaser-down-3.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-digital/phaser-up-1.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-digital/phaser-up-2.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-digital/phaser-up-3.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-digital/power-up-1.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-digital/power-up-10.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-digital/power-up-11.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-digital/space-trash-1.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-digital/space-trash-2.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-digital/space-trash-3.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-digital/three-tone-1.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-digital/three-tone-2.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-digital/tone-1.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-digital/two-tone-1.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-digital/two-tone-2.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-digital/zap-1.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-digital/zap-2.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-digital/zap-three-tone-down.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-digital/zap-three-tone-up.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-digital/zap-two-tone-2.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-digital/zap-two-tone.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-impact/footstep-carpet-000.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-impact/footstep-carpet-001.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-impact/footstep-carpet-002.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-impact/footstep-concrete-000.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-impact/footstep-concrete-001.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-impact/footstep-concrete-002.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-impact/footstep-grass-000.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-impact/footstep-grass-001.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-impact/footstep-grass-002.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-impact/footstep-snow-000.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-impact/footstep-snow-001.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-impact/footstep-snow-002.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-impact/footstep-wood-000.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-impact/footstep-wood-001.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-impact/footstep-wood-002.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-impact/impact-bell-heavy-000.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-impact/impact-bell-heavy-001.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-impact/impact-bell-heavy-002.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-impact/impact-generic-light-000.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-impact/impact-generic-light-001.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-impact/impact-generic-light-002.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-impact/impact-glass-heavy-000.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-impact/impact-glass-heavy-001.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-impact/impact-glass-heavy-002.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-impact/impact-glass-light-000.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-impact/impact-glass-light-001.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-impact/impact-glass-light-002.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-impact/impact-glass-medium-000.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-impact/impact-glass-medium-001.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-impact/impact-glass-medium-002.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-impact/impact-metal-heavy-000.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-impact/impact-metal-heavy-001.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-impact/impact-metal-heavy-002.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-impact/impact-metal-light-000.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-impact/impact-metal-light-001.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-impact/impact-metal-light-002.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-impact/impact-metal-medium-000.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-impact/impact-metal-medium-001.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-impact/impact-metal-medium-002.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-impact/impact-mining-000.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-impact/impact-mining-001.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-impact/impact-mining-002.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-impact/impact-plank-medium-000.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-impact/impact-plank-medium-001.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-impact/impact-plank-medium-002.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-impact/impact-plate-heavy-000.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-impact/impact-plate-heavy-001.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-impact/impact-plate-heavy-002.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-impact/impact-plate-light-000.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-impact/impact-plate-light-001.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-impact/impact-plate-light-002.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-impact/impact-plate-medium-000.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-impact/impact-plate-medium-001.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-impact/impact-plate-medium-002.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-impact/impact-punch-heavy-000.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-impact/impact-punch-heavy-001.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-impact/impact-punch-heavy-002.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-impact/impact-punch-medium-000.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-impact/impact-punch-medium-001.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-impact/impact-punch-medium-002.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-impact/impact-soft-heavy-000.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-impact/impact-soft-heavy-001.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-impact/impact-soft-heavy-002.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-impact/impact-soft-medium-000.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-impact/impact-soft-medium-001.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-impact/impact-soft-medium-002.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-impact/impact-tin-medium-000.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-impact/impact-tin-medium-001.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-impact/impact-tin-medium-002.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-impact/impact-wood-heavy-000.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-impact/impact-wood-heavy-001.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-impact/impact-wood-heavy-002.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-impact/impact-wood-light-000.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-impact/impact-wood-light-001.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-impact/impact-wood-light-002.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-impact/impact-wood-medium-000.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-impact/impact-wood-medium-001.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-impact/impact-wood-medium-002.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-interface/back-001.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-interface/back-002.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-interface/back-003.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-interface/bong-001.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-interface/click-001.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-interface/click-002.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-interface/click-003.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-interface/close-001.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-interface/close-002.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-interface/close-003.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-interface/confirmation-001.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-interface/confirmation-002.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-interface/confirmation-003.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-interface/drop-001.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-interface/drop-002.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-interface/drop-003.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-interface/error-001.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-interface/error-002.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-interface/error-003.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-interface/glass-001.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-interface/glass-002.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-interface/glass-003.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-interface/glitch-001.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-interface/glitch-002.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-interface/glitch-003.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-interface/maximize-001.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-interface/maximize-002.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-interface/maximize-003.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-interface/minimize-001.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-interface/minimize-002.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-interface/minimize-003.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-interface/open-001.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-interface/open-002.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-interface/open-003.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-interface/pluck-001.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-interface/pluck-002.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-interface/question-001.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-interface/question-002.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-interface/question-003.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-interface/scratch-001.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-interface/scratch-002.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-interface/scratch-003.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-interface/scroll-001.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-interface/scroll-002.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-interface/scroll-003.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-interface/select-001.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-interface/select-002.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-interface/select-003.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-interface/switch-001.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-interface/switch-002.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-interface/switch-003.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-interface/tick-001.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-interface/tick-002.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-interface/tick-004.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-interface/toggle-001.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-interface/toggle-002.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-interface/toggle-003.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-rpg/belt-handle-1.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-rpg/belt-handle-2.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-rpg/book-close.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-rpg/book-flip-1.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-rpg/book-flip-2.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-rpg/book-flip-3.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-rpg/book-open.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-rpg/book-place-1.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-rpg/book-place-2.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-rpg/book-place-3.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-rpg/chop.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-rpg/cloth-1.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-rpg/cloth-2.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-rpg/cloth-3.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-rpg/cloth-belt-2.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-rpg/cloth-belt.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-rpg/creak-1.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-rpg/creak-2.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-rpg/creak-3.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-rpg/door-close-1.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-rpg/door-close-2.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-rpg/door-close-3.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-rpg/door-open-1.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-rpg/door-open-2.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-rpg/draw-knife-1.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-rpg/draw-knife-2.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-rpg/draw-knife-3.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-rpg/drop-leather.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-rpg/footstep-00.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-rpg/footstep-01.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-rpg/footstep-02.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-rpg/handle-coins-2.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-rpg/handle-coins.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-rpg/handle-small-leather-2.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-rpg/handle-small-leather.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-rpg/knife-slice-2.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-rpg/knife-slice.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-rpg/metal-click.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-rpg/metal-latch.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-rpg/metal-pot-1.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-rpg/metal-pot-2.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-rpg/metal-pot-3.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-scifi/computer-noise-000.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-scifi/computer-noise-001.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-scifi/computer-noise-002.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-scifi/door-close-000.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-scifi/door-close-001.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-scifi/door-close-002.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-scifi/door-open-000.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-scifi/door-open-001.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-scifi/door-open-002.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-scifi/explosion-crunch-000.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-scifi/explosion-crunch-001.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-scifi/explosion-crunch-002.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-scifi/force-field-000.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-scifi/force-field-001.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-scifi/force-field-002.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-scifi/impact-metal-000.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-scifi/impact-metal-001.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-scifi/impact-metal-002.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-scifi/laser-large-000.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-scifi/laser-large-001.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-scifi/laser-large-002.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-scifi/laser-retro-000.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-scifi/laser-retro-001.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-scifi/laser-retro-002.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-scifi/laser-small-000.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-scifi/laser-small-001.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-scifi/laser-small-002.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-scifi/low-frequency-explosion-000.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-scifi/low-frequency-explosion-001.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-scifi/slime-000.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-scifi/slime-001.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-scifi/thruster-fire-000.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-scifi/thruster-fire-001.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-scifi/thruster-fire-002.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-ui/click-1.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-ui/click-2.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-ui/click-3.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-ui/mouseclick-1.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-ui/mouserelease-1.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-ui/rollover-1.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-ui/rollover-2.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-ui/rollover-3.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-ui/switch-1.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-ui/switch-10.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-ui/switch-11.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-ui/switch-12.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-ui/switch-13.wav +0 -0
- package/dist/pkgs/soundlib/library/kenney-ui/switch-14.wav +0 -0
- package/dist/pkgs/soundlib/licences/kenney-digital.txt +21 -0
- package/dist/pkgs/soundlib/licences/kenney-impact.txt +23 -0
- package/dist/pkgs/soundlib/licences/kenney-interface.txt +22 -0
- package/dist/pkgs/soundlib/licences/kenney-rpg.txt +21 -0
- package/dist/pkgs/soundlib/licences/kenney-scifi.txt +22 -0
- package/dist/pkgs/soundlib/licences/kenney-ui.txt +21 -0
- package/dist/pkgs/studio/dist/exported-player.js +67012 -0
- package/dist/pkgs/studio/dist/scripting.js +2435 -0
- package/dist/pkgs/studio/dist/server/playtest-worker.js +90348 -0
- package/dist/resolved/@fontsource-variable/geist/files/geist-latin-wght-normal.woff2 +0 -0
- package/dist/resolved/@fontsource-variable/jetbrains-mono/files/jetbrains-mono-latin-wght-normal.woff2 +0 -0
- package/dist/resolved/@fontsource-variable/plus-jakarta-sans/files/plus-jakarta-sans-latin-wght-normal.woff2 +0 -0
- package/dist/resolved/@gamesteel/site/package.json +17 -0
- package/package.json +29 -15
|
@@ -0,0 +1,741 @@
|
|
|
1
|
+
"""Gamesteel's builder (the Blender spec, "How it works", 4).
|
|
2
|
+
|
|
3
|
+
Blender runs it in the background with a job, which is plain data in the
|
|
4
|
+
model's frame: tiles, x to the right, y up, z to the front. The builder:
|
|
5
|
+
|
|
6
|
+
- builds every part;
|
|
7
|
+
- colours each part with its material, and lights it with the game's Style;
|
|
8
|
+
- measures the model over every frame;
|
|
9
|
+
- renders each frame to a PNG.
|
|
10
|
+
|
|
11
|
+
It reads numbers and names from the job, and never evaluates any of it (R1).
|
|
12
|
+
|
|
13
|
+
blender --background --factory-startup --python-exit-code 1 \\
|
|
14
|
+
--python builder.py -- <job.json> <out folder>
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import json
|
|
18
|
+
import math
|
|
19
|
+
import os
|
|
20
|
+
import sys
|
|
21
|
+
import time
|
|
22
|
+
|
|
23
|
+
import bmesh
|
|
24
|
+
import bpy
|
|
25
|
+
import numpy
|
|
26
|
+
from mathutils import Matrix, Vector
|
|
27
|
+
|
|
28
|
+
SIDES = 32
|
|
29
|
+
RINGS = 16
|
|
30
|
+
# Edges sharper than this are rounded by a part's rounding and drawn sharp.
|
|
31
|
+
BEVEL_FROM = math.radians(40)
|
|
32
|
+
SHARP = math.radians(35)
|
|
33
|
+
# Roughness and metal for each kind; shine overrides the roughness.
|
|
34
|
+
# Metal is half metallic: fully, it would only mirror the sky's fill colour and
|
|
35
|
+
# lose its own palette colour (tuned by eye, the plan's Task 10).
|
|
36
|
+
KINDS = {
|
|
37
|
+
'painted': (0.5, 0.0), 'metal': (0.3, 0.45), 'plastic': (0.22, 0.0), 'rubber': (0.85, 0.0),
|
|
38
|
+
'cloth': (0.95, 0.0), 'leather': (0.55, 0.0), 'skin': (0.6, 0.0), 'wood': (0.7, 0.0),
|
|
39
|
+
'stone': (0.85, 0.0), 'glass': (0.06, 0.0), 'glow': (1.0, 0.0),
|
|
40
|
+
}
|
|
41
|
+
SHINY = {'metal', 'plastic', 'glass'}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def linear(c):
|
|
45
|
+
"""An sRGB colour, 0 to 1, as Blender's linear light."""
|
|
46
|
+
return tuple(v / 12.92 if v <= 0.04045 else ((v + 0.055) / 1.055) ** 2.4 for v in c)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def link(ob, collection=None):
|
|
50
|
+
(collection or bpy.context.scene.collection).objects.link(ob)
|
|
51
|
+
return ob
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def empty(name, parent=None):
|
|
55
|
+
ob = link(bpy.data.objects.new(name, None))
|
|
56
|
+
ob.parent = parent
|
|
57
|
+
ob.rotation_mode = 'QUATERNION'
|
|
58
|
+
return ob
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def place(ob, t):
|
|
62
|
+
ob.location = t['position']
|
|
63
|
+
ob.rotation_quaternion = t['rotation']
|
|
64
|
+
ob.scale = t['scale']
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
# Shapes, each built in the model's frame, y up, centred as the kit says.
|
|
68
|
+
|
|
69
|
+
def lift(bm):
|
|
70
|
+
"""bmesh makes round things along z; the model's up is y."""
|
|
71
|
+
bmesh.ops.rotate(bm, verts=bm.verts, cent=(0, 0, 0), matrix=Matrix.Rotation(math.radians(-90), 3, 'X'))
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def make_box(s):
|
|
75
|
+
bm = bmesh.new()
|
|
76
|
+
bmesh.ops.create_cube(bm, size=1.0)
|
|
77
|
+
bmesh.ops.scale(bm, vec=(s['w'], s['h'], s['d']), verts=bm.verts)
|
|
78
|
+
return bm
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def make_ball(s):
|
|
82
|
+
bm = bmesh.new()
|
|
83
|
+
bmesh.ops.create_uvsphere(bm, u_segments=SIDES, v_segments=RINGS, radius=s['r'])
|
|
84
|
+
lift(bm)
|
|
85
|
+
return bm
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def make_egg(s):
|
|
89
|
+
bm = bmesh.new()
|
|
90
|
+
bmesh.ops.create_uvsphere(bm, u_segments=SIDES, v_segments=RINGS, radius=0.5)
|
|
91
|
+
lift(bm)
|
|
92
|
+
bmesh.ops.scale(bm, vec=(s['w'], s['h'], s['d']), verts=bm.verts)
|
|
93
|
+
return bm
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def make_cone(s, top=None):
|
|
97
|
+
bm = bmesh.new()
|
|
98
|
+
bmesh.ops.create_cone(bm, cap_ends=True, cap_tris=False, segments=SIDES, radius1=s['r'],
|
|
99
|
+
radius2=s['top'] if top is None else top, depth=s['h'])
|
|
100
|
+
bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=1e-6)
|
|
101
|
+
lift(bm)
|
|
102
|
+
return bm
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def make_cylinder(s):
|
|
106
|
+
return make_cone(s, s['r'])
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def make_lathe(profile):
|
|
110
|
+
bm = bmesh.new()
|
|
111
|
+
verts = [bm.verts.new((r, h, 0.0)) for r, h in profile]
|
|
112
|
+
edges = [bm.edges.new((verts[i], verts[i + 1])) for i in range(len(verts) - 1)]
|
|
113
|
+
bmesh.ops.spin(bm, geom=verts + edges, cent=(0, 0, 0), axis=(0, 1, 0), angle=2 * math.pi,
|
|
114
|
+
steps=SIDES, use_duplicate=False)
|
|
115
|
+
bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=1e-5)
|
|
116
|
+
open_edges = [e for e in bm.edges if e.is_boundary]
|
|
117
|
+
if open_edges:
|
|
118
|
+
bmesh.ops.holes_fill(bm, edges=open_edges, sides=0)
|
|
119
|
+
return bm
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def make_capsule(s):
|
|
123
|
+
r, h, arc = s['r'], s['h'], 8
|
|
124
|
+
low = [(r * math.cos(math.radians(-90 + 90 * i / arc)), -h / 2 + r + r * math.sin(math.radians(-90 + 90 * i / arc)))
|
|
125
|
+
for i in range(arc + 1)]
|
|
126
|
+
high = [(r * math.cos(math.radians(90 * i / arc)), h / 2 - r + r * math.sin(math.radians(90 * i / arc)))
|
|
127
|
+
for i in range(arc + 1)]
|
|
128
|
+
return make_lathe(low + high)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def make_torus(s):
|
|
132
|
+
bm = bmesh.new()
|
|
133
|
+
big, small, rings = s['r'], s['thickness'] / 2, []
|
|
134
|
+
for i in range(SIDES):
|
|
135
|
+
a = 2 * math.pi * i / SIDES
|
|
136
|
+
ring = []
|
|
137
|
+
for j in range(12):
|
|
138
|
+
b = 2 * math.pi * j / 12
|
|
139
|
+
reach = big + small * math.cos(b)
|
|
140
|
+
ring.append(bm.verts.new((reach * math.cos(a), small * math.sin(b), reach * math.sin(a))))
|
|
141
|
+
rings.append(ring)
|
|
142
|
+
for i in range(SIDES):
|
|
143
|
+
a, b = rings[i], rings[(i + 1) % SIDES]
|
|
144
|
+
for j in range(12):
|
|
145
|
+
bm.faces.new((a[j], a[(j + 1) % 12], b[(j + 1) % 12], b[j]))
|
|
146
|
+
return bm
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def make_dome(s):
|
|
150
|
+
bm = make_ball(s)
|
|
151
|
+
geom = bm.verts[:] + bm.edges[:] + bm.faces[:]
|
|
152
|
+
bmesh.ops.bisect_plane(bm, geom=geom, dist=1e-6, plane_co=(0, 0, 0), plane_no=(0, 1, 0), clear_inner=True)
|
|
153
|
+
open_edges = [e for e in bm.edges if e.is_boundary]
|
|
154
|
+
if open_edges:
|
|
155
|
+
bmesh.ops.holes_fill(bm, edges=open_edges, sides=0)
|
|
156
|
+
return bm
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def make_wedge(s):
|
|
160
|
+
w, h, d = s['w'] / 2, s['h'] / 2, s['d'] / 2
|
|
161
|
+
bm = bmesh.new()
|
|
162
|
+
v = [bm.verts.new(p) for p in [(-w, -h, -d), (w, -h, -d), (w, h, -d), (-w, h, -d), (-w, -h, d), (w, -h, d)]]
|
|
163
|
+
for face in [(0, 1, 2, 3), (4, 5, 1, 0), (3, 2, 5, 4), (0, 3, 4), (1, 5, 2)]:
|
|
164
|
+
bm.faces.new([v[i] for i in face])
|
|
165
|
+
return bm
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def make_tube(s):
|
|
169
|
+
curve = bpy.data.curves.new('gm-tube', 'CURVE')
|
|
170
|
+
curve.dimensions = '3D'
|
|
171
|
+
curve.bevel_depth = 1.0
|
|
172
|
+
curve.bevel_resolution = 4
|
|
173
|
+
curve.use_fill_caps = True
|
|
174
|
+
curve.resolution_u = 8
|
|
175
|
+
path = s['path']
|
|
176
|
+
radii = s['radius'] if len(s['radius']) == len(path) else [s['radius'][0]] * len(path)
|
|
177
|
+
spline = curve.splines.new('NURBS' if len(path) > 2 else 'POLY')
|
|
178
|
+
spline.points.add(len(path) - 1)
|
|
179
|
+
for point, at, r in zip(spline.points, path, radii):
|
|
180
|
+
point.co = (at[0], at[1], at[2], 1.0)
|
|
181
|
+
point.radius = r
|
|
182
|
+
if spline.type == 'NURBS':
|
|
183
|
+
spline.use_endpoint_u = True
|
|
184
|
+
spline.order_u = 3
|
|
185
|
+
ob = link(bpy.data.objects.new('gm-tube', curve))
|
|
186
|
+
mesh = bpy.data.meshes.new_from_object(ob.evaluated_get(bpy.context.evaluated_depsgraph_get()))
|
|
187
|
+
bpy.data.objects.remove(ob)
|
|
188
|
+
bpy.data.curves.remove(curve)
|
|
189
|
+
bm = bmesh.new()
|
|
190
|
+
bm.from_mesh(mesh)
|
|
191
|
+
bpy.data.meshes.remove(mesh)
|
|
192
|
+
return bm
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def make_slab(s):
|
|
196
|
+
t = s['thickness'] / 2
|
|
197
|
+
bm = bmesh.new()
|
|
198
|
+
face = bm.faces.new([bm.verts.new((x, y, t)) for x, y in s['outline']])
|
|
199
|
+
made = bmesh.ops.extrude_face_region(bm, geom=[face])
|
|
200
|
+
moved = [e for e in made['geom'] if isinstance(e, bmesh.types.BMVert)]
|
|
201
|
+
bmesh.ops.translate(bm, verts=moved, vec=(0, 0, -2 * t))
|
|
202
|
+
return bm
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def resample(outline, n):
|
|
206
|
+
"""An outline as n points evenly along it, from its first point round."""
|
|
207
|
+
points = [Vector(p) for p in outline] + [Vector(outline[0])]
|
|
208
|
+
lengths = [(points[i + 1] - points[i]).length for i in range(len(points) - 1)]
|
|
209
|
+
total = sum(lengths) or 1.0
|
|
210
|
+
out = []
|
|
211
|
+
for k in range(n):
|
|
212
|
+
d, i = total * k / n, 0
|
|
213
|
+
while i < len(lengths) - 1 and d > lengths[i]:
|
|
214
|
+
d -= lengths[i]
|
|
215
|
+
i += 1
|
|
216
|
+
t = 0.0 if lengths[i] == 0 else min(1.0, d / lengths[i])
|
|
217
|
+
out.append(points[i].lerp(points[i + 1], t))
|
|
218
|
+
return out
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def make_loft(s):
|
|
222
|
+
sections = s['sections']
|
|
223
|
+
counts = {len(sec['outline']) for sec in sections}
|
|
224
|
+
# Outlines of one count keep their corners; others are spaced evenly.
|
|
225
|
+
n = counts.pop() if len(counts) == 1 else max(len(sec['outline']) for sec in sections)
|
|
226
|
+
same = all(len(sec['outline']) == n for sec in sections)
|
|
227
|
+
bm = bmesh.new()
|
|
228
|
+
loops = []
|
|
229
|
+
for sec in sections:
|
|
230
|
+
points = [Vector(p) for p in sec['outline']] if same else resample(sec['outline'], n)
|
|
231
|
+
loops.append([bm.verts.new((p.x, p.y, sec['z'])) for p in points])
|
|
232
|
+
for a, b in zip(loops, loops[1:]):
|
|
233
|
+
for k in range(n):
|
|
234
|
+
bm.faces.new((a[k], a[(k + 1) % n], b[(k + 1) % n], b[k]))
|
|
235
|
+
bm.faces.new(loops[0])
|
|
236
|
+
bm.faces.new(loops[-1])
|
|
237
|
+
return bm
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
MAKERS = {
|
|
241
|
+
'box': make_box, 'ball': make_ball, 'egg': make_egg, 'capsule': make_capsule, 'cylinder': make_cylinder,
|
|
242
|
+
'cone': make_cone, 'torus': make_torus, 'dome': make_dome, 'wedge': make_wedge,
|
|
243
|
+
'lathe': lambda s: make_lathe(s['profile']), 'tube': make_tube, 'slab': make_slab, 'loft': make_loft,
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def finish(bm, rounding):
|
|
248
|
+
"""Normals out, sharp edges rounded and kept sharp, and each vertex's
|
|
249
|
+
convexity kept as gm_edge, where wear rubs."""
|
|
250
|
+
bmesh.ops.recalc_face_normals(bm, faces=bm.faces)
|
|
251
|
+
if rounding > 0:
|
|
252
|
+
sharp = [e for e in bm.edges if len(e.link_faces) == 2 and e.calc_face_angle(0.0) > BEVEL_FROM]
|
|
253
|
+
if sharp:
|
|
254
|
+
bmesh.ops.bevel(bm, geom=sharp, offset=rounding, offset_type='OFFSET', segments=3, profile=0.5,
|
|
255
|
+
affect='EDGES', clamp_overlap=True)
|
|
256
|
+
for f in bm.faces:
|
|
257
|
+
f.smooth = True
|
|
258
|
+
for e in bm.edges:
|
|
259
|
+
if len(e.link_faces) != 2 or e.calc_face_angle(0.0) > SHARP:
|
|
260
|
+
e.smooth = False
|
|
261
|
+
layer = bm.verts.layers.float.new('gm_edge')
|
|
262
|
+
for v in bm.verts:
|
|
263
|
+
convex = max([e.calc_face_angle_signed(0.0) for e in v.link_edges if len(e.link_faces) == 2] + [0.0])
|
|
264
|
+
v[layer] = max(0.0, min(1.0, convex / 1.2))
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
class Graph:
|
|
268
|
+
"""A material's nodes, made by calls; each call answers the socket it outputs."""
|
|
269
|
+
|
|
270
|
+
def __init__(self, material):
|
|
271
|
+
self.tree = material.node_tree
|
|
272
|
+
self.tree.nodes.clear()
|
|
273
|
+
|
|
274
|
+
def node(self, kind, **settings):
|
|
275
|
+
n = self.tree.nodes.new(kind)
|
|
276
|
+
for key, value in settings.items():
|
|
277
|
+
setattr(n, key, value)
|
|
278
|
+
return n
|
|
279
|
+
|
|
280
|
+
def feed(self, socket, value):
|
|
281
|
+
if isinstance(value, (int, float, tuple)):
|
|
282
|
+
socket.default_value = value
|
|
283
|
+
else:
|
|
284
|
+
self.tree.links.new(value, socket)
|
|
285
|
+
|
|
286
|
+
def math(self, op, a, b=0.0, clamp=False):
|
|
287
|
+
n = self.node('ShaderNodeMath', operation=op, use_clamp=clamp)
|
|
288
|
+
self.feed(n.inputs[0], a)
|
|
289
|
+
self.feed(n.inputs[1], b)
|
|
290
|
+
return n.outputs[0]
|
|
291
|
+
|
|
292
|
+
def colour(self, rgb):
|
|
293
|
+
n = self.node('ShaderNodeRGB')
|
|
294
|
+
n.outputs[0].default_value = linear(rgb) + (1.0,)
|
|
295
|
+
return n.outputs[0]
|
|
296
|
+
|
|
297
|
+
def mix(self, blend, fac, a, b):
|
|
298
|
+
# By position: Blender 5 renamed some sockets (Fac is Factor).
|
|
299
|
+
n = self.node('ShaderNodeMixRGB', blend_type=blend, use_clamp=True)
|
|
300
|
+
self.feed(n.inputs[0], fac)
|
|
301
|
+
self.feed(n.inputs[1], a)
|
|
302
|
+
self.feed(n.inputs[2], b)
|
|
303
|
+
return n.outputs[0]
|
|
304
|
+
|
|
305
|
+
def split(self, vector):
|
|
306
|
+
n = self.node('ShaderNodeSeparateXYZ')
|
|
307
|
+
self.feed(n.inputs[0], vector)
|
|
308
|
+
return n.outputs[0], n.outputs[1], n.outputs[2]
|
|
309
|
+
|
|
310
|
+
def scaled(self, vector, k):
|
|
311
|
+
n = self.node('ShaderNodeVectorMath', operation='SCALE')
|
|
312
|
+
self.feed(n.inputs[0], vector)
|
|
313
|
+
n.inputs['Scale'].default_value = k
|
|
314
|
+
return n.outputs[0]
|
|
315
|
+
|
|
316
|
+
def noise(self, vector, scale, detail=2.0):
|
|
317
|
+
n = self.node('ShaderNodeTexNoise')
|
|
318
|
+
self.feed(n.inputs['Vector'], vector)
|
|
319
|
+
n.inputs['Scale'].default_value = scale
|
|
320
|
+
n.inputs['Detail'].default_value = detail
|
|
321
|
+
return n.outputs[0]
|
|
322
|
+
|
|
323
|
+
def voronoi(self, vector, scale, feature='F1'):
|
|
324
|
+
n = self.node('ShaderNodeTexVoronoi', feature=feature)
|
|
325
|
+
self.feed(n.inputs['Vector'], vector)
|
|
326
|
+
n.inputs['Scale'].default_value = scale
|
|
327
|
+
return n
|
|
328
|
+
|
|
329
|
+
def to_grid(self, u):
|
|
330
|
+
"""How far u is from the nearest whole number, 0 to 0.5."""
|
|
331
|
+
return self.math('ABSOLUTE', self.math('SUBTRACT', self.math('FRACT', self.math('ADD', u, 0.5)), 0.5))
|
|
332
|
+
|
|
333
|
+
def triplanar(self, point, normal, plane):
|
|
334
|
+
"""plane(u, v) on each of the three planes, blended by which way the surface faces."""
|
|
335
|
+
x, y, z = self.split(point)
|
|
336
|
+
nx, ny, nz = self.split(normal)
|
|
337
|
+
weights = [self.math('POWER', self.math('ABSOLUTE', c), 4.0) for c in (nx, ny, nz)]
|
|
338
|
+
total = self.math('ADD', self.math('ADD', weights[0], weights[1]), self.math('ADD', weights[2], 1e-6))
|
|
339
|
+
out = None
|
|
340
|
+
for w, face in zip(weights, (plane(y, z), plane(x, z), plane(x, y))):
|
|
341
|
+
part = self.math('MULTIPLY', self.math('DIVIDE', w, total), face)
|
|
342
|
+
out = part if out is None else self.math('ADD', out, part)
|
|
343
|
+
return out
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
class Builder:
|
|
347
|
+
def __init__(self, job, out):
|
|
348
|
+
self.job = job
|
|
349
|
+
self.out = out
|
|
350
|
+
self.objects = {}
|
|
351
|
+
self.rest = {}
|
|
352
|
+
self.materials = {}
|
|
353
|
+
|
|
354
|
+
def run(self):
|
|
355
|
+
self.setup()
|
|
356
|
+
for m in self.job['materials']:
|
|
357
|
+
self.materials[m['id']] = (self.material(m), m)
|
|
358
|
+
for o in self.job['objects']:
|
|
359
|
+
self.make(o)
|
|
360
|
+
for o in self.job['objects']:
|
|
361
|
+
for cutter in o['cuts']:
|
|
362
|
+
self.objects[o['id']].modifiers['cut-%d' % cutter].object = self.objects[cutter]
|
|
363
|
+
self.lights()
|
|
364
|
+
self.outline()
|
|
365
|
+
frame = self.measure()
|
|
366
|
+
if frame is None:
|
|
367
|
+
return {'ok': False, 'reason': 'nothing in the model is drawn'}
|
|
368
|
+
if frame == 'cut':
|
|
369
|
+
return {'ok': False, 'reason': 'nothing in the model is drawn: its cuts take all of it'}
|
|
370
|
+
self.camera(frame)
|
|
371
|
+
for i, f in enumerate(self.job['frames']):
|
|
372
|
+
self.pose(f)
|
|
373
|
+
self.scene.render.filepath = os.path.join(self.out, 'frame-%03d.png' % i)
|
|
374
|
+
bpy.ops.render.render(write_still=True)
|
|
375
|
+
return {
|
|
376
|
+
'ok': True, 'frame': {'w': frame['w'], 'h': frame['h']}, 'pixelsPerTile': frame['ppt'],
|
|
377
|
+
'extent': {'half': frame['half'], 'bottom': frame['bottom'], 'top': frame['top']},
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
def setup(self):
|
|
381
|
+
bpy.ops.wm.read_factory_settings(use_empty=True)
|
|
382
|
+
s = self.scene = bpy.context.scene
|
|
383
|
+
engines = [i.identifier for i in bpy.types.RenderSettings.bl_rna.properties['engine'].enum_items]
|
|
384
|
+
s.render.engine = 'BLENDER_EEVEE_NEXT' if 'BLENDER_EEVEE_NEXT' in engines else 'BLENDER_EEVEE'
|
|
385
|
+
pixel = self.job['style']['pixel']
|
|
386
|
+
s.eevee.taa_render_samples = 1 if pixel else 16
|
|
387
|
+
s.render.film_transparent = True
|
|
388
|
+
s.render.resolution_percentage = 100
|
|
389
|
+
# Exact colours, crisp edges: no dither noise on 8-bit output, and a
|
|
390
|
+
# tighter filter than Blender's 1.5 px, so a sprite is not soft.
|
|
391
|
+
s.render.dither_intensity = 0.0
|
|
392
|
+
s.render.filter_size = 1.0
|
|
393
|
+
s.render.image_settings.file_format = 'PNG'
|
|
394
|
+
s.render.image_settings.color_mode = 'RGBA'
|
|
395
|
+
s.render.image_settings.color_depth = '8'
|
|
396
|
+
# Standard, so a palette colour renders as it is set (R3).
|
|
397
|
+
s.view_settings.view_transform = 'Standard'
|
|
398
|
+
s.view_settings.look = 'None'
|
|
399
|
+
s.view_settings.exposure = 0.0
|
|
400
|
+
s.view_settings.gamma = 1.0
|
|
401
|
+
# The model's frame in Blender's: its y up is Blender's z, its front faces the camera.
|
|
402
|
+
kit = empty('gm-kit')
|
|
403
|
+
kit.rotation_mode = 'XYZ'
|
|
404
|
+
kit.rotation_euler = (math.radians(90), 0, 0)
|
|
405
|
+
view = empty('gm-view', kit)
|
|
406
|
+
turn, tilt = self.job['view']['turn'], self.job['view']['tilt']
|
|
407
|
+
view.rotation_quaternion = (Matrix.Rotation(math.radians(tilt), 4, 'X') @ Matrix.Rotation(math.radians(turn), 4, 'Y')).to_quaternion()
|
|
408
|
+
self.model = empty('gm-model', view)
|
|
409
|
+
self.unlined = bpy.data.collections.new('gm-unlined')
|
|
410
|
+
s.collection.children.link(self.unlined)
|
|
411
|
+
|
|
412
|
+
def make(self, o):
|
|
413
|
+
shape = o['shape']
|
|
414
|
+
parent = self.model if o['parent'] is None else self.objects[o['parent']]
|
|
415
|
+
if shape['kind'] == 'group':
|
|
416
|
+
ob = empty(o['name'], parent)
|
|
417
|
+
else:
|
|
418
|
+
bm = MAKERS[shape['kind']](shape)
|
|
419
|
+
finish(bm, o['round'])
|
|
420
|
+
mesh = bpy.data.meshes.new(o['name'])
|
|
421
|
+
bm.to_mesh(mesh)
|
|
422
|
+
bm.free()
|
|
423
|
+
ob = link(bpy.data.objects.new(o['name'], mesh))
|
|
424
|
+
ob.parent = parent
|
|
425
|
+
ob.rotation_mode = 'QUATERNION'
|
|
426
|
+
material, spec = self.materials[o['material']]
|
|
427
|
+
mesh.materials.append(material)
|
|
428
|
+
for cutter in o['cuts']:
|
|
429
|
+
cut = ob.modifiers.new('cut-%d' % cutter, 'BOOLEAN')
|
|
430
|
+
cut.operation = 'DIFFERENCE'
|
|
431
|
+
cut.solver = 'EXACT'
|
|
432
|
+
if o['smooth'] > 0:
|
|
433
|
+
sub = ob.modifiers.new('smooth', 'SUBSURF')
|
|
434
|
+
sub.levels = o['smooth']
|
|
435
|
+
sub.render_levels = o['smooth']
|
|
436
|
+
if o['cutter']:
|
|
437
|
+
ob.hide_render = True
|
|
438
|
+
ob.display_type = 'WIRE'
|
|
439
|
+
elif spec['kind'] == 'glow':
|
|
440
|
+
self.unlined.objects.link(ob)
|
|
441
|
+
place(ob, o['rest'])
|
|
442
|
+
self.objects[o['id']] = ob
|
|
443
|
+
self.rest[o['id']] = o['rest']
|
|
444
|
+
|
|
445
|
+
def material(self, m):
|
|
446
|
+
mat = bpy.data.materials.new('gm-%d' % m['id'])
|
|
447
|
+
mat.use_nodes = True
|
|
448
|
+
g = Graph(mat)
|
|
449
|
+
d = m['detail']
|
|
450
|
+
shading = self.job['style']['shading']
|
|
451
|
+
coords = g.node('ShaderNodeTexCoord')
|
|
452
|
+
point, normal = coords.outputs['Object'], coords.outputs['Normal']
|
|
453
|
+
model = g.node('ShaderNodeTexCoord')
|
|
454
|
+
model.object = self.model
|
|
455
|
+
roughness, metal = KINDS[m['kind']]
|
|
456
|
+
if d['shine'] is not None:
|
|
457
|
+
roughness = 1.0 - d['shine']
|
|
458
|
+
shiny = m['kind'] in SHINY or (d['shine'] is not None and d['shine'] >= 0.6)
|
|
459
|
+
col = g.colour(m['colour'])
|
|
460
|
+
rough = roughness
|
|
461
|
+
height = None
|
|
462
|
+
|
|
463
|
+
def bump(h):
|
|
464
|
+
nonlocal height
|
|
465
|
+
height = h if height is None else g.math('ADD', height, h)
|
|
466
|
+
|
|
467
|
+
if d['stripes'] is not None:
|
|
468
|
+
st = d['stripes']
|
|
469
|
+
_, gy, _ = g.split(coords.outputs['Generated'])
|
|
470
|
+
band = g.math('LESS_THAN', g.math('FRACT', g.math('MULTIPLY', gy, st['count'])), st['width'])
|
|
471
|
+
col = g.mix('MIX', band, col, g.colour(st['colour']))
|
|
472
|
+
if d['spots'] is not None:
|
|
473
|
+
sp = d['spots']
|
|
474
|
+
cells = g.voronoi(point, 1.0 / sp['size'])
|
|
475
|
+
pick = g.math('LESS_THAN', g.split(cells.outputs['Color'])[0], sp['amount'])
|
|
476
|
+
spot = g.math('MULTIPLY', pick, g.math('LESS_THAN', cells.outputs['Distance'], 0.32))
|
|
477
|
+
col = g.mix('MIX', spot, col, g.colour(sp['colour']))
|
|
478
|
+
seam = None
|
|
479
|
+
if d['panels'] > 0 or d['stitches'] > 0:
|
|
480
|
+
n = d['panels'] if d['panels'] > 0 else 2.0
|
|
481
|
+
seam = g.triplanar(g.scaled(point, n), normal, lambda u, v: g.math('MINIMUM', g.to_grid(u), g.to_grid(v)))
|
|
482
|
+
if d['panels'] > 0:
|
|
483
|
+
line = g.math('LESS_THAN', seam, 0.035)
|
|
484
|
+
col = g.mix('MULTIPLY', g.math('MULTIPLY', line, 0.45), col, (0.0, 0.0, 0.0, 1.0))
|
|
485
|
+
bump(g.math('MULTIPLY', line, -0.4))
|
|
486
|
+
if d['rivets'] > 0:
|
|
487
|
+
def dots(u, v):
|
|
488
|
+
gu, gv = g.to_grid(u), g.to_grid(v)
|
|
489
|
+
return g.math('SQRT', g.math('ADD', g.math('MULTIPLY', gu, gu), g.math('MULTIPLY', gv, gv)))
|
|
490
|
+
dot = g.triplanar(g.scaled(point, d['panels'] * 6), normal, dots)
|
|
491
|
+
near = g.math('MULTIPLY', g.math('LESS_THAN', seam, 0.12), g.math('GREATER_THAN', seam, 0.05))
|
|
492
|
+
rivet = g.math('MULTIPLY', g.math('MULTIPLY', g.math('LESS_THAN', dot, 0.16), near), d['rivets'])
|
|
493
|
+
col = g.mix('SCREEN', g.math('MULTIPLY', rivet, 0.5), col, (1.0, 1.0, 1.0, 1.0))
|
|
494
|
+
bump(g.math('MULTIPLY', rivet, 0.5))
|
|
495
|
+
if d['stitches'] > 0:
|
|
496
|
+
band = g.math('MULTIPLY', g.math('LESS_THAN', seam, 0.08), g.math('GREATER_THAN', seam, 0.045))
|
|
497
|
+
dash = g.math('GREATER_THAN', g.noise(g.scaled(point, 1.0), 60.0, 0.0), 0.5)
|
|
498
|
+
stitch = g.math('MULTIPLY', g.math('MULTIPLY', band, dash), d['stitches'])
|
|
499
|
+
col = g.mix('SCREEN', g.math('MULTIPLY', stitch, 0.6), col, (1.0, 1.0, 1.0, 1.0))
|
|
500
|
+
if d['grain'] > 0:
|
|
501
|
+
wave = g.node('ShaderNodeTexWave', wave_type='BANDS', bands_direction='Y')
|
|
502
|
+
g.feed(wave.inputs['Vector'], point)
|
|
503
|
+
wave.inputs['Scale'].default_value = 6.0
|
|
504
|
+
wave.inputs['Distortion'].default_value = 6.0
|
|
505
|
+
col = g.mix('MULTIPLY', g.math('MULTIPLY', wave.outputs[1], d['grain'] * 0.5), col, (0.62, 0.55, 0.5, 1.0))
|
|
506
|
+
if d['cracks'] > 0:
|
|
507
|
+
edges = g.voronoi(point, 5.0, 'DISTANCE_TO_EDGE')
|
|
508
|
+
crack = g.math('MULTIPLY', g.math('LESS_THAN', edges.outputs['Distance'], 0.035), d['cracks'])
|
|
509
|
+
col = g.mix('MULTIPLY', crack, col, (0.35, 0.33, 0.33, 1.0))
|
|
510
|
+
bump(g.math('MULTIPLY', crack, -0.5))
|
|
511
|
+
if d['rust'] > 0:
|
|
512
|
+
patch = g.math('MULTIPLY', g.math('SUBTRACT', g.noise(point, 3.5, 6.0), 1.0 - d['rust']), 4.0, clamp=True)
|
|
513
|
+
col = g.mix('MIX', patch, col, g.colour((0.48, 0.24, 0.11)))
|
|
514
|
+
rough = g.math('ADD', rough, g.math('MULTIPLY', patch, 0.3))
|
|
515
|
+
if d['grime'] > 0:
|
|
516
|
+
span = self.job['span']
|
|
517
|
+
low = g.node('ShaderNodeMapRange', clamp=True)
|
|
518
|
+
g.feed(low.inputs['Value'], g.split(model.outputs['Object'])[1])
|
|
519
|
+
low.inputs['From Min'].default_value = span['low']
|
|
520
|
+
low.inputs['From Max'].default_value = span['high'] if span['high'] > span['low'] else span['low'] + 1.0
|
|
521
|
+
low.inputs['To Min'].default_value = 1.0
|
|
522
|
+
low.inputs['To Max'].default_value = 0.0
|
|
523
|
+
dirt = g.math('MULTIPLY', g.math('MULTIPLY', low.outputs['Result'], g.math('ADD', g.noise(point, 4.0), 0.5)), d['grime'] * 1.6, clamp=True)
|
|
524
|
+
col = g.mix('MULTIPLY', dirt, col, (0.33, 0.29, 0.25, 1.0))
|
|
525
|
+
if d['scratches'] > 0:
|
|
526
|
+
scratch = g.math('MULTIPLY', g.math('GREATER_THAN', g.noise(point, 38.0, 8.0), 0.66), d['scratches'] * 0.8)
|
|
527
|
+
col = g.mix('SCREEN', scratch, col, (0.7, 0.7, 0.7, 1.0))
|
|
528
|
+
if d['wear'] > 0:
|
|
529
|
+
edge = g.node('ShaderNodeAttribute', attribute_name='gm_edge').outputs[2]
|
|
530
|
+
worn = g.math('MULTIPLY', g.math('MULTIPLY', edge, g.math('ADD', g.noise(point, 9.0), 0.4)), d['wear'] * 2.2, clamp=True)
|
|
531
|
+
col = g.mix('MIX', worn, col, g.mix('MIX', 0.55, col, (0.85, 0.85, 0.85, 1.0)))
|
|
532
|
+
rough = g.math('SUBTRACT', rough, g.math('MULTIPLY', worn, 0.25))
|
|
533
|
+
if d['dents'] > 0:
|
|
534
|
+
bump(g.math('MULTIPLY', g.voronoi(point, 7.0, 'SMOOTH_F1').outputs['Distance'], d['dents'] * 0.6))
|
|
535
|
+
if d['bumps'] > 0:
|
|
536
|
+
bump(g.math('MULTIPLY', g.noise(point, 22.0, 4.0), d['bumps'] * 0.35))
|
|
537
|
+
|
|
538
|
+
out = g.node('ShaderNodeOutputMaterial')
|
|
539
|
+
if m['kind'] == 'glow':
|
|
540
|
+
emit = g.node('ShaderNodeEmission')
|
|
541
|
+
g.feed(emit.inputs[0], col)
|
|
542
|
+
emit.inputs[1].default_value = 1.0 + 2.0 * d['glow']
|
|
543
|
+
g.feed(out.inputs['Surface'], emit.outputs[0])
|
|
544
|
+
return mat
|
|
545
|
+
surface = g.node('ShaderNodeBsdfPrincipled')
|
|
546
|
+
g.feed(surface.inputs['Roughness'], rough)
|
|
547
|
+
if height is not None:
|
|
548
|
+
relief = g.node('ShaderNodeBump')
|
|
549
|
+
relief.inputs['Strength'].default_value = 0.35 if shading == 'soft' else 0.15
|
|
550
|
+
g.feed(relief.inputs['Height'], height)
|
|
551
|
+
g.feed(surface.inputs['Normal'], relief.outputs['Normal'])
|
|
552
|
+
if shading == 'soft':
|
|
553
|
+
g.feed(surface.inputs['Base Color'], col if m['kind'] != 'glass' else g.mix('MULTIPLY', 0.4, col, (0.0, 0.0, 0.0, 1.0)))
|
|
554
|
+
surface.inputs['Metallic'].default_value = metal
|
|
555
|
+
if m['kind'] == 'glass':
|
|
556
|
+
surface.inputs['Coat Weight'].default_value = 1.0
|
|
557
|
+
if d['glow'] > 0:
|
|
558
|
+
g.feed(surface.inputs['Emission Color'], col)
|
|
559
|
+
surface.inputs['Emission Strength'].default_value = 2.0 * d['glow']
|
|
560
|
+
g.feed(out.inputs['Surface'], surface.outputs[0])
|
|
561
|
+
return mat
|
|
562
|
+
# Cel shading: the light on white, in bands, times the colour (R4).
|
|
563
|
+
surface.inputs['Base Color'].default_value = (1.0, 1.0, 1.0, 1.0)
|
|
564
|
+
surface.inputs['Specular IOR Level'].default_value = 0.5 if shiny else 0.0
|
|
565
|
+
lit = g.node('ShaderNodeShaderToRGB')
|
|
566
|
+
g.feed(lit.inputs[0], surface.outputs[0])
|
|
567
|
+
ramp = g.node('ShaderNodeValToRGB')
|
|
568
|
+
ramp.color_ramp.interpolation = 'CONSTANT'
|
|
569
|
+
bands = [(0.0, (0.58, 0.6, 0.7)), (0.2, (1.0, 1.0, 1.0))] if shading == 'flat' else \
|
|
570
|
+
[(0.0, (0.5, 0.52, 0.62)), (0.15, (0.76, 0.77, 0.84)), (0.5, (1.0, 1.0, 1.0))]
|
|
571
|
+
if shiny:
|
|
572
|
+
bands.append((0.92, (1.3, 1.3, 1.3)))
|
|
573
|
+
elements = ramp.color_ramp.elements
|
|
574
|
+
while len(elements) < len(bands):
|
|
575
|
+
elements.new(0.99)
|
|
576
|
+
for element, (at, tone) in zip(elements, bands):
|
|
577
|
+
element.position = at
|
|
578
|
+
element.color = tone + (1.0,)
|
|
579
|
+
g.feed(ramp.inputs[0], lit.outputs[0])
|
|
580
|
+
toned = g.mix('MULTIPLY', 1.0, col, ramp.outputs[0])
|
|
581
|
+
if d['glow'] > 0:
|
|
582
|
+
toned = g.mix('ADD', d['glow'], toned, col)
|
|
583
|
+
emit = g.node('ShaderNodeEmission')
|
|
584
|
+
g.feed(emit.inputs[0], toned)
|
|
585
|
+
emit.inputs[1].default_value = 1.0
|
|
586
|
+
g.feed(out.inputs['Surface'], emit.outputs[0])
|
|
587
|
+
return mat
|
|
588
|
+
|
|
589
|
+
def lights(self):
|
|
590
|
+
style = self.job['style']
|
|
591
|
+
theta = math.radians(style['light'])
|
|
592
|
+
# From where the Style says on screen, a little toward the camera, so fronts are lit.
|
|
593
|
+
toward = Vector((math.sin(theta), -0.8, math.cos(theta))).normalized()
|
|
594
|
+
sun = bpy.data.lights.new('gm-sun', 'SUN')
|
|
595
|
+
sun.energy = 3.0 if style['shading'] == 'soft' else 3.2
|
|
596
|
+
sun.angle = math.radians(4)
|
|
597
|
+
ob = link(bpy.data.objects.new('gm-sun', sun))
|
|
598
|
+
ob.rotation_mode = 'QUATERNION'
|
|
599
|
+
ob.rotation_quaternion = toward.to_track_quat('Z', 'Y')
|
|
600
|
+
world = bpy.data.worlds.new('gm-world')
|
|
601
|
+
world.use_nodes = True
|
|
602
|
+
self.scene.world = world
|
|
603
|
+
background = world.node_tree.nodes['Background']
|
|
604
|
+
sky = [(a + b) / 2 for a, b in zip(style['sky']['top'], style['sky']['horizon'])]
|
|
605
|
+
background.inputs['Color'].default_value = linear(sky) + (1.0,)
|
|
606
|
+
background.inputs['Strength'].default_value = 0.55 if style['shading'] == 'soft' else 0.0
|
|
607
|
+
|
|
608
|
+
def outline(self):
|
|
609
|
+
line = self.job['style']['outline']
|
|
610
|
+
render = self.scene.render
|
|
611
|
+
render.use_freestyle = line is not None
|
|
612
|
+
if line is None:
|
|
613
|
+
return
|
|
614
|
+
render.line_thickness_mode = 'ABSOLUTE'
|
|
615
|
+
settings = bpy.context.view_layer.freestyle_settings
|
|
616
|
+
settings.crease_angle = math.radians(134)
|
|
617
|
+
lines = settings.linesets[0] if len(settings.linesets) > 0 else settings.linesets.new('gm-lines')
|
|
618
|
+
lines.select_by_visibility = True
|
|
619
|
+
lines.select_by_edge_types = True
|
|
620
|
+
lines.select_silhouette = True
|
|
621
|
+
lines.select_border = True
|
|
622
|
+
lines.select_crease = True
|
|
623
|
+
lines.select_external_contour = True
|
|
624
|
+
# Glowing parts carry no outline.
|
|
625
|
+
lines.select_by_collection = True
|
|
626
|
+
lines.collection = self.unlined
|
|
627
|
+
lines.collection_negation = 'EXCLUSIVE'
|
|
628
|
+
if lines.linestyle is None:
|
|
629
|
+
lines.linestyle = bpy.data.linestyles.new('gm-line')
|
|
630
|
+
lines.linestyle.color = linear(line['colour'])
|
|
631
|
+
lines.linestyle.thickness = line['px']
|
|
632
|
+
|
|
633
|
+
def pose(self, frame):
|
|
634
|
+
place(self.model, frame['model'])
|
|
635
|
+
for key, ob in self.objects.items():
|
|
636
|
+
place(ob, frame['objects'].get(str(key), self.rest[key]))
|
|
637
|
+
if frame.get('grounded'):
|
|
638
|
+
self.ground()
|
|
639
|
+
|
|
640
|
+
def ground(self):
|
|
641
|
+
"""Its lowest drawn point on the ground line, measured on the shapes as
|
|
642
|
+
built, after rounding, smoothing and cuts (phase 2, R2). In a tilted view a
|
|
643
|
+
foot further back is drawn higher, so the drawn point is the one that stands
|
|
644
|
+
(the final review); seen from the top, the lowest point in the model's frame."""
|
|
645
|
+
bpy.context.view_layer.update()
|
|
646
|
+
graph = bpy.context.evaluated_depsgraph_get()
|
|
647
|
+
# How far a step up the model's y moves it up the picture: 1 untilted, 0 from the top.
|
|
648
|
+
rise = (self.model.parent.matrix_world.to_3x3() @ Vector((0.0, 1.0, 0.0))).z
|
|
649
|
+
flat = rise < 0.05
|
|
650
|
+
to_ground = self.model.parent.matrix_world.inverted() if flat else Matrix.Identity(4)
|
|
651
|
+
row = 1 if flat else 2
|
|
652
|
+
low = math.inf
|
|
653
|
+
for ob in self.objects.values():
|
|
654
|
+
if ob.type != 'MESH' or ob.hide_render:
|
|
655
|
+
continue
|
|
656
|
+
seen = ob.evaluated_get(graph)
|
|
657
|
+
count = len(seen.data.vertices)
|
|
658
|
+
if count == 0:
|
|
659
|
+
continue
|
|
660
|
+
co = numpy.empty(count * 3, dtype=numpy.float32)
|
|
661
|
+
seen.data.vertices.foreach_get('co', co)
|
|
662
|
+
m = numpy.array(to_ground @ seen.matrix_world, dtype=numpy.float64)
|
|
663
|
+
ys = co.reshape(count, 3).astype(numpy.float64) @ m[row, :3] + m[row, 3]
|
|
664
|
+
low = min(low, float(ys.min()))
|
|
665
|
+
if low < math.inf:
|
|
666
|
+
self.model.location.y -= low if flat else low / rise
|
|
667
|
+
|
|
668
|
+
def measure(self):
|
|
669
|
+
"""The model's extent over every frame, as the camera sees it, and the frame that holds it:
|
|
670
|
+
None with nothing to draw, 'cut' when its cuts take all of it."""
|
|
671
|
+
shown = [ob for ob in self.objects.values() if ob.type == 'MESH' and not ob.hide_render]
|
|
672
|
+
if not shown:
|
|
673
|
+
return None
|
|
674
|
+
left = bottom = math.inf
|
|
675
|
+
right = top = -math.inf
|
|
676
|
+
drawn = False
|
|
677
|
+
for frame in self.job['frames']:
|
|
678
|
+
self.pose(frame)
|
|
679
|
+
bpy.context.view_layer.update()
|
|
680
|
+
graph = bpy.context.evaluated_depsgraph_get()
|
|
681
|
+
for ob in shown:
|
|
682
|
+
seen = ob.evaluated_get(graph)
|
|
683
|
+
# A part its cuts take all of has no surface left, and its box is
|
|
684
|
+
# a point at its origin: it is not drawn (the final review).
|
|
685
|
+
if len(seen.data.vertices) == 0:
|
|
686
|
+
continue
|
|
687
|
+
drawn = True
|
|
688
|
+
for corner in seen.bound_box:
|
|
689
|
+
at = seen.matrix_world @ Vector(corner)
|
|
690
|
+
left, right = min(left, at.x), max(right, at.x)
|
|
691
|
+
bottom, top = min(bottom, at.z), max(top, at.z)
|
|
692
|
+
if not drawn:
|
|
693
|
+
return 'cut'
|
|
694
|
+
ppt = self.job['pixelsPerTile']
|
|
695
|
+
while True:
|
|
696
|
+
margin = self.job['marginPx'] / ppt
|
|
697
|
+
half = max(abs(left), abs(right)) + margin
|
|
698
|
+
low = min(0.0, bottom) - margin
|
|
699
|
+
high = top + margin
|
|
700
|
+
w, h = math.ceil(2 * half * ppt), math.ceil((high - low) * ppt)
|
|
701
|
+
if (w <= self.job['maxFrame'] and h <= self.job['maxFrame']) or ppt <= 4:
|
|
702
|
+
return {'w': max(1, w), 'h': max(1, h), 'ppt': ppt, 'half': w / (2 * ppt), 'bottom': low, 'top': low + h / ppt}
|
|
703
|
+
ppt -= 1
|
|
704
|
+
|
|
705
|
+
def camera(self, frame):
|
|
706
|
+
cam = bpy.data.cameras.new('gm-camera')
|
|
707
|
+
cam.type = 'ORTHO'
|
|
708
|
+
cam.ortho_scale = max(frame['w'], frame['h']) / frame['ppt']
|
|
709
|
+
cam.clip_start = 0.01
|
|
710
|
+
cam.clip_end = 1000.0
|
|
711
|
+
ob = link(bpy.data.objects.new('gm-camera', cam))
|
|
712
|
+
ob.rotation_mode = 'XYZ'
|
|
713
|
+
ob.rotation_euler = (math.radians(90), 0, 0)
|
|
714
|
+
ob.location = (0.0, -200.0, (frame['bottom'] + frame['top']) / 2)
|
|
715
|
+
self.scene.camera = ob
|
|
716
|
+
self.scene.render.resolution_x = frame['w']
|
|
717
|
+
self.scene.render.resolution_y = frame['h']
|
|
718
|
+
|
|
719
|
+
|
|
720
|
+
def write_result(out, result):
|
|
721
|
+
with open(os.path.join(out, 'result.json'), 'w') as f:
|
|
722
|
+
json.dump(result, f)
|
|
723
|
+
|
|
724
|
+
|
|
725
|
+
def main():
|
|
726
|
+
job_path, out = sys.argv[sys.argv.index('--') + 1:][:2]
|
|
727
|
+
with open(job_path) as f:
|
|
728
|
+
job = json.load(f)
|
|
729
|
+
started = time.time()
|
|
730
|
+
result = Builder(job, out).run()
|
|
731
|
+
result['ms'] = int((time.time() - started) * 1000)
|
|
732
|
+
write_result(out, result)
|
|
733
|
+
|
|
734
|
+
|
|
735
|
+
if __name__ == '__main__':
|
|
736
|
+
try:
|
|
737
|
+
main()
|
|
738
|
+
except Exception as error:
|
|
739
|
+
# The run says what stopped it, in words, then fails.
|
|
740
|
+
write_result(sys.argv[sys.argv.index('--') + 1:][1], {'ok': False, 'reason': '%s: %s' % (type(error).__name__, error)})
|
|
741
|
+
raise
|