引用:官方源码
在官方给出的源码中采用的模型格式是tfgl格式,查看源码可知新建DecalGeometry对象时,传入的参数为mesh, position, orientation, size,因此对obj文件不可以直接操作,需要遍历obj中的children,在这里采用drc格式(drc为使用Draco压缩后的格式)的模型实现贴花效果。
DecalGeometry源码中传入的参数:
function DecalGeometry( mesh, position, orientation, size ){...}
实现贴花效果,需要借助DecalGeometry.js,因此要其进行引入:
<script src="js/geometries/DecalGeometry.js"></script>
接着新建贴花时需要的参数,raycaster用于拾取鼠标点击的对象,intersection中有intersect、point、normal,intersect为boolean用于判断鼠标点击的位置是否属于模型表面,point与normal用于存储鼠标点击模型时的point与normal。
var mouse=new THREE.Vector2();
var moved;//
var line;//跟随鼠标
var raycaster;//拾取射线
var intersection;//相交线
var decalPosition;//贴花位置
var decalOrientation;//贴花方向
var decalMaterial;//贴花材质
var decals;//贴花数组
var decalSize;//贴花数组
var mouseHelper;
对参数进行初始化,首先在场景中添加line,若物体表面出现线条,则表明当前位置允许进行贴花操作,通过textureLoader加载所需要的贴花纹理:
function initDecalData(){
let geometry=new THREE.BufferGeometry();
geometry.setFromPoints([new THREE.Vector3(),new THREE.Vector3()]);
line=new THREE.Line(geometry,new THREE.LineBasicMaterial({color:0xff0000}));
scene.add(line);
decalSize=new THREE.Vector3( 10, 10, 10 );
decalPosition=new THREE.Vector3();
decalOrientation=new THREE.Euler();
decals=[];
mouseHelper = new THREE.Mesh( new THREE.BoxBufferGeometry( 1, 1, 10 ), new THREE.MeshNormalMaterial() );
mouseHelper.visible = false;
scene.add( mouseHelper );//
let textureLoader=new THREE.TextureLoader();
let decalDiffuse=textureLoader.load( 'textures/decal/decal-diffuse.png' );//贴花
let decalNormal=textureLoader.load( 'textures/decal/decal-normal.jpg' );
decalMaterial=new THREE.MeshLambertMaterial( {
pecular: 0x444444,
map: decalDiffuse,//贴花图
normalMap: decalNormal,//法向量纹理
normalScale: new THREE.Vector2( 1, 1 ),
shininess: 30,
transparent: true,//透明度
depthTest: true,
depthWrite: false,
polygonOffset: true,
polygonOffsetFactor: - 4,
wireframe: false
} );
intersection={//相交线
intersects:false,
point:new THREE.Vector3(),
normal:new THREE.Vector3()
};
}
通过拾取射线对选取的贴花的位置进行判断,是否位于模型表面。当位于模型表面时,获取点击模型时的point:
function checkIntersection(){
if(!model) return;
raycaster=new THREE.Raycaster();//拾取射线
raycaster.setFromCamera(mouse,camera);
let intersects=raycaster.intersectObjects([model]);
//console.log(intersects);
if(intersects.length>0){//在模型表面
let modelPoint=intersects[0].point;//获取模型的point
mouseHelper.position.copy(modelPoint);
intersection.point.copy(modelPoint);
let modelNormal=intersects[0].face.normal;//.clone();
modelNormal.transformDirection(model.matrixWorld);
modelNormal.multiplyScalar(10);
modelNormal.add(modelPoint);
intersection.normal.copy(modelNormal);
mouseHelper.lookAt(modelNormal);
let decalPosition=line.geometry.attributes.position;
decalPosition.setXYZ(0,modelPoint.x,modelPoint.y,modelPoint.z);
decalPosition.setXYZ(1,modelNormal.x,modelNormal.y,modelNormal.z);
decalPosition.needsUpdate=true;
intersection.intersects=true;//在模型表面 则贴花
}else{
intersection.intersects=false;
}
}
将需要进行贴花操作的模型、获取的point即decalPosition等传入DecalGeometry中,进行贴花操作:
function shoot() {//实现贴花
decalPosition.copy( intersection.point );
decalOrientation.copy( mouseHelper.rotation );
// if ( params.rotate ) orientation.z = Math.random() * 2 * Math.PI;//params为GUI组件
// var scale = params.minScale + Math.random() * ( params.maxScale - params.minScale );
// size.set( scale, scale, scale );
var material = decalMaterial.clone();//克隆贴花材质
material.color.setHex( Math.random() * 0xffffff );
var m = new THREE.Mesh( new THREE.DecalGeometry( model, decalPosition, decalOrientation, decalSize ), material );
decals.push( m );//放入数组中,为了后续的清除操作
scene.add( m );//场景中添加贴花
console.log(decals);
}
事件监听部分,需要将鼠标点击的二维坐标转换为三维坐标:
window.addEventListener( 'mousedown', function () {
moved = false;
}, false );
window.addEventListener( 'mouseup', function () {//鼠标抬起
checkIntersection();//判断是否为合理的位置
if ( ! moved && intersection.intersects ) shoot();//进行贴花
} );
window.addEventListener( 'mousemove', onTouchMove );
window.addEventListener( 'touchmove', onTouchMove );
function onTouchMove( event ) {//获取三维场景的坐标
var x, y;
if ( event.changedTouches ) {
x = event.changedTouches[ 0 ].pageX;
y = event.changedTouches[ 0 ].pageY;
} else {
x = event.clientX;
y = event.clientY;
}
mouse.x = ( x / window.innerWidth ) * 2 - 1;
mouse.y = - ( y / window.innerHeight ) * 2 + 1;
//console.log(mouse.x+","+mouse.y);
checkIntersection();//合理的位置内线条始终跟着鼠标
}
运行代码后,点击模型中的任意位置,都可以实现贴花效果,实现如下:
点击后:
完整代码:
HTML部分:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>贴花</title>
<script type="text/javascript" src="build/three.js"></script>
<script type="text/javascript" src="js/loaders/DRACOLoader.js"></script>
<script type="text/javascript" src="js/loaders/OBJLoader.js"></script>
<script type="text/javascript" src="js/loaders/MTLLoader.js"></script>
<script type="text/javascript" src="js/geometries/DecalGeometry.js"></script>
<script type="text/javascript" src="js/controls/OrbitControls.js"></script>
</head>
<body>
<div id="webgl-out"></div>
<script type="text/javascript" src="drcDecals.js"></script>
</body>
</html>
JS部分:
var scene;
var camera;
var renderer;
var ambientLight,spotLight;
var orbitControls;
var model;
var box;
var mouse=new THREE.Vector2();
var moved;//
var line;//跟随鼠标
var raycaster;//拾取射线
var intersection;//相交线
var decalPosition;//贴花位置
var decalOrientation;//贴花方向
var decalMaterial;//贴花材质
var decals;//贴花数组
var decalSize;//贴花数组
var mouseHelper;
init();
animate();
function init(){
moved = false;
initScene();
initRenderer();
initCamera();
addOrbitControls();
addLight();
initDecalData();
addModeldrc({//添加模型
drcPath:"obj/OM132326单150dpi_OM132326/",
drcName:"OM132326单150dpi_OM132326_outside.drc",
texturePath:"obj/OM132326单150dpi_OM132326/OM132326单150dpi_OM132326-textures/",
textureName:"outside_tex_3a1887db.png"
});
document.getElementById("webgl-out").appendChild(renderer.domElement);
window.addEventListener("resize",onWindowResize,false);
orbitControls.addEventListener( 'change', function () {
moved = true;
} );
window.addEventListener( 'mousedown', function () {
moved = false;
}, false );
window.addEventListener( 'mouseup', function () {//鼠标抬起
checkIntersection();//判断是否为合理的位置
if ( ! moved && intersection.intersects ) shoot();//进行贴花
} );
window.addEventListener( 'mousemove', onTouchMove );
window.addEventListener( 'touchmove', onTouchMove );
function onTouchMove( event ) {//获取三维场景的坐标
var x, y;
if ( event.changedTouches ) {
x = event.changedTouches[ 0 ].pageX;
y = event.changedTouches[ 0 ].pageY;
} else {
x = event.clientX;
y = event.clientY;
}
mouse.x = ( x / window.innerWidth ) * 2 - 1;
mouse.y = - ( y / window.innerHeight ) * 2 + 1;
//console.log(mouse.x+","+mouse.y);
checkIntersection();//合理位置内线条始终跟着鼠标
}
}
function initScene(){
scene=new THREE.Scene();
}
function initRenderer(){
renderer=new THREE.WebGLRenderer({antialias:true});//抗锯齿
renderer.setSize(window.innerWidth,window.innerHeight);
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setClearColor(new THREE.Color(0x4f6cca));
}
function initCamera(){
camera=new THREE.PerspectiveCamera( 45, window.innerWidth/window.innerHeight, 1, 1000 );
camera.position.set(0,0,500);
scene.add(camera);
}
function addOrbitControls(){
orbitControls=new THREE.OrbitControls(camera,renderer.domElement);
orbitControls.autoRotate=false;
orbitControls.zoomSpeed=1.2;
orbitControls.rotateSpeed=0.5;
orbitControls.stopAutoRotateWhenTuch=true;
orbitControls.enableDamping=false;
orbitControls.minDistance=60;
orbitControls.maxDistance=900;
orbitControls.intensity=0.1;//灵敏度
orbitControls.enablePan=true;//启用右键
}
function addLight(){
ambientLight=new THREE.AmbientLight( {color:0x0fffff} );
scene.add(ambientLight);
spotLight=new THREE.SpotLight({color:0xffffff});
spotLight.position.set(100,300,1300);
scene.add(spotLight);
}
function addModeldrc(drcDef){//加载drc文件
var material;
var textureLoader=new THREE.TextureLoader();
var dracoLoader=new THREE.DRACOLoader();
THREE.DRACOLoader.setDecoderConfig( { type: 'js' } );//js类型
//dracoLoader.setPath("obj/OM132326单150dpi_OM132326/");
dracoLoader.setPath(drcDef.drcPath);
//dracoLoader.load("OM132326单150dpi_OM132326.drc",function(mesh){
dracoLoader.load(drcDef.drcName,function(mesh){
//var texture=new THREE.ImageUtils.loadTexture(texturePath+"outside_tex_3a1887db.png");
var texture=new THREE.ImageUtils.loadTexture(drcDef.texturePath+drcDef.textureName);
material=new THREE.MeshLambertMaterial();
//material.side=THREE.FrontSide;
material.side=THREE.BreakSide;
material.map=texture;
material.needsUpdate=true;
model=new THREE.Mesh(mesh,material);
setModelPosition(model);
scene.add(model);
},onProgress, onError);
var onProgress = function ( xhr ) {
if ( xhr.lengthComputable ) {
var percentComplete = xhr.loaded / xhr.total * 100;
console.log( Math.round(percentComplete, 2) + '% downloaded' );
}
};
var onError = function ( xhr ) { alert("错误了") ;};
}
function setModelPosition(model){
box=new THREE.Box3().setFromObject(model);
var scaleNum=1;
var modelMax=box.max;
var modelMin=box.min;
var modelWidth=modelMax.x-modelMin.x;
var modelHeight=modelMax.y-modelMin.y;
if(modelWidth>window.innerWidth/10){
scaleNum=window.innerWidth/(10*modelWidth);
model.scale.set(scaleNum,scaleNum,scaleNum);//设置模型的缩放大小
}
if(modelHeight>window.innerHeight/3){
if(scaleNum>window.innerHeight/(3*modelHeight)){
scaleNum=window.innerHeight/(3*modelHeight);
model.scale.set(scaleNum,scaleNum,scaleNum);//设置模型的缩放大小
}
}
console.log(scaleNum);
box.max.x*=scaleNum;//重置包围盒的大小
box.max.y*=scaleNum;
box.max.z*=scaleNum;
box.min.x*=scaleNum;
box.min.y*=scaleNum;
box.min.z*=scaleNum;
console.log(box);
console.log(model.position);
box.getCenter(model.position);
model.position.multiplyScalar(-1);
}
function initDecalData(){
let geometry=new THREE.BufferGeometry();
geometry.setFromPoints([new THREE.Vector3(),new THREE.Vector3()]);
line=new THREE.Line(geometry,new THREE.LineBasicMaterial({color:0xff0000}));
scene.add(line);
decalSize=new THREE.Vector3( 10, 10, 10 );
decalPosition=new THREE.Vector3();
decalOrientation=new THREE.Euler();
decals=[];
mouseHelper = new THREE.Mesh( new THREE.BoxBufferGeometry( 1, 1, 10 ), new THREE.MeshNormalMaterial() );
mouseHelper.visible = false;
scene.add( mouseHelper );//
let textureLoader=new THREE.TextureLoader();
let decalDiffuse=textureLoader.load( 'textures/decal/decal-diffuse.png' );//贴花
let decalNormal=textureLoader.load( 'textures/decal/decal-normal.jpg' );
decalMaterial=new THREE.MeshLambertMaterial( {
pecular: 0x444444,
map: decalDiffuse,//贴花图
normalMap: decalNormal,//法向量纹理
normalScale: new THREE.Vector2( 1, 1 ),
shininess: 30,
transparent: true,//透明度
depthTest: true,
depthWrite: false,
polygonOffset: true,
polygonOffsetFactor: - 4,
wireframe: false
} );
intersection={//相交线
intersects:false,
point:new THREE.Vector3(),
normal:new THREE.Vector3()
};
}
function checkIntersection(){
if(!model) return;
raycaster=new THREE.Raycaster();//拾取射线
raycaster.setFromCamera(mouse,camera);
let intersects=raycaster.intersectObjects([model]);
//console.log(intersects);
if(intersects.length>0){//在模型表面
let modelPoint=intersects[0].point;//获取模型的point
mouseHelper.position.copy(modelPoint);
intersection.point.copy(modelPoint);
let modelNormal=intersects[0].face.normal;//.clone();
modelNormal.transformDirection(model.matrixWorld);
modelNormal.multiplyScalar(10);
modelNormal.add(modelPoint);
intersection.normal.copy(modelNormal);
mouseHelper.lookAt(modelNormal);
let decalPosition=line.geometry.attributes.position;
decalPosition.setXYZ(0,modelPoint.x,modelPoint.y,modelPoint.z);
decalPosition.setXYZ(1,modelNormal.x,modelNormal.y,modelNormal.z);
decalPosition.needsUpdate=true;
intersection.intersects=true;//在模型表面 则贴花
}else{
intersection.intersects=false;
}
}
function shoot() {//实现贴花
decalPosition.copy( intersection.point );
decalOrientation.copy( mouseHelper.rotation );
// if ( params.rotate ) orientation.z = Math.random() * 2 * Math.PI;//params为GUI组件
// var scale = params.minScale + Math.random() * ( params.maxScale - params.minScale );
// size.set( scale, scale, scale );
var material = decalMaterial.clone();//克隆贴花材质
material.color.setHex( Math.random() * 0xffffff );
var m = new THREE.Mesh( new THREE.DecalGeometry( model, decalPosition, decalOrientation, decalSize ), material );
decals.push( m );//放入数组中,为了后续的清除操作
scene.add( m );//场景中添加贴花
console.log(decals);
}
function onWindowResize(){
camera.aspect=window.innerWidth/window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth,window.innerHeight);
}
function animate(){
requestAnimationFrame(animate);
render();
}
function render(){
renderer.render(scene,camera);
}