how to flip a cube in air using three.js - javascript

I am using three.js framework and using physi.js for forces and gravity. My problem is that i has given some position to a dice on a plane and want to lob it or flip it from its position with some angle by clicking on it such that it rotate in air and fall on plane due to gravity force of plane.
i have idea about onclickevent method but i don't know how to set the dice to jump with some height in 90 degree and rotate it with some angle only one time for one click.
if you have some suggestion please give me method to set up the dice
thank you

i have no experience with physi.js, for physics i use cannon.js, so i do have some experience with physics. i can tell you how it's done, but i cant provide an example just yet. the first thing you need to do is cast a ray from the camera to the dice. This tells you if the object is clicked on. from there you apply a force to where the object is clicked. i hope this helps.
EDIT/Follow up:
I've been looking over physi.js, and it seems pretty similar to cannon.js. Also, there are many aspects to what you're asking, but i'll try to be as comprehensive as possible.
The first thing to do is set the gravity. In the examples it is done by:
var scene = new Physijs.Scene({ fixedTimeStep: 1 / 120 });
scene.setGravity(new THREE.Vector3( 0, -30, 0 ));
the next thing to do is define the plane the dice is sitting on as a rigid body, and the dice as a rigid body.
var dice = new Physijs.BoxMesh(
new THREE.CubeGeometry(5, 5, 5), //the collision geometry
new THREE.MeshLambertMaterial({ map: THREE.ImageUtils.loadTexture('images/dicetexture.jpg' )});, // material of the dice
5, // mass, for the plane use 0, meaning it doesnt move.
{ restitution: .2, friction: .8 } //contact material
);
and finally, you need to apply a force, i cant find an example of it, but going over the source real quick, i found:
// Physijs.Mesh.applyForce
Physijs.Mesh.prototype.applyForce = function ( force, offset ) {
if ( this.world ) {
this.world.execute( 'applyForce', { id: this._physijs.id, force_x: force.x, force_y : force.y, force_z : force.z, x: offset.x, y: offset.y, z: offset.z } );
}
};
both force and offset should be vectors. force being the direction and magnitude, offset being the point of origin.
i'll experiment a bit more with physi.js it seems to have solved some problems i've been pondering with. if this still doesnt solve the problem. i'll post an example.
as for letting it spin for a set number of times in a preset height, i think that's a lot of experimenting with the values...
EDIT 2:
In the fiddle you posted the problem you explained was an undefined variable, effect.
if (intersects.length > 0) {
intersects[ 0 ].applyImpulse(effect, offset);
}
change this to:
if (intersects.length > 0) {
var effect = new THREE.Vector3( 0, 100, 0 );
var offset = new THREE.Vector3( 0, 0, 0 );
intersects[ 0 ].object.applyImpulse(effect, offset);
}
the values ARE defined, but you define them in a different function.
function apply_force(){
var effect = new THREE.Vector3( 0, 100, 0 );
var offset = new THREE.Vector3( 0, 0, 0 );
object.applyImpulse( effect, offset );
}
this function will not be used anymore, so you might as well remove it.
CLICK TIMER:
if(canClick == true){
//process click
canClick = false;
setTimeout(function(){canClick = true}, 5000)//5 seconds before the user can click again.
}

Related

In THREE.js, how to map one texture to a 3D rectangle

I'm trying to make a box in THREE that represents a box of 2x4 Legos, 24 pieces wide by 48 pieces long and and arbitrary number of pieces tall. I've generated a texture that shows this pattern using random colors:
I need to show two sides this cube, but the textures have to align so that the pieces on the edges are the same colors, like so (generated in Blender):
I'd really prefer not to make six images for a CubeTexture, particularly since four are not visible. Is it possible to flip the texture on one side so that they appear to align? (We're just going for visual effect here.)
Further, not all 3D rectangles will be cubes, but I can't quite figure out how to set the texture.repeat.x and texture.repeat.y so that the x is scaled correctly and the y is at the same scale, but just cuts off when the height of the object ends, like so:
Thanks!
You can flip an image by flipping the UVs.
You'll need to figure out which UVs correspond to the face you're trying to mirror, and which direction to flip them (not sure how your geometry is created).
Here's an example using a basic BoxBufferGeometry and modifying its uv attribute. (The face on the right is the mirrored-by-UV-flipping face.)
var textureURL = "https://upload.wikimedia.org/wikipedia/commons/0/02/Triangular_hebesphenorotunda.png";
// attribution and license here: https://commons.wikimedia.org/wiki/File:Triangular_hebesphenorotunda.png
var renderer = new THREE.WebGLRenderer({antialias:true});
document.body.appendChild(renderer.domElement);
renderer.setSize(500, 500);
var textureLoader = new THREE.TextureLoader();
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(28, 1, 1, 1000);
camera.position.set(50, 25, 50);
camera.lookAt(scene.position);
scene.add(camera);
camera.add(new THREE.PointLight(0xffffff, 1, Infinity));
var cubeGeo = new THREE.BoxBufferGeometry(20, 20, 20);
var uvs = cubeGeo.attributes.uv;
// originally:
// [0] = 0,1
// [1] = 1,1
// [2] = 0,0
// [3] = 1,0
// convert to:
// [0] = 1,1
// [1] = 0,1
// [2] = 1,0
// [3] = 0.0
uvs.setX(0, 1);
uvs.setY(0, 1);
uvs.setX(1, 0);
uvs.setY(1, 1);
uvs.setX(2, 1);
uvs.setY(2, 0);
uvs.setX(3, 0);
uvs.setY(3, 0);
uvs.needsUpdate = true;
var mat = new THREE.MeshLambertMaterial({
color: "white",
map: textureLoader.load(textureURL, function(){
animate();
})
});
var mesh = new THREE.Mesh(cubeGeo, mat);
scene.add(mesh);
function render() {
renderer.render(scene, camera);
}
function animate() {
requestAnimationFrame(animate);
render();
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/91/three.min.js"></script>
You can create six PlaneBufferGeometries assign the same material, and then position them to form a cube. Rotate them in 90deg increments until you reach the desired result. For performance reasons, you could merge these back into a single BufferGeometry.
You can export the model you made in blender, either using the THREE.js json exporter, or a format like OBJ or GLTF, and load and render it directly.
What you are talking about is simply having the UV's laid out the way you have them in blender.. so if you need that level of control.. it's probably easier to just load the model instead of trying to generate it.
If you use either three.js .json or .gltf, both exporters have an option to embed the textures directly in the export. This can make it easier to get things working quicker, at the expense of possibly less efficient storage.

Three.js: ExtrudeGeometry: Problems setting different material for front and back side

I'm creating an ExtrudeGeometry of a hexagon shape and trying to set a different material for the front side and back side, as it is stated in this thread.
let shape = new THREE.Shape();
/*...*/
let geometry = new THREE.ExtrudeGeometry(shape, {
steps: 2,
amount: 0.05,
bevelEnabled: false,
material: 0, //frontMaterial = green
extrudeMaterial: 1 //sideMaterial = gray
});
//Searching for the back side and setting the marialIndex accordingly
for (let face of geometry.faces) {
if (face.normal.z == 1) {
face.materialIndex = 2; //backMaterial = red
}
}
let mesh = new THREE.Mesh(geometry, new THREE.MultiMaterial([frontMaterial, sideMaterial, backMaterial]));
The problem now is, that this method (iterating over the faces and looking for those with normal.z == 1) does not work correctly with a extrudeGeometry.amount = 0.05. A value of 0.1 works fine.
See this jsfiddle
Is there another method for setting a different material for front and back side or am I doing it just wrong?
Thanks for your help!
The problem is due to rounding. Do this, instead
if ( face.normal.z < - 0.99999 ) { // instead of == - 1
face.materialIndex = 2;
}
Also, the back face normal is in theory (0, 0, - 1 ).
updated fiddle: https://jsfiddle.net/xhbu2e01/3/
three.js r.84

THREE.js lookAt - How to pan smoothly between old and new target positions?

Example JSfiddle
I can get my cone to point at each target sphere in turn (red,green,yellow,blue) using the THREE.js "lookAt" function.
// Initialisation
c_geometry = new THREE.CylinderGeometry(3, 40, 120, 40, 10, false);
c_geometry.applyMatrix( new THREE.Matrix4().makeRotationX( Math.PI / 2 ) );
c_material = new THREE.MeshNormalMaterial()
myCone = new THREE.Mesh(c_geometry, c_material);
scene.add(myCone);
// Application (within the Animation loop)
myCone.lookAt(target.position);
But now I want the cone to pan smoothly and slowly from the old target to the new target. I guess that I can do it by computing intermediate points on the circular arc which is centred at the cone centre Cxyz and which passes from the previous target position Pxyz to the new target position Nxyz.
Please can someone point me to suitable: (a) utilities or (b) trigonometry algorithms or (c) code examples for calculating the xyz coordinates of the intermediate points on such an arc? (I will supply the angular increment between points based on desired sweep rate and time interval between frames).
You want to smoothly transition from one orientation to another.
In your case, you would pre-calculate the target quaternions:
myCone.lookAt( s1.position );
q1 = new THREE.Quaternion().copy( myCone.quaternion );
myCone.lookAt( s2.position );
q2 = new THREE.Quaternion().copy( myCone.quaternion );
Then, in your render loop:
myCone.quaternion.slerpQuaternions( q1, q2, time ); // 0 < time < 1
three.js r.141
For those of you looking to lerp position and lookAt, you can create a initial lookAt target from the current look direction and lerp that towards the final target:
function MoveWhileLookingAt(object: Object3D, destination: Vector3, lookAt: Vector3){
const fromPosition = object.position.clone();
const fromLookAt = new Vector3(
0,
.1, // To avoid initial camera flip on certain starting points (like top down view)
-object.position.distanceTo(lookAt) // THREE.Camera looks down negative Z. Remove the minus if working with a regular object.
);
object.localToWorld(fromLookAt);
const tempTarget = fromLookAt.clone();
function LookAtLerp(alpha: number){
// This goes in your render loop
object.position.lerpVectors(fromPosition, destination, alpha);
tempTarget.lerpVectors(fromLookAt, lookAt, alpha);
object.lookAt(tempTarget);
}
}

Three.js Pointerlockcontrols shooting along y-axis

Currently I am developing a FPS with three.js and pointerlockcontrols.
Using the code below I can shoot into any horizontal direction:
var direction = new THREE.Vector3( 0, 0, -1 );
var rotation = new THREE.Euler( 0, 0, 0, "XYZ" );
var cameraDirection = new THREE.Vector3(this.game.usermodel.root.children[0].position.x, this.game.usermodel.root.children[0].rotation._x, this.game.usermodel.root.children[0].position.z);
cameraDirection.copy( direction ).applyEuler( this.game.user.rotation );
var raycaster = new THREE.Raycaster(this.game.usermodel.root.children[0].position, cameraDirection);
But my code doesn't take the y-axis into account. The line below holds the pitch rotation:
this.game.usermodel.root.children[0].rotation._x
How can I apply this value so I can shoot along the y-axis (vertically into any direction) as well? Currently the bullet is going along a straight line.
Thanks in advance for your assistance.
If you are using PointerLockControls and you want to set a raycaster, you can use this pattern:
var direction = new THREE.Vector3();
var raycaster = new THREE.Raycaster(); // create once and reuse
...
controls.getDirection( direction );
raycater.set( controls.getObject().position, direction );
Do not set the camera position or rotation directly if you are using PointerLockControls.
three.js r.71
Investigating this a bit more, I finally came up with a workaround myself. It might not be the perfect way to do this, but it works.
It now works like this: I'm getting the basic mesh rotation and apply the euler, I then add the pitch rotation. In this way I pass the horizontal and vertical rotation into the raycaster.
var direction = new THREE.Vector3( 0, 0, -1 );
direction.copy( direction ).applyEuler( this.game.user.rotation );
direction.y = this.game.usermodel.root.children[0].rotation._x;
var raycaster = new THREE.Raycaster(this.game.usermodel.root.children[0].position, direction);
Everyone is still welcome to comment on this or come up with a more elegant solution.

Thickness of lines using THREE.LineBasicMaterial

I am using the code below to create hundreds of lines in my three.js scene
edgeGeometry[i] = new THREE.Geometry();
edgeGeometry[i].vertices[0] = v(x1,y1,z1);
edgeGeometry[i].vertices[1] = v(x2,y2,z2);
edgesMat[i] = new THREE.LineBasicMaterial({
color: 0x6699FF, linewidth: 1, fog:true});
edge[i] = new THREE.Line(edgeGeometry[i], edgesMat[i]);
edge[i].type = THREE.Lines;
scene2.add(edge[i]);
It works just fine, but when i change the value of "linewidth" to a bigger OR smaller value, i see NO difference in the scene.
How should i change the thickness of the lines? Any ideas?
Thanks, Dimitris
1) Use native OpenGL
You can achieve rendering of line thicknesses with a workaround by setting your browser to use native OpenGL instead of ANGLE. You can read here on how to do this on Chrome.
Keep in mind that you will experience performance differences if you swap to native OpenGL.
EDIT:
The master MrDoob himself posted here how to do this for both Chrome and Firefox.
Note: This first option is no longer a valid solution since the latest OpenGL versions no longer support line thickness either. Check also #gman his answer. This means if you want to use line thickness the second option is the way to go.
2) Use THREE.MeshLine class
There is also another solution; this THREE.MeshLine class on github is a nice workaround. It comes with a special THREE.MeshLineMaterial. According to the docs it is as simple as:
Create and populate a geometry
Create a THREE.MeshLine and assign the geometry
Create a THREE.MeshLineMaterial
Use THREE.MeshLine and THREE.MeshLineMaterial to create a THREE.Mesh
Are you using Windows?
I remember this not working on Windows because it wasn't implemented in ANGLE.
This occurs in Windows Chrome and Firefox, both using ANGLE (WebGL to DirectX wrapper).
The issue is still not solved by the ANGLE project. You can star the issue here to get higher priority and get a notification if it's going to be implemented:
https://code.google.com/p/angleproject/issues/detail?id=119
I use TubeGeometry to create a Thick line between two points:
See Green lines in Helix
// line material
var lineMaterial = new THREE.LineBasicMaterial({ color: 0x00ff00 });
let startVector = new THREE.Vector3(
RADI * Math.cos(t),
RADI * Math.sin(t),
3 * t
);
let endVector = new THREE.Vector3(
RADI * Math.cos(t + 10),
RADI * Math.sin(t + 10),
3 * t
);
let linePoints = [];
linePoints.push(startVector, endVector);
// Create Tube Geometry
var tubeGeometry = new THREE.TubeGeometry(
new THREE.CatmullRomCurve3(linePoints),
512,// path segments
0.5,// THICKNESS
8, //Roundness of Tube
false //closed
);
let line = new THREE.Line(tubeGeometry, lineMaterial);
scene.add(line);
This is no longer an issue just in ANGLE it's an issue on all platforms. Browsers needed to switching to the OpenGL 4+ core profile to support WebGL2 and the OpenGL 4+ core profile does not support line widths greater than 1. From the OpenGL 4.0+ spec, section E.2.1
E.2.1 Deprecated But Still Supported Features
The following features are deprecated, but still present in the core profile. They may be removed from a future version of OpenGL, and are removed in a forward compatible context implementing the core profile.
Wide lines - LineWidth values greater than 1.0 will generate an INVALID_VALUE error.
To draw thicker lines you need generate geometry. For three.js there is this library (pointed out by Wilt as well)
https://github.com/spite/THREE.MeshLine
You can use CanvasRenderer instead of Webglrenderer. Check out the ifficial documentation here where each shape has a border of linewidth = 10;
You can achieve the same effect using extrude-polyline to generate a simplicial complex for the thickened (poly)line and three-simplicial-complex to convert this to a three.js Mesh:
const THREE = require('three');
const extrudePolyline = require('extrude-polyline');
const Complex = require('three-simplicial-complex')(THREE);
function thickPolyline(points, lineWidth) {
const simplicialComplex = extrudePolyline({
// Adjust to taste!
thickness: lineWidth,
cap: 'square', // or 'butt'
join: 'bevel', // or 'miter',
miterLimit: 10,
}).build(points);
// Add a z-coordinate.
for (const position of simplicialComplex.positions) {
position[2] = 0;
}
return Complex(simplicialComplex);
}
const vertices = [[0, 0], [10, 0], [10, 10], [20, 10], [30, 00]];
const geometry = thickPolyline(vertices, 10);
const material = new THREE.MeshBasicMaterial({
color: 0x009900,
side: THREE.DoubleSide
});
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
If you want to texture map the polyline, things get a little more complicated.
Thanks to Wilt's answer for pointing me in the right direction with THREE.MeshLine.
It can be slightly trickier than they make it out to be, however... So here's my solution following their docs and their demo code very carefully... (assuming you've already included Three and MeshLine):
renderer = new THREE.WebGLRenderer({ canvas });
//...
function createCircle(resolution) {
let circleGeometry = new THREE.Geometry();
for (let rotation = 0; rotation <= Math.PI * 2.0; rotation += Math.PI * 0.1) {
circleGeometry.vertices.push(
new THREE.Vector3(Math.cos(rotation), Math.sin(rotation), 0));
}
let circleLine = new MeshLine();
circleLine.setGeometry(circleGeometry);
//Bonus: parabolic width! (See Z rotation below.)
//circleLine.setGeometry(circleGeometry, function(point) {
//return Math.pow(4 * point * (1 - point), 1);
//});
//Note: resolution is *required*!
return new THREE.Mesh(circleLine.geometry,
new MeshLineMaterial({
color: 'blue',
resolution,
sizeAttenuation: 0,
lineWidth: 5.0,
side: THREE.DoubleSide
}));
}
let circle = createCircle(new THREE.Vector2(canvas.width, canvas.height));
circle.rotation.x = Math.PI * 0.5;
circle.position.y = 20.0;
scene.add(circle);
//In update, to rotate the circle (e.g. if using parabola above):
world.circle.rotation.z += 0.05;
With size attenuation off and using THREE.DoubleSide, like I did above, the circle will look like a nice, consistent circle no matter where you're looking at it from (not "true 3D").
For just a line, you can obviously easily adapt.
Why not set the opacity to something like 0.1?
NOTE: This only works if you are giving borders to something, if there's nothing behind it the it won't work.

Categories