'plus' or 'x' shaped wave in threejs - javascript

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

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.

Gif not playing over canvas animation on Firefox

I'm having this weird bug on Firefox where a gif used as backgound image in not playing when there's a canvas animation. Like on this example. If you check on Webkit, it's totally fine.
const separation = 100, amountX = 70, amountY = 50;
let container, camera, scene, renderer, particles;
let count = 0, windowHalfX = window.innerWidth / 2, windowHalfY = window.innerHeight / 2, cameraPosition = 80;
const init = () => {
container = document.createElement('div');
document.body.appendChild(container);
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 10000);
camera.position.x = cameraPosition;
camera.position.y = 1000;
camera.position.z = -550;
camera.zoom = 1.2;
scene = new THREE.Scene();
const numParticles = amountX * amountY;
const positions = new Float32Array(numParticles * 3);
const scales = new Float32Array(numParticles);
let i = 0, j = 0;
for(let ix = 0; ix < amountX; ix++) {
for(let iy = 0; iy < amountY; iy++) {
positions[i] = ix * separation - ((amountX * separation ) / 2);
positions[i + 1] = 0;
positions[i + 2] = iy * separation - ((amountY * separation ) / 2);
scales[j] = 1;
i += 3;
j ++;
}
}
const geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
geometry.setAttribute('scale', new THREE.BufferAttribute(scales, 1));
const material = new THREE.ShaderMaterial({
uniforms: {
color: {value: new THREE.Color(0xeae6c3)},
},
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.setClearColor(0x192735, 1);
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
container.appendChild(renderer.domElement);
container.style.touchAction = 'none';
window.addEventListener( 'resize', onWindowResize );
};
const onWindowResize = () => {
windowHalfX = window.innerWidth / 2;
windowHalfY = window.innerHeight / 2;
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
};
const render = () => {
camera.lookAt(scene.position);
const positions = particles.geometry.attributes.position.array;
const scales = particles.geometry.attributes.scale.array;
let i = 0, j = 0;
for (let ix = 0; ix < amountX; ix ++) {
for (let 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) * 7 + (Math.sin((iy + count) * 0.5) + 1) * 7;
i += 3;
j ++;
}
}
particles.geometry.attributes.position.needsUpdate = true;
particles.geometry.attributes.scale.needsUpdate = true;
renderer.render(scene, camera);
count += 0.011;
};
const animate = () => {
requestAnimationFrame(animate);
render();
};
init();
animate();
const moveCamera = () => {
cameraPosition = 150;
};
body::after {
content: '';
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 1000;
background-image: url(https://res.cloudinary.com/axiol/image/upload/v1612477975/CodePen/noise.gif);
opacity: 0.05;
pointer-events: none;
}
canvas {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
display: none;
z-index: -1;
}
<script src="https://unpkg.com/three#0.125.2/build/three.min.js"></script>
<script type="x-shader/x-vertex" id="vertexshader">
attribute float scale;
void main() {
vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
gl_PointSize = scale * (300.0 / - mvPosition.z);
gl_Position = projectionMatrix * mvPosition;
}
</script>
<script type="x-shader/x-fragment" id="fragmentshader">
uniform vec3 color;
void main() {
if (length(gl_PointCoord - vec2(0.5, 0.5)) > 0.475 ) discard;
gl_FragColor = vec4(color, 1.0);
}
</script>
And I really have no idea where this could come from. On another test I have the same issue. But on this second one the canvas is reacting to the mouse movements. And when the mouse moves, the gif is animated. But once it stop the gif stops.
Anybody already saw that?
This is indeed a recent (one week) regression, apparently caused by this commit.
I did open https://bugzilla.mozilla.org/1692736, let's hope they can fix it before it lands on stable branch.
You can workaround this bug either by disabling WebRender in your own Firefox by going to about:config and then toggle gfx.webrender.force-disabled to false, or by forcing a re-rendering of the element where the gif is rendered, e.g through a minimal opacity variation in a CSS animation:
const separation = 100, amountX = 70, amountY = 50;
let container, camera, scene, renderer, particles;
let count = 0, windowHalfX = window.innerWidth / 2, windowHalfY = window.innerHeight / 2, cameraPosition = 80;
const init = () => {
container = document.createElement('div');
document.body.appendChild(container);
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 10000);
camera.position.x = cameraPosition;
camera.position.y = 1000;
camera.position.z = -550;
camera.zoom = 1.2;
scene = new THREE.Scene();
const numParticles = amountX * amountY;
const positions = new Float32Array(numParticles * 3);
const scales = new Float32Array(numParticles);
let i = 0, j = 0;
for(let ix = 0; ix < amountX; ix++) {
for(let iy = 0; iy < amountY; iy++) {
positions[i] = ix * separation - ((amountX * separation ) / 2);
positions[i + 1] = 0;
positions[i + 2] = iy * separation - ((amountY * separation ) / 2);
scales[j] = 1;
i += 3;
j ++;
}
}
const geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
geometry.setAttribute('scale', new THREE.BufferAttribute(scales, 1));
const material = new THREE.ShaderMaterial({
uniforms: {
color: {value: new THREE.Color(0xeae6c3)},
},
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.setClearColor(0x192735, 1);
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
container.appendChild(renderer.domElement);
container.style.touchAction = 'none';
window.addEventListener( 'resize', onWindowResize );
};
const onWindowResize = () => {
windowHalfX = window.innerWidth / 2;
windowHalfY = window.innerHeight / 2;
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
};
const render = () => {
camera.lookAt(scene.position);
const positions = particles.geometry.attributes.position.array;
const scales = particles.geometry.attributes.scale.array;
let i = 0, j = 0;
for (let ix = 0; ix < amountX; ix ++) {
for (let 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) * 7 + (Math.sin((iy + count) * 0.5) + 1) * 7;
i += 3;
j ++;
}
}
particles.geometry.attributes.position.needsUpdate = true;
particles.geometry.attributes.scale.needsUpdate = true;
renderer.render(scene, camera);
count += 0.011;
};
const animate = () => {
requestAnimationFrame(animate);
render();
};
init();
animate();
const moveCamera = () => {
cameraPosition = 150;
};
/*
we animate a very small opacity variation
to force rerendering of the gif image
*/
#keyframes bug1692736 { to { opacity: 0.051; } }
body::after {
animation: bug1692736 10s infinite;
content: '';
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 1000;
background-image: url(https://res.cloudinary.com/axiol/image/upload/v1612477975/CodePen/noise.gif);
opacity: 0.05;
pointer-events: none;
}
canvas {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
display: none;
z-index: -1;
}
<script src="https://unpkg.com/three#0.125.2/build/three.min.js"></script>
<script type="x-shader/x-vertex" id="vertexshader">
attribute float scale;
void main() {
vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
gl_PointSize = scale * (300.0 / - mvPosition.z);
gl_Position = projectionMatrix * mvPosition;
}
</script>
<script type="x-shader/x-fragment" id="fragmentshader">
uniform vec3 color;
void main() {
if (length(gl_PointCoord - vec2(0.5, 0.5)) > 0.475 ) discard;
gl_FragColor = vec4(color, 1.0);
}
</script>

Trying to simulate a 3D effect via Three.js

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

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>

Use same animation for multiple elements

I have this working canvas javascript animation but i would like to use it multiple times, currently it's only possible to have one canvas element with the id "stars" and use that one. Could i perhaps add a class for them instead and get the elements class and loop or what would be my best solution for achieving this? I would like to make this work without repeating to much since i could end up using the animation on different pages.
// Settings
var particleCount = 40,
flareCount = 0,
motion = 0.05,
tilt = 0.05,
color = '#00FF7B',
particleSizeBase = 1,
particleSizeMultiplier = 0.5,
flareSizeBase = 100,
flareSizeMultiplier = 100,
lineWidth = 1,
linkChance = 75, // chance per frame of link, higher = smaller chance
linkLengthMin = 5, // min linked vertices
linkLengthMax = 7, // max linked vertices
linkOpacity = 0.25; // number between 0 & 1
linkFade = 90, // link fade-out frames
linkSpeed = 0, // distance a link travels in 1 frame
glareAngle = -60,
glareOpacityMultiplier = 0.4,
renderParticles = true,
renderParticleGlare = true,
renderFlares = false,
renderLinks = false,
renderMesh = false,
flicker = false,
flickerSmoothing = 15, // higher = smoother flicker
blurSize = 0,
orbitTilt = true,
randomMotion = true,
noiseLength = 1000,
noiseStrength = 3;
var canvas = document.getElementById('stars'),
context = canvas.getContext('2d'),
mouse = {
x: 0,
y: 0
},
m = {},
r = 0,
c = 1000, // multiplier for delaunay points, since floats too small can mess up the algorithm
n = 0,
nAngle = (Math.PI * 2) / noiseLength,
nRad = 100,
nScale = 0.5,
nPos = {
x: 0,
y: 0
},
points = [],
vertices = [],
triangles = [],
links = [],
particles = [],
flares = [];
function init() {
var i, j, k;
// requestAnimFrame polyfill
window.requestAnimFrame = (function() {
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
function(callback) {
window.setTimeout(callback, 1000 / 60);
};
})();
// Size canvas
resize();
mouse.x = canvas.clientWidth / 2;
mouse.y = canvas.clientHeight / 2;
// Create particle positions
for (i = 0; i < particleCount; i++) {
var p = new Particle();
particles.push(p);
points.push([p.x * c, p.y * c]);
}
vertices = Delaunay.triangulate(points);
var tri = [];
for (i = 0; i < vertices.length; i++) {
if (tri.length == 3) {
triangles.push(tri);
tri = [];
}
tri.push(vertices[i]);
}
// Tell all the particles who their neighbors are
for (i = 0; i < particles.length; i++) {
// Loop through all tirangles
for (j = 0; j < triangles.length; j++) {
// Check if this particle's index is in this triangle
k = triangles[j].indexOf(i);
// If it is, add its neighbors to the particles contacts list
if (k !== -1) {
triangles[j].forEach(function(value, index, array) {
if (value !== i && particles[i].neighbors.indexOf(value) == -1) {
particles[i].neighbors.push(value);
}
});
}
}
}
var fps = 15;
var now;
var then = Date.now();
var interval = 1000 / fps;
var delta;
// Animation loop
(function animloop() {
requestAnimFrame(animloop);
now = Date.now();
delta = now - then;
if (delta > interval) {
then = now - (delta % interval);
resize();
render();
}
})();
}
function render() {
if (randomMotion) {
n++;
if (n >= noiseLength) {
n = 0;
}
nPos = noisePoint(n);
}
if (renderParticles) {
// Render particles
for (var i = 0; i < particleCount; i++) {
particles[i].render();
}
}
}
function resize() {
canvas.width = window.innerWidth * (window.devicePixelRatio || 1);
canvas.height = canvas.width * (canvas.clientHeight / canvas.clientWidth);
}
// Particle class
var Particle = function() {
this.x = random(-0.1, 1.1, true);
this.y = random(-0.1, 1.1, true);
this.z = random(0, 4);
this.color = color;
this.opacity = random(0.1, 1, true);
this.flicker = 0;
this.neighbors = []; // placeholder for neighbors
};
Particle.prototype.render = function() {
var pos = position(this.x, this.y, this.z),
r = ((this.z * particleSizeMultiplier) + particleSizeBase) * (sizeRatio() / 1000),
o = this.opacity;
context.fillStyle = this.color;
context.globalAlpha = o;
context.beginPath();
context.fill();
context.closePath();
if (renderParticleGlare) {
context.globalAlpha = o * glareOpacityMultiplier;
context.ellipse(pos.x, pos.y, r * 100, r, (glareAngle - ((nPos.x - 0.5) * noiseStrength * motion)) * (Math.PI / 180), 0, 2 * Math.PI, false);
context.fill();
context.closePath();
}
context.globalAlpha = 1;
};
// Flare class
// Link class
var Link = function(startVertex, numPoints) {
this.length = numPoints;
this.verts = [startVertex];
this.stage = 0;
this.linked = [startVertex];
this.distances = [];
this.traveled = 0;
this.fade = 0;
this.finished = false;
};
// Utils
function noisePoint(i) {
var a = nAngle * i,
cosA = Math.cos(a),
sinA = Math.sin(a),
rad = nRad;
return {
x: rad * cosA,
y: rad * sinA
};
}
function position(x, y, z) {
return {
x: (x * canvas.width) + ((((canvas.width / 2) - mouse.x + ((nPos.x - 0.5) * noiseStrength)) * z) * motion),
y: (y * canvas.height) + ((((canvas.height / 2) - mouse.y + ((nPos.y - 0.5) * noiseStrength)) * z) * motion)
};
}
function sizeRatio() {
return canvas.width >= canvas.height ? canvas.width : canvas.height;
}
function random(min, max, float) {
return float ?
Math.random() * (max - min) + min :
Math.floor(Math.random() * (max - min + 1)) + min;
}
// init
if (canvas) init();
html,
body {
margin: 0;
padding: 0;
height: 100%;
}
body {
background: #000;
background-image: linear-gradient(-180deg, rgba(0, 0, 0, 0.00) 0%, #000000 100%);
}
#stars {
display: block;
position: relative;
width: 100%;
height: 100%;
z-index: 1;
position: absolute;
}
<script src="https://rawgit.com/ironwallaby/delaunay/master/delaunay.js"></script>
<script src="http://requirejs.org/docs/release/2.1.15/minified/require.js"></script>
<canvas id="stars" width="300" height="300"></canvas>
id="identifier" is unique
class="identifier" could be shared by a list of items
As you mention using class could be an option but you'll need to change your code to select all elements by that class before:
$(".identifier").each(function(a,b)
{
// Actions for each element
}
With javascript:
var elementList = document.getElementsByClassName("identifier");
var elementListSize=elementList.length;
for(var i=0;i<elementListSize;i++) {
// Actions for each element (elementList[i])
}

Categories

Resources