Skip to content

Commit

Permalink
Merge pull request #1 from JuliaGeometry/master
Browse files Browse the repository at this point in the history
upate fork
  • Loading branch information
Sov-trotter authored Jun 16, 2020
2 parents 0cc4a97 + a6b6d42 commit 44f8030
Show file tree
Hide file tree
Showing 10 changed files with 174 additions and 70 deletions.
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "GeometryBasics"
uuid = "5c1252a2-5f33-56bf-86c9-59e7332b4326"
authors = ["SimonDanisch <[email protected]>"]
version = "0.2.10"
version = "0.2.13"

[deps]
IterTools = "c8e1da08-722c-5040-9ed9-7db0dc04731e"
Expand Down
104 changes: 71 additions & 33 deletions docs/src/decomposition.md
Original file line number Diff line number Diff line change
@@ -1,51 +1,89 @@
# Decomposition


## Displaying primitives
## GeometryBasic Mesh interface

To display geometry primitives, they need to be decomposable.
GeometryBasic defines an interface, to decompose abstract geometries into
points and triangle meshes.
This can be done for any arbitrary primitive, by overloading the following interface:

```julia
# Let's take SimpleRectangle as an example:
# Below is a minimal set of decomposable attributes to build up a triangle mesh:
isdecomposable(::Type{T}, ::Type{HR}) where {T<:Point, HR<:SimpleRectangle} = true
isdecomposable(::Type{T}, ::Type{HR}) where {T<:Face, HR<:SimpleRectangle} = true

# This is an example implementation of `decompose` for points.
function GeometryBasics.decompose(P::Type{Point{3, PT}}, r::SimpleRectangle, resolution=(2,2)) where PT
w,h = resolution
vec(
PT[
(x,y,0)
for x in range(r.x, stop = r.x+r.w, length = w),
y in range(r.y, stop = r.y+ r .h, length = h)
]
)

function GeometryBasics.coordinates(rect::Rect2D, nvertices=(2,2))
mini, maxi = extrema(rect)
xrange, yrange = LinRange.(mini, maxi, nvertices)
return ivec(((x,y) for x in xrange, y in yrange))
end

function GeometryBasics.decompose(::Type{T}, r::SimpleRectangle, resolution=(2,2)) where T <: Face
w,h = resolution
Idx = LinearIndices(resolution)
faces = vec([Face{4, Int}(
Idx[i, j], Idx[i+1, j],
Idx[i+1, j+1], Idx[i, j+1]
) for i=1:(w-1), j=1:(h-1)]
)
decompose(T, faces)
function GeometryBasics.faces(rect::Rect2D, nvertices=(2, 2))
w, h = nvertices
idx = LinearIndices(nvertices)
quad(i, j) = QuadFace{Int}(idx[i, j], idx[i+1, j], idx[i+1, j+1], idx[i, j+1])
return ivec((quad(i, j) for i=1:(w-1), j=1:(h-1)))
end
```
Those methods, for performance reasons, expect you to return an iterator, to make
materializing them with different element types allocation free. But of course,
can also return any `AbstractArray`.

With these methods defined, this constructor will magically work:

```julia
rect = SimpleRectangle(0, 0, 1, 1)
m = GLNormalMesh(rect)
vertices(m) == decompose(Point3f0, rect)
rect = Rect2D(0.0, 0.0, 1.0, 1.0)
m = GeometryBasics.mesh(rect)
```
If you want to set the `nvertices` argument, you need to wrap your primitive in a `Tesselation`
object:
```julia
m = GeometryBasics.mesh(Tesselation(rect, (50, 50)))
length(coordinates(m)) == 50^2
```

faces(m) == decompose(GLTriangle, rect) # GLFace{3} == GLTriangle
normals(m) # automatically calculated from mesh
As you can see, `coordinates` and `faces` is also defined on a mesh
```julia
coordinates(m)
faces(m)
```
But will actually not be an iterator anymore. Instead, the mesh constructor uses
the `decompose` function, that will collect the result of coordinates and will
convert it to a concrete element type:
```julia
decompose(Point2f0, rect) == convert(Vector{Point2f0}, collect(coordinates(rect)))
```
The element conversion is handled by `simplex_convert`, which also handles convert
between different face types:
```julia
decompose(QuadFace{Int}, rect) == convert(Vector{QuadFace{Int}}, collect(faces(rect)))
length(decompose(QuadFace{Int}, rect)) == 1
fs = decompose(GLTriangleFace, rect)
fs isa Vector{GLTriangleFace}
length(fs) == 2 # 2 triangles make up one quad ;)
```
`mesh` uses the most natural element type by default, which you can get with the unqualified Point type:
```julia
decompose(Point, rect) isa Vector{Point{2, Float64}}
```
You can also pass the element type to `mesh`:
```julia
m = GeometryBasics.mesh(rect, pointtype=Point2f0, facetype=QuadFace{Int})
```
You can also set the uv and normal type for the mesh constructor, which will then
calculate them for you, with the requested element type:
```julia
m = GeometryBasics.mesh(rect, uv=Vec2f0, normaltype=Vec3f0)
```

As you can see, the normals are automatically calculated only with the faces and points.
You can overwrite that behavior by also defining decompose for the `Normal` type!
As you can see, the normals are automatically calculated,
the same is true for texture coordinates. You can overload this behavior by overloading
`normals` or `texturecoordinates` the same way as coordinates.
`decompose` works a bit different for normals/texturecoordinates, since they dont have their own element type.
Instead, you can use `decompose` like this:
```julia
decompose(UV(Vec2f0), rect)
decompose(Normal(Vec3f0), rect)
# the short form for the above:
decompose_uv(rect)
decompose_normals(rect)
```
You can also use `triangle_mesh`, `normal_mesh` and `uv_normal_mesh` to call the
`mesh` constructor with predefined element types (Point2/3f0, Vec2/3f0), and the requested attributes.
2 changes: 1 addition & 1 deletion src/GeometryBasics.jl
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ module GeometryBasics
export OffsetInteger, ZeroIndex, OneIndex, GLIndex
export FaceView, SimpleFaceView
export AbstractPoint, PointMeta, PointWithUV
export PolygonMeta, MultiPointMeta
export PolygonMeta, MultiPointMeta, MultiLineStringMeta, MeshMeta
export decompose, coordinates, faces, normals, decompose_uv, decompose_normals, texturecoordinates
export Tesselation, pointmeta, Normal, UV, UVW
export GLTriangleFace, GLNormalMesh3D, GLPlainTriangleMesh, GLUVMesh3D, GLUVNormalMesh3D
Expand Down
8 changes: 6 additions & 2 deletions src/basic_types.jl
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Base.ndims(x::AbstractGeometry{Dim}) where Dim = Dim
"""
Geometry made of N connected points. Connected as one flat geometry, it makes a Ngon / Polygon.
Connected as volume it will be a Simplex / Tri / Cube.
Note That `Polytype{N} where N == 3` denotes a Triangle both as a Simplex or Ngon.
Note That `Polytope{N} where N == 3` denotes a Triangle both as a Simplex or Ngon.
"""
abstract type Polytope{Dim, T} <: AbstractGeometry{Dim, T} end
abstract type AbstractPolygon{Dim, T} <: Polytope{Dim, T} end
Expand Down Expand Up @@ -108,6 +108,10 @@ Base.summary(io::IO, x::Type{<: TriangleP}) = print(io, "Triangle")

const Quadrilateral{Dim, T} = Ngon{Dim, T, 4, P} where P <: AbstractPoint{Dim, T}

Base.show(io::IO, x::Quadrilateral) = print(io, "Quad(", join(x, ", "), ")")
Base.summary(io::IO, x::Type{<: Quadrilateral}) = print(io, "Quad")


"""
A `Simplex` is a generalization of an N-dimensional tetrahedra and can be thought
of as a minimal convex set containing the specified points.
Expand Down Expand Up @@ -336,7 +340,7 @@ An abstract mesh is a collection of Polytope elements (Simplices / Ngons).
The connections are defined via faces(mesh), the coordinates of the elements are returned by
coordinates(mesh). Arbitrary meta information can be attached per point or per face
"""
const AbstractMesh{Element} = AbstractVector{Element}
abstract type AbstractMesh{Element<:Polytope} <: AbstractVector{Element} end

"""
Mesh <: AbstractVector{Element}
Expand Down
9 changes: 9 additions & 0 deletions src/geometry_primitives.jl
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@ end

##
# conversion & decompose
convert_simplex(::Type{T}, x::T) where T = (x,)

function convert_simplex(NFT::Type{NgonFace{N, T1}}, f::Union{NgonFace{N, T2}}) where {T1, T2, N}
return (convert(NFT, f),)
end

convert_simplex(NFT::Type{NgonFace{3,T}}, f::NgonFace{3,T2}) where {T, T2} = (convert(NFT, f),)

"""
convert_simplex(::Type{Face{3}}, f::Face{N})
Expand Down Expand Up @@ -42,7 +49,9 @@ end

to_pointn(::Type{T}, x) where T<:Point = convert_simplex(T, x)[1]

# disambiguation method overlords
convert_simplex(::Type{Point}, x::Point) = (x,)
convert_simplex(::Type{Point{N,T}}, p::Point{N,T}) where {N, T} = (p,)
function convert_simplex(::Type{Point{N, T}}, x) where {N, T}
N2 = length(x)
return (Point{N, T}(ntuple(i-> i <= N2 ? T(x[i]) : T(0), N)),)
Expand Down
19 changes: 15 additions & 4 deletions src/interfaces.jl
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ function faces(primitive, nvertices=nothing)
return nothing
end

texturecoordinates(primitive, nvertices=nothing) = nothing

"""
Tesselation(primitive, nvertices)
For abstract geometries, when we generate
Expand Down Expand Up @@ -123,16 +125,25 @@ function decompose(NT::Normal{T}, primitive) where T
end

function decompose(UVT::Union{UV{T}, UVW{T}}, primitive) where T
# This is the fallback for texture coordinates if a primitive doesn't overload them
# We just take the positions and normalize them
uv = texturecoordinates(primitive)
if uv === nothing
return decompose(UVT, texturecoordinates(coordinates(primitive)))
# If the primitive doesn't even have coordinates, we're out of options and return
# nothing, indicating that texturecoordinates aren't implemented
positions = decompose(Point, primitive)
positions === nothing && return nothing
# Let this overlord do the work
return decompose(UVT, positions)
end
return collect_with_eltype(T, uv)
end

function texturecoordinates(positions::AbstractVector{<:VecTypes})
bb = Rect(positions)
return map(positions) do p
function decompose(UVT::Union{UV{T}, UVW{T}}, positions::AbstractVector{<:VecTypes}) where T
N = length(T)
positions_nd = decompose(Point{N, eltype(T)}, positions)
bb = Rect(positions_nd) # Make sure we get this as points
return map(positions_nd) do p
return (p .- minimum(bb)) ./ widths(bb)
end
end
Expand Down
6 changes: 2 additions & 4 deletions src/meshes.jl
Original file line number Diff line number Diff line change
Expand Up @@ -85,11 +85,9 @@ const GLNormalUVWMesh{Dim} = NormalUVWMesh{Dim, Float32}
const GLNormalUVWMesh2D = GLNormalUVWMesh{2}
const GLNormalUVWMesh3D = GLNormalUVWMesh{3}

best_pointtype(::Meshable{Dim, T}) where {Dim, T} = Point{Dim, T}

"""
mesh(primitive::GeometryPrimitive;
pointtype=best_pointtype(primitive), facetype=GLTriangle,
pointtype=Point, facetype=GLTriangle,
uvtype=nothing, normaltype=nothing)
Creates a mesh from `primitive`.
Expand All @@ -100,7 +98,7 @@ It also only losely correlates to the number of vertices, depending on the algor
#TODO: find a better number here!
"""
function mesh(primitive::Meshable;
pointtype=best_pointtype(primitive), facetype=GLTriangleFace,
pointtype=Point, facetype=GLTriangleFace,
uv=nothing, normaltype=nothing)

positions = decompose(pointtype, primitive)
Expand Down
60 changes: 38 additions & 22 deletions src/metadata.jl
Original file line number Diff line number Diff line change
Expand Up @@ -63,46 +63,52 @@ macro meta_type(name, mainfield, supertype, params...)
MetaName = Symbol("$(name)Meta")
field = QuoteNode(mainfield)
NoParams = Symbol("$(MetaName)NoParams")

params_sym = map(params) do param
param isa Symbol && return param
param isa Expr && param.head == :(<:) && return param.args[1]
error("Unsupported type parameter: $(param)")
end

expr = quote
struct $MetaName{$(params...), Typ <: $supertype{$(params...)}, Names, Types} <: $supertype{$(params...)}
struct $MetaName{$(params...), Typ <: $supertype{$(params_sym...)}, Names, Types} <: $supertype{$(params_sym...)}
main::Typ
meta::NamedTuple{Names, Types}
end

const $NoParams{Typ, Names, Types} = $MetaName{$(params...), Typ, Names, Types} where {$(params...)}
const $NoParams{Typ, Names, Types} = $MetaName{$(params_sym...), Typ, Names, Types} where {$(params_sym...)}

function Base.getproperty(x::$MetaName{$(params...), Typ, Names, Types}, field::Symbol) where {$(params...), Typ, Names, Types}
function Base.getproperty(x::$MetaName{$(params_sym...), Typ, Names, Types},
field::Symbol) where {$(params...), Typ, Names, Types}
field === $field && return getfield(x, :main)
field === :main && return getfield(x, :main)
Base.sym_in(field, Names) && return getfield(getfield(x, :meta), field)
error("Field $field not part of Element")
end

GeometryBasics.MetaType(T::Type{<: $supertype}) = $MetaName{T}
function GeometryBasics.MetaType(XX::Type{<: $supertype{$(params_sym...)} where {$(params...)}})
return $MetaName
end

function GeometryBasics.MetaType(
ST::Type{<: $supertype{$(params...)}},
ST::Type{<: $supertype{$(params_sym...)}},
::Type{NamedTuple{Names, Types}}) where {$(params...), Names, Types}
return $MetaName{$(params...), ST, Names, Types}
return $MetaName{$(params_sym...), ST, Names, Types}
end


GeometryBasics.MetaFree(::Type{<: $MetaName{Typ}}) where Typ = Typ
GeometryBasics.MetaFree(::Type{<: $MetaName}) = $name
GeometryBasics.metafree(x::$MetaName) = getfield(x, :main)
GeometryBasics.metafree(x::AbstractVector{<: $MetaName}) = getcolumns(x, $field)[1]
GeometryBasics.metafree(x::AbstractVector{<: $MetaName}) = getproperty(x, $field)
GeometryBasics.meta(x::$MetaName) = getfield(x, :meta)
GeometryBasics.meta(x::AbstractVector{<: $MetaName}) = getcolumns(x, :meta)[1]
GeometryBasics.meta(x::AbstractVector{<: $MetaName}) = getproperty(x, :meta)

function GeometryBasics.meta(main::$supertype; meta...)
function GeometryBasics.meta(main::$supertype{$(params_sym...)}; meta...) where {$(params...)}
isempty(meta) && return elements # no meta to add!
return $MetaName(main; meta...)
end

function GeometryBasics.attributes(hasmeta::$MetaName)
return Dict{Symbol, Any}((name => getproperty(hasmeta, name) for name in propertynames(hasmeta)))
end

function GeometryBasics.meta(elements::AbstractVector{T}; meta...) where T <: $supertype
function GeometryBasics.meta(elements::AbstractVector{XX}; meta...) where XX <: $supertype{$(params_sym...)} where {$(params...)}
isempty(meta) && return elements # no meta to add!
n = length(elements)
for (k, v) in meta
Expand All @@ -118,7 +124,11 @@ macro meta_type(name, mainfield, supertype, params...)
# get the first element to get the per element named tuple type
ElementNT = typeof(map(first, nt))

return StructArray{MetaType(T, ElementNT)}(($(mainfield) = elements, nt...))
return StructArray{MetaType(XX, ElementNT)}(($(mainfield) = elements, nt...))
end

function GeometryBasics.attributes(hasmeta::$MetaName)
return Dict{Symbol, Any}((name => getproperty(hasmeta, name) for name in propertynames(hasmeta)))
end

function (MT::Type{<: $MetaName})(args...; meta...)
Expand All @@ -132,22 +142,20 @@ macro meta_type(name, mainfield, supertype, params...)
return MT(main, nt)
end

function Base.propertynames(::$MetaName{$(params...), Typ, Names, Types}) where {$(params...), Typ, Names, Types}
function Base.propertynames(::$MetaName{$(params_sym...), Typ, Names, Types}) where {$(params...), Typ, Names, Types}
return ($field, Names...)
end

function StructArrays.staticschema(::Type{$MetaName{$(params...), Typ, Names, Types}}) where {$(params...), Typ, Names, Types}
function StructArrays.staticschema(::Type{$MetaName{$(params_sym...), Typ, Names, Types}}) where {$(params...), Typ, Names, Types}
NamedTuple{($field, Names...), Base.tuple_type_cons(Typ, Types)}
end

function StructArrays.createinstance(
::Type{$MetaName{$(params...), Typ, Names, Types}},
::Type{$MetaName{$(params_sym...), Typ, Names, Types}},
metafree, args...
) where {$(params...), Typ, Names, Types}
$MetaName(metafree, NamedTuple{Names, Types}(args))
end


end
return esc(expr)
end
Expand All @@ -163,6 +171,14 @@ Base.getindex(x::SimplexFaceMeta, idx::Int) = getindex(metafree(x), idx)

@meta_type(Polygon, polygon, AbstractPolygon, N, T)

@meta_type(MultiPoint, points, AbstractVector, P)
@meta_type(MultiPoint, points, AbstractVector, P <: AbstractPoint)
Base.getindex(x::MultiPointMeta, idx::Int) = getindex(metafree(x), idx)
Base.size(x::MultiPointMeta) = size(metafree(x))

@meta_type(MultiLineString, linestrings, AbstractVector, P <: LineString)
Base.getindex(x::MultiLineStringMeta, idx::Int) = getindex(metafree(x), idx)
Base.size(x::MultiLineStringMeta) = size(metafree(x))

@meta_type(Mesh, mesh, AbstractMesh, Element <: Polytope)
Base.getindex(x::MeshMeta, idx::Int) = getindex(metafree(x), idx)
Base.size(x::MeshMeta) = size(metafree(x))
Loading

0 comments on commit 44f8030

Please sign in to comment.