Threejs / EffectComposer - Interactively masking an UnrealBloomPass - javascript

So, I am trying to dynamically mask an UnrealBloomPass with the EffectComposer and I am getting unexpected results. I am not sure if I am missing a key concept here, or if I should be trying to achieve this in a different way. Any input would be appreciated.
The composer is set up into these layers:
hexgradientPass (Main content)
maskingPass (I am trying to move this dynamically with the mouse)
bloomPass (Which I am trying to mask with ^)
clearMaskPass (Clearing the mask)
effectCopyPass (Applying the effects)
I've been following tutorials and examples as closely as possible and still no dice. Thanks in advance.
<!DOCTYPE html>
<html lang="en">
<head>
<title>BloomShader Mask</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<style>
body {
color: #fff;
font-family:Monospace;
font-size:13px;
text-align:center;
background-color: #fff;
margin: 0px;
overflow: hidden;
}
#info {
position: absolute;
top: 0px;
width: 100%;
padding: 5px;
}
#info p {
max-width: 600px;
margin-left: auto;
margin-right: auto;
padding: 0 2em;
}
a {
color: #2983ff;
}
</style>
</head>
<body>
<div id="container"></div>
<script src="./build/three.js"></script>
<script src="js/libs/stats.min.js"></script>
<script src="js/libs/dat.gui.min.js"></script>
<script src="js/controls/OrbitControls.js"></script>
<script src="js/loaders/GLTFLoader.js"></script>
<script src="js/postprocessing/EffectComposer.js"></script>
<script src="js/postprocessing/RenderPass.js"></script>
<script src="js/postprocessing/ShaderPass.js"></script>
<script src="js/postprocessing/MaskPass.js "></script>
<script src="js/shaders/CopyShader.js"></script>
<script src="js/shaders/LuminosityHighPassShader.js"></script>
<script src="js/postprocessing/UnrealBloomPass.js"></script>
<script id="gradientShader" type="shader-code">
uniform vec2 resolution;
uniform vec2 mousePosition;
void main() {
vec2 pos = (gl_FragCoord.xy - mousePosition.xy * 1.2) / resolution.xy;
gl_FragColor = vec4(1.0, pos.x, pos.y, 1.0);
}
</script>
<script>
var clock = new THREE.Clock();
var stats;
var gui;
var sceneHexGradient;
var sceneMask;
var camera;
var orbitControls;
var pointLight;
var composer;
var mixer;
var maskerMesh;
var bloomPass;
var width = 0;
var height = 0;
var cameraZoom = 120;
var mousePositionY = 0;
var params = {
exposure: 2,
bloomStrength: .81,
bloomThreshold: 0,
bloomRadius: .05,
cameraZoom: cameraZoom
};
function init() {
// Init vars (DOM needed)
gui = initGui();
stats = initStats();
width = window.innerWidth;
height = window.innerHeight;
pixelRatio = window.devicePixelRatio;
// Renderer
var webGLRenderer = new THREE.WebGLRenderer({ antialias: true });
webGLRenderer.setClearColor(new THREE.Color(0x000000, 1.0));
webGLRenderer.setPixelRatio(pixelRatio);
webGLRenderer.setSize(width , height);
webGLRenderer.toneMapping = THREE.ReinhardToneMapping;
webGLRenderer.shadowMap.enabled = true;
appendChild(webGLRenderer.domElement);
// Scene
sceneHexGradient = new THREE.Scene();
sceneMasker = new THREE.Scene();
// Camera
camera = new THREE.OrthographicCamera(-width, width, height, -height, -1000, 1000);
camera.zoom = cameraZoom;
camera.lookAt(new THREE.Vector3(0, 0, 0));
camera.updateProjectionMatrix();
sceneHexGradient.add(camera);
sceneMasker.add(camera);
// Orbit
orbitControls = new THREE.OrbitControls(camera, webGLRenderer.domElement);
orbitControls.maxPolarAngle = Math.PI * 0.5;
orbitControls.minDistance = 1;
orbitControls.maxDistance = 10;
// Draw onto Scenes
drawGradientToScene(sceneHexGradient);
drawHexToScene(sceneHexGradient);
drawMaskerToScene(sceneMasker);
// ShaderPass
var hexgradientPass = new THREE.RenderPass(sceneHexGradient, camera);
hexgradientPass.clear = false;
var effectCopyPass = new THREE.ShaderPass(THREE.CopyShader);
effectCopyPass.renderToScreen = true;
var maskingPass = new THREE.MaskPass(sceneMasker, camera);
maskingPass.clear = true;
// var maskingPass = new THREE.RenderPass(sceneMasker, camera);
// maskingPass.renderToScreen = true;
bloomPass = new THREE.UnrealBloomPass(new THREE.Vector2(width, height), 1.5, 0.4, 0.85);
bloomPass.renderToScreen = true;
bloomPass.threshold = params.bloomThreshold;
bloomPass.strength = params.bloomStrength;
bloomPass.radius = params.bloomRadius;
var clearMaskPass = new THREE.ClearMaskPass();
composer = new THREE.EffectComposer(webGLRenderer);
composer.renderTarget1.stencilBuffer = true;
composer.renderTarget2.stencilBuffer = true;
composer.setSize(width, height);
composer.addPass(hexgradientPass);
composer.addPass(maskingPass);
// composer.addPass(maskingVerticalBlurPass);
composer.addPass(bloomPass);
composer.addPass(clearMaskPass);
composer.addPass(effectCopyPass);
render();
function initStats() { // Debug only
var stats = new Stats();
stats.setMode(0);
stats.domElement.style.position = 'absolute';
stats.domElement.style.left = '0px';
stats.domElement.style.top = '0px';
appendChild(stats.dom);
return stats;
}
function initGui() { // Debug only
gui = new dat.GUI();
gui.add(params, 'exposure', 0.1, 2 ).onChange((value) => {
webGLRenderer.toneMappingExposure = Math.pow(value, 4.0);
});
gui.add(params, 'bloomThreshold', 0.0, 1.0).onChange((value) => {
bloomPass.threshold = Number(value);
});
gui.add(params, 'bloomStrength', 0.0, 3.0).onChange((value) => {
bloomPass.strength = Number(value);
});
gui.add(params, 'bloomRadius', 0.0, 1.0).step(0.01).onChange((value) => {
bloomPass.radius = Number(value);
});
gui.add(params, 'cameraZoom', 100, 150).onChange((value) => {
camera.zoom = Number(value);
camera.updateProjectionMatrix();
});
return gui;
}
function appendChild(domElement) {
var container = document.getElementById('container');
if (container) {
return container.appendChild(domElement);
}
return false;
}
function drawGradientToScene(scene) {
var gradientUniforms = {};
gradientUniforms["resolution"] = { type:'v2', value:new THREE.Vector2(width, height)};
gradientUniforms['mousePosition'] = { type:'v2', value:new THREE.Vector2(0, 0) };
var shaderCode = document.getElementById('gradientShader').innerHTML;
var material = new THREE.ShaderMaterial({ uniforms:gradientUniforms, fragmentShader:shaderCode });
var geometry = new THREE.PlaneBufferGeometry(width, height);
var mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
}
function drawHexToScene(scene) {
var hexes = [];
var colCount = 100;
var rowCount = 100;
var hexDiameter = 1;
var xStart = -(colCount) * hexDiameter * 0.5;
var rowSpace = Math.sqrt(3) * (hexDiameter * 0.5);
var yStart = (rowCount - 1) * (rowSpace * 0.5);
var hexGeom = new THREE.CylinderGeometry(hexDiameter * .55, hexDiameter * .55, 0.0625, 6, 1);
hexGeom.rotateX(Math.PI * 0.5);
for (let j = 0; j < rowCount; j++) {
for (let i = 0; i < colCount + (j % 2 === 0 ? 0 : 1); i++) {
let hex = new THREE.Mesh(hexGeom, new THREE.MeshBasicMaterial({
color: 0x000000,
wireframe: false,
}));
var x = (xStart + i * hexDiameter + (j % 2 === 0 ? 0.5 * hexDiameter : 0));
var y = (yStart - j * rowSpace);
hex.position.set(x, y, 0);
hexes.push(hex);
scene.add(hex);
}
}
}
function drawMaskerToScene(scene) {
var boxGeometry = new THREE.BoxGeometry(width, height, 10);
var basicMaterial = new THREE.MeshBasicMaterial({ color: 0xffffff });
maskerMesh = new THREE.Mesh(boxGeometry, basicMaterial);
maskerMesh.position.y = -height;
scene.add(maskerMesh);
}
function onResze() {
camera.position.set(0, 0, -15);
camera.zoom = cameraZoom;
camera.updateProjectionMatrix();
composer.setSize(width, height);
}
function onMouseMove(event) {
mousePositionY = event.clientY;
}
function render() {
webGLRenderer.autoClear = false;
stats.update();
var delta = clock.getDelta();
// orbitControls.update(delta);
requestAnimationFrame(render);
maskerMesh.position.y = mousePositionY;
// composer.setSize(window.innerWidth, window.innerHeight);
composer.render();
}
window.addEventListener('mousemove', onMouseMove);
} // End - init
// Listeners
window.onload = init;
window.onresize = init.onResze;
</script>
</body>
</html>

It was actually a combination of issues. The main issue was setting the flags on the passes, to clear or to render to screen. This wasn't an easy thing to debug:
<!DOCTYPE html>
<html lang="en">
<head>
<title>BloomShader Mask</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no,
minimum-scale=1.0, maximum-scale=1.0">
<style>
body {
color: #fff;
font-family:Monospace;
font-size:13px;
text-align:center;
background-color: #fff;
margin: 0px;
overflow: hidden;
}
#info {
position: absolute;
top: 0px;
width: 100%;
padding: 5px;
}
#info p {
max-width: 600px;
margin-left: auto;
margin-right: auto;
padding: 0 2em;
}
a {
color: #2983ff;
}
</style>
</head>
<body>
<div id="container"></div>
<script src="./build/three.js"></script>
<script src="js/libs/stats.min.js"></script>
<script src="js/libs/dat.gui.min.js"></script>
<script src="js/controls/OrbitControls.js"></script>
<script src="js/loaders/GLTFLoader.js"></script>
<script src="js/postprocessing/EffectComposer.js"></script>
<script src="js/postprocessing/ClearPass.js"></script>
<script src="js/postprocessing/RenderPass.js"></script>
<script src="js/postprocessing/ShaderPass.js"></script>
<script src="js/postprocessing/MaskPass.js "></script>
<script src="js/shaders/CopyShader.js"></script>
<script src="js/shaders/LuminosityHighPassShader.js"></script>
<script src="js/postprocessing/UnrealBloomPass.js"></script>
<script id="gradientShader" type="shader-code">
uniform vec2 resolution;
uniform vec2 mousePosition;
void main() {
vec2 pos = ((gl_FragCoord.xy - mousePosition.xy * 1.3) / resolution.xy);
gl_FragColor = vec4(1.0, pos.x, pos.y, 1.0);
}
</script>
<script>
var clock = new THREE.Clock();
var stats;
var gui;
var sceneHexGradient;
var sceneBloomMasker;
var sceneMasker;
var camera;
var orbitControls;
var pointLight;
var composer;
var mixer;
var maskerMesh;
var bloomPass;
var width = 0;
var height = 0;
var cameraZoom = 120;
var mousePositionY = 0;
var params = {
exposure: 2, //1.1
bloomStrength: 1.75,
bloomThreshold: 0,
bloomRadius: .16,
cameraZoom: cameraZoom
};
function init() {
// Init vars (DOM needed)
gui = initGui();
stats = initStats();
width = window.innerWidth;
height = window.innerHeight;
pixelRatio = window.devicePixelRatio;
// Camera
camera = new THREE.OrthographicCamera(-width, width, height, -height, -1000, 1000);
camera.zoom = cameraZoom;
camera.lookAt(new THREE.Vector3(0, 0, 0));
camera.updateProjectionMatrix();
// Renderer
var webGLRenderer = new THREE.WebGLRenderer({ antialias: true });
webGLRenderer.setClearColor(new THREE.Color(0x000000, 1.0));
webGLRenderer.setPixelRatio(pixelRatio);
webGLRenderer.setSize(width , height);
webGLRenderer.toneMapping = THREE.ReinhardToneMapping;
webGLRenderer.shadowMap.enabled = true;
webGLRenderer.autoClear = false;
appendChild(webGLRenderer.domElement);
// Scene
sceneHexGradient = new THREE.Scene();
sceneBloom = new THREE.Scene();
sceneMasker = new THREE.Scene();
sceneHexGradient.add(camera);
sceneBloom.add(camera);
sceneMasker.add(camera);
// Lights (are they needed ? )
sceneBloom.add(new THREE.AmbientLight(0xffffff));
// Orbit
orbitControls = new THREE.OrbitControls(camera, webGLRenderer.domElement);
orbitControls.maxPolarAngle = Math.PI * 0.5;
orbitControls.minDistance = 1;
orbitControls.maxDistance = 10;
// Draw onto Scenes
drawGradientToScene(sceneHexGradient);
drawHexToScene(sceneHexGradient);
drawMaskerToScene(sceneMasker);
bloomPass = new THREE.UnrealBloomPass(new THREE.Vector2(width, height), 1.5, 0.4, 0.85);
bloomPass.threshold = params.bloomThreshold;
bloomPass.strength = params.bloomStrength;
bloomPass.radius = params.bloomRadius;
// ShaderPasses
var clearPass = new THREE.ClearPass();
var clearMaskPass = new THREE.ClearMaskPass();
var hexgradientPass = new THREE.RenderPass(sceneHexGradient, camera);
var maskingPass = new THREE.MaskPass(sceneMasker, camera);
var outputPass = new THREE.ShaderPass(THREE.CopyShader);
outputPass.renderToScreen = true;
var parameters = {
minFilter: THREE.LinearFilter,
magFilter: THREE.LinearFilter,
format: THREE.RGBFormat,
stencilBuffer: true
};
var renderTarget = new THREE.WebGLRenderTarget(width, height, parameters);
composer = new THREE.EffectComposer(webGLRenderer, renderTarget);
composer.addPass(clearPass);
composer.addPass(hexgradientPass);
composer.addPass(maskingPass);
composer.addPass(bloomPass);
composer.addPass(clearMaskPass);
composer.addPass(outputPass);
function initStats() { // Debug only
var stats = new Stats();
stats.setMode(0);
stats.domElement.style.position = 'absolute';
stats.domElement.style.left = '0px';
stats.domElement.style.top = '0px';
appendChild(stats.dom);
return stats;
}
function initGui() { // Debug only
gui = new dat.GUI();
gui.add(params, 'exposure', 0.1, 2 ).onChange((value) => {
webGLRenderer.toneMappingExposure = Math.pow(value, 4.0);
});
gui.add(params, 'bloomThreshold', 0.0, 1.0).onChange((value) => {
bloomPass.threshold = Number(value);
});
gui.add(params, 'bloomStrength', 0.0, 3.0).onChange((value) => {
bloomPass.strength = Number(value);
});
gui.add(params, 'bloomRadius', 0.0, 1.0).step(0.01).onChange((value) => {
bloomPass.radius = Number(value);
});
gui.add(params, 'cameraZoom', 100, 150).onChange((value) => {
camera.zoom = Number(value);
camera.updateProjectionMatrix();
});
return gui;
}
function appendChild(domElement) {
var container = document.getElementById('container');
if (container) {
return container.appendChild(domElement);
}
return false;
}
function getRandomNumber(min = 1, max = 100) {
return Math.floor(Math.random() * (+max - +min)) + +min;
}
function getArrayOfRandomNumbers(count = 1, min = 1, max = 100) {
const outArr = [];
for( i = 0; i < count; ++i) {
outArr.push(getRandomNumber(min, max));
}
return outArr.sort();
}
function drawBlackoutMask(scene) {
// var x = 0, y = 0;
// var hexDiameter = 2;
// var shapeWidth = 10;
// var facets = getRandomNumber(1, shapeWidth);
// var xValuesArr = getArrayOfRandomNumbers(facets, 0, shapeWidth);
// var yValuesArr = getArrayOfRandomNumbers(facets, 0, shapeWidth);
// var hexGeom = new THREE.CylinderGeometry(hexDiameter * .55, hexDiameter * .55, 0.0625, 6, 1);
// hexGeom.rotateX(Math.PI * 0.5);
// var boxGeometry = new THREE.BoxGeometry(10, 10, 10);
// var mesh = new THREE.Mesh(boxGeometry, new THREE.MeshBasicMaterial({ color: 0xff0000 }));
// mesh.position.x = 0;
// mesh.position.y = 0;
// scene.add(mesh);
// for(var i = 0; i < facets; i++) {
// var hexGeom = new THREE.CylinderGeometry(hexDiameter * .55, hexDiameter * .55, 0.0625, 6, 1);
// var mesh = new THREE.Mesh(hexGeom, new THREE.MeshBasicMaterial({
// color: 0xff0000,
// wireframe: false,
// }));
// mesh.position.x = xValuesArr[i];
// mesh.position.y = yValuesArr[i];
// scene.add(mesh);
// }
}
function drawMaskerToScene(scene) {
var boxGeometry = new THREE.CylinderBufferGeometry(width, height, 10);
// var basicMaterial = new THREE.MeshBasicMaterial({ color: 0xffffff });
boxGeometry.computeBoundingBox();
var material = new THREE.ShaderMaterial({
uniforms: {
color1: {
value: new THREE.Color(0xffffff)
},
color2: {
value: new THREE.Color(0x000000)
},
bboxMin: {
value: boxGeometry.boundingBox.min
},
bboxMax: {
value: boxGeometry.boundingBox.max
}
},
vertexShader: `
uniform vec3 bboxMin;
uniform vec3 bboxMax;
varying vec2 vUv;
void main() {
vUv.y = (position.y - bboxMin.y) / (bboxMax.y - bboxMin.y);
gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0);
}
`,
fragmentShader: `
uniform vec3 color1;
uniform vec3 color2;
varying vec2 vUv;
void main() {
gl_FragColor = vec4(mix(color1, color2, vUv.y), 1.0);
}
`,
wireframe: false
});
maskerMesh = new THREE.Mesh(boxGeometry, material); //basicMaterial);
// maskerMesh.scale.set(1,1,1);
// maskerMesh.position.y = -height;
scene.add(maskerMesh);
}
function drawGradientToScene(scene) {
var gradientUniforms = {};
gradientUniforms["resolution"] = { type:'v2', value:new THREE.Vector2(width, height)};
gradientUniforms['mousePosition'] = { type:'v2', value:new THREE.Vector2(0, 0) };
var shaderCode = document.getElementById('gradientShader').innerHTML;
var material = new THREE.ShaderMaterial({ uniforms:gradientUniforms, fragmentShader:shaderCode });
var geometry = new THREE.PlaneBufferGeometry(width, height);
var mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
}
function drawHexToScene(scene) {
var hexes = [];
var colCount = 100;
var rowCount = 100;
var hexDiameter = 1;
var xStart = -(colCount) * hexDiameter * 0.5;
var rowSpace = Math.sqrt(3) * (hexDiameter * 0.5);
var yStart = (rowCount - 1) * (rowSpace * 0.5);
var hexGeom = new THREE.CylinderGeometry(hexDiameter * .55, hexDiameter * .55, 0.0625, 6, 1);
hexGeom.rotateX(Math.PI * 0.5);
for (let j = 0; j < rowCount; j++) {
for (let i = 0; i < colCount + (j % 2 === 0 ? 0 : 1); i++) {
let hex = new THREE.Mesh(hexGeom, new THREE.MeshBasicMaterial({
color: 0x000000,
wireframe: false,
}));
var x = (xStart + i * hexDiameter + (j % 2 === 0 ? 0.5 * hexDiameter : 0));
var y = (yStart - j * rowSpace);
hex.position.set(x, y, 0);
hexes.push(hex);
scene.add(hex);
}
}
}
function onResze() {
camera.position.set(0, 0, -15);
camera.zoom = cameraZoom;
camera.updateProjectionMatrix();
composer.setSize(width, height);
}
function onMouseMove(event) {
mousePositionY = event.clientY;
}
function render() {
webGLRenderer.autoClear = false;
stats.update();
var delta = clock.getDelta();
// orbitControls.update(delta);
requestAnimationFrame(render);
maskerMesh.position.y = mousePositionY;
webGLRenderer.clear();
composer.render(delta);
}
window.addEventListener('mousemove', onMouseMove);
render();
} // End - init
// Listeners
window.onload = init;
window.onresize = init.onResze;
</script>
</body>

Related

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>

'plus' or 'x' shaped wave in threejs

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

cannon.js object is flipping end to end

I'm working on a little three.js scene in which I want to drive a car down a road. The trouble is my car is flipping end to end each frame, instead of rolling on its tires:
Does anyone know how I can make my car roll with cannon.js? Any pointers would be hugely helpful. For the sake of preservation, here's my raw scene:
var carBody,
floorBody,
pressed = {},
rotation = 0,
clock = new THREE.Clock(),
loader = new THREE.TextureLoader(),
container = document.querySelector('body'),
w = container.clientWidth,
h = container.clientHeight,
scene = new THREE.Scene(),
camera = new THREE.PerspectiveCamera(75, w/h, 0.1, 100000),
controls = new THREE.TrackballControls(camera, container),
renderConfig = {antialias: true, alpha: true},
renderer = new THREE.WebGLRenderer(renderConfig);
controls.target = new THREE.Vector3(0, 0, 0.75);
controls.panSpeed = 0.4;
camera.position.set(0,80,-4900);
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(w, h);
container.appendChild(renderer.domElement);
window.addEventListener('resize', function() {
w = container.clientWidth;
h = container.clientHeight;
camera.aspect = w/h;
camera.updateProjectionMatrix();
renderer.setSize(w, h);
})
function render() {
requestAnimationFrame(render);
renderer.render(scene, camera);
if (car && carBody && carBody.position) {
moveCar();
updatePhysics();
moveCamera();
//controls.update();
}
}
function getPlane(img, w, h, wrap) {
var texture = loader.load(img);
if (wrap) {
texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
texture.repeat.set(10, 10);
}
var material = new THREE.MeshBasicMaterial({
map: texture,
side: THREE.DoubleSide,
});
var geometry = new THREE.PlaneGeometry(w, h, 10, 10);
return new THREE.Mesh(geometry, material);
}
function getSides() {
var d = 300;
var group = new THREE.Group();
for (var i=0; i<2; i++) {
var plane = getPlane('asphalt.jpg', 10000, 200, true);
plane.position.y = 100;
plane.rotation.set(Math.PI/2, -Math.PI/2, Math.PI/2);
plane.position.x = i == 0 ? -d : d;
group.add(plane);
}
return group;
}
function getSky() {
var directions = ['right', 'left', 'top', 'bottom', 'front', 'back'];
var geometry = new THREE.BoxGeometry(50000, 50000, 50000);
var materialArray = [];
for (var i=0; i<6; i++)
materialArray.push( new THREE.MeshBasicMaterial({
map: loader.load(directions[i] + '.bmp'),
side: THREE.BackSide
}));
return new THREE.Mesh( geometry, materialArray );
}
function getCar() {
var mtlLoader = new THREE.MTLLoader();
mtlLoader.load('car.mtl', function(mat) {
mat.preload();
var objLoader = new THREE.OBJLoader();
objLoader.setMaterials(mat);
objLoader.load('car.obj', function(obj) {
window.car = obj;
car.scale.set(0.1, 0.1, 0.1);
obj.position.set(0, 0, -4800);
scene.add(obj);
})
})
}
function getPhysics() {
world = new CANNON.World();
world.gravity.set(0, -100, 0); // earth = -9.82 m/s
world.broadphase = new CANNON.NaiveBroadphase();
world.broadphase.useBoundingBoxes = true;
var solver = new CANNON.GSSolver();
solver.iterations = 7;
solver.tolerance = 0.1;
world.solver = solver;
world.quatNormalizeSkip = 0;
world.quatNormalizeFast = false;
world.defaultContactMaterial.contactEquationStiffness = 1e9;
world.defaultContactMaterial.contactEquationRelaxation = 4;
return world;
}
function addPhysics() {
var m = getPhysicsMaterial();
carBody = new CANNON.Body({
mass: 10,
material: m,
shape: new CANNON.Sphere(30),
linearDamping: 0.6,
angularDamping: 0.8,
position: new CANNON.Vec3(0, 30, -4900)
});
world.addBody(carBody);
// floor
var q = floor.quaternion;
var floorBody = new CANNON.Body({
mass: 0, // mass = 0 makes the body static
material: m,
shape: new CANNON.Plane(),
quaternion: new CANNON.Quaternion(-q._x, q._y, q._z, q._w)
});
world.addBody(floorBody);
}
function getPhysicsMaterial() {
var m = new CANNON.Material('slipperyMaterial');
var c = new CANNON.ContactMaterial(m, m, {
friction: 0.3,
restitution: 0.3,
})
world.addContactMaterial(c);
return m;
}
function moveCar() {
var delta = clock.getDelta(); // seconds
var moveDistance = 2000 * delta; // n pixels per second
// set roll sensitivity
var sensitivity = 1.5;
var rotateAngle = Math.PI / 2 * delta * sensitivity;
// determine the direction to travel
var p = carBody.position;
var dir = new THREE.Vector3(p.x, p.y, p.z);
dir.sub(camera.position).normalize(); // vector b/w camera and car
if (pressed['W'] || pressed['ARROWUP']) {
carBody.velocity.x += moveDistance * dir.x;
carBody.velocity.z += moveDistance * dir.z;
}
if (pressed['S'] || pressed['ARROWDOWN']) {
carBody.velocity.x -= moveDistance * dir.x;
carBody.velocity.z -= moveDistance * dir.z;
}
if (pressed['A'] || pressed['ARROWLEFT']) {
rotation += rotateAngle;
}
if (pressed['D'] || pressed['ARROWRIGHT']) {
rotation -= rotateAngle;
}
if (pressed[' ']) {
carBody.velocity.y = 10;
}
}
function updatePhysics() {
world.step(1/60);
car.position.copy(carBody.position);
car.quaternion.copy(carBody.quaternion);
}
function moveCamera() {
var rotZ = Math.cos(rotation);
var rotX = Math.sin(rotation);
var distance = 100;
camera.position.x = carBody.position.x - (distance * rotX);
camera.position.y = carBody.position.y + 20;
camera.position.z = carBody.position.z - (distance * rotZ);
camera.lookAt(car.position);
}
window.addEventListener('keydown', function(e) {
pressed[e.key.toUpperCase()] = true;
})
window.addEventListener('keyup', function(e) {
pressed[e.key.toUpperCase()] = false;
})
/**
* Add elems
**/
var light = new THREE.HemisphereLight(0xffffbb, 0x080820, 1);
scene.add(light);
var geometry = new THREE.PlaneGeometry(10000, 10000);
var material = new THREE.MeshBasicMaterial();
var floor = new THREE.Mesh(geometry, material);
floor.rotation.x = Math.PI / 2;
scene.add(floor);
var street = getPlane('asphalt.jpg', 10000, 1000, true);
street.rotation.x = Math.PI/2;
street.rotation.z = Math.PI/2;
scene.add(street);
var sides = getSides();
scene.add(sides);
var sky = getSky();
scene.add(sky);
var car = getCar();
var world = getPhysics();
addPhysics();
render();
html,
body {
width: 100%;
height: 100%;
background: #aaa;
}
body {
margin: 0;
overflow: hidden;
}
canvas {
width: 100%;
height: 100%;
}
<script src="https://threejs.org/build/three.min.js"></script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/cannon.js/0.6.2/cannon.js'></script>
<script src='https://raw.githack.com/mrdoob/three.js/master/examples/js/loaders/ObjLoader.js'></script>
<script src='https://raw.githack.com/mrdoob/three.js/master/examples/js/loaders/MTLLoader.js'></script>
<script src='https://raw.githack.com/mrdoob/three.js/master/examples/js/controls/TrackballControls.js'></script>

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

I just started exploring three.js and have been trying to adapt a project I found.
I would like to know if it would be possible to have the globe object rotate around it's y-axis with minor additions to the code or whether it has to be rewritten from the ground up.
var canvas = document.querySelector('canvas');
var width = canvas.offsetWidth,
height = canvas.offsetHeight;
var colors = [
new THREE.Color(0xac1122),
new THREE.Color(0x96789f),
new THREE.Color(0x535353)];
var renderer = new THREE.WebGLRenderer({
canvas: canvas,
antialias: true
});
renderer.setPixelRatio(window.devicePixelRatio > 1 ? 2 : 1);
renderer.setSize(width, height);
renderer.setClearColor(0xffffff);
var scene = new THREE.Scene();
var raycaster = new THREE.Raycaster();
raycaster.params.Points.threshold = 6;
var camera = new THREE.PerspectiveCamera(50, width / height, 0.1, 2000);
camera.position.set(0, 0, 350);
var galaxy = new THREE.Group();
scene.add(galaxy);
// Create dots
var loader = new THREE.TextureLoader();
loader.crossOrigin = "";
var dotTexture = loader.load("img/dotTexture.png");
var dotsAmount = 3000;
var dotsGeometry = new THREE.Geometry();
var positions = new Float32Array(dotsAmount * 3);
var sizes = new Float32Array(dotsAmount);
var colorsAttribute = new Float32Array(dotsAmount * 3);
for (var i = 0; i < dotsAmount; i++) {
var vector = new THREE.Vector3();
vector.color = Math.floor(Math.random() * colors.length);
vector.theta = Math.random() * Math.PI * 2;
vector.phi =
(1 - Math.sqrt(Math.random())) *
Math.PI /
2 *
(Math.random() > 0.5 ? 1 : -1);
vector.x = Math.cos(vector.theta) * Math.cos(vector.phi);
vector.y = Math.sin(vector.phi);
vector.z = Math.sin(vector.theta) * Math.cos(vector.phi);
vector.multiplyScalar(120 + (Math.random() - 0.5) * 5);
vector.scaleX = 5;
if (Math.random() > 0.5) {
moveDot(vector, i);
}
dotsGeometry.vertices.push(vector);
vector.toArray(positions, i * 3);
colors[vector.color].toArray(colorsAttribute, i*3);
sizes[i] = 5;
}
function moveDot(vector, index) {
var tempVector = vector.clone();
tempVector.multiplyScalar((Math.random() - 0.5) * 0.2 + 1);
TweenMax.to(vector, Math.random() * 3 + 3, {
x: tempVector.x,
y: tempVector.y,
z: tempVector.z,
yoyo: true,
repeat: -1,
delay: -Math.random() * 3,
ease: Power0.easeNone,
onUpdate: function () {
attributePositions.array[index*3] = vector.x;
attributePositions.array[index*3+1] = vector.y;
attributePositions.array[index*3+2] = vector.z;
}
});
}
var bufferWrapGeom = new THREE.BufferGeometry();
var attributePositions = new THREE.BufferAttribute(positions, 3);
bufferWrapGeom.addAttribute('position', attributePositions);
var attributeSizes = new THREE.BufferAttribute(sizes, 1);
bufferWrapGeom.addAttribute('size', attributeSizes);
var attributeColors = new THREE.BufferAttribute(colorsAttribute, 3);
bufferWrapGeom.addAttribute('color', attributeColors);
var shaderMaterial = new THREE.ShaderMaterial({
uniforms: {
texture: {
value: dotTexture
}
},
vertexShader: document.getElementById("wrapVertexShader").textContent,
fragmentShader: document.getElementById("wrapFragmentShader").textContent,
transparent:true
});
var wrap = new THREE.Points(bufferWrapGeom, shaderMaterial);
scene.add(wrap);
// Create white segments
var segmentsGeom = new THREE.Geometry();
var segmentsMat = new THREE.LineBasicMaterial({
color: 0xffffff,
transparent: true,
opacity: 0.3,
vertexColors: THREE.VertexColors
});
for (i = dotsGeometry.vertices.length - 1; i >= 0; i--) {
vector = dotsGeometry.vertices[i];
for (var j = dotsGeometry.vertices.length - 1; j >= 0; j--) {
if (i !== j && vector.distanceTo(dotsGeometry.vertices[j]) < 12) {
segmentsGeom.vertices.push(vector);
segmentsGeom.vertices.push(dotsGeometry.vertices[j]);
segmentsGeom.colors.push(colors[vector.color]);
segmentsGeom.colors.push(colors[vector.color]);
}
}
}
var segments = new THREE.LineSegments(segmentsGeom, segmentsMat);
galaxy.add(segments);
var hovered = [];
var prevHovered = [];
function render(a) {
var i;
dotsGeometry.verticesNeedUpdate = true;
segmentsGeom.verticesNeedUpdate = true;
raycaster.setFromCamera( mouse, camera );
var intersections = raycaster.intersectObjects([wrap]);
hovered = [];
if (intersections.length) {
for(i = 0; i < intersections.length; i++) {
var index = intersections[i].index;
hovered.push(index);
if (prevHovered.indexOf(index) === -1) {
onDotHover(index);
}
}
}
for(i = 0; i < prevHovered.length; i++){
if(hovered.indexOf(prevHovered[i]) === -1){
mouseOut(prevHovered[i]);
}
}
prevHovered = hovered.slice(0);
attributeSizes.needsUpdate = true;
attributePositions.needsUpdate = true;
renderer.render(scene, camera);
}
function onDotHover(index) {
dotsGeometry.vertices[index].tl = new TimelineMax();
dotsGeometry.vertices[index].tl.to(dotsGeometry.vertices[index], 1, {
scaleX: 10,
ease: Elastic.easeOut.config(2, 0.2),
onUpdate: function() {
attributeSizes.array[index] = dotsGeometry.vertices[index].scaleX;
}
});
}
function mouseOut(index) {
dotsGeometry.vertices[index].tl.to(dotsGeometry.vertices[index], 0.4, {
scaleX: 5,
ease: Power2.easeOut,
onUpdate: function() {
attributeSizes.array[index] = dotsGeometry.vertices[index].scaleX;
}
});
}
function onResize() {
canvas.style.width = '';
canvas.style.height = '';
width = canvas.offsetWidth;
height = canvas.offsetHeight;
camera.aspect = width / height;
camera.updateProjectionMatrix();
renderer.setSize(width, height);
}
var mouse = new THREE.Vector2(-100,-100);
function onMouseMove(e) {
var canvasBounding = canvas.getBoundingClientRect();
mouse.x = ((e.clientX - canvasBounding.left) / width) * 2 - 1;
mouse.y = -((e.clientY - canvasBounding.top) / height) * 2 + 1;
}
TweenMax.ticker.addEventListener("tick", render);
window.addEventListener("mousemove", onMouseMove);
var resizeTm;
window.addEventListener("resize", function(){
resizeTm = clearTimeout(resizeTm);
resizeTm = setTimeout(onResize, 200);
});
Codepen here - https://codepen.io/quickwaste/pen/PaGPdw
Thanks.
(A stretch goal would be to have the camera move in response to mouse movement)
Simply add galaxy.rotateY(0.005 * Math.PI); to render(), right before renderer.render(scene, camera) call, like this:
// pulled from the CodePen
function render(a) {
// ... omitted for brevity
prevHovered = hovered.slice(0);
attributeSizes.needsUpdate = true;
attributePositions.needsUpdate = true;
galaxy.rotateY(0.005 * Math.PI);
renderer.render(scene, camera);
}
I used a multiplier of 0.005 to give the globe a nice, lazy spin.
The 'galaxy' object is a THREE.Group, a wrapper of sorts for collections of THREE.Object3D objects. The Object3D has all sorts of nifty functions to help rotate, translate, and transform 3D objects. The rotateY() will spin the model around its local y-axis.

How to remove jitter/shaking in my special application of OrbitControl and PerspectiveCamera?

In the live snippet below (also at https://jsfiddle.net/gpolyn/bpo7t7f6), I offer optional dynamic updating of PerspectiveCamera's lookAt parameter and fov in the three.js render cycle. (My goal is to fill as much of the viewport as possible with the subject cube, through all orbit positions.)
I suspect that lack of precision in the matrix math code used to calculate my dynamic fov and lookAt paramters (three.js uses Float32 arrays) causes the jitter/shaking I notice in the cube when orbiting with the dynamic options selected from the controls.
(The matrix operations can be found in the snippet addExtrema function.)
In my demo, my highest goal is to remove jitter/shaking in case 1, described here:
the "dynamicFovAndLookAt" control option uses dynamic fov and lookAt updating causing quite a bit of jitter in the cube, no matter the orbit position; the fov and lookAt parameters can be seen fluctuating in the demo's lower right status box;
"dynamicFov" uses dynamic fov updating causing some jitter in the cube, depending on orbiting; the fov parameter in the lower right status box will vary due to the dynamic recalculation;
the "boundingSphere" option uses no dynamic fov, lookAt updating and the cube exhibits no jitter/shake through orbiting – the fov and lookAt parameters are constant in the lower right status box.
(Orbit position is reported in the lower left corner of the demo, while one of the box corners has a green dot to aid any discussion of the jitter effect.)
var renderer, scene, camera, controls;
var object;
var vertices3;
var cloud;
var boxToBufferAlphaMapping = {
0: 0,
2: 1,
1: 2,
3: 4,
6: 7,
7: 10,
5: 8,
4: 6
}
var lastAlphas = [];
var canvasWidth, canvasHeight;
var windowMatrix;
var boundingSphere;
var figure;
var fovWidth, fovDistance, fovHeight;
var newFov, newLookAt;
var dist, height, fov, lookAt;
var aspect;
var CONSTANT_FOR_FOV_CALC = 180 / Math.PI;
var mat3;
var CORNERS = 8;
var ndc = new Array(CORNERS);
var USE_GREEN_DOTS = false;
var stats, orbitPosition, cameraFacts;
var useDynamicFov;
var datGuiData = {};
init();
render();
afterInit();
animate();
function init() {
mat3 = new THREE.Matrix4();
canvasWidth = window.innerWidth;
canvasHeight = window.innerHeight;
aspect = canvasWidth / canvasHeight;
// renderer
<!-- renderer = new THREE.WebGLRenderer({antialias:true, logarithmicDepthBuffer:true}); -->
renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setSize(canvasWidth, canvasHeight);
document.body.appendChild(renderer.domElement);
// scene
scene = new THREE.Scene();
// object
var geometry = new THREE.BoxGeometry(4, 4, 6);
// too lazy to add edges without EdgesHelper...
var material = new THREE.MeshBasicMaterial({
transparent: true,
opacity: 0
});
var cube = new THREE.Mesh(geometry, material);
object = cube;
// bounding sphere used for orbiting control in render
object.geometry.computeBoundingSphere();
boundingSphere = object.geometry.boundingSphere;
cube.position.set(2, 2, 3)
// awkward, but couldn't transfer cube position to sphere...
boundingSphere.translate(new THREE.Vector3(2, 2, 3));
// save vertices for subsequent use
vertices = cube.geometry.vertices;
var edges = new THREE.EdgesHelper(cube)
scene.add(edges);
scene.add(cube);
<!-- if (USE_GREEN_DOTS) addGreenDotsToScene(geometry); -->
addGreenDotsToScene(geometry);
// camera
<!-- camera = new THREE.PerspectiveCamera( 17, window.innerWidth / window.innerHeight, 20, 40 ); -->
camera = new THREE.PerspectiveCamera(17, window.innerWidth / window.innerHeight);
camera.position.set(20, 20, 20);
// controls
controls = new THREE.OrbitControls(camera);
controls.maxPolarAngle = 0.5 * Math.PI;
controls.minAzimuthAngle = 0;
controls.maxAzimuthAngle = 0.5 * Math.PI;
controls.enableZoom = false;
// performance monitor
stats = new Stats();
document.body.appendChild(stats.dom);
// orbitposition tracker
orbitPosition = new THREEg.OrbitReporter()
orbitPosition.domElement.style.position = 'absolute'
orbitPosition.domElement.style.left = '0px'
orbitPosition.domElement.style.bottom = '0px'
document.body.appendChild(orbitPosition.domElement)
// camera facts
cameraFacts = new THREEg.CameraReporter()
cameraFacts.domElement.style.position = 'absolute'
cameraFacts.domElement.style.right = '0px'
cameraFacts.domElement.style.bottom = '0px'
document.body.appendChild(cameraFacts.domElement)
// ambient
scene.add(new THREE.AmbientLight(0x222222));
// axes
scene.add(new THREE.AxisHelper(20));
// initial settings
dist = boundingSphere.distanceToPoint(camera.position);
height = boundingSphere.radius * 2;
fov = 2 * Math.atan(height / (2 * dist)) * (CONSTANT_FOR_FOV_CALC);
newFov = fov;
lookAt = new THREE.Vector3(2, 2, 3); // center of box
newLookAt = lookAt;
// dat.gui
window.onload = function() {
var view = datGuiData;
view.boundingSphere = true;
view.dynamicFov = false;
view.dynamicFovAndLookAt = false;
var gui = new dat.GUI();
var CB1Controller = gui.add(view, 'boundingSphere').listen();
CB1Controller.onChange(function(value) {
view.boundingSphere = true;
view.dynamicFov = false;
view.dynamicFovAndLookAt = false;
});
var CB2Controller = gui.add(view, 'dynamicFov').listen();
CB2Controller.onChange(function(value) {
view.boundingSphere = false;
view.dynamicFov = true;
view.dynamicFovAndLookAt = false;
});
var CB3Controller = gui.add(view, 'dynamicFovAndLookAt').listen();
CB3Controller.onChange(function(value) {
view.boundingSphere = false;
view.dynamicFov = true;
view.dynamicFovAndLookAt = true;
});
};
}
function addExtrema() {
// thread A
mat3.multiplyMatrices(camera.matrixWorld, mat3.getInverse(camera.projectionMatrix));
// thread B
var scratchVar;
var topIdx, bottomIdx, leftIdx, rightIdx;
var top = Number.NEGATIVE_INFINITY;
var bottom = Number.POSITIVE_INFINITY;
var right = Number.NEGATIVE_INFINITY;
var left = Number.POSITIVE_INFINITY;
var closestVertex, closestVertexDistance = Number.POSITIVE_INFINITY;
var vtx;
for (var i = 0; i < CORNERS; i++) {
scratchVar = vertices3[i].clone().applyMatrix4(camera.matrixWorldInverse);
scratchVar.applyMatrix4(camera.projectionMatrix);
scratchVar.divideScalar(scratchVar.w)
ndc[i] = scratchVar;
vtx = ndc[i];
if (vtx.x < left) {
left = vtx.x;
leftIdx = i;
} else if (vtx.x > right) {
right = vtx.x;
rightIdx = i;
}
if (vtx.y < bottom) {
bottom = vtx.y;
bottomIdx = i;
} else if (vtx.y > top) {
top = vtx.y;
topIdx = i;
}
if (vtx.z < closestVertexDistance) {
closestVertex = i;
closestVertexDistance = vtx.z;
}
}
var yNDCPercentCoverage = (Math.abs(ndc[topIdx].y) + Math.abs(ndc[bottomIdx].y)) / 2;
yNDCPercentCoverage = Math.min(1, yNDCPercentCoverage);
var xNDCPercentCoverage = (Math.abs(ndc[leftIdx].x) + Math.abs(ndc[rightIdx].x)) / 2;
xNDCPercentCoverage = Math.min(1, xNDCPercentCoverage);
var ulCoords = [ndc[leftIdx].x, ndc[topIdx].y, closestVertexDistance, 1]
var blCoords = [ndc[leftIdx].x, ndc[bottomIdx].y, closestVertexDistance, 1]
var urCoords = [ndc[rightIdx].x, ndc[topIdx].y, closestVertexDistance, 1]
var ul = new THREE.Vector4().fromArray(ulCoords);
ul.applyMatrix4(mat3).divideScalar(ul.w);
var bl = new THREE.Vector4().fromArray(blCoords);
bl.applyMatrix4(mat3).divideScalar(bl.w);
var ur = new THREE.Vector4().fromArray(urCoords);
ur.applyMatrix4(mat3).divideScalar(ur.w);
var center = new THREE.Vector3();
center.addVectors(ur, bl);
center.divideScalar(2);
var dist = camera.position.distanceTo(center);
var upperLeft = new THREE.Vector3().fromArray(ul.toArray().slice(0, 3));
var p;
if ((1 - yNDCPercentCoverage) < (1 - xNDCPercentCoverage)) { // height case
var bottomLeft = new THREE.Vector3().fromArray(bl.toArray().slice(0, 3));
var height = upperLeft.distanceTo(bottomLeft);
p = 2 * Math.atan(height / (2 * dist)) * (CONSTANT_FOR_FOV_CALC);
} else { // width case
var upperRight = new THREE.Vector3().fromArray(ur.toArray().slice(0, 3));
var width = upperRight.distanceTo(upperLeft);
p = 2 * Math.atan((width / aspect) / (2 * dist)) * (CONSTANT_FOR_FOV_CALC);
}
if (datGuiData.dynamicFovAndLookAt || datGuiData.dynamicFov) {
newFov = p;
} else {
dist = boundingSphere.distanceToPoint(camera.position);
height = boundingSphere.radius * 2;
newFov = 2 * Math.atan(height / (2 * dist)) * (CONSTANT_FOR_FOV_CALC);
}
if (datGuiData.dynamicFovAndLookAt == true) {
newLookAt = center;
} else {
newLookAt = lookAt;
}
if (USE_GREEN_DOTS) {
var alphas = cloud.geometry.attributes.alpha;
// make last points invisible
lastAlphas.forEach(function(alphaIndex) {
alphas.array[alphaIndex] = 0.0;
});
// now, make new points visible...
// (boxToBufferAlphaMapping is a BufferGeometry-Object3D geometry
// map between the object and green dots)
alphas.array[boxToBufferAlphaMapping[rightIdx]] = 1.0;
alphas.array[boxToBufferAlphaMapping[bottomIdx]] = 1.0;
alphas.array[boxToBufferAlphaMapping[topIdx]] = 1.0;
alphas.array[boxToBufferAlphaMapping[leftIdx]] = 1.0;
// store visible points for next cycle
lastAlphas = [boxToBufferAlphaMapping[rightIdx]];
lastAlphas.push(boxToBufferAlphaMapping[bottomIdx])
lastAlphas.push(boxToBufferAlphaMapping[topIdx])
lastAlphas.push(boxToBufferAlphaMapping[leftIdx])
alphas.needsUpdate = true;
}
}
function addGreenDotsToScene(geometry) {
var bg = new THREE.BufferGeometry();
bg.fromGeometry(geometry);
bg.translate(2, 2, 3); // yucky, and quick
var numVertices = bg.attributes.position.count;
var alphas = new Float32Array(numVertices * 1); // 1 values per vertex
<!-- for( var i = 0; i < numVertices; i ++ ) { -->
<!-- alphas[ i ] = 1; -->
<!-- } -->
alphas[2] = 1;
bg.addAttribute('alpha', new THREE.BufferAttribute(alphas, 1));
var uniforms = {
color: {
type: "c",
value: new THREE.Color(0x00ff00)
},
};
var shaderMaterial = new THREE.ShaderMaterial({
uniforms: uniforms,
vertexShader: document.getElementById('vertexshader').textContent,
fragmentShader: document.getElementById('fragmentshader').textContent,
transparent: true
});
cloud = new THREE.Points(bg, shaderMaterial);
scene.add(cloud);
}
function afterInit() {
windowMatrix = new THREE.Matrix4();
windowMatrix.set(canvasWidth / 2, 0, 0, canvasWidth / 2, 0, canvasHeight / 2, 0, canvasHeight / 2, 0, 0, 0.5, 0.5, 0, 0, 0, 1);
var vertices2 = object.geometry.vertices.map(function(vtx) {
return (new THREE.Vector4(vtx.x, vtx.y, vtx.z));
});
// create 'world-space' geometry points, using
// model ('world') matrix
vertices3 = vertices2.map(function(vt) {
return vt.applyMatrix4(object.matrixWorld);
})
}
function render() {
<!-- console.log({far: camera.far, camera_near: camera.near}) -->
camera.lookAt(newLookAt);
camera.fov = newFov;
camera.updateProjectionMatrix();
renderer.render(scene, camera);
}
function animate() {
requestAnimationFrame(animate);
render();
addExtrema()
stats.update();
orbitPosition.update(controls);
cameraFacts.update(camera, newLookAt);
}
body {
background-color: #000;
margin: 0px;
overflow: hidden;
}
.dg .c {
width: 40%
}
.dg .property-name {
width: 60%
}
<script src="https://rawgit.com/mrdoob/three.js/dev/build/three.min.js"></script>
<script src="https://rawgit.com/mrdoob/three.js/master/examples/js/libs/stats.min.js"></script>
<script src="https://rawgit.com/mrdoob/three.js/master/examples/js/controls/OrbitControls.js"></script>
<script src="https://rawgit.com/gpolyn/789d63a662c1768320756f68a6099f15/raw/3a0f323bb284b09e624a11f93ff4055e23adea80/OrbitReporter.js"></script>
<script src="https://rawgit.com/gpolyn/70352cb34c7900ed2489400d4ecc45f7/raw/7b6e7e6bb3e175d4145879ef1afdeb38c31cf785/camera_reporter.js"></script>
<script src="https://rawgit.com/mrdoob/three.js/master/examples/js/libs/dat.gui.min.js"></script>
<script type="x-shader/x-vertex" id="vertexshader">
attribute float alpha; varying float vAlpha; void main() { vAlpha = alpha; vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 ); gl_PointSize = 8.0; gl_Position = projectionMatrix * mvPosition; }
</script>
<script type="x-shader/x-fragment" id="fragmentshader">
uniform vec3 color; varying float vAlpha; void main() { gl_FragColor = vec4( color, vAlpha ); }
</script>

Categories

Resources