← Back to ThetaVox Products
ThetaVox

Hex Planet System

Documentation

About

Create and paint hexagonal planets with tiles, props, rivers, and terrain deformation. A complete planetary generation system for strategy games and simulations.

Key Features

Technical Details

Getting Started Guide

Creating Your First Planet

To create a planet, you first must create a tileset in Window → Planet → Tileset Editor. There you can create new tilesets or edit existing ones. Once complete, go to your scene hierarchy and go to Create → 3D Object → Planet. This is where you will create or load planets into your scene. Frequency affects the subdivision process and controls how many tiles your planet will have. You can pick a frequency using the slider and it'll display your tile count. Once you have chosen your subdivision frequency, load your tileset and press create planet.

Now you can go to Window → Planet → Planet Painter. There you can apply your tiles from the tileset to your planet, and place props, rivers, and deform the terrain. If the editor was reloaded or a recompile has occurred, you will have to click "Regenerate Mesh" in the painter tool in order to reload the tile information.

Working with Props

Props use a Prop Palette much like tiles use a tileset. Creating a new palette can be done on the props tab of the planet painter, or optionally from your asset window with Create → Planet → Prop Palette. But first you must create your Prop Type from Create → Planet → Prop Type. Prop types, such as trees, can have multiple prefabs assigned to it which will be randomized on generation. Sizing your prefab will take some care. The size is a factor of tile size, so once set, it will be properly sized for planets of all sizes and subdivision levels.

Rivers

The River tab requires a river material to continue. Keep in mind that rivers have a pathfinding modifier which can be modified in Pathfinding.cs.

Editor Tip

To rotate your view on the planet, select it, press F to focus, hold alt and then left click to rotate and right click to zoom.

Additional Details

If loading a planet with over 10k props, the time it takes would be significant without the use of the editor optimizations and the runtime use of the prop registry. After creating a prop, update the registry by going to Tools → Planet → Build Prop Type Registry. If you press play with props not in the registry, you may notice it works fine. However, you will get an error stating that it's working because you're using the editor fast load optimizations and that it will not work (missing props won't load) in a build version of the game. You have an option of using the Resources location for your props if you wish to bypass the use of the registry and its optimizations.

Icosphere Template Creator

To create planets with frequencies greater than 50 you must use a template. Without a template, planet load times would be long. To create a template, go to Tools → Planet → Generate Icosphere Templates. Here you can generate the data that can assist you later when creating and loading planets by dramatically reducing their load times. When creating or editing a planet's settings select Use Template. If the drop down is empty, you need to create one from the path above. Even a <50 frequency planet can take a few seconds to load and can benefit from a template being created to assist. Each template can range from a few megabytes to over 10MB.

Final Details

Planet Architecture Guide

Icosahedron Generator Overview

The planet mesh generation follows a two-step process:

  1. Subdivision: Start with a base icosahedron (20-sided polyhedron) and subdivide each triangular face based on frequency. Higher frequency = more subdivisions = more triangles.
  2. Dual Mesh Generation: Generate a dual mesh by computing centroids of the subdivided icosahedron triangles. These centroids become vertices of the tile mesh, creating hexagonal tiles (with 12 pentagons at original vertices) covering the sphere uniformly.

Key Data Structures: Understanding the Lists

The mesh generation uses several key lists to organize geometry data:

1. ORIGINAL ICOSAHEDRON (subdivision output):

vertices (List<Vector3>)    // The subdivided icosahedron vertices
triangles (List<int>)       // Triangle indices for the subdivided icosahedron mesh

2. DUAL MESH VERTICES (the append-based structure):

baseUnitDualVertices (Vector3[])    // THE master vertex array with TWO parts

PART 1 [indices 0 to centroidVertexCount-1]:
Centroids of the subdivided icosahedron triangles. These form the corners/edges where tiles meet. Shared between neighboring tiles.

PART 2 [indices centroidVertexCount onwards]:
Center vertices, one per tile (indexed by tile.id). Used for fan triangulation to render each tile as a set of triangles radiating from its center.

Each Tile object contains:

id                    // Unique identifier for this tile
vertexIndices         // Points into PART 1 (the centroids that form this tile's boundary)
centerVertexIndex     // Direct index into PART 2 for this tile's center vertex
triangleIndices       // The triangles that make up this tile's visual mesh (fan pattern)
neighborIds           // List of adjacent tile IDs
tileTypeGuid          // The painted tile type (grass, water, etc.)
prop                  // Optional PropInstance (trees, rocks, etc.)
riverIds              // Rivers flowing along this tile's edges

The Tile class (Tile.cs) can be expanded to handle game-specific logic such as structures being built, resource generation, unit occupancy, fog of war, or any other per-tile state needed for your game.

For spawning structures on tiles, use these IcosahedronGenerator methods:

GetAverageTileSize()
// Returns typical tile edge length for scaling structure footprints

GetTileCenterPosition(tile)
// Gets the tile's deformed center with height applied

GetSurfacePositionWithinTile(tile, position)
// Projects position onto tile surface
//   - Normalizes input to angular direction (discards radial/height component)
//   - Uses math to calculate ray-plane intersection with deformed triangle geometry
//   - Returns exact 3D surface position - used for jittered prop placement

SampleNormalAtUnitDir(unitDir)
// Gets interpolated surface normal for rotation

Pathfinding System

Pathfinding.cs - Core A* Algorithm

TilePathfinder.cs - MonoBehaviour Wrapper

Core API:

FindPath(startTile, endTile)                // Basic path
FindPath(startTileId, endTileId)            // Path by ID
FindPath(tile, tile, AgentType)             // Agent-specific pathfinding
IsTilePassable(tile, AgentType)             // Check walkability
GetNeighbors(tile)                          // Get adjacent tiles

PathAgent.cs - Movement Execution

Core API:

SetPath(path, planet)    // Start moving along path
TilesPerSecond           // Speed control (auto-scales with planet size)
OnTileEntered            // Event fired when entering each tile
OnPathComplete           // Event fired when reaching destination

Territory System

Creating Territories

Auto-assign materials (random cycling):

Territory t = territories.CreateTerritory(tiles);

Automatically assigns materials from the pool using random selection. Tiles parameter can be null for dynamic addition later.

Custom materials:

Material borderMat = /* your material */;
Material fillMat = /* your material */;
Territory t = territories.CreateTerritory(tiles, borderMat, fillMat);

Managing Territory Tiles

Add tiles incrementally:

territory.AddTile(tileId);
territories.RefreshTerritory(territory);

Remove tiles incrementally:

territory.RemoveTile(tileId);
territories.RefreshTerritory(territory);

Replace all tiles at once:

territories.SetTerritoryTiles(territoryId, newTileList);

Remove tile and update lookup:

territories.RemoveTileFromTerritory(territory, tileId);

Querying Territories

// Find which territory contains a tile
Territory t = territories.GetTerritoryForTile(tileId);  // Returns Territory or null
int id = territories.GetTerritoryIdForTile(tileId);     // Returns ID or -1

// Get territory by ID
Territory t = territories.GetTerritory(territoryId);

// Get all territories
List<Territory> allTerritories = territories.GetAllTerritories();

// Check if territory contains tile
bool contains = territory.ContainsTile(tileId);

Visual Control & Cleanup

// Show/hide territory
territory.Show();
territory.Hide();

// Destroy single territory
territories.DestroyTerritory(territory);

// Clear all territories
territories.ClearAllTerritories();

Setup Requirements

Place material pairs in Resources/Materials/ folder with sequential naming (gap-free):

Works with any number of material pairs (minimum 1 required). System stops loading at first missing pair.