From 54dd62365058b96043224aaccc2a802b6509bf50 Mon Sep 17 00:00:00 2001 From: Alessio Quaglino Date: Thu, 22 Jan 2026 04:54:11 -0800 Subject: [PATCH] Add vertex-based flex constraints (dim=2). PiperOrigin-RevId: 859552050 Change-Id: I61d4b9dc40f041c5b930e5f8810a803e8bccb87a --- doc/includes/references.h | 10 +- include/mujoco/mjdata.h | 2 + include/mujoco/mjmodel.h | 7 +- include/mujoco/mjspec.h | 1 + include/mujoco/mjxmacro.h | 9 +- python/mujoco/introspect/structs.py | 65 ++++- src/engine/engine_core_smooth.c | 319 +++++++++++++++++++++++++ src/engine/engine_io.c | 9 +- src/engine/engine_io.h | 17 +- src/user/user_mesh.cc | 10 +- src/user/user_model.cc | 30 ++- src/user/user_model.h | 3 +- test/engine/engine_core_smooth_test.cc | 201 ++++++++++++++++ unity/Runtime/Bindings/MjBindings.cs | 9 +- wasm/codegen/generated/bindings.cc | 35 +++ 15 files changed, 705 insertions(+), 22 deletions(-) diff --git a/doc/includes/references.h b/doc/includes/references.h index 4251e296..7f2d46b9 100644 --- a/doc/includes/references.h +++ b/doc/includes/references.h @@ -274,6 +274,8 @@ struct mjData_ { mjtNum* flexelem_aabb; // flex element bounding boxes (center, size) (nflexelem x 6) mjtNum* flexedge_J; // flex edge Jacobian (nJfe x 1) mjtNum* flexedge_length; // flex edge lengths (nflexedge x 1) + mjtNum* flexvert_J; // flex vertex Jacobian (nJfv x 2) + mjtNum* flexvert_length; // flex vertex lengths (nflexvert x 2) mjtNum* bvh_aabb_dyn; // global bounding box (center, size) (nbvhdynamic x 6) // computed by mj_fwdPosition/mj_tendon @@ -1034,6 +1036,7 @@ struct mjModel_ { int nflexevpair; // number of element-vertex pairs in all flexes int nflextexcoord; // number of vertices with texture coordinates int nJfe; // number of non-zeros in sparse flexedge Jacobian matrix + int nJfv; // number of non-zeros in sparse flexvert Jacobian matrix int nmesh; // number of meshes int nmeshvert; // number of vertices in all meshes int nmeshnormal; // number of normals in all meshes @@ -1328,12 +1331,13 @@ struct mjModel_ { mjtNum* flexedge_length0; // edge lengths in qpos0 (nflexedge x 1) mjtNum* flexedge_invweight0; // edge inv. weight in qpos0 (nflexedge x 1) mjtNum* flex_radius; // radius around primitive element (nflex x 1) + mjtNum* flex_size; // vertex bounding box half sizes in qpos0 (nflex x 3) mjtNum* flex_stiffness; // finite element stiffness matrix (nflexelem x 21) mjtNum* flex_bending; // bending stiffness (nflexedge x 17) mjtNum* flex_damping; // Rayleigh's damping coefficient (nflex x 1) mjtNum* flex_edgestiffness; // edge stiffness (nflex x 1) mjtNum* flex_edgedamping; // edge damping (nflex x 1) - mjtByte* flex_edgeequality; // is edge equality constraint defined (nflex x 1) + int* flex_edgeequality; // 0: none, 1: edges, 2: vertices (nflex x 1) mjtByte* flex_rigid; // are all vertices in the same body (nflex x 1) mjtByte* flexedge_rigid; // are both edge vertices in same body (nflexedge x 1) mjtByte* flex_centered; // are all vertex coordinates (0,0,0) (nflex x 1) @@ -1343,6 +1347,9 @@ struct mjModel_ { int* flexedge_J_rownnz; // number of non-zeros in Jacobian row (nflexedge x 1) int* flexedge_J_rowadr; // row start address in colind array (nflexedge x 1) int* flexedge_J_colind; // column indices in sparse Jacobian (nJfe x 1) + int* flexvert_J_rownnz; // number of non-zeros in Jacobian row (nflexvert x 2) + int* flexvert_J_rowadr; // row start address in colind array (nflexvert x 2) + int* flexvert_J_colind; // column indices in sparse Jacobian (nJfv x 2) float* flex_rgba; // rgba when material is omitted (nflex x 4) float* flex_texcoord; // vertex texture coordinates (nflextexcoord x 2) @@ -2186,6 +2193,7 @@ typedef struct mjsFlex_ { // flex specification // other properties int dim; // element dimensionality double radius; // radius around primitive element + double size[3]; // vertex bounding box half sizes in qpos0 mjtByte internal; // enable internal collisions mjtByte flatskin; // render flex skin with flat shading int selfcollide; // mode for flex self collision diff --git a/include/mujoco/mjdata.h b/include/mujoco/mjdata.h index 202823cc..c96cd6cc 100644 --- a/include/mujoco/mjdata.h +++ b/include/mujoco/mjdata.h @@ -308,6 +308,8 @@ struct mjData_ { mjtNum* flexelem_aabb; // flex element bounding boxes (center, size) (nflexelem x 6) mjtNum* flexedge_J; // flex edge Jacobian (nJfe x 1) mjtNum* flexedge_length; // flex edge lengths (nflexedge x 1) + mjtNum* flexvert_J; // flex vertex Jacobian (nJfv x 2) + mjtNum* flexvert_length; // flex vertex lengths (nflexvert x 2) mjtNum* bvh_aabb_dyn; // global bounding box (center, size) (nbvhdynamic x 6) // computed by mj_fwdPosition/mj_tendon diff --git a/include/mujoco/mjmodel.h b/include/mujoco/mjmodel.h index c951de0b..ef9b390c 100644 --- a/include/mujoco/mjmodel.h +++ b/include/mujoco/mjmodel.h @@ -702,6 +702,7 @@ struct mjModel_ { int nflexevpair; // number of element-vertex pairs in all flexes int nflextexcoord; // number of vertices with texture coordinates int nJfe; // number of non-zeros in sparse flexedge Jacobian matrix + int nJfv; // number of non-zeros in sparse flexvert Jacobian matrix int nmesh; // number of meshes int nmeshvert; // number of vertices in all meshes int nmeshnormal; // number of normals in all meshes @@ -996,12 +997,13 @@ struct mjModel_ { mjtNum* flexedge_length0; // edge lengths in qpos0 (nflexedge x 1) mjtNum* flexedge_invweight0; // edge inv. weight in qpos0 (nflexedge x 1) mjtNum* flex_radius; // radius around primitive element (nflex x 1) + mjtNum* flex_size; // vertex bounding box half sizes in qpos0 (nflex x 3) mjtNum* flex_stiffness; // finite element stiffness matrix (nflexelem x 21) mjtNum* flex_bending; // bending stiffness (nflexedge x 17) mjtNum* flex_damping; // Rayleigh's damping coefficient (nflex x 1) mjtNum* flex_edgestiffness; // edge stiffness (nflex x 1) mjtNum* flex_edgedamping; // edge damping (nflex x 1) - mjtByte* flex_edgeequality; // is edge equality constraint defined (nflex x 1) + int* flex_edgeequality; // 0: none, 1: edges, 2: vertices (nflex x 1) mjtByte* flex_rigid; // are all vertices in the same body (nflex x 1) mjtByte* flexedge_rigid; // are both edge vertices in same body (nflexedge x 1) mjtByte* flex_centered; // are all vertex coordinates (0,0,0) (nflex x 1) @@ -1011,6 +1013,9 @@ struct mjModel_ { int* flexedge_J_rownnz; // number of non-zeros in Jacobian row (nflexedge x 1) int* flexedge_J_rowadr; // row start address in colind array (nflexedge x 1) int* flexedge_J_colind; // column indices in sparse Jacobian (nJfe x 1) + int* flexvert_J_rownnz; // number of non-zeros in Jacobian row (nflexvert x 2) + int* flexvert_J_rowadr; // row start address in colind array (nflexvert x 2) + int* flexvert_J_colind; // column indices in sparse Jacobian (nJfv x 2) float* flex_rgba; // rgba when material is omitted (nflex x 4) float* flex_texcoord; // vertex texture coordinates (nflextexcoord x 2) diff --git a/include/mujoco/mjspec.h b/include/mujoco/mjspec.h index b44a8711..af487864 100644 --- a/include/mujoco/mjspec.h +++ b/include/mujoco/mjspec.h @@ -438,6 +438,7 @@ typedef struct mjsFlex_ { // flex specification // other properties int dim; // element dimensionality double radius; // radius around primitive element + double size[3]; // vertex bounding box half sizes in qpos0 mjtByte internal; // enable internal collisions mjtByte flatskin; // render flex skin with flat shading int selfcollide; // mode for flex self collision diff --git a/include/mujoco/mjxmacro.h b/include/mujoco/mjxmacro.h index 94f8ad01..606ce95b 100644 --- a/include/mujoco/mjxmacro.h +++ b/include/mujoco/mjxmacro.h @@ -97,6 +97,7 @@ X( nflexevpair ) \ X( nflextexcoord ) \ X( nJfe ) \ + X( nJfv ) \ X( nmesh ) \ X( nmeshvert ) \ X( nmeshnormal ) \ @@ -386,12 +387,13 @@ X ( mjtNum, flexedge_length0, nflexedge, 1 ) \ X ( mjtNum, flexedge_invweight0, nflexedge, 1 ) \ X ( mjtNum, flex_radius, nflex, 1 ) \ + X ( mjtNum, flex_size, nflex, 3 ) \ X ( mjtNum, flex_stiffness, nflexelem, 21 ) \ X ( mjtNum, flex_bending, nflexedge, 17 ) \ X ( mjtNum, flex_damping, nflex, 1 ) \ X ( mjtNum, flex_edgestiffness, nflex, 1 ) \ X ( mjtNum, flex_edgedamping, nflex, 1 ) \ - X ( mjtByte, flex_edgeequality, nflex, 1 ) \ + X ( int, flex_edgeequality, nflex, 1 ) \ X ( mjtByte, flex_rigid, nflex, 1 ) \ X ( mjtByte, flexedge_rigid, nflexedge, 1 ) \ X ( mjtByte, flex_centered, nflex, 1 ) \ @@ -401,6 +403,9 @@ X ( int, flexedge_J_rownnz, nflexedge, 1 ) \ X ( int, flexedge_J_rowadr, nflexedge, 1 ) \ X ( int, flexedge_J_colind, nJfe, 1 ) \ + X ( int, flexvert_J_rownnz, nflexvert, 2 ) \ + X ( int, flexvert_J_rowadr, nflexvert, 2 ) \ + X ( int, flexvert_J_colind, nJfv, 2 ) \ X ( float, flex_rgba, nflex, 4 ) \ X ( float, flex_texcoord, nflextexcoord, 2 ) @@ -736,6 +741,8 @@ X ( mjtNum, flexelem_aabb, nflexelem, 6 ) \ X ( mjtNum, flexedge_J, nJfe, 1 ) \ X ( mjtNum, flexedge_length, nflexedge, 1 ) \ + X ( mjtNum, flexvert_J, nJfv, 2 ) \ + X ( mjtNum, flexvert_length, nflexvert, 2 ) \ X ( mjtNum, bvh_aabb_dyn, nbvhdynamic, 6 ) \ X ( int, ten_wrapadr, ntendon, 1 ) \ X ( int, ten_wrapnum, ntendon, 1 ) \ diff --git a/python/mujoco/introspect/structs.py b/python/mujoco/introspect/structs.py index f518de53..be3f54a8 100644 --- a/python/mujoco/introspect/structs.py +++ b/python/mujoco/introspect/structs.py @@ -997,6 +997,11 @@ STRUCTS: Mapping[str, StructDecl] = dict([ type=ValueType(name='int'), doc='number of non-zeros in sparse flexedge Jacobian matrix', ), + StructFieldDecl( + name='nJfv', + type=ValueType(name='int'), + doc='number of non-zeros in sparse flexvert Jacobian matrix', + ), StructFieldDecl( name='nmesh', type=ValueType(name='int'), @@ -2880,6 +2885,14 @@ STRUCTS: Mapping[str, StructDecl] = dict([ doc='radius around primitive element', array_extent=('nflex',), ), + StructFieldDecl( + name='flex_size', + type=PointerType( + inner_type=ValueType(name='mjtNum'), + ), + doc='vertex bounding box half sizes in qpos0', + array_extent=('nflex', 3), + ), StructFieldDecl( name='flex_stiffness', type=PointerType( @@ -2923,9 +2936,9 @@ STRUCTS: Mapping[str, StructDecl] = dict([ StructFieldDecl( name='flex_edgeequality', type=PointerType( - inner_type=ValueType(name='mjtByte'), + inner_type=ValueType(name='int'), ), - doc='is edge equality constraint defined', + doc='0: none, 1: edges, 2: vertices', array_extent=('nflex',), ), StructFieldDecl( @@ -3000,6 +3013,30 @@ STRUCTS: Mapping[str, StructDecl] = dict([ doc='column indices in sparse Jacobian', array_extent=('nJfe',), ), + StructFieldDecl( + name='flexvert_J_rownnz', + type=PointerType( + inner_type=ValueType(name='int'), + ), + doc='number of non-zeros in Jacobian row', + array_extent=('nflexvert', 2), + ), + StructFieldDecl( + name='flexvert_J_rowadr', + type=PointerType( + inner_type=ValueType(name='int'), + ), + doc='row start address in colind array', + array_extent=('nflexvert', 2), + ), + StructFieldDecl( + name='flexvert_J_colind', + type=PointerType( + inner_type=ValueType(name='int'), + ), + doc='column indices in sparse Jacobian', + array_extent=('nJfv', 2), + ), StructFieldDecl( name='flex_rgba', type=PointerType( @@ -5609,6 +5646,22 @@ STRUCTS: Mapping[str, StructDecl] = dict([ doc='flex edge lengths', array_extent=('nflexedge',), ), + StructFieldDecl( + name='flexvert_J', + type=PointerType( + inner_type=ValueType(name='mjtNum'), + ), + doc='flex vertex Jacobian', + array_extent=('nJfv', 2), + ), + StructFieldDecl( + name='flexvert_length', + type=PointerType( + inner_type=ValueType(name='mjtNum'), + ), + doc='flex vertex lengths', + array_extent=('nflexvert', 2), + ), StructFieldDecl( name='bvh_aabb_dyn', type=PointerType( @@ -7879,6 +7932,14 @@ STRUCTS: Mapping[str, StructDecl] = dict([ type=ValueType(name='double'), doc='radius around primitive element', ), + StructFieldDecl( + name='size', + type=ArrayType( + inner_type=ValueType(name='double'), + extents=(3,), + ), + doc='vertex bounding box half sizes in qpos0', + ), StructFieldDecl( name='internal', type=ValueType(name='mjtByte'), diff --git a/src/engine/engine_core_smooth.c b/src/engine/engine_core_smooth.c index 2926bb73..37344cac 100644 --- a/src/engine/engine_core_smooth.c +++ b/src/engine/engine_core_smooth.c @@ -537,6 +537,7 @@ void mj_updateDynamicBVH(const mjModel* m, mjData* d, int bvhadr, int bvhnum) { void mj_flex(const mjModel* m, mjData* d) { int nv = m->nv; int* rowadr = m->flexedge_J_rowadr, *rownnz = m->flexedge_J_rownnz; + int* vrowadr = m->flexvert_J_rowadr, *vrownnz = m->flexvert_J_rownnz; // skip if no flexes if (!m->nflex) { @@ -659,6 +660,9 @@ void mj_flex(const mjModel* m, mjData* d) { // clear Jacobian mju_zeroInt(rowadr, m->nflexedge); mju_zeroInt(rownnz, m->nflexedge); + mju_zeroInt(vrowadr, 2*m->nflexvert); + mju_zeroInt(vrownnz, 2*m->nflexvert); + mju_zero(d->flexvert_J, 2*m->nJfv); // compute lengths and Jacobians of edges for (int f=0; f < m->nflex; f++) { @@ -715,6 +719,321 @@ void mj_flex(const mjModel* m, mjData* d) { rownnz[ebase+e] = NV; mju_copyInt(m->flexedge_J_colind + rowadr[ebase+e], chain, NV); } + + // if dim=2 and constraints are active we use the vertex-based constraint defined in + // Chen, Kry, and Vouga, "Locking-free Simulation of Isometric Thin Plates", 2019. + if (m->flex_dim[f] == 2 && m->flex_edgeequality[f] == 2) { + int nvert = m->flex_vertnum[f]; + mjtNum edge1[3], edge2[3], normal[3]; + mjtNum quat[4], mat[9]; + int t_adr, t0, t1, t2; + + mj_markStack(d); + int* buf_ind = mjSTACKALLOC(d, nv, int); + + // compute normal from first element + t_adr = m->flex_elemdataadr[f]; + t0 = m->flex_elem[t_adr]; + t1 = m->flex_elem[t_adr+1]; + t2 = m->flex_elem[t_adr+2]; + + mju_sub3(edge1, m->flex_vert0 + 3*(vbase+t1), m->flex_vert0 + 3*(vbase+t0)); + mju_sub3(edge2, m->flex_vert0 + 3*(vbase+t2), m->flex_vert0 + 3*(vbase+t0)); + mji_cross(normal, edge1, edge2); + mju_normalize3(normal); + + // compute rotation to Z + mju_quatZ2Vec(quat, normal); + mju_quat2Mat(mat, quat); + + // compute edge vectors + mjtNum* edge_dx = mjSTACKALLOC(d, 3*m->flex_edgenum[f], mjtNum); + mjtNum* edge_dy = mjSTACKALLOC(d, 3*m->flex_edgenum[f], mjtNum); + for (int e=0; e < m->flex_edgenum[f]; e++) { + int v1 = m->flex_edge[2*(ebase+e)]; + int v2 = m->flex_edge[2*(ebase+e)+1]; + mjtNum dx3[3]; + mju_sub3(dx3, m->flex_vert0 + 3*(vbase+v2), m->flex_vert0 + 3*(vbase+v1)); + dx3[0] *= 2*m->flex_size[3*f+0]; + dx3[1] *= 2*m->flex_size[3*f+1]; + dx3[2] *= 2*m->flex_size[3*f+2]; + mji_mulMatTVec3(edge_dx+3*e, mat, dx3); + if (mju_abs((edge_dx+3*e)[2]) > mjMINVAL) { + mjERROR("flex vertices are not in the same plane"); // SHOULD NOT OCCUR + } + mju_sub3(edge_dy+3*e, d->flexvert_xpos+3*(vbase+v2), d->flexvert_xpos+3*(vbase+v1)); + } + + // build vertex adjacency list + int* v_edge_cnt = mjSTACKALLOC(d, nvert, int); + int* v_edge_adr = mjSTACKALLOC(d, nvert, int); + int* adj_edges = mjSTACKALLOC(d, 2*m->flex_edgenum[f], int); + mju_zeroInt(v_edge_cnt, nvert); + for (int e = 0; e < m->flex_edgenum[f]; ++e) { + v_edge_cnt[m->flex_edge[2*(ebase+e)+0]]++; + v_edge_cnt[m->flex_edge[2*(ebase+e)+1]]++; + } + int total_adj_edges = 0; + for (int v = 0; v < nvert; ++v) { + v_edge_adr[v] = total_adj_edges; + total_adj_edges += v_edge_cnt[v]; + } + int* v_edge_fill = mjSTACKALLOC(d, nvert, int); + mju_zeroInt(v_edge_fill, nvert); + for (int e = 0; e < m->flex_edgenum[f]; ++e) { + int v1 = m->flex_edge[2*(ebase+e)+0]; + int v2 = m->flex_edge[2*(ebase+e)+1]; + adj_edges[v_edge_adr[v1] + v_edge_fill[v1]] = e; + v_edge_fill[v1]++; + adj_edges[v_edge_adr[v2] + v_edge_fill[v2]] = e; + v_edge_fill[v2]++; + } + + mjtNum* F_vert = mjSTACKALLOC(d, 6*nvert, mjtNum); + mjtNum* Binv_vert = mjSTACKALLOC(d, 4*nvert, mjtNum); + + // compute averaged Cauchy strain tensors for each vertex + for (int v=0; v < nvert; v++) { + mjtNum A[6] = {0}, B[4] = {0}; + int k, edge_idx; + + for (k=0; kflex_edge[2 * (ebase + edge_idx)]; + int v2 = m->flex_edge[2 * (ebase + edge_idx) + 1]; + int neighbor_v = (v == v1) ? v2 : v1; + int b_neighbor = m->flex_vertbodyid[vbase + neighbor_v]; + if (b_neighbor >= 0) { + weight = m->body_mass[b_neighbor]; + if (weight < mjMINVAL) weight = mjMINVAL; + } + + // accumulate A += w * dy * dx', B += w * dx * dx' + for (int row=0; row < 3; row++) { + for (int col=0; col < 2; col++) { + A[2 * row + col] += weight * dy[row] * dx[col]; + } + } + for (int row=0; row < 2; row++) { + for (int col=0; col < 2; col++) { + B[2 * row + col] += weight * dx[row] * dx[col]; + } + } + } + + int vadr = vbase+v; + mjtNum* F = F_vert + 6*v; + mjtNum* Binv = Binv_vert + 4*v; + mjtNum cauchy[2][2]; + + // compute Binv = B^-1 + mjtNum det = B[0]*B[3] - B[1]*B[2]; + if (mju_abs(det) < mjMINVAL) { + mju_zero(Binv, 4); + } else { + mjtNum invdet = 1/det; + Binv[0] = B[3]*invdet; + Binv[1] = -B[1]*invdet; + Binv[2] = -B[2]*invdet; + Binv[3] = B[0]*invdet; + } + + // compute deformation gradient F = A * Binv + mju_mulMatMat(F, A, Binv, 3, 2, 2); + + // compute Cauchy strain tensor F^T F + cauchy[0][0] = F[0]*F[0] + F[2]*F[2] + F[4]*F[4]; + cauchy[0][1] = F[0]*F[1] + F[2]*F[3] + F[4]*F[5]; + cauchy[1][0] = F[1]*F[0] + F[3]*F[2] + F[5]*F[4]; + cauchy[1][1] = F[1]*F[1] + F[3]*F[3] + F[5]*F[5]; + + // compute tensor invariants + d->flexvert_length[2*vadr+0] = cauchy[0][0] + cauchy[1][1] - 2; + d->flexvert_length[2*vadr+1] = cauchy[0][0] * cauchy[1][1] - + cauchy[0][1] * cauchy[1][0] - 1; + } + + // 1st pass: compute vrownnz + int* chain1 = mjSTACKALLOC(d, nv, int); + int* chain2 = mjSTACKALLOC(d, nv, int); + + // determine start address for this flex + int v0_base = 2*vbase; + int current_adr = 0; + if (v0_base > 0) { + current_adr = vrowadr[v0_base - 1] + vrownnz[v0_base - 1]; + } + vrowadr[v0_base] = current_adr; + + for (int v=0; vflex_edge[2*(ebase+e)]; + int v2 = m->flex_edge[2*(ebase+e)+1]; + + // chains from edge e + int b1 = m->flex_vertbodyid[vbase+v1]; + int b2 = m->flex_vertbodyid[vbase+v2]; + int NV1 = mj_bodyChain(m, b1, chain1); + int NV2 = mj_bodyChain(m, b2, chain2); + + for (int j=0; jnflexvert) { + vrowadr[row1+1] = vrowadr[row1] + current_nnz; + } + + // fill colind + int count = 0; + for (int j=0; jflexvert_J_colind[vrowadr[row0]+count] = j; + m->flexvert_J_colind[vrowadr[row1]+count] = j; + count++; + } + } + } + + // 2nd pass: clear Jacobian and assemble vertex by vertex + mjtNum* J0_dense = mjSTACKALLOC(d, nv, mjtNum); + mjtNum* J1_dense = mjSTACKALLOC(d, nv, mjtNum); + mjtNum dI1dy1[3], dI1dy2[3], FB[6]; + mjtNum dI2dy1[3], dI2dy2[3]; + mjtNum cauchy[4], adj[4], Fadj[6], FadjBinv[6], dI2dy[3]; + + for (int v=0; vflex_edge[2*(ebase+e)]; + int v2 = m->flex_edge[2*(ebase+e)+1]; + + // reuse precomputed edge vector + mjtNum* dx = edge_dx + 3 * e; + + // get mass of neighbor vertex + mjtNum weight = 1.0; + int neighbor_v = (v == v1) ? v2 : v1; + int b_neighbor = m->flex_vertbodyid[vbase + neighbor_v]; + if (b_neighbor >= 0) { + weight = m->body_mass[b_neighbor]; + if (weight < mjMINVAL) weight = mjMINVAL; + } + + // dI1/dy1, dI1/dy2 (scaled by weight) + mju_mulMatVec(dI1dy1, FB, dx, 3, 2); + mju_scl3(dI1dy1, dI1dy1, -2 * weight); + mju_scl3(dI1dy2, dI1dy1, -1); // dI1dy2 = -dI1dy1 + + // dI2/dy1, dI2/dy2 (scaled by weight) + mju_mulMatVec(dI2dy, FadjBinv, dx, 3, 2); + mju_scl3(dI2dy1, dI2dy, -2 * weight); + mju_scl3(dI2dy2, dI2dy1, -1); // dI2dy2 = -dI2dy1 + + // get endpoint Jacobians + int b1 = m->flex_vertbodyid[vbase+v1]; + int b2 = m->flex_vertbodyid[vbase+v2]; + int NV1 = mj_bodyChain(m, b1, chain1); + mj_jacSparse(m, d, jac1, NULL, d->flexvert_xpos + 3*(vbase+v1), b1, NV1, chain1); + int NV2 = mj_bodyChain(m, b2, chain2); + mj_jacSparse(m, d, jac2, NULL, d->flexvert_xpos + 3*(vbase+v2), b2, NV2, chain2); + + // accumulate dense Jacobians for vertex v + for (int j=0; jflexvert_J[vrowadr[row0]+j] += J0_dense[m->flexvert_J_colind[vrowadr[row0]+j]]; + } + int row1 = 2*(vbase+v)+1; + int nnz1 = vrownnz[row1]; + for (int j = 0; j < nnz1; j++) { + d->flexvert_J[vrowadr[row1] + j] += + J1_dense[m->flexvert_J_colind[vrowadr[row1] + j]]; + } + + // mass scaling: scale constraint by sqrt(mass) to improve condition + // number + int b = m->flex_vertbodyid[vbase + v]; + if (b >= 0) { + mjtNum mass = m->body_mass[b]; + if (mass > mjMINVAL) { + mjtNum scale = mju_sqrt(mass); + d->flexvert_length[2 * (vbase + v) + 0] *= scale; + d->flexvert_length[2 * (vbase + v) + 1] *= scale; + + nnz0 = vrownnz[row0]; + for (int j = 0; j < nnz0; j++) { + d->flexvert_J[vrowadr[row0] + j] *= scale; + } + nnz1 = vrownnz[row1]; + for (int j = 0; j < nnz1; j++) { + d->flexvert_J[vrowadr[row1] + j] *= scale; + } + } + } + } + + mj_freeStack(d); + } } mj_freeStack(d); diff --git a/src/engine/engine_io.c b/src/engine/engine_io.c index 902361b5..ed728f91 100644 --- a/src/engine/engine_io.c +++ b/src/engine/engine_io.c @@ -220,7 +220,7 @@ void mj_makeModel(mjModel** dest, int nbvhstatic, int nbvhdynamic, int noct, int njnt, int ntree, int nM, int nB, int nC, int nD, int ngeom, int nsite, int ncam, int nlight, int nflex, int nflexnode, int nflexvert, int nflexedge, int nflexelem, int nflexelemdata, - int nflexelemedge, int nflexshelldata, int nflexevpair, int nflextexcoord, int nJfe, + int nflexelemedge, int nflexshelldata, int nflexevpair, int nflextexcoord, int nJfe, int nJfv, int nmesh, int nmeshvert, int nmeshnormal, int nmeshtexcoord, int nmeshface, int nmeshgraph, int nmeshpoly, int nmeshpolyvert, int nmeshpolymap, int nskin, int nskinvert, int nskintexvert, int nskinface, @@ -279,6 +279,7 @@ void mj_makeModel(mjModel** dest, m->nflexevpair = nflexevpair; m->nflextexcoord = nflextexcoord; m->nJfe = nJfe; + m->nJfv = nJfv; m->nmesh = nmesh; m->nmeshvert = nmeshvert; m->nmeshnormal = nmeshnormal; @@ -406,8 +407,8 @@ mjModel* mj_copyModel(mjModel* dest, const mjModel* src) { src->nbvhstatic, src->nbvhdynamic, src->noct, src->njnt, src->ntree, src->nM, src->nB, src->nC, src->nD, src->ngeom, src->nsite, src->ncam, src->nlight, src->nflex, src->nflexnode, src->nflexvert, src->nflexedge, - src->nflexelem, src->nflexelemdata, src->nflexelemedge, - src->nflexshelldata, src->nflexevpair, src->nflextexcoord, src->nJfe, src->nmesh, + src->nflexelem, src->nflexelemdata, src->nflexelemedge, src->nflexshelldata, + src->nflexevpair, src->nflextexcoord, src->nJfe, src->nJfv, src->nmesh, src->nmeshvert, src->nmeshnormal, src->nmeshtexcoord, src->nmeshface, src->nmeshgraph, src->nmeshpoly, src->nmeshpolyvert, src->nmeshpolymap, src->nskin, src->nskinvert, src->nskintexvert, src->nskinface, @@ -598,7 +599,7 @@ mjModel* mj_loadModelBuffer(const void* buffer, int buffer_sz) { ints[49], ints[50], ints[51], ints[52], ints[53], ints[54], ints[55], ints[56], ints[57], ints[58], ints[59], ints[60], ints[61], ints[62], ints[63], ints[64], ints[65], ints[66], ints[67], ints[68], ints[69], - ints[70], ints[71], ints[72], ints[73], ints[74], ints[75]); + ints[70], ints[71], ints[72], ints[73], ints[74], ints[75], ints[76]); // read mjModel mjtSize fields mjtSize sizes[8]; diff --git a/src/engine/engine_io.h b/src/engine/engine_io.h index e4a8e355..d7101e69 100644 --- a/src/engine/engine_io.h +++ b/src/engine/engine_io.h @@ -51,14 +51,15 @@ void mj_makeModel(mjModel** dest, int nq, int nv, int nu, int na, int nbody, int nbvh, int nbvhstatic, int nbvhdynamic, int noct, int njnt, int ntree, int nM, int nB, int nC, int nD, int ngeom, int nsite, int ncam, int nlight, int nflex, int nflexnode, int nflexvert, int nflexedge, int nflexelem, int nflexelemdata, - int nJfe, int nflexelemedge, int nflexshelldata, int nflexevpair, int nflextexcoord, int nmesh, - int nmeshvert, int nmeshnormal, int nmeshtexcoord, int nmeshface, int nmeshgraph, int nmeshpoly, - int nmeshpolyvert, int nmeshpolymap, int nskin, int nskinvert, int nskintexvert, int nskinface, - int nskinbone, int nskinbonevert, int nhfield, int nhfielddata, int ntex, int ntexdata, - int nmat, int npair, int nexclude, int neq, int ntendon, int nwrap, int nsensor, int nnumeric, - int nnumericdata, int ntext, int ntextdata, int ntuple, int ntupledata, int nkey, int nmocap, - int nplugin, int npluginattr, int nuser_body, int nuser_jnt, int nuser_geom, int nuser_site, - int nuser_cam, int nuser_tendon, int nuser_actuator, int nuser_sensor, int nnames, int npaths); + int nflexelemedge, int nflexshelldata, int nflexevpair, int nflextexcoord, int nJfe, int nJfv, + int nmesh, int nmeshvert, int nmeshnormal, int nmeshtexcoord, int nmeshface, int nmeshgraph, + int nmeshpoly, int nmeshpolyvert, int nmeshpolymap, int nskin, int nskinvert, int nskintexvert, + int nskinface, int nskinbone, int nskinbonevert, int nhfield, int nhfielddata, int ntex, + int ntexdata, int nmat, int npair, int nexclude, int neq, int ntendon, int nwrap, int nsensor, + int nnumeric, int nnumericdata, int ntext, int ntextdata, int ntuple, int ntupledata, int nkey, + int nmocap, int nplugin, int npluginattr, int nuser_body, int nuser_jnt, int nuser_geom, + int nuser_site, int nuser_cam, int nuser_tendon, int nuser_actuator, int nuser_sensor, + int nnames, int npaths); // copy mjModel; allocate new if dest is NULL MJAPI mjModel* mj_copyModel(mjModel* dest, const mjModel* src); diff --git a/src/user/user_mesh.cc b/src/user/user_mesh.cc index 8c46840b..000c298b 100644 --- a/src/user/user_mesh.cc +++ b/src/user/user_mesh.cc @@ -4392,10 +4392,16 @@ void mjCFlex::Compile(const mjVFS* vfs) { // compute bounding box coordinates vert0_.assign(3*nvert, 0); const mjtNum* bvh = tree.Bvh().data(); + size[0] = bvh[3] - radius; + size[1] = bvh[4] - radius; + size[2] = bvh[5] - radius; for (int j=0; j < nvert; j++) { for (int k=0; k < 3; k++) { - double size = 2*(bvh[k+3] - radius); - vert0_[3*j+k] = (vertxpos[3*j+k] - bvh[k]) / size + 0.5; + if (size[k] > mjMINVAL) { + vert0_[3*j+k] = (vertxpos[3*j+k] - bvh[k]) / (2*size[k]) + 0.5; + } else { + vert0_[3*j+k] = 0.5; + } } } diff --git a/src/user/user_model.cc b/src/user/user_model.cc index 3bba27bf..4c2c8677 100644 --- a/src/user/user_model.cc +++ b/src/user/user_model.cc @@ -1155,6 +1155,7 @@ void mjCModel::Clear() { nflexevpair = 0; nflextexcoord = 0; nJfe = 0; + nJfv = 0; nmeshvert = 0; nmeshnormal = 0; nmeshtexcoord = 0; @@ -2166,6 +2167,31 @@ void mjCModel::SetSizes() { nJfe += b->dofnum; } } + + // compute nJfv + std::vector> adj(flexes_[i]->nvert); + for (const auto& edge : flexes_[i]->edge) { + adj[edge.first].push_back(edge.second); + adj[edge.second].push_back(edge.first); + } + for (int j=0; j < flexes_[i]->nvert; j++) { + std::unordered_set vert_bodies; + vert_bodies.insert(flexes_[i]->vertbodyid[j]); + for (int neighbor : adj[j]) { + vert_bodies.insert(flexes_[i]->vertbodyid[neighbor]); + } + std::unordered_set bodies_in_jac; + for (int body_id : vert_bodies) { + mjCBody* b = bodies_[body_id]; + while (b) { + bodies_in_jac.insert(b); + b = b->parent; + } + } + for (mjCBody* b : bodies_in_jac) { + nJfv += b->dofnum; + } + } } // mesh counts @@ -3288,6 +3314,7 @@ void mjCModel::CopyObjects(mjModel* m) { mjuu_copyvec(m->flex_solref + mjNREF * i, pfl->solref, mjNREF); mjuu_copyvec(m->flex_solimp + mjNIMP * i, pfl->solimp, mjNIMP); m->flex_radius[i] = (mjtNum)pfl->radius; + mjuu_copyvec(m->flex_size + 3 * i, pfl->size, 3); mjuu_copyvec(m->flex_friction + 3 * i, pfl->friction, 3); m->flex_margin[i] = (mjtNum)pfl->margin; m->flex_gap[i] = (mjtNum)pfl->gap; @@ -3363,6 +3390,7 @@ void mjCModel::CopyObjects(mjModel* m) { m->flex_edgeequality[i] = 1; break; } + // TODO: support flex_edgeequality = 2 } // copy bvh data (flex aabb computed dynamically in mjData) @@ -4856,7 +4884,7 @@ void mjCModel::TryCompile(mjModel*& m, mjData*& d, const mjVFS* vfs) { mj_makeModel(&m, nq, nv, nu, na, nbody, nbvh, nbvhstatic, nbvhdynamic, noct, njnt, ntree, nM, nB, nC, nD, ngeom, nsite, ncam, nlight, nflex, nflexnode, nflexvert, nflexedge, nflexelem, - nflexelemdata, nflexelemedge, nflexshelldata, nflexevpair, nflextexcoord, nJfe, + nflexelemdata, nflexelemedge, nflexshelldata, nflexevpair, nflextexcoord, nJfe, nJfv, nmesh, nmeshvert, nmeshnormal, nmeshtexcoord, nmeshface, nmeshgraph, nmeshpoly, nmeshpolyvert, nmeshpolymap, nskin, nskinvert, nskintexvert, nskinface, nskinbone, nskinbonevert, nhfield, nhfielddata, ntex, ntexdata, nmat, npair, nexclude, diff --git a/src/user/user_model.h b/src/user/user_model.h index a4af02f5..27b9ee48 100644 --- a/src/user/user_model.h +++ b/src/user/user_model.h @@ -99,7 +99,8 @@ class mjCModel_ : public mjsElement { int nflexshelldata; // number of shell fragment vertex ids in all flexes int nflexevpair; // number of element-vertex pairs in all flexes int nflextexcoord; // number of vertex texture coordinates in all flexes - int nJfe; // number of non-zeros in sparse flex constraint Jacobian + int nJfe; // number of non-zeros in sparse flex edge constraint Jacobian + int nJfv; // number of non-zeros in sparse flex vertex constraint Jacobian int nmeshvert; // number of vertices in all meshes int nmeshnormal; // number of normals in all meshes int nmeshtexcoord; // number of texture coordinates in all meshes diff --git a/test/engine/engine_core_smooth_test.cc b/test/engine/engine_core_smooth_test.cc index 017ff64e..c08e0ffa 100644 --- a/test/engine/engine_core_smooth_test.cc +++ b/test/engine/engine_core_smooth_test.cc @@ -41,6 +41,7 @@ using ::testing::Eq; using ::testing::Pointwise; using ::testing::DoubleNear; using ::testing::NotNull; +using ::testing::Not; using CoreSmoothTest = MujocoTest; @@ -876,5 +877,205 @@ TEST_F(CoreSmoothTest, FactorIs) { mj_deleteModel(m); } +TEST_F(CoreSmoothTest, FlexVertLengthScaling) { + constexpr char xml[] = R"( + + + )"; + char error[1024]; + mjModel* m = LoadModelFromString(xml, error, sizeof(error)); + ASSERT_THAT(m, NotNull()) << error; + mjData* d = mj_makeData(m); + + // check that nJfv is correct: + // corner vertices: 2 * (1+3) * 3 + 2 * (1+2) * 3 = 42 + // edge vertices: 4 * (1+4) * 3 = 60 + // center vertex: 1 * (1+6) * 3 = 21 + // nJfv = 42 + 60 + 21 = 123 + EXPECT_EQ(m->nJfv, 123); + + // Set edge equality to 2 + m->flex_edgeequality[0] = 2; + + // Run kinematics to populate xpos/xmat initially + mj_fwdKinematics(m, d); + + // Check invariants for scale=1 + // The constraints should be satisfied + int nvert = m->flex_vertnum[0]; + ASSERT_EQ(nvert, 9); + for (int i=0; i < nvert; i++) { + EXPECT_NEAR(d->flexvert_length[2*i+0], 0.0, 1e-5); + EXPECT_NEAR(d->flexvert_length[2*i+1], 0.0, 1e-5); + } + + // set qvel to rigid rotation + ASSERT_EQ(m->nv, 3*nvert); + mju_zero(d->qvel, m->nv); + for (int i=0; i < nvert; i++) { + const mjtNum* p = d->xpos + 3*m->flex_vertbodyid[i]; + d->qvel[3*i+0] = -p[1]; + d->qvel[3*i+1] = p[0]; + d->qvel[3*i+2] = 1.0; + } + + // check that Jacobian times velocity is zero for rigid body motion + vector Jv(2*nvert, 0); + for (int i=0; i < 2*nvert; i++) { + int row_start = m->flexvert_J_rowadr[i]; + int row_nnz = m->flexvert_J_rownnz[i]; + for (int j=0; j < row_nnz; j++) { + Jv[i] += d->flexvert_J[row_start + j] * + d->qvel[m->flexvert_J_colind[row_start + j]]; + } + } + EXPECT_THAT(Jv, Each(DoubleNear(0.0, 1e-9))); + + // check sparsity pattern + int corners[] = {0, 2, 6, 8}; + int edges[] = {1, 3, 5, 7}; + int center[] = {4}; + for (int i : corners) { + EXPECT_EQ(m->flexvert_J_rownnz[2*i+0], (i == 0 || i == 8) ? 12 : 9); + EXPECT_EQ(m->flexvert_J_rownnz[2*i+1], (i == 0 || i == 8) ? 12 : 9); + } + for (int i : edges) { + EXPECT_EQ(m->flexvert_J_rownnz[2*i+0], 15); + EXPECT_EQ(m->flexvert_J_rownnz[2*i+1], 15); + } + for (int i : center) { + EXPECT_EQ(m->flexvert_J_rownnz[2*i+0], 21); + EXPECT_EQ(m->flexvert_J_rownnz[2*i+1], 21); + } + + // check rowadr + EXPECT_EQ(m->flexvert_J_rowadr[0], 0); + for (int i=1; i < 2*nvert; i++) { + EXPECT_EQ(m->flexvert_J_rowadr[i], + m->flexvert_J_rowadr[i-1] + m->flexvert_J_rownnz[i-1]); + } + + // check that colind are sorted and unique + int nnzJ = 0; + for (int i = 0; i < 2*nvert; i++) { + nnzJ += m->flexvert_J_rownnz[i]; + } + EXPECT_EQ(nnzJ, 2*m->nJfv); + for (int i=0; i < 2*nvert; i++) { + int row_start = m->flexvert_J_rowadr[i]; + int row_nnz = m->flexvert_J_rownnz[i]; + for (int j=0; j < row_nnz-1; j++) { + EXPECT_LE(m->flexvert_J_colind[row_start+j], + m->flexvert_J_colind[row_start+j+1]); + } + } + + // Finite-difference check for flexvert_J + auto fd_check = [&](double tolerance) { + std::vector qpos0(m->nq); + mju_copy(qpos0.data(), d->qpos, m->nq); + mj_kinematics(m, d); + mj_flex(m, d); + + mjtNum eps = 1e-7; + int nflexvert = m->flex_vertnum[0]; + std::vector jac_fd(2 * nflexvert * m->nv); + std::vector qpos_backup(m->nq); + mju_copy(qpos_backup.data(), d->qpos, m->nq); + + for (int i=0; i < m->nv; ++i) { + std::vector qvel(m->nv, 0); + qvel[i] = 1.0; + + // plus + mju_copy(d->qpos, qpos_backup.data(), m->nq); + mj_integratePos(m, d->qpos, qvel.data(), eps); + mj_kinematics(m, d); + mj_flex(m, d); + std::vector L_plus(2 * nflexvert); + for (int e = 0; e < 2 * nflexvert; ++e) { + L_plus[e] = d->flexvert_length[e]; + } + + // minus + mju_copy(d->qpos, qpos_backup.data(), m->nq); + mj_integratePos(m, d->qpos, qvel.data(), -eps); + mj_kinematics(m, d); + mj_flex(m, d); + std::vector L_minus(2 * nflexvert); + for (int e = 0; e < 2 * nflexvert; ++e) { + L_minus[e] = d->flexvert_length[e]; + } + + for (int e = 0; e < 2 * nflexvert; ++e) { + jac_fd[e*m->nv + i] = (L_plus[e] - L_minus[e]) / (2*eps); + } + } + mju_copy(d->qpos, qpos_backup.data(), m->nq); + mj_kinematics(m, d); + mj_flex(m, d); + + // Compare with analytic + std::vector jac_analytic(2 * nflexvert * m->nv); + mju_zero(jac_analytic.data(), 2 * nflexvert * m->nv); + for (int e = 0; e < 2 * nflexvert; ++e) { + int row_start = m->flexvert_J_rowadr[e]; + int row_nnz = m->flexvert_J_rownnz[e]; + for (int i = 0; i < row_nnz; ++i) { + jac_analytic[e*m->nv + m->flexvert_J_colind[row_start+i]] = + d->flexvert_J[row_start+i]; + } + } + EXPECT_THAT(jac_analytic, Not(Each(Eq(0)))); + EXPECT_THAT(jac_analytic, Pointwise(DoubleNear(tolerance), jac_fd)); + + mju_copy(d->qpos, qpos0.data(), m->nq); + mj_kinematics(m, d); + mj_flex(m, d); + }; + + fd_check(5e-5); + + // Set qpos to put flex in scale=2 configuration. + for (int i=0; i < nvert; i++) { + d->qpos[3*i+0] = d->xpos[3*(i+1)+0]; + d->qpos[3*i+1] = d->xpos[3*(i+1)+1]; + d->qpos[3*i+2] = d->xpos[3*(i+1)+2]; + } + mj_fwdKinematics(m, d); + + // Get mass scaling factor + mjtNum scale = 1.0; + int b = m->flex_vertbodyid[0]; + if (b >= 0 && m->body_mass[b] > mjMINVAL) { + scale = mju_sqrt(m->body_mass[b]); + } + + // Check invariants for scale=2 + // F should be [2, 2]. C = F'F = 4I. + // Strain E = C - I = 3I. + // Invariant 0: Trace(E) = 3 + 3 = 6 + // Invariant 1: Det(C) - 1 = 4 * 4 - 1 = 15 + for (int i=0; i < nvert; i++) { + EXPECT_NEAR(d->flexvert_length[2 * i + 0], 6.0 * scale, 1e-5); + EXPECT_NEAR(d->flexvert_length[2 * i + 1], 15.0 * scale, 1e-5); + } + + // Perturb z-positions so configuration is not flat + for (int i=0; i < nvert; i++) { + d->qpos[3*i+2] += 0.01 * (i%2 ? 1 : -1); + } + fd_check(5e-5); + + mj_deleteData(d); + mj_deleteModel(m); +} + } // namespace } // namespace mujoco diff --git a/unity/Runtime/Bindings/MjBindings.cs b/unity/Runtime/Bindings/MjBindings.cs index dc0e9e1f..fe515c12 100644 --- a/unity/Runtime/Bindings/MjBindings.cs +++ b/unity/Runtime/Bindings/MjBindings.cs @@ -4983,6 +4983,8 @@ public unsafe struct mjData_ { public double* flexelem_aabb; public double* flexedge_J; public double* flexedge_length; + public double* flexvert_J; + public double* flexvert_length; public double* bvh_aabb_dyn; public int* ten_wrapadr; public int* ten_wrapnum; @@ -5314,6 +5316,7 @@ public unsafe struct mjModel_ { public int nflexevpair; public int nflextexcoord; public int nJfe; + public int nJfv; public int nmesh; public int nmeshvert; public int nmeshnormal; @@ -5572,12 +5575,13 @@ public unsafe struct mjModel_ { public double* flexedge_length0; public double* flexedge_invweight0; public double* flex_radius; + public double* flex_size; public double* flex_stiffness; public double* flex_bending; public double* flex_damping; public double* flex_edgestiffness; public double* flex_edgedamping; - public byte* flex_edgeequality; + public int* flex_edgeequality; public byte* flex_rigid; public byte* flexedge_rigid; public byte* flex_centered; @@ -5587,6 +5591,9 @@ public unsafe struct mjModel_ { public int* flexedge_J_rownnz; public int* flexedge_J_rowadr; public int* flexedge_J_colind; + public int* flexvert_J_rownnz; + public int* flexvert_J_rowadr; + public int* flexvert_J_colind; public float* flex_rgba; public float* flex_texcoord; public int* mesh_vertadr; diff --git a/wasm/codegen/generated/bindings.cc b/wasm/codegen/generated/bindings.cc index cd6231dc..4ad90672 100644 --- a/wasm/codegen/generated/bindings.cc +++ b/wasm/codegen/generated/bindings.cc @@ -2157,6 +2157,9 @@ struct MjsFlex { void set_radius(double value) { ptr_->radius = value; } + emscripten::val size() const { + return emscripten::val(emscripten::typed_memory_view(3, ptr_->size)); + } mjtByte internal() const { return ptr_->internal; } @@ -3585,6 +3588,12 @@ struct MjModel { void set_nJfe(int value) { ptr_->nJfe = value; } + int nJfv() const { + return ptr_->nJfv; + } + void set_nJfv(int value) { + ptr_->nJfv = value; + } int nmesh() const { return ptr_->nmesh; } @@ -4521,6 +4530,9 @@ struct MjModel { emscripten::val flex_radius() const { return emscripten::val(emscripten::typed_memory_view(ptr_->nflex, ptr_->flex_radius)); } + emscripten::val flex_size() const { + return emscripten::val(emscripten::typed_memory_view(ptr_->nflex * 3, ptr_->flex_size)); + } emscripten::val flex_stiffness() const { return emscripten::val(emscripten::typed_memory_view(ptr_->nflexelem * 21, ptr_->flex_stiffness)); } @@ -4566,6 +4578,15 @@ struct MjModel { emscripten::val flexedge_J_colind() const { return emscripten::val(emscripten::typed_memory_view(ptr_->nJfe, ptr_->flexedge_J_colind)); } + emscripten::val flexvert_J_rownnz() const { + return emscripten::val(emscripten::typed_memory_view(ptr_->nflexvert * 2, ptr_->flexvert_J_rownnz)); + } + emscripten::val flexvert_J_rowadr() const { + return emscripten::val(emscripten::typed_memory_view(ptr_->nflexvert * 2, ptr_->flexvert_J_rowadr)); + } + emscripten::val flexvert_J_colind() const { + return emscripten::val(emscripten::typed_memory_view(ptr_->nJfv * 2, ptr_->flexvert_J_colind)); + } emscripten::val flex_rgba() const { return emscripten::val(emscripten::typed_memory_view(ptr_->nflex * 4, ptr_->flex_rgba)); } @@ -6306,6 +6327,12 @@ struct MjData { emscripten::val flexedge_length() const { return emscripten::val(emscripten::typed_memory_view(model->nflexedge, ptr_->flexedge_length)); } + emscripten::val flexvert_J() const { + return emscripten::val(emscripten::typed_memory_view(model->nJfv * 2, ptr_->flexvert_J)); + } + emscripten::val flexvert_length() const { + return emscripten::val(emscripten::typed_memory_view(model->nflexvert * 2, ptr_->flexvert_length)); + } emscripten::val bvh_aabb_dyn() const { return emscripten::val(emscripten::typed_memory_view(model->nbvhdynamic * 6, ptr_->bvh_aabb_dyn)); } @@ -11019,6 +11046,8 @@ EMSCRIPTEN_BINDINGS(mujoco_bindings) { .property("flexedge_length", &MjData::flexedge_length) .property("flexedge_velocity", &MjData::flexedge_velocity) .property("flexelem_aabb", &MjData::flexelem_aabb) + .property("flexvert_J", &MjData::flexvert_J) + .property("flexvert_length", &MjData::flexvert_length) .property("flexvert_xpos", &MjData::flexvert_xpos) .property("geom_xmat", &MjData::geom_xmat) .property("geom_xpos", &MjData::geom_xpos) @@ -11334,6 +11363,7 @@ EMSCRIPTEN_BINDINGS(mujoco_bindings) { .property("flex_shell", &MjModel::flex_shell) .property("flex_shelldataadr", &MjModel::flex_shelldataadr) .property("flex_shellnum", &MjModel::flex_shellnum) + .property("flex_size", &MjModel::flex_size) .property("flex_solimp", &MjModel::flex_solimp) .property("flex_solmix", &MjModel::flex_solmix) .property("flex_solref", &MjModel::flex_solref) @@ -11351,6 +11381,9 @@ EMSCRIPTEN_BINDINGS(mujoco_bindings) { .property("flexedge_invweight0", &MjModel::flexedge_invweight0) .property("flexedge_length0", &MjModel::flexedge_length0) .property("flexedge_rigid", &MjModel::flexedge_rigid) + .property("flexvert_J_colind", &MjModel::flexvert_J_colind) + .property("flexvert_J_rowadr", &MjModel::flexvert_J_rowadr) + .property("flexvert_J_rownnz", &MjModel::flexvert_J_rownnz) .property("geom_aabb", &MjModel::geom_aabb) .property("geom_bodyid", &MjModel::geom_bodyid) .property("geom_conaffinity", &MjModel::geom_conaffinity) @@ -11477,6 +11510,7 @@ EMSCRIPTEN_BINDINGS(mujoco_bindings) { .property("nC", &MjModel::nC, &MjModel::set_nC, reference()) .property("nD", &MjModel::nD, &MjModel::set_nD, reference()) .property("nJfe", &MjModel::nJfe, &MjModel::set_nJfe, reference()) + .property("nJfv", &MjModel::nJfv, &MjModel::set_nJfv, reference()) .property("nJmom", &MjModel::nJmom, &MjModel::set_nJmom, reference()) .property("nM", &MjModel::nM, &MjModel::set_nM, reference()) .property("na", &MjModel::na, &MjModel::set_na, reference()) @@ -12048,6 +12082,7 @@ EMSCRIPTEN_BINDINGS(mujoco_bindings) { .property("radius", &MjsFlex::radius, &MjsFlex::set_radius, reference()) .property("rgba", &MjsFlex::rgba) .property("selfcollide", &MjsFlex::selfcollide, &MjsFlex::set_selfcollide, reference()) + .property("size", &MjsFlex::size) .property("solimp", &MjsFlex::solimp) .property("solmix", &MjsFlex::solmix, &MjsFlex::set_solmix, reference()) .property("solref", &MjsFlex::solref)