Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URLs added here will be added as <link>s in order, and before the CSS in the editor. You can use the CSS from another Pen by using its URL and the proper URL extension.

+ add another resource

JavaScript

Babel includes JSX processing.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Packages

Add Packages

Search for and use JavaScript packages from npm here. By selecting a package, an import statement will be added to the top of the JavaScript editor for this package.

Behavior

Auto Save

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

HTML

              
                <div id="frameCounter"></div>
<div id="stage">
  <canvas id="canvas"></canvas>
  <a id="imageLink" href="" download>
    <img id="image"/>
  </a>
</div>
              
            
!

CSS

              
                body
  background: #333332

#stage 
  position: absolute
  width: min(95vw, 95vh)
  height: min(95vw, 95vh)
  margin: auto
  top: 0
  bottom: 0
  left: 0
  right: 0
  background: #222222
  filter: drop-shadow(0 0 20px rgba(0,0,0,0.5))
  
.particle
  display: none
  position: absolute
  top: 0
  left: 0
  
.particleDisplay
  position: absolute
  width: 100px
  height: 100px
  transform: translate3d(-50px, -50px, 0)
  background: rgba(0,0,0,0.2)
  
.activator
  display: none
  position: absolute
  top: 0
  left: 0
  
.activatorDisplay
  position: absolute
  width: 10px
  height: 10px
  transform: translate(-5px, -5px)
  background: rgba(255,0,0,1)
 
#canvas
  position: absolute
  top: 0
  left: 0
  height: 100% !important
  width: 100% !important
  // filter: invert(100%)

#image
  display: none
  position: absolute
  top: 0
  left: 0
#frameCounter
  color: white
  background: black
  font-family: "Courier New"
  font-size: 15px
  position: absolute
  padding: 3px
  z-index: 10000
              
            
!

JS

              
                import $ from "https://cdn.skypack.dev/jquery@3.6.0";

let TAU = Math.PI * 2;
let HALF_TURN = Math.PI;
let QUARTER_TURN = TAU / 4;

var stageWidth = 1080;
var stageHeight = 1080;
var settings = {
  numFrames: 60 * 8,
  record: false
};

let frameCount = 0;

const $stage = $("#stage");
var $canvas = $("#canvas");
const $image = $("#image");
var cellParticles = [];
var numCellsX = Math.ceil(stageWidth / settings.cellSizeX);
var numCellsY = Math.ceil(stageHeight / settings.cellSizeY);
var rAF;
var ctx;

let taurus = {
  radius: 200,
  tubeMin: 50,
  thicknessMax: 100
}


let borderX = settings.cellSizeX * 2;
let borderY = settings.cellSizeY * 2;

let fullTripX = stageWidth + borderX * 2;
let fullTripY = stageHeight + borderY * 2;

const zip = new JSZip();

let colormapOffsetX = 0;

//----------------------------------------------------------------

$(window).click(() => {
  // if(settings.record == false){
  //   settings.record = true;
  //   frameCount = 0;
  // }
})

//----------------------------------------------------------------

function setup() {
  $canvas[0].width = stageWidth;
  $canvas[0].height = stageHeight;
  
  initializeStage()
  update(0);
  // initial Fill
  // ctx.globalCompositeOperation = "source-over";
  // ctx.fillStyle = `rgba(${0x22},${0x22},${0x22},1)`;
  // ctx.fillRect(0, 0, stageWidth, stageHeight);

  play();
}

//----------------------------------------------------------------
// Animation Player

var startTime = Date.now() - 4000;
var lastFrameTime = Date.now();
var playhead = 0;
var ticksPerSecond = 60;
var millisPerTick = 1000 / ticksPerSecond;
var now = startTime;
var afID;

function play() {
	if (afID) return;
	// console.log(activeParticles);
	lastFrameTime = Date.now();
	loop();
}

function stop() {
	cancelAnimationFrame(afID);
	afID = null;
}

function loop() {
  // console.log("----------- loop() ------------")
	afID = requestAnimationFrame(loop);
	now = Date.now();
	playhead += now - lastFrameTime;
	update(playhead);
	lastFrameTime = now;
  
  draw();
  if (settings.record){
    if (frameCount <= settings.numFrames){
      $("#frameCounter").text( pad(frameCount, 3));
      zip.file(
        `frame_${pad(frameCount, 3)}.png`, 
        dataURItoBlob($canvas[0].toDataURL( 'image/png' )), 
        {base64: true}
      );
    } else if (frameCount == settings.numFrames + 1){
      stopRecording();
      // stop();
    }
  }
  
  frameCount++;
}

// ------------------------------------

var tick = 0;
var prevTick = -1;
const PARTICLE_LIFESPAN = settings.numFrames;
var particlesPerFrame = 10 / PARTICLE_LIFESPAN;
var numColumns = 16

function update(playhead) {
	// tick = playhead / millisPerTick;
	tick++;
	// if (prevTick != tick) {
		// getParticles(particlesPerFrame, {
		// 	birthday: tick
		// });
	// }
	activeParticles.forEach(function(p) {
		updateParticle(p, tick);
	});
  
  // Move Camera
  
	// var cameraTick = Math.PI * 2 * ((tick % 1080) / 1080);
	//camera.rotation.z =  cameraTick
	//camera.position.x = Math.cos(cameraTick) * WIDTH * 0.25
	//camera.position.y = Math.sin(cameraTick) * HEIGHT * 0.25
	//camera.lookAt(center);
	prevTick = tick;
}

//----------------------------------------------------------------
// THREE System

// set the scene size
var WIDTH = stageWidth,
	HEIGHT = stageHeight,
	//center = new THREE.Vector3(WIDTH * 0.5, HEIGHT * 0.5, 0),
	center = new THREE.Vector3(0, 0, 0),
	particleDestination = new THREE.Vector3(0, 0, 800);

let numParticles = 3000;

// set some camera attributes
var VIEW_ANGLE = 100,
	ASPECT =  WIDTH / HEIGHT,
	NEAR = 0.01,
	FAR = 5000;

// create a WebGL renderer, camera
// and a scene
var renderer = new THREE.WebGLRenderer({
	preserveDrawingBuffer: true,
  canvas:  $canvas[0]
});

$canvas = $('#canvas');
$canvas.css({
  width: '100%',
  height: '100%'
})

ctx = renderer.getContext();
renderer.autoClear = true;
var camera =
	new THREE.PerspectiveCamera(
		VIEW_ANGLE,
		ASPECT,
		NEAR,
		FAR);

// var camera = new THREE.OrthographicCamera(
//   -512,
//   512,
//   -512,
//   512,
//   1,
//   2000
// )

var scene = new THREE.Scene();
scene.background = new THREE.Color( 0xffffff )
// add the camera to the scene
scene.add(camera);

// the camera starts at 0,0,0
// so pull it back
camera.position.x = 0;
camera.position.y = 0;
camera.position.z = 512;
camera.lookAt(new THREE.Vector3(0, 0, 0));
// camera.rotation.z += TAU*0.75;

// start the renderer
renderer.setSize(WIDTH, HEIGHT);
renderer.setClearColor(0x222221, 1);

// attach the render-supplied DOM element
// $container.appendChild(renderer.domElement);

// create the sphere's material
var standardMaterial = new THREE.MeshLambertMaterial({
	color: 0xffffff,
	transparent: true,
	blending: THREE.AdditiveBlending,
	opacity: .3,
  // side: THREE.BackSide ,
});

var standardGeometry = new THREE.CylinderGeometry(
	.1, // upper radius
	10, // lower radius
	100, // height
	12 // segments
);
// var translationMatrix = new THREE.Matrix4().makeTranslation( 0, 0, 0 );
// standardGeometry.applyMatrix( translationMatrix );
// standardGeometry.applyMatrix(new THREE.Matrix4().makeRotationY(Math.PI)
//                              // .multiply (new THREE.Matrix4().makeRotationX(Math.PI / 2))
//                             );
standardGeometry.applyMatrix(new THREE.Matrix4().identity()
                             .multiply (new THREE.Matrix4().makeRotationY(Math.PI / 2))
                            );


// create a point light
var pointLight = new THREE.PointLight(0xFFFFFF, 1);

// set its position
pointLight.position.x = camera.position.x;
pointLight.position.y = camera.position.y;
pointLight.position.z = camera.position.z;

// add to the scene
scene.add(pointLight);


// ------------------------------------

// create object pool
var availableParticles = [];
var activeParticles = [];

// ------------------------------------

function getParticles(quantity, options) {
	quantity = quantity || 1;
	while (availableParticles.length < quantity) {
		var p = spawnParticle();
		scene.add(p);
		availableParticles.push(p);
	}
	var newParticles = availableParticles.splice(0, quantity);
	newParticles.forEach(function(p) {
		setupParticle(p, options);
	});
	activeParticles = activeParticles.concat(newParticles);
	return newParticles;
}

// ------------------------------------

function setupParticle(particle, options) {
	particle = _.extend(particle, options);
	// particle.geometry.dynamic = true;
	particle.enabled = true;
	scene.add(particle);
	// changes to the vertices
	// particle.geometry.verticesNeedUpdate = true;

	// changes to the normals
	// particle.geometry.normalsNeedUpdate = true;
	// 	var rX = Math.random()
	// 	var rY = Math.random()
	// 	var x = (rX * WIDTH) - (WIDTH*0.5);
	// 	var y = (rY * HEIGHT) - (HEIGHT*0.5);
	// 	particle.position.x = x;
	// 	particle.position.y = y;
	// 	particle.angle = Math.atan2(y, x);
	// 	particle.speed = 0;

   let globalMeshPosition = particle.mesh.localToWorld(new THREE.Vector3());
  
		var color = getPixel(colorMap, colorMapData, globalMeshPosition.x + WIDTH*0.5, globalMeshPosition.y + HEIGHT*0.5);
		particle.mesh.material.color = new THREE.Color(color);
	// let angle = particle.angle;
	// particle.home.x = Math.cos(angle) * radius;
	// particle.home.y = Math.sin(angle) * radius;
	particle.home.z = 0;
  
}

// ------------------------------------

function recycleParticle(particle) {
	scene.remove(particle);
	particle.enabled = false;
	var index = activeParticles.indexOf(particle);
	activeParticles.splice(index, 1);
	availableParticles.push(particle);
}

// ------------------------------------

function spawnParticle(options) {
	var particle = _.extend( new THREE.Object3D(),
    {
		age: 0,
		lifespan: PARTICLE_LIFESPAN, // 2 seconds @ 60fps
		birthday: startTime,
		home: {}
	}, options);
	particle.endTime = particle.birthday + particle.lifespan;
 
  particle.child0 = new THREE.Object3D();
  particle.child1 = new THREE.Object3D();
  particle.add(particle.child0);
  particle.child0.add(particle.child1);
  particle.taurusAngleOffset;
  particle.mesh = new THREE.Mesh(
      standardGeometry,
      standardMaterial.clone()
    )
  particle.child1.add(particle.mesh);
  
  particle.child1.position.y = 100;
  
  var i = activeParticles.length;
	particle.angle = Math.random() * TAU;
  particle.taurusAngle = Math.random() * TAU;
	var x = Math.cos(particle.angle) * taurus.radius
	var y = Math.sin(particle.angle) * taurus.radius;
  var z = 0
  
	particle.position.x = particle.home.x = x;
	particle.position.y = particle.home.y = y;
  particle.rotation.set(0, 0,particle.angle - Math.PI * 0.5);
	particle.speed = 0.0;

	var color = getPixel(colorMap, colorMapData, x + WIDTH * 0.5, y + HEIGHT * 0.5);
	particle.mesh.material.color = new THREE.Color(color);
	return particle;
}

// ------------------------------------

function updateParticle(p, tick) {
	p.age = (tick - p.birthday) / p.lifespan;
  
	p.position.x = p.home.x;// + p.age * p.lifespan * Math.cos(p.angle) * p.speed;
	p.position.y = p.home.y;// + p.age * p.lifespan * Math.sin(p.angle) * p.speed;
	p.position.z = 0; //particleDestination.z * p.age + 0.1 * (p.birthday % particlesPerFrame);
	 // p.lookAt(camera.position);
  
  p.child0.setRotationFromEuler(new THREE.Euler(p.age * TAU + p.taurusAngle,0,0));
  
  let angleRatio = p.angle / TAU;
  let centerOffset = 100 * Math.sin(TAU * (p.age*2 + angleRatio))
  p.mesh.position.y = centerOffset;
  
  // p.mesh.material.opacity = Math.pow(Math.sin( Math.PI * p.age), 0.5) * .3;
	// if (p.age >= 1) {
	// 	recycleParticle(p);
	// }
}

// ------------------------------------
var columnSpacing = WIDTH / numColumns; 

function initializeStage() {
  const geometry = new THREE.TorusGeometry( taurus.radius, taurus.tubeMin, 16, 100 );
  const material = new THREE.MeshBasicMaterial( { color: 0x222221 } );
  const torus = new THREE.Mesh( geometry, material );
  scene.add( torus );
  
  
  
  
	var particles = getParticles(numParticles);
	particles.forEach(function(particle, i, particles) {

		particle.birthday = -Math.floor(i / particlesPerFrame);

		// changes to the normals
		particle.mesh.geometry.normalsNeedUpdate = true;
		var rX = Math.random();
		var rY = Math.random();
    particle.angle = Math.random() * TAU;
    particle.taurusAngle = Math.random() * TAU;
    var x = Math.cos(particle.angle) * taurus.radius;
    var y = Math.sin(particle.angle) * taurus.radius;
    var z = 0

    particle.position.x = particle.home.x = x;
    particle.position.y = particle.home.y = y;
  particle.rotation.set(0,0, particle.angle - Math.PI * 0.5);

		var color = getPixel(colorMap, colorMapData, x + WIDTH * 0.5, y + HEIGHT * 0.5);
		particle.mesh.material.color = new THREE.Color(color);
	});
}

// ------------------------------------

// DYNAMIC COLORMAPS

// sample colormap at particle locations
// create new dymaniccolormap
// on epicycles draw circles with sampled colors
// may work as just sampling the pixels directly instead of drawing shapes

//----------------------------------------------------------------

function draw() {
	renderer.render(scene, camera);
}

//----------------------------------------------------------------


function stopRecording() {
    console.log("GENERATING!")
  zip.generateAsync({type:"blob"})
    .then(function(content) {
        // see FileSaver.js
    console.log("SAVING!")
        saveAs(content, "frames.zip");
    });
  
  
}

//----------------------------------------------------------------

function pad(num, len){
  while(num.toString().length < len ){
    num = "0" + num.toString()
  }
  return num;
}

function dist(x1, y1, x2, y2) {
  return Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
}

//----------------------------------------------------------------

function dataURItoBlob( dataURI ) {
    // Convert Base64 to raw binary data held in a string.

    var byteString = atob(dataURI.split(',')[1]);

    // Separate the MIME component.
    var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0]

    // Write the bytes of the string to an ArrayBuffer.
    var ab = new ArrayBuffer(byteString.length);
    var ia = new Uint8Array(ab);
    for (var i = 0; i < byteString.length; i++) {
        ia[i] = byteString.charCodeAt(i);
    }

    // Write the ArrayBuffer to a BLOB and you're done.
    var bb = new Blob([ab]);

    return bb;
}

// ----------------------

var colorMap;
var colorMapCanvas;
var colorMapCtx;
var colorMapData;

function loadColorMap(callback) {
	var imgSrc = 
      // "https://assets.codepen.io/98232/Imagemap+2048_1.png";
  // "https://assets.codepen.io/98232/comp54.jpg";
  // "https://assets.codepen.io/98232/colormap.png";
  // "https://assets.codepen.io/98232/es-rI4T0qmq6Y3GU6yMRwQC8hFTgvCHCbCFJsUkBinE.jpg";
  "https://assets.codepen.io/98232/31892160116_61e9768112_h.jpg";
  // "https://assets.codepen.io/98232/1422986251_35977ecbf5_z.jpg";  
  // "https://assets.codepen.io/98232/2185214920_47abcf6681_n.jpg";
  // "https://assets.codepen.io/98232/colorful-daisies-wallpaper-high-resolution.jpg";
  // "https://assets.codepen.io/98232/2366933472_22862e3593_z.jpg";
  // "https://assets.codepen.io/98232/31767985362_a049860c55_b.jpg";
  // "https://assets.codepen.io/98232/2197405434_a832fa4a8e_n.jpg";
  // "https://assets.codepen.io/98232/2145232367_10af630924_n_1.jpg";
  // "https://assets.codepen.io/98232/1845616206_7e238fe1bd_b.jpg";
  // "https://assets.codepen.io/98232/4803262469_94db8dc465_o.jpg";
  
	colorMap = new Image();
	colorMap.crossOrigin = "anonymous";
	colorMap.src = imgSrc;
	colorMap.onload = function() {
		postColorMapLoad();
		callback();
	};
}

function postColorMapLoad() {
	// create the sampler
	var colorMapCanvas = document.createElement("canvas");
  // $("body").append($(colorMapCanvas));
	colorMapCanvas.height = colorMapCanvas.width = 2048;
	colorMapCtx = colorMapCanvas.getContext("2d");
	colorMapCtx.drawImage(colorMap, 0, 0, 2048, 2048);
	colorMapData = colorMapCtx.getImageData(0, 0, 2048, 2048);
}


function getPixel(colorMap, colorMapData, x, y) {
	var data = colorMapData.data;
	var propX = x / WIDTH;
	var propY = y / HEIGHT;
	var col = (propX * colorMapData.width) << 2;
	var row = (propY * colorMapData.height) >> 0;
	var rowWidth = colorMapData.width << 2;
	return (data[col + (row * rowWidth) + 0] << 16) | (data[col + (row * rowWidth) + 1] << 8) | data[col + (row * rowWidth) + 2];
}

// function getPixel(colorMapData, propX, propY) {
// 	var data = colorMapData.data;
// 	var col = (propX * colorMapData.width) << 2;
// 	var row = (propY * colorMapData.height) >> 0;
// 	var rowWidth = colorMapData.width << 2;
// 	return (data[col + (row * rowWidth) + 0] << 16) | (data[col + (row * rowWidth) + 1] << 8) | data[col + (row * rowWidth) + 2];
// }

//----------------------------------------------------------------
// RUN

loadColorMap(setup);
              
            
!
999px

Console