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>
Related
I have been trying for days to modify the colors in this project and I couldn't
In particulat I'd like to change:
the background color [edit, I was able to do this]
the yellow and white fixed colors for the text in the linked snippet
I tried to do it via the const particle = new THREE.TextureLoader(manager).load linking to another png image but the text disappears even if the link is valid
If there is a working project similar to this please feel free to share it
Thanks!
const preload = () => {
let manager = new THREE.LoadingManager();
manager.onLoad = function() {
const environment = new Environment( typo, particle );
}
var typo = null;
const loader = new THREE.FontLoader( manager );
const font = loader.load('https://res.cloudinary.com/dydre7amr/raw/upload/v1612950355/font_zsd4dr.json', function ( font ) { typo = font; });
const particle = new THREE.TextureLoader( manager ).load( 'https://res.cloudinary.com/dfvtkoboz/image/upload/v1605013866/particle_a64uzf.png');
}
if ( document.readyState === "complete" || (document.readyState !== "loading" && !document.documentElement.doScroll))
preload ();
else
document.addEventListener("DOMContentLoaded", preload );
class Environment {
constructor( font, particle ){
this.font = font;
this.particle = particle;
this.container = document.querySelector( '#wn-magic' );
this.scene = new THREE.Scene();
this.createCamera();
this.createRenderer();
this.setup()
this.bindEvents();
}
bindEvents(){
window.addEventListener( 'resize', this.onWindowResize.bind( this ));
}
setup(){
this.createParticles = new CreateParticles( this.scene, this.font, this.particle, this.camera, this.renderer );
}
render() {
this.createParticles.render()
this.renderer.render( this.scene, this.camera )
}
createCamera() {
this.camera = new THREE.PerspectiveCamera( 65, this.container.clientWidth / this.container.clientHeight, 1, 10000 );
this.camera.position.set( 0,0, 100 );
}
createRenderer() {
this.renderer = new THREE.WebGLRenderer();
this.renderer.setSize( this.container.clientWidth, this.container.clientHeight );
this.renderer.setPixelRatio( Math.min( window.devicePixelRatio, 2));
this.renderer.outputEncoding = THREE.sRGBEncoding;
this.container.appendChild( this.renderer.domElement );
this.renderer.setAnimationLoop(() => { this.render() })
}
onWindowResize(){
this.camera.aspect = this.container.clientWidth / this.container.clientHeight;
this.camera.updateProjectionMatrix();
this.renderer.setSize( this.container.clientWidth, this.container.clientHeight );
}
}
class CreateParticles {
constructor( scene, font, particleImg, camera, renderer ) {
this.scene = scene;
this.font = font;
this.particleImg = particleImg;
this.camera = camera;
this.renderer = renderer;
this.raycaster = new THREE.Raycaster();
this.mouse = new THREE.Vector2(-200, 200);
this.colorChange = new THREE.Color();
this.buttom = false;
this.data = {
text: 'Welcome\n To Rostami\n Creative\n Studio',
amount: 800,
particleSize: 2,
particleColor: 0xeeeeee,
textSize: 16,
area: 250,
ease: .05,
}
this.setup();
this.bindEvents();
}
setup(){
const geometry = new THREE.PlaneGeometry( this.visibleWidthAtZDepth( 100, this.camera ), this.visibleHeightAtZDepth( 100, this.camera ));
const material = new THREE.MeshBasicMaterial( { color: 0x00ff00, transparent: true } );
this.planeArea = new THREE.Mesh( geometry, material );
this.planeArea.visible = false;
this.createText();
}
bindEvents() {
document.addEventListener( 'mousedown', this.onMouseDown.bind( this ));
document.addEventListener( 'mousemove', this.onMouseMove.bind( this ));
document.addEventListener( 'mouseup', this.onMouseUp.bind( this ));
}
onMouseDown(){
this.mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
this.mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
const vector = new THREE.Vector3( this.mouse.x, this.mouse.y, 0.5);
vector.unproject( this.camera );
const dir = vector.sub( this.camera.position ).normalize();
const distance = - this.camera.position.z / dir.z;
this.currenPosition = this.camera.position.clone().add( dir.multiplyScalar( distance ) );
const pos = this.particles.geometry.attributes.position;
this.buttom = true;
this.data.ease = .01;
}
onMouseUp(){
this.buttom = false;
this.data.ease = .05;
}
onMouseMove( ) {
this.mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
this.mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
}
render( level ){
const time = ((.001 * performance.now())%12)/12;
const zigzagTime = (1 + (Math.sin( time * 2 * Math.PI )))/6;
this.raycaster.setFromCamera( this.mouse, this.camera );
const intersects = this.raycaster.intersectObject( this.planeArea );
if ( intersects.length > 0 ) {
const pos = this.particles.geometry.attributes.position;
const copy = this.geometryCopy.attributes.position;
const coulors = this.particles.geometry.attributes.customColor;
const size = this.particles.geometry.attributes.size;
const mx = intersects[ 0 ].point.x;
const my = intersects[ 0 ].point.y;
const mz = intersects[ 0 ].point.z;
for ( var i = 0, l = pos.count; i < l; i++) {
const initX = copy.getX(i);
const initY = copy.getY(i);
const initZ = copy.getZ(i);
let px = pos.getX(i);
let py = pos.getY(i);
let pz = pos.getZ(i);
this.colorChange.setHSL( .5, 1 , 1 )
coulors.setXYZ( i, this.colorChange.r, this.colorChange.g, this.colorChange.b )
coulors.needsUpdate = true;
size.array[ i ] = this.data.particleSize;
size.needsUpdate = true;
let dx = mx - px;
let dy = my - py;
const dz = mz - pz;
const mouseDistance = this.distance( mx, my, px, py )
let d = ( dx = mx - px ) * dx + ( dy = my - py ) * dy;
const f = - this.data.area/d;
if( this.buttom ){
const t = Math.atan2( dy, dx );
px -= f * Math.cos( t );
py -= f * Math.sin( t );
this.colorChange.setHSL( .5 + zigzagTime, 1.0 , .5 )
coulors.setXYZ( i, this.colorChange.r, this.colorChange.g, this.colorChange.b )
coulors.needsUpdate = true;
if ((px > (initX + 70)) || ( px < (initX - 70)) || (py > (initY + 70) || ( py < (initY - 70)))){
this.colorChange.setHSL( .15, 1.0 , .5 )
coulors.setXYZ( i, this.colorChange.r, this.colorChange.g, this.colorChange.b )
coulors.needsUpdate = true;
}
}else{
if( mouseDistance < this.data.area ){
if(i%5==0){
const t = Math.atan2( dy, dx );
px -= .03 * Math.cos( t );
py -= .03 * Math.sin( t );
this.colorChange.setHSL( .15 , 1.0 , .5 )
coulors.setXYZ( i, this.colorChange.r, this.colorChange.g, this.colorChange.b )
coulors.needsUpdate = true;
size.array[ i ] = this.data.particleSize /1.2;
size.needsUpdate = true;
}else{
const t = Math.atan2( dy, dx );
px += f * Math.cos( t );
py += f * Math.sin( t );
pos.setXYZ( i, px, py, pz );
pos.needsUpdate = true;
size.array[ i ] = this.data.particleSize * 1.3 ;
size.needsUpdate = true;
}
if ((px > (initX + 10)) || ( px < (initX - 10)) || (py > (initY + 10) || ( py < (initY - 10)))){
this.colorChange.setHSL( .15, 1.0 , .5 )
coulors.setXYZ( i, this.colorChange.r, this.colorChange.g, this.colorChange.b )
coulors.needsUpdate = true;
size.array[ i ] = this.data.particleSize /1.8;
size.needsUpdate = true;
}
}
}
px += ( initX - px ) * this.data.ease;
py += ( initY - py ) * this.data.ease;
pz += ( initZ - pz ) * this.data.ease;
pos.setXYZ( i, px, py, pz );
pos.needsUpdate = true;
}
}
}
createText(){
let thePoints = [];
let shapes = this.font.generateShapes( this.data.text , this.data.textSize );
let geometry = new THREE.ShapeGeometry( shapes );
geometry.computeBoundingBox();
const xMid = - 0.5 * ( geometry.boundingBox.max.x - geometry.boundingBox.min.x );
const yMid = (geometry.boundingBox.max.y - geometry.boundingBox.min.y)/2.85;
geometry.center();
let holeShapes = [];
for ( let q = 0; q < shapes.length; q ++ ) {
let shape = shapes[ q ];
if ( shape.holes && shape.holes.length > 0 ) {
for ( let j = 0; j < shape.holes.length; j ++ ) {
let hole = shape.holes[ j ];
holeShapes.push( hole );
}
}
}
shapes.push.apply( shapes, holeShapes );
let colors = [];
let sizes = [];
for ( let x = 0; x < shapes.length; x ++ ) {
let shape = shapes[ x ];
const amountPoints = ( shape.type == 'Path') ? this.data.amount/2 : this.data.amount;
let points = shape.getSpacedPoints( amountPoints ) ;
points.forEach( ( element, z ) => {
const a = new THREE.Vector3( element.x, element.y, 0 );
thePoints.push( a );
colors.push( this.colorChange.r, this.colorChange.g, this.colorChange.b);
sizes.push( 1 )
});
}
let geoParticles = new THREE.BufferGeometry().setFromPoints( thePoints );
geoParticles.translate( xMid, yMid, 0 );
geoParticles.setAttribute( 'customColor', new THREE.Float32BufferAttribute( colors, 3 ) );
geoParticles.setAttribute( 'size', new THREE.Float32BufferAttribute( sizes, 1) );
const material = new THREE.ShaderMaterial( {
uniforms: {
color: { value: new THREE.Color( 0xffffff ) },
pointTexture: { value: this.particleImg }
},
vertexShader: document.getElementById( 'vertexshader' ).textContent,
fragmentShader: document.getElementById( 'fragmentshader' ).textContent,
blending: THREE.AdditiveBlending,
depthTest: false,
transparent: true,
} );
this.particles = new THREE.Points( geoParticles, material );
this.scene.add( this.particles );
this.geometryCopy = new THREE.BufferGeometry();
this.geometryCopy.copy( this.particles.geometry );
}
visibleHeightAtZDepth ( depth, camera ) {
const cameraOffset = camera.position.z;
if ( depth < cameraOffset ) depth -= cameraOffset;
else depth += cameraOffset;
const vFOV = camera.fov * Math.PI / 180;
return 2 * Math.tan( vFOV / 2 ) * Math.abs( depth );
}
visibleWidthAtZDepth( depth, camera ) {
const height = this.visibleHeightAtZDepth( depth, camera );
return height * camera.aspect;
}
distance (x1, y1, x2, y2){
return Math.sqrt(Math.pow((x1 - x2), 2) + Math.pow((y1 - y2), 2));
}
}
I gave a look to your code and your comments
how to use setHSL(): quite easy
H values are between 0 and 360, S and L between 0 and 100. You can see this clearly here. The same link will shortcut the colors selection too
On the other side, ThreeJS defines this values on a different pattern, i.e. each of the H,S,L values is between 0.0 and 1.0
So if you wanna go green [120,100,50] you will translate the ThreeJS setHSL() call with values [0.33,1,0.5] where 0.33=120/360
changing the colors in your code
const preload = () => {
let manager = new THREE.LoadingManager();
manager.onLoad = function() {
const environment = new Environment(typo, particle);
}
var typo = null;
const loader = new THREE.FontLoader(manager);
const font = loader.load('https://res.cloudinary.com/dydre7amr/raw/upload/v1612950355/font_zsd4dr.json', function(font) {
typo = font;
});
const particle = new THREE.TextureLoader(manager).load('https://res.cloudinary.com/dfvtkoboz/image/upload/v1605013866/particle_a64uzf.png');
}
if (document.readyState === "complete" || (document.readyState !== "loading" && !document.documentElement.doScroll))
preload();
else
document.addEventListener("DOMContentLoaded", preload);
class Environment {
constructor(font, particle) {
this.font = font;
this.particle = particle;
this.container = document.querySelector('#magic');
this.scene = new THREE.Scene();
this.createCamera();
this.createRenderer();
this.setup()
this.bindEvents();
}
bindEvents() {
window.addEventListener('resize', this.onWindowResize.bind(this));
}
setup() {
this.createParticles = new CreateParticles(this.scene, this.font, this.particle, this.camera, this.renderer);
}
render() {
this.createParticles.render()
this.renderer.render(this.scene, this.camera)
}
createCamera() {
this.camera = new THREE.PerspectiveCamera(65, this.container.clientWidth / this.container.clientHeight, 1, 10000);
this.camera.position.set(0, 0, 100);
}
createRenderer() {
this.renderer = new THREE.WebGLRenderer();
this.renderer.setSize(this.container.clientWidth, this.container.clientHeight);
this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
this.renderer.outputEncoding = THREE.sRGBEncoding;
this.container.appendChild(this.renderer.domElement);
this.renderer.setAnimationLoop(() => {
this.render()
})
}
onWindowResize() {
this.camera.aspect = this.container.clientWidth / this.container.clientHeight;
this.camera.updateProjectionMatrix();
this.renderer.setSize(this.container.clientWidth, this.container.clientHeight);
}
}
class CreateParticles {
constructor(scene, font, particleImg, camera, renderer) {
this.scene = scene;
this.font = font;
this.particleImg = particleImg;
this.camera = camera;
this.renderer = renderer;
this.raycaster = new THREE.Raycaster();
this.mouse = new THREE.Vector2(-200, 200);
this.colorChange = new THREE.Color();
this.buttom = false;
this.data = {
text: 'FUTURE\nIS NOW',
amount: 1500,
particleSize: 1,
particleColor: 0xffffff,
textSize: 16,
area: 250,
ease: .05,
}
this.setup();
this.bindEvents();
}
setup() {
const geometry = new THREE.PlaneGeometry(this.visibleWidthAtZDepth(100, this.camera), this.visibleHeightAtZDepth(100, this.camera));
const material = new THREE.MeshBasicMaterial({
color: 0x00ff00,
transparent: true
});
this.planeArea = new THREE.Mesh(geometry, material);
this.planeArea.visible = false;
this.createText();
}
bindEvents() {
document.addEventListener('mousedown', this.onMouseDown.bind(this));
document.addEventListener('mousemove', this.onMouseMove.bind(this));
document.addEventListener('mouseup', this.onMouseUp.bind(this));
}
onMouseDown() {
this.mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
this.mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
const vector = new THREE.Vector3(this.mouse.x, this.mouse.y, 0.5);
vector.unproject(this.camera);
const dir = vector.sub(this.camera.position).normalize();
const distance = -this.camera.position.z / dir.z;
this.currenPosition = this.camera.position.clone().add(dir.multiplyScalar(distance));
const pos = this.particles.geometry.attributes.position;
this.buttom = true;
this.data.ease = .01;
}
onMouseUp() {
this.buttom = false;
this.data.ease = .05;
}
onMouseMove() {
this.mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
this.mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
}
render(level) {
const time = ((.001 * performance.now()) % 12) / 12;
const zigzagTime = (1 + (Math.sin(time * 2 * Math.PI))) / 6;
this.raycaster.setFromCamera(this.mouse, this.camera);
const intersects = this.raycaster.intersectObject(this.planeArea);
if (intersects.length > 0) {
const pos = this.particles.geometry.attributes.position;
const copy = this.geometryCopy.attributes.position;
const coulors = this.particles.geometry.attributes.customColor;
const size = this.particles.geometry.attributes.size;
const mx = intersects[0].point.x;
const my = intersects[0].point.y;
const mz = intersects[0].point.z;
for (var i = 0, l = pos.count; i < l; i++) {
const initX = copy.getX(i);
const initY = copy.getY(i);
const initZ = copy.getZ(i);
let px = pos.getX(i);
let py = pos.getY(i);
let pz = pos.getZ(i);
// base color when the mouse is distant
// color that will be distorted when the mouse hovers on the text
// e.g. red
this.colorChange.setHSL(0, 1, .50)
coulors.setXYZ(i, this.colorChange.r, this.colorChange.g, this.colorChange.b)
coulors.needsUpdate = true;
size.array[i] = this.data.particleSize;
size.needsUpdate = true;
let dx = mx - px;
let dy = my - py;
const dz = mz - pz;
const mouseDistance = this.distance(mx, my, px, py)
let d = (dx = mx - px) * dx + (dy = my - py) * dy;
const f = -this.data.area / d;
if (this.buttom) {
const t = Math.atan2(dy, dx);
px -= f * Math.cos(t);
py -= f * Math.sin(t);
//onMouseDown color
//e.g. light blue when zigzagTime equals 0 but keep pressing on the text and the color will change because of the time dependency
this.colorChange.setHSL(.5 + zigzagTime, 1.0, .5)
coulors.setXYZ(i, this.colorChange.r, this.colorChange.g, this.colorChange.b)
coulors.needsUpdate = true;
if ((px > (initX + 70)) || (px < (initX - 70)) || (py > (initY + 70) || (py < (initY - 70)))) {
//color of the external particles when mouse is down on the text
//e.g. yellow
this.colorChange.setHSL(.15, 1.0, .5);
coulors.setXYZ(i, this.colorChange.r, this.colorChange.g, this.colorChange.b)
coulors.needsUpdate = true;
}
} else {
if (mouseDistance < this.data.area) {
if (i % 5 == 0) {
const t = Math.atan2(dy, dx);
px -= .03 * Math.cos(t);
py -= .03 * Math.sin(t);
// changing the color around the mouse position
// e.g. green
this.colorChange.setHSL(.33, 1.0, .5)
coulors.setXYZ(i, this.colorChange.r, this.colorChange.g, this.colorChange.b)
coulors.needsUpdate = true;
size.array[i] = this.data.particleSize / 1.2;
size.needsUpdate = true;
} else {
const t = Math.atan2(dy, dx);
px += f * Math.cos(t);
py += f * Math.sin(t);
pos.setXYZ(i, px, py, pz);
pos.needsUpdate = true;
size.array[i] = this.data.particleSize * 1.3;
size.needsUpdate = true;
}
if ((px > (initX + 10)) || (px < (initX - 10)) || (py > (initY + 10) || (py < (initY - 10)))) {
// changing color of the external particles when mouse is down on the text
this.colorChange.setHSL(.15, 1.0, .5)
coulors.setXYZ(i, this.colorChange.r, this.colorChange.g, this.colorChange.b)
coulors.needsUpdate = true;
size.array[i] = this.data.particleSize / 1.8;
size.needsUpdate = true;
}
}
}
px += (initX - px) * this.data.ease;
py += (initY - py) * this.data.ease;
pz += (initZ - pz) * this.data.ease;
pos.setXYZ(i, px, py, pz);
pos.needsUpdate = true;
}
}
}
createText() {
let thePoints = [];
let shapes = this.font.generateShapes(this.data.text, this.data.textSize);
let geometry = new THREE.ShapeGeometry(shapes);
geometry.computeBoundingBox();
const xMid = -0.5 * (geometry.boundingBox.max.x - geometry.boundingBox.min.x);
const yMid = (geometry.boundingBox.max.y - geometry.boundingBox.min.y) / 2.85;
geometry.center();
let holeShapes = [];
for (let q = 0; q < shapes.length; q++) {
let shape = shapes[q];
if (shape.holes && shape.holes.length > 0) {
for (let j = 0; j < shape.holes.length; j++) {
let hole = shape.holes[j];
holeShapes.push(hole);
}
}
}
shapes.push.apply(shapes, holeShapes);
let colors = [];
let sizes = [];
for (let x = 0; x < shapes.length; x++) {
let shape = shapes[x];
const amountPoints = (shape.type == 'Path') ? this.data.amount / 2 : this.data.amount;
let points = shape.getSpacedPoints(amountPoints);
points.forEach((element, z) => {
const a = new THREE.Vector3(element.x, element.y, 0);
thePoints.push(a);
colors.push(this.colorChange.r, this.colorChange.g, this.colorChange.b);
sizes.push(1)
});
}
let geoParticles = new THREE.BufferGeometry().setFromPoints(thePoints);
geoParticles.translate(xMid, yMid, 0);
geoParticles.setAttribute('customColor', new THREE.Float32BufferAttribute(colors, 3));
geoParticles.setAttribute('size', new THREE.Float32BufferAttribute(sizes, 1));
const material = new THREE.ShaderMaterial({
uniforms: {
color: {
value: new THREE.Color(0xffffff)
},
pointTexture: {
value: this.particleImg
}
},
vertexShader: document.getElementById('vertexshader').textContent,
fragmentShader: document.getElementById('fragmentshader').textContent,
blending: THREE.AdditiveBlending,
depthTest: false,
transparent: true,
});
this.particles = new THREE.Points(geoParticles, material);
this.scene.add(this.particles);
this.geometryCopy = new THREE.BufferGeometry();
this.geometryCopy.copy(this.particles.geometry);
}
visibleHeightAtZDepth(depth, camera) {
const cameraOffset = camera.position.z;
if (depth < cameraOffset) depth -= cameraOffset;
else depth += cameraOffset;
const vFOV = camera.fov * Math.PI / 180;
return 2 * Math.tan(vFOV / 2) * Math.abs(depth);
}
visibleWidthAtZDepth(depth, camera) {
const height = this.visibleHeightAtZDepth(depth, camera);
return height * camera.aspect;
}
distance(x1, y1, x2, y2) {
return Math.sqrt(Math.pow((x1 - x2), 2) + Math.pow((y1 - y2), 2));
}
}
This should solve your issue from a theoretical and practical point of view
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)
-->
I am working on three.js to create cloth simulator like hermes' website just difference is I wanted top-down waves instead of horozontal waves that is in hermes.
However I succeed to make vertical waves as I wanted (here is live also added snippet below)
but as you can see top side is not fixed it is also moving slightly I want to make top side Fixed it should not move like hermes website, and want to make this wave continuous instead of just once when webpage loads, also I noticed once wired thing that when I keep open my modified version in browser for 5-10 minutes it shrinks in size (height & width) and gets too smaller after sometime. I don't know why!!
Can any one expert here do some help me for this three things?
make top side fixed like hermes.
Continuous waves.
Get rid of size reducing.
function Particle( x, y, z, mass, drag, clothFunction ) {
this.position = clothFunction( x, y ); // position
this.previous = clothFunction( x, y ); // previous
this.original = clothFunction( x, y );
this.a = new THREE.Vector3( 0, 0, 0 ); // acceleration
this.mass = mass;
this.drag = drag;
this.invMass = 1 / mass;
this.tmp = new THREE.Vector3();
this.tmp2 = new THREE.Vector3();
}
Particle.prototype.addForce = function( force ) {
this.a.add(
this.tmp2.copy( force ).multiplyScalar( this.invMass )
);
};
Particle.prototype.integrate = function( timesq ) {
var newPos = this.tmp.subVectors( this.position, this.previous );
// newPos.multiplyScalar( this.drag ).add( this.position );
newPos.add( this.position );
newPos.add( this.a.multiplyScalar( timesq ) );
this.tmp = this.previous;
this.previous = this.position;
this.position = newPos;
this.a.set( 0, 0, 0 );
};
function Cloth( mass, w, h, restDistance, drag, clothFunction ) {
function index( u, v ) {
return u + v * ( w + 1 );
}
w = w || 10;
h = h || 10;
this.w = w;
this.h = h;
var particles = [];
var constraints = [];
var u, v;
// Create particles
for ( v = 0; v <= h; v ++ ) {
for ( u = 0; u <= w; u ++ ) {
particles.push(
new Particle( u / w, -v / h, 0, mass, drag, clothFunction )
);
}
}
// Structural
for ( v = 0; v < h; v ++ ) {
for ( u = 0; u < w; u ++ ) {
constraints.push( [
particles[ index( u, v ) ],
particles[ index( u, v + 1 ) ],
restDistance
] );
constraints.push( [
particles[ index( u, v ) ],
particles[ index( u + 1, v ) ],
restDistance
] );
}
}
for ( u = w, v = 0; v < h; v ++ ) {
constraints.push( [
particles[ index( u, v ) ],
particles[ index( u, v + 1 ) ],
restDistance
] );
}
for ( v = h, u = 0; u < w; u ++ ) {
constraints.push( [
particles[ index( u, v ) ],
particles[ index( u + 1, v ) ],
restDistance
] );
}
this.particles = particles;
this.constraints = constraints;
this.index = index;
}
function animatedProduct( container, size, canvas, image ) {
this.DAMPING = .02;
this.DRAG = 1 - this.DAMPING
this.MASS = 2000;
this.STIFFNESS = 1;
this.SEGMENTS = 40;
this.canvas = canvas;
this.size = size;
this.demoMode = !0;
this.startTime = Date.now();
this.image = image;
this.restDistance = this.size / this.SEGMENTS;
this.container = container;
this.gravity = new THREE.Vector3( 0, -80, 0 ).multiplyScalar( this.MASS );
this.TIMESTEP_SQ = Math.pow(.01, 2);
this.tmpForce = new THREE.Vector3;
this.diff = new THREE.Vector3;
this.pins = [];
for( var i = 0; i <= this.SEGMENTS; i++ )
this.pins.push( i );
this.degree = 0;
this.wave = 0;
}
animatedProduct.prototype = {
createPlane: function( width, height ) {
return function(c, d) {
var e = ( c - .5 ) * width,
f = ( d + .5 ) * height,
g = 0;
return new THREE.Vector3( e, f, g )
}
},
satisfyConstraints: function( p1, p2, distance ) {
this.diff.subVectors( p2.position, p1.position );
var currentDist = this.diff.length();
if ( currentDist === 0 )
return; // prevents division by 0
this.diff.normalize();
var correction = this.diff.multiplyScalar( currentDist - distance );
var correctionHalf = correction.multiplyScalar( 0.5 );
p1.position.add( correctionHalf );
p2.position.sub( correctionHalf );
},
simulate: function( timestep_sq ) {
var b, c, d, e, f, g, h, i, j = this.clothGeometry.faces;
for (d = this.cloth.particles, b = 0, c = d.length; c > b; b++) {
e = d[b];
e.addForce(this.gravity);
e.integrate(timestep_sq);
}
for (f = this.cloth.constraints, c = f.length, b = 0; c > b; b++) {
g = f[b];
this.satisfyConstraints(g[0], g[1], g[2]);
}
for (d = this.cloth.particles, b = 0, c = d.length; c > b; b++) {
e = d[b];
e.position.x = e.original.x;
}
for (b = 0, c = this.pins.length; c > b; b++) {
var k = this.pins[ b ],
l = d[ k ];
l.position.y = l.original.y;
l.position.x = l.original.x;
l.position.z = l.position.z + this.wave;
}
if( this.degree <= 6 ) {
this.wave = Math.sin( this.degree ) * 6;
this.degree += 0.017 * 42;
}
else
this.wave = 0;
},
init: function() {
this.clothFunction = this.createPlane( this.size, this.size );
this.cloth = new Cloth( this.MASS, this.SEGMENTS, this.SEGMENTS, this.restDistance, this.DRAG, this.createPlane( this.size, this.size ) );
this.scene = new THREE.Scene;
this.camera = new THREE.PerspectiveCamera( 45, this.canvas.width / this.canvas.height, 1, 1e4 );
this.camera.position.y = 0;
this.camera.position.z = 1e3;
this.scene.add( this.camera );
this.light = new THREE.DirectionalLight( 16777215, 1 );
this.light.position.set( 20, -20, 100 );
this.scene.add( this.light );
THREE.ImageUtils.crossOrigin = "";
var texture = THREE.ImageUtils.loadTexture( this.image, {}, function() {
this.canvas.classList.add("play")
}.bind( this ) );
texture.flipY = !1;
texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
texture.anisotropy = 16;
var b = new THREE.MeshPhongMaterial({
ambient: 16777215,
shininess: 20,
map: texture,
side: THREE.DoubleSide
});
this.clothGeometry = new THREE.ParametricGeometry( this.clothFunction, this.cloth.w, this.cloth.h );
this.clothGeometry.dynamic = !0;
this.clothGeometry.computeFaceNormals();
var c = {
texture: {
type: "t",
value: texture
}
},
d = "varying vec2 vUV;void main() {vUV = 0.75 * uv;vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );gl_Position = projectionMatrix * mvPosition;}",
e = "uniform sampler2D texture;varying vec2 vUV;vec4 pack_depth( const in float depth ) {const vec4 bit_shift = vec4( 256.0 * 256.0 * 256.0, 256.0 * 256.0, 256.0, 1.0 );const vec4 bit_mask = vec4( 0.0, 1.0 / 256.0, 1.0 / 256.0, 1.0 / 256.0 );vec4 res = fract( depth * bit_shift );res -= res.xxyz * bit_mask;return res;}void main() {vec4 pixel = texture2D( texture, vUV );if ( pixel.a < 0.5 ) discard;gl_FragData[ 0 ] = pack_depth( gl_FragCoord.z );}";
this.object = new THREE.Mesh( this.clothGeometry, b );
this.object.position.set( 0, 0, 0 );
this.scene.add( this.object );
this.object.customDepthMaterial = new THREE.ShaderMaterial({
uniforms: c,
vertexShader: d,
fragmentShader: e
});
this.renderer = new THREE.WebGLRenderer({
antialias: !0,
canvas: this.canvas
});
this.renderer.setSize( this.canvas.width, this.canvas.height );
this.renderer.setClearColor( 16777215, 1 );
this.renderer.autoClear = !1;
this.renderer.autoClearDepth = !1;
this.container.appendChild( this.renderer.domElement );
this.renderer.gammaInput = !0;
this.renderer.gammaOutput = !0;
this.canvas.addEventListener("mousedown", this.onClick.bind( this ), !1 );
for (var f = 0; 20 > f; f++) this.simulate(this.TIMESTEP_SQ);
this.play();
},
onClick: function(a) {
},
animate: function() {
this.animationFrame = window.requestAnimationFrame(this.animate.bind(this));
this.simulate(this.TIMESTEP_SQ);
this.render();
},
pause: function() {
window.cancelAnimationFrame( this.animationFrame );
},
play: function() {
this.scene ? this.animate() : this.init();
},
render: function() {
for ( var a = this.cloth.particles, b = 0, c = a.length; c > b; b++ )
this.clothGeometry.vertices[ b ].copy( a[ b ].position );
this.clothGeometry.computeFaceNormals();
this.clothGeometry.computeVertexNormals();
this.clothGeometry.normalsNeedUpdate = !0;
this.clothGeometry.verticesNeedUpdate = !0;
this.camera.lookAt( this.scene.position );
this.renderer.clear();
this.renderer.render( this.scene, this.camera );
},
stop: function() {
this.pause();
this.canvas.parentNode.removeChild( this.canvas );
}
};
var size = 700,
container = document.getElementById( "product-container" ),
image = "http://media.hermes.com/media/catalog/product/import/S/S01/S011/item/flat/hd/H001485S-17.jpg",
canvas = document.createElement( "canvas" );
canvas.width = canvas.height = 600 + 20,
canvas.id = "product",
container.appendChild( canvas ),
productAnimation = new animatedProduct( container, size, canvas, image );
productAnimation.play();
<script src="http://maksible.com/cloth/cloth_slower_v2/cloth/three.min.js"></script>
<body>
<div id="product-container"></div>
<script type="text/javascript" src="three.min.js"></script>
<script type="text/javascript" src="logic.js"></script>
</body>
Here is the solution with three.js but with different logic than yours. Hope it might be useful for you.
var size = 500;
var img = 'Image.jpg';
window.onload = function() {
createWGL();
render();
}
// render
//
function render() {
requestAnimationFrame( render );
if(window.mat)
mat.uniforms.time.value = now();
ctx.render( scn, cam );
}
// create renderer
//
function createWGL() {
// check desktop/mobile
window.desk = !(/Android|webOS|iPhone|iPad|BlackBerry|Windows Phone|Opera Mini|IEMobile|Mobile/i.test(navigator.userAgent));
window.ctx = new THREE.WebGLRenderer({antialias:window.desk});
ctx.setClearColor( 0xffffff );
ctx.setPixelRatio( window.devicePixelRatio );
ctx.setSize( size, size );
// camera
window.cam = new THREE.PerspectiveCamera( 90, 1, 1, 30 );
cam.position.z = 25;
// scene
window.scn = new THREE.Scene();
// canvas
window.cvs = createCanvas();
scn.add( cvs );
loadCanvasTexture( img );
// clear viewport
ctx.render( scn, cam );
document.body.appendChild( ctx.domElement );
}
// now
//
function now(){
return performance.now() * 0.001;
}
// load canvas texture
//
function loadCanvasTexture( path ) {
if(window.tex)
window.tex.dispose();
cvs.visible = false;
window.tex = new THREE.TextureLoader().load( path, function(){
cvs.visible = true;
});
window.tex.anisotropy = ctx.getMaxAnisotropy();
window.mat.uniforms.tex.value = window.tex;
}
// create canvas
//
function createCanvas() {
window.mat = new THREE.RawShaderMaterial({
uniforms: {
time: { value: now() },
tex: { value: null }
},
vertexShader: 'precision mediump float;precision mediump int;uniform mat4 modelViewMatrix;'+
'uniform mat4 projectionMatrix;attribute vec2 pos;uniform float time;varying vec2 uv;varying float amb;'+
'float d(float y){return cos(sin(time/2.)+time/2.+y/2.14)*sin(time+y/4.17)*(.5-y/40.)*1.5;}'+
'void main(){vec3 p=vec3( pos.x+sin(time/3.)*(.5-pos.y/40.), pos.y+sin(time)*(.5-pos.y/40.)/2., d(pos.y));amb=(d(pos.y-1.)-d(pos.y+1.))/4.;'+
'uv=vec2(pos.x/40.+.5,pos.y/40.+.5);gl_Position=projectionMatrix*modelViewMatrix*vec4(p,1.);}',
fragmentShader: 'precision mediump float;precision mediump int;uniform sampler2D tex;varying vec2 uv;varying float amb;'+
'void main(){vec4 col=texture2D(tex,uv)+amb;gl_FragColor=vec4(col.xyz,1.);}'
});
var d = 40,d2=~~(d/2),i,j,k,n,fi,v,m,z1=-1,z2;
fi = new Uint16Array( d * d * 6 );
v = new Int8Array( (d+1) * (d+1) * 2 );
for(j=0;j<=d;j++)
for(i=0;i<=d;i++) {
k = i + j*(d+1);
v[k*2] = i - d2;
v[k*2+1] = j - d2;
if(i<d&&j<d) {
n = (i + j*d) * 6;
fi[n] = k;
fi[n+1] = k + 1;
fi[n+2] = k + d + 1;
fi[n+3] = k + d + 1;
fi[n+4] = k + 1;
fi[n+5] = k + d + 2;
}
}
for(i=0,j=-1;i<fi.length;i++)
if(j<fi[i])
j = fi[i];
m = new THREE.Mesh( new THREE.BufferGeometry(), mat );
m.geometry.setIndex( new THREE.BufferAttribute( fi, 1 ));
m.geometry.addAttribute( 'pos', new THREE.BufferAttribute( v, 2 ));
return m;
}
just change your logic code with this and run.
cheers!
As my previous approach doesn't seem to work and a solution would be rather complex, I have decided to try another approach which might be a little bit simpler.
This time, before the code draws any hexagon, it has to determine as how many rows and columns can fit in the pre-defined circle and based on this outcome it then starts drawing all the hexagons.
So far it kind of work, but as in my previous approach, there are times when the hexes are overlapping , or leaving a large gap in the lower part of the circle.
Another concern is , how do I format these hexagons into a grid?
Note, there is a small slider under the canvas, that lets you increase/decrease circle's radius and redraw the hexagons.
var c_el = document.getElementById("myCanvas");
var ctx = c_el.getContext("2d");
var canvas_width = c_el.clientWidth;
var canvas_height = c_el.clientHeight;
var circle = {
r: 120, /// radius
pos: {
x: (canvas_width / 2),
y: (canvas_height / 2)
}
}
var hexagon = {
r: 20,
pos:{
x: 0,
y: 0
}
}
var hex_w = hexagon.r * 2;
var hex_h = Math.floor( Math.sqrt(3) * hexagon.r );
var hex_s = (3/2) * hexagon.r;
fill_CircleWithHex( circle );
function fill_CircleWithHex(circle){
drawCircle( circle );
var c_h = circle.r * 2; /// circle height ////
var c_w = c_h; //// circle width /////
var max_hex_H = Math.round( c_h / hex_h );
var row_sizes = []
for(var row= 0; row< max_hex_H; row++){
var d = circle.r - ( row* hex_h); //// distance from circle's center to the row's chord ////
var c = 2 * (Math.sqrt((circle.r*circle.r) - (d * d))); /// length of the row's chord ////
var row_length = Math.floor(c / (hexagon.r * 3));
row_sizes.push( row_length )
}
console.log("circle_r : "+circle.r);
console.log("hex_r : "+hexagon.r);
console.log("max_hex_H : "+max_hex_H);
console.log("max_hex_W : ", row_sizes)
for(var row = 0; row < row_sizes.length; row++){
var max_hex_W = row_sizes[row];
var x_offset = Math.floor((c_w - (max_hex_W * hex_w)) / 2);
for(var col = 1; col < max_hex_W; col++){
hexagon.pos.x = (col * hex_w) + (circle.pos.x - circle.r) + x_offset ;
hexagon.pos.y = (row * hex_h) + (circle.pos.y - circle.r);
ctx.fillText(row+""+col, hexagon.pos.x - 6, hexagon.pos.y+4);
drawHexagon(hexagon)
}
}
}
function drawHexagon(hex){
var angle_deg, angle_rad, cor_x, cor_y;
ctx.beginPath();
for(var c=0; c <= 5; c++){
angle_deg = 60 * c;
angle_rad = (Math.PI / 180) * angle_deg;
cor_x = hex.r * Math.cos(angle_rad); //// corner_x ///
cor_y = hex.r* Math.sin(angle_rad); //// corner_y ///
if(c === 0){
ctx.moveTo(hex.pos.x+ cor_x, hex.pos.y+cor_y);
}else{
ctx.lineTo(hex.pos.x+cor_x, hex.pos.y+cor_y);
}
}
ctx.closePath();
ctx.stroke();
}
function drawCircle( circle ){
ctx.beginPath();
ctx.arc(circle.pos.x, circle.pos.y, circle.r, 0, 2 * Math.PI);
ctx.stroke();
}
$(function() {
$( "#slider" ).slider({
max: 200,
min:0,
value:100,
create: function( event, ui ) {
$("#value").html( $(this).slider('value') );
},
change: function( event, ui ) {
$("#value").html(ui.value);
},
slide: function( event, ui){
$("#value").html(ui.value);
circle.r = ui.value;
ctx.clearRect(0,0, canvas_width, canvas_height);
fill_CircleWithHex(circle);
}
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.11.4/jquery-ui.min.css" rel="stylesheet"/>
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.4/jquery-ui.min.js"></script>
<canvas id="myCanvas" width="350" height="250" style="border:1px solid #d3d3d3;"> </canvas>
<div style="width: 200px; height: 40px;">
<div id="slider" style="position:relative; width: 150px; top: 4px;float: left;"></div> <div id="value" style="float: left;"> 0 </div>
</div>
The following solves the packing problem for a regular honeycomb structure centered on the circle's midpoint. Regular means:
the set of all hexagons is symmetric under 60 deg rotations around the circle's center.
The coordinates of the individual hexagons represent the ordinal number of the hexagon shell countered from the center and the clockwise sequence number starting at high noon.
As the circle widens, new hexagon shells do not necessarily get filled as a whole. Though the degree of freedom to fill the outer shell partially produces an improved solution, it is still not optimal. Relaxing the regularity to rotational symmetries wrt other angles than 60 deg ( namely 120 and 180 deg ) will permit a higher coverage of the circle's interior.
I shall look into the math behind that for the next revision of this code (and possibly find a theorem to prove rotational symmetry around the circle'smidpoint is a necessary condition for optimality).
var c_el;
var ctx;
var canvas_width;
var canvas_height;
var circle;
var hexagon;
var hex_w;
var hex_h;
var hex_s;
var delta;
function drawHexagonAt ( po_ctr_hex, pn_circle, pn_sector ) {
var cur
;
cur = { x: po_ctr_hex.x - 0.5 * hexagon.r, y: po_ctr_hex.y - delta };
ctx.beginPath();
ctx.moveTo(cur.x, cur.y);
cur.x = cur.x + hexagon.r;
cur.y = cur.y;
ctx.lineTo(cur.x, cur.y);
cur.x = cur.x + hexagon.r / 2;
cur.y = cur.y + delta;
ctx.lineTo(cur.x, cur.y);
cur.x = cur.x - hexagon.r / 2;
cur.y = cur.y + delta;
ctx.lineTo(cur.x, cur.y);
cur.x = cur.x - hexagon.r;
cur.y = cur.y;
ctx.lineTo(cur.x, cur.y);
cur.x = cur.x - hexagon.r / 2;
cur.y = cur.y - delta;
ctx.lineTo(cur.x, cur.y);
cur.x = cur.x + hexagon.r / 2;
cur.y = cur.y - delta;
ctx.lineTo(cur.x, cur.y);
ctx.closePath();
ctx.stroke();
cur.x = cur.x + hexagon.r / 2;
cur.y = cur.y + delta;
ctx.fillText(pn_circle + "/" + pn_sector, cur.x-6, cur.y+4);
} // drawHexagonAt
function fill_CircleWithHex(circle){
drawCircle( circle );
var radacc2;
var iter = 0;
var sector = 0;
var i, j;
var ctr = { x: circle.pos.x , y: circle.pos.y };
var cur = { x: 0 , y: 0 };
delta = Math.floor(Math.sqrt(3) * 0.5 * hexagon.r);
radacc2 = hexagon.r * hexagon.r;
while ( (radacc2 < circle.r * circle.r) ) {
cur.x = ctr.x;
cur.y = ctr.y - iter * 2 * delta;
if (iter === 0) {
drawHexagonAt ( cur, 0, 0 );
}
else {
for ( var i=0; i < 6; i++ ) {
// j-loops -- next honeycomb
sector = 0;
for ( var j=0; j < iter; j++ ) {
cur.x = cur.x + 1.5 * hexagon.r;
cur.y = cur.y + delta;
drawHexagonAt ( cur, iter, sector++ );
}
for ( var j=0; j < iter; j++ ) {
cur.x = cur.x;
cur.y = cur.y + 2 * delta;
drawHexagonAt ( cur, iter, sector++ );
}
for ( var j=0; j < iter; j++ ) {
cur.x = cur.x - 1.5 * hexagon.r;
cur.y = cur.y + delta;
drawHexagonAt ( cur, iter, sector++ );
}
for ( var j=0; j < iter; j++ ) {
cur.x = cur.x - 1.5 * hexagon.r;
cur.y = cur.y - delta;
drawHexagonAt ( cur, iter, sector++ );
}
for ( var j=0; j < iter; j++ ) {
cur.x = cur.x;
cur.y = cur.y - 2 * delta;
drawHexagonAt ( cur, iter, sector++ );
}
for ( var j=0; j < iter; j++ ) {
cur.x = cur.x + 1.5 * hexagon.r;
cur.y = cur.y - delta;
drawHexagonAt ( cur, iter, sector++ );
}
} // i-loop -- meta-honeycomb
} // if -- Iteration 1 vs. n > 1
// radacc update
iter++;
radacc2 = ((2*iter + 1) * delta) * ((2*iter + 1) * delta) + hexagon.r * hexagon.r / 4;
} // while -- komplette Shells
//
// Partielle Shells
//
var proceed;
do {
cur.x = ctr.x;
cur.y = ctr.y - iter * 2 * delta;
proceed = false;
for ( var i=0; i < 6; i++ ) {
// j-loops -- next honeycomb
sector = 0;
for ( var j=0; j < iter; j++ ) {
cur.x = cur.x + 1.5 * hexagon.r;
cur.y = cur.y + delta;
sector++
if ( Math.sqrt ( ( cur.x - ctr.x) * ( cur.x - ctr.x) + ( cur.y - ctr.y) * ( cur.y - ctr.y) ) + hexagon.r < circle.r ) {
drawHexagonAt ( cur, iter, sector );
proceed = true;
}
}
for ( var j=0; j < iter; j++ ) {
cur.x = cur.x;
cur.y = cur.y + 2 * delta;
sector++
if ( Math.sqrt ( ( cur.x - ctr.x) * ( cur.x - ctr.x) + ( cur.y - ctr.y) * ( cur.y - ctr.y) ) + hexagon.r < circle.r ) {
drawHexagonAt ( cur, iter, sector );
proceed = true;
}
}
for ( var j=0; j < iter; j++ ) {
cur.x = cur.x - 1.5 * hexagon.r;
cur.y = cur.y + delta;
sector++
if ( Math.sqrt ( ( cur.x - ctr.x) * ( cur.x - ctr.x) + ( cur.y - ctr.y) * ( cur.y - ctr.y) ) + hexagon.r < circle.r ) {
drawHexagonAt ( cur, iter, sector );
proceed = true;
}
}
for ( var j=0; j < iter; j++ ) {
cur.x = cur.x - 1.5 * hexagon.r;
cur.y = cur.y - delta;
sector++
if ( Math.sqrt ( ( cur.x - ctr.x) * ( cur.x - ctr.x) + ( cur.y - ctr.y) * ( cur.y - ctr.y) ) + hexagon.r < circle.r ) {
drawHexagonAt ( cur, iter, sector );
proceed = true;
}
}
for ( var j=0; j < iter; j++ ) {
cur.x = cur.x;
cur.y = cur.y - 2 * delta;
sector++
if ( Math.sqrt ( ( cur.x - ctr.x) * ( cur.x - ctr.x) + ( cur.y - ctr.y) * ( cur.y - ctr.y) ) + hexagon.r < circle.r ) {
drawHexagonAt ( cur, iter, sector );
proceed = true;
}
}
for ( var j=0; j < iter; j++ ) {
cur.x = cur.x + 1.5 * hexagon.r;
cur.y = cur.y - delta;
sector++
if ( Math.sqrt ( ( cur.x - ctr.x) * ( cur.x - ctr.x) + ( cur.y - ctr.y) * ( cur.y - ctr.y) ) + hexagon.r < circle.r ) {
drawHexagonAt ( cur, iter, sector );
proceed = true;
}
}
} // i-loop -- meta-honeycomb
iter++;
} while (proceed && (iter < 15));
} // fill_CircleWithHex
function drawCircle( circle ){
ctx.beginPath();
ctx.arc(circle.pos.x, circle.pos.y, circle.r, 0, 2 * Math.PI);
ctx.stroke();
}
$(function() {
$( "#slider" ).slider({
max: 200,
min:0,
value:100,
create: function( event, ui ) {
$("#value").html( $(this).slider('value') );
},
change: function( event, ui ) {
$("#value").html(ui.value);
},
slide: function( event, ui){
$("#value").html(ui.value);
circle.r = ui.value;
ctx.clearRect(0,0, canvas_width, canvas_height);
fill_CircleWithHex(circle);
}
});
});
$(document).ready(function () {
c_el = document.getElementById("myCanvas");
ctx = c_el.getContext("2d");
canvas_width = c_el.clientWidth;
canvas_height = c_el.clientHeight;
circle = {
r: 120, /// radius
pos: {
x: (canvas_width / 2),
y: (canvas_height / 2)
}
};
hexagon = {
r: 20,
pos:{
x: 0,
y: 0
}
};
hex_w = hexagon.r * 2;
hex_h = Math.floor( Math.sqrt(3) * hexagon.r );
hex_s = (3/2) * hexagon.r;
fill_CircleWithHex( circle );
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.11.4/jquery-ui.min.css" rel="stylesheet"/>
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.4/jquery-ui.min.js"></script>
<canvas id="myCanvas" width="350" height="250" style="border:1px solid #d3d3d3;"> </canvas>
<div style="width: 200px; height: 40px;">
<div id="slider" style="position:relative; width: 150px; top: 4px;float: left;"></div> <div id="value" style="float: left;"> 0 </div>
</div>
Spent some time on your code to pack the hex's. Its not perfect and am sure there is a better way to do this. Check it out if it helps, or if you could fix the hex's stepping out of the circle [now, there is an issue with calculation of row_sizes]. Maybe I can look at it again whenever I get time, or we can look at other ways to do this.
var c_el = document.getElementById("myCanvas");
var ctx = c_el.getContext("2d");
var canvas_width = c_el.clientWidth;
var canvas_height = c_el.clientHeight;
var circle = {
r: 120, /// radius
pos: {
x: (canvas_width / 2),
y: (canvas_height / 2)
}
}
var hexagon = {
r: 20,
pos:{
x: 0,
y: 0
}
}
var hex_w = hexagon.r * 3; /// added spacing
var hex_h = Math.floor( Math.sqrt(3) * hexagon.r / 2 ); /// added spacing
var hex_s = (3/2) * hexagon.r;
var hex_width = 33.4; //based on r = 20
fill_CircleWithHex( circle );
function fill_CircleWithHex(circle){
drawCircle( circle );
var c_h = circle.r * 2; /// circle height ////
var c_w = c_h; //// circle width /////
var max_hex_H = Math.round( c_h / ( hex_h ));
var row_sizes = []
for(var col= 0; col < max_hex_H; col++){
var d = circle.r - ( col * hex_h ); //// distance from circle's center to the row's chord ////
var c = 2 * (Math.sqrt((circle.r*circle.r) - (d * d))); /// length of the row's chord ////
row_sizes.push( Math.ceil(c / (hexagon.r * 3)) )
}
for(var row = 0; row < row_sizes.length; row++){
var max_hex_W = row_sizes[row];
console.log(hex_w);
var x_offset = Math.floor((c_w - (max_hex_W * hex_w))) + row%2 * hex_width - hex_width/2; // changed offset to define a zig zag
for(var col = 1; col < max_hex_W; col++){
hexagon.pos.x = (col * hex_w) + (circle.pos.x - circle.r) + x_offset ;
hexagon.pos.y = (row * 17.3) + (circle.pos.y - circle.r) ;
ctx.fillText(row+""+col, hexagon.pos.x - 6, hexagon.pos.y+4);
drawHexagon(hexagon)
}
}
}
function drawHexagon(hex){
var angle_deg, angle_rad, cor_x, cor_y;
ctx.beginPath();
for(var c=0; c <= 5; c++){
angle_deg = 60 * c;
angle_rad = (Math.PI / 180) * angle_deg;
cor_x = hex.r * Math.cos(angle_rad); //// corner_x ///
cor_y = hex.r* Math.sin(angle_rad); //// corner_y ///
if(c === 0){
ctx.moveTo(hex.pos.x+ cor_x, hex.pos.y+cor_y);
}else{
ctx.lineTo(hex.pos.x+cor_x, hex.pos.y+cor_y);
}
}
ctx.closePath();
ctx.stroke();
}
function drawCircle( circle ){
ctx.beginPath();
ctx.arc(circle.pos.x, circle.pos.y, circle.r, 0, 2 * Math.PI);
ctx.stroke();
}
$(function() {
$( "#slider" ).slider({
max: 200,
min:0,
value:100,
create: function( event, ui ) {
$("#value").html( $(this).slider('value') );
},
change: function( event, ui ) {
$("#value").html(ui.value);
},
slide: function( event, ui){
$("#value").html(ui.value);
circle.r = ui.value;
ctx.clearRect(0,0, canvas_width, canvas_height);
fill_CircleWithHex(circle);
}
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.11.4/jquery-ui.min.css" rel="stylesheet"/>
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.4/jquery-ui.min.js"></script>
<canvas id="myCanvas" width="350" height="250" style="border:1px solid #d3d3d3;"> </canvas>
<div style="width: 200px; height: 40px;">
<div id="slider" style="position:relative; width: 150px; top: 4px;float: left;"></div> <div id="value" style="float: left;"> 0 </div>
</div>
The short answer: there is no easy way to do this. You have too many special cases. To illustrate that, start simple with 1, 2, 3, 4, 6 and 7 hexagons, draw the minimum circle that will fit over them, and make note where the center of the circle ends up.
As you can see the center of the circle moves around quite a bit. It can end up in the middle of a hexagon, on a vertex or a junction.
From then on it only gets worse.
The closest I could find on this problem is this page.
EDIT: You may want to check out the following blog page for a very comprehensive treatment on hexagons in programming.
http://www.redblobgames.com/grids/hexagons/
I'm searching the web for quite sometime for the name of this effect... I've seen it on many sites but can't find a guide or a name to look for it and learn how to do it.
The effect I'm talking about is in this website header:
http://diogodantas.com/demo/elegant-index#0
check this. i just ctrl + c,v.
(function() {
var width, height, largeHeader, canvas, ctx, points, target, animateHeader = true;
// Main
initHeader();
initAnimation();
addListeners();
function initHeader() {
width = window.innerWidth;
height = window.innerHeight;
target = {x: width/2, y: height/2};
largeHeader = document.getElementById('large-header');
largeHeader.style.height = height+'px';
canvas = document.getElementById('demo-canvas');
canvas.width = width;
canvas.height = height;
ctx = canvas.getContext('2d');
// create points
points = [];
for(var x = 0; x < width; x = x + width/20) {
for(var y = 0; y < height; y = y + height/20) {
var px = x + Math.random()*width/20;
var py = y + Math.random()*height/20;
var p = {x: px, originX: px, y: py, originY: py };
points.push(p);
}
}
// for each point find the 5 closest points
for(var i = 0; i < points.length; i++) {
var closest = [];
var p1 = points[i];
for(var j = 0; j < points.length; j++) {
var p2 = points[j]
if(!(p1 == p2)) {
var placed = false;
for(var k = 0; k < 5; k++) {
if(!placed) {
if(closest[k] == undefined) {
closest[k] = p2;
placed = true;
}
}
}
for(var k = 0; k < 5; k++) {
if(!placed) {
if(getDistance(p1, p2) < getDistance(p1, closest[k])) {
closest[k] = p2;
placed = true;
}
}
}
}
}
p1.closest = closest;
}
// assign a circle to each point
for(var i in points) {
var c = new Circle(points[i], 2+Math.random()*2, 'rgba(255,255,255,0.3)');
points[i].circle = c;
}
}
// Event handling
function addListeners() {
if(!('ontouchstart' in window)) {
window.addEventListener('mousemove', mouseMove);
}
window.addEventListener('scroll', scrollCheck);
window.addEventListener('resize', resize);
}
function mouseMove(e) {
var posx = posy = 0;
if (e.pageX || e.pageY) {
posx = e.pageX;
posy = e.pageY;
}
else if (e.clientX || e.clientY) {
posx = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
posy = e.clientY + document.body.scrollTop + document.documentElement.scrollTop;
}
target.x = posx;
target.y = posy;
}
function scrollCheck() {
if(document.body.scrollTop > height) animateHeader = false;
else animateHeader = true;
}
function resize() {
width = window.innerWidth;
height = window.innerHeight;
largeHeader.style.height = height+'px';
canvas.width = width;
canvas.height = height;
}
// animation
function initAnimation() {
animate();
for(var i in points) {
shiftPoint(points[i]);
}
}
function animate() {
if(animateHeader) {
ctx.clearRect(0,0,width,height);
for(var i in points) {
// detect points in range
if(Math.abs(getDistance(target, points[i])) < 4000) {
points[i].active = 0.3;
points[i].circle.active = 0.6;
} else if(Math.abs(getDistance(target, points[i])) < 20000) {
points[i].active = 0.1;
points[i].circle.active = 0.3;
} else if(Math.abs(getDistance(target, points[i])) < 40000) {
points[i].active = 0.02;
points[i].circle.active = 0.1;
} else {
points[i].active = 0;
points[i].circle.active = 0;
}
drawLines(points[i]);
points[i].circle.draw();
}
}
requestAnimationFrame(animate);
}
function shiftPoint(p) {
TweenLite.to(p, 1+1*Math.random(), {x:p.originX-50+Math.random()*100,
y: p.originY-50+Math.random()*100, ease:Circ.easeInOut,
onComplete: function() {
shiftPoint(p);
}});
}
// Canvas manipulation
function drawLines(p) {
if(!p.active) return;
for(var i in p.closest) {
ctx.beginPath();
ctx.moveTo(p.x, p.y);
ctx.lineTo(p.closest[i].x, p.closest[i].y);
ctx.strokeStyle = 'rgba(156,217,249,'+ p.active+')';
ctx.stroke();
}
}
function Circle(pos,rad,color) {
var _this = this;
// constructor
(function() {
_this.pos = pos || null;
_this.radius = rad || null;
_this.color = color || null;
})();
this.draw = function() {
if(!_this.active) return;
ctx.beginPath();
ctx.arc(_this.pos.x, _this.pos.y, _this.radius, 0, 2 * Math.PI, false);
ctx.fillStyle = 'rgba(156,217,249,'+ _this.active+')';
ctx.fill();
};
}
// Util
function getDistance(p1, p2) {
return Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2);
}
})();
I still cannot find a name but here is a link
https://codepen.io/thetwistedtaste/pen/GgrWLp
/*
*
* TERMS OF USE -
*
* Open source under the BSD License.
*
* Copyright © 2001 Robert Penner
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* Neither the name of the author nor the names of contributors may be used to endorse
* or promote products derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
* GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
// http://paulirish.com/2011/requestanimationframe-for-smart-animating/
// http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating
// requestAnimationFrame polyfill by Erik Möller. fixes from Paul Irish and Tino Zijdel
// MIT license
(function() {
var lastTime = 0;
var vendors = ['ms', 'moz', 'webkit', 'o'];
for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame'];
}
if (!window.requestAnimationFrame)
window.requestAnimationFrame = function(callback, element) {
var currTime = new Date().getTime();
var timeToCall = Math.max(0, 16 - (currTime - lastTime));
var id = window.setTimeout(function() { callback(currTime + timeToCall); },
timeToCall);
lastTime = currTime + timeToCall;
return id;
};
if (!window.cancelAnimationFrame)
window.cancelAnimationFrame = function(id) {
clearTimeout(id);
};
}());
$( function() {
var width, height, canvas, ctx, points, target, animateHeader = true;
// Main
initHeader();
initAnimation();
addListeners();
function initHeader() {
width = window.innerWidth;
height = window.innerHeight;
target = {
x: width / 2,
y: height / 3
};
canvas = document.getElementById( 'spiders' );
canvas.width = width;
canvas.height = height;
ctx = canvas.getContext( '2d' );
// create points
points = [];
for ( var x = 0; x < width; x = x + width / 20 ) {
for ( var y = 0; y < height; y = y + height / 20 ) {
var px = x + Math.random() * width / 20;
var py = y + Math.random() * height / 20;
var p = {
x: px,
originX: px,
y: py,
originY: py
};
points.push( p );
}
}
// for each point find the 5 closest points
for ( var i = 0; i < points.length; i++ ) {
var closest = [];
var p1 = points[ i ];
for ( var j = 0; j < points.length; j++ ) {
var p2 = points[ j ]
if ( !( p1 == p2 ) ) {
var placed = false;
for ( var k = 0; k < 5; k++ ) {
if ( !placed ) {
if ( closest[ k ] == undefined ) {
closest[ k ] = p2;
placed = true;
}
}
}
for ( var k = 0; k < 5; k++ ) {
if ( !placed ) {
if ( getDistance( p1, p2 ) < getDistance( p1, closest[ k ] ) ) {
closest[ k ] = p2;
placed = true;
}
}
}
}
}
p1.closest = closest;
}
// assign a circle to each point
for ( var i in points ) {
var c = new Circle( points[ i ], 2 + Math.random() * 2, 'rgba(255,255,255,0.3)' );
points[ i ].circle = c;
}
}
// Event handling
function addListeners() {
if ( !( 'ontouchstart' in window ) ) {
window.addEventListener( 'mousemove', mouseMove );
}
window.addEventListener( 'scroll', scrollCheck );
window.addEventListener( 'resize', resize );
}
function mouseMove( e ) {
var posx = posy = 0;
if ( e.pageX || e.pageY ) {
posx = e.pageX;
posy = e.pageY;
} else if ( e.clientX || e.clientY ) {
posx = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
posy = e.clientY + document.body.scrollTop + document.documentElement.scrollTop;
}
target.x = posx;
target.y = posy;
}
function scrollCheck() {
if ( document.body.scrollTop > height ) animateHeader = false;
else animateHeader = true;
}
function resize() {
width = window.innerWidth;
height = window.innerHeight;
canvas.width = width;
canvas.height = height;
}
// animation
function initAnimation() {
animate();
for ( var i in points ) {
shiftPoint( points[ i ] );
}
}
function animate() {
if ( animateHeader ) {
ctx.clearRect( 0, 0, width, height );
for ( var i in points ) {
// detect points in range
if ( Math.abs( getDistance( target, points[ i ] ) ) < 2000 ) {
points[ i ].active = 0.2;
points[ i ].circle.active = 0.5;
} else if ( Math.abs( getDistance( target, points[ i ] ) ) < 20000 ) {
points[ i ].active = 0.1;
points[ i ].circle.active = 0.3;
} else if ( Math.abs( getDistance( target, points[ i ] ) ) < 70000 ) {
points[ i ].active = 0.02;
points[ i ].circle.active = 0.09;
} else if ( Math.abs( getDistance( target, points[ i ] ) ) < 140000 ) {
points[ i ].active = 0;
points[ i ].circle.active = 0.02;
} else {
points[ i ].active = 0;
points[ i ].circle.active = 0;
}
drawLines( points[ i ] );
points[ i ].circle.draw();
}
}
requestAnimationFrame( animate );
}
function shiftPoint( p ) {
TweenLite.to( p, 1 + 1 * Math.random(), {
x: p.originX - 50 + Math.random() * 100,
y: p.originY - 50 + Math.random() * 100,
onComplete: function() {
shiftPoint( p );
}
} );
}
// Canvas manipulation
function drawLines( p ) {
if ( !p.active ) return;
for ( var i in p.closest ) {
ctx.beginPath();
ctx.moveTo( p.x, p.y );
ctx.lineTo( p.closest[ i ].x, p.closest[ i ].y );
ctx.strokeStyle = 'rgba(255,255,255,' + p.active + ')';
ctx.stroke();
}
}
function Circle( pos, rad, color ) {
var _this = this;
// constructor
( function() {
_this.pos = pos || null;
_this.radius = rad || null;
_this.color = color || null;
} )();
this.draw = function() {
if ( !_this.active ) return;
ctx.beginPath();
ctx.arc( _this.pos.x, _this.pos.y, _this.radius, 0, 2 * Math.PI, false );
ctx.fillStyle = 'rgba(255,255,255,' + _this.active + ')';
ctx.fill();
};
}
// Util
function getDistance( p1, p2 ) {
return Math.pow( p1.x - p2.x, 2 ) + Math.pow( p1.y - p2.y, 2 );
}
} );
body {}
i {
position: absolute;
top:0; bottom:0;left:0;right:0;
display:block;
background:black;
z-index:-1;
}
canvas#spiders {
height:100vh;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/1.19.0/TweenLite.min.js"></script><script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<i></i>
<canvas id="spiders" class="hidden-xs" width="1280" height="451"></canvas>