THREE.JS Mesh position smooth animation - javascript

I have a pretty easy question for you with three.js about x position translation of an imported .obj mesh.
I'm fairly new to three.js and was wandering if someone could give me some lead on what to do or solve this problem.
So... I have this mesh on (0,-200,0) and i just wanted to move it to (50,-200,0) with a smooth translation through a button back and forth to the two positions.
objLoader = new THREE.OBJLoader();
objLoader.load('models/map_real.obj', function (obj) {
var blackMat = new THREE.MeshStandardMaterial({color:0xfaf9c9});
obj.traverse(function (child) {
if (child instanceof THREE.Mesh) {
child.material = blackMat;
}
});
obj.castShadow = true;
obj.receiveShadow = true;
obj.position.set(0,-200,0)
obj.rotation.y = getDeg(-20);
scene.add(obj);
objWrap = obj;
});
I have in my main.js an init() which contains all the functions such as camera(), animate(), render() etc... and from the variable objWrap.position.x it logs the correct position.
I've tried to capture the click (as the snippet below shows) on my #test button and only increment the position by 0.5 - - i get this, is not in the animate loop so it cant keep add 0.5.
$('#test').click(function(){
if (objWrap.position.x <= 50) {
objWrap.position += 0.5}
});
So the final result that i want is a button that toggle back and forth a smooth animation that goes from objWrap.position.x = 0 to objWrap.position.x = 50
I hope to have been clear, feel free to ask if you need to know more, i'll respond in seconds... All the help is truly appreciate!

Just an example of how you can do it with Tween.js:
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.setScalar(50);
camera.lookAt(scene.position);
var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
scene.add(new THREE.GridHelper(200, 100));
var box = new THREE.Mesh(new THREE.BoxGeometry(2, 2, 2), new THREE.MeshBasicMaterial({
color: "aqua"
}));
scene.add(box);
btnMove.addEventListener("click", onClick, false);
var forth = true;
function onClick() {
new TWEEN.Tween(box.position)
.to(box.position.clone().setX(forth ? 50 : 0), 1000)
.onStart(function() {
btnMove.disabled = true;
})
.onComplete(function() {
btnMove.disabled = false;
forth = !forth;
})
.start();
}
render();
function render() {
requestAnimationFrame(render);
TWEEN.update();
renderer.render(scene, camera);
}
body {
overflow: hidden;
margin: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/94/three.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/tween.js/17.2.0/Tween.min.js"></script>
<button id="btnMove" style="position:absolute;">Move</button>

This works with ReactJS,
import TWEEN.
import {TWEEN} from "three/examples/jsm/libs/tween.module.min";
Enable drag controller and tween animation.
const { gl, camera } = useThree();
const objects = []; // objects.push(ref.current)
useEffect(() => {
const controls = new DragControls( objects, camera, gl.domElement );
controls.addEventListener( 'dragend', (e) => {
new TWEEN.Tween(e.object.position)
.to(new Vector3(x, y, z), 1000)
.easing(TWEEN.Easing.Back.InOut)
.start();
});
})
UseFrame to show the animation.
useFrame(() => {
TWEEN.update();
});
Hope to help someone.

Related

Can't get material from a gltf loaded scene in threejs

I'd like to modify the material in a gltf loaded model to set its opacity/transparency.
I can't seem to get any meshes. I have created this fiddle that says 'no mesh found'. I have found quite a few examples of getting material and they all seem to do it this way (making sure it's mesh then getting the material).
<style>
canvas {
display: block;
width: 98%;
height: 98%;
}
</style>
<canvas id="canvasid"></canvas>
<script src="https://cdn.jsdelivr.net/npm/three#0.143/examples/js/controls/OrbitControls.js"></script>-->
<script async src="https://unpkg.com/es-module-shims#1.3.6/dist/es-module-shims.js"></script>
<script type="importmap">
{
"imports": {
"three": "https://cdn.jsdelivr.net/npm/three#0.143/build/three.module.js"
}
}
</script>
<script type="module">
import * as THREE from 'three';
import { GLTFLoader } from 'https://cdn.jsdelivr.net/npm/three#0.143/examples/jsm/loaders/GLTFLoader.js';
import { OrbitControls } from 'https://cdn.jsdelivr.net/npm/three#0.143/examples/jsm/controls/OrbitControls.js';
var scene = new THREE.Scene();
scene.background = new THREE.Color(0x6699cc);
const lights = [];
lights[0] = new THREE.PointLight(0xffffff, 1, 0);
lights[1] = new THREE.PointLight(0xffffff, 1, 0);
lights[2] = new THREE.PointLight(0xffffff, 1, 0);
lights[0].position.set(0, 100, 0);
lights[1].position.set(100, 100, 100);
lights[2].position.set(- 100, - 100, - 100);
scene.add(lights[0]);
scene.add(lights[1]);
scene.add(lights[2]);
const loader = new GLTFLoader();
loader.load("https://threejs.org/examples/models/gltf/DamagedHelmet/glTF/DamagedHelmet.gltf", function (gltf) {
const mesh = gltf.scene.children[0];
scene.add(mesh);
//my main question is about this code - looks like everyone can test if isMesh, then can get the material. I have tried multiple models and can never find any mesh. If I just assumme a material that doesn't work either
gltf.scene.traverse((o) => {
if (o.isMesh) {
console.log("found mesh");
console.log(o && o.material);
} else {
console.log("no mesh found");
}
});
}, undefined, function (error) {
console.error(error);
});
var canvas = document.getElementById("canvasid");
var renderer = new THREE.WebGLRenderer({
canvas: canvas,
antialias: true
});
renderer.setSize(canvas.parentElement.clientWidth, canvas.parentElement.clientHeight);
var camera = new THREE.PerspectiveCamera(1, canvas.parentElement.clientWidth / canvas.parentElement.clientHeight, 1, 1000);
camera.position.z = 100;
const controls = new OrbitControls(camera, renderer.domElement);
controls.update();
controls.autoRotate = true;
controls.enableDamping = true;
var animate = function () {
requestAnimationFrame(animate);
controls.update();
renderer.render(scene, camera);
};
window.addEventListener('resize', function () {
renderer.setSize(canvas.parentElement.clientWidth, canvas.parentElement.clientHeight);
camera.aspect = canvas.parentElement.clientWidth / canvas.parentElement.clientHeight;
camera.updateProjectionMatrix();
}, false);
animate();
How can I access the mesh/material?
The traverse does not work since you add the first child of gltf.scene to scene. That means you change the object hierarchy and thus break the traversal.
To fix this, simply move the below two lines of code after the call of Object3D.traverse().
const mesh = gltf.scene.children[0];
scene.add(mesh);
Also, consider to just do this since a glTF asset does not necessarily only have a single child mesh:
scene.add(gltf.scene);
Updated fiddle: https://jsfiddle.net/ftcnby9e/

ThreeJS extension for Thingworx

I am trying to create a 3 JS extension for Thingworx, but the renderHtml keeps bugging in combination with a 3 JS canvas in it (See code).
//runtime.ts file
renderHtml(): string {
let htmlString = '<div class="widget-content"><canvas></canvas></div>';
return htmlString;
}
afterRender(): void {
const OrbitControls = require('three-orbit-controls')(CourseView);
const OBJLoader = require('three-obj-loader')(CourseView);
var scene = new CourseView.Scene();
var width = this.getProperty('SceneWidth', 0);
var height = this.getProperty('SceneHeight', 0);
var color = this.getProperty('SceneColor', '#000000');
if(width <= 0) { width = window.innerWidth }
if(height <= 0) { height = window.innerHeight }
if(color == undefined){ color = "#000000" }
var ratio = width / height;
var camera = new CourseView.PerspectiveCamera(75, ratio, 0.1, 1000);
camera.position.z = 30;
var cv = this.jqElement.find("canvas").get(0);
console.log(cv);
this.renderer = new CourseView.WebGLRenderer({canvas: cv});
this.renderer.setSize(width, height);
this.renderer.setClearColor("#0000ff");
var control = new OrbitControls(camera, this.renderer.domElement);
const geometry = new CourseView.SphereGeometry( 15, 32, 16 );
const material = new CourseView.MeshBasicMaterial( { color: 0xff00ff, wireframe: true } );
const sphere = new CourseView.Mesh( geometry, material );
scene.add( sphere );
control.addEventListener('change', () => this.myRender(scene, camera));
this.myRender(scene, camera);
}
myRender(scene, camera) {
this.renderer.render(scene, camera);
}
As shown, the WebGLRenderer gets the canvas inside the div with the class widget-content. I need this div, to realize bindings of Thingworks. When I leave out the div, everything works fine. If the div exists to implement bindings, the sphere is not rendered. Moreover, the renderer seems stuck and also has no blue background, despite the clear-color call.
When I click on it (maybe its not updated) the color changes to blue, but still there is no sphere. Does anyone has realized ThreeJS in Thingworx and can show me how they did it? I think maybe the div widget-content does apply some changes to all childern (also my ThreeJS canvas), but I cant tell which changes... Maybe someone knows?
Full code: https://www.toptal.com/developers/hastebin/olelowawih.js
For those of you might having this problem in the future, check your setter, you might want to render there as well and keep track of NaN values...

THREE.PointerLockControls doesn't lock my pointer

Uncaught TypeError: THREE.PointerLockControls is not a constructor
I can't use firstperson controls for whatever reason, I am really lost for reason with this one. It's got me really stumped.
const THREE = require('THREE');
var FirstPersonControls = require('first-person-controls');
const CANNON = require('cannon');
var keyboard = new THREEx.KeyboardState();
var lights = [];
var camSpeed = 1;
var world, mass, body, body2, shape, shape2, timeStep=1/60,
camera, scene, renderer, geometry, material, mesh, textureCube;
initThree();
initCannon();
animate();
function initCannon() {
world = new CANNON.World();
world.gravity.set(0,-9.8,0);
world.broadphase = new CANNON.NaiveBroadphase();
world.solver.iterations = 10;
shape = new CANNON.Box(new CANNON.Vec3(1,1,1));
shape2 = new CANNON.Box(new CANNON.Vec3(50,1,50));
mass = 1;
body = new CANNON.Body({
mass: 1
});
body2 = new CANNON.Body({
mass: 0
});
body.position.set(1,10,1);
body.addShape(shape);
body2.addShape(shape2);
body.angularDamping = 0.5;
world.addBody(body);
world.addBody(body2);
}
function initThree() {
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 10000 );
var controls = new THREE.FirstPersonControls(camera);
controls.lookSpeed = 0.1;
controls.movementSpeed = 10;
var clock = new THREE.Clock(true);
var prefix = ".png"
var r = __dirname + "/skyboxes/mp_cliffside/";
var urls = [
r + "px" + prefix, r + "nx" + prefix,
r + "py" + prefix, r + "ny" + prefix,
r + "pz" + prefix, r + "nz" + prefix
];
textureCube = new THREE.CubeTextureLoader().load( urls );
var dottedAlphaMap = new THREE.TextureLoader().load( __dirname + "/textures/brickmap.png" );
var dottedAlphaMap2 = new THREE.TextureLoader().load( __dirname + "/textures/stonemap-wet-texture.jpg" );
scene.background = textureCube;
lights[0] = new THREE.PointLight( '#ffffff', 3, 100 );
lights[0].position.set( 0, 5, 0 );
scene.add( lights[0] );
scene.add( camera );
renderer = new THREE.WebGLRenderer({ alpha:false });
renderer.setSize( window.innerWidth, window.innerHeight );
camera.position.y = 40;
camera.rotation.x = -90 * Math.PI / 180;
document.body.appendChild( renderer.domElement );
}
function animate() {
requestAnimationFrame( animate );
updatePhysics();
render();
}
var controllee = camera;
function updatePhysics() {
// Step the physics world
world.step(timeStep);
// Copy coordinates from Cannon.js to Three.js
lights[0].position.copy(camera.position)
}
function render() {
requestAnimationFrame(render);
controls.update(clock.getDelta());
if(keyboard.pressed("F")){
camera.fov += 0.1;
camera.updateProjectionMatrix();
}
if(keyboard.pressed("G")){
camera.fov -= 0.1;
camera.updateProjectionMatrix();
}
if(keyboard.pressed("space")){
controllee.translateY(camSpeed/10);
}
if(keyboard.pressed("shift")){
controllee.translateY(-camSpeed/10);
}
if(keyboard.pressed("W")){
controllee.translateZ(-camSpeed/10);
}
if(keyboard.pressed("S")){
controllee.translateZ(camSpeed/10);
}
if(keyboard.pressed("A")){
controllee.translateX(-camSpeed/10);
}
if(keyboard.pressed("D")){
controllee.translateX(camSpeed/10);
}
if(keyboard.pressed("I")){
controllee.rotateX(camSpeed/100);
}
if(keyboard.pressed("K")){
controllee.rotateX(-camSpeed/100);
}
if(keyboard.pressed("J")){
controllee.rotateY(camSpeed/100);
}
if(keyboard.pressed("L")){
controllee.rotateY(-camSpeed/100);
}
if(keyboard.pressed("U")){
controllee.rotateZ(camSpeed/100);
}
if(keyboard.pressed("O")){
controllee.rotateZ(-camSpeed/100);
}
renderer.render( scene, camera );
}
I am using imported three.js and cannon.js, from node package manager.
I am trying to get the controls to be like an fps, but stuff like this keeps getting in my way!
Any help is appreciated, the only thing i can think of is that its not included in the NPM version of three, in which case, I'm SOL
Update: I have changed my code to include three via a tag. Same goes with the PointerLockControls, but now the problem is that I dont know how the heck to lock the pointer.
UNDERSTAND that you have to use "controls.lock()" to effectively lock your mouse to the screen (your pointer will disappear and you will be able to look around like a FPS game).
Unfortunately, you CAN NOT lock the mouse pointer from code. Instead, a user interaction WITH A DOM ELEMENT is required.
The simplest Dom element you can use is the "body", by using document.body.. see:
//add document.body to PointerLockControls constructor
let fpsControls = new PointerLockControls( camera , document.body );
//add event listener to your document.body
document.body.addEventListener( 'click', function () {
//lock mouse on screen
fpsControls.lock();
}, false );
NOTE 1: no need to call fpsControls.update() in animation function;
NOTE 2: make sure your body section is IN FRONT of the canvas and covering the entire screen, setting z-index: -1 on the canvas' CSS if necessary (or setting z-index: 10, in body's CSS ). Example:
body{
z-index: 10
margin: 0;
padding: 0;
height: 100vh;
width: 100vh;
overflow: hidden;
}
This way, you can click anywhere in the screen to experience the expected behavior. ESC will make you unlock the controller
Keep Calm and Happy Coding
but now the problem is that I don't know how the heck to lock the pointer.
You can do it like in the following example:
Create splash screen that says "Click to Play"
Register an click event listener to the respective DOM element
In the listener code call document.body.requestPointerLock() in order to asynchronously ask the browser for the pointer lock

Using a raycaster with the camera to create the crosshair Google Cardboard effect

An example of what I'm trying to achieve: https://workshop.chromeexperiments.com/examples/guiVR/#1--Basic-Usage
How could I get the Google Cardboard crosshair, (gaze)pointer, reticle, whatever you want to call it effect in three.js? I would like to make a dot, as crosshair, in the center of the screen in my scene. Like that I would like to use a raycaster to identify what I'm looking at in VR. Which way would be best to go here?
Do I fake an X and Y position of my mouse? Because I found other people have answered how to cover this by adding an event listener to the mousemove event. But this works on desktop, and I want to bring this to mobile.
Here are the main parts needed for building your own gaze cursor:
An object that serves as the indicator (cursor) for user feedback
An array of objects that you want the cursor to interact with
A loop to iterate over the array of interactive objects to test if the cursor is pointing at them
Here's an example of how this can be implemented.
Gaze cursor indicator
Using a ring here so it can be animated, as you typically want to give the user some feedback that the cursor is about to select something, instead of instantly triggering the interaction.
const cursor = new THREE.Mesh(
new THREE.RingBufferGeometry(0.1, 0.15),
new THREE.MeshBasicMaterial({ color: "white" })
);
Interactive Objects
Keep track of the objects you want to make interactive, and the actions they should execute when they are looked at.
const selectable = [];
const cube = new THREE.Mesh(
new THREE.BoxBufferGeometry(1, 1, 1),
new THREE.MeshNormalMaterial()
);
selectable.push({
object: cube,
action() {
console.log("Cube selected");
},
});
Checking Interactive Objects
Check for interactions on every frame and execute the action.
const raycaster = new THREE.Raycaster();
(function animate() {
for (let i = 0, length = selectable.length; i < length; i++) {
const camPosition = camera.position.clone();
const objectPosition = selectable[i].object.position.clone();
raycaster.set(camPosition, camera.getWorldDirection(objectPosition));
const intersects = raycaster.intersectObject(selectable[i].object);
const selected = intersects.length > 0;
// Visual feedback to inform the user they have selected an object
cursor.material.color.set(selected ? "crimson" : "white");
// Execute object action once
if (selected && !selectable[i].selected) {
selectable[i].action();
}
selectable[i].selected = selected;
}
})();
Here's a demo of this in action:
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
canvas {
display: block;
}
<script type="module">
import * as THREE from "https://cdn.jsdelivr.net/npm/three#0.121.1/build/three.module.js";
import { OrbitControls } from "https://cdn.jsdelivr.net/npm/three#0.121.1/examples/jsm/controls/OrbitControls.js";
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
const cameraMin = 0.0001;
const aspect = window.innerWidth / window.innerHeight;
const camera = new THREE.PerspectiveCamera(75, aspect, cameraMin, 1000);
const controls = new OrbitControls(camera, renderer.domElement);
const scene = new THREE.Scene();
camera.position.z = 5;
scene.add(camera);
const cube = new THREE.Mesh(
new THREE.BoxBufferGeometry(),
new THREE.MeshNormalMaterial()
);
cube.position.x = 1;
cube.position.y = 0.5;
scene.add(cube);
const cursorSize = 1;
const cursorThickness = 1.5;
const cursorGeometry = new THREE.RingBufferGeometry(
cursorSize * cameraMin,
cursorSize * cameraMin * cursorThickness,
32,
0,
Math.PI * 0.5,
Math.PI * 2
);
const cursorMaterial = new THREE.MeshBasicMaterial({ color: "white" });
const cursor = new THREE.Mesh(cursorGeometry, cursorMaterial);
cursor.position.z = -cameraMin * 50;
camera.add(cursor);
const selectable = [
{
selected: false,
object: cube,
action() {
console.log("Cube selected");
},
}
];
const raycaster = new THREE.Raycaster();
let firstRun = true;
(function animate() {
requestAnimationFrame(animate);
renderer.setSize(window.innerWidth, window.innerHeight);
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
controls.update();
if (!firstRun) {
for (let i = 0, length = selectable.length; i < length; i++) {
const camPosition = camera.position.clone();
const objectPosition = selectable[i].object.position.clone();
raycaster.set(camPosition, camera.getWorldDirection(objectPosition));
const intersects = raycaster.intersectObject(selectable[i].object);
const selected = intersects.length > 0;
cursor.material.color.set(selected ? "crimson" : "white");
if (selected && !selectable[i].selected) {
selectable[i].action();
}
selectable[i].selected = selected;
}
}
renderer.render(scene, camera);
firstRun = false;
})();
</script>
This library works like a charm - https://github.com/skezo/Reticulum

three js clickable objects

I have two 3d objects "meshes" that I created in three.js.
I want to add click event to these objects so that when I click on one of them, it is scaled up and when I click on the other one, it rotates.
I tried this method to add a click event to an object and it's not working.
<script src='threex.domevent.js'> </script>
<script>
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(20, window.innerWidth/window.innerHeight, 1, 1000);
camera.position.z = 100;
var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var geometry = new THREE.CubeGeometry(1,1,1);
var material = new THREE.MeshBasicMaterial({color: 0x00ff00});
var cube = new THREE.Mesh(geometry, material);
scene.add(cube);
cube.addEventListener( 'click',function(){cubeScale()} , false );
function cubeScale()
{
cube.scale.x *= 20;
}
var render = function () {
requestAnimationFrame(render);
cube.rotation.y += 0.1;
renderer.render(scene, camera);
};
render();
</script>
you can use this event manager three.interaction
and see this demo
import { Scene, PerspectiveCamera, WebGLRenderer, Mesh, BoxGeometry, MeshBasicMaterial } from 'three';
import { Interaction } from 'three.interaction';
const renderer = new WebGLRenderer({ canvas: canvasElement });
const scene = new Scene();
const camera = new PerspectiveCamera(60, width / height, 0.1, 100);
// new a interaction, then you can add interaction-event with your free style
const interaction = new Interaction(renderer, scene, camera);
const cube = new Mesh(
new BoxGeometry(1, 1, 1),
new MeshBasicMaterial({ color: 0xffffff }),
);
scene.add(cube);
cube.cursor = 'pointer';
cube.on('click', function(ev) {});
cube.on('touchstart', function(ev) {});
cube.on('touchcancel', function(ev) {});
cube.on('touchmove', function(ev) {});
cube.on('touchend', function(ev) {});
cube.on('mousedown', function(ev) {});
cube.on('mouseout', function(ev) {});
cube.on('mouseover', function(ev) {});
cube.on('mousemove', function(ev) {});
cube.on('mouseup', function(ev) {});
// and so on ...
/**
* you can also listen on parent-node or any display-tree node,
* source event will bubble up along with display-tree.
* you can stop the bubble-up by invoke ev.stopPropagation function.
*/
scene.on('touchstart', ev => {
console.log(ev);
})
scene.on('touchmove', ev => {
console.log(ev);
})
Absolutely you need to implement raycaster to select/click/pick certain object in three.js. I've already implemented it in one of my project using three.js and raycaster against collada model. For your reference, these links can help you:
http://soledadpenades.com/articles/three-js-tutorials/object-picking/
http://jensarps.de/2012/08/10/mouse-picking-collada-models-with-three-js/
You can use pickingRay to perform clicking with orthographic camera like that follows in this code:
var vector = new THREE.Vector3(
(event.clientX / window.innerWidth) * 2 - 1,
-(event.clientY / window.innerHeight) * 2 + 1, 0.5);
var rayCaster = projector.pickingRay(vector, camera);
var intersectedObjects = rayCaster.intersectObjects(scene.children, true);
But if you want perform clicking on perspective camera you have to perform an clicking using the unprojectVector like that follows below:
var vector = new THREE.Vector3(
(event.clientX / window.innerWidth) * 2 - 1,
- (event.clientY / window.innerHeight) * 2 + 1,
0.5 );
var rayCaster = projector.unprojectVector(vector, camera);
var intersectedObjects = rayCaster.intersectObjects(objects);

Categories

Resources