diff --git a/.ai/memory/build/dependencies.md b/.ai/memory/build/dependencies.md
index 25d5bcd..31f8504 100644
--- a/.ai/memory/build/dependencies.md
+++ b/.ai/memory/build/dependencies.md
@@ -5,6 +5,13 @@ dependencies, and GoogleTest is supplied by the `tests` feature. The optional
`full-drivers` feature adds a larger set of GDAL drivers. The manifest baseline
and GDAL minimum version must be considered together when updating GDAL.
+GDAL 3.12 renamed the OGR geometry accessors to snake_case (`get_Area()`,
+`get_Length()`); the old camelCase names such as `getArea()` are removed.
+`get_Area()` is not on the `OGRGeometry` base at all (only on `OGRCurve` and
+`OGRSurface`), and a disjoint polygon `Intersection()` returns `POLYGON EMPTY`,
+which still reports dimension 2 — check `IsEmpty()` before using `getDimension()`
+or a surface cast on intersection results.
+
MSBuild resolves vcpkg from the `MwgVcpkgRoot` property, `MWG_VCPKG_ROOT`,
the sibling `..\vcpkg` directory, then `VCPKG_ROOT`. It installs to a
platform-specific tree because vcpkg can remove another triplet from a shared
diff --git a/.ai/memory/testing/testing.md b/.ai/memory/testing/testing.md
index b9b7978..232afeb 100644
--- a/.ai/memory/testing/testing.md
+++ b/.ai/memory/testing/testing.md
@@ -24,6 +24,13 @@ The C# host calls ActiveX methods late-bound. An invalid layer ID surfaces as
The host assertion accepts this mapping as well as `COMException`; see
`samples/CSharp/ActiveXTestHost/MainForm.cs`.
+GeoPackage fixture layers need an explicit geometry type: a layer created with
+`wkbUnknown` maps to `ShapeType::NullShape` and is rejected as non-polygonal by
+scene creation, while `wkbMultiPolygon` layers map to `ShapeType::Polygon` and
+accept multipart WKT. The GPKG driver also writes self-intersecting polygon WKT
+without validation, so invalid-geometry behavior is testable through a generated
+GeoPackage.
+
## References
- [Testing guide](../../../docs/TESTING.md)
diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md
index ed1a4e8..a6e760d 100644
--- a/docs/ARCHITECTURE.md
+++ b/docs/ARCHITECTURE.md
@@ -288,7 +288,11 @@ Rendering consumes backend-neutral input prepared from core data; neither the
coordinator nor a backend loads datasets or implements GIS rules. For each
visible map layer, Core sequentially reads Polygon, MultiPolygon,
CurvePolygon, and MultiSurface features into an owned vector scene,
-linearizing curved geometries. `ViewTransform` captures the `Viewport` once
+linearizing curved geometries. Scene creation also triangulates every polygon
+into an indexed, backend-neutral triangle mesh with the constrained Delaunay
+triangulation of GDAL/OGR and GEOS, retained per polygon so a backend never
+triangulates per frame; a polygon that cannot be triangulated keeps its rings
+and has no mesh. `ViewTransform` captures the `Viewport` once
per frame so both backends transform every vertex of every layer with the
same arithmetic as `Viewport::MapToScreen`; GDI draws a screen position in
the pixel that contains it (`ToPixel`). One backend draws the whole frame.
diff --git a/src/MapWinGIS.Core/CMakeLists.txt b/src/MapWinGIS.Core/CMakeLists.txt
index c5e1a3c..6644b34 100644
--- a/src/MapWinGIS.Core/CMakeLists.txt
+++ b/src/MapWinGIS.Core/CMakeLists.txt
@@ -23,6 +23,7 @@ set(MWG_CORE_PUBLIC_HEADERS
include/mapwingis/map/MapView.h
include/mapwingis/map/Viewport.h
include/mapwingis/rendering/MapRenderScene.h
+ include/mapwingis/rendering/PolygonTriangulation.h
include/mapwingis/rendering/VectorRenderScene.h
include/mapwingis/vector/FieldInfo.h
include/mapwingis/vector/VectorDataset.h
@@ -48,6 +49,7 @@ set(MWG_CORE_SOURCES
src/map/MapView.cpp
src/map/Viewport.cpp
src/rendering/MapRenderScene.cpp
+ src/rendering/PolygonTriangulation.cpp
src/rendering/VectorRenderScene.cpp
src/vector/VectorDataset.cpp
)
diff --git a/src/MapWinGIS.Core/MapWinGIS.Core.vcxproj b/src/MapWinGIS.Core/MapWinGIS.Core.vcxproj
index 23a6914..1d73ffb 100644
--- a/src/MapWinGIS.Core/MapWinGIS.Core.vcxproj
+++ b/src/MapWinGIS.Core/MapWinGIS.Core.vcxproj
@@ -67,6 +67,7 @@
+
@@ -88,6 +89,7 @@
+
diff --git a/src/MapWinGIS.Core/include/mapwingis/rendering/PolygonTriangulation.h b/src/MapWinGIS.Core/include/mapwingis/rendering/PolygonTriangulation.h
new file mode 100644
index 0000000..6c9ee4c
--- /dev/null
+++ b/src/MapWinGIS.Core/include/mapwingis/rendering/PolygonTriangulation.h
@@ -0,0 +1,40 @@
+// SPDX-License-Identifier: MPL-2.0
+
+/**
+ * @file PolygonTriangulation.h
+ * @brief Declares reusable polygon triangulation for rendering.
+ *
+ * Converts a backend-neutral polygon with optional interior rings into an
+ * indexed triangle mesh using the constrained Delaunay triangulation provided
+ * by GDAL/OGR and GEOS. The mesh stays in the source coordinate system and
+ * contains no graphics-API or platform types.
+ */
+#pragma once
+
+#include "mapwingis/core/Api.h"
+#include "mapwingis/rendering/VectorRenderScene.h"
+
+namespace mwg::rendering
+{
+ /// Triangulates a polygon with zero or more interior rings into a 2D
+ /// fill mesh.
+ ///
+ /// The first ring of \p polygon is the exterior boundary; any further
+ /// rings are interior holes. Rings do not need to repeat their first
+ /// point at the end. Interior rings with fewer than three points are
+ /// ignored. Duplicate consecutive vertices are tolerated.
+ ///
+ /// The triangulation is the constrained Delaunay triangulation from
+ /// OGRGeometry::ConstrainedDelaunayTriangulation(), which preserves the
+ /// polygon boundaries. Vertices are shared through the index buffer and
+ /// all triangles have counter-clockwise winding. The result is the 2D
+ /// rendering mesh; no Z or M values are computed.
+ ///
+ /// Invalid input is never repaired: self-intersecting or otherwise
+ /// invalid polygons are rejected instead of being reinterpreted.
+ /// \throws InvalidArgument when the polygon has no exterior ring of at
+ /// least three points or contains non-finite coordinates.
+ /// \throws CoreError when the polygon is invalid or the triangulation
+ /// fails or produces no triangles.
+ [[nodiscard]] MWG_CORE_API TriangleMesh TriangulatePolygon(const Polygon& polygon);
+}
diff --git a/src/MapWinGIS.Core/include/mapwingis/rendering/VectorRenderScene.h b/src/MapWinGIS.Core/include/mapwingis/rendering/VectorRenderScene.h
index 0ced468..6a63d73 100644
--- a/src/MapWinGIS.Core/include/mapwingis/rendering/VectorRenderScene.h
+++ b/src/MapWinGIS.Core/include/mapwingis/rendering/VectorRenderScene.h
@@ -10,6 +10,7 @@
#pragma once
#include
+#include
#include
#include "mapwingis/core/Api.h"
@@ -35,6 +36,20 @@ namespace mwg
/// A polygon and its rings. The first ring is the exterior boundary.
using Polygon = std::vector;
+ /// A backend-neutral triangulated 2D fill mesh for one polygon.
+ ///
+ /// Vertices stay in the source layer's coordinate system. Indices
+ /// occur in groups of three that each describe one triangle; every
+ /// index references a valid vertex. Triangles produced by
+ /// TriangulatePolygon share vertices through the index buffer and
+ /// have consistent counter-clockwise winding. The representation
+ /// contains no graphics-API, Windows, COM, ATL, or MFC types.
+ struct MWG_CORE_API TriangleMesh
+ {
+ std::vector vertices;
+ std::vector indices;
+ };
+
/// Owned polygon geometry and its source-coordinate bounds.
struct MWG_CORE_API VectorRenderScene
{
@@ -42,6 +57,13 @@ namespace mwg
/// Feature count of the layer used to create this scene.
std::int64_t sourceFeatureCount = 0;
std::vector polygons;
+ /// Triangulated fill meshes prepared once when the scene is
+ /// created, aligned with polygons: fillMeshes[i] belongs to
+ /// polygons[i]. An entry is std::nullopt when that polygon could
+ /// not be triangulated; its rings in polygons remain available
+ /// for outline drawing and for backends that draw rings directly,
+ /// such as GDI.
+ std::vector> fillMeshes;
};
/// Copies polygon geometry from a layer into backend-neutral scene data.
diff --git a/src/MapWinGIS.Core/src/rendering/PolygonTriangulation.cpp b/src/MapWinGIS.Core/src/rendering/PolygonTriangulation.cpp
new file mode 100644
index 0000000..12a6a38
--- /dev/null
+++ b/src/MapWinGIS.Core/src/rendering/PolygonTriangulation.cpp
@@ -0,0 +1,235 @@
+// SPDX-License-Identifier: MPL-2.0
+
+/**
+ * @file PolygonTriangulation.cpp
+ * @brief Implements polygon triangulation through OGR and GEOS.
+ *
+ * Builds a temporary OGR polygon from the backend-neutral rings, validates
+ * it, and converts the constrained Delaunay triangulation into an indexed
+ * triangle mesh. All OGR geometry is owned through RAII handles.
+ */
+#include "mapwingis/rendering/PolygonTriangulation.h"
+
+#include
+#include
+#include
+#include