Files
Mujoco_WASM/src/render/filament/support/mesh_util.cc
T
Haroon Qureshi ba9a65031b Allow mesh data to be reuploaded without having to recreate the mesh.
Splits mjrfMeshData into config+data parts.

PiperOrigin-RevId: 952042545
Change-Id: I19a572a0f591fa0d33bbc2c32b974efc075310f4
2026-07-22 04:56:30 -07:00

525 lines
18 KiB
C++

// Copyright 2026 DeepMind Technologies Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "render/filament/support/mesh_util.h"
#include <cfloat>
#include <cstddef>
#include <cstring>
#include <vector>
#include <math/TVecHelpers.h>
#include <math/vec2.h>
#include <math/vec3.h>
#include <math/vec4.h>
#include <mujoco/mjrfilament.h>
#include <mujoco/mujoco.h>
#include "engine/engine_vis_visualize.h"
#include "render/filament/mjrfilament_cpp.h"
#include "render/filament/support/filament_util.h"
namespace mujoco {
using filament::math::float2;
using filament::math::float3;
using filament::math::float4;
// Vertex types that can be used to fill in mesh data using generic functions.
namespace {
struct VertexNoUv {
float3 position;
float4 orientation;
static constexpr bool kHasUv = false;
};
struct VertexWithUv {
float3 position;
float4 orientation;
float2 uv;
static constexpr bool kHasUv = true;
};
} // namespace
static void AccumulateNormal(float3* normals, const mjtNum* src_positions,
const int* indices) {
const int idx0 = indices[0];
const int idx1 = indices[1];
const int idx2 = indices[2];
const float3 v0 = ReadFloat3(src_positions, idx0);
const float3 v1 = ReadFloat3(src_positions, idx1);
const float3 v2 = ReadFloat3(src_positions, idx2);
const float3 normal = normalize(cross(v1 - v0, v2 - v0));
normals[idx0] += normal;
normals[idx1] += normal;
normals[idx2] += normal;
}
template <typename T>
static void AddFlexFace(T* vertices, const mjtNum* src_positions,
const float3* src_normals, float radius,
bool flatten_normals, const int* indices, int i0,
int i1, int i2) {
const int idx0 = indices[i0];
const int idx1 = indices[i1];
const int idx2 = indices[i2];
const float3 v0 = ReadFloat3(src_positions, idx0);
const float3 v1 = ReadFloat3(src_positions, idx1);
const float3 v2 = ReadFloat3(src_positions, idx2);
vertices[0].position = v0 + (radius * src_normals[idx0]);
vertices[1].position = v1 + (radius * src_normals[idx1]);
vertices[2].position = v2 + (radius * src_normals[idx2]);
const float sign = radius >= 0 ? 1.f : -1.f;
if (flatten_normals) {
const float3 flat = normalize(cross(v1 - v0, v2 - v0));
const float4 orientation = CalculateOrientation(sign * flat);
vertices[0].orientation = orientation;
vertices[1].orientation = orientation;
vertices[2].orientation = orientation;
} else {
vertices[0].orientation = CalculateOrientation(sign * src_normals[idx0]);
vertices[1].orientation = CalculateOrientation(sign * src_normals[idx1]);
vertices[2].orientation = CalculateOrientation(sign * src_normals[idx2]);
}
}
template <typename T>
static void AddSideFace(T* vertices, const mjtNum* src_positions,
const float3* src_normals, float radius,
const int* indices, int i0, int i1) {
const int idx0 = indices[i0];
const int idx1 = indices[i1];
const float3 v0 = ReadFloat3(src_positions, idx0);
const float3 v1 = ReadFloat3(src_positions, idx1);
const float3 v01 = v1 - v0;
float3 normal = normalize(cross(v01, src_normals[idx1]));
if (radius < 0) {
normal = -normal;
}
vertices[0].position = v0 + radius * src_normals[idx0];
vertices[1].position = v1 - radius * src_normals[idx1];
vertices[2].position = v1 + radius * src_normals[idx1];
vertices[0].orientation = CalculateOrientation(normal);
vertices[1].orientation = vertices[0].orientation;
vertices[2].orientation = vertices[0].orientation;
}
template <typename T>
static void AddFaceUvs(T* vertices, const float* src_uvs, const int* indices,
int t0, int t1, int t2) {
if constexpr (T::kHasUv) {
if (src_uvs) {
vertices[0].uv = ReadFloat2(src_uvs, indices[t0]);
vertices[1].uv = ReadFloat2(src_uvs, indices[t1]);
vertices[2].uv = ReadFloat2(src_uvs, indices[t2]);
}
}
}
template <typename T>
static void UpdateBounds(float3* min_pt, float3* max_pt, const T* vertices) {
*min_pt = min(*min_pt, vertices[0].position);
*max_pt = max(*max_pt, vertices[0].position);
*min_pt = min(*min_pt, vertices[1].position);
*max_pt = max(*max_pt, vertices[1].position);
*min_pt = min(*min_pt, vertices[2].position);
*max_pt = max(*max_pt, vertices[2].position);
}
template <typename T>
static void FillFlexVertices(T* vertices, const mjModel* model,
const mjData* data, int flex_id, float3* min_pt,
float3* max_pt) {
const int dim = model->flex_dim[flex_id];
const int* edata = model->flex_elem + model->flex_elemdataadr[flex_id];
const int* sdata = model->flex_shell + model->flex_shelldataadr[flex_id];
const int* tdata =
model->flex_elemtexcoord + model->flex_elemdataadr[flex_id];
const float radius = (float)model->flex_radius[flex_id];
const bool flat_shading = (bool)model->flex_flatskin[flex_id];
const mjtNum* src_positions =
data->flexvert_xpos + 3 * model->flex_vertadr[flex_id];
const float* src_uvs = nullptr;
if (model->flex_texcoordadr[flex_id] >= 0) {
src_uvs = model->flex_texcoord + 2 * model->flex_texcoordadr[flex_id];
}
// Determine the maximum vertex index.
int max_index = 0;
if (dim == 2) {
for (int e = 0; e < model->flex_elemnum[flex_id]; ++e) {
const int* subindices = edata + (e * 3);
max_index = mjMAX(max_index, subindices[0]);
max_index = mjMAX(max_index, subindices[1]);
max_index = mjMAX(max_index, subindices[2]);
}
} else {
for (int s = 0; s < model->flex_shellnum[flex_id]; ++s) {
const int* subindices = sdata + (s * 3);
max_index = mjMAX(max_index, subindices[0]);
max_index = mjMAX(max_index, subindices[1]);
max_index = mjMAX(max_index, subindices[2]);
}
}
// Accumulate normals.
std::vector<float3> normals(max_index + 1, float3(0, 0, 0));
if (dim == 2) {
for (int e = 0; e < model->flex_elemnum[flex_id]; ++e) {
const int* subindices = edata + (e * 3);
AccumulateNormal(normals.data(), src_positions, subindices);
}
} else {
for (int s = 0; s < model->flex_shellnum[flex_id]; ++s) {
const int* subindices = sdata + (s * 3);
AccumulateNormal(normals.data(), src_positions, subindices);
}
}
// Normalize the accumulated normals.
for (float3& n : normals) {
if (length(n) > 1e-16f) {
n = normalize(n);
} else {
n = float3(1, 0, 0);
}
}
if (dim == 2) {
for (int e = 0; e < model->flex_elemnum[flex_id]; ++e) {
const int* indices = edata + (e * (dim + 1));
const int* tex_indices = tdata + (e * (dim + 1));
AddFlexFace(vertices, src_positions, normals.data(), radius,
flat_shading, indices, 0, 1, 2);
AddFaceUvs(vertices, src_uvs, tex_indices, 0, 1, 2);
UpdateBounds(min_pt, max_pt, vertices);
vertices += 3;
AddFlexFace(vertices, src_positions, normals.data(), -radius,
flat_shading, indices, 0, 2, 1);
AddFaceUvs(vertices, src_uvs, tex_indices, 0, 2, 1);
UpdateBounds(min_pt, max_pt, vertices);
vertices += 3;
}
for (int s = 0; s < model->flex_shellnum[flex_id]; ++s) {
const int* indices = sdata + (s * dim);
AddSideFace(vertices, src_positions, normals.data(), radius, indices, 0,
1);
AddFaceUvs(vertices, src_uvs, indices, 0, 1, 1);
UpdateBounds(min_pt, max_pt, vertices);
vertices += 3;
AddSideFace(vertices, src_positions, normals.data(), -radius, indices, 1,
0);
AddFaceUvs(vertices, src_uvs, indices, 1, 0, 0);
UpdateBounds(min_pt, max_pt, vertices);
vertices += 3;
}
} else if (dim == 3) {
for (int s = 0; s < model->flex_shellnum[flex_id]; ++s) {
const int* indices = sdata + s * dim;
AddFlexFace(vertices, src_positions, normals.data(), radius,
flat_shading, indices, 0, 1, 2);
AddFaceUvs(vertices, src_uvs, indices, 0, 1, 2);
UpdateBounds(min_pt, max_pt, vertices);
vertices += 3;
}
}
}
template <typename T>
static void FillSkinVertices(T* vertices, const mjModel* model,
const mjData* data, int skin_id, float3* min_pt,
float3* max_pt) {
const int vertadr = model->skin_vertadr[skin_id];
const int faceadr = model->skin_faceadr[skin_id];
const int facenum = model->skin_facenum[skin_id];
const int boneadr = model->skin_boneadr[skin_id];
const int bonenum = model->skin_bonenum[skin_id];
// Accumulate positions from all bones.
for (int bone_idx = boneadr; bone_idx < boneadr + bonenum; ++bone_idx) {
mjtNum bind_pos[3] = {(mjtNum)model->skin_bonebindpos[3 * bone_idx + 0],
(mjtNum)model->skin_bonebindpos[3 * bone_idx + 1],
(mjtNum)model->skin_bonebindpos[3 * bone_idx + 2]};
mjtNum bind_quat[4] = {(mjtNum)model->skin_bonebindquat[4 * bone_idx + 0],
(mjtNum)model->skin_bonebindquat[4 * bone_idx + 1],
(mjtNum)model->skin_bonebindquat[4 * bone_idx + 2],
(mjtNum)model->skin_bonebindquat[4 * bone_idx + 3]};
const int body_id = model->skin_bonebodyid[bone_idx];
const mjtNum* body_quat = data->xquat + 4 * body_id;
const mjtNum* body_pos = data->xpos + 3 * body_id;
// Apply the bone's current pose to the bind pose.
mjtNum neg_bind_quat[4];
mju_negQuat(neg_bind_quat, bind_quat);
mjtNum quat[4];
mju_mulQuat(quat, body_quat, neg_bind_quat);
mjtNum rotate[9];
mju_quat2Mat(rotate, quat);
mjtNum translate[3];
mju_mulMatVec3(translate, rotate, bind_pos);
mju_sub3(translate, body_pos, translate);
// Apply the bone's position to all vertices "connected" to the bone by
// the weighting of the bone to the vertex.
const int bonevertadr = model->skin_bonevertadr[bone_idx];
const int bonevertnum = model->skin_bonevertnum[bone_idx];
for (int i = bonevertadr; i < bonevertadr + bonevertnum; ++i) {
const int vertex_id = model->skin_bonevertid[i];
const mjtNum base_pos[3] = {
(mjtNum)model->skin_vert[3 * (vertadr + vertex_id) + 0],
(mjtNum)model->skin_vert[3 * (vertadr + vertex_id) + 1],
(mjtNum)model->skin_vert[3 * (vertadr + vertex_id) + 2],
};
mjtNum unweighted_pos[3];
mju_mulMatVec3(unweighted_pos, rotate, base_pos);
mju_addTo3(unweighted_pos, translate);
const float weight = model->skin_bonevertweight[i];
vertices[vertex_id].position.x += weight * (float)unweighted_pos[0];
vertices[vertex_id].position.y += weight * (float)unweighted_pos[1];
vertices[vertex_id].position.z += weight * (float)unweighted_pos[2];
}
}
// Compute normals for each face. For now, we'll store the normals in the
// xyz components of the orientation field.
for (int i = faceadr; i < faceadr + facenum; ++i) {
const int i0 = model->skin_face[(3 * i) + 0];
const int i1 = model->skin_face[(3 * i) + 1];
const int i2 = model->skin_face[(3 * i) + 2];
T& v0 = vertices[i0];
T& v1 = vertices[i1];
T& v2 = vertices[i2];
const float3 vec01 = v1.position - v0.position;
const float3 vec02 = v2.position - v0.position;
const float4 normal = float4(cross(vec01, vec02), 0);
v0.orientation += normal;
v1.orientation += normal;
v2.orientation += normal;
}
const float* uvs = nullptr;
if (model->skin_texcoordadr[skin_id] >= 0) {
const int uaddr = model->skin_texcoordadr[skin_id];
uvs = model->skin_texcoord + (2 * uaddr);
}
// Perform final adjustments/corrections on all the vertices.
const float inflate = model->skin_inflate[skin_id];
const size_t num_vertices = model->skin_vertnum[skin_id];
for (int i = 0; i < num_vertices; ++i) {
// Ensure normals are normalized.
vertices[i].orientation = normalize(vertices[i].orientation);
// Inflate the vertex position in direction of normal (if applicable).
if (inflate != 0.0f) {
vertices[i].position += inflate * vertices[i].orientation.xyz;
}
// Convert the normals into orientations.
vertices[i].orientation = CalculateOrientation(vertices[i].orientation.xyz);
// Assign uvs (if applicable).
if constexpr (T::kHasUv) {
vertices[i].uv = ReadFloat2(uvs, i);
}
// Calculate the bounds of the vertex buffer.
*min_pt = min(*min_pt, vertices[i].position);
*max_pt = max(*max_pt, vertices[i].position);
}
}
static void PrepareMeshData(mjrfMeshConfig* config, mjrfMeshData* data,
int num_vertices, bool has_uvs) {
mjrf_defaultMeshConfig(config);
config->max_vertices = num_vertices;
config->num_attributes = has_uvs ? 3 : 2;
config->interleaved = true;
config->primitive_type = mjMESH_PRIMITIVE_TYPE_TRIANGLES;
config->max_indices = num_vertices;
config->index_type = mjINDEX_TYPE_U32;
config->attributes[0].usage = mjVERTEX_ATTRIBUTE_USAGE_POSITION;
config->attributes[0].type = mjVERTEX_ATTRIBUTE_TYPE_FLOAT3;
config->attributes[1].usage = mjVERTEX_ATTRIBUTE_USAGE_TANGENTS;
config->attributes[1].type = mjVERTEX_ATTRIBUTE_TYPE_FLOAT4;
if (has_uvs) {
config->attributes[2].usage = mjVERTEX_ATTRIBUTE_USAGE_UV;
config->attributes[2].type = mjVERTEX_ATTRIBUTE_TYPE_FLOAT2;
}
mjrf_defaultMeshData(data);
if (has_uvs) {
const int nbytes = sizeof(VertexWithUv) * num_vertices;
data->user_data = new char[nbytes];
std::memset(data->user_data, 0, nbytes);
} else {
const int nbytes = sizeof(VertexNoUv) * num_vertices;
data->user_data = new char[nbytes];
std::memset(data->user_data, 0, nbytes);
}
data->release = [](void* user_data) {
delete[] (char*)(user_data);
};
char* buf = reinterpret_cast<char*>(data->user_data);
data->num_vertices = num_vertices;
data->vertices[0] = buf;
data->vertices[1] = buf + sizeof(float[3]);
if (has_uvs) {
data->vertices[2] = buf + sizeof(float[7]);
}
data->num_indices = num_vertices;
data->indices = nullptr;
}
static void SetBounds(mjrfMeshData* mesh_data, const float3& min_pt,
const float3& max_pt) {
mesh_data->bounds_min[0] = min_pt.x;
mesh_data->bounds_min[1] = min_pt.y;
mesh_data->bounds_min[2] = min_pt.z;
mesh_data->bounds_max[0] = max_pt.x;
mesh_data->bounds_max[1] = max_pt.y;
mesh_data->bounds_max[2] = max_pt.z;
}
UniquePtr<mjrfMesh> CreateFlexMesh(mjrfContext* ctx, const mjModel* model,
const mjData* data, int flex_id) {
const int dim = model->flex_dim[flex_id];
int num_faces = 0;
if (dim == 2) {
num_faces += (2 * model->flex_elemnum[flex_id]);
num_faces += (2 * model->flex_shellnum[flex_id]);
} else if (dim == 3) {
num_faces += model->flex_shellnum[flex_id];
} else {
// 1D flex objects should be rendered as a collection of capsules.
mju_error("Unsupported flex dimension: %d", dim);
}
const int num_vertices = num_faces * 3;
const bool has_uvs = model->flex_texcoordadr[flex_id] >= 0;
mjrfMeshConfig mesh_config;
mjrfMeshData mesh_data;
PrepareMeshData(&mesh_config, &mesh_data, num_vertices, has_uvs);
float3 min_pt = float3(FLT_MAX);
float3 max_pt = float3(FLT_MIN);
if (has_uvs) {
VertexWithUv* vertices = (VertexWithUv*)(mesh_data.user_data);
FillFlexVertices(vertices, model, data, flex_id, &min_pt, &max_pt);
} else {
VertexNoUv* vertices = (VertexNoUv*)(mesh_data.user_data);
FillFlexVertices(vertices, model, data, flex_id, &min_pt, &max_pt);
}
SetBounds(&mesh_data, min_pt, max_pt);
auto mesh = CreateMesh(ctx, mesh_config);
mjrf_setMeshData(mesh.get(), &mesh_data);
return mesh;
}
UniquePtr<mjrfMesh> CreateSkinMesh(mjrfContext* ctx, const mjModel* model,
const mjData* data, int skin_id) {
const int num_vertices = model->skin_vertnum[skin_id];
const bool has_uvs = model->skin_texcoordadr[skin_id] >= 0;
mjrfMeshConfig mesh_config;
mjrfMeshData mesh_data;
PrepareMeshData(&mesh_config, &mesh_data, num_vertices, has_uvs);
float3 min_pt = float3(FLT_MAX);
float3 max_pt = float3(FLT_MIN);
if (has_uvs) {
VertexWithUv* vertices = (VertexWithUv*)(mesh_data.user_data);
FillSkinVertices(vertices, model, data, skin_id, &min_pt, &max_pt);
} else {
VertexNoUv* vertices = (VertexNoUv*)(mesh_data.user_data);
FillSkinVertices(vertices, model, data, skin_id, &min_pt, &max_pt);
}
mesh_data.num_indices = 3 * model->skin_facenum[skin_id];
mesh_data.indices = model->skin_face + 3 * model->skin_faceadr[skin_id];
SetBounds(&mesh_data, min_pt, max_pt);
auto mesh = CreateMesh(ctx, mesh_config);
mjrf_setMeshData(mesh.get(), &mesh_data);
return mesh;
}
void GatherSpatialTendonPoints(const mjModel* model, const mjData* data,
int tendon_id, std::vector<float4>& points) {
mjtNum length = 0.f;
const bool is_catenary = mjv_isCatenary(model, data, tendon_id, &length);
if (is_catenary) {
const int max_segments = mjMIN(model->vis.quality.numslices + 1, 100);
mjtNum x0[3];
mju_copy3(x0, data->wrap_xpos + 3 * data->ten_wrapadr[tendon_id] + 0);
mjtNum x1[3];
mju_copy3(x1, data->wrap_xpos + 3 * data->ten_wrapadr[tendon_id] + 3);
const float width = model->tendon_width[tendon_id];
mjtNum pts[3 * 100];
const int npoints =
mjv_catenary(x0, x1, model->opt.gravity, length, pts, max_segments);
for (int j = 0; j < npoints - 1; ++j) {
points.emplace_back(ReadFloat3(pts, j), width);
points.emplace_back(ReadFloat3(pts, j + 1), width);
}
} else {
const int adr = data->ten_wrapadr[tendon_id];
const int num = data->ten_wrapnum[tendon_id];
for (int j = adr; j < adr + num - 1; j++) {
if (data->wrap_obj[j] == -2 || data->wrap_obj[j + 1] == -2) {
continue;
}
float width = model->tendon_width[tendon_id];
if (data->wrap_obj[j] >= 0 && data->wrap_obj[j + 1] >= 0) {
width *= 0.5;
}
points.emplace_back(ReadFloat3(data->wrap_xpos, j + 0), width);
points.emplace_back(ReadFloat3(data->wrap_xpos, j + 1), width);
}
}
}
} // namespace mujoco