Click the above image to see a recreation of a previous Flash experiment (source code here), using Three.js. And see below for the source code used to create this thing.
// shim layer with setTimeout fallback set to 60 frames per second window.requestAnimFrame = (function(){ return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function( callback ){ window.setTimeout(callback, 1000 / 60); }; })(); var canvas, stage, container, scene, camera, renderer, projector, light; var w = 800; var h = 600; var VIEW_ANGLE = 45; var ASPECT = w/h; var NEAR = .1; var FAR = 10000; var centerX = 0; var centerY = 0; var centerZ = 0; var radius = 175; var cameraRadius = 1000; var cameraX = cameraRadius; var cameraY = cameraRadius; var cameraZ = cameraRadius; var theta = 0; var group; var objects = []; var numObjects = 0; var orbitSteps = 1000; var orbitSpeed = Math.PI*2/orbitSteps; var objectInterval; var objectPosition; var direction = 1; var index = 0; var startingObjects = 400; onload = init; function init() { canvas = document.getElementById("myCanvas"); scene = new THREE.Scene(); camera = new THREE.PerspectiveCamera( VIEW_ANGLE, ASPECT, NEAR, FAR ); camera.position.x = cameraX; camera.position.y = cameraY; camera.position.z = cameraZ; camera.lookAt(scene.position); scene.add( camera ); projector = new THREE.Projector(); renderer = new THREE.WebGLRenderer( { antialias: true } ); renderer.setSize( w, h ); light = new THREE.SpotLight(); light.position.set( centerX, centerY, 160 ); scene.add(light); canvas.appendChild( renderer.domElement ); renderer.render(scene,camera); initObjects(); onEnterFrame(); } function initObjects() { group = new THREE.Object3D(); group.position.x = centerX; group.position.y = centerY; group.position.x = centerZ; for(var i=0;i<startingObjects;i++) { addObject(); } scene.add(group); } function addObject() { var sphereMaterial = new THREE.MeshLambertMaterial({ color: Math.round(Math.random()*0xffffff) }); var obj = new THREE.Mesh( new THREE.SphereGeometry(8,8,8), // radius,segments,rings sphereMaterial); obj.position.x = 0; obj.position.y = 0; obj.position.z = 0; obj.overdraw = true; group.add(obj); objects.push(obj); numObjects = objects.length; objectInterval = orbitSteps/numObjects; } function onEnterFrame() { requestAnimFrame(onEnterFrame); renderer.render(scene,camera); for(var i = 0; i < numObjects; i++) { objectPosition = orbitSpeed*objectInterval*i; // each object is individually updated objects[i].position.x = radius * (Math.cos(theta + objectPosition) * (Math.pow(5,Math.cos(theta+objectPosition)) - 2 * Math.cos(4 * (theta+objectPosition)) - Math.pow(Math.sin((theta+objectPosition)/12),4))); objects[i].position.y = radius * (Math.sin(theta + objectPosition) * (Math.pow(5,Math.cos(theta+objectPosition)) - 2 * Math.cos(4 * (theta+objectPosition)) - Math.pow(Math.sin((theta+objectPosition)/12),4))); objects[i].position.z = centerZ + radius * Math.sin(theta + objectPosition) - radius*1*(Math.sin((radius/radius + 3) * (theta + objectPosition))); } group.rotation.x += .002; group.rotation.y += .004; group.rotation.z += .008; theta += (orbitSpeed*direction); }