@jscad/regl-renderer
Version:
Renderer for JSCAD Geometries
218 lines (197 loc) • 6.99 kB
JavaScript
const vec3 = require('gl-vec3')
const mat4 = require('gl-mat4')
const maxIndex = 65535
/*
* Convert the given solid into one or more geometries for rendering.
* @param {Object} options - options for conversion
* @param {Array} options.color - RGBA of solid
* @param {Boolean} options.smoothLighting - set to true in order to use interpolated vertex normals
* this creates nice round spheres but does not represent the shape of the actual model
* @param {geom3} solid - the solid to convert
* @return {Array} list of new geometries
*/
const geom3ToGeometries = (options, solid) => {
let { color, smoothLighting } = options
if ('color' in solid) color = solid.color
const polygons = solid.polygons
const transforms = solid.transforms ? mat4.clone(solid.transforms) : mat4.create()
const geometries = []
let setstart = 0
while (setstart < polygons.length) {
// calculate end of this set
let vcount = 0
let setend = setstart
for (let i = setstart; i < polygons.length; i++) {
vcount += polygons[i].vertices.length
if (vcount > maxIndex) break
setend++
}
// temporary storage
// create attributes of geometry
const positions = []
const normals = []
const indices = []
const colors = []
const isTransparent = (color[3] < 1.0)
for (let i = setstart; i < setend; i++) {
const polygon = polygons[i]
const vertices = polygon.vertices
const normal = calculateNormal(polygon)
const faceColor = polygonColor(polygon, color)
const polygonIndices = []
for (let j = 0; j < vertices.length; j++) {
const position = vertices[j]
positions.push(position)
normals.push(normal)
colors.push(faceColor)
const index = positions.length - 1
polygonIndices.push(index)
}
// add indices to list
for (let j = 2; j < polygonIndices.length; j++) {
indices.push([polygonIndices[0], polygonIndices[j - 1], polygonIndices[j]])
}
}
// FIXME positions should be Float32Array buffers to eliminate another conversion
// FIXME normals should be Float32Array buffers to eliminate another conversion
// FIXME indices should be Uint16Array buffers to eliminate another conversion
// FIXME colors should be Float32Array buffers to eliminate another conversion
// assemble the geometry
const geometry = {
type: '3d',
positions,
normals,
indices,
colors,
transforms,
isTransparent
}
geometries.push(geometry)
// continue on
setstart = setend
}
return geometries
}
/*
* TODO
* TODO Implement smoothing
* TODO
*/
const smoothing = () => {
const polygons = []
const color = []
let isTransparent = true
const smoothLighting = true
const normalThreshold = 0.5 // threshold beyond which to split normals
const positions = []
const normals = []
const indices = []
const colors = []
const normalPositionLookup = []
let tupplesIndex = 0
for (let i = 0; i < polygons.length; i++) {
const polygon = polygons[i]
const vertices = polygon.vertices
const faceColor = polygonColor(polygon, color)
const normal = calculateNormal(polygon)
if (faceColor && faceColor[3] !== 1) isTransparent = true
const polygonIndices = []
// we need unique tupples of normal + position, that gives us a specific index (indices)
// if the angle between a given normal and another normal is less than X they are considered the same
for (let j = 0; j < vertices.length; j++) {
let index
const position = vertices[j]
if (smoothLighting) {
const candidateTupple = { normal, position }
const existingTupple = fuzyNormalAndPositionLookup(normalPositionLookup, candidateTupple, normalThreshold)
if (!existingTupple) {
const existingPositing = normalPositionLookup[candidateTupple.position]
const itemToAdd = [{ normal: candidateTupple.normal, index: tupplesIndex }]
if (!existingPositing) {
normalPositionLookup[candidateTupple.position] = itemToAdd
} else {
normalPositionLookup[candidateTupple.position] = normalPositionLookup[candidateTupple.position]
.concat(itemToAdd)
}
index = tupplesIndex
if (faceColor) {
colors.push(faceColor)
}
normals.push(normal)
positions.push(position)
tupplesIndex += 1
} else {
index = existingTupple.index
}
} else {
if (faceColor) {
colors.push(faceColor)
}
normals.push(normal)
positions.push(position)
index = positions.length - 1
}
polygonIndices.push(index)
}
for (let j = 2; j < polygonIndices.length; j++) {
indices.push([polygonIndices[0], polygonIndices[j - 1], polygonIndices[j]])
}
}
}
/**
* return the color information of a polygon
* @param {Object} polygon a polygon
* @param {Object} color a default color
* @returns {Array} `[r, g, b, a]`
*/
const polygonColor = (polygon, color) => {
let faceColor = color
if (polygon.color) {
faceColor = polygon.color
}
// opaque is default
if (faceColor && faceColor.length < 4) {
faceColor.push(1.0)
}
return faceColor
}
/*
* Calculate the normal of the given polygon
*/
const calculateNormal = (polygon) => {
if (polygon.plane) return vec3.clone(polygon.plane)
const vertices = polygon.vertices
const ba = vec3.create()
vec3.subtract(ba, vertices[1], vertices[0])
const ca = vec3.create()
vec3.subtract(ca, vertices[2], vertices[0])
const normal = vec3.create()
vec3.cross(normal, ba, ca)
vec3.normalize(normal, normal)
return normal
}
/*
* determine if the two given normals are 'similar' ie if the distance/angle between the
* two is less than the given threshold
* @param {Array} normal a 3 component array normal
* @param {Array} otherNormal another 3 component array normal
* @returns {Boolean} true if the two normals are similar
*/
const areNormalsSimilar = (normal, otherNormal, threshold) => (vec3.distance(normal, otherNormal) <= threshold)
const fuzyNormalAndPositionLookup = (normalPositionLookup, toCompare, normalThreshold) => {
const normalsCandidates = normalPositionLookup[toCompare.position]
if (normalsCandidates) {
// normalPositionLookup[toCompare.position] = normalPositionLookup[toCompare.position].concat([toCompare.normal])
// get array of normals with same position
for (let i = 0; i < normalsCandidates.length; i++) {
const normal = normalsCandidates[i].normal
const similarNormal = areNormalsSimilar(normal, toCompare.normal, normalThreshold)
const similar = similarNormal
if (similar) {
return { tupple: { position: toCompare.position, normal }, index: normalsCandidates[i].index }
}
}
}
return undefined
}
module.exports = geom3ToGeometries