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