diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..6f83e9e --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,235 @@ +# AGENTS.md — RTS Civilization Game Engine Architecture & Roadmap + +This document serves as the primary technical specification, architecture reference, and development guide for AI agents and developers implementing an **RTS Civilization** game (Real-Time Strategy / 4X hybrid) on top of this hexagonal 3D terrain engine. + +--- + +## 1. Project Vision & Core Concept + +**Goal:** Transform this 3D hexagonal terrain engine into a seamless **Real-Time Strategy Civilization** game. + +### Gameplay Formula: +- **Civilization Depth (4X):** Hex-based empire building, city founding, tile improvements (farms, mines, lumber mills), territory border expansion, technology tree, and resource economy (Food, Production, Gold, Science, Culture). +- **RTS Dynamics:** Continuous real-time simulation (with pause/speed controls) rather than strict turn-waiting. Units move, scout, build, and fight in real time with tactical unit control, responsive pathfinding, and dynamic skirmishes. +- **Visual Realism:** Modern OpenGL 4.0 rendering with realistic heightmaps, animated procedural water, smooth multi-biome blending (grassland, forest, desert, tundra, red rock canyon, snow-capped mountains), and hex-edge river networks. + +--- + +## 2. Existing Codebase Architecture + +The project is built on **C++**, **Qt (Widgets & OpenGL)**, and **OpenGL 4.0 Core Profile**. + +### Key Source Files & Responsibilities: +- `terrain.h`: + - Map dimensions: `MAP_WIDTH = 300`, `MAP_HEIGHT = 125` (total 37,500 hexagons). + - Hex geometry constants: `LOD_0_SIZE = 1.0f`, `LOD_0_SIZE_Y = 0.4328799f`. + - Staggered column neighbor resolution: `getHexNeighbor(x, y, edge, &nx, &ny)`. + - Edge midpoints: `getHexEdgeMidpoint(edge)` for river routing. + - Data structure `TerrainPoint`: height, biome type, river flags. +- `tile.h` & `tile.cpp`: + - Hexagon subdivision into triangles (LOD system: 6 base triangles subdivided $\times 4 \times 4 = 96$ triangles per hex). + - Per-vertex elevation sampling from `heightmap.jpg` (scaled to $3.5\text{ km}$ peak height) with riverbed depression carving. + - Smooth neighbor-aware normal generation across hex seams. + - Triplanar texture binding: grass, rock cliff, sand beach, animated water, snow, sahara, tundra, red sand, red rock canyon, and woodland forest. +- `qscenerenderer.h` & `qscenerenderer.cpp`: + - `QOpenGLWidget` subclass managing camera view, rotation, and mouse interaction. + - Interactive orthographic hex-tile minimap with camera viewport outline, drag-to-pan, and zoom controls. +- `shaders/simple.vert` & `shaders/simple.frag`: + - GLSL 400 shaders. + - Biome typemap sampling and altitude/latitude-based snowline ($2.5\text{ km}$ temperate, down to sea-level at polar caps). + - Procedural dynamic dual-wave ocean and riverbed water rendering. +- `generate_rivers.py`: + - Python map preprocessor for strict hex-edge river connectivity and orthographic minimap generation. + +### Coordinate Systems: +- **Grid Coordinates:** `(tx, ty)` with $0 \le tx < 300$ and $0 \le ty < 125$. +- **World Coordinates:** + $$\text{worldX} = tx \times 0.75$$ + $$\text{worldY} = ty \times (2.0 \times \text{LOD\_0\_SIZE\_Y}) + (tx \% 2 == 0 \; ? \; 0.0 : \text{LOD\_0\_SIZE\_Y})$$ +- **Elevation (Z):** $0.0$ at sea level; land rises up to $\approx 3.5\text{ km}$. + +--- + +## 3. Game Systems Architecture + +To implement the RTS Civilization game, develop modular subsystems decoupled from the raw rendering loop: + +``` + +-------------------+ + | GameManager | + +---------+---------+ + | + +-----------------+--------+--------+-----------------+ + | | | | ++----+----+ +-----+-----+ +-----+-----+ +-----+-----+ +| Faction | | Unit | | City | | Economy & | +| Manager | | Manager | | Manager | | Tech Tree | ++----+----+ +-----+-----+ +-----+-----+ +-----+-----+ + | | | | + | +-------+-------+ | | + | | Pathfinding | | | + | | (Hex A*) | | | + | +---------------+ | | + +-----------------+-----------------+ | + | | + +--------+--------+ | + | Fog of War / | | + | Visibility |<-------------------------+ + +--------+--------+ + | + +--------+--------+ + | QSceneRenderer | (OpenGL Viewport + UI Overlays) + +-----------------+ +``` + +--- + +## 4. Agents & Unit Specifications + +Units are autonomous or player-directed entities traversing the hex map in real time. + +### 4.1 Unit Types + +1. **Civilian Units:** + - **Settler:** Consumed to found a new City Center on valid land tiles (cannot settle on water or mountains). + - **Worker / Builder:** Constructs tile improvements (Farms, Mines, Lumber Mills, Quarries, Roads) and repairs pillaged tiles. Can be automated via worker AI. + - **Trader / Caravan:** Establishes land or sea trade routes between cities, providing continuous Gold, Food, and Production. + +2. **Military Units (RTS Combat):** + - **Melee:** Scout $\rightarrow$ Warrior $\rightarrow$ Swordsman $\rightarrow$ Musketman $\rightarrow$ Infantry. + - **Ranged:** Archer $\rightarrow$ Crossbowman $\rightarrow$ Field Cannon $\rightarrow$ Artillery. + - **Cavalry:** Horseman $\rightarrow$ Knight $\rightarrow$ Cavalry $\rightarrow$ Tank (high movement speed, flanking bonuses). + - **Siege:** Catapult $\rightarrow$ Trebuchet $\rightarrow$ Bombard (high bonus damage against city walls). + - **Naval:** Galley $\rightarrow$ Caravel $\rightarrow$ Frigate $\rightarrow$ Battleship (operates only on water hexes). + +### 4.2 Unit Data Model (`Unit.h`) +```cpp +enum class UnitType { Settler, Worker, Warrior, Archer, Horseman, Galley }; +enum class UnitState { Idle, Moving, Attacking, Harvesting, Building, Fortified }; + +class Unit { +public: + int id; + int factionId; + UnitType type; + UnitState state; + + // Grid Position & Smooth Real-time World Position + int tileX, tileY; + QVector3D worldPosition; + QVector3D targetPosition; + float moveProgress; // 0.0 to 1.0 between hexes + + // Attributes + float health, maxHealth; + float attackDamage; + float defense; + float movementSpeed; // hexes per second + float visionRange; // in hex radius + + // Path & Target + std::vector> currentPath; + int targetEntityId; // target unit or city + + void update(float deltaTime); + void commandMoveTo(int destX, int destY); + void commandAttack(int targetId); + void commandBuildImprovement(int improvementType); +}; +``` + +### 4.3 Real-time Hex Pathfinding (`Pathfinder.h`) +- Implement **A\*** over the hexagonal grid with cost functions: + - **Flatland (Grassland/Plains):** Base cost $1.0$. + - **Forest / Jungle:** Cost $2.0$. + - **Hills:** Cost $2.0$. + - **River Crossing:** Penalty $+1.5$ (unless road/bridge is present). + - **Mountain / Deep Water:** Impassable for land units. + - **Friendly vs Enemy Hexes:** ZOC (Zone of Control) penalties near hostile military units. + +--- + +## 5. Cities, Territory & Improvements + +### 5.1 City Architecture +- **City Founding:** Consumes a Settler. Claims the center hex and immediate ring of 6 adjacent hexes. +- **Borders:** Expanding territory based on accumulated Culture. +- **Hex Yields:** + - **Food:** Drives city population growth ($Pop = 1, 2, 3 \dots$). + - **Production (Hammers):** Contributes to the active construction queue (units, buildings, wonders). + - **Gold:** Empire treasury for unit upkeep, tile purchasing, and rush production. + - **Science:** Advances active technology research in real time. + - **Culture:** Expands borders and advances civics/policies. + +### 5.2 Tile Improvements +- **Farm:** Built on Grassland/Plains/Floodplains ($+2\text{ Food}$, river adjacency bonus). +- **Mine:** Built on Hills ($+2\text{ Production}$). +- **Lumber Mill:** Built on Forest ($+1\text{ Production}$, preserves forest defense). +- **Road:** Built along hex edges/centers to reduce movement cost to $0.33$. + +--- + +## 6. AI Agent Behavior (Civilization Empire AI) + +AI opponents must operate through a hierarchical decision architecture: + +1. **Strategic Commander (Empire Level):** + - Assesses empire needs: expansion, military defense, economic development, or technology rush. + - Queues city construction and manages empire-wide resource allocation. +2. **Operational Commander (Theater Level):** + - Coordinates multi-unit formations (e.g. Settler with Warrior escort; army squads containing front-line melee and rear-line archers). + - Manages tactical skirmishes, city sieges, and territorial defense. +3. **Tactical Agent (Unit Level):** + - Implements Utility AI or Behavior Trees: + - Worker AI: evaluates unworked tiles in city radius and constructs optimal improvements. + - Scout AI: moves toward unrevealed fog of war while avoiding hostile barbarians. + - Military AI: attacks vulnerable targets, retreats when health drops below $30\%$, uses terrain defense bonuses. + +--- + +## 7. Fog of War & Vision System + +1. **Three Visibility States:** + - **Unexplored (Black):** Never seen. No terrain, no entities. + - **Explored / Fog of War (Grayscale/Dimmed):** Previously discovered terrain and static cities visible; enemy units hidden. + - **Visible (Full Color):** Currently within line-of-sight of any friendly unit or city. +2. **GPU Texture Mask:** + - Maintain a $300 \times 125$ texture updated on the CPU / compute shader representing visibility $(0 = \text{black}, 128 = \text{fog}, 255 = \text{visible})$. + - Sample this mask in `simple.frag` to dim or mask terrain and hide out-of-vision units. + +--- + +## 8. Implementation Roadmap + +### Phase 1: Core Simulation Loop & Pathfinding +- [ ] Create `GameSimulation` class with fixed-tick update (e.g., 20 ticks/sec). +- [ ] Implement hex A* pathfinding (`Pathfinder.h/.cpp`) using `getHexNeighbor`. +- [ ] Add basic `Unit` class with real-time waypoint interpolation across hex centers. +- [ ] Implement mouse raycasting / hex picking from screen click to `(tx, ty)`. + +### Phase 2: Unit Rendering & RTS Controls +- [ ] Render 3D unit meshes / billboards on tile surfaces at correct elevation $Z$. +- [ ] Implement standard RTS controls: Left-click select, box-select, right-click move/attack. +- [ ] Add unit health bars, selection rings, and minimap unit dots. + +### Phase 3: Cities & Tile Improvements +- [ ] Implement `City` class, city founding with Settler, and territory border rendering. +- [ ] Add Worker unit improvement actions (Farm, Mine, Road). +- [ ] Implement resource yield accumulation (Food, Production, Gold, Science). + +### Phase 4: Combat, Technology & Opponent AI +- [ ] Implement real-time combat calculation, ranged attack trajectories, and damage resolution. +- [ ] Add Technology Tree interface and research progression. +- [ ] Implement AI Factions and Barbarian encampments. + +--- + +## 9. Coding Guidelines for Agents & Developers + +- **Language Standard:** C++17 with Qt 5/6 framework. +- **Rendering Thread Safety:** Game logic and pathfinding must run synchronously or safely decoupled from `paintGL()`. Never call OpenGL functions outside the OpenGL context thread. +- **Memory Management:** Use RAII and smart pointers (`std::unique_ptr`, `std::shared_ptr`) for entities. Avoid raw manual array allocations in game loop updates. +- **Performance:** + - With 37,500 hexes, avoid iterating all hexes every frame. Use spatial partitioning or chunking for unit queries. + - Batch unit rendering using instanced OpenGL draw calls (`glDrawArraysInstanced` or `glDrawElementsInstanced`). +- **Math Consistency:** Always use `terrain.h` helper functions (`getHexNeighbor`, `getHexEdgeMidpoint`, `LOD_0_SIZE_Y`) to maintain consistent geometry and alignment with the terrain mesh. diff --git a/Makefile b/Makefile index dae7817..d441f1d 100644 --- a/Makefile +++ b/Makefile @@ -16,7 +16,7 @@ CC = gcc CXX = g++ DEFINES = -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB CFLAGS = -pipe -O2 -Wall -Wextra -D_REENTRANT -fPIC $(DEFINES) -CXXFLAGS = -pipe -O2 -Wall -Wextra -D_REENTRANT -fPIC $(DEFINES) +CXXFLAGS = -pipe -O2 -std=gnu++1z -Wall -Wextra -D_REENTRANT -fPIC $(DEFINES) INCPATH = -I. -I/usr/include/x86_64-linux-gnu/qt5 -I/usr/include/x86_64-linux-gnu/qt5/QtWidgets -I/usr/include/x86_64-linux-gnu/qt5/QtGui -I/usr/include/x86_64-linux-gnu/qt5/QtCore -I. -I. -I/usr/lib/x86_64-linux-gnu/qt5/mkspecs/linux-g++ QMAKE = /usr/lib/qt5/bin/qmake DEL_FILE = rm -f @@ -58,7 +58,14 @@ SOURCES = main.cpp \ vertex.cpp \ tile.cpp \ tilevertexinfo.cpp \ - face.cpp qrc_shaders.cpp \ + face.cpp \ + player.cpp \ + yamlparser.cpp \ + unitconfig.cpp \ + pathfinder.cpp \ + unit.cpp \ + gamemanager.cpp \ + unitrenderer.cpp qrc_shaders.cpp \ moc_mainwindow.cpp OBJECTS = main.o \ mainwindow.o \ @@ -67,10 +74,19 @@ OBJECTS = main.o \ tile.o \ tilevertexinfo.o \ face.o \ + player.o \ + yamlparser.o \ + unitconfig.o \ + pathfinder.o \ + unit.o \ + gamemanager.o \ + unitrenderer.o \ qrc_shaders.o \ moc_mainwindow.o DIST = shaders/simple.vert \ shaders/simple.frag \ + shaders/unit.vert \ + shaders/unit.frag \ /usr/lib/x86_64-linux-gnu/qt5/mkspecs/features/spec_pre.prf \ /usr/lib/x86_64-linux-gnu/qt5/mkspecs/common/unix.conf \ /usr/lib/x86_64-linux-gnu/qt5/mkspecs/common/linux.conf \ @@ -137,6 +153,7 @@ DIST = shaders/simple.vert \ /usr/lib/x86_64-linux-gnu/qt5/mkspecs/features/qt_config.prf \ /usr/lib/x86_64-linux-gnu/qt5/mkspecs/linux-g++/qmake.conf \ /usr/lib/x86_64-linux-gnu/qt5/mkspecs/features/spec_post.prf \ + .qmake.stash \ /usr/lib/x86_64-linux-gnu/qt5/mkspecs/features/exclusive_builds.prf \ /usr/lib/x86_64-linux-gnu/qt5/mkspecs/features/toolchain.prf \ /usr/lib/x86_64-linux-gnu/qt5/mkspecs/features/default_pre.prf \ @@ -164,13 +181,28 @@ DIST = shaders/simple.vert \ terrain.h \ tilevertexinfo.h \ matrix2d.h \ - face.h main.cpp \ + face.h \ + player.h \ + yamlparser.h \ + unitconfig.h \ + pathfinder.h \ + unit.h \ + city.h \ + gamemanager.h \ + unitrenderer.h main.cpp \ mainwindow.cpp \ qscenerenderer.cpp \ vertex.cpp \ tile.cpp \ tilevertexinfo.cpp \ - face.cpp + face.cpp \ + player.cpp \ + yamlparser.cpp \ + unitconfig.cpp \ + pathfinder.cpp \ + unit.cpp \ + gamemanager.cpp \ + unitrenderer.cpp QMAKE_TARGET = tiletest DESTDIR = TARGET = tiletest @@ -248,6 +280,7 @@ Makefile: tiletest.pro /usr/lib/x86_64-linux-gnu/qt5/mkspecs/linux-g++/qmake.con /usr/lib/x86_64-linux-gnu/qt5/mkspecs/features/qt_config.prf \ /usr/lib/x86_64-linux-gnu/qt5/mkspecs/linux-g++/qmake.conf \ /usr/lib/x86_64-linux-gnu/qt5/mkspecs/features/spec_post.prf \ + .qmake.stash \ /usr/lib/x86_64-linux-gnu/qt5/mkspecs/features/exclusive_builds.prf \ /usr/lib/x86_64-linux-gnu/qt5/mkspecs/features/toolchain.prf \ /usr/lib/x86_64-linux-gnu/qt5/mkspecs/features/default_pre.prf \ @@ -337,6 +370,7 @@ Makefile: tiletest.pro /usr/lib/x86_64-linux-gnu/qt5/mkspecs/linux-g++/qmake.con /usr/lib/x86_64-linux-gnu/qt5/mkspecs/features/qt_config.prf: /usr/lib/x86_64-linux-gnu/qt5/mkspecs/linux-g++/qmake.conf: /usr/lib/x86_64-linux-gnu/qt5/mkspecs/features/spec_post.prf: +.qmake.stash: /usr/lib/x86_64-linux-gnu/qt5/mkspecs/features/exclusive_builds.prf: /usr/lib/x86_64-linux-gnu/qt5/mkspecs/features/toolchain.prf: /usr/lib/x86_64-linux-gnu/qt5/mkspecs/features/default_pre.prf: @@ -375,8 +409,8 @@ distdir: FORCE $(COPY_FILE) --parents $(DIST) $(DISTDIR)/ $(COPY_FILE) --parents shaders.qrc $(DISTDIR)/ $(COPY_FILE) --parents /usr/lib/x86_64-linux-gnu/qt5/mkspecs/features/data/dummy.cpp $(DISTDIR)/ - $(COPY_FILE) --parents mainwindow.h qscenerenderer.h vertex.h tile.h terrain.h tilevertexinfo.h matrix2d.h face.h $(DISTDIR)/ - $(COPY_FILE) --parents main.cpp mainwindow.cpp qscenerenderer.cpp vertex.cpp tile.cpp tilevertexinfo.cpp face.cpp $(DISTDIR)/ + $(COPY_FILE) --parents mainwindow.h qscenerenderer.h vertex.h tile.h terrain.h tilevertexinfo.h matrix2d.h face.h player.h yamlparser.h unitconfig.h pathfinder.h unit.h city.h gamemanager.h unitrenderer.h $(DISTDIR)/ + $(COPY_FILE) --parents main.cpp mainwindow.cpp qscenerenderer.cpp vertex.cpp tile.cpp tilevertexinfo.cpp face.cpp player.cpp yamlparser.cpp unitconfig.cpp pathfinder.cpp unit.cpp gamemanager.cpp unitrenderer.cpp $(DISTDIR)/ $(COPY_FILE) --parents mainwindow.ui $(DISTDIR)/ @@ -414,7 +448,7 @@ compiler_moc_predefs_make_all: moc_predefs.h compiler_moc_predefs_clean: -$(DEL_FILE) moc_predefs.h moc_predefs.h: /usr/lib/x86_64-linux-gnu/qt5/mkspecs/features/data/dummy.cpp - g++ -pipe -O2 -Wall -Wextra -dM -E -o moc_predefs.h /usr/lib/x86_64-linux-gnu/qt5/mkspecs/features/data/dummy.cpp + g++ -pipe -O2 -std=gnu++1z -Wall -Wextra -dM -E -o moc_predefs.h /usr/lib/x86_64-linux-gnu/qt5/mkspecs/features/data/dummy.cpp compiler_moc_header_make_all: moc_mainwindow.cpp compiler_moc_header_clean: @@ -439,7 +473,13 @@ ui_mainwindow.h: mainwindow.ui \ vertex.h \ face.h \ tilevertexinfo.h \ - matrix2d.h + matrix2d.h \ + unitrenderer.h \ + unit.h \ + unitconfig.h \ + player.h \ + city.h \ + gamemanager.h /usr/lib/qt5/bin/uic mainwindow.ui -o ui_mainwindow.h compiler_yacc_decl_make_all: @@ -456,7 +496,20 @@ main.o: main.cpp mainwindow.h $(CXX) -c $(CXXFLAGS) $(INCPATH) -o main.o main.cpp mainwindow.o: mainwindow.cpp mainwindow.h \ - ui_mainwindow.h + ui_mainwindow.h \ + qscenerenderer.h \ + terrain.h \ + tile.h \ + vertex.h \ + face.h \ + tilevertexinfo.h \ + matrix2d.h \ + unitrenderer.h \ + unit.h \ + unitconfig.h \ + player.h \ + city.h \ + gamemanager.h $(CXX) -c $(CXXFLAGS) $(INCPATH) -o mainwindow.o mainwindow.cpp qscenerenderer.o: qscenerenderer.cpp qscenerenderer.h \ @@ -465,7 +518,14 @@ qscenerenderer.o: qscenerenderer.cpp qscenerenderer.h \ vertex.h \ face.h \ tilevertexinfo.h \ - matrix2d.h + matrix2d.h \ + unitrenderer.h \ + unit.h \ + unitconfig.h \ + player.h \ + city.h \ + gamemanager.h \ + pathfinder.h $(CXX) -c $(CXXFLAGS) $(INCPATH) -o qscenerenderer.o qscenerenderer.cpp vertex.o: vertex.cpp vertex.h \ @@ -479,7 +539,13 @@ tile.o: tile.cpp tile.h \ tilevertexinfo.h \ terrain.h \ matrix2d.h \ - qscenerenderer.h + qscenerenderer.h \ + unitrenderer.h \ + unit.h \ + unitconfig.h \ + player.h \ + city.h \ + gamemanager.h $(CXX) -c $(CXXFLAGS) $(INCPATH) -o tile.o tile.cpp tilevertexinfo.o: tilevertexinfo.cpp tilevertexinfo.h \ @@ -492,6 +558,48 @@ face.o: face.cpp face.h \ tilevertexinfo.h $(CXX) -c $(CXXFLAGS) $(INCPATH) -o face.o face.cpp +player.o: player.cpp player.h + $(CXX) -c $(CXXFLAGS) $(INCPATH) -o player.o player.cpp + +yamlparser.o: yamlparser.cpp yamlparser.h + $(CXX) -c $(CXXFLAGS) $(INCPATH) -o yamlparser.o yamlparser.cpp + +unitconfig.o: unitconfig.cpp unitconfig.h \ + yamlparser.h + $(CXX) -c $(CXXFLAGS) $(INCPATH) -o unitconfig.o unitconfig.cpp + +pathfinder.o: pathfinder.cpp pathfinder.h \ + terrain.h \ + matrix2d.h + $(CXX) -c $(CXXFLAGS) $(INCPATH) -o pathfinder.o pathfinder.cpp + +unit.o: unit.cpp unit.h \ + unitconfig.h \ + terrain.h \ + matrix2d.h \ + pathfinder.h + $(CXX) -c $(CXXFLAGS) $(INCPATH) -o unit.o unit.cpp + +gamemanager.o: gamemanager.cpp gamemanager.h \ + player.h \ + unitconfig.h \ + unit.h \ + terrain.h \ + matrix2d.h \ + city.h \ + pathfinder.h \ + yamlparser.h + $(CXX) -c $(CXXFLAGS) $(INCPATH) -o gamemanager.o gamemanager.cpp + +unitrenderer.o: unitrenderer.cpp unitrenderer.h \ + unit.h \ + unitconfig.h \ + terrain.h \ + matrix2d.h \ + player.h \ + city.h + $(CXX) -c $(CXXFLAGS) $(INCPATH) -o unitrenderer.o unitrenderer.cpp + qrc_shaders.o: qrc_shaders.cpp $(CXX) -c $(CXXFLAGS) $(INCPATH) -o qrc_shaders.o qrc_shaders.cpp diff --git a/agents.md b/agents.md new file mode 120000 index 0000000..23b2e31 --- /dev/null +++ b/agents.md @@ -0,0 +1 @@ +/home/bart/project/tiletest/AGENTS.md \ No newline at end of file diff --git a/agents.ms b/agents.ms new file mode 120000 index 0000000..23b2e31 --- /dev/null +++ b/agents.ms @@ -0,0 +1 @@ +/home/bart/project/tiletest/AGENTS.md \ No newline at end of file diff --git a/city.h b/city.h new file mode 100644 index 0000000..f44d325 --- /dev/null +++ b/city.h @@ -0,0 +1,20 @@ +#ifndef CITY_H +#define CITY_H + +#include +#include + +struct City { + int id; + int playerId; + QString name; + int tileX; + int tileY; + QVector3D worldPos; + + City() : id(0), playerId(0), name(""), tileX(0), tileY(0), worldPos(0, 0, 0) {} + City(int cId, int pId, const QString &cName, int tx, int ty, const QVector3D &pos) + : id(cId), playerId(pId), name(cName), tileX(tx), tileY(ty), worldPos(pos) {} +}; + +#endif // CITY_H diff --git a/config/game.yaml b/config/game.yaml new file mode 100644 index 0000000..d1a0feb --- /dev/null +++ b/config/game.yaml @@ -0,0 +1 @@ +movement_tick_duration: 30.0 diff --git a/config/units/settler.yaml b/config/units/settler.yaml new file mode 100644 index 0000000..88f61c1 --- /dev/null +++ b/config/units/settler.yaml @@ -0,0 +1,10 @@ +id: settler +name: Settler +category: bouwen +health: 100 +movement_speed: 2.0 +cost: 80 +icon: images/units/settler.png +actions: + - verplaatsen + - steden_bouwen diff --git a/config/units/slinger.yaml b/config/units/slinger.yaml new file mode 100644 index 0000000..3e4a980 --- /dev/null +++ b/config/units/slinger.yaml @@ -0,0 +1,12 @@ +id: slinger +name: Slinger +category: militair +health: 70 +movement_speed: 2.0 +cost: 35 +attack_damage: 15 +attack_range: 2 +icon: images/units/slinger.png +actions: + - verplaatsen + - aanvallen diff --git a/config/units/warrior.yaml b/config/units/warrior.yaml new file mode 100644 index 0000000..2ebd36c --- /dev/null +++ b/config/units/warrior.yaml @@ -0,0 +1,12 @@ +id: warrior +name: Warrior +category: militair +health: 100 +movement_speed: 2.0 +cost: 40 +attack_damage: 20 +attack_range: 1 +icon: images/units/warrior.png +actions: + - verplaatsen + - aanvallen diff --git a/face.cpp b/face.cpp index 3520ade..b3a5152 100644 --- a/face.cpp +++ b/face.cpp @@ -1,5 +1,5 @@ #include "face.h" - +#include float Face::sign (QVector2D p1, QVector2D p2, QVector2D p3) { @@ -8,13 +8,8 @@ float Face::sign (QVector2D p1, QVector2D p2, QVector2D p3) bool Face::PointInFace (QVector2D pt) { - bool b1, b2, b3; - - //b1 = sign(pt, v1, v2) < 0.0f; - //b2 = sign(pt, v2, v3) < 0.0f; - // b3 = sign(pt, v3, v1) < 0.0f; - - return ((b1 == b2) && (b2 == b3)); + Q_UNUSED(pt); + return true; } Face::Face(Vertex *v1,TileVertexInfo *vi1, Vertex *v2,TileVertexInfo *vi2,Vertex *v3,TileVertexInfo *vi3) @@ -42,8 +37,10 @@ void Face::recalcNormal() QVector3D va = v1 - v2; QVector3D vb = v3 - v2; m_normal = QVector3D::crossProduct(va,vb); - /*float norm_len = sqrt(dot(norm, norm)); - norm /= norm_len; */ + if (m_normal.z() < 0.0f) { + m_normal = -m_normal; + } + m_normal.normalize(); } QVector3D Face::normal() @@ -51,6 +48,11 @@ QVector3D Face::normal() return m_normal; } +static inline bool pointMatch(const QVector2D& p1, const QVector2D& p2) +{ + return (std::abs(p1.x() - p2.x()) < 1e-4f) && (std::abs(p1.y() - p2.y()) < 1e-4f); +} + void Face::findFaces(std::vector &vec, QVector2D s) { if (m_subface!=NULL) { @@ -59,11 +61,10 @@ void Face::findFaces(std::vector &vec, QVector2D s) } } else { - if ( - ((m_v1->position().x() == s.x()) && (m_v1->position().y() == s.y())) || - ((m_v2->position().x() == s.x()) && (m_v2->position().y() == s.y())) || - ((m_v3->position().x()== s.x()) && (m_v3->position().y() == s.y())) - ) { + if (m_v1 && m_v2 && m_v3 && ( + pointMatch(m_v1->position(), s) || + pointMatch(m_v2->position(), s) || + pointMatch(m_v3->position(), s))) { vec.push_back(this); } } diff --git a/face.o b/face.o index a9b0921..8910264 100644 Binary files a/face.o and b/face.o differ diff --git a/gamemanager.cpp b/gamemanager.cpp new file mode 100644 index 0000000..a23466d --- /dev/null +++ b/gamemanager.cpp @@ -0,0 +1,223 @@ +#include "gamemanager.h" +#include "pathfinder.h" +#include "yamlparser.h" +#include +#include + +GameManager& GameManager::instance() +{ + static GameManager s_instance; + return s_instance; +} + +GameManager::GameManager() + : m_selectedUnitId(-1), m_actionMode(ActionMode::None), + m_movementTickDuration(30.0f), m_tickTimer(0.0f), + m_simSpeedMultiplier(1.0f), m_nextUnitId(0), m_nextCityId(0), + m_initialized(false) +{ +} + +void GameManager::init(const QString &baseDir, Matrix2D *mapPoints, int numPoints05) +{ + if (m_initialized) return; + + // Load game settings + QString gameConfigPath = baseDir + "/config/game.yaml"; + YamlNode gameRoot = YamlParser::parseFile(gameConfigPath); + if (gameRoot.hasChild("movement_tick_duration")) { + m_movementTickDuration = gameRoot["movement_tick_duration"].asFloat(30.0f); + } + + // Load unit YAML configurations + QString unitsConfigDir = baseDir + "/config/units"; + m_unitConfigManager.loadFromDirectory(unitsConfigDir); + + // Spawn initial demo units near starting camera position (Cam: ~28, 16 km -> hex ~37, 18) + // Player 1 (Blue & Gold) + spawnUnit("settler", 1, 36, 18, mapPoints, numPoints05); + spawnUnit("warrior", 1, 37, 18, mapPoints, numPoints05); + spawnUnit("slinger", 1, 37, 19, mapPoints, numPoints05); + + // Player 2 (Red & White) - Opponent units + spawnUnit("warrior", 2, 40, 18, mapPoints, numPoints05); + spawnUnit("slinger", 2, 41, 19, mapPoints, numPoints05); + + m_initialized = true; + qDebug() << "GameManager initialized with" << m_units.size() << "units."; +} + +Unit* GameManager::spawnUnit(const QString &configId, int playerId, int tileX, int tileY, + Matrix2D *mapPoints, int numPoints05) +{ + const UnitConfig *cfg = m_unitConfigManager.getConfig(configId); + if (!cfg) { + qWarning() << "Unit config not found:" << configId; + return nullptr; + } + + QVector2D w = Pathfinder::hexToWorld(tileX, tileY); + float gz = Unit::getTerrainHeight(w.x(), w.y(), mapPoints, numPoints05); + + int id = getNextUnitId(); + auto unit = std::make_shared(id, playerId, *cfg, tileX, tileY, gz); + m_units.push_back(unit); + return unit.get(); +} + +void GameManager::update(float deltaTime, Matrix2D *mapPoints, int numPoints05) +{ + // Update movement tick timer + m_tickTimer += deltaTime * m_simSpeedMultiplier; + if (m_tickTimer >= m_movementTickDuration) { + m_tickTimer = std::fmod(m_tickTimer, m_movementTickDuration); + } + + // Update units + for (auto &u : m_units) { + if (!u->isDead()) { + u->update(deltaTime, m_movementTickDuration, m_simSpeedMultiplier, mapPoints, numPoints05); + } + } + + // Clean up dead units + for (auto it = m_units.begin(); it != m_units.end(); ) { + if ((*it)->isDead()) { + if ((*it)->id == m_selectedUnitId) { + m_selectedUnitId = -1; + m_actionMode = ActionMode::None; + } + it = m_units.erase(it); + } else { + ++it; + } + } + + // Update floating texts + for (auto it = m_floatingTexts.begin(); it != m_floatingTexts.end(); ) { + it->life -= deltaTime; + it->worldPos.setZ(it->worldPos.z() + deltaTime * 0.4f); // Float upward + if (it->life <= 0.0f) { + it = m_floatingTexts.erase(it); + } else { + ++it; + } + } +} + +Unit* GameManager::getSelectedUnit() +{ + return getUnitById(m_selectedUnitId); +} + +const Unit* GameManager::getSelectedUnit() const +{ + for (const auto &u : m_units) { + if (u->id == m_selectedUnitId) return u.get(); + } + return nullptr; +} + +void GameManager::selectUnit(int id) +{ + m_selectedUnitId = id; + m_actionMode = ActionMode::None; +} + +void GameManager::deselectUnit() +{ + m_selectedUnitId = -1; + m_actionMode = ActionMode::None; +} + +Unit* GameManager::getUnitAt(int tx, int ty) +{ + for (auto &u : m_units) { + if (!u->isDead() && u->tileX == tx && u->tileY == ty) { + return u.get(); + } + } + return nullptr; +} + +Unit* GameManager::getUnitById(int id) +{ + for (auto &u : m_units) { + if (u->id == id && !u->isDead()) return u.get(); + } + return nullptr; +} + +void GameManager::commandMoveSelected(int targetX, int targetY, Matrix2D *mapPoints, int numPoints05) +{ + Unit *selected = getSelectedUnit(); + if (!selected) return; + + if (targetX == selected->tileX && targetY == selected->tileY) { + return; + } + + std::vector> path = Pathfinder::findPath( + selected->tileX, selected->tileY, targetX, targetY, mapPoints, numPoints05 + ); + + if (path.size() > 1) { + selected->setPath(path); + addFloatingText("Verplaatsen", selected->worldPos + QVector3D(0, 0, 0.8f), QColor(255, 230, 80)); + } +} + +void GameManager::commandAttack(Unit *target) +{ + Unit *selected = getSelectedUnit(); + if (!selected || !target) return; + + if (!selected->canAttack(*target)) { + addFloatingText("Buiten bereik!", selected->worldPos + QVector3D(0, 0, 0.8f), QColor(255, 80, 80)); + return; + } + + float damage = (float)selected->config.attackDamage; + selected->performAttack(*target); + + QString dmgText = QString("-%1 HP").arg((int)damage); + addFloatingText(dmgText, target->worldPos + QVector3D(0, 0, 0.8f), QColor(255, 50, 50)); + + if (target->isDead()) { + addFloatingText("Verslagen!", target->worldPos + QVector3D(0, 0, 1.2f), QColor(255, 200, 50), 2.2f); + } + + m_actionMode = ActionMode::None; +} + +void GameManager::commandBuildCitySelected(Matrix2D *mapPoints, int numPoints05) +{ + Unit *selected = getSelectedUnit(); + if (!selected || !selected->config.canBuildCity()) return; + + // Check if tile already has a city + for (const auto &c : m_cities) { + if (c.tileX == selected->tileX && c.tileY == selected->tileY) { + addFloatingText("Al een stad hier!", selected->worldPos + QVector3D(0, 0, 0.8f), QColor(255, 80, 80)); + return; + } + } + + int cId = getNextCityId(); + QString cityName = QString("Stad %1").arg(cId); + QVector2D w = Pathfinder::hexToWorld(selected->tileX, selected->tileY); + float gz = Unit::getTerrainHeight(w.x(), w.y(), mapPoints, numPoints05); + + m_cities.emplace_back(cId, selected->playerId, cityName, selected->tileX, selected->tileY, QVector3D(w.x(), w.y(), gz)); + addFloatingText(QString("%1 gesticht!").arg(cityName), QVector3D(w.x(), w.y(), gz + 1.2f), QColor(80, 255, 120), 2.5f); + + // Consuming settler + selected->takeDamage(99999.0f); + m_selectedUnitId = -1; + m_actionMode = ActionMode::None; +} + +void GameManager::addFloatingText(const QString &text, const QVector3D &worldPos, const QColor &color, float duration) +{ + m_floatingTexts.push_back({text, worldPos, duration, duration, color}); +} diff --git a/gamemanager.h b/gamemanager.h new file mode 100644 index 0000000..17ac4e5 --- /dev/null +++ b/gamemanager.h @@ -0,0 +1,95 @@ +#ifndef GAMEMANAGER_H +#define GAMEMANAGER_H + +#include +#include +#include +#include "player.h" +#include "unitconfig.h" +#include "unit.h" +#include "city.h" +#include "matrix2d.h" +#include "terrain.h" + +enum class ActionMode { + None, + Move, + Attack, + BuildCity +}; + +struct FloatingText { + QString text; + QVector3D worldPos; + float life; // Remaining seconds + float maxLife; + QColor color; +}; + +class GameManager { +public: + static GameManager& instance(); + + void init(const QString &baseDir, Matrix2D *mapPoints, int numPoints05); + void update(float deltaTime, Matrix2D *mapPoints, int numPoints05); + + PlayerManager& players() { return m_playerManager; } + UnitConfigManager& unitConfigs() { return m_unitConfigManager; } + + const std::vector>& getUnits() const { return m_units; } + const std::vector& getCities() const { return m_cities; } + const std::vector& getFloatingTexts() const { return m_floatingTexts; } + + Unit* getSelectedUnit(); + const Unit* getSelectedUnit() const; + void selectUnit(int id); + void deselectUnit(); + + ActionMode currentActionMode() const { return m_actionMode; } + void setActionMode(ActionMode mode) { m_actionMode = mode; } + + Unit* getUnitAt(int tx, int ty); + Unit* getUnitById(int id); + + void commandMoveSelected(int targetX, int targetY, Matrix2D *mapPoints, int numPoints05); + void commandAttack(Unit *target); + void commandBuildCitySelected(Matrix2D *mapPoints, int numPoints05); + + float getTickDuration() const { return m_movementTickDuration; } + void setTickDuration(float d) { m_movementTickDuration = std::max(1.0f, d); } + float getTickTimer() const { return m_tickTimer; } + + float getSimSpeed() const { return m_simSpeedMultiplier; } + void setSimSpeed(float s) { m_simSpeedMultiplier = std::max(0.2f, std::min(10.0f, s)); } + + void addFloatingText(const QString &text, const QVector3D &worldPos, const QColor &color, float duration = 1.8f); + + int getNextUnitId() { return ++m_nextUnitId; } + int getNextCityId() { return ++m_nextCityId; } + + Unit* spawnUnit(const QString &configId, int playerId, int tileX, int tileY, + Matrix2D *mapPoints, int numPoints05); + +private: + GameManager(); + ~GameManager() = default; + + PlayerManager m_playerManager; + UnitConfigManager m_unitConfigManager; + std::vector> m_units; + std::vector m_cities; + std::vector m_floatingTexts; + + int m_selectedUnitId; + ActionMode m_actionMode; + + float m_movementTickDuration; // Default 30.0s + float m_tickTimer; + float m_simSpeedMultiplier; // 1.0x, 2.0x, etc. + + int m_nextUnitId; + int m_nextCityId; + bool m_initialized; +}; + +#endif // GAMEMANAGER_H diff --git a/gamemanager.o b/gamemanager.o new file mode 100644 index 0000000..cbde797 Binary files /dev/null and b/gamemanager.o differ diff --git a/generate_rivers.py b/generate_rivers.py new file mode 100644 index 0000000..b4e24f3 --- /dev/null +++ b/generate_rivers.py @@ -0,0 +1,178 @@ +#!/usr/bin/env python3 +""" +River generator and map builder for tiletest. +Encodes rivers onto images/rivermap.png such that rivers STRICTLY pass through +the exact midpoints of the 6 hexagon edges, ensuring 100% seamless connections. +""" + +from PIL import Image, ImageDraw +import numpy as np + +WIDTH = 3600 +HEIGHT = 1500 +SCALE_X = 8.0 +SCALE_Y = 9.24045 +LOD_0_SIZE_Y = 0.4328799197461722 + +# 6 edge midpoints in local tile space (radius R = 0.5) +MIDPOINTS = [ + (-0.375, 0.21650635), # Edge 0 (Top-Left) + ( 0.0, 0.43301270), # Edge 1 (Top) + ( 0.375, 0.21650635), # Edge 2 (Top-Right) + ( 0.375, -0.21650635), # Edge 3 (Bottom-Right) + ( 0.0, -0.43301270), # Edge 4 (Bottom) + (-0.375, -0.21650635) # Edge 5 (Bottom-Left) +] + +def tile_world_pos(tx, ty): + wx = tx * 0.75 + wy = ty * (LOD_0_SIZE_Y * 2.0) + (0.0 if tx % 2 == 0 else LOD_0_SIZE_Y) + return wx, wy + +def world_to_pixel(wx, wy): + px = int(round(wx * SCALE_X)) + py = int(round(wy * SCALE_Y)) + return px, HEIGHT - 1 - py + +def get_hex_neighbor(x, y, edge): + even = (x % 2 == 0) + if edge == 0: return (x - 1, y if even else y + 1) + if edge == 1: return (x, y + 1) + if edge == 2: return (x + 1, y if even else y + 1) + if edge == 3: return (x + 1, y - 1 if even else y) + if edge == 4: return (x, y - 1) + if edge == 5: return (x - 1, y - 1 if even else y) + return (x, y) + +def trace_river(start_tile, edge_steps): + """Traces a river from a start tile following a sequence of exit edges.""" + path = [] + curr = start_tile + in_edge = None + for out_edge in edge_steps: + path.append((curr, in_edge, out_edge)) + curr = get_hex_neighbor(curr[0], curr[1], out_edge) + in_edge = (out_edge + 3) % 6 + path.append((curr, in_edge, None)) + return path + +def build_rivermap(output_path="/home/bart/project/tiletest/images/rivermap.png", preview_path="/home/bart/project/tiletest/images/rivermap_preview.png"): + river_img = Image.new('RGB', (WIDTH, HEIGHT), (0, 0, 0)) + draw = ImageDraw.Draw(river_img) + + # 1. Main River: starts high on mountain (36, 31) in snow, flows down to South Ocean (32, 14) + main_river_steps = [5, 5, 5, 5] + [4] * 15 + main_river = trace_river((36, 31), main_river_steps) + + # 2. Mountain Tributary: starts on ridge (36, 28) and merges with main river at (32, 26) + trib_river_steps = [5, 5, 5, 5] + tributary = trace_river((36, 28), trib_river_steps) + + # 3. Canyon River in Prairie (Red Sand & Red Rock) + prairie_steps = [4, 4, 5, 4, 4, 3, 4, 4, 4] + prairie_river = trace_river((110, 32), prairie_steps) + + rivers = [main_river, tributary, prairie_river] + river_color = (0, 180, 255) + + for r in rivers: + for (tx, ty), e_in, e_out in r: + wx, wy = tile_world_pos(tx, ty) + cx, cy = world_to_pixel(wx, wy) + edges = [e for e in (e_in, e_out) if e is not None] + for e in edges: + mx, my = MIDPOINTS[e] + mpx, mpy = world_to_pixel(wx + mx, wy + my) + # Line between midpoint and tile center + draw.line([(mpx, mpy), (cx, cy)], fill=river_color, width=2) + # Small marker at midpoint for detection + draw.rectangle([mpx - 1, mpy - 1, mpx + 1, mpy + 1], fill=river_color) + + river_img.save(output_path) + print(f"Saved rivermap to {output_path}") + + # Build an overlay preview on top of typemap.png + try: + type_img = Image.open("/home/bart/project/tiletest/images/typemap.png").convert("RGBA") + r_arr = np.array(river_img) + t_arr = np.array(type_img) + mask = (r_arr[:, :, 2] > 120) & (r_arr[:, :, 0] < 100) + t_arr[mask] = [0, 220, 255, 255] + preview = Image.fromarray(t_arr) + preview.save(preview_path) + print(f"Saved preview to {preview_path}") + + # Build orthographic hex-tile minimap.png + h_arr = np.array(Image.open("/home/bart/project/tiletest/images/heightmap.jpg")) + R = 8.0 + W = 3608 + H = 1740 + hex_img = Image.new('RGB', (W, H), (15, 20, 30)) + hdraw = ImageDraw.Draw(hex_img) + angles = [np.pi * 60.0 * i / 180.0 for i in range(6)] + corner_dx = [-R * np.cos(a) for a in angles] + corner_dy = [ R * np.sin(a) for a in angles] + + for ty in range(125): + for tx in range(300): + wx = tx * 0.75 + wy = ty * (LOD_0_SIZE_Y * 2.0) + (0.0 if tx % 2 == 0 else LOD_0_SIZE_Y) + cx = (wx + 0.5) * 16.0 + cy = H - 1 - (wy + LOD_0_SIZE_Y) * 16.0 + + px = min(3599, max(0, int(round(wx * SCALE_X)))) + py = min(1499, max(0, int(round(wy * SCALE_Y)))) + h = h_arr[1500 - 1 - py, px] + tp = t_arr[1500 - 1 - py, px] + dist_pole = abs((wy / 108.22) - 0.5) * 2.0 + + if h <= 2: + color = (220, 235, 250) if dist_pole > 0.85 else (28, 95, 175) # Water (blue) / polar ice + elif h > 182 or (dist_pole > 0.80 and h > 20): + color = (245, 250, 255) # Snow (white) above 2500m + elif h > 73: + # Mountain rock (between 1000m and 2500m) + r, g, b = int(tp[0]), int(tp[1]), int(tp[2]) + if r > 150 and g < 100: + color = (175, 75, 55) # Red rock mountain in prairie + else: + color = (125, 130, 135) # Gray alpine mountain rock + else: + # Biome ground below 1000m + r, g, b = int(tp[0]), int(tp[1]), int(tp[2]) + sat = max(r, g, b) - min(r, g, b) + if r > 150 and g < 100: + color = (195, 75, 45) # Prairie / Red Rock + elif r > 150 and g > 150 and b < 100: + color = (225, 185, 65) # Sahara yellow + elif b > 50 and r < 50: + color = (35, 115, 50) # Forest green + elif sat < 40 and g < 140: + color = (110, 125, 95) # Tundra slate + else: + color = (85, 175, 55) # Grassland green + + poly = [(cx + corner_dx[i], cy + corner_dy[i]) for i in range(6)] + hdraw.polygon(poly, fill=color, outline=(0, 0, 0, 45)) + + # Draw rivers on hex map + for r in rivers: + for (tx, ty), e_in, e_out in r: + wx, wy = tile_world_pos(tx, ty) + cx = (wx + 0.5) * 16.0 + cy = H - 1 - (wy + LOD_0_SIZE_Y) * 16.0 + edges = [e for e in (e_in, e_out) if e is not None] + for e in edges: + mx, my = MIDPOINTS[e] + mpx = (wx + mx + 0.5) * 16.0 + mpy = H - 1 - (wy + my + LOD_0_SIZE_Y) * 16.0 + hdraw.line([(mpx, mpy), (cx, cy)], fill=(0, 220, 255), width=3) + hdraw.ellipse([mpx - 2, mpy - 2, mpx + 2, mpy + 2], fill=(0, 240, 255)) + + hex_img.save("/home/bart/project/tiletest/images/minimap.png") + print("Saved orthographic hex minimap to /home/bart/project/tiletest/images/minimap.png") + except Exception as ex: + print(f"Minimap/preview generation skipped: {ex}") + +if __name__ == "__main__": + build_rivermap() diff --git a/images/Texturelabs_Forest_seamless.jpg b/images/Texturelabs_Forest_seamless.jpg new file mode 100644 index 0000000..a2cc015 Binary files /dev/null and b/images/Texturelabs_Forest_seamless.jpg differ diff --git a/images/Texturelabs_RedRock_seamless.jpg b/images/Texturelabs_RedRock_seamless.jpg new file mode 100644 index 0000000..3a6a266 Binary files /dev/null and b/images/Texturelabs_RedRock_seamless.jpg differ diff --git a/images/Texturelabs_RedSand_seamless.jpg b/images/Texturelabs_RedSand_seamless.jpg new file mode 100644 index 0000000..d94f69a Binary files /dev/null and b/images/Texturelabs_RedSand_seamless.jpg differ diff --git a/images/Texturelabs_Soil_126XL.jpg b/images/Texturelabs_Soil_126XL.jpg new file mode 100644 index 0000000..c5c339d Binary files /dev/null and b/images/Texturelabs_Soil_126XL.jpg differ diff --git a/images/Texturelabs_Soil_126XL_seamless.jpg b/images/Texturelabs_Soil_126XL_seamless.jpg new file mode 100644 index 0000000..804ffeb Binary files /dev/null and b/images/Texturelabs_Soil_126XL_seamless.jpg differ diff --git a/images/Texturelabs_Soil_126XL_seamless_2x2.jpg b/images/Texturelabs_Soil_126XL_seamless_2x2.jpg new file mode 100644 index 0000000..5938137 Binary files /dev/null and b/images/Texturelabs_Soil_126XL_seamless_2x2.jpg differ diff --git a/images/Texturelabs_Soil_126XL_seamless_4k.jpg b/images/Texturelabs_Soil_126XL_seamless_4k.jpg new file mode 100644 index 0000000..e79da53 Binary files /dev/null and b/images/Texturelabs_Soil_126XL_seamless_4k.jpg differ diff --git a/images/Texturelabs_Soil_133L.jpg b/images/Texturelabs_Soil_133L.jpg new file mode 100644 index 0000000..051d380 Binary files /dev/null and b/images/Texturelabs_Soil_133L.jpg differ diff --git a/images/Texturelabs_Soil_133L_seamless.jpg b/images/Texturelabs_Soil_133L_seamless.jpg new file mode 100644 index 0000000..c07af78 Binary files /dev/null and b/images/Texturelabs_Soil_133L_seamless.jpg differ diff --git a/images/Texturelabs_Soil_135XL.jpg b/images/Texturelabs_Soil_135XL.jpg new file mode 100644 index 0000000..4f4fcec Binary files /dev/null and b/images/Texturelabs_Soil_135XL.jpg differ diff --git a/images/Texturelabs_Soil_135XL_seamless.jpg b/images/Texturelabs_Soil_135XL_seamless.jpg new file mode 100644 index 0000000..9bb4412 Binary files /dev/null and b/images/Texturelabs_Soil_135XL_seamless.jpg differ diff --git a/images/Texturelabs_Stone_136XL.jpg b/images/Texturelabs_Stone_136XL.jpg new file mode 100644 index 0000000..4c40414 Binary files /dev/null and b/images/Texturelabs_Stone_136XL.jpg differ diff --git a/images/Texturelabs_Stone_136XL_seamless.jpg b/images/Texturelabs_Stone_136XL_seamless.jpg new file mode 100644 index 0000000..1270fb2 Binary files /dev/null and b/images/Texturelabs_Stone_136XL_seamless.jpg differ diff --git a/images/Texturelabs_Water_152XL.jpg b/images/Texturelabs_Water_152XL.jpg new file mode 100644 index 0000000..c4b0cb1 Binary files /dev/null and b/images/Texturelabs_Water_152XL.jpg differ diff --git a/images/Texturelabs_Water_152XL_seamless.jpg b/images/Texturelabs_Water_152XL_seamless.jpg new file mode 100644 index 0000000..e88db0e Binary files /dev/null and b/images/Texturelabs_Water_152XL_seamless.jpg differ diff --git a/images/Texturelabs_Water_155XL.jpg b/images/Texturelabs_Water_155XL.jpg new file mode 100644 index 0000000..24cdf33 Binary files /dev/null and b/images/Texturelabs_Water_155XL.jpg differ diff --git a/images/Texturelabs_Water_155XL_seamless.jpg b/images/Texturelabs_Water_155XL_seamless.jpg new file mode 100644 index 0000000..07be01e Binary files /dev/null and b/images/Texturelabs_Water_155XL_seamless.jpg differ diff --git a/images/actions/attack.png b/images/actions/attack.png new file mode 100644 index 0000000..8af6693 Binary files /dev/null and b/images/actions/attack.png differ diff --git a/images/actions/build_city.png b/images/actions/build_city.png new file mode 100644 index 0000000..dee21d8 Binary files /dev/null and b/images/actions/build_city.png differ diff --git a/images/actions/move.png b/images/actions/move.png new file mode 100644 index 0000000..c4774e7 Binary files /dev/null and b/images/actions/move.png differ diff --git a/images/heightmap.jpg b/images/heightmap.jpg index 939b34c..74bc8ff 100644 Binary files a/images/heightmap.jpg and b/images/heightmap.jpg differ diff --git a/images/heightmap_original.jpg b/images/heightmap_original.jpg new file mode 100644 index 0000000..939b34c Binary files /dev/null and b/images/heightmap_original.jpg differ diff --git a/images/hex_minimap.png b/images/hex_minimap.png new file mode 100644 index 0000000..2f5b94d Binary files /dev/null and b/images/hex_minimap.png differ diff --git a/images/minimap.png b/images/minimap.png new file mode 100644 index 0000000..def75de Binary files /dev/null and b/images/minimap.png differ diff --git a/images/rivermap.png b/images/rivermap.png new file mode 100644 index 0000000..af44a72 Binary files /dev/null and b/images/rivermap.png differ diff --git a/images/rivermap_preview.png b/images/rivermap_preview.png new file mode 100644 index 0000000..0c22da3 Binary files /dev/null and b/images/rivermap_preview.png differ diff --git a/images/typemap.png b/images/typemap.png new file mode 100644 index 0000000..ab0b4b9 Binary files /dev/null and b/images/typemap.png differ diff --git a/images/units/settler.png b/images/units/settler.png new file mode 100644 index 0000000..b4df293 Binary files /dev/null and b/images/units/settler.png differ diff --git a/images/units/slinger.png b/images/units/slinger.png new file mode 100644 index 0000000..5bd9c97 Binary files /dev/null and b/images/units/slinger.png differ diff --git a/images/units/warrior.png b/images/units/warrior.png new file mode 100644 index 0000000..c03b713 Binary files /dev/null and b/images/units/warrior.png differ diff --git a/mainwindow.o b/mainwindow.o index 9f18cef..1ea71e4 100644 Binary files a/mainwindow.o and b/mainwindow.o differ diff --git a/matrix2d.h b/matrix2d.h index 0cc4932..9c234dd 100644 --- a/matrix2d.h +++ b/matrix2d.h @@ -7,6 +7,9 @@ class Matrix2D { std::vector data; unsigned int sizeX, sizeY; public: + unsigned int getWidth() const { return sizeX; } + unsigned int getHeight() const { return sizeY; } + Matrix2D (unsigned int x, unsigned int y) : sizeX (x), sizeY (y) { data.resize (sizeX*sizeY); diff --git a/pathfinder.cpp b/pathfinder.cpp new file mode 100644 index 0000000..4e8d7d9 --- /dev/null +++ b/pathfinder.cpp @@ -0,0 +1,162 @@ +#include "pathfinder.h" +#include +#include +#include +#include + +QVector2D Pathfinder::hexToWorld(int x, int y) +{ + float wx = x * (LOD_0_SIZE * 0.75f); + float wy = y * (LOD_0_SIZE_Y * 2.0f) + ((x % 2 == 0) ? 0.0f : LOD_0_SIZE_Y); + return QVector2D(wx, wy); +} + +void Pathfinder::worldToHex(float wx, float wy, int &outX, int &outY) +{ + float stepX = LOD_0_SIZE * 0.75f; + float stepY = LOD_0_SIZE_Y * 2.0f; + + int approxX = (int) std::round(wx / stepX); + int approxY = (int) std::round(wy / stepY); + + float minDistSq = 1e9f; + int bestX = 0; + int bestY = 0; + + for (int dx = -2; dx <= 2; ++dx) { + for (int dy = -2; dy <= 2; ++dy) { + int cx = approxX + dx; + int cy = approxY + dy; + if (cx >= 0 && cx < MAP_WIDTH && cy >= 0 && cy < MAP_HEIGHT) { + QVector2D center = hexToWorld(cx, cy); + float distSq = (wx - center.x()) * (wx - center.x()) + (wy - center.y()) * (wy - center.y()); + if (distSq < minDistSq) { + minDistSq = distSq; + bestX = cx; + bestY = cy; + } + } + } + } + + outX = bestX; + outY = bestY; +} + +int Pathfinder::hexDistance(int x1, int y1, int x2, int y2) +{ + int q1 = x1; + int r1 = y1 - (x1 - (x1 & 1)) / 2; + int s1 = -q1 - r1; + + int q2 = x2; + int r2 = y2 - (x2 - (x2 & 1)) / 2; + int s2 = -q2 - r2; + + return (std::abs(q1 - q2) + std::abs(r1 - r2) + std::abs(s1 - s2)) / 2; +} + +namespace { + struct Node { + int x, y; + float g, f; + bool operator>(const Node &other) const { + return f > other.f; + } + }; + + inline int packCoord(int x, int y) { + return (y << 16) | (x & 0xFFFF); + } +} + +std::vector> Pathfinder::findPath( + int startX, int startY, + int goalX, int goalY, + Matrix2D *mapPoints, + int numPoints05) +{ + std::vector> path; + if (startX == goalX && startY == goalY) { + path.emplace_back(startX, startY); + return path; + } + + if (goalX < 0 || goalX >= MAP_WIDTH || goalY < 0 || goalY >= MAP_HEIGHT) { + return path; + } + + std::priority_queue, std::greater> openSet; + std::unordered_map gScore; + std::unordered_map cameFrom; + + int startPacked = packCoord(startX, startY); + gScore[startPacked] = 0.0f; + openSet.push({startX, startY, 0.0f, (float)hexDistance(startX, startY, goalX, goalY)}); + + int maxIterations = 3000; + int iterations = 0; + + bool found = false; + + while (!openSet.empty() && iterations++ < maxIterations) { + Node current = openSet.top(); + openSet.pop(); + + if (current.x == goalX && current.y == goalY) { + found = true; + break; + } + + int currPacked = packCoord(current.x, current.y); + if (current.g > gScore[currPacked]) continue; + + for (int edge = 0; edge < 6; ++edge) { + int nx, ny; + getHexNeighbor(current.x, current.y, edge, nx, ny); + + if (nx < 0 || nx >= MAP_WIDTH || ny < 0 || ny >= MAP_HEIGHT) continue; + + float stepCost = 1.0f; + + // Elevation check if terrain points are available + if (mapPoints && numPoints05 > 0) { + float scaleX = numPoints05 * 2.0f; + float scaleY = numPoints05 / LOD_0_SIZE_Y; + QVector2D nCenter = hexToWorld(nx, ny); + int mapX = (int) std::round(nCenter.x() * scaleX); + int mapY = (int) std::round(nCenter.y() * scaleY); + if (mapX >= 0 && mapX < (int)mapPoints->getWidth() && mapY >= 0 && mapY < (int)mapPoints->getHeight()) { + unsigned char h = mapPoints->operator()(mapX, mapY).height; + // Slightly penalize water tiles or high mountains + if (h < 5) stepCost += 4.0f; + else if (h > 220) stepCost += 2.0f; + } + } + + float tentativeG = current.g + stepCost; + int nPacked = packCoord(nx, ny); + + if (!gScore.count(nPacked) || tentativeG < gScore[nPacked]) { + gScore[nPacked] = tentativeG; + cameFrom[nPacked] = currPacked; + float h = (float) hexDistance(nx, ny, goalX, goalY); + openSet.push({nx, ny, tentativeG, tentativeG + h}); + } + } + } + + if (found) { + int curr = packCoord(goalX, goalY); + while (curr != startPacked) { + int cx = curr & 0xFFFF; + int cy = curr >> 16; + path.emplace_back(cx, cy); + curr = cameFrom[curr]; + } + path.emplace_back(startX, startY); + std::reverse(path.begin(), path.end()); + } + + return path; +} diff --git a/pathfinder.h b/pathfinder.h new file mode 100644 index 0000000..5cd6fff --- /dev/null +++ b/pathfinder.h @@ -0,0 +1,23 @@ +#ifndef PATHFINDER_H +#define PATHFINDER_H + +#include +#include +#include +#include "terrain.h" +#include "matrix2d.h" + +class Pathfinder { +public: + static QVector2D hexToWorld(int x, int y); + static void worldToHex(float wx, float wy, int &outX, int &outY); + static int hexDistance(int x1, int y1, int x2, int y2); + static std::vector> findPath( + int startX, int startY, + int goalX, int goalY, + Matrix2D *mapPoints = nullptr, + int numPoints05 = 0 + ); +}; + +#endif // PATHFINDER_H diff --git a/pathfinder.o b/pathfinder.o new file mode 100644 index 0000000..2836139 Binary files /dev/null and b/pathfinder.o differ diff --git a/player.cpp b/player.cpp new file mode 100644 index 0000000..0c42d0c --- /dev/null +++ b/player.cpp @@ -0,0 +1,53 @@ +#include "player.h" + +PlayerManager::PlayerManager() +{ + // Register default players with distinct, unique color combinations + // Player 1: Royal Blue primary, Radiant Gold secondary + addPlayer(1, "Speler 1", QColor(30, 136, 229), QColor(255, 213, 79)); + + // Player 2: Crimson Red primary, Crisp White secondary + addPlayer(2, "Speler 2", QColor(229, 57, 53), QColor(255, 255, 255)); + + // Player 3: Emerald Green primary, Amber Yellow secondary + addPlayer(3, "Speler 3", QColor(67, 160, 71), QColor(255, 179, 0)); + + // Player 4: Imperial Purple primary, Cyan secondary + addPlayer(4, "Speler 4", QColor(142, 36, 170), QColor(0, 229, 255)); +} + +bool PlayerManager::isCombinationUnique(const QColor &primary, const QColor &secondary, int excludeId) const +{ + for (const auto &p : m_players) { + if (p.id == excludeId) continue; + if (p.primaryColor == primary && p.secondaryColor == secondary) { + return false; + } + } + return true; +} + +bool PlayerManager::addPlayer(int id, const QString &name, const QColor &primary, const QColor &secondary) +{ + if (!isCombinationUnique(primary, secondary)) { + return false; + } + m_players.emplace_back(id, name, primary, secondary); + return true; +} + +const Player* PlayerManager::getPlayer(int id) const +{ + for (const auto &p : m_players) { + if (p.id == id) return &p; + } + return nullptr; +} + +Player* PlayerManager::getPlayer(int id) +{ + for (auto &p : m_players) { + if (p.id == id) return &p; + } + return nullptr; +} diff --git a/player.h b/player.h new file mode 100644 index 0000000..31184cc --- /dev/null +++ b/player.h @@ -0,0 +1,34 @@ +#ifndef PLAYER_H +#define PLAYER_H + +#include +#include +#include + +struct Player { + int id; + QString name; + QColor primaryColor; + QColor secondaryColor; + + Player() : id(0), name("Unknown"), primaryColor(Qt::blue), secondaryColor(Qt::white) {} + Player(int pId, const QString &pName, const QColor &primary, const QColor &secondary) + : id(pId), name(pName), primaryColor(primary), secondaryColor(secondary) {} +}; + +class PlayerManager { +public: + PlayerManager(); + + bool addPlayer(int id, const QString &name, const QColor &primary, const QColor &secondary); + const Player* getPlayer(int id) const; + Player* getPlayer(int id); + const std::vector& getPlayers() const { return m_players; } + + bool isCombinationUnique(const QColor &primary, const QColor &secondary, int excludeId = -1) const; + +private: + std::vector m_players; +}; + +#endif // PLAYER_H diff --git a/player.o b/player.o new file mode 100644 index 0000000..81b854b Binary files /dev/null and b/player.o differ diff --git a/qrc_shaders.cpp b/qrc_shaders.cpp index edb440d..87895e4 100644 --- a/qrc_shaders.cpp +++ b/qrc_shaders.cpp @@ -8,18 +8,60 @@ static const unsigned char qt_resource_data[] = { // /home/bart/project/tiletest/shaders/simple.frag - 0x0,0x0,0xa,0x2f, + 0x0,0x0,0x2d,0xf1, 0x23, 0x76,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x34,0x30,0x30,0xa,0x75,0x6e,0x69,0x66, 0x6f,0x72,0x6d,0x20,0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,0x32,0x44,0x20,0x74,0x65, - 0x78,0x3b,0x20,0x2f,0x2f,0x74,0x68,0x69,0x73,0x20,0x69,0x73,0x20,0x74,0x68,0x65, - 0x20,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0xa,0x75,0x6e,0x69,0x66,0x6f,0x72,0x6d, - 0x20,0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,0x32,0x44,0x20,0x62,0x6f,0x72,0x64,0x65, - 0x72,0x3b,0x20,0x2f,0x2f,0x74,0x68,0x69,0x73,0x20,0x69,0x73,0x20,0x74,0x68,0x65, - 0x20,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0xa,0x75,0x6e,0x69,0x66,0x6f,0x72,0x6d, - 0x20,0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,0x32,0x44,0x20,0x72,0x6f,0x63,0x6b,0x3b, - 0x20,0x2f,0x2f,0x74,0x68,0x69,0x73,0x20,0x69,0x73,0x20,0x74,0x68,0x65,0x20,0x74, - 0x65,0x78,0x74,0x75,0x72,0x65,0xa,0x73,0x6d,0x6f,0x6f,0x74,0x68,0x20,0x69,0x6e, + 0x78,0x47,0x72,0x61,0x73,0x73,0x3b,0x20,0x20,0x20,0x2f,0x2f,0x20,0x55,0x6e,0x69, + 0x74,0x20,0x30,0xa,0x75,0x6e,0x69,0x66,0x6f,0x72,0x6d,0x20,0x73,0x61,0x6d,0x70, + 0x6c,0x65,0x72,0x32,0x44,0x20,0x74,0x65,0x78,0x42,0x6f,0x72,0x64,0x65,0x72,0x3b, + 0x20,0x20,0x2f,0x2f,0x20,0x55,0x6e,0x69,0x74,0x20,0x31,0xa,0x75,0x6e,0x69,0x66, + 0x6f,0x72,0x6d,0x20,0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,0x32,0x44,0x20,0x74,0x65, + 0x78,0x52,0x6f,0x63,0x6b,0x3b,0x20,0x20,0x20,0x20,0x2f,0x2f,0x20,0x55,0x6e,0x69, + 0x74,0x20,0x32,0x20,0x28,0x53,0x74,0x6f,0x6e,0x65,0x20,0x63,0x6c,0x69,0x66,0x66, + 0x73,0x29,0xa,0x75,0x6e,0x69,0x66,0x6f,0x72,0x6d,0x20,0x73,0x61,0x6d,0x70,0x6c, + 0x65,0x72,0x32,0x44,0x20,0x74,0x65,0x78,0x53,0x61,0x6e,0x64,0x3b,0x20,0x20,0x20, + 0x20,0x2f,0x2f,0x20,0x55,0x6e,0x69,0x74,0x20,0x33,0x20,0x28,0x53,0x6f,0x69,0x6c, + 0x20,0x62,0x65,0x61,0x63,0x68,0x29,0xa,0x75,0x6e,0x69,0x66,0x6f,0x72,0x6d,0x20, + 0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,0x32,0x44,0x20,0x74,0x65,0x78,0x57,0x61,0x74, + 0x65,0x72,0x3b,0x20,0x20,0x20,0x2f,0x2f,0x20,0x55,0x6e,0x69,0x74,0x20,0x34,0x20, + 0x28,0x44,0x79,0x6e,0x61,0x6d,0x69,0x63,0x20,0x6f,0x63,0x65,0x61,0x6e,0x29,0xa, + 0x75,0x6e,0x69,0x66,0x6f,0x72,0x6d,0x20,0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,0x32, + 0x44,0x20,0x74,0x65,0x78,0x53,0x6e,0x6f,0x77,0x3b,0x20,0x20,0x20,0x20,0x2f,0x2f, + 0x20,0x55,0x6e,0x69,0x74,0x20,0x35,0x20,0x28,0x54,0x65,0x78,0x74,0x75,0x72,0x65, + 0x6c,0x61,0x62,0x73,0x5f,0x57,0x61,0x74,0x65,0x72,0x5f,0x31,0x35,0x35,0x58,0x4c, + 0x20,0x73,0x6e,0x6f,0x77,0x2f,0x69,0x63,0x65,0x29,0xa,0x75,0x6e,0x69,0x66,0x6f, + 0x72,0x6d,0x20,0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,0x32,0x44,0x20,0x74,0x65,0x78, + 0x54,0x79,0x70,0x65,0x4d,0x61,0x70,0x3b,0x20,0x2f,0x2f,0x20,0x55,0x6e,0x69,0x74, + 0x20,0x36,0x20,0x28,0x42,0x69,0x6f,0x6d,0x65,0x20,0x74,0x79,0x70,0x65,0x20,0x6d, + 0x61,0x70,0x29,0xa,0x75,0x6e,0x69,0x66,0x6f,0x72,0x6d,0x20,0x73,0x61,0x6d,0x70, + 0x6c,0x65,0x72,0x32,0x44,0x20,0x74,0x65,0x78,0x53,0x61,0x68,0x61,0x72,0x61,0x3b, + 0x20,0x20,0x2f,0x2f,0x20,0x55,0x6e,0x69,0x74,0x20,0x37,0x20,0x28,0x54,0x65,0x78, + 0x74,0x75,0x72,0x65,0x6c,0x61,0x62,0x73,0x5f,0x53,0x6f,0x69,0x6c,0x5f,0x31,0x33, + 0x33,0x4c,0x20,0x64,0x65,0x73,0x65,0x72,0x74,0x20,0x73,0x61,0x6e,0x64,0x29,0xa, + 0x75,0x6e,0x69,0x66,0x6f,0x72,0x6d,0x20,0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,0x32, + 0x44,0x20,0x74,0x65,0x78,0x54,0x75,0x6e,0x64,0x72,0x61,0x3b,0x20,0x20,0x2f,0x2f, + 0x20,0x55,0x6e,0x69,0x74,0x20,0x38,0x20,0x28,0x54,0x65,0x78,0x74,0x75,0x72,0x65, + 0x6c,0x61,0x62,0x73,0x5f,0x53,0x6f,0x69,0x6c,0x5f,0x31,0x33,0x35,0x58,0x4c,0x20, + 0x74,0x75,0x6e,0x64,0x72,0x61,0x20,0x73,0x6f,0x69,0x6c,0x29,0xa,0x75,0x6e,0x69, + 0x66,0x6f,0x72,0x6d,0x20,0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,0x32,0x44,0x20,0x74, + 0x65,0x78,0x52,0x65,0x64,0x53,0x61,0x6e,0x64,0x3b,0x20,0x2f,0x2f,0x20,0x55,0x6e, + 0x69,0x74,0x20,0x39,0x20,0x28,0x54,0x65,0x78,0x74,0x75,0x72,0x65,0x6c,0x61,0x62, + 0x73,0x5f,0x52,0x65,0x64,0x53,0x61,0x6e,0x64,0x20,0x70,0x72,0x61,0x69,0x72,0x69, + 0x65,0x20,0x73,0x61,0x6e,0x64,0x29,0xa,0x75,0x6e,0x69,0x66,0x6f,0x72,0x6d,0x20, + 0x73,0x61,0x6d,0x70,0x6c,0x65,0x72,0x32,0x44,0x20,0x74,0x65,0x78,0x52,0x65,0x64, + 0x52,0x6f,0x63,0x6b,0x3b,0x20,0x2f,0x2f,0x20,0x55,0x6e,0x69,0x74,0x20,0x31,0x30, + 0x20,0x28,0x54,0x65,0x78,0x74,0x75,0x72,0x65,0x6c,0x61,0x62,0x73,0x5f,0x52,0x65, + 0x64,0x52,0x6f,0x63,0x6b,0x20,0x63,0x61,0x6e,0x79,0x6f,0x6e,0x20,0x63,0x6c,0x69, + 0x66,0x66,0x29,0xa,0x75,0x6e,0x69,0x66,0x6f,0x72,0x6d,0x20,0x73,0x61,0x6d,0x70, + 0x6c,0x65,0x72,0x32,0x44,0x20,0x74,0x65,0x78,0x46,0x6f,0x72,0x65,0x73,0x74,0x3b, + 0x20,0x20,0x2f,0x2f,0x20,0x55,0x6e,0x69,0x74,0x20,0x31,0x31,0x20,0x28,0x54,0x65, + 0x78,0x74,0x75,0x72,0x65,0x6c,0x61,0x62,0x73,0x5f,0x46,0x6f,0x72,0x65,0x73,0x74, + 0x5f,0x73,0x65,0x61,0x6d,0x6c,0x65,0x73,0x73,0x20,0x77,0x6f,0x6f,0x64,0x6c,0x61, + 0x6e,0x64,0x20,0x66,0x6c,0x6f,0x6f,0x72,0x29,0xa,0xa,0x75,0x6e,0x69,0x66,0x6f, + 0x72,0x6d,0x20,0x66,0x6c,0x6f,0x61,0x74,0x20,0x74,0x69,0x6d,0x65,0x3b,0xa,0x75, + 0x6e,0x69,0x66,0x6f,0x72,0x6d,0x20,0x69,0x6e,0x74,0x20,0x72,0x69,0x76,0x65,0x72, + 0x4d,0x61,0x73,0x6b,0x3b,0xa,0xa,0x73,0x6d,0x6f,0x6f,0x74,0x68,0x20,0x69,0x6e, 0x20,0x76,0x65,0x63,0x33,0x20,0x76,0x4e,0x6f,0x72,0x6d,0x61,0x6c,0x3b,0xa,0x73, 0x6d,0x6f,0x6f,0x74,0x68,0x20,0x69,0x6e,0x20,0x76,0x65,0x63,0x33,0x20,0x76,0x57, 0x6f,0x72,0x6c,0x64,0x50,0x6f,0x73,0x3b,0xa,0x69,0x6e,0x20,0x76,0x65,0x63,0x33, @@ -30,151 +72,682 @@ static const unsigned char qt_resource_data[] = { 0x20,0x6d,0x61,0x74,0x34,0x20,0x6d,0x6f,0x64,0x65,0x6c,0x4d,0x61,0x74,0x72,0x69, 0x78,0x3b,0xa,0x69,0x6e,0x20,0x76,0x65,0x63,0x32,0x20,0x74,0x69,0x6c,0x65,0x50, 0x6f,0x73,0x3b,0xa,0x69,0x6e,0x20,0x76,0x65,0x63,0x32,0x20,0x66,0x72,0x61,0x67, - 0x54,0x65,0x78,0x43,0x6f,0x6f,0x72,0x64,0x3b,0x20,0x2f,0x2f,0x74,0x68,0x69,0x73, - 0x20,0x69,0x73,0x20,0x74,0x68,0x65,0x20,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x20, - 0x63,0x6f,0x6f,0x72,0x64,0xa,0x6f,0x75,0x74,0x20,0x76,0x65,0x63,0x34,0x20,0x66, - 0x69,0x6e,0x61,0x6c,0x43,0x6f,0x6c,0x6f,0x72,0x3b,0x20,0x2f,0x2f,0x74,0x68,0x69, - 0x73,0x20,0x69,0x73,0x20,0x74,0x68,0x65,0x20,0x6f,0x75,0x74,0x70,0x75,0x74,0x20, - 0x63,0x6f,0x6c,0x6f,0x72,0x20,0x6f,0x66,0x20,0x74,0x68,0x65,0x20,0x70,0x69,0x78, - 0x65,0x6c,0xa,0xa,0xa,0xa,0xa,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x53,0x69, - 0x6d,0x70,0x6c,0x65,0x44,0x69,0x72,0x65,0x63,0x74,0x69,0x6f,0x6e,0x61,0x6c,0x4c, - 0x69,0x67,0x68,0x74,0xa,0x7b,0xa,0x20,0x20,0x20,0x76,0x65,0x63,0x33,0x20,0x76, - 0x43,0x6f,0x6c,0x6f,0x72,0x3b,0xa,0x20,0x20,0x20,0x76,0x65,0x63,0x33,0x20,0x76, - 0x44,0x69,0x72,0x65,0x63,0x74,0x69,0x6f,0x6e,0x3b,0xa,0x20,0x20,0x20,0x66,0x6c, - 0x6f,0x61,0x74,0x20,0x66,0x41,0x6d,0x62,0x69,0x65,0x6e,0x74,0x49,0x6e,0x74,0x65, - 0x6e,0x73,0x69,0x74,0x79,0x3b,0xa,0x7d,0x3b,0xa,0xa,0xa,0xa,0x75,0x6e,0x69, - 0x66,0x6f,0x72,0x6d,0x20,0x53,0x69,0x6d,0x70,0x6c,0x65,0x44,0x69,0x72,0x65,0x63, - 0x74,0x69,0x6f,0x6e,0x61,0x6c,0x4c,0x69,0x67,0x68,0x74,0x20,0x73,0x75,0x6e,0x4c, - 0x69,0x67,0x68,0x74,0x3b,0xa,0xa,0x76,0x65,0x63,0x34,0x20,0x47,0x65,0x74,0x44, - 0x69,0x72,0x65,0x63,0x74,0x69,0x6f,0x6e,0x61,0x6c,0x4c,0x69,0x67,0x68,0x74,0x43, - 0x6f,0x6c,0x6f,0x72,0x28,0x53,0x69,0x6d,0x70,0x6c,0x65,0x44,0x69,0x72,0x65,0x63, - 0x74,0x69,0x6f,0x6e,0x61,0x6c,0x4c,0x69,0x67,0x68,0x74,0x20,0x64,0x69,0x72,0x4c, - 0x69,0x67,0x68,0x74,0x2c,0x20,0x76,0x65,0x63,0x33,0x20,0x76,0x4e,0x6f,0x72,0x6d, - 0x61,0x6c,0x29,0xa,0x7b,0xa,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x66,0x6c, + 0x54,0x65,0x78,0x43,0x6f,0x6f,0x72,0x64,0x3b,0xa,0x6f,0x75,0x74,0x20,0x76,0x65, + 0x63,0x34,0x20,0x66,0x69,0x6e,0x61,0x6c,0x43,0x6f,0x6c,0x6f,0x72,0x3b,0xa,0xa, + 0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x53,0x69,0x6d,0x70,0x6c,0x65,0x44,0x69,0x72, + 0x65,0x63,0x74,0x69,0x6f,0x6e,0x61,0x6c,0x4c,0x69,0x67,0x68,0x74,0xa,0x7b,0xa, + 0x20,0x20,0x20,0x76,0x65,0x63,0x33,0x20,0x76,0x43,0x6f,0x6c,0x6f,0x72,0x3b,0xa, + 0x20,0x20,0x20,0x76,0x65,0x63,0x33,0x20,0x76,0x44,0x69,0x72,0x65,0x63,0x74,0x69, + 0x6f,0x6e,0x3b,0xa,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x20,0x66,0x41,0x6d, + 0x62,0x69,0x65,0x6e,0x74,0x49,0x6e,0x74,0x65,0x6e,0x73,0x69,0x74,0x79,0x3b,0xa, + 0x7d,0x3b,0xa,0xa,0x75,0x6e,0x69,0x66,0x6f,0x72,0x6d,0x20,0x53,0x69,0x6d,0x70, + 0x6c,0x65,0x44,0x69,0x72,0x65,0x63,0x74,0x69,0x6f,0x6e,0x61,0x6c,0x4c,0x69,0x67, + 0x68,0x74,0x20,0x73,0x75,0x6e,0x4c,0x69,0x67,0x68,0x74,0x3b,0xa,0xa,0x76,0x65, + 0x63,0x34,0x20,0x47,0x65,0x74,0x44,0x69,0x72,0x65,0x63,0x74,0x69,0x6f,0x6e,0x61, + 0x6c,0x4c,0x69,0x67,0x68,0x74,0x43,0x6f,0x6c,0x6f,0x72,0x28,0x53,0x69,0x6d,0x70, + 0x6c,0x65,0x44,0x69,0x72,0x65,0x63,0x74,0x69,0x6f,0x6e,0x61,0x6c,0x4c,0x69,0x67, + 0x68,0x74,0x20,0x64,0x69,0x72,0x4c,0x69,0x67,0x68,0x74,0x2c,0x20,0x76,0x65,0x63, + 0x33,0x20,0x6e,0x6f,0x72,0x6d,0x29,0xa,0x7b,0xa,0x20,0x20,0x20,0x20,0x66,0x6c, 0x6f,0x61,0x74,0x20,0x66,0x44,0x69,0x66,0x66,0x75,0x73,0x65,0x49,0x6e,0x74,0x65, - 0x6e,0x73,0x69,0x74,0x79,0x20,0x3d,0x20,0x20,0x64,0x6f,0x74,0x28,0x76,0x4e,0x6f, - 0x72,0x6d,0x61,0x6c,0x2c,0x20,0x2d,0x64,0x69,0x72,0x4c,0x69,0x67,0x68,0x74,0x2e, - 0x76,0x44,0x69,0x72,0x65,0x63,0x74,0x69,0x6f,0x6e,0x29,0x3b,0xa,0x20,0x20,0x20, - 0x20,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x20,0x66,0x4d,0x75,0x6c,0x74, - 0x20,0x3d,0x20,0x70,0x6f,0x77,0x28,0x64,0x69,0x72,0x4c,0x69,0x67,0x68,0x74,0x2e, - 0x66,0x41,0x6d,0x62,0x69,0x65,0x6e,0x74,0x49,0x6e,0x74,0x65,0x6e,0x73,0x69,0x74, - 0x79,0x2b,0x66,0x44,0x69,0x66,0x66,0x75,0x73,0x65,0x49,0x6e,0x74,0x65,0x6e,0x73, - 0x69,0x74,0x79,0x2c,0x31,0x2e,0x30,0x29,0x3b,0x2f,0x2f,0x63,0x6c,0x61,0x6d,0x70, - 0x28,0x64,0x69,0x72,0x4c,0x69,0x67,0x68,0x74,0x2e,0x66,0x41,0x6d,0x62,0x69,0x65, - 0x6e,0x74,0x49,0x6e,0x74,0x65,0x6e,0x73,0x69,0x74,0x79,0x2b,0x66,0x44,0x69,0x66, - 0x66,0x75,0x73,0x65,0x49,0x6e,0x74,0x65,0x6e,0x73,0x69,0x74,0x79,0x2c,0x20,0x2d, - 0x31,0x2e,0x30,0x2c,0x20,0x31,0x2e,0x30,0x29,0x3b,0xa,0x20,0x20,0x2f,0x2f,0x20, - 0x69,0x66,0x28,0x64,0x69,0x72,0x4c,0x69,0x67,0x68,0x74,0x2e,0x69,0x53,0x6b,0x79, - 0x62,0x6f,0x78,0x20,0x3d,0x3d,0x20,0x31,0x29,0x66,0x4d,0x75,0x6c,0x74,0x20,0x3d, - 0x20,0x64,0x69,0x72,0x4c,0x69,0x67,0x68,0x74,0x2e,0x66,0x41,0x6d,0x62,0x69,0x65, - 0x6e,0x74,0x49,0x6e,0x74,0x65,0x6e,0x73,0x69,0x74,0x79,0x3b,0xa,0x20,0x20,0x20, - 0x20,0x20,0x20,0x20,0x20,0x72,0x65,0x74,0x75,0x72,0x6e,0x20,0x76,0x65,0x63,0x34, - 0x28,0x64,0x69,0x72,0x4c,0x69,0x67,0x68,0x74,0x2e,0x76,0x43,0x6f,0x6c,0x6f,0x72, - 0x2a,0x66,0x4d,0x75,0x6c,0x74,0x2c,0x20,0x31,0x2e,0x30,0x29,0x3b,0xa,0x7d,0xa, - 0xa,0x76,0x65,0x63,0x34,0x20,0x47,0x65,0x74,0x53,0x70,0x65,0x63,0x75,0x6c,0x61, - 0x72,0x43,0x6f,0x6c,0x6f,0x72,0x28,0x76,0x65,0x63,0x33,0x20,0x76,0x57,0x6f,0x72, - 0x6c,0x64,0x50,0x6f,0x73,0x2c,0x20,0x76,0x65,0x63,0x33,0x20,0x76,0x45,0x79,0x65, - 0x50,0x6f,0x73,0x2c,0x20,0x20,0x53,0x69,0x6d,0x70,0x6c,0x65,0x44,0x69,0x72,0x65, - 0x63,0x74,0x69,0x6f,0x6e,0x61,0x6c,0x4c,0x69,0x67,0x68,0x74,0x20,0x64,0x4c,0x69, - 0x67,0x68,0x74,0x2c,0x20,0x76,0x65,0x63,0x33,0x20,0x76,0x4e,0x6f,0x72,0x6d,0x61, - 0x6c,0x29,0xa,0x7b,0xa,0x20,0x20,0x20,0x76,0x65,0x63,0x34,0x20,0x76,0x52,0x65, - 0x73,0x75,0x6c,0x74,0x20,0x3d,0x20,0x76,0x65,0x63,0x34,0x28,0x30,0x2e,0x30,0x2c, - 0x20,0x30,0x2e,0x30,0x2c,0x20,0x30,0x2e,0x30,0x2c,0x20,0x30,0x2e,0x30,0x29,0x3b, - 0xa,0xa,0x20,0x20,0x20,0x76,0x65,0x63,0x33,0x20,0x76,0x52,0x65,0x66,0x6c,0x65, - 0x63,0x74,0x65,0x64,0x56,0x65,0x63,0x74,0x6f,0x72,0x20,0x3d,0x20,0x6e,0x6f,0x72, - 0x6d,0x61,0x6c,0x69,0x7a,0x65,0x28,0x72,0x65,0x66,0x6c,0x65,0x63,0x74,0x28,0x64, - 0x4c,0x69,0x67,0x68,0x74,0x2e,0x76,0x44,0x69,0x72,0x65,0x63,0x74,0x69,0x6f,0x6e, - 0x2c,0x20,0x76,0x4e,0x6f,0x72,0x6d,0x61,0x6c,0x29,0x29,0x3b,0xa,0x20,0x20,0x20, - 0x76,0x65,0x63,0x33,0x20,0x76,0x56,0x65,0x72,0x74,0x65,0x78,0x54,0x6f,0x45,0x79, - 0x65,0x56,0x65,0x63,0x74,0x6f,0x72,0x20,0x3d,0x20,0x6e,0x6f,0x72,0x6d,0x61,0x6c, - 0x69,0x7a,0x65,0x28,0x76,0x45,0x79,0x65,0x50,0x6f,0x73,0x2d,0x76,0x57,0x6f,0x72, - 0x6c,0x64,0x50,0x6f,0x73,0x29,0x3b,0xa,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74, + 0x6e,0x73,0x69,0x74,0x79,0x20,0x3d,0x20,0x6d,0x61,0x78,0x28,0x64,0x6f,0x74,0x28, + 0x6e,0x6f,0x72,0x6d,0x2c,0x20,0x2d,0x64,0x69,0x72,0x4c,0x69,0x67,0x68,0x74,0x2e, + 0x76,0x44,0x69,0x72,0x65,0x63,0x74,0x69,0x6f,0x6e,0x29,0x2c,0x20,0x30,0x2e,0x30, + 0x29,0x3b,0xa,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x20,0x66,0x4d,0x75, + 0x6c,0x74,0x20,0x3d,0x20,0x64,0x69,0x72,0x4c,0x69,0x67,0x68,0x74,0x2e,0x66,0x41, + 0x6d,0x62,0x69,0x65,0x6e,0x74,0x49,0x6e,0x74,0x65,0x6e,0x73,0x69,0x74,0x79,0x20, + 0x2b,0x20,0x66,0x44,0x69,0x66,0x66,0x75,0x73,0x65,0x49,0x6e,0x74,0x65,0x6e,0x73, + 0x69,0x74,0x79,0x20,0x2a,0x20,0x28,0x31,0x2e,0x30,0x20,0x2d,0x20,0x64,0x69,0x72, + 0x4c,0x69,0x67,0x68,0x74,0x2e,0x66,0x41,0x6d,0x62,0x69,0x65,0x6e,0x74,0x49,0x6e, + 0x74,0x65,0x6e,0x73,0x69,0x74,0x79,0x20,0x2a,0x20,0x30,0x2e,0x35,0x29,0x3b,0xa, + 0x20,0x20,0x20,0x20,0x72,0x65,0x74,0x75,0x72,0x6e,0x20,0x76,0x65,0x63,0x34,0x28, + 0x64,0x69,0x72,0x4c,0x69,0x67,0x68,0x74,0x2e,0x76,0x43,0x6f,0x6c,0x6f,0x72,0x20, + 0x2a,0x20,0x66,0x4d,0x75,0x6c,0x74,0x2c,0x20,0x31,0x2e,0x30,0x29,0x3b,0xa,0x7d, + 0xa,0xa,0x76,0x65,0x63,0x34,0x20,0x47,0x65,0x74,0x53,0x70,0x65,0x63,0x75,0x6c, + 0x61,0x72,0x43,0x6f,0x6c,0x6f,0x72,0x28,0x76,0x65,0x63,0x33,0x20,0x77,0x6f,0x72, + 0x6c,0x64,0x50,0x6f,0x73,0x2c,0x20,0x76,0x65,0x63,0x33,0x20,0x65,0x79,0x65,0x50, + 0x6f,0x73,0x2c,0x20,0x53,0x69,0x6d,0x70,0x6c,0x65,0x44,0x69,0x72,0x65,0x63,0x74, + 0x69,0x6f,0x6e,0x61,0x6c,0x4c,0x69,0x67,0x68,0x74,0x20,0x64,0x4c,0x69,0x67,0x68, + 0x74,0x2c,0x20,0x76,0x65,0x63,0x33,0x20,0x6e,0x6f,0x72,0x6d,0x2c,0x20,0x66,0x6c, + 0x6f,0x61,0x74,0x20,0x73,0x68,0x69,0x6e,0x69,0x6e,0x65,0x73,0x73,0x2c,0x20,0x66, + 0x6c,0x6f,0x61,0x74,0x20,0x69,0x6e,0x74,0x65,0x6e,0x73,0x69,0x74,0x79,0x29,0xa, + 0x7b,0xa,0x20,0x20,0x20,0x20,0x76,0x65,0x63,0x33,0x20,0x76,0x52,0x65,0x66,0x6c, + 0x65,0x63,0x74,0x65,0x64,0x56,0x65,0x63,0x74,0x6f,0x72,0x20,0x3d,0x20,0x6e,0x6f, + 0x72,0x6d,0x61,0x6c,0x69,0x7a,0x65,0x28,0x72,0x65,0x66,0x6c,0x65,0x63,0x74,0x28, + 0x64,0x4c,0x69,0x67,0x68,0x74,0x2e,0x76,0x44,0x69,0x72,0x65,0x63,0x74,0x69,0x6f, + 0x6e,0x2c,0x20,0x6e,0x6f,0x72,0x6d,0x29,0x29,0x3b,0xa,0x20,0x20,0x20,0x20,0x76, + 0x65,0x63,0x33,0x20,0x76,0x56,0x65,0x72,0x74,0x65,0x78,0x54,0x6f,0x45,0x79,0x65, + 0x56,0x65,0x63,0x74,0x6f,0x72,0x20,0x3d,0x20,0x6e,0x6f,0x72,0x6d,0x61,0x6c,0x69, + 0x7a,0x65,0x28,0x65,0x79,0x65,0x50,0x6f,0x73,0x20,0x2d,0x20,0x77,0x6f,0x72,0x6c, + 0x64,0x50,0x6f,0x73,0x29,0x3b,0xa,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74, 0x20,0x66,0x53,0x70,0x65,0x63,0x75,0x6c,0x61,0x72,0x46,0x61,0x63,0x74,0x6f,0x72, - 0x20,0x3d,0x20,0x64,0x6f,0x74,0x28,0x76,0x56,0x65,0x72,0x74,0x65,0x78,0x54,0x6f, - 0x45,0x79,0x65,0x56,0x65,0x63,0x74,0x6f,0x72,0x2c,0x20,0x76,0x52,0x65,0x66,0x6c, - 0x65,0x63,0x74,0x65,0x64,0x56,0x65,0x63,0x74,0x6f,0x72,0x29,0x3b,0xa,0xa,0x20, - 0x20,0x20,0x69,0x66,0x20,0x28,0x66,0x53,0x70,0x65,0x63,0x75,0x6c,0x61,0x72,0x46, - 0x61,0x63,0x74,0x6f,0x72,0x20,0x3e,0x20,0x30,0x29,0xa,0x20,0x20,0x20,0x7b,0xa, + 0x20,0x3d,0x20,0x6d,0x61,0x78,0x28,0x64,0x6f,0x74,0x28,0x76,0x56,0x65,0x72,0x74, + 0x65,0x78,0x54,0x6f,0x45,0x79,0x65,0x56,0x65,0x63,0x74,0x6f,0x72,0x2c,0x20,0x76, + 0x52,0x65,0x66,0x6c,0x65,0x63,0x74,0x65,0x64,0x56,0x65,0x63,0x74,0x6f,0x72,0x29, + 0x2c,0x20,0x30,0x2e,0x30,0x29,0x3b,0xa,0xa,0x20,0x20,0x20,0x20,0x69,0x66,0x20, + 0x28,0x66,0x53,0x70,0x65,0x63,0x75,0x6c,0x61,0x72,0x46,0x61,0x63,0x74,0x6f,0x72, + 0x20,0x3e,0x20,0x30,0x2e,0x30,0x29,0xa,0x20,0x20,0x20,0x20,0x7b,0xa,0x20,0x20, 0x20,0x20,0x20,0x20,0x20,0x20,0x66,0x53,0x70,0x65,0x63,0x75,0x6c,0x61,0x72,0x46, 0x61,0x63,0x74,0x6f,0x72,0x20,0x3d,0x20,0x70,0x6f,0x77,0x28,0x66,0x53,0x70,0x65, - 0x63,0x75,0x6c,0x61,0x72,0x46,0x61,0x63,0x74,0x6f,0x72,0x2c,0x20,0x31,0x2e,0x30, - 0x29,0x3b,0xa,0x20,0x20,0x20,0x20,0x20,0x20,0x76,0x52,0x65,0x73,0x75,0x6c,0x74, - 0x20,0x3d,0x20,0x76,0x65,0x63,0x34,0x28,0x64,0x4c,0x69,0x67,0x68,0x74,0x2e,0x76, - 0x43,0x6f,0x6c,0x6f,0x72,0x2c,0x20,0x31,0x2e,0x30,0x29,0x20,0x2a,0x20,0x31,0x2e, - 0x30,0x20,0x2a,0x20,0x66,0x53,0x70,0x65,0x63,0x75,0x6c,0x61,0x72,0x46,0x61,0x63, - 0x74,0x6f,0x72,0x3b,0xa,0x20,0x20,0x20,0x7d,0xa,0xa,0x20,0x20,0x20,0x72,0x65, - 0x74,0x75,0x72,0x6e,0x20,0x76,0x52,0x65,0x73,0x75,0x6c,0x74,0x3b,0xa,0x7d,0xa, - 0xa,0x76,0x6f,0x69,0x64,0x20,0x6d,0x61,0x69,0x6e,0x28,0x29,0x20,0x7b,0xa,0xa, - 0x20,0x20,0x20,0x20,0x76,0x65,0x63,0x33,0x20,0x73,0x75,0x72,0x66,0x61,0x63,0x65, - 0x54,0x6f,0x4c,0x69,0x67,0x68,0x74,0x20,0x3d,0x20,0x6e,0x6f,0x72,0x6d,0x61,0x6c, - 0x69,0x7a,0x65,0x28,0x73,0x75,0x6e,0x4c,0x69,0x67,0x68,0x74,0x2e,0x76,0x44,0x69, - 0x72,0x65,0x63,0x74,0x69,0x6f,0x6e,0x29,0x3b,0xa,0x20,0x20,0x20,0x20,0x76,0x65, - 0x63,0x33,0x20,0x73,0x75,0x72,0x66,0x61,0x63,0x65,0x50,0x6f,0x73,0x20,0x3d,0x20, - 0x76,0x65,0x63,0x33,0x28,0x6d,0x6f,0x64,0x65,0x6c,0x4d,0x61,0x74,0x72,0x69,0x78, - 0x20,0x2a,0x20,0x76,0x65,0x63,0x34,0x28,0x66,0x72,0x61,0x67,0x56,0x65,0x72,0x74, - 0x2c,0x20,0x31,0x29,0x29,0x3b,0xa,0x20,0x20,0x20,0x20,0x76,0x65,0x63,0x33,0x20, - 0x73,0x75,0x72,0x66,0x61,0x63,0x65,0x54,0x6f,0x43,0x61,0x6d,0x65,0x72,0x61,0x20, - 0x3d,0x20,0x6e,0x6f,0x72,0x6d,0x61,0x6c,0x69,0x7a,0x65,0x28,0x63,0x61,0x6d,0x65, - 0x72,0x61,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x2d,0x20,0x73,0x75,0x72, - 0x66,0x61,0x63,0x65,0x50,0x6f,0x73,0x29,0x3b,0xa,0x20,0x20,0x20,0x20,0x76,0x65, - 0x63,0x34,0x20,0x62,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x74,0x65,0x78,0x74, - 0x75,0x72,0x65,0x28,0x62,0x6f,0x72,0x64,0x65,0x72,0x2c,0x20,0x76,0x65,0x63,0x32, - 0x28,0x74,0x69,0x6c,0x65,0x50,0x6f,0x73,0x2e,0x78,0x2c,0x74,0x69,0x6c,0x65,0x50, - 0x6f,0x73,0x2e,0x79,0x2a,0x31,0x2e,0x31,0x35,0x34,0x37,0x33,0x34,0x34,0x31,0x31, - 0x29,0x29,0x3b,0xa,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x20,0x66,0x44, - 0x69,0x66,0x66,0x75,0x73,0x65,0x49,0x6e,0x74,0x65,0x6e,0x73,0x69,0x74,0x79,0x20, - 0x3d,0x20,0x6d,0x61,0x78,0x28,0x30,0x2e,0x30,0x2c,0x20,0x64,0x6f,0x74,0x28,0x6e, - 0x6f,0x72,0x6d,0x61,0x6c,0x69,0x7a,0x65,0x28,0x76,0x4e,0x6f,0x72,0x6d,0x61,0x6c, - 0x29,0x2c,0x20,0x2d,0x73,0x75,0x6e,0x4c,0x69,0x67,0x68,0x74,0x2e,0x76,0x44,0x69, - 0x72,0x65,0x63,0x74,0x69,0x6f,0x6e,0x29,0x29,0x3b,0xa,0xa,0x20,0x20,0x20,0x20, - 0x66,0x6c,0x6f,0x61,0x74,0x20,0x61,0x64,0x64,0x76,0x61,0x6c,0x20,0x3d,0x20,0x62, - 0x63,0x6f,0x6c,0x6f,0x72,0x2e,0x61,0x3b,0xa,0x20,0x20,0x20,0x20,0x76,0x65,0x63, - 0x34,0x20,0x76,0x53,0x70,0x65,0x63,0x75,0x6c,0x61,0x72,0x43,0x6f,0x6c,0x6f,0x72, - 0x20,0x3d,0x20,0x47,0x65,0x74,0x53,0x70,0x65,0x63,0x75,0x6c,0x61,0x72,0x43,0x6f, - 0x6c,0x6f,0x72,0x28,0x76,0x57,0x6f,0x72,0x6c,0x64,0x50,0x6f,0x73,0x2c,0x20,0x63, - 0x61,0x6d,0x65,0x72,0x61,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x2c,0x20,0x73, - 0x75,0x6e,0x4c,0x69,0x67,0x68,0x74,0x2c,0x20,0x6e,0x6f,0x72,0x6d,0x61,0x6c,0x69, - 0x7a,0x65,0x28,0x76,0x4e,0x6f,0x72,0x6d,0x61,0x6c,0x29,0x29,0x2b,0x20,0x47,0x65, - 0x74,0x44,0x69,0x72,0x65,0x63,0x74,0x69,0x6f,0x6e,0x61,0x6c,0x4c,0x69,0x67,0x68, - 0x74,0x43,0x6f,0x6c,0x6f,0x72,0x28,0x73,0x75,0x6e,0x4c,0x69,0x67,0x68,0x74,0x2c, - 0x6e,0x6f,0x72,0x6d,0x61,0x6c,0x69,0x7a,0x65,0x28,0x76,0x4e,0x6f,0x72,0x6d,0x61, - 0x6c,0x29,0x29,0x3b,0xa,0x20,0x20,0x20,0x20,0x76,0x65,0x63,0x34,0x20,0x67,0x72, - 0x6f,0x75,0x6e,0x64,0x20,0x3d,0x20,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x28,0x74, - 0x65,0x78,0x2c,0x20,0x66,0x72,0x61,0x67,0x54,0x65,0x78,0x43,0x6f,0x6f,0x72,0x64, - 0x29,0x3b,0xa,0x20,0x20,0x20,0x20,0x76,0x65,0x63,0x34,0x20,0x72,0x6f,0x63,0x6b, - 0x74,0x20,0x3d,0x20,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x28,0x72,0x6f,0x63,0x6b, - 0x2c,0x20,0x66,0x72,0x61,0x67,0x54,0x65,0x78,0x43,0x6f,0x6f,0x72,0x64,0x29,0x3b, - 0xa,0xa,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x20,0x67,0x6e,0x64,0x66, - 0x61,0x63,0x20,0x3d,0x20,0x31,0x2e,0x30,0x3b,0xa,0x20,0x20,0x20,0x20,0x69,0x66, - 0x20,0x28,0x7a,0x5f,0x70,0x6f,0x73,0x3e,0x30,0x2e,0x33,0x29,0xa,0x20,0x20,0x20, - 0x20,0x20,0x20,0x20,0x20,0x20,0x67,0x6e,0x64,0x66,0x61,0x63,0x20,0x3d,0x20,0x30, - 0x2e,0x30,0x3b,0xa,0x20,0x20,0x20,0x20,0x65,0x6c,0x73,0x65,0x20,0x69,0x66,0x20, - 0x28,0x7a,0x5f,0x70,0x6f,0x73,0x3e,0x30,0x2e,0x31,0x29,0xa,0x20,0x20,0x20,0x20, - 0x20,0x20,0x20,0x20,0x67,0x6e,0x64,0x66,0x61,0x63,0x3d,0x31,0x2e,0x30,0x20,0x2d, - 0x20,0x31,0x2e,0x30,0x20,0x2f,0x20,0x30,0x2e,0x32,0x20,0x2a,0x20,0x28,0x7a,0x5f, - 0x70,0x6f,0x73,0x2d,0x30,0x2e,0x31,0x29,0x20,0x3b,0xa,0x20,0x20,0x20,0x20,0x66, - 0x69,0x6e,0x61,0x6c,0x43,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x28,0x67,0x72,0x6f, - 0x75,0x6e,0x64,0x20,0x2a,0x67,0x6e,0x64,0x66,0x61,0x63,0x2b,0x72,0x6f,0x63,0x6b, - 0x74,0x20,0x2a,0x28,0x31,0x2e,0x30,0x2d,0x67,0x6e,0x64,0x66,0x61,0x63,0x29,0x20, - 0x2b,0x62,0x63,0x6f,0x6c,0x6f,0x72,0x2a,0x61,0x64,0x64,0x76,0x61,0x6c,0x29,0x20, - 0x2a,0x20,0x76,0x53,0x70,0x65,0x63,0x75,0x6c,0x61,0x72,0x43,0x6f,0x6c,0x6f,0x72, - 0x3b,0x20,0x2f,0x2f,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x28,0x74,0x65,0x78,0x2c, - 0x20,0x66,0x72,0x61,0x67,0x54,0x65,0x78,0x43,0x6f,0x6f,0x72,0x64,0x29,0x20,0x2a, - 0x20,0x70,0x6f,0x77,0x28,0x6d,0x61,0x78,0x28,0x30,0x2e,0x30,0x2c,0x20,0x64,0x6f, - 0x74,0x28,0x73,0x75,0x72,0x66,0x61,0x63,0x65,0x54,0x6f,0x43,0x61,0x6d,0x65,0x72, - 0x61,0x2c,0x20,0x72,0x65,0x66,0x6c,0x65,0x63,0x74,0x28,0x2d,0x73,0x75,0x72,0x66, - 0x61,0x63,0x65,0x54,0x6f,0x4c,0x69,0x67,0x68,0x74,0x2c,0x20,0x6e,0x6f,0x72,0x6d, - 0x61,0x6c,0x29,0x29,0x29,0x2c,0x20,0x30,0x2e,0x35,0x29,0x3b,0xa,0xa,0x20,0x20, - 0x20,0x20,0x2f,0x2f,0x20,0x28,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x28,0x74,0x65, - 0x78,0x2c,0x20,0x66,0x72,0x61,0x67,0x54,0x65,0x78,0x43,0x6f,0x6f,0x72,0x64,0x29, - 0x2b,0x62,0x63,0x6f,0x6c,0x6f,0x72,0x2a,0x61,0x64,0x64,0x76,0x61,0x6c,0x29,0x2a, - 0x76,0x65,0x63,0x34,0x28,0x73,0x75,0x6e,0x4c,0x69,0x67,0x68,0x74,0x2e,0x76,0x43, - 0x6f,0x6c,0x6f,0x72,0x2a,0x28,0x73,0x75,0x6e,0x4c,0x69,0x67,0x68,0x74,0x2e,0x66, - 0x41,0x6d,0x62,0x69,0x65,0x6e,0x74,0x49,0x6e,0x74,0x65,0x6e,0x73,0x69,0x74,0x79, - 0x2b,0x66,0x44,0x69,0x66,0x66,0x75,0x73,0x65,0x49,0x6e,0x74,0x65,0x6e,0x73,0x69, - 0x74,0x79,0x29,0x2c,0x20,0x31,0x2e,0x30,0x29,0x3b,0xa,0xa,0x7d,0xa, + 0x63,0x75,0x6c,0x61,0x72,0x46,0x61,0x63,0x74,0x6f,0x72,0x2c,0x20,0x73,0x68,0x69, + 0x6e,0x69,0x6e,0x65,0x73,0x73,0x29,0x3b,0xa,0x20,0x20,0x20,0x20,0x20,0x20,0x20, + 0x20,0x72,0x65,0x74,0x75,0x72,0x6e,0x20,0x76,0x65,0x63,0x34,0x28,0x64,0x4c,0x69, + 0x67,0x68,0x74,0x2e,0x76,0x43,0x6f,0x6c,0x6f,0x72,0x2c,0x20,0x31,0x2e,0x30,0x29, + 0x20,0x2a,0x20,0x69,0x6e,0x74,0x65,0x6e,0x73,0x69,0x74,0x79,0x20,0x2a,0x20,0x66, + 0x53,0x70,0x65,0x63,0x75,0x6c,0x61,0x72,0x46,0x61,0x63,0x74,0x6f,0x72,0x3b,0xa, + 0x20,0x20,0x20,0x20,0x7d,0xa,0x20,0x20,0x20,0x20,0x72,0x65,0x74,0x75,0x72,0x6e, + 0x20,0x76,0x65,0x63,0x34,0x28,0x30,0x2e,0x30,0x29,0x3b,0xa,0x7d,0xa,0xa,0x66, + 0x6c,0x6f,0x61,0x74,0x20,0x64,0x69,0x73,0x74,0x54,0x6f,0x53,0x65,0x67,0x6d,0x65, + 0x6e,0x74,0x28,0x76,0x65,0x63,0x32,0x20,0x70,0x2c,0x20,0x76,0x65,0x63,0x32,0x20, + 0x61,0x2c,0x20,0x76,0x65,0x63,0x32,0x20,0x62,0x29,0x20,0x7b,0xa,0x20,0x20,0x20, + 0x20,0x76,0x65,0x63,0x32,0x20,0x62,0x61,0x20,0x3d,0x20,0x62,0x20,0x2d,0x20,0x61, + 0x3b,0xa,0x20,0x20,0x20,0x20,0x76,0x65,0x63,0x32,0x20,0x70,0x61,0x20,0x3d,0x20, + 0x70,0x20,0x2d,0x20,0x61,0x3b,0xa,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74, + 0x20,0x6c,0x32,0x20,0x3d,0x20,0x64,0x6f,0x74,0x28,0x62,0x61,0x2c,0x20,0x62,0x61, + 0x29,0x3b,0xa,0x20,0x20,0x20,0x20,0x69,0x66,0x20,0x28,0x6c,0x32,0x20,0x3c,0x3d, + 0x20,0x31,0x65,0x2d,0x36,0x29,0x20,0x72,0x65,0x74,0x75,0x72,0x6e,0x20,0x6c,0x65, + 0x6e,0x67,0x74,0x68,0x28,0x70,0x61,0x29,0x3b,0xa,0x20,0x20,0x20,0x20,0x66,0x6c, + 0x6f,0x61,0x74,0x20,0x68,0x20,0x3d,0x20,0x63,0x6c,0x61,0x6d,0x70,0x28,0x64,0x6f, + 0x74,0x28,0x70,0x61,0x2c,0x20,0x62,0x61,0x29,0x20,0x2f,0x20,0x6c,0x32,0x2c,0x20, + 0x30,0x2e,0x30,0x2c,0x20,0x31,0x2e,0x30,0x29,0x3b,0xa,0x20,0x20,0x20,0x20,0x72, + 0x65,0x74,0x75,0x72,0x6e,0x20,0x6c,0x65,0x6e,0x67,0x74,0x68,0x28,0x70,0x61,0x20, + 0x2d,0x20,0x62,0x61,0x20,0x2a,0x20,0x68,0x29,0x3b,0xa,0x7d,0xa,0xa,0x63,0x6f, + 0x6e,0x73,0x74,0x20,0x76,0x65,0x63,0x32,0x20,0x45,0x44,0x47,0x45,0x5f,0x4d,0x49, + 0x44,0x50,0x4f,0x49,0x4e,0x54,0x53,0x5b,0x36,0x5d,0x20,0x3d,0x20,0x76,0x65,0x63, + 0x32,0x5b,0x36,0x5d,0x28,0xa,0x20,0x20,0x20,0x20,0x76,0x65,0x63,0x32,0x28,0x2d, + 0x30,0x2e,0x33,0x37,0x35,0x2c,0x20,0x20,0x30,0x2e,0x32,0x31,0x36,0x35,0x30,0x36, + 0x33,0x35,0x29,0x2c,0xa,0x20,0x20,0x20,0x20,0x76,0x65,0x63,0x32,0x28,0x20,0x30, + 0x2e,0x30,0x2c,0x20,0x20,0x20,0x20,0x30,0x2e,0x34,0x33,0x33,0x30,0x31,0x32,0x37, + 0x30,0x29,0x2c,0xa,0x20,0x20,0x20,0x20,0x76,0x65,0x63,0x32,0x28,0x20,0x30,0x2e, + 0x33,0x37,0x35,0x2c,0x20,0x20,0x30,0x2e,0x32,0x31,0x36,0x35,0x30,0x36,0x33,0x35, + 0x29,0x2c,0xa,0x20,0x20,0x20,0x20,0x76,0x65,0x63,0x32,0x28,0x20,0x30,0x2e,0x33, + 0x37,0x35,0x2c,0x20,0x2d,0x30,0x2e,0x32,0x31,0x36,0x35,0x30,0x36,0x33,0x35,0x29, + 0x2c,0xa,0x20,0x20,0x20,0x20,0x76,0x65,0x63,0x32,0x28,0x20,0x30,0x2e,0x30,0x2c, + 0x20,0x20,0x20,0x2d,0x30,0x2e,0x34,0x33,0x33,0x30,0x31,0x32,0x37,0x30,0x29,0x2c, + 0xa,0x20,0x20,0x20,0x20,0x76,0x65,0x63,0x32,0x28,0x2d,0x30,0x2e,0x33,0x37,0x35, + 0x2c,0x20,0x2d,0x30,0x2e,0x32,0x31,0x36,0x35,0x30,0x36,0x33,0x35,0x29,0xa,0x29, + 0x3b,0xa,0xa,0x66,0x6c,0x6f,0x61,0x74,0x20,0x67,0x65,0x74,0x52,0x69,0x76,0x65, + 0x72,0x44,0x69,0x73,0x74,0x61,0x6e,0x63,0x65,0x28,0x76,0x65,0x63,0x32,0x20,0x70, + 0x2c,0x20,0x69,0x6e,0x74,0x20,0x6d,0x61,0x73,0x6b,0x29,0x20,0x7b,0xa,0x20,0x20, + 0x20,0x20,0x69,0x66,0x20,0x28,0x6d,0x61,0x73,0x6b,0x20,0x3d,0x3d,0x20,0x30,0x29, + 0x20,0x72,0x65,0x74,0x75,0x72,0x6e,0x20,0x39,0x39,0x39,0x2e,0x30,0x3b,0xa,0xa, + 0x20,0x20,0x20,0x20,0x69,0x6e,0x74,0x20,0x65,0x64,0x67,0x65,0x73,0x5b,0x36,0x5d, + 0x3b,0xa,0x20,0x20,0x20,0x20,0x69,0x6e,0x74,0x20,0x63,0x6f,0x75,0x6e,0x74,0x20, + 0x3d,0x20,0x30,0x3b,0xa,0x20,0x20,0x20,0x20,0x66,0x6f,0x72,0x20,0x28,0x69,0x6e, + 0x74,0x20,0x65,0x20,0x3d,0x20,0x30,0x3b,0x20,0x65,0x20,0x3c,0x20,0x36,0x3b,0x20, + 0x2b,0x2b,0x65,0x29,0x20,0x7b,0xa,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x69, + 0x66,0x20,0x28,0x28,0x6d,0x61,0x73,0x6b,0x20,0x26,0x20,0x28,0x31,0x20,0x3c,0x3c, + 0x20,0x65,0x29,0x29,0x20,0x21,0x3d,0x20,0x30,0x29,0x20,0x7b,0xa,0x20,0x20,0x20, + 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x65,0x64,0x67,0x65,0x73,0x5b,0x63, + 0x6f,0x75,0x6e,0x74,0x5d,0x20,0x3d,0x20,0x65,0x3b,0xa,0x20,0x20,0x20,0x20,0x20, + 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x63,0x6f,0x75,0x6e,0x74,0x2b,0x2b,0x3b,0xa, + 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x7d,0xa,0x20,0x20,0x20,0x20,0x7d,0xa, + 0xa,0x20,0x20,0x20,0x20,0x69,0x66,0x20,0x28,0x63,0x6f,0x75,0x6e,0x74,0x20,0x3d, + 0x3d,0x20,0x32,0x29,0x20,0x7b,0xa,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x76, + 0x65,0x63,0x32,0x20,0x70,0x30,0x20,0x3d,0x20,0x45,0x44,0x47,0x45,0x5f,0x4d,0x49, + 0x44,0x50,0x4f,0x49,0x4e,0x54,0x53,0x5b,0x65,0x64,0x67,0x65,0x73,0x5b,0x30,0x5d, + 0x5d,0x3b,0xa,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x76,0x65,0x63,0x32,0x20, + 0x70,0x32,0x20,0x3d,0x20,0x45,0x44,0x47,0x45,0x5f,0x4d,0x49,0x44,0x50,0x4f,0x49, + 0x4e,0x54,0x53,0x5b,0x65,0x64,0x67,0x65,0x73,0x5b,0x31,0x5d,0x5d,0x3b,0xa,0x20, + 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x76,0x65,0x63,0x32,0x20,0x73,0x30,0x20,0x3d, + 0x20,0x70,0x30,0x3b,0xa,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x76,0x65,0x63, + 0x32,0x20,0x73,0x31,0x20,0x3d,0x20,0x30,0x2e,0x35,0x36,0x32,0x35,0x20,0x2a,0x20, + 0x70,0x30,0x20,0x2b,0x20,0x30,0x2e,0x30,0x36,0x32,0x35,0x20,0x2a,0x20,0x70,0x32, + 0x3b,0xa,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x76,0x65,0x63,0x32,0x20,0x73, + 0x32,0x20,0x3d,0x20,0x30,0x2e,0x32,0x35,0x30,0x30,0x20,0x2a,0x20,0x70,0x30,0x20, + 0x2b,0x20,0x30,0x2e,0x32,0x35,0x30,0x30,0x20,0x2a,0x20,0x70,0x32,0x3b,0xa,0x20, + 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x76,0x65,0x63,0x32,0x20,0x73,0x33,0x20,0x3d, + 0x20,0x30,0x2e,0x30,0x36,0x32,0x35,0x20,0x2a,0x20,0x70,0x30,0x20,0x2b,0x20,0x30, + 0x2e,0x35,0x36,0x32,0x35,0x20,0x2a,0x20,0x70,0x32,0x3b,0xa,0x20,0x20,0x20,0x20, + 0x20,0x20,0x20,0x20,0x76,0x65,0x63,0x32,0x20,0x73,0x34,0x20,0x3d,0x20,0x70,0x32, + 0x3b,0xa,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x20, + 0x64,0x30,0x20,0x3d,0x20,0x64,0x69,0x73,0x74,0x54,0x6f,0x53,0x65,0x67,0x6d,0x65, + 0x6e,0x74,0x28,0x70,0x2c,0x20,0x73,0x30,0x2c,0x20,0x73,0x31,0x29,0x3b,0xa,0x20, + 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x20,0x64,0x31,0x20, + 0x3d,0x20,0x64,0x69,0x73,0x74,0x54,0x6f,0x53,0x65,0x67,0x6d,0x65,0x6e,0x74,0x28, + 0x70,0x2c,0x20,0x73,0x31,0x2c,0x20,0x73,0x32,0x29,0x3b,0xa,0x20,0x20,0x20,0x20, + 0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x20,0x64,0x32,0x20,0x3d,0x20,0x64, + 0x69,0x73,0x74,0x54,0x6f,0x53,0x65,0x67,0x6d,0x65,0x6e,0x74,0x28,0x70,0x2c,0x20, + 0x73,0x32,0x2c,0x20,0x73,0x33,0x29,0x3b,0xa,0x20,0x20,0x20,0x20,0x20,0x20,0x20, + 0x20,0x66,0x6c,0x6f,0x61,0x74,0x20,0x64,0x33,0x20,0x3d,0x20,0x64,0x69,0x73,0x74, + 0x54,0x6f,0x53,0x65,0x67,0x6d,0x65,0x6e,0x74,0x28,0x70,0x2c,0x20,0x73,0x33,0x2c, + 0x20,0x73,0x34,0x29,0x3b,0xa,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x72,0x65, + 0x74,0x75,0x72,0x6e,0x20,0x6d,0x69,0x6e,0x28,0x6d,0x69,0x6e,0x28,0x64,0x30,0x2c, + 0x20,0x64,0x31,0x29,0x2c,0x20,0x6d,0x69,0x6e,0x28,0x64,0x32,0x2c,0x20,0x64,0x33, + 0x29,0x29,0x3b,0xa,0x20,0x20,0x20,0x20,0x7d,0x20,0x65,0x6c,0x73,0x65,0x20,0x7b, + 0xa,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x20,0x6d, + 0x69,0x6e,0x44,0x69,0x73,0x74,0x20,0x3d,0x20,0x39,0x39,0x39,0x2e,0x30,0x3b,0xa, + 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x66,0x6f,0x72,0x20,0x28,0x69,0x6e,0x74, + 0x20,0x69,0x20,0x3d,0x20,0x30,0x3b,0x20,0x69,0x20,0x3c,0x20,0x63,0x6f,0x75,0x6e, + 0x74,0x3b,0x20,0x2b,0x2b,0x69,0x29,0x20,0x7b,0xa,0x20,0x20,0x20,0x20,0x20,0x20, + 0x20,0x20,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x20,0x64,0x20,0x3d,0x20, + 0x64,0x69,0x73,0x74,0x54,0x6f,0x53,0x65,0x67,0x6d,0x65,0x6e,0x74,0x28,0x70,0x2c, + 0x20,0x45,0x44,0x47,0x45,0x5f,0x4d,0x49,0x44,0x50,0x4f,0x49,0x4e,0x54,0x53,0x5b, + 0x65,0x64,0x67,0x65,0x73,0x5b,0x69,0x5d,0x5d,0x2c,0x20,0x76,0x65,0x63,0x32,0x28, + 0x30,0x2e,0x30,0x29,0x29,0x3b,0xa,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20, + 0x20,0x20,0x20,0x6d,0x69,0x6e,0x44,0x69,0x73,0x74,0x20,0x3d,0x20,0x6d,0x69,0x6e, + 0x28,0x6d,0x69,0x6e,0x44,0x69,0x73,0x74,0x2c,0x20,0x64,0x29,0x3b,0xa,0x20,0x20, + 0x20,0x20,0x20,0x20,0x20,0x20,0x7d,0xa,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20, + 0x72,0x65,0x74,0x75,0x72,0x6e,0x20,0x6d,0x69,0x6e,0x44,0x69,0x73,0x74,0x3b,0xa, + 0x20,0x20,0x20,0x20,0x7d,0xa,0x7d,0xa,0xa,0x76,0x6f,0x69,0x64,0x20,0x6d,0x61, + 0x69,0x6e,0x28,0x29,0x20,0x7b,0xa,0x20,0x20,0x20,0x20,0x76,0x65,0x63,0x33,0x20, + 0x6e,0x6f,0x72,0x6d,0x20,0x3d,0x20,0x6e,0x6f,0x72,0x6d,0x61,0x6c,0x69,0x7a,0x65, + 0x28,0x76,0x4e,0x6f,0x72,0x6d,0x61,0x6c,0x29,0x3b,0xa,0xa,0x20,0x20,0x20,0x20, + 0x2f,0x2f,0x20,0x31,0x2e,0x20,0x42,0x69,0x6f,0x6d,0x65,0x20,0x54,0x79,0x70,0x65, + 0x6d,0x61,0x70,0x20,0x53,0x61,0x6d,0x70,0x6c,0x69,0x6e,0x67,0xa,0x20,0x20,0x20, + 0x20,0x2f,0x2f,0x20,0x57,0x6f,0x72,0x6c,0x64,0x20,0x65,0x78,0x74,0x65,0x6e,0x74, + 0x3a,0x20,0x5b,0x30,0x2e,0x2e,0x32,0x32,0x35,0x2e,0x30,0x20,0x6b,0x6d,0x2c,0x20, + 0x30,0x2e,0x2e,0x31,0x30,0x38,0x2e,0x32,0x32,0x20,0x6b,0x6d,0x5d,0xa,0x20,0x20, + 0x20,0x20,0x2f,0x2f,0x20,0x4e,0x6f,0x74,0x65,0x3a,0x20,0x51,0x49,0x6d,0x61,0x67, + 0x65,0x20,0x72,0x6f,0x77,0x20,0x30,0x20,0x69,0x73,0x20,0x4e,0x6f,0x72,0x74,0x68, + 0x20,0x28,0x77,0x6f,0x72,0x6c,0x64,0x59,0x20,0x3d,0x20,0x31,0x30,0x38,0x2e,0x32, + 0x32,0x29,0x2c,0x20,0x77,0x68,0x69,0x63,0x68,0x20,0x51,0x4f,0x70,0x65,0x6e,0x47, + 0x4c,0x54,0x65,0x78,0x74,0x75,0x72,0x65,0x20,0x6d,0x61,0x70,0x73,0x20,0x74,0x6f, + 0x20,0x76,0x20,0x3d,0x20,0x30,0x2e,0x30,0x2e,0xa,0x20,0x20,0x20,0x20,0x2f,0x2f, + 0x20,0x49,0x6e,0x76,0x65,0x72,0x74,0x20,0x59,0x20,0x73,0x6f,0x20,0x4e,0x6f,0x72, + 0x74,0x68,0x20,0x73,0x61,0x6d,0x70,0x6c,0x65,0x73,0x20,0x72,0x6f,0x77,0x20,0x30, + 0x20,0x61,0x6e,0x64,0x20,0x53,0x6f,0x75,0x74,0x68,0x20,0x73,0x61,0x6d,0x70,0x6c, + 0x65,0x73,0x20,0x72,0x6f,0x77,0x20,0x31,0x34,0x39,0x39,0x2e,0xa,0x20,0x20,0x20, + 0x20,0x76,0x65,0x63,0x32,0x20,0x75,0x76,0x54,0x79,0x70,0x65,0x20,0x3d,0x20,0x76, + 0x65,0x63,0x32,0x28,0x63,0x6c,0x61,0x6d,0x70,0x28,0x76,0x57,0x6f,0x72,0x6c,0x64, + 0x50,0x6f,0x73,0x2e,0x78,0x20,0x2f,0x20,0x32,0x32,0x35,0x2e,0x30,0x2c,0x20,0x30, + 0x2e,0x30,0x2c,0x20,0x31,0x2e,0x30,0x29,0x2c,0x20,0x31,0x2e,0x30,0x20,0x2d,0x20, + 0x63,0x6c,0x61,0x6d,0x70,0x28,0x76,0x57,0x6f,0x72,0x6c,0x64,0x50,0x6f,0x73,0x2e, + 0x79,0x20,0x2f,0x20,0x31,0x30,0x38,0x2e,0x32,0x32,0x2c,0x20,0x30,0x2e,0x30,0x2c, + 0x20,0x31,0x2e,0x30,0x29,0x29,0x3b,0xa,0x20,0x20,0x20,0x20,0x76,0x65,0x63,0x34, + 0x20,0x74,0x79,0x70,0x65,0x50,0x69,0x78,0x65,0x6c,0x20,0x3d,0x20,0x74,0x65,0x78, + 0x74,0x75,0x72,0x65,0x28,0x74,0x65,0x78,0x54,0x79,0x70,0x65,0x4d,0x61,0x70,0x2c, + 0x20,0x75,0x76,0x54,0x79,0x70,0x65,0x29,0x3b,0xa,0xa,0x20,0x20,0x20,0x20,0x2f, + 0x2f,0x20,0x43,0x6f,0x6d,0x70,0x75,0x74,0x65,0x20,0x73,0x6d,0x6f,0x6f,0x74,0x68, + 0x20,0x62,0x69,0x6f,0x6d,0x65,0x20,0x77,0x65,0x69,0x67,0x68,0x74,0x73,0x20,0x66, + 0x72,0x6f,0x6d,0x20,0x74,0x79,0x70,0x65,0x6d,0x61,0x70,0x20,0x63,0x6f,0x6c,0x6f, + 0x72,0x73,0x3a,0xa,0x20,0x20,0x20,0x20,0x2f,0x2f,0x20,0x31,0x2e,0x20,0x50,0x72, + 0x61,0x69,0x72,0x69,0x65,0x3a,0x20,0x72,0x65,0x64,0x20,0x64,0x6f,0x6d,0x69,0x6e, + 0x61,0x6e,0x74,0x20,0x28,0x72,0x20,0x2d,0x20,0x67,0x29,0xa,0x20,0x20,0x20,0x20, + 0x66,0x6c,0x6f,0x61,0x74,0x20,0x70,0x72,0x61,0x69,0x72,0x69,0x65,0x57,0x65,0x69, + 0x67,0x68,0x74,0x20,0x3d,0x20,0x63,0x6c,0x61,0x6d,0x70,0x28,0x28,0x74,0x79,0x70, + 0x65,0x50,0x69,0x78,0x65,0x6c,0x2e,0x72,0x20,0x2d,0x20,0x74,0x79,0x70,0x65,0x50, + 0x69,0x78,0x65,0x6c,0x2e,0x67,0x20,0x2d,0x20,0x30,0x2e,0x32,0x30,0x29,0x20,0x2f, + 0x20,0x30,0x2e,0x32,0x35,0x2c,0x20,0x30,0x2e,0x30,0x2c,0x20,0x31,0x2e,0x30,0x29, + 0x3b,0xa,0x20,0x20,0x20,0x20,0x2f,0x2f,0x20,0x32,0x2e,0x20,0x53,0x61,0x68,0x61, + 0x72,0x61,0x3a,0x20,0x79,0x65,0x6c,0x6c,0x6f,0x77,0x20,0x28,0x72,0x20,0x26,0x20, + 0x67,0x20,0x68,0x69,0x67,0x68,0x2c,0x20,0x62,0x20,0x6c,0x6f,0x77,0x29,0xa,0x20, + 0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x20,0x73,0x61,0x68,0x61,0x72,0x61,0x57, + 0x65,0x69,0x67,0x68,0x74,0x20,0x3d,0x20,0x63,0x6c,0x61,0x6d,0x70,0x28,0x28,0x6d, + 0x69,0x6e,0x28,0x74,0x79,0x70,0x65,0x50,0x69,0x78,0x65,0x6c,0x2e,0x72,0x2c,0x20, + 0x74,0x79,0x70,0x65,0x50,0x69,0x78,0x65,0x6c,0x2e,0x67,0x29,0x20,0x2d,0x20,0x30, + 0x2e,0x34,0x35,0x29,0x20,0x2f,0x20,0x30,0x2e,0x32,0x35,0x2c,0x20,0x30,0x2e,0x30, + 0x2c,0x20,0x31,0x2e,0x30,0x29,0x20,0x2a,0x20,0x28,0x31,0x2e,0x30,0x20,0x2d,0x20, + 0x70,0x72,0x61,0x69,0x72,0x69,0x65,0x57,0x65,0x69,0x67,0x68,0x74,0x29,0x3b,0xa, + 0x20,0x20,0x20,0x20,0x2f,0x2f,0x20,0x33,0x2e,0x20,0x46,0x6f,0x72,0x65,0x73,0x74, + 0x3a,0x20,0x65,0x6c,0x65,0x76,0x61,0x74,0x65,0x64,0x20,0x62,0x6c,0x75,0x65,0x20, + 0x77,0x69,0x74,0x68,0x20,0x6c,0x6f,0x77,0x20,0x72,0x65,0x64,0x20,0x28,0x62,0x20, + 0x3e,0x20,0x30,0x2e,0x31,0x35,0x2c,0x20,0x72,0x20,0x3c,0x20,0x30,0x2e,0x32,0x35, + 0x29,0xa,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x20,0x66,0x6f,0x72,0x65, + 0x73,0x74,0x57,0x65,0x69,0x67,0x68,0x74,0x20,0x3d,0x20,0x63,0x6c,0x61,0x6d,0x70, + 0x28,0x28,0x74,0x79,0x70,0x65,0x50,0x69,0x78,0x65,0x6c,0x2e,0x62,0x20,0x2d,0x20, + 0x30,0x2e,0x31,0x35,0x29,0x20,0x2f,0x20,0x30,0x2e,0x32,0x30,0x2c,0x20,0x30,0x2e, + 0x30,0x2c,0x20,0x31,0x2e,0x30,0x29,0x20,0x2a,0x20,0x63,0x6c,0x61,0x6d,0x70,0x28, + 0x28,0x30,0x2e,0x32,0x35,0x20,0x2d,0x20,0x74,0x79,0x70,0x65,0x50,0x69,0x78,0x65, + 0x6c,0x2e,0x72,0x29,0x20,0x2f,0x20,0x30,0x2e,0x31,0x35,0x2c,0x20,0x30,0x2e,0x30, + 0x2c,0x20,0x31,0x2e,0x30,0x29,0x20,0x2a,0x20,0x28,0x31,0x2e,0x30,0x20,0x2d,0x20, + 0x70,0x72,0x61,0x69,0x72,0x69,0x65,0x57,0x65,0x69,0x67,0x68,0x74,0x29,0x20,0x2a, + 0x20,0x28,0x31,0x2e,0x30,0x20,0x2d,0x20,0x73,0x61,0x68,0x61,0x72,0x61,0x57,0x65, + 0x69,0x67,0x68,0x74,0x29,0x3b,0xa,0x20,0x20,0x20,0x20,0x2f,0x2f,0x20,0x34,0x2e, + 0x20,0x54,0x75,0x6e,0x64,0x72,0x61,0x3a,0x20,0x6d,0x75,0x74,0x65,0x64,0x20,0x73, + 0x6c,0x61,0x74,0x65,0x2f,0x6f,0x6c,0x69,0x76,0x65,0x20,0x28,0x6c,0x6f,0x77,0x20, + 0x73,0x61,0x74,0x75,0x72,0x61,0x74,0x69,0x6f,0x6e,0x2c,0x20,0x6d,0x6f,0x64,0x65, + 0x72,0x61,0x74,0x65,0x2f,0x6c,0x6f,0x77,0x20,0x67,0x72,0x65,0x65,0x6e,0x29,0xa, + 0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x20,0x73,0x61,0x74,0x20,0x3d,0x20, + 0x6d,0x61,0x78,0x28,0x74,0x79,0x70,0x65,0x50,0x69,0x78,0x65,0x6c,0x2e,0x72,0x2c, + 0x20,0x6d,0x61,0x78,0x28,0x74,0x79,0x70,0x65,0x50,0x69,0x78,0x65,0x6c,0x2e,0x67, + 0x2c,0x20,0x74,0x79,0x70,0x65,0x50,0x69,0x78,0x65,0x6c,0x2e,0x62,0x29,0x29,0x20, + 0x2d,0x20,0x6d,0x69,0x6e,0x28,0x74,0x79,0x70,0x65,0x50,0x69,0x78,0x65,0x6c,0x2e, + 0x72,0x2c,0x20,0x6d,0x69,0x6e,0x28,0x74,0x79,0x70,0x65,0x50,0x69,0x78,0x65,0x6c, + 0x2e,0x67,0x2c,0x20,0x74,0x79,0x70,0x65,0x50,0x69,0x78,0x65,0x6c,0x2e,0x62,0x29, + 0x29,0x3b,0xa,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x20,0x74,0x75,0x6e, + 0x64,0x72,0x61,0x57,0x65,0x69,0x67,0x68,0x74,0x20,0x3d,0x20,0x63,0x6c,0x61,0x6d, + 0x70,0x28,0x28,0x30,0x2e,0x32,0x35,0x20,0x2d,0x20,0x73,0x61,0x74,0x29,0x20,0x2f, + 0x20,0x30,0x2e,0x31,0x35,0x2c,0x20,0x30,0x2e,0x30,0x2c,0x20,0x31,0x2e,0x30,0x29, + 0x20,0x2a,0x20,0x63,0x6c,0x61,0x6d,0x70,0x28,0x28,0x30,0x2e,0x35,0x35,0x20,0x2d, + 0x20,0x74,0x79,0x70,0x65,0x50,0x69,0x78,0x65,0x6c,0x2e,0x67,0x29,0x20,0x2f,0x20, + 0x30,0x2e,0x31,0x35,0x2c,0x20,0x30,0x2e,0x30,0x2c,0x20,0x31,0x2e,0x30,0x29,0x20, + 0x2a,0x20,0x28,0x31,0x2e,0x30,0x20,0x2d,0x20,0x70,0x72,0x61,0x69,0x72,0x69,0x65, + 0x57,0x65,0x69,0x67,0x68,0x74,0x29,0x20,0x2a,0x20,0x28,0x31,0x2e,0x30,0x20,0x2d, + 0x20,0x73,0x61,0x68,0x61,0x72,0x61,0x57,0x65,0x69,0x67,0x68,0x74,0x29,0x20,0x2a, + 0x20,0x28,0x31,0x2e,0x30,0x20,0x2d,0x20,0x66,0x6f,0x72,0x65,0x73,0x74,0x57,0x65, + 0x69,0x67,0x68,0x74,0x29,0x3b,0xa,0x20,0x20,0x20,0x20,0x2f,0x2f,0x20,0x35,0x2e, + 0x20,0x47,0x72,0x61,0x73,0x73,0x6c,0x61,0x6e,0x64,0x3a,0x20,0x67,0x72,0x65,0x65, + 0x6e,0x20,0x64,0x6f,0x6d,0x69,0x6e,0x61,0x6e,0x74,0x20,0x6f,0x70,0x65,0x6e,0x20, + 0x70,0x6c,0x61,0x69,0x6e,0x73,0xa,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74, + 0x20,0x67,0x72,0x61,0x73,0x73,0x6c,0x61,0x6e,0x64,0x57,0x65,0x69,0x67,0x68,0x74, + 0x20,0x3d,0x20,0x63,0x6c,0x61,0x6d,0x70,0x28,0x31,0x2e,0x30,0x20,0x2d,0x20,0x28, + 0x70,0x72,0x61,0x69,0x72,0x69,0x65,0x57,0x65,0x69,0x67,0x68,0x74,0x20,0x2b,0x20, + 0x73,0x61,0x68,0x61,0x72,0x61,0x57,0x65,0x69,0x67,0x68,0x74,0x20,0x2b,0x20,0x66, + 0x6f,0x72,0x65,0x73,0x74,0x57,0x65,0x69,0x67,0x68,0x74,0x20,0x2b,0x20,0x74,0x75, + 0x6e,0x64,0x72,0x61,0x57,0x65,0x69,0x67,0x68,0x74,0x29,0x2c,0x20,0x30,0x2e,0x30, + 0x2c,0x20,0x31,0x2e,0x30,0x29,0x3b,0xa,0xa,0x20,0x20,0x20,0x20,0x2f,0x2f,0x20, + 0x32,0x2e,0x20,0x50,0x6f,0x6c,0x61,0x72,0x20,0x4c,0x61,0x74,0x69,0x74,0x75,0x64, + 0x65,0x20,0x43,0x61,0x6c,0x63,0x75,0x6c,0x61,0x74,0x69,0x6f,0x6e,0x20,0x28,0x57, + 0x6f,0x72,0x6c,0x64,0x20,0x59,0x20,0x69,0x6e,0x20,0x5b,0x30,0x2e,0x2e,0x31,0x30, + 0x38,0x2e,0x32,0x35,0x20,0x6b,0x6d,0x5d,0x29,0xa,0x20,0x20,0x20,0x20,0x66,0x6c, + 0x6f,0x61,0x74,0x20,0x6e,0x6f,0x72,0x6d,0x59,0x20,0x3d,0x20,0x63,0x6c,0x61,0x6d, + 0x70,0x28,0x76,0x57,0x6f,0x72,0x6c,0x64,0x50,0x6f,0x73,0x2e,0x79,0x20,0x2f,0x20, + 0x31,0x30,0x38,0x2e,0x32,0x35,0x2c,0x20,0x30,0x2e,0x30,0x2c,0x20,0x31,0x2e,0x30, + 0x29,0x3b,0xa,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x20,0x64,0x69,0x73, + 0x74,0x46,0x72,0x6f,0x6d,0x43,0x65,0x6e,0x74,0x65,0x72,0x20,0x3d,0x20,0x61,0x62, + 0x73,0x28,0x6e,0x6f,0x72,0x6d,0x59,0x20,0x2d,0x20,0x30,0x2e,0x35,0x29,0x20,0x2a, + 0x20,0x32,0x2e,0x30,0x3b,0x20,0x2f,0x2f,0x20,0x30,0x2e,0x30,0x20,0x61,0x74,0x20, + 0x6d,0x69,0x64,0x64,0x6c,0x65,0x2c,0x20,0x31,0x2e,0x30,0x20,0x61,0x74,0x20,0x4e, + 0x6f,0x72,0x74,0x68,0x2f,0x53,0x6f,0x75,0x74,0x68,0x20,0x70,0x6f,0x6c,0x65,0x73, + 0xa,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x20,0x70,0x6f,0x6c,0x65,0x46, + 0x61,0x63,0x74,0x6f,0x72,0x20,0x3d,0x20,0x73,0x6d,0x6f,0x6f,0x74,0x68,0x73,0x74, + 0x65,0x70,0x28,0x30,0x2e,0x34,0x30,0x2c,0x20,0x30,0x2e,0x38,0x35,0x2c,0x20,0x64, + 0x69,0x73,0x74,0x46,0x72,0x6f,0x6d,0x43,0x65,0x6e,0x74,0x65,0x72,0x29,0x3b,0xa, + 0xa,0x20,0x20,0x20,0x20,0x2f,0x2f,0x20,0x53,0x6e,0x6f,0x77,0x6c,0x69,0x6e,0x65, + 0x20,0x69,0x6e,0x20,0x6b,0x69,0x6c,0x6f,0x6d,0x65,0x74,0x65,0x72,0x73,0x3a,0x20, + 0x32,0x2e,0x35,0x20,0x28,0x32,0x35,0x30,0x30,0x6d,0x29,0x20,0x69,0x6e,0x20,0x74, + 0x65,0x6d,0x70,0x65,0x72,0x61,0x74,0x65,0x20,0x7a,0x6f,0x6e,0x65,0x2c,0x20,0x64, + 0x72,0x6f,0x70,0x70,0x69,0x6e,0x67,0x20,0x74,0x6f,0x20,0x30,0x2e,0x30,0x20,0x28, + 0x73,0x65,0x61,0x20,0x6c,0x65,0x76,0x65,0x6c,0x29,0x20,0x61,0x74,0x20,0x70,0x6f, + 0x6c,0x65,0x73,0x21,0xa,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x20,0x73, + 0x6e,0x6f,0x77,0x4c,0x69,0x6e,0x65,0x20,0x3d,0x20,0x6d,0x69,0x78,0x28,0x32,0x2e, + 0x35,0x2c,0x20,0x30,0x2e,0x30,0x2c,0x20,0x70,0x6f,0x6c,0x65,0x46,0x61,0x63,0x74, + 0x6f,0x72,0x29,0x3b,0xa,0xa,0x20,0x20,0x20,0x20,0x2f,0x2f,0x20,0x33,0x2e,0x20, + 0x57,0x61,0x74,0x65,0x72,0x20,0x76,0x73,0x20,0x4c,0x61,0x6e,0x64,0x20,0x64,0x65, + 0x74,0x65,0x63,0x74,0x69,0x6f,0x6e,0x20,0x28,0x7a,0x5f,0x70,0x6f,0x73,0x20,0x3d, + 0x20,0x68,0x65,0x69,0x67,0x68,0x74,0x20,0x69,0x6e,0x20,0x6b,0x69,0x6c,0x6f,0x6d, + 0x65,0x74,0x65,0x72,0x73,0x2c,0x20,0x30,0x2e,0x30,0x20,0x3d,0x20,0x73,0x65,0x61, + 0x20,0x6c,0x65,0x76,0x65,0x6c,0x29,0xa,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61, + 0x74,0x20,0x6f,0x63,0x65,0x61,0x6e,0x57,0x61,0x74,0x65,0x72,0x46,0x61,0x63,0x20, + 0x3d,0x20,0x31,0x2e,0x30,0x20,0x2d,0x20,0x73,0x6d,0x6f,0x6f,0x74,0x68,0x73,0x74, + 0x65,0x70,0x28,0x30,0x2e,0x30,0x30,0x35,0x2c,0x20,0x30,0x2e,0x30,0x32,0x35,0x2c, + 0x20,0x7a,0x5f,0x70,0x6f,0x73,0x29,0x3b,0xa,0xa,0x20,0x20,0x20,0x20,0x2f,0x2f, + 0x20,0x52,0x69,0x76,0x65,0x72,0x20,0x77,0x61,0x74,0x65,0x72,0x20,0x64,0x65,0x74, + 0x65,0x63,0x74,0x69,0x6f,0x6e,0xa,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74, + 0x20,0x72,0x69,0x76,0x65,0x72,0x44,0x69,0x73,0x74,0x20,0x3d,0x20,0x67,0x65,0x74, + 0x52,0x69,0x76,0x65,0x72,0x44,0x69,0x73,0x74,0x61,0x6e,0x63,0x65,0x28,0x66,0x72, + 0x61,0x67,0x56,0x65,0x72,0x74,0x2e,0x78,0x79,0x2c,0x20,0x72,0x69,0x76,0x65,0x72, + 0x4d,0x61,0x73,0x6b,0x29,0x3b,0xa,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74, + 0x20,0x72,0x69,0x76,0x65,0x72,0x57,0x61,0x74,0x65,0x72,0x52,0x61,0x64,0x69,0x75, + 0x73,0x20,0x3d,0x20,0x30,0x2e,0x30,0x32,0x34,0x3b,0xa,0x20,0x20,0x20,0x20,0x66, + 0x6c,0x6f,0x61,0x74,0x20,0x72,0x69,0x76,0x65,0x72,0x42,0x61,0x6e,0x6b,0x52,0x61, + 0x64,0x69,0x75,0x73,0x20,0x20,0x3d,0x20,0x30,0x2e,0x30,0x35,0x32,0x3b,0xa,0x20, + 0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x20,0x72,0x69,0x76,0x65,0x72,0x57,0x61, + 0x74,0x65,0x72,0x46,0x61,0x63,0x20,0x3d,0x20,0x31,0x2e,0x30,0x20,0x2d,0x20,0x73, + 0x6d,0x6f,0x6f,0x74,0x68,0x73,0x74,0x65,0x70,0x28,0x72,0x69,0x76,0x65,0x72,0x57, + 0x61,0x74,0x65,0x72,0x52,0x61,0x64,0x69,0x75,0x73,0x20,0x2a,0x20,0x30,0x2e,0x37, + 0x2c,0x20,0x72,0x69,0x76,0x65,0x72,0x57,0x61,0x74,0x65,0x72,0x52,0x61,0x64,0x69, + 0x75,0x73,0x2c,0x20,0x72,0x69,0x76,0x65,0x72,0x44,0x69,0x73,0x74,0x29,0x3b,0xa, + 0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x20,0x72,0x69,0x76,0x65,0x72,0x42, + 0x61,0x6e,0x6b,0x46,0x61,0x63,0x20,0x20,0x3d,0x20,0x28,0x31,0x2e,0x30,0x20,0x2d, + 0x20,0x73,0x6d,0x6f,0x6f,0x74,0x68,0x73,0x74,0x65,0x70,0x28,0x72,0x69,0x76,0x65, + 0x72,0x57,0x61,0x74,0x65,0x72,0x52,0x61,0x64,0x69,0x75,0x73,0x2c,0x20,0x72,0x69, + 0x76,0x65,0x72,0x42,0x61,0x6e,0x6b,0x52,0x61,0x64,0x69,0x75,0x73,0x2c,0x20,0x72, + 0x69,0x76,0x65,0x72,0x44,0x69,0x73,0x74,0x29,0x29,0x20,0x2a,0x20,0x28,0x31,0x2e, + 0x30,0x20,0x2d,0x20,0x72,0x69,0x76,0x65,0x72,0x57,0x61,0x74,0x65,0x72,0x46,0x61, + 0x63,0x29,0x3b,0xa,0xa,0x20,0x20,0x20,0x20,0x2f,0x2f,0x20,0x43,0x6f,0x6d,0x62, + 0x69,0x6e,0x65,0x64,0x20,0x77,0x61,0x74,0x65,0x72,0x20,0x66,0x61,0x63,0x74,0x6f, + 0x72,0x20,0x28,0x6f,0x63,0x65,0x61,0x6e,0x20,0x6f,0x72,0x20,0x72,0x69,0x76,0x65, + 0x72,0x29,0xa,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x20,0x77,0x61,0x74, + 0x65,0x72,0x46,0x61,0x63,0x20,0x3d,0x20,0x6d,0x61,0x78,0x28,0x6f,0x63,0x65,0x61, + 0x6e,0x57,0x61,0x74,0x65,0x72,0x46,0x61,0x63,0x2c,0x20,0x72,0x69,0x76,0x65,0x72, + 0x57,0x61,0x74,0x65,0x72,0x46,0x61,0x63,0x29,0x3b,0xa,0xa,0x20,0x20,0x20,0x20, + 0x2f,0x2f,0x20,0x34,0x2e,0x20,0x44,0x79,0x6e,0x61,0x6d,0x69,0x63,0x20,0x61,0x6e, + 0x69,0x6d,0x61,0x74,0x65,0x64,0x20,0x6f,0x63,0x65,0x61,0x6e,0x20,0x77,0x61,0x74, + 0x65,0x72,0xa,0x20,0x20,0x20,0x20,0x76,0x65,0x63,0x32,0x20,0x75,0x76,0x57,0x31, + 0x20,0x3d,0x20,0x76,0x57,0x6f,0x72,0x6c,0x64,0x50,0x6f,0x73,0x2e,0x78,0x79,0x20, + 0x2a,0x20,0x30,0x2e,0x33,0x30,0x20,0x2b,0x20,0x76,0x65,0x63,0x32,0x28,0x74,0x69, + 0x6d,0x65,0x20,0x2a,0x20,0x30,0x2e,0x30,0x33,0x35,0x2c,0x20,0x74,0x69,0x6d,0x65, + 0x20,0x2a,0x20,0x30,0x2e,0x30,0x32,0x30,0x29,0x3b,0xa,0x20,0x20,0x20,0x20,0x76, + 0x65,0x63,0x32,0x20,0x75,0x76,0x57,0x32,0x20,0x3d,0x20,0x76,0x57,0x6f,0x72,0x6c, + 0x64,0x50,0x6f,0x73,0x2e,0x78,0x79,0x20,0x2a,0x20,0x30,0x2e,0x33,0x38,0x20,0x2b, + 0x20,0x76,0x65,0x63,0x32,0x28,0x2d,0x74,0x69,0x6d,0x65,0x20,0x2a,0x20,0x30,0x2e, + 0x30,0x32,0x35,0x2c,0x20,0x74,0x69,0x6d,0x65,0x20,0x2a,0x20,0x30,0x2e,0x30,0x33, + 0x30,0x29,0x3b,0xa,0xa,0x20,0x20,0x20,0x20,0x76,0x65,0x63,0x32,0x20,0x77,0x61, + 0x76,0x65,0x44,0x69,0x73,0x74,0x6f,0x72,0x74,0x31,0x20,0x3d,0x20,0x76,0x65,0x63, + 0x32,0x28,0x73,0x69,0x6e,0x28,0x75,0x76,0x57,0x31,0x2e,0x79,0x20,0x2a,0x20,0x31, + 0x34,0x2e,0x30,0x20,0x2b,0x20,0x74,0x69,0x6d,0x65,0x20,0x2a,0x20,0x32,0x2e,0x32, + 0x29,0x2c,0x20,0x63,0x6f,0x73,0x28,0x75,0x76,0x57,0x31,0x2e,0x78,0x20,0x2a,0x20, + 0x31,0x34,0x2e,0x30,0x20,0x2b,0x20,0x74,0x69,0x6d,0x65,0x20,0x2a,0x20,0x31,0x2e, + 0x38,0x29,0x29,0x20,0x2a,0x20,0x30,0x2e,0x30,0x32,0x35,0x3b,0xa,0x20,0x20,0x20, + 0x20,0x76,0x65,0x63,0x32,0x20,0x77,0x61,0x76,0x65,0x44,0x69,0x73,0x74,0x6f,0x72, + 0x74,0x32,0x20,0x3d,0x20,0x76,0x65,0x63,0x32,0x28,0x63,0x6f,0x73,0x28,0x75,0x76, + 0x57,0x32,0x2e,0x79,0x20,0x2a,0x20,0x31,0x38,0x2e,0x30,0x20,0x2d,0x20,0x74,0x69, + 0x6d,0x65,0x20,0x2a,0x20,0x31,0x2e,0x35,0x29,0x2c,0x20,0x73,0x69,0x6e,0x28,0x75, + 0x76,0x57,0x32,0x2e,0x78,0x20,0x2a,0x20,0x31,0x38,0x2e,0x30,0x20,0x2d,0x20,0x74, + 0x69,0x6d,0x65,0x20,0x2a,0x20,0x32,0x2e,0x35,0x29,0x29,0x20,0x2a,0x20,0x30,0x2e, + 0x30,0x32,0x30,0x3b,0xa,0xa,0x20,0x20,0x20,0x20,0x76,0x65,0x63,0x34,0x20,0x77, + 0x53,0x61,0x6d,0x70,0x6c,0x65,0x31,0x20,0x3d,0x20,0x74,0x65,0x78,0x74,0x75,0x72, + 0x65,0x28,0x74,0x65,0x78,0x57,0x61,0x74,0x65,0x72,0x2c,0x20,0x75,0x76,0x57,0x31, + 0x20,0x2b,0x20,0x77,0x61,0x76,0x65,0x44,0x69,0x73,0x74,0x6f,0x72,0x74,0x31,0x29, + 0x3b,0xa,0x20,0x20,0x20,0x20,0x76,0x65,0x63,0x34,0x20,0x77,0x53,0x61,0x6d,0x70, + 0x6c,0x65,0x32,0x20,0x3d,0x20,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x28,0x74,0x65, + 0x78,0x57,0x61,0x74,0x65,0x72,0x2c,0x20,0x75,0x76,0x57,0x32,0x20,0x2b,0x20,0x77, + 0x61,0x76,0x65,0x44,0x69,0x73,0x74,0x6f,0x72,0x74,0x32,0x29,0x3b,0xa,0x20,0x20, + 0x20,0x20,0x76,0x65,0x63,0x34,0x20,0x77,0x61,0x74,0x65,0x72,0x43,0x6f,0x6c,0x6f, + 0x72,0x20,0x3d,0x20,0x6d,0x69,0x78,0x28,0x77,0x53,0x61,0x6d,0x70,0x6c,0x65,0x31, + 0x2c,0x20,0x77,0x53,0x61,0x6d,0x70,0x6c,0x65,0x32,0x2c,0x20,0x30,0x2e,0x35,0x29, + 0x3b,0xa,0x20,0x20,0x20,0x20,0x77,0x61,0x74,0x65,0x72,0x43,0x6f,0x6c,0x6f,0x72, + 0x2e,0x72,0x67,0x62,0x20,0x3d,0x20,0x6d,0x69,0x78,0x28,0x77,0x61,0x74,0x65,0x72, + 0x43,0x6f,0x6c,0x6f,0x72,0x2e,0x72,0x67,0x62,0x2c,0x20,0x76,0x65,0x63,0x33,0x28, + 0x30,0x2e,0x30,0x35,0x2c,0x20,0x30,0x2e,0x33,0x30,0x2c,0x20,0x30,0x2e,0x35,0x30, + 0x29,0x2c,0x20,0x30,0x2e,0x33,0x30,0x29,0x3b,0xa,0xa,0x20,0x20,0x20,0x20,0x76, + 0x65,0x63,0x33,0x20,0x77,0x61,0x74,0x65,0x72,0x4e,0x6f,0x72,0x6d,0x20,0x3d,0x20, + 0x6e,0x6f,0x72,0x6d,0x61,0x6c,0x69,0x7a,0x65,0x28,0x76,0x65,0x63,0x33,0x28,0xa, + 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x28,0x77,0x61,0x76,0x65,0x44,0x69,0x73, + 0x74,0x6f,0x72,0x74,0x31,0x2e,0x78,0x20,0x2b,0x20,0x77,0x61,0x76,0x65,0x44,0x69, + 0x73,0x74,0x6f,0x72,0x74,0x32,0x2e,0x78,0x29,0x20,0x2a,0x20,0x32,0x2e,0x35,0x2c, + 0xa,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x28,0x77,0x61,0x76,0x65,0x44,0x69, + 0x73,0x74,0x6f,0x72,0x74,0x31,0x2e,0x79,0x20,0x2b,0x20,0x77,0x61,0x76,0x65,0x44, + 0x69,0x73,0x74,0x6f,0x72,0x74,0x32,0x2e,0x79,0x29,0x20,0x2a,0x20,0x32,0x2e,0x35, + 0x2c,0xa,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x31,0x2e,0x30,0xa,0x20,0x20, + 0x20,0x20,0x29,0x29,0x3b,0xa,0xa,0x20,0x20,0x20,0x20,0x2f,0x2f,0x20,0x35,0x2e, + 0x20,0x54,0x72,0x69,0x70,0x6c,0x61,0x6e,0x61,0x72,0x20,0x6d,0x61,0x70,0x70,0x69, + 0x6e,0x67,0x20,0x63,0x6f,0x6f,0x72,0x64,0x69,0x6e,0x61,0x74,0x65,0x73,0x20,0x66, + 0x6f,0x72,0x20,0x4c,0x61,0x6e,0x64,0x2c,0x20,0x43,0x6c,0x69,0x66,0x66,0x73,0x20, + 0x26,0x20,0x53,0x6e,0x6f,0x77,0xa,0x20,0x20,0x20,0x20,0x76,0x65,0x63,0x33,0x20, + 0x62,0x6c,0x65,0x6e,0x64,0x57,0x65,0x69,0x67,0x68,0x74,0x73,0x20,0x3d,0x20,0x70, + 0x6f,0x77,0x28,0x61,0x62,0x73,0x28,0x6e,0x6f,0x72,0x6d,0x29,0x2c,0x20,0x76,0x65, + 0x63,0x33,0x28,0x34,0x2e,0x30,0x29,0x29,0x3b,0xa,0x20,0x20,0x20,0x20,0x62,0x6c, + 0x65,0x6e,0x64,0x57,0x65,0x69,0x67,0x68,0x74,0x73,0x20,0x2f,0x3d,0x20,0x28,0x62, + 0x6c,0x65,0x6e,0x64,0x57,0x65,0x69,0x67,0x68,0x74,0x73,0x2e,0x78,0x20,0x2b,0x20, + 0x62,0x6c,0x65,0x6e,0x64,0x57,0x65,0x69,0x67,0x68,0x74,0x73,0x2e,0x79,0x20,0x2b, + 0x20,0x62,0x6c,0x65,0x6e,0x64,0x57,0x65,0x69,0x67,0x68,0x74,0x73,0x2e,0x7a,0x29, + 0x3b,0xa,0xa,0x20,0x20,0x20,0x20,0x76,0x65,0x63,0x32,0x20,0x75,0x76,0x58,0x20, + 0x3d,0x20,0x76,0x57,0x6f,0x72,0x6c,0x64,0x50,0x6f,0x73,0x2e,0x79,0x7a,0x20,0x2a, + 0x20,0x30,0x2e,0x33,0x35,0x3b,0xa,0x20,0x20,0x20,0x20,0x76,0x65,0x63,0x32,0x20, + 0x75,0x76,0x59,0x20,0x3d,0x20,0x76,0x57,0x6f,0x72,0x6c,0x64,0x50,0x6f,0x73,0x2e, + 0x78,0x7a,0x20,0x2a,0x20,0x30,0x2e,0x33,0x35,0x3b,0xa,0x20,0x20,0x20,0x20,0x76, + 0x65,0x63,0x32,0x20,0x75,0x76,0x5a,0x20,0x3d,0x20,0x76,0x57,0x6f,0x72,0x6c,0x64, + 0x50,0x6f,0x73,0x2e,0x78,0x79,0x20,0x2a,0x20,0x30,0x2e,0x33,0x35,0x3b,0xa,0xa, + 0x20,0x20,0x20,0x20,0x2f,0x2f,0x20,0x52,0x6f,0x63,0x6b,0x20,0x74,0x65,0x78,0x74, + 0x75,0x72,0x65,0x73,0x3a,0xa,0x20,0x20,0x20,0x20,0x2f,0x2f,0x20,0x47,0x72,0x61, + 0x79,0x20,0x72,0x6f,0x63,0x6b,0x20,0x28,0x54,0x65,0x78,0x74,0x75,0x72,0x65,0x6c, + 0x61,0x62,0x73,0x5f,0x53,0x74,0x6f,0x6e,0x65,0x5f,0x31,0x33,0x36,0x58,0x4c,0x5f, + 0x73,0x65,0x61,0x6d,0x6c,0x65,0x73,0x73,0x29,0xa,0x20,0x20,0x20,0x20,0x76,0x65, + 0x63,0x34,0x20,0x72,0x6f,0x63,0x6b,0x47,0x72,0x61,0x79,0x20,0x3d,0x20,0x74,0x65, + 0x78,0x74,0x75,0x72,0x65,0x28,0x74,0x65,0x78,0x52,0x6f,0x63,0x6b,0x2c,0x20,0x75, + 0x76,0x58,0x29,0x20,0x2a,0x20,0x62,0x6c,0x65,0x6e,0x64,0x57,0x65,0x69,0x67,0x68, + 0x74,0x73,0x2e,0x78,0xa,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20, + 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x2b,0x20,0x74,0x65,0x78,0x74,0x75,0x72,0x65, + 0x28,0x74,0x65,0x78,0x52,0x6f,0x63,0x6b,0x2c,0x20,0x75,0x76,0x59,0x29,0x20,0x2a, + 0x20,0x62,0x6c,0x65,0x6e,0x64,0x57,0x65,0x69,0x67,0x68,0x74,0x73,0x2e,0x79,0xa, + 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20, + 0x20,0x20,0x2b,0x20,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x28,0x74,0x65,0x78,0x52, + 0x6f,0x63,0x6b,0x2c,0x20,0x75,0x76,0x5a,0x29,0x20,0x2a,0x20,0x62,0x6c,0x65,0x6e, + 0x64,0x57,0x65,0x69,0x67,0x68,0x74,0x73,0x2e,0x7a,0x3b,0xa,0xa,0x20,0x20,0x20, + 0x20,0x2f,0x2f,0x20,0x52,0x65,0x64,0x20,0x72,0x6f,0x63,0x6b,0x20,0x63,0x61,0x6e, + 0x79,0x6f,0x6e,0x20,0x63,0x6c,0x69,0x66,0x66,0x20,0x28,0x54,0x65,0x78,0x74,0x75, + 0x72,0x65,0x6c,0x61,0x62,0x73,0x5f,0x52,0x65,0x64,0x52,0x6f,0x63,0x6b,0x5f,0x73, + 0x65,0x61,0x6d,0x6c,0x65,0x73,0x73,0x29,0xa,0x20,0x20,0x20,0x20,0x76,0x65,0x63, + 0x34,0x20,0x72,0x6f,0x63,0x6b,0x52,0x65,0x64,0x20,0x3d,0x20,0x74,0x65,0x78,0x74, + 0x75,0x72,0x65,0x28,0x74,0x65,0x78,0x52,0x65,0x64,0x52,0x6f,0x63,0x6b,0x2c,0x20, + 0x75,0x76,0x58,0x29,0x20,0x2a,0x20,0x62,0x6c,0x65,0x6e,0x64,0x57,0x65,0x69,0x67, + 0x68,0x74,0x73,0x2e,0x78,0xa,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20, + 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x2b,0x20,0x74,0x65,0x78,0x74,0x75,0x72,0x65, + 0x28,0x74,0x65,0x78,0x52,0x65,0x64,0x52,0x6f,0x63,0x6b,0x2c,0x20,0x75,0x76,0x59, + 0x29,0x20,0x2a,0x20,0x62,0x6c,0x65,0x6e,0x64,0x57,0x65,0x69,0x67,0x68,0x74,0x73, + 0x2e,0x79,0xa,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20, + 0x20,0x20,0x20,0x20,0x2b,0x20,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x28,0x74,0x65, + 0x78,0x52,0x65,0x64,0x52,0x6f,0x63,0x6b,0x2c,0x20,0x75,0x76,0x5a,0x29,0x20,0x2a, + 0x20,0x62,0x6c,0x65,0x6e,0x64,0x57,0x65,0x69,0x67,0x68,0x74,0x73,0x2e,0x7a,0x3b, + 0xa,0xa,0x20,0x20,0x20,0x20,0x2f,0x2f,0x20,0x43,0x6c,0x69,0x66,0x66,0x20,0x72, + 0x6f,0x63,0x6b,0x3a,0x20,0x72,0x65,0x64,0x20,0x73,0x61,0x6e,0x64,0x73,0x74,0x6f, + 0x6e,0x65,0x20,0x69,0x6e,0x20,0x50,0x72,0x61,0x69,0x72,0x69,0x65,0x20,0x63,0x61, + 0x6e,0x79,0x6f,0x6e,0x2c,0x20,0x67,0x72,0x61,0x79,0x20,0x61,0x6c,0x70,0x69,0x6e, + 0x65,0x20,0x65,0x6c,0x73,0x65,0x77,0x68,0x65,0x72,0x65,0xa,0x20,0x20,0x20,0x20, + 0x76,0x65,0x63,0x34,0x20,0x63,0x6c,0x69,0x66,0x66,0x52,0x6f,0x63,0x6b,0x43,0x6f, + 0x6c,0x6f,0x72,0x20,0x3d,0x20,0x6d,0x69,0x78,0x28,0x72,0x6f,0x63,0x6b,0x47,0x72, + 0x61,0x79,0x2c,0x20,0x72,0x6f,0x63,0x6b,0x52,0x65,0x64,0x2c,0x20,0x70,0x72,0x61, + 0x69,0x72,0x69,0x65,0x57,0x65,0x69,0x67,0x68,0x74,0x29,0x3b,0xa,0xa,0x20,0x20, + 0x20,0x20,0x2f,0x2f,0x20,0x53,0x6e,0x6f,0x77,0x20,0x2f,0x20,0x49,0x63,0x65,0x20, + 0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x20,0x28,0x54,0x65,0x78,0x74,0x75,0x72,0x65, + 0x6c,0x61,0x62,0x73,0x5f,0x57,0x61,0x74,0x65,0x72,0x5f,0x31,0x35,0x35,0x58,0x4c, + 0x5f,0x73,0x65,0x61,0x6d,0x6c,0x65,0x73,0x73,0x29,0xa,0x20,0x20,0x20,0x20,0x76, + 0x65,0x63,0x34,0x20,0x73,0x6e,0x6f,0x77,0x43,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20, + 0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x28,0x74,0x65,0x78,0x53,0x6e,0x6f,0x77,0x2c, + 0x20,0x75,0x76,0x58,0x29,0x20,0x2a,0x20,0x62,0x6c,0x65,0x6e,0x64,0x57,0x65,0x69, + 0x67,0x68,0x74,0x73,0x2e,0x78,0xa,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20, + 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x2b,0x20,0x74,0x65,0x78,0x74, + 0x75,0x72,0x65,0x28,0x74,0x65,0x78,0x53,0x6e,0x6f,0x77,0x2c,0x20,0x75,0x76,0x59, + 0x29,0x20,0x2a,0x20,0x62,0x6c,0x65,0x6e,0x64,0x57,0x65,0x69,0x67,0x68,0x74,0x73, + 0x2e,0x79,0xa,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20, + 0x20,0x20,0x20,0x20,0x20,0x20,0x2b,0x20,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x28, + 0x74,0x65,0x78,0x53,0x6e,0x6f,0x77,0x2c,0x20,0x75,0x76,0x5a,0x29,0x20,0x2a,0x20, + 0x62,0x6c,0x65,0x6e,0x64,0x57,0x65,0x69,0x67,0x68,0x74,0x73,0x2e,0x7a,0x3b,0xa, + 0xa,0x20,0x20,0x20,0x20,0x2f,0x2f,0x20,0x47,0x72,0x6f,0x75,0x6e,0x64,0x20,0x74, + 0x65,0x78,0x74,0x75,0x72,0x65,0x73,0x3a,0xa,0x20,0x20,0x20,0x20,0x76,0x65,0x63, + 0x34,0x20,0x67,0x72,0x61,0x73,0x73,0x43,0x6f,0x6c,0x6f,0x72,0x20,0x20,0x20,0x3d, + 0x20,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x28,0x74,0x65,0x78,0x47,0x72,0x61,0x73, + 0x73,0x2c,0x20,0x75,0x76,0x5a,0x29,0x3b,0xa,0x20,0x20,0x20,0x20,0x76,0x65,0x63, + 0x34,0x20,0x66,0x6f,0x72,0x65,0x73,0x74,0x43,0x6f,0x6c,0x6f,0x72,0x20,0x20,0x3d, + 0x20,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x28,0x74,0x65,0x78,0x46,0x6f,0x72,0x65, + 0x73,0x74,0x2c,0x20,0x75,0x76,0x5a,0x29,0x3b,0xa,0x20,0x20,0x20,0x20,0x76,0x65, + 0x63,0x34,0x20,0x62,0x65,0x61,0x63,0x68,0x43,0x6f,0x6c,0x6f,0x72,0x20,0x20,0x20, + 0x3d,0x20,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x28,0x74,0x65,0x78,0x53,0x61,0x6e, + 0x64,0x2c,0x20,0x75,0x76,0x5a,0x29,0x3b,0xa,0x20,0x20,0x20,0x20,0x76,0x65,0x63, + 0x34,0x20,0x73,0x61,0x68,0x61,0x72,0x61,0x43,0x6f,0x6c,0x6f,0x72,0x20,0x20,0x3d, + 0x20,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x28,0x74,0x65,0x78,0x53,0x61,0x68,0x61, + 0x72,0x61,0x2c,0x20,0x75,0x76,0x5a,0x29,0x3b,0xa,0x20,0x20,0x20,0x20,0x76,0x65, + 0x63,0x34,0x20,0x74,0x75,0x6e,0x64,0x72,0x61,0x43,0x6f,0x6c,0x6f,0x72,0x20,0x20, + 0x3d,0x20,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x28,0x74,0x65,0x78,0x54,0x75,0x6e, + 0x64,0x72,0x61,0x2c,0x20,0x75,0x76,0x5a,0x29,0x3b,0xa,0x20,0x20,0x20,0x20,0x76, + 0x65,0x63,0x34,0x20,0x72,0x65,0x64,0x53,0x61,0x6e,0x64,0x43,0x6f,0x6c,0x6f,0x72, + 0x20,0x3d,0x20,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x28,0x74,0x65,0x78,0x52,0x65, + 0x64,0x53,0x61,0x6e,0x64,0x2c,0x20,0x75,0x76,0x5a,0x29,0x3b,0xa,0xa,0x20,0x20, + 0x20,0x20,0x2f,0x2f,0x20,0x43,0x6f,0x6d,0x70,0x6f,0x73,0x69,0x74,0x65,0x20,0x67, + 0x72,0x6f,0x75,0x6e,0x64,0x20,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x20,0x61,0x63, + 0x72,0x6f,0x73,0x73,0x20,0x61,0x6c,0x6c,0x20,0x62,0x69,0x6f,0x6d,0x65,0x73,0x3a, + 0xa,0x20,0x20,0x20,0x20,0x76,0x65,0x63,0x34,0x20,0x62,0x69,0x6f,0x6d,0x65,0x47, + 0x72,0x6f,0x75,0x6e,0x64,0x20,0x3d,0x20,0x67,0x72,0x61,0x73,0x73,0x43,0x6f,0x6c, + 0x6f,0x72,0x20,0x20,0x20,0x2a,0x20,0x67,0x72,0x61,0x73,0x73,0x6c,0x61,0x6e,0x64, + 0x57,0x65,0x69,0x67,0x68,0x74,0xa,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20, + 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x2b,0x20,0x66,0x6f, + 0x72,0x65,0x73,0x74,0x43,0x6f,0x6c,0x6f,0x72,0x20,0x20,0x2a,0x20,0x66,0x6f,0x72, + 0x65,0x73,0x74,0x57,0x65,0x69,0x67,0x68,0x74,0xa,0x20,0x20,0x20,0x20,0x20,0x20, + 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x2b, + 0x20,0x73,0x61,0x68,0x61,0x72,0x61,0x43,0x6f,0x6c,0x6f,0x72,0x20,0x20,0x2a,0x20, + 0x73,0x61,0x68,0x61,0x72,0x61,0x57,0x65,0x69,0x67,0x68,0x74,0xa,0x20,0x20,0x20, + 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20, + 0x20,0x20,0x2b,0x20,0x74,0x75,0x6e,0x64,0x72,0x61,0x43,0x6f,0x6c,0x6f,0x72,0x20, + 0x20,0x2a,0x20,0x74,0x75,0x6e,0x64,0x72,0x61,0x57,0x65,0x69,0x67,0x68,0x74,0xa, + 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20, + 0x20,0x20,0x20,0x20,0x20,0x2b,0x20,0x72,0x65,0x64,0x53,0x61,0x6e,0x64,0x43,0x6f, + 0x6c,0x6f,0x72,0x20,0x2a,0x20,0x70,0x72,0x61,0x69,0x72,0x69,0x65,0x57,0x65,0x69, + 0x67,0x68,0x74,0x3b,0xa,0xa,0x20,0x20,0x20,0x20,0x2f,0x2f,0x20,0x53,0x68,0x6f, + 0x72,0x65,0x6c,0x69,0x6e,0x65,0x20,0x62,0x65,0x61,0x63,0x68,0x3a,0x20,0x77,0x68, + 0x65,0x72,0x65,0x20,0x67,0x65,0x6e,0x74,0x6c,0x65,0x20,0x73,0x6c,0x6f,0x70,0x65, + 0x73,0x20,0x6d,0x65,0x65,0x74,0x20,0x77,0x61,0x74,0x65,0x72,0x20,0x28,0x73,0x65, + 0x61,0x20,0x6c,0x65,0x76,0x65,0x6c,0x20,0x75,0x70,0x20,0x74,0x6f,0x20,0x7e,0x31, + 0x31,0x30,0x6d,0x2c,0x20,0x6f,0x72,0x20,0x61,0x6c,0x6f,0x6e,0x67,0x20,0x72,0x69, + 0x76,0x65,0x72,0x73,0x29,0xa,0x20,0x20,0x20,0x20,0x2f,0x2f,0x20,0x53,0x74,0x65, + 0x65,0x70,0x20,0x73,0x6c,0x6f,0x70,0x65,0x73,0x20,0x28,0x6e,0x6f,0x72,0x6d,0x2e, + 0x7a,0x20,0x3c,0x20,0x30,0x2e,0x35,0x35,0x29,0x20,0x72,0x65,0x6d,0x61,0x69,0x6e, + 0x20,0x72,0x6f,0x63,0x6b,0x79,0x20,0x63,0x6c,0x69,0x66,0x66,0x73,0x3b,0x20,0x67, + 0x65,0x6e,0x74,0x6c,0x65,0x20,0x73,0x6c,0x6f,0x70,0x65,0x73,0x20,0x28,0x6e,0x6f, + 0x72,0x6d,0x2e,0x7a,0x20,0x3e,0x20,0x30,0x2e,0x38,0x30,0x29,0x20,0x67,0x65,0x74, + 0x20,0x61,0x20,0x73,0x61,0x6e,0x64,0x79,0x20,0x62,0x65,0x61,0x63,0x68,0xa,0x20, + 0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x20,0x67,0x65,0x6e,0x74,0x6c,0x65,0x53, + 0x6c,0x6f,0x70,0x65,0x20,0x3d,0x20,0x73,0x6d,0x6f,0x6f,0x74,0x68,0x73,0x74,0x65, + 0x70,0x28,0x30,0x2e,0x35,0x35,0x2c,0x20,0x30,0x2e,0x38,0x35,0x2c,0x20,0x6e,0x6f, + 0x72,0x6d,0x2e,0x7a,0x29,0x3b,0xa,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74, + 0x20,0x63,0x6f,0x61,0x73,0x74,0x42,0x65,0x61,0x63,0x68,0x20,0x3d,0x20,0x28,0x31, + 0x2e,0x30,0x20,0x2d,0x20,0x73,0x6d,0x6f,0x6f,0x74,0x68,0x73,0x74,0x65,0x70,0x28, + 0x30,0x2e,0x30,0x31,0x35,0x2c,0x20,0x30,0x2e,0x31,0x31,0x30,0x2c,0x20,0x7a,0x5f, + 0x70,0x6f,0x73,0x29,0x29,0x20,0x2a,0x20,0x73,0x6d,0x6f,0x6f,0x74,0x68,0x73,0x74, + 0x65,0x70,0x28,0x30,0x2e,0x30,0x30,0x33,0x2c,0x20,0x30,0x2e,0x30,0x31,0x35,0x2c, + 0x20,0x7a,0x5f,0x70,0x6f,0x73,0x29,0x20,0x2a,0x20,0x67,0x65,0x6e,0x74,0x6c,0x65, + 0x53,0x6c,0x6f,0x70,0x65,0x3b,0xa,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74, + 0x20,0x72,0x69,0x76,0x65,0x72,0x42,0x65,0x61,0x63,0x68,0x20,0x3d,0x20,0x72,0x69, + 0x76,0x65,0x72,0x42,0x61,0x6e,0x6b,0x46,0x61,0x63,0x3b,0xa,0x20,0x20,0x20,0x20, + 0x66,0x6c,0x6f,0x61,0x74,0x20,0x62,0x65,0x61,0x63,0x68,0x46,0x61,0x63,0x20,0x3d, + 0x20,0x6d,0x61,0x78,0x28,0x63,0x6f,0x61,0x73,0x74,0x42,0x65,0x61,0x63,0x68,0x2c, + 0x20,0x72,0x69,0x76,0x65,0x72,0x42,0x65,0x61,0x63,0x68,0x29,0x3b,0xa,0xa,0x20, + 0x20,0x20,0x20,0x2f,0x2f,0x20,0x53,0x75,0x70,0x70,0x72,0x65,0x73,0x73,0x20,0x62, + 0x65,0x61,0x63,0x68,0x20,0x6f,0x6e,0x6c,0x79,0x20,0x61,0x74,0x20,0x74,0x68,0x65, + 0x20,0x65,0x78,0x74,0x72,0x65,0x6d,0x65,0x20,0x70,0x6f,0x6c,0x61,0x72,0x20,0x69, + 0x63,0x65,0x20,0x73,0x68,0x65,0x65,0x74,0x20,0x65,0x64,0x67,0x65,0x73,0xa,0x20, + 0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x20,0x70,0x6f,0x6c,0x61,0x72,0x49,0x63, + 0x65,0x43,0x6f,0x76,0x65,0x72,0x20,0x3d,0x20,0x73,0x6d,0x6f,0x6f,0x74,0x68,0x73, + 0x74,0x65,0x70,0x28,0x30,0x2e,0x38,0x32,0x2c,0x20,0x30,0x2e,0x39,0x36,0x2c,0x20, + 0x64,0x69,0x73,0x74,0x46,0x72,0x6f,0x6d,0x43,0x65,0x6e,0x74,0x65,0x72,0x29,0x3b, + 0xa,0x20,0x20,0x20,0x20,0x62,0x65,0x61,0x63,0x68,0x46,0x61,0x63,0x20,0x2a,0x3d, + 0x20,0x28,0x31,0x2e,0x30,0x20,0x2d,0x20,0x70,0x6f,0x6c,0x61,0x72,0x49,0x63,0x65, + 0x43,0x6f,0x76,0x65,0x72,0x29,0x3b,0xa,0xa,0x20,0x20,0x20,0x20,0x2f,0x2f,0x20, + 0x41,0x70,0x70,0x6c,0x79,0x20,0x73,0x61,0x6e,0x64,0x79,0x20,0x62,0x65,0x61,0x63, + 0x68,0x20,0x61,0x63,0x72,0x6f,0x73,0x73,0x20,0x61,0x6c,0x6c,0x20,0x6c,0x61,0x6e, + 0x64,0x20,0x74,0x79,0x70,0x65,0x73,0x20,0x67,0x65,0x6e,0x74,0x6c,0x79,0x20,0x6d, + 0x65,0x65,0x74,0x69,0x6e,0x67,0x20,0x77,0x61,0x74,0x65,0x72,0xa,0x20,0x20,0x20, + 0x20,0x62,0x69,0x6f,0x6d,0x65,0x47,0x72,0x6f,0x75,0x6e,0x64,0x20,0x3d,0x20,0x6d, + 0x69,0x78,0x28,0x62,0x69,0x6f,0x6d,0x65,0x47,0x72,0x6f,0x75,0x6e,0x64,0x2c,0x20, + 0x62,0x65,0x61,0x63,0x68,0x43,0x6f,0x6c,0x6f,0x72,0x2c,0x20,0x62,0x65,0x61,0x63, + 0x68,0x46,0x61,0x63,0x20,0x2a,0x20,0x30,0x2e,0x39,0x30,0x29,0x3b,0xa,0xa,0x20, + 0x20,0x20,0x20,0x2f,0x2f,0x20,0x50,0x6f,0x6c,0x61,0x72,0x20,0x70,0x61,0x63,0x6b, + 0x20,0x69,0x63,0x65,0x20,0x6f,0x6e,0x20,0x63,0x6f,0x6c,0x64,0x20,0x70,0x6f,0x6c, + 0x61,0x72,0x20,0x77,0x61,0x74,0x65,0x72,0x73,0xa,0x20,0x20,0x20,0x20,0x66,0x6c, + 0x6f,0x61,0x74,0x20,0x70,0x6f,0x6c,0x61,0x72,0x53,0x65,0x61,0x49,0x63,0x65,0x20, + 0x3d,0x20,0x73,0x6d,0x6f,0x6f,0x74,0x68,0x73,0x74,0x65,0x70,0x28,0x30,0x2e,0x37, + 0x32,0x2c,0x20,0x30,0x2e,0x39,0x34,0x2c,0x20,0x64,0x69,0x73,0x74,0x46,0x72,0x6f, + 0x6d,0x43,0x65,0x6e,0x74,0x65,0x72,0x29,0x3b,0xa,0x20,0x20,0x20,0x20,0x77,0x61, + 0x74,0x65,0x72,0x43,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x6d,0x69,0x78,0x28,0x77, + 0x61,0x74,0x65,0x72,0x43,0x6f,0x6c,0x6f,0x72,0x2c,0x20,0x73,0x6e,0x6f,0x77,0x43, + 0x6f,0x6c,0x6f,0x72,0x2c,0x20,0x70,0x6f,0x6c,0x61,0x72,0x53,0x65,0x61,0x49,0x63, + 0x65,0x20,0x2a,0x20,0x30,0x2e,0x39,0x35,0x29,0x3b,0xa,0xa,0x20,0x20,0x20,0x20, + 0x2f,0x2f,0x20,0x36,0x2e,0x20,0x53,0x6c,0x6f,0x70,0x65,0x20,0x26,0x20,0x45,0x6c, + 0x65,0x76,0x61,0x74,0x69,0x6f,0x6e,0x20,0x42,0x6c,0x65,0x6e,0x64,0x69,0x6e,0x67, + 0x20,0x52,0x75,0x6c,0x65,0x73,0xa,0x20,0x20,0x20,0x20,0x2f,0x2f,0x20,0x6e,0x6f, + 0x72,0x6d,0x2e,0x7a,0x3a,0x20,0x31,0x2e,0x30,0x20,0x3d,0x20,0x66,0x6c,0x61,0x74, + 0x20,0x68,0x6f,0x72,0x69,0x7a,0x6f,0x6e,0x74,0x61,0x6c,0x20,0x67,0x72,0x6f,0x75, + 0x6e,0x64,0x2c,0x20,0x3c,0x20,0x30,0x2e,0x35,0x35,0x20,0x3d,0x20,0x73,0x74,0x65, + 0x65,0x70,0x20,0x73,0x6c,0x6f,0x70,0x65,0x20,0x2f,0x20,0x76,0x65,0x72,0x74,0x69, + 0x63,0x61,0x6c,0x20,0x63,0x6c,0x69,0x66,0x66,0xa,0x20,0x20,0x20,0x20,0x66,0x6c, + 0x6f,0x61,0x74,0x20,0x66,0x6c,0x61,0x74,0x6e,0x65,0x73,0x73,0x20,0x3d,0x20,0x63, + 0x6c,0x61,0x6d,0x70,0x28,0x28,0x6e,0x6f,0x72,0x6d,0x2e,0x7a,0x20,0x2d,0x20,0x30, + 0x2e,0x35,0x35,0x29,0x20,0x2f,0x20,0x30,0x2e,0x32,0x35,0x2c,0x20,0x30,0x2e,0x30, + 0x2c,0x20,0x31,0x2e,0x30,0x29,0x3b,0xa,0xa,0x20,0x20,0x20,0x20,0x2f,0x2f,0x20, + 0x4d,0x6f,0x75,0x6e,0x74,0x61,0x69,0x6e,0x20,0x72,0x6f,0x63,0x6b,0x20,0x65,0x6c, + 0x65,0x76,0x61,0x74,0x69,0x6f,0x6e,0x20,0x66,0x61,0x63,0x74,0x6f,0x72,0x3a,0x20, + 0x62,0x65,0x67,0x69,0x6e,0x73,0x20,0x61,0x74,0x20,0x31,0x30,0x30,0x30,0x6d,0x20, + 0x28,0x31,0x2e,0x30,0x20,0x6b,0x6d,0x29,0x2c,0x20,0x72,0x61,0x6d,0x70,0x73,0x20, + 0x75,0x70,0x20,0x74,0x6f,0x77,0x61,0x72,0x64,0x73,0x20,0x32,0x32,0x30,0x30,0x6d, + 0x20,0x28,0x32,0x2e,0x32,0x20,0x6b,0x6d,0x29,0xa,0x20,0x20,0x20,0x20,0x66,0x6c, + 0x6f,0x61,0x74,0x20,0x72,0x6f,0x63,0x6b,0x4c,0x69,0x6e,0x65,0x20,0x3d,0x20,0x6d, + 0x69,0x78,0x28,0x31,0x2e,0x30,0x2c,0x20,0x30,0x2e,0x30,0x2c,0x20,0x70,0x6f,0x6c, + 0x65,0x46,0x61,0x63,0x74,0x6f,0x72,0x29,0x3b,0xa,0x20,0x20,0x20,0x20,0x66,0x6c, + 0x6f,0x61,0x74,0x20,0x6d,0x6f,0x75,0x6e,0x74,0x61,0x69,0x6e,0x41,0x6c,0x74,0x69, + 0x74,0x75,0x64,0x65,0x20,0x3d,0x20,0x73,0x6d,0x6f,0x6f,0x74,0x68,0x73,0x74,0x65, + 0x70,0x28,0x72,0x6f,0x63,0x6b,0x4c,0x69,0x6e,0x65,0x2c,0x20,0x6d,0x69,0x6e,0x28, + 0x73,0x6e,0x6f,0x77,0x4c,0x69,0x6e,0x65,0x2c,0x20,0x72,0x6f,0x63,0x6b,0x4c,0x69, + 0x6e,0x65,0x20,0x2b,0x20,0x31,0x2e,0x32,0x29,0x2c,0x20,0x7a,0x5f,0x70,0x6f,0x73, + 0x29,0x3b,0xa,0xa,0x20,0x20,0x20,0x20,0x2f,0x2f,0x20,0x53,0x74,0x65,0x65,0x70, + 0x20,0x73,0x6c,0x6f,0x70,0x65,0x73,0x20,0x61,0x72,0x65,0x20,0x61,0x6c,0x77,0x61, + 0x79,0x73,0x20,0x72,0x6f,0x63,0x6b,0x3b,0x20,0x66,0x6c,0x61,0x74,0x20,0x67,0x72, + 0x6f,0x75,0x6e,0x64,0x20,0x62,0x65,0x63,0x6f,0x6d,0x65,0x73,0x20,0x72,0x6f,0x63, + 0x6b,0x20,0x61,0x73,0x20,0x61,0x6c,0x74,0x69,0x74,0x75,0x64,0x65,0x20,0x72,0x69, + 0x73,0x65,0x73,0x20,0x61,0x62,0x6f,0x76,0x65,0x20,0x31,0x30,0x30,0x30,0x6d,0xa, + 0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x20,0x72,0x6f,0x63,0x6b,0x57,0x65, + 0x69,0x67,0x68,0x74,0x20,0x3d,0x20,0x6d,0x61,0x78,0x28,0x31,0x2e,0x30,0x20,0x2d, + 0x20,0x66,0x6c,0x61,0x74,0x6e,0x65,0x73,0x73,0x2c,0x20,0x6d,0x6f,0x75,0x6e,0x74, + 0x61,0x69,0x6e,0x41,0x6c,0x74,0x69,0x74,0x75,0x64,0x65,0x29,0x3b,0xa,0xa,0x20, + 0x20,0x20,0x20,0x2f,0x2f,0x20,0x53,0x6e,0x6f,0x77,0x20,0x72,0x75,0x6c,0x65,0x3a, + 0x20,0x73,0x74,0x61,0x72,0x74,0x73,0x20,0x61,0x62,0x6f,0x76,0x65,0x20,0x32,0x35, + 0x30,0x30,0x6d,0x20,0x69,0x6e,0x20,0x74,0x65,0x6d,0x70,0x65,0x72,0x61,0x74,0x65, + 0x20,0x7a,0x6f,0x6e,0x65,0x2c,0x20,0x64,0x72,0x6f,0x70,0x70,0x69,0x6e,0x67,0x20, + 0x74,0x6f,0x20,0x73,0x65,0x61,0x20,0x6c,0x65,0x76,0x65,0x6c,0x20,0x61,0x74,0x20, + 0x70,0x6f,0x6c,0x65,0x73,0xa,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x20, + 0x73,0x6e,0x6f,0x77,0x48,0x65,0x69,0x67,0x68,0x74,0x20,0x3d,0x20,0x73,0x6d,0x6f, + 0x6f,0x74,0x68,0x73,0x74,0x65,0x70,0x28,0x73,0x6e,0x6f,0x77,0x4c,0x69,0x6e,0x65, + 0x20,0x2d,0x20,0x30,0x2e,0x32,0x30,0x2c,0x20,0x73,0x6e,0x6f,0x77,0x4c,0x69,0x6e, + 0x65,0x20,0x2b,0x20,0x30,0x2e,0x33,0x30,0x2c,0x20,0x7a,0x5f,0x70,0x6f,0x73,0x29, + 0x3b,0xa,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x20,0x73,0x6e,0x6f,0x77, + 0x53,0x6c,0x6f,0x70,0x65,0x20,0x3d,0x20,0x63,0x6c,0x61,0x6d,0x70,0x28,0x28,0x6e, + 0x6f,0x72,0x6d,0x2e,0x7a,0x20,0x2d,0x20,0x30,0x2e,0x32,0x38,0x29,0x20,0x2f,0x20, + 0x30,0x2e,0x32,0x35,0x2c,0x20,0x30,0x2e,0x30,0x2c,0x20,0x31,0x2e,0x30,0x29,0x3b, + 0xa,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x20,0x73,0x6e,0x6f,0x77,0x57, + 0x65,0x69,0x67,0x68,0x74,0x20,0x3d,0x20,0x73,0x6e,0x6f,0x77,0x48,0x65,0x69,0x67, + 0x68,0x74,0x20,0x2a,0x20,0x73,0x6e,0x6f,0x77,0x53,0x6c,0x6f,0x70,0x65,0x3b,0xa, + 0xa,0x20,0x20,0x20,0x20,0x2f,0x2f,0x20,0x41,0x74,0x20,0x70,0x6f,0x6c,0x61,0x72, + 0x20,0x63,0x61,0x70,0x73,0x2c,0x20,0x69,0x63,0x65,0x20,0x63,0x6f,0x76,0x65,0x72, + 0x73,0x20,0x66,0x6c,0x61,0x74,0x20,0x67,0x72,0x6f,0x75,0x6e,0x64,0x20,0x61,0x74, + 0x20,0x73,0x65,0x61,0x20,0x6c,0x65,0x76,0x65,0x6c,0xa,0x20,0x20,0x20,0x20,0x66, + 0x6c,0x6f,0x61,0x74,0x20,0x70,0x6f,0x6c,0x61,0x72,0x4c,0x61,0x6e,0x64,0x49,0x63, + 0x65,0x20,0x3d,0x20,0x73,0x6d,0x6f,0x6f,0x74,0x68,0x73,0x74,0x65,0x70,0x28,0x30, + 0x2e,0x37,0x32,0x2c,0x20,0x30,0x2e,0x39,0x32,0x2c,0x20,0x64,0x69,0x73,0x74,0x46, + 0x72,0x6f,0x6d,0x43,0x65,0x6e,0x74,0x65,0x72,0x29,0x3b,0xa,0x20,0x20,0x20,0x20, + 0x73,0x6e,0x6f,0x77,0x57,0x65,0x69,0x67,0x68,0x74,0x20,0x3d,0x20,0x6d,0x61,0x78, + 0x28,0x73,0x6e,0x6f,0x77,0x57,0x65,0x69,0x67,0x68,0x74,0x2c,0x20,0x70,0x6f,0x6c, + 0x61,0x72,0x4c,0x61,0x6e,0x64,0x49,0x63,0x65,0x20,0x2a,0x20,0x73,0x6e,0x6f,0x77, + 0x53,0x6c,0x6f,0x70,0x65,0x29,0x3b,0xa,0xa,0x20,0x20,0x20,0x20,0x2f,0x2f,0x20, + 0x43,0x6f,0x6d,0x70,0x6f,0x73,0x69,0x74,0x65,0x20,0x67,0x72,0x6f,0x75,0x6e,0x64, + 0x3a,0x20,0x62,0x69,0x6f,0x6d,0x65,0x20,0x67,0x72,0x6f,0x75,0x6e,0x64,0x20,0x2d, + 0x3e,0x20,0x6d,0x6f,0x75,0x6e,0x74,0x61,0x69,0x6e,0x20,0x72,0x6f,0x63,0x6b,0x20, + 0x61,0x62,0x6f,0x76,0x65,0x20,0x31,0x30,0x30,0x30,0x6d,0x20,0x26,0x20,0x6f,0x6e, + 0x20,0x63,0x6c,0x69,0x66,0x66,0x73,0x20,0x2d,0x3e,0x20,0x73,0x6e,0x6f,0x77,0x20, + 0x61,0x62,0x6f,0x76,0x65,0x20,0x32,0x35,0x30,0x30,0x6d,0xa,0x20,0x20,0x20,0x20, + 0x76,0x65,0x63,0x34,0x20,0x6e,0x6f,0x6e,0x53,0x6e,0x6f,0x77,0x4c,0x61,0x6e,0x64, + 0x20,0x3d,0x20,0x6d,0x69,0x78,0x28,0x62,0x69,0x6f,0x6d,0x65,0x47,0x72,0x6f,0x75, + 0x6e,0x64,0x2c,0x20,0x63,0x6c,0x69,0x66,0x66,0x52,0x6f,0x63,0x6b,0x43,0x6f,0x6c, + 0x6f,0x72,0x2c,0x20,0x72,0x6f,0x63,0x6b,0x57,0x65,0x69,0x67,0x68,0x74,0x29,0x3b, + 0xa,0x20,0x20,0x20,0x20,0x76,0x65,0x63,0x34,0x20,0x6c,0x61,0x6e,0x64,0x43,0x6f, + 0x6c,0x6f,0x72,0x20,0x3d,0x20,0x6d,0x69,0x78,0x28,0x6e,0x6f,0x6e,0x53,0x6e,0x6f, + 0x77,0x4c,0x61,0x6e,0x64,0x2c,0x20,0x73,0x6e,0x6f,0x77,0x43,0x6f,0x6c,0x6f,0x72, + 0x2c,0x20,0x73,0x6e,0x6f,0x77,0x57,0x65,0x69,0x67,0x68,0x74,0x29,0x3b,0xa,0xa, + 0x20,0x20,0x20,0x20,0x2f,0x2f,0x20,0x37,0x2e,0x20,0x43,0x6f,0x6d,0x62,0x69,0x6e, + 0x65,0x20,0x4c,0x61,0x6e,0x64,0x20,0x61,0x6e,0x64,0x20,0x53,0x65,0x61,0xa,0x20, + 0x20,0x20,0x20,0x76,0x65,0x63,0x34,0x20,0x73,0x75,0x72,0x66,0x61,0x63,0x65,0x43, + 0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x6d,0x69,0x78,0x28,0x6c,0x61,0x6e,0x64,0x43, + 0x6f,0x6c,0x6f,0x72,0x2c,0x20,0x77,0x61,0x74,0x65,0x72,0x43,0x6f,0x6c,0x6f,0x72, + 0x2c,0x20,0x77,0x61,0x74,0x65,0x72,0x46,0x61,0x63,0x29,0x3b,0xa,0xa,0x20,0x20, + 0x20,0x20,0x2f,0x2f,0x20,0x38,0x2e,0x20,0x48,0x65,0x78,0x61,0x67,0x6f,0x6e,0x20, + 0x67,0x72,0x69,0x64,0x20,0x62,0x6f,0x72,0x64,0x65,0x72,0x20,0x6f,0x75,0x74,0x6c, + 0x69,0x6e,0x65,0xa,0x20,0x20,0x20,0x20,0x76,0x65,0x63,0x34,0x20,0x62,0x63,0x6f, + 0x6c,0x6f,0x72,0x20,0x3d,0x20,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x28,0x74,0x65, + 0x78,0x42,0x6f,0x72,0x64,0x65,0x72,0x2c,0x20,0x76,0x65,0x63,0x32,0x28,0x74,0x69, + 0x6c,0x65,0x50,0x6f,0x73,0x2e,0x78,0x2c,0x20,0x74,0x69,0x6c,0x65,0x50,0x6f,0x73, + 0x2e,0x79,0x20,0x2a,0x20,0x31,0x2e,0x31,0x35,0x34,0x37,0x33,0x34,0x34,0x31,0x31, + 0x29,0x29,0x3b,0xa,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x20,0x62,0x6f, + 0x72,0x64,0x65,0x72,0x41,0x6c,0x70,0x68,0x61,0x20,0x3d,0x20,0x62,0x63,0x6f,0x6c, + 0x6f,0x72,0x2e,0x61,0x20,0x2a,0x20,0x28,0x31,0x2e,0x30,0x20,0x2d,0x20,0x77,0x61, + 0x74,0x65,0x72,0x46,0x61,0x63,0x20,0x2a,0x20,0x30,0x2e,0x37,0x29,0x3b,0xa,0xa, + 0x20,0x20,0x20,0x20,0x2f,0x2f,0x20,0x39,0x2e,0x20,0x4c,0x69,0x67,0x68,0x74,0x69, + 0x6e,0x67,0x20,0x26,0x20,0x53,0x70,0x65,0x63,0x75,0x6c,0x61,0x72,0x20,0x67,0x6c, + 0x69,0x6e,0x74,0xa,0x20,0x20,0x20,0x20,0x76,0x65,0x63,0x33,0x20,0x61,0x63,0x74, + 0x69,0x76,0x65,0x4e,0x6f,0x72,0x6d,0x20,0x3d,0x20,0x6d,0x69,0x78,0x28,0x6e,0x6f, + 0x72,0x6d,0x2c,0x20,0x77,0x61,0x74,0x65,0x72,0x4e,0x6f,0x72,0x6d,0x2c,0x20,0x77, + 0x61,0x74,0x65,0x72,0x46,0x61,0x63,0x29,0x3b,0xa,0x20,0x20,0x20,0x20,0x76,0x65, + 0x63,0x34,0x20,0x6c,0x69,0x67,0x68,0x74,0x44,0x69,0x66,0x66,0x75,0x73,0x65,0x20, + 0x3d,0x20,0x47,0x65,0x74,0x44,0x69,0x72,0x65,0x63,0x74,0x69,0x6f,0x6e,0x61,0x6c, + 0x4c,0x69,0x67,0x68,0x74,0x43,0x6f,0x6c,0x6f,0x72,0x28,0x73,0x75,0x6e,0x4c,0x69, + 0x67,0x68,0x74,0x2c,0x20,0x61,0x63,0x74,0x69,0x76,0x65,0x4e,0x6f,0x72,0x6d,0x29, + 0x3b,0xa,0xa,0x20,0x20,0x20,0x20,0x2f,0x2f,0x20,0x53,0x70,0x65,0x63,0x75,0x6c, + 0x61,0x72,0x3a,0x20,0x63,0x72,0x69,0x73,0x70,0x20,0x73,0x70,0x61,0x72,0x6b,0x6c, + 0x65,0x20,0x6f,0x6e,0x20,0x73,0x6e,0x6f,0x77,0x20,0x61,0x6e,0x64,0x20,0x77,0x61, + 0x74,0x65,0x72,0xa,0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x20,0x73,0x70, + 0x65,0x63,0x53,0x68,0x69,0x6e,0x69,0x6e,0x65,0x73,0x73,0x20,0x3d,0x20,0x6d,0x69, + 0x78,0x28,0x6d,0x69,0x78,0x28,0x31,0x36,0x2e,0x30,0x2c,0x20,0x33,0x32,0x2e,0x30, + 0x2c,0x20,0x73,0x6e,0x6f,0x77,0x57,0x65,0x69,0x67,0x68,0x74,0x29,0x2c,0x20,0x34, + 0x38,0x2e,0x30,0x2c,0x20,0x77,0x61,0x74,0x65,0x72,0x46,0x61,0x63,0x29,0x3b,0xa, + 0x20,0x20,0x20,0x20,0x66,0x6c,0x6f,0x61,0x74,0x20,0x73,0x70,0x65,0x63,0x49,0x6e, + 0x74,0x65,0x6e,0x73,0x69,0x74,0x79,0x20,0x3d,0x20,0x6d,0x69,0x78,0x28,0x6d,0x69, + 0x78,0x28,0x30,0x2e,0x31,0x32,0x2c,0x20,0x30,0x2e,0x35,0x35,0x2c,0x20,0x73,0x6e, + 0x6f,0x77,0x57,0x65,0x69,0x67,0x68,0x74,0x29,0x2c,0x20,0x30,0x2e,0x38,0x35,0x2c, + 0x20,0x77,0x61,0x74,0x65,0x72,0x46,0x61,0x63,0x29,0x3b,0xa,0x20,0x20,0x20,0x20, + 0x76,0x65,0x63,0x34,0x20,0x6c,0x69,0x67,0x68,0x74,0x53,0x70,0x65,0x63,0x75,0x6c, + 0x61,0x72,0x20,0x3d,0x20,0x47,0x65,0x74,0x53,0x70,0x65,0x63,0x75,0x6c,0x61,0x72, + 0x43,0x6f,0x6c,0x6f,0x72,0x28,0x76,0x57,0x6f,0x72,0x6c,0x64,0x50,0x6f,0x73,0x2c, + 0x20,0x63,0x61,0x6d,0x65,0x72,0x61,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x2c, + 0x20,0x73,0x75,0x6e,0x4c,0x69,0x67,0x68,0x74,0x2c,0x20,0x61,0x63,0x74,0x69,0x76, + 0x65,0x4e,0x6f,0x72,0x6d,0x2c,0x20,0x73,0x70,0x65,0x63,0x53,0x68,0x69,0x6e,0x69, + 0x6e,0x65,0x73,0x73,0x2c,0x20,0x73,0x70,0x65,0x63,0x49,0x6e,0x74,0x65,0x6e,0x73, + 0x69,0x74,0x79,0x29,0x3b,0xa,0xa,0x20,0x20,0x20,0x20,0x66,0x69,0x6e,0x61,0x6c, + 0x43,0x6f,0x6c,0x6f,0x72,0x20,0x3d,0x20,0x28,0x73,0x75,0x72,0x66,0x61,0x63,0x65, + 0x43,0x6f,0x6c,0x6f,0x72,0x20,0x2b,0x20,0x62,0x63,0x6f,0x6c,0x6f,0x72,0x20,0x2a, + 0x20,0x62,0x6f,0x72,0x64,0x65,0x72,0x41,0x6c,0x70,0x68,0x61,0x29,0x20,0x2a,0x20, + 0x6c,0x69,0x67,0x68,0x74,0x44,0x69,0x66,0x66,0x75,0x73,0x65,0x20,0x2b,0x20,0x6c, + 0x69,0x67,0x68,0x74,0x53,0x70,0x65,0x63,0x75,0x6c,0x61,0x72,0x3b,0xa,0x7d,0xa, + // /home/bart/project/tiletest/shaders/simple.vert - 0x0,0x0,0x3,0xf9, + 0x0,0x0,0x4,0x27, 0x23, 0x76,0x65,0x72,0x73,0x69,0x6f,0x6e,0x20,0x34,0x30,0x30,0xa,0x75,0x6e,0x69,0x66, 0x6f,0x72,0x6d,0x20,0x6d,0x61,0x74,0x34,0x20,0x63,0x61,0x6d,0x65,0x72,0x61,0x3b, @@ -185,61 +758,64 @@ static const unsigned char qt_resource_data[] = { 0x6e,0x69,0x66,0x6f,0x72,0x6d,0x20,0x6d,0x61,0x74,0x34,0x20,0x6e,0x6f,0x72,0x6d, 0x61,0x6c,0x4d,0x61,0x74,0x72,0x69,0x78,0x3b,0xa,0x75,0x6e,0x69,0x66,0x6f,0x72, 0x6d,0x20,0x6d,0x61,0x74,0x34,0x20,0x6d,0x6f,0x64,0x65,0x6c,0x4d,0x61,0x74,0x72, - 0x69,0x78,0x3b,0xa,0xa,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61, - 0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x30,0x29,0x20,0x69,0x6e,0x20,0x76,0x65,0x63, - 0x33,0x20,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x3b,0xa,0x6c,0x61,0x79,0x6f, - 0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x31,0x29, - 0x20,0x69,0x6e,0x20,0x76,0x65,0x63,0x32,0x20,0x69,0x6e,0x43,0x6f,0x6f,0x72,0x64, - 0x3b,0xa,0xa,0x6c,0x61,0x79,0x6f,0x75,0x74,0x20,0x28,0x6c,0x6f,0x63,0x61,0x74, - 0x69,0x6f,0x6e,0x20,0x3d,0x20,0x32,0x29,0x20,0x69,0x6e,0x20,0x66,0x6c,0x6f,0x61, - 0x74,0x20,0x7a,0x50,0x6f,0x73,0x3b,0xa,0x6c,0x61,0x79,0x6f,0x75,0x74,0x20,0x28, - 0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x33,0x29,0x20,0x69,0x6e,0x20, - 0x76,0x65,0x63,0x33,0x20,0x69,0x6e,0x4e,0x6f,0x72,0x6d,0x61,0x6c,0x3b,0xa,0xa, - 0x2f,0x2f,0x69,0x6e,0x20,0x66,0x6c,0x6f,0x61,0x74,0x20,0x68,0x65,0x69,0x67,0x68, - 0x74,0x3b,0xa,0x6f,0x75,0x74,0x20,0x76,0x65,0x63,0x32,0x20,0x66,0x72,0x61,0x67, - 0x54,0x65,0x78,0x43,0x6f,0x6f,0x72,0x64,0x3b,0xa,0x6f,0x75,0x74,0x20,0x76,0x65, - 0x63,0x32,0x20,0x74,0x69,0x6c,0x65,0x50,0x6f,0x73,0x3b,0xa,0x6f,0x75,0x74,0x20, - 0x76,0x65,0x63,0x33,0x20,0x66,0x72,0x61,0x67,0x56,0x65,0x72,0x74,0x3b,0xa,0x73, - 0x6d,0x6f,0x6f,0x74,0x68,0x20,0x6f,0x75,0x74,0x20,0x76,0x65,0x63,0x33,0x20,0x76, - 0x57,0x6f,0x72,0x6c,0x64,0x50,0x6f,0x73,0x3b,0xa,0x75,0x6e,0x69,0x66,0x6f,0x72, - 0x6d,0x20,0x6d,0x61,0x74,0x34,0x20,0x4d,0x56,0x50,0x3b,0xa,0x73,0x6d,0x6f,0x6f, - 0x74,0x68,0x20,0x6f,0x75,0x74,0x20,0x76,0x65,0x63,0x33,0x20,0x76,0x4e,0x6f,0x72, - 0x6d,0x61,0x6c,0x3b,0xa,0x6f,0x75,0x74,0x20,0x66,0x6c,0x6f,0x61,0x74,0x20,0x7a, - 0x5f,0x70,0x6f,0x73,0x3b,0xa,0x76,0x6f,0x69,0x64,0x20,0x6d,0x61,0x69,0x6e,0x28, - 0x29,0xa,0x7b,0xa,0xa,0x76,0x65,0x63,0x34,0x20,0x69,0x6e,0x50,0x6f,0x73,0x69, - 0x74,0x69,0x6f,0x6e,0x53,0x63,0x61,0x6c,0x65,0x64,0x20,0x3d,0x20,0x76,0x65,0x63, - 0x34,0x28,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x2c,0x20,0x31,0x2e,0x30,0x29, - 0x3b,0xa,0x20,0x20,0x67,0x6c,0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20, - 0x3d,0x20,0x4d,0x56,0x50,0x20,0x2a,0x76,0x65,0x63,0x34,0x28,0x70,0x6f,0x73,0x69, - 0x74,0x69,0x6f,0x6e,0x2e,0x78,0x2c,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x2e, - 0x79,0x2c,0x7a,0x50,0x6f,0x73,0x2c,0x20,0x31,0x2e,0x30,0x29,0x3b,0xa,0x20,0x20, - 0x76,0x65,0x63,0x34,0x20,0x74,0x65,0x78,0x70,0x3d,0x20,0x6d,0x6f,0x64,0x65,0x6c, - 0x56,0x69,0x65,0x77,0x4d,0x61,0x74,0x72,0x69,0x78,0x20,0x2a,0x76,0x65,0x63,0x34, - 0x28,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x2e,0x78,0x2c,0x70,0x6f,0x73,0x69, - 0x74,0x69,0x6f,0x6e,0x2e,0x79,0x2c,0x30,0x2c,0x20,0x31,0x2e,0x30,0x29,0x3b,0xa, - 0x20,0x20,0x66,0x72,0x61,0x67,0x54,0x65,0x78,0x43,0x6f,0x6f,0x72,0x64,0x20,0x3d, - 0x20,0x76,0x65,0x63,0x32,0x28,0x74,0x65,0x78,0x70,0x2e,0x78,0x2f,0x32,0x2c,0x74, - 0x65,0x78,0x70,0x2e,0x79,0x2f,0x32,0x29,0x3b,0xa,0x20,0x20,0x76,0x65,0x63,0x34, - 0x20,0x74,0x70,0x6f,0x73,0x20,0x3d,0x20,0x6d,0x6f,0x64,0x65,0x6c,0x4d,0x61,0x74, - 0x72,0x69,0x78,0x2a,0x76,0x65,0x63,0x34,0x28,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f, - 0x6e,0x2e,0x78,0x2c,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x2e,0x79,0x2c,0x30, - 0x2c,0x20,0x31,0x2e,0x30,0x29,0x3b,0xa,0x20,0x20,0x74,0x69,0x6c,0x65,0x50,0x6f, - 0x73,0x20,0x3d,0x20,0x76,0x65,0x63,0x32,0x28,0x74,0x70,0x6f,0x73,0x2e,0x78,0x2c, - 0x74,0x70,0x6f,0x73,0x2e,0x79,0x29,0x3b,0xa,0x20,0x20,0x2f,0x2f,0x76,0x43,0x6f, - 0x6c,0x6f,0x72,0x20,0x3d,0x20,0x76,0x65,0x63,0x34,0x28,0x63,0x6f,0x6c,0x6f,0x72, - 0x2c,0x20,0x31,0x2e,0x30,0x29,0x3b,0xa,0x20,0x20,0x76,0x65,0x63,0x34,0x20,0x76, - 0x52,0x65,0x73,0x20,0x3d,0x20,0x76,0x65,0x63,0x34,0x28,0x69,0x6e,0x4e,0x6f,0x72, - 0x6d,0x61,0x6c,0x2c,0x20,0x30,0x2e,0x30,0x29,0x3b,0xa,0x20,0x20,0x20,0x20,0x20, - 0x76,0x4e,0x6f,0x72,0x6d,0x61,0x6c,0x20,0x3d,0x20,0x69,0x6e,0x4e,0x6f,0x72,0x6d, - 0x61,0x6c,0x2e,0x78,0x79,0x7a,0x3b,0xa,0x20,0x20,0x66,0x72,0x61,0x67,0x56,0x65, - 0x72,0x74,0x20,0x3d,0x20,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x3b,0xa,0xa, - 0x20,0x20,0x76,0x65,0x63,0x34,0x20,0x76,0x57,0x6f,0x72,0x6c,0x64,0x50,0x6f,0x73, - 0x4c,0x6f,0x63,0x61,0x6c,0x20,0x3d,0x6d,0x6f,0x64,0x65,0x6c,0x4d,0x61,0x74,0x72, - 0x69,0x78,0x2a,0x69,0x6e,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x53,0x63,0x61, - 0x6c,0x65,0x64,0x3b,0xa,0x20,0x20,0x76,0x57,0x6f,0x72,0x6c,0x64,0x50,0x6f,0x73, - 0x20,0x3d,0x20,0x76,0x57,0x6f,0x72,0x6c,0x64,0x50,0x6f,0x73,0x4c,0x6f,0x63,0x61, - 0x6c,0x2e,0x78,0x79,0x7a,0x3b,0xa,0x20,0x20,0x7a,0x5f,0x70,0x6f,0x73,0x3d,0x7a, - 0x50,0x6f,0x73,0x3b,0xa,0x7d,0xa,0xa, + 0x69,0x78,0x3b,0xa,0x75,0x6e,0x69,0x66,0x6f,0x72,0x6d,0x20,0x6d,0x61,0x74,0x34, + 0x20,0x77,0x6f,0x72,0x6c,0x64,0x4d,0x61,0x74,0x72,0x69,0x78,0x3b,0xa,0xa,0x6c, + 0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d, + 0x20,0x30,0x29,0x20,0x69,0x6e,0x20,0x76,0x65,0x63,0x33,0x20,0x70,0x6f,0x73,0x69, + 0x74,0x69,0x6f,0x6e,0x3b,0xa,0x6c,0x61,0x79,0x6f,0x75,0x74,0x28,0x6c,0x6f,0x63, + 0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x31,0x29,0x20,0x69,0x6e,0x20,0x76,0x65, + 0x63,0x32,0x20,0x69,0x6e,0x43,0x6f,0x6f,0x72,0x64,0x3b,0xa,0xa,0x6c,0x61,0x79, + 0x6f,0x75,0x74,0x20,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20, + 0x32,0x29,0x20,0x69,0x6e,0x20,0x66,0x6c,0x6f,0x61,0x74,0x20,0x7a,0x50,0x6f,0x73, + 0x3b,0xa,0x6c,0x61,0x79,0x6f,0x75,0x74,0x20,0x28,0x6c,0x6f,0x63,0x61,0x74,0x69, + 0x6f,0x6e,0x20,0x3d,0x20,0x33,0x29,0x20,0x69,0x6e,0x20,0x76,0x65,0x63,0x33,0x20, + 0x69,0x6e,0x4e,0x6f,0x72,0x6d,0x61,0x6c,0x3b,0xa,0xa,0x6f,0x75,0x74,0x20,0x76, + 0x65,0x63,0x32,0x20,0x66,0x72,0x61,0x67,0x54,0x65,0x78,0x43,0x6f,0x6f,0x72,0x64, + 0x3b,0xa,0x6f,0x75,0x74,0x20,0x76,0x65,0x63,0x32,0x20,0x74,0x69,0x6c,0x65,0x50, + 0x6f,0x73,0x3b,0xa,0x6f,0x75,0x74,0x20,0x76,0x65,0x63,0x33,0x20,0x66,0x72,0x61, + 0x67,0x56,0x65,0x72,0x74,0x3b,0xa,0x73,0x6d,0x6f,0x6f,0x74,0x68,0x20,0x6f,0x75, + 0x74,0x20,0x76,0x65,0x63,0x33,0x20,0x76,0x57,0x6f,0x72,0x6c,0x64,0x50,0x6f,0x73, + 0x3b,0xa,0x75,0x6e,0x69,0x66,0x6f,0x72,0x6d,0x20,0x6d,0x61,0x74,0x34,0x20,0x4d, + 0x56,0x50,0x3b,0xa,0x73,0x6d,0x6f,0x6f,0x74,0x68,0x20,0x6f,0x75,0x74,0x20,0x76, + 0x65,0x63,0x33,0x20,0x76,0x4e,0x6f,0x72,0x6d,0x61,0x6c,0x3b,0xa,0x6f,0x75,0x74, + 0x20,0x66,0x6c,0x6f,0x61,0x74,0x20,0x7a,0x5f,0x70,0x6f,0x73,0x3b,0xa,0xa,0x76, + 0x6f,0x69,0x64,0x20,0x6d,0x61,0x69,0x6e,0x28,0x29,0xa,0x7b,0xa,0x20,0x20,0x67, + 0x6c,0x5f,0x50,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x3d,0x20,0x4d,0x56,0x50, + 0x20,0x2a,0x20,0x76,0x65,0x63,0x34,0x28,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e, + 0x2e,0x78,0x2c,0x20,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x2e,0x79,0x2c,0x20, + 0x7a,0x50,0x6f,0x73,0x2c,0x20,0x31,0x2e,0x30,0x29,0x3b,0xa,0xa,0x20,0x20,0x2f, + 0x2f,0x20,0x57,0x6f,0x72,0x6c,0x64,0x20,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e, + 0x3a,0x20,0x61,0x6e,0x63,0x68,0x6f,0x72,0x65,0x64,0x20,0x74,0x6f,0x20,0x74,0x65, + 0x72,0x72,0x61,0x69,0x6e,0x20,0x67,0x72,0x6f,0x75,0x6e,0x64,0xa,0x20,0x20,0x76, + 0x65,0x63,0x34,0x20,0x77,0x50,0x6f,0x73,0x20,0x3d,0x20,0x77,0x6f,0x72,0x6c,0x64, + 0x4d,0x61,0x74,0x72,0x69,0x78,0x20,0x2a,0x20,0x76,0x65,0x63,0x34,0x28,0x70,0x6f, + 0x73,0x69,0x74,0x69,0x6f,0x6e,0x2e,0x78,0x2c,0x20,0x70,0x6f,0x73,0x69,0x74,0x69, + 0x6f,0x6e,0x2e,0x79,0x2c,0x20,0x7a,0x50,0x6f,0x73,0x2c,0x20,0x31,0x2e,0x30,0x29, + 0x3b,0xa,0x20,0x20,0x76,0x57,0x6f,0x72,0x6c,0x64,0x50,0x6f,0x73,0x20,0x3d,0x20, + 0x77,0x50,0x6f,0x73,0x2e,0x78,0x79,0x7a,0x3b,0xa,0xa,0x20,0x20,0x2f,0x2f,0x20, + 0x54,0x65,0x78,0x74,0x75,0x72,0x65,0x20,0x63,0x6f,0x6f,0x72,0x64,0x69,0x6e,0x61, + 0x74,0x65,0x73,0x20,0x62,0x61,0x73,0x65,0x64,0x20,0x6f,0x6e,0x20,0x77,0x6f,0x72, + 0x6c,0x64,0x20,0x63,0x6f,0x6f,0x72,0x64,0x69,0x6e,0x61,0x74,0x65,0x73,0x20,0x28, + 0x73,0x74,0x61,0x79,0x73,0x20,0x66,0x69,0x78,0x65,0x64,0x20,0x77,0x68,0x65,0x6e, + 0x20,0x63,0x61,0x6d,0x65,0x72,0x61,0x20,0x6d,0x6f,0x76,0x65,0x73,0x29,0xa,0x20, + 0x20,0x66,0x72,0x61,0x67,0x54,0x65,0x78,0x43,0x6f,0x6f,0x72,0x64,0x20,0x3d,0x20, + 0x76,0x65,0x63,0x32,0x28,0x77,0x50,0x6f,0x73,0x2e,0x78,0x20,0x2a,0x20,0x30,0x2e, + 0x35,0x2c,0x20,0x77,0x50,0x6f,0x73,0x2e,0x79,0x20,0x2a,0x20,0x30,0x2e,0x35,0x29, + 0x3b,0xa,0xa,0x20,0x20,0x2f,0x2f,0x20,0x54,0x69,0x6c,0x65,0x2d,0x6c,0x6f,0x63, + 0x61,0x6c,0x20,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e,0x20,0x66,0x6f,0x72,0x20, + 0x74,0x68,0x65,0x20,0x68,0x65,0x78,0x61,0x67,0x6f,0x6e,0x20,0x62,0x6f,0x72,0x64, + 0x65,0x72,0x20,0x6f,0x75,0x74,0x6c,0x69,0x6e,0x65,0xa,0x20,0x20,0x76,0x65,0x63, + 0x34,0x20,0x74,0x70,0x6f,0x73,0x20,0x3d,0x20,0x6d,0x6f,0x64,0x65,0x6c,0x4d,0x61, + 0x74,0x72,0x69,0x78,0x20,0x2a,0x20,0x76,0x65,0x63,0x34,0x28,0x70,0x6f,0x73,0x69, + 0x74,0x69,0x6f,0x6e,0x2e,0x78,0x2c,0x20,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f,0x6e, + 0x2e,0x79,0x2c,0x20,0x30,0x2e,0x30,0x2c,0x20,0x31,0x2e,0x30,0x29,0x3b,0xa,0x20, + 0x20,0x74,0x69,0x6c,0x65,0x50,0x6f,0x73,0x20,0x3d,0x20,0x76,0x65,0x63,0x32,0x28, + 0x74,0x70,0x6f,0x73,0x2e,0x78,0x2c,0x20,0x74,0x70,0x6f,0x73,0x2e,0x79,0x29,0x3b, + 0xa,0xa,0x20,0x20,0x76,0x4e,0x6f,0x72,0x6d,0x61,0x6c,0x20,0x3d,0x20,0x69,0x6e, + 0x4e,0x6f,0x72,0x6d,0x61,0x6c,0x2e,0x78,0x79,0x7a,0x3b,0xa,0x20,0x20,0x66,0x72, + 0x61,0x67,0x56,0x65,0x72,0x74,0x20,0x3d,0x20,0x70,0x6f,0x73,0x69,0x74,0x69,0x6f, + 0x6e,0x3b,0xa,0x20,0x20,0x7a,0x5f,0x70,0x6f,0x73,0x20,0x3d,0x20,0x7a,0x50,0x6f, + 0x73,0x3b,0xa,0x7d,0xa,0xa, }; @@ -271,10 +847,10 @@ static const unsigned char qt_resource_struct[] = { 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, // :/shaders/simple.frag 0x0,0x0,0x0,0x14,0x0,0x0,0x0,0x0,0x0,0x1,0x0,0x0,0x0,0x0, -0x0,0x0,0x1,0x70,0x12,0x77,0x25,0xb5, +0x0,0x0,0x1,0xa1,0xbb,0x69,0x8c,0x82, // :/shaders/simple.vert - 0x0,0x0,0x0,0x30,0x0,0x0,0x0,0x0,0x0,0x1,0x0,0x0,0xa,0x33, -0x0,0x0,0x1,0x70,0x12,0x77,0x25,0x9d, + 0x0,0x0,0x0,0x30,0x0,0x0,0x0,0x0,0x0,0x1,0x0,0x0,0x2d,0xf5, +0x0,0x0,0x1,0xa1,0xba,0xb6,0x16,0x20, }; diff --git a/qrc_shaders.o b/qrc_shaders.o index a93197e..a1d4f5b 100644 Binary files a/qrc_shaders.o and b/qrc_shaders.o differ diff --git a/qscenerenderer.cpp b/qscenerenderer.cpp index af93242..94e9b5a 100644 --- a/qscenerenderer.cpp +++ b/qscenerenderer.cpp @@ -1,101 +1,1144 @@ #include "qscenerenderer.h" #include - - +#include +#include +#include +#include +#include +#include "pathfinder.h" GLuint vertexbuffer; - GLuint VertexArrayID; +GLuint VertexArrayID; + QSceneRenderer::QSceneRenderer(QWidget* parent): QOpenGLWidget(parent) { setFocusPolicy(Qt::FocusPolicy::StrongFocus); + setMouseTracking(true); installEventFilter(this); + QImage img = QImage("/home/bart/project/tiletest/images/heightmap.jpg").convertToFormat(QImage::Format_RGB32); + QImage typeImg = QImage("/home/bart/project/tiletest/images/typemap.png").convertToFormat(QImage::Format_RGB32); + QImage riverImg = QImage("/home/bart/project/tiletest/images/rivermap.png").convertToFormat(QImage::Format_RGB32); m_zoom = 4; - m_point = new Matrix2D(img.width(),img.height()); - m_numPoints05= img.width()/MAP_WIDTH/1.5/2; - for (int y = 0; y(img.width(), img.height()); + m_numPoints05 = img.width() / MAP_WIDTH / 1.5 / 2; + + for (int y = 0; y < img.height(); y++) { QRgb *line = (QRgb *) img.scanLine(img.height() - 1 - y); + QRgb *typeLine = !typeImg.isNull() ? (QRgb *) typeImg.scanLine(typeImg.height() - 1 - y) : nullptr; + QRgb *riverLine = !riverImg.isNull() ? (QRgb *) riverImg.scanLine(riverImg.height() - 1 - y) : nullptr; TerrainPoint *row = m_point->row(y); - for (int x = 0; x 120 && qRed(riverLine[x]) < 100) ? 1 : 0; + if (typeLine) { + QRgb c = typeLine[x]; + int r = qRed(c), g = qGreen(c), b = qBlue(c); + int sat = std::max(r, std::max(g, b)) - std::min(r, std::min(g, b)); + if (r > 150 && g < 100) row[x].type = 3; // Prairie / Red Canyon + else if (r > 150 && g > 150 && b < 100) row[x].type = 1; // Sahara + else if (b > 50 && r < 50) row[x].type = 4; // Bos (Forest) + else if (sat < 40 && g < 140) row[x].type = 2; // Tundra + else row[x].type = 0; // Grasland + } else { + row[x].type = 0; + } } } + zRot = 90.0f; // Default looking North (+Y) + m_zoom = 8.0f; cameraUpDirection = cameraTransformation * QVector3D(0, 1, 0); cameraTransformation.rotate(zRot, 0, 0, 1); - cameraPosition = cameraTransformation * QVector3D(5, 5, 5); -/* - for (int y=0; yoperator ()(x,y).height = abs((x*10%200)-100); - }*/ + tiles = new Matrix2D(MAP_WIDTH, MAP_HEIGHT); + loadRiverMap(riverImg); - tiles=new Matrix2D(MAP_WIDTH,MAP_HEIGHT); + m_draggingMinimap = false; + m_minimapZoom = 1.0f; + m_image_minimap = QImage("/home/bart/project/tiletest/images/minimap.png"); + if (m_image_minimap.isNull()) { + m_image_minimap = QImage("/home/bart/project/tiletest/images/rivermap_preview.png"); + } + + // Initialize GameManager and spawn demo units + GameManager::instance().init("/home/bart/project/tiletest", m_point, m_numPoints05); + + m_lastFrameTime = 0.0f; + m_elapsedTimer.start(); + QTimer *animTimer = new QTimer(this); + connect(animTimer, &QTimer::timeout, this, QOverload<>::of(&QSceneRenderer::update)); + animTimer->start(16); +} + +void QSceneRenderer::loadRiverMap(const QImage &riverImg) +{ + if (riverImg.isNull()) return; + + float scaleX = m_numPoints05 * 2.0f; + float scaleY = m_numPoints05 / LOD_0_SIZE_Y; + + for (int y = 0; y < MAP_HEIGHT; ++y) { + for (int x = 0; x < MAP_WIDTH; ++x) { + float worldX = x * (LOD_0_SIZE * 0.75f); + float worldY = y * (LOD_0_SIZE_Y * 2.0f) + ((x % 2 == 0) ? 0.0f : LOD_0_SIZE_Y); + + for (int e = 0; e < 6; ++e) { + QVector2D mid = getHexEdgeMidpoint(e); + float midX = worldX + mid.x(); + float midY = worldY + mid.y(); + + int px = (int) round(midX * scaleX); + int py = (int) round(midY * scaleY); + + bool hasRiver = false; + for (int dy = -1; dy <= 1 && !hasRiver; ++dy) { + for (int dx = -1; dx <= 1 && !hasRiver; ++dx) { + int sx = px + dx; + int sy = py + dy; + if (sx >= 0 && sx < riverImg.width() && sy >= 0 && sy < riverImg.height()) { + QRgb c = riverImg.pixel(sx, riverImg.height() - 1 - sy); + if (qBlue(c) > 120 && qRed(c) < 100) { + hasRiver = true; + } + } + } + } + + if (hasRiver) { + tiles->operator()(x, y).addRiverEdge(e); + int nx, ny; + getHexNeighbor(x, y, e, nx, ny); + if (nx >= 0 && nx < MAP_WIDTH && ny >= 0 && ny < MAP_HEIGHT) { + tiles->operator()(nx, ny).addRiverEdge((e + 3) % 6); + } + } + } + } + } } Tile * QSceneRenderer::getTile(int x, int y) { - return &tiles->operator ()(x,y); + return &tiles->operator()(x, y); +} + +QRect QSceneRenderer::getMinimapRect() const +{ + int maxW = std::min(480, std::max(200, width() / 3)); + int mapW = maxW; + int mapH = (int) round(mapW / (225.0f / 108.22f)); + if (mapH > 500) mapH = 500; + if (mapW > 500) mapW = 500; + int margin = 16; + return QRect(margin, height() - mapH - margin - 22, mapW, mapH); +} + +QRect QSceneRenderer::getMinimapCardRect() const +{ + QRect m = getMinimapRect(); + return m.adjusted(-6, -6, 6, 24); +} + +QRect QSceneRenderer::getZoomOutButtonRect() const +{ + QRect card = getMinimapCardRect(); + return QRect(card.left() + 4, card.top() - 28, 30, 22); +} + +QRect QSceneRenderer::getZoomResetButtonRect() const +{ + QRect card = getMinimapCardRect(); + return QRect(card.left() + 38, card.top() - 28, 48, 22); +} + +QRect QSceneRenderer::getZoomInButtonRect() const +{ + QRect card = getMinimapCardRect(); + return QRect(card.left() + 90, card.top() - 28, 30, 22); +} + +QRect QSceneRenderer::getMinimapSourceRect() const +{ + if (m_image_minimap.isNull()) return QRect(); + int imgW = m_image_minimap.width(); + int imgH = m_image_minimap.height(); + if (m_minimapZoom <= 1.01f) { + return QRect(0, 0, imgW, imgH); + } + + int srcW = (int) round(imgW / m_minimapZoom); + int srcH = (int) round(imgH / m_minimapZoom); + + float u = std::max(0.0f, std::min(1.0f, cameraPosition.x() / 225.0f)); + float v = std::max(0.0f, std::min(1.0f, 1.0f - (cameraPosition.y() / 108.22f))); + + int camX = (int) round(u * imgW); + int camY = (int) round(v * imgH); + + int srcX = camX - srcW / 2; + int srcY = camY - srcH / 2; + + if (srcX < 0) srcX = 0; + if (srcY < 0) srcY = 0; + if (srcX + srcW > imgW) srcX = imgW - srcW; + if (srcY + srcH > imgH) srcY = imgH - srcH; + + return QRect(srcX, srcY, srcW, srcH); +} + +QPointF QSceneRenderer::worldToMinimap(float wx, float wy, const QRect &mapRect) const +{ + QRect src = getMinimapSourceRect(); + if (src.isEmpty() || m_image_minimap.isNull() || m_minimapZoom <= 1.01f) { + float u = std::max(0.0f, std::min(1.0f, wx / 225.0f)); + float v = std::max(0.0f, std::min(1.0f, 1.0f - (wy / 108.22f))); + return QPointF(mapRect.left() + u * mapRect.width(), mapRect.top() + v * mapRect.height()); + } + + float imgW = m_image_minimap.width(); + float imgH = m_image_minimap.height(); + + float uMin = (float)src.left() / imgW; + float uMax = (float)src.right() / imgW; + float vMin = (float)src.top() / imgH; + float vMax = (float)src.bottom() / imgH; + + float u = wx / 225.0f; + float v = 1.0f - (wy / 108.22f); + + float relU = (u - uMin) / (uMax - uMin); + float relV = (v - vMin) / (vMax - vMin); + + return QPointF(mapRect.left() + relU * mapRect.width(), mapRect.top() + relV * mapRect.height()); +} + +QVector2D QSceneRenderer::minimapToWorld(const QPoint &pos, const QRect &mapRect) const +{ + QRect src = getMinimapSourceRect(); + float relU = std::max(0.0f, std::min(1.0f, (float)(pos.x() - mapRect.left()) / mapRect.width())); + float relV = std::max(0.0f, std::min(1.0f, (float)(pos.y() - mapRect.top()) / mapRect.height())); + + if (src.isEmpty() || m_image_minimap.isNull() || m_minimapZoom <= 1.01f) { + float wx = relU * 225.0f; + float wy = (1.0f - relV) * 108.22f; + return QVector2D(wx, wy); + } + + float imgW = m_image_minimap.width(); + float imgH = m_image_minimap.height(); + + float uMin = (float)src.left() / imgW; + float uMax = (float)src.right() / imgW; + float vMin = (float)src.top() / imgH; + float vMax = (float)src.bottom() / imgH; + + float u = uMin + relU * (uMax - uMin); + float v = vMin + relV * (vMax - vMin); + + float wx = std::max(0.0f, std::min(225.0f, u * 225.0f)); + float wy = std::max(0.0f, std::min(108.22f, (1.0f - v) * 108.22f)); + return QVector2D(wx, wy); +} + +void QSceneRenderer::drawMinimap(QPainter &painter) +{ + QRect mapRect = getMinimapRect(); + if (mapRect.width() <= 10 || mapRect.height() <= 10) return; + + painter.setRenderHint(QPainter::Antialiasing, true); + + // 1. Draw Zoom Buttons above card + QRect btnOut = getZoomOutButtonRect(); + QRect btnReset = getZoomResetButtonRect(); + QRect btnIn = getZoomInButtonRect(); + + // Zoom Out [-] + painter.setBrush(QColor(22, 28, 40, 230)); + painter.setPen(QPen(QColor(80, 110, 150, 200), 1.2)); + painter.drawRoundedRect(btnOut, 4, 4); + painter.setFont(QFont("sans-serif", 10, QFont::Bold)); + painter.setPen(QColor(220, 230, 245)); + painter.drawText(btnOut, Qt::AlignCenter, "-"); + + // Zoom Indicator / Reset [Full / 2.0x] + painter.setBrush(QColor(22, 28, 40, 230)); + painter.setPen(QPen(QColor(80, 110, 150, 200), 1.2)); + painter.drawRoundedRect(btnReset, 4, 4); + painter.setFont(QFont("sans-serif", 8, QFont::Bold)); + painter.setPen(m_minimapZoom > 1.01f ? QColor(255, 220, 80) : QColor(200, 215, 235)); + QString zoomText = m_minimapZoom <= 1.01f ? "Full" : QString("%1x").arg(m_minimapZoom, 0, 'f', 1); + painter.drawText(btnReset, Qt::AlignCenter, zoomText); + + // Zoom In [+] + painter.setBrush(QColor(22, 28, 40, 230)); + painter.setPen(QPen(QColor(80, 110, 150, 200), 1.2)); + painter.drawRoundedRect(btnIn, 4, 4); + painter.setFont(QFont("sans-serif", 10, QFont::Bold)); + painter.setPen(QColor(220, 230, 245)); + painter.drawText(btnIn, Qt::AlignCenter, "+"); + + // Title label next to zoom buttons: + painter.setFont(QFont("sans-serif", 8, QFont::Bold)); + painter.setPen(QColor(160, 195, 230)); + QRect titleRect(btnIn.right() + 8, btnIn.top(), 180, btnIn.height()); + painter.drawText(titleRect, Qt::AlignLeft | Qt::AlignVCenter, "HEX MINIMAP"); + + // 2. Semi-transparent dark background card + QRect cardRect = getMinimapCardRect(); + QColor cardBg(20, 24, 32, 225); + painter.setBrush(cardBg); + painter.setPen(QPen(QColor(80, 110, 150, 180), 1.5)); + painter.drawRoundedRect(cardRect, 8, 8); + + // 3. Draw map image inside mapRect (with source crop for zoom) + QRect srcRect = getMinimapSourceRect(); + if (!m_image_minimap.isNull()) { + painter.drawImage(mapRect, m_image_minimap, srcRect); + } else { + painter.fillRect(mapRect, QColor(30, 40, 60)); + } + + // Border around map image + painter.setBrush(Qt::NoBrush); + painter.setPen(QPen(QColor(120, 150, 180, 160), 1.0)); + painter.drawRect(mapRect); + + // Draw unit dots on minimap + for (const auto &u : GameManager::instance().getUnits()) { + if (u->isDead()) continue; + QPointF p = worldToMinimap(u->worldPos.x(), u->worldPos.y(), mapRect); + if (mapRect.contains(p.toPoint())) { + const Player *pl = GameManager::instance().players().getPlayer(u->playerId); + QColor dotCol = pl ? pl->primaryColor : Qt::yellow; + painter.setBrush(dotCol); + painter.setPen(QPen(Qt::white, 1.0)); + painter.drawEllipse(p, 3.0, 3.0); + } + } + + // 4. Camera view representation (clipped to mapRect): + QPointF camCenter = worldToMinimap(cameraPosition.x(), cameraPosition.y(), mapRect); + + float rad = zRot * PI / 180.0f; + float fovHalf = 25.0f * PI / 180.0f; + float viewDist = std::max(12.0f, m_zoom * 2.2f); + + float lx = cameraPosition.x() + viewDist * cos(rad - fovHalf); + float ly = cameraPosition.y() + viewDist * sin(rad - fovHalf); + float rx = cameraPosition.x() + viewDist * cos(rad + fovHalf); + float ry = cameraPosition.y() + viewDist * sin(rad + fovHalf); + float cx = cameraPosition.x() + viewDist * cos(rad); + float cy = cameraPosition.y() + viewDist * sin(rad); + + QPointF sLeft = worldToMinimap(lx, ly, mapRect); + QPointF sRight = worldToMinimap(rx, ry, mapRect); + QPointF sCenter = worldToMinimap(cx, cy, mapRect); + + painter.save(); + painter.setClipRect(mapRect); + + QPolygonF cone; + cone << camCenter << sLeft << sCenter << sRight; + + painter.setBrush(QColor(255, 230, 50, 60)); + painter.setPen(QPen(QColor(255, 235, 70, 200), 1.5)); + painter.drawPolygon(cone); + + painter.setPen(QPen(QColor(255, 240, 100, 180), 1.0, Qt::DashLine)); + painter.drawLine(camCenter, sCenter); + + painter.setBrush(QColor(0, 0, 0, 140)); + painter.setPen(Qt::NoPen); + painter.drawEllipse(camCenter, 5.0, 5.0); + + painter.setBrush(QColor(255, 50, 50)); + painter.setPen(QPen(Qt::white, 1.5)); + painter.drawEllipse(camCenter, 3.5, 3.5); + + painter.restore(); + + // 5. North Compass Indicator + QPoint compassCenter(mapRect.right() - 16, mapRect.top() + 16); + painter.setBrush(QColor(15, 20, 30, 180)); + painter.setPen(QPen(QColor(100, 140, 180, 200), 1.0)); + painter.drawEllipse(compassCenter, 10, 10); + painter.setPen(QPen(QColor(240, 50, 50), 2.0)); + painter.drawLine(compassCenter, QPoint(compassCenter.x(), compassCenter.y() - 7)); + painter.setFont(QFont("sans-serif", 7, QFont::Bold)); + painter.setPen(QColor(240, 60, 60)); + painter.drawText(QRect(compassCenter.x() - 6, compassCenter.y() - 18, 12, 10), Qt::AlignCenter, "N"); + + // 6. Coordinates bar + QRect bottomBar(cardRect.left() + 6, mapRect.bottom() + 4, cardRect.width() - 12, 16); + painter.setFont(QFont("sans-serif", 8)); + painter.setPen(QColor(180, 200, 220)); + QString info = QString("Cam: %1, %2 km | Alt: %3 km") + .arg(cameraPosition.x(), 0, 'f', 1) + .arg(cameraPosition.y(), 0, 'f', 1) + .arg(m_zoom, 0, 'f', 1); + painter.drawText(bottomBar, Qt::AlignLeft | Qt::AlignVCenter, info); +} + +QImage QSceneRenderer::getTintedIcon(const QString &path, const QColor &color) +{ + QString cacheKey = path + "_" + color.name(); + if (m_tintedIconCache.contains(cacheKey)) { + return m_tintedIconCache[cacheKey]; + } + + if (!m_iconCache.contains(path)) { + QString fullPath = path.startsWith("/") ? path : ("/home/bart/project/tiletest/" + path); + QImage loaded(fullPath); + if (loaded.isNull()) { + loaded = QImage(32, 32, QImage::Format_ARGB32); + loaded.fill(Qt::transparent); + QPainter p(&loaded); + p.setRenderHint(QPainter::Antialiasing, true); + p.setBrush(Qt::white); + p.setPen(Qt::NoPen); + p.drawEllipse(6, 6, 20, 20); + } + m_iconCache[path] = loaded; + } + + QImage base = m_iconCache[path]; + QImage tinted(base.size(), QImage::Format_ARGB32); + tinted.fill(Qt::transparent); + + QPainter p(&tinted); + p.drawImage(0, 0, base); + p.setCompositionMode(QPainter::CompositionMode_SourceIn); + p.fillRect(tinted.rect(), color); + p.end(); + + m_tintedIconCache[cacheKey] = tinted; + return tinted; +} + +bool QSceneRenderer::screenToHex(const QPoint &screenPos, const QMatrix4x4 &vMatrix, int &outX, int &outY) +{ + float ndcX = (2.0f * screenPos.x()) / width() - 1.0f; + float ndcY = 1.0f - (2.0f * screenPos.y()) / height(); + + QMatrix4x4 invVP = (Projection * vMatrix).inverted(); + QVector4D pNear = invVP.map(QVector4D(ndcX, ndcY, -1.0f, 1.0f)); + QVector4D pFar = invVP.map(QVector4D(ndcX, ndcY, 1.0f, 1.0f)); + + if (std::abs(pNear.w()) < 1e-6f || std::abs(pFar.w()) < 1e-6f) return false; + + QVector3D rayStart = pNear.toVector3DAffine(); + QVector3D rayEnd = pFar.toVector3DAffine(); + QVector3D rayDir = (rayEnd - rayStart).normalized(); + + if (rayDir.z() >= -0.001f) return false; + + float t = (2.0f - rayStart.z()) / rayDir.z(); + if (t < 0.0f) return false; + + QVector3D hit = rayStart + t * rayDir; + for (int iter = 0; iter < 4; ++iter) { + float h = Unit::getTerrainHeight(hit.x(), hit.y(), m_point, m_numPoints05); + float dt = (h - hit.z()) / rayDir.z(); + hit += dt * rayDir; + } + + Pathfinder::worldToHex(hit.x(), hit.y(), outX, outY); + return (outX >= 0 && outX < MAP_WIDTH && outY >= 0 && outY < MAP_HEIGHT); +} + +void QSceneRenderer::drawTickBar(QPainter &painter) +{ + painter.setRenderHint(QPainter::Antialiasing, true); + + int barW = 340, barH = 34; + QRect barRect(16, 16, barW, barH); + + // Dark glass card + painter.setBrush(QColor(18, 24, 34, 230)); + painter.setPen(QPen(QColor(70, 100, 140, 180), 1.2)); + painter.drawRoundedRect(barRect, 6, 6); + + // Movement Tick Info + float tickDur = GameManager::instance().getTickDuration(); + float curTick = GameManager::instance().getTickTimer(); + float progress = curTick / tickDur; + + painter.setFont(QFont("sans-serif", 8, QFont::Bold)); + painter.setPen(QColor(210, 225, 245)); + QString tickLabel = QString("Tick: %1s / %2s").arg(curTick, 0, 'f', 1).arg(tickDur, 0, 'f', 0); + painter.drawText(QRect(barRect.left() + 10, barRect.top() + 4, 120, 14), Qt::AlignLeft | Qt::AlignVCenter, tickLabel); + + // Progress bar + QRect progBg(barRect.left() + 10, barRect.top() + 20, 140, 6); + painter.setBrush(QColor(30, 40, 55)); + painter.setPen(Qt::NoPen); + painter.drawRoundedRect(progBg, 3, 3); + + QRect progFg(barRect.left() + 10, barRect.top() + 20, (int)(140 * progress), 6); + painter.setBrush(QColor(60, 180, 255)); + painter.drawRoundedRect(progFg, 3, 3); + + // Sim Speed Buttons [1x] [2x] [5x] + float curSpeed = GameManager::instance().getSimSpeed(); + m_speed1xRect = QRect(barRect.right() - 150, barRect.top() + 6, 42, 22); + m_speed2xRect = QRect(barRect.right() - 102, barRect.top() + 6, 42, 22); + m_speed5xRect = QRect(barRect.right() - 54, barRect.top() + 6, 42, 22); + + auto drawSpeedBtn = [&](const QRect &r, const QString &text, bool active) { + painter.setBrush(active ? QColor(40, 70, 110, 240) : QColor(25, 32, 45, 220)); + painter.setPen(QPen(active ? QColor(255, 215, 60) : QColor(80, 110, 150), active ? 1.5 : 1.0)); + painter.drawRoundedRect(r, 4, 4); + painter.setFont(QFont("sans-serif", 8, QFont::Bold)); + painter.setPen(active ? QColor(255, 225, 80) : QColor(200, 215, 235)); + painter.drawText(r, Qt::AlignCenter, text); + }; + + drawSpeedBtn(m_speed1xRect, "1x", std::abs(curSpeed - 1.0f) < 0.1f); + drawSpeedBtn(m_speed2xRect, "2x", std::abs(curSpeed - 2.0f) < 0.1f); + drawSpeedBtn(m_speed5xRect, "5x", std::abs(curSpeed - 5.0f) < 0.1f); +} + +void QSceneRenderer::drawUnitsOverlay(QPainter &painter, const QMatrix4x4 &vMatrix) +{ + m_activeActionBubbles.clear(); + painter.setRenderHint(QPainter::Antialiasing, true); + + Unit *selectedUnit = GameManager::instance().getSelectedUnit(); + + // 1. Draw City Banners + for (const auto &c : GameManager::instance().getCities()) { + bool inFront = false; + QPointF screenP = UnitRenderer::projectToScreen(c.worldPos + QVector3D(0, 0, 0.65f), Projection, vMatrix, width(), height(), inFront); + if (!inFront) continue; + + const Player *pl = GameManager::instance().players().getPlayer(c.playerId); + QColor col = pl ? pl->primaryColor : Qt::blue; + + QRect bannerRect((int)screenP.x() - 40, (int)screenP.y() - 14, 80, 20); + painter.setBrush(QColor(20, 24, 34, 230)); + painter.setPen(QPen(col, 1.8)); + painter.drawRoundedRect(bannerRect, 4, 4); + + painter.setFont(QFont("sans-serif", 8, QFont::Bold)); + painter.setPen(Qt::white); + painter.drawText(bannerRect, Qt::AlignCenter, c.name); + } + + // 2. Draw Units: Teardrop (Druppel in secondary color) + Icon (in primary color) + const float R = 14.0f; // Head circle radius + for (const auto &unit : GameManager::instance().getUnits()) { + if (unit->isDead()) continue; + + bool inFront = false; + QVector3D topCylinder = unit->worldPos + QVector3D(0.0f, 0.0f, 0.38f); + QPointF tipPos = UnitRenderer::projectToScreen(topCylinder, Projection, vMatrix, width(), height(), inFront); + if (!inFront) continue; + + if (tipPos.x() < -60 || tipPos.x() > width() + 60 || tipPos.y() < -60 || tipPos.y() > height() + 60) { + continue; + } + + const Player *player = GameManager::instance().players().getPlayer(unit->playerId); + QColor primaryCol = player ? player->primaryColor : Qt::blue; + QColor secondaryCol = player ? player->secondaryColor : Qt::white; + + QPointF headCenter(tipPos.x(), tipPos.y() - 25.0f); + + // Teardrop path (pointing down to top of cylinder) + QPainterPath teardrop; + teardrop.moveTo(tipPos); + teardrop.lineTo(headCenter.x() - R * 0.88f, headCenter.y() + R * 0.45f); + teardrop.arcTo(headCenter.x() - R, headCenter.y() - R, R * 2.0f, R * 2.0f, 205.0f, -230.0f); + teardrop.lineTo(tipPos); + teardrop.closeSubpath(); + + // Drop shadow + painter.save(); + painter.translate(1.0, 1.5); + painter.fillPath(teardrop, QColor(0, 0, 0, 70)); + painter.restore(); + + // Fill teardrop with secondary color + painter.fillPath(teardrop, secondaryCol); + + // Border of teardrop + painter.setPen(QPen(QColor(25, 30, 40, 220), 1.6)); + painter.drawPath(teardrop); + + // Inside the druppel: Draw unit icon tinted in player primary color + QImage icon = getTintedIcon(unit->config.iconPath, primaryCol); + QRectF iconRect(headCenter.x() - 9.0f, headCenter.y() - 9.0f, 18.0f, 18.0f); + painter.drawImage(iconRect, icon); + + // Health bar above head + float hpFrac = std::max(0.0f, std::min(1.0f, unit->health / unit->maxHealth)); + QRectF hpBg(headCenter.x() - 14.0f, headCenter.y() - R - 6.0f, 28.0f, 3.5f); + painter.setBrush(QColor(30, 30, 30, 200)); + painter.setPen(Qt::NoPen); + painter.drawRoundedRect(hpBg, 1.5, 1.5); + + QRectF hpFg(headCenter.x() - 14.0f, headCenter.y() - R - 6.0f, 28.0f * hpFrac, 3.5f); + painter.setBrush(hpFrac > 0.5f ? QColor(70, 220, 90) : (hpFrac > 0.25f ? QColor(255, 190, 40) : QColor(240, 50, 50))); + painter.drawRoundedRect(hpFg, 1.5, 1.5); + + // If unit is selected: pulsing selection halo & floating action bubbles + if (unit.get() == selectedUnit) { + float pulse = (std::sin(m_elapsedTimer.elapsed() * 0.008f) + 1.0f) * 0.5f; + painter.setPen(QPen(QColor(255, 220, 40, (int)(180 + pulse * 75)), 2.5)); + painter.setBrush(Qt::NoBrush); + painter.drawEllipse(headCenter, R + 3.0f, R + 3.0f); + + // Floating Action Bubbles above the unit's druppel + const QStringList &actions = unit->config.actions; + int numActions = actions.size(); + float bubbleR = 12.0f; + float spacing = 28.0f; + float startX = headCenter.x() - ((numActions - 1) * spacing) / 2.0f; + float bubbleY = headCenter.y() - R - 20.0f; + + for (int a = 0; a < numActions; ++a) { + QString act = actions[a]; + QPointF bCenter(startX + a * spacing, bubbleY); + QRect bRect((int)(bCenter.x() - bubbleR), (int)(bCenter.y() - bubbleR), (int)(bubbleR * 2), (int)(bubbleR * 2)); + + bool isActive = false; + QString iconFile = "images/actions/move.png"; + QString tip = "Verplaatsen (M)"; + if (act == "verplaatsen") { + isActive = (GameManager::instance().currentActionMode() == ActionMode::Move); + iconFile = "images/actions/move.png"; + tip = "Verplaatsen (M)"; + } else if (act == "aanvallen") { + isActive = (GameManager::instance().currentActionMode() == ActionMode::Attack); + iconFile = "images/actions/attack.png"; + tip = QString("Aanvallen (A) [Bereik: %1]").arg(unit->config.attackRange); + } else if (act == "steden_bouwen" || act == "build_city") { + isActive = (GameManager::instance().currentActionMode() == ActionMode::BuildCity); + iconFile = "images/actions/build_city.png"; + tip = "Steden bouwen (B)"; + } + + // Draw action bubble + painter.setBrush(isActive ? QColor(40, 80, 140, 240) : QColor(20, 26, 38, 230)); + painter.setPen(QPen(isActive ? QColor(255, 220, 50) : secondaryCol, isActive ? 2.0 : 1.2)); + painter.drawEllipse(bCenter, bubbleR, bubbleR); + + // Action icon inside bubble + QImage aIcon = getTintedIcon(iconFile, isActive ? QColor(255, 230, 80) : primaryCol); + painter.drawImage(QRectF(bCenter.x() - 7, bCenter.y() - 7, 14, 14), aIcon); + + // Register for click detection + m_activeActionBubbles.push_back({bRect, act, tip}); + + // Tooltip on hover + if (bRect.contains(m_mouseCurrentPos)) { + QRect tipRect((int)bCenter.x() - 55, (int)bCenter.y() - 26, 110, 18); + painter.setBrush(QColor(15, 20, 30, 240)); + painter.setPen(QPen(QColor(120, 160, 210), 1.0)); + painter.drawRoundedRect(tipRect, 3, 3); + painter.setFont(QFont("sans-serif", 7, QFont::Bold)); + painter.setPen(Qt::white); + painter.drawText(tipRect, Qt::AlignCenter, tip); + } + } + } + } +} + +void QSceneRenderer::drawPathAndRange(QPainter &painter, Unit *unit, const QMatrix4x4 &vMatrix) +{ + if (!unit) return; + painter.setRenderHint(QPainter::Antialiasing, true); + + // 1. If unit is moving or has a path: draw path line and destination reticle + if (unit->path.size() > 1) { + QPainterPath pathLine; + bool first = true; + for (const auto &p : unit->path) { + QVector2D w = Pathfinder::hexToWorld(p.first, p.second); + float z = Unit::getTerrainHeight(w.x(), w.y(), m_point, m_numPoints05); + bool inFront = false; + QPointF sPos = UnitRenderer::projectToScreen(QVector3D(w.x(), w.y(), z + 0.05f), Projection, vMatrix, width(), height(), inFront); + if (inFront) { + if (first) { + pathLine.moveTo(sPos); + first = false; + } else { + pathLine.lineTo(sPos); + } + } + } + painter.setPen(QPen(QColor(80, 220, 255, 200), 2.2, Qt::DashLine)); + painter.setBrush(Qt::NoBrush); + painter.drawPath(pathLine); + + // Destination target reticle + const auto &lastHex = unit->path.back(); + QVector2D destW = Pathfinder::hexToWorld(lastHex.first, lastHex.second); + float destZ = Unit::getTerrainHeight(destW.x(), destW.y(), m_point, m_numPoints05); + bool inFront = false; + QPointF sDest = UnitRenderer::projectToScreen(QVector3D(destW.x(), destW.y(), destZ + 0.05f), Projection, vMatrix, width(), height(), inFront); + if (inFront) { + painter.setPen(QPen(QColor(255, 220, 60), 2.0)); + painter.drawEllipse(sDest, 10.0, 10.0); + painter.drawLine(sDest.x() - 14, sDest.y(), sDest.x() + 14, sDest.y()); + painter.drawLine(sDest.x(), sDest.y() - 14, sDest.x(), sDest.y() + 14); + } + } + + // 2. Attack range visualization if Attack mode is active + if (GameManager::instance().currentActionMode() == ActionMode::Attack) { + int range = unit->config.attackRange; + for (const auto &u : GameManager::instance().getUnits()) { + if (u->isDead() || u->playerId == unit->playerId) continue; + + int d = Pathfinder::hexDistance(unit->tileX, unit->tileY, u->tileX, u->tileY); + if (d <= range) { + // Target reticle over enemy unit + bool inFront = false; + QPointF sTarget = UnitRenderer::projectToScreen(u->worldPos + QVector3D(0, 0, 0.4f), Projection, vMatrix, width(), height(), inFront); + if (inFront) { + float pulse = (std::sin(m_elapsedTimer.elapsed() * 0.01f) + 1.0f) * 0.5f; + painter.setPen(QPen(QColor(255, 50, 50, (int)(180 + pulse * 75)), 2.2)); + painter.setBrush(QColor(255, 40, 40, 40)); + painter.drawEllipse(sTarget, 16.0f, 16.0f); + painter.drawLine(sTarget.x() - 20, sTarget.y(), sTarget.x() + 20, sTarget.y()); + painter.drawLine(sTarget.x(), sTarget.y() - 20, sTarget.x(), sTarget.y() + 20); + } + } + } + } +} + +void QSceneRenderer::drawFloatingTexts(QPainter &painter, const QMatrix4x4 &vMatrix) +{ + painter.setRenderHint(QPainter::Antialiasing, true); + for (const auto &ft : GameManager::instance().getFloatingTexts()) { + bool inFront = false; + QPointF sPos = UnitRenderer::projectToScreen(ft.worldPos, Projection, vMatrix, width(), height(), inFront); + if (!inFront) continue; + + float alpha = std::min(1.0f, ft.life / (ft.maxLife * 0.5f)); + QColor col = ft.color; + col.setAlphaF(alpha); + + painter.setFont(QFont("sans-serif", 10, QFont::Bold)); + painter.setPen(QPen(QColor(0, 0, 0, (int)(alpha * 200)), 3.0)); + // Stroke + QRect textRect((int)sPos.x() - 80, (int)sPos.y() - 12, 160, 24); + painter.drawText(textRect, Qt::AlignCenter, ft.text); + + // Fill + painter.setPen(col); + painter.drawText(textRect, Qt::AlignCenter, ft.text); + } +} + +void QSceneRenderer::drawUnitCard(QPainter &painter, Unit *unit) +{ + if (!unit) return; + painter.setRenderHint(QPainter::Antialiasing, true); + + int cardW = 310, cardH = 180; + QRect cardRect(width() - cardW - 16, height() - cardH - 16, cardW, cardH); + m_unitCardRect = cardRect; + + // Dark glass card + painter.setBrush(QColor(18, 24, 34, 235)); + painter.setPen(QPen(QColor(80, 115, 160, 200), 1.5)); + painter.drawRoundedRect(cardRect, 8, 8); + + const Player *player = GameManager::instance().players().getPlayer(unit->playerId); + QColor primaryCol = player ? player->primaryColor : Qt::blue; + QColor secondaryCol = player ? player->secondaryColor : Qt::white; + + // Header badge + QRect badgeRect(cardRect.left() + 12, cardRect.top() + 12, 22, 22); + painter.setBrush(secondaryCol); + painter.setPen(QPen(QColor(30, 30, 30), 1.2)); + painter.drawEllipse(badgeRect); + QImage uIcon = getTintedIcon(unit->config.iconPath, primaryCol); + painter.drawImage(QRect(badgeRect.left() + 3, badgeRect.top() + 3, 16, 16), uIcon); + + // Unit Name & Player + painter.setFont(QFont("sans-serif", 11, QFont::Bold)); + painter.setPen(QColor(240, 245, 255)); + painter.drawText(QRect(badgeRect.right() + 10, cardRect.top() + 10, 160, 18), Qt::AlignLeft | Qt::AlignVCenter, unit->config.name.toUpper()); + + painter.setFont(QFont("sans-serif", 8)); + painter.setPen(QColor(160, 185, 215)); + QString playerInfo = QString("%1 | Hex: (%2, %3)").arg(player ? player->name : "Onbekend").arg(unit->tileX).arg(unit->tileY); + painter.drawText(QRect(badgeRect.right() + 10, cardRect.top() + 28, 200, 14), Qt::AlignLeft | Qt::AlignVCenter, playerInfo); + + // Category Pill + QString catStr = unitCategoryToString(unit->config.category); + if (unit->config.isRanged()) catStr += " (Ranged)"; + QRect catRect(cardRect.right() - 110, cardRect.top() + 12, 98, 18); + painter.setBrush(QColor(35, 48, 68, 200)); + painter.setPen(QPen(QColor(100, 140, 190), 1.0)); + painter.drawRoundedRect(catRect, 4, 4); + painter.setFont(QFont("sans-serif", 7, QFont::Bold)); + painter.setPen(QColor(200, 225, 255)); + painter.drawText(catRect, Qt::AlignCenter, catStr); + + // Separator line + painter.setPen(QPen(QColor(60, 85, 120, 150), 1.0)); + painter.drawLine(cardRect.left() + 12, cardRect.top() + 48, cardRect.right() - 12, cardRect.top() + 48); + + // Health Bar + float hpFrac = std::max(0.0f, std::min(1.0f, unit->health / unit->maxHealth)); + QRect hpBg(cardRect.left() + 12, cardRect.top() + 56, 170, 14); + painter.setBrush(QColor(30, 40, 55)); + painter.setPen(Qt::NoPen); + painter.drawRoundedRect(hpBg, 3, 3); + + QRect hpFg(cardRect.left() + 12, cardRect.top() + 56, (int)(170 * hpFrac), 14); + painter.setBrush(hpFrac > 0.5f ? QColor(60, 200, 80) : (hpFrac > 0.25f ? QColor(255, 180, 40) : QColor(240, 50, 50))); + painter.drawRoundedRect(hpFg, 3, 3); + + painter.setFont(QFont("sans-serif", 8, QFont::Bold)); + painter.setPen(Qt::white); + QString hpText = QString("HP: %1 / %2").arg((int)unit->health).arg((int)unit->maxHealth); + painter.drawText(hpBg, Qt::AlignCenter, hpText); + + // Stats Grid + painter.setFont(QFont("sans-serif", 8)); + painter.setPen(QColor(200, 220, 240)); + + QString speedInfo = QString("Snelheid: %1 hex/tick").arg(unit->config.movementSpeed, 0, 'f', 1); + painter.drawText(QRect(cardRect.left() + 12, cardRect.top() + 76, 140, 14), Qt::AlignLeft | Qt::AlignVCenter, speedInfo); + + QString costInfo = QString("Kosten: %1 pnt").arg(unit->config.cost); + painter.drawText(QRect(cardRect.left() + 160, cardRect.top() + 76, 130, 14), Qt::AlignLeft | Qt::AlignVCenter, costInfo); + + if (unit->config.isMilitary()) { + QString combatInfo = QString("Aanval: %1 | Bereik: %2 hex").arg(unit->config.attackDamage).arg(unit->config.attackRange); + painter.drawText(QRect(cardRect.left() + 12, cardRect.top() + 94, 280, 14), Qt::AlignLeft | Qt::AlignVCenter, combatInfo); + } else { + painter.drawText(QRect(cardRect.left() + 12, cardRect.top() + 94, 280, 14), Qt::AlignLeft | Qt::AlignVCenter, "Civiele eenheid"); + } + + // Action Bubbles Row at bottom of card + QRect actionArea(cardRect.left() + 12, cardRect.top() + 116, cardRect.width() - 24, 52); + painter.setBrush(QColor(12, 16, 24, 180)); + painter.setPen(QPen(QColor(50, 75, 105, 120), 1.0)); + painter.drawRoundedRect(actionArea, 6, 6); + + const QStringList &actions = unit->config.actions; + int numActions = actions.size(); + int btnW = (actionArea.width() - 8 * (numActions + 1)) / std::max(1, numActions); + + for (int a = 0; a < numActions; ++a) { + QString act = actions[a]; + QRect bRect(actionArea.left() + 8 + a * (btnW + 8), actionArea.top() + 6, btnW, 40); + + bool isActive = false; + QString iconFile = "images/actions/move.png"; + QString label = "Verplaats"; + QString tip = "Verplaatsen (M)"; + + if (act == "verplaatsen") { + isActive = (GameManager::instance().currentActionMode() == ActionMode::Move); + iconFile = "images/actions/move.png"; + label = "Beweeg [M]"; + tip = "Verplaatsen (M)"; + } else if (act == "aanvallen") { + isActive = (GameManager::instance().currentActionMode() == ActionMode::Attack); + iconFile = "images/actions/attack.png"; + label = "Aanval [A]"; + tip = QString("Aanvallen (A) [Bereik: %1]").arg(unit->config.attackRange); + } else if (act == "steden_bouwen" || act == "build_city") { + isActive = (GameManager::instance().currentActionMode() == ActionMode::BuildCity); + iconFile = "images/actions/build_city.png"; + label = "Sticht [B]"; + tip = "Steden bouwen (B)"; + } + + // Circular bubble button + painter.setBrush(isActive ? QColor(45, 85, 145, 250) : QColor(24, 32, 46, 220)); + painter.setPen(QPen(isActive ? QColor(255, 220, 50) : QColor(80, 110, 150), isActive ? 1.8 : 1.0)); + painter.drawRoundedRect(bRect, 6, 6); + + // Icon inside button + QImage aIcon = getTintedIcon(iconFile, isActive ? QColor(255, 230, 80) : primaryCol); + painter.drawImage(QRect(bRect.left() + 8, bRect.top() + 10, 20, 20), aIcon); + + // Label + painter.setFont(QFont("sans-serif", 7, QFont::Bold)); + painter.setPen(isActive ? QColor(255, 235, 90) : QColor(210, 225, 245)); + painter.drawText(QRect(bRect.left() + 32, bRect.top(), bRect.width() - 36, bRect.height()), Qt::AlignLeft | Qt::AlignVCenter, label); + + m_activeActionBubbles.push_back({bRect, act, tip}); + } } void QSceneRenderer::mousePressEvent(QMouseEvent *event) { - lastPos = event->pos(); + // 1. Zoom Out / Reset / In buttons + if (getZoomOutButtonRect().contains(event->pos())) { + m_minimapZoom = std::max(1.0f, m_minimapZoom / 1.5f); + update(); + return; + } + if (getZoomResetButtonRect().contains(event->pos())) { + m_minimapZoom = 1.0f; + update(); + return; + } + if (getZoomInButtonRect().contains(event->pos())) { + m_minimapZoom = std::min(8.0f, m_minimapZoom * 1.5f); + update(); + return; + } + + // 2. Minimap click & drag + QRect mapRect = getMinimapRect(); + if (mapRect.contains(event->pos())) { + m_draggingMinimap = true; + QVector2D wPos = minimapToWorld(event->pos(), mapRect); + cameraPosition.setX(wPos.x()); + cameraPosition.setY(wPos.y()); + update(); + return; + } + + // 3. Sim Speed Buttons [1x] [2x] [5x] + if (m_speed1xRect.contains(event->pos())) { + GameManager::instance().setSimSpeed(1.0f); + update(); + return; + } + if (m_speed2xRect.contains(event->pos())) { + GameManager::instance().setSimSpeed(2.0f); + update(); + return; + } + if (m_speed5xRect.contains(event->pos())) { + GameManager::instance().setSimSpeed(5.0f); + update(); + return; + } + + // 4. Action Bubbles clicked + for (const auto &ab : m_activeActionBubbles) { + if (ab.rect.contains(event->pos())) { + if (ab.actionId == "verplaatsen") { + if (GameManager::instance().currentActionMode() == ActionMode::Move) { + GameManager::instance().setActionMode(ActionMode::None); + } else { + GameManager::instance().setActionMode(ActionMode::Move); + } + } else if (ab.actionId == "aanvallen") { + if (GameManager::instance().currentActionMode() == ActionMode::Attack) { + GameManager::instance().setActionMode(ActionMode::None); + } else { + GameManager::instance().setActionMode(ActionMode::Attack); + } + } else if (ab.actionId == "steden_bouwen" || ab.actionId == "build_city") { + GameManager::instance().commandBuildCitySelected(m_point, m_numPoints05); + } + update(); + return; + } + } + + // Compute view matrix for raycasting and 3D screen tests + QMatrix4x4 vMatrix; + cameraUpDirection = cameraTransformation * QVector3D(0, 0, 1); + QVector3D lookat = QVector3D( + cameraPosition.x() + m_zoom * m_zoom / 5.0f * cos(PI * zRot / 180.0f), + cameraPosition.y() + m_zoom * m_zoom / 5.0f * sin(PI * zRot / 180.0f), + 0 + ); + vMatrix.lookAt(cameraPosition, lookat, cameraUpDirection); + + // 5. Left Click: Check if directly clicked on any unit's Druppel pin + if (event->button() == Qt::LeftButton) { + for (const auto &u : GameManager::instance().getUnits()) { + if (u->isDead()) continue; + + bool inFront = false; + QVector3D topPoint = u->worldPos + QVector3D(0, 0, 0.38f); + QPointF tipPos = UnitRenderer::projectToScreen(topPoint, Projection, vMatrix, width(), height(), inFront); + if (!inFront) continue; + + QPointF headCenter(tipPos.x(), tipPos.y() - 25.0f); + float distSq = (event->x() - headCenter.x()) * (event->x() - headCenter.x()) + + (event->y() - headCenter.y()) * (event->y() - headCenter.y()); + + if (distSq <= 18.0f * 18.0f) { + // If in attack mode and clicked enemy: attack! + Unit *sel = GameManager::instance().getSelectedUnit(); + if (sel && GameManager::instance().currentActionMode() == ActionMode::Attack && u->playerId != sel->playerId) { + GameManager::instance().commandAttack(u.get()); + } else { + GameManager::instance().selectUnit(u->id); + } + update(); + return; + } + } + } + + // 6. Terrain hex picking via Raycast + int hitX = -1, hitY = -1; + bool hitTerrain = screenToHex(event->pos(), vMatrix, hitX, hitY); + + if (hitTerrain) { + Unit *selected = GameManager::instance().getSelectedUnit(); + + if (event->button() == Qt::LeftButton) { + if (selected) { + if (GameManager::instance().currentActionMode() == ActionMode::Move) { + GameManager::instance().commandMoveSelected(hitX, hitY, m_point, m_numPoints05); + GameManager::instance().setActionMode(ActionMode::None); + update(); + return; + } else if (GameManager::instance().currentActionMode() == ActionMode::Attack) { + Unit *enemy = GameManager::instance().getUnitAt(hitX, hitY); + if (enemy && enemy->playerId != selected->playerId) { + GameManager::instance().commandAttack(enemy); + } + update(); + return; + } + } + + // Check if there is a unit on hit tile to select + Unit *unitOnTile = GameManager::instance().getUnitAt(hitX, hitY); + if (unitOnTile) { + GameManager::instance().selectUnit(unitOnTile->id); + } else if (!m_unitCardRect.contains(event->pos())) { + GameManager::instance().deselectUnit(); + } + update(); + return; + } else if (event->button() == Qt::RightButton) { + // Right click: Move selected unit directly to hex! + if (selected) { + GameManager::instance().commandMoveSelected(hitX, hitY, m_point, m_numPoints05); + update(); + return; + } + } + } + + lastPos = event->pos(); } void QSceneRenderer::mouseMoveEvent(QMouseEvent *event) { - /*int dx = event->x() - lastPos.x(); - int dy = event->y() - lastPos.y(); + m_mouseCurrentPos = event->pos(); - if (event->buttons() & Qt::LeftButton) { - zRot += 8 * dx; - } else if (event->buttons() & Qt::RightButton) { - zRot -= 8 * dx; + QRect mapRect = getMinimapRect(); + if (m_draggingMinimap && (event->buttons() & Qt::LeftButton)) { + QVector2D wPos = minimapToWorld(event->pos(), mapRect); + cameraPosition.setX(wPos.x()); + cameraPosition.setY(wPos.y()); + update(); + return; } - lastPos = event->pos(); - update();*/ + if (event->buttons() & Qt::RightButton) { + int dx = event->x() - lastPos.x(); + zRot -= dx * 0.5f; + if (zRot < 0.0f) zRot += 360.0f; + if (zRot >= 360.0f) zRot -= 360.0f; + update(); + } + lastPos = event->pos(); +} + +void QSceneRenderer::mouseReleaseEvent(QMouseEvent *event) +{ + Q_UNUSED(event); + m_draggingMinimap = false; } void QSceneRenderer::keyPressEvent(QKeyEvent *e) { - if (e->key()== Qt::Key_A) { - cameraPosition.setX(cameraPosition.x()+ 0.1f*cos(PI*(zRot+90)/180.0f)); - cameraPosition.setY(cameraPosition.y()+ 0.1f*sin(PI*(zRot+90)/180.0f)); + float speed = (e->modifiers() & Qt::ShiftModifier) ? 1.2f : 0.4f; + + // Movement & camera controls + if (e->key() == Qt::Key_A && !(e->modifiers() & Qt::ControlModifier)) { + cameraPosition.setX(cameraPosition.x() + speed * cos(PI * (zRot + 90) / 180.0f)); + cameraPosition.setY(cameraPosition.y() + speed * sin(PI * (zRot + 90) / 180.0f)); } - else if (e->key()== Qt::Key_D) { - cameraPosition.setX(cameraPosition.x()+ 0.1f*cos(PI*(zRot-90)/180.0f)); - cameraPosition.setY(cameraPosition.y()+ 0.1f*sin(PI*(zRot-90)/180.0f)); + else if (e->key() == Qt::Key_D) { + cameraPosition.setX(cameraPosition.x() + speed * cos(PI * (zRot - 90) / 180.0f)); + cameraPosition.setY(cameraPosition.y() + speed * sin(PI * (zRot - 90) / 180.0f)); + } + else if (e->key() == Qt::Key_W) { + cameraPosition.setX(cameraPosition.x() + speed * cos(PI * (zRot + 0) / 180.0f)); + cameraPosition.setY(cameraPosition.y() + speed * sin(PI * (zRot + 0) / 180.0f)); + } + else if (e->key() == Qt::Key_S) { + cameraPosition.setX(cameraPosition.x() + speed * cos(PI * (zRot + 180) / 180.0f)); + cameraPosition.setY(cameraPosition.y() + speed * sin(PI * (zRot + 180) / 180.0f)); + } + else if (e->key() == Qt::Key_E) { + zRot -= 2.0f; + if (zRot < 0.0f) zRot += 360.0f; + } + else if (e->key() == Qt::Key_Q) { + zRot += 2.0f; + if (zRot >= 360.0f) zRot -= 360.0f; } - if (e->key()== Qt::Key_W) { - cameraPosition.setX(cameraPosition.x()+ 0.1f*cos(PI*(zRot+0)/180.0f)); - cameraPosition.setY(cameraPosition.y()+ 0.1f*sin(PI*(zRot+0)/180.0f)); + // Unit action hotkeys + Unit *sel = GameManager::instance().getSelectedUnit(); + if (e->key() == Qt::Key_M && sel) { + GameManager::instance().setActionMode( + GameManager::instance().currentActionMode() == ActionMode::Move ? ActionMode::None : ActionMode::Move + ); } - else if (e->key()== Qt::Key_S) { - cameraPosition.setX(cameraPosition.x()+ 0.1f*cos(PI*(zRot+180)/180.0f)); - cameraPosition.setY(cameraPosition.y()+ 0.1f*sin(PI*(zRot+180)/180.0f)); + else if (e->key() == Qt::Key_K && sel && sel->config.canAttack()) { + GameManager::instance().setActionMode( + GameManager::instance().currentActionMode() == ActionMode::Attack ? ActionMode::None : ActionMode::Attack + ); } - else if (e->key()== Qt::Key_E) { - zRot --; - if (zRot<0.0) - zRot+=360.0; + else if (e->key() == Qt::Key_B && sel && sel->config.canBuildCity()) { + GameManager::instance().commandBuildCitySelected(m_point, m_numPoints05); } - else if (e->key()== Qt::Key_Q) { - zRot ++; - if (zRot>=360.0) - zRot-=360.0; + else if (e->key() == Qt::Key_Escape) { + if (GameManager::instance().currentActionMode() != ActionMode::None) { + GameManager::instance().setActionMode(ActionMode::None); + } else { + GameManager::instance().deselectUnit(); + } } + // Speed hotkeys: 1, 2, 5 + else if (e->key() == Qt::Key_1) { + GameManager::instance().setSimSpeed(1.0f); + } + else if (e->key() == Qt::Key_2) { + GameManager::instance().setSimSpeed(2.0f); + } + else if (e->key() == Qt::Key_3 || e->key() == Qt::Key_5) { + GameManager::instance().setSimSpeed(5.0f); + } + update(); } void QSceneRenderer::wheelEvent(QWheelEvent *e) { - m_zoom -= e->delta()/100.0; - if (m_zoom<2) m_zoom =2; - if (m_zoom>10)m_zoom =10; + QRect card = getMinimapCardRect(); + if (card.contains(e->pos()) || getZoomOutButtonRect().contains(e->pos()) || getZoomInButtonRect().contains(e->pos())) { + if (e->delta() > 0) { + m_minimapZoom = std::min(8.0f, m_minimapZoom * 1.4f); + } else { + m_minimapZoom = std::max(1.0f, m_minimapZoom / 1.4f); + } + update(); + return; + } + + m_zoom -= e->delta() / 100.0f; + if (m_zoom < 2.0f) m_zoom = 2.0f; + if (m_zoom > 16.0f) m_zoom = 16.0f; update(); } @@ -103,40 +1146,48 @@ void QSceneRenderer::initializeGL() { glEnable(GL_DEPTH_TEST); glDepthFunc(GL_LESS); + + m_unitRenderer.initialize(); } void QSceneRenderer::resizeGL(int width, int height) { - Projection= QMatrix4x4(); - Projection.perspective(45, (float) width / (float)height,0.1f,100.0f); - - + Projection = QMatrix4x4(); + Projection.perspective(45.0f, (float) width / (float) height, 0.1f, 100.0f); } void QSceneRenderer::paintGL() { glEnable(GL_DEPTH_TEST); - QMatrix4x4 cameraTransformation; + QMatrix4x4 cameraTrans; QMatrix4x4 vMatrix; - cameraTransformation.rotate(zRot, 0, 0, 1); - cameraUpDirection = cameraTransformation * QVector3D(0, 0, 1); + cameraTrans.rotate(zRot, 0, 0, 1); + cameraUpDirection = cameraTrans * QVector3D(0, 0, 1); cameraPosition.setZ(m_zoom); - QVector3D lookat = QVector3D(cameraPosition.x()+ m_zoom*m_zoom/5.0f*cos(PI*zRot/180.0f) - , cameraPosition.y()+ m_zoom*m_zoom/5.0f*sin(PI*zRot/180.0f) - ,0); + QVector3D lookat = QVector3D( + cameraPosition.x() + m_zoom * m_zoom / 5.0f * cos(PI * zRot / 180.0f), + cameraPosition.y() + m_zoom * m_zoom / 5.0f * sin(PI * zRot / 180.0f), + 0.0f + ); vMatrix.lookAt(cameraPosition, lookat, cameraUpDirection); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - // Bounding box of the visible area in world coordinates + // Update simulation + float currentTime = m_elapsedTimer.elapsed() / 1000.0f; + float deltaTime = m_lastFrameTime > 0.0f ? std::min(0.1f, currentTime - m_lastFrameTime) : 0.016f; + m_lastFrameTime = currentTime; + GameManager::instance().update(deltaTime, m_point, m_numPoints05); + + // Visible terrain tiles bounding box float minWorldX = std::min(cameraPosition.x(), lookat.x()); float maxWorldX = std::max(cameraPosition.x(), lookat.x()); float minWorldY = std::min(cameraPosition.y(), lookat.y()); float maxWorldY = std::max(cameraPosition.y(), lookat.y()); - float margin = m_zoom * 2.5f + 8.0f; + float margin = std::max(m_zoom * 3.0f + 15.0f, 35.0f); minWorldX -= margin; maxWorldX += margin; minWorldY -= margin; @@ -150,31 +1201,56 @@ void QSceneRenderer::paintGL() int minTileY = std::max(0, (int)floor(minWorldY / tileStepY)); int maxTileY = std::min(MAP_HEIGHT - 1, (int)ceil(maxWorldY / tileStepY)); - // Expand margin by 1 for neighbor normal calculations int initMinX = std::max(0, minTileX - 1); int initMaxX = std::min(MAP_WIDTH - 1, maxTileX + 1); int initMinY = std::max(0, minTileY - 1); int initMaxY = std::min(MAP_HEIGHT - 1, maxTileY + 1); - // 1. Lazy initialize visible tiles and direct neighbors for (int y = initMinY; y <= initMaxY; ++y) { for (int x = initMinX; x <= initMaxX; ++x) { tiles->operator()(x, y).ensureInitialized(this, x, y); } } - // 2. Ensure normals are built for visible tiles for (int y = minTileY; y <= maxTileY; ++y) { for (int x = minTileX; x <= maxTileX; ++x) { tiles->operator()(x, y).ensureNormalsBuilt(); } } - // 3. Render only visible tiles + // 1. Render terrain tiles for (int y = minTileY; y <= maxTileY; ++y) { for (int x = minTileX; x <= maxTileX; ++x) { - tiles->operator()(x, y).paint(Projection, cameraPosition, cameraUpDirection, vMatrix, zRot); + tiles->operator()(x, y).paint(Projection, cameraPosition, cameraUpDirection, vMatrix, currentTime); } } + // 2. Render 3D Units (Cylinders) + float fSunAngle = 45.0f; + QVector3D vSunPos(-sin(fSunAngle * PI / 180.0f), -cos(fSunAngle * PI / 180.0f), 0.6f); + vSunPos.normalize(); + + Unit *selected = GameManager::instance().getSelectedUnit(); + m_unitRenderer.renderUnits( + GameManager::instance().getUnits(), + GameManager::instance().getCities(), + GameManager::instance().players(), + Projection, + vMatrix, + vSunPos, + selected ? selected->id : -1 + ); + + // 3. Render 2D UI & HUD Overlays via QPainter + QPainter painter(this); + drawPathAndRange(painter, selected, vMatrix); + drawMinimap(painter); + drawTickBar(painter); + drawUnitsOverlay(painter, vMatrix); + drawFloatingTexts(painter, vMatrix); + + if (selected) { + drawUnitCard(painter, selected); + } + painter.end(); } diff --git a/qscenerenderer.h b/qscenerenderer.h index 83f961e..1f3e641 100644 --- a/qscenerenderer.h +++ b/qscenerenderer.h @@ -7,45 +7,94 @@ #include "terrain.h" #include "tile.h" #include +#include +#include #include +#include #include "matrix2d.h" +#include "unitrenderer.h" +#include "gamemanager.h" - -class QSceneRenderer : public QOpenGLWidget +class QSceneRenderer : public QOpenGLWidget { public: - QSceneRenderer(QWidget* parent = Q_NULLPTR) ; - inline Matrix2D * getMapPoints() {return m_point;} - inline int getNumPointsWidth() const {return m_numPointsWidth;} - inline int getNumPointsHeight() const {return m_numPointsHeight;} - inline int getNumPoints05() const {return m_numPoints05;} + QSceneRenderer(QWidget* parent = Q_NULLPTR); + inline Matrix2D * getMapPoints() { return m_point; } + inline int getNumPointsWidth() const { return m_numPointsWidth; } + inline int getNumPointsHeight() const { return m_numPointsHeight; } + inline int getNumPoints05() const { return m_numPoints05; } Tile * getTile(int x, int y); -protected: - void mousePressEvent(QMouseEvent *event); - void mouseMoveEvent(QMouseEvent *event); - void keyPressEvent(QKeyEvent * e); - void wheelEvent(QWheelEvent * e); - void initializeGL(); - void resizeGL(int w, int h); - void paintGL(); + void loadRiverMap(const QImage &riverImg); - float m_camera_x =10.0; - float m_camera_y=0.0; +protected: + void mousePressEvent(QMouseEvent *event) override; + void mouseMoveEvent(QMouseEvent *event) override; + void mouseReleaseEvent(QMouseEvent *event) override; + void keyPressEvent(QKeyEvent * e) override; + void wheelEvent(QWheelEvent * e) override; + void initializeGL() override; + void resizeGL(int w, int h) override; + void paintGL() override; + + // Minimap helpers + QRect getMinimapRect() const; + QRect getMinimapCardRect() const; + QRect getZoomOutButtonRect() const; + QRect getZoomResetButtonRect() const; + QRect getZoomInButtonRect() const; + QRect getMinimapSourceRect() const; + QPointF worldToMinimap(float wx, float wy, const QRect &rect) const; + QVector2D minimapToWorld(const QPoint &pos, const QRect &rect) const; + void drawMinimap(QPainter &painter); + + // Units HUD and 2D overlays + void drawUnitsOverlay(QPainter &painter, const QMatrix4x4 &vMatrix); + void drawUnitCard(QPainter &painter, Unit *unit); + void drawTickBar(QPainter &painter); + void drawPathAndRange(QPainter &painter, Unit *unit, const QMatrix4x4 &vMatrix); + void drawFloatingTexts(QPainter &painter, const QMatrix4x4 &vMatrix); + bool screenToHex(const QPoint &screenPos, const QMatrix4x4 &vMatrix, int &outX, int &outY); + QImage getTintedIcon(const QString &path, const QColor &color); + + float m_camera_x = 10.0f; + float m_camera_y = 0.0f; QMatrix4x4 Projection; QMatrix4x4 cameraTransformation; QVector3D cameraPosition; QVector3D cameraUpDirection; - Matrix2D * m_point; Matrix2D * tiles; QPoint lastPos; - float zRot=0; + float zRot = 0.0f; float m_numPointsWidth; float m_numPointsHeight; int m_numPoints05; float m_zoom; + QElapsedTimer m_elapsedTimer; + float m_lastFrameTime; + QImage m_image_minimap; + bool m_draggingMinimap; + float m_minimapZoom; + + // Units & UI state + UnitRenderer m_unitRenderer; + QMap m_iconCache; + QMap m_tintedIconCache; + QPoint m_mouseCurrentPos; + + struct ActionBubble { + QRect rect; + QString actionId; + QString tooltip; + }; + std::vector m_activeActionBubbles; + + QRect m_speed1xRect; + QRect m_speed2xRect; + QRect m_speed5xRect; + QRect m_unitCardRect; }; #endif // QSCENERENDERER_H diff --git a/qscenerenderer.o b/qscenerenderer.o index 97d61b7..23e0e44 100644 Binary files a/qscenerenderer.o and b/qscenerenderer.o differ diff --git a/shaders/simple.frag b/shaders/simple.frag index eee1a38..bdbe0c5 100644 --- a/shaders/simple.frag +++ b/shaders/simple.frag @@ -1,7 +1,20 @@ #version 400 -uniform sampler2D tex; //this is the texture -uniform sampler2D border; //this is the texture -uniform sampler2D rock; //this is the texture +uniform sampler2D texGrass; // Unit 0 +uniform sampler2D texBorder; // Unit 1 +uniform sampler2D texRock; // Unit 2 (Stone cliffs) +uniform sampler2D texSand; // Unit 3 (Soil beach) +uniform sampler2D texWater; // Unit 4 (Dynamic ocean) +uniform sampler2D texSnow; // Unit 5 (Texturelabs_Water_155XL snow/ice) +uniform sampler2D texTypeMap; // Unit 6 (Biome type map) +uniform sampler2D texSahara; // Unit 7 (Texturelabs_Soil_133L desert sand) +uniform sampler2D texTundra; // Unit 8 (Texturelabs_Soil_135XL tundra soil) +uniform sampler2D texRedSand; // Unit 9 (Texturelabs_RedSand prairie sand) +uniform sampler2D texRedRock; // Unit 10 (Texturelabs_RedRock canyon cliff) +uniform sampler2D texForest; // Unit 11 (Texturelabs_Forest_seamless woodland floor) + +uniform float time; +uniform int riverMask; + smooth in vec3 vNormal; smooth in vec3 vWorldPos; in vec3 fragVert; @@ -10,11 +23,8 @@ uniform vec3 cameraPosition; uniform mat4 modelMatrix; in vec2 tilePos; -in vec2 fragTexCoord; //this is the texture coord -out vec4 finalColor; //this is the output color of the pixel - - - +in vec2 fragTexCoord; +out vec4 finalColor; struct SimpleDirectionalLight { @@ -23,55 +33,243 @@ struct SimpleDirectionalLight float fAmbientIntensity; }; - - uniform SimpleDirectionalLight sunLight; -vec4 GetDirectionalLightColor(SimpleDirectionalLight dirLight, vec3 vNormal) +vec4 GetDirectionalLightColor(SimpleDirectionalLight dirLight, vec3 norm) { - float fDiffuseIntensity = dot(vNormal, -dirLight.vDirection); - float fMult = pow(dirLight.fAmbientIntensity+fDiffuseIntensity,1.0);//clamp(dirLight.fAmbientIntensity+fDiffuseIntensity, -1.0, 1.0); - // if(dirLight.iSkybox == 1)fMult = dirLight.fAmbientIntensity; - return vec4(dirLight.vColor*fMult, 1.0); + float fDiffuseIntensity = max(dot(norm, -dirLight.vDirection), 0.0); + float fMult = dirLight.fAmbientIntensity + fDiffuseIntensity * (1.0 - dirLight.fAmbientIntensity * 0.5); + return vec4(dirLight.vColor * fMult, 1.0); } -vec4 GetSpecularColor(vec3 vWorldPos, vec3 vEyePos, SimpleDirectionalLight dLight, vec3 vNormal) +vec4 GetSpecularColor(vec3 worldPos, vec3 eyePos, SimpleDirectionalLight dLight, vec3 norm, float shininess, float intensity) { - vec4 vResult = vec4(0.0, 0.0, 0.0, 0.0); + vec3 vReflectedVector = normalize(reflect(dLight.vDirection, norm)); + vec3 vVertexToEyeVector = normalize(eyePos - worldPos); + float fSpecularFactor = max(dot(vVertexToEyeVector, vReflectedVector), 0.0); - vec3 vReflectedVector = normalize(reflect(dLight.vDirection, vNormal)); - vec3 vVertexToEyeVector = normalize(vEyePos-vWorldPos); - float fSpecularFactor = dot(vVertexToEyeVector, vReflectedVector); + if (fSpecularFactor > 0.0) + { + fSpecularFactor = pow(fSpecularFactor, shininess); + return vec4(dLight.vColor, 1.0) * intensity * fSpecularFactor; + } + return vec4(0.0); +} - if (fSpecularFactor > 0) - { - fSpecularFactor = pow(fSpecularFactor, 1.0); - vResult = vec4(dLight.vColor, 1.0) * 1.0 * fSpecularFactor; - } +float distToSegment(vec2 p, vec2 a, vec2 b) { + vec2 ba = b - a; + vec2 pa = p - a; + float l2 = dot(ba, ba); + if (l2 <= 1e-6) return length(pa); + float h = clamp(dot(pa, ba) / l2, 0.0, 1.0); + return length(pa - ba * h); +} - return vResult; +const vec2 EDGE_MIDPOINTS[6] = vec2[6]( + vec2(-0.375, 0.21650635), + vec2( 0.0, 0.43301270), + vec2( 0.375, 0.21650635), + vec2( 0.375, -0.21650635), + vec2( 0.0, -0.43301270), + vec2(-0.375, -0.21650635) +); + +float getRiverDistance(vec2 p, int mask) { + if (mask == 0) return 999.0; + + int edges[6]; + int count = 0; + for (int e = 0; e < 6; ++e) { + if ((mask & (1 << e)) != 0) { + edges[count] = e; + count++; + } + } + + if (count == 2) { + vec2 p0 = EDGE_MIDPOINTS[edges[0]]; + vec2 p2 = EDGE_MIDPOINTS[edges[1]]; + vec2 s0 = p0; + vec2 s1 = 0.5625 * p0 + 0.0625 * p2; + vec2 s2 = 0.2500 * p0 + 0.2500 * p2; + vec2 s3 = 0.0625 * p0 + 0.5625 * p2; + vec2 s4 = p2; + float d0 = distToSegment(p, s0, s1); + float d1 = distToSegment(p, s1, s2); + float d2 = distToSegment(p, s2, s3); + float d3 = distToSegment(p, s3, s4); + return min(min(d0, d1), min(d2, d3)); + } else { + float minDist = 999.0; + for (int i = 0; i < count; ++i) { + float d = distToSegment(p, EDGE_MIDPOINTS[edges[i]], vec2(0.0)); + minDist = min(minDist, d); + } + return minDist; + } } void main() { + vec3 norm = normalize(vNormal); - vec3 surfaceToLight = normalize(sunLight.vDirection); - vec3 surfacePos = vec3(modelMatrix * vec4(fragVert, 1)); - vec3 surfaceToCamera = normalize(cameraPosition - surfacePos); - vec4 bcolor = texture(border, vec2(tilePos.x,tilePos.y*1.154734411)); - float fDiffuseIntensity = max(0.0, dot(normalize(vNormal), -sunLight.vDirection)); + // 1. Biome Typemap Sampling + // World extent: [0..225.0 km, 0..108.22 km] + // Note: QImage row 0 is North (worldY = 108.22), which QOpenGLTexture maps to v = 0.0. + // Invert Y so North samples row 0 and South samples row 1499. + vec2 uvType = vec2(clamp(vWorldPos.x / 225.0, 0.0, 1.0), 1.0 - clamp(vWorldPos.y / 108.22, 0.0, 1.0)); + vec4 typePixel = texture(texTypeMap, uvType); - float addval = bcolor.a; - vec4 vSpecularColor = GetSpecularColor(vWorldPos, cameraPosition, sunLight, normalize(vNormal))+ GetDirectionalLightColor(sunLight,normalize(vNormal)); - vec4 ground = texture(tex, fragTexCoord); - vec4 rockt = texture(rock, fragTexCoord); + // Compute smooth biome weights from typemap colors: + // 1. Prairie: red dominant (r - g) + float prairieWeight = clamp((typePixel.r - typePixel.g - 0.20) / 0.25, 0.0, 1.0); + // 2. Sahara: yellow (r & g high, b low) + float saharaWeight = clamp((min(typePixel.r, typePixel.g) - 0.45) / 0.25, 0.0, 1.0) * (1.0 - prairieWeight); + // 3. Forest: elevated blue with low red (b > 0.15, r < 0.25) + float forestWeight = clamp((typePixel.b - 0.15) / 0.20, 0.0, 1.0) * clamp((0.25 - typePixel.r) / 0.15, 0.0, 1.0) * (1.0 - prairieWeight) * (1.0 - saharaWeight); + // 4. Tundra: muted slate/olive (low saturation, moderate/low green) + float sat = max(typePixel.r, max(typePixel.g, typePixel.b)) - min(typePixel.r, min(typePixel.g, typePixel.b)); + float tundraWeight = clamp((0.25 - sat) / 0.15, 0.0, 1.0) * clamp((0.55 - typePixel.g) / 0.15, 0.0, 1.0) * (1.0 - prairieWeight) * (1.0 - saharaWeight) * (1.0 - forestWeight); + // 5. Grassland: green dominant open plains + float grasslandWeight = clamp(1.0 - (prairieWeight + saharaWeight + forestWeight + tundraWeight), 0.0, 1.0); - float gndfac = 1.0; - if (z_pos>0.3) - gndfac = 0.0; - else if (z_pos>0.1) - gndfac=1.0 - 1.0 / 0.2 * (z_pos-0.1) ; - finalColor = (ground *gndfac+rockt *(1.0-gndfac) +bcolor*addval) * vSpecularColor; //texture(tex, fragTexCoord) * pow(max(0.0, dot(surfaceToCamera, reflect(-surfaceToLight, normal))), 0.5); + // 2. Polar Latitude Calculation (World Y in [0..108.25 km]) + float normY = clamp(vWorldPos.y / 108.25, 0.0, 1.0); + float distFromCenter = abs(normY - 0.5) * 2.0; // 0.0 at middle, 1.0 at North/South poles + float poleFactor = smoothstep(0.40, 0.85, distFromCenter); - // (texture(tex, fragTexCoord)+bcolor*addval)*vec4(sunLight.vColor*(sunLight.fAmbientIntensity+fDiffuseIntensity), 1.0); + // Snowline in kilometers: 2.5 (2500m) in temperate zone, dropping to 0.0 (sea level) at poles! + float snowLine = mix(2.5, 0.0, poleFactor); + // 3. Water vs Land detection (z_pos = height in kilometers, 0.0 = sea level) + float oceanWaterFac = 1.0 - smoothstep(0.005, 0.025, z_pos); + + // River water detection + float riverDist = getRiverDistance(fragVert.xy, riverMask); + float riverWaterRadius = 0.024; + float riverBankRadius = 0.052; + float riverWaterFac = 1.0 - smoothstep(riverWaterRadius * 0.7, riverWaterRadius, riverDist); + float riverBankFac = (1.0 - smoothstep(riverWaterRadius, riverBankRadius, riverDist)) * (1.0 - riverWaterFac); + + // Combined water factor (ocean or river) + float waterFac = max(oceanWaterFac, riverWaterFac); + + // 4. Dynamic animated ocean water + vec2 uvW1 = vWorldPos.xy * 0.30 + vec2(time * 0.035, time * 0.020); + vec2 uvW2 = vWorldPos.xy * 0.38 + vec2(-time * 0.025, time * 0.030); + + vec2 waveDistort1 = vec2(sin(uvW1.y * 14.0 + time * 2.2), cos(uvW1.x * 14.0 + time * 1.8)) * 0.025; + vec2 waveDistort2 = vec2(cos(uvW2.y * 18.0 - time * 1.5), sin(uvW2.x * 18.0 - time * 2.5)) * 0.020; + + vec4 wSample1 = texture(texWater, uvW1 + waveDistort1); + vec4 wSample2 = texture(texWater, uvW2 + waveDistort2); + vec4 waterColor = mix(wSample1, wSample2, 0.5); + waterColor.rgb = mix(waterColor.rgb, vec3(0.05, 0.30, 0.50), 0.30); + + vec3 waterNorm = normalize(vec3( + (waveDistort1.x + waveDistort2.x) * 2.5, + (waveDistort1.y + waveDistort2.y) * 2.5, + 1.0 + )); + + // 5. Triplanar mapping coordinates for Land, Cliffs & Snow + vec3 blendWeights = pow(abs(norm), vec3(4.0)); + blendWeights /= (blendWeights.x + blendWeights.y + blendWeights.z); + + vec2 uvX = vWorldPos.yz * 0.35; + vec2 uvY = vWorldPos.xz * 0.35; + vec2 uvZ = vWorldPos.xy * 0.35; + + // Rock textures: + // Gray rock (Texturelabs_Stone_136XL_seamless) + vec4 rockGray = texture(texRock, uvX) * blendWeights.x + + texture(texRock, uvY) * blendWeights.y + + texture(texRock, uvZ) * blendWeights.z; + + // Red rock canyon cliff (Texturelabs_RedRock_seamless) + vec4 rockRed = texture(texRedRock, uvX) * blendWeights.x + + texture(texRedRock, uvY) * blendWeights.y + + texture(texRedRock, uvZ) * blendWeights.z; + + // Cliff rock: red sandstone in Prairie canyon, gray alpine elsewhere + vec4 cliffRockColor = mix(rockGray, rockRed, prairieWeight); + + // Snow / Ice texture (Texturelabs_Water_155XL_seamless) + vec4 snowColor = texture(texSnow, uvX) * blendWeights.x + + texture(texSnow, uvY) * blendWeights.y + + texture(texSnow, uvZ) * blendWeights.z; + + // Ground textures: + vec4 grassColor = texture(texGrass, uvZ); + vec4 forestColor = texture(texForest, uvZ); + vec4 beachColor = texture(texSand, uvZ); + vec4 saharaColor = texture(texSahara, uvZ); + vec4 tundraColor = texture(texTundra, uvZ); + vec4 redSandColor = texture(texRedSand, uvZ); + + // Composite ground texture across all biomes: + vec4 biomeGround = grassColor * grasslandWeight + + forestColor * forestWeight + + saharaColor * saharaWeight + + tundraColor * tundraWeight + + redSandColor * prairieWeight; + + // Shoreline beach: where gentle slopes meet water (sea level up to ~110m, or along rivers) + // Steep slopes (norm.z < 0.55) remain rocky cliffs; gentle slopes (norm.z > 0.80) get a sandy beach + float gentleSlope = smoothstep(0.55, 0.85, norm.z); + float coastBeach = (1.0 - smoothstep(0.015, 0.110, z_pos)) * smoothstep(0.003, 0.015, z_pos) * gentleSlope; + float riverBeach = riverBankFac; + float beachFac = max(coastBeach, riverBeach); + + // Suppress beach only at the extreme polar ice sheet edges + float polarIceCover = smoothstep(0.82, 0.96, distFromCenter); + beachFac *= (1.0 - polarIceCover); + + // Apply sandy beach across all land types gently meeting water + biomeGround = mix(biomeGround, beachColor, beachFac * 0.90); + + // Polar pack ice on cold polar waters + float polarSeaIce = smoothstep(0.72, 0.94, distFromCenter); + waterColor = mix(waterColor, snowColor, polarSeaIce * 0.95); + + // 6. Slope & Elevation Blending Rules + // norm.z: 1.0 = flat horizontal ground, < 0.55 = steep slope / vertical cliff + float flatness = clamp((norm.z - 0.55) / 0.25, 0.0, 1.0); + + // Mountain rock elevation factor: begins at 1000m (1.0 km), ramps up towards 2200m (2.2 km) + float rockLine = mix(1.0, 0.0, poleFactor); + float mountainAltitude = smoothstep(rockLine, min(snowLine, rockLine + 1.2), z_pos); + + // Steep slopes are always rock; flat ground becomes rock as altitude rises above 1000m + float rockWeight = max(1.0 - flatness, mountainAltitude); + + // Snow rule: starts above 2500m in temperate zone, dropping to sea level at poles + float snowHeight = smoothstep(snowLine - 0.20, snowLine + 0.30, z_pos); + float snowSlope = clamp((norm.z - 0.28) / 0.25, 0.0, 1.0); + float snowWeight = snowHeight * snowSlope; + + // At polar caps, ice covers flat ground at sea level + float polarLandIce = smoothstep(0.72, 0.92, distFromCenter); + snowWeight = max(snowWeight, polarLandIce * snowSlope); + + // Composite ground: biome ground -> mountain rock above 1000m & on cliffs -> snow above 2500m + vec4 nonSnowLand = mix(biomeGround, cliffRockColor, rockWeight); + vec4 landColor = mix(nonSnowLand, snowColor, snowWeight); + + // 7. Combine Land and Sea + vec4 surfaceColor = mix(landColor, waterColor, waterFac); + + // 8. Hexagon grid border outline + vec4 bcolor = texture(texBorder, vec2(tilePos.x, tilePos.y * 1.154734411)); + float borderAlpha = bcolor.a * (1.0 - waterFac * 0.7); + + // 9. Lighting & Specular glint + vec3 activeNorm = mix(norm, waterNorm, waterFac); + vec4 lightDiffuse = GetDirectionalLightColor(sunLight, activeNorm); + + // Specular: crisp sparkle on snow and water + float specShininess = mix(mix(16.0, 32.0, snowWeight), 48.0, waterFac); + float specIntensity = mix(mix(0.12, 0.55, snowWeight), 0.85, waterFac); + vec4 lightSpecular = GetSpecularColor(vWorldPos, cameraPosition, sunLight, activeNorm, specShininess, specIntensity); + + finalColor = (surfaceColor + bcolor * borderAlpha) * lightDiffuse + lightSpecular; } diff --git a/shaders/simple.vert b/shaders/simple.vert index c872316..0200c60 100644 --- a/shaders/simple.vert +++ b/shaders/simple.vert @@ -5,14 +5,14 @@ uniform mat4 projectionMatrix; uniform mat4 normalMatrix; uniform mat4 modelMatrix; +uniform mat4 worldMatrix; layout(location = 0) in vec3 position; layout(location = 1) in vec2 inCoord; layout (location = 2) in float zPos; -layout (location =3) in vec3 inNormal; +layout (location = 3) in vec3 inNormal; -//in float height; out vec2 fragTexCoord; out vec2 tilePos; out vec3 fragVert; @@ -20,22 +20,24 @@ smooth out vec3 vWorldPos; uniform mat4 MVP; smooth out vec3 vNormal; out float z_pos; + void main() { + gl_Position = MVP * vec4(position.x, position.y, zPos, 1.0); -vec4 inPositionScaled = vec4(position, 1.0); - gl_Position = MVP *vec4(position.x,position.y,zPos, 1.0); - vec4 texp= modelViewMatrix *vec4(position.x,position.y,0, 1.0); - fragTexCoord = vec2(texp.x/2,texp.y/2); - vec4 tpos = modelMatrix*vec4(position.x,position.y,0, 1.0); - tilePos = vec2(tpos.x,tpos.y); - //vColor = vec4(color, 1.0); - vec4 vRes = vec4(inNormal, 0.0); - vNormal = inNormal.xyz; + // World position: anchored to terrain ground + vec4 wPos = worldMatrix * vec4(position.x, position.y, zPos, 1.0); + vWorldPos = wPos.xyz; + + // Texture coordinates based on world coordinates (stays fixed when camera moves) + fragTexCoord = vec2(wPos.x * 0.5, wPos.y * 0.5); + + // Tile-local position for the hexagon border outline + vec4 tpos = modelMatrix * vec4(position.x, position.y, 0.0, 1.0); + tilePos = vec2(tpos.x, tpos.y); + + vNormal = inNormal.xyz; fragVert = position; - - vec4 vWorldPosLocal =modelMatrix*inPositionScaled; - vWorldPos = vWorldPosLocal.xyz; - z_pos=zPos; + z_pos = zPos; } diff --git a/shaders/unit.frag b/shaders/unit.frag new file mode 100644 index 0000000..2efe23d --- /dev/null +++ b/shaders/unit.frag @@ -0,0 +1,18 @@ +#version 400 +in vec3 vNormal; +in vec3 vWorldPos; + +uniform vec4 u_color; +uniform vec3 u_lightDir; + +out vec4 fragColor; + +void main() +{ + vec3 n = normalize(vNormal); + vec3 l = normalize(u_lightDir); + float diff = max(dot(n, l), 0.0); + float ambient = 0.55; + float light = clamp(ambient + diff * 0.45, 0.0, 1.0); + fragColor = vec4(u_color.rgb * light, u_color.a); +} diff --git a/shaders/unit.vert b/shaders/unit.vert new file mode 100644 index 0000000..e9239fd --- /dev/null +++ b/shaders/unit.vert @@ -0,0 +1,17 @@ +#version 400 +layout(location = 0) in vec3 inPosition; +layout(location = 1) in vec3 inNormal; + +uniform mat4 MVP; +uniform mat4 modelMatrix; + +out vec3 vNormal; +out vec3 vWorldPos; + +void main() +{ + vec4 wPos = modelMatrix * vec4(inPosition, 1.0); + vWorldPos = wPos.xyz; + vNormal = mat3(modelMatrix) * inNormal; + gl_Position = MVP * vec4(inPosition, 1.0); +} diff --git a/terrain.h b/terrain.h index 15292b6..389370c 100644 --- a/terrain.h +++ b/terrain.h @@ -20,9 +20,39 @@ #define PI 3.14159265359f -struct TerrainPoint { - char height; +#include +struct TerrainPoint { + unsigned char height; + unsigned char type; + unsigned char river; }; +// Returns the 6 edge midpoints in local tile coordinates (radius = 0.5) +inline QVector2D getHexEdgeMidpoint(int edge) { + static const QVector2D midpoints[6] = { + QVector2D(-0.375f, 0.21650635f), // Edge 0 (Top-Left) + QVector2D( 0.0f, 0.43301270f), // Edge 1 (Top) + QVector2D( 0.375f, 0.21650635f), // Edge 2 (Top-Right) + QVector2D( 0.375f, -0.21650635f), // Edge 3 (Bottom-Right) + QVector2D( 0.0f, -0.43301270f), // Edge 4 (Bottom) + QVector2D(-0.375f, -0.21650635f) // Edge 5 (Bottom-Left) + }; + return midpoints[(edge % 6 + 6) % 6]; +} + +// Returns the coordinates of the neighbor tile sharing the given edge +inline void getHexNeighbor(int x, int y, int edge, int &nx, int &ny) { + bool even = (x % 2 == 0); + switch ((edge % 6 + 6) % 6) { + case 0: nx = x - 1; ny = even ? y : y + 1; break; + case 1: nx = x; ny = y + 1; break; + case 2: nx = x + 1; ny = even ? y : y + 1; break; + case 3: nx = x + 1; ny = even ? y - 1 : y; break; + case 4: nx = x; ny = y - 1; break; + case 5: nx = x - 1; ny = even ? y - 1 : y; break; + default: nx = x; ny = y; break; + } +} + #endif // TERRAIN_H diff --git a/tile.cpp b/tile.cpp index 2017a78..bddf243 100644 --- a/tile.cpp +++ b/tile.cpp @@ -3,12 +3,30 @@ #include #include "qscenerenderer.h" #include -QImage * Tile::m_image = NULL; -QOpenGLTexture * Tile::m_texture = NULL; +QImage * Tile::m_image_grass = NULL; +QOpenGLTexture * Tile::m_texture_grass = NULL; QImage * Tile::m_image_border = NULL; QOpenGLTexture * Tile::m_texture_border = NULL; QOpenGLTexture * Tile::m_texture_rock = NULL; QImage * Tile::m_image_rock = NULL; +QOpenGLTexture * Tile::m_texture_sand = NULL; +QImage * Tile::m_image_sand = NULL; +QOpenGLTexture * Tile::m_texture_water = NULL; +QImage * Tile::m_image_water = NULL; +QOpenGLTexture * Tile::m_texture_snow = NULL; +QImage * Tile::m_image_snow = NULL; +QOpenGLTexture * Tile::m_texture_typemap = NULL; +QImage * Tile::m_image_typemap = NULL; +QOpenGLTexture * Tile::m_texture_sahara = NULL; +QImage * Tile::m_image_sahara = NULL; +QOpenGLTexture * Tile::m_texture_tundra = NULL; +QImage * Tile::m_image_tundra = NULL; +QOpenGLTexture * Tile::m_texture_redsand = NULL; +QImage * Tile::m_image_redsand = NULL; +QOpenGLTexture * Tile::m_texture_redrock = NULL; +QImage * Tile::m_image_redrock = NULL; +QOpenGLTexture * Tile::m_texture_forest = NULL; +QImage * Tile::m_image_forest = NULL; Vertex * Tile::m_vertices= NULL; int Tile::m_numVertex; @@ -18,10 +36,53 @@ Vertex * Tile::m_vertices= NULL; QOpenGLShaderProgram *Tile::m_program= NULL; +static float distToSegment(QVector2D p, QVector2D a, QVector2D b) { + QVector2D ba = b - a; + QVector2D pa = p - a; + float l2 = QVector2D::dotProduct(ba, ba); + if (l2 <= 1e-6f) return pa.length(); + float h = std::max(0.0f, std::min(1.0f, QVector2D::dotProduct(pa, ba) / l2)); + return (pa - ba * h).length(); +} + +float Tile::getRiverDistance(QVector2D p, unsigned char mask) { + if (mask == 0) return 999.0f; + + int edges[6]; + int count = 0; + for (int e = 0; e < 6; ++e) { + if (mask & (1 << e)) { + edges[count++] = e; + } + } + + if (count == 2) { + QVector2D p0 = getHexEdgeMidpoint(edges[0]); + QVector2D p2 = getHexEdgeMidpoint(edges[1]); + QVector2D s0 = p0; + QVector2D s1 = 0.5625f * p0 + 0.0625f * p2; + QVector2D s2 = 0.2500f * p0 + 0.2500f * p2; + QVector2D s3 = 0.0625f * p0 + 0.5625f * p2; + QVector2D s4 = p2; + float d0 = distToSegment(p, s0, s1); + float d1 = distToSegment(p, s1, s2); + float d2 = distToSegment(p, s2, s3); + float d3 = distToSegment(p, s3, s4); + return std::min(std::min(d0, d1), std::min(d2, d3)); + } else { + float minDist = 999.0f; + for (int i = 0; i < count; ++i) { + float d = distToSegment(p, getHexEdgeMidpoint(edges[i]), QVector2D(0.0f, 0.0f)); + minDist = std::min(minDist, d); + } + return minDist; + } +} + Tile::Tile() : m_scene(NULL), m_tilevertices(NULL), m_x(0), m_y(0), m_startpoint_x(0.0f), m_startpoint_y(0.0f), - m_initialized(false), m_normalsBuilt(false) + m_initialized(false), m_normalsBuilt(false), m_riverMask(0) { } @@ -62,36 +123,114 @@ void Tile::init(QSceneRenderer* s, int x, int y) buildHeights(); - if (Tile::m_image == NULL) - Tile::m_image = new QImage("/home/bart/project/tiletest/images/grass.jpg"); + if (Tile::m_image_grass == NULL) + Tile::m_image_grass = new QImage("/home/bart/project/tiletest/images/grass.jpg"); if (Tile::m_image_border == NULL) Tile::m_image_border = new QImage("/home/bart/project/tiletest/images/border.png"); if (Tile::m_image_rock == NULL) - Tile::m_image_rock = new QImage("/home/bart/project/tiletest/images/rock.jpg"); + Tile::m_image_rock = new QImage("/home/bart/project/tiletest/images/Texturelabs_Stone_136XL_seamless.jpg"); + if (Tile::m_image_sand == NULL) + Tile::m_image_sand = new QImage("/home/bart/project/tiletest/images/Texturelabs_Soil_126XL_seamless.jpg"); + if (Tile::m_image_water == NULL) + Tile::m_image_water = new QImage("/home/bart/project/tiletest/images/Texturelabs_Water_152XL_seamless.jpg"); + if (Tile::m_image_snow == NULL) + Tile::m_image_snow = new QImage("/home/bart/project/tiletest/images/Texturelabs_Water_155XL_seamless.jpg"); + if (Tile::m_image_typemap == NULL) + Tile::m_image_typemap = new QImage("/home/bart/project/tiletest/images/typemap.png"); + if (Tile::m_image_sahara == NULL) + Tile::m_image_sahara = new QImage("/home/bart/project/tiletest/images/Texturelabs_Soil_133L_seamless.jpg"); + if (Tile::m_image_tundra == NULL) + Tile::m_image_tundra = new QImage("/home/bart/project/tiletest/images/Texturelabs_Soil_135XL_seamless.jpg"); + if (Tile::m_image_redsand == NULL) + Tile::m_image_redsand = new QImage("/home/bart/project/tiletest/images/Texturelabs_RedSand_seamless.jpg"); + if (Tile::m_image_redrock == NULL) + Tile::m_image_redrock = new QImage("/home/bart/project/tiletest/images/Texturelabs_RedRock_seamless.jpg"); + if (Tile::m_image_forest == NULL) + Tile::m_image_forest = new QImage("/home/bart/project/tiletest/images/Texturelabs_Forest_seamless.jpg"); - if (Tile::m_texture_rock == NULL) { - m_texture_rock = new QOpenGLTexture(*m_image_rock); - m_texture_rock->setWrapMode(QOpenGLTexture::Repeat); - m_texture_rock->setMinificationFilter(QOpenGLTexture::NearestMipMapNearest); - m_texture_rock->setMagnificationFilter(QOpenGLTexture::Nearest); - //m_texture->setFormat(QOpenGLTexture::RGB32F);33 - m_texture_rock->allocateStorage(); - } + if (Tile::m_texture_grass == NULL) { + m_texture_grass = new QOpenGLTexture(*m_image_grass); + m_texture_grass->setWrapMode(QOpenGLTexture::Repeat); + m_texture_grass->setMinificationFilter(QOpenGLTexture::LinearMipMapLinear); + m_texture_grass->setMagnificationFilter(QOpenGLTexture::Linear); + m_texture_grass->generateMipMaps(); + } if (Tile::m_texture_border == NULL) { m_texture_border = new QOpenGLTexture(*m_image_border); m_texture_border->setWrapMode(QOpenGLTexture::Repeat); - m_texture_border->setMinificationFilter(QOpenGLTexture::NearestMipMapNearest); - m_texture_border->setMagnificationFilter(QOpenGLTexture::Nearest); - //m_texture->setFormat(QOpenGLTexture::RGB32F);33 - m_texture_border->allocateStorage(); + m_texture_border->setMinificationFilter(QOpenGLTexture::LinearMipMapLinear); + m_texture_border->setMagnificationFilter(QOpenGLTexture::Linear); + m_texture_border->generateMipMaps(); } - if (Tile::m_texture == NULL) { - m_texture = new QOpenGLTexture(*m_image); - m_texture->setWrapMode(QOpenGLTexture::Repeat); - m_texture->setMinificationFilter(QOpenGLTexture::NearestMipMapNearest); - m_texture->setMagnificationFilter(QOpenGLTexture::Nearest); - //m_texture->setFormat(QOpenGLTexture::RGB32F); - m_texture->allocateStorage(); + if (Tile::m_texture_rock == NULL) { + m_texture_rock = new QOpenGLTexture(*m_image_rock); + m_texture_rock->setWrapMode(QOpenGLTexture::Repeat); + m_texture_rock->setMinificationFilter(QOpenGLTexture::LinearMipMapLinear); + m_texture_rock->setMagnificationFilter(QOpenGLTexture::Linear); + m_texture_rock->generateMipMaps(); + } + if (Tile::m_texture_sand == NULL) { + m_texture_sand = new QOpenGLTexture(*m_image_sand); + m_texture_sand->setWrapMode(QOpenGLTexture::Repeat); + m_texture_sand->setMinificationFilter(QOpenGLTexture::LinearMipMapLinear); + m_texture_sand->setMagnificationFilter(QOpenGLTexture::Linear); + m_texture_sand->generateMipMaps(); + } + if (Tile::m_texture_water == NULL) { + m_texture_water = new QOpenGLTexture(*m_image_water); + m_texture_water->setWrapMode(QOpenGLTexture::Repeat); + m_texture_water->setMinificationFilter(QOpenGLTexture::LinearMipMapLinear); + m_texture_water->setMagnificationFilter(QOpenGLTexture::Linear); + m_texture_water->generateMipMaps(); + } + if (Tile::m_texture_snow == NULL) { + m_texture_snow = new QOpenGLTexture(*m_image_snow); + m_texture_snow->setWrapMode(QOpenGLTexture::Repeat); + m_texture_snow->setMinificationFilter(QOpenGLTexture::LinearMipMapLinear); + m_texture_snow->setMagnificationFilter(QOpenGLTexture::Linear); + m_texture_snow->generateMipMaps(); + } + if (Tile::m_texture_typemap == NULL) { + m_texture_typemap = new QOpenGLTexture(*m_image_typemap); + m_texture_typemap->setWrapMode(QOpenGLTexture::ClampToEdge); + m_texture_typemap->setMinificationFilter(QOpenGLTexture::LinearMipMapLinear); + m_texture_typemap->setMagnificationFilter(QOpenGLTexture::Linear); + m_texture_typemap->generateMipMaps(); + } + if (Tile::m_texture_sahara == NULL) { + m_texture_sahara = new QOpenGLTexture(*m_image_sahara); + m_texture_sahara->setWrapMode(QOpenGLTexture::Repeat); + m_texture_sahara->setMinificationFilter(QOpenGLTexture::LinearMipMapLinear); + m_texture_sahara->setMagnificationFilter(QOpenGLTexture::Linear); + m_texture_sahara->generateMipMaps(); + } + if (Tile::m_texture_tundra == NULL) { + m_texture_tundra = new QOpenGLTexture(*m_image_tundra); + m_texture_tundra->setWrapMode(QOpenGLTexture::Repeat); + m_texture_tundra->setMinificationFilter(QOpenGLTexture::LinearMipMapLinear); + m_texture_tundra->setMagnificationFilter(QOpenGLTexture::Linear); + m_texture_tundra->generateMipMaps(); + } + if (Tile::m_texture_redsand == NULL) { + m_texture_redsand = new QOpenGLTexture(*m_image_redsand); + m_texture_redsand->setWrapMode(QOpenGLTexture::Repeat); + m_texture_redsand->setMinificationFilter(QOpenGLTexture::LinearMipMapLinear); + m_texture_redsand->setMagnificationFilter(QOpenGLTexture::Linear); + m_texture_redsand->generateMipMaps(); + } + if (Tile::m_texture_redrock == NULL) { + m_texture_redrock = new QOpenGLTexture(*m_image_redrock); + m_texture_redrock->setWrapMode(QOpenGLTexture::Repeat); + m_texture_redrock->setMinificationFilter(QOpenGLTexture::LinearMipMapLinear); + m_texture_redrock->setMagnificationFilter(QOpenGLTexture::Linear); + m_texture_redrock->generateMipMaps(); + } + if (Tile::m_texture_forest == NULL) { + m_texture_forest = new QOpenGLTexture(*m_image_forest); + m_texture_forest->setWrapMode(QOpenGLTexture::Repeat); + m_texture_forest->setMinificationFilter(QOpenGLTexture::LinearMipMapLinear); + m_texture_forest->setMagnificationFilter(QOpenGLTexture::Linear); + m_texture_forest->generateMipMaps(); } if (first){ @@ -132,7 +271,7 @@ void Tile::init(QSceneRenderer* s, int x, int y) m_initialized = true; } -void Tile::paint(QMatrix4x4 p, QVector3D& cameraPosition,QVector3D& cameraUpDirection,QMatrix4x4& vMatrix, float zRot) +void Tile::paint(QMatrix4x4 p, QVector3D& cameraPosition,QVector3D& cameraUpDirection,QMatrix4x4& vMatrix, float time) { if (!m_initialized) return; @@ -151,9 +290,18 @@ void Tile::paint(QMatrix4x4 p, QVector3D& cameraPosition,QVector3D& cameraUpDire //mMatrix.translate(m_x*LOD_0_SIZE *1.5+ (m_y%2==1 ?( LOD_0_SIZE*1.5f)/2.0f:0),m_y*LOD_0_SIZE/2.0f,1.0f); mMatrix.translate(m_x*(LOD_0_SIZE*0.75) ,(m_y*(LOD_0_SIZE_Y*2)+(m_x%2==0?0:LOD_0_SIZE_Y)) ,1.0f); - m_texture->bind(0); + m_texture_grass->bind(0); m_texture_border->bind(1); m_texture_rock->bind(2); + m_texture_sand->bind(3); + m_texture_water->bind(4); + m_texture_snow->bind(5); + m_texture_typemap->bind(6); + m_texture_sahara->bind(7); + m_texture_tundra->bind(8); + m_texture_redsand->bind(9); + m_texture_redrock->bind(10); + m_texture_forest->bind(11); // Render using our shader m_program->bind(); @@ -174,9 +322,20 @@ void Tile::paint(QMatrix4x4 p, QVector3D& cameraPosition,QVector3D& cameraUpDire //mModelToCamera.scale(0.8); - m_program->setUniformValue("grass", 0); - m_program->setUniformValue("border", 1); - m_program->setUniformValue("rock", 2); + m_program->setUniformValue("texGrass", 0); + m_program->setUniformValue("texBorder", 1); + m_program->setUniformValue("texRock", 2); + m_program->setUniformValue("texSand", 3); + m_program->setUniformValue("texWater", 4); + m_program->setUniformValue("texSnow", 5); + m_program->setUniformValue("texTypeMap", 6); + m_program->setUniformValue("texSahara", 7); + m_program->setUniformValue("texTundra", 8); + m_program->setUniformValue("texRedSand", 9); + m_program->setUniformValue("texRedRock", 10); + m_program->setUniformValue("texForest", 11); + m_program->setUniformValue("time", time); + m_program->setUniformValue("riverMask", (int)m_riverMask); m_object.bind(); m_heightsbuffer.bind(); @@ -195,6 +354,7 @@ m_program->setUniformValue("cameraPosition", cameraPosition); //mModelToCamera =vMatrix * mMatrix.inverted().transposed();//QMatrix4x4();// vPos.translate(1.0f); m_program->setUniformValue("normalMatrix", mModelToCamera); m_program->setUniformValue("modelMatrix", rMatrix); + m_program->setUniformValue("worldMatrix", mMatrix); m_program->setUniformValue("modelViewMatrix", vMatrix * mMatrix); m_program->setUniformValue("MVP", p * vMatrix * mMatrix); glDrawArrays(GL_TRIANGLES, 0, NUM_TRIANGLES*NUM_TRIANGLE_VERTICES); @@ -317,18 +477,30 @@ void Tile::drawSubTriangleVertices(int &cnt, QVector2D t1, QVector2D t2, QVector void Tile::buildHeights() { m_tilevertices = new TileVertexInfo[m_numVertex]; Matrix2D * points = Tile::m_scene->getMapPoints(); - for (int i = 0; i< m_numVertex; i++) { - /*float x=m_vertices[i].position().x(); - int xpos = m_vertices[i].position().x()*(m_scene->getNumPoints05()*2); - int mappos=(int) round( m_startpoint_x+ ( m_vertices[i].position().x())*(m_scene->getNumPoints05()*2)); - int res = (int) round( m_startpoint_x+ ( m_vertices[i].position().x())*(m_scene->getNumPoints05()*2));*/ - float r = points->operator ()( (int) round( m_startpoint_x+ ( m_vertices[i].position().x())*(m_scene->getNumPoints05()*2.0f)) - ,(int) round( m_startpoint_y+ ( m_vertices[i].position().y())*(m_scene->getNumPoints05()*2.0f))).height /255.0f; - //m_heights[i] = 1r; + float tileWorldX = m_x * (LOD_0_SIZE * 0.75f); + float tileWorldY = m_y * (LOD_0_SIZE_Y * 2.0f) + ((m_x % 2 == 0) ? 0.0f : LOD_0_SIZE_Y); + + float scaleX = m_scene->getNumPoints05() * 2.0f; + float scaleY = m_scene->getNumPoints05() / LOD_0_SIZE_Y; + + for (int i = 0; i < m_numVertex; i++) { + float worldX = tileWorldX + m_vertices[i].position().x(); + float worldY = tileWorldY + m_vertices[i].position().y(); + + int mapX = (int) round(worldX * scaleX); + int mapY = (int) round(worldY * scaleY); + + float r = (points->operator()(mapX, mapY).height / 255.0f) * 3.5f; + if (m_riverMask != 0) { + float rDist = getRiverDistance(m_vertices[i].position(), m_riverMask); + float riverBankRadius = 0.052f; + if (rDist < riverBankRadius) { + float carve = 0.018f * (1.0f - (rDist / riverBankRadius)); + r = std::max(0.0f, r - carve); + } + } m_tilevertices[i].setHeight(r); - //m_tilevertices[i].setNormal(QVector3D(rand()%20/10.0f-1.0,rand()%20/10.0f-1.0,rand()%20/10.0f-1.0)); - } m_faces.reserve(m_numVertex/3); int i =0; @@ -362,27 +534,62 @@ void Tile::findFaces(std::vector &vec, QVector2D s) void Tile::buildNormals() { if (!m_initialized) return; + + QVector2D myWorld = getWorldPosition(); + int nbrs[6][2]; + if (m_x % 2 == 0) { + nbrs[0][0] = m_x; nbrs[0][1] = m_y - 1; + nbrs[1][0] = m_x; nbrs[1][1] = m_y + 1; + nbrs[2][0] = m_x - 1; nbrs[2][1] = m_y - 1; + nbrs[3][0] = m_x - 1; nbrs[3][1] = m_y; + nbrs[4][0] = m_x + 1; nbrs[4][1] = m_y - 1; + nbrs[5][0] = m_x + 1; nbrs[5][1] = m_y; + } else { + nbrs[0][0] = m_x; nbrs[0][1] = m_y - 1; + nbrs[1][0] = m_x; nbrs[1][1] = m_y + 1; + nbrs[2][0] = m_x - 1; nbrs[2][1] = m_y; + nbrs[3][0] = m_x - 1; nbrs[3][1] = m_y + 1; + nbrs[4][0] = m_x + 1; nbrs[4][1] = m_y; + nbrs[5][0] = m_x + 1; nbrs[5][1] = m_y + 1; + } + std::vector vec; for (int i=0; igetTile(m_x-1,m_y)->findFaces(vec,m_vertices[i].position()); - m_scene->getTile(m_x+1,m_y)->findFaces(vec,m_vertices[i].position()); + QVector2D vPos = m_vertices[i].position(); + findFaces(vec, vPos); - QVector3D r; - float cnt =0; - for (std::vector::iterator it = vec.begin() ; it != vec.end(); ++it) { - QVector3D v = (*it)->normal(); - cnt++; - r.setX(r.x() + v.x()); - r.setY(r.y() + v.y()); - r.setZ(r.z() + v.z()); + if (vPos.length() > 0.4f) { + QVector2D worldV = myWorld + vPos; + for (int k = 0; k < 6; ++k) { + int nx = nbrs[k][0]; + int ny = nbrs[k][1]; + if (nx >= 0 && nx < MAP_WIDTH && ny >= 0 && ny < MAP_HEIGHT) { + Tile *nbr = m_scene->getTile(nx, ny); + if (nbr && nbr->isInitialized()) { + QVector2D localInNbr = worldV - nbr->getWorldPosition(); + nbr->findFaces(vec, localInNbr); + } + } + } } - r.setX(r.x() /cnt); - r.setY(r.y() /cnt); - r.setZ(r.z() /cnt); + QVector3D r(0.0f, 0.0f, 0.0f); + float cnt = 0; + for (std::vector::iterator it = vec.begin() ; it != vec.end(); ++it) { + QVector3D v = (*it)->normal(); + if (!v.isNull()) { + cnt++; + r += v; + } + } + + if (cnt > 0.0f && !r.isNull()) { + r.normalize(); + } else { + r = QVector3D(0.0f, 0.0f, 1.0f); + } m_tilevertices[i].setNormal(r); } } diff --git a/tile.h b/tile.h index 08d6845..62f73fe 100644 --- a/tile.h +++ b/tile.h @@ -16,7 +16,7 @@ class Tile public: Tile(); void init(QSceneRenderer* s,int x, int y); - void paint(QMatrix4x4 p,QVector3D& cameraPosition,QVector3D& cameraUpDirection,QMatrix4x4& vMatrix, float zRot); + void paint(QMatrix4x4 p,QVector3D& cameraPosition,QVector3D& cameraUpDirection,QMatrix4x4& vMatrix, float time); void buildVertices(); void buildNormals(); bool isInitialized() const { return m_initialized; } @@ -24,6 +24,14 @@ public: void ensureInitialized(QSceneRenderer* s, int x, int y); void ensureNormalsBuilt(); void findFaces(std::vector &vec, QVector2D s); + QVector2D getWorldPosition() const { + return QVector2D(m_x * (LOD_0_SIZE * 0.75f), m_y * (LOD_0_SIZE_Y * 2.0f) + (m_x % 2 == 0 ? 0.0f : LOD_0_SIZE_Y)); + } + void addRiverEdge(int edge) { m_riverMask |= (1 << ((edge % 6 + 6) % 6)); } + void setRiverMask(unsigned char mask) { m_riverMask = mask; } + unsigned char getRiverMask() const { return m_riverMask; } + bool hasRiver() const { return m_riverMask != 0; } + static float getRiverDistance(QVector2D p, unsigned char mask); protected: void addTriangleVertices(int &cnt, QVector2D t1, QVector2D t2, QVector2D t3,int level, int hex, int l1); void drawSubTriangleVertices(int &cnt, QVector2D t1, QVector2D t2, QVector2D t3, int hex, int l1, int l2); @@ -47,13 +55,32 @@ protected: float m_startpoint_y; bool m_initialized; bool m_normalsBuilt; + unsigned char m_riverMask; static Vertex * m_vertices; - static QOpenGLTexture * m_texture; + static QOpenGLTexture * m_texture_grass; static QOpenGLTexture * m_texture_border; static QOpenGLTexture * m_texture_rock; - static QImage * m_image; + static QOpenGLTexture * m_texture_sand; + static QOpenGLTexture * m_texture_water; + static QOpenGLTexture * m_texture_snow; + static QOpenGLTexture * m_texture_typemap; + static QOpenGLTexture * m_texture_sahara; + static QOpenGLTexture * m_texture_tundra; + static QOpenGLTexture * m_texture_redsand; + static QOpenGLTexture * m_texture_redrock; + static QOpenGLTexture * m_texture_forest; + static QImage * m_image_grass; static QImage * m_image_rock; static QImage * m_image_border; + static QImage * m_image_sand; + static QImage * m_image_water; + static QImage * m_image_snow; + static QImage * m_image_typemap; + static QImage * m_image_sahara; + static QImage * m_image_tundra; + static QImage * m_image_redsand; + static QImage * m_image_redrock; + static QImage * m_image_forest; }; #endif // TILE_H diff --git a/tile.o b/tile.o index 85f60c9..9433dea 100644 Binary files a/tile.o and b/tile.o differ diff --git a/tiletest b/tiletest index 04c44f8..f220ed0 100755 Binary files a/tiletest and b/tiletest differ diff --git a/tiletest.pro b/tiletest.pro index a994d2d..7b7483b 100644 --- a/tiletest.pro +++ b/tiletest.pro @@ -11,14 +11,22 @@ greaterThan(QT_MAJOR_VERSION, 4): QT += widgets TARGET = tiletest TEMPLATE = app +CONFIG += c++17 -SOURCES += main.cpp\ - mainwindow.cpp \ +SOURCES += main.cpp \ + mainwindow.cpp \ qscenerenderer.cpp \ vertex.cpp \ tile.cpp \ tilevertexinfo.cpp \ - face.cpp + face.cpp \ + player.cpp \ + yamlparser.cpp \ + unitconfig.cpp \ + pathfinder.cpp \ + unit.cpp \ + gamemanager.cpp \ + unitrenderer.cpp HEADERS += mainwindow.h \ qscenerenderer.h \ @@ -27,7 +35,15 @@ HEADERS += mainwindow.h \ terrain.h \ tilevertexinfo.h \ matrix2d.h \ - face.h + face.h \ + player.h \ + yamlparser.h \ + unitconfig.h \ + pathfinder.h \ + unit.h \ + city.h \ + gamemanager.h \ + unitrenderer.h FORMS += mainwindow.ui @@ -36,7 +52,9 @@ unix: PKGCONFIG += glew DISTFILES += \ shaders/simple.vert \ - shaders/simple.frag + shaders/simple.frag \ + shaders/unit.vert \ + shaders/unit.frag RESOURCES += \ shaders.qrc diff --git a/unit.cpp b/unit.cpp new file mode 100644 index 0000000..dcee020 --- /dev/null +++ b/unit.cpp @@ -0,0 +1,137 @@ +#include "unit.h" +#include "pathfinder.h" +#include +#include + +Unit::Unit(int uId, int pId, const UnitConfig &cfg, int startX, int startY, float groundZ) + : id(uId), playerId(pId), config(cfg), tileX(startX), tileY(startY), + health((float)cfg.health), maxHealth((float)cfg.health), + state(UnitState::Idle), pathIndex(0), moveProgress(0.0f), + targetTileX(startX), targetTileY(startY), attackFlashTimer(0.0f) +{ + QVector2D w = Pathfinder::hexToWorld(startX, startY); + worldPos = QVector3D(w.x(), w.y(), groundZ); +} + +float Unit::getTerrainHeight(float worldX, float worldY, Matrix2D *mapPoints, int numPoints05) +{ + if (!mapPoints || numPoints05 <= 0) return 1.0f; + + float scaleX = numPoints05 * 2.0f; + float scaleY = numPoints05 / LOD_0_SIZE_Y; + + int mapX = (int) std::round(worldX * scaleX); + int mapY = (int) std::round(worldY * scaleY); + + if (mapX < 0) mapX = 0; + if (mapX >= (int)mapPoints->getWidth()) mapX = (int)mapPoints->getWidth() - 1; + if (mapY < 0) mapY = 0; + if (mapY >= (int)mapPoints->getHeight()) mapY = (int)mapPoints->getHeight() - 1; + + unsigned char h = mapPoints->operator()(mapX, mapY).height; + return (h / 255.0f) * 3.5f + 1.0f; +} + +void Unit::setPath(const std::vector> &newPath) +{ + path = newPath; + pathIndex = 0; + moveProgress = 0.0f; + + if (path.size() > 1) { + state = UnitState::Moving; + targetTileX = path.back().first; + targetTileY = path.back().second; + } else { + state = UnitState::Idle; + } +} + +void Unit::stopMovement() +{ + path.clear(); + pathIndex = 0; + moveProgress = 0.0f; + state = UnitState::Idle; +} + +void Unit::update(float deltaTime, float tickDuration, float simSpeedMultiplier, + Matrix2D *mapPoints, int numPoints05) +{ + if (attackFlashTimer > 0.0f) { + attackFlashTimer = std::max(0.0f, attackFlashTimer - deltaTime); + } + + if (state != UnitState::Moving || path.size() <= 1) { + return; + } + + // Speed in hexes per second: movementSpeed points per tickDuration seconds + float effectiveTickDuration = std::max(0.1f, tickDuration); + float hexesPerSec = (config.movementSpeed / effectiveTickDuration) * simSpeedMultiplier; + + moveProgress += deltaTime * hexesPerSec; + + while (moveProgress >= 1.0f && pathIndex + 1 < path.size()) { + moveProgress -= 1.0f; + pathIndex++; + tileX = path[pathIndex].first; + tileY = path[pathIndex].second; + } + + if (pathIndex + 1 >= path.size()) { + // Reached destination + tileX = path.back().first; + tileY = path.back().second; + QVector2D dest = Pathfinder::hexToWorld(tileX, tileY); + float gz = getTerrainHeight(dest.x(), dest.y(), mapPoints, numPoints05); + worldPos = QVector3D(dest.x(), dest.y(), gz); + state = UnitState::Idle; + path.clear(); + moveProgress = 0.0f; + return; + } + + // Smoothly interpolate between current waypoint and next waypoint + QVector2D from = Pathfinder::hexToWorld(path[pathIndex].first, path[pathIndex].second); + QVector2D to = Pathfinder::hexToWorld(path[pathIndex + 1].first, path[pathIndex + 1].second); + + float t = std::max(0.0f, std::min(1.0f, moveProgress)); + float curX = from.x() + t * (to.x() - from.x()); + float curY = from.y() + t * (to.y() - from.y()); + float curZ = getTerrainHeight(curX, curY, mapPoints, numPoints05); + + worldPos = QVector3D(curX, curY, curZ); +} + +bool Unit::canAttack(const Unit &other) const +{ + if (other.playerId == playerId) return false; + if (other.isDead() || isDead()) return false; + if (!config.canAttack()) return false; + + int dist = Pathfinder::hexDistance(tileX, tileY, other.tileX, other.tileY); + return dist <= config.attackRange; +} + +void Unit::performAttack(Unit &target) +{ + if (!canAttack(target)) return; + + attackFlashTimer = 0.5f; // Visual flash duration + target.takeDamage((float)config.attackDamage); + + // Retaliation if target is adjacent melee military unit + if (!target.isDead() && target.config.isMilitary() && !config.isRanged()) { + int dist = Pathfinder::hexDistance(tileX, tileY, target.tileX, target.tileY); + if (dist <= target.config.attackRange) { + takeDamage(target.config.attackDamage * 0.5f); // 50% counterattack damage + } + } +} + +void Unit::takeDamage(float damage) +{ + health = std::max(0.0f, health - damage); + attackFlashTimer = 0.4f; +} diff --git a/unit.h b/unit.h new file mode 100644 index 0000000..3848826 --- /dev/null +++ b/unit.h @@ -0,0 +1,55 @@ +#ifndef UNIT_H +#define UNIT_H + +#include +#include +#include +#include +#include "unitconfig.h" +#include "terrain.h" +#include "matrix2d.h" + +enum class UnitState { + Idle, + Moving, + Attacking +}; + +class Unit { +public: + Unit(int uId, int pId, const UnitConfig &cfg, int startX, int startY, float groundZ); + + int id; + int playerId; + UnitConfig config; + int tileX; + int tileY; + QVector3D worldPos; + float health; + float maxHealth; + UnitState state; + + // Movement Path + std::vector> path; + size_t pathIndex; + float moveProgress; // 0.0 to 1.0 between hexes + int targetTileX; + int targetTileY; + + // Attack cooldown / flash + float attackFlashTimer; + + void update(float deltaTime, float tickDuration, float simSpeedMultiplier, + Matrix2D *mapPoints, int numPoints05); + void setPath(const std::vector> &newPath); + void stopMovement(); + + bool canAttack(const Unit &other) const; + void performAttack(Unit &target); + void takeDamage(float damage); + bool isDead() const { return health <= 0.001f; } + + static float getTerrainHeight(float worldX, float worldY, Matrix2D *mapPoints, int numPoints05); +}; + +#endif // UNIT_H diff --git a/unit.o b/unit.o new file mode 100644 index 0000000..275c38c Binary files /dev/null and b/unit.o differ diff --git a/unitconfig.cpp b/unitconfig.cpp new file mode 100644 index 0000000..893c3a2 --- /dev/null +++ b/unitconfig.cpp @@ -0,0 +1,68 @@ +#include "unitconfig.h" +#include "yamlparser.h" +#include +#include +#include + +UnitConfigManager::UnitConfigManager() +{ +} + +bool UnitConfigManager::loadFromDirectory(const QString &dirPath) +{ + QDir dir(dirPath); + if (!dir.exists()) { + qWarning() << "Unit config directory does not exist:" << dirPath; + return false; + } + + QStringList filters; + filters << "*.yaml" << "*.yml"; + QFileInfoList fileList = dir.entryInfoList(filters, QDir::Files); + + for (const QFileInfo &fileInfo : fileList) { + loadConfigFile(fileInfo.absoluteFilePath()); + } + + return !m_configs.isEmpty(); +} + +bool UnitConfigManager::loadConfigFile(const QString &filePath) +{ + YamlNode root = YamlParser::parseFile(filePath); + if (root.children.isEmpty()) { + qWarning() << "Failed to parse unit config YAML:" << filePath; + return false; + } + + UnitConfig cfg; + cfg.id = root.hasChild("id") ? root["id"].asString() : QFileInfo(filePath).baseName(); + cfg.name = root.hasChild("name") ? root["name"].asString() : cfg.id; + cfg.category = stringToUnitCategory(root.hasChild("category") ? root["category"].asString() : "bouwen"); + cfg.health = root.hasChild("health") ? root["health"].asInt(100) : 100; + cfg.movementSpeed = root.hasChild("movement_speed") ? root["movement_speed"].asFloat(2.0f) : 2.0f; + cfg.cost = root.hasChild("cost") ? root["cost"].asInt(20) : 20; + cfg.attackDamage = root.hasChild("attack_damage") ? root["attack_damage"].asInt(0) : 0; + cfg.attackRange = root.hasChild("attack_range") ? root["attack_range"].asInt(1) : 1; + cfg.iconPath = root.hasChild("icon") ? root["icon"].asString() : ""; + + if (root.hasChild("actions")) { + cfg.actions = root["actions"].listValues; + } + // Always ensure verplaatsen action is present + if (!cfg.actions.contains("verplaatsen")) { + cfg.actions.prepend("verplaatsen"); + } + + m_configs[cfg.id] = cfg; + return true; +} + +const UnitConfig* UnitConfigManager::getConfig(const QString &id) const +{ + auto it = m_configs.find(id); + if (it != m_configs.end()) { + return &it.value(); + } + return nullptr; +} diff --git a/unitconfig.h b/unitconfig.h new file mode 100644 index 0000000..f82f313 --- /dev/null +++ b/unitconfig.h @@ -0,0 +1,68 @@ +#ifndef UNITCONFIG_H +#define UNITCONFIG_H + +#include +#include +#include +#include + +enum class UnitCategory { + Military, // militair + Reconnaissance, // verkenning + Builder // bouwen +}; + +inline QString unitCategoryToString(UnitCategory cat) { + switch (cat) { + case UnitCategory::Military: return "Militair"; + case UnitCategory::Reconnaissance: return "Verkenning"; + case UnitCategory::Builder: return "Bouwen"; + } + return "Onbekend"; +} + +inline UnitCategory stringToUnitCategory(const QString &str) { + QString s = str.trimmed().toLower(); + if (s == "militair" || s == "military") return UnitCategory::Military; + if (s == "verkenning" || s == "recon" || s == "scout") return UnitCategory::Reconnaissance; + if (s == "bouwen" || s == "builder" || s == "civilian") return UnitCategory::Builder; + return UnitCategory::Builder; +} + +struct UnitConfig { + QString id; + QString name; + UnitCategory category; + int health; + float movementSpeed; + int cost; + int attackDamage; + int attackRange; // 1 for melee, >1 for ranged + QString iconPath; + QStringList actions; + + UnitConfig() + : id(""), name(""), category(UnitCategory::Builder), health(100), + movementSpeed(2.0f), cost(10), attackDamage(0), attackRange(1), + iconPath("") {} + + bool isRanged() const { return attackRange > 1; } + bool isMilitary() const { return category == UnitCategory::Military; } + bool canAttack() const { return actions.contains("aanvallen") || isMilitary(); } + bool canBuildCity() const { return actions.contains("steden_bouwen") || actions.contains("build_city"); } +}; + +class UnitConfigManager { +public: + UnitConfigManager(); + + bool loadFromDirectory(const QString &dirPath); + bool loadConfigFile(const QString &filePath); + const UnitConfig* getConfig(const QString &id) const; + const QMap& getAllConfigs() const { return m_configs; } + +private: + QMap m_configs; +}; + +#endif // UNITCONFIG_H diff --git a/unitconfig.o b/unitconfig.o new file mode 100644 index 0000000..540bd02 Binary files /dev/null and b/unitconfig.o differ diff --git a/unitrenderer.cpp b/unitrenderer.cpp new file mode 100644 index 0000000..9b5e3d3 --- /dev/null +++ b/unitrenderer.cpp @@ -0,0 +1,191 @@ +#include "unitrenderer.h" +#include +#include + +struct VertexPN { + float x, y, z; + float nx, ny, nz; +}; + +UnitRenderer::UnitRenderer() + : m_program(nullptr), m_vertexCount(0), m_initialized(false) +{ +} + +UnitRenderer::~UnitRenderer() +{ + if (m_program) delete m_program; +} + +void UnitRenderer::initialize() +{ + if (m_initialized) return; + initializeOpenGLFunctions(); + + m_program = new QOpenGLShaderProgram(); + m_program->addShaderFromSourceFile(QOpenGLShader::Vertex, "/home/bart/project/tiletest/shaders/unit.vert"); + m_program->addShaderFromSourceFile(QOpenGLShader::Fragment, "/home/bart/project/tiletest/shaders/unit.frag"); + m_program->link(); + + buildCylinderMesh(); + m_initialized = true; +} + +void UnitRenderer::buildCylinderMesh() +{ + std::vector vertices; + const int sectors = 24; + const float pi = 3.14159265359f; + + for (int i = 0; i < sectors; ++i) { + float a1 = 2.0f * pi * i / sectors; + float a2 = 2.0f * pi * (i + 1) / sectors; + + float c1 = std::cos(a1), s1 = std::sin(a1); + float c2 = std::cos(a2), s2 = std::sin(a2); + + // --- Side Quad (2 Triangles) --- + // Tri 1 + vertices.push_back({c1, s1, 0.0f, c1, s1, 0.0f}); + vertices.push_back({c2, s2, 0.0f, c2, s2, 0.0f}); + vertices.push_back({c2, s2, 1.0f, c2, s2, 0.0f}); + // Tri 2 + vertices.push_back({c1, s1, 0.0f, c1, s1, 0.0f}); + vertices.push_back({c2, s2, 1.0f, c2, s2, 0.0f}); + vertices.push_back({c1, s1, 1.0f, c1, s1, 0.0f}); + + // --- Top Cap --- + vertices.push_back({0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f}); + vertices.push_back({c1, s1, 1.0f, 0.0f, 0.0f, 1.0f}); + vertices.push_back({c2, s2, 1.0f, 0.0f, 0.0f, 1.0f}); + + // --- Bottom Cap --- + vertices.push_back({0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f}); + vertices.push_back({c2, s2, 0.0f, 0.0f, 0.0f, -1.0f}); + vertices.push_back({c1, s1, 0.0f, 0.0f, 0.0f, -1.0f}); + } + + m_vertexCount = (int)vertices.size(); + + m_vao.create(); + m_vao.bind(); + + m_vbo.create(); + m_vbo.bind(); + m_vbo.setUsagePattern(QOpenGLBuffer::StaticDraw); + m_vbo.allocate(vertices.data(), (int)(vertices.size() * sizeof(VertexPN))); + + m_program->enableAttributeArray(0); + m_program->setAttributeBuffer(0, GL_FLOAT, 0, 3, sizeof(VertexPN)); + + m_program->enableAttributeArray(1); + m_program->setAttributeBuffer(1, GL_FLOAT, 3 * sizeof(float), 3, sizeof(VertexPN)); + + m_vao.release(); + m_vbo.release(); +} + +void UnitRenderer::renderUnits( + const std::vector> &units, + const std::vector &cities, + const PlayerManager &players, + const QMatrix4x4 &projection, + const QMatrix4x4 &vMatrix, + const QVector3D &lightDir, + int selectedUnitId) +{ + if (!m_initialized || !m_program) return; + + glEnable(GL_DEPTH_TEST); + m_program->bind(); + m_program->setUniformValue("u_lightDir", lightDir); + m_vao.bind(); + + // 1. Render Cities (as stylized hexagonal / cylindrical city bastions) + for (const auto &c : cities) { + const Player *p = players.getPlayer(c.playerId); + QColor col = p ? p->primaryColor : QColor(100, 100, 100); + + QMatrix4x4 model; + model.translate(c.worldPos.x(), c.worldPos.y(), c.worldPos.z()); + model.scale(0.30f, 0.30f, 0.45f); + + QMatrix4x4 mvp = projection * vMatrix * model; + m_program->setUniformValue("modelMatrix", model); + m_program->setUniformValue("MVP", mvp); + m_program->setUniformValue("u_color", QVector4D(col.redF(), col.greenF(), col.blueF(), 1.0f)); + + glDrawArrays(GL_TRIANGLES, 0, m_vertexCount); + } + + // 2. Render Units (Cylinders in player primary color) + const float unitRadius = 0.18f; + const float unitHeight = 0.36f; + + for (const auto &unit : units) { + if (unit->isDead()) continue; + + const Player *player = players.getPlayer(unit->playerId); + QColor col = player ? player->primaryColor : QColor(200, 200, 200); + + // Flash white/red if attacked + if (unit->attackFlashTimer > 0.0f) { + float f = unit->attackFlashTimer / 0.5f; + col = QColor( + (int)(col.red() * (1.0f - f) + 255 * f), + (int)(col.green() * (1.0f - f) + 255 * f), + (int)(col.blue() * (1.0f - f) + 255 * f) + ); + } + + QMatrix4x4 model; + model.translate(unit->worldPos.x(), unit->worldPos.y(), unit->worldPos.z()); + model.scale(unitRadius, unitRadius, unitHeight); + + QMatrix4x4 mvp = projection * vMatrix * model; + m_program->setUniformValue("modelMatrix", model); + m_program->setUniformValue("MVP", mvp); + m_program->setUniformValue("u_color", QVector4D(col.redF(), col.greenF(), col.blueF(), 1.0f)); + + glDrawArrays(GL_TRIANGLES, 0, m_vertexCount); + + // Ground selection base disk if selected + if (unit->id == selectedUnitId) { + QMatrix4x4 selModel; + selModel.translate(unit->worldPos.x(), unit->worldPos.y(), unit->worldPos.z()); + selModel.scale(unitRadius * 1.6f, unitRadius * 1.6f, 0.04f); + + QMatrix4x4 selMvp = projection * vMatrix * selModel; + m_program->setUniformValue("modelMatrix", selModel); + m_program->setUniformValue("MVP", selMvp); + m_program->setUniformValue("u_color", QVector4D(1.0f, 0.9f, 0.2f, 0.9f)); + + glDrawArrays(GL_TRIANGLES, 0, m_vertexCount); + } + } + + m_vao.release(); + m_program->release(); +} + +QPointF UnitRenderer::projectToScreen( + const QVector3D &worldPoint, + const QMatrix4x4 &projection, + const QMatrix4x4 &vMatrix, + int viewportW, int viewportH, + bool &inFront) +{ + QMatrix4x4 vp = projection * vMatrix; + QVector4D clip = vp.map(QVector4D(worldPoint, 1.0f)); + + inFront = (clip.w() > 0.01f); + if (!inFront) { + return QPointF(-9999, -9999); + } + + QVector3D ndc = clip.toVector3DAffine(); + float sx = (ndc.x() + 1.0f) * 0.5f * viewportW; + float sy = (1.0f - (ndc.y() + 1.0f) * 0.5f) * viewportH; + + return QPointF(sx, sy); +} diff --git a/unitrenderer.h b/unitrenderer.h new file mode 100644 index 0000000..56f5c20 --- /dev/null +++ b/unitrenderer.h @@ -0,0 +1,51 @@ +#ifndef UNITRENDERER_H +#define UNITRENDERER_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "unit.h" +#include "player.h" +#include "city.h" + +class UnitRenderer : protected QOpenGLFunctions_4_0_Core { +public: + UnitRenderer(); + ~UnitRenderer(); + + void initialize(); + void renderUnits( + const std::vector> &units, + const std::vector &cities, + const PlayerManager &players, + const QMatrix4x4 &projection, + const QMatrix4x4 &vMatrix, + const QVector3D &lightDir, + int selectedUnitId + ); + + static QPointF projectToScreen( + const QVector3D &worldPoint, + const QMatrix4x4 &projection, + const QMatrix4x4 &vMatrix, + int viewportW, int viewportH, + bool &inFront + ); + +private: + void buildCylinderMesh(); + + QOpenGLShaderProgram *m_program; + QOpenGLBuffer m_vbo; + QOpenGLVertexArrayObject m_vao; + int m_vertexCount; + bool m_initialized; +}; + +#endif // UNITRENDERER_H diff --git a/unitrenderer.o b/unitrenderer.o new file mode 100644 index 0000000..00ab373 Binary files /dev/null and b/unitrenderer.o differ diff --git a/yamlparser.cpp b/yamlparser.cpp new file mode 100644 index 0000000..c5b4e61 --- /dev/null +++ b/yamlparser.cpp @@ -0,0 +1,61 @@ +#include "yamlparser.h" +#include +#include + +YamlNode YamlParser::parseFile(const QString &filePath) +{ + QFile file(filePath); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + return YamlNode(); + } + QTextStream in(&file); + return parseString(in.readAll()); +} + +YamlNode YamlParser::parseString(const QString &content) +{ + YamlNode root; + QStringList lines = content.split('\n'); + QString currentKey = ""; + + for (int i = 0; i < lines.size(); ++i) { + QString line = lines[i]; + + // Strip comments + int commentIdx = line.indexOf('#'); + if (commentIdx != -1) { + line = line.left(commentIdx); + } + + QString trimmed = line.trimmed(); + if (trimmed.isEmpty()) continue; + + // Check for list item: "- something" + if (trimmed.startsWith("- ")) { + QString itemVal = trimmed.mid(2).trimmed(); + if (!currentKey.isEmpty()) { + root.children[currentKey].listValues.append(itemVal); + } + continue; + } + + // Check for key: value + int colonIdx = trimmed.indexOf(':'); + if (colonIdx != -1) { + QString key = trimmed.left(colonIdx).trimmed(); + QString val = trimmed.mid(colonIdx + 1).trimmed(); + + // Strip enclosing quotes if any + if ((val.startsWith('"') && val.endsWith('"')) || (val.startsWith('\'') && val.endsWith('\''))) { + val = val.mid(1, val.length() - 2); + } + + currentKey = key; + YamlNode node; + node.value = val; + root.children[key] = node; + } + } + + return root; +} diff --git a/yamlparser.h b/yamlparser.h new file mode 100644 index 0000000..55ff37a --- /dev/null +++ b/yamlparser.h @@ -0,0 +1,36 @@ +#ifndef YAMLPARSER_H +#define YAMLPARSER_H + +#include +#include +#include +#include + +class YamlNode { +public: + QString value; + QStringList listValues; + QMap children; + + bool hasChild(const QString &key) const { return children.contains(key); } + YamlNode operator[](const QString &key) const { return children.value(key); } + YamlNode& operator[](const QString &key) { return children[key]; } + + QString asString(const QString &def = "") const { return value.isEmpty() ? def : value; } + int asInt(int def = 0) const { bool ok; int v = value.toInt(&ok); return ok ? v : def; } + float asFloat(float def = 0.0f) const { bool ok; float v = value.toFloat(&ok); return ok ? v : def; } + bool asBool(bool def = false) const { + QString s = value.trimmed().toLower(); + if (s == "true" || s == "yes" || s == "1") return true; + if (s == "false" || s == "no" || s == "0") return false; + return def; + } +}; + +class YamlParser { +public: + static YamlNode parseFile(const QString &filePath); + static YamlNode parseString(const QString &content); +}; + +#endif // YAMLPARSER_H diff --git a/yamlparser.o b/yamlparser.o new file mode 100644 index 0000000..3af45d4 Binary files /dev/null and b/yamlparser.o differ