I am currently trying to change the geometry of a cube in three js to that of a sphere after a certain time interval or an event click. I have tried to change the property of geometry from Three.BoxGeometry to Three.SphereGeometry but have had no luck.
I have also tried to implement some "solutions" that I found, but have hit a wall there too.
Here is what I initially had:
export class ThreeJSService {
geometry: THREE.BoxGeometry;
camera: THREE.PerspectiveCamera;
cube: THREE.Mesh;
movingObject;
renderer: THREE.WebGLRenderer;
scene: THREE.Scene;
texture: THREE.Texture;
/**
* Setups scene for 3d objects
*/
constructor() {
this.scene = new THREE.Scene();
this.texture = new THREE.TextureLoader().load('assets/apartment_background.png');
this.scene.background = this.texture;
this.camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );
this.renderer = new THREE.WebGLRenderer();
this.geometry = new THREE.BoxGeometry(0.5, 0.5, 0.5);
const material = new THREE.MeshBasicMaterial( {color: 0x348713} );
this.cube = new THREE.Mesh( this.geometry, material );
this.movingObject = {
object: this.cube,
geometry: this.geometry,
direction: {
x: 0.03,
y: 0,
},
};
}
/**
* Sets all the requirements needed for the scene
* #param {HTMLElement} parent The element to attach to
*/
setup(parent: HTMLElement) {
const canvas = this.renderer.domElement;
this.scene.add( this.cube );
this.camera.position.z = 5;
parent.appendChild(canvas);
}
// transform() {
// for ( var i = 0, l = this.geometry.vertices.length; i < l; i ++ ) {
//
// var vertex = this.geometry.vertices[ i ];
// vertex.normalize().multiplyScalar( 550 );
//
// // After a certain interval change the geometry from Cylinder to Sphere
//
// }
// }
/**
* Provides animation to the rendered object. Makes the cube move and sends it to another direction when getting to the border
* #param {number | undefined} headerHeight The height of the header
*/
animate() {
const header = document.getElementById('header')?.clientHeight;
this.renderer.setSize( window.innerWidth, window.innerHeight - (header ? header : 0));
this.movingObject.object.rotateX(0.01);
this.movingObject.object.rotateY(0.01);
requestAnimationFrame( this.animate.bind(this) );
this.renderer.render( this.scene, this.camera );
this.movingObject.object.position.x += this.movingObject.direction.x;
// Check if the position of the cube is on the border
if (this.cube.position.x > 7.3 || this.cube.position.x < -7.3) {
this.movingObject.direction.x = -this.movingObject.direction.x;
}
}
Any help would be greatly appreciated!
Here is a complete live example that shows a geometry is replaced after two seconds.
let camera, scene, renderer;
init();
render();
function init() {
camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.01, 10);
camera.position.set(2, 2, 2);
camera.lookAt(0, 0, 0);
scene = new THREE.Scene();
geometry = new THREE.BoxGeometry();
material = new THREE.MeshNormalMaterial();
mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
setTimeout(function() {
mesh.geometry.dispose();
mesh.geometry = new THREE.SphereGeometry();
render();
}, 2000);
}
function render() {
renderer.render(scene, camera);
}
body {
margin: 0;
}
<script src="https://cdn.jsdelivr.net/npm/three#0.141/build/three.min.js"></script>
I managed to get it to work.
This is my solution:
changeShape() {
this.interval = (Math.floor(Math.random() * (400 + 1)) * 1000);
// this.interval = Math.floor((Math.random() * (20 - 10 + 10)) + 10) * 1000;
if (this.switch) {
setTimeout(() => {
this.movingObject.object.geometry.dispose();
this.movingObject.object.geometry = new THREE.CylinderGeometry(0.5, 0.5, 2);
this.switch = false;
// console.log(this.interval);
}, this.interval);
} else {
setTimeout(() => {
this.movingObject.object.geometry.dispose();
this.movingObject.object.geometry = new THREE.SphereGeometry(0.5, 8, 8);
this.switch = true;
// console.log(this.interval);
}, this.interval);
}
}
Thank you for the assist!
Related
Hi i am loading a GLTF model in my scene. But the model looks too yellow i assume this has to do with the material. But i have no clue what is causing it, it is not the model itself since if i check it on donmccurdy's gltf viewer it looks perfect.
See a reference below:
GLTF Viewer:
And this is how the same model looks in my scene:
I have added an environment map to the scene this fixed may things but not the yellowness..
See current code below:
let scene, camera, renderer, controls;
let dummy = new THREE.Object3D();
let mat4 = new THREE.Matrix4();
const loader = new THREE.GLTFLoader();
let clock = new THREE.Clock();
function loadModel(url, callback) {
loader.load(
url,
function ( gltf ) {
const model = gltf.scene.children[0];
callback(model);
},
function ( xhr ) {
console.log( ( xhr.loaded / xhr.total * 100 ) + '% loaded' );
},
function ( error ) {
console.log( 'An error happened', error );
}
);
}
function generateInstances(model, count) {
const geometry = model.geometry;
geometry.scale(0.1, 0.1, 0.1);
const material = model.material;
return new THREE.InstancedMesh(geometry, material, count);
}
function setInstancePositions(instances, startPos, counts, spacing) {
if(instances.count < counts.x * counts.y * counts.z) throw Error("counts exceeds max instanciated meshes");
let index = 0;
for (let x = 0; x < counts.x; x++) {
for (let y = 0; y < counts.y; y++) {
for (let z = 0; z < counts.z; z++) {
dummy.position.addVectors(startPos, new THREE.Vector3(x * spacing.x, y * spacing.y, z * spacing.z));
dummy.updateMatrix();
instances.setMatrixAt(index, dummy.matrix);
index++;
}
}
}
return instances;
}
function init() {
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 1, 1000);
camera.position.set(50, 75, 100);
renderer = new THREE.WebGLRenderer();
renderer.setSize(innerWidth, innerHeight);
renderer.setClearColor(0xFFFFFF);
renderer.physicallyCorrectLights = true;
document.body.appendChild(renderer.domElement);
new THREE.RGBELoader()
.load('assets/enviroment.hdr', function ( texture ) {
texture.mapping = THREE.EquirectangularReflectionMapping;
scene.environment = texture;
});
let controls = new THREE.OrbitControls(camera, renderer.domElement);
const light1 = new THREE.AmbientLight(0xffffff, 0.3);
light1.name = 'ambient_light';
scene.add( light1 );
loadModel('assets/100-dollars.glb', (model) => {
let instances = generateInstances(model, 1500);
instances = setInstancePositions(instances, new THREE.Vector3(), new THREE.Vector3(5, 30, 10), new THREE.Vector3(27, 3, 13));
scene.add(instances);
});
renderer.setAnimationLoop(() => {
camera.lookAt(scene.position);
renderer.render(scene, camera);
});
}
init();
here is the hdr file: url
and here is the model: url
Using GLTFLoader requires a sRGB workflow which is not configured in your app. Add the following line and see if it fixes the color issue:
renderer.outputEncoding = THREE.sRGBEncoding;
Besides, consider to turn on antialiasing and the usage of setPixelRatio() for better quality:
renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setPixelRatio(window.devicePixelRatio);
I am new to Three.js. I am having issues using a gltf model as the actual scene and not part of the scene in three.js. The gltf model is an apartment. I want the scene to load from inside the apartment and not outside the apartment. the controls should work within the apartment too. So far, I have loaded the model on the scene but I can't get the scene to render from inside the model.
Here is my code in Typescript and also JavaScript been at it for weeks now. Any help will be much appreciated. Thank you so much.
Typescript code
import * as THREE from '/build/three.module.js'
import { OrbitControls } from '/jsm/controls/OrbitControls'
import { GLTFLoader } from '/jsm/loaders/GLTFLoader'
import Stats from '/jsm/libs/stats.module'
const scene: THREE.Scene = new THREE.Scene()
const axesHelper = new THREE.AxesHelper(5)
//scene.add(axesHelper)
var light = new THREE.SpotLight();
light.position.set(5, 5, 5)
scene.add(light);
const camera: THREE.PerspectiveCamera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000)
camera.position.z = 2
const renderer: THREE.WebGLRenderer = new THREE.WebGLRenderer()
//renderer.physicallyCorrectLights = true
//renderer.shadowMap.enabled = true
renderer.outputEncoding = THREE.sRGBEncoding
renderer.setSize(window.innerWidth, window.innerHeight)
document.body.appendChild(renderer.domElement)
const controls = new OrbitControls(camera, renderer.domElement)
const loader = new GLTFLoader()
loader.load(
'apartment.glb',
function (gltf) {
// gltf.scene.traverse(function (child) {
// if ((<THREE.Mesh>child).isMesh) {
// let m = <THREE.Mesh>child
// m.receiveShadow = true
// m.castShadow = true
// }
// if ((<THREE.Light>child).isLight) {
// let l = <THREE.Light>child
// l.castShadow = true
// //l.shadow.bias = -.003
// l.shadow.mapSize.width = 2048
// l.shadow.mapSize.height = 2048
// }
// })
scene.add(gltf.scene);
},
(xhr) => {
console.log((xhr.loaded / xhr.total * 100) + '% loaded')
},
(error) => {
console.log(error);
}
);
window.addEventListener('resize', onWindowResize, false)
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight
camera.updateProjectionMatrix()
renderer.setSize(window.innerWidth, window.innerHeight)
render()
}
const stats = Stats()
document.body.appendChild(stats.dom)
var animate = function () {
requestAnimationFrame(animate)
controls.update()
render()
stats.update()
};
function render() {
renderer.render(scene, camera)
}
animate();
JavaScript code
import * as THREE from '/build/three.module.js';
import { OrbitControls } from '/jsm/controls/OrbitControls';
import { GLTFLoader } from '/jsm/loaders/GLTFLoader';
import Stats from '/jsm/libs/stats.module';
const scene = new THREE.Scene();
const axesHelper = new THREE.AxesHelper(5);
//scene.add(axesHelper)
var light = new THREE.SpotLight();
light.position.set(5, 5, 5);
scene.add(light);
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.z = 2;
const renderer = new THREE.WebGLRenderer();
//renderer.physicallyCorrectLights = true
//renderer.shadowMap.enabled = true
renderer.outputEncoding = THREE.sRGBEncoding;
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
const controls = new OrbitControls(camera, renderer.domElement);
const loader = new GLTFLoader();
loader.load('apartment.glb', function (gltf) {
// gltf.scene.traverse(function (child) {
// if ((<THREE.Mesh>child).isMesh) {
// let m = <THREE.Mesh>child
// m.receiveShadow = true
// m.castShadow = true
// }
// if ((<THREE.Light>child).isLight) {
// let l = <THREE.Light>child
// l.castShadow = true
// //l.shadow.bias = -.003
// l.shadow.mapSize.width = 2048
// l.shadow.mapSize.height = 2048
// }
// })
scene.add(gltf.scene);
}, (xhr) => {
console.log((xhr.loaded / xhr.total * 100) + '% loaded');
}, (error) => {
console.log(error);
});
window.addEventListener('resize', onWindowResize, false);
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
render();
}
const stats = Stats();
document.body.appendChild(stats.dom);
var animate = function () {
requestAnimationFrame(animate);
controls.update();
render();
stats.update();
};
function render() {
renderer.render(scene, camera);
}
animate();
Finally was able to solve my issue.
firstly I had to use a plane model that is with no roof
secondly the pivot point was properly at the center and the model was on the plane grid, unlike the first model I was working with.
with the model opened the room was what was rendered in the scene and not the entire model itself and because the pivot point was in the middle and the model on the plane, not below or above it, I did not have to look for it in the scene when it rendered. Thank you for reading and trying to help.
because I had solved this problem I was able to load multiple glb models and place them where I wanted in the scene without hassle.
my advice if you want to use a model as your scene make sure the pivot point is at the center and the model is on the grid, not over or below it. also make sure its an open model so your light and camera can see inside.
here was my final code.
import * as THREE from '/build/three.module.js'
import { OrbitControls } from '/jsm/controls/OrbitControls'
import { GLTFLoader } from '/jsm/loaders/GLTFLoader'
// import Stats from '/jsm/libs/stats.module'
// import { GUI } from '/jsm/libs/dat.gui.module'
const scene: THREE.Scene = new THREE.Scene()
const axesHelper = new THREE.AxesHelper(5)
//scene.add(axesHelper)
const light = new THREE.SpotLight(0xffa95c,4);
light.position.set(-50,50,50);
light.castShadow = true;
scene.add( light );
var hemiLight = new THREE.HemisphereLight(0xffeeb1, 0x080820, 4);
scene.add(hemiLight);
var light3 = new THREE.DirectionalLight( 0xffffff );
light3.position.set( 0, 200, 100 );
light3.castShadow = true;
light3.shadow.mapSize.width = 2048;
light3.shadow.mapSize.height = 2048;
light3.shadow.camera.top = 3000;
light3.shadow.camera.bottom = -3000;
light3.shadow.camera.left = -3000;
light3.shadow.camera.right = 3000;
light3.shadow.camera.far = 3000;
scene.add( light );
const camera: THREE.PerspectiveCamera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000)
camera.position.z = 2
camera.position.y = 2
camera.position.x = 2
const renderer: THREE.WebGLRenderer = new THREE.WebGLRenderer()
//renderer.physicallyCorrectLights = true
//renderer.shadowMap.enabled = true
renderer.outputEncoding = THREE.sRGBEncoding
renderer.setSize(window.innerWidth, window.innerHeight)
document.body.appendChild(renderer.domElement)
// var mesh = new THREE.Mesh( new THREE.PlaneBufferGeometry( 2000, 2000 ), new THREE.MeshPhongMaterial( { color: 0x999999, depthWrite: false } ) );
// mesh.rotation.x = - Math.PI / 2;
// //mesh.position.y = -100;
// mesh.receiveShadow = true;
// //this.scene.add( mesh );
// var grid = new THREE.GridHelper( 2000, 40, 0x000000, 0x000000 );
// //grid.position.y = -100;
// // grid.material.opacity = 0.2;
// // grid.material.transparent = true;
const raycaster = new THREE.Raycaster();
const controls = new OrbitControls(camera, renderer.domElement)
controls.screenSpacePanning = true
controls.target.set(0, 1, 0)
controls.minDistance = 5;
controls.maxDistance = 10;
// const backGroundTexture = new THREE.CubeTextureLoader().load(["img/py_eso0932a.jpg", "img/ny_eso0932a.jpg", "img/py_eso0932a.jpg", "img/ny_eso0932a.jpg", "img/pz_eso0932a.jpg", "img/nz_eso0932a.jpg"]);
// scene.background = backGroundTexture;
const loader = new GLTFLoader()
loader.load(
'models3/untitled.glb',
function (gltf) {
scene.add(gltf.scene);
gltf.scene.position.set(0,-2,3)
},
(xhr) => {
console.log((xhr.loaded / xhr.total * 100) + '% loaded')
},
(error) => {
console.log(error);
}
);
loader.load(
'models3/man.glb',
function (gltf1) {
scene.add(gltf1.scene);
gltf1.scene.position.set(1,-1.3,0)
gltf1.scene.scale.set(2,2,2)
},
(xhr) => {
console.log((xhr.loaded / xhr.total * 100) + '% loaded')
},
(error) => {
console.log(error);
}
);
loader.load(
'models3/jack_daniels.glb',
function (gltf2) {
gltf2.scene.traverse(function (child) {
if ((<THREE.Mesh>child).isMesh) {
let m = <THREE.Mesh>child
m.receiveShadow = true
m.castShadow = true;
//(<THREE.MeshStandardMaterial>m.material).flatShading = true
//sceneMeshes.push(m)
}
if ((<THREE.Light>child).isLight) {
let l = <THREE.Light>child
l.castShadow = true
l.shadow.bias = -.003
l.shadow.mapSize.width = 2048
l.shadow.mapSize.height = 2048
}
scene.add(gltf2.scene);
gltf2.scene.position.set(-1,0.55,-1)
gltf2.scene.scale.set(0.15,0.15,0.15)})
},
(xhr) => {
console.log((xhr.loaded / xhr.total * 100) + '% loaded')
},
(error) => {
console.log(error);
}
);
// renderer.domElement.addEventListener('dblclick', onDoubleClick, false);
// renderer.domElement.addEventListener('mousemove', onMouseMove, false);
// function onMouseMove(event: MouseEvent) {
// const mouse = {
// x: (event.clientX / renderer.domElement.clientWidth) * 2 - 1,
// y: -(event.clientY / renderer.domElement.clientHeight) * 2 + 1
// }
// //console.log(mouse)
// raycaster.setFromCamera(mouse, camera);
window.addEventListener('resize', onWindowResize, false)
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight
camera.updateProjectionMatrix()
renderer.setSize(window.innerWidth, window.innerHeight)
render()
}
// const stats = Stats()
// document.body.appendChild(stats.dom)
var animate = function () {
requestAnimationFrame(animate)
// light.position.set(
// camera.position.x + 10,
// camera.position.y + 10,
// camera.position.z + 10,
// );
controls.update()
render()
// stats.update()
};
function render() {
renderer.render(scene, camera)
}
animate();
function onDoubleClick(arg0: string, onDoubleClick: any, arg2: boolean) {
throw new Error('Function not implemented.')
}
PLEASE FEEL FREE TO COMMENT IF THERE IS SOMETHING I NEED TO KNOW. I WILL HUMBLY TAKE CORRECTIONS. THANK YOU
I have code in place to create load, render and display a STL object using Vue.js and Three.js.
I want to render a new face in place of the plane I am currently racasting to. I have already found a way to get the 3 vertices of the mous-overed (Clicked on) face (aVertex, bVertex, cVertex).
Now I want to render a triangle in this position (with a different color), but to be honest I have no idea how. I have tried googling it, but with no luck yet (I am very unfamiliar with 3d and rendering in general). Can someone give me a push in the right direction?
<template>
<div id="scene-container" ref="sceneContainer" class="scene-container"></div>
</template>
<script>
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
import { STLLoader } from "three/examples/jsm/loaders/STLLoader";
export default {
name: "HelloWorld",
data() {
return {
container: null,
scene: null,
camera: null,
controls: null,
renderer: null,
stats: null,
mouse: null,
raycaster: null,
objName: "testobj",
};
},
methods: {
init() {
// set container
this.container = this.$refs.sceneContainer;
// add raycaster
this.raycaster = new THREE.Raycaster();
this.mouse = new THREE.Vector2();
const onMouseClick = (event) => {
event.preventDefault();
// calculate mouse position in normalized device coordinates
// (-1 to +1) for both components
this.mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
this.mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
// update the picking ray with the camera and mouse position
};
window.addEventListener("mousedown", onMouseClick, false);
// add camera
const fov = 60; // Field of view
const aspect = this.container.clientWidth / this.container.clientHeight;
// const near = 0.1; // the near clipping plane
// const far = 3000; // the far clipping plane
const camera = new THREE.PerspectiveCamera(fov, aspect);
camera.position.set(0, 20, 75);
this.camera = camera;
// create scene
this.scene = new THREE.Scene();
this.scene.background = new THREE.Color(0xdddddd);
// add lights
const ambientLight = new THREE.HemisphereLight(
0xffffff, // bright sky color
0x222222, // dim ground color
1 // intensity
);
const mainLight = new THREE.DirectionalLight(0xffffff, 4.0);
mainLight.position.set(10, 10, 10);
this.scene.add(ambientLight, mainLight);
let hlight = new THREE.AmbientLight(0xffffff, 1.3);
this.scene.add(hlight);
//Add some point lights to simulate real lights
let light = new THREE.PointLight(0xffffff, 1, 1000);
light.position.set(0, 300, 500);
this.scene.add(light);
// add controls
this.controls = new OrbitControls(this.camera, this.container);
// create renderer
this.renderer = new THREE.WebGLRenderer({ antialias: true });
this.renderer.setSize(
this.container.clientWidth,
this.container.clientHeight
);
this.renderer.setPixelRatio(window.devicePixelRatio);
this.renderer.gammaFactor = 2.2;
this.renderer.outputEncoding = THREE.sRGBEncoding;
this.renderer.physicallyCorrectLights = true;
document
.getElementById("scene-container")
.appendChild(this.renderer.domElement);
// set aspect ratio to match the new browser window aspect ratio
this.camera.aspect =
this.container.clientWidth / this.container.clientHeight;
this.camera.updateProjectionMatrix();
this.renderer.setSize(
this.container.clientWidth,
this.container.clientHeight
);
let me = this;
let loader = new STLLoader();
let mesh = new THREE.Object3D();
loader.load("/three-assets/RobotExpressive.stl", function (geometry) {
// console.log(geometry);
let material = new THREE.MeshLambertMaterial({
color: 0x1313,
wireframe: false,
transparent: false,
vertexColors: false,
});
mesh = new THREE.Mesh(geometry, material);
mesh.rotation.x = -0.5 * Math.PI;
mesh.position.set(0, 0, 0);
mesh.name = me.objName;
me.scene.add(mesh);
});
window.addEventListener("resize", onWindowResize, false);
function onWindowResize() {
me.camera.aspect = window.innerWidth / window.innerHeight;
me.camera.updateProjectionMatrix();
me.renderer.setSize(window.innerWidth, window.innerHeight);
}
this.renderer.setAnimationLoop(() => {
this.render();
});
},
render() {
this.raycaster.setFromCamera(this.mouse, this.camera);
this.intersects = this.raycaster.intersectObjects(this.scene.children);
// window.addEventListener( 'mousemove', onMouseMove, false );
if (this.intersects.length > 1) {
// this.intersects[0].object.material.color.set(0xff);
for (let i = 0; i < this.intersects.length; i++) {
if (this.intersects[i].object.name == "testobj") {
let positionAttribute = this.intersects[i].object.geometry
.attributes["position"];
let intersection = this.intersects[i];
let aVertex = new THREE.Vector3(
positionAttribute.getX(intersection.face.a),
positionAttribute.getY(intersection.face.a),
positionAttribute.getZ(intersection.face.a)
);
let bVertex = new THREE.Vector3(
positionAttribute.getX(intersection.face.b),
positionAttribute.getY(intersection.face.b),
positionAttribute.getZ(intersection.face.b)
);
let cVertex = new THREE.Vector3(
positionAttribute.getX(intersection.face.c),
positionAttribute.getY(intersection.face.c),
positionAttribute.getZ(intersection.face.c)
);
console.log(aVertex, bVertex, cVertex);
}
}
}
this.renderer.render(this.scene, this.camera);
},
},
mounted() {
this.init();
},
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h3 {
margin: 40px 0 0;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
#scene-container {
height: 99.8%;
}
</style>
Example (for picking meshes with BufferGeometry):
// initital setup:
let lineGeometry = new THREE.BufferGeometry();
let linePositionAttribute = new THREE.BufferAttribute(new Float32Array(4 * 3), 3);
lineGeometry.addAttribute('position', linePositionAttribute);
let lineMaterial = new THREE.LineBasicMaterial(
{
color: 0xff0000
});
var intersectionFaceEdge = new THREE.Line(lineGeometry, lineMaterial);
scene.add(intersectionFaceEdge);
// ... on each raycasting:
let face = intersection.face;
let obj = intersection.object;
let positionAttribute = obj.geometry.attributes['position'];
linePositionAttribute.copyAt(0, positionAttribute, face.a);
linePositionAttribute.copyAt(1, positionAttribute, face.b);
linePositionAttribute.copyAt(2, positionAttribute, face.c);
linePositionAttribute.copyAt(3, positionAttribute, face.a);
lineGeometry.applyMatrix(obj.matrixWorld);
And I recommend using GPU picking instead of simple raycasting
I am relatively new to three.js and I am trying to replicate the following code block I found on Observable inside of a fiddle.
https://observablehq.com/#bumbeishvili/three-js-wooden-bar-chart
When I run the code, all I see is a giant black box. I thought I added the group materials and light elements properly to the scene but all that renders is blackness. Any help would be immensely appreciated.
Here is my code:
canvas {
display: block;
}
initiateThree();
function initiateThree() {
data = [
[3,2,1],
[6,5,4],
[8,7,6],
]
var i =1;
height = 500
fov = 18
//aspect = width / height
aspect = 500 / 500
near = 0.1
far = 1000
loader = new THREE.TextureLoader()
function update() {
//cubeGroup.rotation.x += 0.001;
cubeGroup.rotation.y += 0.001;
//cubeGroup.rotation.z += 0.001;
}
function render(scene) {
renderer.render(scene, camera);
}
const scene = new THREE.Scene(); // ADDED
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
camera.position.set(-4, 1, 4);
//max = Math.max.apply(Math, data.map(d=>Math.max.apply(this,d)))
cubeGeometries = data.map(row=>{
return row.map(c=>{
//return new THREE.BoxGeometry( 0.2, c/max, 0.2 );
return new THREE.BoxGeometry( 0.2, c/8, 0.2 );
})
})
const cubeMaterial = new THREE.MeshStandardMaterial({
map:loader.load('https://threejsfundamentals.org/threejs/lessons/resources/
images/compressed-but-large-wood-texture.jpg')
});
cubeMaterial.color.convertSRGBToLinear();
const cubeMeshes = cubeGeometries.map(row=>{
return row.map(cubeGeometry => new THREE.Mesh( cubeGeometry, cubeMaterial ))
})
const cubeGroup = new THREE.Group();
data.forEach((row,i,iarr)=>{
row.forEach((d,j,jarr)=>{
cubeMeshes[i][j].position.set(
i/iarr.length-0.5,
//d/max*0.5-0.6,
d/8*0.5-0.6,
j/jarr.length-0.5);
//cubeMeshes[i][j].scale.set(1,4,1);
cubeGroup.add(cubeMeshes[i][j]);
})
})
const mainLight = new THREE.DirectionalLight(0xffffff, 3.0);
mainLight.position.set(10, 10, 10);
const ambientLight = new THREE.HemisphereLight(0xddeeff, 0x202020, 3);
//scene.add(cubeMeshes); //Not This One?
scene.add(cubeGroup);
scene.add(mainLight);
scene.add(ambientLight);
const renderer = new THREE.WebGLRenderer({
antialias: true
});
//renderer.setSize(width, height);
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(window.devicePixelRatio);
renderer.gammaFactor = 2.2;
renderer.gammaOutput = true;
renderer.physicallyCorrectLights = true;
document.body.appendChild( renderer.domElement ); //ADDED
}
Here is my fiddle:
https://jsfiddle.net/bullybear/m826gkrt/326/
A few things are missing in your code:
There is no animation loop.
The camera does not look at the boxes.
Also upgraded to the latest version of three.js (r118) and removed legacy code.
let scene, camera, renderer;
let cubeGroup;
init();
animate();
function init() {
const data = [
[3, 2, 1],
[6, 5, 4],
[8, 7, 6],
]
var i = 1;
height = 500
fov = 18
//aspect = width / height
aspect = 500 / 500
near = 0.1
far = 1000
loader = new THREE.TextureLoader()
scene = new THREE.Scene(); // ADDED
camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
camera.position.set(-4, 1, 4);
camera.lookAt( scene.position );
//max = Math.max.apply(Math, data.map(d=>Math.max.apply(this,d)))
cubeGeometries = data.map(row => {
return row.map(c => {
//return new THREE.BoxGeometry( 0.2, c/max, 0.2 );
return new THREE.BoxBufferGeometry(0.2, c / 8, 0.2);
})
})
const cubeMaterial = new THREE.MeshStandardMaterial({
map: loader.load('https://threejsfundamentals.org/threejs/lessons/resources/images/compressed-but-large-wood-texture.jpg')
});
cubeMaterial.color.convertSRGBToLinear();
const cubeMeshes = cubeGeometries.map(row => {
return row.map(cubeGeometry => new THREE.Mesh(cubeGeometry, cubeMaterial))
})
cubeGroup = new THREE.Group();
data.forEach((row, i, iarr) => {
row.forEach((d, j, jarr) => {
cubeMeshes[i][j].position.set(
i / iarr.length - 0.5,
//d/max*0.5-0.6,
d / 8 * 0.5 - 0.6,
j / jarr.length - 0.5);
//cubeMeshes[i][j].scale.set(1,4,1);
cubeGroup.add(cubeMeshes[i][j]);
})
})
const mainLight = new THREE.DirectionalLight(0xffffff, 3.0);
mainLight.position.set(10, 10, 10);
const ambientLight = new THREE.HemisphereLight(0xddeeff, 0x202020, 3);
scene.add(cubeGroup);
scene.add(mainLight);
scene.add(ambientLight);
renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(window.devicePixelRatio);
renderer.outputEncoding = THREE.sRGBEncoding;
renderer.physicallyCorrectLights = true;
document.body.appendChild(renderer.domElement); //ADDED
}
function animate() {
requestAnimationFrame( animate );
cubeGroup.rotation.y += 0.001;
renderer.render( scene, camera );
}
body {
margin: 0;
}
canvas {
display: block;
}
<script src="https://cdn.jsdelivr.net/npm/three#0.118.3/build/three.js"></script>
The short version: How can one make a camera follow an object controlled by physics within a Three.js scene?
The long version: I'm working on a Three.js scene in which the W,A,S,D keys move a sphere along a plane. So far, however, I haven't figured out how to make the camera follow behind the sphere.
In the example below, the camera follows the sphere perfectly if one only presses the W key. However, if one presses A or D, the sphere starts to turn, and the camera is no longer behind the ball. If the sphere starts to turn, I want the camera to turn with it, so the camera is always following just behind the sphere, and is always a constant distance from the sphere. As users continue to press W, the ball will continue rolling forward relative to the camera.
In a previous scene [demo], I was able to implement this behavior by creating the sphere, adding that sphere to a group, then using the following bit of code each frame:
var relativeCameraOffset = new THREE.Vector3(0,50,200);
var cameraOffset = relativeCameraOffset.applyMatrix4(sphereGroup.matrixWorld);
camera.position.x = cameraOffset.x;
camera.position.y = cameraOffset.y;
camera.position.z = cameraOffset.z;
camera.lookAt(sphereGroup.position);
The key in the demo above was to rotate the sphere while keeping the sphereGroup unrotated, so I could compute the cameraOffset on the un-rotated sphereGroup.
In the demo below, the sphere's position is controlled by the Cannon.js physics library, which translates and rotates the sphere as forces are applied to the body. Does anyone know how I can make the camera follow behind the sphere in the scene below?
/**
* Generate a scene object with a background color
**/
function getScene() {
var scene = new THREE.Scene();
scene.background = new THREE.Color(0x111111);
return scene;
}
/**
* Generate the camera to be used in the scene. Camera args:
* [0] field of view: identifies the portion of the scene
* visible at any time (in degrees)
* [1] aspect ratio: identifies the aspect ratio of the
* scene in width/height
* [2] near clipping plane: objects closer than the near
* clipping plane are culled from the scene
* [3] far clipping plane: objects farther than the far
* clipping plane are culled from the scene
**/
function getCamera() {
var aspectRatio = window.innerWidth / window.innerHeight;
var camera = new THREE.PerspectiveCamera(75, aspectRatio, 0.1, 10000);
camera.position.set(0, 2000, -5000);
camera.lookAt(scene.position);
return camera;
}
/**
* Generate the light to be used in the scene. Light args:
* [0]: Hexadecimal color of the light
* [1]: Numeric value of the light's strength/intensity
* [2]: The distance from the light where the intensity is 0
* #param {obj} scene: the current scene object
**/
function getLight(scene) {
var light = new THREE.PointLight( 0xffffff, 0.6, 0, 0 )
light.position.set( -2000, 1000, -2100 );
scene.add( light );
var light = new THREE.PointLight( 0xffffff, 0.15, 0, 0 )
light.position.set( -190, 275, -1801 );
light.castShadow = true;
scene.add( light );
// create some ambient light for the scene
var ambientLight = new THREE.AmbientLight(0xffffff, 0.8);
scene.add(ambientLight);
return light;
}
/**
* Generate the renderer to be used in the scene
**/
function getRenderer() {
// Create the canvas with a renderer
var renderer = new THREE.WebGLRenderer({antialias: true});
// Add support for retina displays
renderer.setPixelRatio(window.devicePixelRatio);
// Specify the size of the canvas
renderer.setSize(window.innerWidth, window.innerHeight);
// Enable shadows
renderer.shadowMap.enabled = true;
// Specify the shadow type; default = THREE.PCFShadowMap
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
// Add the canvas to the DOM
document.body.appendChild(renderer.domElement);
return renderer;
}
/**
* Generate the controls to be used in the scene
* #param {obj} camera: the three.js camera for the scene
* #param {obj} renderer: the three.js renderer for the scene
**/
function getControls(camera, renderer) {
var controls = new THREE.TrackballControls(camera, renderer.domElement);
controls.zoomSpeed = 0.4;
controls.panSpeed = 0.4;
return controls;
}
/**
* Get stats
**/
function getStats() {
stats = new Stats();
stats.domElement.style.position = 'absolute';
stats.domElement.style.top = '0px';
stats.domElement.style.right = '0px';
document.body.appendChild( stats.domElement );
return stats;
}
/**
* Get grass
**/
function getGrass() {
var texture = loader.load('http://4.bp.blogspot.com/-JiJEc7lH1Is/UHJs3kn261I/AAAAAAAADYA/gQRAxHK2q_w/s1600/tileable_old_school_video_game_grass.jpg');
texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
texture.repeat.set(10, 10);
var material = new THREE.MeshLambertMaterial({
map: texture,
side: THREE.DoubleSide,
});
return material;
}
function getPlanes(scene, loader) {
var planes = [];
var material = getGrass();
[ [4000, 2000, 0, 0, -1000, 0] ].map(function(p) {
var geometry = new THREE.PlaneGeometry(p[0], p[1]);
var plane = new THREE.Mesh(geometry, material);
plane.position.x = p[2];
plane.position.y = p[3];
plane.position.z = p[4];
plane.rotation.y = p[5];
plane.rotation.x = Math.PI / 2;
plane.receiveShadow = true;
planes.push(plane);
scene.add(plane);
})
return planes;
}
/**
* Add background
**/
function getBackground(scene, loader) {
var imagePrefix = 'sky-parts/';
var directions = ['right', 'left', 'top', 'bottom', 'front', 'back'];
var imageSuffix = '.bmp';
var geometry = new THREE.BoxGeometry( 4000, 4000, 4000 );
// Add each of the images for the background cube
var materialArray = [];
for (var i = 0; i < 6; i++)
materialArray.push( new THREE.MeshBasicMaterial({
//map: loader.load(imagePrefix + directions[i] + imageSuffix),
color: 0xff0000,
side: THREE.BackSide
}));
var sky = new THREE.Mesh( geometry, materialArray );
scene.add(sky);
return sky;
}
/**
* Add a character
**/
function getSphere(scene) {
var geometry = new THREE.SphereGeometry( 30, 12, 9 );
var material = new THREE.MeshPhongMaterial({
color: 0xd0901d,
emissive: 0xaa0000,
side: THREE.DoubleSide,
flatShading: true
});
var sphere = new THREE.Mesh( geometry, material );
// allow the sphere to cast a shadow
sphere.castShadow = true;
sphere.receiveShadow = false;
// create a group for translations and rotations
var sphereGroup = new THREE.Group();
sphereGroup.add(sphere)
sphereGroup.castShadow = true;
sphereGroup.receiveShadow = false;
scene.add(sphereGroup);
return [sphere, sphereGroup];
}
/**
* Initialize physics engine
**/
function getPhysics() {
world = new CANNON.World();
world.gravity.set(0, -400, 0); // earth = -9.82 m/s
world.broadphase = new CANNON.NaiveBroadphase();
world.broadphase.useBoundingBoxes = true;
var solver = new CANNON.GSSolver();
solver.iterations = 7;
solver.tolerance = 0.1;
world.solver = solver;
world.quatNormalizeSkip = 0;
world.quatNormalizeFast = false;
world.defaultContactMaterial.contactEquationStiffness = 1e9;
world.defaultContactMaterial.contactEquationRelaxation = 4;
return world;
}
/**
* Generate the materials to be used for contacts
**/
function getPhysicsMaterial() {
var physicsMaterial = new CANNON.Material('slipperyMaterial');
var physicsContactMaterial = new CANNON.ContactMaterial(
physicsMaterial, physicsMaterial, 0.0, 0.3)
world.addContactMaterial(physicsContactMaterial);
return physicsMaterial;
}
/**
* Add objects to the world
**/
function addObjectPhysics() {
addFloorPhysics()
addSpherePhysics()
}
function addFloorPhysics() {
floors.map(function(floor) {
var q = floor.quaternion;
floorBody = new CANNON.Body({
mass: 0, // mass = 0 makes the body static
material: physicsMaterial,
shape: new CANNON.Plane(),
quaternion: new CANNON.Quaternion(-q._x, q._y, q._z, q._w)
});
world.addBody(floorBody);
})
}
function addSpherePhysics() {
sphereBody = new CANNON.Body({
mass: 1,
material: physicsMaterial,
shape: new CANNON.Sphere(30),
linearDamping: 0.5,
position: new CANNON.Vec3(1000, 500, -2000)
});
world.addBody(sphereBody);
}
/**
* Store all currently pressed keys & handle window resize
**/
function addListeners() {
window.addEventListener('keydown', function(e) {
pressed[e.key.toUpperCase()] = true;
})
window.addEventListener('keyup', function(e) {
pressed[e.key.toUpperCase()] = false;
})
window.addEventListener('resize', function(e) {
windowHalfX = window.innerWidth / 2;
windowHalfY = window.innerHeight / 2;
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
if (typeof(controls) != 'undefined') controls.handleResize();
})
}
/**
* Update the sphere's position
**/
function moveSphere() {
var delta = clock.getDelta(); // seconds
var moveDistance = 500 * delta; // n pixels per second
var rotateAngle = Math.PI / 2 * delta; // 90 deg per second
// move forwards, backwards, left, or right
if (pressed['W'] || pressed['ARROWUP']) {
sphereBody.velocity.z += moveDistance;
}
if (pressed['S'] || pressed['ARROWDOWN']) {
sphereBody.velocity.z -= moveDistance;
}
if (pressed['A'] || pressed['ARROWLEFT']) {
sphereBody.velocity.x += moveDistance;
}
if (pressed['D'] || pressed['ARROWRIGHT']) {
sphereBody.velocity.x -= moveDistance;
}
}
/**
* Follow the sphere
**/
function moveCamera() {
camera.position.x = sphereBody.position.x + 0;
camera.position.y = sphereBody.position.y + 50;
camera.position.z = sphereBody.position.z + -200;
camera.lookAt(sphereGroup.position);
}
function updatePhysics() {
world.step(1/60);
sphereGroup.position.copy(sphereBody.position);
sphereGroup.quaternion.copy(sphereBody.quaternion);
}
// Render loop
function render() {
requestAnimationFrame(render);
renderer.render(scene, camera);
moveSphere();
updatePhysics();
if (typeof(controls) === 'undefined') moveCamera();
if (typeof(controls) !== 'undefined') controls.update();
if (typeof(stats) !== 'undefined') stats.update();
};
// state
var pressed = {};
var clock = new THREE.Clock();
// globals
var scene = getScene();
var camera = getCamera();
var light = getLight(scene);
var renderer = getRenderer();
var world = getPhysics();
var physicsMaterial = getPhysicsMaterial();
//var stats = getStats();
//var controls = getControls(camera, renderer);
// global body references
var sphereBody, floorBody;
// add meshes
var loader = new THREE.TextureLoader();
var floors = getPlanes(scene, loader);
var background = getBackground(scene, loader);
var sphereData = getSphere(scene);
var sphere = sphereData[0];
var sphereGroup = sphereData[1];
addObjectPhysics();
addListeners();
render();
body { margin: 0; overflow: hidden; }
canvas { width: 100%; height: 100%; }
<script src='https://cdnjs.cloudflare.com/ajax/libs/three.js/88/three.min.js'></script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/cannon.js/0.6.2/cannon.js'></script>
Answers to comment questions
#jparimaa I think the most intuitive implementation would make W add forward momentum, S add backward momentum, and A and D rotate the camera around the ball. Is that possible?
#HariV The controls you link to are the ones I used in the demo without physics above. Is it possible to get that logic working with physics?
I think it's most intuitive for users if the W key always moves the ball "forward" relative to the camera
One option would be to calculate the direction between the ball and the camera and add velocity to that direction. In this case if you push the ball forward then you could rotate the camera without it affecting the velocity of the ball. Only after you press W/S after the rotation it would change the direction. I'm not sure if that is what you want but maybe this will give you some ideas.
I tried the following code (rotation is global variable initialized to 0)
function moveSphere() {
var delta = clock.getDelta(); // seconds
var moveDistance = 500 * delta; // n pixels per second
var dir = new THREE.Vector3(sphereBody.position.x, sphereBody.position.y, sphereBody.position.z);
dir.sub(camera.position).normalize(); // direction vector between the camera and the ball
if (pressed['W'] || pressed['ARROWUP']) {
sphereBody.velocity.x += moveDistance * dir.x;
sphereBody.velocity.z += moveDistance * dir.z;
}
if (pressed['S'] || pressed['ARROWDOWN']) {
sphereBody.velocity.x -= moveDistance * dir.x;
sphereBody.velocity.z -= moveDistance * dir.z;
}
}
function moveCamera() {
var delta = clock.getDelta();
var sensitivity = 150;
var rotateAngle = Math.PI / 2 * delta * sensitivity;
if (pressed['A'] || pressed['ARROWLEFT']) {
rotation -= rotateAngle;
}
if (pressed['D'] || pressed['ARROWRIGHT']) {
rotation += rotateAngle;
}
var rotZ = Math.cos(rotation)
var rotX = Math.sin(rotation)
var distance = 200;
camera.position.x = sphereBody.position.x - (distance * rotX);
camera.position.y = sphereBody.position.y + 50;
camera.position.z = sphereBody.position.z - (distance * rotZ);
camera.lookAt(sphereGroup.position);
}