前言
最近有需求将一系列不同类型的3D文件在web端做出展示,即使从未接触过web端图形编程,three.js会提供很好的帮助,而对应的完成加载模型这些需求显得非常简单,很多格式提供了three的加载器,这些代码在互联网很容易找到,然而部分格式[.off]的加载器暂时没有,笔者在这里简单写了一个
以下javascript代码可以直接复制使用,案例供参考
一、分析OFF文件
OFF
8 9 0
-0.274878 -0.274878 -0.274878
-0.274878 0.274878 -0.274878
0.274878 0.274878 -0.274878
0.274878 -0.274878 -0.274878
-0.274878 -0.274878 0.274878
-0.274878 0.274878 0.274878
0.274878 0.274878 0.274878
0.274878 -0.274878 0.274878
1 0 1 0 0
1 1 1 0 0
1 2 1 0 0
1 3 1 0 0
1 4 1 0 0
1 5 1 0 0
1 6 1 0 0
1 7 1 0 0
1 8 0 1 0
这就是OFF文件的内容。第一行固定为OFF,第二行为3个数字,a,b,c分别表示点的数量,面的数量,边的数量。实际上边的数量比较鸡肋,很多OFF文件边的数量直接填0了。从第二行开始的a行数组表示点的坐标,例如:-0.274878 -0.274878 -0.274878,表示第一个点的坐标为-0.274878 -0.274878 -0.274878。这里有a个点。从第a+1行开始的b行数组表示面的构成信息,第一个数字表示构成面的点的数量,一般为3或者4,后面跟着对应数量的数字,为点的下标。
二、读取OFF文件,将点面信息保存起来
1.创建点面对象
构造一个对象起名OFFTEMP,对象初始化点,面,边数量,同时创建出点数组和边数组,顺便定义一个材质对象,使得该对象在渲染的时候直可以由自己直接传递材质,再定义一个读取OFF源文件的方法,将其点,面读取出来
个人需要,读取文件系统的url,对于文件解析方式不固定,只要能读取出off文件的内容即可
function OFFTEMP(v,f,g)
{
this.Material = null;
this.title = "";
this.Member_V = v;
this.Member_F = f;
this.Member_G = g;
this.Set=function(x,y,z) {
this.Member_V = x;
this.Member_F = y;
this.Member_G = z;
};
this.PointList=[];
this.Facelist=[];
this.SetMaterial = function(m)
{
this.Material = m;
}
this.ReadingOFFFile=function(url){
var htmlobj= $.ajax({url:url,async:false});
var dataString = htmlobj.responseText;
var DataStr = dataString.split('\r\n');
let title = DataStr[0];
this.Set(parseInt(DataStr[1].split(/\s+/)[0]),parseInt(DataStr[1].split(/\s+/)[1]),parseInt(DataStr[1].split(/\s+/)[2]));
var beginline = 2;
var endline = 2+this.Member_V;
for(let i =2;i<endline;i++)
{
let beginindex = 0;
var pointitem = {};
var dline = DataStr[i].split(/\s+/);
for(let i =0;i<dline.length;i++)
{
if(dline[i]==""||dline[i]==" "||dline[i]==undefined)
{
beginindex++;
}
else
{
break;
}
}
pointitem = new THREE.Vector3(dline[0+beginindex],dline[1+beginindex],dline[2+beginindex]);
this.PointList.push(pointitem);
}
for(let j = endline;j<endline+this.Member_F;j++)
{
var faceitem = [];
var dline_vex = DataStr[j].split(/\s+/);
let beginindex = 0;
for(let i =0;i<dline_vex.length;i++)
{
if(dline_vex[i]==""||dline_vex[i]==" "||dline_vex[i]==undefined)
{
beginindex++;
}
else
{
break;
}
}
var count = parseInt(dline_vex[beginindex]);
for(let x = beginindex+1;x<dline_vex.length;x++)
{
if(dline_vex[x]!='')
{
faceitem.push(parseInt(dline_vex[x]));
}
}
this.Facelist.push(faceitem);
}
}
}
OFF文件使用大量的空格分割,把握好每行的数据,就能很容易的把点面数据读取出来。
2.尝试渲染点
代码如下(示例):
this.CreatGponit=function() {
let geometry = new THREE.Geometry();
for(let i =0;i<this.PointList.length;i++)
{
geometry.vertices.push(this.PointList[i]);
}
let material = new THREE.PointsMaterial( {color: 'red', size:1} );
let mesh = new THREE.Points( geometry, material );
return mesh;
}
在OFFTEMP对象中增加此方法,通过调用此方法来将自己的点形成网格。
let ot = new OFFTEMP();
ot.ReadingOFFFile(url1);
let mesh = ot.CreatGponit();
mesh.scale.set(12, 12, 12);
scene.children.push(mesh);
mcloud = mesh;
根据点的情况,看起来提取数据没有太大的问题,再将面形成,实现三角化。这里可能需要先手写法线计算,3点确定一个平面,通过公式即可正确计算法线
this.GetNomal=function(point1,point2,point3){
let x;
let y;
let z;
x = (point2.y-point1.y)*(point3.z-point1.z) - (point2.z-point1.z)*(point3.y-point1.y);
y = (point2.z-point1.z)*(point3.x-point1.x) - (point2.x-point1.x)*(point3.z-point1.z);
z = (point2.x-point1.x)*(point3.y-point1.y) - (point2.y-point1.y)*(point3.x-point1.x);
return new THREE.Vector3(x,y,z)
};
然后封装出形成网格的方法
this.CreatFace=function(){
let geometry = new THREE.Geometry();
for(let i =0;i<this.PointList.length;i++)
{
let thispoint =this.PointList[i];
geometry.vertices.push(thispoint);
}
var met = []
let material2 = new THREE.MeshLambertMaterial( {color: 'yellow'} );
for(let j =0;j<this.Facelist.length;j++)
{
if(this.Facelist[j].length==3)
{
var v1 = this.Facelist[j][0];
var v2 = this.Facelist[j][1];
var v3 = this.Facelist[j][2];
let face = new THREE.Face3(v1, v2, v3, this.GetNomal(this.PointList[v1],this.PointList[v2],this.PointList[v3]), new THREE.Color(), 0);
geometry.faces.push(face);
}
}
let mesh = new THREE.Mesh( geometry, this.Material);
return mesh;
}
再将这些方法集中一下,提供好材质,直接加入场景。没有很大的材质效果需求,用matcap是非常省时省力的。这里再封装一个填入材质生成网格的方法。
this.CreatMeshWithMaterial = function(m)
{
let geometry = new THREE.Geometry();
for(let i =0;i<this.PointList.length;i++)
{
let thispoint = this.PointList[i];
geometry.vertices.push(thispoint);
}
var met = []
let material2 = new THREE.MeshLambertMaterial( {color: 'yellow'} );
for(let j =0;j<this.Facelist.length;j++)
{
if(this.Facelist[j].length==3)
{
var v1 = this.Facelist[j][0];
var v2 = this.Facelist[j][1];
var v3 = this.Facelist[j][2];
let face = new THREE.Face3(v1, v2, v3, this.GetNomal(this.PointList[v1],this.PointList[v2],this.PointList[v3]), new THREE.Color(), 0);
geometry.faces.push(face);
}
}
let mesh = new THREE.Mesh( geometry, m);
return mesh;
}
this.LoadOFFobject = function(url,m)
{
this.ReadingOFFFile(url);
return this.CreatMeshWithMaterial(m);
}
一般来说材质设置 side: THREE.DoubleSide,,我这里使用的模型是封闭面的,所以看不出什么区别。最终效果如下
完整代码,可以直接使用对象调用。
function OFFTEMP(v,f,g)
{
this.Material = null;
this.title = "";
this.Member_V = v;
this.Member_F = f;
this.Member_G = g;
this.Set=function(x,y,z) {
this.Member_V = x;
this.Member_F = y;
this.Member_G = z;
};
this.PointList=[];
this.Facelist=[];
this.ReadingOFFFile=function(url){
var htmlobj= $.ajax({url:url,async:false});
var dataString = htmlobj.responseText;
var DataStr = dataString.split('\r\n');
let title = DataStr[0];
this.Set(parseInt(DataStr[1].split(/\s+/)[0]),parseInt(DataStr[1].split(/\s+/)[1]),parseInt(DataStr[1].split(/\s+/)[2]));
var beginline = 2;
var endline = 2+this.Member_V;
for(let i =2;i<endline;i++)
{
let beginindex = 0;
var pointitem = {};
var dline = DataStr[i].split(/\s+/);
for(let i =0;i<dline.length;i++)
{
if(dline[i]==""||dline[i]==" "||dline[i]==undefined)
{
beginindex++;
}
else
{
break;
}
}
pointitem = new THREE.Vector3(dline[0+beginindex],dline[1+beginindex],dline[2+beginindex]);
this.PointList.push(pointitem);
}
for(let j = endline;j<endline+this.Member_F;j++)
{
var faceitem = [];
var dline_vex = DataStr[j].split(/\s+/);
let beginindex = 0;
for(let i =0;i<dline_vex.length;i++)
{
if(dline_vex[i]==""||dline_vex[i]==" "||dline_vex[i]==undefined)
{
beginindex++;
}
else
{
break;
}
}
var count = parseInt(dline_vex[beginindex]);
for(let x = beginindex+1;x<dline_vex.length;x++)
{
if(dline_vex[x]!='')
{
faceitem.push(parseInt(dline_vex[x]));
}
}
this.Facelist.push(faceitem);
}
}
this.CreatGponit=function() {
let geometry = new THREE.Geometry();
for(let i =0;i<this.PointList.length;i++)
{
geometry.vertices.push(this.PointList[i]);
}
let material = new THREE.PointsMaterial( {color: 'red', size:1} );
let mesh = new THREE.Points( geometry, material );
return mesh;
}
this.CreatGponit_c=function() {
let geometry = new THREE.Geometry();
for(let i =0;i<this.PointList.length;i++)
{
this.PointList[i].x+=20;
this.PointList[i].y+=20;
this.PointList[i].z+=20;
geometry.vertices.push(this.PointList[i]);
}
let material = new THREE.PointsMaterial( {color: 'red', size:1} );
let mesh = new THREE.Points( geometry, material );
return mesh;
}
this.SetMaterial = function(m)
{
this.Material = m;
}
this.CreatFace=function(){
let geometry = new THREE.Geometry();
for(let i =0;i<this.PointList.length;i++)
{
let thispoint =this.PointList[i];
geometry.vertices.push(thispoint);
}
var met = []
let material2 = new THREE.MeshLambertMaterial( {color: 'yellow'} );
for(let j =0;j<this.Facelist.length;j++)
{
if(this.Facelist[j].length==3)
{
var v1 = this.Facelist[j][0];
var v2 = this.Facelist[j][1];
var v3 = this.Facelist[j][2];
let face = new THREE.Face3(v1, v2, v3, this.GetNomal(this.PointList[v1],this.PointList[v2],this.PointList[v3]), new THREE.Color(), 0);
geometry.faces.push(face);
}
}
let mesh = new THREE.Mesh( geometry, this.Material);
return mesh;
}
this.CreatMeshWithMaterial = function(m)
{
let geometry = new THREE.Geometry();
for(let i =0;i<this.PointList.length;i++)
{
let thispoint = this.PointList[i];
geometry.vertices.push(thispoint);
}
var met = []
let material2 = new THREE.MeshLambertMaterial( {color: 'yellow'} );
for(let j =0;j<this.Facelist.length;j++)
{
if(this.Facelist[j].length==3)
{
var v1 = this.Facelist[j][0];
var v2 = this.Facelist[j][1];
var v3 = this.Facelist[j][2];
let face = new THREE.Face3(v1, v2, v3, this.GetNomal(this.PointList[v1],this.PointList[v2],this.PointList[v3]), new THREE.Color(), 0);
geometry.faces.push(face);
}
}
let mesh = new THREE.Mesh( geometry, m);
return mesh;
}
this.GetNomal=function(point1,point2,point3){
let x;
let y;
let z;
x = (point2.y-point1.y)*(point3.z-point1.z) - (point2.z-point1.z)*(point3.y-point1.y);
y = (point2.z-point1.z)*(point3.x-point1.x) - (point2.x-point1.x)*(point3.z-point1.z);
z = (point2.x-point1.x)*(point3.y-point1.y) - (point2.y-point1.y)*(point3.x-point1.x);
return new THREE.Vector3(x,y,z)
};
this.LoadOFFobject = function(url,m)
{
this.ReadingOFFFile(url);
return this.CreatMeshWithMaterial(m);
}
}
可以正常运行,有问题找。