范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文
国学影视

以终为始,设计优先看DataWind数字孪生3D地图事件系统设计

  作者:梁煜其
  智能数据洞察 DataWind 是支持千亿级别数据自助分析的一站式数据分析与协作平台。从数据接入、数据整合,到查询、分析,最终以数据门户、数字大屏、管理驾驶舱的可视化形态呈现给业务用户,让数据发挥价值。详情请看: https://www.volcengine.com/product/datawind
  DataWind 沉淀了丰富美观的行业 Demo,包含分析型数据看板与酷炫动态大屏,为用户制作优质看板和大屏提供参考。其中数字孪生是特色之一,数字孪生指将物理世界中的物体、人、自然、环境、生产制造过程等要素一一复制,形成 全量数字三维模型 ,结合科学的数学模型与智能算法 ,分析和仿真微观世界和宏观世界的变化过程,解决城市管理或者工业制造过程中资源分配不合理带来的各种问题。
  接下来给大家带来数字孪生中,进行三维场景事件系统重构所使用的 xGis 图形库。 一、背景
  xGis 是一个基于数据驱动,简单、易用、完备的 web 3D 地图库。XGis 专注于地理空间数据可视化,拥有炫酷展示效果的同时兼顾高性能/高渲染质量,在内/外部多个项目落地使用。提供一个小 Tips: xGis 可以灵活支持十余种 自定义图层的增删改查 ,组成想要的复杂场景。
  二、问题
  随着各种各样新图层的累加,出现了如下问题: 业务方:不同图层交互字段配置不统一
  有些图层支持交互,有些不支持交互,且交互字段根据不同图层开发同学决定。业务方:图层暴露的内置事件逻辑不符合场景需求
  场景:双击触发地图钻取的交互能不能改成单击?
  场景:可不可以让蓝色柱子的 hover 样式,当 value 大于 10 时为绿色,小于 10 时为红色?开发者:不同图层关于交互模块重复开发,开发成本较大
  场景:新图层开发完毕,要支持交互的话还得改交互模块的逻辑。交互性能:由于各模块重复开发,导致事件监听里逻辑判断复杂冗余 /** * 老的事件监听 */ handleEvent = (e)=>{   // 遍历所有图层   layerManagers.layers.forEach((layer)=>{    const objects = []     // 支持交互绑定的     if (layer instanceof mapLayer){ // 不同图层需要casebycase处理       objects.push(mapLayer.meshGroup)     }else if (layer instanceof bubbleLayer){        objects.push(mapLayer.group)     }     // 其他如柱状图没有写逻辑,则不支持交互      const objIntersects =  intersectObjects(objects,mouse)      // 检测成功     if (objIntersects[0]){       if (e.type === "mousemove"){         layer.emit("hover",e) // 图层只要支持交互就emit,不管当前eventType是否有用户监听       } else (e.type === "click"){         layer.emit("select",e)       }     }else {     if (e.type === "mousemove"){         layer.emit("hover", null)       } else (e.type === "click"){         layer.emit("select", null)       }     }     }) } 三、重构思路
  3.1 事件分类
  首先梳理一下三维场景有哪些事件触发主体(Selector),不同的事件主体的后续逻辑也不相同(Trigger / Effect)。我们这里根据事件触发主体作为分类,每个主体下事件触发类型又由鼠标交互、生命周期两大类组成。 3.1.1 containter
  containter 就是用户传入的绑定 DOM 容器,主要场景是监听 resize 后 auotFit:  /**    * 劫持监听 containerDom resize    * ! MutationObserver 不行,因为config的attributes观察目标属性变化,是指css属性变化,width:100%,虽然宽度px是变了,但是这个属性没变    * ! 监听window.resize() 也不合理,逻辑覆盖面太小    */   private __initContainerDomResizeHandle() {     const { autoSize, containerDom } = this.props;      if (autoSize) {       this.containerDomResizeObserver = new ResizeObserver(() => {         this.__containerDomResizeHandler();       });       this.containerDomResizeObserver.observe(containerDom);     }   }
  也支持用户监听鼠标交互(绑定 DOM ),举个例子
  鼠标移出地图容器时隐藏当前地图内的 Popup: gis.on("mouseout", (ev) => {   popup.set({visible:false}) }); 3.1.2 context
  监听 WebGL 上下文是否丢失,抛出报错做一些容错处理:  canvasDom.addEventListener("webglcontextlost", this._contextLost, false); //上下文丢失事件  canvasDom.addEventListener( // 上下文恢复事件       "webglcontextrestored",       this._contextRestored,       false     ); 3.1.3 components
  监听第三方组件,即地图辅助组件,本质就是独立 DOM,特性为 HUD(始终正对于屏幕,不影响交互,类似锚点),主要事件为 DOM 的鼠标交互。 layerControl:控制图层显隐 zoomControl:控制图层缩放 scaleControl:显示当前地图比例尺 popup:标注信息窗口,用于展示地图要素的属性信息 3.1.4 controls
  监听地图控制器,即实现地图的平移、缩放、旋转、倾斜等,主要事件为 DOM 的鼠标交互及生命周期,其中 DOM 的鼠标交互部分底层我们参考飞行器姿态,使用 Pointer Events API 实现,可以更好的适配鼠标(Mouse)、触摸(Touch)和触控笔(Pen)场景,这里不再展开。
  3.1.5 window
  监听 window,有些库对于 resize 的监听放到了这里: window.addEventListener("orientationchange", this.onWindowResize, false); // 设备的纵横方向改变时触发 3.1.6 layer
  监听图层,即创建的点、线、面、氛围等图层,我们的 WebGL 渲染主画面。主要事件为图层的鼠标交互及生命周期。技术原理见章节 3.3 模版。
  常规的鼠标交互,如  click  、dblclick   等均要支持,其次也我们进行了一层状态机抽象,方便业务更简便的接入使用
  hover ,即为悬停激活,默认逻辑为:图层内 hover 态只能拥有一个 移开时 hover 态取消
  select ,即为选中激活,逻辑为:图层内 select 态可以有多个 已 select 的区域再次 select 则为取消 select 点击空白区域取消全部 select 态
  active ,激活,支持批量:heatmapLayer.active({name:"四川省"}) // 行政区域图层激活四川省  bar.active({id:123672}) // 激活 柱子 /**  * 激活 参数  */ export interface IActiveFnProps {   id?: Array | number; // 激活的 object.id   name?: Array | string; // 激活的 object name, 优先级低于id   color?: ColorType; // 激活颜色,默认为图层 interaction select color   cover?: boolean; // 是否 全量覆盖, 默认 false,则active时不会清空之前已active的对象   type?: "hover" | "select"; }
  unActive ,取消激活,默认不穿参则取消全部/**  * 取消激活  参数  */ export interface IUnActiveFnProps {   id?: Array | number; // 取消激活的 object.id,不传则全部取消   name?: Array | string; // 激活的 object name, 优先级低于id   type?: "hover" | "select"; }
  部分图层独有事件 ,如baseMapLayer  的钻取事件,drillup  、drilldown   等。
  交互触发: drill: {       preventMouse: false,       drillDownEvent: "dblclick", // 双击 触发向下钻取       drillUpEvent: "undblclick", // 双击非地图区域 触发向上钻取 }
  API 触发,如下钻到陕西省: baseMapLayer.drillDown("610000") 3.1.7 custom
  监听 xGis 实例,暴露了一些自定义事件,例如生命周期 / 控制器交互回调等。 gis.on("loaded", () => {}); //地图加载完成触发 gis.on("destroy", () => {}); // 地图容启动销毁时触发 gis.on("resize", () => {}); // 地图容器大小改变事件  gis.on("viewportChange", () => {}); // 地图视角发生变化时触发,pan rotate pitch zoom 均会触发 gis.on("pan", () => {}); // 地图平移时触发事件 gis.on("panStart", () => {}); // 地图平移开始时触发 gis.on("panEnd", () => {}); // 地图移动结束后触发,包括平移,以及中心点变化的缩放。如地图有拖拽缓动效果,则在缓动结束后触发 gis.on("zoom", () => {}); // 地图缩放级别更改后触发 gis.on("zoomStart", () => {}); // 缩放开始时触发 gis.on("zoomEnd", () => {}); // 缩放停止时触发 gis.on("rotate", () => {}); // 地图水平旋转更改后触发 gis.on("rotateStart", () => {}); // 水平旋转开始时触发 gis.on("rotateEnd", () => {}); // 水平旋转停止时触发 gis.on("pitch", () => {}); // 地图上下倾斜更改后触发 gis.on("pitchStart", () => {}); // 上下倾斜开始时触发 gis.on("pitchEnd", () => {}); // 上下倾斜停止时触发 3.2 语法设计
  梳理完全部的事件后,需要设计一套统一易理解的 API。先参考 JavaScript 事件最核心的包括事件监听(addListener)、事件触发(emit)、事件删除(removeListener)。
  举个例子 : const button = document.querySelector("button"); button.addEventListener("click", (event) => {     // do sth else })
  我们向按钮单击事件添加了一个 listener (监听器),并且已经订阅了一个正在被发出的事件,当事件发生时会触发回调。每次单击该按钮时,都会发出该事件,而该事件会触发回调。
  当处理现有代码库时,或许需要触发自定义事件。不像单击按钮这样的特定 DOM 事件,而是假设想基于其他触发器发出一个事件,并得到一个事件响应。我们需要一个自定义事件派发器来实现这一点。
  事件派发器是一种模式,它监听一个已命名的事件,触发回调,然后发出该事件并附带一个值。有时这被称为" 发布/订阅 "模型或监听器。
  eventemitter3(https://www.npmjs.com/package/eventemitter3) 功能比较简单,就是一个事件注册触发的类库。注册发布,非 DOM 事件。
  // 我们的目标语法设计 barLayer.on("click", (event) => {     // do sth else }) 3.3 图层交互事件代理
  上面关于 layer 的交互事件监听是如何绑定上的呢?如柱状层,蜂窝热力层它们只是抽象的业务概念,并不是一个独立的 DOM 呀。这里的思路是将图层的事件监听由上层 canvas DOM 代理。
  我们的屏幕是二维的,但是我们展示物体的世界是三维的,当我们在构建一个物体的时候我们是以一个三维世界即时世界坐标来构建,而转化为屏幕坐标展示在我们眼前。那么在交互判断是否命中时,就得由屏幕坐标经过一系列坐标转换后判断:
  图层基类提供 on 事件绑定方法。判断不是自定义事件后将触发 DOM 绑定。
  好处 1:用户可以绑定任意交互事件,没有所谓的内置事件白名单,比如只支持 click;
  好处 2:地图不会默认绑定任何 UI 事件,场景无 hover 监听时滑动 cpu 不会升高。/**    * 绑定图层事件    * @param eventType    * @param handle    * @param context    */   public on(     eventType: CustomUIEventType | LifeCycleEventType | string,     handle: (...args: any[]) => void, // 回调函数     context?: any   ) {     // case1: 判断是否属于鼠标交互事件     if (!isNotCustomUIEventType(eventType)) {       this.eventManager.bindEvent(this.id, eventType as CustomUIEventType);     }      // case2: hover select LifeCycleEventType     this.ee.on(eventType, handle, context);   }
  举个例子 : barLayer.on("click",()=>{ // 进入 case1 ,DOM 增加监听 // to sth else })  barLayer.on("destroy",()=>{ // 不会进入case1 // to sth else }) UI 事件绑定会去重后再由 DOM 代理。维护对应「事件-图层 ID」映射表。
  好处 1: UI 事件不会重复绑定;
  好处 2: UI 事件和图层 ID 映射起来,后续碰撞检测会只检测绑定该事件的图层。
  举个例子 : barLayer.on("click",(e)=>{ if (e){  }else {  } }) // eventsPool {click: Set([barLayerID])} mapLayer.on("unclick",cb) // 不会重复往 DOM 上绑定click事件 // eventsPool {click: Set([barLayerID,mapLayerID])}
  /**    * 将 事件模型 插入 eventsPool,并触发ee.bindEvent    * 同类型 事件 有多个监听,但是 containerDom 对应只有一个 事件监听    * 比如  gis.baseMapLayer.on("mousemove", barPointLayer.on("mousemove",    * 但是 containerDom.addEventListener(mousemove一次    * @param originalUIEventType    * @param eventType    * @param layerID    */   private __addEventToPool = (     originalUIEventType: OriginalUIEventType,     layerID: LayerId   ) => {     // 将 对应事件 存在 原生事件下     let layerIDs = this.eventsPool.get(originalUIEventType);      if (!layerIDs) {       this.eventsPool.set(originalUIEventType, new Set());        this.ee.emit("bindLayerEvent", originalUIEventType);     }     layerIDs = this.eventsPool.get(originalUIEventType);      layerIDs.add(layerID);   }; 真正的 DOM 事件绑定: handleEvent = (event: MouseEvent | TouchEvent) => {      const type = event.type as OriginalUIEventType;     // 1. 得到已绑定此事件的图层     const layerIDs = this.eventManager.eventsPool.get(type);       // 2. 遍历已绑定此事件的图层     layerIDs.forEach((layerID) => {       const layer = layerManager.get({ layerID }) as Layer;       if (layer) {         // 判断图层是否 匹配到了obj         const objIntersects = intersectObjects (layer,event.xy) // ! 根据不同 picking-engine 计算是否相交          const firstIntersect =           objIntersects.length > 0 ? objIntersects[0] : null;         // case1: 当前图层绑定此事件且匹配到了obj         if (firstIntersect) {           const body = {             x: offsetX,             y: offsetY,             code: 200,             properties: {               ...firstIntersect.object.ext,               id: firstIntersect.object.id,             },           } as Partial;            // 通用 emit           layer.ee.emit(type, body);           layer.ee.emit("un" + type, null);         } else {           // case2: 当前图层虽绑定此事件,但是没有匹配到obj           // 通用 emit           layer.ee.emit(type, null);           layer.ee.emit("un"+ type, null);           }    }) } 四、picking-engine 技术实现
  对于事件分类 layer(图层)部分,由于载体不是独立 DOM,而是 WebGLContext,所以交互事件检测(如点击了柱状图层的哪根柱子)要由自研的 picking-enigne 实现。 4.1 CPU
  第一种方案是通过 CPU 计算,判断交互事件是否在 layer 区域。
  射线追踪法(raycasting)  ,其基本原理是:从鼠标处发射一条射线,穿透场景的视椎体,通过计算,找出视锥体中哪些对象与射线相交。
  首先,获取鼠标的屏幕坐标。其次,对其应用摄像机的投影和方向的矩阵变换,得到其在世界空间的坐标。然后,计算出一条射线,从视锥体的近端平面射向远端平面。再然后,对于场景中每一个对象的每一个三角,检查其是否与射线相交。假设你的场景中有 1000 个对象,每个对象有 1000 个三角,那么就需要检查一百万个三角。
  对此,可以做一些优化,先检查对象的包围球或包围盒是否与射线相交,包围球或包围盒是指包含整个对象的球体或者立方体,如果射线未相交,就不需要检查组成该对象的三角们了。
  什么是包围盒?
  包围盒广泛地应用于碰撞检测,比如射击、点击、相撞等,每一个物体都有自己的包围盒。因为包围盒一般为规则物体,因此用它来代替物体本身进行计算,会比直接用物体本身更加高效和简单。
  /**   * 射线检测   */   raycast(raycaster, intersects) {      // step1: ray-sphere,Broad Phase (粗略检测)      if (geometry.boundingSphere === null) geometry.computeBoundingSphere();      if (raycaster.ray.intersectsSphere(_sphere) === false) return ;       // step2: ray-box,Broad Phase (粗略检测)      if (geometry.boundingBox !== null) {          if (_ray.intersectsBox(geometry.boundingBox) === false) return;      }      // step3: ray-triangle Narrow Phase (精细检测)      const intersection = ray.intersectTriangle()        if (intersection) {         intersects.push(intersection);       }   }
  怎么样判断射线是否和包围球相交?
  /**    * 若这一射线与Sphere相交,则将返回true    * @param sphere 将被检查是否与之相交的Sphere    * @returns    */   intersectsSphere(sphere) {     return (       this.distanceSqToPoint(sphere.center) <= sphere.radius * sphere.radius     );   }
  射线与球体相交可能是射线几何相交测试的最简单形式,这就是为什么这么多射线追踪器显示球体图像的原因。 由于其简单性,它还具有非常快的优势。
  怎么样判断射线是否和轴对齐包围盒(AABB)相交?
  怎么样判断是否三角面相交? 首先判断射线是否与三角形所在的平面相交(面的法向量与线的方向向量垂直 = 平行不相交); 如果相交,再判断交点是否在三角形内。
  根据右手定则,假设我们三角形的顶点连接顺序为 v0,v1,v2 则我们的三角形法线指向屏幕外,当我们 v0v1 和 v0P 的叉积同样指向屏幕外,即它们的叉积和法线的点积大于零,则表明 P 点在 v0v1 的左侧,当对 3 条边都执行此操作后都在左边,则可以表明 P 点在三角形内部,即射线和三角形相交。
  总结
  这种方式看起来效果不错,而且能处理很多用户场景,但是也存在问题:
  基于 CPU 运算的 Javascript 遍历每一个对象,检查其包围盒或包围球是否与射线相交,如果相交,它必须遍历组成该对象的每一个三角,检查它们是否与射线相交。
  CPU 要做大量的工作,当你的对象由大量的三角组成时,这个过程会有些慢。 4.2 GPU
  第一种方案是通过 GPU 计算,判断交互事件是否在 layer 区域。
  为了完成 GPU 拾取,对每一个对象使用唯一的颜色进行离屏渲染。然后,检查鼠标位置关联的像素的颜色。这个颜色就能告诉我们哪个对象被选中。
  每个对象会被绘制两次,一次用于观看,一次用于拾取。
  如下:
  我们要判断图层点选中了哪个绿点(左下),我们其实在 buffer frame 里"fork"了一份新图层(右下),每个点都是全局唯一的某个颜色,这样就可以根据选中颜色来做匹配了。
  这样性能开销也是很大,但是拾取时我们只需读取 1px,所以我们可以设置摄像机,只绘制 1px,摄像机只呈现一个大矩形的一个很小的部分。这应该能节省一些运行时间。
  实现这种拾取方式,需要创建两个场景。一个使用正常的网格对象填充。另外一个使用"拾取材质"的网格对象填充。
  创建 buffer frame 代码如下: /**    * 初始化pickingScene    */   private __initGPUPick() {     // 1.为对象创建新场景和新渲染目标     this.pickingScene = new Scene();     // 方法1     this.pickingTexture = new WebGLRenderTarget(       containerDom.clientWidth,       containerDom.clientHeight     );      // 2. 为对象拾取创建新的着色器材质;     const pickingMaterial = new ShaderMaterial({       vertexShader: pickVshader,       fragmentShader: pickFshader,       transparent: false,       side: DoubleSide,     });      // 3.id-> object字典     this.pickingObjMap = new Map();       // 当有图层变化动态更新     this.props.layerManager.ee.on("loaded", (layer) => {       const geometry = mesh.geometry.clone();       // id 生成颜色策略,最大 999999     this.applyVertexColors(geometry, color.setHex(mesh.id));     const pickingObject = new Mesh(geometry, pickingMaterial);      this.pickingScene.add(pickingObject);     this.pickingObjMap.set(mesh.id, mesh);     })  }
  拾取逻辑如下:     renderSystem.renderer.setRenderTarget(this.pickingTexture);     renderSystem.renderer.clear(); // 一定要清缓冲区,renderer没开启自动清空缓冲区     renderSystem.renderer.render(this.pickingScene, cameraSystem.camera);       // pixel是Uint8Array(4),分别保存rgba颜色的四个通道,每个通道取值范围是0~255     const pixelBuffer = new Uint8Array(4);      // 读取 坐标上的 宽度为1,高度为1的像素的颜色     renderSystem.renderer.readRenderTargetPixels(       this.pickingTexture,       this.mouse.x,       this.pickingTexture.height - this.mouse.y,       1,       1,       pixelBuffer     );      // 颜色转id     const id = (pixelBuffer[0] << 16) | (pixelBuffer[1] << 8) | pixelBuffer[2];      // id 匹配 mesh     const currentMesh = this.pickingObjMap.get(id); 4.3 CPU or GPU
  那到底选择 CPU 还是 GPU 当作拾取引擎的计算策略呢?
  例如 :当场景一直在变化时,如旋转 / 或新增图元,frame buffer 一直也在同步更新矩阵运算 / 新增。
  CPU 方案性能测试案例:
  https://threejs.org/examples/?q=inter#webgl_interactive_cubes
  GPU 方案性能测试案例:
  https://threejs.org/examples/?q=inter#webgl_interactive_cubes_gpu
  可以看得出来 GPU 更适合数据量极大且稳定布局场景,不适合我们,我们是图层要动态增减 / 交互变换的,frame Buffer 会高频一直重绘,且检测时结束后切换回真实场景也有开销。  所以我们默认使用了 CPU 方案,当然也可以根据场景灵活切换。this.eventSystem = new EventSystem({       engine: PICKING_ENGINE.CPU, // 拾取引擎默认使用 PICKING_ENGINE.CPU, 可切换 PICKING_ENGINE.GPU }); 五、收益与反思5.1 重构收益
  针对之前提过的【二、问题】,都进行了优化 业务方:不同图层 interaction 交互字段配置统一。 /**  * 图层基础交互配置  */ export interface ILayerInteractionConfig {   hover?: Partial<{     enabled: boolean; // 是否开启 hover 交互,默认 true 开启,     effect: { // hover 交互响应       color?: ColorType;       poi?: boolean;     };     trigger: CustomUIEventType; // hover 交互触发事件   }>;   select?: Partial<{     enabled: boolean;     effect: {       color?: ColorType;       poi?: boolean;     };     trigger: CustomUIEventType;   }>; } 业务方:图层事件可灵活绑定,配合回调可以支持任意场景。
  场景:双击钻取地图能不能改成单击呀? // 方法1: new mapLayer({     drill: {         preventMouse: false,         drillDownEvent: "dblclick", // 双击 触发向下钻取 , 改为"click"         drillUpEvent: "undblclick", // 双击非地图区域 触发向上钻取 "unclick"      } })   // 方法2: // 1.首先关闭默认钻取交互,preventMouse: true // 2.自己绑定click监听,完成自定义下钻交互逻辑 mapLayer.on("click",(e)=>{     if (e){         barLayer.drilldown(e.ext.adcode)     } else {         barLayer.drillup()     } })
  场景:可不可以让蓝色柱子的 hover 样式,当 value 大于 10 时为绿色,小于 10 时为红色呀? // 1.首先关闭默认hover逻辑 interaction: {   hover: {     enabled: false,   } }  // 2. 自己绑定mousemove监听,完成自定义hover交互逻辑 barLayer.on("mousemove",(e)=>{     if (e){         if(e.ext.value>10){           barLayer.active(e.id,{color:"green"})         } else {           barLayer.active(e.id,{color:"red"})         }     }else {         barLayer.unactive()     } }) 开发者:图层交互模块统一基类实现,特殊图层可逻辑覆盖。
  场景:柱状图我开发完了,交互绑定先不加了,目前业务方也不需要。 class BarLayer{    ctor(){    ...    super.registerInteraction();  // 柱状层一行代码即可    } }   class mapLayer{    ctor(){    ...    super.registerInteraction(this.districtMeshGroup);  // 地图层只有省份区块响应交互,国界线、省线都不需要响应交互,所以使用默认的 coreGroup 作为检测集合不合理    } } 交互性能:新的事件系统去除截流后,性能依然提升 20%。
  举个例子 :地图层存在 10 个图层,其中 8 个支持事件监听,其中 5 个绑定了事件监听,其中 2 个监听了 A 类型事件。
  当 A 类型事件触发时,旧版思路和最优思路的对比如下:
  5.2 后续优化空间
  case1: CPU 场景下可以八叉树优化吗?
  我们将视棱台划分成 8 个区域,分别从区域 1 到区域 8,所有场景中的模型 geometry 都分布在这 8 个区域中,现在我们就通过这 8 个区域缩小射线碰撞的遍历 geometry 模型的范围。具体的操作很简单,那就是先让射线和这 8 个区域的棱台几何体进行射线相交计算,只有与射线产生交点的棱台几何体区域才是射线检测的模型空间范围,其余和射线不产生交点的区域中的 geometry 模型就不必参与到 raycaster 检测中来,这样就极大地缩小了遍历 geometry 的数量,从而优化了 raycaster 的功能。
  我们来看看上图中依照 8 叉树优化逻辑进行的 raycaster 步骤。首先,射线只交 2 个区域的棱台他们分别是区域 7 和区域 3,那么区域 1,2,4,5,6,8 中的所有 geometry 就都不用参与 raycaster 射线碰撞检测了,一下子我们就排除了 Triangle3 三角形 3,因为他处于区域 4 中,不在检测区域范围内,是不是就减少了后面线段和面相交的计算量,优化了 raycaster 整体的性能。
  直接缩小了检测范围,而且还能继续递归细分下去,比如区域 3 还能细分成 8 个小区域,将检测范围缩得更小,进一步排除检测区域外的多余模型,进一步减少计算量。
  但八叉树的成本增加了几何图形的内存消耗,当然还有八叉树的生成/更新,如果几何图形被修改,则必须重复操作。所以 八叉树适合稳定布局场景,后续尝试一下是否正优化 。
  case2: 物体合并后渲染怎么解决? 维护三角面映射到原始几何图形的索引; 维护两组几何体(一组 merge 一组没 merge),这种解决方案比较麻烦,暂不采纳。 5.3 以终为始,设计优先
  应该以终为始,想清楚最终目标后再开始实现。
  此次事件系统设计,主要对比参考了 mapbox / antv L7 / echarts 的实现。
  正常代码逻辑复杂度会随着场景的复杂度而同步提升,但是如果纯粹靠场景推动去升级迭代,会发现代码经常要重构所以设计的拓展能力/前沿性得靠经验去未雨绸缪,经验不足就参考成熟产品的设计思路,等于快速拓展了场景复杂度,是一条捷径。 参考
  游戏开发中的渲染加速算法总结: https://zhuanlan.zhihu.com/p/32300891
  关注「字节前端 ByteFE」公众号,追更不迷路!

清明踏青祭扫,专家提示要注意这些防护细节清明将至,不少人安排了外出踏青和祭扫活动。面对当前的疫情形势,有哪些针对性的疫情防控措施?在4月1日举行的国务院联防联控机制新闻发布会上,专家给出权威解答。不建议做跨省的踏青旅行发年均游客近千万,乌镇为这家企业带去了什么?本文来源时代周报作者徐美娟2021年,中青旅(600138。SH)扭亏为盈。2022年3月30日晚,中青旅发布的2021年年度报告显示,报告期内,中青旅营业收入86。35亿元,同比春天吃豆,胜过吃肉,4月这中国豆要多吃,营养健康,别错过有一种豆子我们中国人把它叫做荷兰豆,而荷兰人却把它叫做中国豆,原来这个豆子原本只有中国一个小边陲地区才有,17世纪时荷兰人通过航海贸易发现了此豆,并带回本国在西方各地传播开来,再经为何只有地球诞生了生命?明明金星和火星曾经更适合生存有这样一个疑问不知道大家注意过没有,不谈太阳系以外的星球,仅以太阳系内的行星为例,地球真的是唯一存在生命的星球吗?和银河系内其他数千亿颗恒星系相比,像太阳系这样拥有8颗行星的恒星系高中地理试题解析第84题(地球公转小行星带太阳活动)知识点八大行星太阳系共有八大行星,距离太阳从近到远分别为水星金星地球火星木星土星天王星海王星。在火星和木星之间,存在着小行星带。八大行星的自转公转特点如下自转金星自东向西,天王星躺被冤枉13年我没说缺钱就去中国,张娜拉还能火回中国吗?2006年一部刁蛮公主让韩国女星张娜拉在中国红透了半边天。剧中古灵精怪善良仗义的司徒静至今还让人记忆犹新,这让她在中国俘获了一大批粉丝。然而很多人发现,其实在刁蛮公主之后没有几年,北向资金净买入最多的20股,最新数据来了统计时间4月2日(1日收盘数据)。北向资金净买入最多的20股序号代码名称最新价涨跌幅北向净流入所属板块1hr601919hr中远海控17。0510。007。23亿航运港口2hr60房产政策信息2022年4月2日一北京拟供应87宗租赁住房用地,八成用于建设保障性租赁住房。3月31日,北京市规划和自然资源委员会发布的信息显示,2022年拟供租赁住房用地项目共计87宗,约307公顷。据悉,本次加入华为29年,孟晚舟出任轮值董事长4月2日,华为宣布完成监事会换届选举。官网最新信息显示,华为CFO孟晚舟现已担任华为副董事长轮值董事长。(华为官网截图)华为官网显示,孟晚舟毕业于华中理工大学,硕士。1993年加入小学生作文我的小姨火了,内容真实有趣,小姨别啥都往外说小学生的作文总是让家长和老师头疼不已,却又忍俊不禁。在他们的小脑袋瓜里,总是会有一些天马行空的奇怪想法,甚至有时一不留神就把家人的秘密在作文里说了出来。小学生在写作文时往往都是真情心语心情过好每一个今天,明天才会更有价值心语心情Apr1(1)。日期天气晴朗心情文案生活从来不是选择而是热爱快樂不难知足就好(2)。星期五大家都说今年太难了人民日报却说你很健康,那就是最好的一年(3)每日一签Day91别
战舰世界航母如何玩,全球同步震撼推出S系航母每一个航母玩家在进入战局后,都要面临当团队保姆还是输出中坚的选择问题。这个问题的本质是航母应该权衡什么时候该去抓驱逐,什么时候该去帮队友打输出的问题。随着硬核军武游戏战舰世界国服版我国等级最高的国字号港口专题博物馆,位于世界第四大港口城市它是海上丝绸之路的东方第一站,是拥有着7000多年历史的古老港口,有世界五佳港口之美誉。这里诞生了灿烂辉煌的河姆渡文化,不仅拥有比上海外滩历史还早20年的中国最早的老外滩,还拥有中牙膏生日开启首播,本人出现切蛋糕,正式宣布露脸时间最近关于直播圈的消息是很多人在讨论的,相信大家也已经看过不少这方面的内容了,现在这段时间真的话题多多啊,比如有王者荣耀比赛的内容,英雄联盟全明星赛的内容,吃鸡方面也有赛事在进行,话剑网1归来与非遗合作,工艺与情怀结合,唤醒老玩家青春国内知名IP剑侠情缘系列最新手游作品剑网1归来今年在国内正式公测,作为剑侠情缘网络版的手游复刻版本,早在上线之前就已经备受老玩家期待,公测当天更是沸沸扬扬,服务器人满为患。在游戏公亮剑演员现状有人成视帝有人负债上亿有人成鬼子专业户文老赵戏说编辑老赵戏说2005年,一部打破人们对抗日英雄脸谱化形象的亮剑横空出世,成为那一年最火的电视剧。里面粗话满天飞的李云龙鲁莽但有情有义的魏和尚热爱国家但却站在对立面的楚云飞工艺倒退6年?华为将发布全新14nm芯片,突破限制众所周知,2020年9月15日之后,华为麒麟芯片就成为了绝唱,没有厂商代工了,原因大家才最知道,就不再多说了。而要解决这个问题,一是禁令解除,二是代工厂使用不受限制的设备,比如全国华林科纳半导体工艺IZTO薄膜在盐酸中的化学蚀刻特性引言本文研究采用射频磁控溅射法在室温下在聚合物衬底上制备了透明半导体氧化锌锡铟(IZTO)薄膜,并研究了其在盐酸溶液中的湿式化学蚀刻特性。研究表明,沉积过程中o2流速的控制是由于I赵本山徒弟丫蛋庆35岁生日,切三层蛋糕排场大,肚子隆起疑怀三胎饿了吗?戳右边关注我们,每天给您送上最新出炉的娱乐硬核大餐!1月4日,有网友晒出赵本山徒弟丫蛋庆生时的视频,在网络上引发热议。当天作为寿星的丫蛋穿着奶白色羽绒服搭配黑色长裤,衣着十湖南26岁服务员姚碧本科毕业,3次为国宴服务,一般宴会请不动星辰大海无垠,每个人都需要横穿自己的沧海桑田。而星海里的黯淡光鲜,极昼极夜下的璀璨深沉,一心一念,一花一果总在婆娑琳琅里呈现,每每选择与努力共鸣,会使得人们共情蜿蜒岁月旖旎。与众不怎样烙饼才能不硬?关键在于和面,面点师教你1招,放凉了也好吃怎样烙饼才能不硬?关键在于和面,面点师教你1招,放凉了也好吃。哈喽,大家好,我是大厨江一舟,今天又到了和大家分享美食的时刻了,你准备好了吗?烙饼是我最喜欢吃的主食了,这不前两天正好冬季手脚冰凉怎么办?知嘛健康专业医师分享三七补血养生妙方随着最近全国各地气温的明显下降,人们都感受到了冷空气的威力,不少人还出现了手脚冰凉的情况,知嘛健康骏豪店医师从专业的角度出发分析,手脚冰凉往往与个人的体质状况及身体的免疫力有关,为