Add texture coordinates to primitive shape types.
PiperOrigin-RevId: 964882123 Change-Id: I17723d21f29b3fe28e171de5c3d2bea1f55a94c2
This commit is contained in:
committed by
Copybara-Service
parent
4929f2cd99
commit
cc7fb98cf4
@@ -85,6 +85,21 @@ Rendering
|
||||
.. admonition:: Breaking API changes
|
||||
:class: attention
|
||||
|
||||
.. image:: https://www.gstatic.com/mujoco/doc/images/changelog/primitives_textured.gif
|
||||
:align: right
|
||||
:width: 40%
|
||||
|
||||
- Added explicit texture coordinates to built-in geometries (Plane, Box, Sphere, Ellipsoid, Capsule,
|
||||
Cylinder) in both the Classic renderer and Filament. 2D textures applied to primitive shapes will look different
|
||||
as textures are mapped using canonical UV parameterizations rather than projecting onto the :math:`x,y` plane.
|
||||
|
||||
For finite planes, textures are now anchored to the bottom-left corner instead of the center. This will cause the
|
||||
most common visual breakage, as common procedural checker textures will be phase shifted. Infinite planes continue
|
||||
to be anchored at the origin with no visual changes.
|
||||
|
||||
.. image:: images/changelog/plane_uv_tiling.png
|
||||
:align: center
|
||||
:width: 70%
|
||||
- Added :ref:`light/softness<body-light-softness>`: edge softness for spotlights under physically-based lighting
|
||||
models, given as the fraction of the cone over which intensity falls to zero. The default of 0.2 is a semi-soft
|
||||
cone which delivers the full :ref:`intensity<body-light-intensity>` everywhere inside it, so that illuminance
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 163 KiB |
@@ -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
@@ -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();
|
||||
|
||||
@@ -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]) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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_;
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user