Skip to content
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

Update MeshIO for GeometryBasics refactor #97

Merged
merged 27 commits into from
Nov 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
3b27411
track materials
ffreyer Jul 14, 2024
5cbd965
add experimental mtl loading + mesh splitting util
ffreyer Jul 14, 2024
2df6da8
fix test failures
ffreyer Jul 14, 2024
fd36a88
normalize path syntax and allow broken paths
ffreyer Jul 14, 2024
ba3b348
make cat loadable
ffreyer Sep 4, 2024
b7739eb
update stl
ffreyer Sep 6, 2024
7658f18
update MeshIO
ffreyer Sep 6, 2024
78f443b
update for FaceView
ffreyer Sep 12, 2024
e1ec55d
Merge and refactor branch 'ff/materials' into ff/materials2
ffreyer Sep 15, 2024
6ed5a79
change origin offset -> offset
ffreyer Sep 15, 2024
64d624d
remove redundant split_mesh function
ffreyer Sep 16, 2024
9a834c4
add transmission filter & allow simultaneous alpha + transmission
ffreyer Oct 3, 2024
0b10677
Merge branch 'ff/materials2' of https://github.com/JuliaIO/MeshIO.jl …
ffreyer Oct 3, 2024
c72b507
Merge pull request #98 from JuliaIO/ff/materials2
ffreyer Oct 17, 2024
41f813d
skip material and return basic Mesh if no mtl files found
ffreyer Oct 21, 2024
c9041d3
1.6 compat
ffreyer Oct 21, 2024
fd21113
more 1.6 compat
ffreyer Oct 21, 2024
ef2801e
bump GeometryBasics version
ffreyer Nov 18, 2024
ebe5c38
fix renamed function
ffreyer Nov 18, 2024
79cbc3f
update CI to 1.6, 1 and avoid running twice
ffreyer Nov 18, 2024
39fa740
Merge branch 'master' into ff/GeometryBasics_refactor
ffreyer Nov 18, 2024
4f83216
add partial Sponza model for testing
ffreyer Nov 18, 2024
9c538d3
Merge branch 'ff/GeometryBasics_refactor' of https://github.com/Julia…
ffreyer Nov 18, 2024
47837c0
keep two sub-meshes in Sponza model
ffreyer Nov 18, 2024
e65166f
bump MeshIO version
ffreyer Nov 19, 2024
b65be9e
update readme
ffreyer Nov 19, 2024
8666d91
fix position-normal faces, cleanup material warning
ffreyer Nov 19, 2024
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
12 changes: 7 additions & 5 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
name: CI
on:
- push
- pull_request

push:
branches:
- master
tags: "*"
pull_request:
branches:
- master
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true

jobs:
test:
name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} - ${{ github.event_name }}
Expand All @@ -15,7 +18,6 @@ jobs:
fail-fast: false
matrix:
version:
- '1.3'
- '1.6'
- '1'
os:
Expand Down
4 changes: 2 additions & 2 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "MeshIO"
uuid = "7269a6da-0436-5bbc-96c2-40638cbb6118"
author = "Simon Danisch"
version = "0.4.13"
version = "0.5.0"

[deps]
ColorTypes = "3da002f7-5984-5a60-b8a6-cbb66c0b333f"
Expand All @@ -12,7 +12,7 @@ Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7"
[compat]
ColorTypes = "0.8, 0.9, 0.10, 0.11, 0.12"
FileIO = "1.2.4"
GeometryBasics = "0.4.1"
GeometryBasics = "0.5"
julia = "1.3"

[extras]
Expand Down
58 changes: 22 additions & 36 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
[![codecov.io](http://codecov.io/github/JuliaIO/MeshIO.jl/coverage.svg?branch=master)](http://codecov.io/github/JuliaIO/MeshIO.jl?branch=master)
[![Coverage Status](https://coveralls.io/repos/JuliaIO/MeshIO.jl/badge.svg?branch=master&service=github)](https://coveralls.io/github/JuliaIO/MeshIO.jl?branch=master)

This package supports loading 3D model file formats: `obj`, `stl`, `ply`, `off` and `2DM`.
This package supports loading 3D model file formats: `obj`, `stl`, `ply`, `off`, `msh` and `2DM`.
More 3D model formats will be supported in the future.

## Installation
Expand All @@ -22,49 +22,35 @@ This means loading a mesh is as simple as this:
using FileIO
mesh = load("path/to/mesh.obj")
```
The result will usually be a [GeometryBasics](https://github.com/JuliaGeometry/GeometryBasics.jl) `Mesh`.
The exception are `obj` files with non-vertex data such as material data or "group" tags, which return a `MetaMesh`.

Displaying a mesh can be achieved with [Makie](https://github.com/JuliaPlots/Makie.jl).

Functions for mesh manipulation can be found in [JuliaGeometry](https://github.com/JuliaGeometry)

## Additional Information

MeshIO now has the HomogenousMesh type. Name is still not settled, but it's supposed to be a dense mesh with all attributes either having the length of one (constant over the whole mesh) or the same length (per vertex).
This meshtype holds a large variability for all the different attribute mixtures that I've encountered while trying to visualize things over at GLVisualize. This is the best type I've found so far to encode this large variability, without an explosion of functions.

The focus is on conversion between different mesh types and creation of different mesh types.
This has led to some odd seeming design choices.
First, you can get an attribute via `decompose(::Type{AttributeType}, ::Mesh)`.
This will try to get this attribute, and if it has the wrong type try to convert it, or if it is not available try to create it.
So `decompose(Point3{Float32}, mesh)` on a mesh with vertices of type `Point3{Float64}` will return a vector of type `Point3{Float32}`.
Similarly, if you call `decompose(Normal{3, Float32}, mesh)` but the mesh doesn't have normals, it will call the function `normals(mesh.vertices, mesh.faces, Normal{3, Float32}`, which will create the normals for the mesh.
As most attributes are independent, this enables us to easily create all kinds of conversions.
Also, I can define `decompose` for arbitrary geometric types.
`decompose{T}(Point3{T}, r::Rectangle)` can actually return the needed vertices for a rectangle.
This together with `convert` enables us to create mesh primitives like this:
```Julia
MeshType(Cube(...))
MeshType(Sphere(...))
MeshType(Volume, 0.4f0) #0.4f0 => isovalue
```
### Usage

Similarly, I can pass a meshtype to an IO function, which then parses only the attributes that I really need.
So passing `Mesh{Point3{Float32}, Face3{UInt32}}` to the obj importer will skip normals, uv coordinates etc, and automatically converts the given attributes to the right number type.
The GeometryBasics `Mesh` supports vertex attributes with different lengths which get addressed by different faces (as of 0.5).
As such MeshIO makes no effort to convert vertex attributes to a common length, indexed by one set of faces.
If you need a single set of faces, e.g. for rendering, you can use `new_mesh = GeometryBasics.expand_faceviews(mesh)` to generate a fitting mesh.

To put this one level further, the `Face` type has the index offset relative to Julia's indexing as a parameter (e.g. `Face3{T, 0}` is 1 indexed). Also, you can index into an array with this face type, and it will convert the indexes correctly while accessing the array. So something like this always works, independent of the underlying index offset:
```Julia
v1, v2, v3 = vertices[face]
The GeometryBasics `Mesh` allows for different element types for coordinates, normals, faces, etc.
These can set when loading a mesh using keyword arguments:
```julia
load(filename; pointtype = Point3f, uvtype = Vec2f, facetype = GLTriangleFace, normaltype = Vec3f)
```
Also, the importer is sensitive to this, so if you always want to work with 0-indexed faces (like it makes sense for opengl based visualizations), you can parse the mesh already as an 0-indexed mesh, by just defining the mesh format to use `Face3{T, -1}`. (only the OBJ importer yet)
Note that not every file format supports normals and uvs (texture coordinates) and thus some loaders don't accept `uvtype` and/or `normaltype`.

Small example to demonstrate the advantage for IO:
```Julia
#Export takes any mesh
function write{M <: Mesh}(msh::M, fn::File{:ply_binary})
# even if the native mesh format doesn't have an array of dense points or faces, the correct ones will
# now be created, or converted:
vts = decompose(Point3{Float32}, msh) # I know ply_binary needs Point3{Float32}
fcs = decompose(Face3{Int32, -1}, msh) # And 0 indexed Int32 faces.
#write code...
end
```
The facetypes from GeometryBasics support 0 and 1-based indexing using `OffsetInteger`s.
For example `GLTriangleFace` is an alias for `NgonFace{3, OffsetInteger{-1, UInt32}}`, i.e. a face containing 3 indices offset from 1-based indexing by `-1`.
The raw data in a `GLTriangleFace` is 0-based so that it can be uploaded directly in a Graphics API.
In Julia code it gets converted back to a 1-based Int, so that it can be used as is.

### Extending MeshIO

To implement a new file format you need to add the appropriate `load()` and `save()` methods.
You also need to register the file format with [FileIO](https://juliaio.github.io/FileIO.jl/stable/registering/)
For saving it may be useful to know that you can convert vertex data to specific types using the [decompose interface](https://juliageometry.github.io/GeometryBasics.jl/stable/decomposition/).
2 changes: 0 additions & 2 deletions src/MeshIO.jl
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ using FileIO: FileIO, @format_str, Stream, File, stream, skipmagic

import Base.show

include("util.jl")

include("io/off.jl")
include("io/ply.jl")
include("io/stl.jl")
Expand Down
12 changes: 5 additions & 7 deletions src/io/gts.jl
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,13 @@
end
end

function load( st::Stream{format"GTS"}, MeshType=GLNormalMesh )
function load( st::Stream{format"GTS"}; facetype=GLTriangleFace, pointtype=Point)

Check warning on line 15 in src/io/gts.jl

View check run for this annotation

Codecov / codecov/patch

src/io/gts.jl#L15

Added line #L15 was not covered by tests
io = stream(st)
head = readline( io )
FT = facetype(MeshType)
VT = vertextype(MeshType)

nVertices, nEdges, nFacets = parseGtsLine( head, Tuple{Int,Int,Int} )
iV = iE = iF = 1
vertices = Vector{VT}(undef, nVertices)
vertices = Vector{pointtype}(undef, nVertices)

Check warning on line 21 in src/io/gts.jl

View check run for this annotation

Codecov / codecov/patch

src/io/gts.jl#L21

Added line #L21 was not covered by tests
edges = Vector{Vector{Int}}(undef, nEdges)
facets = Vector{Vector{Int}}(undef, nFacets)
for full_line::String in eachline(io)
Expand All @@ -30,7 +28,7 @@

if !startswith(line, "#") && !isempty(line) && !all(iscntrl, line) #ignore comments
if iV <= nVertices
vertices[iV] = parseGtsLine( line, VT )
vertices[iV] = parseGtsLine( line, pointtype )

Check warning on line 31 in src/io/gts.jl

View check run for this annotation

Codecov / codecov/patch

src/io/gts.jl#L31

Added line #L31 was not covered by tests
iV += 1
elseif iV > nVertices && iE <= nEdges
edges[iE] = parseGtsLine( line, Array{Int} )
Expand All @@ -41,8 +39,8 @@
end # if
end # if
end # for
faces = [ FT( union( edges[facets[i][1]], edges[facets[i][2]], edges[facets[i][3]] ) ) for i in 1:length(facets) ] # orientation not guaranteed
return MeshType( vertices, faces )
faces = [ facetype( union( edges[facets[i][1]], edges[facets[i][2]], edges[facets[i][3]] ) ) for i in 1:length(facets) ] # orientation not guaranteed
return Mesh( vertices, faces )

Check warning on line 43 in src/io/gts.jl

View check run for this annotation

Codecov / codecov/patch

src/io/gts.jl#L42-L43

Added lines #L42 - L43 were not covered by tests
end

function save( st::Stream{format"GTS"}, mesh::AbstractMesh )
Expand Down
12 changes: 6 additions & 6 deletions src/io/ifs.jl
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
function load(fs::Stream{format"IFS"}, MeshType = GLNormalMesh)
function load(fs::Stream{format"IFS"}; facetype=GLTriangleFace, pointtype=Point3f)

Check warning on line 1 in src/io/ifs.jl

View check run for this annotation

Codecov / codecov/patch

src/io/ifs.jl#L1

Added line #L1 was not covered by tests
io = stream(fs)
function str()
n = read(io, UInt32)
Expand All @@ -11,15 +11,15 @@
end
nverts = read(io, UInt32)
verts_float = read(io, Float32, nverts * 3)
verts = reinterpret(Point3f0, verts_float)
verts = reinterpret(pointtype, verts_float)

Check warning on line 14 in src/io/ifs.jl

View check run for this annotation

Codecov / codecov/patch

src/io/ifs.jl#L14

Added line #L14 was not covered by tests
tris = str()
if tris != "TRIANGLES\0"
error("$(filename(fs)) does not seem to be of format IFS")
end
nfaces = read(io, UInt32)
faces_int = read(io, UInt32, nfaces * 3)
faces = reinterpret(GLTriangle, faces_int)
MeshType(vertices = verts, faces = faces)
faces = reinterpret(facetype, faces_int)
return GeometryBasics.Mesh(verts, faces)

Check warning on line 22 in src/io/ifs.jl

View check run for this annotation

Codecov / codecov/patch

src/io/ifs.jl#L21-L22

Added lines #L21 - L22 were not covered by tests
end

function save(fs::Stream{format"IFS"}, msh::AbstractMesh; meshname = "mesh")
Expand All @@ -29,8 +29,8 @@
write(io, UInt32(length(s0)))
write(io, s0)
end
vts = decompose(Point3f0, msh)
fcs = decompose(GLTriangle, msh)
vts = decompose(Point3f, msh)
fcs = decompose(GLTriangleFace, msh)

Check warning on line 33 in src/io/ifs.jl

View check run for this annotation

Codecov / codecov/patch

src/io/ifs.jl#L32-L33

Added lines #L32 - L33 were not covered by tests

# write the header
write0str("IFS")
Expand Down
Loading
Loading