Last active
December 22, 2016 21:30
-
-
Save shshaw/ed60855e9cec28d7d067725f8a9fae23 to your computer and use it in GitHub Desktop.
⌨️ 3D Text Input ⌨️
This file contains hidden or 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
<script>console.clear();</script> |
This file contains hidden or 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
const backgroundColor = 0xFFFFFF; | |
const textColor = 0x2F24C1; | |
const cursorColor = 0x3FD1CB; | |
const highlightColor = 0x3FD1CB; | |
/*////////////////////////////////////////*/ | |
var renderCalls = []; | |
function render () { | |
requestAnimationFrame( render ); | |
renderCalls.forEach((callback)=>{ callback(); }); | |
} | |
render(); | |
/*////////////////////////////////////////*/ | |
var scene = new THREE.Scene(); | |
scene.fog = new THREE.Fog(backgroundColor, 30, 300); | |
var camera = new THREE.PerspectiveCamera( 80, window.innerWidth / window.innerHeight, 0.1, 800 ); | |
camera.position.z = 30; | |
camera.position.x = 5; | |
camera.position.y = -2; | |
var renderer = new THREE.WebGLRenderer( { antialias: true } ); | |
renderer.setPixelRatio( window.devicePixelRatio ); | |
renderer.setSize( window.innerWidth, window.innerHeight ); | |
renderer.setClearColor( backgroundColor );//0x ); | |
renderer.toneMapping = THREE.LinearToneMapping; | |
renderer.toneMappingExposure = Math.pow( 0.94, 5.0 ); | |
// if ( window.innerWidth > 500 ) { | |
// renderer.shadowMap.enabled = true; | |
// renderer.shadowMap.type = THREE.PCFShadowMap; | |
// } | |
window.addEventListener( 'resize', function () { | |
camera.aspect = window.innerWidth / window.innerHeight; | |
camera.updateProjectionMatrix(); | |
renderer.setSize( window.innerWidth, window.innerHeight ); | |
}, false ); | |
document.body.appendChild( renderer.domElement); | |
function renderScene(){ renderer.render( scene, camera ); } | |
renderCalls.push(renderScene); | |
/*////////////////////////////////////////*/ | |
var pointer = { | |
x: 0, | |
y: 0, | |
}; | |
document.addEventListener('mousemove', pointerMove); | |
document.addEventListener('touchmove', pointerMove); | |
document.body.addEventListener('mouseleave', pointerReset); | |
document.body.addEventListener('touchcancel', pointerReset); | |
function pointerReset(e){ | |
pointer.x = 0.5; | |
pointer.y = 0.5; | |
} | |
function pointerMove(e){ | |
let pos = e.touches ? e.touches[0] : e; | |
pointer.x = pos.clientX / window.innerWidth; | |
pointer.y = pos.clientY / window.innerWidth; | |
}; | |
let mousePos = { x: 0.25, y: 0.5 }; | |
function trackMouse(e){ | |
let pointer = e.touches ? e.touches[0] : e; | |
mousePos.x = ( pointer.clientX / window.innerWidth ); | |
mousePos.y = ( pointer.clientY / window.innerHeight ); | |
}; | |
function ease(current,target,ease){ return current + (target - current) * ( ease || 0.2 ); } | |
// var center = new THREE.Vector3(0,0,0); | |
// function updateCamera(){ | |
// mousePos._x = ease(mousePos._x || 0.5, mousePos.x, 0.06); | |
// mousePos._y = ease(mousePos._y || 0.5, mousePos.y, 0.06); | |
// scene.rotation.y = (mousePos._x - 0.5) * Math.PI/4; | |
// scene.rotation.x = (mousePos._y - 0.5) * Math.PI/4; | |
// // scene.position.x = -(12 * (mousePos._x - 0.5) * 2); | |
// // scene.position.y = (12 * (mousePos._y - 0.5) * 2); | |
// //scene.lookAt(center); | |
// // camera.lookAt( new THREE.Vector3( | |
// // (10 * (mousePos._x - 0.5) * 2), | |
// // -(10 * (mousePos._y - 0.5) * 2), | |
// // 0 | |
// // )); | |
// } | |
// updateCamera(); | |
// window.addEventListener('mousemove', trackMouse); | |
// renderCalls.push(updateCamera); | |
/*////////////////////////////////////////*/ | |
let orbit = new THREE.OrbitControls(camera, renderer.domElement); | |
// orbit.enableRotate = false; | |
// orbit.enablePan = false; | |
//orbit.enableKeys = true; | |
orbit.zoomSpeed = 0.6; | |
orbit.minDistance = 10; | |
/*////////////////////////////////////////*/ | |
var ambientLight = new THREE.AmbientLight(0x222222); | |
scene.add(ambientLight); | |
var hemiLight = new THREE.HemisphereLight( 0xFFF7EB, 0xEBF7FD, 0.3 ); | |
scene.add( hemiLight ); | |
var light = new THREE.SpotLight( 0xffffff ); | |
light.position.y = 40; | |
light.position.x = 0; | |
light.position.z = 200; | |
light.castShadow = true; | |
light.shadow.mapSize.width = 1024; | |
light.shadow.mapSize.height = 1024; | |
light.shadow.camera.near = 1; | |
light.shadow.camera.far = 800; | |
light.shadow.camera.fov = 40; | |
light.power = 1.5; | |
scene.add(light); | |
renderCalls.push(()=>{ | |
light.position.copy(camera.position); | |
}); | |
//var axisHelper = new THREE.AxisHelper( 30 ); | |
//scene.add( axisHelper ); | |
// The X axis is red. The Y axis is green. The Z axis is blue. | |
/*////////////////////////////////////////*/ | |
// http://codepen.io/shshaw/pen/LbaKpa?editors=0010 | |
var characters = { | |
"0":[[0,0],[1,0],[2,0],[0,1],[2,1],[0,2],[2,2],[0,3],[2,3],[0,4],[1,4],[2,4]], | |
"1":[[1,0],[0,1],[1,1],[1,2],[1,3],[1,4]], | |
"2":[[0,0],[1,0],[2,1],[1,2],[0,3],[0,4],[1,4],[2,4]], | |
"3":[[0,0],[1,0],[2,1],[1,2],[2,2],[2,3],[0,4],[1,4],[2,4]], | |
"4":[[0,0],[2,0],[0,1],[2,1],[0,2],[1,2],[2,2],[2,3],[2,4]], | |
"5":[[0,0],[1,0],[2,0],[0,1],[0,2],[1,2],[2,2],[2,3],[0,4],[1,4]], | |
"6":[[0,0],[1,0],[2,0],[0,1],[0,2],[1,2],[2,2],[0,3],[2,3],[0,4],[1,4],[2,4]], | |
"7":[[0,0],[1,0],[2,0],[2,1],[2,2],[1,3],[1,4]], | |
"8":[[0,0],[1,0],[2,0],[0,1],[2,1],[0,2],[1,2],[2,2],[0,3],[2,3],[0,4],[1,4],[2,4]], | |
"9":[[0,0],[1,0],[2,0],[0,1],[2,1],[0,2],[1,2],[2,2],[2,3],[0,4],[1,4],[2,4]], | |
"Z":[[0,0],[1,0],[2,0],[2,1],[1,2],[0,3],[0,4],[1,4],[2,4]], | |
"Y":[[0,0],[2,0],[0,1],[2,1],[0,2],[1,2],[2,2],[2,3],[0,4],[1,4]], | |
"X":[[0,0],[2,0],[0,1],[2,1],[1,2],[0,3],[2,3],[0,4],[2,4]], | |
"W":[[0,0],[4,0],[0,1],[2,1],[4,1],[0,2],[2,2],[4,2],[0,3],[2,3],[4,3],[1,4],[3,4]], | |
"V":[[0,0],[2,0],[0,1],[2,1],[0,2],[2,2],[0,3],[2,3],[1,4]], | |
"U":[[0,0],[2,0],[0,1],[2,1],[0,2],[2,2],[0,3],[2,3],[0,4],[1,4],[2,4]], | |
"T":[[0,0],[1,0],[2,0],[1,1],[1,2],[1,3],[1,4]], | |
"S":[[1,0],[2,0],[0,1],[1,2],[2,3],[0,4],[1,4]], | |
"R":[[0,0],[1,0],[0,1],[2,1],[0,2],[2,2],[0,3],[1,3],[0,4],[2,4]], | |
"Q":[[1,0],[0,1],[2,1],[0,2],[2,2],[0,3],[2,3],[1,4],[2,4],[3,4]], | |
"P":[[0,0],[1,0],[0,1],[2,1],[0,2],[1,2],[2,2],[0,3],[0,4]], | |
"O":[[1,0],[0,1],[2,1],[0,2],[2,2],[0,3],[2,3],[1,4]], | |
"N":[[0,0],[1,0],[0,1],[2,1],[0,2],[2,2],[0,3],[2,3],[0,4],[2,4]], | |
"M":[[0,0],[1,0],[2,0],[3,0],[0,1],[2,1],[4,1],[0,2],[2,2],[4,2],[0,3],[2,3],[4,3],[0,4],[4,4]], | |
"L":[[0,0],[0,1],[0,2],[0,3],[0,4],[1,4],[2,4]], | |
"K":[[0,0],[2,0],[0,1],[2,1],[0,2],[1,2],[0,3],[2,3],[0,4],[2,4]], | |
"J":[[2,0],[2,1],[0,2],[2,2],[0,3],[2,3],[1,4]], | |
"I":[[0,0],[1,0],[2,0],[1,1],[1,2],[1,3],[0,4],[1,4],[2,4]], | |
"H":[[0,0],[2,0],[0,1],[2,1],[0,2],[1,2],[2,2],[0,3],[2,3],[0,4],[2,4]], | |
"G":[[1,0],[2,0],[0,1],[0,2],[2,2],[0,3],[2,3],[0,4],[1,4],[2,4]], | |
"F":[[0,0],[1,0],[2,0],[0,1],[0,2],[1,2],[0,3],[0,4]], | |
"E":[[1,0],[2,0],[0,1],[0,2],[1,2],[0,3],[0,4],[1,4],[2,4]], | |
"D":[[0,0],[1,0],[0,1],[2,1],[0,2],[2,2],[0,3],[2,3],[0,4],[1,4]], | |
"C":[[1,0],[2,0],[0,1],[0,2],[0,3],[1,4],[2,4]], | |
"B":[[0,0],[1,0],[0,1],[2,1],[0,2],[1,2],[0,3],[2,3],[0,4],[1,4]], | |
"A":[[1,0],[2,0],[0,1],[2,1],[0,2],[1,2],[2,2],[0,3],[2,3],[0,4],[2,4]], | |
"}":[[0,0],[1,0],[1,1],[2,2],[1,3],[0,4],[1,4]], | |
"{":[[1,0],[2,0],[1,1],[0,2],[1,3],[1,4],[2,4]], | |
"]":[[0,0],[1,0],[1,1],[1,2],[1,3],[0,4],[1,4]], | |
"[":[[0,0],[1,0],[0,1],[0,2],[0,3],[0,4],[1,4]], | |
")":[[0,0],[1,1],[1,2],[1,3],[0,4]], | |
"(":[[1,0],[0,1],[0,2],[0,3],[1,4]], | |
"—":[[0,2],[1,2],[2,2],[3,2]], | |
"–":[[0,2],[1,2],[2,2]], | |
"-":[[0,2],[1,2]], | |
"`":[[0,0],[1,1]], | |
"\"":[[0,0],[1,0],[3,0],[4,0],[1,1],[4,1],[0,2],[3,2]], | |
"\\":[[0,0],[0,1],[1,2],[2,3],[2,4]], | |
",":[[1,3],[0,4]], | |
":":[[0,1],[0,3]], | |
"^":[[2,0],[1,1],[3,1],[0,2],[4,2]], | |
"!":[[0,0],[0,1],[0,2],[0,4]], | |
"=":[[0,1],[1,1],[2,1],[0,3],[1,3],[2,3]], | |
"|":[[0,0],[0,1],[0,2],[0,3],[0,4]], | |
".":[[0,4]], | |
"%":[[0,0],[1,0],[4,0],[0,1],[1,1],[3,1],[2,2],[1,3],[3,3],[4,3],[0,4],[3,4],[4,4]], | |
"'":[[0,0],[1,0],[1,1],[0,2]], | |
"?":[[0,0],[1,0],[2,0],[2,1],[1,2],[1,4]], | |
";":[[0,1],[0,3],[0,4]], | |
"/":[[2,0],[2,1],[1,2],[0,3],[0,4]], | |
">":[[0,0],[1,1],[2,2],[1,3],[0,4]], | |
"_":[[0,4],[1,4],[2,4]], | |
"+":[[1,1],[0,2],[1,2],[2,2],[1,3]], | |
"<":[[2,0],[1,1],[0,2],[1,3],[2,4]], | |
"~":[[1,0],[3,0],[0,1],[2,1]], | |
} | |
/*////////////////////////////////////////*/ | |
var poxelGeometry = new THREE.BoxGeometry( 1, 1, 1 ); | |
var poxelMaterial = new THREE.MeshPhongMaterial({ | |
color: 0xFF0000, //color || '#F00', | |
shininess: 60 | |
}); | |
function Poxel(material){ | |
THREE.Mesh.call( this, poxelGeometry, poxelMaterial); | |
this.castShadow = true; | |
this.receiveShadow = true; | |
return this; | |
} | |
CustomBounce.create("myBounce", {strength: 0.1, squash:0 }); | |
Poxel.prototype = Object.assign(Object.create(THREE.Mesh.prototype), { | |
constructor: Poxel, | |
transitionOut(speed){ | |
speed = speed || 0.4 + Math.random() * 0.2; | |
let tl = new TimelineLite({ | |
onStart: ()=> { this.animating = true; }, | |
onComplete: ()=> { this.animating = false; } | |
}); | |
tl.to(this.position, speed, { | |
z: -80, | |
delay: Math.random(), | |
ease: 'Power2.easeIn' //Bounce.easeOut | |
},0); | |
this.material = this.material.clone(); | |
this.material.transparent = true; | |
tl.to(this.material, speed * 0.6, { | |
opacity: 0, | |
ease: 'Linear.easeNone' | |
},speed * 0.6); | |
return tl; | |
}, | |
transitionIn(speed){ | |
speed = speed || 0.4 + Math.random() * 0.35; | |
return TweenLite.from(this.position, speed, { | |
z: 80, | |
ease: 'myBounce', | |
onStart: ()=> { this.animating = true; }, | |
onComplete: ()=> { this.animating = false; } | |
},0); | |
}, | |
}); | |
/*////////////////////////////////////////*/ | |
function Character(name,opts){ | |
THREE.Object3D.call(this); | |
this.name = name; | |
for (var key in opts){ this[key] = opts[key]; } | |
if ( !opts || !opts.poxelMaterial ) { | |
this.poxelMaterial = new THREE.MeshPhongMaterial({ | |
color: this.color, //color || '#F00', | |
transparent: true, | |
emissive: this.color, | |
emissiveIntensity: 0.6, | |
opacity: 0.95, | |
shininess: 120 | |
}); | |
} | |
this.buildPoxels(name); | |
} | |
Character.prototype = Object.assign(Object.create(THREE.Object3D.prototype), { | |
constructor: Character, | |
color: textColor, //0x454545, | |
buildPoxels(name){ | |
let pixels = characters[name]; | |
let i = pixels.length; | |
let width = 0; | |
let height = 0; | |
while( i-- ){ | |
let pixel = pixels[i]; | |
let pixelCube = new Poxel(); | |
pixelCube.material = this.poxelMaterial; | |
width = pixel[0] > width ? pixel[0] : width; | |
height = pixel[1] > height ? pixel[1] : height; | |
pixelCube.position.set( | |
pixel[0],// - (this.spriteWidth/2), | |
-pixel[1],// + (this.spriteHeight/2), | |
0 | |
); | |
this.add(pixelCube); | |
} | |
this.width = width+1; | |
this.height = height+1; | |
}, | |
transitionIn(delay, onComplete){ | |
onComplete = onComplete || function(){}; | |
let tl = new TimelineLite({ | |
onStart: ()=>{ this.animating = true; }, | |
onComplete: ()=>{ | |
this.animating = false; | |
onComplete.call(this,this); | |
}, | |
delay: delay | |
}); | |
this.traverseVisible((child)=>{ | |
if ( child !== this ) { tl.add(child.transitionIn(),0); } | |
}); | |
return tl; | |
}, | |
transitionOut(delay, onComplete){ | |
onComplete = onComplete || function(){}; | |
//this.matrixAutoUpdate = false; | |
// this.position.copy(this.getWorldPosition()); | |
// this.parent.remove(this); | |
// scene.add(this); | |
let tl = new TimelineLite({ | |
onStart: ()=>{ this.animating = true; }, | |
onComplete: ()=>{ | |
this.animating = false; | |
onComplete.call(this,this); | |
}, | |
delay: delay | |
}); | |
this.traverseVisible((child)=>{ | |
if ( child !== this ) { tl.add(child.transitionOut(),0); } | |
}); | |
return tl; | |
} | |
}); | |
/*////////////////////////////////////////*/ | |
function ease(current,target,ease){ return current + (target - current) * ( ease || 0.2 ); } | |
var container = new THREE.Group(); | |
scene.add(container); | |
var el = document.createElement('div'); | |
document.body.appendChild(el); | |
var inputter = new Vue({ | |
el: el, | |
data: ()=>({ | |
message: '', | |
defaultMessage: "Start\nTyping", | |
characters: [], | |
offsetX: 0, | |
offsetY: 0, | |
group: new THREE.Group(), | |
cursor: null | |
}), | |
template: '<textarea ref="input" type="text" v-model:value="message" style="position: absolute; bottom: 0; left: 0; opacity: 0;" />', | |
mounted(){ | |
this.message = ( location.hash && decodeURI(location.hash.slice(1)) ) || this.defaultMessage; | |
this.$refs.input.focus(); | |
//renderCalls.push(()=>{ this.$refs.input.focus(); }); | |
this.$nextTick(()=>{ this.$refs.input.select(); }); | |
document.addEventListener('focus', e=>{ | |
console.log('click!'); | |
this.$refs.input.focus(); | |
}); | |
// Cursor for tracking position in textarea | |
this.cursor = new Character('|',{ | |
poxelMaterial: new THREE.MeshPhongMaterial({ | |
color: cursorColor, | |
emissive: cursorColor, | |
emissiveIntensity: 0.6, | |
transparent: true, | |
opacity: 0.6 | |
}) | |
}); | |
this.cursor.visible = false; | |
TweenMax.to({},0.7,{ | |
onRepeat: ()=>{ | |
if ( !this.cursor.animate ) { | |
return; | |
} else { | |
this.cursor.visible = !this.cursor.visible; | |
} | |
}, | |
repeat: -1 | |
}); | |
this.cursor.traverse((child)=>{ child.attractive = false }); | |
this.cursor.position.z = -1.1; | |
this.group.add(this.cursor); | |
// text highlight when selection is made | |
this.highlight = new Poxel(); | |
this.highlight.material = new THREE.MeshPhongMaterial({ | |
color: highlightColor, | |
emissive: highlightColor, | |
emissiveIntensity: 0.6, | |
transparent: true, | |
opacity: 0.6 | |
}); | |
this.highlight.position.z = -2; | |
container.add(this.highlight); | |
container.add(this.group); | |
// Keep some dimension to the group. | |
let poxel = new Poxel(); | |
poxel.visible = false; | |
this.group.add(poxel); | |
// for sizing calculations | |
this.helper = new THREE.BoundingBoxHelper(this.group, 0xff0000); | |
this.helper.visible = false; | |
container.add(this.helper); | |
console.log(this.helper); | |
renderCalls.push( this.update ); | |
}, | |
watch: { | |
message: function(val, oldVal){ | |
val = val && val.toUpperCase(); | |
oldVal = oldVal && oldVal.toUpperCase(); | |
if ( val === oldVal ) { return; } | |
let len = Math.max(val.length, oldVal.length); | |
let i = 0; | |
let delay = 0; | |
let tl = new TimelineLite(); | |
let oldChars = this.characters; | |
let newChars = val.split(''); | |
for (; i< len; i++){ | |
let char = newChars[i]; | |
let oldChar = ( oldChars[i] && oldChars[i].name || oldChars[i] ); | |
if ( oldChar !== char ) { | |
//console.log('not equal', i, char, oldChar); | |
if ( oldChar ) { tl.add(this.removeChar(oldChars[i], i), delay += 0.05 ); } | |
if ( char ) { tl.add(this.addChar(char, i), delay += 0.05 ); } | |
} | |
} | |
//this.cursor.visible = true; | |
}, | |
}, | |
methods: { | |
addChar(char, i){ | |
let character = char.toUpperCase(); | |
let tl = function(){}; | |
if ( characters[character] ) { | |
character = new Character(character); | |
tl = character.transitionIn(null); | |
this.group.add(character); | |
} | |
this.characters[i] = character; | |
let pos = this.getPosition(i); | |
if ( character.position ) { | |
character.position.x = pos[0] - character.width; | |
character.position.y = pos[1]; | |
} | |
return tl; | |
}, | |
removeChar(char, i){ | |
delete this.characters[i]; | |
if ( char && char.transitionOut ) { | |
return char.transitionOut(null,(char)=>{ | |
if ( char.parent ) { char.parent.remove(char); } | |
}); | |
} else { return function(){} } | |
}, | |
getPosition(index){ | |
let offsetX = 0; | |
let offsetY = 0; | |
this.characters.forEach((char,i)=>{ | |
if ( index !== null && i > index ) { return false; } | |
if ( char.match && char.match(/[\n\r]/) ) { | |
offsetY -= 6; | |
offsetX = 0; | |
} else if ( char.width ) { | |
offsetX += (char.width + 1); | |
} else { | |
offsetX += 4; | |
} | |
}); | |
this.offsetX = offsetX; | |
this.offsetY = offsetY; | |
return [offsetX, offsetY]; | |
}, | |
updateCursor(){ | |
let start = this.$refs.input.selectionStart; | |
let end = this.$refs.input.selectionEnd; | |
let cursorPos = this.getPosition(start-1); | |
let cursorX = cursorPos[0] + 1; | |
let cursorY = cursorPos[1]; | |
let scaleX = 1; | |
let scaleY = 1; | |
if ( start !== end ) { | |
let cursorEnd = this.getPosition(end); | |
//this.cursor.position.x -= start-end; | |
cursorX = cursorEnd[0] - ((cursorEnd[0] - cursorPos[0])/2); | |
//scaleX = cursorEnd[0] - cursorPos[0]; //start-end; | |
if ( cursorEnd[1] !== cursorPos[1] ) { | |
//cursorY = (cursorEnd[1]) + ( cursorEnd[1] - cursorPos[1] ) / 2; | |
//scaleY = ( cursorEnd[1] - cursorPos[1] ) / 3; //start-end; | |
} | |
this.cursor.visible = false; | |
this.cursor.animate = false; | |
this.updateHighlight(); | |
} else { | |
this.highlight.visible = false; | |
this.cursor.animate = true; | |
} | |
this.cursor.position.x = ease(this.cursor.position.x, cursorX, 0.2); | |
this.cursor.position.y = ease(this.cursor.position.y, cursorY, 0.5); | |
this.cursor.scale.x = ease( this.cursor.scale.x, scaleX, 0.2); | |
this.cursor.scale.y = ease( this.cursor.scale.y, scaleY, 0.2); | |
}, | |
updateHighlight(){ | |
this.highlight.visible = true; | |
this.highlight.position.copy(this.group.position); | |
this.highlight.scale.x = Math.abs(this.helper.box.max.x) + Math.abs(this.helper.box.min.x); | |
this.highlight.position.x += this.highlight.scale.x/2; | |
this.highlight.scale.y = Math.abs(this.helper.box.max.y) + Math.abs(this.helper.box.min.y); | |
this.highlight.position.y -= this.highlight.scale.y/2; | |
this.highlight.position.z -= 2; | |
}, | |
update(){ | |
this.helper.position.copy(this.group.position); | |
this.helper.position.z = -1; | |
this.helper.update(); | |
this.updateCursor(); | |
let centerX = (( this.helper.box.max.x + this.helper.box.min.x ))/2; | |
let centerY = (( this.helper.box.max.y + this.helper.box.min.y ))/1.5; | |
//console.log(this.helper.size());//this.highlight.scale.x = | |
this.group.position.x = ease(this.group.position.x, (- this.offsetX) - centerX, 0.05); | |
this.group.position.y = ease(this.group.position.y, -this.offsetY - centerY, 0.05); | |
this.group.position.z = ease(this.group.position.z, -this.characters.length / 2, 0.05); | |
} | |
} | |
}); | |
/*////////////////////////////////////////*/ | |
This file contains hidden or 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
var renderCalls = []; | |
function render () { | |
requestAnimationFrame( render ); | |
renderCalls.forEach((callback)=>{ callback(); }); | |
} | |
render(); | |
/*////////////////////////////////////////*/ | |
var scene = new THREE.Scene(); | |
scene.fog = new THREE.Fog(0xEEEEEE, 30, 300); | |
var camera = new THREE.PerspectiveCamera( 80, window.innerWidth / window.innerHeight, 0.1, 800 ); | |
camera.position.z = 30; | |
camera.position.y = -2; | |
var renderer = new THREE.WebGLRenderer( { antialias: true, alpha: true } ); | |
renderer.setPixelRatio( window.devicePixelRatio ); | |
renderer.setSize( window.innerWidth, window.innerHeight ); | |
renderer.setClearColor( 0xFFFFFF );//0x ); | |
renderer.toneMapping = THREE.LinearToneMapping; | |
renderer.toneMappingExposure = Math.pow( 0.94, 5.0 ); | |
// if ( window.innerWidth > 500 ) { | |
// renderer.shadowMap.enabled = true; | |
// renderer.shadowMap.type = THREE.PCFShadowMap; | |
// } | |
window.addEventListener( 'resize', function () { | |
camera.aspect = window.innerWidth / window.innerHeight; | |
camera.updateProjectionMatrix(); | |
renderer.setSize( window.innerWidth, window.innerHeight ); | |
}, false ); | |
document.body.appendChild( renderer.domElement); | |
function renderScene(){ renderer.render( scene, camera ); } | |
renderCalls.push(renderScene); | |
/*////////////////////////////////////////*/ | |
function convertImage(src,callback){ | |
var img = new Image(); | |
img.onload = callback || buildBoxes; | |
img.src = ( src.target ? src.target.result : src ); | |
} | |
// Convert image to canvas ImageData | |
function imageToData(img, width, height, offsetX, offsetY) { | |
var canvas = document.createElement("canvas"), | |
ctx = canvas.getContext("2d"); | |
offsetX = offsetX || 0; | |
offsetY = offsetY || 0; | |
width = width || img.width; | |
height = height || img.height; | |
canvas.width = width; | |
canvas.height = height; | |
ctx.drawImage(img, offsetX, offsetY); | |
return ctx.getImageData(0,0,width,height); | |
} | |
function getPixels(img) { | |
var pixels = [], | |
data = img.data, | |
len = data.length, | |
w = img.width, | |
h = img.height, | |
x = 0, | |
y = 0, | |
i = 0; | |
for (; i < len; i+= 4) { | |
if ( data[i+3] > 0 ) { | |
x = (i / 4) % w; | |
y = Math.floor((i / 4) / w); | |
pixels.push([x,y]); | |
} | |
} | |
return pixels; | |
} | |
function getColors(img) { | |
var colors = {}, | |
data = img.data, | |
len = data.length, | |
w = img.width, | |
h = img.height, | |
x = 0, | |
y = 0, | |
i = 0, | |
color; | |
for (; i < len; i+= 4) { | |
if ( data[i+3] > 0 ) { | |
color = 'rgb('+data[i]+','+data[i+1]+','+data[i+2]+')' //,'+data[i+3]/255+')'; | |
if ( !colors[color] ) { | |
colors[color] = []; | |
colors[color].r = data[i]; | |
colors[color].g = data[i+1]; | |
colors[color].b = data[i+2]; | |
colors[color].a = data[i+3] / 255; | |
colors[color].y = 0.299*data[i] + 0.587*data[i+1] + 0.114*data[i+2]; | |
} | |
x = (i / 4) % w; | |
y = Math.floor((i / 4) / w); | |
colors[color].push([x,y]); | |
} | |
} | |
return colors; | |
} | |
/*////////////////////////////////////////*/ | |
var poxelGeometry = new THREE.BoxGeometry( 1, 1, 1 ); | |
function Poxel(color){ | |
var material = new THREE.MeshPhongMaterial({ | |
color: 0xFF0000, //color || '#F00', | |
shininess: 30 | |
}); | |
THREE.Mesh.call( this, poxelGeometry, material); | |
this.castShadow = true; | |
this.receiveShadow = true; | |
return this; | |
} | |
CustomBounce.create("myBounce", {strength: 0.2, squash:0 }); | |
Poxel.prototype = Object.assign(Object.create(THREE.Mesh.prototype), { | |
constructor: Poxel, | |
transitionOut(speed){ | |
speed = speed || 0.3 + Math.random() * 0.3; | |
let tl = new TimelineLite({ | |
onStart: ()=> { this.animating = true; }, | |
onComplete: ()=> { this.animating = false; } | |
}); | |
this.material = this.material.clone(); | |
this.material.transparent = true; | |
tl.to(this.material, speed * 0.5, { | |
opacity: 0, | |
delay: speed * 0.5, | |
ease: 'Power1.easeIn' | |
}); | |
tl.to(this.position, speed, { | |
z: -100, | |
delay: Math.random(), | |
ease: 'Power3.easeIn' //Bounce.easeOut | |
},0); | |
return tl; | |
}, | |
transitionIn(speed){ | |
speed = speed || 0.4 + Math.random() * 0.3; | |
return TweenLite.from(this.position, speed, { | |
z: 100, | |
ease: 'myBounce', | |
onStart: ()=> { this.animating = true; }, | |
onComplete: ()=> { this.animating = false; } | |
},0); | |
} | |
}); | |
/*////////////////////////////////////////*/ | |
let characterGroup = new THREE.Group(); | |
scene.add(characterGroup); | |
var font = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJQAAAAFAQMAAACZyFdxAAAABlBMVEUmRckAAADvxClRAAAAAXRSTlMAQObYZgAAAG5JREFUeAFjyMnJy3q04rOwYO/VwNCiM+vevXvAsKqro8tpxapVKwOmhoaKKa1oWrWA4U3XmXVeJ1atXukxNVRdye3do3cLQOpWeQHVabZMDQ10alJasmgBw5qcF6+erOuaJBsxpyus6J3Ok3cPACq+NLD+sVySAAAAAElFTkSuQmCC'; | |
let fontImage = new Image(); | |
fontImage.src = font; | |
function Character(name,opts){ | |
THREE.Object3D.call(this); | |
this.name = name; | |
for (var key in opts){ this[key] = opts[key]; } | |
this.buildPoxels(); | |
} | |
Character.prototype = Object.assign(Object.create(THREE.Object3D.prototype), { | |
constructor: Character, | |
spriteWidth: 3, | |
spriteHeight: 5, | |
spriteOffset: 0, | |
spriteImg: fontImage, | |
buildPoxels(img){ | |
img = img || this.spriteImg; | |
let data = imageToData(img, this.spriteWidth, this.spriteHeight, -this.spriteOffset); | |
let pixels = getPixels(data); | |
let i = pixels.length; | |
let poxel = new Poxel('#F00'); | |
while( i-- ){ | |
let pixel = pixels[i]; | |
let pixelCube = poxel.clone(); | |
pixelCube.position.set( | |
pixel[0],// - (this.spriteWidth/2), | |
-pixel[1],// + (this.spriteHeight/2), | |
0 | |
); | |
this.add(pixelCube); | |
} | |
}, | |
transitionIn(delay, onComplete){ | |
onComplete = onComplete || function(){}; | |
let tl = new TimelineLite({ | |
onStart: ()=>{ this.animating = true; }, | |
onComplete: ()=>{ | |
this.animating = false; | |
onComplete.call(this,this); | |
}, | |
delay: delay | |
}); | |
this.traverseVisible((child)=>{ | |
if ( child !== this ) { | |
tl.add(child.transitionIn(),0); | |
} | |
}); | |
return tl; | |
}, | |
transitionOut(delay, onComplete){ | |
onComplete = onComplete || function(){}; | |
let tl = new TimelineLite({ | |
onStart: ()=>{ this.animating = true; }, | |
onComplete: ()=>{ | |
this.animating = false; | |
onComplete.call(this,this); | |
}, | |
delay: delay | |
}); | |
this.traverseVisible((child)=>{ | |
if ( child !== this ) { | |
tl.add(child.transitionOut(),0); | |
} | |
}); | |
return tl; | |
} | |
}); | |
/*////////////////////////////////////////*/ | |
var characters = { | |
A: { spriteOffset: 0 }, | |
B: { spriteOffset: 4 }, | |
C: { spriteOffset: 8 }, | |
D: { spriteOffset: 12 }, | |
E: { spriteOffset: 16 }, | |
F: { spriteOffset: 20 }, | |
G: { spriteOffset: 24 }, | |
H: { spriteOffset: 28 }, | |
I: { spriteOffset: 32 }, | |
J: { spriteOffset: 36 }, | |
K: { spriteOffset: 40 }, | |
L: { spriteOffset: 44 }, | |
M: { spriteOffset: 48, spriteWidth: 5 }, | |
N: { spriteOffset: 54 }, | |
O: { spriteOffset: 58 }, | |
P: { spriteOffset: 62 }, | |
Q: { spriteOffset: 66, spriteWidth: 4 }, | |
R: { spriteOffset: 71 }, | |
S: { spriteOffset: 75 }, | |
T: { spriteOffset: 79 }, | |
U: { spriteOffset: 83 }, | |
V: { spriteOffset: 87 }, | |
W: { spriteOffset: 91, spriteWidth: 5 }, | |
X: { spriteOffset: 97 }, | |
Y: { spriteOffset: 101 }, | |
Z: { spriteOffset: 105 }, | |
1: { spriteOffset: 109, spriteWidth: 2, }, | |
2: { spriteOffset: 112 }, | |
3: { spriteOffset: 116 }, | |
4: { spriteOffset: 120 }, | |
5: { spriteOffset: 124 }, | |
6: { spriteOffset: 128 }, | |
7: { spriteOffset: 132 }, | |
8: { spriteOffset: 136 }, | |
9: { spriteOffset: 140 }, | |
0: { spriteOffset: 144 }, | |
SPACE: { spriteOffset: 148 } | |
}; | |
// convertImage(font, function(){ | |
// let img = this; | |
// this; | |
// let offset = 0; | |
// let i = 0; | |
// let tl = new TimelineMax({ repeat: -1, yoyo: true, paused: true }); | |
// for (var key in characters){ | |
// offset = characters[key].spriteOffset; | |
// //let char = new Character(key, characters[key]) | |
// //characters[key] = char; | |
// //scene.add(char); | |
// let char2 = makeCharacter(key); | |
// char2.position.x = offset; | |
// char2.position.y = -6; | |
// scene.add(char2); | |
// //tl.add(char2.transitionIn(i * 0.2),0); | |
// i++; | |
// } | |
// console.log(tl.duration()); | |
// // tl.to(scene.position, tl.duration() + 0.3, { | |
// // x: -offset, | |
// // delay: 0.3, | |
// // ease: Power1.easeInOut | |
// // },0); | |
// tl.play(); | |
// }); | |
function makeCharacter(key){ | |
//offset = characters[key].spriteOffset; | |
let char = new Character(key, characters[key]); | |
return char; | |
} | |
/*////////////////////////////////////////*/ | |
// var pointer = { | |
// x: 0, | |
// y: 0, | |
// }; | |
// document.addEventListener('mousemove', pointerMove); | |
// document.addEventListener('touchmove', pointerMove); | |
// document.body.addEventListener('mouseleave', pointerReset); | |
// document.body.addEventListener('touchcancel', pointerReset); | |
// function pointerReset(e){ | |
// pointer.x = 0.5; | |
// pointer.y = 0.5; | |
// } | |
// function pointerMove(e){ | |
// let pos = e.touches ? e.touches[0] : e; | |
// pointer.x = pos.clientX / window.innerWidth; | |
// pointer.y = pos.clientY / window.innerWidth; | |
// }; | |
// let mousePos = { x: 0.5, y: 0.5, _x: 0.5, _y: 0.5 }; | |
// function trackMouse(e){ | |
// let pointer = e.touches ? e.touches[0] : e; | |
// mousePos.x = ( pointer.clientX / window.innerWidth ); | |
// mousePos.y = ( pointer.clientY / window.innerHeight ); | |
// }; | |
// function ease(current,target,ease){ return current + (target - current) * ( ease || 0.2 ); } | |
// function updateCamera(){ | |
// mousePos._x = ease(mousePos._x || 0.5, mousePos.x, 0.06); | |
// mousePos._y = ease(mousePos._y || 0.5, mousePos.y, 0.06); | |
// camera.position.x = (-25 * (mousePos._x - 0.5) * 2); | |
// camera.position.y = (25 * (mousePos._y - 0.5) * 2); | |
// camera.lookAt(new THREE.Vector3(0,0,0)); | |
// } | |
// updateCamera(); | |
// window.addEventListener('mousemove', trackMouse); | |
// renderCalls.push(updateCamera); | |
/*////////////////////////////////////////*/ | |
/*////////////////////////////////////////*/ | |
let orbit = new THREE.OrbitControls(camera, renderer.domElement); | |
// orbit.enableRotate = false; | |
// orbit.enablePan = false; | |
// orbit.enableKeys = false; | |
orbit.zoomSpeed = 0.6; | |
orbit.minDistance = 10; | |
/*////////////////////////////////////////*/ | |
var ambientLight = new THREE.AmbientLight(0x222222); | |
scene.add(ambientLight); | |
var hemiLight = new THREE.HemisphereLight( 0xFFF7EB, 0xEBF7FD, 0.3 ); | |
scene.add( hemiLight ); | |
var light = new THREE.SpotLight( 0xffffff ); | |
light.position.y = 40; | |
light.position.x = 0; | |
light.position.z = 200; | |
light.castShadow = true; | |
light.shadow.mapSize.width = 1024; | |
light.shadow.mapSize.height = 1024; | |
light.shadow.camera.near = 1; | |
light.shadow.camera.far = 800; | |
light.shadow.camera.fov = 40; | |
light.power = 2.5; | |
scene.add(light); | |
renderCalls.push(()=>{ | |
light.position.copy(camera.position); | |
}); | |
var light2 = new THREE.PointLight( 0xffffff ); | |
light2.position.y = 40; | |
light2.position.x = 0; | |
light2.position.z = 200; | |
light2.castShadow = true; | |
light2.power = 1.5; | |
scene.add(light); | |
var axisHelper = new THREE.AxisHelper( 30 ); | |
scene.add( axisHelper ); | |
// The X axis is red. The Y axis is green. The Z axis is blue. | |
/*////////////////////////////////////////*/ | |
/* | |
let controls = { | |
radius: 4, | |
amount: 4.5, | |
type: 'attract', | |
methods: { | |
attract: function(object, pos){ | |
let dist = distance(object.position, pos); | |
object.__position = object.__position || object.position.clone(); | |
object.position.z = ( dist < controls.radius ? controls.amount * (controls.radius-dist)/controls.radius / object.scale.z : 0 ); | |
}, | |
// loosely based on http://jsfiddle.net/225cb9d7/3/ | |
repel: function(object, pos){ | |
let x0 = object.position.x; | |
let y0 = object.position.y; | |
let diffX = pos.x - x0; | |
let diffY = pos.y - y0; | |
let dist = Math.sqrt( diffX * diffX + diffY * diffY);// + diffZ * diffZ); | |
// Save copy of original position | |
object.__position = object.__position || object.position.clone(); | |
let powerx = x0 - ( diffX / dist) * (controls.amount/2) / dist; | |
let powery = y0 - ( diffY / dist) * (controls.amount/2) / dist; | |
let forcex = ( object.__position.x - x0); //(forcex + ( object.__position.x - x0) / 2) / 2.1; | |
let forcey = ( object.__position.y - y0); //(forcey + ( object.__position.y - y0) / 2) / 2.1; | |
object.position.x = powerx + forcex; | |
object.position.y = powery + forcey; | |
object.position.z = ( dist < controls.radius ? -controls.amount * 2 * (controls.radius-dist)/controls.radius / object.scale.z : 0 ); | |
} | |
} | |
}; | |
var gui = new dat.GUI(); | |
gui.add(controls, 'type', Object.keys(controls.methods)); | |
gui.add(controls, 'radius', 1, 20).step(0.5); | |
gui.add(controls, 'amount', 0.25, 20).step(0.25); | |
gui.domElement.style.zIndex = 20; | |
/*////////////////////////////////////////*/ | |
/* | |
function distance(pos1, pos2){ | |
let diffX = pos2.x - pos1.x, | |
diffY = pos2.y - pos1.y;//, | |
//diffZ = pos2.z - pos1.z; | |
return Math.sqrt( diffX * diffX + diffY * diffY);// + diffZ * diffZ); | |
} | |
let mouse = new THREE.Vector3(); | |
let raycaster = new THREE.Raycaster(); | |
const TWOPI = Math.PI * 2; | |
document.addEventListener('mousemove',click); | |
document.addEventListener('touchmove',click); | |
function click(e){ | |
if ( animating || animatingOut || !group ) { return; } | |
let pointer = e.touches ? e.touches : [e]; | |
for (let i = 0, len = pointer.length; i < len; i++){ | |
let event = pointer[i]; | |
mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1; | |
mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1; | |
mouse.unproject( camera ); | |
let dir = mouse.sub( camera.position ).normalize(); | |
let dist = -camera.position.z / dir.z; | |
let pos = camera.position.clone().add( dir.multiplyScalar( dist ) ); | |
group.traverseVisible((object)=>{ | |
if ( object !== group ) { | |
controls.methods[controls.type](object, pos); | |
} | |
}); | |
} | |
} | |
/*////////////////////////////////////////*/ | |
function ease(current,target,ease){ return current + (target - current) * ( ease || 0.2 ); } | |
var el = document.createElement('div'); | |
document.body.appendChild(el); | |
var inputter = new Vue({ | |
el: el, | |
data: { | |
message: '', | |
chars: [], | |
}, | |
template: `<textarea ref="input" type="text" v-model:value="message" style="position: absolute; bottom: 0; left: 0;" />`, | |
mounted(){ | |
this.$refs.input.focus(); | |
this.message = 'Hello'; | |
renderCalls.push(()=>{ | |
characterGroup.position.x = ease(characterGroup.position.x, -this.getPosition() + 3, 0.1); | |
}); | |
}, | |
watch: { | |
message: function(val, oldVal){ | |
let delay = 0; | |
let len = val.length; | |
let i = oldVal.length; | |
let tl = new TimelineLite(); | |
if ( len > i ) { | |
let arr = val.split(''); | |
for (; i < len; i++){ | |
tl.add(this.addChar(arr[i]) || function(){}, delay += 0.05 ); | |
} | |
} else { | |
for (; i > len; i--){ | |
let char = this.chars.pop(); | |
tl.add(this.removeChar(char) || function(){}, delay += 0.1 ); | |
} | |
} | |
// let dur = Math.max(tl.duration(), 0.4); //, 1); | |
// tl.to(scene.position, dur * 1.1, { | |
// x: , | |
// ease: 'Power2.easeInOut' | |
// },dur * 0.1); | |
}, | |
}, | |
methods: { | |
addChar(char, delay){ | |
if ( char === ' ' ) { char = 'SPACE'; } | |
char = char.toUpperCase(); | |
console.log(char); | |
if ( characters[char] ) { | |
let character = makeCharacter(char); | |
character.position.x = this.getPosition(); | |
let tl = character.transitionIn(delay); | |
characterGroup.add(character); | |
this.chars.push(character); | |
return tl; | |
} | |
}, | |
removeChar(char, delay){ | |
if ( char && char.transitionOut ) { | |
return char.transitionOut(delay,(char)=>{ characterGroup.remove(char); }); | |
} | |
}, | |
getPosition(){ | |
let offset = 0; | |
this.chars.forEach((char)=>{ | |
offset += char.spriteWidth + 1; | |
}); | |
return offset; | |
} | |
// keydown(e){ | |
// let char = e.key.toUpperCase(); //String.fromCharCode( e.which ).toUpperCase(); | |
// if ( characters[char] ) { | |
// let character = characters[char].clone(); | |
// character.position.x = this.message.length; | |
// //character.position.y = -5; | |
// character.transitionIn(); | |
// scene.add(character); | |
// console.log(char, this.message); | |
// } | |
// } | |
} | |
}) |
This file contains hidden or 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
<script src="http://codepen.io/shshaw/pen/epmrgO"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.6.1/dat.gui.min.js"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/1.19.0/TweenMax.min.js"></script> | |
<script src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/16327/CustomEase.min.js"></script> | |
<script src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/16327/CustomBounce.min.js"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r79/three.min.js"></script> | |
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.0.1/vue.min.js"></script> | |
<script src="https://threejs.org/examples/js/controls/TrackballControls.js"></script> |
This file contains hidden or 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
canvas { display: block; } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment