I made a simple threeJS 3d app in which you can look around and move with 4 keys. The problem is that it moves in the same direction no matter where you look to, obviously because I use:
camera.position.x += 0.1
You have to set the default displacement of (0.1, 0, 0) to a Vector3, then apply the camera's rotation (its quaternion) to that vector before adding it to its position. See the code below:
// Create camera
var camera = new THREE.PerspectiveCamera();
// Create displacement vector to re-use
var dispVector = new THREE.Vector3();
function onRightKey() {
dispVector.set(0.1, 0, 0); // Right key moves 0.1 on x axis
applyDisplacement();
}
function applyDisplacement() {
// We apply the camera's rotation to the displacement vector
dispVector.applyQuaternion(camera.quaternion);
// Move the camera position by the resulting displacement vector
camera.position.add(dispVector);
}
You can apply this same idea to the other keys: (-0.1, 0, 0) if you want to move left, or (0, 0.1, 0) if you want to move up, etc:
function onLeftKey() {
dispVector.set(-0.1, 0, 0); // Left key moves -0.1 on x axis
applyDisplacement();
}
function onUpKey() {
dispVector.set(0, 0.1, 0); // Left key moves 0.1 on y axis
applyDisplacement();
}
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 have a vector like this
{x: 0, y: 0, z: 1}
Then I have another vector that is a normal, a direction, like this
{x: 1, y: 0, z: 0}
How do i rotate the vector based on the direction in the normal so it looks like this?
{x: 1, y: 0, z: 0}
I'm using Three.js
After digging in to this answer I come up with a solution that seems to work
https://github.com/mrdoob/three.js/issues/1486
rotateVectorWithNormal(toRotate: Vector3, normal: Vector3) {
const newVector: Vector3 = new Vector3().copy(toRotate);
// set up direction
let up = new Vector3(0, 1, 0);
let axis: Vector3;
// we want the vector to point in the direction of the face normal
// determine an axis to rotate around
// cross will not work if vec == +up or -up, so there is a special case
if (normal.y == 1 || normal.y == -1) {
axis = new Vector3(1, 0, 0);
} else {
axis = new Vector3().cross(up, normal);
}
// determine the amount to rotate
let radians = Math.acos(normal.dot(up));
const quat = new Quaternion().setFromAxisAngle(axis, radians);
newVector.applyQuaternion(quat);
return newVector;
}
Code in Typescript
While the auto-answer is correct, here some general points about such a rotation:
If only two vectors are given, namely a and b, there are infinite rotations transforming a into b. The answer above takes the shortest rotation but requires to determine the axis of rotation via a cross product. A second solution is to take the bisector as rotation axis and rotate by Pi. Here you would normalize to a_n and b_n and rotate around (a_n + b_n).
The difference between the rotations would only affect non-rotational symmetric object.
If all vectors are normalized already it should be as simple as
var a = new THREE.Vector3( 0, 0, 1 );
var b = new THREE.Vector3( 1, 0, 0 );
var c = new THREE.Vector3( x, y, z );
var quaternion = new THREE.Quaternion();
quaternion.setFromAxisAngle( a + b, Math.PI );
c.applyQuaternion( quaternion );
If c==a then c is rotated onto b and if c==b then c is rotated onto a.
I've created some objects in my scene, and set up raycasting/tweening code so that whenever I click on an object, the object animates directly to the position and rotation of the camera.
This is my code for raycasting/tweening the object:
function onDocumentMouseDown( event ) {
event.preventDefault();
mouse.x = ( event.clientX / renderer.domElement.clientWidth ) * 2 - 1;
mouse.y = - ( event.clientY / renderer.domElement.clientHeight ) * 2 + 1;
raycaster.setFromCamera( mouse, camera );
var intersects = raycaster.intersectObjects( scene.children );
if ( intersects.length > 0 ) {
new TWEEN.Tween( intersects[ 0 ].object.position ).to( {
x: 0,
y: 0,
z: -100 }, 2000 )
.easing( TWEEN.Easing.Elastic.Out).start();
new TWEEN.Tween( intersects[ 0 ].object.rotation ).to( {
x: 0,
y: 0,
z: 0 }, 2000 )
.easing( TWEEN.Easing.Elastic.Out).start();
object.lookAt.camera;
}
}
However, I was wondering, how can I make the tween animate the camera to the object, rather than the object to the camera? I want to do this because I want the objects to be constantly rotating and moving around the scene, and would like the camera to be able to stay on and track individual objects.
This is all in perspective camera, by the way.
Let's assume you are using OrbitControls.js to control your camera.
What you need to do then is set the controls target by controls.target = new THREE.Vector3(0, 0, -100); to the center of you object(or tween it). You are setting your objects rotation to (0, 0 ,0), so let's say you want to look at your object from top, you set your camera position to (0, 10, -100) - same as you target, just offset in the direction you want to be looking form.
Assuming your objects rotation is not (0, 0 ,0), you need to calculate its forward vector (or any other vector you want to be looking at it from), and put the camera position somewhere along the axis of this vector.
I am trying to do something similar, here is what i have so far but struggling with getting the objects face direction vector
function toObj(obj) {
var lookAtVector = new THREE.Vector3(0, 0, 1);
lookAtVector.applyQuaternion(obj.quaternion);
console.log(lookAtVector);
var rotateTween = new TWEEN.Tween(controls.target)
.to({
x: obj.position.x,
y: obj.position.y,
z: obj.position.z
}, 4000)
.interpolation(TWEEN.Interpolation.CatmullRom)
.easing(TWEEN.Easing.Quintic.InOut)
.start();
var goTween = new TWEEN.Tween(camera.position)
.to({
x: obj.position.x,
y: obj.position.y,
z: obj.position.z + 10
}, 4000)
.interpolation(TWEEN.Interpolation.CatmullRom)
.easing(TWEEN.Easing.Quintic.InOut);
goTween.start();
goTween.onComplete(function() {
console.log('done!');
});
}
you will need to add
controls = new THREE.TrackballControls(camera);
I'm trying to scale a moving object in THREE, however when I scale it it will scale my object and move it to another position.
I use the following to set the scale of the object
// initiates the scale transition
function doTriangleScale(intersection){
var tween = new TWEEN.Tween(intersection.object.parent.scale)
.to({
x: scaleSize,
y: scaleSize,
z: scaleSize,
}, scaleEase)
tween.start();
// should have a check if the user is still on the object in question, no time for this now
setTimeout(function(){
doTriangleScaleRevert(intersection);
}, 2000)
}
// triggers the scale revert to default
function doTriangleScaleRevert(intersection) {
var tween = new TWEEN.Tween(intersection.object.parent.scale)
.to({
x: 1,
y: 1,
z: 1
}, scaleEase)
tween.start();
}
This works if the objects are not moving about, however my objects ARE moving about with the following code in the render animation
scene.traverse(function (e) {
if (e instanceof THREE.Mesh && e.name != "pyramid") {
e.rotation.x += rotationSpeed;
e.rotation.y += rotationSpeed;
e.rotation.z += rotationSpeed;
if(triangleFloatLeft){
e.position.x += 0.01;
}else{
e.position.x -= 0.01;
}
}
});
I'm looking for a solution that will scale the objects from it's center.
Thanks!
Objects scale around their origin. For example, if you have a cube:
var geo = new THREE.BoxGeometry(1, 1, 1);
If you scale a mesh with geometry geo, it will grow around its center. However if you move the geometry's origin:
geo.applyMatrix(new THREE.Matrix4().makeTranslation(0, 0.5, 0));
Now it will grow upwards, because the origin was moved to the box's floor.
The origin of your object is likely not at its center. You can move the origin either in the model itself (if you imported it) or in Three.js as above.