Add texture coordinates to primitive shape types.

PiperOrigin-RevId: 964882123
Change-Id: I17723d21f29b3fe28e171de5c3d2bea1f55a94c2
This commit is contained in:
Sam Haves
2026-08-14 14:10:32 -07:00
committed by Copybara-Service
parent 4929f2cd99
commit cc7fb98cf4
18 changed files with 552 additions and 368 deletions
@@ -28,19 +28,6 @@
namespace mujoco {
// Returns the tile size for infinite plane texture alignment.
// This is duplicated from engine_vis_visualize.c (re-center infinite plane)
// to ensure UV scaling matches the re-centering increments.
static float GetPlaneTileSize(const mjModel* model, int matid,
float texrepeat) {
if (matid >= 0 && texrepeat > 0) {
return 2.0f / texrepeat;
} else {
const float zfar = model->vis.map.zfar * model->stat.extent;
return 2.1f * zfar / (mjMAXPLANEGRID - 2);
}
}
static void PrepareGeomMeshes(mjrfRenderable* renderable, const mjvGeom& geom,
ModelObjects* model_objs,
SceneObjects* scene_objs) {
@@ -230,37 +217,31 @@ static void UpdateGeomMaterial(mjrfRenderable* renderable, const mjvGeom& geom,
}
}
if (tex_uniform) {
if (geom.size[0] > 0) {
material.uv_scale[0] *= geom.size[0];
}
if (geom.size[1] > 0) {
material.uv_scale[1] *= geom.size[1];
}
}
const bool is_infinite_plane =
geom.type == mjGEOM_PLANE && (geom.size[0] <= 0 || geom.size[1] <= 0);
if (is_infinite_plane) {
// Infinite planes are scaled to match the tile size used by
// re-centering in engine_vis_visualize.c.
const float plane_scale = static_cast<float>(mjMAXPLANEGRID) / 2.0f;
const float tile_size_x =
GetPlaneTileSize(model, geom.matid, tex_repeat[0]);
const float tile_size_y =
GetPlaneTileSize(model, geom.matid, tex_repeat[1]);
material.uv_scale[0] = 2.0f * plane_scale / tile_size_x;
material.uv_scale[1] = 2.0f * plane_scale / tile_size_y;
}
// We want to do the equivalent of:
// mjr_setf4(splane, 0.5 * scl.x, 0, 0, -0.5);
// mjr_setf4(tplane, 0, -0.5 * scl.y, 0, -0.5);
// glTexGenfv(GL_S, GL_OBJECT_PLANE, splane);
// glTexGenfv(GL_T, GL_OBJECT_PLANE, tplane);
material.uv_scale[0] = 0.5f * material.uv_scale[0];
material.uv_scale[1] = -0.5f * material.uv_scale[1];
material.uv_offset[0] = -0.5f;
material.uv_offset[1] = -0.5f;
const float dot_pos_axis_x = geom.pos[0] * geom.mat[0] +
geom.pos[1] * geom.mat[3] +
geom.pos[2] * geom.mat[6];
const float dot_pos_axis_y = geom.pos[0] * geom.mat[1] +
geom.pos[1] * geom.mat[4] +
geom.pos[2] * geom.mat[7];
material.uv_scale[0] *= plane_scale;
material.uv_scale[1] *= plane_scale;
// The vertex UVs in PlaneBuilder are u0 = 0.5*x + 0.5, v0 = -0.5*y + 0.5.
// To keep the world-space texture coordinate u = 0.5*worldX*texrepeat - 0.5
// independent of the snapped geomPos, uv_offset must compensate by 0.5*dot_pos.
material.uv_offset[0] =
(0.5f * dot_pos_axis_x - 0.5f * plane_scale) * tex_repeat[0] - 0.5f;
material.uv_offset[1] =
(-0.5f * dot_pos_axis_y - 0.5f * plane_scale) * tex_repeat[1] - 0.5f;
} else if (tex_uniform) {
material.uv_scale[0] *= (geom.size[0] ? geom.size[0] : 1.0f);
material.uv_scale[1] *= (geom.size[1] ? geom.size[1] : 1.0f);
}
} else {
// For cube maps, if `tex_uniform` is true, then scale the texture so that
// it covers a 1x1 area of world space rather than the area of the object.
+181 -120
View File
@@ -117,11 +117,19 @@ static void makePlane(const mjModel* m, mjrContext* con) {
glBegin(GL_QUADS);
glNormal3d(0, 0, 1);
double d = 2.0/m->vis.quality.numquads;
double nq = (double)m->vis.quality.numquads;
for (int x=0; x < m->vis.quality.numquads; x++) {
for (int y=0; y < m->vis.quality.numquads; y++) {
glTexCoord2d((double)x/nq, 1.0 - (double)y/nq);
glVertex3d(d*(x+0)-1, d*(y+0)-1, 0);
glTexCoord2d((double)(x+1)/nq, 1.0 - (double)y/nq);
glVertex3d(d*(x+1)-1, d*(y+0)-1, 0);
glTexCoord2d((double)(x+1)/nq, 1.0 - (double)(y+1)/nq);
glVertex3d(d*(x+1)-1, d*(y+1)-1, 0);
glTexCoord2d((double)x/nq, 1.0 - (double)(y+1)/nq);
glVertex3d(d*(x+0)-1, d*(y+1)-1, 0);
}
}
@@ -198,10 +206,27 @@ static void makePlane(const mjModel* m, mjrContext* con) {
// make grid
for (int x=0; x < nn[0]; x++) {
for (int y=0; y < nn[1]; y++) {
glVertex3d(grid[0][x+0], grid[1][y+0], 0);
glVertex3d(grid[0][x+1], grid[1][y+0], 0);
glVertex3d(grid[0][x+1], grid[1][y+1], 0);
glVertex3d(grid[0][x+0], grid[1][y+1], 0);
double u0, u1, v0, v1;
if (sz[0] > 0) {
u0 = (grid[0][x+0] + sz[0]) / (2.0 * sz[0]);
u1 = (grid[0][x+1] + sz[0]) / (2.0 * sz[0]);
} else {
u0 = 0.5 * grid[0][x+0];
u1 = 0.5 * grid[0][x+1];
}
if (sz[1] > 0) {
v0 = 1.0 - (grid[1][y+0] + sz[1]) / (2.0 * sz[1]);
v1 = 1.0 - (grid[1][y+1] + sz[1]) / (2.0 * sz[1]);
} else {
v0 = -0.5 * grid[1][y+0];
v1 = -0.5 * grid[1][y+1];
}
glTexCoord2d(u0, v0); glVertex3d(grid[0][x+0], grid[1][y+0], 0);
glTexCoord2d(u1, v0); glVertex3d(grid[0][x+1], grid[1][y+0], 0);
glTexCoord2d(u1, v1); glVertex3d(grid[0][x+1], grid[1][y+1], 0);
glTexCoord2d(u0, v1); glVertex3d(grid[0][x+0], grid[1][y+1], 0);
}
}
@@ -552,21 +577,26 @@ static void halfSphere(int sign, int nSlice, int nStack) {
n3[0] = n3[1] = 0;
n3[2] = sign;
float u1 = az1 / (2.0f*mjPI);
float v_el1 = (float)(nStack-1) / (float)nStack;
float u2 = az2 / (2.0f*mjPI);
float u3 = (az1 + az2) / 2.0f / (2.0f*mjPI);
float v3_uv = 1.0f;
if (sign > 0) {
v_el1 = 1.0f - v_el1;
v3_uv = 1.0f - v3_uv;
}
// make triangle
if (sign > 0) {
glNormal3fv(n1);
glVertex3fv(v1);
glNormal3fv(n2);
glVertex3fv(v2);
glNormal3fv(n3);
glVertex3fv(v3);
glTexCoord2f(u1, v_el1); glNormal3fv(n1); glVertex3fv(v1);
glTexCoord2f(u2, v_el1); glNormal3fv(n2); glVertex3fv(v2);
glTexCoord2f(u3, v3_uv); glNormal3fv(n3); glVertex3fv(v3);
} else {
glNormal3fv(n3);
glVertex3fv(v3);
glNormal3fv(n2);
glVertex3fv(v2);
glNormal3fv(n1);
glVertex3fv(v1);
glTexCoord2f(u3, v3_uv); glNormal3fv(n3); glVertex3fv(v3);
glTexCoord2f(u2, v_el1); glNormal3fv(n2); glVertex3fv(v2);
glTexCoord2f(u1, v_el1); glNormal3fv(n1); glVertex3fv(v1);
}
}
glEnd();
@@ -577,6 +607,14 @@ static void halfSphere(int sign, int nSlice, int nStack) {
el1 = (mjPI/2.0f * sign * (i+0)) / (float)nStack;
el2 = (mjPI/2.0f * sign * (i+1)) / (float)nStack;
float v_el1 = (float)(i+0) / (float)nStack;
float v_el2 = (float)(i+1) / (float)nStack;
if (sign > 0) {
v_el1 = 1.0f - v_el1;
v_el2 = 1.0f - v_el2;
}
for (int j=0; j < nSlice; j++) {
az1 = (2.0f*mjPI * (j+0)) / (float)nSlice;
az2 = (2.0f*mjPI * (j+1)) / (float)nSlice;
@@ -587,25 +625,20 @@ static void halfSphere(int sign, int nSlice, int nStack) {
setVertexSphere(v3, n3, az2, el2, sign);
setVertexSphere(v4, n4, az1, el2, sign);
float u1 = az1 / (2.0f*mjPI);
float u2 = az2 / (2.0f*mjPI);
// make quad
if (sign > 0) {
glNormal3fv(n1);
glVertex3fv(v1);
glNormal3fv(n2);
glVertex3fv(v2);
glNormal3fv(n3);
glVertex3fv(v3);
glNormal3fv(n4);
glVertex3fv(v4);
glTexCoord2f(u1, v_el1); glNormal3fv(n1); glVertex3fv(v1);
glTexCoord2f(u2, v_el1); glNormal3fv(n2); glVertex3fv(v2);
glTexCoord2f(u2, v_el2); glNormal3fv(n3); glVertex3fv(v3);
glTexCoord2f(u1, v_el2); glNormal3fv(n4); glVertex3fv(v4);
} else {
glNormal3fv(n4);
glVertex3fv(v4);
glNormal3fv(n3);
glVertex3fv(v3);
glNormal3fv(n2);
glVertex3fv(v2);
glNormal3fv(n1);
glVertex3fv(v1);
glTexCoord2f(u1, v_el2); glNormal3fv(n4); glVertex3fv(v4);
glTexCoord2f(u2, v_el2); glNormal3fv(n3); glVertex3fv(v3);
glTexCoord2f(u2, v_el1); glNormal3fv(n2); glVertex3fv(v2);
glTexCoord2f(u1, v_el1); glNormal3fv(n1); glVertex3fv(v1);
}
}
}
@@ -624,6 +657,9 @@ static void sphere(int nSlice, int nStack) {
glBegin(GL_TRIANGLES);
for (int sign=-1; sign <= 1; sign+=2) {
el1 = (0.5*mjPI * sign * (nStack/2-1)) / (float)(nStack/2);
float v_el1 = 0.5f - sign * (float)(nStack/2-1) / (float)nStack;
float v3_uv = sign > 0 ? 0.0f : 1.0f;
for (int j=0; j < nSlice; j++) {
az1 = (2.0f*mjPI * (j+0.0f)) / (float)nSlice;
az2 = (2.0f*mjPI * (j+1.0f)) / (float)nSlice;
@@ -636,21 +672,19 @@ static void sphere(int nSlice, int nStack) {
n3[0] = n3[1] = 0;
n3[2] = sign;
float u1 = az1 / (2.0f*mjPI);
float u2 = az2 / (2.0f*mjPI);
float u3 = (az1 + az2) / 2.0f / (2.0f*mjPI);
// make triangle
if (sign > 0) {
glNormal3fv(n1);
glVertex3fv(v1);
glNormal3fv(n2);
glVertex3fv(v2);
glNormal3fv(n3);
glVertex3fv(v3);
glTexCoord2f(u1, v_el1); glNormal3fv(n1); glVertex3fv(v1);
glTexCoord2f(u2, v_el1); glNormal3fv(n2); glVertex3fv(v2);
glTexCoord2f(u3, v3_uv); glNormal3fv(n3); glVertex3fv(v3);
} else {
glNormal3fv(n3);
glVertex3fv(v3);
glNormal3fv(n2);
glVertex3fv(v2);
glNormal3fv(n1);
glVertex3fv(v1);
glTexCoord2f(u3, v3_uv); glNormal3fv(n3); glVertex3fv(v3);
glTexCoord2f(u2, v_el1); glNormal3fv(n2); glVertex3fv(v2);
glTexCoord2f(u1, v_el1); glNormal3fv(n1); glVertex3fv(v1);
}
}
}
@@ -663,6 +697,9 @@ static void sphere(int nSlice, int nStack) {
el1 = (0.5*mjPI * sign * (i+0)) / (float)(nStack/2);
el2 = (0.5*mjPI * sign * (i+1)) / (float)(nStack/2);
float v_el1 = 0.5f - sign * (float)(i+0) / (float)nStack;
float v_el2 = 0.5f - sign * (float)(i+1) / (float)nStack;
for (int j=0; j < nSlice; j++) {
az1 = (2.0f*mjPI * (j+0)) / (float)nSlice;
az2 = (2.0f*mjPI * (j+1)) / (float)nSlice;
@@ -673,25 +710,20 @@ static void sphere(int nSlice, int nStack) {
setVertexSphere(v3, n3, az2, el2, 0);
setVertexSphere(v4, n4, az1, el2, 0);
float u1 = az1 / (2.0f*mjPI);
float u2 = az2 / (2.0f*mjPI);
// make quad
if (sign > 0) {
glNormal3fv(n1);
glVertex3fv(v1);
glNormal3fv(n2);
glVertex3fv(v2);
glNormal3fv(n3);
glVertex3fv(v3);
glNormal3fv(n4);
glVertex3fv(v4);
glTexCoord2f(u1, v_el1); glNormal3fv(n1); glVertex3fv(v1);
glTexCoord2f(u2, v_el1); glNormal3fv(n2); glVertex3fv(v2);
glTexCoord2f(u2, v_el2); glNormal3fv(n3); glVertex3fv(v3);
glTexCoord2f(u1, v_el2); glNormal3fv(n4); glVertex3fv(v4);
} else {
glNormal3fv(n4);
glVertex3fv(v4);
glNormal3fv(n3);
glVertex3fv(v3);
glNormal3fv(n2);
glVertex3fv(v2);
glNormal3fv(n1);
glVertex3fv(v1);
glTexCoord2f(u1, v_el2); glNormal3fv(n4); glVertex3fv(v4);
glTexCoord2f(u2, v_el2); glNormal3fv(n3); glVertex3fv(v3);
glTexCoord2f(u2, v_el1); glNormal3fv(n2); glVertex3fv(v2);
glTexCoord2f(u1, v_el1); glNormal3fv(n1); glVertex3fv(v1);
}
}
}
@@ -729,15 +761,22 @@ static void disk(int sign, int nSlice, int nStack) {
v3[0] = v3[1] = 0;
v3[2] = sign;
float u1 = 0.5f + 0.5f * v1[0];
float v1_uv = 0.5f + 0.5f * v1[1];
float u2 = 0.5f + 0.5f * v2[0];
float v2_uv = 0.5f + 0.5f * v2[1];
float u3 = 0.5f;
float v3_uv = 0.5f;
// make triangle
if (sign > 0) {
glVertex3fv(v1);
glVertex3fv(v2);
glVertex3fv(v3);
glTexCoord2f(u1, v1_uv); glVertex3fv(v1);
glTexCoord2f(u2, v2_uv); glVertex3fv(v2);
glTexCoord2f(u3, v3_uv); glVertex3fv(v3);
} else {
glVertex3fv(v3);
glVertex3fv(v2);
glVertex3fv(v1);
glTexCoord2f(u3, v3_uv); glVertex3fv(v3);
glTexCoord2f(u2, v2_uv); glVertex3fv(v2);
glTexCoord2f(u1, v1_uv); glVertex3fv(v1);
}
}
glEnd();
@@ -759,17 +798,26 @@ static void disk(int sign, int nSlice, int nStack) {
setVertexDisk(v3, az2, r1, sign);
setVertexDisk(v4, az1, r1, sign);
float u1 = 0.5f + 0.5f * v1[0];
float v1_uv = 0.5f + 0.5f * v1[1];
float u2 = 0.5f + 0.5f * v2[0];
float v2_uv = 0.5f + 0.5f * v2[1];
float u3 = 0.5f + 0.5f * v3[0];
float v3_uv = 0.5f + 0.5f * v3[1];
float u4 = 0.5f + 0.5f * v4[0];
float v4_uv = 0.5f + 0.5f * v4[1];
// make quad
if (sign > 0) {
glVertex3fv(v1);
glVertex3fv(v2);
glVertex3fv(v3);
glVertex3fv(v4);
glTexCoord2f(u1, v1_uv); glVertex3fv(v1);
glTexCoord2f(u2, v2_uv); glVertex3fv(v2);
glTexCoord2f(u3, v3_uv); glVertex3fv(v3);
glTexCoord2f(u4, v4_uv); glVertex3fv(v4);
} else {
glVertex3fv(v4);
glVertex3fv(v3);
glVertex3fv(v2);
glVertex3fv(v1);
glTexCoord2f(u4, v4_uv); glVertex3fv(v4);
glTexCoord2f(u3, v3_uv); glVertex3fv(v3);
glTexCoord2f(u2, v2_uv); glVertex3fv(v2);
glTexCoord2f(u1, v1_uv); glVertex3fv(v1);
}
}
}
@@ -817,13 +865,14 @@ static void cone(int nSlice, int nStack) {
n3[2] = n1[2]+n2[2];
mjr_normalizeVec(n3);
float u1 = (float)j / (float)nSlice;
float u2 = (float)(j+1) / (float)nSlice;
float u3 = (u1 + u2) / 2.0f;
// make triangle
glNormal3fv(n1);
glVertex3fv(v1);
glNormal3fv(n2);
glVertex3fv(v2);
glNormal3fv(n3);
glVertex3fv(v3);
glTexCoord2f(u1, r1); glNormal3fv(n1); glVertex3fv(v1);
glTexCoord2f(u2, r1); glNormal3fv(n2); glVertex3fv(v2);
glTexCoord2f(u3, 0.0f); glNormal3fv(n3); glVertex3fv(v3);
}
glEnd();
@@ -843,15 +892,14 @@ static void cone(int nSlice, int nStack) {
setVertexCone(v3, n3, az2, r1);
setVertexCone(v4, n4, az1, r1);
float u1 = (float)j / (float)nSlice;
float u2 = (float)(j+1) / (float)nSlice;
// make quad
glNormal3fv(n1);
glVertex3fv(v1);
glNormal3fv(n2);
glVertex3fv(v2);
glNormal3fv(n3);
glVertex3fv(v3);
glNormal3fv(n4);
glVertex3fv(v4);
glTexCoord2f(u1, r2); glNormal3fv(n1); glVertex3fv(v1);
glTexCoord2f(u2, r2); glNormal3fv(n2); glVertex3fv(v2);
glTexCoord2f(u2, r1); glNormal3fv(n3); glVertex3fv(v3);
glTexCoord2f(u1, r1); glNormal3fv(n4); glVertex3fv(v4);
}
}
glEnd();
@@ -883,6 +931,9 @@ static void cylinder(int nSlice, int nStack) {
h1 = 2*(i+0)/(float)nStack - 1;
h2 = 2*(i+1)/(float)nStack - 1;
float v_el1 = (1.0f - h1) / 2.0f;
float v_el2 = (1.0f - h2) / 2.0f;
for (int j=0; j < nSlice; j++) {
az1 = (2.0f*mjPI * (j+0)) / (float)nSlice;
az2 = (2.0f*mjPI * (j+1)) / (float)nSlice;
@@ -893,15 +944,14 @@ static void cylinder(int nSlice, int nStack) {
setVertexCylinder(v3, n3, az2, h2);
setVertexCylinder(v4, n4, az1, h2);
float u1 = (float)j / (float)nSlice;
float u2 = (float)(j+1) / (float)nSlice;
// make quad
glNormal3fv(n1);
glVertex3fv(v1);
glNormal3fv(n2);
glVertex3fv(v2);
glNormal3fv(n3);
glVertex3fv(v3);
glNormal3fv(n4);
glVertex3fv(v4);
glTexCoord2f(u1, v_el1); glNormal3fv(n1); glVertex3fv(v1);
glTexCoord2f(u2, v_el1); glNormal3fv(n2); glVertex3fv(v2);
glTexCoord2f(u2, v_el2); glNormal3fv(n3); glVertex3fv(v3);
glTexCoord2f(u1, v_el2); glNormal3fv(n4); glVertex3fv(v4);
}
}
glEnd();
@@ -1009,43 +1059,54 @@ static void makeBuiltin(const mjModel* m, mjrContext* con) {
// box
glNewList(con->baseBuiltin + mjrBOX, GL_COMPILE);
glBegin(GL_QUADS);
double nq = (double)numquads;
for (int x=0; x < numquads; x++) {
for (int y=0; y < numquads; y++) {
double u0_t = (double)x / nq;
double u1_t = (double)(x+1) / nq;
double v0_t = 1.0 - (double)y / nq;
double v1_t = 1.0 - (double)(y+1) / nq;
double u0_b = (double)x / nq;
double u1_b = (double)(x+1) / nq;
double v0_b = 1.0 - (double)(y+1) / nq;
double v1_b = 1.0 - (double)y / nq;
glNormal3f(0, 0, 1); // top
glVertex3f(d*(x+0)-1, d*(y+0)-1, 1);
glVertex3f(d*(x+1)-1, d*(y+0)-1, 1);
glVertex3f(d*(x+1)-1, d*(y+1)-1, 1);
glVertex3f(d*(x+0)-1, d*(y+1)-1, 1);
glTexCoord2d(u0_t, v0_t); glVertex3f(d*(x+0)-1, d*(y+0)-1, 1);
glTexCoord2d(u1_t, v0_t); glVertex3f(d*(x+1)-1, d*(y+0)-1, 1);
glTexCoord2d(u1_t, v1_t); glVertex3f(d*(x+1)-1, d*(y+1)-1, 1);
glTexCoord2d(u0_t, v1_t); glVertex3f(d*(x+0)-1, d*(y+1)-1, 1);
glNormal3f(0, 0, -1); // bottom
glVertex3f(d*(x+0)-1, d*(y+1)-1, -1);
glVertex3f(d*(x+1)-1, d*(y+1)-1, -1);
glVertex3f(d*(x+1)-1, d*(y+0)-1, -1);
glVertex3f(d*(x+0)-1, d*(y+0)-1, -1);
glTexCoord2d(u0_b, v0_b); glVertex3f(d*(x+0)-1, d*(y+1)-1, -1);
glTexCoord2d(u1_b, v0_b); glVertex3f(d*(x+1)-1, d*(y+1)-1, -1);
glTexCoord2d(u1_b, v1_b); glVertex3f(d*(x+1)-1, d*(y+0)-1, -1);
glTexCoord2d(u0_b, v1_b); glVertex3f(d*(x+0)-1, d*(y+0)-1, -1);
glNormal3f(1, 0, 0); // right
glVertex3f(1, d*(x+0)-1, d*(y+0)-1);
glVertex3f(1, d*(x+1)-1, d*(y+0)-1);
glVertex3f(1, d*(x+1)-1, d*(y+1)-1);
glVertex3f(1, d*(x+0)-1, d*(y+1)-1);
glTexCoord2d(u0_t, v0_t); glVertex3f(1, d*(x+0)-1, d*(y+0)-1);
glTexCoord2d(u1_t, v0_t); glVertex3f(1, d*(x+1)-1, d*(y+0)-1);
glTexCoord2d(u1_t, v1_t); glVertex3f(1, d*(x+1)-1, d*(y+1)-1);
glTexCoord2d(u0_t, v1_t); glVertex3f(1, d*(x+0)-1, d*(y+1)-1);
glNormal3f(-1, 0, 0); // left
glVertex3f(-1, d*(x+0)-1, d*(y+1)-1);
glVertex3f(-1, d*(x+1)-1, d*(y+1)-1);
glVertex3f(-1, d*(x+1)-1, d*(y+0)-1);
glVertex3f(-1, d*(x+0)-1, d*(y+0)-1);
glTexCoord2d(u0_b, v0_b); glVertex3f(-1, d*(x+0)-1, d*(y+1)-1);
glTexCoord2d(u1_b, v0_b); glVertex3f(-1, d*(x+1)-1, d*(y+1)-1);
glTexCoord2d(u1_b, v1_b); glVertex3f(-1, d*(x+1)-1, d*(y+0)-1);
glTexCoord2d(u0_b, v1_b); glVertex3f(-1, d*(x+0)-1, d*(y+0)-1);
glNormal3f(0, -1, 0); // front
glVertex3f(d*(x+0)-1, -1, d*(y+0)-1);
glVertex3f(d*(x+1)-1, -1, d*(y+0)-1);
glVertex3f(d*(x+1)-1, -1, d*(y+1)-1);
glVertex3f(d*(x+0)-1, -1, d*(y+1)-1);
glTexCoord2d(u0_t, v0_t); glVertex3f(d*(x+0)-1, -1, d*(y+0)-1);
glTexCoord2d(u1_t, v0_t); glVertex3f(d*(x+1)-1, -1, d*(y+0)-1);
glTexCoord2d(u1_t, v1_t); glVertex3f(d*(x+1)-1, -1, d*(y+1)-1);
glTexCoord2d(u0_t, v1_t); glVertex3f(d*(x+0)-1, -1, d*(y+1)-1);
glNormal3f(0, 1, 0); // back
glVertex3f(d*(x+0)-1, 1, d*(y+1)-1);
glVertex3f(d*(x+1)-1, 1, d*(y+1)-1);
glVertex3f(d*(x+1)-1, 1, d*(y+0)-1);
glVertex3f(d*(x+0)-1, 1, d*(y+0)-1);
glTexCoord2d(u0_b, v0_b); glVertex3f(d*(x+0)-1, 1, d*(y+1)-1);
glTexCoord2d(u1_b, v0_b); glVertex3f(d*(x+1)-1, 1, d*(y+1)-1);
glTexCoord2d(u1_b, v1_b); glVertex3f(d*(x+1)-1, 1, d*(y+0)-1);
glTexCoord2d(u0_b, v1_b); glVertex3f(d*(x+0)-1, 1, d*(y+0)-1);
}
}
glEnd();
+45 -1
View File
@@ -58,6 +58,21 @@ enum {
};
// check if geom type is a builtin shape with explicit UVs
static int isBuiltinWithUV(int type) {
return (type == mjGEOM_PLANE ||
type == mjGEOM_SPHERE ||
type == mjGEOM_ELLIPSOID ||
type == mjGEOM_BOX ||
type == mjGEOM_CYLINDER ||
type == mjGEOM_CAPSULE ||
type == mjGEOM_ARROW ||
type == mjGEOM_ARROW1 ||
type == mjGEOM_ARROW2 ||
type == mjGEOM_TRIANGLE);
}
// enable/disable texture mapping
static void settexture(int type, int state, const mjrContext* con, const mjvGeom* geom) {
float plane[4], scl[2];
@@ -102,18 +117,44 @@ static void settexture(int type, int state, const mjrContext* con, const mjvGeom
}
// explicit texture coordinates
else if (type == mjtexREGULAR && geom->texcoord) {
else if (type == mjtexREGULAR && geom &&
(geom->texcoord || (texid >= 0 && con->textureType[texid] == mjTEXTURE_2D && isBuiltinWithUV(geom->type)))) {
// enable
if (state && texid >= 0) {
glActiveTexture(GL_TEXTURE0);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, con->texture[texid]);
// determine scaling
scl[0] = texrepeat[0] > 0 ? texrepeat[0] : 1.0f;
scl[1] = texrepeat[1] > 0 ? texrepeat[1] : 1.0f;
// uniform: repeat relative to spatial units rather than object
if (texuniform) {
if (geom->size[0] > 0) {
scl[0] = scl[0] * geom->size[0];
}
if (geom->size[1] > 0) {
scl[1] = scl[1] * geom->size[1];
}
}
glMatrixMode(GL_TEXTURE);
glLoadIdentity();
if (geom->type == mjGEOM_PLANE && (geom->size[0] <= 0 || geom->size[1] <= 0)) {
glTranslatef(-0.5f, -0.5f, 0.0f);
}
glScalef(scl[0], scl[1], 1.0f);
glMatrixMode(GL_MODELVIEW);
}
// disable
else {
glActiveTexture(GL_TEXTURE0);
glDisable(GL_TEXTURE_2D);
glMatrixMode(GL_TEXTURE);
glLoadIdentity();
glMatrixMode(GL_MODELVIEW);
}
}
@@ -458,8 +499,11 @@ static void renderGeom(const mjvGeom* geom, int mode, const float* headpos,
glDisable(GL_CULL_FACE);
glBegin(GL_TRIANGLES);
glNormal3f(0, 0, 1);
glTexCoord2f(0, 1);
glVertex3f(0, 0, 0);
glTexCoord2f(1, 1);
glVertex3f(size[0], 0, 0);
glTexCoord2f(0, 0);
glVertex3f(0, size[1], 0);
glEnd();
if (scn->flags[mjRND_CULL_FACE]) {
+4 -2
View File
@@ -26,7 +26,9 @@ material {
{ type : float4, name : BaseColorFactor },
{ type : float, name : MetallicFactor },
{ type : float, name : RoughnessFactor },
{ type : float, name : EmissiveFactor }
{ type : float, name : EmissiveFactor },
{ type : float3, name : UvScale },
{ type : float3, name : UvOffset }
],
requires : [
@@ -36,7 +38,7 @@ material {
fragment {
void material(inout MaterialInputs material) {
vec2 uv = getUV0();
vec2 uv = getUV0() * materialParams.UvScale.xy + materialParams.UvOffset.xy;
material.normal = texture(materialParams_Normal, uv).xyz * 2.0 - 1.0;
prepareMaterial(material);
+4 -2
View File
@@ -24,7 +24,9 @@ material {
{ type : float4, name : BaseColorFactor },
{ type : float, name : MetallicFactor },
{ type : float, name : RoughnessFactor },
{ type : float, name : EmissiveFactor }
{ type : float, name : EmissiveFactor },
{ type : float3, name : UvScale },
{ type : float3, name : UvOffset }
],
requires : [
uv0
@@ -33,7 +35,7 @@ material {
fragment {
void material(inout MaterialInputs material) {
vec2 uv = getUV0();
vec2 uv = getUV0() * materialParams.UvScale.xy + materialParams.UvOffset.xy;
material.normal = texture(materialParams_Normal, uv).xyz * 2.0 - 1.0;
prepareMaterial(material);
+5 -1
View File
@@ -38,7 +38,11 @@ vertex {
fragment {
void material(inout MaterialInputs material) {
vec2 uv = variable_vertex_pos.xy * materialParams.UvScale.xy + materialParams.UvOffset.xy;
// Convert centered object-space positions [-1, 1] to standard [0, 1] UVs:
// * 0.5 scales [-1, 1] to [-0.5, 0.5]
// * -0.5 on Y flips the vertical axis (top of geom = V=0)
// * - vec2(0.5, 0.5) offsets origin to match OpenGL glTexGen conventions
vec2 uv = variable_vertex_pos.xy * vec2(0.5, -0.5) * materialParams.UvScale.xy - vec2(0.5, 0.5) + materialParams.UvOffset.xy;
prepareMaterial(material);
material.baseColor = materialParams.BaseColorFactor;
+5 -1
View File
@@ -39,7 +39,11 @@ vertex {
fragment {
void material(inout MaterialInputs material) {
vec2 uv = variable_vertex_pos.xy * materialParams.UvScale.xy + materialParams.UvOffset.xy;
// Convert centered object-space positions [-1, 1] to standard [0, 1] UVs:
// * 0.5 scales [-1, 1] to [-0.5, 0.5]
// * -0.5 on Y flips the vertical axis (top of geom = V=0)
// * - vec2(0.5, 0.5) offsets origin to match OpenGL glTexGen conventions
vec2 uv = variable_vertex_pos.xy * vec2(0.5, -0.5) * materialParams.UvScale.xy - vec2(0.5, 0.5) + materialParams.UvOffset.xy;
prepareMaterial(material);
material.baseColor = materialParams.BaseColorFactor;
@@ -40,7 +40,11 @@ vertex {
fragment {
void material(inout MaterialInputs material) {
vec2 uv = variable_vertex_pos.xy * materialParams.UvScale.xy + materialParams.UvOffset.xy;
// Convert centered object-space positions [-1, 1] to standard [0, 1] UVs:
// * 0.5 scales [-1, 1] to [-0.5, 0.5]
// * -0.5 on Y flips the vertical axis (top of geom = V=0)
// * - vec2(0.5, 0.5) offsets origin to match OpenGL glTexGen conventions
vec2 uv = variable_vertex_pos.xy * vec2(0.5, -0.5) * materialParams.UvScale.xy - vec2(0.5, 0.5) + materialParams.UvOffset.xy;
vec2 screen_uv = gl_FragCoord.xy * getResolution().zw;
prepareMaterial(material);
+4 -2
View File
@@ -21,7 +21,9 @@ material {
{ type : float, name : SpecularFactor },
{ type : float, name : GlossinessFactor },
{ type : float, name : EmissiveFactor },
{ type : sampler2d, name : BaseColor }
{ type : sampler2d, name : BaseColor },
{ type : float3, name : UvScale },
{ type : float3, name : UvOffset }
],
requires : [
uv0
@@ -30,7 +32,7 @@ material {
fragment {
void material(inout MaterialInputs material) {
vec2 uv = getUV0();
vec2 uv = getUV0() * materialParams.UvScale.xy + materialParams.UvOffset.xy;
prepareMaterial(material);
material.baseColor = materialParams.BaseColorFactor;
@@ -22,7 +22,9 @@ material {
{ type : float, name : SpecularFactor },
{ type : float, name : GlossinessFactor },
{ type : float, name : EmissiveFactor },
{ type : sampler2d, name : BaseColor }
{ type : sampler2d, name : BaseColor },
{ type : float3, name : UvScale },
{ type : float3, name : UvOffset }
],
requires : [
uv0
@@ -31,7 +33,7 @@ material {
fragment {
void material(inout MaterialInputs material) {
vec2 uv = getUV0();
vec2 uv = getUV0() * materialParams.UvScale.xy + materialParams.UvOffset.xy;
prepareMaterial(material);
material.baseColor = materialParams.BaseColorFactor;
@@ -23,7 +23,9 @@ material {
{ type : float, name : EmissiveFactor },
{ type : float, name : Reflectance },
{ type : sampler2d, name : BaseColor },
{ type : sampler2d, name : Reflection }
{ type : sampler2d, name : Reflection },
{ type : float3, name : UvScale },
{ type : float3, name : UvOffset }
],
requires : [
uv0
@@ -32,7 +34,7 @@ material {
fragment {
void material(inout MaterialInputs material) {
vec2 uv = getUV0();
vec2 uv = getUV0() * materialParams.UvScale.xy + materialParams.UvOffset.xy;
vec2 screen_uv = gl_FragCoord.xy * getResolution().zw;
prepareMaterial(material);
+161 -99
View File
@@ -76,17 +76,24 @@ class BuiltinBuilder {
config.max_indices = builder->indices_.size();
config.index_type = mjINDEX_TYPE_U16;
config.primitive_type = builder->primitive_type_;
config.num_attributes = 2;
config.num_attributes = builder->texcoords_.empty() ? 2 : 3;
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 (!builder->texcoords_.empty()) {
config.attributes[2].usage = mjVERTEX_ATTRIBUTE_USAGE_UV;
config.attributes[2].type = mjVERTEX_ATTRIBUTE_TYPE_FLOAT2;
}
mjrfMeshData data;
mjrf_defaultMeshData(&data);
data.num_vertices = builder->positions_.size();
data.vertices[0] = builder->positions_.data();
data.vertices[1] = builder->orientations_.data();
if (!builder->texcoords_.empty()) {
data.vertices[2] = builder->texcoords_.data();
}
data.num_indices = builder->indices_.size();
data.indices = builder->indices_.data();
data.bounds_min[0] = builder->bounds_min_.x;
@@ -114,6 +121,7 @@ class BuiltinBuilder {
int primitive_type_ = mjMESH_PRIMITIVE_TYPE_TRIANGLES;
std::vector<float3> positions_;
std::vector<float4> orientations_;
std::vector<float2> texcoords_;
std::vector<uint16_t> indices_;
float3 bounds_min_ = {0, 0, 0};
float3 bounds_max_ = {0, 0, 0};
@@ -143,6 +151,7 @@ class PlaneBuilder : public BuiltinBuilder {
explicit PlaneBuilder(int num_quads_per_axis) {
const int num_vertices = NumVerticesPerSide(num_quads_per_axis);
positions_.reserve(num_vertices);
texcoords_.reserve(num_vertices);
const float delta = 2.0f / num_quads_per_axis;
for (int x = 0; x <= num_quads_per_axis; ++x) {
@@ -150,6 +159,7 @@ class PlaneBuilder : public BuiltinBuilder {
const float dx = delta * static_cast<float>(x);
const float dy = delta * static_cast<float>(y);
positions_.emplace_back(dx - 1.0f, dy - 1.0f, 0);
texcoords_.emplace_back(0.5f * dx, 1.0f - (0.5f * dy));
}
}
@@ -180,6 +190,11 @@ class TriangleBuilder : public BuiltinBuilder {
positions_.emplace_back(1, 0, 0);
positions_.emplace_back(0, 1, 0);
texcoords_.reserve(3);
texcoords_.emplace_back(0, 1);
texcoords_.emplace_back(1, 1);
texcoords_.emplace_back(0, 0);
orientations_.resize(positions_.size(), CalculateOrientation({0, 0, 1}));
indices_.reserve(3);
@@ -187,7 +202,7 @@ class TriangleBuilder : public BuiltinBuilder {
indices_.emplace_back(1);
indices_.emplace_back(2);
SetBounds({-1, -1, -0.001}, {1, 1, 0.001});
SetBounds({0, 0, -0.001}, {1, 1, 0.001});
}
};
@@ -255,6 +270,7 @@ class BoxBuilder : public BuiltinBuilder {
positions_.reserve(num_vertices);
orientations_.reserve(num_vertices);
texcoords_.reserve(num_vertices);
indices_.reserve(num_indices);
GenerateVerticesForSide({0, 1, 0},
@@ -302,6 +318,9 @@ class BoxBuilder : public BuiltinBuilder {
const float3 position = pt_gen({dx, dy});
positions_.push_back(position);
orientations_.push_back(orientation);
const float u = static_cast<float>(x) / num_quads_per_axis_;
const float v = 1.0f - (static_cast<float>(y) / num_quads_per_axis_);
texcoords_.emplace_back(u, v);
}
}
}
@@ -313,20 +332,26 @@ class BoxBuilder : public BuiltinBuilder {
class TubeBuilder : public BuiltinBuilder {
public:
TubeBuilder(int num_stacks, int num_slices) {
const int num_vertices = num_slices * (num_stacks + 1);
const int verts_per_ring = num_slices + 1;
const int num_vertices = verts_per_ring * (num_stacks + 1);
positions_.reserve(num_vertices);
orientations_.reserve(num_vertices);
texcoords_.reserve(num_vertices);
const float delta_angle = 2.f * std::numbers::pi / (float)num_slices;
const float delta_stack = 2.f / static_cast<float>(num_stacks);
for (int i = 0; i < num_slices; ++i) {
const float angle = static_cast<float>(i) * delta_angle;
for (int i = 0; i <= num_slices; ++i) {
const int geo_i = i % num_slices;
const float angle = static_cast<float>(geo_i) * delta_angle;
const float2 pt{std::cos(angle), std::sin(angle)};
const float4 orientation = CalculateOrientation({pt.x, pt.y, 0});
const float u = static_cast<float>(i) / num_slices;
for (int j = 0; j <= num_stacks; ++j) {
const float z = -1.0f + (static_cast<float>(j) * delta_stack);
positions_.emplace_back(pt.x, pt.y, z);
orientations_.push_back(orientation);
const float v = 1.0f - ((static_cast<float>(j) * delta_stack) / 2.0f);
texcoords_.emplace_back(u, v);
}
}
@@ -337,10 +362,11 @@ class TubeBuilder : public BuiltinBuilder {
for (int i = 0; i < num_slices; ++i) {
for (int j = 0; j < num_stacks; ++j) {
const int base_idx = (i * num_vertices_in_spine) + j;
const int next_base_idx = ((i + 1) * num_vertices_in_spine) + j;
const int i0 = base_idx + 0;
const int i1 = base_idx + 1;
const int i2 = (base_idx + num_stacks + 2) % num_vertices;
const int i3 = (base_idx + num_stacks + 1) % num_vertices;
const int i2 = next_base_idx + 1;
const int i3 = next_base_idx + 0;
AppendQuadIndices(indices_, i0, i3, i2, i1);
}
}
@@ -357,8 +383,9 @@ class ConeBuilder : public BuiltinBuilder {
((num_stacks - 1) * num_slices * kNumVerticesPerQuad);
positions_.reserve(num_vertices);
orientations_.reserve(num_vertices);
texcoords_.reserve(num_vertices);
// pole: use triangles
// Pole: use triangles
const float delta_angle =
2.0 * std::numbers::pi / static_cast<float>(num_slices);
const float delta_radius = 1.0f / static_cast<float>(num_stacks);
@@ -367,14 +394,20 @@ class ConeBuilder : public BuiltinBuilder {
const float angle1 = (j + 0) * delta_angle;
const float angle2 = (j + 1) * delta_angle;
AppendVert(angle1, delta_radius);
AppendVert(angle2, delta_radius);
const float u1 = static_cast<float>(j) / num_slices;
const float u2 = static_cast<float>(j + 1) / num_slices;
const float v1 = delta_radius;
const float u_mid = (u1 + u2) / 2.f;
AppendVert(angle1, delta_radius, u1, v1);
AppendVert(angle2, delta_radius, u2, v1);
positions_.emplace_back(0, 0, 1);
orientations_.emplace_back(CalculateOrientation({0, 0, 1}));
texcoords_.emplace_back(u_mid, 0.f);
}
// the rest: use quads
// The rest: use quads
for (int i = 1; i < num_stacks; ++i) {
const float radius1 = delta_radius * (i + 0);
const float radius2 = delta_radius * (i + 1);
@@ -382,10 +415,14 @@ class ConeBuilder : public BuiltinBuilder {
for (int j = 0; j < num_slices; ++j) {
const float angle1 = (j + 0) * delta_angle;
const float angle2 = (j + 1) * delta_angle;
AppendVert(angle1, radius2);
AppendVert(angle2, radius2);
AppendVert(angle2, radius1);
AppendVert(angle1, radius1);
const float u1 = static_cast<float>(j) / num_slices;
const float u2 = static_cast<float>(j + 1) / num_slices;
const float v1 = radius1;
const float v2 = radius2;
AppendVert(angle1, radius2, u1, v2);
AppendVert(angle2, radius2, u2, v2);
AppendVert(angle2, radius1, u2, v1);
AppendVert(angle1, radius1, u1, v1);
}
}
@@ -413,7 +450,7 @@ class ConeBuilder : public BuiltinBuilder {
}
private:
void AppendVert(float theta, float radius) {
void AppendVert(float theta, float radius, float u, float v) {
static constexpr float kNormalScale = 0.70710678118f;
const float cz = std::cos(theta);
const float sz = std::sin(theta);
@@ -421,6 +458,7 @@ class ConeBuilder : public BuiltinBuilder {
const float3 n{cz * kNormalScale, sz * kNormalScale, kNormalScale};
positions_.push_back(pt);
orientations_.push_back(CalculateOrientation(n));
texcoords_.emplace_back(u, v);
}
};
@@ -429,15 +467,18 @@ class DiskBuilder : public BuiltinBuilder {
explicit DiskBuilder(int num_slices) {
const int num_vertices = num_slices + 1;
positions_.reserve(num_vertices);
texcoords_.reserve(num_vertices);
const float delta_angle =
2.0 * std::numbers::pi / static_cast<float>(num_slices);
positions_.push_back({0, 0, 0});
texcoords_.emplace_back(0.5f, 0.5f);
for (int i = 0; i < num_slices; ++i) {
const float angle = static_cast<float>(i) * delta_angle;
const float x = std::cos(angle);
const float y = std::sin(angle);
positions_.push_back({x, y, 0});
texcoords_.emplace_back(0.5f + 0.5f * x, 0.5f + 0.5f * y);
}
orientations_.resize(positions_.size(), CalculateOrientation({0, 0, 1}));
@@ -458,178 +499,195 @@ class DiskBuilder : public BuiltinBuilder {
class SphereBuilder : public BuiltinBuilder {
public:
SphereBuilder(int num_stacks, int num_slices) {
static constexpr uint16_t kNorthPoleIndex = 0;
static constexpr uint16_t kSouthPoleIndex = 1;
// To avoid a UV seam artifact, each latitude ring has num_slices+1
// vertices: the last vertex is a geometric duplicate of the first but
// with u=1.0 instead of u=0.0. This prevents the GPU from interpolating
// backwards from u≈0.97 to u=0.0 across the last quad.
//
// Each polar triangle also gets its own pole vertex with u set to the
// midpoint of the two ring vertices, avoiding the degenerate atan2 at
// the pole.
const int num_vertices = (num_stacks * num_slices) + 2; // +2 for poles
const int verts_per_ring = num_slices + 1; // extra vertex for u=1 seam
const int ring_verts = num_stacks * verts_per_ring;
const int pole_verts = 2 * num_slices; // one pole vert per polar triangle
const int num_vertices = ring_verts + pole_verts;
positions_.reserve(num_vertices);
orientations_.reserve(num_vertices);
texcoords_.reserve(num_vertices);
const float lat_angle_delta =
std::numbers::pi / static_cast<float>(num_stacks + 1);
const float lon_angle_delta =
2.0 * std::numbers::pi / static_cast<float>(num_slices);
// Add the north and south poles.
AppendVert(0, 0, 1);
AppendVert(0, 0, -1);
// Vertices by latitude.
// Latitude ring vertices (with seam column).
for (int lat = 0; lat < num_stacks; ++lat) {
// +1 because we handle the north pole (which would be at a lat angle of
// 0-degrees) explicitly.
const float lat_angle = static_cast<float>(lat + 1) * lat_angle_delta;
const float cos_lat_angle = std::cos(lat_angle);
const float sin_lat_angle = std::sin(lat_angle);
const float z = cos_lat_angle;
const float v = lat_angle / std::numbers::pi_v<float>;
for (int lon = 0; lon < num_slices; ++lon) {
const float lon_angle = static_cast<float>(lon) * lon_angle_delta;
for (int lon = 0; lon <= num_slices; ++lon) {
const float u = static_cast<float>(lon) / num_slices;
// Wrap the geometry back to lon=0 for the seam column.
const int geo_lon = lon % num_slices;
const float lon_angle = static_cast<float>(geo_lon) * lon_angle_delta;
const float cos_lon_angle = std::cos(lon_angle);
const float sin_lon_angle = std::sin(lon_angle);
const float x = sin_lat_angle * cos_lon_angle;
const float y = sin_lat_angle * sin_lon_angle;
AppendVert(x, y, z);
const float x = sin_lat_angle * std::cos(lon_angle);
const float y = sin_lat_angle * std::sin(lon_angle);
AppendVert(x, y, z, u, v);
}
}
// Per-face pole vertices. Each polar triangle gets a unique pole vertex
// with u set to the midpoint of the two adjacent ring vertices.
const int north_pole_start = ring_verts;
for (int lon = 0; lon < num_slices; ++lon) {
const float u = (static_cast<float>(lon) + 0.5f) / num_slices;
AppendVert(0, 0, 1, u, 0.0f);
}
const int south_pole_start = north_pole_start + num_slices;
for (int lon = 0; lon < num_slices; ++lon) {
const float u = (static_cast<float>(lon) + 0.5f) / num_slices;
AppendVert(0, 0, -1, u, 1.0f);
}
// Indices.
const size_t num_tris_polar_cap = num_slices;
const size_t num_quads_body = num_slices * (num_stacks - 1);
const int num_indices = (2 * num_tris_polar_cap * kNumIndicesPerTriangle) +
(num_quads_body * kNumIndicesPerQuad);
indices_.reserve(num_indices);
// The first two vertices are the poles, so the first vertex in the first
// row starts at index 2.
uint16_t row_start = kSouthPoleIndex + 1;
// North polar cap.
// North polar cap — each triangle uses its own pole vertex.
const uint16_t first_ring_start = 0;
for (int lon = 0; lon < num_slices; ++lon) {
const int next = lon < (num_slices - 1) ? lon + 1 : 0;
indices_.push_back(kNorthPoleIndex);
indices_.push_back(row_start + lon);
indices_.push_back(row_start + next);
indices_.push_back(north_pole_start + lon);
indices_.push_back(first_ring_start + lon);
indices_.push_back(first_ring_start + lon + 1);
}
// Latitudinal triangle strips.
// Latitudinal quad strips — no index wrapping needed thanks to seam column.
for (int lat = 0; lat < num_stacks - 1; lat++) {
const uint16_t north_start = row_start;
const uint16_t south_start = row_start + num_slices;
const uint16_t north_start = lat * verts_per_ring;
const uint16_t south_start = (lat + 1) * verts_per_ring;
for (int lon = 0; lon < num_slices; ++lon) {
// The offset to the index that is adjacent to the current index.
const int adjacent = lon < (num_slices - 1) ? lon + 1 : 0;
const int i0 = (north_start + lon);
const int i1 = (south_start + lon);
const int i2 = (south_start + adjacent);
const int i3 = (north_start + adjacent);
const int i0 = north_start + lon;
const int i1 = south_start + lon;
const int i2 = south_start + lon + 1;
const int i3 = north_start + lon + 1;
AppendQuadIndices(indices_, i0, i1, i2, i3);
}
row_start += num_slices;
}
// South polar cap.
const uint16_t last_ring_start = (num_stacks - 1) * verts_per_ring;
for (int lon = 0; lon < num_slices; ++lon) {
const int adjacent = lon < (num_slices - 1) ? lon + 1 : 0;
indices_.push_back(kSouthPoleIndex);
indices_.push_back(row_start + adjacent);
indices_.push_back(row_start + lon);
indices_.push_back(south_pole_start + lon);
indices_.push_back(last_ring_start + lon + 1);
indices_.push_back(last_ring_start + lon);
}
SetBounds({-1, -1, -1}, {1, 1, 1});
}
private:
void AppendVert(float x, float y, float z) {
void AppendVert(float x, float y, float z, float u, float v) {
const float3 pt{x, y, z};
positions_.push_back(pt);
orientations_.push_back(CalculateOrientation(pt));
texcoords_.emplace_back(u, v);
}
};
class DomeBuilder : public BuiltinBuilder {
public:
DomeBuilder(int num_stacks, int num_slices) {
static constexpr uint16_t kPoleIndex = 0;
DomeBuilder(int num_stacks, int num_slices, bool flip_u = false,
bool flip_v = false) {
// Same seam-fix strategy as SphereBuilder: extra vertex per ring at u=1.0
// and per-face pole vertices.
const int num_vertices = (num_stacks * num_slices) + 1; // +1 for poles
const int verts_per_ring = num_slices + 1;
const int ring_verts = num_stacks * verts_per_ring;
const int pole_verts = num_slices; // one pole vert per polar triangle
const int num_vertices = ring_verts + pole_verts;
positions_.reserve(num_vertices);
orientations_.reserve(num_vertices);
texcoords_.reserve(num_vertices);
const float lat_angle_delta =
0.5 * std::numbers::pi / static_cast<float>(num_stacks);
const float lon_angle_delta =
2.0 * std::numbers::pi / static_cast<float>(num_slices);
// Add the pole.
AppendVert(0, 0, 1);
// Vertices by latitude.
// Latitude ring vertices (with seam column).
for (int lat = 0; lat < num_stacks; ++lat) {
// +1 because we handle the north pole (which would be at a lat angle of
// 0-degrees) explicitly.
const float lat_angle = static_cast<float>(lat + 1) * lat_angle_delta;
const float cos_lat_angle = std::cos(lat_angle);
const float sin_lat_angle = std::sin(lat_angle);
const float z = cos_lat_angle;
const float v_val = 1.0f - (2.0f * lat_angle / std::numbers::pi_v<float>);
const float v = flip_v ? 1.0f - v_val : v_val;
for (int lon = 0; lon < num_slices; ++lon) {
const float lon_angle = static_cast<float>(lon) * lon_angle_delta;
const float cos_lon_angle = std::cos(lon_angle);
const float sin_lon_angle = std::sin(lon_angle);
for (int lon = 0; lon <= num_slices; ++lon) {
const float u_val = static_cast<float>(lon) / num_slices;
const float u = flip_u ? 1.0f - u_val : u_val;
const int geo_lon = lon % num_slices;
const float lon_angle = static_cast<float>(geo_lon) * lon_angle_delta;
const float x = sin_lat_angle * cos_lon_angle;
const float y = sin_lat_angle * sin_lon_angle;
AppendVert(x, y, z);
const float x = sin_lat_angle * std::cos(lon_angle);
const float y = sin_lat_angle * std::sin(lon_angle);
AppendVert(x, y, z, u, v);
}
}
// Per-face pole vertices.
const int pole_start = ring_verts;
for (int lon = 0; lon < num_slices; ++lon) {
const float u_val = (static_cast<float>(lon) + 0.5f) / num_slices;
const float u = flip_u ? 1.0f - u_val : u_val;
AppendVert(0, 0, 1, u, flip_v ? 0.0f : 1.0f);
}
// Indices.
const size_t num_tris_polar_cap = num_slices;
const size_t num_quads_body = num_slices * (num_stacks - 1);
const int num_indices = (num_tris_polar_cap * kNumIndicesPerTriangle) +
(num_quads_body * kNumIndicesPerQuad);
indices_.reserve(num_indices);
// The first vertex is the poles, so the first vertex in the first row
// starts at index 1.
uint16_t row_start = kPoleIndex + 1;
// North polar cap.
// Polar cap — each triangle uses its own pole vertex.
const uint16_t first_ring_start = 0;
for (int lon = 0; lon < num_slices; ++lon) {
const int next = lon < (num_slices - 1) ? lon + 1 : 0;
indices_.push_back(kPoleIndex);
indices_.push_back(row_start + lon);
indices_.push_back(row_start + next);
indices_.push_back(pole_start + lon);
indices_.push_back(first_ring_start + lon);
indices_.push_back(first_ring_start + lon + 1);
}
// Latitudinal quad strips. The first "stack" was handled above, so we
// only need to iterate over N-1 stacks.
// Latitudinal quad strips.
for (int lat = 0; lat < num_stacks - 1; lat++) {
const int north_start = row_start;
const int south_start = row_start + num_slices;
const uint16_t north_start = lat * verts_per_ring;
const uint16_t south_start = (lat + 1) * verts_per_ring;
for (int lon = 0; lon < num_slices; ++lon) {
// The offset to the index that is adjacent to the current index.
const int adjacent = lon < (num_slices - 1) ? lon + 1 : 0;
const int i0 = (north_start + lon);
const int i1 = (south_start + lon);
const int i2 = (south_start + adjacent);
const int i3 = (north_start + adjacent);
const int i0 = north_start + lon;
const int i1 = south_start + lon;
const int i2 = south_start + lon + 1;
const int i3 = north_start + lon + 1;
AppendQuadIndices(indices_, i0, i1, i2, i3);
}
row_start += num_slices;
}
SetBounds({-1, -1, 0}, {1, 1, 1});
}
private:
void AppendVert(float x, float y, float z) {
void AppendVert(float x, float y, float z, float u, float v) {
const float3 pt{x, y, z};
positions_.push_back(pt);
orientations_.push_back(CalculateOrientation(pt));
texcoords_.emplace_back(u, v);
}
};
@@ -643,7 +701,10 @@ Builtins::Builtins(filament::Engine* engine, int nstack, int nslice,
sphere_ = BuiltinBuilder::Create<SphereBuilder>(engine, nstack, nslice);
tube_ = BuiltinBuilder::Create<TubeBuilder>(engine, nstack, nslice);
disk_ = BuiltinBuilder::Create<DiskBuilder>(engine, nslice);
dome_ = BuiltinBuilder::Create<DomeBuilder>(engine, nstack, nslice);
dome_top_ =
BuiltinBuilder::Create<DomeBuilder>(engine, nstack, nslice, false, true);
dome_bottom_ =
BuiltinBuilder::Create<DomeBuilder>(engine, nstack, nslice, true, false);
cone_ = BuiltinBuilder::Create<ConeBuilder>(engine, nstack, nslice);
}
@@ -655,7 +716,8 @@ const Mesh* Builtins::Box() { return box_.get(); }
const Mesh* Builtins::Sphere() { return sphere_.get(); }
const Mesh* Builtins::Cone() { return cone_.get(); }
const Mesh* Builtins::Disk() { return disk_.get(); }
const Mesh* Builtins::Dome() { return dome_.get(); }
const Mesh* Builtins::DomeTop() { return dome_top_.get(); }
const Mesh* Builtins::DomeBottom() { return dome_bottom_.get(); }
const Mesh* Builtins::Tube() { return tube_.get(); }
} // namespace mujoco
+4 -2
View File
@@ -36,7 +36,8 @@ class Builtins {
const Mesh* Sphere();
const Mesh* Cone();
const Mesh* Disk();
const Mesh* Dome();
const Mesh* DomeTop();
const Mesh* DomeBottom();
const Mesh* Tube();
private:
@@ -48,7 +49,8 @@ class Builtins {
std::unique_ptr<Mesh> sphere_;
std::unique_ptr<Mesh> cone_;
std::unique_ptr<Mesh> disk_;
std::unique_ptr<Mesh> dome_;
std::unique_ptr<Mesh> dome_top_;
std::unique_ptr<Mesh> dome_bottom_;
std::unique_ptr<Mesh> tube_;
};
+6 -40
View File
@@ -170,8 +170,11 @@ void Renderable::UpdateTransform() {
filament::TransformManager& tm = GetEngine()->getTransformManager();
if (geom_type_ == mjGEOM_PLANE && (trs_.size[0] <= 0 || trs_.size[1] <= 0)) {
infinite_plane_ = true;
static constexpr float kInfiniteScale = 0.5f * mjMAXPLANEGRID;
const mat4f scaling =
mat4f::scaling(float3{kInfiniteScale, kInfiniteScale, 1.0f});
const mat4f transform =
filament::math::mat4f(trs_.rotation, trs_.translation);
filament::math::mat4f(trs_.rotation, trs_.translation) * scaling;
for (Part& part : parts_) {
tm.setTransform(tm.getInstance(part.entity), transform);
}
@@ -340,43 +343,6 @@ void Renderable::BindMaterialInstance(const mjrfRenderRequest& request) {
mju_error("No material instances to bind.");
}
if (geom_type_ == mjGEOM_PLANE && infinite_plane_) {
// Emulate an infinite plane by recentering a large quad in world space
// relative to the camera. We use the shared mjMAXPLANEGRID value as the
// size of the quad to ensure the texture scaling matches.
static constexpr float kInfiniteScale = 0.5f * mjMAXPLANEGRID;
const mat4f scaling =
mat4f::scaling(float3{kInfiniteScale, kInfiniteScale, 1.0f});
const float3 camera_pos = ReadFloat3(request.camera.pos);
const float3 plane_origin = transform_[3].xyz;
const mat3f plane_rotation = transform_.upperLeft();
const float3 vec = camera_pos - plane_origin;
const float3 plane_x = normalize(plane_rotation[0]);
const float3 plane_y = normalize(plane_rotation[1]);
// Project camera position onto the plane's local XY axes.
float dx = dot(vec, plane_x);
float dy = dot(vec, plane_y);
// Quantize based on uv_scale.
const float tile_size[] = {kInfiniteScale / material_.uv_scale[0],
kInfiniteScale / material_.uv_scale[1]};
dx = tile_size[0] * mju_round(dx / tile_size[0]);
dy = tile_size[1] * mju_round(dy / tile_size[1]);
// Calculate the new center quad as a displacement from the plane origin.
const float3 displacement = dx * plane_x + dy * plane_y;
const float3 center = plane_origin + displacement;
const mat4f transform = mat4f(plane_rotation, center) * scaling;
filament::TransformManager& tm = GetEngine()->getTransformManager();
for (Part& part : parts_) {
tm.setTransform(tm.getInstance(part.entity), transform);
}
}
const DrawState& state = draw_queue_.front();
SetCastShadows(state.cast_shadows);
SetReceiveShadows(state.receive_shadows);
@@ -502,8 +468,8 @@ void Renderable::SetGeomMesh(mjtGeom type, int nstack, int nslice, int nquad) {
case mjGEOM_CAPSULE:
// Capsules are a tube with two domes at the ends.
AppendMesh(builtins->Tube());
AppendMesh(builtins->Dome());
AppendMesh(builtins->Dome());
AppendMesh(builtins->DomeTop());
AppendMesh(builtins->DomeBottom());
get_transform_fn_ = [](int index, const Trs& trs) {
// We apply an inverse scale to the domes to counteract the capsule's
+43 -17
View File
@@ -157,15 +157,17 @@ static void FillMeshBuffer(MeshBuilder& builder, const mjModel* model, int meshi
static void FillHeightFieldBuffer(MeshBuilder& builder, const mjModel* model,
int hfieldid) {
auto append_tri = [&](float3 a, float3 b, float3 c) {
auto append_tri = [&](float3 a, float3 b, float3 c, float2 uv_a, float2 uv_b,
float2 uv_c) {
float4 orientation = CalculateOrientation(a, b, c);
builder.Append(a, orientation, float2(0, 0));
builder.Append(b, orientation, float2(0, 0));
builder.Append(c, orientation, float2(0, 0));
builder.Append(a, orientation, uv_a);
builder.Append(b, orientation, uv_b);
builder.Append(c, orientation, uv_c);
};
auto append_quad = [&](float3 a, float3 b, float3 c, float3 d) {
append_tri(a, b, d);
append_tri(d, b, c);
auto append_quad = [&](float3 a, float3 b, float3 c, float3 d, float2 uv_a,
float2 uv_b, float2 uv_c, float2 uv_d) {
append_tri(a, b, d, uv_a, uv_b, uv_d);
append_tri(d, b, c, uv_d, uv_b, uv_c);
};
const float* data = model->hfield_data + model->hfield_adr[hfieldid];
@@ -227,10 +229,21 @@ static void FillHeightFieldBuffer(MeshBuilder& builder, const mjModel* model,
}
const float3 mid = {mid_x, mid_y, mid_z};
append_tri(a, b, mid);
append_tri(b, c, mid);
append_tri(c, d, mid);
append_tri(d, a, mid);
const float2 uv_a = {(float)col / (ncol - 1),
1.f - (float)row / (nrow - 1)};
const float2 uv_b = {(float)(col + 1) / (ncol - 1),
1.f - (float)row / (nrow - 1)};
const float2 uv_c = {(float)(col + 1) / (ncol - 1),
1.f - (float)(row + 1) / (nrow - 1)};
const float2 uv_d = {(float)col / (ncol - 1),
1.f - (float)(row + 1) / (nrow - 1)};
const float2 uv_mid = {(float)(col + 0.5f) / (ncol - 1),
1.f - (float)(row + 0.5f) / (nrow - 1)};
append_tri(a, b, mid, uv_a, uv_b, uv_mid);
append_tri(b, c, mid, uv_b, uv_c, uv_mid);
append_tri(c, d, mid, uv_c, uv_d, uv_mid);
append_tri(d, a, mid, uv_d, uv_a, uv_mid);
}
}
// Build the left edge.
@@ -239,7 +252,9 @@ static void FillHeightFieldBuffer(MeshBuilder& builder, const mjModel* model,
const float3 b = get_pos(row + 1, 0);
const float3 c = {b.x, b.y, -sz[3]};
const float3 d = {a.x, a.y, -sz[3]};
append_quad(a, b, c, d);
const float2 uv_a = {0.f, 1.f - (float)row / (nrow - 1)};
const float2 uv_b = {0.f, 1.f - (float)(row + 1) / (nrow - 1)};
append_quad(a, b, c, d, uv_a, uv_b, uv_b, uv_a);
}
// Build the right edge.
for (int row = 0; row < nrow - 1; ++row) {
@@ -247,7 +262,9 @@ static void FillHeightFieldBuffer(MeshBuilder& builder, const mjModel* model,
const float3 b = get_pos(row, ncol - 1);
const float3 c = {b.x, b.y, -sz[3]};
const float3 d = {a.x, a.y, -sz[3]};
append_quad(a, b, c, d);
const float2 uv_a = {1.f, 1.f - (float)(row + 1) / (nrow - 1)};
const float2 uv_b = {1.f, 1.f - (float)row / (nrow - 1)};
append_quad(a, b, c, d, uv_a, uv_b, uv_b, uv_a);
}
// Build the front edge.
for (int col = 0; col < ncol - 1; ++col) {
@@ -255,7 +272,9 @@ static void FillHeightFieldBuffer(MeshBuilder& builder, const mjModel* model,
const float3 b = {a.x, a.y, -sz[3]};
const float3 d = get_pos(0, col + 1);
const float3 c = {d.x, d.y, -sz[3]};
append_quad(a, b, c, d);
const float2 uv_a = {(float)col / (ncol - 1), 1.f};
const float2 uv_d = {(float)(col + 1) / (ncol - 1), 1.f};
append_quad(a, b, c, d, uv_a, uv_a, uv_d, uv_d);
}
// Build the back edge.
for (int col = 0; col < ncol - 1; ++col) {
@@ -263,7 +282,9 @@ static void FillHeightFieldBuffer(MeshBuilder& builder, const mjModel* model,
const float3 b = {a.x, a.y, -sz[3]};
const float3 d = get_pos(nrow - 1, col);
const float3 c = {d.x, d.y, -sz[3]};
append_quad(a, b, c, d);
const float2 uv_a = {(float)(col + 1) / (ncol - 1), 0.f};
const float2 uv_d = {(float)col / (ncol - 1), 0.f};
append_quad(a, b, c, d, uv_a, uv_a, uv_d, uv_d);
}
// Build the base. We use the visualization quality as the size rather than
// the height field dimensions.
@@ -275,8 +296,13 @@ static void FillHeightFieldBuffer(MeshBuilder& builder, const mjModel* model,
const float x1 = sz[0] * ((col + 1) / base_width - 1.0f);
const float y0 = sz[1] * ((row + 0) / base_height - 1.0f);
const float y1 = sz[1] * ((row + 1) / base_height - 1.0f);
const float2 uv0 = {(col + 0) / (2.f * base_width),
1.f - (row + 0) / (2.f * base_height)};
const float2 uv1 = {(col + 1) / (2.f * base_width),
1.f - (row + 1) / (2.f * base_height)};
append_quad({x0, y0, -sz[3]}, {x0, y1, -sz[3]}, {x1, y1, -sz[3]},
{x1, y0, -sz[3]});
{x1, y0, -sz[3]}, {uv0.x, uv0.y}, {uv0.x, uv1.y},
{uv1.x, uv1.y}, {uv1.x, uv0.y});
}
}
}
@@ -303,7 +329,7 @@ static int CalculateHeightFieldVertexCount(const mjModel* model, int hfieldid) {
}
static bool HasUvs(const mjModel* model, int id, MeshType mesh_type) {
return mesh_type != MeshType::kHeightField &&
return mesh_type == MeshType::kHeightField ||
model->mesh_texcoordadr[id] >= 0;
}
@@ -50,19 +50,6 @@ void xtof(float* dst, const T* src, int n) {
}
}
// Returns the tile size for infinite plane texture alignment.
// This is duplicated from engine_vis_visualize.c (re-center infinite plane)
// to ensure UV scaling matches the re-centering increments.
static float GetPlaneTileSize(const mjModel* model, int matid,
float texrepeat) {
if (matid >= 0 && texrepeat > 0) {
return 2.0f / texrepeat;
} else {
const float zfar = model->vis.map.zfar * model->stat.extent;
return 2.1f * zfar / (mjMAXPLANEGRID - 2);
}
}
static mjtCatBit GetBodyCategory(const mjModel* m, int bodyid) {
// mocap subtrees are their own weld, hence not static
if (m->body_weldid[bodyid] == 0) {
@@ -745,12 +732,16 @@ mjrfMaterial ModelRenderables::GetDefaultMaterial(mjtObj obj_type,
mjtGeom geom_type = mjGEOM_NONE;
const float* rgba = nullptr;
const mjtNum* size = nullptr;
const mjtNum* pos = nullptr;
const mjtNum* quat = nullptr;
switch (obj_type) {
case mjOBJ_GEOM:
geom_type = (mjtGeom)model->geom_type[obj_index];
rgba = model->geom_rgba + (4 * obj_index);
size = model->geom_size + (3 * obj_index);
pos = model->geom_pos + (3 * obj_index);
quat = model->geom_quat + (4 * obj_index);
matid = model->geom_matid[obj_index];
break;
case mjOBJ_SITE:
@@ -873,35 +864,49 @@ mjrfMaterial ModelRenderables::GetDefaultMaterial(mjtObj obj_type,
}
}
if (tex_uniform) {
if (fsize[0] > 0) {
material.uv_scale[0] *= fsize[0];
}
if (fsize[1] > 0) {
material.uv_scale[1] *= fsize[1];
}
}
const bool is_infinite_plane =
geom_type == mjGEOM_PLANE && (fsize[0] <= 0 || fsize[1] <= 0);
if (is_infinite_plane) {
// Infinite planes are scaled to match the tile size used by
// re-centering in engine_vis_visualize.c.
// With infinite planes, we want to use the UvOffset to account for
// the plane's position in world space due to re-centering.
//
// Infinite planes will use world-space UVs, with a tile size of 1x1
// world units by default (texrepeat=(1, 1)).
//
// The shader computes: uv = uv0 * UvScale + UvOffset
// We want: uv = dot(worldPos, planeAxis) * texFrequency
//
// Since worldPos = rotation * (objectPos * planeScale) + geomPos:
//
// dot(worldPos, axisK) = objectPos.k * planeScale + dot(geomPos, axisK)
//
// Meaning:
// UvScale = planeScale * texrepeat
// UvOffset = dot(geomPos, planeAxis) * texrepeat
const float plane_scale = static_cast<float>(mjMAXPLANEGRID) / 2.0f;
const float tile_size_x = GetPlaneTileSize(model, matid, tex_repeat[0]);
const float tile_size_y = GetPlaneTileSize(model, matid, tex_repeat[1]);
material.uv_scale[0] = 2.0f * plane_scale / tile_size_x;
material.uv_scale[1] = 2.0f * plane_scale / tile_size_y;
}
// We want to do the equivalent of:
// mjr_setf4(splane, 0.5 * scl.x, 0, 0, -0.5);
// mjr_setf4(tplane, 0, -0.5 * scl.y, 0, -0.5);
// glTexGenfv(GL_S, GL_OBJECT_PLANE, splane);
// glTexGenfv(GL_T, GL_OBJECT_PLANE, tplane);
material.uv_scale[0] = 0.5f * material.uv_scale[0];
material.uv_scale[1] = -0.5f * material.uv_scale[1];
material.uv_offset[0] = -0.5f;
material.uv_offset[1] = -0.5f;
float dot_pos_axis_x = 0.0f;
float dot_pos_axis_y = 0.0f;
if (pos && quat) {
mjtNum mat[9];
mju_quat2Mat(mat, quat);
dot_pos_axis_x = pos[0] * mat[0] + pos[1] * mat[3] + pos[2] * mat[6];
dot_pos_axis_y = pos[0] * mat[1] + pos[1] * mat[4] + pos[2] * mat[7];
}
material.uv_scale[0] *= plane_scale;
material.uv_scale[1] *= plane_scale;
// The vertex UVs in PlaneBuilder are u0 = 0.5*x + 0.5, v0 = -0.5*y + 0.5.
// To keep the world-space texture coordinate u = 0.5*worldX*texrepeat - 0.5
// independent of the snapped geomPos, uv_offset must compensate by 0.5*dot_pos.
material.uv_offset[0] =
(0.5f * dot_pos_axis_x - 0.5f * plane_scale) * tex_repeat[0] - 0.5f;
material.uv_offset[1] =
(-0.5f * dot_pos_axis_y - 0.5f * plane_scale) * tex_repeat[1] - 0.5f;
} else if (tex_uniform) {
material.uv_scale[0] *= (fsize[0] ? fsize[0] : 1.0f);
material.uv_scale[1] *= (fsize[1] ? fsize[1] : 1.0f);
}
} else {
// For cube maps, if `tex_uniform` is true, then scale the texture so that
// it covers a 1x1 area of world space rather than the area of the object.