Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
13 changes: 6 additions & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,12 @@ jobs:
with:
version: "1.11"
- uses: julia-actions/cache@v2
- run: |
DISPLAY=:0 xvfb-run -s '-screen 0 1024x768x24' julia --project=docs -e '
using Pkg
Pkg.develop(PackageSpec(path=pwd()))
pkg"add MakieCore Makie GLMakie"
Pkg.instantiate()'
- run: DISPLAY=:0 xvfb-run -s '-screen 0 1024x768x24' julia --project=docs docs/make.jl
- name: Configure doc environment
shell: julia --project=docs --color=yes {0}
run: |
using Pkg
Pkg.instantiate()
- run: julia --project=docs docs/make.jl
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }}
7 changes: 7 additions & 0 deletions docs/Project.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
[deps]
Bonito = "824d6782-a2ef-11e9-3a09-e5662e0c26f8"
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
GeometryBasics = "5c1252a2-5f33-56bf-86c9-59e7332b4326"
MeshIO = "7269a6da-0436-5bbc-96c2-40638cbb6118"
Meshing = "e6723b4c-ebff-59f1-b4b7-d97aa5274f73"
WGLMakie = "276b4fcb-3e11-5398-bf8b-a0c2d153d008"

[sources]
GeometryBasics = {path = "../"}
13 changes: 6 additions & 7 deletions docs/make.jl
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,17 @@ using GeometryBasics

DocMeta.setdocmeta!(GeometryBasics, :DocTestSetup, :(using GeometryBasics); recursive=true)

makedocs(format=Documenter.HTML(prettyurls=get(ENV, "CI", "false") == "true"),
sitename="GeometryBasics.jl",
pages=[
"index.md",
makedocs(; sitename="GeometryBasics.jl",
format=Documenter.HTML(; prettyurls=false, size_threshold=3000000,
example_size_threshold=3000000),
pages=["index.md",
"primitives.md",
"polygons.md",
"meshes.md",
"decomposition.md",
"boundingboxes.md",
"static_array_types.md",
"api.md",
],
"api.md"],
modules=[GeometryBasics])

deploydocs(repo="github.com/JuliaGeometry/GeometryBasics.jl.git", push_preview=true)
deploydocs(; repo="github.com/JuliaGeometry/GeometryBasics.jl.git", push_preview=true)
109 changes: 105 additions & 4 deletions docs/src/meshes.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Meshes

```@setup 1
using Bonito
Page()
```

GeometryBasics defines two mesh types to work with - `Mesh` and `MetaMesh`

## Mesh
Expand All @@ -17,6 +22,48 @@ You can get data from a mesh using a few interface functions:

You can also grab the contents of `mesh.vertex_attributes` as if they were fields of the `Mesh`, e.g. `mesh.position` works.

### Custom Attributes

Meshes support arbitrary custom attributes beyond the standard position, normal, and UV coordinates. You can attach per-vertex or per-face data like material properties, identifiers, or computed values. These are stored in `mesh.vertex_attributes` and accessed using the same interface.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like using material as a faceview example. That's not something that changes on such a low level imo. Like a loaf of bread is gonna be made out of bread no matter where you look at it, even if details like color, roughness, reflectivity etc change. Material details changing per face also seems odd to me.

Maybe we can just remove the association with materials here and directly have reflectivity etc as vertex/face attributes? Or use something else as an example, e.g. color?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The way I envisioned materials to be used is to mark sections of the mesh with views and have corresponding materials in a metamesh. E.g.:

using GeometryBasics
using Colors

struct Material
    color::RGB{Float32}
    diffuse::Float32
    specular::Float32
    shininess::Float32
end

head = GeometryBasics.mesh(Sphere(Point3f(0), 0.3))
needle = GeometryBasics.mesh(Cone(Point3f(0, 0, -0.2), Point3f(0, 0, -2.0), 0.1))

pin = MetaMesh(
    merge([head, needle]),
    materials = [
        Material(RGB(1, 0, 0), 0.8, 0.4, 4.0),
        Material(RGB(0.8, 0.8, 0.8), 0.4, 2.0, 64.0),
    ]
)

# contains two ranges, one for the faces from sphere, the other for faces from cone
pin.views
pin[:materials]

using GLMakie
f = Figure()
a = LScene(f[1, 1])
# This basically undoes the merge, could also pass pisitions, normals etc as usual and faces as `faces(pin)[pin.views[i]]`
submeshes = GeometryBasics.split_mesh(pin.mesh)
for (m, material) in zip(submeshes, pin[:materials])
    Makie.mesh!(
        a, m, color = material.color, diffuse = material.diffuse, 
        specular = material.specular, shininess = material.shininess
    )
end
f

Iirc obj/mtl loading has another step of indirection here, where views correspond to material names which are used to look up materials in a dict.


```julia
using GeometryBasics

# Define custom data type
struct Material
emissivity::Float64
absorptivity::Float64
reflectivity::Float64
end

# Create mesh with custom attributes
points = [Point3f(0,0,0), Point3f(1,0,0), Point3f(0,1,0), Point3f(0,0,1)]
faces = [TriangleFace(1,2,3), TriangleFace(1,2,4), TriangleFace(1,3,4), TriangleFace(2,3,4)]

materials = [
Material(0.1, 0.8, 0.1),
Material(0.2, 0.7, 0.1),
Material(0.0, 0.9, 0.1),
Material(0.3, 0.6, 0.1)
]

face_names = ["bottom", "side1", "side2", "top"]

# Use per_face to create FaceViews for per-face attributes
mesh = GeometryBasics.mesh(
points,
faces,
material=per_face(materials, faces),
face_name=per_face(face_names, faces)
)

# Access custom attributes
mesh.material[2] # Get material of second face
mesh.face_name[1] # Get name of first face
```

This pattern is useful for physical simulations, rendering with material properties, or tagging mesh regions for analysis.

### FaceView


Expand Down Expand Up @@ -47,6 +94,40 @@ per_face
MetaMesh
```

`MetaMesh` wraps a `Mesh` and allows you to attach global metadata that applies to the entire mesh rather than individual vertices or faces. This is useful for storing information like source file paths, transformation matrices, object identifiers, or simulation parameters.

```julia
using GeometryBasics

# Create a basic mesh
points = [Point3f(0,0,0), Point3f(1,0,0), Point3f(0,1,0)]
faces = [TriangleFace(1,2,3)]
mesh = GeometryBasics.mesh(points, faces; attribute=rand(3))

# Wrap with MetaMesh and add global metadata
meta_mesh = MetaMesh(mesh, source_file="model.obj", object_id=42, scale=1.5)

# Access metadata
meta_mesh[:source_file] # "model.obj"
meta_mesh[:object_id] # 42
meta_mesh.attribute # access vertex attributes via getproperty
# The underlying mesh is still accessible
meta_mesh.mesh
```

You can combine `MetaMesh` for global properties with per-face/per-vertex attributes for complete geometric and metadata representation:

```julia
# Create mesh with both per-face attributes and global metadata
mesh = GeometryBasics.mesh(
points, faces,
material=per_face(materials, faces),
normal=face_normals(points, faces)
)

meta_mesh = MetaMesh(mesh, gltf_file="spacecraft.gltf", mass=150.0)
```

## How to create a mesh

### GeometryBasics
Expand All @@ -66,13 +147,33 @@ Note that this doesn't remove any data (e.g. hidden or duplicate vertices), and

### Meshing.jl

```@example 1
using Meshing
using GeometryBasics
using WGLMakie
using LinearAlgebra

gyroid(v) = cos(v[1])*sin(v[2])+cos(v[2])*sin(v[3])+cos(v[3])*sin(v[1])
gyroid_shell(v) = max(gyroid(v)-0.4,-gyroid(v)-0.4)
xr,yr,zr = ntuple(_->LinRange(0,pi*4,50),3)

A = [gyroid_shell((x,y,z)) for x in xr, y in yr, z in zr]
# generate directly using GeometryBasics API
# Rect specifies the sampling intervals
vts, fcs = isosurface(A, MarchingCubes())
# view with Makie
fcs = TriangleFace{Int}.(fcs)
vts = Point3d.(vts)
Makie.mesh(GeometryBasics.Mesh(vts, fcs), color=[norm(v) for v in vts])
```

### MeshIO.jl

The [`MeshIO.jl`](https://github.com/JuliaIO/MeshIO.jl) package provides load/save support for several file formats which store meshes.

```@example
using GLMakie, GLMakie.FileIO, GeometryBasics
```@example 1
using WGLMakie, GeometryBasics

m = load(GLMakie.assetpath("cat.obj"))
GLMakie.mesh(m; color=load(GLMakie.assetpath("diffusemap.png")), axis=(; show_axis=false))
m = Makie.loadasset("cat.obj")
Makie.mesh(m; color=Makie.loadasset("diffusemap.png"), axis=(; show_axis=false))
```
4 changes: 3 additions & 1 deletion src/basic_types.jl
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,7 @@ faces(x::FaceView) = x.faces
Base.values(x::FaceView) = x.data
facetype(x::FaceView) = eltype(x.faces)
Base.getindex(x::FaceView, f::AbstractFace) = getindex(values(x), f)
Base.getindex(x::FaceView, i::Integer) = x.data[i]
Base.isempty(x::FaceView) = isempty(values(x))
Base.:(==)(a::FaceView, b::FaceView) = (values(a) == values(b)) && (faces(a) == faces(b))

Expand Down Expand Up @@ -763,17 +764,18 @@ function MetaMesh(points::AbstractVector{<:Point}, faces::AbstractVector{<:Abstr
MetaMesh(Mesh(points, faces), Dict{Symbol, Any}(kwargs))
end


@inline function Base.hasproperty(mesh::MetaMesh, field::Symbol)
return hasfield(MetaMesh, field) || hasproperty(getfield(mesh, :mesh), field)
end

@inline function Base.getproperty(mesh::MetaMesh, field::Symbol)
if hasfield(MetaMesh, field)
return getfield(mesh, field)
else
return getproperty(getfield(mesh, :mesh), field)
end
end

@inline function Base.propertynames(mesh::MetaMesh)
return (fieldnames(MetaMesh)..., propertynames(getfield(mesh, :mesh))...)
end
Expand Down
Loading