Skip to content

Commit

Permalink
Support loaders.gl data attributes (visgl#3302)
Browse files Browse the repository at this point in the history
  • Loading branch information
Pessimistress authored Jul 11, 2019
1 parent 1fd5eb8 commit 6aeb983
Show file tree
Hide file tree
Showing 7 changed files with 206 additions and 83 deletions.
70 changes: 28 additions & 42 deletions examples/website/point-cloud/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,12 @@ export class App extends PureComponent {
super(props);

this.state = {
viewState: INITIAL_VIEW_STATE,
pointsCount: 0,
points: null
viewState: INITIAL_VIEW_STATE
};

this._onLoad = this._onLoad.bind(this);
this._onViewStateChange = this._onViewStateChange.bind(this);
this._rotateCamera = this._rotateCamera.bind(this);

load(LAZ_SAMPLE).then(this._onLoad);
}

_onViewStateChange({viewState}) {
Expand All @@ -66,65 +62,55 @@ export class App extends PureComponent {
});
}

_onLoad({header, loaderData, attributes, progress}) {
_onLoad({header, loaderData, progress}) {
// metadata from LAZ file header
const {mins, maxs} = loaderData.header;
let {viewState} = this.state;

if (mins && maxs) {
// File contains bounding box info
viewState = {
...viewState,
target: [(mins[0] + maxs[0]) / 2, (mins[1] + maxs[1]) / 2, (mins[2] + maxs[2]) / 2],
/* global window */
zoom: Math.log2(window.innerWidth / (maxs[0] - mins[0])) - 1
};
this.setState(
{
viewState: {
...this.state.viewState,
target: [(mins[0] + maxs[0]) / 2, (mins[1] + maxs[1]) / 2, (mins[2] + maxs[2]) / 2],
/* global window */
zoom: Math.log2(window.innerWidth / (maxs[0] - mins[0])) - 1
}
},
this._rotateCamera
);
}

if (this.props.onLoad) {
this.props.onLoad({count: header.vertexCount, progress: 1});
}

this.setState(
{
pointsCount: header.vertexCount,
points: attributes.POSITION.value,
viewState
},
this._rotateCamera
);
}

_renderLayers() {
const {pointsCount, points} = this.state;

return [
points &&
new PointCloudLayer({
id: 'laz-point-cloud-layer',
coordinateSystem: COORDINATE_SYSTEM.IDENTITY,
numInstances: pointsCount,
instancePositions: points,
getNormal: [0, 1, 0],
getColor: [255, 255, 255],
opacity: 0.5,
pointSize: 0.5
})
];
}

render() {
const {viewState} = this.state;

const layers = [
new PointCloudLayer({
id: 'laz-point-cloud-layer',
data: LAZ_SAMPLE,
onDataLoad: this._onLoad,
coordinateSystem: COORDINATE_SYSTEM.IDENTITY,
getNormal: [0, 1, 0],
getColor: [255, 255, 255],
opacity: 0.5,
pointSize: 0.5
})
];

return (
<DeckGL
views={new OrbitView()}
viewState={viewState}
controller={true}
onViewStateChange={this._onViewStateChange}
layers={this._renderLayers()}
layers={layers}
parameters={{
clearColor: [0.07, 0.14, 0.19, 1]
clearColor: [0.93, 0.86, 0.81, 1]
}}
/>
);
Expand Down
6 changes: 5 additions & 1 deletion modules/core/src/lib/attribute-manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,11 @@ export default class AttributeManager {
for (const attributeName in this.attributes) {
const attribute = this.attributes[attributeName];

if (attribute.setExternalBuffer(buffers[attributeName], this.numInstances)) {
if (
attribute.setExternalBuffer(
buffers[attributeName] || (data.attributes && data.attributes[attributeName])
)
) {
// Attribute is using external buffer from the props
} else if (attribute.setGenericValue(props[attribute.getAccessor()])) {
// Attribute is using generic value from the props
Expand Down
65 changes: 34 additions & 31 deletions modules/core/src/lib/attribute.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import typedArrayManager from '../utils/typed-array-manager';

const DEFAULT_STATE = {
isExternalBuffer: false,
lastExternalBuffer: null,
needsUpdate: true,
needsRedraw: false,
updateRanges: range.FULL
Expand Down Expand Up @@ -303,44 +304,46 @@ export default class Attribute extends BaseAttribute {

// Use external buffer
// Returns true if successful
setExternalBuffer(buffer, numInstances) {
setExternalBuffer(buffer) {
const state = this.userData;

if (buffer) {
state.isExternalBuffer = true;
this.clearNeedsUpdate();
if (!buffer) {
state.isExternalBuffer = false;
state.lastExternalBuffer = null;
return false;
}

if (buffer instanceof Buffer) {
if (this.externalBuffer !== buffer) {
this.update({constant: false, buffer});
state.needsRedraw = true;
}
} else if (this.value !== buffer) {
if (!ArrayBuffer.isView(buffer)) {
throw new Error('Attribute prop must be typed array');
}
if (state.auto && buffer.length <= numInstances * this.size) {
throw new Error('Attribute prop array must match length and size');
}
this.clearNeedsUpdate();

const ArrayType = glArrayFromType(this.type || GL.FLOAT);
if (buffer instanceof ArrayType) {
this.update({constant: false, value: buffer});
} else {
log.warn(`Attribute prop ${this.id} is casted to ${ArrayType.name}`)();
// Cast to proper type
this.update({constant: false, value: new ArrayType(buffer)});
}
// Save original typed array
this.value = buffer;
state.needsRedraw = true;
if (state.lastExternalBuffer === buffer) {
return false;
}
state.isExternalBuffer = true;
state.lastExternalBuffer = buffer;

let opts;
if (ArrayBuffer.isView(buffer)) {
opts = {constant: false, value: buffer};
} else if (buffer instanceof Buffer) {
opts = {constant: false, buffer};
} else {
opts = Object.assign({constant: false}, buffer);
}

if (opts.value) {
const ArrayType = glArrayFromType(this.type || GL.FLOAT);
if (!(opts.value instanceof ArrayType)) {
log.warn(`Attribute prop ${this.id} is casted to ${ArrayType.name}`)();
// Cast to proper type
opts.value = new ArrayType(opts.value);
}
this._updateShaderAttributes();
return true;
}

state.isExternalBuffer = false;
return false;
this.update(opts);
state.needsRedraw = true;

this._updateShaderAttributes();
return true;
}

// PRIVATE HELPER METHODS
Expand Down
24 changes: 24 additions & 0 deletions modules/layers/src/point-cloud-layer/point-cloud-layer.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,27 @@ const defaultProps = {
radiusPixels: {deprecatedFor: 'pointSize'}
};

// support loaders.gl point cloud format
function normalizeData(data) {
const {header, attributes} = data;
if (!header || !attributes) {
return;
}

data.length = header.vertexCount;

if (attributes.POSITION) {
attributes.instancePositions = attributes.POSITION;
attributes.instancePositions64xyLow = {constant: true, value: new Float32Array(2)};
}
if (attributes.NORMAL) {
attributes.instanceNormals = attributes.NORMAL;
}
if (attributes.COLOR_0) {
attributes.instanceColors = attributes.COLOR_0;
}
}

export default class PointCloudLayer extends Layer {
getShaders(id) {
return super.getShaders({vs, fs, modules: ['project32', 'gouraud-lighting', 'picking']});
Expand Down Expand Up @@ -88,6 +109,9 @@ export default class PointCloudLayer extends Layer {
this.setState({model: this._getModel(gl)});
this.getAttributeManager().invalidateAll();
}
if (changeFlags.dataChanged) {
normalizeData(props.data);
}
}

draw({uniforms}) {
Expand Down
82 changes: 73 additions & 9 deletions test/modules/core/lib/attribute.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,18 @@ test('Attribute#constructor', t => {
t.ok(attribute, 'Attribute construction successful');
t.is(typeof attribute.getBuffer, 'function', 'Attribute.getBuffer function available');
t.ok(attribute.allocate, 'Attribute.allocate function available');
t.ok(attribute.update, 'Attribute._updateBuffer function available');
t.ok(attribute.setExternalBuffer, 'Attribute._setExternalBuffer function available');
t.ok(attribute.update, 'Attribute.update function available');

t.end();
});

test('Attribute#delete', t => {
const attribute = new Attribute(gl, {size: 1, accessor: 'a', value: new Float32Array(4)});
t.ok(attribute.buffer, 'Attribute created Buffer object');

attribute.delete();
t.notOk(attribute.buffer, 'Attribute deleted Buffer object');

t.end();
});

Expand Down Expand Up @@ -129,6 +139,10 @@ test('Attribute#shaderAttributes', t => {
'Shader attribute buffer was updated'
);

buffer1.delete();
buffer2.delete();
attribute.delete();

t.end();
});

Expand Down Expand Up @@ -418,13 +432,63 @@ test('Attribute#updateBuffer - partial', t => {
);
}

ATTRIBUTE_1.delete();
ATTRIBUTE_2.delete();
t.end();
});

// t.ok(attribute.allocate(attributeName, allocCount), 'Attribute.allocate function available');
// t.ok(attribute._setExternalBuffer(attributeName, buffer, numInstances), 'Attribute._setExternalBuffer function available');
// t.ok(attribute._analyzeBuffer(attributeName, numInstances), 'Attribute._analyzeBuffer function available');
// t.ok(attribute._updateBuffer({attributeName, numInstances, data, props, context}), 'Attribute._updateBuffer function available');
// t.ok(attribute._updateBufferViaStandardAccessor(data, props), 'Attribute._updateBufferViaStandardAccessor function available');
// t.ok(attribute._validateAttributeDefinition(attributeName), 'Attribute._validateAttributeDefinition function available');
// t.ok(attribute._checkAttributeArray(attributeName, 'Attribute._checkAttributeArray function available');
test('Attribute#setExternalBuffer', t => {
const attribute = new Attribute(gl, {
id: 'test-attribute',
type: GL.FLOAT,
size: 3,
update: () => {}
});
const buffer = new Buffer(gl, 12);
const value1 = new Float32Array(4);
const value2 = new Uint8Array(4);

attribute.setNeedsUpdate();
t.notOk(
attribute.setExternalBuffer(null),
'should do nothing if setting external buffer to null'
);
t.ok(attribute.needsUpdate(), 'attribute still needs update');

t.ok(attribute.setExternalBuffer(buffer), 'should set external buffer to Buffer object');
t.is(attribute.getBuffer(), buffer, 'external buffer is set');
t.notOk(attribute.needsUpdate(), 'attribute is updated');

t.notOk(
attribute.setExternalBuffer(buffer),
'should do nothing if setting external buffer to the same object'
);

t.ok(attribute.setExternalBuffer(value1), 'should set external buffer to typed array');
t.is(attribute.value, value1, 'external value is set');

t.ok(attribute.setExternalBuffer(value2), 'should set external buffer to typed array');
t.is(attribute.value.constructor.name, 'Float32Array', 'external value is cast to correct type');

t.notOk(
attribute.setExternalBuffer(value2),
'should do nothing if setting external buffer to the same object'
);

t.ok(
attribute.setExternalBuffer({
offset: 4,
stride: 8,
value: value1
}),
'should set external buffer to attribute descriptor'
);
t.is(attribute.offset, 4, 'attribute accessor is updated');
t.is(attribute.stride, 8, 'attribute accessor is updated');
t.is(attribute.value, value1, 'external value is set');

buffer.delete();
attribute.delete();

t.end();
});
1 change: 1 addition & 0 deletions test/modules/layers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import './core-layers.spec';
import './polygon-layer.spec';
import './geojson.spec';
import './geojson-layer.spec';
import './point-cloud-layer.spec';
import './simple-mesh-layer.spec';
import './scenegraph-layer.spec';
import './path-layer/path-layer-vertex.spec';
Expand Down
Loading

0 comments on commit 6aeb983

Please sign in to comment.