How to get rid of gray overlay on Three.js transition? - javascript

I have a transition between two pngs using three.js. The images have a white background, but they appear to have a gray tint. I know it is probably something obvious but I can't find where in the code this is specified. How do I get rid of this? Here is a demo on Codepen:
https://codepen.io/mastroneel/pen/Oazzyo
window.onload = init;
console.ward = function() {}; // what warnings?
function init() {
var root = new THREERoot({
createCameraControls: !true,
antialias: (window.devicePixelRatio === 1),
fov: 80
});
root.renderer.setClearColor(0x000000, 0);
root.renderer.setPixelRatio(window.devicePixelRatio || 1);
root.camera.position.set(0, 0, 60);
var width = 100;
var height = 100;
var slide = new Slide(width, height, 'out');
var l1 = new THREE.ImageLoader();
l1.setCrossOrigin('Anonymous');
slide.setImage(l1.load('https://image.ibb.co/f6mVsA/helmet.png'));
root.scene.add(slide);
var slide2 = new Slide(width, height, 'in');
var l2 = new THREE.ImageLoader();
l2.setCrossOrigin('Anonymous');
slide2.setImage(l2.load('https://image.ibb.co/mb1KkV/player.png'));
root.scene.add(slide2);
var tl = new TimelineMax({
repeat: -1,
repeatDelay: 1.0,
yoyo: true
});
tl.add(slide.transition(), 0);
tl.add(slide2.transition(), 0);
createTweenScrubber(tl);
window.addEventListener('keyup', function(e) {
if (e.keyCode === 80) {
tl.paused(!tl.paused());
}
});
}
////////////////////
// CLASSES
////////////////////
function Slide(width, height, animationPhase) {
var plane = new THREE.PlaneGeometry(width, height, width * 2, height * 2);
THREE.BAS.Utils.separateFaces(plane);
var geometry = new SlideGeometry(plane);
geometry.bufferUVs();
var aAnimation = geometry.createAttribute('aAnimation', 2);
var aStartPosition = geometry.createAttribute('aStartPosition', 3);
var aControl0 = geometry.createAttribute('aControl0', 3);
var aControl1 = geometry.createAttribute('aControl1', 3);
var aEndPosition = geometry.createAttribute('aEndPosition', 3);
var i, i2, i3, i4, v;
var minDuration = 0.8;
var maxDuration = 1.2;
var maxDelayX = 0.9;
var maxDelayY = 0.125;
var stretch = 0.11;
this.totalDuration = maxDuration + maxDelayX + maxDelayY + stretch;
var startPosition = new THREE.Vector3();
var control0 = new THREE.Vector3();
var control1 = new THREE.Vector3();
var endPosition = new THREE.Vector3();
var tempPoint = new THREE.Vector3();
function getControlPoint0(centroid) {
var signY = Math.sign(centroid.y);
tempPoint.x = THREE.Math.randFloat(0.1, 0.3) * 50;
tempPoint.y = signY * THREE.Math.randFloat(0.1, 0.3) * 70;
tempPoint.z = THREE.Math.randFloatSpread(20);
return tempPoint;
}
function getControlPoint1(centroid) {
var signY = Math.sign(centroid.y);
tempPoint.x = THREE.Math.randFloat(0.3, 0.6) * 50;
tempPoint.y = -signY * THREE.Math.randFloat(0.3, 0.6) * 70;
tempPoint.z = THREE.Math.randFloatSpread(20);
return tempPoint;
}
for (i = 0, i2 = 0, i3 = 0, i4 = 0; i < geometry.faceCount; i++, i2 += 6, i3 += 9, i4 += 12) {
var face = plane.faces[i];
var centroid = THREE.BAS.Utils.computeCentroid(plane, face);
// animation
var duration = THREE.Math.randFloat(minDuration, maxDuration);
var delayX = THREE.Math.mapLinear(centroid.x, -width * 0.5, width * 0.5, 0.0, maxDelayX);
var delayY;
if (animationPhase === 'in') {
delayY = THREE.Math.mapLinear(Math.abs(centroid.y), 0, height * 0.5, 0.0, maxDelayY)
} else {
delayY = THREE.Math.mapLinear(Math.abs(centroid.y), 0, height * 0.5, maxDelayY, 0.0)
}
for (v = 0; v < 6; v += 2) {
aAnimation.array[i2 + v] = delayX + delayY + (Math.random() * stretch * duration);
aAnimation.array[i2 + v + 1] = duration;
}
// positions
endPosition.copy(centroid);
startPosition.copy(centroid);
if (animationPhase === 'in') {
control0.copy(centroid).sub(getControlPoint0(centroid));
control1.copy(centroid).sub(getControlPoint1(centroid));
} else { // out
control0.copy(centroid).add(getControlPoint0(centroid));
control1.copy(centroid).add(getControlPoint1(centroid));
}
for (v = 0; v < 9; v += 3) {
aStartPosition.array[i3 + v] = startPosition.x;
aStartPosition.array[i3 + v + 1] = startPosition.y;
aStartPosition.array[i3 + v + 2] = startPosition.z;
aControl0.array[i3 + v] = control0.x;
aControl0.array[i3 + v + 1] = control0.y;
aControl0.array[i3 + v + 2] = control0.z;
aControl1.array[i3 + v] = control1.x;
aControl1.array[i3 + v + 1] = control1.y;
aControl1.array[i3 + v + 2] = control1.z;
aEndPosition.array[i3 + v] = endPosition.x;
aEndPosition.array[i3 + v + 1] = endPosition.y;
aEndPosition.array[i3 + v + 2] = endPosition.z;
}
}
var material = new THREE.BAS.BasicAnimationMaterial({
shading: THREE.FlatShading,
side: THREE.DoubleSide,
uniforms: {
uTime: {
type: 'f',
value: 0
}
},
shaderFunctions: [
THREE.BAS.ShaderChunk['cubic_bezier'],
//THREE.BAS.ShaderChunk[(animationPhase === 'in' ? 'ease_out_cubic' : 'ease_in_cubic')],
THREE.BAS.ShaderChunk['ease_in_out_cubic'],
THREE.BAS.ShaderChunk['quaternion_rotation']
],
shaderParameters: [
'uniform float uTime;',
'attribute vec2 aAnimation;',
'attribute vec3 aStartPosition;',
'attribute vec3 aControl0;',
'attribute vec3 aControl1;',
'attribute vec3 aEndPosition;',
],
shaderVertexInit: [
'float tDelay = aAnimation.x;',
'float tDuration = aAnimation.y;',
'float tTime = clamp(uTime - tDelay, 0.0, tDuration);',
'float tProgress = ease(tTime, 0.0, 1.0, tDuration);'
//'float tProgress = tTime / tDuration;'
],
shaderTransformPosition: [
(animationPhase === 'in' ? 'transformed *= tProgress;' : 'transformed *= 1.0 - tProgress;'),
'transformed += cubicBezier(aStartPosition, aControl0, aControl1, aEndPosition, tProgress);'
]
}, {
map: new THREE.Texture(),
});
THREE.Mesh.call(this, geometry, material);
this.frustumCulled = false;
}
Slide.prototype = Object.create(THREE.Mesh.prototype);
Slide.prototype.constructor = Slide;
Object.defineProperty(Slide.prototype, 'time', {
get: function() {
return this.material.uniforms['uTime'].value;
},
set: function(v) {
this.material.uniforms['uTime'].value = v;
}
});
Slide.prototype.setImage = function(image) {
this.material.uniforms.map.value.image = image;
this.material.uniforms.map.value.needsUpdate = true;
};
Slide.prototype.transition = function() {
return TweenMax.fromTo(this, 3.0, {
time: 0.0
}, {
time: this.totalDuration,
ease: Power0.easeInOut
});
};
function SlideGeometry(model) {
THREE.BAS.ModelBufferGeometry.call(this, model);
}
SlideGeometry.prototype = Object.create(THREE.BAS.ModelBufferGeometry.prototype);
SlideGeometry.prototype.constructor = SlideGeometry;
SlideGeometry.prototype.bufferPositions = function() {
var positionBuffer = this.createAttribute('position', 3).array;
for (var i = 0; i < this.faceCount; i++) {
var face = this.modelGeometry.faces[i];
var centroid = THREE.BAS.Utils.computeCentroid(this.modelGeometry, face);
var a = this.modelGeometry.vertices[face.a];
var b = this.modelGeometry.vertices[face.b];
var c = this.modelGeometry.vertices[face.c];
positionBuffer[face.a * 3] = a.x - centroid.x;
positionBuffer[face.a * 3 + 1] = a.y - centroid.y;
positionBuffer[face.a * 3 + 2] = a.z - centroid.z;
positionBuffer[face.b * 3] = b.x - centroid.x;
positionBuffer[face.b * 3 + 1] = b.y - centroid.y;
positionBuffer[face.b * 3 + 2] = b.z - centroid.z;
positionBuffer[face.c * 3] = c.x - centroid.x;
positionBuffer[face.c * 3 + 1] = c.y - centroid.y;
positionBuffer[face.c * 3 + 2] = c.z - centroid.z;
}
};
function THREERoot(params) {
params = utils.extend({
fov: 60,
zNear: 10,
zFar: 100000,
createCameraControls: true
}, params);
this.renderer = new THREE.WebGLRenderer({
antialias: params.antialias,
alpha: true
});
this.renderer.setPixelRatio(Math.min(2, window.devicePixelRatio || 1));
document.getElementById('three-container').appendChild(this.renderer.domElement);
this.camera = new THREE.PerspectiveCamera(
params.fov,
window.innerWidth / window.innerHeight,
params.zNear,
params.zfar
);
this.scene = new THREE.Scene();
if (params.createCameraControls) {
this.controls = new THREE.OrbitControls(this.camera, this.renderer.domElement);
}
this.resize = this.resize.bind(this);
this.tick = this.tick.bind(this);
this.resize();
this.tick();
window.addEventListener('resize', this.resize, false);
}
THREERoot.prototype = {
tick: function() {
this.update();
this.render();
requestAnimationFrame(this.tick);
},
update: function() {
this.controls && this.controls.update();
},
render: function() {
this.renderer.render(this.scene, this.camera);
},
resize: function() {
this.camera.aspect = window.innerWidth / window.innerHeight;
this.camera.updateProjectionMatrix();
this.renderer.setSize(window.innerWidth, window.innerHeight);
}
};
////////////////////
// UTILS
////////////////////
var utils = {
extend: function(dst, src) {
for (var key in src) {
dst[key] = src[key];
}
return dst;
},
randSign: function() {
return Math.random() > 0.5 ? 1 : -1;
},
ease: function(ease, t, b, c, d) {
return b + ease.getRatio(t / d) * c;
},
fibSpherePoint: (function() {
var vec = {
x: 0,
y: 0,
z: 0
};
var G = Math.PI * (3 - Math.sqrt(5));
return function(i, n, radius) {
var step = 2.0 / n;
var r, phi;
vec.y = i * step - 1 + (step * 0.5);
r = Math.sqrt(1 - vec.y * vec.y);
phi = i * G;
vec.x = Math.cos(phi) * r;
vec.z = Math.sin(phi) * r;
radius = radius || 1;
vec.x *= radius;
vec.y *= radius;
vec.z *= radius;
return vec;
}
})(),
spherePoint: (function() {
return function(u, v) {
u === undefined && (u = Math.random());
v === undefined && (v = Math.random());
var theta = 2 * Math.PI * u;
var phi = Math.acos(2 * v - 1);
var vec = {};
vec.x = (Math.sin(phi) * Math.cos(theta));
vec.y = (Math.sin(phi) * Math.sin(theta));
vec.z = (Math.cos(phi));
return vec;
}
})()
};
function createTweenScrubber(tween, seekSpeed) {
seekSpeed = seekSpeed || 0.001;
function stop() {
TweenMax.to(tween, 1, {
timeScale: 0
});
}
function resume() {
TweenMax.to(tween, 1, {
timeScale: 1
});
}
function seek(dx) {
var progress = tween.progress();
var p = THREE.Math.clamp((progress + (dx * seekSpeed)), 0, 1);
tween.progress(p);
}
var _cx = 0;
// desktop
var mouseDown = false;
document.body.style.cursor = 'pointer';
window.addEventListener('mousedown', function(e) {
mouseDown = true;
document.body.style.cursor = 'ew-resize';
_cx = e.clientX;
stop();
});
window.addEventListener('mouseup', function(e) {
mouseDown = false;
document.body.style.cursor = 'pointer';
resume();
});
window.addEventListener('mousemove', function(e) {
if (mouseDown === true) {
var cx = e.clientX;
var dx = cx - _cx;
_cx = cx;
seek(dx);
}
});
// mobile
window.addEventListener('touchstart', function(e) {
_cx = e.touches[0].clientX;
stop();
e.preventDefault();
});
window.addEventListener('touchend', function(e) {
resume();
e.preventDefault();
});
window.addEventListener('touchmove', function(e) {
var cx = e.touches[0].clientX;
var dx = cx - _cx;
_cx = cx;
seek(dx);
e.preventDefault();
});
}
body {
margin: 0;
background: #fff;
}
canvas {
background: #fff;
}
<script src="//cdnjs.cloudflare.com/ajax/libs/three.js/r75/three.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/gsap/1.18.0/TweenMax.min.js"></script>
<script src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/175711/bas.js"></script>
<script src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/175711/OrbitControls-2.js"></script>
<div id="three-container"></div>
Thanks in advance!

Related

JS canva code has horrible performance on firefox, works fine on chomium

I basically have 3 curved bezier lines rotating around with a somewhat dynamic background.
It is done in 2D canvas, it works fine in chromium, but in Firefox I get 1 FPS for some reason.
I tried various solutions but while some did give me like 20% performance increase, 1.2 FPS is still unacceptable.
Here is the codepen with the code.
class BG {
animationContainer = null;
options = {
rotation: 45,
waves: 3,
width: 300,
hue: [14, 50],
amplitude: .5,
background: 1,
preload: 1,
speed: [.001, .004]
};
waves = [];
hue = 0;
hueFw = true;
canvas = null;
ctx = null;
observer = null;
radius = 0;
centerX = 0;
centerY = 0;
width = 0;
height = 0;
scale = 1;
isVisible = false;
gradient = null;
color = "#000000";
constructor(t, e) {
this.animationContainer = t, this.options = {
rotation: e.rotation || 45,
waves: e.waves || 2,
width: e.width || 300,
hue: e.hue || [17, 50],
amplitude: e.amplitude || .75,
background: e.background || 1,
preload: e.preload || 1,
speed: e.speed || [.001, .004]
}, this.waves = [], this.hue = this.options.hue[0], this.hueFw = true, this.setup(), this.render()
}
setup() {
this.canvas = document.createElement("canvas"), this.ctx = this.canvas.getContext("2d"), this.animationContainer.appendChild(this.canvas), this.resize(), window.addEventListener("resize", this.resize.bind(this)), this.waves = [];
for (let t = 0; t < this.options.waves; t++) this.waves.push(WV(this));
this.options.preload && this.preload(), this.addIntersection()
}
resize() {
let {
offsetWidth: t,
offsetHeight: e
} = this.animationContainer;
this.scale = window.devicePixelRatio || 1, this.width = t * this.scale, this.height = e * this.scale, this.canvas.width = this.width, this.canvas.height = this.height, this.canvas.style.width = `${t}px`, this.canvas.style.height = `${e}px`, this.radius = Math.sqrt(Math.pow(this.width, 2) + Math.pow(this.height, 2)) / 2, this.centerX = this.width / 2, this.centerY = this.height / 2;
}
addIntersection() {
this.observer = new IntersectionObserver(t => {
for (let e of t) this.isVisible = e.intersectionRatio > 0
}), this.observer.observe(this.animationContainer)
}
preload() {
for (let t = 0; t < this.options.waves; t++) {
this.updateColor();
for (let e = 0; e < this.options.width; e++) this.waves[t].update()
}
}
updateColor() {
this.hue += this.hueFw ? .01 : -.01, this.hue > this.options.hue[1] && this.hueFw ? this.hueFw = false : this.hue < this.options.hue[0] && !this.hueFw && (this.hueFw = true);
let t = Math.floor(127 * Math.sin(.3 * this.hue) + 128),
e = Math.floor(127 * Math.sin(.3 * this.hue + 2) + 128),
i = Math.floor(127 * Math.sin(.3 * this.hue + 4) + 128);
this.color = "rgba(" + t + "," + e + "," + i + ", 0.1)"
}
render() {
requestAnimationFrame(() => {
if (this.isVisible) {
this.draw();
}
this.render();
});
}
draw() {
for (let t of (this.updateColor(), this.clear(), this.options.background && this.background(), this.waves)) t.update(), t.draw()
}
clear() {
this.ctx.clearRect(0, 0, this.width, this.height);
}
background() {
this.gradient = this.ctx.createLinearGradient(0, 0, 0, this.height), this.gradient.addColorStop(1, "#0E1218"), this.gradient.addColorStop(0, this.color), this.ctx.fillStyle = this.gradient, this.ctx.fillRect(1, 0, this.width, this.height)
}
}
function LN(t, e) {
t.angle[0] += t.speed[0];
t.angle[1] += t.speed[1];
t.angle[2] += t.speed[2];
t.angle[3] += t.speed[3];
return { angle: [Math.sin(t.angle[0]), Math.sin(t.angle[1]), Math.sin(t.angle[2]), Math.sin(t.angle[3])], wave: t, color: e };
}
function rng(t, e) {
return null == e ? Math.random() * t : t + Math.random() * (e - t);
}
function dr() {
return Math.random() > .5 ? 1 : -1;
}
function WV(t) {
let e = [];
const angles = [
rng(2 * Math.PI),
rng(2 * Math.PI),
rng(2 * Math.PI),
rng(2 * Math.PI)
];
const speeds = [
rng(t.options.speed[0], t.options.speed[1]) * dr(),
rng(t.options.speed[0], t.options.speed[1]) * dr(),
rng(t.options.speed[0], t.options.speed[1]) * dr(),
rng(t.options.speed[0], t.options.speed[1]) * dr()
];
const h = () => {
e.push(LN({ angle: angles, speed: speeds }, t.color));
if (e.length > t.options.width) {
e.shift();
}
};
const n = () => {
const {
ctx: i,
radius: s,
centerX: h,
centerY: n,
options: o
} = t;
const r = s / 3;
const a = (o.rotation * Math.PI) / 360;
const { amplitude: d } = o;
for (const l of e) {
const { angle: $ } = l;
const wave1 = $[0] * d + a;
const wave2 = $[3] * d + a;
const wave3 = $[1] * d * 2;
const wave4 = $[2] * d * 2;
const u = h - s * Math.cos(wave1);
const p = n - s * Math.sin(wave1);
const w = h - r * Math.cos(wave3);
const _ = n - r * Math.sin(wave3);
const v = h + r * Math.cos(wave4);
const b = n + r * Math.sin(wave4);
const c = h + s * Math.cos(wave2);
const g = n + s * Math.sin(wave2);
i.strokeStyle = l.color;
i.beginPath();
i.moveTo(u, p);
i.bezierCurveTo(w, _, v, b, c, g);
i.stroke();
}
};
return { lines: e, angle: angles, speed: speeds, update: h, draw: n };
}

How to add JavaScript file in angular cli application?

I am trying to replicate this effect - https://codepen.io/jonathasborges1/pen/YzryRpX in my angular app application.
But I'm having a hard time applying the effect
Someone can help me?
"use strict";
var LeafScene = function (el) {
this.viewport = el;
this.world = document.createElement("div");
this.leaves = [];
this.options = {
numLeaves: 60,
wind: {
magnitude: 1.2,
maxSpeed: 12,
duration: 300,
start: 0,
speed: 0
}
};
this.width = this.viewport.offsetWidth;
this.height = this.viewport.offsetHeight;
// animation helper
this.timer = 0;
this._resetLeaf = function (leaf) {
// place leaf towards the top left
leaf.x = this.width * 2 - Math.random() * this.width * 1.75;
leaf.y = -10;
leaf.z = Math.random() * 200;
if (leaf.x > this.width) {
leaf.x = this.width + 10;
leaf.y = (Math.random() * this.height) / 2;
}
// at the start, the leaf can be anywhere
if (this.timer == 0) {
leaf.y = Math.random() * this.height;
}
// Choose axis of rotation.
// If axis is not X, chose a random static x-rotation for greater variability
leaf.rotation.speed = Math.random() * 10;
var randomAxis = Math.random();
if (randomAxis > 0.5) {
leaf.rotation.axis = "X";
}
else if (randomAxis > 0.25) {
leaf.rotation.axis = "Y";
leaf.rotation.x = Math.random() * 180 + 90;
}
else {
leaf.rotation.axis = "Z";
leaf.rotation.x = Math.random() * 360 - 180;
// looks weird if the rotation is too fast around this axis
leaf.rotation.speed = Math.random() * 3;
}
// random speed
leaf.xSpeedVariation = Math.random() * 1 - 0.2;
leaf.ySpeed = Math.random();
return leaf;
};
this._updateLeaf = function (leaf) {
var leafWindSpeed = this.options.wind.speed(this.timer - this.options.wind.start, leaf.y);
var xSpeed = leafWindSpeed + leaf.xSpeedVariation;
leaf.x -= xSpeed;
leaf.y += leaf.ySpeed;
leaf.rotation.value += leaf.rotation.speed;
var t = "translateX( " +
leaf.x +
"px ) translateY( " +
leaf.y +
"px ) translateZ( " +
leaf.z +
"px ) rotate" +
leaf.rotation.axis +
"( " +
leaf.rotation.value +
"deg )";
if (leaf.rotation.axis !== "X") {
t += " rotateX(" + leaf.rotation.x + "deg)";
}
leaf.el.style.webkitTransform = t;
leaf.el.style.MozTransform = t;
leaf.el.style.oTransform = t;
leaf.el.style.transform = t;
// reset if out of view
if (leaf.x < -10 || leaf.y > this.height + 10) {
this._resetLeaf(leaf);
}
};
this._updateWind = function () {
if (this.timer === 0 ||
this.timer > this.options.wind.start + this.options.wind.duration) {
this.options.wind.magnitude = Math.random() * this.options.wind.maxSpeed;
this.options.wind.duration =
this.options.wind.magnitude * 50 + (Math.random() * 20 - 10);
this.options.wind.start = this.timer;
var screenHeight = this.height;
this.options.wind.speed = function (t, y) {
var a = ((this.magnitude / 2) * (screenHeight - (2 * y) / 3)) / screenHeight;
return (a *
Math.sin(((2 * Math.PI) / this.duration) * t + (3 * Math.PI) / 2) +
a);
};
}
};
};
LeafScene.prototype.init = function () {
for (var i = 0; i < this.options.numLeaves; i++) {
var leaf = {
el: document.createElement("div"),
x: 0,
y: 0,
z: 0,
rotation: {
axis: "X",
value: 0,
speed: 0,
x: 0
},
xSpeedVariation: 0,
ySpeed: 0,
path: {
type: 1,
start: 0
},
image: 1
};
this._resetLeaf(leaf);
this.leaves.push(leaf);
this.world.appendChild(leaf.el);
}
this.world.className = "leaf-scene";
this.viewport.appendChild(this.world);
// reset window height/width on resize
var self = this;
window.onresize = function (event) {
self.width = self.viewport.offsetWidth;
self.height = self.viewport.offsetHeight;
};
};
LeafScene.prototype.render = function () {
this._updateWind();
for (var i = 0; i < this.leaves.length; i++) {
this._updateLeaf(this.leaves[i]);
}
this.timer++;
requestAnimationFrame(this.render.bind(this));
};
// start up leaf scene
var leafContainer = document.querySelector(".falling-leaves"), leaves = new LeafScene(leafContainer);
leaves.init();
leaves.render();
You can create a file like <script> ..that js code.. </script> and save it as script.js and then add it to your index.html or dynamicly load it:
public loadScript(){
return new Promise(resolve => {
const scriptElement = document.createElement('script');
scriptElement.src = '/assets/js/script.js'
scriptElement.onload = resolve;
document.body.appendChild(scriptElement);
});
}
and then call and subscribe this function in your ts file to know when it is loaded or do something when its loaded in your page.

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>

(Three.js) Change color on particles forming image

I'm currently working on a particle system where the particles move around and create pictures with some seconds in between (almost like a slide). I know where to put the color code to change the color on the particles forming the pictures bur for some reason it doesn't work. I therefore suspect that the problem is somewhere else in the script but the question is where...
You can see the code below (tried to make a codepen but it didn't work):
var dispersed = false;
var firstDone = false;
var secondDone = false;
var thirdDone = false;
var fRgba = []; // first img rgba data
var sRgba = []; // second img rgba data
var tRgba = []; // third img rgba data
var WIDTH = window.innerWidth,
HEIGHT = window.innerHeight;
var VIEW_ANGLE = 45,
ASPECT = WIDTH / HEIGHT,
NEAR = 0.01,
FAR = 10000;
var $container = $("#container");
var renderer = new THREE.WebGLRenderer();
var camera = new THREE.PerspectiveCamera(
VIEW_ANGLE,
ASPECT,
NEAR,
FAR);
var scene = new THREE.Scene();
scene.add(camera);
camera.position.z = 900;
renderer.setSize(WIDTH, HEIGHT);
$container.append(renderer.domElement);
var particleCount = 5200,
particles = new THREE.Geometry();
var pMaterial = new THREE.PointsMaterial({
size: 6,
map: createCircleTexture('#CACACA', 256),
transparent: true,
depthWrite: false
});
function createCircleTexture(color, size) {
var matCanvas = document.createElement('canvas');
matCanvas.width = matCanvas.height = size;
var matContext = matCanvas.getContext('2d');
var texture = new THREE.Texture(matCanvas);
var center = size / 2;
matContext.beginPath();
matContext.arc(center, center, size/2, 0, 2 * Math.PI, false);
matContext.closePath();
matContext.fillStyle = color;
matContext.fill();
texture.needsUpdate = true;
return texture;
}
for (var i = 0; i < particleCount; i++) {
var x = Math.random() * 1600 - 800;
var y = getRandomInt(600, 1500)
var z = Math.random() * 30 - 15;
var particle = new THREE.Vector3(x, y, z);
particle.updated = 0;
particles.vertices.push(particle);
};
var particleSystem = new THREE.Points(particles, pMaterial);
particleSystem.sortParticles = true;
scene.add(particleSystem);
function drawImage(imageObj, array) {
var canvas = $("#canvas")[0];
var context = canvas.getContext("2d");
var imageX = 0;
var imageY = 0;
var imageWidth = imageObj.width;
var imageHeight = imageObj.height;
context.drawImage(imageObj, imageX, imageY);
var imageData = context.getImageData(imageX, imageY, imageWidth,
imageHeight);
var data = imageData.data;
for(var y = 0; y < imageHeight; y+= 4) {
for(var x = 0; x < imageWidth; x+= 4) {
var red = data[((imageWidth * y) + x) * 4];
var green = data[((imageWidth * y) + x) * 4 + 1];
var blue = data[((imageWidth * y) + x) * 4 + 2];
var alpha = data[((imageWidth * y) + x) * 4 + 3];
if (red < 100) {
var pX = (x % 500) - 249;
var pY = 249 - y;
array.push([pX, pY, red, green, blue, alpha]);
}
}
}
};
var addDestination = function(particle, x, y, z) {
var dest = new THREE.Vector3(x, y, z);
particle.destination = dest;
};
var addVelocity = function(particle) {
var xDiff = (particle.destination.x - particle.x) / 180;
var yDiff = (particle.destination.y - particle.y) / 180;
var zDiff = (particle.destination.z - particle.z) / 180;
var vel = new THREE.Vector3(xDiff, yDiff, zDiff);
particle.velocity = vel;
};
var move = function(particle) {
particle.x += particle.velocity.x;
particle.y += particle.velocity.y;
particle.z += particle.velocity.z;
particle.updated += 1;
};
var slowDown = function(particle) {
particle.velocity.x -= (particle.velocity.x / 300)
particle.velocity.y -= (particle.velocity.y / 300)
particle.velocity.z -= (particle.velocity.z / 160)
};
var resetProperties = function() {
var pCount = particleCount;
while (pCount--) {
var particle = particles.vertices[pCount];
particle.destination = null
particle.updated = 0;
};
};
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min)) + min;
};
var distributedZ = function(level) {
var z;
if (level === 1) {
z = getRandomInt(50, 100);
} else if (level === 2) {
z = getRandomInt(350, 400);
} else {
z = getRandomInt(650, 700);
}
return z;
};
function shuffle(array) {
var currentIndex = array.length, temporaryValue, randomIndex;
while (0 !== currentIndex) {
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex -= 1;
temporaryValue = array[currentIndex];
array[currentIndex] = array[randomIndex];
array[randomIndex] = temporaryValue;
}
return array;
};
var disperse = function() {
pCount = particleCount;
for (var i = 0; i < pCount; i++) {
var particle = particles.vertices[i];
if (typeof(particle.destination) === "undefined") {
var nums = [-1, 1];
var x = particle.x + nums[Math.round(Math.random())];
var y = particle.y - 1000;
var z = Math.random() * 30 - 15;
addDestination(particle, x, y, z);
particle.velocity = new THREE.Vector3(x - particle.x, -3, z -
particle.z);
}
if (particle.updated <= 300) {
move(particle);
} else {
particles.vertices = shuffle(particles.vertices);
resetProperties();
dispersed = true;
return;
}
}
}
var morphImageParticles = function(imageParticles, rgba) {
for (var i = 0; i < imageParticles.length; i++) {
var particle = imageParticles[i]
if (particle.destination === null) {
var pixelData = rgba[i];
var x = pixelData[0];
var y = pixelData[1];
var z = Math.random() * 15 - 7;
addDestination(particle, x, y, z);
addVelocity(particle);
}
if (particle.updated <= 180) {
move(particle);
}
}
};
var morphOuterParticles = function(outerParticles, ord) {
for (var i = 0; i < outerParticles.length; i++) {
var nums = [-1, 1];
var particle = outerParticles[i];
if (particle.destination === null) {
var x = Math.random() * 1000 - 500;
var y = Math.random() * 1000 - 500;
var z;
if (i <= Math.round(outerParticles.length * 0.6)) {
z = distributedZ(1)
} else if (i > Math.round(outerParticles.length * 0.6) && i <
Math.round(outerParticles.length * 0.9)) {
z = distributedZ(2)
} else {
z = distributedZ(3);
}
addDestination(particle, x, y, z);
addVelocity(particle);
}
if (particle.updated <= 600) {
move(particle);
slowDown(particle);
} else {
particles.vertices = shuffle(particles.vertices);
resetProperties();
if (ord === 1) {
firstDone = true;
} else if (ord === 2) {
secondDone = true;
} else {
thirdDone = true;
}
return;
}
}
};
var makeImg = function(rgba, ord) {
var pCount = particleCount;
var imagePs = particles.vertices.slice(0, rgba.length);
var outerPs = particles.vertices.slice(rgba.length, pCount);
morphImageParticles(imagePs, rgba);
morphOuterParticles(outerPs, ord);
};
var update = function() {
if (thirdDone) {
} else if (secondDone) {
makeImg(tRgba, 3);
} else if (firstDone) {
makeImg(sRgba, 2);
} else if (dispersed) {
makeImg(fRgba, 1);
} else {
disperse();
}
particleSystem.geometry.verticesNeedUpdate = true;
renderer.render(scene, camera);
requestAnimationFrame(update);
TWEEN.update();
};
var rotXScale = d3.scale.linear().domain([0, window.innerHeight]).range([15,
-15]);
var rotYScale = d3.scale.linear().domain([0, window.innerWidth]).range([25,
-25]);
d3.select("body").on("mousemove", function() {
var scaledX = rotXScale(d3.mouse(this)[1]) * Math.PI / 180;
var scaledY = rotYScale(d3.mouse(this)[0]) * Math.PI / 180;
var tween = new TWEEN.Tween(particleSystem.rotation).to({ x: scaledX, y:
scaledY, z: 0 });
tween.easing( TWEEN.Easing.Quartic.Out);
tween.start();
transparency: true
});
var img1 = new Image();
var img2 = new Image();
var img3 = new Image();
img1.onload = function() {
drawImage(this, fRgba);
img2.onload = function() {
drawImage(this, sRgba);
img3.onload = function() {
drawImage(this, tRgba);
}
img3.src = "images/p1.png";
}
img2.src = "images/p2.png";
update();
}
img1.src = "images/p3.png";
update();
I thought I only need to add the code below, for example ['0xffffff'], that's how it should work at least but it didn't. Therefore I guess the problem is somewhere else in the script.
var fRgba = []; // first img rgba data
var sRgba = []; // second img rgba data
var tRgba = []; // third img rgba data

Canvas Marching Squares Glitch / Scroll Integration

I'm attempting to teach myself how to work with canvas — and I'm probably in way over my head — but thought I'd ask if anyone has a solution to this issue I came across.
As a lesson I decided to try to start with this metaball idea:
http://codepen.io/ge1doot/pen/RNdwQB
and rework it so the metaballs are stationary but move upwards at varying rates as one scrolls down the page.
I sort of got it working here — displays much better on the fiddle than below.
https://jsfiddle.net/L7cr46px/2/
function getScrollOffsets() {
var doc = document, w = window;
var x, y, docEl;
if ( typeof w.pageYOffset === 'number' ) {
x = w.pageXOffset;
y = w.pageYOffset;
} else {
docEl = (doc.compatMode && doc.compatMode === 'CSS1Compat')?
doc.documentElement: doc.body;
x = docEl.scrollLeft;
y = docEl.scrollTop;
}
return {x:x, y:y};
}
var lava, ballData;
ballData = new Array();
(function() {
var metablobby = metablobby || {
screen: {
elem: null,
callback: null,
ctx: null,
width: 0,
height: 0,
left: 0,
top: 0,
init: function (id, callback, initRes) {
this.elem = document.getElementById(id);
this.callback = callback || null;
if (this.elem.tagName == "CANVAS") this.ctx = this.elem.getContext("2d");
window.addEventListener('resize', function () {
this.resize();
}.bind(this), false);
this.elem.onselectstart = function () { return false; }
this.elem.ondrag = function () { return false; }
initRes && this.resize();
return this;
},
resize: function () {
var o = this.elem;
this.width = o.offsetWidth;
this.height = o.offsetHeight;
for (this.left = 0, this.top = 0; o != null; o = o.offsetParent) {
this.left += o.offsetLeft;
this.top += o.offsetTop;
}
if (this.ctx) {
this.elem.width = this.width;
this.elem.height = this.height;
}
this.callback && this.callback();
},
pointer: {
screen: null,
elem: null,
callback: null,
pos: {x:0, y:0},
mov: {x:0, y:0},
drag: {x:0, y:0},
start: {x:0, y:0},
end: {x:0, y:0},
active: false,
touch: false,
move: function (e, touch) {
//this.active = true;
this.touch = touch;
e.preventDefault();
var pointer = touch ? e.touches[0] : e;
this.mov.x = pointer.clientX - this.screen.left;
this.mov.y = pointer.clientY - this.screen.top;
if (this.active) {
this.pos.x = this.mov.x;
this.pos.y = this.mov.y;
this.drag.x = this.end.x - (this.pos.x - this.start.x);
this.drag.y = this.end.y - (this.pos.y - this.start.y);
this.callback.move && this.callback.move();
}
},
scroll: function(e, touch){
run();
},
init: function (callback) {
this.screen = metablobby.screen;
this.elem = this.screen.elem;
this.callback = callback || {};
if ('ontouchstart' in window) {
// touch
this.elem.ontouchstart = function (e) { this.down(e, true); }.bind(this);
this.elem.ontouchmove = function (e) { this.move(e, true); }.bind(this);
this.elem.ontouchend = function (e) { this.up(e, true); }.bind(this);
this.elem.ontouchcancel = function (e) { this.up(e, true); }.bind(this);
}
document.addEventListener("mousemove", function (e) { this.move(e, false); }.bind(this), true);
document.addEventListener("scroll", function (e) { this.scroll(e, false); }.bind(this), true);
return this;
}
},
}
}
// ==== Point constructor ====
var Point = function(x, y) {
this.x = x;
this.y = y;
this.magnitude = x * x + y * y;
this.computed = 0;
this.force = 0;
}
Point.prototype.add = function(p) {
return new Point(this.x + p.x, this.y + p.y);
}
// ==== Ball constructor ====
var Ball = function(parent,i) {
var x = Math.floor(Math.random() * window.innerWidth) + 1;
var y = Math.floor(Math.random() * (window.innerHeight)*5) + 1;
var radius = (Math.floor(Math.random() * 65) + 15)
var drift = Math.random();
ballData[i]=[x,y,radius, drift];
this.vel = new Point(0,0);
this.pos = new Point(x,y);
this.size = radius;
this.width = parent.width;
this.height = parent.height;
}
// ==== move balls ====
Ball.prototype.move = function(i) {
// ---- interact with pointer ----
if (pointer.active) {
var dx = pointer.pos.x - this.pos.x;
var dy = pointer.pos.y - this.pos.y;
var a = Math.atan2(dy, dx);
var v = -Math.min(
10,
500 / Math.sqrt(dx * dx + dy * dy)
);
this.pos = this.pos.add(
new Point(
Math.cos(a) * v,
Math.sin(a) * v
)
);
}
var drift = ballData[i-1][3];
var pageOffset = getScrollOffsets().y;
this.pos.y = ballData[i-1][1] - (pageOffset*drift);
this.vel.y = 0 - (pageOffset*drift);
this.pos = this.pos.add(this.vel);
}
// ==== lavalamp constructor ====
var LavaLamp = function(width, height, numBalls) {
this.step = 4;
this.width = width;
this.height = height;
this.wh = Math.min(width, height);
this.sx = Math.floor(this.width / this.step);
this.sy = Math.floor(this.height / this.step);
this.paint = false;
this.metaFill = '#000000';
this.plx = [0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0];
this.ply = [0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1];
this.mscases = [0, 3, 0, 3, 1, 3, 0, 3, 2, 2, 0, 2, 1, 1, 0];
this.ix = [1, 0, -1, 0, 0, 1, 0, -1, -1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1];
this.grid = [];
this.balls = [];
this.iter = 0;
this.sign = 1;
// ---- init grid ----
for (var i = 0; i < (this.sx + 2) * (this.sy + 2); i++) {
this.grid[i] = new Point(
(i % (this.sx + 2)) * this.step, (Math.floor(i / (this.sx + 2))) * this.step
)
}
// ---- create metaballs ----
for (var i = 0; i < 50; i++) {
this.balls[i] = new Ball(this,i);
}
}
// ==== compute cell force ====
LavaLamp.prototype.computeForce = function(x, y, idx) {
var force;
var id = idx || x + y * (this.sx + 2);
if (x === 0 || y === 0 || x === this.sx || y === this.sy) {
var force = 0.6 * this.sign;
} else {
var cell = this.grid[id];
var force = 0;
var i = 0,
ball;
while (ball = this.balls[i++]) {
force += ball.size * ball.size / (-2 * cell.x * ball.pos.x - 2 * cell.y * ball.pos.y + ball.pos.magnitude + cell.magnitude);
}
force *= this.sign
}
this.grid[id].force = force;
return force;
}
// ---- compute cell ----
LavaLamp.prototype.marchingSquares = function(next) {
var x = next[0];
var y = next[1];
var pdir = next[2];
var id = x + y * (this.sx + 2);
if(typeof this.grid[id] !== "undefined"){
if (this.grid[id].computed === this.iter) return false;
var dir, mscase = 0;
// ---- neighbors force ----
for (var i = 0; i < 4; i++) {
var idn = (x + this.ix[i + 12]) + (y + this.ix[i + 16]) * (this.sx + 2);
var force = this.grid[idn].force;
if ((force > 0 && this.sign < 0) || (force < 0 && this.sign > 0) || !force) {
// ---- compute force if not in buffer ----
force = this.computeForce(
x + this.ix[i + 12],
y + this.ix[i + 16],
idn
);
}
if (Math.abs(force) > 1) mscase += Math.pow(2, i);
}
if (mscase === 15) {
// --- inside ---
return [x, y - 1, false];
} else {
// ---- ambiguous cases ----
if (mscase === 5) dir = (pdir === 2) ? 3 : 1;
else if (mscase === 10) dir = (pdir === 3) ? 0 : 2;
else {
// ---- lookup ----
dir = this.mscases[mscase];
this.grid[id].computed = this.iter;
}
// ---- draw line ----
var ix = this.step / (
Math.abs(Math.abs(this.grid[(x + this.plx[4 * dir + 2]) + (y + this.ply[4 * dir + 2]) * (this.sx + 2)].force) - 1) /
Math.abs(Math.abs(this.grid[(x + this.plx[4 * dir + 3]) + (y + this.ply[4 * dir + 3]) * (this.sx + 2)].force) - 1) + 1
);
ctx.lineTo(
this.grid[(x + this.plx[4 * dir + 0]) + (y + this.ply[4 * dir + 0]) * (this.sx + 2)].x + this.ix[dir] * ix,
this.grid[(x + this.plx[4 * dir + 1]) + (y + this.ply[4 * dir + 1]) * (this.sx + 2)].y + this.ix[dir + 4] * ix
);
this.paint = true;
// ---- next ----
return [
x + this.ix[dir + 4],
y + this.ix[dir + 8],
dir
];
}
}
}
LavaLamp.prototype.renderMetaballs = function() {
var i = 0, ball;
while (ball = this.balls[i++]) ball.move(i);
// ---- reset grid ----
this.iter++;
this.sign = -this.sign;
this.paint = false;
ctx.fillStyle = '#FF0000';
ctx.beginPath();
// ---- compute metaballs ----
i = 0;
while (ball = this.balls[i++]) {
// ---- first cell ----
var next = [
Math.round(ball.pos.x / this.step),
Math.round(ball.pos.y / this.step), false
];
// ---- marching squares ----
do {
next = this.marchingSquares(next);
} while (next);
// ---- fill and close path ----
if (this.paint) {
ctx.fill();
ctx.closePath();
ctx.beginPath();
this.paint = false;
}
}
}
// ==== main loop ====
var run = function() {
//requestAnimationFrame(run);
ctx.clearRect(0, 0, screen.width, screen.height);
lava.renderMetaballs();
}
// ---- canvas ----
var screen = metablobby.screen.init("thedots", null, true),
ctx = screen.ctx,
pointer = screen.pointer.init();
screen.resize();
// ---- create LavaLamps ----
lava = new LavaLamp(screen.width, screen.height, 10);
// ---- start engine ----
run();
})();
html,body {
height: 100%;
width: 100%;
background-color: white;
}
body {
height: 500vh;
}
#thedots {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
z-index: 100;
pointer-events: none;
}
<canvas id="thedots" width="1203" height="363"></canvas>
but I'm getting insane rendering bugs as you'll see. Or maybe they're not rendering bugs and just some faulty code. Any help would be much appreciated.
Thanks!

Categories

Resources