"use strict"; // 參考 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode //////////////////////////////////////////////////////////////////////////////// // 螺旋:用膠囊取代球體 // 你的任務是修改 createHelix 函數 //////////////////////////////////////////////////////////////////////////////// var camera, scene, renderer; var cameraControls, effectController; var clock = new THREE.Clock(); var gridX = true; var gridY = false; var gridZ = false; var axes = true; var ground = true; /** * 回傳一個 THREE.Object3D 螺旋 * @param material - THREE.Material * @param radius - 螺旋的半徑 * @param tube - 膠囊的半徑 * @param radialSegments - 一圈有多少個膠囊 * @param tubularSegments - 膠囊繞橫切面一圈的細分數 * @param height - 膠囊沿 Y 軸延伸的高度 * @param arc - 繞 Y 軸多少次;目前是整數 * @param clockwise - 若為 true,沿著軸向往上時為順時針方向 */ function createHelix( material, radius, tube, radialSegments, tubularSegments, height, arc, clockwise ) { // 預設 tubularSegments = (tubularSegments === undefined) ? 32 : tubularSegments; arc = (arc === undefined) ? 1 : arc; clockwise = (clockwise === undefined) ? true : clockwise; var helix = new THREE.Object3D(); var top = new THREE.Vector3(); var sine_sign = clockwise ? 1 : -1; /////////////// // 你的程式在這裡:移除球,使用膠囊替代,頭尾相接 // var sphGeom = new THREE.SphereGeometry( tube, tubularSegments, tubularSegments/2 ); for ( var i = 0; i <= arc*radialSegments ; i++ ) { // 從 X 軸到 Z 軸 top.set( radius * Math.cos( i * 2*Math.PI / radialSegments ), height * (i/(arc*radialSegments)) - height/2, sine_sign * radius * Math.sin( i * 2*Math.PI / radialSegments ) ); var sphere = new THREE.Mesh( sphGeom, material ); sphere.position.copy( top ); helix.add( sphere ); } /////////////// return helix; } /** * 回傳一個 THREE.Object3D 位置從 top 到 bottom 的圓柱體和兩端的兩個球 * @param material - THREE.Material * @param radius - 膠囊的半徑 * @param top, bottom - THREE.Vector3,圓柱體的上下端位置 * @param segmentsWidth - 繞橫切面一圈的細分數,類似 CylinderGeometry 裡的 radiusSegments * @param openTop, openBottom - 是否在兩端產生球,true 代表不產生 */ function createCapsule( material, radius, top, bottom, segmentsWidth, openTop, openBottom ) { // 預設 segmentsWidth = (segmentsWidth === undefined) ? 32 : segmentsWidth; openTop = (openTop === undefined) ? false : openTop; openBottom = (openBottom === undefined) ? false : openBottom; // 取得圓柱體高度 var cylAxis = new THREE.Vector3(); cylAxis.subVectors( top, bottom ); var length = cylAxis.length(); // 取得圓柱體高度中心供平移使用 var center = new THREE.Vector3(); center.addVectors( top, bottom ); center.divideScalar( 2.0 ); // 設定圓柱體為 openEnded var cylGeom = new THREE.CylinderGeometry( radius, radius, length, segmentsWidth, 1, 1 ); var cyl = new THREE.Mesh( cylGeom, material ); // 傳遞圓柱體本身,希望對齊的軸,與中心點目標位置 makeLengthAngleAxisTransform( cyl, cylAxis, center ); var capsule = new THREE.Object3D(); capsule.add( cyl ); if ( !openTop || !openBottom ) { // instance geometry var sphGeom = new THREE.SphereGeometry( radius, segmentsWidth, segmentsWidth/2 ); if ( !openTop ) { var sphTop = new THREE.Mesh( sphGeom, material ); sphTop.position.set( top.x, top.y, top.z ); capsule.add( sphTop ); } if ( !openBottom ) { var sphBottom = new THREE.Mesh( sphGeom, material ); sphBottom.position.set( bottom.x, bottom.y, bottom.z ); capsule.add( sphBottom ); } } return capsule; } // 將圓柱變換到貼齊給定的軸,再移動中心點 function makeLengthAngleAxisTransform( cyl, cylAxis, center ) { cyl.matrixAutoUpdate = false; // 從左到右使用參考座標系:平移(T)、旋轉(R);TR // 所以平移先 cyl.matrix.makeTranslation( center.x, center.y, center.z ); // cylAxis 與向上向量外積以取得旋轉軸 var yAxis = new THREE.Vector3(0,1,0); // 之後內積需要正規化,先在這裡做掉 // 我有點懶惰,應該要複製到另一個 Vector3 cylAxis.normalize(); var rotationAxis = new THREE.Vector3(); rotationAxis.crossVectors( cylAxis, yAxis ); if ( rotationAxis.length() < 0.000001 ) { // 特殊情況:如果 rotationAxis 長度趨近於零,使用 X 軸 // 角度則可以使用 0 或是 PI // 可以這樣是因為我們知道外積其中一軸是 +Y rotationAxis.set( 1, 0, 0 ); } rotationAxis.normalize(); // cylAxis 和向上向量內積取得旋轉角的餘弦 var theta = -Math.acos( cylAxis.dot( yAxis ) ); var rotMatrix = new THREE.Matrix4(); rotMatrix.makeRotationAxis( rotationAxis, theta ); cyl.matrix.multiply( rotMatrix ); } function fillScene() { scene = new THREE.Scene(); scene.fog = new THREE.Fog( 0x808080, 2000, 4000 ); // 燈光 var ambientLight = new THREE.AmbientLight( 0x222222 ); var light = new THREE.DirectionalLight( 0xFFFFFF, 1.0 ); light.position.set( 200, 400, 500 ); var light2 = new THREE.DirectionalLight( 0xFFFFFF, 1.0 ); light2.position.set( -500, 250, -200 ); scene.add(ambientLight); scene.add(light); scene.add(light2); // 測試材質與物件 var redMaterial = new THREE.MeshLambertMaterial( { color: 0xFF0000 } ); var greenMaterial = new THREE.MeshLambertMaterial( { color: 0x00FF00 } ); var blueMaterial = new THREE.MeshLambertMaterial( { color: 0x0000FF } ); var grayMaterial = new THREE.MeshLambertMaterial( { color: 0x808080 } ); var yellowMaterial = new THREE.MeshLambertMaterial( { color: 0xFFFF00 } ); var cyanMaterial = new THREE.MeshLambertMaterial( { color: 0x00FFFF } ); var magentaMaterial = new THREE.MeshLambertMaterial( { color: 0xFF00FF } ); var radius = 60; var tube = 10; var radialSegments = 24; var height = 300; var segmentsWidth = 12; var arc = 2; var helix; helix = createHelix( redMaterial, radius, tube, radialSegments, segmentsWidth, height, arc, true ); helix.position.y = height/2; scene.add( helix ); helix = createHelix( greenMaterial, radius/2, tube, radialSegments, segmentsWidth, height, arc, false ); helix.position.y = height/2; scene.add( helix ); // DNA helix = createHelix( blueMaterial, radius, tube/2, radialSegments, segmentsWidth, height, arc, false ); helix.position.y = height/2; helix.position.z = 2.5 * radius; scene.add( helix ); helix = createHelix( blueMaterial, radius, tube/2, radialSegments, segmentsWidth, height, arc, false ); helix.rotation.y = 120 * Math.PI / 180; helix.position.y = height/2; helix.position.z = 2.5 * radius; scene.add( helix ); helix = createHelix( grayMaterial, radius, tube/2, radialSegments, segmentsWidth, height/2, arc, true ); helix.position.y = height/2; helix.position.x = 2.5 * radius; scene.add( helix ); helix = createHelix( yellowMaterial, 0.75*radius, tube/2, radialSegments, segmentsWidth, height, 4*arc, false ); helix.position.y = height/2; helix.position.x = 2.5 * radius; helix.position.z = -2.5 * radius; scene.add( helix ); helix = createHelix( cyanMaterial, 0.75*radius, 4*tube, radialSegments, segmentsWidth, height, 2*arc, false ); helix.position.y = height/2; helix.position.x = 2.5 * radius; helix.position.z = 2.5 * radius; scene.add( helix ); helix = createHelix( magentaMaterial, radius, tube, radialSegments, segmentsWidth, height, arc, true ); helix.rotation.x = 45 * Math.PI / 180; helix.position.y = height/2; helix.position.z = -2.5 * radius; scene.add( helix ); } function init() { canvasWidth = window.innerWidth; canvasHeight = window.innerHeight; var canvasRatio = canvasWidth/canvasHeight; // RENDERER renderer = new THREE.WebGLRenderer( { antialias: false } ); renderer.gammaInput = true; renderer.gammaOutput = true; renderer.setSize(canvasWidth, canvasHeight); renderer.setClearColorHex( 0xAAAAAA, 1.0 ); // 攝影機 camera = new THREE.PerspectiveCamera( 40, canvasRatio, 1, 10000 ); camera.position.set( -528, 513, 92 ); // 控制 cameraControls = new THREE.OrbitAndPanControls(camera, renderer.domElement); cameraControls.target.set(0,200,0); } function addToDOM() { var container = document.getElementById('container'); var canvas = container.getElementsByTagName('canvas'); if (canvas.length>0) { container.removeChild(canvas[0]); } container.appendChild( renderer.domElement ); } function drawHelpers() { if (ground) { Coordinates.drawGround({size:10000}); } if (gridX) { Coordinates.drawGrid({size:10000,scale:0.01}); } if (gridY) { Coordinates.drawGrid({size:10000,scale:0.01, orientation:"y"}); } if (gridZ) { Coordinates.drawGrid({size:10000,scale:0.01, orientation:"z"}); } if (axes) { Coordinates.drawAllAxes({axisLength:200,axisRadius:1,axisTess:50}); } } function animate() { window.requestAnimationFrame(animate); render(); } function render() { var delta = clock.getDelta(); cameraControls.update(delta); if ( effectController.newGridX !== gridX || effectController.newGridY !== gridY || effectController.newGridZ !== gridZ || effectController.newGround !== ground || effectController.newAxes !== axes) { gridX = effectController.newGridX; gridY = effectController.newGridY; gridZ = effectController.newGridZ; ground = effectController.newGround; axes = effectController.newAxes; fillScene(); drawHelpers(); } renderer.render(scene, camera); } function setupGui() { effectController = { newGridX: gridX, newGridY: gridY, newGridZ: gridZ, newGround: ground, newAxes: axes }; var gui = new dat.GUI(); var h = gui.addFolder("顯示格線"); h.add( effectController, "newGridX").name("顯示 XZ 格線"); h.add( effectController, "newGridY" ).name("顯示 YZ 格線"); h.add( effectController, "newGridZ" ).name("顯示 XY 格線"); h.add( effectController, "newGround" ).name("顯示地面"); h.add( effectController, "newAxes" ).name("顯示軸線"); } document.addEventListener("DOMContentLoaded", function () { try { init(); fillScene(); drawHelpers(); setupGui() addToDOM(); animate(); } catch(e) { var errorReport = "你的程式遇到不可復原的錯誤,無法繪製 Canvas。錯誤是:<br/><br/>"; $('#container').append(errorReport+e); } })