Skip to content

improve thay docs #57

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
May 30, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.
4 changes: 4 additions & 0 deletions src/basic_types.jl
Original file line number Diff line number Diff line change
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
4 changes: 3 additions & 1 deletion src/geometry_primitives.jl
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ end

##
# conversion & decompose

convert_simplex(::Type{T}, x::T) where T = (x,)
"""
convert_simplex(::Type{Face{3}}, f::Face{N})

Expand Down Expand Up @@ -42,7 +42,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
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