Related
I found the following answer on Stackoverflow.
From what I understand, this is not the proper way to do it. The math is linear, however, the coordinates are mapped to a spherical surface. So what is the proper way to do it?
I have a function that calculates a midpoint, how can I alter it to accept a percentage as a parameter. In other words, find a midpoint that is between point 1 and 2 and is a percentage away from point 1...
middle_point(lat1, long1, lat2, long2) {
// Longitude difference.
var d_long = (long2 - long1) * Math.PI / 180;
// Convert to radians.
lat1 = lat1 * Math.PI / 180;
lat2 = lat2 * Math.PI / 180;
long1 = long1 * Math.PI / 180;
var b_x = Math.cos(lat2) * Math.cos(d_long);
var b_y = Math.cos(lat2) * Math.sin(d_long);
var lat3 = Math.atan2(Math.sin(lat1) + Math.sin(lat2), Math.sqrt((Math.cos(lat1) + b_x) * (Math.cos(lat1) + b_x) + b_y * b_y));
var long3 = long1 + Math.atan2(b_y, Math.cos(lat1) + b_x);
// Return result.
return [long3 * 180/Math.PI, lat3 * 180/Math.PI];
}
The answer is given here: http://www.movable-type.co.uk/scripts/latlong.html and here: http://www.edwilliams.org/avform.htm#Intermediate
Here is the function you need, with "perc" between 0 and 100:
intermediate_point(lat1, long1, lat2, long2, perc) {
// Normalize percentage
perc = perc / 100;
// Convert to radians.
lat1 = lat1 * Math.PI / 180;
lat2 = lat2 * Math.PI / 180;
long1 = long1 * Math.PI / 180;
long2 = long2 * Math.PI / 180;
// get angular distance between points
var d_lat = lat2 - lat1;
var d_lon = long2 - long1;
var a = Math.sin(d_lat/2) * Math.sin(d_lat/2) + Math.cos(lat1) *
Math.cos(lat2) * Math.sin(d_lon/2) * Math.sin(d_lat/2);
var d = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
var A = Math.sin((1-perc)*d) / Math.sin(d);
var B = Math.sin(perc*d) / Math.sin(d);
var x = A * Math.cos(lat1) * Math.cos(long1) + B * cos(lat2) * cos(long2);
var y = A * Math.cos(lat1) * Math.sin(long1) + B * cos(lat2) * sin(long2);
var z = A * Math.sin(lat1) + B * Math.sin(lat2);
var lat3 = Math.atan2(z, Math.sqrt(x*x + y*y));
var long3 = Math.atan2(y, x);
// Return result, normalising longitude to -180°...+180°
return [long3 * 180/Math.PI, (lat3 * 180/Math.PI + 540)%360-180];
}
I recently added a canvas element,random dots on sphere,in my page.It works great on PC but on mobile phones and tablets rendering is very slow.
How can I speed up the sphere and reduce lags?
Any help would be much appreciated.GitHub example
There is so much room for improvements.
In the render loop you have
for (var p of points) {
p = rotation.multiplyVector(p);
ctx.beginPath();
ctx.arc(p.x + c.width / 2, p.y + c.height / 2, 2, 0, 2 * Math.PI);
ctx.closePath();
ctx.fill();
}
The beginPath is not needed if you are rendering the same style over and over
ctx.beginPath();
for (var p of points) {
p = rotation.multiplyVector(p);
const x = p.x + c.width / 2;
const y = p.y + c.height / 2;
ctx.moveTo(x + 2, y)
ctx.arc(x, y, 2, 0, 2 * Math.PI);
}
ctx.fill();
Then the matrix vector multiply has needless vetting and needless memory assignment and object instantiations
You had
Matrix3.prototype.multiplyVector = function (vec) {
if (vec instanceof Vector3) {
var x = this.data[0 + 0 * 3] * vec.x + this.data[0 + 1 * 3] * vec.y + this.data[0 + 2 * 3] * vec.z;
var y = this.data[1 + 0 * 3] * vec.x + this.data[1 + 1 * 3] * vec.y + this.data[1 + 2 * 3] * vec.z;
var z = this.data[2 + 0 * 3] * vec.x + this.data[2 + 1 * 3] * vec.y + this.data[2 + 2 * 3] * vec.z;
return new Vector3(x, y, z);
}
}
Will this ever not happen if (vec instanceof Vector3) { ??? not in your code so why waste the CPU time doing it.
Then this.data[2 + 0 * 3] The optimiser may get this for you, but mobiles do not optimise as well as desktops and I am not sure if this will be picked up on. Also some browsers are slower when using indirect references this.data[?] is slower than data[?]
And creating a new vector with for each circle to immediately discard it is not at all memory friendly. You only need one object so pass it to the function to set.
Improved
Matrix3.prototype.multiplyVector = function (vec, retVec = new Vector3(0,0,0)) {
const d = this.data;
retVec.x = d[0] * vec.x + d[3] * vec.y + d[6] * vec.z;
retVec.y = d[1] * vec.x + d[4] * vec.y + d[7] * vec.z;
retVec.z = d[2] * vec.x + d[5] * vec.y + d[8] * vec.z;
return retVec;
};
Then in the loop
const rp = new Vector3(0,0,0);
ctx.beginPath();
for (var p of points) {
rotation.multiplyVector(p,rp);
const x = rp.x + c.width / 2;
const y = rp.y + c.height / 2;
ctx.moveTo(x + 2, y)
ctx.arc(x, y, 2, 0, 2 * Math.PI);
}
ctx.fill();
Both the Vector3 and Matrix3 objects are very memory wasteful which means CPU cycles you have no control over being used just to assign and delete when you should be reusing memory as shown above.
You have the Matrix rotate function to build the rotation that creates a new matrix to create a rotation matrix which then needs a new matrix to multiply. You create 6 full matrix objects just to get one matrix.
The rotate function is called with 2 of x,y,z as 0 meaning that many of the multiplications and additions are just getting zero or you end up adding omc + cos which equals 1 or you multiply by 1 making no change.
You have
Matrix3.rotate = function (angle, x, y, z) {
var result = new Matrix3();
result.setIdentity();
var cos = Math.cos(angle);
var sin = Math.sin(angle);
var omc = 1 - cos;
result.data[0 + 0 * 3] = x * omc + cos;
result.data[1 + 0 * 3] = y * x * omc + z * sin;
result.data[2 + 0 * 3] = x * z * omc - y * sin;
result.data[0 + 1 * 3] = x * y * omc - z * sin;
result.data[1 + 1 * 3] = y * omc + cos;
result.data[2 + 1 * 3] = y * z * omc + x * sin;
result.data[0 + 2 * 3] = x * z * omc + y * sin;
result.data[1 + 2 * 3] = y * z * omc - x * sin;
result.data[2 + 2 * 3] = z * omc + cos;
return result;
}
Create a rotation matrix by directly multiplying a matrix, you will need one for each axis.
Matrix3.prototype.rotateX = function(angle, result = new Matrix3()) {
const r = result.data;
const d = this.data;
const c = Math.cos(angle);
const s = Math.sin(angle));
const ns = -s;
r[0] = d[0]
r[1] = d[1] * c + d[2] * ns;
r[2] = d[1] * s + d[2] * c;
r[3] = d[3];
r[4] = d[4] * c + d[5] * ns;
r[5] = d[4] * s + d[5] * c;
r[6] = d[6];
r[7] = d[7] * c + d[8] * ns;
r[8] = d[7] * s + d[8] * c;
return result;
},
Do same for rotateY,rotateZ (each is different than above)
Instance matrix directly setting the identity rather than needing a second call.
function Matrix3() { this.data = [1,0,0,0,1,0,0,0,1] }
Set identity with
Matrix3.prototype.setIdentity = function () {
const d = this.data;
d.fill(0);
d[8] = d[4] = d[0] = 1;
}
Then in your loop function have access to two matrix objects.
const mat1 = new Matrix3();
const mat2 = new Matrix3();
const rp = new Vector3(0,0,0);
const MPI2 = 2 * Math.PI;
function loop(){
mat1.setIdentity();
mat1.rotateX(angle.x,mat2);
mat2.rotateY(angle.y,mat1);
mat1.rotateZ(angle.z,mat2);
// your text rendering in here
const cw = c.width / 2;
const ch = c.height / 2;
ctx.beginPath();
for (var p of points) {
mat2.multiplyVector(p,rp);
const x = rp.x + cw;
const y = rp.y + ch;
ctx.moveTo(x + 2, y)
ctx.arc(x, y, 2, 0, MPI2);
}
ctx.fill();
}
That will give you a little extra speed. Any other improvements will be browser / device specific.
UPDATE as requested in the comments the following snippet contains the shortened rotation matrix multiplication of rotation around the X,Y, andZ axis
// How I find the optimum matrix multiplication via
// eleminating a[?] * 0 = 0
// reducing a[?] * 1 = a[?]
//
// The following are the rotations for X,Y,Z as matrix
//-------------------
// rotate X
// 1 0 0
// 0 cos(r) sin(r)
// 0 -sin(r) cos(r)
//-------------------
// rotate Y
// cos(r) 0 sin(r)
// 0 1 0
// -sin(r) 0 cos(r)
//-------------------
// rotate Z
// cos(r) sin(r) 0
// -sin(r) cos(r) 0
// 0 0 1
// The matrix indexes
// [0][1][2]
// [3][4][5]
// [6][7][8]
// Using the indexs and multiply is c = a * b
// c[0] = a[0] * b[0] + a[1] * b[3] + a[2] * b[6]
// c[1] = a[0] * b[1] + a[1] * b[4] + a[2] * b[7]
// c[2] = a[0] * b[2] + a[1] * b[5] + a[2] * b[8]
// c[3] = a[3] * b[0] + a[4] * b[3] + a[5] * b[6]
// c[4] = a[3] * b[1] + a[4] * b[4] + a[5] * b[7]
// c[5] = a[3] * b[2] + a[4] * b[5] + a[5] * b[8]
// c[6] = a[6] * b[0] + a[7] * b[3] + a[8] * b[6]
// c[7] = a[6] * b[1] + a[7] * b[4] + a[8] * b[7]
// c[8] = a[6] * b[2] + a[7] * b[5] + a[8] * b[8]
// Then use the rotations matrix to find the zeros and ones
// EG rotate X b[1],b[2],b[3],b[6] are zero and b[0] is one
// c[0] = a[0] * 1 + a[1] * 0 + a[2] * 0
// c[1] = a[0] * 0 + a[1] * b[4] + a[2] * b[7]
// c[2] = a[0] * 0 + a[1] * b[5] + a[2] * b[8]
// c[3] = a[3] * 1 + a[4] * 0 + a[5] * 0
// c[4] = a[3] * 0 + a[4] * b[4] + a[5] * b[7]
// c[5] = a[3] * 0 + a[4] * b[5] + a[5] * b[8]
// c[6] = a[6] * 1 + a[7] * 0 + a[8] * 0
// c[7] = a[6] * 0 + a[7] * b[4] + a[8] * b[7]
// c[8] = a[6] * 0 + a[7] * b[5] + a[8] * b[8]
// then eliminate all the zero terms a[?] * 0 == 0 and
// remove the 1 from 1 * a[?] = a[?]
// c[0] = a[0]
// c[1] = a[1] * b[4] + a[2] * b[7]
// c[2] = a[1] * b[5] + a[2] * b[8]
// c[3] = a[3]
// c[4] = a[4] * b[4] + a[5] * b[7]
// c[5] = a[4] * b[5] + a[5] * b[8]
// c[6] = a[6]
// c[7] = a[7] * b[4] + a[8] * b[7]
// c[8] = a[7] * b[5] + a[8] * b[8]
// And you are left with the minimum calculations required to apply a particular rotation Or any other transform.
Matrix3.prototype.rotateX = function(angle, result = new Matrix3()) {
const r = result.data;
const d = this.data;
const c = Math.cos(angle);
const s = Math.sin(angle);
const ns = -s;
r[0] = d[0];
r[1] = d[1] * c + d[2] * ns;
r[2] = d[1] * s + d[2] * c;
r[3] = d[3];
r[4] = d[4] * c + d[5] * ns;
r[5] = d[4] * s + d[5] * c;
r[6] = d[6];
r[7] = d[7] * c + d[8] * ns;
r[8] = d[7] * s + d[8] * c;
return result;
}
Matrix3.prototype.rotateY = function(angle, result = new Matrix3()) {
const r = result.data;
const d = this.data;
const c = Math.cos(angle);
const s = Math.sin(angle);
const ns = -s;
r[0] = d[0] * c + d[2] * ns;
r[1] = d[1];
r[2] = d[0] * s + d[2] * c;
r[3] = d[3] * c + d[5] * ns;
r[4] = d[4];
r[5] = d[3] * s + d[5] * c;
r[6] = d[6] * c + d[8] * ns;
r[7] = d[7];
r[8] = d[6] * s + d[8] * c;
return result;
}
Matrix3.prototype.rotateZ = function(angle, result = new Matrix3()) {
const r = result.data;
const d = this.data;
const c = Math.cos(angle);
const s = Math.sin(angle);
const ns = -s;
r[0] = d[0] * c + d[1] * ns;
r[1] = d[0] * s + d[1] * c;
r[2] = d[2];
r[3] = d[3] * c + d[4] * ns;
r[4] = d[3] * s + d[4] * c;
r[5] = d[5];
r[6] = d[6] * c + d[7] * ns;
r[7] = d[6] * s + d[7] * c;
r[8] = d[8];
return result;
}
Using the geolocation plugin for apache cordova / phonegap
Originally from, DaveAlden + Chris Veness
I'm simply trying to sum up the deltaDistMetres. Have been working at it all day but can't figure it out. Tried the push and reduce method with arrays, for loops, try troubleshooting with isNaN(). I'm at your mercy stack overflow pros.
My understanding of deltaDistMetres is the distance between the current gps location and the one before. So summing them up would be the total distance correct?
Thanks for any help!
var currentUpdate, lastUpdate;
function onPositionUpdate(position) {
if (currentUpdate) lastUpdate = currentUpdate;
currentUpdate = {
position: new LatLon(position.coords.latitude, position.coords.longitude),
time: new Date()
};
if (!lastUpdate) return;
currentUpdate.deltaDistMetres = lastUpdate.position.distanceTo(currentUpdate.position) * 1000;
currentUpdate.deltaTimeSecs = (currentUpdate.time - lastUpdate.time) * 1000;
currentUpdate.speed = (currentUpdate.deltaDistMetres / currentUpdate.deltaTimeSecs);
currentUpdate.accelerationGPS = (currentUpdate.speed - lastUpdate.speed) / currentUpdate.deltaTimeSecs;
/* THIS IS MY LITTLE CODE
(I have an element id="log" in my html)
*/
var distanceTotal= 0;
var distanceChange = currentUpdate.deltaDistMetres;
distanceTotal += distanceChange;
document.getElementById("log").innerHTML = "Total distance " + distanceTotal + " m";
}
function onPositionError(error) {
console.log("Error: " + error.message);
}
$(document).on("deviceready", function () {
navigator.geolocation.watchPosition(onPositionUpdate, onPositionError, { frequency:3000, timeout: 30000, enableHighAccuracy: true });
});
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/* Latitude/longitude spherical geodesy formulae & scripts (c) Chris Veness 2002-2012 */
/* - www.movable-type.co.uk/scripts/latlong.html */
/* */
/* Sample usage: */
/* var p1 = new LatLon(51.5136, -0.0983); */
/* var p2 = new LatLon(51.4778, -0.0015); */
/* var dist = p1.distanceTo(p2); // in km */
/* var brng = p1.bearingTo(p2); // in degrees clockwise from north */
/* ... etc */
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/* Note that minimal error checking is performed in this example code! */
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/**
* Object LatLon: tools for geodetic calculations
*
* #requires Geo
*/
/**
* Creates a point on the earth's surface at the supplied latitude / longitude
*
* #constructor
* #param {Number} lat: latitude in degrees
* #param {Number} lon: longitude in degrees
* #param {Number} [radius=6371]: radius of earth if different value is required from standard 6,371km
*/
function LatLon(lat, lon, radius) {
if (typeof (radius) == 'undefined') radius = 6371; // earth's mean radius in km
this.lat = Number(lat);
this.lon = Number(lon);
this.radius = Number(radius);
}
/**
* Returns the distance from this point to the supplied point, in km
* (using Haversine formula)
*
* from: Haversine formula - R. W. Sinnott, "Virtues of the Haversine",
* Sky and Telescope, vol 68, no 2, 1984
*
* #this {LatLon} latitude/longitude of origin point
* #param {LatLon} point: latitude/longitude of destination point
* #param {Number} [precision=4]: number of significant digits to use for returned value
* #returns {Number} distance in km between this point and destination point
*/
LatLon.prototype.distanceTo = function (point, precision) {
// default 4 sig figs reflects typical 0.3% accuracy of spherical model
if (typeof precision == 'undefined') precision = 4;
var R = this.radius;
var φ1 = this.lat.toRadians(), λ1 = this.lon.toRadians();
var φ2 = point.lat.toRadians(), λ2 = point.lon.toRadians();
var Δφ = φ2 - φ1;
var Δλ = λ2 - λ1;
var a = Math.sin(Δφ / 2) * Math.sin(Δφ / 2) +
Math.cos(φ1) * Math.cos(φ2) *
Math.sin(Δλ / 2) * Math.sin(Δλ / 2);
var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
var d = R * c;
return d.toPrecisionFixed(Number(precision));
}
/**
* Returns the (initial) bearing from this point to the supplied point, in degrees
* see http://williams.best.vwh.net/avform.htm#Crs
*
* #this {LatLon} latitude/longitude of origin point
* #param {LatLon} point: latitude/longitude of destination point
* #returns {Number} initial bearing in degrees from North
*/
LatLon.prototype.bearingTo = function (point) {
var φ1 = this.lat.toRadians(), φ2 = point.lat.toRadians();
var Δλ = (point.lon - this.lon).toRadians();
var y = Math.sin(Δλ) * Math.cos(φ2);
var x = Math.cos(φ1) * Math.sin(φ2) -
Math.sin(φ1) * Math.cos(φ2) * Math.cos(Δλ);
var θ = Math.atan2(y, x);
return (θ.toDegrees() + 360) % 360;
}
/**
* Returns final bearing arriving at supplied destination point from this point; the final bearing
* will differ from the initial bearing by varying degrees according to distance and latitude
*
* #this {LatLon} latitude/longitude of origin point
* #param {LatLon} point: latitude/longitude of destination point
* #returns {Number} final bearing in degrees from North
*/
LatLon.prototype.finalBearingTo = function (point) {
// get initial bearing from supplied point back to this point...
var φ1 = point.lat.toRadians(), φ2 = this.lat.toRadians();
var Δλ = (this.lon - point.lon).toRadians();
var y = Math.sin(Δλ) * Math.cos(φ2);
var x = Math.cos(φ1) * Math.sin(φ2) -
Math.sin(φ1) * Math.cos(φ2) * Math.cos(Δλ);
var θ = Math.atan2(y, x);
// ... & reverse it by adding 180°
return (θ.toDegrees() + 180) % 360;
}
/**
* Returns the midpoint between this point and the supplied point.
* see http://mathforum.org/library/drmath/view/51822.html for derivation
*
* #this {LatLon} latitude/longitude of origin point
* #param {LatLon} point: latitude/longitude of destination point
* #returns {LatLon} midpoint between this point and the supplied point
*/
LatLon.prototype.midpointTo = function (point) {
var φ1 = this.lat.toRadians(), λ1 = this.lon.toRadians();
var φ2 = point.lat.toRadians();
var Δλ = (point.lon - this.lon).toRadians();
var Bx = Math.cos(φ2) * Math.cos(Δλ);
var By = Math.cos(φ2) * Math.sin(Δλ);
var φ3 = Math.atan2(Math.sin(φ1) + Math.sin(φ2),
Math.sqrt((Math.cos(φ1) + Bx) * (Math.cos(φ1) + Bx) + By * By));
var λ3 = λ1 + Math.atan2(By, Math.cos(φ1) + Bx);
λ3 = (λ3 + 3 * Math.PI) % (2 * Math.PI) - Math.PI; // normalise to -180..+180º
return new LatLon(φ3.toDegrees(), λ3.toDegrees());
}
/**
* Returns the destination point from this point having travelled the given distance (in km) on the
* given initial bearing (bearing may vary before destination is reached)
*
* see http://williams.best.vwh.net/avform.htm#LL
*
* #this {LatLon} latitude/longitude of origin point
* #param {Number} brng: initial bearing in degrees
* #param {Number} dist: distance in km
* #returns {LatLon} destination point
*/
LatLon.prototype.destinationPoint = function (brng, dist) {
var θ = Number(brng).toRadians();
var δ = Number(dist) / this.radius; // angular distance in radians
var φ1 = this.lat.toRadians();
var λ1 = this.lon.toRadians();
var φ2 = Math.asin(Math.sin(φ1) * Math.cos(δ) +
Math.cos(φ1) * Math.sin(δ) * Math.cos(θ));
var λ2 = λ1 + Math.atan2(Math.sin(θ) * Math.sin(δ) * Math.cos(φ1),
Math.cos(δ) - Math.sin(φ1) * Math.sin(φ2));
λ2 = (λ2 + 3 * Math.PI) % (2 * Math.PI) - Math.PI; // normalise to -180..+180º
return new LatLon(φ2.toDegrees(), λ2.toDegrees());
}
/**
* Returns the point of intersection of two paths defined by point and bearing
*
* see http://williams.best.vwh.net/avform.htm#Intersection
*
* #param {LatLon} p1: first point
* #param {Number} brng1: initial bearing from first point
* #param {LatLon} p2: second point
* #param {Number} brng2: initial bearing from second point
* #returns {LatLon} destination point (null if no unique intersection defined)
*/
LatLon.intersection = function (p1, brng1, p2, brng2) {
var φ1 = p1.lat.toRadians(), λ1 = p1.lon.toRadians();
var φ2 = p2.lat.toRadians(), λ2 = p2.lon.toRadians();
var θ13 = Number(brng1).toRadians(), θ23 = Number(brng2).toRadians();
var Δφ = φ2 - φ1, Δλ = λ2 - λ1;
var δ12 = 2 * Math.asin(Math.sqrt(Math.sin(Δφ / 2) * Math.sin(Δφ / 2) +
Math.cos(φ1) * Math.cos(φ2) * Math.sin(Δλ / 2) * Math.sin(Δλ / 2)));
if (δ12 == 0) return null;
// initial/final bearings between points
var θ1 = Math.acos((Math.sin(φ2) - Math.sin(φ1) * Math.cos(δ12)) /
(Math.sin(δ12) * Math.cos(φ1)));
if (isNaN(θ1)) θ1 = 0; // protect against rounding
var θ2 = Math.acos((Math.sin(φ1) - Math.sin(φ2) * Math.cos(δ12)) /
(Math.sin(δ12) * Math.cos(φ2)));
if (Math.sin(λ2 - λ1) > 0) {
θ12 = θ1;
θ21 = 2 * Math.PI - θ2;
} else {
θ12 = 2 * Math.PI - θ1;
θ21 = θ2;
}
var α1 = (θ13 - θ12 + Math.PI) % (2 * Math.PI) - Math.PI; // angle 2-1-3
var α2 = (θ21 - θ23 + Math.PI) % (2 * Math.PI) - Math.PI; // angle 1-2-3
if (Math.sin(α1) == 0 && Math.sin(α2) == 0) return null; // infinite intersections
if (Math.sin(α1) * Math.sin(α2) < 0) return null; // ambiguous intersection
//α1 = Math.abs(α1);
//α2 = Math.abs(α2);
// ... Ed Williams takes abs of α1/α2, but seems to break calculation?
var α3 = Math.acos(-Math.cos(α1) * Math.cos(α2) +
Math.sin(α1) * Math.sin(α2) * Math.cos(δ12));
var δ13 = Math.atan2(Math.sin(δ12) * Math.sin(α1) * Math.sin(α2),
Math.cos(α2) + Math.cos(α1) * Math.cos(α3))
var φ3 = Math.asin(Math.sin(φ1) * Math.cos(δ13) +
Math.cos(φ1) * Math.sin(δ13) * Math.cos(θ13));
var Δλ13 = Math.atan2(Math.sin(θ13) * Math.sin(δ13) * Math.cos(φ1),
Math.cos(δ13) - Math.sin(φ1) * Math.sin(φ3));
var λ3 = λ1 + Δλ13;
λ3 = (λ3 + 3 * Math.PI) % (2 * Math.PI) - Math.PI; // normalise to -180..+180º
return new LatLon(φ3.toDegrees(), λ3.toDegrees());
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/**
* Returns the distance from this point to the supplied point, in km, travelling along a rhumb line
*
* see http://williams.best.vwh.net/avform.htm#Rhumb
*
* #this {LatLon} latitude/longitude of origin point
* #param {LatLon} point: latitude/longitude of destination point
* #returns {Number} distance in km between this point and destination point
*/
LatLon.prototype.rhumbDistanceTo = function (point) {
var R = this.radius;
var φ1 = this.lat.toRadians(), φ2 = point.lat.toRadians();
var Δφ = φ2 - φ1;
var Δλ = Math.abs(point.lon - this.lon).toRadians();
// if dLon over 180° take shorter rhumb line across the anti-meridian:
if (Math.abs(Δλ) > Math.PI) Δλ = Δλ > 0 ? -(2 * Math.PI - Δλ) : (2 * Math.PI + Δλ);
// on Mercator projection, longitude gets increasing stretched by latitude; q is the 'stretch factor'
var Δψ = Math.log(Math.tan(φ2 / 2 + Math.PI / 4) / Math.tan(φ1 / 2 + Math.PI / 4));
// the stretch factor becomes ill-conditioned along E-W line (0/0); use empirical tolerance to avoid it
var q = Math.abs(Δψ) > 10e-12 ? Δφ / Δψ : Math.cos(φ1);
// distance is pythagoras on 'stretched' Mercator projection
var δ = Math.sqrt(Δφ * Δφ + q * q * Δλ * Δλ); // angular distance in radians
var dist = δ * R;
return dist.toPrecisionFixed(4); // 4 sig figs reflects typical 0.3% accuracy of spherical model
}
/**
* Returns the bearing from this point to the supplied point along a rhumb line, in degrees
*
* #this {LatLon} latitude/longitude of origin point
* #param {LatLon} point: latitude/longitude of destination point
* #returns {Number} bearing in degrees from North
*/
LatLon.prototype.rhumbBearingTo = function (point) {
var φ1 = this.lat.toRadians(), φ2 = point.lat.toRadians();
var Δλ = (point.lon - this.lon).toRadians();
// if dLon over 180° take shorter rhumb line across the anti-meridian:
if (Math.abs(Δλ) > Math.PI) Δλ = Δλ > 0 ? -(2 * Math.PI - Δλ) : (2 * Math.PI + Δλ);
var Δψ = Math.log(Math.tan(φ2 / 2 + Math.PI / 4) / Math.tan(φ1 / 2 + Math.PI / 4));
var θ = Math.atan2(Δλ, Δψ);
return (θ.toDegrees() + 360) % 360;
}
/**
* Returns the destination point from this point having travelled the given distance (in km) on the
* given bearing along a rhumb line
*
* #this {LatLon} latitude/longitude of origin point
* #param {Number} brng: bearing in degrees from North
* #param {Number} dist: distance in km
* #returns {LatLon} destination point
*/
LatLon.prototype.rhumbDestinationPoint = function (brng, dist) {
var δ = Number(dist) / this.radius; // angular distance in radians
var φ1 = this.lat.toRadians(), λ1 = this.lon.toRadians();
var θ = Number(brng).toRadians();
var Δφ = δ * Math.cos(θ);
var φ2 = φ1 + Δφ;
// check for some daft bugger going past the pole, normalise latitude if so
if (Math.abs(φ2) > Math.PI / 2) φ2 = φ2 > 0 ? Math.PI - φ2 : -Math.PI - φ2;
var Δψ = Math.log(Math.tan(φ2 / 2 + Math.PI / 4) / Math.tan(φ1 / 2 + Math.PI / 4));
var q = Math.abs(Δψ) > 10e-12 ? Δφ / Δψ : Math.cos(φ1); // E-W course becomes ill-conditioned with 0/0
var Δλ = δ * Math.sin(θ) / q;
var λ2 = λ1 + Δλ;
λ2 = (λ2 + 3 * Math.PI) % (2 * Math.PI) - Math.PI; // normalise to -180..+180º
return new LatLon(φ2.toDegrees(), λ2.toDegrees());
}
/**
* Returns the loxodromic midpoint (along a rhumb line) between this point and the supplied point.
* see http://mathforum.org/kb/message.jspa?messageID=148837
*
* #this {LatLon} latitude/longitude of origin point
* #param {LatLon} point: latitude/longitude of destination point
* #returns {LatLon} midpoint between this point and the supplied point
*/
LatLon.prototype.rhumbMidpointTo = function (point) {
var φ1 = this.lat.toRadians(), λ1 = this.lon.toRadians();
var φ2 = point.lat.toRadians(), λ2 = point.lon.toRadians();
if (Math.abs(λ2 - λ1) > Math.PI) λ1 += 2 * Math.PI; // crossing anti-meridian
var φ3 = (φ1 + φ2) / 2;
var f1 = Math.tan(Math.PI / 4 + φ1 / 2);
var f2 = Math.tan(Math.PI / 4 + φ2 / 2);
var f3 = Math.tan(Math.PI / 4 + φ3 / 2);
var λ3 = ((λ2 - λ1) * Math.log(f3) + λ1 * Math.log(f2) - λ2 * Math.log(f1)) / Math.log(f2 / f1);
if (!isFinite(λ3)) λ3 = (λ1 + λ2) / 2; // parallel of latitude
λ3 = (λ3 + 3 * Math.PI) % (2 * Math.PI) - Math.PI; // normalise to -180..+180º
return new LatLon(φ3.toDegrees(), λ3.toDegrees());
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/**
* Returns a string representation of this point; format and dp as per lat()/lon()
*
* #this {LatLon} latitude/longitude of origin point
* #param {String} [format]: return value as 'd', 'dm', 'dms'
* #param {Number} [dp=0|2|4]: number of decimal places to display
* #returns {String} comma-separated latitude/longitude
*/
LatLon.prototype.toString = function (format, dp) {
if (typeof format == 'undefined') format = 'dms';
return Geo.toLat(this.lat, format, dp) + ', ' + Geo.toLon(this.lon, format, dp);
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
// ---- extend Number object with methods for converting degrees/radians
/** Converts numeric degrees to radians */
if (typeof Number.prototype.toRadians == 'undefined') {
Number.prototype.toRadians = function () {
return this * Math.PI / 180;
}
}
/** Converts radians to numeric (signed) degrees */
if (typeof Number.prototype.toDegrees == 'undefined') {
Number.prototype.toDegrees = function () {
return this * 180 / Math.PI;
}
}
/**
* Formats the significant digits of a number, using only fixed-point notation (no exponential)
*
* #param {Number} precision: Number of significant digits to appear in the returned string
* #returns {String} A string representation of number which contains precision significant digits
*/
if (typeof Number.prototype.toPrecisionFixed == 'undefined') {
Number.prototype.toPrecisionFixed = function (precision) {
// use standard toPrecision method
var n = this.toPrecision(precision);
// ... but replace +ve exponential format with trailing zeros
n = n.replace(/(.+)e\+(.+)/, function (n, sig, exp) {
sig = sig.replace(/\./, ''); // remove decimal from significand
l = sig.length - 1;
while (exp-- > l) sig = sig + '0'; // append zeros from exponent
return sig;
});
// ... and replace -ve exponential format with leading zeros
n = n.replace(/(.+)e-(.+)/, function (n, sig, exp) {
sig = sig.replace(/\./, ''); // remove decimal from significand
while (exp-- > 1) sig = '0' + sig; // prepend zeros from exponent
return '0.' + sig;
});
return n;
}
}
/** Trims whitespace from string (q.v. blog.stevenlevithan.com/archives/faster-trim-javascript) */
if (typeof String.prototype.trim == 'undefined') {
String.prototype.trim = function () {
return String(this).replace(/^\s\s*/, '').replace(/\s\s*$/, '');
}
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
if (!window.console) window.console = { log: function () { } };
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/* Geodesy representation conversion functions (c) Chris Veness 2002-2012 */
/* - www.movable-type.co.uk/scripts/latlong.html */
/* */
/* Sample usage: */
/* var lat = Geo.parseDMS('51° 28′ 40.12″ N'); */
/* var lon = Geo.parseDMS('000° 00′ 05.31″ W'); */
/* var p1 = new LatLon(lat, lon); */
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
var Geo = {}; // Geo namespace, representing static class
/**
* Parses string representing degrees/minutes/seconds into numeric degrees
*
* This is very flexible on formats, allowing signed decimal degrees, or deg-min-sec optionally
* suffixed by compass direction (NSEW). A variety of separators are accepted (eg 3º 37' 09"W)
* or fixed-width format without separators (eg 0033709W). Seconds and minutes may be omitted.
* (Note minimal validation is done).
*
* #param {String|Number} dmsStr: Degrees or deg/min/sec in variety of formats
* #returns {Number} Degrees as decimal number
* #throws {TypeError} dmsStr is an object, perhaps DOM object without .value?
*/
Geo.parseDMS = function (dmsStr) {
if (typeof deg == 'object') throw new TypeError('Geo.parseDMS - dmsStr is [DOM?] object');
// check for signed decimal degrees without NSEW, if so return it directly
if (typeof dmsStr === 'number' && isFinite(dmsStr)) return Number(dmsStr);
// strip off any sign or compass dir'n & split out separate d/m/s
var dms = String(dmsStr).trim().replace(/^-/, '').replace(/[NSEW]$/i, '').split(/[^0-9.,]+/);
if (dms[dms.length - 1] == '') dms.splice(dms.length - 1); // from trailing symbol
if (dms == '') return NaN;
// and convert to decimal degrees...
switch (dms.length) {
case 3: // interpret 3-part result as d/m/s
var deg = dms[0] / 1 + dms[1] / 60 + dms[2] / 3600;
break;
case 2: // interpret 2-part result as d/m
var deg = dms[0] / 1 + dms[1] / 60;
break;
case 1: // just d (possibly decimal) or non-separated dddmmss
var deg = dms[0];
// check for fixed-width unseparated format eg 0033709W
//if (/[NS]/i.test(dmsStr)) deg = '0' + deg; // - normalise N/S to 3-digit degrees
//if (/[0-9]{7}/.test(deg)) deg = deg.slice(0,3)/1 + deg.slice(3,5)/60 + deg.slice(5)/3600;
break;
default:
return NaN;
}
if (/^-|[WS]$/i.test(dmsStr.trim())) deg = -deg; // take '-', west and south as -ve
return Number(deg);
}
/**
* Convert decimal degrees to deg/min/sec format
* - degree, prime, double-prime symbols are added, but sign is discarded, though no compass
* direction is added
*
* #private
* #param {Number} deg: Degrees
* #param {String} [format=dms]: Return value as 'd', 'dm', 'dms'
* #param {Number} [dp=0|2|4]: No of decimal places to use - default 0 for dms, 2 for dm, 4 for d
* #returns {String} deg formatted as deg/min/secs according to specified format
* #throws {TypeError} deg is an object, perhaps DOM object without .value?
*/
Geo.toDMS = function (deg, format, dp) {
if (typeof deg == 'object') throw new TypeError('Geo.toDMS - deg is [DOM?] object');
if (isNaN(deg)) return null; // give up here if we can't make a number from deg
// default values
if (typeof format == 'undefined') format = 'dms';
if (typeof dp == 'undefined') {
switch (format) {
case 'd': dp = 4; break;
case 'dm': dp = 2; break;
case 'dms': dp = 0; break;
default: format = 'dms'; dp = 0; // be forgiving on invalid format
}
}
deg = Math.abs(deg); // (unsigned result ready for appending compass dir'n)
switch (format) {
case 'd':
d = deg.toFixed(dp); // round degrees
if (d < 100) d = '0' + d; // pad with leading zeros
if (d < 10) d = '0' + d;
dms = d + '\u00B0'; // add º symbol
break;
case 'dm':
var min = (deg * 60).toFixed(dp); // convert degrees to minutes & round
var d = Math.floor(min / 60); // get component deg/min
var m = (min % 60).toFixed(dp); // pad with trailing zeros
if (d < 100) d = '0' + d; // pad with leading zeros
if (d < 10) d = '0' + d;
if (m < 10) m = '0' + m;
dms = d + '\u00B0' + m + '\u2032'; // add º, ' symbols
break;
case 'dms':
var sec = (deg * 3600).toFixed(dp); // convert degrees to seconds & round
var d = Math.floor(sec / 3600); // get component deg/min/sec
var m = Math.floor(sec / 60) % 60;
var s = (sec % 60).toFixed(dp); // pad with trailing zeros
if (d < 100) d = '0' + d; // pad with leading zeros
if (d < 10) d = '0' + d;
if (m < 10) m = '0' + m;
if (s < 10) s = '0' + s;
dms = d + '\u00B0' + m + '\u2032' + s + '\u2033'; // add º, ', " symbols
break;
}
return dms;
}
/**
* Convert numeric degrees to deg/min/sec latitude (suffixed with N/S)
*
* #param {Number} deg: Degrees
* #param {String} [format=dms]: Return value as 'd', 'dm', 'dms'
* #param {Number} [dp=0|2|4]: No of decimal places to use - default 0 for dms, 2 for dm, 4 for d
* #returns {String} Deg/min/seconds
*/
Geo.toLat = function (deg, format, dp) {
var lat = Geo.toDMS(deg, format, dp);
return lat == null ? '–' : lat.slice(1) + (deg < 0 ? 'S' : 'N'); // knock off initial '0' for lat!
}
/**
* Convert numeric degrees to deg/min/sec longitude (suffixed with E/W)
*
* #param {Number} deg: Degrees
* #param {String} [format=dms]: Return value as 'd', 'dm', 'dms'
* #param {Number} [dp=0|2|4]: No of decimal places to use - default 0 for dms, 2 for dm, 4 for d
* #returns {String} Deg/min/seconds
*/
Geo.toLon = function (deg, format, dp) {
var lon = Geo.toDMS(deg, format, dp);
return lon == null ? '–' : lon + (deg < 0 ? 'W' : 'E');
}
/**
* Convert numeric degrees to deg/min/sec as a bearing (0º..360º)
*
* #param {Number} deg: Degrees
* #param {String} [format=dms]: Return value as 'd', 'dm', 'dms'
* #param {Number} [dp=0|2|4]: No of decimal places to use - default 0 for dms, 2 for dm, 4 for d
* #returns {String} Deg/min/seconds
*/
Geo.toBrng = function (deg, format, dp) {
deg = (Number(deg) + 360) % 360; // normalise -ve values to 180º..360º
var brng = Geo.toDMS(deg, format, dp);
return brng == null ? '–' : brng.replace('360', '0'); // just in case rounding took us up to 360º!
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
if (!window.console) window.console = { log: function () { } };
You have placed the declaration var distanceTotal= 0; inside onPositionUpdate() which means it will be reset to zero on every position update.
To preserve the previous value, declare it outside of onPositionUpdate:
var currentUpdate, lastUpdate;
var distanceTotal= 0;
function onPositionUpdate(position) {
if (currentUpdate) lastUpdate = currentUpdate;
currentUpdate = {
position: new LatLon(position.coords.latitude, position.coords.longitude),
time: new Date()
};
if (!lastUpdate) return;
currentUpdate.deltaDistMetres = lastUpdate.position.distanceTo(currentUpdate.position) * 1000;
currentUpdate.deltaTimeSecs = (currentUpdate.time - lastUpdate.time) * 1000;
currentUpdate.speed = (currentUpdate.deltaDistMetres / currentUpdate.deltaTimeSecs);
currentUpdate.accelerationGPS = (currentUpdate.speed - lastUpdate.speed) / currentUpdate.deltaTimeSecs;
/* THIS IS MY LITTLE CODE
(I have an element id="log" in my html)
*/
var distanceChange = currentUpdate.deltaDistMetres;
distanceTotal += distanceChange;
document.getElementById("log").innerHTML = "Total distance " + distanceTotal + " m";
}
function onPositionError(error) {
console.log("Error: " + error.message);
}
$(document).on("deviceready", function () {
navigator.geolocation.watchPosition(onPositionUpdate, onPositionError, { frequency:3000, timeout: 30000, enableHighAccuracy: true });
});
I like to get some points from bezier curve.I found
Find all the points of a cubic bezier curve in javascript
Position is easy. First, compute the blending functions. These control the "effect" of your control points on the curve.
B0_t = (1-t)^3
B1_t = 3 * t * (1-t)^2
B2_t = 3 * t^2 * (1-t)
B3_t = t^3
Notice how B0_t is1 when t is 0 (and everything else is zero). Also, B3_t is 1 when t is 1 (and everything else is zero). So the curve starts at (ax, ay), and ends at (dx, dy).
Any intermediate point (px_t, py_t) will be given by the following (vary t from 0 to 1, in small increments inside a loop):
px_t = (B0_t * ax) + (B1_t * bx) + (B2_t * cx) + (B3_t * dx)
py_t = (B0_t * ay) + (B1_t * by) + (B2_t * cy) + (B3_t * dy)
My code
var ax = 100, ay = 250;
var bx = 150, by = 100;
var cx = 350, cy = 100;
var dx = 400, dy = 250;
ctx.lineWidth = 1;
ctx.strokeStyle = "#333";
ctx.beginPath();
ctx.moveTo(ax, ay);
ctx.bezierCurveTo(bx, by, cx, cy, dx, dy);
ctx.stroke();
var t = 0
var B0_t = (1 - t) ^ 3
var B1_t = 3 * t * (1 - t) ^ 2
var B2_t = 3 * t ^ 2 * (1 - t)
var B3_t = t ^ 3
// override manually *Notice* above
//This is work first and laste point in curve
// B0_t = 1; B1_t = 0; B2_t = 0; B3_t = 0; t = 0;
// B0_t = 0; B1_t = 0; B2_t = 0; B3_t = 1; t = 1;
var px_t = (B0_t * ax) + (B1_t * bx) + (B2_t * cx) + (B3_t * dx)
var py_t = (B0_t * ay) + (B1_t * by) + (B2_t * cy) + (B3_t * dy)
// doesnt work
var t = 0
var B0_t = (1 - t) ^ 3 //*Notice* above should be 1
//Debug (1 - t) ^ 3 = 2 ??
var B1_t = 3 * t * (1 - t) ^ 2 //*Notice* above should be 0
//Debug 3 * t * (1 - t) ^ 2 = 2 ??
var B2_t = 3 * t ^ 2 * (1 - t)//*Notice* above should be 0
//Debug 3 * t ^ 2 * (1 - t) =2 ??
var B3_t = t ^ 3//*Notice* above should be 0 but its 2
//Debug t ^ 3 = 3 ??
var px_t = (B0_t * ax) + (B1_t * bx) + (B2_t * cx) + (B3_t * dx)
var py_t = (B0_t * ay) + (B1_t * by) + (B2_t * cy) + (B3_t * dy)
Appreciate any help thanks
How to find the pixels along a Bezier Curve
This set of functions will find an [x,y] point at interval T along cubic Bezier curve where 0<=T<=1.
In simple terms: It plots points along a cubic Bezier curve from start to end.
// Given the 4 control points on a Bezier curve
// get x,y at interval T along the curve (0<=T<=1)
// The curve starts when T==0 and ends when T==1
function getCubicBezierXYatPercent(startPt, controlPt1, controlPt2, endPt, percent) {
var x = CubicN(percent, startPt.x, controlPt1.x, controlPt2.x, endPt.x);
var y = CubicN(percent, startPt.y, controlPt1.y, controlPt2.y, endPt.y);
return ({
x: x,
y: y
});
}
// cubic helper formula
function CubicN(T, a, b, c, d) {
var t2 = T * T;
var t3 = t2 * T;
return a + (-a * 3 + T * (3 * a - a * T)) * T + (3 * b + T * (-6 * b + b * 3 * T)) * T + (c * 3 - c * 3 * T) * t2 + d * t3;
}
You can fetch the points along the curve by sending the plotting function a large number of T values between 0.00 & 1.00.
Example code and a demo:
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
var cBez1=[{x:250,y: 120},{x:290,y:-40},{x:300,y:200},{x:400,y:150}]
drawBez(cBez1);
var cPoints=findCBezPoints(cBez1);
drawPlots(cPoints);
function findCBezPoints(b){
var startPt=b[0];
var controlPt1=b[1];
var controlPt2=b[2];
var endPt=b[3];
var pts=[b[0]];
var lastPt=b[0];
var tests=5000;
for(var t=0;t<=tests;t++){
// calc another point along the curve
var pt=getCubicBezierXYatT(b[0],b[1],b[2],b[3], t/tests);
// add the pt if it's not already in the pts[] array
var dx=pt.x-lastPt.x;
var dy=pt.y-lastPt.y;
var d=Math.sqrt(dx*dx+dy*dy);
var dInt=parseInt(d);
if(dInt>0 || t==tests){
lastPt=pt;
pts.push(pt);
}
}
return(pts);
}
// Given the 4 control points on a Bezier curve
// Get x,y at interval T along the curve (0<=T<=1)
// The curve starts when T==0 and ends when T==1
function getCubicBezierXYatT(startPt, controlPt1, controlPt2, endPt, T) {
var x = CubicN(T, startPt.x, controlPt1.x, controlPt2.x, endPt.x);
var y = CubicN(T, startPt.y, controlPt1.y, controlPt2.y, endPt.y);
return ({
x: x,
y: y
});
}
// cubic helper formula
function CubicN(T, a, b, c, d) {
var t2 = T * T;
var t3 = t2 * T;
return a + (-a * 3 + T * (3 * a - a * T)) * T + (3 * b + T * (-6 * b + b * 3 * T)) * T + (c * 3 - c * 3 * T) * t2 + d * t3;
}
function drawPlots(pts){
ctx.fillStyle='red';
// don't draw the last dot b/ its radius will display past the curve
for(var i=0;i<pts.length-1;i++){
ctx.beginPath();
ctx.arc(pts[i].x,pts[i].y,1,0,Math.PI*2);
ctx.fill();
}
}
function drawBez(b){
ctx.lineWidth=7;
ctx.beginPath();
ctx.moveTo(b[0].x,b[0].y);
ctx.bezierCurveTo(b[1].x,b[1].y, b[2].x,b[2].y, b[3].x,b[3].y);
ctx.stroke();
}
body{ background-color: ivory; }
#canvas{border:1px solid red; margin:0 auto; }
<h4>Black line is context.bezierCurveTo<br>Red "line" is really dot-points plotted along the curve</h4>
<canvas id="canvas" width=500 height=300></canvas>
So, I'm converting some c++ to javascript and I really need to know how D3DX defines their quaternion operators.
//Here's the c++ version
D3DXQUATERNION Qvel = 0.5f * otherQuat* D3DXQUATERNION(angVel.x, angVel.y, angVel.z, 0);
//Here's the js quat multiplication
function quat_mul(q1, q2) {
return
[q1.x * q2.w + q1.y * q2.z - q1.z * q2.y + q1.w * q2.x,
-q1.x * q2.z + q1.y * q2.w + q1.z * q2.x + q1.w * q2.y,
q1.x * q2.y - q1.y * q2.x + q1.z * q2.w + q1.w * q2.z,
-q1.x * q2.x - q1.y * q2.y - q1.z * q2.z + q1.w * q2.w]
Is the scalar operation quat * 0.5f just like this?
quat.x *= .5;
quat.y *= .5;
quat.z *= .5;
quat.w *= .5;
According to this link, it's like you say (and also in the quaternion number system):
inline D3DXQUATERNION& D3DXQUATERNION::operator *= (FLOAT f)
{
x *= f;
y *= f;
z *= f;
w *= f;
return *this;
}
This is a very old question but I can verify this from a published source: Vince 2011 - Quaternion for Computer Graphics, page 57-58.
In simpler form:
q = [s, v] where s \in R and v \in R^3
\lambda q = [\lambda s, \lambda v] where \lambda \in R
In more complex notation:
q = a + bi + cj + dk
q * s = s * a + bi * s + cj * s + dk * s
= s * a + (b * s)i + (c * s)j + (d * s)k
in javascript
let quat = [0.4, 0.3, 0.7, 0.1];
function quaternion_scalar_multiplication(quat, s){
quat[0] *= s;
quat[1] *= s;
quat[2] *= s;
quat[3] *= s;
return quat;
}