UNPKG

gl2d

Version:

2D graphics package for WebGL

475 lines 21.7 kB
"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