model3d

package
v0.1.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 20, 2020 License: BSD-2-Clause Imports: 18 Imported by: 22

Documentation

Overview

Package model3d provides a set of APIs for creating, manipulating, and storing 3D models.

model3d includes a few sub-packages:

  • render3d - ray tracing, materials, etc.
  • model2d - 2D graphics, smoothing, bitmaps.
  • toolbox3d - modular 3D-printable components to use in larger 3D models.

In addition, model3d comes with a large collection of examples for both modeling and rendering.

Representations

Models can be represented in three different ways, and model3d can convert between them seamlessly.

In particular, models can be:

  • *Mesh - a triangle mesh, good for exporting to most 3D CAD tools, renderers, etc.
  • Solid - a 3D boolean function defining which points are contained in the model. Ideal for composition, hand-coding, etc.
  • Collider - a surface that reports collisions with rays and other geometric shapes. Ideal for ray tracing, rendering, and physics.

Generally, it is easiest to create new models by implementing the Solid interface, or by using existing solids like *Sphere or *Cylinder and combining them with JoinedSolid or SubtractedSolid.

To convert a Solid to a *Mesh, use MarchingCubes() or MarchingCubesSearch() for more precision. To convert a Solid to a Collider, use SolidCollider or simply create a Mesh and convert that to a Collider.

To convert a *Mesh to a Collider, use MeshToCollider(). To convert a *Mesh to a Solid, first convert it to a Collider and then convert that to a Solid.

To convert a Collider to a Solid, use NewColliderSolid() or NewColliderSolidHollow(). To convert a Collider to a *Mesh, the simplest approach is to convert it to a Solid and then to convert the Solid to a *Mesh.

Creating models

The easiest way to create new models is by defining an object that implements the Solid interface. Once defined, a Solid can be converted to a Mesh and exported to a file (e.g. an STL file for 3D printing).

For example, here's how to implement a sphere as a Solid, taken from the actual model3d.Sphere type:

type Sphere struct {
    Center Coord3D
    Radius float64
}

func (s *Sphere) Min() Coord3D {
    return Coord3D{
        X: s.Center.X - s.Radius,
        Y: s.Center.Y - s.Radius,
        Z: s.Center.Z - s.Radius,
    }
}

func (s *Sphere) Max() Coord3D {
    return Coord3D{
        X: s.Center.X + s.Radius,
        Y: s.Center.Y + s.Radius,
        Z: s.Center.Z + s.Radius,
    }
}

func (s *Sphere) Contains(c Coord3D) bool {
    return c.Dist(s.Center) <= s.Radius
}

Once you have implemented a Solid, you can create a mesh and export it to a file like so:

solid := &Sphere{...}
mesh := MarchingCubesSearch(solid, 0.1, 8)
mesh.SaveGroupedSTL("output.stl")

In the above example, the mesh is created with an epsilon of 0.01 and 8 search steps. These parameters control the mesh resolution. See MarchingCubesSearch() for more details.

Mesh manipulation

The Mesh type provides various methods to check for singularities, fix small holes, eliminate redundant triangles, etc. There are also APIs that operate on a Mesh in more complex ways, making it easier to generate meshes programmatically:

  • Decimator - polygon reduction.
  • MeshSmoother - smoothing for reducing sharp edges or corners.
  • Subdivider - edge-based sub-division to add resolution where it is needed.

Exporting models

Software for 3D printing, rendering, and modeling typically expects to import 3D models as triangle meshes. Thus, model3d provides a number of ways to import and export triangle meshes. The simplest method is Mesh.SaveGroupedSTL(), which exports and STL file to a path. For colored models, Mesh.EncodeMaterialOBJ() is the method to use.

Index

Constants

View Source
const (
	DefaultDecimatorMinAspectRatio = 0.1
	DefaultDecimatorFeatureAngle   = 0.5
)

Variables

This section is empty.

Functions

func EncodeMaterialOBJ

func EncodeMaterialOBJ(triangles []*Triangle, colorFunc func(t *Triangle) [3]float64) []byte

EncodeMaterialOBJ encodes a 3D model as a zip file containing both an OBJ and an MTL file.

The colorFunc maps faces to real-valued RGB colors.

The encoding creates a different material for every color, so the resulting file will be much smaller if a few identical colors are reused for many triangles.

func EncodePLY

func EncodePLY(triangles []*Triangle, colorFunc func(Coord3D) [3]uint8) []byte

EncodePLY encodes a 3D model as a PLY file, including colors for every vertex.

The colorFunc maps coordinates to 24-bit RGB colors.

func EncodeSTL

func EncodeSTL(triangles []*Triangle) []byte

EncodeSTL encodes a list of triangles in the binary STL format for use in 3D printing.

func GroupTriangles

func GroupTriangles(tris []*Triangle)

GroupTriangles sorts the triangle slice into a balanced bounding volume hierarchy. In particular, the sorted slice can be recursively cut in half, and each half will be spatially separated as well as possible along some axis.

This can be used to prepare models for being turned into a collider efficiently, or for storing meshes in an order well-suited for file compression.

The resulting hierarchy can be passed directly to GroupedTrianglesToCollider().

func InBounds

func InBounds(b Bounder, c Coord3D) bool

InBounds returns true if c is contained within the bounding rectangular volume of b.

func Triangulate

func Triangulate(polygon []Coord2D) [][3]Coord2D

Triangulate turns any simple polygon into a set of equivalent triangles.

The polygon is passed as a series of points, in order. The first point is re-used as the ending point, so no ending should be explicitly specified.

func VertexColorsToTriangle

func VertexColorsToTriangle(f func(c Coord3D) [3]float64) func(t *Triangle) [3]float64

VertexColorsToTriangle creates a per-triangle color function that averages the colors at each of the vertices.

func WriteMaterialOBJ added in v0.1.1

func WriteMaterialOBJ(w io.Writer, ts []*Triangle, colorFunc func(t *Triangle) [3]float64) error

WriteMaterialOBJ encodes a 3D model as a zip file containing both an OBJ and an MTL file.

The colorFunc maps faces to real-valued RGB colors.

The encoding creates a different material for every color, so the resulting file will be much smaller if a few identical colors are reused for many triangles.

func WritePLY added in v0.1.1

func WritePLY(w io.Writer, triangles []*Triangle, colorFunc func(Coord3D) [3]uint8) error

WritePLY writes the 3D model as a PLY file, including colors for every vertex.

The colorFunc maps coordinates to 24-bit RGB colors.

func WriteSTL

func WriteSTL(w io.Writer, triangles []*Triangle) error

WriteSTL writes a list of triangles in the binary STL format to w.

Types

type BVH

type BVH struct {
	// Leaf, if non-nil, is the final triangle.
	Leaf *Triangle

	// Branch, if Leaf is nil, points to two children.
	Branch []*BVH
}

BVH represents a (possibly unbalanced) axis-aligned bounding volume hierarchy of triangles.

A BVH can be used to accelerate collision detection. See BVHToCollider() for more details.

A BVH node is either a leaf (a triangle), or a branch with two or more children.

func NewBVHAreaDensity

func NewBVHAreaDensity(triangles []*Triangle) *BVH

NewBVHAreaDensity creates a BVH by minimizing the product of each bounding box's area with the number of triangles contained in the bounding box at each branch.

This is good for efficient ray collision detection.

type Bounder

type Bounder interface {
	// Get the corners of a bounding rectangular volume.
	//
	// A point p satisfies: p >= Min and p <= Max if it is
	// within the bounds.
	Min() Coord3D
	Max() Coord3D
}

A Bounder is a shape contained within a rectangular volume of space.

type Collider

type Collider interface {
	Bounder

	// RayCollisions enumerates the collisions with a ray.
	// It returns the total number of collisions.
	//
	// f may be nil, in which case this is simply used for
	// counting.
	RayCollisions(r *Ray, f func(RayCollision)) int

	// FirstRayCollision gets the ray collision with the
	// lowest scale.
	//
	// The second return value is false if no collisions
	// were found.
	FirstRayCollision(r *Ray) (collision RayCollision, collides bool)

	// SphereCollision checks if the collider touches a
	// sphere with origin c and radius r.
	SphereCollision(c Coord3D, r float64) bool
}

A Collider is a surface which can detect intersections with linear rays and spheres.

All methods of a Collider are safe for concurrency.

type ColliderSolid

type ColliderSolid struct {
	// contains filtered or unexported fields
}

A ColliderSolid is a Solid that uses a Collider to check if points are in the solid.

There are two modes for a ColliderSolid. In the first, points are inside the solid if a ray passes through the surface of the Collider an odd number of times. In the second, points are inside the solid if a sphere of a pre-determined radius touches the surface of the Collider from the point. The second modality is equivalent to creating a thick but hollow solid.

func NewColliderSolid

func NewColliderSolid(collider Collider) *ColliderSolid

NewColliderSolid creates a ColliderSolid.

func NewColliderSolidHollow

func NewColliderSolidHollow(collider Collider, r float64) *ColliderSolid

NewColliderSolidHollow creates a ColliderSolid which includes all points within r distance from the surface of the Collider.

The radius r must be greater than zero.

func (*ColliderSolid) Contains

func (c *ColliderSolid) Contains(p Coord3D) bool

func (*ColliderSolid) Max

func (c *ColliderSolid) Max() Coord3D

func (*ColliderSolid) Min

func (c *ColliderSolid) Min() Coord3D

type ConvexPolytope

type ConvexPolytope []*LinearConstraint

A ConvexPolytope is the intersection of some linear constraints.

func (ConvexPolytope) Contains

func (c ConvexPolytope) Contains(coord Coord3D) bool

Contains checks that c satisfies the constraints.

func (ConvexPolytope) Mesh

func (c ConvexPolytope) Mesh() *Mesh

Mesh creates a mesh containing all of the finite faces of the polytope.

For complicated polytopes, this may take a long time to run, since it is O(n^3) in the constraints.

func (ConvexPolytope) Solid

func (c ConvexPolytope) Solid() Solid

Solid creates a solid out of the polytope.

This runs in O(n^3) in the constraints, so it may be unacceptable for large polytopes.

type Coord2D

type Coord2D = model2d.Coord

func NewCoord2DRandNorm

func NewCoord2DRandNorm() Coord2D

NewCoord2DRandNorm creates a random Coord2D with normally distributed components.

func NewCoord2DRandUnit

func NewCoord2DRandUnit() Coord2D

NewCoord2DRandUnit creates a random Coord with magnitude 1.

type Coord3D

type Coord3D struct {
	X float64
	Y float64
	Z float64
}

A Coord3D is a coordinate in 3-D Euclidean space.

func NewCoord3DArray

func NewCoord3DArray(a [3]float64) Coord3D

NewCoord3DArray creates a Coord3D from an array of x, y, and z.

func NewCoord3DRandNorm

func NewCoord3DRandNorm() Coord3D

NewCoord3DRandNorm creates a random Coord3D with normally distributed components.

func NewCoord3DRandUnit

func NewCoord3DRandUnit() Coord3D

NewCoord3DRandUnit creates a random Coord3D with magnitude 1.

func (Coord3D) Add

func (c Coord3D) Add(c1 Coord3D) Coord3D

Add computes the sum of c and c1.

func (Coord3D) Array

func (c Coord3D) Array() [3]float64

Array creates an array with the x, y, and z. This can be useful for some vectorized code.

func (Coord3D) Coord2D

func (c Coord3D) Coord2D() Coord2D

Coord2D projects c onto the x,y plane and drops the Z value.

func (Coord3D) Cross

func (c Coord3D) Cross(c1 Coord3D) Coord3D

Cross computes the cross product of c and c1.

func (Coord3D) Dist

func (c Coord3D) Dist(c1 Coord3D) float64

Dist computes the Euclidean distance to c1.

func (Coord3D) Dot

func (c Coord3D) Dot(c1 Coord3D) float64

Dot computes the dot product of c and c1.

func (Coord3D) Geo

func (c Coord3D) Geo() GeoCoord

Geo computes a normalized geo coordinate.

func (Coord3D) Max

func (c Coord3D) Max(c1 Coord3D) Coord3D

Max gets the element-wise maximum of c and c1.

func (Coord3D) Mid

func (c Coord3D) Mid(c1 Coord3D) Coord3D

Mid computes the midpoint between c and c1.

func (Coord3D) Min

func (c Coord3D) Min(c1 Coord3D) Coord3D

Min gets the element-wise minimum of c and c1.

func (Coord3D) Mul

func (c Coord3D) Mul(c1 Coord3D) Coord3D

Mul computes the element-wise product of c and c1.

func (Coord3D) Norm

func (c Coord3D) Norm() float64

Norm computes the vector L2 norm.

func (Coord3D) Normalize

func (c Coord3D) Normalize() Coord3D

Normalize gets a unit vector from c.

func (Coord3D) OrthoBasis

func (c Coord3D) OrthoBasis() (Coord3D, Coord3D)

OrthoBasis creates two unit vectors which are orthogonal to c and to each other.

func (Coord3D) ProjectOut

func (c Coord3D) ProjectOut(c1 Coord3D) Coord3D

ProjectOut projects the c1 direction out of c.

func (Coord3D) Reflect

func (c Coord3D) Reflect(c1 Coord3D) Coord3D

Reflect reflects c1 around c on the plane spanned by both vectors.

func (Coord3D) Scale

func (c Coord3D) Scale(s float64) Coord3D

Scale scales all the coordinates by s and returns the new coordinate.

func (Coord3D) Sub

func (c Coord3D) Sub(c1 Coord3D) Coord3D

Sub computes c - c1.

func (Coord3D) Sum

func (c Coord3D) Sum() float64

Sum sums the elements of c.

type Cylinder

type Cylinder struct {
	P1     Coord3D
	P2     Coord3D
	Radius float64
}

A Cylinder is a cylindrical 3D primitive.

The cylinder is defined as all the positions within a radius distance from the line segment between P1 and P2.

func (*Cylinder) Contains

func (c *Cylinder) Contains(p Coord3D) bool

Contains checks if a point p is within the cylinder.

func (*Cylinder) FirstRayCollision

func (c *Cylinder) FirstRayCollision(r *Ray) (RayCollision, bool)

FirstRayCollision gets the first ray collision with the cylinder, if one occurs.

func (*Cylinder) Max

func (c *Cylinder) Max() Coord3D

Max gets the maximum point of the bounding box.

func (*Cylinder) Min

func (c *Cylinder) Min() Coord3D

Min gets the minimum point of the bounding box.

func (*Cylinder) RayCollisions

func (c *Cylinder) RayCollisions(r *Ray, f func(RayCollision)) int

RayCollisions calls f (if non-nil) with every ray collision.

It returns the total number of collisions.

func (*Cylinder) SDF

func (c *Cylinder) SDF(coord Coord3D) float64

SDF gets the signed distance to the cylinder.

func (*Cylinder) SphereCollision

func (c *Cylinder) SphereCollision(center Coord3D, r float64) bool

SphereCollision detects if a sphere collides with the cylinder.

type CylinderSolid

type CylinderSolid = Cylinder

type Decimator

type Decimator struct {
	// The minimum dihedral angle between two triangles
	// to consider an edge a "feature edge".
	//
	// If 0, DefaultDecimatorFeatureAngle is used.
	//
	// This is measured in radians.
	FeatureAngle float64

	// The maximum distance for a vertex to be from its
	// average plane for it to be deleted.
	PlaneDistance float64

	// The maximum distance for a vertex to be from the
	// line defining a feature edge.
	BoundaryDistance float64

	// If true, use PlaneDistance to evaluate all vertices
	// rather than consulting BoundaryDistance.
	NoEdgePreservation bool

	// If true, eliminate corner vertices.
	EliminateCorners bool

	// MinimumAspectRatio is the minimum aspect ratio for
	// triangulation splits.
	//
	// If 0, a default of DefaultDecimatorMinAspectRatio
	// is used.
	MinimumAspectRatio float64
}

Decimator implements a decimation algorithm to simplify triangle meshes.

This may only be applied to closed, manifold meshes. Thus, all edges are touching exactly two triangles, and there are no singularities or holes.

The algorithm is described in: "Decimation of Triangle Meshes" - William J. Schroeder, Jonathan A. Zarge and William E. Lorensen. https://webdocs.cs.ualberta.ca/~lin/ABProject/papers/4.pdf.

func (*Decimator) Decimate

func (d *Decimator) Decimate(m *Mesh) *Mesh

Decimate applies the decimation algorithm to m, producing a new mesh.

type GeoCoord

type GeoCoord struct {
	Lat float64
	Lon float64
}

A GeoCoord specifies a location on a sphere with a unit radius.

The latitude is an angle from -math.Pi/2 to math.pi/2 representing the North-South direction. The longitude is an angle from -math.Pi to math.Pi representing the West-East direction.

func (GeoCoord) Coord3D

func (g GeoCoord) Coord3D() Coord3D

Coord3D converts g to Euclidean coordinates on a unit sphere centered at the origin.

func (GeoCoord) Distance

func (g GeoCoord) Distance(g1 GeoCoord) float64

Distance gets the Euclidean distance between g and g1 when traveling on the surface of the sphere.

func (GeoCoord) Normalize

func (g GeoCoord) Normalize() GeoCoord

Normalize brings the latitude and longitude into the standard range while (approximately) preserving the absolute position.

type IntersectedSolid

type IntersectedSolid []Solid

IntersectedSolid is a Solid containing the intersection of one or more Solids.

func (IntersectedSolid) Contains

func (i IntersectedSolid) Contains(c Coord3D) bool

func (IntersectedSolid) Max

func (i IntersectedSolid) Max() Coord3D

func (IntersectedSolid) Min

func (i IntersectedSolid) Min() Coord3D

type JoinedCollider

type JoinedCollider struct {
	// contains filtered or unexported fields
}

A JoinedCollider wraps multiple other Colliders and only passes along rays and spheres that enter their combined bounding box.

func NewJoinedCollider

func NewJoinedCollider(other []Collider) *JoinedCollider

NewJoinedCollider creates a JoinedCollider which combines one or more other colliders.

func (*JoinedCollider) FirstRayCollision

func (j *JoinedCollider) FirstRayCollision(r *Ray) (RayCollision, bool)

func (*JoinedCollider) Max

func (j *JoinedCollider) Max() Coord3D

func (*JoinedCollider) Min

func (j *JoinedCollider) Min() Coord3D

func (*JoinedCollider) RayCollisions

func (j *JoinedCollider) RayCollisions(r *Ray, f func(RayCollision)) int

func (*JoinedCollider) SphereCollision

func (j *JoinedCollider) SphereCollision(center Coord3D, r float64) bool

type JoinedSolid

type JoinedSolid []Solid

A JoinedSolid is a Solid composed of other solids.

func (JoinedSolid) Contains

func (j JoinedSolid) Contains(c Coord3D) bool

func (JoinedSolid) Max

func (j JoinedSolid) Max() Coord3D

func (JoinedSolid) Min

func (j JoinedSolid) Min() Coord3D

type JoinedTransform

type JoinedTransform []Transform

A JoinedTransform composes transformations from left to right.

func (JoinedTransform) Apply

func (j JoinedTransform) Apply(c Coord3D) Coord3D

func (JoinedTransform) ApplyBounds

func (j JoinedTransform) ApplyBounds(min Coord3D, max Coord3D) (Coord3D, Coord3D)

func (JoinedTransform) Inverse

func (j JoinedTransform) Inverse() Transform

type LinearConstraint

type LinearConstraint struct {
	Normal Coord3D
	Max    float64
}

A LinearConstraint defines a half-space of all points c such that c.Dot(Normal) <= Max.

func (*LinearConstraint) Contains

func (l *LinearConstraint) Contains(c Coord3D) bool

Contains checks if the half-space contains c.

type Matrix2

type Matrix2 = model2d.Matrix2

type Matrix3

type Matrix3 [9]float64

Matrix3 is a 3x3 matrix, stored in row-major order.

func NewMatrix3Columns

func NewMatrix3Columns(c1, c2, c3 Coord3D) *Matrix3

NewMatrix3Columns creates a Matrix3 with the given coordinates as column entries.

func NewMatrix3Rotation

func NewMatrix3Rotation(axis Coord3D, angle float64) *Matrix3

NewMatrix3Rotation creates a 3D rotation matrix. Points are rotated around the given vector in a right-handed direction.

The axis is assumed to be normalized. The angle is measured in radians.

func (*Matrix3) Det

func (m *Matrix3) Det() float64

Det computes the determinant of the matrix.

func (*Matrix3) Inverse

func (m *Matrix3) Inverse() *Matrix3

Inverse computes the inverse matrix.

func (*Matrix3) InvertInPlace

func (m *Matrix3) InvertInPlace()

InvertInPlace moves the inverse of m into m without causing any new allocations.

func (*Matrix3) Mul

func (m *Matrix3) Mul(m1 *Matrix3) *Matrix3

Mul computes m*m1 and returns the product.

func (*Matrix3) MulColumn

func (m *Matrix3) MulColumn(c Coord3D) Coord3D

MulColumn multiplies the matrix m by a column vector represented by c.

func (*Matrix3) Scale

func (m *Matrix3) Scale(s float64)

Scale scales m by a factor s.

func (*Matrix3) Transpose

func (m *Matrix3) Transpose() *Matrix3

Transpose computes the matrix transpose.

type Matrix3Transform

type Matrix3Transform struct {
	Matrix *Matrix3
}

Matrix3Transform is a Transform that applies a matrix to coordinates.

func (*Matrix3Transform) Apply

func (m *Matrix3Transform) Apply(c Coord3D) Coord3D

func (*Matrix3Transform) ApplyBounds

func (m *Matrix3Transform) ApplyBounds(min, max Coord3D) (Coord3D, Coord3D)

func (*Matrix3Transform) Inverse

func (m *Matrix3Transform) Inverse() Transform

type Mesh

type Mesh struct {
	// contains filtered or unexported fields
}

A Mesh is a collection of triangles.

The triangles are uniquely identified as pointers, not as values. This is important for methods which reference existing triangles, such as Remove and Neighbors.

Triangles in a mesh are "connected" when they contain exactly identical points. Thus, small rounding errors can cause triangles to incorrectly be disassociated with each other.

A Mesh can be read safely from concurrent Goroutines, but modifications must not be performed concurrently with any mesh operations.

func DecimateSimple

func DecimateSimple(m *Mesh, epsilon float64) *Mesh

DecimateSimple decimates a mesh using a specified distance epsilon combined with default parameters.

For more fine-grained control, use Decimator.

func LoopSubdivision

func LoopSubdivision(m *Mesh, iters int) *Mesh

LoopSubdivision subdivides the mesh using the Loop subdivision rule, creating a smoother surface with more triangles.

The mesh is subdivided iters times.

The mesh must not have singular edges.

func MarchingCubes

func MarchingCubes(s Solid, delta float64) *Mesh

MarchingCubes turns a Solid into a surface mesh using a corrected marching cubes algorithm.

func MarchingCubesSearch

func MarchingCubesSearch(s Solid, delta float64, iters int) *Mesh

MarchingCubesSearch is like MarchingCubes, but applies an additional search step to move the vertices along the edges of each cube.

The tightness of the triangulation will double for every iteration.

func NewMesh

func NewMesh() *Mesh

NewMesh creates an empty mesh.

func NewMeshPolar

func NewMeshPolar(radius func(g GeoCoord) float64, stops int) *Mesh

NewMeshPolar creates a mesh with a 3D polar function.

func NewMeshRect

func NewMeshRect(min, max Coord3D) *Mesh

NewMeshRect creates a new mesh around the rectangular bounds.

func NewMeshTriangles

func NewMeshTriangles(ts []*Triangle) *Mesh

NewMeshTriangles creates a mesh with the given collection of triangles.

func (*Mesh) Add

func (m *Mesh) Add(t *Triangle)

Add adds the triangle t to the mesh.

func (*Mesh) AddMesh

func (m *Mesh) AddMesh(m1 *Mesh)

AddMesh adds all the triangles from m1 to m.

func (*Mesh) Blur

func (m *Mesh) Blur(rates ...float64) *Mesh

Blur creates a new mesh by moving every vertex closer to its connected vertices.

The rate argument specifies how much the vertex should be moved, 0 being no movement and 1 being the most. If multiple rates are passed, then multiple iterations of the algorithm are performed in succession. If a rate of -1 is passed, then all of neighbors are averaged together with each point, and the resulting average is used.

func (*Mesh) BlurFiltered

func (m *Mesh) BlurFiltered(f func(c1, c2 Coord3D) bool, rates ...float64) *Mesh

BlurFiltered is like Blur, but vertices are only considered neighbors if f returns true for their initial coordinates.

Once vertices are considered neighbors, they will be treated as such for every blur iteration, even if the coordinates change in such a way that f would no longer consider them neighbors.

If f is nil, then this is equivalent to Blur().

func (*Mesh) Contains

func (m *Mesh) Contains(t *Triangle) bool

Contains checks if t has been added to the mesh.

func (*Mesh) EliminateCoplanar

func (m *Mesh) EliminateCoplanar(epsilon float64) *Mesh

EliminateCoplanar eliminates vertices inside whose neighboring triangles are all co-planar.

The epsilon argument controls how close two normals must be for the triangles to be considered coplanar. A good value for very precise results is 1e-8.

func (*Mesh) EliminateEdges

func (m *Mesh) EliminateEdges(f func(tmp *Mesh, segment Segment) bool) *Mesh

EliminateEdges creates a new mesh by iteratively removing edges according to the function f.

The f function takes the current new mesh and a line segment, and returns true if the segment should be removed.

func (*Mesh) EncodeMaterialOBJ

func (m *Mesh) EncodeMaterialOBJ(colorFunc func(t *Triangle) [3]float64) []byte

EncodeMaterialOBJ encodes the mesh as a zip file with per-triangle material.

func (*Mesh) EncodePLY

func (m *Mesh) EncodePLY(colorFunc func(c Coord3D) [3]uint8) []byte

EncodePLY encodes the mesh as a PLY file with color.

func (*Mesh) EncodeSTL

func (m *Mesh) EncodeSTL() []byte

EncodeSTL encodes the mesh as STL data.

func (*Mesh) Find

func (m *Mesh) Find(ps ...Coord3D) []*Triangle

Find gets all the triangles that contain all of the passed points.

For example, to find all triangles containing a line from p1 to p2, you could do m.Find(p1, p2).

func (*Mesh) FlattenBase

func (m *Mesh) FlattenBase(maxAngle float64) *Mesh

FlattenBase flattens out the bases of objects for printing on an FDM 3D printer. It is intended to be used for meshes based on flat-based solids, where the base's edges got rounded by smoothing.

The maxAngle argument specifies the maximum angle (in radians) to flatten. If left at zero, 45 degrees is used since this is the recommended angle on many FDM 3D printers.

In some meshes, this may cause triangles to overlap on the base of the mesh. Thus, it is only intended to be used when the base is clearly defined, and all of the triangles touching it are not above any other triangles (along the Z-axis).

func (*Mesh) Iterate

func (m *Mesh) Iterate(f func(t *Triangle))

Iterate calls f for every triangle in m in an arbitrary order.

If f adds or removes triangles, they will not be visited.

func (*Mesh) IterateSorted

func (m *Mesh) IterateSorted(f func(t *Triangle), cmp func(t1, t2 *Triangle) bool)

IterateSorted is like Iterate, but it first sorts all the triangles according to a less than function, cmp.

func (*Mesh) IterateVertices added in v0.1.1

func (m *Mesh) IterateVertices(f func(c Coord3D))

IterateVertices calls f for every vertex in m in an arbitrary order.

If f adds or removes vertices, they will not be visited.

func (*Mesh) MapCoords

func (m *Mesh) MapCoords(f func(Coord3D) Coord3D) *Mesh

MapCoords creates a new mesh by transforming all of the coordinates according to the function f.

func (*Mesh) Max

func (m *Mesh) Max() Coord3D

Max gets the component-wise maximum across all the vertices in the mesh.

func (*Mesh) Min

func (m *Mesh) Min() Coord3D

Min gets the component-wise minimum across all the vertices in the mesh.

func (*Mesh) NeedsRepair

func (m *Mesh) NeedsRepair() bool

NeedsRepair checks if every edge touches exactly two triangles. If not, NeedsRepair returns true.

func (*Mesh) Neighbors

func (m *Mesh) Neighbors(t *Triangle) []*Triangle

Neighbors gets all the triangles with a side touching a given triangle t.

The triangle t itself is not included in the results.

The triangle t needn't be in the mesh. However, if it is not in the mesh, but an equivalent triangle is, then said equivalent triangle will be in the results.

func (*Mesh) Remove

func (m *Mesh) Remove(t *Triangle)

Remove removes the triangle t from the mesh.

It looks at t as a pointer, so the pointer must be exactly the same as a triangle passed to Add.

func (*Mesh) Repair

func (m *Mesh) Repair(epsilon float64) *Mesh

Repair finds vertices that are close together and combines them into one.

The epsilon argument controls how close points have to be. In particular, it sets the approximate maximum distance across all dimensions.

func (*Mesh) RepairNormals

func (m *Mesh) RepairNormals(epsilon float64) (*Mesh, int)

RepairNormals flips normals when they point within the solid defined by the mesh, as determined by the even-odd rule.

The repaired mesh is returned, along with the number of modified triangles.

The check is performed by adding the normal, scaled by epsilon, to the center of the triangle, and then counting the number of ray collisions from this point in the direction of the normal.

func (*Mesh) SaveGroupedSTL

func (m *Mesh) SaveGroupedSTL(path string) error

SaveGroupedSTL writes the mesh to an STL file with the triangles grouped in such a way that the file can be compressed efficiently.

func (*Mesh) Scale

func (m *Mesh) Scale(s float64) *Mesh

Scale creates a new mesh by scaling the coordinates by a factor s.

func (*Mesh) SelfIntersections

func (m *Mesh) SelfIntersections() int

SelfIntersections counts the number of times the mesh intersects itself. In an ideal mesh, this would be 0.

func (*Mesh) SingularVertices

func (m *Mesh) SingularVertices() []Coord3D

SingularVertices gets the points at which the mesh is squeezed to zero volume. In other words, it gets the points where two pieces of volume are barely touching by a single point.

func (*Mesh) SmoothAreas

func (m *Mesh) SmoothAreas(stepSize float64, iters int) *Mesh

SmoothAreas uses gradient descent to iteratively smooth out the surface by moving every vertex in the direction that minimizes the area of its adjacent triangles.

The stepSize argument specifies how much the vertices are moved at each iteration. Good values depend on the mesh, but a good start is on the order of 0.1.

The iters argument specifies how many gradient steps are taken.

This algorithm can produce very smooth objects, but it is much less efficient than Blur(). Consider Blur() when perfect smoothness is not required.

This method is a simpler version of the MeshSmoother object, which provides a more controlled smoothing API.

func (*Mesh) Transform

func (m *Mesh) Transform(t Transform) *Mesh

Transform applies t to the coordinates.

func (*Mesh) TriangleSlice

func (m *Mesh) TriangleSlice() []*Triangle

TriangleSlice gets a snapshot of all the triangles currently in the mesh. The resulting slice is a copy, and will not change as the mesh is updated.

func (*Mesh) VertexSlice added in v0.1.1

func (m *Mesh) VertexSlice() []Coord3D

VertexSlice gets a snapshot of all the vertices currently in the mesh.

The result is a copy and is in no way connected to the mesh in memory.

type MeshSmoother

type MeshSmoother struct {
	// StepSize controls how fast the mesh is updated.
	// A good value will depend on the mesh, but a good
	// default is 0.1.
	StepSize float64

	// Iterations controls the number of gradient steps
	// to take in order to smooth a mesh.
	// More values result in smoother meshes but take
	// more time.
	Iterations int

	// ConstraintDistance is the minimum distance after
	// which the origin constraint will take effect.
	// This allows points to move freely a little bit
	// without being constrained at all, which is good
	// for sharp meshes created from voxel grids.
	//
	// If this is 0, then points will always be pulled
	// towards their origin by a factor of
	// ConstraintWeight.
	ConstraintDistance float64

	// ConstraintWeight is the weight of the distance
	// constraint term, ||x-x0||^2.
	// If this is 0, no constraint is applied.
	ConstraintWeight float64

	// ConstraintFunc, if specified, is a totally custom
	// gradient term that constrains points to their
	// original positions.
	//
	// The return value of the function is added to the
	// gradient at every step.
	//
	// This is independent of ConstraintDistance and
	// ConstraintWeight, which can be used simultaneously.
	ConstraintFunc func(origin, newCoord Coord3D) Coord3D
}

A MeshSmoother uses gradient descent to smooth out the surface of a mesh by minimizing surface area.

The smoother can be constrained to discourage vertices from moving far from their origins, making the surface locally smooth without greatly modifying the volume.

func (*MeshSmoother) Smooth

func (m *MeshSmoother) Smooth(mesh *Mesh) *Mesh

Smooth applies gradient descent to smooth the mesh.

type Ray

type Ray struct {
	Origin    Coord3D
	Direction Coord3D
}

A Ray is a line originating at a point and extending infinitely in some (positive) direction.

type RayCollision

type RayCollision struct {
	// The amount of the ray direction to add to the ray
	// origin to hit the point in question.
	//
	// The scale should be non-negative.
	Scale float64

	// The normal pointing outward from the surface at the
	// point of collision.
	Normal Coord3D

	// Extra contains additional, implementation-specific
	// information about the collision.
	//
	// For an example, see TriangleCollision.
	Extra interface{}
}

RayCollision is a point where a ray intersects a surface.

type Rect

type Rect struct {
	MinVal Coord3D
	MaxVal Coord3D
}

A Rect is a 3D primitive that fills an axis-aligned rectangular volume.

func (*Rect) Contains

func (r *Rect) Contains(c Coord3D) bool

Contains checks if c is inside of r.

func (*Rect) FirstRayCollision

func (r *Rect) FirstRayCollision(ray *Ray) (RayCollision, bool)

FirstRayCollision gets the first ray collision with the rectangular surface.

func (*Rect) Max

func (r *Rect) Max() Coord3D

Max yields r.MaxVal.

func (*Rect) Min

func (r *Rect) Min() Coord3D

Min yields r.MinVal.

func (*Rect) RayCollisions

func (r *Rect) RayCollisions(ray *Ray, f func(RayCollision)) int

RayCollisions calls f (if non-nil) with each ray collision with the rectangular surface. It returns the number of collisions.

func (*Rect) SDF

func (r *Rect) SDF(c Coord3D) float64

SDF gets the signed distance to the surface of the rectangular volume.

func (*Rect) SphereCollision

func (r *Rect) SphereCollision(c Coord3D, radius float64) bool

SphereCollision checks if a solid sphere touches any part of the rectangular surface.

type RectSolid

type RectSolid = Rect

Backwards compatibility type aliases.

type SDF

type SDF interface {
	Bounder

	SDF(c Coord3D) float64
}

An SDF is a signed distance function.

An SDF returns 0 on the boundary of some surface, positive values inside the surface, and negative values outside the surface. The magnitude is the distance to the surface.

All methods of an SDF are safe for concurrency.

func ColliderToSDF

func ColliderToSDF(c Collider, iterations int) SDF

ColliderToSDF generates an SDF that uses bisection search to approximate the SDF for any Collider.

The iterations argument controls the precision. If set to 0, a default of 32 is used.

func GroupedTrianglesToSDF

func GroupedTrianglesToSDF(tris []*Triangle) SDF

GroupedTrianglesToSDF creates an SDF from a slice of triangles. If the triangles are not grouped by GroupTriangles(), the resulting SDF is inefficient.

func MeshToSDF

func MeshToSDF(m *Mesh) SDF

MeshToSDF turns a mesh into an SDF.

type Segment

type Segment [2]Coord3D

A Segment is a line segment in a canonical ordering, such that segments can be compared via the == operator even if they were created with their points in the opposite order.

func NewSegment

func NewSegment(p1, p2 Coord3D) Segment

NewSegment creates a segment with the canonical ordering.

func (Segment) Dist

func (s Segment) Dist(c Coord3D) float64

Dist gets the minimum distance from c to a point on the line segment.

func (Segment) Mid

func (s Segment) Mid() Coord3D

Mid gets the midpoint of the segment.

type Solid

type Solid interface {
	// Contains must always return false outside of the
	// boundaries of the solid.
	Bounder

	Contains(p Coord3D) bool
}

A Solid is a boolean function in 3D where a value of true indicates that a point is part of the solid, and false indicates that it is not.

All methods of a Solid are safe for concurrency.

func CacheSolidBounds

func CacheSolidBounds(s Solid) Solid

CacheSolidBounds creates a Solid that has a cached version of the solid's boundary coordinates.

The solid also explicitly checks that points are inside the boundary before passing them off to s.

func StackSolids added in v0.1.1

func StackSolids(s ...Solid) Solid

StackSolids joins solids together and moves each solid after the first so that the lowest Z value of its bounding box collides with the highest Z value of the previous solid's bounding box. In other words, the solids are stacked on top of each other along the Z axis.

func TransformSolid

func TransformSolid(t Transform, s Solid) Solid

TransformSolid applies t to the solid s to produce a new, transformed solid.

type SolidCollider

type SolidCollider struct {
	Solid Solid

	// Epsilon is a distance considered "small" in the
	// context of the solid.
	// It is used to walk along rays to find
	// intersections.
	Epsilon float64

	// BisectCount, if non-zero, specifies the number of
	// bisections to use to narrow down collisions.
	// If it is zero, a reasonable default is used.
	BisectCount int

	// NormalSamples, if non-zero, specifies how many
	// samples to use to approximate normals.
	// If not specified, a default is used.
	NormalSamples int

	// NormalBisectEpsilon, if non-zero, specifies a small
	// distance to use in a bisection-based method to
	// compute approximate normals.
	//
	// If set, this should typically be smaller than
	// Epsilon, since smaller values don't affect runtime
	// but do improve accuracy (up to a point).
	//
	// If this is 0, bisection is not used to approximate
	// normals, but rather a more noisy but less brittle
	// algorithm.
	NormalBisectEpsilon float64
}

A SolidCollider approximates the behavior of a Collider based on nothing but a Solid.

func (*SolidCollider) FirstRayCollision

func (s *SolidCollider) FirstRayCollision(r *Ray) (RayCollision, bool)

FirstRayCollision approximately finds the first time the ray collides with the solid.

The result may be inaccurate for parts of the solid smaller than epsilon.

func (*SolidCollider) Max

func (s *SolidCollider) Max() Coord3D

Max gets the maximum boundary of the Solid.

func (*SolidCollider) Min

func (s *SolidCollider) Min() Coord3D

Min gets the minimum boundary of the Solid.

func (*SolidCollider) RayCollisions

func (s *SolidCollider) RayCollisions(r *Ray, f func(RayCollision)) int

RayCollisions counts the approximate number of times the ray collides with the solid's border.

The result may be inaccurate for parts of the solid smaller than epsilon.

func (*SolidCollider) SphereCollision

func (s *SolidCollider) SphereCollision(c Coord3D, r float64) bool

SphereCollision checks if the solid touches a sphere with origin c and radius r.

The result may be inaccurate for parts of the solid smaller than epsilon.

This grows slower with r as O(r^3).

type Sphere

type Sphere struct {
	Center Coord3D
	Radius float64
}

A Sphere is a spherical 3D primitive.

func (*Sphere) Contains

func (s *Sphere) Contains(c Coord3D) bool

Contains checks if a point c is inside the sphere.

func (*Sphere) FirstRayCollision

func (s *Sphere) FirstRayCollision(r *Ray) (RayCollision, bool)

FirstRayCollision gets the first ray collision with the sphere, if one occurs.

func (*Sphere) Max

func (s *Sphere) Max() Coord3D

Max gets the maximum point of the bounding box.

func (*Sphere) Min

func (s *Sphere) Min() Coord3D

Min gets the minimum point of the bounding box.

func (*Sphere) RayCollisions

func (s *Sphere) RayCollisions(r *Ray, f func(RayCollision)) int

RayCollisions calls f (if non-nil) with every ray collision.

It returns the total number of collisions.

func (*Sphere) SDF

func (s *Sphere) SDF(c Coord3D) float64

SDF gets the signed distance relative to the sphere.

func (*Sphere) SphereCollision

func (s *Sphere) SphereCollision(c Coord3D, r float64) bool

SphereCollision checks if the surface of s collides with another sphere centered at c with radius r.

type SphereSolid

type SphereSolid = Sphere

type StackedSolid

type StackedSolid []Solid

A StackedSolid is like a JoinedSolid, but the solids after the first are moved so that the lowest Z value of their bounding box collides with the highest Z value of the previous solid. In other words, the solids are stacked on top of each other along the Z axis.

This API is deprecated in favor of the StackSolids() function.

func (StackedSolid) Contains

func (s StackedSolid) Contains(c Coord3D) bool

func (StackedSolid) Max

func (s StackedSolid) Max() Coord3D

func (StackedSolid) Min

func (s StackedSolid) Min() Coord3D

type Subdivider

type Subdivider struct {
	// contains filtered or unexported fields
}

A Subdivider is used for sub-dividing triangles in a mesh to add levels of detail where it is needed in a model.

To achieve this, it tracks line segments that are to be split, and then replaces triangles in the mesh such that the given lines are split and the midpoint is replaced with a more accurate value.

func NewSubdivider

func NewSubdivider() *Subdivider

NewSubdivider creates an empty Subdivider.

func (*Subdivider) Add

func (s *Subdivider) Add(p1, p2 Coord3D)

Add adds a line segment that needs to be split.

func (*Subdivider) AddFiltered

func (s *Subdivider) AddFiltered(m *Mesh, f func(p1, p2 Coord3D) bool)

AddFiltered adds line segments for which f returns true.

func (*Subdivider) Subdivide

func (s *Subdivider) Subdivide(mesh *Mesh, midpointFunc func(p1, p2 Coord3D) Coord3D)

Subdivide modifies the mesh by replacing triangles whose sides are affected by subdivision.

The midpointFunc computes a 3D coordinate that should replace the midpoint of a given line segment from the original mesh.

type SubtractedSolid

type SubtractedSolid struct {
	Positive Solid
	Negative Solid
}

SubtractedSolid is a Solid consisting of all the points in Positive which are not in Negative.

func (*SubtractedSolid) Contains

func (s *SubtractedSolid) Contains(c Coord3D) bool

func (*SubtractedSolid) Max

func (s *SubtractedSolid) Max() Coord3D

func (*SubtractedSolid) Min

func (s *SubtractedSolid) Min() Coord3D

type Torus

type Torus struct {
	Center      Coord3D
	Axis        Coord3D
	OuterRadius float64
	InnerRadius float64
}

A Torus is a 3D primitive that represents a torus.

The torus is defined by revolving a sphere of radius InnerRadius around the point Center and around the axis Axis, at a distance of OuterRadius from Center.

The Torus is only valid if the inner radius is lower than the outer radius. Otherwise, invalid ray collisions and SDF values may be reported.

func (*Torus) Contains

func (t *Torus) Contains(c Coord3D) bool

Contains determines if c is within the torus.

func (*Torus) Max

func (t *Torus) Max() Coord3D

Max gets the maximum point of the bounding box.

func (*Torus) Min

func (t *Torus) Min() Coord3D

Min gets the minimum point of the bounding box.

func (*Torus) SDF

func (t *Torus) SDF(c Coord3D) float64

SDF determines the minimum distance from a point to the surface of the torus.

type TorusSolid

type TorusSolid = Torus

type Transform

type Transform interface {
	// Apply applies the transformation to c.
	Apply(c Coord3D) Coord3D

	// ApplyBounds gets a new bounding rectangle that is
	// guaranteed to bound the old bounding rectangle when
	// it is transformed.
	ApplyBounds(min, max Coord3D) (Coord3D, Coord3D)

	// Inverse gets an inverse transformation.
	//
	// The inverse may not perfectly invert bounds
	// transformations, since some information may be lost
	// during such a transformation.
	Inverse() Transform
}

Transform is an invertible coordinate transformation.

type Translate

type Translate struct {
	Offset Coord3D
}

Translate is a Transform that adds an offset to coordinates.

func (*Translate) Apply

func (t *Translate) Apply(c Coord3D) Coord3D

func (*Translate) ApplyBounds

func (t *Translate) ApplyBounds(min, max Coord3D) (Coord3D, Coord3D)

func (*Translate) Inverse

func (t *Translate) Inverse() Transform

type Triangle

type Triangle [3]Coord3D

A Triangle is a triangle in 3D Euclidean space.

func ReadOFF

func ReadOFF(r io.Reader) ([]*Triangle, error)

ReadOFF decodes a file in the object file format. See http://segeval.cs.princeton.edu/public/off_format.html.

func ReadSTL

func ReadSTL(r io.Reader) ([]*Triangle, error)

ReadSTL decodes a file in the STL file format.

func TriangulateFace

func TriangulateFace(polygon []Coord3D) []*Triangle

TriangulateFace turns any simple polygon face into a set of triangles.

If the points are not coplanar, the result is approximated.

func (*Triangle) Area

func (t *Triangle) Area() float64

Area computes the area of the triangle.

func (*Triangle) AreaGradient

func (t *Triangle) AreaGradient() *Triangle

AreaGradient computes the gradient of the triangle's area with respect to every coordinate.

func (*Triangle) Dist

func (t *Triangle) Dist(c Coord3D) float64

Dist gets the minimum distance from c to a point on the triangle.

func (*Triangle) FirstRayCollision

func (t *Triangle) FirstRayCollision(r *Ray) (RayCollision, bool)

FirstRayCollision gets the ray collision if there is one.

The Extra field is a *TriangleCollision.

func (*Triangle) Max

func (t *Triangle) Max() Coord3D

Max gets the element-wise maximum of all the points.

func (*Triangle) Min

func (t *Triangle) Min() Coord3D

Min gets the element-wise minimum of all the points.

func (*Triangle) Normal

func (t *Triangle) Normal() Coord3D

Normal computes a normal vector for the triangle using the right-hand rule.

func (*Triangle) RayCollisions

func (t *Triangle) RayCollisions(r *Ray, f func(RayCollision)) int

RayCollisions calls f (if non-nil) with a collision (if applicable) and returns the collisions count (0 or 1).

The Extra field is a *TriangleCollision.

func (*Triangle) Segments

func (t *Triangle) Segments() [3]Segment

Segments gets all three line segments in the triangle.

func (*Triangle) SharesEdge

func (t *Triangle) SharesEdge(t1 *Triangle) bool

SharesEdge checks if p shares exactly one edge with p1.

func (*Triangle) SphereCollision

func (t *Triangle) SphereCollision(c Coord3D, r float64) bool

SphereCollision checks if any part of the triangle is within the sphere.

func (*Triangle) TriangleCollisions

func (t *Triangle) TriangleCollisions(t1 *Triangle) []Segment

TriangleCollisions finds the segment where t intersects t1. If no segment exists, an empty slice is returned.

If t and t1 are (nearly) co-planar, no collisions are reported, since small numerical differences can have a major impact.

type TriangleCollider

type TriangleCollider interface {
	Collider

	// TriangleCollisions gets all of the segments on the
	// surface which intersect the triangle t.
	TriangleCollisions(t *Triangle) []Segment
}

A TriangleCollider is like a Collider, but it can also check if and where a triangle intersects the surface.

All methods of a TriangleCollider are safe for concurrency.

func BVHToCollider

func BVHToCollider(b *BVH) TriangleCollider

BVHToCollider converts a BVH into a TriangleCollider in a hierarchical way.

func GroupedTrianglesToCollider

func GroupedTrianglesToCollider(tris []*Triangle) TriangleCollider

GroupedTrianglesToCollider converts a mesh of triangles into a TriangleCollider.

The triangles should be sorted by GroupTriangles. Otherwise, the resulting Collider may not be efficient.

func MeshToCollider

func MeshToCollider(m *Mesh) TriangleCollider

MeshToCollider creates an efficient TriangleCollider out of a mesh.

type TriangleCollision

type TriangleCollision struct {
	// The triangle that reported the collision.
	Triangle *Triangle

	// Barycentric coordinates in the triangle,
	// corresponding to the corners.
	Barycentric [3]float64
}

TriangleCollision is triangle-specific collision information.

type VoxelSmoother

type VoxelSmoother struct {
	StepSize   float64
	Iterations int

	// MaxDistance is the maximum L_infinity distance a
	// vertex must move.
	MaxDistance float64
}

VoxelSmoother uses hard-constraints on top of gradient descent to minimize the surface area of a mesh while keeping vertices within a square region of space.

This is based on the surface nets algorithm.

Also see MeshSmoother, which is similar but more general-purpose.

func (*VoxelSmoother) Smooth

func (v *VoxelSmoother) Smooth(mesh *Mesh) *Mesh

Smooth applies gradient descent to smooth the mesh.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL