Lagrange
Loading...
Searching...
No Matches
shared_utils.h
1/*
2 * Copyright 2026 Adobe. All rights reserved.
3 * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License. You may obtain a copy
5 * of the License at http://www.apache.org/licenses/LICENSE-2.0
6 *
7 * Unless required by applicable law or agreed to in writing, software distributed under
8 * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9 * OF ANY KIND, either express or implied. See the License for the specific language
10 * governing permissions and limitations under the License.
11 */
12#pragma once
13
14#include <lagrange/AttributeValueType.h>
15#include <lagrange/CameraTransforms.h>
16#include <lagrange/Logger.h>
17#include <lagrange/image/Array3D.h>
18#include <lagrange/image/View3D.h>
19#include <lagrange/scene/Scene.h>
20#include <lagrange/scene/scene_utils.h>
21#include <lagrange/transform_mesh.h>
22#include <lagrange/utils/fmt/format.h>
23
24#include <algorithm>
25#include <vector>
26
27namespace lagrange::scene::internal {
28
29using Array3Df = image::experimental::Array3D<float>;
30using View3Df = image::experimental::View3D<float>;
31using ConstView3Df = image::experimental::View3D<const float>;
32
33// FIXME this strips non-color channel, other variants of this function don't.
34inline Array3Df convert_from(const ImageBufferExperimental& image)
35{
36 size_t nc = std::min(image.num_channels, size_t(3));
37 auto result = image::experimental::create_image<float>(image.width, image.height, nc);
38
39 auto copy_buffer = [&](auto scalar) {
40 using T = std::decay_t<decltype(scalar)>;
41 constexpr bool IsChar = std::is_integral_v<T> && sizeof(T) == 1;
42 la_runtime_assert(sizeof(T) * 8 == image.get_bits_per_element());
43 auto rawbuf = reinterpret_cast<const T*>(image.data.data());
44 for (size_t y = 0, i = 0; y < image.height; ++y) {
45 for (size_t x = 0; x < image.width; ++x) {
46 for (size_t c = 0; c < image.num_channels; ++c) {
47 if (c >= nc) {
48 ++i;
49 continue;
50 }
51 if constexpr (IsChar) {
52 result(x, y, c) = static_cast<float>(rawbuf[i++]) / 255.f;
53 } else {
54 result(x, y, c) = rawbuf[i++];
55 }
56 }
57 }
58 }
59 };
60
61 switch (image.element_type) {
62 case AttributeValueType::e_uint8_t: copy_buffer(uint8_t()); break;
63 case AttributeValueType::e_int8_t: copy_buffer(int8_t()); break;
64 case AttributeValueType::e_uint32_t: copy_buffer(uint32_t()); break;
65 case AttributeValueType::e_int32_t: copy_buffer(int32_t()); break;
66 case AttributeValueType::e_float: copy_buffer(float()); break;
67 case AttributeValueType::e_double: copy_buffer(double()); break;
68 default: throw std::runtime_error("Unsupported image scalar type");
69 }
70
71 return result;
72}
73
74// Convert a float Array3D image to an ImageBufferExperimental (uint8, row-major y,x,c order).
75// Note: convert_from() truncates to 3 channels, so this is only a true round-trip inverse for
76// images with <= 3 channels.
77inline ImageBufferExperimental convert_to(const ConstView3Df& image)
78{
79 ImageBufferExperimental result;
80 result.width = image.extent(0);
81 result.height = image.extent(1);
82 const size_t num_channels = image.extent(2);
84 num_channels == 1 || num_channels == 3 || num_channels == 4,
85 "ImageBufferExperimental requires 1, 3, or 4 channels");
86 result.num_channels = num_channels;
87 result.element_type = AttributeValueType::e_uint8_t;
88 result.data.resize(result.width * result.height * result.num_channels);
89 for (size_t y = 0, i = 0; y < result.height; ++y) {
90 for (size_t x = 0; x < result.width; ++x) {
91 for (size_t c = 0; c < result.num_channels; ++c) {
92 result.data[i++] =
93 static_cast<unsigned char>(std::clamp(image(x, y, c), 0.0f, 1.0f) * 255.0f);
94 }
95 }
96 }
97 return result;
98}
99
101{
102 MaterialExperimental::AlphaMode alpha_mode = MaterialExperimental::AlphaMode::Opaque;
103 float alpha_cutoff = 0.5f;
104};
105
106// Create a scene containing a single mesh with a base color texture.
107// This is the inverse of single_mesh_from_scene().
108template <typename Scalar, typename Index>
109Scene<Scalar, Index> single_mesh_to_scene(
111 const ConstView3Df& image,
112 const SingleMeshToSceneOptions& options = {})
113{
115
116 auto mesh_id = scene.add(std::move(mesh));
117
118 ImageExperimental scene_image;
119 scene_image.name = "base_color";
120 scene_image.image = convert_to(image);
121 auto image_id = scene.add(std::move(scene_image));
122
123 Texture texture;
124 texture.name = "base_color";
125 texture.image = image_id;
126 auto texture_id = scene.add(std::move(texture));
127
128 MaterialExperimental material;
129 material.name = "material";
130 material.alpha_mode = options.alpha_mode;
131 material.alpha_cutoff = options.alpha_cutoff;
132 material.base_color_texture.index = texture_id;
133 material.base_color_texture.texcoord = 0;
134 auto material_id = scene.add(std::move(material));
135
136 Node node;
137 node.name = "mesh";
138 SceneMeshInstance instance;
139 instance.mesh = mesh_id;
140 instance.materials.push_back(material_id);
141 node.meshes.push_back(std::move(instance));
142 auto node_id = scene.add(std::move(node));
143 scene.root_nodes.push_back(node_id);
144
145 return scene;
146}
147
148// Extract a single uv unwrapped mesh and optionally its base color tensor from a scene.
149template <typename Scalar, typename Index>
150std::tuple<SurfaceMesh<Scalar, Index>, std::optional<Array3Df>> single_mesh_from_scene(
151 const Scene<Scalar, Index>& scene)
152{
153 using ElementId = scene::ElementId;
154
155 // Find mesh nodes in the scene
156 std::vector<ElementId> mesh_node_ids;
157 for (ElementId node_id = 0; node_id < scene.nodes.size(); ++node_id) {
158 const auto& node = scene.nodes[node_id];
159 if (!node.meshes.empty()) {
160 mesh_node_ids.push_back(node_id);
161 }
162 }
163
164 if (mesh_node_ids.size() != 1) {
165 throw std::runtime_error(format(
166 "Input scene contains {} mesh nodes. Expected exactly 1 mesh node.",
167 mesh_node_ids.size()));
168 }
169 const auto& mesh_node = scene.nodes[mesh_node_ids.front()];
170
171 if (mesh_node.meshes.size() != 1) {
172 throw std::runtime_error(format(
173 "Input scene has a mesh node with {} instance per node. Expected "
174 "exactly 1 instance per node",
175 mesh_node.meshes.size()));
176 }
177 const auto& mesh_instance = mesh_node.meshes.front();
178
179 [[maybe_unused]] const auto mesh_id = mesh_instance.mesh;
180 la_debug_assert(mesh_id < scene.meshes.size());
181 SurfaceMesh<Scalar, Index> mesh = scene.meshes[mesh_instance.mesh];
182 {
183 // Apply node local->world transform
184 auto world_from_mesh = utils::compute_global_node_transform(scene, mesh_node_ids.front())
185 .template cast<Scalar>();
186 transform_mesh(mesh, world_from_mesh);
187 }
188
189 // Find base texture if available
190 if (auto num_mats = mesh_instance.materials.size(); num_mats != 1) {
191 logger().warn(
192 "Mesh node has {} materials. Expected exactly 1 material. Ignoring materials.",
193 num_mats);
194 return {mesh, std::nullopt};
195 }
196 const auto& material = scene.materials[mesh_instance.materials.front()];
197 if (material.base_color_texture.texcoord != 0) {
198 logger().warn(
199 "Mesh node material texcoord is {} != 0. Expected 0. Ignoring texcoord.",
200 material.base_color_texture.texcoord);
201 }
202 const auto texture_id = material.base_color_texture.index;
203 la_debug_assert(texture_id < scene.textures.size());
204 const auto& texture = scene.textures[texture_id];
205
206 const auto image_id = texture.image;
207 la_debug_assert(image_id < scene.images.size());
208 const auto& image_ = scene.images[image_id].image;
209 Array3Df image = convert_from(image_);
210
211 return {mesh, image};
212}
213
214// Extract camera view + projection transforms for every camera referenced by a node in the scene.
215template <typename Scalar, typename Index>
216std::vector<CameraTransforms> camera_transforms_from_scene(const Scene<Scalar, Index>& scene)
217{
218 using ElementId = scene::ElementId;
219 std::vector<CameraTransforms> cameras;
220 for (ElementId node_id = 0; node_id < scene.nodes.size(); ++node_id) {
221 const auto& node = scene.nodes[node_id];
222 if (!node.cameras.empty()) {
223 auto world_from_node = utils::compute_global_node_transform(scene, node_id);
224 for (auto camera_id : node.cameras) {
225 const auto& scene_camera = scene.cameras[camera_id];
226 CameraTransforms camera;
227 camera.view = utils::camera_view_transform(scene_camera, world_from_node);
228 camera.projection = utils::camera_projection_transform(scene_camera);
229 cameras.push_back(camera);
230 }
231 }
232 }
233
234 return cameras;
235}
236
237} // namespace lagrange::scene::internal
A general purpose polygonal mesh class.
Definition SurfaceMesh.h:73
LA_CORE_API spdlog::logger & logger()
Retrieves the current logger.
Definition Logger.cpp:40
void transform_mesh(SurfaceMesh< Scalar, Index > &mesh, const Eigen::Transform< Scalar, Dimension, Eigen::Affine > &transform, const TransformOptions &options={})
Apply an affine transform to a mesh in-place.
Definition transform_mesh.cpp:137
#define la_runtime_assert(...)
Runtime assertion check.
Definition assert.h:175
#define la_debug_assert(...)
Debug assertion check.
Definition assert.h:195
Array3D< T > create_image(size_t width, size_t height, size_t num_channels)
Create an image with the given dimensions and number of channels.
Definition Array3D.h:46
Basic image data structure.
Image structure that can store either image data or reference to an image file.
Definition Scene.h:116
ImageBufferExperimental image
Image data.
Definition Scene.h:121
std::string name
Image name. Not guaranteed to be unique and can be empty.
Definition Scene.h:118
Definition Scene.h:58
Definition Scene.h:353
ElementId add(T &&value)
Add an element to the scene.
Definition Scene.h:417
Definition Scene.h:188
int texcoord
Index of UV coordinates.
Definition Scene.h:141
ElementId index
Texture index. Index in scene.textures vector.
Definition Scene.h:137