-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
switch to using element groups for lines
This starts bringing rendering closer to how its done in -native. Lines are added to a single vertex and element buffer. If the number of vertices exceeds the maximum number, instead of creating a new buffer it creates a new element group, and stores the offset of that group into the buffer.
- Loading branch information
Showing
7 changed files
with
314 additions
and
21 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
'use strict'; | ||
|
||
module.exports = createBucket; | ||
|
||
var Bucket = require('./bucket.js'); | ||
var LineBucket = require('./linebucket.js'); | ||
|
||
function createBucket(info, geometry, placement, indices, buffers) { | ||
if (info.line) { | ||
return new LineBucket(info, buffers, placement, indices); | ||
} else { | ||
return new Bucket(info, geometry, placement, indices); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
'use strict'; | ||
|
||
module.exports = ElementGroups; | ||
|
||
function ElementGroups(vertexBuffer, elementBuffer, groups) { | ||
|
||
this.vertexBuffer = vertexBuffer; | ||
this.elementBuffer = elementBuffer; | ||
|
||
if (groups) { | ||
this.groups = groups; | ||
} else { | ||
this.groups = []; | ||
} | ||
} | ||
|
||
ElementGroups.prototype.makeRoomFor = function(numVertices) { | ||
if (!this.current || this.current.vertexLength + numVertices > 65535) { | ||
this.current = new ElementGroup(this.vertexBuffer.index, this.elementBuffer.index); | ||
this.groups.push(this.current); | ||
} | ||
}; | ||
|
||
function ElementGroup(vertexStartIndex, elementStartIndex) { | ||
// the offset into the vertex buffer of the first vertex in this group | ||
this.vertexStartIndex = vertexStartIndex; | ||
this.elementStartIndex = elementStartIndex; | ||
this.elementLength = 0; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,224 @@ | ||
'use strict'; | ||
|
||
var ElementGroups = require('./elementgroups.js'); | ||
|
||
module.exports = LineBucket; | ||
|
||
function LineBucket(info, buffers, placement, elementGroups) { | ||
this.info = info; | ||
this.buffers = buffers; | ||
this.elementGroups = elementGroups || new ElementGroups(buffers.lineVertex, buffers.lineElement); | ||
} | ||
|
||
LineBucket.prototype.addFeature = function(lines) { | ||
var info = this.info; | ||
for (var i = 0; i < lines.length; i++) { | ||
this.addLine(lines[i], info['line-join'], info['line-cap'], | ||
info['line-miter-limit'], info['line-round-limit']); | ||
} | ||
}; | ||
|
||
LineBucket.prototype.addLine = function(vertices, join, cap, miterLimit, roundLimit) { | ||
if (vertices.length < 2) { | ||
console.warn('a line must have at least two vertices'); | ||
return; | ||
} | ||
|
||
var len = vertices.length, | ||
firstVertex = vertices[0], | ||
lastVertex = vertices[len - 1], | ||
closed = firstVertex.equals(lastVertex); | ||
|
||
var lineVertex = this.buffers.lineVertex; | ||
var lineElement = this.buffers.lineElement; | ||
|
||
// we could be more precies, but it would only save a negligible amount of space | ||
this.elementGroups.makeRoomFor(len * 4); | ||
var elementGroup = this.elementGroups.current; | ||
var vertexStartIndex = elementGroup.vertexStartIndex; | ||
|
||
if (len == 2 && closed) { | ||
// console.warn('a line may not have coincident points'); | ||
return; | ||
} | ||
|
||
join = join || 'miter'; | ||
cap = cap || 'butt'; | ||
miterLimit = miterLimit || 2; | ||
roundLimit = roundLimit || 1; | ||
|
||
var beginCap = cap, | ||
endCap = closed ? 'butt' : cap, | ||
flip = 1, | ||
distance = 0, | ||
currentVertex, prevVertex, nextVertex, prevNormal, nextNormal; | ||
|
||
// the last three vertices added | ||
var e1, e2, e3; | ||
|
||
if (closed) { | ||
currentVertex = vertices[len - 2]; | ||
nextNormal = firstVertex.sub(currentVertex)._unit()._perp(); | ||
} | ||
|
||
for (var i = 0; i < len; i++) { | ||
|
||
nextVertex = closed && i === len - 1 ? | ||
vertices[1] : // if the line is closed, we treat the last vertex like the first | ||
vertices[i + 1]; // just the next vertex | ||
|
||
// if two consecutive vertices exist, skip the current one | ||
if (nextVertex && vertices[i].equals(nextVertex)) continue; | ||
|
||
if (nextNormal) prevNormal = nextNormal; | ||
if (currentVertex) prevVertex = currentVertex; | ||
|
||
currentVertex = vertices[i]; | ||
|
||
// 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 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; | ||
|
||
// 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)._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'; | ||
} | ||
|
||
if (currentJoin === 'miter' && miterLength > miterLimit && miterLength < Math.SQRT2) { | ||
currentJoin = 'bevel'; | ||
} | ||
|
||
// Mitered joins | ||
if (currentJoin === 'miter') { | ||
|
||
if (miterLength > 100) { | ||
// Almost parallel lines | ||
flip = -flip; | ||
joinNormal = nextNormal; | ||
|
||
} 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); | ||
|
||
} else { | ||
// scale the unit vector by the miter length | ||
joinNormal._mult(miterLength); | ||
} | ||
|
||
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 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); | ||
} | ||
|
||
// Start next segment with a butt or square cap | ||
if (nextVertex) { | ||
addCurrentVertex(nextNormal, currentJoin === 'square' ? -1 : 0, false); | ||
} | ||
} | ||
|
||
} | ||
|
||
|
||
/* | ||
* 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) - vertexStartIndex; | ||
if (e1 >= 0 && e2 >= 0) { | ||
lineElement.add(e1, e2, e3); | ||
elementGroup.elementLength++; | ||
} | ||
e1 = e2; e2 = e3; | ||
|
||
extrude = normal.mult(-flip); | ||
if (endBox) extrude._sub(normal.perp()._mult(endBox)); | ||
e3 = lineVertex.add(currentVertex, extrude, tx, 1, distance) - vertexStartIndex; | ||
if (e1 >= 0 && e2 >= 0) { | ||
lineElement.add(e1, e2, e3); | ||
elementGroup.elementLength++; | ||
} | ||
e1 = e2; e2 = e3; | ||
} | ||
}; | ||
|
||
LineBucket.prototype.toJSON = function() { | ||
return { | ||
indices: this.elementGroups | ||
}; | ||
}; | ||
|
||
LineBucket.prototype.start = function() { | ||
}; | ||
LineBucket.prototype.end = function() { | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
1 comment
on commit 1acfa85
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
It's better to avoid forEach in perf-sensitive places (like functions called many times on each frame). Simple for loop is much faster and doesn't create a closure every time.