How to center and scale object threejs - javascript

I am using the following code to scale and center a msgpack compressed object loaded using the ObjectLoader and it is not working. I think that my object has a rotation on it, and hence causing weird behaviors. On some objects, it successfully centers, but on others the centering is offset and scaling isn't right either.
In this snippet, result is the scene from the ObjectLoader. My thought was that the object was not very well formed, but I'm not sure. I wanted the table on the image or any other user entered mesh to be on the top of the grid, centered and scaled so that the maximum size is 1 unit.
Each square measures 0.25, the axis are at 0,0,0 http://i.stack.imgur.com/fkKYC.png
// result is a threejs scene
var geometry = result.children[0].geometry;
var mesh = result.children[0];
geometry.computeBoundingBox();
var middle = new THREE.Vector3();
middle.x = ( geometry.boundingBox.max.x + geometry.boundingBox.min.x ) / 2;
middle.y = -geometry.boundingBox.min.y;
middle.z = ( geometry.boundingBox.max.z + geometry.boundingBox.min.z ) / 2;
middle.negate();
mesh.position.copy(middle);
// scales the mesh up to maxsize
var maxSize = 1;
// gets the biggest axis
var maxVal = geometry.boundingBox.max.x - geometry.boundingBox.min.x;
if (maxVal < geometry.boundingBox.max.y - geometry.boundingBox.min.y) {
maxVal = geometry.boundingBox.max.y - geometry.boundingBox.min.y;
}
if (maxVal < geometry.boundingBox.max.z - geometry.boundingBox.min.z) {
maxVal = geometry.boundingBox.max.z - geometry.boundingBox.min.z;
// scales the current size proportional to the maxval, times maxsize
mesh.scale.divideScalar(maxVal * maxSize);
self.scene.add(result);

Instead of calling geometry.computeBoundingBox(); call geometry.center(); then you don't need the middle.x or middle.z and you can just call mesh.translateY() rather than fiddling with middle at all

Related

Using After Effects Expression To Script A Simple Shadow Behavior

I'm working on an AE project where around 50 Emojis should have a drop shadow on the floor.To make things easier I tried to add an expression that auto shrinks and grows the shadows based on the distance of the emoji to the floor.
Here is what I've tried
Drop Shadow Approach
You can see that the shadow grows and shrinks but in the wrong direction. So when emoji comes closer to the floor it shrinks and when the distance is more it grows. I need the opposite of the current behavior.
How do I achieve that?
This is the expression I've used for the scale property of the shadow layer. Shadow layer is separate from the emoji layer. So I have a composition with only 2 layers.
var y = thisComp.layer("smile").position[1];
var dist = Math.sqrt( Math.pow((this.position[0]-this.position[0]), 2) + Math.pow((this.position[1]-y), 2) );
newValue = dist ;
xScale = newValue;
yScale = newValue;
[xScale,yScale]
Thanks for your time.
The basic concept here is mapping values from one range to another. You want to say that (for example) as the distance changes between 0 and 100, the scale should change proportionally between 1 and 0.
function map ( x, oldMin, oldMax, newMin, newMax ) {
return newMin + ( x - oldMin ) / ( oldMax - oldMin ) * ( newMax - newMin );
}
var minDistance = 0;
var maxDistance = 100;
var maxScale = 1;
var minScale = 0;
xScale = yScale = map( dist, minDistance, maxDistance, maxScale, minScale );

How can I change this Three.js ConvexGeometry to a non-convex geometry?

I'm worked with Three.JS before, but not on meshes. I think I am approaching my problem the right way, but I'm not sure.
The Goal
I'm trying to make a 3D blobby object that has specific verticies. The direction of the verticies are fixed, but their radius from center varies. You can imagine it sort of like an audio equalizer, except radial and in 3D.
I'm open to scrapping this approach and taking a totally different one if there's some easier way to do this.
Current Progress
I took this example and cleaned/modified it to my needs. Here's the HTML and JavaScript:
HTML (disco-ball.html)
<!DOCTYPE html>
<html>
<head>
<title>Disco Ball</title>
<script type="text/javascript" src="../libs/three.js"></script>
<script type="text/javascript" src="../libs/stats.js"></script>
<script type="text/javascript" src="../libs/ConvexGeometry.js"></script>
<script type="text/javascript" src="../libs/dat.gui.js"></script>
<style type='text/css'>
/* set margin to 0 and overflow to hidden, to go fullscreen */
body { margin: 0; overflow: hidden; }
</style>
</head>
<body>
<div id="Stats-output"></div>
<div id="WebGL-output"></div>
<script type="text/javascript" src="01-app.js"></script>
</body>
</html>
And the JavaScript (01-app.js):
window.onload = init;
const PARAMS = {
SHOW_SURFACE : true,
SHOW_POINTS : true,
SHOW_WIREFRAME : true,
SHOW_STATS : true
};
// once everything is loaded, we run our Three.js stuff.
function init() {
var renderParams = {
webGLRenderer : createWebGLRenderer(),
step : 0,
rotationSpeed : 0.007,
scene : new THREE.Scene(),
camera : createCamera(),
};
// Create the actual points.
var points = getPoints(
100, // Number of points (approximate)
10, // Unweighted radius
// Radius weights for a few points. This is a multiplier.
[2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2]
);
if (PARAMS.SHOW_STATS) {
renderParams.stats = initStats();
}
if (PARAMS.SHOW_SURFACE) {
renderParams.surface = getHullMesh(points);
renderParams.scene.add(renderParams.surface);
}
if (PARAMS.SHOW_POINTS) {
renderParams.sphereGroup = getSphereGroup(points);
renderParams.scene.add(sphereGroup);
}
render(renderParams);
}
function render(params) {
if (params.stats) {
params.stats.update();
}
if (params.sphereGroup) {
params.sphereGroup.rotation.y = params.step;
}
params.step += params.rotationSpeed;
if (params.surface) {
params.surface.rotation.y = params.step;
}
// render using requestAnimationFrame
requestAnimationFrame(function () {render(params)});
params.webGLRenderer.render(params.scene, params.camera);
}
// ******************************************************************
// Helper functions
// ******************************************************************
function getPoints (count, baseRadius, weightMap) {
// Because this is deterministic, we can pass in a weight map to adjust
// the radii.
var points = distributePoints(count,baseRadius,weightMap);
points.forEach((d,i) => {
points[i] = new THREE.Vector3(d[0],d[1],d[2]);
});
return points;
}
// A deterministic function for (approximately) evenly distributing n points
// over a sphere.
function distributePoints (count, radius, weightMap) {
// I'm not sure why I need this...
count *= 100;
var points = [];
var area = 4 * Math.PI * Math.pow(radius,2) / count;
var dist = Math.sqrt(area);
var Mtheta = Math.round(Math.PI / dist);
var distTheta = Math.PI / Mtheta
var distPhi = area / distTheta;
for (var m = 0; m < Mtheta; m++) {
let theta = (Math.PI * (m + 0.5)) / Mtheta;
let Mphi = Math.round((2 * Math.PI * Math.sin(theta)) / distPhi);
for (var n = 0; n < Mphi; n++) {
let phi = ((2 * Math.PI * n) / Mphi);
// Use the default radius, times any multiplier passed in through the
// weightMap. If no multiplier is present, use 1 to leave it
// unchanged.
points.push(createPoint(radius * (weightMap[points.length] || 1),theta,phi));
}
}
return points;
}
function createPoint (radius, theta, phi) {
var x = radius * Math.sin(theta) * Math.cos(phi);
var y = radius * Math.sin(theta) * Math.sin(phi);
var z = radius * Math.cos(theta);
return [Math.round(x), Math.round(y), Math.round(z)];
}
function createWebGLRenderer () {
// create a render and set the size
var webGLRenderer = new THREE.WebGLRenderer();
webGLRenderer.setClearColor(new THREE.Color(0xEEEEEE, 1.0));
webGLRenderer.setSize(window.innerWidth, window.innerHeight);
webGLRenderer.shadowMapEnabled = true;
// add the output of the renderer to the html element
document.getElementById("WebGL-output").appendChild(webGLRenderer.domElement);
return webGLRenderer;
}
function createCamera () {
// create a camera, which defines where we're looking at.
var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
// position and point the camera to the center of the scene
camera.position.x = -30;
camera.position.y = 40;
camera.position.z = 50;
camera.lookAt(new THREE.Vector3(0, 0, 0));
return camera;
}
function getSphereGroup (points) {
sphereGroup = new THREE.Object3D();
var material = new THREE.MeshBasicMaterial({color: 0xFF0000, transparent: false});
points.forEach(function (point) {
var spGeom = new THREE.SphereGeometry(0.2);
var spMesh = new THREE.Mesh(spGeom, material);
spMesh.position.copy(point);
sphereGroup.add(spMesh);
});
return sphereGroup;
}
function getHullMesh (points) {
// use the same points to create a convexgeometry
var surfaceGeometry = new THREE.ConvexGeometry(points);
var surface = createMesh(surfaceGeometry);
return surface;
}
function createMesh(geom) {
// assign two materials
var meshMaterial = new THREE.MeshBasicMaterial({color: 0x666666, transparent: true, opacity: 0.25});
meshMaterial.side = THREE.DoubleSide;
var wireFrameMat = new THREE.MeshBasicMaterial({color: 0x0000ff});
wireFrameMat.wireframe = PARAMS.SHOW_WIREFRAME;
// create a multimaterial
var mesh = THREE.SceneUtils.createMultiMaterialObject(geom, [meshMaterial, wireFrameMat]);
return mesh;
}
function initStats() {
var stats = new Stats();
stats.setMode(0); // 0: fps, 1: ms
// Align top-left
stats.domElement.style.position = 'absolute';
stats.domElement.style.left = '0px';
stats.domElement.style.top = '0px';
document.getElementById("Stats-output").appendChild(stats.domElement);
return stats;
}
What I'm Missing
You can see that there are two points on the "ball" for which I've doubled the radius (big spikes). Of course, since I'm using a ConvexGeometry, the shape is convex... so a number of the points are hidden. What kind of ... non-convex geometry can I use to make those points no longer be hidden?
I would like to subdivide the mesh a bit so it's not simply vertex-to-vertex, but a bit smoother. How can I do that (the spikes less spikey and more blobby)?
I'd like to modify the mesh so different points spike different amounts every few seconds (I have some data arrays that describe how much). How do I modify the geometry after its been made? Ideally with some kind of tweening, but I can do without of that's extremely hard =)
Thanks!
Smooth and animate a mesh.
Three provides a huge range of options. These are just suggestions, your best bet is to read the Three documentation start point and find what suits you.
A mesh is just a set of 3D points and an array of indexes describing each triangle. Once you have built the mesh you only need to update the verts and let Three update the shader attributes, and the mesh normals
Your questions
Q1. Use Three.Geometry for the mesh.
Q2. As you are building the mesh you can use the curve helpers eg Three.CubicBezierCurve3 or Three.QuadraticBezierCurve3 or maybe your best option Three.SplineCurve
Another option is to use a modifier and create the simple mesh and then let Three subdivide the mesh for you. eg three example webgl modifier subdivision
Though not the fastest solution, if the vert count is low it will do this each frame without any loss of frame rate.
Q3. Using Three.Geometry you can can set the mesh morph targets, an array of vertices.
Another option is to use a modifier, eg three example webgl modifier subdivision
Or you can modify the vertices directly each frame.
for ( var i = 0, l = geometry.vertices.length; i < l; i ++ ) {
geometry.vertices[ i ].x = ?;
geometry.vertices[ i ].y = ?;
geometry.vertices[ i ].z = ?;
}
mesh.geometry.verticesNeedUpdate = true;
How you do it?
There are a zillion other ways to do this. Which is the best will depend on the load and amount of complexity you want to create. Spend some time and read the doc's, and experiment.
What I would do! maybe?
I am not too sure what you are trying to achieve but the following is a way of getting some life into the animation rather than the overdone curves that seem so ubiquitous these days.
So if the vert count is not too high I would use a Three.BufferGeometry and modify the verts each frame. Rather than use curves I would weight subdivision verts to follow a polynomial curve f(x) = x^2/(x^2 + (1-x)^2) where x is the normalized distance between two control verts (note don't use x=0.5 rather subdivide the mesh in > 2 times)
EG the two control points and two smoothing verts
// two control points
const p1 = {x,y,z};
const p2 = {x,y,z};
// two weighted points
// dx,dy,dz are deltas
// w is the weighted position s-curve
// wa, and wd are acceleration and drag coefficients. Try to keep their sum < 1
const pw1 = {x, y, z, dx, dy, dz, w : 1/3, wa : 0.1,wd : 0.7};
const pw2 = {x, y, z, dx, dy, dz, w : 2/3, wa : 0.1,wd : 0.7};
// Compute w
pw1.w = Math.pow(pw1.w,2) / ( Math.pow(pw1.w,2) + Math.pow(1 - pw1.w,2));
pw2.w = Math.pow(pw2.w,2) / ( Math.pow(pw2.w,2) + Math.pow(1 - pw2.w,2));
Then for each weighted point you can find the new delta and update the position
// do for x,y,z
x = (p2.x - p1.x); // these points are updated every frame
// get the new pw1 vert target position
x = p1.x + x * w;
// get new delta
pw1.dx += (x - pw1.x) * pw1.wa; // set delta
pw1.dx *= pw1.wd;
// set new position
pw1.x += pw1.dx;
Do for all weighted points then set geometry.vertices
The wa,wd coefficients will change the behaviour of the smoothing, you will have to play with these values to suit your own taste. Must be 0 <= (wa,wd) < 1 and the sum should be wa + wd < 1. High sumed values will result in oscillations, too high and the oscillations will be uncontrolled.

Three JS convert 3D dimensions to 2D

I would like to take the world space coordinates of a sphere to screenspace coordinates in order to get the screen space bounds, which I can then use to overlay a div.
Ideally I would like to extend this function to return the height and width of the object, as well as the x & y :
toScreenPosition : function (obj, camera)
{
var vector = new THREE.Vector3();
var widthHalf = 0.5*world.renderer.context.canvas.width;
var heightHalf = 0.5*world.renderer.context.canvas.height;
obj.updateMatrixWorld();
vector.setFromMatrixPosition(obj.matrixWorld);
vector.project(camera);
vector.x = ( vector.x * widthHalf ) + widthHalf;
vector.y = - ( vector.y * heightHalf ) + heightHalf;
return {
x: vector.x,
y: vector.y
};
},
You can create few THREE.Object3D and locate them in the scene in position of the border of the main object you want to project to the screen.
then you can use the method you used on the main object on the other empty objects and get the pixels position on the screen of the border of the main object.
If for example you want to know the screen coordinates of the border of a sphere that has a radius of 5:
var first = new THREE.Object3D();
var second = new THREE.Object3D();
var third = new THREE.Object3D();
var fourth = new THREE.Object3D();
first.position.set(sphere.x,sphere.y+5,sphere.z);
second.position.set(sphere.x,sphere.y-5,sphere.z);
then you can apply the function you wrote, but instead of:
obj.updateMatrixWorld();
etc...
you will do:
first.updateMatrixWorld();
second.updateMatrixWorld();
etc...
then you will have the x,y coordinates of those two objects (that are on border of the main object) on screen and you can check the height by subtracting.
I would write a function that takes as input a 3D point and returns the 2D screenpoint, like the one presented there. Then if you deal with a sphere, that would be easy: get the center of the sphere in 3D, compute the 2D point, that will be the center of the overlay div, get any 3D point on the surface of the sphere, compute the 2D point, you will know the required radius of the overlay div, from there you can easily compute a rectangular area.

PIXI container bigger than stage?

While trying to follow this tutorial for pixi.js I came across something I can't quite understand.
I set the size of the renderer to 512 by 512, but when trying to render the stage with a TilingSprite, the scene gets cut before it reaches the end of the canvas, like so:
EDIT: the blackness you see on the image above is still within the 512 by 512 canvas itself, it is not the browser's background.
Instead of reproducing the floor tile forever, the scene just becomes black after 100 pixels or so.
What's happening here?
Here is the code:
PIXI.SCALE_MODES.DEFAULT = PIXI.SCALE_MODES.NEAREST;
//Aliases
var Container = PIXI.Container,
autoDetectRenderer = PIXI.autoDetectRenderer,
loader = PIXI.loader,
resources = PIXI.loader.resources,
TextureCache = PIXI.utils.TextureCache,
Texture = PIXI.Texture,
Sprite = PIXI.Sprite;
TilingSprite = PIXI.TilingSprite;
var stage = new Container();
stage.scale.set(3,3);
var renderer = autoDetectRenderer(512, 512);
document.body.appendChild(renderer.view);
PIXI.loader
.add("src/tileset.json")
.load(setup);
function setup() {
var id = PIXI.loader.resources["src/tileset.json"].textures;
let dude = new Sprite( id["dude"] );
let blob = new Sprite( id["blob"] );
let floor = new TilingSprite( id["floor"] );
let chest = new Sprite( id["chest"] );
//Position the chest next to the right edge of the canvas
chest.x = stage.width - chest.width - 48;
chest.y = stage.height / 2 - chest.height / 2;
stage.addChild(floor);
stage.addChild(chest);
stage.addChild(dude);
renderer.render(stage);
}
Turns out TilingSprite takes width and height as parameters as well, and while I erroneously assumed that it would just repeat until the stage was completely covered, it has a default value of 100px for both width and height:
#param {PIXI.Texture} texture - the texture of the tiling sprite
#param {number} [width=100] - the width of the tiling sprite
#param {number} [height=100] - the height of the tiling sprite
Initializing it with the dimensions I wanted solved the issue.

how to render alphabets in 2D using threejs

I'm making a 2d game, where blocks are falling down ( tetris style). I need to render alphabets on these blocks. This is how I am creating blocks:
var geometry = new THREE.BoxGeometry( this.BLOCK_WIDTH, this.BLOCK_WIDTH, 4 );
var material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } );
this.blocks = [];
for (var i = 0; i < rows * columns; i++) {
cube = new THREE.Mesh( geometry, material );
cube.visible = false;
cube.letter = letterGenerator.getNextLetter();
this.blocks[i] = cube;
scene.add( this.blocks[i] );
};
As you can see, all blocks will look exactly alike except for the fact, that they will have a different alphabet associated with them. In my update(), I move the block, left/right or down. When I do so, block position will be updated and obviously the alphabet should be rendered accordingly.
How should I go about rendering alphabets on these blocks ?
EDIT: I am using WebGLRenderer.
You can get the screen position of each block (your "cube" variable above) that you want to paint text on and use HTML to paint text at that screen location over each block. Using HTML to make a text sprite like this is discussed here:
https://github.com/mrdoob/three.js/issues/1321
You can get the screen position for your "cube" above like so:
var container = document.getElementById("idcanvas");
var containerWidth = container.clientWidth;
var containerHeight = container.clientHeight;
var widthHalf = containerWidth / 2, heightHalf = containerHeight / 2;
var locvector = new THREE.Vector3();
locvector.setFromMatrixPosition(cube.matrixWorld);
locvector.project(your_camera); //returns center of mesh
var xpos = locvector.x = (locvector.x * widthHalf) + widthHalf; //convert to screen coordinates
var ypos = locvector.y = -(locvector.y * heightHalf) + heightHalf;
You'll have to update the HTML for cube movement.
Another approach is to create specific textures with the text you want and apply each texture as a material to the appropriate cube.

Categories

Resources