|
var cubeItems = 17 + 1; |
|
var cubeSpace = 7; |
|
var cubeRatio = 4; |
|
var cubeSize = cubeSpace / cubeRatio; |
|
var margin = -cubeSpace * (cubeItems - 1) / 2; |
|
var colorStep = 255 / (cubeItems - 1); |
|
var r = colorStep * 256 * 256; |
|
var g = colorStep * 256; |
|
var b = colorStep; |
|
|
|
var maxSpeed = 3; |
|
var maxSpeedSq = Math.pow(maxSpeed, 2); |
|
var meshes = null; |
|
|
|
var container, stats; |
|
|
|
var camera, controls, scene, renderer; |
|
|
|
var axisX = new THREE.Vector3(1, 0, 0); |
|
var axisY = new THREE.Vector3(0, 1, 0); |
|
var axisZ = new THREE.Vector3(0, 0, 1); |
|
var angleX = -Math.PI / 4; |
|
var angleY = -Math.PI / 4; |
|
var angleZ = Math.atan(Math.sqrt(2) / 2); |
|
|
|
init(); |
|
|
|
function move(p, t) { |
|
var distance = p.distanceToSquared(t); |
|
var isTooFar = distance > maxSpeedSq; |
|
|
|
if (isTooFar) { |
|
var step = new THREE.Vector3() |
|
.subVectors(t, p) |
|
.setLength(maxSpeed); |
|
p.add(step); |
|
return false; |
|
} else { |
|
p.copy(t); |
|
return true; |
|
} |
|
} |
|
|
|
function animate() { |
|
render(); |
|
meshes.forEach(function (mesh) { |
|
var data = mesh.userData; |
|
if (data.targetReached) { |
|
return; |
|
} |
|
var p = mesh.position; |
|
var t = data.target; |
|
|
|
data.targetReached = move(p, t); |
|
mesh.updateMatrix(); |
|
|
|
}); |
|
stats.update(); |
|
controls.update(); |
|
requestAnimationFrame(animate); |
|
} |
|
|
|
function numberToHex(i) { |
|
var pad = "000000"; |
|
var s = i.toString(16); |
|
return '#' + pad.substring(0, pad.length - s.length) + s |
|
} |
|
|
|
function calcLabTarget(rgb) { |
|
var lab = d3.lab(rgb); |
|
return new THREE.Vector3( |
|
(lab.b), |
|
-100 + 2 * lab.l, |
|
(lab.a) |
|
); |
|
} |
|
|
|
function setLabTarget(mesh) { |
|
mesh.userData.targetReached = false; |
|
mesh.userData.target = calcLabTarget(mesh.userData.color); |
|
} |
|
|
|
function calcHslTarget(rgb) { |
|
var hsl = d3.hsl(rgb); |
|
if (isNaN(hsl.s)) hsl.s = 0; |
|
if (isNaN(hsl.h)) hsl.h = 0; |
|
hsl.h -= 45; |
|
var rad = Math.PI * hsl.h / 180; |
|
return new THREE.Vector3( |
|
hsl.s * 100, |
|
-100 + 200 * hsl.l, |
|
0 |
|
) |
|
.applyAxisAngle(axisY, rad); |
|
} |
|
function setHslTarget(mesh) { |
|
mesh.userData.targetReached = false; |
|
mesh.userData.target = calcHslTarget(mesh.userData.color); |
|
} |
|
|
|
function rgbTransform(x) { |
|
return margin + cubeSpace * x / colorStep; |
|
} |
|
function calcRgbTarget(rgb) { |
|
return new THREE.Vector3( |
|
rgbTransform(rgb.r), |
|
rgbTransform(rgb.g), |
|
rgbTransform(rgb.b) |
|
) |
|
.applyAxisAngle(axisX, angleX) |
|
.applyAxisAngle(axisZ, angleZ) |
|
.applyAxisAngle(axisY, angleY) |
|
|
|
} |
|
function setRgbTarget(mesh) { |
|
mesh.userData.targetReached = false; |
|
mesh.userData.target = calcRgbTarget(mesh.userData.color); |
|
} |
|
|
|
function setPosition(vector, values) { |
|
Object.keys(values).forEach(function (k) { |
|
vector[k] = values[k]; |
|
}); |
|
} |
|
|
|
function init() { |
|
camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 10000); |
|
camera.position.z = 500; |
|
|
|
controls = new THREE.OrbitControls(camera); |
|
|
|
scene = new THREE.Scene(); |
|
|
|
// world |
|
var cube = new THREE.SphereGeometry(cubeSize); |
|
var material = new THREE.MeshBasicMaterial({shading: THREE.FlatShading}); |
|
|
|
for (var x = 0; x < cubeItems; x++) { |
|
for (var y = 0; y < cubeItems; y++) { |
|
for (var z = 0; z < cubeItems; z++) { |
|
var rgbInt = x * r + y * g + b * z; |
|
var color = d3.rgb(numberToHex(rgbInt)); |
|
|
|
var m = material.clone(); |
|
m.color = new THREE.Color(rgbInt); |
|
var mesh = new THREE.Mesh(cube, m); |
|
|
|
mesh.userData = {color: color}; |
|
setRgbTarget(mesh); |
|
|
|
scene.add(mesh); |
|
|
|
mesh.matrixAutoUpdate = false; |
|
setPosition(mesh.position, mesh.userData.target); |
|
mesh.updateMatrix(); |
|
} |
|
} |
|
} |
|
|
|
meshes = scene.children.filter(function (o) { |
|
return o instanceof THREE.Mesh; |
|
}); |
|
|
|
// renderer |
|
|
|
renderer = new THREE.WebGLRenderer({antialias: true}); |
|
renderer.setPixelRatio(window.devicePixelRatio); |
|
renderer.setClearColor(0x808080); |
|
renderer.setSize(window.innerWidth, window.innerHeight); |
|
|
|
container = document.getElementById('container'); |
|
container.appendChild(renderer.domElement); |
|
|
|
stats = new Stats(); |
|
var statsStyle = stats.domElement.style; |
|
statsStyle.position = 'absolute'; |
|
statsStyle.top = '0px'; |
|
statsStyle.zIndex = 100; |
|
container.appendChild(stats.domElement); |
|
|
|
window.addEventListener('resize', onWindowResize, false); |
|
|
|
animate(); |
|
} |
|
|
|
function onWindowResize() { |
|
|
|
camera.aspect = window.innerWidth / window.innerHeight; |
|
camera.updateProjectionMatrix(); |
|
|
|
renderer.setSize(window.innerWidth, window.innerHeight); |
|
} |
|
|
|
function render() { |
|
renderer.render(scene, camera); |
|
} |