dmurdoch / rgl

rgl is a 3D visualization system based on OpenGL. It provides a medium to high level interface for use in R, currently modelled on classic R graphics, with extensions to allow for interaction.
https://dmurdoch.github.io/rgl/
GNU General Public License v2.0
85 stars 20 forks source link

`writeSTL()` does not support `indices` argument when used with `triangles3d()` #293

Closed tylermorganwall closed 1 year ago

tylermorganwall commented 1 year ago

Currently, the writeSTL() function does not write the correct STL model to file when the user uses the indices argument in triangles3d(). See example code below:

library(rgl)
#Draw a basic plane with vertices and indices
vertices = matrix(c(0,0,0,
                    1,0,0,
                    0,1,0,
                    1,1,0),
                  nrow=4,ncol=3,byrow=T)

indices = c(1,2,3,
            2,4,3)

triangles3d(x=vertices,
            indices = indices)

#This errors out "Need 3N vertices"
writeSTL("teststl.stl")
#> Error in writeTriangles(rgl.attrib(ids[i], "vertices")): Need 3N vertices
close3d()

#Change number of vertices, use same indices
vertices = matrix(c(0,0,0,
                    1,0,0,
                    0,1,0,
                    1,1,0,
                    0,0,0,
                    0,0,0),
                  nrow=6,ncol=3,byrow=T)

indices = c(1,2,3,
            2,4,3)

triangles3d(x=vertices,
                 indices = indices)

#This writes successfully, but does not write the correct model
writeSTL("teststl2.stl")
close3d()
dmurdoch commented 1 year ago

I see this too. It's probably easy to fix...

tylermorganwall commented 1 year ago

Yep, I ended up writing a custom version for rayshader that patched in a fix, along with additional functionality I needed. I see your other write* functions in stl.R pre-index the vertex matrix with the indices for those shapes, so you could probably just do the same here.

#rayshader STL fix in `write_stl.R` 
  for(id in id_val) {
    vertices = rgl::rgl.attrib(id, "vertices")
    indices = rgl::rgl.attrib(id, "indices")
    if(nrow(indices) == 0) {
      indices = matrix(1:nrow(vertices), ncol = 3, nrow = nrow(vertices)/3, byrow = TRUE)
    } else {
      indices = matrix(indices, ncol = 3, nrow = nrow(indices)/3, byrow = TRUE)
    }
    triangles = triangles + nrow(indices)
    for (i in seq_len(nrow(indices))) {
      if(!rotate) {
        vec0 = vertices[indices[i,1],] * multiplier
        vec1 = vertices[indices[i,2],] * multiplier
        vec2 = vertices[indices[i,3],] * multiplier
        normal = unit_vector(cross(as.vector(vec2-vec0), as.vector(vec1-vec0)))
      } else {
        vec0 = rot_z_90(vertices[indices[i,1],] * multiplier) 
        vec1 = rot_z_90(vertices[indices[i,2],] * multiplier) 
        vec2 = rot_z_90(vertices[indices[i,3],] * multiplier) 
        #The inverse transpose of zrot here is the same as zrot, so we can reuse the same function
        normal = rot_z_90(unit_vector(cross(as.vector(vec2-vec0), as.vector(vec1-vec0))))
      }
      writeBin(c(normal, vec0, vec1, vec2), con, size = 4, endian = "little")
      writeBin(0L, con, size = 2, endian = "little")
    }
  }
dmurdoch commented 1 year ago

There are a lot of functions that use rgl.attrib(id, "vertices"), and I'll have to check each one to see whether it handles indices properly or not. This will take a while, but in the meantime I have put in a fix for writeSTL only in the new "issue293" branch.