Line data Source code
1 : using FileIO
2 : import GeometryBasics
3 : using GeometryBasics: coordinates, meta,
4 : GLTriangleFace, Point3, Vec3, Vec2, Point
5 :
6 : # NOTE: Look at Meshes.jl and GeoIO.jl for more readers
7 :
8 : """
9 : mesh = createmesh(mobj::GeometryBasics.Mesh, template::Mesh;
10 : [position = :x])
11 :
12 : Convert `GeometryBasics.Mesh` to `Mesh`.
13 : """
14 0 : function createmesh(mobj::GeometryBasics.Mesh,
15 : template::Mesh{V, F, H, E};
16 : position=Val(:x),
17 : normal=Val(:n), texcoord=Val(:u)) where {V, F, H, E}
18 0 : n = length(coordinates(mobj))
19 0 : m = length(GeometryBasics.faces(mobj))
20 :
21 0 : mesh = similar(template)
22 :
23 0 : sizehint!(mesh, n, m)
24 :
25 0 : addvertices!(mesh, n)
26 :
27 0 : maybesetvattr(f, name::Symbol) = maybeattr(f, Val(S))
28 :
29 0 : function maybesetvattr(f, ::Val{S}) where {S}
30 0 : hasvattr(mesh, S) || return nothing
31 0 : attr = vattr(mesh, Val(S))
32 0 : isenabled(attr) && f(attr)
33 0 : nothing
34 : end
35 :
36 0 : vx = vattr(mesh, position)
37 :
38 0 : for (j, c) in enumerate(coordinates(mobj))
39 0 : vx[VHnd(j)] = c
40 0 : end
41 :
42 0 : for f in GeometryBasics.faces(mobj)
43 0 : addface!(mesh, VHnd.(convert.(Int, f))...)
44 0 : end
45 :
46 0 : hasproperty(mobj, :normal) && maybesetvattr(normal) do vn
47 0 : if isa(GeometryBasics.normals(mobj), GeometryBasics.FaceView)
48 0 : @warn "Drop vertex normals from GeometryBasics.FaceView"
49 0 : return nothing
50 : end
51 :
52 0 : for (j, n) in enumerate(GeometryBasics.normals(mobj))
53 0 : vn[VHnd(j)] = n
54 0 : end
55 : end
56 :
57 0 : hasproperty(mobj, :uv) && maybesetvattr(texcoord) do vu
58 0 : for (j, u) in enumerate(GeometryBasics.texturecoordinates(mobj))
59 0 : vu[VHnd(j)] = u
60 0 : end
61 : end
62 :
63 0 : mesh
64 : end
65 :
66 : # TODO: MeshIO loaders don't recognize all options (OFF fails for
67 : # uvtyupe=...), but I have problems passing just options...
68 : # -- currently restriction to pointstype
69 :
70 : """
71 : mesh, mobj = read(:MeshIO, filename, template::Mesh
72 : [; position=:x, exceptions=false,
73 : pointstype=Point3])
74 :
75 : Load `mesh` from file, e.g., in OFF format using
76 : [`FileIO`](https://juliaio.github.io/FileIO.jl/stable/) and
77 : [`GeometryBasics`](https://juliageometry.github.io/GeometryBasics.jl/stable/)
78 :
79 : `mobj` is the mesh as defined by `GeometryBasics` and can be used for
80 : rendering. `GeometryBasics.coordinates(mobj)` and `GeometryBasics.faces(mobj)`
81 : provide `Vector`s of vertex positions and triangle indices, respectively.
82 :
83 : !!! warning "Precondition"
84 : This method requires a **triangle mesh**!
85 :
86 : !!! warning
87 : `MeshIO.load` takes keyword arguments `uvtype` and `normaltype` only
88 : for file formats, which support these. For other formats, it just
89 : fails. We stick to the default settings (`Float32`) and pass only
90 : the vertex position type.
91 :
92 : !!! note
93 : For some file formats (e.g., OBJ), `FileIO.load` returns a `MetaMesh`.
94 : We drop the meta data, which may include material properties etc.
95 : """
96 0 : function Base.read(::Val{:MeshIO},
97 : filename::AbstractString,
98 : template::Mesh{V, F, H, E};
99 : position=Val(:x),
100 : normal=Val(:n), texcoord=Val(:u),
101 : exceptions::Bool=false,
102 : pointstype=Point3{Float32},
103 : options...) where {V, F, H, E}
104 0 : try
105 : # NOTE: MeshIO labels option "pointtype" instead of "pointstype" !!
106 0 : mobj = GeometryBasics.Mesh(FileIO.load(abspath(filename);
107 : pointtype=pointstype)) :: GeometryBasics.Mesh
108 : # NOTE: Mesh(...) drops extras from MetaMesh (e.g., from OBJ)
109 :
110 0 : createmesh(mobj, template; position, normal, texcoord), mobj
111 : catch e
112 0 : exceptions && rethrow()
113 0 : showerror(stderr, e, Base.catch_backtrace())
114 0 : nothing
115 : end
116 : end
117 :
118 0 : _len(::Type{SVector{N, T}}) where {N, T} = N
119 0 : _len(::Type{NTuple{N, T}}) where {N, T} = N
120 0 : _len(::Type{T}) where {T<:Number} = 1
121 :
122 0 : toPoint(x::V, p::Point{N, T}) where {V, N, T} =
123 : toPoint(x, Val(_len(V)), p) :: Point{N, T}
124 :
125 0 : toPoint(x, ::Val{N}, p::Point{N, T}) where {N, T} = Point{N, T}(x...)
126 :
127 0 : toPoint(x, m::Val{M}, p::Point{N, T}) where {M, N, T} =
128 : toPoint(x, m, Val(M < N), p)
129 0 : toPoint(x, ::Val{M}, ::Val{true}, _::Point{N, T}) where {M, N, T} =
130 : Point{N, T}(x..., (zero(T) for _ in M+1:N)...)
131 0 : toPoint(x, ::Val{M}, ::Val{false}, _::Point{N, T}) where {M, N, T} =
132 0 : Point{N, T}(ntuple(i -> x[i], N)...)
133 :
134 0 : toPoints(vx::Attribute{V}, pointstype::Point{N, T}, n) where {V, N, T} =
135 : [ toPoint(vx[VHnd(j)], pointstype) for j in 1:n ]
136 :
137 : # TODO: make this type stable
138 :
139 0 : function meshobj(mesh::Mesh;
140 : position = Val(:x),
141 : uv = nothing, normals = nothing,
142 : pointstype::Type{Point{N, T}} = Point3{Float32},
143 : uvtype=Vec2{Float32},
144 : normaltype=Vec3{Float32}) where {N, T}
145 : # see, e.g., https://github.com/JuliaIO/MeshIO.jl/blob/master/src/io/obj.jl
146 0 : n = n_total(vattr(mesh))
147 0 : m = nf(mesh)
148 :
149 0 : @massert istrianglemesh(mesh)
150 :
151 0 : vx = vattr(mesh, position)
152 0 : points = toPoints(vx, zero(pointstype), n)
153 :
154 0 : t = triangletable(mesh)
155 0 : faces = [ GLTriangleFace(t[j]...) for j in 1:m ]
156 :
157 0 : point_attributes = Dict{Symbol, Any}()
158 :
159 0 : if uv !== nothing
160 0 : u = vattr(mesh, uv)
161 0 : point_attributes[:uv] = [ uvtype(u[VHnd(j)]...) for j in 1:n ]
162 : end
163 :
164 0 : if normals !== nothing
165 0 : point_attributes[:normals] =
166 : if normals == :auto
167 0 : GeometryBasics.normals(points, faces)
168 : else
169 0 : nrm = vattr(mesh, normals)
170 0 : [ normalize(normaltype(nrm[VHnd(j)]...)) for j in 1:n ]
171 : end
172 : end
173 :
174 : # GeometryBasics.Mesh(meta(points; point_attributes...), faces) # < v0.5.0
175 0 : GeometryBasics.Mesh(points, faces; point_attributes...) # >=v0.0.5
176 : end
177 :
178 : """
179 : mobj = meshobj(mesh[; position=:x, uv=nothing, normals=nothing,
180 : pointstype = Point3, uvtype=Vec2,
181 : normaltype=Vec3])
182 :
183 : Convert `mesh` to mesh as defined by
184 : [`GeometryBasics`](https://juliageometry.github.io/GeometryBasics.jl),
185 : which can be used for rendering, e.g., with
186 : [`Makie`](https://makie.juliaplots.org) (see `Makie.mesh`). Texture
187 : coordinates `uv` are passed as matrix columns or as a function that
188 : maps vertex positions. Normals are passed as a matrix. They are
189 : estimated if either `:auto` or an empty matrix is passed.
190 :
191 : The argument `normals` is used as follows:
192 :
193 : - `nothing`: don't include normals as attributes
194 : - `:auto`: have `GeometryBasics` compute normals
195 : - use provided *attribute* as normals.
196 :
197 : The dimension `ni` of the input `position` attribute may differ from the
198 : dimension `no` of the output `pointstype`:
199 :
200 : - `ni == no` copy (possibly convert types)
201 : - `ni < no` copy and fill remaining components of output with zeros
202 : - `ni > ni` copy only fist `no` dimensions of input
203 :
204 : !!! note
205 : `meshobj` calls [`triangletable`](@ref) and *not*
206 : [`compacttriangletable`](@ref), i.e., the returned object may represent
207 : a mesh including unused vertices.
208 :
209 : """ meshobj
|