I have no idea what I'm doing, but I guess you can design your own WiggleMonsters by clicking on their segments.
Last active
March 8, 2019 23:03
-
-
Save AdmiralPotato/071919bb5716ceed0fe6cdfe85c35f01 to your computer and use it in GitHub Desktop.
Wiggly Things
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
.idea | |
*.iml |
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
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8" /> | |
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" /> | |
<link rel="stylesheet" href="./styles.css" /> | |
<title>Wiggly Thing</title> | |
</head> | |
<body> | |
<canvas id="3d"></canvas> | |
<div class="controls"> | |
<button data-action="pause">Pause</button> | |
<button data-action="clear">Clear</button> | |
<button data-action="reset">Reset</button> | |
</div> | |
<script src="https://unpkg.com/[email protected]/build/three.js"></script> | |
<script src="https://unpkg.com/[email protected]/examples/js/controls/OrbitControls.js"></script> | |
<script src="https://unpkg.com/[email protected]/examples/js/utils/BufferGeometryUtils.js"></script> | |
<script src="./wiggly_thing.js"></script> | |
</body> | |
</html> |
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
* { | |
margin: 0; | |
padding: 0; | |
border: 0; | |
outline: 0; | |
} | |
html, body{ | |
height: 100%; | |
} | |
body { | |
background-color: #000; | |
position: relative; | |
} | |
canvas { | |
display: block; | |
position: absolute; | |
top: 0; | |
left: 0; | |
width: 100%; | |
height: 100%; | |
} | |
.controls { | |
position: absolute; | |
top: 16px; | |
left: 16px; | |
width: 128px; | |
} | |
.controls button { | |
display: block; | |
width: 100%; | |
font-size: 16px; | |
line-height: 16px; | |
padding: 5px; | |
margin: 2px; | |
border-radius: 4px; | |
background-color: rgba(255, 255, 255, 0.06); | |
border: 1px solid #6a0; | |
color: #6a0; | |
} | |
.controls button:hover { | |
background-color: rgba(255, 255, 255, 0.1); | |
border: 1px solid #9f0; | |
color: #9f0; | |
} | |
.controls button:active { | |
background-color: rgba(255, 255, 255, 0.2); | |
border: 1px solid #ff0; | |
color: #ff0; | |
} |
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 THREE = window.THREE | |
const tau = Math.PI * 2 | |
const canvas = document.getElementById('3d') | |
const scene = new THREE.Scene() | |
const renderer = new THREE.WebGLRenderer({ | |
antialias: true, | |
canvas | |
}) | |
renderer.setPixelRatio(window.devicePixelRatio) | |
renderer.setSize(window.innerWidth, window.innerHeight) | |
const camera = new THREE.PerspectiveCamera( | |
60, | |
window.innerWidth / window.innerHeight, | |
1, | |
1000 | |
) | |
camera.position.set(0, 0, 40) | |
// controls | |
const controls = new THREE.OrbitControls(camera, canvas) | |
controls.enableKeys = true | |
controls.minDistance = 2 | |
controls.maxDistance = 80 | |
controls.maxPolarAngle = Math.PI | |
const material = new THREE.MeshLambertMaterial({ color: 0xffffff, emissive: 0x000000 }) | |
const coneGeometry = new THREE.CylinderBufferGeometry(0, 0.2, 1, 4, 1) | |
coneGeometry.applyMatrix( | |
new THREE.Matrix4().makeTranslation(0, 0.5, 0) | |
) | |
const sphereGeometry = new THREE.SphereBufferGeometry(0.1, 4, 2) | |
sphereGeometry.applyMatrix( | |
new THREE.Matrix4().makeTranslation(0, 1, 0) | |
) | |
const mergedGeometry = THREE.BufferGeometryUtils.mergeBufferGeometries([ | |
coneGeometry, | |
sphereGeometry | |
]) | |
const originalWiggle = new THREE.Mesh(mergedGeometry, material) | |
let wiggles = [] | |
const makeWiggle = ({ parent, position }) => { | |
const newWiggle = originalWiggle.clone() | |
newWiggle.material = material.clone() | |
newWiggle.itersectable = true | |
position && newWiggle.position.set(...position) | |
parent && parent.add(newWiggle) | |
wiggles.push(newWiggle) | |
return newWiggle | |
} | |
const makeWiggleChain = (segmentCount) => { | |
let lastWiggle | |
let firstWiggle | |
for (let i = 0; i < segmentCount; i++) { | |
const currentWiggle = makeWiggle({ | |
parent: lastWiggle, | |
position: lastWiggle ? [0, 1, 0] : undefined | |
}) | |
if (!lastWiggle) { | |
firstWiggle = currentWiggle | |
} | |
lastWiggle = currentWiggle | |
} | |
return firstWiggle | |
} | |
const crawl = (target, func) => { | |
if (target.children.length) { | |
target.children.forEach((child) => { | |
crawl(child, func) | |
}) | |
} | |
func(target) | |
} | |
const makeWiggleMonster = (legCount, legSegmentCount) => { | |
const legFraction = 1 / legCount | |
const legSegmentFraction = 1 / legSegmentCount | |
const axis = new THREE.Vector3(1, 1, 1).normalize() | |
const mesh = new THREE.Object3D() | |
let legs = [] | |
for (let i = 0; i < legCount; i++) { | |
const leg = makeWiggleChain(legSegmentCount) | |
leg.rotation.z = legFraction * i * tau | |
legs.push(leg) | |
mesh.add(leg) | |
} | |
return { | |
mesh, | |
update: (miliseconds) => { | |
const phase = (miliseconds / 1000) / 15 | |
legs.forEach((leg, legIndex) => { | |
const legPhaseOffset = legIndex * legFraction | |
let segmentIndex = 1 | |
leg.material.color.setHSL(phase + legPhaseOffset, 1, 0.5) | |
leg.children.forEach((child) => crawl( | |
child, | |
(segment) => { | |
const segmentPhaseOffset = segmentIndex * legSegmentFraction | |
const phasePlusOffsets = phase + legPhaseOffset + (segmentPhaseOffset * 0.5) | |
const angle = Math.PI * 0.1 * Math.cos(tau * phasePlusOffsets) | |
segment.quaternion.setFromAxisAngle(axis, angle) | |
if (segment.material) { | |
segment.material.color.setHSL(phase + legPhaseOffset + (segmentPhaseOffset * 0.1), 1, 0.5) | |
} | |
segmentIndex += 1 | |
} | |
)) | |
}) | |
} | |
} | |
} | |
const makeBigWiggleMonster = () => { | |
const wiggleMonster = makeWiggleMonster(6, 8) | |
crawl(wiggleMonster.mesh, (mesh) => { | |
if (!mesh.children.length) { | |
const wiggleMonster = makeWiggleMonster(3, 8) | |
crawl(wiggleMonster.mesh, (mesh) => { | |
if (!mesh.children.length) { | |
const wiggleMonster = makeWiggleMonster(2, 8) | |
crawl(wiggleMonster.mesh, (mesh) => { | |
if (!mesh.children.length) { | |
const wiggleMonster = makeWiggleMonster(2, 8) | |
wiggleMonster.mesh.position.set(0, 1, 0) | |
mesh.add(wiggleMonster.mesh) | |
} | |
}) | |
wiggleMonster.mesh.position.set(0, 1, 0) | |
mesh.add(wiggleMonster.mesh) | |
} | |
}) | |
wiggleMonster.mesh.position.set(0, 1, 0) | |
mesh.add(wiggleMonster.mesh) | |
} | |
}) | |
return wiggleMonster | |
} | |
let wiggleMonster = makeBigWiggleMonster() | |
scene.add(wiggleMonster.mesh) | |
const directionalList = new THREE.DirectionalLight(0xcccccc) | |
directionalList.position.set(1, 1, 1) | |
scene.add(directionalList) | |
const ambientLight = new THREE.AmbientLight(0x444444) | |
scene.add(ambientLight) | |
window.addEventListener('resize', onWindowResize, false) | |
function onWindowResize () { | |
camera.aspect = window.innerWidth / window.innerHeight | |
camera.updateProjectionMatrix() | |
renderer.setSize(window.innerWidth, window.innerHeight) | |
} | |
let cursor2d = new THREE.Vector2(-10, -10) | |
const updateCursor = (x, y) => { | |
cursor2d.x = (x / window.innerWidth) * 2 - 1 | |
cursor2d.y = -(y / window.innerHeight) * 2 + 1 | |
} | |
const updateMouse = (event) => { | |
updateCursor( | |
event.clientX, | |
event.clientY | |
) | |
} | |
const updateTouch = (event) => { | |
updateCursor( | |
event.touches[0].clientX, | |
event.touches[0].clientY | |
) | |
intersectCursor2dWithObjects() | |
} | |
const touchEnd = (event) => { | |
updateCursor( | |
-10, | |
-10 | |
) | |
} | |
canvas.addEventListener('mousemove', updateMouse) | |
canvas.addEventListener('touchstart', updateTouch) | |
canvas.addEventListener('touchmove', updateTouch) | |
canvas.addEventListener('touchend', touchEnd) | |
let raycaster = new THREE.Raycaster() | |
let hoveredObject = null | |
let intersectCursor2dWithObjects = () => { | |
raycaster.setFromCamera(cursor2d, camera) | |
const intersects = raycaster.intersectObjects(wiggles) | |
let target = null | |
if (intersects.length > 0) { | |
target = intersects[0].object | |
if (target.itersectable && target !== hoveredObject) { | |
if (hoveredObject) { | |
hoveredObject.material.emissive.setHex(hoveredObject.currentHex) | |
} | |
target.currentHex = target.material.emissive.getHex() | |
target.material.emissive.setHex(0xffffff) | |
hoveredObject = target | |
} | |
} else { | |
if (hoveredObject) { | |
hoveredObject.material.emissive.setHex(hoveredObject.currentHex) | |
} | |
hoveredObject = null | |
} | |
} | |
const addWiggleOnClick = () => { | |
if (hoveredObject) { | |
controls.enabled = false | |
makeWiggle({ | |
parent: hoveredObject, | |
position: [0, 1, 0] | |
}) | |
wiggleMonster.update(progress) | |
} | |
} | |
const enableCameraControl = () => { | |
controls.enabled = true | |
} | |
canvas.addEventListener('mousedown', addWiggleOnClick) | |
canvas.addEventListener('touchstart', addWiggleOnClick) | |
canvas.addEventListener('mouseup', enableCameraControl) | |
canvas.addEventListener('touchstop', enableCameraControl) | |
let go = true | |
let progress = 0 | |
let lastTick = 0 | |
function animate (milliseconds) { | |
const timeDiff = milliseconds - lastTick | |
requestAnimationFrame(animate) | |
intersectCursor2dWithObjects() | |
controls.update() // only required if controls.enableDamping = true, or if controls.autoRotate = true | |
if (go) { | |
progress += timeDiff | |
wiggleMonster.update(progress) | |
} | |
render() | |
lastTick = milliseconds | |
} | |
function render () { | |
renderer.render(scene, camera) | |
} | |
requestAnimationFrame(animate) | |
const buttonActions = { | |
pause (event) { | |
event.target.innerText = go ? 'Resume' : 'Pause' | |
go = !go | |
}, | |
clear (event) { | |
progress = 0 | |
wiggles = [] | |
scene.remove(wiggleMonster.mesh) | |
wiggleMonster = makeWiggleMonster(6, 2) | |
scene.add(wiggleMonster.mesh) | |
wiggleMonster.update(progress) | |
}, | |
reset (event) { | |
progress = 0 | |
wiggles = [] | |
scene.remove(wiggleMonster.mesh) | |
wiggleMonster = makeBigWiggleMonster() | |
scene.add(wiggleMonster.mesh) | |
wiggleMonster.update(progress) | |
} | |
} | |
const buttons = [...document.querySelectorAll('[data-action]')] | |
buttons.forEach((button) => { | |
button.addEventListener('click', buttonActions[button.dataset.action]) | |
}) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment