Related
So I'm trying to do a mousemove event in three.js where the user would hover over a geometry and then it would scale.
So for the animations I am using GSAP as tween doesn't seem to be working for me. However, I keep getting this error when I want to scale my geometry:
I don't know why because in the official gsap documentation they use scale, without a plugin I believe. Here is an example from their website
gsap.to(".box", 1, {
scale: 0.1,
y: 60,
yoyo: true,
repeat: -1,
ease: "power1.inOut",
delay:1,
stagger: {
amount: 1.5,
grid: "auto",
from: "center"
}
});
Here is my code:
function init () {
/*------Bunch of three.js code and initialsation*/
window.addEventListener('mousemove',onMouseMove);
}
function onMouseMove(event) {
event.preventDefault();
mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
raycaster.setFromCamera(mouse,camera);
var intersects = raycaster.intersectObjects(scene.children, true);
for(var i = 0; i < intersects.length; i++) {
gsap.to(intersects[i].object.scale, {duration: 1, scale: 0.8});
}
}
So I figured the answer. It seems that in three.js, the gsap scale property doesn't work so you need to make sure you're referring to scale before adding transformations, and then use x and y (or maybe z - I haven't tried) to increase or decrease the size.
This is the code that worked for me:
gsap.to(intersects[i].object.scale, {duration: .7, x: 1.2, y:1.2});
First Note: They wont let me embed images until i have more reputation points (sorry), but all the links are images posted on imgur! :) thanks
I have replicated a method to animate any single path (1 closed path) using fourier transforms. This creates an animation of epicylces (rotating circles) which rotate around each other, and follow the imputed points, tracing the path as a continuous loop/function.
I would like to adopt this system to 3D. the two methods i can think of to achieve this is to use a Spherical Coordinate system (two complex planes) or 3 Epicycles --> one for each axis (x,y,z) with their individual parametric equations. This is probably the best way to start!!
2 Cycles, One for X and one for Y:
Picture: One Cycle --> Complex Numbers --> For X and Y
Fourier Transformation Background!!!:
• Eulers formula allows us to decompose each point in the complex plane into an angle (the argument to the exponential function) and an amplitude (Cn coefficients)
• In this sense, there is a connection to imaging each term in the infinite series above as representing a point on a circle with radius cn, offset by 2πnt/T radians
• The image below shows how a sum of complex numbers in terms of phases/amplitudes can be visualized as a set of concatenated cirlces in the complex plane. Each red line is a vector representing a term in the sequence of sums: cne2πi(nT)t
• Adding the summands corresponds to simply concatenating each of these red vectors in complex space:
Animated Rotating Circles:
Circles to Animated Drawings:
• If you have a line drawing in 2D (x-y) space, you can describe this path mathematically as a parametric function. (two separate single variable functions, both in terms of an auxiliary variable (T in this case):
• For example, below is a simple line drawing of a horse, and a parametric path through the black pixels in image, and that path then seperated into its X and Y components:
• At this point, we need to calculate the Fourier approximations of these two paths, and use coefficients from this approximation to determine the phase and amplitudes of the circles needed for the final visualization.
Python Code:
The python code used for this example can be found here on guithub
I have successful animated this process in 2D, but i would like to adopt this to 3D.
The Following Code Represents Animations in 2D --> something I already have working:
[Using JavaScript & P5.js library]
The Fourier Algorithm (fourier.js):
// a + bi
class Complex {
constructor(a, b) {
this.re = a;
this.im = b;
}
add(c) {
this.re += c.re;
this.im += c.im;
}
mult(c) {
const re = this.re * c.re - this.im * c.im;
const im = this.re * c.im + this.im * c.re;
return new Complex(re, im);
}
}
function dft(x) {
const X = [];
const Values = [];
const N = x.length;
for (let k = 0; k < N; k++) {
let sum = new Complex(0, 0);
for (let n = 0; n < N; n++) {
const phi = (TWO_PI * k * n) / N;
const c = new Complex(cos(phi), -sin(phi));
sum.add(x[n].mult(c));
}
sum.re = sum.re / N;
sum.im = sum.im / N;
let freq = k;
let amp = sqrt(sum.re * sum.re + sum.im * sum.im);
let phase = atan2(sum.im, sum.re);
X[k] = { re: sum.re, im: sum.im, freq, amp, phase };
Values[k] = {phase};
console.log(Values[k]);
}
return X;
}
The Sketch Function/ Animations (Sketch.js):
let x = [];
let fourierX;
let time = 0;
let path = [];
function setup() {
createCanvas(800, 600);
const skip = 1;
for (let i = 0; i < drawing.length; i += skip) {
const c = new Complex(drawing[i].x, drawing[i].y);
x.push(c);
}
fourierX = dft(x);
fourierX.sort((a, b) => b.amp - a.amp);
}
function epicycles(x, y, rotation, fourier) {
for (let i = 0; i < fourier.length; i++) {
let prevx = x;
let prevy = y;
let freq = fourier[i].freq;
let radius = fourier[i].amp;
let phase = fourier[i].phase;
x += radius * cos(freq * time + phase + rotation);
y += radius * sin(freq * time + phase + rotation);
stroke(255, 100);
noFill();
ellipse(prevx, prevy, radius * 2);
stroke(255);
line(prevx, prevy, x, y);
}
return createVector(x, y);
}
function draw() {
background(0);
let v = epicycles(width / 2, height / 2, 0, fourierX);
path.unshift(v);
beginShape();
noFill();
for (let i = 0; i < path.length; i++) {
vertex(path[i].x, path[i].y);
}
endShape();
const dt = TWO_PI / fourierX.length;
time += dt;
And Most Importantly! THE PATH / COORDINATES:
(this one is a triangle)
let drawing = [
{ y: -8.001009734 , x: -50 },
{ y: -7.680969345 , x: -49 },
{ y: -7.360928956 , x: -48 },
{ y: -7.040888566 , x: -47 },
{ y: -6.720848177 , x: -46 },
{ y: -6.400807788 , x: -45 },
{ y: -6.080767398 , x: -44 },
{ y: -5.760727009 , x: -43 },
{ y: -5.440686619 , x: -42 },
{ y: -5.12064623 , x: -41 },
{ y: -4.800605841 , x: -40 },
...
...
{ y: -8.001009734 , x: -47 },
{ y: -8.001009734 , x: -48 },
{ y: -8.001009734 , x: -49 },
];
This answer is in response to: "Do you think [three.js] can replicate what i have in 2D but in 3D? with the rotating circles and stuff?"
Am not sure whether you're looking to learn 3D modeling from scratch (ie, creating your own library of vector routines, homogeneous coordinate transformations, rendering perspective, etc) or whether you're simply looking to produce a final product. In the case of the latter, three.js is a powerful graphics library built on webGL that in my estimation is simple enough for a beginner to dabble with, but has a lot of depth to produce very sophisticated 3D effects. (Peruse the examples at https://threejs.org/examples/ and you'll see for yourself.)
I happen to be working a three.js project of my own, and whipped up a quick example of epicyclic circles as a warm up exercise. This involved pulling pieces and parts from the following references...
https://threejs.org/docs/index.html#manual/en/introduction/Creating-a-scene
https://threejs.org/examples/#misc_controls_orbit
https://threejs.org/examples/#webgl_geometry_shapes (This three.js example is a great resource showing a variety of ways that a shape can be rendered.)
The result is a simple scene with one circle running around the other, permitting mouse controls to orbit around the scene, viewing it from different angles and distances.
<html>
<head>
<title>Epicyclic Circles</title>
<style>
body { margin: 0; }
canvas { width: 100%; height: 100% }
</style>
</head>
<body>
<script src="https://rawgit.com/mrdoob/three.js/dev/build/three.js"></script>
<script src="https://rawgit.com/mrdoob/three.js/dev/examples/js/controls/OrbitControls.js"></script>
<script>
// Set up the basic scene, camera, and lights.
var scene = new THREE.Scene();
scene.background = new THREE.Color( 0xf0f0f0 );
var camera = new THREE.PerspectiveCamera( 75, window.innerWidth/window.innerHeight, 0.1, 1000 );
scene.add(camera)
var light = new THREE.PointLight( 0xffffff, 0.8 );
camera.add( light );
camera.position.z = 50;
var renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
// Add the orbit controls to permit viewing the scene from different angles via the mouse.
controls = new THREE.OrbitControls( camera, renderer.domElement );
controls.enableDamping = true; // an animation loop is required when either damping or auto-rotation are enabled
controls.dampingFactor = 0.25;
controls.screenSpacePanning = false;
controls.minDistance = 0;
controls.maxDistance = 500;
// Create center and epicyclic circles, extruding them to give them some depth.
var extrudeSettings = { depth: 2, bevelEnabled: true, bevelSegments: 2, steps: 2, bevelSize: .25, bevelThickness: .25 };
var arcShape1 = new THREE.Shape();
arcShape1.moveTo( 0, 0 );
arcShape1.absarc( 0, 0, 15, 0, Math.PI * 2, false );
var holePath1 = new THREE.Path();
holePath1.moveTo( 0, 10 );
holePath1.absarc( 0, 10, 2, 0, Math.PI * 2, true );
arcShape1.holes.push( holePath1 );
var geometry1 = new THREE.ExtrudeBufferGeometry( arcShape1, extrudeSettings );
var mesh1 = new THREE.Mesh( geometry1, new THREE.MeshPhongMaterial( { color: 0x804000 } ) );
scene.add( mesh1 );
var arcShape2 = new THREE.Shape();
arcShape2.moveTo( 0, 0 );
arcShape2.absarc( 0, 0, 15, 0, Math.PI * 2, false );
var holePath2 = new THREE.Path();
holePath2.moveTo( 0, 10 );
holePath2.absarc( 0, 10, 2, 0, Math.PI * 2, true );
arcShape2.holes.push( holePath2 );
var geometry2 = new THREE.ExtrudeGeometry( arcShape2, extrudeSettings );
var mesh2 = new THREE.Mesh( geometry2, new THREE.MeshPhongMaterial( { color: 0x00ff00 } ) );
scene.add( mesh2 );
// Define variables to hold the current epicyclic radius and current angle.
var mesh2AxisRadius = 30;
var mesh2AxisAngle = 0;
var animate = function () {
requestAnimationFrame( animate );
// During each animation frame, let's rotate the objects on their center axis,
// and also set the position of the epicyclic circle.
mesh1.rotation.z -= 0.02;
mesh2.rotation.z += 0.02;
mesh2AxisAngle += 0.01;
mesh2.position.set ( mesh2AxisRadius * Math.cos(mesh2AxisAngle), mesh2AxisRadius * Math.sin(mesh2AxisAngle), 0 );
renderer.render( scene, camera );
};
animate();
</script>
</body>
</html>
Note that I've used basic trigonometry within the animate function to position the epicyclic circle around the center circle, and fudged the rate of rotation for the circles (rather than doing the precise math), but there's probably a better "three.js"-way of doing this via matrices or built in functions. Given that you obviously have a strong math background, I don't think you'll have any issues with translating your 2D model of multi-epicyclic circles using basic trigonometry when porting to 3D.
Hope this helps in your decision making process on how to proceed with a 3D version of your program.
The method that I would suggest is as follows. Start with a parametrized path v(t) = (v_x(t), v_y(t), v_z(t)). Consider the following projection onto the X-Y plane: v1(t) = (v_x(t)/2, v_y(t), 0). And the corresponding projection onto the X-Z plane: v2(t) = (v_x(t)/2, 0, v_z(t)).
When we add these projections together we get the original curve. But each projection is now a closed 2-D curve, and you have solutions for arbitrary closed 2-D curves. So solve each problem. And then interleave them to get a projection where your first circle goes in the X-Y plane, your second one in the X-Z plane, your third one in the X-Y plane, your fourth one in the X-Z plane ... and they sum up to your answer!
I am trying to create spikes on earth(sphere geometry). Though everything works fines, but spikes dont align with globe. I want spike to align something like below image. But my spikes dont lookAt(new THREE.Vector3(0,0,0)) despite mentioned. Please help me out.
I purposefully mentioned code required for debugging. Let me know if you need more code for this. Below image is how i want my spikes to align with sphere.
But this is how it looks
My Main JS initialization file.
$(document).ready(function () {
// Initializing Camera
Influx.Camera = new Influx.Camera({
fov: 60,
aspectRatio: window.innerWidth / window.innerHeight,
near: 1,
far: 1000,
position: {
x: 0,
y: 0,
z: 750
}
});
//Initializing Scene
Influx.Scene = new Influx.Scene();
// Initializing renderer
Influx.Renderer = new Influx.Renderer({
clearColor: 0x000000,
size: {
width: window.innerWidth,
height: window.innerHeight
}
});
Influx.Globe = new Influx.Globe({
radius: 300,
width: 50,
height: 50
});
//
Influx.Stars = new Influx.Stars({
particleCount: 15000,
particle: {
color: 0xFFFFFF,
size: 1
}
});
Influx.moveTracker = new Influx.moveTracker();
Influx.EventListener = new Influx.EventListener();
(function animate() {
requestAnimationFrame( animate );
render();
controls.update();
})();
function render() {
camera.lookAt(scene.position);
group.rotation.y -= 0.001;
renderer.render( scene, camera );
};
});
Below is code responsible for generating spikes on Globe.
Influx.Spikes = function (lat, long) {
// convert the positions from a lat, lon to a position on a sphere.
var latLongToVector3 = function(lat, lon, RADIUS, heigth) {
var phi = (lat) * Math.PI/180,
theta = (lon-180) * Math.PI/180;
var x = -(RADIUS+heigth) * Math.cos(phi) * Math.cos(theta),
y = (RADIUS+heigth) * Math.sin(phi),
z = (RADIUS+heigth) * Math.cos(phi) * Math.sin(theta);
return new THREE.Vector3(x, y, z);
};
var geom = new THREE.Geometry();
var BoxGeometry = new THREE.BoxGeometry(1, 100, 1);
//iterates through the data points and makes boxes with the coordinates
var position = latLongToVector3(lat, long, 300, 2);
var box = new THREE.Mesh( BoxGeometry );
//each position axis needs to be set separately, otherwise the box
//will instantiate at (0,0,0)
box.position.x = position.x;
box.position.y = position.y;
box.position.z = position.z;
box.lookAt(new THREE.Vector3(0, 0, 0));
box.updateMatrix();
//merges the geometry to speed up rendering time, don't use THREE.GeometryUtils.merge because it's deprecated
geom.merge(box.geometry, box.matrix);
var total = new THREE.Mesh(geom, new THREE.MeshBasicMaterial({
color: getRandomColor(),
morphTargets: true
}));
function getRandomColor() {
var letters = '0123456789ABCDEF';
var color = '#';
for (var i = 0; i < 6; i++ ) {
color += letters[Math.floor(Math.random() * 16)];
}
return color;
};
//add boxes to the group
group.add(total);
scene.add(group);
};
Influx.Camera = function(params = {}) {
if ( !$.isEmptyObject(params) ) {
window.camera = new THREE.PerspectiveCamera(params.fov, params.aspectRatio, params.near, params.far);
camera.position.set(params.position.x, params.position.y, params.position.z);
camera.lookAt(new THREE.Vector3(0,0,0));
} else {
console.log("Trouble with Initializing Camera");
return;
}
};
Remember that lookAt takes a direction vector, you give to this method the vector (0, 0, 0), this is actually not a normalized direction vector. So you must calculate the direction:
from your box position to the center of the sphere AND normalize it.
var dir = box.position.sub(world.position).normalize();
box.lookAt(dir);
And now just a set of code good conventions that may help you:
var BoxGeometry = new THREE.BoxGeometry(1, 100, 1);
Here I would rather use another var name for the box geometry, not to mix up with the "class" definition from THREE and to follow naming conventions:
var boxGeometry = new THREE.BoxGeometry(1, 100, 1);
And here:
box.position.x = position.x;
box.position.y = position.y;
box.position.z = position.z;
You can just set:
box.position.copy(position);
I also meet this problem, and I fixed it, the solution is: box.lookAt(new THREE.Vector3(0, 0, 0)) must after box.scale.z = xxxx
could any one show my how to drag my object but still keep its previous offset in rotate animation. I try so many times but still not find the solution. When i drag an object, it didn't rotate in orbit :( .
Here is my code
HTML
<!DOCTYPE HTML>
<html>
<body>
<div id="container"></div>
<script src="http://d3lp1msu2r81bx.cloudfront.net/kjs/js/lib/kinetic-v5.0.2.min.js"></script>
</body>
</html>
CSS
body {
margin: 0px;
padding: 0px;
}
canvas {
border: 1px solid #9C9898;
}
JS
window.onload = function() {
var stage = new Kinetic.Stage({
container: 'container',
width: 500,
height: 500
});
var layer = new Kinetic.Layer(),
cx = stage.getWidth() / 2,
cy = stage.getWidth() / 2;
var bigCircle = new Kinetic.Circle({
x: cx ,
y: cy,
radius: 200,
stroke: 'lightgray',
strokeWidth: 8,
});
/*
* move center point outside of the rectangle
* with offset
*/
var smallCirlce = new Kinetic.Circle({
x: cx,
y: cy,
radius: 20,
fill:'rgb(0,102,204)',
strokeWidth: 4,
offset: {x:getRandom(-150,150), y:getRandom(-150,150)}
});
function getRandom(minNum, maxNum){
return Math.random() * (maxNum - minNum) + minNum;
};
layer.add(bigCircle);
layer.add(smallCirlce);
stage.add(layer);
// one revolution per 4 seconds
var angularSpeed = 360 / 4;
var anim = new Kinetic.Animation(function(frame) {
var angleDiff = frame.timeDiff * angularSpeed / 1000;
smallCirlce.rotate(angleDiff);
}, layer);
anim.start();
};
Not sure if this is what you are after. What I took from your description was that you want to be able to drag the blue ball and have it continue orbiting around its initial point but at a greater radius.
Firstly, here is a fiddle:
http://jsfiddle.net/Paul_Smith/206meq0a/
What this solution does is on mousedown, convert the circle's position, offset and rotation into a single coordinate representing the actual position of the circle in space.
It then allows you to drag the circle, and resets its offset to the new one by subtracting its actual position from its original position.
Probably not the best solution out there but I am not a very math oriented programmer :)
The important functions are as below:
//Calculate an angle in radians given degrees
function degreesToRadians(degrees) {
return degrees * Math.PI / 180;
}
//Transform a coordinate by way of a rotation in degrees.
//Rotated about { x: 0, y: 0 }
function translateCoordinate(coordinate, degrees) {
var radians = degreesToRadians(degrees);
return {
x: (coordinate.x * Math.cos(radians)) - (coordinate.y * Math.sin(radians)),
y: (coordinate.x * Math.sin(radians)) + (coordinate.y * Math.cos(radians))
};
}
smallCircle.on("mousedown", function () {
var rotation = smallCircle.rotation(),
offset = smallCircle.offset(),
position = smallCircle.position(),
translatedOffset;
//Calculate how the offset has changed due to the rotation
translatedOffset = translateCoordinate(offset, rotation);
//Set the position to that of the current position - the offset
smallCircle.position({
x: position.x - translatedOffset.x,
y: position.y - translatedOffset.y
});
//set the offset to 0 as it has now been added to the position
smallCircle.offset({
x: 0,
y: 0
});
//Set the rotation to 0 as it is now part of the position due to the
//translation which has taken place
smallCircle.rotation(0);
//Reset the drag position
smallCircle.stopDrag();
smallCircle.startDrag();
});
smallCircle.on("mouseup", function () {
var position = smallCircle.position();
//Calculate the new offset (original position - new position)
smallCircle.offset({
x: cx - position.x,
y: cy - position.y
});
//Set the position back to the original position
smallCircle.position({
x: cx,
y: cy
});
});
Hope this helps
I have t his effect on this website
http://www.immersive-garden.com/
there's this point of light sparking, on hover you get the background, I want something similar without using flash
this is the script I'm using right now
/*
Particle Emitter JavaScript Library
Version 0.3
by Erik Friend
Creates a circular particle emitter of specified radius centered and offset at specified screen location. Particles appear outside of emitter and travel outward at specified velocity while fading until disappearing in specified decay time. Particle size is specified in pixels. Particles reduce in size toward 1px as they decay. A custom image(s) may be used to represent particles. Multiple images will be cycled randomly to create a mix of particle types.
example:
var emitter = new particle_emitter({
image: ['resources/particle.white.gif', 'resources/particle.black.gif'],
center: ['50%', '50%'], offset: [0, 0], radius: 0,
size: 6, velocity: 40, decay: 1000, rate: 10
}).start();
*/
particle_emitter = function (opts) {
// DEFAULT VALUES
var defaults = {
center: ['50%', '50%'], // center of emitter (x / y coordinates)
offset: [0, 0], // offset emitter relative to center
radius: 0, // radius of emitter circle
image: 'particle.gif', // image or array of images to use as particles
size: 1, // particle diameter in pixels
velocity: 10, // particle speed in pixels per second
decay: 500, // evaporation rate in milliseconds
rate: 10 // emission rate in particles per second
};
// PASSED PARAMETER VALUES
var _options = $.extend({}, defaults, opts);
// CONSTRUCTOR
var _timer, _margin, _distance, _interval, _is_chrome = false;
(function () {
// Detect Google Chrome to avoid alpha transparency clipping bug when adjusting opacity
if (navigator.userAgent.indexOf('Chrome') >= 0) _is_chrome = true;
// Convert particle size into emitter surface margin (particles appear outside of emitter)
_margin = _options.size / 2;
// Convert emission velocity into distance traveled
_distance = _options.velocity * (_options.decay / 1000);
// Convert emission rate into callback interval
_interval = 1000 / _options.rate;
})();
// PRIVATE METHODS
var _sparkle = function () {
// Pick a random angle and convert to radians
var rads = (Math.random() * 360) * (Math.PI / 180);
// Starting coordinates
var sx = parseInt((Math.cos(rads) * (_options.radius + _margin)) + _options.offset[0] - _margin);
var sy = parseInt((Math.sin(rads) * (_options.radius + _margin)) + _options.offset[1] - _margin);
// Ending Coordinates
var ex = parseInt((Math.cos(rads) * (_options.radius + _distance + _margin + 0.5)) + _options.offset[0] - 0.5);
var ey = parseInt((Math.sin(rads) * (_options.radius + _distance + _margin + 0.5)) + _options.offset[1] - 0.5);
// Pick from available particle images
var image;
if (typeof(_options.image) == 'object') image = _options.image[Math.floor(Math.random() * _options.image.length)];
else image = _options.image;
// Attach sparkle to page, then animate movement and evaporation
var s = $('<img>')
.attr('src', image)
.css({
zIndex: 10,
position: 'absolute',
width: _options.size + 'px',
height: _options.size + 'px',
left: _options.center[0],
top: _options.center[1],
marginLeft: sx + 'px',
marginTop: sy + 'px'
})
.appendTo('body')
.animate({
width: '1px',
height: '1px',
marginLeft: ex + 'px',
marginTop: ey + 'px',
opacity: _is_chrome ? 1 : 0
}, _options.decay, 'linear', function () { $(this).remove(); });
// Spawn another sparkle
_timer = setTimeout(function () { _sparkle(); }, _interval);
};
// PUBLIC INTERFACE
// This is what gets returned by "new particle_emitter();"
// Everything above this point behaves as private thanks to closure
return {
start:function () {
clearTimeout(_timer);
_timer = setTimeout(function () { _sparkle(); }, 0);
return(this);
},
stop:function () {
clearTimeout(_timer);
return(this);
},
centerTo:function (x, y) {
_options.center[0] = x;
_options.center[1] = y;
},
offsetTo:function (x, y) {
if ((typeof(x) == 'number') && (typeof(y) == 'number')) {
_options.center[0] = x;
_options.center[1] = y;
}
}
}
};
you probably need something like this: http://www.realcombiz.com/2012/09/customize-blackquote-with-light-bulb.html