Skip to content

Commit d485293

Browse files
authored
Geometry Basics refactor (#219)
* update MetaMesh * add MultiFace type * update Mesh & MultiFace types * update Mesh & MetaMesh utils * add MultiFace remapping code * prototype MultiFace Rect -> Mesh pipeline * generalize MultiFace getindex to Integer * remove add_meta, pop_meta * update merge for MultiFace and views * add AbstractVertexFace and AbstractMultiFace * split up mesh() for better usability * minor fixes * add views to Mesh constructors * add `mesh()` method for converting facetype of mesh * switch back to "normals" * fix missing normals rename * add back point/normal/uv-type kwargs * consider face views in face decompose * add mesh(mesh; attribs...) & improve dispatch safety * fix normals(), cleanup tests * add mesh constructor tests + fixes * deprecate normals for normal as vertex attribute name * make NormalFace and NormalUVFace types again * cleanup tests & normals vs normal * let ci run * remove views aware face decomposition * fix rect index order * fix MultiFace remapping with OffsetIntegers * add moreMultiFace utils * restore decompose(FaceType, faces, views) * allow MultiFace -> LineFace conversion * define Rect3 faces counterclockwise * add more convenience types * filter nothing attributes * drop Base.Pairs for 1.6 compat * Add depwarn in hasproperty too * improve MultiFace show * update Pyramid * update Cylinder * add MultiFace decompose * fix Cylinder tests * make OffsetInteger printing copyable * update tests for Rect3 and normal gen * fix remaining tests * fix incorrect vertex index counter * simplify merge of mixed Face types * test merge(meshes) * prototype swapping from MultiFace to FaceViews * treat views in vertex index remapping + some fixes * fix face type change * clean up AbstractVertexFace * extend FaceView interface * update Cylinder, Pyramids * declutter NgonFace prints * update tests * cleanup some test_broken * switch to Dict * fix tests * remove shorthands * export vertex_attributes and FaceView * add center point to Circle to avoid shallow triangles * make untesselated rect vertices counter-clockwise * fix tests * fix Cylinder face windig direction * add `face_normals()` helepr function * cleanup face_normals and normals a bit more * update tests for Cylinder * rename connectivity -> faces * update docs (meshes, primitives, decompose, Point, Vec, Mat) * add/update docstrings * add quick test for face_normals() * fix TetrahedronFace conversions * restore volume functions * fix tests * add some Polygon tests * test Pyramids * test TetrahedronFace decomposition * test and improve Mesh validation * test Mesh inteface functions * test decompose with views * test and fix matrix det, inv, transpose, mat*vec * fix tests * cleanup normal gen and export face_normals * add util for splitting meshes by views * fix missing dot in range .+ idx * improve performance of merge * fix tests * improve GeoInterface conversion performance * switch back to NamedTuple for performance * cleanup merge(meshes) * test clear_faceviews with mesh.views * fix missing import in docs examples * add convert for arrays of meshes * add function for removing duplicate faces * update normal gen tests + fixes - fix normal gen for varying face types - fix normalization of face_normals * remove time piracy * bring back shorthand types * restrict type in meshes to error earlier * update precompiles * autoconvert point dim in merge(meshes) instead of restricitng type * add compat entry * ignore unused PrecompileTools in 1.6, 1.7 * bring back old precompiles * add convert target to orthogonal_vector * revert triangulation changes of Circle * revert to using StaticArrays * avoid some invalidations (and fix get) * fix stale instances due to AbstractVector Apparently T[] is an AbstractVector here? * use string interpolation in error to avoid invalidation from string * * fix test * export clear_faceviews & update FaceView docstring * update type docstring * add notes about views * update Mesh docs * add FaceView ref to Mesh docstring * add brief section about extending decompose * derive Point eltype when dimension is given * reuse docstring * fix docs? * fix docs??
1 parent 94aefd5 commit d485293

31 files changed

+2273
-1343
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ on:
77
pull_request:
88
branches:
99
- master
10+
- sd/simple-mesh
1011
jobs:
1112
test:
1213
name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} - ${{ github.event_name }}

Project.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ Extents = "411431e0-e8b7-467b-b5e0-f676ba4f2910"
99
GeoInterface = "cf35fbd7-0cd7-5166-be24-54bfbe79505f"
1010
IterTools = "c8e1da08-722c-5040-9ed9-7db0dc04731e"
1111
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
12+
PrecompileTools = "aea7be01-6a6a-4083-8856-8a6e6704d82a"
1213
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
14+
StaticArrays = "90137ffa-7385-5640-81b9-e52037218182"
1315

1416
[compat]
1517
Aqua = "0.8"
@@ -20,7 +22,9 @@ GeoJSON = "0.7, 0.8"
2022
IterTools = "1.3.0"
2123
LinearAlgebra = "<0.0.1,1"
2224
OffsetArrays = "1"
25+
PrecompileTools = "1.0"
2326
Random = "<0.0.1,1"
27+
StaticArrays = "0.6, 1"
2428
Test = "<0.0.1,1"
2529
julia = "1.6"
2630

docs/make.jl

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,10 @@ makedocs(format=Documenter.HTML(prettyurls=get(ENV, "CI", "false") == "true"),
1010
pages=[
1111
"index.md",
1212
"primitives.md",
13-
"rectangles.md",
1413
"polygons.md",
1514
"meshes.md",
1615
"decomposition.md",
17-
"metadata.md",
16+
"static_array_types.md",
1817
"api.md"
1918
],
2019
modules=[GeometryBasics])

docs/src/decomposition.md

Lines changed: 24 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -1,89 +1,26 @@
11
# Decomposition
22

3-
4-
## GeometryBasics Mesh interface
5-
6-
GeometryBasics defines an interface to decompose abstract geometries into
7-
points and triangle meshes.
8-
This can be done for any arbitrary primitive, by overloading the following interface:
9-
10-
```julia
11-
12-
function GeometryBasics.coordinates(rect::Rect2, 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))
16-
end
17-
18-
function GeometryBasics.faces(rect::Rect2, 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)))
23-
end
24-
```
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`.
28-
29-
With these methods defined, this constructor will magically work:
30-
31-
```julia
32-
rect = Rect2(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-
```
41-
42-
As you can see, `coordinates` and `faces` are 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(Point2f, rect) == convert(Vector{Point2f}, 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=Point2f, 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=Vec2f, normaltype=Vec3f)
74-
```
75-
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(Vec2f), rect)
83-
decompose(Normal(Vec3f), 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/3f, Vec2/3f), and the requested attributes.
3+
## decompose functions
4+
5+
The `decompose` functions allow you to grab certain data from an `AbstractGeometry` like a mesh or primitive and convert it to a requested type, if possible.
6+
They can also be used to convert an array of e.g. faces into a different face type directly.
7+
The default decomposition implemented by GeoemtryBasics are:
8+
- `decompose(::Type{<: Point}, source)` which collects data from `source` using `coordinates(source)` and converts it to the given point type.
9+
- `decompose_normals([::Type{<: Vec},] source) = decompose([::Type{Normals{<: Vec}}},] source)` which collects data with `normals(source)` and converts it to the given Vec type.
10+
- `decompose_uv([::Type{<: Vec},] source) = decompose([::Type{UV{<: Vec}}},] source)` which collects data with `texturecoordinates(source)` and converts it to the given Vec type. This function also exists with `UVW` texture coordinates.
11+
- `decompose(::Type{<: AbstractFace}, source)` which collects data with `faces(source)` and converts it to the given face type.
12+
13+
### Extending decompose
14+
15+
For `decompose` to work there needs to be a conversion from some element type to some target type.
16+
GeometryBasics relies on `GeometryBasics.convert_simplex(TargetType, value)` for this.
17+
If you want to add new types to decompose, e.g. a new face type, you will need to add a method to that function.
18+
19+
## Primitive decomposition
20+
21+
GeometryBasics defines an interface to decompose geometry primitives into vertex attributes and faces.
22+
The interface includes four functions:
23+
- `coordinates(primitive[, nvertices])` which produces the positions associated with the primitive
24+
- `faces(primitive[, nvertices])` which produces the faces which connect the vertex positions to a mesh
25+
- `normals(primitive[, nvertices])` which optionally provide normal vectors of the primitive
26+
- `texturecoordinates(primitive[, nvertices])` which optional provide texture coordinates (uv/uvw) of the primitive

docs/src/implementation.md

Lines changed: 0 additions & 5 deletions
This file was deleted.

docs/src/index.md

Lines changed: 6 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -22,56 +22,20 @@ p2 = Point(1, 3);
2222
p3 = Point(4, 4);
2323
```
2424

25-
Geometries can carry metadata:
26-
27-
```@repl quickstart
28-
poi = meta(p1, city="Abuja", rainfall=1221.2)
29-
```
30-
31-
Metadata is stored in a NamedTuple and can be retrieved as such:
32-
33-
```@repl quickstart
34-
meta(poi)
35-
```
36-
37-
Specific metadata attributes can be directly retrieved:
38-
39-
```@repl quickstart
40-
poi.rainfall
41-
```
42-
43-
To remove the metadata and keep only the geometry, use `metafree`:
44-
45-
```@repl quickstart
46-
metafree(poi)
47-
```
48-
49-
Geometries have predefined metatypes:
50-
51-
```@repl quickstart
52-
multipoi = MultiPointMeta([p1], city="Abuja", rainfall=1221.2)
53-
```
54-
55-
Connect the points with lines:
25+
Connect pairs of points as line segments:
5626

5727
```@repl quickstart
5828
l1 = Line(p1, p2)
5929
l2 = Line(p2, p3);
6030
```
6131

62-
Connect the lines in a linestring:
63-
64-
```@repl quickstart
65-
LineString([l1, l2])
66-
```
67-
68-
Linestrings can also be constructed directly from points:
32+
Or connect multiple points as a linestring:
6933

7034
```@repl quickstart
7135
LineString([p1, p2, p3])
7236
```
7337

74-
The same goes for polygons:
38+
You can also create polygons from points:
7539

7640
```@repl quickstart
7741
Polygon(Point{2, Int}[(3, 1), (4, 4), (2, 4), (1, 2), (3, 1)])
@@ -89,57 +53,20 @@ Decompose the rectangle into two triangular faces:
8953
rect_faces = decompose(TriangleFace{Int}, rect)
9054
```
9155

92-
Decompose the rectangle into four vertices:
56+
Decompose the rectangle into four positions:
9357

9458
```@repl quickstart
95-
rect_vertices = decompose(Point{2, Float64}, rect)
59+
rect_positions = decompose(Point{2, Float64}, rect)
9660
```
9761

9862
Combine the vertices and faces into a triangle mesh:
9963

10064
```@repl quickstart
101-
mesh = Mesh(rect_vertices, rect_faces)
65+
mesh = Mesh(rect_positions, rect_faces)
10266
```
10367

10468
Use `GeometryBasics.mesh` to get a mesh directly from a geometry:
10569

10670
```@repl quickstart
10771
mesh = GeometryBasics.mesh(rect)
10872
```
109-
110-
111-
## Aliases
112-
113-
GeometryBasics exports common aliases for Point, Vec, Mat and Rect:
114-
115-
### Vec
116-
117-
| |`T`(eltype) |`Float64` |`Float32` |`Int` |`UInt` |
118-
|--------|------------|----------|----------|----------|----------|
119-
|`N`(dim)|`Vec{N,T}` |`Vecd{N}` |`Vecf{N}` |`Veci{N}` |`Vecui{N}`|
120-
|`2` |`Vec2{T}` |`Vec2d` |`Vec2f` |`Vec2i` |`Vec2ui` |
121-
|`3` |`Vec3{T}` |`Vec3d` |`Vec3f` |`Vec3i` |`Vec3ui` |
122-
123-
### Point
124-
125-
| |`T`(eltype) |`Float64` |`Float32` |`Int` |`UInt` |
126-
|--------|------------|----------|----------|----------|----------|
127-
|`N`(dim)|`Point{N,T}`|`Pointd{N}`|`Pointf{N}`|`Pointi{N}`|`Pointui{N}`|
128-
|`2` |`Point2{T}` |`Point2d` |`Point2f` |`Point2i` |`Point2ui`|
129-
|`3` |`Point3{T}` |`Point3d` |`Point3f` |`Point3i` |`Point3ui`|
130-
131-
### Mat
132-
133-
| |`T`(eltype) |`Float64` |`Float32` |`Int` |`UInt` |
134-
|--------|------------|----------|----------|----------|----------|
135-
|`N`(dim)|`Mat{N,T}` |`Matd{N}` |`Matf{N}` |`Mati{N}` |`Matui{N}`|
136-
|`2` |`Mat2{T}` |`Mat2d` |`Mat2f` |`Mat2i` |`Mat2ui` |
137-
|`3` |`Mat3{T}` |`Mat3d` |`Mat3f` |`Mat3i` |`Mat3ui` |
138-
139-
### Rect
140-
141-
| |`T`(eltype) |`Float64` |`Float32` |`Int` |`UInt` |
142-
|--------|------------|----------|----------|----------|----------|
143-
|`N`(dim)|`Rect{N,T}` |`Rectd{N}`|`Rectf{N}`|`Recti{N}`|`Rectui{N}`|
144-
|`2` |`Rect2{T}` |`Rect2d` |`Rect2f` |`Rect2i` |`Rect2ui` |
145-
|`3` |`Rect3{T}` |`Rect3d` |`Rect3f` |`Rect3i` |`Rect3ui` |

docs/src/meshes.md

Lines changed: 68 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,80 @@
11
# Meshes
22

3-
## Types
3+
GeometryBasics defines two mesh types to work with - `Mesh` and `MetaMesh`
44

5-
* [`AbstractMesh`](@ref)
6-
* [`Mesh`](@ref)
5+
## Mesh
6+
7+
```@docs; canonical=false
8+
Mesh
9+
```
10+
11+
You can get data from a mesh using a few interface functions:
12+
- `vertex_attributes(mesh) = mesh.vertex_attributes`
13+
- `coordinates(mesh) = mesh.vertex_attributes[:position]`
14+
- `normals(mesh) = mesh.vertex_attributes[:normal]`
15+
- `texturecoordinates(mesh) = mesh.vertex_attributes[:uv]`
16+
- `faces(mesh) = mesh.faces`
17+
18+
You can also grab the contents of `mesh.vertex_attributes` as if they were fields of the `Mesh`, e.g. `mesh.position` works.
19+
20+
### FaceView
21+
22+
As mentioned above, a vertex attribute can be a `FaceView`.
23+
A `FaceView` is simply defined as a vector of data and a vector of faces:
24+
25+
```julia
26+
struct FaceView{T, AVT <: AbstractVector{T}, FVT <: AbstractVector{<: AbstractFace}}
27+
data::AVT
28+
faces::FVT
29+
end
30+
```
31+
32+
Its purpose is to allow you to add data that needs to be defined per vertex but does not match the vertex structure used by `mesh.faces`.
33+
34+
As a minimal example consider a mesh that is just one triangle, i.e. 3 position and one triangle face `TriangleFace(1,2,3)`.
35+
Let's say we want to add a flat color to the triangle.
36+
In this case we only have one color, but our face refers to 3 different vertices (3 different positions).
37+
To avoid duplicating the color data, we can instead define a new triangle face `TriangleFace(1)` and add the color attribute as a `FaceView([color], [TriangleFace(1)])`.
38+
If we ever need the mesh to be defined with just one common set of faces, i.e. no FaceView and appropriately duplicated vertex data, we can use `clear_faceviews(mesh)` to generate it.
39+
40+
On a larger scale this can be useful for memory and performance reason, e.g. when you do calculations with vertex attributes.
41+
It can also simplify some definitions, like for example `Rect3`.
42+
In that case we have 8 positions and 6 normals with FaceViews, or 24 without (assuming per-face normals).
43+
44+
45+
## MetaMesh
46+
47+
A `MetaMesh` is given by
48+
49+
```julia
50+
struct MetaMesh{Dim, T, M <: AbstractMesh{Dim, T}} <: AbstractMesh{Dim, T}
51+
mesh::M
52+
meta::Dict{Symbol, Any}
53+
end
54+
```
55+
56+
where `meta` may contain any data you want to include with a mesh.
57+
For example, you could include group names or material data corresponding to `mesh.views`.
758

859
## How to create a mesh
960

10-
### Meshing.jl
61+
### GeometryBasics
1162

12-
### MeshIO.jl
63+
In GeometryBasics you mainly create meshes from primitives using a few constructors:
64+
- `triangle_mesh(primitive)` generates the most basic mesh (i.e. positions and faces)
65+
- `normal_mesh(primitive)` generates a mesh with normals (generated if the primitive doesn't implement `normal()`)
66+
- `uv_mesh(primitive)` generates a mesh with texture coordinates (generated if the primitive doesn't implement `texturecoordinates()`)
67+
- `uv_normal_mesh(primitive)` generates a mesh with normals and texture coordinates
1368

14-
The [`MeshIO.jl`](https://github.com/JuliaIO/MeshIO.jl) package provides load/save support for several file formats which store meshes.
69+
Each of these constructors also includes keyword arguments for setting types, i.e. `pointtype`, `facetype`, `normaltype` and `uvtype` as appropriate.
70+
Of course you can also construct a mesh directly from data, either with there various `Mesh()` or `GeometryBasics.mesh()` constructors.
71+
The latter also include a `pointtype` and `facetype` conversion.
1572

16-
## How to access data
73+
Finally there is also a `merge(::Vector{Mesh})` function which combines multiple meshes into a single one.
74+
Note that this doesn't remove any data (e.g. hidden or duplicate vertices), and may remove `FaceView`s if they are incompatible between meshes.
1775

18-
The following functions can be called on an [`AbstractMesh`](@ref) to access its underlying data.
76+
### Meshing.jl
1977

20-
* [`faces`](@ref)
21-
* [`coordinates`](@ref)
22-
* `texturecoordinates`
23-
* [`normals`](@ref)
78+
### MeshIO.jl
2479

80+
The [`MeshIO.jl`](https://github.com/JuliaIO/MeshIO.jl) package provides load/save support for several file formats which store meshes.

0 commit comments

Comments
 (0)