OpenGLContext 3.0.0a1__py3-none-any.whl

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 (417) hide show
  1. OpenGLContext/__init__.py +151 -0
  2. OpenGLContext/arrays.py +2 -0
  3. OpenGLContext/atlas.py +300 -0
  4. OpenGLContext/audio/__init__.py +32 -0
  5. OpenGLContext/audio/scene.py +130 -0
  6. OpenGLContext/audio/settings.py +82 -0
  7. OpenGLContext/bin/__init__.py +0 -0
  8. OpenGLContext/bin/choosecontext.py +224 -0
  9. OpenGLContext/bin/choosefonts.py +209 -0
  10. OpenGLContext/bin/gltest.py +121 -0
  11. OpenGLContext/bin/gltf_demo.py +492 -0
  12. OpenGLContext/bin/gltf_regression.py +645 -0
  13. OpenGLContext/bin/gltf_view.py +30 -0
  14. OpenGLContext/bin/keyboardevents.py +93 -0
  15. OpenGLContext/bin/lorentz.py +109 -0
  16. OpenGLContext/bin/physics_cook.py +81 -0
  17. OpenGLContext/bin/profile_view.py +38 -0
  18. OpenGLContext/bin/terrain_view.py +581 -0
  19. OpenGLContext/bin/tiles_view.py +39 -0
  20. OpenGLContext/bin/ui_demo.py +228 -0
  21. OpenGLContext/bin/view.py +341 -0
  22. OpenGLContext/bin/vrml_view.py +42 -0
  23. OpenGLContext/capture.py +125 -0
  24. OpenGLContext/context.py +1253 -0
  25. OpenGLContext/contextconfig.py +177 -0
  26. OpenGLContext/contextdefinition.py +275 -0
  27. OpenGLContext/debug/__init__.py +6 -0
  28. OpenGLContext/debug/bufferimage.py +80 -0
  29. OpenGLContext/debug/leaks.py +13 -0
  30. OpenGLContext/debug/logcontext.py +9 -0
  31. OpenGLContext/debug/logs.py +15 -0
  32. OpenGLContext/debug/state.py +329 -0
  33. OpenGLContext/displaylist.py +69 -0
  34. OpenGLContext/doinchildmatrix.py +34 -0
  35. OpenGLContext/drawcube.py +58 -0
  36. OpenGLContext/events/__init__.py +10 -0
  37. OpenGLContext/events/default_config.py +29 -0
  38. OpenGLContext/events/event.py +65 -0
  39. OpenGLContext/events/eventhandlermixin.py +214 -0
  40. OpenGLContext/events/eventmanager.py +269 -0
  41. OpenGLContext/events/fxevents.py +90 -0
  42. OpenGLContext/events/glfwevents.py +424 -0
  43. OpenGLContext/events/glutevents.py +184 -0
  44. OpenGLContext/events/inputstate.py +129 -0
  45. OpenGLContext/events/internaltime.py +161 -0
  46. OpenGLContext/events/keyboardevents.py +153 -0
  47. OpenGLContext/events/mouseevents.py +473 -0
  48. OpenGLContext/events/pygameevents.py +162 -0
  49. OpenGLContext/events/systemtime.py +12 -0
  50. OpenGLContext/events/timeeventgeneratormanager.py +40 -0
  51. OpenGLContext/events/timeevents.py +143 -0
  52. OpenGLContext/events/timer.py +170 -0
  53. OpenGLContext/events/tkevents.py +172 -0
  54. OpenGLContext/events/wxevents.py +185 -0
  55. OpenGLContext/extensionmanager.py +158 -0
  56. OpenGLContext/framecounter.py +74 -0
  57. OpenGLContext/frustum.py +135 -0
  58. OpenGLContext/glfwcontext.py +395 -0
  59. OpenGLContext/glfwinteractivecontext.py +34 -0
  60. OpenGLContext/glfwvrmlcontext.py +11 -0
  61. OpenGLContext/glutcontext.py +224 -0
  62. OpenGLContext/glutinteractivecontext.py +24 -0
  63. OpenGLContext/gluttestingcontext.py +11 -0
  64. OpenGLContext/glutvrmlcontext.py +25 -0
  65. OpenGLContext/glutvrmltestingcontext.py +48 -0
  66. OpenGLContext/hud.py +404 -0
  67. OpenGLContext/interactivecontext.py +29 -0
  68. OpenGLContext/loaders/__init__.py +1 -0
  69. OpenGLContext/loaders/base.py +84 -0
  70. OpenGLContext/loaders/cc0.py +137 -0
  71. OpenGLContext/loaders/gltf/__init__.py +71 -0
  72. OpenGLContext/loaders/gltf/accessors.py +255 -0
  73. OpenGLContext/loaders/gltf/animation.py +611 -0
  74. OpenGLContext/loaders/gltf/draco.py +166 -0
  75. OpenGLContext/loaders/gltf/environment_sky.py +416 -0
  76. OpenGLContext/loaders/gltf/loader.py +80 -0
  77. OpenGLContext/loaders/gltf/materials.py +298 -0
  78. OpenGLContext/loaders/gltf/meshes.py +292 -0
  79. OpenGLContext/loaders/gltf/samples.py +162 -0
  80. OpenGLContext/loaders/gltf/scene.py +673 -0
  81. OpenGLContext/loaders/gltf/specular_glossiness.py +154 -0
  82. OpenGLContext/loaders/gltf/textures.py +155 -0
  83. OpenGLContext/loaders/gltf/transforms.py +261 -0
  84. OpenGLContext/loaders/gltf_demos.py +511 -0
  85. OpenGLContext/loaders/hdr.py +228 -0
  86. OpenGLContext/loaders/hdri.py +83 -0
  87. OpenGLContext/loaders/loader.py +255 -0
  88. OpenGLContext/loaders/obj.py +299 -0
  89. OpenGLContext/loaders/resolver.py +536 -0
  90. OpenGLContext/loaders/tiles3d/__init__.py +9 -0
  91. OpenGLContext/loaders/tiles3d/boundingvolume.py +120 -0
  92. OpenGLContext/loaders/tiles3d/dem.py +71 -0
  93. OpenGLContext/loaders/tiles3d/fetch.py +102 -0
  94. OpenGLContext/loaders/tiles3d/foliage.py +786 -0
  95. OpenGLContext/loaders/tiles3d/frustum.py +70 -0
  96. OpenGLContext/loaders/tiles3d/geomorph.py +57 -0
  97. OpenGLContext/loaders/tiles3d/gltf_uploader.py +246 -0
  98. OpenGLContext/loaders/tiles3d/loadmanager.py +121 -0
  99. OpenGLContext/loaders/tiles3d/physics_colliders.py +78 -0
  100. OpenGLContext/loaders/tiles3d/procedural.py +355 -0
  101. OpenGLContext/loaders/tiles3d/residency.py +102 -0
  102. OpenGLContext/loaders/tiles3d/runtime.py +168 -0
  103. OpenGLContext/loaders/tiles3d/sample.py +212 -0
  104. OpenGLContext/loaders/tiles3d/scatter.py +77 -0
  105. OpenGLContext/loaders/tiles3d/screenspaceerror.py +31 -0
  106. OpenGLContext/loaders/tiles3d/tileset.py +390 -0
  107. OpenGLContext/loaders/tiles3d/traversal.py +108 -0
  108. OpenGLContext/loaders/tiles3d/vegetation.py +361 -0
  109. OpenGLContext/loaders/vrml97.py +133 -0
  110. OpenGLContext/looptrace.py +307 -0
  111. OpenGLContext/move/__init__.py +0 -0
  112. OpenGLContext/move/bindingstore.py +232 -0
  113. OpenGLContext/move/direct.py +156 -0
  114. OpenGLContext/move/dragwatcher.py +42 -0
  115. OpenGLContext/move/examinemanager.py +100 -0
  116. OpenGLContext/move/followcam.py +121 -0
  117. OpenGLContext/move/modes.py +522 -0
  118. OpenGLContext/move/movementmanager.py +74 -0
  119. OpenGLContext/move/navigation.py +137 -0
  120. OpenGLContext/move/physicsplatform.py +203 -0
  121. OpenGLContext/move/physicswalk.py +469 -0
  122. OpenGLContext/move/smooth.py +68 -0
  123. OpenGLContext/move/terrainwalk.py +451 -0
  124. OpenGLContext/move/trackball.py +92 -0
  125. OpenGLContext/move/viewplatform.py +283 -0
  126. OpenGLContext/move/viewplatformmixin.py +335 -0
  127. OpenGLContext/nav/__init__.py +13 -0
  128. OpenGLContext/nav/navmesh.py +476 -0
  129. OpenGLContext/passes/__init__.py +0 -0
  130. OpenGLContext/passes/_flat.py +1317 -0
  131. OpenGLContext/passes/asyncpick.py +216 -0
  132. OpenGLContext/passes/bloom.py +257 -0
  133. OpenGLContext/passes/flatcompat.py +246 -0
  134. OpenGLContext/passes/flatcore.py +198 -0
  135. OpenGLContext/passes/flateffects.py +372 -0
  136. OpenGLContext/passes/ibl.py +711 -0
  137. OpenGLContext/passes/instancing.py +775 -0
  138. OpenGLContext/passes/pbrpass.py +829 -0
  139. OpenGLContext/passes/renderpass.py +79 -0
  140. OpenGLContext/passes/renderstats.py +55 -0
  141. OpenGLContext/passes/rendervisitor.py +60 -0
  142. OpenGLContext/passes/selection.py +450 -0
  143. OpenGLContext/passes/selectionbuffers.py +442 -0
  144. OpenGLContext/passes/shaderpass.py +1018 -0
  145. OpenGLContext/passes/shaderpass_shadow.py +197 -0
  146. OpenGLContext/passes/shadersource.py +121 -0
  147. OpenGLContext/passes/shadowcaps.py +182 -0
  148. OpenGLContext/passes/shadowmap.py +330 -0
  149. OpenGLContext/passes/shadowmath.py +290 -0
  150. OpenGLContext/passes/shadowmixin.py +906 -0
  151. OpenGLContext/passes/shadowpool.py +186 -0
  152. OpenGLContext/passes/transmission.py +124 -0
  153. OpenGLContext/physics/__init__.py +16 -0
  154. OpenGLContext/physics/debugdraw.py +370 -0
  155. OpenGLContext/physics/demo.py +157 -0
  156. OpenGLContext/physics/gltf_world.py +185 -0
  157. OpenGLContext/physics/manager.py +84 -0
  158. OpenGLContext/physics/threaded.py +93 -0
  159. OpenGLContext/plugins.py +61 -0
  160. OpenGLContext/pygamecontext.py +178 -0
  161. OpenGLContext/pygameinteractivecontext.py +36 -0
  162. OpenGLContext/pygametestingcontext.py +10 -0
  163. OpenGLContext/pygamevrmlcontext.py +13 -0
  164. OpenGLContext/quaternion.py +190 -0
  165. OpenGLContext/renderoptions.py +280 -0
  166. OpenGLContext/resources/__init__.py +36 -0
  167. OpenGLContext/resources/available_contexts.txt +4 -0
  168. OpenGLContext/resources/available_contexts_txt.py +9 -0
  169. OpenGLContext/resources/context_icon_png.py +46 -0
  170. OpenGLContext/resources/context_icon_small_png.py +21 -0
  171. OpenGLContext/resources/disk_icon_hi_png.py +14 -0
  172. OpenGLContext/resources/disk_icon_png.py +15 -0
  173. OpenGLContext/resources/environment/pimbackground_BK.jpg +0 -0
  174. OpenGLContext/resources/environment/pimbackground_DN.jpg +0 -0
  175. OpenGLContext/resources/environment/pimbackground_FR.jpg +0 -0
  176. OpenGLContext/resources/environment/pimbackground_LF.jpg +0 -0
  177. OpenGLContext/resources/environment/pimbackground_RT.jpg +0 -0
  178. OpenGLContext/resources/environment/pimbackground_UP.jpg +0 -0
  179. OpenGLContext/resources/environment/studio_BK.jpg +0 -0
  180. OpenGLContext/resources/environment/studio_DN.jpg +0 -0
  181. OpenGLContext/resources/environment/studio_FR.jpg +0 -0
  182. OpenGLContext/resources/environment/studio_LF.jpg +0 -0
  183. OpenGLContext/resources/environment/studio_RT.jpg +0 -0
  184. OpenGLContext/resources/environment/studio_UP.jpg +0 -0
  185. OpenGLContext/resources/environment/studiobright_BK.jpg +0 -0
  186. OpenGLContext/resources/environment/studiobright_DN.jpg +0 -0
  187. OpenGLContext/resources/environment/studiobright_FR.jpg +0 -0
  188. OpenGLContext/resources/environment/studiobright_LF.jpg +0 -0
  189. OpenGLContext/resources/environment/studiobright_RT.jpg +0 -0
  190. OpenGLContext/resources/environment/studiobright_UP.jpg +0 -0
  191. OpenGLContext/resources/green_arrow_right_png.py +47 -0
  192. OpenGLContext/resources/legacy_lighting.vert.txt +37 -0
  193. OpenGLContext/resources/legacy_lighting_vert_txt.py +18 -0
  194. OpenGLContext/resources/lights.vert.txt +107 -0
  195. OpenGLContext/resources/lights_vert_txt.py +23 -0
  196. OpenGLContext/resources/phongprecalc_vert.py +30 -0
  197. OpenGLContext/resources/phongweights_frag.py +47 -0
  198. OpenGLContext/resources/pygame_icon_png.py +47 -0
  199. OpenGLContext/resources/red_arrow_right_png.py +50 -0
  200. OpenGLContext/resources/simpleshader.frag.txt +3 -0
  201. OpenGLContext/resources/simpleshader.vert.txt +3 -0
  202. OpenGLContext/resources/simpleshader_frag_txt.py +9 -0
  203. OpenGLContext/resources/simpleshader_vert_txt.py +10 -0
  204. OpenGLContext/scenegraph/__init__.py +19 -0
  205. OpenGLContext/scenegraph/appearance.py +107 -0
  206. OpenGLContext/scenegraph/arraygeometry.py +238 -0
  207. OpenGLContext/scenegraph/audio.py +632 -0
  208. OpenGLContext/scenegraph/background.py +108 -0
  209. OpenGLContext/scenegraph/basenodes.py +50 -0
  210. OpenGLContext/scenegraph/billboard.py +14 -0
  211. OpenGLContext/scenegraph/boundingvolume.py +494 -0
  212. OpenGLContext/scenegraph/box.py +180 -0
  213. OpenGLContext/scenegraph/collision.py +14 -0
  214. OpenGLContext/scenegraph/coordinate.py +20 -0
  215. OpenGLContext/scenegraph/coordinatebounded.py +18 -0
  216. OpenGLContext/scenegraph/cubebackground.py +207 -0
  217. OpenGLContext/scenegraph/extrusions.py +275 -0
  218. OpenGLContext/scenegraph/fog.py +123 -0
  219. OpenGLContext/scenegraph/gear.py +323 -0
  220. OpenGLContext/scenegraph/group.py +20 -0
  221. OpenGLContext/scenegraph/grouping.py +140 -0
  222. OpenGLContext/scenegraph/hdrbackground.py +384 -0
  223. OpenGLContext/scenegraph/ifscompiler.py +633 -0
  224. OpenGLContext/scenegraph/imagetexture.py +365 -0
  225. OpenGLContext/scenegraph/indexedfaceset.py +238 -0
  226. OpenGLContext/scenegraph/indexedlineset.py +418 -0
  227. OpenGLContext/scenegraph/indexedpolygons.py +422 -0
  228. OpenGLContext/scenegraph/inline.py +79 -0
  229. OpenGLContext/scenegraph/instancedgl.py +187 -0
  230. OpenGLContext/scenegraph/interpolators.py +122 -0
  231. OpenGLContext/scenegraph/light.py +213 -0
  232. OpenGLContext/scenegraph/lod.py +22 -0
  233. OpenGLContext/scenegraph/material.py +96 -0
  234. OpenGLContext/scenegraph/material_fields.py +50 -0
  235. OpenGLContext/scenegraph/mouseover.py +43 -0
  236. OpenGLContext/scenegraph/nodepath.py +52 -0
  237. OpenGLContext/scenegraph/nurbs.py +637 -0
  238. OpenGLContext/scenegraph/nurbssampling.py +101 -0
  239. OpenGLContext/scenegraph/nurbstess.py +316 -0
  240. OpenGLContext/scenegraph/nurbstrim.py +54 -0
  241. OpenGLContext/scenegraph/particles.py +786 -0
  242. OpenGLContext/scenegraph/pbrmaterial.py +229 -0
  243. OpenGLContext/scenegraph/pbrmesh.py +558 -0
  244. OpenGLContext/scenegraph/physicsbody.py +72 -0
  245. OpenGLContext/scenegraph/pointset.py +257 -0
  246. OpenGLContext/scenegraph/polygon.py +166 -0
  247. OpenGLContext/scenegraph/polygonsort.py +119 -0
  248. OpenGLContext/scenegraph/polygontessellator.py +213 -0
  249. OpenGLContext/scenegraph/quadrics.py +494 -0
  250. OpenGLContext/scenegraph/scenegraph.py +21 -0
  251. OpenGLContext/scenegraph/shadergeometry.py +403 -0
  252. OpenGLContext/scenegraph/shaders.py +812 -0
  253. OpenGLContext/scenegraph/shape.py +243 -0
  254. OpenGLContext/scenegraph/simplebackground.py +34 -0
  255. OpenGLContext/scenegraph/spherebackground.py +462 -0
  256. OpenGLContext/scenegraph/switch.py +75 -0
  257. OpenGLContext/scenegraph/teapot.py +439 -0
  258. OpenGLContext/scenegraph/teapot_nurbs.py +291 -0
  259. OpenGLContext/scenegraph/teapot_nurbs_data.py +384 -0
  260. OpenGLContext/scenegraph/terrain/__init__.py +12 -0
  261. OpenGLContext/scenegraph/terrain/heightfield.py +172 -0
  262. OpenGLContext/scenegraph/terrain/splat.py +224 -0
  263. OpenGLContext/scenegraph/tessellationlod.py +97 -0
  264. OpenGLContext/scenegraph/text/__init__.py +1 -0
  265. OpenGLContext/scenegraph/text/_toolsfont.py +200 -0
  266. OpenGLContext/scenegraph/text/font.py +452 -0
  267. OpenGLContext/scenegraph/text/fontprovider.py +193 -0
  268. OpenGLContext/scenegraph/text/fonts/__init__.py +75 -0
  269. OpenGLContext/scenegraph/text/fonts/font_atlas_10.py +133 -0
  270. OpenGLContext/scenegraph/text/fonts/font_atlas_12.py +158 -0
  271. OpenGLContext/scenegraph/text/fonts/font_atlas_14.py +176 -0
  272. OpenGLContext/scenegraph/text/fonts/font_atlas_16.py +204 -0
  273. OpenGLContext/scenegraph/text/fonts/font_atlas_18.py +222 -0
  274. OpenGLContext/scenegraph/text/fonts/font_atlas_20.py +244 -0
  275. OpenGLContext/scenegraph/text/fonts/font_atlas_24.py +294 -0
  276. OpenGLContext/scenegraph/text/fonts/font_atlas_28.py +339 -0
  277. OpenGLContext/scenegraph/text/fonts/font_atlas_32.py +390 -0
  278. OpenGLContext/scenegraph/text/fontstyle3d.py +62 -0
  279. OpenGLContext/scenegraph/text/glutfont.py +186 -0
  280. OpenGLContext/scenegraph/text/pygamefont.py +213 -0
  281. OpenGLContext/scenegraph/text/shaderfont.py +340 -0
  282. OpenGLContext/scenegraph/text/shadertext.py +433 -0
  283. OpenGLContext/scenegraph/text/text.py +75 -0
  284. OpenGLContext/scenegraph/text/toolsfont.py +819 -0
  285. OpenGLContext/scenegraph/text/ttfregistry.py +135 -0
  286. OpenGLContext/scenegraph/text/wglfont.py +228 -0
  287. OpenGLContext/scenegraph/text/wglfontprovider.py +37 -0
  288. OpenGLContext/scenegraph/text/wxfont.py +314 -0
  289. OpenGLContext/scenegraph/texturetransform.py +66 -0
  290. OpenGLContext/scenegraph/tilesterrain.py +95 -0
  291. OpenGLContext/scenegraph/timesensor.py +51 -0
  292. OpenGLContext/scenegraph/transform.py +180 -0
  293. OpenGLContext/scenegraph/vegetation/__init__.py +20 -0
  294. OpenGLContext/scenegraph/vegetation/base.py +96 -0
  295. OpenGLContext/scenegraph/vegetation/billboards.py +145 -0
  296. OpenGLContext/scenegraph/vegetation/clumps.py +260 -0
  297. OpenGLContext/scenegraph/vegetation/grid.py +112 -0
  298. OpenGLContext/scenegraph/vegetation/nearmesh.py +185 -0
  299. OpenGLContext/scenegraph/vertex.py +54 -0
  300. OpenGLContext/scenegraph/viewpoint.py +42 -0
  301. OpenGLContext/scenegraph/winding.py +49 -0
  302. OpenGLContext/shaders/_brdf_inc.glsl +124 -0
  303. OpenGLContext/shaders/_common_inc.glsl +24 -0
  304. OpenGLContext/shaders/_cubemap_inc.glsl +26 -0
  305. OpenGLContext/shaders/_lights_inc.glsl +32 -0
  306. OpenGLContext/shaders/_objectid_inc.glsl +14 -0
  307. OpenGLContext/shaders/_shadow_inc.glsl +254 -0
  308. OpenGLContext/shaders/_vrml97_lighting_inc.glsl +78 -0
  309. OpenGLContext/shaders/hdr_background.frag +32 -0
  310. OpenGLContext/shaders/hdr_background.vert +15 -0
  311. OpenGLContext/shaders/ibl_brdf.frag +78 -0
  312. OpenGLContext/shaders/ibl_env.frag +53 -0
  313. OpenGLContext/shaders/ibl_equirect.frag +19 -0
  314. OpenGLContext/shaders/ibl_fullscreen.vert +11 -0
  315. OpenGLContext/shaders/ibl_irradiance.frag +40 -0
  316. OpenGLContext/shaders/ibl_prefilter.frag +56 -0
  317. OpenGLContext/shaders/particle.frag +42 -0
  318. OpenGLContext/shaders/particle.vert +52 -0
  319. OpenGLContext/shaders/pbr.frag +906 -0
  320. OpenGLContext/shaders/pbr.vert +70 -0
  321. OpenGLContext/shaders/shadow_depth.frag +4 -0
  322. OpenGLContext/shaders/shadow_depth.vert +18 -0
  323. OpenGLContext/shaders/terrain_splat.frag +80 -0
  324. OpenGLContext/shaders/terrain_splat.vert +16 -0
  325. OpenGLContext/shaders/veg_billboard.frag +35 -0
  326. OpenGLContext/shaders/veg_billboard.vert +17 -0
  327. OpenGLContext/shaders/veg_clump.frag +27 -0
  328. OpenGLContext/shaders/veg_mesh.frag +26 -0
  329. OpenGLContext/shaders/veg_mesh.vert +17 -0
  330. OpenGLContext/shaders/vrml97_background.frag +17 -0
  331. OpenGLContext/shaders/vrml97_background.vert +24 -0
  332. OpenGLContext/shaders/vrml97_lighting.frag +111 -0
  333. OpenGLContext/shaders/vrml97_lighting.vert +50 -0
  334. OpenGLContext/shaders/vrml97_line.frag +23 -0
  335. OpenGLContext/shaders/vrml97_line.vert +24 -0
  336. OpenGLContext/shaders/vrml97_point.frag +36 -0
  337. OpenGLContext/shaders/vrml97_point.vert +21 -0
  338. OpenGLContext/shaders/vrml97_unlit.frag +59 -0
  339. OpenGLContext/shaders/vrml97_unlit.vert +18 -0
  340. OpenGLContext/shaders/vrml97_vertex_color.frag +85 -0
  341. OpenGLContext/shaders/vrml97_vertex_color.vert +34 -0
  342. OpenGLContext/stalltrace.py +637 -0
  343. OpenGLContext/testing/__init__.py +67 -0
  344. OpenGLContext/testing/display.py +30 -0
  345. OpenGLContext/testing/event_injector.py +564 -0
  346. OpenGLContext/testing/framebuffer_comparison.py +842 -0
  347. OpenGLContext/testing/gl_env.py +39 -0
  348. OpenGLContext/testing/paths.py +23 -0
  349. OpenGLContext/testing/process_exit.py +30 -0
  350. OpenGLContext/testing/report_generator.py +594 -0
  351. OpenGLContext/testing/subprocess_runner.py +411 -0
  352. OpenGLContext/testingcontext.py +36 -0
  353. OpenGLContext/tests/test_atlas.py +62 -0
  354. OpenGLContext/tests/test_configs.py +27 -0
  355. OpenGLContext/tests/test_polygonsort.py +45 -0
  356. OpenGLContext/tests/test_utilities.py +84 -0
  357. OpenGLContext/tests/test_viewplatform.py +39 -0
  358. OpenGLContext/texture.py +449 -0
  359. OpenGLContext/texturecache.py +37 -0
  360. OpenGLContext/triangleutilities.py +89 -0
  361. OpenGLContext/ui/__init__.py +39 -0
  362. OpenGLContext/ui/bindings.py +243 -0
  363. OpenGLContext/ui/console.py +339 -0
  364. OpenGLContext/ui/debugoverlay.py +519 -0
  365. OpenGLContext/ui/dialogs.py +124 -0
  366. OpenGLContext/ui/draw.py +617 -0
  367. OpenGLContext/ui/gallery.py +425 -0
  368. OpenGLContext/ui/generate.py +195 -0
  369. OpenGLContext/ui/geometry.py +88 -0
  370. OpenGLContext/ui/hudwidgets.py +1053 -0
  371. OpenGLContext/ui/layout.py +263 -0
  372. OpenGLContext/ui/metrics.py +218 -0
  373. OpenGLContext/ui/overlay.py +412 -0
  374. OpenGLContext/ui/panel.py +459 -0
  375. OpenGLContext/ui/pictures.py +261 -0
  376. OpenGLContext/ui/screen.py +177 -0
  377. OpenGLContext/ui/scroll.py +297 -0
  378. OpenGLContext/ui/session.py +329 -0
  379. OpenGLContext/ui/settings.py +273 -0
  380. OpenGLContext/ui/skin.py +330 -0
  381. OpenGLContext/ui/widgets.py +1199 -0
  382. OpenGLContext/userpaths.py +48 -0
  383. OpenGLContext/utilities.py +94 -0
  384. OpenGLContext/vectorutilities.py +101 -0
  385. OpenGLContext/viewer/__init__.py +60 -0
  386. OpenGLContext/viewer/adapters/__init__.py +150 -0
  387. OpenGLContext/viewer/adapters/base.py +157 -0
  388. OpenGLContext/viewer/adapters/gltf.py +31 -0
  389. OpenGLContext/viewer/adapters/obj.py +20 -0
  390. OpenGLContext/viewer/adapters/scenegraph.py +71 -0
  391. OpenGLContext/viewer/adapters/tiles.py +296 -0
  392. OpenGLContext/viewer/adapters/vrml.py +25 -0
  393. OpenGLContext/viewer/asyncscene.py +117 -0
  394. OpenGLContext/viewer/caption.py +126 -0
  395. OpenGLContext/viewer/capture.py +77 -0
  396. OpenGLContext/viewer/debug.py +78 -0
  397. OpenGLContext/viewer/environment.py +131 -0
  398. OpenGLContext/viewer/framing.py +111 -0
  399. OpenGLContext/viewer/library.py +235 -0
  400. OpenGLContext/viewer/menu.py +218 -0
  401. OpenGLContext/viewer/options.py +150 -0
  402. OpenGLContext/viewer/overlay.py +63 -0
  403. OpenGLContext/viewer/sceneviewer.py +1056 -0
  404. OpenGLContext/viewer/screens.py +172 -0
  405. OpenGLContext/viewer/source.py +56 -0
  406. OpenGLContext/visitor.py +68 -0
  407. OpenGLContext/vrmlcontext.py +72 -0
  408. OpenGLContext/wxcontext.py +424 -0
  409. OpenGLContext/wxinteractivecontext.py +25 -0
  410. OpenGLContext/wxtestingcontext.py +9 -0
  411. OpenGLContext/wxvrmlcontext.py +13 -0
  412. openglcontext-3.0.0a1.dist-info/METADATA +650 -0
  413. openglcontext-3.0.0a1.dist-info/RECORD +417 -0
  414. openglcontext-3.0.0a1.dist-info/WHEEL +5 -0
  415. openglcontext-3.0.0a1.dist-info/entry_points.txt +11 -0
  416. openglcontext-3.0.0a1.dist-info/licenses/license.txt +89 -0
  417. openglcontext-3.0.0a1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,786 @@
1
+ """Procedurally-textured, alpha-cut foliage for a semi-realistic forest.
2
+
3
+ Solid-colour primitives read as toy geometry; real foliage is thin alpha-textured
4
+ cards — grass blades, pine needles — plus textured bark. This module generates those
5
+ textures with numpy and bakes them into small glTF prototypes (alpha-MASK cutout,
6
+ double-sided) that the runtime loads once and instances thousands of times.
7
+
8
+ Textures are deterministic (seeded) so a scene is reproducible.
9
+ """
10
+ import os
11
+ from collections.abc import Callable, Sequence
12
+ from typing import Any, Optional
13
+
14
+ import numpy as np
15
+
16
+ from OpenGLContext.loaders import gltf
17
+
18
+ # Terrain height sampler: height_fn(x, z) -> height (scalar or array, matching x/z).
19
+ HeightFn = Callable[..., Any]
20
+
21
+
22
+ # --- procedural textures ------------------------------------------------------
23
+
24
+ def _noise2d(h: int, w: int, scale: int, seed: int) -> np.ndarray:
25
+ rng = np.random.default_rng(seed)
26
+ small = rng.random((max(2, h // scale), max(2, w // scale)))
27
+ ys = np.linspace(0, small.shape[0] - 1, h)
28
+ xs = np.linspace(0, small.shape[1] - 1, w)
29
+ y0 = np.floor(ys).astype(int)
30
+ x0 = np.floor(xs).astype(int)
31
+ y1 = np.minimum(y0 + 1, small.shape[0] - 1)
32
+ x1 = np.minimum(x0 + 1, small.shape[1] - 1)
33
+ fy = (ys - y0)[:, None]
34
+ fx = (xs - x0)[None, :]
35
+ top = small[y0][:, x0] * (1 - fx) + small[y0][:, x1] * fx
36
+ bot = small[y1][:, x0] * (1 - fx) + small[y1][:, x1] * fx
37
+ return top * (1 - fy) + bot * fy
38
+
39
+
40
+ def grass_texture(size: int = 128, blades: int = 26, seed: int = 3) -> np.ndarray:
41
+ """RGBA grass-blade card: tapered green blades on a transparent background."""
42
+ img = np.zeros((size, size, 4), np.uint8)
43
+ rng = np.random.default_rng(seed)
44
+ for _ in range(blades):
45
+ x0 = rng.uniform(0.08, 0.92) * size
46
+ sway = rng.uniform(-0.22, 0.22) * size
47
+ width = rng.uniform(0.015, 0.045) * size
48
+ hgt = rng.uniform(0.55, 0.99)
49
+ shade = rng.uniform(0.55, 1.0)
50
+ tipy = rng.uniform(0.85, 1.0)
51
+ for yy in range(size):
52
+ t = yy / size
53
+ if t > hgt:
54
+ continue
55
+ cx = x0 + sway * (t ** 1.6)
56
+ w = width * (1.0 - (t / hgt) ** 1.2)
57
+ # Lush base -> yellow-green tip, brighter toward the top (backlit look).
58
+ up = t / hgt
59
+ r = int((40 + 90 * up) * shade)
60
+ g = int((110 + 80 * up) * shade * tipy)
61
+ b = int((25 + 15 * up) * shade)
62
+ row = size - 1 - yy
63
+ for xx in range(max(0, int(cx - w)), min(size, int(cx + w) + 1)):
64
+ img[row, xx] = (min(255, r), min(255, g), min(255, b), 255)
65
+ return img
66
+
67
+
68
+ def bark_texture(size: int = 128, seed: int = 7) -> np.ndarray:
69
+ """RGB bark: vertical brown fibres with noise."""
70
+ n = _noise2d(size, size, 6, seed)
71
+ v = _noise2d(size, size, 2, seed + 1) * 0.4
72
+ fibre = 0.5 + 0.5 * np.sin(np.linspace(0, np.pi * 14, size))[None, :]
73
+ val = np.clip(0.35 + 0.5 * n + 0.25 * v + 0.15 * fibre, 0, 1)
74
+ img = np.zeros((size, size, 3), np.uint8)
75
+ img[..., 0] = (val * 105 + 25).astype(np.uint8)
76
+ img[..., 1] = (val * 75 + 18).astype(np.uint8)
77
+ img[..., 2] = (val * 55 + 12).astype(np.uint8)
78
+ return img
79
+
80
+
81
+ def ground_texture(size: int = 128, seed: int = 5) -> np.ndarray:
82
+ """RGB forest-floor: brown earth with moss and litter mottle (offline fallback)."""
83
+ dirt = _noise2d(size, size, 8, seed)
84
+ moss = _noise2d(size, size, 4, seed + 2)
85
+ fine = _noise2d(size, size, 2, seed + 5)
86
+ img = np.zeros((size, size, 3), np.uint8)
87
+ base_r = 70 + 55 * dirt + 25 * fine
88
+ base_g = 55 + 45 * dirt + 20 * fine
89
+ base_b = 38 + 25 * dirt
90
+ m = np.clip((moss - 0.55) / 0.25, 0, 1) # moss patches
91
+ img[..., 0] = np.clip(base_r * (1 - m) + (45 + 40 * fine) * m, 0, 255)
92
+ img[..., 1] = np.clip(base_g * (1 - m) + (85 + 50 * fine) * m, 0, 255)
93
+ img[..., 2] = np.clip(base_b * (1 - m) + (30 + 20 * fine) * m, 0, 255)
94
+ return img
95
+
96
+
97
+ def procedural_ground_maps(seed: int = 5) -> dict[str, str]:
98
+ """A color-only ground map set (dict) for offline use; cached to a temp file."""
99
+ import tempfile
100
+ from PIL import Image
101
+ d = os.path.join(tempfile.gettempdir(), "oglc_proc_ground")
102
+ os.makedirs(d, exist_ok=True)
103
+ path = os.path.join(d, "ground_%d.png" % seed)
104
+ if not os.path.exists(path):
105
+ Image.fromarray(ground_texture(256, seed), "RGB").save(path)
106
+ return {"color": path}
107
+
108
+
109
+ def needle_texture(size: int = 128, sprigs: int = 40, seed: int = 11) -> np.ndarray:
110
+ """RGBA pine-foliage card: clusters of needles on a transparent background."""
111
+ img = np.zeros((size, size, 4), np.uint8)
112
+ rng = np.random.default_rng(seed)
113
+ for _ in range(sprigs):
114
+ cx = rng.uniform(0.1, 0.9) * size
115
+ cy = rng.uniform(0.1, 0.9) * size
116
+ ln = rng.uniform(0.06, 0.16) * size
117
+ ang = rng.uniform(0, 2 * np.pi)
118
+ shade = rng.uniform(0.5, 1.0)
119
+ for k in range(-6, 7):
120
+ a = ang + k * 0.14
121
+ for t in np.linspace(0, ln, int(ln)):
122
+ x = int(cx + np.cos(a) * t)
123
+ y = int(cy + np.sin(a) * t)
124
+ if 0 <= x < size and 0 <= y < size:
125
+ g = int(120 * shade + 60 * (1 - t / ln))
126
+ img[y, x] = (int(30 * shade), min(255, g), int(28 * shade), 255)
127
+ return img
128
+
129
+
130
+ # --- glTF prototypes ----------------------------------------------------------
131
+
132
+ def _textured_glb(positions: np.ndarray, uvs: np.ndarray, indices: np.ndarray,
133
+ texture: np.ndarray, alpha_mode: str = "OPAQUE",
134
+ double_sided: bool = True,
135
+ base_color: tuple[float, float, float, float] = (1, 1, 1, 1)
136
+ ) -> bytes:
137
+ from PIL import Image
138
+ import io
139
+ from pygltflib import (
140
+ GLTF2, Scene, Node, Mesh, Primitive, Attributes, Accessor, BufferView,
141
+ Buffer, Material, PbrMetallicRoughness, Texture, Image as GImage, Sampler,
142
+ TextureInfo,
143
+ )
144
+ mode = "RGBA" if texture.shape[2] == 4 else "RGB"
145
+ buf = io.BytesIO()
146
+ Image.fromarray(texture, mode).save(buf, format="PNG")
147
+ png = buf.getvalue()
148
+
149
+ V = np.asarray(positions, "<f4")
150
+ UV = np.asarray(uvs, "<f4")
151
+ IDX = np.asarray(indices, "<u4")
152
+ blob, spans = b"", []
153
+ for arr in (V, UV, IDX):
154
+ raw = np.ascontiguousarray(arr).tobytes()
155
+ blob += b"\x00" * ((-len(blob)) % 4)
156
+ spans.append((len(blob), len(raw)))
157
+ blob += raw
158
+ img_off = len(blob)
159
+ blob += png
160
+
161
+ g = GLTF2()
162
+ g.scene = 0
163
+ g.scenes = [Scene(nodes=[0])]
164
+ g.nodes = [Node(mesh=0)]
165
+ g.meshes = [Mesh(primitives=[Primitive(
166
+ attributes=Attributes(POSITION=0, TEXCOORD_0=1), indices=2, material=0)])]
167
+ g.materials = [Material(
168
+ pbrMetallicRoughness=PbrMetallicRoughness(
169
+ baseColorFactor=list(base_color),
170
+ baseColorTexture=TextureInfo(index=0),
171
+ metallicFactor=0.0, roughnessFactor=1.0),
172
+ alphaMode=alpha_mode, alphaCutoff=0.4, doubleSided=double_sided)]
173
+ g.textures = [Texture(source=0, sampler=0)]
174
+ g.samplers = [Sampler()]
175
+ g.images = [GImage(bufferView=3, mimeType="image/png")]
176
+ g.accessors = [
177
+ Accessor(bufferView=0, componentType=5126, count=len(V), type="VEC3",
178
+ min=V.min(0).tolist(), max=V.max(0).tolist()),
179
+ Accessor(bufferView=1, componentType=5126, count=len(UV), type="VEC2"),
180
+ Accessor(bufferView=2, componentType=5125, count=len(IDX), type="SCALAR")]
181
+ g.bufferViews = [BufferView(buffer=0, byteOffset=o, byteLength=l)
182
+ for o, l in spans]
183
+ g.bufferViews.append(BufferView(buffer=0, byteOffset=img_off, byteLength=len(png)))
184
+ g.buffers = [Buffer(byteLength=len(blob))]
185
+ g.set_binary_blob(blob)
186
+ return b"".join(g.save_to_bytes())
187
+
188
+
189
+ def _crossed_quads(width: float, height: float, planes: int = 2
190
+ ) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
191
+ V: list[tuple[float, float, float]] = []
192
+ UV: list[tuple[float, float]] = []
193
+ IDX: list[int] = []
194
+ for i in range(planes):
195
+ rot = np.pi * i / planes
196
+ c, s = np.cos(rot), np.sin(rot)
197
+ corners = [(-width / 2, 0, 0), (width / 2, 0, 0),
198
+ (width / 2, height, 0), (-width / 2, height, 0)]
199
+ base = len(V)
200
+ for x, y, z in corners:
201
+ V.append((x * c - z * s, y, x * s + z * c))
202
+ UV += [(0, 1), (1, 1), (1, 0), (0, 0)]
203
+ IDX += [base, base + 1, base + 2, base, base + 2, base + 3]
204
+ return np.array(V, "f4"), np.array(UV, "f4"), np.array(IDX, "u4")
205
+
206
+
207
+ def _cylinder(radius: float, height: float, sides: int = 10
208
+ ) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
209
+ V, UV, IDX = [], [], []
210
+ for i in range(sides + 1):
211
+ a = 2 * np.pi * i / sides
212
+ x, z = np.cos(a) * radius, np.sin(a) * radius
213
+ u = i / sides
214
+ V += [(x, 0, z), (x, height, z)]
215
+ UV += [(u, 1), (u, 0)]
216
+ for i in range(sides):
217
+ b = i * 2
218
+ IDX += [b, b + 2, b + 1, b + 1, b + 2, b + 3]
219
+ return np.array(V, "f4"), np.array(UV, "f4"), np.array(IDX, "u4")
220
+
221
+
222
+ def grass_card_glb(width: float = 0.9, height: float = 0.6, seed: int = 3,
223
+ planes: int = 3) -> bytes:
224
+ V, UV, IDX = _crossed_quads(width, height, planes=planes)
225
+ return _textured_glb(V, UV, IDX, grass_texture(seed=seed), alpha_mode="MASK")
226
+
227
+
228
+ def conifer_glb(height: float = 9.0, seed: int = 11,
229
+ bark_path: Optional[str] = None) -> bytes:
230
+ """Bark-textured trunk + stacked alpha needle skirts baked into one glTF.
231
+
232
+ `bark_path` uses a real CC0 bark JPG for the trunk; otherwise procedural bark."""
233
+ from pygltflib import (
234
+ GLTF2, Scene, Node, Mesh, Primitive, Attributes, Accessor, BufferView,
235
+ Buffer, Material, PbrMetallicRoughness, Texture, Image as GImage, Sampler,
236
+ TextureInfo,
237
+ )
238
+ import io
239
+ from PIL import Image
240
+
241
+ trunk_h = height * 0.45
242
+ tV, tUV, tIDX = _cylinder(height * 0.045, trunk_h, sides=8)
243
+ # Three needle skirts (crossed cards) up the tree.
244
+ off = len(tV)
245
+ fV = [tV]
246
+ fUV = [tUV]
247
+ fIDX = [tIDX]
248
+ for i in range(3):
249
+ y = trunk_h * 0.5 + i * height * 0.22
250
+ w = height * 0.5 * (1.0 - 0.22 * i)
251
+ h = height * 0.42
252
+ cV, cUV, cIDX = _crossed_quads(w, h, planes=2)
253
+ cV = cV.copy()
254
+ cV[:, 1] += y
255
+ fV.append(cV)
256
+ fUV.append(cUV)
257
+ fIDX.append(cIDX + off)
258
+ off += len(cV)
259
+ V = np.vstack(fV).astype("<f4")
260
+ UV = np.vstack(fUV).astype("<f4")
261
+ trunk_count = len(tIDX)
262
+ IDX = np.concatenate(fIDX).astype("<u4")
263
+
264
+ def png_of(arr: np.ndarray) -> bytes:
265
+ mode = "RGBA" if arr.shape[2] == 4 else "RGB"
266
+ b = io.BytesIO()
267
+ Image.fromarray(arr, mode).save(b, format="PNG")
268
+ return b.getvalue()
269
+ if bark_path:
270
+ with open(bark_path, "rb") as fh:
271
+ bark = fh.read()
272
+ bark_mime = "image/jpeg" if bark_path.lower().endswith((".jpg", ".jpeg")) \
273
+ else "image/png"
274
+ else:
275
+ bark = png_of(bark_texture(seed=seed))
276
+ bark_mime = "image/png"
277
+ needle = png_of(needle_texture(seed=seed + 1))
278
+
279
+ blob, spans = b"", []
280
+ for arr in (V, UV, IDX):
281
+ raw = np.ascontiguousarray(arr).tobytes()
282
+ blob += b"\x00" * ((-len(blob)) % 4)
283
+ spans.append((len(blob), len(raw)))
284
+ blob += raw
285
+ bark_off = len(blob)
286
+ blob += bark
287
+ needle_off = len(blob)
288
+ blob += needle
289
+
290
+ g = GLTF2()
291
+ g.scene = 0
292
+ g.scenes = [Scene(nodes=[0])]
293
+ g.nodes = [Node(mesh=0)]
294
+ g.meshes = [Mesh(primitives=[
295
+ Primitive(attributes=Attributes(POSITION=0, TEXCOORD_0=1),
296
+ indices=2, material=0), # trunk
297
+ Primitive(attributes=Attributes(POSITION=0, TEXCOORD_0=1),
298
+ indices=3, material=1)])] # needles
299
+ g.materials = [
300
+ Material(pbrMetallicRoughness=PbrMetallicRoughness(
301
+ baseColorTexture=TextureInfo(index=0), metallicFactor=0, roughnessFactor=1),
302
+ alphaMode="OPAQUE", doubleSided=False),
303
+ Material(pbrMetallicRoughness=PbrMetallicRoughness(
304
+ baseColorTexture=TextureInfo(index=1), metallicFactor=0, roughnessFactor=1),
305
+ alphaMode="MASK", alphaCutoff=0.4, doubleSided=True)]
306
+ g.textures = [Texture(source=0, sampler=0), Texture(source=1, sampler=0)]
307
+ g.samplers = [Sampler()]
308
+ g.images = [GImage(bufferView=3, mimeType=bark_mime),
309
+ GImage(bufferView=4, mimeType="image/png")]
310
+ g.accessors = [
311
+ Accessor(bufferView=0, componentType=5126, count=len(V), type="VEC3",
312
+ min=V.min(0).tolist(), max=V.max(0).tolist()),
313
+ Accessor(bufferView=1, componentType=5126, count=len(UV), type="VEC2"),
314
+ Accessor(bufferView=2, componentType=5125, count=trunk_count, type="SCALAR"),
315
+ Accessor(bufferView=2, componentType=5125, count=len(IDX) - trunk_count,
316
+ type="SCALAR", byteOffset=trunk_count * 4)]
317
+ g.bufferViews = [BufferView(buffer=0, byteOffset=o, byteLength=l)
318
+ for o, l in spans]
319
+ g.bufferViews += [BufferView(buffer=0, byteOffset=bark_off, byteLength=len(bark)),
320
+ BufferView(buffer=0, byteOffset=needle_off, byteLength=len(needle))]
321
+ g.buffers = [Buffer(byteLength=len(blob))]
322
+ g.set_binary_blob(blob)
323
+ return b"".join(g.save_to_bytes())
324
+
325
+
326
+ def _pbr_glb(positions: np.ndarray, uvs: np.ndarray, indices: np.ndarray,
327
+ maps: dict[str, str], tile: float = 1.0, alpha_mode: str = "OPAQUE",
328
+ double_sided: bool = True) -> bytes:
329
+ """Build a glTF mesh textured with a CC0 PBR map set (color + normal[+rough]).
330
+
331
+ `maps` is the dict from `cc0.material(...)` (local jpg paths). UVs are scaled by
332
+ `tile`. Falls back cleanly if some maps are missing.
333
+ """
334
+ from pygltflib import (
335
+ GLTF2, Scene, Node, Mesh, Primitive, Attributes, Accessor, BufferView,
336
+ Buffer, Material, PbrMetallicRoughness, Texture, Image as GImage, Sampler,
337
+ TextureInfo, NormalMaterialTexture,
338
+ )
339
+ V = np.asarray(positions, "<f4")
340
+ UV = (np.asarray(uvs, "<f4") * tile).astype("<f4")
341
+ IDX = np.asarray(indices, "<u4")
342
+ order = ["color", "normal", "roughness"]
343
+ present = [k for k in order if maps.get(k)]
344
+ blob, spans = b"", []
345
+ for arr in (V, UV, IDX):
346
+ raw = np.ascontiguousarray(arr).tobytes()
347
+ blob += b"\x00" * ((-len(blob)) % 4)
348
+ spans.append((len(blob), len(raw)))
349
+ blob += raw
350
+ img_spans = {}
351
+ for k in present:
352
+ data = open(maps[k], "rb").read()
353
+ img_spans[k] = (len(blob), len(data))
354
+ blob += data
355
+
356
+ g = GLTF2()
357
+ g.scene = 0
358
+ g.scenes = [Scene(nodes=[0])]
359
+ g.nodes = [Node(mesh=0)]
360
+ g.meshes = [Mesh(primitives=[Primitive(
361
+ attributes=Attributes(POSITION=0, TEXCOORD_0=1), indices=2, material=0)])]
362
+ pbr = PbrMetallicRoughness(metallicFactor=0.0, roughnessFactor=1.0)
363
+ mat = Material(pbrMetallicRoughness=pbr, alphaMode=alpha_mode,
364
+ doubleSided=double_sided)
365
+ g.samplers = [Sampler(wrapS=10497, wrapT=10497)]
366
+ g.textures, g.images, g.bufferViews = [], [], [
367
+ BufferView(buffer=0, byteOffset=o, byteLength=l) for o, l in spans]
368
+ for k in present:
369
+ idx = len(g.textures)
370
+ o, l = img_spans[k]
371
+ mime = "image/png" if maps[k].lower().endswith(".png") else "image/jpeg"
372
+ g.bufferViews.append(BufferView(buffer=0, byteOffset=o, byteLength=l))
373
+ g.images.append(GImage(bufferView=len(g.bufferViews) - 1, mimeType=mime))
374
+ g.textures.append(Texture(source=idx, sampler=0))
375
+ if k == "color":
376
+ pbr.baseColorTexture = TextureInfo(index=idx)
377
+ elif k == "normal":
378
+ mat.normalTexture = NormalMaterialTexture(index=idx)
379
+ elif k == "roughness":
380
+ pbr.metallicRoughnessTexture = TextureInfo(index=idx)
381
+ g.materials = [mat]
382
+ g.accessors = [
383
+ Accessor(bufferView=0, componentType=5126, count=len(V), type="VEC3",
384
+ min=V.min(0).tolist(), max=V.max(0).tolist()),
385
+ Accessor(bufferView=1, componentType=5126, count=len(UV), type="VEC2"),
386
+ Accessor(bufferView=2, componentType=5125, count=len(IDX), type="SCALAR")]
387
+ g.buffers = [Buffer(byteLength=len(blob))]
388
+ g.set_binary_blob(blob)
389
+ return b"".join(g.save_to_bytes())
390
+
391
+
392
+ def textured_ground(positions: np.ndarray, uvs: np.ndarray, indices: np.ndarray,
393
+ maps: dict[str, str], tile: float = 1.0) -> Any:
394
+ """A loaded ground node textured with a CC0 material set."""
395
+ return gltf.load_gltf(_pbr_glb(positions, uvs, indices, maps, tile=tile,
396
+ alpha_mode="OPAQUE", double_sided=False)).group
397
+
398
+
399
+ def _scene_glb(positions: np.ndarray, uvs: np.ndarray,
400
+ primitives: list[dict[str, Any]]) -> bytes:
401
+ """Build a glTF from shared POSITION/TEXCOORD and a list of primitives.
402
+
403
+ Each primitive is a dict: ``indices`` (u4 array), and either ``maps`` (a CC0 map
404
+ dict) or ``color`` (rgba); optional ``alpha_mode``, ``double_sided``. Textures are
405
+ deduplicated by path across primitives.
406
+ """
407
+ from pygltflib import (
408
+ GLTF2, Scene, Node, Mesh, Primitive, Attributes, Accessor, BufferView,
409
+ Buffer, Material, PbrMetallicRoughness, Texture, Image as GImage, Sampler,
410
+ TextureInfo, NormalMaterialTexture,
411
+ )
412
+ V = np.asarray(positions, "<f4")
413
+ UV = np.asarray(uvs, "<f4")
414
+ blob, spans = b"", []
415
+ for arr in (V, UV):
416
+ raw = np.ascontiguousarray(arr).tobytes()
417
+ blob += b"\x00" * ((-len(blob)) % 4)
418
+ spans.append((len(blob), len(raw)))
419
+ blob += raw
420
+ idx_spans = []
421
+ for prim in primitives:
422
+ arr = np.asarray(prim["indices"], "<u4")
423
+ raw = np.ascontiguousarray(arr).tobytes()
424
+ blob += b"\x00" * ((-len(blob)) % 4)
425
+ idx_spans.append((len(blob), len(raw), len(arr)))
426
+ blob += raw
427
+
428
+ # collect images
429
+ img_data = {}
430
+ for prim in primitives:
431
+ for k in ("color", "normal", "roughness"):
432
+ p = (prim.get("maps") or {}).get(k)
433
+ if p:
434
+ if p not in img_data:
435
+ img_data[p] = open(p, "rb").read()
436
+
437
+ g = GLTF2()
438
+ g.scene = 0
439
+ g.scenes = [Scene(nodes=[0])]
440
+ g.nodes = [Node(mesh=0)]
441
+ g.samplers = [Sampler(wrapS=10497, wrapT=10497)]
442
+ g.accessors = [
443
+ Accessor(bufferView=0, componentType=5126, count=len(V), type="VEC3",
444
+ min=V.min(0).tolist(), max=V.max(0).tolist()),
445
+ Accessor(bufferView=1, componentType=5126, count=len(UV), type="VEC2")]
446
+ g.bufferViews = [BufferView(buffer=0, byteOffset=o, byteLength=l)
447
+ for o, l in spans]
448
+ # index accessors
449
+ idx_acc_base = len(g.accessors)
450
+ for o, l, n in idx_spans:
451
+ g.bufferViews.append(BufferView(buffer=0, byteOffset=o, byteLength=l))
452
+ g.accessors.append(Accessor(bufferView=len(g.bufferViews) - 1,
453
+ componentType=5125, count=n, type="SCALAR"))
454
+ # images
455
+ path_to_img = {}
456
+ g.images, g.textures = [], []
457
+ for path, data in img_data.items():
458
+ o = len(blob)
459
+ blob += data
460
+ g.bufferViews.append(BufferView(buffer=0, byteOffset=o, byteLength=len(data)))
461
+ mime = "image/png" if path.lower().endswith(".png") else "image/jpeg"
462
+ g.images.append(GImage(bufferView=len(g.bufferViews) - 1, mimeType=mime))
463
+ g.textures.append(Texture(source=len(g.images) - 1, sampler=0))
464
+ path_to_img[path] = len(g.textures) - 1
465
+ # materials + primitives
466
+ g.materials, prims = [], []
467
+ for pi, prim in enumerate(primitives):
468
+ pbr = PbrMetallicRoughness(metallicFactor=0.0, roughnessFactor=1.0)
469
+ maps = prim.get("maps") or {}
470
+ if maps.get("color"):
471
+ pbr.baseColorTexture = TextureInfo(index=path_to_img[maps["color"]])
472
+ if prim.get("color"):
473
+ pbr.baseColorFactor = list(prim["color"])
474
+ mat = Material(pbrMetallicRoughness=pbr,
475
+ alphaMode=prim.get("alpha_mode", "OPAQUE"),
476
+ doubleSided=prim.get("double_sided", True))
477
+ if prim.get("alpha_mode") == "MASK":
478
+ mat.alphaCutoff = 0.4
479
+ if maps.get("normal"):
480
+ mat.normalTexture = NormalMaterialTexture(index=path_to_img[maps["normal"]])
481
+ g.materials.append(mat)
482
+ prims.append(Primitive(attributes=Attributes(POSITION=0, TEXCOORD_0=1),
483
+ indices=idx_acc_base + pi, material=pi))
484
+ g.meshes = [Mesh(primitives=prims)]
485
+ g.buffers = [Buffer(byteLength=len(blob))]
486
+ g.set_binary_blob(blob)
487
+ return b"".join(g.save_to_bytes())
488
+
489
+
490
+ def slope01(height_fn: HeightFn, x: np.ndarray, z: np.ndarray,
491
+ eps: float = 1.5) -> np.ndarray:
492
+ """Terrain steepness at (x,z): 0 flat .. ~1 very steep (gradient magnitude)."""
493
+ x = np.asarray(x, "d")
494
+ z = np.asarray(z, "d")
495
+ hx = np.asarray(height_fn(x + eps, z), "d") - np.asarray(height_fn(x - eps, z), "d")
496
+ hz = np.asarray(height_fn(x, z + eps), "d") - np.asarray(height_fn(x, z - eps), "d")
497
+ return np.sqrt(hx * hx + hz * hz) / (2 * eps)
498
+
499
+
500
+ def ground_patch_split(center: Sequence[float], radius: float, height_fn: HeightFn,
501
+ dirt_maps: dict[str, str],
502
+ rock_maps: Optional[dict[str, str]], res: int = 64,
503
+ uv_scale: float = 6.0, water_level: float = 0.0,
504
+ rock_slope: float = 0.55) -> Any:
505
+ """Textured ground where flat areas are dirt and steep areas are rock.
506
+
507
+ Triangles are classified by their face slope, so slopes read as exposed stone.
508
+ Returns one glTF node with two textured primitives (dirt + rock)."""
509
+ cx, _, cz = center
510
+ xs = np.linspace(cx - radius, cx + radius, res)
511
+ zs = np.linspace(cz - radius, cz + radius, res)
512
+ gx, gz = np.meshgrid(xs, zs, indexing="ij")
513
+ gy = np.maximum(np.asarray(height_fn(gx, gz), "d"), water_level) + 0.03
514
+ V = np.stack([gx, gy, gz], -1).reshape(-1, 3)
515
+ UV = np.stack([(gx - cx) / uv_scale, (gz - cz) / uv_scale], -1).reshape(-1, 2)
516
+ dirt: list[int] = []
517
+ rock: list[int] = []
518
+ for i in range(res - 1):
519
+ for j in range(res - 1):
520
+ a = i * res + j
521
+ b, c, d = a + 1, a + res, a + res + 1
522
+ for tri in ((a, b, c), (b, d, c)):
523
+ p0, p1, p2 = V[tri[0]], V[tri[1]], V[tri[2]]
524
+ n = np.cross(p1 - p0, p2 - p0)
525
+ ny = abs(n[1]) / (np.linalg.norm(n) + 1e-9)
526
+ (rock if (1.0 - ny) > rock_slope else dirt).extend(tri)
527
+ prims = [{"indices": np.array(dirt or [0, 0, 0], "u4"), "maps": dirt_maps,
528
+ "double_sided": False}]
529
+ if rock_maps and rock:
530
+ prims.append({"indices": np.array(rock, "u4"), "maps": rock_maps,
531
+ "double_sided": False})
532
+ return gltf.load_gltf(_scene_glb(V, UV, prims)).group
533
+
534
+
535
+ def _load_rgb(path: str, size: int) -> np.ndarray:
536
+ from PIL import Image
537
+ return np.asarray(Image.open(path).convert("RGB").resize((size, size)),
538
+ np.float32)
539
+
540
+
541
+ def _upsample(grid: np.ndarray, size: int) -> np.ndarray:
542
+ from PIL import Image
543
+ g = np.asarray(grid, np.float32)
544
+ im = Image.fromarray((np.clip(g, 0, 1) * 255).astype(np.uint8))
545
+ return np.asarray(im.resize((size, size)), np.float32) / 255.0
546
+
547
+
548
+ def blended_ground_texture(mat_paths: Sequence[str], slope_grid: np.ndarray,
549
+ size: int = 1024, tile_px: int = 256,
550
+ seed: int = 5) -> np.ndarray:
551
+ """Bake a ground albedo by blending [dirt, rock, needle] materials with a large-
552
+ scale 'painting': rock where the terrain is steep, needle-litter and dirt in
553
+ low-frequency patches. This is texture splatting realised at bake time — one
554
+ control field (noise + slope) chooses/blends N tiled materials per texel."""
555
+ tiles = []
556
+ reps = size // tile_px + 1
557
+ for p in mat_paths:
558
+ t = _load_rgb(p, tile_px)
559
+ tiles.append(np.tile(t, (reps, reps, 1))[:size, :size])
560
+ slope = _upsample(slope_grid, size)
561
+ n_rock = _noise2d(size, size, 24, seed)
562
+ n_needle = _noise2d(size, size, 18, seed + 3)
563
+ w_rock = np.clip(slope * 1.8 + (n_rock - 0.5) * 1.2, 0, 1)
564
+ w_needle = np.clip((n_needle - 0.5) / 0.22, 0, 1) * (1 - w_rock)
565
+ w_dirt = np.clip(1.0 - w_rock - w_needle, 0.02, 1)
566
+ W = np.stack([w_dirt, w_rock, w_needle], -1)
567
+ W /= W.sum(-1, keepdims=True) + 1e-6
568
+ out = sum(tiles[i] * W[..., i:i + 1] for i in range(len(tiles)))
569
+ return np.clip(out, 0, 255).astype(np.uint8)
570
+
571
+
572
+ def ground_patch_blended(center: Sequence[float], radius: float,
573
+ height_fn: HeightFn, mat_paths: Sequence[str],
574
+ res: int = 64, uv_scale: float = 8.0,
575
+ water_level: float = 0.0, tex_size: int = 1024,
576
+ seed: int = 5) -> Any:
577
+ """A ground patch whose single albedo is a splat-blended bake of several CC0
578
+ materials (dirt/rock/needle), painted by slope + noise. UVs span the patch 1:1 so
579
+ the baked painting maps across it; material tiling inside the bake gives detail."""
580
+ cx, _, cz = center
581
+ xs = np.linspace(cx - radius, cx + radius, res)
582
+ zs = np.linspace(cz - radius, cz + radius, res)
583
+ gx, gz = np.meshgrid(xs, zs, indexing="ij")
584
+ gy = np.maximum(np.asarray(height_fn(gx, gz), "d"), water_level) + 0.03
585
+ slope = slope01(height_fn, gx, gz)
586
+ V = np.stack([gx, gy, gz], -1).reshape(-1, 3)
587
+ # UV covers the patch once; the bake already tiles the materials internally.
588
+ UV = np.stack([(gx - cx) / (2 * radius) + 0.5,
589
+ (gz - cz) / (2 * radius) + 0.5], -1).reshape(-1, 2)
590
+ idx = []
591
+ for i in range(res - 1):
592
+ for j in range(res - 1):
593
+ a = i * res + j
594
+ b, c, d = a + 1, a + res, a + res + 1
595
+ idx += [a, b, c, b, d, c]
596
+ tex = blended_ground_texture(mat_paths, slope, size=tex_size,
597
+ tile_px=tex_size // 6, seed=seed)
598
+ return gltf.load_gltf(_textured_glb(V.astype("f4"), UV.astype("f4"),
599
+ np.array(idx, "u4"), tex,
600
+ alpha_mode="OPAQUE",
601
+ double_sided=False)).group
602
+
603
+
604
+ def ground_patch_node(center: Sequence[float], radius: float, height_fn: HeightFn,
605
+ maps: dict[str, str], res: int = 48, uv_scale: float = 8.0,
606
+ water_level: float = 0.0) -> Any:
607
+ """A CC0-textured ground mesh over a disc-ish square around `center`.
608
+
609
+ Meshes `height_fn` at `res`x`res`, UV-tiled every `uv_scale` metres, textured with
610
+ the given CC0 map set (colour + normal + roughness). Sits over the streamed terrain
611
+ as the close-up, detailed ground the coarse tiles can't provide."""
612
+ cx, _, cz = center
613
+ xs = np.linspace(cx - radius, cx + radius, res)
614
+ zs = np.linspace(cz - radius, cz + radius, res)
615
+ gx, gz = np.meshgrid(xs, zs, indexing="ij")
616
+ gy = np.maximum(np.asarray(height_fn(gx, gz), "d"), water_level) + 0.03
617
+ V = np.stack([gx, gy, gz], -1).reshape(-1, 3)
618
+ UV = np.stack([(gx - cx) / uv_scale, (gz - cz) / uv_scale], -1).reshape(-1, 2)
619
+ idx = []
620
+ for i in range(res - 1):
621
+ for j in range(res - 1):
622
+ a = i * res + j
623
+ b, c, d = a + 1, a + res, a + res + 1
624
+ idx += [a, b, c, b, d, c]
625
+ return textured_ground(V, UV, np.array(idx, "u4"), maps, tile=1.0)
626
+
627
+
628
+ def _uv_sphere(rings: int = 5, sectors: int = 7
629
+ ) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
630
+ V, UV, IDX = [], [], []
631
+ for i in range(rings + 1):
632
+ phi = np.pi * i / rings
633
+ for j in range(sectors + 1):
634
+ th = 2 * np.pi * j / sectors
635
+ V.append((np.sin(phi) * np.cos(th), np.cos(phi), np.sin(phi) * np.sin(th)))
636
+ UV.append((j / sectors, i / rings))
637
+ w = sectors + 1
638
+ for i in range(rings):
639
+ for j in range(sectors):
640
+ a = i * w + j
641
+ b, c, d = a + 1, a + w, a + w + 1
642
+ IDX += [a, c, b, b, c, d]
643
+ return np.array(V, "f4"), np.array(UV, "f4"), np.array(IDX, "u4")
644
+
645
+
646
+ def rock_glb(maps: dict[str, str], seed: int = 0) -> bytes:
647
+ """A rough low-poly boulder textured with a CC0 rock material.
648
+
649
+ The origin sits *inside* the rock (base below y=0) so that, placed at terrain
650
+ height, the boulder is partly buried rather than floating on the surface."""
651
+ V, UV, IDX = _uv_sphere(5, 7)
652
+ rng = np.random.default_rng(seed)
653
+ V = V * (1.0 + 0.4 * (rng.random(len(V)) - 0.5))[:, None]
654
+ V[:, 0] *= rng.uniform(0.8, 1.4)
655
+ V[:, 2] *= rng.uniform(0.8, 1.4)
656
+ V[:, 1] *= rng.uniform(0.5, 0.9)
657
+ V[:, 1] -= V[:, 1].min() # base at 0
658
+ V[:, 1] -= 0.4 * V[:, 1].max() # then bury ~40% below the ground
659
+ return _scene_glb(V.astype("f4"), (UV * 2.0).astype("f4"),
660
+ [{"indices": IDX, "maps": maps, "double_sided": False}])
661
+
662
+
663
+ def rock(maps: dict[str, str], seed: int = 0) -> Any:
664
+ return gltf.load_gltf(rock_glb(maps, seed)).group
665
+
666
+
667
+ def flower_texture(size: int = 64, seed: int = 0) -> np.ndarray:
668
+ """RGBA wildflower card: a couple of bright blooms on a transparent background."""
669
+ img = np.zeros((size, size, 4), np.uint8)
670
+ rng = np.random.default_rng(seed)
671
+ yy, xx = np.mgrid[0:size, 0:size]
672
+ palette = [(235, 90, 90), (245, 225, 95), (205, 125, 225), (245, 245, 245),
673
+ (245, 150, 65)]
674
+ for _ in range(2):
675
+ cx = rng.uniform(0.3, 0.7) * size
676
+ cy = rng.uniform(0.15, 0.5) * size
677
+ col = palette[rng.integers(len(palette))]
678
+ r = rng.uniform(0.09, 0.15) * size
679
+ # stem
680
+ stem = (np.abs(xx - cx) < size * 0.02) & (yy > cy)
681
+ img[stem] = (60, 120, 45, 255)
682
+ for p in range(6):
683
+ a = 2 * np.pi * p / 6
684
+ px, py = cx + np.cos(a) * r, cy + np.sin(a) * r
685
+ petal = (xx - px) ** 2 + (yy - py) ** 2 < (r * 0.6) ** 2
686
+ img[petal] = (*col, 255)
687
+ centre = (xx - cx) ** 2 + (yy - cy) ** 2 < (r * 0.42) ** 2
688
+ img[centre] = (250, 215, 80, 255)
689
+ # flip so stems point down in UV
690
+ return img[::-1]
691
+
692
+
693
+ def flower_card(width: float = 0.5, height: float = 0.5, seed: int = 0) -> Any:
694
+ V, UV, IDX = _crossed_quads(width, height, planes=2)
695
+ return gltf.load_gltf(_textured_glb(V, UV, IDX, flower_texture(seed=seed),
696
+ alpha_mode="MASK")).group
697
+
698
+
699
+ def branch_glb(bark_path: Optional[str] = None, length: float = 1.4,
700
+ seed: int = 0) -> bytes:
701
+ """A fallen stick lying on the ground — a thin gnarled cylinder laid horizontal.
702
+
703
+ Modelled along +Z at ground level (so instance yaw spins its direction), with a
704
+ slight bend and taper; reads as a branch, not a post."""
705
+ rng = np.random.default_rng(seed)
706
+ V, UV, IDX = _cylinder(0.035, length, sides=5)
707
+ V = V.copy()
708
+ # Lay it down: the cylinder is built along +Y, rotate onto +Z.
709
+ V = np.stack([V[:, 0], V[:, 2] + 0.035, V[:, 1] - length * 0.5], axis=1)
710
+ V[:, 1] += 0.05 * np.sin(V[:, 2] * 2.0) # gentle bend
711
+ V[:, 0] += 0.02 * rng.standard_normal(len(V)) # gnarl
712
+ if bark_path:
713
+ maps = {"color": bark_path}
714
+ else:
715
+ import tempfile
716
+ from PIL import Image
717
+ p = os.path.join(tempfile.gettempdir(), "oglc_branch_bark.png")
718
+ if not os.path.exists(p):
719
+ Image.fromarray(bark_texture(64), "RGB").save(p)
720
+ maps = {"color": p}
721
+ return _scene_glb(V.astype("f4"), (UV * np.array([1, 3], "f4")).astype("f4"),
722
+ [{"indices": IDX, "maps": maps, "double_sided": False}])
723
+
724
+
725
+ def branch(bark_path: Optional[str] = None, length: float = 1.4,
726
+ seed: int = 0) -> Any:
727
+ return gltf.load_gltf(branch_glb(bark_path, length, seed)).group
728
+
729
+
730
+ def tree_texture(size: int = 128, seed: int = 0) -> np.ndarray:
731
+ """RGBA conifer silhouette (stacked foliage + trunk) on a transparent background."""
732
+ img = np.zeros((size, size, 4), np.uint8)
733
+ yy, xx = np.mgrid[0:size, 0:size]
734
+ cx = size / 2.0
735
+ trunk = (np.abs(xx - cx) < size * 0.04) & (yy > size * 0.72)
736
+ img[trunk] = (70, 46, 26, 255)
737
+ rng = np.random.default_rng(seed)
738
+ for y0, y1, w in ((0.03, 0.42, 0.24), (0.28, 0.62, 0.33), (0.5, 0.80, 0.42)):
739
+ row = yy / size
740
+ within = (row >= y0) & (row <= y1)
741
+ t = np.clip((row - y0) / (y1 - y0 + 1e-6), 0, 1)
742
+ halfw = w * size * t
743
+ band = within & (np.abs(xx - cx) < halfw)
744
+ g = (95 + 55 * (1 - t)).astype(np.uint8)
745
+ img[band, 0] = 28
746
+ img[band, 1] = np.clip(g[band], 0, 255)
747
+ img[band, 2] = 34
748
+ img[band, 3] = 255
749
+ # a little noise on the foliage edge
750
+ edge = (img[:, :, 3] > 0) & (rng.random((size, size)) < 0.08)
751
+ img[edge, 3] = 0
752
+ return img[::-1]
753
+
754
+
755
+ def tree_billboard(width: float = 6.0, height: float = 10.0, seed: int = 0) -> Any:
756
+ """A cheap 2-plane conifer billboard for distant trees (no shadow-casting)."""
757
+ V, UV, IDX = _crossed_quads(width, height, planes=2)
758
+ node = gltf.load_gltf(_textured_glb(V, UV, IDX, tree_texture(seed=seed),
759
+ alpha_mode="MASK")).group
760
+ return no_shadow(node)
761
+
762
+
763
+ def no_shadow(node: Any) -> Any:
764
+ """Mark every Shape under `node` as not casting shadows (returns the node).
765
+
766
+ Dense alpha foliage into every shadow cascade is the dominant cost; opting grass
767
+ out keeps the framerate up while trees/rocks still cast."""
768
+ stack = [node]
769
+ while stack:
770
+ n = stack.pop()
771
+ if type(n).__name__ == "Shape":
772
+ n.castsShadow = False
773
+ stack.extend(getattr(n, "children", None) or [])
774
+ return node
775
+
776
+
777
+ def grass_card(width: float = 0.9, height: float = 0.6, seed: int = 3,
778
+ planes: int = 3) -> Any:
779
+ """A loaded, mountable grass-card node (share it across instances)."""
780
+ return gltf.load_gltf(grass_card_glb(width, height, seed, planes)).group
781
+
782
+
783
+ def conifer(height: float = 9.0, seed: int = 11,
784
+ bark_path: Optional[str] = None) -> Any:
785
+ """A loaded, mountable textured-conifer node (share it across instances)."""
786
+ return gltf.load_gltf(conifer_glb(height, seed, bark_path=bark_path)).group