cesium三维场景中点击实体如何让实体旋转平移缩放

在学习cesium中,有这么一个需求,在三维场景中,在加载的实体billbord后,点击billbord,能在三维场景中进行旋转拖拽和平移.

  • 利用fabric.js Fabric.js Javascript Canvas Library (fabricjs.com),, 一个能够让你轻而易举操作canvas的神奇的库. Fabric 不仅提供了一个虚拟canvas对象, 还有svg渲染器, 交互层, 还有一整套十分有用的工具. 这是一个完全开源的项目, MIT协议, 多年以来依靠许多贡献者共同维护
  • 在对实体billbord进行平移时,要对平移的点进行判断,是在地形上还是模型,对点位进行位置拾取和坐标转换
  • 点击实体billbord获取出来的属性中rotation是弧度,而fabric.js接受的是度,所有需要进行弧度与度之间的转换
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <script src="Cesium-1.91//Cesium.js"></script>
  <link href="Cesium-1.91/Widgets/widgets.css" rel="stylesheet">
  <style>
    body {
      margin: 0;
      padding: 0;
      overflow: hidden;
    }

    #con {
      position: absolute;
      z-index: 999;
      float: left;
      font-size: 18px;
      color: #fff;
    }

    #cesiumContainer {
      width: 100vw;
      height: 100vh;
      position: relative;
    }

    #editorcanvas {
      position: absolute;
      z-index: 999;
      right: 0;
      top: 100px;
    }

    .container {
      position: absolute !important;
      z-index: 999 !important;
      left: 50% !important;
      top: 50% !important;
      transform: translate(-50%, -50%) !important;
      display: none !important;
    }
  </style>
</head>

<body>
  <div id="con">
    <div><button onclick="addBillboard()">图标</button></div>
  </div>
  <div id="cesiumContainer">

  </div>
  <div class="container" style="position: relative;">
    <div id="CanvasContainer" style="width: 100vw;height: 100vh;">
      <canvas id="Canvas" width="800" height="800"></canvas>
    </div>
  </div>
  <script src="libs/fabric.min.js"></script>
  <script type="module">
    import { EditBoard } from './utils/zeditBoard.js';
    Cesium.Ion.defaultAccessToken ='xxxxx';
    // 实例化cesium
    let viewer = new Cesium.Viewer('cesiumContainer', {
      infoBox: false,
    });
    window.viewer = viewer;
  </div>
</body>

</html>
    import { EditBoard } from './utils/zeditBoard.js';
 if (EditBoard) {
      const Canvas = document.getElementById('Canvas');
      window.editBoard = new EditBoard(viewer, Canvas);
    }
 handler.setInputAction(movement => {
if (editBoard) {
          editBoard.update(true);
        }
        var pick = viewer.scene.pick(movement.position);
    if (pick.id && pick.id._billboard){
  let obj: any = JSON.parse(boardToolsClick);  //判断浏览器是否存在了编辑好的点,目的是为了如果第一次编辑完后,第二次继续在第一次基础上继续编辑
                    let targetPosition = obj ? obj.position : cartesian;//如果浏览器obj存在,使用obj.position(笛卡尔坐标),不存在,使用cartesian
          pick.id.show = false;
                    // 将Cartesian3坐标转换为屏幕坐标
                    const windowCoordinates =
                      Cesium.SceneTransforms.wgs84ToWindowCoordinates(
                        window.map.viewer.scene,
                        targetPosition
                      );


                  const ele = document.getElementsByClassName('container')[0];
          ele.style.cssText = `display: block !important;`;

                    const top = windowCoordinates.y
                    const left = windowCoordinates.x
                    if (editBoardRef) {
                      if (obj) {
                        editBoardRef.editBillboardImage(
                          image,//实体billboard是一个图片
                          width,//实体billboard宽度
                          height,//实体billboard高度
                          left,
                          top,
                          feature.entity,//实体billboard
                          styles,//实体billboard央视
                          windowCoordinates,//Cartesian3坐标转换为屏幕坐标
                          obj.graphic.style.rotation,//已存在的角度
                          obj.graphic.style.scale//已存在的缩放大小
                        );
                      } else {
                        editBoardRef.editBillboardImage(
                          image,
                          width,
                          height,
                          left,
                          top,
                          feature.entity,
                          styles,
                          windowCoordinates,
                        );
                      }

}
       
      }, Cesium.ScreenSpaceEventType.LEFT_CLICK);

zeditBoard.js

import {
  getImgURL, setFlagToolsClick,
  setBoardClick, getBoardClick
} from "@/utils/common";
import * as Cesium from "cesium";
import { displayCanvasContainer, radiansToDegrees, degreesToRadians } from "./const";
const fabric = window.fabric;

// fabric 默认在html 引入,如果需要可以通过npm 管理 import
export class EditBoard {
  /**
   *
   * @param {*} viewer  -cesium 实例
   * @param {*} canvas  -编辑 实例
   */
  constructor(viewer, canvas, cancontainerCanvasEl) {
    this.viewer = viewer;
    this.changePosition = {};
    this.fabricCanvas = null;
    this.currentPick = null;
    this.windowCoordinates = null;
    this.cancontainerCanvasEl = cancontainerCanvasEl;
    this.rotating = null;
    this.initEdit(canvas);
  }

  // 初始化
  initEdit(Canvas) {
    // 自定义旋转鼠标样式
    fabric.util.addListener(fabric.window, "load", function () {
      fabric.freeDrawingBrush = new fabric.PencilBrush(
        fabric.Canvas.prototype.freeDrawingBrush
      );
      fabric.freeDrawingBrush.rotateMouseCursor = `${getImgURL(
        "rotate.png"
      )},auto`; // 自定义旋转光标的URL
    });

    // 初始化Fabric.js画布
    this.fabricCanvas = new fabric.Canvas(Canvas);
    this.fabricCanvas.setWidth(this.viewer.canvas.clientWidth);
    this.fabricCanvas.setHeight(this.viewer.canvas.clientHeight);
    // 设置最小缩放比例
    this.fabricCanvas.minScale = 0.1;
    this.changeRotateIcon();
  }
  // 编辑图像的函数
  editBillboardImage(
    imageUrl,
    width = 200,
    height = 50,
    left,
    top,
    item,
    styles,
    windowCoordinates,
    objRotation,
    objScale
  ) {
    this.currentPick = item;
    this.windowCoordinates = windowCoordinates
    const { scale, rotation = 0 } = styles;
    const angle = objRotation ? radiansToDegrees(objRotation) : radiansToDegrees(rotation || 0);
    this.rotating = 360 - angle;

    // 清空Fabric.js画布
    const { fabricCanvas } = this;
    fabricCanvas.clear();
    // 加载广告牌图像
    const _this = this;
    fabric.Image.fromURL(imageUrl, function (img) {
      img.set({
        scaleX: objScale ? (width * objScale) / img.width : (width * scale) / img.width,
        scaleY: objScale ? (height * objScale) / img.height : (height * scale) / img.height,
        // scaleX:scale,
        // scaleY:scale,
        left, // 设置图片左上角的x坐标
        top, // 设置图片左上角的y坐标
        transparentCorners: false,
        cornerColor: "#fff",
        cornerStrokeColor: "#fff",
        borderColor: "blue",
        cornerSize: 6,
        // padding: 10,
        cornerStyle: "rect",
        angle: 360 - angle,
        originalAngle: 360 - angle,
        originalFlipX: img.flipX,
        originalFlipY: img.flipY,
        originX: 'center',
        originY: 'center',
      });
      // 控制是否旋转显示按钮
      img.setControlsVisibility({
        mt: false,
        mb: false,
        // ml: false,
        mr: false,
        // tr: false,
        // tl: false,
        // br: false,
        // bl: false,
        // mtr: false, // 为true则middle-top-rotate控制器可用,false则不可用。
      });

      fabricCanvas.add(img);
      fabricCanvas.setActiveObject(img);
      fabricCanvas.renderAll();
    });
    // 监听函数 可自行在这里监听调用
    fabricCanvas.on("selection:created", function (e) {
      // 创建自定义事件 这一步可不需参考
      let myEvent = new CustomEvent("editBillboardCreate", {
        detail: {
          editBoard: _this,
        }, // 可选,传递一些数据
      });
      // 触发自定义事件
      document.dispatchEvent(myEvent);
    });
    fabricCanvas.on("selection:updated", function (e) { });
    fabricCanvas.on("selection:cleared", function (e) {
      _this.update(true);
      setFlagToolsClick(false)  //存在浏览器的方法
    });
    fabricCanvas.on("mouse:down", function (e) { });

    fabricCanvas.on("object:modified", function (e) { });
    fabricCanvas.on("object:scaling", function (e) {
    });
    fabricCanvas.on("object:moving", function (e) {
      _this.changePosition = e.pointer;
    });
    fabricCanvas.on("object:rotating", function (e) {
      _this.rotating = e.target.angle;

    });
    fabricCanvas.on("mouse:wheel", function (e) { });
  }
  //屏幕坐标转三维坐标
  /**
   * 
   * @param {object} px 
   * @returns {object} cartesian三维坐标或者 false
   * 该方法根据给定的屏幕坐标px,计算出对应的三维世界坐标。该三维世界坐标可以代表一个具体的点在地图上的位置。
    使用drillPick方法来获取屏幕坐标点上所有的对象。如果该点上有一个或多个对象,方法会尝试从3D模型上拾取坐标。
    如果拾取不在3D模型上,并且地形存在,则从地形上拾取坐标。
    如果既不在3D模型上也不在地形上,则从地球椭球体上拾取坐标。
    最后返回这个点的三维世界坐标,或者在无法确定时返回false。
      transformWGS84ToCartesian方法说明:
    该方法根据给定的地理坐标(WGS84格式)计算出相应的三维世界坐标(笛卡尔坐标)。
    使用Cesium的Cartesian3.fromDegrees方法从给定的经纬度和高度计算出三维坐标。
      transformCartesianToWGS84方法说明:
    该方法根据给定的三维世界坐标(笛卡尔坐标)计算出相应的地理坐标(WGS84格式)。
    使用Cesium的Ellipsoid.WGS84和cartesianToCartographic方法将三维世界坐标转换为地理坐标。
   */
  getCatesian3FromPX(px) {
    if (window.map.viewer && px) {
      //获取场景中坐标点上所有的对象
      var picks = window.map.viewer.scene.drillPick(px);
      var cartesian = null;
      var isOn3dtiles = false,
        isOnTerrain = false;
      // drillPick
      for (let i in picks) {
        let pick = picks[i];

        if (
          (pick && pick.primitive instanceof Cesium.Cesium3DTileFeature) ||
          (pick && pick.primitive instanceof Cesium.Cesium3DTileset) ||
          (pick && pick.primitive instanceof Cesium.Model)
        ) {
          //模型上拾取
          isOn3dtiles = true;
        }
        // 3dtilset
        if (isOn3dtiles) {
          window.map.viewer.scene.pick(px); // pick
          cartesian = window.map.viewer.scene.pickPosition(px);
          if (cartesian) {
            let cartographic = Cesium.Cartographic.fromCartesian(cartesian);
            if (cartographic.height < 0) cartographic.height = 0;
            let lon = Cesium.Math.toDegrees(cartographic.longitude),
              lat = Cesium.Math.toDegrees(cartographic.latitude),
              height = cartographic.height;
            cartesian = this.transformWGS84ToCartesian({
              lng: lon,
              lat: lat,
              alt: height
            });
          }
        }
      }
      // 地形
      let boolTerrain =
        window.map.viewer.terrainProvider instanceof Cesium.EllipsoidTerrainProvider;
      // Terrain
      if (!isOn3dtiles && !boolTerrain) {
        var ray = window.map.viewer.scene.camera.getPickRay(px);
        if (!ray) return null;
        cartesian = window.map.viewer.scene.globe.pick(ray, window.map.viewer.scene);
        isOnTerrain = true;
      }
      // 地球
      if (!isOn3dtiles && !isOnTerrain && boolTerrain) {
        cartesian = window.map.viewer.scene.camera.pickEllipsoid(
          px,
          window.map.viewer.scene.globe.ellipsoid
        );
      }
      if (cartesian) {
        let position = this.transformCartesianToWGS84(cartesian);
        if (position.alt < 0) {
          cartesian = this.transformWGS84ToCartesian(position, 0.1);
        }
        return cartesian;
      }
      return false;
    }
  }
  /***
   * 坐标转换 84转笛卡尔
   * @param {Object} {lng,lat,alt} 地理坐标
   * @return {Object} Cartesian3 三维位置坐标
   */
  transformWGS84ToCartesian(position, alt) {
    if (window.map.viewer) {
      return position
        ? Cesium.Cartesian3.fromDegrees(
          position.lng || position.lon,
          position.lat,
          (position.alt = alt || position.alt),
          Cesium.Ellipsoid.WGS84
        )
        : Cesium.Cartesian3.ZERO;
    }
  }
  /***
   * 坐标转换 笛卡尔转84
   * @param {Object} Cartesian3 三维位置坐标
   * @return {Object} {lng,lat,alt} 地理坐标
   */
  transformCartesianToWGS84(cartesian) {
    if (window.map.viewer && cartesian) {
      var ellipsoid = Cesium.Ellipsoid.WGS84;
      var cartographic = ellipsoid.cartesianToCartographic(cartesian);
      return {
        lng: Cesium.Math.toDegrees(cartographic.longitude),
        lat: Cesium.Math.toDegrees(cartographic.latitude),
        alt: cartographic.height
      };
    }
  }
  update(val) {
    // 保存且写入信息
    const { fabricCanvas } = this;
    if (val) {
      if (!fabricCanvas._objects[0]) return;
      const dataURL = fabricCanvas._objects[0].toDataURL();
      // 写入数据
      const item = fabricCanvas._objects[0];

      const width = item.getScaledWidth();
      const height = item.getScaledHeight();


      if (!this.currentPick) return;
      this.currentPick._billboard._image._value = dataURL;
      // 大小
      // this.currentPick._billboard._width._value = width;
      // this.currentPick._billboard._height._value = height;
      // 屏幕坐标转经纬度
      // var pick1 = new Cesium.Cartesian2(item.left + width, item.top + height);
      // let pick1 = new Cesium.Cartesian2(
      //   item.left + width / 2,
      //   item.top + height
      // );

      let cartesian1 = this.getCatesian3FromPX(
        this.windowCoordinates
      );
      let cartesian2 = this.getCatesian3FromPX(
        new Cesium.Cartesian2(
          this.changePosition.x,
          this.changePosition.y)
      );
      // 判断是否移动了
      if (this.changePosition.x || this.changePosition.y) {
        this.currentPick._position._value = cartesian2;
      }


      // 需要存的数据(方便入库)
      const obj = {
        width,
        height,
        position: this.changePosition.x || this.changePosition.y ? cartesian2 : cartesian1,
        image: dataURL,
        angle: 360 - this.rotating,
      };
      // 改变的数据存在这个字段
      this.currentPick.changeData = obj;

      // 创建自定义事件
      let myEvent = new CustomEvent("editBillboard", {
        detail: {
          objInfo: obj,
          editBoard: this,
        }, // 可选,传递一些数据
      });

      // 触发自定义事件
      document.dispatchEvent(myEvent);
      this.destroyEdit();
      // 更新广告牌的图像
    } else {
      // 退出编辑状态
      this.destroyEdit();
    }
  }
  // 结束
  destroyEdit() {
    if (this.fabricCanvas) {
      // const node = fabricCanvas.getElement()
      // fabricCanvas.dispose() // 释放资源
      // fabricCanvas = null
      // node.remove()

      displayCanvasContainer(false);
      this.currentPick.show = true;
      this.currentPick = null;
      this.changePosition = {};
      this.rotating = null;
    }
  }
  // 设置旋转图标
  changeRotateIcon() {
    const iconURL = getImgURL("rotate.png");
    const callback = (image, isError) => {
      if (!isError) {
        fabric.Object.prototype.controls.ml = new fabric.Control({
          x: 0,
          y: -0.5,
          offsetY: -40,
          cursorStyle: "help",
          actionHandler: fabric.controlsUtils.rotationWithSnapping,
          cursorStyleHandler: fabric.controlsUtils.rotationStyleHandler,
          // 渲染图标
          render: this.renderIcon(image._element, 20),
          // 设置控制点大小
          cornerSize: 30,
        });
      }
    };
    fabric.Image.fromURL(iconURL, callback);
  }
  renderIcon(image, initialAngle) {
    return function (ctx, left, top, styleOverride, fabricObject) {
      let size = this.cornerSize;
      ctx.save();
      ctx.translate(left, top);
      ctx.rotate(
        fabric.util.degreesToRadians(fabricObject.angle + initialAngle)
      );
      ctx.drawImage(image, -size / 2, -size / 2, size, size);
      ctx.restore();
    };
  }
}
export const getFlagToolsClick = () => {
  return localStorage.getItem("flagToolsClick") === "true"
    ? true
    : false
};

export const setFlagToolsClick = (value) => {
  localStorage.setItem("flagToolsClick", value);
};
/**
 *弧度转度
 *
 * @export
 * @param {number} radians
 * @return {*} 
 */
export function radiansToDegrees(radians:number) {
  return radians * (180 / Math.PI);
}
  • 5
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值