Decomposition

GeometryBasics Mesh interface

GeometryBasics 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:


function GeometryBasics.coordinates(rect::Rect2, 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.faces(rect::Rect2, 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:

rect = Rect2(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:

m = GeometryBasics.mesh(Tesselation(rect, (50, 50)))
length(coordinates(m)) == 50^2

As you can see, coordinates and faces are also defined on a mesh

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:

decompose(Point2f, rect) == convert(Vector{Point2f}, collect(coordinates(rect)))

The element conversion is handled by simplex_convert, which also handles convert between different face types:

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:

decompose(Point, rect) isa Vector{Point{2, Float64}}

You can also pass the element type to mesh:

m = GeometryBasics.mesh(rect, pointtype=Point2f, 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:

m = GeometryBasics.mesh(rect, uv=Vec2f, normaltype=Vec3f)

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:

decompose(UV(Vec2f), rect)
decompose(Normal(Vec3f), 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/3f, Vec2/3f), and the requested attributes.