kopia lustrzana https://github.com/robhawkes/vizicities
Deduped mesh creation using static methods
rodzic
8a9da23566
commit
86fc1f6d5d
|
@ -98,197 +98,153 @@ class GeoJSONLayer extends LayerGroup {
|
|||
// Need to be careful as to not make it impossible to fork this off into a
|
||||
// worker script at a later stage
|
||||
_processData(data) {
|
||||
// Collects features into a single FeatureCollection
|
||||
//
|
||||
// Also converts TopoJSON to GeoJSON if instructed
|
||||
this._geojson = GeoJSON.collectFeatures(data, this._options.topojson);
|
||||
return new Promise((resolve) => {
|
||||
// Collects features into a single FeatureCollection
|
||||
//
|
||||
// Also converts TopoJSON to GeoJSON if instructed
|
||||
this._geojson = GeoJSON.collectFeatures(data, this._options.topojson);
|
||||
|
||||
// TODO: Check that GeoJSON is valid / usable
|
||||
// TODO: Check that GeoJSON is valid / usable
|
||||
|
||||
var features = this._geojson.features;
|
||||
var features = this._geojson.features;
|
||||
|
||||
// Run filter, if provided
|
||||
if (this._options.filter) {
|
||||
features = this._geojson.features.filter(this._options.filter);
|
||||
}
|
||||
|
||||
var defaults = {};
|
||||
|
||||
// Assume that a style won't be set per feature
|
||||
var style = this._options.style;
|
||||
|
||||
var options;
|
||||
features.forEach(feature => {
|
||||
// Get per-feature style object, if provided
|
||||
if (typeof this._options.style === 'function') {
|
||||
style = extend({}, GeoJSON.defaultStyle, this._options.style(feature));
|
||||
// Run filter, if provided
|
||||
if (this._options.filter) {
|
||||
features = this._geojson.features.filter(this._options.filter);
|
||||
}
|
||||
|
||||
options = extend({}, defaults, {
|
||||
// If merging feature layers, stop them outputting themselves
|
||||
// If not, let feature layers output themselves to the world
|
||||
output: !this.isOutput(),
|
||||
interactive: this._options.interactive,
|
||||
style: style
|
||||
var defaults = {};
|
||||
|
||||
// Assume that a style won't be set per feature
|
||||
var style = this._options.style;
|
||||
|
||||
var layerPromises = [];
|
||||
|
||||
var options;
|
||||
features.forEach(feature => {
|
||||
// Get per-feature style object, if provided
|
||||
if (typeof this._options.style === 'function') {
|
||||
style = extend({}, GeoJSON.defaultStyle, this._options.style(feature));
|
||||
}
|
||||
|
||||
options = extend({}, defaults, {
|
||||
// If merging feature layers, stop them outputting themselves
|
||||
// If not, let feature layers output themselves to the world
|
||||
output: !this.isOutput(),
|
||||
interactive: this._options.interactive,
|
||||
style: style
|
||||
});
|
||||
|
||||
var layer = this._featureToLayer(feature, options);
|
||||
|
||||
if (!layer) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Sometimes you don't want to store a reference to the feature
|
||||
//
|
||||
// For example, to save memory when being used by tile layers
|
||||
if (this._options.keepFeatures) {
|
||||
layer.feature = feature;
|
||||
}
|
||||
|
||||
// If defined, call a function for each feature
|
||||
//
|
||||
// This is commonly used for adding event listeners from the user script
|
||||
if (this._options.onEachFeature) {
|
||||
this._options.onEachFeature(feature, layer);
|
||||
}
|
||||
|
||||
// TODO: Make this a promise array and only continue on completion
|
||||
layerPromises.push(this.addLayer(layer));
|
||||
});
|
||||
|
||||
var layer = this._featureToLayer(feature, options);
|
||||
|
||||
if (!layer) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Sometimes you don't want to store a reference to the feature
|
||||
//
|
||||
// For example, to save memory when being used by tile layers
|
||||
if (this._options.keepFeatures) {
|
||||
layer.feature = feature;
|
||||
}
|
||||
|
||||
// If defined, call a function for each feature
|
||||
//
|
||||
// This is commonly used for adding event listeners from the user script
|
||||
if (this._options.onEachFeature) {
|
||||
this._options.onEachFeature(feature, layer);
|
||||
}
|
||||
|
||||
// TODO: Make this a promise array and only continue on completion
|
||||
this.addLayer(layer);
|
||||
});
|
||||
|
||||
// If merging layers do that now, otherwise skip as the geometry layers
|
||||
// should have already outputted themselves
|
||||
if (!this.isOutput()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// From here on we can assume that we want to merge the layers
|
||||
|
||||
var polygonAttributes = [];
|
||||
var polygonFlat = true;
|
||||
|
||||
var polylineAttributes = [];
|
||||
var pointAttributes = [];
|
||||
|
||||
this._layers.forEach(layer => {
|
||||
if (layer instanceof PolygonLayer) {
|
||||
polygonAttributes.push(layer.getBufferAttributes());
|
||||
|
||||
if (polygonFlat && !layer.isFlat()) {
|
||||
polygonFlat = false;
|
||||
Promise.all(layerPromises).then((results) => {
|
||||
// If merging layers do that now, otherwise skip as the geometry layers
|
||||
// should have already outputted themselves
|
||||
if (!this.isOutput()) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
} else if (layer instanceof PolylineLayer) {
|
||||
polylineAttributes.push(layer.getBufferAttributes());
|
||||
} else if (layer instanceof PointLayer) {
|
||||
pointAttributes.push(layer.getBufferAttributes());
|
||||
}
|
||||
|
||||
// From here on we can assume that we want to merge the layers
|
||||
|
||||
var polygonAttributes = [];
|
||||
var polygonAttributeLengths = {
|
||||
positions: 3,
|
||||
normals: 3,
|
||||
colors: 3
|
||||
};
|
||||
var polygonFlat = true;
|
||||
|
||||
var polylineAttributes = [];
|
||||
var pointAttributes = [];
|
||||
|
||||
this._layers.forEach(layer => {
|
||||
if (layer instanceof PolygonLayer) {
|
||||
polygonAttributes.push(layer.getBufferAttributes());
|
||||
|
||||
if (polygonFlat && !layer.isFlat()) {
|
||||
polygonFlat = false;
|
||||
}
|
||||
|
||||
if (this._options.interactive) {
|
||||
polygonAttributeLengths.pickingIds = 1;
|
||||
}
|
||||
} else if (layer instanceof PolylineLayer) {
|
||||
polylineAttributes.push(layer.getBufferAttributes());
|
||||
} else if (layer instanceof PointLayer) {
|
||||
pointAttributes.push(layer.getBufferAttributes());
|
||||
}
|
||||
});
|
||||
|
||||
if (polygonAttributes.length > 0) {
|
||||
var mergedPolygonAttributes = Buffer.mergeAttributes(polygonAttributes);
|
||||
this._setPolygonMesh(mergedPolygonAttributes, polygonAttributeLengths, polygonFlat).then((result) => {
|
||||
this._polygonMesh = result.mesh;
|
||||
this.add(this._polygonMesh);
|
||||
|
||||
if (result.pickingMesh) {
|
||||
this._pickingMesh.add(pickingMesh);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (polylineAttributes.length > 0) {
|
||||
var mergedPolylineAttributes = Buffer.mergeAttributes(polylineAttributes);
|
||||
this._setPolylineMesh(mergedPolylineAttributes);
|
||||
this.add(this._polylineMesh);
|
||||
}
|
||||
|
||||
if (pointAttributes.length > 0) {
|
||||
var mergedPointAttributes = Buffer.mergeAttributes(pointAttributes);
|
||||
this._setPointMesh(mergedPointAttributes);
|
||||
this.add(this._pointMesh);
|
||||
}
|
||||
|
||||
// Clean up layers
|
||||
//
|
||||
// TODO: Are there ever situations where the unmerged buffer attributes
|
||||
// and coordinates would still be required?
|
||||
this._layers.forEach(layer => {
|
||||
layer.clearBufferAttributes();
|
||||
layer.clearCoordinates();
|
||||
});
|
||||
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
if (polygonAttributes.length > 0) {
|
||||
var mergedPolygonAttributes = Buffer.mergeAttributes(polygonAttributes);
|
||||
this._setPolygonMesh(mergedPolygonAttributes, polygonFlat);
|
||||
this.add(this._polygonMesh);
|
||||
}
|
||||
|
||||
if (polylineAttributes.length > 0) {
|
||||
var mergedPolylineAttributes = Buffer.mergeAttributes(polylineAttributes);
|
||||
this._setPolylineMesh(mergedPolylineAttributes);
|
||||
this.add(this._polylineMesh);
|
||||
}
|
||||
|
||||
if (pointAttributes.length > 0) {
|
||||
var mergedPointAttributes = Buffer.mergeAttributes(pointAttributes);
|
||||
this._setPointMesh(mergedPointAttributes);
|
||||
this.add(this._pointMesh);
|
||||
}
|
||||
|
||||
// Clean up layers
|
||||
//
|
||||
// TODO: Are there ever situations where the unmerged buffer attributes
|
||||
// and coordinates would still be required?
|
||||
this._layers.forEach(layer => {
|
||||
layer.clearBufferAttributes();
|
||||
layer.clearCoordinates();
|
||||
});
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
// Create and store mesh from buffer attributes
|
||||
//
|
||||
// TODO: De-dupe this from the individual mesh creation logic within each
|
||||
// geometry layer (materials, settings, etc)
|
||||
//
|
||||
// Could make this an abstract method for each geometry layer
|
||||
_setPolygonMesh(attributes, flat) {
|
||||
var geometry = new THREE.BufferGeometry();
|
||||
|
||||
// itemSize = 3 because there are 3 values (components) per vertex
|
||||
geometry.addAttribute('position', new THREE.BufferAttribute(attributes.vertices, 3));
|
||||
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();
|
||||
|
||||
// TODO: Probably remove this and call static method directly as it's just a proxy
|
||||
_setPolygonMesh(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.polygonMaterial && this._options.polygonMaterial instanceof THREE.Material) {
|
||||
material = this._options.polygonMaterial;
|
||||
} else if (!this._world._environment._skybox) {
|
||||
material = new THREE.MeshPhongMaterial({
|
||||
vertexColors: THREE.VertexColors,
|
||||
side: THREE.BackSide,
|
||||
transparent: style.transparent,
|
||||
opacity: style.opacity,
|
||||
blending: style.blending
|
||||
});
|
||||
} else {
|
||||
material = new THREE.MeshStandardMaterial({
|
||||
vertexColors: THREE.VertexColors,
|
||||
side: THREE.BackSide,
|
||||
transparent: style.transparent,
|
||||
opacity: style.opacity,
|
||||
blending: style.blending
|
||||
});
|
||||
material.roughness = 1;
|
||||
material.metalness = 0.1;
|
||||
material.envMapIntensity = 3;
|
||||
material.envMap = this._world._environment._skybox.getRenderTarget();
|
||||
}
|
||||
|
||||
var mesh;
|
||||
|
||||
// Pass mesh through callback, if defined
|
||||
if (typeof this._options.onPolygonMesh === 'function') {
|
||||
mesh = this._options.onPolygonMesh(geometry, material);
|
||||
} else {
|
||||
mesh = new THREE.Mesh(geometry, material);
|
||||
|
||||
mesh.castShadow = true;
|
||||
mesh.receiveShadow = true;
|
||||
}
|
||||
|
||||
if (flat) {
|
||||
material.depthWrite = false;
|
||||
mesh.renderOrder = 1;
|
||||
}
|
||||
|
||||
if (this._options.interactive && this._pickingMesh) {
|
||||
material = new PickingMaterial();
|
||||
material.side = THREE.BackSide;
|
||||
|
||||
var pickingMesh = new THREE.Mesh(geometry, material);
|
||||
this._pickingMesh.add(pickingMesh);
|
||||
}
|
||||
|
||||
this._polygonMesh = mesh;
|
||||
return PolygonLayer.SetMesh(attributes, attributeLengths, flat, style, this._options, this._world._environment._skybox);
|
||||
}
|
||||
|
||||
_setPolylineMesh(attributes) {
|
||||
|
|
|
@ -97,20 +97,15 @@ class GeoJSONWorkerLayer extends Layer {
|
|||
Worker.exec('GeoJSONWorkerLayer.Process', [geojson, topojson, headers, originPoint, style, interactive], transferrables).then((results) => {
|
||||
console.timeEnd('Worker round trip');
|
||||
|
||||
var splitVertices = Buffer.splitFloat32Array(results.attributes.vertices);
|
||||
var splitPositions = Buffer.splitFloat32Array(results.attributes.positions);
|
||||
var splitNormals = Buffer.splitFloat32Array(results.attributes.normals);
|
||||
var splitColours = Buffer.splitFloat32Array(results.attributes.colours);
|
||||
var splitColors = Buffer.splitFloat32Array(results.attributes.colors);
|
||||
|
||||
var splitProperties;
|
||||
if (results.properties) {
|
||||
splitProperties = Buffer.splitUint8Array(results.properties);
|
||||
}
|
||||
|
||||
// var splitPickingIds;
|
||||
// if (results.pickingIds) {
|
||||
// splitPickingIds = Buffer.splitFloat32Array(results.attributes.pickingIds);
|
||||
// }
|
||||
|
||||
var flats = results.flats;
|
||||
|
||||
var objects = [];
|
||||
|
@ -125,7 +120,7 @@ class GeoJSONWorkerLayer extends Layer {
|
|||
colors: 3
|
||||
};
|
||||
|
||||
for (var i = 0; i < splitVertices.length; i++) {
|
||||
for (var i = 0; i < splitPositions.length; i++) {
|
||||
if (splitProperties && splitProperties[i]) {
|
||||
properties = JSON.parse(Buffer.uint8ArrayToString(splitProperties[i]));
|
||||
} else {
|
||||
|
@ -136,24 +131,20 @@ class GeoJSONWorkerLayer extends Layer {
|
|||
// the feature, though the current logic isn't aware of that
|
||||
obj = {
|
||||
attributes: [{
|
||||
positions: splitVertices[i],
|
||||
positions: splitPositions[i],
|
||||
normals: splitNormals[i],
|
||||
colors: splitColours[i]
|
||||
colors: splitColors[i]
|
||||
}],
|
||||
properties: properties,
|
||||
flat: flats[i]
|
||||
};
|
||||
|
||||
// if (splitPickingIds && splitPickingIds[i]) {
|
||||
// obj.attributes.pickingIds = splitPickingIds[i];
|
||||
// }
|
||||
|
||||
// WORKERS: If interactive, generate unique ID for each feature, create
|
||||
// the buffer attributes and set up event listeners
|
||||
if (this._options.interactive) {
|
||||
pickingId = this.getPickingId();
|
||||
|
||||
pickingIds = new Float32Array(splitVertices[i].length / 3);
|
||||
pickingIds = new Float32Array(splitPositions[i].length / 3);
|
||||
pickingIds.fill(pickingId);
|
||||
|
||||
obj.attributes[0].pickingIds = pickingIds;
|
||||
|
@ -192,28 +183,6 @@ class GeoJSONWorkerLayer extends Layer {
|
|||
polygonAttributes.push(bufferAttributes);
|
||||
};
|
||||
|
||||
// console.log(splitVertices, splitNormals, splitColours, splitPickingIds);
|
||||
|
||||
// var layer;
|
||||
|
||||
// var polygonAttributes = [];
|
||||
// var polygonFlat = true;
|
||||
|
||||
// objects.forEach((obj, index) => {
|
||||
// layer = polygonWorkerLayers[index];
|
||||
// layer.createGeometry(obj);
|
||||
|
||||
// if (layer.isOutput()) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
// polygonAttributes.push(layer.getBufferAttributes());
|
||||
|
||||
// if (polygonFlat && !layer.isFlat()) {
|
||||
// polygonFlat = false;
|
||||
// }
|
||||
// });
|
||||
|
||||
if (polygonAttributes.length > 0) {
|
||||
var mergedPolygonAttributes = Buffer.mergeAttributes(polygonAttributes);
|
||||
|
||||
|
@ -221,8 +190,14 @@ class GeoJSONWorkerLayer extends Layer {
|
|||
var style = (typeof this._options.style === 'function') ? this._options.style(objects[0]) : this._options.style;
|
||||
style = extend({}, GeoJSON.defaultStyle, style);
|
||||
|
||||
this._setPolygonMesh(mergedPolygonAttributes, polygonAttributeLengths, style, polygonFlat);
|
||||
this.add(this._polygonMesh);
|
||||
this._setPolygonMesh(mergedPolygonAttributes, polygonAttributeLengths, style, polygonFlat).then((result) => {
|
||||
this._polygonMesh = result.mesh;
|
||||
this.add(this._polygonMesh);
|
||||
|
||||
if (result.pickingMesh) {
|
||||
this._pickingMesh.add(pickingMesh);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
resolve();
|
||||
|
@ -337,9 +312,9 @@ class GeoJSONWorkerLayer extends Layer {
|
|||
var transferrables = [];
|
||||
var transferrablesSize = 0;
|
||||
|
||||
var vertices = [];
|
||||
var positions = [];
|
||||
var normals = [];
|
||||
var colours = [];
|
||||
var colors = [];
|
||||
// var pickingIds = [];
|
||||
|
||||
var properties = [];
|
||||
|
@ -364,14 +339,9 @@ class GeoJSONWorkerLayer extends Layer {
|
|||
for (var j = 0; j < result.attributes.length; j++) {
|
||||
attributes = result.attributes[j];
|
||||
|
||||
vertices.push(attributes.vertices);
|
||||
positions.push(attributes.positions);
|
||||
normals.push(attributes.normals);
|
||||
colours.push(attributes.colours);
|
||||
|
||||
// WORKERS: Handle interaction back in the main thread
|
||||
// if (attributes.pickingIds) {
|
||||
// pickingIds.push(attributes.pickingIds);
|
||||
// }
|
||||
colors.push(attributes.colors);
|
||||
|
||||
if (_properties) {
|
||||
properties.push(Buffer.stringToUint8Array(JSON.stringify(polygon.properties)));
|
||||
|
@ -380,19 +350,19 @@ class GeoJSONWorkerLayer extends Layer {
|
|||
};
|
||||
|
||||
var mergedAttributes = {
|
||||
vertices: Buffer.mergeFloat32Arrays(vertices),
|
||||
positions: Buffer.mergeFloat32Arrays(positions),
|
||||
normals: Buffer.mergeFloat32Arrays(normals),
|
||||
colours: Buffer.mergeFloat32Arrays(colours)
|
||||
colors: Buffer.mergeFloat32Arrays(colors)
|
||||
};
|
||||
|
||||
transferrables.push(mergedAttributes.vertices[0].buffer);
|
||||
transferrables.push(mergedAttributes.vertices[1].buffer);
|
||||
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.colours[0].buffer);
|
||||
transferrables.push(mergedAttributes.colours[1].buffer);
|
||||
transferrables.push(mergedAttributes.colors[0].buffer);
|
||||
transferrables.push(mergedAttributes.colors[1].buffer);
|
||||
|
||||
var mergedProperties;
|
||||
if (_properties) {
|
||||
|
@ -402,13 +372,6 @@ class GeoJSONWorkerLayer extends Layer {
|
|||
transferrables.push(mergedProperties[1].buffer);
|
||||
}
|
||||
|
||||
// WORKERS: Handle interaction back in the main thread
|
||||
// if (pickingIds.length > 0) {
|
||||
// mergedAttributes.pickingIds = Buffer.mergeFloat32Arrays(pickingIds);
|
||||
// transferrables.push(mergedAttributes.pickingIds[0].buffer);
|
||||
// transferrables.push(mergedAttributes.pickingIds[1].buffer);
|
||||
// }
|
||||
|
||||
var output = {
|
||||
attributes: mergedAttributes,
|
||||
flats: flats
|
||||
|
@ -452,70 +415,7 @@ class GeoJSONWorkerLayer extends Layer {
|
|||
//
|
||||
// Could make this an abstract method for each geometry layer
|
||||
_setPolygonMesh(attributes, attributeLengths, style, flat) {
|
||||
var geometry = new THREE.BufferGeometry();
|
||||
|
||||
for (var key in attributes) {
|
||||
geometry.addAttribute(key.slice(0, -1), new THREE.BufferAttribute(attributes[key], attributeLengths[key]));
|
||||
}
|
||||
|
||||
geometry.computeBoundingBox();
|
||||
|
||||
// Temporary until the above style logic is fixed for workers
|
||||
// var style = extend({}, GeoJSON.defaultStyle);
|
||||
|
||||
var material;
|
||||
if (this._options.polygonMaterial && this._options.polygonMaterial instanceof THREE.Material) {
|
||||
material = this._options.polygonMaterial;
|
||||
} else if (!this._world._environment._skybox) {
|
||||
material = new THREE.MeshPhongMaterial({
|
||||
vertexColors: THREE.VertexColors,
|
||||
side: THREE.BackSide,
|
||||
transparent: style.transparent,
|
||||
opacity: style.opacity,
|
||||
blending: style.blending
|
||||
});
|
||||
} else {
|
||||
material = new THREE.MeshStandardMaterial({
|
||||
vertexColors: THREE.VertexColors,
|
||||
side: THREE.BackSide,
|
||||
transparent: style.transparent,
|
||||
opacity: style.opacity,
|
||||
blending: style.blending
|
||||
});
|
||||
material.roughness = 1;
|
||||
material.metalness = 0.1;
|
||||
material.envMapIntensity = 3;
|
||||
material.envMap = this._world._environment._skybox.getRenderTarget();
|
||||
}
|
||||
|
||||
var mesh;
|
||||
|
||||
// Pass mesh through callback, if defined
|
||||
if (typeof this._options.onPolygonMesh === 'function') {
|
||||
mesh = this._options.onPolygonMesh(geometry, material);
|
||||
} else {
|
||||
mesh = new THREE.Mesh(geometry, material);
|
||||
|
||||
mesh.castShadow = true;
|
||||
mesh.receiveShadow = true;
|
||||
}
|
||||
|
||||
if (flat) {
|
||||
material.depthWrite = false;
|
||||
mesh.renderOrder = 1;
|
||||
}
|
||||
|
||||
if (this._options.interactive && this._pickingMesh) {
|
||||
material = new PickingMaterial();
|
||||
material.side = THREE.BackSide;
|
||||
|
||||
var pickingMesh = new THREE.Mesh(geometry, material);
|
||||
this._pickingMesh.add(pickingMesh);
|
||||
|
||||
this.addToPicking(this._pickingMesh);
|
||||
}
|
||||
|
||||
this._polygonMesh = mesh;
|
||||
return PolygonLayer.SetMesh(attributes, attributeLengths, flat, style, this._options, this._world._environment._skybox);
|
||||
}
|
||||
|
||||
// Set up and re-emit interaction events
|
||||
|
|
|
@ -57,34 +57,57 @@ class PolygonLayer 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();
|
||||
}
|
||||
PolygonLayer.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,
|
||||
normals: 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
|
||||
PolygonLayer.SetMesh(this._bufferAttributes, attributeLengths, this._flat, style, this._options, this._world._environment._skybox).then((result) => {
|
||||
// Output mesh
|
||||
this.add(result.mesh);
|
||||
|
||||
if (result.pickingMesh) {
|
||||
this._pickingMesh.add(pickingMesh);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
result.attributes = null;
|
||||
result = null;
|
||||
|
||||
resolve(this);
|
||||
}).catch(reject);
|
||||
});
|
||||
}
|
||||
|
||||
// Return center of polygon as a LatLon
|
||||
|
@ -117,122 +140,6 @@ class PolygonLayer extends Layer {
|
|||
}
|
||||
|
||||
// Create and store reference to THREE.BufferAttribute data for this layer
|
||||
//
|
||||
// TODO: Remove this and instead use the SetBufferAttributes static method
|
||||
_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 {
|
||||
var height = 0;
|
||||
|
||||
// Convert height into world units
|
||||
if (this._options.style.height && this._options.style.height !== 0) {
|
||||
height = this._world.metresToWorld(this._options.style.height, this._pointScale);
|
||||
}
|
||||
|
||||
var colour = new THREE.Color();
|
||||
colour.set(this._options.style.color);
|
||||
|
||||
// Light and dark colours used for poor-mans AO gradient on object sides
|
||||
var light = new THREE.Color(0xffffff);
|
||||
var shadow = new THREE.Color(0x666666);
|
||||
|
||||
// For each polygon
|
||||
attributes = this._projectedCoordinates.map(_projectedCoordinates => {
|
||||
// Convert coordinates to earcut format
|
||||
var _earcut = PolygonLayer.ToEarcut(_projectedCoordinates);
|
||||
|
||||
// Triangulate faces using earcut
|
||||
var faces = PolygonLayer.Triangulate(_earcut.vertices, _earcut.holes, _earcut.dimensions);
|
||||
|
||||
var groupedVertices = [];
|
||||
for (i = 0, il = _earcut.vertices.length; i < il; i += _earcut.dimensions) {
|
||||
groupedVertices.push(_earcut.vertices.slice(i, i + _earcut.dimensions));
|
||||
}
|
||||
|
||||
var extruded = extrudePolygon(groupedVertices, faces, {
|
||||
bottom: 0,
|
||||
top: height
|
||||
});
|
||||
|
||||
var topColor = colour.clone().multiply(light);
|
||||
var bottomColor = colour.clone().multiply(shadow);
|
||||
|
||||
var _vertices = extruded.positions;
|
||||
var _faces = [];
|
||||
var _colours = [];
|
||||
|
||||
var _colour;
|
||||
extruded.top.forEach((face, fi) => {
|
||||
_colour = [];
|
||||
|
||||
_colour.push([colour.r, colour.g, colour.b]);
|
||||
_colour.push([colour.r, colour.g, colour.b]);
|
||||
_colour.push([colour.r, colour.g, colour.b]);
|
||||
|
||||
_faces.push(face);
|
||||
_colours.push(_colour);
|
||||
});
|
||||
|
||||
this._flat = true;
|
||||
|
||||
if (extruded.sides) {
|
||||
this._flat = false;
|
||||
|
||||
// Set up colours for every vertex with poor-mans AO on the sides
|
||||
extruded.sides.forEach((face, fi) => {
|
||||
_colour = [];
|
||||
|
||||
// First face is always bottom-bottom-top
|
||||
if (fi % 2 === 0) {
|
||||
_colour.push([bottomColor.r, bottomColor.g, bottomColor.b]);
|
||||
_colour.push([bottomColor.r, bottomColor.g, bottomColor.b]);
|
||||
_colour.push([topColor.r, topColor.g, topColor.b]);
|
||||
// Reverse winding for the second face
|
||||
// top-top-bottom
|
||||
} else {
|
||||
_colour.push([topColor.r, topColor.g, topColor.b]);
|
||||
_colour.push([topColor.r, topColor.g, topColor.b]);
|
||||
_colour.push([bottomColor.r, bottomColor.g, bottomColor.b]);
|
||||
}
|
||||
|
||||
_faces.push(face);
|
||||
_colours.push(_colour);
|
||||
});
|
||||
}
|
||||
|
||||
// Skip bottom as there's no point rendering it
|
||||
// allFaces.push(extruded.faces);
|
||||
|
||||
var polygon = {
|
||||
vertices: _vertices,
|
||||
faces: _faces,
|
||||
colours: _colours,
|
||||
facesCount: _faces.length
|
||||
};
|
||||
|
||||
if (this._options.interactive && this._pickingId) {
|
||||
// Inject picking ID
|
||||
polygon.pickingId = this._pickingId;
|
||||
}
|
||||
|
||||
// Convert polygon representation to proper attribute arrays
|
||||
return PolygonLayer.ToAttributes(polygon);
|
||||
});
|
||||
}
|
||||
|
||||
this._bufferAttributes = Buffer.mergeAttributes(attributes);
|
||||
|
||||
// Original attributes are no longer required so free the memory
|
||||
attributes = null;
|
||||
}
|
||||
|
||||
// TODO: Ensure that this has feature parity with the non-static method
|
||||
static SetBufferAttributes(coordinates, options) {
|
||||
return new Promise((resolve) => {
|
||||
var height = 0;
|
||||
|
@ -365,50 +272,125 @@ class PolygonLayer extends Layer {
|
|||
// Create and store mesh from buffer attributes
|
||||
//
|
||||
// This is only called if the layer is controlling its own output
|
||||
_setMesh(attributes) {
|
||||
//
|
||||
// TODO: Dedupe with code used by workers, perhaps via a static method
|
||||
// _setMesh(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));
|
||||
// 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();
|
||||
|
||||
// var material;
|
||||
// if (this._options.material && this._options.material instanceof THREE.Material) {
|
||||
// material = this._options.material;
|
||||
// } else if (!this._world._environment._skybox) {
|
||||
// material = new THREE.MeshPhongMaterial({
|
||||
// vertexColors: THREE.VertexColors,
|
||||
// side: THREE.BackSide,
|
||||
// transparent: this._options.style.transparent,
|
||||
// opacity: this._options.style.opacity,
|
||||
// blending: this._options.style.blending
|
||||
// });
|
||||
// } else {
|
||||
// material = new THREE.MeshStandardMaterial({
|
||||
// vertexColors: THREE.VertexColors,
|
||||
// side: THREE.BackSide,
|
||||
// transparent: this._options.style.transparent,
|
||||
// opacity: this._options.style.opacity,
|
||||
// blending: this._options.style.blending
|
||||
// });
|
||||
// material.roughness = 1;
|
||||
// material.metalness = 0.1;
|
||||
// material.envMapIntensity = 3;
|
||||
// material.envMap = this._world._environment._skybox.getRenderTarget();
|
||||
// }
|
||||
|
||||
// var mesh;
|
||||
|
||||
// // Pass mesh through callback, if defined
|
||||
// if (typeof this._options.onMesh === 'function') {
|
||||
// mesh = this._options.onMesh(geometry, material);
|
||||
// } else {
|
||||
// mesh = new THREE.Mesh(geometry, material);
|
||||
|
||||
// mesh.castShadow = true;
|
||||
// mesh.receiveShadow = true;
|
||||
// }
|
||||
|
||||
// if (this.isFlat()) {
|
||||
// material.depthWrite = false;
|
||||
// mesh.renderOrder = 1;
|
||||
// }
|
||||
|
||||
// if (this._options.interactive && this._pickingMesh) {
|
||||
// material = new PickingMaterial();
|
||||
// material.side = THREE.BackSide;
|
||||
|
||||
// var pickingMesh = new THREE.Mesh(geometry, material);
|
||||
// this._pickingMesh.add(pickingMesh);
|
||||
// }
|
||||
|
||||
// this._mesh = mesh;
|
||||
// }
|
||||
|
||||
static SetMesh(attributes, attributeLengths, flat, style, options, skybox) {
|
||||
var geometry = new THREE.BufferGeometry();
|
||||
|
||||
// itemSize = 3 because there are 3 values (components) per vertex
|
||||
geometry.addAttribute('position', new THREE.BufferAttribute(attributes.vertices, 3));
|
||||
geometry.addAttribute('normal', new THREE.BufferAttribute(attributes.normals, 3));
|
||||
geometry.addAttribute('color', new THREE.BufferAttribute(attributes.colours, 3));
|
||||
// TODO: Remove
|
||||
// // itemSize = 3 because there are 3 values (components) per vertex
|
||||
// geometry.addAttribute('position', new THREE.BufferAttribute(attributes.positions, 3));
|
||||
// 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));
|
||||
// TODO: Remove
|
||||
// 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 material;
|
||||
if (this._options.material && this._options.material instanceof THREE.Material) {
|
||||
material = this._options.material;
|
||||
} else if (!this._world._environment._skybox) {
|
||||
if (options.polygonMaterial && options.polygonMaterial instanceof THREE.Material) {
|
||||
material = options.polygonMaterial;
|
||||
} else if (!skybox) {
|
||||
material = new THREE.MeshPhongMaterial({
|
||||
vertexColors: THREE.VertexColors,
|
||||
side: THREE.BackSide,
|
||||
transparent: this._options.style.transparent,
|
||||
opacity: this._options.style.opacity,
|
||||
blending: this._options.style.blending
|
||||
transparent: style.transparent,
|
||||
opacity: style.opacity,
|
||||
blending: style.blending
|
||||
});
|
||||
} else {
|
||||
material = new THREE.MeshStandardMaterial({
|
||||
vertexColors: THREE.VertexColors,
|
||||
side: THREE.BackSide,
|
||||
transparent: this._options.style.transparent,
|
||||
opacity: this._options.style.opacity,
|
||||
blending: this._options.style.blending
|
||||
transparent: style.transparent,
|
||||
opacity: style.opacity,
|
||||
blending: style.blending
|
||||
});
|
||||
material.roughness = 1;
|
||||
material.metalness = 0.1;
|
||||
material.envMapIntensity = 3;
|
||||
material.envMap = this._world._environment._skybox.getRenderTarget();
|
||||
material.envMap = skybox.getRenderTarget();
|
||||
}
|
||||
|
||||
var mesh;
|
||||
|
||||
// Pass mesh through callback, if defined
|
||||
if (typeof this._options.onMesh === 'function') {
|
||||
mesh = this._options.onMesh(geometry, material);
|
||||
if (typeof options.onPolygonMesh === 'function') {
|
||||
mesh = options.onPolygonMesh(geometry, material);
|
||||
} else {
|
||||
mesh = new THREE.Mesh(geometry, material);
|
||||
|
||||
|
@ -416,20 +398,25 @@ class PolygonLayer extends Layer {
|
|||
mesh.receiveShadow = true;
|
||||
}
|
||||
|
||||
if (this.isFlat()) {
|
||||
if (flat) {
|
||||
material.depthWrite = false;
|
||||
mesh.renderOrder = 1;
|
||||
}
|
||||
|
||||
if (this._options.interactive && this._pickingMesh) {
|
||||
if (options.interactive) {
|
||||
material = new PickingMaterial();
|
||||
material.side = THREE.BackSide;
|
||||
|
||||
var pickingMesh = new THREE.Mesh(geometry, material);
|
||||
this._pickingMesh.add(pickingMesh);
|
||||
|
||||
// TODO: Move this to after whatever calls PolygonLayer.SetMesh()
|
||||
// this._pickingMesh.add(pickingMesh);
|
||||
}
|
||||
|
||||
this._mesh = mesh;
|
||||
return Promise.resolve({
|
||||
mesh: mesh,
|
||||
pickingMesh: pickingMesh
|
||||
});
|
||||
}
|
||||
|
||||
// Convert and project coordinates
|
||||
|
@ -531,7 +518,7 @@ class PolygonLayer extends Layer {
|
|||
// TODO: Can this be simplified? It's messy and huge
|
||||
static ToAttributes(polygon) {
|
||||
// Three components per vertex per face (3 x 3 = 9)
|
||||
var vertices = new Float32Array(polygon.facesCount * 9);
|
||||
var positions = new Float32Array(polygon.facesCount * 9);
|
||||
var normals = new Float32Array(polygon.facesCount * 9);
|
||||
var colours = new Float32Array(polygon.facesCount * 9);
|
||||
|
||||
|
@ -603,9 +590,9 @@ class PolygonLayer extends Layer {
|
|||
var ny = cb.y;
|
||||
var nz = cb.z;
|
||||
|
||||
vertices[lastIndex * 9 + 0] = ax;
|
||||
vertices[lastIndex * 9 + 1] = ay;
|
||||
vertices[lastIndex * 9 + 2] = az;
|
||||
positions[lastIndex * 9 + 0] = ax;
|
||||
positions[lastIndex * 9 + 1] = ay;
|
||||
positions[lastIndex * 9 + 2] = az;
|
||||
|
||||
normals[lastIndex * 9 + 0] = nx;
|
||||
normals[lastIndex * 9 + 1] = ny;
|
||||
|
@ -615,9 +602,9 @@ class PolygonLayer extends Layer {
|
|||
colours[lastIndex * 9 + 1] = c1[1];
|
||||
colours[lastIndex * 9 + 2] = c1[2];
|
||||
|
||||
vertices[lastIndex * 9 + 3] = bx;
|
||||
vertices[lastIndex * 9 + 4] = by;
|
||||
vertices[lastIndex * 9 + 5] = bz;
|
||||
positions[lastIndex * 9 + 3] = bx;
|
||||
positions[lastIndex * 9 + 4] = by;
|
||||
positions[lastIndex * 9 + 5] = bz;
|
||||
|
||||
normals[lastIndex * 9 + 3] = nx;
|
||||
normals[lastIndex * 9 + 4] = ny;
|
||||
|
@ -627,9 +614,9 @@ class PolygonLayer extends Layer {
|
|||
colours[lastIndex * 9 + 4] = c2[1];
|
||||
colours[lastIndex * 9 + 5] = c2[2];
|
||||
|
||||
vertices[lastIndex * 9 + 6] = cx;
|
||||
vertices[lastIndex * 9 + 7] = cy;
|
||||
vertices[lastIndex * 9 + 8] = cz;
|
||||
positions[lastIndex * 9 + 6] = cx;
|
||||
positions[lastIndex * 9 + 7] = cy;
|
||||
positions[lastIndex * 9 + 8] = cz;
|
||||
|
||||
normals[lastIndex * 9 + 6] = nx;
|
||||
normals[lastIndex * 9 + 7] = ny;
|
||||
|
@ -649,9 +636,9 @@ class PolygonLayer extends Layer {
|
|||
}
|
||||
|
||||
var attributes = {
|
||||
vertices: vertices,
|
||||
positions: positions,
|
||||
normals: normals,
|
||||
colours: colours
|
||||
colors: colours
|
||||
};
|
||||
|
||||
if (pickingIds) {
|
||||
|
|
Ładowanie…
Reference in New Issue