// Copyright 2025 International Digital Economy Academy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
///|
/// A minimal, Bevy-aligned 2D mesh geometry container.
///
/// Notes:
/// - Only triangle list is supported for now.
/// - `indices=None` means the vertex arrays are already laid out as triangles.
/// - UVs are required (matching Bevy mesh conventions) even if a pipeline does
/// not use them yet.
pub struct Mesh2dGeometry {
positions : Array[@math.Vec2]
uvs : Array[@math.Vec2]
indices : Array[Int]?
}
///|
pub(all) enum Mesh2dValidationError {
EmptyVertices
AttributeLengthMismatch(Int, Int)
NonIndexedVertexCountNotMultipleOf3(Int)
IndicesEmpty
IndicesCountNotMultipleOf3(Int)
IndexOutOfBounds(Int, Int)
} derive(Eq, Show)
///|
pub fn Mesh2dGeometry::new(
positions : Array[@math.Vec2],
uvs : Array[@math.Vec2],
indices : Array[Int]?,
) -> Mesh2dGeometry {
Mesh2dGeometry::{ positions, uvs, indices }
}
///|
pub fn Mesh2dGeometry::validate(
self : Mesh2dGeometry,
) -> Mesh2dValidationError? {
let vertex_count = self.positions.length()
if vertex_count <= 0 {
return Some(Mesh2dValidationError::EmptyVertices)
}
let uv_count = self.uvs.length()
if uv_count != vertex_count {
return Some(
Mesh2dValidationError::AttributeLengthMismatch(vertex_count, uv_count),
)
}
match self.indices {
None => {
if vertex_count % 3 != 0 {
return Some(
Mesh2dValidationError::NonIndexedVertexCountNotMultipleOf3(
vertex_count,
),
)
}
None
}
Some(indices) => {
let index_count = indices.length()
if index_count <= 0 {
return Some(Mesh2dValidationError::IndicesEmpty)
}
if index_count % 3 != 0 {
return Some(
Mesh2dValidationError::IndicesCountNotMultipleOf3(index_count),
)
}
for idx in indices {
if idx < 0 || idx >= vertex_count {
return Some(
Mesh2dValidationError::IndexOutOfBounds(idx, vertex_count),
)
}
}
None
}
}
}
///|
pub fn Mesh2dGeometry::vertex_count(self : Mesh2dGeometry) -> Int {
self.positions.length()
}
///|
pub fn Mesh2dGeometry::triangle_count(self : Mesh2dGeometry) -> Int? {
if self.validate() is Some(_) {
return None
}
match self.indices {
None => Some(self.positions.length() / 3)
Some(indices) => Some(indices.length() / 3)
}
}
///|
pub fn Mesh2dGeometry::flatten_positions_xy(
self : Mesh2dGeometry,
) -> Array[Double]? {
if self.validate() is Some(_) {
return None
}
let out : Array[Double] = []
for v in self.positions {
out.push(v.x.to_double())
out.push(v.y.to_double())
}
Some(out)
}
///|
pub fn Mesh2dGeometry::flatten_interleaved_xy_uv(
self : Mesh2dGeometry,
) -> Array[Double]? {
if self.validate() is Some(_) {
return None
}
let out : Array[Double] = []
let n = self.positions.length()
for i in 0.. Array[Double]? {
if self.validate() is Some(_) {
return None
}
match self.indices {
None => self.flatten_positions_xy()
Some(indices) => {
let out : Array[Double] = []
for idx in indices {
let p = self.positions[idx]
out.push(p.x.to_double())
out.push(p.y.to_double())
}
Some(out)
}
}
}
///|
/// Encodes non-indexed triangle list positions as a comma-separated string:
/// `x0,y0,x1,y1,...`.
pub fn Mesh2dGeometry::encode_triangle_positions_csv(
self : Mesh2dGeometry,
) -> String? {
match self.flatten_triangle_positions_xy() {
None => None
Some(values) => {
let mut out = ""
let n = values.length()
for i in 0.. 0 {
out = out + ","
}
out = out + values[i].to_string()
}
Some(out)
}
}
}