From ef0184c5e4cfc7d29c448c7e67c9d768f3b77583 Mon Sep 17 00:00:00 2001 From: Don McCurdy <1848368+donmccurdy@users.noreply.github.com> Date: Tue, 21 Apr 2026 11:37:27 -0400 Subject: [PATCH 1/5] Add EXT_mesh_polygon --- .../2.0/Vendor/EXT_mesh_polygon/README.md | 154 ++++++++++++++++++ .../figures/polygon-encoding.png | Bin 0 -> 23912 bytes 2 files changed, 154 insertions(+) create mode 100644 extensions/2.0/Vendor/EXT_mesh_polygon/README.md create mode 100644 extensions/2.0/Vendor/EXT_mesh_polygon/figures/polygon-encoding.png diff --git a/extensions/2.0/Vendor/EXT_mesh_polygon/README.md b/extensions/2.0/Vendor/EXT_mesh_polygon/README.md new file mode 100644 index 0000000000..b527ad3513 --- /dev/null +++ b/extensions/2.0/Vendor/EXT_mesh_polygon/README.md @@ -0,0 +1,154 @@ + + +# EXT_mesh_polygon + +## Contributors + +- Don McCurdy, Bentley Systems, [@donmccurdy](https://github.com/donmccurdy) +- TODO + +## Status + +Draft + +## Dependencies + +Written against the glTF 2.0 spec. + +## Overview + +Extends glTF mesh primitives, adding an encoding of polygon primitive topology, including triangulation (indices) for backwards-compatible rendering. While the core glTF 2.0 specification already allows polygons to be triangulated and encoded as TRIANGLES, TRIANGLE_STRIP, or TRIANGLE_FAN primitive modes, the original topology — which triangles together form a polygon? — is lost without the additional specification and metadata provided by this extension. + +## Extending Mesh Primitives + +The `EXT_mesh_polygon` extension may be added to a mesh primitive, indicating that the primitive represents a series of polygons. + +An extended mesh primitive **MUST** include `primitive.mode = 4` ("TRIANGLES"), `primitive.mode = 5` ("TRIANGLE_STRIP"), or `primitive.mode = 6` ("TRIANGLE_FAN"). + +An extended mesh primitive **MUST** include `indices`. Indices for each polygon must be contiguous: for a polygon composed of 4 triangles, indices defining these triangles must occupy a single contiguous range within the primitive's indices accessor. Primitive vertices associated with the polygon are not required to be contiguous. + +The `EXT_mesh_polygon` extension includes the following additional properties, all required. + +### count + +Integer number of polygons encoded in the mesh primitive. + +- **Type:** `number` +- **Required:** ✓ Yes +- **Minimum:** ≥ 1 + +### loopIndices + +Index of the accessor containing indices of the polygons' exterior and interior loops. The accessor **MUST** have `SCALAR` type and an unsigned integer component type. + +A polygon is composed of 1 or more loops, encoded as indices equivalent to `primitive.mode = 2` ("LINE_LOOP") topology. Each loop must be separated by the "primitive restart" value applicable to the accessor type: + +| `accessor.componentType` | restart value | +| ---------------------------- | ------------------------- | +| `5121` (UNSIGNED_BYTE) | `255` (0xFF) | +| `5123` (UNSIGNED_SHORT) | `65535` (0xFFFF) | +| `5125` (UNSIGNED_INT) | `4294967295` (0xFFFFFFFF) | + +The first loop in each polygon represents the polygon's exterior ring, or boundary. Additional loops, if any, represent interior rings ("holes") within the exterior ring. Polygons must be fully-connected — holes cannot intersect the exterior ring, and additional exterior rings ("islands") are not allowed, whether outside the exterior ring or within holes. + +- **Type:** `number` +- **Required:** ✓ Yes +- **Minimum:** ≥ 0 + +### loopIndicesOffsets + +Index of the accessor containing one integer offset per polygon in the primitive, indicating the first index of the first linear ring associated with that polygon. The accessor **MUST** have `SCALAR` type and an unsigned integer component type, and the accessor's `count` **MUST** be the same as `EXT_mesh_polygon.count` for the primitive. + +All loops associated with a polygon MUST be contiguous, one (1) exterior ring followed immediately by zero or more interior rings.\ + +> **Implementation note:** The range of loop indices for the `nth` polygon is `loopIndicesOffsets[n]` to `loopIndicesOffsets[n+1]` if `n < count - 1`, otherwise `loopIndicesOffsets[n]` to the end of the `loopIndices` accessor. + +- **Type:** `number` +- **Required:** ✓ Yes +- **Minimum:** ≥ 0 + +### indicesOffsets + +Index of the accessor containing one integer offset per polygon in the primitive, indicating the first index of the first triangle associated with that polygon. The accessor **MUST** have `SCALAR` type and an unsigned integer component type, and the accessor's `count` **MUST** be the same as `EXT_mesh_polygon.count` for the primitive. + +Indices for each polygon **MUST** be contiguous. + +- **Type:** `number` +- **Required:** ✓ Yes +- **Minimum:** ≥ 0 + +## Additional Restrictions + +Triangles in the extended mesh primitive **MUST** be associated with exactly one polygon. Loose triangles cannot be included, and a single triangle cannot be associated with multiple polygons. + +Polygon `loopIndices` values **MUST** be associated with at least one triangle. Exterior and interior loops may not contain additional vertex indices missing from triangulation. + +Polygon `loopIndices` values **MUST** be unique within each exterior or interior loop. The same vertex index cannot be used twice within a loop. + +> **Implementation note:** As in LINE_LOOP topology, the first and last indices are defined to be connected by a line segment. Unlike in some geospatial formats, it is NOT necessary that the first index be repeated at the end of the loop to indicate a closed ring, and doing so would violate the requirement above. + +## Example + +_This section is non-normative._ + +The JSON example below shows a mesh having one mesh primitive, which contains 100 polygon primitives. The `indices` accessor defines the triangle indices required to draw the polygons, and the `loopIndices` accessor defines the `LINE_LOOP` indices delimiting the exterior (and interior, if holes are present) boundaries of each polygon. The `loopIndicesOffsets` and `indicesOffsets` accessors enable random access, allowing implementations to immediately find the particular triangle and loop indices associated with the Nth polygon. + +```jsonc +{ + "extensionsUsed": ["EXT_mesh_polygon"], + ... + "meshes": [{ + "name": "MyMesh", + "primitives": [{ + "mode": 4, + "attributes": { + "POSITION": 0, + }, + "indices": 1, + "extensions": { + "EXT_mesh_polygon": { + "count": 100, + "loopIndices": 2, + "loopIndicesOffsets": 3, + "indicesOffsets": 4 + } + } + }] + }] +} +``` + +Consider a simple mesh primitive containing three polygons, one with a single hole, and two without: + +![Polygon encoding](./figures/polygon-encoding) + +One valid encoding of `EXT_mesh_polygon` for this polygon set, based on `UNSIGNED_SHORT` indices and restart values, would be as follows: + +```plaintext +indices +0 2 1 / 0 3 2 / 0 4 3 +5 7 6 / 6 8 7 +9 17 18 / 9 18 10 / 10 18 11 / ... + +indicesOffsets +0 3 6 + +loopIndices +0 1 2 3 4 0xFFFF +5 6 7 8 0xFFFF +9 10 11 12 13 14 15 16 / 17 18 19 20 + +loopIndiceOffsets +0 6 11 +``` + +## JSON Schema + +The `"EXT_mesh_polygon"` string must be added to the root-level `extensionsUsed` array. Where preservation of polygon topology — not just display of equivalent triangles — is required, the string should also be added to the root-level `extensionsRequired` array. As the extension is intentionally backwards-compatible for rendering purposes, the extension is expected to be optional in most cases. + +## Known Implementations + +- TODO diff --git a/extensions/2.0/Vendor/EXT_mesh_polygon/figures/polygon-encoding.png b/extensions/2.0/Vendor/EXT_mesh_polygon/figures/polygon-encoding.png new file mode 100644 index 0000000000000000000000000000000000000000..1ba28b87015a12acfbacccd6b90198c2c742ec8f GIT binary patch literal 23912 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 Date: Tue, 21 Apr 2026 12:01:21 -0400 Subject: [PATCH 2/5] cleanup --- .../2.0/Vendor/EXT_mesh_polygon/README.md | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/extensions/2.0/Vendor/EXT_mesh_polygon/README.md b/extensions/2.0/Vendor/EXT_mesh_polygon/README.md index b527ad3513..8ca365d8b6 100644 --- a/extensions/2.0/Vendor/EXT_mesh_polygon/README.md +++ b/extensions/2.0/Vendor/EXT_mesh_polygon/README.md @@ -20,7 +20,7 @@ Written against the glTF 2.0 spec. ## Overview -Extends glTF mesh primitives, adding an encoding of polygon primitive topology, including triangulation (indices) for backwards-compatible rendering. While the core glTF 2.0 specification already allows polygons to be triangulated and encoded as TRIANGLES, TRIANGLE_STRIP, or TRIANGLE_FAN primitive modes, the original topology — which triangles together form a polygon? — is lost without the additional specification and metadata provided by this extension. +Extends glTF mesh primitives, adding an encoding of polygon primitive topology, including triangle indices for backwards-compatible rendering. While the core glTF 2.0 specification already allows polygons to be triangulated and encoded as TRIANGLES, TRIANGLE_STRIP, or TRIANGLE_FAN primitive modes, the original topology would be lost without the additional specification and metadata provided by this extension. ## Extending Mesh Primitives @@ -28,7 +28,7 @@ The `EXT_mesh_polygon` extension may be added to a mesh primitive, indicating th An extended mesh primitive **MUST** include `primitive.mode = 4` ("TRIANGLES"), `primitive.mode = 5` ("TRIANGLE_STRIP"), or `primitive.mode = 6` ("TRIANGLE_FAN"). -An extended mesh primitive **MUST** include `indices`. Indices for each polygon must be contiguous: for a polygon composed of 4 triangles, indices defining these triangles must occupy a single contiguous range within the primitive's indices accessor. Primitive vertices associated with the polygon are not required to be contiguous. +An extended mesh primitive **MUST** include `indices`. Indices for each polygon must be contiguous: for a polygon composed of 4 triangles, indices defining these triangles must occupy a single range within the primitive's indices accessor. Only indices, not vertex attributes, are required to be contiguous. The `EXT_mesh_polygon` extension includes the following additional properties, all required. @@ -42,9 +42,11 @@ Integer number of polygons encoded in the mesh primitive. ### loopIndices -Index of the accessor containing indices of the polygons' exterior and interior loops. The accessor **MUST** have `SCALAR` type and an unsigned integer component type. +Index of an accessor containing indices of the polygons' exterior and interior loops. The accessor **MUST** have `SCALAR` type and an unsigned integer component type. -A polygon is composed of 1 or more loops, encoded as indices equivalent to `primitive.mode = 2` ("LINE_LOOP") topology. Each loop must be separated by the "primitive restart" value applicable to the accessor type: +A polygon is composed of 1 or more loops, encoded as indices equivalent to `primitive.mode = 2` ("LINE_LOOP") topology. The first loop in each polygon represents the polygon's exterior ring, or boundary. Additional loops, if any, represent interior rings ("holes") within the polygon. Polygons must be fully-connected: holes cannot intersect the exterior ring, and additional exterior rings ("islands") are not allowed, whether outside the exterior ring or within holes. + +Each loop must be separated by a primitive restart value, including loops associated with different polygons. Primitive restart values applicable to each accessor type are: | `accessor.componentType` | restart value | | ---------------------------- | ------------------------- | @@ -52,17 +54,15 @@ A polygon is composed of 1 or more loops, encoded as indices equivalent to `prim | `5123` (UNSIGNED_SHORT) | `65535` (0xFFFF) | | `5125` (UNSIGNED_INT) | `4294967295` (0xFFFFFFFF) | -The first loop in each polygon represents the polygon's exterior ring, or boundary. Additional loops, if any, represent interior rings ("holes") within the exterior ring. Polygons must be fully-connected — holes cannot intersect the exterior ring, and additional exterior rings ("islands") are not allowed, whether outside the exterior ring or within holes. - - **Type:** `number` - **Required:** ✓ Yes - **Minimum:** ≥ 0 ### loopIndicesOffsets -Index of the accessor containing one integer offset per polygon in the primitive, indicating the first index of the first linear ring associated with that polygon. The accessor **MUST** have `SCALAR` type and an unsigned integer component type, and the accessor's `count` **MUST** be the same as `EXT_mesh_polygon.count` for the primitive. +Index of an accessor containing one integer offset per polygon in the primitive, indicating the first index of the first linear ring associated with that polygon. The accessor **MUST** have `SCALAR` type and an unsigned integer component type, and the accessor's `count` **MUST** be the same as `EXT_mesh_polygon.count` for the primitive. -All loops associated with a polygon MUST be contiguous, one (1) exterior ring followed immediately by zero or more interior rings.\ +All loops associated with a polygon MUST be contiguous: one exterior ring followed immediately by zero or more interior rings. > **Implementation note:** The range of loop indices for the `nth` polygon is `loopIndicesOffsets[n]` to `loopIndicesOffsets[n+1]` if `n < count - 1`, otherwise `loopIndicesOffsets[n]` to the end of the `loopIndices` accessor. @@ -72,7 +72,7 @@ All loops associated with a polygon MUST be contiguous, one (1) exterior ring fo ### indicesOffsets -Index of the accessor containing one integer offset per polygon in the primitive, indicating the first index of the first triangle associated with that polygon. The accessor **MUST** have `SCALAR` type and an unsigned integer component type, and the accessor's `count` **MUST** be the same as `EXT_mesh_polygon.count` for the primitive. +Index of an accessor containing one integer offset per polygon in the primitive, indicating the first index of the first triangle associated with that polygon. The accessor **MUST** have `SCALAR` type and an unsigned integer component type, and the accessor's `count` **MUST** be the same as `EXT_mesh_polygon.count` for the primitive. Indices for each polygon **MUST** be contiguous. @@ -123,7 +123,7 @@ The JSON example below shows a mesh having one mesh primitive, which contains 10 Consider a simple mesh primitive containing three polygons, one with a single hole, and two without: -![Polygon encoding](./figures/polygon-encoding) +![Polygon encoding](./figures/polygon-encoding.png) One valid encoding of `EXT_mesh_polygon` for this polygon set, based on `UNSIGNED_SHORT` indices and restart values, would be as follows: From 6a2f50ed82266f554f6ca840dc74017ce19a0b29 Mon Sep 17 00:00:00 2001 From: Don McCurdy <1848368+donmccurdy@users.noreply.github.com> Date: Tue, 28 Apr 2026 13:58:43 -0400 Subject: [PATCH 3/5] Update JSON example, use Markdown callout boxes for implementation notes Co-authored-by: Sean Lilley Co-authored-by: Adam Morris --- .../2.0/Vendor/EXT_mesh_polygon/README.md | 23 ++++--------------- 1 file changed, 4 insertions(+), 19 deletions(-) diff --git a/extensions/2.0/Vendor/EXT_mesh_polygon/README.md b/extensions/2.0/Vendor/EXT_mesh_polygon/README.md index 8ca365d8b6..2b6a02dfc4 100644 --- a/extensions/2.0/Vendor/EXT_mesh_polygon/README.md +++ b/extensions/2.0/Vendor/EXT_mesh_polygon/README.md @@ -64,7 +64,8 @@ Index of an accessor containing one integer offset per polygon in the primitive, All loops associated with a polygon MUST be contiguous: one exterior ring followed immediately by zero or more interior rings. -> **Implementation note:** The range of loop indices for the `nth` polygon is `loopIndicesOffsets[n]` to `loopIndicesOffsets[n+1]` if `n < count - 1`, otherwise `loopIndicesOffsets[n]` to the end of the `loopIndices` accessor. +> [!NOTE] +> The range of loop indices for the `nth` polygon is `loopIndicesOffsets[n]` to `loopIndicesOffsets[n+1]` if `n < count - 1`, otherwise `loopIndicesOffsets[n]` to the end of the `loopIndices` accessor. - **Type:** `number` - **Required:** ✓ Yes @@ -88,7 +89,8 @@ Polygon `loopIndices` values **MUST** be associated with at least one triangle. Polygon `loopIndices` values **MUST** be unique within each exterior or interior loop. The same vertex index cannot be used twice within a loop. -> **Implementation note:** As in LINE_LOOP topology, the first and last indices are defined to be connected by a line segment. Unlike in some geospatial formats, it is NOT necessary that the first index be repeated at the end of the loop to indicate a closed ring, and doing so would violate the requirement above. +> [!NOTE] +> As in LINE_LOOP topology, the first and last indices are defined to be connected by a line segment. Unlike in some geospatial formats, it is NOT necessary that the first index be repeated at the end of the loop to indicate a closed ring, and doing so would violate the requirement above. ## Example @@ -127,23 +129,6 @@ Consider a simple mesh primitive containing three polygons, one with a single ho One valid encoding of `EXT_mesh_polygon` for this polygon set, based on `UNSIGNED_SHORT` indices and restart values, would be as follows: -```plaintext -indices -0 2 1 / 0 3 2 / 0 4 3 -5 7 6 / 6 8 7 -9 17 18 / 9 18 10 / 10 18 11 / ... - -indicesOffsets -0 3 6 - -loopIndices -0 1 2 3 4 0xFFFF -5 6 7 8 0xFFFF -9 10 11 12 13 14 15 16 / 17 18 19 20 - -loopIndiceOffsets -0 6 11 -``` ## JSON Schema From cf47f385f6dd6425a3e890cb81c3b73b49cb41d4 Mon Sep 17 00:00:00 2001 From: Don McCurdy <1848368+donmccurdy@users.noreply.github.com> Date: Wed, 20 May 2026 11:31:48 -0400 Subject: [PATCH 4/5] Use LINE_LOOP indices on primitive, TRIANGLES indices as extension property. --- .../2.0/Vendor/EXT_mesh_polygon/README.md | 84 +++++++++++-------- 1 file changed, 47 insertions(+), 37 deletions(-) diff --git a/extensions/2.0/Vendor/EXT_mesh_polygon/README.md b/extensions/2.0/Vendor/EXT_mesh_polygon/README.md index 2b6a02dfc4..0485874bc3 100644 --- a/extensions/2.0/Vendor/EXT_mesh_polygon/README.md +++ b/extensions/2.0/Vendor/EXT_mesh_polygon/README.md @@ -26,11 +26,25 @@ Extends glTF mesh primitives, adding an encoding of polygon primitive topology, The `EXT_mesh_polygon` extension may be added to a mesh primitive, indicating that the primitive represents a series of polygons. -An extended mesh primitive **MUST** include `primitive.mode = 4` ("TRIANGLES"), `primitive.mode = 5` ("TRIANGLE_STRIP"), or `primitive.mode = 6` ("TRIANGLE_FAN"). +An extended mesh primitive **MUST** include `primitive.mode = 2` ("LINE_LOOP"). -An extended mesh primitive **MUST** include `indices`. Indices for each polygon must be contiguous: for a polygon composed of 4 triangles, indices defining these triangles must occupy a single range within the primitive's indices accessor. Only indices, not vertex attributes, are required to be contiguous. +Each polygon is composed of 1 or more loops. The first loop in each polygon represents the polygon's exterior ring, or boundary. Additional loops, if any, represent interior rings ("holes") within the polygon. Polygons must be fully-connected: holes cannot intersect the exterior ring, and additional exterior rings ("islands") are not allowed, whether outside the exterior ring or within holes. -The `EXT_mesh_polygon` extension includes the following additional properties, all required. +Each loop must be separated by a primitive restart value, including loops associated with different polygons. Primitive restart values applicable to each accessor type are: + +| `accessor.componentType` | restart value | +| ---------------------------- | ------------------------- | +| `5121` (UNSIGNED_BYTE) | `255` (0xFF) | +| `5123` (UNSIGNED_SHORT) | `65535` (0xFFFF) | +| `5125` (UNSIGNED_INT) | `4294967295` (0xFFFFFFFF) | + +A single mesh primitive may contain any number of line loops, defining exterior and interior rings for any number of polygons. Each polygon is associated with its loop(s) within the mesh primitive according to the `indicesOffsets` extension property. + +Winding order of exterior rings is counterclockwise; winding order of interior rings (holes) is clockwise. + +An extended mesh primitive **MUST** include `indices`. Indices for each polygon must be contiguous: for a polygon composed of 4 loops (1 exterior, 3 holes), indices defining these loops must occupy an uninterrupted range within the primitive's indices accessor. Only indices, not vertex attributes, are required to be contiguous. + +The `EXT_mesh_polygon` extension includes the following additional properties. ### count @@ -40,63 +54,60 @@ Integer number of polygons encoded in the mesh primitive. - **Required:** ✓ Yes - **Minimum:** ≥ 1 -### loopIndices - -Index of an accessor containing indices of the polygons' exterior and interior loops. The accessor **MUST** have `SCALAR` type and an unsigned integer component type. +### indicesOffsets -A polygon is composed of 1 or more loops, encoded as indices equivalent to `primitive.mode = 2` ("LINE_LOOP") topology. The first loop in each polygon represents the polygon's exterior ring, or boundary. Additional loops, if any, represent interior rings ("holes") within the polygon. Polygons must be fully-connected: holes cannot intersect the exterior ring, and additional exterior rings ("islands") are not allowed, whether outside the exterior ring or within holes. +Index of an accessor containing one integer offset per polygon in the primitive, indicating the first index of the first line loop associated with the polygon. The accessor **MUST** have `SCALAR` type and an unsigned integer component type, and the accessor's `count` **MUST** be the same as `EXT_mesh_polygon.count` for the primitive. -Each loop must be separated by a primitive restart value, including loops associated with different polygons. Primitive restart values applicable to each accessor type are: - -| `accessor.componentType` | restart value | -| ---------------------------- | ------------------------- | -| `5121` (UNSIGNED_BYTE) | `255` (0xFF) | -| `5123` (UNSIGNED_SHORT) | `65535` (0xFFFF) | -| `5125` (UNSIGNED_INT) | `4294967295` (0xFFFFFFFF) | +All line loop indices associated with a polygon MUST be contiguous: one exterior ring followed immediately by zero or more interior rings. - **Type:** `number` - **Required:** ✓ Yes - **Minimum:** ≥ 0 -### loopIndicesOffsets +### triangleIndices -Index of an accessor containing one integer offset per polygon in the primitive, indicating the first index of the first linear ring associated with that polygon. The accessor **MUST** have `SCALAR` type and an unsigned integer component type, and the accessor's `count` **MUST** be the same as `EXT_mesh_polygon.count` for the primitive. +Index of an accessor containing indices satisfying all requirements associated with `primitive.mode = 4` ("TRIANGLES"), defining the tessellated surface of the polygon as a series of triangles. These indices refer to the same vertex attributes as the primitive's line loop indices. The accessor **MUST** have `SCALAR` type and an unsigned integer component type. -All loops associated with a polygon MUST be contiguous: one exterior ring followed immediately by zero or more interior rings. +When omitted, client implementations **MUST** compute a tessellation for the polygon, or fall back on another method of rendering the polygon. > [!NOTE] -> The range of loop indices for the `nth` polygon is `loopIndicesOffsets[n]` to `loopIndicesOffsets[n+1]` if `n < count - 1`, otherwise `loopIndicesOffsets[n]` to the end of the `loopIndices` accessor. +> Runtime tessellation of polygons is expensive and not uniquely-defined for all 3D line loops; authoring implementations should include `triangleIndices` whenever practical. - **Type:** `number` -- **Required:** ✓ Yes +- **Required:** No - **Minimum:** ≥ 0 -### indicesOffsets +### triangleIndicesOffsets + +Index of an accessor containing one integer offset per polygon in the primitive, indicating the first index of the first triangle in the `triangleIndices` accessor associated with that polygon. The accessor **MUST** have `SCALAR` type and an unsigned integer component type, and the accessor's `count` **MUST** be the same as `EXT_mesh_polygon.count` for the primitive. -Index of an accessor containing one integer offset per polygon in the primitive, indicating the first index of the first triangle associated with that polygon. The accessor **MUST** have `SCALAR` type and an unsigned integer component type, and the accessor's `count` **MUST** be the same as `EXT_mesh_polygon.count` for the primitive. +All triangles associated with a polygon MUST be contiguous. -Indices for each polygon **MUST** be contiguous. +When `triangleIndices` is defined, `triangleIndicesOffsets` is required. When `triangleIndices` is omitted, `triangleIndicesOffsets` must be undefined. + +> [!NOTE] +> The range of loop indices for the `nth` polygon is `loopIndicesOffsets[n]` to `loopIndicesOffsets[n+1]` if `n < count - 1`, otherwise `loopIndicesOffsets[n]` to the end of the `loopIndices` accessor. - **Type:** `number` -- **Required:** ✓ Yes +- **Required:** No - **Minimum:** ≥ 0 ## Additional Restrictions Triangles in the extended mesh primitive **MUST** be associated with exactly one polygon. Loose triangles cannot be included, and a single triangle cannot be associated with multiple polygons. -Polygon `loopIndices` values **MUST** be associated with at least one triangle. Exterior and interior loops may not contain additional vertex indices missing from triangulation. +Polygon `indices` values **MUST** be associated with at least one triangle. Exterior and interior loops may not contain additional vertex indices missing from triangulation. -Polygon `loopIndices` values **MUST** be unique within each exterior or interior loop. The same vertex index cannot be used twice within a loop. +Polygon `indices` values **MUST** be unique within each exterior or interior loop. The same vertex index cannot be used twice within a loop. > [!NOTE] -> As in LINE_LOOP topology, the first and last indices are defined to be connected by a line segment. Unlike in some geospatial formats, it is NOT necessary that the first index be repeated at the end of the loop to indicate a closed ring, and doing so would violate the requirement above. +> Unlike in some geospatial formats, it is NOT necessary that the first index be repeated at the end of the loop to indicate a closed ring, and doing so would violate the requirement above. The first and last index are implicitly connected, as defined for line loop indices in the core glTF 2.0 specification. ## Example _This section is non-normative._ -The JSON example below shows a mesh having one mesh primitive, which contains 100 polygon primitives. The `indices` accessor defines the triangle indices required to draw the polygons, and the `loopIndices` accessor defines the `LINE_LOOP` indices delimiting the exterior (and interior, if holes are present) boundaries of each polygon. The `loopIndicesOffsets` and `indicesOffsets` accessors enable random access, allowing implementations to immediately find the particular triangle and loop indices associated with the Nth polygon. +The JSON example below shows a mesh having one mesh primitive, which contains 100 polygon primitives. The `indices` accessor defines `LINE_LOOP` indices delimiting the exterior (and interior, if holes are present) rings of each polygon. The `triangleIndices` accessor defines triangle indices available to draw the polygons as filled surfaces. `indicesOffsets` and `triangleIndicesOffsets` accessors enable random access, allowing implementations to immediately find the particular indices (for loops and triangles, respectively) associated with the Nth polygon. ```jsonc { @@ -105,18 +116,18 @@ The JSON example below shows a mesh having one mesh primitive, which contains 10 "meshes": [{ "name": "MyMesh", "primitives": [{ - "mode": 4, + "mode": 2, // LINE_LOOP + "indices": 0, "attributes": { - "POSITION": 0, + "POSITION": 1, }, - "indices": 1, "extensions": { - "EXT_mesh_polygon": { - "count": 100, - "loopIndices": 2, - "loopIndicesOffsets": 3, - "indicesOffsets": 4 - } + "EXT_mesh_polygon": { + "count": 100, + "indicesOffsets": 2, + "triangleIndices": 3, + "triangleIndicesOffsets": 4 + } } }] }] @@ -129,7 +140,6 @@ Consider a simple mesh primitive containing three polygons, one with a single ho One valid encoding of `EXT_mesh_polygon` for this polygon set, based on `UNSIGNED_SHORT` indices and restart values, would be as follows: - ## JSON Schema The `"EXT_mesh_polygon"` string must be added to the root-level `extensionsUsed` array. Where preservation of polygon topology — not just display of equivalent triangles — is required, the string should also be added to the root-level `extensionsRequired` array. As the extension is intentionally backwards-compatible for rendering purposes, the extension is expected to be optional in most cases. From c1a035499b70aeb5d8281470101423e5e285dfe3 Mon Sep 17 00:00:00 2001 From: Don McCurdy <1848368+donmccurdy@users.noreply.github.com> Date: Thu, 4 Jun 2026 11:14:18 -0400 Subject: [PATCH 5/5] Revert previous: Use TRIANLGES indices on primitive, LINE_LOOP indices as extension property. --- .../2.0/Vendor/EXT_mesh_polygon/README.md | 65 +++++++++---------- 1 file changed, 29 insertions(+), 36 deletions(-) diff --git a/extensions/2.0/Vendor/EXT_mesh_polygon/README.md b/extensions/2.0/Vendor/EXT_mesh_polygon/README.md index 0485874bc3..5624dce398 100644 --- a/extensions/2.0/Vendor/EXT_mesh_polygon/README.md +++ b/extensions/2.0/Vendor/EXT_mesh_polygon/README.md @@ -26,23 +26,9 @@ Extends glTF mesh primitives, adding an encoding of polygon primitive topology, The `EXT_mesh_polygon` extension may be added to a mesh primitive, indicating that the primitive represents a series of polygons. -An extended mesh primitive **MUST** include `primitive.mode = 2` ("LINE_LOOP"). +An extended mesh primitive **MUST** include `primitive.mode = 4` ("TRIANGLES"), defining the tessellated surface of each polygon as a series of triangles. -Each polygon is composed of 1 or more loops. The first loop in each polygon represents the polygon's exterior ring, or boundary. Additional loops, if any, represent interior rings ("holes") within the polygon. Polygons must be fully-connected: holes cannot intersect the exterior ring, and additional exterior rings ("islands") are not allowed, whether outside the exterior ring or within holes. - -Each loop must be separated by a primitive restart value, including loops associated with different polygons. Primitive restart values applicable to each accessor type are: - -| `accessor.componentType` | restart value | -| ---------------------------- | ------------------------- | -| `5121` (UNSIGNED_BYTE) | `255` (0xFF) | -| `5123` (UNSIGNED_SHORT) | `65535` (0xFFFF) | -| `5125` (UNSIGNED_INT) | `4294967295` (0xFFFFFFFF) | - -A single mesh primitive may contain any number of line loops, defining exterior and interior rings for any number of polygons. Each polygon is associated with its loop(s) within the mesh primitive according to the `indicesOffsets` extension property. - -Winding order of exterior rings is counterclockwise; winding order of interior rings (holes) is clockwise. - -An extended mesh primitive **MUST** include `indices`. Indices for each polygon must be contiguous: for a polygon composed of 4 loops (1 exterior, 3 holes), indices defining these loops must occupy an uninterrupted range within the primitive's indices accessor. Only indices, not vertex attributes, are required to be contiguous. +An extended mesh primitive **MUST** include `indices`. All triangle indices associated with a polygon MUST be contiguous. For a polygon composed of N triangles, indices defining these triangles must occupy an uninterrupted range within the primitive's indices accessor. Only indices, not vertex attributes, are required to be contiguous. The `EXT_mesh_polygon` extension includes the following additional properties. @@ -56,49 +42,56 @@ Integer number of polygons encoded in the mesh primitive. ### indicesOffsets -Index of an accessor containing one integer offset per polygon in the primitive, indicating the first index of the first line loop associated with the polygon. The accessor **MUST** have `SCALAR` type and an unsigned integer component type, and the accessor's `count` **MUST** be the same as `EXT_mesh_polygon.count` for the primitive. +Index of an accessor containing one integer offset per polygon in the primitive, indicating the first index of the first triangle associated with the polygon. The accessor **MUST** have `SCALAR` type and an unsigned integer component type, and the accessor's `count` **MUST** be the same as `EXT_mesh_polygon.count` for the primitive. -All line loop indices associated with a polygon MUST be contiguous: one exterior ring followed immediately by zero or more interior rings. +All triangle indices associated with a polygon MUST be contiguous. - **Type:** `number` - **Required:** ✓ Yes - **Minimum:** ≥ 0 -### triangleIndices +### loopIndices -Index of an accessor containing indices satisfying all requirements associated with `primitive.mode = 4` ("TRIANGLES"), defining the tessellated surface of the polygon as a series of triangles. These indices refer to the same vertex attributes as the primitive's line loop indices. The accessor **MUST** have `SCALAR` type and an unsigned integer component type. +Index of an accessor containing indices satisfying all requirements associated with `primitive.mode = 2` ("LINE_LOOP"). These indices refer to the same vertex attributes as the primitive's triangle indices. The accessor **MUST** have `SCALAR` type and an unsigned integer component type. -When omitted, client implementations **MUST** compute a tessellation for the polygon, or fall back on another method of rendering the polygon. +Each polygon is composed of 1 or more loops. The first loop in each polygon represents the polygon's exterior ring, or boundary. Additional loops, if any, represent interior rings ("holes") within the polygon. Polygons must be fully-connected: holes cannot intersect the exterior ring, and additional exterior rings ("islands") are not allowed. -> [!NOTE] -> Runtime tessellation of polygons is expensive and not uniquely-defined for all 3D line loops; authoring implementations should include `triangleIndices` whenever practical. +Each loop must be separated by a primitive restart value, including loops associated with different polygons. Primitive restart values applicable to each accessor type are: + +| `accessor.componentType` | restart value | +| ---------------------------- | ------------------------- | +| `5121` (UNSIGNED_BYTE) | `255` (0xFF) | +| `5123` (UNSIGNED_SHORT) | `65535` (0xFFFF) | +| `5125` (UNSIGNED_INT) | `4294967295` (0xFFFFFFFF) | + +A single mesh primitive may contain any number of line loops, defining 1 exterior and >=0 interior rings for any number of polygons. Each polygon is associated with its loop(s) within the mesh primitive according to the `indicesOffsets` extension property. + +Winding order of exterior rings is counterclockwise; winding order of interior rings (holes) is clockwise. - **Type:** `number` -- **Required:** No +- **Required:** ✓ Yes - **Minimum:** ≥ 0 -### triangleIndicesOffsets - -Index of an accessor containing one integer offset per polygon in the primitive, indicating the first index of the first triangle in the `triangleIndices` accessor associated with that polygon. The accessor **MUST** have `SCALAR` type and an unsigned integer component type, and the accessor's `count` **MUST** be the same as `EXT_mesh_polygon.count` for the primitive. +### loopIndicesOffsets -All triangles associated with a polygon MUST be contiguous. +Index of an accessor containing one integer offset per polygon in the primitive, indicating the first index of the first loop in the `loopIndices` accessor associated with that polygon. The accessor **MUST** have `SCALAR` type and an unsigned integer component type, and the accessor's `count` **MUST** be the same as `EXT_mesh_polygon.count` for the primitive. -When `triangleIndices` is defined, `triangleIndicesOffsets` is required. When `triangleIndices` is omitted, `triangleIndicesOffsets` must be undefined. +All line loop indices associated with a polygon MUST be contiguous: one exterior ring followed immediately by zero or more interior rings. Only indices, not vertex attributes, are required to be contiguous. > [!NOTE] > The range of loop indices for the `nth` polygon is `loopIndicesOffsets[n]` to `loopIndicesOffsets[n+1]` if `n < count - 1`, otherwise `loopIndicesOffsets[n]` to the end of the `loopIndices` accessor. - **Type:** `number` -- **Required:** No +- **Required:** ✓ Yes - **Minimum:** ≥ 0 ## Additional Restrictions Triangles in the extended mesh primitive **MUST** be associated with exactly one polygon. Loose triangles cannot be included, and a single triangle cannot be associated with multiple polygons. -Polygon `indices` values **MUST** be associated with at least one triangle. Exterior and interior loops may not contain additional vertex indices missing from triangulation. +Polygon `loopIndices` values **MUST** be associated with at least one triangle. Exterior and interior loops may not contain additional vertex indices missing from triangulation. -Polygon `indices` values **MUST** be unique within each exterior or interior loop. The same vertex index cannot be used twice within a loop. +Polygon `loopIndices` values **MUST** be unique within each exterior or interior loop. The same vertex index cannot be used twice within a loop. > [!NOTE] > Unlike in some geospatial formats, it is NOT necessary that the first index be repeated at the end of the loop to indicate a closed ring, and doing so would violate the requirement above. The first and last index are implicitly connected, as defined for line loop indices in the core glTF 2.0 specification. @@ -107,7 +100,7 @@ Polygon `indices` values **MUST** be unique within each exterior or interior loo _This section is non-normative._ -The JSON example below shows a mesh having one mesh primitive, which contains 100 polygon primitives. The `indices` accessor defines `LINE_LOOP` indices delimiting the exterior (and interior, if holes are present) rings of each polygon. The `triangleIndices` accessor defines triangle indices available to draw the polygons as filled surfaces. `indicesOffsets` and `triangleIndicesOffsets` accessors enable random access, allowing implementations to immediately find the particular indices (for loops and triangles, respectively) associated with the Nth polygon. +The JSON example below shows a mesh having one mesh primitive, which contains 100 polygon primitives. The `indices` accessor defines `TRIANGLES` indices delimiting the tessellated surface of each polygon. The `loopIndices` accessor defines each polygon's exterior (and interior, if holes are present) rings. `indicesOffsets` and `loopIndicesOffsets` accessors enable random access, allowing implementations to immediately find the particular indices (for triangles and loops, respectively) associated with the Nth polygon. ```jsonc { @@ -116,7 +109,7 @@ The JSON example below shows a mesh having one mesh primitive, which contains 10 "meshes": [{ "name": "MyMesh", "primitives": [{ - "mode": 2, // LINE_LOOP + "mode": 4, // TRIANGLES "indices": 0, "attributes": { "POSITION": 1, @@ -125,8 +118,8 @@ The JSON example below shows a mesh having one mesh primitive, which contains 10 "EXT_mesh_polygon": { "count": 100, "indicesOffsets": 2, - "triangleIndices": 3, - "triangleIndicesOffsets": 4 + "loopIndices": 3, + "loopIndicesOffsets": 4 } } }]