Chess3D is a 3D chess sandbox built entirely from scratch in C++ and DirectX 11 on top of the custom Eurekiel engine. The project replicates a full chess experience with multiplayer support, real-time Blinn-Phong rendering with normal mapping, an OBJ model loader that automatically deduces and corrects missing tangent/bitangent/normal data, and a data-driven architecture where every chess piece, board layout, and match rule is defined in XML with no recompilation needed.
Core Technical Highlights
Chess Gameplay & Rules
The chess rule engine enforces all standard movement constraints per piece type. Each ChessPieceDefinition is loaded from XML with a glyph, slide flag, and movement pattern, then the ChessMatch controller validates every move against the current board state. Pieces animate smoothly between tiles using an easing-based interpolation system, and the turn manager alternates control between factions after each valid move.
When a piece is selected, the board highlights all legal destination tiles in real time. The highlight system queries the piece’s movement definition, filters out occupied-by-friendly squares, and renders translucent overlays on valid cells. This gives players immediate visual feedback before committing a move.
3D Raycasting & Piece Selection
Piece selection uses a 3D raycast from the camera through the mouse cursor into world space. The ray is tested against each piece’s collision volume (defined per-piece in ChessPieceDefinition.xml with radius and height), and the closest hit is selected. This same system drives the tile-targeting for move destinations.
Blinn-Phong Rendering Pipeline
The rendering pipeline implements Blinn-Phong shading with full normal mapping support. Each mesh samples three texture maps (diffuse, normal, and a packed specular-gloss-emissive SGE map) and computes lighting in world space using a TBN (Tangent-Bitangent-Normal) matrix constructed per vertex.
The material pipeline supports per-object shader and texture assignment through the MeshComponent, which references diffuse, normal, and specular-gloss-emissive texture files. This three-map approach packs specular intensity, gloss (smoothness), and emissive into a single RGB texture, reducing draw calls while preserving material richness.
Shader Debug Visualization
The engine exposes 16+ shader debug modes through a ShaderDebugType enum, allowing real-time inspection of every stage in the rendering pipeline. Cycling through these modes reveals the raw data flowing through the shader, from diffuse color and UV coordinates to per-pixel normals in TBN space and world space, surface tangent/bitangent vectors, and the final lighting result.
enum class ShaderDebugType
{
Lit = 0, DiffuseColor, SurfaceColor,
UVCoords, SurfaceTangentModelSpace, SurfaceBitangentModelSpace,
SurfaceNormalModelSpace, NormalColor, PixelNormalTBNSpace,
PixelNormalWorldSpace, Lighting, SurfaceTangentWorldSpace,
SurfaceBitangentWorldSpace, SurfaceNormalWorldSpace,
ModelIBasisWorld, ModelJBasisWorld, ModelKBasisWorld
};
These debug views were essential during development for diagnosing normal mapping artifacts, verifying TBN matrix correctness, and validating the lighting model. The engine also provides higher-level DebugViewMode toggles (Lit, Unlit, Wireframe, Lighting-only) for quick visual checks.
OBJ Model Loader with TBN Correction
The OBJ loader parses .obj files into the engine’s Vertex_PCUTBN format (Position, Color, UV, Tangent, Bitangent, Normal). A key feature is its automatic deduction and correction of missing TBN components. If an OBJ file lacks tangent or bitangent data, the loader computes them from the existing normals and UV coordinates using cross-product reconstruction, ensuring every loaded model is ready for normal-mapped rendering without manual preprocessing.
Multiplayer Networking
The NetworkDispatcher implements a command-based multiplayer protocol over TCP. It supports multiple message boundary modes (NULL_TERMINATED, RAW_BYTES, LENGTH_PREFIXED) and handles message buffering across frames for incomplete packets. The server accepts up to 20 concurrent client connections.
sequenceDiagram
participant CA as Client A
participant S as Server
participant CB as Client B
CA->>S: Connect
CB->>S: Connect
CA->>S: MovePiece A2 A4
S->>S: ExecuteCommand with remote flag
S->>CB: Broadcast state update
CB->>CB: ExecuteCommand with remote flag
Multiplayer commands flow through the engine’s dev console system. The NetworkDispatcher receives a raw string command, appends a remote=true flag, and executes it through the same console pipeline used for local commands. This unified approach means every game action (move piece, reset board, change camera) works identically in single-player and multiplayer without separate code paths.
Data-Driven Architecture
The entire game configuration lives in XML files under Run/Data/, making it possible to modify chess rules, board layouts, piece visuals, and server settings without recompiling. Three key definition files drive the system:
ChessPieceDefinition.xml defines each piece type with its glyph, movement behavior (slide flag), collision volume, and visual components:
<ChessPieceDefinition name="Knight" glyph="N" slide="false">
<Collision radius="0.05" height="0.6"
collidesWithWorld="true" collidesWithActors="false"/>
<Components>
<Component type="MeshComponent" bakeModel="Knight" renderLit="true"
shader="Data/Shaders/Diffuse"
texture="Data\Images\FunkyBricks_d.png"
normal="Data\Images\FunkyBricks_n.png"
specGlossEmit="Data\Images\FunkyBricks_sge.png"/>
</Components>
</ChessPieceDefinition>
ChessMatchConfig.xml defines the board layout, faction colors, camera positions, and initial piece placement using standard chess notation:
<ChessBoard texture="Data\Images\Bricks_d.png" normal="Data\Images\Bricks_n.png"
specGlossEmit="Data\Images\Bricks_sge.png" shader="Data/Shaders/Diffuse">
<Factions>
<Faction display="Player 0" id="0" color="161,40,35"
viewPosition="4,-1.5,4" viewOrientation="90,45,0"/>
<Faction display="Player 1" id="1" color="85,110,28"
viewPosition="4,9.5,4" viewOrientation="-90,45,0"/>
</Factions>
<ChessPieces>
<ChessPiece name="King" position="E1" faction="0"/>
<!-- ... full board layout ... -->
</ChessPieces>
</ChessBoard>
GameConfig.xml controls screen resolution, world dimensions, and debug settings.
System Architecture
graph TD
subgraph Engine["Eurekiel Engine Core"]
RS[RenderSubsystem]
NS[NetworkSubsystem]
WS[WidgetSubsystem]
LS[LoggerSubsystem]
IS[InputSystem]
AS[AudioSystem]
end
subgraph Rendering["Rendering Pipeline"]
BP[BlinnPhong Shader]
PP[Post-Processing Chain]
BL[EffectBloom]
DM[Debug Modes]
PP --> BL
end
subgraph GameLogic["Chess Game Logic"]
CM[ChessMatch]
CB[ChessBoard]
CP[ChessPiece]
CM --> CB
CM --> CP
end
subgraph DataDriven["Data-Driven Layer"]
PD[ChessPieceDefinition.xml]
MC[ChessMatchConfig.xml]
GC[GameConfig.xml]
end
subgraph ActorSystem["Actor-Component System"]
AC[Actor]
MC2[MeshComponent]
CC[CollisionComponent]
AC --> MC2
AC --> CC
end
RS --> BP
RS --> PP
BP --> DM
DataDriven --> GameLogic
GameLogic --> ActorSystem
ActorSystem --> RS
NS --> CM
IS --> CM
Design Philosophy
Several principles guided the architecture of Chess3D:
Data Over Code — Every tunable parameter lives in XML. Piece definitions, board layouts, faction colors, camera angles, and server settings are all externalized. This separation means gameplay iteration happens in a text editor, not a compiler — the same philosophy used by production engines to empower non-programmers.
Unified Command Pipeline — Local and remote actions share a single execution path through the dev console. A move command typed locally and one received over the network both flow through ExecuteCommand() with only a remote=true flag distinguishing them. This eliminates an entire class of desync bugs and keeps the multiplayer codebase minimal.
Debug-First Rendering — The 16+ shader debug modes were not an afterthought — they were built alongside the Blinn-Phong pipeline. Being able to visualize raw normals, tangent vectors, and UV coordinates in real time made it possible to diagnose and fix TBN issues in the OBJ loader without guesswork.
Defensive Model Loading — Rather than requiring artists to export perfect data, the OBJ loader assumes incomplete input and reconstructs what’s missing. If tangents or bitangents are absent, they’re computed from normals and UVs. This robustness means any valid OBJ file renders correctly with normal mapping on first import.
Component Composition — The Actor-Component system uses template-based type-safe queries (AddComponent<T>, GetComponent<T>) to compose behavior. Chess pieces, board tiles, and debug visualizers are all Actors with different component configurations — no deep inheritance hierarchies.