マウスで3Dモデルを回転させる。その3
http://jsdo.it/babu_baboo/inv0
<!DOCTYPE html> <meta charset="utf-8"> <title>GAME</title> <style> body { color: #ccc; background: black; } </style> <body> <canvas width="800" height="700" id="c1"></canvas> <script> //___________________________________ { //アニメーションの環境か? const win = this; const substitution = function (callBackFunc, that) { let tmpFunc = function () { let timestamp = +(new Date); callBackFunc (timestamp); }; win.setTimeout (tmpFunc, Math.floor (1000/60)); }; if ('undefined' === typeof win.requestAnimationFrame) win.requestAnimationFrame = win.requestAnimationFrame || win.webkitRequestAnimationFrame || win.mozRequestAnimationFrame || win.oRequestAnimationFrame || win.msRequestAnimationFrame || substitution; if ('undefined' === typeof win.cancelAnimationFrame) win.cancelAnimationFrame = win.cancelAnimationFrame || win.mozCancelAnimationFrame || win.webkitCancelAnimationFrame || win.msCancelAnimationFrame; } { //色を定義する const MAX_COLOR = 255, MAX_OPACITY = 1, MIN = Math.min, MAX = Math.max, INT = Math.floor; class Color { constructor (r = MAX_COLOR, g = MAX_COLOR, b = MAX_COLOR, a = MAX_OPACITY) { this.r = MAX (0, MIN (r, MAX_COLOR)); this.g = MAX (0, MIN (g, MAX_COLOR)); this.b = MAX (0, MIN (b, MAX_COLOR)); this.a = MAX (0, MIN (a, MAX_OPACITY)); } //色の加算 addColor (colorObj) { let {r, g, b, a} = colorObj; this.r = MAX (0, r * a + this.r); this.g = MAX (0, g * a + this.g); this.b = MAX (0, b * a + this.b); } //色のコピーを作る crone () { return new Color (this.r, this.g, this.b, this.a); } //乗算したコピーを作る multiplication (x) { return new Color ( MAX (0, this.r * x), MAX (0, this.g * x), MAX (0, this.b * x), this.a ); } //文字列で返す(透明度含む) toStringRGBA () { return 'rgba(' + [ MIN (INT (this.r), MAX_COLOR), MIN (INT (this.g), MAX_COLOR), MIN (INT (this.b), MAX_COLOR), MIN (this.a, MAX_OPACITY) ].join (',') + ')'; } //文字列で返す toSrtingRGB () { return 'rgb(' + [ MIN (INT (this.r), MAX_COLOR), MIN (INT (this.g), MAX_COLOR), MIN (INT (this.b), MAX_COLOR) ].join (',') + ')'; } } //__________ this.Color = Color; } { //点を定義する //単純な2次元の点として定義 class Point2D { constructor (x = 0, y = 0) { this.x = x; this.y = y; } } //単純な3次元の点 class Point3D extends Point2D { constructor (x, y, z = 0) { super (x, y); this.z = z; } static average (...vertex) { let len = vertex.length, ax = 0, ay = 0, az = 0; vertex.forEach (p => { let {x, y, z} = p; ax += x, ay += y, az += z; }); return new Point3D (ax / len, ay / len, az / len); } } //__________ this.Point2D = Point2D; this.Point3D = Point3D; } { //点・線・面を定義する const DEF_COLOR = [255, 255, 255, .5], DEF_OPTION = { disabled: false, size : 2 }; //空間に表現するための「点」 class Point extends Point3D { constructor ([x, y, z], color = new Color(...DEF_COLOR), option = DEF_OPTION) { super (x, y, z); this.color = color; this.option = Object.assign ({ }, DEF_OPTION, option); } } //面を構成する「線」 class Line { constructor (list = [ ], color = new Color(...DEF_COLOR), option = { }) { this.list = list; this.color = color; this.option = Object.assign ({ }, DEF_OPTION, option); } addLine (vertex_no) { let len = this.list.length; this.list.push (vertex_no); return len; } } //「面」 class Surface { constructor (lineList = [ ], color = new Color(...DEF_COLOR), option = { }) { this.lineList = lineList; this.color = color; this.option = Object.assign ({ }, DEF_OPTION, option); } //面の3点を利用して面のベクトルを返す。面自体には点の座標の情報が無いので注意 getVector (pointList) { let [i0, i1, i2] = this.lineList, {x: x0, y: y0, z: z0} = pointList[i0], {x: x1, y: y1, z: z1} = pointList[i1], {x: x2, y: y2, z: z2} = pointList[i2], [px, py, pz] = [x1 - x0, y1 - y0, z1 - z0], //p [qx, qy, qz] = [x2 - x1, y2 - y1, z2 - z1]; //q return new Vector ( py * qz - pz * qy, pz * qx - px * qz, px * qy - py * qx ); } getDistance (pointList) { let [i0, i1, i2] = this.lineList, {x: x0, y: y0, z: z0} = pointList[i0], {x: x1, y: y1, z: z1} = pointList[i1], {x: x2, y: y2, z: z2} = pointList[i2], x = (x0 + x1 + x2) / 3, y = (y0 + y1 + y2) / 3, z = (z0 + z1 + z2) / 3; return new Point3D (x, y, z); } } //__________ this.Point = Point; this.Line = Line; this.Surface = Surface; } { //モデルを定義する const DEF_COLOR = new Color (), DEF_OPTION = { disabled: false }; //__________ class Model { constructor (vertex = [ ], surface = [ ], option = { }) { this.vertex = vertex; //頂点 this.surface = surface; //面 this.option = Object.assign ({ }, DEF_OPTION, option); } //面を追加する addSurface (surface) { return this.surface.push (surface) -1; } //頂点リストに追加する addVertex (vertex) { return this.vertex.push (vertex) -1; } //頂点の色をセットする setVertexColor (color, no = null) { let i, p; if (null === no) for (i = 0; p = this.vertex[i]; i += 1) Object.assign (p.color, color); else if (p = this.vertex[no]) Object.assign (p.color, color); } //面の色をセットする setSurfaceColor (color, no = null) { let p; if (null === no) for (let i = 0; p = this.surface[i]; i += 1) Object.assign (p.color, color); else if (p = this.surface[no]) Object.assign (p.color, color); } //物体の頂点の配列を返す getViewPoint () { return this.vertex.map (p => [p.x, p.y, p.z]); } } //__________ this.Model = Model; } { //ベクトルの定義 class Vector extends Point3D { constructor (x, y, z) { super (x, y, z); } //加算 add ({x, y, z}) { this.x += x; this.y += y; this.z += z; } //内積 innerProducts ({x, y, z}) { return this.x * x + this.y * y + this.z * z; } } //__________ this.Vector = Vector; } //___________________________________ // 焦点距離(Focal Length), 画角(Field Of View) //http://www.cyber.t.u-tokyo.ac.jp/~tani/class/mech_enshu/enshu2011mi2.pdf { // カメラ const FOCAL_LENGTH = 50, //mm 標準レンズの焦点距離 DEF_POSITION = [0, 0, 1000], DEF_OPTION = { disabled: false, scale: 1 }; class Camera { constructor (position = new Point3D (...DEF_POSITION), focalLength = FOCAL_LENGTH, option = { }) { this.position = position; this.FOV = focalLength; //2 * Math.atan ((APERTURE_X / 2) / focalLength); this.option = Object.assign ({ }, DEF_OPTION, option); this.Z = position.z; this.f0 = this.option.scale * (focalLength / (1 + focalLength / position.z)) } //3次から2次へ投影 project ({x, y, z}) { let {Z, f0} = this, s = f0 / (Z - z); return [x * s, y * s]; } } //__________ this.Camera = Camera; } { const EYE_POINT = new Point3D (3000, 0, 0), TRGET_POINT = new Point3D (0, 0, 0), UP_VECTOR = new Vector (0, 1, 0), FOCAL_LENGTH = 50, //mm 標準レンズの焦点距離 DEF_POSITION = [0, 0, 1000], DEF_OPTION = { disabled: false, scale: 1 }; class VCamera { constructor (eye = EYE_POINT, target = TRGET_POINT, upVector = UP_VECTOR, option = { }) { this.eye = eye; //視点 this.target = target; //目標点 this.upVector = upVector; //カメラの上方向のベクトル this.option = Object.assign ({ }, DEF_OPTION, option); } //3次から2次へ投影 project ({x, y, z}) { let { x: ex, y: ey, z: ez } = this.eye; } } } { //照明(将来的に点光源など複数の照明を使えるようにするべき) const SQRT = Math.sqrt, DEF_COLOR = [255, 255, 255, 1], DEF_VECTOR = [-1, 0, 0], //平行光なのでベクトルだけ DEF_OPTION = { disabled : false, distance : 3000, targetPoint : new Point3D (0, 0, 0), brightness : 1, //明るさ ambientLight : 0, //環境光 kc : 0, //一定減衰定数 kl : .1, //1次減衰定数 kq : .2, //2次減衰定数 kb : 100000000 //光源の明るさ }; const init = function () { let { distance, targetPoint } = this.option, { x, y, z} = this.vector, { x: tx, y: ty, z: tz } = targetPoint, dd = distance * distance; xyz = x * x + y * y + z * z; this.position = new Point3D ( SQRT (dd * x / xyz) + tx, SQRT (dd * y / xyz) + ty, SQRT (dd * z / xyz) + tz ); this.distance = distance; //距離 }; class Light { constructor (vector = new Vector (...DEF_VECTOR), color = new Color (...DEF_COLOR), option = { }) { this.vector = vector; this.color = color; this.option = Object.assign ({ }, DEF_OPTION, option); init.call (this); } getBrightness (p) { let {x, y, z} = p, { x: tx, y: ty, z: tz } = this.position, { kc, kl, kq, kb } = this.option, dx = tx - x, dy = ty - y, dz = tz - z, d = SQRT (dx * dx + dy * dy + dz * dz); return kb / (kc + kl * d + kq * d * d); } } //__________ this.Light = Light; } { //マウスのドラッグ操作で配列の回転を制御(QUATERNIONによる回転) const SQRT = Math.sqrt, SIN = Math.sin, COS = Math.cos, PI = Math.PI, DEG = PI / 180, TOUCH_EVENT = ['touchstart', 'touchend', 'touchmove'], MOUSE_EVENT = ['mousedown', 'mouseup', 'mousemove', 'mouseout'], DEF_OPTION = { inertia : true, //慣性モード有効 sensitivity : 500, //マウスの感度 gain : 0.999 //慣性移動の減速率 }; //ローテーションクラス本体 class RotationController { constructor (element = document.body, model, option = {}) { this.target = element; this.model = model; this.option = Object.assign ({}, DEF_OPTION, option); this.mouseX = null;//マウス座標の基点 this.mouseY = null;//マウス座標の基点 this.touched = false; //ドラッグ中か? this.animeId = 0; this.timeStamp = null; //慣性を行うかどうかのもう一つの基準 this.dx = 0;//マウスの慣性移動量 this.dy = 0;//マウスの慣性移動量 this.sensitivity = 1 / this.option.sensitivity; //mouse移動の感度 this.inertia_min = 0.001; //最小の移動量で完成移動を止める //touchイベントがあるなら優先 (window.TouchEvent ? TOUCH_EVENT: MOUSE_EVENT) .forEach (addEventType, this) } //各イベント処理 handleEvent (event) { let e = event.target; switch (event.type) { // 制御終了 case 'mouseup' : case 'mouseout' : case 'touchend' : if (this.touched){ this.touched = false; if (event.timeStamp - this.timeStamp < 50) //mouseup から 50mm秒以内なら慣性モードへ if (this.option.inertia) inertia.call (this);//制御を慣性に移す } break; // 制御開始 case 'mousedown' : case 'touchstart' : if (this.animeId) { cancelAnimationFrame (this.animeId); this.animeId = 0; } this.dx = 0; this.dy = 0; this.mouseX = event.pageX; this.mouseY = event.pageY; this.touched = true; break; // 回転制御中 case 'mousemove' : case 'touchmove' : if (this.touched) { event.preventDefault ();//ipadなどでスクロールさせないため this.timeStamp = event.timeStamp; let {pageX, pageY} = event, {mouseX, mouseY, sensitivity} = this, sx = mouseX - pageX, sy = mouseY - pageY; if (sx || sy) { this.dx = sx * sensitivity; this.dy = sy * sensitivity; rotation.call (this); this.mouseX = pageX; this.mouseY = pageY; } } break; } } //quaternion rotation //回転の関数の独立した呼び出し static rotation (aryPoint3D, qx, qy, qz, qa) { let ql = qx * qx + qy * qy + qz * qz; if (ql) { let qh = qa * DEG * .5, s = SIN (qh) / SQRT (ql); product (aryPoint3D, qx * s, qy * s, qz * s, COS (qh)); } } } const //イベントの登録 addEventType = function (eventType) { this.target.addEventListener (eventType, this, false); }, //慣性モードに移行 inertia = function () { let gain = this.option.gain; this.dx *= gain; this.dy *= gain; if (this.inertia_min < rotation.call (this)) this.animeId = requestAnimationFrame (inertia.bind (this)); }, //回転を行う rotation = function () { //画面の2次元移動量から3次元の回転量を求める let {dx, dy} = this, t = dx * dx + dy * dy; if (t) { // クオータニオンによる回転 t = SQRT (t); let as = -SIN (t) / t; product (this.model.vertex, dy * as, dx * as, 0, COS (t)); } return t; //移動距離を返す(慣性?) }, //積(座標)の計算 product = function (point, q0, q1, q2, q3) { for (let i = 0, p; p = point[i]; i++) { let {x, y, z} = p, a0 = q3 * x + q1 * z - q2 * y, a1 = q3 * y + q2 * x - q0 * z, a2 = q3 * z + q0 * y - q1 * x, a3 = -q0 * x - q1 * y - q2 * z; point[i].x = a0 * q3 - a3 * q0 - a1 * q2 + a2 * q1; point[i].y = a1 * q3 - a3 * q1 - a2 * q0 + a0 * q2; point[i].z = a2 * q3 - a3 * q2 - a0 * q1 + a1 * q0; } }; //__________ this.RotationController = RotationController; } {// Render (Canvas) const DEF_OPTION = { surface : true, hiddenSurface: true, lighting : true, wireFrame : false, vertex : false, clsColor : 'rgb(0,0,0)' }, PI2 = Math.PI * 2; class Render { constructor (canvas, camera, light, option = { }) { this.canvas = canvas; this.ctx = canvas.getContext ('2d'); this.camera = camera; this.light = light; this.option = Object.assign ({}, DEF_OPTION, option); this.width = canvas.width; this.height = canvas.height; this.centerX= canvas.width * 0.5 + 0.5; this.centerY= canvas.height * 0.5 + 0.5; } //CANVASを塗りつぶす clsScreen (rgba = this.option.clsColor) { let {ctx, width, height} = this; ctx.fillStyle = rgba; ctx.fillRect (0, 0, width, height); } //model を描く draw (model) { let cos = Math.cos, acos = Math.acos, sqrt = Math.sqrt, max = Math.max, {ctx, centerX, centerY, light, camera, option} = this, {vertex, surface} = model, zbuf = [ ], {distance, option: {brightness: lb, ambientLight: la}, vector: {x: lx, y: ly, z: lz}} = light; //カメラからの投影結果をxy[]に保存 let xy = vertex.map (s => { let [px, py] = camera.project (s); return [centerX + px, centerY - py]; }); //面の構成が左回りを利用して2次元上で裏の向きを省く if (option.hiddenSurface) { let {x: cx, y: cy, z: cz} = camera.position; zbuf = surface.reduce ((a, b) => { let [p0, p2, p1] = b.lineList,//3点からベクトルの外積 [[x0, y0], [x1, y1], [x2, y2]] = [xy[p0], xy[p1], xy[p2]]; if ((x2 - x1) * (y0 - y1) - (y2 - y1) * (x0 - x1) > 0) a.push (b); return a; }, []); } else zbuf = surface.slice (0); //距離でソート zbuf = zbuf.map (s => { let {x: cx, y: cy, z: cz} = camera.position, {x, y, z} = s.getDistance (vertex), dx = x - cx, dy = y - cy, dz = cz -z; return [dx*dx + dy*dy + dz*dz, s]; }) .sort ((a, b) => a[0] < b[0]) .map ((a)=> a[1]); //面の描画 let brightness = 1; zbuf.forEach (s => { let P = s.lineList; if (option.lighting) { let {x, y, z} = s.getVector (vertex), len = (x*lx + y*ly + z*lz) / (distance * sqrt (x*x + y*y + z*z)), av = Point.average (...P.map (n => vertex[n])); brightness = la + max (0, len * light.getBrightness (av)); } ctx.fillStyle = s.color.multiplication (brightness).toStringRGBA (); ctx.beginPath (); ctx.moveTo (...xy[P[0]]); P.forEach ((p, i) => { ctx.lineTo (...xy[P[i]]) }); ctx.fill (); }); } } //__________ this.Render = Render; } { //物体の定義 const PI = Math.PI, ACOS = Math.acos, ATAN2 = Math.atan2, SQRT = Math.sqrt, SIN = Math.sin, COS = Math.cos, DEF_OPTION_MODEL = { size : 1, pointColor : new Color (255, 255, 255, 1), surfaceColor : new Color (255, 255, 255, .5) }, //ドーナツの定義 DEF_OPTION_DOUGHNUT = { radius : 1, N : 24, radius2 : .2, N2 : 12, pointColor : new Color (255, 255, 255, 1), surfaceColor : new Color (255, 255, 255, .5) }, Doughnut = function (option = { }) { option = Object.assign ({ }, DEF_OPTION_DOUGHNUT, option); let {radius: r0, N: n0, radius2: r1, N2: n1} = option, pi2 = PI + PI, s0 = pi2 / n0, s1 = pi2 / n1, all = n0 * n1, point = [ ], surface = [ ]; for (let i = 0; i < n0; i++) { let angle = s0 * i, idx = i * n1, idx2 = (idx + n1) % all; for (let j = 0; j < n1; j++) { let angle2 = s1 * j, r = r0 + SIN (angle2) * r1, jn = (j + 1) % n1; point.push (new Point ( [SIN (angle) * r, COS (angle2) * r1, COS (angle) * r], option.pointColor.crone (), option )); surface.push (new Surface ( [idx + j, idx + jn, idx2 + jn, idx2 + j], option.surfaceColor.crone (), option )); } } return [point, surface, option]; }, //立方体の定義 Squares = function (option = { }) { option = Object.assign ({ }, DEF_OPTION_MODEL, option); let {size, pointColor: pcol, surfaceColor: scol} = option, a = size * .5, point = [ [-a, a, a], [a, a, a], [a, a, -a], [-a, a, -a], [-a,-a, a], [a,-a, a], [a,-a, -a], [-a,-a, -a] ].map (p => new Point (p, pcol.crone (), option)), surface = [ [0,1,2,3], [0,4,5,1], [1,5,6,2], [2,6,7,3], [3,7,4,0], [4,7,6,5] ].map (p => new Surface (p, scol.crone (), option)); return [point, surface, option]; }, //正二十面体を定義する DEF_OPTION_REGULAR_ICOSAHEDRON = { radius : 100, pointColor : new Color (255, 255, 255, 1), surfaceColor : new Color (255, 255, 255, .5) }, RegularIcosahedron = function (option = { }) { option = Object.assign ({ }, DEF_OPTION_REGULAR_ICOSAHEDRON, option); let r = .5, gr = (1 + SQRT (5)) / 2, a = r / SQRT (1 + gr * gr), b = a * gr, point = [ //物体の頂点 [ 0,-a,-b], [ 0, a,-b], [ 0,-a, b], [0, a, b], [-b, 0,-a], [-b, 0, a], [ b, 0,-a], [b, 0, a], [-a,-b, 0], [ a,-b, 0], [-a, b, 0], [a, b, 0] ].map (p => new Point (p, option.pointColor, option)), surface = [//辺の順序が面を現す [ 0, 1, 6], [ 1, 0, 4], [ 2, 3, 5], [ 3, 2, 7], [ 4, 5,10], [ 5, 4, 8], [ 6, 7, 9], [ 7, 6,11], [ 8, 9, 2], [ 9, 8, 0], [10,11, 1], [11,10, 3], [ 0, 6, 9], [ 0, 8, 4], [ 1, 4,10], [ 1,11, 6], [ 2, 5, 8], [ 2, 9, 7], [ 3, 7,11], [ 3,10, 5] ].map (p => new Surface (p, option.surfaceColor, option)); return [point, surface, option]; }, //正四面体を定義する DEF_OPTION_REGULAR_TETRAHEDRON = { radius : 100, pointColor : new Color (255, 255, 255, 1), surfaceColor : new Color (255, 255, 255, .5) }, RegularTetrahedron = function (option = { }) { option = Object.assign ({ }, DEF_OPTION_REGULAR_TETRAHEDRON, option); let r = option.radius, a = 1/SQRT(3/8) * r, h = SQRT(2/3)*a, b = -h + r, point = [ //物体の頂点 [0, r, 0], [0, b, r], [a / 2, b, b], [-a/2, b, b] ].map (p => new Point (p, option.pointColor, option)), surface = [//辺の順序が面を現す(先に登録された頂点の番号) [ 0, 1, 2], [ 0, 2, 3], [ 0, 3, 1], [ 3, 2, 1], ].map (p => new Surface (p, option.surfaceColor, option)); return [point, surface, option]; }, //角柱を定義する DEF_OPTION_PRISM = { radius : 100, pointColor : new Color (255, 255, 255, 1), surfaceColor : new Color (255, 255, 255, .5) }, Prism = function (option = { }) { option = Object.assign ({ }, DEF_OPTION_PRISM, option); let r = option.radius, N = option.N, h = r / 2, S = (PI + PI) / N, vertex = [ ], surface = [ ], bottom = [ ], top = [ ]; for (let i = 0; i < N; i++) { let s = S * i, i2 = i * 2, sir = SIN (s) * r, cir = COS (s) * r; vertex.push (new Point ([sir, h, cir], option.pointColor, option)); vertex.push (new Point ([sir, -h, cir], option.pointColor, option)); surface.push (new Surface ([i2, i2 + 1, ((i2 + 2) % (N*2))+1, ((i2 + 2) % (N*2))], option.surfaceColor, option)); top.push (i2); bottom.push ((i2 + 1)); } surface.push (new Surface (top, option.surfaceColor, option)); surface.push (new Surface (bottom.reverse (), option.surfaceColor, option)); return [vertex, surface, option]; }; const createModel = function (type, option) { switch (type) { case 'regularIcosahedron' : return RegularIcosahedron (option); //正二十面体 case 'regularTetrahedron' : return RegularTetrahedron (option); //正四面体 case 'doughnut' : return Doughnut (option); //ドーナツ case 'pyramid' : return Pyramid (option); //多角推 case 'squares' : return Squares (option); //立方体 case 'prism' : return Prism (option); //角柱 default : throw new Error ('無効な形状です') } }; //________________________ const //2つのベクトルの中間を返す createPoint = function (a, b, r) { let { x: ax, y: ay, z: az } = a, { x: bx, y: by, z: bz } = b; cx = (ax + bx) * .5, cy = (ay + by) * .5, cz = (az + bz) * .5, d = cx * cx + cy * cy + cz * cz, d2 = d * 2, e = SQRT (r * 2 * d2) / d2; return new Point ([cx * e, cy * e, cz * e]); }; class Modeler extends Model { constructor (type, option) { super (...createModel (type, option)); } //面の細分化 subdivideSurfaces (n = 1) { if (n < 1 || 10 < n) throw new Error ('引数が範囲外です'); let V = this.vertex, R = this.option.radius, RR = R * R; for (let i = 0; i < n; i++) { this.surface.forEach (s => { let {lineList, color, option} = s, [p0, p1, p2] = lineList, [ a, b, c] = [V[p0], V[p1], V[p2]], [ d, e, f] = [createPoint (a, b, RR), createPoint (b, c, RR), createPoint (c, a, RR)], [dn, en, fn] = [this.addVertex (d), this.addVertex (e), this.addVertex (f)]; //従来の三角形を4分割した中央の三角形にする s.lineList = [dn, en, fn]; [V[dn], V[en], V[fn]] = [d, e, f]; //他の3つの三角形の面を追加する this.addSurface (new Surface ([p0, dn, fn], color, option)); this.addSurface (new Surface ([p1, en, dn], color, option)); this.addSurface (new Surface ([p2, fn, en], color, option)); }); } return this; } subdivide (surfaceNo = [ ], n = 1) { let V = this.vertex, A = [ ], //分割しない面 B = [ ]; //分割対象面 if (0 === surfaceNo.length) B = this.surface; else { let a = [...Array.from (this.surface).key ()]; surfaceNo.forEach (n => a.splice (n, 1)); A = a.map (n => this.surface[n]); B = surfaceNo.map (n => this.surface[n]); } for (let i = 0; i < n; i++) { let C = [ ]; B.forEach (s => { let type = s.lineList.length, {lineList, color, option} = s; if (3 === type) { //面の2分化(三角形ABCの辺ABを二等分した点Dと、点Cを結ぶ線で二分する) //2つの三角形はそれぞれ、三角形CDBと三角形 let [a, b, c] = lineList, d = this.addVertex (Point.average (...[V[a], V[b]])); C.push (new Surface ([c, a, d], color, option)); C.push (new Surface ([b, c, d], color, option)); } else if (3 < type) { //面の頂点の数が3を越える場合は、面の頂点の中心(平均)点を中心とした三角形を形成 let n = this.addVertex (Point.average (...lineList.map (n => V[n]))); for (let i = 0, I = lineList.length; i < I; i++) { C.push (new Surface ([lineList[i], lineList[(i+1)%I], n], color, option)); } } }); B = C; } this.surface = A.concat (B); return this; } //面を選択する(余計な面、点を排除する) selectTo (surfaceNo = []) { if (0 === surfaceNo.length) throw new Error ('引数がありません'); let { vertex, surface } = this, V = [ ], //整理された頂点 M = [ ], //頂点の変換表 S = surfaceNo.map (n => surface[n]);//面を選択 for (let i = 0, I = S.length; i < I; i++) { let l = S[i].lineList; for (let j = 0, J = l.length; j < J; j++) { let no = l[j], idx = M[no]; if ('undefined' === typeof idx) //変換表にあるか? M[no] = idx = V.push (vertex[no]) -1; S[i].lineList[j] = idx; } } this.surface = S; this.vertex = V; } //拡大・縮小 scaleTo (nx = 1, ny = nx, nz = nx) { if (0 >= nx || 0 >= ny || 0 >= nz) throw new Error ('範囲外の数値が指定されました'); this.vertex.forEach (p => { p.x *= nx, p.y *= ny, p.z *= nz}); return this; } //移動 moveTo (nx = 0, ny = 0, nz = 0) { this.vertex.forEach (p => { p.x += nx, p.y += ny, p.z += nz}); return this; } //コピー copyTo () { let {vertex, surface, option} = this; return new Model (vertex, surface, option); // return Object.assign ({ }, this); } //回転 rotationTo (angleX, angleY, angleZ, angle) { let deg = PI / 180, q0 = SIN (angleX * deg /2), q1 = SIN (angleY * deg /2), q2 = SIN (angleZ * deg /2), q3 = COS (angle * deg / 2); this.vertex.forEach (p => { let {x, y, z} = p, a0 = q3 * x + q1 * z - q2 * y, a1 = q3 * y + q2 * x - q0 * z, a2 = q3 * z + q0 * y - q1 * x, a3 = -q0 * x - q1 * y - q2 * z; p.x = a0 * q3 - a3 * q0 - a1 * q2 + a2 * q1; p.y = a1 * q3 - a3 * q1 - a2 * q0 + a0 * q2; p.z = a2 * q3 - a3 * q2 - a0 * q1 + a1 * q0; }); return this; } } //__________ this.Modeler = Modeler; } { //demo const CANVAS = document.querySelector ('canvas'), CAMERA = new Camera (new Point3D (0, 0, 500), 50/*mm*/, {scale: 10}), OPACITY = 1, MCtrl_MODEL_OPT = { radius: .5, N: 24, radius2: .1, N2: 24}, MCtrl_ROTE_OPT = {gain: 1}, MCtrl_LIGHT_OPT = {ambientLight: 0.1}, MCtrl_RENDER_OPT = {hiddenSurface: true}, MCtrl_MODEL = new Modeler ('squares', MCtrl_MODEL_OPT), MCtrl_ROTE = new RotationController (CANVAS, MCtrl_MODEL, MCtrl_ROTE_OPT), MCtrl_LIGHT = new Light (new Vector (1, 5, 10), new Color (), MCtrl_LIGHT_OPT), MCtrl_RENDER = new Render (CANVAS, CAMERA, MCtrl_LIGHT, MCtrl_RENDER_OPT), rnd = function () { return Math.floor (Math.random() * 128+128); }, setRandomColor = function (model) { let i, surface; for (i = 0; surface = model.surface[i]; i++) surface.color = new Color (rnd (), rnd (), rnd (), OPACITY); }, setSurfaceColor = function (model, col) { model.surface.forEach (s => s.color = col); }, loop = function loop () { MCtrl_RENDER.clsScreen (); MCtrl_RENDER.draw (MCtrl_MODEL); requestAnimationFrame (loop); }; //_____________ MCtrl_MODEL.scaleTo (300, 300, 300);//subdivideSurfaces (2); MCtrl_MODEL.subdivide ([], 7); setSurfaceColor (MCtrl_MODEL, new Color (255,100,100,OPACITY)); this.demo = loop; } //___________________________________ demo (); </script>