基于 HTML5 Canvas 的 3D 模型贴图问题

2024-08-30 11:32

本文主要是介绍基于 HTML5 Canvas 的 3D 模型贴图问题,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

之前注意到的一个例子,但是一直没有沉下心来看这个例子到底有什么优点,总觉得就是一个 list 列表,也不知道右边的 3d 场景放两个节点是要干嘛,今天突然想起来就仔细地看了一下这个例子的代码,实际操作中应该还是有用处的,就跟大家分享一下。

本例地址: http://hightopo.com/guide/guide/core/listview/examples/example_custom.html

实现图如下,看起来略有点简陋,但是可以自己天马心空增加或者更改成你需要的东西:

图片描述

首先,创建场景,HT 中有一个 BorderPane 面板组件是拿来页面排布的,可以排布 html 标签,也可以排布 HT 的组件,这里我们将整个页面分为三个部分,顶部工具条 toolbar、左侧列表 listView 和中间 3d 场景 g3d,再将这个面板组件添加进 html body 体中:

borderPane = new ht.widget.BorderPane();//面板组件                
toolbar = new ht.widget.Toolbar(); //工具条                    listView = new ht.widget.ListView(); //列表组件
g3d = new ht.graph3d.Graph3dView();// 3d 组件borderPane.setTopView(toolbar);//将 toolbar 放置到面板中的顶部
borderPane.setLeftView(listView, 350); //将 listView 放置到面板中的左侧
borderPane.setCenterView(g3d); //将 g3d 放置到面板中的中间borderPane.addToDOM(); //将面板组件添加进 body 中

addToDOM 函数是 HT 封装好的将 HT 组件添加进 body 体中的一个方法,其实现逻辑如下:

addToDOM = function(){   var self = this,view = self.getView(),//通过 getView 函数获取组件的底层 divstyle = view.style;document.body.appendChild(view); //body 添加孩子 view           style.left = '0';style.right = '0';style.top = '0';style.bottom = '0';      window.addEventListener('resize', function () { self.iv(); }, false);//窗口大小变化时,立即刷新组件            
}

我们一个部分一个部分来解析,从最上层的 toolbar 工具条开始,如下:

图片描述

工具条也是分为三个部分,一是左侧的搜索框,二是中间的分割线,三是右侧的点击按钮。

我们首先向工具条 toolbar 中添加这三个元素,具体添加方法请参考 HT for Web 工具条手册:

toolbar.setItems([//设置工具条元素数组              {id: 'text',label: 'Search',icon: 'images/search.png',textField: {width: 120}},'separator',{label: 'Sort by price',type: 'toggle',//toggle表示开关按钮selected: true,action: function(){listView.setSortFunc(this.selected ? sortFunc : null);}}
]);

接下来向左侧的 listView 列表中添加数据,这个数据就是 product.js 中的变量 products,通过遍历这个数组变量,将这个数组中的所有值都填充到 listView 列表中:

图片描述

products.forEach(function(product){//products 是在product.js 文件中定义的var data = new ht.Data();data.a(product);//设置数据 data 的 attr 属性、listView.dm().add(data);//将 data 添加进 listView 的数据容器中
}); 

然后对 listView 列表进行一系列的样式属性的设置:行高、背景、icon 图标、文字提示等等。代码如下,解释都在代码中了,还有不懂的请查阅 HT for Web 列表手册:

listView.setRowHeight(50);//设置行高
listView.drawRowBackground = function(g, data, selected, x, y, width, height){//绘制行背景色,默认仅在选中该行时填充选中背景色,可重载自定义if(this.isSelected(data)){//选中时g.fillStyle = '#87A6CB';}else if(this.getRowIndex(data) % 2 === 0){//偶数行时g.fillStyle = '#F1F4F7';}else{g.fillStyle = '#FAFAFA';}g.beginPath();g.rect(x, y, width, height);g.fill();
};
// HT 通过 ht.Default.setImage('name', json) 函数来注册图片
ht.Default.setImage('productIcon', {width: 50,height: 50,clip: function(g, width, height) {//clip 用于裁剪绘制区域//利用canvas画笔绘制,实现自定义裁剪任意形状的效果//这里是将图片裁剪成圆形g.beginPath();//x, y, radius, startAngle, endAngle, anticlockwiseg.arc(width/2, height/2, Math.min(width, height)/2-3, 0, Math.PI * 2, true);g.clip();},comps: [//矢量图形的组件Array数组,每个数组对象为一个独立的组件类型,数组的顺序为组件绘制先后顺序{type: 'image',stretch: 'uniform',//图片始终保持原始宽高比例不变化,并尽量填充满矩形区域rect: [0, 0, 50, 50],//指定组件绘制在矢量中的矩形边界 [x, y, width, height]四个参数方式,分别代表左上角坐标x和y,以及宽高width和heightname: {func: function(data){return data.a('ProductId');}}//图片的名字为 data.a('ProductId') 返回的值}]
});
listView.setIndent(60);//设置indent缩进,该参数一般用于指定图标的宽度                 
listView.getIcon = function(data){//返回data对象对应的icon图标,可重载自定义return 'productIcon';//这个是前面 ht.Default.setImage 函数注册过的矢量图形
};  listView.enableToolTip();//开启文字提示
listView.getLabel = function(data){//返回data对象显示的文字,默认返回data.toLabel(),可重载自定义return data.a('ProductName') + ' - $' + data.a('UnitPrice').toFixed(2);
};                
listView.getToolTip = function(e){//根据传入的交互事件,返回文本提示信息,可重载自定义var data = this.getDataAt(e);//传入逻辑坐标点或者交互event事件参数,返回当前点下的数据元素if(data){return '<span style="color:#3D97D0">ProductId:&nbsp;</span>' + data.a('ProductId') + '<br>' +  '<span style="color:#3D97D0">ProductName:&nbsp;</span>' + data.a('ProductName') + '<br>' +  '<span style="color:#3D97D0">QuantityPerUnit:&nbsp;</span>' + data.a('QuantityPerUnit') + '<br>' + '<span style="color:#3D97D0">Description:&nbsp;</span>' + data.a('Description');                        }return null;
};

列表组件中还封装了一个很方便的函数 setSortFunc,用于设置排序函数,用户也可以自定义,目前我们希望对这些”商品“进行排序:

sortFunc = function(d1, d2){//自定义排序函数return d1.a('UnitPrice') - d2.a('UnitPrice');
};
listView.setSortFunc(sortFunc);//HT 定义的 设置排序函数

因为我们要进行数据的搜索,就要对数据以及显示方面进行过滤,因为在数据变化时,HT 无法获知需要更新,这时候就要我们手动对有显示变化的部分调用更新函数 invalidate 简写为 iv。

我们对文本输入框的键盘弹起事件进行事件的监听,然后判断我们输入的值在 listView 列表中是否存在等操作对显示界面进行过滤:

// 对text文本框进行键盘按键弹起事件监听 toolbar.getItemById('text').element.getElement().onkeyup = function(e){listView.invalidateModel();//无效模型,最彻底的刷新方式 “完全刷新”
};
//如果文本框输入的值在
listView.setVisibleFunc(function(data){//设置可见过滤器                    var text = toolbar.v('text');//getValue(id)根据id获取对应item元素值,简写函数为v(id)if(text){              return data.a('ProductName').toLowerCase().indexOf(text.toLowerCase()) >= 0;//indexOf()方法返回在类型数组中可以找到给定元素的第一个索引,如果不存在,则返回-1}return true;
});

第三个部分,右侧 3d 场景,利用的是 HT 的三维组件 ht.graph3d.Graph3dView,然后在 3d 场景上添加两个节点,作为对照:

//创建两个节点放到 3d 场景中
var node = new ht.Node();
node.s3(30, 30, 30);//设置三维大小
node.p3(-30, 15, 0);//设置三维坐标
node.s('all.color', '#87A6CB');//设置 node 的六个面颜色                
g3d.dm().add(node);//将新建的 node 添加进 3d 场景的数据容器中var node = new ht.Node();
node.s3(30, 30, 30);
node.p3(30, 15, 0);
node.s('all.color', '#87A6CB');
node.setElevation(15);
g3d.dm().add(node);g3d.setEye(-100, 100, 80);//设置 3d 场景的眼睛(或Camera)所在位置,默认值为[0, 300, 1000]
g3d.setGridVisible(true);//设置是否显示网格
g3d.setGridColor('#F1F4F7');//设置网格线颜色
整个场景创建完毕,接下来就是将 listView 中显示的 icon 图标拖拽到 3d 中的节点上,作为贴图。列表组件中封装了一个拖拽的功能 handleDragAndDrop,这个函数有两个参数,event 交互事件和 state 当前状态,我们对拖拽事件的不同状态进行不同的处理:listView.handleDragAndDrop = function(e, state){//该函数默认为空,若该函数被重载,则pan平移组件功能将被关闭if(state === 'prepare'){//state当前状态,先后会有prepare-begin-between-end四种过程var data = listView.getDataAt(e);//传入逻辑坐标点或者交互event事件参数,返回当前点下的数据元素listView.sm().ss(data);//设置选中当前事件所在的数据元素if(dragImage && dragImage.parentNode){document.body.removeChild(dragImage);}dragImage = ht.Default.toCanvas('productIcon', 30, 30, 'uniform', data);// toCanvas(image, width, height, stretch, data, view, color)将图片转换成Canvas对象productId = data.a('ProductId');}else if(state === 'begin'){if(dragImage){var pagePoint = ht.Default.getPagePoint(e);//返回page属性坐标dragImage.style.left = pagePoint.x - dragImage.width/2 + 'px';//实时更新拖拽时的图标的位置dragImage.style.top = pagePoint.y - dragImage.height/2 + 'px';document.body.appendChild(dragImage);//在 html body 体中添加这个拖拽的图片}}else if(state === 'between'){if(dragImage){var pagePoint = ht.Default.getPagePoint(e);//返回page属性坐标dragImage.style.left = pagePoint.x - dragImage.width/2 + 'px';dragImage.style.top = pagePoint.y - dragImage.height/2 + 'px';   if(ht.Default.containedInView(e, g3d)){//判断交互事件所处位置是否在View组件之上,一般用于Drog And Drop的拖拽操作判断//这边做了两个判断,一个是鼠标在拖拽的时候未松开,一个是鼠标拖拽的时候松开了。if(lastFaceInfo){//鼠标未松开的情况下,贴图显示旧值//data.face 默认值为front,图标在3D下的朝向,可取值left|right|top|bottom|front|back|centerlastFaceInfo.data.s(lastFaceInfo.face + '.image', lastFaceInfo.oldValue);lastFaceInfo = null;}//鼠标松开时,将新值赋给这个面var faceInfo = g3d.getHitFaceInfo(e);//获取鼠标所在面信息if(faceInfo){faceInfo.oldValue = faceInfo.data.s(faceInfo.face + '.image');//获取面的“老值”faceInfo.data.s(faceInfo.face + '.image', productId);//front/back/top/bottom/left/right.image 设置这些面的贴图lastFaceInfo = faceInfo;}}}}else{//拖拽结束之后,所有值都回到初始值if(dragImage){//有从列表中拖拽图片if(lastFaceInfo){//有赋“图片”到 3d 中的节点上lastFaceInfo.data.s(lastFaceInfo.face + '.image', lastFaceInfo.oldValue);lastFaceInfo = null;}                             if(ht.Default.containedInView(e, g3d)){                               var faceInfo = g3d.getHitFaceInfo(e);if(faceInfo){faceInfo.data.s(faceInfo.face + '.image', productId);}}                                                        if(dragImage.parentNode){document.body.removeChild(dragImage);}dragImage = null;  productId = null;}}
}; 

这篇关于基于 HTML5 Canvas 的 3D 模型贴图问题的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1120694

相关文章

Vue3 的 shallowRef 和 shallowReactive:优化性能

大家对 Vue3 的 ref 和 reactive 都很熟悉,那么对 shallowRef 和 shallowReactive 是否了解呢? 在编程和数据结构中,“shallow”(浅层)通常指对数据结构的最外层进行操作,而不递归地处理其内部或嵌套的数据。这种处理方式关注的是数据结构的第一层属性或元素,而忽略更深层次的嵌套内容。 1. 浅层与深层的对比 1.1 浅层(Shallow) 定义

大模型研发全揭秘:客服工单数据标注的完整攻略

在人工智能(AI)领域,数据标注是模型训练过程中至关重要的一步。无论你是新手还是有经验的从业者,掌握数据标注的技术细节和常见问题的解决方案都能为你的AI项目增添不少价值。在电信运营商的客服系统中,工单数据是客户问题和解决方案的重要记录。通过对这些工单数据进行有效标注,不仅能够帮助提升客服自动化系统的智能化水平,还能优化客户服务流程,提高客户满意度。本文将详细介绍如何在电信运营商客服工单的背景下进行

这15个Vue指令,让你的项目开发爽到爆

1. V-Hotkey 仓库地址: github.com/Dafrok/v-ho… Demo: 戳这里 https://dafrok.github.io/v-hotkey 安装: npm install --save v-hotkey 这个指令可以给组件绑定一个或多个快捷键。你想要通过按下 Escape 键后隐藏某个组件,按住 Control 和回车键再显示它吗?小菜一碟: <template

无人叉车3d激光slam多房间建图定位异常处理方案-墙体画线地图切分方案

墙体画线地图切分方案 针对问题:墙体两侧特征混淆误匹配,导致建图和定位偏差,表现为过门跳变、外月台走歪等 ·解决思路:预期的根治方案IGICP需要较长时间完成上线,先使用切分地图的工程化方案,即墙体两侧切分为不同地图,在某一侧只使用该侧地图进行定位 方案思路 切分原理:切分地图基于关键帧位置,而非点云。 理论基础:光照是直线的,一帧点云必定只能照射到墙的一侧,无法同时照到两侧实践考虑:关

【 html+css 绚丽Loading 】000046 三才归元阵

前言:哈喽,大家好,今天给大家分享html+css 绚丽Loading!并提供具体代码帮助大家深入理解,彻底掌握!创作不易,如果能帮助到大家或者给大家一些灵感和启发,欢迎收藏+关注哦 💕 目录 📚一、效果📚二、信息💡1.简介:💡2.外观描述:💡3.使用方式:💡4.战斗方式:💡5.提升:💡6.传说: 📚三、源代码,上代码,可以直接复制使用🎥效果🗂️目录✍️

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

好题——hdu2522(小数问题:求1/n的第一个循环节)

好喜欢这题,第一次做小数问题,一开始真心没思路,然后参考了网上的一些资料。 知识点***********************************无限不循环小数即无理数,不能写作两整数之比*****************************(一开始没想到,小学没学好) 此题1/n肯定是一个有限循环小数,了解这些后就能做此题了。 按照除法的机制,用一个函数表示出来就可以了,代码如下

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

Andrej Karpathy最新采访:认知核心模型10亿参数就够了,AI会打破教育不公的僵局

夕小瑶科技说 原创  作者 | 海野 AI圈子的红人,AI大神Andrej Karpathy,曾是OpenAI联合创始人之一,特斯拉AI总监。上一次的动态是官宣创办一家名为 Eureka Labs 的人工智能+教育公司 ,宣布将长期致力于AI原生教育。 近日,Andrej Karpathy接受了No Priors(投资博客)的采访,与硅谷知名投资人 Sara Guo 和 Elad G

Retrieval-based-Voice-Conversion-WebUI模型构建指南

一、模型介绍 Retrieval-based-Voice-Conversion-WebUI(简称 RVC)模型是一个基于 VITS(Variational Inference with adversarial learning for end-to-end Text-to-Speech)的简单易用的语音转换框架。 具有以下特点 简单易用:RVC 模型通过简单易用的网页界面,使得用户无需深入了