mirror of
https://github.com/supabase/supabase.git
synced 2026-10-15 22:29:11 +03:00
cleanup
This commit is contained in:
1 parent
598a22d35a
commit
83ea892b3c
7 files changed
+67
-823
No files matched your search
@@ -1,261 +1,80 @@
|
||||
import * as THREE from 'three'
|
||||
import { cn } from 'ui'
|
||||
import { createThreeSetup, useThreeJS, createTicketMesh } from './helpers'
|
||||
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
|
||||
import { useCallback, useRef, useState } from 'react'
|
||||
import { useThreeJS } from './helpers'
|
||||
import { useCallback, useRef } from 'react'
|
||||
import SceneRenderer, { BaseScene } from './utils/SceneRenderer'
|
||||
import TicketScene from './scenes/TicketScene'
|
||||
|
||||
const LwCanvas = ({ className }: { className?: string }) => {
|
||||
const secretRef = useRef(false)
|
||||
interface TicketCanvasProps {
|
||||
secret?: boolean
|
||||
platinum?: boolean
|
||||
user: {
|
||||
id?: string
|
||||
name?: string
|
||||
ticketNumber?: number
|
||||
}
|
||||
startDate: Date
|
||||
playmodeRTChannel: unknown
|
||||
className?: string
|
||||
onUpgrade?: () => void
|
||||
}
|
||||
|
||||
const setup = useCallback((container: HTMLElement) => {
|
||||
// Create scene with postprocessing effects
|
||||
const {
|
||||
scene,
|
||||
camera,
|
||||
renderer,
|
||||
composer,
|
||||
resize,
|
||||
crtPass,
|
||||
glitchPass,
|
||||
bloomPass,
|
||||
stats,
|
||||
debug,
|
||||
orbitControls,
|
||||
} = createThreeSetup(container, {
|
||||
cameraPosition: new THREE.Vector3(0, 0, 5),
|
||||
debug: false,
|
||||
postprocessing: {
|
||||
bloom: {
|
||||
enabled: true,
|
||||
strength: 0.25,
|
||||
radius: 0.7,
|
||||
threshold: 0.2,
|
||||
const TicketCanvas = ({
|
||||
secret,
|
||||
platinum,
|
||||
user,
|
||||
startDate,
|
||||
playmodeRTChannel,
|
||||
className,
|
||||
onUpgrade,
|
||||
}: TicketCanvasProps) => {
|
||||
const sceneRef = useRef<TicketScene | null>(null)
|
||||
const setup = useCallback(
|
||||
(container: HTMLElement) => {
|
||||
let scene: BaseScene | null = null
|
||||
|
||||
const sceneRenderer = new SceneRenderer(container)
|
||||
|
||||
|
||||
sceneRef.current = new TicketScene({
|
||||
defaultSecret: secret,
|
||||
defaultPlatinum: platinum,
|
||||
user,
|
||||
startDate,
|
||||
onSeatChartButtonClicked: () => {
|
||||
sceneRef.current?.showBackSide()
|
||||
sceneRef.current?.upgradeToSecret()
|
||||
},
|
||||
glitch: {
|
||||
enabled: true,
|
||||
dtSize: 512, // Increase detail size for less frequent glitches
|
||||
colS: 0,
|
||||
onGoBackButtonClicked: () => {
|
||||
sceneRef.current?.showFrontSide()
|
||||
},
|
||||
crt: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
// Set renderer clear color to make background visible
|
||||
renderer.setClearColor(0x000000, 0)
|
||||
|
||||
// Add ambient light with increased intensity
|
||||
const ambientLight = new THREE.AmbientLight(0xffffff, 1.0)
|
||||
scene.add(ambientLight)
|
||||
|
||||
// Add directional light
|
||||
const directionalLight = new THREE.DirectionalLight(0xffffff, 1.5)
|
||||
directionalLight.position.set(5, 5, 5)
|
||||
scene.add(directionalLight)
|
||||
|
||||
// Add a point light to better illuminate the placeholder
|
||||
const pointLight = new THREE.PointLight(0xffffff, 1.0)
|
||||
pointLight.position.set(0, 0, 5)
|
||||
scene.add(pointLight)
|
||||
|
||||
// Create ticket mesh placeholder (will be replaced when texture loads)
|
||||
|
||||
const planeGeometrySize = [12, 6]
|
||||
const placeholderGeometry = new THREE.PlaneGeometry(...planeGeometrySize)
|
||||
const placeholderMaterial = new THREE.MeshBasicMaterial({
|
||||
color: 0x000000,
|
||||
transparent: true,
|
||||
opacity: 1,
|
||||
side: THREE.DoubleSide,
|
||||
})
|
||||
// Use a more generic type for ticket to allow reassignment to either Mesh or Group
|
||||
let ticket: THREE.Object3D = new THREE.Mesh(placeholderGeometry, placeholderMaterial)
|
||||
|
||||
scene.add(ticket)
|
||||
|
||||
// Ensure the ticket is visible in the camera's view
|
||||
ticket.position.z = 0
|
||||
|
||||
// Load the ticket texture and create the actual ticket mesh
|
||||
// Replace this URL with your actual ticket texture
|
||||
const ticketScene = '/images/launchweek/14/supabase-ticket.glb'
|
||||
const ticketStaticTexture = '/images/launchweek/14/supabase-static-ticket.png'
|
||||
|
||||
createTicketMesh(ticketScene, {
|
||||
width: planeGeometrySize[0],
|
||||
height: planeGeometrySize[1],
|
||||
ticketTextureSource: ticketStaticTexture,
|
||||
debug,
|
||||
})
|
||||
.then((ticketModel) => {
|
||||
// Remove placeholder and add the actual ticket model
|
||||
scene.remove(ticket)
|
||||
scene.add(ticketModel)
|
||||
|
||||
// Store reference to the new ticket for animation
|
||||
if (ticket instanceof THREE.Mesh) {
|
||||
ticket.geometry.dispose()
|
||||
if (Array.isArray(ticket.material))
|
||||
ticket.material.forEach((material) => material.dispose())
|
||||
else ticket.material.dispose()
|
||||
}
|
||||
|
||||
// Update the ticket reference
|
||||
ticket = ticketModel
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Failed to load ticket texture:', error)
|
||||
})
|
||||
|
||||
// Add window resize listener
|
||||
window.addEventListener('resize', resize)
|
||||
sceneRenderer.activateScene(sceneRef.current)
|
||||
|
||||
// Mouse movement tracking for glitch effect and ticket following
|
||||
let lastMouseX = 0
|
||||
let lastMouseY = 0
|
||||
let mouseIntensity = 0
|
||||
let mouseIntensityDecay = 0.95 // How quickly the intensity decays
|
||||
sceneRenderer.init()
|
||||
|
||||
// For mouse tracking of ticket
|
||||
let mouseX = 0
|
||||
let mouseY = 0
|
||||
let targetX = 0
|
||||
let targetY = 0
|
||||
|
||||
const handleMouseMove = (event: MouseEvent) => {
|
||||
// Calculate mouse movement distance for glitch effect
|
||||
const dx = event.clientX - lastMouseX
|
||||
const dy = event.clientY - lastMouseY
|
||||
const distance = Math.sqrt(dx * dx + dy * dy)
|
||||
|
||||
// Update intensity based on movement (clamped between 0 and 1)
|
||||
mouseIntensity = Math.max(0, mouseIntensity + distance * 0.005)
|
||||
|
||||
// Update last position
|
||||
lastMouseX = event.clientX
|
||||
lastMouseY = event.clientY
|
||||
|
||||
// Update glitch intensity
|
||||
if (glitchPass) {
|
||||
glitchPass.setIntensity(mouseIntensity)
|
||||
}
|
||||
|
||||
// Calculate normalized mouse position for ticket following
|
||||
// Convert from screen coordinates to normalized device coordinates (-1 to +1)
|
||||
const rect = container.getBoundingClientRect()
|
||||
mouseX = ((event.clientX - rect.left) / container.clientWidth) * 2 - 1
|
||||
mouseY = -((event.clientY - rect.top) / container.clientHeight) * 2 + 1
|
||||
|
||||
// Scale down the effect for subtle movement
|
||||
targetX = mouseX * 0.5
|
||||
targetY = mouseY * 0.3
|
||||
}
|
||||
|
||||
// Make the container element respond to pointer events
|
||||
container.style.pointerEvents = 'auto'
|
||||
|
||||
// Add mouse event listeners
|
||||
container.addEventListener('mousemove', handleMouseMove)
|
||||
|
||||
// Animation function
|
||||
|
||||
const animate = (time?: number) => {
|
||||
const currentTime = time || 0
|
||||
|
||||
// Update CRT shader time uniform for animation effects
|
||||
if (crtPass) {
|
||||
crtPass.uniforms.time.value = currentTime
|
||||
crtPass.enabled = secretRef.current
|
||||
}
|
||||
|
||||
if(glitchPass) {
|
||||
glitchPass.enabled = secretRef.current
|
||||
}
|
||||
|
||||
if(bloomPass) {
|
||||
bloomPass.enabled = secretRef.current
|
||||
}
|
||||
|
||||
// Gradually decay mouse intensity when not moving
|
||||
if (mouseIntensity > 0) {
|
||||
mouseIntensity *= mouseIntensityDecay
|
||||
if (mouseIntensity < 0.0000001) {
|
||||
mouseIntensity = 0
|
||||
}
|
||||
if (glitchPass) {
|
||||
glitchPass.setIntensity(mouseIntensity)
|
||||
}
|
||||
}
|
||||
|
||||
if (stats.instance) {
|
||||
stats.instance.begin()
|
||||
}
|
||||
|
||||
if (orbitControls.instance) {
|
||||
// Update controls in animation loop
|
||||
orbitControls.instance.update()
|
||||
}
|
||||
|
||||
// Render with post-processing
|
||||
composer.render()
|
||||
|
||||
if (stats.instance) {
|
||||
stats.instance.end()
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup function
|
||||
const cleanup = () => {
|
||||
window.removeEventListener('resize', resize)
|
||||
|
||||
// Your existing cleanup code...
|
||||
orbitControls.instance?.dispose() // Dispose controls on cleanup
|
||||
|
||||
// Dispose of all resources
|
||||
scene.traverse((object) => {
|
||||
if (object instanceof THREE.Mesh) {
|
||||
if (object.geometry) object.geometry.dispose()
|
||||
|
||||
if (object.material) {
|
||||
if (Array.isArray(object.material)) {
|
||||
object.material.forEach((material) => material.dispose())
|
||||
} else {
|
||||
object.material.dispose()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
renderer.dispose()
|
||||
container.removeChild(renderer.domElement)
|
||||
}
|
||||
|
||||
return { cleanup, animate }
|
||||
}, [])
|
||||
return sceneRenderer
|
||||
},
|
||||
[platinum, secret, startDate, user]
|
||||
)
|
||||
|
||||
const { containerRef } = useThreeJS(setup)
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={cn(
|
||||
'w-screen absolute inset-0 h-[600px] lg:min-h-full lg:max-h-[1000px] flex justify-center items-center overflow-hidden',
|
||||
className
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
'w-screen absolute inset-0 h-[600px] lg:min-h-full lg:max-h-[1000px] flex justify-center items-center overflow-hidden pointer-events-none',
|
||||
className
|
||||
)}
|
||||
>
|
||||
<div ref={containerRef} className="w-full lg:h-full" />
|
||||
</div>
|
||||
|
||||
<button
|
||||
className="absolute top-4 right-4 bg-white text-black p-2 rounded-md shadow-md z-20 hover:bg-gray-200 cursor-pointer"
|
||||
onClick={() => secretRef.current = !secretRef.current}
|
||||
>
|
||||
Toggle secret
|
||||
</button>
|
||||
</>
|
||||
ref={containerRef}
|
||||
className="w-full lg:h-full"
|
||||
onClick={(e) => {
|
||||
sceneRef.current?.click(e.nativeEvent)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export { LwCanvas }
|
||||
export default TicketCanvas
|
||||
@@ -1,80 +0,0 @@
|
||||
import { cn } from 'ui'
|
||||
import { useThreeJS } from './helpers'
|
||||
import { useCallback, useRef } from 'react'
|
||||
import SceneRenderer, { BaseScene } from './utils/SceneRenderer'
|
||||
import TicketScene from './scenes/TicketScene'
|
||||
|
||||
interface TicketCanvasProps {
|
||||
secret?: boolean
|
||||
platinum?: boolean
|
||||
user: {
|
||||
id?: string
|
||||
name?: string
|
||||
ticketNumber?: number
|
||||
}
|
||||
startDate: Date
|
||||
playmodeRTChannel: unknown
|
||||
className?: string
|
||||
onUpgrade?: () => void
|
||||
}
|
||||
|
||||
const TicketCanvas = ({
|
||||
secret,
|
||||
platinum,
|
||||
user,
|
||||
startDate,
|
||||
playmodeRTChannel,
|
||||
className,
|
||||
onUpgrade,
|
||||
}: TicketCanvasProps) => {
|
||||
const sceneRef = useRef<TicketScene | null>(null)
|
||||
const setup = useCallback(
|
||||
(container: HTMLElement) => {
|
||||
let scene: BaseScene | null = null
|
||||
|
||||
const sceneRenderer = new SceneRenderer(container)
|
||||
|
||||
|
||||
sceneRef.current = new TicketScene({
|
||||
defaultSecret: secret,
|
||||
defaultPlatinum: platinum,
|
||||
user,
|
||||
startDate,
|
||||
onSeatChartButtonClicked: () => {
|
||||
sceneRef.current?.showBackSide()
|
||||
sceneRef.current?.upgradeToSecret()
|
||||
},
|
||||
onGoBackButtonClicked: () => {
|
||||
sceneRef.current?.showFrontSide()
|
||||
},
|
||||
})
|
||||
|
||||
sceneRenderer.activateScene(sceneRef.current)
|
||||
|
||||
sceneRenderer.init()
|
||||
|
||||
return sceneRenderer
|
||||
},
|
||||
[platinum, secret, startDate, user]
|
||||
)
|
||||
|
||||
const { containerRef } = useThreeJS(setup)
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'w-screen absolute inset-0 h-[600px] lg:min-h-full lg:max-h-[1000px] flex justify-center items-center overflow-hidden',
|
||||
className
|
||||
)}
|
||||
>
|
||||
<div
|
||||
ref={containerRef}
|
||||
className="w-full lg:h-full"
|
||||
onClick={(e) => {
|
||||
sceneRef.current?.click(e.nativeEvent)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default TicketCanvas
|
||||
Whitespace-only changes.
@@ -1,49 +0,0 @@
|
||||
import { ShaderMaterial } from "three/src/materials/ShaderMaterial";
|
||||
import { Color } from "three/src/math/Color";
|
||||
|
||||
// vertexShader.js
|
||||
const vertexShader = `
|
||||
varying vec2 vUv;
|
||||
|
||||
void main() {
|
||||
// Pass UV coords to the fragment shader
|
||||
vUv = uv;
|
||||
|
||||
// Standard transform
|
||||
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
||||
}
|
||||
`;
|
||||
|
||||
// fragmentShader.js
|
||||
const fragmentShader = `
|
||||
// A sampler2D to receive your texture
|
||||
uniform sampler2D myTexture;
|
||||
|
||||
// Base color, passed in as a uniform
|
||||
uniform vec3 baseColor;
|
||||
|
||||
// UV coords from the vertex shader
|
||||
varying vec2 vUv;
|
||||
|
||||
void main() {
|
||||
// Fetch the texture color
|
||||
vec4 texColor = texture2D(myTexture, vUv);
|
||||
|
||||
// Simple alpha-based blend:
|
||||
// The texture's alpha decides how strongly we blend it over the base color
|
||||
vec3 blendedColor = mix(baseColor, texColor.rgb, texColor.a);
|
||||
|
||||
// Output final color
|
||||
gl_FragColor = vec4(blendedColor, 1.0);
|
||||
}
|
||||
`;
|
||||
|
||||
export const coloredTextureMaterial = {
|
||||
uniforms: {
|
||||
myTexture: { value: null },
|
||||
// For example, a green base color
|
||||
baseColor: { value: new Color(0xffffff) },
|
||||
},
|
||||
vertexShader,
|
||||
fragmentShader,
|
||||
};
|
||||
@@ -1,16 +1,5 @@
|
||||
import { useRef, useEffect } from 'react'
|
||||
import * as THREE from 'three'
|
||||
import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js'
|
||||
import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js'
|
||||
import { UnrealBloomPass } from 'three/examples/jsm/postprocessing/UnrealBloomPass.js'
|
||||
import { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass.js'
|
||||
import { GLTF, GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'
|
||||
import { GlitchPass } from './effects/glitch'
|
||||
import { CRTShader } from './effects/crt-shader'
|
||||
import { FontLoader } from 'three/examples/jsm/loaders/FontLoader.js'
|
||||
import { TextGeometry } from 'three/examples/jsm/geometries/TextGeometry.js'
|
||||
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
|
||||
import { coloredTextureMaterial } from './effects/colored-texture-shader'
|
||||
|
||||
/**
|
||||
* Helper to simplifies controlling requestAnimationFrame
|
||||
@@ -42,169 +31,6 @@ export const createThreeAnimation = (callback: (time?: number) => void) => {
|
||||
return { start, stop }
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to create a Three.js setup with scene, camera, renderer, and post-processing
|
||||
* @param container DOM element to render into
|
||||
* @param options Configuration options
|
||||
* @returns Object with scene, camera, renderer, composer, and resize function
|
||||
*/
|
||||
export const createThreeSetup = (
|
||||
container: HTMLElement,
|
||||
options: {
|
||||
cameraPosition?: THREE.Vector3
|
||||
postprocessing?: {
|
||||
bloom?: {
|
||||
enabled: boolean
|
||||
strength?: number
|
||||
radius?: number
|
||||
threshold?: number
|
||||
}
|
||||
glitch?: {
|
||||
enabled: boolean
|
||||
dtSize?: number
|
||||
colS?: number
|
||||
}
|
||||
crt?: {
|
||||
enabled: boolean
|
||||
}
|
||||
}
|
||||
debug?: boolean
|
||||
} = {}
|
||||
) => {
|
||||
// Create scene
|
||||
const scene = new THREE.Scene()
|
||||
|
||||
if (options.debug) {
|
||||
const axesHelper = new THREE.AxesHelper(5)
|
||||
scene.add(axesHelper)
|
||||
}
|
||||
|
||||
// Create camera
|
||||
const camera = new THREE.PerspectiveCamera(
|
||||
75,
|
||||
container.clientWidth / container.clientHeight,
|
||||
0.1,
|
||||
1000
|
||||
)
|
||||
camera.position.copy(options.cameraPosition || new THREE.Vector3(0, 0, 5))
|
||||
|
||||
// Create renderer
|
||||
const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true })
|
||||
renderer.setSize(container.clientWidth, container.clientHeight)
|
||||
renderer.setPixelRatio(window.devicePixelRatio)
|
||||
container.appendChild(renderer.domElement)
|
||||
|
||||
// Enable debug info if debug mode is enabled
|
||||
if (options.debug) {
|
||||
renderer.info.autoReset = false
|
||||
console.log('Three.js Debug Mode Enabled')
|
||||
}
|
||||
|
||||
// Create composer for post-processing
|
||||
const composer = new EffectComposer(renderer)
|
||||
const renderPass = new RenderPass(scene, camera)
|
||||
composer.addPass(renderPass)
|
||||
|
||||
// Add bloom pass if enabled
|
||||
let bloomPass: UnrealBloomPass | null = null
|
||||
if (options.postprocessing?.bloom?.enabled) {
|
||||
bloomPass = new UnrealBloomPass(
|
||||
new THREE.Vector2(container.clientWidth, container.clientHeight),
|
||||
options.postprocessing.bloom.strength || 1.5,
|
||||
options.postprocessing.bloom.radius || 0.4,
|
||||
options.postprocessing.bloom.threshold || 0.85
|
||||
)
|
||||
composer.addPass(bloomPass)
|
||||
}
|
||||
|
||||
// Add glitch pass if enabled
|
||||
let glitchPass: GlitchPass | null = null
|
||||
if (options.postprocessing?.glitch?.enabled) {
|
||||
glitchPass = new GlitchPass(options.postprocessing.glitch.dtSize || 64)
|
||||
glitchPass.uniforms.col_s.value = options.postprocessing.glitch.colS || 0
|
||||
composer.addPass(glitchPass)
|
||||
}
|
||||
|
||||
// Add CRT pass if enabled
|
||||
let crtPass: ShaderPass | null = null
|
||||
if (options.postprocessing?.crt?.enabled) {
|
||||
crtPass = new ShaderPass(CRTShader)
|
||||
crtPass.uniforms.resolution.value.set(container.clientWidth, container.clientHeight)
|
||||
composer.addPass(crtPass)
|
||||
}
|
||||
|
||||
// Resize function
|
||||
const resize = () => {
|
||||
camera.aspect = container.clientWidth / container.clientHeight
|
||||
camera.updateProjectionMatrix()
|
||||
renderer.setSize(container.clientWidth, container.clientHeight)
|
||||
composer.setSize(container.clientWidth, container.clientHeight)
|
||||
|
||||
// Update resolution uniform for CRT shader
|
||||
if (crtPass) {
|
||||
crtPass.uniforms.resolution.value.set(container.clientWidth, container.clientHeight)
|
||||
}
|
||||
}
|
||||
|
||||
// Add stats display if debug mode is enabled
|
||||
let stats: { instance: Stats | null } = { instance: null }
|
||||
let orbitControls: { instance: OrbitControls | null } = { instance: null }
|
||||
if (options.debug) {
|
||||
import('three/examples/jsm/libs/stats.module.js').then(({ default: Stats }) => {
|
||||
const statsInstance = new Stats()
|
||||
statsInstance.showPanel(0) // 0: fps, 1: ms, 2: mb, 3+: custom
|
||||
statsInstance.dom.style.position = 'absolute'
|
||||
statsInstance.dom.style.top = '0px'
|
||||
statsInstance.dom.style.left = '0px'
|
||||
container.appendChild(statsInstance.dom)
|
||||
stats.instance = statsInstance
|
||||
})
|
||||
|
||||
import('three/examples/jsm/controls/OrbitControls.js').then(({ OrbitControls }) => {
|
||||
// Create OrbitControls after the setup
|
||||
const controls = new OrbitControls(camera, renderer.domElement)
|
||||
|
||||
// Configure controls as needed
|
||||
controls.enableDamping = true // Add smooth damping
|
||||
controls.dampingFactor = 0.05
|
||||
controls.enableZoom = true
|
||||
controls.enablePan = true
|
||||
orbitControls.instance = controls
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
scene,
|
||||
camera,
|
||||
renderer,
|
||||
composer,
|
||||
resize,
|
||||
bloomPass,
|
||||
glitchPass,
|
||||
crtPass,
|
||||
stats,
|
||||
orbitControls,
|
||||
debug: options.debug || false,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to load a texture
|
||||
* @param url URL of the texture to load
|
||||
* @returns Promise that resolves with the loaded texture
|
||||
*/
|
||||
export const loadTexture = (url: string): Promise<THREE.Texture> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const textureLoader = new THREE.TextureLoader()
|
||||
textureLoader.load(
|
||||
url,
|
||||
(texture) => resolve(texture),
|
||||
undefined,
|
||||
(error) => reject(error)
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to load a GLTF model
|
||||
* @param url URL of the GLTF model to load
|
||||
@@ -224,189 +50,6 @@ export const loadGLTFModel = (url: string) => {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to create a flight ticket mesh
|
||||
* @param source URL of the GLTF model or texture to use for the ticket
|
||||
* @param options Configuration options for the ticket
|
||||
* @returns Promise that resolves with the created model
|
||||
*/
|
||||
export const createTicketMesh = async (
|
||||
source: string,
|
||||
options: {
|
||||
ticketTextureSource?: string
|
||||
width?: number
|
||||
height?: number
|
||||
enhanceEmissive?: boolean
|
||||
debug?: boolean
|
||||
materialOptions?: {
|
||||
transparent?: boolean
|
||||
emissiveColor?: THREE.Color | number
|
||||
emissiveIntensity?: number
|
||||
color?: THREE.Color | number
|
||||
}
|
||||
} = {}
|
||||
): Promise<THREE.Object3D> => {
|
||||
try {
|
||||
const useTextureMode = !(source.endsWith('.glb') || source.endsWith('.gltf'))
|
||||
|
||||
if (!useTextureMode) {
|
||||
// Load the GLTF model
|
||||
const model = (await loadGLTFModel(source)).scene
|
||||
const ticketTexture = options.ticketTextureSource
|
||||
? await loadTexture(options.ticketTextureSource)
|
||||
: null
|
||||
|
||||
if (ticketTexture) {
|
||||
const ticket = model.getObjectByName('ticket')
|
||||
if (ticket instanceof THREE.Mesh) {
|
||||
// Store the original material properties
|
||||
const newMaterial = new THREE.ShaderMaterial(coloredTextureMaterial)
|
||||
newMaterial.uniforms.myTexture.value = ticketTexture
|
||||
|
||||
ticket.material = newMaterial
|
||||
}
|
||||
}
|
||||
|
||||
const objectsNames = ['planet', 'name', 'time', 'date', 'spicies', 'number'] as const
|
||||
const namedObjects = getNamedTextObjects(objectsNames, model)
|
||||
const textures = {} as {
|
||||
[key in (typeof objectsNames)[number]]?: ReturnType<typeof createTextureForObject>
|
||||
}
|
||||
|
||||
for (let i = 0; i < namedObjects.length; i++) {
|
||||
const namedObject = namedObjects[i]
|
||||
const objectName = objectsNames[i]
|
||||
|
||||
if (!namedObject) {
|
||||
console.error(`Named object ${objectsNames} not found`)
|
||||
continue
|
||||
}
|
||||
|
||||
if (namedObject instanceof THREE.Mesh) {
|
||||
// Spline exports some objects with additional invalid materials. Clean it up here
|
||||
namedObject.material = new THREE.MeshBasicMaterial({})
|
||||
}
|
||||
|
||||
let text = ''
|
||||
switch (objectName) {
|
||||
case 'planet':
|
||||
text = 'Earth'
|
||||
break
|
||||
case 'name':
|
||||
text = 'John Doe'
|
||||
break
|
||||
|
||||
case 'time':
|
||||
text = '00:12:00'
|
||||
break
|
||||
|
||||
case 'date':
|
||||
text = '03/24/2025'
|
||||
break
|
||||
|
||||
case 'spicies':
|
||||
text = 'Modern human'
|
||||
break
|
||||
|
||||
case 'number':
|
||||
text = 'A001'
|
||||
break
|
||||
|
||||
default:
|
||||
objectName satisfies never
|
||||
}
|
||||
|
||||
const meshWithTexture = createTextureForObject(namedObject, text)
|
||||
textures[objectName] = meshWithTexture
|
||||
}
|
||||
|
||||
// Calculate scale to fit the model within the specified width and height
|
||||
const box = new THREE.Box3().setFromObject(model)
|
||||
const size = box.getSize(new THREE.Vector3())
|
||||
|
||||
// Center the model
|
||||
box.setFromObject(model)
|
||||
const center = box.getCenter(new THREE.Vector3())
|
||||
model.position.sub(center)
|
||||
|
||||
// Add debug helpers if debug mode is enabled
|
||||
if (options.debug) {
|
||||
// Add bounding box helper
|
||||
const boxHelper = new THREE.BoxHelper(model, 0xff0000)
|
||||
model.add(boxHelper)
|
||||
|
||||
// Add wireframe helper for each mesh
|
||||
model.traverse((child) => {
|
||||
if (child instanceof THREE.Mesh) {
|
||||
const wireframe = new THREE.WireframeGeometry(child.geometry)
|
||||
const line = new THREE.LineSegments(wireframe)
|
||||
line.material = new THREE.LineBasicMaterial({ color: 0x00ffff })
|
||||
|
||||
// Instead of adding to the model, add directly to the child mesh
|
||||
// This prevents double transformation
|
||||
child.add(line)
|
||||
|
||||
// Reset position, rotation, and scale to match the parent mesh exactly
|
||||
line.position.set(0, 0, 0)
|
||||
line.rotation.set(0, 0, 0)
|
||||
line.scale.set(1, 1, 1)
|
||||
|
||||
if (!child.name) {
|
||||
console.log('Debug: Mesh has no name', child)
|
||||
}
|
||||
|
||||
console.log('Debug: Added wireframe to mesh', child.name || 'unnamed')
|
||||
}
|
||||
})
|
||||
|
||||
console.log('Debug: Model dimensions', size)
|
||||
console.log('Debug: Center position', center)
|
||||
}
|
||||
|
||||
// Only enhance emissive properties if explicitly requested
|
||||
// This respects the original GLTF materials by default
|
||||
if (options.enhanceEmissive) {
|
||||
model.traverse((child) => {
|
||||
if (child instanceof THREE.Mesh && child.material) {
|
||||
if (Array.isArray(child.material)) {
|
||||
child.material.forEach((mat) => {
|
||||
if (mat instanceof THREE.MeshStandardMaterial) {
|
||||
mat.emissive = options.materialOptions?.emissiveColor
|
||||
? new THREE.Color(options.materialOptions.emissiveColor)
|
||||
: new THREE.Color(0xffffff)
|
||||
mat.emissiveIntensity = options.materialOptions?.emissiveIntensity ?? 0.2
|
||||
}
|
||||
})
|
||||
} else if (child.material instanceof THREE.MeshStandardMaterial) {
|
||||
child.material.emissive = options.materialOptions?.emissiveColor
|
||||
? new THREE.Color(options.materialOptions.emissiveColor)
|
||||
: new THREE.Color(0xffffff)
|
||||
child.material.emissiveIntensity = options.materialOptions?.emissiveIntensity ?? 0.2
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return model
|
||||
}
|
||||
throw new Error(`Failed to load model from: ${source}`)
|
||||
} catch (error) {
|
||||
console.error('Error loading model/texture:', error)
|
||||
|
||||
// Fallback to a simple plane with a default material if loading fails
|
||||
const geometry = new THREE.PlaneGeometry(options.width || 4, options.height || 2)
|
||||
const material = new THREE.MeshStandardMaterial({
|
||||
color: 0x00ff00,
|
||||
transparent: true,
|
||||
side: THREE.DoubleSide,
|
||||
emissive: new THREE.Color(0xffffff),
|
||||
emissiveIntensity: 0.2,
|
||||
})
|
||||
|
||||
return new THREE.Mesh(geometry, material)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom hook for Three.js setup and animation
|
||||
* @param setupCallback Callback function for setting up the scene
|
||||
@@ -440,94 +83,6 @@ export const useThreeJS = (
|
||||
return { containerRef }
|
||||
}
|
||||
|
||||
function getNamedTextObjects<TNamedObjects extends readonly string[]>(
|
||||
names: TNamedObjects,
|
||||
model: THREE.Group<THREE.Object3DEventMap>
|
||||
): { [K in keyof TNamedObjects]: THREE.Object3D | null } {
|
||||
let namedObjects = Array(names.length).fill(null) as (THREE.Object3D | null)[]
|
||||
for (let i = 0; i < names.length; i++) {
|
||||
const name = names[i]
|
||||
const obj = model.getObjectByName(name)
|
||||
if (obj) {
|
||||
namedObjects[i] = obj
|
||||
}
|
||||
}
|
||||
|
||||
return namedObjects as { [K in keyof TNamedObjects]: THREE.Object3D | null }
|
||||
}
|
||||
|
||||
function createTextureForObject(object: THREE.Object3D, text: string) {
|
||||
// Update matrix in case object has moved
|
||||
object.updateWorldMatrix(true, true)
|
||||
|
||||
const localBox = new THREE.Box3().setFromObject(object)
|
||||
const localSize = localBox.getSize(new THREE.Vector3())
|
||||
|
||||
// Get world scale
|
||||
const worldScale = new THREE.Vector3()
|
||||
object.matrixWorld.decompose(new THREE.Vector3(), new THREE.Quaternion(), worldScale)
|
||||
|
||||
// Apply world scale to get world size. Spline by default exports models with 0.01 scale
|
||||
const worldSize = new THREE.Vector3(
|
||||
localSize.x / Math.abs(worldScale.x),
|
||||
localSize.y / Math.abs(worldScale.y),
|
||||
localSize.z / Math.abs(worldScale.z)
|
||||
)
|
||||
|
||||
const canvas = document.createElement('canvas')
|
||||
const canvasWidth = worldSize.x // Base canvas width
|
||||
const canvasHeight = Math.floor(canvasWidth * (localSize.y / localSize.x)) // Maintain aspect ratio
|
||||
|
||||
canvas.width = canvasWidth
|
||||
canvas.height = canvasHeight
|
||||
|
||||
// Get canvas context and set up text rendering
|
||||
const context = canvas.getContext('2d')
|
||||
|
||||
if (!context) {
|
||||
throw new Error(`Could not get 2D context for text "${text}"`)
|
||||
}
|
||||
|
||||
// Clear canvas
|
||||
context.clearRect(0, 0, canvas.width, canvas.height)
|
||||
|
||||
// Set text properties
|
||||
context.fillStyle = 'black'
|
||||
|
||||
// Calculate font size based on canvas dimensions
|
||||
// Adjust the divisor to control text size relative to canvas
|
||||
const fontSize = Math.floor(canvasHeight)
|
||||
context.font = `bold ${fontSize}px Arial`
|
||||
|
||||
// Center text
|
||||
context.textAlign = 'left'
|
||||
context.textBaseline = 'middle'
|
||||
context.fillText(text, 0, canvas.height / 2)
|
||||
|
||||
// Create texture from canvas
|
||||
const texture = new THREE.CanvasTexture(canvas)
|
||||
texture.needsUpdate = true
|
||||
|
||||
// Create a plane geometry sized to match the named object
|
||||
const textPlane = new THREE.PlaneGeometry(worldSize.x, worldSize.y)
|
||||
const textMaterial = new THREE.MeshBasicMaterial({
|
||||
map: texture,
|
||||
transparent: true,
|
||||
side: THREE.DoubleSide,
|
||||
depthWrite: false, // Prevents z-fighting
|
||||
})
|
||||
|
||||
const textMesh = new THREE.Mesh(textPlane, textMaterial)
|
||||
|
||||
object.add(textMesh)
|
||||
|
||||
// Position the text slightly in front of the object
|
||||
// Adjust the z-offset as needed to prevent z-fighting
|
||||
// Offset position to align top-left corner instead of center
|
||||
textMesh.position.set(worldSize.x / 2, -worldSize.y / 2, 0.01)
|
||||
|
||||
return textMesh
|
||||
}
|
||||
|
||||
export function colorObjToRgb(color: { rgb: number; alpha: number }) {
|
||||
return `rgb(${(color.rgb >> 16) & 255} ${(color.rgb >> 8) & 255} ${(color.rgb >> 0) & 255} / ${color.alpha})`
|
||||
|
||||
@@ -182,7 +182,7 @@ class TicketScene implements BaseScene {
|
||||
loadedTextureType: null as 'basic' | 'secret' | 'platinum' | null,
|
||||
effectsIntensity: 0,
|
||||
mouseIntensityDecay: 0.98, // How quickly the intensity decays
|
||||
mouseIntensityGainRate: 0.005,
|
||||
mouseIntensityGainRate: 0.003,
|
||||
}
|
||||
|
||||
private _sceneConfig = {
|
||||
@@ -343,7 +343,7 @@ class TicketScene implements BaseScene {
|
||||
// Update glitch pass intensity
|
||||
if (this._glitchPass) {
|
||||
const glitchIntensity = this._internalState.mousePosition?.mouseIntensity ?? 1
|
||||
this._glitchPass.setIntensity(glitchIntensity * this._internalState.effectsIntensity * 6)
|
||||
this._glitchPass.setIntensity(glitchIntensity * this._internalState.effectsIntensity * 4)
|
||||
}
|
||||
|
||||
// Update CRT shader intensity
|
||||
|
||||
@@ -2,8 +2,7 @@ import { NextSeo } from 'next-seo'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useState } from 'react'
|
||||
import { Session } from '@supabase/supabase-js'
|
||||
import { LwCanvas } from '~/components/LaunchWeek/14/TicketCanvas'
|
||||
import TicketCanvas from '~/components/LaunchWeek/14/TicketCanvas2'
|
||||
import TicketCanvas from '~/components/LaunchWeek/14/TicketCanvas'
|
||||
import DefaultLayout from '~/components/Layouts/Default'
|
||||
import SectionContainer from '~/components/Layouts/SectionContainer'
|
||||
import { TicketState, ConfDataContext, UserData } from '~/components/LaunchWeek/hooks/use-conf-data'
|
||||
|
||||
Reference in new issue
Block a user