How can I create a WebGL basic firework - javascript

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.

Related

Transformation matrix rotation not preserving local axis scaling?

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

Arc sweep direction in two.js

two.js library has a function as follows;
makeArcSegment two.makeArcSegment(ox, oy, ir, or, sa, ea, res);
I calculate my parameters as follows;
var prevPt = points[i - 1];
var dxNext = nextPt.x - pt.x;
var dyNext = nextPt.y - pt.y;
var angleNext = Math.atan2(dyNext, dxNext);
var dxPrev = prevPt.x - pt.x;
var dyPrev = prevPt.y - pt.y;
var anglePrev = Math.atan2(dyPrev, dxPrev);
var innerRadius = 0;
var outerRadius = 20;
var arc = two.makeArcSegment(pt.x, pt.y, innerRadius, outerRadius, anglePrev, angleNext)
And my output is kind of unexpected.
At first node (after the line with the length of 225.46), sweep direction is counter clockwise.
At second node (after 142.35), sweep direction is clockwise.How do I force it to be always counter clockwise?
Thanks.
This is the workaround I found.
if (anglePrev < 0.0)
{
anglePrev += Math.PI * 2.0;
}
if (angleNext < 0.0)
{
angleNext += Math.PI * 2.0;
}
if (angleNext > anglePrev)
{
anglePrev += Math.PI * 2.0;
}
According to this if you make the arc.endAngle less than the arc.startAngle then it should go in the opposite direction..
So you can try to draw between min(prev,next) and max(prev,next) as workaround.
Also make all angles positive (adding 2*Pi to negative ones).
Looks like serious flaw of library though.

draw as sphere as uniform square tiles

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.

With HTML5 canvas, how to calculate the final point coordinates with an offset?

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.

Adding perspective to fake 3D animation

I'm working on a canvas-based animation, and I'm trying to get a 3D effect in a 2D canvas.
So far, things are going well! I've got my "orbiting line of triangles" working very well:
var c = document.createElement('canvas');
c.width = c.height = 100;
document.body.appendChild(c);
var ctx = c.getContext("2d");
function Triangles() {
this.rotation = {
x: Math.random()*Math.PI*2,
y: Math.random()*Math.PI*2,
z: Math.random()*Math.PI*2
};
/* Uncomment this for testing perspective...
this.rotation = {
x: Math.PI/2,
y: 0,
z: 0
};
*/
}
Triangles.prototype.draw = function(t) {
this.rotation.z += t/1000;
var i, points;
for( i=0; i<15; i++) {
points = [
this.computeRotation(Math.cos(0.25*i),-Math.sin(0.25*i),0),
this.computeRotation(Math.cos(0.25*(i+1)),-Math.sin(0.25*(i+1)),-0.1),
this.computeRotation(Math.cos(0.25*(i+1)),-Math.sin(0.25*(i+1)),0.1)
];
ctx.fillStyle = "black";
ctx.beginPath();
ctx.moveTo(50+40*points[0][0],50+40*points[0][1]);
ctx.lineTo(50+40*points[1][0],50+40*points[1][1]);
ctx.lineTo(50+40*points[2][0],50+40*points[2][1]);
ctx.closePath();
ctx.fill();
}
};
Triangles.prototype.computeRotation = function(x,y,z) {
var rz, ry, rx;
rz = [
Math.cos(this.rotation.z) * x - Math.sin(this.rotation.z) * y,
Math.sin(this.rotation.z) * x + Math.cos(this.rotation.z) * y,
z
];
ry = [
Math.cos(this.rotation.y) * rz[0] + Math.sin(this.rotation.y) * rz[2],
rz[1],
-Math.sin(this.rotation.y) * rz[0] + Math.cos(this.rotation.y) * rz[2]
];
rx = [
ry[0],
Math.cos(this.rotation.x) * ry[1] - Math.sin(this.rotation.x) * ry[2],
Math.sin(this.rotation.x) * ry[1] + Math.cos(this.rotation.x) * ry[2]
];
return rx;
};
var tri = new Triangles();
requestAnimationFrame(function(start) {
function step(t) {
var delta = t-start;
ctx.clearRect(0,0,100,100)
tri.draw(delta);
start = t;
requestAnimationFrame(step);
}
step(start);
});
As you can see it's using rotation matrices for calculating the position of the points after their rotation, and I'm using this to draw the triangles using the output x and y coordinates.
I want to take this a step further by using the z coordinate and adding perspective to this animation, which will make the triangles slightly bigger when in the foreground, and smaller when in the background. However, I'm not sure how to go about doing this.
I guess this is more of a maths question than a programming one, sorry about that!
Define a focal length to control the amount of perspective. The greater the value the less the amount of perspective. Then
var fl = 200; // focal length;
var px = 100; // point in 3D space
var py = 200;
var pz = 500;
Then to get the screen X,Y
var sx = (px * fl) / pz;
var sy = (py * fl) / pz;
The resulting point is relative to the center of the veiw so you need to center it to the canvas.
sx += canvas.width/2;
sy += canvas.height/2;
That is a point.
It assumes that the point being viewed is in front of the view and further than the focal length from the focal point.
I've managed to figure out a basic solution, but I'm sure there's better ones, so if you have a more complete answer feel free to add it! But for now...
Since the coordinate system is already based around the origin with the viewpoint directly on the Z axis looking at the (x,y) plane, it's actually sufficient to just multiply the (x,y) coordinates by a value proportional to z. For example, x * (z+2)/2 will do just fine in this case
There's bound to be a more proper, general solution though!

Categories