Cannon.js and Three.js – Character Control with Physics - three.js

I am trying to build a little world with a Third Person Controller.
For the character, I followed this tutorial by SimonDev and just changed the model from a FBX to GLTF.
Now, I try to implement a Physics World with Cannon.js.
I got it to the point where the collider body is positioned at the starting point of the model. But it stays there after I move the model. I need the collider body to be attached at the model.
I know that I should move the collider body and update the character model to that position but I cannot get it to work. This is my current code. Maybe it is just a simple solution but I am new to Cannon.js, so any help is appreciated. Thank you!
class BasicCharacterController {
constructor(experience, params) {
this.experience = experience;
this._Init(params);
}
_Init(params) {
this._params = params;
this._decceleration = new THREE.Vector3(-0.0005, -0.0001, -5.0);
this._acceleration = new THREE.Vector3(1, 0.25, 50.0);
this._velocity = new THREE.Vector3(0, 0, 0);
this._position = new THREE.Vector3();
this._animations = {};
this._input = new BasicCharacterControllerInput();
this._stateMachine = new CharacterFSM(
new BasicCharacterControllerProxy(this._animations));
this._LoadModels();
}
_LoadModels() {
this.physicsCharacterShape = new CANNON.Box(new CANNON.Vec3(0.5, 1, 0.5));
this.physicsCharacterBody = new CANNON.Body({
mass: 0,
shape: this.physicsCharacterShape,
position: new CANNON.Vec3(0, 0, 0)
});
this.experience.physicsWorld.addBody(this.physicsCharacterBody);
this.gltfLoader = new GLTFLoader();
this.gltfLoader.setPath('./sources/assets/');
this.gltfLoader.load('VSLBINA_TPOSE_GLTF.gltf', (gltf) => {
gltf.scene.traverse(c => {
c.castShadow = true;
});
this._target = gltf.scene;
this._params.scene.add(this._target);
this._target.position.copy(this.physicsCharacterBody.position);
this._target.quaternion.copy(this.physicsCharacterBody.quaternion);
this._mixer = new THREE.AnimationMixer(this._target);
this._manager = new THREE.LoadingManager();
this._manager.onLoad = () => {
this._stateMachine.SetState('idle');
};
const _OnLoad = (animName, anim) => {
const clip = anim.animations[0];
const action = this._mixer.clipAction(clip);
this._animations[animName] = {
clip: clip,
action: action,
};
};
const loader = new GLTFLoader(this._manager);
loader.setPath('./sources/assets/');
loader.load('VSLBINA_WALKING_GLTF.gltf', (a) => { _OnLoad('walk', a); });
loader.load('VSLBINA_IDLE_GLTF.gltf', (a) => { _OnLoad('idle', a); });
});
}
get Position() {
return this._position;
}
get Rotation() {
if (!this._target) {
return new THREE.Quaternion();
}
return this._target.quaternion;
}
Update(timeInSeconds) {
if (!this._stateMachine._currentState) {
return;
}
this._stateMachine.Update(timeInSeconds, this._input);
const velocity = this._velocity;
const frameDecceleration = new THREE.Vector3(
velocity.x * this._decceleration.x,
velocity.y * this._decceleration.y,
velocity.z * this._decceleration.z
);
frameDecceleration.multiplyScalar(timeInSeconds);
frameDecceleration.z = Math.sign(frameDecceleration.z) * Math.min(
Math.abs(frameDecceleration.z), Math.abs(velocity.z));
velocity.add(frameDecceleration);
const controlObject = this._target;
const _Q = new THREE.Quaternion();
const _A = new THREE.Vector3();
const _R = controlObject.quaternion.clone();
const acc = this._acceleration.clone();
if (this._input._keys.shift) {
acc.multiplyScalar(2.0);
}
if (this._input._keys.forward) {
velocity.z += acc.z * timeInSeconds;
}
if (this._input._keys.backward) {
velocity.z -= acc.z * timeInSeconds;
}
if (this._input._keys.left) {
_A.set(0, 1, 0);
_Q.setFromAxisAngle(_A, 4.0 * Math.PI * timeInSeconds * this._acceleration.y);
_R.multiply(_Q);
}
if (this._input._keys.right) {
_A.set(0, 1, 0);
_Q.setFromAxisAngle(_A, 4.0 * -Math.PI * timeInSeconds * this._acceleration.y);
_R.multiply(_Q);
}
controlObject.quaternion.copy(_R);
const oldPosition = new THREE.Vector3();
oldPosition.copy(controlObject.position);
const forward = new THREE.Vector3(0, 0, 1);
forward.applyQuaternion(controlObject.quaternion);
forward.normalize();
const sideways = new THREE.Vector3(1, 0, 0);
sideways.applyQuaternion(controlObject.quaternion);
sideways.normalize();
sideways.multiplyScalar(velocity.x * timeInSeconds);
forward.multiplyScalar(velocity.z * timeInSeconds);
controlObject.position.add(forward);
controlObject.position.add(sideways);
this._position.copy(controlObject.position);
if (this._mixer) {
this._mixer.update(timeInSeconds);
};
// Physics Collider Body
// if (this._target) {
// this._target.position.copy(this.physicsCharacterBody.position);
// this._target.quaternion.copy(this.physicsCharacterBody.quaternion);
// }
}
};

Related

Three.js + webxr animations not playing from .glb file

I am unable to get the animation clips from the .glb file to play. The .glb model loads and is displayed on the screen but the animation does not play.
The animations play fine when viewing the object in the three.js editor.
The .glb has 1 animation clip associated with it.
Any Help would be much appreciated!!!
import {
Mesh,
AmbientLight,
Clock,
AnimationMixer,
PerspectiveCamera,
Scene,
WebGLRenderer,
MeshBasicMaterial,
RingGeometry,
sRGBEncoding
} from 'three';
import {ARButton} from 'three/examples/jsm/webxr/ARButton';
import {GLTFLoader} from 'three/examples/jsm/loaders/GLTFLoader.js'
class App {
constructor() {
this.clock = new Clock();
this.camera = new PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
this.camera.position.set(0, 1.6, 3);
this.scene = new Scene();
this.renderer = new WebGLRenderer({
antialias: true,
alpha: true
});
this.renderer.setPixelRatio(window.devicePixelRatio);
this.renderer.setSize(window.innerWidth, window.innerHeight);
this.renderer.outputEncoding = sRGBEncoding;
document.body.appendChild(this.renderer.domElement);
this.initXR();
this.initScene();
window.addEventListener('resize', this.onWindowResize.bind(this), false);
this.renderer.setAnimationLoop(this.render.bind(this));
}
initXR() {
this.renderer.xr.enabled = true;
document.body.appendChild(ARButton.createButton(this.renderer, {requiredFeatures: ['hit-test']}));
this.hitTestSourceRequested = false;
this.hitTestSource = null;
this.controller = this.renderer.xr.getController(0);
this.controller.addEventListener('select', this.onSelect.bind(this));
}
initScene() {
let geometry = new RingGeometry(0.08, 0.10, 32).rotateX(-Math.PI / 2);
let material = new MeshBasicMaterial();
this.reticle = new Mesh(geometry, material);
this.reticle.matrixAutoUpdate = false;
this.reticle.visible = false;
this.scene.add(this.reticle);
const loader = new GLTFLoader();
loader.load('./models/stylized_character.glb', gltf => {
this.myObj = gltf;
gltf.scene.scale.set(.25, .25, .25);
gltf.scene.visible = true;
this.scene.add(gltf.scene);
//todo: this doesnt seem to work
this.mixer = new AnimationMixer(gltf);
var action = this.mixer.clipAction(gltf.animations[0]);
action.play();
});
var aLight = new AmbientLight(0xffffff, 1);
this.scene.add(aLight)
}
render(_, frame) {
const delta = this.clock.getDelta();
if (this.mixer) {
this.mixer.update(delta);
}
if (frame) {
if (this.hitTestSourceRequested === false) {
this.requestHitTestSource();
}
if (this.hitTestSource) {
this.getHitTestResults(frame);
}
}
this.renderer.render(this.scene, this.camera);
}
onWindowResize() {
this.camera.aspect = window.innerWidth / window.innerHeight;
this.camera.updateProjectionMatrix();
this.renderer.setSize(window.innerWidth, window.innerHeight);
this.renderer.render(this.scene, this.camera);
}
onSelect() {
if (this.reticle.visible) {
this.myObj.scene.position.setFromMatrixPosition(this.reticle.matrix);
this.myObj.scene.visible = true;
}
}
async requestHitTestSource() {
const session = this.renderer.xr.getSession();
session.addEventListener('end', () => {
this.hitTestSourceRequested = false;
this.hitTestSource = null;
});
const referenceSpace = await session.requestReferenceSpace('viewer');
this.hitTestSource = await session.requestHitTestSource({space: referenceSpace, entityTypes: ['plane']});
this.hitTestSourceRequested = true;
}
getHitTestResults(frame) {
const hitTestResults = frame.getHitTestResults(this.hitTestSource);
if (hitTestResults.length) {
const hit = hitTestResults[0];
const pose = hit.getPose(this.renderer.xr.getReferenceSpace());
this.reticle.visible = true;
this.reticle.matrix.fromArray(pose.transform.matrix);
} else {
this.reticle.visible = false;
}
}
}
window.addEventListener('DOMContentLoaded', () => {
new App();
});

How to use Threejs to create a boundary for the Mesh load by fbxloador

The created boundary's scale and rotation are totally different with the import fbxmodel.
Hi, I have loaded a fbx model into the scene by fbxLoador.
const addFbxModel = (modelName: string, position: Vector3) => {
const fbxLoader = new FBXLoader();
fbxLoader.load(
`../../src/assets/models/${modelName}.fbx`,
(fbxObject: Object3D) => {
fbxObject.position.set(position.x, position.y, position.z);
console.log(fbxObject.scale);
fbxObject.scale.set(0.01, 0.01, 0.01);
const material = new MeshBasicMaterial({ color: 0x008080 });
fbxObject.traverse((object) => {
if (object instanceof Mesh) {
object.material = material;
}
});
scene.add(fbxObject);
updateRenderer();
updateCamera();
render();
},
(xhr) => {
console.log((xhr.loaded / xhr.total) * 100 + "% loaded");
},
(error) => {
console.log(error);
}
);
};
And now i want to add a click function on this model which will highlight it by showing it's boundary box.
const onclick = (event: MouseEvent) => {
event.preventDefault();
if (renderBox.value) {
const mouse = new Vector2();
mouse.x = (event.offsetX / renderBox.value.clientWidth) * 2 - 1;
mouse.y = -(event.offsetY / renderBox.value.clientHeight) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObjects(scene.children, true);
if (intersects.length > 0) {
const intersect = intersects[0];
const object = intersect.object;
createBoundary(object);
}
}
};
const createBoundary = (object: Object3D) => {
if (object instanceof Mesh) {
console.log(object.geometry, object.scale, object.rotation);
const edges = new EdgesGeometry(object.geometry);
const material = new LineBasicMaterial({ color: 0xffffff });
const wireframe = new LineSegments(edges, material);
wireframe.scale.copy(object.scale);
wireframe.rotation.copy(object.rotation);
console.log(wireframe.scale);
scene.add(wireframe);
}
};
But now the boundary's scale and rotation are totally different with the fbxmodel.
And also the boundary is too complex, is it possible to create one only include the outline and the top point.
Thank you. :)

Postprocessing affects everything but it shouldn't

i have a question about render layers. I now have a large app. That's why I'm trying to explain my main problem here only with the part of the code where I have difficulties.
The special thing here is that I use a different camera for the depth texture for the post-processing with exactly the same positions and other values ​​as the texture camera. I only use a floating value in the near range because of the accuracy. it all works great.
my problem is that my postprocessing affects everything in the scene and of course it looks bad. my postprocessing should only affect certain objects in the scene.
my problem is that all objects are part of the scene. almost every object can spawn or disappear (triggered in other modules to keep code clean). it all works great. I just don't know how to limit (encapsulate) the postprocessing effect to objects of my choice.
as a very simple example, how would you add a box or all other objects here so that they would not be affected by the postprocessing.
import {THREE, OrbitControls, RenderPass, ShaderPass, EffectComposer, CopyShader, FXAAShader} from './three-defs.js';
import {entity} from "./entity.js";
const vertexShad = `
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`;
const fragmentShad = `
varying vec2 vUv;
uniform sampler2D tDiffuse;
void main() {
vec3 color = texture2D(tDiffuse, vUv).rgb;
gl_FragColor = vec4(color, 1.);
}
`;
export const threejs_component = (() => {
class ThreeJSController extends entity.Component {
constructor() {
super();
}
InitEntity() {
this.threejs_ = new THREE.WebGLRenderer({antialias: true, logarithmicDepthBuffer: true});
this.threejs_.outputEncoding = THREE.sRGBEncoding;
this.threejs_.setPixelRatio(window.devicePixelRatio);
this.threejs_.shadowMap.enabled = true;
this.threejs_.shadowMap.type = THREE.PCFSoftShadowMap;
this.threejs_.domElement.id = 'threejs';
this.container = document.getElementById('container');
this.threejs_.setSize(this.container.clientWidth, this.container.clientHeight);
this.container.appendChild( this.threejs_.domElement );
const aspect = this.container.clientWidth / this.container.clientHeight;
const fov = 60;
const near = 1e-6;
const far = 1E27;
this.camera_ = new THREE.PerspectiveCamera(fov, aspect, near, far);
this.scene_ = new THREE.Scene();
this.scene_.background = new THREE.Color( 0x111111 );
this.depthCameraNear_ = 100000;
this.depthCamera_ = new THREE.PerspectiveCamera(fov, aspect, this.depthCameraNear_, far);
this.postCamera_ = new THREE.OrthographicCamera( - 1, 1, 1, - 1, 0, 1 );
this.postScene_ = new THREE.Scene();
/*
this.composer_ = new EffectComposer(this.threejs_);
const renderPass = new RenderPass(this.scene_, this.camera_);
const fxaaPass = new ShaderPass(FXAAShader);
this.composer_.addPass(renderPass);
this.composer_.addPass(fxaaPass);
*/
this.resolution_ = new THREE.Vector2();
this.threejs_.getDrawingBufferSize(this.resolution_);
this.target1_ = new THREE.WebGLRenderTarget(this.resolution_.x, this.resolution_.y);
this.target1_.texture.format = THREE.RGBFormat;
this.target1_.texture.minFilter = THREE.NearestFilter;
this.target1_.texture.magFilter = THREE.NearestFilter;
this.target1_.texture.generateMipmaps = false;
this.target1_.stencilBuffer = false;
this.target2_ = new THREE.WebGLRenderTarget(this.resolution_.x, this.resolution_.y);
this.target2_.stencilBuffer = false;
this.target2_.depthBuffer = true;
this.target2_.depthTexture = new THREE.DepthTexture();
this.target2_.depthTexture.format = THREE.DepthFormat;
this.target2_.depthTexture.type = THREE.FloatType;
this.depthPass_ = new THREE.ShaderMaterial({
vertexShader: vertexShad,
fragmentShader: fragmentShad,
uniforms: {
tDiffuse: { value: null },
tDepth: { value: null },
},
});
var postPlane = new THREE.PlaneBufferGeometry( 2, 2 );
this.postScreen_ = new THREE.Mesh( postPlane, this.depthPass_ );
this.postScene_.add( this.postScreen_ );
}
Render() {
this.threejs_.setRenderTarget(this.target1_);
this.threejs_.clear();
this.threejs_.render(this.scene_, this.camera_);
this.threejs_.setRenderTarget( null );
this.threejs_.setRenderTarget(this.target2_);
this.threejs_.clear();
this.threejs_.render(this.scene_, this.depthCamera_);
this.threejs_.setRenderTarget( null );
this.depthPass_.uniforms.tDiffuse.value = this.target1_.texture;
this.depthPass_.uniforms.tDepth.value = this.target2_.depthTexture;
this.depthPass_.uniformsNeedUpdate = true;
this.threejs_.render(this.postScene_, this.postCamera_);
}
Update(timeElapsed) {
const player = this.FindEntity('player');
if (!player) {
return;
}
const pos = player._position;
}
}//end class
return {
ThreeJSController: ThreeJSController,
};
})();
maybe my postprocessing plane is a bad idea and the composer with layers is the solution? or i need both?
Programming can be frustrating at times. after half a day i have the solution. I use the composer because it makes the code shorter and cleaner.
export const threejs_component = (() => {
class ThreeJSController extends entity.Component {
constructor() {
super();
}
InitEntity() {
this.threejs_ = new THREE.WebGLRenderer({antialias: true, logarithmicDepthBuffer: true});
this.threejs_.outputEncoding = THREE.sRGBEncoding;
this.threejs_.setPixelRatio(window.devicePixelRatio);
this.threejs_.shadowMap.enabled = true;
this.threejs_.shadowMap.type = THREE.PCFSoftShadowMap;
this.threejs_.domElement.id = 'threejs';
this.container = document.getElementById('container');
this.threejs_.setSize(this.container.clientWidth, this.container.clientHeight);
this.container.appendChild( this.threejs_.domElement );
const aspect = this.container.clientWidth / this.container.clientHeight;
const fov = 60;
const near = 1e-6;
const far = 1E27;
this.camera_ = new THREE.PerspectiveCamera(fov, aspect, near, far);
this.scene_ = new THREE.Scene();
//this.scene_.background = new THREE.Color( 0x111111 );
this.threejs_.setClearColor( 0x000000 );
this.depthCameraNear_ = 100000;
this.depthCamera_ = new THREE.PerspectiveCamera(fov, aspect, this.depthCameraNear_, far);
this.composer_ = new EffectComposer(this.threejs_);
const renderPass = new RenderPass(this.scene_, this.camera_);
const fxaaPass = new ShaderPass(FXAAShader);
this.composer_.addPass(renderPass);
this.composer_.addPass(fxaaPass);
this.resolution_ = new THREE.Vector2();
this.threejs_.getDrawingBufferSize(this.resolution_);
this.target_ = new THREE.WebGLRenderTarget(this.resolution_.x, this.resolution_.y);
this.target_.stencilBuffer = false;
this.target_.depthBuffer = true;
this.target_.depthTexture = new THREE.DepthTexture();
this.target_.depthTexture.format = THREE.DepthFormat;
this.target_.depthTexture.type = THREE.FloatType;
}
Render() {
this.threejs_.autoClear = true;
this.threejs_.setRenderTarget(this.target_);
this.threejs_.render(this.scene_, this.depthCamera_);
this.threejs_.setRenderTarget( null );
this.threejs_.clear();
this.camera_.layers.set(0);
this.threejs_.render(this.scene_, this.camera_);
this.composer_.render();
this.threejs_.autoClear = false;
this.threejs_.clearDepth();
this.camera_.layers.set(1);
this.threejs_.render(this.scene_, this.camera_);
}
Update(timeElapsed) {
const player = this.FindEntity('player');
if (!player) {
return;
}
const pos = player._position;
}
}//end class
return {
ThreeJSController: ThreeJSController,
};
})();
submodules access the composer to set postprocessing shaders, like this
composer_.addPass(new ShaderPass(customShader));
for groups i use this to set the models and groups to the preferred layer
model.traverse( function( child ) { child.layers.set(1); });
I almost never drink beer, but now I could use one to celebrate the day 🥳

Drag&Drop element into canvas from HTML

I've got a very trivial task to drag&drop the element from a gallery into the Three.js canvas.
Nothing seems tricky until I face the problem that when I drag the item and add it to the scene I cannot update the item's coordinates until I finish the drag&drop event
I already played around with all events that watch the mouse event mousemove, drag, dragover but the element is just stuck at the initial coordinates, the ones I applied in the dragenter event
export const params = {
devicePixelRatio: Math.min(window.devicePixelRatio, 2),
size: getSizeParams(),
grid: {
size: 20,
divisions: 20,
},
}
const itemProtos = ['Box', 'Sphere', 'Cone']
export const canvas = document.querySelector(`#canvas`)
const raycaster = new Raycaster()
const pointer = new Vector2()
const scene = new Scene()
const camera = new PerspectiveCamera(75, params.size.width / params.size.height, 0.1, 100)
camera.position.z = 5
camera.position.y = 2
/**
* Variable for Drag&Drop - just created object that's being moved around
*/
let newObjectType = null
let newObject = null
/**
* Groups
*/
export const itemGroup = new Group()
scene.add(itemGroup)
/**
* Grid
*/
export const gridHelper = new GridHelper(params.grid.size, params.grid.divisions)
scene.add(gridHelper)
/**
* Renderer
*/
const renderer = new WebGLRenderer({
canvas,
antialias: true,
})
renderer.setSize(params.size.width, params.size.height)
renderer.setPixelRatio(params.devicePixelRatio)
/**
* Resizing updates to fit the screen
*/
window.addEventListener('resize', () => {
params.size = getSizeParams()
camera.aspect = params.size.width / params.size.height
camera.updateProjectionMatrix()
renderer.setSize(params.size.width, params.size.height)
renderer.setPixelRatio()
})
canvas.addEventListener('mouseenter', () => {
canvas.style.cursor = 'grab'
})
/**
* Controls
*/
const orbitControls = new OrbitControls(camera, canvas)
orbitControls.enableDamping = true
orbitControls.addEventListener('start', () => {
canvas.style.cursor = 'grabbing'
})
orbitControls.addEventListener('end', () => {
canvas.style.cursor = 'grab'
})
const tick = () => {
orbitControls.update()
requestAnimationFrame(tick)
renderer.render(scene, camera)
}
window.onload = tick
/**
* Raycaster functions
*/
const refreshMouseCoords = (event) => {
pointer.x = (event.clientX / params.size.width) * 2 - 1
pointer.y = -(event.clientY / params.size.height) * 2 + 1
}
let currentIntersect = null
let currentPick = null
canvas.addEventListener('mousemove', (event) => {
refreshMouseCoords(event)
raycaster.setFromCamera(pointer, camera)
const intersects = raycaster.intersectObjects(itemGroup.children, false)
if(intersects.length && intersects[0].object instanceof Mesh) {
if(!currentIntersect) {
canvas.style.cursor = 'all-scroll'
intersects[0].object.material.color.set('red')
console.log(`mouse enter`)
}
if(currentIntersect && currentIntersect !== intersects[0].object) {
currentIntersect.material.color.set('blue')
intersects[0].object.material.color.set('red')
}
currentIntersect = intersects[0].object
} else {
if(currentIntersect) {
console.log(`mouse leave`)
currentIntersect.material.color.set('blue')
canvas.style.cursor = 'grab'
}
currentIntersect = null
}
moveItem(currentPick)
})
/**
* Function to move items around GridHelper
*/
const moveItem = (item) => {
const intersectsGround = raycaster.intersectObject(gridHelper, false)
if(item && intersectsGround[0]) {
item.position.z = intersectsGround[0].point.z
item.position.x = intersectsGround[0].point.x
}
}
canvas.addEventListener('mousedown', (event) => {
event.preventDefault()
refreshMouseCoords(event)
raycaster.setFromCamera(pointer, camera)
const intersects = raycaster.intersectObjects(itemGroup.children, false)
if(intersects.length && intersects[0].object instanceof Mesh) {
currentPick = intersects[0].object
}
if(currentIntersect) {
canvas.style.cursor = 'all-scroll'
orbitControls.enabled = false
}
})
canvas.addEventListener('mouseup', () => {
if(currentIntersect) {
canvas.style.cursor = 'all-scroll'
orbitControls.enabled = true
}
if(currentPick) {
currentPick = null
}
})
// *** Drag&Drop *** //
const gallery = document.querySelector(`#gallery`)
setGallery(itemProtos, gallery)
canvas.addEventListener('dragenter', (event) => {
event.preventDefault()
console.log('Drag&Drop: dragenter')
refreshMouseCoords(event)
raycaster.setFromCamera(pointer, camera)
const intersects = raycaster.intersectObject(gridHelper)
if (intersects.length && newObjectType) {
add3DEl(intersects[0].point, newObjectType, itemGroup)
}
})
canvas.addEventListener('dragover', (event) => {
event.preventDefault()
event.stopPropagation()
if(newObject) {
moveItem(newObject)
}
})
function setGallery(itemProtos, gallery) {
for (let i of itemProtos) {
const el = createProto()
gallery.appendChild(el)
el.addEventListener('dragstart', function (event) {
event.dataTransfer.setData('text/plain', i)
newObjectType = i
})
el.addEventListener('dragend', function () {
newObjectType = null
newObject = null
})
}
}
function add3DEl({ x, z }, type = 'Box', scene) {
const geometry = new itemObjects[`${type}Geometry`]()
const material = new MeshBasicMaterial({ color: 0x0000ff }) // Shared material for all items
material.wireframe = true
const el = new Mesh(geometry, material)
el.position.x = x
el.position.y = type == 'Sphere' ? 1 : .5
el.position.z = z
el.userData.name = `${type}_${Date.now()}`
newObject = el
scene.add(el)
}
Here you can find a playgroud with what I've got so far: Playground

Three JS - Uncaught TypeError: Cannot read property 'x' of undefined

I'm trying to create a morphing object based on a number of particles, I have 4 objects, 2 normal three js shapes (cube and sphere) and 2 OBJ objects.
When I hover the related name of the object it changes to that one.
The problem here is that when I try to hover the name of the obj object the console.log returns the following error at the last line where I try to get newParticles.vertices[i].x, etc:
Uncaught TypeError: Cannot read property 'x' of undefined
Code:
// Particle Vars
var particleCount = numberOfParticles;
let spherePoints,
cubePoints,
rocketPoints,
spacemanPoints;
var particles = new Geometry(),
sphereParticles = new Geometry(),
cubeParticles = new Geometry(),
rocketParticles = new Geometry(),
spacemanParticles = new Geometry();
var pMaterial = new PointsMaterial({
color: particleColor,
size: particleSize,
map: new TextureLoader().load(particleImage),
blending: AdditiveBlending,
transparent: true
});
// Objects
var geometry = new SphereGeometry( 5, 30, 30 );
spherePoints = GeometryUtils.randomPointsInGeometry(geometry, particleCount)
var geometry = new BoxGeometry( 9, 9, 9 );
cubePoints = GeometryUtils.randomPointsInGeometry(geometry, particleCount)
// Custom (OGJ) Objects
const codepenAssetUrl = 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/605067/';
var objLoader = new OBJLoader();
objLoader.setPath('./upload/');
objLoader.load( 'Nymph.obj', function ( object ) {
object.traverse( function ( child ) {
if ( child instanceof Mesh ) {
let scale = 0.2; //era 2.1 per il razzo
let area = new Box3();
area.setFromObject( child );
let yOffset = (area.max.y * scale) / 2;
child.geometry.scale(scale,scale,scale);
rocketPoints = GeometryUtils.randomPointsInBufferGeometry(child.geometry, particleCount);
createVertices(rocketParticles, rocketPoints, yOffset, 2);
}
});
});
var objLoader = new OBJLoader();
objLoader.setPath(codepenAssetUrl);
objLoader.load( 'Astronaut.obj', function ( object ) {
object.traverse( function ( child ) {
if ( child instanceof Mesh ) {
let scale = 4.6;
let area = new Box3();
area.setFromObject( child );
let yOffset = (area.max.y * scale) / 2;
child.geometry.scale(scale,scale,scale);
spacemanPoints = GeometryUtils.randomPointsInBufferGeometry(child.geometry, particleCount);
createVertices(spacemanParticles, spacemanPoints, yOffset, 3);
}
});
});
// Particles
for (var p = 0; p < particleCount; p++) {
var vertex = new Vector3();
vertex.x = 0;
vertex.y = 0;
vertex.z = 0;
particles.vertices.push(vertex);
}
createVertices (sphereParticles, spherePoints, null, null)
createVertices (cubeParticles, cubePoints, null, 1)
function createVertices (emptyArray, points, yOffset = 0, trigger = null) {
for (var p = 0; p < particleCount; p++) {
var vertex = new Vector3();
vertex.x = points[p]['x'];
vertex.y = points[p]['y'] - yOffset;
vertex.z = points[p]['z'];
emptyArray.vertices.push(vertex);
}
if (trigger !== null) {
triggers[trigger].setAttribute('data-disabled', false)
}
}
var particleSystem = new Points(
particles,
pMaterial
);
particleSystem.sortParticles = true;
// Add the particles to the scene
scene.add(particleSystem);
// Animate
const normalSpeed = (defaultAnimationSpeed/100),
fullSpeed = (morphAnimationSpeed/100)
let animationVars = {
speed: normalSpeed
}
function animate() {
particleSystem.rotation.y += animationVars.speed;
particles.verticesNeedUpdate = true;
window.requestAnimationFrame( animate );
renderer.render( scene, camera );
}
animate();
setTimeout(toSphere, 500);
function toSphere () {
handleTriggers(0);
morphTo(sphereParticles);
}
function toCube () {
handleTriggers(1);
morphTo(cubeParticles);
}
function toRocket () {
handleTriggers(2);
morphTo(rocketParticles);
}
function toSpaceman () {
handleTriggers(3);
morphTo(spacemanParticles);
}
function morphTo (newParticles, color = '0xffffff') {
TweenMax.to(animationVars, .3, {ease:
Power4.easeIn, speed: fullSpeed, onComplete: slowDown});
particleSystem.material.color.setHex(color);
for (var i = 0; i < particles.vertices.length; i++){
TweenMax.to(particles.vertices[i], 4, {ease:
Elastic.easeOut.config( 1, 0.75), x: newParticles.vertices[i].x, y: newParticles.vertices[i].y, z: newParticles.vertices[i].z})
}
}
P.S. note that I'm using webpack.

Resources