diff options
| -rw-r--r-- | client/splash/cloud/index.js | 4 | ||||
| -rw-r--r-- | client/splash/constants.js | 2 | ||||
| -rw-r--r-- | client/splash/face/index.js | 26 | ||||
| -rw-r--r-- | client/splash/face/markers.js | 21 | ||||
| -rw-r--r-- | client/splash/face/mesh.js | 25 | ||||
| -rw-r--r-- | client/splash/index.js | 25 | ||||
| -rw-r--r-- | client/splash/modal.js | 10 | ||||
| -rw-r--r-- | client/splash/renderer.js | 2 | ||||
| -rw-r--r-- | site/assets/css/splash.css | 38 | ||||
| -rw-r--r-- | site/assets/demo/splash/index.html | 29 |
10 files changed, 155 insertions, 27 deletions
diff --git a/client/splash/cloud/index.js b/client/splash/cloud/index.js index d0a39d8c..1d1fb293 100644 --- a/client/splash/cloud/index.js +++ b/client/splash/cloud/index.js @@ -26,7 +26,9 @@ export function init() { textSize: CLOUD_TEXT_MIN_SIZE + Math.random() * (CLOUD_TEXT_MAX_SIZE - CLOUD_TEXT_MIN_SIZE), redrawInterval: 1, material: { - color: choice(CLOUD_COLORS), + color: 0xffffff, + opacity: (0.1 + Math.random() * 0.9), + transparent: true, }, texture: { text: datasetList[i], diff --git a/client/splash/constants.js b/client/splash/constants.js index 74e695e0..c7b3d785 100644 --- a/client/splash/constants.js +++ b/client/splash/constants.js @@ -49,6 +49,6 @@ export const MARKER_COLORS = [ 0xffffff, ] -export const POINT_SCALE = 0.01 +export const POINT_SCALE = 0.005 export const LINE_THICKNESS = 0.0075 export const FACE_POINT_COUNT = 68 diff --git a/client/splash/face/index.js b/client/splash/face/index.js index 31ce8f3b..46b7b847 100644 --- a/client/splash/face/index.js +++ b/client/splash/face/index.js @@ -16,10 +16,18 @@ export function startAnimation() { const name = choice(names) const face = faces[name] mesh.remove() - mesh.load(name).then(meshes => { - meshes.wireframe.position.z -= 0.001 + mesh.load(name).then(geometry => { + let meshes markers.swap(face) .then({ + obj: {}, + duration: 0, + finished: () => { + meshes = mesh.createFaceMeshes(geometry) + meshes.wireframe.position.z -= 0.001 + } + }) + .then({ from: { n: 0 }, to: { n: 1 }, duration: 500, @@ -43,27 +51,31 @@ export function startAnimation() { } }) .then({ - from: { n: 0 }, - to: { n: 1 }, + from: { n: 0, lines: 1, cubes: 1, }, + to: { n: 1, lines: 0, cubes: 0.5 }, delay: 500, duration: 1000, easing: oktween.easing.quad_in, update: (obj) => { meshes.solid.material.opacity = 1 - obj.n meshes.wireframe.material.opacity = obj.n + markers.fadePointsTo(obj.cubes) + markers.fadeLinesTo(obj.lines) }, finished: () => { mesh.removeMesh('solid') } }) .then({ - from: { n: 1 }, - to: { n: 0 }, + from: { n: 1, lines: 0, cubes: 0 }, + to: { n: 0, lines: 1, cubes: 1 }, delay: 5000, duration: 500, - easing: oktween.easing.quad_in_out, + easing: oktween.easing.quad_out, update: (obj) => { meshes.wireframe.material.opacity = obj.n + markers.fadePointsTo(obj.cubes) + markers.fadeLinesTo(obj.lines) }, }) .then({ diff --git a/client/splash/face/markers.js b/client/splash/face/markers.js index 20a96fb6..354ed68c 100644 --- a/client/splash/face/markers.js +++ b/client/splash/face/markers.js @@ -42,7 +42,11 @@ export function build(points) { quaternion.setFromEuler(rotation, false) matrix.compose(position.clone(), quaternion, boxScale) geometry.applyMatrix(matrix) - let material = new THREE.MeshBasicMaterial({ color: boxColor }) + let material = new THREE.MeshBasicMaterial({ + color: boxColor, + opacity: 1, + transparent: true, + }) let cube = new THREE.Mesh(geometry, material) group.add(cube) return cube @@ -52,6 +56,9 @@ export function build(points) { const color = new THREE.Color() const material = new MeshLineMaterial({ color: color.setHex(MARKER_COLORS[i % MARKER_COLORS.length]), + alphaTest: 0.01, + opacity: 1.0, + transparent: true, }) const line = new MeshLine() line.setGeometry(geometry, () => LINE_THICKNESS) @@ -60,13 +67,23 @@ export function build(points) { group.add(mesh) return [line, mesh] }) - + window.meshes = meshes group.frustumCulled = false scene.add(group) updateFace(scaledPoints, cubes, meshes) } +export function fadePointsTo(opacity) { + cubes.forEach(cube => cube.material.opacity = opacity) +} +export function fadeLinesTo(opacity) { + meshes.forEach((pair) => { + pair[1].material.uniforms.visibility.value = opacity + pair[1].material.uniformsNeedUpdate = true + }) +} + function scalePoints(points) { const bbox = getBboxForPoints(points) let { scale, midX, midY, midZ } = getBboxScaleAndCentroid(bbox) diff --git a/client/splash/face/mesh.js b/client/splash/face/mesh.js index 92aff020..d823ecf5 100644 --- a/client/splash/face/mesh.js +++ b/client/splash/face/mesh.js @@ -1,4 +1,8 @@ -import { Points, Mesh, MeshBasicMaterial, MeshStandardMaterial, VertexColors, TrianglesDrawMode, DoubleSide } from 'three' +import { + Points, + Mesh, MeshBasicMaterial, MeshStandardMaterial, + VertexColors, TrianglesDrawMode, DoubleSide +} from 'three' import { scene } from '../renderer' @@ -13,15 +17,16 @@ DRACOLoader.setDecoderPath('/assets/js/vendor/draco/') const dracoLoader = new DRACOLoader() DRACOLoader.getDecoderModule() +dracoLoader.setVerbosity(1) +dracoLoader.setDrawMode(TrianglesDrawMode) export function load(name) { - dracoLoader.setVerbosity(1) - dracoLoader.setDrawMode(TrianglesDrawMode) dracoLoader.setSkipDequantization('position', true) return new Promise((resolve, reject) => { - dracoLoader.load('/assets/data/faces/' + name + '.drc', (geometry) => { - resolve(createFaceMeshes(geometry)) - }) + const loaded = geometry => resolve(geometry) + const progress = () => {} + const error = () => reject() + dracoLoader.load('/assets/data/faces/' + name + '.drc', loaded, progress, error) }) } @@ -29,7 +34,7 @@ export function update(name) { load(name) } -function createFaceMeshes(geometry) { +export function createFaceMeshes(geometry) { return { blank: createBlankFace(geometry), wireframe: createWireframeFace(geometry), @@ -52,6 +57,7 @@ function createBlankFace(geometry) { color: 0xFFFFFF, metalness: 0.2, roughness: 0.5, + alphaTest: 0.01, }) material.wireframe = false material.transparent = true @@ -61,7 +67,10 @@ function createBlankFace(geometry) { } function createWireframeFace(geometry) { - const material = new MeshBasicMaterial({ vertexColors: VertexColors }) + const material = new MeshBasicMaterial({ + vertexColors: VertexColors, + alphaTest: 0.01, + }) material.wireframe = true material.transparent = true material.opacity = 0 diff --git a/client/splash/index.js b/client/splash/index.js index 88211337..322ed0ff 100644 --- a/client/splash/index.js +++ b/client/splash/index.js @@ -1,8 +1,11 @@ import { Vector3 } from 'three' import OrbitControls from 'three-orbitcontrols' -import { init, render, camera, renderer } from './renderer' +import { appendRenderer, render, camera, renderer } from './renderer' +import { toArray } from '../util' + +import * as modal from './modal' import * as cloud from './cloud' import * as face from './face' @@ -14,11 +17,25 @@ controls.rotateSpeed = 1 / 4 controls.zoomSpeed = 1 controls.keyPanSpeed = 1 / 2 +function init() { + build() + bind() + animate() +} + function build() { - init() + appendRenderer() cloud.init() face.init() - animate() +} + +function bind() { + toArray(document.querySelectorAll('.aboutLink')).forEach(el => { + el.addEventListener('click', modal.open) + }) + document.querySelector('.about .inner').addEventListener('click', e => e.stopPropagation()) + document.querySelector('.about').addEventListener('click', modal.close) + document.querySelector('.close').addEventListener('click', modal.close) } function animate() { @@ -37,4 +54,4 @@ function animate() { render() } -document.addEventListener('DOMContentLoaded', build) +document.addEventListener('DOMContentLoaded', init) diff --git a/client/splash/modal.js b/client/splash/modal.js new file mode 100644 index 00000000..d5a63d75 --- /dev/null +++ b/client/splash/modal.js @@ -0,0 +1,10 @@ + +export function open() { + const el = document.querySelector('.about') + el.classList.add('open') +} + +export function close() { + const el = document.querySelector('.about') + el.classList.remove('open') +} diff --git a/client/splash/renderer.js b/client/splash/renderer.js index 14326f77..fbb27b40 100644 --- a/client/splash/renderer.js +++ b/client/splash/renderer.js @@ -21,7 +21,7 @@ lights[0].position.set(3, 3, 3).normalize() lights[1].position.set(-3, -3, -3).normalize() lights.forEach(light => scene.add(light)) -export function init() { +export function appendRenderer() { const container = document.querySelector('#three_container') container.appendChild(renderer.domElement) window.addEventListener('resize', () => { diff --git a/site/assets/css/splash.css b/site/assets/css/splash.css index eef3126f..238139a4 100644 --- a/site/assets/css/splash.css +++ b/site/assets/css/splash.css @@ -13,6 +13,7 @@ footer { background: transparent; position: absolute; bottom: 0; left: 0; + padding: 20px; } footer div { z-index: 2; @@ -26,10 +27,47 @@ footer > div { padding: 3px; border-radius: 2px; } +header .links a.activeLink { + color: white; + border-bottom-color: white; +} .splash { } #three_container { position: absolute; top: 0; left: 0; width: 100%; height: 100%; +} + +.about.open { + pointer-events: auto; + opacity: 1; + cursor: pointer; +} +.about { + position: absolute; + top: 0; left: 0; + width: 100%; height: 100%; + display: flex; + align-items: center; + justify-content: center; + background: rgba(0,0,0,0.5); + opacity: 0; + pointer-events: none; + transition: opacity 400ms; + overflow-y: scroll; +} +.about .inner { + padding: 40px; + background: #000; + color: #eee; + max-width: 600px; + line-height: 1.5; + cursor: text; +} +.about .inner b { + color: #fff; +} +.about a { + color: #fff; }
\ No newline at end of file diff --git a/site/assets/demo/splash/index.html b/site/assets/demo/splash/index.html index 0592c637..80c34e0a 100644 --- a/site/assets/demo/splash/index.html +++ b/site/assets/demo/splash/index.html @@ -18,17 +18,40 @@ <div class='site_name'>MegaPixels</div> </a> <div class='links'> - <a href="#" className='aboutLink'>LAUNCHING MAY 2019</a> + <a href="#" class='aboutLink'>LAUNCHING MAY 2019</a> + <a href="#" class='aboutLink activeLink'>ABOUT</a> </div> </header> <div class="splash"> <div id="three_container"></div> </div> + <div class="about"> + <div class="close"></div> + <div class="inner"> + <p> + <b>MegaPixels</b> is an online art project that explores the history of face recognition from the perspective of datasets. MegaPixels aims to unravel the meanings behind the data and expose the darker corners of the biometric industry that have contributed to its growth. + </p> + <p> + Through a mix of case studies, visualizations, and interactive tools, Megapixels will use face recognition datasets to tell the history of modern biometrics. Many people have contributed to the development of face recognition technology, both wittingly and unwittingly. Not only scientists, but also celebrities and regular internet users have played a part. + </p> + <p> + Face recognition is a mess of contradictions. It works, yet it doesn't actually work. It's cheap and accessible, but also expensive and out of control. Face recognition research has achieved headline grabbing superhuman accuracies over 99.9%, yet in practice it's also dangerously inaccurate. + </p> + <p> + During a trial installation at Sudkreuz station in Berlin in 2018, 20% of the matches were wrong, a number so low that it should not have any connection to law enforcement or justice. And in London, the Metropolitan police had been using face recognition software that mistakenly identified an alarming 98% of people as criminals, which perhaps is a crime itself. + </p> + <p> + MegaPixels was created by <a href="https://ahprojects.com/">Adam Harvey</a> and will launch in May 2019. + </p> + </div> + </div> <footer> - <div></div> + <div> + <a href="#" class='aboutLink'>About This Project</a> + </div> <div> MegaPixels ©2017-19 Adam R. Harvey / - <a href="https://ahprojects.com/">ahprojects.com</a> + <a href="https://ahprojects.com/megapixels/">ahprojects.com</a> </div> </footer> </body> |
