The as.mesh3d generic function converts various objects to mesh3d objects.

The default method takes takes a matrix of vertices as input and (optionally) merges repeated vertices, producing a mesh3d object as output. It will contain either triangles or quads or segments or points according to the type argument.

If the generic is called without any argument, it will pass all RGL ids from the current scene to the as.mesh3d.rglId method.

as.mesh3d(x, ...)
# S3 method for default
as.mesh3d(x, y = NULL, z = NULL,
type = c("triangles", "quads", "segments", "points"),
smooth = FALSE,
tolerance = sqrt(.Machine$double.eps), notEqual = NULL, merge = TRUE, ..., triangles) ## Arguments x, y, z For the generic, x is the object to convert. For the default method, x, y and z are coordinates. Any reasonable way of defining the coordinates is acceptable. See the function xyz.coords for details. type What type of things should be in the mesh? Tries this list in order until it finds one that works. smooth If TRUE, addNormals will be called on the mesh object to make it render smoothly. tolerance The numerical tolerance to be used in all.equal to determine whether two vertices should be merged. notEqual If not NULL, an n by n matrix of logical values, where n is the number of vertices as input. TRUE entries indicate that the corresponding pair of vertices should not be merged even if they appear equal. merge Should apparently equal vertices be merged? ... Material properties to pass to tmesh3d or qmesh3d. triangles Deprecated. If present, TRUE indicates type = "triangles" and FALSE indicates type = "quads". ## Details The motivation for this function is the following problem: I was asked whether RGL could render a surface made up of triangles or quadrilaterals to look smooth. It can do that, but needs normals at each vertex; they should be the average of the normals for each polygon sharing that vertex. Then OpenGL will interpolate the normals across the polygons and give the illusion of smoothness. To do this, it needs to know which polygons share each vertex. If the surface is described as a list of triangles or quadrilaterals, that means identifying vertices that are in multiple polygons, and converting the representation to a "mesh3d" object (which is a matrix of vertices and a matrix of vertex numbers making up triangles or quads). Then the addNormals function will add the normals. Sometimes two polygons will share vertices (within numerical tolerance) without the user wanting them to be considered internal to the surface, or might want one sharp edge in an otherwise smooth surface. This means I needed a way to declare that two vertices from the original list of vertices in the triangles or quads are "not equal", even when they test numerically equal. That's what the notEqual matrix specifies. ## Value A "mesh3d" object with the same faces as in the input, but (if merge=TRUE) with vertices that test equal to within tolerance merged. ## Author Duncan Murdoch ## Examples xyz <- matrix(c(-1, -1, -1, -1, 1, -1, 1, 1, -1, 1, -1, -1, -1, 1, -1, -1, 1, 1, 1, 1, 1, 1, 1, -1, 1, -1, -1, 1, 1, -1, 1, 1, 1, 1, -1, 1), byrow = TRUE, ncol = 3) mesh <- as.mesh3d(xyz, type = "quads", col = "red") mesh$vb
#>      [,1] [,2] [,3] [,4] [,5] [,6] [,7]
#> [1,]   -1   -1    1    1   -1    1    1
#> [2,]   -1    1    1   -1    1    1   -1
#> [3,]   -1   -1   -1   -1    1    1    1
#> [4,]    1    1    1    1    1    1    1
mesh$ib #> [,1] [,2] [,3] #> [1,] 1 2 4 #> [2,] 2 5 3 #> [3,] 3 6 6 #> [4,] 4 3 7 open3d() shade3d(mesh) {"x":{"material":{"color":"#000000","alpha":1,"lit":true,"ambient":"#000000","specular":"#FFFFFF","emission":"#000000","shininess":50,"smooth":true,"front":"filled","back":"filled","size":3,"lwd":1,"fog":true,"point_antialias":false,"line_antialias":false,"texture":null,"textype":"rgb","texmipmap":false,"texminfilter":"linear","texmagfilter":"linear","texenvmap":false,"depth_mask":true,"depth_test":"less","isTransparent":false,"polygon_offset":[0,0],"margin":"","floating":false,"tag":""},"rootSubscene":106,"objects":{"112":{"id":112,"type":"quads","material":{},"vertices":"0","colors":"2","centers":"3","normals":"1","ignoreExtent":false,"flags":32771},"110":{"id":110,"type":"light","vertices":[[0,0,1]],"colors":[[1,1,1,1],[1,1,1,1],[1,1,1,1]],"viewpoint":true,"finite":false},"109":{"id":109,"type":"background","material":{},"colors":"4","centers":"5","sphere":false,"fogtype":"none","fogscale":1,"flags":32768},"111":{"id":111,"type":"background","material":{"lit":false,"back":"lines"},"colors":"6","centers":"7","sphere":false,"fogtype":"none","fogscale":1,"flags":32768},"106":{"id":106,"type":"subscene","par3d":{"antialias":8,"FOV":30,"ignoreExtent":false,"listeners":106,"mouseMode":{"none":"none","left":"trackball","right":"zoom","middle":"fov","wheel":"pull"},"observer":[0,0,6.69213056564331],"modelMatrix":[[1,0,0,0],[0,0.342020153999329,0.939692616462708,0],[0,-0.939692616462708,0.342020153999329,-6.69213056564331],[0,0,0,1]],"projMatrix":[[3.73205089569092,0,0,0],[0,3.73205089569092,0,0],[0,0,-3.86370348930359,-24.1243553161621],[0,0,-1,0]],"skipRedraw":false,"userMatrix":[[1,0,0,0],[0,0.342020143325668,0.939692620785909,0],[0,-0.939692620785909,0.342020143325668,0],[0,0,0,1]],"userProjection":[[1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]],"scale":[1,1,1],"viewport":{"x":0,"y":0,"width":1,"height":1},"zoom":1,"bbox":[-1,1,-1,1,-1,1],"windowRect":[80,125,336,381],"family":"sans","font":1,"cex":1,"useFreeType":true,"fontname":"/System/Library/Fonts/Supplemental/Arial.ttf","maxClipPlanes":6,"glVersion":2.1,"activeSubscene":0},"embeddings":{"viewport":"replace","projection":"replace","model":"replace","mouse":"replace"},"objects":[111,112,110],"subscenes":[],"flags":33027}},"crosstalk":{"key":[],"group":[],"id":[],"options":[]},"width":480,"height":480,"buffer":{"accessors":[{"bufferView":0,"componentType":5120,"count":12,"type":"VEC3"},{"bufferView":1,"componentType":5120,"count":12,"type":"VEC3"},{"bufferView":2,"componentType":5121,"count":1,"type":"VEC4"},{"bufferView":3,"componentType":5120,"count":3,"type":"VEC3"},{"bufferView":4,"componentType":5126,"count":1,"type":"VEC4"},{"bufferView":5,"componentType":5121,"count":1,"type":"VEC3"},{"bufferView":6,"componentType":5121,"count":1,"type":"VEC4"},{"bufferView":7,"componentType":5121,"count":1,"type":"VEC3"}],"bufferViews":[{"buffer":0,"byteLength":36,"byteOffset":0},{"buffer":0,"byteLength":36,"byteOffset":36},{"buffer":0,"byteLength":4,"byteOffset":72},{"buffer":0,"byteLength":9,"byteOffset":76},{"buffer":0,"byteLength":16,"byteOffset":88},{"buffer":0,"byteLength":3,"byteOffset":104},{"buffer":0,"byteLength":4,"byteOffset":107},{"buffer":0,"byteLength":3,"byteOffset":111}],"buffers":[{"byteLength":114,"bytes":"/////wH/AQH/Af///wH//wEBAQEBAQH/Af//AQH/AQEBAf8BAAD/AAD/AAD/AAD/AAEAAAEA\nAAEAAAEAAQAAAQAAAQAAAQAAAQAAAQAA/wABAAEAAAAAAJmYmD6ZmJg+mZiYPgAAgD8AAAAB\nAQEBAAAA"}]},"context":{"shiny":false,"rmarkdown":null},"vertexShader":"#line 2 1\n// File 1 is the vertex shader\n#ifdef GL_ES\n#ifdef GL_FRAGMENT_PRECISION_HIGH\nprecision highp float;\n#else\nprecision mediump float;\n#endif\n#endif\n\nattribute vec3 aPos;\nattribute vec4 aCol;\nuniform mat4 mvMatrix;\nuniform mat4 prMatrix;\nvarying vec4 vCol;\nvarying vec4 vPosition;\n\n#ifdef NEEDS_VNORMAL\nattribute vec3 aNorm;\nuniform mat4 normMatrix;\nvarying vec4 vNormal;\n#endif\n\n#if defined(HAS_TEXTURE) || defined (IS_TEXT)\nattribute vec2 aTexcoord;\nvarying vec2 vTexcoord;\n#endif\n\n#ifdef FIXED_SIZE\nuniform vec3 textScale;\n#endif\n\n#ifdef FIXED_QUADS\nattribute vec3 aOfs;\n#endif\n\n#ifdef IS_TWOSIDED\n#ifdef HAS_NORMALS\nvarying float normz;\nuniform mat4 invPrMatrix;\n#else\nattribute vec3 aPos1;\nattribute vec3 aPos2;\nvarying float normz;\n#endif\n#endif // IS_TWOSIDED\n\n#ifdef FAT_LINES\nattribute vec3 aNext;\nattribute vec2 aPoint;\nvarying vec2 vPoint;\nvarying float vLength;\nuniform float uAspect;\nuniform float uLwd;\n#endif\n\n\nvoid main(void) {\n \n#ifndef IS_BRUSH\n#if defined(NCLIPPLANES) || !defined(FIXED_QUADS) || defined(HAS_FOG)\n vPosition = mvMatrix * vec4(aPos, 1.);\n#endif\n \n#ifndef FIXED_QUADS\n gl_Position = prMatrix * vPosition;\n#endif\n#endif // !IS_BRUSH\n \n#ifdef IS_POINTS\n gl_PointSize = POINTSIZE;\n#endif\n \n vCol = aCol;\n \n#ifdef NEEDS_VNORMAL\n vNormal = normMatrix * vec4(-aNorm, dot(aNorm, aPos));\n#endif\n \n#ifdef IS_TWOSIDED\n#ifdef HAS_NORMALS\n /* normz should be calculated *after* projection */\n normz = (invPrMatrix*vNormal).z;\n#else\n vec4 pos1 = prMatrix*(mvMatrix*vec4(aPos1, 1.));\n pos1 = pos1/pos1.w - gl_Position/gl_Position.w;\n vec4 pos2 = prMatrix*(mvMatrix*vec4(aPos2, 1.));\n pos2 = pos2/pos2.w - gl_Position/gl_Position.w;\n normz = pos1.x*pos2.y - pos1.y*pos2.x;\n#endif\n#endif // IS_TWOSIDED\n \n#ifdef NEEDS_VNORMAL\n vNormal = vec4(normalize(vNormal.xyz/vNormal.w), 1);\n#endif\n \n#if defined(HAS_TEXTURE) || defined(IS_TEXT)\n vTexcoord = aTexcoord;\n#endif\n \n#if defined(FIXED_SIZE) && !defined(ROTATING)\n vec4 pos = prMatrix * mvMatrix * vec4(aPos, 1.);\n pos = pos/pos.w;\n gl_Position = pos + vec4(aOfs*textScale, 0.);\n#endif\n \n#if defined(IS_SPRITES) && !defined(FIXED_SIZE)\n vec4 pos = mvMatrix * vec4(aPos, 1.);\n pos = pos/pos.w + vec4(aOfs, 0.);\n gl_Position = prMatrix*pos;\n#endif\n \n#ifdef FAT_LINES\n /* This code was inspired by Matt Deslauriers' code in \n https://mattdesl.svbtle.com/drawing-lines-is-hard */\n vec2 aspectVec = vec2(uAspect, 1.0);\n mat4 projViewModel = prMatrix * mvMatrix;\n vec4 currentProjected = projViewModel * vec4(aPos, 1.0);\n currentProjected = currentProjected/currentProjected.w;\n vec4 nextProjected = projViewModel * vec4(aNext, 1.0);\n vec2 currentScreen = currentProjected.xy * aspectVec;\n vec2 nextScreen = (nextProjected.xy / nextProjected.w) * aspectVec;\n float len = uLwd;\n vec2 dir = vec2(1.0, 0.0);\n vPoint = aPoint;\n vLength = length(nextScreen - currentScreen)/2.0;\n vLength = vLength/(vLength + len);\n if (vLength > 0.0) {\n dir = normalize(nextScreen - currentScreen);\n }\n vec2 normal = vec2(-dir.y, dir.x);\n dir.x /= uAspect;\n normal.x /= uAspect;\n vec4 offset = vec4(len*(normal*aPoint.x*aPoint.y - dir), 0.0, 0.0);\n gl_Position = currentProjected + offset;\n#endif\n \n#ifdef IS_BRUSH\n gl_Position = vec4(aPos, 1.);\n#endif\n}","fragmentShader":"#line 2 2\n// File 2 is the fragment shader\n#ifdef GL_ES\n#ifdef GL_FRAGMENT_PRECISION_HIGH\nprecision highp float;\n#else\nprecision mediump float;\n#endif\n#endif\nvarying vec4 vCol; // carries alpha\nvarying vec4 vPosition;\n#if defined(HAS_TEXTURE) || defined (IS_TEXT)\nvarying vec2 vTexcoord;\nuniform sampler2D uSampler;\n#endif\n\n#ifdef HAS_FOG\nuniform int uFogMode;\nuniform vec3 uFogColor;\nuniform vec4 uFogParms;\n#endif\n\n#if defined(IS_LIT) && !defined(FIXED_QUADS)\nvarying vec4 vNormal;\n#endif\n\n#if NCLIPPLANES > 0\nuniform vec4 vClipplane[NCLIPPLANES];\n#endif\n\n#if NLIGHTS > 0\nuniform mat4 mvMatrix;\n#endif\n\n#ifdef IS_LIT\nuniform vec3 emission;\nuniform float shininess;\n#if NLIGHTS > 0\nuniform vec3 ambient[NLIGHTS];\nuniform vec3 specular[NLIGHTS]; // light*material\nuniform vec3 diffuse[NLIGHTS];\nuniform vec3 lightDir[NLIGHTS];\nuniform bool viewpoint[NLIGHTS];\nuniform bool finite[NLIGHTS];\n#endif\n#endif // IS_LIT\n\n#ifdef IS_TWOSIDED\nuniform bool front;\nvarying float normz;\n#endif\n\n#ifdef FAT_LINES\nvarying vec2 vPoint;\nvarying float vLength;\n#endif\n\nvoid main(void) {\n vec4 fragColor;\n#ifdef FAT_LINES\n vec2 point = vPoint;\n bool neg = point.y < 0.0;\n point.y = neg ? (point.y + vLength)/(1.0 - vLength) :\n -(point.y - vLength)/(1.0 - vLength);\n#if defined(IS_TRANSPARENT) && defined(IS_LINESTRIP)\n if (neg && length(point) <= 1.0) discard;\n#endif\n point.y = min(point.y, 0.0);\n if (length(point) > 1.0) discard;\n#endif // FAT_LINES\n \n#ifdef ROUND_POINTS\n vec2 coord = gl_PointCoord - vec2(0.5);\n if (length(coord) > 0.5) discard;\n#endif\n \n#if NCLIPPLANES > 0\n for (int i = 0; i < NCLIPPLANES; i++)\n if (dot(vPosition, vClipplane[i]) < 0.0) discard;\n#endif\n \n#ifdef FIXED_QUADS\n vec3 n = vec3(0., 0., 1.);\n#elif defined(IS_LIT)\n vec3 n = normalize(vNormal.xyz);\n#endif\n \n#ifdef IS_TWOSIDED\n if ((normz <= 0.) != front) discard;\n#endif\n \n#ifdef IS_LIT\n vec3 eye = normalize(-vPosition.xyz/vPosition.w);\n vec3 lightdir;\n vec4 colDiff;\n vec3 halfVec;\n vec4 lighteffect = vec4(emission, 0.);\n vec3 col;\n float nDotL;\n#ifdef FIXED_QUADS\n n = -faceforward(n, n, eye);\n#endif\n \n#if NLIGHTS > 0\n for (int i=0;i<NLIGHTS;i++) {\n colDiff = vec4(vCol.rgb * diffuse[i], vCol.a);\n lightdir = lightDir[i];\n if (!viewpoint[i])\n lightdir = (mvMatrix * vec4(lightdir, 1.)).xyz;\n if (!finite[i]) {\n halfVec = normalize(lightdir + eye);\n } else {\n lightdir = normalize(lightdir - vPosition.xyz/vPosition.w);\n halfVec = normalize(lightdir + eye);\n }\n col = ambient[i];\n nDotL = dot(n, lightdir);\n col = col + max(nDotL, 0.) * colDiff.rgb;\n col = col + pow(max(dot(halfVec, n), 0.), shininess) * specular[i];\n lighteffect = lighteffect + vec4(col, colDiff.a);\n }\n#endif\n \n#else // not IS_LIT\n vec4 colDiff = vCol;\n vec4 lighteffect = colDiff;\n#endif\n \n#ifdef IS_TEXT\n vec4 textureColor = lighteffect*texture2D(uSampler, vTexcoord);\n#endif\n \n#ifdef HAS_TEXTURE\n#ifdef TEXTURE_rgb\n vec4 textureColor = lighteffect*vec4(texture2D(uSampler, vTexcoord).rgb, 1.);\n#endif\n \n#ifdef TEXTURE_rgba\n vec4 textureColor = lighteffect*texture2D(uSampler, vTexcoord);\n#endif\n \n#ifdef TEXTURE_alpha\n vec4 textureColor = texture2D(uSampler, vTexcoord);\n float luminance = dot(vec3(1.,1.,1.), textureColor.rgb)/3.;\n textureColor = vec4(lighteffect.rgb, lighteffect.a*luminance);\n#endif\n \n#ifdef TEXTURE_luminance\n vec4 textureColor = vec4(lighteffect.rgb*dot(texture2D(uSampler, vTexcoord).rgb, vec3(1.,1.,1.))/3., lighteffect.a);\n#endif\n \n#ifdef TEXTURE_luminance_alpha\n vec4 textureColor = texture2D(uSampler, vTexcoord);\n float luminance = dot(vec3(1.,1.,1.),textureColor.rgb)/3.;\n textureColor = vec4(lighteffect.rgb*luminance, lighteffect.a*textureColor.a);\n#endif\n \n fragColor = textureColor;\n\n#elif defined(IS_TEXT)\n if (textureColor.a < 0.1)\n discard;\n else\n fragColor = textureColor;\n#else\n fragColor = lighteffect;\n#endif // HAS_TEXTURE\n \n#ifdef HAS_FOG\n // uFogParms elements: x = near, y = far, z = fogscale, w = (1-sin(FOV/2))/(1+sin(FOV/2))\n // In Exp and Exp2: use density = density/far\n // fogF will be the proportion of fog\n // Initialize it to the linear value\n float fogF;\n if (uFogMode > 0) {\n fogF = (uFogParms.y - vPosition.z/vPosition.w)/(uFogParms.y - uFogParms.x);\n if (uFogMode > 1)\n fogF = mix(uFogParms.w, 1.0, fogF);\n fogF = fogF*uFogParms.z;\n if (uFogMode == 2)\n fogF = 1.0 - exp(-fogF);\n // Docs are wrong: use (density*c)^2, not density*c^2\n // https://gitlab.freedesktop.org/mesa/mesa/-/blob/master/src/mesa/swrast/s_fog.c#L58\n else if (uFogMode == 3)\n fogF = 1.0 - exp(-fogF*fogF);\n fogF = clamp(fogF, 0.0, 1.0);\n gl_FragColor = vec4(mix(fragColor.rgb, uFogColor, fogF), fragColor.a);\n } else gl_FragColor = fragColor;\n#else\n gl_FragColor = fragColor;\n#endif // HAS_FOG\n \n}","players":[],"webGLoptions":{"preserveDrawingBuffer":true}},"evals":[],"jsHooks":[]} # Stop vertices 2 and 5 from being merged notEQ <- matrix(FALSE, 12, 12) notEQ[2, 5] <- TRUE mesh <- as.mesh3d(xyz, type = "quads", notEqual = notEQ) mesh$vb
#>      [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8]
#> [1,]   -1   -1    1    1   -1   -1    1    1
#> [2,]   -1    1    1   -1    1    1    1   -1
#> [3,]   -1   -1   -1   -1   -1    1    1    1
#> [4,]    1    1    1    1    1    1    1    1
mesh\$ib
#>      [,1] [,2] [,3]
#> [1,]    1    5    4
#> [2,]    2    6    3
#> [3,]    3    7    7
#> [4,]    4    3    8