THREE.Box3.setFromObject(*object*) returns wrong values. The best way to show you is by showing you how I work through it:
I create 2 meshes from vertices. First one with the triangle() function, the other with trapezoidForm().
var triangle = function (base, height) {
return [
new THREE.Vector2(0, -height / 2),
new THREE.Vector2(-base / 2, height / 2),
new THREE.Vector2(base / 2, height / 2)
];
}
var trapezoidForm = function (base, upperBase, height) {
return [
new THREE.Vector2(-base / 2, height / 2),
new THREE.Vector2(-upperBase / 2, -height / 2),
new THREE.Vector2(upperBase / 2, -height / 2),
new THREE.Vector2(base / 2, height / 2),
];
}
I use the returned value to create my mesh:
var material = new THREE.MeshPhongMaterial({ color: 0x666666, /*specular: 0x101010*//*, shininess: 200*/ });
var shape = new THREE.Shape(vertices);
var mesh = new THREE.Mesh(new THREE.ShapeGeometry(shape), material);
And use that to place it in the scene, and to create a boundingbox:
mesh.position.set(posX, 0, posZ);
mesh.rotation.set(-Math.PI / 2, 0, 0);
boundingBox.setFromObject(mesh);
Now, I want to find the center of my 2 shapes. Easy enough: I take the boundingbox, and calculate it. Like this:
var centerX = (boundingBox.max.x + boundingBox.min.x) * 0.5;
var centerZ = (boundingBox.max.z + boundingBox.min.z) * 0.5;
Here is where it goes wrong: For the triangle, it calculates the right spot, but for the trapezoid, it messes up.
Below is a printscreen of the console. The first 3 vertices are for the triangle, followed by the boundingbox. The next 4 are for the trapezoid, with again the bounding box. For the vertices: first number is X-coord, second one is Z-coord.
Desired result: 2nd boundingbox should return something like:
max: X: 200
Z: 200
min: X: -200
Z: -100
Image showing the current state (triangle has the minus-sign in the middle, trapezoid not):
Found the solution myself in the end:
I have to create my boundingBox before I reposition or rotate it:
//Boundingbox
boundingBox.setFromObject(mesh);
mesh.position.set(posX, 0, posZ);
mesh.rotation.set(-Math.PI / 2, 0, 0);
instead of:
mesh.position.set(posX, 0, posZ);
mesh.rotation.set(-Math.PI / 2, 0, 0);
//Boundingbox
boundingBox.setFromObject(mesh);
the reason why the triangle didn't cause problems, was because it was rendered on 0,0.
Related
I can't figure out how to properly rotate an object in ThreeJS. The object is a simple box geometry that is rendered from above somewhere on the screen.
Codepen with the full code.
The object is supposed to rotate around it's own Y axis (the vertical axis) to always face the mouse cursor. I can get it to rotate as the cursor moves around the global axis in the middle of the screen, but not when the cursor moves around the object's own local axis.
UPDATE: I got it to work using ray casting. See code further down or in the codepen.
The orthographic camera is set up like this:
camera = new THREE.OrthographicCamera(
window.innerWidth / - 2, // left
window.innerWidth / 2, // right
window.innerHeight / 2, // top
window.innerHeight / - 2, // bottom
0, // near
1000 ); // far
camera.position.set(0, 0, 500)
camera.updateProjectionMatrix()
The object is set up like this:
const geometry = new THREE.BoxGeometry( 10, 10, 10 );
const material = new THREE.MeshBasicMaterial( { color: "grey" } );
const mesh = new THREE.Mesh( geometry, material );
Code for handling rotation at mousemove:
function onMouseMove(event) {
// Get mouse position
let mousePos = new THREE.Vector2();
mousePos.set(
(event.clientX / window.innerWidth) * 2 - 1, // x
-(event.clientY / window.innerHeight) * 2 + 1); // y
// Calculate angle
let angle = Math.atan2(mousePos.y, mousePos.x);
// Add rotation to object
mesh.rotation.set(
0, // x
angle, // y
0) // z
}
I have also tried
mesh.rotateY(angle)
but this only makes the object spinn like a helicopter.
It's obvious the rotation needs to be based on the relationship between the cursor and the local axis rather than the global axis. I just can't figure out how to achieve that.
UPDATE
I have added a codepen at the top of the question.
UPDATE
I got it to work using the following method with ray casting.
let plane = new THREE.Plane(new THREE.Vector3(0, 0, 1), 0);
let pointOfIntersection = new THREE.Vector3();
let raycaster = new THREE.Raycaster();
let mousePos = new THREE.Vector2();
mousePos.set(
(event.clientX / window.innerWidth) * 2 - 1, // x
-(event.clientY / window.innerHeight) * 2 + 1))
raycaster.setFromCamera(mousePos, camera);
raycaster.ray.intersectPlane(plane, pointOfIntersection);
mesh.lookAt(pointOfIntersection)
What I ultimately want is a vector, giving the direction of the green line in the image below, knowing only the position of the yellow and green dots.
To be more specific, it's angle can be random as long as it's endpoint ends up somewhere on the green-blue surface of the cylinder. So, 360° free around cylinder, and about 15° limited to the edges of the cylinder.
The cylinder is perpendicular to the line from the yellow and green dot.
Length is not important, only direction.
My main problem is I don't know how to go from vector Yellow to green dot, to any vector perpendicular to it.
PS None of these things are aligned on a x y z axis. That grid is not xyz, just to help visualize.
here is the code: given an angle theta and two points it will give you a vector starting from pointStart perpendicular to the vector from pointStart to pointEnd:
function perpendicularVector(pointStart,pointEnd,theta){
let vDiff = new THREE.Vector3(0, 0, 0)
.subVectors(pointEnd, pointStart)
.normalize()
let V = new THREE.Vector3(
vDiff.y + vDiff.x * vDiff.z,
vDiff.y * vDiff.z -vDiff.x,
-(vDiff.x * vDiff.x) - vDiff.y * vDiff.y
)
return
V .applyAxisAngle(vDiff, theta)
.applyAxisAngle( new THREE.Vector3().multiplyVectors(V, vDiff).normalize(), 15*Math.PI/180 )
}
here is a small showoff of what the above code do: (the snippet is intentionally bad because its there just to show the functionality of the above code)
(you can zoom rotate and pan using the mouse on the render that appears after you click run snippet)
body {
font-family: sans-serif;
margin: 0;
background-color: #e2cba9;
width: 100%;
height: 100%;
overflow: hidden;
}
canvas {
width: 100%;
height: 100%;
}
<div id="app"></div>
<script type="module">
import { OrbitControls } from "https://cdn.jsdelivr.net/npm/three#0.121.1/examples/jsm/controls/OrbitControls.js";
import * as THREE from "https://cdn.jsdelivr.net/npm/three#0.121.1/build/three.module.js";
var scene = new THREE.Scene, theta = 0;
let point1 = new THREE.Vector3(4, 2, 1),
point2 = new THREE.Vector3(0, 3, 3);
function perpendicularVector(e, n, t) {
let r = new THREE.Vector3(0, 0, 0).subVectors(n, e).normalize(),
o = new THREE.Vector3(r.y, -r.x, 0),
i = new THREE.Vector3(r.x * r.z, r.y * r.z, -r.x * r.x - r.y * r.y);
var a = o.multiplyScalar(Math.cos(t)).add(i.multiplyScalar(Math.sin(t)));
return a.add(e), a
}
function pointAtCoords(e, n) {
let t = new THREE.MeshBasicMaterial({ color: n }),
r = new THREE.SphereGeometry(.1, 8, 8),
o = new
THREE.Mesh(r, t);
return o.position.add(e), o
}
function lineFromAtoB(e, n, t) {
let r = new THREE.LineBasicMaterial({ color: t }),
o = [];
o.push(e), o.push(n);
let i = (new THREE.BufferGeometry).setFromPoints(o);
return new THREE.Line(i, r)
}
var renderer = new THREE.WebGLRenderer({ antialias: !0 });
renderer.setSize(window.innerWidth, window.innerHeight), document.getElementById("app").appendChild(renderer.domElement);
var camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight,
.1, 1e3);
camera.position.set(7, 7, 8), camera.lookAt(new THREE.Vector3), camera.position.add(new THREE.Vector3(3, 0, 3));
var controls = new OrbitControls(camera, renderer.domElement);
function drawEverything(e) {
const n = new THREE.AxesHelper(30);
scene.add(n);
const t = new THREE.GridHelper(30, 30);
t.position.add(new THREE.Vector3(15, 0, 15)), scene.add(t);
const r = new THREE.GridHelper(30, 30);
r.rotateX(Math.PI / 2), r.position.add(new THREE.Vector3(15, 15, 0)), scene.add(r);
const o = new THREE.GridHelper(30, 30);
o.rotateZ(Math.PI / 2), o.position.add(new THREE.Vector3(0, 15, 15)), scene.add(o);
let i = new THREE.Vector3(0, 0, 0),
a = perpendicularVector(point1, point2, e);
scene.add(pointAtCoords(point1, 16776960)), scene.add(pointAtCoords(point2, 65280));
var d = pointAtCoords(a, 255);
scene.add(d), scene.add(lineFromAtoB(point1, point2, 16711935)), scene.add(lineFromAtoB(i, point1, 16711680)), scene.add(lineFromAtoB(i, point2, 16711680)), scene.add(lineFromAtoB(point1, a, 65280))
}
function animate() {
scene = new THREE.Scene, drawEverything(theta += .1),
setTimeout((() => {
requestAnimationFrame(animate)
}), 1e3 / 30), renderer.render(scene, camera)
}
animate();
</script>
This is totally achievable with some math calculations. The term you're looking for is "Orthogonal vectors", which means vectors that are perpendicular to each other. The cylinder radius is orthogonal to the line between blue to yellow points.
However, since you're already using Three.js, you can just let it do all the hard work for you with the help of an Object3D.
// Declare vectorA (center, green)
const vecA = new THREE.Vector3(xA, yA, zA);
// Declare vectorB (destination, yellow)
const vecB = new THREE.Vector3(xB, yB, zB);
// Create helper object
const helper = new THREE.Object3D();
// Center helper at vecA
helper.position.copy(vecA);
// Rotate helper towards vecB
helper.lookAt(vecB);
// Move helper perpendicularly along its own y-axis
const cylinderRadius = 27;
helper.translateY(cylinderRadius);
// Now you have your final position!
console.log(helper.position);
In the diagram below, the helper Object3D is shown as a red line only to give you a sense of its rotation and position, but in reality it is invisible unless you add a Mesh to it.
If you want to add/subtract 15 degrees from the perpendicular, you could just rotate the helper along its own x-axis before translateY()
const xAngle = THREE.MathUtils.degToRad(15);
helper.rotateX(xAngle);
const cylinderRadius = 27;
helper.translateY(cylinderRadius);
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!
Scenario
I making a 2d visualization of two round rectangles as following:
const app = new PIXI.Application({ backgroundColor: 0xffffff} );
const roundBox = new PIXI.Graphics();
roundBox.lineStyle(4, 0x99CCFF, 1);
roundBox.beginFill(0xffffff);
roundBox.drawRoundedRect(0, 0, 200, 100, 10);
roundBox.endFill();
const roundBox2 = new PIXI.Graphics();
roundBox2.lineStyle(4, 0x99CCFF, 1);
roundBox2.beginFill(0xffffff);
roundBox2.drawRoundedRect(0, 0, 200, 100, 10);
roundBox2.endFill();
app.stage.addChild(roundBox, roundBox2);
I want to append horizontally the second rectangle without calculating the horizontal position of the second child.
Question
Is there a way to insert horizontally a graphic element in pixi.js?
You can rotate the second rectangle 90 degrees, like this:
// Center the point to rotate this rectangle at its center
roundBox2.pivot = new PIXI.Point(100, 50);
// Math.PI / 2 is equal to 90 degrees in radians.
roundBox2.rotation = Math.PI / 2;
Trying to develop an Agar clone, and I've got a lot of it, but I can't quite figure out how to decrement the player's speed as its mass increases. I've tried several different ways, but nothing works. How would I make the speed go down as the mass goes up? Here's my jsFiddle. This is where I set the speed of of the players:
var playerOneMass = 36;
var player1X = (canvas.width / 2) + 50;
var player = new Player({
x: player1X,
y: canvas.height / 2,
radius: playerOneMass,
speed: {
x: 5,
y: 5
},
name: "player 1",
dir: null
});
var playerTwoMass = 36;
var player2X = (canvas.width / 2) - 50;
var player2 = new Player({
x: player2X,
y: canvas.height / 2,
radius: playerTwoMass,
speed: {
x: 5,
y: 5
},
name: "player 2",
dir: null
});
Let us bring some math in to help us out a little bit. When you want something to grow smaller as another grows bigger, the best option that I have found is to use an inversely proportional relationship. This will allow a smooth smaller and smaller look for you.
new_speed = scalar * start_speed / current_mass
When coming up with the scalar, I have found it best to trial and error until it looks how you want it to.
Here is an example of the equation in action utilizing Two.js.
var two = new Two({width:320, height:180}).appendTo(document.getElementById("mytwo")),
rect = two.makeRectangle(100, 100, 10, 10),
circ = two.makeCircle(5, 100, 5),
mass = 10,
rspeed = Math.PI / 10,
mspeed = 14,
scalar = 10;
// Make it look pretty!
rect.fill = "rgb(100,255,100)";
circ.fill = "rgb(100,100,255)";
// Looping...
two.bind('update', function(fc) {
// Prevents from growing indefinitely
if(mass > 150) return;
mass += 1.5;
rect.scale += .1;
circ.scale += .1;
rect.rotation += scalar * rspeed / mass;
circ.translation.addSelf(new Two.Vector(
scalar * mspeed / mass, 0));
}).play();
<script type="text/javascript" src="http://cdn.jsdelivr.net/gh/jonobr1/two.js/build/two.min.js"></script>
<div id="mytwo"><div></div></div>