|
| 1 | +import { ExtrudeGeometry, InstancedMesh, Material, MeshStandardMaterial, Object3D, Shape } from "three"; |
| 2 | + |
| 3 | +export interface HexagonalTileOptions { |
| 4 | + width: number; // Total area width to fill (x-axis) |
| 5 | + depth: number; // Total area depth to fill (z-axis) |
| 6 | + height: number; // Height of each tile (y-axis) |
| 7 | + radius: number; // Radius of each hexagonal tile |
| 8 | + gap: number; // Gap spacing between tiles |
| 9 | + material?: Material; // Optional custom material |
| 10 | +} |
| 11 | + |
| 12 | +/** |
| 13 | + * Hexagonal tile pattern factory. |
| 14 | + * |
| 15 | + * Example usage: |
| 16 | + * ```ts |
| 17 | + * const hexTile = createHexagonalTile({ |
| 18 | + * width: 10, |
| 19 | + * depth: 10, |
| 20 | + * height: 0.01, |
| 21 | + * radius: 0.1, |
| 22 | + * gap: 0.01, |
| 23 | + * material: new THREE.MeshStandardMaterial({ color: ColorPalette.White }), |
| 24 | + * }); |
| 25 | + * |
| 26 | + * scene.add(hexTile); |
| 27 | + * ``` |
| 28 | + */ |
| 29 | +export function createHexagonalTile(options: HexagonalTileOptions): InstancedMesh { |
| 30 | + const { width, depth, height, radius, gap, material } = options; |
| 31 | + |
| 32 | + const tileMaterial = material || new MeshStandardMaterial({ color: 0x8b4513 }); // Default earthy color |
| 33 | + |
| 34 | + // Effective spacing between hexagon centers, including the gap |
| 35 | + const spacingX = (radius * 3) / 2 + gap; // Horizontal distance between hex centers |
| 36 | + const spacingZ = Math.sqrt(3) * radius + gap; // Vertical distance between hex centers |
| 37 | + |
| 38 | + // Calculate the number of tiles that fit within the area |
| 39 | + const hexTileCountX = Math.floor(width / spacingX); |
| 40 | + const hexTileCountZ = Math.floor(depth / spacingZ); |
| 41 | + |
| 42 | + const hexTileCount = hexTileCountX * hexTileCountZ; |
| 43 | + |
| 44 | + // Create a hexagonal prism geometry |
| 45 | + const hexShape = new Shape(); |
| 46 | + for (let i = 0; i < 6; i++) { |
| 47 | + const angle = (Math.PI / 3) * i; // 60-degree increments |
| 48 | + const x = Math.cos(angle) * radius; |
| 49 | + const y = Math.sin(angle) * radius; |
| 50 | + |
| 51 | + if (i === 0) hexShape.moveTo(x, y); |
| 52 | + else hexShape.lineTo(x, y); |
| 53 | + } |
| 54 | + hexShape.closePath(); |
| 55 | + const geometry = new ExtrudeGeometry(hexShape, { depth: height, bevelEnabled: false }); |
| 56 | + |
| 57 | + // Rotate geometry so tiles lay flat |
| 58 | + geometry.rotateX(-Math.PI / 2); |
| 59 | + |
| 60 | + // Create the instanced mesh |
| 61 | + const instancedMesh = new InstancedMesh(geometry, tileMaterial, hexTileCount); |
| 62 | + |
| 63 | + const dummy = new Object3D(); |
| 64 | + let index = 0; |
| 65 | + |
| 66 | + for (let x = 0; x < hexTileCountX; x++) { |
| 67 | + for (let z = 0; z < hexTileCountZ; z++) { |
| 68 | + // Calculate the staggered row offset |
| 69 | + const offsetX = x * spacingX; |
| 70 | + const offsetZ = z * spacingZ + (x % 2) * (spacingZ / 2); // Stagger odd rows by half a tile |
| 71 | + |
| 72 | + // Center the grid |
| 73 | + const positionX = offsetX - (hexTileCountX * spacingX) / 2 + spacingX / 2; |
| 74 | + const positionZ = offsetZ - (hexTileCountZ * spacingZ) / 2 + spacingZ / 2; |
| 75 | + |
| 76 | + dummy.position.set(positionX, 0, positionZ); |
| 77 | + dummy.updateMatrix(); |
| 78 | + instancedMesh.setMatrixAt(index++, dummy.matrix); |
| 79 | + } |
| 80 | + } |
| 81 | + |
| 82 | + instancedMesh.instanceMatrix.needsUpdate = true; |
| 83 | + |
| 84 | + return instancedMesh; |
| 85 | +} |
0 commit comments