计算机图形学&Web前端笔记-图形平移放缩原理及实现(two.js鼠标事件适用所有渲染)

在two.js中,只提供了svg渲染时的鼠标事件,而canvas和webgl并没有提供,这样就对本人造成了很大的困扰,因此学习了下计算机图形学相关的知识,实现了利用two.js绘图在canvas、svg、webgl渲染方式下,放缩或移动场景,还能使用鼠标对其进行点击交互。

 

下面先演示下截图:

点击绿色的矩形:

从上面的图可以看出变色了。

下面滚动滚轮及拖动下场景:

同样点击刚刚那个矩形:

还是可以被找到,并且改变颜色。

 

下面先来说下2d图形中平移和放缩的原理(非常重要,下面的代码实现都需要用到):

平移:

放缩:

放缩+平移:

可见是先放缩,然后再进行平移。

总结下:

a b c
d e f
g h i

其中a为:水平放缩;

其中c为:水平位移;

其中e为:垂直放缩;

其中f为:垂直位移。

 

下面来看下代码是如何实现的:

在two.js,two对象:

scene中存在_matrix里面存储了Float32Array(9),这里就对应了上面的3*3矩阵,上面的就是单位矩阵,

1 0 0
0 1 0
0 0 1

 

当放缩或移动后:

创建这几个矩形的代码:

代码如下:

function createBtn(id, x, y, weight, color) {

  rect = two.makeRectangle(x, y, 200, 200);
  rect.noStroke();
  rect.fill = color;
  rect.myId = id;

  map.set(rect, weight);
}
这些rect都存储在map中,如何在:
$stage.bind('mousedown', function(event){

  isPressed = true;
  originalPositionX = event.clientX;
  originalPositionY = event.clientY;

  let x = event.clientX;
  let y = event.clientY;

  let letX = (rect._translation._x / 2 * (two.scene._matrix.elements[0]) + two.scene._matrix.elements[2]);
  let letY = (rect._translation._y / 2 * (two.scene._matrix.elements[4]) + two.scene._matrix.elements[5]);
  let letWidth = rect._width * two.scene._matrix.elements[0];
  let letHeight = rect._height *two.scene._matrix.elements[4];

  // if(x > letX &&
  //   y > letY &&
  //   x < letX + letWidth &&
  //   y < letY + letHeight
  // ){
  //
  //   console.log("find it");
  // }

  // console.log("坐标 x=" + x + "  y=" + y);

  console.log(two);
  for(let value of map){

    let xOffset = value[0]._width / 2;
    let yOffset = value[0]._height / 2;
    // console.log("xOffset:" + xOffset);
    // console.log("yOffset:" + yOffset);

    // console.log(value[0])
    let letX = ((value[0]._translation._x - xOffset) * (two.scene._matrix.elements[0]) + two.scene._matrix.elements[2]);
    let letY = ((value[0]._translation._y - yOffset) * (two.scene._matrix.elements[4]) + two.scene._matrix.elements[5]);
    let letWidth = value[0]._width * two.scene._matrix.elements[0];
    let letHeight = value[0]._height * two.scene._matrix.elements[4];

    // console.log("id:" + value[0].myId);
    // console.log("letX:" + letX + "  letY:" + letY);
    // console.log("letWidth: " + letWidth + "  letHeight:" + letHeight);
    // console.log("");

    if(x > letX &&
      y > letY &&
      x < letX + letWidth &&
      y < letY + letHeight
    ){

      let r = Math.round(Math.random() * 255);
      let g = Math.round(Math.random() * 255);
      let b = Math.round(Math.random() * 255);

      let rgbStr = "rgb(" + r + "," + g + "," + b + ")";
      value[0].fill = rgbStr;
      console.log("find it " + value[0].myId);
      break;
    }
  }

  // console.log("---------------------------------");

});

代码按下时对map进行遍历,如何将其进行转换后的坐标以及宽度进行矩阵变化即可:

这里有一点要说明的two.js中画矩形如果是从200, 200开始,那么这个200,200将会是矩形的

中心点,并不是左上角的点,所以这里有功offset.

 

鼠标拖动,滚轮放缩相关的代码:

(function(Two){

  let _ = Two.Utils;

  let Surface = function(object) {

    this.object = object;

  };

  _.extend(Surface.prototype, {

    limits: function(min, max) {

      let min_exists = !_.isUndefined(min);
      let max_exists = !_.isUndefined(max);

      if (!max_exists && !min_exists) {
        return { min: this.min, max: this.max };
      }

      this.min = min_exists ? min : this.min;
      this.max = max_exists ? max : this.max;

      return this;

    },

    apply: function(px, py, s) {
      this.object.translation.set(px, py);
      this.object.scale = s;
      return this;
    }

  });

  let ZUI = Two.ZUI = function(group, domElement) {

    this.limits = {

      scale: ZUI.Limit.clone(),
      x: ZUI.Limit.clone(),
      y: ZUI.Limit.clone()
    };

    this.viewport = domElement || document.body;
    this.viewportOffset = {
      matrix: new Two.Matrix()
    };

    this.surfaceMatrix = new Two.Matrix();

    this.surfaces = [];
    this.reset();
    this.updateSurface();

    this.add(new Surface(group));

  };

  _.extend(ZUI, {

    Surface: Surface,

    Clamp: function(v, min, max) {
      return Math.min(Math.max(v, min), max);
    },

    Limit: {

      min: -Infinity,
      max: Infinity,
      clone: function() {

        let result = {};
        for (let k in this) {

          result[k] = this[k];
        }
        return result;
      }
    },

    TranslateMatrix: function(m, x, y) {

      m.elements[2] += x;
      m.elements[5] += y;
      return m;
    },

    PositionToScale: function(pos){

      return Math.exp(pos);
    },

    ScaleToPosition: function(scale){

      return Math.log(scale);
    }

  });

  _.extend(ZUI.prototype, {

    constructor: ZUI,

    add: function(surface){

      this.surfaces.push(surface);
      let limits = surface.limits();
      this.addLimits(limits.min, limits.max);
      return this;
    },

    addLimits: function(min, max, type) {

      type = type || 'scale';

      if (!_.isUndefined(min)){

        if(this.limits[type].min){

          this.limits[type].min = Math.max(min, this.limits[type].min);
        }
        else{

          this.limits[type].min = min;
        }
      }

      if(_.isUndefined(max)){

        return this;
      }

      if(this.limits[type].max){

        this.limits[type].max = Math.min(max, this.limits[type].max);
      }
      else{

        this.limits[type].max = max;
      }

      return this;

    },

    clientToSurface: function(x, y) {

      this.updateOffset();
      let m = this.surfaceMatrix.inverse();
      let n = this.viewportOffset.matrix.inverse().multiply(x, y, 1);
      return m.multiply.apply(m, _.toArray(n));
    },

    surfaceToClient: function(v) {

      this.updateOffset();
      let vo = this.viewportOffset.matrix.clone();
      let sm = this.surfaceMatrix.multiply.apply(this.surfaceMatrix, _.toArray(v));
      return vo.multiply.apply(vo, _.toArray(sm));
    },

    graphicMove: function(clientX, clientY){

      let dx = clientX;
      let dy = clientY;
      this.translateSurface(dx, dy);
      return this;
    },

    zoomBy: function(byF, clientX, clientY){

      let s = ZUI.PositionToScale(this.zoom + byF);
      this.zoomSet(s, clientX, clientY);
      return this;
    },

    zoomSet: function(zoom, clientX, clientY) {

      let newScale = this.fitToLimits(zoom);
      this.zoom = ZUI.ScaleToPosition(newScale);

      if (newScale === this.scale) {

        return this;
      }

      let sf = this.clientToSurface(clientX, clientY);
      let scaleBy = newScale / this.scale;

      this.surfaceMatrix.scale(scaleBy);
      this.scale = newScale;

      let c = this.surfaceToClient(sf);
      let dx = clientX - c.x;
      let dy = clientY - c.y;
      this.translateSurface(dx, dy);

      return this;
    },

    translateSurface: function(x, y) {

      ZUI.TranslateMatrix(this.surfaceMatrix, x, y);
      this.updateSurface();
      return this;
    },

    updateOffset: function() {

      let rect = this.viewport.getBoundingClientRect();
      _.extend(this.viewportOffset, rect);

      this.viewportOffset.left -= document.body.scrollLeft;
      this.viewportOffset.top -= document.body.scrollTop;

      this.viewportOffset.matrix
        .identity()
        .translate(this.viewportOffset.left, this.viewportOffset.top);

      return this;

    },

    updateSurface: function() {

      let e = this.surfaceMatrix.elements;
      for(let i = 0; i < this.surfaces.length; i++){

        this.surfaces[i].apply(e[2], e[5], e[0]);
      }
      return this;
    },

    reset: function() {
      this.zoom = 0;
      this.scale = 1.0;
      this.surfaceMatrix.identity();
      return this;
    },

    fitToLimits: function(s) {
      return ZUI.Clamp(s, this.limits.scale.min, this.limits.scale.max);
    }

  });

})
((typeof global !== 'undefined' ? global : (this || window)).Two);

整个项目打包下载地址:

https://github.com/fengfanchen/frontUI/tree/master/mouseDemo

  • 51
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

IT1995

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值