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/Logger.h>
16#include <lagrange/image/Array3D.h>
17#include <lagrange/image/View3D.h>
18#include <lagrange/scene/Scene.h>
19#include <lagrange/scene/scene_utils.h>
20#include <lagrange/transform_mesh.h>
21
22#include <algorithm>
23
24namespace lagrange::scene::internal {
25
26using Array3Df = image::experimental::Array3D<float>;
27using View3Df = image::experimental::View3D<float>;
28using ConstView3Df = image::experimental::View3D<const float>;
29
30// FIXME this strips non-color channel, other variants of this function don't.
31inline Array3Df convert_from(const ImageBufferExperimental& image)
32{
33 size_t nc = std::min(image.num_channels, size_t(3));
34 auto result = image::experimental::create_image<float>(image.width, image.height, nc);
35
36 auto copy_buffer = [&](auto scalar) {
37 using T = std::decay_t<decltype(scalar)>;
38 constexpr bool IsChar = std::is_integral_v<T> && sizeof(T) == 1;
39 la_runtime_assert(sizeof(T) * 8 == image.get_bits_per_element());
40 auto rawbuf = reinterpret_cast<const T*>(image.data.data());
41 for (size_t y = 0, i = 0; y < image.height; ++y) {
42 for (size_t x = 0; x < image.width; ++x) {
43 for (size_t c = 0; c < image.num_channels; ++c) {
44 if (c >= nc) {
45 ++i;
46 continue;
47 }
48 if constexpr (IsChar) {
49 result(x, y, c) = static_cast<float>(rawbuf[i++]) / 255.f;
50 } else {
51 result(x, y, c) = rawbuf[i++];
52 }
53 }
54 }
55 }
56 };
57
58 switch (image.element_type) {
59 case AttributeValueType::e_uint8_t: copy_buffer(uint8_t()); break;
60 case AttributeValueType::e_int8_t: copy_buffer(int8_t()); break;
61 case AttributeValueType::e_uint32_t: copy_buffer(uint32_t()); break;
62 case AttributeValueType::e_int32_t: copy_buffer(int32_t()); break;
63 case AttributeValueType::e_float: copy_buffer(float()); break;
64 case AttributeValueType::e_double: copy_buffer(double()); break;
65 default: throw std::runtime_error("Unsupported image scalar type");
66 }
67
68 return result;
69}
70
71// Convert a float Array3D image to an ImageBufferExperimental (uint8, row-major y,x,c order).
72// Note: convert_from() truncates to 3 channels, so this is only a true round-trip inverse for
73// images with <= 3 channels.
74inline ImageBufferExperimental convert_to(const ConstView3Df& image)
75{
76 ImageBufferExperimental result;
77 result.width = image.extent(0);
78 result.height = image.extent(1);
79 const size_t num_channels = image.extent(2);
81 num_channels == 1 || num_channels == 3 || num_channels == 4,
82 "ImageBufferExperimental requires 1, 3, or 4 channels");
83 result.num_channels = num_channels;
84 result.element_type = AttributeValueType::e_uint8_t;
85 result.data.resize(result.width * result.height * result.num_channels);
86 for (size_t y = 0, i = 0; y < result.height; ++y) {
87 for (size_t x = 0; x < result.width; ++x) {
88 for (size_t c = 0; c < result.num_channels; ++c) {
89 result.data[i++] =
90 static_cast<unsigned char>(std::clamp(image(x, y, c), 0.0f, 1.0f) * 255.0f);
91 }
92 }
93 }
94 return result;
95}
96
98{
99 MaterialExperimental::AlphaMode alpha_mode = MaterialExperimental::AlphaMode::Opaque;
100 float alpha_cutoff = 0.5f;
101};
102
103// Create a scene containing a single mesh with a base color texture.
104// This is the inverse of single_mesh_from_scene().
105template <typename Scalar, typename Index>
106Scene<Scalar, Index> single_mesh_to_scene(
108 const ConstView3Df& image,
109 const SingleMeshToSceneOptions& options = {})
110{
112
113 auto mesh_id = scene.add(std::move(mesh));
114
115 ImageExperimental scene_image;
116 scene_image.name = "base_color";
117 scene_image.image = convert_to(image);
118 auto image_id = scene.add(std::move(scene_image));
119
120 Texture texture;
121 texture.name = "base_color";
122 texture.image = image_id;
123 auto texture_id = scene.add(std::move(texture));
124
125 MaterialExperimental material;
126 material.name = "material";
127 material.alpha_mode = options.alpha_mode;
128 material.alpha_cutoff = options.alpha_cutoff;
129 material.base_color_texture.index = texture_id;
130 material.base_color_texture.texcoord = 0;
131 auto material_id = scene.add(std::move(material));
132
133 Node node;
134 node.name = "mesh";
135 SceneMeshInstance instance;
136 instance.mesh = mesh_id;
137 instance.materials.push_back(material_id);
138 node.meshes.push_back(std::move(instance));
139 auto node_id = scene.add(std::move(node));
140 scene.root_nodes.push_back(node_id);
141
142 return scene;
143}
144
145// Extract a single uv unwrapped mesh and optionally its base color tensor from a scene.
146template <typename Scalar, typename Index>
147std::tuple<SurfaceMesh<Scalar, Index>, std::optional<Array3Df>> single_mesh_from_scene(
148 const Scene<Scalar, Index>& scene)
149{
150 using ElementId = scene::ElementId;
151
152 // Find mesh nodes in the scene
153 std::vector<ElementId> mesh_node_ids;
154 for (ElementId node_id = 0; node_id < scene.nodes.size(); ++node_id) {
155 const auto& node = scene.nodes[node_id];
156 if (!node.meshes.empty()) {
157 mesh_node_ids.push_back(node_id);
158 }
159 }
160
161 if (mesh_node_ids.size() != 1) {
162 throw std::runtime_error(
163 fmt::format(
164 "Input scene contains {} mesh nodes. Expected exactly 1 mesh node.",
165 mesh_node_ids.size()));
166 }
167 const auto& mesh_node = scene.nodes[mesh_node_ids.front()];
168
169 if (mesh_node.meshes.size() != 1) {
170 throw std::runtime_error(
171 fmt::format(
172 "Input scene has a mesh node with {} instance per node. Expected "
173 "exactly 1 instance per node",
174 mesh_node.meshes.size()));
175 }
176 const auto& mesh_instance = mesh_node.meshes.front();
177
178 [[maybe_unused]] const auto mesh_id = mesh_instance.mesh;
179 la_debug_assert(mesh_id < scene.meshes.size());
180 SurfaceMesh<Scalar, Index> mesh = scene.meshes[mesh_instance.mesh];
181 {
182 // Apply node local->world transform
183 auto world_from_mesh = utils::compute_global_node_transform(scene, mesh_node_ids.front())
184 .template cast<Scalar>();
185 transform_mesh(mesh, world_from_mesh);
186 }
187
188 // Find base texture if available
189 if (auto num_mats = mesh_instance.materials.size(); num_mats != 1) {
190 logger().warn(
191 "Mesh node has {} materials. Expected exactly 1 material. Ignoring materials.",
192 num_mats);
193 return {mesh, std::nullopt};
194 }
195 const auto& material = scene.materials[mesh_instance.materials.front()];
196 if (material.base_color_texture.texcoord != 0) {
197 logger().warn(
198 "Mesh node material texcoord is {} != 0. Expected 0. Ignoring texcoord.",
199 material.base_color_texture.texcoord);
200 }
201 const auto texture_id = material.base_color_texture.index;
202 la_debug_assert(texture_id < scene.textures.size());
203 const auto& texture = scene.textures[texture_id];
204
205 const auto image_id = texture.image;
206 la_debug_assert(image_id < scene.images.size());
207 const auto& image_ = scene.images[image_id].image;
208 Array3Df image = convert_from(image_);
209
210 return {mesh, image};
211}
212
213} // 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:199
#define la_runtime_assert(...)
Runtime assertion check.
Definition assert.h:174
#define la_debug_assert(...)
Debug assertion check.
Definition assert.h:194
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