This commit is contained in:
2026-09-08 13:31:57 -07:00
commit 2637189690
257 changed files with 19205 additions and 0 deletions

View File

@@ -0,0 +1,93 @@
import * as THREE from 'three'
export const cardDimensions = {
width: 0.063,
height: 0.0882,
thickness: 0.0004,
cornerRadius: 0.0028,
} as const
const sceneHeight = 4
const sceneScale = sceneHeight / cardDimensions.height
export const cardSceneDimensions = {
width: cardDimensions.width * sceneScale,
height: cardDimensions.height * sceneScale,
thickness: cardDimensions.thickness * sceneScale,
cornerRadius: cardDimensions.cornerRadius * sceneScale,
} as const
function createRoundedRectangle(width: number, height: number, radius: number) {
const halfWidth = width / 2
const halfHeight = height / 2
const shape = new THREE.Shape()
shape.moveTo(-halfWidth + radius, -halfHeight)
shape.lineTo(halfWidth - radius, -halfHeight)
shape.quadraticCurveTo(halfWidth, -halfHeight, halfWidth, -halfHeight + radius)
shape.lineTo(halfWidth, halfHeight - radius)
shape.quadraticCurveTo(halfWidth, halfHeight, halfWidth - radius, halfHeight)
shape.lineTo(-halfWidth + radius, halfHeight)
shape.quadraticCurveTo(-halfWidth, halfHeight, -halfWidth, halfHeight - radius)
shape.lineTo(-halfWidth, -halfHeight + radius)
shape.quadraticCurveTo(-halfWidth, -halfHeight, -halfWidth + radius, -halfHeight)
return shape
}
export function createCardGeometry(
frontMaterial: THREE.Material,
backMaterial: THREE.Material,
edgeMaterial: THREE.Material,
) {
const { width, height, thickness, cornerRadius } = cardSceneDimensions
const shape = createRoundedRectangle(width, height, cornerRadius)
const bodyGeometry = new THREE.ExtrudeGeometry(shape, {
curveSegments: 16,
depth: thickness,
steps: 1,
bevelEnabled: false,
})
bodyGeometry.translate(0, 0, -thickness / 2)
// Extrusion caps would duplicate the dedicated faces and z-fight at Pack's camera distances.
const sideWalls = bodyGeometry.groups.find((group) => group.materialIndex === 1)
if (!sideWalls) throw new Error('Card extrusion is missing its side-wall geometry')
bodyGeometry.setDrawRange(sideWalls.start, sideWalls.count)
const faceGeometry = new THREE.ShapeGeometry(shape, 16)
const positions = faceGeometry.getAttribute('position')
const uvs = faceGeometry.getAttribute('uv')
for (let index = 0; index < positions.count; index += 1) {
uvs.setXY(
index,
positions.getX(index) / width + 0.5,
positions.getY(index) / height + 0.5,
)
}
uvs.needsUpdate = true
const faceOffset = thickness / 2
const body = new THREE.Mesh(bodyGeometry, edgeMaterial)
body.name = 'CARD_EDGE'
const front = new THREE.Mesh(faceGeometry, frontMaterial)
front.name = 'CARD_FRONT'
front.position.z = faceOffset
const back = new THREE.Mesh(faceGeometry.clone(), backMaterial)
back.name = 'CARD_BACK'
back.position.z = -faceOffset
back.rotation.y = Math.PI
const card = new THREE.Group()
card.name = 'CARD_PROCEDURAL'
card.add(body, front, back)
for (const mesh of [body, front, back]) {
mesh.castShadow = true
mesh.receiveShadow = true
}
return card
}