07-THREE.JS 各种形状的几何图形
<!DOCTYPE html> <html> <head> <title>Example 02.04 - Geometries</title> <script src="https://cdn.bootcss.com/three.js/r67/three.js"></script> <script type="text/javascript" src="../libs/ParametricGeometries.js"></script> <script type="text/javascript" src="../libs/ConvexGeometry.js"></script> <script src="https://cdn.bootcss.com/stats.js/r10/Stats.min.js"></script> <script type="text/javascript" src="https://cdn.bootcss.com/dat-gui/0.7.3/dat.gui.js"></script> <style> body { /* set margin to 0 and overflow to hidden, to go fullscreen */ margin: 0; overflow: hidden; } </style> </head> <body> <div id="Stats-output"> </div> <div id="WebGL-output"> </div> <script type="text/javascript"> // 初始化 function init() { var stats = initStats(); // 创建一个场景 var scene = new THREE.Scene(); // 创建一个相机 var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000); // 创建渲染器 var renderer = new THREE.WebGLRenderer(); renderer.setClearColor(new THREE.Color(0xEEEEEE, 1.0)); renderer.setSize(window.innerWidth, window.innerHeight); renderer.shadowMapEnabled = true; // 创建地面 var planeGeometry = new THREE.PlaneGeometry(60, 40, 1, 1); var planeMaterial = new THREE.MeshLambertMaterial({color: 0xffffff}); var plane = new THREE.Mesh(planeGeometry, planeMaterial); plane.receiveShadow = true; // 旋转地面 plane.rotation.x = -0.5 * Math.PI; plane.position.x = 0; plane.position.y = 0; plane.position.z = 0; // 把地面添加到场景中去 scene.add(plane); // 相机对准场景 camera.position.x = -50; camera.position.y = 30; camera.position.z = 20; camera.lookAt(new THREE.Vector3(-10, 0, 0)); // 添加自然光 var ambientLight = new THREE.AmbientLight(0x090909); scene.add(ambientLight); // 添加聚光灯 var spotLight = new THREE.SpotLight(0xffffff); spotLight.position.set(-40, 40, 50); spotLight.castShadow = true; scene.add(spotLight); // 添加形状 addGeometries(scene); // 生成的渲染到DOM元素中去 document.getElementById("WebGL-output").appendChild(renderer.domElement); var step = 0; render(); function addGeometries(scene) { var geoms = []; geoms.push(new THREE.CylinderGeometry(1, 4, 4)); // 基本立体型 geoms.push(new THREE.BoxGeometry(2, 2, 2)); // 圆形 geoms.push(new THREE.SphereGeometry(2)); //用于生成二十面体几何的类 geoms.push(new THREE.IcosahedronGeometry(4)); //几何/凸面 var points = [ new THREE.Vector3(2, 2, 2), new THREE.Vector3(2, 2, -2), new THREE.Vector3(-2, 2, -2), new THREE.Vector3(-2, 2, 2), new THREE.Vector3(2, -2, 2), new THREE.Vector3(2, -2, -2), new THREE.Vector3(-2, -2, -2), new THREE.Vector3(-2, -2, 2) ]; geoms.push(new THREE.ConvexGeometry(points)); // create a lathgeometry //http://en.wikipedia.org/wiki/Lathe_(graphics) var pts = [];//points array - the path profile points will be stored here var detail = .1;//half-circle detail - how many angle increments will be used to generate points var radius = 3;//radius for half_sphere for (var angle = 0.0; angle < Math.PI; angle += detail)//loop from 0.0 radians to PI (0 - 180 degrees) pts.push(new THREE.Vector3(Math.cos(angle) * radius, 0, Math.sin(angle) * radius));//angle/radius to x,z geoms.push(new THREE.LatheGeometry(pts, 12)); //面包圈 geoms.push(new THREE.OctahedronGeometry(3)); // create a geometry based on a function geoms.push(new THREE.ParametricGeometry(THREE.ParametricGeometries.mobius3d, 20, 10)); // geoms.push(new THREE.TetrahedronGeometry(3)); geoms.push(new THREE.TorusGeometry(3, 1, 10, 10)); geoms.push(new THREE.TorusKnotGeometry(3, 0.5, 50, 20)); var j = 0; for (var i = 0; i < geoms.length; i++) { var cubeMaterial = new THREE.MeshLambertMaterial({wireframe: true, color: Math.random() * 0xffffff}); var materials = [ new THREE.MeshLambertMaterial({color: Math.random() * 0xffffff, shading: THREE.FlatShading}), new THREE.MeshBasicMaterial({color: 0x000000, wireframe: true}) ]; var mesh = THREE.SceneUtils.createMultiMaterialObject(geoms[i], materials); mesh.traverse(function (e) { e.castShadow = true }); mesh.position.x = -24 + ((i % 4) * 12); mesh.position.y = 4; mesh.position.z = -8 + (j * 12); if ((i + 1) % 4 == 0) j++; scene.add(mesh); } } function render() { stats.update(); requestAnimationFrame(render); renderer.render(scene, camera); } function initStats() { var stats = new Stats(); stats.setMode(0); // 0: fps, 1: ms stats.domElement.style.position = 'absolute'; stats.domElement.style.left = '0px'; stats.domElement.style.top = '0px'; document.getElementById("Stats-output").appendChild(stats.domElement); return stats; } } window.onload = init </script> </body> </html>
ParametricGeometries.js
/* * @author zz85 * * Experimenting of primitive geometry creation using Surface Parametric equations * */ THREE.ParametricGeometries = { klein: function (v, u) { u *= Math.PI; v *= 2 * Math.PI; u = u * 2; var x, y, z; if (u < Math.PI) { x = 3 * Math.cos(u) * (1 + Math.sin(u)) + (2 * (1 - Math.cos(u) / 2)) * Math.cos(u) * Math.cos(v); z = -8 * Math.sin(u) - 2 * (1 - Math.cos(u) / 2) * Math.sin(u) * Math.cos(v); } else { x = 3 * Math.cos(u) * (1 + Math.sin(u)) + (2 * (1 - Math.cos(u) / 2)) * Math.cos(v + Math.PI); z = -8 * Math.sin(u); } y = -2 * (1 - Math.cos(u) / 2) * Math.sin(v); return new THREE.Vector3(x, y, z); }, plane: function (width, height) { return function(u, v) { var x = u * width; var y = 0; var z = v * height; return new THREE.Vector3(x, y, z); }; }, mobius: function(u, t) { // flat mobius strip // http://www.wolframalpha.com/input/?i=M%C3%B6bius+strip+parametric+equations&lk=1&a=ClashPrefs_*Surface.MoebiusStrip.SurfaceProperty.ParametricEquations- u = u - 0.5; var v = 2 * Math.PI * t; var x, y, z; var a = 2; x = Math.cos(v) * (a + u * Math.cos(v/2)); y = Math.sin(v) * (a + u * Math.cos(v/2)); z = u * Math.sin(v/2); return new THREE.Vector3(x, y, z); }, mobius3d: function(u, t) { // volumetric mobius strip u *= Math.PI; t *= 2 * Math.PI; u = u * 2; var phi = u / 2; var major = 2.25, a = 0.125, b = 0.65; var x, y, z; x = a * Math.cos(t) * Math.cos(phi) - b * Math.sin(t) * Math.sin(phi); z = a * Math.cos(t) * Math.sin(phi) + b * Math.sin(t) * Math.cos(phi); y = (major + x) * Math.sin(u); x = (major + x) * Math.cos(u); return new THREE.Vector3(x, y, z); } }; /********************************************* * * Parametric Replacement for TubeGeometry * *********************************************/ THREE.ParametricGeometries.TubeGeometry = function(path, segments, radius, segmentsRadius, closed, debug) { this.path = path; this.segments = segments || 64; this.radius = radius || 1; this.segmentsRadius = segmentsRadius || 8; this.closed = closed || false; if (debug) this.debug = new THREE.Object3D(); var scope = this, tangent, normal, binormal, numpoints = this.segments + 1, x, y, z, tx, ty, tz, u, v, cx, cy, pos, pos2 = new THREE.Vector3(), i, j, ip, jp, a, b, c, d, uva, uvb, uvc, uvd; var frames = new THREE.TubeGeometry.FrenetFrames(path, segments, closed), tangents = frames.tangents, normals = frames.normals, binormals = frames.binormals; // proxy internals this.tangents = tangents; this.normals = normals; this.binormals = binormals; var ParametricTube = function(u, v) { v *= 2 * Math.PI; i = u * (numpoints - 1); i = Math.floor(i); pos = path.getPointAt(u); tangent = tangents[i]; normal = normals[i]; binormal = binormals[i]; if (scope.debug) { scope.debug.add(new THREE.ArrowHelper(tangent, pos, radius, 0x0000ff)); scope.debug.add(new THREE.ArrowHelper(normal, pos, radius, 0xff0000)); scope.debug.add(new THREE.ArrowHelper(binormal, pos, radius, 0x00ff00)); } cx = -scope.radius * Math.cos(v); // TODO: Hack: Negating it so it faces outside. cy = scope.radius * Math.sin(v); pos2.copy(pos); pos2.x += cx * normal.x + cy * binormal.x; pos2.y += cx * normal.y + cy * binormal.y; pos2.z += cx * normal.z + cy * binormal.z; return pos2.clone(); }; THREE.ParametricGeometry.call(this, ParametricTube, segments, segmentsRadius); }; THREE.ParametricGeometries.TubeGeometry.prototype = Object.create( THREE.Geometry.prototype ); /********************************************* * * Parametric Replacement for TorusKnotGeometry * *********************************************/ THREE.ParametricGeometries.TorusKnotGeometry = function ( radius, tube, segmentsR, segmentsT, p, q, heightScale ) { var scope = this; this.radius = radius || 200; this.tube = tube || 40; this.segmentsR = segmentsR || 64; this.segmentsT = segmentsT || 8; this.p = p || 2; this.q = q || 3; this.heightScale = heightScale || 1; var TorusKnotCurve = THREE.Curve.create( function() { }, function(t) { t *= Math.PI * 2; var r = 0.5; var tx = (1 + r * Math.cos(q * t)) * Math.cos(p * t), ty = (1 + r * Math.cos(q * t)) * Math.sin(p * t), tz = r * Math.sin(q * t); return new THREE.Vector3(tx, ty * heightScale, tz).multiplyScalar(radius); } ); var segments = segmentsR; var radiusSegments = segmentsT; var extrudePath = new TorusKnotCurve(); THREE.ParametricGeometries.TubeGeometry.call( this, extrudePath, segments, tube, radiusSegments, true, false ); }; THREE.ParametricGeometries.TorusKnotGeometry.prototype = Object.create( THREE.Geometry.prototype ); /********************************************* * * Parametric Replacement for SphereGeometry * *********************************************/ THREE.ParametricGeometries.SphereGeometry = function(size, u, v) { function sphere(u, v) { u *= Math.PI; v *= 2 * Math.PI; var x = size * Math.sin(u) * Math.cos(v); var y = size * Math.sin(u) * Math.sin(v); var z = size * Math.cos(u); return new THREE.Vector3(x, y, z); } THREE.ParametricGeometry.call(this, sphere, u, v, !true); }; THREE.ParametricGeometries.SphereGeometry.prototype = Object.create( THREE.Geometry.prototype ); /********************************************* * * Parametric Replacement for PlaneGeometry * *********************************************/ THREE.ParametricGeometries.PlaneGeometry = function(width, depth, segmentsWidth, segmentsDepth) { function plane(u, v) { var x = u * width; var y = 0; var z = v * depth; return new THREE.Vector3(x, y, z); } THREE.ParametricGeometry.call(this, plane, segmentsWidth, segmentsDepth); }; THREE.ParametricGeometries.PlaneGeometry.prototype = Object.create( THREE.Geometry.prototype );
ConvexGeometry.js
/** * @author qiao / https://github.com/qiao * @fileoverview This is a convex hull generator using the incremental method. * The complexity is O(n^2) where n is the number of vertices. * O(nlogn) algorithms do exist, but they are much more complicated. * * Benchmark: * * Platform: CPU: P7350 @2.00GHz Engine: V8 * * Num Vertices Time(ms) * * 10 1 * 20 3 * 30 19 * 40 48 * 50 107 */ THREE.ConvexGeometry = function( vertices ) { THREE.Geometry.call( this ); var faces = [ [ 0, 1, 2 ], [ 0, 2, 1 ] ]; for ( var i = 3; i < vertices.length; i++ ) { addPoint( i ); } function addPoint( vertexId ) { var vertex = vertices[ vertexId ].clone(); var mag = vertex.length(); vertex.x += mag * randomOffset(); vertex.y += mag * randomOffset(); vertex.z += mag * randomOffset(); var hole = []; for ( var f = 0; f < faces.length; ) { var face = faces[ f ]; // for each face, if the vertex can see it, // then we try to add the face's edges into the hole. if ( visible( face, vertex ) ) { for ( var e = 0; e < 3; e++ ) { var edge = [ face[ e ], face[ ( e + 1 ) % 3 ] ]; var boundary = true; // remove duplicated edges. for ( var h = 0; h < hole.length; h++ ) { if ( equalEdge( hole[ h ], edge ) ) { hole[ h ] = hole[ hole.length - 1 ]; hole.pop(); boundary = false; break; } } if ( boundary ) { hole.push( edge ); } } // remove faces[ f ] faces[ f ] = faces[ faces.length - 1 ]; faces.pop(); } else { // not visible f++; } } // construct the new faces formed by the edges of the hole and the vertex for ( var h = 0; h < hole.length; h++ ) { faces.push( [ hole[ h ][ 0 ], hole[ h ][ 1 ], vertexId ] ); } } /** * Whether the face is visible from the vertex */ function visible( face, vertex ) { var va = vertices[ face[ 0 ] ]; var vb = vertices[ face[ 1 ] ]; var vc = vertices[ face[ 2 ] ]; var n = normal( va, vb, vc ); // distance from face to origin var dist = n.dot( va ); return n.dot( vertex ) >= dist; } /** * Face normal */ function normal( va, vb, vc ) { var cb = new THREE.Vector3(); var ab = new THREE.Vector3(); cb.subVectors( vc, vb ); ab.subVectors( va, vb ); cb.cross( ab ); cb.normalize(); return cb; } /** * Detect whether two edges are equal. * Note that when constructing the convex hull, two same edges can only * be of the negative direction. */ function equalEdge( ea, eb ) { return ea[ 0 ] === eb[ 1 ] && ea[ 1 ] === eb[ 0 ]; } /** * Create a random offset between -1e-6 and 1e-6. */ function randomOffset() { return ( Math.random() - 0.5 ) * 2 * 1e-6; } /** * XXX: Not sure if this is the correct approach. Need someone to review. */ function vertexUv( vertex ) { var mag = vertex.length(); return new THREE.Vector2( vertex.x / mag, vertex.y / mag ); } // Push vertices into `this.vertices`, skipping those inside the hull var id = 0; var newId = new Array( vertices.length ); // map from old vertex id to new id for ( var i = 0; i < faces.length; i++ ) { var face = faces[ i ]; for ( var j = 0; j < 3; j++ ) { if ( newId[ face[ j ] ] === undefined ) { newId[ face[ j ] ] = id++; this.vertices.push( vertices[ face[ j ] ] ); } face[ j ] = newId[ face[ j ] ]; } } // Convert faces into instances of THREE.Face3 for ( var i = 0; i < faces.length; i++ ) { this.faces.push( new THREE.Face3( faces[ i ][ 0 ], faces[ i ][ 1 ], faces[ i ][ 2 ] ) ); } // Compute UVs for ( var i = 0; i < this.faces.length; i++ ) { var face = this.faces[ i ]; this.faceVertexUvs[ 0 ].push( [ vertexUv( this.vertices[ face.a ] ), vertexUv( this.vertices[ face.b ] ), vertexUv( this.vertices[ face.c ]) ] ); } this.computeFaceNormals(); this.computeVertexNormals(); }; THREE.ConvexGeometry.prototype = Object.create( THREE.Geometry.prototype );