LCOV - code coverage report
Current view: top level - src/convenience - meshes.jl (source / functions) Hit Total Coverage
Test: on branch nothing Lines: 194 194 100.0 %
Date: 2025-07-10 13:12:25 Functions: 0 0 -

          Line data    Source code
       1             : module Meshes
       2             : 
       3             : import PMesh: Mesh, Types, VHnd, nv, nf, vattr, addvertex!, addvertices!,
       4             :     addface!, insertedge!, halfedge, istrianglemesh, createmesh, vertices
       5             : 
       6             : const TEMPLATE = Types.MESH3N
       7             : const V3 = Types.V3
       8             : 
       9             : const VQUAD = [V3(0, 0, 0), V3(1, 0, 0), V3(0, 1, 0), V3(1, 1, 0)]
      10             : 
      11             : """
      12             :     mesh = triangle([; template::Mesh])
      13             : 
      14             : Create planar 3d mesh with one triangle.
      15             : """
      16          14 : function triangle(; template::Mesh = TEMPLATE)
      17           7 :     mesh = similar(template)
      18           7 :     a, b, c = VHnd.(addvertices!(mesh, VQUAD[1:3]))
      19           7 :     addface!(mesh, a, b, c)
      20             : 
      21           7 :     @assert nv(mesh) == 3 && nf(mesh) == 1
      22             : 
      23           7 :     mesh
      24             : end
      25             : 
      26             : """
      27             :     mesh = quad([; template::Mesh])
      28             : 
      29             : Create planar 3d mesh with one quad.
      30             : """
      31           6 : function quad(; template::Mesh = TEMPLATE)
      32           3 :     mesh = similar(template)
      33           3 :     a, b, c, d = VHnd.(addvertices!(mesh, VQUAD))
      34           3 :     addface!(mesh, a, b, c, d)
      35             : 
      36           3 :     @assert nv(mesh) == 4 && nf(mesh) == 1
      37             : 
      38           3 :     mesh
      39             : end
      40             : 
      41             : """
      42             :     mesh = wing([; template::Mesh])
      43             : 
      44             : Create planar 3d mesh with two triangles, which triangulate a quad.
      45             : """
      46           2 : function wing(; template::Mesh = TEMPLATE)
      47           1 :     mesh = similar(template)
      48           1 :     a, b, c, d = VHnd.(addvertices!(mesh, VQUAD))
      49           1 :     addface!(mesh, a, b, c)
      50           1 :     addface!(mesh, c, b, d)
      51             : 
      52           1 :     @assert nv(mesh) == 4 && nf(mesh) == 2
      53             : 
      54           1 :     mesh
      55             : end
      56             : 
      57             : """
      58             :     mesh = nring([;n = 6, r=A(t), α=B(t), template::Mesh])
      59             : 
      60             : Create 3d mesh of a 1-ring with `n` neighbors around
      61             : the center vertex `VHnd(1)` at `(0, 0, 0)`.
      62             : 
      63             : The functions `r(t)` and `α(t)` determine the vertex positions
      64             : for `t = [0, 1/n, ..., (n-1)/m]`. The default positions boundary
      65             : vertices regularly on the unit circle in the `x-y-plane`.
      66             : 
      67             : See also [`ngon`](@ref)
      68             : """
      69          32 : function nring(;n::Int=6, r=t -> 1, α=t ->2π*t, template::Mesh = TEMPLATE)
      70           3 :     @assert n > 1
      71           3 :     mesh = similar(template)
      72           3 :     x = vattr(mesh, Val(:x))
      73           3 :     v = addvertex!(mesh)
      74           3 :     x[v] = V3(0, 0, 0)
      75             : 
      76           3 :     vs = [addvertex!(mesh) for _ in 1:n]
      77             : 
      78           3 :     for i in 1:n
      79          26 :         t = (i - 1) / n
      80          26 :         rt = r(t)
      81          26 :         αt = α(t)
      82          26 :         x[vs[i]] = V3(rt * cos(αt), rt * sin(αt), 0)
      83             : 
      84          26 :         j = (i % n) + 1
      85          26 :         addface!(mesh, v, vs[i], vs[j])
      86          49 :     end
      87             : 
      88           3 :     @assert nv(mesh) == n+1 && nf(mesh) == n
      89             : 
      90           3 :     mesh
      91             : end
      92             : 
      93             : """
      94             :     mesh = ngon([;n = 6, r=A(t), α=B(t), template::Mesh])
      95             : 
      96             : Create 3d mesh of a `n`-gon.
      97             : 
      98             : The functions `r(t)` and `α`(t)` determine the vertex positions
      99             : for `t = [0, 1/n, ..., (n-1)/m]`. The default positions boundary
     100             : vertices regularly on the unit circle in the `x-y-plane`.
     101             : 
     102             : See also [`nring`](@ref)
     103             : """
     104          24 : function ngon(;n::Int=6, r=t -> 1, α=t ->2π*t, template::Mesh = TEMPLATE)
     105           2 :     @assert n > 1
     106           2 :     mesh = similar(template)
     107           2 :     x = vattr(mesh, Val(:x))
     108             : 
     109           2 :     vs = [addvertex!(mesh) for _ in 1:n]
     110             : 
     111           2 :     for i in 1:n
     112          20 :         t = (i - 1) / n
     113          20 :         rt = r(t)
     114          20 :         αt = α(t)
     115          20 :         x[vs[i]] = V3(rt * cos(αt), rt * sin(αt), 0)
     116          38 :     end
     117             : 
     118           2 :     addface!(mesh, vs)
     119             : 
     120           2 :     @assert nv(mesh) == n && nf(mesh) == 1
     121             : 
     122           2 :     mesh
     123             : end
     124             : 
     125             : """
     126             :     mesh = cube([; triangulate=false, template::Mesh])
     127             : 
     128             : Create standard cube with each quad optionally split into two `triangles`.
     129             : """
     130           6 : function cube(;triangles=false, template::Mesh = TEMPLATE)
     131           3 :     mesh = similar(template)
     132           3 :     x = vattr(mesh, Val(:x))
     133             : 
     134           3 :     vs = [addvertex!(mesh) for _ in 1:8]
     135           6 :     x[vs[1:4]] = VQUAD[[1, 2, 4, 3]]
     136          18 :     x[vs[5:8]] = map(x -> V3(x[1], x[2], 1), VQUAD[[1, 2, 4, 3]])
     137             : 
     138           3 :     addface!(mesh, vs[1], vs[2], vs[3], vs[4])
     139           3 :     addface!(mesh, vs[5], vs[8], vs[7], vs[6])
     140             : 
     141           3 :     addface!(mesh, vs[1], vs[4], vs[8], vs[5])
     142           3 :     addface!(mesh, vs[2], vs[6], vs[7], vs[3])
     143             : 
     144           3 :     addface!(mesh, vs[1], vs[5], vs[6], vs[2])
     145           3 :     addface!(mesh, vs[4], vs[3], vs[7], vs[8])
     146             : 
     147           3 :     @assert nv(mesh) == 8 && nf(mesh) == 6
     148             : 
     149           3 :     if triangles
     150           6 :         SPLIT = [ ((VHnd(1), VHnd(2)), (VHnd(3), VHnd(4))),
     151             :                   ((VHnd(6), VHnd(5)), (VHnd(8), VHnd(7))),
     152             :                   ((VHnd(1), VHnd(4)), (VHnd(8), VHnd(5))),
     153             :                   ((VHnd(3), VHnd(2)), (VHnd(6), VHnd(7))),
     154             :                   ((VHnd(1), VHnd(5)), (VHnd(6), VHnd(2))),
     155             :                   ((VHnd(8), VHnd(4)), (VHnd(3), VHnd(7)))]
     156             : 
     157             : 
     158           1 :         for (a, b) in SPLIT
     159           6 :             insertedge!(mesh, halfedge(mesh, a), halfedge(mesh, b))
     160           6 :         end
     161             : 
     162           1 :         @assert nv(mesh) == 8 && nf(mesh) == 12 && istrianglemesh(mesh)
     163             :     end
     164             : 
     165           3 :     mesh
     166             : end
     167             : 
     168             : """
     169             :     mesh = tetrahedron([; template::Mesh])
     170             : 
     171             : Create standard tetrahedron.
     172             : """
     173           4 : function tetrahedron(; template::Mesh = TEMPLATE)
     174           2 :    mesh = similar(template)
     175           2 :     x = vattr(mesh, Val(:x))
     176           2 :     a, b, c, d = VHnd.(addvertices!(mesh, VQUAD))
     177           2 :     x[d] = V3(0, 0, 1)
     178             : 
     179           2 :     addface!(mesh, a, b, c)
     180           2 :     addface!(mesh, b, a, d)
     181           2 :     addface!(mesh, c, b, d)
     182           2 :     addface!(mesh, a, c, d)
     183             : 
     184           2 :     @assert nv(mesh) == 4 && nf(mesh) == 4
     185             : 
     186           2 :     mesh
     187             : end
     188             : 
     189             : #------------------------------------------------------------------------------
     190             : 
     191             : #
     192             : # code from lecture mp23 (solution to assignment)
     193             : #
     194             : 
     195           3 : function _rowsperiodic!(idx)
     196           3 :     idx[end, :] .= idx[1, :]
     197           3 :     idx
     198             : end
     199             : 
     200          14 : function _colsperiodic!(idx)
     201          14 :     idx[:, end] .= idx[:, 1]
     202          14 :     idx
     203             : end
     204             : 
     205           8 : function _rowpoles!(idx)
     206         128 :     idx[1, :] .= idx[1]
     207         128 :     idx[end, :] .= idx[end]
     208           8 :     idx
     209             : end
     210             : 
     211       10176 : _validtriangle(i, j, k) = (i != j && j != k && k != i)
     212             : 
     213             : """
     214             :     x, t = _samplemesh(f, u, v; boundary=identity)
     215             : 
     216             : Sample (x, y, z) = f(u, v)` given ranges `u` and `v`, apply
     217             : tessellation and return mesh `x, t` in shared vertex representation.
     218             : 
     219             : The function `boundary` reassigns vertex indices: `_rowsperiodic` and
     220             : `_colsperiodic!` create periodic "boundaries", `_rowpoles!` maps `v`
     221             : boundaries to a single vertex. The `boudary` function takes an index
     222             : grid as input and returns the same grid to enable concatenation, e.g.,
     223             : `_rowsperiodic! ∘ _colsperiodic!` for a torus.
     224             : 
     225             : The method "filters: unused vertex indices and invalid triangles.
     226             : """
     227          32 : function _samplemesh(f, u, v; boundary=identity)
     228          16 :     n, m = length(u), length(v)
     229          16 :     T = eltype(u)
     230             : 
     231          16 :     idxgrid = collect(reshape(1:(m * n), (m, n)))
     232             : 
     233             :     # apply boundary configuration
     234          16 :     boundary(idxgrid)
     235             : 
     236             :     # filter unused indices: "re-index"
     237        5476 :     idxused = fill(false, length(idxgrid))
     238        5476 :     idxused[vec(idxgrid)] .= true
     239          16 :     nused = count(idxused)
     240        5476 :     idxmap = zeros(Int, length(idxgrid))
     241          32 :     idxmap[idxused] .= 1:nused
     242        5492 :     idxgrid = map(idx -> idxmap[idx], idxgrid)
     243             : 
     244             :     # sample surface f(u, v)
     245       15126 :     x = zeros(T, 3, nused)
     246             : 
     247          16 :     ci = 1
     248          16 :     for (k, idx) in enumerate(CartesianIndices(idxgrid))
     249        5476 :         idxused[k] || continue
     250        5042 :         i, j = Tuple(idx)
     251             : 
     252        5188 :         x[:, ci] .= f(u[j], v[i])
     253        5042 :         ci += 1
     254       10936 :     end
     255             : 
     256             :     # tessellate regular mesh
     257       30528 :     t = zeros(Int, 3, 2 * (m - 1) * (n - 1))
     258             : 
     259          16 :     ci = 0
     260             : 
     261       10192 :     function _add(a, b, c)
     262       10416 :         _validtriangle(a, b, c) || return
     263             : 
     264        9936 :         ci += 1
     265        9936 :         t[:, ci] .= (a, b, c)
     266             :     end
     267             : 
     268          16 :     for j in 1:(n-1), i in 1:(m-1)
     269        5088 :         _add(idxgrid[i, j], idxgrid[i+1, j], idxgrid[i, j+1])
     270        5088 :         _add(idxgrid[i+1, j], idxgrid[i+1, j+1], idxgrid[i, j+1])
     271        5256 :     end
     272             : 
     273          16 :     x, copy(t[:, 1:ci])
     274             : end
     275             : 
     276        4324 : function _sphere(r::T, u::T, v::T) where {T<:Real}
     277        4324 :     (r * sin(v) * cos(u),
     278             :      r * sin(v) * sin(u),
     279             :      r * cos(v))
     280             : end
     281             : 
     282             : """
     283             :     mesh = sphere([; r=1, m=8 ,n=m, triangles=true, template::Mesh])
     284             : 
     285             : Generate sphere mesh with radius `r` tessellated on a `m × n`
     286             : grid of faces (with two poles).
     287             : 
     288             : See also [`torus`](@ref)
     289             : """
     290          12 : function sphere(; r=1.0, m::Int=8, n::Int=m, triangles::Bool=true,
     291             :                   template::Mesh = TEMPLATE)
     292           6 :     u = range(0, 2π, length=n + 1)
     293           6 :     v = range(0, π, length=m + 1)
     294             : 
     295        4330 :     f = (u, v) -> _sphere(r, u, v) # bind radius
     296           6 :     _samplemesh(f, u, v; boundary = _rowpoles! ∘ _colsperiodic!) |>
     297             :         _tessellate(triangles; template)
     298             : end
     299             : 
     300         192 : function _torus(R::T, r::T, u::T, v::T) where {T<:Real}
     301         192 :     (( R + r * cos(v)) * cos(u),
     302             :      ( R + r * cos(v)) * sin(u),
     303             :      -r * sin(v))
     304             : end
     305             : 
     306             : """
     307             :     mesh = torus([; ro=1, ri=0.25, m=8, n=8, triangles=true, template::Mesh])
     308             : 
     309             : Generate torus mesh with outer radius `ro and inner ("tube")
     310             : radius `ri` tessellated on a `m × n` grid of faces.
     311             : 
     312             : See also [`sphere`](@ref)
     313             : """
     314           6 : function torus(; ro=1.0, ri=0.25, m::Int=8, n::Int=m, triangles::Bool=true,
     315             :                  template::Mesh = TEMPLATE)
     316           3 :     @assert ro >= ri
     317             : 
     318           3 :     u = range(0, 2π, length=n + 1)
     319           3 :     v = range(0, 2π, length=m + 1)
     320             : 
     321         195 :     f = (u, v) -> _torus(ro, ri, u, v) # bind radii
     322           3 :     _samplemesh(f, u, v;
     323             :                 boundary=_rowsperiodic! ∘ _colsperiodic!) |>
     324             :                     _tessellate(triangles; template)
     325             : end
     326             : 
     327             : """
     328             :     mesh = tessellate(x, t[; template::Mesh])
     329             : 
     330             : Create mesh from shared vertex table using `TEMPLATE`.
     331             : 
     332             : See also [`createmesh`](@ref)
     333             : """
     334          32 : function tessellate(x, t; template = TEMPLATE)
     335          16 :     mesh = createmesh(t, template)
     336             : 
     337          16 :     mx = vattr(mesh, Val(:x))
     338          16 :     Ns = 1:length(mx[VHnd(1)])
     339             : 
     340          32 :     for (j, v) in enumerate(vertices(mesh))
     341        5042 :         mx[v] = V3(x[:, j])[Ns]
     342       10068 :     end
     343             : 
     344          16 :     mesh
     345             : end
     346             : 
     347          38 : function _tessellate(triangles; template)
     348          16 :     if triangles
     349          20 :         xt -> tessellate(xt...; template)
     350             :     else
     351          12 :         function f(xt)
     352           6 :             x, t = xt
     353           6 :             m = size(t, 2)
     354        3008 :             qs = zeros(Int, 4, m)
     355             : 
     356           6 :             j = 1
     357           6 :             k = 1
     358         403 :             while j < m
     359        1191 :                 ta = t[:, j]
     360        1191 :                 tb = t[:, j+1]
     361             : 
     362             :                 # guess triangulated quad from _samplemesh
     363         397 :                 if ta[2] == tb[1] && ta[3] == tb[3]
     364         352 :                     qs[:, k] .= (t[1, j], t[2, j], t[2, j+1], t[3, j])
     365         352 :                     j += 2
     366             :                 else
     367          45 :                     qs[1:3, k] .= t[:, j]
     368          45 :                     j += 1
     369             :                 end
     370             : 
     371         397 :                 k += 1
     372         397 :             end
     373             : 
     374           6 :             if j == m
     375           3 :                 qs[1:3, k] = t[:, j]
     376           3 :                 k += 1
     377             :             end
     378             : 
     379           6 :             tessellate(x, qs[:, 1:k-1]; template)
     380             :         end
     381             : 
     382             :     end
     383             : end
     384             : 
     385         360 : function _cylinder(r::T, u::T, v::T) where {T<:Real}
     386         360 :     (r * cos(u),
     387             :      r * sin(u),
     388             :      v)
     389             : end
     390             : 
     391         148 : function _cylinderclosed(r::T, h::T, u::T, v::T) where {T<:Real}
     392         148 :     (v < 0) && return (T(0), T(0), T(0))
     393         146 :     (v > h) && return (T(0), T(0), h)
     394             : 
     395         144 :     _cylinder(r, u, v)
     396             : end
     397             : 
     398             : """
     399             :     mesh = cylinder([; rh=1, h=1, m=8, n=8,
     400             :                        triangles=true, closed=false, template::Mesh])
     401             : 
     402             : Generate cylinder mesh with radius `r and height `h`
     403             : tessellated on a `m × n` grid of faces.
     404             : 
     405             : See also [`sphere`](@ref)
     406             : """
     407          10 : function cylinder(; r=1.0, h=1.0, m::Int=8, n::Int=m,
     408             :                   triangles::Bool=true, closed=false, template::Mesh = TEMPLATE)
     409           5 :     closed && return _closedcylinder(; r, h, m, n, triangles, template)
     410             : 
     411           3 :     _opencylinder(; r, h, m, n, triangles, template)
     412             : end
     413             : 
     414           4 : function _closedcylinder(; r, h, m, n, triangles::Bool=true, template)
     415           2 :     u = range(0, 2π, length=n + 1)
     416           2 :     v = collect(range(0, h, length=m + 1))
     417           2 :     v = vcat([-h], v, [h+1])
     418             : 
     419         296 :     f = (u, v) -> _cylinderclosed(r, h, u, v) # bind radius, h
     420           2 :     _samplemesh(f, u, v; boundary = _rowpoles! ∘ _colsperiodic!) |>
     421             :         _tessellate(triangles; template)
     422             : end
     423             : 
     424           6 : function _opencylinder(; r, h, m, n, triangles::Bool=true, template)
     425           3 :     u = range(0, 2π, length=n + 1)
     426           3 :     v = range(0, h, length=m + 1)
     427             : 
     428         219 :     f = (u, v) -> _cylinder(r, u, v) # bind radius
     429           3 :     _samplemesh(f, u, v; boundary = _colsperiodic!) |>
     430             :         _tessellate(triangles; template)
     431             : end
     432             : 
     433             : """
     434             :     mesh = rectangle([; w=1.0, h=1.0, triangles=true, z=(x, y) -> 0.0,
     435             :                         template::Mesh])
     436             : 
     437             : Generate a rectangular patch with width `w` and height `h` tessellated
     438             : on a `m × n` grid of faces. Optionally lift third coordinate to
     439             : `z(x, y)`.
     440             : 
     441             : See also [`sphere`](@ref)
     442             : """
     443           4 : function rectangle(; w=1.0, h=1.0, m::Int=8, n::Int=m, triangles=true,
     444          81 :                      z = (u, v) -> zero(u), template::Mesh = TEMPLATE)
     445           2 :     u = range(0, w, length=n + 1)
     446           2 :     v = range(0, h, length=m + 1)
     447             : 
     448         164 :     f = (u, v) -> (u, v, z(u, v))
     449           2 :     _samplemesh(f, u, v) |> _tessellate(triangles; template)
     450             : end
     451             : 
     452             : # TODO: right w/ hole (2 boundaries)
     453             : 
     454             : end

Generated by: LCOV version 1.16