calculating half-edges/faces of arbitrarily generated lines - javascript

I found this script by mrdoob that generates a web of lines. I've figured out where a line begins and where it ends. Now I want to extrude faces from these shapes, however all I have are lines and vertices. I'm trying to read through some half-edges theory, but i don't think I understand it that well.
Is it a matter of following a line until it's a rectangle, checking if it intersects a line or subdivides? I need a bump in the right direction.
// Based on Jared Tarbell's Substrate algorithm concept.
// http://www.complexification.net/gallery/machines/substrate/index.php
var Boid = function ( x, y, angle ) {
this.x = x;
this.y = y;
this.angle = Math.pow( Math.random(), 20 ) + angle;
this.dx = Math.cos( this.angle );
this.dy = Math.sin( this.angle );
this.life = Math.random() * 100 + 100;
this.dead = false;
this.update = function () {
context.strokeStyle = '#000000';
context.beginPath();
context.moveTo( this.x, this.y );
this.x += this.dx * 2;
this.y += this.dy * 2;
this.life -= 2;
context.lineTo( this.x, this.y );
context.stroke();
var index = ( Math.floor( this.x ) + width * Math.floor( this.y ) ) * 4;
if ( this.life <= 0 ) this.kill();
if ( data[ index + 3 ] > 0 ) this.kill();
if ( this.x < 0 || this.x > width ) this.kill();
if ( this.y < 0 || this.y > height ) this.kill();
}
this.kill = function () {
boids.splice( boids.indexOf( this ), 1 );
this.dead = true;
}
}
var width = window.innerWidth;
var height = window.innerHeight;
var canvas = document.getElementById( 'world' );
canvas.width = width;
canvas.height = height;
var context = canvas.getContext( '2d' );
var image, data;
var boids = [];
boids.push( new Boid( width / 2, height / 2, Math.random() * 360 * Math.PI / 180 ) );
setInterval( function () {
image = context.getImageData( 0, 0, width, height );
data = image.data;
for ( var i = 0; i < boids.length; i ++ ) {
var boid = boids[ i ];
boid.update();
if ( !boid.dead && Math.random() > 0.5 && boids.length < 500 ) {
boids.push( new Boid( boid.x, boid.y, ( Math.random() > 0.5 ? 90 : - 90 ) * Math.PI / 180 + boid.angle ) );
}
}
}, 1000 / 60 );

This looks more complicated than I tough. I'm not sure if it is the answer you are asking for, but may help you to decide the next step:
If you have to use this algorithm: I think you are going to need to keep track of every pair of points that makes an edge: the first point at the beginning of the Boid function and the second when the Boid is killed; both points (or the x1, x2, y1 and y2 values) are saved in a new edge object that will be added to an edges array (every edge will be kind of the soul of a death Boid).
There are two problems before applying the half-edges theory: you have an array of edges, but you need to know what other edges are connected to the beginning or end of a given edge. The other problem is that the "collision" between two Boids only affects the Boid currently being updated, which is killed during the collision. In order to use the half-edge theory you'll have to "notify" the other Boid/edge about this collision and split it at that point: a collision point is a vertex of three edges, the one that collides and the two in which the one being collided was split.
Also note that the shapes (faces) are not necessarily made of four edges, I opened the link you provider and there where a lot of shapes with tree and five edges.
If you can use a different algorithm for generating the mesh then you may have a better representation of the edges and vertexes that will help you to find the "corners" that makes every shape.

Related

Making and connecting hexagons on a rectangle grid

I am creating a pathfinding application and I want to connect every hexgon(H) to its adjacent hexagons. The grid is a rectangle but it is populated with hexagons. The issue is the code right now to connect these hexagons is lengthy and extremely finicky. An example of what i am trying to achieve is:
The issue is that the connections between say one hexagon and its neighbours (range from 2-6 depending on their placement in the grid) is not working properly. An example of the code i am using right now to connect a hexagon with 6 neighbours is:
currentState.graph().addEdge(i, i + 1, 1);
currentState.graph().addEdge(i, i - HexBoard.rows + 1, 1);
currentState.graph().addEdge(i, i - HexBoard.rows, 1);
currentState.graph().addEdge(i, i + HexBoard.rows +1, 1);
currentState.graph().addEdge(i, i + HexBoard.rows , 1);
The graph is essetialy the grid, addEdge adds a connection from src ->dest with cost(c) in order. Is there any algorithm or way to make my code less bulky ? (right now it is polluted with if-else clauses)?
The site which inspired me :https://clementmihailescu.github.io/Pathfinding-Visualizer/#
EDIT : The problem is not in drawing hexagons (They are already SVGs), it in assigning them edges and connections.
Interesting problem... To set a solid foundation, here's a hexagon grid class that is neither lengthy nor finicky, based on a simple data structure of a linear array. A couple of notes...
The HexagonGrid constructor accepts the hexagon grid dimensions in terms of the number of hexagons wide (hexWidth) by number of hexagons high (hexHeight).
The hexHeight alternates by an additional hexagon every other column for a more pleasing appearance. Thus an odd number for hexWidth bookends the hexagon grid with the same number of hexagons in the first and last columns.
The length attribute represents the total number of hexagons in the grid.
Each hexagon is referenced by a linear index from 0..length.
The hexagonIndex method which takes (x,y) coordinates returns an the linear index based on an approximation of the closest hexagon. Thus, when near the edges of a hexagon, the index returned might be a close neighbor.
Am not totally satisfied with the class structure, but is sufficient to show the key algorithms involved in a linear indexed hexagon grid.
To aid in visualizing the linear indexing scheme, the code snippet displays the linear index value in the hexagon. Such an indexing scheme offers the opportunity to have a parallel array of the same length which represents the characteristics of each specific hexagon by index.
Also exemplified is the ability to translate from mouse coordinates to the hexagon index, by clicking on any hexagon, which will redraw the hexagon with a thicker border.
const canvas = document.getElementById( 'canvas' );
const ctx = canvas.getContext( '2d' );
class HexagonGrid {
constructor( hexWidth, hexHeight, edgeLength ) {
this.hexWidth = hexWidth;
this.hexHeight = hexHeight;
this.edgeLength = edgeLength;
this.cellWidthPair = this.hexHeight * 2 + 1;
this.length = this.cellWidthPair * ( hexWidth / 2 |0 ) + hexHeight * ( hexWidth % 2 );
this.dx = edgeLength * Math.sin( Math.PI / 6 );
this.dy = edgeLength * Math.cos( Math.PI / 6 );
}
centerOfHexagon( i ) {
let xPairNo = i % this.cellWidthPair;
return {
x: this.dx + this.edgeLength / 2 + ( i / this.cellWidthPair |0 ) * ( this.dx + this.edgeLength ) * 2 + ( this.hexHeight <= i % this.cellWidthPair ) * ( this.dx + this.edgeLength ),
y: xPairNo < this.hexHeight ? ( xPairNo + 1 ) * this.dy * 2 : this.dy + ( xPairNo - this.hexHeight ) * this.dy * 2
};
}
hexagonIndex( point ) {
let col = ( point.x - this.dx / 2 ) / ( this.dx + this.edgeLength ) |0;
let row = ( point.y - ( col % 2 === 0 ) * this.dy ) / ( this.dy * 2 ) |0;
let hexIndex = ( col / 2 |0 ) * this.cellWidthPair + ( col % 2 ) * this.hexHeight + row;
//console.log( `(${point.x},${point.y}): col=${col} row=${row} hexIndex=${hexIndex}` );
return ( 0 <= hexIndex && hexIndex < this.length ? hexIndex : null );
}
edge( i ) {
let topCheck = i % ( this.hexHeight + 0.5 );
return (
i < this.hexHeight
|| ( i + 1 ) % ( this.hexHeight + 0.5 ) === this.hexHeight
|| i % ( this.hexHeight + 0.5 ) === this.hexHeight
|| ( i + 1 ) % ( this.hexHeight + 0.5 ) === 0
|| i % ( this.hexHeight + 0.5 ) === 0
|| this.length - this.hexHeight < i
);
}
drawHexagon( ctx, center, lineWidth ) {
let halfEdge = this.edgeLength / 2;
ctx.lineWidth = lineWidth || 1;
ctx.beginPath();
ctx.moveTo( center.x - halfEdge, center.y - this.dy );
ctx.lineTo( center.x + halfEdge, center.y - this.dy );
ctx.lineTo( center.x + halfEdge + this.dx, center.y );
ctx.lineTo( center.x + halfEdge, center.y + this.dy );
ctx.lineTo( center.x - halfEdge, center.y + this.dy );
ctx.lineTo( center.x - halfEdge - this.dx, center.y );
ctx.lineTo( center.x - halfEdge, center.y - this.dy );
ctx.stroke();
}
drawGrid( ctx, topLeft ) {
ctx.font = '10px Arial';
for ( let i = 0; i < this.length; i++ ) {
let center = this.centerOfHexagon( i );
this.drawHexagon( ctx, { x: topLeft.x + center.x, y: topLeft.y + center.y } );
ctx.fillStyle = this.edge( i ) ? 'red' : 'black';
ctx.fillText( i, topLeft.x + center.x - 5, topLeft.y + center.y + 5 );
}
}
}
let myHexGrid = new HexagonGrid( 11, 5, 20 );
let gridLeftTop = { x: 20, y: 20 };
myHexGrid.drawGrid( ctx, gridLeftTop );
canvas.addEventListener( 'mousedown', function( event ) {
let i = myHexGrid.hexagonIndex( { x: event.offsetX - gridLeftTop.x, y: event.offsetY - gridLeftTop.y } );
if ( i !== null ) {
let center = myHexGrid.centerOfHexagon( i );
myHexGrid.drawHexagon( ctx, { x: gridLeftTop.x + center.x, y: gridLeftTop.y + center.y }, 3 );
}
} );
<canvas id=canvas width=1000 height=1000 />
A large benefit of the linear index is that it makes path searching easier, as each interior hexagon is surrounded by hexagons with relative indexes of -1, -6, -5, +1, +6, +5. For example, applying the relative indexes to hexagon 18 results in a list of surrounding hexagons of 17, 12, 13, 19, 24, 23.
As a bonus, the edge method indicates whether the hexagon is on the edge of the grid. (In the code snippet, the edge cells are identified by red text.) Highly recommend that edge cells not be part of the pathing (ie, they are unreachable) as this simplifies any path searching. Otherwise the pathing logic becomes very complex, as now if on an edge cell, the relative indexes indicating the surrounding hexagons no longer fully apply...

Porting 3D Rose written by Wolfram Language into JavaScript

I'd like to get help from Geometry / Wolfram Mathematica people.
I want to visualize this 3D Rose in JavaScript (p5.js) environment.
This figure is originally generated using wolfram language by Paul Nylanderin 2004-2006, and below is the code:
Rose[x_, theta_] := Module[{
phi = (Pi/2)Exp[-theta/(8 Pi)],
X = 1 - (1/2)((5/4)(1 - Mod[3.6 theta, 2 Pi]/Pi)^2 - 1/4)^2},
y = 1.95653 x^2 (1.27689 x - 1)^2 Sin[phi];
r = X(x Sin[phi] + y Cos[phi]);
{r Sin[theta], r Cos[theta], X(x Cos[phi] - y Sin[phi]), EdgeForm[]
}];
ParametricPlot3D[
Rose[x, theta], {x, 0, 1}, {theta, -2 Pi, 15 Pi},
PlotPoints -> {25, 576}, LightSources -> {{{0, 0, 1}, RGBColor[1, 0, 0]}},
Compiled -> False
]
I tried implement that code in JavaScript like this below.
function rose(){
for(let theta = 0; theta < 2700; theta += 3){
beginShape(POINTS);
for(let x = 2.3; x < 3.3; x += 0.02){
let phi = (180/2) * Math.exp(- theta / (8*180));
let X = 1 - (1/2) * pow(((5/4) * pow((1 - (3.6 * theta % 360)/180), 2) - 1/4), 2);
let y = 1.95653 * pow(x, 2) * pow((1.27689*x - 1), 2) * sin(phi);
let r = X * (x*sin(phi) + y*cos(phi));
let pX = r * sin(theta);
let pY = r * cos(theta);
let pZ = (-X * (x * cos(phi) - y * sin(phi)))-200;
vertex(pX, pY, pZ);
}
endShape();
}
}
But I got this result below
Unlike original one, the petal at the top is too stretched.
I suspected that the
let y = 1.95653 * pow(x, 2) * pow((1.27689*x - 1), 2) * sin(phi);
may should be like below...
let y = pow(1.95653*x, 2*pow(1.27689*x - 1, 2*sin(theta)));
But that went even further away from the original.
Maybe I'm asking a dumb question, but I've been stuck for several days.
If you see a mistake, please let me know.
Thank you in advanse🙏
Update:
I changed the x range to 0~1 as defined by the original one.
Also simplified the JS code like below to find the error.
function rose_debug(){
for(let theta = 0; theta < 15*PI; theta += PI/60){
beginShape(POINTS);
for(let x = 0.0; x < 1.0; x += 0.005){
let phi = (PI/2) * Math.exp(- theta / (8*PI));
let y = pow(x, 4) * sin(phi);
let r = (x * sin(phi) + y * cos(phi));
let pX = r * sin(theta);
let pY = r * cos(theta);
let pZ = x * cos(phi) - y * sin(phi);
vertex(pX, pY, pZ);
}
endShape();
}
}
But the result still keeps the wrong proportion↓↓↓
Also, when I remove the term "sin(phi)" in the line "let y =..." like below
let y = pow(x, 4);
then I got a figure somewhat resemble the original like below🤣
At this moment I was starting to suspect the mistake on the original equation, but I found another article by Jorge García Tíscar(Spanish) that implemented the exact same 3D rose in wolfram language successfully.
So, now I really don't know how the original is formed by the equation😇
Update2: Solved
I followed a suggestion by Trentium (Answer No.2 below) that stick to 0 ~ 1 as the range of x, then multiply the r and X by an arbitrary number.
for(let x = 0; x < 1; x += 0.05){
r = r * 200;
X = X * 200;
Then I got this correct result looks exactly the same as the original🥳
Simplified final code:
function rose_debug3(){
for(let x = 0; x <= 1; x += 0.05){
beginShape(POINTS);
for(let theta = -2*PI; theta <= 15*PI; theta += 17*PI/2000){
let phi = (PI / 2) * Math.exp(- theta / (8 * PI));
let X = 1 - (1/2) * ((5/4) * (1 - ((3.6 * theta) % (2*PI))/PI) ** 2 - 1/4) ** 2;
let y = 1.95653 * (x ** 2) * ((1.27689*x - 1) ** 2) * sin(phi);
let r = X * (x * sin(phi) + y * cos(phi));
if(0 < r){
const factor = 200;
let pX = r * sin(theta)*factor;
let pY = r * cos(theta)*factor;
let pZ = X * (x * cos(phi) - y * sin(phi))*factor;
vertex(pX, pY, pZ);
}
}
endShape();
}
}
The reason I got the vertically stretched figure at first was the range of the x. I thought that changing the range of the x just affect the whole size of the figure. But actually, the range affects like this below.
(1): 0 ~ x ~ 1, (2): 0 ~ x ~ 1.2
(3): 0 ~ x ~ 1.5, (4): 0 ~ x ~ 2.0
(5): flipped the (4)
So far I saw the result like (5) above, didn't realize that the correct shape was hiding inside that figure.
Thank you Trentium so much for kindly helping me a lot!
Since this response is a significant departure from my earlier response, am adding a new answer...
In rendering the rose algorithm in ThreeJS (sorry, I'm not a P5 guy) it became apparent that when generating the points, that only the points with a positive radius are to be rendered. Otherwise, superfluous points are rendered far outside the rose petals.
(Note: When running the code snippet, use the mouse to zoom and rotate the rendering of the rose.)
<script type="module">
import * as THREE from 'https://cdn.jsdelivr.net/npm/three#0.115.0/build/three.module.js';
import { OrbitControls } from 'https://cdn.jsdelivr.net/npm/three#0.115.0/examples/jsm/controls/OrbitControls.js';
//
// Set up the ThreeJS environment.
//
var renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
var camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 500 );
camera.position.set( 0, 0, 100 );
camera.lookAt( 0, 0, 0 );
var scene = new THREE.Scene();
let controls = new OrbitControls(camera, renderer.domElement);
//
// Create the points.
//
function rose( xLo, xHi, xCount, thetaLo, thetaHi, thetaCount ){
let vertex = [];
let colors = [];
let radius = [];
for( let x = xLo; x <= xHi; x += ( xHi - xLo ) / xCount ) {
for( let theta = thetaLo; theta <= thetaHi; theta += ( thetaHi - thetaLo ) / thetaCount ) {
let phi = ( Math.PI / 2 ) * Math.exp( -theta / ( 8 * Math.PI ) );
let X = 1 - ( 1 / 2 ) * ( ( 5 / 4 ) * ( 1 - ( ( 3.6 * theta ) % ( 2 * Math.PI ) ) / Math.PI ) ** 2 - 1 / 4 ) ** 2;
let y = 1.95653 * ( x ** 2 ) * ( (1.27689 * x - 1) ** 2 ) * Math.sin( phi );
let r = X * ( x * Math.sin( phi ) + y * Math.cos( phi ) );
//
// Fix: Ensure radius is positive, and scale up accordingly...
//
if ( 0 < r ) {
const factor = 20;
r = r * factor;
radius.push( r );
X = X * factor;
vertex.push( r * Math.sin( theta ), r * Math.cos( theta ), X * ( x * Math.cos( phi ) - y * Math.sin( phi ) ) );
}
}
}
//
// For the fun of it, lets adjust the color of the points based on the radius
// of the point such that the larger the radius, the deeper the red.
//
let rLo = Math.min( ...radius );
let rHi = Math.max( ...radius );
for ( let i = 0; i < radius.length; i++ ) {
let clr = new THREE.Color( Math.floor( 0x22 + ( 0xff - 0x22 ) * ( ( radius[ i ] - rLo ) / ( rHi - rLo ) ) ) * 0x10000 + 0x002222 );
colors.push( clr.r, clr.g, clr.b );
}
return [ vertex, colors, radius ];
}
//
// Create the geometry and mesh, and add to the THREE scene.
//
const geometry = new THREE.BufferGeometry();
let [ positions, colors, radius ] = rose( 0, 1, 20, -2 * Math.PI, 15 * Math.PI, 2000 );
geometry.setAttribute( 'position', new THREE.Float32BufferAttribute( positions, 3 ) );
geometry.setAttribute( 'color', new THREE.Float32BufferAttribute( colors, 3 ) );
const material = new THREE.PointsMaterial( { size: 4, vertexColors: true, depthTest: false, sizeAttenuation: false } );
const mesh = new THREE.Points( geometry, material );
scene.add( mesh );
//
// Render...
//
var animate = function () {
requestAnimationFrame( animate );
renderer.render( scene, camera );
};
animate();
</script>
Couple of notables:
When calling rose( xLo, xHi, xCount, thetaLo, thetaHi, thetaCount ), the upper range thetaHi can vary from Math.PI to 15 * Math.PI, which varies the number of petals.
Both xCount and thetaCount vary the density of the points. The Wolfram example uses 25 and 576, respectively, but this is to create a geometry mesh, whereas if creating a point field the density of points needs to be increased. Hence, in the code the values are 20 and 2000.
Enjoy!
Presumably the algorithm above is referencing cos() and sin() functions that handle the angles in degrees rather than radians, but wherever using angles while employing non-trigonometric transformations, the result will be incorrect.
For example, the following formula using radians...
phi = (Pi/2)Exp[-theta/(8 Pi)]
...has been incorrectly translated to...
phi = ( 180 / 2 ) * Math.exp( -theta / ( 8 * 180 ) )
To test, let's assume theta = 2. Using the original formula in radians...
phi = ( Math.PI / 2 ) * Math.exp( -2 / ( 8 * Math.PI ) )
= 1.451 rad
= 83.12 deg
...and now the incorrect version using degrees, which returns a different angle...
phi = ( 180 / 2 ) * Math.exp( -2 / ( 8 * 180 ) )
= 89.88 deg
= 1.569 rad
A similar issue will occur with the incorrectly translated expression...
pow( ( 1 - ( 3.6 * theta % 360 ) / 180 ), 2 )
Bottom line: Stick to radians.
P.S. Note that there might be other issues, but using radians rather than degrees needs to be corrected foremost...

Canvas Rotating Star Field

I'm taking the following approach to animate a star field across the screen, but I'm stuck for the next part.
JS
var c = document.getElementById('stars'),
ctx = c.getContext("2d"),
t = 0; // time
c.width = 300;
c.height = 300;
var w = c.width,
h = c.height,
z = c.height,
v = Math.PI; // angle of vision
(function animate() {
Math.seedrandom('bg');
ctx.globalAlpha = 1;
for (var i = 0; i <= 100; i++) {
var x = Math.floor(Math.random() * w), // pos x
y = Math.floor(Math.random() * h), // pos y
r = Math.random()*2 + 1, // radius
a = Math.random()*0.5 + 0.5, // alpha
// linear
d = (r*a), // depth
p = t*d; // pixels per t
x = x - p; // movement
x = x - w * Math.floor(x / w); // go around when x < 0
(function draw(x,y) {
var gradient = ctx.createRadialGradient(x, y, 0, x + r, y + r, r * 2);
gradient.addColorStop(0, 'rgba(255, 255, 255, ' + a + ')');
gradient.addColorStop(1, 'rgba(0, 0, 0, 0)');
ctx.beginPath();
ctx.arc(x, y, r, 0, 2*Math.PI);
ctx.fillStyle = gradient;
ctx.fill();
return draw;
})(x, y);
}
ctx.restore();
t += 1;
requestAnimationFrame(function() {
ctx.clearRect(0, 0, c.width, c.height);
animate();
});
})();
HTML
<canvas id="stars"></canvas>
CSS
canvas {
background: black;
}
JSFiddle
What it does right now is animate each star with a delta X that considers the opacity and size of the star, so the smallest ones appear to move slower.
Use p = t; to have all the stars moving at the same speed.
QUESTION
I'm looking for a clearly defined model where the velocities give the illusion of the stars rotating around the expectator, defined in terms of the center of the rotation cX, cY, and the angle of vision v which is what fraction of 2π can be seen (if the center of the circle is not the center of the screen, the radius should be at least the largest portion). I'm struggling to find a way that applies this cosine to the speed of star movements, even for a centered circle with a rotation of π.
These diagrams might further explain what I'm after:
Centered circle:
Non-centered:
Different angle of vision:
I'm really lost as to how to move forwards. I already stretched myself a bit to get here. Can you please help me with some first steps?
Thanks
UPDATE
I have made some progress with this code:
// linear
d = (r*a)*z, // depth
v = (2*Math.PI)/w,
p = Math.floor( d * Math.cos( t * v ) ); // pixels per t
x = x + p; // movement
x = x - w * Math.floor(x / w); // go around when x < 0
JSFiddle
Where p is the x coordinate of a particle in uniform circular motion and v is the angular velocity, but this generates a pendulum effect. I am not sure how to change these equations to create the illusion that the observer is turning instead.
UPDATE 2:
Almost there. One user at the ##Math freenode channel was kind enough to suggest the following calculation:
// linear
d = (r*a), // depth
p = t*d; // pixels per t
x = x - p; // movement
x = x - w * Math.floor(x / w); // go around when x < 0
x = (x / w) - 0.5;
y = (y / h) - 0.5;
y /= Math.cos(x);
x = (x + 0.5) * w;
y = (y + 0.5) * h;
JSFiddle
This achieves the effect visually, but does not follow a clearly defined model in terms of the variables (it just "hacks" the effect) so I cannot see a straightforward way to do different implementations (change the center, angle of vision). The real model might be very similar to this one.
UPDATE 3
Following from Iftah's response, I was able to use Sylvester to apply a rotation matrix to the stars, which need to be saved in an array first. Also each star's z coordinate is now determined and the radius r and opacity a are derived from it instead. The code is substantially different and lenghthier so I am not posting it, but it might be a step in the right direction. I cannot get this to rotate continuously yet. Using matrix operations on each frame seems costly in terms of performance.
JSFiddle
Here's some pseudocode that does what you're talking about.
Make a bunch of stars not too far but not too close (via rejection sampling)
Set up a projection matrix (defines the camera frustum)
Each frame
Compute our camera rotation angle
Make a "view" matrix (repositions the stars to be relative to our view)
Compose the view and projection matrix into the view-projection matrix
For each star
Apply the view-projection matrix to give screen star coordinates
If the star is behind the camera skip it
Do some math to give the star a nice seeming 'size'
Scale the star coordinate to the canvas
Draw the star with its canvas coordinate and size
I've made an implementation of the above. It uses the gl-matrix Javascript library to handle some of the matrix math. It's good stuff. (Fiddle for this is here, or see below.)
var c = document.getElementById('c');
var n = c.getContext('2d');
// View matrix, defines where you're looking
var viewMtx = mat4.create();
// Projection matrix, defines how the view maps onto the screen
var projMtx = mat4.create();
// Adapted from http://stackoverflow.com/questions/18404890/how-to-build-perspective-projection-matrix-no-api
function ComputeProjMtx(field_of_view, aspect_ratio, near_dist, far_dist, left_handed) {
// We'll assume input parameters are sane.
field_of_view = field_of_view * Math.PI / 180.0; // Convert degrees to radians
var frustum_depth = far_dist - near_dist;
var one_over_depth = 1 / frustum_depth;
var e11 = 1.0 / Math.tan(0.5 * field_of_view);
var e00 = (left_handed ? 1 : -1) * e11 / aspect_ratio;
var e22 = far_dist * one_over_depth;
var e32 = (-far_dist * near_dist) * one_over_depth;
return [
e00, 0, 0, 0,
0, e11, 0, 0,
0, 0, e22, e32,
0, 0, 1, 0
];
}
// Make a view matrix with a simple rotation about the Y axis (up-down axis)
function ComputeViewMtx(angle) {
angle = angle * Math.PI / 180.0; // Convert degrees to radians
return [
Math.cos(angle), 0, Math.sin(angle), 0,
0, 1, 0, 0,
-Math.sin(angle), 0, Math.cos(angle), 0,
0, 0, 0, 1
];
}
projMtx = ComputeProjMtx(70, c.width / c.height, 1, 200, true);
var angle = 0;
var viewProjMtx = mat4.create();
var minDist = 100;
var maxDist = 1000;
function Star() {
var d = 0;
do {
// Create random points in a cube.. but not too close.
this.x = Math.random() * maxDist - (maxDist / 2);
this.y = Math.random() * maxDist - (maxDist / 2);
this.z = Math.random() * maxDist - (maxDist / 2);
var d = this.x * this.x +
this.y * this.y +
this.z * this.z;
} while (
d > maxDist * maxDist / 4 || d < minDist * minDist
);
this.dist = Math.sqrt(d);
}
Star.prototype.AsVector = function() {
return [this.x, this.y, this.z, 1];
}
var stars = [];
for (var i = 0; i < 5000; i++) stars.push(new Star());
var lastLoop = Date.now();
function loop() {
var now = Date.now();
var dt = (now - lastLoop) / 1000.0;
lastLoop = now;
angle += 30.0 * dt;
viewMtx = ComputeViewMtx(angle);
//console.log('---');
//console.log(projMtx);
//console.log(viewMtx);
mat4.multiply(viewProjMtx, projMtx, viewMtx);
//console.log(viewProjMtx);
n.beginPath();
n.rect(0, 0, c.width, c.height);
n.closePath();
n.fillStyle = '#000';
n.fill();
n.fillStyle = '#fff';
var v = vec4.create();
for (var i = 0; i < stars.length; i++) {
var star = stars[i];
vec4.transformMat4(v, star.AsVector(), viewProjMtx);
v[0] /= v[3];
v[1] /= v[3];
v[2] /= v[3];
//v[3] /= v[3];
if (v[3] < 0) continue;
var x = (v[0] * 0.5 + 0.5) * c.width;
var y = (v[1] * 0.5 + 0.5) * c.height;
// Compute a visual size...
// This assumes all stars are the same size.
// It also doesn't scale with canvas size well -- we'd have to take more into account.
var s = 300 / star.dist;
n.beginPath();
n.arc(x, y, s, 0, Math.PI * 2);
//n.rect(x, y, s, s);
n.closePath();
n.fill();
}
window.requestAnimationFrame(loop);
}
loop();
<script src="https://cdnjs.cloudflare.com/ajax/libs/gl-matrix/2.3.1/gl-matrix-min.js"></script>
<canvas id="c" width="500" height="500"></canvas>
Some links:
More on projection matrices
gl-matrix
Using view/projection matrices
Update
Here's another version that has keyboard controls. Kinda fun. You can see the difference between rotating and parallax from strafing. Works best full page. (Fiddle for this is here or see below.)
var c = document.getElementById('c');
var n = c.getContext('2d');
// View matrix, defines where you're looking
var viewMtx = mat4.create();
// Projection matrix, defines how the view maps onto the screen
var projMtx = mat4.create();
// Adapted from http://stackoverflow.com/questions/18404890/how-to-build-perspective-projection-matrix-no-api
function ComputeProjMtx(field_of_view, aspect_ratio, near_dist, far_dist, left_handed) {
// We'll assume input parameters are sane.
field_of_view = field_of_view * Math.PI / 180.0; // Convert degrees to radians
var frustum_depth = far_dist - near_dist;
var one_over_depth = 1 / frustum_depth;
var e11 = 1.0 / Math.tan(0.5 * field_of_view);
var e00 = (left_handed ? 1 : -1) * e11 / aspect_ratio;
var e22 = far_dist * one_over_depth;
var e32 = (-far_dist * near_dist) * one_over_depth;
return [
e00, 0, 0, 0,
0, e11, 0, 0,
0, 0, e22, e32,
0, 0, 1, 0
];
}
// Make a view matrix with a simple rotation about the Y axis (up-down axis)
function ComputeViewMtx(angle) {
angle = angle * Math.PI / 180.0; // Convert degrees to radians
return [
Math.cos(angle), 0, Math.sin(angle), 0,
0, 1, 0, 0,
-Math.sin(angle), 0, Math.cos(angle), 0,
0, 0, -250, 1
];
}
projMtx = ComputeProjMtx(70, c.width / c.height, 1, 200, true);
var angle = 0;
var viewProjMtx = mat4.create();
var minDist = 100;
var maxDist = 1000;
function Star() {
var d = 0;
do {
// Create random points in a cube.. but not too close.
this.x = Math.random() * maxDist - (maxDist / 2);
this.y = Math.random() * maxDist - (maxDist / 2);
this.z = Math.random() * maxDist - (maxDist / 2);
var d = this.x * this.x +
this.y * this.y +
this.z * this.z;
} while (
d > maxDist * maxDist / 4 || d < minDist * minDist
);
this.dist = 100;
}
Star.prototype.AsVector = function() {
return [this.x, this.y, this.z, 1];
}
var stars = [];
for (var i = 0; i < 5000; i++) stars.push(new Star());
var lastLoop = Date.now();
var dir = {
up: 0,
down: 1,
left: 2,
right: 3
};
var dirStates = [false, false, false, false];
var shiftKey = false;
var moveSpeed = 100.0;
var turnSpeed = 1.0;
function loop() {
var now = Date.now();
var dt = (now - lastLoop) / 1000.0;
lastLoop = now;
angle += 30.0 * dt;
//viewMtx = ComputeViewMtx(angle);
var tf = mat4.create();
if (dirStates[dir.up]) mat4.translate(tf, tf, [0, 0, moveSpeed * dt]);
if (dirStates[dir.down]) mat4.translate(tf, tf, [0, 0, -moveSpeed * dt]);
if (dirStates[dir.left])
if (shiftKey) mat4.rotate(tf, tf, -turnSpeed * dt, [0, 1, 0]);
else mat4.translate(tf, tf, [moveSpeed * dt, 0, 0]);
if (dirStates[dir.right])
if (shiftKey) mat4.rotate(tf, tf, turnSpeed * dt, [0, 1, 0]);
else mat4.translate(tf, tf, [-moveSpeed * dt, 0, 0]);
mat4.multiply(viewMtx, tf, viewMtx);
//console.log('---');
//console.log(projMtx);
//console.log(viewMtx);
mat4.multiply(viewProjMtx, projMtx, viewMtx);
//console.log(viewProjMtx);
n.beginPath();
n.rect(0, 0, c.width, c.height);
n.closePath();
n.fillStyle = '#000';
n.fill();
n.fillStyle = '#fff';
var v = vec4.create();
for (var i = 0; i < stars.length; i++) {
var star = stars[i];
vec4.transformMat4(v, star.AsVector(), viewProjMtx);
if (v[3] < 0) continue;
var d = Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
v[0] /= v[3];
v[1] /= v[3];
v[2] /= v[3];
//v[3] /= v[3];
var x = (v[0] * 0.5 + 0.5) * c.width;
var y = (v[1] * 0.5 + 0.5) * c.height;
// Compute a visual size...
// This assumes all stars are the same size.
// It also doesn't scale with canvas size well -- we'd have to take more into account.
var s = 300 / d;
n.beginPath();
n.arc(x, y, s, 0, Math.PI * 2);
//n.rect(x, y, s, s);
n.closePath();
n.fill();
}
window.requestAnimationFrame(loop);
}
loop();
function keyToDir(evt) {
var d = -1;
if (evt.keyCode === 38) d = dir.up
else if (evt.keyCode === 37) d = dir.left;
else if (evt.keyCode === 39) d = dir.right;
else if (evt.keyCode === 40) d = dir.down;
return d;
}
window.onkeydown = function(evt) {
var d = keyToDir(evt);
if (d >= 0) dirStates[d] = true;
if (evt.keyCode === 16) shiftKey = true;
}
window.onkeyup = function(evt) {
var d = keyToDir(evt);
if (d >= 0) dirStates[d] = false;
if (evt.keyCode === 16) shiftKey = false;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/gl-matrix/2.3.1/gl-matrix-min.js"></script>
<div>Click in this pane. Use up/down/left/right, hold shift + left/right to rotate.</div>
<canvas id="c" width="500" height="500"></canvas>
Update 2
Alain Jacomet Forte asked:
What is your recommended method of creating general purpose 3d and if you would recommend working at the matrices level or not, specifically perhaps to this particular scenario.
Regarding matrices: If you're writing an engine from scratch on any platform, then you're unavoidably going to end up working with matrices since they help generalize the basic 3D mathematics. Even if you use OpenGL/WebGL or Direct3D you're still going to end up making a view and projection matrix and additional matrices for more sophisticated purposes. (Handling normal maps, aligning world objects, skinning, etc...)
Regarding a method of creating general purpose 3d... Don't. It will run slow, and it won't be performant without a lot of work. Rely on a hardware-accelerated library to do the heavy lifting. Creating limited 3D engines for specific projects is fun and instructive (e.g. I want a cool animation on my webpage), but when it comes to putting the pixels on the screen for anything serious, you want hardware to handle that as much as you can for performance purposes.
Sadly, the web has no great standard for that yet, but it is coming in WebGL -- learn WebGL, use WebGL. It runs great and works well when it's supported. (You can, however, get away with an awful lot just using CSS 3D transforms and Javascript.)
If you're doing desktop programming, I highly recommend OpenGL via SDL (I'm not sold on SFML yet) -- it's cross-platform and well supported.
If you're programming mobile phones, OpenGL ES is pretty much your only choice (other than a dog-slow software renderer).
If you want to get stuff done rather than writing your own engine from scratch, the defacto for the web is Three.js (which I find effective but mediocre). If you want a full game engine, there's some free options these days, the main commercial ones being Unity and Unreal. Irrlicht has been around a long time -- never had a chance to use it, though, but I hear it's good.
But if you want to make all the 3D stuff from scratch... I always found how the software renderer in Quake was made a pretty good case study. Some of that can be found here.
You are resetting the stars 2d position each frame, then moving the stars (depending on how much time and speed of each star) - this is a bad way to achieve your goal. As you discovered, it gets very complex when you try to extend this solution to more scenarios.
A better way would be to set the stars 3d location only once (at initialization) then move a "camera" each frame (depending on time). When you want to render the 2d image you then calculate the stars location on screen. The location on screen depends on the stars 3d location and the current camera location.
This will allow you to move the camera (in any direction), rotate the camera (to any angle) and render the correct stars position AND keep your sanity.

Converting mouse events to touch events for mobile

I am trying to use the below code collected from internet (Sorry, I forget the URL). It's working well in the Desktop browser as it should be. But I want to use it on mobile(may be with cordova). I have tried to change the mouse events with with equivalent touch events with no luck. So, can any one help me to customize it for mobile.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Physics + Particles Mashup</title>
<script src="jquery-1.11.1.js"></script>
<STYLE type="text/css">
/* basic styles for black background and crosshair cursor */
body {
background: #000;
margin: 0;
}
canvas {
cursor: crosshair;
display: block;
}
</STYLE>
</head>
<body>
<!-- setup our canvas element -->
<canvas id="canvas">Canvas is not supported in your browser.</canvas>
<script type="text/javascript">
window.requestAnimFrame = ( function() {
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
function( callback ) {
window.setTimeout( callback, 1000 / 60 );
};
})();
// now we will setup our basic variables for the demo
var canvas = document.getElementById( 'canvas' ),
ctx = canvas.getContext( '2d' ),
// full screen dimensions
cw = window.innerWidth,
ch = window.innerHeight,
// firework collection
fireworks = [],
// particle collection
particles = [],
// starting hue
hue = 120,
// when launching fireworks with a click, too many get launched at once without a limiter, one launch per 5 loop ticks
limiterTotal = 5,
limiterTick = 0,
// this will time the auto launches of fireworks, one launch per 80 loop ticks
timerTotal = 80,
timerTick = 0,
mousedown = false,
// mouse x coordinate,
mx,
// mouse y coordinate
my;
// set canvas dimensions
canvas.width = cw;
canvas.height = ch;
// now we are going to setup our function placeholders for the entire demo
// get a random number within a range
function random( min, max ) {
return Math.random() * ( max - min ) + min;
}
// calculate the distance between two points
function calculateDistance( p1x, p1y, p2x, p2y ) {
var xDistance = p1x - p2x,
yDistance = p1y - p2y;
return Math.sqrt( Math.pow( xDistance, 2 ) + Math.pow( yDistance, 2 ) );
}
// create firework
function Firework( sx, sy, tx, ty ) {
// actual coordinates
this.x = sx;
this.y = sy;
// starting coordinates
this.sx = sx;
this.sy = sy;
// target coordinates
this.tx = tx;
this.ty = ty;
// distance from starting point to target
this.distanceToTarget = calculateDistance( sx, sy, tx, ty );
this.distanceTraveled = 0;
// track the past coordinates of each firework to create a trail effect, increase the coordinate count to create more prominent trails
this.coordinates = [];
this.coordinateCount = 3;
// populate initial coordinate collection with the current coordinates
while( this.coordinateCount-- ) {
this.coordinates.push( [ this.x, this.y ] );
}
this.angle = Math.atan2( ty - sy, tx - sx );
this.speed = 2;
this.acceleration = 1.05;
this.brightness = random( 50, 70 );
// circle target indicator radius
this.targetRadius = 1;
}
// update firework
Firework.prototype.update = function( index ) {
// remove last item in coordinates array
this.coordinates.pop();
// add current coordinates to the start of the array
this.coordinates.unshift( [ this.x, this.y ] );
// cycle the circle target indicator radius
if( this.targetRadius < 8 ) {
this.targetRadius += 0.3;
} else {
this.targetRadius = 1;
}
// speed up the firework
this.speed *= this.acceleration;
// get the current velocities based on angle and speed
var vx = Math.cos( this.angle ) * this.speed,
vy = Math.sin( this.angle ) * this.speed;
// how far will the firework have traveled with velocities applied?
this.distanceTraveled = calculateDistance( this.sx, this.sy, this.x + vx, this.y + vy );
// if the distance traveled, including velocities, is greater than the initial distance to the target, then the target has been reached
if( this.distanceTraveled >= this.distanceToTarget ) {
createParticles( this.tx, this.ty );
// remove the firework, use the index passed into the update function to determine which to remove
fireworks.splice( index, 1 );
} else {
// target not reached, keep traveling
this.x += vx;
this.y += vy;
}
}
// draw firework
Firework.prototype.draw = function() {
ctx.beginPath();
// move to the last tracked coordinate in the set, then draw a line to the current x and y
ctx.moveTo( this.coordinates[ this.coordinates.length - 1][ 0 ], this.coordinates[ this.coordinates.length - 1][ 1 ] );
ctx.lineTo( this.x, this.y );
ctx.strokeStyle = 'hsl(' + hue + ', 100%, ' + this.brightness + '%)';
ctx.stroke();
ctx.beginPath();
// draw the target for this firework with a pulsing circle
ctx.arc( this.tx, this.ty, this.targetRadius, 0, Math.PI * 2 );
ctx.stroke();
}
// create particle
function Particle( x, y ) {
this.x = x;
this.y = y;
// track the past coordinates of each particle to create a trail effect, increase the coordinate count to create more prominent trails
this.coordinates = [];
this.coordinateCount = 8;
while( this.coordinateCount-- ) {
this.coordinates.push( [ this.x, this.y ] );
}
// set a random angle in all possible directions, in radians
this.angle = random( 0, Math.PI * 2 );
this.speed = random( 1, 10 );
// friction will slow the particle down
this.friction = 0.95;
// gravity will be applied and pull the particle down
this.gravity = 1;
// set the hue to a random number +-50 of the overall hue variable
this.hue = random( hue - 50, hue + 50 );
this.brightness = random( 50, 80 );
this.alpha = 1;
// set how fast the particle fades out
this.decay = random( 0.015, 0.03 );
}
// update particle
Particle.prototype.update = function( index ) {
// remove last item in coordinates array
this.coordinates.pop();
// add current coordinates to the start of the array
this.coordinates.unshift( [ this.x, this.y ] );
// slow down the particle
this.speed *= this.friction;
// apply velocity
this.x += Math.cos( this.angle ) * this.speed;
this.y += Math.sin( this.angle ) * this.speed + this.gravity;
// fade out the particle
this.alpha -= this.decay;
// remove the particle once the alpha is low enough, based on the passed in index
if( this.alpha <= this.decay ) {
particles.splice( index, 1 );
}
}
// draw particle
Particle.prototype.draw = function() {
ctx. beginPath();
// move to the last tracked coordinates in the set, then draw a line to the current x and y
ctx.moveTo( this.coordinates[ this.coordinates.length - 1 ][ 0 ], this.coordinates[ this.coordinates.length - 1 ][ 1 ] );
ctx.lineTo( this.x, this.y );
ctx.strokeStyle = 'hsla(' + this.hue + ', 100%, ' + this.brightness + '%, ' + this.alpha + ')';
ctx.stroke();
}
// create particle group/explosion
function createParticles( x, y ) {
// increase the particle count for a bigger explosion, beware of the canvas performance hit with the increased particles though
var particleCount = 30;
while( particleCount-- ) {
particles.push( new Particle( x, y ) );
}
}
// main demo loop
function loop() {
// this function will run endlessly with requestAnimationFrame
requestAnimFrame( loop );
// increase the hue to get different colored fireworks over time
//hue += 0.5;
// create random color
hue= random(0, 360 );
// normally, clearRect() would be used to clear the canvas
// we want to create a trailing effect though
// setting the composite operation to destination-out will allow us to clear the canvas at a specific opacity, rather than wiping it entirely
ctx.globalCompositeOperation = 'destination-out';
// decrease the alpha property to create more prominent trails
ctx.fillStyle = 'rgba(0, 0, 0, 0.5)';
ctx.fillRect( 0, 0, cw, ch );
// change the composite operation back to our main mode
// lighter creates bright highlight points as the fireworks and particles overlap each other
ctx.globalCompositeOperation = 'lighter';
// loop over each firework, draw it, update it
var i = fireworks.length;
while( i-- ) {
fireworks[ i ].draw();
fireworks[ i ].update( i );
}
// loop over each particle, draw it, update it
var i = particles.length;
while( i-- ) {
particles[ i ].draw();
particles[ i ].update( i );
}
// launch fireworks automatically to random coordinates, when the mouse isn't down
// limit the rate at which fireworks get launched when mouse is down
if( limiterTick >= limiterTotal ) {
if( mousedown ) {
// start the firework at the bottom middle of the screen, then set the current mouse coordinates as the target
fireworks.push( new Firework( cw / 2, ch, mx, my ) );
limiterTick = 0;
}
} else {
limiterTick++;
}
}
// mouse event bindings
// update the mouse coordinates on mousemove
canvas.addEventListener( 'mousemove', function( e ) {
mx = e.pageX - canvas.offsetLeft;
my = e.pageY - canvas.offsetTop;
});
// toggle mousedown state and prevent canvas from being selected
canvas.addEventListener( 'mousedown', function( e ) {
e.preventDefault();
mousedown = true;
});
canvas.addEventListener( 'mouseup', function( e ) {
e.preventDefault();
mousedown = false;
});
// once the window loads, we are ready for some fireworks!
window.onload = loop;
</script>
</body>
</html>
Any help will be highly appreciated.
Thanks in advance.

get Y value of quadratic curve given the max height of the curve

When Y is 100 the maximum height of the curve will be (+/-) 60. I need a way to calculate Y when I have the maximum height of the curve.
Code:
point1 and point2 have x, y and z coordinates
this.drawLine = function(point1, point2) {
context = this.getContext();
context.beginPath();
context.moveTo(this.getX(point1), this.getY(point1));
point3 = {
x: ( point1.x + point2.x ) / 2,
y: ( point1.y + point2.y ) / 2,
z: ( point1.z + point2.z ) / 2
}
context.quadraticCurveTo( this.getX(point3), this.getY(point3) + point3.z * 0, this.getX(point2), this.getY(point2));
context.stroke();
}
I need the line of the curve to hit the coordinates of point3 instead of it not reaching the coordinates.
There are still many possible curves with the same maximums. Therefore, you cannot isolate a single curve to figure out your Y value.
I would suggest finding a way to obtain more information about your curve such as a point, property or relation.
Check out these links:
http://www.personal.kent.edu/~bosikiew/Algebra-handouts/quad-extval.pdf
http://hotmath.com/hotmath_help/topics/graphing-quadratic-equations-using-transformations.html
Found my answer: here
this.drawLine = function(point1, point2, style) {
context = this.getContext();
context.beginPath();
context.moveTo(this.getX(point1), this.getY(point1));
point3 = {
x: ( point1.x + point2.x ) / 2,
y: ( point1.y + point2.y ) / 2,
z: ( point1.z + point2.z ) / 2
}
context.strokeStyle = style;
x = this.getX(point3) * 2 - ( this.getX(point1) + this.getX(point2) ) / 2;
y = this.getY(point3) * 2 - ( this.getY(point1) + this.getY(point2) ) / 2;
context.quadraticCurveTo( x, y, this.getX(point2), this.getY(point2));
context.stroke();
}

Categories

Resources