From b3470b69bf633125e11b0cd7d163694d58a229d6 Mon Sep 17 00:00:00 2001 From: zyxkad Date: Mon, 27 Nov 2023 20:21:49 -0700 Subject: [PATCH] update Object --- block.go | 5 +- box.go | 12 +-- engine.go | 43 ++++++++-- force.go | 6 +- motion.go | 33 ++++++- object.go | 202 +++++++++++++++++++++++++++++-------------- vector.go | 251 +++++++++++++++++++++++++++++++++++++++++++----------- 7 files changed, 416 insertions(+), 136 deletions(-) diff --git a/block.go b/block.go index 52038bb..c84faf2 100644 --- a/block.go +++ b/block.go @@ -14,7 +14,10 @@ const ( type Block interface { Mass() float64 + // Material returns the material of the face, nil is allowed Material(f Facing) *Material + // Outline specific the position and the maximum space of the block Outline() *Cube - Tick(dt float64) + // Tick will be called when the block need to update it's state + Tick(dt float64, o *Object) } diff --git a/box.go b/box.go index 1e117d9..29155a5 100644 --- a/box.go +++ b/box.go @@ -1,11 +1,11 @@ package molecular type Cube struct { - P Vec // Pos - S Vec // Size + P Vec3 // Pos + S Vec3 // Size } -func NewCube(pos, size Vec) (b *Cube) { +func NewCube(pos, size Vec3) (b *Cube) { if size.X < 0 { pos.X += size.X size.X = -size.X @@ -32,15 +32,15 @@ func (b *Cube) Equals(x *Cube) bool { return b.P == x.P && b.S == x.S } -func (b *Cube) Pos() Vec { +func (b *Cube) Pos() Vec3 { return b.P } -func (b *Cube) Size() Vec { +func (b *Cube) Size() Vec3 { return b.S } -func (b *Cube) EndPos() Vec { +func (b *Cube) EndPos() Vec3 { return b.P.Added(b.S) } diff --git a/engine.go b/engine.go index d2c1325..0556797 100644 --- a/engine.go +++ b/engine.go @@ -1,6 +1,8 @@ package molecular import ( + "sync" + "github.com/google/uuid" ) @@ -11,20 +13,28 @@ type Config struct { MaxSpeed float64 } +// Engine includes a sync.RWMutex which should be locked when operating global things inside a tick type Engine struct { - cfg Config - mainAnchor *Object // the main anchor object, must be invincible and unmoveable + sync.RWMutex + + // the config should not change while engine running + cfg Config + // the main anchor object must be invincible and unmovable + mainAnchor *Object objects map[uuid.UUID]*Object } -func NewEngine(cfg Config) *Engine { - return &Engine{ +func NewEngine(cfg Config) (e *Engine) { + e = &Engine{ cfg: cfg, mainAnchor: &Object{ id: uuid.Nil, attachs: make(set[*Object], 10), }, + objects: make(map[uuid.UUID]*Object, 10), } + e.objects[uuid.Nil] = e.mainAnchor + return } func (e *Engine) Config() Config { @@ -36,18 +46,35 @@ func (e *Engine) MainAnchor() *Object { } // NewObject will create an object use v7 UUID -func (e *Engine) NewObject(anchor *Object, pos Vec) *Object { +func (e *Engine) NewObject(anchor *Object, pos Vec3) *Object { + e.Lock() + defer e.Unlock() + + return e.newObjectLocked(anchor, pos) +} + +func (e *Engine) newObjectLocked(anchor *Object, pos Vec3) *Object { for i := 20; i > 0; i-- { if id, err := uuid.NewV7(); err == nil { if _, ok := e.objects[id]; !ok { - return newObject(id, anchor, pos) + return e.newAndPutObject(id, anchor, pos) } } } panic("molecular.Engine: Too many UUID generation failures") } -// Tick will call Tick on the main anchor +func (e *Engine) GetObject(id uuid.UUID) *Object { + e.RLock() + defer e.RUnlock() + + return e.objects[id] +} + +// Tick will call tick on the main anchor func (e *Engine) Tick(dt float64) { - e.mainAnchor.Tick(dt, e) + var wg sync.WaitGroup + wg.Add(1) + e.mainAnchor.tick(&wg, dt, e) + wg.Wait() } diff --git a/force.go b/force.go index 1c3f31a..840407a 100644 --- a/force.go +++ b/force.go @@ -8,7 +8,7 @@ type GravityField struct { mass float64 } -func NewGravityField(pos Vec, mass float64) *GravityField { +func NewGravityField(pos Vec3, mass float64) *GravityField { return &GravityField{ mass: mass, } @@ -23,7 +23,7 @@ func (f *GravityField) SetMass(mass float64) { } // FieldAt returns the acceleration at the distance due to the gravity field -func (f *GravityField) FieldAt(distance Vec) Vec { +func (f *GravityField) FieldAt(distance Vec3) Vec3 { l := distance.Len() if l == 0 { return ZeroVec @@ -55,7 +55,7 @@ func (f *MagnetField) SetPower(power float64) { f.power = power } -func (f *MagnetField) FieldAt(distance Vec) Vec { +func (f *MagnetField) FieldAt(distance Vec3) Vec3 { l := distance.Len() if l == 0 { return ZeroVec diff --git a/motion.go b/motion.go index 6ef634c..6c3172e 100644 --- a/motion.go +++ b/motion.go @@ -8,9 +8,36 @@ const ( C = 299792458.0 // The speed of light ) -// RelativeDeltaTime returns the actual delta time that relative to the moving object, +// See +func (e *Engine) LorentzFactor(speed float64) float64 { + return 1 / e.ReLorentzFactor(speed) +} + +// ReLorentzFactor is the reciprocal of the Lorentz Factor +// It's used for faster calculate in some specific cases +// See +func (e *Engine) ReLorentzFactor(speed float64) float64 { + if speed == 0 { + return 1 + } + n := speed / C + return math.Sqrt(1 - n*n) +} + +// Note: F = dP / dt +func (e *Engine) Momentum(mass float64, velocity Vec3) Vec3 { + return velocity.ScaledN(e.ReLorentzFactor(velocity.Len()) * mass) +} + +// AccFromForce calculate the acceleration from force +// TODO: Not sure if this is correct in SR +func (e *Engine) AccFromForce(mass float64, speed float64, force Vec3) Vec3 { + return force.ScaledN(e.ReLorentzFactor(speed) / mass) +} + +// ProperTime returns the delta time that relative to the moving object, // with given speed and the delta time relative to the observer (or the server) -func (e *Engine) RelativeDeltaTime(t float64, speed float64) float64 { +func (e *Engine) ProperTime(t float64, speed float64) float64 { if t == 0 { return 0 } @@ -23,7 +50,7 @@ func (e *Engine) RelativeDeltaTime(t float64, speed float64) float64 { if speed >= C { return math.SmallestNonzeroFloat64 } - t2 := t * math.Sqrt(1-speed*speed/(C*C)) + t2 := t * e.ReLorentzFactor(speed) if t2 == 0 { return math.SmallestNonzeroFloat64 } diff --git a/object.go b/object.go index 981f587..565d28c 100644 --- a/object.go +++ b/object.go @@ -2,40 +2,56 @@ package molecular import ( "math" + "sync" "github.com/google/uuid" ) -// Object represents an object in the physics engine -// If the object is a main anchor, it's position will always be zero, -// and it should not contains any block -// Only the objects that have GravityField can become an sub-anchor +// Object represents an object in the physics engine. type Object struct { - id uuid.UUID // a v7 UUID - attachs set[*Object] - anchor *Object - pos Vec // the position relative to the anchor - facing Vec - blocks []Block - speed Vec - field *GravityField -} - -func newObject(id uuid.UUID, anchor *Object, pos Vec) (o *Object) { + mux sync.RWMutex + e *Engine + id uuid.UUID // a v7 UUID + anchor *Object + attachs set[*Object] + blocks []Block + gcenter Vec3 // the gravity center + gfield *GravityField + pitch, yaw, roll float64 + pos Vec3 // the position relative to the anchor + tickForce Vec3 + velocity Vec3 +} + +func (e *Engine) newAndPutObject(id uuid.UUID, anchor *Object, pos Vec3) (o *Object) { + if anchor == nil { + anchor = e.mainAnchor + } o = &Object{ + e: e, id: id, - attachs: make(set[*Object], 10), anchor: anchor, + attachs: make(set[*Object], 10), pos: pos, } anchor.attachs.Put(o) + if _, ok := e.objects[id]; ok { + panic("molecular.Engine: Object id " + id.String() + " is already exists") + } + e.objects[id] = o return } +// An object's id will never be changed func (o *Object) Id() uuid.UUID { return o.id } +// Engine returns the engine of the object +func (o *Object) Engine() *Engine { + return o.e +} + // Attachs returns all the objects that anchored to this object directly func (o *Object) Attachs() []*Object { return o.attachs.AsSlice() @@ -48,35 +64,70 @@ func (o *Object) Anchor() *Object { } // Pos returns the position relative to the anchor -func (o *Object) Pos() Vec { - if o.anchor == nil { - return ZeroVec - } +func (o *Object) Pos() Vec3 { return o.pos } // SetPos sets the position relative to the anchor -func (o *Object) SetPos(pos Vec) { +func (o *Object) SetPos(pos Vec3) { o.pos = pos } -func (o *Object) Facing() Vec { +// AttachTo will change the object's anchor to another. +// The new position will be calculated at the same time. +func (o *Object) AttachTo(anchor *Object) { + if anchor == nil { + panic("molecular.Object: new anchor cannot be nil") + } if o.anchor == nil { - return ZeroVec + panic("molecular.Object: cannot attach main anchor") } - return o.facing + p1, p2 := o.AbsPos(), anchor.AbsPos() + o.pos = p1.Subbed(p2) + o.anchor.mux.Lock() + o.anchor.attachs.Remove(o) + o.anchor.mux.Unlock() + anchor.mux.Lock() + anchor.attachs.Put(o) + anchor.mux.Unlock() + o.anchor = anchor } -func (o *Object) SetFacing(f Vec) { - o.facing = f +// forEachAnchor will invoke the callback function on each anchor object. +// forEachAnchor will lock the reader locker for the anchors. +func (o *Object) forEachAnchor(cb func(*Object)) { + o.mux.RLock() + defer o.mux.RUnlock() + + if o.anchor == nil { + return + } + cb(o.anchor) + o.anchor.forEachAnchor(cb) } -func (o *Object) Speed() Vec { - return o.speed +// AbsPos returns the position relative to the main anchor +func (o *Object) AbsPos() (p Vec3) { + o.mux.RLock() + if o.anchor == nil { + o.mux.RUnlock() + return + } + p = o.pos + o.mux.RUnlock() + + o.forEachAnchor(func(a *Object) { + p.Add(a.pos) + }) + return +} + +func (o *Object) Velocity() Vec3 { + return o.velocity } -func (o *Object) SetSpeed(speed Vec) { - o.speed = speed +func (o *Object) SetVelocity(velocity Vec3) { + o.velocity = velocity } func (o *Object) Blocks() []Block { @@ -91,55 +142,78 @@ func (o *Object) AddBlock(b Block) { o.blocks = append(o.blocks, b) } -// AbsPos returns the position relative to the main anchor -func (o *Object) AbsPos() Vec { - if o.anchor == nil { - return ZeroVec - } - return o.anchor.AbsPos().Added(o.pos) +// TickForce returns the force vector that can be edit during a tick. +// You should never read/write the vector concurrently or outside a tick. +func (o *Object) TickForce() *Vec3 { + return &o.tickForce } -// AttachTo will change the object's anchor to another. -// The new position will be calculated at the same time. -func (o *Object) AttachTo(anchor *Object) { - if anchor == nil { - panic("molecular.Object: new anchor cannot be nil") - } - if o.anchor == nil { - panic("molecular.Object: cannot attach main anchor") - } - p1, p2 := o.AbsPos(), anchor.AbsPos() - o.anchor.attachs.Remove(o) - o.anchor = anchor - o.pos = p1.Subbed(p2) - anchor.attachs.Put(o) +// GField returns the gravitational field +func (o *Object) GField() *GravityField { + return o.gfield } -func (o *Object) tick(dt float64, e *Engine) { - if o.anchor == nil { - return - } +// SetGField sets the gravitational field +func (o *Object) SetGField(field *GravityField) { + o.gfield = field +} - speed := o.speed.Len() - if e.cfg.MaxSpeed > 0 { - speed = math.Min(e.cfg.MaxSpeed, speed) +func (o *Object) _tick(dt float64) { + vel := o.velocity + vl := vel.Len() + if o.e.cfg.MaxSpeed > 0 { + vl = math.Min(o.e.cfg.MaxSpeed, vl) } - dt = e.RelativeDeltaTime(dt, speed) + pt := o.e.ProperTime(dt, vl) + + // reset the state + o.tickForce = ZeroVec + { // apply the gravity + p := o.pos + o.forEachAnchor(func(a *Object) { + if a.gfield != nil { + f := a.gfield.FieldAt(p.Subbed(a.gcenter)) + f.ScaledN(dt) + o.velocity.Add(f) + } + p.Add(a.pos) + }) + } + + // TODO: update gcenter mass := 0.0 for _, b := range o.blocks { - b.Tick(dt) + b.Tick(pt, o) mass += b.Mass() } + if mass > 0 { + o.velocity.Add(o.e.AccFromForce(mass, o.velocity.Len(), o.tickForce)) + } + if o.gfield != nil { + o.gfield.SetMass(mass) + } - o.pos.Add(o.speed.ScaledN(dt)) + if vl > o.e.cfg.MinSpeed { + o.pos.Add(vel.ScaledN(dt)) + } } -// Tick will tick the object and it's attachments. -func (o *Object) Tick(dt float64, e *Engine) { - o.tick(dt, e) +// tick will tick the object itself and it's attachments concurrently +// it will call wg.Done when exit +func (o *Object) tick(wg *sync.WaitGroup, dt float64, e *Engine) { + defer wg.Done() + o.mux.RLock() for a := range o.attachs { - a.Tick(dt, e) + wg.Add(1) + go a.tick(wg, dt, e) + } + o.mux.RUnlock() + + if o.anchor != nil { // only tick on non-main anchor + o.mux.Lock() + defer o.mux.Unlock() + o._tick(dt) } } diff --git a/vector.go b/vector.go index ebe7df0..db7692c 100644 --- a/vector.go +++ b/vector.go @@ -5,62 +5,66 @@ import ( "math" ) -type Vec struct { +type Vec3 struct { X, Y, Z float64 } var ( - ZeroVec Vec - OneVec Vec = Vec{1, 1, 1} - NegOneVec Vec = Vec{-1, -1, -1} + ZeroVec Vec3 + OneVec Vec3 = Vec3{1, 1, 1} + NegOneVec Vec3 = Vec3{-1, -1, -1} ) -func (v *Vec) Clone() *Vec { - return &Vec{ +func (v *Vec3) Clone() *Vec3 { + return &Vec3{ X: v.X, Y: v.Y, Z: v.Z, } } -func (v Vec) String() string { - return fmt.Sprintf("Vec(%v, %v, %v)", v.X, v.Y, v.Z) +func (v Vec3) String() string { + return fmt.Sprintf("Vec3(%v, %v, %v)", v.X, v.Y, v.Z) } -func (v Vec) XYZ() (x, y, z float64) { +func (v Vec3) XYZ() (x, y, z float64) { return v.X, v.Y, v.Z } -func (v Vec) IsZero() bool { +func (v Vec3) IsZero() bool { return v.X == 0 && v.Y == 0 && v.Z == 0 } -func (v Vec) Len() float64 { +func (v Vec3) Equals(u Vec3) bool { + return v.X == u.X && v.Y == u.Y && v.Z == u.Z +} + +func (v Vec3) Len() float64 { return math.Sqrt(v.SqLen()) } // Squared length -func (v Vec) SqLen() float64 { +func (v Vec3) SqLen() float64 { return v.X*v.X + v.Y*v.Y + v.Z*v.Z } -func (v Vec) Abs() Vec { - return Vec{ +func (v Vec3) Abs() Vec3 { + return Vec3{ X: math.Abs(v.X), Y: math.Abs(v.Y), Z: math.Abs(v.Z), } } -func (v *Vec) Map(m func(float64) float64) *Vec { +func (v *Vec3) Map(m func(float64) float64) *Vec3 { v.X = m(v.X) v.Y = m(v.Y) v.Z = m(v.Z) return v } -func (v Vec) Mapped(m func(float64) float64) Vec { - return Vec{ +func (v Vec3) Mapped(m func(float64) float64) Vec3 { + return Vec3{ X: m(v.X), Y: m(v.Y), Z: m(v.Z), @@ -68,7 +72,7 @@ func (v Vec) Mapped(m func(float64) float64) Vec { } // Negate is a shortcut of ScaleN(-1) -func (v *Vec) Negate() *Vec { +func (v *Vec3) Negate() *Vec3 { v.X = -v.X v.Y = -v.Y v.Z = -v.Z @@ -76,68 +80,68 @@ func (v *Vec) Negate() *Vec { } // Negated is a shortcut of ScaledN(-1) -func (v Vec) Negated() Vec { - return Vec{ +func (v Vec3) Negated() Vec3 { + return Vec3{ X: -v.X, Y: -v.Y, Z: -v.Z, } } -func (v *Vec) Add(u Vec) *Vec { +func (v *Vec3) Add(u Vec3) *Vec3 { v.X += u.X v.Y += u.Y v.Z += u.Z return v } -func (v Vec) Added(u Vec) Vec { - return Vec{ +func (v Vec3) Added(u Vec3) Vec3 { + return Vec3{ X: v.X + u.X, Y: v.Y + u.Y, Z: v.Z + u.Z, } } -func (v *Vec) Sub(u Vec) *Vec { +func (v *Vec3) Sub(u Vec3) *Vec3 { v.X -= u.X v.Y -= u.Y v.Z -= u.Z return v } -func (v Vec) Subbed(u Vec) Vec { - return Vec{ +func (v Vec3) Subbed(u Vec3) Vec3 { + return Vec3{ X: v.X - u.X, Y: v.Y - u.Y, Z: v.Z - u.Z, } } -func (v *Vec) Scale(u Vec) *Vec { +func (v *Vec3) Scale(u Vec3) *Vec3 { v.X *= u.X v.Y *= u.Y v.Z *= u.Z return v } -func (v Vec) Scaled(u Vec) Vec { - return Vec{ +func (v Vec3) Scaled(u Vec3) Vec3 { + return Vec3{ X: v.X * u.X, Y: v.Y * u.Y, Z: v.Z * u.Z, } } -func (v *Vec) ScaleN(n float64) *Vec { +func (v *Vec3) ScaleN(n float64) *Vec3 { v.X *= n v.Y *= n v.Z *= n return v } -func (v Vec) ScaledN(n float64) Vec { - return Vec{ +func (v Vec3) ScaledN(n float64) Vec3 { + return Vec3{ X: v.X * n, Y: v.Y * n, Z: v.Z * n, @@ -145,7 +149,7 @@ func (v Vec) ScaledN(n float64) Vec { } // Normalize make the length of the vector to 1 and keep the current direction. -func (v *Vec) Normalize() *Vec { +func (v *Vec3) Normalize() *Vec3 { if v.IsZero() { v.X = 1 } else { @@ -155,14 +159,14 @@ func (v *Vec) Normalize() *Vec { } // Normalized returns a vector of length 1 facing the direction of u with the same angle. -func (v Vec) Normalized() Vec { +func (v Vec3) Normalized() Vec3 { if v.IsZero() { - return Vec{1, 0, 0} + return Vec3{1, 0, 0} } return v.ScaledN(1 / v.Len()) } -func (v Vec) Dot(u Vec) float64 { +func (v Vec3) Dot(u Vec3) float64 { return v.X*u.X + v.Y*u.Y + v.Z*u.Z } @@ -172,7 +176,7 @@ func (v Vec) Dot(u Vec) float64 { // |/ // --+--> // | Y -func (v Vec) AngleX() float64 { +func (v Vec3) AngleX() float64 { return math.Atan2(v.Z, v.Y) } @@ -182,7 +186,7 @@ func (v Vec) AngleX() float64 { // |/ // --+--> // | Z -func (v Vec) AngleY() float64 { +func (v Vec3) AngleY() float64 { return math.Atan2(v.X, v.Z) } @@ -192,12 +196,12 @@ func (v Vec) AngleY() float64 { // |/ // --+--> // | X -func (v Vec) AngleZ() float64 { +func (v Vec3) AngleZ() float64 { return math.Atan2(v.Y, v.X) } // Rotate around x-axis -func (v *Vec) RotateX(angle float64) *Vec { +func (v *Vec3) RotateX(angle float64) *Vec3 { s, c := math.Sincos(angle) v.Y = v.Y*c - v.Z*s v.Z = v.Y*s + v.Z*c @@ -205,7 +209,7 @@ func (v *Vec) RotateX(angle float64) *Vec { } // Rotate around y-axis -func (v *Vec) RotateY(angle float64) *Vec { +func (v *Vec3) RotateY(angle float64) *Vec3 { s, c := math.Sincos(angle) v.X = v.Y*s + v.Z*c v.Z = v.Y*c - v.Z*s @@ -213,7 +217,7 @@ func (v *Vec) RotateY(angle float64) *Vec { } // Rotate around z-axis -func (v *Vec) RotateZ(angle float64) *Vec { +func (v *Vec3) RotateZ(angle float64) *Vec3 { s, c := math.Sincos(angle) v.X = v.X*c - v.Y*s v.Y = v.X*s + v.Y*c @@ -221,9 +225,9 @@ func (v *Vec) RotateZ(angle float64) *Vec { } // Rotate around x-axis -func (v Vec) RotatedX(angle float64) Vec { +func (v Vec3) RotatedX(angle float64) Vec3 { s, c := math.Sincos(angle) - return Vec{ + return Vec3{ X: v.X, Y: v.Y*c - v.Z*s, Z: v.Y*s + v.Z*c, @@ -231,9 +235,9 @@ func (v Vec) RotatedX(angle float64) Vec { } // Rotate around y-axis -func (v Vec) RotatedY(angle float64) Vec { +func (v Vec3) RotatedY(angle float64) Vec3 { s, c := math.Sincos(angle) - return Vec{ + return Vec3{ X: v.Y*s + v.Z*c, Y: v.Y, Z: v.Y*c - v.Z*s, @@ -241,16 +245,161 @@ func (v Vec) RotatedY(angle float64) Vec { } // Rotate around z-axis -func (v Vec) RotatedZ(angle float64) Vec { +func (v Vec3) RotatedZ(angle float64) Vec3 { s, c := math.Sincos(angle) - return Vec{ + return Vec3{ X: v.X*c - v.Y*s, Y: v.X*s + v.Y*c, Z: v.Z, } } -// Volume returns X * Y * Z -func (v Vec) Volume() float64 { - return v.X * v.Y * v.Z +type Vec4 struct { + T, X, Y, Z float64 +} + +func (v Vec4) String() string { + return fmt.Sprintf("Vec4(%v, %v, %v, %v)", v.T, v.X, v.Y, v.Z) +} + +func (v Vec4) XYZ() (x, y, z float64) { + return v.X, v.Y, v.Z +} + +func (v Vec4) IsZero() bool { + return v.T == 0 && v.X == 0 && v.Y == 0 && v.Z == 0 +} + +func (v Vec4) Equals(u Vec4) bool { + return v.T == u.T && v.X == u.X && v.Y == u.Y && v.Z == u.Z +} + +func (v Vec4) Len() float64 { + return math.Sqrt(v.SqLen()) +} + +// Squared length +func (v Vec4) SqLen() float64 { + return v.T*v.T + v.X*v.X + v.Y*v.Y + v.Z*v.Z +} + +func (v Vec4) To3() Vec3 { + return Vec3{ + X: v.X, + Y: v.Y, + Z: v.Z, + } +} + +func (v Vec4) Abs() Vec4 { + return Vec4{ + T: math.Abs(v.T), + X: math.Abs(v.X), + Y: math.Abs(v.Y), + Z: math.Abs(v.Z), + } +} + +func (v *Vec4) Map(m func(float64) float64) *Vec4 { + v.T = m(v.T) + v.X = m(v.X) + v.Y = m(v.Y) + v.Z = m(v.Z) + return v +} + +func (v Vec4) Mapped(m func(float64) float64) Vec4 { + return Vec4{ + T: m(v.T), + X: m(v.X), + Y: m(v.Y), + Z: m(v.Z), + } +} + +// Negate is a shortcut of ScaleN(-1) +func (v *Vec4) Negate() *Vec4 { + v.T = -v.T + v.X = -v.X + v.Y = -v.Y + v.Z = -v.Z + return v +} + +// Negated is a shortcut of ScaledN(-1) +func (v Vec4) Negated() Vec4 { + return Vec4{ + T: -v.T, + X: -v.X, + Y: -v.Y, + Z: -v.Z, + } +} + +func (v *Vec4) Add(u Vec4) *Vec4 { + v.T += u.T + v.X += u.X + v.Y += u.Y + v.Z += u.Z + return v +} + +func (v Vec4) Added(u Vec4) Vec4 { + return Vec4{ + T: v.T + u.T, + X: v.X + u.X, + Y: v.Y + u.Y, + Z: v.Z + u.Z, + } +} + +func (v *Vec4) Sub(u Vec4) *Vec4 { + v.T -= u.T + v.X -= u.X + v.Y -= u.Y + v.Z -= u.Z + return v +} + +func (v Vec4) Subbed(u Vec4) Vec4 { + return Vec4{ + T: v.T - u.T, + X: v.X - u.X, + Y: v.Y - u.Y, + Z: v.Z - u.Z, + } +} + +func (v *Vec4) Scale(u Vec4) *Vec4 { + v.T *= u.T + v.X *= u.X + v.Y *= u.Y + v.Z *= u.Z + return v +} + +func (v Vec4) Scaled(u Vec4) Vec4 { + return Vec4{ + T: v.T * u.T, + X: v.X * u.X, + Y: v.Y * u.Y, + Z: v.Z * u.Z, + } +} + +func (v *Vec4) ScaleN(n float64) *Vec4 { + v.T *= n + v.X *= n + v.Y *= n + v.Z *= n + return v +} + +func (v Vec4) ScaledN(n float64) Vec4 { + return Vec4{ + T: v.T * n, + X: v.X * n, + Y: v.Y * n, + Z: v.Z * n, + } }