Animating an object from its center with sine wave in three.js - javascript

I am trying to replicate this effect: https://dribbble.com/shots/1754428-Wave?list=users&offset=5
I want to animate a plane's vertices simlarly to the link I've provided. I know that it's achieved using a sine wave propagation, but I can't figure out how to start the movement from the central point of the plane. Right now, I have something like this
(function drawFrame(ts){
window.requestAnimationFrame(drawFrame);
var vLength = plane.geometry.vertices.length;
for (var i = 0; i < vLength; i++) {
var v = plane.geometry.vertices[i];
v.z = Math.sin(ts / 500 + (v.x * (vLength / 2)) * (v.y / (vLength / 2))) * 3 + 5;
}
It works kind of OK, but notice how in the top left and bottom right corners the movement is inward, towards the centre of the plane and not outwards, as it should be. The other two corners are behaving in exactly the way I want them to be.
Here's a link to what I currently have:
http://codepen.io/gbnikolov/pen/QwjGPg
All suggestions and ideas are more then welcome!

I have found the function you are after it was fun!
(function drawFrame(ts){
var center = new THREE.Vector2(0,0);
window.requestAnimationFrame(drawFrame);
var vLength = plane.geometry.vertices.length;
for (var i = 0; i < vLength; i++) {
var v = plane.geometry.vertices[i];
var dist = new THREE.Vector2(v.x, v.y).sub(center);
var size = 5.0;
var magnitude = 2.0;
v.z = Math.sin(dist.length()/size + (ts/500)) * magnitude;
}
plane.geometry.verticesNeedUpdate = true;
renderer.render(scene, camera);
}());
The circular pattern is created by creating a point as I did above called center. This is where the wave originates. We calculate distance to the center point. We then sin the distance from the center point to create the up/down. Next we add the time ts to create the movement. Finally we add some variables to tweak the size of the wave.

Related

Finding length of arc on unit circle only given x position

Some background:
I've been trying to map a texture onto a "sphere" using a look up table of texture co-ordinate changes. This is for a really slow micro controller to draw on a little LCD panel. So Three.JS is out, WebGL etc... the look up table should work!
The equations for texturing a sphere all pinch the poles. I can't "pre-spread" the texture of these extremes because the texture offset changes to make the "sphere" appear to rotate.
If you examine the code for making the lookup table here, you'll see the approach, and the running demo shows the issue.
https://codepen.io/SarahC/pen/KKoKqKW
I figured I'd try and come up with a new approach myself!
After thinking a while, I realised a sphere texture in effect moves the location of the texture pixel further from the spheres origin the further away from the origin it is! In a straight line from the origin.
So I figured - calculate the angle the current pixel is from the origin, calculate it's unit distance, then all I need to do is make a function that is given the distance, and calculates the new distance based on some "sphere calculation". That new distance is almost 1 to 1 near the center of the sphere, and rapidly increases right at the edges. Mapping a sphere texture!
That offset function I figured (may be wrong here?) (diagrammed below) given the distance from the origin L1 (unit circle) it returns the length of the arc L2 which in effect is the flat pixel offset to use from the source texture.
(I asked on Reddit, and got given Math.acos for X, but now I know that's wrong, because that's the X position of the circle! Not the straight line X position from the offset, AND it gives an angle, not the Y position... wrong on two counts. Oooph!
Oddly, surprisingly, because I thought it gave the Y position, I dropped it into an atan2 function, and it ALMOST worked... for all the wrong reasons of course but it made a bump at the center of the "sphere".
The current "state of the mistake" is right here:
https://codepen.io/SarahC/pen/abYbgwa?editors=0010 )
Now I know that aCos isn't the function I need, I'm at a loss for the solution.
But! Perhaps this approach I thought up is stupid? I'd happily use any look-up mapping function you think would work. =)
Thanks for your time and reading and sharing, I like learning new stuff.
//JS
An interesting but befuddling problem...
Per Spektre's comment and my follow up comment, the mapping of x to the length of the circle's arc still resulted in the center bubble effect of the image as described in the question. I tried numerous mathematically "correct" attempts, including picking a distant view point from the sphere and then calculating the compression of the 2d image as the view point angle swept from top center of the sphere to the edge, but again, to no avail, as the bubble effect persisted...
In the end, I introduced a double fudge factor. To eliminate the bubble effect, I took the 32nd root of the unit radius to stretch the sphere's central points. Then, when calculating the arc length (per the diagram in the question and the comments on "L2") I undid the stretching fudge factor by raising to the 128th power the unit radius to compress and accentuate the curvature towards the edge of the sphere.
Although this solution appears to provide the proper results, it offends the mathematician in me, as it is a pure fudge to eliminate the confusing bubble effect. The use of the 32nd root and 128th power were simply arrived at via trial and error, rather than any true mathematical reasoning. Ugh...
So, FWIW, the code snippet below exemplifies both the calculation and use of the lookup table in functions unitCircleLut2() and drawSphere2(), respectively...
// https://www.reddit.com/r/GraphicsProgramming/comments/vlnqjc/oldskool_textured_sphere_using_lut_and_texture_xy/
// Perhaps useable as a terminator eye?........
// https://www.youtube.com/watch?v=nSlEQumWLHE
// https://www.youtube.com/watch?v=hx_0Ge4hDpI
// This is an attempt to recreate the 3D eyeball that the Terminator upgrade produces on the Adafruit M4sk system.
// As the micro control unit only has 200Kb RAM stack and C and no 3D graphics support, chances are there's no textured 3D sphere, but a look up table to map an eyeball texture to a sphere shape on the display.
// I've got close - but this thing pinches the two poles - which I can't see happening with the M4sk version.
// Setup the display, and get its pixels so we can write to them later.
let c = document.createElement("canvas");
c.width = 300;
c.height = 300;
document.body.appendChild(c);
let ctx = c.getContext("2d");
let imageDataWrapper = ctx.getImageData(0, 0, c.width, c.height);
let imageData = imageDataWrapper.data; // 8 bit ARGB
let imageData32 = new Uint32Array(imageData.buffer); // 32 bit pixel
// Declare the look up table - dimensions same as display.
let offsetLUT = null;
// Texture to map to sphere.
let textureCanvas = null;
let textureCtx = null;
let textureDataWrapper = null;
let textureData = null;
let textureData32 = null;
let px = 0;
let py = 0;
let vx = 2;
let vy = 0.5;
// Load the texture and get its pixels.
let textureImage = new Image();
textureImage.crossOrigin = "anonymous";
textureImage.onload = _ => {
textureCanvas = document.createElement("canvas");
textureCtx = textureCanvas.getContext("2d");
offsetLUT = unitCircleLut2( 300 );
textureCanvas.width = textureImage.width;
textureCanvas.height = textureImage.height;
textureCtx.drawImage(textureImage, 0, 0);
textureDataWrapper = textureCtx.getImageData(0, 0, textureCanvas.width, textureCanvas.height);
textureData = textureDataWrapper.data;
textureData32 = new Uint32Array(textureData.buffer);
// Draw texture to display - just to show we got it.
// Won't appear if everything works, as it will be replaced with the sphere draw.
for(let i = 0; i < imageData32.length; i++) {
imageData32[i] = textureData32[i];
}
ctx.putImageData(imageDataWrapper, 0, 0);
requestAnimationFrame(animation);
}
textureImage.src = "https://untamed.zone/miscImages/metalEye.jpg";
function unitCircleLut2( resolution ) {
function y( x ) {
// x ** 128 compresses when x approaches 1. This helps accentuate the
// curvature of the sphere near the edges...
return ( Math.PI / 2 - Math.acos( x ** 128 ) ) / ( Math.PI / 2 );
}
let r = resolution / 2 |0;
// Rough calculate the length of the arc...
let arc = new Array( r );
for ( let i = 0; i < r; i++ ) {
// The calculation for nx stretches x when approaching 0. This removes the
// center bubble effect...
let nx = ( i / r ) ** ( 1 / 32 );
arc[ i ] = { x: nx, y: y( nx ), arcLen: 0 };
if ( 0 < i ) {
arc[ i ].arcLen = arc[ i - 1 ].arcLen + Math.sqrt( ( arc[ i ].x - arc[ i - 1 ].x ) ** 2 + ( arc[ i ].y - arc[ i - 1 ].y ) ** 2 );
}
}
let arcLength = arc[ r - 1 ].arcLen;
// Now, for each element in the array, calculate the factor to apply to the
// metal eye to either stretch (center) or compress (edges) the image...
let lut = new Array( resolution );
let centerX = r;
let centerY = r;
for( let y = 0; y < resolution; y++ ) {
let ny = y - centerY;
lut[ y ] = new Array( resolution );
for( let x = 0; x < resolution; x++ ) {
let nx = x - centerX;
let nd = Math.sqrt( nx * nx + ny * ny ) |0;
if ( r <= nd ) {
lut[ y ][ x ] = null;
} else {
lut[ y ][ x ] = arc[ nd ].arcLen / arcLength;
}
}
}
return lut;
}
function drawSphere2(dx, dy){
const mx = textureCanvas.width - c.width;
const my = textureCanvas.height - c.height;
const idx = Math.round(dx);
const idy = Math.round(dy);
const textureCenterX = textureCanvas.width / 2 |0;
const textureCenterY = textureCanvas.height / 2 |0;
let iD32index = 0;
for(let y = 0; y < c.height; y++){
for(let x = 0; x < c.width; x++){
let stretch = offsetLUT[y][x];
if(stretch == null){
imageData32[iD32index++] = 0;
}else{
// The 600/150 is the ratio of the metal eye to the offsetLut. But, since the
// eye doesn't fill the entire image, the ratio is fudged to get more of the
// eye into the sphere...
let tx = ( x - 150 ) * 600/150 * Math.abs( stretch ) + textureCenterX + dx |0;
let ty = ( y - 150 ) * 600/150 * Math.abs( stretch ) + textureCenterY + dy |0;
let textureIndex = tx + ty * textureCanvas.width;
imageData32[iD32index++] = textureData32[textureIndex];
}
}
}
ctx.putImageData(imageDataWrapper, 0, 0);
}
// Move the texture on the sphere and keep redrawing.
function animation(){
px += vx;
py += vy;
let xx = Math.cos(px / 180 * Math.PI) * 180 + 0;
let yy = Math.cos(py / 180 * Math.PI) * 180 + 0;
drawSphere2(xx, yy);
requestAnimationFrame(animation);
}
body {
background: #202020;
color: #f0f0f0;
font-family: arial;
}
canvas {
border: 1px solid #303030;
}

Animating SVG: Move shape in direction of mouse/rotate around fixed point?

After looking through similar questions posted to the forum and not finding something that helped me solve my own problem, I'm posting it.
I'm using SVG.js to generate SVG shapes in a web document. I'd like one of those shapes to ”follow” the mouse/cursor.
By that I mean: The shape has a fixed position/anchor point (at its original center) and it can only move a limited distance (let's say 50px) away from this fixed point.
I want the shape to move in the direction of the cursor, whenever the cursor moves, but never further than a defined distance away from its orignal position. I'm attaching a short animation to illustrate my description:
If the cursor were to disappear, the shape would snap back to its original center.
I know my way around Javascript, HTML and CSS. This type of element-manipulation is new to me and the math is giving my quite the headache, any help would be great.
It looks like I need the shape to basically rotate around its original center, with an angle relative to the cursor? I'm really unsure how to solve this. I have tried using a method to calculate the angle described in this post. My shape moves, but not as intended:
// init
var draw = SVG().addTo('body')
// draw
window.shape = draw.circle(25, 25).stroke({
color: '#000',
width: 2.5
}).fill("#fff");
shape.attr("id", "circle1");
shape.move(50, 50)
// move
var circle = $("#circle1");
var dist = 10;
$(document).mousemove(function(e) {
// angle
var circleCenter = [circle.offset().left + circle.width() / 2, circle.offset().top + circle.height() / 2];
var angle = Math.atan2(e.clientX - circleCenter[0], -(e.clientY - circleCenter[1])) * (180 / Math.PI);
var x = Math.sin(angle) * dist;
var y = (Math.cos(angle) * dist) * -1;
shape.animate().dmove(x, y);
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/svg.js/3.0.16/svg.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
Note: It does not matter to me whether the solution depends on jQuery or not (ideally it doesn't).
After more fiddling around with some solutions to calculating angles and distances, I found the answer.
I'm using a fixed reference point to calculate the angle of the direct line between the center of the shape and the cursor. Then I move the shape relative to this reference point and by a given amount:
// Init canvas
var draw = SVG().addTo('body')
// Draw reference/anchor
var shape_marker_center = draw.circle(3,3).fill("#f00").move(150, 150);;
var grafikCenter = [shape_marker_center.attr("cx"), shape_marker_center.attr("cy")]
// Draw shapes
var shape = draw.circle(25, 25).stroke({color: '#000', width: 2.5 }).fill("none");
shape.attr("id", "circle1").attr({cx: grafikCenter[0], cy:grafikCenter[1]})
var shape2 = draw.circle(50, 50).stroke({color: '#000', width: 2.5 }).fill("none");
shape2.attr("id", "circle2").attr({cx: grafikCenter[0], cy:grafikCenter[1]})
var shape3 = draw.circle(75, 75).stroke({color: '#000', width: 2.5 }).fill("none");
shape3.attr("id", "circle3").attr({cx: grafikCenter[0], cy:grafikCenter[1]})
$(document).mousemove(function(e) {
var pointA = [shape_marker_center.attr("cx"), shape_marker_center.attr("cy")];
var pointB = [e.clientX, e.clientY];
var angle = Math.atan2(pointB[1] - pointA[1], pointB[0] - pointA[0]) * 180 / Math.PI ;
//
var distance_x_1 = Math.cos(angle*Math.PI/180) * 16;
var distance_y_1 = Math.sin(angle*Math.PI/180) * 16;
var distance_x_2 = Math.cos(angle*Math.PI/180) * 8;
var distance_y_2 = Math.sin(angle*Math.PI/180) * 8;
//
shape.center((grafikCenter[0] + distance_x_1), (grafikCenter[1] + distance_y_1));
shape2.center((grafikCenter[0] + (distance_x_2) ), (grafikCenter[1] + (distance_y_2)));
})
svg {
width: 100vw;
height: 100vh;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/svg.js/3.0.16/svg.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

Rotating One Object Around Another In createJS/easelJS

In easelJS, what is the best way to rotate an object around another? What I'm trying to accomplish is a method to rotate the crosshair around the circle pictured below, just like a planet orbits the sun:
I've been able to rotate objects around their own center point, but am having a difficult time devising a way to rotate one object around the center point of a second object. Any ideas?
Might make sense to wrap content in a Container. Translate the coordinates so the center point is where you want it, and then rotate the container.
To build on what Lanny is suggesting, there may be cases where you don't want to rotate the entire container. An alternative would be to use trigonometric functions and an incrementing angle to calculate the x/y position of the crosshair. You can find the x/y by using an angle (converted to radians) and Math.cos(angleInRadians) for x and Math.sin(angleInRadians) for y, the multiply by the radius of the orbit.
See this working example for reference.
Here's a complete snippet.
var stage = new createjs.Stage("stage");
var angle = 0;
var circle = new createjs.Shape();
circle.graphics.beginFill("#FF0000").drawEllipse(-25, -25, 50, 50).endFill();
circle.x = 100;
circle.y = 100;
var crosshair = new createjs.Shape();
crosshair.graphics.setStrokeStyle(2).beginStroke("#FF0000").moveTo(5, 0).lineTo(5, 10).moveTo(0, 5).lineTo(10, 5).endStroke();
stage.addChild(circle);
stage.addChild(crosshair);
createjs.Ticker.addEventListener("tick", function(){
angle++;
if(angle > 360)
angle = 1;
var rads = angle * Math.PI / 180;
var x = 100 * Math.cos(rads);
var y = 100 * Math.sin(rads);
crosshair.x = x + 100;
crosshair.y = y + 100;
stage.update();
});
Put another point respect to origin point with the same direction
var one_meter = 1 / map_resolution;
// get one meter distance from pointed points
var extra_x = one_meter * Math.cos(temp_rotation);
var extra_y = one_meter * Math.sin(-temp_rotation);
var new_x = mapXY.x + extra_x;
var new_y = mapXY.y + extra_y;
var home_point = new createjs.Shape().set({ x: new_x, y: new_y });
home_point.graphics.beginFill("Blue").drawCircle(0, 0, 10);
stage.addChild(home_point);
stage.update();

Finding a location on cosine curve with a specified distance to another location JS

I am working on a "rally" game where a car is drawing on hills made of cosine curves. I know the current xspeed of the car (without hills) but the problem is that I need to know the xspeed of the car on the hills to be able to draw the wheels on right places and keep the speed steady.
At the moment my solution looks like this.
function drawWheelOnBasicHill(hillStart, xLocWheel, wheelNro) {
var cw = 400 //the width of the hill
t_max = 2*Math.PI;
var scale = 80, step = cw, inc = t_max/step;
var t1 = (xLocWheel-hillStart)*inc
var y1 = -scale*0.5 * Math.cos(t1);
if(wheelNro == 1 ){ //backwheel
drawRotatedImage(wheel, car.wheel1x, car.wheel1y-y1-45,sx);
//drawing the wheel on canvas
} else { //frontwheel
drawRotatedImage(wheel, car.wheel2x, car.wheel2y-y1-45,sx);
}
for(var i=1; i<=car.speed; i++){ //finding the next xlocation of the wheel with the
//same distance (on the curve) to the previous location as the speed of the car(=the
//distance to the new point on the flat ground)
var t2 = (xLocWheel + i -hillStart)*inc
var y2 = -scale*0.5 * Math.cos(t2);
if(Math.round(Math.sqrt(i^2+(y2-y1)^2))==car.speed){
sx = sx+i; //the new xcoordinate break;
}
}
}
The for loop is the problem. It might bee too slow (animation with fps 24). I cant understand why the if statement isnt working at the moment. It works sometimes but most of the times the value of the condition newer reaches the actual xspeed.
Are there some more efficient and easier ways to do this? Or does this code contain some errors? I really appreciate your efforts to solve this! Ive been looking at this piece of code the whole day..
So i is the variable and
x2=x1+i
t2=t1+i*inc
y1=-scale*0.5 * Math.cos(t1)
y2=-scale*0.5 * Math.cos(t2)
which somehow is strange. The landscape should be time independent, that is, y should be a function of x only. The time step is external, determined by the speed of the animation loop. So a more logical model would have dx as variable and
dt = t2-t1
x2 = x1 + dx
y1 = f(x1) = -0.5*scale*cos(x1)
y2 = f(x2) = -0.5*scale*cos(x2)
and you would be looking for the intersection of
(x2-x1)^2+(y2-y1)^2 = (speed*dt)^2
which simplifies to
(speed*dt)^2=dx^2+0.25*scale^2*(cos(x1+dx)-cos(x1))^2
For small values of dx, which would be the case if dt or speed*dt is small,
cos(x1+dx)-cos(x1) is approx. -sin(x1)*dx
leading to
dx = (speed*dt) / sqrt( 1+0.25*scale^2*sin(x1)^2 )
To get closer to the intersection of curve and circle, you can then iterate the fixed point equation
dydx = 0.5*scale*(cos(x1+dx)-cos(x1))/dx
dx = (speed*dt) / ( 1+dydx^2 )
a small number of times.

Multiple rotation of the camera

I'm trying to do some simple camera rotation in WebGL app(using lesson 10 from Learning WebGL), but I'm definetly doing something wrong.. I mean the horizontal movement of camera seems good,movement with WASD seems also OK, but when I'm adding the vertical movement, in some points of the map, something goes wrong and the map starts to incline. Where is my mistake? (demo is here)
what I'm doing is:
function handleMouseMove(event) {
var canvas = document.getElementById("quak-canvas");
var newX = event.clientX;
var newY = event.clientY;
var newRotationMatrix = mat4.create();
mat4.identity(newRotationMatrix);
var deltaY = newY - lastMouseY;
mat4.rotate(newRotationMatrix, degToRad(deltaY / 40), [1, 0, 0]);
var deltaX = newX - lastMouseX;
horizontalAngle = (event.pageX/(canvas.width/2))*180;
mat4.rotate(newRotationMatrix, degToRad(deltaX / 3.75), [0, 1, 0]);
mat4.multiply(newRotationMatrix, moonRotationMatrix, moonRotationMatrix);
lastMouseX = newX
lastMouseY = newY;
window.moveBy(10, 10);
}
I think there is some translate is missing or something like, but I have tried some combinations but it wasn't successfull..
Thanks a lot
Serhiy.
First off, let me say that you're demo actually looks okay to me. Maybe it's a side effect of the dampened vertical rotation, but I don't see anything that looks too terribly off. Your code looks okay too for the most part. I think the core of your problem may be the matrix multiply at the end. The fact that you're always building off the previous frame's results can lead to some complications.
In my FPS movement code I re-calculate the view matrix every frame like so:
var viewMat = mat4.create();
mat4.identity(viewMat);
mat4.rotateX(viewMat, xAngle); // X angle comes from Y mouse movement
mat4.rotateY(viewMat, yAngle); // Y angle comes from X mouse movement
mat4.translate(viewMat, position);
The position is calculated when WASD are pressed like so:
var dir = vec3.create();
if (pressedKeys['W']) { dir[2] -= speed; }
if (pressedKeys['S']) { dir[2] += speed; }
if (pressedKeys['A']) { dir[0] -= speed; }
if (pressedKeys['D']) { dir[0] += speed; }
if (pressedKeys[32]) { dir[1] += speed; } // Space, moves up
if (pressedKeys[17]) { dir[1] -= speed; } // Ctrl, moves down
// Create an inverted rotation matrix to transform the direction of movement by
var cam = mat4.create();
mat4.identity(cam);
mat4.rotateY(cam, -yAngle);
mat4.rotateX(cam, -xAngle);
// Move the camera in the direction we are facing
mat4.multiplyVec3(cam, dir);
vec3.add(position, dir);
Hopefully that helps you get a working solution for your own code!

Categories