go-gl / mathgl

A pure Go 3D math library.
BSD 3-Clause "New" or "Revised" License
554 stars 65 forks source link

Swizzle-like functionality #9

Closed UserAB1236872 closed 10 years ago

UserAB1236872 commented 10 years ago

Given the package's glm inspiration, it seems useful to have functions that act like swizzle operators. Obviously strict swizzle is impossible, but functions should emulate the functionality adequately, and be in-lineable, though they do necessarily do an allocation (albeit as long as you're careful, probably only a stack allocation).

Examples:

func (vec Vec4f) XYZ() Vec3f {
    return Vec3f{vec[0], vec[1], vec[2]}
}

func (vec Vec4f) WYX Vec3f {
    return Vec3f{vec[3], vec[1], vec[0]}
}

// Autopromotion
func (vec Vec2f) XYZ() Vec3f {
    return Vec3f{vec[0], vec[1], 1.0}
}

One missing feature is the nice property of swizzle assignment; vec1.xyz = vec2. This is undeniably convenient, but impossible in Go. One possible solution is a Set method:

func (vec *Vec4f) SetXYZ(src Vec3f) *Vec4f {
    vec[0], vec[1], vec[2] = src[0], src[1], src[2]
    return vec
}

Alternatively, for non-sparse, ascending cases, we could use copy(vec[startIndex:], src[:]). So XYZ is copy(vec[0:], src[:]) and YZW would be copy(vec[1:], src[:])

The only question here is performance. These functions are nice to have, but how much of a performance hit is vec.SetXYZ(vec2.ZXY()) going to be? My opinion is that it's worth it to offer these for convenience, and allow the individual developer decide whether the performance is okay.

UserAB1236872 commented 10 years ago

An alternate implementation is to introduce a type Vecf []float32. Swizzle operators would return a Vecf of the correct len (but always with a cap of 4). To use these values as actual vectors, you'd have to call an As method,

func (v Vecf) AsVec2() Vec2f {
    v = v[:2] // Slice to right length
    return Vec2f{v[0], v[1]}
}

Swizzles on Vecf types would always return the correct array:

func (v Vecf) XYY() Vec3f {
   v = v[:4]
   return Vec3f{v[0], v[1], v[1]}
}

This is analogous to glm swizzle operators returning reference types that need to be cast to be used.

Personally, I like the array method better, but I can't deny that the slice method has some nice properties. It also makes Set methods able to increase the size of vectors, which is impossible with the above method without introducing some idiosyncratic copying semantics. Above if I wanted

func (vec *Vec2f) SetXYZ(src Vec3f)

I really don't have anywhere to go. Either the information from the "Z" will be lost, or it will return a totally new Vec3 and won't set the method receiver at all, OR it will return the new Vec3 AND set the receiver. None of these are intuitive.

Alternatively, we could introduce the Vecf type, but only use it when size promotion is required.

UserAB1236872 commented 10 years ago

I've decided to not implement swizzle functions for now. They just pollute the namespace too much. If someone really needs to reorder or downslice a Vec3 in a certain way, it's not difficult to write the function themselves. I see no reason to apply code gen for a bunch of inane functions like vec.WYZY()

I will, however, provide aliases for direct element access vec.X() vec.Y() as aliases for vec[0], vec[1], etc. This is already done for the Quat type. My benchmarks show that access in this way is about the same speed as direct array access (Go probably inlines it). I'm only going to provide XYZW style, not RST(U?) or RGBA.

dmitshur commented 10 years ago

Sounds good.