Skip to content
Draft
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ version = "3.0.2"
ColorSchemes = "35d6a980-a343-548e-a6ea-1d62b119f2f4"
Colors = "5ae59095-9a9b-59fe-a467-6f913c188581"
DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae"
GeometryBasics = "5c1252a2-5f33-56bf-86c9-59e7332b4326"
StaticArrays = "90137ffa-7385-5640-81b9-e52037218182"
StaticArraysCore = "1e83bf80-4336-4d27-bf5d-d5a4f845583c"

[compat]
ColorSchemes = "3"
Colors = "0.12, 0.13"
DocStringExtensions = "0.8, 0.9"
GeometryBasics = "0.5.9"
StaticArrays = "1.9.13"
StaticArraysCore = "1.4"
julia = "1.6"
8 changes: 8 additions & 0 deletions src/GridVisualizeTools.jl
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,24 @@ import ColorSchemes
using DocStringExtensions: SIGNATURES, TYPEDEF, TYPEDSIGNATURES
using StaticArraysCore: SVector
using StaticArrays: @MArray
using GeometryBasics # replace by SArray!
Copy link

Copilot AI Jun 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] There's a TODO-style comment suggesting a future replacement of this import. Consider addressing or removing this to avoid lingering technical debt.

Suggested change
using GeometryBasics # replace by SArray!
using StaticArrays: SArray

Copilot uses AI. Check for mistakes.


include("colors.jl")
export region_cmap, bregion_cmap, rgbtuple, rgbcolor

include("extraction.jl")
export extract_visible_cells3D, extract_visible_bfaces3D

include("linearsimplex.jl")
export LinearSimplex, LinearSimplexIterator
export LinearEdge, LinearTriangle, LinearTetrahedron
export testloop

include("marching.jl")
include("marching_iterators.jl")
export marching_tetrahedra, marching_triangles


include("markerpoints.jl")
export markerpoints

Expand Down
2 changes: 2 additions & 0 deletions src/colors.jl
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ function bregion_cmap(n)
end

"""
$(SIGNATURES)

Create color tuple from color description (e.g. string)

```jldoctest
Expand Down
95 changes: 95 additions & 0 deletions src/linearsimplex.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
struct LinearSimplex{D, N, Tv}
points::SVector{N, Point{D, Tv}}
values::SVector{N, Tv}
end

values(s::LinearSimplex) = s.values
points(s::LinearSimplex) = s.points


function LinearSimplex(::Type{Val{D}}, points::Union{Vector, Tuple}, values::Union{Vector, Tuple}) where {D}
spoints = SVector{D + 1, Point{D, Float32}}(points...)
svalues = SVector{D + 1, Float32}(values...)
return LinearSimplex(spoints, svalues)
end

function LinearSimplex(::Type{Val{1}}, points::AbstractMatrix, values::AbstractVector)
@views spoints = SVector{2, Point{1, Float32}}(points[:, 1], points[:, 2])
svalues = SVector{2, Float32}(values[1], values[2])
return LinearSimplex(spoints, svalues)
end

function LinearSimplex(::Type{Val{2}}, points::AbstractMatrix, values::AbstractVector)
@views spoints = SVector{3, Point{2, Float32}}(points[:, 1], points[:, 2], points[:, 3])
svalues = SVector{3, Float32}(values[1], values[2], values[3])
return LinearSimplex(spoints, svalues)
end

function LinearSimplex(::Type{Val{3}}, points::AbstractMatrix, values::AbstractVector)
@views spoints = SVector{4, Point{3, Float32}}(points[:, 1], points[:, 2], points[:, 3], points[:, 4])
svalues = SVector{4, Float32}(values[1], values[2], values[3], values[4])
return LinearSimplex(spoints, svalues)
end


function LinearSimplex(::Type{Val{1}}, points::AbstractMatrix, values::AbstractVector, coordscale)
@views spoints = SVector{2, Point{1, Float32}}(points[:, 1] * coordscale, points[:, 2] * coordscale)
svalues = SVector{2, Float32}(values[1], values[2])
return LinearSimplex(spoints, svalues)
end

function LinearSimplex(::Type{Val{2}}, points::AbstractMatrix, values::AbstractVector, coordscale)
@views spoints = SVector{3, Point{2, Float32}}(points[:, 1] * coordscale, points[:, 2] * coordscale, points[:, 3] * coordscale)
svalues = SVector{3, Float32}(values[1], values[2], values[3])
return LinearSimplex(spoints, svalues)
end

function LinearSimplex(::Type{Val{3}}, points::AbstractMatrix, values::AbstractVector, coordscale)
@views spoints = SVector{4, Point{3, Float32}}(points[:, 1] * coordscale, points[:, 2] * coordscale, points[:, 3] * coordscale, points[:, 4] * coordscale)
svalues = SVector{4, Float32}(values[1], values[2], values[3], values[4])
return LinearSimplex(spoints, svalues)
end

LinearEdge(points, values) = LinearSimplex(Val{1}, points, values)
LinearTriangle(points, values) = LinearSimplex(Val{2}, points, values)
LinearTetrahedron(points, values) = LinearSimplex(Val{3}, points, values)


"""
abstract type LinearSimplexIterator{D}

Iterator over D-dimensional linear simplices.

Any subtype `TSub` should comply with the [iteration interface](https://docs.julialang.org/en/v1/manual/interfaces/#man-interface-iteration)
and implement
```julia
Base.iterate(vs::TSub{D}, state):: (LinearSimplex{D}, state) where D
```
The optional methods (in particular, size, length) are not needed.
"""
abstract type LinearSimplexIterator{D} end

"""
testloop(iterators::AbstractVector{T}) where T<:LinearSimplexIterator

Sum up all values passed via the iterator. Designed for testing iterators.

Useful for:
- Consistency test - e.g. when passing constant ones, the sum can be calculated by
other means and compared to the return value of testloop
- Allocation test. `testloop` itself does allocate only a few (<1000) bytes in
very few (<10) allocations. So any allocations beyond this from a
call to `testloop` hint at possibilities to improve an iterator implementation.
"""
function testloop(iterators::AbstractVector{T}) where {T <: LinearSimplexIterator}
threads = map(iterators) do iterator
begin
local x = 0.0
for vt in iterator
x += sum(vt.values)
end
x
end
end
return sum(fetch.(threads))
end
7 changes: 2 additions & 5 deletions src/marching.jl
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ end
This method can be used both for the evaluation of plane sections and for
the evaluation of function isosurfaces.
"""
function calculate_plane_tetrahedron_intersection!(
function calculatCe_plane_tetrahedron_intersection!(
ixcoord,
ixvalues,
coordinates,
Expand All @@ -78,7 +78,6 @@ function calculate_plane_tetrahedron_intersection!(
)
return 0
end

amount_intersections = 0

@inbounds for n1 in 1:4
Expand Down Expand Up @@ -233,7 +232,7 @@ function marching_tetrahedra(

function pushtris(ns, ixcoord, ixvalues)
# number of intersection points can be 3 or 4
if ns >= 3
return if ns >= 3
Copy link

Copilot AI Jun 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This early return inverts the original logic by skipping valid cases when ns >= 3. It likely should be return if ns < 3 to skip cases with fewer than three intersections.

Suggested change
return if ns >= 3
return if ns < 3

Copilot uses AI. Check for mistakes.

last_i = length(all_ixvalues)
for is in 1:ns
@views push!(all_ixcoord, ixcoord[:, is])
Expand Down Expand Up @@ -356,7 +355,6 @@ function marching_triangles(
points = Vector{Tp}(undef, 0)
values = Vector{Tv}(undef, 0)
adjacencies = Vector{SVector{2, Ti}}(undef, 0)

for igrid in 1:length(coords)
func = funcs[igrid]
coord = coords[igrid]
Expand Down Expand Up @@ -416,7 +414,6 @@ function marching_triangles(
# connect last two points
push!(adjacencies, SVector{2, Ti}((length(points) - 1, length(points))))
end

return
end

Expand Down
64 changes: 64 additions & 0 deletions src/marching_iterators.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
function pushintersection!(intersection_points, triangle::LinearSimplex{2}, levels)
f = values(triangle)
coord = points(triangle)

(n1, n2, n3) = (1, 2, 3)

f[1] <= f[2] ? (n1, n2) = (1, 2) : (n1, n2) = (2, 1)
f[n2] <= f[3] ? n3 = 3 : (n2, n3) = (3, n2)
f[n1] > f[n2] ? (n1, n2) = (n2, n1) : nothing

dx31 = coord[n3][1] - coord[n1][1]
dx21 = coord[n2][1] - coord[n1][1]
dx32 = coord[n3][1] - coord[n2][1]

dy31 = coord[n3][2] - coord[n1][2]
dy21 = coord[n2][2] - coord[n1][2]
dy32 = coord[n3][2] - coord[n2][2]

df31 = f[n3] != f[n1] ? 1 / (f[n3] - f[n1]) : 0.0
df21 = f[n2] != f[n1] ? 1 / (f[n2] - f[n1]) : 0.0
df32 = f[n3] != f[n2] ? 1 / (f[n3] - f[n2]) : 0.0

for level in levels
if (f[n1] <= level) && (level < f[n3])
α = (level - f[n1]) * df31
x1 = coord[n1][1] + α * dx31
y1 = coord[n1][2] + α * dy31

if (level < f[n2])
α = (level - f[n1]) * df21
x2 = coord[n1][1] + α * dx21
y2 = coord[n1][2] + α * dy21
else
α = (level - f[n2]) * df32
x2 = coord[n2][1] + α * dx32
y2 = coord[n2][2] + α * dy32
end
push!(intersection_points, Point{2, Float32}(x1, y1))
push!(intersection_points, Point{2, Float32}(x2, y2))
end
end
return
end

function intersections(triangles::T, levels) where {T <: LinearSimplexIterator{2}}
local intersection_points = Vector{Point{2, Float32}}(undef, 0)
for triangle in triangles
pushintersection!(intersection_points, triangle, levels)
end
return intersection_points
end

function marching_triangles(triangle_iterators::Vector{T}, levels) where {T <: LinearSimplexIterator{2}}
return if Threads.nthreads() == 1
map(triangle_iterators) do triangles
intersections(triangles, levels)
end
else
threads = map(triangle_iterators) do triangles
Threads.@spawn intersections(triangles, levels)
end
Comment on lines +55 to +61
Copy link

Copilot AI Jun 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The call to map uses the collection as the first argument and a do-block as the second; map expects a function first and collection(s) after. Consider using map(triangles -> intersections(triangles, levels), triangle_iterators) or swapping the arguments appropriately.

Suggested change
map(triangle_iterators) do triangles
intersections(triangles, levels)
end
else
threads = map(triangle_iterators) do triangles
Threads.@spawn intersections(triangles, levels)
end
map(triangles -> intersections(triangles, levels), triangle_iterators)
else
threads = map(triangles -> Threads.@spawn intersections(triangles, levels), triangle_iterators)

Copilot uses AI. Check for mistakes.

Comment on lines +59 to +61
Copy link

Copilot AI Jun 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same issue with argument ordering in map for spawning threads; the function should be the first argument. Consider:

threads = map(triangles -> Threads.@spawn intersections(triangles, levels), triangle_iterators)
Suggested change
threads = map(triangle_iterators) do triangles
Threads.@spawn intersections(triangles, levels)
end
threads = map(triangles -> Threads.@spawn(intersections(triangles, levels)), triangle_iterators)

Copilot uses AI. Check for mistakes.

fetch.(threads)
end
end
Loading