Three.js - Control camera motion with device tilting / gyroscope on mobile - javascript

i have simple 3d project made in three.js.
Currently its controlled by mouse motion which rotates camera around the scene (this also works by touch on mobile).
I want to implement to control this basic camera movement to be controlled by device motion / tilting / gyroscope while accesing web on mobile devices.
Here is my code:
const nearDist = 0.1;
const farDist = 10000;
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(
55,
window.innerWidth / window.innerHeight,
nearDist,
farDist
);
camera.position.x = farDist * -2;
camera.position.z = 500;
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setClearColor("#e8a82b");
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
document.querySelector("#canvas-wrapper").appendChild(renderer.domElement);
// Object
const cubeSize = 300;
const geometry = new THREE.IcosahedronGeometry(cubeSize, 0 ); // BufferAttribute allows for more efficient passing of data to the GPU
const material = new THREE.MeshNormalMaterial({roughness: 1});
const group = new THREE.Group();
for (let i = 0; i < 260; i++) {
const mesh = new THREE.Mesh(geometry, material);
const dist = farDist / 3;
const distDouble = dist * 2;
const tau = 2 * Math.PI; // One turn
mesh.position.x = Math.random() * distDouble - dist;
mesh.position.y = Math.random() * distDouble - dist;
mesh.position.z = Math.random() * distDouble - dist;
mesh.rotation.x = Math.random() * tau;
mesh.rotation.y = Math.random() * tau;
mesh.rotation.z = Math.random() * tau;
// Manually control when 3D transformations recalculation occurs for better performance
mesh.matrixAutoUpdate = false;
mesh.updateMatrix();
group.add(mesh);
}
scene.add(group);
// Typo
const loader = new THREE.FontLoader();
const textMesh = new THREE.Mesh();
const createTypo = font => {
const word = "be creative";
const typoProperties = {
font: font,
size: cubeSize,
height: cubeSize / 2,
curveSegments: 16
};
const text = new THREE.TextGeometry(word, typoProperties);
textMesh.geometry = text;
textMesh.material = material;
textMesh.position.x = cubeSize * -2;
textMesh.position.z = cubeSize * -1;
scene.add(textMesh);
};
loader.load(
"https://threejs.org/examples/fonts/helvetiker_regular.typeface.json",
createTypo
);
// Mouse move
let mouseX = 0;
let mouseY = 0;
const mouseFX = {
windowHalfX: window.innerWidth / 2,
windowHalfY: window.innerHeight / 2,
coordinates: function(coordX, coordY) {
mouseX = (coordX - mouseFX.windowHalfX) * 2;
mouseY = (coordY - mouseFX.windowHalfY) * 2;
},
onMouseMove: function(e) {
mouseFX.coordinates(e.clientX, e.clientY);
},
onTouchMove: function(e) {
mouseFX.coordinates(e.changedTouches[0].clientX, e.changedTouches[0].clientY);
}
};
document.addEventListener("mousemove", mouseFX.onMouseMove, false);
document.addEventListener("touchmove", mouseFX.onTouchMove, false);
// Render
const render = () => {
requestAnimationFrame(render);
// Camera animation
camera.position.x += (mouseX - camera.position.x) * 0.05;
camera.position.y += (mouseY * -1 - camera.position.y) * 0.05;
camera.lookAt(scene.position);
const t = Date.now() * 0.001;
const rx = Math.sin(t * 0.6) * 0.5;
const ry = Math.sin(t * 0.3) * 0.5;
const rz = Math.sin(t * 0.2) * 0.5;
group.rotation.x = rx;
group.rotation.y = ry;
group.rotation.z = rz;
textMesh.rotation.x = rx;
textMesh.rotation.y = ry;
textMesh.rotation.z = rx; // :)
renderer.render(scene, camera);
};
render();
const resizeCanvas = () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
};
window.addEventListener("resize", resizeCanvas, false);
I tried to implement threeVR library but without any luck. But since this is my first three.js prject i belive i did it something wrong. I couldnt find any other solution for this.
Thanks for any ideas.

Related

How to get a random Vector3 within a sphere?

I am trying to create a random point within a sphere, and I am not sure how to do this. I came up with this but I think it is returning a point within a cube I think I have to do something with Math.PI but not sure how.
#createParticlePosition() {
const shape = this.options.shape;
// shape.radius = 2;
if (shape.type === 'sphere') {
return new Three.Vector3(
(Math.random() * shape.radius - (shape.radius / 2)) * 1.0,
(Math.random() * shape.radius - (shape.radius / 2)) * 1.0,
(Math.random() * shape.radius - (shape.radius / 2)) * 1.0
);
}
}
You are indeed just creating boxes. You're only calculating the x,y,z values linearly, not spherically. Three.js has a Vector3.randomDirection method that could do these calculations for you:
const maxRadius = 2;
// Randomize to range [0, 2]
const randomRadius = Math.random() * maxRadius;
// Create vec3
const randomVec = new THREE.Vector3();
// Make vector point in a random direction with a radius of 1
randomVec.randomDirection();
// Scale vector to match random radius
randomVec.multiplyScalar(randomRadius);
This method utilizes this approach internally to avoid density accumulation in the poles.
To distribute points inside a sphere evenly, having direction and radius, the radius computes with Math.sqrt( r * r * Math.random());
In the snippet, the red point cloud utilizes simple r * Math.random(), the aqua one utilizes that I wrote above:
body{
overflow: hidden;
margin: 0;
}
<script type="module">
import * as THREE from "https://cdn.skypack.dev/three#0.136.0";
import {OrbitControls} from "https://cdn.skypack.dev/three#0.136.0/examples/jsm/controls/OrbitControls";
let scene = new THREE.Scene();
scene.background = new THREE.Color(0x160016);
let camera = new THREE.PerspectiveCamera(45, innerWidth / innerHeight, 1, 1000);
camera.position.set(0, 0, 8);
let renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setSize(innerWidth, innerHeight);
document.body.appendChild(renderer.domElement);
window.addEventListener("resize", event => {
camera.aspect = innerWidth / innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(innerWidth, innerHeight);
})
let controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.enablePan = false;
let pts = new Array(1000).fill().map(p => {
let rMax = 2 * Math.random();
return new THREE.Vector3().randomDirection().multiplyScalar(rMax);
})
let g = new THREE.BufferGeometry().setFromPoints(pts);
let m = new THREE.PointsMaterial({size: 0.1, color: "red"})
let p = new THREE.Points(g, m);
p.position.x = -2;
scene.add(p)
let pts2 = new Array(1000).fill().map(p => {
let rMax = 2;
let r = Math.sqrt(rMax * rMax * Math.random());
return new THREE.Vector3().randomDirection().multiplyScalar(r);
})
let g2 = new THREE.BufferGeometry().setFromPoints(pts2);
let m2 = new THREE.PointsMaterial({size: 0.1, color: "aqua"})
let p2 = new THREE.Points(g2, m2);
p2.position.x = 2;
scene.add(p2)
renderer.setAnimationLoop(() => {
controls.update();
renderer.render(scene, camera);
});
</script>
For reference: https://discourse.threejs.org/t/random-points-on-surfaces/34153

Why are all particles positioned in the center on x axis in THREE.JS

I'm new to THREE.JS and I'm trying to figure out how to make particle system and I can't get it to work properly. As title says all of the particles are positioned in the center on X axis, it seems that Y and Z are ok.
Picture of the result: https://i.stack.imgur.com/xUuAn.png
What I want to achieve: https://i.stack.imgur.com/vA0tL.jpg
Code:
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(
75,
window.innerWidth - 10 / window.innerHeight,
1,
1000
);
camera.position.z = 300;
const ambientLight = new THREE.AmbientLight(
0xFFFFFF
);
const particleBufferGeometry = new THREE.BufferGeometry();
const positionArray = [];
for (let i = 0; i < 10000; i++) {
positionArray.push((Math.random() * 2 - 1) * 200);
positionArray.push((Math.random() * 2 - 1) * 200);
positionArray.push((Math.random() * 2 - 1) * 200);
}
particleBufferGeometry.setAttribute("position", new THREE.Float32BufferAttribute(positionArray, 3));
const particlePointsMaterial = new THREE.PointsMaterial({
size: 0.1
});
const particlePoints = new THREE.Points(particleBufferGeometry, particlePointsMaterial);
const renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true,
canvas: canvasRef.current!
});
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setClearColor(0xFFFFFF, 0);
renderer.setSize(
window.innerWidth - 10,
window.innerHeight
);
scene.add(ambientLight, particlePoints);
renderer.render(scene, camera);
The error occurs when you initialize your camera. Your aspect ratio is
window.innerWidth - 10 / window.innerHeight
Example:
1920 - 10 / 1080 = 1919.99 (wrong aspect ratio)
but due to the order of operations, it's calculating the division first so 10 / height happens before the subtraction. Just make sure you use parentheses correctly and the problem will be solved:
(window.innerWidth - 10) / window.innerHeight
Example: (1920 - 10) / 1080 = 1.76 (Correct aspect ratio)
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(
45,
(window.innerWidth - 10) / window.innerHeight,
1,
1000
);
camera.position.z = 300;
const particleBufferGeometry = new THREE.BufferGeometry();
const positionArray = [];
for (let i = 0; i < 10000; i++) {
positionArray.push((Math.random() * 2 - 1) * 200);
positionArray.push((Math.random() * 2 - 1) * 200);
positionArray.push((Math.random() * 2 - 1) * 200);
}
particleBufferGeometry.setAttribute("position", new THREE.Float32BufferAttribute(positionArray, 3));
const particlePointsMaterial = new THREE.PointsMaterial({
size: 0.1
});
const particlePoints = new THREE.Points(particleBufferGeometry, particlePointsMaterial);
const canvasRef = document.querySelector("#canvas");
const renderer = new THREE.WebGLRenderer({
antialias: true,
canvas: canvasRef
});
renderer.setSize(window.innerWidth - 10, window.innerHeight);
scene.add(particlePoints);
function animate() {
particlePoints.rotation.y += 0.01;
renderer.render(scene, camera);
requestAnimationFrame(animate);
}
animate();
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r126/three.min.js"></script>
<canvas id="canvas"></canvas>

How to move camera back on canvas

I am looking for a way to move the camera back with the code that I have here. Does anyone have any suggestions?
I have adjusted camera.position.set and added camera.position.x parameters.
function main() {
const canvas = document.querySelector('#c');
const renderer = new THREE.WebGLRenderer({
canvas
});
const fov = 45;
const aspect = 2;
const near = 0.1;
const far = 100;
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
camera.position.set(0, 10, 20);
const controls = new THREE.OrbitControls(camera, canvas);
controls.target.set(0, 5, 0);
controls.update();
const scene = new THREE.Scene();
scene.background = new THREE.Color('black'); {
const planeSize = 0;
const loader = new THREE.TextureLoader();
const texture = loader.load('');
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
texture.magFilter = THREE.NearestFilter;
const repeats = planeSize / 2;
texture.repeat.set(repeats, repeats);
const planeGeo = new THREE.PlaneBufferGeometry(planeSize, planeSize);
const planeMat = new THREE.MeshPhongMaterial({
map: texture,
side: THREE.DoubleSide,
});
const mesh = new THREE.Mesh(planeGeo, planeMat);
mesh.rotation.x = Math.PI * -.5;
scene.add(mesh);
}
function frameArea(sizeToFitOnScreen, boxSize, boxCenter, camera) {
const halfSizeToFitOnScreen = sizeToFitOnScreen * 0.5;
const halfFovY = THREE.Math.degToRad(camera.fov * .5);
const distance = halfSizeToFitOnScreen / Math.tan(halfFovY);
const direction = (new THREE.Vector3())
.subVectors(camera.position, boxCenter)
.multiply(new THREE.Vector3(1, 0, 1))
.normalize();
camera.near = boxSize / 100;
camera.far = boxSize * 100;
camera.updateProjectionMatrix();
camera.lookAt(boxCenter.x, boxCenter.y, boxCenter.z);
}
const box = new THREE.Box3().setFromObject(root);
const boxSize = box.getSize(new THREE.Vector3()).length();
const boxCenter = box.getCenter(new THREE.Vector3());
frameArea(boxSize * 0.5, boxSize, boxCenter, camera);
controls.maxDistance = boxSize * 10;
controls.target.copy(boxCenter);
controls.update();
});
}
function resizeRendererToDisplaySize(renderer) {
const canvas = renderer.domElement;
const width = canvas.clientWidth;
const height = canvas.clientHeight;
const needResize = canvas.width !== width || canvas.height !== height;
if (needResize) {
renderer.setSize(width, height, false);
}
return needResize;
}
function render() {
if (resizeRendererToDisplaySize(renderer)) {
const canvas = renderer.domElement;
camera.aspect = canvas.clientWidth / canvas.clientHeight;
camera.updateProjectionMatrix();
}
renderer.render(scene, camera);
The answer to this question has been resolved.
I needed to increase the box size using this line of code:
frameArea(boxSize * 0.5, boxSize, boxCenter, camera);
The equations are based off of SOHCAHTOA. I needed to compute the distance and then move the camera that distance from the center of the box.

Three.js OrbitControls with azimuth close to +/-180 degrees

I have a situation where I use an OrbitControls to have a limited view in a room/space. The OrbitControls gets min/max values for azimuth and polar angles to control the view.
The OrbitControls' target is close by the camera to achieve the correct view, compare to the "panorama / cube" example (https://threejs.org/examples/?q=cube#webgl_panorama_cube).
In one situation this works fine. In an other situation this does not work. The reason is that because of the placement of objects, the starting azimuth is -179.999 degrees.
I have not found a way to tell the orbitcontrol to have the azimuth between -179.999 +/- 20 degrees.
I have experimented a little with the algorithm of #Αλέκος (Is angle in between two angles) and it looks like I can calculate the correct angles (not fully implemented). For that I would have set an azimuth and a delta in stead of min/max (and change the OrbitControls code).
Any suggestions for an easier solution? Thanks!
Test code:
var scene = new THREE.Scene();
var orbitControls;
var camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 1000);
var renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var material = new THREE.MeshBasicMaterial({
color: 0xffffff,
vertexColors: THREE.FaceColors
});
var geometry = new THREE.BoxGeometry(1, 1, 1);
// colors
red = new THREE.Color(1, 0, 0);
green = new THREE.Color(0, 1, 0);
blue = new THREE.Color(0, 0, 1);
var colors = [red, green, blue];
console.log("FACES", geometry.faces.length)
for (var i = 0; i < 3; i++) {
geometry.faces[4 * i].color = colors[i];
geometry.faces[4 * i + 1].color = colors[i];
geometry.faces[4 * i + 2].color = colors[i];
geometry.faces[4 * i + 3].color = colors[i];
}
geometry.faces[0].color = new THREE.Color(1, 1, 1);
geometry.faces[4].color = new THREE.Color(1, 1, 1);
geometry.faces[8].color = new THREE.Color(1, 1, 1);
var cube = new THREE.Mesh(geometry, material);
cube.position.x = 0;
cube.position.y = 4;
cube.position.z = 44;
console.log("CUBE", cube.position);
var cubeAxis = new THREE.AxesHelper(20);
cube.add(cubeAxis);
scene.add(cube);
camera.position.x = 0.8;
camera.position.y = 4.5;
camera.position.z = 33;
orbitControls = new THREE.OrbitControls(camera, renderer.domElement);
orbitControls.enablePan = false;
orbitControls.enableZoom = false;
orbitControls.minPolarAngle = (90 - 10) * Math.PI / 180;
orbitControls.maxPolarAngle = (90 + 10) * Math.PI / 180;
// These values do not work:
orbitControls.maxAzimuthAngle = 200 * Math.PI / 180;
orbitControls.minAzimuthAngle = 160 * Math.PI / 180;
orbitControls.target.x = 0.8;
orbitControls.target.y = 4.5;
orbitControls.target.z = 33.1;
orbitControls.enabled = true;
var animate = function () {
requestAnimationFrame(animate);
if (orbitControls) {
orbitControls.update();
}
renderer.render(scene, camera);
// console.log("orbitControls", "azi", orbitControls.getAzimuthalAngle() * 180 / Math.PI);
};
animate();

Draw Point GeoJSON in Three.js and D3.js

I want to overlay points from a GeoJSON file onto the surface of my Mars basemap using D3.js, similar to what Mike Bostock has done with a MultiLineString, as well as match the rotation angle and speed:
https://bl.ocks.org/mbostock/2b85250396c17a79155302f91ec21224
I am completely new to Three.js, but have been learning D3.js, so I am not sure how to mix the two together. Any help would be much appreciated.
var renderer, scene, camera;
var control;
var stats;
var cameraControl;
var radius = 15;
// Initialize the scene, camera and objects.
function init() {
// To display anything, you need 3 things: (1) Scene, (2) Camera, (3) Renderer
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
renderer = new THREE.WebGLRenderer();
renderer.setClearColor(0x000000, 1.0);
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMapEnabled = true;
// Mars needs (1) geometry, (2) material, (3) mesh
var sphereGeometry = new THREE.SphereGeometry(15, 60, 60);
var sphereMaterial = createMarsMaterial();
var marsMesh = new THREE.Mesh(sphereGeometry, sphereMaterial);
marsMesh.name = 'mars';
scene.add(marsMesh);
// position and point the camera to the center of the scene
camera.position.x = 25;
camera.position.y = 26;
camera.position.z = 23;
camera.lookAt(scene.position);
// add controls
cameraControl = new THREE.OrbitControls(camera);
// setup the control object for the control gui
control = new function () {
this.rotationSpeed = 0.001;
};
// add extras
addControlGui(control);
addStatsObject();
// add the output of the renderer to the html element
document.body.appendChild(renderer.domElement);
// start animating
render();
}
function createMarsMaterial() {
// 4096 is the maximum width for maps
var marsTexture = THREE.ImageUtils;
marsTexture.crossOrigin = "";
marsTexture = THREE.ImageUtils.loadTexture("https://tatornator12.github.io/classes/final-project/Using_Three_-_D3/mars.jpg");
var marsMaterial = new THREE.MeshBasicMaterial();
marsMaterial.map = marsTexture;
return marsMaterial;
}
function addControlGui(controlObject) {
var gui = new dat.GUI();
gui.add(controlObject, 'rotationSpeed', -0.01, 0.01);
}
function addStatsObject() {
stats = new Stats();
stats.setMode(0);
stats.domElement.style.position = 'absolute';
stats.domElement.style.left = '0px';
stats.domElement.style.top = '0px';
document.body.appendChild(stats.domElement);
}
function render() {
stats.update();
cameraControl.update();
scene.getObjectByName('mars').rotation.y += control.rotationSpeed;
renderer.render(scene, camera);
requestAnimationFrame(render);
}
function handleResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
window.onload = init;
window.addEventListener('resize', handleResize, false);
d3.json("https://tatornator12.github.io/classes/final-project/Mars_LandingSites.json", function(error, topology) {
if (error) throw error;
scene.add(graticule = wireframe(graticule10(), new THREE.LineBasicMaterial({color: 0xaaaaaa})));
scene.add(mesh = wireframe(topojson.mesh(topology, topology.features), new THREE.LineBasicMaterial({color: 0xff0000})));
d3.timer(function(t) {
graticule.rotation.x = mesh.rotation.x = Math.sin(t / 11000) * Math.PI / 3 - Math.PI / 2;
graticule.rotation.z = mesh.rotation.z = t / 10000;
renderer.render(scene, camera);
});
});
// Converts a point [longitude, latitude] in degrees to a THREE.Vector3.
function vertex(point) {
var lambda = point[0] * Math.PI / 180,
phi = point[1] * Math.PI / 180,
cosPhi = Math.cos(phi);
return new THREE.Vector3(
radius * cosPhi * Math.cos(lambda),
radius * cosPhi * Math.sin(lambda),
radius * Math.sin(phi)
);
}
// Converts a GeoJSON MultiLineString in spherical coordinates to a THREE.LineSegments.
function wireframe(multilinestring, material) {
var geometry = new THREE.Geometry;
multilinestring.coordinates.forEach(function(line) {
d3.pairs(line.map(vertex), function(a, b) {
geometry.vertices.push(a, b);
});
});
return new THREE.LineSegments(geometry, material);
}
// See https://github.com/d3/d3-geo/issues/95
function graticule10() {
var epsilon = 1e-6,
x1 = 180, x0 = -x1, y1 = 80, y0 = -y1, dx = 10, dy = 10,
X1 = 180, X0 = -X1, Y1 = 90, Y0 = -Y1, DX = 90, DY = 360,
x = graticuleX(y0, y1, 2.5), y = graticuleY(x0, x1, 2.5),
X = graticuleX(Y0, Y1, 2.5), Y = graticuleY(X0, X1, 2.5);
function graticuleX(y0, y1, dy) {
var y = d3.range(y0, y1 - epsilon, dy).concat(y1);
return function(x) { return y.map(function(y) { return [x, y]; }); };
}
function graticuleY(x0, x1, dx) {
var x = d3.range(x0, x1 - epsilon, dx).concat(x1);
return function(y) { return x.map(function(x) { return [x, y]; }); };
}
return {
type: "MultiPoint",
coordinates: d3.range(Math.ceil(X0 / DX) * DX, X1, DX).map(X)
.concat(d3.range(Math.ceil(Y0 / DY) * DY, Y1, DY).map(Y))
.concat(d3.range(Math.ceil(x0 / dx) * dx, x1, dx).filter(function(x) { return Math.abs(x % DX) > epsilon; }).map(x))
.concat(d3.range(Math.ceil(y0 / dy) * dy, y1 + epsilon, dy).filter(function(y) { return Math.abs(y % DY) > epsilon; }).map(y))
};
}
body {
/* set margin to 0 and overflow to hidden, to go fullscreen */
margin: 0;
overflow: hidden;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/topojson.v2.min.js"></script>
<script src="https://tatornator12.github.io/classes/final-project/Using_Three_-_D3/three.js"></script>
<script src="https://tatornator12.github.io/classes/final-project/Using_Three_-_D3/OrbitControls.js"></script>
<script src="https://tatornator12.github.io/classes/final-project/Using_Three_-_D3/dat.gui.min.js"></script>
<script src="https://tatornator12.github.io/classes/final-project/Using_Three_-_D3/stats.min.js"></script>

Categories

Resources