How to move the camera in a forge viewer to face the true north direction, using the euler angles - autodesk-forge

I understand that forge viewer uses three.js extensively, I have a couple of questions
I want to point my forge viewer camera to the north direction (true north) and further synchronise the rotation based on the north values.
Also is it possible to set the bounds ?
I'm trying to synchronise forge viewer based on a set of euler angles (pitch, yaw and roll) available at my hand
I'm using the forge viewer version 7.

Fareed also asked this via email, so I'm copying & pasting my replies here.
Not sure which source model format you used, so suppose it's Revit (RVT).
In Revit model metadata, two attributes can help calculate the north rotation to the true north.
metadata['world north vector']['XYZ']: The project north vector of the Revit view.
metadata['custom values']['angleToTrueNorth']: The angel from the project north to true north of Revit view.
// Calculate project north angle
const projectNorthVector = new THREE.Vector3().fromArray( model.getData().metadata['world north vector']['XYZ'] );
const autoCam = viewer.autocam;
const frontDirection = autoCam.sceneFrontDirection.clone(); //!<<< viewer world north
const upVector = autoCam.sceneUpDirection.clone();
let crossVector = new THREE.Vector3();
crossVector.crossVectors( frontDirection, projectNorthVector );
const projectNorthAngle = projectNorthVector.angleTo( frontDirection ) * ( crossVector.dot( upVector ) < 0 ? -1 : 1 );
// Calculate true north angle
let trueNorthAngle = metadata['custom values']['angleToTrueNorth'] * (Math.PI / 180);
// Final rotation angle from viewer world north to true north
const finalRotationAngle = projectNorthAngle + trueNorthAngle;
// and then rotate your vector by Z.
Sorry, I'm not familiar with the Euler angles (pitch, yaw, and roll), but from three.js documentation, I can see it uses intrinsic Tait-Bryan angles.
Three.js uses intrinsic Tait-Bryan angles. This means that rotations are performed with respect to the local coordinate system. That is, for order 'XYZ', the rotation is first around the local-X axis (which is the same as the world-X axis), then around local-Y (which may now be different from the world Y-axis), then local-Z (which may be different from the world Z-axis).
So, probably, you can try to get that with the either way below:
Use three.js API to get Euler Tait-Bryan angles from quaternion
const quaternion = viewer.getCamera().quaternion.clone();
const rotation = new THREE.Euler().setFromQuaternion( quaternion, 'XYZ' );
Or get it from the camera's rotation
const { rotation } = viewer.getCamera();
const eulerOrder = rotation.order;
Or refer to the Navisworks approach: https://adndevblog.typepad.com/aec/2019/07/get-roll-value-of-edit-current-viewpoint.html
viewer.navigation.setCameraUpVector( new THREE.Vector3(0,1,0), true );
const quaternion = viewer.getCamera().quaternion.clone();
let { x, y, z, w } = quaternion;
let roll = Math.atan2(2*y*w - 2*x*z, 1 - 2*y*y - 2*z*z);
let pitch = Math.atan2(2*x*w - 2*y*z, 1 - 2*x*x - 2*z*z);
let yaw = Math.asin(2*x*y + 2*z*w);
To set Euler angles to camera, here is an approach, but I think you will need to change the Euler order if it's not XYZ.
const euler = new THREE.Euler(..., ..., ..., 'XYZ');
viewer.getCamera().quaternion..setFromEuler(euler);

Related

Cesium convert lat lon to x and y

I am looking to turn 2 lat/lon positions into an x and y distance of the canvas, then apply the distance formula to it.
Right now I have:
const leftPoint = new LatLon(center.lat, center.lon).destinationPoint(semiMajorAxis, 270);
const rightPoint = new LatLon(center.lat, center.lon).destinationPoint(semiMajorAxis, 90);
const leftXY = Cartographic.toCartesian(Cartographic.fromDegrees(leftPoint.lon, leftPoint.lat));
const rightXY = Cartographic.toCartesian(Cartographic.fromDegrees(rightPoint.lon, rightPoint.lat));
const diameter = distanceFormula(leftXY.x, leftXY.y, rightXY.x, rightXY.y);
But the result of diameter is 18,000, even though both points are on my screen!
Cesium's Cartographic.toCartesian function converts a Cartographic (lon/lat/alt) type of coordinate to a full 3D Cartesian position. Imagine X, Y, Z with zero being the center of the Earth itself, with the Earth's surface being approximately 6.3 million meters in any direction.
If you're looking for 2D canvas / screen coordinates, you must follow this call with another function, Cesium.SceneTransforms.wgs84ToWindowCoordinates. That function converts the 3D WGS84 (Cartesian3) Earth position into a 2D (Cartesian2) screen position. There's a demo of wgs84ToWindowCoordinates being used in the Sandcastle Star Burst Example around line 287.
Also it looks like you've rolled your own LatLon class, not specified above, that appears to have similar functions to Cesium's Cartographic class. You might be able to make the code a little cleaner by using Cartographic directly instead of a homebrew class there. Likewise you don't need to roll your own distanceFormula on the last line. Once you have 2D Cartesian2 window coordinates, call Cesium.Cartesian2.distance to get the distance.
I can't understand your saying 'x and y distance of the canvas'.
Generally, for calculate distance between two point on CesiumJS follow below steps.
1.Define two points
//Define x,y coordinate and convert to radian
const longitudeRadian_1 = Cesium.Math.toRadians(longitudeDegree_1)
const latitudeRadian_1 = Cesium.Math.toRadians(latitudeDegree_1)
const longitudeRadian_2 = Cesium.Math.toRadians(longitudeDegree_2)
const latitudeRadian_2 = Cesium.Math.toRadians(latitudeDegree_2)
//Get cartographic from degrees
const Carto_Point_1 = new Cesium.Cartographic(longitudeRadian_1 , latitudeRadian_1 )
const Carto_Point_2 = new Cesium.Cartographic(longitudeRadian_2 , latitudeRadian_2)
//Get cartesian from cartographic
const Cartesian_Point_1 = Cesium.Cartographic.toCartesian(Carto_Point_1)
const Cartesian_Point_2 = Cesium.Cartographic.toCartesian(Carto_Point_2)
2.Calculate distance between two points
const distance = Cesium.Cartesian3.distance(Cartesian_Point_1, Cartesian_Point_2)
console.log(distance)
I hope this would help

Autodesk Forge Viewer transform 2D to 3D coordinates

I am testing the possibilities of the forge V7 viewer on a web browser with a revit model.
The idea of ​​the test is:
display 2 viewers simultaneously with 1 3D view and a 2D view
click on a point of the 2D view and
display a sphere in the 2D view on this point
display another sphere in the 3D view at the equivalent point
the spheres are added with SceneBuilder (to allow them to be selected later)
I tried to follow the behavior of
https://forge.autodesk.com/ja/node/1765 and
https://github.com/Autodesk-Forge/viewer-navigation.sample
and i have questions
1 - the 2D view must be a sheet, using a 2D view directly (a floor view for example) does not allow the calculation, is that correct?
2 - https://forge.autodesk.com/ja/node/1765 seems to have calculation and/or precision issues (cf forge1.png generated from the test in Heroku and forge2.png generated by my program)
3 - the method only works if you click on an object
how can I retrieve the coordinates if I click in an empty area?
4 - the spheres are identified in the viewer (cf forge3.png), but
a - how to give them a name (and not "object")
b - how to replace the name "model" by the name of the scene?
Can you help me ?
Thanks in advance
Luc
Here is my code
// click listener on 2d viewer
function listenerScene(ev){
var intersection = viewer2d.hitTest(ev.offsetX, ev.offsetY);
if (intersection) {
AddItem(intersection.intersectPoint.x, intersection.intersectPoint.y,
intersection.intersectPoint.z);
AddItem3d(intersection);
}
// add in 2d
async function AddItem(x,y,z){
... add a sphere at (x,y,z)
}
// add in 3d
async function AddItem3d(intersection) {
const worldPos = sheetToWorld(intersection.intersectPoint,
viewer2d.model,viewer.model);
if (worldPos) {
... add a sphere at worldPos
}
}
// compute 3d point
function sheetToWorld(sheetPos, model2d, model3d) {
const viewportExt = viewer2d.getExtension('Autodesk.AEC.ViewportsExtension');
const viewport = viewportExt.findViewportAtPoint(model2d, new THREE.Vector2(sheetPos.x, sheetPos.y));
if (!viewport) {
return null;
}
const sheetUnitScale = model2d.getUnitScale();
const globalOffset = model3d.getData().globalOffset;
const matrix = viewport.get2DTo3DMatrix(sheetUnitScale);
var worldPos = sheetPos.clone().applyMatrix4(matrix);
worldPos = worldPos.sub(globalOffset);
return worldPos;
}

Calculate large distance between two points using GeoTools

New to GeoTools and GIS and I am trying to calculate distance between Mumbai and Durban using GeoTools library. I am getting close to accurate results for small distances but when i go for bigger ones,the calculation is way too offcourse by 2000 km, i dont completely understand the CRS system .Below is my Code to calculate the distance between Mumbai and Durban
Coordinate source = new Coordinate(19.0760, 72.8777); ///Mumbai Lat Long
Coordinate destination1 = new Coordinate(-29.883333, 31.049999); //Durban Lat Long
GeometryFactory geometryFactory = new GeometryFactory();
Geometry point1 = geometryFactory.createPoint(source);
Geometry point2 = geometryFactory.createPoint(destination1);
CoordinateReferenceSystem auto = auto = CRS.decode("AUTO:42001,13.45,52.3");
MathTransform transform = CRS.findMathTransform(DefaultGeographicCRS.WGS84, auto);
Geometry g3 = JTS.transform(point1, transform);
Geometry g4 = JTS.transform(point2, transform);
double distance = g3.distance(g4);
This is what happens when you copy code blindly from stackexchange questions without reading the question it was based on which explains why.
All the times I've answered that question (and posted code like that) the questioner is trying to use lat/lon coordinates in degrees to measure a short distance in metres. The trick shown in your question creates an automatic UTM projection centred on the position specified after the "AUTO:42001," bit (in your case 52N 13E) - this needs to be the centre of the area you are interested in, so in your case those values are probably wrong anyway.
But you aren't interested in a small region Mumbai to Durban is a significant way around the Earth so you need to allow for the curvature of the Earth's surface. Also you aren't trying to do something difficult for which JTS is the only source of process (e.g buffering). In this case you should use the GeodeticCalculator which takes the shape of the Earth into account using the library from C. F. F. Karney, Algorithms for geodesics, J. Geodesy 87, 43–55 (2013).
Anyway enough explanation that no one will read in the future, here's the code:
public static void main(String[] args) {
DefaultGeographicCRS crs = DefaultGeographicCRS.WGS84;
if (args.length != 4) {
System.err.println("Need 4 numbers lat_1 lon_1 lat_2 lon_2");
return;
}
GeometryFactory geomFactory = new GeometryFactory();
Point[] points = new Point[2];
for (int i = 0, k = 0; i < 2; i++, k += 2) {
double x = Double.valueOf(args[k]);
double y = Double.valueOf(args[k + 1]);
if (CRS.getAxisOrder(crs).equals(AxisOrder.NORTH_EAST)) {
System.out.println("working with a lat/lon crs");
points[i] = geomFactory.createPoint(new Coordinate(x, y));
} else {
System.out.println("working with a lon/lat crs");
points[i] = geomFactory.createPoint(new Coordinate(y, x));
}
}
double distance = 0.0;
GeodeticCalculator calc = new GeodeticCalculator(crs);
calc.setStartingGeographicPoint(points[0].getX(), points[0].getY());
calc.setDestinationGeographicPoint(points[1].getX(), points[1].getY());
distance = calc.getOrthodromicDistance();
double bearing = calc.getAzimuth();
Quantity<Length> dist = Quantities.getQuantity(distance, SI.METRE);
System.out.println(dist.to(MetricPrefix.KILO(SI.METRE)).getValue() + " Km");
System.out.println(dist.to(USCustomary.MILE).getValue() + " miles");
System.out.println("Bearing " + bearing + " degrees");
}
Giving:
working with a lon/lat crs
POINT (72.8777 19.076)
POINT (31.049999 -29.883333)
7032.866960793305 Km
4370.020928274692 miles
Bearing -139.53428618565218 degrees

GLTF file not well positioned by Cesium

I want to display a hurricane (big isosurface object) in Cesium. For this I converted an OBJ file with longitude, latitude, altitude columns for each vertex of the isosurface representing the hurricane, in a new OBJ file reprojected in ECEF (Earth Centered) projection.So the final OBJ file contains now X,Y,Z for each vertex instead of longitude, latitude, altitude. After final reformat by obj2gltf, I try to display the GLTF "hurricane" file in Cesium.JS using the code below:
console.log('loading hurricane.gltf';
var mymodel = viewer.scene.primitives.add(Cesium.Model.fromGltf({
url : 'data/hurricane.gltf',
modelMatrix : Cesium.Matrix4.IDENTITY,
asynchronous: false
}));
I can see my hurricane on the earth, but not at the good position. I suspect a problem of matrix. IDENTITY matrix seems not to be the good one. I could try to make a new matrix but I can't find enough informations about the axes orientation used by Cesium.
I verified the X,Y,Z ECEF coordinates, they are good. Does anyone already meet this problem ?
If your glTF model origin is at the center of the hurricane, you can place it using a Cesium Entity, something like this:
// Longitude degrees, Latitude degrees, height in meters
var position = Cesium.Cartesian3.fromDegrees(-123.0744619, 44.0503706, height);
var heading = Cesium.Math.toRadians(0);
var pitch = 0;
var roll = 0;
var hpr = new Cesium.HeadingPitchRoll(heading, pitch, roll);
var orientation = Cesium.Transforms.headingPitchRollQuaternion(position, hpr);
var entity = viewer.entities.add({
name : 'Hurricane',
position : position,
orientation : orientation,
model : {
uri : 'data/hurricane.gltf'
}
});
viewer.trackedEntity = entity;
There are more complete working demos of this on Sandcastle.
But, if your hurricane is visible on the surface of the Earth using the identity matrix, that likely means that the origin of that model is nowhere near the center of the hurricane. You may need to edit the glTF file, to make sure that the model is centered on its own origin, and does not have some fixed Earth location pre-baked into the model's internal transformations.

Projection drift when rendering in WebGL over Google Map

I am trying to implement a WebGL-based rendering on Google Map (api3) as I want to render a massive amount of dynamic geometries.
Basically, I create a google.maps.OverlayView attached with a WebGL canvas into the map.
However, I encountered some problem with the mapping of the projection. Basically, I extracted the "fromLatLngToPoint" function from the googlemap api as follows:
function fromLatLngToPoint(a){
var c={x:0,y:0},
d=this.j;
c.x=d.x+a.lng*this.B;
var e=oe(m.sin(re(a.lat)),-(1-1E-15),1-1E-15);
c.y=d.y+.5*m.log((1+e)/(1-e))*-this.F;
return c
}
function oe(a,b,c){null!=b&&(a=m.max(a,b));null!=c&&(a=m.min(a,c));return a}
function re(a){return m.PI/180*a}
Then I implemented it in my vertex shader based on the documentation in Google Map Coordinates.
Basically, I have a event listener to send the updated projection constants, the viewport bounds, and the zoom level to my shader.
Then my shader will calculate the new screen coordinates based on these inputs.
highp float e, x, y, offsetY, offsetX;
// projection transformation for target points
e = sin(p.y* PI/180.0);
y = prj_y + 0.5 * log((1.0+e)/(1.0-e))*(-F);
x = prj_x + p.x*B;
// projection transformation for offset (bounds)
e = sin(bound_y*PI/180.0);
offsetY = prj_y + 0.5 * log((1.0+e)/(1.0-e))*(-F);
offsetX = prj_x + bound_x*B;
// calculate actual pixel coord wrt zoom/numTiles
x = (x* numTiles - offsetX* numTiles);
y = (y* numTiles - offsetY* numTiles);
gl_PointSize = 5.0;
gl_Position = projectionMatrix * modelViewMatrix * vec4(x,y,0.0,1.0);
However, as shown in the screenshot below, it seems there are some errors? The rendered geometries are distorted. (I used the google map polygon api to render some of the geometries as comparison)
Screenshot Here
I am totally at a loss, what might be the reason for this distortion?
I am suspecting that the single precision in the shader is giving rise to the error. So I am wondering if there is any workaround?
It is hard to debug this piece of code and diagnose the cause of the issue. I would suggest you using the CanvasLayer library that hides all these concrete details of specifying the coordinates you want to draw the polygon. Rather you would be able to focus on your app code and functionality. The performance will be better in terms of projected image.