Packages
A configurable spherical geodesic grid data model designed for simulations GEOF Planet offers an ideal data model for simulations of 2½D spherical phenomena like oceanography and climate. It is ported from the JavaScript project g-e-o-f/peels.
Current section
Files
Jump to
Current section
Files
lib/geometry/mesh.ex
defmodule GEOF.Planet.Geometry.Mesh do
@moduledoc """
Functions for translating a Planet's geometry into an ordinary 3D solid.
"""
import :math
alias GEOF.Planet.{
Sphere,
Field,
Geometry.FieldCentroids,
Geometry.InterfieldCentroids
}
# Constant lists of integers that break down vertices of hexagons and polygons into triangles.
@pent_faces [0, 2, 1, 0, 4, 2, 4, 3, 2]
@pent_faces_cw [1, 2, 0, 2, 4, 0, 2, 3, 4]
@hex_faces [0, 2, 1, 0, 3, 2, 0, 5, 3, 5, 4, 3]
# The order of Field adjacent directions to process.
@adj_order [:nw, :w, :sw, :se, :e, :ne]
###
#
# TYPES
#
###
@typedoc "Maps flattened Field indexes to the vertex index."
@type vertex_order :: %{non_neg_integer => non_neg_integer}
@typedoc "The payload given to the 3D environment."
@type mesh :: [
position: nonempty_list(float),
normal: nonempty_list(float),
index: nonempty_list(non_neg_integer),
vertex_order: vertex_order
]
###
#
# FUNCTIONS
#
###
# Convenience function
@doc "Produces a `mesh` with a polygon for each field. Deprecated outside of testing and examples."
@spec poly_per_field(Sphere.divisions()) :: mesh
def poly_per_field(divisions) do
field_centroids = FieldCentroids.field_centroids(divisions)
poly_per_field(
divisions,
field_centroids,
InterfieldCentroids.interfield_centroids(field_centroids, divisions)
)
end
# Main function
@doc "Produces a `mesh` with a polygon for each field. Vertices are copied for each polygon."
@spec poly_per_field(
Sphere.divisions(),
FieldCentroids.centroid_sphere(),
InterfieldCentroids.interfield_centroid_sphere()
) :: mesh
def poly_per_field(divisions, field_centroids, interfield_centroids) do
d = divisions
interfield_cartesian_points =
Enum.reduce(interfield_centroids, %{}, fn {field_index_set, {:pos, lat, lon}}, acc ->
Map.put(acc, field_index_set, {:xzy, cos(lat) * cos(lon), cos(lat) * sin(lon), sin(lat)})
end)
mesh_attr_buffers =
Sphere.for_all_fields(
[
position: [],
normal: [],
index: [],
vertex_order: %{},
pos_c: 0,
buffer_i: 0
],
d,
fn acc, field_index ->
pos_c = acc[:pos_c]
adj = Field.adjacents(field_index, d)
sides = if Map.has_key?(adj, :ne), do: 6, else: 5
{:pos, lat, lon} = Map.get(field_centroids, field_index)
position =
Enum.reduce(
0..(sides - 1),
acc[:position],
fn s, acc ->
next_s = rem(s + sides + 1, sides)
{:xzy, x, z, y} =
Map.get(
interfield_cartesian_points,
MapSet.new([
field_index,
Map.get(adj, Enum.at(@adj_order, s)),
Map.get(adj, Enum.at(@adj_order, next_s))
])
)
[z | [y | [x | acc]]]
end
)
poly_normal = [
# x
cos(lat) * cos(lon),
# y
sin(lat),
# z
cos(lat) * sin(lon)
]
# This repeats the same normal for each vertex
normal =
Enum.reduce(
0..(sides - 1),
acc[:normal],
fn _, acc ->
[x, y, z] = poly_normal
[z | [y | [x | acc]]]
end
)
index =
cond do
# :south is a special case; its faces must wind backwards
field_index == :south ->
Enum.reduce(@pent_faces_cw, acc[:index], fn f, acc -> [f + pos_c | acc] end)
sides == 5 ->
Enum.reduce(@pent_faces, acc[:index], fn f, acc -> [f + pos_c | acc] end)
true ->
Enum.reduce(@hex_faces, acc[:index], fn f, acc -> [f + pos_c | acc] end)
end
[
position: position,
normal: normal,
index: index,
vertex_order:
Map.put(acc[:vertex_order], Field.flatten_index(field_index, d), acc[:buffer_i]),
pos_c: acc[:pos_c] + sides,
buffer_i: acc[:buffer_i] + sides * 3
]
end
)
[
position: Enum.reverse(mesh_attr_buffers[:position]),
normal: Enum.reverse(mesh_attr_buffers[:normal]),
index: Enum.reverse(mesh_attr_buffers[:index]),
vertex_order: mesh_attr_buffers[:vertex_order]
]
end
end