writeSTL <- function(con, ascii=FALSE, pointRadius=0.005, 
                     pointShape = icosahedron3d(),
                     lineRadius = pointRadius,
                     lineSides = 20,
                     ids = tagged3d(tags),
                     tags = NULL) {
 
  writeHeader <- function() {
    ident <- paste(filename, " produced by RGL\n")
    if (ascii) 
      cat("solid ", ident, file=con)
    else {
      padding <- paste(rep(" ", 80), collapse="")
      ident <- substr( paste("binary", ident, padding), 1, 80)
      writeChar(ident, con, nchars=80, useBytes=TRUE, eos=NULL)
      writeBin(0L, con, size=4, endian="little")
    }
  }
  
  finish  <- function()
  {  if (!ascii) {
      seek(con, 80)
      writeBin(as.integer(triangles), con, size=4, endian="little")
     } else
       cat("endsolid\n", file = con)
  }
      
  triangles <- 0
  
  writeTriangles <- function(vertices) {
    if (nrow(vertices) %% 3 != 0) stop("Need 3N vertices")
    n <- nrow(vertices) / 3
    for (i in seq_len(n)) {
      vec0 <- vertices[3*i - 2,]
      vec1 <- vertices[3*i - 1,]
      vec2 <- vertices[3*i,]
      normal <- normalize(xprod(vec2-vec0, vec1-vec0))
      if (ascii) {
        cat("facet normal ", normal, "\n", file=con)
        cat("outer loop\n", file=con)
        cat("vertex ", vec0, "\n", file=con)
        cat("vertex ", vec1, "\n", file=con)
        cat("vertex ", vec2, "\n", file=con)
        cat("endloop\n", file=con)
        cat("endfacet\n", file=con)
      } else {
        writeBin(c(normal, vec0, vec1, vec2), con, size=4, endian="little")
        writeBin(0L, con, size=2, endian="little")
      }
    }
    triangles <<- triangles + n
  }
  
  writeQuads <- function(vertices) {
    if (nrow(vertices) %% 4 != 0) stop("Need 4N vertices")
    n <- nrow(vertices) / 4
    indices <- rep(seq_len(n)*4, each=6) - rep(c(3, 2, 1, 3, 1, 0), n)
    writeTriangles( vertices[indices,] )
  }
      
  writeSurface <- function(id) {
    vertices <- expandVertices(id)
    dims <- rgl.attrib(id, "dim")
    nx <- dims[1]
    nz <- dims[2]
    indices <- integer(0)
    for (j in seq_len(nx-1) - 1) {
      v1 <- j + nx*(seq_len(nz) - 1) + 1
      indices <- c(indices, rbind(v1[-nz], v1[-nz]+1, v1[-1]+1, v1[-1]))
    }
    writeQuads(vertices[indices,])
  }
  
  writeMesh <- function(mesh, scale=1, offset=c(0,0,0)) {
    vertices <- asEuclidean(t(mesh$vb))*scale 
    vertices <- vertices + rep(offset, each=nrow(vertices))
    nt <- length(mesh$it)/3
    nq <- length(mesh$ib)/4
    newverts <- matrix(NA, 3*nt+6*nq, 3)
    if (nt) 
      newverts[1:(3*nt),] <- vertices[c(mesh$it),]
    if (nq)
      newverts[3*nt+1:(6*nq),] <- vertices[c(mesh$ib[1:3,], 
      				    mesh$ib[c(1,3,4),]),]
    writeTriangles(newverts)
  }

  writeSpheres <- function(id) {
    vertices <- expandVertices(id)
    radii <- rgl.attrib(id, "radii") # FIXME to handle indices?
    n <- nrow(vertices)
    radii <- rep(radii, length.out=n)
    x <- subdivision3d(icosahedron3d(),3)
    r <- sqrt(x$vb[1,]^2 + x$vb[2,]^2 + x$vb[3,]^2)
    x$vb[4,] <- r
    for (i in seq_along(radii)) 
      writeMesh(x, radii[i], vertices[i,])
  }  
  
  avgScale <- function() {
    bbox <- par3d("bbox")
    ranges <- c(bbox[2]-bbox[1], bbox[4]-bbox[3], bbox[6]-bbox[5])
    if (prod(ranges) == 0) 1
    else exp(mean(log(ranges)))
  }  
   
  writePoints <- function(id) {
    vertices <- expandVertices(id)
    n <- nrow(vertices)
    radius <- pointRadius*avgScale()
    for (i in seq_len(n))
      writeMesh(pointShape, radius, vertices[i,])
  }
  
  writeSegments <- function(id) {
    vertices <- expandVertices(id)
    n <- nrow(vertices)/2
    radius <- lineRadius*avgScale()
    for (i in seq_len(n)) {
      cyl <- cylinder3d(vertices[(2*i-1):(2*i),], 
      			radius = radius,
      			sides = lineSides,
      			closed = -2)
      writeMesh(cyl)
    }
  }
  
  writeLines <- function(id) {
    vertices <- expandVertices(id)
    n <- nrow(vertices) - 1
    radius <- lineRadius*avgScale()
    for (i in seq_len(n)) 
      writeMesh( cylinder3d( vertices[i:(i+1),],
     			   radius = radius,
     			   sides = lineSides, 
     			   closed = -2) )
  }
  
  knowntypes <- c("triangles", "quads",
                  "surface", "planes", "spheres",
                  "points", "lines", "linestrip")
  
  #  Execution starts here!

  if (is.character(con)) {
    con <- file(con, if (ascii) "w" else "wb")
    on.exit(close(con))
  }
  filename <- summary(con)$description
  
  if (NROW(bbox <- ids3d("bboxdeco")) && (is.null(ids) || bbox$id %in% ids)) {
    ids <- setdiff(ids, bbox$id)
    save <- par3d(skipRedraw = TRUE)
    bbox <- convertBBox(bbox$id)
    on.exit({ pop3d(id=bbox); par3d(save) }, add=TRUE) # nolint
    dobbox <- TRUE
  } else dobbox <- FALSE 
  
  if (is.null(ids)) {
    ids <- ids3d()
    types <- as.character(ids$type)
    ids <- ids$id
  } else {
    if (dobbox) ids <- c(ids, bbox)
    allids <- ids3d()
    ind <- match(ids, allids$id)
    keep <- !is.na(ind)
    if (any(!keep)) warning(gettextf("Object(s) with id %s not found", paste(ids[!keep], collapse=" ")),
    			    domain = NA)
    ids <- ids[keep]
    types <- allids$type[ind[keep]]
  }  
    
  unknowntypes <- setdiff(types, knowntypes)
  if (length(unknowntypes))
    warning(gettextf("Object type(s) %s not handled", 
      paste("'", unknowntypes, "'", sep="", collapse=", ")), domain = NA)

  keep <- types %in% knowntypes
  ids <- ids[keep]
  types <- types[keep]

  writeHeader()
  
  for (i in seq_along(ids)) 
    switch(types[i],
      planes =,
      triangles = writeTriangles(expandVertices(ids[i])),
      quads = writeQuads(expandVertices(ids[i])),
      surface = writeSurface(ids[i]),
      spheres = writeSpheres(ids[i]),
      points = writePoints(ids[i]),
      lines = writeSegments(ids[i]),
      linestrip = writeLines(ids[i])
    )
  
  finish()
    
  invisible(filename)
}

readSTL <- function(con, ascii=NA, plot=TRUE, ...) {
 
  # Utility functions and constants defined first; execution starts way down there...
  
  triangles <- NULL
  
  readHeader <- function() {
    if (is.na(ascii)) {
      summ <- summary(con)
      if (summ$class != "file" || readChar(con, 5) != "solid")
        ascii <<- FALSE
      else {
        ascii <<- TRUE
        filename <- summ$description
        close(con)
        con <<- file(filename, "rt")
      }
    }
    if (ascii) {
      ident <- readLines(con, 1)
      if (!grepl("^solid ", ident)) stop("This does not appear to be an ASCII STL file")
    }
    if (!ascii) {
      seek(con, 80)
      triangles <<- readBin(con, "integer", n=1, size=4, endian="little")
    }
  }
  
  readTriangles <- function(n) {
    
    if (ascii) {
      lines <- readLines(con)
      facet <- c(grep("^ *facet", lines),
                 length(lines) + 1) # sentinel
      n <- length(facet) - 1
      vertex <- grep("^ *vertex ", lines)
    }
   
    vertices <- matrix(NA, 3*n, 3)
    
    if (ascii) {
      for (i in seq_len(n)) {
        j <- vertex[facet[i] < vertex & vertex < facet[i+1]]

        if (any(is.na(j)) || length(j) != 3) {
          warning("problem on triangle ", i, " j = ", j)
          j <- j[1:3]
        }
        v <- read.table(text = lines[j],
                        colClasses = c("character", "numeric", "numeric", "numeric"))
        vertices[3*i + c(-2, -1, 0),] <- as.matrix(v[, 2:4])
      }
    } else {
      for (i in seq_len(n)) {
        m <- matrix(readBin(con, "double", n=12, size=4, endian="little"), 4, 3, byrow=TRUE)
        vertices[3*i + c(-2,-1,0),] <- m[2:4,]
        count <- readBin(con, "integer", n=1, size=2, endian="little") # nolint
      }
    }
    vertices
  }
      
  #  Execution starts here!

  if (is.character(con)) {
    con <- file(con, if (isTRUE(ascii)) "rt" else "rb")
    on.exit(close(con))
  }
  
  readHeader()
  vertices <- readTriangles(triangles)
  if (plot)
    triangles3d(vertices, ...)
  else
    vertices
}
