diff --git a/examples/source_stream_wfs_25d.html b/examples/source_stream_wfs_25d.html
index 978a7a7d03..167c5a5f71 100644
--- a/examples/source_stream_wfs_25d.html
+++ b/examples/source_stream_wfs_25d.html
@@ -235,8 +235,10 @@
altitude: altitudeBuildings,
extrude: extrudeBuildings }),
onMeshCreated: function scaleZ(mesh) {
- mesh.scale.z = 0.01;
- meshes.push(mesh);
+ mesh.children.forEach(c => {
+ c.scale.z = 0.01;
+ meshes.push(c);
+ })
},
filter: acceptFeature,
overrideAltitudeInToZero: true,
diff --git a/examples/source_stream_wfs_3d.html b/examples/source_stream_wfs_3d.html
index c630264bad..8187bde1ff 100644
--- a/examples/source_stream_wfs_3d.html
+++ b/examples/source_stream_wfs_3d.html
@@ -186,8 +186,10 @@
altitude: altitudeBuildings,
extrude: extrudeBuildings }),
onMeshCreated: function scaleZ(mesh) {
- mesh.scale.z = 0.01;
- meshes.push(mesh);
+ mesh.children.forEach(c => {
+ c.scale.z = 0.01;
+ meshes.push(c);
+ })
},
filter: acceptFeature,
overrideAltitudeInToZero: true,
diff --git a/src/Controls/StreetControls.js b/src/Controls/StreetControls.js
index 38b9e78069..48ad942ff2 100644
--- a/src/Controls/StreetControls.js
+++ b/src/Controls/StreetControls.js
@@ -31,6 +31,8 @@ function updateSurfaces(surfaces, position, norm) {
// vector use in the pick method
const target = new THREE.Vector3();
+const normal = new THREE.Vector3();
+const normalMatrix = new THREE.Matrix3();
const up = new THREE.Vector3();
const startQuaternion = new THREE.Quaternion();
@@ -45,7 +47,9 @@ function pick(event, view, buildingsLayer, pickGround = () => {}, pickObject = (
// to detect pick on building, compare first picked building distance to ground distance
if (buildings.length && buildings[0].distance < distanceToGround) { // pick buildings
// callback
- pickObject(buildings[0].point, buildings[0].face.normal);
+ normalMatrix.getNormalMatrix(buildings[0].object.matrixWorld);
+ normal.copy(buildings[0].face.normal).applyNormalMatrix(normalMatrix);
+ pickObject(buildings[0].point, normal);
} else if (view.tileLayer) {
const far = view.camera.camera3D.far * 0.95;
if (distanceToGround < far) {
diff --git a/src/Converter/Feature2Mesh.js b/src/Converter/Feature2Mesh.js
index 634de98b92..deaf261360 100644
--- a/src/Converter/Feature2Mesh.js
+++ b/src/Converter/Feature2Mesh.js
@@ -428,29 +428,6 @@ function featureToMesh(feature, options) {
return mesh;
}
-function featuresToThree(features, options) {
- if (!features || features.length == 0) { return; }
-
- if (features.length == 1) {
- coord.crs = features[0].crs;
- coord.setFromValues(0, 0, 0);
- return featureToMesh(features[0], options);
- }
-
- const group = new THREE.Group();
- group.minAltitude = Infinity;
-
- for (const feature of features) {
- coord.crs = feature.crs;
- coord.setFromValues(0, 0, 0);
- const mesh = featureToMesh(feature, options);
- group.add(mesh);
- group.minAltitude = Math.min(mesh.minAltitude, group.minAltitude);
- }
-
- return group;
-}
-
/**
* @module Feature2Mesh
*/
@@ -505,7 +482,19 @@ export default {
return function _convert(collection) {
if (!collection) { return; }
- return featuresToThree(collection.features, options);
+ const features = collection.features;
+
+ if (!features || features.length == 0) { return; }
+
+ const group = new THREE.Group();
+ options.GlobalZTrans = collection.center.z;
+
+ features.forEach(feature => group.add(featureToMesh(feature, options)));
+
+ group.quaternion.copy(collection.quaternion);
+ group.position.copy(collection.position);
+
+ return group;
};
},
};
diff --git a/src/Core/Feature.js b/src/Core/Feature.js
index dcc79cdfa3..8241b21602 100644
--- a/src/Core/Feature.js
+++ b/src/Core/Feature.js
@@ -117,6 +117,8 @@ export class FeatureGeometry {
pushCoordinates(coordIn, feature) {
coordIn.as(feature.crs, coordOut);
+ feature.transformToLocalSystem(coordOut);
+
if (feature.normals) {
coordOut.geodesicNormal.toArray(feature.normals, feature._pos);
}
@@ -131,6 +133,7 @@ export class FeatureGeometry {
/**
* Push new values coordinates in vertices buffer.
* No geographical conversion is made or the normal doesn't stored.
+ * No local transformation is made on coordinates.
*
* @param {Feature} feature - the feature containing the geometry
* @param {number} long The longitude coordinate.
@@ -222,6 +225,7 @@ class Feature {
this.crs = collection.crs;
this.size = collection.size;
this.normals = collection.size == 3 ? [] : undefined;
+ this.transformToLocalSystem = collection.transformToLocalSystem.bind(collection);
if (collection.extent) {
// this.crs is final crs projection, is out projection.
// If the extent crs is the same then we use output coordinate (coordOut) to expand it.
@@ -261,9 +265,20 @@ class Feature {
export default Feature;
+const doNothing = () => {};
+
+const transformToLocalSystem3D = (coord, collection) => {
+ coord.geodesicNormal.applyNormalMatrix(collection.normalMatrixInverse);
+ return coord.applyMatrix4(collection.matrixWorldInverse);
+};
+
+const transformToLocalSystem2D = (coord, collection) => coord.applyMatrix4(collection.matrixWorldInverse);
+const axisZ = new THREE.Vector3(0, 0, 1);
+const alignYtoEast = new THREE.Quaternion();
/**
* An object regrouping a list of [features]{@link Feature} and the extent of this collection.
* **Warning**, the data (`extent` or `Coordinates`) can be stored in a local system.
+ * The local system center is the `center` property.
* To use `Feature` vertices or `FeatureCollection/Feature` extent in FeatureCollection.crs projection,
* it's necessary to transform `Coordinates` or `Extent` by `FeatureCollection.matrixWorld`.
*
@@ -301,6 +316,8 @@ export default Feature;
* https://alastaira.wordpress.com/2011/07/06/converting-tms-tile-coordinates-to-googlebingosm-tile-coordinates}
* for more informations.
* @property {THREE.Matrix4} matrixWorldInverse - The matrix world inverse.
+ * @property {Coordinates} center - The local center coordinates in `EPSG:4326`.
+ * The local system is centred in this center.
*
*/
@@ -321,6 +338,55 @@ export class FeatureCollection extends THREE.Object3D {
this.style = options.style;
this.isInverted = false;
this.matrixWorldInverse = new THREE.Matrix4();
+ this.center = new Coordinates('EPSG:4326', 0, 0);
+
+ if (this.size == 2) {
+ this._setLocalSystem = (center) => {
+ // set local system center
+ center.as('EPSG:4326', this.center);
+
+ // set position to local system center
+ this.position.copy(center);
+ this.updateMatrixWorld();
+ this._setLocalSystem = doNothing;
+ };
+ this._transformToLocalSystem = transformToLocalSystem2D;
+ } else {
+ this._setLocalSystem = (center) => {
+ // set local system center
+ center.as('EPSG:4326', this.center);
+
+ if (this.crs == 'EPSG:4978') {
+ // align Z axe to geodesic normal.
+ this.quaternion.setFromUnitVectors(axisZ, center.geodesicNormal);
+ // align Y axe to East
+ alignYtoEast.setFromAxisAngle(axisZ, THREE.MathUtils.degToRad(90 + this.center.longitude));
+ this.quaternion.multiply(alignYtoEast);
+ }
+
+ // set position to local system center
+ this.position.copy(center);
+ this.updateMatrixWorld();
+ this.normalMatrix.getNormalMatrix(this.matrix);
+ this.normalMatrixInverse = new THREE.Matrix3().copy(this.normalMatrix).invert();
+
+ this._setLocalSystem = doNothing;
+ };
+ this._transformToLocalSystem = transformToLocalSystem3D;
+ }
+ }
+
+ /**
+ * Apply the matrix World inverse on the coordinates.
+ * This method is used when the coordinates is pushed
+ * to transform it in local system.
+ *
+ * @param {Coordinates} coordinates The coordinates
+ * @returns {Coordinates} The coordinates in local system
+ */
+ transformToLocalSystem(coordinates) {
+ this._setLocalSystem(coordinates);
+ return this._transformToLocalSystem(coordinates, this);
}
/**
diff --git a/src/Layer/OrientedImageLayer.js b/src/Layer/OrientedImageLayer.js
index 06902851f1..00e42ba457 100644
--- a/src/Layer/OrientedImageLayer.js
+++ b/src/Layer/OrientedImageLayer.js
@@ -147,7 +147,7 @@ class OrientedImageLayer extends GeometryLayer {
for (const pano of this.panos) {
// set position
coord.crs = pano.crs;
- coord.setFromArray(pano.vertices);
+ coord.setFromArray(pano.vertices).applyMatrix4(orientation.matrix);
pano.position = coord.toVector3();
// set quaternion
diff --git a/src/Process/FeatureProcessing.js b/src/Process/FeatureProcessing.js
index 1c5fd18e02..9c3c2f0609 100644
--- a/src/Process/FeatureProcessing.js
+++ b/src/Process/FeatureProcessing.js
@@ -5,14 +5,6 @@ import handlingError from 'Process/handlerNodeError';
import Coordinates from 'Core/Geographic/Coordinates';
const coord = new Coordinates('EPSG:4326', 0, 0, 0);
-const mat4 = new THREE.Matrix4();
-
-function applyMatrix4(obj, mat4) {
- if (obj.geometry) {
- obj.geometry.applyMatrix4(mat4);
- }
- obj.children.forEach(c => applyMatrix4(c, mat4));
-}
function assignLayer(object, layer) {
if (object) {
@@ -89,7 +81,6 @@ export default {
// if request return empty json, WFSProvider.getFeatures return undefined
result = result[0];
if (result) {
- const isApplied = !result.layer;
assignLayer(result, layer);
// call onMeshCreated callback if needed
if (layer.onMeshCreated) {
@@ -100,22 +91,14 @@ export default {
ObjectRemovalHelper.removeChildrenAndCleanupRecursively(layer, result);
return;
}
- // We don't use node.matrixWorld here, because feature coordinates are
- // expressed in crs coordinates (which may be different than world coordinates,
- // if node's layer is attached to an Object with a non-identity transformation)
- if (isApplied) {
- // NOTE: now data source provider use cache on Mesh
- // TODO move transform in feature2Mesh
- mat4.copy(node.matrixWorld).invert().elements[14] -= result.minAltitude;
- applyMatrix4(result, mat4);
- }
-
- if (result.minAltitude) {
- result.position.z = result.minAltitude;
- }
- result.layer = layer;
- node.add(result);
- node.updateMatrixWorld();
+ // remove old group layer
+ node.remove(...node.children.filter(c => c.layer && c.layer.id == layer.id));
+ const group = new THREE.Group();
+ group.layer = layer;
+ group.matrixWorld.copy(node.matrixWorld).invert();
+ group.matrixWorld.decompose(group.position, group.quaternion, group.scale);
+ node.add(group.add(result));
+ group.updateMatrixWorld(true);
} else {
node.layerUpdateState[layer.id].failure(1, true);
}
diff --git a/test/unit/feature.js b/test/unit/feature.js
index d2d60ca089..eb3cfb0b91 100644
--- a/test/unit/feature.js
+++ b/test/unit/feature.js
@@ -12,16 +12,18 @@ describe('Feature', function () {
const coord = new Coordinates('EPSG:4326', 0, 0, 0);
it('Should instance Features', function () {
- const featurePoint = new Feature(FEATURE_TYPES.POINT, 'EPSG:4326');
- const featureLine = new Feature(FEATURE_TYPES.LINE, 'EPSG:4326');
- const featurePolygon = new Feature(FEATURE_TYPES.POLYGON, 'EPSG:4326');
+ const collection = new FeatureCollection(options_A);
+ const featurePoint = new Feature(FEATURE_TYPES.POINT, collection);
+ const featureLine = new Feature(FEATURE_TYPES.LINE, collection);
+ const featurePolygon = new Feature(FEATURE_TYPES.POLYGON, collection);
assert.equal(featurePoint.type, FEATURE_TYPES.POINT);
assert.equal(featureLine.type, FEATURE_TYPES.LINE);
assert.equal(featurePolygon.type, FEATURE_TYPES.POLYGON);
});
it('Should bind FeatureGeometry', function () {
- const featureLine = new Feature(FEATURE_TYPES.LINE, 'EPSG:4326');
+ const collection = new FeatureCollection(options_A);
+ const featureLine = new Feature(FEATURE_TYPES.LINE, collection);
featureLine.bindNewGeometry();
assert.equal(featureLine.geometryCount, 1);
});
@@ -56,6 +58,8 @@ describe('Feature', function () {
featureLine.updateExtent(geometry);
+ collection_A.updateMatrix();
+ featureLine.extent.applyMatrix4(collection_A.matrix);
assert.equal(featureLine.extent.south, -1118889.9748579601);
assert.equal(featureLine.vertices.length, geometry.indices[0].count * featureLine.size);
assert.equal(featureLine.vertices.length, featureLine.normals.length);
diff --git a/test/unit/featureUtils.js b/test/unit/featureUtils.js
index 988db3c055..5789d76271 100644
--- a/test/unit/featureUtils.js
+++ b/test/unit/featureUtils.js
@@ -15,10 +15,11 @@ describe('FeaturesUtils', function () {
}));
it('should correctly compute extent geojson', () =>
promise.then((collection) => {
- assert.equal(collection.extent.west, 0.30798339284956455);
- assert.equal(collection.extent.east, 2.4722900334745646);
- assert.equal(collection.extent.south, 42.91620643817353);
- assert.equal(collection.extent.north, 43.72744458647463);
+ const extent = collection.extent.clone().applyMatrix4(collection.matrix);
+ assert.equal(extent.west, 0.30798339284956455);
+ assert.equal(extent.east, 2.4722900334745646);
+ assert.equal(extent.south, 42.91620643817353);
+ assert.equal(extent.north, 43.72744458647463);
}));
it('should correctly filter point', () =>
promise.then((collection) => {
diff --git a/test/unit/geojson.js b/test/unit/geojson.js
index 961295af37..baa17fdaab 100644
--- a/test/unit/geojson.js
+++ b/test/unit/geojson.js
@@ -15,14 +15,14 @@ function parse(geojson) {
}
describe('GeoJsonParser', function () {
- it('should set all z coordinates to 1', () =>
+ it('should set all z coordinates to 0', () =>
parse(holes).then((collection) => {
- assert.ok(collection.features[0].vertices.every((v, i) => i == 0 || ((i + 1) % 3) != 0 || v == 0));
+ assert.ok(collection.features[0].vertices.every((v, i) => ((i + 1) % 3) != 0 || (v + collection.position.z) == 0));
}));
it('should respect all z coordinates', () =>
parse(gpx).then((collection) => {
- assert.ok(collection.features[0].vertices.every((v, i) => i == 0 || ((i + 1) % 3) != 0 || v != 0));
+ assert.ok(collection.features[0].vertices.every((v, i) => ((i + 1) % 3) != 0 || (v + collection.position.z) != 0));
}));
it('should return an empty collection', () =>