blob: 92d48617f51754a458bbe4d44851939a886a9956 [file] [edit]
// Copyright 2018 The Immersive Web Community Group
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import {Primitive, PrimitiveAttribute} from '../core/primitive.js';
import {mat3, vec3} from '../math/gl-matrix.js';
const GL = WebGLRenderingContext; // For enums
const tempVec3 = vec3.create();
export class PrimitiveStream {
constructor(options) {
this._vertices = [];
this._indices = [];
this._geometryStarted = false;
this._vertexOffset = 0;
this._vertexIndex = 0;
this._highIndex = 0;
this._flipWinding = false;
this._invertNormals = false;
this._transform = null;
this._normalTransform = null;
this._min = null;
this._max = null;
}
set flipWinding(value) {
if (this._geometryStarted) {
throw new Error(`Cannot change flipWinding before ending the current geometry.`);
}
this._flipWinding = value;
}
get flipWinding() {
this._flipWinding;
}
set invertNormals(value) {
if (this._geometryStarted) {
throw new Error(`Cannot change invertNormals before ending the current geometry.`);
}
this._invertNormals = value;
}
get invertNormals() {
this._invertNormals;
}
set transform(value) {
if (this._geometryStarted) {
throw new Error(`Cannot change transform before ending the current geometry.`);
}
this._transform = value;
if (this._transform) {
if (!this._normalTransform) {
this._normalTransform = mat3.create();
}
mat3.fromMat4(this._normalTransform, this._transform);
}
}
get transform() {
this._transform;
}
startGeometry() {
if (this._geometryStarted) {
throw new Error(`Attempted to start a new geometry before the previous one was ended.`);
}
this._geometryStarted = true;
this._vertexIndex = 0;
this._highIndex = 0;
}
endGeometry() {
if (!this._geometryStarted) {
throw new Error(`Attempted to end a geometry before one was started.`);
}
if (this._highIndex >= this._vertexIndex) {
throw new Error(`Geometry contains indices that are out of bounds.
(Contains an index of ${this._highIndex} when the vertex count is ${this._vertexIndex})`);
}
this._geometryStarted = false;
this._vertexOffset += this._vertexIndex;
// TODO: Anything else need to be done to finish processing here?
}
pushVertex(x, y, z, u = 0, v = 0, nx = 0, ny = 0, nz = 1) {
if (!this._geometryStarted) {
throw new Error(`Cannot push vertices before calling startGeometry().`);
}
// Transform the incoming vertex if we have a transformation matrix
if (this._transform) {
tempVec3[0] = x;
tempVec3[1] = y;
tempVec3[2] = z;
vec3.transformMat4(tempVec3, tempVec3, this._transform);
x = tempVec3[0];
y = tempVec3[1];
z = tempVec3[2];
tempVec3[0] = nx;
tempVec3[1] = ny;
tempVec3[2] = nz;
vec3.transformMat3(tempVec3, tempVec3, this._normalTransform);
nx = tempVec3[0];
ny = tempVec3[1];
nz = tempVec3[2];
}
if (this._invertNormals) {
nx *= -1.0;
ny *= -1.0;
nz *= -1.0;
}
this._vertices.push(x, y, z, u, v, nx, ny, nz);
if (this._min) {
this._min[0] = Math.min(this._min[0], x);
this._min[1] = Math.min(this._min[1], y);
this._min[2] = Math.min(this._min[2], z);
this._max[0] = Math.max(this._max[0], x);
this._max[1] = Math.max(this._max[1], y);
this._max[2] = Math.max(this._max[2], z);
} else {
this._min = vec3.fromValues(x, y, z);
this._max = vec3.fromValues(x, y, z);
}
return this._vertexIndex++;
}
get nextVertexIndex() {
return this._vertexIndex;
}
pushTriangle(idxA, idxB, idxC) {
if (!this._geometryStarted) {
throw new Error(`Cannot push triangles before calling startGeometry().`);
}
this._highIndex = Math.max(this._highIndex, idxA, idxB, idxC);
idxA += this._vertexOffset;
idxB += this._vertexOffset;
idxC += this._vertexOffset;
if (this._flipWinding) {
this._indices.push(idxC, idxB, idxA);
} else {
this._indices.push(idxA, idxB, idxC);
}
}
clear() {
if (this._geometryStarted) {
throw new Error(`Cannot clear before ending the current geometry.`);
}
this._vertices = [];
this._indices = [];
this._vertexOffset = 0;
this._min = null;
this._max = null;
}
finishPrimitive(renderer) {
if (!this._vertexOffset) {
throw new Error(`Attempted to call finishPrimitive() before creating any geometry.`);
}
let vertexBuffer = renderer.createRenderBuffer(GL.ARRAY_BUFFER, new Float32Array(this._vertices));
let indexBuffer = renderer.createRenderBuffer(GL.ELEMENT_ARRAY_BUFFER, new Uint16Array(this._indices));
let attribs = [
new PrimitiveAttribute('POSITION', vertexBuffer, 3, GL.FLOAT, 32, 0),
new PrimitiveAttribute('TEXCOORD_0', vertexBuffer, 2, GL.FLOAT, 32, 12),
new PrimitiveAttribute('NORMAL', vertexBuffer, 3, GL.FLOAT, 32, 20),
];
let primitive = new Primitive(attribs, this._indices.length);
primitive.setIndexBuffer(indexBuffer);
primitive.setBounds(this._min, this._max);
return primitive;
}
}
export class GeometryBuilderBase {
constructor(primitiveStream) {
if (primitiveStream) {
this._stream = primitiveStream;
} else {
this._stream = new PrimitiveStream();
}
}
set primitiveStream(value) {
this._stream = value;
}
get primitiveStream() {
return this._stream;
}
finishPrimitive(renderer) {
return this._stream.finishPrimitive(renderer);
}
clear() {
this._stream.clear();
}
}