Skip to content

Instantly share code, notes, and snippets.

@AdmiralPotato
Last active March 8, 2019 23:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save AdmiralPotato/071919bb5716ceed0fe6cdfe85c35f01 to your computer and use it in GitHub Desktop.
Save AdmiralPotato/071919bb5716ceed0fe6cdfe85c35f01 to your computer and use it in GitHub Desktop.
Wiggly Things

Wiggly Things

I have no idea what I'm doing, but I guess you can design your own WiggleMonsters by clicking on their segments.

<!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/three@0.102.1/build/three.js"></script>
<script src="https://unpkg.com/three@0.102.1/examples/js/controls/OrbitControls.js"></script>
<script src="https://unpkg.com/three@0.102.1/examples/js/utils/BufferGeometryUtils.js"></script>
<script src="./wiggly_thing.js"></script>
</body>
</html>
* {
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;
}
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