Created
September 4, 2024 06:33
-
-
Save bfollington/4080172841cc5f24ef7fc0c5acf472c1 to your computer and use it in GitHub Desktop.
Sticker Interaction
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.0"> | |
<title>Holographic Stickers with Improved Lighting</title> | |
<style> | |
body { margin: 0; overflow: hidden; } | |
canvas { display: block; } | |
</style> | |
</head> | |
<body> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.9.1/gsap.min.js"></script> | |
<script> | |
let camera, scene, renderer, raycaster, mouse, floor; | |
let draggingSticker = null; | |
const stickers = []; | |
let lastMousePosition = new THREE.Vector2(); | |
let mouseVelocity = new THREE.Vector2(); | |
const holographicVertexShader = ` | |
varying vec2 vUv; | |
varying vec3 vNormal; | |
varying vec3 vViewPosition; | |
void main() { | |
vUv = uv; | |
vNormal = normalize(normalMatrix * normal); | |
vec4 mvPosition = modelViewMatrix * vec4(position, 1.0); | |
vViewPosition = -mvPosition.xyz; | |
gl_Position = projectionMatrix * mvPosition; | |
} | |
`; | |
const holographicFragmentShader = ` | |
uniform vec3 baseColor; | |
varying vec2 vUv; | |
varying vec3 vNormal; | |
varying vec3 vViewPosition; | |
vec3 mod289(vec3 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; } | |
vec2 mod289(vec2 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; } | |
vec3 permute(vec3 x) { return mod289(((x*34.0)+1.0)*x); } | |
float snoise(vec2 v) { | |
const vec4 C = vec4(0.211324865405187, 0.366025403784439, | |
-0.577350269189626, 0.024390243902439); | |
vec2 i = floor(v + dot(v, C.yy)); | |
vec2 x0 = v - i + dot(i, C.xx); | |
vec2 i1; | |
i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0); | |
vec4 x12 = x0.xyxy + C.xxzz; | |
x12.xy -= i1; | |
i = mod289(i); | |
vec3 p = permute( permute( i.y + vec3(0.0, i1.y, 1.0)) + i.x + vec3(0.0, i1.x, 1.0 )); | |
vec3 m = max(0.5 - vec3(dot(x0,x0), dot(x12.xy,x12.xy), dot(x12.zw,x12.zw)), 0.0); | |
m = m*m ; | |
m = m*m ; | |
vec3 x = 2.0 * fract(p * C.www) - 1.0; | |
vec3 h = abs(x) - 0.5; | |
vec3 ox = floor(x + 0.5); | |
vec3 a0 = x - ox; | |
m *= 1.79284291400159 - 0.85373472095314 * ( a0*a0 + h*h ); | |
vec3 g; | |
g.x = a0.x * x0.x + h.x * x0.y; | |
g.yz = a0.yz * x12.xz + h.yz * x12.yw; | |
return 130.0 * dot(m, g); | |
} | |
void main() { | |
vec3 normal = normalize(vNormal); | |
vec3 viewDir = normalize(vViewPosition); | |
float fresnelTerm = dot(normal, viewDir); | |
fresnelTerm = clamp(1.0 - fresnelTerm, 0.0, 1.0); | |
fresnelTerm = pow(fresnelTerm, 3.0); | |
float noiseValue = snoise(vUv * 10.0); | |
vec3 rainbow = 0.5 + 0.5 * cos(2.0 * 3.14159 * (vec3(0.0, 0.33, 0.67) + fresnelTerm + noiseValue)); | |
vec3 reflection = normalize(reflect(-viewDir, normal)); | |
float specular = max(0.0, dot(normal, reflection)); | |
specular = pow(specular, 20.0); | |
gl_FragColor = vec4(mix(baseColor, rainbow, 0.8) + specular, 1.0); | |
} | |
`; | |
function init() { | |
scene = new THREE.Scene(); | |
camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000); | |
camera.position.set(0, 10, 10); | |
camera.lookAt(0, 0, 0); | |
renderer = new THREE.WebGLRenderer({ antialias: true }); | |
renderer.setSize(window.innerWidth, window.innerHeight); | |
renderer.shadowMap.enabled = true; | |
document.body.appendChild(renderer.domElement); | |
// Ambient light | |
const ambientLight = new THREE.AmbientLight(0x404040); | |
scene.add(ambientLight); | |
// Cross-angled, colored lights | |
const light1 = new THREE.DirectionalLight(0xffcccc, 0.5); // Warm light | |
light1.position.set(5, 10, 5); | |
light1.castShadow = true; | |
scene.add(light1); | |
const light2 = new THREE.DirectionalLight(0xccccff, 0.5); // Cool light | |
light2.position.set(-5, 10, -5); | |
light2.castShadow = true; | |
scene.add(light2); | |
floor = new THREE.Mesh( | |
new THREE.PlaneGeometry(20, 20), | |
new THREE.MeshPhongMaterial({ color: 0x222222 }) | |
); | |
floor.rotation.x = -Math.PI / 2; | |
floor.receiveShadow = true; | |
scene.add(floor); | |
raycaster = new THREE.Raycaster(); | |
mouse = new THREE.Vector2(); | |
createCircleSticker(-3, 0.01, 0, new THREE.Color(0xff0000)); | |
createTriangleSticker(0, 0.01, 0, new THREE.Color(0x00ff00)); | |
createDiamondSticker(3, 0.01, 0, new THREE.Color(0x0000ff)); | |
window.addEventListener('mousemove', onMouseMove); | |
window.addEventListener('mousedown', onMouseDown); | |
window.addEventListener('mouseup', onMouseUp); | |
window.addEventListener('resize', onWindowResize); | |
} | |
function createHolographicMaterial(baseColor) { | |
return new THREE.ShaderMaterial({ | |
uniforms: { | |
baseColor: { value: baseColor } | |
}, | |
vertexShader: holographicVertexShader, | |
fragmentShader: holographicFragmentShader, | |
side: THREE.DoubleSide | |
}); | |
} | |
function createCircleSticker(x, y, z, color) { | |
const geometry = new THREE.CircleGeometry(1, 32); | |
const material = createHolographicMaterial(color); | |
createSticker(geometry, material, x, y, z); | |
} | |
function createTriangleSticker(x, y, z, color) { | |
const shape = new THREE.Shape(); | |
shape.moveTo(0, 1); | |
shape.lineTo(-1, -1); | |
shape.lineTo(1, -1); | |
shape.lineTo(0, 1); | |
const geometry = new THREE.ShapeGeometry(shape); | |
const material = createHolographicMaterial(color); | |
createSticker(geometry, material, x, y, z); | |
} | |
function createDiamondSticker(x, y, z, color) { | |
const shape = new THREE.Shape(); | |
shape.moveTo(0, 1); | |
shape.lineTo(1, 0); | |
shape.lineTo(0, -1); | |
shape.lineTo(-1, 0); | |
shape.lineTo(0, 1); | |
const geometry = new THREE.ShapeGeometry(shape); | |
const material = createHolographicMaterial(color); | |
createSticker(geometry, material, x, y, z); | |
} | |
function createSticker(geometry, material, x, y, z) { | |
const sticker = new THREE.Mesh(geometry, material); | |
sticker.position.set(x, y, z); | |
sticker.rotation.x = -Math.PI / 2; | |
sticker.castShadow = true; | |
sticker.receiveShadow = true; | |
scene.add(sticker); | |
stickers.push(sticker); | |
} | |
function onMouseMove(event) { | |
mouse.x = (event.clientX / window.innerWidth) * 2 - 1; | |
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1; | |
mouseVelocity.x = mouse.x - lastMousePosition.x; | |
mouseVelocity.y = mouse.y - lastMousePosition.y; | |
lastMousePosition.copy(mouse); | |
if (draggingSticker) { | |
raycaster.setFromCamera(mouse, camera); | |
const intersects = raycaster.intersectObject(floor); | |
if (intersects.length > 0) { | |
const targetPosition = intersects[0].point.clone(); | |
targetPosition.y = 1; | |
gsap.to(draggingSticker.position, { | |
x: targetPosition.x, | |
y: targetPosition.y, | |
z: targetPosition.z, | |
duration: 0.3, | |
ease: "power2.out" | |
}); | |
// Wobbly spring effect | |
const springStrength = 0.05; | |
const dampingFactor = 0.8; | |
const tiltX = -mouseVelocity.y * 2 * springStrength; | |
const tiltZ = mouseVelocity.x * 2 * springStrength; | |
gsap.to(draggingSticker.rotation, { | |
x: -Math.PI / 2 + tiltX, | |
z: tiltZ, | |
duration: 0.5, | |
ease: "elastic.out(1.5, 0.3)" | |
}); | |
} | |
} | |
} | |
function onMouseDown(event) { | |
raycaster.setFromCamera(mouse, camera); | |
const intersects = raycaster.intersectObjects(stickers); | |
if (intersects.length > 0) { | |
draggingSticker = intersects[0].object; | |
gsap.to(draggingSticker.position, { | |
y: 1, | |
duration: 0.2, | |
ease: "back.out(2)" | |
}); | |
} | |
} | |
function onMouseUp() { | |
if (draggingSticker) { | |
gsap.to(draggingSticker.position, { | |
y: 0.01, | |
duration: 0.5, | |
ease: "bounce.out" | |
}); | |
gsap.to(draggingSticker.rotation, { | |
x: -Math.PI / 2, | |
z: 0, | |
duration: 0.5, | |
ease: "power2.out" | |
}); | |
draggingSticker = null; | |
} | |
} | |
function onWindowResize() { | |
camera.aspect = window.innerWidth / window.innerHeight; | |
camera.updateProjectionMatrix(); | |
renderer.setSize(window.innerWidth, window.innerHeight); | |
} | |
function animate() { | |
requestAnimationFrame(animate); | |
renderer.render(scene, camera); | |
} | |
init(); | |
animate(); | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment