Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

index line renders + cleanup #351

Merged
merged 11 commits into from
Mar 23, 2014
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

- `zoomTo` and `scaleTo` now accept origin point as array.
- Added `Map` `interactive` option that disables interactions when set to false.
- **breaking**: round linejoins are now specified with `"join": "round"` on the bucket,
and they no longer need `"linejoin": "round"` in the style.

### 0.0.9

Expand Down
4 changes: 4 additions & 0 deletions js/geometry/bucket.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@ Bucket.prototype.start = function() {
var geometry = this.geometry;

this.indices = {
lineBufferIndex: geometry.lineBufferIndex,
lineVertexIndex: geometry.lineVertex.index,
lineElementIndex: geometry.lineElement.index,

fillBufferIndex: geometry.fillBufferIndex,
fillVertexIndex: geometry.fillVertex.index,
Expand All @@ -62,7 +64,9 @@ Bucket.prototype.end = function() {
var geometry = this.geometry;
var indices = this.indices;

indices.lineBufferIndexEnd = geometry.lineBufferIndex;
indices.lineVertexIndexEnd = geometry.lineVertex.index;
indices.lineElementIndexEnd = geometry.lineElement.index;

indices.fillBufferIndexEnd = geometry.fillBufferIndex;
indices.fillVertexIndexEnd = geometry.fillVertex.index;
Expand Down
249 changes: 142 additions & 107 deletions js/geometry/geometry.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use strict';

var LineVertexBuffer = require('./linevertexbuffer.js');
var LineElementBuffer = require('./lineelementbuffer.js');
var FillVertexBuffer = require('./fillvertexbuffer.js');
var FillElementsBuffer = require('./fillelementsbuffer.js');
var GlyphVertexBuffer = require('./glyphvertexbuffer.js');
Expand All @@ -11,10 +12,15 @@ module.exports = Geometry;

function Geometry() {

this.lineVertex = new LineVertexBuffer();
this.glyphVertex = new GlyphVertexBuffer();
this.pointVertex = new PointVertexBuffer();

this.lineBuffers = [];
this.lineBufferIndex = -1;
this.lineVertex = null;
this.lineElement = null;
this.swapLineBuffers(0);

this.fillBuffers = [];
this.fillBufferIndex = -1;
this.fillVertex = null;
Expand All @@ -28,9 +34,14 @@ function Geometry() {
Geometry.prototype.bufferList = function() {
var buffers = [
this.glyphVertex.array,
this.lineVertex.array,
this.pointVertex.array
this.pointVertex.array,
];

for (var k = 0, linelen = this.lineBuffers.length; k < linelen; k++) {
buffers.push(
this.lineBuffers[k].vertex.array,
this.lineBuffers[k].element.array);
}
for (var i = 0, len = this.fillBuffers.length; i < len; i++) {
buffers.push(
this.fillBuffers[i].vertex.array,
Expand All @@ -56,6 +67,20 @@ Geometry.prototype.swapFillBuffers = function(vertexCount) {
}
};

Geometry.prototype.swapLineBuffers = function(vertexCount) {

if (!this.lineVertex || this.lineVertex.index + vertexCount >= 65536) {
this.lineVertex = new LineVertexBuffer();
this.lineElement = new LineElementBuffer();

this.lineBuffers.push({
vertex: this.lineVertex,
element: this.lineElement
});
this.lineBufferIndex++;
}
};

Geometry.prototype.addPoints = function(vertices, place) {
var fullRange = [2 * Math.PI, 0];

Expand All @@ -82,6 +107,12 @@ Geometry.prototype.addLine = function(vertices, join, cap, miterLimit, roundLimi
lastVertex = vertices[len - 1],
closed = firstVertex.equals(lastVertex);

// we could be more precies, but it would only save a negligible amount of space
this.swapLineBuffers(len * 4);

var lineVertex = this.lineVertex;
var lineElement = this.lineElement;

if (len == 2 && closed) {
// console.warn('a line may not have coincident points');
return;
Expand All @@ -98,14 +129,14 @@ Geometry.prototype.addLine = function(vertices, join, cap, miterLimit, roundLimi
distance = 0,
currentVertex, prevVertex, nextVertex, prevNormal, nextNormal;

// the last three vertices added
var e1, e2, e3;

if (closed) {
currentVertex = vertices[len - 2];
nextNormal = currentVertex.normal(lastVertex);
nextNormal = firstVertex.sub(currentVertex)._unit()._perp();
}

// Start all lines with a degenerate vertex
this.lineVertex.addDegenerate();

for (var i = 0; i < len; i++) {

nextVertex = closed && i === len - 1 ?
Expand All @@ -115,135 +146,139 @@ Geometry.prototype.addLine = function(vertices, join, cap, miterLimit, roundLimi
// if two consecutive vertices exist, skip the current one
if (nextVertex && vertices[i].equals(nextVertex)) continue;

if (nextNormal) prevNormal = nextNormal.mult(-1);
if (nextNormal) prevNormal = nextNormal;
if (currentVertex) prevVertex = currentVertex;

currentVertex = vertices[i];

// At this point:
// currentVertex will be added this iteration, and is distinct from prevVertex and nextVertex
// prevNormal and prevVertex are undefined if this is the first vertex of an unclosed line
// nextVertex and nextNormal are undefined if this is the last vertex of an unclosed line

// Calculate how far along the line the currentVertex is
if (prevVertex) distance += currentVertex.dist(prevVertex);

// Calculate the normal towards the next vertex in this line. In case
// there is no next vertex, pretend that the line is continuing straight,
// meaning that we are just reversing the previous normal
nextNormal = nextVertex ? currentVertex.normal(nextVertex) : prevNormal.mult(-1);
// meaning that we are just using the previous normal.
nextNormal = nextVertex ? nextVertex.sub(currentVertex)._unit()._perp() : prevNormal;

// If we still don't have a previous normal, this is the beginning of a
// non-closed line, so we're doing a straight "join".
prevNormal = prevNormal || nextNormal.mult(-1);
prevNormal = prevNormal || nextNormal;

// Determine the normal of the join extrusion. It is the angle bisector
// of the segments between the previous line and the next line.
var joinNormal = prevNormal.add(nextNormal);

// Cross product yields 0..1 depending on whether they are parallel or perpendicular.
var joinAngularity = nextNormal.x * joinNormal.y - nextNormal.y * joinNormal.x;
joinNormal._div(joinAngularity);

var roundness = Math.max(Math.abs(joinNormal.x), Math.abs(joinNormal.y));

// Determine whether we should actually draw a round/bevelled/butt line join. It looks
// better if we do, but we can get away with drawing a mitered join for
// joins that have a very small angle. For this, we have the "roundLimit"
// parameter. We do this to reduce the number of vertices we have to
// write into the line vertex buffer. Note that joinAngularity may be 0,
// so the roundness grows to infinity. This is intended.
var roundJoin = (join === 'round' || join === 'bevel' || join === 'butt') && roundness > roundLimit;

// Close up the previous line for a round join
if (roundJoin && prevVertex && nextVertex) {
// Add first vertex
this.lineVertex.add(currentVertex.x, currentVertex.y, // vertex pos
flip * prevNormal.y, -flip * prevNormal.x, // extrude normal
0, 0, distance); // texture normal

// Add second vertex.
this.lineVertex.add(currentVertex.x, currentVertex.y, // vertex pos
-flip * prevNormal.y, flip * prevNormal.x, // extrude normal
0, 1, distance); // texture normal

// Degenerate triangle
if (join === 'round' || join === 'butt') {
this.lineVertex.addDegenerate();
}

if (join === 'round') prevVertex = null;
var joinNormal = prevNormal.add(nextNormal)._unit();

/* joinNormal prevNormal
* ↖ ↑
* .________. prevVertex
* |
* nextNormal ← | currentVertex
* |
* nextVertex !
*
*/

// Calculate the length of the miter (the ratio of the miter to the width).
// Find the cosine of the angle between the next and join normals
// using dot product. The inverse of that is the miter length.
var cosHalfAngle = joinNormal.x * nextNormal.x + joinNormal.y * nextNormal.y;
var miterLength = 1 / cosHalfAngle;

// Whether any vertices have been
var startOfLine = e1 === undefined || e2 === undefined;

// The join if a middle vertex, otherwise the cap.
var currentJoin = (prevVertex && nextVertex) ? join :
nextVertex ? beginCap : endCap;

if (currentJoin === 'round' && miterLength < roundLimit) {
currentJoin = 'miter';
}

prevNormal = nextNormal.mult(-1);
flip = 1;
if (currentJoin === 'miter' && miterLength > miterLimit && miterLength < Math.SQRT2) {
currentJoin = 'bevel';
}

// Add a cap.
if (!prevVertex && (beginCap == 'round' || beginCap == 'square' || (roundJoin && join === 'round'))) {
// Mitered joins
if (currentJoin === 'miter') {

var tex = beginCap == 'round' || roundJoin ? 1 : 0;
if (miterLength > 100) {
// Almost parallel lines
flip = -flip;
joinNormal = nextNormal;

// Add first vertex
this.lineVertex.add(currentVertex.x, currentVertex.y, // vertex pos
flip * (prevNormal.x + prevNormal.y), flip * (-prevNormal.x + prevNormal.y), // extrude normal
tex, 0, distance); // texture normal
} else if (miterLength > miterLimit) {
flip = -flip;
// miter is too big, flip the direction to make a beveled join
var bevelLength = miterLength * prevNormal.add(nextNormal).mag() / prevNormal.sub(nextNormal).mag();
joinNormal._perp()._mult(flip * bevelLength);

// Add second vertex
this.lineVertex.add(currentVertex.x, currentVertex.y, // vertex pos
flip * (prevNormal.x - prevNormal.y), flip * (prevNormal.x + prevNormal.y), // extrude normal
tex, 1, distance); // texture normal
}
} else {
// scale the unit vector by the miter length
joinNormal._mult(miterLength);
}

if (roundJoin) {
// ROUND JOIN
// Add first vertex
this.lineVertex.add(currentVertex.x, currentVertex.y, // vertex pos
-flip * nextNormal.y, flip * nextNormal.x, // extrude normal
0, 0, distance); // texture normal

// Add second vertex
this.lineVertex.add(currentVertex.x, currentVertex.y, // vertex pos
flip * nextNormal.y, -flip * nextNormal.x, // extrude normal
0, 1, distance); // texture normal

} else if ((nextVertex || endCap != 'square') && (prevVertex || beginCap != 'square')) {
// MITER JOIN
if (Math.abs(joinAngularity) < 0.01) {
// The two normals are almost parallel.
joinNormal.x = -nextNormal.y;
joinNormal.y = nextNormal.x;

} else if (roundness > miterLimit) {
// If the miter grows too large, flip the direction to make a bevel join.
joinNormal = prevNormal.sub(nextNormal)._div(joinAngularity);
flip = -flip;
addCurrentVertex(joinNormal, 0, false);

// All other types of joins
} else {

// Close previous segment with a butt or a square cap
if (!startOfLine) {
addCurrentVertex(prevNormal, currentJoin === 'square' ? 1 : 0, false);
}

// Add first vertex
this.lineVertex.add(currentVertex.x, currentVertex.y, // vertex pos
flip * joinNormal.x, flip * joinNormal.y, // extrude normal
0, 0, distance); // texture normal
// Add round cap or linejoin at end of segment
if (!startOfLine && currentJoin === 'round') {
addCurrentVertex(prevNormal, 1, true);
}

// Segment include cap are done, unset vertices to disconnect segments.
// Or leave them to create a bevel.
if (startOfLine || currentJoin !== 'bevel') {
e1 = e2 = -1;
flip = 1;
}

// Add round cap before first segment
if (startOfLine && beginCap === 'round') {
addCurrentVertex(nextNormal, -1, true);
}

// Add second vertex
this.lineVertex.add(currentVertex.x, currentVertex.y, // vertex pos
-flip * joinNormal.x, -flip * joinNormal.y, // extrude normal
0, 1, distance); // texture normal
// Start next segment with a butt or square cap
if (nextVertex) {
addCurrentVertex(nextNormal, currentJoin === 'square' ? -1 : 0, false);
}
}

// Add the end cap, but only if this vertex is distinct from the begin vertex.
if (!nextVertex && (endCap == 'round' || endCap == 'square')) {
var capTex = endCap == 'round' ? 1 : 0;
}

// Add first vertex
this.lineVertex.add(currentVertex.x, currentVertex.y, // vertex pos
nextNormal.x - flip * nextNormal.y, flip * nextNormal.x + nextNormal.y, // extrude normal
capTex, 0, distance); // texture normal

// Add second vertex
this.lineVertex.add(currentVertex.x, currentVertex.y, // vertex pos
nextNormal.x + flip * nextNormal.y, -flip * nextNormal.x + nextNormal.y, // extrude normal
capTex, 1, distance); // texture normal
}
/*
* Adds two vertices to the buffer that are
* normal and -normal from the currentVertex.
*
* endBox moves the extrude one unit in the direction of the line
* to create square or round cap.
*
* endBox === 1 moves the extrude in the direction of the line
* endBox === -1 moves the extrude in the reverse direction
*/
function addCurrentVertex(normal, endBox, round) {

var tx = round ? 1 : 0;
var extrude;

extrude = normal.mult(flip);
if (endBox) extrude._sub(normal.perp()._mult(endBox));
e3 = lineVertex.add(currentVertex, extrude, tx, 0, distance);
if (e1 >= 0 && e2 >= 0) lineElement.add(e1, e2, e3);
e1 = e2; e2 = e3;

extrude = normal.mult(-flip);
if (endBox) extrude._sub(normal.perp()._mult(endBox));
e3 = lineVertex.add(currentVertex, extrude, tx, 1, distance);
if (e1 >= 0 && e2 >= 0) lineElement.add(e1, e2, e3);
e1 = e2; e2 = e3;
}
};

Expand Down
26 changes: 26 additions & 0 deletions js/geometry/lineelementbuffer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
'use strict';

var Buffer = require('./buffer.js');

module.exports = LineElementBuffer;

function LineElementBuffer(buffer) {
Buffer.call(this, buffer);
}

LineElementBuffer.prototype = Object.create(Buffer.prototype);

LineElementBuffer.prototype.itemSize = 6; // bytes per triangle (3 * unsigned short == 6 bytes)
LineElementBuffer.prototype.arrayType = 'ELEMENT_ARRAY_BUFFER';

LineElementBuffer.prototype.add = function(a, b, c) {
var pos2 = this.pos / 2;

this.resize();

this.ushorts[pos2 + 0] = a;
this.ushorts[pos2 + 1] = b;
this.ushorts[pos2 + 2] = c;

this.pos += this.itemSize;
};
Loading