使用ThreeJs从零开始构建3D智能仓库——第二章(创建地面与门窗)
使用ThreeJs从零开始构建3D智能仓库——第二章
- 最新进展
- 如何添加墙壁、窗户、门
- 添加三面实心的墙壁
- 创建挖去门窗的墙
- 安装门及窗户
- 完整的代码
- 结束语
最新进展
最近这两天因为项目上比较空闲了,所以就想着怎么给我这个粗劣的小玩意儿加点高大上的东西,经过身边同事的提醒,我发现自己做的这个仓库只有一个房间,但是一般来讲厂房内可能会有多个仓库,或者说同一个仓库也有可能会有好几层。
所以开发一个场景切换的功能至关重要。经过一天的探索与发现,我终于顺利解决了这个问题,归功于网络上眼花缭乱的开源Js和插件,现将效果展示在下面:
2019.11.26 更新:我最近建立了个人网站,大家可以访问下面的链接查看演示
3D仓库演示
2019.11.28 更新:代码和图片资源等已上传至GitHub
https://github.com/xiao149/ThreeJsDemo
可见右侧新添了一个选择的控件,总共有两个场景(第二个场景暂时还没做,先用一张图片代替),通过点击不同的按钮来实现不同场景的切换,切换过程中有类似翻转的特效。这部分的内容如果大家有兴趣的话我会放到以后讲解,今天还是继续第一章的内容,来看看如何添加墙壁、窗户、门和一个很关键的重点:如何选中一个物体并添加选中特效。如何选中一个物体并添加选中特效。如何选中一个物体并添加选中特效。(重要的话说三遍,选中是之后很多功能的前提)
如何添加墙壁、窗户、门
这部分内容整体来说并不难,无论是墙壁,还是门窗户,其实质都是一个长方体,我们使用THREE.BoxGeometry这个几何体来构建这一切,完成后的效果如下:
添加三面实心的墙壁
实心的墙壁是很简单的,这里直接给出代码
//创建墙
function createCubeWall(width, height, depth, angle, material, x, y, z, name){var cubeGeometry = new THREE.BoxGeometry(width, height, depth );var cube = new THREE.Mesh( cubeGeometry, material );cube.position.x = x;cube.position.y = y;cube.position.z = z;cube.rotation.y += angle*Math.PI; //-逆时针旋转,+顺时针cube.name = name;scene.add(cube);
}
稍微解释一下,width, height, depth这三个参数定义了长方体的长宽高,angle定义了长方体旋转的角度,material定义了物体的材质,x, y, z定义了该物体放在场景中的具体位置,name定义了该物体的名字。然后再初始化的函数中加入
//创建墙
createCubeWall(10, 200, 1400, 0, new THREE.MeshPhongMaterial({color: 0xafc0ca}), -1295, 100, 0, "墙面");
然后就可以看到这样的效果,地面上出现了一堵左侧的墙
依葫芦画瓢可以很简单地创建出三面实心的墙
//创建墙
createCubeWall(10, 200, 1400, 0, new THREE.MeshPhongMaterial({color: 0xafc0ca}), -1295, 100, 0, "墙面");
createCubeWall(10, 200, 1400, 1, new THREE.MeshPhongMaterial({color: 0xafc0ca}), 1295, 100, 0, "墙面");
createCubeWall(10, 200, 2600, 1.5, new THREE.MeshPhongMaterial({color: 0xafc0ca}), 0, 100, -700, "墙面");
创建挖去门窗的墙
首先我们来分析下,要创造一个挖去某些部分的墙其实也很简单,其实质就是先创建一个实心的墙面,然后再创建出实心的门窗,最后用某个工具像做减法一样:实心的墙面减去实心的门窗。就可以得到挖去了门窗的墙面。很幸运的是,ThreeJs给我们提供了这样的方法,使用ThreeBSP这个库就可以实现差集(相减)、并集(组合、相加)、交集(两几何体重合的部分)等一系列功能
我们先来创建需要的一面实心墙,两扇门,四扇窗户,代码如下:
//返回墙对象
function returnWallObject(width, height, depth, angle, material, x, y, z, name){var cubeGeometry = new THREE.BoxGeometry(width, height, depth);var cube = new THREE.Mesh( cubeGeometry, material );cube.position.x = x;cube.position.y = y;cube.position.z = z;cube.rotation.y += angle*Math.PI;cube.name = name;return cube;
}
//创建挖了门的墙
var wall = returnWallObject(2600, 200, 10, 0, matArrayB, 0, 100, 700, "墙面");
var door_cube1 = returnWallObject(200, 180, 10, 0, matArrayB, -600, 90, 700, "前门1");
var door_cube2 = returnWallObject(200, 180, 10, 0, matArrayB, 600, 90, 700, "前门2");
var window_cube1 = returnWallObject(100, 100, 10, 0, matArrayB, -900, 90, 700, "窗户1");
var window_cube2 = returnWallObject(100, 100, 10, 0, matArrayB, 900, 90, 700, "窗户2");
var window_cube3 = returnWallObject(100, 100, 10, 0, matArrayB, -200, 90, 700, "窗户3");
var window_cube4 = returnWallObject(100, 100, 10, 0, matArrayB, 200, 90, 700, "窗户4");
var objects_cube = [];
objects_cube.push(door_cube1);
objects_cube.push(door_cube2);
objects_cube.push(window_cube1);
objects_cube.push(window_cube2);
objects_cube.push(window_cube3);
objects_cube.push(window_cube4);
createResultBsp(wall, objects_cube);
这里我们创建了一个数组objects_cube来存放要挖去的内容,最后使用createResultBsp(wall, objects_cube)这个方法创建出挖去门窗的墙面,参数很简单,第一个是被挖去的墙,第二个是要挖去的物体的数组。该方法代码如下:
//墙上挖门窗,通过两个几何体生成BSP对象
function createResultBsp(bsp,objects_cube){var material = new THREE.MeshPhongMaterial({color:0x9cb2d1,specular:0x9cb2d1,shininess:30,transparent:true,opacity:1});var BSP = new ThreeBSP(bsp);for(var i = 0; i < objects_cube.length; i++){var less_bsp = new ThreeBSP(objects_cube[i]);BSP = BSP.subtract(less_bsp);}var result = BSP.toMesh(material);result.material.flatshading = THREE.FlatShading;result.geometry.computeFaceNormals(); //重新计算几何体侧面法向量result.geometry.computeVertexNormals();result.material.needsUpdate = true; //更新纹理result.geometry.buffersNeedUpdate = true;result.geometry.uvsNeedUpdate = true;scene.add(result);
}
如此这般,我们就能看到如下的效果(是不是还挺简单的呢):
安装门及窗户
接下来我们要在上面完成的挖去了门窗的墙面上安装门和窗户,我们使用三个方法来实现这个功能,因为我这里的门分为左门和右门,如果不需要的话只留下一种门就可以啦,代码如下,这三个方法类似,我就只介绍一个,方法参数很简单,width, height, depth定义了门窗的长宽高,angle定义了门窗的旋转角度,x, y, z定义了门窗的空间位置,name定义了门窗的名字。
这里都使用了THREE.TextureLoader来加载本地的图片作为门窗的贴图,门窗的实体设置为全透明,也就是opacity = 1.0和transparent = true,其他不做过多阐述。
//创建门_左侧
function createDoor_left(width, height, depth, angle, x, y, z, name){var loader = new THREE.TextureLoader();loader.load("./ThreeJs/images/door_left.png",function(texture){var doorgeometry = new THREE.BoxGeometry(width, height, depth);doorgeometry.translate(50, 0, 0);var doormaterial = new THREE.MeshBasicMaterial({map:texture,color:0xffffff});doormaterial.opacity = 1.0;doormaterial.transparent = true;var door = new THREE.Mesh( doorgeometry,doormaterial);door.position.set(x, y, z);door.rotation.y += angle*Math.PI; //-逆时针旋转,+顺时针door.name = name;scene.add(door);});
}//创建门_右侧
function createDoor_right(width, height, depth, angle, x, y, z, name){var loader = new THREE.TextureLoader();loader.load("./ThreeJs/images/door_right.png",function(texture){var doorgeometry = new THREE.BoxGeometry(width, height, depth);doorgeometry.translate(-50, 0, 0);var doormaterial = new THREE.MeshBasicMaterial({map:texture,color:0xffffff});doormaterial.opacity = 1.0;doormaterial.transparent = true;var door = new THREE.Mesh( doorgeometry,doormaterial);door.position.set(x, y, z);door.rotation.y += angle*Math.PI; //-逆时针旋转,+顺时针door.name = name;scene.add(door);});
}//创建窗户
function createWindow(width, height, depth, angle, x, y, z, name){var loader = new THREE.TextureLoader();loader.load("./ThreeJs/images/window.png",function(texture){var windowgeometry = new THREE.BoxGeometry(width, height, depth);var windowmaterial = new THREE.MeshBasicMaterial({map:texture,color:0xffffff});windowmaterial.opacity = 1.0;windowmaterial.transparent = true;var window = new THREE.Mesh( windowgeometry,windowmaterial);window.position.set(x, y, z);window.rotation.y += angle*Math.PI; //-逆时针旋转,+顺时针window.name = name;scene.add(window);});
}
最后在初始化函数里加上如下的代码,就可以看到门窗都已经顺利安装成功了!
//为墙面安装门
createDoor_left(100, 180, 2, 0, -700, 90, 700, "左门1");
createDoor_right(100, 180, 2, 0, -500, 90, 700, "右门1");
createDoor_left(100, 180, 2, 0, 500, 90, 700, "左门2");
createDoor_right(100, 180, 2, 0, 700, 90, 700, "右门2");
//为墙面安装窗户
createWindow(100, 100, 2, 0, -900, 90, 700, "窗户");
createWindow(100, 100, 2, 0, 900, 90, 700, "窗户");
createWindow(100, 100, 2, 0, -200, 90, 700, "窗户");
createWindow(100, 100, 2, 0, 200, 90, 700, "窗户");
完整的代码
想了想还是把选中的功能放到下一章来讲吧,这部分比较复杂,写在这一章的话实在太长了,还请同学们多多期待吧,最后照例给出该章全部的代码。
<!DOCTYPE html>
<html>
<head includeDefault="true"><meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0"><title>3D库图显示</title><style>body {margin: 0;overflow: hidden;}#label {position: absolute;padding: 10px;background: rgba(255, 255, 255, 0.6);line-height: 1;border-radius: 5px;}</style><script src="./ThreeJs/three.js"></script><script src="./ThreeJs/stats.min.js"></script><script src="./ThreeJs/OrbitControls.js"></script><script src="./ThreeJs/OBJLoader.js"></script><script src="./ThreeJs/MTLLoader.js"></script><script src="./ThreeJs/ThreeBSP.js"></script>
</head>
<body><div id="label"></div><div id="container"></div><script>var stats = initStats();var scene, camera, renderer, controls, light, composer;var matArrayA=[];//内墙var matArrayB = [];//外墙var group = new THREE.Group();// 初始化场景function initScene() {scene = new THREE.Scene();scene.fog = new THREE.Fog( scene.background, 3000, 5000 );}// 初始化相机function initCamera() {camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 10000);camera.position.set(0, 800, 1500);camera.lookAt(new THREE.Vector3(0, 0, 0));}// 初始化灯光function initLight() {var directionalLight = new THREE.DirectionalLight( 0xffffff, 0.3 );//模拟远处类似太阳的光源directionalLight.color.setHSL( 0.1, 1, 0.95 );directionalLight.position.set( 0, 200, 0).normalize();scene.add( directionalLight );var ambient = new THREE.AmbientLight( 0xffffff, 1 ); //AmbientLight,影响整个场景的光源ambient.position.set(0,0,0);scene.add( ambient );}// 初始化性能插件function initStats() {var stats = new Stats();stats.domElement.style.position = 'absolute';stats.domElement.style.left = '0px';stats.domElement.style.top = '0px';document.body.appendChild(stats.domElement);return stats;}// 初始化渲染器function initRenderer() {renderer = new THREE.WebGLRenderer({antialias: true});renderer.setSize(window.innerWidth, window.innerHeight);renderer.setClearColor(0x4682B4,1.0);document.body.appendChild(renderer.domElement);}//创建地板function createFloor(){var loader = new THREE.TextureLoader();loader.load("./ThreeJs/images/floor.jpg",function(texture){texture.wrapS = texture.wrapT = THREE.RepeatWrapping;texture.repeat.set( 10, 10 );var floorGeometry = new THREE.BoxGeometry(2600, 1400, 1);var floorMaterial = new THREE.MeshBasicMaterial( { map: texture, side: THREE.DoubleSide } );var floor = new THREE.Mesh(floorGeometry, floorMaterial);floor.position.y = -0.5;floor.rotation.x = Math.PI / 2;floor.name = "地面";scene.add(floor);});}//创建墙function createCubeWall(width, height, depth, angle, material, x, y, z, name){var cubeGeometry = new THREE.BoxGeometry(width, height, depth );var cube = new THREE.Mesh( cubeGeometry, material );cube.position.x = x;cube.position.y = y;cube.position.z = z;cube.rotation.y += angle*Math.PI; //-逆时针旋转,+顺时针cube.name = name;scene.add(cube);}//创建门_左侧function createDoor_left(width, height, depth, angle, x, y, z, name){var loader = new THREE.TextureLoader();loader.load("./ThreeJs/images/door_left.png",function(texture){var doorgeometry = new THREE.BoxGeometry(width, height, depth);doorgeometry.translate(50, 0, 0);var doormaterial = new THREE.MeshBasicMaterial({map:texture,color:0xffffff});doormaterial.opacity = 1.0;doormaterial.transparent = true;var door = new THREE.Mesh( doorgeometry,doormaterial);door.position.set(x, y, z);door.rotation.y += angle*Math.PI; //-逆时针旋转,+顺时针door.name = name;scene.add(door);});}//创建门_右侧function createDoor_right(width, height, depth, angle, x, y, z, name){var loader = new THREE.TextureLoader();loader.load("./ThreeJs/images/door_right.png",function(texture){var doorgeometry = new THREE.BoxGeometry(width, height, depth);doorgeometry.translate(-50, 0, 0);var doormaterial = new THREE.MeshBasicMaterial({map:texture,color:0xffffff});doormaterial.opacity = 1.0;doormaterial.transparent = true;var door = new THREE.Mesh( doorgeometry,doormaterial);door.position.set(x, y, z);door.rotation.y += angle*Math.PI; //-逆时针旋转,+顺时针door.name = name;scene.add(door);});}//创建窗户function createWindow(width, height, depth, angle, x, y, z, name){var loader = new THREE.TextureLoader();loader.load("./ThreeJs/images/window.png",function(texture){var windowgeometry = new THREE.BoxGeometry(width, height, depth);var windowmaterial = new THREE.MeshBasicMaterial({map:texture,color:0xffffff});windowmaterial.opacity = 1.0;windowmaterial.transparent = true;var window = new THREE.Mesh( windowgeometry,windowmaterial);window.position.set(x, y, z);window.rotation.y += angle*Math.PI; //-逆时针旋转,+顺时针window.name = name;scene.add(window);});}//返回墙对象function returnWallObject(width, height, depth, angle, material, x, y, z, name){var cubeGeometry = new THREE.BoxGeometry(width, height, depth);var cube = new THREE.Mesh( cubeGeometry, material );cube.position.x = x;cube.position.y = y;cube.position.z = z;cube.rotation.y += angle*Math.PI;cube.name = name;return cube;}//墙上挖门,通过两个几何体生成BSP对象function createResultBsp(bsp,objects_cube){var material = new THREE.MeshPhongMaterial({color:0x9cb2d1,specular:0x9cb2d1,shininess:30,transparent:true,opacity:1});var BSP = new ThreeBSP(bsp);for(var i = 0; i < objects_cube.length; i++){var less_bsp = new ThreeBSP(objects_cube[i]);BSP = BSP.subtract(less_bsp);}var result = BSP.toMesh(material);result.material.flatshading = THREE.FlatShading;result.geometry.computeFaceNormals(); //重新计算几何体侧面法向量result.geometry.computeVertexNormals();result.material.needsUpdate = true; //更新纹理result.geometry.buffersNeedUpdate = true;result.geometry.uvsNeedUpdate = true;scene.add(result);}//创建墙纹理function createWallMaterail(){matArrayA.push(new THREE.MeshPhongMaterial({color: 0xafc0ca})); //前 0xafc0ca :灰色matArrayA.push(new THREE.MeshPhongMaterial({color: 0xafc0ca})); //后matArrayA.push(new THREE.MeshPhongMaterial({color: 0xd6e4ec})); //上 0xd6e4ec: 偏白色matArrayA.push(new THREE.MeshPhongMaterial({color: 0xd6e4ec})); //下matArrayA.push(new THREE.MeshPhongMaterial({color: 0xafc0ca})); //左 0xafc0ca :灰色matArrayA.push(new THREE.MeshPhongMaterial({color: 0xafc0ca})); //右matArrayB.push(new THREE.MeshPhongMaterial({color: 0xafc0ca})); //前 0xafc0ca :灰色matArrayB.push(new THREE.MeshPhongMaterial({color: 0x9cb2d1})); //后 0x9cb2d1:淡紫matArrayB.push(new THREE.MeshPhongMaterial({color: 0xd6e4ec})); //上 0xd6e4ec: 偏白色matArrayB.push(new THREE.MeshPhongMaterial({color: 0xd6e4ec})); //下matArrayB.push(new THREE.MeshPhongMaterial({color: 0xafc0ca})); //左 0xafc0ca :灰色matArrayB.push(new THREE.MeshPhongMaterial({color: 0xafc0ca})); //右}// 初始化模型function initContent() {createFloor();createWallMaterail();createCubeWall(10, 200, 1400, 0, matArrayB, -1295, 100, 0, "墙面");createCubeWall(10, 200, 1400, 1, matArrayB, 1295, 100, 0, "墙面");createCubeWall(10, 200, 2600, 1.5, matArrayB, 0, 100, -700, "墙面");//创建挖了门的墙var wall = returnWallObject(2600, 200, 10, 0, matArrayB, 0, 100, 700, "墙面");var door_cube1 = returnWallObject(200, 180, 10, 0, matArrayB, -600, 90, 700, "前门1");var door_cube2 = returnWallObject(200, 180, 10, 0, matArrayB, 600, 90, 700, "前门2");var window_cube1 = returnWallObject(100, 100, 10, 0, matArrayB, -900, 90, 700, "窗户1");var window_cube2 = returnWallObject(100, 100, 10, 0, matArrayB, 900, 90, 700, "窗户2");var window_cube3 = returnWallObject(100, 100, 10, 0, matArrayB, -200, 90, 700, "窗户3");var window_cube4 = returnWallObject(100, 100, 10, 0, matArrayB, 200, 90, 700, "窗户4");var objects_cube = [];objects_cube.push(door_cube1);objects_cube.push(door_cube2);objects_cube.push(window_cube1);objects_cube.push(window_cube2);objects_cube.push(window_cube3);objects_cube.push(window_cube4);createResultBsp(wall, objects_cube);//为墙面安装门createDoor_left(100, 180, 2, 0, -700, 90, 700, "左门1");createDoor_right(100, 180, 2, 0, -500, 90, 700, "右门1");createDoor_left(100, 180, 2, 0, 500, 90, 700, "左门2");createDoor_right(100, 180, 2, 0, 700, 90, 700, "右门2");//为墙面安装窗户createWindow(100, 100, 2, 0, -900, 90, 700, "窗户");createWindow(100, 100, 2, 0, 900, 90, 700, "窗户");createWindow(100, 100, 2, 0, -200, 90, 700, "窗户");createWindow(100, 100, 2, 0, 200, 90, 700, "窗户");}// 初始化轨迹球控件function initControls() {controls = new THREE.OrbitControls( camera, renderer.domElement );controls.enableDamping = true;controls.dampingFactor = 0.5;// 视角最小距离controls.minDistance = 100;// 视角最远距离controls.maxDistance = 5000;// 最大角度controls.maxPolarAngle = Math.PI/2.2;}// 更新控件function update() {stats.update();controls.update();}// 初始化function init() {initScene();initCamera();initRenderer();initContent();initLight();initControls();document.addEventListener('resize', onWindowResize, false);}// 窗口变动触发的方法function onWindowResize() {camera.aspect = window.innerWidth / window.innerHeight;camera.updateProjectionMatrix();renderer.setSize(window.innerWidth, window.innerHeight);}function animate() {requestAnimationFrame(animate);renderer.render(scene, camera);update();}init();animate();</script>
</body>
</html>
结束语
回顾下第一章我们涉及了基础场景的创建,包括场景、相机、光源、控制器等等。第二章我们讲解了如何创造实心的墙面和带有门窗的墙面。下一章我们将会推出如何选中一个物体并给其加上选中后的特效。
我跟广大学习ThreeJs的初学者一样,仍带着懵懂的心去探索这片新大陆,CSDN上的许多前辈都给了我很多关键的灵感和技术方法,如果大家有兴趣,也可以互相交流成长,欢迎大家指导咨询。PS:大家有兴趣可以点进去我的头像,陆陆续续也写了十来篇了。
链接:使用ThreeJs从零开始构建3D智能仓库——第一章: 点我跳转.
链接:使用ThreeJs从零开始构建3D智能仓库——第二章: 点我跳转.
链接:使用ThreeJs从零开始构建3D智能仓库——第三章: 点我跳转.
链接:使用ThreeJs从零开始构建3D智能仓库——第四章: 点我跳转.
链接:使用ThreeJs从零开始构建3D智能仓库——第五章: 点我跳转.
使用ThreeJs从零开始构建3D智能仓库——第二章(创建地面与门窗)相关推荐
- 使用ThreeJs从零开始构建3D智能仓库——第一章(一切的基础)
引用链接:https://blog.csdn.net/homula123/article/details/101197463 使用ThreeJs从零开始构建3D智能仓库--第一章 写在前面 如何实现 ...
- 使用ThreeJs从零开始构建3D智能仓库——第五章(添加货架、货物与侧边栏)
使用ThreeJs从零开始构建3D智能仓库--第五章 写在前面 创建货架对象 创建货架.货位类 根据配置添加货架 添加货物 添加信息侧边栏 结束语 写在前面 本章我们来讲解下如何添加货架货物和显示各类 ...
- 使用ThreeJs从零开始构建3D智能仓库——第三章(选中物体与特效)
使用ThreeJs从零开始构建3D智能仓库--第三章 写在前面--目录结构 如何选中物体 选中物体的原理 选中物体的实现 添加选中后的发光特效 HTML更新如下 结束语 写在前面--目录结构 这一章我 ...
- 使用ThreeJs从零开始构建3D智能仓库——第四章(添加动画及库区)
使用ThreeJs从零开始构建3D智能仓库--第四章 写在前面 使用TweenJS创建补间动画 TweenJS简介 开关门动画的实现 添加库区与文字 结束语 写在前面 本章我们来探索下如何添加补间动画 ...
- benet 3.0的构建企业网络视频第二章地址
benet 3.0 构建企业网络视频第二章,网络介质 第二部分: http://www.namipan.com/d/3-2%e5%8f%8c%e7%bb%9e%e7%ba%bf%e5%81%9a%e6 ...
- 《构建之法》第二章读书摘要
第二章 个人技术和流程 <构建之法>第二章主要讲了个人技术和流程,因为团队需要一定的流程来管理开发活动,每个工程师在软件生命周期所做的工作也应该有一个流程,这一章中着重介绍PSP,即个 ...
- 从零开始Android游戏编程(第二版) 第二章 创建第一个程序Hello Tank
第二章 创建第一个程序Hello Tank 难度:容易 现在开始,我们要真正写作Android程序了.虽然前面安装过程那么复杂,但是写起程序来却是非常简单.而且为了让大家有一个直观的认识,本文不会叙述 ...
- 《Effective Java》学习笔记 第二章 创建和销毁对象
第二章 创建和销毁对象 何时以及如何创建对象,何时以及如何避免创建对象,如何确保他们能够适时地销毁,以及如何管理对象销毁之前必须进行的各种清理动作. 1 考虑用静态工厂方法代替构造器 一般在某处获取一 ...
- [Effective Java]第二章 创建和销毁对象
第一章 前言 略... 第二章 创建和销毁对象 1. 考虑用静态工厂方法代替构造器 创建对象方法:一是最常用的公有构造器,二是静态工厂方法.下面是一个Bool ...
最新文章
- PPF(Point Pair Features)原理及实战技巧
- HDU 4166 BNU 32715 Robot Navigation (记忆化bfs)
- 计算机应用培训资料,计算机应用培训资料.doc
- div垂直水平居中经常使用的方法
- CTFshow 命令执行 web59
- 你知道CSS实现水平垂直居中的第10种方式吗?
- django-vue-admin脚手架快速开发CRUD教程
- 信息发布webpart——网页编辑器应用攻略
- [转载] 大道至简:软件工程实践者的思想——第十章 是思考还是思想
- 从LFS官方文档构建完整Linux系统
- org.springframework.beans.BeanUtils
- 巧用margin/padidng的百分比值占位,避免闪烁
- OSGi中的ServletContext
- SameSite Cookie支持的浏览器版本
- 关于glew.h / glut.h引用【转】
- 用C#编写Visionpro相关窗体应用(.NET Framework)
- 0xbc指令 st75256_st75256 - 涂Sir的个人空间 - OSCHINA - 中文开源技术交流社区
- (一看就会)让VM虚拟机之间联网并能相互ping通
- C++第三方日志库Pantheios
- net start mysql启动mysql,提示发生系统错误5拒绝访问解决方法