昨天学习了Three.js的入门教程,然后就想做个小项目练手,首先想到的就是以前在网页上看的模拟故宫,觉得好漂亮呀!感觉在电脑的web上浏览就像进入了真的故宫一样。那么我的小项目受到此启发,我要做一个模拟书架。因为我家书很多,然后放书的位置也很多,包括书架,小书架,3个小柜子,桌子上,箱子中。所以导致我找书很麻烦,这次五一我就想整理下一,给每本书贴个标签排序。当然我可以用表格记录每个标号的书名称及位置。但是关于位置记录不可能描述的很到位,基本上就是书架或者小柜子等。
快速学习了Three.js后,我觉得我可以做一个模拟书架,这样用鼠标单击某本书就会弹出它的位置和书名。这样位置信息就很详细,比如书架第一行,也很直观。比用文字描述来的有趣。
有了想法(项目需求)后,我要分解下任务。通过入门教程如下些内容我还没有尝试过。所以做为小的专攻主题,一一解决了。(关于blender我制作时用了多个贴图,但是three.js如何导入多个贴图不清楚,贴图特效是在Three.js中直接做的)
- 会用webstorm调试(我之前python都是用pycharm,它们一脉相承,所以瞬间学会)
- threejs对象拾取。
- 创建文字--最后学习了ccs,用此方法。
- 随机创建创建多个长方形对象。
- blender绘制材质及贴图后导出。
- Three.js导入obj模型及贴图。
- 导入Json数据。(所有书籍信息,暂时还没有完善信息)
这些主题学习的过程中,我感觉到了经验的迁移, 以前学的各种知识点和技能都派上用处了。但是我发现js的代码要是定义一个结构体数组都不太方便,另外js代码有错误也不提示,必须通过调试看出来,还好一开始我就选择了webstorm。还是它的函数function{}中的内容不是按顺序来执行的,我暂时还没搞清楚。明天需要专门补下js的课程,这样可以系统的了解js,而不仅仅是学习Three.js。
最后动态效果(单击某本书会移出书架到左边显示,并且显示书名,单击书架则书名无显示)今天只实现了一个书架,将来还会添加小柜子,桌子,箱子等。另外,背景等需要美化,若之后不继续精益求精,还不如看表格了哈,花费的时间只能算是js初学者练手,不能算正式的制作工具。
books.gif
静态效果
image.png
blender2.81原始渲染的书架(我也只是入门水平)
image.png
我未经过优化的源码(对js的函数及调用顺序不太清楚,先顺序着写了)
<!DOCTYPE html>
<html>
<head>
<title>three.js css3d - molecules</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<style>
body {
background-color: #050505;
background: radial-gradient(ellipse at center, rgba(43,45,48,1) 0%,rgba(0,0,0,1) 100%);
}
#topmenu {
position: absolute;
top: 50px;
width: 50%;
padding: 10px;
box-sizing: border-box;
text-align: left;
display:block;
}
label {
color: rgb(255, 0, 0);
background: rgb(255,255,255,1);
border: 0px;
padding: 5px 10px;
margin: 2px;
font-size: 14px;
}
.tagBook {
color: #FFF;
font-family: sans-serif;
padding: 2px;
background: rgba( 0, 0, 0, .6 );
}
</style>
</head>
<body>
<div id="container"></div>
<div id="topmenu">
<label id="taga">文件名:未选择书籍</label>
</div>
<script type="text/javascript" src="./libs/three.js"></script>
<script type="text/javascript" src="./libs/OrbitControls.js"></script>
<script type="text/javascript" src="./libs/OBJLoader.js"></script>
<script type="text/javascript" src="./libs/MTLLoader.js"></script>
<script type="text/javascript" src="./libs/GLTFLoader.js"></script>
<script type="text/javascript" src="./libs/CSS2DRenderer.js"></script>
<script type="application/json" src="./data/data.json"></script>
<script >
var scene = null;
var camera = null;
var renderer = null;
var mesh = null;
var id = null;
var INTERSECTED=null;
var SELECTED = null;
var SELECTEDpos =new THREE.Vector3();
var bookstores =null;
var materials = null;
function readTextFile(file, callback) {
var rawFile = new XMLHttpRequest();
rawFile.overrideMimeType("application/json");
rawFile.open("GET", file, true);
rawFile.onreadystatechange = function() {
if (rawFile.readyState === 4 && rawFile.status == "200") {
callback(rawFile.responseText);
}
}
rawFile.send(null);
}
readTextFile("./data/data.json", function(text){
bookstores = JSON.parse(text);
console.log(bookstores);
init();
});
function init() {
renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.getElementById('container').appendChild(renderer.domElement);
scene = new THREE.Scene();//创建场景
//var axisHelper = new THREE.AxisHelper(500);
//scene.add(axisHelper);
object = new THREE.Object3D();
scene.add(object);
var geometry = new THREE.BoxGeometry(0.02, 0.20, 0.1);
var tempH = 0;
for (var i = 0; i < 35*5; i++) {
switch (bookstores.mybooks[i].u8line) {
case 0:
tempH = 0.715;
break;
case 1:
tempH = 0.35;
break;
case 2:
tempH = 0.06;
break;
case 3:
tempH = -0.32;
break;
case 4:
tempH = -0.72;
break;
default:
break
}
console.log(tempH);
var material = new THREE.MeshPhongMaterial({
color: 0xffffff * Math.random(), flatShading: true
});
var mesh = new THREE.Mesh(geometry, material);
mesh.position.set(-0.38 + bookstores.mybooks[i].u8pos * 0.02, tempH, 0);
//mesh.position.set(-0.4+i*0.02, 0.085, 0);
object.add(mesh);
}
var width = window.innerWidth; //窗口宽度
var height = window.innerHeight; //窗口高度
var k = width / height; //窗口宽高比
var s = 1; //三维场景显示范围控制系数,系数越大,显示的范围越大
//创建相机对象
camera = new THREE.OrthographicCamera(-s * k, s * k, s, -s, 0.1, 100);
//创建相机对象
//camera = new THREE.OrthographicCamera(-5, 5, 3.75, -3.75, 0.1, 100);
//camera.position.set(0, 0, 50); //正上方
camera.position.set(10, 10, 50); //设置相机位置为斜上方
camera.lookAt(scene.position); //设置相机方向(指向的场景对象)
var meshbookshelf=null;
//导入模型
var OBJLoader = new THREE.OBJLoader();//obj加载器
var MTLLoader = new THREE.MTLLoader();//材质文件加载器
MTLLoader.load('obj/bookshelf.mtl', function(materials) {
//obj的模型会和MaterialCreator包含的材质对应起来
OBJLoader.setMaterials(materials);
OBJLoader.load('obj/bookshelf.obj', function(obj) {
var texture = new THREE.TextureLoader().load('./img/tree3.jpg');
// 颜色贴图中已经包含了光照信息,所以直接使用不受光照影响的基础网格材质MeshBasicMaterial
obj.children[0].material= new THREE.MeshBasicMaterial({
map:texture,//设置颜色纹理贴图
})
obj.position.y = 0;
obj.position.z = 0;
obj.position.x = 0;
scene.add(obj);//返回的组对象插入场景中
})
})
mouse = new THREE.Vector2();
raycaster = new THREE.Raycaster();
var light = new THREE.DirectionalLight(0xffffff);//光源颜色
light.position.set(30, 20, 10);//光源位置
scene.add(light);//光源添加到场景中
//环境光
var ambient = new THREE.AmbientLight(0x333333);
scene.add(ambient);
var books = document.getElementById("taga");
var index = -1;
function onDocumentClick(event) {
//阻止默认动作
event.preventDefault();
//鼠标转为屏幕中的
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
raycaster.setFromCamera( mouse, camera );
//射线选中一系列直线
var intersects = raycaster.intersectObjects(scene.children,true);
//拾取物体数大于0时
if (intersects.length > 0) {
if (INTERSECTED != intersects[0].object) {
//恢复选择前的默认颜色和位置
if (SELECTED) {
//SELECTED.material.color.setHex(SELECTED.currentHex);
SELECTED.scale.y = 1;
SELECTED.scale.z = 1;
SELECTED.rotateY(Math.PI/2);//绕z轴旋转π/2
SELECTED.position.set(SELECTEDpos.x,SELECTEDpos.y,SELECTEDpos.z);
}
//选择书架后则退出
if(intersects[0].object.parent.type == "Group")
{
SELECTED = null;
books.textContent = '文件名:未选择书籍';
return;
}
SELECTED = intersects[0].object;
SELECTED.scale.y = 1.2;
SELECTED.scale.z = 1.5;
SELECTED.rotateY(-Math.PI/2);//绕z轴旋转π/4
SELECTEDpos.copy(SELECTED.position);
index = Math.round((SELECTEDpos.x+0.38)*50);
SELECTED.position.set(-0.6,0.085,0.3);
var lineNum=0;
switch (SELECTEDpos.y) {
case 0.715:
lineNum = 1;
break;
case 0.35:
lineNum = 2;
break;
case 0.06:
lineNum = 3;
break;
case -0.32:
lineNum = 4;
break;
case -0.72:
lineNum = 5;
break;
default:
break
}
books.textContent = '行数'+lineNum+"-文件名"+bookstores.mybooks[index].strname;
} else {
if (SELECTED) {
SELECTED.material.color.set(SELECTED.currentHex);//恢复选择前的默认颜色
}
SELECTED = null;
books.textContent = '文件名:未选择书籍';
}
}
}
document.addEventListener('click', onDocumentClick, false);
render();
controls = new THREE.OrbitControls(camera,renderer.domElement);//创建控件对象
}
function render() {
requestAnimationFrame(render);//请求再次执行渲染函数render
renderer.render(scene, camera);//执行渲染操作
}
</script>
</body>
</html>
2020-06-11更新:网友找我要demo,应网友要求,源码及模型已经上传gitee
https://gitee.com/applecai/mybookshelf_js
此demo中已添加了补间动画,参考我的blog
书架选书Tween补间动画应用--Apple的学习笔记
网友评论