Skip to content

Commit

Permalink
Merge pull request #2217 from nelianos/fix-load-cam-gltf
Browse files Browse the repository at this point in the history
Fix loading camera in gltf
  • Loading branch information
mikedh committed May 6, 2024
2 parents 38ab5bc + a1cd8e1 commit 3c6775b
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 0 deletions.
25 changes: 25 additions & 0 deletions tests/test_scene.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import numpy as np

try:
from . import generic as g
except BaseException:
Expand Down Expand Up @@ -102,6 +104,29 @@ def test_scene(self):
# make sure explode doesn't crash
s.explode()

def test_cam_gltf(self):
# Test that the camera is stored and loaded successfully into a Scene from a gltf.
cam = g.trimesh.scene.cameras.Camera(fov=[60, 90], name="cam1")
box = g.trimesh.creation.box(extents=[1, 2, 3])
scene = g.trimesh.Scene(
geometry=[box],
camera=cam,
camera_transform=np.array([[0, 1, 0, -1], [1, 0, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]])
)
with g.TemporaryDirectory() as d:
# exports by path allow files to be written
path = g.os.path.join(d, "tmp.glb")
scene.export(path)
r = g.trimesh.load(path, force="scene")

# ensure no added nodes
assert set(r.graph.nodes) == {"world", "geometry_0", "cam1"}
# ensure same camera parameters and extrinsics
assert (r.camera_transform == scene.camera_transform).all()
assert r.camera.name == cam.name
assert (r.camera.fov == cam.fov).all()
assert r.camera.z_near == cam.z_near

def test_scaling(self):
# Test the scaling of scenes including unit conversion.

Expand Down
54 changes: 54 additions & 0 deletions trimesh/exchange/gltf.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from .. import rendering, resources, transformations, util, visual
from ..caching import hash_fast
from ..constants import log, tol
from ..scene.cameras import Camera
from ..typed import NDArray, Optional
from ..util import unique_name
from ..visual.gloss import specular_to_pbr
Expand Down Expand Up @@ -1664,6 +1665,10 @@ def _read_buffers(
# unvisited, pairs of node indexes
queue = deque()

# camera(s), if they exist
camera = None
camera_transform = None

if "scene" in header:
# specify the index of scenes if specified
scene_index = header["scene"]
Expand Down Expand Up @@ -1735,6 +1740,20 @@ def _read_buffers(
kwargs["matrix"], np.diag(np.concatenate((child["scale"], [1.0])))
)

# If a camera exists, create the camera and dont add the node to the graph
#TODO only process the first camera, ignore the rest
#TODO assumes the camera node is child of the world frame
#TODO will only read perspective camera
if "camera" in child and camera is None:
cam_idx = child["camera"]
try:
camera = _cam_from_gltf(header['cameras'][cam_idx])
except KeyError:
log.debug("GLTF camera is not fully-defined")
if camera:
camera_transform = kwargs["matrix"]
continue

# treat node metadata similarly to mesh metadata
if isinstance(child.get("extras"), dict):
kwargs["metadata"] = child["extras"]
Expand Down Expand Up @@ -1780,6 +1799,8 @@ def _read_buffers(
"geometry": meshes,
"graph": graph,
"base_frame": base_frame,
"camera": camera,
"camera_transform": camera_transform
}
try:
# load any scene extras into scene.metadata
Expand All @@ -1799,6 +1820,39 @@ def _read_buffers(
return result


def _cam_from_gltf(cam):
"""
Convert a gltf perspective camera to trimesh.
The retrieved camera will have default resolution, since the gltf specification
does not contain it.
If the camera is not perspective will return None.
If the camera is perspective but is missing fields, will raise `KeyError`
Parameters
------------
cam : dict
Camera represented as a dictionary according to glTF
Returns
-------------
camera : trimesh.scene.cameras.Camera
Trimesh camera object
"""
if "perspective" not in cam:
return
name = cam.get("name")
znear = cam["perspective"]["znear"]
aspect_ratio = cam["perspective"]["aspectRatio"]
yfov = np.degrees(cam["perspective"]["yfov"])

fov = (aspect_ratio * yfov, yfov)

return Camera(name=name,
fov=fov, z_near=znear)


def _convert_camera(camera):
"""
Convert a trimesh camera to a GLTF camera.
Expand Down
6 changes: 6 additions & 0 deletions trimesh/exchange/load.py
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,12 @@ def handle_scene():
else:
scene = Scene(geometry)

# camera, if it exists
camera = kwargs.get('camera')
if camera:
scene.camera = camera
scene.camera_transform = kwargs.get('camera_transform')

if "base_frame" in kwargs:
scene.graph.base_frame = kwargs["base_frame"]
metadata = kwargs.get("metadata")
Expand Down

0 comments on commit 3c6775b

Please sign in to comment.