Fix wireframe size in debug mode

Signed-off-by: Piotr Goszczyński <pg@tonik.com>
This commit is contained in:
Piotr Goszczyński committed 2025-03-17 19:46:48 +01:00
1 parent 84206336f8
commit 7a922e6678
2 files changed
+197 -61

No files matched your search

+196 -61
View File
@@ -9,6 +9,7 @@ import { GlitchPass } from './glitch'
import { CRTShader } from './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'
/**
* Helper to simplifies controlling requestAnimationFrame
@@ -66,11 +67,17 @@ export const createThreeSetup = (
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,
@@ -86,6 +93,12 @@ export const createThreeSetup = (
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)
@@ -132,6 +145,33 @@ export const createThreeSetup = (
}
}
// 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,
@@ -141,6 +181,9 @@ export const createThreeSetup = (
bloomPass,
glitchPass,
crtPass,
stats,
orbitControls,
debug: options.debug || false,
}
}
@@ -191,6 +234,7 @@ export const createTicketMesh = async (
height?: number
forceTextureMode?: boolean
enhanceEmissive?: boolean
debug?: boolean
materialOptions?: {
transparent?: boolean
emissiveColor?: THREE.Color | number
@@ -200,9 +244,9 @@ export const createTicketMesh = async (
} = {}
): Promise<THREE.Object3D> => {
try {
const width = options.width || 4
const height = options.height || 2
// const width = options.width || 4
// const height = options.height || 2
//
// Determine if we should use texture mode
const useTextureMode =
options.forceTextureMode || !(source.endsWith('.glb') || source.endsWith('.gltf'))
@@ -210,49 +254,73 @@ export const createTicketMesh = async (
if (!useTextureMode) {
// Load the GLTF model
const model = await loadGLTFModel(source)
const objectsNames = ['planet'] as const
const namedObjects = getNamedTextObjects(objectsNames, model)
const textures = {} as {
[key in (typeof objectsNames)[number]]?: ReturnType<typeof createTextureForObject>
}
const namedObject = model.getObjectByName('planetName')
for (let i = 0; i < namedObjects.length; i++) {
const namedObject = namedObjects[i]
const objectName = objectsNames[i]
const boxHelper = new THREE.BoxHelper(namedObject, 0xff0000) // Red outline
model.add(boxHelper) // Add to the model so it moves with it
if (!namedObject) {
console.error(`Named object ${objectsNames} not found`)
continue
}
const fontLoader = new FontLoader()
let text = ''
switch (objectName) {
case 'planet':
text = 'Earth'
break
default:
objectName satisfies never
}
fontLoader.load('/images/launchweek/14/Arial_Regular.json', function (font) {
const textGeometry = new TextGeometry('Earth', {
font: font,
size: 96,
depth: 0.05,
})
const textMaterial = new THREE.MeshBasicMaterial({ color: 0xffffff })
const textMesh = new THREE.Mesh(textGeometry, textMaterial)
// Add text as child of your named object
if (namedObject) namedObject.add(textMesh)
else console.error('Named object not found')
// Position relative to parent
textMesh.position.set(0, 0, 0.01) // Slight offset to avoid z-fighting
})
const texture = createTextureForObject(namedObject, text)
textures[objectName] = texture
namedObject.add(texture)
}
// 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())
// Calculate scale factors to fit the model within the specified dimensions
const scaleX = width / size.x
const scaleY = height / size.y
const scale = Math.min(scaleX, scaleY)
// Apply the scale
model.scale.set(scale, scale, scale)
// 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 })
line.position.copy(child.position)
line.rotation.copy(child.rotation)
line.scale.copy(child.scale)
model.add(line)
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) {
@@ -278,35 +346,8 @@ export const createTicketMesh = async (
}
return model
} else {
// Load as a texture
const texture = await loadTexture(source)
// Create a plane geometry for the ticket
const geometry = new THREE.PlaneGeometry(width, height)
// Create a material with the loaded texture
const material = new THREE.MeshStandardMaterial({
map: texture,
transparent:
options.materialOptions?.transparent !== undefined
? options.materialOptions.transparent
: true,
side: THREE.DoubleSide,
emissive: options.materialOptions?.emissiveColor
? new THREE.Color(options.materialOptions.emissiveColor)
: new THREE.Color(0xffffff),
emissiveIntensity: options.materialOptions?.emissiveIntensity ?? 0.2,
color: options.materialOptions?.color
? new THREE.Color(options.materialOptions.color)
: new THREE.Color(0xffffff),
})
// Create the mesh
const mesh = new THREE.Mesh(geometry, material)
return mesh
}
}
throw new Error(`Failed to load model from: ${source}`)
} catch (error) {
console.error('Error loading model/texture:', error)
@@ -381,3 +422,97 @@ 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) {
const box = new THREE.Box3().setFromObject(object)
const size = box.getSize(new THREE.Vector3())
const canvas = document.createElement('canvas')
const canvasWidth = 512 // Base canvas width
const canvasHeight = Math.floor(canvasWidth * (size.y / size.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)
// Optional: fill background (can be transparent)
context.fillStyle = 'rgba(0, 0, 0, 0.5)'
context.fillRect(0, 0, canvas.width, canvas.height)
// Set text properties
context.fillStyle = 'white'
// Calculate font size based on canvas dimensions
// Adjust the divisor to control text size relative to canvas
const fontSize = Math.floor(canvasWidth / 5)
context.font = `bold ${fontSize}px Arial`
// Center text
context.textAlign = 'center'
context.textBaseline = 'middle'
context.fillText(text, canvas.width / 2, canvas.height / 2)
// Debug info - draw canvas border
context.strokeStyle = 'red'
context.lineWidth = 4
context.strokeRect(0, 0, canvas.width, canvas.height)
// Debug info - draw center point
context.fillStyle = 'red'
context.beginPath()
context.arc(canvas.width / 2, canvas.height / 2, 5, 0, Math.PI * 2)
context.fill()
// 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(20, 10)
const textMaterial = new THREE.MeshBasicMaterial({
map: texture,
transparent: true,
side: THREE.DoubleSide,
depthWrite: false, // Prevents z-fighting
})
const textMesh = new THREE.Mesh(textPlane, textMaterial)
// Position the text slightly in front of the object
// Adjust the z-offset as needed to prevent z-fighting
textMesh.position.set(0, 0, 0.01)
textMesh.rotation.set(0, 0, 0) // Rotate 180 degrees to face the camera
console.log('canvasWidth', canvasWidth)
console.log('canvasHeight', canvasHeight)
console.log('size', size)
console.log('text', text)
console.log('fontSize', fontSize)
return textMesh
}
@@ -0,0 +1 @@
{"glyphs": {"0": {"ha": 833, "x_min": 99, "x_max": 735, "o": "m 417 -17 q 183 103 268 -17 q 99 446 99 224 q 183 786 99 669 q 417 903 268 903 q 650 786 565 903 q 735 446 735 669 q 650 103 735 224 q 417 -17 565 -17 m 417 75 q 569 165 510 75 q 628 446 628 256 q 613 610 628 542 q 569 724 597 679 q 501 790 540 769 q 417 811 463 811 q 332 790 371 811 q 265 724 293 769 q 221 610 236 679 q 206 446 206 542 q 265 165 206 256 q 417 75 324 75 m 417 363 q 356 387 382 363 q 331 456 331 411 q 356 523 331 499 q 417 547 382 547 q 477 523 451 547 q 503 456 503 499 q 477 387 503 411 q 417 363 451 363 z "}, "1": {"ha": 833, "x_min": 135, "x_max": 736, "o": "m 135 0 l 135 94 l 390 94 l 390 747 l 192 747 l 192 821 q 324 847 271 832 q 419 886 376 863 l 504 886 l 504 94 l 736 94 l 736 0 l 135 0 z "}, "2": {"ha": 833, "x_min": 96, "x_max": 725, "o": "m 101 0 l 101 68 q 301 249 214 168 q 447 397 388 329 q 536 522 506 464 q 567 636 567 581 q 519 761 567 713 q 374 810 472 810 q 258 778 311 810 q 161 703 204 747 l 96 768 q 224 866 156 829 q 388 903 293 903 q 508 884 454 903 q 599 831 561 865 q 656 749 636 797 q 676 642 676 700 q 647 516 676 579 q 563 385 617 453 q 433 245 508 318 q 268 90 358 172 q 351 96 310 93 q 433 99 393 99 l 725 99 l 725 0 l 101 0 z "}, "3": {"ha": 833, "x_min": 79, "x_max": 719, "o": "m 399 -17 q 206 22 283 -17 q 79 108 128 60 l 138 183 q 244 110 182 142 q 392 78 306 78 q 476 90 438 78 q 544 122 515 101 q 588 173 572 143 q 604 240 604 203 q 587 312 604 279 q 531 368 569 344 q 431 404 492 392 q 282 417 369 417 l 282 504 q 416 517 361 504 q 506 551 471 529 q 556 604 540 574 q 572 669 572 635 q 523 772 572 735 q 392 810 474 810 q 274 785 329 810 q 174 719 218 760 l 113 792 q 239 872 168 840 q 396 903 310 903 q 512 888 458 903 q 605 844 565 872 q 667 774 644 815 q 689 678 689 732 q 636 546 689 597 q 500 467 583 494 l 500 461 q 584 433 544 451 q 654 385 624 414 q 702 319 685 357 q 719 236 719 282 q 694 131 719 178 q 626 51 669 83 q 524 1 582 18 q 399 -17 465 -17 z "}, "4": {"ha": 833, "x_min": 54, "x_max": 757, "o": "m 511 0 l 511 244 l 54 244 l 54 321 l 497 886 l 619 886 l 619 336 l 757 336 l 757 244 l 619 244 l 619 0 l 511 0 m 181 336 l 511 336 l 511 611 q 514 690 513 647 q 518 768 515 732 l 511 768 q 467 703 490 736 q 421 639 443 671 l 181 336 z "}, "5": {"ha": 833, "x_min": 78, "x_max": 725, "o": "m 397 -17 q 290 -6 338 -17 q 203 21 242 4 q 134 59 165 38 q 78 104 103 81 l 135 179 q 182 141 157 160 q 237 108 207 122 q 303 86 267 94 q 385 78 340 78 q 473 92 432 78 q 544 133 514 107 q 592 197 575 160 q 610 281 610 235 q 551 424 610 372 q 392 475 492 475 q 297 461 336 475 q 211 421 258 447 l 150 460 l 179 886 l 681 886 l 681 788 l 281 788 l 257 525 q 333 551 296 542 q 421 561 369 561 q 540 544 485 561 q 637 494 596 528 q 701 408 678 460 q 725 283 725 356 q 698 156 725 211 q 625 62 671 100 q 520 3 579 24 q 397 -17 461 -17 z "}, "6": {"ha": 833, "x_min": 107, "x_max": 742, "o": "m 446 -17 q 312 10 374 -17 q 204 90 250 36 q 133 223 158 143 q 107 410 107 303 q 138 637 107 543 q 219 790 168 731 q 338 876 271 849 q 478 903 406 903 q 620 876 560 903 q 725 808 681 849 l 661 738 q 581 788 626 769 q 485 807 535 807 q 383 788 432 807 q 298 724 335 768 q 238 606 261 679 q 214 426 215 533 q 333 510 268 479 q 465 542 399 542 q 577 524 526 542 q 665 473 628 507 q 722 388 701 439 q 742 268 742 336 q 717 152 742 204 q 653 63 693 100 q 559 4 613 25 q 446 -17 506 -17 m 446 74 q 519 88 485 74 q 579 127 554 101 q 619 188 604 153 q 633 268 633 224 q 585 408 633 360 q 444 456 536 456 q 331 428 392 456 q 218 336 271 400 q 290 140 231 207 q 446 74 349 74 z "}, "7": {"ha": 833, "x_min": 97, "x_max": 739, "o": "m 314 0 q 338 230 319 126 q 390 426 356 333 q 478 607 425 519 q 608 788 532 694 l 97 788 l 97 886 l 739 886 l 739 815 q 594 624 651 715 q 503 437 538 532 q 453 236 468 342 q 433 0 438 131 l 314 0 z "}, "8": {"ha": 833, "x_min": 94, "x_max": 738, "o": "m 419 -17 q 284 1 344 -17 q 182 52 224 19 q 117 129 140 85 q 94 226 94 174 q 111 308 94 272 q 154 374 128 344 q 215 424 181 403 q 285 463 249 446 l 285 468 q 190 553 229 503 q 150 675 150 604 q 170 768 150 726 q 227 840 190 810 q 314 886 264 869 q 424 903 364 903 q 540 885 489 903 q 626 837 590 868 q 680 762 661 806 q 699 667 699 718 q 659 549 699 604 q 564 456 619 494 l 564 450 q 632 411 600 432 q 688 364 664 390 q 724 302 711 338 q 738 219 738 267 q 716 126 738 169 q 654 51 694 83 q 555 1 614 19 q 419 -17 496 -17 m 489 483 q 600 661 600 564 q 554 773 600 728 q 421 818 508 818 q 300 779 346 818 q 254 675 254 740 q 272 606 254 635 q 322 556 290 578 q 397 516 354 533 q 489 483 439 499 m 422 68 q 575 111 522 68 q 628 224 628 154 q 606 301 628 269 q 547 354 585 332 q 459 394 510 376 q 350 431 408 411 q 242 349 285 394 q 199 238 199 304 q 259 115 199 163 q 422 68 319 68 z "}, "9": {"ha": 833, "x_min": 93, "x_max": 728, "o": "m 390 431 q 503 458 443 431 q 617 549 563 486 q 545 746 603 679 q 389 813 488 813 q 315 799 349 813 q 256 759 281 785 q 216 697 231 733 q 201 618 201 661 q 249 478 201 526Line truncated