Interfacing the law project proposal Zalan Szakacs: Difference between revisions
(54 intermediate revisions by 2 users not shown) | |||
Line 41: | Line 41: | ||
<br> | <br> | ||
=Project proposal= | =Project proposal XPPL Volumetric Catalogue= | ||
<onlyinclude> | |||
[[File:Screen Shot 2018-05-22 at 14.38.06.png|thumbnail|left|First sketch of 3D stack]] | |||
''How would it be possible to explore an exisiting book collection in a serendipitous way?'' | |||
<br> | <br> | ||
' | An addition to the XPPL (space for potential pirate librarianship of the Piet Zwart Institute) would be a 3D web interface, which translates the metadata of the books into volumetric geometries. The morphology of the shapes is generated based on the string values of the book titles. Either round, spicy, robust, geometric or organic–each book has it's own character, which will be mirrored in the interface. By clicking on the specific shape the user will be linked to the API of the specific book. This project explores new ways of spreading information in the multidimensional environment, datavisualisation and alternative ways of representing books in digital libraries. It aims to break the rigidness of square presentation formats and plays with syntaxes of abusing file formats. | ||
<br> | <br> | ||
The interface will be written in JavaScript. WebGL is an JavaScript API for rendering interactive 3D and 2D graphics within any compatible web browser without the use of plug-ins. WebGL does so by introducing an API that closely conforms to OpenGL ES 2.0 that can be used in HTML5 <canvas> elements. | |||
<br> | |||
This project uses the similar way of thinking, but the outcome differentiats from the other students. The fascination came from a long time research about encoding and decoding information on 2D and 3D levels, steganography (hiding information in other file formats). | |||
<br> | |||
During my previous project ACCP I started to explore different encoding and decoding systems for informations. This fascination became very intriguing for me, so therefor I decided to invest more time about it for a new project, which will explore the potential of hiding informations in 3D shapes. An other learning goal with this project to get familiar with JavaScript and WebGL. | |||
<br> | |||
The issues could arise on the technical level, since there is a limited time (4 weeks) to realise the whole project without JavaScript knowledge beforehand. An other issue could arise about the connection way of the original interface and 3D interface. | |||
<br> | |||
</onlyinclude> | |||
== Bibliography == | |||
* Golden, K. (2015). The Nest Interface Is No Interface: The simple path to brilliant technology. New Riders. | |||
* Steyerl, H. (2012). The Wretched of the Screen. SternbergPress. | |||
* Fuller, M. (2013). Behind The Blip: Essays on the culture of software. Autonomedia. | |||
<br> | |||
=Steps to take= | |||
→ <del>Loading all the books in the .json file</del> | |||
<br> | |||
→ <del>scenery creation (light, shadow)</del> | |||
<br> | |||
→ <del>materiality of the shapes (material or color)</del> | |||
<br> | <br> | ||
→ <del>rotation add to the shapes </del> | |||
<br> | <br> | ||
→ <del>hover over mouse effect</del> | |||
<br> | <br> | ||
→ <del>link the shapes with the books</del> | |||
<br> | <br> | ||
→ <del>hosting online or offline?</del> | |||
<br> | <br> | ||
→ <del>fine-tuning!!!</del> | |||
<br> | <br> | ||
=Javascript process= | |||
'''Transforming the book titles into ASCII numeric values''' | |||
<gallery class="center" widths=692px heights=405px> | |||
Screen Shot 2018-05-31 at 23.33.05.png|The book titles are translated into ASCII numeric values | |||
</gallery> | |||
<br> | <br> | ||
<gallery class="center" widths=692px heights=405px> | |||
1600px-USASCII code chart.png|ASCII code chart | |||
</gallery> | |||
<br> | <br> | ||
''' | |||
''HTML'' | |||
<pre> | |||
<!DOCTYPE html> | |||
<html> | |||
<head> | |||
<title>Collection</title> | |||
<script> | |||
var booktitles = [ | |||
{ | |||
title: "Mac OS X Leopard Edition", | |||
id: "1", | |||
}, | |||
{ | |||
title: "The Qmail Handbook", | |||
id: "2", | |||
}, | |||
{ | |||
title: "Hardening Network Infrastructure: Bulletproof Your Systems Before You Are Hacked!", | |||
id: "3", | |||
}, | |||
{ | |||
title: "Cocoa Programming for Mac OS X Second Edition", | |||
id: "4", | |||
}, | |||
{ | |||
title: "LDAP System Administration", | |||
id: "5", | |||
} | |||
]; | |||
</script> | |||
</head> | |||
<body> | |||
<ul> | |||
<script> | |||
for(i =0; i < booktitles.length;i++){ | |||
document.write('<h4>'+booktitles[i].title+'</h4>'); | |||
var myCoords = xyzGen(booktitles[i].title) | |||
console.log("Coordinates of " + booktitles[i].title) | |||
console.log("x : " + myCoords.x) | |||
console.log("y : " + myCoords.y) | |||
console.log("z : " + myCoords.z) | |||
} | |||
// console.log(booktitles); | |||
var x,y,z | |||
function xyzGen(string) { | |||
let coords = { | |||
x: 0, | |||
y: 0, | |||
z: 0, | |||
} | |||
string = string.split(' ') | |||
for (var i = 0; i < 3; i++) { | |||
for (c in string[i]) { | |||
if (i == 0) { | |||
coords.x += string[i].charCodeAt(c) | |||
} else if (i == 1) { | |||
coords.y += string[i].charCodeAt(c) | |||
} else if (i == 2) { | |||
coords.z += string[i].charCodeAt(c) | |||
} | |||
} | |||
} | |||
return coords | |||
} | |||
</script> | |||
</ul> | |||
</body> | |||
</html> | |||
</pre> | |||
'''Using ASCII numeric values for x, y, z values for generating 3D shapes (spheres and cubes)''' | |||
<gallery class="center" widths=692px heights=405px> | |||
Threejs.gif|Using ASCII numeric values for x, y, z values for generating 3D shapes (spheres and cubes) | |||
</gallery> | |||
<br> | <br> | ||
* | |||
* | |||
''HTML'' | |||
<pre> | |||
<!DOCTYPE html> | |||
<html> | |||
<head> | |||
<title></title> | |||
</head> | |||
<body> | |||
<canvas id="viewer"></canvas> | |||
<script src="https://rawgit.com/mrdoob/three.js/dev/build/three.min.js"></script> | |||
<script src="https://rawgit.com/mrdoob/three.js/dev/examples/js/controls/OrbitControls.js"></script> | |||
<script src="setup.js"></script> | |||
</body> | |||
</html> | |||
</pre> | |||
''JS'' | |||
<pre> | |||
const MAX_DISTANCE = 100; | |||
const MAX_SPHERE_RADIUS = 50; | |||
let camera, scene, controls, renderer; | |||
console.clear(); | |||
let bookTitles = [{ | |||
title: "Mac OS X Leopard Edition", | |||
id: "1", | |||
type: 'sphere' | |||
}, | |||
{ | |||
title: "The Qmail Handbook", | |||
id: "2", | |||
type: 'sphere' | |||
}, | |||
{ | |||
title: "Hardening Network Infrastructure: Bulletproof Your Systems Before You Are Hacked!", | |||
id: "3", | |||
type: 'sphere' | |||
}, | |||
{ | |||
title: "Cocoa Programming for Mac OS X Second Edition", | |||
id: "4", | |||
type: 'cube' | |||
}, | |||
{ | |||
title: "LDAP System Administration", | |||
id: "5", | |||
type: 'cube' | |||
} | |||
]; | |||
function setupBooks() { | |||
bookTitles.forEach(book => { | |||
let geometry; | |||
let radius = Math.floor(Math.random() * MAX_SPHERE_RADIUS); | |||
switch (book.type) { | |||
case 'sphere': | |||
geometry = new THREE.SphereGeometry(radius, 50, 50); | |||
break; | |||
case 'cube': | |||
geometry = new THREE.BoxGeometry(radius, radius, radius, 10, 10); | |||
break; | |||
} | |||
// Add missing material | |||
let mesh = new THREE.Mesh(geometry); | |||
mesh.position.copy(getRandomVector()); | |||
scene.add(mesh); | |||
}); | |||
} | |||
function getRandomVector() { | |||
return new THREE.Vector3(getRandom(), getRandom(), getRandom()); | |||
} | |||
function getRandom() { | |||
return Math.floor(Math.random() * MAX_DISTANCE); | |||
} | |||
function animate() { | |||
requestAnimationFrame(animate); | |||
controls.update(); | |||
render(); | |||
} | |||
function render() { | |||
renderer.render(scene, camera); | |||
} | |||
window.onload = function () { | |||
scene = new THREE.Scene(); | |||
scene.background = new THREE.Color(0xeeeeee); | |||
camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.01, 10000); | |||
camera.position.z = 500; | |||
camera.updateProjectionMatrix(); | |||
scene.add(camera); | |||
renderer = new THREE.WebGLRenderer({ | |||
antialias: true | |||
}); | |||
renderer.setPixelRatio(window.devicePixelRatio); | |||
renderer.setSize(window.innerWidth, window.innerHeight); | |||
let viewer = document.querySelector('#viewer'); | |||
viewer.parentNode.replaceChild(renderer.domElement, viewer); | |||
controls = new THREE.OrbitControls(camera, renderer.domElement); | |||
animate(); | |||
setupBooks(); | |||
} | |||
window.onresize = function () { | |||
renderer.setSize(window.innerWidth, window.innerHeight); | |||
camera.aspect = window.innerWidth / window.innerHeight; | |||
camera.updateProjectionMatrix(); | |||
}; | |||
</pre> | |||
'''Final working script''' | |||
<gallery class="center" widths=692px heights=405px> | |||
Xppl.gif | |||
</gallery> | |||
<br> | <br> | ||
= | |||
string value = radius | |||
colours = categories of the library | |||
category2color["Technical"] = "#003d5d"; | |||
<br> | |||
category2color["Science/History"] = "#f23d5d"; | |||
<br> | |||
category2color["Media studies"] = "#8c3d5d"; | |||
<br> | |||
category2color["Philosophy"] = "#723d5d"; | |||
<br> | |||
category2color["Computer culture"] = "#423d5d"; | |||
<br> | |||
category2color["Design / writing / publishing"] = "#ff0000"; | |||
<br> | |||
category2color["Desk"] = "#09edff"; | |||
<br> | <br> | ||
category2color["Art"] = "#ffedff"; | |||
<br> | <br> | ||
category2color["Digital (Steve Trim 2 reading)"] = "#ffe1cc"; | |||
<br> | <br> | ||
category2color["Media Studies (Femke Trim 3)"] = "#00e1cc"; | |||
<br> | <br> | ||
category2color["Literature, Culture, Theory"] = "#00a1d4"; | |||
x,y,z = title, category, file format | |||
sphere.position.x = get_x(book.title) | |||
<br> | <br> | ||
sphere.position.z = get_z(book.category) | |||
<br> | <br> | ||
sphere.rotation.y =get_y(book.fileformat) | |||
<br> | <br> | ||
''The string values of the titles and the metadata from the JSON file is generating the radius and the position of the shapes'' | |||
<gallery class="center" widths=692px heights=405px> | |||
Screen Shot 2018-06-13 at 19.35.47.png | |||
</gallery> | |||
<br> | <br> | ||
<pre> | |||
<!DOCTYPE html> | |||
<html> | |||
<head> | |||
<title>XPPL VOLUMETRIC CATALOGUE </title> | |||
<meta charset="utf-8"> | |||
<link href="https://fonts.googleapis.com/css?family=Karla" rel="stylesheet"> | |||
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0"> | |||
<style> | |||
body{ | |||
font-family: 'Karla', sans-serif; | |||
font-size:15px; | |||
text-align:left; | |||
background: #140b33; | |||
margin: 0px; | |||
overflow: hidden; | |||
} | |||
#info { | |||
position: absolute; | |||
top: 0px; width: 10%; | |||
padding: 10px; | |||
} | |||
a { | |||
color: #444b7c; | |||
} | |||
.swal-button { | |||
padding: 7px 19px; | |||
background-color: #aca4c1; | |||
font-size: 12px; | |||
} | |||
.swal-title { | |||
margin: 0px; | |||
font-size: 16px; | |||
box-shadow: 0px 1px 1px rgba(0, 0, 0, 0.21); | |||
margin-bottom: 28px; | |||
} | |||
.swal-overlay { | |||
background-color: rgba(20, 13, 50, 0.6); | |||
} | |||
.swal-modal { | |||
background-color: rgba(172, 164, 193, 1); | |||
} | |||
</style> | |||
</head> | |||
<body> | |||
<div id="info"><a target="_blank" rel="noopener"> <b> <font size="5"> XPPL VOLUMETRIC CATALOGUE </font> </b> <br> <br> Feel free to move around and explore the content of the library by clicking on the volumetric shapes</div> | |||
<div id="container"></div> | |||
<canvas id="viewer"></canvas> | |||
<script src="https://rawgit.com/mrdoob/three.js/dev/build/three.min.js"></script> | |||
<script src="https://rawgit.com/mrdoob/three.js/dev/examples/js/controls/OrbitControls.js"></script> | |||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script> | |||
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootbox.js/4.4.0/bootbox.min.js"></script> | |||
<script src="https://unpkg.com/sweetalert/dist/sweetalert.min.js"></script> | |||
<script> | |||
var category2color = new Object(); | |||
category2color["Technical"] = "#003d5d"; | |||
category2color["Science/History"] = "#f23d5d"; | |||
category2color["Media studies"] = "#8c3d5d"; | |||
category2color["Philosophy"] = "#723d5d"; | |||
category2color["Computer culture"] = "#423d5d"; | |||
category2color["Design / writing / publishing"] = "#ff0000"; | |||
category2color["Desk"] = "#09edff"; | |||
category2color["Art"] = "#ffedff"; | |||
category2color["Digital (Steve Trim 2 reading)"] = "#ffe1cc"; | |||
category2color["Media Studies (Femke Trim 3)"] = "#00e1cc"; | |||
category2color["Literature, Culture, Theory"] = "#00a1d4"; | |||
category2color["default"] = "#cdc1ec"; | |||
console.clear(); | |||
const MAX_DISTANCE = 1000; | |||
// | |||
const MIN_DISTANCE = 200; | |||
const MAX_SPHERE_RADIUS = 700; | |||
var bookTitles; | |||
var request = new XMLHttpRequest(); | |||
request.open('GET', 'export.json', true); | |||
request.onload = function() { | |||
if (request.status >= 200 && request.status < 400) { | |||
var data = JSON.parse(request.responseText); | |||
//bookTitles = data; | |||
bookTitles = data.books[0]; | |||
bookTitles.forEach(function(book) { | |||
book.type = "sphere"; | |||
console.log('BOOK:', book); | |||
}); | |||
console.log("got books", bookTitles) | |||
// loaded data, start | |||
init(); | |||
animate(); | |||
} else { | |||
// We reached our target server, but it returned an error | |||
} | |||
}; | |||
request.onerror = function() { | |||
// There was a connection error of some sort | |||
}; | |||
request.send(); | |||
// --- threeJS --- // | |||
var renderer, scene, camera, distance, raycaster, projector; | |||
var container = document.getElementById('container'); | |||
var raycaster = new THREE.Raycaster(),INTERSECTED; | |||
var mouse = new THREE.Vector2(); | |||
var distance = 600; | |||
// -- basic initialization -- // | |||
function init() { | |||
renderer = new THREE.WebGLRenderer({ | |||
antialias: true | |||
}); | |||
renderer.setClearColor(0x140b33, 1); | |||
//renderer.setClearColor(0xFF00FF, 1.0); | |||
renderer.setSize(window.innerWidth, window.innerHeight); | |||
container.appendChild(renderer.domElement); | |||
renderer.clear(); | |||
scene = new THREE.Scene(); | |||
camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.01, 300000); | |||
// controls = new THREE.OrbitControls(camera); | |||
//camera.position.set(1, 1, -5); | |||
camera.position.set(MAX_DISTANCE/2, MAX_DISTANCE/20, MAX_DISTANCE/20); | |||
camera.updateProjectionMatrix(); | |||
controls = new THREE.OrbitControls(camera, renderer.domElement); | |||
// controls.maxDistance = MAX_DISTANCE * 3; | |||
controls.target = new THREE.Vector3(0, 0, 0); | |||
controls.rotateSpeed = .2; | |||
controls.enableDamping = true; | |||
console.log(controls.enableDamping); | |||
controls.dampingFactor = .15; | |||
controls.panSpeed = .15; | |||
controls.screenSpacePanning = true; | |||
// to disable zoom | |||
controls.enableZoom = true; | |||
// to disable rotation | |||
controls.enableRotate = true; | |||
// to disable pan | |||
controls.enablePan = true; | |||
scene.add(camera); | |||
//camera.position.set(-150, 10, 1); | |||
//controls.update(); | |||
// scene.add(camera); | |||
light = new THREE.PointLight(0x404040); | |||
light.position.set(50, 0, 0); | |||
scene.add(light); | |||
/* light_two = new THREE.PointLight(0xffffff, 1, 4000); | |||
light_two.position.set(-100, 800, 800); | |||
lightAmbient = new THREE.AmbientLight(0x404040); | |||
scene.add(light, light_two, lightAmbient);*/ | |||
// createSpheres(); | |||
scene.add(new THREE.DirectionalLight(0xffffff)); | |||
scene.add(new THREE.AmbientLight(0x222222)); | |||
renderer.setPixelRatio(window.devicePixelRatio); | |||
renderer.clear(); | |||
let viewer = document.querySelector('#viewer'); | |||
viewer.parentNode.replaceChild(renderer.domElement, viewer); | |||
window.onload = function () { | |||
createDiamond(); | |||
createSpace(); | |||
document.addEventListener('mousemove', onMouseMove, false); | |||
document.addEventListener('mousedown', onDocumentMouseDown, false); | |||
window.addEventListener('resize', onWindowResize, false); | |||
}; | |||
} | |||
// -- diamonds -- // | |||
function catInobject(obj, category){ //checks if category is in object, if so return corresponding color | |||
if (obj.hasOwnProperty(category)) { | |||
var color = obj[category]; | |||
} else { var color = obj["default"]; | |||
} | |||
return color; | |||
} | |||
function createDiamond() { | |||
diamondsGroup = new THREE.Object3D(); | |||
console.log(); | |||
bookTitles.forEach(function (book) { | |||
var chosencolor = catInobject(category2color, book.category); | |||
var sphere = new THREE.SphereGeometry(1.3, Math.random() * 50, Math.random() * 50); | |||
var material = new THREE.MeshPhongMaterial({ | |||
color: chosencolor,//Math.random() * 0xff00000 - 0xff00000, | |||
shading: THREE.FlatShading | |||
}); | |||
var sphere = new THREE.Mesh(sphere, material); | |||
sphere.position.x = get_x(book.title) * Math.random() * 100 - 1000; | |||
sphere.position.z = get_z(book.category) * Math.random() * 100 - 1000; | |||
sphere.rotation.y =get_y(book.fileformat) * Math.random() * 100 - 1000; | |||
//sphere.scale.x = sphere.scale.y = sphere.scale.z = Math.random() * 50 + 10; | |||
sphere.scale.x = sphere.scale.y = sphere.scale.z = get_size(book.title); | |||
sphere.userData = book; | |||
diamondsGroup.add(sphere); | |||
}) | |||
diamondsGroup.position.x = 500; | |||
scene.add(diamondsGroup); | |||
}; | |||
function get_size(title){ | |||
radius = 0; | |||
for (var i = 0; i < title.length; i++) { | |||
radius = radius + title.charCodeAt(i) | |||
} | |||
console.log(radius) | |||
return radius/100; | |||
} | |||
function get_x(title){ | |||
radius = 0; | |||
for (var i = 0; i < title.length; i++) { | |||
radius = radius + title.charCodeAt(i) | |||
} | |||
console.log(radius) | |||
return radius/100; | |||
} | |||
function get_y(title){ | |||
radius = 0; | |||
for (var i = 0; i < title.length; i++) { | |||
radius = radius + title.charCodeAt(i) | |||
} | |||
console.log(radius) | |||
return radius/100; | |||
} | |||
function get_z(title){ | |||
radius = 0; | |||
for (var i = 0; i < title.length; i++) { | |||
radius = radius + title.charCodeAt(i) | |||
} | |||
console.log(radius) | |||
return radius/100; | |||
} | |||
// -- dots on the back -- // | |||
function createSpace() { | |||
let dots = new THREE.Object3D(); | |||
for (let i = 0; i < 1000; i++) { | |||
let circleGeometry = new THREE.SphereGeometry(2, Math.random() * 5, Math.random() * 5); | |||
let color; | |||
if (Math.round(Math.random()) === 0) | |||
color = new THREE.Color('#003d5d'); | |||
else | |||
//color = new THREE.Color('#f23d5d'); | |||
color = new THREE.Color('#f23d5d'); | |||
let material = new THREE.MeshPhongMaterial({ | |||
color: color | |||
//side: THREE.DoubleSide | |||
}); | |||
material.flatShading = true; | |||
//var material = new THREE.MeshBasicMaterial({ | |||
//color : new THREE.Color(Math.floor(Math.random() * 1) === 1), | |||
//color : new THREE.Color(Math.random() * 0.5 + 0.5, Math.random() * 0.5 + 0.5, 0), | |||
//shading: THREE.FlatShading, | |||
//}) | |||
var circle = new THREE.Mesh(circleGeometry, material); | |||
material.side = THREE.DoubleSide; | |||
circle.position.x = Math.random() * -distance * 100; | |||
circle.position.y = Math.random() * -distance * 8; | |||
circle.position.z = Math.random() * distance * 5; | |||
circle.rotation.y = Math.random() * 2 * Math.PI; | |||
circle.scale.x = circle.scale.y = circle.scale.z = Math.random() * 8 + 5; | |||
dots.add(circle); | |||
} | |||
dots.position.x = 14000; | |||
dots.position.y = 1500; | |||
dots.position.z = 4000; | |||
dots.rotation.y = Math.PI * 600; | |||
dots.rotation.z = Math.PI * 500; | |||
scene.add(dots); | |||
}; | |||
/* | |||
let circle = new THREE.Mesh(circleGeometry, material); | |||
let direction = getRandomVector().normalize(); | |||
let minDistance = MAX_DISTANCE * 8; | |||
let maxDistance = minDistance + 0.5; | |||
direction.multiplyScalar(Math.random() * (maxDistance - minDistance) + minDistance); | |||
circle.position.add(direction); | |||
dots.add(circle); | |||
} | |||
scene.add(dots); | |||
} | |||
*/ | |||
function getRandomVector(max_distance) { | |||
if (max_distance === undefined) | |||
max_distance = 1; | |||
return new THREE.Vector3(getRandom(max_distance), getRandom(max_distance), getRandom(max_distance)); | |||
} | |||
function getRandom(max_distance) { | |||
let random = Math.random() * max_distance; | |||
random *= Math.floor(Math.random() * 2) === 1 ? 1 : -1; | |||
return random; | |||
} | |||
// -- events -- // | |||
function onMouseMove(event) { | |||
mouse.x = (event.clientX / window.innerWidth) * 2 - 1; | |||
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1; | |||
mouseX = event.clientX - window.innerWidth / 2 ; | |||
mouseY = event.clientY - window.innerHeight / 2 ; | |||
//camera.position.x += (mouseX - camera.position.x) * 0.01; | |||
//camera.position.y += (mouseY - camera.position.y) * 0.01; | |||
// camera.lookAt(scene.position); | |||
}; | |||
function onDocumentMouseDown(event) { | |||
event.preventDefault(); | |||
mouse.x = (event.clientX / window.innerWidth) * 2 - 1; | |||
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1; | |||
raycaster.setFromCamera(mouse, camera); | |||
var intersects = raycaster.intersectObjects(diamondsGroup.children); | |||
if (intersects.length > 0) { | |||
var book = intersects[0].object.userData; | |||
console.log("click", book); | |||
//alert("book" + book.title); | |||
//swal(book.title + book.authors); | |||
swal(book.title + " \n " + " " + book.authors.map(function(x){return x.author_name}).join(", ") + " \n" + book.category, { | |||
buttons: ["return", "open" ], | |||
}).then (function(button){ | |||
console.log("button", button) | |||
if (button) { | |||
window.location = "/books/"+book.id | |||
} | |||
}); | |||
} | |||
}; | |||
function onDocumentMouseWheel( event ) { | |||
fov -= event.wheelDeltaY * 0.05; | |||
camera.projectionMatrix = THREE.Matrix4.makePerspective( fov, window.innerWidth / window.innerHeight, 1, 1100 ); | |||
} | |||
function onWindowResize() { | |||
camera.aspect = window.innerWidth / window.innerHeight; | |||
renderer.setSize(window.innerWidth, window.innerHeight); | |||
camera.updateProjectionMatrix(); | |||
controls.update(); | |||
}; | |||
// ---- // | |||
function animate() { | |||
requestAnimationFrame(animate); | |||
controls.update(); | |||
render(); | |||
}; | |||
// -- render all -- // | |||
function render() { | |||
var timer = 0.00001 * Date.now(); | |||
for (var i = 0, l = diamondsGroup.children.length; i < l; i++) { | |||
var object = diamondsGroup.children[i]; | |||
object.position.y = 500 * Math.cos(timer + i); | |||
object.rotation.y += Math.PI / 500; | |||
} | |||
// update the picking ray with the camera and mouse position | |||
raycaster.setFromCamera(mouse, camera); | |||
// calculate objects intersecting the picking ray | |||
var intersects = raycaster.intersectObjects(diamondsGroup.children); | |||
if (intersects.length > 0) { | |||
if (INTERSECTED != intersects[0].object) { | |||
if (INTERSECTED) INTERSECTED.material.emissive.setHex(INTERSECTED.currentHex); | |||
INTERSECTED = intersects[0].object; | |||
INTERSECTED.currentHex = INTERSECTED.material.emissive.getHex(); | |||
INTERSECTED.material.emissive.setHex(Math.random() * 0xff00000 - 0xff00000); | |||
} | |||
} else { | |||
if (INTERSECTED) INTERSECTED.material.emissive.setHex(INTERSECTED.currentHex); | |||
INTERSECTED = null; | |||
} | |||
renderer.render(scene, camera); | |||
}; | |||
// -- run functions -- // | |||
//init(); | |||
//animate(); | |||
</script> | |||
</body> | |||
</html> | |||
</pre> | |||
=Resources= | =Resources= |
Latest revision as of 20:07, 13 June 2018
Research about 3D Steganography
Redesigns of the 3D printed gun
3D Steganography talk by Dennis de Bel
3D Steganography 3D printing project by Dennis de Bel
Pattern based 3D image Steganography
Embedding data in 3D models
A High Capacity 3D Steganography Algorithm
A High-Capacity Data Hiding Method for Polygonal Meshes?
Object Steganography, Noah Feehan (2012)
Disarming Corruptor
pirating 3d prints by listening to the printer
Questions to think about
- Which part of the collection should be hidden? Why?
- How to link with the X-Lib?
- Based on which metadata variables are the shapes created?
- Which feature would transform the 3D generated shapes into 3D printable files?
- Who is using it? Only PZ?
- What is the most important features?
- Where would be situated? local? networked? physical space?
- When would it be available? Temporary? Always?
Project proposal XPPL Volumetric Catalogue
How would it be possible to explore an exisiting book collection in a serendipitous way?
An addition to the XPPL (space for potential pirate librarianship of the Piet Zwart Institute) would be a 3D web interface, which translates the metadata of the books into volumetric geometries. The morphology of the shapes is generated based on the string values of the book titles. Either round, spicy, robust, geometric or organic–each book has it's own character, which will be mirrored in the interface. By clicking on the specific shape the user will be linked to the API of the specific book. This project explores new ways of spreading information in the multidimensional environment, datavisualisation and alternative ways of representing books in digital libraries. It aims to break the rigidness of square presentation formats and plays with syntaxes of abusing file formats.
The interface will be written in JavaScript. WebGL is an JavaScript API for rendering interactive 3D and 2D graphics within any compatible web browser without the use of plug-ins. WebGL does so by introducing an API that closely conforms to OpenGL ES 2.0 that can be used in HTML5 <canvas> elements.
This project uses the similar way of thinking, but the outcome differentiats from the other students. The fascination came from a long time research about encoding and decoding information on 2D and 3D levels, steganography (hiding information in other file formats).
During my previous project ACCP I started to explore different encoding and decoding systems for informations. This fascination became very intriguing for me, so therefor I decided to invest more time about it for a new project, which will explore the potential of hiding informations in 3D shapes. An other learning goal with this project to get familiar with JavaScript and WebGL.
The issues could arise on the technical level, since there is a limited time (4 weeks) to realise the whole project without JavaScript knowledge beforehand. An other issue could arise about the connection way of the original interface and 3D interface.
Bibliography
- Golden, K. (2015). The Nest Interface Is No Interface: The simple path to brilliant technology. New Riders.
- Steyerl, H. (2012). The Wretched of the Screen. SternbergPress.
- Fuller, M. (2013). Behind The Blip: Essays on the culture of software. Autonomedia.
Steps to take
→ Loading all the books in the .json file
→ scenery creation (light, shadow)
→ materiality of the shapes (material or color)
→ rotation add to the shapes
→ hover over mouse effect
→ link the shapes with the books
→ hosting online or offline?
→ fine-tuning!!!
Javascript process
Transforming the book titles into ASCII numeric values
HTML
<!DOCTYPE html> <html> <head> <title>Collection</title> <script> var booktitles = [ { title: "Mac OS X Leopard Edition", id: "1", }, { title: "The Qmail Handbook", id: "2", }, { title: "Hardening Network Infrastructure: Bulletproof Your Systems Before You Are Hacked!", id: "3", }, { title: "Cocoa Programming for Mac OS X Second Edition", id: "4", }, { title: "LDAP System Administration", id: "5", } ]; </script> </head> <body> <ul> <script> for(i =0; i < booktitles.length;i++){ document.write('<h4>'+booktitles[i].title+'</h4>'); var myCoords = xyzGen(booktitles[i].title) console.log("Coordinates of " + booktitles[i].title) console.log("x : " + myCoords.x) console.log("y : " + myCoords.y) console.log("z : " + myCoords.z) } // console.log(booktitles); var x,y,z function xyzGen(string) { let coords = { x: 0, y: 0, z: 0, } string = string.split(' ') for (var i = 0; i < 3; i++) { for (c in string[i]) { if (i == 0) { coords.x += string[i].charCodeAt(c) } else if (i == 1) { coords.y += string[i].charCodeAt(c) } else if (i == 2) { coords.z += string[i].charCodeAt(c) } } } return coords } </script> </ul> </body> </html>
Using ASCII numeric values for x, y, z values for generating 3D shapes (spheres and cubes)
HTML
<!DOCTYPE html> <html> <head> <title></title> </head> <body> <canvas id="viewer"></canvas> <script src="https://rawgit.com/mrdoob/three.js/dev/build/three.min.js"></script> <script src="https://rawgit.com/mrdoob/three.js/dev/examples/js/controls/OrbitControls.js"></script> <script src="setup.js"></script> </body> </html>
JS
const MAX_DISTANCE = 100; const MAX_SPHERE_RADIUS = 50; let camera, scene, controls, renderer; console.clear(); let bookTitles = [{ title: "Mac OS X Leopard Edition", id: "1", type: 'sphere' }, { title: "The Qmail Handbook", id: "2", type: 'sphere' }, { title: "Hardening Network Infrastructure: Bulletproof Your Systems Before You Are Hacked!", id: "3", type: 'sphere' }, { title: "Cocoa Programming for Mac OS X Second Edition", id: "4", type: 'cube' }, { title: "LDAP System Administration", id: "5", type: 'cube' } ]; function setupBooks() { bookTitles.forEach(book => { let geometry; let radius = Math.floor(Math.random() * MAX_SPHERE_RADIUS); switch (book.type) { case 'sphere': geometry = new THREE.SphereGeometry(radius, 50, 50); break; case 'cube': geometry = new THREE.BoxGeometry(radius, radius, radius, 10, 10); break; } // Add missing material let mesh = new THREE.Mesh(geometry); mesh.position.copy(getRandomVector()); scene.add(mesh); }); } function getRandomVector() { return new THREE.Vector3(getRandom(), getRandom(), getRandom()); } function getRandom() { return Math.floor(Math.random() * MAX_DISTANCE); } function animate() { requestAnimationFrame(animate); controls.update(); render(); } function render() { renderer.render(scene, camera); } window.onload = function () { scene = new THREE.Scene(); scene.background = new THREE.Color(0xeeeeee); camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.01, 10000); camera.position.z = 500; camera.updateProjectionMatrix(); scene.add(camera); renderer = new THREE.WebGLRenderer({ antialias: true }); renderer.setPixelRatio(window.devicePixelRatio); renderer.setSize(window.innerWidth, window.innerHeight); let viewer = document.querySelector('#viewer'); viewer.parentNode.replaceChild(renderer.domElement, viewer); controls = new THREE.OrbitControls(camera, renderer.domElement); animate(); setupBooks(); } window.onresize = function () { renderer.setSize(window.innerWidth, window.innerHeight); camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); };
Final working script
string value = radius
colours = categories of the library
category2color["Technical"] = "#003d5d";
category2color["Science/History"] = "#f23d5d";
category2color["Media studies"] = "#8c3d5d";
category2color["Philosophy"] = "#723d5d";
category2color["Computer culture"] = "#423d5d";
category2color["Design / writing / publishing"] = "#ff0000";
category2color["Desk"] = "#09edff";
category2color["Art"] = "#ffedff";
category2color["Digital (Steve Trim 2 reading)"] = "#ffe1cc";
category2color["Media Studies (Femke Trim 3)"] = "#00e1cc";
category2color["Literature, Culture, Theory"] = "#00a1d4";
x,y,z = title, category, file format
sphere.position.x = get_x(book.title)
sphere.position.z = get_z(book.category)
sphere.rotation.y =get_y(book.fileformat)
The string values of the titles and the metadata from the JSON file is generating the radius and the position of the shapes
<!DOCTYPE html> <html> <head> <title>XPPL VOLUMETRIC CATALOGUE </title> <meta charset="utf-8"> <link href="https://fonts.googleapis.com/css?family=Karla" rel="stylesheet"> <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0"> <style> body{ font-family: 'Karla', sans-serif; font-size:15px; text-align:left; background: #140b33; margin: 0px; overflow: hidden; } #info { position: absolute; top: 0px; width: 10%; padding: 10px; } a { color: #444b7c; } .swal-button { padding: 7px 19px; background-color: #aca4c1; font-size: 12px; } .swal-title { margin: 0px; font-size: 16px; box-shadow: 0px 1px 1px rgba(0, 0, 0, 0.21); margin-bottom: 28px; } .swal-overlay { background-color: rgba(20, 13, 50, 0.6); } .swal-modal { background-color: rgba(172, 164, 193, 1); } </style> </head> <body> <div id="info"><a target="_blank" rel="noopener"> <b> <font size="5"> XPPL VOLUMETRIC CATALOGUE </font> </b> <br> <br> Feel free to move around and explore the content of the library by clicking on the volumetric shapes</div> <div id="container"></div> <canvas id="viewer"></canvas> <script src="https://rawgit.com/mrdoob/three.js/dev/build/three.min.js"></script> <script src="https://rawgit.com/mrdoob/three.js/dev/examples/js/controls/OrbitControls.js"></script> <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/bootbox.js/4.4.0/bootbox.min.js"></script> <script src="https://unpkg.com/sweetalert/dist/sweetalert.min.js"></script> <script> var category2color = new Object(); category2color["Technical"] = "#003d5d"; category2color["Science/History"] = "#f23d5d"; category2color["Media studies"] = "#8c3d5d"; category2color["Philosophy"] = "#723d5d"; category2color["Computer culture"] = "#423d5d"; category2color["Design / writing / publishing"] = "#ff0000"; category2color["Desk"] = "#09edff"; category2color["Art"] = "#ffedff"; category2color["Digital (Steve Trim 2 reading)"] = "#ffe1cc"; category2color["Media Studies (Femke Trim 3)"] = "#00e1cc"; category2color["Literature, Culture, Theory"] = "#00a1d4"; category2color["default"] = "#cdc1ec"; console.clear(); const MAX_DISTANCE = 1000; // const MIN_DISTANCE = 200; const MAX_SPHERE_RADIUS = 700; var bookTitles; var request = new XMLHttpRequest(); request.open('GET', 'export.json', true); request.onload = function() { if (request.status >= 200 && request.status < 400) { var data = JSON.parse(request.responseText); //bookTitles = data; bookTitles = data.books[0]; bookTitles.forEach(function(book) { book.type = "sphere"; console.log('BOOK:', book); }); console.log("got books", bookTitles) // loaded data, start init(); animate(); } else { // We reached our target server, but it returned an error } }; request.onerror = function() { // There was a connection error of some sort }; request.send(); // --- threeJS --- // var renderer, scene, camera, distance, raycaster, projector; var container = document.getElementById('container'); var raycaster = new THREE.Raycaster(),INTERSECTED; var mouse = new THREE.Vector2(); var distance = 600; // -- basic initialization -- // function init() { renderer = new THREE.WebGLRenderer({ antialias: true }); renderer.setClearColor(0x140b33, 1); //renderer.setClearColor(0xFF00FF, 1.0); renderer.setSize(window.innerWidth, window.innerHeight); container.appendChild(renderer.domElement); renderer.clear(); scene = new THREE.Scene(); camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.01, 300000); // controls = new THREE.OrbitControls(camera); //camera.position.set(1, 1, -5); camera.position.set(MAX_DISTANCE/2, MAX_DISTANCE/20, MAX_DISTANCE/20); camera.updateProjectionMatrix(); controls = new THREE.OrbitControls(camera, renderer.domElement); // controls.maxDistance = MAX_DISTANCE * 3; controls.target = new THREE.Vector3(0, 0, 0); controls.rotateSpeed = .2; controls.enableDamping = true; console.log(controls.enableDamping); controls.dampingFactor = .15; controls.panSpeed = .15; controls.screenSpacePanning = true; // to disable zoom controls.enableZoom = true; // to disable rotation controls.enableRotate = true; // to disable pan controls.enablePan = true; scene.add(camera); //camera.position.set(-150, 10, 1); //controls.update(); // scene.add(camera); light = new THREE.PointLight(0x404040); light.position.set(50, 0, 0); scene.add(light); /* light_two = new THREE.PointLight(0xffffff, 1, 4000); light_two.position.set(-100, 800, 800); lightAmbient = new THREE.AmbientLight(0x404040); scene.add(light, light_two, lightAmbient);*/ // createSpheres(); scene.add(new THREE.DirectionalLight(0xffffff)); scene.add(new THREE.AmbientLight(0x222222)); renderer.setPixelRatio(window.devicePixelRatio); renderer.clear(); let viewer = document.querySelector('#viewer'); viewer.parentNode.replaceChild(renderer.domElement, viewer); window.onload = function () { createDiamond(); createSpace(); document.addEventListener('mousemove', onMouseMove, false); document.addEventListener('mousedown', onDocumentMouseDown, false); window.addEventListener('resize', onWindowResize, false); }; } // -- diamonds -- // function catInobject(obj, category){ //checks if category is in object, if so return corresponding color if (obj.hasOwnProperty(category)) { var color = obj[category]; } else { var color = obj["default"]; } return color; } function createDiamond() { diamondsGroup = new THREE.Object3D(); console.log(); bookTitles.forEach(function (book) { var chosencolor = catInobject(category2color, book.category); var sphere = new THREE.SphereGeometry(1.3, Math.random() * 50, Math.random() * 50); var material = new THREE.MeshPhongMaterial({ color: chosencolor,//Math.random() * 0xff00000 - 0xff00000, shading: THREE.FlatShading }); var sphere = new THREE.Mesh(sphere, material); sphere.position.x = get_x(book.title) * Math.random() * 100 - 1000; sphere.position.z = get_z(book.category) * Math.random() * 100 - 1000; sphere.rotation.y =get_y(book.fileformat) * Math.random() * 100 - 1000; //sphere.scale.x = sphere.scale.y = sphere.scale.z = Math.random() * 50 + 10; sphere.scale.x = sphere.scale.y = sphere.scale.z = get_size(book.title); sphere.userData = book; diamondsGroup.add(sphere); }) diamondsGroup.position.x = 500; scene.add(diamondsGroup); }; function get_size(title){ radius = 0; for (var i = 0; i < title.length; i++) { radius = radius + title.charCodeAt(i) } console.log(radius) return radius/100; } function get_x(title){ radius = 0; for (var i = 0; i < title.length; i++) { radius = radius + title.charCodeAt(i) } console.log(radius) return radius/100; } function get_y(title){ radius = 0; for (var i = 0; i < title.length; i++) { radius = radius + title.charCodeAt(i) } console.log(radius) return radius/100; } function get_z(title){ radius = 0; for (var i = 0; i < title.length; i++) { radius = radius + title.charCodeAt(i) } console.log(radius) return radius/100; } // -- dots on the back -- // function createSpace() { let dots = new THREE.Object3D(); for (let i = 0; i < 1000; i++) { let circleGeometry = new THREE.SphereGeometry(2, Math.random() * 5, Math.random() * 5); let color; if (Math.round(Math.random()) === 0) color = new THREE.Color('#003d5d'); else //color = new THREE.Color('#f23d5d'); color = new THREE.Color('#f23d5d'); let material = new THREE.MeshPhongMaterial({ color: color //side: THREE.DoubleSide }); material.flatShading = true; //var material = new THREE.MeshBasicMaterial({ //color : new THREE.Color(Math.floor(Math.random() * 1) === 1), //color : new THREE.Color(Math.random() * 0.5 + 0.5, Math.random() * 0.5 + 0.5, 0), //shading: THREE.FlatShading, //}) var circle = new THREE.Mesh(circleGeometry, material); material.side = THREE.DoubleSide; circle.position.x = Math.random() * -distance * 100; circle.position.y = Math.random() * -distance * 8; circle.position.z = Math.random() * distance * 5; circle.rotation.y = Math.random() * 2 * Math.PI; circle.scale.x = circle.scale.y = circle.scale.z = Math.random() * 8 + 5; dots.add(circle); } dots.position.x = 14000; dots.position.y = 1500; dots.position.z = 4000; dots.rotation.y = Math.PI * 600; dots.rotation.z = Math.PI * 500; scene.add(dots); }; /* let circle = new THREE.Mesh(circleGeometry, material); let direction = getRandomVector().normalize(); let minDistance = MAX_DISTANCE * 8; let maxDistance = minDistance + 0.5; direction.multiplyScalar(Math.random() * (maxDistance - minDistance) + minDistance); circle.position.add(direction); dots.add(circle); } scene.add(dots); } */ function getRandomVector(max_distance) { if (max_distance === undefined) max_distance = 1; return new THREE.Vector3(getRandom(max_distance), getRandom(max_distance), getRandom(max_distance)); } function getRandom(max_distance) { let random = Math.random() * max_distance; random *= Math.floor(Math.random() * 2) === 1 ? 1 : -1; return random; } // -- events -- // function onMouseMove(event) { mouse.x = (event.clientX / window.innerWidth) * 2 - 1; mouse.y = -(event.clientY / window.innerHeight) * 2 + 1; mouseX = event.clientX - window.innerWidth / 2 ; mouseY = event.clientY - window.innerHeight / 2 ; //camera.position.x += (mouseX - camera.position.x) * 0.01; //camera.position.y += (mouseY - camera.position.y) * 0.01; // camera.lookAt(scene.position); }; function onDocumentMouseDown(event) { event.preventDefault(); mouse.x = (event.clientX / window.innerWidth) * 2 - 1; mouse.y = -(event.clientY / window.innerHeight) * 2 + 1; raycaster.setFromCamera(mouse, camera); var intersects = raycaster.intersectObjects(diamondsGroup.children); if (intersects.length > 0) { var book = intersects[0].object.userData; console.log("click", book); //alert("book" + book.title); //swal(book.title + book.authors); swal(book.title + " \n " + " " + book.authors.map(function(x){return x.author_name}).join(", ") + " \n" + book.category, { buttons: ["return", "open" ], }).then (function(button){ console.log("button", button) if (button) { window.location = "/books/"+book.id } }); } }; function onDocumentMouseWheel( event ) { fov -= event.wheelDeltaY * 0.05; camera.projectionMatrix = THREE.Matrix4.makePerspective( fov, window.innerWidth / window.innerHeight, 1, 1100 ); } function onWindowResize() { camera.aspect = window.innerWidth / window.innerHeight; renderer.setSize(window.innerWidth, window.innerHeight); camera.updateProjectionMatrix(); controls.update(); }; // ---- // function animate() { requestAnimationFrame(animate); controls.update(); render(); }; // -- render all -- // function render() { var timer = 0.00001 * Date.now(); for (var i = 0, l = diamondsGroup.children.length; i < l; i++) { var object = diamondsGroup.children[i]; object.position.y = 500 * Math.cos(timer + i); object.rotation.y += Math.PI / 500; } // update the picking ray with the camera and mouse position raycaster.setFromCamera(mouse, camera); // calculate objects intersecting the picking ray var intersects = raycaster.intersectObjects(diamondsGroup.children); if (intersects.length > 0) { if (INTERSECTED != intersects[0].object) { if (INTERSECTED) INTERSECTED.material.emissive.setHex(INTERSECTED.currentHex); INTERSECTED = intersects[0].object; INTERSECTED.currentHex = INTERSECTED.material.emissive.getHex(); INTERSECTED.material.emissive.setHex(Math.random() * 0xff00000 - 0xff00000); } } else { if (INTERSECTED) INTERSECTED.material.emissive.setHex(INTERSECTED.currentHex); INTERSECTED = null; } renderer.render(scene, camera); }; // -- run functions -- // //init(); //animate(); </script> </body> </html>
Resources
A High-Capacity 3D Steganography Algorithm With Adjustable Distortion
3D image watermarking
Watermarking of 3D Meshes using Matlab
3D printing
Matlab 3D printing
Pure Javascript demo code for parsing and rendering STL (ascii and binary) files