|
1 | 1 | # Decomposition
|
2 | 2 |
|
3 | 3 |
|
4 |
| -## Displaying primitives |
| 4 | +## GeometryBasic Mesh interface |
5 | 5 |
|
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. |
7 | 8 | This can be done for any arbitrary primitive, by overloading the following interface:
|
8 | 9 |
|
9 | 10 | ```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)) |
25 | 16 | end
|
26 | 17 |
|
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))) |
36 | 23 | end
|
37 | 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`. |
38 | 28 |
|
39 | 29 | With these methods defined, this constructor will magically work:
|
40 | 30 |
|
41 | 31 | ```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 | +``` |
45 | 41 |
|
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) |
48 | 74 | ```
|
49 | 75 |
|
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. |
0 commit comments