warp-lang 1.9.0__py3-none-win_amd64.whl → 1.10.0rc2__py3-none-win_amd64.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.

Potentially problematic release.


This version of warp-lang might be problematic. Click here for more details.

Files changed (350) hide show
  1. warp/__init__.py +301 -287
  2. warp/__init__.pyi +2220 -313
  3. warp/_src/__init__.py +14 -0
  4. warp/_src/autograd.py +1075 -0
  5. warp/_src/build.py +618 -0
  6. warp/_src/build_dll.py +640 -0
  7. warp/{builtins.py → _src/builtins.py} +1497 -226
  8. warp/_src/codegen.py +4359 -0
  9. warp/{config.py → _src/config.py} +178 -169
  10. warp/_src/constants.py +57 -0
  11. warp/_src/context.py +8294 -0
  12. warp/_src/dlpack.py +462 -0
  13. warp/_src/fabric.py +355 -0
  14. warp/_src/fem/__init__.py +14 -0
  15. warp/_src/fem/adaptivity.py +508 -0
  16. warp/_src/fem/cache.py +687 -0
  17. warp/_src/fem/dirichlet.py +188 -0
  18. warp/{fem → _src/fem}/domain.py +40 -30
  19. warp/_src/fem/field/__init__.py +131 -0
  20. warp/_src/fem/field/field.py +701 -0
  21. warp/{fem → _src/fem}/field/nodal_field.py +30 -15
  22. warp/{fem → _src/fem}/field/restriction.py +1 -1
  23. warp/{fem → _src/fem}/field/virtual.py +53 -27
  24. warp/_src/fem/geometry/__init__.py +32 -0
  25. warp/{fem → _src/fem}/geometry/adaptive_nanogrid.py +77 -163
  26. warp/_src/fem/geometry/closest_point.py +97 -0
  27. warp/{fem → _src/fem}/geometry/deformed_geometry.py +14 -22
  28. warp/{fem → _src/fem}/geometry/element.py +32 -10
  29. warp/{fem → _src/fem}/geometry/geometry.py +48 -20
  30. warp/{fem → _src/fem}/geometry/grid_2d.py +12 -23
  31. warp/{fem → _src/fem}/geometry/grid_3d.py +12 -23
  32. warp/{fem → _src/fem}/geometry/hexmesh.py +40 -63
  33. warp/{fem → _src/fem}/geometry/nanogrid.py +255 -248
  34. warp/{fem → _src/fem}/geometry/partition.py +121 -63
  35. warp/{fem → _src/fem}/geometry/quadmesh.py +26 -45
  36. warp/{fem → _src/fem}/geometry/tetmesh.py +40 -63
  37. warp/{fem → _src/fem}/geometry/trimesh.py +26 -45
  38. warp/{fem → _src/fem}/integrate.py +164 -158
  39. warp/_src/fem/linalg.py +383 -0
  40. warp/_src/fem/operator.py +396 -0
  41. warp/_src/fem/polynomial.py +229 -0
  42. warp/{fem → _src/fem}/quadrature/pic_quadrature.py +15 -20
  43. warp/{fem → _src/fem}/quadrature/quadrature.py +95 -47
  44. warp/_src/fem/space/__init__.py +248 -0
  45. warp/{fem → _src/fem}/space/basis_function_space.py +20 -11
  46. warp/_src/fem/space/basis_space.py +679 -0
  47. warp/{fem → _src/fem}/space/dof_mapper.py +3 -3
  48. warp/{fem → _src/fem}/space/function_space.py +14 -13
  49. warp/{fem → _src/fem}/space/grid_2d_function_space.py +4 -7
  50. warp/{fem → _src/fem}/space/grid_3d_function_space.py +4 -4
  51. warp/{fem → _src/fem}/space/hexmesh_function_space.py +4 -10
  52. warp/{fem → _src/fem}/space/nanogrid_function_space.py +3 -9
  53. warp/{fem → _src/fem}/space/partition.py +117 -60
  54. warp/{fem → _src/fem}/space/quadmesh_function_space.py +4 -10
  55. warp/{fem → _src/fem}/space/restriction.py +66 -33
  56. warp/_src/fem/space/shape/__init__.py +152 -0
  57. warp/{fem → _src/fem}/space/shape/cube_shape_function.py +9 -9
  58. warp/{fem → _src/fem}/space/shape/shape_function.py +8 -9
  59. warp/{fem → _src/fem}/space/shape/square_shape_function.py +6 -6
  60. warp/{fem → _src/fem}/space/shape/tet_shape_function.py +3 -3
  61. warp/{fem → _src/fem}/space/shape/triangle_shape_function.py +3 -3
  62. warp/{fem → _src/fem}/space/tetmesh_function_space.py +3 -9
  63. warp/_src/fem/space/topology.py +459 -0
  64. warp/{fem → _src/fem}/space/trimesh_function_space.py +3 -9
  65. warp/_src/fem/types.py +112 -0
  66. warp/_src/fem/utils.py +486 -0
  67. warp/_src/jax.py +186 -0
  68. warp/_src/jax_experimental/__init__.py +14 -0
  69. warp/_src/jax_experimental/custom_call.py +387 -0
  70. warp/_src/jax_experimental/ffi.py +1284 -0
  71. warp/_src/jax_experimental/xla_ffi.py +656 -0
  72. warp/_src/marching_cubes.py +708 -0
  73. warp/_src/math.py +414 -0
  74. warp/_src/optim/__init__.py +14 -0
  75. warp/_src/optim/adam.py +163 -0
  76. warp/_src/optim/linear.py +1606 -0
  77. warp/_src/optim/sgd.py +112 -0
  78. warp/_src/paddle.py +406 -0
  79. warp/_src/render/__init__.py +14 -0
  80. warp/_src/render/imgui_manager.py +289 -0
  81. warp/_src/render/render_opengl.py +3636 -0
  82. warp/_src/render/render_usd.py +937 -0
  83. warp/_src/render/utils.py +160 -0
  84. warp/_src/sparse.py +2716 -0
  85. warp/_src/tape.py +1206 -0
  86. warp/{thirdparty → _src/thirdparty}/unittest_parallel.py +9 -2
  87. warp/_src/torch.py +391 -0
  88. warp/_src/types.py +5870 -0
  89. warp/_src/utils.py +1693 -0
  90. warp/autograd.py +12 -1054
  91. warp/bin/warp-clang.dll +0 -0
  92. warp/bin/warp.dll +0 -0
  93. warp/build.py +8 -588
  94. warp/build_dll.py +6 -471
  95. warp/codegen.py +6 -4246
  96. warp/constants.py +6 -39
  97. warp/context.py +12 -7851
  98. warp/dlpack.py +6 -444
  99. warp/examples/distributed/example_jacobi_mpi.py +4 -5
  100. warp/examples/fem/example_adaptive_grid.py +1 -1
  101. warp/examples/fem/example_apic_fluid.py +1 -1
  102. warp/examples/fem/example_burgers.py +8 -8
  103. warp/examples/fem/example_diffusion.py +1 -1
  104. warp/examples/fem/example_distortion_energy.py +1 -1
  105. warp/examples/fem/example_mixed_elasticity.py +2 -2
  106. warp/examples/fem/example_navier_stokes.py +1 -1
  107. warp/examples/fem/example_nonconforming_contact.py +7 -7
  108. warp/examples/fem/example_stokes.py +1 -1
  109. warp/examples/fem/example_stokes_transfer.py +1 -1
  110. warp/examples/fem/utils.py +2 -2
  111. warp/examples/interop/example_jax_callable.py +1 -1
  112. warp/examples/interop/example_jax_ffi_callback.py +1 -1
  113. warp/examples/interop/example_jax_kernel.py +3 -2
  114. warp/examples/tile/example_tile_mcgp.py +191 -0
  115. warp/fabric.py +6 -337
  116. warp/fem/__init__.py +159 -97
  117. warp/fem/adaptivity.py +7 -489
  118. warp/fem/cache.py +9 -648
  119. warp/fem/dirichlet.py +6 -184
  120. warp/fem/field/__init__.py +8 -109
  121. warp/fem/field/field.py +7 -652
  122. warp/fem/geometry/__init__.py +7 -18
  123. warp/fem/geometry/closest_point.py +11 -77
  124. warp/fem/linalg.py +18 -366
  125. warp/fem/operator.py +11 -369
  126. warp/fem/polynomial.py +9 -209
  127. warp/fem/space/__init__.py +5 -211
  128. warp/fem/space/basis_space.py +6 -662
  129. warp/fem/space/shape/__init__.py +41 -118
  130. warp/fem/space/topology.py +6 -437
  131. warp/fem/types.py +6 -81
  132. warp/fem/utils.py +11 -444
  133. warp/jax.py +8 -165
  134. warp/jax_experimental/__init__.py +14 -1
  135. warp/jax_experimental/custom_call.py +8 -342
  136. warp/jax_experimental/ffi.py +17 -853
  137. warp/jax_experimental/xla_ffi.py +5 -596
  138. warp/marching_cubes.py +5 -689
  139. warp/math.py +16 -393
  140. warp/native/array.h +385 -37
  141. warp/native/builtin.h +316 -39
  142. warp/native/bvh.cpp +43 -9
  143. warp/native/bvh.cu +62 -27
  144. warp/native/bvh.h +310 -309
  145. warp/native/clang/clang.cpp +102 -97
  146. warp/native/coloring.cpp +0 -1
  147. warp/native/crt.h +208 -0
  148. warp/native/exports.h +156 -0
  149. warp/native/hashgrid.cu +2 -0
  150. warp/native/intersect.h +24 -1
  151. warp/native/intersect_tri.h +44 -35
  152. warp/native/mat.h +1456 -276
  153. warp/native/mesh.cpp +4 -4
  154. warp/native/mesh.cu +4 -2
  155. warp/native/mesh.h +176 -61
  156. warp/native/quat.h +0 -52
  157. warp/native/scan.cu +2 -0
  158. warp/native/sort.cu +22 -13
  159. warp/native/sort.h +2 -0
  160. warp/native/sparse.cu +7 -3
  161. warp/native/spatial.h +12 -0
  162. warp/native/tile.h +837 -70
  163. warp/native/tile_radix_sort.h +1 -1
  164. warp/native/tile_reduce.h +394 -46
  165. warp/native/tile_scan.h +4 -4
  166. warp/native/vec.h +469 -53
  167. warp/native/version.h +23 -0
  168. warp/native/volume.cpp +1 -1
  169. warp/native/volume.cu +1 -0
  170. warp/native/volume.h +1 -1
  171. warp/native/volume_builder.cu +2 -0
  172. warp/native/warp.cpp +60 -32
  173. warp/native/warp.cu +313 -201
  174. warp/native/warp.h +14 -11
  175. warp/optim/__init__.py +6 -3
  176. warp/optim/adam.py +6 -145
  177. warp/optim/linear.py +14 -1585
  178. warp/optim/sgd.py +6 -94
  179. warp/paddle.py +6 -388
  180. warp/render/__init__.py +8 -4
  181. warp/render/imgui_manager.py +7 -267
  182. warp/render/render_opengl.py +6 -3616
  183. warp/render/render_usd.py +6 -918
  184. warp/render/utils.py +6 -142
  185. warp/sparse.py +37 -2563
  186. warp/tape.py +6 -1188
  187. warp/tests/__main__.py +1 -1
  188. warp/tests/cuda/test_async.py +4 -4
  189. warp/tests/cuda/test_conditional_captures.py +1 -1
  190. warp/tests/cuda/test_multigpu.py +1 -1
  191. warp/tests/cuda/test_streams.py +58 -1
  192. warp/tests/geometry/test_bvh.py +157 -22
  193. warp/tests/geometry/test_hash_grid.py +38 -0
  194. warp/tests/geometry/test_marching_cubes.py +0 -1
  195. warp/tests/geometry/test_mesh.py +5 -3
  196. warp/tests/geometry/test_mesh_query_aabb.py +5 -12
  197. warp/tests/geometry/test_mesh_query_point.py +5 -2
  198. warp/tests/geometry/test_mesh_query_ray.py +15 -3
  199. warp/tests/geometry/test_volume_write.py +5 -5
  200. warp/tests/interop/test_dlpack.py +14 -14
  201. warp/tests/interop/test_jax.py +1382 -79
  202. warp/tests/interop/test_paddle.py +1 -1
  203. warp/tests/test_adam.py +0 -1
  204. warp/tests/test_arithmetic.py +9 -9
  205. warp/tests/test_array.py +529 -100
  206. warp/tests/test_array_reduce.py +3 -3
  207. warp/tests/test_atomic.py +12 -8
  208. warp/tests/test_atomic_bitwise.py +209 -0
  209. warp/tests/test_atomic_cas.py +4 -4
  210. warp/tests/test_bool.py +2 -2
  211. warp/tests/test_builtins_resolution.py +5 -571
  212. warp/tests/test_codegen.py +34 -15
  213. warp/tests/test_conditional.py +1 -1
  214. warp/tests/test_context.py +6 -6
  215. warp/tests/test_copy.py +242 -161
  216. warp/tests/test_ctypes.py +3 -3
  217. warp/tests/test_devices.py +24 -2
  218. warp/tests/test_examples.py +16 -84
  219. warp/tests/test_fabricarray.py +35 -35
  220. warp/tests/test_fast_math.py +0 -2
  221. warp/tests/test_fem.py +60 -14
  222. warp/tests/test_fixedarray.py +3 -3
  223. warp/tests/test_func.py +8 -5
  224. warp/tests/test_generics.py +1 -1
  225. warp/tests/test_indexedarray.py +24 -24
  226. warp/tests/test_intersect.py +39 -9
  227. warp/tests/test_large.py +1 -1
  228. warp/tests/test_lerp.py +3 -1
  229. warp/tests/test_linear_solvers.py +1 -1
  230. warp/tests/test_map.py +49 -4
  231. warp/tests/test_mat.py +52 -62
  232. warp/tests/test_mat_constructors.py +4 -5
  233. warp/tests/test_mat_lite.py +1 -1
  234. warp/tests/test_mat_scalar_ops.py +121 -121
  235. warp/tests/test_math.py +34 -0
  236. warp/tests/test_module_aot.py +4 -4
  237. warp/tests/test_modules_lite.py +28 -2
  238. warp/tests/test_print.py +11 -11
  239. warp/tests/test_quat.py +93 -58
  240. warp/tests/test_runlength_encode.py +1 -1
  241. warp/tests/test_scalar_ops.py +38 -10
  242. warp/tests/test_smoothstep.py +1 -1
  243. warp/tests/test_sparse.py +126 -15
  244. warp/tests/test_spatial.py +105 -87
  245. warp/tests/test_special_values.py +6 -6
  246. warp/tests/test_static.py +7 -7
  247. warp/tests/test_struct.py +13 -2
  248. warp/tests/test_triangle_closest_point.py +48 -1
  249. warp/tests/test_tuple.py +96 -0
  250. warp/tests/test_types.py +82 -9
  251. warp/tests/test_utils.py +52 -52
  252. warp/tests/test_vec.py +29 -29
  253. warp/tests/test_vec_constructors.py +5 -5
  254. warp/tests/test_vec_scalar_ops.py +97 -97
  255. warp/tests/test_version.py +75 -0
  256. warp/tests/tile/test_tile.py +239 -0
  257. warp/tests/tile/test_tile_atomic_bitwise.py +403 -0
  258. warp/tests/tile/test_tile_cholesky.py +7 -4
  259. warp/tests/tile/test_tile_load.py +26 -2
  260. warp/tests/tile/test_tile_mathdx.py +3 -3
  261. warp/tests/tile/test_tile_matmul.py +1 -1
  262. warp/tests/tile/test_tile_mlp.py +2 -4
  263. warp/tests/tile/test_tile_reduce.py +214 -13
  264. warp/tests/unittest_suites.py +6 -14
  265. warp/tests/unittest_utils.py +10 -9
  266. warp/tests/walkthrough_debug.py +3 -1
  267. warp/torch.py +6 -373
  268. warp/types.py +29 -5750
  269. warp/utils.py +10 -1659
  270. {warp_lang-1.9.0.dist-info → warp_lang-1.10.0rc2.dist-info}/METADATA +47 -103
  271. warp_lang-1.10.0rc2.dist-info/RECORD +468 -0
  272. warp_lang-1.10.0rc2.dist-info/licenses/licenses/Gaia-LICENSE.txt +6 -0
  273. warp_lang-1.10.0rc2.dist-info/licenses/licenses/appdirs-LICENSE.txt +22 -0
  274. warp_lang-1.10.0rc2.dist-info/licenses/licenses/asset_pixel_jpg-LICENSE.txt +3 -0
  275. warp_lang-1.10.0rc2.dist-info/licenses/licenses/cuda-LICENSE.txt +1582 -0
  276. warp_lang-1.10.0rc2.dist-info/licenses/licenses/dlpack-LICENSE.txt +201 -0
  277. warp_lang-1.10.0rc2.dist-info/licenses/licenses/fp16-LICENSE.txt +28 -0
  278. warp_lang-1.10.0rc2.dist-info/licenses/licenses/libmathdx-LICENSE.txt +220 -0
  279. warp_lang-1.10.0rc2.dist-info/licenses/licenses/llvm-LICENSE.txt +279 -0
  280. warp_lang-1.10.0rc2.dist-info/licenses/licenses/moller-LICENSE.txt +16 -0
  281. warp_lang-1.10.0rc2.dist-info/licenses/licenses/nanovdb-LICENSE.txt +2 -0
  282. warp_lang-1.10.0rc2.dist-info/licenses/licenses/nvrtc-LICENSE.txt +1592 -0
  283. warp_lang-1.10.0rc2.dist-info/licenses/licenses/svd-LICENSE.txt +23 -0
  284. warp_lang-1.10.0rc2.dist-info/licenses/licenses/unittest_parallel-LICENSE.txt +21 -0
  285. warp_lang-1.10.0rc2.dist-info/licenses/licenses/usd-LICENSE.txt +213 -0
  286. warp_lang-1.10.0rc2.dist-info/licenses/licenses/windingnumber-LICENSE.txt +21 -0
  287. warp/examples/assets/cartpole.urdf +0 -110
  288. warp/examples/assets/crazyflie.usd +0 -0
  289. warp/examples/assets/nv_ant.xml +0 -92
  290. warp/examples/assets/nv_humanoid.xml +0 -183
  291. warp/examples/assets/quadruped.urdf +0 -268
  292. warp/examples/optim/example_bounce.py +0 -266
  293. warp/examples/optim/example_cloth_throw.py +0 -228
  294. warp/examples/optim/example_drone.py +0 -870
  295. warp/examples/optim/example_inverse_kinematics.py +0 -182
  296. warp/examples/optim/example_inverse_kinematics_torch.py +0 -191
  297. warp/examples/optim/example_softbody_properties.py +0 -400
  298. warp/examples/optim/example_spring_cage.py +0 -245
  299. warp/examples/optim/example_trajectory.py +0 -227
  300. warp/examples/sim/example_cartpole.py +0 -143
  301. warp/examples/sim/example_cloth.py +0 -225
  302. warp/examples/sim/example_cloth_self_contact.py +0 -316
  303. warp/examples/sim/example_granular.py +0 -130
  304. warp/examples/sim/example_granular_collision_sdf.py +0 -202
  305. warp/examples/sim/example_jacobian_ik.py +0 -244
  306. warp/examples/sim/example_particle_chain.py +0 -124
  307. warp/examples/sim/example_quadruped.py +0 -203
  308. warp/examples/sim/example_rigid_chain.py +0 -203
  309. warp/examples/sim/example_rigid_contact.py +0 -195
  310. warp/examples/sim/example_rigid_force.py +0 -133
  311. warp/examples/sim/example_rigid_gyroscopic.py +0 -115
  312. warp/examples/sim/example_rigid_soft_contact.py +0 -140
  313. warp/examples/sim/example_soft_body.py +0 -196
  314. warp/examples/tile/example_tile_walker.py +0 -327
  315. warp/sim/__init__.py +0 -74
  316. warp/sim/articulation.py +0 -793
  317. warp/sim/collide.py +0 -2570
  318. warp/sim/graph_coloring.py +0 -307
  319. warp/sim/import_mjcf.py +0 -791
  320. warp/sim/import_snu.py +0 -227
  321. warp/sim/import_urdf.py +0 -579
  322. warp/sim/import_usd.py +0 -898
  323. warp/sim/inertia.py +0 -357
  324. warp/sim/integrator.py +0 -245
  325. warp/sim/integrator_euler.py +0 -2000
  326. warp/sim/integrator_featherstone.py +0 -2101
  327. warp/sim/integrator_vbd.py +0 -2487
  328. warp/sim/integrator_xpbd.py +0 -3295
  329. warp/sim/model.py +0 -4821
  330. warp/sim/particles.py +0 -121
  331. warp/sim/render.py +0 -431
  332. warp/sim/utils.py +0 -431
  333. warp/tests/sim/disabled_kinematics.py +0 -244
  334. warp/tests/sim/test_cloth.py +0 -863
  335. warp/tests/sim/test_collision.py +0 -743
  336. warp/tests/sim/test_coloring.py +0 -347
  337. warp/tests/sim/test_inertia.py +0 -161
  338. warp/tests/sim/test_model.py +0 -226
  339. warp/tests/sim/test_sim_grad.py +0 -287
  340. warp/tests/sim/test_sim_grad_bounce_linear.py +0 -212
  341. warp/tests/sim/test_sim_kinematics.py +0 -98
  342. warp/thirdparty/__init__.py +0 -0
  343. warp_lang-1.9.0.dist-info/RECORD +0 -456
  344. /warp/{fem → _src/fem}/quadrature/__init__.py +0 -0
  345. /warp/{tests/sim → _src/thirdparty}/__init__.py +0 -0
  346. /warp/{thirdparty → _src/thirdparty}/appdirs.py +0 -0
  347. /warp/{thirdparty → _src/thirdparty}/dlpack.py +0 -0
  348. {warp_lang-1.9.0.dist-info → warp_lang-1.10.0rc2.dist-info}/WHEEL +0 -0
  349. {warp_lang-1.9.0.dist-info → warp_lang-1.10.0rc2.dist-info}/licenses/LICENSE.md +0 -0
  350. {warp_lang-1.9.0.dist-info → warp_lang-1.10.0rc2.dist-info}/top_level.txt +0 -0
@@ -1,4 +1,4 @@
1
- # SPDX-FileCopyrightText: Copyright (c) 2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
1
+ # SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2
2
  # SPDX-License-Identifier: Apache-2.0
3
3
  #
4
4
  # Licensed under the Apache License, Version 2.0 (the "License");
@@ -13,3622 +13,12 @@
13
13
  # See the License for the specific language governing permissions and
14
14
  # limitations under the License.
15
15
 
16
- from __future__ import annotations
16
+ # TODO: Remove after cleaning up the public API.
17
17
 
18
- import ctypes
19
- import sys
20
- import time
21
- from collections import defaultdict
22
- from typing import List, Union
18
+ from warp._src.render import render_opengl as _render_opengl
23
19
 
24
- import numpy as np
25
20
 
26
- import warp as wp
21
+ def __getattr__(name):
22
+ from warp._src.utils import get_deprecated_api
27
23
 
28
- from .utils import tab10_color_map
29
-
30
- Mat44 = Union[List[float], List[List[float]], np.ndarray]
31
-
32
-
33
- wp.set_module_options({"enable_backward": False})
34
-
35
- shape_vertex_shader = """
36
- #version 330 core
37
- layout (location = 0) in vec3 aPos;
38
- layout (location = 1) in vec3 aNormal;
39
- layout (location = 2) in vec2 aTexCoord;
40
-
41
- // column vectors of the instance transform matrix
42
- layout (location = 3) in vec4 aInstanceTransform0;
43
- layout (location = 4) in vec4 aInstanceTransform1;
44
- layout (location = 5) in vec4 aInstanceTransform2;
45
- layout (location = 6) in vec4 aInstanceTransform3;
46
-
47
- // colors to use for the checkerboard pattern
48
- layout (location = 7) in vec3 aObjectColor1;
49
- layout (location = 8) in vec3 aObjectColor2;
50
-
51
- uniform mat4 view;
52
- uniform mat4 model;
53
- uniform mat4 projection;
54
-
55
- out vec3 Normal;
56
- out vec3 FragPos;
57
- out vec2 TexCoord;
58
- out vec3 ObjectColor1;
59
- out vec3 ObjectColor2;
60
-
61
- void main()
62
- {
63
- mat4 transform = model * mat4(aInstanceTransform0, aInstanceTransform1, aInstanceTransform2, aInstanceTransform3);
64
- vec4 worldPos = transform * vec4(aPos, 1.0);
65
- gl_Position = projection * view * worldPos;
66
- FragPos = vec3(worldPos);
67
- Normal = mat3(transpose(inverse(transform))) * aNormal;
68
- TexCoord = aTexCoord;
69
- ObjectColor1 = aObjectColor1;
70
- ObjectColor2 = aObjectColor2;
71
- }
72
- """
73
-
74
- shape_fragment_shader = """
75
- #version 330 core
76
- out vec4 FragColor;
77
-
78
- in vec3 Normal;
79
- in vec3 FragPos;
80
- in vec2 TexCoord;
81
- in vec3 ObjectColor1;
82
- in vec3 ObjectColor2;
83
-
84
- uniform vec3 viewPos;
85
- uniform vec3 lightColor;
86
- uniform vec3 sunDirection;
87
-
88
- void main()
89
- {
90
- float ambientStrength = 0.3;
91
- vec3 ambient = ambientStrength * lightColor;
92
- vec3 norm = normalize(Normal);
93
-
94
- float diff = max(dot(norm, sunDirection), 0.0);
95
- vec3 diffuse = diff * lightColor;
96
-
97
- vec3 lightDir2 = normalize(vec3(1.0, 0.3, -0.3));
98
- diff = max(dot(norm, lightDir2), 0.0);
99
- diffuse += diff * lightColor * 0.3;
100
-
101
- float specularStrength = 0.5;
102
- vec3 viewDir = normalize(viewPos - FragPos);
103
-
104
- vec3 reflectDir = reflect(-sunDirection, norm);
105
- float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32);
106
- vec3 specular = specularStrength * spec * lightColor;
107
-
108
- reflectDir = reflect(-lightDir2, norm);
109
- spec = pow(max(dot(viewDir, reflectDir), 0.0), 64);
110
- specular += specularStrength * spec * lightColor * 0.3;
111
-
112
- // checkerboard pattern
113
- float u = TexCoord.x;
114
- float v = TexCoord.y;
115
- // blend the checkerboard pattern dependent on the gradient of the texture coordinates
116
- // to void Moire patterns
117
- vec2 grad = abs(dFdx(TexCoord)) + abs(dFdy(TexCoord));
118
- float blendRange = 1.5;
119
- float blendFactor = max(grad.x, grad.y) * blendRange;
120
- float scale = 2.0;
121
- float checker = mod(floor(u * scale) + floor(v * scale), 2.0);
122
- checker = mix(checker, 0.5, smoothstep(0.0, 1.0, blendFactor));
123
- vec3 checkerColor = mix(ObjectColor1, ObjectColor2, checker);
124
-
125
- vec3 result = (ambient + diffuse + specular) * checkerColor;
126
- FragColor = vec4(result, 1.0);
127
- }
128
- """
129
-
130
- grid_vertex_shader = """
131
- #version 330 core
132
-
133
- uniform mat4 view;
134
- uniform mat4 model;
135
- uniform mat4 projection;
136
-
137
- in vec3 position;
138
-
139
- void main() {
140
- gl_Position = projection * view * model * vec4(position, 1.0);
141
- }
142
- """
143
-
144
- # Fragment shader source code
145
- grid_fragment_shader = """
146
- #version 330 core
147
-
148
- out vec4 outColor;
149
-
150
- void main() {
151
- outColor = vec4(0.5, 0.5, 0.5, 1.0);
152
- }
153
- """
154
-
155
- sky_vertex_shader = """
156
- #version 330 core
157
-
158
- layout (location = 0) in vec3 aPos;
159
- layout (location = 1) in vec3 aNormal;
160
- layout (location = 2) in vec2 aTexCoord;
161
-
162
- uniform mat4 view;
163
- uniform mat4 inv_model;
164
- uniform mat4 projection;
165
- uniform vec3 viewPos;
166
-
167
- out vec3 FragPos;
168
- out vec2 TexCoord;
169
-
170
- void main()
171
- {
172
- vec4 worldPos = vec4(aPos + viewPos, 1.0);
173
- gl_Position = projection * view * inv_model * worldPos;
174
-
175
- FragPos = vec3(worldPos);
176
- TexCoord = aTexCoord;
177
- }
178
- """
179
-
180
- sky_fragment_shader = """
181
- #version 330 core
182
-
183
- out vec4 FragColor;
184
-
185
- in vec3 FragPos;
186
- in vec2 TexCoord;
187
-
188
- uniform vec3 color1;
189
- uniform vec3 color2;
190
- uniform float farPlane;
191
-
192
- uniform vec3 sunDirection;
193
-
194
- void main()
195
- {
196
- float y = tanh(FragPos.y/farPlane*10.0)*0.5+0.5;
197
- float height = sqrt(1.0-y);
198
-
199
- float s = pow(0.5, 1.0 / 10.0);
200
- s = 1.0 - clamp(s, 0.75, 1.0);
201
-
202
- vec3 haze = mix(vec3(1.0), color2 * 1.3, s);
203
- vec3 sky = mix(color1, haze, height / 1.3);
204
-
205
- float diff = max(dot(sunDirection, normalize(FragPos)), 0.0);
206
- vec3 sun = pow(diff, 32) * vec3(1.0, 0.8, 0.6) * 0.5;
207
-
208
- FragColor = vec4(sky + sun, 1.0);
209
- }
210
- """
211
-
212
- frame_vertex_shader = """
213
- #version 330 core
214
- layout (location = 0) in vec3 aPos;
215
- layout (location = 1) in vec2 aTexCoord;
216
-
217
- out vec2 TexCoord;
218
-
219
- void main() {
220
- gl_Position = vec4(aPos, 1.0);
221
- TexCoord = aTexCoord;
222
- }
223
- """
224
-
225
- frame_fragment_shader = """
226
- #version 330 core
227
- in vec2 TexCoord;
228
-
229
- out vec4 FragColor;
230
-
231
- uniform sampler2D textureSampler;
232
-
233
- void main() {
234
- FragColor = texture(textureSampler, TexCoord);
235
- }
236
- """
237
-
238
- frame_depth_fragment_shader = """
239
- #version 330 core
240
- in vec2 TexCoord;
241
-
242
- out vec4 FragColor;
243
-
244
- uniform sampler2D textureSampler;
245
-
246
- vec3 bourkeColorMap(float v) {
247
- vec3 c = vec3(1.0, 1.0, 1.0);
248
-
249
- v = clamp(v, 0.0, 1.0); // Ensures v is between 0 and 1
250
-
251
- if (v < 0.25) {
252
- c.r = 0.0;
253
- c.g = 4.0 * v;
254
- } else if (v < 0.5) {
255
- c.r = 0.0;
256
- c.b = 1.0 + 4.0 * (0.25 - v);
257
- } else if (v < 0.75) {
258
- c.r = 4.0 * (v - 0.5);
259
- c.b = 0.0;
260
- } else {
261
- c.g = 1.0 + 4.0 * (0.75 - v);
262
- c.b = 0.0;
263
- }
264
-
265
- return c;
266
- }
267
-
268
- void main() {
269
- float depth = texture(textureSampler, TexCoord).r;
270
- FragColor = vec4(bourkeColorMap(sqrt(1.0 - depth)), 1.0);
271
- }
272
- """
273
-
274
-
275
- @wp.kernel
276
- def update_vbo_transforms(
277
- instance_id: wp.array(dtype=int),
278
- instance_body: wp.array(dtype=int),
279
- instance_transforms: wp.array(dtype=wp.transform),
280
- instance_scalings: wp.array(dtype=wp.vec3),
281
- body_q: wp.array(dtype=wp.transform),
282
- # outputs
283
- vbo_transforms: wp.array(dtype=wp.mat44),
284
- ):
285
- tid = wp.tid()
286
- i = instance_id[tid]
287
- X_ws = instance_transforms[i]
288
- if instance_body:
289
- body = instance_body[i]
290
- if body >= 0:
291
- if body_q:
292
- X_ws = body_q[body] * X_ws
293
- else:
294
- return
295
- p = wp.transform_get_translation(X_ws)
296
- q = wp.transform_get_rotation(X_ws)
297
- s = instance_scalings[i]
298
- rot = wp.quat_to_matrix(q)
299
- # transposed definition
300
- vbo_transforms[tid] = wp.mat44(
301
- rot[0, 0] * s[0],
302
- rot[1, 0] * s[0],
303
- rot[2, 0] * s[0],
304
- 0.0,
305
- rot[0, 1] * s[1],
306
- rot[1, 1] * s[1],
307
- rot[2, 1] * s[1],
308
- 0.0,
309
- rot[0, 2] * s[2],
310
- rot[1, 2] * s[2],
311
- rot[2, 2] * s[2],
312
- 0.0,
313
- p[0],
314
- p[1],
315
- p[2],
316
- 1.0,
317
- )
318
-
319
-
320
- @wp.kernel
321
- def update_vbo_vertices(
322
- points: wp.array(dtype=wp.vec3),
323
- # outputs
324
- vbo_vertices: wp.array(dtype=float, ndim=2),
325
- ):
326
- tid = wp.tid()
327
- p = points[tid]
328
- vbo_vertices[tid, 0] = p[0]
329
- vbo_vertices[tid, 1] = p[1]
330
- vbo_vertices[tid, 2] = p[2]
331
-
332
-
333
- @wp.kernel
334
- def update_points_positions(
335
- instance_positions: wp.array(dtype=wp.vec3),
336
- instance_scalings: wp.array(dtype=wp.vec3),
337
- # outputs
338
- vbo_transforms: wp.array(dtype=wp.mat44),
339
- ):
340
- tid = wp.tid()
341
- p = instance_positions[tid]
342
- s = wp.vec3(1.0)
343
- if instance_scalings:
344
- s = instance_scalings[tid]
345
- # transposed definition
346
- # fmt: off
347
- vbo_transforms[tid] = wp.mat44(
348
- s[0], 0.0, 0.0, 0.0,
349
- 0.0, s[1], 0.0, 0.0,
350
- 0.0, 0.0, s[2], 0.0,
351
- p[0], p[1], p[2], 1.0)
352
- # fmt: on
353
-
354
-
355
- @wp.kernel
356
- def update_line_transforms(
357
- lines: wp.array(dtype=wp.vec3, ndim=2),
358
- # outputs
359
- vbo_transforms: wp.array(dtype=wp.mat44),
360
- ):
361
- tid = wp.tid()
362
- p0 = lines[tid, 0]
363
- p1 = lines[tid, 1]
364
- p = 0.5 * (p0 + p1)
365
- d = p1 - p0
366
- s = wp.length(d)
367
- axis = wp.normalize(d)
368
- y_up = wp.vec3(0.0, 1.0, 0.0)
369
- angle = wp.acos(wp.dot(axis, y_up))
370
- axis = wp.normalize(wp.cross(axis, y_up))
371
- q = wp.quat_from_axis_angle(axis, -angle)
372
- rot = wp.quat_to_matrix(q)
373
- # transposed definition
374
- # fmt: off
375
- vbo_transforms[tid] = wp.mat44(
376
- rot[0, 0], rot[1, 0], rot[2, 0], 0.0,
377
- s * rot[0, 1], s * rot[1, 1], s * rot[2, 1], 0.0,
378
- rot[0, 2], rot[1, 2], rot[2, 2], 0.0,
379
- p[0], p[1], p[2], 1.0,
380
- )
381
- # fmt: on
382
-
383
-
384
- @wp.kernel
385
- def compute_gfx_vertices(
386
- indices: wp.array(dtype=int, ndim=2),
387
- vertices: wp.array(dtype=wp.vec3, ndim=1),
388
- scale: wp.vec3,
389
- # outputs
390
- gfx_vertices: wp.array(dtype=float, ndim=2),
391
- ):
392
- tid = wp.tid()
393
- v0 = vertices[indices[tid, 0]] * scale[0]
394
- v1 = vertices[indices[tid, 1]] * scale[1]
395
- v2 = vertices[indices[tid, 2]] * scale[2]
396
- i = tid * 3
397
- j = i + 1
398
- k = i + 2
399
- gfx_vertices[i, 0] = v0[0]
400
- gfx_vertices[i, 1] = v0[1]
401
- gfx_vertices[i, 2] = v0[2]
402
- gfx_vertices[j, 0] = v1[0]
403
- gfx_vertices[j, 1] = v1[1]
404
- gfx_vertices[j, 2] = v1[2]
405
- gfx_vertices[k, 0] = v2[0]
406
- gfx_vertices[k, 1] = v2[1]
407
- gfx_vertices[k, 2] = v2[2]
408
- n = wp.normalize(wp.cross(v1 - v0, v2 - v0))
409
- gfx_vertices[i, 3] = n[0]
410
- gfx_vertices[i, 4] = n[1]
411
- gfx_vertices[i, 5] = n[2]
412
- gfx_vertices[j, 3] = n[0]
413
- gfx_vertices[j, 4] = n[1]
414
- gfx_vertices[j, 5] = n[2]
415
- gfx_vertices[k, 3] = n[0]
416
- gfx_vertices[k, 4] = n[1]
417
- gfx_vertices[k, 5] = n[2]
418
-
419
-
420
- @wp.kernel
421
- def compute_average_normals(
422
- indices: wp.array(dtype=int, ndim=2),
423
- vertices: wp.array(dtype=wp.vec3),
424
- # outputs
425
- normals: wp.array(dtype=wp.vec3),
426
- faces_per_vertex: wp.array(dtype=int),
427
- ):
428
- tid = wp.tid()
429
- i = indices[tid, 0]
430
- j = indices[tid, 1]
431
- k = indices[tid, 2]
432
- v0 = vertices[i]
433
- v1 = vertices[j]
434
- v2 = vertices[k]
435
- n = wp.normalize(wp.cross(v1 - v0, v2 - v0))
436
- wp.atomic_add(normals, i, n)
437
- wp.atomic_add(faces_per_vertex, i, 1)
438
- wp.atomic_add(normals, j, n)
439
- wp.atomic_add(faces_per_vertex, j, 1)
440
- wp.atomic_add(normals, k, n)
441
- wp.atomic_add(faces_per_vertex, k, 1)
442
-
443
-
444
- @wp.kernel
445
- def assemble_gfx_vertices(
446
- vertices: wp.array(dtype=wp.vec3, ndim=1),
447
- normals: wp.array(dtype=wp.vec3),
448
- faces_per_vertex: wp.array(dtype=int),
449
- # outputs
450
- gfx_vertices: wp.array(dtype=float, ndim=2),
451
- ):
452
- tid = wp.tid()
453
- v = vertices[tid]
454
- n = normals[tid] / float(faces_per_vertex[tid])
455
- gfx_vertices[tid, 0] = v[0]
456
- gfx_vertices[tid, 1] = v[1]
457
- gfx_vertices[tid, 2] = v[2]
458
- gfx_vertices[tid, 3] = n[0]
459
- gfx_vertices[tid, 4] = n[1]
460
- gfx_vertices[tid, 5] = n[2]
461
-
462
-
463
- @wp.kernel
464
- def copy_rgb_frame(
465
- input_img: wp.array(dtype=wp.uint8),
466
- width: int,
467
- height: int,
468
- # outputs
469
- output_img: wp.array(dtype=float, ndim=3),
470
- ):
471
- w, v = wp.tid()
472
- pixel = v * width + w
473
- pixel *= 3
474
- r = float(input_img[pixel + 0])
475
- g = float(input_img[pixel + 1])
476
- b = float(input_img[pixel + 2])
477
- # flip vertically (OpenGL coordinates start at bottom)
478
- v = height - v - 1
479
- output_img[v, w, 0] = r / 255.0
480
- output_img[v, w, 1] = g / 255.0
481
- output_img[v, w, 2] = b / 255.0
482
-
483
-
484
- @wp.kernel
485
- def copy_rgb_frame_uint8(
486
- input_img: wp.array(dtype=wp.uint8),
487
- width: int,
488
- height: int,
489
- # outputs
490
- output_img: wp.array(dtype=wp.uint8, ndim=3),
491
- ):
492
- w, v = wp.tid()
493
- pixel = v * width + w
494
- pixel *= 3
495
- # flip vertically (OpenGL coordinates start at bottom)
496
- v = height - v - 1
497
- output_img[v, w, 0] = input_img[pixel + 0]
498
- output_img[v, w, 1] = input_img[pixel + 1]
499
- output_img[v, w, 2] = input_img[pixel + 2]
500
-
501
-
502
- @wp.kernel
503
- def copy_depth_frame(
504
- input_img: wp.array(dtype=wp.float32),
505
- width: int,
506
- height: int,
507
- near: float,
508
- far: float,
509
- # outputs
510
- output_img: wp.array(dtype=wp.float32, ndim=3),
511
- ):
512
- w, v = wp.tid()
513
- pixel = v * width + w
514
- # flip vertically (OpenGL coordinates start at bottom)
515
- v = height - v - 1
516
- d = 2.0 * input_img[pixel] - 1.0
517
- d = 2.0 * near * far / ((far - near) * d - near - far)
518
- output_img[v, w, 0] = -d
519
-
520
-
521
- @wp.kernel
522
- def copy_rgb_frame_tiles(
523
- input_img: wp.array(dtype=wp.uint8),
524
- positions: wp.array(dtype=int, ndim=2),
525
- screen_width: int,
526
- screen_height: int,
527
- tile_height: int,
528
- # outputs
529
- output_img: wp.array(dtype=float, ndim=4),
530
- ):
531
- tile, x, y = wp.tid()
532
- p = positions[tile]
533
- qx = x + p[0]
534
- qy = y + p[1]
535
- pixel = qy * screen_width + qx
536
- # flip vertically (OpenGL coordinates start at bottom)
537
- y = tile_height - y - 1
538
- if qx >= screen_width or qy >= screen_height:
539
- output_img[tile, y, x, 0] = 0.0
540
- output_img[tile, y, x, 1] = 0.0
541
- output_img[tile, y, x, 2] = 0.0
542
- return # prevent out-of-bounds access
543
- pixel *= 3
544
- r = float(input_img[pixel + 0])
545
- g = float(input_img[pixel + 1])
546
- b = float(input_img[pixel + 2])
547
- output_img[tile, y, x, 0] = r / 255.0
548
- output_img[tile, y, x, 1] = g / 255.0
549
- output_img[tile, y, x, 2] = b / 255.0
550
-
551
-
552
- @wp.kernel
553
- def copy_rgb_frame_tiles_uint8(
554
- input_img: wp.array(dtype=wp.uint8),
555
- positions: wp.array(dtype=int, ndim=2),
556
- screen_width: int,
557
- screen_height: int,
558
- tile_height: int,
559
- # outputs
560
- output_img: wp.array(dtype=wp.uint8, ndim=4),
561
- ):
562
- tile, x, y = wp.tid()
563
- p = positions[tile]
564
- qx = x + p[0]
565
- qy = y + p[1]
566
- pixel = qy * screen_width + qx
567
- # flip vertically (OpenGL coordinates start at bottom)
568
- y = tile_height - y - 1
569
- if qx >= screen_width or qy >= screen_height:
570
- output_img[tile, y, x, 0] = wp.uint8(0)
571
- output_img[tile, y, x, 1] = wp.uint8(0)
572
- output_img[tile, y, x, 2] = wp.uint8(0)
573
- return # prevent out-of-bounds access
574
- pixel *= 3
575
- output_img[tile, y, x, 0] = input_img[pixel + 0]
576
- output_img[tile, y, x, 1] = input_img[pixel + 1]
577
- output_img[tile, y, x, 2] = input_img[pixel + 2]
578
-
579
-
580
- @wp.kernel
581
- def copy_depth_frame_tiles(
582
- input_img: wp.array(dtype=wp.float32),
583
- positions: wp.array(dtype=int, ndim=2),
584
- screen_width: int,
585
- screen_height: int,
586
- tile_height: int,
587
- near: float,
588
- far: float,
589
- # outputs
590
- output_img: wp.array(dtype=wp.float32, ndim=4),
591
- ):
592
- tile, x, y = wp.tid()
593
- p = positions[tile]
594
- qx = x + p[0]
595
- qy = y + p[1]
596
- pixel = qy * screen_width + qx
597
- # flip vertically (OpenGL coordinates start at bottom)
598
- y = tile_height - y - 1
599
- if qx >= screen_width or qy >= screen_height:
600
- output_img[tile, y, x, 0] = far
601
- return # prevent out-of-bounds access
602
- d = 2.0 * input_img[pixel] - 1.0
603
- d = 2.0 * near * far / ((far - near) * d - near - far)
604
- output_img[tile, y, x, 0] = -d
605
-
606
-
607
- @wp.kernel
608
- def copy_rgb_frame_tile(
609
- input_img: wp.array(dtype=wp.uint8),
610
- offset_x: int,
611
- offset_y: int,
612
- screen_width: int,
613
- screen_height: int,
614
- tile_height: int,
615
- # outputs
616
- output_img: wp.array(dtype=float, ndim=4),
617
- ):
618
- tile, x, y = wp.tid()
619
- qx = x + offset_x
620
- qy = y + offset_y
621
- pixel = qy * screen_width + qx
622
- # flip vertically (OpenGL coordinates start at bottom)
623
- y = tile_height - y - 1
624
- if qx >= screen_width or qy >= screen_height:
625
- output_img[tile, y, x, 0] = 0.0
626
- output_img[tile, y, x, 1] = 0.0
627
- output_img[tile, y, x, 2] = 0.0
628
- return # prevent out-of-bounds access
629
- pixel *= 3
630
- r = float(input_img[pixel + 0])
631
- g = float(input_img[pixel + 1])
632
- b = float(input_img[pixel + 2])
633
- output_img[tile, y, x, 0] = r / 255.0
634
- output_img[tile, y, x, 1] = g / 255.0
635
- output_img[tile, y, x, 2] = b / 255.0
636
-
637
-
638
- @wp.kernel
639
- def copy_rgb_frame_tile_uint8(
640
- input_img: wp.array(dtype=wp.uint8),
641
- offset_x: int,
642
- offset_y: int,
643
- screen_width: int,
644
- screen_height: int,
645
- tile_height: int,
646
- # outputs
647
- output_img: wp.array(dtype=wp.uint8, ndim=4),
648
- ):
649
- tile, x, y = wp.tid()
650
- qx = x + offset_x
651
- qy = y + offset_y
652
- pixel = qy * screen_width + qx
653
- # flip vertically (OpenGL coordinates start at bottom)
654
- y = tile_height - y - 1
655
- if qx >= screen_width or qy >= screen_height:
656
- output_img[tile, y, x, 0] = wp.uint8(0)
657
- output_img[tile, y, x, 1] = wp.uint8(0)
658
- output_img[tile, y, x, 2] = wp.uint8(0)
659
- return # prevent out-of-bounds access
660
- pixel *= 3
661
- output_img[tile, y, x, 0] = input_img[pixel + 0]
662
- output_img[tile, y, x, 1] = input_img[pixel + 1]
663
- output_img[tile, y, x, 2] = input_img[pixel + 2]
664
-
665
-
666
- def check_gl_error():
667
- from pyglet import gl
668
-
669
- error = gl.glGetError()
670
- if error != gl.GL_NO_ERROR:
671
- print(f"OpenGL error: {error}")
672
-
673
-
674
- class ShapeInstancer:
675
- """
676
- Handles instanced rendering for a mesh.
677
- Note the vertices must be in the 8-dimensional format:
678
- [3D point, 3D normal, UV texture coordinates]
679
- """
680
-
681
- gl = None # Class-level variable to hold the imported module
682
-
683
- @classmethod
684
- def initialize_gl(cls):
685
- if cls.gl is None: # Only import if not already imported
686
- from pyglet import gl
687
-
688
- cls.gl = gl
689
-
690
- def __new__(cls, *args, **kwargs):
691
- instance = super().__new__(cls)
692
- instance.instance_transform_gl_buffer = None
693
- instance.vao = None
694
- return instance
695
-
696
- def __init__(self, shape_shader, device):
697
- self.shape_shader = shape_shader
698
- self.device = device
699
- self.face_count = 0
700
- self.instance_color1_buffer = None
701
- self.instance_color2_buffer = None
702
- self.color1 = (1.0, 1.0, 1.0)
703
- self.color2 = (0.0, 0.0, 0.0)
704
- self.num_instances = 0
705
- self.transforms = None
706
- self.scalings = None
707
- self._instance_transform_cuda_buffer = None
708
-
709
- ShapeInstancer.initialize_gl()
710
-
711
- def __del__(self):
712
- gl = ShapeInstancer.gl
713
-
714
- if self.instance_transform_gl_buffer is not None:
715
- try:
716
- gl.glDeleteBuffers(1, self.instance_transform_gl_buffer)
717
- gl.glDeleteBuffers(1, self.instance_color1_buffer)
718
- gl.glDeleteBuffers(1, self.instance_color2_buffer)
719
- except gl.GLException:
720
- pass
721
- if self.vao is not None:
722
- try:
723
- gl.glDeleteVertexArrays(1, self.vao)
724
- gl.glDeleteBuffers(1, self.vbo)
725
- gl.glDeleteBuffers(1, self.ebo)
726
- except gl.GLException:
727
- pass
728
-
729
- def register_shape(self, vertices, indices, color1=(1.0, 1.0, 1.0), color2=(0.0, 0.0, 0.0)):
730
- gl = ShapeInstancer.gl
731
-
732
- if color1 is not None and color2 is None:
733
- color2 = np.clip(np.array(color1) + 0.25, 0.0, 1.0)
734
- self.color1 = color1
735
- self.color2 = color2
736
-
737
- gl.glUseProgram(self.shape_shader.id)
738
-
739
- # Create VAO, VBO, and EBO
740
- self.vao = gl.GLuint()
741
- gl.glGenVertexArrays(1, self.vao)
742
- gl.glBindVertexArray(self.vao)
743
-
744
- self.vbo = gl.GLuint()
745
- gl.glGenBuffers(1, self.vbo)
746
- gl.glBindBuffer(gl.GL_ARRAY_BUFFER, self.vbo)
747
- gl.glBufferData(gl.GL_ARRAY_BUFFER, vertices.nbytes, vertices.ctypes.data, gl.GL_STATIC_DRAW)
748
-
749
- self.ebo = gl.GLuint()
750
- gl.glGenBuffers(1, self.ebo)
751
- gl.glBindBuffer(gl.GL_ELEMENT_ARRAY_BUFFER, self.ebo)
752
- gl.glBufferData(gl.GL_ELEMENT_ARRAY_BUFFER, indices.nbytes, indices.ctypes.data, gl.GL_STATIC_DRAW)
753
-
754
- # Set up vertex attributes
755
- vertex_stride = vertices.shape[1] * vertices.itemsize
756
- # positions
757
- gl.glVertexAttribPointer(0, 3, gl.GL_FLOAT, gl.GL_FALSE, vertex_stride, ctypes.c_void_p(0))
758
- gl.glEnableVertexAttribArray(0)
759
- # normals
760
- gl.glVertexAttribPointer(1, 3, gl.GL_FLOAT, gl.GL_FALSE, vertex_stride, ctypes.c_void_p(3 * vertices.itemsize))
761
- gl.glEnableVertexAttribArray(1)
762
- # uv coordinates
763
- gl.glVertexAttribPointer(2, 2, gl.GL_FLOAT, gl.GL_FALSE, vertex_stride, ctypes.c_void_p(6 * vertices.itemsize))
764
- gl.glEnableVertexAttribArray(2)
765
-
766
- gl.glBindVertexArray(0)
767
-
768
- self.face_count = len(indices)
769
-
770
- def update_colors(self, colors1, colors2):
771
- gl = ShapeInstancer.gl
772
-
773
- if colors1 is None:
774
- colors1 = np.tile(self.color1, (self.num_instances, 1))
775
- if colors2 is None:
776
- colors2 = np.tile(self.color2, (self.num_instances, 1))
777
- if np.shape(colors1) != (self.num_instances, 3):
778
- colors1 = np.tile(colors1, (self.num_instances, 1))
779
- if np.shape(colors2) != (self.num_instances, 3):
780
- colors2 = np.tile(colors2, (self.num_instances, 1))
781
- colors1 = np.array(colors1, dtype=np.float32)
782
- colors2 = np.array(colors2, dtype=np.float32)
783
-
784
- gl.glBindVertexArray(self.vao)
785
-
786
- # create buffer for checkerboard colors
787
- if self.instance_color1_buffer is None:
788
- self.instance_color1_buffer = gl.GLuint()
789
- gl.glGenBuffers(1, self.instance_color1_buffer)
790
- gl.glBindBuffer(gl.GL_ARRAY_BUFFER, self.instance_color1_buffer)
791
- gl.glBufferData(gl.GL_ARRAY_BUFFER, colors1.nbytes, colors1.ctypes.data, gl.GL_STATIC_DRAW)
792
-
793
- if self.instance_color2_buffer is None:
794
- self.instance_color2_buffer = gl.GLuint()
795
- gl.glGenBuffers(1, self.instance_color2_buffer)
796
- gl.glBindBuffer(gl.GL_ARRAY_BUFFER, self.instance_color2_buffer)
797
- gl.glBufferData(gl.GL_ARRAY_BUFFER, colors2.nbytes, colors2.ctypes.data, gl.GL_STATIC_DRAW)
798
-
799
- gl.glBindBuffer(gl.GL_ARRAY_BUFFER, self.instance_color1_buffer)
800
- gl.glVertexAttribPointer(7, 3, gl.GL_FLOAT, gl.GL_FALSE, colors1[0].nbytes, ctypes.c_void_p(0))
801
- gl.glEnableVertexAttribArray(7)
802
- gl.glVertexAttribDivisor(7, 1)
803
-
804
- gl.glBindBuffer(gl.GL_ARRAY_BUFFER, self.instance_color2_buffer)
805
- gl.glVertexAttribPointer(8, 3, gl.GL_FLOAT, gl.GL_FALSE, colors2[0].nbytes, ctypes.c_void_p(0))
806
- gl.glEnableVertexAttribArray(8)
807
- gl.glVertexAttribDivisor(8, 1)
808
-
809
- def allocate_instances(self, positions, rotations=None, colors1=None, colors2=None, scalings=None):
810
- gl = ShapeInstancer.gl
811
-
812
- gl.glBindVertexArray(self.vao)
813
-
814
- self.num_instances = len(positions)
815
-
816
- # Create instance buffer and bind it as an instanced array
817
- if self.instance_transform_gl_buffer is None:
818
- self.instance_transform_gl_buffer = gl.GLuint()
819
- gl.glGenBuffers(1, self.instance_transform_gl_buffer)
820
- gl.glBindBuffer(gl.GL_ARRAY_BUFFER, self.instance_transform_gl_buffer)
821
-
822
- self.instance_ids = wp.array(np.arange(self.num_instances), dtype=wp.int32, device=self.device)
823
- if rotations is None:
824
- self.instance_transforms = wp.array(
825
- [(*pos, 0.0, 0.0, 0.0, 1.0) for pos in positions], dtype=wp.transform, device=self.device
826
- )
827
- else:
828
- self.instance_transforms = wp.array(
829
- [(*pos, *rot) for pos, rot in zip(positions, rotations)],
830
- dtype=wp.transform,
831
- device=self.device,
832
- )
833
-
834
- if scalings is None:
835
- self.instance_scalings = wp.array(
836
- np.tile((1.0, 1.0, 1.0), (self.num_instances, 1)), dtype=wp.vec3, device=self.device
837
- )
838
- else:
839
- self.instance_scalings = wp.array(scalings, dtype=wp.vec3, device=self.device)
840
-
841
- vbo_transforms = wp.zeros(dtype=wp.mat44, shape=(self.num_instances,), device=self.device)
842
-
843
- wp.launch(
844
- update_vbo_transforms,
845
- dim=self.num_instances,
846
- inputs=[
847
- self.instance_ids,
848
- None,
849
- self.instance_transforms,
850
- self.instance_scalings,
851
- None,
852
- ],
853
- outputs=[
854
- vbo_transforms,
855
- ],
856
- device=self.device,
857
- record_tape=False,
858
- )
859
-
860
- vbo_transforms = vbo_transforms.numpy()
861
- gl.glBufferData(gl.GL_ARRAY_BUFFER, vbo_transforms.nbytes, vbo_transforms.ctypes.data, gl.GL_DYNAMIC_DRAW)
862
-
863
- # Create CUDA buffer for instance transforms
864
- self._instance_transform_cuda_buffer = wp.RegisteredGLBuffer(
865
- int(self.instance_transform_gl_buffer.value), self.device
866
- )
867
-
868
- self.update_colors(colors1, colors2)
869
-
870
- # Set up instance attribute pointers
871
- matrix_size = vbo_transforms[0].nbytes
872
-
873
- gl.glBindBuffer(gl.GL_ARRAY_BUFFER, self.instance_transform_gl_buffer)
874
-
875
- # we can only send vec4s to the shader, so we need to split the instance transforms matrix into its column vectors
876
- for i in range(4):
877
- gl.glVertexAttribPointer(
878
- 3 + i, 4, gl.GL_FLOAT, gl.GL_FALSE, matrix_size, ctypes.c_void_p(i * matrix_size // 4)
879
- )
880
- gl.glEnableVertexAttribArray(3 + i)
881
- gl.glVertexAttribDivisor(3 + i, 1)
882
-
883
- gl.glBindVertexArray(0)
884
-
885
- def update_instances(self, transforms: wp.array = None, scalings: wp.array = None, colors1=None, colors2=None):
886
- gl = ShapeInstancer.gl
887
-
888
- if transforms is not None:
889
- if transforms.device.is_cuda:
890
- wp_transforms = transforms
891
- else:
892
- wp_transforms = transforms.to(self.device)
893
- self.transforms = wp_transforms
894
- if scalings is not None:
895
- if transforms.device.is_cuda:
896
- wp_scalings = scalings
897
- else:
898
- wp_scalings = scalings.to(self.device)
899
- self.scalings = wp_scalings
900
-
901
- if transforms is not None or scalings is not None:
902
- gl.glBindVertexArray(self.vao)
903
- vbo_transforms = self._instance_transform_cuda_buffer.map(dtype=wp.mat44, shape=(self.num_instances,))
904
-
905
- wp.launch(
906
- update_vbo_transforms,
907
- dim=self.num_instances,
908
- inputs=[
909
- self.instance_ids,
910
- None,
911
- self.instance_transforms,
912
- self.instance_scalings,
913
- None,
914
- ],
915
- outputs=[
916
- vbo_transforms,
917
- ],
918
- device=self.device,
919
- record_tape=False,
920
- )
921
-
922
- self._instance_transform_cuda_buffer.unmap()
923
-
924
- if colors1 is not None or colors2 is not None:
925
- self.update_colors(colors1, colors2)
926
-
927
- def render(self):
928
- gl = ShapeInstancer.gl
929
-
930
- gl.glUseProgram(self.shape_shader.id)
931
-
932
- gl.glBindVertexArray(self.vao)
933
- gl.glDrawElementsInstanced(gl.GL_TRIANGLES, self.face_count, gl.GL_UNSIGNED_INT, None, self.num_instances)
934
- gl.glBindVertexArray(0)
935
-
936
- # scope exposes VBO transforms to be set directly by a warp kernel
937
- def __enter__(self):
938
- gl = ShapeInstancer.gl
939
-
940
- gl.glBindVertexArray(self.vao)
941
- self.vbo_transforms = self._instance_transform_cuda_buffer.map(dtype=wp.mat44, shape=(self.num_instances,))
942
- return self
943
-
944
- def __exit__(self, exc_type, exc_value, traceback):
945
- self._instance_transform_cuda_buffer.unmap()
946
-
947
-
948
- def str_buffer(string: str):
949
- return ctypes.c_char_p(string.encode("utf-8"))
950
-
951
-
952
- def arr_pointer(arr: np.ndarray):
953
- return arr.astype(np.float32).ctypes.data_as(ctypes.POINTER(ctypes.c_float))
954
-
955
-
956
- class OpenGLRenderer:
957
- """
958
- OpenGLRenderer is a simple OpenGL renderer for rendering 3D shapes and meshes.
959
- """
960
-
961
- # number of segments to use for rendering spheres, capsules, cones and cylinders
962
- default_num_segments = 32
963
-
964
- gl = None # Class-level variable to hold the imported module
965
-
966
- @classmethod
967
- def initialize_gl(cls):
968
- if cls.gl is None: # Only import if not already imported
969
- from pyglet import gl
970
-
971
- cls.gl = gl
972
-
973
- def __init__(
974
- self,
975
- title="Warp",
976
- scaling=1.0,
977
- fps=60,
978
- up_axis="Y",
979
- screen_width=1024,
980
- screen_height=768,
981
- near_plane=1.0,
982
- far_plane=100.0,
983
- camera_fov=45.0,
984
- camera_pos=(0.0, 2.0, 10.0),
985
- camera_front=(0.0, 0.0, -1.0),
986
- camera_up=(0.0, 1.0, 0.0),
987
- background_color=(0.53, 0.8, 0.92),
988
- draw_grid=True,
989
- draw_sky=True,
990
- draw_axis=True,
991
- show_info=True,
992
- render_wireframe=False,
993
- render_depth=False,
994
- axis_scale=1.0,
995
- vsync=False,
996
- headless=None,
997
- enable_backface_culling=True,
998
- enable_mouse_interaction=True,
999
- enable_keyboard_interaction=True,
1000
- device=None,
1001
- use_legacy_opengl: bool | None = None,
1002
- ):
1003
- """
1004
- Args:
1005
-
1006
- title (str): The window title.
1007
- scaling (float): The scaling factor for the scene.
1008
- fps (int): The target frames per second.
1009
- up_axis (str): The up axis of the scene. Can be "X", "Y", or "Z".
1010
- screen_width (int): The width of the window.
1011
- screen_height (int): The height of the window.
1012
- near_plane (float): The near clipping plane.
1013
- far_plane (float): The far clipping plane.
1014
- camera_fov (float): The camera field of view in degrees.
1015
- camera_pos (tuple): The initial camera position.
1016
- camera_front (tuple): The initial camera front direction.
1017
- camera_up (tuple): The initial camera up direction.
1018
- background_color (tuple): The background color of the scene.
1019
- draw_grid (bool): Whether to draw a grid indicating the ground plane.
1020
- draw_sky (bool): Whether to draw a sky sphere.
1021
- draw_axis (bool): Whether to draw the coordinate system axes.
1022
- show_info (bool): Whether to overlay rendering information.
1023
- render_wireframe (bool): Whether to render scene shapes as wireframes.
1024
- render_depth (bool): Whether to show the depth buffer instead of the RGB image.
1025
- axis_scale (float): The scale of the coordinate system axes being rendered (only if ``draw_axis`` is True).
1026
- vsync (bool): Whether to enable vertical synchronization.
1027
- headless (bool): Whether to run in headless mode (no window is created). If None, the value is determined by the Pyglet configuration defined in ``pyglet.options["headless"]``.
1028
- enable_backface_culling (bool): Whether to enable backface culling.
1029
- enable_mouse_interaction (bool): Whether to enable mouse interaction.
1030
- enable_keyboard_interaction (bool): Whether to enable keyboard interaction.
1031
- device (Devicelike): Where to store the internal data.
1032
- use_legacy_opengl (bool | None): Whether to use a legacy OpenGL implementation that is more compatible with macOS. If ``None``, it will be automatically detected based on the operating system.
1033
-
1034
- Note:
1035
-
1036
- :class:`OpenGLRenderer` requires Pyglet (version >= 2.0, known to work on 2.0.7) to be installed.
1037
-
1038
- Headless rendering is supported via EGL on UNIX operating systems. To enable headless rendering, set the following pyglet options before importing ``warp.render``:
1039
-
1040
- .. code-block:: python
1041
-
1042
- import pyglet
1043
-
1044
- pyglet.options["headless"] = True
1045
-
1046
- import warp.render
1047
-
1048
- # OpenGLRenderer is instantiated with headless=True by default
1049
- renderer = warp.render.OpenGLRenderer()
1050
- """
1051
- try:
1052
- import pyglet
1053
-
1054
- # disable error checking for performance
1055
- pyglet.options["debug_gl"] = False
1056
-
1057
- from pyglet.graphics.shader import Shader, ShaderProgram
1058
- from pyglet.math import Vec3 as PyVec3
1059
-
1060
- OpenGLRenderer.initialize_gl()
1061
- gl = OpenGLRenderer.gl
1062
- except ImportError as e:
1063
- raise Exception("OpenGLRenderer requires pyglet (version >= 2.0) to be installed.") from e
1064
-
1065
- self.camera_near_plane = near_plane
1066
- self.camera_far_plane = far_plane
1067
- self.camera_fov = camera_fov
1068
-
1069
- self.background_color = background_color
1070
- self.draw_grid = draw_grid
1071
- self.draw_sky = draw_sky
1072
- self.draw_axis = draw_axis
1073
- self.show_info = show_info
1074
- self.render_wireframe = render_wireframe
1075
- self.render_depth = render_depth
1076
- self.enable_backface_culling = enable_backface_culling
1077
-
1078
- if use_legacy_opengl is None:
1079
- self.use_legacy_opengl = sys.platform == "darwin"
1080
- else:
1081
- self.use_legacy_opengl = use_legacy_opengl
1082
-
1083
- if device is None:
1084
- self._device = wp.get_preferred_device()
1085
- else:
1086
- self._device = wp.get_device(device)
1087
-
1088
- self._title = title
1089
-
1090
- self.window = pyglet.window.Window(
1091
- width=screen_width, height=screen_height, caption=title, resizable=True, vsync=vsync, visible=not headless
1092
- )
1093
- if headless is None:
1094
- self.headless = pyglet.options.get("headless", False)
1095
- else:
1096
- self.headless = headless
1097
- self.app = pyglet.app
1098
-
1099
- # making window current opengl rendering context
1100
- self._switch_context()
1101
-
1102
- self.screen_width, self.screen_height = self.window.get_framebuffer_size()
1103
-
1104
- self.enable_mouse_interaction = enable_mouse_interaction
1105
- self.enable_keyboard_interaction = enable_keyboard_interaction
1106
-
1107
- self._camera_speed = 0.04
1108
- if isinstance(up_axis, int):
1109
- self._camera_axis = up_axis
1110
- else:
1111
- self._camera_axis = "XYZ".index(up_axis.upper())
1112
- self._last_x, self._last_y = self.screen_width // 2, self.screen_height // 2
1113
- self._first_mouse = True
1114
- self._left_mouse_pressed = False
1115
- self._keys_pressed = defaultdict(bool)
1116
- self._input_processors = []
1117
- self._key_callbacks = []
1118
-
1119
- self.render_2d_callbacks = []
1120
- self.render_3d_callbacks = []
1121
-
1122
- self._camera_pos = PyVec3(0.0, 0.0, 0.0)
1123
- self._camera_front = PyVec3(0.0, 0.0, -1.0)
1124
- self._camera_up = PyVec3(0.0, 1.0, 0.0)
1125
- self._scaling = scaling
1126
-
1127
- self._model_matrix = self.compute_model_matrix(self._camera_axis, scaling)
1128
- self._inv_model_matrix = np.linalg.inv(self._model_matrix.reshape(4, 4)).flatten()
1129
- self.update_view_matrix(cam_pos=camera_pos, cam_front=camera_front, cam_up=camera_up)
1130
- self.update_projection_matrix()
1131
-
1132
- self._camera_front = self._camera_front.normalize()
1133
- self._pitch = np.rad2deg(np.arcsin(self._camera_front.y))
1134
- self._yaw = -np.rad2deg(np.arccos(self._camera_front.x / np.cos(np.deg2rad(self._pitch))))
1135
-
1136
- self._frame_dt = 1.0 / fps
1137
- self.time = 0.0
1138
- self._start_time = time.time()
1139
- self.clock_time = 0.0
1140
- self._paused = False
1141
- self._frame_speed = 0.0
1142
- self.skip_rendering = False
1143
- self._skip_frame_counter = 0
1144
- self._fps_update = 0.0
1145
- self._fps_render = 0.0
1146
- self._fps_alpha = 0.1 # low pass filter rate to update FPS stats
1147
-
1148
- self._body_name = {}
1149
- self._shapes = []
1150
- self._shape_geo_hash = {}
1151
- self._shape_gl_buffers = {}
1152
- self._shape_instances = defaultdict(list)
1153
- self._instances = {}
1154
- self._instance_custom_ids = {}
1155
- self._instance_shape = {}
1156
- self._instance_gl_buffers = {}
1157
- self._instance_transform_gl_buffer = None
1158
- self._instance_transform_cuda_buffer = None
1159
- self._instance_color1_buffer = None
1160
- self._instance_color2_buffer = None
1161
- self._instance_count = 0
1162
- self._wp_instance_ids = None
1163
- self._wp_instance_custom_ids = None
1164
- self._np_instance_visible = None
1165
- self._instance_ids = None
1166
- self._inverse_instance_ids = None
1167
- self._wp_instance_transforms = None
1168
- self._wp_instance_scalings = None
1169
- self._wp_instance_bodies = None
1170
- self._update_shape_instances = False
1171
- self._add_shape_instances = False
1172
- self.instance_matrix_size = 0
1173
- self.instance_color_size = 0
1174
-
1175
- # additional shape instancer used for points and line rendering
1176
- self._shape_instancers = {}
1177
-
1178
- # instancer for the arrow shapes sof the coordinate system axes
1179
- self._axis_instancer = None
1180
-
1181
- # toggle tiled rendering
1182
- self._tiled_rendering = False
1183
- self._tile_instances = None
1184
- self._tile_ncols = 0
1185
- self._tile_nrows = 0
1186
- self._tile_width = 0
1187
- self._tile_height = 0
1188
- self._tile_viewports = None
1189
- self._tile_view_matrices = None
1190
- self._tile_projection_matrices = None
1191
-
1192
- self._frame_texture = None
1193
- self._frame_depth_texture = None
1194
- self._frame_fbo = None
1195
- self._frame_pbo = None
1196
-
1197
- if not headless:
1198
- self.window.push_handlers(on_draw=self._draw)
1199
- self.window.push_handlers(on_resize=self._window_resize_callback)
1200
- self.window.push_handlers(on_key_press=self._key_press_callback)
1201
- self.window.push_handlers(on_close=self._close_callback)
1202
-
1203
- self._key_handler = pyglet.window.key.KeyStateHandler()
1204
- self.window.push_handlers(self._key_handler)
1205
-
1206
- self.window.on_mouse_scroll = self._scroll_callback
1207
- self.window.on_mouse_drag = self._mouse_drag_callback
1208
-
1209
- gl.glClearColor(*self.background_color, 1)
1210
- gl.glEnable(gl.GL_DEPTH_TEST)
1211
- gl.glDepthMask(True)
1212
- gl.glDepthRange(0.0, 1.0)
1213
-
1214
- self._shape_shader = ShaderProgram(
1215
- Shader(shape_vertex_shader, "vertex"), Shader(shape_fragment_shader, "fragment")
1216
- )
1217
- self._grid_shader = ShaderProgram(
1218
- Shader(grid_vertex_shader, "vertex"), Shader(grid_fragment_shader, "fragment")
1219
- )
1220
-
1221
- self._sun_direction = np.array((-0.2, 0.8, 0.3))
1222
- self._sun_direction /= np.linalg.norm(self._sun_direction)
1223
- with self._shape_shader:
1224
- gl.glUniform3f(
1225
- gl.glGetUniformLocation(self._shape_shader.id, str_buffer("sunDirection")), *self._sun_direction
1226
- )
1227
- gl.glUniform3f(gl.glGetUniformLocation(self._shape_shader.id, str_buffer("lightColor")), 1, 1, 1)
1228
- self._loc_shape_model = gl.glGetUniformLocation(self._shape_shader.id, str_buffer("model"))
1229
- self._loc_shape_view = gl.glGetUniformLocation(self._shape_shader.id, str_buffer("view"))
1230
- self._loc_shape_projection = gl.glGetUniformLocation(self._shape_shader.id, str_buffer("projection"))
1231
- self._loc_shape_view_pos = gl.glGetUniformLocation(self._shape_shader.id, str_buffer("viewPos"))
1232
- gl.glUniform3f(self._loc_shape_view_pos, 0, 0, 10)
1233
-
1234
- # create grid data
1235
- limit = 10.0
1236
- ticks = np.linspace(-limit, limit, 21)
1237
- grid_vertices = []
1238
- for i in ticks:
1239
- if self._camera_axis == 0:
1240
- grid_vertices.extend([0, -limit, i, 0, limit, i])
1241
- grid_vertices.extend([0, i, -limit, 0, i, limit])
1242
- elif self._camera_axis == 1:
1243
- grid_vertices.extend([-limit, 0, i, limit, 0, i])
1244
- grid_vertices.extend([i, 0, -limit, i, 0, limit])
1245
- elif self._camera_axis == 2:
1246
- grid_vertices.extend([-limit, i, 0, limit, i, 0])
1247
- grid_vertices.extend([i, -limit, 0, i, limit, 0])
1248
- grid_vertices = np.array(grid_vertices, dtype=np.float32)
1249
- self._grid_vertex_count = len(grid_vertices) // 3
1250
-
1251
- with self._grid_shader:
1252
- self._grid_vao = gl.GLuint()
1253
- gl.glGenVertexArrays(1, self._grid_vao)
1254
- gl.glBindVertexArray(self._grid_vao)
1255
-
1256
- self._grid_vbo = gl.GLuint()
1257
- gl.glGenBuffers(1, self._grid_vbo)
1258
- gl.glBindBuffer(gl.GL_ARRAY_BUFFER, self._grid_vbo)
1259
- gl.glBufferData(gl.GL_ARRAY_BUFFER, grid_vertices.nbytes, grid_vertices.ctypes.data, gl.GL_STATIC_DRAW)
1260
-
1261
- self._loc_grid_view = gl.glGetUniformLocation(self._grid_shader.id, str_buffer("view"))
1262
- self._loc_grid_model = gl.glGetUniformLocation(self._grid_shader.id, str_buffer("model"))
1263
- self._loc_grid_projection = gl.glGetUniformLocation(self._grid_shader.id, str_buffer("projection"))
1264
-
1265
- self._loc_grid_pos_attribute = gl.glGetAttribLocation(self._grid_shader.id, str_buffer("position"))
1266
- gl.glVertexAttribPointer(self._loc_grid_pos_attribute, 3, gl.GL_FLOAT, gl.GL_FALSE, 0, None)
1267
- gl.glEnableVertexAttribArray(self._loc_grid_pos_attribute)
1268
-
1269
- # create sky data
1270
- self._sky_shader = ShaderProgram(Shader(sky_vertex_shader, "vertex"), Shader(sky_fragment_shader, "fragment"))
1271
-
1272
- with self._sky_shader:
1273
- self._loc_sky_view = gl.glGetUniformLocation(self._sky_shader.id, str_buffer("view"))
1274
- self._loc_sky_inv_model = gl.glGetUniformLocation(self._sky_shader.id, str_buffer("inv_model"))
1275
- self._loc_sky_projection = gl.glGetUniformLocation(self._sky_shader.id, str_buffer("projection"))
1276
-
1277
- self._loc_sky_color1 = gl.glGetUniformLocation(self._sky_shader.id, str_buffer("color1"))
1278
- self._loc_sky_color2 = gl.glGetUniformLocation(self._sky_shader.id, str_buffer("color2"))
1279
- self._loc_sky_far_plane = gl.glGetUniformLocation(self._sky_shader.id, str_buffer("farPlane"))
1280
- gl.glUniform3f(self._loc_sky_color1, *background_color)
1281
- # glUniform3f(self._loc_sky_color2, *np.clip(np.array(background_color)+0.5, 0.0, 1.0))
1282
- gl.glUniform3f(self._loc_sky_color2, 0.8, 0.4, 0.05)
1283
- gl.glUniform1f(self._loc_sky_far_plane, self.camera_far_plane)
1284
- self._loc_sky_view_pos = gl.glGetUniformLocation(self._sky_shader.id, str_buffer("viewPos"))
1285
- gl.glUniform3f(
1286
- gl.glGetUniformLocation(self._sky_shader.id, str_buffer("sunDirection")), *self._sun_direction
1287
- )
1288
-
1289
- # create VAO, VBO, and EBO
1290
- self._sky_vao = gl.GLuint()
1291
- gl.glGenVertexArrays(1, self._sky_vao)
1292
- gl.glBindVertexArray(self._sky_vao)
1293
-
1294
- vertices, indices = self._create_sphere_mesh(self.camera_far_plane * 0.9, 32, 32, reverse_winding=True)
1295
- self._sky_tri_count = len(indices)
1296
-
1297
- self._sky_vbo = gl.GLuint()
1298
- gl.glGenBuffers(1, self._sky_vbo)
1299
- gl.glBindBuffer(gl.GL_ARRAY_BUFFER, self._sky_vbo)
1300
- gl.glBufferData(gl.GL_ARRAY_BUFFER, vertices.nbytes, vertices.ctypes.data, gl.GL_STATIC_DRAW)
1301
-
1302
- self._sky_ebo = gl.GLuint()
1303
- gl.glGenBuffers(1, self._sky_ebo)
1304
- gl.glBindBuffer(gl.GL_ELEMENT_ARRAY_BUFFER, self._sky_ebo)
1305
- gl.glBufferData(gl.GL_ELEMENT_ARRAY_BUFFER, indices.nbytes, indices.ctypes.data, gl.GL_STATIC_DRAW)
1306
-
1307
- # set up vertex attributes
1308
- vertex_stride = vertices.shape[1] * vertices.itemsize
1309
- # positions
1310
- gl.glVertexAttribPointer(0, 3, gl.GL_FLOAT, gl.GL_FALSE, vertex_stride, ctypes.c_void_p(0))
1311
- gl.glEnableVertexAttribArray(0)
1312
- # normals
1313
- gl.glVertexAttribPointer(1, 3, gl.GL_FLOAT, gl.GL_FALSE, vertex_stride, ctypes.c_void_p(3 * vertices.itemsize))
1314
- gl.glEnableVertexAttribArray(1)
1315
- # uv coordinates
1316
- gl.glVertexAttribPointer(2, 2, gl.GL_FLOAT, gl.GL_FALSE, vertex_stride, ctypes.c_void_p(6 * vertices.itemsize))
1317
- gl.glEnableVertexAttribArray(2)
1318
-
1319
- gl.glBindVertexArray(0)
1320
-
1321
- self._last_time = time.time()
1322
- self._last_begin_frame_time = self._last_time
1323
- self._last_end_frame_time = self._last_time
1324
-
1325
- # create arrow shapes for the coordinate system axes
1326
- vertices, indices = self._create_arrow_mesh(
1327
- base_radius=0.02 * axis_scale, base_height=0.85 * axis_scale, cap_height=0.15 * axis_scale
1328
- )
1329
- self._axis_instancer = ShapeInstancer(self._shape_shader, self._device)
1330
- self._axis_instancer.register_shape(vertices, indices)
1331
- sqh = np.sqrt(0.5)
1332
- self._axis_instancer.allocate_instances(
1333
- positions=[(0.0, 0.0, 0.0), (0.0, 0.0, 0.0), (0.0, 0.0, 0.0)],
1334
- rotations=[(0.0, 0.0, 0.0, 1.0), (0.0, 0.0, -sqh, sqh), (sqh, 0.0, 0.0, sqh)],
1335
- colors1=[(0.0, 1.0, 0.0), (1.0, 0.0, 0.0), (0.0, 0.0, 1.0)],
1336
- colors2=[(0.0, 1.0, 0.0), (1.0, 0.0, 0.0), (0.0, 0.0, 1.0)],
1337
- )
1338
-
1339
- # create frame buffer for rendering to a texture
1340
- self._frame_texture = None
1341
- self._frame_depth_texture = None
1342
- self._frame_fbo = None
1343
- self._setup_framebuffer()
1344
-
1345
- # fmt: off
1346
- # set up VBO for the quad that is rendered to the user window with the texture
1347
- self._frame_vertices = np.array([
1348
- # Positions TexCoords
1349
- -1.0, -1.0, 0.0, 0.0,
1350
- 1.0, -1.0, 1.0, 0.0,
1351
- 1.0, 1.0, 1.0, 1.0,
1352
- -1.0, 1.0, 0.0, 1.0
1353
- ], dtype=np.float32)
1354
- # fmt: on
1355
-
1356
- self._frame_indices = np.array([0, 1, 2, 2, 3, 0], dtype=np.uint32)
1357
-
1358
- self._frame_vao = gl.GLuint()
1359
- gl.glGenVertexArrays(1, self._frame_vao)
1360
- gl.glBindVertexArray(self._frame_vao)
1361
-
1362
- self._frame_vbo = gl.GLuint()
1363
- gl.glGenBuffers(1, self._frame_vbo)
1364
- gl.glBindBuffer(gl.GL_ARRAY_BUFFER, self._frame_vbo)
1365
- gl.glBufferData(
1366
- gl.GL_ARRAY_BUFFER, self._frame_vertices.nbytes, self._frame_vertices.ctypes.data, gl.GL_STATIC_DRAW
1367
- )
1368
-
1369
- self._frame_ebo = gl.GLuint()
1370
- gl.glGenBuffers(1, self._frame_ebo)
1371
- gl.glBindBuffer(gl.GL_ELEMENT_ARRAY_BUFFER, self._frame_ebo)
1372
- gl.glBufferData(
1373
- gl.GL_ELEMENT_ARRAY_BUFFER, self._frame_indices.nbytes, self._frame_indices.ctypes.data, gl.GL_STATIC_DRAW
1374
- )
1375
-
1376
- gl.glVertexAttribPointer(0, 2, gl.GL_FLOAT, gl.GL_FALSE, 4 * self._frame_vertices.itemsize, ctypes.c_void_p(0))
1377
- gl.glEnableVertexAttribArray(0)
1378
- gl.glVertexAttribPointer(
1379
- 1, 2, gl.GL_FLOAT, gl.GL_FALSE, 4 * self._frame_vertices.itemsize, ctypes.c_void_p(2 * vertices.itemsize)
1380
- )
1381
- gl.glEnableVertexAttribArray(1)
1382
-
1383
- self._frame_shader = ShaderProgram(
1384
- Shader(frame_vertex_shader, "vertex"), Shader(frame_fragment_shader, "fragment")
1385
- )
1386
- gl.glUseProgram(self._frame_shader.id)
1387
- self._frame_loc_texture = gl.glGetUniformLocation(self._frame_shader.id, str_buffer("textureSampler"))
1388
-
1389
- self._frame_depth_shader = ShaderProgram(
1390
- Shader(frame_vertex_shader, "vertex"), Shader(frame_depth_fragment_shader, "fragment")
1391
- )
1392
- gl.glUseProgram(self._frame_depth_shader.id)
1393
- self._frame_loc_depth_texture = gl.glGetUniformLocation(
1394
- self._frame_depth_shader.id, str_buffer("textureSampler")
1395
- )
1396
-
1397
- # unbind the VBO and VAO
1398
- gl.glBindBuffer(gl.GL_ARRAY_BUFFER, 0)
1399
- gl.glBindVertexArray(0)
1400
-
1401
- # update model matrix
1402
- self.scaling = scaling
1403
-
1404
- check_gl_error()
1405
-
1406
- # create text to render stats on the screen
1407
- self._info_label = pyglet.text.Label(
1408
- "",
1409
- font_name="Arial",
1410
- font_size=12,
1411
- color=(255, 255, 255, 255),
1412
- x=10,
1413
- y=10,
1414
- anchor_x="left",
1415
- anchor_y="top",
1416
- multiline=True,
1417
- width=400,
1418
- )
1419
-
1420
- if not headless:
1421
- # set up our own event handling so we can synchronously render frames
1422
- # by calling update() in a loop
1423
- from pyglet.window import Window
1424
-
1425
- Window._enable_event_queue = False
1426
-
1427
- self.window.dispatch_pending_events()
1428
-
1429
- platform_event_loop = self.app.platform_event_loop
1430
- platform_event_loop.start()
1431
-
1432
- # start event loop
1433
- self.app.event_loop.dispatch_event("on_enter")
1434
-
1435
- @property
1436
- def paused(self):
1437
- return self._paused
1438
-
1439
- @paused.setter
1440
- def paused(self, value):
1441
- self._paused = value
1442
- if value:
1443
- self.window.set_caption(f"{self._title} (paused)")
1444
- else:
1445
- self.window.set_caption(self._title)
1446
-
1447
- @property
1448
- def has_exit(self):
1449
- return self.app.event_loop.has_exit
1450
-
1451
- def clear(self):
1452
- gl = OpenGLRenderer.gl
1453
-
1454
- self._switch_context()
1455
-
1456
- if not self.headless:
1457
- self.app.event_loop.dispatch_event("on_exit")
1458
- self.app.platform_event_loop.stop()
1459
-
1460
- if self._instance_transform_gl_buffer is not None:
1461
- try:
1462
- gl.glDeleteBuffers(1, self._instance_transform_gl_buffer)
1463
- gl.glDeleteBuffers(1, self._instance_color1_buffer)
1464
- gl.glDeleteBuffers(1, self._instance_color2_buffer)
1465
- except gl.GLException:
1466
- pass
1467
- for vao, vbo, ebo, _, _vertex_cuda_buffer in self._shape_gl_buffers.values():
1468
- try:
1469
- gl.glDeleteVertexArrays(1, vao)
1470
- gl.glDeleteBuffers(1, vbo)
1471
- gl.glDeleteBuffers(1, ebo)
1472
- except gl.GLException:
1473
- pass
1474
-
1475
- self._body_name.clear()
1476
- self._shapes.clear()
1477
- self._shape_geo_hash.clear()
1478
- self._shape_gl_buffers.clear()
1479
- self._shape_instances.clear()
1480
- self._instances.clear()
1481
- self._instance_shape.clear()
1482
- self._instance_gl_buffers.clear()
1483
- self._instance_transform_gl_buffer = None
1484
- self._instance_transform_cuda_buffer = None
1485
- self._instance_color1_buffer = None
1486
- self._instance_color2_buffer = None
1487
- self._wp_instance_ids = None
1488
- self._wp_instance_custom_ids = None
1489
- self._wp_instance_transforms = None
1490
- self._wp_instance_scalings = None
1491
- self._wp_instance_bodies = None
1492
- self._np_instance_visible = None
1493
- self._update_shape_instances = False
1494
-
1495
- def close(self):
1496
- self.clear()
1497
- self.window.close()
1498
-
1499
- @property
1500
- def tiled_rendering(self):
1501
- return self._tiled_rendering
1502
-
1503
- @tiled_rendering.setter
1504
- def tiled_rendering(self, value):
1505
- if value:
1506
- assert self._tile_instances is not None, "Tiled rendering is not set up. Call setup_tiled_rendering first."
1507
- self._tiled_rendering = value
1508
-
1509
- def setup_tiled_rendering(
1510
- self,
1511
- instances: list[list[int]],
1512
- rescale_window: bool = False,
1513
- tile_width: int | None = None,
1514
- tile_height: int | None = None,
1515
- tile_ncols: int | None = None,
1516
- tile_nrows: int | None = None,
1517
- tile_positions: list[tuple[int]] | None = None,
1518
- tile_sizes: list[tuple[int]] | None = None,
1519
- projection_matrices: list[Mat44] | None = None,
1520
- view_matrices: list[Mat44] | None = None,
1521
- ):
1522
- """
1523
- Set up tiled rendering where the render buffer is split into multiple tiles that can visualize
1524
- different shape instances of the scene with different view and projection matrices.
1525
- See :meth:`get_pixels` which allows to retrieve the pixels of for each tile.
1526
- See :meth:`update_tile` which allows to update the shape instances, projection matrix, view matrix, tile size, or tile position for a given tile.
1527
-
1528
- :param instances: A list of lists of shape instance ids. Each list of shape instance ids
1529
- will be rendered into a separate tile.
1530
- :param rescale_window: If True, the window will be resized to fit the tiles.
1531
- :param tile_width: The width of each tile in pixels (optional).
1532
- :param tile_height: The height of each tile in pixels (optional).
1533
- :param tile_ncols: The number of tiles rendered horizontally (optional). Will be considered
1534
- if `tile_width` is set to compute the tile positions, unless `tile_positions` is defined.
1535
- :param tile_positions: A list of (x, y) tuples specifying the position of each tile in pixels.
1536
- If None, the tiles will be arranged in a square grid, or, if `tile_ncols` and `tile_nrows`
1537
- is set, in a grid with the specified number of columns and rows.
1538
- :param tile_sizes: A list of (width, height) tuples specifying the size of each tile in pixels.
1539
- If None, the tiles will have the same size as specified by `tile_width` and `tile_height`.
1540
- :param projection_matrices: A list of projection matrices for each tile (each view matrix is
1541
- either a flattened 16-dimensional array or a 4x4 matrix).
1542
- If the entire array is None, or only a view instances, the projection matrices for all, or these
1543
- instances, respectively, will be derived from the current render settings.
1544
- :param view_matrices: A list of view matrices for each tile (each view matrix is either a flattened
1545
- 16-dimensional array or a 4x4 matrix).
1546
- If the entire array is None, or only a view instances, the view matrices for all, or these
1547
- instances, respectively, will be derived from the current camera settings and be
1548
- updated when the camera is moved.
1549
- """
1550
-
1551
- assert len(instances) > 0 and all(isinstance(i, list) for i in instances), "Invalid tile instances."
1552
-
1553
- self._tile_instances = instances
1554
- n = len(self._tile_instances)
1555
-
1556
- if tile_positions is None or tile_sizes is None:
1557
- if tile_ncols is None or tile_nrows is None:
1558
- # try to fit the tiles into a square
1559
- self._tile_ncols = int(np.ceil(np.sqrt(n)))
1560
- self._tile_nrows = int(np.ceil(n / float(self._tile_ncols)))
1561
- else:
1562
- self._tile_ncols = tile_ncols
1563
- self._tile_nrows = tile_nrows
1564
- self._tile_width = tile_width or max(32, self.screen_width // self._tile_ncols)
1565
- self._tile_height = tile_height or max(32, self.screen_height // self._tile_nrows)
1566
- self._tile_viewports = [
1567
- (i * self._tile_width, j * self._tile_height, self._tile_width, self._tile_height)
1568
- for i in range(self._tile_ncols)
1569
- for j in range(self._tile_nrows)
1570
- ]
1571
- if rescale_window:
1572
- self.window.set_size(self._tile_width * self._tile_ncols, self._tile_height * self._tile_nrows)
1573
- else:
1574
- assert len(tile_positions) == n and len(tile_sizes) == n, (
1575
- "Number of tiles does not match number of instances."
1576
- )
1577
- self._tile_ncols = None
1578
- self._tile_nrows = None
1579
- self._tile_width = None
1580
- self._tile_height = None
1581
- if all(tile_sizes[i][0] == tile_sizes[0][0] for i in range(n)):
1582
- # tiles all have the same width
1583
- self._tile_width = tile_sizes[0][0]
1584
- if all(tile_sizes[i][1] == tile_sizes[0][1] for i in range(n)):
1585
- # tiles all have the same height
1586
- self._tile_height = tile_sizes[0][1]
1587
- self._tile_viewports = [(x, y, w, h) for (x, y), (w, h) in zip(tile_positions, tile_sizes)]
1588
-
1589
- if projection_matrices is None:
1590
- projection_matrices = [None] * n
1591
- self._tile_projection_matrices = []
1592
- for i, p in enumerate(projection_matrices):
1593
- if p is None:
1594
- w, h = self._tile_viewports[i][2:]
1595
- self._tile_projection_matrices.append(
1596
- self.compute_projection_matrix(
1597
- self.camera_fov, w / h, self.camera_near_plane, self.camera_far_plane
1598
- )
1599
- )
1600
- else:
1601
- self._tile_projection_matrices.append(np.array(p).flatten())
1602
-
1603
- if view_matrices is None:
1604
- self._tile_view_matrices = [None] * n
1605
- else:
1606
- self._tile_view_matrices = [np.array(m).flatten() for m in view_matrices]
1607
-
1608
- self._tiled_rendering = True
1609
-
1610
- def update_tile(
1611
- self,
1612
- tile_id,
1613
- instances: list[int] | None = None,
1614
- projection_matrix: Mat44 | None = None,
1615
- view_matrix: Mat44 | None = None,
1616
- tile_size: tuple[int] | None = None,
1617
- tile_position: tuple[int] | None = None,
1618
- ):
1619
- """
1620
- Update the shape instances, projection matrix, view matrix, tile size, or tile position
1621
- for a given tile given its index.
1622
-
1623
- :param tile_id: The index of the tile to update.
1624
- :param instances: A list of shape instance ids (optional).
1625
- :param projection_matrix: A projection matrix (optional).
1626
- :param view_matrix: A view matrix (optional).
1627
- :param tile_size: A (width, height) tuple specifying the size of the tile in pixels (optional).
1628
- :param tile_position: A (x, y) tuple specifying the position of the tile in pixels (optional).
1629
- """
1630
-
1631
- assert self._tile_instances is not None, "Tiled rendering is not set up. Call setup_tiled_rendering first."
1632
- assert tile_id < len(self._tile_instances), "Invalid tile id."
1633
-
1634
- if instances is not None:
1635
- self._tile_instances[tile_id] = instances
1636
- if projection_matrix is not None:
1637
- self._tile_projection_matrices[tile_id] = np.array(projection_matrix).flatten()
1638
- if view_matrix is not None:
1639
- self._tile_view_matrices[tile_id] = np.array(view_matrix).flatten()
1640
- (x, y, w, h) = self._tile_viewports[tile_id]
1641
- if tile_size is not None:
1642
- w, h = tile_size
1643
- if tile_position is not None:
1644
- x, y = tile_position
1645
- self._tile_viewports[tile_id] = (x, y, w, h)
1646
-
1647
- def _setup_framebuffer(self):
1648
- gl = OpenGLRenderer.gl
1649
-
1650
- self._switch_context()
1651
-
1652
- if self._frame_texture is None:
1653
- self._frame_texture = gl.GLuint()
1654
- gl.glGenTextures(1, self._frame_texture)
1655
- if self._frame_depth_texture is None:
1656
- self._frame_depth_texture = gl.GLuint()
1657
- gl.glGenTextures(1, self._frame_depth_texture)
1658
-
1659
- # set up RGB texture
1660
- gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, 0)
1661
- gl.glBindBuffer(gl.GL_PIXEL_UNPACK_BUFFER, 0)
1662
- gl.glBindTexture(gl.GL_TEXTURE_2D, self._frame_texture)
1663
- gl.glTexImage2D(
1664
- gl.GL_TEXTURE_2D,
1665
- 0,
1666
- gl.GL_RGB,
1667
- self.screen_width,
1668
- self.screen_height,
1669
- 0,
1670
- gl.GL_RGB,
1671
- gl.GL_UNSIGNED_BYTE,
1672
- None,
1673
- )
1674
- gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MIN_FILTER, gl.GL_LINEAR)
1675
- gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MAG_FILTER, gl.GL_LINEAR)
1676
-
1677
- # set up depth texture
1678
- gl.glBindTexture(gl.GL_TEXTURE_2D, self._frame_depth_texture)
1679
- gl.glTexImage2D(
1680
- gl.GL_TEXTURE_2D,
1681
- 0,
1682
- gl.GL_DEPTH_COMPONENT32,
1683
- self.screen_width,
1684
- self.screen_height,
1685
- 0,
1686
- gl.GL_DEPTH_COMPONENT,
1687
- gl.GL_FLOAT,
1688
- None,
1689
- )
1690
- gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MIN_FILTER, gl.GL_LINEAR)
1691
- gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MAG_FILTER, gl.GL_LINEAR)
1692
- gl.glBindTexture(gl.GL_TEXTURE_2D, 0)
1693
-
1694
- # create a framebuffer object (FBO)
1695
- if self._frame_fbo is None:
1696
- self._frame_fbo = gl.GLuint()
1697
- gl.glGenFramebuffers(1, self._frame_fbo)
1698
- gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, self._frame_fbo)
1699
-
1700
- # attach the texture to the FBO as its color attachment
1701
- gl.glFramebufferTexture2D(
1702
- gl.GL_FRAMEBUFFER, gl.GL_COLOR_ATTACHMENT0, gl.GL_TEXTURE_2D, self._frame_texture, 0
1703
- )
1704
- # attach the depth texture to the FBO as its depth attachment
1705
- gl.glFramebufferTexture2D(
1706
- gl.GL_FRAMEBUFFER, gl.GL_DEPTH_ATTACHMENT, gl.GL_TEXTURE_2D, self._frame_depth_texture, 0
1707
- )
1708
-
1709
- if gl.glCheckFramebufferStatus(gl.GL_FRAMEBUFFER) != gl.GL_FRAMEBUFFER_COMPLETE:
1710
- print("Framebuffer is not complete!", flush=True)
1711
- gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, 0)
1712
- sys.exit(1)
1713
-
1714
- # unbind the FBO (switch back to the default framebuffer)
1715
- gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, 0)
1716
-
1717
- if self._frame_pbo is None:
1718
- self._frame_pbo = gl.GLuint()
1719
- gl.glGenBuffers(1, self._frame_pbo) # generate 1 buffer reference
1720
- gl.glBindBuffer(gl.GL_PIXEL_PACK_BUFFER, self._frame_pbo) # binding to this buffer
1721
-
1722
- # allocate memory for PBO
1723
- rgb_bytes_per_pixel = 3
1724
- depth_bytes_per_pixel = 4
1725
- pixels = np.zeros(
1726
- (self.screen_height, self.screen_width, rgb_bytes_per_pixel + depth_bytes_per_pixel), dtype=np.uint8
1727
- )
1728
- gl.glBufferData(gl.GL_PIXEL_PACK_BUFFER, pixels.nbytes, pixels.ctypes.data, gl.GL_DYNAMIC_DRAW)
1729
- gl.glBindBuffer(gl.GL_PIXEL_PACK_BUFFER, 0)
1730
-
1731
- @staticmethod
1732
- def compute_projection_matrix(
1733
- fov: float,
1734
- aspect_ratio: float,
1735
- near_plane: float,
1736
- far_plane: float,
1737
- ) -> Mat44:
1738
- """
1739
- Compute a projection matrix given the field of view, aspect ratio, near plane, and far plane.
1740
-
1741
- :param fov: The field of view in degrees.
1742
- :param aspect_ratio: The aspect ratio (width / height).
1743
- :param near_plane: The near plane.
1744
- :param far_plane: The far plane.
1745
- :return: A projection matrix.
1746
- """
1747
-
1748
- from pyglet.math import Mat4 as PyMat4
1749
-
1750
- return np.array(PyMat4.perspective_projection(aspect_ratio, near_plane, far_plane, fov))
1751
-
1752
- def update_projection_matrix(self):
1753
- if self.screen_height == 0:
1754
- return
1755
- aspect_ratio = self.screen_width / self.screen_height
1756
- self._projection_matrix = self.compute_projection_matrix(
1757
- self.camera_fov, aspect_ratio, self.camera_near_plane, self.camera_far_plane
1758
- )
1759
-
1760
- @property
1761
- def camera_pos(self):
1762
- return self._camera_pos
1763
-
1764
- @camera_pos.setter
1765
- def camera_pos(self, value):
1766
- self.update_view_matrix(cam_pos=value)
1767
-
1768
- @property
1769
- def camera_front(self):
1770
- return self._camera_front
1771
-
1772
- @camera_front.setter
1773
- def camera_front(self, value):
1774
- self.update_view_matrix(cam_front=value)
1775
-
1776
- @property
1777
- def camera_up(self):
1778
- return self._camera_up
1779
-
1780
- @camera_up.setter
1781
- def camera_up(self, value):
1782
- self.update_view_matrix(cam_up=value)
1783
-
1784
- def compute_view_matrix(self, cam_pos, cam_front, cam_up):
1785
- from pyglet.math import Mat4, Vec3
1786
-
1787
- model = np.array(self._model_matrix).reshape((4, 4))
1788
- cp = model @ np.array([*cam_pos / self._scaling, 1.0])
1789
- cf = model @ np.array([*cam_front / self._scaling, 1.0])
1790
- up = model @ np.array([*cam_up / self._scaling, 0.0])
1791
- cp = Vec3(*cp[:3])
1792
- cf = Vec3(*cf[:3])
1793
- up = Vec3(*up[:3])
1794
- return np.array(Mat4.look_at(cp, cp + cf, up), dtype=np.float32)
1795
-
1796
- def update_view_matrix(self, cam_pos=None, cam_front=None, cam_up=None, stiffness=1.0):
1797
- from pyglet.math import Vec3
1798
-
1799
- if cam_pos is not None:
1800
- self._camera_pos = self._camera_pos * (1.0 - stiffness) + Vec3(*cam_pos) * stiffness
1801
- if cam_front is not None:
1802
- self._camera_front = self._camera_front * (1.0 - stiffness) + Vec3(*cam_front) * stiffness
1803
- if cam_up is not None:
1804
- self._camera_up = self._camera_up * (1.0 - stiffness) + Vec3(*cam_up) * stiffness
1805
-
1806
- self._view_matrix = self.compute_view_matrix(self._camera_pos, self._camera_front, self._camera_up)
1807
-
1808
- @staticmethod
1809
- def compute_model_matrix(camera_axis: int, scaling: float):
1810
- if camera_axis == 0:
1811
- return np.array((0, 0, scaling, 0, scaling, 0, 0, 0, 0, scaling, 0, 0, 0, 0, 0, 1), dtype=np.float32)
1812
- elif camera_axis == 2:
1813
- return np.array((0, scaling, 0, 0, 0, 0, scaling, 0, scaling, 0, 0, 0, 0, 0, 0, 1), dtype=np.float32)
1814
-
1815
- return np.array((scaling, 0, 0, 0, 0, scaling, 0, 0, 0, 0, scaling, 0, 0, 0, 0, 1), dtype=np.float32)
1816
-
1817
- def update_model_matrix(self, model_matrix: Mat44 | None = None):
1818
- gl = OpenGLRenderer.gl
1819
-
1820
- self._switch_context()
1821
-
1822
- if model_matrix is None:
1823
- self._model_matrix = self.compute_model_matrix(self._camera_axis, self._scaling)
1824
- else:
1825
- self._model_matrix = np.array(model_matrix).flatten()
1826
- self._inv_model_matrix = np.linalg.inv(self._model_matrix.reshape((4, 4))).flatten()
1827
- # update model view matrix in shaders
1828
- ptr = arr_pointer(self._model_matrix)
1829
- gl.glUseProgram(self._shape_shader.id)
1830
- gl.glUniformMatrix4fv(self._loc_shape_model, 1, gl.GL_FALSE, ptr)
1831
- gl.glUseProgram(self._grid_shader.id)
1832
- gl.glUniformMatrix4fv(self._loc_grid_model, 1, gl.GL_FALSE, ptr)
1833
- # sky shader needs inverted model view matrix
1834
- gl.glUseProgram(self._sky_shader.id)
1835
- inv_ptr = arr_pointer(self._inv_model_matrix)
1836
- gl.glUniformMatrix4fv(self._loc_sky_inv_model, 1, gl.GL_FALSE, inv_ptr)
1837
-
1838
- @property
1839
- def num_tiles(self):
1840
- return len(self._tile_instances)
1841
-
1842
- @property
1843
- def tile_width(self):
1844
- return self._tile_width
1845
-
1846
- @property
1847
- def tile_height(self):
1848
- return self._tile_height
1849
-
1850
- @property
1851
- def num_shapes(self):
1852
- return len(self._shapes)
1853
-
1854
- @property
1855
- def num_instances(self):
1856
- return self._instance_count
1857
-
1858
- @property
1859
- def scaling(self):
1860
- return self._scaling
1861
-
1862
- @scaling.setter
1863
- def scaling(self, scaling):
1864
- self._scaling = scaling
1865
- self.update_model_matrix()
1866
-
1867
- def begin_frame(self, t: float | None = None):
1868
- self._last_begin_frame_time = time.time()
1869
- self.time = t or self.clock_time
1870
-
1871
- def end_frame(self):
1872
- self._last_end_frame_time = time.time()
1873
- if self._add_shape_instances:
1874
- self.allocate_shape_instances()
1875
- if self._update_shape_instances:
1876
- self.update_shape_instances()
1877
- self.update()
1878
- while self.paused and self.is_running():
1879
- self.update()
1880
-
1881
- def update(self):
1882
- self.clock_time = time.time() - self._start_time
1883
- update_duration = self.clock_time - self._last_time
1884
- frame_duration = self._last_end_frame_time - self._last_begin_frame_time
1885
- self._last_time = self.clock_time
1886
- self._frame_speed = update_duration * 100.0
1887
-
1888
- if not self.headless:
1889
- self.app.platform_event_loop.step(self._frame_dt * 1e-3)
1890
-
1891
- if not self.skip_rendering:
1892
- self._skip_frame_counter += 1
1893
- if self._skip_frame_counter > 100:
1894
- self._skip_frame_counter = 0
1895
-
1896
- if frame_duration > 0.0:
1897
- if self._fps_update is None:
1898
- self._fps_update = 1.0 / frame_duration
1899
- else:
1900
- update = 1.0 / frame_duration
1901
- self._fps_update = (1.0 - self._fps_alpha) * self._fps_update + self._fps_alpha * update
1902
- if update_duration > 0.0:
1903
- if self._fps_render is None:
1904
- self._fps_render = 1.0 / update_duration
1905
- else:
1906
- update = 1.0 / update_duration
1907
- self._fps_render = (1.0 - self._fps_alpha) * self._fps_render + self._fps_alpha * update
1908
-
1909
- if not self.headless:
1910
- self.app.event_loop._redraw_windows(self._frame_dt * 1e-3)
1911
- else:
1912
- self._draw()
1913
-
1914
- def _draw(self):
1915
- gl = OpenGLRenderer.gl
1916
-
1917
- self._switch_context()
1918
-
1919
- if not self.headless:
1920
- # catch key hold events
1921
- self._process_inputs()
1922
-
1923
- if self.enable_backface_culling:
1924
- gl.glEnable(gl.GL_CULL_FACE)
1925
- else:
1926
- gl.glDisable(gl.GL_CULL_FACE)
1927
-
1928
- if self._frame_fbo is not None:
1929
- gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, self._frame_fbo)
1930
-
1931
- gl.glClearColor(*self.background_color, 1)
1932
- gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT)
1933
- gl.glBindVertexArray(0)
1934
-
1935
- if not self._tiled_rendering:
1936
- if self.draw_grid:
1937
- self._draw_grid()
1938
-
1939
- if self.draw_sky:
1940
- self._draw_sky()
1941
-
1942
- view_mat_ptr = arr_pointer(self._view_matrix)
1943
- projection_mat_ptr = arr_pointer(self._projection_matrix)
1944
- gl.glUseProgram(self._shape_shader.id)
1945
- gl.glUniformMatrix4fv(self._loc_shape_view, 1, gl.GL_FALSE, view_mat_ptr)
1946
- gl.glUniform3f(self._loc_shape_view_pos, *self._camera_pos)
1947
- gl.glUniformMatrix4fv(self._loc_shape_view, 1, gl.GL_FALSE, view_mat_ptr)
1948
- gl.glUniformMatrix4fv(self._loc_shape_projection, 1, gl.GL_FALSE, projection_mat_ptr)
1949
-
1950
- if self.render_wireframe:
1951
- gl.glPolygonMode(gl.GL_FRONT_AND_BACK, gl.GL_LINE)
1952
-
1953
- if self._tiled_rendering:
1954
- self._render_scene_tiled()
1955
- else:
1956
- self._render_scene()
1957
-
1958
- for cb in self.render_3d_callbacks:
1959
- cb()
1960
-
1961
- gl.glPolygonMode(gl.GL_FRONT_AND_BACK, gl.GL_FILL)
1962
-
1963
- gl.glBindBuffer(gl.GL_PIXEL_UNPACK_BUFFER, 0)
1964
- gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, 0)
1965
- gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT)
1966
- gl.glViewport(0, 0, self.screen_width, self.screen_height)
1967
-
1968
- # render frame buffer texture to screen
1969
- if self._frame_fbo is not None:
1970
- if self.render_depth:
1971
- with self._frame_depth_shader:
1972
- gl.glActiveTexture(gl.GL_TEXTURE0)
1973
- gl.glBindTexture(gl.GL_TEXTURE_2D, self._frame_depth_texture)
1974
- gl.glUniform1i(self._frame_loc_depth_texture, 0)
1975
-
1976
- gl.glBindVertexArray(self._frame_vao)
1977
- gl.glDrawElements(gl.GL_TRIANGLES, len(self._frame_indices), gl.GL_UNSIGNED_INT, None)
1978
- gl.glBindVertexArray(0)
1979
- gl.glBindTexture(gl.GL_TEXTURE_2D, 0)
1980
- else:
1981
- with self._frame_shader:
1982
- gl.glActiveTexture(gl.GL_TEXTURE0)
1983
- gl.glBindTexture(gl.GL_TEXTURE_2D, self._frame_texture)
1984
- gl.glUniform1i(self._frame_loc_texture, 0)
1985
-
1986
- gl.glBindVertexArray(self._frame_vao)
1987
- gl.glDrawElements(gl.GL_TRIANGLES, len(self._frame_indices), gl.GL_UNSIGNED_INT, None)
1988
- gl.glBindVertexArray(0)
1989
- gl.glBindTexture(gl.GL_TEXTURE_2D, 0)
1990
-
1991
- # check for OpenGL errors
1992
- # check_gl_error()
1993
-
1994
- if self.show_info:
1995
- gl.glClear(gl.GL_DEPTH_BUFFER_BIT)
1996
- gl.glBlendFunc(gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA)
1997
- gl.glEnable(gl.GL_BLEND)
1998
-
1999
- # disable depth test to fix text rendering
2000
- # https://github.com/pyglet/pyglet/issues/1302
2001
- gl.glDisable(gl.GL_DEPTH_TEST)
2002
-
2003
- text = f"""Sim Time: {self.time:.1f}
2004
- Update FPS: {self._fps_update:.1f}
2005
- Render FPS: {self._fps_render:.1f}
2006
-
2007
- Shapes: {len(self._shapes)}
2008
- Instances: {len(self._instances)}"""
2009
- if self.paused:
2010
- text += "\nPaused (press space to resume)"
2011
-
2012
- self._info_label.text = text
2013
- self._info_label.y = self.screen_height - 5
2014
- self._info_label.draw()
2015
-
2016
- gl.glEnable(gl.GL_DEPTH_TEST)
2017
-
2018
- for cb in self.render_2d_callbacks:
2019
- cb()
2020
-
2021
- def _draw_grid(self, is_tiled=False):
2022
- gl = OpenGLRenderer.gl
2023
-
2024
- self._switch_context()
2025
-
2026
- if not is_tiled:
2027
- gl.glUseProgram(self._grid_shader.id)
2028
-
2029
- gl.glUniformMatrix4fv(self._loc_grid_view, 1, gl.GL_FALSE, arr_pointer(self._view_matrix))
2030
- gl.glUniformMatrix4fv(self._loc_grid_projection, 1, gl.GL_FALSE, arr_pointer(self._projection_matrix))
2031
-
2032
- gl.glBindVertexArray(self._grid_vao)
2033
- gl.glDrawArrays(gl.GL_LINES, 0, self._grid_vertex_count)
2034
- gl.glBindVertexArray(0)
2035
-
2036
- def _draw_sky(self, is_tiled=False):
2037
- gl = OpenGLRenderer.gl
2038
-
2039
- self._switch_context()
2040
-
2041
- if not is_tiled:
2042
- gl.glUseProgram(self._sky_shader.id)
2043
-
2044
- gl.glUniformMatrix4fv(self._loc_sky_view, 1, gl.GL_FALSE, arr_pointer(self._view_matrix))
2045
- gl.glUniformMatrix4fv(self._loc_sky_projection, 1, gl.GL_FALSE, arr_pointer(self._projection_matrix))
2046
- gl.glUniform3f(self._loc_sky_view_pos, *self._camera_pos)
2047
-
2048
- gl.glBindVertexArray(self._sky_vao)
2049
- gl.glDrawElements(gl.GL_TRIANGLES, self._sky_tri_count, gl.GL_UNSIGNED_INT, None)
2050
- gl.glBindVertexArray(0)
2051
-
2052
- def _render_scene(self):
2053
- gl = OpenGLRenderer.gl
2054
-
2055
- self._switch_context()
2056
-
2057
- start_instance_idx = 0
2058
-
2059
- for shape, (vao, _, _, tri_count, _) in self._shape_gl_buffers.items():
2060
- num_instances = len(self._shape_instances[shape])
2061
-
2062
- gl.glBindVertexArray(vao)
2063
- if self.use_legacy_opengl:
2064
- if self.instance_matrix_size > 0 and self.instance_color_size:
2065
- # update attribute pointers
2066
- matrix_size = self.instance_matrix_size
2067
- color_size = self.instance_color_size
2068
-
2069
- # update transforms
2070
- gl.glBindBuffer(gl.GL_ARRAY_BUFFER, self._instance_transform_gl_buffer)
2071
- for i in range(4):
2072
- gl.glVertexAttribPointer(
2073
- 3 + i,
2074
- 4,
2075
- gl.GL_FLOAT,
2076
- gl.GL_FALSE,
2077
- matrix_size,
2078
- ctypes.c_void_p(start_instance_idx * matrix_size + i * matrix_size // 4),
2079
- )
2080
- gl.glVertexAttribDivisor(3 + i, 1)
2081
-
2082
- # update colors
2083
- gl.glBindBuffer(gl.GL_ARRAY_BUFFER, self._instance_color1_buffer)
2084
- gl.glVertexAttribPointer(
2085
- 7, 3, gl.GL_FLOAT, gl.GL_FALSE, color_size, ctypes.c_void_p(start_instance_idx * color_size)
2086
- )
2087
- gl.glVertexAttribDivisor(7, 1)
2088
-
2089
- gl.glBindBuffer(gl.GL_ARRAY_BUFFER, self._instance_color2_buffer)
2090
- gl.glVertexAttribPointer(
2091
- 8, 3, gl.GL_FLOAT, gl.GL_FALSE, color_size, ctypes.c_void_p(start_instance_idx * color_size)
2092
- )
2093
- gl.glVertexAttribDivisor(8, 1)
2094
-
2095
- gl.glDrawElementsInstanced(gl.GL_TRIANGLES, tri_count, gl.GL_UNSIGNED_INT, None, num_instances)
2096
- else:
2097
- gl.glDrawElementsInstancedBaseInstance(
2098
- gl.GL_TRIANGLES, tri_count, gl.GL_UNSIGNED_INT, None, num_instances, start_instance_idx
2099
- )
2100
-
2101
- start_instance_idx += num_instances
2102
-
2103
- if self.draw_axis:
2104
- self._axis_instancer.render()
2105
-
2106
- for instancer in self._shape_instancers.values():
2107
- instancer.render()
2108
-
2109
- gl.glBindVertexArray(0)
2110
-
2111
- def _render_scene_tiled(self):
2112
- gl = OpenGLRenderer.gl
2113
-
2114
- self._switch_context()
2115
-
2116
- for i, viewport in enumerate(self._tile_viewports):
2117
- projection_matrix_ptr = arr_pointer(self._tile_projection_matrices[i])
2118
- view_matrix_ptr = arr_pointer(
2119
- self._tile_view_matrices[i] if self._tile_view_matrices[i] is not None else self._view_matrix
2120
- )
2121
-
2122
- gl.glViewport(*viewport)
2123
- if self.draw_grid:
2124
- gl.glUseProgram(self._grid_shader.id)
2125
- gl.glUniformMatrix4fv(self._loc_grid_projection, 1, gl.GL_FALSE, projection_matrix_ptr)
2126
- gl.glUniformMatrix4fv(self._loc_grid_view, 1, gl.GL_FALSE, view_matrix_ptr)
2127
- self._draw_grid(is_tiled=True)
2128
-
2129
- if self.draw_sky:
2130
- gl.glUseProgram(self._sky_shader.id)
2131
- gl.glUniformMatrix4fv(self._loc_sky_projection, 1, gl.GL_FALSE, projection_matrix_ptr)
2132
- gl.glUniformMatrix4fv(self._loc_sky_view, 1, gl.GL_FALSE, view_matrix_ptr)
2133
- self._draw_sky(is_tiled=True)
2134
-
2135
- gl.glUseProgram(self._shape_shader.id)
2136
- gl.glUniformMatrix4fv(self._loc_shape_projection, 1, gl.GL_FALSE, projection_matrix_ptr)
2137
- gl.glUniformMatrix4fv(self._loc_shape_view, 1, gl.GL_FALSE, view_matrix_ptr)
2138
-
2139
- instances = self._tile_instances[i]
2140
-
2141
- for instance in instances:
2142
- shape = self._instance_shape[instance]
2143
-
2144
- vao, _, _, tri_count, _ = self._shape_gl_buffers[shape]
2145
-
2146
- start_instance_idx = self._inverse_instance_ids[instance]
2147
-
2148
- gl.glBindVertexArray(vao)
2149
- if self.use_legacy_opengl:
2150
- if self.instance_matrix_size > 0 and self.instance_color_size:
2151
- # update attribute pointers
2152
- matrix_size = self.instance_matrix_size
2153
- color_size = self.instance_color_size
2154
-
2155
- # update transforms
2156
- gl.glBindBuffer(gl.GL_ARRAY_BUFFER, self._instance_transform_gl_buffer)
2157
- for j in range(4):
2158
- gl.glVertexAttribPointer(
2159
- 3 + j,
2160
- 4,
2161
- gl.GL_FLOAT,
2162
- gl.GL_FALSE,
2163
- matrix_size,
2164
- ctypes.c_void_p(start_instance_idx * matrix_size + j * matrix_size // 4),
2165
- )
2166
- gl.glVertexAttribDivisor(3 + j, 1)
2167
-
2168
- # update colors
2169
- gl.glBindBuffer(gl.GL_ARRAY_BUFFER, self._instance_color1_buffer)
2170
- gl.glVertexAttribPointer(
2171
- 7, 3, gl.GL_FLOAT, gl.GL_FALSE, color_size, ctypes.c_void_p(start_instance_idx * color_size)
2172
- )
2173
- gl.glVertexAttribDivisor(7, 1)
2174
-
2175
- gl.glBindBuffer(gl.GL_ARRAY_BUFFER, self._instance_color2_buffer)
2176
- gl.glVertexAttribPointer(
2177
- 8, 3, gl.GL_FLOAT, gl.GL_FALSE, color_size, ctypes.c_void_p(start_instance_idx * color_size)
2178
- )
2179
- gl.glVertexAttribDivisor(8, 1)
2180
-
2181
- gl.glDrawElementsInstanced(gl.GL_TRIANGLES, tri_count, gl.GL_UNSIGNED_INT, None, 1)
2182
- else:
2183
- gl.glDrawElementsInstancedBaseInstance(
2184
- gl.GL_TRIANGLES, tri_count, gl.GL_UNSIGNED_INT, None, 1, start_instance_idx
2185
- )
2186
-
2187
- if self.draw_axis:
2188
- self._axis_instancer.render()
2189
-
2190
- for instancer in self._shape_instancers.values():
2191
- instancer.render()
2192
-
2193
- gl.glBindVertexArray(0)
2194
-
2195
- def _close_callback(self):
2196
- self.close()
2197
-
2198
- def _mouse_drag_callback(self, x, y, dx, dy, buttons, modifiers):
2199
- if not self.enable_mouse_interaction:
2200
- return
2201
-
2202
- import pyglet
2203
- from pyglet.math import Vec3 as PyVec3
2204
-
2205
- if buttons & pyglet.window.mouse.LEFT:
2206
- sensitivity = 0.1
2207
- dx *= sensitivity
2208
- dy *= sensitivity
2209
-
2210
- self._yaw += dx
2211
- self._pitch += dy
2212
-
2213
- self._pitch = max(min(self._pitch, 89.0), -89.0)
2214
-
2215
- self._camera_front = PyVec3(
2216
- np.cos(np.deg2rad(self._yaw)) * np.cos(np.deg2rad(self._pitch)),
2217
- np.sin(np.deg2rad(self._pitch)),
2218
- np.sin(np.deg2rad(self._yaw)) * np.cos(np.deg2rad(self._pitch)),
2219
- ).normalize()
2220
-
2221
- self.update_view_matrix()
2222
-
2223
- def _scroll_callback(self, x, y, scroll_x, scroll_y):
2224
- if not self.enable_mouse_interaction:
2225
- return
2226
-
2227
- self.camera_fov -= scroll_y
2228
- self.camera_fov = max(min(self.camera_fov, 90.0), 15.0)
2229
- self.update_projection_matrix()
2230
-
2231
- def _process_inputs(self):
2232
- import pyglet
2233
- from pyglet.math import Vec3 as PyVec3
2234
-
2235
- for cb in self._input_processors:
2236
- if cb(self._key_handler) == pyglet.event.EVENT_HANDLED:
2237
- return
2238
-
2239
- if self._key_handler[pyglet.window.key.W] or self._key_handler[pyglet.window.key.UP]:
2240
- self._camera_pos += self._camera_front * (self._camera_speed * self._frame_speed)
2241
- self.update_view_matrix()
2242
- if self._key_handler[pyglet.window.key.S] or self._key_handler[pyglet.window.key.DOWN]:
2243
- self._camera_pos -= self._camera_front * (self._camera_speed * self._frame_speed)
2244
- self.update_view_matrix()
2245
- if self._key_handler[pyglet.window.key.A] or self._key_handler[pyglet.window.key.LEFT]:
2246
- camera_side = PyVec3.cross(self._camera_front, self._camera_up).normalize()
2247
- self._camera_pos -= camera_side * (self._camera_speed * self._frame_speed)
2248
- self.update_view_matrix()
2249
- if self._key_handler[pyglet.window.key.D] or self._key_handler[pyglet.window.key.RIGHT]:
2250
- camera_side = PyVec3.cross(self._camera_front, self._camera_up).normalize()
2251
- self._camera_pos += camera_side * (self._camera_speed * self._frame_speed)
2252
- self.update_view_matrix()
2253
-
2254
- def register_input_processor(self, callback):
2255
- self._input_processors.append(callback)
2256
-
2257
- def _key_press_callback(self, symbol, modifiers):
2258
- import pyglet
2259
-
2260
- if not self.enable_keyboard_interaction:
2261
- return
2262
-
2263
- for cb in self._key_callbacks:
2264
- if cb(symbol, modifiers) == pyglet.event.EVENT_HANDLED:
2265
- return pyglet.event.EVENT_HANDLED
2266
-
2267
- if symbol == pyglet.window.key.ESCAPE:
2268
- self.close()
2269
- if symbol == pyglet.window.key.SPACE:
2270
- self.paused = not self.paused
2271
- if symbol == pyglet.window.key.TAB:
2272
- self.skip_rendering = not self.skip_rendering
2273
- if symbol == pyglet.window.key.C:
2274
- self.draw_axis = not self.draw_axis
2275
- if symbol == pyglet.window.key.G:
2276
- self.draw_grid = not self.draw_grid
2277
- if symbol == pyglet.window.key.I:
2278
- self.show_info = not self.show_info
2279
- if symbol == pyglet.window.key.X:
2280
- self.render_wireframe = not self.render_wireframe
2281
- if symbol == pyglet.window.key.T:
2282
- self.render_depth = not self.render_depth
2283
- if symbol == pyglet.window.key.B:
2284
- self.enable_backface_culling = not self.enable_backface_culling
2285
-
2286
- def register_key_press_callback(self, callback):
2287
- self._key_callbacks.append(callback)
2288
-
2289
- def _window_resize_callback(self, width, height):
2290
- self._first_mouse = True
2291
- self.screen_width, self.screen_height = self.window.get_framebuffer_size()
2292
- self.update_projection_matrix()
2293
- self._setup_framebuffer()
2294
-
2295
- def register_shape(self, geo_hash, vertices, indices, color1=None, color2=None):
2296
- gl = OpenGLRenderer.gl
2297
-
2298
- self._switch_context()
2299
-
2300
- shape = len(self._shapes)
2301
- if color1 is None:
2302
- color1 = tab10_color_map(len(self._shape_geo_hash))
2303
- if color2 is None:
2304
- color2 = np.clip(np.array(color1) + 0.25, 0.0, 1.0)
2305
- # TODO check if we actually need to store the shape data
2306
- self._shapes.append((vertices, indices, color1, color2, geo_hash))
2307
- self._shape_geo_hash[geo_hash] = shape
2308
-
2309
- gl.glUseProgram(self._shape_shader.id)
2310
-
2311
- # Create VAO, VBO, and EBO
2312
- vao = gl.GLuint()
2313
- gl.glGenVertexArrays(1, vao)
2314
- gl.glBindVertexArray(vao)
2315
-
2316
- vbo = gl.GLuint()
2317
- gl.glGenBuffers(1, vbo)
2318
- gl.glBindBuffer(gl.GL_ARRAY_BUFFER, vbo)
2319
- gl.glBufferData(gl.GL_ARRAY_BUFFER, vertices.nbytes, vertices.ctypes.data, gl.GL_STATIC_DRAW)
2320
-
2321
- vertex_cuda_buffer = wp.RegisteredGLBuffer(int(vbo.value), self._device)
2322
-
2323
- ebo = gl.GLuint()
2324
- gl.glGenBuffers(1, ebo)
2325
- gl.glBindBuffer(gl.GL_ELEMENT_ARRAY_BUFFER, ebo)
2326
- gl.glBufferData(gl.GL_ELEMENT_ARRAY_BUFFER, indices.nbytes, indices.ctypes.data, gl.GL_STATIC_DRAW)
2327
-
2328
- # Set up vertex attributes
2329
- vertex_stride = vertices.shape[1] * vertices.itemsize
2330
- # positions
2331
- gl.glVertexAttribPointer(0, 3, gl.GL_FLOAT, gl.GL_FALSE, vertex_stride, ctypes.c_void_p(0))
2332
- gl.glEnableVertexAttribArray(0)
2333
- # normals
2334
- gl.glVertexAttribPointer(1, 3, gl.GL_FLOAT, gl.GL_FALSE, vertex_stride, ctypes.c_void_p(3 * vertices.itemsize))
2335
- gl.glEnableVertexAttribArray(1)
2336
- # uv coordinates
2337
- gl.glVertexAttribPointer(2, 2, gl.GL_FLOAT, gl.GL_FALSE, vertex_stride, ctypes.c_void_p(6 * vertices.itemsize))
2338
- gl.glEnableVertexAttribArray(2)
2339
-
2340
- gl.glBindVertexArray(0)
2341
-
2342
- self._shape_gl_buffers[shape] = (vao, vbo, ebo, len(indices), vertex_cuda_buffer)
2343
-
2344
- return shape
2345
-
2346
- def deregister_shape(self, shape):
2347
- gl = OpenGLRenderer.gl
2348
-
2349
- self._switch_context()
2350
-
2351
- if shape not in self._shape_gl_buffers:
2352
- return
2353
-
2354
- vao, vbo, ebo, _, vertex_cuda_buffer = self._shape_gl_buffers[shape]
2355
- try:
2356
- gl.glDeleteVertexArrays(1, vao)
2357
- gl.glDeleteBuffers(1, vbo)
2358
- gl.glDeleteBuffers(1, ebo)
2359
- except gl.GLException:
2360
- pass
2361
-
2362
- _, _, _, _, geo_hash = self._shapes[shape]
2363
- del self._shape_geo_hash[geo_hash]
2364
- del self._shape_gl_buffers[shape]
2365
- self._shapes.pop(shape)
2366
-
2367
- def add_shape_instance(
2368
- self,
2369
- name: str,
2370
- shape: int,
2371
- body,
2372
- pos: tuple,
2373
- rot: tuple,
2374
- scale: tuple = (1.0, 1.0, 1.0),
2375
- color1=None,
2376
- color2=None,
2377
- custom_index: int = -1,
2378
- visible: bool = True,
2379
- ):
2380
- if color1 is None:
2381
- color1 = self._shapes[shape][2]
2382
- if color2 is None:
2383
- color2 = self._shapes[shape][3]
2384
- instance = len(self._instances)
2385
- self._shape_instances[shape].append(instance)
2386
- body = self._resolve_body_id(body)
2387
- self._instances[name] = (instance, body, shape, [*pos, *rot], scale, color1, color2, visible)
2388
- self._instance_shape[instance] = shape
2389
- self._instance_custom_ids[instance] = custom_index
2390
- self._add_shape_instances = True
2391
- self._instance_count = len(self._instances)
2392
- return instance
2393
-
2394
- def remove_shape_instance(self, name: str):
2395
- if name not in self._instances:
2396
- return
2397
-
2398
- instance, _, shape, _, _, _, _, _ = self._instances[name]
2399
-
2400
- self._shape_instances[shape].remove(instance)
2401
- self._instance_count = len(self._instances)
2402
- self._add_shape_instances = self._instance_count > 0
2403
- del self._instance_shape[instance]
2404
- del self._instance_custom_ids[instance]
2405
- del self._instances[name]
2406
-
2407
- def update_instance_colors(self):
2408
- gl = OpenGLRenderer.gl
2409
-
2410
- self._switch_context()
2411
-
2412
- colors1, colors2 = [], []
2413
- all_instances = list(self._instances.values())
2414
- for _shape, instances in self._shape_instances.items():
2415
- for i in instances:
2416
- if i >= len(all_instances):
2417
- continue
2418
- instance = all_instances[i]
2419
- colors1.append(instance[5])
2420
- colors2.append(instance[6])
2421
- colors1 = np.array(colors1, dtype=np.float32)
2422
- colors2 = np.array(colors2, dtype=np.float32)
2423
-
2424
- # create color buffers
2425
- if self._instance_color1_buffer is None:
2426
- self._instance_color1_buffer = gl.GLuint()
2427
- gl.glGenBuffers(1, self._instance_color1_buffer)
2428
- if self._instance_color2_buffer is None:
2429
- self._instance_color2_buffer = gl.GLuint()
2430
- gl.glGenBuffers(1, self._instance_color2_buffer)
2431
-
2432
- gl.glBindBuffer(gl.GL_ARRAY_BUFFER, self._instance_color1_buffer)
2433
- gl.glBufferData(gl.GL_ARRAY_BUFFER, colors1.nbytes, colors1.ctypes.data, gl.GL_STATIC_DRAW)
2434
-
2435
- gl.glBindBuffer(gl.GL_ARRAY_BUFFER, self._instance_color2_buffer)
2436
- gl.glBufferData(gl.GL_ARRAY_BUFFER, colors2.nbytes, colors2.ctypes.data, gl.GL_STATIC_DRAW)
2437
-
2438
- def allocate_shape_instances(self):
2439
- gl = OpenGLRenderer.gl
2440
-
2441
- self._switch_context()
2442
-
2443
- self._add_shape_instances = False
2444
- self._wp_instance_transforms = wp.array(
2445
- [instance[3] for instance in self._instances.values()], dtype=wp.transform, device=self._device
2446
- )
2447
- self._wp_instance_scalings = wp.array(
2448
- [instance[4] for instance in self._instances.values()], dtype=wp.vec3, device=self._device
2449
- )
2450
- self._wp_instance_bodies = wp.array(
2451
- [instance[1] for instance in self._instances.values()], dtype=wp.int32, device=self._device
2452
- )
2453
-
2454
- gl.glUseProgram(self._shape_shader.id)
2455
- if self._instance_transform_gl_buffer is None:
2456
- # create instance buffer and bind it as an instanced array
2457
- self._instance_transform_gl_buffer = gl.GLuint()
2458
- gl.glGenBuffers(1, self._instance_transform_gl_buffer)
2459
- gl.glBindBuffer(gl.GL_ARRAY_BUFFER, self._instance_transform_gl_buffer)
2460
-
2461
- transforms = np.tile(np.diag(np.ones(4, dtype=np.float32)), (len(self._instances), 1, 1))
2462
- gl.glBufferData(gl.GL_ARRAY_BUFFER, transforms.nbytes, transforms.ctypes.data, gl.GL_DYNAMIC_DRAW)
2463
-
2464
- # create CUDA buffer for instance transforms
2465
- self._instance_transform_cuda_buffer = wp.RegisteredGLBuffer(
2466
- int(self._instance_transform_gl_buffer.value), self._device
2467
- )
2468
-
2469
- self.update_instance_colors()
2470
-
2471
- # set up instance attribute pointers
2472
- matrix_size = transforms[0].nbytes
2473
- self.instance_matrix_size = matrix_size
2474
-
2475
- instance_ids = []
2476
- instance_custom_ids = []
2477
- instance_visible = []
2478
- instances = list(self._instances.values())
2479
- inverse_instance_ids = {}
2480
- instance_count = 0
2481
- colors_size = np.zeros(3, dtype=np.float32).nbytes
2482
- self.instance_color_size = colors_size
2483
- for shape, (vao, _vbo, _ebo, _tri_count, _vertex_cuda_buffer) in self._shape_gl_buffers.items():
2484
- gl.glBindVertexArray(vao)
2485
-
2486
- gl.glBindBuffer(gl.GL_ARRAY_BUFFER, self._instance_transform_gl_buffer)
2487
-
2488
- # we can only send vec4s to the shader, so we need to split the instance transforms matrix into its column vectors
2489
- for i in range(4):
2490
- gl.glVertexAttribPointer(
2491
- 3 + i, 4, gl.GL_FLOAT, gl.GL_FALSE, matrix_size, ctypes.c_void_p(i * matrix_size // 4)
2492
- )
2493
- gl.glEnableVertexAttribArray(3 + i)
2494
- gl.glVertexAttribDivisor(3 + i, 1)
2495
-
2496
- gl.glBindBuffer(gl.GL_ARRAY_BUFFER, self._instance_color1_buffer)
2497
- gl.glVertexAttribPointer(7, 3, gl.GL_FLOAT, gl.GL_FALSE, colors_size, ctypes.c_void_p(0))
2498
- gl.glEnableVertexAttribArray(7)
2499
- gl.glVertexAttribDivisor(7, 1)
2500
-
2501
- gl.glBindBuffer(gl.GL_ARRAY_BUFFER, self._instance_color2_buffer)
2502
- gl.glVertexAttribPointer(8, 3, gl.GL_FLOAT, gl.GL_FALSE, colors_size, ctypes.c_void_p(0))
2503
- gl.glEnableVertexAttribArray(8)
2504
- gl.glVertexAttribDivisor(8, 1)
2505
-
2506
- instance_ids.extend(self._shape_instances[shape])
2507
- for i in self._shape_instances[shape]:
2508
- inverse_instance_ids[i] = instance_count
2509
- instance_count += 1
2510
- instance_custom_ids.append(self._instance_custom_ids[i])
2511
- instance_visible.append(instances[i][7])
2512
-
2513
- # trigger update to the instance transforms
2514
- self._update_shape_instances = True
2515
-
2516
- self._wp_instance_ids = wp.array(instance_ids, dtype=wp.int32, device=self._device)
2517
- self._wp_instance_custom_ids = wp.array(instance_custom_ids, dtype=wp.int32, device=self._device)
2518
- self._np_instance_visible = np.array(instance_visible)
2519
- self._instance_ids = instance_ids
2520
- self._inverse_instance_ids = inverse_instance_ids
2521
-
2522
- gl.glBindVertexArray(0)
2523
-
2524
- def update_shape_instance(self, name, pos=None, rot=None, color1=None, color2=None, scale=None, visible=None):
2525
- """Update the instance properties of the shape
2526
-
2527
- Args:
2528
- name: The name of the shape
2529
- pos: The position of the shape
2530
- rot: The rotation of the shape
2531
- color1: The first color of the checker pattern
2532
- color2: The second color of the checker pattern
2533
- visible: Whether the shape is visible
2534
- """
2535
- gl = OpenGLRenderer.gl
2536
-
2537
- self._switch_context()
2538
-
2539
- if name in self._instances:
2540
- i, body, shape, tf, old_scale, old_color1, old_color2, v = self._instances[name]
2541
- if visible is None:
2542
- visible = v
2543
- new_tf = np.copy(tf)
2544
- if pos is not None:
2545
- new_tf[:3] = pos
2546
- if rot is not None:
2547
- new_tf[3:] = rot
2548
- self._instances[name] = (
2549
- i,
2550
- body,
2551
- shape,
2552
- new_tf,
2553
- old_scale if scale is None else scale,
2554
- old_color1 if color1 is None else color1,
2555
- old_color2 if color2 is None else color2,
2556
- visible,
2557
- )
2558
- self._update_shape_instances = True
2559
- if color1 is not None or color2 is not None:
2560
- vao, vbo, ebo, tri_count, vertex_cuda_buffer = self._shape_gl_buffers[shape]
2561
- gl.glBindVertexArray(vao)
2562
- self.update_instance_colors()
2563
- gl.glBindVertexArray(0)
2564
- return True
2565
- return False
2566
-
2567
- def update_shape_instances(self):
2568
- with self._shape_shader:
2569
- self._update_shape_instances = False
2570
- self._wp_instance_transforms = wp.array(
2571
- [instance[3] for instance in self._instances.values()], dtype=wp.transform, device=self._device
2572
- )
2573
- self.update_body_transforms(None)
2574
-
2575
- def update_body_transforms(self, body_tf: wp.array):
2576
- if self._instance_transform_cuda_buffer is None:
2577
- return
2578
-
2579
- body_q = None
2580
- if body_tf is not None:
2581
- if body_tf.device.is_cuda:
2582
- body_q = body_tf
2583
- else:
2584
- body_q = body_tf.to(self._device)
2585
-
2586
- vbo_transforms = self._instance_transform_cuda_buffer.map(dtype=wp.mat44, shape=(self._instance_count,))
2587
-
2588
- wp.launch(
2589
- update_vbo_transforms,
2590
- dim=self._instance_count,
2591
- inputs=[
2592
- self._wp_instance_ids,
2593
- self._wp_instance_bodies,
2594
- self._wp_instance_transforms,
2595
- self._wp_instance_scalings,
2596
- body_q,
2597
- ],
2598
- outputs=[
2599
- vbo_transforms,
2600
- ],
2601
- device=self._device,
2602
- record_tape=False,
2603
- )
2604
-
2605
- self._instance_transform_cuda_buffer.unmap()
2606
-
2607
- def register_body(self, name):
2608
- # register body name and return its ID
2609
- if name not in self._body_name:
2610
- self._body_name[name] = len(self._body_name)
2611
- return self._body_name[name]
2612
-
2613
- def _resolve_body_id(self, body):
2614
- if body is None:
2615
- return -1
2616
- if isinstance(body, int):
2617
- return body
2618
- return self._body_name[body]
2619
-
2620
- def is_running(self):
2621
- return not self.app.event_loop.has_exit
2622
-
2623
- def save(self):
2624
- # save just keeps the window open to allow the user to interact with the scene
2625
- while not self.app.event_loop.has_exit:
2626
- self.update()
2627
- if self.app.event_loop.has_exit:
2628
- self.clear()
2629
- self.app.event_loop.exit()
2630
-
2631
- def get_pixels(self, target_image: wp.array, split_up_tiles=True, mode="rgb", use_uint8=False):
2632
- """
2633
- Read the pixels from the frame buffer (RGB or depth are supported) into the given array.
2634
-
2635
- If `split_up_tiles` is False, array must be of shape (screen_height, screen_width, 3) for RGB mode or
2636
- (screen_height, screen_width, 1) for depth mode.
2637
- If `split_up_tiles` is True, the pixels will be split up into tiles (see :attr:`tile_width` and :attr:`tile_height` for dimensions):
2638
- array must be of shape (num_tiles, tile_height, tile_width, 3) for RGB mode or (num_tiles, tile_height, tile_width, 1) for depth mode.
2639
-
2640
- Args:
2641
- target_image (array): The array to read the pixels into. Must have float32 as dtype and be on a CUDA device.
2642
- split_up_tiles (bool): Whether to split up the viewport into tiles, see :meth:`setup_tiled_rendering`.
2643
- mode (str): can be either "rgb" or "depth"
2644
- use_uint8 (bool): Whether to use uint8 as dtype in RGB mode for the target_image array and return values in the range [0, 255]. Otherwise, float32 is assumed as dtype with values in the range [0, 1].
2645
-
2646
- Returns:
2647
- bool: Whether the pixels were successfully read.
2648
- """
2649
- gl = OpenGLRenderer.gl
2650
-
2651
- self._switch_context()
2652
-
2653
- channels = 3 if mode == "rgb" else 1
2654
-
2655
- if split_up_tiles:
2656
- assert self._tile_width is not None and self._tile_height is not None, (
2657
- "Tile width and height are not set, tiles must all have the same size"
2658
- )
2659
- assert all(vp[2] == self._tile_width for vp in self._tile_viewports), (
2660
- "Tile widths do not all equal global tile_width, use `get_tile_pixels` instead to retrieve pixels for a single tile"
2661
- )
2662
- assert all(vp[3] == self._tile_height for vp in self._tile_viewports), (
2663
- "Tile heights do not all equal global tile_height, use `get_tile_pixels` instead to retrieve pixels for a single tile"
2664
- )
2665
- assert target_image.shape == (
2666
- self.num_tiles,
2667
- self._tile_height,
2668
- self._tile_width,
2669
- channels,
2670
- ), (
2671
- f"Shape of `target_image` array does not match {self.num_tiles} x {self._tile_height} x {self._tile_width} x {channels}"
2672
- )
2673
- else:
2674
- assert target_image.shape == (
2675
- self.screen_height,
2676
- self.screen_width,
2677
- channels,
2678
- ), f"Shape of `target_image` array does not match {self.screen_height} x {self.screen_width} x {channels}"
2679
-
2680
- gl.glBindBuffer(gl.GL_PIXEL_PACK_BUFFER, self._frame_pbo)
2681
- if mode == "rgb":
2682
- gl.glBindTexture(gl.GL_TEXTURE_2D, self._frame_texture)
2683
- if mode == "depth":
2684
- gl.glBindTexture(gl.GL_TEXTURE_2D, self._frame_depth_texture)
2685
- try:
2686
- # read screen texture into PBO
2687
- if mode == "rgb":
2688
- gl.glGetTexImage(gl.GL_TEXTURE_2D, 0, gl.GL_RGB, gl.GL_UNSIGNED_BYTE, ctypes.c_void_p(0))
2689
- elif mode == "depth":
2690
- gl.glGetTexImage(gl.GL_TEXTURE_2D, 0, gl.GL_DEPTH_COMPONENT, gl.GL_FLOAT, ctypes.c_void_p(0))
2691
- except gl.GLException:
2692
- # this can happen if the window is closed/being moved to a different display
2693
- gl.glBindTexture(gl.GL_TEXTURE_2D, 0)
2694
- gl.glBindBuffer(gl.GL_PIXEL_PACK_BUFFER, 0)
2695
- return False
2696
- gl.glBindTexture(gl.GL_TEXTURE_2D, 0)
2697
- gl.glBindBuffer(gl.GL_PIXEL_PACK_BUFFER, 0)
2698
-
2699
- pbo_buffer = wp.RegisteredGLBuffer(int(self._frame_pbo.value), self._device, wp.RegisteredGLBuffer.READ_ONLY)
2700
- screen_size = self.screen_height * self.screen_width
2701
- if mode == "rgb":
2702
- img = pbo_buffer.map(dtype=wp.uint8, shape=(screen_size * channels))
2703
- elif mode == "depth":
2704
- img = pbo_buffer.map(dtype=wp.float32, shape=(screen_size * channels))
2705
- img = img.to(target_image.device)
2706
- if split_up_tiles:
2707
- positions = wp.array(self._tile_viewports, ndim=2, dtype=wp.int32, device=target_image.device)
2708
- if mode == "rgb":
2709
- wp.launch(
2710
- copy_rgb_frame_tiles_uint8 if use_uint8 else copy_rgb_frame_tiles,
2711
- dim=(self.num_tiles, self._tile_width, self._tile_height),
2712
- inputs=[img, positions, self.screen_width, self.screen_height, self._tile_height],
2713
- outputs=[target_image],
2714
- device=target_image.device,
2715
- )
2716
- elif mode == "depth":
2717
- wp.launch(
2718
- copy_depth_frame_tiles,
2719
- dim=(self.num_tiles, self._tile_width, self._tile_height),
2720
- inputs=[
2721
- img,
2722
- positions,
2723
- self.screen_width,
2724
- self.screen_height,
2725
- self._tile_height,
2726
- self.camera_near_plane,
2727
- self.camera_far_plane,
2728
- ],
2729
- outputs=[target_image],
2730
- device=target_image.device,
2731
- )
2732
- else:
2733
- if mode == "rgb":
2734
- wp.launch(
2735
- copy_rgb_frame_uint8 if use_uint8 else copy_rgb_frame,
2736
- dim=(self.screen_width, self.screen_height),
2737
- inputs=[img, self.screen_width, self.screen_height],
2738
- outputs=[target_image],
2739
- device=target_image.device,
2740
- )
2741
- elif mode == "depth":
2742
- wp.launch(
2743
- copy_depth_frame,
2744
- dim=(self.screen_width, self.screen_height),
2745
- inputs=[img, self.screen_width, self.screen_height, self.camera_near_plane, self.camera_far_plane],
2746
- outputs=[target_image],
2747
- device=target_image.device,
2748
- )
2749
- pbo_buffer.unmap()
2750
- return True
2751
-
2752
- # def create_image_texture(self, file_path):
2753
- # from PIL import Image
2754
- # img = Image.open(file_path)
2755
- # img_data = np.array(list(img.getdata()), np.uint8)
2756
- # texture = glGenTextures(1)
2757
- # glBindTexture(GL_TEXTURE_2D, texture)
2758
- # glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, img.width, img.height, 0, GL_RGB, GL_UNSIGNED_BYTE, img_data)
2759
- # glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT)
2760
- # glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT)
2761
- # glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
2762
- # glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
2763
- # return texture
2764
-
2765
- # def create_check_texture(self, color1=(0, 0.5, 1.0), color2=None, width=default_texture_size, height=default_texture_size):
2766
- # if width == 1 and height == 1:
2767
- # pixels = np.array([np.array(color1)*255], dtype=np.uint8)
2768
- # else:
2769
- # pixels = np.zeros((width, height, 3), dtype=np.uint8)
2770
- # half_w = width // 2
2771
- # half_h = height // 2
2772
- # color1 = np.array(np.array(color1)*255, dtype=np.uint8)
2773
- # pixels[0:half_w, 0:half_h] = color1
2774
- # pixels[half_w:width, half_h:height] = color1
2775
- # if color2 is None:
2776
- # color2 = np.array(np.clip(np.array(color1, dtype=np.float32) + 50, 0, 255), dtype=np.uint8)
2777
- # else:
2778
- # color2 = np.array(np.array(color2)*255, dtype=np.uint8)
2779
- # pixels[half_w:width, 0:half_h] = color2
2780
- # pixels[0:half_w, half_h:height] = color2
2781
- # texture = glGenTextures(1)
2782
- # glBindTexture(GL_TEXTURE_2D, texture)
2783
- # glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, pixels.flatten())
2784
- # glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT)
2785
- # glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT)
2786
- # glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
2787
- # glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
2788
- # return texture
2789
-
2790
- def render_plane(
2791
- self,
2792
- name: str,
2793
- pos: tuple,
2794
- rot: tuple,
2795
- width: float,
2796
- length: float,
2797
- color: tuple = (1.0, 1.0, 1.0),
2798
- color2=None,
2799
- parent_body: str | None = None,
2800
- is_template: bool = False,
2801
- u_scaling=1.0,
2802
- v_scaling=1.0,
2803
- visible: bool = True,
2804
- ):
2805
- """Add a plane for visualization
2806
-
2807
- Args:
2808
- name: The name of the plane
2809
- pos: The position of the plane
2810
- rot: The rotation of the plane
2811
- width: The width of the plane
2812
- length: The length of the plane
2813
- color: The color of the plane
2814
- texture: The texture of the plane (optional)
2815
- """
2816
- geo_hash = hash(("plane", width, length))
2817
- if geo_hash in self._shape_geo_hash:
2818
- shape = self._shape_geo_hash[geo_hash]
2819
- if self.update_shape_instance(name, pos, rot):
2820
- return shape
2821
- else:
2822
- faces = np.array([0, 1, 2, 2, 3, 0], dtype=np.uint32)
2823
- normal = (0.0, 1.0, 0.0)
2824
- width = width if width > 0.0 else 100.0
2825
- length = length if length > 0.0 else 100.0
2826
- aspect = width / length
2827
- u = width * aspect * u_scaling
2828
- v = length * v_scaling
2829
- gfx_vertices = np.array(
2830
- [
2831
- [-width, 0.0, -length, *normal, 0.0, 0.0],
2832
- [-width, 0.0, length, *normal, 0.0, v],
2833
- [width, 0.0, length, *normal, u, v],
2834
- [width, 0.0, -length, *normal, u, 0.0],
2835
- ],
2836
- dtype=np.float32,
2837
- )
2838
- shape = self.register_shape(geo_hash, gfx_vertices, faces, color1=color, color2=color2)
2839
- if not is_template:
2840
- body = self._resolve_body_id(parent_body)
2841
- self.add_shape_instance(name, shape, body, pos, rot)
2842
- return shape
2843
-
2844
- def render_ground(self, size: float = 1000.0, plane=None):
2845
- """Add a ground plane for visualization
2846
-
2847
- Args:
2848
- size: The size of the ground plane
2849
- """
2850
- color1 = (200 / 255, 200 / 255, 200 / 255)
2851
- color2 = (150 / 255, 150 / 255, 150 / 255)
2852
- sqh = np.sqrt(0.5)
2853
- if self._camera_axis == 0:
2854
- q = (0.0, 0.0, -sqh, sqh)
2855
- elif self._camera_axis == 1:
2856
- q = (0.0, 0.0, 0.0, 1.0)
2857
- elif self._camera_axis == 2:
2858
- q = (sqh, 0.0, 0.0, sqh)
2859
- pos = (0.0, 0.0, 0.0)
2860
- if plane is not None:
2861
- normal = np.array(plane[:3])
2862
- normal /= np.linalg.norm(normal)
2863
- pos = plane[3] * normal
2864
- if np.allclose(normal, (0.0, 1.0, 0.0)):
2865
- # no rotation necessary
2866
- q = (0.0, 0.0, 0.0, 1.0)
2867
- else:
2868
- c = np.cross(normal, (0.0, 1.0, 0.0))
2869
- angle = wp.float32(np.arcsin(np.linalg.norm(c)))
2870
- axis = wp.vec3(np.abs(c))
2871
- axis = wp.normalize(axis)
2872
- q = wp.quat_from_axis_angle(axis, angle)
2873
- return self.render_plane(
2874
- "ground",
2875
- pos,
2876
- q,
2877
- size,
2878
- size,
2879
- color1,
2880
- color2=color2,
2881
- u_scaling=1.0,
2882
- v_scaling=1.0,
2883
- )
2884
-
2885
- def render_sphere(
2886
- self,
2887
- name: str,
2888
- pos: tuple,
2889
- rot: tuple,
2890
- radius: float,
2891
- parent_body: str | None = None,
2892
- is_template: bool = False,
2893
- color: tuple[float, float, float] | None = None,
2894
- visible: bool = True,
2895
- ):
2896
- """Add a sphere for visualization
2897
-
2898
- Args:
2899
- pos: The position of the sphere
2900
- radius: The radius of the sphere
2901
- name: A name for the USD prim on the stage
2902
- color: The color of the sphere
2903
- """
2904
- geo_hash = hash(("sphere", radius))
2905
- if geo_hash in self._shape_geo_hash:
2906
- shape = self._shape_geo_hash[geo_hash]
2907
- if self.update_shape_instance(name, pos, rot, color1=color, color2=color):
2908
- return shape
2909
- else:
2910
- vertices, indices = self._create_sphere_mesh(radius)
2911
- shape = self.register_shape(geo_hash, vertices, indices, color1=color, color2=color)
2912
- if not is_template:
2913
- body = self._resolve_body_id(parent_body)
2914
- self.add_shape_instance(name, shape, body, pos, rot, color1=color, color2=color)
2915
- return shape
2916
-
2917
- def render_capsule(
2918
- self,
2919
- name: str,
2920
- pos: tuple,
2921
- rot: tuple,
2922
- radius: float,
2923
- half_height: float,
2924
- parent_body: str | None = None,
2925
- is_template: bool = False,
2926
- up_axis: int = 1,
2927
- color: tuple[float, float, float] | None = None,
2928
- visible: bool = True,
2929
- ):
2930
- """Add a capsule for visualization
2931
-
2932
- Args:
2933
- pos: The position of the capsule
2934
- radius: The radius of the capsule
2935
- half_height: The half height of the capsule
2936
- name: A name for the USD prim on the stage
2937
- up_axis: The axis of the capsule that points up (0: x, 1: y, 2: z)
2938
- color: The color of the capsule
2939
- """
2940
- geo_hash = hash(("capsule", radius, half_height, up_axis))
2941
- if geo_hash in self._shape_geo_hash:
2942
- shape = self._shape_geo_hash[geo_hash]
2943
- if self.update_shape_instance(name, pos, rot):
2944
- return shape
2945
- else:
2946
- vertices, indices = self._create_capsule_mesh(radius, half_height, up_axis=up_axis)
2947
- shape = self.register_shape(geo_hash, vertices, indices, color1=color, color2=color)
2948
- if not is_template:
2949
- body = self._resolve_body_id(parent_body)
2950
- self.add_shape_instance(name, shape, body, pos, rot)
2951
- return shape
2952
-
2953
- def render_cylinder(
2954
- self,
2955
- name: str,
2956
- pos: tuple,
2957
- rot: tuple,
2958
- radius: float,
2959
- half_height: float,
2960
- parent_body: str | None = None,
2961
- is_template: bool = False,
2962
- up_axis: int = 1,
2963
- color: tuple[float, float, float] | None = None,
2964
- visible: bool = True,
2965
- ):
2966
- """Add a cylinder for visualization
2967
-
2968
- Args:
2969
- pos: The position of the cylinder
2970
- radius: The radius of the cylinder
2971
- half_height: The half height of the cylinder
2972
- name: A name for the USD prim on the stage
2973
- up_axis: The axis of the cylinder that points up (0: x, 1: y, 2: z)
2974
- color: The color of the capsule
2975
- """
2976
- geo_hash = hash(("cylinder", radius, half_height, up_axis))
2977
- if geo_hash in self._shape_geo_hash:
2978
- shape = self._shape_geo_hash[geo_hash]
2979
- if self.update_shape_instance(name, pos, rot):
2980
- return shape
2981
- else:
2982
- vertices, indices = self._create_cylinder_mesh(radius, half_height, up_axis=up_axis)
2983
- shape = self.register_shape(geo_hash, vertices, indices, color1=color, color2=color)
2984
- if not is_template:
2985
- body = self._resolve_body_id(parent_body)
2986
- self.add_shape_instance(name, shape, body, pos, rot)
2987
- return shape
2988
-
2989
- def render_cone(
2990
- self,
2991
- name: str,
2992
- pos: tuple,
2993
- rot: tuple,
2994
- radius: float,
2995
- half_height: float,
2996
- parent_body: str | None = None,
2997
- is_template: bool = False,
2998
- up_axis: int = 1,
2999
- color: tuple[float, float, float] | None = None,
3000
- visible: bool = True,
3001
- ):
3002
- """Add a cone for visualization
3003
-
3004
- Args:
3005
- pos: The position of the cone
3006
- radius: The radius of the cone
3007
- half_height: The half height of the cone
3008
- name: A name for the USD prim on the stage
3009
- up_axis: The axis of the cone that points up (0: x, 1: y, 2: z)
3010
- color: The color of the cone
3011
- """
3012
- geo_hash = hash(("cone", radius, half_height, up_axis))
3013
- if geo_hash in self._shape_geo_hash:
3014
- shape = self._shape_geo_hash[geo_hash]
3015
- if self.update_shape_instance(name, pos, rot):
3016
- return shape
3017
- else:
3018
- vertices, indices = self._create_cone_mesh(radius, half_height, up_axis=up_axis)
3019
- shape = self.register_shape(geo_hash, vertices, indices, color1=color, color2=color)
3020
- if not is_template:
3021
- body = self._resolve_body_id(parent_body)
3022
- self.add_shape_instance(name, shape, body, pos, rot)
3023
- return shape
3024
-
3025
- def render_box(
3026
- self,
3027
- name: str,
3028
- pos: tuple,
3029
- rot: tuple,
3030
- extents: tuple,
3031
- parent_body: str | None = None,
3032
- is_template: bool = False,
3033
- color: tuple[float, float, float] | None = None,
3034
- visible: bool = True,
3035
- ):
3036
- """Add a box for visualization
3037
-
3038
- Args:
3039
- pos: The position of the box
3040
- extents: The extents of the box
3041
- name: A name for the USD prim on the stage
3042
- color: The color of the box
3043
- """
3044
- geo_hash = hash(("box", tuple(extents)))
3045
- if geo_hash in self._shape_geo_hash:
3046
- shape = self._shape_geo_hash[geo_hash]
3047
- if self.update_shape_instance(name, pos, rot, color1=color, color2=color):
3048
- return shape
3049
- else:
3050
- vertices, indices = self._create_box_mesh(extents)
3051
- shape = self.register_shape(geo_hash, vertices, indices, color1=color, color2=color)
3052
- if not is_template:
3053
- body = self._resolve_body_id(parent_body)
3054
- self.add_shape_instance(name, shape, body, pos, rot)
3055
- return shape
3056
-
3057
- def render_mesh(
3058
- self,
3059
- name: str,
3060
- points,
3061
- indices,
3062
- colors=None,
3063
- pos=(0.0, 0.0, 0.0),
3064
- rot=(0.0, 0.0, 0.0, 1.0),
3065
- scale=(1.0, 1.0, 1.0),
3066
- update_topology=False,
3067
- parent_body: str | None = None,
3068
- is_template: bool = False,
3069
- smooth_shading: bool = True,
3070
- visible: bool = True,
3071
- ):
3072
- """Add a mesh for visualization
3073
-
3074
- Args:
3075
- points: The points of the mesh
3076
- indices: The indices of the mesh
3077
- colors: The colors of the mesh
3078
- pos: The position of the mesh
3079
- rot: The rotation of the mesh
3080
- scale: The scale of the mesh
3081
- name: A name for the USD prim on the stage
3082
- smooth_shading: Whether to average face normals at each vertex or introduce additional vertices for each face
3083
- """
3084
- if colors is not None:
3085
- colors = np.array(colors, dtype=np.float32)
3086
-
3087
- points = np.array(points, dtype=np.float32)
3088
- point_count = len(points)
3089
-
3090
- indices = np.array(indices, dtype=np.int32).reshape((-1, 3))
3091
- idx_count = len(indices)
3092
-
3093
- geo_hash = hash((points.tobytes(), indices.tobytes()))
3094
-
3095
- if name in self._instances:
3096
- # We've already registered this mesh instance and its associated shape.
3097
- shape = self._instances[name][2]
3098
- else:
3099
- if geo_hash in self._shape_geo_hash:
3100
- # We've only registered the shape, which can happen when `is_template` is `True`.
3101
- shape = self._shape_geo_hash[geo_hash]
3102
- else:
3103
- shape = None
3104
-
3105
- # Check if we already have that shape registered and can perform
3106
- # minimal updates since the topology is not changing, before exiting.
3107
- if not update_topology:
3108
- if name in self._instances:
3109
- # Update the instance's transform.
3110
- self.update_shape_instance(name, pos, rot, color1=colors, color2=colors, scale=scale, visible=visible)
3111
-
3112
- if shape is not None:
3113
- # Update the shape's point positions.
3114
- self.update_shape_vertices(shape, points)
3115
-
3116
- if not is_template and name not in self._instances:
3117
- # Create a new instance.
3118
- body = self._resolve_body_id(parent_body)
3119
- self.add_shape_instance(name, shape, body, pos, rot, color1=colors, scale=scale)
3120
-
3121
- return shape
3122
-
3123
- # No existing shape for the given mesh was found, or its topology may have changed,
3124
- # so we need to define a new one either way.
3125
- with wp.ScopedDevice(self._device):
3126
- if smooth_shading:
3127
- normals = wp.zeros(point_count, dtype=wp.vec3)
3128
- vertices = wp.array(points, dtype=wp.vec3)
3129
- faces_per_vertex = wp.zeros(point_count, dtype=int)
3130
- wp.launch(
3131
- compute_average_normals,
3132
- dim=idx_count,
3133
- inputs=[wp.array(indices, dtype=int), vertices],
3134
- outputs=[normals, faces_per_vertex],
3135
- record_tape=False,
3136
- )
3137
- gfx_vertices = wp.zeros((point_count, 8), dtype=float)
3138
- wp.launch(
3139
- assemble_gfx_vertices,
3140
- dim=point_count,
3141
- inputs=[vertices, normals, faces_per_vertex],
3142
- outputs=[gfx_vertices],
3143
- record_tape=False,
3144
- )
3145
- gfx_vertices = gfx_vertices.numpy()
3146
- gfx_indices = indices.flatten()
3147
- else:
3148
- gfx_vertices = wp.zeros((idx_count * 3, 8), dtype=float)
3149
- wp.launch(
3150
- compute_gfx_vertices,
3151
- dim=idx_count,
3152
- inputs=[wp.array(indices, dtype=int), wp.array(points, dtype=wp.vec3)],
3153
- outputs=[gfx_vertices],
3154
- record_tape=False,
3155
- )
3156
- gfx_vertices = gfx_vertices.numpy()
3157
- gfx_indices = np.arange(idx_count * 3)
3158
-
3159
- # If there was a shape for the given mesh, clean it up.
3160
- if shape is not None:
3161
- self.deregister_shape(shape)
3162
-
3163
- # If there was an instance for the given mesh, clean it up.
3164
- if name in self._instances:
3165
- self.remove_shape_instance(name)
3166
-
3167
- # Register the new shape.
3168
- shape = self.register_shape(geo_hash, gfx_vertices, gfx_indices)
3169
-
3170
- if not is_template:
3171
- # Create a new instance if necessary.
3172
- body = self._resolve_body_id(parent_body)
3173
- self.add_shape_instance(name, shape, body, pos, rot, color1=colors, scale=scale)
3174
-
3175
- return shape
3176
-
3177
- def render_arrow(
3178
- self,
3179
- name: str,
3180
- pos: tuple,
3181
- rot: tuple,
3182
- base_radius: float,
3183
- base_height: float,
3184
- cap_radius: float | None = None,
3185
- cap_height: float | None = None,
3186
- parent_body: str | None = None,
3187
- is_template: bool = False,
3188
- up_axis: int = 1,
3189
- color: tuple[float, float, float] | None = None,
3190
- visible: bool = True,
3191
- ):
3192
- """Add a arrow for visualization
3193
-
3194
- Args:
3195
- pos: The position of the arrow
3196
- base_radius: The radius of the cylindrical base of the arrow
3197
- base_height: The height of the cylindrical base of the arrow
3198
- cap_radius: The radius of the conical cap of the arrow
3199
- cap_height: The height of the conical cap of the arrow
3200
- name: A name for the USD prim on the stage
3201
- up_axis: The axis of the arrow that points up (0: x, 1: y, 2: z)
3202
- """
3203
- geo_hash = hash(("arrow", base_radius, base_height, cap_radius, cap_height, up_axis))
3204
- if geo_hash in self._shape_geo_hash:
3205
- shape = self._shape_geo_hash[geo_hash]
3206
- if self.update_shape_instance(name, pos, rot, color1=color, color2=color):
3207
- return shape
3208
- else:
3209
- vertices, indices = self._create_arrow_mesh(
3210
- base_radius, base_height, cap_radius, cap_height, up_axis=up_axis
3211
- )
3212
- shape = self.register_shape(geo_hash, vertices, indices, color1=color, color2=color)
3213
- if not is_template:
3214
- body = self._resolve_body_id(parent_body)
3215
- self.add_shape_instance(name, shape, body, pos, rot, color1=color, color2=color)
3216
- return shape
3217
-
3218
- def render_ref(
3219
- self,
3220
- name: str,
3221
- path: str,
3222
- pos: tuple,
3223
- rot: tuple,
3224
- scale: tuple,
3225
- color: tuple[float, float, float] | None = None,
3226
- ):
3227
- """Create a reference (instance) with the given name to the given path."""
3228
-
3229
- if path in self._instances:
3230
- _, body, shape, _, original_scale, color1, color2 = self._instances[path]
3231
- if color is not None:
3232
- color1 = color2 = color
3233
- self.add_shape_instance(name, shape, body, pos, rot, scale or original_scale, color1, color2)
3234
- return
3235
-
3236
- raise Exception("Cannot create reference to path: " + path)
3237
-
3238
- def render_points(self, name: str, points, radius, colors=None, as_spheres: bool = True, visible: bool = True):
3239
- """Add a set of points
3240
-
3241
- Args:
3242
- points: The points to render
3243
- radius: The radius of the points (scalar or list)
3244
- colors: The colors of the points
3245
- name: A name for the USD prim on the stage
3246
- """
3247
-
3248
- if len(points) == 0:
3249
- return
3250
-
3251
- if isinstance(points, wp.array):
3252
- wp_points = points
3253
- else:
3254
- wp_points = wp.array(points, dtype=wp.vec3, device=self._device)
3255
-
3256
- if name not in self._shape_instancers:
3257
- np_points = points.numpy() if isinstance(points, wp.array) else points
3258
- instancer = ShapeInstancer(self._shape_shader, self._device)
3259
- radius_is_scalar = np.isscalar(radius)
3260
- if radius_is_scalar:
3261
- vertices, indices = self._create_sphere_mesh(radius)
3262
- else:
3263
- vertices, indices = self._create_sphere_mesh(1.0)
3264
- if colors is None:
3265
- color = tab10_color_map(len(self._shape_geo_hash))
3266
- elif len(colors) == 3:
3267
- color = colors
3268
- else:
3269
- color = colors[0]
3270
- instancer.register_shape(vertices, indices, color, color)
3271
- scalings = None if radius_is_scalar else np.tile(radius, (3, 1)).T
3272
- instancer.allocate_instances(np_points, colors1=colors, colors2=colors, scalings=scalings)
3273
- self._shape_instancers[name] = instancer
3274
- else:
3275
- instancer = self._shape_instancers[name]
3276
- if len(points) != instancer.num_instances:
3277
- np_points = points.numpy() if isinstance(points, wp.array) else points
3278
- instancer.allocate_instances(np_points)
3279
-
3280
- with instancer:
3281
- wp.launch(
3282
- update_points_positions,
3283
- dim=len(points),
3284
- inputs=[wp_points, instancer.instance_scalings],
3285
- outputs=[instancer.vbo_transforms],
3286
- device=self._device,
3287
- )
3288
-
3289
- def _render_lines(self, name: str, lines, color: tuple, radius: float = 0.01):
3290
- if len(lines) == 0:
3291
- return
3292
-
3293
- if name not in self._shape_instancers:
3294
- instancer = ShapeInstancer(self._shape_shader, self._device)
3295
- vertices, indices = self._create_capsule_mesh(radius, 0.5)
3296
- if color is None or (isinstance(color, list) and len(color) > 0 and isinstance(color[0], list)):
3297
- color = tab10_color_map(len(self._shape_geo_hash))
3298
- instancer.register_shape(vertices, indices, color, color)
3299
- instancer.allocate_instances(np.zeros((len(lines), 3)))
3300
- self._shape_instancers[name] = instancer
3301
- else:
3302
- instancer = self._shape_instancers[name]
3303
- if len(lines) != instancer.num_instances:
3304
- instancer.allocate_instances(np.zeros((len(lines), 3)))
3305
- instancer.update_colors(color, color)
3306
-
3307
- lines_wp = wp.array(lines, dtype=wp.vec3, ndim=2, device=self._device)
3308
- with instancer:
3309
- wp.launch(
3310
- update_line_transforms,
3311
- dim=len(lines),
3312
- inputs=[lines_wp],
3313
- outputs=[instancer.vbo_transforms],
3314
- device=self._device,
3315
- )
3316
-
3317
- def render_line_list(
3318
- self,
3319
- name: str,
3320
- vertices,
3321
- indices,
3322
- color: tuple[float, float, float] | None = None,
3323
- radius: float = 0.01,
3324
- visible: bool = True,
3325
- ):
3326
- """Add a line list as a set of capsules
3327
-
3328
- Args:
3329
- vertices: The vertices of the line-list
3330
- indices: The indices of the line-list
3331
- color: The color of the line
3332
- radius: The radius of the line
3333
- """
3334
- lines = []
3335
- for i in range(len(indices) // 2):
3336
- lines.append((vertices[indices[2 * i]], vertices[indices[2 * i + 1]]))
3337
- lines = np.array(lines)
3338
- self._render_lines(name, lines, color, radius)
3339
-
3340
- def render_line_strip(
3341
- self,
3342
- name: str,
3343
- vertices,
3344
- color: tuple[float, float, float] | None = None,
3345
- radius: float = 0.01,
3346
- visible: bool = True,
3347
- ):
3348
- """Add a line strip as a set of capsules
3349
-
3350
- Args:
3351
- vertices: The vertices of the line-strip
3352
- color: The color of the line
3353
- radius: The radius of the line
3354
- """
3355
- lines = []
3356
- for i in range(len(vertices) - 1):
3357
- lines.append((vertices[i], vertices[i + 1]))
3358
- lines = np.array(lines)
3359
- self._render_lines(name, lines, color, radius)
3360
-
3361
- def update_shape_vertices(self, shape, points):
3362
- if isinstance(points, wp.array):
3363
- wp_points = points.to(self._device)
3364
- else:
3365
- wp_points = wp.array(points, dtype=wp.vec3, device=self._device)
3366
-
3367
- cuda_buffer = self._shape_gl_buffers[shape][4]
3368
- vertices_shape = self._shapes[shape][0].shape
3369
- vbo_vertices = cuda_buffer.map(dtype=wp.float32, shape=vertices_shape)
3370
-
3371
- wp.launch(
3372
- update_vbo_vertices,
3373
- dim=vertices_shape[0],
3374
- inputs=[wp_points],
3375
- outputs=[vbo_vertices],
3376
- device=self._device,
3377
- )
3378
-
3379
- cuda_buffer.unmap()
3380
-
3381
- @staticmethod
3382
- def _create_sphere_mesh(
3383
- radius=1.0,
3384
- num_latitudes=default_num_segments,
3385
- num_longitudes=default_num_segments,
3386
- reverse_winding=False,
3387
- ):
3388
- vertices = []
3389
- indices = []
3390
-
3391
- for i in range(num_latitudes + 1):
3392
- theta = i * np.pi / num_latitudes
3393
- sin_theta = np.sin(theta)
3394
- cos_theta = np.cos(theta)
3395
-
3396
- for j in range(num_longitudes + 1):
3397
- phi = j * 2 * np.pi / num_longitudes
3398
- sin_phi = np.sin(phi)
3399
- cos_phi = np.cos(phi)
3400
-
3401
- x = cos_phi * sin_theta
3402
- y = cos_theta
3403
- z = sin_phi * sin_theta
3404
-
3405
- u = float(j) / num_longitudes
3406
- v = float(i) / num_latitudes
3407
-
3408
- vertices.append([x * radius, y * radius, z * radius, x, y, z, u, v])
3409
-
3410
- for i in range(num_latitudes):
3411
- for j in range(num_longitudes):
3412
- first = i * (num_longitudes + 1) + j
3413
- second = first + num_longitudes + 1
3414
-
3415
- if reverse_winding:
3416
- indices.extend([first, second, first + 1, second, second + 1, first + 1])
3417
- else:
3418
- indices.extend([first, first + 1, second, second, first + 1, second + 1])
3419
-
3420
- return np.array(vertices, dtype=np.float32), np.array(indices, dtype=np.uint32)
3421
-
3422
- @staticmethod
3423
- def _create_capsule_mesh(radius, half_height, up_axis=1, segments=default_num_segments):
3424
- vertices = []
3425
- indices = []
3426
-
3427
- x_dir, y_dir, z_dir = ((1, 2, 0), (2, 0, 1), (0, 1, 2))[up_axis]
3428
- up_vector = np.zeros(3)
3429
- up_vector[up_axis] = half_height
3430
-
3431
- for i in range(segments + 1):
3432
- theta = i * np.pi / segments
3433
- sin_theta = np.sin(theta)
3434
- cos_theta = np.cos(theta)
3435
-
3436
- for j in range(segments + 1):
3437
- phi = j * 2 * np.pi / segments
3438
- sin_phi = np.sin(phi)
3439
- cos_phi = np.cos(phi)
3440
-
3441
- z = cos_phi * sin_theta
3442
- y = cos_theta
3443
- x = sin_phi * sin_theta
3444
-
3445
- u = cos_theta * 0.5 + 0.5
3446
- v = cos_phi * sin_theta * 0.5 + 0.5
3447
-
3448
- xyz = x, y, z
3449
- x, y, z = xyz[x_dir], xyz[y_dir], xyz[z_dir]
3450
- xyz = np.array((x, y, z), dtype=np.float32) * radius
3451
- if j < segments // 2:
3452
- xyz += up_vector
3453
- else:
3454
- xyz -= up_vector
3455
-
3456
- vertices.append([*xyz, x, y, z, u, v])
3457
-
3458
- nv = len(vertices)
3459
- for i in range(segments + 1):
3460
- for j in range(segments + 1):
3461
- first = (i * (segments + 1) + j) % nv
3462
- second = (first + segments + 1) % nv
3463
- indices.extend([first, second, (first + 1) % nv, second, (second + 1) % nv, (first + 1) % nv])
3464
-
3465
- vertex_data = np.array(vertices, dtype=np.float32)
3466
- index_data = np.array(indices, dtype=np.uint32)
3467
-
3468
- return vertex_data, index_data
3469
-
3470
- @staticmethod
3471
- def _create_cone_mesh(radius, half_height, up_axis=1, segments=default_num_segments):
3472
- # render it as a cylinder with zero top radius so we get correct normals on the sides
3473
- return OpenGLRenderer._create_cylinder_mesh(radius, half_height, up_axis, segments, 0.0)
3474
-
3475
- @staticmethod
3476
- def _create_cylinder_mesh(radius, half_height, up_axis=1, segments=default_num_segments, top_radius=None):
3477
- if up_axis not in (0, 1, 2):
3478
- raise ValueError("up_axis must be between 0 and 2")
3479
-
3480
- x_dir, y_dir, z_dir = (
3481
- (1, 2, 0),
3482
- (0, 1, 2),
3483
- (2, 0, 1),
3484
- )[up_axis]
3485
-
3486
- indices = []
3487
-
3488
- cap_vertices = []
3489
- side_vertices = []
3490
-
3491
- # create center cap vertices
3492
- position = np.array([0, -half_height, 0])[[x_dir, y_dir, z_dir]]
3493
- normal = np.array([0, -1, 0])[[x_dir, y_dir, z_dir]]
3494
- cap_vertices.append([*position, *normal, 0.5, 0.5])
3495
- cap_vertices.append([*-position, *-normal, 0.5, 0.5])
3496
-
3497
- if top_radius is None:
3498
- top_radius = radius
3499
- side_slope = -np.arctan2(top_radius - radius, 2 * half_height)
3500
-
3501
- # create the cylinder base and top vertices
3502
- for j in (-1, 1):
3503
- center_index = max(j, 0)
3504
- if j == 1:
3505
- radius = top_radius
3506
- for i in range(segments):
3507
- theta = 2 * np.pi * i / segments
3508
-
3509
- cos_theta = np.cos(theta)
3510
- sin_theta = np.sin(theta)
3511
-
3512
- x = cos_theta
3513
- y = j * half_height
3514
- z = sin_theta
3515
-
3516
- position = np.array([radius * x, y, radius * z])
3517
-
3518
- normal = np.array([x, side_slope, z])
3519
- normal = normal / np.linalg.norm(normal)
3520
- uv = (i / (segments - 1), (j + 1) / 2)
3521
- vertex = np.hstack([position[[x_dir, y_dir, z_dir]], normal[[x_dir, y_dir, z_dir]], uv])
3522
- side_vertices.append(vertex)
3523
-
3524
- normal = np.array([0, j, 0])
3525
- uv = (cos_theta * 0.5 + 0.5, sin_theta * 0.5 + 0.5)
3526
- vertex = np.hstack([position[[x_dir, y_dir, z_dir]], normal[[x_dir, y_dir, z_dir]], uv])
3527
- cap_vertices.append(vertex)
3528
-
3529
- cs = center_index * segments
3530
- indices.extend([center_index, i + cs + 2, (i + 1) % segments + cs + 2][::-j])
3531
-
3532
- # create the cylinder side indices
3533
- for i in range(segments):
3534
- index1 = len(cap_vertices) + i + segments
3535
- index2 = len(cap_vertices) + ((i + 1) % segments) + segments
3536
- index3 = len(cap_vertices) + i
3537
- index4 = len(cap_vertices) + ((i + 1) % segments)
3538
-
3539
- indices.extend([index1, index2, index3, index2, index4, index3])
3540
-
3541
- vertex_data = np.array(np.vstack((cap_vertices, side_vertices)), dtype=np.float32)
3542
- index_data = np.array(indices, dtype=np.uint32)
3543
-
3544
- return vertex_data, index_data
3545
-
3546
- @staticmethod
3547
- def _create_arrow_mesh(
3548
- base_radius, base_height, cap_radius=None, cap_height=None, up_axis=1, segments=default_num_segments
3549
- ):
3550
- if up_axis not in (0, 1, 2):
3551
- raise ValueError("up_axis must be between 0 and 2")
3552
- if cap_radius is None:
3553
- cap_radius = base_radius * 1.8
3554
- if cap_height is None:
3555
- cap_height = base_height * 0.18
3556
-
3557
- up_vector = np.array([0, 0, 0])
3558
- up_vector[up_axis] = 1
3559
-
3560
- base_vertices, base_indices = OpenGLRenderer._create_cylinder_mesh(
3561
- base_radius, base_height / 2, up_axis, segments
3562
- )
3563
- cap_vertices, cap_indices = OpenGLRenderer._create_cone_mesh(cap_radius, cap_height / 2, up_axis, segments)
3564
-
3565
- base_vertices[:, :3] += base_height / 2 * up_vector
3566
- # move cap slightly lower to avoid z-fighting
3567
- cap_vertices[:, :3] += (base_height + cap_height / 2 - 1e-3 * base_height) * up_vector
3568
-
3569
- vertex_data = np.vstack((base_vertices, cap_vertices))
3570
- index_data = np.hstack((base_indices, cap_indices + len(base_vertices)))
3571
-
3572
- return vertex_data, index_data
3573
-
3574
- @staticmethod
3575
- def _create_box_mesh(extents):
3576
- x_extent, y_extent, z_extent = extents
3577
-
3578
- vertices = [
3579
- # Position Normal UV
3580
- [-x_extent, -y_extent, -z_extent, -1, 0, 0, 0, 0],
3581
- [-x_extent, -y_extent, z_extent, -1, 0, 0, 1, 0],
3582
- [-x_extent, y_extent, z_extent, -1, 0, 0, 1, 1],
3583
- [-x_extent, y_extent, -z_extent, -1, 0, 0, 0, 1],
3584
- [x_extent, -y_extent, -z_extent, 1, 0, 0, 0, 0],
3585
- [x_extent, -y_extent, z_extent, 1, 0, 0, 1, 0],
3586
- [x_extent, y_extent, z_extent, 1, 0, 0, 1, 1],
3587
- [x_extent, y_extent, -z_extent, 1, 0, 0, 0, 1],
3588
- [-x_extent, -y_extent, -z_extent, 0, -1, 0, 0, 0],
3589
- [-x_extent, -y_extent, z_extent, 0, -1, 0, 1, 0],
3590
- [x_extent, -y_extent, z_extent, 0, -1, 0, 1, 1],
3591
- [x_extent, -y_extent, -z_extent, 0, -1, 0, 0, 1],
3592
- [-x_extent, y_extent, -z_extent, 0, 1, 0, 0, 0],
3593
- [-x_extent, y_extent, z_extent, 0, 1, 0, 1, 0],
3594
- [x_extent, y_extent, z_extent, 0, 1, 0, 1, 1],
3595
- [x_extent, y_extent, -z_extent, 0, 1, 0, 0, 1],
3596
- [-x_extent, -y_extent, -z_extent, 0, 0, -1, 0, 0],
3597
- [-x_extent, y_extent, -z_extent, 0, 0, -1, 1, 0],
3598
- [x_extent, y_extent, -z_extent, 0, 0, -1, 1, 1],
3599
- [x_extent, -y_extent, -z_extent, 0, 0, -1, 0, 1],
3600
- [-x_extent, -y_extent, z_extent, 0, 0, 1, 0, 0],
3601
- [-x_extent, y_extent, z_extent, 0, 0, 1, 1, 0],
3602
- [x_extent, y_extent, z_extent, 0, 0, 1, 1, 1],
3603
- [x_extent, -y_extent, z_extent, 0, 0, 1, 0, 1],
3604
- ]
3605
-
3606
- # fmt: off
3607
- indices = [
3608
- 0, 1, 2,
3609
- 0, 2, 3,
3610
- 4, 6, 5,
3611
- 4, 7, 6,
3612
- 8, 10, 9,
3613
- 8, 11, 10,
3614
- 12, 13, 14,
3615
- 12, 14, 15,
3616
- 16, 17, 18,
3617
- 16, 18, 19,
3618
- 20, 22, 21,
3619
- 20, 23, 22,
3620
- ]
3621
- # fmt: on
3622
- return np.array(vertices, dtype=np.float32), np.array(indices, dtype=np.uint32)
3623
-
3624
- def _switch_context(self):
3625
- try:
3626
- self.window.switch_to()
3627
- except AttributeError:
3628
- # The window could be in the process of being closed, in which case
3629
- # its corresponding context might have been destroyed and set to `None`.
3630
- pass
3631
-
3632
-
3633
- if __name__ == "__main__":
3634
- renderer = OpenGLRenderer()
24
+ return get_deprecated_api(_render_opengl, "wp.render", name)