What is this particle system position bug? - javascript

This particle cloud of sprites should sit to the right of the x-axis and above the y-axis. However, as you can see in this fiddle it mystifyingly hangs just below the y-axis and despite all my editing and rearranging and can't figure out what's going on here. I'd appreciate a fresh pair of eyes to help me out. Thanks!
Here is the basis for my math in the elliptical cloud.
function pointInEllipticalCloud(radiusA, radiusB, pZ) {
var pX = Math.random() * (radiusA * 2);
var rightBisector = Math.abs( radiusA - pX );
var chord = (2 * radiusB) * Math.sqrt(1 - Math.pow((rightBisector / radiusA), 2));
var pY = (Math.random() * chord) + ((radiusB - chord) / 2);
return new THREE.Vector3(pX, pY, pZ);
}
var pZ = 1;
var particleGroup = new THREE.Object3D();
for( var i = 0; i < 300; i++ ) {
var radiusA = 200;
var radiusB = 50;
var particle = new THREE.Sprite( spriteMaterial );
particle.scale.set( 20, 20, 1 );
var spritePoint = pointInEllipticalCloud(radiusA, radiusB, pZ);
// *** Why the resulting form hanging below the y axis?
particle.position = spritePoint ;
particleGroup.add( particle );
pZ += 0.1;
}
particleGroup.position.set(0,0,0);
scene.add( particleGroup );

Ah! Found it. The bug was in part of the calculation for pointInEllipticalCloud(). pY should instead equal this:
var pY = (Math.random() * chord) + (((radiusB * 2) - chord) / 2);
...where radiusB is multiplied by two to make it the vertical diameter of the ellipse.

Related

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...

Issue when rendering a torus in webGL

I'm writing a program that is supposed to draw 3D parametric shapes in webgl. The code I have currently seems to work (mostly) for a sphere, but when I switch out the equations used to find x, y, and z, for a torus, only the top half of the torus is being rendered (in the x-y plane). All of the parametric equations I have (for a sphere, a torus, and a cylinder) work when just the 2d canvas context is used without any webgl; however, when webgl is used, there seems to be a problem. In addition the problem with only rendering half of the torus, the equation used to render the cylinder doesn't render anything.
What could be causing only half of the torus to be rendered? The code for rendering the parametric shapes is below:
var latitudeBands = 30;
var longitudeBands = 30;
var radius = 0.5;
var vertexPositionData = [];
var normalData = [];
var textureCoordData = [];
var indexData = [];
for (var latNumber = 0; latNumber <= latitudeBands; latNumber++)
{ var theta = latNumber * Math.PI / latitudeBands;
var sinTheta = Math.sin(theta);
var cosTheta = Math.cos(theta);
for (var longNumber = 0; longNumber <= longitudeBands; longNumber++) {
var phi = longNumber * 2 * Math.PI / longitudeBands;
var sinPhi = Math.sin(phi);
var cosPhi = Math.cos(phi);
//Equation used for sphere
//var x = Math.cos(phi) * Math.cos(theta);
//var y = Math.cos(phi) * Math.sin(theta);
//var z = Math.sin(phi);
//Equation used for torus
var x = (1 + radius * Math.cos(phi)) * Math.cos(theta);
var y = (1 + radius * Math.cos(phi)) * Math.sin(theta);
var z = radius * Math.sin(phi);
//Equation used for cylinder
//var x = Math.cos(theta);
//var y = Math.sin(theta);
//var z = 2 * latNumber - 1;
var u = 1 - (longNumber / longitudeBands);
var v = 1 - (latNumber / latitudeBands);
normalData.push(x);
normalData.push(y);
normalData.push(z);
textureCoordData.push(u);
textureCoordData.push(v);
vertexPositionData.push(radius * x);
vertexPositionData.push(radius * y);
vertexPositionData.push(radius * z)
}
}
for (var latNumber = 0; latNumber < latitudeBands; latNumber++) {
for (var longNumber = 0; longNumber < longitudeBands; longNumber++) {
var first = (latNumber * (longitudeBands + 1)) + longNumber;
var second = first + longitudeBands + 1;
indexData.push(first);
indexData.push(second);
indexData.push(first + 1);
indexData.push(second);
indexData.push(second + 1);
indexData.push(first + 1);
}
}
moonVertexPositionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, moonVertexPositionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertexPositionData), gl.STATIC_DRAW);
moonVertexPositionBuffer.itemSize = 3;
moonVertexPositionBuffer.numItems = vertexPositionData.length / 3;
moonVertexIndexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, moonVertexIndexBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indexData), gl.STATIC_DRAW);
moonVertexIndexBuffer.itemSize = 1;
moonVertexIndexBuffer.numItems = indexData.length;
Your elevation angle range for a sphere is only PI (from pole to pole), but
the range is 2*PI for a torus; Thus
theta = latNumber * Math.PI / latitudeBands
should be
theta = latNumber * 2 * Math.PI / latitudeBands
See WebGL Torus Example.
Honestly for these type of problems the most likely error arise from the geometry generation code. I suggest consult the excellent THREE.js source code to check for discrepancies. Toros generation code here.
You can find code for other geometry generation here should you need them in the future.

Translating pixels in canvas on sine wave

I am trying to create an image distortion effect on my canvas, but nothing appears to be happening. Here is my code:
self.drawScreen = function (abilityAnimator, elapsed) {
if (!self.initialized) {
self.initialized = true;
self.rawData = abilityAnimator.context.getImageData(self.targetX, self.targetY, self.width, self.height);
self.initialImgData = self.rawData.data;
}
abilityAnimator.drawBackground();
self.rawData = abilityAnimator.context.getImageData(self.targetX, self.targetY, self.width, self.height);
var imgData = self.rawData.data, rootIndex, translationIndex, newX;
for (var y = 0; y < self.height; y++) {
for (var x = 0; x < self.width; x++) {
rootIndex = (y * self.height + x) * 4;
newX = Math.ceil(self.amplitude * Math.sin(self.frequency * (y + elapsed)));
translationIndex = (y * self.width + newX) * 4;
imgData[translationIndex + 0] = self.initialImgData[rootIndex + 0];
imgData[translationIndex + 1] = self.initialImgData[rootIndex + 1];
imgData[translationIndex + 2] = self.initialImgData[rootIndex + 2];
imgData[translationIndex + 3] = self.initialImgData[rootIndex + 3];
}
}
abilityAnimator.context.putImageData(self.rawData, self.targetX, self.targetY);
};
abilityAnimator is a wrapper for my canvas object:
abilityAnimator.context = //canvas.context
abilityAnimator.drawBackground = function(){
this.canvas.width = this.canvas.width;
}
elapsed is simply the number of milliseconds since the animation began (elapsed is always <= 2000)
My member variables have the following values:
self.width = 125;
self.height = 125;
self.frequency = 0.5;
self.amplitude = self.width / 4;
self.targetX = //arbitrary value within canvas
self.targetY = //arbitrary value within canvas
I can translate the image to the right very easily so long as there is no sine function, however, introducing these lines:
newX = Math.ceil(self.amplitude * Math.sin(self.frequency * (y + elapsed)));
translationIndex = (y * self.width + newX) * 4;
Causes nothing to render at all. The translation indexes don't appear to be very strange, and the nature of the sinusoidal function should guarantee that the offset is no greater than 125 / 4 pixels.
Your formula using sin is wrong, the frequency will be so high it will be seen as noise.
The typical formula to build a sinusoid is :
res = sin ( 2 * PI * frequency * time ) ;
where frequency is in Hz and time in s.
So in js that would translate to :
res = Math.sin ( 2 * Math.PI * f * time_ms * 1e-3 ) ;
you can obviously compute just once the constant factor :
self.frequency = 0.5 * ( 2 * Math.PI * 1e-3 );
// then use
res = Math.sin ( self.frequency * time_ms ) ;
So you see you were 1000 times too fast.
Second issue :
Now that you have your time frequency ok, let's fix your spatial frequency : when multiplying time frequency by y, you're quite adding apples and cats.
To build the formula, think that you want to cross n time 2*PI during the height of the canvas.
So :
spatialFrequency = ( n ) * 2 * Math.PI / canvasHeight ;
and your formula becomes :
res = Math.sin ( self.frequency * time_ms + spatialFrequency * y ) ;
You can play with various values with this jsbin i made so you can visualize the effect :
http://jsbin.com/ludizubo/1/edit?js,output

Threejs: Rotate sphere(globe) to another point(city) on the sphere itself

I'm building a globe(sphere geometry) with set of predefined locations on geo-mapped and drawn as point(sphere geometry). I would like to focus(moving one location to another) those locations by rotating globe along y-axis. I tried the following code, seems not working for all locations.
location.geometry.computeBoundingBox();
var position = new THREE.Vector3();
position.subVectors( location.geometry.boundingBox.max, location.geometry.boundingBox.min );
position.multiplyScalar( 0.20 );
position.sub( location.geometry.boundingBox.min );
location.matrixWorld.multiplyVector3( position );
var point1 = scene.clone().position;
var point2 = position;
var distance = point1.distanceTo( point2 );
locationCollection.rotation.y = distance;
I think, I don't understand the concept enough. Hopefully, I will get some idea from the community.
Fiddle
var c = group.rotation.y;
var d = -b * (Math.PI / 180)%(2 * Math.PI);
var e = Math.PI / 2 * -1;
group.rotation.y = c % (2 * Math.PI);
group.rotation.x = a * (Math.PI / 180) % Math.PI;
group.rotation.y= d+e;
where a= latitude, b= longitude,group=Object3D(or sphere)

ray intersect object, picking object with mouse-pointer

Thank you for your consideration, as my problem is in title already I will get directly to the point:
UPDATE 2:
Problem solved. I did not transformed them into meshes, or so it seems. With the hint got from supernova and a failed attempt to enable shadowmap it became so clear, although half a day was lost...
UPDATE 1:
The example is here: JSFiddle
.
.
Original problem:
The standard example of ray casting for object picking using the mouse coordinates, and I mean this one:
var vector = new THREE.Vector3((event.clientX / window.innerWidth ) * 2 - 1, -(event.clientY / window.innerHeight ) * 2 + 1, 1);
projector.unprojectVector(vector, camera);
var ray = new THREE.Ray(camera.position, vector.subSelf(camera.position).normalize());
var intersects = ray.intersectObjects(cubes);
if (intersects.length > 0)
{
console.log('hit');
}
, didn't work at all.
After taking some time to think about it I have tried this:
// start a ray from your camera to the coordinates on
// x,y plane you pointed with the mouse:
// *I also draw the actual ray on the screen, it looked perfect! :( but...
var cx = event.clientX;
var cy = event.clientY;
var rx = cx - (SCREEN_WIDTH / 2);
var ry = (SCREEN_HEIGHT / 2) - cy;
var origin = new THREE.Vector3(camera.position.x, camera.position.y, camera.position.z - 200);
var direction = new THREE.Vector3(rx, ry, -20);
var ray = new THREE.Ray(origin, direction);
var intersects = ray.intersectObjects(cubes);
But the "intersect" variable is still empty.
Although it seems logical to work, because my elements are as follows:
for ( i = 0; i < cols; i++)
{
for ( j = 0; j < rows; j++)
{
var cwidth = (SCREEN_WIDTH / cols) / scale;
var cheight = (SCREEN_HEIGHT / rows) / scale;
var cdepth = 10;
var cube = THREE.SceneUtils.createMultiMaterialObject(new THREE.CubeGeometry(cwidth, cheight, cdepth, 1, 1, 1), getMaterials());
// getMaterials() is a simple function returning an Array of materials
cube.position.x = cwidth * i - (SCREEN_WIDTH / 2) + cwidth / 2;
cube.position.x /= scale;
cube.position.y = cheight * j - (SCREEN_HEIGHT / 2) + cheight / 2;
cube.position.y /= scale;
cube.position.z = 0;
cubes.push(cube);
scene.add(cube);
}
}
And my camera is:
camera.position.z = getDistance(window.innerHeight);
// or {x: 0, y:0, z: 403.00000000000006}
I am trying to fix this for over two hours now, can anybody see what I am missing? And I have searched the internet with no avail.
P.S. I hope i did not tagged this question erroneous with "ray".

Categories

Resources