import * as THREE from 'three' export type EnemyKind = 'drone' | 'weaver' | 'tank' export type EnemyStats = { radius: number hp: number speed: number score: number color: number } export function getEnemyStats(kind: EnemyKind): EnemyStats { switch (kind) { case 'drone': return { radius: 0.8, hp: 1, speed: 6, score: 100, color: 0xf87171 } case 'weaver': return { radius: 0.8, hp: 2, speed: 4.5, score: 150, color: 0xe879f9 } case 'tank': return { radius: 1.1, hp: 5, speed: 2.2, score: 300, color: 0xfbbf24 } default: { const unknownKind: never = kind throw new Error(`Unknown enemy kind: ${String(unknownKind)}`) } } } export const ENEMY_EMISSIVE_INTENSITY = 0.35 function createGlowMaterial(color: number, emissiveIntensity: number) { return new THREE.MeshStandardMaterial({ color, emissive: color, emissiveIntensity, flatShading: true, roughness: 0.45, metalness: 0.2, }) } const enemyGeometries: Record = { drone: new THREE.OctahedronGeometry(0.85), weaver: new THREE.TorusGeometry(0.6, 0.24, 6, 12), tank: new THREE.IcosahedronGeometry(1.15), } export function createEnemyMesh(kind: EnemyKind) { const { color } = getEnemyStats(kind) // Each enemy gets its own material so hit flashes don't affect the others. return new THREE.Mesh( enemyGeometries[kind], createGlowMaterial(color, ENEMY_EMISSIVE_INTENSITY), ) } export const playerBulletGeometry = new THREE.BoxGeometry(0.16, 0.9, 0.16) export const playerBulletMaterial = new THREE.MeshBasicMaterial({ color: 0xa5f3fc }) export type PlayerShip = { group: THREE.Group flame: THREE.Mesh } export function createPlayerShip(): PlayerShip { const group = new THREE.Group() const hull = new THREE.Mesh( new THREE.ConeGeometry(0.6, 2.2, 6), createGlowMaterial(0x7dd3fc, 0.25), ) group.add(hull) const wings = new THREE.Mesh( new THREE.BoxGeometry(2.4, 0.6, 0.15), createGlowMaterial(0x38bdf8, 0.2), ) wings.position.y = -0.4 group.add(wings) const cockpit = new THREE.Mesh( new THREE.SphereGeometry(0.28, 10, 8), createGlowMaterial(0xf0abfc, 0.6), ) cockpit.position.set(0, 0.15, 0.35) group.add(cockpit) const flame = new THREE.Mesh( new THREE.ConeGeometry(0.35, 1, 8), new THREE.MeshBasicMaterial({ color: 0xfb923c, transparent: true, opacity: 0.85 }), ) flame.rotation.z = Math.PI flame.position.y = -1.6 group.add(flame) return { group, flame } }