Insert child element after another in Pixi.js - javascript

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;

Related

Rotate object to face the mouse cursor

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)

Changing the way three.js projects a texture onto a halfsphere

I am currently trying to project an image onto the inside of a halfsphere in a three.js project. The half sphere is created via
const geometry = new THREE.SphereGeometry(Component.radius, this.resolution, this.resolution,
Math.PI, Math.PI, 0, Math.PI);
this.material = new THREE.MeshStandardMaterial({color: 0xffffff});
this.material.side = THREE.DoubleSide;
this.sphere = new THREE.Mesh(geometry, this.material);
// The image of the texture is set later dynamically via
// this.material.map = textureLoader.load(...);
With radius and resolution being constants. This works fine, except for one issue: The image becomes distorted around the "top" and "bottom" of the sphere, like this:
Simple example of distorted texture:
The texture originally had the value "0,0" in the bottom left and "0,1" in the bottom right, and with the camera facing down from the center of the demisphere the bottom left and bottom right corner are squished onto the "bottom" point of the sphere.
I want to change this behavior so the texture corners are instead on where they would be if you place the square texture into a sphere, with the corners touching the circle, then stretching the lines between the corners to match the circle. Simple mock of what i mean:
I have tried playing with the mapping Attribute of my texture, but that doesn't change the behaviour from my understanding.
After changing the UV coordinates, my half sphere texture is't stretching on border as well:
this.sphereGeometry = new THREE.SphereGeometry(
10,
32,
24,
0,
Math.PI,
0,
Math.PI
);
const {
uv: uvAttribute,
normal
} = this.sphereGeometry.attributes;
for (let i = 0; i < normal.count; i += 1) {
let u = normal.getX(i);
let v = normal.getY(i);
u = u / 2 + 0.5;
v = v / 2 + 0.5;
uvAttribute.setXY(i, u, v);
}
const texture = new THREE.TextureLoader().load(
'https://i.imgur.com/CslEXIS.jpg'
);
texture.flipY = false;
texture.mapping = THREE.CubeUVRefractionMapping;
texture.needsUpdate = true;
const basicMaterial = new THREE.MeshBasicMaterial({
map: texture,
side: THREE.DoubleSide,
});
this.sphere = new THREE.Mesh(this.sphereGeometry, basicMaterial);
this.scene.add(this.sphere);

setFromObject(mesh) returns wrong values, sometimes

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.

Raphael JS position rectangle based on center coordinates

Is there a way to position a rectangle in Raphael.js based on the center coordinates (much like positioning a circle)?
You can do it by simply creating your own custom element, here is an example on how it can be done :
Raphael.fn.MyRect = function( cx, cy, width, height ) {
var xLeftTop = cx - (width / 2);
var yLeftTop = cy - (height / 2);
this.rectObj = paper.rect( xLeftTop, yLeftTop, width, height );
return this;
};
var paper = Raphael(10, 50, 320, 200);
paper.MyRect( 95, 35, 50, 50 ); // 95 is the center in x and 35 the center in y
A live example : http://jsfiddle.net/7QMbH/
This way, you can create as many rectangle you want, and it makes your code understandable.
The easiest way is to create your rectangle with coordinates x - rect_width/2 and y - rect_heigth/2:
Example:
var rect_w = 180,
rect_h = 80;
var paper = Raphael(10, 10, 500, 500);
paper.rect(100 - rect_w/2, 100 - rect_h/2, rect_w, rect_h, 5);
// The last 5 gives curved edges to you rectangle (if you need it of course).
Basically, instead of 100,100 for top-left, you give 10,60. Good Luck

Fill three.js scene with a grid

What I am looking for
Display a grid within a three.js scene that fills the entire scene. In this case, the scene is the whole window.
This grid represents a surface in 3D and can be moved around with the mouse using THREE.TrackballControls This grid is facing the camera so initially it looks like a flat (2D) surface until the trackball is pulled around with the mouse.
The width of the grid lines should equal to the width of the renderer.
What I did
I have setup a working jsFiddle for what I have done so far.
First I find the bounds of the scene (all of this is in the jsFiddle),
App = function(sceneContainerName) {
this.sceneContainerName = sceneContainerName;
this.SCREEN_WIDTH = window.innerWidth;
this.SCREEN_HEIGHT = window.innerHeight;
this.MAX_X = this.SCREEN_WIDTH / 2;
this.MIN_X = 0 - (this.SCREEN_WIDTH / 2);
this.MAX_Y = this.SCREEN_HEIGHT / 2;
this.MIN_Y = 0 - (this.SCREEN_HEIGHT / 2);
this.NUM_HORIZONTAL_LINES = 50;
this.init();
};
Setup three.js
init: function() {
// init scene
this.scene = new THREE.Scene();
// init camera
// View Angle, Aspect, Near, Far
this.camera = new THREE.PerspectiveCamera(45, this.SCREEN_WIDTH / this.SCREEN_HEIGHT, 1, 10000);
// set camera position
this.camera.position.z = 1000;
this.camera.position.y = 0;
// add the camera to the scene
this.scene.add(this.camera);
this.projector = new THREE.Projector();
// init renderer
this.renderer = new THREE.CanvasRenderer();
// start the renderer
this.renderer.setSize(this.SCREEN_WIDTH, this.SCREEN_HEIGHT);
this.drawGrid(this.NUM_HORIZONTAL_LINES);
this.trackball = new THREE.TrackballControls(this.camera, this.renderer.domElement);
this.trackball.staticMoving = true;
var me = this;
this.trackball.addEventListener('change', function() {
me.render();
});
// attach the render-supplied DOM element
var container = document.getElementById(this.sceneContainerName);
container.appendChild(this.renderer.domElement);
this.animate();
},
These functions provide a vector for each screen corner,
getNWScreenVector: function() {
return new THREE.Vector3(this.MIN_X, this.MAX_Y, 0);
},
getNEScreenVector: function() {
return new THREE.Vector3(this.MAX_X, this.MAX_Y, 0);
},
getSWScreenVector: function() {
return new THREE.Vector3(this.MIN_X, this.MIN_Y, 0);
},
getSEScreenVector: function() {
return new THREE.Vector3(this.MAX_X, this.MIN_Y, 0);
},
I create some geometry that will represent a line at the very top of the screen, and try to draw lines starting from the top and going down to the bottom of the screen.
// drawGrid will determine blocksize based on the
// amount of horizontal gridlines to draw
drawGrid: function(numHorizontalGridLines) {
// Determine the size of a grid block (square)
this.gridBlockSize = this.SCREEN_HEIGHT / numHorizontalGridLines;
var geometry = new THREE.Geometry();
geometry.vertices.push(this.getNWScreenVector());
geometry.vertices.push(this.getNEScreenVector());
var material = new THREE.LineBasicMaterial({
color: 0x000000,
opacity: 0.2
});
for (var c = 0; c <= numHorizontalGridLines; c++) {
var line = new THREE.Line(geometry, material);
line.position.y = this.MAX_Y - (c * this.gridBlockSize);
this.scene.add(line);
}
}
The problem
This method does not work, in the jsFiddle the first line starts off the screen and the width of the lines do not fill the screen width.
Since ThreeJS r57 onwards, there is a helper called GridHelper using which you can easily draw a nice grid, just like any other geometric object.
GridHelper takes 2 parameters. First one is the size of the grid and the 2nd one is the size of the step between 2 lines
Below is the code to draw the grid on the scene, with the size = 100 and step = 10
var grid = new THREE.GridHelper(100, 10);
scene.add(grid);
In your case, you can avoid having a method called drawGrid and directly replace that with the above two lines code, or you can add these above two lines of code with in the drawgrid method.
A live example is available here in the following link
Grid Helper Example
So there are two bugs in your code.
First, you're starting with the line at MAX_Y and then putting each line a fixed distance below the last. The relatively minor bug is that in getNWScreenVector and getNEScreenVector you're putting the line's vertices at a height of MAX_Y, then in
line.position.y = this.MAX_Y - (c * this.gridBlockSize);
you're adding a translation to the line of MAX_Y - (c * this.gridBlockSize), which gives a final y position of MAX_Y + MAX_Y - (c * this.gridBlockSize), which is one MAX_Y too many. If it makes sense to your program to start with lines descending from getNWScreenVector and getNEScreenVector, then you should change the line.position.y line to just
line.position.y = -c * this.gridBlockSize;
You can see that the lines are now centered on jsFiddle, but they're still sized incorrectly. This is because you aren't accounting for the fact that your scene is in perspective. Your lines all have their z coordinates set to 0, so when you set this.camera.position.z = 1000, they are 1000 units away from the camera. There is no reason to assume that something with the same width as the pixel width of the canvas will have the same width when drawn in perspective from 1000 units away.
Instead, we can calculate how big they need to be. I won't go in to a full explanation of perspective here, but we can figure out how big an area the lines need to cover to cover the screen. You've specified a vertical FOV of 45 degrees in your camera constructor and a distance of 1000 between the camera and your lines. Three.js basically shows the solution if you peak into how it creates the perspective matrix: makePerspective
First, we need the vertical distance of the upper half of the screen, since 0 is at the center of the screen. Math.tan(45 / 2 * (Math.PI / 180)) (tangent of half the angle, converted to radians) gives the vertical distance divided by the distance away from the camera, so you can calculate the height with
halfDisplayHeight = 1000 * Math.tan(45 / 2 * (Math.PI / 180));
or double it to
this.DISPLAY_HEIGHT = 2 * 1000 * Math.tan(45 / 2 * (Math.PI / 180));
The horizontal FOV is not the same unless the canvas is square, but the width and height ratio of the line area will be proportional to the width and height ratio of the screen. Like three.js does, you can just multiply by the aspect ratio you also provided to the camera constructor to figure out the width:
this.DISPLAY_WIDTH = this.DISPLAY_HEIGHT * (this.SCREEN_WIDTH / this.SCREEN_HEIGHT);
These are the values that you should use for placing your lines. All together:
this.DISPLAY_HEIGHT = 2 * 1000 * Math.tan(45 / 2 * (Math.PI / 180));
this.DISPLAY_WIDTH = this.DISPLAY_HEIGHT * (this.SCREEN_WIDTH / this.SCREEN_HEIGHT);
this.MAX_X = this.DISPLAY_WIDTH / 2;
this.MIN_X = 0 - (this.DISPLAY_WIDTH / 2);
this.MAX_Y = this.DISPLAY_HEIGHT / 2;
this.MIN_Y = 0 - (this.DISPLAY_HEIGHT / 2);
Finally, you'll want to spread the lines over the full area, so you should set
this.gridBlockSize = this.DISPLAY_HEIGHT / numHorizontalGridLines;
You can see that working here: http://jsfiddle.net/pa46hxwo/
There is another way to do this, though, which is to keep the lines the same, but move the camera closer to your lines so that the width of the lines just happens to equal the pixel width of the canvas at that distance. The formula is just a reworking of the above for DISPLAY_HEIGHT, but instead of solving for height, we solve for a distance when the height equals the screen height:
this.camera.position.z = this.SCREEN_HEIGHT / (2 * Math.tan(45 / 2 * (Math.PI / 180)));
You can see that here: http://jsfiddle.net/0jtykgrL/
It's a much smaller change to your code, but it means that the camera position will change depending on how big the canvas is, which could affect other content which you need to place precisely, so the choice is yours.
You can draw a grid like this.
// each square
var planeW = 50; // pixels
var planeH = 50; // pixels
var numW = 50; // how many wide (50*50 = 2500 pixels wide)
var numH = 50; // how many tall (50*50 = 2500 pixels tall)
var plane = new THREE.Mesh(
new THREE.PlaneGeometry( planeW*numW, planeH*numH, planeW, planeH ),
new THREE.MeshBasicMaterial( {
color: 0x000000,
wireframe: true
} )
);
scene.add(plane);
This thread helped with a variation of the technique applied to A-Frame and with a slight twist; dynamically creating and sizing the grid as a function of the bounding box for the scene's children.
Note: The following method must be invoked to get the the bounding box coordinates that are initially null, unlike the bounding sphere which is pre-calculated for you:
'boundingBox.geometry.computeBoundingBox();'
Codepen for this 'scene-ario' (pun intended) can be found here: https://codepen.io/ubermario/pen/JvZMPg
<!DOCTYPE html>
<html lang="en" >
<head>
<meta charset="UTF-8">
<link rel="shortcut icon" type="image/x-icon" href="https://static.codepen.io/assets/favicon/favicon-8ea04875e70c4b0bb41da869e81236e54394d63638a1ef12fa558a4a835f1164.ico" />
<link rel="mask-icon" type="" href="https://static.codepen.io/assets/favicon/logo-pin-f2d2b6d2c61838f7e76325261b7195c27224080bc099486ddd6dccb469b8e8e6.svg" color="#111" />
<title>CodePen - A-Frame Dynamically Sized GridHelper</title>
</head>
<body translate="no" >
<script src="https://aframe.io/releases/0.8.0/aframe.min.js"></script>
<a-scene>
<a-entity>
<a-sphere position="0 1.25 -5" radius="1.25" color="#EF2D5E"></a-sphere>
<a-box position="-1 0.5 -3" rotation="0 45 0" width="1" height="1" depth="1" color="#4CC3D9"></a-box>
<a-cylinder position="1 0.75 -3" radius="0.5" height="1.5" color="#FFC65D"></a-cylinder>
<!-- replaced with gridhelper>
<a-plane position="0 0 -4" rotation="-90 0 0" width="4" height="4" color="#7BC8A4"></a-plane> -->
<!-- Remove sky; it distorts the dimensions of just the sphere, box, cylinder objects
<a-sky color="#ECECEC"></a-sky>
-->
<a-entity>
</a-scene>
<script >
AFRAME.registerComponent("cmpGridHelper", {
schema: {
size: { default: 5, type: "int" },
divisions: { default: 10, type: "int" },
colorCenterLine: { default: "red", type: "color" },
colorGrid: { default: "black", type: "color" },
x: { default: 0, type: "number" },
y: { default: 0, type: "number" },
z: { default: 0, type: "number" }
},
init: function() {
var entity = this.el.object3D,
schema = this.data,
grid = new THREE.GridHelper(
schema.size,
schema.divisions,
schema.colorCenterLine,
schema.colorGrid
);
grid.name = "gridHelper";
entity.add(grid);
},
update: function(oldData) {
var entity = this.el.object3D,
grid = entity.getObjectByName("gridHelper"),
schema = this.data;
grid.position.set(schema.x, schema.y, schema.z);
},
remove: function() {
var entity = this.el.object3D;
entity.remove(entity.getObjectByName("gridHelper"));
}
});
var sceneObj3d = document.querySelector("a-scene");
if (sceneObj3d.hasLoaded) {
fnAddDyanmicGridHelper();
} else {
sceneObj3d.addEventListener("loaded", fnAddDyanmicGridHelper);
}
function fnAddDyanmicGridHelper() {
//var helper = new THREE.BoundingBoxHelper(sceneObj3d.querySelector('a-entity').object3D, 0xff0000);
var childObjects = sceneObj3d.querySelector('a-entity').object3D;
var boundingBox = new THREE.BoxHelper(childObjects, 0xff0000);
//Uncomment the next line to display the bounding box
//childObjects.add(boundingBox);
//Need the following method call to get the geometry.boundingBox coordinates in addition to the sphere coordinates
boundingBox.geometry.computeBoundingBox();
var x = boundingBox.geometry.boundingSphere.center.x;
var z = boundingBox.geometry.boundingSphere.center.z;
var size = boundingBox.geometry.boundingSphere.radius * 2;
sceneObj3d.setAttribute("cmpGridHelper", {
x: x,
z: z,
y: boundingBox.geometry.boundingBox.min.y,
size: size,
divisions: size
});
}
</script>
</body>
</html>

Categories

Resources