Skip to content

Commit

Permalink
Support binary logical attributes in PathLayer and SolidPolygon… (vis…
Browse files Browse the repository at this point in the history
  • Loading branch information
Pessimistress authored Nov 25, 2019
1 parent 045f4f2 commit 3b19806
Show file tree
Hide file tree
Showing 9 changed files with 272 additions and 35 deletions.
4 changes: 3 additions & 1 deletion modules/core/src/lib/attribute/attribute-manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -243,8 +243,10 @@ export default class AttributeManager {
// Step 1: try update attribute directly from external buffers
} else if (attribute.setBinaryValue(buffers[accessorName], data.startIndices)) {
// Step 2: try set packed value from external typed array
} else if (attribute.setConstantValue(props[accessorName])) {
} else if (!buffers[accessorName] && attribute.setConstantValue(props[accessorName])) {
// Step 3: try set constant value from props
// Note: if buffers[accessorName] is supplied, ignore props[accessorName]
// This may happen when setBinaryValue falls through to use the auto updater
} else if (attribute.needsUpdate()) {
// Step 4: update via updater callback
updated = true;
Expand Down
111 changes: 81 additions & 30 deletions modules/core/src/utils/tesselator.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@
// 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 {createIterable} from './iterable-utils';
import {createIterable, getAccessorFromBuffer} from './iterable-utils';
import defaultTypedArrayManager from './typed-array-manager';
import assert from './assert';

export default class Tesselator {
constructor(opts = {}) {
Expand All @@ -41,13 +42,38 @@ export default class Tesselator {
/* Public methods */
updateGeometry(opts) {
Object.assign(this.opts, opts);
const {data, getGeometry, positionFormat, dataChanged, normalize = true} = this.opts;

const {
data,
buffers = {},
getGeometry,
geometryBuffer,
positionFormat,
dataChanged,
normalize = true
} = this.opts;
this.data = data;
this.getGeometry = getGeometry;
this.positionSize = positionFormat === 'XY' ? 2 : 3;
this.positionSize =
(geometryBuffer && geometryBuffer.size) || (positionFormat === 'XY' ? 2 : 3);
this.buffers = buffers;
this.normalize = normalize;

// Handle external logical value
if (geometryBuffer) {
assert(
ArrayBuffer.isView(geometryBuffer.value || geometryBuffer) && data.startIndices,
'invalid geometries'
);
this.getGeometry = this.getGeometryFromBuffer(geometryBuffer);

if (!normalize) {
// skip packing and set attribute value directly
// TODO - avoid mutating user-provided object
buffers.positions = geometryBuffer;
}
}
this.geometryBuffer = buffers.positions;

if (Array.isArray(dataChanged)) {
// is partial update
for (const dataRange of dataChanged) {
Expand All @@ -74,18 +100,33 @@ export default class Tesselator {
throw new Error('Not implemented');
}

getGeometryFromBuffer(geometryBuffer) {
return getAccessorFromBuffer(geometryBuffer.value || geometryBuffer, {
size: this.positionSize,
offset: geometryBuffer.offset,
stride: geometryBuffer.stride,
startIndices: this.data.startIndices
});
}

/* Private utility methods */
_allocate(instanceCount, copy) {
// allocate attributes
const {attributes, _attributeDefs, typedArrayManager} = this;
const {attributes, buffers, _attributeDefs, typedArrayManager} = this;
for (const name in _attributeDefs) {
const def = _attributeDefs[name];
// If dataRange is supplied, this is a partial update.
// In case we need to reallocate the typed array, it will need the old values copied
// before performing partial update.
def.copy = copy;

attributes[name] = typedArrayManager.allocate(attributes[name], instanceCount, def);
if (name in buffers) {
// Use external buffer
typedArrayManager.release(attributes[name]);
attributes[name] = null;
} else {
const def = _attributeDefs[name];
// If dataRange is supplied, this is a partial update.
// In case we need to reallocate the typed array, it will need the old values copied
// before performing partial update.
def.copy = copy;

attributes[name] = typedArrayManager.allocate(attributes[name], instanceCount, def);
}
}
}

Expand All @@ -109,25 +150,33 @@ export default class Tesselator {
return;
}

let {indexStarts, vertexStarts} = this;

if (!dataRange) {
// Full update - regenerate buffer layout from scratch
indexStarts = [0];
vertexStarts = [0];
}

let {indexStarts, vertexStarts, instanceCount} = this;
const {geometryBuffer} = this;
const {startRow = 0, endRow = Infinity} = dataRange || {};
this._forEachGeometry(
(geometry, dataIndex) => {
vertexStarts[dataIndex + 1] = vertexStarts[dataIndex] + this.getGeometrySize(geometry);
},
startRow,
endRow
);

// count instances
const instanceCount = vertexStarts[vertexStarts.length - 1];
if (this.normalize || !geometryBuffer) {
if (!dataRange) {
// Full update - regenerate buffer layout from scratch
indexStarts = [0];
vertexStarts = [0];
}
this._forEachGeometry(
(geometry, dataIndex) => {
vertexStarts[dataIndex + 1] = vertexStarts[dataIndex] + this.getGeometrySize(geometry);
},
startRow,
endRow
);
// count instances
instanceCount = vertexStarts[vertexStarts.length - 1];
} else {
const bufferValue = geometryBuffer.value || geometryBuffer;
const bufferStride =
geometryBuffer.stride / bufferValue.BYTES_PER_ELEMENT || this.positionSize;
// assume user provided data is already normalized
vertexStarts = this.data.startIndices;
instanceCount = bufferValue.length / bufferStride;
}

// allocate attributes
this._allocate(instanceCount, Boolean(dataRange));
Expand All @@ -142,7 +191,9 @@ export default class Tesselator {
(geometry, dataIndex) => {
context.vertexStart = vertexStarts[dataIndex];
context.indexStart = indexStarts[dataIndex];
context.geometrySize = vertexStarts[dataIndex + 1] - vertexStarts[dataIndex];
const vertexEnd =
dataIndex < vertexStarts.length - 1 ? vertexStarts[dataIndex + 1] : instanceCount;
context.geometrySize = vertexEnd - vertexStarts[dataIndex];
context.geometryIndex = dataIndex;
this.updateGeometryAttributes(geometry, context);
},
Expand Down
3 changes: 3 additions & 0 deletions modules/layers/src/path-layer/path-layer.js
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,11 @@ export default class PathLayer extends Layer {

if (geometryChanged) {
const {pathTesselator} = this.state;
const buffers = props.data.attributes || {};
pathTesselator.updateGeometry({
data: props.data,
geometryBuffer: buffers.getPath,
buffers,
normalize: !props._pathType,
loop: props._pathType === 'loop',
getGeometry: props.getPath,
Expand Down
11 changes: 11 additions & 0 deletions modules/layers/src/path-layer/path-tesselator.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,14 @@ export default class PathTesselator extends Tesselator {
});
}

getGeometryFromBuffer(buffer) {
if (this.normalize) {
return super.getGeometryFromBuffer(buffer);
}
// we don't need to read the positions if no normalization
return () => null;
}

/* Getters */
get(attributeName) {
return this.attributes[attributeName];
Expand Down Expand Up @@ -89,6 +97,9 @@ export default class PathTesselator extends Tesselator {

_updatePositions(path, context) {
const {positions} = this.attributes;
if (!positions) {
return;
}
const {vertexStart, geometrySize} = context;

// positions -- A0 A1 B0 B1 B2 B3 B0 B1 B2 --
Expand Down
22 changes: 19 additions & 3 deletions modules/layers/src/solid-polygon-layer/polygon-tesselator.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,18 +44,28 @@ export default class PolygonTesselator extends Tesselator {

/* Getters */
get(attributeName) {
const {attributes} = this;
if (attributeName === 'indices') {
return this.attributes.indices.subarray(0, this.vertexCount);
return attributes.indices && attributes.indices.subarray(0, this.vertexCount);
}

return this.attributes[attributeName];
return attributes[attributeName];
}

/* Implement base Tesselator interface */
getGeometrySize(polygon) {
return Polygon.getVertexCount(polygon, this.positionSize, this.normalize);
}

getGeometryFromBuffer(buffer) {
const getGeometry = super.getGeometryFromBuffer(buffer);
if (this.normalize || !this.buffers.indices) {
return getGeometry;
}
// we don't need to read the positions if no normalization/tesselation
return () => null;
}

updateGeometryAttributes(polygon, context) {
if (this.normalize) {
polygon = Polygon.normalize(polygon, this.positionSize, context.geometrySize);
Expand All @@ -71,6 +81,9 @@ export default class PolygonTesselator extends Tesselator {
const {attributes, indexStarts, typedArrayManager} = this;

let target = attributes.indices;
if (!target) {
return;
}
let i = indexStart;

// 1. get triangulated indices for the internal areas
Expand All @@ -96,6 +109,9 @@ export default class PolygonTesselator extends Tesselator {
attributes: {positions},
positionSize
} = this;
if (!positions) {
return;
}
const polygonPositions = polygon.positions || polygon;

for (let i = vertexStart, j = 0; j < geometrySize; i++, j++) {
Expand All @@ -114,7 +130,7 @@ export default class PolygonTesselator extends Tesselator {
attributes: {vertexValid},
positionSize
} = this;
const {holeIndices} = polygon;
const holeIndices = polygon && polygon.holeIndices;
/* We are reusing the some buffer for `nextPositions` by offseting one vertex
* to the left. As a result,
* the last vertex of each ring overlaps with the first vertex of the next ring.
Expand Down
3 changes: 3 additions & 0 deletions modules/layers/src/solid-polygon-layer/solid-polygon-layer.js
Original file line number Diff line number Diff line change
Expand Up @@ -244,9 +244,12 @@ export default class SolidPolygonLayer extends Layer {
// tessellator needs to be invoked
if (geometryConfigChanged) {
const {polygonTesselator} = this.state;
const buffers = props.data.attributes || {};
polygonTesselator.updateGeometry({
data: props.data,
normalize: props._normalize,
geometryBuffer: buffers.getPolygon,
buffers,
getGeometry: props.getPolygon,
positionFormat: props.positionFormat,
fp64: this.use64bitPositions(),
Expand Down
51 changes: 51 additions & 0 deletions test/modules/layers/path-tesselator.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -219,3 +219,54 @@ test('PathTesselator#normalize', t => {

t.end();
});

test('PathTesselator#geometryBuffer', t => {
const sampleData = {
length: 2,
startIndices: [0, 2],
attributes: {
getPath: new Float64Array([1, 1, 2, 2, 1, 1, 2, 2, 3, 3, 1, 1])
}
};
const tesselator = new PathTesselator({
data: sampleData,
buffers: sampleData.attributes,
geometryBuffer: sampleData.attributes.getPath,
positionFormat: 'XY'
});

t.is(tesselator.instanceCount, 8, 'Updated instanceCount from geometryBuffer');
t.deepEquals(
tesselator.get('positions').slice(0, 24),
[1, 1, 0, 2, 2, 0, 1, 1, 0, 2, 2, 0, 3, 3, 0, 1, 1, 0, 2, 2, 0, 3, 3, 0],
'positions are populated'
);
t.deepEquals(
tesselator.get('segmentTypes').slice(0, 8),
[3, 4, 4, 0, 0, 0, 4, 4],
'segmentTypes are populated'
);

tesselator.updateGeometry({
normalize: false
});

t.is(tesselator.instanceCount, 6, 'Updated instanceCount from geometryBuffer');
t.is(tesselator.vertexStarts, sampleData.startIndices, 'Used external startIndices');
t.notOk(tesselator.get('positions'), 'skipped packing positions');
t.deepEquals(
tesselator.get('segmentTypes').slice(0, 6),
[3, 4, 1, 0, 2, 4],
'segmentTypes are populated'
);

t.throws(
() =>
tesselator.updateGeometry({
data: {length: 2}
}),
'throws if missing startIndices'
);

t.end();
});
57 changes: 57 additions & 0 deletions test/modules/layers/polygon-tesselation.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -283,3 +283,60 @@ test('PolygonTesselator#normalize', t => {

t.end();
});

test('PolygonTesselator#geometryBuffer', t => {
const sampleData = {
length: 2,
startIndices: [0, 3],
attributes: {
getPolygon: new Float64Array([1, 1, 2, 2, 3, 3, 0, 0, 2, 0, 2, 2, 0, 2, 0, 0])
}
};
const tesselator = new PolygonTesselator({
data: sampleData,
buffers: sampleData.attributes,
geometryBuffer: sampleData.attributes.getPolygon,
positionFormat: 'XY'
});

t.is(tesselator.instanceCount, 9, 'Updated instanceCount from geometryBuffer');
t.deepEquals(
tesselator.get('positions').slice(0, 27),
[1, 1, 0, 2, 2, 0, 3, 3, 0, 1, 1, 0, 0, 0, 0, 2, 0, 0, 2, 2, 0, 0, 2, 0, 0, 0, 0],
'positions are populated'
);
t.ok(tesselator.get('indices'), 'indices generated');
t.deepEquals(
tesselator.get('vertexValid').slice(0, 9),
[1, 1, 1, 0, 1, 1, 1, 1, 0],
'vertexValid are populated'
);

tesselator.updateGeometry({
normalize: false
});

t.is(tesselator.instanceCount, 8, 'Updated instanceCount from geometryBuffer');
t.is(tesselator.vertexStarts, sampleData.startIndices, 'Used external startIndices');
t.notOk(tesselator.get('positions'), 'skipped packing positions');
t.ok(tesselator.get('indices'), 'indices generated');
t.deepEquals(
tesselator.get('vertexValid').slice(0, 8),
[1, 1, 0, 1, 1, 1, 1, 0],
'vertexValid are populated'
);

sampleData.attributes.indices = new Uint16Array([6, 3, 4, 4, 5, 6]);
tesselator.updateGeometry({
normalize: false
});
t.notOk(tesselator.get('positions'), 'skipped packing positions');
t.notOk(tesselator.get('indices'), 'skipped packing indices');
t.deepEquals(
tesselator.get('vertexValid').slice(0, 8),
[1, 1, 0, 1, 1, 1, 1, 0],
'vertexValid are populated'
);

t.end();
});
Loading

0 comments on commit 3b19806

Please sign in to comment.