GeometryBasics.jl

repository·master·Indexed 42 days ago

https://github.com/juliageometry/geometrybasics.jl

A standard set of geometry types for Julia, serving as a foundation for graphics, finite element analysis (FEA), and geographic applications. It provides tools for creating points, lines, polygons, and meshes, as well as primitives like spheres, cylinders, and cones. It is designed as a modern replacement for GeometryTypes.jl, featuring support for metadata, various memory layouts, and performant C-interop.

Tokens
5.1K
Snippets
14
Records
19
Agent score
53%

What's inside GeometryBasics.jl

  1. Overview of GeometryBasics.jl

    master

    GeometryBasics.jl provides a standard set of basic geometry types designed for interoperability and performance. It is intended to serve as a foundation for graphics/plotting, finite element analysis (FEA), geographic applications, and general geometry manipulation.

    Key features include:

    • Support for metadata attached to geometries.
    • Compatibility with query frameworks.
    • Support for various memory layouts.
    • A 'Julian' API designed to allow performant C-interop.

    Note: This package is the intended replacement for the discontinued GeometryTypes.jl.

  2. Use Tessellation to control mesh density

    master

    The Tessellation type is a wrapper for primitives that specifies how dense the resulting mesh should be. It allows you to pass a density parameter (like vertex counts or grid dimensions) to the primitive's generation functions.

    • For shapes like Cylinder, you can pass an integer representing the number of vertices per circle.
    • For Rect2, you can pass a tuple (nx, ny) representing the number of vertices in the x and y directions.
    using GeometryBasics
    
    # 32 vertices for each circle in the cylinder
    t = Tessellation(Cylinder(Point3f(0), Point3f(0,0,1), 0.2), 32)
    normal_mesh(t)
    
    # 8 vertices in x direction by 6 in y direction for a Rect2
    t = Tessellation(Rect2(Point2f(0), Vec2f(1)), (8, 6))
    triangle_mesh(t)
  3. Implement the Primitive decomposition interface

    master

    GeometryBasics provides an interface for decomposing geometry primitives into vertex attributes and faces. If you are implementing a new primitive, you should provide implementations for the following four functions:

    • coordinates(primitive[, nvertices]): Produces the positions associated with the primitive.
    • faces(primitive[, nvertices]): Produces the faces that connect the vertex positions to a mesh.
    • normals(primitive[, nvertices]): (Optional) Provides normal vectors of the primitive.
    • texturecoordinates(primitive[, nvertices]): (Optional) Provides texture coordinates (UV or UVW).
  4. Use FaceView for per-face data

    master

    FaceView allows you to attach data to a mesh that uses a different indexing scheme than the main mesh.faces. This is useful for memory efficiency when you want to avoid duplicating vertex data for attributes that are constant across a face (like a single color for a triangle).

    Key concepts:

    • Efficiency: Instead of duplicating a color for every vertex in a triangle, you can define a FaceView with one color and a face that refers to a single index.
    • Expansion: If you need to convert a mesh with FaceViews into a standard mesh with duplicated vertex data, use expand_faceviews(mesh).
    • Convenience: For common per-face data scenarios, use the per_face function.
  5. Understand GeometryBasics static array types

    master

    GeometryBasics provides its own set of small, optimized static vectors and matrices to accelerate geometric calculations. These types are designed to behave similarly to StaticArrays.jl and are used throughout the library.

    The core types are:

    • Point{N,T}: A point in $N$ dimensions with element type $T$. It subtypes StaticVector{N,T} and AbstractVector{T}.
    • Vec{N,T}: A vector in $N$ dimensions with element type $T$. It subtypes StaticVector{N,T} and AbstractVector{T}.
    • Mat{Row, Column, T, L}: A matrix with specified rows, columns, element type $T$, and layout $L$. It subtypes AbstractMatrix{T}.
    Point{N,T} <: StaticVector{N,T} <: AbstractVector{T}
    Vec{N,T} <: StaticVector{N,T} <: AbstractVector{T}
    Mat{Row, Column, T, L} <: AbstractMatrix{T}
  6. Extend bounding box functionality for custom geometries

    master

    To provide a specialized, more efficient, or exact bounding box for a new geometry type, implement the Rect{N, T}(geom) method.

    Key implementation details:

    1. Method Signature: All bounding box calls funnel into Rect{N, T}(geom). The parameters N (dimension) and T (type) default to the dimension and type of the input AbstractGeometry{N, T}.
    2. Dimension Constraints: The user-requested dimension N can be less than or equal to the geometry's dimension.
    3. Validation: Use GeometryBasics.bbox_dim_check(user_dim, geom_dim) to validate that the requested bounding box dimension is compatible with the geometry's dimension.
    function Rect{N, T}(a::HyperSphere{N2}) where {N, N2, T}
        GeometryBasics.bbox_dim_check(N, N2)
        return Rect{N, T}(minimum(a), widths(a))
    end
  7. Build documentation locally

    master

    To build the project documentation on your local machine, you must first instantiate the documentation project located in the docs/ directory. Follow these steps in your terminal:

    1. Open a Julia REPL pointing to the docs project: julia --project=docs/
    2. Instantiate the dependencies: pkg> instantiate
    3. Develop the current package: pkg> dev .
    4. Run the build script: julia --project=docs/ docs/make.jl

    Running docs/make.jl will also execute the doctests defined in the Markdown files. Ensure your doctests are compatible with the Julia version specified in the project's CI configuration.

    # Step 1: Enter the docs project
    julia --project=docs/
    
    # Inside the Julia REPL:
    pkg> instantiate
    pkg> dev .
    
    # Step 2: Build the docs from the shell
    julia --project=docs/ docs/make.jl
  8. Use existing GeometryPrimitives

    master

    GeometryBasics provides several predefined primitives that can be used to construct meshes. These include axis-aligned hyperrectangles, spheres, cylinders, cones, and pyramids.

    using GeometryBasics
    
    # HyperRectangle (Rect)
    # Rect2 supports normals, texture coordinates, and tessellation.
    # Rect3 supports normals and texture coordinates, but not tessellation.
    r1 = HyperRectangle{4, Float64}(Point{4, Float64}(0), Vec{4, Float64}(1))
    r2 = Rect3f(Point3f(-1), Vec3f(2))
    r3 = Rect2i(0, 0, 1, 1)
    
    # Sphere and Circle
    s1 = HyperSphere{4, Int}(Point{4, Int}(0), 5)
    s2 = Sphere(Point3f(0, 0, 1), 1)
    s3 = Circle(Point2d(0), 2.0)
    
    # Cylinder (3D: start point, end point, radius)
    c = Cylinder(Point3f(-1, 0, 0), Point3f(0, 0, 1), 0.3f0)
    
    # Cone (3D: start point, tip point, radius)
    cone = Cone(Point3f(-1, 0, 0), Point3f(0, 0, 1), 0.3f0)
    
    # Pyramid (3D: center, height, width)
    p = Pyramid(Point3f(0), 1f0, 0.3f0)
  9. Decompose geometries into meshes

    master

    You can decompose complex geometries into simpler components like faces and positions to build meshes.

    1. Use decompose(Type, geometry) to extract specific elements. For example, extracting TriangleFace types from a rectangle provides the connectivity information.
    2. Use decompose(Type, geometry) to extract vertex positions (e.g., Point types).
    3. Combine these elements using the Mesh(positions, faces) constructor, or use the convenience function GeometryBasics.mesh(geometry) to generate a mesh directly from a geometry object.
    using GeometryBasics
    
    # Setup a rectangle
    rect = Rect(Vec(0.0, 0.0), Vec(1.0, 1.0))
    
    # Decompose into triangular faces
    rect_faces = decompose(TriangleFace{Int}, rect)
    
    # Decompose into vertex positions
    rect_positions = decompose(Point{2, Float64}, rect)
    
    # Method 1: Manual mesh construction
    mesh = Mesh(rect_positions, rect_faces)
    
    # Method 2: Direct mesh extraction from geometry
    mesh_direct = GeometryBasics.mesh(rect)
  10. Quick start with GeometryBasics.jl

    master

    GeometryBasics.jl provides a standard set of geometry types designed for metadata support, query frameworks, and various memory layouts. It serves as a replacement for the discontinued GeometryTypes.jl and is suitable for graphics, finite element analysis, and geographic applications.

    To get started, load the package and use the provided constructors for points, lines, and polygons.

    using GeometryBasics
    
    # Create points
    p1 = Point(3, 1)
    p2 = Point(1, 3)
    p3 = Point(4, 4)
    
    # Create line segments
    l1 = Line(p1, p2)
    l2 = Line(p2, p3)
    
    # Create a linestring from multiple points
    ls = LineString([p1, p2, p3])
    
    # Create a polygon from points
    poly = Polygon(Point{2, Int}[(3, 1), (4, 4), (2, 4), (1, 2), (3, 1)])
    
    # Create a rectangle (Rect) using vectors for origin and dimensions
    rect = Rect(Vec(0.0, 0.0), Vec(1.0, 1.0))
  11. Implement a new GeometryPrimitive

    master

    To create a custom primitive, your type must inherit from GeometryPrimitive{Dim, eltype} and implement the following core functions:

    1. coordinates(primitive): Returns the positions of the vertices.
    2. faces(primitive): Returns the connectivity (e.g., QuadFace or TriangleFace) defining the surfaces.

    Implementing these two functions is sufficient to automatically generate normals (for 3D) and texture coordinates (for 2D). For more control, you can manually implement:

    • normals(primitive)
    • texturecoordinates(primitive)

    Note on Winding Order: For correct normal generation and backface culling (e.g., in OpenGL), faces should be defined with a counter-clockwise winding order when viewed from the outside of the shape.

    struct MyPrimitive{T} <: GeometryPrimitive{3, T}
        # ... fields ...
    end
    
    function GeometryBasics.coordinates(p::MyPrimitive)
        # Return array of points
    end
    
    function GeometryBasics.faces(p::MyPrimitive)
        # Return array of faces (e.g., QuadFace{Int}[...])
    end
  12. Create meshes from primitives in GeometryBasics

    master

    GeometryBasics provides several constructors to generate meshes from geometric primitives:

    • triangle_mesh(primitive): Generates a basic mesh with only positions and faces.
    • normal_mesh(primitive): Generates a mesh with normals (calculates them if the primitive doesn't implement normal()).
    • uv_mesh(primitive): Generates a mesh with texture coordinates (calculates them if the primitive doesn't implement texturecoordinates()).
    • uv_normal_mesh(primitive): Generates a mesh with both normals and texture coordinates.

    Customization and Construction:

    • Type Control: These constructors accept keyword arguments to set specific types: pointtype, facetype, normaltype, and uvtype.
    • Direct Construction: You can use Mesh() or GeometryBasics.mesh() to construct a mesh directly from raw data. GeometryBasics.mesh() includes automatic conversion for pointtype and facetype.
    • Combining Meshes: Use merge(::Vector{Mesh}) to combine multiple meshes into one. Note that merge does not remove duplicate vertices and may remove FaceViews if they are incompatible between the input meshes.
    using GeometryBasics
    
    # Create a mesh from a primitive
    m = normal_mesh(Rect3(0,0,0, 1,1,1))
    
    # Create a mesh from raw data
    m_direct = GeometryBasics.mesh(positions, faces)
    
    # Combine multiple meshes
    combined_mesh = merge([m1, m2, m3])