This is a 3D particles simulation created using three.js for CME151: Introduction to Data Visualization (Stanford University, Winter 2016).
Last active
February 15, 2016 02:13
-
-
Save kennyng/7467fe39eb4165b77a6f to your computer and use it in GitHub Desktop.
3D Particles Simulation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<meta charset="utf-8"> | |
<style> | |
body { | |
color: #808080; | |
font-family:Monospace; | |
font-size:13px; | |
text-align:center; | |
background-color: black; | |
margin: 0px; | |
overflow: hidden; | |
} | |
</style> | |
<body> | |
<div><canvas id="three_particle"></canvas></div> | |
<!-- JavaScript --> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r74/three.min.js"> | |
</script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.5.1/dat.gui.min.js"> | |
</script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/stats.js/r14/Stats.min.js"> | |
</script> | |
<script src="three_particle.js"></script> | |
</body> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Setup scene, camera, and renderer. | |
// ---------------------------------- | |
var WIDTH = HEIGHT = 720; | |
var scene = new THREE.Scene(); | |
// Create a canvas and renderer (use WebGL). | |
var canvas = document.getElementById("three_particle"); | |
var renderer = new THREE.WebGLRenderer({ | |
canvas: canvas, | |
antialias: true | |
}); | |
renderer.setSize(WIDTH, HEIGHT); | |
var camera = new THREE.PerspectiveCamera(80, WIDTH / HEIGHT, 0.1, 3*WIDTH); | |
camera.position.set(0, 200, 1000); | |
camera.rotation.set(-.2, 0, 0); | |
scene.add(camera); | |
// Add FPS using Stats.js | |
var stats = new Stats(); | |
stats.setMode(0); // 0: fps, 1: ms | |
// document.getElementById("three_particle_container").appendChild(stats.domElement); | |
document.body.appendChild(stats.domElement); | |
stats.domElement.style.position = 'absolute'; | |
stats.domElement.style.left = '0px'; | |
stats.domElement.style.top = '0px'; | |
var gui = new dat.GUI(); | |
// document.getElementById('three_particle_container').appendChild(gui.domElement); | |
// Particle geometry. | |
// ------------------ | |
var particle = function(width, height) { | |
this.type = "particle"; | |
this.gravity = -0.05; | |
this.drag = 0.995; | |
this.radius = Math.random() * 20; | |
this.y = 18 * Math.random() - 9; | |
this.x = 18 * Math.random() - 9; | |
this.z = 18 * Math.random() - 9; | |
this.max_v = 3; | |
this.x_v = Math.random() * 2 * this.max_v - 2 * this.max_v; | |
this.y_v = Math.random() * this.max_v; | |
this.z_v = Math.random() * 2 * this.max_v - 2 * this.max_v; | |
this.rx = 0; | |
this.ry = 0; | |
this.rz = 0; | |
this.rx_v = Math.random() / 10; | |
this.ry_v = Math.random() / 10; | |
this.rz_v = Math.random() / 10; | |
this.hue = 180 * Math.random(); | |
this.y_max = height - (this.radius / 2) - 10; | |
this.x_max = width - (this.radius / 2) - 10; | |
this.z_max = width - (this.radius / 2) - 10; | |
// ThreeJS stuff. | |
var geom = new THREE.SphereGeometry(this.radius, 20, 12); | |
this.obj = new THREE.Mesh(geom, | |
new THREE.MeshPhongMaterial({ | |
color: Math.floor(Math.random() * 0x1000000), | |
specular: 0x333333, | |
shininess: 100 | |
})); | |
this.constrain_x = function() { | |
if (this.x >= this.x_max) { | |
this.x = this.x_max; | |
this.x_v *= -1; | |
} | |
if (this.x <= -this.x_max) { | |
this.x = -this.x_max; | |
this.x_v *= -1; | |
} | |
} | |
this.constrain_y = function() { | |
if (this.y >= this.y_max) { | |
this.y = this.y_max; | |
this.y_v *= -1; | |
} | |
if (this.y <= -this.y_max) { | |
this.y = -this.y_max; | |
this.y_v *= -1; | |
} | |
} | |
this.constrain_z = function() { | |
if (this.z >= this.z_max) { | |
this.z = this.z_max; | |
this.z_v *= -1; | |
} | |
if (this.z <= -this.z_max) { | |
this.z = -this.z_max; | |
this.z_v *= -1; | |
} | |
} | |
this.update = function() { | |
// Y direction. | |
if ((Math.abs(this.y_v) > 0)) { | |
this.y_v *= this.drag; | |
this.y_v += this.gravity; | |
this.y += this.y_v; | |
this.constrain_y(); | |
} | |
// X direction. | |
if ((Math.abs(this.x_v) > 0)) { | |
this.x_v *= this.drag; | |
this.x += this.x_v; | |
this.constrain_x(); | |
} | |
// Z direction. | |
if ((Math.abs(this.z_v) > 0)) { | |
this.z_v *= this.drag; | |
this.z += this.z_v; | |
this.constrain_z(); | |
} | |
// Set hue. | |
this.hue += 1; | |
this.hue = this.hue % 360; | |
// Rotateon update. | |
this.rx += this.rx_v; | |
this.ry += this.ry_v; | |
this.rz += this.rz_v; | |
} | |
} | |
// Initialize arent object (like a sub-scene). | |
var parent = new THREE.Object3D(); | |
// Add Axes | |
// --------- | |
// Reference: | |
// http://soledadpenades.com/articles/three-js-tutorials/drawing-the-coordinate-axes | |
function buildAxes(length) { | |
var axes = new THREE.Object3D(); | |
var axisPosX = buildAxis(new THREE.Vector3(0,0,0), | |
new THREE.Vector3(length, 0, 0), 0xFF0000, false); | |
var axisNegX = buildAxis(new THREE.Vector3(0, 0, 0), | |
new THREE.Vector3(-length, 0, 0), 0xFF0000, true); | |
axes.add(axisPosX); // +X | |
axes.add(axisNegX); // -X | |
var axisPosY = buildAxis(new THREE.Vector3(0, 0, 0), | |
new THREE.Vector3(0, length, 0), 0x00FF00, false); | |
var axisNegY = buildAxis(new THREE.Vector3(0, 0, 0), | |
new THREE.Vector3(0, -length, 0 ), 0x00FF00, true); | |
axes.add(axisPosY); // +Y | |
axes.add(axisNegY); // -Y | |
var axisPosZ = buildAxis(new THREE.Vector3(0, 0, 0), | |
new THREE.Vector3(0, 0, length), 0x0000FF, false); | |
var axisNegZ = buildAxis(new THREE.Vector3(0, 0, 0), | |
new THREE.Vector3( 0, 0, -length), 0x0000FF, true); | |
axes.add(axisPosZ); // +Z | |
axes.add(axisNegZ); // -Z | |
return axes; | |
} | |
function buildAxis(src, dst, colorHex, dashed) { | |
var geom = new THREE.Geometry(), | |
mat; | |
if (dashed) { | |
mat = new THREE.LineDashedMaterial({linewidth:3, color: colorHex, | |
dashSize:3, gapSize:3}); | |
} else { | |
mat = new THREE.LineBasicMaterial({linewidth:3, color:colorHex}); | |
} | |
geom.vertices.push(src.clone()); | |
geom.vertices.push(dst.clone()); | |
// This is IMPORTANT, otherwise dashed lines will appear as plain lines. | |
geom.computeLineDistances(); | |
var axis = new THREE.Line(geom, mat, THREE.LineSegments); | |
return axis; | |
} | |
axes = buildAxes(WIDTH / 2); | |
parent.add(axes); | |
/* | |
Add Bounding Box | |
---------------- | |
BoundingBoxHelper(object, hex): | |
object (Object3D) - the object3D to show the world-axis-aligned boundingbox. | |
hex (optional) - hexadecimal value to define color ex:0x888888 | |
This creates an line object to the bounding box. | |
github.com/mrdoob/three.js/blob/master/src/extras/helpers/BoundingBoxHelper.js | |
*/ | |
var bounding_box = new THREE.BoundingBoxHelper(parent); // can be tied to scene | |
// var bounding_box = new THREE.BoundingBoxHelper( mesh ); // can be tied to mesh | |
bounding_box.update(); // render | |
parent.add(bounding_box); | |
// Add particle objects | |
// --------------------- | |
var balls = []; // Array of objects, each object has data for one bouncing ball. | |
var data = []; | |
var n = 300; | |
for (var i = 0; i < n; i++) { | |
var p = new particle(WIDTH/2, HEIGHT/2); | |
p.obj.position.set(p.x, p.y, p.z); | |
parent.add(p.obj); | |
data.push(p); | |
} | |
scene.add(parent); | |
// Add Light | |
// ---------- | |
var ambientLight = new THREE.AmbientLight(0x444444); | |
scene.add(ambientLight); | |
var directionalLight = new THREE.DirectionalLight(0xffffff); | |
directionalLight.position.set(10, 10, 10).normalize(); | |
scene.add(directionalLight); | |
var directionalLight2 = new THREE.DirectionalLight(0xffffff); | |
directionalLight2.position.set(-10, -10, -10).normalize(); | |
scene.add(directionalLight2); | |
// Add GUI Controls | |
// ------------------ | |
var controls = new function() { | |
this.x_rot_v = 0.02; | |
this.y_rot_v = 0.02; | |
this.z_rot_v = 0.02; | |
this.p_x_rot_v = 0; | |
this.p_y_rot_v = 0.01; | |
this.p_z_rot_v = 0; | |
this.ambient_light = true; | |
this.direction_light = true; | |
this.direction_light_2 = true; | |
} | |
gui.add(controls, 'x_rot_v', 0, 0.5); | |
gui.add(controls, 'y_rot_v', 0, 0.5); | |
gui.add(controls, 'z_rot_v', 0, 0.5); | |
gui.add(controls, 'p_x_rot_v', 0, 0.5); | |
gui.add(controls, 'p_y_rot_v', 0, 0.5); | |
gui.add(controls, 'p_z_rot_v', 0, 0.5); | |
ambient_light = gui.add(controls, 'ambient_light'); | |
ambient_light.onChange(function (value) { | |
if (value) { | |
scene.add(ambientLight); | |
} else { | |
scene.remove(ambientLight); | |
} | |
}); | |
direction_light = gui.add(controls, 'direction_light'); | |
direction_light.onChange(function (value) { | |
if (value) { | |
scene.add(directionalLight); | |
} else { | |
scene.remove(directionalLight); | |
} | |
}); | |
direction_light_2 = gui.add(controls, 'direction_light_2'); | |
direction_light_2.onChange(function (value) { | |
if (value) { | |
scene.add(directionalLight2); | |
} else { | |
scene.remove(directionalLight2); | |
} | |
}); | |
// Draw Loop | |
// ---------- | |
function draw() { | |
// Start recording statistics. | |
stats.begin(); | |
for (var i = 0; i <n; i++) { | |
data[i].update(); | |
data[i].obj.position.set(data[i].x, data[i].y, data[i].z); | |
} | |
parent.rotation.x += controls.p_x_rot_v; | |
parent.rotation.y += controls.p_y_rot_v; | |
parent.rotation.z += controls.p_z_rot_v; | |
// Render scene. | |
renderer.render(scene, camera); | |
// Stop recording statistics. | |
stats.end(); | |
// Run draw loop again. | |
requestAnimationFrame(draw); | |
} | |
// Start Animation | |
// ---------------- | |
requestAnimationFrame(draw); | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment