How to levitate non-static objects with oimo.js - three.js

I use the oimo.js library (physic engine) with three.js and I would like my objects (not static) to levitate.
https://github.com/lo-th/Oimo.js/
I try to position the objects at a y value defined in my loop function but they keep falling.
How can I have these objects levitate while keeping non-static objects?
Here below my codepen to explain that :
https://codepen.io/ogames/pen/oNEWKLE?editors=0011
import * as THREE from 'https://cdn.jsdelivr.net/npm/three#0.117.1/build/three.module.js'
import {
OrbitControls
} from "https://cdn.jsdelivr.net/npm/three#0.117.1/examples/jsm/controls/OrbitControls.js"
//import * as oimo from 'https://cdnjs.cloudflare.com/ajax/libs/oimo/1.0.9/oimo.min.js'
import {
TWEEN
} from 'https://unpkg.com/three#0.125.2/examples//jsm/libs/tween.module.min'
import {
CSM
} from "https://cdn.jsdelivr.net/npm/three#0.117.1/examples/jsm/csm/CSM.js"
///////////////////////////////////////////////////////////////////////////////
// utils
///////////////////////////////////////////////////////////////////////////////
export const random = (min, max) => Math.floor(Math.random() * (max - min + 1) + min);
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
// variables
///////////////////////////////////////////////////////////////////////////////
var scene, container, controls, camera, renderer, light, ambient_light, additionalDirectionalLight, csm, stats, physics, position, body, world, ground_body, camera_body;
var game_start = false
window.setTimeout(() => {
game_start = true
}, 1000)
//material
var m = {};
//geometry
var g = {};
//objects
var o = {};
//
o.enemy = [];
//class
class Enemy_instance {
constructor(config) {
var g = {}
var m = {}
this.config = config;
g.body = new THREE.BoxGeometry(1, 1, 1);
m.body = new THREE.MeshToonMaterial({
color: this.config.color_body,
});
// a lot of body
this.body = this.create_instance(g.body, m.body, this.config.count, true);
this.config.scene.add(this.body)
// create several group and add dummy into it
this.mesh = [];
this.body.dummy = []
this.physic_body = []
for (let i = 0; i < this.config.count; i++) {
this.body.dummy[i] = new THREE.Object3D()
this.mesh[i] = new THREE.Group()
this.mesh[i].is_alive = true;
this.mesh[i].is_collide = false;
// rename
let mesh = this.mesh[i]
mesh.position.x = this.config.posx[i]
mesh.position.y = this.config.posy
mesh.position.z = this.config.posz[i]
this.config.scene.add(mesh)
this.physic_body[i] = this.config.world.add({
type: 'box', // type of shape : sphere, box, cylinder
size: [1, 1, 1], // size of shape
pos: [mesh.position.x, mesh.position.y, mesh.position.z], // start position in degree
rot: [0, 0, 0], // start rotation in degree
move: true, // dynamic or statique
density: 10,
friction: .5,
restitution: 0.1,
belongsTo: 1, // The bits of the collision groups to which the shape belongs.
collidesWith: 0xffffffff // The bits of the collision groups with which the shape collides.
});
this.physic_body[i].position.copy(mesh.position)
this.physic_body[i].quaternion.copy(mesh.position)
console.log(this.physic_body[0])
};
}
//without having "this[obj]mesh"
create_instance(geo, mat, count, fl) {
let obj = new THREE.InstancedMesh(geo, mat, count);
obj.castShadow = fl
obj.receiveShadow = fl
obj.dummy = []
return obj
}
// for all instances
animate_instance(obj) {
obj.instanceMatrix.needsUpdate = true;
for (let i = 0; i < this.config.count; i++) {
obj.dummy[i].updateMatrix()
obj.setMatrixAt(i, obj.dummy[i].matrix);
this.add_element(obj.dummy, i)
}
}
add_element(obj, num) {
let master = this.mesh[num]
let slave = obj[num]
slave.position.set(master.position.x, master.position.y, master.position.z)
slave.rotation.set(master.rotation.x, master.rotation.y, master.rotation.z)
}
die(num) {
this.mesh[num].is_alive = false
this.hide(this.body.dummy[num])
}
hide(obj) {
new TWEEN.Tween(obj.scale)
.to({
x: 1.2,
y: 1.2,
z: 1.2,
}, 100)
.yoyo(true)
.delay(2000)
.repeat(1)
.easing(TWEEN.Easing.Linear.None)
.start()
.onComplete(() => {
new TWEEN.Tween(obj.scale)
.to({
x: 0,
y: 0,
z: 0,
}, 80)
.easing(TWEEN.Easing.Linear.None)
.start()
.onComplete(() => {});
});
}
animate() {
this.animate_instance(this.body)
for (let i = 0; i < this.config.count; i++) {
this.mesh[i].position.copy(this.physic_body[i].getPosition());
this.mesh[i].quaternion.copy(this.physic_body[i].getQuaternion());
// contacts
if (this.physic_body[i].numContacts > 0) {
this.mesh[i].is_collide = true
this.mesh[i].is_alive && this.die(i)
}
// no contacts
if (this.mesh[i].is_collide == false) {
this.physic_body[i].position.y = 2
}
}
}
};
var data = {
fade: true,
far: 200,
mode: 'practical',
lightX: -.31,
lightY: -1,
}
init_app();
function init_app() {
start_physic()
init();
animate();
}
// init physic with oimo
function start_physic() {
world = new OIMO.World({
timestep: 1 / 30,
// timestep: 1 / 60,
iterations: 1,
// iterations: 8,
broadphase: 2, // 1 brute force, 2 sweep and prune, 3 volume tree
worldscale: 1, // scale full world
random: true, // randomize sample
info: true, // calculate statistic or not
gravity: [0, -10, 0]
});
ground_body = world.add({
type: 'box', // type of shape : sphere, box, cylinder
size: [100000, .1, 100000], // size of shape
pos: [0, -.7, 0], // start position in degree
rot: [0, 0, 0], // start rotation in degree
move: false, // dynamic or statique
density: 1,
friction: 0.2,
restitution: 0.2,
belongsTo: 1, // The bits of the collision groups to which the shape belongs.
collidesWith: 0xffffffff // The bits of the collision groups with which the shape collides.
});
}
function init() {
Minimal()
create_object()
init_camera()
}
function create_object() {
create_ground();
create_enemy();
}
function init_camera() {
camera_body = world.add({
type: 'sphere', // type of shape : sphere, box, cylinder
size: [10, 10, 10], // size of shape
pos: [camera.position.x, camera.position.y, camera.position.z], // start position in degree
rot: [0, 0, 0], // start rotation in degree
move: true, // dynamic or statique
density: .0001,
friction: 0,
restitution: .5,
belongsTo: 1, // The bits of the collision groups to which the shape belongs.
collidesWith: 0xffffff1 // The bits of the collision groups with which the shape collides.
});
camera_body.position.copy(camera.position)
camera_body.quaternion.copy(camera.position)
}
function create_ground() {
g.ground = new THREE.BoxGeometry(100000, .1, 100000);
m.ground = new THREE.MeshStandardMaterial({
color: 0xfce098,
});
csm.setupMaterial(m.ground)
o.ground = new THREE.Mesh(g.ground, m.ground);
o.ground.position.y = -.7
o.ground.castShadow = true;
o.ground.receiveShadow = true;
scene.add(o.ground);
}
function create_enemy() {
let c = {
scene: scene,
world: world,
speed: random(8, 16) * .01,
color_body: 0xbb1a1a,
count: 50,
}
c.posx = []
c.posz = []
for (let i = 0; i < c.count; i++) {
c.posx[i] = random(-20, -29)
c.posy = 20
c.posz[i] = i * 7
};
o.enemy = new Enemy_instance(c)
}
function Minimal() {
container = document.getElementById('world');
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(90, window.innerWidth / window.innerHeight, 2, 20000);
camera.position.y = 0
camera.position.z = -4
// camera.position.x = -4
camera.lookAt(0, 0, 0)
camera.updateMatrixWorld()
renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true,
});
//Shadows
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.autoClear = false;
// ORBIT
controls = new OrbitControls(camera, renderer.domElement);
controls.target.y = 0
controls.maxPolarAngle = Math.PI
controls.maxDistance = 10000;
controls.minDistance = .01;
// LIGHTS
ambient_light = new THREE.AmbientLight(0xffffff, 0.4);
scene.add(ambient_light);
//PLUG IN SHADOW FOR HUGE SCENE
csm = new CSM({
maxFar: data.far,
cascades: 4,
mode: data.mode,
parent: scene,
shadowMapSize: 4048,
lightDirection: new THREE.Vector3(data.lightX, data.lightY, data.lightZ).normalize(),
camera: camera
});
csm.fade = true;
container.appendChild(renderer.domElement);
}
function animate() {
requestAnimationFrame(animate);
world.step();
o.enemy.animate()
// copy position and rotation to three mesh
o.ground.position.copy(ground_body.getPosition());
o.ground.quaternion.copy(ground_body.getQuaternion());
// physical camera
camera_body.position.copy(camera.position)
camera_body.quaternion.copy(camera.position)
TWEEN.update();
camera.updateMatrixWorld();
csm.update();
renderer.render(scene, camera);
};
Thank you for your precious help (I'm stuck on this question for 10 days without finding a real solution).

Thanks #Marquizzo,
It works with create an "new THREE.Object3D();" and set position with another object :
https://codepen.io/ogames/pen/mdXwbvr
import * as THREE from 'https://cdn.jsdelivr.net/npm/three#0.117.1/build/three.module.js'
import {
OrbitControls
} from "https://cdn.jsdelivr.net/npm/three#0.117.1/examples/jsm/controls/OrbitControls.js"
//import * as oimo from 'https://cdnjs.cloudflare.com/ajax/libs/oimo/1.0.9/oimo.min.js'
import {
TWEEN
} from 'https://unpkg.com/three#0.125.2/examples//jsm/libs/tween.module.min'
import {
CSM
} from "https://cdn.jsdelivr.net/npm/three#0.117.1/examples/jsm/csm/CSM.js"
///////////////////////////////////////////////////////////////////////////////
// utils
///////////////////////////////////////////////////////////////////////////////
export const random = (min, max) => Math.floor(Math.random() * (max - min + 1) + min);
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
// variables
///////////////////////////////////////////////////////////////////////////////
var scene, container, controls, camera, renderer, light, ambient_light, additionalDirectionalLight, csm, stats, physics, position, body, world, ground_body, camera_body;
var game_start = false
window.setTimeout(() => {
game_start = true
}, 2000)
//material
var m = {};
//geometry
var g = {};
//objects
var o = {};
//
o.enemy = [];
//class
class Enemy_instance {
constructor(config) {
var g = {}
var m = {}
this.config = config;
g.body = new THREE.BoxGeometry(1, 1, 1);
m.body = new THREE.MeshToonMaterial({
color: this.config.color_body,
});
// a lot of body
this.body = this.create_instance(g.body, m.body, this.config.count, true);
this.config.scene.add(this.body)
// create several group and add dummy into it
this.mesh = [];
this.body.dummy = []
this.physic_body = []
for (let i = 0; i < this.config.count; i++) {
this.body.dummy[i] = new THREE.Object3D()
this.mesh[i] = new THREE.Group()
this.mesh[i].is_alive = true;
this.mesh[i].is_collide = false;
// rename
let mesh = this.mesh[i]
mesh.position.x = this.config.posx[i]
mesh.position.y = this.config.posy
mesh.position.z = this.config.posz[i]
this.config.scene.add(mesh)
this.physic_body[i] = this.config.world.add({
type: 'box', // type of shape : sphere, box, cylinder
size: [1, 1, 1], // size of shape
pos: [mesh.position.x, mesh.position.y, mesh.position.z], // start position in degree
rot: [0, 0, 0], // start rotation in degree
move: true, // dynamic or statique
density: 10,
friction: .5,
restitution: 0.1,
belongsTo: 1, // The bits of the collision groups to which the shape belongs.
collidesWith: 0xffffffff // The bits of the collision groups with which the shape collides.
});
this.physic_body[i].position.copy(mesh.position)
this.physic_body[i].quaternion.copy(mesh.position)
console.log(this.physic_body[0])
};
}
//without having "this[obj]mesh"
create_instance(geo, mat, count, fl) {
let obj = new THREE.InstancedMesh(geo, mat, count);
obj.castShadow = fl
obj.receiveShadow = fl
obj.dummy = []
return obj
}
// for all instances
animate_instance(obj) {
obj.instanceMatrix.needsUpdate = true;
for (let i = 0; i < this.config.count; i++) {
obj.dummy[i].updateMatrix()
obj.setMatrixAt(i, obj.dummy[i].matrix);
this.add_element(obj.dummy, i)
}
}
add_element(obj, num) {
let master = this.mesh[num]
let slave = obj[num]
slave.position.set(master.position.x, master.position.y, master.position.z)
slave.rotation.set(master.rotation.x, master.rotation.y, master.rotation.z)
}
die(num) {
this.mesh[num].is_alive = false
//console.log(num, "collide and die")
this.hide(this.body.dummy[num])
}
hide(obj) {
new TWEEN.Tween(obj.scale)
.to({
x: 1.2,
y: 1.2,
z: 1.2,
}, 100)
.yoyo(true)
.delay(2000)
.repeat(1)
.easing(TWEEN.Easing.Linear.None)
.start()
.onComplete(() => {
new TWEEN.Tween(obj.scale)
.to({
x: 0,
y: 0,
z: 0,
}, 80)
.easing(TWEEN.Easing.Linear.None)
.start()
.onComplete(() => {});
});
}
move(num) {
if (this.config.posx[num] < 0) {
this.physic_body[num].position.x += this.config.speed
}
if (this.config.posx[num] > 0) {
this.physic_body[num].position.x -= this.config.speed
}
if (this.physic_body[num].position.x > 30) {
this.physic_body[num].position.x = this.config.posx[num]
}
if (this.physic_body[num].position.x < -30) {
this.physic_body[num].position.x = this.config.posx[num]
}
}
animate() {
this.animate_instance(this.body)
for (let i = 0; i < this.config.count; i++) {
this.mesh[i].position.copy(this.physic_body[i].getPosition());
this.mesh[i].quaternion.copy(this.physic_body[i].getQuaternion());
// contacts
if (this.physic_body[i].numContacts > 0) {
this.mesh[i].is_collide = true
this.mesh[i].is_alive && this.die(i)
}
// no contacts
if (this.mesh[i].is_collide == false) {
this.physic_body[i].position.y = 2
this.move(i)
}
}
}
};
var data = {
fade: true,
far: 200,
mode: 'practical',
lightX: -.31,
lightY: -1,
}
init_app();
function init_app() {
start_physic()
init();
animate();
}
// init physic with oimo
function start_physic() {
world = new OIMO.World({
timestep: 1 / 30,
// timestep: 1 / 60,
iterations: 1,
// iterations: 8,
broadphase: 2, // 1 brute force, 2 sweep and prune, 3 volume tree
worldscale: 1, // scale full world
random: true, // randomize sample
info: true, // calculate statistic or not
gravity: [0, -20, 0]
});
ground_body = world.add({
type: 'box', // type of shape : sphere, box, cylinder
size: [100000, .1, 100000], // size of shape
pos: [0, -.7, 0], // start position in degree
rot: [0, 0, 0], // start rotation in degree
move: false, // dynamic or statique
density: 1,
friction: 0.2,
restitution: 0.2,
belongsTo: 1, // The bits of the collision groups to which the shape belongs.
collidesWith: 0xffffffff // The bits of the collision groups with which the shape collides.
});
}
function init() {
Minimal()
create_object()
init_camera()
}
function create_object() {
create_ground();
create_enemy();
o.paddel = new THREE.Object3D();
o.paddel.position.set(10, 10, 10)
scene.add(o.paddel);
}
function init_camera() {
camera_body = world.add({
type: 'sphere', // type of shape : sphere, box, cylinder
size: [10, 10, 10], // size of shape
pos: [camera.position.x, camera.position.y, camera.position.z], // start position in degree
rot: [0, 0, 0], // start rotation in degree
move: true, // dynamic or statique
density: .0001,
friction: 0,
restitution: .5,
belongsTo: 1, // The bits of the collision groups to which the shape belongs.
collidesWith: 0xffffff1 // The bits of the collision groups with which the shape collides.
});
camera_body.position.copy(camera.position)
camera_body.quaternion.copy(camera.position)
}
function create_ground() {
g.ground = new THREE.BoxGeometry(100000, .1, 100000);
m.ground = new THREE.MeshStandardMaterial({
color: 0xfce098,
});
csm.setupMaterial(m.ground)
o.ground = new THREE.Mesh(g.ground, m.ground);
o.ground.position.y = -.7
o.ground.castShadow = true;
o.ground.receiveShadow = true;
scene.add(o.ground);
}
function create_enemy() {
let c = {
scene: scene,
world: world,
speed: random(8, 16) * .01,
color_body: 0xbb1a1a,
count: 5000,
}
c.posx = []
c.posy = 20
c.posz = []
for (let i = 0; i < c.count; i++) {
// pour générer du choix
let rnd = Math.round(Math.random())
if (rnd == 0) {
c.posx[i] = random(20, 29)
} else {
c.posx[i] = random(-20, -29)
}
c.posz[i] = i * 7
};
o.enemy = new Enemy_instance(c)
}
function Minimal() {
container = document.getElementById('world');
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(90, window.innerWidth / window.innerHeight, 2, 20000);
camera.position.y = 0
camera.position.z = -4
// camera.position.x = -4
camera.lookAt(0, 0, 0)
camera.updateMatrixWorld()
renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true,
});
//Shadows
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.autoClear = false;
// ORBIT
controls = new OrbitControls(camera, renderer.domElement);
controls.target.y = 0
controls.maxPolarAngle = Math.PI
controls.maxDistance = 10000;
controls.minDistance = .01;
// LIGHTS
ambient_light = new THREE.AmbientLight(0xffffff, 0.4);
scene.add(ambient_light);
//PLUG IN SHADOW FOR HUGE SCENE
csm = new CSM({
maxFar: data.far,
cascades: 4,
mode: data.mode,
parent: scene,
shadowMapSize: 4048,
lightDirection: new THREE.Vector3(data.lightX, data.lightY, data.lightZ).normalize(),
camera: camera
});
csm.fade = true;
container.appendChild(renderer.domElement);
}
function animate() {
requestAnimationFrame(animate);
world.step();
o.enemy.animate()
o.enemy.physic_body[0].setPosition(o.paddel.position);
// copy position and rotation to three mesh
o.ground.position.copy(ground_body.getPosition());
o.ground.quaternion.copy(ground_body.getQuaternion());
// physical camera
camera_body.position.copy(camera.position)
camera_body.quaternion.copy(camera.position)
TWEEN.update();
camera.updateMatrixWorld();
csm.update();
renderer.render(scene, camera);
};

Related

How to use line with width of three.js with morphTargets to animate one line moving to another

question reference
I want to implement an animation.
The animation should be a line move to another line. There will be some deformation in the process of the line moving
There is a correspondence between the points of the two lines.
I can use the basic line of three.js to animate one line moving to another.
<script type="module">
import * as THREE from '../../build/three.module.js'
import { OrbitControls } from '../jsm/controls/OrbitControls.js'
function main() {
var scene = new THREE.Scene()
const geometry = new THREE.BufferGeometry().setAttribute('position',
new THREE.Float32BufferAttribute([-2, 0, 0, -0.5, 0, -0.5, 0, 0, -2], 3))
const geometry1 = new THREE.BufferGeometry().setAttribute('position',
new THREE.Float32BufferAttribute([5, 0, 0, 1, 1, 1, 0, 0, 5], 3))
geometry.morphAttributes.position = [
geometry1.attributes.position,
]
var material = new THREE.LineBasicMaterial({
color: 0x0000ff,
})
var mesh = new THREE.Line(geometry, material)
mesh.morphTargetInfluences[0] = 0
scene.add(mesh)
/**
*
*/
var width = window.innerWidth
var height = window.innerHeight
var k = width / height
var s = 10
var camera = new THREE.OrthographicCamera(
-s * k,
s * k,
s,
-s,
-1500,
1500
)
camera.position.set(10, 10, 10)
camera.lookAt(10, 0, 0)
/**
*
*/
var renderer = new THREE.WebGLRenderer()
// window.renderer = renderer;
renderer.setSize(width, height)
renderer.setClearColor(0xb9d3ff, 1)
document.body.appendChild(renderer.domElement)
// renderer.render(scene, camera);
new OrbitControls(camera, renderer.domElement)
var clock = new THREE.Clock()
const axes = new THREE.AxesHelper(10)
scene.add(axes)
let flag = false
let index = 0
function render() {
renderer.render(scene, camera)
requestAnimationFrame(render)
if (index <0) {
flag = true
} else if (index > 1) {
flag = false
}
if (flag) {
index += 0.01
} else {
index -= 0.01
}
mesh.morphTargetInfluences[0] = index
console.log(flag, index)
}
render()
}
main()
</script>
Now I want to use the LineGeometry of three.js because this line has a width property. How to use the morphtargets with LineGeometry?
If the LineGeometry can't do it,any other bufferGeometry to replace LineGeometry?
Three.js 136.
<script type="module">
import * as THREE from '../../build/three.module.js'
import { OrbitControls } from '../jsm/controls/OrbitControls.js'
import { Line2 } from '../jsm/lines/Line2.js'
import { LineMaterial } from '../jsm/lines/LineMaterial.js'
import { LineGeometry } from '../jsm/lines/LineGeometry.js'
function main() {
var scene = new THREE.Scene()
const geometry2 = new LineGeometry()
geometry2.setPositions([-3, 0, 0, -0.5, 0, -0.5, 0, 0, -2])
console.log(geometry2)
const geometry1 = new LineGeometry()
geometry1.setPositions([-3, 0, 0, -1, 0, -1, 0, 0, -3])
geometry2.morphAttributes.position = [geometry1.attributes.position]
let matLine = new LineMaterial({
color: '#ffdb2d',
linewidth: 5, // in world units with size attenuation, pixels otherwise
})
let line = new Line2(geometry2, matLine)
line.morphTargetInfluences[0] = 0
scene.add(line)
/**
*/
var width = window.innerWidth
var height = window.innerHeight
var k = width / height
var s = 5
var camera = new THREE.OrthographicCamera(
-s * k,
s * k,
s,
-s,
-1500,
1500
)
camera.position.set(5, 5, 5)
camera.lookAt(5, 0, 0)
var renderer = new THREE.WebGLRenderer()
// window.renderer = renderer;
renderer.setSize(width, height)
renderer.setClearColor(0xb9d3ff, 1)
document.body.appendChild(renderer.domElement)
// renderer.render(scene, camera);
new OrbitControls(camera, renderer.domElement)
var clock = new THREE.Clock()
const axes = new THREE.AxesHelper(10)
scene.add(axes)
let flag = false
let index = 0
function render() {
renderer.render(scene, camera)
requestAnimationFrame(render)
if (index < 0) {
flag = true
} else if (index > 1) {
flag = false
}
if (flag) {
index += 0.01
} else {
index -= 0.01
}
line.morphTargetInfluences[0] = index
matLine.resolution.set(window.innerWidth, window.innerHeight) // resolution of the viewport
}
render()
}
main()
</script>
Use TubeGeometry:
const curve = new THREE.CatmullRomCurve3([new THREE.Vector3(-2, 0, 0), new THREE.Vector3(-0.5, 0, -0.5), new THREE.Vector3(0, 0, -2)])
var tubeGeometry = new THREE.TubeGeometry(curve, 100, 0.05, 50, false);
const curve1 = new THREE.CatmullRomCurve3([new THREE.Vector3(0, 0, 5,), new THREE.Vector3( 1, 1, 1,), new THREE.Vector3(5, 0, 0,)])
var tubeGeometry1 = new THREE.TubeGeometry(curve1, 100, 0.05, 50, false);
tubeGeometry.morphAttributes.position = [
tubeGeometry1.attributes.position,
]
var tubeMaterial = new THREE.MeshBasicMaterial({
side:THREE.DoubleSide,
color:'#ffffff',
});
var tube = new THREE.Mesh(tubeGeometry, tubeMaterial);
tube.morphTargetInfluences[0] = 0
scene.add(tube)
In the render function:
tube.morphTargetInfluences[0] += 0.01

Three.js. Get localToWorld from BufferGeometry vertices

I get faces from geometry attributes
get the coordinates for each point of the face
draw lines for each edge of the face
And everything is fine if the object is not moved:
without moving
But if you move, then the lines remain in place:
after moving
If I try to get the world coordinates of points after moving, then I see the same picture:
after moving + localToWorld
If I update matrix world for cube, then I see this:
after moving + localToWorld + updateMatrixWorld
var scene = new THREE.Scene();
scene.background = new THREE.Color().setStyle('#e0e0e0');
var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 100000);
camera.position.set(0, 500, 3000);
var renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var controls = new THREE.OrbitControls(camera, renderer.domElement);
///////////////////////CREATE CUBE////////////////////////////////
const cube = new THREE.Mesh(
new THREE.BoxBufferGeometry(1000, 1000, 1000),
new THREE.MeshBasicMaterial({
color: 'red',
transparent: true,
opacity: 0.4
})
);
scene.add(cube);
//MOVE
cube.position.set(100, 100, 100);
//UPDATE
cube.updateMatrixWorld(true);
//GET EDGE LINES(THREE.Line3) FOR EDGES OF FACES
const lines = getLinesFromFaces(cube);
//CONVERT FROM LOCAL TO WORLD
lines.map(l => {
//l.applyMatrix4(cube.matrixWorld);
//l.start.applyMatrix4(cube.matrixWorld);
//l.end.applyMatrix4(cube.matrixWorld);
cube.localToWorld(l.start);
cube.localToWorld(l.end);
});
//DRAW
drawLines(lines);
function drawLines(lines) {
for (let i = 0; i < lines.length; i += 1) {
addLine(lines[i].start, lines[i].end);
}
}
function addLine(p1, p2) {
const material = new THREE.LineBasicMaterial({
color: 0x0000ff
});
const points = [p1, p2];
const geometry = new THREE.BufferGeometry().setFromPoints(points);
const line = new THREE.Line(geometry, material);
scene.add(line);
}
function getLinesFromFaces(object) {
const facesWithPos = getFacesWithPos(object.geometry);
const lines = [];
for (let i = 0; i < facesWithPos.length; i += 1) {
const f = facesWithPos[i];
const lineAB = new THREE.Line3(f.a, f.b);
let isExist = false;
isExist = lines.some(l => {
return (l.start.equals(lineAB.start) && l.end.equals(lineAB.end)) ||
(l.start.equals(lineAB.end) && l.end.equals(lineAB.start));
});
if (!isExist) lines.push(lineAB);
const lineBC = new THREE.Line3(f.b, f.c);
isExist = false;
isExist = lines.some(l => {
return (l.start.equals(lineBC.start) && l.end.equals(lineBC.end)) ||
(l.start.equals(lineBC.end) && l.end.equals(lineBC.start));
});
if (!isExist) lines.push(lineBC);
const lineCA = new THREE.Line3(f.c, f.a);
isExist = false;
isExist = lines.some(l => {
return (l.start.equals(lineCA.start) && l.end.equals(lineCA.end)) ||
(l.start.equals(lineCA.end) && l.end.equals(lineCA.start));
});
if (!isExist) lines.push(lineCA);
}
return lines;
}
function getFacesWithPos(geometry) {
const faces = getFaces(geometry);
const facesWithPos = [];
const position = geometry.getAttribute('position');
for (let i = 0; i < faces.length; i += 1) {
const f = faces[i];
facesWithPos.push({
a: new THREE.Vector3(position.array[f.a * 3], position.array[f.a * 3 + 1], position.array[f.a * 3 + 2]),
b: new THREE.Vector3(position.array[f.b * 3], position.array[f.b * 3 + 1], position.array[f.b * 3 + 2]),
c: new THREE.Vector3(position.array[f.c * 3], position.array[f.c * 3 + 1], position.array[f.c * 3 + 2])
});
}
return facesWithPos;
}
function getFaces(geometry) {
const faces = [];
const index = geometry.getIndex();
for (let i = 0; i < index.count; i += 3) {
faces.push({
a: index.getX(i),
b: index.getX(i + 1),
c: index.getX(i + 2)
});
}
return faces;
}
render();
function render() {
requestAnimationFrame(render);
renderer.render(scene, camera);
}
body {
overflow: hidden;
margin: 0;
}
<script src="https://cdn.jsdelivr.net/npm/three#0.117.0/build/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three#0.117.0/examples/js/controls/OrbitControls.js"></script>
How to get world coordinates? what am I doing wrong?
Some of the line points were common objects. If you apply localToWorld to them, then the method was applied to them several times and the result was not correct. Below is the solution
var scene = new THREE.Scene();
scene.background = new THREE.Color().setStyle('#e0e0e0');
var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 100000);
camera.position.set(0, 500, 3000);
var renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var controls = new THREE.OrbitControls(camera, renderer.domElement);
///////////////////////CREATE CUBE////////////////////////////////
const cube = new THREE.Mesh(
new THREE.BoxBufferGeometry(1000, 1000, 1000),
new THREE.MeshBasicMaterial({
color: 'red',
transparent: true,
opacity: 0.4
})
);
scene.add(cube);
//MOVE
cube.position.set(100, 100, 100);
//UPDATE
cube.updateMatrixWorld(true);
//GET EDGE LINES(THREE.Line3) FOR EDGES OF FACES
const lines = getLinesFromFaces(cube);
//CONVERT FROM LOCAL TO WORLD
lines.map(l => {
//l.applyMatrix4(cube.matrixWorld);
//l.start.applyMatrix4(cube.matrixWorld);
//l.end.applyMatrix4(cube.matrixWorld);
cube.localToWorld(l.start);
cube.localToWorld(l.end);
});
//DRAW
drawLines(lines);
function drawLines(lines) {
for (let i = 0; i < lines.length; i += 1) {
addLine(lines[i].start, lines[i].end);
}
}
function addLine(p1, p2) {
const material = new THREE.LineBasicMaterial({
color: 0x0000ff
});
const points = [p1, p2];
const geometry = new THREE.BufferGeometry().setFromPoints(points);
const line = new THREE.Line(geometry, material);
scene.add(line);
}
function getLinesFromFaces(object) {
const facesWithPos = getFacesWithPos(object.geometry);
const lines = [];
for (let i = 0; i < facesWithPos.length; i += 1) {
const f = facesWithPos[i];
const lineAB = new THREE.Line3(f.a, f.b);
let isExist = false;
isExist = lines.some(l => {
return (l.start.equals(lineAB.start) && l.end.equals(lineAB.end)) ||
(l.start.equals(lineAB.end) && l.end.equals(lineAB.start));
});
if (!isExist) lines.push(lineAB.clone());
const lineBC = new THREE.Line3(f.b, f.c);
isExist = false;
isExist = lines.some(l => {
return (l.start.equals(lineBC.start) && l.end.equals(lineBC.end)) ||
(l.start.equals(lineBC.end) && l.end.equals(lineBC.start));
});
if (!isExist) lines.push(lineBC.clone());
const lineCA = new THREE.Line3(f.c, f.a);
isExist = false;
isExist = lines.some(l => {
return (l.start.equals(lineCA.start) && l.end.equals(lineCA.end)) ||
(l.start.equals(lineCA.end) && l.end.equals(lineCA.start));
});
if (!isExist) lines.push(lineCA.clone());
}
return lines;
}
function getFacesWithPos(geometry) {
const faces = getFaces(geometry);
const facesWithPos = [];
const position = geometry.getAttribute('position');
for (let i = 0; i < faces.length; i += 1) {
const f = faces[i];
facesWithPos.push({
a: new THREE.Vector3(position.array[f.a * 3], position.array[f.a * 3 + 1], position.array[f.a * 3 + 2]),
b: new THREE.Vector3(position.array[f.b * 3], position.array[f.b * 3 + 1], position.array[f.b * 3 + 2]),
c: new THREE.Vector3(position.array[f.c * 3], position.array[f.c * 3 + 1], position.array[f.c * 3 + 2])
});
}
return facesWithPos;
}
function getFaces(geometry) {
const faces = [];
const index = geometry.getIndex();
for (let i = 0; i < index.count; i += 3) {
faces.push({
a: index.getX(i),
b: index.getX(i + 1),
c: index.getX(i + 2)
});
}
return faces;
}
render();
function render() {
requestAnimationFrame(render);
renderer.render(scene, camera);
}
body {
overflow: hidden;
margin: 0;
}
<script src="https://cdn.jsdelivr.net/npm/three#0.117.0/build/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three#0.117.0/examples/js/controls/OrbitControls.js"></script>

Draw a 2D line with width in three.js

I'm looking to draw a continuous line with a given thickness showing only the edges using three.js. I have achieved it. I'm trying to add thickness to the line but it is not getting reflected in the scene due to some angle in three.js. Can anyone help me out with the issue.
Here's the fiddle https://jsfiddle.net/16vhjm0y/1/
var renderer, scene, camera;
var line;
var count = 0;
var mouse = new THREE.Vector3();
var mesh3D;
var maxPoint = 6;
var height = window.innerHeight * .99;
var plane = new THREE.Plane(new THREE.Vector3(0, 0, 1), 0); // facing us for mouse intersection
var raycaster = new THREE.Raycaster();
var point3ds = [];
var usePerspectiveCamera = false; // toggles back and forth
var perspOrbit;
var perspCam;
var orthoOrbit;
var orthoCam;
var labelRenderer, labelAjay;
var testBoolean = false;
var mouseDownBoolean = false;
var distanceData, showDistanceData;
var ajay;
var arrAjay = [];
var arrAjayFinal = [];
var mouseUpBoolean = false;
init();
animate();
function init() {
// renderer
renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, height);
document.body.appendChild(renderer.domElement);
// scene
scene = new THREE.Scene();
scene.background = new THREE.Color(0xffffff);
// camera perspective
perspCam = new THREE.PerspectiveCamera(45, window.innerWidth / height, 1, 10000);
perspCam.position.set(0, 0, 200);
// camera ortho
var width = window.innerWidth;
//var height = window.innerHeight;
orthoCam = new THREE.OrthographicCamera(-width / 2, width / 2, height / 2, -height / 2, 0, 1200);
// assign cam
camera = perspCam;
someMaterial = new THREE.MeshBasicMaterial({ color: 0xA9A9A9, side: THREE.DoubleSide, transparent: true, opacity: 0.3 });
// grid
var grid = new THREE.GridHelper(1024, 56);
grid.rotateX(Math.PI / 2);
// scene.add(grid);
// geometry
var geometry = new THREE.BufferGeometry();
var MAX_POINTS = 500;
positions = new Float32Array(MAX_POINTS * 3);
geometry.addAttribute('position', new THREE.BufferAttribute(positions, 3));
// material
var material = new THREE.LineBasicMaterial({
color: 0xff0000,
linewidth: 10
});
// line
line = new THREE.Line(geometry, material);
// line.position.z = 20;
scene.add(line);
// var geometry = new THREE.BoxBufferGeometry( 10, 2, 20 );
// var edgesPavement = new THREE.EdgesGeometry( geomPavement );
// var lineGeometry = new THREE.LineSegmentsGeometry().setPositions( edgesPavement.attributes.position.array );
// var lineMaterial = new THREE.LineMaterial( { color: 0xff0000, linewidth: 10 } );
// lineMaterial.resolution.set( window.innerWidth, window.innerHeight ); // important, for now...
// var line = new THREE.LineSegments2( lineGeometry, lineMaterial );
// scene.add( line );
document.addEventListener("mousemove", onMouseMove, false);
document.addEventListener('mousedown', onMouseDown, false);
document.addEventListener('mouseup', onMouseUp, false);
createUI();
labelRenderer = new THREE.CSS2DRenderer();
ajay = document.createElement('div');
ajay.className = 'ajay';
ajay.style.color = "black";
ajayInsert = document.createElement('div');
ajayInsert.className = 'ajay';
ajayInsert.style.color = "black";
// ajay.style.color = "black";
// console.log(ajay)
labelAjay = new THREE.CSS2DObject(ajay);
labelAjayFinal = new THREE.CSS2DObject(ajayInsert);
labelRenderer.setSize(window.innerWidth, window.innerHeight);
labelRenderer.domElement.style.position = 'absolute';
labelRenderer.domElement.style.top = '0';
labelRenderer.domElement.style.pointerEvents = 'none';
ajay.style.display = "none";
ajayInsert.style.display = "none";
}
// update line
function updateLine() {
positions[count * 3 - 3] = mouse.x;
positions[count * 3 - 2] = mouse.y;
positions[count * 3 - 1] = mouse.z;
line.geometry.attributes.position.needsUpdate = true;
}
// mouse move handler
function onMouseMove(event) {
var rect = renderer.domElement.getBoundingClientRect();
mouse.x = (event.clientX - rect.left) / (rect.right - rect.left) * 2 - 1;
mouse.y = - ((event.clientY - rect.top) / (rect.bottom - rect.top)) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
mouse = raycaster.ray.intersectPlane(plane, mouse);
if (count !== 0 && count < maxPoint) {
updateLine();
}
testBoolean = true;
if (testBoolean == true) {
// scene.remove(labelAjay);
var geometry = line.geometry;
geometry.computeBoundingBox();
center = geometry.boundingBox.getCenter();
// line.localToWorld(center);
// console.log(center);
if (mouseDownBoolean == true) {
labelAjay.position.set(mouse.x, mouse.y, mouse.z);
// console.log(line.position)
scene.add(labelAjay);
document.body.appendChild(labelRenderer.domElement);
// console.log(positions);
distanceData = point3ds[0].distanceTo(new THREE.Vector3(mouse.x, mouse.y, mouse.z));
showDistanceData = Math.round(distanceData * 1000);
// console.log(point3ds[0]);
// console.log(point3ds[1]);
// console.log(distanceData);
// console.log(showDistanceData)
ajay.textContent = showDistanceData + ' mm';
// console.log(labelRenderer)
}
// console.log(labelRenderer.domElement)
// document.getElementsByClassName("ajay").remove();
// document.getElementsByClassName("ajay").outerHTML = "";
}
}
// add point
function addPoint(event) {
if (count < maxPoint) {
console.log("point nr " + count + ": " + mouse.x + " " + mouse.y + " " + mouse.z);
positions[count * 3 + 0] = mouse.x;
positions[count * 3 + 1] = mouse.y;
positions[count * 3 + 2] = mouse.z
count++;
line.geometry.setDrawRange(0, count);
updateLine();
point3ds.push(new THREE.Vector3(mouse.x, mouse.y, mouse.z));
} else {
console.log('max points reached: ' + maxPoint);
}
}
function getPointInBetweenByLen(pointA, pointB, length) {
var dir = pointB.clone().sub(pointA).normalize().multiplyScalar(length);
return pointA.clone().add(dir);
}
// mouse down handler
function onMouseDown(evt) {
mouseDownBoolean = true;
// force add an extra point on first click so buffer line can display
// buffer geometry requires two points to display, so first click should add two points
if (count === 0) {
addPoint();
}
if (count < maxPoint) {
addPoint();
}
}
function onMouseUp(event){
mouseUpBoolean = true;
if(mouseUpBoolean == true){
// showDistanceData = Math.round(distanceData * 1000);
arrAjay.push(showDistanceData);
console.log(arrAjay);
arrAjayFinal = arrAjay.splice(-1)[0];
var geometry = line.geometry;
geometry.computeBoundingBox();
center = geometry.boundingBox.getCenter();
if (mouseDownBoolean == true) {
labelAjayFinal.position.set(center.x, center.y, center.z);
scene.add(labelAjayFinal);
document.body.appendChild(labelRenderer.domElement);
// distanceData = point3ds[0].distanceTo(new THREE.Vector3(mouse.x, mouse.y, mouse.z));
// showDistanceData = Math.round(distanceData * 1000);
console.log('arrAjayFinal', arrAjayFinal);
ajayInsert.textContent = arrAjayFinal;
}
}
}
// render
function render() {
renderer.render(scene, camera);
labelRenderer.render(scene, camera);
}
// animate
function animate() {
requestAnimationFrame(animate);
render();
}
// loop through all the segments and create their 3D
function create3D() {
if (!mesh3D && point3ds && point3ds.length) {
console.log('creating 3D');
mesh3D = new THREE.Mesh(); // metpy mesh but is the root mesh for all 3D
scene.add(mesh3D);
// prepare create segments from point3ds - every two points create a segement
var index = 1;
var segmentHeight = 56;
point3ds.forEach(point3d => {
if (index < point3ds.length) {
var seg = new Segment(point3d, point3ds[index], someMaterial, segmentHeight);
mesh3D.add(seg.mesh3D);
index++;
}
});
}
}
function createUI() {
// create3D
var btn = document.createElement('button');
document.body.appendChild(btn);
btn.innerHTML = 'Create3D';
btn.addEventListener('mousedown', () => {
create3D();
// add orbiting controls to both cameras
var controls;
if (!perspOrbit) {
perspOrbit = new THREE.OrbitControls(perspCam, renderer.domElement);
perspOrbit.screenSpacePanning = true;
// raotation is enabled once create3D is pressed
setToFullOrbit(perspOrbit);
perspOrbit.enabled = true; // set to true by default
}
// add orbit to orthocam
if (!orthoOrbit) {
orthoOrbit = new THREE.OrbitControls(orthoCam, renderer.domElement);
orthoOrbit.screenSpacePanning = true;
orthoOrbit.enabled = false; // set to false by default
//orthoOrbit.enableDamping = true;
//orthoOrbit.dampingFactor = .15;
}
});
}
function switchCam() {
usePerspectiveCamera = !usePerspectiveCamera;
if (usePerspectiveCamera) {
if (perspCam) {
camera = perspCam;
perspOrbit.enabled = true;
orthoOrbit.enabled = false;
} else {
throw new Error('Switch to perspective cam failed, perspective cam is null');
}
} else {
if (orthoCam) {
camera = orthoCam;
orthoOrbit.enabled = true;
perspOrbit.enabled = false;
} else {
throw new Error('Switch to ortho cam failed, orthoCam is null');
}
}
}
function rotateCam90() {
if (camera instanceof THREE.OrthographicCamera) {
orthoOrbit.update();
camera.applyMatrix(new THREE.Matrix4().makeRotationZ(Math.PI / 2));
}
}
function reset() {
scene.remove(mesh3D);
mesh3D = null;
for (var i = 0; i < 3 * 8; i++) {
positions[i] = 0;
}
count = 0;
line.geometry.setDrawRange(0, count);
updateLine();
point3ds = [];
}
function setToFullOrbit(orbitControl) {
// how far you can orbit vertically
orbitControl.minPolarAngle = 0;
orbitControl.maxPolarAngle = Math.PI;
// How far you can dolly in and out ( PerspectiveCamera only )
orbitControl.minDistance = 0;
orbitControl.maxDistance = Infinity;
orbitControl.enableZoom = true; // Set to false to disable zooming
orbitControl.zoomSpeed = 1.0;
orbitControl.enableRotate = true;
// allow keyboard arrows
orbitControl.enableKeys = true;
// Set to false to disable panning (ie vertical and horizontal translations)
orbitControl.enablePan = true;
}
// each segment knows how to create its 3D
class Segment {
constructor(start, end, material, height) {
this.start = start;
this.end = end;
this.height = height; // height of the segment's 3D
this.material = material;
this.mesh3D = null;
this.create3D();
}
create3D() {
if (this.start && this.end) {
//create the shape geometry
var distStartToEnd = this.start.distanceTo(this.end);
var vec2s = [
new THREE.Vector2(),
new THREE.Vector2(0, this.height),
new THREE.Vector2(distStartToEnd, this.height),
new THREE.Vector2(distStartToEnd, 0)
];
console.log('vec2s', vec2s);
var shape = new THREE.Shape(vec2s);
var geo = new THREE.BoxGeometry(5, 5, 5);
// console.log('shape', shape);
var geo = new THREE.ShapeGeometry(shape);
geo.applyMatrix(new THREE.Matrix4().makeRotationX(THREE.Math.degToRad(90)));
this.mesh3D = new THREE.Mesh(geo, this.material);
this.alignRotation();
this.alignPosition();
// the mesh3D should be added to the scene outside of this class
}
}
alignRotation() {
var p1 = this.start.clone();
var p2 = this.end.clone();
var direction = new THREE.Vector3();
direction.subVectors(p2, p1);
direction.normalize();
this.mesh3D.quaternion.setFromUnitVectors(new THREE.Vector3(1, 0, 0), direction);
}
alignPosition() {
if (this.mesh3D) {
this.mesh3D.position.copy(this.start);
} else {
throw new Error('mesh3D null');
}
}
}
The linewidth parameter relies on native WebGL support for drawing line thickness, but its performance is very spotty across browsers & operating systems. I think Windows doesn't support it, but MacOS does, so it shouldn't be relied upon. See this discussion on the Three.js Github for several bug reports.
As a workaround, they've created LineGeometry, which sort of re-builds a line with geometry to allow for thickness. See this example for how to use it. It even allows for dashed lines. After importing the module, you can implement it with:
const geometry = new LineGeometry();
geometry.setPositions( positions );
geometry.setColors( colors );
matLine = new LineMaterial( {
color: 0xffffff,
linewidth: 5, // in pixels
vertexColors: true,
dashed: false
} );
line = new Line2( geometry, matLine );
line.computeLineDistances();
scene.add( line );

How to create rotation on perspective camera on Three.js?

I have build a little 3D-TileMap in Three.js.
Currently, i have problems with my PerspectiveCamera. I wan't to add some camera handling like Map rotating or zooming. The zooming always works, here i'm only use the field of view and mousewheel.
But how i can implement a rotating of my map? When i'm using the coordinates of camera to modify x, y or z, i've misunderstand the calculation.
Here is my current work:
function Input(renderer, camera) {
var press = false
var sensitivity = 0.2
renderer.domElement.addEventListener('mousemove', event => {
if(!press){ return }
camera.position.x += event.movementX * sensitivity
camera.position.y += event.movementY * sensitivity
camera.position.z += event.movementY * sensitivity / 10
})
renderer.domElement.addEventListener('mousedown', () => { press = true })
renderer.domElement.addEventListener('mouseup', () => { press = false })
renderer.domElement.addEventListener('mouseleave', () => { press = false })
renderer.domElement.addEventListener('mousewheel', event => {
// Add MIN/MAX LIMITS
const ratio = camera.position.y / camera.position.z
camera.position.y -= (event.wheelDelta * sensitivity * ratio)
camera.position.z -= (event.wheelDelta * sensitivity)
camera.updateProjectionMatrix()
})
}
var controls;
const Type = 'WebGL'; // WebGL or Canvas
var _width, _height, CUBE_SIZE, GRID, TOTAL_CUBES, WALL_SIZE, HALF_WALL_SIZE,
MAIN_COLOR, SECONDARY_COLOR, cubes, renderer, camera, scene, group
var clock = new THREE.Clock();
clock.start();
var FOV = 45;
_width = window.innerWidth
_height = window.innerHeight
CUBE_SIZE = 80 /* width, height */
GRID = 12 /* cols, rows */
TOTAL_CUBES = (GRID * GRID)
WALL_SIZE = (GRID * CUBE_SIZE)
HALF_WALL_SIZE = (WALL_SIZE / 2)
MAIN_COLOR = 0xFFFFFF
SECONDARY_COLOR = 0x222222
cubes = []
var directions = [];
var normalized = [];
switch(Type) {
case 'WebGL':
renderer = new THREE.WebGLRenderer({antialias: true})
break;
case 'Canvas':
renderer = new THREE.CanvasRenderer({antialias: true})
break;
}
camera = new THREE.PerspectiveCamera(FOV, (_width / _height), 0.1, 10000)
scene = new THREE.Scene()
group = new THREE.Object3D()
/* -- -- */
setupCamera(0, 0, 800)
setupBox(group)
setupFloor(group)
setupCubes(group)
setupLights(group)
group.position.y = 10
group.rotation.set(-60 * (Math.PI/180), 0, -45 * (Math.PI/180))
scene.add(group)
setupRenderer(document.body)
window.addEventListener('resize', resizeHandler, false)
new Input(renderer, camera);
/* -- -- */
function resizeHandler() {
_width = window.innerWidth
_height = window.innerHeight
renderer.setSize(_width, _height)
camera.aspect = _width / _height
camera.updateProjectionMatrix()
}
/* -- CAMERA -- */
function setupCamera(x, y, z) {
camera.position.set(x, y, z)
scene.add(camera)
}
/* -- BOX -- */
function setupBox(parent) {
var i, boxesArray, geometry, material
boxesArray = []
geometry = new THREE.BoxGeometry(WALL_SIZE, WALL_SIZE, 0.05)
geometry.faces[8].color.setHex(SECONDARY_COLOR)
geometry.faces[9].color.setHex(SECONDARY_COLOR)
geometry.colorsNeedUpdate = true
material = new THREE.MeshBasicMaterial({
color : MAIN_COLOR,
vertexColors : THREE.FaceColors,
overdraw: 0.5
})
for (i = 0; i < 5; i++) {
boxesArray.push(new THREE.Mesh(geometry, material))
}
// back
boxesArray[0].position.set(0, HALF_WALL_SIZE, -HALF_WALL_SIZE)
boxesArray[0].rotation.x = 90 * (Math.PI/180)
// right
boxesArray[1].position.set(HALF_WALL_SIZE, 0, -HALF_WALL_SIZE)
boxesArray[1].rotation.y = -90 * (Math.PI/180)
// front
boxesArray[2].position.set(0, -HALF_WALL_SIZE, -HALF_WALL_SIZE)
boxesArray[2].rotation.x = -90 * (Math.PI/180)
// left
boxesArray[3].position.set(-HALF_WALL_SIZE, 0, -HALF_WALL_SIZE)
boxesArray[3].rotation.y = 90 * (Math.PI/180)
// bottom
boxesArray[4].position.set(0, 0, -WALL_SIZE)
boxesArray.forEach(function(box) {
box.renderOrder = 1;
parent.add(box)
});
}
/* -- FLOOR -- */
function setupFloor(parent) {
var i, tilesArray, geometry, material
tilesArray = []
geometry = new THREE.PlaneBufferGeometry(WALL_SIZE, WALL_SIZE)
material = new THREE.MeshLambertMaterial({
color : MAIN_COLOR,
overdraw: 1
})
for (i = 0; i < 8; i++) {
tilesArray.push(new THREE.Mesh(geometry, material))
}
tilesArray[0].position.set(-WALL_SIZE, WALL_SIZE, 0)
tilesArray[1].position.set(0, WALL_SIZE, 0)
tilesArray[2].position.set(WALL_SIZE, WALL_SIZE, 0)
tilesArray[3].position.set(-WALL_SIZE, 0, 0)
tilesArray[4].position.set(WALL_SIZE, 0, 0)
tilesArray[5].position.set(-WALL_SIZE, -WALL_SIZE, 0)
tilesArray[6].position.set(0, -WALL_SIZE, 0)
tilesArray[7].position.set(WALL_SIZE, -WALL_SIZE, 0)
tilesArray.forEach(function(tile) {
tile.receiveShadow = true
tile.renderOrder = 4;
parent.add(tile)
})
}
/* -- CUBES --*/
function setupCubes(parent) {
var i, geometry, material, x, y, row, col
geometry = new THREE.BoxGeometry(CUBE_SIZE, CUBE_SIZE, 0.05)
material = new THREE.MeshPhongMaterial( {
map: new THREE.TextureLoader().load('http://ak.game-socket.de/assets/grass.png'),
normalMap: new THREE.TextureLoader().load('http://ak.game-socket.de/assets/paper_low_nmap.png'),
overdraw: 1,
depthTest: true,
depthWrite: true
} );
x = 0
y = 0
row = 0
col = 0
for (i = 0; i < TOTAL_CUBES; i++) {
cubes.push(new THREE.Mesh(geometry, material))
if ((i % GRID) === 0) {
col = 1
row++
} else col++
x = -(((GRID * CUBE_SIZE) / 2) - ((CUBE_SIZE) * col) + (CUBE_SIZE/2))
y = -(((GRID * CUBE_SIZE) / 2) - ((CUBE_SIZE) * row) + (CUBE_SIZE/2))
cubes[i].position.set(x, y, 0)
}
cubes.forEach(function(cube, index) {
if(index % 2 == 0) {
directions[index] = -1;
normalized[index] = false;
} else {
directions[index] = 1;
normalized[index] = true;
}
cube.castShadow = true
cube.receiveShadow = true
cube.rotation.x = 0;
cube.renderOrder = 3;
cube.doubleSide = true;
parent.add(cube)
})
}
/* -- LIGHTS -- */
function setupLights(parent) {
var light, soft_light
light = new THREE.DirectionalLight(MAIN_COLOR, 1.25)
soft_light = new THREE.DirectionalLight(MAIN_COLOR, 1.5)
light.position.set(-WALL_SIZE, -WALL_SIZE, CUBE_SIZE * GRID)
light.castShadow = true
soft_light.position.set(WALL_SIZE, WALL_SIZE, CUBE_SIZE * GRID)
parent.add(light).add(soft_light)
}
/* -- RENDERER -- */
function setupRenderer(parent) {
renderer.setSize(_width, _height)
renderer.setClearColor(MAIN_COLOR, 1.0);
parent.appendChild(renderer.domElement)
}
var speed = 0.003;
var reach = 40;
function render() {
var delta = clock.getDelta();
requestAnimationFrame(render);
cubes.forEach(function(cube, index) {
cube.castShadow = true
cube.receiveShadow = true
if(directions[index] >= 1) {
++directions[index];
if(directions[index] >= reach) {
directions[index] = -1
}
cube.rotation.x += speed;
} else if(directions[index] <= -1) {
--directions[index];
if(directions[index] <= -reach) {
directions[index] = 1
}
cube.rotation.x -= speed;
}
});
renderer.render(scene, camera)
}
render();
html, body, canvas {
padding: 0;
margin: 0;
width: 100%;
height: 100%;
display: block;
}
<script src="https://rawcdn.githack.com/mrdoob/three.js/dev/build/three.min.js"></script>
<script src="https://rawcdn.githack.com/mrdoob/three.js/dev/examples/js/renderers/CanvasRenderer.js"></script>
<script src="https://rawcdn.githack.com/mrdoob/three.js/dev/examples/js/renderers/Projector.js"></script>
<script src="https://rawcdn.githack.com/mrdoob/three.js/dev/examples/js/controls/TrackballControls.js"></script>
<script src="https://rawcdn.githack.com/mrdoob/three.js/dev/examples/js/shaders/ParallaxShader.js"></script>
By option, i don't want, that the rotating/zooming reach the end of the map - The user is not able to look under the map, as example.

Threejs/Physijs Game MMO Character controlls

I'm working on a third person character control for a game i'm developing. I'm happy with the results so far. The character controls have a lot of neat features like: if an object is in front of the camera it will move forward so you can still see the character, however the camera stutters horribly when I rotate it to the side and then turn my player away from it. I uploaded a test on JSFiddle: http://jsfiddle.net/nA8SV/ I have only tested this in chrome, and for some reason the results part doesn't get the keypresses until you click on the white space bordering the canvas on that frame.
[also i started chrome with "--disable-web-security" to ignore the cross origin]
But once you click the page the key presses work. The controls are a modified version of the orbital controls. So you can left click and rotate the view. Additionally you can use the wasd keys to move around and the camera view should return behind the player when you are moving/rotating.
I apologize for the buggyness this was very difficult to get working on JSFiddle.
(But the rotation bug is happening so it at least reproduces that.)
Basically I'm trying to get my camera rotation back behind my character, so i have some code that fixes the rotation on line 250, but the camera stutters as the character moves.
Here are my theories I think the camera overall jerkyness has something to do with the physics simulation bouncing the player around slightly, but I'm not sure what to do to solve this, any help would be appreciated.
here is the code for completeness but I would recommend the JSFiddle link, I'ts much easier to see it work.
THREE.PlayerControls = function (anchor, scene, player, camera, domElement) {
this.walking = false;
this.occ = false;
this.scene = scene;
this.occLastZoom = 0;
this.jumpRelease = true;
this.jumping = false;
this.falling = false;
this.moving = false;
this.turning = false;
this.anchor = anchor;
this.player = player;
this.camera = camera;
this.camera.position.set(0, 8.25, -20);
this.domElement = (domElement !== undefined) ? domElement : document;
this.anchor.add(this.camera);
// API
this.enabled = true;
this.center = new THREE.Vector3(0, 4, 0);
this.userZoom = true;
this.userZoomSpeed = 2.0;
this.userRotate = true;
this.userRotateSpeed = 1.0;
this.minPolarAngle = 0; // radians
this.maxPolarAngle = Math.PI; // radians
this.minDistance = 2;
this.maxDistance = 30;
this.keys = {
LEFT: 65,
STRAFFLEFT: 81,
UP: 87,
RIGHT: 68,
STRAFFRIGHT: 69,
DOWN: 83,
JUMP: 32,
SLASH: 191
};
// internals
var scope = this;
var EPS = 0.000001;
var PIXELS_PER_ROUND = 1800;
var rotateStart = new THREE.Vector2();
var rotateEnd = new THREE.Vector2();
var rotateDelta = new THREE.Vector2();
var zoomStart = new THREE.Vector2();
var zoomEnd = new THREE.Vector2();
var zoomDelta = new THREE.Vector2();
var phiDelta = 0;
var thetaDelta = 0;
var scale = 1;
var lastPosition = new THREE.Vector3();
var STATE = {
NONE: -1,
ROTATE: 0,
ZOOM: 1
};
var state = STATE.NONE;
var key_state = [];
// events
var changeEvent = {
type: 'change'
};
this.rotateLeft = function (angle) {
thetaDelta -= angle;
};
this.rotateRight = function (angle) {
thetaDelta += angle;
};
this.rotateUp = function (angle) {
phiDelta -= angle;
};
this.rotateDown = function (angle) {
phiDelta += angle;
};
this.zoomIn = function (zoomScale) {
if (zoomScale === undefined) {
zoomScale = getZoomScale();
}
scale /= zoomScale;
};
this.zoomOut = function (zoomScale) {
if (zoomScale === undefined) {
zoomScale = getZoomScale();
}
scale *= zoomScale;
};
this.update = function (delta) {
// detect falling
if (this.scene.children.length > 0) {
var originPoint = this.anchor.position.clone();
var ray = new THREE.Raycaster(originPoint, new THREE.Vector3(0, -1, 0));
var collisionResults = ray.intersectObjects(this.scene.children.filter(function (child) {
return child.occ;
}));
if (collisionResults.length > 0) {
if (collisionResults[0].distance < 1.25 && this.falling) {
this.falling = false;
this.jumping = false;
} else if (collisionResults[0].distance > 2 + (this.jumping ? 1 : 0) && !this.falling) {
this.falling = true;
}
}
}
// handle movement
if (!this.falling) {
if (key_state.indexOf(this.keys.JUMP) > -1 && this.jumpRelease && !this.jumping) {
// jump
var lv = this.anchor.getLinearVelocity();
this.anchor.setLinearVelocity(new THREE.Vector3(lv.x, 15, lv.z));
this.jumpRelease = false;
this.jumping = true;
//jump
} else if (!this.jumping) {
// move
if (key_state.indexOf(this.keys.UP) > -1) {
var rotation_matrix = new THREE.Matrix4().extractRotation(this.anchor.matrix);
var speed = this.walking ? 2.5 : 10;
var force_vector;
// straffing?
if (key_state.indexOf(this.keys.STRAFFLEFT) > -1 && key_state.indexOf(this.keys.STRAFFRIGHT) < 0) {
force_vector = new THREE.Vector3((2 * speed / 3), 0, (2 * speed / 3)).applyMatrix4(rotation_matrix);
this.player.rotation.set(0, Math.PI / 4, 0);
} else if (key_state.indexOf(this.keys.STRAFFRIGHT) > -1) {
force_vector = new THREE.Vector3((-2 * speed / 3), 0, (2 * speed / 3)).applyMatrix4(rotation_matrix);
this.player.rotation.set(0, -Math.PI / 4, 0);
} else {
force_vector = new THREE.Vector3(0, 0, speed).applyMatrix4(rotation_matrix);
this.player.rotation.set(0, 0, 0);
}
this.anchor.setLinearVelocity(force_vector);
this.moving = true;
// forward
} else if (key_state.indexOf(this.keys.DOWN) > -1) {
var rotation_matrix = new THREE.Matrix4().extractRotation(this.anchor.matrix);
var speed = this.walking ? -2.5 : -5;
var force_vector;
// straffing?
if (key_state.indexOf(this.keys.STRAFFLEFT) > -1 && key_state.indexOf(this.keys.STRAFFRIGHT) < 0) {
force_vector = new THREE.Vector3((-2 * speed / 3), 0, (2 * speed / 3)).applyMatrix4(rotation_matrix);
this.player.rotation.set(0, -Math.PI / 4, 0);
} else if (key_state.indexOf(this.keys.STRAFFRIGHT) > -1) {
force_vector = new THREE.Vector3((2 * speed / 3), 0, (2 * speed / 3)).applyMatrix4(rotation_matrix);
this.player.rotation.set(0, Math.PI / 4, 0);
} else {
force_vector = new THREE.Vector3(0, 0, speed).applyMatrix4(rotation_matrix);
this.player.rotation.set(0, 0, 0);
}
this.anchor.setLinearVelocity(force_vector);
this.moving = true;
//back
} else if (key_state.indexOf(this.keys.STRAFFLEFT) > -1) {
var rotation_matrix = new THREE.Matrix4().extractRotation(this.anchor.matrix);
var speed = this.walking ? 2.5 : 10;
var force_vector = new THREE.Vector3(speed, 0, 0).applyMatrix4(rotation_matrix);
this.player.rotation.set(0, Math.PI / 2, 0);
this.anchor.setLinearVelocity(force_vector);
this.moving = true;
//straff
} else if (key_state.indexOf(this.keys.STRAFFRIGHT) > -1) {
var rotation_matrix = new THREE.Matrix4().extractRotation(this.anchor.matrix);
var speed = this.walking ? 2.5 : 10;
var force_vector = new THREE.Vector3(-speed, 0, 0).applyMatrix4(rotation_matrix);
this.player.rotation.set(0, -Math.PI / 2, 0);
this.anchor.setLinearVelocity(force_vector);
this.moving = true;
//straff
} else if (this.moving) {
this.player.rotation.set(0, 0, 0);
this.anchor.setLinearVelocity(new THREE.Vector3(0, 0, 0));
this.moving = false;
}
//turn
if (key_state.indexOf(this.keys.LEFT) > -1 && key_state.indexOf(this.keys.RIGHT) < 0) {
this.anchor.setAngularVelocity(new THREE.Vector3(0, 1.5, 0));
this.turning = true;
//turning
} else if (key_state.indexOf(this.keys.RIGHT) > -1) {
this.anchor.setAngularVelocity(new THREE.Vector3(0, -1.5, 0));
this.turning = true;
//turning
} else if (this.turning) {
this.anchor.setAngularVelocity(new THREE.Vector3(0, 0, 0));
this.turning = false;
}
//idle
}
if (key_state.indexOf(this.keys.JUMP) == -1) {
this.jumpRelease = true;
}
} else {
//falling
}
var position = this.camera.position;
var offset = position.clone().sub(this.center);
// angle from z-axis around y-axis
var theta = Math.atan2(offset.x, offset.z);
// angle from y-axis
var phi = Math.atan2(Math.sqrt(offset.x * offset.x + offset.z * offset.z), offset.y);
theta += thetaDelta;
phi += phiDelta;
if ((this.moving || this.turning) && state != STATE.ROTATE) {
// fix camera rotation
if (theta < 0) theta -= Math.max(delta, (-1 * Math.PI) + theta);
else theta += Math.min(delta, Math.PI - theta);
// fix pitch (should be an option or it could get anoying)
//phi = 9*Math.PI/24;
}
// restrict phi to be between desired limits
phi = Math.max(this.minPolarAngle, Math.min(this.maxPolarAngle, phi));
// restrict phi to be betwee EPS and PI-EPS
phi = Math.max(EPS, Math.min(Math.PI - EPS, phi));
var radius;
if (this.occ) {
this.occLastZoom = Math.max(this.minDistance, Math.min(this.maxDistance, this.occLastZoom * scale));
radius = this.occLastZoom;
} else {
radius = offset.length() * scale;
}
// restrict radius to be between desired limits
radius = Math.max(this.minDistance, Math.min(this.maxDistance, radius));
// check for objects infront of camera
var projector = new THREE.Projector();
var vector = new THREE.Vector3(0, 0, 1);
projector.unprojectVector(vector, camera);
var point = new THREE.Vector3(this.anchor.position.x + this.center.x, this.anchor.position.y + this.center.y, this.anchor.position.z + this.center.z);
var vec = camera.position.clone().sub(vector).normalize()
var checkray = new THREE.Raycaster(point, vec, this.minDistance, this.maxDistance);
var checkcollisionResults = checkray.intersectObjects(this.scene.children.filter(function (child) {
return child.occ;
}));
if (checkcollisionResults.length > 0) {
var min = radius;
for (var i = 0; i < checkcollisionResults.length; i++) {
if (min > checkcollisionResults[i].distance) min = checkcollisionResults[i].distance;
}
if (min < radius) {
if (!this.occ) {
this.occ = true;
this.occLastZoom = radius;
}
radius = min;
} else {
this.occ = false;
}
}
offset.x = radius * Math.sin(phi) * Math.sin(theta);
offset.y = radius * Math.cos(phi);
offset.z = radius * Math.sin(phi) * Math.cos(theta);
if (radius < 5) {
this.player.material.opacity = Math.max(0, radius / 5.0);
this.center.y = 4 + ((5 - radius) / 2.5);
} else {
if (this.player.material.opacity != 1.0) {
this.player.material.opacity = 1.0;
this.center.y = 4;
}
}
position.copy(this.center).add(offset);
this.camera.lookAt(this.center);
thetaDelta = 0;
phiDelta = 0;
scale = 1;
if (lastPosition.distanceTo(this.camera.position) > 0) {
this.dispatchEvent(changeEvent);
lastPosition.copy(this.camera.position);
}
};
function roundTothree(num) {
return +(Math.round(num + "e+3") + "e-3");
}
function getZoomScale() {
return Math.pow(0.95, scope.userZoomSpeed);
}
function onMouseDown(event) {
if (scope.enabled === false) return;
if (scope.userRotate === false) return;
event.preventDefault();
if (state === STATE.NONE) {
if (event.button === 0) state = STATE.ROTATE;
}
if (state === STATE.ROTATE) {
rotateStart.set(event.clientX, event.clientY);
}
document.addEventListener('mousemove', onMouseMove, false);
document.addEventListener('mouseup', onMouseUp, false);
}
function onMouseMove(event) {
if (scope.enabled === false) return;
event.preventDefault();
if (state === STATE.ROTATE) {
rotateEnd.set(event.clientX, event.clientY);
rotateDelta.subVectors(rotateEnd, rotateStart);
scope.rotateLeft(2 * Math.PI * rotateDelta.x / PIXELS_PER_ROUND * scope.userRotateSpeed);
scope.rotateUp(2 * Math.PI * rotateDelta.y / PIXELS_PER_ROUND * scope.userRotateSpeed);
rotateStart.copy(rotateEnd);
} else if (state === STATE.ZOOM) {
zoomEnd.set(event.clientX, event.clientY);
zoomDelta.subVectors(zoomEnd, zoomStart);
if (zoomDelta.y > 0) {
scope.zoomIn();
} else {
scope.zoomOut();
}
zoomStart.copy(zoomEnd);
}
}
function onMouseUp(event) {
if (scope.enabled === false) return;
if (scope.userRotate === false) return;
document.removeEventListener('mousemove', onMouseMove, false);
document.removeEventListener('mouseup', onMouseUp, false);
state = STATE.NONE;
}
function onMouseWheel(event) {
if (scope.enabled === false) return;
if (scope.userZoom === false) return;
var delta = 0;
if (event.wheelDelta) { // WebKit / Opera / Explorer 9
delta = event.wheelDelta;
} else if (event.detail) { // Firefox
delta = -event.detail;
}
if (delta > 0) {
scope.zoomOut();
} else {
scope.zoomIn();
}
}
function onKeyDown(event) {
console.log('onKeyDown')
if (scope.enabled === false) return;
switch (event.keyCode) {
case scope.keys.UP:
var index = key_state.indexOf(scope.keys.UP);
if (index == -1) key_state.push(scope.keys.UP);
break;
case scope.keys.DOWN:
var index = key_state.indexOf(scope.keys.DOWN);
if (index == -1) key_state.push(scope.keys.DOWN);
break;
case scope.keys.LEFT:
var index = key_state.indexOf(scope.keys.LEFT);
if (index == -1) key_state.push(scope.keys.LEFT);
break;
case scope.keys.STRAFFLEFT:
var index = key_state.indexOf(scope.keys.STRAFFLEFT);
if (index == -1) key_state.push(scope.keys.STRAFFLEFT);
break;
case scope.keys.RIGHT:
var index = key_state.indexOf(scope.keys.RIGHT);
if (index == -1) key_state.push(scope.keys.RIGHT);
break;
case scope.keys.STRAFFRIGHT:
var index = key_state.indexOf(scope.keys.STRAFFRIGHT);
if (index == -1) key_state.push(scope.keys.STRAFFRIGHT);
break;
case scope.keys.JUMP:
var index = key_state.indexOf(scope.keys.JUMP);
if (index == -1) key_state.push(scope.keys.JUMP);
break;
}
}
function onKeyUp(event) {
switch (event.keyCode) {
case scope.keys.UP:
var index = key_state.indexOf(scope.keys.UP);
if (index > -1) key_state.splice(index, 1);
break;
case scope.keys.DOWN:
var index = key_state.indexOf(scope.keys.DOWN);
if (index > -1) key_state.splice(index, 1);
break;
case scope.keys.LEFT:
var index = key_state.indexOf(scope.keys.LEFT);
if (index > -1) key_state.splice(index, 1);
break;
case scope.keys.STRAFFLEFT:
var index = key_state.indexOf(scope.keys.STRAFFLEFT);
if (index > -1) key_state.splice(index, 1);
break;
case scope.keys.RIGHT:
var index = key_state.indexOf(scope.keys.RIGHT);
if (index > -1) key_state.splice(index, 1);
break;
case scope.keys.STRAFFRIGHT:
var index = key_state.indexOf(scope.keys.STRAFFRIGHT);
if (index > -1) key_state.splice(index, 1);
break;
case scope.keys.JUMP:
var index = key_state.indexOf(scope.keys.JUMP);
if (index > -1) key_state.splice(index, 1);
break;
case scope.keys.SLASH:
scope.walking = !scope.walking;
break;
}
}
this.domElement.addEventListener('contextmenu', function (event) {
event.preventDefault();
}, false);
this.domElement.addEventListener('mousedown', onMouseDown, false);
this.domElement.addEventListener('mousewheel', onMouseWheel, false);
this.domElement.addEventListener('DOMMouseScroll', onMouseWheel, false); // firefox
window.addEventListener('keydown', onKeyDown, false);
window.addEventListener('keyup', onKeyUp, false);
};
THREE.PlayerControls.prototype = Object.create(THREE.EventDispatcher.prototype);
// end player controlls
Physijs.scripts.worker = 'https://rawgithub.com/chandlerprall/Physijs/master/physijs_worker.js';
Physijs.scripts.ammo = 'http://chandlerprall.github.io/Physijs/examples/js/ammo.js';
// standard global variables
var container, scene, camera, renderer, controls;
//var keyboard = new THREEx.KeyboardState();
var clock = new THREE.Clock();
// MAIN //
window.onload = function() {
console.log('loaded')
// SCENE //
scene = new Physijs.Scene();
scene.setGravity(new THREE.Vector3(0, -32, 0));
scene.addEventListener(
'update',
function () {
scene.simulate();
});
// CAMERA //
var SCREEN_WIDTH = window.innerWidth,
SCREEN_HEIGHT = window.innerHeight;
var VIEW_ANGLE = 45,
ASPECT = SCREEN_WIDTH / SCREEN_HEIGHT,
NEAR = 1,
FAR = 1000;
camera = new THREE.PerspectiveCamera(VIEW_ANGLE, ASPECT, NEAR, FAR);
// RENDERER //
renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.shadowMapEnabled = true;
// to antialias the shadow
renderer.shadowMapSoft = true;
renderer.setSize(SCREEN_WIDTH, SCREEN_HEIGHT);
container = document.getElementById('container');
container.appendChild(renderer.domElement);
// EVENTS //
//THREEx.WindowResize(renderer, camera);
// LIGHT //
var hemiLight = new THREE.HemisphereLight(0xffffff, 0xffffff, 0.6);
hemiLight.color.setHSL(0.6, 1, 0.6);
hemiLight.groundColor.setHSL(0.095, 1, 0.75);
hemiLight.position.set(0, 500, 0);
scene.add(hemiLight);
var light = new THREE.DirectionalLight(0xffffff, 1);
light.color.setHSL(0.1, 1, 0.95);
light.position.set(-1, 1.75, 1);
light.position.multiplyScalar(50);
light.castShadow = true;
light.shadowMapWidth = 2048;
light.shadowMapHeight = 2048;
light.shadowDarkness = 0.5;
var d = 50;
light.shadowCameraLeft = -d;
light.shadowCameraRight = d;
light.shadowCameraTop = d;
light.shadowCameraBottom = -d;
light.shadowCameraFar = 3500;
light.shadowBias = -0.0001;
light.shadowDarkness = 0.35;
scene.add(light);
// GEOMETRY //
var checkerboard = new THREE.ImageUtils.loadTexture('http://www.cns.nyu.edu/lcv/texture/artificial-periodic/checkerboard.o.jpg');
checkerboard.wrapS = checkerboard.wrapT = THREE.RepeatWrapping;
checkerboard.repeat.set(4, 4);
var checkerboard2 = new THREE.ImageUtils.loadTexture('http://www.cns.nyu.edu/lcv/texture/artificial-periodic/checkerboard.o.jpg');
var cubeMaterial = Physijs.createMaterial(
new THREE.MeshLambertMaterial({
map: checkerboard2
}),
1.0, // high friction
0.0 // low restitution
);
var cubeGeometry = new THREE.CubeGeometry(10, 5, 10, 1, 1, 1);
var cube = new Physijs.BoxMesh(
cubeGeometry,
cubeMaterial,
1);
cube.position.set(-10, 1, -10);
cube.castShadow = true;
cube.receiveShadow = true;
cube.occ = true;
scene.add(cube);
var cubeMaterial2 = Physijs.createMaterial(
new THREE.MeshLambertMaterial({
map: checkerboard2
}),
1.0, // high friction
0.0 // low restitution
);
var cubeGeometry2 = new THREE.CubeGeometry(10, 5, 10, 1, 1, 1);
var cube2 = new Physijs.BoxMesh(
cubeGeometry2,
cubeMaterial2,
1);
cube2.position.set(-10, 7, -1);
cube2.castShadow = true;
cube2.receiveShadow = true;
cube2.occ = true;
scene.add(cube2);
var cubeMaterial3 = Physijs.createMaterial(
new THREE.MeshLambertMaterial({
map: checkerboard2
}),
1.0, // high friction
0.0 // low restitution
);
var cubeGeometry3 = new THREE.CubeGeometry(10, 5, 10, 1, 1, 1);
var cube3 = new Physijs.BoxMesh(
cubeGeometry3,
cubeMaterial3,
1);
cube3.position.set(-10, 13, 8);
cube3.castShadow = true;
cube3.receiveShadow = true;
cube3.occ = true;
scene.add(cube3);
var cone = new Physijs.ConeMesh(
new THREE.CylinderGeometry(0, 5, 4, 30, 30, true),
Physijs.createMaterial(
new THREE.MeshLambertMaterial({
map: checkerboard2
}),
1.0, // high friction
0.0 // low restitution
),
0);
cone.position.set(0, 2, 0);
scene.castShadow = true;
scene.receiveShadow = true;
cone.occ = true;
scene.add(cone);
// FLOOR //
var floorMaterial = new THREE.MeshLambertMaterial({
map: checkerboard
});
var floorGeometry = new THREE.PlaneGeometry(100, 100, 1, 1);
var floor = new Physijs.PlaneMesh(floorGeometry, floorMaterial);
floor.rotation.x = -Math.PI / 2;
floor.castShadow = false;
floor.receiveShadow = true;
floor.occ = true;
scene.add(floor);
// SKY //
var skyBoxGeometry = new THREE.CubeGeometry( 1000, 1000, 1000 );
var skyBox = new THREE.Mesh(skyBoxGeometry, new THREE.MeshLambertMaterial({
color: '#3333bb'
}));
scene.add(skyBox);
// fog must be added to scene before first render
scene.fog = new THREE.FogExp2(0x999999, 0.001);
var bounding = new Physijs.SphereMesh(
new THREE.SphereGeometry(0.75, 4, 4),
Physijs.createMaterial(
new THREE.MeshBasicMaterial({
color: '#ff0000'
}),
1.0, // high friction
0.0 // low restitution
),
0.1);
var player = new THREE.Mesh(
new THREE.CubeGeometry(1, 6, 1, 1, 1, 1),
new THREE.MeshLambertMaterial({
color: '#00ff00'
}),
1);
player.position.set(0, 3, 0);
bounding.position.set(10, 0.75, -10);
bounding.add(player);
scene.add(bounding);
bounding.setAngularFactor(new THREE.Vector3(0, 0, 0));
controls = new THREE.PlayerControls(bounding, scene, player, camera, renderer.domElement);
// animation loop / game loop
scene.simulate();
animate();
};
function animate() {
requestAnimationFrame(animate);
render();
update();
}
function update() {
// delta = change in time since last call (in seconds)
var delta = clock.getDelta();
THREE.AnimationHandler.update(delta);
if (controls) controls.update(delta);
}
function render() {
renderer.render(scene, camera);
}
Thank you!!!
Ok, I ended up fixing this on my own, but it was a very difficult process.
I have spent so much time on this. I tried starting over completely and ended up rewriting all my controls objects in different ways with no success in fact things got slightly worse with that approach. And I learned some things:
updating the control after rendering causes horrible stutter (or makes the physics stutter worse). I must of not been paying attention to where i put my update function, but it needed to be before render.
I also started looking at the demos for Physijs to see what settings they used to get things smooth. this one specifically (http://chandlerprall.github.io/Physijs/examples/body.html)
I tweaked around with my friction and mass settings and I started using a BoxMesh for the floor instead of a plane, that seems to help with the jitters.
Finally I changed player control class a bit:
instead of straight my camera to my player, i started using a gyroscope to buffer the rotation.
this.camera_anchor_gyro = new THREE.Gyroscope();
this.camera_anchor_gyro.add(this.camera);
this.anchor.add(this.camera_anchor_gyro);
next i wanted to rotate the camera_anchor_gyro instead of the camera to match up the rotations, and this became a huge headache until i learned about: http://en.wikipedia.org/wiki/Gimbal_lock
so i soon added this after the gyro stuff:
this.anchor.rotation.order = "YXZ";
this.camera_anchor_gyro.rotation.order = "YXZ";
this.camera.rotation.order = "YXZ";
finally here is my updated rotation fix logic:
if ((this.moving || this.turning) && state != STATE.ROTATE) {
var curr_rot = new THREE.Euler(0, 0, 0, "YXZ").setFromRotationMatrix(this.camera.matrixWorld).y;
var dest_rot = new THREE.Euler(0, 0, 0, "YXZ").setFromRotationMatrix(this.anchor.matrixWorld).y;
var dest_rot = dest_rot + (dest_rot > 0 ? -Math.PI : Math.PI);
var step = shortestArc(curr_rot,dest_rot)*delta*2;
this.camera_anchor_gyro.rotation.y += step;//Math.max(-delta, diff);
// fix pitch (should be an option or it could get anoying)
//phi = 9*Math.PI/24;
}
I have updated my fiddle http://jsfiddle.net/nA8SV/2/ and this works so much better. but there is still a slight stuttering issue but i will have to continue to investigate.

Resources