可视化交互之vector

Vector在数学中的含义是向量(矢量),也是在很多计算中常存在的名词,相信很多地方也都可以见到有关Vector的身影,但是大多数人会在项目中去不知不觉使用,而不是单独抽出来形成依赖,这也导致了很多人忽视了它的存在,这一节中主要是讲解了Vector的相关基础操作以及可以如何利用Vector来使得项目更简洁。

如何表示Vector

以平面为例

,这里的

vec(a) 对应了一个坐标

(x=1,y=2) ,但是在定义上,Vector是具有方向与大小的,便出现了

(x-0=x,y-0=y)

对应着Vector的减法公式

可以发现顿时以某个Vector自身,出现了一个坐标系,Vector在坐标系内以轴为参考系,可以进行平移,旋转等操作

同一表示类型的Vector会共用坐标系平面向量百度百科

于是无论是在2d还是3d中,所有的对象都会存在向量的概念。

向量的存在

    1. 坐标(世界坐标,屏幕坐标,NDC坐标…)
    1. 颜色空间(RGB,CMYK,HSV…)
    1. 向量支持(力,速度…)

例如:

const Color = function(r,g,b){
  this.r = r
  this.g = g
  this.b = b
  //r,g,b 范围[0~255]
}

这是一个常用的Color类,包含了 RGB 三个属性,当我们通过这种方式来设置颜色值,则可以很轻松对某一单独色彩通道进行操作或者多通道进行协同操作

Vector的相关操作

首先看看Vector的可用操作,加减乘除?

Vector.prototype = {
    negative: function() {//反向量
      return new Vector(-this.x, -this.y, -this.z);
    },
    add: function(v) {//向量和
      if (v instanceof Vector) return new Vector(this.x + v.x, this.y + v.y, this.z + v.z);
      else return new Vector(this.x + v, this.y + v, this.z + v);
    },
    subtract: function(v) {//向量差
      if (v instanceof Vector) return new Vector(this.x - v.x, this.y - v.y, this.z - v.z);
      else return new Vector(this.x - v, this.y - v, this.z - v);
    },
    divide: function(v) {//向量除
      if (v instanceof Vector) return new Vector(this.x / v.x, this.y / v.y, this.z / v.z);
      else return new Vector(this.x / v, this.y / v, this.z / v);
    },
    dot: function(v) {//点积
      return this.x * v.x + this.y * v.y + this.z * v.z;
    },
    length: function() {//返回长度
      return Math.sqrt(this.dot(this));
    },
    unit: function() {//单位向量
      return this.divide(this.length());
    }
  };

实现一个数据完全由Vector支撑的交互

目标:实现一个[50*50]的矩形,并且当有点击事件后,会产生弹性放大到[100,100]

    1. 创建矩形类
/*
  s1-s4都是为 vector2
*/
const Rect = function (s1,s2,s3,s4){
    this.s1 = s1
    this.s2 = s2
    this.s3 = s3
    this.s4 = s4
}

于是我们就有了一个矩形,四个顶点都是以vector2的实例

有了点,那幺我们就需要考虑如何去让它变大,屏幕中的显示都是以像素为整单位,于是可以利用 vector.add 来为为每一个顶点进行导航,由于50-100是很均匀的,而且每个点的变化量都相同

/add.png)

Rect.prototype.move = function (a,b,c,d){
  const {s1, s2, s3, s4} = this
  this.s1 = s1.add(a)
  this.s2 = s1.add(c)
  this.s3 = s1.add(d)
  this.s4 = s1.add(b)
}

这样一来,我们就实现了我们所需要的主要功能,可以很轻易让矩形进行形变 接下来就是核心的 vector 插值部分

Rect.prototype.lerpTo = function (_s1,_s2,_s3,_s4,t){
  const {s1, s2, s3, s4} = this
  var dir1 = _s1.subtract(s1).divide(t)
  var dir2 = _s1.subtract(s2).divide(t)
  var dir3 = _s1.subtract(s3).divide(t)
  var dir4 = _s1.subtract(s4).divide(t)
  this.move(dir1,dir2,dir3,dir4)
}

这里利用 vector.subtract 来讲当前顶点的坐标点进行向量差,由于向量的方向性,通过 rect.move 来对各个顶点进行分别处理。 动画的部分, requestAnimationFrame 将会是你的优秀助手。 整体的过渡部分是利用了 vector.subtract(s1).divide(t) 来计算的,对应的公式是 S=vt(匀速运动) 也可以被称为均匀插值,但是我们想让它在动的过程中妖娆一点,于是便有了如二次项插值,多次项插值等插值方法。

单独的运动效果出来了,空洞的骨架,当然也需要色彩的肉体。 我们知道渲染的管道中,是有顶点着色与片元着色,顶点的着色部分我们可以使用矩形的四个顶点 (s1,s2,s3,s4) 来作为顶点的着色 (red,green,yellow,blue) 。通过前面的方法,将颜色值映射到坐标中形成色彩向量。 red->[255,0,0] , green->[0,255,0] , yellow->[255,255,0] , blue->[0,0,255] , 拥有了这些顶点的着色材料,接下来我们就可以模拟计算机的片元着色,通过向量的插值来计算出顶点到顶点之间路径上的每一个像素点的色彩值,一般是采一个点往其他点进行计算,也许无法知道这个路径上有多少的像素点,那幺 vector.unit 就可以为你计算出路径上的单位向量,模拟出最小的向量增量。

仿弹性形变

人们在欣赏动画的时候,往往更会偏爱于常见的一些动态效果,比如匀加速,又或者不断减速再反向加速,可能有些人比较难理解这个过程中的变化,但是一般来说,动态的效果绝大多数都是物理动效,比如匀变速运动,又或者弹性运动,当运动学碰到了力学,就会互相催动,从而产生各种效果。

比如我们要做这个仿弹性的效果,我们需要对比的实物就可以是弹簧,什幺力的可突变与不可突变性,统统放弃不考虑,撸就行了,一个公式 F=kx (F是力的大小,k是弹性系数,x是长度)

const k = 0.2 //设置弹性系数
const F =function(v = new Vector(0,0)){
    this.x = v.x
    this.y = v.y
    this.getF = v.length()*k
}
F.prototype.getA = function(){
    //通过力来对物体进行加速度求解,通过加速度来处理物体的运动状态
    //运动公式 F =ma ; v=v0+at
}

这里我把力的大小与 xy 方向的力进行了拆分,是由于Vector在各个方向上的分量既有协调性,也有独立性,这也为我们在做整体分析的时候可以把很繁杂的效果拆分来考虑。