Last active
June 2, 2026 11:52
-
-
Save gkucmierz/843e36d822e2fc3f14721e835e60ab8b to your computer and use it in GitHub Desktop.
Run this code instantly in your browser: https://instacode.app/gist/843e36d822e2fc3f14721e835e60ab8b
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
| // Dichroic Glass X-Cube Optical Prism with Interactive Drag-to-Rotate | |
| // Built using Three.js inside Instacode Web Worker | |
| import { canvas, onResize, getDisplaySize, onPointerDown, onPointerMove, onPointerUp } from 'canvas'; | |
| import * as THREE from 'three'; | |
| // ========================================== | |
| // CONFIGURATION CONSTANTS (Adjust these to customize your X-Cube!) | |
| // ========================================== | |
| const BG_COLOR = 0xf3f2f8; // Soft light studio background color (#f3f2f8) | |
| const FLOOR_COLOR = 0xe5e4ec; // Light metallic floor color (#e5e4ec) | |
| // Outer Glass Dimensions | |
| const CUBE_SIZE = 80; // Total width/height/depth of the combined cube | |
| // Physical Glass Settings for the 4 Segments | |
| const GLASS_ROUGHNESS = 0.0; // 0.0 = perfect mirror polish | |
| const GLASS_METALNESS = 0.15; // Adds metallic reflection sheen to glass edges | |
| const GLASS_TRANSMISSION = 0.95; // Light pass-through (0.95 = 95% clear glass) | |
| const GLASS_IOR = 1.75; // Index of refraction (higher = more edge distortion) | |
| const GLASS_THICKNESS = 30.0; // Thickness of each segment's glass volume | |
| const GLASS_DISPERSION = 25.0; // Chromatic dispersion (rainbow splitting along edges) | |
| const GLASS_CLEARCOAT = 1.0; // Additional high-gloss protective layer | |
| const GLASS_CLEARCOAT_ROUGH = 0.0; // Glossy finish for the clearcoat layer | |
| // Dichroic Iridescence Settings (rainbow color shifting) | |
| const IRIDESCENCE_INTENSITY = 1.0; // 0.0 = no iridescence, 1.0 = maximum dichroic effect | |
| const IRIDESCENCE_IOR = 2.4; // Refractive index of thin-film layer (higher = more color shifts) | |
| // Colors of the 4 individual glass segments (simulating dichroic filtering per sector) | |
| const SEGMENT_COLORS = [ | |
| 0xff0066, // Segment 1 (Left Back): Magenta/Red bias | |
| 0x00ffcc, // Segment 2 (Right Back): Cyan/Teal bias | |
| 0xffaa00, // Segment 3 (Left Front): Orange/Gold bias | |
| 0x0055ff // Segment 4 (Right Front): Deep Blue bias | |
| ]; | |
| // Internal Mirror Joints (The "X" separator planes) | |
| const JOINT_OPACITY = 0.65; // Opacity of the intersecting dichroic planes | |
| const JOINT_METALNESS = 0.9; // High metalness makes them act like semi-reflective mirrors | |
| const JOINT_COLOR_1 = 0xff00bb; // Joint plane 1 color reflection (Magenta/Violet) | |
| const JOINT_COLOR_2 = 0x00ffaa; // Joint plane 2 color reflection (Teal/Green) | |
| // Scene Lights & Neon Elements | |
| const SHOW_NEON_RODS = false; // Set to true to show the colorful neon rods underneath | |
| const NEON_COLORS = [0xff0055, 0x00ffcc, 0xffcc00, 0x0055ff]; // Floor neon rods color if shown | |
| const AMBIENT_INTENSITY = 0.9; // Ambient background fill light | |
| const SPOT_1_INTENSITY = 12.0; // Key light highlight intensity | |
| const SPOT_2_INTENSITY = 8.0; // Rim light intensity | |
| const SPOT_3_INTENSITY = 8.0; // Bottom highlights intensity | |
| // Animation & Interaction Parameters | |
| const ROTATION_SPEED = 0.35; // Rotation speed multiplier when idle | |
| const FLOAT_SPEED = 0.8; // Levitation speed multiplier | |
| const FLOAT_HEIGHT = 5.0; // Floating amplitude in pixels | |
| // ========================================== | |
| // 1. Get Safe Initial Dimensions (preventing 0px WebGL Framebuffer crashes) | |
| const initSize = getDisplaySize(); | |
| const initWidth = initSize.width > 0 ? initSize.width : 300; | |
| const initHeight = initSize.height > 0 ? initSize.height : 150; | |
| // Setup WebGL Renderer with High-Quality Settings | |
| const renderer = new THREE.WebGLRenderer({ | |
| canvas: canvas, | |
| antialias: true, | |
| alpha: false, | |
| powerPreference: "high-performance" | |
| }); | |
| renderer.setSize(initWidth, initHeight, false); | |
| renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); | |
| renderer.toneMapping = THREE.ACESFilmicToneMapping; | |
| renderer.toneMappingExposure = 1.35; | |
| // 2. Main Scene and Camera Setup | |
| const scene = new THREE.Scene(); | |
| scene.background = new THREE.Color(BG_COLOR); | |
| const camera = new THREE.PerspectiveCamera(38, initWidth / initHeight, 0.1, 1000); | |
| camera.position.set(0, 85, 235); | |
| camera.lookAt(0, -10, 0); | |
| // 3. Create Neon Light Rods on the Floor | |
| const neonGroup = new THREE.Group(); | |
| const rodGeo = new THREE.CylinderGeometry(2.5, 2.5, 180, 8); | |
| for (let i = 0; i < 4; i++) { | |
| const rodMat = new THREE.MeshBasicMaterial({ color: NEON_COLORS[i] }); | |
| const rod = new THREE.Mesh(rodGeo, rodMat); | |
| rod.rotation.z = Math.PI / 2; // Horizontal alignment | |
| rod.position.set(0, -42, (i - 1.5) * 35); // Space them out on the floor | |
| neonGroup.add(rod); | |
| } | |
| if (SHOW_NEON_RODS) { | |
| scene.add(neonGroup); | |
| } | |
| // 4. Floor Plane (Light reflective surface) | |
| const floorGeo = new THREE.PlaneGeometry(400, 400); | |
| const floorMat = new THREE.MeshStandardMaterial({ | |
| color: FLOOR_COLOR, | |
| roughness: 0.15, | |
| metalness: 0.6 | |
| }); | |
| const floor = new THREE.Mesh(floorGeo, floorMat); | |
| floor.rotation.x = -Math.PI / 2; | |
| floor.position.y = -44; // Just underneath the neon rods | |
| scene.add(floor); | |
| // 5. Build the Dichroic X-Cube (4 Glass Segments + Internal Mirror Joints) | |
| const cubeGroup = new THREE.Group(); | |
| const halfSize = CUBE_SIZE / 2; | |
| const segGeo = new THREE.BoxGeometry(halfSize, CUBE_SIZE, halfSize); | |
| // Offset positions for the 4 quadrants | |
| const offsets = [ | |
| { x: -halfSize / 2, z: -halfSize / 2, name: 'left-back' }, | |
| { x: halfSize / 2, z: -halfSize / 2, name: 'right-back' }, | |
| { x: -halfSize / 2, z: halfSize / 2, name: 'left-front' }, | |
| { x: halfSize / 2, z: halfSize / 2, name: 'right-front' } | |
| ]; | |
| // Create the 4 glass blocks representing the quadrants of the X-Cube | |
| offsets.forEach((offset, idx) => { | |
| const segMat = new THREE.MeshPhysicalMaterial({ | |
| color: SEGMENT_COLORS[idx], | |
| roughness: GLASS_ROUGHNESS, | |
| metalness: GLASS_METALNESS, | |
| transmission: GLASS_TRANSMISSION, | |
| ior: GLASS_IOR, | |
| thickness: GLASS_THICKNESS, | |
| dispersion: GLASS_DISPERSION, | |
| clearcoat: GLASS_CLEARCOAT, | |
| clearcoatRoughness: GLASS_CLEARCOAT_ROUGH, | |
| opacity: 1.0, | |
| transparent: true, | |
| side: THREE.DoubleSide, | |
| iridescence: IRIDESCENCE_INTENSITY, | |
| iridescenceIOR: IRIDESCENCE_IOR, | |
| iridescenceThicknessRange: [200 + idx * 100, 500 + idx * 100] // Shift colors slightly for each block | |
| }); | |
| const block = new THREE.Mesh(segGeo, segMat); | |
| block.position.set(offset.x, 0, offset.z); | |
| cubeGroup.add(block); | |
| }); | |
| // C. Intersecting Semi-Reflective Joint Planes (The "X" separator) | |
| const planeGeo = new THREE.PlaneGeometry(CUBE_SIZE - 0.2, CUBE_SIZE - 0.2); | |
| const jointMat1 = new THREE.MeshStandardMaterial({ | |
| color: JOINT_COLOR_1, | |
| roughness: 0.0, | |
| metalness: JOINT_METALNESS, | |
| transparent: true, | |
| opacity: JOINT_OPACITY, | |
| side: THREE.DoubleSide, | |
| depthWrite: false | |
| }); | |
| const jointPlane1 = new THREE.Mesh(planeGeo, jointMat1); | |
| cubeGroup.add(jointPlane1); | |
| const jointMat2 = new THREE.MeshStandardMaterial({ | |
| color: JOINT_COLOR_2, | |
| roughness: 0.0, | |
| metalness: JOINT_METALNESS, | |
| transparent: true, | |
| opacity: JOINT_OPACITY, | |
| side: THREE.DoubleSide, | |
| depthWrite: false | |
| }); | |
| const jointPlane2 = new THREE.Mesh(planeGeo, jointMat2); | |
| jointPlane2.rotation.y = Math.PI / 2; // Perpendicular to jointPlane1 | |
| cubeGroup.add(jointPlane2); | |
| scene.add(cubeGroup); | |
| // 6. Direct Lighting (No environment maps to bypass risky WebGL blit mipmap worker failures) | |
| const ambientLight = new THREE.AmbientLight(0xffffff, AMBIENT_INTENSITY); | |
| scene.add(ambientLight); | |
| // Key light from top-right-front | |
| const spotLight1 = new THREE.SpotLight(0xffffff, SPOT_1_INTENSITY, 400, Math.PI / 4, 0.5, 1.0); | |
| spotLight1.position.set(150, 200, 150); | |
| scene.add(spotLight1); | |
| // Fill light from left-back | |
| const spotLight2 = new THREE.SpotLight(0xffffff, SPOT_2_INTENSITY, 400, Math.PI / 4, 0.5, 1.0); | |
| spotLight2.position.set(-150, 150, -150); | |
| scene.add(spotLight2); | |
| // Highlight light from bottom-front to lift up floor shadows | |
| const spotLight3 = new THREE.SpotLight(0xffffff, SPOT_3_INTENSITY, 300, Math.PI / 3, 0.5, 1.0); | |
| spotLight3.position.set(0, -100, 150); | |
| scene.add(spotLight3); | |
| // Handle resizing safely | |
| onResize((w, h) => { | |
| if (w > 0 && h > 0) { | |
| renderer.setSize(w, h, false); | |
| camera.aspect = w / h; | |
| camera.updateProjectionMatrix(); | |
| } | |
| }); | |
| // Interactive Drag-to-Rotate logic | |
| let isDragging = false; | |
| let previousPointerPos = { x: 0, y: 0 }; | |
| const rotationTarget = { x: 0.15, y: 0.0 }; | |
| onPointerDown(e => { | |
| isDragging = true; | |
| previousPointerPos = { x: e.x, y: e.y }; | |
| }); | |
| onPointerMove(e => { | |
| if (!isDragging) return; | |
| const deltaX = e.x - previousPointerPos.x; | |
| const deltaY = e.y - previousPointerPos.y; | |
| rotationTarget.y += deltaX * 0.007; | |
| rotationTarget.x += deltaY * 0.007; | |
| previousPointerPos = { x: e.x, y: e.y }; | |
| }); | |
| onPointerUp(() => { | |
| isDragging = false; | |
| }); | |
| // 7. Animation Loop | |
| let time = 0; | |
| function animate() { | |
| requestAnimationFrame(animate); | |
| // Skip rendering if the canvas width/height is zero (which auto-syncs with DOM layout) | |
| if (canvas.width === 0 || canvas.height === 0) { | |
| return; | |
| } | |
| time += 0.007; | |
| if (isDragging) { | |
| // Smoothly interpolate rotation to drag target | |
| cubeGroup.rotation.y = THREE.MathUtils.lerp(cubeGroup.rotation.y, rotationTarget.y, 0.15); | |
| cubeGroup.rotation.x = THREE.MathUtils.lerp(cubeGroup.rotation.x, rotationTarget.x, 0.15); | |
| } else { | |
| // Slowly rotate when idle | |
| cubeGroup.rotation.y += 0.0025 * ROTATION_SPEED; | |
| rotationTarget.y = cubeGroup.rotation.y; | |
| rotationTarget.x = cubeGroup.rotation.x; | |
| } | |
| // Keep Z rotation stable | |
| cubeGroup.rotation.z = Math.cos(time * 0.05) * 0.02; | |
| // Float above the floor to show off under-glass light refraction | |
| cubeGroup.position.y = Math.sin(time * FLOAT_SPEED) * FLOAT_HEIGHT; | |
| renderer.render(scene, camera); | |
| } | |
| animate(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment