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.
Related
I'm trying customizing mouse cursor javascript on my own html but it doesn't work well.
This is javascript code and I get it from "https://codepen.io/dlch/pen/eWXgyo"
var camera, renderer, scene, particleSystem, baseParticle, mouse;
window.onload = function () {
mouse = [window.innerWidth / 2, window.innerHeight / 2];
renderer = new THREE.WebGLRenderer({ antialias: true });
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(20, window.innerWidth / window.innerHeight, 0.1, 1000);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
camera.position.z = 50;
scene.background = new THREE.Color(0x333344);
canvas = document.querySelector('#b canvas');
baseParticle = new THREE.PlaneGeometry(1, 1, 1);
baseParticle.applyMatrix(new THREE.Matrix4().makeRotationFromEuler(new THREE.Euler(0, 0, Math.PI / 4)));
for (var i = 0; i < baseParticle.vertices.length; i++) {
if (Math.round(baseParticle.vertices[i].y) != 0) {
baseParticle.vertices[i].x = 0;
baseParticle.vertices[i].z = 0;
}
}
baseParticle.mergeVertices();
baseParticle.verticesNeedUpdate = true;
baseParticle = new THREE.Mesh(baseParticle, new THREE.MeshBasicMaterial({ color: 0xffffff, emissive: 0x555555 }));
particleSystem = new ParticleSystem(99);
render();
};
window.onresize = function () {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
};
window.onmousemove = function (e) {
mouse = [e.clientX, e.clientY];
};
function randomFloat(a, b) {
var r = Math.random() * (b - a) + a;
return r;
}
function partToHex(part) {
var h = part.toString(16);
return h.length == 1 ? "0" + h : h;
}
console.log(partToHex(255));
var color;
function FireParticle() {
this.direction;
this.scaleSpeed;
this.curAge;
this.parent;
this.obj;
this.colorRamp = [[255, 255, 0], [255, 136, 34], [255, 17, 68], [153, 136, 136]];
this.update = function () {
if (Math.abs(this.parent.pos.x - this.obj.position.x) > 10 || Math.abs(-this.parent.pos.y - this.obj.position.y) > 10) {
this.obj.scale.x *= .8;
this.obj.scale.y *= .8;
this.obj.scale.z *= .8;
}
var point = this.curAge / 40;
var pointRem = point % 1;
if (Math.round(point) >= this.colorRamp.length - 1) {
color = this.colorRamp[this.colorRamp.length - 1];
} else {
color = [Math.floor(this.colorRamp[Math.floor(point)][0] * (1 - pointRem) + this.colorRamp[Math.floor(point) + 1][0] * pointRem), Math.floor(this.colorRamp[Math.floor(point)][1] * (1 - pointRem) + this.colorRamp[Math.floor(point) + 1][1] * pointRem), Math.floor(this.colorRamp[Math.floor(point)][2] * (1 - pointRem) + this.colorRamp[Math.floor(point) + 1][2] * pointRem)];
}
color = partToHex(color[0]) + partToHex(color[1]) + partToHex(color[2]);
color = parseInt(color, 16);
this.obj.material.color.setHex(color);
this.curAge++;
if (this.obj.scale.x < .01) {
this.init();
}
this.obj.position.x += this.direction.x;
this.obj.position.y += this.direction.y;
this.obj.position.z += this.direction.z;
this.obj.scale.x *= this.scaleSpeed;
this.obj.scale.y *= this.scaleSpeed;
this.obj.scale.z *= this.scaleSpeed;
};
this.init = function () {
this.direction = new THREE.Vector3(randomFloat(-.01, .01), randomFloat(.01, .1), randomFloat(-.01, .01));
this.scaleSpeed = randomFloat(.8, .99);
this.curAge = 0;
if (this.obj != undefined) {
scene.remove(this.obj);
}
this.obj = baseParticle.clone();
this.obj.position.set(this.parent.obj.position.x + randomFloat(-.2, .2), this.parent.obj.position.y, this.parent.obj.position.z + randomFloat(-.2, .2));
this.obj.scale.set(1, 2, 1);
this.obj.material = this.obj.material.clone();
// var size = randomFloat(.5, 1);
// this.obj.scale.set(size, 2*size, size);
for (var i = 0; i < randomFloat(0, 100); i++) {
this.update();
}
scene.add(this.obj);
};
}
function ParticleSystem(size) {
this.particles = [];
this.obj = new THREE.Group();
this.p = new THREE.Vector3();
this.d;
this.dis;
this.pos = new THREE.Vector3(0, 0, 0);
this.init = function () {
for (var i = 0; i < size; i++) {
this.particles.push(new FireParticle());
this.particles[i].parent = this;
this.particles[i].init();
}
scene.add(this.obj);
};
this.init();
this.update = function () {
this.p.set(mouse[0] / window.innerWidth * 2 - 1, mouse[1] / window.innerHeight * 2 - 1, .5);
this.p.unproject(camera);
this.d = this.p.sub(camera.position).normalize();
this.dis = -camera.position.z / this.d.z;
this.pos = camera.position.clone().add(this.d.multiplyScalar(this.dis));
this.obj.position.x = this.pos.x;
this.obj.position.y = -this.pos.y;
for (var i = 0; i < this.particles.length; i++) {
this.particles[i].update();
}
this.obj.rotation.y += .03;
};
}
function render() {
requestAnimationFrame(render);
renderer.render(scene, camera);
particleSystem.update();
}
It just come out bottom of my html surrounded code author's background which is grey, not inside my html.
This particle fire mouse cursor doesn't show when it get out of code author's background.
Could you help me how to solve this problem?
I want to remove author's background and only want to use mouse cursor in my own html.
Please help me.
I'm trying to achieve something akin to this amazing effect : https://www.cobosrl.co/
Here's what I have so far : https://codepen.io/routsou/pen/ZEGWJgR?editors=0010
/*--------------------
Setup
--------------------*/
console.clear();
const canvas = document.querySelector('#bubble');
//wobble
let mouseDown = false;
let howMuch = 0;
let howMuchLimit = 0.25;
//ripple
let rippleAmount = 0;
let rippleRatio = 5;
let step = 0;
let sphereVerticesArray = [];
let sphereVerticesNormArray = [];
//raycaster
let raycaster;
let INTERSECTED = null;
let width = canvas.offsetWidth,
height = canvas.offsetHeight;
const renderer = new THREE.WebGLRenderer({
canvas: canvas,
antialias: true,
alpha: true
});
const scene = new THREE.Scene();
const setup = () => {
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize(width, height);
renderer.setClearColor(0xebebeb, 0);
renderer.shadowMap.enabled = true;
renderer.shadowMapSoft = true;
scene.fog = new THREE.Fog(0x000000, 10, 950);
const aspectRatio = width / height;
const fieldOfView = 100;
const nearPlane = 0.1;
const farPlane = 10000;
camera = new THREE.PerspectiveCamera(
fieldOfView,
aspectRatio,
nearPlane,
farPlane
);
raycaster = new THREE.Raycaster();
camera.position.x = 0;
camera.position.y = 0;
camera.position.z = 300;
}
setup();
/*--------------------
Lights
--------------------*/
let hemispshereLight, shadowLight, light2;
const createLights = () => {
hemisphereLight = new THREE.HemisphereLight(0xffffff,0x000000, .5)
shadowLight = new THREE.DirectionalLight(0x666666, .4);
shadowLight.position.set(0, 450, 350);
shadowLight.castShadow = true;
shadowLight.shadow.camera.left = -650;
shadowLight.shadow.camera.right = 650;
shadowLight.shadow.camera.top = 650;
shadowLight.shadow.camera.bottom = -650;
shadowLight.shadow.camera.near = 1;
shadowLight.shadow.camera.far = 1000;
shadowLight.shadow.mapSize.width = 4096;
shadowLight.shadow.mapSize.height = 4096;
light2 = new THREE.DirectionalLight(0x666666, .25);
light2.position.set(-600, 350, 350);
light3 = new THREE.DirectionalLight(0x666666, .15);
light3.position.set(0, -250, 300);
scene.add(hemisphereLight);
scene.add(shadowLight);
scene.add(light2);
scene.add(light3);
}
createLights();
/*--------------------
Bubble
--------------------*/
const vertex = width > 575 ? 80 : 40;
const bubbleGeometry = new THREE.SphereGeometry( 150, vertex, vertex );
let bubble;
const createBubble = () => {
for(let i = 0; i < bubbleGeometry.vertices.length; i++) {
let vector = bubbleGeometry.vertices[i];
vector.original = vector.clone();
}
const bubbleMaterial = new THREE.MeshStandardMaterial({
emissive: 0x91176b,
emissiveIntensity: 0.85,
roughness: 0.55,
metalness: 0.51,
side: THREE.FrontSide,
});
// save points for later calculation
for (var i = 0; i < bubbleGeometry.vertices.length; i += 1) {
var vertex = bubbleGeometry.vertices[i];
var vec = new THREE.Vector3(vertex.x, vertex.y, vertex.z);
sphereVerticesArray.push(vec);
var mag = vec.x * vec.x + vec.y * vec.y + vec.z * vec.z;
mag = Math.sqrt(mag);
var norm = new THREE.Vector3(vertex.x / mag, vertex.y / mag, vertex.z / mag);
sphereVerticesNormArray.push(norm);
}
bubble = new THREE.Mesh(bubbleGeometry, bubbleMaterial);
bubble.castShadow = true;
bubble.receiveShadow = false;
bubble.rotation.y = -90;
scene.add(bubble);
}
createBubble();
/*--------------------
Plane
--------------------*/
const createPlane = () => {
const planeGeometry = new THREE.PlaneBufferGeometry( 2000, 2000 );
const planeMaterial = new THREE.ShadowMaterial({
opacity: 0.15
});
const plane = new THREE.Mesh( planeGeometry, planeMaterial );
plane.position.y = -150;
plane.position.x = 0;
plane.position.z = 0;
plane.rotation.x = Math.PI / 180 * -90;
plane.receiveShadow = true;
scene.add(plane);
}
createPlane();
/*--------------------
Map
--------------------*/
const map = (num, in_min, in_max, out_min, out_max) => {
return (num - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}
/*--------------------
Distance
--------------------*/
const distance = (a, b) => {
const dx = a.x - b.x;
const dy = a.y - b.y;
const d = Math.sqrt( dx * dx + dy * dy );
return d;
}
/*--------------------
Mouse
--------------------*/
let mouse = new THREE.Vector2(0, 0);
const onMouseMove = (e) => {
TweenMax.to(mouse, 0.8, {
x : ( e.clientX / window.innerWidth ) * 2 - 1,
y: - ( e.clientY / window.innerHeight ) * 2 + 1,
ease: Power2.easeOut
});
raycaster.setFromCamera( mouse, camera );
let intersects = raycaster.intersectObjects( scene.children );
try{
if ( intersects.length > 0 ) {
if ( INTERSECTED != intersects[ 0 ].object ) {
if ( INTERSECTED ) INTERSECTED.material.emissive.setHex( INTERSECTED.currentHex );
INTERSECTED = intersects[ 0 ].object;
INTERSECTED.currentHex = INTERSECTED.material.emissive.getHex();
INTERSECTED.material.emissive.setHex( 0x000000 );
document.body.style.cursor = 'pointer';
}
} else {
if ( INTERSECTED ) INTERSECTED.material.emissive.setHex( INTERSECTED.currentHex );
INTERSECTED = null;
document.body.style.cursor = 'auto';
}
}catch(e){
}
};
['mousemove', 'touchmove'].forEach(event => {
window.addEventListener(event, onMouseMove);
});
/*--------------------
Spring
--------------------*/
let spring = {
scale: 1
};
const clicking = {
down: () => {
mouseDown = true;
},
up: () => {
mouseDown = false;
}
};
['mousedown', 'touchstart'].forEach(event => {
window.addEventListener(event, clicking.down);
});
['mouseup', 'touchend'].forEach(event => {
window.addEventListener(event, clicking.up);
});
/*--------------------
Resize
--------------------*/
const onResize = () => {
canvas.style.width = '';
canvas.style.height = '';
width = canvas.offsetWidth;
height = canvas.offsetHeight;
camera.aspect = width / height;
camera.updateProjectionMatrix();
maxDist = distance(mouse, {x: width / 2, y: height / 2});
renderer.setSize(width, height);
}
let resizeTm;
window.addEventListener('resize', function(){
resizeTm = clearTimeout(resizeTm);
resizeTm = setTimeout(onResize, 200);
});
/*--------------------
Noise
--------------------*/
let dist = new THREE.Vector2(0, 0);
let maxDist = distance(mouse, {x: width / 2, y: height / 2});
const updateVertices = (time) => {
dist = distance(mouse, {x: width / 2, y: height / 2});
dist /= maxDist;
dist = map(dist, 1, 0, 0, 1);
for(let i = 0; i < bubbleGeometry.vertices.length; i++) {
let vector = bubbleGeometry.vertices[i];
vector.copy(vector.original);
let perlin = noise.simplex3(
(vector.x * 0.006) + (time * 0.0005),
(vector.y * 0.006) + (time * 0.0005),
(vector.z * 0.006)
);
let ratio = ((perlin * 0.3 * (howMuch + 0.1)) + 0.9);
vector.multiplyScalar(ratio);
}
bubbleGeometry.verticesNeedUpdate = true;
}
/*--------------------
Animate
--------------------*/
const render = (a) => {
step +=1;
requestAnimationFrame(render);
//bubble.scale.set(spring.scale, spring.scale, spring.scale);
updateVertices(a);
renderer.clear();
renderer.render(scene, camera);
//Activate on mouse down
if(mouseDown && howMuch < howMuchLimit)
howMuch += 0.01;
else if (howMuch > 0)
howMuch -= 0.01;
if(INTERSECTED){
if(rippleAmount < 10)
rippleAmount += 0.05;
}else if(rippleAmount > 0)
rippleAmount -= 0.05;
doRipple();
}
requestAnimationFrame(render);
renderer.render(scene, camera);
/*--------------------
Helpers
--------------------*/
function fbm(p) {
var result = noise.simplex3(p._x, p._y, p._z);
return result;
}
function addPoint(arr) {
var r = new Point(0, 0, 0);
var len = arr.length;
for (var i = 0; i < len; i += 1) {
r._x += arr[i]._x;
r._y += arr[i]._y;
r._z += arr[i]._z;
}
return r;
}
function Point(_x=0, _y=0, _z=0) {
this._x = _x;
this._y = _y;
this._z = _z;
}
function ripple(p) {
var q = new Point(fbm(addPoint([p, new Point(0, 0, 0)])),
fbm(addPoint([p, new Point(0, 1, 0)])),
fbm(addPoint([p, new Point(0, 0, 1)])));
return fbm(addPoint([p, new Point(0.5 * q._x, 0.5 * q._y, 0.5 * q._z)]));
}
function doRipple(){
//ripple
for (var i = 0; i < bubbleGeometry.vertices.length; i += 1) {
var vertex = bubbleGeometry.vertices[i];
// var value = pn.noise((vertex.x + step)/ 10, vertex.y / 10, vertex.z / 10);
var value = ripple(new Point((vertex.x + step) / 100.0), vertex.y / 100.0, vertex.z / 100.0);
vertex.x = sphereVerticesArray[i].x + sphereVerticesNormArray[i].x * value * rippleAmount;
vertex.y = sphereVerticesArray[i].y + sphereVerticesNormArray[i].y * value * rippleAmount;
vertex.z = sphereVerticesArray[i].z + sphereVerticesNormArray[i].z * value * rippleAmount;
}
bubbleGeometry.computeFaceNormals();
bubbleGeometry.computeVertexNormals();
bubbleGeometry.verticesNeedUpdate = true;
bubbleGeometry.normalsNeedUpdate = true;
}
Any help, particularly about the mouse pointer "sculpting the geometry", and the waves being more natural and from the pointer?
Thank you very much in advance
I've investigated and found you're intersecting with all children (6) in the scene, including the bubble shadow and the lights. The shadow seems to also intersect with the mouse triggering a false contact.
About "sculpting the geometry" I noticed you hardcode the ripple effect from one specific point of the bubble during initial construction and that's why the sculpting effect is always from that same point. This is my recommendation:
Remove the hard-coded sphereVerticesArray and sphereVerticesNormArray.
After computing the intersection with the mouse, find out the face of the bubble getting hit: intersections[0].point provides the point of intersection, in world coordinates. Use this to find out the face of contact.
During ripple effect use the normal of the contact face as starting point and orientation of the ripple.
This is the code to fix the shadow intersection issue including some comments:
/*--------------------
Setup
--------------------*/
console.clear();
const canvas = document.querySelector('#bubble');
//wobble
let mouseDown = false;
let howMuch = 0;
let howMuchLimit = 0.25;
//ripple
let rippleAmount = 0;
let rippleRatio = 5;
let step = 0;
let sphereVerticesArray = [];
let sphereVerticesNormArray = [];
//raycaster
let raycaster;
let isIntersectingWithBubble = false;
let width = canvas.offsetWidth,
height = canvas.offsetHeight;
const renderer = new THREE.WebGLRenderer({
canvas: canvas,
antialias: true,
alpha: true
});
const scene = new THREE.Scene();
const setup = () => {
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize(width, height);
renderer.setClearColor(0xebebeb, 0);
renderer.shadowMap.enabled = true;
renderer.shadowMapSoft = true;
scene.fog = new THREE.Fog(0x000000, 10, 950);
const aspectRatio = width / height;
const fieldOfView = 100;
const nearPlane = 0.1;
const farPlane = 10000;
camera = new THREE.PerspectiveCamera(
fieldOfView,
aspectRatio,
nearPlane,
farPlane
);
raycaster = new THREE.Raycaster();
camera.position.x = 0;
camera.position.y = 0;
camera.position.z = 300;
}
setup();
/*--------------------
Lights
--------------------*/
let hemispshereLight, shadowLight, light2;
const createLights = () => {
hemisphereLight = new THREE.HemisphereLight(0xffffff,0x000000, .5)
shadowLight = new THREE.DirectionalLight(0x666666, .4);
shadowLight.position.set(0, 450, 350);
shadowLight.castShadow = true;
shadowLight.shadow.camera.left = -650;
shadowLight.shadow.camera.right = 650;
shadowLight.shadow.camera.top = 650;
shadowLight.shadow.camera.bottom = -650;
shadowLight.shadow.camera.near = 1;
shadowLight.shadow.camera.far = 1000;
shadowLight.shadow.mapSize.width = 4096;
shadowLight.shadow.mapSize.height = 4096;
light2 = new THREE.DirectionalLight(0x666666, .25);
light2.position.set(-600, 350, 350);
light3 = new THREE.DirectionalLight(0x666666, .15);
light3.position.set(0, -250, 300);
scene.add(hemisphereLight);
scene.add(shadowLight);
scene.add(light2);
scene.add(light3);
}
createLights();
/*--------------------
Bubble
--------------------*/
const vertex = width > 575 ? 80 : 40;
const bubbleGeometry = new THREE.SphereGeometry( 150, vertex, vertex );
const bubbleEmissive = 0x91176b;
const bubbleEmissiveOnContact = 0x000000;
const createBubble = () => {
for(let i = 0; i < bubbleGeometry.vertices.length; i++) {
let vector = bubbleGeometry.vertices[i];
vector.original = vector.clone();
}
const bubbleMaterial = new THREE.MeshStandardMaterial({
emissive: bubbleEmissive,
emissiveIntensity: 0.85,
roughness: 0.55,
metalness: 0.51,
side: THREE.FrontSide,
});
// save points for later calculation
for (var i = 0; i < bubbleGeometry.vertices.length; i += 1) {
var vertex = bubbleGeometry.vertices[i];
var vec = new THREE.Vector3(vertex.x, vertex.y, vertex.z);
sphereVerticesArray.push(vec);
var mag = vec.x * vec.x + vec.y * vec.y + vec.z * vec.z;
mag = Math.sqrt(mag);
var norm = new THREE.Vector3(vertex.x / mag, vertex.y / mag, vertex.z / mag);
sphereVerticesNormArray.push(norm);
}
const _bubble = new THREE.Mesh(bubbleGeometry, bubbleMaterial);
_bubble.castShadow = true;
_bubble.receiveShadow = false;
_bubble.rotation.y = -90;
scene.add(_bubble);
return _bubble;
}
const bubble = createBubble();
/*--------------------
Plane
--------------------*/
const createPlane = () => {
const planeGeometry = new THREE.PlaneBufferGeometry( 2000, 2000 );
const planeMaterial = new THREE.ShadowMaterial({
opacity: 0.15
});
const plane = new THREE.Mesh( planeGeometry, planeMaterial );
plane.position.y = -150;
plane.position.x = 0;
plane.position.z = 0;
plane.rotation.x = Math.PI / 180 * -90;
plane.receiveShadow = true;
scene.add(plane);
}
createPlane();
/*--------------------
Map
--------------------*/
const map = (num, in_min, in_max, out_min, out_max) => {
return (num - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}
/*--------------------
Distance
--------------------*/
const distance = (a, b) => {
const dx = a.x - b.x;
const dy = a.y - b.y;
const d = Math.sqrt( dx * dx + dy * dy );
return d;
}
/*--------------------
Mouse
--------------------*/
let mouse = new THREE.Vector2(0, 0);
const onMouseMove = (e) => {
TweenMax.to(mouse, 0.8, {
x : ( e.clientX / window.innerWidth ) * 2 - 1,
y: - ( e.clientY / window.innerHeight ) * 2 + 1,
ease: Power2.easeOut
});
raycaster.setFromCamera( mouse, camera );
isIntersectingWithBubble = raycaster.intersectObject( bubble ).length > 0; // we are only interested in intersections with the bubble object
try {
if (isIntersectingWithBubble) {
// is intersecting: change color, change pointer, change point of contact
bubble.material.emissive.setHex(bubbleEmissiveOnContact);
document.body.style.cursor = 'pointer';
} else {
// is not intersecting: restore color, restore pointer, remove point of contact
bubble.material.emissive.setHex(bubbleEmissive);
document.body.style.cursor = 'auto';
}
} catch (e) {
}
};
['mousemove', 'touchmove'].forEach(event => {
window.addEventListener(event, onMouseMove);
});
/*--------------------
Spring
--------------------*/
let spring = {
scale: 1
};
const clicking = {
down: () => {
mouseDown = true;
},
up: () => {
mouseDown = false;
}
};
['mousedown', 'touchstart'].forEach(event => {
window.addEventListener(event, clicking.down);
});
['mouseup', 'touchend'].forEach(event => {
window.addEventListener(event, clicking.up);
});
/*--------------------
Resize
--------------------*/
const onResize = () => {
canvas.style.width = '';
canvas.style.height = '';
width = canvas.offsetWidth;
height = canvas.offsetHeight;
camera.aspect = width / height;
camera.updateProjectionMatrix();
maxDist = distance(mouse, {x: width / 2, y: height / 2});
renderer.setSize(width, height);
}
let resizeTm;
window.addEventListener('resize', function(){
resizeTm = clearTimeout(resizeTm);
resizeTm = setTimeout(onResize, 200);
});
/*--------------------
Noise
--------------------*/
let dist = new THREE.Vector2(0, 0);
let maxDist = distance(mouse, {x: width / 2, y: height / 2});
const updateVertices = (time) => {
dist = distance(mouse, {x: width / 2, y: height / 2});
dist /= maxDist;
dist = map(dist, 1, 0, 0, 1);
for(let i = 0; i < bubbleGeometry.vertices.length; i++) {
let vector = bubbleGeometry.vertices[i];
vector.copy(vector.original);
let perlin = noise.simplex3(
(vector.x * 0.006) + (time * 0.0005),
(vector.y * 0.006) + (time * 0.0005),
(vector.z * 0.006)
);
let ratio = ((perlin * 0.3 * (howMuch + 0.1)) + 0.9);
vector.multiplyScalar(ratio);
}
bubbleGeometry.verticesNeedUpdate = true;
}
/*--------------------
Animate
--------------------*/
const render = (a) => {
step +=1;
requestAnimationFrame(render);
//bubble.scale.set(spring.scale, spring.scale, spring.scale);
updateVertices(a);
renderer.clear();
renderer.render(scene, camera);
//Activate on mouse down
if(mouseDown && howMuch < howMuchLimit)
howMuch += 0.01;
else if (howMuch > 0)
howMuch -= 0.01;
if(isIntersectingWithBubble){
if(rippleAmount < 10)
rippleAmount += 0.05;
}else if(rippleAmount > 0)
rippleAmount -= 0.05;
doRipple();
}
requestAnimationFrame(render);
renderer.render(scene, camera);
/*--------------------
Helpers
--------------------*/
function fbm(p) {
var result = noise.simplex3(p._x, p._y, p._z);
return result;
}
function addPoint(arr) {
var r = new Point(0, 0, 0);
var len = arr.length;
for (var i = 0; i < len; i += 1) {
r._x += arr[i]._x;
r._y += arr[i]._y;
r._z += arr[i]._z;
}
return r;
}
function Point(_x=0, _y=0, _z=0) {
this._x = _x;
this._y = _y;
this._z = _z;
}
function ripple(p) {
var q = new Point(fbm(addPoint([p, new Point(0, 0, 0)])),
fbm(addPoint([p, new Point(0, 1, 0)])),
fbm(addPoint([p, new Point(0, 0, 1)])));
return fbm(addPoint([p, new Point(0.5 * q._x, 0.5 * q._y, 0.5 * q._z)]));
}
function doRipple(){
//ripple
for (var i = 0; i < bubbleGeometry.vertices.length; i += 1) {
var vertex = bubbleGeometry.vertices[i];
// var value = pn.noise((vertex.x + step)/ 10, vertex.y / 10, vertex.z / 10);
var value = ripple(new Point((vertex.x + step) / 100.0), vertex.y / 100.0, vertex.z / 100.0);
vertex.x = sphereVerticesArray[i].x + sphereVerticesNormArray[i].x * value * rippleAmount;
vertex.y = sphereVerticesArray[i].y + sphereVerticesNormArray[i].y * value * rippleAmount;
vertex.z = sphereVerticesArray[i].z + sphereVerticesNormArray[i].z * value * rippleAmount;
}
bubbleGeometry.computeFaceNormals();
bubbleGeometry.computeVertexNormals();
bubbleGeometry.verticesNeedUpdate = true;
bubbleGeometry.normalsNeedUpdate = true;
}
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 back for question two on .points. This time wondering how to change the opacity from 0, to 1 and then back within certain pixel distances from the emitter.
var particleCount = 14,
particles = new THREE.Geometry(),
pMaterial = new THREE.PointsMaterial({
map: new THREE.TextureLoader().load("x.png"),
blending: THREE.multiplyBlending,
flatShading: true,
size: 40,
transparent: true,
depthTest: true,
sizeAttenuation: true,
opacity: 1
});
var particleSystem;
My main confusion is that even though I've given it transparency I can't change the value within the update comp I've made for my emitter.
function update() {
//particleSystem.rotation.y += 0.01;
pCount = particleCount;
while (pCount--) {
particle = particles.vertices[pCount];
(This is where a bunch of validation is for where the points are)
particleSystem.geometry.verticesNeedUpdate = true;
particleSystem.rotation.y += (Math.random()*0.001)
}
Render loop:
renderer.setAnimationLoop(() => {
update();
composer.render(scene, camera);
});
I want to make it fade out and not appear in the scene for 20 or so pixels and then fade in. But I'm not entirely sure on how to change the opacity as particle.opacity += 0.1 won't work.
Edit: I'm also uncertain about Size as I want to do a similar thing with it but from 20 to 40, I could probably base it depending on it's Y cordinate. Anyway; I'm also uncertain how to gradually change that too.
Sorry if this is a obvious answer, duplicate question and any help I get. Any alternate methods of what I've seen is in an alternate structure that I don't understand or in array in which I don't know how to put into what I want.
(Thanks in advance)
The issue is that the opacity and the size is a property of the THREE.PointsMaterial. If the pints should have different sizes it is not sufficient to have a list of different vertices in one THREE.Points. There has to be a list of different THREE.Points with different HREE.PointsMaterials.
Create a list of THREE.Points with different materials:
var texture = new THREE.TextureLoader().load( "..." );
var particleSystemCount = 14;
var particleSystems = [];
for (var i = 0; i < particleSystemCount; ++ i) {
var geometry = new THREE.Geometry();
var pMaterial = new THREE.PointsMaterial({
size: 20,
map: texture,
blending: THREE.AdditiveBlending,
transparent: true,
depthTest: false,
sizeAttenuation: true,
opacity: 0
});
// ...
var points = new THREE.Points(geometry, pMaterial);
scene.add(points);
particleSystems.push(points);
}
So in update the opacity and size can be changed individually:
function update() {
for (var i = 0; i < particleSystems.length; ++ i) {
var points = particleSystems[i];
var material = points.material;
var particle = points.geometry.vertices[0];
// ....
if ( material.size < 40 )
material.size += 0.5;
if ( material.opacity < 1 )
material.opacity += 0.01;
// ....
}
}
var canvas_w = window.innerWidth, canvas_h = window.innerHeight;
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, canvas_w/canvas_h, 1, 1000);
camera.position.set(0, 0, 400);
var renderer = new THREE.WebGLRenderer();
renderer.setSize(canvas_w, window.innerHeight);
document.body.appendChild(renderer.domElement);
window.onresize = function() {
canvas_w = window.innerWidth, canvas_h = window.innerHeight;
renderer.setSize(canvas_w, canvas_h);
camera.aspect = canvas_w/canvas_h;
camera.updateProjectionMatrix();
}
var texture = new THREE.TextureLoader().load("https://threejs.org/examples/textures/sprites/circle.png");
var particleSystemCount = 14;
var particleSystems = [];
for (var i = 0; i < particleSystemCount; ++ i) {
var geometry = new THREE.Geometry();
var pMaterial = new THREE.PointsMaterial({
size: 20,
map: texture,
blending: THREE.AdditiveBlending,
transparent: true,
depthTest: false,
sizeAttenuation: true,
opacity: 0
});
var px = (Math.random() - 0.5) * 100;
var py = (Math.random() - 0.5) * 100 + 200;
var pz = (Math.random() - 0.5) * 100;
var particle = new THREE.Vector3(px, py, pz);
particle.velocity = new THREE.Vector3(0, 0, 0);
geometry.vertices.push(particle);
var points = new THREE.Points(geometry, pMaterial);
scene.add(points);
particleSystems.push(points);
}
function update() {
for (var i = 0; i < particleSystems.length; ++ i) {
var points = particleSystems[i];
var material = points.material;
var particle = points.geometry.vertices[0];
if (particle.y < -200) {
particle.x = (Math.random() - 0.5) * 100;
particle.y = (Math.random() - 0.5) * 100 + 200;
particle.z = (Math.random() - 0.5) * 100;
particle.velocity.y = 0;
material.size = 20;
material.opacity = 0;
}
particle.velocity.y -= Math.random() * .1;
particle.add(particle.velocity);
if ( material.size < 40 )
material.size += 0.25;
if ( material.opacity < 1 )
material.opacity += 0.01;
points.geometry.verticesNeedUpdate = true;
points.rotation.y += (Math.random()*0.001)
}
}
renderer.setAnimationLoop(() => {
update();
renderer.render(scene, camera);
});
body { overflow: hidden; margin: 0; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/99/three.min.js"></script>
In the live snippet below (also at https://jsfiddle.net/gpolyn/bpo7t7f6), I offer optional dynamic updating of PerspectiveCamera's lookAt parameter and fov in the three.js render cycle. (My goal is to fill as much of the viewport as possible with the subject cube, through all orbit positions.)
I suspect that lack of precision in the matrix math code used to calculate my dynamic fov and lookAt paramters (three.js uses Float32 arrays) causes the jitter/shaking I notice in the cube when orbiting with the dynamic options selected from the controls.
(The matrix operations can be found in the snippet addExtrema function.)
In my demo, my highest goal is to remove jitter/shaking in case 1, described here:
the "dynamicFovAndLookAt" control option uses dynamic fov and lookAt updating causing quite a bit of jitter in the cube, no matter the orbit position; the fov and lookAt parameters can be seen fluctuating in the demo's lower right status box;
"dynamicFov" uses dynamic fov updating causing some jitter in the cube, depending on orbiting; the fov parameter in the lower right status box will vary due to the dynamic recalculation;
the "boundingSphere" option uses no dynamic fov, lookAt updating and the cube exhibits no jitter/shake through orbiting – the fov and lookAt parameters are constant in the lower right status box.
(Orbit position is reported in the lower left corner of the demo, while one of the box corners has a green dot to aid any discussion of the jitter effect.)
var renderer, scene, camera, controls;
var object;
var vertices3;
var cloud;
var boxToBufferAlphaMapping = {
0: 0,
2: 1,
1: 2,
3: 4,
6: 7,
7: 10,
5: 8,
4: 6
}
var lastAlphas = [];
var canvasWidth, canvasHeight;
var windowMatrix;
var boundingSphere;
var figure;
var fovWidth, fovDistance, fovHeight;
var newFov, newLookAt;
var dist, height, fov, lookAt;
var aspect;
var CONSTANT_FOR_FOV_CALC = 180 / Math.PI;
var mat3;
var CORNERS = 8;
var ndc = new Array(CORNERS);
var USE_GREEN_DOTS = false;
var stats, orbitPosition, cameraFacts;
var useDynamicFov;
var datGuiData = {};
init();
render();
afterInit();
animate();
function init() {
mat3 = new THREE.Matrix4();
canvasWidth = window.innerWidth;
canvasHeight = window.innerHeight;
aspect = canvasWidth / canvasHeight;
// renderer
<!-- renderer = new THREE.WebGLRenderer({antialias:true, logarithmicDepthBuffer:true}); -->
renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setSize(canvasWidth, canvasHeight);
document.body.appendChild(renderer.domElement);
// scene
scene = new THREE.Scene();
// object
var geometry = new THREE.BoxGeometry(4, 4, 6);
// too lazy to add edges without EdgesHelper...
var material = new THREE.MeshBasicMaterial({
transparent: true,
opacity: 0
});
var cube = new THREE.Mesh(geometry, material);
object = cube;
// bounding sphere used for orbiting control in render
object.geometry.computeBoundingSphere();
boundingSphere = object.geometry.boundingSphere;
cube.position.set(2, 2, 3)
// awkward, but couldn't transfer cube position to sphere...
boundingSphere.translate(new THREE.Vector3(2, 2, 3));
// save vertices for subsequent use
vertices = cube.geometry.vertices;
var edges = new THREE.EdgesHelper(cube)
scene.add(edges);
scene.add(cube);
<!-- if (USE_GREEN_DOTS) addGreenDotsToScene(geometry); -->
addGreenDotsToScene(geometry);
// camera
<!-- camera = new THREE.PerspectiveCamera( 17, window.innerWidth / window.innerHeight, 20, 40 ); -->
camera = new THREE.PerspectiveCamera(17, window.innerWidth / window.innerHeight);
camera.position.set(20, 20, 20);
// controls
controls = new THREE.OrbitControls(camera);
controls.maxPolarAngle = 0.5 * Math.PI;
controls.minAzimuthAngle = 0;
controls.maxAzimuthAngle = 0.5 * Math.PI;
controls.enableZoom = false;
// performance monitor
stats = new Stats();
document.body.appendChild(stats.dom);
// orbitposition tracker
orbitPosition = new THREEg.OrbitReporter()
orbitPosition.domElement.style.position = 'absolute'
orbitPosition.domElement.style.left = '0px'
orbitPosition.domElement.style.bottom = '0px'
document.body.appendChild(orbitPosition.domElement)
// camera facts
cameraFacts = new THREEg.CameraReporter()
cameraFacts.domElement.style.position = 'absolute'
cameraFacts.domElement.style.right = '0px'
cameraFacts.domElement.style.bottom = '0px'
document.body.appendChild(cameraFacts.domElement)
// ambient
scene.add(new THREE.AmbientLight(0x222222));
// axes
scene.add(new THREE.AxisHelper(20));
// initial settings
dist = boundingSphere.distanceToPoint(camera.position);
height = boundingSphere.radius * 2;
fov = 2 * Math.atan(height / (2 * dist)) * (CONSTANT_FOR_FOV_CALC);
newFov = fov;
lookAt = new THREE.Vector3(2, 2, 3); // center of box
newLookAt = lookAt;
// dat.gui
window.onload = function() {
var view = datGuiData;
view.boundingSphere = true;
view.dynamicFov = false;
view.dynamicFovAndLookAt = false;
var gui = new dat.GUI();
var CB1Controller = gui.add(view, 'boundingSphere').listen();
CB1Controller.onChange(function(value) {
view.boundingSphere = true;
view.dynamicFov = false;
view.dynamicFovAndLookAt = false;
});
var CB2Controller = gui.add(view, 'dynamicFov').listen();
CB2Controller.onChange(function(value) {
view.boundingSphere = false;
view.dynamicFov = true;
view.dynamicFovAndLookAt = false;
});
var CB3Controller = gui.add(view, 'dynamicFovAndLookAt').listen();
CB3Controller.onChange(function(value) {
view.boundingSphere = false;
view.dynamicFov = true;
view.dynamicFovAndLookAt = true;
});
};
}
function addExtrema() {
// thread A
mat3.multiplyMatrices(camera.matrixWorld, mat3.getInverse(camera.projectionMatrix));
// thread B
var scratchVar;
var topIdx, bottomIdx, leftIdx, rightIdx;
var top = Number.NEGATIVE_INFINITY;
var bottom = Number.POSITIVE_INFINITY;
var right = Number.NEGATIVE_INFINITY;
var left = Number.POSITIVE_INFINITY;
var closestVertex, closestVertexDistance = Number.POSITIVE_INFINITY;
var vtx;
for (var i = 0; i < CORNERS; i++) {
scratchVar = vertices3[i].clone().applyMatrix4(camera.matrixWorldInverse);
scratchVar.applyMatrix4(camera.projectionMatrix);
scratchVar.divideScalar(scratchVar.w)
ndc[i] = scratchVar;
vtx = ndc[i];
if (vtx.x < left) {
left = vtx.x;
leftIdx = i;
} else if (vtx.x > right) {
right = vtx.x;
rightIdx = i;
}
if (vtx.y < bottom) {
bottom = vtx.y;
bottomIdx = i;
} else if (vtx.y > top) {
top = vtx.y;
topIdx = i;
}
if (vtx.z < closestVertexDistance) {
closestVertex = i;
closestVertexDistance = vtx.z;
}
}
var yNDCPercentCoverage = (Math.abs(ndc[topIdx].y) + Math.abs(ndc[bottomIdx].y)) / 2;
yNDCPercentCoverage = Math.min(1, yNDCPercentCoverage);
var xNDCPercentCoverage = (Math.abs(ndc[leftIdx].x) + Math.abs(ndc[rightIdx].x)) / 2;
xNDCPercentCoverage = Math.min(1, xNDCPercentCoverage);
var ulCoords = [ndc[leftIdx].x, ndc[topIdx].y, closestVertexDistance, 1]
var blCoords = [ndc[leftIdx].x, ndc[bottomIdx].y, closestVertexDistance, 1]
var urCoords = [ndc[rightIdx].x, ndc[topIdx].y, closestVertexDistance, 1]
var ul = new THREE.Vector4().fromArray(ulCoords);
ul.applyMatrix4(mat3).divideScalar(ul.w);
var bl = new THREE.Vector4().fromArray(blCoords);
bl.applyMatrix4(mat3).divideScalar(bl.w);
var ur = new THREE.Vector4().fromArray(urCoords);
ur.applyMatrix4(mat3).divideScalar(ur.w);
var center = new THREE.Vector3();
center.addVectors(ur, bl);
center.divideScalar(2);
var dist = camera.position.distanceTo(center);
var upperLeft = new THREE.Vector3().fromArray(ul.toArray().slice(0, 3));
var p;
if ((1 - yNDCPercentCoverage) < (1 - xNDCPercentCoverage)) { // height case
var bottomLeft = new THREE.Vector3().fromArray(bl.toArray().slice(0, 3));
var height = upperLeft.distanceTo(bottomLeft);
p = 2 * Math.atan(height / (2 * dist)) * (CONSTANT_FOR_FOV_CALC);
} else { // width case
var upperRight = new THREE.Vector3().fromArray(ur.toArray().slice(0, 3));
var width = upperRight.distanceTo(upperLeft);
p = 2 * Math.atan((width / aspect) / (2 * dist)) * (CONSTANT_FOR_FOV_CALC);
}
if (datGuiData.dynamicFovAndLookAt || datGuiData.dynamicFov) {
newFov = p;
} else {
dist = boundingSphere.distanceToPoint(camera.position);
height = boundingSphere.radius * 2;
newFov = 2 * Math.atan(height / (2 * dist)) * (CONSTANT_FOR_FOV_CALC);
}
if (datGuiData.dynamicFovAndLookAt == true) {
newLookAt = center;
} else {
newLookAt = lookAt;
}
if (USE_GREEN_DOTS) {
var alphas = cloud.geometry.attributes.alpha;
// make last points invisible
lastAlphas.forEach(function(alphaIndex) {
alphas.array[alphaIndex] = 0.0;
});
// now, make new points visible...
// (boxToBufferAlphaMapping is a BufferGeometry-Object3D geometry
// map between the object and green dots)
alphas.array[boxToBufferAlphaMapping[rightIdx]] = 1.0;
alphas.array[boxToBufferAlphaMapping[bottomIdx]] = 1.0;
alphas.array[boxToBufferAlphaMapping[topIdx]] = 1.0;
alphas.array[boxToBufferAlphaMapping[leftIdx]] = 1.0;
// store visible points for next cycle
lastAlphas = [boxToBufferAlphaMapping[rightIdx]];
lastAlphas.push(boxToBufferAlphaMapping[bottomIdx])
lastAlphas.push(boxToBufferAlphaMapping[topIdx])
lastAlphas.push(boxToBufferAlphaMapping[leftIdx])
alphas.needsUpdate = true;
}
}
function addGreenDotsToScene(geometry) {
var bg = new THREE.BufferGeometry();
bg.fromGeometry(geometry);
bg.translate(2, 2, 3); // yucky, and quick
var numVertices = bg.attributes.position.count;
var alphas = new Float32Array(numVertices * 1); // 1 values per vertex
<!-- for( var i = 0; i < numVertices; i ++ ) { -->
<!-- alphas[ i ] = 1; -->
<!-- } -->
alphas[2] = 1;
bg.addAttribute('alpha', new THREE.BufferAttribute(alphas, 1));
var uniforms = {
color: {
type: "c",
value: new THREE.Color(0x00ff00)
},
};
var shaderMaterial = new THREE.ShaderMaterial({
uniforms: uniforms,
vertexShader: document.getElementById('vertexshader').textContent,
fragmentShader: document.getElementById('fragmentshader').textContent,
transparent: true
});
cloud = new THREE.Points(bg, shaderMaterial);
scene.add(cloud);
}
function afterInit() {
windowMatrix = new THREE.Matrix4();
windowMatrix.set(canvasWidth / 2, 0, 0, canvasWidth / 2, 0, canvasHeight / 2, 0, canvasHeight / 2, 0, 0, 0.5, 0.5, 0, 0, 0, 1);
var vertices2 = object.geometry.vertices.map(function(vtx) {
return (new THREE.Vector4(vtx.x, vtx.y, vtx.z));
});
// create 'world-space' geometry points, using
// model ('world') matrix
vertices3 = vertices2.map(function(vt) {
return vt.applyMatrix4(object.matrixWorld);
})
}
function render() {
<!-- console.log({far: camera.far, camera_near: camera.near}) -->
camera.lookAt(newLookAt);
camera.fov = newFov;
camera.updateProjectionMatrix();
renderer.render(scene, camera);
}
function animate() {
requestAnimationFrame(animate);
render();
addExtrema()
stats.update();
orbitPosition.update(controls);
cameraFacts.update(camera, newLookAt);
}
body {
background-color: #000;
margin: 0px;
overflow: hidden;
}
.dg .c {
width: 40%
}
.dg .property-name {
width: 60%
}
<script src="https://rawgit.com/mrdoob/three.js/dev/build/three.min.js"></script>
<script src="https://rawgit.com/mrdoob/three.js/master/examples/js/libs/stats.min.js"></script>
<script src="https://rawgit.com/mrdoob/three.js/master/examples/js/controls/OrbitControls.js"></script>
<script src="https://rawgit.com/gpolyn/789d63a662c1768320756f68a6099f15/raw/3a0f323bb284b09e624a11f93ff4055e23adea80/OrbitReporter.js"></script>
<script src="https://rawgit.com/gpolyn/70352cb34c7900ed2489400d4ecc45f7/raw/7b6e7e6bb3e175d4145879ef1afdeb38c31cf785/camera_reporter.js"></script>
<script src="https://rawgit.com/mrdoob/three.js/master/examples/js/libs/dat.gui.min.js"></script>
<script type="x-shader/x-vertex" id="vertexshader">
attribute float alpha; varying float vAlpha; void main() { vAlpha = alpha; vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 ); gl_PointSize = 8.0; gl_Position = projectionMatrix * mvPosition; }
</script>
<script type="x-shader/x-fragment" id="fragmentshader">
uniform vec3 color; varying float vAlpha; void main() { gl_FragColor = vec4( color, vAlpha ); }
</script>