Defining scope of spatial/r3 and r2 packages

20 views
Skip to first unread message

Patricio Whittingslow

unread,
May 28, 2022, 7:43:28 PM5/28/22
to gonum-dev
Hello gophers. This thread seeks to concretely define the scope of the spatial/r3 package and the spatial/r2 package. 

Background
I am a mechanical engineer who works with physical simulations and has interest in CAD using Golang. I have started using gonum due to the excellent design of the spatial/r3 and spatial/r2 packages. Today I rely on lots of spatial code I have programmed which I feel belongs in a common gonum package which could be spatial/r3 or a new spatial package.

These include
* Spatial primitives like line and plane types for minimum distance calculations  
* Triangle types for 3D/2D meshing and graphical rendering
*Tetrahedron for 3D meshing solids or volumes
* Affine transform for spatial linear transformations of vectors
* scalar and vector field differentiation (Hessian, Gradient, Divergence, Laplacian)
* Box type for 3D rendering 
* Element-wise vector functions

Scope of additions
Most additions below apply to the r2 package as well with the exception of Tetrahedron and Plane types.

New type additions
The types mentioned above would amount to the following signature addition in the r3 package
type (
    Tetrahedron [4]Vec
    Triangle [3]Vec
    Line [2]Vec
    Plane struct{ P, n Vec }
    Affine struct{ mat [16]float64 }
)
The Box type is not included since it already is part of r3. Although a triangle can represent a plane it is suggested a dedicated type be used since minimum distance calculation depends on having the unit normal to the plane. If instead triangle's Normal() method was used this would call math.Sqrt twice during minimum distance calculation, a far cry from simple addition and subtraction necessary with a dedicated Plane type. Worth noting dedicated Plane type also uses less memory, 2 vs. 3 vectors for representation.

Package level functions additions
New package level functions would amount to the following:

func Gradient(p Vec, tol float64, f func(Vec) float64) Vec
func Divergence(p Vec, tol float64, f func(Vec) Vec) float64
func Laplacian(p Vec, tol float64, f func(Vec) float64) float64


It is worth noting Laplacian can be constructed as follows from Gradient and Divergence though this would incur the cost of calling f twice as much.

func Laplacian(p Vec, tol float64, f func(Vec) float64) float64 {
    return Divergence(p, tol, func(v Vec) Vec { return Gradient(p, tol, f) })
}

The functions corresponding to Hessian and Jacobian should be added as methods to the existing r3.Mat type. These should set the matrix to the result.

I mentioned the use of element-wise vector functions. These are commonly used for computer graphics for bounding box calculations. I have not included them here since they would bloat the API and are rather easy to implement and have a niche application.

Package level constructors added
Some of the new types would need constructors to be used effectively. These are detailed as follows
For new Affine type NewAffine gives users precise control over how the linear transformation is constructed.
func NewAffine(a []float64) Affine

ComposeAffine is a shorthand way of setting rotational, translational and scaling parameters of the linear transform
func ComposeAffine(position, scale Vec, q Rotation) Affine
 
To construct a valid plane for best performance the normal vector must be normalized. This means a constructor is necessary to avoid giving users a footgun. The normal vector field would then be unexported.
func NewPlane(pointOnPlane, normal Vec) Plane

Transformer interface special consideration
It has been discussed that vector transformations could be expressed as an interface as follows
Idiomatic Go:
type Transformer interface {
     Transform(Vec) Vec
}
alternative with more familiar mathematical terminology:
type Transform interface {
     Apply(Vec) Vec
}
gonum/spatial/r3 types Rotation and Affine would then implement this interface and help users design APIs that use these or other transformations. An example of where this is needed is the sdf package with the Transform3D function.

Relevant links to this discussion
issue #1809 spatial/r3: Transformer interface and scope of r3 package
issue #1808  spatial/r3: Add Tetrahedron type
issue #1797 proposal: spatial manipulation matrices (Affine transforms)
issue #1791  proposal: spatial package: add Box, Triangle types and Elem functions

Patricio Whittingslow

unread,
May 28, 2022, 7:50:19 PM5/28/22
to gonum-dev
I forgot to add a package level function which I feel should also be discussed:
func Curl(p Vec, tol float64, f func(Vec) Vec) Vec
With this addition I believe all most common r3 differentiation operations are included.

Dan Kortschak

unread,
May 28, 2022, 8:11:32 PM5/28/22
to gonu...@googlegroups.com
Thanks, Patricio.

Some comments in-line.

On Sat, 2022-05-28 at 16:43 -0700, Patricio Whittingslow wrote:

> New type additions
> The types mentioned above would amount to the following signature
> addition in the r3 package
> type (
>      Tetrahedron [4]Vec
>      Triangle [3]Vec
>      Line [2]Vec
>      Plane struct{ P, n Vec }
>      Affine struct{ mat [16]float64 }
> )
>

A line could be represented either as two points or a point and a
vector. It would be good to get an idea of which would be more
generally useful.
 
> To construct a valid plane for best performance the normal vector
> must be normalized. This means a constructor is necessary to avoid
> giving users a footgun. The normal vector field would then be
> unexported.
> func NewPlane(pointOnPlane, normal Vec) Plane

It's not clear to me why this needs to have the normal be unexported;
we can document that the normal must be unit when it must and that this
is satisfied by Planes returned by NewPlane. I'm also wondering about
being able to obtain a Plane from a Triangle, though this could be
achieved by NewPlane(t[0], t.Normal()).

> Transformer interface special consideration
> It has been discussed that vector transformations could be expressed
> as an interface as follows
> Idiomatic Go:
> type Transformer interface {
> Transform(Vec) Vec
> }
> alternative with more familiar mathematical terminology:
> type Transform interface {
> Apply(Vec) Vec
> }
> gonum/spatial/r3 types Rotation and Affine would then implement this
> interface and help users design APIs that use these or other
> transformations. An example of where this is needed is the sdf
> package with the Transform3D function.

I would suggest also here the potential for a dual-quat (r3) and dual-
complex (r2) transforme. These are more efficient than the Affine
matrix and have other useful properties, like easy SLERP. I don't have
a good name for them, maybe RigidTransform.

Dan


Dan Kortschak

unread,
May 28, 2022, 9:45:15 PM5/28/22
to gonu...@googlegroups.com
On Sat, 2022-05-28 at 16:43 -0700, Patricio Whittingslow wrote:
> ComposeAffine is a shorthand way of setting rotational, translational
> and scaling parameters of the linear transform
> func ComposeAffine(position, scale Vec, q Rotation) Affine

The other thing to consider is that the affine transform can express
skew, which is not included here. It probably should be.

Dan

Patricio Whittingslow

unread,
May 29, 2022, 9:47:00 AM5/29/22
to gonum-dev
> A line could be represented either as two points or a point and a
> vector. It would be good to get an idea of which would be more
> generally useful.

So I'm biased in this aspect- I like the idea of a line representing a concrete object in space (so two points definition)
which makes it easy to use for graphing. If the line were represented by a point and a unit normal it would be impossible
 to construct finite line plots without using "invalid" lines. Here's an example of what that would look like: https://github.com/soypat/go-play3d/blob/main/line.go

> document that the normal must be unit

Fair enough, does not sound too crazy.

> RigidTransform

Sounds good. Should these be implemented like Rotation inside r3/r2? Or should it be in the dual packages and satisfy the interface?

> The other thing to consider is that the affine transform can express
> skew, which is not included here. It probably should be.

Darn. Good catch. I'll add it

Dan Kortschak

unread,
May 29, 2022, 5:59:43 PM5/29/22
to gonu...@googlegroups.com
On Sun, 2022-05-29 at 06:47 -0700, Patricio Whittingslow wrote:
> > A line could be represented either as two points or a point and a
> > vector. It would be good to get an idea of which would be more
> > generally useful.
>
> So I'm biased in this aspect- I like the idea of a line representing
> a concrete object in space (so two points definition)
> which makes it easy to use for graphing. If the line were represented
> by a point and a unit normal it would be impossible
>  to construct finite line plots without using "invalid" lines. Here's
> an example of what that would look
> like: https://github.com/soypat/go-play3d/blob/main/line.go
>

The vector is not a normal, it's a direction and differs only from the
current implementation by an addition operation. It's also more aligned
with the representation of the plane by the point a vector rather than
the minimal concrete object required for representing a plane, the
triangle. In the code you link there are many times where you are doing
Sub(l[1], l[0]), which is giving exactly the vector, which you apply to
l[0]. There is no intrisic reason that the vector needs to be unit, so
if you want to represent a line segment, you can have the vector encode
the length, but also, in parallel with the dual potential
representations of planes, segments (not lines) which are the triangle
cognate could be represented by two points while lines (the cognate of
planes) are represented by point and direction.

> > RigidTransform
>
> Sounds good. Should these be implemented like Rotation inside r3/r2?
> Or should it be in the dual packages and satisfy the interface?

The code is almost identical to Rotate, so it should b in r2 and r3.
With skew added to transform, I'm happy to call the dual* operator
"Rigid".

Dan

Reply all
Reply to author
Forward
0 new messages