ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

《HarmonyOS 7 ArkGraphics 3D 空间设计开发实战》06:3D对象拾取、家具选择与交互状态管理【鸿蒙心迹】

《HarmonyOS 7 ArkGraphics 3D 空间设计开发实战》06:3D对象拾取、家具选择与交互状态管理【鸿蒙心迹】 用户点的是屏幕上的二维点程序怎么知道他点的是沙发前言上一篇做了视角控制用户可以自己转着看房间了。但只能看不能操作。这一篇加一个最基本的交互用户点一下沙发能选中它显示选中状态然后可以移动它、删除它。看起来很简单用户点屏幕触发点击事件拿到坐标然后呢问题就出在这用户点的是二维屏幕坐标但沙发在三维空间里。你怎么知道屏幕上这个点对应的是三维世界里的哪个物体这就是 3D 拾取Picking。从二维屏幕坐标反推到三维空间里被点中的物体。这一篇就讲这个怎么从屏幕上的一个点找到对应的 3D 节点以及怎么管理选中状态。一、先搞清楚二维点怎么对应三维物体先建立一个直觉相机从一个点看出去看到的是三维空间里的一个锥形视野用户点屏幕上某个点相当于从相机出发经过这个屏幕点画一条射线到三维空间里这条射线穿过哪些物体最前面那个就是被点中的这个过程叫射线检测Ray Casting。打个比方你站在房间里眼睛就是相机。你用手指指向墙上的一幅画从你的眼睛到手指尖画一条线这条线穿过的第一个物体就是你指的那个。3D 拾取就是干这个的从屏幕坐标算出这条射线然后看它和哪个 3D 物体相交。二、写一个 PickingManager先写个工具类把点击坐标转成射线然后检测命中import{scene}fromkit.ArkGraphics3DKit;// 家具业务对象interfaceFurnitureEntity{nodeId:string;// 对应的3D节点IDname:string;// 家具名称type:string;// 家具类型price:number;// 价格position:{x:number,y:number,z:number};}exportclassPickingManager{// 节点ID到业务对象的映射privatenodeEntityMap:Mapstring,FurnitureEntitynewMap();// 当前选中的家具privateselectedEntity:FurnitureEntity|nullnull;constructor(privatescene:scene.Scene){}/** * 注册家具把节点和业务对象对应起来 */registerFurniture(node:scene.Node,entity:FurnitureEntity):void{this.nodeEntityMap.set(node.id,entity);}/** * 处理屏幕点击 * param screenX 屏幕x坐标像素 * param screenY 屏幕y坐标像素 * param viewWidth 视口宽度 * param viewHeight 视口高度 */handleClick(screenX:number,screenY:number,viewWidth:number,viewHeight:number):FurnitureEntity|null{// 1. 屏幕坐标归一化转成 -1 到 1 之间的 NDC 坐标constndcX(screenX/viewWidth)*2-1;constndcY1-(screenY/viewHeight)*2;console.info(PickingManager: click at ndc,ndcX,ndcY);// 2. 从相机发射射线检测命中// ArkGraphics 3D 提供拾取接口consthitNodethis.scene.pickNode(ndcX,ndcY);if(!hitNode){// 没点中任何物体取消选中this.setSelected(null);returnnull;}// 3. 通过节点ID找业务对象constentitythis.nodeEntityMap.get(hitNode.id);if(entity){this.setSelected(entity);console.info(PickingManager: selected,entity.name);returnentity;}returnnull;}/** * 设置选中状态 */privatesetSelected(entity:FurnitureEntity|null):void{// 取消之前的选中状态if(this.selectedEntity){constprevNodethis.findNodeById(this.selectedEntity.nodeId);if(prevNode){// 恢复原来的材质边框this.setNodeHighlight(prevNode,false);}}this.selectedEntityentity;// 设置新的选中状态if(entity){constnodethis.findNodeById(entity.nodeId);if(node){// 加个高亮边框表示选中了this.setNodeHighlight(node,true);}}}privatefindNodeById(nodeId:string):scene.Node|null{// 递归找节点constrootthis.scene.getRoot();returnthis.findNodeRecursive(root,nodeId);}privatefindNodeRecursive(node:scene.Node,nodeId:string):scene.Node|null{if(node.idnodeId)returnnode;constchildrennode.getChildren();for(constchildofchildren){constfoundthis.findNodeRecursive(child,nodeId);if(found)returnfound;}returnnull;}privatesetNodeHighlight(node:scene.Node,highlight:boolean):void{// 选中的时候加个边框或者改颜色console.info(PickingManager: set highlight,node.name,highlight);}getSelected():FurnitureEntity|null{returnthis.selectedEntity;}}核心逻辑就三步把屏幕像素坐标转成 NDC 归一化坐标-1 到 1调用 scene.pickNode() 拿到命中的节点通过节点ID找到对应的业务对象这里有个关键设计Scene Node 和业务对象是分开的。Node 只负责 3D 渲染相关的东西位置、旋转、Mesh、材质。业务对象FurnitureEntity负责业务数据名称、价格、类型。两者通过 nodeId 关联。为什么不把业务数据直接塞到 Node 上因为 3D 引擎的 Node 是渲染层的东西不应该知道你的业务逻辑。以后换渲染引擎业务对象不用动。三、接 ArkUI 的点击手势PickingManager 写好了接下来接 ArkUI 的点击EntryComponentstruct SpaceRoomPage{privatepickingMgr:PickingManager|nullnull;StateselectedName:string;// 选中家具的名字显示在UI上build(){Stack(){scene.SceneView({scene:undefined}).width(100%).height(100%).onClick((event:ClickEvent){// 拿到点击的屏幕坐标constxevent.x;constyevent.y;// 调用拾取constentitythis.pickingMgr?.handleClick(x,y,1080,1920);// 更新UI显示if(entity){this.selectedName选中${entity.name}${entity.type};}else{this.selectedName;}})// 底部显示选中信息的浮层Column(){if(this.selectedName){Text(this.selectedName).fontSize(14).fontColor(Color.White).backgroundColor(rgba(0,0,0,0.7)).padding(10).borderRadius(8)}}.position({x:20,y:100})}.width(100%).height(100%)}}现在用户点沙发底部就会显示选中三人沙发沙发类。点空白地方选中状态取消。四、几个最容易踩的拾取坑把这一篇遇到的坑总结一下坑现象解决办法业务数据直接塞Node里以后换引擎全要改Node和业务对象分开通过ID关联点中了父节点不是真实物体点沙发上靠垫选中整个沙发组拾取时找最底层的叶子节点透明物体也被点中点窗户玻璃选中玻璃而不是后面的墙透明物体设置不参与拾取删除家具后映射表没清删除后还能选到已经删掉的家具删除家具时同步从map里移除UI选中状态和3D状态不同步界面显示选中了但3D里没高亮状态统一在一个地方管理点击和拖动冲突拖视角的时候也触发选中区分点击和拖动移动距离大就不算点击这些问题在简单场景下看不出来家具多了、层级复杂了就全冒出来了。特别是最后一个用户拖动视角的时候如果拖动结束的位置刚好在一个家具上就会触发选中。这就需要判断手指移动的距离很小才算点击移动距离大就是拖动不算。总结第六篇的核心就一句话3D 拾取本质上是把二维屏幕坐标反推成三维射线再找和哪个物体相交。从屏幕点击坐标算出一条从相机出发的射线射线和场景里的物体求交最前面的就是被点中的Node渲染层和业务对象数据层分开通过ID关联选中状态要统一管理UI和3D高亮要同步要区分点击和拖动不然转视角的时候会误触选中SpaceRoom 现在能点选家具了。下一篇做性能优化家具加到 50 个、100 个的时候帧率为什么掉下来怎么优化。
返回列表