Skip to content

Commit

Permalink
refactor(Feature): compute and apply local transform matrix in Feature.
Browse files Browse the repository at this point in the history
  • Loading branch information
gchoqueux committed May 28, 2021
1 parent 9b62770 commit e244f55
Show file tree
Hide file tree
Showing 10 changed files with 117 additions and 66 deletions.
6 changes: 4 additions & 2 deletions examples/source_stream_wfs_25d.html
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
6 changes: 4 additions & 2 deletions examples/source_stream_wfs_3d.html
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
6 changes: 5 additions & 1 deletion src/Controls/StreetControls.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand All @@ -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) {
Expand Down
37 changes: 13 additions & 24 deletions src/Converter/Feature2Mesh.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down Expand Up @@ -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;
};
},
};
66 changes: 66 additions & 0 deletions src/Core/Feature.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand All @@ -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.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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`.
*
Expand Down Expand Up @@ -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.
*
*/

Expand All @@ -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);
}

/**
Expand Down
2 changes: 1 addition & 1 deletion src/Layer/OrientedImageLayer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
33 changes: 8 additions & 25 deletions src/Process/FeatureProcessing.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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) {
Expand All @@ -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);
}
Expand Down
12 changes: 8 additions & 4 deletions test/unit/feature.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
Expand Down Expand Up @@ -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);
Expand Down
9 changes: 5 additions & 4 deletions test/unit/featureUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down
6 changes: 3 additions & 3 deletions test/unit/geojson.js
Original file line number Diff line number Diff line change
Expand Up @@ -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', () =>
Expand Down

0 comments on commit e244f55

Please sign in to comment.