gl2d
Version:
2D graphics package for WebGL
475 lines • 21.7 kB
JavaScript
"use strict";
var __extends = (this && this.__extends) || (function () {
var extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
var miter_1 = require("../math/miter");
var indextuple_1 = require("../struct/indextuple");
var mat2d_1 = require("../struct/mat2d");
var point_1 = require("../struct/point");
var vec2_1 = require("../struct/vec2");
var vertex_1 = require("../struct/vertex");
var shape_1 = require("./shape");
/**
* Stores static vertex and index data that multiple graphics can share.
*/
var Mesh = (function () {
/**
* Creates a mesh with the specified data.
* @param vertices the mesh vertices.
* @param triangleIndices the indices for each triangle in the mesh.
* @param polygonIndices the indices for each polygon in the mesh.
* @param id an optional id for the mesh.
* @param bounds the boundaries of the mesh.
*/
function Mesh(vertices, triangleIndices, id, bounds) {
if (bounds === void 0) { bounds = vertices.measureBoundaries(); }
this.vertices = vertices;
this.triangleIndices = triangleIndices;
this.bounds = bounds;
this.id = id;
}
/**
* Creates a mesh with the specified source data.
* @param source obejct containing the data for the mesh.
*/
Mesh.fromSpecification = function (spec) {
var vertices;
var indices;
var id = spec.id;
if (spec.vertices instanceof vertex_1.VertexBuffer) {
vertices = spec.vertices;
}
else if (spec.vertices instanceof Float32Array) {
vertices = new vertex_1.VertexBuffer(spec.vertices);
}
else if (spec.vertices) {
vertices = new vertex_1.VertexBuffer(new Float32Array(spec.vertices));
}
if (spec.triangleIndices instanceof indextuple_1.IndexTupleBuffer) {
indices = spec.triangleIndices;
}
else if (spec.triangleIndices instanceof Uint16Array) {
indices = new indextuple_1.IndexTupleBuffer(spec.triangleIndices);
}
else if (spec.triangleIndices) {
indices = new indextuple_1.IndexTupleBuffer(new Uint16Array(spec.triangleIndices));
}
switch (spec.type) {
case "polygon":
var _a = spec, sides = _a.sides, hasFlatTop = _a.hasFlatTop;
if (!spec.vertices) {
vertices = PolygonMesh.regularVertices(sides, hasFlatTop);
}
if (!spec.triangleIndices) {
indices = PolygonMesh.regularIndices(sides || vertices.capacity());
}
break;
case "star":
var _b = spec, points = _b.points, ratio = _b.ratio;
if (!spec.vertices) {
vertices = PolygonMesh.starVertices(points, ratio);
}
if (!spec.triangleIndices) {
indices = PolygonMesh.starIndices(points);
}
break;
case "rectangle":
var bounds = spec.bounds;
if (!spec.vertices) {
vertices = PolygonMesh.rectangleVertices(bounds);
}
if (!spec.triangleIndices) {
indices = PolygonMesh.regularIndices(4);
}
break;
default:
if (!spec.vertices) {
throw new Error("Insufficient vertex data in specification " + spec);
}
}
if (spec.polygonIndices) {
return new MultiPolygonMesh(vertices, spec.polygonIndices, indices, id);
}
var mesh = new PolygonMesh(vertices, indices, id);
if (spec.effect === "spray") {
var _c = spec, innerRing = _c.innerRing, rings = _c.rings;
return InstancedPolygonMesh.spray(mesh, innerRing, rings, id);
}
else {
return mesh;
}
};
/**
* Checks if this mesh contains the specified point
* @param pt the point to check.
*/
Mesh.prototype.contains = function (pt) {
return this.contains$(pt.x, pt.y);
};
return Mesh;
}());
exports.Mesh = Mesh;
var PolygonMesh = (function (_super) {
__extends(PolygonMesh, _super);
/**
* Creates a mesh with the specified data.
* @param vertices the mesh vertices.
* @param polygonIndices the indices for each polygon in the mesh.
* @param triangleIndices the indices for each triangle in the mesh.
* @param id an optional id for the mesh.
* @param bounds the boundaries of the mesh.
*/
function PolygonMesh(vertices, triangleIndices, id, bounds) {
var _this = _super.call(this, vertices, triangleIndices, id, bounds) || this;
_this.miters = PolygonMesh.measureMiters(vertices);
return _this;
}
PolygonMesh.measureMiters = function (vertices, offset, count) {
if (offset === void 0) { offset = 0; }
if (count === void 0) { count = vertices.capacity() - offset; }
var miters = vec2_1.Vec2Buffer.create(count);
var previous = new vertex_1.VertexBuffer(vertices.data);
var current = vertices;
var line1 = new vec2_1.Vec2();
var line2 = new vec2_1.Vec2();
var last = offset + count - 1;
// Ex: vertices (0 1 2), offset = 0, count = 3
// (2 0 1): miter(<2,0>, <0,1>)
// (0 1 2): miter(<0,1>, <1,2>)
// (1 2 0): miter(<1,2>, <2,0>)
previous.moveToPosition(last); // 2
current.moveToPosition(offset); // 0
line1.setFromPointToPoint(previous, current); // <2,0>
previous.dataPosition = current.dataPosition; // 0
for (var i = offset; i < last; i++) {
current.moveToNext(); // 1, 2
line2.setFromPointToPoint(previous, current); // <0,1>, <1,2>
miters.rset(miter_1.measureMiter(line1, line2, 1, 3)); // miter(<2,0>, <0,1>), miter(<0,1>, <1,2>)
line1.set(line2); // <0,1>, <1,2>
previous.dataPosition = current.dataPosition; // 1, 2
}
current.moveToFirst(); // 0
line2.setFromPointToPoint(previous, current); // <2,0>
miters.rset(miter_1.measureMiter(line1, line2, 1, 3)); // miter(<1,2>, <2,0>)
return miters;
};
/**
* Creates the mesh for a regular polygon with n sides.
* @param n how many sides the polygon should have.
* @param isFlatTopped whether the polygon is flat-topped (true) or pointy-topped (false). Defaults to false.
* @param id an optional id for the mesh.
*/
PolygonMesh.regular = function (n, isFlatTopped, id) {
if (isFlatTopped === void 0) { isFlatTopped = false; }
var vertices = PolygonMesh.regularVertices(n, isFlatTopped);
var indices = PolygonMesh.regularIndices(n);
return new PolygonMesh(vertices, indices, id);
};
/**
* Generates the vertices for a regular polygon centered at (0,0).
* @param n how many sides the polygon should have.
* @param isFlatTopped whether the polygon is flat-topped (true) or pointy-topped (false). Defaults to false.
*/
PolygonMesh.regularVertices = function (n, isFlatTopped) {
if (isFlatTopped === void 0) { isFlatTopped = false; }
// Create a buffer big enough to hold the n vertices
var vertices = vertex_1.VertexBuffer.create(n);
// Create a matrix to rotate from vertex to vertex
var angle = 2 * Math.PI / n;
var rotation = mat2d_1.Mat2d.rotate(angle);
// Begin with the vertex (1,0), rotating for flat top polygon if requested
var vertex = vec2_1.Vec2Struct.create$(0, 1);
if (isFlatTopped) {
mat2d_1.Mat2d.rotate(angle / 2).map(vertex, vertex);
}
vertices.rset(vertex);
// Keep rotating the point and adding to buffer till it is full
while (vertices.hasValidPosition()) {
rotation.map(vertex, vertex);
vertices.rset(vertex);
}
return vertices;
};
/**
* Generates the indices for a regular mesh with n sides.
* The mesh will have 3*(n-2) indices.
* @param n how many sides the mesh should have.
*/
PolygonMesh.regularIndices = function (n) {
// Create an array big enough to hold all the index tuples
var indices = indextuple_1.IndexTupleBuffer.create(n - 2);
// Compute indices and add to array until it is full
var second = 1, third = 2;
while (indices.hasValidPosition()) {
indices.rset$(0, second, third);
second = third++;
}
// Return the indices
return indices;
};
/**
* Creates the mesh for a rectangle
* @param id an optional id for the mesh.
*/
PolygonMesh.rectangle = function (rect, id) {
var vertices = PolygonMesh.rectangleVertices(rect);
var indices = PolygonMesh.regularIndices(4);
return new PolygonMesh(vertices, indices, id, rect);
};
/**
* Extracts the vertices from the specified rect into a new vertex buffer.
* @param rect the rect from which to extract the vertices.
*/
PolygonMesh.rectangleVertices = function (rect) {
return new vertex_1.VertexBuffer(new Float32Array([
rect.left, rect.top,
rect.left, rect.bottom,
rect.right, rect.bottom,
rect.right, rect.top
]));
};
/**
* Creates the mesh for a star with n points and the specified inner and outer radii.
* @param n how many points the star should have.
* @param ratio ratio of the inner radius to the outer radius.
* @param id an optional id for the mesh.
*/
PolygonMesh.star = function (n, ratio, id) {
var vertices = PolygonMesh.starVertices(n, ratio);
var indices = PolygonMesh.starIndices(n);
return new PolygonMesh(vertices, indices, id);
};
/**
* Generates the vertices for a star centered at (0,0).
* @param points how many points the star should have.
* @param ratio ratio of the inner radius to the outer radius.
*/
PolygonMesh.starVertices = function (points, ratio) {
// Create vertex buffer big enough to hold the n inner vertices and n outer vertices
var vertices = vertex_1.VertexBuffer.create(points + points);
// Calculate the rotation angle
var angle = 2 * Math.PI / points;
// Create a rotation matrix
var rotation = new mat2d_1.Mat2d();
// Translate the center point vertically by the
// outer radius to get the first outer vertex.
var outerVertex = vec2_1.Vec2Struct.create$(0, 1);
// Translate the center point vertically by the inner radius
// and rotate by half the angle to get the first inner vertex
var innerVertex = vec2_1.Vec2Struct.create$(0, ratio);
rotation.setRotate(0.5 * angle);
rotation.map(innerVertex, innerVertex);
// Add the first outer and inner vertices to the buffer
vertices.rset(outerVertex);
vertices.rset(innerVertex);
// Set the matrix to rotate by the full angle
rotation.setRotate(angle);
// Keep rotating the inner and outer vertices and
// adding them to the array until it is full.
while (vertices.hasValidPosition()) {
rotation.map(outerVertex, outerVertex);
rotation.map(innerVertex, innerVertex);
vertices.rset(outerVertex);
vertices.rset(innerVertex);
}
// Return the path
return vertices;
};
/**
* Generates the indices for a star with n points.
* The star will have 3*(n-2) inner indices and 3n outer indices.
* @param n how many points the star should have.
*/
PolygonMesh.starIndices = function (n) {
var innerIndexCount = n - 2;
var outerIndexCount = n;
// Create an array big enough to hold all the indices
var indices = indextuple_1.IndexTupleBuffer.create(innerIndexCount + outerIndexCount);
// Compute inner indices and add to array
var first = 1, second = 3, third = 5;
while (innerIndexCount--) {
indices.rset$(first, second, third);
second = third++;
third++;
}
// Computer outer indices and add to array
first = 2 * n - 1;
second = 0;
third = 1;
while (outerIndexCount--) {
indices.rset$(first, second, third);
first = third++;
second = third++;
}
// Return the indices
return indices;
};
PolygonMesh.prototype.contains$ = function (x, y) {
return this.vertices.contains$(x, y);
};
return PolygonMesh;
}(Mesh));
exports.PolygonMesh = PolygonMesh;
var InstancedPolygonMesh = (function (_super) {
__extends(InstancedPolygonMesh, _super);
/**
* Creates a mesh with the specified data.
* @param vertices the mesh vertices.
* @param triangleIndices the indices for each triangle in the mesh.
* @param polygonIndices the indices for each polygon in the mesh.
* @param id an optional id for the mesh.
* @param bounds the boundaries of the mesh.
*/
function InstancedPolygonMesh(vertices, verticesPerInstance, triangleIndices, id, bounds) {
var _this = _super.call(this, vertices, triangleIndices, id, bounds) || this;
_this.verticesPerInstance = verticesPerInstance;
return _this;
}
InstancedPolygonMesh.spray = function (instance, instancesInInnerRing, rings, id) {
// Create vertex buffer big enough to hold all the shapes in the spray
var instanceVertices = instance.vertices;
var instanceVertexCount = instanceVertices.capacity();
var instanceCount = InstancedPolygonMesh.countInstancesInSpray(instancesInInnerRing, rings);
var vertexCount = instanceVertexCount * instanceCount;
var vertices = vertex_1.VertexBuffer.create(instanceVertexCount * instanceCount);
// Create helper variables for placing each shape in its ring
var angle = 2 * Math.PI / instancesInInnerRing;
var rotation = mat2d_1.Mat2d.rotate(angle);
var p1 = new point_1.Point(0, 0);
var p2 = new point_1.Point(0, 1);
var matrix = new mat2d_1.Mat2d();
// Fill each ring with shapes
for (var ring = 0; ring < rings; ring++) {
for (var shapes = instancesInInnerRing << ring; shapes > 0; shapes--) {
// Copy vertices into buffer
var offset = vertices.position();
instanceVertices.moveToFirst();
vertices.rsetFromBuffer(instanceVertices);
// Transform shape across line from p1 to p2
shape_1.Shape.stretchAcrossLine(matrix, instance, p1, p2);
vertices.transform(matrix, offset, instanceVertexCount);
// Position p1 and p2 for next shape in ring
rotation.map(p1, p1);
rotation.map(p2, p2);
}
// Push p1 and p2 onto the next ring
p1.y++;
p2.y++;
// Halve the rotation angle, doubling the number of shapes on the subsequent ring
rotation.setRotate(angle *= 0.5);
}
// Create index buffer big enough to hold all the indices
var instanceIndices = instance.triangleIndices;
var instanceTriangleCount = instanceIndices.capacity();
var indices = indextuple_1.IndexTupleBuffer.create(instanceTriangleCount * instanceCount);
// Copy indices repeatedly to buffer, offseting according to position
for (var offset = 0; offset < vertexCount; offset += instanceVertexCount) {
instanceIndices.moveToPosition(-1);
while (instanceIndices.moveToNext()) {
indices.set(instanceIndices);
indices.add$(offset, offset, offset);
indices.moveToNext();
}
}
return new InstancedPolygonMesh(vertices, instanceVertexCount, indices, id);
};
InstancedPolygonMesh.countInstancesInSpray = function (shapesInInnerRing, rings) {
//Note: uses the formula for the sum of the first n terms of a geometric series
//(3,2) -> 3*1 + 3*2 = 3(2^0 + 2^1) = 3*(1-2^2)/(1-2) = 3*(-3/-1) = 3*3 = 9;
//(3,3) -> 3*1 + 3*2 + 3*4 = 3(2^0 + 2^1 + 2^2) = 3*(1-2^3)/(1-2) = 3*(-7/-1) = 21;
//(m,n) -> m*2^0 + ... + m*2^(n-1) = m*(1-2^n)/(1-2) = m*(1-2^n)/(-1) = m*(2^n - 1)
return shapesInInnerRing * ((1 << rings) - 1);
};
InstancedPolygonMesh.prototype.contains$ = function (x, y) {
if (this.bounds.contains$(x, y)) {
var vertices = this.vertices;
var vertexCount = vertices.capacity();
var verticesPerInstance = this.verticesPerInstance;
for (var offset = 0; offset < vertexCount; offset += verticesPerInstance) {
if (vertices.contains$(x, y, offset, verticesPerInstance)) {
return vertices.moveToLast();
}
}
}
return false;
};
return InstancedPolygonMesh;
}(Mesh));
exports.InstancedPolygonMesh = InstancedPolygonMesh;
var MultiPolygonMesh = (function (_super) {
__extends(MultiPolygonMesh, _super);
/**
* Creates a mesh with the specified data.
* @param vertices the mesh vertices.
* @param polygonIndices the indices for each polygon in the mesh.
* @param triangleIndices the indices for each triangle in the mesh.
* @param id an optional id for the mesh.
* @param bounds the boundaries of the mesh.
*/
function MultiPolygonMesh(vertices, polygonIndices, triangleIndices, id, bounds) {
var _this = _super.call(this, vertices, triangleIndices, id, bounds) || this;
_this.polygonIndices = polygonIndices;
_this.miters = MultiPolygonMesh.measureMiters(vertices, polygonIndices);
return _this;
}
MultiPolygonMesh.measureMiters = function (vertices, polygonIndices) {
// Ex: vertices = [0 1 2 3 4], polygonIndices = [[0,1,2], [2,3,4]]
// (2 0 1): miter(<2,0>, <0,1>)
// (0 1 2): miter(<0,1>, <1,2>)
// (1 2 0): miter(<1,2>, <2,0>)
// (4 2 3): miter(<4,2>, <2,3>)
// (2 3 4): miter(<2,3>, <3,4>)
// (3 4 2): miter(<3,4>, <4,2>)
var count = 0;
for (var _i = 0, polygonIndices_1 = polygonIndices; _i < polygonIndices_1.length; _i++) {
var indices = polygonIndices_1[_i];
count += indices.length; // 3, 6
}
var miters = vec2_1.Vec2Buffer.create(count);
var previous = new vertex_1.VertexBuffer(vertices.data);
var current = vertices;
var line1 = new vec2_1.Vec2();
var line2 = new vec2_1.Vec2();
for (var _a = 0, polygonIndices_2 = polygonIndices; _a < polygonIndices_2.length; _a++) {
var indices = polygonIndices_2[_a];
var length_1 = indices.length;
previous.moveToPosition(indices[length_1 - 1]); // [2], [4]
current.moveToPosition(indices[0]); // [0], [2]
line1.setFromPointToPoint(previous, current); // [<2,0>], [<4,2>]
previous.dataPosition = current.dataPosition; // [0], [2]
for (var i = 1; i < length_1; i++) {
current.moveToPosition(indices[i]); // [1, 2], [3, 4]
line2.setFromPointToPoint(previous, current); // [<0,1>, <1,2>], [<2,3>, <3,4>]
miters.rset(miter_1.measureMiter(line1, line2, 1, 3)); // [[<2,0>, <0,1>], [<0,1>, <1,2>], [<4,2>, <2,3>], [<2,3>, <3,4>]]
line1.set(line2); // [<0,1>, <1,2>], [<2,3>, <3,4>]
previous.dataPosition = current.dataPosition; // [1, 2], [3, 4]
}
current.moveToPosition(indices[0]); // [0], [2]
line2.setFromPointToPoint(previous, current); // [<2,0>], [4,2]
miters.rset(miter_1.measureMiter(line1, line2, 1, 3)); // [<1,2>, <2,0>], [<3,4>, <4,2>]
}
return miters;
};
MultiPolygonMesh.prototype.contains$ = function (x, y) {
if (this.bounds.contains$(x, y)) {
for (var _i = 0, _a = this.polygonIndices; _i < _a.length; _i++) {
var indices = _a[_i];
if (this.vertices.indexedContains$(x, y, indices)) {
return this.vertices.moveToLast();
}
}
}
return false;
};
return MultiPolygonMesh;
}(Mesh));
exports.MultiPolygonMesh = MultiPolygonMesh;
//# sourceMappingURL=mesh.js.map