I'm experimenting with drawing spheres in WebGL, and I can't fathom how to iterate in the y-direction so that the squares appear uniform.
I believe the function is perhaps logarithmic? I've been studying WGS84 and I can't grasp the answer, but I bet someone knows exactly how Google maps is creating the sphere.
I am drawing a sphere the most simplistic method, with polar logic with the poles on the y-axis. Since I am using polar logic, the y-direction is uniform, which causes the rectangles to change shape from -90 to 0 and again from 0 to 90:
function setGeometry () {
let vertices = []
for (let j = 0; j <= devisionCount; j++) {
let lat = j * Math.PI / devisionCount
let lat2 = (j + 1) * Math.PI / devisionCount
for (let i = 0; i <= (devisionCount * 2); i++) {
let lon = i * (Math.PI * 2) / (devisionCount * 2)
let lon2 = (i + 1) * (Math.PI * 2) / (devisionCount * 2)
vertices.push(radiusWide * Math.sin(lon) * Math.sin(lat)) // X
vertices.push(radiusTall * Math.cos(lat)) // Y
vertices.push(radiusWide * Math.cos(lon) * Math.sin(lat)) // Z
vertices.push(radiusWide * Math.sin(lon) * Math.sin(lat2)) // X
vertices.push(radiusTall * Math.cos(lat2)) // Y
vertices.push(radiusWide * Math.cos(lon) * Math.sin(lat2)) // Z
vertices.push(radiusWide * Math.sin(lon2) * Math.sin(lat)) // X
vertices.push(radiusTall * Math.cos(lat)) // Y
vertices.push(radiusWide * Math.cos(lon2) * Math.sin(lat)) // Z
}
}
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW)
return vertices.length
}
NOTE: obviously I can just do a triangle strip here, but I'm doing triangles so my fragment shader is simpler to quickly show off what I'm struggling with.
My attempts at a sphere:
What I am trying to replicate (Look carefully at the white lines, and notices it stays close to rectangular as we approach the poles.
Related
I'm developing a WebGL game where the user would use the orientation of the device to control the camera ie look around. From the three rotations of deviceorientation event, I converted them to Quaternions.
THe problem starts here: I need to have the user to hold the device horizontally. In this position, the beta angle will be used to turn around the vertical axis y from the user's perspective.
However, the beta angle only goes from -180 to 180, any number outside will cause the other axes to change, to get a valid rotation. Due to how my WebGL camera is handled (I'm using Unity Engine), I need to separate the axes to remap them to the Camera coordinate accordingly, and to offset the angles from the horizontal pose (adding 90 degrees to the Y axis for example, because the device is rolled back). I have to convert the quaternion back to 3 euler angles and control each camera axis through those.
Is there a way to solve this?
Here's what I use to get the device orientation from the web:
var degtorad = Math.PI / 180; // Degree-to-Radian conversion
function getQuaternion(alpha, beta, gamma) {
var _x = beta ? beta * degtorad : 0; // beta value
var _y = gamma ? gamma * degtorad : 0; // gamma value
var _z = alpha ? alpha * degtorad : 0; // alpha value
var cX = Math.cos(_x / 2);
var cY = Math.cos(_y / 2);
var cZ = Math.cos(_z / 2);
var sX = Math.sin(_x / 2);
var sY = Math.sin(_y / 2);
var sZ = Math.sin(_z / 2);
//
// ZXY quaternion construction.
//
var w = cX * cY * cZ - sX * sY * sZ;
var x = sX * cY * cZ - cX * sY * sZ;
var y = cX * sY * cZ + sX * cY * sZ;
var z = cX * cY * sZ + sX * sY * cZ;
return [w, x, y, z];
}
function GyroEvent(e) {
var quat = getQuaternion(e.alpha, e.beta, e.gamma + 90);
// w
unityInstance.SendMessage("Main Camera", "GetGyroW", quat[0]);
// x
unityInstance.SendMessage("Main Camera", "GetGyroX", quat[1]);
// y
unityInstance.SendMessage("Main Camera", "GetGyroY", quat[2]);
// z
unityInstance.SendMessage("Main Camera", "GetGyroZ", quat[3]);
}
window.addEventListener("deviceorientation", GyroEvent);
I am using p5.js to create a sphere
What I am interested in is getting the points coordinates of the shapes used to modelize the sphere.
Is it possible to do so?
Basically, I would like to get the series of points used to draw the triangles that modelize the sphere.
You can use the spherical coordinates (two angles and radius) to cartesian coordinates (x,y,z) conversion formula to compute points on a sphere:
(Image source: wikipedia)
If you think of the two angles as latitude(lat), longitude(lon) angles on our globe and a constant radius, in the JS you can look at this formula as:
var x = radius * cos(lat) * sin(lon);
var y = radius * sin(lat) * sin(lon);
var z = radius * cos(lon);
Here's a basic sketch to illustrate the idea:
var radius = 120;
var latDetail = 0.243;
var lonDetail = 0.15;
function setup() {
createCanvas(300, 300, WEBGL);
strokeWeight(9);
}
function draw() {
background(255);
orbitControl();
beginShape(POINTS);
// iterate through lat, lon angles (in radians)
for(var lat = 0; lat <= PI; lat += latDetail){
for(var lon = 0; lon <= TWO_PI; lon += lonDetail){
// for each sperical coordinate (lat, lon angles, radius)
// convert to cartesian (x, y, z)
var x = radius * cos(lat) * sin(lon);
var y = radius * sin(lat) * sin(lon);
var z = radius * cos(lon);
// render each point
vertex(x, y, z);
}
}
endShape();
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.1/p5.min.js"></script>
Have a play with the latDetail, lonDetail variables which define how dense/sparse the sphere's parallels/meridians will be.
Having a quick look online for UV Spheres, Daniel Sieger's Generating Spheres article is neat!
Even though the code is c++, the syntax is similar enough to understand:
// generate vertices per stack / slice
for (int i = 0; i < n_stacks - 1; i++)
{
auto phi = M_PI * double(i + 1) / double(n_stacks);
for (int j = 0; j < n_slices; j++)
{
auto theta = 2.0 * M_PI * double(j) / double(n_slices);
auto x = std::sin(phi) * std::cos(theta);
auto y = std::cos(phi);
auto z = std::sin(phi) * std::sin(theta);
mesh.add_vertex(Point(x, y, z));
}
}
Pretty much the same formula (withouth the radius scalar) and a counter to for the number of segments on each angle (instead of an angle increment).
Here's a p5.js port:
var radius = 120;
var uSegments = 12;
var vSegments = 12;
// sliders
var uSegmentsSlider;
var vSegmentsSlider;
function setup() {
createCanvas(300, 300, WEBGL);
strokeWeight(9);
uSegmentsSlider = createSlider(3, 36, 12, 1);
vSegmentsSlider = createSlider(3, 36, 12, 1);
uSegmentsSlider.position(10, 10);
vSegmentsSlider.position(10, 30);
createP('U').position(145, -3);
createP('V').position(145, 17);
}
function draw() {
// read slider values
uSegments = uSegmentsSlider.value();
vSegments = vSegmentsSlider.value();
background(255);
orbitControl();
beginShape(POINTS);
// iterate through u, v segments
for(var u = 0; u < uSegments; u++){
var phi = PI * (u + 1) / uSegments;
for(var v = 0; v < vSegments; v++){
var theta = TWO_PI * v / vSegments;
// for each sperical coordinate (lat, lon angles, radius)
// convert to cartesian (x, y, z)
var x = radius * cos(theta) * sin(phi);
var y = radius * sin(theta) * sin(phi);
var z = radius * cos(phi);
// render each point
vertex(x, y, z);
}
}
endShape();
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.1/p5.min.js"></script>
Update Turns out p5.Vector.fromAngles() does this for you.
e.g.
// iterate through u, v segments
for(var u = 0; u < uSegments; u++){
var phi = PI * (u + 1) / uSegments;
for(var v = 0; v < vSegments; v++){
var theta = TWO_PI * v / vSegments;
// for each sperical coordinate (lat, lon angles, radius)
// convert to cartesian (x, y, z)
let p = p5.Vector.fromAngles(phi, theta, radius);
// render each point
vertex(p.x, p.y, p.z);
}
}
The above isn't great because it's allocating a new p5.Vector on each call (not recommended in draw()), but hopefully the idea is illustrated and you can pre-calculate the points in setup() then simply render in draw()
I have a simple transform class to apply translations, scales and rotations on a div in any arbitrary order:
class TransformDiv{
constructor(div)
{
this.div = div;
this.translateX = 0;
this.translateY = 0;
this.scaleX = 1;
this.scaleY = 1;
this.shearX = 0;
this.shearY = 0;
}
translate(x, y)
{
this.translateX += x;
this.translateY += y;
this.setTransform();
}
scale(x, y, anchorX = 0, anchorY = 0)
{
this.scaleX *= x;
this.shearX *= x;
this.scaleY *= y;
this.shearY *= y;
this.translateX -= (this.translateX - anchorX) * (1 - x);
this.translateY -= (this.translateY - anchorY) * (1 - y);
this.setTransform();
}
rotate(rad, anchorX = 0, anchorY = 0)
{
let cos = Math.cos(rad);
let sin = Math.sin(rad);
// the composition of two successive rotations are additive
let newScaleX = this.scaleX * cos + this.shearX * sin;
let newShearX = this.scaleX * (-sin) + this.shearX * cos;
let newShearY = this.shearY * cos + this.scaleY * sin;
let newScaleY = this.shearY * (-sin) + this.scaleY * cos;
this.scaleX = newScaleX;
this.shearX = newShearX;
this.shearY = newShearY;
this.scaleY = newScaleY;
//rotation about an arbitrary point
let originX = (this.translateX - anchorX);
let originY = (this.translateY - anchorY);
this.translateX -= (originY * sin - originX * (cos - 1));
this.translateY -= (-originY * (cos - 1) - originX * sin);
this.setTransform();
}
setTransform()
{
this.div.style.transform = `matrix(${this.scaleX}, ${this.shearY}, ${this.shearX}, ${this.scaleY}, ${this.translateX}, ${this.translateY})`;
}
}
A problem arises when I wish to rotate after a non-uniform scale has been made.
Edit - Newer interactive example: https://codepen.io/manstie/pen/RwGGOmB
Here is the example I made:
https://jsfiddle.net/ft61q230/1/
In the example here:
div2.translate(100, 100);
div2.scale(2, 1, 100, 100);
div2.rotate(Math.PI / 2, 100, 100);
The expected result is for Test 1 Text and Test 2 Text to be the same length, as if you were rotating from the top left of the div clockwise 90 degrees; but as you can see the result is such that the rotation logic I am performing retains the scale on the world-space axis, so now Test 2 Text is twice as tall rather than twice as long.
Current outcome:
Desired outcome:
The current rotation logic is based on multiplying the existing transformation matrix that makes up rotation by another transformation matrix containing an angle to rotate by, but I realize it is not as simple as that and I am missing something to retain local-axial scale.
Thank you for your assistance.
Edit:
Was recommended DOMMatrix which does all this math for me, but it has the same problem, although there is some skew which I don't think is accurate:
https://jsfiddle.net/heqo7vrt/1/
The skew is caused by the scale function scaling it's local X axis while it is rotated, and then rotating after not keeping that local X axis scaling. Also, DOMMatrix translate function has the translations apply on its local axis which is not desired in my situation but if its rotate function worked as expected I would be able to use it.
I managed to fix it here:
Regular: https://jsfiddle.net/sbca61k5/
let newScaleX = cos * this.scaleX + sin * this.shearY;
let newShearX = cos * this.shearX + sin * this.scaleY;
let newShearY = -sin * this.scaleX + cos * this.shearY;
let newScaleY = -sin * this.shearX + cos * this.scaleY;
DOMMatrix version: https://jsfiddle.net/b36kqrsg/
this.matrix = new DOMMatrix([cos, sin, -sin, cos, 0, 0]).multiply(this.matrix);
// or
this.matrix = new DOMMatrix().rotate(deg).multiply(this.matrix);
The difference is to have the rotation matrix multiplied by the rest of the matrix to "add" it on, not the other way round:
[a c e] [cos -sin 0] [scx shy tx]
[b d f] = [sin cos 0] . [shx scy ty]
[0 0 1] [0 0 1] [0 0 1 ]
I'm unsure about the details of the anchor mathematics but the DOMMatrix version's anchor is relative to its own top left whereas the other is relative to the top left of the document.
From my interactive example the anchor maths does not work as after a multitude of rotations the objects get further away from the anchor origin.
https://codepen.io/manstie/pen/PoGXMed
On a HTML5 canvas object, I have to subtract a distance from a destination point, to give the final destination on the same line.
So, first I have calculated the distance between the source and target points, with the Pythagorean theorem, but my memories of Thales's theorem are too faulty to find the final point (on same line), with the right x and y attributes.
function getDistance (from, to){
return Math.hypot(to.x - from.x, to.y - from.y);
}
function getFinalTo (from, to, distanceToSubstract){
//with Pythagore we obtain the distance between the 2 points
var originalDistance = getDistance(from, to);
var finalDistance = originalDistance - distanceToSubstract;
//Now, I was thinking about Thales but all my tries are wrong
//Here some of ones, I need to get finalTo properties to draw an arrow to a node without
var finalTo = new Object;
finalTo.x = ((1 - finalDistance) * from.x) + (finalDistance * to.x);
finalTo.y = ((1 - finalDistance) * from.y) + (finalDistance * to.y);
return finalTo;
}
Indeed, the arrowhead be hidden by the round node that can be about 100 pixels of radius, so I try to get the final point.
Thanks a lot.
Regards,
Will depend on the line cap. For "butt" there is no change, for "round" and "square" you the line extends by half the width at each end
The following function shortens the line to fit depending on the line cap.
drawLine(x1,y1,x2,y2){
// get vector from start to end
var x = x2-x1;
var y = y2-y1;
// get length
const len = Math.hypot(x,y) * 2; // *2 because we want half the width
// normalise vector
x /= len;
y /= len;
if(ctx.lineCap !== "butt"){
// shorten both ends to fit the length
const lw = ctx.lineWidth;
x1 += x * lw;
y1 += y * lw;
x2 -= x * lw;
y2 -= y * lw;
}
ctx.beginPath()
ctx.lineTo(x1,y1);
ctx.lineTo(x2,y2);
ctx.stroke();
}
For miter joins the following answer will help https://stackoverflow.com/a/41184052/3877726
You can use simple proportion by distance ratio:
(I did not account for round cap)
ratio = finalDistance / originalDistance
finalTo.x = from.x + (to.x - from.x) * ratio;
finalTo.y = from.y + (to.y - from.y) * ratio;
Your approach was attempt to use linear interpolation, but you erroneously mixed distances (in pixels, meters etc) with ratios (dimensionless - is this term right?)
ratio = finalDistance / originalDistance
finalTo.x = ((1 - ratio) * from.x) + (ratio * to.x);
finalTo.y = ((1 - ratio) * from.y) + (ratio * to.y);
Note that both approaches is really the same formula.
I kinda have like a fountain of particles, but I want to make them ''explode'' making more of them where I click like a firework.
var nFireworks = 10000;
function initParticleSystem() {
var particlesData = [];
for (var i= 0; i < nFireworks; i++) {
// angulos del cono
var theta = Math.PI / 6.0 * Math.random();
var phi = 5.0 * Math.PI * Math.random();
// direccion
var x1 = Math.sin(theta) * Math.cos(phi) ;
var y1 = velocity;
var z1 = 0.0;
// velocidad
var alpha = Math.random();
var velocity = (1.4 * alpha) + (0.80 * (1.0 - alpha));
particlesData[i * 4 + 0] = x1 * velocity;
particlesData[i * 4 + 1] = y1 * velocity;
particlesData[i * 4 + 2] = z1 * velocity;
particlesData[i * 4 + 3] = i * 0.095;
}
}
Your code is a bit odd, it uses velocity before it defines it, and you don't actually show the step function or anything else, but hey, I'll give it a go.
Your code (probably) generates a cone of particles where they all move along y at a constant velocity, and the x velocity is spread randomly in a PI/6 wide cone. If you want your particles to spread out in all directions randomly I would suggest starting by changing it like this:
before your for loop, set velocity to a constant first instead of all that nonsense:
var velocity = 5;
Then, you want particles to move outwards from the point in all equally random x and y directions, so change your x and y values to:
var x1 = ((Math.random() - 0.5) * velocity) * 2;
var y1 = ((Math.random() - 0.5) * velocity) * 2;
to form particles where their x and y velocities are random between -velocity and +velocity
Then, I don't know why your code generates particle data in a single array like that, I would make it
particleData.push([x1,y1,z1,i]);
and then reference each particle that way, or a possibly less performant but much more readable:
particleData.push({x: x1, y: y1, z: z1, brightness: i]};
(I'm just going to guess that i is brightness there).
Good luck buddy, it's not really a WebGL question, it's just you asking someone how to write your code for you, but hopefully that helps.