Optimize hundreds of dynamic lines in Three.js - javascript

I'm new to Three.js, I'm just having fun with it. I'm trying to achieve a simple dynamic background fullscreen on a page, you get the example here:
function createHexagon( vertices, color ) {
var geometry = new THREE.BufferGeometry();
geometry.addAttribute( 'position', new THREE.BufferAttribute( vertices, 3 ) );
var material = new THREE.LineBasicMaterial( { color: color, opacity: Math.min((Math.random() / 5), 0.1), transparent: true } );
var hexagon = new THREE.Line( geometry, material );
return hexagon;
}
function initMatrix() {
var color = defaultColor.getHex();
var vertices;
var x = ( width / -2 ) - 90;
var y = height / -2;
var deltaX = 120;
var deltaY = 60;
var time = 5.0;
while( y < height / 2 ) {
while( x < width / 2 ) {
vertices = new Float32Array([
0, 30, 0,
20, 0, 0
]);
var hexagon = createHexagon( vertices, color );
scene.add( hexagon );
hexagon.position.set( x, y, 0 );
vertices = new Float32Array([
20, 0, 0,
60, 0, 0
]);
var hexagon = createHexagon( vertices, color );
scene.add( hexagon );
hexagon.position.set( x, y, 0 );
vertices = new Float32Array([
60, 0, 0,
80, 30, 0
]);
var hexagon = createHexagon( vertices, color );
scene.add( hexagon );
hexagon.position.set( x, y, 0 );
x += deltaX;
}
x = ( width / -2 ) - 90;
y += deltaY;
}
x = ( width / -2 ) - 30;
y = ( height / -2 ) - 30;
deltaX = 120;
deltaY = 60;
while( y < height / 2 ) {
while( x < width / 2 ) {
vertices = new Float32Array([
0, 30, 0,
20, 0, 0
]);
var hexagon = createHexagon( vertices, color );
scene.add( hexagon );
hexagon.position.set( x, y, 0 );
vertices = new Float32Array([
20, 0, 0,
60, 0, 0
]);
var hexagon = createHexagon( vertices, color );
scene.add( hexagon );
hexagon.position.set( x, y, 0 );
vertices = new Float32Array([
60, 0, 0,
80, 30, 0
]);
var hexagon = createHexagon( vertices, color );
scene.add( hexagon );
hexagon.position.set( x, y, 0 );
x += deltaX;
}
x = ( width / -2 ) - 30;
y += deltaY;
}
}
Those are single bufferGeometry lines (as you can see in the above functions to create the background) that randomly rotate and change opacity on mouse hover with raycaster and TweenLite. It works pretty fine. What you can notice is that CPU usage goes to almost 100%.
I know that if I group lines into the same geometry it'll be better on performance, but then I'm not able to animate single lines with raycaster, especially the opacity.
I searched a lot of discussions and tried so many things. The best result is this way, rendering single lines separately. Can you suggest some tips about it?

(Posted on behalf of the question author).
I found a solution. I made a whole geometry and I indexed vertices and respective colors. The only problem is that I can't mange opacity this way, but it works fine and CPU is around 20%.
document.addEventListener( 'DOMContentLoaded', main );
var width = screen.width;
var height = screen.height;
var camera, scene, renderer, raycaster, mouse;
var rayColor = new THREE.Color( 0x0640C2 );
var rayColor2 = new THREE.Color( 0xCC311B );
var colors;
var linesPositions, originalPositions;
var linesMesh;
var geometry;
var intersects;
var stats;
function initMatrix() {
var AlinesPositions = [ ];
var x = ( width / - 2 ) - 80;
var y = ( height / - 2 ) - 60;
var deltaX = 120;
var deltaY = 60;
while( y <= ( height / 2 ) ) {
while( x <= ( width / 2 ) ) {
AlinesPositions.push( x, y, 0 /*Math.abs( ( 100 / ( width / 2 ) * x ) + ( 100 / ( height / 2 ) * y ) )*/ );
AlinesPositions.push( x + 80, y, 0 /*Math.abs( ( 100 / ( width / 2 ) * x ) + ( 100 / ( height / 2 ) * y ) )*/ );
AlinesPositions.push( x + 80, y, 0 /*Math.abs( ( 100 / ( width / 2 ) * x ) + ( 100 / ( height / 2 ) * y ) )*/ );
AlinesPositions.push( x + 80, y + 60, 0 /*Math.abs( ( 100 / ( width / 2 ) * x ) + ( 100 / ( height / 2 ) * y ) )*/ );
AlinesPositions.push( x + 80, y + 60, 0 /*Math.abs( ( 100 / ( width / 2 ) * x ) + ( 100 / ( height / 2 ) * y ) )*/ );
AlinesPositions.push( x, y + 60, 0 /*Math.abs( ( 100 / ( width / 2 ) * x ) + ( 100 / ( height / 2 ) * y ) )*/ );
AlinesPositions.push( x, y + 60, 0 /*Math.abs( ( 100 / ( width / 2 ) * x ) + ( 100 / ( height / 2 ) * y ) )*/ );
AlinesPositions.push( x, y, 0 /*Math.abs( ( 100 / ( width / 2 ) * x ) + ( 100 / ( height / 2 ) * y ) )*/ );
x += deltaX;
}
x = ( width / -2 ) - 80;
y += deltaY;
}
x = ( width / - 2 );
y = ( height / - 2 ) - 60;
while( y <= ( height / 2 ) ) {
while( x <= ( width / 2 ) ) {
AlinesPositions.push( x, y, 0 /*Math.abs( ( 100 / ( width / 2 ) * x ) + ( 100 / ( height / 2 ) * y ) )*/ );
AlinesPositions.push( x + 80, y, 0 /*Math.abs( ( 100 / ( width / 2 ) * x ) + ( 100 / ( height / 2 ) * y ) )*/ );
AlinesPositions.push( x + 80, y, 0 /*Math.abs( ( 100 / ( width / 2 ) * x ) + ( 100 / ( height / 2 ) * y ) )*/ );
AlinesPositions.push( x + 80, y + 60, 0 /*Math.abs( ( 100 / ( width / 2 ) * x ) + ( 100 / ( height / 2 ) * y ) )*/ );
AlinesPositions.push( x + 80, y + 60, 0 /*Math.abs( ( 100 / ( width / 2 ) * x ) + ( 100 / ( height / 2 ) * y ) )*/ );
AlinesPositions.push( x, y + 60, 0 /*Math.abs( ( 100 / ( width / 2 ) * x ) + ( 100 / ( height / 2 ) * y ) )*/ );
AlinesPositions.push( x, y + 60, 0 /*Math.abs( ( 100 / ( width / 2 ) * x ) + ( 100 / ( height / 2 ) * y ) )*/ );
AlinesPositions.push( x, y, 0 /*Math.abs( ( 100 / ( width / 2 ) * x ) + ( 100 / ( height / 2 ) * y ) )*/ );
x += deltaX;
}
x = ( width / -2 );
y += deltaY;
}
linesPositions = new Float32Array( AlinesPositions );
var Acolors = [ ];
for( var i = 0; i < AlinesPositions.length; i++ ) {
var fact = Math.random() * 20.0;
var ran = Math.min( Math.max( ( Math.random() / fact ), 0.01 ), 0.5 );
Acolors[i] = ran;
Acolors[i+1] = ran;
Acolors[i+2] = ran;
i += 2;
}
colors = new Float32Array( Acolors );
geometry = new THREE.BufferGeometry();
geometry.addAttribute( 'position', new THREE.BufferAttribute( linesPositions, 3 ).setDynamic( true ) );
geometry.addAttribute( 'color', new THREE.BufferAttribute( colors, 3 ).setDynamic( true ) );
var material = new THREE.LineBasicMaterial( {
vertexColors: THREE.VertexColors,
blending: THREE.AdditiveBlending,
transparent: true
} );
linesMesh = new THREE.LineSegments( geometry, material );
scene.add( linesMesh );
originalPositions = new THREE.BufferAttribute( linesPositions, 3 );
originalPositions.copy( linesMesh.geometry.attributes.position );
}
function init() {
scene = new THREE.Scene();
//camera = new THREE.OrthographicCamera( width / - 2, width / 2, height / 2, height / - 2, 1, 100 );
camera = new THREE.PerspectiveCamera( 120, width / height, 1, 1000 );
camera.position.set( 0, 0, 200 );
camera.lookAt( 0, 0, 0 );
// Create matrix
initMatrix();
raycaster = new THREE.Raycaster();
raycaster.linePrecision = 20;
mouse = new THREE.Vector2();
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize( width, height );
document.body.appendChild( renderer.domElement );
}
function main() {
init();
animate();
}
function animate() {
requestAnimationFrame( animate );
render();
}
var intersected = [];
function removeX( i ) { intersected.splice( i, 1 ); }
function alterate( index ) {
var randColor = ( Math.random() <= 0.49 ) ? rayColor : rayColor2;
// Change color
var currentC = new THREE.Color( linesMesh.geometry.attributes.color.getX( index ), linesMesh.geometry.attributes.color.getY( index ), linesMesh.geometry.attributes.color.getZ( index ));
var tweenC = TweenLite.to( currentC, 1.0, {
r: randColor.r,
g: randColor.g,
b: randColor.b,
immediateRender: true,
lazy: false,
onUpdate: function() {
linesMesh.geometry.attributes.color.setXYZ( index, currentC.r, currentC.g, currentC.b );
linesMesh.geometry.attributes.color.needsUpdate = true;
},
onUpdateParams: [ index, currentC ]
});
// Change coordinates
var currentXY = { x: originalPositions.getX( index ), y: originalPositions.getY( index ), z: originalPositions.getZ( index ) };
var goal = [ ( currentXY.x + ( Math.random() * 50 ) * ( ( Math.random() < 0.5 ) ? 1 : -1 ) ), ( currentXY.y + ( Math.random() * 50 ) * ( ( Math.random() < 0.5 ) ? 1 : -1 ) ), ( currentXY.z + ( Math.random() * 50 ) * ( ( Math.random() < 0.5 ) ? 1 : -1 ) ) ];
var tweenXY = TweenLite.to( currentXY, 1.0, {
x: goal[ 0 ],
y: goal[ 1 ],
z: goal[ 2 ],
immediateRender: true,
lazy: false,
onUpdate: function() {
linesMesh.geometry.attributes.position.setXYZ( index, currentXY.x, currentXY.y, currentXY.z );
linesMesh.geometry.attributes.position.needsUpdate = true;
},
onUpdateParams: [ index, currentXY ]
});
}
function render() {
// update the picking ray with the camera and mouse position
raycaster.setFromCamera( mouse, camera );
intersects = raycaster.intersectObject( linesMesh );
if( intersects.length ) {
if( !intersected.includes( intersects[ 0 ].index ) ) {
for( var x = 0; x < intersects.length; x++ ) {
if( !intersected.includes( intersects[ x ].index ) ) {
var index = intersects[ x ].index;
// Save index
intersected.push( index );
alterate( index );
}
}
var present = 0;
for( var y = 0; y < intersected.length; y++ ) {
for( var j = 0; j < intersects.length; j++ ) {
if( intersects[ j ].index == intersected[ y ] ) { present = 1; break; }
}
if( !present ) {
( function( y ) {
var randC = Math.min( Math.max( ( Math.random() / ( Math.random() * 20.0 ) ), 0.01 ), 0.5 );
// Current item old coordinates
let index = intersected[ y ];
var indexX = originalPositions.getX( index );
var indexY = originalPositions.getY( index );
var indexZ = originalPositions.getZ( index );
removeX( y );
// Reset color
var currentCreset = new THREE.Color( linesMesh.geometry.attributes.color.getX( index ), linesMesh.geometry.attributes.color.getY( index ), linesMesh.geometry.attributes.color.getZ( index ));
var tweenCreset = TweenLite.to( currentCreset, 1.5, {
r: randC,
g: randC,
b: randC,
immediateRender: true,
lazy: false,
onUpdate: function() {
linesMesh.geometry.attributes.color.setXYZ( index, currentCreset.r, currentCreset.g, currentCreset.b );
linesMesh.geometry.attributes.color.needsUpdate = true;
},
onUpdateParams: [ index, currentCreset ],
onComplete: function() { }
} );
// Reset coordinates
var currentXYreset = { x: linesMesh.geometry.attributes.position.getX( index ), y: linesMesh.geometry.attributes.position.getY( index ), z: linesMesh.geometry.attributes.position.getZ( index ) };
var tweenXYreset = TweenLite.to( currentXYreset, 1.5, {
x: indexX,
y: indexY,
z: indexZ,
immediateRender: true,
lazy: false,
onUpdate: function() {
linesMesh.geometry.attributes.position.setXYZ( index, currentXYreset.x, currentXYreset.y, currentXYreset.z );
linesMesh.geometry.attributes.position.needsUpdate = true;
},
onUpdateParams: [ index, currentXYreset ]
});
}) ( y );
}
}
}
}
renderer.render( scene, camera );
}
function onMouseMove( event ) {
// calculate mouse position in normalized device coordinates
// (-1 to +1) for both components
event.preventDefault();
mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
var normX = event.clientX - ( window.innerWidth / 2 );
var normY = event.clientY - ( window.innerHeight / 2 );
camera.position.set( 0 + ( normY / 50 ), 0 + ( normX / 50 ), 200 );
camera.lookAt( 0 + ( normY / 50 ), 0 + ( normX / 50 ), 0 );
}
window.addEventListener( 'click', function() {
if( intersected.length ) {
for( var x = 0; x < intersected.length; x++ ) {
var index = intersected[ x ];
alterate( index );
}
}
});
window.addEventListener( 'mousemove', onMouseMove, false );
// Set new dimensions for scene when resize window
window.addEventListener( 'resize', function() {
var wWidth = window.innerWidth;
var wHeight = window.innerHeight;
/*camera.left = wWidth / -2;
camera.right = wWidth / 2;
camera.top = wHeight / 2;
camera.bottom = wHeight / -2;*/
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
raycaster.setFromCamera( mouse, camera );
renderer.setSize( window.innerWidth, window.innerHeight );
} );
Here is a Codepen link.

I found a solution. I made a whole geometry and I indexed vertices and respective colors. The only problem is that I can't mange opacity this way, but it works fine and CPU is around 20%.
document.addEventListener( 'DOMContentLoaded', main );
var width = screen.width;
var height = screen.height;
var camera, scene, renderer, raycaster, mouse;
var rayColor = new THREE.Color( 0x0640C2 );
var rayColor2 = new THREE.Color( 0xCC311B );
var colors;
var linesPositions, originalPositions;
var linesMesh;
var geometry;
var intersects;
var stats;
function initMatrix() {
var AlinesPositions = [ ];
var x = ( width / - 2 ) - 80;
var y = ( height / - 2 ) - 60;
var deltaX = 120;
var deltaY = 60;
while( y <= ( height / 2 ) ) {
while( x <= ( width / 2 ) ) {
AlinesPositions.push( x, y, 0 /*Math.abs( ( 100 / ( width / 2 ) * x ) + ( 100 / ( height / 2 ) * y ) )*/ );
AlinesPositions.push( x + 80, y, 0 /*Math.abs( ( 100 / ( width / 2 ) * x ) + ( 100 / ( height / 2 ) * y ) )*/ );
AlinesPositions.push( x + 80, y, 0 /*Math.abs( ( 100 / ( width / 2 ) * x ) + ( 100 / ( height / 2 ) * y ) )*/ );
AlinesPositions.push( x + 80, y + 60, 0 /*Math.abs( ( 100 / ( width / 2 ) * x ) + ( 100 / ( height / 2 ) * y ) )*/ );
AlinesPositions.push( x + 80, y + 60, 0 /*Math.abs( ( 100 / ( width / 2 ) * x ) + ( 100 / ( height / 2 ) * y ) )*/ );
AlinesPositions.push( x, y + 60, 0 /*Math.abs( ( 100 / ( width / 2 ) * x ) + ( 100 / ( height / 2 ) * y ) )*/ );
AlinesPositions.push( x, y + 60, 0 /*Math.abs( ( 100 / ( width / 2 ) * x ) + ( 100 / ( height / 2 ) * y ) )*/ );
AlinesPositions.push( x, y, 0 /*Math.abs( ( 100 / ( width / 2 ) * x ) + ( 100 / ( height / 2 ) * y ) )*/ );
x += deltaX;
}
x = ( width / -2 ) - 80;
y += deltaY;
}
x = ( width / - 2 );
y = ( height / - 2 ) - 60;
while( y <= ( height / 2 ) ) {
while( x <= ( width / 2 ) ) {
AlinesPositions.push( x, y, 0 /*Math.abs( ( 100 / ( width / 2 ) * x ) + ( 100 / ( height / 2 ) * y ) )*/ );
AlinesPositions.push( x + 80, y, 0 /*Math.abs( ( 100 / ( width / 2 ) * x ) + ( 100 / ( height / 2 ) * y ) )*/ );
AlinesPositions.push( x + 80, y, 0 /*Math.abs( ( 100 / ( width / 2 ) * x ) + ( 100 / ( height / 2 ) * y ) )*/ );
AlinesPositions.push( x + 80, y + 60, 0 /*Math.abs( ( 100 / ( width / 2 ) * x ) + ( 100 / ( height / 2 ) * y ) )*/ );
AlinesPositions.push( x + 80, y + 60, 0 /*Math.abs( ( 100 / ( width / 2 ) * x ) + ( 100 / ( height / 2 ) * y ) )*/ );
AlinesPositions.push( x, y + 60, 0 /*Math.abs( ( 100 / ( width / 2 ) * x ) + ( 100 / ( height / 2 ) * y ) )*/ );
AlinesPositions.push( x, y + 60, 0 /*Math.abs( ( 100 / ( width / 2 ) * x ) + ( 100 / ( height / 2 ) * y ) )*/ );
AlinesPositions.push( x, y, 0 /*Math.abs( ( 100 / ( width / 2 ) * x ) + ( 100 / ( height / 2 ) * y ) )*/ );
x += deltaX;
}
x = ( width / -2 );
y += deltaY;
}
linesPositions = new Float32Array( AlinesPositions );
var Acolors = [ ];
for( var i = 0; i < AlinesPositions.length; i++ ) {
var fact = Math.random() * 20.0;
var ran = Math.min( Math.max( ( Math.random() / fact ), 0.01 ), 0.5 );
Acolors[i] = ran;
Acolors[i+1] = ran;
Acolors[i+2] = ran;
i += 2;
}
colors = new Float32Array( Acolors );
geometry = new THREE.BufferGeometry();
geometry.addAttribute( 'position', new THREE.BufferAttribute( linesPositions, 3 ).setDynamic( true ) );
geometry.addAttribute( 'color', new THREE.BufferAttribute( colors, 3 ).setDynamic( true ) );
var material = new THREE.LineBasicMaterial( {
vertexColors: THREE.VertexColors,
blending: THREE.AdditiveBlending,
transparent: true
} );
linesMesh = new THREE.LineSegments( geometry, material );
scene.add( linesMesh );
originalPositions = new THREE.BufferAttribute( linesPositions, 3 );
originalPositions.copy( linesMesh.geometry.attributes.position );
}
function init() {
scene = new THREE.Scene();
//camera = new THREE.OrthographicCamera( width / - 2, width / 2, height / 2, height / - 2, 1, 100 );
camera = new THREE.PerspectiveCamera( 120, width / height, 1, 1000 );
camera.position.set( 0, 0, 200 );
camera.lookAt( 0, 0, 0 );
// Create matrix
initMatrix();
raycaster = new THREE.Raycaster();
raycaster.linePrecision = 20;
mouse = new THREE.Vector2();
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize( width, height );
document.body.appendChild( renderer.domElement );
}
function main() {
init();
animate();
}
function animate() {
requestAnimationFrame( animate );
render();
}
var intersected = [];
function removeX( i ) { intersected.splice( i, 1 ); }
function alterate( index ) {
var randColor = ( Math.random() <= 0.49 ) ? rayColor : rayColor2;
// Change color
var currentC = new THREE.Color( linesMesh.geometry.attributes.color.getX( index ), linesMesh.geometry.attributes.color.getY( index ), linesMesh.geometry.attributes.color.getZ( index ));
var tweenC = TweenLite.to( currentC, 1.0, {
r: randColor.r,
g: randColor.g,
b: randColor.b,
immediateRender: true,
lazy: false,
onUpdate: function() {
linesMesh.geometry.attributes.color.setXYZ( index, currentC.r, currentC.g, currentC.b );
linesMesh.geometry.attributes.color.needsUpdate = true;
},
onUpdateParams: [ index, currentC ]
});
// Change coordinates
var currentXY = { x: originalPositions.getX( index ), y: originalPositions.getY( index ), z: originalPositions.getZ( index ) };
var goal = [ ( currentXY.x + ( Math.random() * 50 ) * ( ( Math.random() < 0.5 ) ? 1 : -1 ) ), ( currentXY.y + ( Math.random() * 50 ) * ( ( Math.random() < 0.5 ) ? 1 : -1 ) ), ( currentXY.z + ( Math.random() * 50 ) * ( ( Math.random() < 0.5 ) ? 1 : -1 ) ) ];
var tweenXY = TweenLite.to( currentXY, 1.0, {
x: goal[ 0 ],
y: goal[ 1 ],
z: goal[ 2 ],
immediateRender: true,
lazy: false,
onUpdate: function() {
linesMesh.geometry.attributes.position.setXYZ( index, currentXY.x, currentXY.y, currentXY.z );
linesMesh.geometry.attributes.position.needsUpdate = true;
},
onUpdateParams: [ index, currentXY ]
});
}
function render() {
// update the picking ray with the camera and mouse position
raycaster.setFromCamera( mouse, camera );
intersects = raycaster.intersectObject( linesMesh );
if( intersects.length ) {
if( !intersected.includes( intersects[ 0 ].index ) ) {
for( var x = 0; x < intersects.length; x++ ) {
if( !intersected.includes( intersects[ x ].index ) ) {
var index = intersects[ x ].index;
// Save index
intersected.push( index );
alterate( index );
}
}
var present = 0;
for( var y = 0; y < intersected.length; y++ ) {
for( var j = 0; j < intersects.length; j++ ) {
if( intersects[ j ].index == intersected[ y ] ) { present = 1; break; }
}
if( !present ) {
( function( y ) {
var randC = Math.min( Math.max( ( Math.random() / ( Math.random() * 20.0 ) ), 0.01 ), 0.5 );
// Current item old coordinates
let index = intersected[ y ];
var indexX = originalPositions.getX( index );
var indexY = originalPositions.getY( index );
var indexZ = originalPositions.getZ( index );
removeX( y );
// Reset color
var currentCreset = new THREE.Color( linesMesh.geometry.attributes.color.getX( index ), linesMesh.geometry.attributes.color.getY( index ), linesMesh.geometry.attributes.color.getZ( index ));
var tweenCreset = TweenLite.to( currentCreset, 1.5, {
r: randC,
g: randC,
b: randC,
immediateRender: true,
lazy: false,
onUpdate: function() {
linesMesh.geometry.attributes.color.setXYZ( index, currentCreset.r, currentCreset.g, currentCreset.b );
linesMesh.geometry.attributes.color.needsUpdate = true;
},
onUpdateParams: [ index, currentCreset ],
onComplete: function() { }
} );
// Reset coordinates
var currentXYreset = { x: linesMesh.geometry.attributes.position.getX( index ), y: linesMesh.geometry.attributes.position.getY( index ), z: linesMesh.geometry.attributes.position.getZ( index ) };
var tweenXYreset = TweenLite.to( currentXYreset, 1.5, {
x: indexX,
y: indexY,
z: indexZ,
immediateRender: true,
lazy: false,
onUpdate: function() {
linesMesh.geometry.attributes.position.setXYZ( index, currentXYreset.x, currentXYreset.y, currentXYreset.z );
linesMesh.geometry.attributes.position.needsUpdate = true;
},
onUpdateParams: [ index, currentXYreset ]
});
}) ( y );
}
}
}
}
renderer.render( scene, camera );
}
function onMouseMove( event ) {
// calculate mouse position in normalized device coordinates
// (-1 to +1) for both components
event.preventDefault();
mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
var normX = event.clientX - ( window.innerWidth / 2 );
var normY = event.clientY - ( window.innerHeight / 2 );
camera.position.set( 0 + ( normY / 50 ), 0 + ( normX / 50 ), 200 );
camera.lookAt( 0 + ( normY / 50 ), 0 + ( normX / 50 ), 0 );
}
window.addEventListener( 'click', function() {
if( intersected.length ) {
for( var x = 0; x < intersected.length; x++ ) {
var index = intersected[ x ];
alterate( index );
}
}
});
window.addEventListener( 'mousemove', onMouseMove, false );
// Set new dimensions for scene when resize window
window.addEventListener( 'resize', function() {
var wWidth = window.innerWidth;
var wHeight = window.innerHeight;
/*camera.left = wWidth / -2;
camera.right = wWidth / 2;
camera.top = wHeight / 2;
camera.bottom = wHeight / -2;*/
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
raycaster.setFromCamera( mouse, camera );
renderer.setSize( window.innerWidth, window.innerHeight );
} );
Here is a Codepen link: Example

Related

Canvas Impulse Animation Effect

I'm studying the following canvas animation by Matei Copot.
Can someone explain how the "impulse"/shooting effect works, and how, say I can simplify the code to only have 3 stationary dots a, b, and c (while showing the impulse effect between a-> b and between b -> c)?
var w = c.width = window.innerWidth,
h = c.height = window.innerHeight,
ctx = c.getContext( '2d' ),
opts = {
range: 180,
baseConnections: 3,
addedConnections: 5,
baseSize: 5,
minSize: 1,
dataToConnectionSize: .4,
sizeMultiplier: .7,
allowedDist: 40,
baseDist: 40,
addedDist: 30,
connectionAttempts: 100,
dataToConnections: 1,
baseSpeed: .04,
addedSpeed: .05,
baseGlowSpeed: .4,
addedGlowSpeed: .4,
rotVelX: .003,
rotVelY: .002,
repaintColor: '#111',
connectionColor: 'hsla(200,60%,light%,alp)',
rootColor: 'hsla(0,60%,light%,alp)',
endColor: 'hsla(160,20%,light%,alp)',
dataColor: 'hsla(40,80%,light%,alp)',
wireframeWidth: .1,
wireframeColor: '#88f',
depth: 250,
focalLength: 250,
vanishPoint: {
x: w / 2,
y: h / 2
}
},
squareRange = opts.range * opts.range,
squareAllowed = opts.allowedDist * opts.allowedDist,
mostDistant = opts.depth + opts.range,
sinX = sinY = 0,
cosX = cosY = 0,
connections = [],
toDevelop = [],
data = [],
all = [],
tick = 0,
totalProb = 0,
animating = false,
Tau = Math.PI * 2;
ctx.fillStyle = '#222';
ctx.fillRect( 0, 0, w, h );
ctx.fillStyle = '#ccc';
ctx.font = '50px Verdana';
ctx.fillText( 'Calculating Nodes', w / 2 - ctx.measureText( 'Calculating Nodes' ).width / 2, h / 2 - 15 );
window.setTimeout( init, 4 ); // to render the loading screen
function init(){
connections.length = 0;
data.length = 0;
all.length = 0;
toDevelop.length = 0;
var connection = new Connection( 0, 0, 0, opts.baseSize );
connection.step = Connection.rootStep;
connections.push( connection );
all.push( connection );
connection.link();
while( toDevelop.length > 0 ){
toDevelop[ 0 ].link();
toDevelop.shift();
}
if( !animating ){
animating = true;
anim();
}
}
function Connection( x, y, z, size ){
this.x = x;
this.y = y;
this.z = z;
this.size = size;
this.screen = {};
this.links = [];
this.probabilities = [];
this.isEnd = false;
this.glowSpeed = opts.baseGlowSpeed + opts.addedGlowSpeed * Math.random();
}
Connection.prototype.link = function(){
if( this.size < opts.minSize )
return this.isEnd = true;
var links = [],
connectionsNum = opts.baseConnections + Math.random() * opts.addedConnections |0,
attempt = opts.connectionAttempts,
alpha, beta, len,
cosA, sinA, cosB, sinB,
pos = {},
passedExisting, passedBuffered;
while( links.length < connectionsNum && --attempt > 0 ){
alpha = Math.random() * Math.PI;
beta = Math.random() * Tau;
len = opts.baseDist + opts.addedDist * Math.random();
cosA = Math.cos( alpha );
sinA = Math.sin( alpha );
cosB = Math.cos( beta );
sinB = Math.sin( beta );
pos.x = this.x + len * cosA * sinB;
pos.y = this.y + len * sinA * sinB;
pos.z = this.z + len * cosB;
if( pos.x*pos.x + pos.y*pos.y + pos.z*pos.z < squareRange ){
passedExisting = true;
passedBuffered = true;
for( var i = 0; i < connections.length; ++i )
if( squareDist( pos, connections[ i ] ) < squareAllowed )
passedExisting = false;
if( passedExisting )
for( var i = 0; i < links.length; ++i )
if( squareDist( pos, links[ i ] ) < squareAllowed )
passedBuffered = false;
if( passedExisting && passedBuffered )
links.push( { x: pos.x, y: pos.y, z: pos.z } );
}
}
if( links.length === 0 )
this.isEnd = true;
else {
for( var i = 0; i < links.length; ++i ){
var pos = links[ i ],
connection = new Connection( pos.x, pos.y, pos.z, this.size * opts.sizeMultiplier );
this.links[ i ] = connection;
all.push( connection );
connections.push( connection );
}
for( var i = 0; i < this.links.length; ++i )
toDevelop.push( this.links[ i ] );
}
}
Connection.prototype.step = function(){
this.setScreen();
this.screen.color = ( this.isEnd ? opts.endColor : opts.connectionColor ).replace( 'light', 30 + ( ( tick * this.glowSpeed ) % 30 ) ).replace( 'alp', .2 + ( 1 - this.screen.z / mostDistant ) * .8 );
for( var i = 0; i < this.links.length; ++i ){
ctx.moveTo( this.screen.x, this.screen.y );
ctx.lineTo( this.links[ i ].screen.x, this.links[ i ].screen.y );
}
}
Connection.rootStep = function(){
this.setScreen();
this.screen.color = opts.rootColor.replace( 'light', 30 + ( ( tick * this.glowSpeed ) % 30 ) ).replace( 'alp', ( 1 - this.screen.z / mostDistant ) * .8 );
for( var i = 0; i < this.links.length; ++i ){
ctx.moveTo( this.screen.x, this.screen.y );
ctx.lineTo( this.links[ i ].screen.x, this.links[ i ].screen.y );
}
}
Connection.prototype.draw = function(){
ctx.fillStyle = this.screen.color;
ctx.beginPath();
ctx.arc( this.screen.x, this.screen.y, this.screen.scale * this.size, 0, Tau );
ctx.fill();
}
function Data( connection ){
this.glowSpeed = opts.baseGlowSpeed + opts.addedGlowSpeed * Math.random();
this.speed = opts.baseSpeed + opts.addedSpeed * Math.random();
this.screen = {};
this.setConnection( connection );
}
Data.prototype.reset = function(){
this.setConnection( connections[ 0 ] );
this.ended = 2;
}
Data.prototype.step = function(){
this.proportion += this.speed;
if( this.proportion < 1 ){
this.x = this.ox + this.dx * this.proportion;
this.y = this.oy + this.dy * this.proportion;
this.z = this.oz + this.dz * this.proportion;
this.size = ( this.os + this.ds * this.proportion ) * opts.dataToConnectionSize;
} else
this.setConnection( this.nextConnection );
this.screen.lastX = this.screen.x;
this.screen.lastY = this.screen.y;
this.setScreen();
this.screen.color = opts.dataColor.replace( 'light', 40 + ( ( tick * this.glowSpeed ) % 50 ) ).replace( 'alp', .2 + ( 1 - this.screen.z / mostDistant ) * .6 );
}
Data.prototype.draw = function(){
if( this.ended )
return --this.ended; // not sre why the thing lasts 2 frames, but it does
ctx.beginPath();
ctx.strokeStyle = this.screen.color;
ctx.lineWidth = this.size * this.screen.scale;
ctx.moveTo( this.screen.lastX, this.screen.lastY );
ctx.lineTo( this.screen.x, this.screen.y );
ctx.stroke();
}
Data.prototype.setConnection = function( connection ){
if( connection.isEnd )
this.reset();
else {
this.connection = connection;
this.nextConnection = connection.links[ connection.links.length * Math.random() |0 ];
this.ox = connection.x; // original coordinates
this.oy = connection.y;
this.oz = connection.z;
this.os = connection.size; // base size
this.nx = this.nextConnection.x; // new
this.ny = this.nextConnection.y;
this.nz = this.nextConnection.z;
this.ns = this.nextConnection.size;
this.dx = this.nx - this.ox; // delta
this.dy = this.ny - this.oy;
this.dz = this.nz - this.oz;
this.ds = this.ns - this.os;
this.proportion = 0;
}
}
Connection.prototype.setScreen = Data.prototype.setScreen = function(){
var x = this.x,
y = this.y,
z = this.z;
// apply rotation on X axis
var Y = y;
y = y * cosX - z * sinX;
z = z * cosX + Y * sinX;
// rot on Y
var Z = z;
z = z * cosY - x * sinY;
x = x * cosY + Z * sinY;
this.screen.z = z;
// translate on Z
z += opts.depth;
this.screen.scale = opts.focalLength / z;
this.screen.x = opts.vanishPoint.x + x * this.screen.scale;
this.screen.y = opts.vanishPoint.y + y * this.screen.scale;
}
function squareDist( a, b ){
var x = b.x - a.x,
y = b.y - a.y,
z = b.z - a.z;
return x*x + y*y + z*z;
}
function anim(){
window.requestAnimationFrame( anim );
ctx.globalCompositeOperation = 'source-over';
ctx.fillStyle = opts.repaintColor;
ctx.fillRect( 0, 0, w, h );
++tick;
var rotX = tick * opts.rotVelX,
rotY = tick * opts.rotVelY;
cosX = Math.cos( rotX );
sinX = Math.sin( rotX );
cosY = Math.cos( rotY );
sinY = Math.sin( rotY );
if( data.length < connections.length * opts.dataToConnections ){
var datum = new Data( connections[ 0 ] );
data.push( datum );
all.push( datum );
}
ctx.globalCompositeOperation = 'lighter';
ctx.beginPath();
ctx.lineWidth = opts.wireframeWidth;
ctx.strokeStyle = opts.wireframeColor;
all.map( function( item ){ item.step(); } );
ctx.stroke();
ctx.globalCompositeOperation = 'source-over';
all.sort( function( a, b ){ return b.screen.z - a.screen.z } );
all.map( function( item ){ item.draw(); } );
/*ctx.beginPath();
ctx.strokeStyle = 'red';
ctx.arc( opts.vanishPoint.x, opts.vanishPoint.y, opts.range * opts.focalLength / opts.depth, 0, Tau );
ctx.stroke();*/
}
window.addEventListener( 'resize', function(){
opts.vanishPoint.x = ( w = c.width = window.innerWidth ) / 2;
opts.vanishPoint.y = ( h = c.height = window.innerHeight ) / 2;
ctx.fillRect( 0, 0, w, h );
});
window.addEventListener( 'click', init );
canvas {
position: absolute;
top: 0;
left: 0;
}
<canvas id=c></canvas>
<!--
ALGORITHM:
structure:
- gen( x,y,z ):
- create node at x,y,z // blue
- append some children to list:
- within a certain distance to parent
- outside a certain distance from any node
- within a global distance
- if no children
- don't append any
- set as end node // green-ish
- gen( 0,0,0 ) // red
- while list has items
- gen( position of first item )
- remove first item
impulse behaviour:
- pick( node ):
- if node is end node
- pick( original node )
- else
- pick( random node from node children )
- pick( original node)
-->

threejs coincident vertices texture discontinued

I want to make a animated tube with hemisphere ends, the tubular segments is 200. I have the vertices from the first 60 segments to copy the position of a SphereGeometry's upper half part, and the last 60 segments lower half part.
The segments between the upper and lower hemisphere is all copied to the vertices around the sphere's equatorial.
The geometry looks fine, but the spherical environment mapping texture is discontinued at the sphere's equatorial.
I have my code as below, anyone know how to solve the problem?
var camera, scene, renderer;
init();
animate();
function init() {
camera = new THREE.PerspectiveCamera( 28, window.innerWidth / window.innerHeight, 1, 1000 );
camera.position.z = 50;
scene = new THREE.Scene();
var nMat = new THREE.MeshNormalMaterial({side:THREE.DoubleSide,});
var tube = new DSTube( 200, 30, 10, nMat);
scene.add( tube.mesh );
renderer = new THREE.WebGLRenderer();
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
//
window.addEventListener( 'resize', onWindowResize, false );
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
}
function animate() {
requestAnimationFrame( animate );
renderer.render( scene, camera );
}
body {
margin: 0px;
background-color: #000000;
overflow: hidden;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r78/three.js"></script>
<script>
function DSTube( lengthSeg, radiusSeg, radius, material ) {
//for speed
this.framerate = 60;
//center
this.origin = new THREE.Vector3();
this.head = this.origin;
this.tail = this.origin;
//setup
this.lengthSeg = lengthSeg;
this.radiusSeg = radiusSeg;
this.radius = radius;
this.pathPoints = [];
for(var i=0; i<this.lengthSeg; i++) {
this.pathPoints.push( new THREE.Vector3( 0, 0, 0) );
}
// TubeGeometry(path, tubularSegments, radius, radiusSegments, closed)
this.geometry = new THREE.TubeGeometry( new THREE.CatmullRomCurve3(this.pathPoints), this.lengthSeg, this.radius, this.radiusSeg, false );
this.material = material;
this.mesh = new THREE.Mesh( this.geometry, this.material );
this.verticeCount = this.geometry.vertices.length;
//sphere part
//adjust height segment if needed
this.sphereHeightSegments = 60;
//SphereGeometry(radius, widthSegments, heightSegments, phiStart, phiLength, thetaStart, thetaLength)
this.headSphere = new THREE.SphereGeometry( this.radius, this.radiusSeg, this.sphereHeightSegments );
this.tailSphere = new THREE.SphereGeometry( this.radius, this.radiusSeg, this.sphereHeightSegments );
this.sphereVerticeCount = this.headSphere.vertices.length;
//count for tube hemisphere
this.headHemisphereVerticeCount = ( this.sphereHeightSegments / 2 + 1 ) * this.radiusSeg;
//layer
this.tubeLayerCount = this.lengthSeg + 1;
this.headHemisphereLayerCount = this.tailHemisphereLayerCount = this.sphereHeightSegments / 2 + 1;
this.tailHemisphereStartLayer = this.tubeLayerCount - this.sphereHeightSegments / 2;
this.middleLayerCount = this.tubeLayerCount - this.headHemisphereLayerCount * 2;
this.initGeometry();
// return this.mesh;
}
DSTube.prototype.initGeometry = function() {
this.copyHeadSphere();
this.copyInitialTubePart();
this.copyTailSphere();
}
DSTube.prototype.copyHeadSphere = function() {
//copy head hemisphere vertice
for( var y = 0; y < this.headHemisphereLayerCount; y++ ) {
for( var x = 0; x < this.radiusSeg; x++ ) {
if( x == 0 ) {
var sphereVertex = this.headSphere.vertices[ y * (this.radiusSeg + 1) + x ];
this.geometry.vertices[ y * this.radiusSeg + x ].copy( sphereVertex );
} else {
var sphereVertex = this.headSphere.vertices[ y * (this.radiusSeg + 1) + ( this.radiusSeg - x ) ];
this.geometry.vertices[ y * this.radiusSeg + x ].copy( sphereVertex );
}
}
}
this.geometry.computeBoundingSphere();
this.geometry.computeFaceNormals();
this.geometry.computeVertexNormals();
}
DSTube.prototype.copyInitialTubePart = function() {
//copy head hemisphere vertice
for( var y = this.headHemisphereLayerCount - 1; y < this.tubeLayerCount ; y++ ) {
for( var x = 0; x < this.radiusSeg; x++ ) {
var vertex = this.geometry.vertices[ ( this.headHemisphereLayerCount - 1 ) * this.radiusSeg + x ];
this.geometry.vertices[ y * this.radiusSeg + x ].copy( vertex );
}
}
this.geometry.computeBoundingSphere();
this.geometry.computeFaceNormals();
this.geometry.computeVertexNormals();
}
DSTube.prototype.copyTailSphere = function() {
//copy tail hemisphere vertice
for( var y = this.tailHemisphereStartLayer; y < this.tubeLayerCount; y++ ) {
for( var x = 0; x < this.radiusSeg; x++ ) {
if( x == 0 ) {
var sphereVertex = this.tailSphere.vertices[ ( y - this.middleLayerCount - 1 ) * (this.radiusSeg + 1) + x ];
this.geometry.vertices[ y * this.radiusSeg + x ].copy( sphereVertex );
} else {
var sphereVertex = this.tailSphere.vertices[ ( y - this.middleLayerCount - 1 ) * (this.radiusSeg + 1) + ( this.radiusSeg - x ) ];
this.geometry.vertices[ y * this.radiusSeg + x ].copy( sphereVertex );
}
}
}
this.geometry.computeBoundingSphere();
this.geometry.computeFaceNormals();
this.geometry.computeVertexNormals();
}
</script>

Using HTML over canvas, nothing has worked

I'm trying to get my OVERLAY tag to appear on top of my canvas javascript. I've gone through all the questions on here but nothing has worked!
Please help! Code:
page.html
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="stylesheet" href="style.css">
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div id='container'>
<canvas id='canvas'></canvas>
<script src="test-script.js"></script>
<div id='overlay'>OVERLAY
<br></br>
OVERLAY
<br></br>
OVERLAY
</div>
</div>
</body>
</html>
style.css
#canvas {position: fixed; z-index: -1;}
#overlay {margin-top: -50px; z-index:0; position: relative;}
test-script.js
var ns = ns || {};
(function draw() {
var c;
var ctx;
var trails = [];
document.body.onload = function() {
c = document.getElementById( 'canvas' );
c.width = 2000;
c.height = 2000;
document.body.appendChild( c );
ctx = c.getContext( "2d" );
trails.push( new ns.trailer( [990000, 990000, 990000, 600000, 600000 ]));
// trails.push( new ns.trailer( [ 600000,600000,600000,600000,600000,600000,600000 ] ));
trails.push( new ns.trailer( [ 8000000, 8000000, 8000000, 990000, 990000 ] ));
document.onmousedown = reset;
reset();
setInterval( compute, 0 );
}
function reset() {
ctx.fillStyle = "white";
ctx.fillRect( 0,0,c.width,c.height );
for( var i =0; i < trails.length; i++ ) {
trails[ i ].reset();
}
}
function compute() {
for( var i =0; i < trails.length; i++ ) {
trails[ i ].compute( ctx );
}
}
})();
ns.trailer = function( colors ) {
this.points = [];
this.stroke = new ns.stroke( null, 100, 10, colors[ 0 ] );
this.colorIterator = 10;
this.colors = colors;
}
ns.trailer.prototype = {
reset : function() {
this.points = [];
this.width = document.body.offsetWidth;
this.height = document.body.offsetHeight;
this.radius = Math.max( this.width, this.height );
this.center = new ns.point( this.width / 2, this.height / 2 );
this.a0 = Math.random() * Math.PI * 2;
this.a1 = Math.random() * Math.PI * 2;
this.a2 = Math.random() * Math.PI * 2;
var mul = 1 + Math.random() * 2;
if( Math.random() > .5 ) mul *= 5;
else mul /= 2;
this.s0 = ( Math.random() - .5 ) * mul / 180 * Math.PI;
this.s1 = ( Math.random() - .5 ) * mul / 180 * Math.PI;
this.s2 = ( Math.random() - .5 ) * mul / 180 * Math.PI;
},
compute : function( ctx ) {
with( this ) {
a0 += s0;
a1 += s1;
a2 += s2;
var c = Math.cos( a0 ) * Math.cos( a1 ) * Math.cos( a2 );
var s = Math.sin( a0 ) * Math.sin( a1 ) * Math.sin( a2 );
points.push( new ns.point( center.x + c * radius,
center.y + s * radius ) );
if( points.length > 10 ) points.shift();
stroke.anchors = points;
stroke.draw( ctx );
var t = .5 + (Math.sin( new Date().getTime() * .001 ) * .5 );
stroke.color = colors[ Math.floor( t * colors.length ) ];
stroke.width = 25 + ( 1 - t ) * 50;
//stroke.strokeCount = 5 + t * 5;
stroke.strokeCount = 5;
}
}
}
ns.point = function( x,y ) {
this.x = x;
this.y = y;
}
ns.point.prototype = {
add : function( p ) {
return new ns.point( this.x + p.x, this.y + p.y );
}.
sub : function( p ) {
return new ns.point( this.x - p.x, this.y - p.y );
},
negate : function() {
this.x *= -1;
this.y *= -1;
return this;
},
clone : function() {
return new ns.point( this.x, this.y );
},
length : function() {
return Math.sqrt( this.x * this.x + this.y * this.y );
},
normalize : function ( scale ) {
scale = scale || 1;
var l = this.length();
this.x /= l;
this.x *= scale;
this.y /= l;
this.y *= scale;
return this;
}
}
ns.stroke = function( anchors, width, strokeCount, color ) {
this.anchors = anchors;
this.width = width;
this.strokeCount = strokeCount;
this.color = color;
}
ns.stroke.prototype = {
normal : function( p0, p1 ){
return new ns.point( -( p1.y - p0.y ), ( p1.x - p0.x ) );
},
draw : function( ctx ) {
if( this.anchors == undefined ) return;
var half = this.height * .5;
var p, c, n, pnorm, pln, prn, cnorm, cln, crn;
with( this ) {
for( var j = 0; j < strokeCount; j++ ) {
half = width * .5 * Math.random();
var col = ns.variation( color, 35 );
ctx.lineWidth = .1 + Math.random() * 2;
for( var i = 0; i < anchors.length - 2; i++ ) {
p = anchors[ i ];
c = anchors[ i+1 ];
n = anchors[ i+2 ];
pnorm = normal( p, c );
cnorm = normal( c, n );
half += ( Math.random() - .5 );
pnorm.normalize( half );
pln = p.add( pnorm );
pnorm.normalize( -half );
prn = p.add( pnorm );
half += ( Math.random() - .5 );
cnorm.normalize( half );
cln = c.add( cnorm );
cnorm.normalize( -half );
crn = c.add( cnorm );
ctx.beginPath();
ctx.strokeStyle = col;
ctx.moveTo( prn.x, prn.y );
ctx.lineTo( crn.x, crn.y );
ctx.stroke();
ctx.closePath();
ctx.beginPath();
ctx.strokeStyle = col;
ctx.moveTo( pln.x, pln.y );
ctx.lineTo( cln.x, cln.y );
ctx.stroke();
ctx.closePath();
}
}
}
}
}
ns.variation = function( color, amount ) {
amount = amount || 25;
var r = color >> 16 & 0xFF;
var g = color >> 8 & 0xFF;
var b = color & 0xFF;
r += Math.floor( ( Math.random() - .5 ) * amount );
g += Math.floor( ( Math.random() - .5 ) * amount );
b += Math.floor( ( Math.random() - .5 ) * amount );
r = r > 0xFF ? 0xFF : r < 0 ? 0 : r;
g = g > 0xFF ? 0xFF : g < 0 ? 0 : g;
b = b > 0xFF ? 0xFF : b < 0 ? 0 : b;
return "rgba("+r+','+g+','+b+','+Math.random()+');';
}
**I've added my Javascript code
You need to use absolute position. Also mention width and height to 100%. z-index should be higher to place element over other elements.
#canvas {
position: fixed;
}
#overlay {
z-index: 9;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.3);
}
<div id='container'>
<canvas id='canvas'></canvas>
<div id='overlay'>OVERLAY
<br>OVERLAY
<br>OVERLAY
</div>
</div>
CSS
#container {
position: relative;
}
#overlay {
position:absolute;
top:50px;
left:150px;
z-index:10;
}
Adjust the "top" and "left" amounts to get OVERLAY positioned on top of the canvas.
It was the JavaScript.
Run my code snippet.
var ns = ns || {};
(function draw() {
var c;
var ctx;
var trails = [];
document.body.onload = function() {
c = document.getElementById( 'canvas' );
c.width = 2000;
c.height = 2000;
document.body.appendChild( c );
ctx = c.getContext( "2d" );
trails.push( new ns.trailer( [990000, 990000, 990000, 600000, 600000 ]));
// trails.push( new ns.trailer( [ 600000,600000,600000,600000,600000,600000,600000 ] ));
trails.push( new ns.trailer( [ 8000000, 8000000, 8000000, 990000, 990000 ] ));
document.onmousedown = reset;
reset();
setInterval( compute, 0 );
};
function reset() {
ctx.fillStyle = "white";
ctx.fillRect( 0,0,c.width,c.height );
for( var i =0; i < trails.length; i++ ) {
trails[ i ].reset();
}
}
function compute() {
for( var i =0; i < trails.length; i++ ) {
trails[ i ].compute( ctx );
}
}
})();
ns.trailer = function( colors ) {
this.points = [];
this.stroke = new ns.stroke( null, 100, 10, colors[ 0 ] );
this.colorIterator = 10;
this.colors = colors;
};
ns.trailer.prototype = {
reset : function() {
this.points = [];
this.width = document.body.offsetWidth;
this.height = document.body.offsetHeight;
this.radius = Math.max( this.width, this.height );
this.center = new ns.point( this.width / 2, this.height / 2 );
this.a0 = Math.random() * Math.PI * 2;
this.a1 = Math.random() * Math.PI * 2;
this.a2 = Math.random() * Math.PI * 2;
var mul = 1 + Math.random() * 2;
if( Math.random() > .5 ) mul *= 5;
else mul /= 2;
this.s0 = ( Math.random() - .5 ) * mul / 180 * Math.PI;
this.s1 = ( Math.random() - .5 ) * mul / 180 * Math.PI;
this.s2 = ( Math.random() - .5 ) * mul / 180 * Math.PI;
},
compute : function( ctx ) {
with( this ) {
a0 += s0;
a1 += s1;
a2 += s2;
var c = Math.cos( a0 ) * Math.cos( a1 ) * Math.cos( a2 );
var s = Math.sin( a0 ) * Math.sin( a1 ) * Math.sin( a2 );
points.push( new ns.point( center.x + c * radius,
center.y + s * radius ) );
if( points.length > 10 ) points.shift();
stroke.anchors = points;
stroke.draw( ctx );
var t = .5 + (Math.sin( new Date().getTime() * .001 ) * .5 );
stroke.color = colors[ Math.floor( t * colors.length ) ];
stroke.width = 25 + ( 1 - t ) * 50;
//stroke.strokeCount = 5 + t * 5;
stroke.strokeCount = 5;
}
}
};
ns.point = function( x,y ) {
this.x = x;
this.y = y;
};
ns.point.prototype = {
add : function( p ) {
return new ns.point( this.x + p.x, this.y + p.y );
},
sub : function( p ) {
return new ns.point( this.x - p.x, this.y - p.y );
},
negate : function() {
this.x *= -1;
this.y *= -1;
return this;
},
clone : function() {
return new ns.point( this.x, this.y );
},
length : function() {
return Math.sqrt( this.x * this.x + this.y * this.y );
},
normalize : function ( scale ) {
scale = scale || 1;
var l = this.length();
this.x /= l;
this.x *= scale;
this.y /= l;
this.y *= scale;
return this;
}
};
ns.stroke = function( anchors, width, strokeCount, color ) {
this.anchors = anchors;
this.width = width;
this.strokeCount = strokeCount;
this.color = color;
};
ns.stroke.prototype = {
normal : function( p0, p1 ){
return new ns.point( -( p1.y - p0.y ), ( p1.x - p0.x ) );
},
draw : function( ctx ) {
if( this.anchors === undefined ) return;
var half = this.height * .5;
var p, c, n, pnorm, pln, prn, cnorm, cln, crn;
with( this ) {
for( var j = 0; j < strokeCount; j++ ) {
half = width * .5 * Math.random();
var col = ns.variation( color, 35 );
ctx.lineWidth = .1 + Math.random() * 2;
for( var i = 0; i < anchors.length - 2; i++ ) {
p = anchors[ i ];
c = anchors[ i+1 ];
n = anchors[ i+2 ];
pnorm = normal( p, c );
cnorm = normal( c, n );
half += ( Math.random() - .5 );
pnorm.normalize( half );
pln = p.add( pnorm );
pnorm.normalize( -half );
prn = p.add( pnorm );
half += ( Math.random() - .5 );
cnorm.normalize( half );
cln = c.add( cnorm );
cnorm.normalize( -half );
crn = c.add( cnorm );
ctx.beginPath();
ctx.strokeStyle = col;
ctx.moveTo( prn.x, prn.y );
ctx.lineTo( crn.x, crn.y );
ctx.stroke();
ctx.closePath();
ctx.beginPath();
ctx.strokeStyle = col;
ctx.moveTo( pln.x, pln.y );
ctx.lineTo( cln.x, cln.y );
ctx.stroke();
ctx.closePath();
}
}
}
}
};
ns.variation = function( color, amount ) {
amount = amount || 25;
var r = color && 16 && 0xFF;
var g = color && 8 && 0xFF;
var b = color && 0xFF;
r += Math.floor( ( Math.random() - .5 ) * amount );
g += Math.floor( ( Math.random() - .5 ) * amount );
b += Math.floor( ( Math.random() - .5 ) * amount );
r = r > 0xFF ? 0xFF : r < 0 ? 0 : r;
g = g > 0xFF ? 0xFF : g < 0 ? 0 : g;
b = b > 0xFF ? 0xFF : b < 0 ? 0 : b;
return "rgba("+r+','+g+','+b+','+Math.random()+');';
};
#container {
position: relative;
}
#overlay {
position:absolute;
top:50px;
left:150px;
z-index:10;
}
<body>
<div id='container'>
<div id='overlay'>
<h1>
OVERLAY
</h1>
</div>
<canvas id='canvas'>
</canvas>
</div>
<!-- scripts -->
<script type="text/javascript" src="test-script.js"></script>
</body>

Altering color of individual sprite in three.js

I just started playing around with three.js and I'm having some trouble modifying the color of individual sprites within an array.
I'm working with the example found here, from threejs.org.
I'm attempting to modify each sprite's color based on its scale value. In particular, I added the following line to the last function, from the source file.
function render() {
camera.position.x += ( mouseX - camera.position.x ) * .05;
camera.position.y += ( - mouseY - camera.position.y ) * .05;
camera.lookAt( scene.position );
var i = 0;
for ( var ix = 0; ix < AMOUNTX; ix ++ ) {
for ( var iy = 0; iy < AMOUNTY; iy ++ ) {
particle = particles[ i++ ];
particle.position.y = ( Math.sin( ( ix + count ) * 0.3 ) * 50 ) +
( Math.sin( ( iy + count ) * 0.5 ) * 50 );
particle.scale.x = particle.scale.y = ( Math.sin( ( ix + count ) * 0.3 ) + 1 ) * 4 +
( Math.sin( ( iy + count ) * 0.5 ) + 1 ) * 4;
// Added this line in an attempt to change color based on scale //
particle.material.color.setHSL(particle.scale.x * .1, .2, .2);
}
}
renderer.render( scene, camera );
count += 0.1;
}
However, the added line changes every particle to the same color values. I'd assumed that each element of the array would be accessed and modified within the loop, but it doesn't seem to be the case.
The material is being shared with all the particles,
var material = new THREE.SpriteCanvasMaterial( {
color: 0xffffff,
program: function ( context ) {
context.beginPath();
context.arc( 0, 0, 0.5, 0, PI2, true );
context.fill();
}
} );
Move this above the loop where the sprites particles are created so they get a separate material each.
The answer about each sprite having its own material sent me in the right direction. I set material to an array and added an association between each material and sprite element.
var i = 0;
var j = 0;
for ( var ix = 0; ix < AMOUNTX; ix ++ ) {
for ( var iy = 0; iy < AMOUNTY; iy ++ ) {
material[ j ] = new THREE.SpriteCanvasMaterial( {
vertexColors: THREE.VertexColors,
program: function ( context ) {
context.beginPath();
context.arc( 0, 0, 0.5, 0, PI2, true );
context.fill();
}
} );
particle = particles[ i ++ ] = new THREE.Sprite( material[ j ++ ] );
particle.position.x = ix * SEPARATION - ( ( AMOUNTX * SEPARATION ) / 2 );
particle.position.z = iy * SEPARATION - ( ( AMOUNTY * SEPARATION ) / 2 );
scene.add( particle );
}
}

(Three.js) Custom Mesh UV Display Texture Properly

I have created a custom Geometry function for Three.js, based on the Plane Geometry.
Everything works fine, except that I don't know how to make the UV display correctly.
I use 4 triangles per Square, while the THREE.PlaneGeometry is only using 2 triangles per square.
The code for PlaneGeometry UV's looks like this:
var uva = new THREE.Vector2( ix / gridX, 1 - iz / gridZ );
var uvb = new THREE.Vector2( ix / gridX, 1 - ( iz + 1 ) / gridZ );
var uvc = new THREE.Vector2( ( ix + 1 ) / gridX, 1 - ( iz + 1 ) / gridZ );
var uvd = new THREE.Vector2( ( ix + 1 ) / gridX, 1 - iz / gridZ );
And my Code in the Source Code:
var uva = new THREE.Vector2( a );
var uvb = new THREE.Vector2( b );
var uvc = new THREE.Vector2( c );
var uvd = new THREE.Vector2( d );
var uve = new THREE.Vector2( e );
Obviously that is wrong. But I have tried to use the PlaneGeometry code, and I get odd distortions, and I am not sure how to calculate the proper positions.
This:
var uva = new THREE.Vector2( ix / gridX, 1 - iz / gridZ );
var uvb = new THREE.Vector2( ix / gridX, 1 - ( iz + 1 ) / gridZ );
var uvc = new THREE.Vector2( ( ix + 1 ) / gridX, 1 - ( iz + 1 ) / gridZ );
var uvd = new THREE.Vector2( (( ix ) / gridX) + gridX, 1 - iz / gridZ );
var uve = new THREE.Vector2( ( ix + 1 ) / gridX, 1 - iz / gridZ );
Gives me this: BAD UV
I got some sort of answer in the THREE.JS ChatRoom, but I did not understand it, and the person did not elaborate...
(Q) So.. are the Vector2 for UV's not supposed to be the same position as the verticies ?
(A) no an UV is a vector that maps into a texture if you
have a 512x512 texture, and an UV with [0.25, 0.75], it would map to
the pixel in the texture at 256, 768 each vertex has an uv
this means that this vertex maps into the texture like explained above
this is done for each face of a vertex, and all fragments in the
face are then interpolated using those three uvs
So that did not remove my confusion.. I did not understand the [0.25, 0.75] part. Or that every vertex has a texture. How can a point have a texture.
Could someone point me in the right direction please ?
All I need is to know how the UV is positioned. But an example would be great.
Here is the source if you would like to take a look:
THREE.DiamondGeometry = function ( width, height, widthSegments, heightSegments ) {
THREE.Geometry.call( this );
this.width = width;
this.height = height;
this.widthSegments = widthSegments || 1;
this.heightSegments = heightSegments || 2;
var long_row = this.widthSegments + 1;
var short_row = this.widthSegments;
// First Row is the long_row, the ternary statement will toggle this.
var current_row = short_row;
var gridY = 0;
var vX = width / 2, vY = height / 2;
var ix, iz;
var width_half = width / 2;
var height_half = height / 2;
var gridX = this.widthSegments;
var gridZ = this.heightSegments;
var gridX1 = gridX + 1;
var gridZ1 = gridZ + ( gridZ - 2) + 1;
var segment_width = this.width / gridX;
var segment_height = this.height / gridZ;
var normal = new THREE.Vector3( 0, 0, 1 );
// Height Segments Verticies
for ( iz = 0; iz < (gridZ1 + 1) * 2; iz ++ ) {
// Ternary Operator:
current_row === long_row ? (current_row = short_row, vX = width_half - (segment_width / 2) ) : (current_row = long_row, vX = width_half );
// Width Segment Verticies
for ( ix = 0; ix < current_row; ix ++ ) {
var x = ix * segment_width - vX ;
var y = (iz * segment_height - vY) / 2 - (vY / 2);
this.vertices.push( new THREE.Vector3( x, - y, 0 ) );
}
}
for ( iz = 0; iz < gridZ ; iz ++ ) {
for ( ix = 0; ix < gridX; ix ++ ) {
var a = ix + gridX * iz + (iz * gridX1) ;
var b = a + 1;
var c = a + gridX1;
var d = c + gridX;
var e = d + 1;
// THIS IS THE BAD PART THAT I NEED TO CALCULATE THE UV POSITIONS FOR:
var uva = new THREE.Vector2( a );
var uvb = new THREE.Vector2( b );
var uvc = new THREE.Vector2( c );
var uvd = new THREE.Vector2( d );
var uve = new THREE.Vector2( e );
// UP
var face = new THREE.Face3( c, b, a );
face.normal.copy( normal );
face.vertexNormals.push( normal.clone(), normal.clone(), normal.clone() );
this.faces.push( face );
this.faceVertexUvs[ 0 ].push( [ uva, uvb, uvc ] );
// DOWN
face = new THREE.Face3( e, c, d );
face.normal.copy( normal );
face.vertexNormals.push( normal.clone(), normal.clone(), normal.clone() );
this.faces.push( face );
this.faceVertexUvs[ 0 ].push( [ uvd, uvc.clone(), uve ] );
// LEFT
face = new THREE.Face3( d, c, a );
face.normal.copy( normal );
face.vertexNormals.push( normal.clone(), normal.clone(), normal.clone() );
this.faces.push( face );
this.faceVertexUvs[ 0 ].push( [ uva.clone(), uvc.clone(), uvd.clone() ] );
// RIGHT
face = new THREE.Face3( e, b, c );
face.normal.copy( normal );
face.vertexNormals.push( normal.clone(), normal.clone(), normal.clone() );
this.faces.push( face );
this.faceVertexUvs[ 0 ].push( [ uvc.clone(), uvb.clone(), uve.clone() ] );
}
}
this.computeCentroids();
};
THREE.DiamondGeometry.prototype = Object.create( THREE.Geometry.prototype );
see this image:
http://cgkit.sourceforge.net/tutorials/_images/uvmap.png
As noted there, for 1 texture without texture repeat, UV coordinates are in the range from (0,0) to (1,1). If you have a quad-plane and assign the UVs to the vertices as in the image, the whole texture gets displayed on the quad. if you add edge loops/ tesselate your quad for example in 4 smaller quads but still want to have the texture compeltely displayed without repetition, you need to calculate values in between for where the vertices are in UV-space. for example the vertex in the middle (resulting from your tesselation) is now UV (0.5, 0.5)
and the one at the top in the middl is (0, 0.5). See how PlaneGeometry does it and try to learn from it^^

Categories