I use the gltf branch of Cesium, and I want to display 3d model of planes. To do that I create czmlDataSource that I load and add to dataSources.
The problem is that I can't figure out how to calculate orientation quaternion to have planes parallel to the ground at a given lat,lon,alt heading north by default (and then impact their heading, eventually pitch and roll).
here is what I do to compute my actual quaternions, but the 3d models are not correctly oriented (and I don't know how to change heading, pitch, roll) :
var geoPosition = new Cesium.Cartographic(Cesium.Math.toRadians(inputPosition.lon), Cesium.Math.toRadians(inputPosition.lat), inputPosition.alt);
var cartesianPosition = Cesium.Ellipsoid.WGS84.cartographicToCartesian(geoPosition);
var euler = [cartesianPosition.x, cartesianPosition.y, cartesianPosition.z];
var qx = Cesium.Quaternion.fromAxisAngle(Cesium.Cartesian3.UNIT_X, euler[0]);
var qy = Cesium.Quaternion.fromAxisAngle(Cesium.Cartesian3.UNIT_Y, euler[1]);
var qz = Cesium.Quaternion.fromAxisAngle(Cesium.Cartesian3.UNIT_Z, euler[2]);
var qt = Cesium.Quaternion.multiply(qz, qy);
var q = Cesium.Quaternion.multiply(qt, qx);
Cesium.Quaternion.normalize(q, q);
var czmlSrc = [{
"orientation": {
"epoch": "2012-08-04T16:00:00Z",
"interpolationAlgorithm": "LINEAR",
"interpolationDegree": 1,
"unitQuaternion": [0, q.x,q.y,q.z,q.w,
3600, q.x,q.y,q.z,q.w]
}
}];
CZML currently has the orientation "backwards" compared to Cesium's convention. So if you are working with CZML you actually want the conjugate of the orientation. We plan on fixing this in a major CZML update within a few months, but didn't want to break all of the existing documents out there with the current format. When we do update, the plan is to try and do it in a backwards compatible way, so existing CZML will still work.
var C3 = Cesium.Cartesian3
var Q = Cesium.Quaternion
// radians everywhere
var q = Q.fromAxisAngle(C3.UNIT_X, -o.pitch ) // or maybe roll first?
Q.multiply(q, Q.fromAxisAngle(C3.UNIT_Y, -o.roll ), q)
Q.multiply(q, Q.fromAxisAngle(C3.UNIT_Z, o.heading - Math.PI/2), q)
Q.multiply(q, Q.fromAxisAngle(C3.UNIT_Y, o.lat - Math.PI/2), q)
Q.multiply(q, Q.fromAxisAngle(C3.UNIT_Z, -o.lon ), q)
Q.conjugate(q, q)
czml.process([{
position : { cartographicRadians: [o.lon, o.lat, 0] }
, orientation: { unitQuaternion: [q.x, q.y, q.z, q.w] }
, model : { gltf: model.key + '.gltf' }
}])
Lots of calcs here. Don't know if and how this could be optimized.
Related
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.
I am trying to create a d3 SVG that draws a map of New York State and scale it so that it fits the size of my SVG, the issue I am having is that when I use .fitSize([height, width], mapObject) it only returns a NaN error in the console.
the topoJSON file of NYS I am using
I am able to get the map to display without scaling but of course, it is not optimized and needs to be scaled
I have attempted what is said in this post but I have not figured out the correct solution
var map = d3.json('./ny.json')
Promise.all([map]).then(data => {
var height = 800;
var width = 800;
var mapData = data[0]
// original geoJSON to that works without scaling
// var geoData = topojson.feature(mapData, mapData.objects["cb_2015_new_york_county_20m"]).features
//
var geoData = topojson.feature(mapData, {
type:"GeometryCollection",
geometries: mapData.objects["cb_2015_new_york_county_20m"].geometries,
})
var projection = d3.geoMercator()
.fitSize([width, height], geoData)
var path = d3.geoPath()
.projection(projection)
d3.select('svg')
.attr('height', height)
.attr('width', width)
.selectAll('.county')
.data(geoData)
.enter()
.append('path')
.classed('.county', true)
.attr('d', path)
})
I am pretty sure this is a formatting error on my part, but I am unsure of what data .fitSize() or .fitExtent() is trying to compare against.
right now the way the code site I receive no error outputted to the console but I also have no data append to the SVG
The issue is that fitSize takes a geojson object while selectAll.data() takes an array, you are using one of these two for both in geoData. This leaves two solutions:
Solution 1:
If we use
var geoData = topojson.feature(mapData, mapData.objects["cb_2015_new_york_county_20m"]).features
var projection = d3.geoMercator()
.fitSize([width, height], geoData)
We get NaN errors because the projection is not set properly as we aren't passing a geojson object, just an array of geojson objects. We could solve this by making a feature collection with geoData and passing that to fitSize:
var geoData = topojson.feature(mapData, mapData.objects["cb_2015_new_york_county_20m"]).features
var projection = d3.geoMercator()
.fitSize([width, height], {type:"FeatureCollection", features: geoData})
Now we are passing a geojson feature collection to fitSize, we're all go on the projection, and since geoData is still an array, we can pass that to selectAll.data() unchanged.
Here's a block.
Solution 2:
If we use:
var geoData = topojson.feature(mapData, {
type:"GeometryCollection",
geometries: mapData.objects["cb_2015_new_york_county_20m"].geometries,
})
We get a geojson object, projection.fitSize works, but selectAll().data(geoData) doesn't add any features as it isn't an array - the enter selection is empty. We can substitute in selectAll().data(geoData.features) to solve this and enter one path per feature (alternatively we could use .data([geoData]) to enter one feature for all the paths).
Here's a block.
Both blocks are drawn at the correct scale - the map exceeds the block bounds as I didn't alter your 800x800 dimensions
Is there a way to get the actual intersection of to geometries with BabylonJS?
E.g. the intersection point of a line and a plane, the line intersection of two planes, the arc intersection of a sphere and a plane, etc...
Thanks!
I believe what you are looking for is the CSG (Constructive Solid Geometry) tools in Babylon.js. To use it you can reference this tutorial here.
Essentially what you want to do is the following:
CSG intersect (modified code from the link)
// a and b can be any mesh you define
var a = BABYLON.Mesh.CreateBox("box", 500, scene);
var b = BABYLON.Mesh.CreateBox("box", 500, scene);
// Convert to CSG meshes
var aCSG = BABYLON.CSG.FromMesh(a);
var bCSG = BABYLON.CSG.FromMesh(b);
var subCSG = bCSG.intersect(aCSG);
// Disposing original meshes since we don't want to see them on the scene
a.dispose();
b.dispose();
// Convert back to regular mesh from CSG mesh
subCSG.toMesh("csg", new BABYLON.StandardMaterial("mat", scene), scene);
For more uses of CSG you should check out the documentation.
I am using gestouch library on github.https://github.com/fljot/Gestouch
zoomall is my movieclip, I am able to zoom in and out at a specific point.
here is my code,
import org.gestouch.events.GestureEvent;
import org.gestouch.gestures.ZoomGesture;
var zoom: ZoomGesture*;
zoom = new ZoomGesture(zoomall);
zoom.addEventListener(org.gestouch.events.GestureEvent.GESTURE_BEGAN, onGesture);
zoom.addEventListener(org.gestouch.events.GestureEvent.GESTURE_CHANGED, onGesture);
function onGesture(event: org.gestouch.events.GestureEvent): void {
const gesture: ZoomGesture = event.target as ZoomGesture;
var matrix: Matrix = zoomall.transform.matrix;
var transformPoint: Point = matrix.transformPoint(zoomall.globalToLocal(zoom.location));
matrix.translate(-transformPoint.x, -transformPoint.y);
matrix.scale(gesture.scaleX, gesture.scaleY);
matrix.translate(transformPoint.x, transformPoint.y);
zoomall.transform.matrix = matrix;
}
Here I want to restrict the zoom in and out to specific scale.
And I also want to pan the movieclip(zoomall) and it should not pan outside the device screen.
If you want to try what flash is all ready built with these are great and simple tutorials that may help you out.
Pan tutorial
http://www.republicofcode.com/tutorials/flash/as3pangesture/
Pinch/Zoom
http://www.republicofcode.com/tutorials/flash/as3pinchzoom/
Hope this helps
You realize that by calling matrix.scale(B, B) you simply do A * B = C,
where A is current scale and C is resulting scale?
So if you don't want C to be bigger than maximum maxC, you should limit B:
B = Math.min(B, maxC / A)
Same for minimum minC:
B = Math.max(minC / A, B)
So you will have:
// assuming you keep the scale ratio
var minS:Number = MIN_SCALE / zoomall.scaleX;
var maxS:Number = MAX_SCALE / zoomall.scaleX;
var s:Number = Math.max(minS, Math.min(gesture.scaleX, maxS));
matrix.scale(s, s);
If I create a rectangle with 100px width and 100px height and then rotate it, the size of the element's "box" will have increased.
With 45 rotation, the size becomes about 143x143 (from 100x100).
Doing sometimes like cos(angleRad) * currentWidth seems to work for 45 rotation, but for other bigger angles it doesn't.
At the moment I am doing this:
var currentRotation = object.rotation;
object.rotation = 0;
var normalizedWidth = object.width;
var normalizedHeight = object.height;
object.rotation = currentRotation;
Surely, there must be a better and more efficient way. How should I get the "normalized" width and height of a displayobject, aka the size when it has not been rotated?
The best approach would probably be to use the code posted in the question - i.e. to unrotate the object, check its width, and then re-rotate it. Here's why.
First, simplicity. It's obvious what's being done, and why it works. Anyone coming along later should have no trouble understanding it.
Second, accuracy. Out of curiosity I coded up all three suggestions currently in this thread, and I was not really surprised to find that for an arbitrarily scaled object, they give three slightly different answers. The reason for this, in a nutshell, is that Flash's rendering internals are heavily optimized, and among other things, width and height are not stored internally as floats. They're stored as "twips" (twentieths of a pixel) on the ground that further accuracy is visually irrelevant.
Anyway, if the three methods give different answers, which is the most accurate? For my money, the most correct answer is what Flash thinks the width of the object is when it's unrotated, which is what the simple method gives us. Also, this method is the only one that always give answers rounded to the nearest 1/20, which I surmise (though I'm guessing) to mean it's probably equal to the value being stored internally, as opposed to being a calculated value.
Finally, speed. I assume this will surprise you, but when I coded the three methods up, the simple approach was the fastest by a small margin. (Don't read too much into that - they were all very close, and if you tweak my code, a different method might edge into the lead. The point is they're very comparable.)
You probably expected the simple method to be slower on the grounds that changing an object's rotation would cause lots of other things to be recalculated, incurring overhead. But all that really happens immediately when you change the rotation is that the object's transform matrix gets some new values. Flash doesn't really do much with that matrix until it's next time to draw the object on the screen. As for what math occurs when you then read the object's width/height, it's difficult to say. But it's worth noting that whatever math takes place in the simple method is done by the Player's heavily optimized internals, rather than being done in AS3 like the algebraic method.
Anyway I invite you to try out the sample code, and I think you'll find that the simple straightforward method is, at the least, no slower than any other. That plus simplicity makes it the one I'd go with.
Here's the code I used:
// init
var clip:MovieClip = new MovieClip();
clip.graphics.lineStyle( 10 );
clip.graphics.moveTo( 12.345, 37.123 ); // arbitrary
clip.graphics.lineTo( 45.678, 29.456 ); // arbitrary
clip.scaleX = .87; // arbitrary
clip.scaleY = 1.12; // arbitrary
clip.rotation = 47.123; // arbitrary
// run the test
var iterations:int = 1000000;
test( method1, iterations );
test( method2, iterations );
test( method3, iterations );
function test( fcn:Function, iter:int ) {
var t0:uint = getTimer();
for (var i:int=0; i<iter; i++) {
fcn( clip, i==0 );
}
trace(["Elapsed time", getTimer()-t0]);
}
// the "simple" method
function method1( m:MovieClip, traceSize:Boolean ) {
var rot:Number = m.rotation;
m.rotation = 0;
var w:Number = m.width;
var h:Number = m.height;
m.rotation = rot;
if (traceSize) { trace([ "method 1", w, h ]); }
}
// the "algebraic" method
function method2( m:MovieClip, traceSize:Boolean ) {
var r:Number = m.rotation * Math.PI/180;
var c:Number = Math.abs( Math.cos( r ) );
var s:Number = Math.abs( Math.sin( r ) );
var denominator:Number = (c*c - s*s); // an optimization
var w:Number = (m.width * c - m.height * s) / denominator;
var h:Number = (m.height * c - m.width * s) / denominator;
if (traceSize) { trace([ "method 2", w, h ]); }
}
// the "getBounds" method
function method3( m:MovieClip, traceSize:Boolean ) {
var r:Rectangle = m.getBounds(m);
var w:Number = r.width*m.scaleX;
var h:Number = r.height*m.scaleY;
if (traceSize) { trace([ "method 3", w, h ]); }
}
And my output:
method 1,37.7,19.75
Elapsed time,1416
method 2,37.74191378925391,19.608455916982187
Elapsed time,1703
method 3,37.7145,19.768000000000004
Elapsed time,1589
Surprising, eh? But there's an important lesson here about Flash development. I hereby christen Fen's Law of Flash Laziness:
Whenever possible, avoid tricky math by getting the renderer to do it for you.
It not only gets you done quicker, in my experience it usually results in a performance win anyway. Happy optimizing!
Here's the algorithmic approach, and its derivation.
First, let's do the opposite problem: Given a rectangle of unrotated width w, unrotated height h, and rotation r, what is the rotated width and height?
wr = abs(sin(r)) * h + abs(cos(r)) * w
hr = abs(sin(r)) * w + abs(cos(r)) * h
Now, try the problem as given: Given a rectangle of rotated width wr, rotated height hr, and rotation r, what is the unrotated width and height?
We need to solve the above equations for h and w. Let c represent abs(cos(r)) and s represent abs(sin(r)). If my rusty algebra skills still work, then the above equations can be solved with:
w = (wr * c - hr * s) / (c2 - s2)
h = (hr * c - wr * s) / (c2 - s2)
You should get the bounds of your square in your object's coordinate space (which means no rotations).
e.g.
var b:Sprite = new Sprite();
b.graphics.lineStyle(0.1);
b.graphics.drawRect(0,0,100,100);
b.rotation = 10;
trace('global coordinate bounds: ' + b.getBounds(this));//prints global coordinate bounds: (x=-17.35, y=0, w=115.85, h=115.85);
trace('local coordinate bounds: ' + b.getBounds(b));//prints local coordinate bounds: (x=0, y=0, w=100, h=100)
HTH,
George
Chip's answer in code:
// convert degrees to radians
var r:Number = this.rotation * Math.PI/180;
// cos, c in the equation
var c:Number = Math.abs(Math.cos(r));
// sin, s in the equation
var s:Number = Math.abs(Math.sin(r));
// get the unrotated width
var w:Number = (this.width * c - this.height * s) / (Math.pow(c, 2) - Math.pow(s, 2));