// 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.
///|
pub struct SharedShape {
shape : Shape
}
///|
pub fn SharedShape::SharedShape(shape : Shape) -> SharedShape {
{ shape, }
}
///|
pub fn SharedShape::as_shape(self : SharedShape) -> Shape {
self.shape
}
///|
pub fn SharedShape::ball(radius : @core.Real) -> SharedShape {
SharedShape(Ball(radius))
}
///|
pub fn SharedShape::cuboid(
half_width : @core.Real,
half_height : @core.Real,
) -> SharedShape {
SharedShape(Cuboid(half_width, half_height))
}
///|
pub fn SharedShape::capsule_x(
half_height : @core.Real,
radius : @core.Real,
) -> SharedShape {
SharedShape(CapsuleX(half_height, radius))
}
///|
pub fn SharedShape::capsule_y(
half_height : @core.Real,
radius : @core.Real,
) -> SharedShape {
SharedShape(CapsuleY(half_height, radius))
}
///|
pub fn SharedShape::segment(a : @core.Vec2, b : @core.Vec2) -> SharedShape {
SharedShape(Segment(a, b))
}
///|
pub fn SharedShape::triangle(
a : @core.Vec2,
b : @core.Vec2,
c : @core.Vec2,
) -> SharedShape {
let builder = ColliderBuilder::triangle(a, b, c)
SharedShape(builder.shape)
}
///|
pub fn SharedShape::polyline(
vertices : Array[@core.Vec2],
indices : Array[(Int, Int)]?,
) -> SharedShape {
SharedShape(Polyline(vertices, indices))
}
///|
pub fn SharedShape::trimesh(
vertices : Array[@core.Vec2],
indices : Array[(Int, Int, Int)],
) -> SharedShape? {
match ColliderBuilder::trimesh(vertices, indices) {
None => None
Some(builder) => Some(SharedShape(builder.shape))
}
}
///|
pub fn SharedShape::heightfield(
heights : Array[@core.Real],
scale : @core.Vec2,
) -> SharedShape {
let builder = ColliderBuilder::heightfield(heights, scale)
SharedShape(builder.shape)
}
///|
pub struct Triangle {
a : @core.Vec2
b : @core.Vec2
c : @core.Vec2
}
///|
pub fn Triangle::Triangle(
a : @core.Vec2,
b : @core.Vec2,
c : @core.Vec2,
) -> Triangle {
{ a, b, c }
}
///|
pub fn Triangle::as_shape(self : Triangle) -> Shape {
let builder = ColliderBuilder::triangle(self.a, self.b, self.c)
builder.shape
}
///|
pub struct HeightField {
heights : Array[@core.Real]
scale : @core.Vec2
}
///|
pub fn HeightField::HeightField(
heights : Array[@core.Real],
scale : @core.Vec2,
) -> HeightField {
{ heights, scale }
}
///|
pub fn HeightField::as_shape(self : HeightField) -> Shape {
let builder = ColliderBuilder::heightfield(self.heights, self.scale)
builder.shape
}
///|
pub(all) enum MeshConverterError {
ConvexHullFailed
TriMeshBuilderError
EmptyVertices
}
///|
pub fn MeshConverterError::to_string(self : MeshConverterError) -> String {
match self {
ConvexHullFailed => "ConvexHullFailed"
TriMeshBuilderError => "TriMeshBuilderError"
EmptyVertices => "EmptyVertices"
}
}
///|
pub struct TriMeshFlags {
value : Int
}
///|
pub fn TriMeshFlags::empty() -> TriMeshFlags {
{ value: 0 }
}
///|
pub fn TriMeshFlags::from_bits(bits : Int) -> TriMeshFlags {
{ value: bits }
}
///|
pub fn TriMeshFlags::bits(self : TriMeshFlags) -> Int {
self.value
}
///|
pub(all) enum MeshConverter {
TriMesh
TriMeshWithFlags(TriMeshFlags)
Aabb
Obb
ConvexHull
ConvexDecomposition
ConvexDecompositionWithParams(VHACDParameters)
}
///|
pub fn MeshConverter::trimesh() -> MeshConverter {
TriMesh
}
///|
pub fn MeshConverter::trimesh_with_flags(flags : TriMeshFlags) -> MeshConverter {
TriMeshWithFlags(flags)
}
///|
pub fn MeshConverter::aabb() -> MeshConverter {
Aabb
}
///|
pub fn MeshConverter::obb() -> MeshConverter {
Obb
}
///|
pub fn MeshConverter::convex_hull() -> MeshConverter {
ConvexHull
}
///|
fn compute_aabb(vertices : Array[@core.Vec2]) -> (@core.Vec2, @core.Vec2)? {
if vertices.length() == 0 {
return None
}
let mut min_x = vertices[0].x
let mut min_y = vertices[0].y
let mut max_x = vertices[0].x
let mut max_y = vertices[0].y
for i in 1.. max_x {
max_x = p.x
}
if p.y > max_y {
max_y = p.y
}
}
Some((Vec2(min_x, min_y), Vec2(max_x, max_y)))
}
///|
pub fn MeshConverter::apply(
self : MeshConverter,
vertices : Array[@core.Vec2],
indices : Array[(Int, Int, Int)],
) -> Result[(SharedShape, @core.Isometry2), MeshConverterError] {
match self {
TriMesh =>
match SharedShape::trimesh(vertices, indices) {
None => Err(TriMeshBuilderError)
Some(shape) => Ok((shape, @core.Isometry2::identity()))
}
TriMeshWithFlags(_) =>
// This port does not implement TriMesh pre-processing flags; treat as regular trimesh.
match SharedShape::trimesh(vertices, indices) {
None => Err(TriMeshBuilderError)
Some(shape) => Ok((shape, @core.Isometry2::identity()))
}
Aabb =>
match compute_aabb(vertices) {
None => Err(EmptyVertices)
Some((min, max)) => {
let center = @core.Vec2(
0.5F * (min.x + max.x),
0.5F * (min.y + max.y),
)
let half = @core.Vec2(0.5F * (max.x - min.x), 0.5F * (max.y - min.y))
let shape = SharedShape::cuboid(half.x, half.y)
Ok((shape, @core.Isometry2::from_translation(center)))
}
}
Obb =>
// NOTE: This is a 2D port and we currently fall back to the axis-aligned bounding box.
MeshConverter::Aabb.apply(vertices, indices)
ConvexHull =>
match ColliderBuilder::convex_hull(vertices) {
None => Err(ConvexHullFailed)
Some(builder) =>
Ok((SharedShape(builder.shape), @core.Isometry2::identity()))
}
ConvexDecomposition =>
MeshConverter::ConvexDecompositionWithParams(VHACDParameters::default()).apply(
vertices, indices,
)
ConvexDecompositionWithParams(params) => {
fn boundary_segments_from_trimesh(
indices : Array[(Int, Int, Int)],
) -> Array[(Int, Int)] {
fn min_int(a : Int, b : Int) -> Int {
if a < b {
a
} else {
b
}
}
fn max_int(a : Int, b : Int) -> Int {
if a > b {
a
} else {
b
}
}
let counts : @hashmap.HashMap[(Int, Int), Int] = HashMap(
[],
capacity=indices.length() * 3,
)
let keys : Array[(Int, Int)] = []
fn bump(
counts : @hashmap.HashMap[(Int, Int), Int],
keys : Array[(Int, Int)],
a : Int,
b : Int,
) -> Unit {
let u = min_int(a, b)
let v = max_int(a, b)
let key = (u, v)
if counts.get(key) is Some(c) {
counts.set(key, c + 1)
} else {
counts.set(key, 1)
keys.push(key)
}
}
for t in indices {
let (i0, i1, i2) = t
bump(counts, keys, i0, i1)
bump(counts, keys, i1, i2)
bump(counts, keys, i2, i0)
}
let out : Array[(Int, Int)] = []
for k in keys {
if counts.get(k) is Some(c) && c == 1 {
out.push(k)
}
}
out
}
let segments = boundary_segments_from_trimesh(indices)
if segments.length() == 0 {
return MeshConverter::ConvexHull.apply(vertices, indices)
}
let builder = ColliderBuilder::convex_decomposition_with_params(
vertices, segments, params,
)
Ok((SharedShape(builder.shape), @core.Isometry2::identity()))
}
}
}
///|
pub fn MeshConverter::convert(
self : MeshConverter,
vertices : Array[@core.Vec2],
indices : Array[(Int, Int, Int)],
) -> Result[(SharedShape, @core.Isometry2), MeshConverterError] {
self.apply(vertices, indices)
}
///|
pub struct ColliderPair {
collider1 : ColliderHandle
collider2 : ColliderHandle
}
///|
pub fn ColliderPair::ColliderPair(
a : ColliderHandle,
b : ColliderHandle,
) -> ColliderPair {
{ collider1: a, collider2: b }
}
///|
pub fn ColliderPair::collider1(self : ColliderPair) -> ColliderHandle {
self.collider1
}
///|
pub fn ColliderPair::collider2(self : ColliderPair) -> ColliderHandle {
self.collider2
}
///|
pub fn ColliderPair::swap(self : ColliderPair) -> ColliderPair {
ColliderPair(self.collider2, self.collider1)
}
///|
pub(all) enum BroadPhasePairEvent {
AddPair(ColliderPair)
DeletePair(ColliderPair)
}
///|
pub(all) enum BvhOptimizationStrategy {
SubtreeOptimizer
None
}
///|
pub fn BvhOptimizationStrategy::none() -> BvhOptimizationStrategy {
None
}
///|
pub fn BvhOptimizationStrategy::subtree_optimizer() -> BvhOptimizationStrategy {
SubtreeOptimizer
}