Laying groundwork for polyline support in workers

feature/threejs-update
Robin Hawkes 2016-09-08 16:32:26 +01:00
rodzic ff8035ad5c
commit cce8475911
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 1EC4C2D6765FA8CF
4 zmienionych plików z 382 dodań i 243 usunięć

Wyświetl plik

@ -178,6 +178,12 @@ class GeoJSONLayer extends LayerGroup {
var polygonFlat = true;
var polylineAttributes = [];
var polylineAttributeLengths = {
positions: 3,
colors: 3
};
var polylineFlat = true;
var pointAttributes = [];
this._layers.forEach(layer => {
@ -193,6 +199,14 @@ class GeoJSONLayer extends LayerGroup {
}
} else if (layer instanceof PolylineLayer) {
polylineAttributes.push(layer.getBufferAttributes());
if (polylineFlat && !layer.isFlat()) {
polylineFlat = false;
}
if (this._options.interactive) {
polylineAttributeLengths.pickingIds = 1;
}
} else if (layer instanceof PointLayer) {
pointAttributes.push(layer.getBufferAttributes());
}
@ -212,8 +226,14 @@ class GeoJSONLayer extends LayerGroup {
if (polylineAttributes.length > 0) {
var mergedPolylineAttributes = Buffer.mergeAttributes(polylineAttributes);
this._setPolylineMesh(mergedPolylineAttributes);
this.add(this._polylineMesh);
this._setPolylineMesh(mergedPolylineAttributes, polylineAttributeLengths, polylineFlat).then((result) => {
this._polylineMesh = result.mesh;
this.add(this._polylineMesh);
if (result.pickingMesh) {
this._pickingMesh.add(pickingMesh);
}
});
}
if (pointAttributes.length > 0) {
@ -247,72 +267,78 @@ class GeoJSONLayer extends LayerGroup {
return PolygonLayer.SetMesh(attributes, attributeLengths, flat, style, this._options, this._world._environment._skybox);
}
_setPolylineMesh(attributes) {
var geometry = new THREE.BufferGeometry();
// itemSize = 3 because there are 3 values (components) per vertex
geometry.addAttribute('position', new THREE.BufferAttribute(attributes.vertices, 3));
if (attributes.normals) {
geometry.addAttribute('normal', new THREE.BufferAttribute(attributes.normals, 3));
}
geometry.addAttribute('color', new THREE.BufferAttribute(attributes.colours, 3));
if (attributes.pickingIds) {
geometry.addAttribute('pickingId', new THREE.BufferAttribute(attributes.pickingIds, 1));
}
geometry.computeBoundingBox();
_setPolylineMesh(attributes, attributeLengths, flat) {
// TODO: Make this work when style is a function per feature
var style = (typeof this._options.style === 'function') ? this._options.style(this._geojson.features[0]) : this._options.style;
style = extend({}, GeoJSON.defaultStyle, style);
var material;
if (this._options.polylineMaterial && this._options.polylineMaterial instanceof THREE.Material) {
material = this._options.polylineMaterial;
} else {
material = new THREE.LineBasicMaterial({
vertexColors: THREE.VertexColors,
linewidth: style.lineWidth,
transparent: style.lineTransparent,
opacity: style.lineOpacity,
blending: style.lineBlending
});
}
return PolylineLayer.SetMesh(attributes, attributeLengths, flat, style, this._options);
var mesh;
// var geometry = new THREE.BufferGeometry();
// Pass mesh through callback, if defined
if (typeof this._options.onPolylineMesh === 'function') {
mesh = this._options.onPolylineMesh(geometry, material);
} else {
mesh = new THREE.LineSegments(geometry, material);
// // itemSize = 3 because there are 3 values (components) per vertex
// geometry.addAttribute('position', new THREE.BufferAttribute(attributes.vertices, 3));
if (style.lineRenderOrder !== undefined) {
material.depthWrite = false;
mesh.renderOrder = style.lineRenderOrder;
}
// if (attributes.normals) {
// geometry.addAttribute('normal', new THREE.BufferAttribute(attributes.normals, 3));
// }
mesh.castShadow = true;
// mesh.receiveShadow = true;
}
// geometry.addAttribute('color', new THREE.BufferAttribute(attributes.colours, 3));
// TODO: Allow this to be overridden, or copy mesh instead of creating a new
// one just for picking
if (this._options.interactive && this._pickingMesh) {
material = new PickingMaterial();
// material.side = THREE.BackSide;
// if (attributes.pickingIds) {
// geometry.addAttribute('pickingId', new THREE.BufferAttribute(attributes.pickingIds, 1));
// }
// Make the line wider / easier to pick
material.linewidth = style.lineWidth + material.linePadding;
// geometry.computeBoundingBox();
var pickingMesh = new THREE.LineSegments(geometry, material);
this._pickingMesh.add(pickingMesh);
}
// // TODO: Make this work when style is a function per feature
// var style = (typeof this._options.style === 'function') ? this._options.style(this._geojson.features[0]) : this._options.style;
// style = extend({}, GeoJSON.defaultStyle, style);
this._polylineMesh = mesh;
// var material;
// if (this._options.polylineMaterial && this._options.polylineMaterial instanceof THREE.Material) {
// material = this._options.polylineMaterial;
// } else {
// material = new THREE.LineBasicMaterial({
// vertexColors: THREE.VertexColors,
// linewidth: style.lineWidth,
// transparent: style.lineTransparent,
// opacity: style.lineOpacity,
// blending: style.lineBlending
// });
// }
// var mesh;
// // Pass mesh through callback, if defined
// if (typeof this._options.onPolylineMesh === 'function') {
// mesh = this._options.onPolylineMesh(geometry, material);
// } else {
// mesh = new THREE.LineSegments(geometry, material);
// if (style.lineRenderOrder !== undefined) {
// material.depthWrite = false;
// mesh.renderOrder = style.lineRenderOrder;
// }
// mesh.castShadow = true;
// // mesh.receiveShadow = true;
// }
// // TODO: Allow this to be overridden, or copy mesh instead of creating a new
// // one just for picking
// if (this._options.interactive && this._pickingMesh) {
// material = new PickingMaterial();
// // material.side = THREE.BackSide;
// // Make the line wider / easier to pick
// material.linewidth = style.lineWidth + material.linePadding;
// var pickingMesh = new THREE.LineSegments(geometry, material);
// this._pickingMesh.add(pickingMesh);
// }
// this._polylineMesh = mesh;
}
_setPointMesh(attributes) {

Wyświetl plik

@ -6,6 +6,7 @@ import Worker from '../util/Worker';
import Buffer from '../util/Buffer';
import Stringify from '../util/Stringify';
import PolygonLayer from './geometry/PolygonLayer';
import PolylineLayer from './geometry/PolylineLayer';
import {latLon as LatLon} from '../geo/LatLon';
import {point as Point} from '../geo/Point';
import Geo from '../geo/Geo';
@ -228,6 +229,7 @@ class GeoJSONWorkerLayer extends Layer {
var pointScale;
var polygons = [];
var polylines = [];
// Deserialise style function if provided
if (typeof _style === 'string') {
@ -295,105 +297,243 @@ class GeoJSONWorkerLayer extends Layer {
polygons.push(polygon);
}
if (geometry.type === 'LineString' || geometry.type === 'MultiLineString') {}
if (geometry.type === 'LineString' || geometry.type === 'MultiLineString') {
coordinates = (PolylineLayer.isSingle(coordinates)) ? [coordinates] : coordinates;
var converted = coordinates.map(_coordinates => {
return _coordinates.map(coordinate => {
return LatLon(coordinate[1], coordinate[0]);
});
});
var point;
var projected = converted.map((_coordinates) => {
return _coordinates.map((latlon) => {
point = Geo.latLonToPoint(latlon)._subtract(originPoint);
if (!pointScale) {
pointScale = Geo.pointScale(latlon);
}
return point;
});
});
var polyline = {
projected: projected,
options: {
pointScale: pointScale,
style: style
}
};
if (_properties) {
polyline.properties = feature.properties;
}
polylines.push(polyline);
}
if (geometry.type === 'Point' || geometry.type === 'MultiPoint') {}
};
var bufferPromises = [];
var polygonBufferPromises = [];
var polylineBufferPromises = [];
var polygon;
for (var i = 0; i < polygons.length; i++) {
polygon = polygons[i];
bufferPromises.push(PolygonLayer.SetBufferAttributes(polygon.projected, polygon.options));
polygonBufferPromises.push(PolygonLayer.SetBufferAttributes(polygon.projected, polygon.options));
};
Promise.all(bufferPromises).then((results) => {
var transferrables = [];
var transferrablesSize = 0;
var polyline;
for (var i = 0; i < polylines.length; i++) {
polyline = polylines[i];
polylineBufferPromises.push(PolylineLayer.SetBufferAttributes(polyline.projected, polyline.options));
};
var positions = [];
var normals = [];
var colors = [];
// var pickingIds = [];
var properties = [];
var flats = [];
var polygon;
var result;
for (var i = 0; i < results.length; i++) {
result = results[i];
polygon = polygons[i];
// WORKERS: Making this a typed array will speed up transfer time
// As things stand this adds on a few milliseconds
flats.push(result.flat);
// WORKERS: result.attributes is actually an array of polygons for each
// feature, though the current logic isn't keeping these all together
var attributes;
for (var j = 0; j < result.attributes.length; j++) {
attributes = result.attributes[j];
positions.push(attributes.positions);
normals.push(attributes.normals);
colors.push(attributes.colors);
if (_properties) {
properties.push(Buffer.stringToUint8Array(JSON.stringify(polygon.properties)));
}
};
};
var mergedAttributes = {
positions: Buffer.mergeFloat32Arrays(positions),
normals: Buffer.mergeFloat32Arrays(normals),
colors: Buffer.mergeFloat32Arrays(colors)
};
transferrables.push(mergedAttributes.positions[0].buffer);
transferrables.push(mergedAttributes.positions[1].buffer);
transferrables.push(mergedAttributes.normals[0].buffer);
transferrables.push(mergedAttributes.normals[1].buffer);
transferrables.push(mergedAttributes.colors[0].buffer);
transferrables.push(mergedAttributes.colors[1].buffer);
var mergedProperties;
if (_properties) {
mergedProperties = Buffer.mergeUint8Arrays(properties);
transferrables.push(mergedProperties[0].buffer);
transferrables.push(mergedProperties[1].buffer);
}
var output = {
attributes: mergedAttributes,
flats: flats
};
if (_properties) {
output.properties = mergedProperties;
}
// TODO: Also return GeoJSON features that can be mapped to objects on
// the main thread. Allow user to provide filter / toggles to only return
// properties from the GeoJSON that they need (eg. don't return geometry,
// or don't return properties.height)
// TODO: Make this work with polylines too
GeoJSONWorkerLayer.ProcessPolygons(polygonBufferPromises, polygons, _properties).then((result) => {
resolve({
data: output,
transferrables: transferrables
data: result.data,
transferrables: result.transferrables
});
});
// GeoJSONWorkerLayer.ProcessPolylines(polylineBufferPromises, polylines, _properties).then((result) => {
// resolve({
// data: result.data,
// transferrables: result.transferrables
// });
// });
});
});
}
static ProcessPolygons(polygonPromises, polygons, _properties) {
return new Promise((resolve, reject) => {
Promise.all(polygonPromises).then((results) => {
var transferrables = [];
var positions = [];
var normals = [];
var colors = [];
var properties = [];
var flats = [];
var polygon;
var result;
for (var i = 0; i < results.length; i++) {
result = results[i];
polygon = polygons[i];
// WORKERS: Making this a typed array will speed up transfer time
// As things stand this adds on a few milliseconds
flats.push(result.flat);
// WORKERS: result.attributes is actually an array of polygons for each
// feature, though the current logic isn't keeping these all together
var attributes;
for (var j = 0; j < result.attributes.length; j++) {
attributes = result.attributes[j];
positions.push(attributes.positions);
normals.push(attributes.normals);
colors.push(attributes.colors);
if (_properties) {
properties.push(Buffer.stringToUint8Array(JSON.stringify(polygon.properties)));
}
};
};
var mergedAttributes = {
positions: Buffer.mergeFloat32Arrays(positions),
normals: Buffer.mergeFloat32Arrays(normals),
colors: Buffer.mergeFloat32Arrays(colors)
};
transferrables.push(mergedAttributes.positions[0].buffer);
transferrables.push(mergedAttributes.positions[1].buffer);
transferrables.push(mergedAttributes.normals[0].buffer);
transferrables.push(mergedAttributes.normals[1].buffer);
transferrables.push(mergedAttributes.colors[0].buffer);
transferrables.push(mergedAttributes.colors[1].buffer);
var mergedProperties;
if (_properties) {
mergedProperties = Buffer.mergeUint8Arrays(properties);
transferrables.push(mergedProperties[0].buffer);
transferrables.push(mergedProperties[1].buffer);
}
var output = {
attributes: mergedAttributes,
flats: flats
};
if (_properties) {
output.properties = mergedProperties;
}
// TODO: Also return GeoJSON features that can be mapped to objects on
// the main thread. Allow user to provide filter / toggles to only return
// properties from the GeoJSON that they need (eg. don't return geometry,
// or don't return properties.height)
resolve({
data: output,
transferrables: transferrables
});
}).catch(reject);
});
}
static ProcessPolylines(polylinePromises, polylines, _properties) {
return new Promise((resolve, reject) => {
Promise.all(polylinePromises).then((results) => {
var transferrables = [];
var positions = [];
var colors = [];
var properties = [];
var flats = [];
var polyline;
var result;
for (var i = 0; i < results.length; i++) {
result = results[i];
polyline = polylines[i];
// WORKERS: Making this a typed array will speed up transfer time
// As things stand this adds on a few milliseconds
flats.push(result.flat);
// WORKERS: result.attributes is actually an array of polygons for each
// feature, though the current logic isn't keeping these all together
var attributes;
for (var j = 0; j < result.attributes.length; j++) {
attributes = result.attributes[j];
positions.push(attributes.positions);
colors.push(attributes.colors);
if (_properties) {
properties.push(Buffer.stringToUint8Array(JSON.stringify(polyline.properties)));
}
};
};
var mergedAttributes = {
positions: Buffer.mergeFloat32Arrays(positions),
colors: Buffer.mergeFloat32Arrays(colors)
};
transferrables.push(mergedAttributes.positions[0].buffer);
transferrables.push(mergedAttributes.positions[1].buffer);
transferrables.push(mergedAttributes.colors[0].buffer);
transferrables.push(mergedAttributes.colors[1].buffer);
var mergedProperties;
if (_properties) {
mergedProperties = Buffer.mergeUint8Arrays(properties);
transferrables.push(mergedProperties[0].buffer);
transferrables.push(mergedProperties[1].buffer);
}
var output = {
attributes: mergedAttributes,
flats: flats
};
if (_properties) {
output.properties = mergedProperties;
}
// TODO: Also return GeoJSON features that can be mapped to objects on
// the main thread. Allow user to provide filter / toggles to only return
// properties from the GeoJSON that they need (eg. don't return geometry,
// or don't return properties.height)
resolve({
data: output,
transferrables: transferrables
});
}).catch(reject);
});
}
static ProcessGeoJSON(geojson, headers) {
if (typeof geojson === 'string') {
return GeoJSONWorkerLayer.RequestGeoJSON(geojson, headers);

Wyświetl plik

@ -33,8 +33,8 @@ class PolygonLayer extends Layer {
// Custom material override
//
// TODO: Should this be in the style object?
material: null,
onMesh: null,
polygonMaterial: null,
onPolygonMesh: null,
onBufferAttributes: null,
// This default style is separate to Util.GeoJSON.defaultStyle
style: {

Wyświetl plik

@ -32,8 +32,8 @@ class PolylineLayer extends Layer {
// Custom material override
//
// TODO: Should this be in the style object?
material: null,
onMesh: null,
polylineMaterial: null,
onPolylineMesh: null,
onBufferAttributes: null,
// This default style is separate to Util.GeoJSON.defaultStyle
style: {
@ -59,34 +59,57 @@ class PolylineLayer extends Layer {
}
_onAdd(world) {
this._setCoordinates();
return new Promise((resolve, reject) => {
this._setCoordinates();
if (this._options.interactive) {
// Only add to picking mesh if this layer is controlling output
//
// Otherwise, assume another component will eventually add a mesh to
// the picking scene
if (this.isOutput()) {
this._pickingMesh = new THREE.Object3D();
this.addToPicking(this._pickingMesh);
if (this._options.interactive) {
// Only add to picking mesh if this layer is controlling output
//
// Otherwise, assume another component will eventually add a mesh to
// the picking scene
if (this.isOutput()) {
this._pickingMesh = new THREE.Object3D();
this.addToPicking(this._pickingMesh);
}
this._setPickingId();
this._addPickingEvents();
}
this._setPickingId();
this._addPickingEvents();
}
// Store geometry representation as instances of THREE.BufferAttribute
PolylineLayer.SetBufferAttributes(this._projectedCoordinates, this._options).then((result) => {
this._bufferAttributes = Buffer.mergeAttributes(result.attributes);
this._flat = result.flat;
// Store geometry representation as instances of THREE.BufferAttribute
this._setBufferAttributes();
var attributeLengths = {
positions: 3,
colors: 3
};
if (this.isOutput()) {
// Set mesh if not merging elsewhere
this._setMesh(this._bufferAttributes);
if (this._options.interactive) {
attributeLengths.pickingIds = 1;
}
// Output mesh
this.add(this._mesh);
}
if (this.isOutput()) {
var style = this._options.style;
return Promise.resolve(this);
// Set mesh if not merging elsewhere
PolylineLayer.SetMesh(this._bufferAttributes, attributeLengths, this._flat, style, this._options).then((result) => {
// Output mesh
this.add(result.mesh);
if (result.pickingMesh) {
this._pickingMesh.add(pickingMesh);
}
});
}
result.attributes = null;
result = null;
resolve(this);
});
});
}
// Return center of polyline as a LatLon
@ -118,28 +141,22 @@ class PolylineLayer extends Layer {
});
}
// Create and store reference to THREE.BufferAttribute data for this layer
_setBufferAttributes() {
var attributes;
// Only use this if you know what you're doing
if (typeof this._options.onBufferAttributes === 'function') {
// TODO: Probably want to pass something less general as arguments,
// though passing the instance will do for now (it's everything)
attributes = this._options.onBufferAttributes(this);
} else {
static SetBufferAttributes(coordinates, options) {
return new Promise((resolve) => {
var height = 0;
// Convert height into world units
if (this._options.style.lineHeight) {
height = this._world.metresToWorld(this._options.style.lineHeight, this._pointScale);
if (options.style.lineHeight) {
height = Geo.world.metresToWorld(options.style.lineHeight, options.pointScale);
}
var colour = new THREE.Color();
colour.set(this._options.style.lineColor);
colour.set(options.style.lineColor);
var flat = true;
// For each line
attributes = this._projectedCoordinates.map(_projectedCoordinates => {
var attributes = coordinates.map(_projectedCoordinates => {
var _vertices = [];
var _colours = [];
@ -164,20 +181,20 @@ class PolylineLayer extends Layer {
verticesCount: _vertices.length
};
if (this._options.interactive && this._pickingId) {
if (options.interactive && options.pickingId) {
// Inject picking ID
line.pickingId = this._pickingId;
line.pickingId = options.pickingId;
}
// Convert line representation to proper attribute arrays
return this._toAttributes(line);
return PolylineLayer.ToAttributes(line);
});
}
this._bufferAttributes = Buffer.mergeAttributes(attributes);
// Original attributes are no longer required so free the memory
attributes = null;
resolve({
attributes: attributes,
flat: flat
});
});
}
getBufferAttributes() {
@ -203,32 +220,18 @@ class PolylineLayer extends Layer {
this._projectedCoordinates = null;
}
// Create and store mesh from buffer attributes
//
// This is only called if the layer is controlling its own output
_setMesh(attributes) {
static SetMesh(attributes, attributeLengths, flat, style, options) {
var geometry = new THREE.BufferGeometry();
// itemSize = 3 because there are 3 values (components) per vertex
geometry.addAttribute('position', new THREE.BufferAttribute(attributes.vertices, 3));
if (attributes.normals) {
geometry.addAttribute('normal', new THREE.BufferAttribute(attributes.normals, 3));
}
geometry.addAttribute('color', new THREE.BufferAttribute(attributes.colours, 3));
if (attributes.pickingIds) {
geometry.addAttribute('pickingId', new THREE.BufferAttribute(attributes.pickingIds, 1));
for (var key in attributes) {
geometry.addAttribute(key.slice(0, -1), new THREE.BufferAttribute(attributes[key], attributeLengths[key]));
}
geometry.computeBoundingBox();
var style = this._options.style;
var material;
if (this._options.material && this._options.material instanceof THREE.Material) {
material = this._options.material;
if (options.polylineMaterial && options.polylineMaterial instanceof THREE.Material) {
material = options.polylineMaterial;
} else {
material = new THREE.LineBasicMaterial({
vertexColors: THREE.VertexColors,
@ -242,8 +245,8 @@ class PolylineLayer extends Layer {
var mesh;
// Pass mesh through callback, if defined
if (typeof this._options.onMesh === 'function') {
mesh = this._options.onMesh(geometry, material);
if (typeof options.onPolylineMesh === 'function') {
mesh = options.onPolylineMesh(geometry, material);
} else {
mesh = new THREE.LineSegments(geometry, material);
@ -256,9 +259,7 @@ class PolylineLayer extends Layer {
// mesh.receiveShadow = true;
}
// TODO: Allow this to be overridden, or copy mesh instead of creating a new
// one just for picking
if (this._options.interactive && this._pickingMesh) {
if (options.interactive) {
material = new PickingMaterial();
// material.side = THREE.BackSide;
@ -266,10 +267,12 @@ class PolylineLayer extends Layer {
material.linewidth = style.lineWidth + material.linePadding;
var pickingMesh = new THREE.LineSegments(geometry, material);
this._pickingMesh.add(pickingMesh);
}
this._mesh = mesh;
return Promise.resolve({
mesh: mesh,
pickingMesh: pickingMesh
});
}
// Convert and project coordinates
@ -323,11 +326,7 @@ class PolylineLayer extends Layer {
});
}
// Transform line representation into attribute arrays that can be used by
// THREE.BufferGeometry
//
// TODO: Can this be simplified? It's messy and huge
_toAttributes(line) {
static ToAttributes(line) {
// Three components per vertex
var vertices = new Float32Array(line.verticesCount * 3);
var colours = new Float32Array(line.verticesCount * 3);
@ -341,13 +340,6 @@ class PolylineLayer extends Layer {
var _vertices = line.vertices;
var _colour = line.colours;
var normals;
var _normals;
if (line.normals) {
normals = new Float32Array(line.verticesCount * 3);
_normals = line.normals;
}
var _pickingId;
if (pickingIds) {
_pickingId = line.pickingId;
@ -360,27 +352,12 @@ class PolylineLayer extends Layer {
var ay = _vertices[i][1];
var az = _vertices[i][2];
var nx;
var ny;
var nz;
if (_normals) {
nx = _normals[i][0];
ny = _normals[i][1];
nz = _normals[i][2];
}
var c1 = _colour[i];
vertices[lastIndex * 3 + 0] = ax;
vertices[lastIndex * 3 + 1] = ay;
vertices[lastIndex * 3 + 2] = az;
if (normals) {
normals[lastIndex * 3 + 0] = nx;
normals[lastIndex * 3 + 1] = ny;
normals[lastIndex * 3 + 2] = nz;
}
colours[lastIndex * 3 + 0] = c1[0];
colours[lastIndex * 3 + 1] = c1[1];
colours[lastIndex * 3 + 2] = c1[2];
@ -393,14 +370,10 @@ class PolylineLayer extends Layer {
}
var attributes = {
vertices: vertices,
colours: colours
positions: vertices,
colors: colours
};
if (normals) {
attributes.normals = normals;
}
if (pickingIds) {
attributes.pickingIds = pickingIds;
}