I have a threejs code that allows me to create a virtual tour, I want to implement an mvc method but for now I have some error that I can't solve.
Here is what I did
Scene.js
import * as THREE from 'three'
import { TweenLite } from '/gsap/src/all.js'
import { init } from '/client.js';
let scene;
class Scene {
constructor(image, camera) {
this.image = image
this.points = []
this.sprites = []
this.scene = null
this.camera = camera
}
//Création de la scène
createScene(scene) {
this.scene = scene
const geometry = new THREE.SphereGeometry(11, 32, 32)
const texture = new THREE.TextureLoader().load(this.image)
texture.wrapS = THREE.RepeatWrapping
texture.repeat.x = -1
texture.minFilter = THREE.LinearFilter;
const material = new THREE.MeshBasicMaterial({
map: texture,
side: THREE.DoubleSide
})
material.transparent = true
this.sphere = new THREE.Mesh(geometry, material)
this.scene.add(this.sphere)
this.points.forEach(this.addTooltip.bind(this))
}
addPoint(point) {
this.points.push(point)
}
//Création du tooltip
addTooltip = function (point) {
var loader = new THREE.TextureLoader();
let spriteMap = loader.load('../photo/' + point.image, (texture) => {
let spriteMaterial = new THREE.SpriteMaterial({
map: spriteMap
})
let sprite = new THREE.Sprite(spriteMaterial)
sprite.name = point.name;
sprite.position.copy(point.position.clone().normalize().multiplyScalar(10));
sprite.scale.multiplyScalar(2)
this.scene.add(sprite);
this.sprites.push(sprite);
if (point.scene !== false) {
sprite.onClick = () => {
this.destroy();
point.scene.destroy(scene);
point.scene.createScene(scene);
TweenLite.to(this.sphere.material, 1, {
opacity: 0,
onComplete: () => {
this.scene.remove(this.sphere)
}
})
}
} else {
sprite.onClick = () => { }
}
})
}
//Destruction des tooltip/scene a chaque changement de scene
destroy() {
this.sprites.forEach((sprite) => {
this.sprites.forEach((sprite) => {
TweenLite.to(sprite.scale, 1, {
x: 0,
y: 0,
z: 0,
onComplete: () => {
this.scene.remove(sprite)
}
})
})
})
}
//Apparition des nouveaux point de scene
appear() {
this.sprites.forEach((sprite) => {
sprite.scale.set(0, 0, 0)
TweenLite.to(sprite.scale, 1, {
x: 1,
y: 3,
z: 3
})
})
}
}
export default Scene;
Client.Js
import * as THREE from 'three'
import { OrbitControls } from './jsm/controls/OrbitControls.js'
import Scene from '/js/scene.js';
window.addEventListener('load', () => {
init();
})
export function init() {
const tooltip = document.querySelector('#tooltip')
let renderer
let scene;
const container = document.body
scene = new THREE.Scene()
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 100)
camera.position.z = 2
renderer = new THREE.WebGLRenderer({
canvas: document.querySelector('#world'),
});
renderer.setSize(window.innerWidth, window.innerHeight)
document.body.appendChild(renderer.domElement)
const controls = new OrbitControls(camera, renderer.domElement)
controls.maxDistance = 3;
controls.minDistance = 0.9;
controls.rotateSpeed = -0.3
controls.enableZoom = true
controls.enablePan = false
controls.autoRotate = true
controls.autoRotateSpeed = 0.1
controls.enableDamping = true;
controls.dampingFactor = 0.3;
camera.position.set(1, 0, 0)
controls.update()
let s = new Scene('photo/entre.jpg', camera)
let s2 = new Scene('photo/entre.jpg', camera)
s.addPoint({
position: new THREE.Vector3(-10.468942480245712, -1.467960149500938, -2.8200827216367097),
name: '',
scene: s2,
image: '/logo/hall.png'
})
s.createScene(scene)
s.appear()
container.appendChild(renderer.domElement)
window.addEventListener(
'resize',
() => {
camera.aspect = window.innerWidth / window.innerHeight
camera.updateProjectionMatrix()
renderer.setSize(window.innerWidth, window.innerHeight)
render()
},
false
)
function animate() {
requestAnimationFrame(animate)
controls.update()
render()
}
function render() {
renderer.render(scene, camera)
}
animate()
const rayCaster = new THREE.Raycaster()
function onClick(e) {
let mouse = new THREE.Vector2(
(e.clientX / window.innerWidth) * 2 - 1,
-(e.clientY / window.innerHeight) * 2 + 1
)
rayCaster.setFromCamera(mouse, camera)
let intersects = rayCaster.intersectObjects(scene.children)
intersects.forEach(function (intersect) {
if (intersect.object.type === 'Sprite') {
intersect.object.onClick()
if (spriteActive) {
tooltip.classList.remove('is-active')
spriteActive = false
}
}
})
intersects = rayCaster.intersectObject(s.sphere)
if (intersects.length > 0) {
console.log(intersects[0].point)
}
let intersectes = rayCaster.intersectObjects(scene.children)
intersects.forEach(function (intersect) {
if (intersectes[0].object.type == "Sprite") {
gsap.to(camera.position, {
x: -intersectes[0].object.position.x,
y: 0,
z: -intersectes[0].object.position.z,
duration: 1.5,
ease: "power4.inOut",
})
}
})
}
function onMouseMove(e) {
let mouse = new THREE.Vector2(
(e.clientX / window.innerWidth) * 2 - 1,
-(e.clientY / window.innerHeight) * 2 + 1
)
rayCaster.setFromCamera(mouse, camera)
let foundSprite = false
let intersects = rayCaster.intersectObjects(scene.children)
intersects.forEach(function (intersect) {
if (intersect.object.name != '') {
let p = intersect.object.position.clone().project(camera)
tooltip.style.top = ((-1 * p.y + 1) * window.innerHeight / 2) + 'px'
tooltip.style.left = ((p.x + 1) * window.innerWidth / 2) + 'px'
tooltip.classList.add('is-active')
//Texte dans le tooltip
foundSprite = true
}
if (foundSprite) {
container.classList.add('hover')
controls.autoRotate = false
} else {
container.classList.remove('hover')
controls.autoRotate = true;
}
})
}
container.addEventListener('click', onClick)
container.addEventListener('mousemove', onMouseMove)
}
And here is the error I get
Uncaught TypeError: Cannot read properties of undefined (reading 'add')
at Scene.createScene (scene.js:30:20)
at sprite.onClick (scene.js:58:37)
at client.js:94:34
at Array.forEach (<anonymous>)
at HTMLBodyElement.onClick (client.js:91:20)
I've tried everything but no idea so far.
it seems to me that I can't get the scene variable which is in the init() or in the CreateScene method, I don't know
Thanks for your future help.
You should read through the error, it gives you lots of info of where the error is happening:
at Sprite.onClick, you're calling Scene.createScene, where you're calling .add() on and undefined variable.
So, looking through your code, now you can pinpoint where it's happening:
if (point.scene !== false) {
sprite.onClick = () => {
this.destroy();
point.scene.destroy(scene);
// 'scene' doesn't exist here, so it's undefined
point.scene.createScene(scene);
Related
I got this message from webpack:
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
in my gulpfile:
// js task
const js = () => {
return src('src/**/*.js')
.pipe(babel({
presets: [['#babel/env']],
plugins: [["#babel/plugin-proposal-class-properties",
{
"loose": true
}]]
}))
.pipe(webpack({
mode: 'development',
devtool: 'inline-source-map'
}))
.pipe(mode.development( sourcemaps.init({ loadMaps: true }) ))
.pipe(rename('scripts.js'))
.pipe(mode.production( terser({ output: { comments: false }}) ))
.pipe(mode.development( sourcemaps.write() ))
.pipe(dest('dist/scripts'))
.pipe(mode.development( browserSync.stream() ));
}
i switched functions from:
init(){}
to:
init = () => {
And then i get those errors, somebody can help?
Thanks already!
Edit:
I came up with the error in webpack so i changed all the functions to:
Well i removed all the = () => from the functions but then i got some reference errors i guess:
class Experience {
constructor(
options = {
containerSelector: "[data-app-container]"
}
) {
this.options = options;
this.container = document.querySelector(this.options.containerSelector);
// GSAP Plugins
gsap.registerPlugin(ScrollTrigger, ScrollSmoother);
// Time
this.clock = new THREE.Clock();
this.time = 0;
// THREE items
this.renderer;
this.camera;
this.scene;
this.controls;
this.meshes = [];
// Rotation
this.targetRotation = 0;
// Settings
this.settings = {
cameraDistance: 5,
scalePeriod: 8
};
this.init();
}
init () {
this.createApp();
this.createItems();
this.initScroll();
this.update();
this.container.classList.add("is-ready");
};
initScroll () {
this.scrollSmoother = ScrollSmoother.create({
content: "#content",
smooth: 1
});
// Add scroll triggers for each span item
document.querySelectorAll("span").forEach((span) => {
ScrollTrigger.create({
trigger: span,
start: "top 90%",
end: "bottom 10%",
onUpdate: (self) => {
const dist = Math.abs(self.progress - 0.5);
const lightness = this.mapToRange(dist, 0, 0.5, 80, 100);
span.style.setProperty("--l", lightness + "%");
}
});
});
};
createApp () {
// Renderer
this.renderer = new THREE.WebGLRenderer({
antialias: false,
alpha: true
});
this.renderer.setPixelRatio(1.5);
this.renderer.setSize(
this.container.offsetWidth,
this.container.offsetHeight
);
this.container.appendChild(this.renderer.domElement);
// Camera
this.camera = new THREE.PerspectiveCamera(
45,
this.container.offsetWidth / this.container.offsetHeight,
1,
10000
);
this.camera.position.set(0, 0, this.settings.cameraDistance);
this.scene = new THREE.Scene();
// Orbit Controls
this.controls = new OrbitControls(
this.camera,
this.renderer.domElement
);
this.controls.enableKeys = false;
this.controls.enableZoom = false;
this.controls.enableDamping = false;
// Resize the renderer on window resize
window.addEventListener(
"resize",
() => {
this.camera.aspect =
this.container.offsetWidth / this.container.offsetHeight;
this.camera.updateProjectionMatrix();
this.renderer.setSize(
this.container.offsetWidth,
this.container.offsetHeight
);
},
true
);
// Ambient Light
let ambientLight = new THREE.AmbientLight(0xffffff, 0.1);
this.scene.add(ambientLight);
// Directional Light
let directionalLight = new THREE.DirectionalLight(0xffffff, 0.4);
directionalLight.position.set(5, 3, 2);
directionalLight.target.position.set(0, 0, 0);
this.scene.add(directionalLight);
};
createItems () {
// Box
let boxGeom = new THREE.BoxGeometry(2, 2, 2);
let material = new THREE.MeshLambertMaterial({
color: 0xffffff
});
const itemCount = 40;
for (let i = 0; i < itemCount; i++) {
const mesh = new THREE.Mesh(boxGeom, material);
mesh.position.y = 13 * (Math.random() * 2 - 1);
mesh.position.x = 3 * (Math.random() * 2 - 1);
mesh.position.z = 4 * (Math.random() * 2 - 1);
mesh.rotation.y = Math.PI * Math.random();
mesh.rotationSpeed = Math.random() * 0.01 + 0.005;
this.scene.add(mesh);
this.meshes.push(mesh);
}
};
updateItems () {
const time = this.time;
const amplitude = 0.05;
const period = this.settings.scalePeriod;
const baseScale = 0.2;
const scaleEffect =
baseScale + amplitude * Math.sin(Math.PI * 2 * (time / period));
this.meshes.forEach((mesh) => {
mesh.scale.set(scaleEffect, scaleEffect, scaleEffect);
// Update rotation
mesh.rotation.x += mesh.rotationSpeed;
});
// Update camera
const cameraRange = 10;
this.camera.position.y = this.mapToRange(
this.scrollSmoother.progress,
0,
1,
cameraRange,
-cameraRange
);
};
mapToRange (value, inMin, inMax, outMin, outMax){
return ((value - inMin) * (outMax - outMin)) / (inMax - inMin) + outMin;
};
update () {
this.time = this.clock.getElapsedTime();
this.updateItems();
this.renderer.render(this.scene, this.camera);
window.requestAnimationFrame(this.update);
console.log(this.clock.getElapsedTime())
};
}
new Experience();
But then i get this error:
Uncaught TypeError: Cannot read properties of undefined (reading 'clock')
at update (gsap-three.js:188:1)
this is my first three js first-person point of view game, I am having trouble when I turn my camera the gun doesn't follow what I am looking at the rotation of the camera.
here is a link for the live site: https://geniussniper.github.io/Glory/
pressing on 0 will show camera rotation and gun position
pressing on y would have control to look around
'asdw' for move
and here is my code
import * as THREE from 'three';
import { PointerLockControls } from 'three/examples/jsm/controls/PointerLockControls';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
class World {
constructor(){
this.scene = new THREE.Scene();
this.camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );
this.clock = new THREE.Clock();
this.world = {
player: {
x: 0, // a,d side way control
y: 30, // flying cheating control
z: 0, // w,s forward backward control
// canShoot: 0,
},
movementSpeed: .4,
size: 1000,
camera: {
y: 10
},
bullets: [],
timer: 0
}
this.renderer = new THREE.WebGLRenderer({
antialias: true
});
this.hemisphereLight = new THREE.HemisphereLight( 0xffffff, 0xffffff, 1 );
this.scene.add( this.hemisphereLight );
this.directionalLight = new THREE.DirectionalLight(0xffffff)
this.scene.add(this.directionalLight);
this.keyborad = [];
addEventListener('keydown', e => {
this.keyborad[e.key] = true;
});
addEventListener('keyup', e => {
this.keyborad[e.key] = false;
});
addEventListener( 'click', () => {
if(this.pistol){
let bullet = new THREE.Mesh(
new THREE.SphereGeometry(0.05, 8, 8),
new THREE.MeshBasicMaterial({color: 0xffffff})
);
bullet.position.set(
this.pistol.position.x,
this.pistol.position.y + 0.4,
this.pistol.position.z
)
bullet.velocity = new THREE.Vector3(
-Math.sin(this.camera.rotation.y),
0,
Math.cos(this.camera.rotation.y)
);
bullet.alive = true;
setTimeout(() => {
bullet.alive = false;
this.scene.remove(bullet);
}, 1000);
this.world.bullets.push(bullet);
this.scene.add(bullet);
}
}, false );
this.loader = new GLTFLoader();
this.render();
}
animate() {
requestAnimationFrame( () => this.animate() );
this.processKeyboard();
// this.controls.update(); //OrbitControls for not following charater
this.shotting();
this.renderer.render( this.scene, this.camera );
}
shotting() {
for( let i = 0; i < this.world.bullets.length; i++){
if(this.world.bullets[i] === undefined) continue;
if(this.world.bullets[i].alive === false){
this.world.bullets.splice(i, 1);
continue;
}
this.world.bullets[i].position.add(this.world.bullets[i].velocity);
}
}
worldSetup() {
this.renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( this.renderer.domElement );
this.camera.position.setY(10);
this.camera.position.setZ(30);
// this.controls = new OrbitControls(this.camera, this.renderer.domElement);
this.controls = new PointerLockControls(this.camera, this.renderer.domElement);
this.loader.load('./src/models/game_pirate_adventure_map/scene.gltf', (gltf) => {
gltf.scene.scale.set(0.01, 0.01, 0.01);
this.scene.add(gltf.scene);
});
this.loader.load('./src/models/vis_p35p_pistol/scene.gltf', (gltf) => {
gltf.scene.scale.set(0.01, 0.01, 0.01);
gltf.scene.position.set(0, 8, 34);
this.pistol = gltf.scene;
this.scene.add(this.pistol);
});
window.addEventListener( 'resize', () => {
this.camera.aspect = window.innerWidth / window.innerHeight;
this.camera.updateProjectionMatrix();
this.renderer.setSize( window.innerWidth, window.innerHeight );
}, false );
}
processKeyboard() {
let time = Date.now() * 0.0005;
if(this.keyborad['w']){
this.controls.moveForward(this.world.movementSpeed);
}
if(this.keyborad['s']) {
this.controls.moveForward(-this.world.movementSpeed);
}
if(this.keyborad['a']){
this.controls.moveRight(-this.world.movementSpeed);
}
if(this.keyborad['d']) {
this.controls.moveRight(this.world.movementSpeed);
}
if(this.keyborad['y']){
this.controls.lock();
}
if(this.keyborad['esc']) {
this.controls.unlock();
}
if(this.keyborad['0']){
//the debugging console
if(this.world.timer <= 0){
// console.log(this.camera.position);
console.log(this.camera.rotation);
console.log(this.pistol.position);
// console.log(this.pistol.rotation);
this.world.timer = 10;
} else {
this.world.timer -= 1;
}
}
if(this.pistol){
let rotation = this.camera.rotation.y + Math.PI/6
this.pistol.position.set(
this.camera.position.x - Math.sin(rotation) * 4,
this.camera.position.y - 1 + Math.sin(time * 4 + this.camera.position.x + this.camera.position.z) * 0.04,
this.camera.position.z + Math.cos(rotation) * 4
)
this.pistol.rotation.set(
this.camera.rotation.x,
this.camera.rotation.y - Math.PI/2,
this.camera.rotation.z
)
}
}
render() {
this.worldSetup();
this.camera.lookAt(new THREE.Vector3(0, this.world.camera.y, 40))
this.animate();
};
}
export default World;
I'm working on a little three.js scene in which I want to drive a car down a road. The trouble is my car is flipping end to end each frame, instead of rolling on its tires:
Does anyone know how I can make my car roll with cannon.js? Any pointers would be hugely helpful. For the sake of preservation, here's my raw scene:
var carBody,
floorBody,
pressed = {},
rotation = 0,
clock = new THREE.Clock(),
loader = new THREE.TextureLoader(),
container = document.querySelector('body'),
w = container.clientWidth,
h = container.clientHeight,
scene = new THREE.Scene(),
camera = new THREE.PerspectiveCamera(75, w/h, 0.1, 100000),
controls = new THREE.TrackballControls(camera, container),
renderConfig = {antialias: true, alpha: true},
renderer = new THREE.WebGLRenderer(renderConfig);
controls.target = new THREE.Vector3(0, 0, 0.75);
controls.panSpeed = 0.4;
camera.position.set(0,80,-4900);
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(w, h);
container.appendChild(renderer.domElement);
window.addEventListener('resize', function() {
w = container.clientWidth;
h = container.clientHeight;
camera.aspect = w/h;
camera.updateProjectionMatrix();
renderer.setSize(w, h);
})
function render() {
requestAnimationFrame(render);
renderer.render(scene, camera);
if (car && carBody && carBody.position) {
moveCar();
updatePhysics();
moveCamera();
//controls.update();
}
}
function getPlane(img, w, h, wrap) {
var texture = loader.load(img);
if (wrap) {
texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
texture.repeat.set(10, 10);
}
var material = new THREE.MeshBasicMaterial({
map: texture,
side: THREE.DoubleSide,
});
var geometry = new THREE.PlaneGeometry(w, h, 10, 10);
return new THREE.Mesh(geometry, material);
}
function getSides() {
var d = 300;
var group = new THREE.Group();
for (var i=0; i<2; i++) {
var plane = getPlane('asphalt.jpg', 10000, 200, true);
plane.position.y = 100;
plane.rotation.set(Math.PI/2, -Math.PI/2, Math.PI/2);
plane.position.x = i == 0 ? -d : d;
group.add(plane);
}
return group;
}
function getSky() {
var directions = ['right', 'left', 'top', 'bottom', 'front', 'back'];
var geometry = new THREE.BoxGeometry(50000, 50000, 50000);
var materialArray = [];
for (var i=0; i<6; i++)
materialArray.push( new THREE.MeshBasicMaterial({
map: loader.load(directions[i] + '.bmp'),
side: THREE.BackSide
}));
return new THREE.Mesh( geometry, materialArray );
}
function getCar() {
var mtlLoader = new THREE.MTLLoader();
mtlLoader.load('car.mtl', function(mat) {
mat.preload();
var objLoader = new THREE.OBJLoader();
objLoader.setMaterials(mat);
objLoader.load('car.obj', function(obj) {
window.car = obj;
car.scale.set(0.1, 0.1, 0.1);
obj.position.set(0, 0, -4800);
scene.add(obj);
})
})
}
function getPhysics() {
world = new CANNON.World();
world.gravity.set(0, -100, 0); // earth = -9.82 m/s
world.broadphase = new CANNON.NaiveBroadphase();
world.broadphase.useBoundingBoxes = true;
var solver = new CANNON.GSSolver();
solver.iterations = 7;
solver.tolerance = 0.1;
world.solver = solver;
world.quatNormalizeSkip = 0;
world.quatNormalizeFast = false;
world.defaultContactMaterial.contactEquationStiffness = 1e9;
world.defaultContactMaterial.contactEquationRelaxation = 4;
return world;
}
function addPhysics() {
var m = getPhysicsMaterial();
carBody = new CANNON.Body({
mass: 10,
material: m,
shape: new CANNON.Sphere(30),
linearDamping: 0.6,
angularDamping: 0.8,
position: new CANNON.Vec3(0, 30, -4900)
});
world.addBody(carBody);
// floor
var q = floor.quaternion;
var floorBody = new CANNON.Body({
mass: 0, // mass = 0 makes the body static
material: m,
shape: new CANNON.Plane(),
quaternion: new CANNON.Quaternion(-q._x, q._y, q._z, q._w)
});
world.addBody(floorBody);
}
function getPhysicsMaterial() {
var m = new CANNON.Material('slipperyMaterial');
var c = new CANNON.ContactMaterial(m, m, {
friction: 0.3,
restitution: 0.3,
})
world.addContactMaterial(c);
return m;
}
function moveCar() {
var delta = clock.getDelta(); // seconds
var moveDistance = 2000 * delta; // n pixels per second
// set roll sensitivity
var sensitivity = 1.5;
var rotateAngle = Math.PI / 2 * delta * sensitivity;
// determine the direction to travel
var p = carBody.position;
var dir = new THREE.Vector3(p.x, p.y, p.z);
dir.sub(camera.position).normalize(); // vector b/w camera and car
if (pressed['W'] || pressed['ARROWUP']) {
carBody.velocity.x += moveDistance * dir.x;
carBody.velocity.z += moveDistance * dir.z;
}
if (pressed['S'] || pressed['ARROWDOWN']) {
carBody.velocity.x -= moveDistance * dir.x;
carBody.velocity.z -= moveDistance * dir.z;
}
if (pressed['A'] || pressed['ARROWLEFT']) {
rotation += rotateAngle;
}
if (pressed['D'] || pressed['ARROWRIGHT']) {
rotation -= rotateAngle;
}
if (pressed[' ']) {
carBody.velocity.y = 10;
}
}
function updatePhysics() {
world.step(1/60);
car.position.copy(carBody.position);
car.quaternion.copy(carBody.quaternion);
}
function moveCamera() {
var rotZ = Math.cos(rotation);
var rotX = Math.sin(rotation);
var distance = 100;
camera.position.x = carBody.position.x - (distance * rotX);
camera.position.y = carBody.position.y + 20;
camera.position.z = carBody.position.z - (distance * rotZ);
camera.lookAt(car.position);
}
window.addEventListener('keydown', function(e) {
pressed[e.key.toUpperCase()] = true;
})
window.addEventListener('keyup', function(e) {
pressed[e.key.toUpperCase()] = false;
})
/**
* Add elems
**/
var light = new THREE.HemisphereLight(0xffffbb, 0x080820, 1);
scene.add(light);
var geometry = new THREE.PlaneGeometry(10000, 10000);
var material = new THREE.MeshBasicMaterial();
var floor = new THREE.Mesh(geometry, material);
floor.rotation.x = Math.PI / 2;
scene.add(floor);
var street = getPlane('asphalt.jpg', 10000, 1000, true);
street.rotation.x = Math.PI/2;
street.rotation.z = Math.PI/2;
scene.add(street);
var sides = getSides();
scene.add(sides);
var sky = getSky();
scene.add(sky);
var car = getCar();
var world = getPhysics();
addPhysics();
render();
html,
body {
width: 100%;
height: 100%;
background: #aaa;
}
body {
margin: 0;
overflow: hidden;
}
canvas {
width: 100%;
height: 100%;
}
<script src="https://threejs.org/build/three.min.js"></script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/cannon.js/0.6.2/cannon.js'></script>
<script src='https://raw.githack.com/mrdoob/three.js/master/examples/js/loaders/ObjLoader.js'></script>
<script src='https://raw.githack.com/mrdoob/three.js/master/examples/js/loaders/MTLLoader.js'></script>
<script src='https://raw.githack.com/mrdoob/three.js/master/examples/js/controls/TrackballControls.js'></script>
I'm trying to build a webapp that let users watch 360 panorama images with three.js, but some of the code isn't working as expected. The problem involves rotating the camera.
I'm able to rotate the camera by listening to either touch events or deviceorientation events but I can't manage to make both work simultaneously.
Currently, the camera rotates by touch gesture, but it snaps back immediately after detecting a device orientation event.
I want the camera to rotate with touch gestures and then rotate with device orientation starting from the rotation it was previously set by touch.
I think I should improve deviceorientation event handler or setQuaternion method below in add-gestures.js but I don't know how.
index.html
<!doctype html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="stylesheet" href="pages/vr/style.css') ?>
</head>
<body>
<canvas id="vr-canvas"></canvas>
<script src="/js/lib/threejs/r104/build/three.min.js"></script>
<script src="/js/pages/vr/init-vr.js"></script>
<script src="/js/pages/vr/add-gestures.js"></script>
<script src="/js/pages/vr/add-sphere.js"></script>
</body>
</html>
init-vr.js
window.VRApp = window.VRApp || {};
const canvas = document.querySelector("#vr-canvas");
const renderer = (() => {
const webGLRenderer = new THREE.WebGLRenderer({ canvas });
webGLRenderer.setPixelRatio(window.devicePixelRatio);
return webGLRenderer;
})();
const scene = new THREE.Scene();
const camera = (() => {
const perspectiveCamera = new THREE.PerspectiveCamera(100, canvas.width / canvas.height, 0.01, 100);
perspectiveCamera.rotation.order = "ZYX";
return perspectiveCamera;
})();
const animate = () => {
requestAnimationFrame(animate);
renderer.render(scene, camera);
};
animate();
window.VRApp.renderer = renderer;
window.VRApp.scene = scene;
window.VRApp.camera = camera;
add-gestures.js
window.VRApp = window.VRApp || {};
const State = {
Neutral: 0x0000,
RotateCamera: 0x0001,
};
let state = State.Neutral;
let windowOrientation = window.orientation || 0;
let cameraRotationCache = window.VRApp.camera.rotation.clone();
let mousePositionCache = {
x: 0,
y: 0,
minYDiff: 0,
maxYDiff: 0,
};
const setState = (newState) => {
if (State.hasOwnProperty(newState)) {
state = State[newState];
}
};
const checkState = (targetState) => {
if (State.hasOwnProperty(targetState)) {
return state === State[targetState];
}
return false;
};
const setQuaternion = (() => {
const zee = new THREE.Vector3(0, 0, 1);
const euler = new THREE.Euler();
const q0 = new THREE.Quaternion();
const q1 = new THREE.Quaternion(-1 * Math.sqrt(0.5), 0, 0, Math.sqrt(0.5));
return (alpha, beta, gamma, orientation) => {
euler.set(beta, alpha, -1 * gamma, "YXZ");
window.VRApp.camera.quaternion.setFromEuler(euler);
window.VRApp.camera.quaternion.multiply(q1);
window.VRApp.camera.quaternion.multiply(q0.setFromAxisAngle(zee, -1 * orientation));
};
})();
const onMouseDownHandler = (clientX, clientY) => {
setState("RotateCamera");
cameraRotationCache = window.VRApp.camera.rotation.clone();
mousePositionCache.x = clientX;
mousePositionCache.y = clientY;
mousePositionCache.minYDiff = -90 - (cameraRotationCache.x * (180 / Math.PI)) - (clientY * (Math.PI / 180));
mousePositionCache.maxYDiff = 90 - (cameraRotationCache.x * (180 / Math.PI)) - (clientY * (Math.PI / 180));
};
const onMouseMoveHandler = (clientX, clientY) => {
if (checkState("RotateCamera")) {
window.VRApp.camera.rotation.order = "ZYX";
let xDiff = clientX - mousePositionCache.x;
let yDiff = clientY - mousePositionCache.y;
if (yDiff < mousePositionCache.minYDiff) {
yDiff = mousePositionCache.minYDiff;
mousePositionCache.y = clientY - mousePositionCache.minYDiff;
}
if (yDiff > mousePositionCache.maxYDiff) {
yDiff = mousePositionCache.maxYDiff;
mousePositionCache.y = clientY - mousePositionCache.maxYDiff;
}
let newAngleX = cameraRotationCache.x + (yDiff * (Math.PI / 180));
let newAngleY = cameraRotationCache.y + (xDiff * (Math.PI / 180));
window.VRApp.camera.rotation.x = newAngleX;
window.VRApp.camera.rotation.y = newAngleY;
}
};
const onMouseUpHandler = () => {
setState("Neutral");
cameraRotationCache = window.VRApp.camera.rotation.clone();
mousePositionCache.x = 0;
mousePositionCache.y = 0;
mousePositionCache.minYDiff = 0;
mousePositionCache.maxYDiff = 0;
};
if ("onresize" in window) {
window.addEventListener("resize", (event) => {
const width = window.innerWidth;
const height = window.innerHeight;
window.VRApp.renderer.domElement.width = width;
window.VRApp.renderer.domElement.height = height;
window.VRApp.renderer.domElement.style.height = height + "px";
window.VRApp.renderer.setSize(width, height);
window.VRApp.camera.aspect = width / height;
window.VRApp.camera.updateProjectionMatrix();
});
}
if ("onload" in window) {
window.addEventListener("load", (event) => {
const width = window.innerWidth;
const height = window.innerHeight;
window.VRApp.renderer.domElement.width = width;
window.VRApp.renderer.domElement.height = height;
window.VRApp.renderer.domElement.style.height = height + "px";
window.VRApp.renderer.setSize(width, height);
window.VRApp.camera.aspect = width / height;
window.VRApp.camera.updateProjectionMatrix();
});
}
if ("onmousedown" in window.VRApp.renderer.domElement) {
window.VRApp.renderer.domElement.addEventListener("mousedown", (event) => {
onMouseDownHandler(event.clientX, event.clientY);
});
}
if ("onmousemove" in window.VRApp.renderer.domElement) {
window.VRApp.renderer.domElement.addEventListener("mousemove", (event) => {
onMouseMoveHandler(event.clientX, event.clientY);
});
}
if ("onmouseup" in window.VRApp.renderer.domElement) {
window.VRApp.renderer.domElement.addEventListener("mouseup", (event) => {
onMouseUpHandler();
});
}
if ("onmouseleave" in window.VRApp.renderer.domElement) {
window.VRApp.renderer.domElement.addEventListener("mouseleave", (event) => {
onMouseUpHandler();
});
}
if ("ontouchstart" in window.VRApp.renderer.domElement) {
window.VRApp.renderer.domElement.addEventListener("touchstart", (event) => {
event.preventDefault();
if (event.touches.length === 1) {
const touch = event.touches[0];
onMouseDownHandler(touch.clientX, touch.clientY);
}
});
}
if ("ontouchmove" in window.VRApp.renderer.domElement) {
window.VRApp.renderer.domElement.addEventListener("touchmove", (event) => {
event.preventDefault();
if (event.touches.length === 1) {
const touch = event.touches[0];
onMouseMoveHandler(touch.clientX, touch.clientY);
}
});
}
if ("ontouchend" in window.VRApp.renderer.domElement) {
window.VRApp.renderer.domElement.addEventListener("touchend", (event) => {
event.preventDefault();
onMouseUpHandler();
});
}
if ("ontouchcancel" in window.VRApp.renderer.domElement) {
window.VRApp.renderer.domElement.addEventListener("touchcancel", (event) => {
event.preventDefault();
onMouseUpHandler();
});
}
if ("onorientationchange" in window) {
window.addEventListener("orientationchange", (event) => {
windowOrientation = window.orientation || 0;
});
}
if ("ondeviceorientation" in window) {
window.addEventListener("deviceorientation", (event) => {
if (checkState("Neutral")) {
let alpha = event.alpha * (Math.PI / 180);
let beta = event.beta * (Math.PI / 180);
let gamma = event.gamma * (Math.PI / 180);
let orientation = windowOrientation * (Math.PI / 180);
setQuaternion(alpha, beta, gamma, orientation);
}
});
}
add-sphere.js
window.VRApp = window.VRApp || {};
const sphere = (() => {
const geometry = new THREE.SphereGeometry(100, 64, 64);
geometry.scale(1, 1, -1);
geometry.rotateY(Math.PI / 2);
const material = new THREE.MeshBasicMaterial({
});
const mesh = new THREE.Mesh(geometry, material);
return mesh;
})();
const textureLoader = new THREE.TextureLoader();
const texture = textureLoader.load("/img/pages/vr/sample-360.jpg");
sphere.material.map = texture;
window.VRApp.scene.add(sphere);
I just started exploring three.js and have been trying to adapt a project I found.
I would like to know if it would be possible to have the globe object rotate around it's y-axis with minor additions to the code or whether it has to be rewritten from the ground up.
var canvas = document.querySelector('canvas');
var width = canvas.offsetWidth,
height = canvas.offsetHeight;
var colors = [
new THREE.Color(0xac1122),
new THREE.Color(0x96789f),
new THREE.Color(0x535353)];
var renderer = new THREE.WebGLRenderer({
canvas: canvas,
antialias: true
});
renderer.setPixelRatio(window.devicePixelRatio > 1 ? 2 : 1);
renderer.setSize(width, height);
renderer.setClearColor(0xffffff);
var scene = new THREE.Scene();
var raycaster = new THREE.Raycaster();
raycaster.params.Points.threshold = 6;
var camera = new THREE.PerspectiveCamera(50, width / height, 0.1, 2000);
camera.position.set(0, 0, 350);
var galaxy = new THREE.Group();
scene.add(galaxy);
// Create dots
var loader = new THREE.TextureLoader();
loader.crossOrigin = "";
var dotTexture = loader.load("img/dotTexture.png");
var dotsAmount = 3000;
var dotsGeometry = new THREE.Geometry();
var positions = new Float32Array(dotsAmount * 3);
var sizes = new Float32Array(dotsAmount);
var colorsAttribute = new Float32Array(dotsAmount * 3);
for (var i = 0; i < dotsAmount; i++) {
var vector = new THREE.Vector3();
vector.color = Math.floor(Math.random() * colors.length);
vector.theta = Math.random() * Math.PI * 2;
vector.phi =
(1 - Math.sqrt(Math.random())) *
Math.PI /
2 *
(Math.random() > 0.5 ? 1 : -1);
vector.x = Math.cos(vector.theta) * Math.cos(vector.phi);
vector.y = Math.sin(vector.phi);
vector.z = Math.sin(vector.theta) * Math.cos(vector.phi);
vector.multiplyScalar(120 + (Math.random() - 0.5) * 5);
vector.scaleX = 5;
if (Math.random() > 0.5) {
moveDot(vector, i);
}
dotsGeometry.vertices.push(vector);
vector.toArray(positions, i * 3);
colors[vector.color].toArray(colorsAttribute, i*3);
sizes[i] = 5;
}
function moveDot(vector, index) {
var tempVector = vector.clone();
tempVector.multiplyScalar((Math.random() - 0.5) * 0.2 + 1);
TweenMax.to(vector, Math.random() * 3 + 3, {
x: tempVector.x,
y: tempVector.y,
z: tempVector.z,
yoyo: true,
repeat: -1,
delay: -Math.random() * 3,
ease: Power0.easeNone,
onUpdate: function () {
attributePositions.array[index*3] = vector.x;
attributePositions.array[index*3+1] = vector.y;
attributePositions.array[index*3+2] = vector.z;
}
});
}
var bufferWrapGeom = new THREE.BufferGeometry();
var attributePositions = new THREE.BufferAttribute(positions, 3);
bufferWrapGeom.addAttribute('position', attributePositions);
var attributeSizes = new THREE.BufferAttribute(sizes, 1);
bufferWrapGeom.addAttribute('size', attributeSizes);
var attributeColors = new THREE.BufferAttribute(colorsAttribute, 3);
bufferWrapGeom.addAttribute('color', attributeColors);
var shaderMaterial = new THREE.ShaderMaterial({
uniforms: {
texture: {
value: dotTexture
}
},
vertexShader: document.getElementById("wrapVertexShader").textContent,
fragmentShader: document.getElementById("wrapFragmentShader").textContent,
transparent:true
});
var wrap = new THREE.Points(bufferWrapGeom, shaderMaterial);
scene.add(wrap);
// Create white segments
var segmentsGeom = new THREE.Geometry();
var segmentsMat = new THREE.LineBasicMaterial({
color: 0xffffff,
transparent: true,
opacity: 0.3,
vertexColors: THREE.VertexColors
});
for (i = dotsGeometry.vertices.length - 1; i >= 0; i--) {
vector = dotsGeometry.vertices[i];
for (var j = dotsGeometry.vertices.length - 1; j >= 0; j--) {
if (i !== j && vector.distanceTo(dotsGeometry.vertices[j]) < 12) {
segmentsGeom.vertices.push(vector);
segmentsGeom.vertices.push(dotsGeometry.vertices[j]);
segmentsGeom.colors.push(colors[vector.color]);
segmentsGeom.colors.push(colors[vector.color]);
}
}
}
var segments = new THREE.LineSegments(segmentsGeom, segmentsMat);
galaxy.add(segments);
var hovered = [];
var prevHovered = [];
function render(a) {
var i;
dotsGeometry.verticesNeedUpdate = true;
segmentsGeom.verticesNeedUpdate = true;
raycaster.setFromCamera( mouse, camera );
var intersections = raycaster.intersectObjects([wrap]);
hovered = [];
if (intersections.length) {
for(i = 0; i < intersections.length; i++) {
var index = intersections[i].index;
hovered.push(index);
if (prevHovered.indexOf(index) === -1) {
onDotHover(index);
}
}
}
for(i = 0; i < prevHovered.length; i++){
if(hovered.indexOf(prevHovered[i]) === -1){
mouseOut(prevHovered[i]);
}
}
prevHovered = hovered.slice(0);
attributeSizes.needsUpdate = true;
attributePositions.needsUpdate = true;
renderer.render(scene, camera);
}
function onDotHover(index) {
dotsGeometry.vertices[index].tl = new TimelineMax();
dotsGeometry.vertices[index].tl.to(dotsGeometry.vertices[index], 1, {
scaleX: 10,
ease: Elastic.easeOut.config(2, 0.2),
onUpdate: function() {
attributeSizes.array[index] = dotsGeometry.vertices[index].scaleX;
}
});
}
function mouseOut(index) {
dotsGeometry.vertices[index].tl.to(dotsGeometry.vertices[index], 0.4, {
scaleX: 5,
ease: Power2.easeOut,
onUpdate: function() {
attributeSizes.array[index] = dotsGeometry.vertices[index].scaleX;
}
});
}
function onResize() {
canvas.style.width = '';
canvas.style.height = '';
width = canvas.offsetWidth;
height = canvas.offsetHeight;
camera.aspect = width / height;
camera.updateProjectionMatrix();
renderer.setSize(width, height);
}
var mouse = new THREE.Vector2(-100,-100);
function onMouseMove(e) {
var canvasBounding = canvas.getBoundingClientRect();
mouse.x = ((e.clientX - canvasBounding.left) / width) * 2 - 1;
mouse.y = -((e.clientY - canvasBounding.top) / height) * 2 + 1;
}
TweenMax.ticker.addEventListener("tick", render);
window.addEventListener("mousemove", onMouseMove);
var resizeTm;
window.addEventListener("resize", function(){
resizeTm = clearTimeout(resizeTm);
resizeTm = setTimeout(onResize, 200);
});
Codepen here - https://codepen.io/quickwaste/pen/PaGPdw
Thanks.
(A stretch goal would be to have the camera move in response to mouse movement)
Simply add galaxy.rotateY(0.005 * Math.PI); to render(), right before renderer.render(scene, camera) call, like this:
// pulled from the CodePen
function render(a) {
// ... omitted for brevity
prevHovered = hovered.slice(0);
attributeSizes.needsUpdate = true;
attributePositions.needsUpdate = true;
galaxy.rotateY(0.005 * Math.PI);
renderer.render(scene, camera);
}
I used a multiplier of 0.005 to give the globe a nice, lazy spin.
The 'galaxy' object is a THREE.Group, a wrapper of sorts for collections of THREE.Object3D objects. The Object3D has all sorts of nifty functions to help rotate, translate, and transform 3D objects. The rotateY() will spin the model around its local y-axis.