diff --git a/docs/src/meshes.md b/docs/src/meshes.md index d538aa2b..9b5bd20e 100644 --- a/docs/src/meshes.md +++ b/docs/src/meshes.md @@ -35,6 +35,11 @@ On a larger scale this can be useful for memory and performance reason, e.g. whe It can also simplify some definitions, like for example `Rect3`. In that case we have 8 positions and 6 normals with FaceViews, or 24 without (assuming per-face normals). +For the relatively common case of per-face data, you can use the `per_face` convenience function. + +```@docs +per_face +``` ## MetaMesh diff --git a/src/GeometryBasics.jl b/src/GeometryBasics.jl index 89ccd370..7c5ddea1 100644 --- a/src/GeometryBasics.jl +++ b/src/GeometryBasics.jl @@ -45,6 +45,7 @@ export expand_faceviews, split_mesh, remove_duplicates export face_normals export Tessellation, Normal, UV, UVW export AbstractMesh, Mesh, MetaMesh, FaceView +export per_face # all the different predefined mesh types diff --git a/src/meshes.jl b/src/meshes.jl index cb38a721..665969db 100644 --- a/src/meshes.jl +++ b/src/meshes.jl @@ -585,3 +585,30 @@ function Base.show(io::IO, mesh::MetaMesh{N, T}) where {N, T} FT = eltype(faces(mesh)) println(io, "MetaMesh{$N, $T, $(FT)}($(join(keys(meta(mesh)), ", ")))") end + +""" + per_face(data, faces) + per_face(data, mesh) + +Generates a `FaceView` that applies the given data per face, rather than per +vertex. The result can then be used to create a (new) mesh: +``` +mesh(..., attribute_name = per_face(data, faces)) +mesh(old_mesh, attribute_name = per_face(data, old_mesh)) +``` +""" +per_face(data, geom::AbstractGeometry) = per_face(data, faces(geom)) +function per_face(data, faces::AbstractVector{<: AbstractFace}) + if length(data) != length(faces) + error("Length of per-face data $(length(data)) must match the number of faces $(length(faces))") + end + + return FaceView(data, [typeof(f)(i) for (i, f) in enumerate(faces)]) +end +function per_face(data, faces::AbstractVector{FT}) where {N, FT <: AbstractFace{N}} + if length(data) != length(faces) + error("Length of per-face data $(length(data)) must match the number of faces $(length(faces))") + end + + return FaceView(data, FT.(eachindex(faces))) +end \ No newline at end of file diff --git a/test/meshes.jl b/test/meshes.jl index 92d396ad..7103bbe1 100644 --- a/test/meshes.jl +++ b/test/meshes.jl @@ -20,6 +20,18 @@ end p = Point2f[(0, 1), (1, 2), (3, 4), (4, 5)] m = Mesh(p, f) @test collect(m) == [Triangle(p[1], p[2], p[3]), GeometryBasics.Quadrilateral(p[1], p[2], p[3], p[4])] + + facedata = FaceView([:red, :blue], [TriangleFace(1), QuadFace(2)]) + m2 = GeometryBasics.mesh(m, color = facedata) + m3 = expand_faceviews(m2) + @test faces(m3) == GLTriangleFace[(1,2,3), (4,5,6), (4,6,7)] + @test coordinates(m3) == Point2f[[0.0, 1.0], [1.0, 2.0], [3.0, 4.0], [0.0, 1.0], [1.0, 2.0], [3.0, 4.0], [4.0, 5.0]] + @test m3.color == [:red, :red, :red, :blue, :blue, :blue, :blue] + + @test per_face([:red, :blue], f) == facedata + @test per_face([:red, :blue], m) == facedata + @test per_face([:red, :blue, :blue], m2) == FaceView([:red, :blue, :blue], GLTriangleFace.(1:3)) + @test per_face([:red, :blue, :blue], m3) == FaceView([:red, :blue, :blue], GLTriangleFace.(1:3)) end @testset "Ambiguous NgonFace constructors" begin @@ -49,6 +61,8 @@ end @test normals(m) == GeometryBasics.FaceView([Vec3f(0,0,1)], [QuadFace(1)]) @test isempty(m.views) + @test per_face([Vec3f(0,0,1)], m) == m.normal + @test faces(m2) == [QuadFace(1,2,3,4)] @test coordinates(m2) == coordinates(m) @test normals(m2) != normals(m)