Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix loading camera in gltf #2217

Merged
merged 7 commits into from
May 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading