/**
* @file
*
* Summary.
* <p>The functions in this file create models in an
* {@link https://math.hws.edu/eck/cs424/downloads/graphicsbook-linked.pdf#page=200 IFS}
* (Indexed Face Set) format that can be drawn using
* {@link https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/drawElements gl.drawElements}
* with primitive type {@link https://webglfundamentals.org/webgl/lessons/webgl-points-lines-triangles.html gl.TRIANGLES}.</p>
*
* Objects have for each vertex:
* <ul>
* <li>vertex coordinates, </li>
* <li>normal vectors, </li>
* <li>texture coordinates, </li>
* <li>vertex tangents, </li>
* <li>plus a list of indices for the element array buffer.</li>
* </ul>
*
* The return value of each function is an object, model,
* with properties:
* <pre>
* model.vertexPositions -- the vertex coordinates;
* model.vertexNormals -- the normal vectors;
* model.vertexTextureCoords -- the texture coordinates;
* model.vertexTangents -- the tangent vectors;
* model.indices -- the face indices.
* </pre>
* The first four properties are of type Float32Array, while
* model.indices is of type Uint16Array.
*
* <ul>
* <li>{@link uvCone cone}</li>
* <li>{@link cube cube}</li>
* <li>{@link uvCylinder cylinder}</li>
* <li>{@link uvSphere sphere}</li>
* <li>{@link uvTorus torus}</li>
* </ul>
*
* @author David J. Eck and modified by Paulo Roma
* @since 19/11/2022
* @see <a href="/WebGL/hws.edu-examples/basic-object-models-with-tangents-IFS.js">source</a>
*
*/
"use strict";
/**
* Create a model of a cube, centered at the origin. (This is not
* a particularly good format for a cube, since an IFS representation
* has a lot of redundancy.)
* @param {Number} side the length of a side of the cube.
* If not given, the value will be 1.
* @return {Object<{vertexPositions: Float32Array,
vertexNormals: Float32Array,
vertexTextureCoords: Float32Array,
vertexTangents: Float32Array,
indices: Uint16Array}>}
*/
function cube(side) {
let s = (side || 1) / 2;
let coords = [];
let normals = [];
let tangents = [];
let texCoords = [];
let indices = [];
function face(xyz, nrm, tang) {
let start = coords.length / 3;
let i;
for (i = 0; i < 12; i++) {
coords.push(xyz[i]);
}
for (i = 0; i < 4; i++) {
normals.push(nrm[0], nrm[1], nrm[2]);
}
for (i = 0; i < 4; i++) {
tangents.push(tang[0], tang[1], tang[2]);
}
texCoords.push(0, 0, 1, 0, 1, 1, 0, 1);
indices.push(start, start + 1, start + 2, start, start + 2, start + 3);
}
face([-s, -s, s, s, -s, s, s, s, s, -s, s, s], [0, 0, 1], [1, 0, 0]);
face([-s, -s, -s, -s, s, -s, s, s, -s, s, -s, -s], [0, 0, -1], [0, 1, 0]);
face([-s, s, -s, -s, s, s, s, s, s, s, s, -s], [0, 1, 0], [0, 0, 1]);
face([-s, -s, -s, s, -s, -s, s, -s, s, -s, -s, s], [0, -1, 0], [1, 0, 0]);
face([s, -s, -s, s, s, -s, s, s, s, s, -s, s], [1, 0, 0], [0, 1, 0]);
face([-s, -s, -s, -s, -s, s, -s, s, s, -s, s, -s], [-1, 0, 0], [0, 0, 1]);
return {
vertexPositions: new Float32Array(coords),
vertexNormals: new Float32Array(normals),
vertexTextureCoords: new Float32Array(texCoords),
vertexTangents: new Float32Array(tangents),
indices: new Uint16Array(indices),
};
}
/**
* Create a model of a torus (surface of a doughnut). The z-axis goes through the doughnut hole,
* and the center of the torus is at (0,0,0).
* @param {Number} outerRadius the distance from the center to the outside of the tube, 0.5 if not specified.
* @param {Number} innerRadius the distance from the center to the inside of the tube, outerRadius/3 if not
* specified. (This is the radius of the doughnut hole.)
* @param {Number} slices the number of lines of longitude, default 32. These are slices parallel to the
* z-axis and go around the tube the short way (through the hole).
* @param {Number} stacks the number of lines of latitude plus 1, default 16. These lines are perpendicular
* to the z-axis and go around the tube the long way (arouind the hole).
* @return {Object<{vertexPositions: Float32Array,
vertexNormals: Float32Array,
vertexTextureCoords: Float32Array,
vertexTangents: Float32Array,
indices: Uint16Array}>}
* @see <a href="/roma/cg/doc/html/torus_8cpp.html#acf04d1331c5f0cedfaacc30d1d3f46f4">torus</a>
* @see <img src="../torusparams.gif">
*/
function uvTorus(outerRadius, innerRadius, slices, stacks) {
outerRadius = outerRadius || 0.5;
innerRadius = innerRadius || outerRadius / 3;
slices = slices || 32;
stacks = stacks || 16;
let vertexCount = (slices + 1) * (stacks + 1);
let vertices = new Float32Array(3 * vertexCount);
let normals = new Float32Array(3 * vertexCount);
let tangents = new Float32Array(3 * vertexCount);
let texCoords = new Float32Array(2 * vertexCount);
let indices = new Uint16Array(2 * slices * stacks * 3);
let du = (2 * Math.PI) / slices;
let dv = (2 * Math.PI) / stacks;
let centerRadius = (innerRadius + outerRadius) / 2;
let tubeRadius = outerRadius - centerRadius;
let i, j, u, v, cx, cy, sin, cos, x, y, z;
let indexV = 0;
let indexT = 0;
for (j = 0; j <= stacks; j++) {
v = -Math.PI + j * dv;
cos = Math.cos(v);
sin = Math.sin(v);
for (i = 0; i <= slices; i++) {
u = i * du;
cx = Math.cos(u);
cy = Math.sin(u);
x = cx * (centerRadius + tubeRadius * cos);
y = cy * (centerRadius + tubeRadius * cos);
z = sin * tubeRadius;
vertices[indexV] = x;
tangents[indexV] = -cy;
normals[indexV++] = cx * cos;
vertices[indexV] = y;
tangents[indexV] = cx;
normals[indexV++] = cy * cos;
vertices[indexV] = z;
tangents[indexV] = 0;
normals[indexV++] = sin;
texCoords[indexT++] = i / slices;
texCoords[indexT++] = j / stacks;
}
}
let k = 0;
for (j = 0; j < stacks; j++) {
let row1 = j * (slices + 1);
let row2 = (j + 1) * (slices + 1);
for (i = 0; i < slices; i++) {
indices[k++] = row1 + i;
indices[k++] = row2 + i + 1;
indices[k++] = row2 + i;
indices[k++] = row1 + i;
indices[k++] = row1 + i + 1;
indices[k++] = row2 + i + 1;
}
}
return {
vertexPositions: vertices,
vertexNormals: normals,
vertexTextureCoords: texCoords,
vertexTangents: tangents,
indices: indices,
};
}
/**
* Defines a model of a cylinder. The axis of the cylinder is the z-axis,
* and the center is at (0,0,0).
* @param {Number} radius the radius of the cylinder
* @param {Number} height the height of the cylinder. The cylinder extends from -height/2
* to height/2 along the z-axis.
* @param {Number} slices the number of slices, like the slices of an orange.
* @param {Boolean} noTop if missing or false, the cylinder has a top; if set to true,
* the cylinder does not have a top. The top is a disk at the positive end of the cylinder.
* @param {Boolean} noBottom if missing or false, the cylinder has a bottom; if set to true,
* the cylinder does not have a bottom. The bottom is a disk at the negtive end of the cylinder.
* @return {Object<{vertexPositions: Float32Array,
vertexNormals: Float32Array,
vertexTextureCoords: Float32Array,
vertexTangents: Float32Array,
indices: Uint16Array}>}
* @see <a href="/roma/cg/doc/html/torus_8cpp.html#a03c085eb7ef8ae60df19dc9e06c0a173">cylinder</a>
*/
function uvCylinder(radius, height, slices, noTop, noBottom) {
radius = radius || 0.5;
height = height || 2 * radius;
slices = slices || 32;
let vertexCount = 2 * (slices + 1);
if (!noTop) vertexCount += slices + 2;
if (!noBottom) vertexCount += slices + 2;
let triangleCount = 2 * slices;
if (!noTop) triangleCount += slices;
if (!noBottom) triangleCount += slices;
let vertices = new Float32Array(vertexCount * 3);
let normals = new Float32Array(vertexCount * 3);
let tangents = new Float32Array(vertexCount * 3);
let texCoords = new Float32Array(vertexCount * 2);
let indices = new Uint16Array(triangleCount * 3);
let du = (2 * Math.PI) / slices;
let kv = 0;
let kt = 0;
let k = 0;
let i, u;
for (i = 0; i <= slices; i++) {
u = i * du;
let c = Math.cos(u);
let s = Math.sin(u);
vertices[kv] = c * radius;
tangents[kv] = -s;
normals[kv++] = c;
vertices[kv] = s * radius;
tangents[kv] = c;
normals[kv++] = s;
vertices[kv] = -height / 2;
tangents[kv] = 0;
normals[kv++] = 0;
texCoords[kt++] = i / slices;
texCoords[kt++] = 0;
vertices[kv] = c * radius;
tangents[kv] = -s;
normals[kv++] = c;
vertices[kv] = s * radius;
tangents[kv] = c;
normals[kv++] = s;
vertices[kv] = height / 2;
tangents[kv] = 0;
normals[kv++] = 0;
texCoords[kt++] = i / slices;
texCoords[kt++] = 1;
}
for (i = 0; i < slices; i++) {
indices[k++] = 2 * i;
indices[k++] = 2 * i + 3;
indices[k++] = 2 * i + 1;
indices[k++] = 2 * i;
indices[k++] = 2 * i + 2;
indices[k++] = 2 * i + 3;
}
let startIndex = kv / 3;
if (!noBottom) {
vertices[kv] = 0;
tangents[kv] = -1;
normals[kv++] = 0;
vertices[kv] = 0;
tangents[kv] = 0;
normals[kv++] = 0;
vertices[kv] = -height / 2;
tangents[kv] = 0;
normals[kv++] = -1;
texCoords[kt++] = 0.5;
texCoords[kt++] = 0.5;
for (i = 0; i <= slices; i++) {
u = 2 * Math.PI - i * du;
let c = Math.cos(u);
let s = Math.sin(u);
vertices[kv] = c * radius;
tangents[kv] = -1;
normals[kv++] = 0;
vertices[kv] = s * radius;
tangents[kv] = 0;
normals[kv++] = 0;
vertices[kv] = -height / 2;
tangents[kv] = 0;
normals[kv++] = -1;
texCoords[kt++] = 0.5 - 0.5 * c;
texCoords[kt++] = 0.5 + 0.5 * s;
}
for (i = 0; i < slices; i++) {
indices[k++] = startIndex;
indices[k++] = startIndex + i + 1;
indices[k++] = startIndex + i + 2;
}
}
startIndex = kv / 3;
if (!noTop) {
vertices[kv] = 0;
tangents[kv] = 1;
normals[kv++] = 0;
vertices[kv] = 0;
tangents[kv] = 0;
normals[kv++] = 0;
vertices[kv] = height / 2;
tangents[kv] = 0;
normals[kv++] = 1;
texCoords[kt++] = 0.5;
texCoords[kt++] = 0.5;
for (i = 0; i <= slices; i++) {
u = i * du;
let c = Math.cos(u);
let s = Math.sin(u);
vertices[kv] = c * radius;
tangents[kv] = 1;
normals[kv++] = 0;
vertices[kv] = s * radius;
tangents[kv] = 0;
normals[kv++] = 0;
vertices[kv] = height / 2;
tangents[kv] = 0;
normals[kv++] = 1;
texCoords[kt++] = 0.5 + 0.5 * c;
texCoords[kt++] = 0.5 + 0.5 * s;
}
for (i = 0; i < slices; i++) {
indices[k++] = startIndex;
indices[k++] = startIndex + i + 1;
indices[k++] = startIndex + i + 2;
}
}
return {
vertexPositions: vertices,
vertexNormals: normals,
vertexTangents: tangents,
vertexTextureCoords: texCoords,
indices: indices,
};
}
/**
* <p>Create a model of a sphere.</p>
* The z-axis is the axis of the sphere,
* with the north pole on the positive z-axis and the center at (0,0,0).
* @param {Number} radius the radius of the sphere, default 0.5 if not specified.
* @param {Number} slices the number of lines of longitude, default 32
* @param {Number} stacks the number of lines of latitude plus 1, default 16.
* (This is the number of vertical slices, bounded by lines of latitude,
* the north pole and the south pole.)
* @return {Object<{vertexPositions:Float32Array,
* vertexNormals:Float32Array,
* vertexTextureCoords:Float32Array,
* vertexTangents: Float32Array,
* indices:Uint16Array}>}
* @see <a href="/roma/cg/doc/html/torus_8cpp.html#a6c5b17163125dd32bd7c04a99738d316">sphere</a>
*/
function uvSphere(radius, slices, stacks) {
radius = radius || 0.5;
slices = slices || 32;
stacks = stacks || 16;
var vertexCount = (slices + 1) * (stacks + 1);
var vertices = new Float32Array(3 * vertexCount);
var normals = new Float32Array(3 * vertexCount);
let tangents = new Float32Array(3 * vertexCount);
var texCoords = new Float32Array(2 * vertexCount);
var indices = new Uint16Array(2 * slices * stacks * 3);
var du = (2 * Math.PI) / slices;
var dv = Math.PI / stacks;
var i, j, u, v, x, y, z;
var indexV = 0;
var indexT = 0;
for (i = 0; i <= stacks; i++) {
v = -Math.PI / 2 + i * dv;
for (j = 0; j <= slices; j++) {
u = j * du;
x = Math.cos(u) * Math.cos(v);
y = Math.sin(u) * Math.cos(v);
z = Math.sin(v);
vertices[indexV] = radius * x;
tangents[indexV] = -y;
normals[indexV++] = x;
vertices[indexV] = radius * y;
tangents[indexV] = x;
normals[indexV++] = y;
vertices[indexV] = radius * z;
tangents[indexV] = 0;
normals[indexV++] = z;
texCoords[indexT++] = j / slices;
texCoords[indexT++] = i / stacks;
}
}
var k = 0;
for (j = 0; j < stacks; j++) {
var row1 = j * (slices + 1);
var row2 = (j + 1) * (slices + 1);
for (i = 0; i < slices; i++) {
indices[k++] = row1 + i;
indices[k++] = row2 + i + 1;
indices[k++] = row2 + i;
indices[k++] = row1 + i;
indices[k++] = row1 + i + 1;
indices[k++] = row2 + i + 1;
}
}
return {
vertexPositions: vertices,
vertexNormals: normals,
vertexTangents: tangents,
vertexTextureCoords: texCoords,
indices: indices,
};
}
/**
* Defines a model of a cone. The axis of the cone is the z-axis,
* and the center is at (0,0,0).
* @param {Number} radius the radius of the cone.
* @param {Number} height the height of the cone. <br>
* The cone extends from -height/2 to height/2 along the z-axis, <br>
* with the tip at (0,0,height/2).
* @param {Number} slices the number of slices, like the slices of an orange.
* @param {Boolean} noBottom if missing or false, the cone has a bottom;
* if set to true, the cone does not have a bottom. <br>
* The bottom is a disk at the wide end of the cone.
* @return {Object<{vertexPositions:Float32Array,
* vertexNormals:Float32Array,
* vertexTextureCoords:Float32Array,
* vertexTangents: Float32Array,
* indices:Uint16Array}>}
* @see <a href="/roma/cg/doc/html/torus_8cpp.html#a2106dc9326540a0309d6e8d815e10a0e">cone</a>
*/
function uvCone(radius, height, slices, noBottom) {
radius = radius || 0.5;
height = height || 2 * radius;
slices = slices || 32;
var fractions = [0, 0.5, 0.75, 0.875, 0.9375];
var vertexCount = fractions.length * (slices + 1) + slices;
if (!noBottom) vertexCount += slices + 2;
var triangleCount = (fractions.length - 1) * slices * 2 + slices;
if (!noBottom) triangleCount += slices;
var vertices = new Float32Array(vertexCount * 3);
var normals = new Float32Array(vertexCount * 3);
var tangents = new Float32Array(vertexCount * 3);
var texCoords = new Float32Array(vertexCount * 2);
var indices = new Uint16Array(triangleCount * 3);
var normallength = Math.sqrt(height * height + radius * radius);
var n1 = height / normallength;
var n2 = radius / normallength;
var du = (2 * Math.PI) / slices;
var kv = 0;
var kt = 0;
var k = 0;
var i, j, u;
for (j = 0; j < fractions.length; j++) {
var uoffset = j % 2 == 0 ? 0 : 0.5;
for (i = 0; i <= slices; i++) {
var h1 = -height / 2 + fractions[j] * height;
u = (i + uoffset) * du;
var c = Math.cos(u);
var s = Math.sin(u);
vertices[kv] = c * radius * (1 - fractions[j]);
tangents[kv] = -s * n1;
normals[kv++] = c * n1;
vertices[kv] = s * radius * (1 - fractions[j]);
tangents[kv] = c * n1;
normals[kv++] = s * n1;
vertices[kv] = h1;
tangents[kv] = 0;
normals[kv++] = n2;
texCoords[kt++] = (i + uoffset) / slices;
texCoords[kt++] = fractions[j];
}
}
var k = 0;
for (j = 0; j < fractions.length - 1; j++) {
var row1 = j * (slices + 1);
var row2 = (j + 1) * (slices + 1);
for (i = 0; i < slices; i++) {
indices[k++] = row1 + i;
indices[k++] = row2 + i + 1;
indices[k++] = row2 + i;
indices[k++] = row1 + i;
indices[k++] = row1 + i + 1;
indices[k++] = row2 + i + 1;
}
}
var start = kv / 3 - (slices + 1);
for (i = 0; i < slices; i++) {
// slices points at top, with different normals, texcoords
u = (i + 0.5) * du;
var c = Math.cos(u);
var s = Math.sin(u);
vertices[kv] = 0;
tangents[kv] = -s * n1;
normals[kv++] = c * n1;
vertices[kv] = 0;
tangents[kv] = c * n1;
normals[kv++] = s * n1;
vertices[kv] = height / 2;
tangents[kv] = 0;
normals[kv++] = n2;
texCoords[kt++] = (i + 0.5) / slices;
texCoords[kt++] = 1;
}
for (i = 0; i < slices; i++) {
indices[k++] = start + i;
indices[k++] = start + i + 1;
indices[k++] = start + (slices + 1) + i;
}
if (!noBottom) {
var startIndex = kv / 3;
vertices[kv] = 0;
tangents[kv] = -1;
normals[kv++] = 0;
vertices[kv] = 0;
tangents[kv] = 0;
normals[kv++] = 0;
vertices[kv] = -height / 2;
tangents[kv] = 0;
normals[kv++] = -1;
texCoords[kt++] = 0.5;
texCoords[kt++] = 0.5;
for (i = 0; i <= slices; i++) {
u = 2 * Math.PI - i * du;
var c = Math.cos(u);
var s = Math.sin(u);
vertices[kv] = c * radius;
tangents[kv] = -1;
normals[kv++] = 0;
vertices[kv] = s * radius;
tangents[kv] = 0;
normals[kv++] = 0;
vertices[kv] = -height / 2;
tangents[kv] = 0;
normals[kv++] = -1;
texCoords[kt++] = 0.5 - 0.5 * c;
texCoords[kt++] = 0.5 + 0.5 * s;
}
for (i = 0; i < slices; i++) {
indices[k++] = startIndex;
indices[k++] = startIndex + i + 1;
indices[k++] = startIndex + i + 2;
}
}
return {
vertexPositions: vertices,
vertexNormals: normals,
vertexTangents: tangents,
vertexTextureCoords: texCoords,
indices: indices,
};
}