Cylinders are represented as a stack of circles, with each circle’s vertices connected with those above and below it. The radius of each circle does not have to be equivalent. In the case where they are not, the object is considered a cone. The vertices are found by dividing the 360 degrees of the circle by n slices to find the angle between each vertex. Then using this and the length of the radius, the vertices are placed. Then, for the top and bottom circles, triangles are generated by connecting each vertex to its next neighbor, then those two points to the midpoint.
Then, the height of the circle is divided into n stacks. Each stack is a circle that is generated “above” the next. For denser cylinders, there are many “inner” circles. Because the height of the cylinder and the number of circles are variable, the radii of the inner circles has to be calculated. This is found by using the ratio of the difference between the top and bottom circles and the height.
To calculate the normal at each vertex, we need to know the angle of the outer wall of the cylinder. This is found by creating a triangle out of the height of the triangle and the difference between the top and bottom radii.
generateAngle(radBase, radTop, height) {
let biggerRad = radBase > radTop ? radBase : radTop
let smallerRad = radBase > radTop ? radTop : radBase
let triangleBase = biggerRad - smallerRad
let angle = Math.atan(height / triangleBase)
if (radTop > radBase) {
return angle * -1
} else {
return angle
}
}
After calculating the positions and normals, the last calculation is finding the indices of points on these 2D circles to be grouped together during triangle generation. The index array holds the indices of points in the position array rather than copying the points to save space. Every 3 points in the array are considered a grouping which will later be turned into a triangle. For 2D circles, each grouping contains the midpoint of the circle followed by the two outer vertices it connects to.
generateCircle(radius, numPoints, y, offset, centerNormalY) {
var ret = {}
var positions = []
var indices = []
var normals = []
//init origin at the start
positions.push(0)
positions.push(y)
positions.push(0)
normals.push(0)
normals.push(centerNormalY != null ? centerNormalY : 0)
normals.push(0)
//init circle points
for(let i = 0; i < numPoints; i++) {
let rad = i * ((2 * (Math.PI)) / (numPoints)) //in radians
let x = radius * Math.cos(rad)
let z = radius * Math.sin(rad)
let dist = Math.sqrt(x * x + z * z)
let yOffset = dist * Math.tan(this.angle) //this.angle calculated previously using generateAngle()
positions.push(x) //x
positions.push(y) //y
positions.push(z) //z
normals.push(x)
normals.push(centerNormalY != null ? centerNormalY + y : y + yOffset)
normals.push(z)
}
if (offset != -1) {
for (let i = 1; i < numPoints; i++) {
indices.push(0) //midpoint
indices.push(0 + i)
indices.push(0 + i + 1)
}
//last triangle
indices.push(0)
indices.push(0 + numPoints)
indices.push(0 + 1)
}
ret.positions = positions
ret.indices = indices
ret.normals = normals
return ret
}
The perimiter of the circles are then connected to those above and below it in a similar way to how the circles were generated. If we wanted to make a cone, we can adjust the radii of each stack accordingly (e.g. larger radius on the bottom and smaller radius on top).
connectCirclesTriangle(bottomPos, topPos, len) {
var ret = [];
for (let i = 1; i < len - 1; i++) {
let triangleOne = [bottomPos + i, topPos + i, topPos + i + 1]
let triangleTwo = [bottomPos + i, bottomPos + i + 1, topPos + i + 1]
ret = ret.concat(triangleOne)
ret = ret.concat(triangleTwo)
}
//last triangle
let triangleOne = [bottomPos + len - 1, topPos + len - 1, topPos + 1]
let triangleTwo = [bottomPos + len - 1, bottomPos + 1, topPos + 1]
ret = ret.concat(triangleOne)
ret = ret.concat(triangleTwo)
return ret
}
class cylinder {
constructor(radBase, radTop, height, numSlices, numStacks, color) {
this.angle = this.generateAngle(radBase, radTop, height)
let init = this.generateCylinderData(radBase, radTop, height, numSlices, numStacks, color)
this.combinedVertexIndices = init.combinedVertexIndices
this.combinedVertexPositions = init.combinedVertexPositions
this.combinedVertexNormals = init.combinedVertexNormals
}
get indices() {
return this.combinedVertexIndices
}
get positions() {
return this.combinedVertexPositions
}
get normals() {
return this.combinedVertexNormals
}
generateCircle(radius, numPoints, y, offset, centerNormalY) {
var ret = {}
var positions = []
var indices = []
var normals = []
//init origin at the start
positions.push(0)
positions.push(y)
positions.push(0)
normals.push(0)
normals.push(centerNormalY != null ? centerNormalY : 0)
normals.push(0)
//init circle points
for(let i = 0; i < numPoints; i++) {
let rad = i * ((2 * (Math.PI)) / (numPoints)) //in radians
let x = radius * Math.cos(rad)
let z = radius * Math.sin(rad)
let dist = Math.sqrt(x * x + z * z)
let yOffset = dist * Math.tan(this.angle)
positions.push(x) //x
positions.push(y) //y
positions.push(z) //z
normals.push(x)
normals.push(centerNormalY != null ? centerNormalY + y : y + yOffset)
normals.push(z)
}
if (offset != -1) {
for (let i = 1; i < numPoints; i++) {
indices.push(0) //midpoint
indices.push(0 + i)
indices.push(0 + i + 1)
}
//last triangle
indices.push(0)
indices.push(0 + numPoints)
indices.push(0 + 1)
}
ret.positions = positions
ret.indices = indices
ret.normals = normals
return ret
}
//bottomPos and topPos are the index in the combined array that each respective element starts at
//len is the length of each list
//list is the combined list with all the positions
connectCirclesTriangle(bottomPos, topPos, len) {
var ret = [];
for (let i = 1; i < len - 1; i++) {
let triangleOne = [bottomPos + i, topPos + i, topPos + i + 1]
let triangleTwo = [bottomPos + i, bottomPos + i + 1, topPos + i + 1]
ret = ret.concat(triangleOne)
ret = ret.concat(triangleTwo)
}
//last triangle
let triangleOne = [bottomPos + len - 1, topPos + len - 1, topPos + 1]
let triangleTwo = [bottomPos + len - 1, bottomPos + 1, topPos + 1]
ret = ret.concat(triangleOne)
ret = ret.concat(triangleTwo)
return ret
}
generateAngle(radBase, radTop, height) {
let biggerRad = radBase > radTop ? radBase : radTop
let smallerRad = radBase > radTop ? radTop : radBase
let triangleBase = biggerRad - smallerRad
let angle = Math.atan(height / triangleBase)
if (radTop > radBase) {
return angle * -1
} else {
return angle
}
}
generateCylinderData(radBase, radTop, height, numSlices, numStacks) {
//prime deltas
var radDelta= (radBase - radTop) / (numStacks - 1)
var workingRad = radBase
var heightDelta = height / (numStacks - 1)
var workingHeight = 0
//create the base
var bottomCircle = this.generateCircle(radBase, numSlices, 0, 0)
var bottomCircleDuplicate = this.generateCircle(radBase, numSlices, 0, 0, -1)
var combinedVertexPositions = bottomCircle.positions
var combinedVertexNormals = bottomCircle.normals
//create the top
var topCircle = this.generateCircle(radTop, numSlices, height, bottomCircle.indices.length / 3)
var topCircleDuplicate = this.generateCircle(radTop, numSlices, height, bottomCircle.indices.length / 3, 1)
//create stacks
for (let i = 1; i < numStacks - 1; i++) {
workingRad -= radDelta
workingHeight += heightDelta
let tempCircle = this.generateCircle(workingRad, numSlices, workingHeight, -1)
combinedVertexPositions = combinedVertexPositions.concat(tempCircle.positions)
combinedVertexNormals = combinedVertexNormals.concat(tempCircle.normals)
}
combinedVertexPositions = combinedVertexPositions.concat(topCircle.positions)
combinedVertexNormals = combinedVertexNormals.concat(topCircle.normals)
var start = combinedVertexPositions.length / 3
combinedVertexPositions = combinedVertexPositions.concat(topCircleDuplicate.positions)
combinedVertexNormals = combinedVertexNormals.concat(topCircleDuplicate.normals)
var nextStart = combinedVertexPositions.length / 3
combinedVertexPositions = combinedVertexPositions.concat(bottomCircleDuplicate.positions)
combinedVertexNormals = combinedVertexNormals.concat(bottomCircleDuplicate.normals)
/////////////////////////////////////////////////////////
for (let i = 0; i < topCircleDuplicate.indices.length; i++) {
topCircleDuplicate.indices[i] += start
bottomCircleDuplicate.indices[i] += nextStart
}
//create top and bottom face triangles
var combinedVertexIndices = bottomCircleDuplicate.indices.concat(topCircleDuplicate.indices)
//Connect all of the slices together
for (let i = 0; i < numStacks; i++) {
var offset = i * (numSlices - 1)
var len = numSlices + 1
let result = this.connectCirclesTriangle(offset, offset + len, len)
combinedVertexIndices = combinedVertexIndices.concat(result)
}
var ret = {}
ret.combinedVertexIndices = combinedVertexIndices
ret.combinedVertexPositions = combinedVertexPositions
ret.combinedVertexNormals = combinedVertexNormals
return ret
}
}
Back