Skip to content

Commit 44f8030

Browse files
authored
Merge pull request #1 from JuliaGeometry/master
upate fork
2 parents 0cc4a97 + a6b6d42 commit 44f8030

File tree

10 files changed

+174
-70
lines changed

10 files changed

+174
-70
lines changed

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "GeometryBasics"
22
uuid = "5c1252a2-5f33-56bf-86c9-59e7332b4326"
33
authors = ["SimonDanisch <[email protected]>"]
4-
version = "0.2.10"
4+
version = "0.2.13"
55

66
[deps]
77
IterTools = "c8e1da08-722c-5040-9ed9-7db0dc04731e"

docs/src/decomposition.md

Lines changed: 71 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,89 @@
11
# Decomposition
22

33

4-
## Displaying primitives
4+
## GeometryBasic Mesh interface
55

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

910
```julia
10-
# Let's take SimpleRectangle as an example:
11-
# Below is a minimal set of decomposable attributes to build up a triangle mesh:
12-
isdecomposable(::Type{T}, ::Type{HR}) where {T<:Point, HR<:SimpleRectangle} = true
13-
isdecomposable(::Type{T}, ::Type{HR}) where {T<:Face, HR<:SimpleRectangle} = true
14-
15-
# This is an example implementation of `decompose` for points.
16-
function GeometryBasics.decompose(P::Type{Point{3, PT}}, r::SimpleRectangle, resolution=(2,2)) where PT
17-
w,h = resolution
18-
vec(
19-
PT[
20-
(x,y,0)
21-
for x in range(r.x, stop = r.x+r.w, length = w),
22-
y in range(r.y, stop = r.y+ r .h, length = h)
23-
]
24-
)
11+
12+
function GeometryBasics.coordinates(rect::Rect2D, nvertices=(2,2))
13+
mini, maxi = extrema(rect)
14+
xrange, yrange = LinRange.(mini, maxi, nvertices)
15+
return ivec(((x,y) for x in xrange, y in yrange))
2516
end
2617

27-
function GeometryBasics.decompose(::Type{T}, r::SimpleRectangle, resolution=(2,2)) where T <: Face
28-
w,h = resolution
29-
Idx = LinearIndices(resolution)
30-
faces = vec([Face{4, Int}(
31-
Idx[i, j], Idx[i+1, j],
32-
Idx[i+1, j+1], Idx[i, j+1]
33-
) for i=1:(w-1), j=1:(h-1)]
34-
)
35-
decompose(T, faces)
18+
function GeometryBasics.faces(rect::Rect2D, nvertices=(2, 2))
19+
w, h = nvertices
20+
idx = LinearIndices(nvertices)
21+
quad(i, j) = QuadFace{Int}(idx[i, j], idx[i+1, j], idx[i+1, j+1], idx[i, j+1])
22+
return ivec((quad(i, j) for i=1:(w-1), j=1:(h-1)))
3623
end
3724
```
25+
Those methods, for performance reasons, expect you to return an iterator, to make
26+
materializing them with different element types allocation free. But of course,
27+
can also return any `AbstractArray`.
3828

3929
With these methods defined, this constructor will magically work:
4030

4131
```julia
42-
rect = SimpleRectangle(0, 0, 1, 1)
43-
m = GLNormalMesh(rect)
44-
vertices(m) == decompose(Point3f0, rect)
32+
rect = Rect2D(0.0, 0.0, 1.0, 1.0)
33+
m = GeometryBasics.mesh(rect)
34+
```
35+
If you want to set the `nvertices` argument, you need to wrap your primitive in a `Tesselation`
36+
object:
37+
```julia
38+
m = GeometryBasics.mesh(Tesselation(rect, (50, 50)))
39+
length(coordinates(m)) == 50^2
40+
```
4541

46-
faces(m) == decompose(GLTriangle, rect) # GLFace{3} == GLTriangle
47-
normals(m) # automatically calculated from mesh
42+
As you can see, `coordinates` and `faces` is also defined on a mesh
43+
```julia
44+
coordinates(m)
45+
faces(m)
46+
```
47+
But will actually not be an iterator anymore. Instead, the mesh constructor uses
48+
the `decompose` function, that will collect the result of coordinates and will
49+
convert it to a concrete element type:
50+
```julia
51+
decompose(Point2f0, rect) == convert(Vector{Point2f0}, collect(coordinates(rect)))
52+
```
53+
The element conversion is handled by `simplex_convert`, which also handles convert
54+
between different face types:
55+
```julia
56+
decompose(QuadFace{Int}, rect) == convert(Vector{QuadFace{Int}}, collect(faces(rect)))
57+
length(decompose(QuadFace{Int}, rect)) == 1
58+
fs = decompose(GLTriangleFace, rect)
59+
fs isa Vector{GLTriangleFace}
60+
length(fs) == 2 # 2 triangles make up one quad ;)
61+
```
62+
`mesh` uses the most natural element type by default, which you can get with the unqualified Point type:
63+
```julia
64+
decompose(Point, rect) isa Vector{Point{2, Float64}}
65+
```
66+
You can also pass the element type to `mesh`:
67+
```julia
68+
m = GeometryBasics.mesh(rect, pointtype=Point2f0, facetype=QuadFace{Int})
69+
```
70+
You can also set the uv and normal type for the mesh constructor, which will then
71+
calculate them for you, with the requested element type:
72+
```julia
73+
m = GeometryBasics.mesh(rect, uv=Vec2f0, normaltype=Vec3f0)
4874
```
4975

50-
As you can see, the normals are automatically calculated only with the faces and points.
51-
You can overwrite that behavior by also defining decompose for the `Normal` type!
76+
As you can see, the normals are automatically calculated,
77+
the same is true for texture coordinates. You can overload this behavior by overloading
78+
`normals` or `texturecoordinates` the same way as coordinates.
79+
`decompose` works a bit different for normals/texturecoordinates, since they dont have their own element type.
80+
Instead, you can use `decompose` like this:
81+
```julia
82+
decompose(UV(Vec2f0), rect)
83+
decompose(Normal(Vec3f0), rect)
84+
# the short form for the above:
85+
decompose_uv(rect)
86+
decompose_normals(rect)
87+
```
88+
You can also use `triangle_mesh`, `normal_mesh` and `uv_normal_mesh` to call the
89+
`mesh` constructor with predefined element types (Point2/3f0, Vec2/3f0), and the requested attributes.

src/GeometryBasics.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ module GeometryBasics
2929
export OffsetInteger, ZeroIndex, OneIndex, GLIndex
3030
export FaceView, SimpleFaceView
3131
export AbstractPoint, PointMeta, PointWithUV
32-
export PolygonMeta, MultiPointMeta
32+
export PolygonMeta, MultiPointMeta, MultiLineStringMeta, MeshMeta
3333
export decompose, coordinates, faces, normals, decompose_uv, decompose_normals, texturecoordinates
3434
export Tesselation, pointmeta, Normal, UV, UVW
3535
export GLTriangleFace, GLNormalMesh3D, GLPlainTriangleMesh, GLUVMesh3D, GLUVNormalMesh3D

src/basic_types.jl

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ Base.ndims(x::AbstractGeometry{Dim}) where Dim = Dim
88
"""
99
Geometry made of N connected points. Connected as one flat geometry, it makes a Ngon / Polygon.
1010
Connected as volume it will be a Simplex / Tri / Cube.
11-
Note That `Polytype{N} where N == 3` denotes a Triangle both as a Simplex or Ngon.
11+
Note That `Polytope{N} where N == 3` denotes a Triangle both as a Simplex or Ngon.
1212
"""
1313
abstract type Polytope{Dim, T} <: AbstractGeometry{Dim, T} end
1414
abstract type AbstractPolygon{Dim, T} <: Polytope{Dim, T} end
@@ -108,6 +108,10 @@ Base.summary(io::IO, x::Type{<: TriangleP}) = print(io, "Triangle")
108108

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

111+
Base.show(io::IO, x::Quadrilateral) = print(io, "Quad(", join(x, ", "), ")")
112+
Base.summary(io::IO, x::Type{<: Quadrilateral}) = print(io, "Quad")
113+
114+
111115
"""
112116
A `Simplex` is a generalization of an N-dimensional tetrahedra and can be thought
113117
of as a minimal convex set containing the specified points.
@@ -336,7 +340,7 @@ An abstract mesh is a collection of Polytope elements (Simplices / Ngons).
336340
The connections are defined via faces(mesh), the coordinates of the elements are returned by
337341
coordinates(mesh). Arbitrary meta information can be attached per point or per face
338342
"""
339-
const AbstractMesh{Element} = AbstractVector{Element}
343+
abstract type AbstractMesh{Element<:Polytope} <: AbstractVector{Element} end
340344

341345
"""
342346
Mesh <: AbstractVector{Element}

src/geometry_primitives.jl

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,13 @@ end
88

99
##
1010
# conversion & decompose
11+
convert_simplex(::Type{T}, x::T) where T = (x,)
12+
13+
function convert_simplex(NFT::Type{NgonFace{N, T1}}, f::Union{NgonFace{N, T2}}) where {T1, T2, N}
14+
return (convert(NFT, f),)
15+
end
16+
17+
convert_simplex(NFT::Type{NgonFace{3,T}}, f::NgonFace{3,T2}) where {T, T2} = (convert(NFT, f),)
1118

1219
"""
1320
convert_simplex(::Type{Face{3}}, f::Face{N})
@@ -42,7 +49,9 @@ end
4249

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

52+
# disambiguation method overlords
4553
convert_simplex(::Type{Point}, x::Point) = (x,)
54+
convert_simplex(::Type{Point{N,T}}, p::Point{N,T}) where {N, T} = (p,)
4655
function convert_simplex(::Type{Point{N, T}}, x) where {N, T}
4756
N2 = length(x)
4857
return (Point{N, T}(ntuple(i-> i <= N2 ? T(x[i]) : T(0), N)),)

src/interfaces.jl

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ function faces(primitive, nvertices=nothing)
3333
return nothing
3434
end
3535

36+
texturecoordinates(primitive, nvertices=nothing) = nothing
37+
3638
"""
3739
Tesselation(primitive, nvertices)
3840
For abstract geometries, when we generate
@@ -123,16 +125,25 @@ function decompose(NT::Normal{T}, primitive) where T
123125
end
124126

125127
function decompose(UVT::Union{UV{T}, UVW{T}}, primitive) where T
128+
# This is the fallback for texture coordinates if a primitive doesn't overload them
129+
# We just take the positions and normalize them
126130
uv = texturecoordinates(primitive)
127131
if uv === nothing
128-
return decompose(UVT, texturecoordinates(coordinates(primitive)))
132+
# If the primitive doesn't even have coordinates, we're out of options and return
133+
# nothing, indicating that texturecoordinates aren't implemented
134+
positions = decompose(Point, primitive)
135+
positions === nothing && return nothing
136+
# Let this overlord do the work
137+
return decompose(UVT, positions)
129138
end
130139
return collect_with_eltype(T, uv)
131140
end
132141

133-
function texturecoordinates(positions::AbstractVector{<:VecTypes})
134-
bb = Rect(positions)
135-
return map(positions) do p
142+
function decompose(UVT::Union{UV{T}, UVW{T}}, positions::AbstractVector{<:VecTypes}) where T
143+
N = length(T)
144+
positions_nd = decompose(Point{N, eltype(T)}, positions)
145+
bb = Rect(positions_nd) # Make sure we get this as points
146+
return map(positions_nd) do p
136147
return (p .- minimum(bb)) ./ widths(bb)
137148
end
138149
end

src/meshes.jl

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -85,11 +85,9 @@ const GLNormalUVWMesh{Dim} = NormalUVWMesh{Dim, Float32}
8585
const GLNormalUVWMesh2D = GLNormalUVWMesh{2}
8686
const GLNormalUVWMesh3D = GLNormalUVWMesh{3}
8787

88-
best_pointtype(::Meshable{Dim, T}) where {Dim, T} = Point{Dim, T}
89-
9088
"""
9189
mesh(primitive::GeometryPrimitive;
92-
pointtype=best_pointtype(primitive), facetype=GLTriangle,
90+
pointtype=Point, facetype=GLTriangle,
9391
uvtype=nothing, normaltype=nothing)
9492
9593
Creates a mesh from `primitive`.
@@ -100,7 +98,7 @@ It also only losely correlates to the number of vertices, depending on the algor
10098
#TODO: find a better number here!
10199
"""
102100
function mesh(primitive::Meshable;
103-
pointtype=best_pointtype(primitive), facetype=GLTriangleFace,
101+
pointtype=Point, facetype=GLTriangleFace,
104102
uv=nothing, normaltype=nothing)
105103

106104
positions = decompose(pointtype, primitive)

src/metadata.jl

Lines changed: 38 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -63,46 +63,52 @@ macro meta_type(name, mainfield, supertype, params...)
6363
MetaName = Symbol("$(name)Meta")
6464
field = QuoteNode(mainfield)
6565
NoParams = Symbol("$(MetaName)NoParams")
66+
67+
params_sym = map(params) do param
68+
param isa Symbol && return param
69+
param isa Expr && param.head == :(<:) && return param.args[1]
70+
error("Unsupported type parameter: $(param)")
71+
end
72+
6673
expr = quote
67-
struct $MetaName{$(params...), Typ <: $supertype{$(params...)}, Names, Types} <: $supertype{$(params...)}
74+
struct $MetaName{$(params...), Typ <: $supertype{$(params_sym...)}, Names, Types} <: $supertype{$(params_sym...)}
6875
main::Typ
6976
meta::NamedTuple{Names, Types}
7077
end
7178

72-
const $NoParams{Typ, Names, Types} = $MetaName{$(params...), Typ, Names, Types} where {$(params...)}
79+
const $NoParams{Typ, Names, Types} = $MetaName{$(params_sym...), Typ, Names, Types} where {$(params_sym...)}
7380

74-
function Base.getproperty(x::$MetaName{$(params...), Typ, Names, Types}, field::Symbol) where {$(params...), Typ, Names, Types}
81+
function Base.getproperty(x::$MetaName{$(params_sym...), Typ, Names, Types},
82+
field::Symbol) where {$(params...), Typ, Names, Types}
7583
field === $field && return getfield(x, :main)
7684
field === :main && return getfield(x, :main)
7785
Base.sym_in(field, Names) && return getfield(getfield(x, :meta), field)
7886
error("Field $field not part of Element")
7987
end
8088

81-
GeometryBasics.MetaType(T::Type{<: $supertype}) = $MetaName{T}
89+
function GeometryBasics.MetaType(XX::Type{<: $supertype{$(params_sym...)} where {$(params...)}})
90+
return $MetaName
91+
end
92+
8293
function GeometryBasics.MetaType(
83-
ST::Type{<: $supertype{$(params...)}},
94+
ST::Type{<: $supertype{$(params_sym...)}},
8495
::Type{NamedTuple{Names, Types}}) where {$(params...), Names, Types}
85-
return $MetaName{$(params...), ST, Names, Types}
96+
return $MetaName{$(params_sym...), ST, Names, Types}
8697
end
8798

88-
8999
GeometryBasics.MetaFree(::Type{<: $MetaName{Typ}}) where Typ = Typ
90100
GeometryBasics.MetaFree(::Type{<: $MetaName}) = $name
91101
GeometryBasics.metafree(x::$MetaName) = getfield(x, :main)
92-
GeometryBasics.metafree(x::AbstractVector{<: $MetaName}) = getcolumns(x, $field)[1]
102+
GeometryBasics.metafree(x::AbstractVector{<: $MetaName}) = getproperty(x, $field)
93103
GeometryBasics.meta(x::$MetaName) = getfield(x, :meta)
94-
GeometryBasics.meta(x::AbstractVector{<: $MetaName}) = getcolumns(x, :meta)[1]
104+
GeometryBasics.meta(x::AbstractVector{<: $MetaName}) = getproperty(x, :meta)
95105

96-
function GeometryBasics.meta(main::$supertype; meta...)
106+
function GeometryBasics.meta(main::$supertype{$(params_sym...)}; meta...) where {$(params...)}
97107
isempty(meta) && return elements # no meta to add!
98108
return $MetaName(main; meta...)
99109
end
100110

101-
function GeometryBasics.attributes(hasmeta::$MetaName)
102-
return Dict{Symbol, Any}((name => getproperty(hasmeta, name) for name in propertynames(hasmeta)))
103-
end
104-
105-
function GeometryBasics.meta(elements::AbstractVector{T}; meta...) where T <: $supertype
111+
function GeometryBasics.meta(elements::AbstractVector{XX}; meta...) where XX <: $supertype{$(params_sym...)} where {$(params...)}
106112
isempty(meta) && return elements # no meta to add!
107113
n = length(elements)
108114
for (k, v) in meta
@@ -118,7 +124,11 @@ macro meta_type(name, mainfield, supertype, params...)
118124
# get the first element to get the per element named tuple type
119125
ElementNT = typeof(map(first, nt))
120126

121-
return StructArray{MetaType(T, ElementNT)}(($(mainfield) = elements, nt...))
127+
return StructArray{MetaType(XX, ElementNT)}(($(mainfield) = elements, nt...))
128+
end
129+
130+
function GeometryBasics.attributes(hasmeta::$MetaName)
131+
return Dict{Symbol, Any}((name => getproperty(hasmeta, name) for name in propertynames(hasmeta)))
122132
end
123133

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

135-
function Base.propertynames(::$MetaName{$(params...), Typ, Names, Types}) where {$(params...), Typ, Names, Types}
145+
function Base.propertynames(::$MetaName{$(params_sym...), Typ, Names, Types}) where {$(params...), Typ, Names, Types}
136146
return ($field, Names...)
137147
end
138148

139-
function StructArrays.staticschema(::Type{$MetaName{$(params...), Typ, Names, Types}}) where {$(params...), Typ, Names, Types}
149+
function StructArrays.staticschema(::Type{$MetaName{$(params_sym...), Typ, Names, Types}}) where {$(params...), Typ, Names, Types}
140150
NamedTuple{($field, Names...), Base.tuple_type_cons(Typ, Types)}
141151
end
142152

143153
function StructArrays.createinstance(
144-
::Type{$MetaName{$(params...), Typ, Names, Types}},
154+
::Type{$MetaName{$(params_sym...), Typ, Names, Types}},
145155
metafree, args...
146156
) where {$(params...), Typ, Names, Types}
147157
$MetaName(metafree, NamedTuple{Names, Types}(args))
148158
end
149-
150-
151159
end
152160
return esc(expr)
153161
end
@@ -163,6 +171,14 @@ Base.getindex(x::SimplexFaceMeta, idx::Int) = getindex(metafree(x), idx)
163171

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

166-
@meta_type(MultiPoint, points, AbstractVector, P)
174+
@meta_type(MultiPoint, points, AbstractVector, P <: AbstractPoint)
167175
Base.getindex(x::MultiPointMeta, idx::Int) = getindex(metafree(x), idx)
168176
Base.size(x::MultiPointMeta) = size(metafree(x))
177+
178+
@meta_type(MultiLineString, linestrings, AbstractVector, P <: LineString)
179+
Base.getindex(x::MultiLineStringMeta, idx::Int) = getindex(metafree(x), idx)
180+
Base.size(x::MultiLineStringMeta) = size(metafree(x))
181+
182+
@meta_type(Mesh, mesh, AbstractMesh, Element <: Polytope)
183+
Base.getindex(x::MeshMeta, idx::Int) = getindex(metafree(x), idx)
184+
Base.size(x::MeshMeta) = size(metafree(x))

0 commit comments

Comments
 (0)