Files
JuliaFEM.jl/src/mesh/refine.jl
T
Jukka Aho 43539f2441 Implement Hex8 longest-edge refinement
- Introduce AbstractRefineStrategy and LongestEdgeBisection
- Split Hex8 elements per axis with midpoint deduplication
- Preserve mesh metadata while iterating refinement levels
2025-11-18 15:21:41 +02:00

310 lines
9.3 KiB
Julia

# This file is a part of JuliaFEM.
# License is MIT: see https://github.com/JuliaFEM/JuliaFEM.jl/blob/master/LICENSE.md
"""
AbstractRefineStrategy
Abstract base type for mesh refinement strategies.
"""
abstract type AbstractRefineStrategy end
"""
LongestEdgeBisection <: AbstractRefineStrategy
Refinement strategy that splits hex elements along their longest dimension.
This is a simple octree-style refinement where each element is analyzed
to find its longest edge, and then split into two elements along that direction.
# Fields
- `levels::Int`: Number of refinement iterations (default: 1)
# Example
```julia
# Create coarse mesh with single element
nodes = [Vec(0.0, 0.0, 0.0), Vec(1.0, 0.0, 0.0),
Vec(1.0, 1.0, 0.0), Vec(0.0, 1.0, 0.0),
Vec(0.0, 0.0, 1.0), Vec(1.0, 0.0, 1.0),
Vec(1.0, 1.0, 1.0), Vec(0.0, 1.0, 1.0)]
connectivity = [(UInt32(1), UInt32(2), UInt32(3), UInt32(4),
UInt32(5), UInt32(6), UInt32(7), UInt32(8))]
mesh = Mesh{Hex8}(nodes, connectivity)
# Refine 2 levels
strategy = LongestEdgeBisection(2)
refined_mesh = refine(mesh, strategy)
```
"""
struct LongestEdgeBisection <: AbstractRefineStrategy
levels::Int
end
LongestEdgeBisection() = LongestEdgeBisection(1)
"""
refine(mesh::Mesh{Hex8}, strategy::LongestEdgeBisection) -> Mesh{Hex8}
Refine a Hex8 mesh using the longest edge bisection strategy.
# Arguments
- `mesh::Mesh{Hex8}`: Input mesh to refine
- `strategy::LongestEdgeBisection`: Refinement strategy with number of levels
# Returns
- `Mesh{Hex8}`: Refined mesh
# Algorithm
For each refinement level:
1. For each element, compute edge lengths
2. Determine longest edge direction (x, y, or z)
3. Create midpoint node and split element into two along that direction
4. Update connectivity and preserve element sets
# Example
```julia
mesh = Mesh{Hex8}(nodes, connectivity)
refined = refine(mesh, LongestEdgeBisection(2)) # 2 refinement levels
println("Original: ", nelements(mesh), " elements")
println("Refined: ", nelements(refined), " elements")
```
"""
function refine(mesh::Mesh{Hex8}, strategy::LongestEdgeBisection)
current_mesh = mesh
for level in 1:strategy.levels
current_mesh = _refine_once_hex8(current_mesh)
end
return current_mesh
end
"""
_refine_once_hex8(mesh::Mesh{Hex8}) -> Mesh{Hex8}
Internal function: Perform one refinement iteration on Hex8 mesh.
For each hex element:
1. Compute element dimensions (max x, y, z extents)
2. Find longest dimension
3. Split element into two along that dimension
4. Create 4 new face nodes at the midplane (or reuse if they exist)
5. Generate two new hex elements
Hex8 node numbering (reference):
```
8-------7
/| /|
5-------6 |
| | | |
| 4-----|-3
|/ |/
1-------2
```
Bottom face: 1-2-3-4 (z = -1)
Top face: 5-6-7-8 (z = +1)
"""
function _refine_once_hex8(mesh::Mesh{Hex8})
old_nodes = copy(mesh.nodes)
new_connectivity = NTuple{8,UInt32}[]
# Dictionary to track existing nodes by position (for deduplication)
node_map = Dict{Vec{3,Float64},UInt32}()
for (i, node) in enumerate(old_nodes)
node_map[node] = UInt32(i)
end
# Helper function to get or create a node
function get_or_create_node(pos::Vec{3,Float64})
# Round to avoid floating point comparison issues
rounded_pos = Vec{3}(round.(Tuple(pos), digits=10))
if haskey(node_map, rounded_pos)
return node_map[rounded_pos]
else
push!(old_nodes, rounded_pos)
node_id = UInt32(length(old_nodes))
node_map[rounded_pos] = node_id
return node_id
end
end
# Track which element set each new element belongs to
new_element_sets = Dict{Symbol,Set{UInt32}}()
for (set_name, _) in mesh.element_sets
new_element_sets[set_name] = Set{UInt32}()
end
element_counter = UInt32(0)
# Process each element
for (elem_idx, elem_conn) in enumerate(mesh.connectivity)
# Get element's 8 nodes
nodes_coords = [mesh.nodes[i] for i in elem_conn]
# Determine longest dimension
min_coords = minimum(nodes_coords)
max_coords = maximum(nodes_coords)
dims = max_coords - min_coords
# Find longest dimension (1=x, 2=y, 3=z)
longest_dim = argmax([dims[1], dims[2], dims[3]])
# Split element along longest dimension
if longest_dim == 1
# Split along X direction
new_elems = _split_hex8_x(elem_conn, nodes_coords, get_or_create_node)
elseif longest_dim == 2
# Split along Y direction
new_elems = _split_hex8_y(elem_conn, nodes_coords, get_or_create_node)
else
# Split along Z direction
new_elems = _split_hex8_z(elem_conn, nodes_coords, get_or_create_node)
end
# Add new elements to connectivity
for new_elem in new_elems
push!(new_connectivity, new_elem)
element_counter += 1
# Update element sets
for (set_name, elem_set) in mesh.element_sets
if elem_idx in elem_set
push!(new_element_sets[set_name], element_counter)
end
end
end
end
# Create new mesh
return Mesh{Hex8}(old_nodes, new_connectivity, new_element_sets, mesh.node_sets)
end
"""
_split_hex8_x(conn, nodes, get_or_create_node) -> Tuple{NTuple{8,UInt32}, NTuple{8,UInt32}}
Split a Hex8 element along the X direction.
Creates 4 new nodes at the midplane perpendicular to X axis:
- Midpoint of edge 1-2
- Midpoint of edge 4-3
- Midpoint of edge 5-6
- Midpoint of edge 8-7
Returns two new hex elements.
"""
function _split_hex8_x(conn::NTuple{8,UInt32}, nodes::Vector{Vec{3,Float64}},
get_or_create_node::Function)
# Original nodes: 1-2-3-4-5-6-7-8
n1, n2, n3, n4, n5, n6, n7, n8 = conn
# Create 4 new midpoint nodes
# Mid12: midpoint of edge 1-2 (bottom front)
# Mid43: midpoint of edge 4-3 (bottom back)
# Mid56: midpoint of edge 5-6 (top front)
# Mid87: midpoint of edge 8-7 (top back)
mid12 = 0.5 * (nodes[1] + nodes[2])
mid43 = 0.5 * (nodes[4] + nodes[3])
mid56 = 0.5 * (nodes[5] + nodes[6])
mid87 = 0.5 * (nodes[8] + nodes[7])
# Get or create nodes (deduplication!)
nmid12 = get_or_create_node(mid12)
nmid43 = get_or_create_node(mid43)
nmid56 = get_or_create_node(mid56)
nmid87 = get_or_create_node(mid87)
# First element (left half)
elem1 = (n1, nmid12, nmid43, n4, n5, nmid56, nmid87, n8)
# Second element (right half)
elem2 = (nmid12, n2, n3, nmid43, nmid56, n6, n7, nmid87)
return (elem1, elem2)
end
"""
_split_hex8_y(conn, nodes, get_or_create_node) -> Tuple{NTuple{8,UInt32}, NTuple{8,UInt32}}
Split a Hex8 element along the Y direction.
Creates 4 new nodes at the midplane perpendicular to Y axis.
"""
function _split_hex8_y(conn::NTuple{8,UInt32}, nodes::Vector{Vec{3,Float64}},
get_or_create_node::Function)
n1, n2, n3, n4, n5, n6, n7, n8 = conn
# Create 4 new midpoint nodes
# Mid21: midpoint of edge 2-1 (bottom front)
# Mid34: midpoint of edge 3-4 (bottom back)
# Mid65: midpoint of edge 6-5 (top front)
# Mid78: midpoint of edge 7-8 (top back)
mid21 = 0.5 * (nodes[2] + nodes[1])
mid34 = 0.5 * (nodes[3] + nodes[4])
mid65 = 0.5 * (nodes[6] + nodes[5])
mid78 = 0.5 * (nodes[7] + nodes[8])
# Get or create nodes (deduplication!)
nmid21 = get_or_create_node(mid21)
nmid34 = get_or_create_node(mid34)
nmid65 = get_or_create_node(mid65)
nmid78 = get_or_create_node(mid78)
# First element (front half)
elem1 = (n1, n2, nmid21, nmid34, n5, n6, nmid65, nmid78)
# Second element (back half)
elem2 = (nmid34, nmid21, n3, n4, nmid78, nmid65, n7, n8)
return (elem1, elem2)
end
"""
_split_hex8_z(conn, nodes, get_or_create_node) -> Tuple{NTuple{8,UInt32}, NTuple{8,UInt32}}
Split a Hex8 element along the Z direction.
Creates 4 new nodes at the midplane perpendicular to Z axis.
"""
function _split_hex8_z(conn::NTuple{8,UInt32}, nodes::Vector{Vec{3,Float64}},
get_or_create_node::Function)
n1, n2, n3, n4, n5, n6, n7, n8 = conn
# Create 4 new midpoint nodes at mid-height
# Mid15: midpoint of edge 1-5
# Mid26: midpoint of edge 2-6
# Mid37: midpoint of edge 3-7
# Mid48: midpoint of edge 4-8
mid15 = 0.5 * (nodes[1] + nodes[5])
mid26 = 0.5 * (nodes[2] + nodes[6])
mid37 = 0.5 * (nodes[3] + nodes[7])
mid48 = 0.5 * (nodes[4] + nodes[8])
# Get or create nodes (deduplication!)
nmid15 = get_or_create_node(mid15)
nmid26 = get_or_create_node(mid26)
nmid37 = get_or_create_node(mid37)
nmid48 = get_or_create_node(mid48)
# First element (bottom half)
elem1 = (n1, n2, n3, n4, nmid15, nmid26, nmid37, nmid48)
# Second element (top half)
elem2 = (nmid15, nmid26, nmid37, nmid48, n5, n6, n7, n8)
return (elem1, elem2)
end
"""
compute_element_volume(nodes::Vector{Vec{3,Float64}}) -> Float64
Compute approximate volume of a hex element using bounding box.
This is a quick approximation for determining element size, not exact volume.
"""
function compute_element_volume(nodes::Vector{Vec{3,Float64}})
min_coords = minimum(nodes)
max_coords = maximum(nodes)
dims = max_coords - min_coords
return dims[1] * dims[2] * dims[3]
end