Fix project structure

This commit is contained in:
2026-09-08 13:39:03 -07:00
parent 2637189690
commit 21dd8fbdbf
249 changed files with 15679 additions and 143 deletions

View File

@@ -0,0 +1,146 @@
import * as THREE from 'three'
const halfWidth = 1.68
const bottom = -2.24
const seam = 1.97
const top = 2.28
const midDepth = -0.09
const smooth = THREE.MathUtils.smoothstep
function wrapperTexture(back = false) {
const canvas = document.createElement('canvas')
canvas.width = 1024
canvas.height = 1400
const context = canvas.getContext('2d')!
context.fillStyle = '#263a40'
context.fillRect(0, 0, 1024, 1400)
const gradient = context.createLinearGradient(0, 0, 1024, 1400)
gradient.addColorStop(0, '#ffffff0c')
gradient.addColorStop(0.45, '#ffffff00')
gradient.addColorStop(1, '#00000022')
context.fillStyle = gradient
context.fillRect(0, 0, 1024, 1400)
context.strokeStyle = '#a59569'
context.lineWidth = 2
context.strokeRect(66, 155, 892, 1100)
context.strokeRect(80, 169, 864, 1072)
context.fillStyle = '#baa775'
context.fillRect(0, 0, 1024, 100)
context.fillRect(0, 1315, 1024, 85)
context.textAlign = 'center'
context.fillStyle = '#283339'
context.font = 'bold 19px sans-serif'
context.fillText('P U L L T O O P E N →', 512, 58)
context.fillStyle = '#d8c994'
context.save()
context.translate(512, 480)
context.rotate(Math.PI / 4)
context.strokeRect(-100, -100, 200, 200)
context.strokeRect(-80, -80, 160, 160)
context.restore()
context.font = '56px Georgia'
context.fillText('S', 512, 500)
context.font = '44px Georgia'
context.fillText('SANCTIFICATION', 512, 740)
context.font = '21px sans-serif'
context.fillText('C O L L E C T O R S E R I E S', 512, 795)
context.font = '19px sans-serif'
context.fillText(back ? 'AUTHORED EDITION / 001' : 'THREE CARDS / ONE COLLECTION', 512, 1175)
const texture = new THREE.CanvasTexture(canvas)
texture.colorSpace = THREE.SRGBColorSpace
return texture
}
/**
* Two joined pouch surfaces and one sealed ribbon, never a label over a shell.
* All deformation is evaluated from immutable UVs, not accumulated frame deltas.
*/
export function createFoilWrapper() {
const root = new THREE.Group()
root.name = 'PACK_FOIL_WRAPPER'
const frontTexture = wrapperTexture()
const frontMaterial = new THREE.MeshStandardMaterial({
map: frontTexture, roughness: 0.43, metalness: 0.68, side: THREE.DoubleSide,
})
const backMaterial = frontMaterial.clone()
backMaterial.map = wrapperTexture(true)
const stripMaterial = frontMaterial.clone()
const sheets = [1, -1].map((side) => {
const geometry = new THREE.PlaneGeometry(halfWidth * 2, seam - bottom, 64, 80)
const mesh = new THREE.Mesh(geometry, side === 1 ? frontMaterial : backMaterial)
mesh.name = side === 1 ? 'FOIL_FRONT' : 'FOIL_BACK'
// UVs span the original, uncut printed sheet; geometry UVs never change.
const uv = geometry.getAttribute('uv')
for (let i = 0; i < uv.count; i++) uv.setY(i, uv.getY(i) * (seam - bottom) / (top - bottom))
root.add(mesh)
return { mesh, side }
})
// Match the pouch's horizontal tessellation so the attached tear boundary shares every sample.
const stripGeometry = new THREE.PlaneGeometry(halfWidth * 2, top - seam, 64, 10)
const stripUV = stripGeometry.getAttribute('uv')
for (let i = 0; i < stripUV.count; i++) {
stripUV.setY(i, (seam - bottom + stripUV.getY(i) * (top - seam)) / (top - bottom))
}
const strip = new THREE.Mesh(stripGeometry, stripMaterial)
strip.name = 'FOIL_TEAR_STRIP'
root.add(strip)
function tornEdge(u: number) {
return 0.011 * Math.sin(u * Math.PI * 96) + 0.005 * Math.sin(u * 173)
}
function deform(tear: number, detach: number, mouth: number, touch: THREE.Vector3, stripExit = 4.8) {
for (const { mesh, side } of sheets) {
const geometry = mesh.geometry
const positions = geometry.getAttribute('position')
const uv = geometry.getAttribute('uv')
for (let i = 0; i < positions.count; i++) {
const u = uv.getX(i)
const y = bottom + uv.getY(i) * (top - bottom)
const x = (u * 2 - 1) * halfWidth
const across = u === 0 || u === 1 ? 0 : Math.pow(Math.sin(u * Math.PI), 0.24)
const lower = smooth(y, bottom + 0.14, bottom + 0.47)
const neck = 1 - smooth(y, 1.65, seam)
const opening = mouth * smooth(y, 0.6, seam)
const envelope = across * lower
const diagonal = Math.sin(x * 13 + y * 7) * Math.sin(y * 3.1 - x * 2)
const folds = (0.009 * diagonal + 0.003 * Math.sin(x * 28 - y * 5))
* envelope * (0.35 + 0.65 * Math.pow(Math.abs(x / halfWidth), 1.5))
const crimp = 0.013 * (1 + 0.7 * Math.sin(x * 36))
* (1 - lower) * smooth(y, bottom, bottom + 0.1) * across
const dent = side === 1 ? -0.045 * touch.z
* Math.exp(-((x - touch.x) ** 2 + (y - touch.y) ** 2) * 5) * envelope * neck : 0
const depth = 0.37 * neck + 0.64 * opening
const lip = mouth * (side === 1 ? -0.28 : 0.08) * across * smooth(y, 1, seam)
positions.setXYZ(i, x, y + tornEdge(u) * smooth(y, 1.85, seam) + lip,
midDepth + side * (depth * envelope + folds * neck + crimp) + dent)
}
positions.needsUpdate = true
geometry.computeVertexNormals()
geometry.computeBoundingSphere()
}
const positions = stripGeometry.getAttribute('position')
for (let i = 0; i < positions.count; i++) {
const u = stripUV.getX(i)
const v = (bottom + stripUV.getY(i) * (top - bottom) - seam) / (top - seam)
const length = Math.max(0, tear - u) * halfWidth * 2
const curvature = 0.55 + detach * 0.5
const angle = length * curvature
const anchor = (Math.max(tear, u) * 2 - 1) * halfWidth
const release = smooth(length, 0, 0.3)
// The released end rolls up around the moving tear front; the attached end stays sealed.
positions.setXYZ(i,
anchor - Math.sin(angle) / curvature + detach * stripExit,
seam + v * (top - seam) + tornEdge(u) * (1 - v)
+ 0.36 * (1 - Math.cos(angle)) / curvature + release * 0.035 + detach * 0.4,
midDepth + 0.012 * Math.sin((u * 2 - 1) * halfWidth * 36) * Math.sin(v * Math.PI)
- 0.85 * (1 - Math.cos(angle)) / curvature - detach * 0.65)
}
positions.needsUpdate = true
stripGeometry.computeVertexNormals()
stripGeometry.computeBoundingSphere()
strip.visible = detach < 1
}
deform(0, 0, 0, new THREE.Vector3())
return { root, deform }
}