/* Copyright 2010 Aaron J. Radke Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package cc.drx /** Constructors to easily create [[Vec]] (a 2d or 3d point in space) */ object Vec{ implicit object ParsableVec extends Parsable[Vec]{ def apply(v:String):Vec = Vec(v) } val x = new Vec(1,0,0) val y = new Vec(0,1,0) val z = new Vec(0,0,1) val zero = new Vec(0,0,0) val NaN = new Vec(Double.NaN, Double.NaN, Double.NaN) private val x2 = 2d.inv.sqrt //sqrt(xx + xx) == 1 private val x3 = 3d.inv.sqrt //sqrt(xx + xx + xx) == 1 /*unit vector where x=y (1 at 45deg) */ val xy = new Vec(x2,x2,0d) /*unit vector where x=y=z */ val xyz = new Vec(x3,x3,x3) def fill(v:Double) = new Vec(v,v,v) def rand(implicit rand:Rand):Vec = Vec.angle(Angle(rand.uniform(τ))) def rand(mag:Double)(implicit rand:Rand):Vec = Vec.polar(rand.uniform(mag), Angle(rand.uniform(τ))) def mag(m:Double) = new Vec(m,0d,0d) // 2017-07-30("use Vec(Angle(angle)) as a more typesafe method instead of this redundent construction method","v0.2.0-angle") def angle(alpha:Angle) = new Vec(alpha.cos, alpha.sin,0d) 2017-07-30("use the Angle type instead of just a double","v0.2.15") def angle(alpha:Double)(implicit d:DummyImplicit) = new Vec(alpha.cos, alpha.sin,0d) def polar(mag:Double,angle:Angle) = new Vec(angle.cos*mag, angle.sin*mag,0d) 2017-07-30("use the Angle type instead of just a double","v0.2.15") def polar(mag:Double,angle:Double)(implicit d:DummyImplicit):Vec = new Vec(angle.cos*mag, angle.sin*mag,0d) 2017-07-30("use Vec.polar(mag,angle) instead","v0.2.15") def apply(mag:Double,angle:Angle)(implicit d:DummyImplicit):Vec = new Vec(angle.cos*mag, angle.sin*mag,0d) // def apply(angle:Angle,mag:Double)(implicit d:DummyImplicit):Vec = apply(mag,angle) //def apply(angle:Double) = new Vec(angle.cos, angle.sin) 2017-07-30("use Vec.angle(angle) instead","v0.2.15") def apply(angle:Angle):Vec = new Vec(angle.cos, angle.sin, 0d) def apply(p:(Double,Double)):Vec = Vec(p._1, p._2) def apply(p:(Double,Double,Double)):Vec = Vec(p._1, p._2, p._3) def apply(ds:Seq[Double]):Vec = if(ds.size > 2) Vec(ds(0),ds(1),ds(2)) else if(ds.size > 1) Vec(ds(0),ds(1)) else if(ds.size > 0) Vec(ds(0),ds(0)) else Vec(0,0,0) def apply(str:String):Vec = Vec(Parse.split[Double](str.trim, """[\s\,\;]+""")) def apply(x:Double,y:Double):Vec = Vec(x,y,0d) def apply(x:Long,y:Long,z:Long):Vec = Vec(x.toDouble,y.toDouble,z.toDouble) } /** a 2d or 3d point in space */ case class Vec(x:Double,y:Double,z:Double) extends Vertex{ //--aliases def a = x; def b = y; def c = z //abc // def _1 = x; def _2 = y; def _3 = z //tuple notation //seems to break Dotty pattern matching??? def p = x; def q = y; def r = z //pqr def u = x; def v = y; def w = z //uvw def n = x; def e = y; def d = z //ned def θ = x; def φ = y; def ψ = z //θφψ def xx = x*x; def yy = y*y; def zz = z*z def toArray = Array(x,y,z) def toVector = Vector(x,y,z) def toPair = (x,y) def toTuple = (x,y,z) def toComplex= Complex(x,y) def round = Vec(x.round, y.round, z.round) def floor = Vec(x.floor, y.floor, z.floor) def ceil = Vec(x.ceil , y.ceil , z.ceil) //--support vertex for lines segments and polygons def last = this def segmentFrom(that:Vec) = Line(that,this) def is2d = z == 0.0 //--vec ops def +(that:Vec):Vec = new Vec(this.x+that.x , this.y+that.y , this.z+that.z) def -(that:Vec):Vec = new Vec(this.x-that.x , this.y-that.y , this.z-that.z) def x2d(that:Vec):Double = this.x*that.y - this.y*that.x /**cross product*/ def x(that:Vec):Vec = new Vec(this.y*that.z - this.z*that.y , this.z*that.x - this.x*that.z , this.x*that.y - this.y*that.x ) /**dot product*/ def *(that:Vec):Double = this.x*that.x + this.y*that.y + this.z*that.z /**element wise multiplication*/ def :*(that:Vec):Vec = Vec(this.x*that.x , this.y*that.y , this.z*that.z) // Vec(1,0) angleBetween Vec(0,1) shouldBe Angle.quarter // Vec(1,0) angleBetween Vec(-1,0) shouldBe Angle.half /**element wise square*/ def sq:Vec = Vec(x*x , y*y , z*z) /**euclidean distance*/ def dist(that:Vec):Double = (that-this).norm /*distance squared (useful for calculations that do not need the square root operation)*/ def dist2(that:Vec):Double = (that-this).normSq /**the smallest angle between two vectors*/ def angleBetween(that:Vec):Angle = Angle(((this*that)/(this.norm*that.norm)).sat(-1,1).acos) //it should never be outside of -1 and 1 but if it is numerically nudged then clip it with sat /**colinear in 3d*/ def dependent(that:Vec):Boolean = ((this*that).abs - this.norm*that.norm).abs < eps //TODO tune eps def eps = 0.001 //TODO this should be tunable maybe by typeclass:Eps? def colinear(a:Vec,b:Vec):Boolean = { val d = Array(this dist a, this dist b, a dist b).sorted //List(this,a,b).combinations(2) (d(0) + d(1) - d(2)).abs < eps //two short legs sum to equal the longest } //---scalar ops /**negate the vector*/ def unary_- :Vec = new Vec(-x , -y , -z) /**scalar multiplication*/ def *(s:Double):Vec = new Vec(x*s, y*s, z*s) /**scalar division*/ def /(s:Double):Vec = new Vec(x/s, y/s, z/s) //---set functions /**keep the angle but use a new magnitude*/ def mag(m:Double) = this*(m/norm) /**keep the magnitude but use a new angle (in the x,y axis) */ def angle(a:Angle) = Vec.polar(norm,a) //---manipulations /**Translate in the x axis*/ def tx(a:Double):Vec = Vec(x+a, y, z ) /**Translate in the y axis*/ def ty(a:Double):Vec = Vec(x, y+a, z ) /**Translate in the z axis*/ def tz(a:Double):Vec = Vec(x, y, z+a) /**Rotate around the x axis*/ def rx(a:Angle):Vec = Vec(x, a.cos*y - a.sin*z, a.sin*y + a.cos*z) /**Rotate around the y axis*/ def ry(a:Angle):Vec = Vec(a.cos*x + a.sin*z, y , -a.sin*x + a.cos*z) /**Rotate around the z axis*/ def rz(a:Angle):Vec = Vec(a.cos*x - a.sin*y, a.sin*x + a.cos*y, z) /**Rotate around the z axis relative to a point * i.e 1) translate pivot point *at* to origin 2) apply rotation *at* vec 3) shift *to* a new location*/ def rzAt(a:Angle,pivot:Vec):Vec = rzTo(a,pivot,pivot) /**Rotate around the z axis relative to a point and then move(translate) to a new point * i.e 1) translate pivot point *at* to origin 2) apply rotation *at* vec 3) shift *to* a new location*/ def rzTo(a:Angle,pivot:Vec,to:Vec):Vec = (this-pivot).rz(a) + to /**Rotate by a right Angle around the z axis (no trig)*/ def rzRight:Vec = Vec(-y, x, z) //cos => 0, sin => sgn of 90 //90.cos = 0, 90.sin = 1 /**Rotate by a negative right Angle around the z axis (no trig)*/ def rzLeft:Vec = Vec(y, -x, z) /**swap the x and y values*/ def swap:Vec = Vec(y, x, z) /**Scale the x axis*/ def sx(a:Double):Vec = Vec(x*a, y, z ) /**Scale the y axis*/ def sy(a:Double):Vec = Vec(x, y*a, z ) /**Scale the z axis*/ def sz(a:Double):Vec = Vec(x, y, z*a) /**Move/set the x value*/ def mx(a:Double):Vec = Vec(a, y, z) /**Move/set the y value*/ def my(a:Double):Vec = Vec(x, a, z) /**Move/set the z value*/ def mz(a:Double):Vec = Vec(x, y, a) //scalar manip /**norm squared (useful for calculations that do not need the square root operation)*/ 2017-07-30("use normSq instead", "0.2.13") lazy val norm2:Double = x*x + y*y + z*z lazy val normSq:Double = x*x + y*y + z*z /**norm |v|*/ lazy val norm:Double = math.sqrt(normSq) /**v/|v|*/ lazy val unit:Vec = this/norm def sat(min:Double,max:Double):Vec = {val n = norm; if(n < min) unit*min else if(n > max) unit*max else this} def sat(max:Double):Vec = sat(-max,max) /**heading assuming a xyz => ned direction, or Cartesian chalk board: x->right, y->up, z->out of board*/ lazy val heading:Angle = Angle(math.atan2(y,x)) lazy val range:Double = math.sqrt(x*x + y*y) def headingDelta(that:Vec):Angle = rz(-that.heading).heading def min:Double = x min y min z def max:Double = x max y max z //TODO are these all needed? try deprecating and see what happens 2017-07-30("use the more direct heading.rad","0.2.0-angle") lazy val rad = heading.rad 2017-07-30("use the more direct heading.pos.rad","0.2.0-angle") lazy val radPos = if(rad < 0) rad + τ else rad 2017-07-30("use the more direct heading.deg","0.2.0-angle") lazy val deg = rad*rad2deg 2017-07-30("use the more direct heading.pos.deg","0.2.0-angle") lazy val degPos = radPos*rad2deg lazy val sin:Double = y/norm //math.sin(heading) lazy val cos:Double = x/norm //math.cos(heading) lazy val tan:Double = y/x //math.tan(heading) /** Collections like map func */ def map(f:Double=>Double):Vec = Vec(f(x),f(y),f(z)) /**pointwise operations ex: a point b {_ max _}*/ def point(that:Vec)(func:(Double,Double)=>Double):Vec = Vec(func(this.x, that.x), func(this.y, that.y), func(this.z, that.z) ) /** linear interpolation (Euclidean) */ def lerp(that:Vec)(amt:Double):Vec = this + (that-this)*amt /** spherical interpolation (great circle) wikipedia://Slerp*/ def slerp(that:Vec)(amt:Double):Vec = { val omega = this angleBetween that val den = omega.sin val a = (omega*(1-amt)).sin/den val b = (omega*amt).sin/den this*a + that*b } //#---Geometric features //2017-07-30("use Circ(r) + vec or Circ(vec,r) instead", "v0.1.19") def ~(r:Double):Circ = Circ(this,r) 2017-07-30("use string ~ vec vec instead (higher priority to the string)", "v0.1.20") def ~(string:String):Text = Text(string,this) //def line(that:Vec):Line = Line(this,that) def lineTo(that:Vec):Line = Line(this,that) def ~(that:Vec):Line = Line(this,that) def ~>(that:Vec):Line = Line(this,this+that) def +-(that:Vec):Line = Line(this-that, this+that) // def hline(width:Double):Line = this +- Vec(width/2,0) // def vline(height:Double):Line = this +- Vec(0,height/2) //Vector projection of this vector onto another def projOn(that:Vec):Vec = that*((this*that)/(that*that)) //dot product multiplied by the norm of that vector (in a computationally efficient order) }