Trying to simulate a 3D effect via Three.js - javascript

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;
}

Related

customize mouse cursor javascript on my own html

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.

'plus' or 'x' shaped wave in threejs

I have this scene made with THREE.js. (see code snippet attached in the post)
or https://codepen.io/farisk/pen/jOPgKGQ
Currently its a radiating a circular wave based on the formula in the ‘distance()’ function
As such :
return Math.sqrt(Math.pow((x1 - x2), 2) + Math.pow((y1 - y2), 2));
I’m wondering if its possible to modify the formula so that I can somehow achieve a ‘X’ or ‘Plus sign (+)’ shaped wave instead?
var once = false;
class App {
init() {
this.stats = new Stats();
this.stats.showPanel(0);
document.body.querySelector('.stats').appendChild(this.stats.domElement);
this.backgroundColor = 0x000000;
this.ambientLightColor = 0xffffff;
this.spotLightColor = 0xff9999;
// this.boxColor = 0x1a63ed;
this.boxColor = 0xffffff;
this.angle = 0;
this.gridSize = 30;
this.ratio = 1.3
this.col = this.gridSize*this.ratio;
this.row = this.gridSize;
this.velocity = .05;
this.boxes = [];
this.amplitude = -7;
this.frequency = 0;
this.waveLength = 242;
this.scene = new THREE.Scene();
this.scene.background = new THREE.Color(this.backgroundColor);
this.camera = new THREE.PerspectiveCamera(2, window.innerWidth / window.innerHeight, 1, 10000);
this.camera.position.set(0, 800, 0);
this.addRenderer();
document.body.appendChild(this.renderer.domElement);
this.controls = new THREE.OrbitControls(this.camera, this.renderer.domElement);
this.addAmbientLight();
this.addDirectionalLight();
this.addFloor();
this.addBoxes(this.scene);
this.addGUIControls();
this.animate();
window.addEventListener('resize', this.onResize.bind(this));
}
addDirectionalLight() {
this.directionalLight = new THREE.DirectionalLight(0xffffff, 1);
this.directionalLight.castShadow = true;
this.directionalLight.position.set(0, 1, 0);
this.directionalLight.shadow.camera.far = 10000;
this.directionalLight.shadow.camera.near = -100;
this.directionalLight.shadow.camera.left = -40;
this.directionalLight.shadow.camera.right = 40;
this.directionalLight.shadow.camera.top = 20;
this.directionalLight.shadow.camera.bottom = -20;
this.directionalLight.shadow.camera.zoom = 1;
this.directionalLight.shadow.camera.needsUpdate = true;
const targetObject = new THREE.Object3D();
targetObject.position.set(-50, -82, 40);
this.directionalLight.target = targetObject;
this.scene.add(this.directionalLight);
this.scene.add(this.directionalLight.target);
}
addGUIControls() {
this.gui = new dat.GUI();
this.gui.add(this, 'amplitude', -10, 10);
this.gui.add(this, 'velocity', 0, .5);
this.gui.add(this, 'waveLength', 100, 500);
this.controller = this.gui.add(this, 'gridSize', 24, 150);
this.controller.onFinishChange((value) => {
this.gridSize = Math.floor(value);
this.clearScene();
this.col = this.gridSize*this.ratio;
this.row = this.gridSize;
this.addBoxes(this.scene);
});
}
addRenderer() {
this.renderer = new THREE.WebGLRenderer({ antialias: true });
this.renderer.shadowMap.enabled = true;
this.renderer.shadowMap.type = THREE.PCFSoftShadowMap;
this.renderer.setSize(window.innerWidth, window.innerHeight);
}
addAmbientLight() {
const light = new THREE.AmbientLight(this.ambientLightColor, .5);
this.scene.add(light);
}
addSpotLight() {
this.spotLight = new THREE.SpotLight(this.spotLightColor);
this.spotLight.position.set(100, 250, 150);
this.spotLight.castShadow = true;
this.scene.add(this.spotLight);
}
clearScene() {
this.scene.remove(this.mesh);
this.boxes = [];
}
addBoxes(scene) {
const size = 0.05;
const height = 20;
const material = new THREE.MeshLambertMaterial({
color: this.boxColor,
});
const geometry = new THREE.BoxBufferGeometry(size, height, size);
geometry.translate( 0, 2.5, 0 );
this.mesh = this.getBox(geometry, material, this.row * this.col);
this.scene.add(this.mesh);
let ii = 0;
for (let i = 0; i < this.col; i++) {
this.boxes[i] = [];
for (let j = 0; j < this.row; j++) {
const pivot = new THREE.Object3D();
this.boxes[i][j] = pivot;
pivot.scale.set(1, 0.001, 1);
// pivot.position.set(i - this.gridSize*this.ratio * .5, height * .5, j - this.gridSize * .5);
pivot.position.set(i - this.gridSize*this.ratio * .5, height * 0, j - this.gridSize * .5);
pivot.updateMatrix();
this.mesh.setMatrixAt(ii++, pivot.matrix);
}
}
this.mesh.instanceMatrix.needsUpdate = true;
}
drawWave() {
let ii= 0;
for (let i = 0; i < this.col; i++) {
for (let j = 0; j < this.row; j++) {
const distance = this.distance(j, i, this.row * .5, this.col * .5);
const offset = this.map(distance, 0, this.waveLength, -100, 100);
const angle = this.angle + offset ;
if (!once) {
console.log(this.boxes)
once = true
}
this.boxes[i][j].scale.y = this.map(Math.sin(angle), -1, -this.amplitude, 0.001, 1);
this.boxes[i][j].rotation.z = this.map(Math.sin(angle), -1, -this.amplitude, 0.001, 1);
this.boxes[i][j].updateMatrix();
this.mesh.setMatrixAt(ii++, this.boxes[i][j].matrix);
}
}
this.mesh.instanceMatrix.needsUpdate = true;
this.angle -= this.velocity;
}
distance(x1, y1, x2, y2) {
// return Math.sin(x1 - x2)
return Math.sqrt(Math.pow((x1 - x2), 2) + Math.pow((y1 - y2), 2));
}
map(value, start1, stop1, start2, stop2) {
return (value - start1) / (stop1 - start1) * (stop2 - start2) + start2
}
addFloor() {
const planeGeometry = new THREE.PlaneBufferGeometry(10, 500);
const planeMaterial = new THREE.ShadowMaterial({ opacity:1 });
this.floor = new THREE.Mesh(planeGeometry, planeMaterial);
planeGeometry.rotateX(- Math.PI / 2);
this.floor.position.y = 2;
this.floor.receiveShadow = true;
this.scene.add(this.floor);
}
getBox(geometry, material, count) {
const mesh = new THREE.InstancedMesh(geometry, material, count);
mesh.instanceMatrix.setUsage(THREE.DynamicDrawUsage);
mesh.castShadow = true;
mesh.receiveShadow = true;
return mesh;
}
addGrid() {
const size = this.col;
const divisions = size;
const gridHelper = new THREE.GridHelper(size, divisions);
gridHelper.position.set(0, 0, 0);
gridHelper.material.opacity = 0;
gridHelper.material.transparent = true;
this.scene.add(gridHelper);
}
animate() {
this.stats.begin();
this.drawWave();
this.controls.update();
this.renderer.render(this.scene, this.camera);
this.stats.end();
requestAnimationFrame(this.animate.bind(this));
}
onResize() {
const ww = window.innerWidth;
const wh = window.innerHeight;
this.camera.aspect = ww / wh;
this.camera.updateProjectionMatrix();
this.renderer.setSize(ww, wh);
}
}
new App().init();
html {
font-family: sans-serif;
}
* {
box-sizing: border-box;
}
body {
background: black;
color: #fff;
font-family: sans-serif;
overflow: hidden;
cursor: -webkit-grab;
cursor: -moz-grab;
padding: 0;
margin: 0;
}
canvas {
width: 100%;
height: 100%;
}
.stats {
opacity: 1;
z-index: 10;
position: absolute;
}
.dg.ac {
position: absolute;
z-index: 10 !important;
}
<main>
<div class="stats"></div>
</main>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/110/three.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/stats.js/r16/Stats.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.7.2/dat.gui.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/1.20.3/TweenMax.min.js"></script>
"Plus" or "X"
The "Plus" is the easiest to implement. You just need the two distances from the center lines (horizontal and vertical).
const yd = j - this.row / 2; // distance from horizontal
const xd = i - this.col / 2; // distance from vertical
The get the minimum of the absolute values of each distance
const distance = Math.min(Math.abs(yd), Math.abs(xd));
You then use that distance as you did in the original.
Implementation
If you change the function draw to the following this will create the plus that I think you are looking for.
drawWave() {
var ii= 0, x, y;
for (x = 0; x < this.col; x++) {
const bRow = this.boxes[x];
for (y = 0; y < this.row; y++) {
const yd = y - this.row / 2;
const xd = x - this.col / 2;
const distance = Math.min(Math.abs(yd), Math.abs(xd));
const angle = this.angle + this.map(distance, 0, this.waveLength, -100, 100);
const size = this.map(Math.sin(angle), -1, -this.amplitude, 0.001, 1);
bRow[y].scale.y = size;
bRow[y].rotation.z = size;
bRow[y].updateMatrix();
this.mesh.setMatrixAt(ii++, bRow[y].matrix);
}
}
this.mesh.instanceMatrix.needsUpdate = true;
this.angle -= this.velocity;
}
The cross
If you want a cross you need only rotate the x and y by 45 deg. The next function does that.
drawWave() {
const xAx = Math.cos(Math.PI / 4); // Axis 45 deg CW
const xAy = Math.sin(Math.PI / 4);
var ii= 0, x, y;
for (x = 0; x < this.col; x++) {
const bRow = this.boxes[x];
for (y = 0; y < this.row; y++) {
const xx = x - this.col / 2;
const yy = y - this.row / 2;
const xd = xx * xAx - yy * xAy; // rotate
const yd = xx * xAy + yy * xAx;
const distance = Math.min(Math.abs(yd), Math.abs(xd));
const angle = this.angle + this.map(distance, 0, this.waveLength, -100, 100);
const size = this.map(Math.sin(angle), -1, -this.amplitude, 0.001, 1);
bRow[y].scale.y = size;
bRow[y].rotation.z = size;
bRow[y].updateMatrix();
this.mesh.setMatrixAt(ii++, bRow[y].matrix);
}
}
this.mesh.instanceMatrix.needsUpdate = true;
this.angle -= this.velocity;
}
It would be far more efficient if you implemented all the above logic in the vertex shader

Camera position and device controls using the waves example

I have a website where i need to use threejs. I used the waves example in a landing page and it is working well on desktop/laptop.
However, I need to be able to move the waves (same behaviour as mouse in desktop) but with the device gyroscope/accelerometer and it is working using the devicecontrols that i found in another example. The problem is that it is my first time using three and working with 3d and i am lost. The dots are really small they only seem to move in a very small line and i dont know how or where the camera should look.
For example,
Desired behaviour (https://www.dropbox.com/s/d11srwqww8jtuda/PhoneWanted.png?dl=0)
Current on desktop (https://www.dropbox.com/s/x3rpcnovrfr2v1x/Desktop.png?dl=0)
Current on mobile (https://www.dropbox.com/s/c9c1mojgi5zt55y/IMG_2385.jpeg?dl=0)
On mobile should look the same as on desktop and move the waves but only horizontally...
Is this possible?
Mobile Script
<script>
if (screen && screen.width < 900) {
//alert("Mobile");
if ( WEBGL.isWebGLAvailable() === false ) {
document.body.appendChild( WEBGL.getWebGLErrorMessage() );
}
var SEPARATION = 100, AMOUNTX = 50, AMOUNTY = 50;
var container;
var camera, scene, renderer, controls;
var particles, count = 0;
var mouseX = 0, mouseY = 0;
var windowHalfX = window.innerWidth / 2;
var windowHalfY = window.innerHeight / 2;
init();
animate();
function init() {
container = document.createElement( 'div' );
document.body.appendChild( container );
camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 1, 10000 );
controls = new THREE.DeviceOrientationControls( camera );
camera.position.z = 1000;
scene = new THREE.Scene();
//
var numParticles = AMOUNTX * AMOUNTY;
var positions = new Float32Array( numParticles * 3 );
var scales = new Float32Array( numParticles );
var i = 0, j = 0;
for ( var ix = 0; ix < AMOUNTX; ix ++ ) {
for ( var iy = 0; iy < AMOUNTY; iy ++ ) {
positions[ i ] = ix * SEPARATION - ( ( AMOUNTX * SEPARATION ) / 2 ); // x
positions[ i + 1 ] = 0; // y
positions[ i + 2 ] = iy * SEPARATION - ( ( AMOUNTY * SEPARATION ) / 2 ); // z
scales[ j ] = 1;
i += 3;
j ++;
}
}
var geometry = new THREE.BufferGeometry();
geometry.addAttribute( 'position', new THREE.BufferAttribute( positions, 3 ) );
geometry.addAttribute( 'scale', new THREE.BufferAttribute( scales, 1 ) );
var material = new THREE.ShaderMaterial( {
uniforms: {
color: { value: new THREE.Color( 0xffffff ) },
},
vertexShader: document.getElementById( 'vertexshader' ).textContent,
fragmentShader: document.getElementById( 'fragmentshader' ).textContent
} );
particles = new THREE.Points( geometry, material );
scene.add( particles );
renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
container.appendChild( renderer.domElement );
document.addEventListener( 'mousemove', onDocumentMouseMove, false );
window.addEventListener( 'resize', onWindowResize, false );
}
function animate() {
window.requestAnimationFrame( animate );
controls.update();
renderer.render( scene, camera );
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
}
}
</script>
Solution
console.log("Desktop");
if (WEBGL.isWebGLAvailable() === false) {
document.body.appendChild(WEBGL.getWebGLErrorMessage());
}
var SEPARATION = 100,
AMOUNTX = 50,
AMOUNTY = 50;
var container;
var camera, scene, renderer, controls;
var particles, count = 0;
var mouseX = 0,
mouseY = 0;
var windowHalfX = window.innerWidth / 2;
var windowHalfY = window.innerHeight / 2;
// var isMobile = isOSMobile();
var isMobile = true;
init();
animate();
function init() {
container = document.createElement('div');
document.body.appendChild(container);
camera = new THREE.PerspectiveCamera(75, window.innerWidth /
window.innerHeight, 1, 10000);
if (isMobile) {
controls = new THREE.DeviceOrientationControls(camera);
}
camera.position.z = 1000;
scene = new THREE.Scene();
//
var numParticles = AMOUNTX * AMOUNTY;
var positions = new Float32Array(numParticles * 3);
var scales = new Float32Array(numParticles);
var i = 0,
j = 0;
for (var ix = 0; ix < AMOUNTX; ix++) {
for (var iy = 0; iy < AMOUNTY; iy++) {
positions[i] = ix * SEPARATION - ((AMOUNTX *
SEPARATION) / 2); // x
positions[i + 1] = 0; // y
positions[i + 2] = iy * SEPARATION - ((AMOUNTY *
SEPARATION) / 2); // z
scales[j] = 1;
i += 3;
j++;
}
}
var geometry = new THREE.BufferGeometry();
geometry.addAttribute('position', new THREE.BufferAttribute(
positions, 3));
geometry.addAttribute('scale', new THREE.BufferAttribute(scales,
1));
var material = new THREE.ShaderMaterial({
uniforms: {
color: {
value: new THREE.Color(isMobile ? 0xffffff : 0x8D8D8F)
},
},
vertexShader: document.getElementById(
'vertexshader').textContent,
fragmentShader: document.getElementById(
'fragmentshader').textContent
});
particles = new THREE.Points(geometry, material);
scene.add(particles);
renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
container.appendChild(renderer.domElement);
document.addEventListener('mousemove', onDocumentMouseMove,
false);
window.addEventListener('resize', onWindowResize, false);
if (isMobile) {
window.addEventListener("deviceorientation", handleOrientation, true);
}
}
function handleOrientation(e) {
var absolute = e.absolute;
var alpha = e.alpha;// x -90 ... 90
var beta = e.beta;// y 180 ... 0
var gamma = e.gamma;// x -90 ... 90
mouseX = -5 * windowHalfX * (gamma / 90);
//mouseY = -windowHalfY * ((beta - 90) / 90);
// console.log(mouseX.toFixed(2), ' x ', mouseY.toFixed(2));
}
function onWindowResize() {
windowHalfX = window.innerWidth / 2;
windowHalfY = window.innerHeight / 2;
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
//
function onDocumentMouseMove(event) {
mouseX = event.clientX - windowHalfX;
mouseY = event.clientY - windowHalfY;
}
//
function animate() {
requestAnimationFrame(animate);
render();
if (isMobile) {
controls.update();
}
}
function render() {
camera.position.x += (mouseX - camera.position.x) * .05;
if(isMobile) {
camera.position.y = 550;
} else {
camera.position.y += (-mouseY - camera.position.y) * .05;
}
camera.lookAt(scene.position);
var positions = particles.geometry.attributes.position.array;
var scales = particles.geometry.attributes.scale.array;
var i = 0,
j = 0;
for (var ix = 0; ix < AMOUNTX; ix++) {
for (var iy = 0; iy < AMOUNTY; iy++) {
positions[i + 1] = (Math.sin((ix + count) * 0.3) * 50) +
(Math.sin((iy + count) * 0.5) * 50);
scales[j] = (Math.sin((ix + count) * 0.3) + 1) * 8 +
(Math.sin((iy + count) * 0.5) + 1) * 8;
i += 3;
j++;
}
}
particles.geometry.attributes.position.needsUpdate = true;
particles.geometry.attributes.scale.needsUpdate = true;
renderer.render(scene, camera);
count += 0.1;
}
function isOSMobile() {
var userAgent = navigator.userAgent || navigator.vendor || window.opera;
if (/android/i.test(userAgent)) {
return true;
}
if (/iPad|iPhone|iPod/.test(userAgent) && !window.MSStream) {
return true;
}
return false;
}
Thank you!
Fixed it. Code works, Y axis movement is disabled.
console.log("Desktop");
if (WEBGL.isWebGLAvailable() === false) {
document.body.appendChild(WEBGL.getWebGLErrorMessage());
}
var SEPARATION = 100,
AMOUNTX = 50,
AMOUNTY = 50;
var container;
var camera, scene, renderer, controls;
var particles, count = 0;
var mouseX = 0,
mouseY = 0;
var windowHalfX = window.innerWidth / 2;
var windowHalfY = window.innerHeight / 2;
// var isMobile = isOSMobile();
var isMobile = true;
init();
animate();
function init() {
container = document.createElement('div');
document.body.appendChild(container);
camera = new THREE.PerspectiveCamera(75, window.innerWidth /
window.innerHeight, 1, 10000);
if (isMobile) {
controls = new THREE.DeviceOrientationControls(camera);
}
camera.position.z = 1000;
scene = new THREE.Scene();
//
var numParticles = AMOUNTX * AMOUNTY;
var positions = new Float32Array(numParticles * 3);
var scales = new Float32Array(numParticles);
var i = 0,
j = 0;
for (var ix = 0; ix < AMOUNTX; ix++) {
for (var iy = 0; iy < AMOUNTY; iy++) {
positions[i] = ix * SEPARATION - ((AMOUNTX *
SEPARATION) / 2); // x
positions[i + 1] = 0; // y
positions[i + 2] = iy * SEPARATION - ((AMOUNTY *
SEPARATION) / 2); // z
scales[j] = 1;
i += 3;
j++;
}
}
var geometry = new THREE.BufferGeometry();
geometry.addAttribute('position', new THREE.BufferAttribute(
positions, 3));
geometry.addAttribute('scale', new THREE.BufferAttribute(scales,
1));
var material = new THREE.ShaderMaterial({
uniforms: {
color: {
value: new THREE.Color(isMobile ? 0xffffff : 0x8D8D8F)
},
},
vertexShader: document.getElementById(
'vertexshader').textContent,
fragmentShader: document.getElementById(
'fragmentshader').textContent
});
particles = new THREE.Points(geometry, material);
scene.add(particles);
renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
container.appendChild(renderer.domElement);
document.addEventListener('mousemove', onDocumentMouseMove,
false);
window.addEventListener('resize', onWindowResize, false);
if (isMobile) {
window.addEventListener("deviceorientation", handleOrientation, true);
}
}
function handleOrientation(e) {
var absolute = e.absolute;
var alpha = e.alpha;// x -90 ... 90
var beta = e.beta;// y 180 ... 0
var gamma = e.gamma;// x -90 ... 90
mouseX = -5 * windowHalfX * (gamma / 90);
//mouseY = -windowHalfY * ((beta - 90) / 90);
// console.log(mouseX.toFixed(2), ' x ', mouseY.toFixed(2));
}
function onWindowResize() {
windowHalfX = window.innerWidth / 2;
windowHalfY = window.innerHeight / 2;
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
//
function onDocumentMouseMove(event) {
mouseX = event.clientX - windowHalfX;
mouseY = event.clientY - windowHalfY;
}
//
function animate() {
requestAnimationFrame(animate);
render();
if (isMobile) {
controls.update();
}
}
function render() {
camera.position.x += (mouseX - camera.position.x) * .05;
if(isMobile) {
camera.position.y = 550;
} else {
camera.position.y += (-mouseY - camera.position.y) * .05;
}
camera.lookAt(scene.position);
var positions = particles.geometry.attributes.position.array;
var scales = particles.geometry.attributes.scale.array;
var i = 0,
j = 0;
for (var ix = 0; ix < AMOUNTX; ix++) {
for (var iy = 0; iy < AMOUNTY; iy++) {
positions[i + 1] = (Math.sin((ix + count) * 0.3) * 50) +
(Math.sin((iy + count) * 0.5) * 50);
scales[j] = (Math.sin((ix + count) * 0.3) + 1) * 8 +
(Math.sin((iy + count) * 0.5) + 1) * 8;
i += 3;
j++;
}
}
particles.geometry.attributes.position.needsUpdate = true;
particles.geometry.attributes.scale.needsUpdate = true;
renderer.render(scene, camera);
count += 0.1;
}
function isOSMobile() {
var userAgent = navigator.userAgent || navigator.vendor || window.opera;
if (/android/i.test(userAgent)) {
return true;
}
if (/iPad|iPhone|iPod/.test(userAgent) && !window.MSStream) {
return true;
}
return false;
}

Rotating an 3D object around it's y-axis in three.js

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.

get boundaries of complex object in image with white background (js)

What is a good way of getting the boundaries for an image (not the image itself, but rather the non-white pixels)? I am using javascript, so try to keep the algos within that realm if you can.
For example, how would I get a list/two lists of all of the x and y points where for the points that exist on the boundary of this (group of) object(s):
Note that the interior part should be included, but a lack of colour that is completely on the inside (like a hole) should be excluded.
Therefore, the result would be two lists that contain the x and y points (for the pixels) that would construct an object similar to this:
Below is how I "achieved" it. While it works for all concave objects, if you try to use it for more complex objects with some convex sides, it inevitably fails.
Success with Concave Object
jsfiddle: https://jsfiddle.net/bhc4qn87/
snippet:
var _PI = Math.PI, _HALF_PI = Math.PI / 2, _TWO_PI = 2 * Math.PI;
var _radius = 10, _damp = 75, _center = new THREE.Vector3(0, 0, 0);
var _phi = _PI / 2, _theta = _theta = _PI / 7;
var _sceneScreenshot = null, _dirty = true;
var _tmpCan = document.createElement("canvas"),
_tmpCtx = _tmpCan.getContext("2d");
var scene = document.getElementById("scene"),
sw = scene.width, sh = scene.height;
var _scene = new THREE.Scene();
var _renderer = new THREE.WebGLRenderer({ canvas: scene, alpha: true, antialias: true });
_renderer.setPixelRatio(window.devicePixelRatio);
_renderer.setSize(sw, sh);
var _camera = new THREE.PerspectiveCamera(35, sw / sh, .1, 1000);
_tmpCan.width = sw; _tmpCan.height = sh;
_scene.add(new THREE.HemisphereLight(0x999999, 0x555555, 1));
_scene.add(new THREE.AmbientLight(0x404040));
var _camLight = new THREE.PointLight(0xdfdfdf, 1.8, 300, 2);
_scene.add(_camLight);
var geometry = new THREE.BoxBufferGeometry( 1, 1, 1 );
var material = new THREE.MeshPhysicalMaterial( { color: 0x2378d3, opacity: .7 } );
var cube = new THREE.Mesh( geometry, material );
_scene.add( cube );
function initialize() {
document.body.appendChild(_tmpCan);
_tmpCan.style.position = "absolute";
_tmpCan.style.left = "8px";
_tmpCan.style.top = "8px";
_tmpCan.style.pointerEvents = "none";
addListeners();
updateCamera();
animate();
}
function addListeners() {
/* mouse events */
var scene = document.getElementById("scene");
scene.oncontextmenu = function(e) {
e.preventDefault();
}
scene.onmousedown = function(e) {
e.preventDefault();
mouseTouchDown(e.pageX, e.pageY, e.button);
}
scene.ontouchstart = function(e) {
if (e.touches.length !== 1) {
return;
}
e.preventDefault();
mouseTouchDown(e.touches[0].pageX, e.touches[0].pageY, e.touches.length, true);
}
function mouseTouchDown(pageX, pageY, button, touch) {
_mouseX = pageX; _mouseY = pageY;
_button = button;
if (touch) {
document.ontouchmove = function(e) {
if (e.touches.length !== 1) {
return;
}
mouseTouchMove(e.touches[0].pageX, e.touches[0].pageY, e.touches.length, true);
}
document.ontouchend = function() {
document.ontouchmove = null;
document.ontouchend = null;
}
} else {
document.onmousemove = function(e) {
mouseTouchMove(e.pageX, e.pageY, _button);
}
document.onmouseup = function() {
document.onmousemove = null;
document.onmouseup = null;
}
}
}
function mouseTouchMove(pageX, pageY, button, touch) {
var dx = pageX - _mouseX,
dy = pageY - _mouseY;
_phi += dx / _damp;
// _theta += dy / _damp;
_phi %= _TWO_PI;
if (_phi < 0) {
_phi += _TWO_PI;
}
// var maxTheta = _HALF_PI - _HALF_PI * .8,
// minTheta = -_HALF_PI + _HALF_PI * .8;
// if (_theta > maxTheta) {
// _theta = maxTheta;
// } else if (_theta < minTheta) {
// _theta = minTheta;
// }
updateCamera();
_dirty = true;
// updateLabels();
_mouseX = pageX;
_mouseY = pageY;
}
}
function updateCamera() {
// var radius = _radius + (Math.sin(_theta % _PI)) * 10;
var radius = _radius;
var y = radius * Math.sin(_theta),
phiR = radius * Math.cos(_theta);
var z = phiR * Math.sin(_phi),
x = phiR * Math.cos(_phi);
_camera.position.set(x, y, z);
_camLight.position.set(x, y, z);
_camera.lookAt(_center);
}
function updateLabels() {
if (_sceneScreenshot === null) {
return;
}
var tmpImg = new Image();
tmpImg.onload = function() {
_tmpCtx.drawImage(tmpImg, 0, 0, sw, sh);
var imgData = _tmpCtx.getImageData(0, 0, sw, sh);
var data = imgData.data;
var firstXs = [];
var lastXs = [];
for (var y = 0; y < sh; y++) {
var firstX = -1;
var lastX = -1;
for (var x = 0; x < sw; x++) {
var i = (x + y * sw) * 4;
var sum = data[i] + data[i + 1] + data[i + 2];
if (firstX === -1) {
if (sum > 3) {
firstX = x;
}
} else {
if (sum > 3) {
lastX = x;
}
}
}
if (lastX === -1 && firstX >= 0) {
lastX = firstX;
}
firstXs.push(firstX);
lastXs.push(lastX);
}
var firstYs = [];
var lastYs = [];
for (var x = 0; x < sw; x++) {
var firstY = -1;
var lastY = -1;
for (var y = 0; y < sh; y++) {
var i = (x + y * sw) * 4;
var sum = data[i] + data[i + 1] + data[i + 2];
if (firstY === -1) {
if (sum < 759) {
firstY = y;
}
} else {
if (sum < 759) {
lastY = y;
}
}
}
if (lastY === -1 && firstY >= 0) {
lastY = firstY;
}
firstYs.push(firstY);
lastYs.push(lastY);
}
postLoad(firstXs, lastXs, firstYs, lastYs);
}
tmpImg.src = _sceneScreenshot;
function postLoad(firstXs, lastXs, firstYs, lastYs) {
_tmpCtx.clearRect(0, 0, sw, sh);
_tmpCtx.beginPath();
for (var y = 0; y < sh; y++) {
_tmpCtx.moveTo(firstXs[y], y);
_tmpCtx.lineTo(lastXs[y], y);
}
/* TODO REMOVE BELOW TODO */
_tmpCtx.strokeStyle = 'black';
console.log(_tmpCtx.globalAlpha);
_tmpCtx.stroke();
/* TODO REMOVE ABOVE TODO */
_tmpCtx.beginPath();
for (var x = 0; x < sw; x++) {
_tmpCtx.moveTo(x, firstYs[x]);
_tmpCtx.lineTo(x, lastYs[x]);
}
/* TODO REMOVE BELOW TODO */
_tmpCtx.strokeStyle = 'black';
_tmpCtx.stroke();
/* TODO REMOVE ABOVE TODO */
var imgData = _tmpCtx.getImageData(0, 0, sw, sh);
var data = imgData.data;
for (var i = 0, iLen = data.length; i < iLen; i += 4) {
if (data[i + 3] < 200) {
data[i + 3] = 0;
}
/* TODO remove v TODO */
else { data[i + 3] = 120; }
}
_tmpCtx.putImageData(imgData, 0, 0);
}
}
function animate () {
cube.rotation.x += 0.001;
cube.rotation.y += 0.001;
_renderer.render(_scene, _camera);
if (_dirty) {
_sceneScreenshot = _renderer.domElement.toDataURL();
updateLabels();
_dirty = false;
}
requestAnimationFrame( animate );
}
initialize();
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/92/three.js"></script>
<canvas id="scene" width="400" height="300"></canvas>
Failure with Complex Object
jsfiddle: https://jsfiddle.net/xdr9bt0w/
var _PI = Math.PI, _HALF_PI = Math.PI / 2, _TWO_PI = 2 * Math.PI;
var _radius = 10, _damp = 75, _center = new THREE.Vector3(0, 0, 0);
var _phi = _PI / 2, _theta = _theta = 0;
var _sceneScreenshot = null, _dirty = true;
var _tmpCan = document.createElement("canvas"),
_tmpCtx = _tmpCan.getContext("2d");
var scene = document.getElementById("scene"),
sw = scene.width, sh = scene.height;
var _scene = new THREE.Scene();
var _renderer = new THREE.WebGLRenderer({ canvas: scene, alpha: true, antialias: true });
_renderer.setPixelRatio(window.devicePixelRatio);
_renderer.setSize(sw, sh);
var _camera = new THREE.PerspectiveCamera(35, sw / sh, .1, 1000);
_tmpCan.width = sw; _tmpCan.height = sh;
_scene.add(new THREE.HemisphereLight(0x999999, 0x555555, 1));
_scene.add(new THREE.AmbientLight(0x404040));
var _camLight = new THREE.PointLight(0xdfdfdf, 1.8, 300, 2);
_scene.add(_camLight);
var geometry = new THREE.BoxBufferGeometry( 1, 1, 1 );
var material = new THREE.MeshPhysicalMaterial( { color: 0x2378d3, opacity: .7 } );
var cube = new THREE.Mesh( geometry, material );
_scene.add( cube );
var geometry = new THREE.BoxBufferGeometry( 1, 1, 1 );
var material = new THREE.MeshPhysicalMaterial( { color: 0xc36843, opacity: .7 } );
var cube2 = new THREE.Mesh( geometry, material );
cube2.position.x = -.75;
cube2.position.y = .75
_scene.add( cube2 );
var geometry = new THREE.BoxBufferGeometry( 1, 1, 1 );
var material = new THREE.MeshPhysicalMaterial( { color: 0x43f873, opacity: .7 } );
var cube3 = new THREE.Mesh( geometry, material );
cube3.position.x = -.25;
cube3.position.y = 1.5;
_scene.add( cube3 );
var geometry = new THREE.BoxBufferGeometry( 1, 1, 1 );
var material = new THREE.MeshPhysicalMaterial( { color: 0x253621, opacity: .7 } );
var cube4 = new THREE.Mesh( geometry, material );
cube4.position.x = 1;
cube4.position.y = .35;
_scene.add( cube4 );
function initialize() {
document.body.appendChild(_tmpCan);
_tmpCan.style.position = "absolute";
_tmpCan.style.left = "200px";
_tmpCan.style.top = "0px";
_tmpCan.style.pointerEvents = "none";
addListeners();
updateCamera();
animate();
}
function addListeners() {
/* mouse events */
var scene = document.getElementById("scene");
scene.oncontextmenu = function(e) {
e.preventDefault();
}
scene.onmousedown = function(e) {
e.preventDefault();
mouseTouchDown(e.pageX, e.pageY, e.button);
}
scene.ontouchstart = function(e) {
if (e.touches.length !== 1) {
return;
}
e.preventDefault();
mouseTouchDown(e.touches[0].pageX, e.touches[0].pageY, e.touches.length, true);
}
function mouseTouchDown(pageX, pageY, button, touch) {
_mouseX = pageX; _mouseY = pageY;
_button = button;
if (touch) {
document.ontouchmove = function(e) {
if (e.touches.length !== 1) {
return;
}
mouseTouchMove(e.touches[0].pageX, e.touches[0].pageY, e.touches.length, true);
}
document.ontouchend = function() {
document.ontouchmove = null;
document.ontouchend = null;
}
} else {
document.onmousemove = function(e) {
mouseTouchMove(e.pageX, e.pageY, _button);
}
document.onmouseup = function() {
document.onmousemove = null;
document.onmouseup = null;
}
}
}
function mouseTouchMove(pageX, pageY, button, touch) {
var dx = pageX - _mouseX,
dy = pageY - _mouseY;
_phi += dx / _damp;
// _theta += dy / _damp;
_phi %= _TWO_PI;
if (_phi < 0) {
_phi += _TWO_PI;
}
// var maxTheta = _HALF_PI - _HALF_PI * .8,
// minTheta = -_HALF_PI + _HALF_PI * .8;
// if (_theta > maxTheta) {
// _theta = maxTheta;
// } else if (_theta < minTheta) {
// _theta = minTheta;
// }
updateCamera();
_dirty = true;
// updateLabels();
_mouseX = pageX;
_mouseY = pageY;
}
}
function updateCamera() {
// var radius = _radius + (Math.sin(_theta % _PI)) * 10;
var radius = _radius;
var y = radius * Math.sin(_theta),
phiR = radius * Math.cos(_theta);
var z = phiR * Math.sin(_phi),
x = phiR * Math.cos(_phi);
_camera.position.set(x, y, z);
_camLight.position.set(x, y, z);
_camera.lookAt(_center);
}
function updateLabels() {
if (_sceneScreenshot === null) {
return;
}
var tmpImg = new Image();
tmpImg.onload = function() {
_tmpCtx.drawImage(tmpImg, 0, 0, sw, sh);
var imgData = _tmpCtx.getImageData(0, 0, sw, sh);
var data = imgData.data;
var firstXs = [];
var lastXs = [];
for (var y = 0; y < sh; y++) {
var firstX = -1;
var lastX = -1;
for (var x = 0; x < sw; x++) {
var i = (x + y * sw) * 4;
var sum = data[i] + data[i + 1] + data[i + 2];
if (firstX === -1) {
if (sum > 3) {
firstX = x;
}
} else {
if (sum > 3) {
lastX = x;
}
}
}
if (lastX === -1 && firstX >= 0) {
lastX = firstX;
}
firstXs.push(firstX);
lastXs.push(lastX);
}
var firstYs = [];
var lastYs = [];
for (var x = 0; x < sw; x++) {
var firstY = -1;
var lastY = -1;
for (var y = 0; y < sh; y++) {
var i = (x + y * sw) * 4;
var sum = data[i] + data[i + 1] + data[i + 2];
if (firstY === -1) {
if (sum > 3) {
firstY = y;
}
} else {
if (sum > 3) {
lastY = y;
}
}
}
if (lastY === -1 && firstY >= 0) {
lastY = firstY;
}
firstYs.push(firstY);
lastYs.push(lastY);
}
postLoad(firstXs, lastXs, firstYs, lastYs);
}
tmpImg.src = _sceneScreenshot;
function postLoad(firstXs, lastXs, firstYs, lastYs) {
_tmpCtx.clearRect(0, 0, sw, sh);
_tmpCtx.beginPath();
for (var y = 0; y < sh; y++) {
_tmpCtx.moveTo(firstXs[y], y);
_tmpCtx.lineTo(lastXs[y], y);
}
/* TODO REMOVE BELOW TODO */
_tmpCtx.strokeStyle = 'black';
console.log(_tmpCtx.globalAlpha);
_tmpCtx.stroke();
/* TODO REMOVE ABOVE TODO */
_tmpCtx.beginPath();
for (var x = 0; x < sw; x++) {
_tmpCtx.moveTo(x, firstYs[x]);
_tmpCtx.lineTo(x, lastYs[x]);
}
/* TODO REMOVE BELOW TODO */
_tmpCtx.strokeStyle = 'black';
_tmpCtx.stroke();
/* TODO REMOVE ABOVE TODO */
var imgData = _tmpCtx.getImageData(0, 0, sw, sh);
var data = imgData.data;
for (var i = 0, iLen = data.length; i < iLen; i += 4) {
if (data[i + 3] < 200) {
data[i + 3] = 0;
}
/* TODO remove v TODO */
else { data[i + 3] = 120; }
}
_tmpCtx.putImageData(imgData, 0, 0);
}
}
function animate () {
cube.rotation.x += 0.001;
cube.rotation.y += 0.001;
cube2.rotation.x -= 0.001;
cube2.rotation.y += 0.001;
cube3.rotation.x += 0.001;
cube3.rotation.y -= 0.001;
cube4.rotation.x -= 0.001;
cube4.rotation.y -= 0.001;
_renderer.render(_scene, _camera);
if (_dirty) {
_sceneScreenshot = _renderer.domElement.toDataURL();
updateLabels();
_dirty = false;
}
requestAnimationFrame( animate );
}
initialize();
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/92/three.js"></script>
<canvas id="scene" width="400" height="300"></canvas>
You can see with the above jsfiddle that the inside of this complex, convex image fails on the inside.
Question
Therefore, the question remains: what is a good way of creating a mask, if you will, of the image (disregarding holes) that will cover all of the outside of any complex/convex object where the background is white and the components of the image are anything but white? thanks
Here is a solution that uses a flood-fill algorithm to cover the outer areas with white and the rest with black.
Keep in mind that this is a very naive implementation, there are lots of optimization that can potentially be done (by calculating the bounding rectangle and only filling inside it for example, another one would be to use 32-bit arrays to do the actual pixel assignment while filling).
Another thing to note is that the filling always starts in the upper left corner, if the object is currently covering that pixel it will not work (you can however pick another pixel to start at).
I removed the touch handlers and some other items to keep the example short.
The updateMask-function is where the mask is created.
function createCube(color, x, y){
const geo = new THREE.BoxBufferGeometry( 1, 1, 1 );
const mat = new THREE.MeshPhysicalMaterial( { color: color, opacity: 1 } );
const mesh = new THREE.Mesh(geo, mat);
mesh.position.x = x;
mesh.position.y = y;
return mesh;
}
const c_main = document.getElementById("main");
const c_mask = document.getElementById("mask");
const ctx_mask = c_mask.getContext("2d");
ctx_mask.fillStyle = "#000";
const cw = c_main.width, ch = c_main.height;
const TWO_PI = Math.PI * 2;
const damp = 75, radius = 10, animspeed = 0.001;
const center = new THREE.Vector3(0, 0, 0);
let x1 = 0;
let phi = Math.PI / 2;
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(35, cw / ch, 0.1, 1000);
const renderer = new THREE.WebGLRenderer({ canvas: c_main, alpha: true, antialias: false });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(cw, ch);
const camLight = new THREE.PointLight(0xdfdfdf, 1.8, 300, 2);
scene.add(new THREE.HemisphereLight(0x999999, 0x555555, 1));
scene.add(new THREE.AmbientLight(0x404040));
scene.add(camLight);
const cubes = [];
cubes.push(createCube(0x2378d3, 0, 0));
cubes.push(createCube(0xc36843, -0.75, 0.75));
cubes.push(createCube(0x43f873, -0.25, 1.5));
cubes.push(createCube(0x253621, 1, 0.35));
scene.add(...cubes);
function initialize() {
c_main.addEventListener("mousedown", mouseDown, false);
updateCamera();
animate();
}
function updateMask(){
//First, fill the canvas with black
ctx_mask.globalCompositeOperation = "source-over";
ctx_mask.fillRect(0,0, cw, ch);
//Then using the composite operation "destination-in" the canvas is made transparent EXCEPT where the new image is drawn.
ctx_mask.globalCompositeOperation = "destination-in";
ctx_mask.drawImage(c_main, 0, 0);
//Now, use a flood fill algorithm of your choice to fill the outer transparent field with white.
const idata = ctx_mask.getImageData(0,0, cw, ch);
const array = idata.data;
floodFill(array, 0, 0, cw, ch);
ctx_mask.putImageData(idata, 0, 0);
//The only transparency left are in the "holes", we make these black by using the composite operation "destination-over" to paint black behind everything.
ctx_mask.globalCompositeOperation = "destination-over";
ctx_mask.fillRect(0,0, cw, ch);
}
function mouseDown(e){
e.preventDefault();
x1 = e.pageX;
const button = e.button;
document.addEventListener("mousemove", mouseMove, false);
document.addEventListener("mouseup", mouseUp, false);
}
function mouseUp(){
document.removeEventListener("mousemove", mouseMove, false);
document.removeEventListener("mouseup", mouseUp, false);
}
function mouseMove(e){
const x2 = e.pageX;
const dx = x2 - x1;
phi += dx/damp;
phi %= TWO_PI;
if( phi < 0 ){
phi += TWO_PI;
}
x1 = x2;
updateCamera();
}
function updateCamera() {
const x = radius * Math.cos(phi);
const y = 0;
const z = radius * Math.sin(phi);
camera.position.set(x, y, z);
camera.lookAt(center);
camLight.position.set(x, y, z);
}
function animate(){
cubes[0].rotation.x += animspeed;
cubes[0].rotation.y += animspeed;
cubes[1].rotation.x -= animspeed;
cubes[1].rotation.y += animspeed;
cubes[2].rotation.x += animspeed;
cubes[2].rotation.y -= animspeed;
cubes[3].rotation.x -= animspeed;
cubes[3].rotation.y -= animspeed;
renderer.render(scene, camera);
updateMask();
requestAnimationFrame(animate);
}
const FILL_THRESHOLD = 254;
//Quickly adapted flood fill from http://www.adammil.net/blog/v126_A_More_Efficient_Flood_Fill.html
function floodStart(array, x, y, width, height){
const M = width * 4;
while(true){
let ox = x, oy = y;
while(y !== 0 && array[(y-1)*M + x*4 + 3] < FILL_THRESHOLD){ y--; }
while(x !== 0 && array[y*M + (x-1)*4 + 3] < FILL_THRESHOLD){ x--; }
if(x === ox && y === oy){ break; }
}
floodFill(array, x, y, width, height);
}
function floodFill(array, x, y, width, height){
const M = width * 4;
let lastRowLength = 0;
do{
let rowLength = 0, sx = x;
let idx = y*M + x*4 + 3;
if(lastRowLength !== 0 && array[idx] >= FILL_THRESHOLD){
do{
if(--lastRowLength === 0){ return; }
}
while(array[ y*M + (++x)*4 + 3]);
sx = x;
}
else{
for(; x !== 0 && array[y*M + (x-1)*4 + 3] < FILL_THRESHOLD; rowLength++, lastRowLength++){
const idx = y*M + (--x)*4;
array[idx] = 255;
array[idx + 1] = 255;
array[idx + 2] = 255;
array[idx + 3] = 255;
if( y !== 0 && array[(y-1)*M + x*4 + 3] < FILL_THRESHOLD ){
floodStart(array, x, y-1, width, height);
}
}
}
for(; sx < width && array[y*M + sx*4 + 3] < FILL_THRESHOLD; rowLength++, sx++){
const idx = y*M + sx*4;
array[idx] = 255;
array[idx + 1] = 255;
array[idx + 2] = 255;
array[idx + 3] = 255;
}
if(rowLength < lastRowLength){
for(let end=x+lastRowLength; ++sx < end; ){
if(array[y*M + sx*4 + 3] < FILL_THRESHOLD){
floodFill(array, sx, y, width, height);
}
}
}
else if(rowLength > lastRowLength && y !== 0){
for(let ux=x+lastRowLength; ++ux<sx; ){
if(array[(y-1)*M + ux*4 + 3] < FILL_THRESHOLD){
floodStart(array, ux, y-1, width, height);
}
}
}
lastRowLength = rowLength;
}
while(lastRowLength !== 0 && ++y < height);
}
initialize();
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/92/three.js"></script>
<canvas id="main" width="300" height="200"></canvas>
<canvas id="mask" width="300" height="200"></canvas>

Categories

Resources