"use strict"; // 參考 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode //////////////////////////////////////////////////////////////////////////////// // 創造膠囊:加球體到兩端創造膠囊(起司條) // 你的任務是修改 createCapsule 函數 //////////////////////////////////////////////////////////////////////////////// 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 位置從 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, true ); var cyl = new THREE.Mesh( cylGeom, material ); // 傳遞圓柱體本身,希望對齊的軸,與中心點目標位置 makeLengthAngleAxisTransform( cyl, cylAxis, center ); // 你的程式在這裡 // 這裡是球體幾何,如果 openTop 或 openBottom 是 false,用它來蓋住圓柱的缺口 // 加分題:使用實例化! var sphGeom = new THREE.SphereGeometry( radius, segmentsWidth, segmentsWidth/2 ); // You'll probably want to return something other than this... return cyl; } // 將圓柱變換到貼齊給定的軸,再移動中心點 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 = 20; var segmentsWidth = 32; var capsule; // 沿 Y 軸 capsule = new createCapsule( greenMaterial, radius, new THREE.Vector3( 0, 300, 0 ), new THREE.Vector3( 0, 0, 0 ), segmentsWidth, false, false ); scene.add( capsule ); // 沿 X 軸 capsule = new createCapsule( redMaterial, radius, new THREE.Vector3( 300, 0, 0 ), new THREE.Vector3( 0, 0, 0 ), segmentsWidth, false, true ); scene.add( capsule ); // 沿 Z 軸 capsule = new createCapsule( blueMaterial, radius, new THREE.Vector3( 0, 0, 300 ), new THREE.Vector3( 0, 0, 0 ), segmentsWidth, false, true ); scene.add( capsule ); // 沿 XYZ 軸 capsule = new createCapsule( grayMaterial, radius, new THREE.Vector3( 200, 200, 200 ), new THREE.Vector3( 0, 0, 0 ), segmentsWidth, false, true ); scene.add( capsule ); // 沿 -Y 軸,沿 XYZ 軸平移 capsule = new createCapsule( yellowMaterial, radius, new THREE.Vector3( 50, 100, -200 ), new THREE.Vector3( 50, 300, -200 ), segmentsWidth, false, true ); scene.add( capsule ); // 沿 X 軸,從上一個圓柱的 top 位置開始 capsule = new createCapsule( cyanMaterial, radius, new THREE.Vector3( 50, 300, -200 ), new THREE.Vector3( 250, 300, -200 ), segmentsWidth, false, true ); scene.add( capsule ); // 從上一個圓柱的 bottom 位置開始 capsule = new createCapsule( magentaMaterial, radius, new THREE.Vector3( 250, 300, -200 ), new THREE.Vector3( -150, 100, 0 ), segmentsWidth, false, false ); scene.add( capsule ); } 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); } })