gamesteel 0.2.0 → 0.3.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.
Files changed (469) hide show
  1. package/README.md +6 -5
  2. package/dist/gamesteel.js +123138 -1609
  3. package/dist/pkgs/art/catalogue.json +153 -0
  4. package/dist/pkgs/art/library/new-platformer/License.txt +28 -0
  5. package/dist/pkgs/art/library/new-platformer/bee.png +0 -0
  6. package/dist/pkgs/art/library/new-platformer/block-bang.png +0 -0
  7. package/dist/pkgs/art/library/new-platformer/block-coin.png +0 -0
  8. package/dist/pkgs/art/library/new-platformer/block.png +0 -0
  9. package/dist/pkgs/art/library/new-platformer/bomb.png +0 -0
  10. package/dist/pkgs/art/library/new-platformer/bush.png +0 -0
  11. package/dist/pkgs/art/library/new-platformer/cactus.png +0 -0
  12. package/dist/pkgs/art/library/new-platformer/clouds.png +0 -0
  13. package/dist/pkgs/art/library/new-platformer/coin-bronze.png +0 -0
  14. package/dist/pkgs/art/library/new-platformer/coin-silver.png +0 -0
  15. package/dist/pkgs/art/library/new-platformer/coin.png +0 -0
  16. package/dist/pkgs/art/library/new-platformer/crate.png +0 -0
  17. package/dist/pkgs/art/library/new-platformer/desert.png +0 -0
  18. package/dist/pkgs/art/library/new-platformer/dirt.png +0 -0
  19. package/dist/pkgs/art/library/new-platformer/fireball.png +0 -0
  20. package/dist/pkgs/art/library/new-platformer/fish.png +0 -0
  21. package/dist/pkgs/art/library/new-platformer/flag.png +0 -0
  22. package/dist/pkgs/art/library/new-platformer/fly.png +0 -0
  23. package/dist/pkgs/art/library/new-platformer/frog.png +0 -0
  24. package/dist/pkgs/art/library/new-platformer/gem-blue.png +0 -0
  25. package/dist/pkgs/art/library/new-platformer/gem-green.png +0 -0
  26. package/dist/pkgs/art/library/new-platformer/gem-red.png +0 -0
  27. package/dist/pkgs/art/library/new-platformer/gem-yellow.png +0 -0
  28. package/dist/pkgs/art/library/new-platformer/grass.png +0 -0
  29. package/dist/pkgs/art/library/new-platformer/heart.png +0 -0
  30. package/dist/pkgs/art/library/new-platformer/hero-beige.png +0 -0
  31. package/dist/pkgs/art/library/new-platformer/hero-green.png +0 -0
  32. package/dist/pkgs/art/library/new-platformer/hero-pink.png +0 -0
  33. package/dist/pkgs/art/library/new-platformer/hero-purple.png +0 -0
  34. package/dist/pkgs/art/library/new-platformer/hero-yellow.png +0 -0
  35. package/dist/pkgs/art/library/new-platformer/hills.png +0 -0
  36. package/dist/pkgs/art/library/new-platformer/key-blue.png +0 -0
  37. package/dist/pkgs/art/library/new-platformer/key-yellow.png +0 -0
  38. package/dist/pkgs/art/library/new-platformer/ladder.png +0 -0
  39. package/dist/pkgs/art/library/new-platformer/ladybug.png +0 -0
  40. package/dist/pkgs/art/library/new-platformer/lever.png +0 -0
  41. package/dist/pkgs/art/library/new-platformer/lock-blue.png +0 -0
  42. package/dist/pkgs/art/library/new-platformer/mouse.png +0 -0
  43. package/dist/pkgs/art/library/new-platformer/mushroom.png +0 -0
  44. package/dist/pkgs/art/library/new-platformer/mushrooms.png +0 -0
  45. package/dist/pkgs/art/library/new-platformer/purple.png +0 -0
  46. package/dist/pkgs/art/library/new-platformer/rock.png +0 -0
  47. package/dist/pkgs/art/library/new-platformer/sand.png +0 -0
  48. package/dist/pkgs/art/library/new-platformer/saw.png +0 -0
  49. package/dist/pkgs/art/library/new-platformer/sign-exit.png +0 -0
  50. package/dist/pkgs/art/library/new-platformer/sign-left.png +0 -0
  51. package/dist/pkgs/art/library/new-platformer/sign-right.png +0 -0
  52. package/dist/pkgs/art/library/new-platformer/sky.png +0 -0
  53. package/dist/pkgs/art/library/new-platformer/slime-fire.png +0 -0
  54. package/dist/pkgs/art/library/new-platformer/slime-spike.png +0 -0
  55. package/dist/pkgs/art/library/new-platformer/slime.png +0 -0
  56. package/dist/pkgs/art/library/new-platformer/snail.png +0 -0
  57. package/dist/pkgs/art/library/new-platformer/snow.png +0 -0
  58. package/dist/pkgs/art/library/new-platformer/spikes.png +0 -0
  59. package/dist/pkgs/art/library/new-platformer/spring.png +0 -0
  60. package/dist/pkgs/art/library/new-platformer/star.png +0 -0
  61. package/dist/pkgs/art/library/new-platformer/stone.png +0 -0
  62. package/dist/pkgs/art/library/new-platformer/torch.png +0 -0
  63. package/dist/pkgs/art/library/new-platformer/trees.png +0 -0
  64. package/dist/pkgs/art/library/new-platformer/worm.png +0 -0
  65. package/dist/pkgs/art/library/pixel-platformer/License.txt +28 -0
  66. package/dist/pkgs/art/library/pixel-platformer/angry-block.png +0 -0
  67. package/dist/pkgs/art/library/pixel-platformer/astronaut-beige.png +0 -0
  68. package/dist/pkgs/art/library/pixel-platformer/astronaut-blue.png +0 -0
  69. package/dist/pkgs/art/library/pixel-platformer/astronaut-green.png +0 -0
  70. package/dist/pkgs/art/library/pixel-platformer/astronaut-pink.png +0 -0
  71. package/dist/pkgs/art/library/pixel-platformer/astronaut-yellow.png +0 -0
  72. package/dist/pkgs/art/library/pixel-platformer/bat.png +0 -0
  73. package/dist/pkgs/art/library/pixel-platformer/block-bang.png +0 -0
  74. package/dist/pkgs/art/library/pixel-platformer/block-coin.png +0 -0
  75. package/dist/pkgs/art/library/pixel-platformer/block-yellow.png +0 -0
  76. package/dist/pkgs/art/library/pixel-platformer/cactus.png +0 -0
  77. package/dist/pkgs/art/library/pixel-platformer/coin.png +0 -0
  78. package/dist/pkgs/art/library/pixel-platformer/crate.png +0 -0
  79. package/dist/pkgs/art/library/pixel-platformer/crawler-bot.png +0 -0
  80. package/dist/pkgs/art/library/pixel-platformer/desert.png +0 -0
  81. package/dist/pkgs/art/library/pixel-platformer/drill-bot.png +0 -0
  82. package/dist/pkgs/art/library/pixel-platformer/flag.png +0 -0
  83. package/dist/pkgs/art/library/pixel-platformer/forest.png +0 -0
  84. package/dist/pkgs/art/library/pixel-platformer/gem.png +0 -0
  85. package/dist/pkgs/art/library/pixel-platformer/gold-imp.png +0 -0
  86. package/dist/pkgs/art/library/pixel-platformer/grass.png +0 -0
  87. package/dist/pkgs/art/library/pixel-platformer/heart-empty.png +0 -0
  88. package/dist/pkgs/art/library/pixel-platformer/heart-half.png +0 -0
  89. package/dist/pkgs/art/library/pixel-platformer/heart.png +0 -0
  90. package/dist/pkgs/art/library/pixel-platformer/key.png +0 -0
  91. package/dist/pkgs/art/library/pixel-platformer/ladder.png +0 -0
  92. package/dist/pkgs/art/library/pixel-platformer/lever.png +0 -0
  93. package/dist/pkgs/art/library/pixel-platformer/mushroom.png +0 -0
  94. package/dist/pkgs/art/library/pixel-platformer/sand.png +0 -0
  95. package/dist/pkgs/art/library/pixel-platformer/sign-left.png +0 -0
  96. package/dist/pkgs/art/library/pixel-platformer/sign-right.png +0 -0
  97. package/dist/pkgs/art/library/pixel-platformer/sky.png +0 -0
  98. package/dist/pkgs/art/library/pixel-platformer/snow.png +0 -0
  99. package/dist/pkgs/art/library/pixel-platformer/snowman.png +0 -0
  100. package/dist/pkgs/art/library/pixel-platformer/spikes.png +0 -0
  101. package/dist/pkgs/art/library/pixel-platformer/spiky-ball.png +0 -0
  102. package/dist/pkgs/art/library/pixel-platformer/spring.png +0 -0
  103. package/dist/pkgs/art/library/pixel-platformer/sprout.png +0 -0
  104. package/dist/pkgs/art/library/pixel-platformer/tank-bot.png +0 -0
  105. package/dist/pkgs/art/library/pixel-platformer/tree.png +0 -0
  106. package/dist/pkgs/art/library/puzzle/License.txt +21 -0
  107. package/dist/pkgs/art/library/puzzle/ball-blue.png +0 -0
  108. package/dist/pkgs/art/library/puzzle/ball-grey.png +0 -0
  109. package/dist/pkgs/art/library/puzzle/brick-blue.png +0 -0
  110. package/dist/pkgs/art/library/puzzle/brick-green.png +0 -0
  111. package/dist/pkgs/art/library/puzzle/brick-grey.png +0 -0
  112. package/dist/pkgs/art/library/puzzle/brick-purple.png +0 -0
  113. package/dist/pkgs/art/library/puzzle/brick-red.png +0 -0
  114. package/dist/pkgs/art/library/puzzle/brick-yellow.png +0 -0
  115. package/dist/pkgs/art/library/puzzle/diamond-blue.png +0 -0
  116. package/dist/pkgs/art/library/puzzle/diamond-green.png +0 -0
  117. package/dist/pkgs/art/library/puzzle/diamond-grey.png +0 -0
  118. package/dist/pkgs/art/library/puzzle/diamond-purple.png +0 -0
  119. package/dist/pkgs/art/library/puzzle/diamond-red.png +0 -0
  120. package/dist/pkgs/art/library/puzzle/diamond-yellow.png +0 -0
  121. package/dist/pkgs/art/library/puzzle/paddle-blue.png +0 -0
  122. package/dist/pkgs/art/library/puzzle/paddle-red.png +0 -0
  123. package/dist/pkgs/art/library/puzzle/star.png +0 -0
  124. package/dist/pkgs/art/library/tiny-dungeon/License.txt +22 -0
  125. package/dist/pkgs/art/library/tiny-dungeon/adventurer.png +0 -0
  126. package/dist/pkgs/art/library/tiny-dungeon/axe.png +0 -0
  127. package/dist/pkgs/art/library/tiny-dungeon/barrel.png +0 -0
  128. package/dist/pkgs/art/library/tiny-dungeon/bat.png +0 -0
  129. package/dist/pkgs/art/library/tiny-dungeon/chest-open.png +0 -0
  130. package/dist/pkgs/art/library/tiny-dungeon/chest.png +0 -0
  131. package/dist/pkgs/art/library/tiny-dungeon/cyclops.png +0 -0
  132. package/dist/pkgs/art/library/tiny-dungeon/dagger.png +0 -0
  133. package/dist/pkgs/art/library/tiny-dungeon/dark-wizard.png +0 -0
  134. package/dist/pkgs/art/library/tiny-dungeon/demon.png +0 -0
  135. package/dist/pkgs/art/library/tiny-dungeon/elder.png +0 -0
  136. package/dist/pkgs/art/library/tiny-dungeon/ghost.png +0 -0
  137. package/dist/pkgs/art/library/tiny-dungeon/guard.png +0 -0
  138. package/dist/pkgs/art/library/tiny-dungeon/hammer.png +0 -0
  139. package/dist/pkgs/art/library/tiny-dungeon/knight.png +0 -0
  140. package/dist/pkgs/art/library/tiny-dungeon/mimic.png +0 -0
  141. package/dist/pkgs/art/library/tiny-dungeon/monk.png +0 -0
  142. package/dist/pkgs/art/library/tiny-dungeon/orc.png +0 -0
  143. package/dist/pkgs/art/library/tiny-dungeon/potion-blue.png +0 -0
  144. package/dist/pkgs/art/library/tiny-dungeon/potion-green.png +0 -0
  145. package/dist/pkgs/art/library/tiny-dungeon/potion-red.png +0 -0
  146. package/dist/pkgs/art/library/tiny-dungeon/princess.png +0 -0
  147. package/dist/pkgs/art/library/tiny-dungeon/shield.png +0 -0
  148. package/dist/pkgs/art/library/tiny-dungeon/slime.png +0 -0
  149. package/dist/pkgs/art/library/tiny-dungeon/snake.png +0 -0
  150. package/dist/pkgs/art/library/tiny-dungeon/soldier.png +0 -0
  151. package/dist/pkgs/art/library/tiny-dungeon/spider.png +0 -0
  152. package/dist/pkgs/art/library/tiny-dungeon/staff.png +0 -0
  153. package/dist/pkgs/art/library/tiny-dungeon/stone-wall.png +0 -0
  154. package/dist/pkgs/art/library/tiny-dungeon/sword.png +0 -0
  155. package/dist/pkgs/art/library/tiny-dungeon/viking.png +0 -0
  156. package/dist/pkgs/art/library/tiny-dungeon/villager.png +0 -0
  157. package/dist/pkgs/art/library/tiny-dungeon/wizard.png +0 -0
  158. package/dist/pkgs/art/library/tiny-dungeon/worm.png +0 -0
  159. package/dist/pkgs/artgen/dist/draw-worker.js +1574 -0
  160. package/dist/pkgs/audio/dist/worker.js +76965 -0
  161. package/dist/pkgs/fonts/files/bungee.ttf +0 -0
  162. package/dist/pkgs/fonts/files/cinzel.ttf +0 -0
  163. package/dist/pkgs/fonts/files/fredoka.ttf +0 -0
  164. package/dist/pkgs/fonts/files/inter.ttf +0 -0
  165. package/dist/pkgs/fonts/files/lilita-one.ttf +0 -0
  166. package/dist/pkgs/fonts/files/luckiest-guy.ttf +0 -0
  167. package/dist/pkgs/fonts/files/medievalsharp.ttf +0 -0
  168. package/dist/pkgs/fonts/files/orbitron.ttf +0 -0
  169. package/dist/pkgs/fonts/files/patrick-hand.ttf +0 -0
  170. package/dist/pkgs/fonts/files/pixelify.ttf +0 -0
  171. package/dist/pkgs/fonts/files/press-start.ttf +0 -0
  172. package/dist/pkgs/fonts/files/russo-one.ttf +0 -0
  173. package/dist/pkgs/fonts/files/silkscreen.ttf +0 -0
  174. package/dist/pkgs/fonts/files/varela-round.ttf +0 -0
  175. package/dist/pkgs/fonts/licences/bungee.txt +93 -0
  176. package/dist/pkgs/fonts/licences/cinzel.txt +94 -0
  177. package/dist/pkgs/fonts/licences/fredoka.txt +93 -0
  178. package/dist/pkgs/fonts/licences/inter.txt +92 -0
  179. package/dist/pkgs/fonts/licences/lilita-one.txt +94 -0
  180. package/dist/pkgs/fonts/licences/luckiest-guy.txt +202 -0
  181. package/dist/pkgs/fonts/licences/medievalsharp.txt +105 -0
  182. package/dist/pkgs/fonts/licences/orbitron.txt +105 -0
  183. package/dist/pkgs/fonts/licences/patrick-hand.txt +93 -0
  184. package/dist/pkgs/fonts/licences/pixelify.txt +93 -0
  185. package/dist/pkgs/fonts/licences/press-start.txt +93 -0
  186. package/dist/pkgs/fonts/licences/russo-one.txt +93 -0
  187. package/dist/pkgs/fonts/licences/silkscreen.txt +93 -0
  188. package/dist/pkgs/fonts/licences/varela-round.txt +93 -0
  189. package/dist/pkgs/instruments/licences/musescore-general.txt +92 -0
  190. package/dist/pkgs/model/blender/builder.py +741 -0
  191. package/dist/pkgs/model/dist/worker.js +58006 -0
  192. package/dist/pkgs/paint/dist/worker.js +56328 -0
  193. package/dist/pkgs/soundlib/catalogue.json +2606 -0
  194. package/dist/pkgs/soundlib/library/kenney-digital/high-down.wav +0 -0
  195. package/dist/pkgs/soundlib/library/kenney-digital/high-up.wav +0 -0
  196. package/dist/pkgs/soundlib/library/kenney-digital/laser-1.wav +0 -0
  197. package/dist/pkgs/soundlib/library/kenney-digital/laser-2.wav +0 -0
  198. package/dist/pkgs/soundlib/library/kenney-digital/laser-3.wav +0 -0
  199. package/dist/pkgs/soundlib/library/kenney-digital/low-down.wav +0 -0
  200. package/dist/pkgs/soundlib/library/kenney-digital/low-random.wav +0 -0
  201. package/dist/pkgs/soundlib/library/kenney-digital/low-three-tone.wav +0 -0
  202. package/dist/pkgs/soundlib/library/kenney-digital/pep-sound-1.wav +0 -0
  203. package/dist/pkgs/soundlib/library/kenney-digital/pep-sound-2.wav +0 -0
  204. package/dist/pkgs/soundlib/library/kenney-digital/pep-sound-3.wav +0 -0
  205. package/dist/pkgs/soundlib/library/kenney-digital/phase-jump-1.wav +0 -0
  206. package/dist/pkgs/soundlib/library/kenney-digital/phase-jump-2.wav +0 -0
  207. package/dist/pkgs/soundlib/library/kenney-digital/phase-jump-3.wav +0 -0
  208. package/dist/pkgs/soundlib/library/kenney-digital/phaser-down-1.wav +0 -0
  209. package/dist/pkgs/soundlib/library/kenney-digital/phaser-down-2.wav +0 -0
  210. package/dist/pkgs/soundlib/library/kenney-digital/phaser-down-3.wav +0 -0
  211. package/dist/pkgs/soundlib/library/kenney-digital/phaser-up-1.wav +0 -0
  212. package/dist/pkgs/soundlib/library/kenney-digital/phaser-up-2.wav +0 -0
  213. package/dist/pkgs/soundlib/library/kenney-digital/phaser-up-3.wav +0 -0
  214. package/dist/pkgs/soundlib/library/kenney-digital/power-up-1.wav +0 -0
  215. package/dist/pkgs/soundlib/library/kenney-digital/power-up-10.wav +0 -0
  216. package/dist/pkgs/soundlib/library/kenney-digital/power-up-11.wav +0 -0
  217. package/dist/pkgs/soundlib/library/kenney-digital/space-trash-1.wav +0 -0
  218. package/dist/pkgs/soundlib/library/kenney-digital/space-trash-2.wav +0 -0
  219. package/dist/pkgs/soundlib/library/kenney-digital/space-trash-3.wav +0 -0
  220. package/dist/pkgs/soundlib/library/kenney-digital/three-tone-1.wav +0 -0
  221. package/dist/pkgs/soundlib/library/kenney-digital/three-tone-2.wav +0 -0
  222. package/dist/pkgs/soundlib/library/kenney-digital/tone-1.wav +0 -0
  223. package/dist/pkgs/soundlib/library/kenney-digital/two-tone-1.wav +0 -0
  224. package/dist/pkgs/soundlib/library/kenney-digital/two-tone-2.wav +0 -0
  225. package/dist/pkgs/soundlib/library/kenney-digital/zap-1.wav +0 -0
  226. package/dist/pkgs/soundlib/library/kenney-digital/zap-2.wav +0 -0
  227. package/dist/pkgs/soundlib/library/kenney-digital/zap-three-tone-down.wav +0 -0
  228. package/dist/pkgs/soundlib/library/kenney-digital/zap-three-tone-up.wav +0 -0
  229. package/dist/pkgs/soundlib/library/kenney-digital/zap-two-tone-2.wav +0 -0
  230. package/dist/pkgs/soundlib/library/kenney-digital/zap-two-tone.wav +0 -0
  231. package/dist/pkgs/soundlib/library/kenney-impact/footstep-carpet-000.wav +0 -0
  232. package/dist/pkgs/soundlib/library/kenney-impact/footstep-carpet-001.wav +0 -0
  233. package/dist/pkgs/soundlib/library/kenney-impact/footstep-carpet-002.wav +0 -0
  234. package/dist/pkgs/soundlib/library/kenney-impact/footstep-concrete-000.wav +0 -0
  235. package/dist/pkgs/soundlib/library/kenney-impact/footstep-concrete-001.wav +0 -0
  236. package/dist/pkgs/soundlib/library/kenney-impact/footstep-concrete-002.wav +0 -0
  237. package/dist/pkgs/soundlib/library/kenney-impact/footstep-grass-000.wav +0 -0
  238. package/dist/pkgs/soundlib/library/kenney-impact/footstep-grass-001.wav +0 -0
  239. package/dist/pkgs/soundlib/library/kenney-impact/footstep-grass-002.wav +0 -0
  240. package/dist/pkgs/soundlib/library/kenney-impact/footstep-snow-000.wav +0 -0
  241. package/dist/pkgs/soundlib/library/kenney-impact/footstep-snow-001.wav +0 -0
  242. package/dist/pkgs/soundlib/library/kenney-impact/footstep-snow-002.wav +0 -0
  243. package/dist/pkgs/soundlib/library/kenney-impact/footstep-wood-000.wav +0 -0
  244. package/dist/pkgs/soundlib/library/kenney-impact/footstep-wood-001.wav +0 -0
  245. package/dist/pkgs/soundlib/library/kenney-impact/footstep-wood-002.wav +0 -0
  246. package/dist/pkgs/soundlib/library/kenney-impact/impact-bell-heavy-000.wav +0 -0
  247. package/dist/pkgs/soundlib/library/kenney-impact/impact-bell-heavy-001.wav +0 -0
  248. package/dist/pkgs/soundlib/library/kenney-impact/impact-bell-heavy-002.wav +0 -0
  249. package/dist/pkgs/soundlib/library/kenney-impact/impact-generic-light-000.wav +0 -0
  250. package/dist/pkgs/soundlib/library/kenney-impact/impact-generic-light-001.wav +0 -0
  251. package/dist/pkgs/soundlib/library/kenney-impact/impact-generic-light-002.wav +0 -0
  252. package/dist/pkgs/soundlib/library/kenney-impact/impact-glass-heavy-000.wav +0 -0
  253. package/dist/pkgs/soundlib/library/kenney-impact/impact-glass-heavy-001.wav +0 -0
  254. package/dist/pkgs/soundlib/library/kenney-impact/impact-glass-heavy-002.wav +0 -0
  255. package/dist/pkgs/soundlib/library/kenney-impact/impact-glass-light-000.wav +0 -0
  256. package/dist/pkgs/soundlib/library/kenney-impact/impact-glass-light-001.wav +0 -0
  257. package/dist/pkgs/soundlib/library/kenney-impact/impact-glass-light-002.wav +0 -0
  258. package/dist/pkgs/soundlib/library/kenney-impact/impact-glass-medium-000.wav +0 -0
  259. package/dist/pkgs/soundlib/library/kenney-impact/impact-glass-medium-001.wav +0 -0
  260. package/dist/pkgs/soundlib/library/kenney-impact/impact-glass-medium-002.wav +0 -0
  261. package/dist/pkgs/soundlib/library/kenney-impact/impact-metal-heavy-000.wav +0 -0
  262. package/dist/pkgs/soundlib/library/kenney-impact/impact-metal-heavy-001.wav +0 -0
  263. package/dist/pkgs/soundlib/library/kenney-impact/impact-metal-heavy-002.wav +0 -0
  264. package/dist/pkgs/soundlib/library/kenney-impact/impact-metal-light-000.wav +0 -0
  265. package/dist/pkgs/soundlib/library/kenney-impact/impact-metal-light-001.wav +0 -0
  266. package/dist/pkgs/soundlib/library/kenney-impact/impact-metal-light-002.wav +0 -0
  267. package/dist/pkgs/soundlib/library/kenney-impact/impact-metal-medium-000.wav +0 -0
  268. package/dist/pkgs/soundlib/library/kenney-impact/impact-metal-medium-001.wav +0 -0
  269. package/dist/pkgs/soundlib/library/kenney-impact/impact-metal-medium-002.wav +0 -0
  270. package/dist/pkgs/soundlib/library/kenney-impact/impact-mining-000.wav +0 -0
  271. package/dist/pkgs/soundlib/library/kenney-impact/impact-mining-001.wav +0 -0
  272. package/dist/pkgs/soundlib/library/kenney-impact/impact-mining-002.wav +0 -0
  273. package/dist/pkgs/soundlib/library/kenney-impact/impact-plank-medium-000.wav +0 -0
  274. package/dist/pkgs/soundlib/library/kenney-impact/impact-plank-medium-001.wav +0 -0
  275. package/dist/pkgs/soundlib/library/kenney-impact/impact-plank-medium-002.wav +0 -0
  276. package/dist/pkgs/soundlib/library/kenney-impact/impact-plate-heavy-000.wav +0 -0
  277. package/dist/pkgs/soundlib/library/kenney-impact/impact-plate-heavy-001.wav +0 -0
  278. package/dist/pkgs/soundlib/library/kenney-impact/impact-plate-heavy-002.wav +0 -0
  279. package/dist/pkgs/soundlib/library/kenney-impact/impact-plate-light-000.wav +0 -0
  280. package/dist/pkgs/soundlib/library/kenney-impact/impact-plate-light-001.wav +0 -0
  281. package/dist/pkgs/soundlib/library/kenney-impact/impact-plate-light-002.wav +0 -0
  282. package/dist/pkgs/soundlib/library/kenney-impact/impact-plate-medium-000.wav +0 -0
  283. package/dist/pkgs/soundlib/library/kenney-impact/impact-plate-medium-001.wav +0 -0
  284. package/dist/pkgs/soundlib/library/kenney-impact/impact-plate-medium-002.wav +0 -0
  285. package/dist/pkgs/soundlib/library/kenney-impact/impact-punch-heavy-000.wav +0 -0
  286. package/dist/pkgs/soundlib/library/kenney-impact/impact-punch-heavy-001.wav +0 -0
  287. package/dist/pkgs/soundlib/library/kenney-impact/impact-punch-heavy-002.wav +0 -0
  288. package/dist/pkgs/soundlib/library/kenney-impact/impact-punch-medium-000.wav +0 -0
  289. package/dist/pkgs/soundlib/library/kenney-impact/impact-punch-medium-001.wav +0 -0
  290. package/dist/pkgs/soundlib/library/kenney-impact/impact-punch-medium-002.wav +0 -0
  291. package/dist/pkgs/soundlib/library/kenney-impact/impact-soft-heavy-000.wav +0 -0
  292. package/dist/pkgs/soundlib/library/kenney-impact/impact-soft-heavy-001.wav +0 -0
  293. package/dist/pkgs/soundlib/library/kenney-impact/impact-soft-heavy-002.wav +0 -0
  294. package/dist/pkgs/soundlib/library/kenney-impact/impact-soft-medium-000.wav +0 -0
  295. package/dist/pkgs/soundlib/library/kenney-impact/impact-soft-medium-001.wav +0 -0
  296. package/dist/pkgs/soundlib/library/kenney-impact/impact-soft-medium-002.wav +0 -0
  297. package/dist/pkgs/soundlib/library/kenney-impact/impact-tin-medium-000.wav +0 -0
  298. package/dist/pkgs/soundlib/library/kenney-impact/impact-tin-medium-001.wav +0 -0
  299. package/dist/pkgs/soundlib/library/kenney-impact/impact-tin-medium-002.wav +0 -0
  300. package/dist/pkgs/soundlib/library/kenney-impact/impact-wood-heavy-000.wav +0 -0
  301. package/dist/pkgs/soundlib/library/kenney-impact/impact-wood-heavy-001.wav +0 -0
  302. package/dist/pkgs/soundlib/library/kenney-impact/impact-wood-heavy-002.wav +0 -0
  303. package/dist/pkgs/soundlib/library/kenney-impact/impact-wood-light-000.wav +0 -0
  304. package/dist/pkgs/soundlib/library/kenney-impact/impact-wood-light-001.wav +0 -0
  305. package/dist/pkgs/soundlib/library/kenney-impact/impact-wood-light-002.wav +0 -0
  306. package/dist/pkgs/soundlib/library/kenney-impact/impact-wood-medium-000.wav +0 -0
  307. package/dist/pkgs/soundlib/library/kenney-impact/impact-wood-medium-001.wav +0 -0
  308. package/dist/pkgs/soundlib/library/kenney-impact/impact-wood-medium-002.wav +0 -0
  309. package/dist/pkgs/soundlib/library/kenney-interface/back-001.wav +0 -0
  310. package/dist/pkgs/soundlib/library/kenney-interface/back-002.wav +0 -0
  311. package/dist/pkgs/soundlib/library/kenney-interface/back-003.wav +0 -0
  312. package/dist/pkgs/soundlib/library/kenney-interface/bong-001.wav +0 -0
  313. package/dist/pkgs/soundlib/library/kenney-interface/click-001.wav +0 -0
  314. package/dist/pkgs/soundlib/library/kenney-interface/click-002.wav +0 -0
  315. package/dist/pkgs/soundlib/library/kenney-interface/click-003.wav +0 -0
  316. package/dist/pkgs/soundlib/library/kenney-interface/close-001.wav +0 -0
  317. package/dist/pkgs/soundlib/library/kenney-interface/close-002.wav +0 -0
  318. package/dist/pkgs/soundlib/library/kenney-interface/close-003.wav +0 -0
  319. package/dist/pkgs/soundlib/library/kenney-interface/confirmation-001.wav +0 -0
  320. package/dist/pkgs/soundlib/library/kenney-interface/confirmation-002.wav +0 -0
  321. package/dist/pkgs/soundlib/library/kenney-interface/confirmation-003.wav +0 -0
  322. package/dist/pkgs/soundlib/library/kenney-interface/drop-001.wav +0 -0
  323. package/dist/pkgs/soundlib/library/kenney-interface/drop-002.wav +0 -0
  324. package/dist/pkgs/soundlib/library/kenney-interface/drop-003.wav +0 -0
  325. package/dist/pkgs/soundlib/library/kenney-interface/error-001.wav +0 -0
  326. package/dist/pkgs/soundlib/library/kenney-interface/error-002.wav +0 -0
  327. package/dist/pkgs/soundlib/library/kenney-interface/error-003.wav +0 -0
  328. package/dist/pkgs/soundlib/library/kenney-interface/glass-001.wav +0 -0
  329. package/dist/pkgs/soundlib/library/kenney-interface/glass-002.wav +0 -0
  330. package/dist/pkgs/soundlib/library/kenney-interface/glass-003.wav +0 -0
  331. package/dist/pkgs/soundlib/library/kenney-interface/glitch-001.wav +0 -0
  332. package/dist/pkgs/soundlib/library/kenney-interface/glitch-002.wav +0 -0
  333. package/dist/pkgs/soundlib/library/kenney-interface/glitch-003.wav +0 -0
  334. package/dist/pkgs/soundlib/library/kenney-interface/maximize-001.wav +0 -0
  335. package/dist/pkgs/soundlib/library/kenney-interface/maximize-002.wav +0 -0
  336. package/dist/pkgs/soundlib/library/kenney-interface/maximize-003.wav +0 -0
  337. package/dist/pkgs/soundlib/library/kenney-interface/minimize-001.wav +0 -0
  338. package/dist/pkgs/soundlib/library/kenney-interface/minimize-002.wav +0 -0
  339. package/dist/pkgs/soundlib/library/kenney-interface/minimize-003.wav +0 -0
  340. package/dist/pkgs/soundlib/library/kenney-interface/open-001.wav +0 -0
  341. package/dist/pkgs/soundlib/library/kenney-interface/open-002.wav +0 -0
  342. package/dist/pkgs/soundlib/library/kenney-interface/open-003.wav +0 -0
  343. package/dist/pkgs/soundlib/library/kenney-interface/pluck-001.wav +0 -0
  344. package/dist/pkgs/soundlib/library/kenney-interface/pluck-002.wav +0 -0
  345. package/dist/pkgs/soundlib/library/kenney-interface/question-001.wav +0 -0
  346. package/dist/pkgs/soundlib/library/kenney-interface/question-002.wav +0 -0
  347. package/dist/pkgs/soundlib/library/kenney-interface/question-003.wav +0 -0
  348. package/dist/pkgs/soundlib/library/kenney-interface/scratch-001.wav +0 -0
  349. package/dist/pkgs/soundlib/library/kenney-interface/scratch-002.wav +0 -0
  350. package/dist/pkgs/soundlib/library/kenney-interface/scratch-003.wav +0 -0
  351. package/dist/pkgs/soundlib/library/kenney-interface/scroll-001.wav +0 -0
  352. package/dist/pkgs/soundlib/library/kenney-interface/scroll-002.wav +0 -0
  353. package/dist/pkgs/soundlib/library/kenney-interface/scroll-003.wav +0 -0
  354. package/dist/pkgs/soundlib/library/kenney-interface/select-001.wav +0 -0
  355. package/dist/pkgs/soundlib/library/kenney-interface/select-002.wav +0 -0
  356. package/dist/pkgs/soundlib/library/kenney-interface/select-003.wav +0 -0
  357. package/dist/pkgs/soundlib/library/kenney-interface/switch-001.wav +0 -0
  358. package/dist/pkgs/soundlib/library/kenney-interface/switch-002.wav +0 -0
  359. package/dist/pkgs/soundlib/library/kenney-interface/switch-003.wav +0 -0
  360. package/dist/pkgs/soundlib/library/kenney-interface/tick-001.wav +0 -0
  361. package/dist/pkgs/soundlib/library/kenney-interface/tick-002.wav +0 -0
  362. package/dist/pkgs/soundlib/library/kenney-interface/tick-004.wav +0 -0
  363. package/dist/pkgs/soundlib/library/kenney-interface/toggle-001.wav +0 -0
  364. package/dist/pkgs/soundlib/library/kenney-interface/toggle-002.wav +0 -0
  365. package/dist/pkgs/soundlib/library/kenney-interface/toggle-003.wav +0 -0
  366. package/dist/pkgs/soundlib/library/kenney-rpg/belt-handle-1.wav +0 -0
  367. package/dist/pkgs/soundlib/library/kenney-rpg/belt-handle-2.wav +0 -0
  368. package/dist/pkgs/soundlib/library/kenney-rpg/book-close.wav +0 -0
  369. package/dist/pkgs/soundlib/library/kenney-rpg/book-flip-1.wav +0 -0
  370. package/dist/pkgs/soundlib/library/kenney-rpg/book-flip-2.wav +0 -0
  371. package/dist/pkgs/soundlib/library/kenney-rpg/book-flip-3.wav +0 -0
  372. package/dist/pkgs/soundlib/library/kenney-rpg/book-open.wav +0 -0
  373. package/dist/pkgs/soundlib/library/kenney-rpg/book-place-1.wav +0 -0
  374. package/dist/pkgs/soundlib/library/kenney-rpg/book-place-2.wav +0 -0
  375. package/dist/pkgs/soundlib/library/kenney-rpg/book-place-3.wav +0 -0
  376. package/dist/pkgs/soundlib/library/kenney-rpg/chop.wav +0 -0
  377. package/dist/pkgs/soundlib/library/kenney-rpg/cloth-1.wav +0 -0
  378. package/dist/pkgs/soundlib/library/kenney-rpg/cloth-2.wav +0 -0
  379. package/dist/pkgs/soundlib/library/kenney-rpg/cloth-3.wav +0 -0
  380. package/dist/pkgs/soundlib/library/kenney-rpg/cloth-belt-2.wav +0 -0
  381. package/dist/pkgs/soundlib/library/kenney-rpg/cloth-belt.wav +0 -0
  382. package/dist/pkgs/soundlib/library/kenney-rpg/creak-1.wav +0 -0
  383. package/dist/pkgs/soundlib/library/kenney-rpg/creak-2.wav +0 -0
  384. package/dist/pkgs/soundlib/library/kenney-rpg/creak-3.wav +0 -0
  385. package/dist/pkgs/soundlib/library/kenney-rpg/door-close-1.wav +0 -0
  386. package/dist/pkgs/soundlib/library/kenney-rpg/door-close-2.wav +0 -0
  387. package/dist/pkgs/soundlib/library/kenney-rpg/door-close-3.wav +0 -0
  388. package/dist/pkgs/soundlib/library/kenney-rpg/door-open-1.wav +0 -0
  389. package/dist/pkgs/soundlib/library/kenney-rpg/door-open-2.wav +0 -0
  390. package/dist/pkgs/soundlib/library/kenney-rpg/draw-knife-1.wav +0 -0
  391. package/dist/pkgs/soundlib/library/kenney-rpg/draw-knife-2.wav +0 -0
  392. package/dist/pkgs/soundlib/library/kenney-rpg/draw-knife-3.wav +0 -0
  393. package/dist/pkgs/soundlib/library/kenney-rpg/drop-leather.wav +0 -0
  394. package/dist/pkgs/soundlib/library/kenney-rpg/footstep-00.wav +0 -0
  395. package/dist/pkgs/soundlib/library/kenney-rpg/footstep-01.wav +0 -0
  396. package/dist/pkgs/soundlib/library/kenney-rpg/footstep-02.wav +0 -0
  397. package/dist/pkgs/soundlib/library/kenney-rpg/handle-coins-2.wav +0 -0
  398. package/dist/pkgs/soundlib/library/kenney-rpg/handle-coins.wav +0 -0
  399. package/dist/pkgs/soundlib/library/kenney-rpg/handle-small-leather-2.wav +0 -0
  400. package/dist/pkgs/soundlib/library/kenney-rpg/handle-small-leather.wav +0 -0
  401. package/dist/pkgs/soundlib/library/kenney-rpg/knife-slice-2.wav +0 -0
  402. package/dist/pkgs/soundlib/library/kenney-rpg/knife-slice.wav +0 -0
  403. package/dist/pkgs/soundlib/library/kenney-rpg/metal-click.wav +0 -0
  404. package/dist/pkgs/soundlib/library/kenney-rpg/metal-latch.wav +0 -0
  405. package/dist/pkgs/soundlib/library/kenney-rpg/metal-pot-1.wav +0 -0
  406. package/dist/pkgs/soundlib/library/kenney-rpg/metal-pot-2.wav +0 -0
  407. package/dist/pkgs/soundlib/library/kenney-rpg/metal-pot-3.wav +0 -0
  408. package/dist/pkgs/soundlib/library/kenney-scifi/computer-noise-000.wav +0 -0
  409. package/dist/pkgs/soundlib/library/kenney-scifi/computer-noise-001.wav +0 -0
  410. package/dist/pkgs/soundlib/library/kenney-scifi/computer-noise-002.wav +0 -0
  411. package/dist/pkgs/soundlib/library/kenney-scifi/door-close-000.wav +0 -0
  412. package/dist/pkgs/soundlib/library/kenney-scifi/door-close-001.wav +0 -0
  413. package/dist/pkgs/soundlib/library/kenney-scifi/door-close-002.wav +0 -0
  414. package/dist/pkgs/soundlib/library/kenney-scifi/door-open-000.wav +0 -0
  415. package/dist/pkgs/soundlib/library/kenney-scifi/door-open-001.wav +0 -0
  416. package/dist/pkgs/soundlib/library/kenney-scifi/door-open-002.wav +0 -0
  417. package/dist/pkgs/soundlib/library/kenney-scifi/explosion-crunch-000.wav +0 -0
  418. package/dist/pkgs/soundlib/library/kenney-scifi/explosion-crunch-001.wav +0 -0
  419. package/dist/pkgs/soundlib/library/kenney-scifi/explosion-crunch-002.wav +0 -0
  420. package/dist/pkgs/soundlib/library/kenney-scifi/force-field-000.wav +0 -0
  421. package/dist/pkgs/soundlib/library/kenney-scifi/force-field-001.wav +0 -0
  422. package/dist/pkgs/soundlib/library/kenney-scifi/force-field-002.wav +0 -0
  423. package/dist/pkgs/soundlib/library/kenney-scifi/impact-metal-000.wav +0 -0
  424. package/dist/pkgs/soundlib/library/kenney-scifi/impact-metal-001.wav +0 -0
  425. package/dist/pkgs/soundlib/library/kenney-scifi/impact-metal-002.wav +0 -0
  426. package/dist/pkgs/soundlib/library/kenney-scifi/laser-large-000.wav +0 -0
  427. package/dist/pkgs/soundlib/library/kenney-scifi/laser-large-001.wav +0 -0
  428. package/dist/pkgs/soundlib/library/kenney-scifi/laser-large-002.wav +0 -0
  429. package/dist/pkgs/soundlib/library/kenney-scifi/laser-retro-000.wav +0 -0
  430. package/dist/pkgs/soundlib/library/kenney-scifi/laser-retro-001.wav +0 -0
  431. package/dist/pkgs/soundlib/library/kenney-scifi/laser-retro-002.wav +0 -0
  432. package/dist/pkgs/soundlib/library/kenney-scifi/laser-small-000.wav +0 -0
  433. package/dist/pkgs/soundlib/library/kenney-scifi/laser-small-001.wav +0 -0
  434. package/dist/pkgs/soundlib/library/kenney-scifi/laser-small-002.wav +0 -0
  435. package/dist/pkgs/soundlib/library/kenney-scifi/low-frequency-explosion-000.wav +0 -0
  436. package/dist/pkgs/soundlib/library/kenney-scifi/low-frequency-explosion-001.wav +0 -0
  437. package/dist/pkgs/soundlib/library/kenney-scifi/slime-000.wav +0 -0
  438. package/dist/pkgs/soundlib/library/kenney-scifi/slime-001.wav +0 -0
  439. package/dist/pkgs/soundlib/library/kenney-scifi/thruster-fire-000.wav +0 -0
  440. package/dist/pkgs/soundlib/library/kenney-scifi/thruster-fire-001.wav +0 -0
  441. package/dist/pkgs/soundlib/library/kenney-scifi/thruster-fire-002.wav +0 -0
  442. package/dist/pkgs/soundlib/library/kenney-ui/click-1.wav +0 -0
  443. package/dist/pkgs/soundlib/library/kenney-ui/click-2.wav +0 -0
  444. package/dist/pkgs/soundlib/library/kenney-ui/click-3.wav +0 -0
  445. package/dist/pkgs/soundlib/library/kenney-ui/mouseclick-1.wav +0 -0
  446. package/dist/pkgs/soundlib/library/kenney-ui/mouserelease-1.wav +0 -0
  447. package/dist/pkgs/soundlib/library/kenney-ui/rollover-1.wav +0 -0
  448. package/dist/pkgs/soundlib/library/kenney-ui/rollover-2.wav +0 -0
  449. package/dist/pkgs/soundlib/library/kenney-ui/rollover-3.wav +0 -0
  450. package/dist/pkgs/soundlib/library/kenney-ui/switch-1.wav +0 -0
  451. package/dist/pkgs/soundlib/library/kenney-ui/switch-10.wav +0 -0
  452. package/dist/pkgs/soundlib/library/kenney-ui/switch-11.wav +0 -0
  453. package/dist/pkgs/soundlib/library/kenney-ui/switch-12.wav +0 -0
  454. package/dist/pkgs/soundlib/library/kenney-ui/switch-13.wav +0 -0
  455. package/dist/pkgs/soundlib/library/kenney-ui/switch-14.wav +0 -0
  456. package/dist/pkgs/soundlib/licences/kenney-digital.txt +21 -0
  457. package/dist/pkgs/soundlib/licences/kenney-impact.txt +23 -0
  458. package/dist/pkgs/soundlib/licences/kenney-interface.txt +22 -0
  459. package/dist/pkgs/soundlib/licences/kenney-rpg.txt +21 -0
  460. package/dist/pkgs/soundlib/licences/kenney-scifi.txt +22 -0
  461. package/dist/pkgs/soundlib/licences/kenney-ui.txt +21 -0
  462. package/dist/pkgs/studio/dist/exported-player.js +67012 -0
  463. package/dist/pkgs/studio/dist/scripting.js +2435 -0
  464. package/dist/pkgs/studio/dist/server/playtest-worker.js +90348 -0
  465. package/dist/resolved/@fontsource-variable/geist/files/geist-latin-wght-normal.woff2 +0 -0
  466. package/dist/resolved/@fontsource-variable/jetbrains-mono/files/jetbrains-mono-latin-wght-normal.woff2 +0 -0
  467. package/dist/resolved/@fontsource-variable/plus-jakarta-sans/files/plus-jakarta-sans-latin-wght-normal.woff2 +0 -0
  468. package/dist/resolved/@gamesteel/site/package.json +17 -0
  469. 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