Access compass data in Mobile Web / HTML5 - javascript

I'm making AR Mobile Web App, and my current goal is to show some events on the camera screen. For example, if there is fire in direction north from me, I want to point phone to north, and fire logo should be visible there.
My problem is - how can I get the absolute compass data?
I've tried this JavaScript code:
function compassHeading(alpha, beta, gamma) {
var dataContainerMotion = document.getElementById('dataContainerMotion');
// Convert degrees to radians
var alphaRad = alpha * (Math.PI / 180);
var betaRad = beta * (Math.PI / 180);
var gammaRad = gamma * (Math.PI / 180);
// Calculate equation components
var cA = Math.cos(alphaRad);
var sA = Math.sin(alphaRad);
var cB = Math.cos(betaRad);
var sB = Math.sin(betaRad);
var cG = Math.cos(gammaRad);
var sG = Math.sin(gammaRad);
// Calculate A, B, C rotation components
var rA = - cA * sG - sA * sB * cG;
var rB = - sA * sG + cA * sB * cG;
var rC = - cB * cG;
// Calculate compass heading
var compassHeading = Math.atan(rA / rB);
// Convert from half unit circle to whole unit circle
if(rB < 0) {
compassHeading += Math.PI;
}else if(rA < 0) {
compassHeading += 2 * Math.PI;
}
// Convert radians to degrees
compassHeading *= 180 / Math.PI;
return compassHeading;
}
window.addEventListener('deviceorientation', function(evt) {
heading = compassHeading(alpha, beta, gamma);
dataContainerMotion.innerHTML = heading;
}, false);
But variable absolute shows false, and I can't get absolute value. Compass is calibrated according to the positioning of the phone on initialization of the page - not according to actual position of the North on the Earth.
I would appreciate any kind of help - I'm stuck here and I can't go further without getting the compass data.

You have not mentioned the mobile device you have tested with. There are some major "problems" you have to consider:
Alpha on iOS (iPhone, iPad) devices has the value = 0 from where you
start/activate the deviceorientation event. If the phone is currently
heading to East then Alpha is 0 at East. If your phone is heading to
W then Alpha is 0 at West, etc.
There is no way to access the implemented hardware compass in Android
devices (by Javascript). Some browsers (versions) handle the
calculated values alpha, beta and gamma different, so you might have
to add/distract a specific angle value. This is also mentioned in the
MDN Web Docs
Chrome (50+) supports the event "ondeviceorientationabsolute" that
might your work easier (at least for Chrome)
Solution for iOS devices:
window.addEventListener('deviceorientation', function(event) {
if (event.webkitCompassHeading) {
// You may consider adding/distracting landscape/portrait mode value here
alpha = event.webkitCompassHeading;
if (alpha < 0) { alpha += 360; }
if (alpha > 360) { alpha -= 360; }
}
}
Solution for Android devices:
Use alpha/beta/gamma values (as you did). There are already many good solutions around. The "best" (imho) can be found here represented by the TILT compass: mentioned here or directly on GitHub
Considering your code (I haven't checked in detail) is that it won't working properly on iOS devices and probably not even on all Android devices. So all over use a construct like Frosty Z mention in the link posted above.
Sadly there is no 100% reliable code around covering all types of devices and browsers. I still hope something written here might help.

Related

Three.js OrthographicCamera -- Zoom to cursor

My Three.js project uses and OrthographicCamera and OrthographicTrackBallControls for zoom/pan. I'm trying to add functionality to zoom to the cursor position with no luck. First things first, here's how I'm getting mouse position:
var mX = ((event.clientX - offset.left) / renderer.domElement.clientWidth) * 2 - 1;
var mY = -((event.clientY - offset.top) / renderer.domElement.clientHeight) * 2 + 1;
var vector = new THREE.Vector3(mX, mY, 0.5);
vector.unproject(camera);
vector.sub(camera.position);
Through looking on StackOverflow, there seems to be a lot of information on how to do this with PerspectiveCamera, but these methods don't work with OrthographicCamera. I was able to find this example:
https://htmlpreview.github.io/?https://github.com/w3dot0/three.js/blob/973bf1d40ef552dbf19c19654a79f70e2882563d/examples/misc_controls_zoom_to_mouse.html
Which does precisely what I am trying to accomplish, but the code that achieves this is hidden, though I am able to discern that the camera position is being changed.
Another SO question which is similar suggests changing camera.left, camera.right, camera.top and camera.bottom, but I have had no luck with this approach. This approach seems like a possibility, but I dont understand the calculations necessary to get the correct left, right, top and bottom values.
So the way I see it I have two possibilities:
Change camera's left/right/top/bottom to get the correct view rectangle.
Change camera position.
But I don't know how to get the values I need to accomplish either, let alone which is the better approach.
UPDATE 11/16/2018:
I've updated my function to this ( based on https://github.com/w3dot0/three.js/blob/973bf1d40ef552dbf19c19654a79f70e2882563d/examples/misc_controls_zoom_to_mouse.html):
zoomDirection = new THREE.Vector3();
function mousewheel(event) {
event.preventDefault();
var amount = event.deltaY / 100;
var zoom = camera.zoom - amount;
var offset = el.offset();
;
var mX = amount > 0 ? 0 : ((event.clientX - offset.left) / renderer.domElement.clientWidth) * 2 - 1;
var mY = amount > 0 ? 0 : -((event.clientY - offset.top) / renderer.domElement.clientHeight) * 2 + 1;
zoomDirection.set(mX, mY, 0.001)
.unproject(camera)
.sub(camera.position)
.multiplyScalar(amount / zoom);
camera.position.subVectors(camera.position, zoomDirection);
orthographictrackBallControls.target.subVectors(orthographictrackBallControls.target, webGl.zoomDirection);
camera.zoom = zoom;
camera.updateProjectionMatrix();
}
This seems to work at first: the camera zooms into the mouse point, but then the camera starts to "jump" around after a bit of zooming, with the mesh no longer visible on screen.
Something that might help: I have an axis helper in the screen as well that "flips" when it stops working as expected. When the scene is loaded, the X-axis helper point due left, but when I get to the point where the camera jumps and I no longer see the mesh, the X-axis helper flips to point due right.
Also, if I zoom OUT first, I can zoom in further before the mesh disappears. I'm not sure what this all adds up to but I would appreciate any help.
First week back after New Year and it's taken too long to fix this. Six sides of A4 covered with linear algebra results in
if ( factor !== 1.0 && factor > 0.0 ) {
const mX = (event.offsetX / event.target.width ) * 2 - 1;
const mY = -(event.offsetY / event.target.height) * 2 + 1;
const vector1 = new THREE.Vector3(mX, mY, 0);
const vector2 = new THREE.Vector3(0, 0, 0);
vector1.unproject(this.camera);
vector2.unproject(this.camera);
vector1.subVectors(vector1, vector2);
this.camera.zoom /= factor;
vector1.multiplyScalar(factor - 1.0);
this.camera.position.subVectors(this.camera.position, vector1);
this.controls.target.subVectors(this.controls.target, vector1);
this.camera.updateProjectionMatrix();
this.camera.updateMatrix();
}
Note the different calculation of mX, mY so that it is valid for a viewport.
Implementing the D3-library with its zoom function may seem like a good idea for this case. But giving up the three-controls is in a lot of cases not a deal.
If you want a zoom-behavior like in Google Maps, the following code could be helpful:
const cameraPosition = camera.position.clone();
// my camera.zoom starts with 0.2
if (zoomOld !== 0.2) {
const xNew = this.curserVector.x + (((cameraPosition.x - this.curserVector.x) * camera.zoom) /zoomOld);
const yNew = this.curserVector.y + (((cameraPosition.y - this.curserVector.y) * camera.zoom) /zoomOld);
const diffX = cameraPosition.x - xNew;
const diffY = cameraPosition.y - yNew;
camera.position.x += diffX;
camera.position.y += diffY;
controls.target.x += diffX;
controls.target.y += diffY;
}
zoomOld = camera.zoom;
Your other problem could be caused by the frustum. But I don't know, I'm still a newbie with Three xD

Mapping latitude and longitude to Canvas points in JavaScript

I'm writing an AR application in which the user sees a ship from the perspective of the captain of the ship like this
I want to use coordinates of other ships (like the one on the left) to draw the ships on top of the image using Canvas. The problem I'm having is mapping the latitude, longitude to Canvas points.
I've read about the Haversine and Bearing formulas but I can't figure out how to use them to do what I want. I know the latitude and longitude of the ship the user is looking from and tried using them to calculate the Canvas points of the other ship, but I can't get it to work. Any ideas?
Finding real world object in camera's view.
This is the most simplistic answer possible, as there are many unknowns that will affect the result.
The problem
The image illustrates the problem as I understand it.
We have 3 ships (the minimum required to find a solution) marked with a red A,B,C. We know the latitude and longitude of each ship. As they are close there is no need to correct for any distortion due to converging longitudes.
The green lines represent the camera's view, the image is projected via a lens onto the CCD (green line above A) The twp gray lines then are projected onto the camera's screen below the ship.
Triangulation and triangles.
From the longitudes we can find the distance from each ship to the other, giving us the lengths of the sides of a triangle.
var ships = {
A : { lat : ?, long : ? },
B : { lat : ?, long : ? },
C : { lat : ?, long : ? },
}
var AB = Math.hypot(ships.A.lat - ships.B.lat, ships.A.long - ships.B.long);
var BC = Math.hypot(ships.C.lat - ships.B.lat, ships.C.long - ships.B.long);
var CA = Math.hypot(ships.A.lat - ships.C.lat, ships.A.long - ships.C.long);
The inset triangle shows how to find the angle of a corner given the lengths of the sides.
For this we are after the angle pheta
var pheta = Math.acos(BC * BC - AB * AB + CA * CA) / (-2 * AB * CA));
The camera's field of view.
Now we need an extra bit of information. That is the field of view of the camera (in image as the green lines and red FOV). You will have to find those details for the camera you are using.
A typical phone camera has a focal length (fl) from 24-35mm equivalent and knowing the CCD size in relative terms you can find the FOV with var FOV = 2 * Math.atan((CCD.width / 2) / fl) But this is problematic as a modern phone is under 10mm thick and the CCD is tiny, what are the actual dimensions?
There is a long and complicated process you can use to determine the FOV without knowing the internal dimensions of the camera, but easy to look up the phone specs.
For now let's assume the camera has a FOV of 67deg (1.17radians). If the camera has a resolution of 1280 and we ignore lens distortions, and keep the camera vertical and at the same level as the targets , we can calculate the distance in pixels between the two ships via the angle between them.
var FOV = 1.17; // radians the Field of View of the camera
var pixels = (1.17 / 1280) * pheta; // 1280 is horizontal resolution of display
So now we have the distance in pixels between the two ships on the camera. Assuming that they fit on the camera we are missing one more important bit of information.
Bearings
We need to know which way the camera is pointing as a bearing. Only when we have that can we find the ships. So lets assume the GPS on the phone gives you the live bearing. What we need is the bearing to one of the ships.
I had to dig deep into my archives to find this, It had no source or referance so can only provide as is. All it had is Haversin so assuming that is the method used.
After some further digging I found a referance to what is likely the original source code that this was derived from Calculate distance, bearing and more between Latitude/Longitude points
function deg2rad(angle) { return angle * 0.017453292519943295 }
function rad2deg(angle) { return angle / 0.017453292519943295 }
//return bearing in radians.
function getBearing(lat1,lon1,lat2,lon2){
var earth = 6371e3;
var lat1 = lat1.toRadians();
var lat2 = lat2.toRadians();
var lon1 = lon1.toRadians();
var lon2 = lon2.toRadians();
var latD = lat2-lat1;
var lonD = lon2-lon1;
var a = Math.sin(latD / 2) * Math.sin(latD / 2) + Math.cos(lat1 ) * Math.cos(lat2) * Math.sin(lonD / 2) * Math.sin(lonD / 2);
var c = 2 * Math.atan2( Math.sqrt(a), Math.sqrt(1-a) );
return earth * c;
}
So now you can get the bearing from you to one of the ships. Use that and your bearing to find the difference in angle between you and it.
var yourBearing = ?; // in radians
var shipBBearing = getBearing(ships.A.lat, ships.A.long, ships.B.lat, ships.B.long);
Now get the differance in angle
var shipBAt = yourBearing - shipBBearing
Knowing the pixel FOV
var shipBPixelsFromCenter = (FOV / 1280) * shipBAt;
var shipBXpos = 1280 / 2 - shipBPixelsFromCenter;
// and from above the dif in pixels between ships is
var shipCXpos = shipBXpos + pixels.
And done.

Detect phone rotation in Javascript Windows Phone mobile application

I am writing a mobile application for Windows Phone in HTML5. I want to detect the phone's rotation around the z-axis (I mean the rotation that is used for example in Doodle jump to make the character jump to the left or to the right).
In my application, a line that is vertical in the screen must remain vertical (absolute position in the real world) if I rotate the screen to the left or to the right. To accomplish this, I use the relative values of the angle: each time the screen is rotated a little, I rotate the points of the line by the same amount, but negative.
This works PERFECTLY if the phone is laying on a table or almost horizontal, but the behaviour is much less precise if for example I watch the phone in front of me. Anyway this is not a big problem of course because most users watch the phone from up to down, but a game like doodle jump behaves perfectly also in this situation.
Here is part of the code:
window.addEventListener("deviceorientation", handleOrientation, true);
function handleOrientation(event) {
alphaDiff = Math.floor(event.alpha) - alphaOld;
alphaOld = Math.floor(event.alpha);
//Rotation of the vertical line (x1,y1) - (x2,y2) around the center of the screen
var newPoint = rotate(225, 400, x1, y1, alphaDiff);
x1 = newPoint[0];
y1 = newPoint[1];
newPoint = rotate(225, 400, x2, y2, alphaDiff);
x2 = newPoint[0];
y2 = newPoint[1];
}
function rotate(cx, cy, x, y, angle) {
var radians = (Math.PI / 180) * angle;
cos = Math.cos(radians);
sin = Math.sin(radians);
nx = (cos * (x - cx)) - (sin * (y - cy)) + cx;
ny = (sin * (x - cx)) + (cos * (y - cy)) + cy;
return [nx, ny];
}
Have you tried beta (for the x-axis) or gamma (for the y-axis)?
e.g.
function handleOrientation(event) {
var absolute = event.absolute;
var alpha = event.alpha; // z-axis
var beta = event.beta; // x-axis
var gamma = event.gamma; // y-axis
// Do stuff with the new orientation data
}
Orientation Values Explained
The value reported for each axis indicates the amount of rotation around a given axis in reference to a standard coordinate frame.
The DeviceOrientationEvent.alpha value represents the motion of the device around the z-axis, represented in degrees with values ranging from 0 to 360.
The DeviceOrientationEvent.beta value represents the motion of the device around the x-axis, represented in degrees with values ranging from -180 to 180. This represents a front to back motion of the device.
The DeviceOrientationEvent.gamma value represents the motion of the device around the y-axis, represented in degrees with values ranging from -90 to 90. This represents a left to right motion of the device.
It also worth checking browser compatibility for this API.
As well as deviceorientation, you can check compassneedscalibration and devicemotion. For more details on deviceorientation see MDN.
A combination of beta and gamma seems to give pretty good results.
Basically, I normalize the values to a specific range and get the ratio.
window.addEventListener("deviceorientation", function(event){
var b = Math.abs(event.beta)/90;
if(b>1) b = 2-b;
var g = event.gamma/90;
if(Math.abs(event.beta)>90) g = -g;
var x = g/Math.max(0.25,b);
});
The final value is in x, positive means right, negative means left.
It works with the phone held horizontally or at any angle facing you.
Update: To make the detection work in a vertical position, the code calculates how much the phone is leaning and to which direction.
The value in x is always positive if the phone is leaning right and negative if leaning left.
This example shows a blue box in the middle of the screen and moves it to whichever direction the phone is leaning:
<!DOCTYPE html>
<html>
<head><meta name="viewport" content="width=1000,user-scalable=no"/></head>
<body style="margin:0">
<pre id="log" style="font-size:4em;"></pre>
<div id="box" style="background:#69c;width:100px;height:100px;position:relative;left:450px"></div>
<script>
var box = document.getElementById("box");
var log = document.getElementById("log");
var smoothx = 0;
window.addEventListener("deviceorientation", function(event){
var b = Math.abs(event.beta)/90;
if(b>1) b = 2-b;
var g = event.gamma/90;
if(Math.abs(event.beta)>90) g = -g;
var x = g/Math.max(0.25,b);
smoothx = smoothx*0.7+x*0.3;
box.style.left = Math.max(0, Math.min(900, smoothx*500+450))+"px";
log.innerText = x.toFixed(1)+"\n"+smoothx.toFixed(1);
});
</script>
</body>
</html>
Because the sensor seems to be very sensitive to movement, I implemented a simple smoothing that gives a steadier value in smoothx.
Use x for immediate value without smoothing.

Semi-Circular swipe scroll

i've been searching for this the last couple of weeks and i can't find an answer.
This is what i'm trying to accomplish ! Click to see the image.
I need to scroll through an infinite number of apps, on a touchscreen device.
The one in the middle needs to be bigger, and the rest smaller and smaller, to give the idea that there are more apps that are not visible and you need to touch and scroll though apps.
I'm trying to do this with Jquery, HTML5 and CSS3.
Can anybody help me, please?
I'm really stuck here..
Thank you very much!
I tried using this http://jsfiddle.net/67zMe/5/
But i had no luck making it semi-circular and based on touch screen scrolling action.
var radius = 300;
var angleSteps = 360 / $('#circle-list li').length;
var baseAngle = 0;
function updateListPositions()
{
$('#circle-list li').each(function(index, element)
{
var angle = baseAngle + (index * angleSteps);
var center = 150;
var distance = 100;
var x = distance * Math.cos(angle * (Math.PI / 180));
var y = distance * Math.sin(angle * (Math.PI / 180));
$(element).css({left:center+x, top:center+y});
});
}
var stepInterval = setInterval(stepAngle, 25);
function stepAngle()
{
baseAngle++;
updateListPositions();
}
Here is the javascript. It's the closest thing i found, to what i need.
The guy who posted this, had the same problem, and didn't find the answer he needed.

Canvas water/blob physics on a circular path with texture?

this is my first question after having relied on this site for years!
Anyway, I'd like to accomplish something similar to this effect:
http://www.flashmonkey.co.uk/html5/wave-physics/
But on a circular path, instead of a horizon. Essentially, a floating circle/blob in the center of the screen that would react to mouse interaction. What I'm not looking for is gravity, or for the circle to bounce around the screen - only surface ripples.
If at all possible I'd like to apply a static texture to the shape, is this a possibility? I'm completely new to Canvas!
I've already tried replacing some code from the above example with circular code from the following link, to very limited success:
http://www.html5canvastutorials.com/tutorials/html5-canvas-circles/
If only it were that easy :)
Any ideas?
Thanks in advance!
I tried to figure out how wave simulation works using View Source and JavaScript console. It's working fine but threw some JS errors. Also, it seems physics update is entangled with rendering in the render() method.
Here is what I found about the code:
The mouseMove() method creates disturbances on the wave based on mouse position, creating a peak around the mouse. The target variable is the index of the particle that needs to be updated, it's calculated from mouse pos.
if (particle && mouseY > particle.y) {
var speed = mouseY - storeY;
particles[target - 2].vy = speed / 6;
particles[target - 1].vy = speed / 5;
particles[target].vy = speed / 3;
particles[target + 1].vy = speed / 5;
particles[target + 2].vy = speed / 6;
storeY = mouseY;
}
Then, the particles around target are updated. The problem I found is that it does no bounds checking, i.e. it can potentially particles[-1] when target == 0. If that happens, an exception is thrown, the method call ends, but the code does not stop.
The render() method first updates the particle positions, then renders the wave.
Here is its physics code:
for (var u = particles.length - 1; u >= 0; --u) {
var fExtensionY = 0;
var fForceY = 0;
if (u > 0) {
fExtensionY = particles[u - 1].y - particles[u].y - springs[u - 1].iLengthY;
fForceY += -fK * fExtensionY;
}
if (u < particles.length - 1) {
fExtensionY = particles[u].y - particles[u + 1].y - springs[u].iLengthY;
fForceY += fK * fExtensionY;
}
fExtensionY = particles[u].y - particles[u].origY;
fForceY += fK / 15 * fExtensionY;
particles[u].ay = -fForceY / particles[u].mass;
particles[u].vy += particles[u].ay;
particles[u].ypos += particles[u].vy;
particles[u].vy /= 1.04;
}
Basically, it's Hooke's Law for a chain of particles linked by springs between them. For each particle u, it adds the attraction to the previous and next particles (the if statements check if they are available), to the variable fForceY. I don't fully understand the purpose of the springs array.
In the last four lines, it calculates the acceleration (force / mass), updates the velocity (add acceleration), then position (add velocity), and finally, reduce velocity by 1.04 (friction).
After the physics update, the code renders the wave:
context.clearRect(0, 0, stageWidth, stageHeight);
context.fillStyle = color;
context.beginPath();
for (u = 0; u < particles.length; u++) {
...
}
...
context.closePath();
context.fill();
I'm not explaining that, you need to read a canvas tutorial to understand it.
Here are some ideas to get started, note that I didn't test these code.
To modify the code to draw a circular wave, we need introduce a polar coordinate system, where the particle's x-position is the angle in the circle and y-position the distance from center. We should use theta and r here but it requires a large amount of refactoring. We will talk about transforming later.
mouseMove(): Compute particle index from mouse position on screen to polar coordinates, and make sure the disturbance wrap around:
Define the function (outside mouseMove(), we need this again later)
function wrapAround(i, a) { return (i + a.length) % a.length; }
Then change
particles[target - 2] --> particles[wrapAround(target - 2, particles)]
particles[target - 1] --> particles[wrapAround(target - 1, particles)]
...
The modulo operator does the job but I added particles.length so I don't modulo a negative number.
render(): Make sure the force calculation wrap around, so we need to wrapAround function again. We can strip away the two if statements:
fExtensionY = particles[wrapAround(u - 1, particles)].y - particles[u].y - springs[wrapAround(u - 1, springs)].iLengthY;
fForceY += -fK * fExtensionY;
fExtensionY = particles[u].y - particles[wrapAround(u + 1, particles)].y - springs[warpAround(u, springs)].iLengthY;
fForceY += fK * fExtensionY;
Here is the result so far in jsfiddle: Notice the wave propagate from the other side. http://jsfiddle.net/DM68M/
After that's done, the hardest part is rendering them on a circle. To do that, we need coordinate transform functions that treat particle's (x, y) as (angle in the circle, distance from center), and we also need inverse transforms for mouse interaction in mouseMove().
function particleCoordsToScreenCoords(particleX, particleY) {
return [ radiusFactor * particleY * Math.cos(particleX / angleFactor),
radiusFactor * particleY * Math.sin(particleX / angleFactor) ];
}
function screenCoordsToParticleCoords(screenX, screenY) {
// something involving Math.atan2 and Math.sqrt
}
Where the ...Factor variables needed to be determined separately. The angleFactor is two pi over the highest x-position found among particles array
Then, in the coordinates supplied to the context.lineTo, context.arc, use the particleCoordsToScreenCoords to transform the coordinates.

Categories