JavaScript对XML的一些DOM操作(续篇 three.js上路)
上次完成了对XML的解析程序后,由kylin审阅后,被严肃的批评了,太没有面向对象的概念了,设计思路停留在面向过程中无法自拔,JavaScript还弄个啥多维数组,不开窍。
因此,改写了一下原有程序,开始补习设计模式。顺带 弄了个Three.js升级下效果。
题外话 啥是Three.js
Three.js是基于原生WebGL封装运行的三维引擎,在所有WebGL引擎中,Three.js是国内文资料最多、使用最广泛的三维引擎。
提到 Three.js,就必须说一下 OpenGL 和 WebGL。
OpenGL 大概许多人都有所听闻,它是最常用的跨平台图形处理开源库。
WebGL 就是基于 OpenGL 设计的面向 web 的 3D 图形标准,它提供了一系列 JavaScript API,通过这些 API 进行图形渲染,系统硬件会加速 3D 渲染,从而获得较高性能。
而 Three.js 是 JavaScript 编写的 WebGL 第三方库,通过对 WebGL 接口的封装与简化而形成的一个易用的图形库。
WebGL 与 Three.js 对比
通过上面的简介,我们知道 WebGL 和 Three.js 都可以进行 Web 端的 3D 图形开发。那问题来了,既然我们有了 WebGL,为什么还需要 Three.js?
这是因为前端工程师想要短时间上手 WebGL 还是挺有难度的。
WebGL 门槛相对较高,计算机图形学需要相对较多的数学知识。一个前端程序员或许还熟悉解析几何,但是还熟悉线性代数的应该寥寥无几了(比如求个逆转置矩阵试试?),更何况使用中强调矩阵运算中的物理意义,这在教学中也是比较缺失。
于是,Three.js 对 WebGL 提供的接口进行了非常好的封装,简化了很多细节,大大降低了学习成本。并且,几乎没有损失 WebGL 的灵活性。
因此,从 Three.js 入手是值得推荐的,这可以让你在较短的学习后就能面对大部分需求场景。
总的来说 就是目前在web上如果你想做3D的开发,Three.js是最佳的选择。易上手,灵活性高。
学习资料 推荐 郭隆邦 老师的中文站。 当然英语水平到位,看官网更合适。我这种英语渣还是老老实实看中文站的说~T_T
项目
回到项目,还是上次那个程序
先上效果,上次的是将XML文件解析,平面图形化,这次就多做一步,做成了3D的实体,并且做了一个恶趣味小功能,点击板件,那个板件就会飘出来。
Tips
-
Three.js核心
a.场景
b.相机
c.渲染器
三者缺一不可,场景需要添加场景对象 比如光源(点光源,平行光源,环境光源) 网格模型,模型又由立方体和材质组成。 这些多看手册,不是很难。
相机就是正交相机和透视相机。
渲染器主要就是刷新渲染的问题。 -
编码过程中遇到的小问题:
a 设置camera.lookat() 一直显示不正常,不是按照角度来的,后来调试代码才发现。camara.lookat() 一定要在设置camera.position之后,如果相机的位置都没确定 何来lookat的偏置。
b 选择一个物体,参考了这篇blog,讲的很清楚,但是有一个问题要注意,最后的一句其实是有一点问题的,对于原例是没问题,因为原例中每个实体都是建立的一种新的材质。所以选中时更改object的material对象的颜色属性是ok的,但是对于我这个项目,材质是共用的。如果更改就是更改了所有的material,大家一起变红。
intersects[ i ].object.material.color.set( 0xff0000 )
源码
<!doctype html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<div>
<input type="file" id="files" style="display: none" onchange="fileImport();">
<input type="button" id="fileImport" value="导入">
</div>
<div id="table">
</div>
<canvas id="3Dtable">
</canvas>
<script src="jquery-3.4.1.js"></script>
<script src="Three.js"></script>
<script src=".\three.js-master\examples\js\controls\OrbitControls.js"></script>
<script>
$("#fileImport").click(function () {
$("#files").click();
})
function fileImport() {
var loadMNO = function (cb) {
var selectedFile = document.getElementById('files').files[0];
var name = selectedFile.name;
var size = selectedFile.size;
var reader = new FileReader();//这是核心,读取操作就是由它完成.
reader.readAsText(selectedFile);//读取文件的内容,也可以读取文件的URL
reader.onload = function () {
//当读取完成后回调这个函数,然后此时文件的内容存储到了result中,直接操作即可
var myxml = this.result
cb(this.result)
}
}
loadMNO(function (cb1) {
var parser = new DOMParser();
var xmlDoc2 = parser.parseFromString(cb1, 'text/xml')
var parts = new Object
var listNum = 0
var FOGLIO = xmlDoc2.getElementsByTagName("FOGLIO")
for (var a = 0; a < FOGLIO.length; a++) {
var nome = FOGLIO[a].getElementsByTagName("NOME")
for (var b = 0; b < nome.length; b++) {
var PROFILO = nome[b].getElementsByTagName("PROFILO")
//绘制幕布
var size = PROFILO[0].getElementsByTagName("VARIABILE")
for (var i = 0; i < size.length; i++) {
if (size[i].getAttribute('NAME') == "LPX" || size[i].getAttribute('name') == "LPX")
var xsize = parseInt(size[i].childNodes[0].nodeValue)
if (size[i].getAttribute('NAME') == "LPY" || size[i].getAttribute('name') == "LPY")
var ysize = parseInt(size[i].childNodes[0].nodeValue)
if (size[i].getAttribute('NAME') == "LPZ" || size[i].getAttribute('name') == "LPZ")
var zsize = parseInt(size[i].childNodes[0].nodeValue)
}
var drawTag = PROFILO[0].getElementsByTagName("DRW")
var min_max = drawTag[0].getElementsByTagName("Original")
var pos = PROFILO[0].getElementsByTagName("POS")
for (var k = 0; k < pos.length; k++) {
var xpos = pos[k].getElementsByTagName("X")
var ypos = pos[k].getElementsByTagName("Y")
var deg = pos[k].getElementsByTagName("DEG")
parts = { board: a, xsize: xsize, ysize: ysize, zsize: zsize, x: parseInt(xpos[0].childNodes[0].nodeValue), y: parseInt(ypos[0].childNodes[0].nodeValue), deg: parseInt(deg[0].childNodes[0].nodeValue), minX: parseInt(min_max[0].getAttribute("minX")), minY: parseInt(min_max[0].getAttribute("minY")), maxX: parseInt(min_max[0].getAttribute("maxX")), maxY: parseInt(min_max[0].getAttribute("maxY")) }
partList[listNum] = parts
listNum++
}
}
}
draw()
draw3d()
});
var standard = function (input) {
var width = 0.8 * window.innerWidth;
var height = window.innerHeight;
return parseFloat(input / bordX * width)
}
var bordX = 2440
var bordY = 1220
var partList = []
var partListFor3D = []
var reverseY = function (input) {
return bordY - input
}
function draw() {
var clear = document.getElementById("table")
while (clear.hasChildNodes()) {
clear.removeChild(clear.firstChild)
}
n = 0
for (i = 0; i < partList[partList.length - 1].board + 1; i++) {
var oDiv = document.createElement('div');
canvas = document.createElement('canvas');
oDiv.appendChild(canvas)
document.getElementById("table").appendChild(oDiv)
var width = canvas.width = 0.8 * window.innerWidth;
var height = canvas.height = window.innerHeight;
var ctx = canvas.getContext('2d')
ctx.fillStyle = 'rgb(197, 197, 197)';
ctx.fillRect(0, 0, width, 0.5 * width)
for (j = n; (j < partList.length) && (partList[j].board < i + 1); j++, n = j) {
ctx.fillStyle = 'rgb(231, 143, 43)';
if (partList[j].deg == 0)
ctx.fillRect(standard(partList[j].x + partList[j].minX), standard(reverseY(partList[j].y + partList[j].minY)), standard(partList[j].maxX - partList[j].minX), standard(-partList[j].maxY + partList[j].minY))
if (partList[j].deg == -90 || partList[j].deg == 270)
ctx.fillRect(standard(partList[j].x + partList[j].minY), standard(reverseY(partList[j].y - partList[j].minX)), standard(partList[j].maxY - partList[j].minY), standard(partList[j].maxX - partList[j].minX))
if (partList[j].deg == 90)
ctx.fillRect(standard(partList[j].x - partList[j].minY), standard(reverseY(partList[j].y + partList[j].minX)), standard(-partList[j].maxY + partList[j].minY), standard(-partList[j].maxX + partList[j].minX))
}
}
}
function draw3d() {
m=0
var _3dModelList = []
var scene = new THREE.Scene()
// var geometry = new THREE.BoxGeometry(100, 100, 100)
var material = new THREE.MeshLambertMaterial({ color: 0XC55724 })
for (r = 0; r < partList[partList.length - 1].board + 1; r++) {
var MDF = new THREE.MeshLambertMaterial({ color: 0XC5C5C5 })
var MdfBoard = new THREE.BoxGeometry(bordX, bordY, 20)
var meshMDF = new THREE.Mesh(MdfBoard, MDF)
scene.add(meshMDF)
meshMDF.position.set(bordX/ 2+3000*r, bordY / 2, -20)
console.log(meshMDF.position)
for (j = m; (j < partList.length) && (partList[j].board < r+ 1); j++, m = j) {
partListFor3D[j] = partList[j]
// console.log(partListFor3D[j].deg)
console.log(r+" "+j)
console.log(partList[j].board)
if (partListFor3D[j].deg == 0) {
partListFor3D[j].x = parseFloat(partListFor3D[j].x) +parseFloat(partListFor3D[j].minX) + parseFloat(partListFor3D[j].maxX-partListFor3D[j].minX) / 2+r*3000
partListFor3D[j].y = parseFloat(partListFor3D[j].y) + parseFloat(partListFor3D[j].minY) +parseFloat(partListFor3D[j].maxY-partListFor3D[j].minY) / 2
partListFor3D[j].xsize=parseFloat(partListFor3D[j].maxX-partListFor3D[j].minX)
partListFor3D[j].ysize=parseFloat(partListFor3D[j].maxY-partListFor3D[j].minY)
}
if (partList[j].deg == -90 || partList[j].deg == 270) {
var temp
temp = partListFor3D[j].ysize
partListFor3D[j].ysize = parseFloat(partListFor3D[j].maxX-partListFor3D[j].minX)
partListFor3D[j].xsize = parseFloat(partListFor3D[j].maxY-partListFor3D[j].minY)
partListFor3D[j].x = parseFloat(partListFor3D[j].x) + parseFloat(partListFor3D[j].xsize)/2+partListFor3D[j].minY+r*3000
partListFor3D[j].y = parseFloat(partListFor3D[j].y) - parseFloat(partListFor3D[j].ysize)/2-partListFor3D[j].minX
}
if (partList[j].deg == 90) {
partListFor3D[j].ysize = parseFloat(partListFor3D[j].maxX-partListFor3D[j].minX)
partListFor3D[j].xsize = parseFloat(partListFor3D[j].maxY-partListFor3D[j].minY)
partListFor3D[j].x = parseFloat(partListFor3D[j].x) + parseFloat(partListFor3D[j].xsize)/2-partListFor3D[j].maxY+r*3000
partListFor3D[j].y = parseFloat(partListFor3D[j].y) + parseFloat(partListFor3D[j].ysize)/2+partListFor3D[j].minX
}
}
}
for (i = 0; i < partListFor3D.length; i++) {
var _3dModel = new THREE.BoxGeometry(partListFor3D[i].xsize, partListFor3D[i].ysize, partListFor3D[i].zsize)
var mesh = new THREE.Mesh(_3dModel, material)
_3dModelList[i] = mesh
scene.add(mesh)
mesh.position.set(partListFor3D[i].x, partListFor3D[i].y, 0)
}
var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();
var goUp = function (e) {
// console.log(e)
function up() {
e.position.z += 0.05
}
setInterval(up, 1.0 / 60)
}
function onMouseClick(event) {
//通过鼠标点击的位置计算出raycaster所需要的点的位置,以屏幕中心为原点,值的范围为-1到1.
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = - (event.clientY / window.innerHeight) * 2 + 1;
// 通过鼠标点的位置和当前相机的矩阵计算出raycaster
raycaster.setFromCamera(mouse, camera);
// 获取raycaster直线和所有模型相交的数组集合
var intersects = raycaster.intersectObjects(scene.children);
console.log(intersects);
// mesh.material.color.set(0xff0000)
//将所有的相交的模型的颜色设置为红色,如果只需要将第一个触发事件,那就数组的第一个模型改变颜色即可
for (var i = 0; i < intersects.length; i++) {
goUp(intersects[0].object)
}
}
window.addEventListener('click', onMouseClick, false);
var point1 = new THREE.PointLight(0xffffff)
var point2 = new THREE.PointLight(0xffffff)
point1.position.set(1000, 500, 1000)
point2.position.set(5000, 200, 1000)
scene.add(point1)
scene.add(point2)
var ambient = new THREE.AmbientLight(0x444444)
scene.add(ambient)
var width1 = window.innerWidth
var height1 = window.innerHeight
var k = width1 / height1
var s = 3000
var axisHelper = new THREE.AxesHelper(2500)
//scene.add(axisHelper)
var camera = new THREE.OrthographicCamera(-s * k, s * k, s, -s, 1, 100000)
camera.position.set(300, 600, 5000)
camera.lookAt(meshMDF.position)
console.log(meshMDF.position)
var renderer = new THREE.WebGLRenderer({ canvas: document.getElementById("3Dtable") });
renderer.setSize(width1, height1)
renderer.setClearColor(0xb9d3ff, 1)
document.body.appendChild(renderer.domElement)
function render() {
renderer.render(scene, camera)
requestAnimationFrame(render)
}
render();
var controls = new THREE.OrbitControls(camera, renderer.domElement);
//controls.addEventListener('change',render)
}
}
</script>
</body>
</html>