# Hex Tile

> A modular low-poly hexagon ground tile for hex-grid strategy and board games, with six biomes: grass, sand, water, snow, stone and lava, each with matching top details. Flat-top orientation: corners point along ±X and flat sides face ±Z, so a radius of 1 m gives 2 m corner to corner and 1.73 m flat to flat. Land tops sit at 0.5 m, water 8 cm lower.

![Hex Tile preview](https://codels.download/m/hex-tile.png)

Multi-view sheet (3/4, front, side, top): https://codels.download/m/hex-tile.sheet.png

## Facts

- Category: Nature › Terrain Pieces
- Size (W × H × D): 2.00 × 0.80 × 1.73 m — units are meters, +Y up, front faces +Z
- Origin: center of the base (y = 0)
- Triangles: 496 · Meshes: 38 · Materials: 9
- License: CC0-1.0 (Codels built-in)
- Tags: hex-tile, hexagon, tile, geometric, low-poly, stylized, desert, grassland, ocean, snow, volcanic, city-builder, puzzle, strategy, tower-defense, modular, terrain, game-ready, mobile-friendly, parametric, color-brown, color-green, size-medium, poly-low
- Page: https://codels.download/models/hex-tile

## Files

| Format | URL |
|---|---|
| three.js ES module (`createHexTile()`) | https://codels.download/m/hex-tile.js |
| React Three Fiber component (`<HexTile />`) | https://codels.download/m/hex-tile.jsx |
| Instancing module (`createHexTileInstances(count, place)`, one draw call per material) | https://codels.download/m/hex-tile.instanced.js |
| Standalone HTML demo | https://codels.download/m/hex-tile.html |
| glTF binary | https://codels.download/m/hex-tile.glb |
| glTF binary, Meshopt-compressed (2–3× smaller; GLTFLoader needs `setMeshoptDecoder(MeshoptDecoder)`) | https://codels.download/m/hex-tile.glb?compress=meshopt |
| glTF JSON | https://codels.download/m/hex-tile.gltf |
| OBJ (vertex colors) + MTL | https://codels.download/m/hex-tile.obj · https://codels.download/m/hex-tile.mtl |
| STL | https://codels.download/m/hex-tile.stl |
| CodelSpec JSON | https://codels.download/m/hex-tile.json |

## Parameters

Append them as query parameters to any URL above to get a customized variant (e.g. `?biome=...`).

| Param | Type | Range / options | Default | Current |
|---|---|---|---|---|
| `biome` | select | grass \| sand \| water \| snow \| stone \| lava | grass | grass |
| `radius` | number | 0.5 – 2 | 1 | 1 |
| `details` | boolean | true \| false | true | true |

## Parts

- surface (cylinder · top)
- body (cylinder · body)
- base (cylinder · deep)
- details (group)
  - patch1 (extrude · topDark)
  - patch2 (extrude · topLight)
  - tuft1 (group)
  - tuft2 (group)
  - tuft3 (group)
  - tuft4 (group)
  - tuft5 (group)
  - tuft6 (group)
  - flower1 (group)
  - flower2 (group)
  - flower3 (group)
  - bush (group)
  - pebble (dodecahedron · rock)

## How to use

Save the code below as `hex-tile.js`, then:

```js
import { createHexTile } from './hex-tile.js';
const model = createHexTile();
scene.add(model);
```

## Code

```js
/**
 * Hex Tile — Codels 3D model library (https://codels.download/models/hex-tile)
 * License: CC0-1.0 · Units: meters · Y-up · Front: +Z · Origin: center of the base (y = 0)
 * Triangles: 496 · Size (W × H × D): 2.00 × 0.80 × 1.73 m
 *
 * Usage:
 *   import { createHexTile } from './hex-tile.js';
 *   
 *   const model = createHexTile();
 *   model.position.set(0, 0, 0);
 *   scene.add(model);
 */
import * as THREE from 'three';

const deg = THREE.MathUtils.degToRad;

// Deterministic vertex displacement (organic low-poly look).
function jitter(geometry, amount, seed) {
  const pos = geometry.attributes.position;
  const h = (a, b, c, s) => {
    let x = Math.imul(a, 73856093) ^ Math.imul(b, 19349663) ^ Math.imul(c, 83492791) ^ Math.imul(s, 2654435761);
    x = Math.imul(x ^ (x >>> 16), 2246822507);
    x = Math.imul(x ^ (x >>> 13), 3266489909);
    return ((x ^ (x >>> 16)) >>> 0) / 4294967295 - 0.5;
  };
  for (let i = 0; i < pos.count; i++) {
    const x = pos.getX(i), y = pos.getY(i), z = pos.getZ(i);
    const qx = Math.round(x * 1000), qy = Math.round(y * 1000), qz = Math.round(z * 1000);
    pos.setXYZ(i, x + h(qx, qy, qz, seed) * 2 * amount, y + h(qx, qy, qz, seed + 1) * 2 * amount, z + h(qx, qy, qz, seed + 2) * 2 * amount);
  }
  pos.needsUpdate = true;
  geometry.computeVertexNormals();
  return geometry;
}

// Extrude a 2D outline along Z (centered). bevel = [thickness, size, segments].
function extrude(outline, depth, holes = [], bevel = null) {
  const shape = new THREE.Shape(outline.map(([x, y]) => new THREE.Vector2(x, y)));
  for (const hole of holes) shape.holes.push(new THREE.Path(hole.map(([x, y]) => new THREE.Vector2(x, y))));
  const options = bevel
    ? { depth, bevelEnabled: true, bevelThickness: bevel[0], bevelSize: bevel[1], bevelSegments: bevel[2] }
    : { depth, bevelEnabled: false };
  return new THREE.ExtrudeGeometry(shape, options).translate(0, 0, -depth / 2);
}

export function createHexTile() {
  const root = new THREE.Group();
  root.name = 'HexTile';

  // Materials
  const topMat = new THREE.MeshStandardMaterial({ name: 'top', color: 0x6aae45, roughness: 0.9, flatShading: true });
  const topLightMat = new THREE.MeshStandardMaterial({ name: 'topLight', color: 0x7fb95f, roughness: 0.85, flatShading: true });
  const topDarkMat = new THREE.MeshStandardMaterial({ name: 'topDark', color: 0x578f39, roughness: 0.9, flatShading: true });
  const bodyMat = new THREE.MeshStandardMaterial({ name: 'body', color: 0x8b5d3b, roughness: 0.95, flatShading: true });
  const deepMat = new THREE.MeshStandardMaterial({ name: 'deep', color: 0x6a4630, roughness: 0.95, flatShading: true });
  const rockMat = new THREE.MeshStandardMaterial({ name: 'rock', color: 0x8e8a83, roughness: 0.9, flatShading: true });
  const greenMat = new THREE.MeshStandardMaterial({ name: 'green', color: 0x3f7a33, roughness: 0.9, flatShading: true });
  const accentMat = new THREE.MeshStandardMaterial({ name: 'accent', color: 0xfff3c4, roughness: 0.7, flatShading: true });
  const accent2Mat = new THREE.MeshStandardMaterial({ name: 'accent2', color: 0xf58fb0, roughness: 0.6, flatShading: true });

  // Shared geometries
  const cylinderGeo = new THREE.CylinderGeometry(0.012, 0.016, 0.18, 4);
  const dodecahedronGeo = new THREE.DodecahedronGeometry(0.0468, 0);
  const coneGeo = new THREE.ConeGeometry(0.0288, 0.072, 3);

  // Parts
  const surface = new THREE.Mesh(new THREE.CylinderGeometry(1, 1, 0.12, 6, 1, false, Math.PI / 6), topMat);
  surface.name = 'surface';
  surface.position.set(0, 0.44, 0);
  root.add(surface);
  const body = new THREE.Mesh(new THREE.CylinderGeometry(0.97, 0.97, 0.209, 6, 1, false, Math.PI / 6), bodyMat);
  body.name = 'body';
  body.position.set(0, 0.2755, 0);
  root.add(body);
  const base = new THREE.Mesh(new THREE.CylinderGeometry(0.97, 0.97, 0.171, 6, 1, false, Math.PI / 6), deepMat);
  base.name = 'base';
  base.position.set(0, 0.0855, 0);
  root.add(base);
  const details = new THREE.Group();
  details.name = 'details';
  root.add(details);
  const patch1 = new THREE.Mesh(extrude([[0.4717, 0.0394], [0.2938, 0.0427], [0.0573, 0.1736], [0.0771, 0.3403], [0.2723, 0.4612], [0.4358, 0.4618], [0.5476, 0.2406]], 0.01), topDarkMat);
  patch1.name = 'patch1';
  patch1.position.set(0, 0.505, 0);
  patch1.rotation.set(deg(-90), 0, 0);
  details.add(patch1);
  const patch2 = new THREE.Mesh(extrude([[-0.0732, -0.3011], [-0.2387, -0.3727], [-0.4988, -0.2549], [-0.56, -0.1181], [-0.4127, 0.0316], [-0.1184, 0.0082], [-0.0405, -0.1544]], 0.01), topLightMat);
  patch2.name = 'patch2';
  patch2.position.set(0, 0.505, 0);
  patch2.rotation.set(deg(-90), 0, 0);
  details.add(patch2);
  const tuft1 = new THREE.Group();
  tuft1.name = 'tuft1';
  tuft1.position.set(-0.3031, 0.5, 0.3029);
  details.add(tuft1);
  const cone8 = new THREE.Mesh(new THREE.ConeGeometry(0.048, 0.1924, 3), topDarkMat);
  cone8.position.set(0.041, 0.0762, 0.0137);
  cone8.rotation.set(deg(25.3383), deg(50.8657), deg(-25.1413));
  cone8.scale.set(1, 1, 0.45);
  tuft1.add(cone8);
  const cone9 = new THREE.Mesh(new THREE.ConeGeometry(0.048, 0.1993, 3), topMat);
  cone9.position.set(-0.0347, 0.0797, 0.0257);
  cone9.rotation.set(deg(-140.2443), deg(63.3564), deg(147.906));
  cone9.scale.set(1, 1, 0.45);
  tuft1.add(cone9);
  const cone10 = new THREE.Mesh(new THREE.ConeGeometry(0.048, 0.1667, 3), greenMat);
  cone10.position.set(-0.0181, 0.0634, -0.0392);
  cone10.rotation.set(deg(-13.7635), deg(5.5164), deg(6.0235));
  cone10.scale.set(1, 1, 0.45);
  tuft1.add(cone10);
  const tuft2 = new THREE.Group();
  tuft2.name = 'tuft2';
  tuft2.position.set(0.1043, 0.5, -0.3634);
  details.add(tuft2);
  const cone12 = new THREE.Mesh(new THREE.ConeGeometry(0.048, 0.1797, 3), topDarkMat);
  cone12.position.set(0.0429, 0.0699, -0.0047);
  cone12.rotation.set(deg(-0.3726), deg(4.9327), deg(-15.981));
  cone12.scale.set(1, 1, 0.45);
  tuft2.add(cone12);
  const cone13 = new THREE.Mesh(new THREE.ConeGeometry(0.048, 0.1834, 3), topMat);
  cone13.position.set(-0.0003, 0.0717, 0.0432);
  cone13.rotation.set(deg(17.6008), deg(18.2339), deg(0.1169));
  cone13.scale.set(1, 1, 0.45);
  tuft2.add(cone13);
  const cone14 = new THREE.Mesh(new THREE.ConeGeometry(0.048, 0.1856, 3), greenMat);
  cone14.position.set(-0.043, 0.0728, 0.0041);
  cone14.rotation.set(deg(-3.5682), deg(14.0817), deg(22.5281));
  cone14.scale.set(1, 1, 0.45);
  tuft2.add(cone14);
  const cone15 = new THREE.Mesh(new THREE.ConeGeometry(0.048, 0.1827, 3), topDarkMat);
  cone15.position.set(-0.0163, 0.0713, -0.04);
  cone15.rotation.set(deg(-16.2697), deg(15.7195), deg(6.114));
  cone15.scale.set(1, 1, 0.45);
  tuft2.add(cone15);
  const tuft3 = new THREE.Group();
  tuft3.name = 'tuft3';
  tuft3.position.set(-0.1928, 0.5, 0.101);
  details.add(tuft3);
  const cone17 = new THREE.Mesh(new THREE.ConeGeometry(0.048, 0.223, 3), topDarkMat);
  cone17.position.set(0.0379, 0.0915, -0.0207);
  cone17.rotation.set(deg(6.2912), deg(41.3223), deg(-23.9996));
  cone17.scale.set(1, 1, 0.45);
  tuft3.add(cone17);
  const cone18 = new THREE.Mesh(new THREE.ConeGeometry(0.048, 0.244, 3), topMat);
  cone18.position.set(-0.0325, 0.102, 0.0284);
  cone18.rotation.set(deg(-146.2924), deg(59.88), deg(154.3996));
  cone18.scale.set(1, 1, 0.45);
  tuft3.add(cone18);
  const cone19 = new THREE.Mesh(new THREE.ConeGeometry(0.048, 0.2394, 3), greenMat);
  cone19.position.set(-0.0118, 0.0997, -0.0416);
  cone19.rotation.set(deg(-16.8488), deg(34.4344), deg(4.8096));
  cone19.scale.set(1, 1, 0.45);
  tuft3.add(cone19);
  const tuft4 = new THREE.Group();
  tuft4.name = 'tuft4';
  tuft4.position.set(0.2107, 0.5, -0.1535);
  details.add(tuft4);
  const cone21 = new THREE.Mesh(new THREE.ConeGeometry(0.048, 0.1878, 3), topDarkMat);
  cone21.position.set(0.043, 0.0739, 0.0042);
  cone21.rotation.set(deg(3.287), deg(10.7682), deg(-11.4784));
  cone21.scale.set(1, 1, 0.45);
  tuft4.add(cone21);
  const cone22 = new THREE.Mesh(new THREE.ConeGeometry(0.048, 0.2226, 3), topMat);
  cone22.position.set(-0.0028, 0.0913, 0.0431);
  cone22.rotation.set(deg(9.4257), deg(81.1143), deg(6.6417));
  cone22.scale.set(1, 1, 0.45);
  tuft4.add(cone22);
  const cone23 = new THREE.Mesh(new THREE.ConeGeometry(0.048, 0.2123, 3), greenMat);
  cone23.position.set(-0.0395, 0.0861, 0.0175);
  cone23.rotation.set(deg(-0.5782), deg(25.4893), deg(18.7119));
  cone23.scale.set(1, 1, 0.45);
  tuft4.add(cone23);
  const cone24 = new THREE.Mesh(new THREE.ConeGeometry(0.048, 0.2367, 3), topDarkMat);
  cone24.position.set(0.0107, 0.0984, -0.0419);
  cone24.rotation.set(deg(2.1663), deg(77.3768), deg(-18.0181));
  cone24.scale.set(1, 1, 0.45);
  tuft4.add(cone24);
  const tuft5 = new THREE.Group();
  tuft5.name = 'tuft5';
  tuft5.position.set(-0.2142, 0.5, 0.2096);
  details.add(tuft5);
  const cone26 = new THREE.Mesh(new THREE.ConeGeometry(0.048, 0.2261, 3), topDarkMat);
  cone26.position.set(0.0431, 0.0931, -0.0026);
  cone26.rotation.set(deg(141.3569), deg(58.5614), deg(-138.1705));
  cone26.scale.set(1, 1, 0.45);
  tuft5.add(cone26);
  const cone27 = new THREE.Mesh(new THREE.ConeGeometry(0.048, 0.1951, 3), topMat);
  cone27.position.set(-0.0292, 0.0775, 0.0319);
  cone27.rotation.set(deg(7.9667), deg(9.9352), deg(8.7159));
  cone27.scale.set(1, 1, 0.45);
  tuft5.add(cone27);
  const cone28 = new THREE.Mesh(new THREE.ConeGeometry(0.048, 0.1669, 3), greenMat);
  cone28.position.set(-0.0059, 0.0635, -0.0428);
  cone28.rotation.set(deg(173.2223), deg(77.6077), deg(168.4115));
  cone28.scale.set(1, 1, 0.45);
  tuft5.add(cone28);
  const tuft6 = new THREE.Group();
  tuft6.name = 'tuft6';
  tuft6.position.set(0.1284, 0.5, -0.1911);
  details.add(tuft6);
  const cone30 = new THREE.Mesh(new THREE.ConeGeometry(0.048, 0.2427, 3), topDarkMat);
  cone30.position.set(0.0425, 0.1013, 0.0078);
  cone30.rotation.set(deg(130.0297), deg(69.5196), deg(-125.1185));
  cone30.scale.set(1, 1, 0.45);
  tuft6.add(cone30);
  const cone31 = new THREE.Mesh(new THREE.ConeGeometry(0.048, 0.1997, 3), topMat);
  cone31.position.set(-0.0143, 0.0799, 0.0408);
  cone31.rotation.set(deg(10.9485), deg(41.2069), deg(7.249));
  cone31.scale.set(1, 1, 0.45);
  tuft6.add(cone31);
  const cone32 = new THREE.Mesh(new THREE.ConeGeometry(0.048, 0.1582, 3), greenMat);
  cone32.position.set(-0.0426, 0.0591, 0.0074);
  cone32.rotation.set(deg(-39.7414), deg(60.1891), deg(47.7368));
  cone32.scale.set(1, 1, 0.45);
  tuft6.add(cone32);
  const cone33 = new THREE.Mesh(new THREE.ConeGeometry(0.048, 0.2001, 3), topDarkMat);
  cone33.position.set(0.0109, 0.0801, -0.0418);
  cone33.rotation.set(deg(6.3458), deg(78.7871), deg(-25.5541));
  cone33.scale.set(1, 1, 0.45);
  tuft6.add(cone33);
  const flower1 = new THREE.Group();
  flower1.name = 'flower1';
  flower1.position.set(-0.5107, 0.5, -0.3568);
  details.add(flower1);
  const cylinder35 = new THREE.Mesh(cylinderGeo, greenMat);
  cylinder35.position.set(0, 0.07, 0);
  flower1.add(cylinder35);
  const dodecahedron36 = new THREE.Mesh(dodecahedronGeo, accentMat);
  dodecahedron36.position.set(0, 0.17, 0);
  dodecahedron36.scale.set(1, 0.6, 1);
  flower1.add(dodecahedron36);
  const cone37 = new THREE.Mesh(coneGeo, greenMat);
  cone37.position.set(0.0144, 0.0252, 0);
  cone37.rotation.set(0, deg(185.7646), deg(-40));
  cone37.scale.set(1, 1, 0.4);
  flower1.add(cone37);
  const flower2 = new THREE.Group();
  flower2.name = 'flower2';
  flower2.position.set(-0.4265, 0.5, 0.1462);
  details.add(flower2);
  const cylinder39 = new THREE.Mesh(cylinderGeo, greenMat);
  cylinder39.position.set(0, 0.07, 0);
  flower2.add(cylinder39);
  const dodecahedron40 = new THREE.Mesh(dodecahedronGeo, accent2Mat);
  dodecahedron40.position.set(0, 0.17, 0);
  dodecahedron40.scale.set(1, 0.6, 1);
  flower2.add(dodecahedron40);
  const cone41 = new THREE.Mesh(coneGeo, greenMat);
  cone41.position.set(0.0144, 0.0252, 0);
  cone41.rotation.set(0, deg(103.3109), deg(-40));
  cone41.scale.set(1, 1, 0.4);
  flower2.add(cone41);
  const flower3 = new THREE.Group();
  flower3.name = 'flower3';
  flower3.position.set(-0.1494, 0.5, -0.4188);
  details.add(flower3);
  const cylinder43 = new THREE.Mesh(cylinderGeo, greenMat);
  cylinder43.position.set(0, 0.07, 0);
  flower3.add(cylinder43);
  const dodecahedron44 = new THREE.Mesh(dodecahedronGeo, accentMat);
  dodecahedron44.position.set(0, 0.17, 0);
  dodecahedron44.scale.set(1, 0.6, 1);
  flower3.add(dodecahedron44);
  const cone45 = new THREE.Mesh(coneGeo, greenMat);
  cone45.position.set(0.0144, 0.0252, 0);
  cone45.rotation.set(0, deg(125.2183), deg(-40));
  cone45.scale.set(1, 1, 0.4);
  flower3.add(cone45);
  const bush = new THREE.Group();
  bush.name = 'bush';
  bush.position.set(-0.3079, 0.47, -0.3866);
  details.add(bush);
  const icosahedron47 = new THREE.Mesh(jitter(new THREE.IcosahedronGeometry(0.2, 0), 0.02, 3), greenMat);
  icosahedron47.position.set(0, 0.15, 0);
  bush.add(icosahedron47);
  const icosahedron48 = new THREE.Mesh(jitter(new THREE.IcosahedronGeometry(0.14, 0), 0.015, 4), topDarkMat);
  icosahedron48.position.set(0.15, 0.1, 0.05);
  bush.add(icosahedron48);
  const pebble = new THREE.Mesh(jitter(new THREE.DodecahedronGeometry(0.07, 0), 0.0126, 437), rockMat);
  pebble.name = 'pebble';
  pebble.position.set(-0.2622, 0.5234, -0.4648);
  pebble.rotation.set(deg(0.3929), deg(175.0807), deg(5.2691));
  pebble.scale.set(1.1326, 0.4783, 1);
  details.add(pebble);

  root.traverse((o) => {
    if (o.isMesh) {
      o.castShadow = true;
      o.receiveShadow = true;
    }
  });
  return root;
}

export default createHexTile;
```

---
Codels — 3D models as code for three.js. Search more: https://codels.download/api/v1/models?q=<query> · Guide: https://codels.download/llms.txt
