CesiumWidget.js的作用

nmj2086 / 2023-05-05 / 原文

顾名思义,CesiumWidget就是cesium小部件的意思。但是,cesium包含哪些小部件?这些小部件又有哪些意义?这些小部件是不可或缺的吗?

看《Cesium原理篇:1最长的一帧之渲染调度》讲,好像cesium的启动就是由widget来触发的?

CesiumWidget.js

startRenderLoop函数需要传入一个widget参数。这个widget参数是从哪来的??

想来这个widget应该不是一般的控件,而是应该由浏览器窗体传过来的对象?但是,又不能仅仅是浏览器的窗体,它应该还包括一些自定义的属性。

首先,CesiumWidget是一个类,它由哪些参数和方法组成?

1. 构造函数:

function CesiumWidget(container, options) {
  //>>includeStart('debug', pragmas.debug);
  if (!defined(container)) {
    throw new DeveloperError("container is required.");
  }
  //>>includeEnd('debug');

  container = getElement(container);

  options = defaultValue(options, defaultValue.EMPTY_OBJECT);

  //Configure the widget DOM elements
  var element = document.createElement("div");
  element.className = "cesium-widget";
  container.appendChild(element);

  var canvas = document.createElement("canvas");
  var supportsImageRenderingPixelated = FeatureDetection.supportsImageRenderingPixelated();
  this._supportsImageRenderingPixelated = supportsImageRenderingPixelated;
  if (supportsImageRenderingPixelated) {
    canvas.style.imageRendering = FeatureDetection.imageRenderingValue();
  }

  canvas.oncontextmenu = function () {
    return false;
  };
  canvas.onselectstart = function () {
    return false;
  };

  // Interacting with a canvas does not automatically blur the previously focused element.
  // This leads to unexpected interaction if the last element was an input field.
  // For example, clicking the mouse wheel could lead to the value in  the field changing
  // unexpectedly. The solution is to blur whatever has focus as soon as canvas interaction begins.
  function blurActiveElement() {
    if (canvas !== canvas.ownerDocument.activeElement) {
      canvas.ownerDocument.activeElement.blur();
    }
  }
  canvas.addEventListener("mousedown", blurActiveElement);
  canvas.addEventListener("pointerdown", blurActiveElement);

  element.appendChild(canvas);

  var innerCreditContainer = document.createElement("div");
  innerCreditContainer.className = "cesium-widget-credits";

  var creditContainer = defined(options.creditContainer)
    ? getElement(options.creditContainer)
    : element;
  creditContainer.appendChild(innerCreditContainer);

  var creditViewport = defined(options.creditViewport)
    ? getElement(options.creditViewport)
    : element;

  var showRenderLoopErrors = defaultValue(options.showRenderLoopErrors, true);

  var useBrowserRecommendedResolution = defaultValue(
    options.useBrowserRecommendedResolution,
    true
  );

  this._element = element;
  this._container = container;
  this._canvas = canvas;
  this._canvasClientWidth = 0;
  this._canvasClientHeight = 0;
  this._lastDevicePixelRatio = 0;
  this._creditViewport = creditViewport;
  this._creditContainer = creditContainer;
  this._innerCreditContainer = innerCreditContainer;
  this._canRender = false;
  this._renderLoopRunning = false;
  this._showRenderLoopErrors = showRenderLoopErrors;
  this._resolutionScale = 1.0;
  this._useBrowserRecommendedResolution = useBrowserRecommendedResolution;
  this._forceResize = false;
  this._clock = defined(options.clock) ? options.clock : new Clock();

  configureCanvasSize(this);

  try {
    var scene = new Scene({
      canvas: canvas,
      contextOptions: options.contextOptions,
      creditContainer: innerCreditContainer,
      creditViewport: creditViewport,
      mapProjection: options.mapProjection,
      orderIndependentTranslucency: options.orderIndependentTranslucency,
      scene3DOnly: defaultValue(options.scene3DOnly, false),
      terrainExaggeration: options.terrainExaggeration,
      shadows: options.shadows,
      mapMode2D: options.mapMode2D,
      requestRenderMode: options.requestRenderMode,
      maximumRenderTimeChange: options.maximumRenderTimeChange,
    });
    this._scene = scene;

    scene.camera.constrainedAxis = Cartesian3.UNIT_Z;

    configurePixelRatio(this);
    configureCameraFrustum(this);

    var ellipsoid = defaultValue(
      scene.mapProjection.ellipsoid,
      Ellipsoid.WGS84
    );

    var globe = options.globe;
    if (!defined(globe)) {
      globe = new Globe(ellipsoid);
    }
    if (globe !== false) {
      scene.globe = globe;
      scene.globe.shadows = defaultValue(
        options.terrainShadows,
        ShadowMode.RECEIVE_ONLY
      );
    }

    var skyBox = options.skyBox;
    if (!defined(skyBox)) {
      skyBox = new SkyBox({
        sources: {
          positiveX: getDefaultSkyBoxUrl("px"),
          negativeX: getDefaultSkyBoxUrl("mx"),
          positiveY: getDefaultSkyBoxUrl("py"),
          negativeY: getDefaultSkyBoxUrl("my"),
          positiveZ: getDefaultSkyBoxUrl("pz"),
          negativeZ: getDefaultSkyBoxUrl("mz"),
        },
      });
    }
    if (skyBox !== false) {
      scene.skyBox = skyBox;
      scene.sun = new Sun();
      scene.moon = new Moon();
    }

    // Blue sky, and the glow around the Earth's limb.
    var skyAtmosphere = options.skyAtmosphere;
    if (!defined(skyAtmosphere)) {
      skyAtmosphere = new SkyAtmosphere(ellipsoid);
    }
    if (skyAtmosphere !== false) {
      scene.skyAtmosphere = skyAtmosphere;
    }

    //Set the base imagery layer
    var imageryProvider =
      options.globe === false ? false : options.imageryProvider;
    if (!defined(imageryProvider)) {
      imageryProvider = createWorldImagery();
    }

    if (imageryProvider !== false) {
      scene.imageryLayers.addImageryProvider(imageryProvider);
    }

    //Set the terrain provider if one is provided.
    if (defined(options.terrainProvider) && options.globe !== false) {
      scene.terrainProvider = options.terrainProvider;
    }

    this._screenSpaceEventHandler = new ScreenSpaceEventHandler(canvas);

    if (defined(options.sceneMode)) {
      if (options.sceneMode === SceneMode.SCENE2D) {
        this._scene.morphTo2D(0);
      }
      if (options.sceneMode === SceneMode.COLUMBUS_VIEW) {
        this._scene.morphToColumbusView(0);
      }
    }

    this._useDefaultRenderLoop = undefined;
    this.useDefaultRenderLoop = defaultValue(
      options.useDefaultRenderLoop,
      true
    );

    this._targetFrameRate = undefined;
    this.targetFrameRate = options.targetFrameRate;

    var that = this;
    this._onRenderError = function (scene, error) {
      that._useDefaultRenderLoop = false;
      that._renderLoopRunning = false;
      if (that._showRenderLoopErrors) {
        var title =
          "An error occurred while rendering.  Rendering has stopped.";
        that.showErrorPanel(title, undefined, error);
      }
    };
    scene.renderError.addEventListener(this._onRenderError);
  } catch (error) {
    if (showRenderLoopErrors) {
      var title = "Error constructing CesiumWidget.";
      var message =
        'Visit <a href="http://get.webgl.org">http://get.webgl.org</a> to verify that your web browser and hardware support WebGL.  Consider trying a different web browser or updating your video drivers.  Detailed error information is below:';
      this.showErrorPanel(title, message, error);
    }
    throw error;
  }
}

构造函数需要传递两个参数container和options

除了构造函数,还有一个Object.defineProperties(CesiumWidget.prototype,{...})

该方法貌似也是用来给类定义属性的?跟在构造函数里使用this.xxx定义的属性有何不同。。。?反正,在这个方法里定义了许多个属性:

Object.defineProperties(CesiumWidget.prototype, {
container: { get: function () { return this._container; }, }, canvas: { get: function () { return this._canvas; }, }, creditContainer: { get: function () { return this._creditContainer; }, }, creditViewport: { get: function () { return this._creditViewport; }, }, scene: { get: function () { return this._scene; }, }, imageryLayers: { get: function () { return this._scene.imageryLayers; }, }, terrainProvider: { get: function () { return this._scene.terrainProvider; }, set: function (terrainProvider) { this._scene.terrainProvider = terrainProvider; }, }, camera: { get: function () { return this._scene.camera; }, }, clock: { get: function () { return this._clock; }, }, screenSpaceEventHandler: { get: function () { return this._screenSpaceEventHandler; }, }, targetFrameRate: { get: function () { return this._targetFrameRate; }, set: function (value) { //>>includeStart('debug', pragmas.debug); if (value <= 0) { throw new DeveloperError( "targetFrameRate must be greater than 0, or undefined." ); } //>>includeEnd('debug'); this._targetFrameRate = value; }, }, useDefaultRenderLoop: { get: function () { return this._useDefaultRenderLoop; }, set: function (value) { if (this._useDefaultRenderLoop !== value) { this._useDefaultRenderLoop = value; if (value && !this._renderLoopRunning) { startRenderLoop(this); } } }, }, resolutionScale: { get: function () { return this._resolutionScale; }, set: function (value) { //>>includeStart('debug', pragmas.debug); if (value <= 0) { throw new DeveloperError("resolutionScale must be greater than 0."); } //>>includeEnd('debug'); if (this._resolutionScale !== value) { this._resolutionScale = value; this._forceResize = true; } }, }, useBrowserRecommendedResolution: { get: function () { return this._useBrowserRecommendedResolution; }, set: function (value) { if (this._useBrowserRecommendedResolution !== value) { this._useBrowserRecommendedResolution = value; this._forceResize = true; } }, }, });

而其中有一个属性useDefaultRenderLoop,这个属性包含两个方法set和set,其中set(value)方法里调用了startRenderLoop(this)。这里面this传递的widget其实就是CesiumWidget自身。

只是我不解的是,CesiumWidget自身好像并不包含render()方法?那么它的render是由谁来完成呢?

CesiumWidget.prototype.render = function () {
  if (this._canRender) {
    this._scene.initializeFrame();
    var currentTime = this._clock.tick();
    this._scene.render(currentTime);
  } else {
    this._clock.tick();
  }
};

CesiumWidget的render函数属性是定义了的,是通过prototype的方式添加进去的。CesiumWidget的render函数其实就是调用的scene中的render()函数。接下来,就是看一下Scene中的render方法了。

参考:link

参考2:link2