Skip to content

Instantly share code, notes, and snippets.

@edom18
Created March 24, 2013 13:44
Show Gist options
  • Save edom18/5232019 to your computer and use it in GitHub Desktop.
Save edom18/5232019 to your computer and use it in GitHub Desktop.
Quaternionを使って3D表現
# Quaternionを使って3D回転を表現
独自のCameraクラスなどを使って、銀河系的な表現に挑戦。
##更新履歴
* Z軸の値を換算し、前後関係を解決後にレンダリングするよう修正
* iOSにも対応しました。ピンチイン・アウトで視点が近づいたり遠のいたりします。
* ドラッグで視点移動ができ、またホイールで視点が近づいたり遠のいたりします。
body, div, p {
margin: 0;
padding: 0;
}
<div>
<canvas id="canvas" width="300" height="300"></canvas>
</div>
do (win = window, doc = window.document, exports = window) ->
#Import
{sqrt, tan, cos, sin, PI} = Math
DEG_TO_RAD = PI / 180
ANGLE = PI * 2
drawTriangle = (g, img, vertex_list, uv_list) ->
width = img.width
height = img.height
# 変換後のベクトル成分を計算
_Ax = vertex_list[2] - vertex_list[0]
_Ay = vertex_list[3] - vertex_list[1]
_Bx = vertex_list[4] - vertex_list[0]
_By = vertex_list[5] - vertex_list[1]
# 裏面カリング
# 頂点を結ぶ順が反時計回りの場合は「裏面」になり、その場合は描画をスキップ
# 裏面かどうかの判定は外積を利用する
# 判定は、3点の内、1-2点目と2-3点目との外積を計算し、結果がマイナスの場合は反時計回り。(外積の結果はZ軸に対しての数値)
return if(((_Ax * (vertex_list[5] - vertex_list[3])) - (_Ay * (vertex_list[4] - vertex_list[2]))) < 0)
# 変換前のベクトル成分を計算
Ax = (uv_list[2] - uv_list[0]) * width
Ay = (uv_list[3] - uv_list[1]) * height
Bx = (uv_list[4] - uv_list[0]) * width
By = (uv_list[5] - uv_list[1]) * height
# move position from A(Ax, Ay) to _A(_Ax, _Ay)
# move position from B(Ax, Ay) to _B(_Bx, _By)
# A,Bのベクトルを、_A,_Bのベクトルに変換することが目的。
# 変換を達成するには、a, b, c, dそれぞれの係数を導き出す必要がある。
#
# ↓まずは公式。アフィン変換の移動以外を考える。
#
# _Ax = a * Ax + c * Ay
# _Ay = b * Ax + d * Ay
# _Bx = a * Bx + c * By
# _By = b * Bx + d * By
#
# ↓上記の公式を行列の計算で表すと以下に。
#
# |_Ax| = |Ax Ay||a|
# |_Bx| = |Bx By||c|
#
# ↓a, cについて求めたいのだから、左に掛けているものを「1」にする必要がある。
#  行列を1にするには、逆行列を左から掛ければいいので、両辺に逆行列を掛ける。(^-1は逆行列の意味)
#
# |Ax Ay|^-1 |_Ax| = |a|
# |Bx By| |_Bx| = |c|
m = new Matrix2()
# 上記の
# |Ax Ay|
# |Bx By|
# を生成
m._11 = Ax; m._12 = Ay
m._21 = Bx; m._22 = By
# 逆行列を取得
# 上記の
# |Ax Ay|^-1
# |Bx By|
# を生成
mi = m.getInvert()
# 逆行列が存在しない場合はスキップ
return if not mi
a = mi._11 * _Ax + mi._12 * _Bx
c = mi._21 * _Ax + mi._22 * _Bx
b = mi._11 * _Ay + mi._12 * _By
d = mi._21 * _Ay + mi._22 * _By
# 各頂点座標を元に三角形を作り、それでクリッピング
g.save()
g.beginPath()
g.moveTo(vertex_list[0], vertex_list[1])
g.lineTo(vertex_list[2], vertex_list[3])
g.lineTo(vertex_list[4], vertex_list[5])
g.clip()
g.transform(a, b, c, d,
vertex_list[0] - (a * uv_list[0] * width + c * uv_list[1] * height),
vertex_list[1] - (b * uv_list[0] * width + d * uv_list[1] * height))
g.drawImage(img, 0, 0)
g.restore()
# -------------------------------------------------------------------------------
###*
Vector3 class
@constructor
@param {number} x Position of x.
@param {number} y Position of y.
@param {number} z Position of z.
###
class Vector3
constructor: (@x = 0, @y = 0, @z = 0) ->
zero: ->
@x = @y = @z = 0;
sub: (v) ->
@x -= v.x
@y -= v.y
@z -= v.z
return @
subVectors: (a, b) ->
@x = a.x - b.x
@y = a.y - b.y
@z = a.z - b.z
return @
add: (v) ->
@x += v.x
@y += v.y
@z += v.z
return @
copy: (v) ->
@x = v.x
@y = v.y
@z = v.z
return @
norm: ->
sqrt(@x * @x + @y * @y + @z * @z)
normalize: ->
nrm = @norm()
if nrm isnt 0
@x /= nrm
@y /= nrm
@z /= nrm
return @
multiply: (v) ->
@x *= v.x
@y *= v.y
@z *= v.z
return @
#scalar multiplication
multiplyScalar: (s) ->
@x *= s
@y *= s
@z *= s
return @
multiplyVectors: (a, b) ->
@x = a.x * b.x
@y = a.y * b.y
@z = a.z * b.z
#dot product
dot: (v) ->
return @x * v.x + @y * v.y + @z * v.z
cross: (v, w) ->
return @crossVector(v, w) if w
@x = (@y * v.z) - (@z * v.y)
@y = (@z * v.x) - (@x * v.z)
@z = (@x * v.y) - (@y * v.x)
return @
#cross product
crossVector: (v, w) ->
@x = (w.y * v.z) - (w.z * v.y)
@y = (w.z * v.x) - (w.x * v.z)
@z = (w.x * v.y) - (w.y * v.x)
return @
toString: ->
"#{@x},#{@y},#{@z}"
# -------------------------------------------------------------------
###*
Matrix2 class
@constructor
###
class Matrix2
constructor: ->
@elements = te = new Float32Array 4
# |1 0|
# |0 1|
# の行列で初期化
te[0] = 1; te[2] = 0;
te[1] = 0; te[3] = 1;
#@_11 = 1; @_12 = 0;
#@_21 = 0; @_22 = 1;
###*
逆行列を生成
[逆行列の公式]
A = |a b|
|c d|
について、detA = ad - bc ≠0のときAの逆行列が存在する
A^-1 = | d -b| * 1 / detA
|-c a|
###
getInvert: ->
out = new Matrix2()
oe = out.elements
te = @elements
det = te[0] * te[3] - te[2] * te[1]
#det = @_11 * @_22 - @_12 * @_21
return null if 0.0001 > det > -0.0001
oe[0] = te[3] / det
oe[1] = -te[1] / det
oe[2] = -te[2] / det
oe[3] = te[0] / det
#out._11 = @_22 / det
#out._22 = @_11 / det
#out._12 = -@_12 / det
#out._21 = -@_21 / det
return out
# -----------------------------------------------------------
###*
Matrix4 class
@constructor
@param {boolean} cpy
###
class Matrix4
constructor: (cpy) ->
@elements = new Float32Array 16
if (cpy) then @copy cpy else @ident()
ident: ->
# 以下のように初期化
# |1 0 0 0|
# |0 1 0 0|
# |0 0 1 0|
# |0 0 0 1|
te = @elements
te[0] = 1; te[4] = 0; te[8] = 0; te[12] = 0;
te[1] = 0; te[5] = 1; te[9] = 0; te[13] = 0;
te[2] = 0; te[6] = 0; te[10] = 1; te[14] = 0;
te[3] = 0; te[7] = 0; te[11] = 0; te[15] = 1;
#@_12 = @_13 = @_14 = 0
#@_21 = @_23 = @_24 = 0
#@_31 = @_32 = @_34 = 0
#@_41 = @_42 = @_43 = 0
#@_11 = @_22 = @_33 = @_44 = 1
return @
getInvert: ->
out = new Matrix4
oe = out.elements
te = @elements
a11 = te[0]; a12 = te[4]; a13 = te[8]; a14 = te[12];
a21 = te[1]; a22 = te[5]; a23 = te[9]; a24 = te[13];
a31 = te[2]; a32 = te[6]; a33 = te[10]; a34 = te[14];
a41 = te[3]; a42 = te[7]; a43 = te[11]; a44 = te[15];
det = (a11 * a22 * a33 * a44
+ a11 * a23 * a34 * a42
+ a11 * a24 * a32 * a43
+ a12 * a21 * a34 * a43
+ a12 * a23 * a31 * a44
+ a12 * a24 * a33 * a41
+ a13 * a21 * a32 * a44
+ a13 * a22 * a34 * a41
+ a13 * a24 * a31 * a42
+ a14 * a21 * a33 * a42
+ a14 * a22 * a31 * a43
+ a14 * a23 * a32 * a41
- a11 * a22 * a34 * a43
- a11 * a23 * a32 * a44
- a11 * a24 * a33 * a42
- a12 * a21 * a33 * a44
- a12 * a23 * a34 * a41
- a12 * a24 * a31 * a43
- a13 * a21 * a34 * a42
- a13 * a22 * a31 * a44
- a13 * a24 * a32 * a41
- a14 * a21 * a32 * a43
- a14 * a22 * a33 * a41
- a14 * a23 * a31 * a42)
return null if 0.0001 > det > -0.0001
b11 = ((a22 * a33 * a44) + (a23 * a34 * a42) + (a24 * a32 * a43) - (a22 * a34 * a43) - (a23 * a32 * a44) - (a24 * a33 * a42)) / det
b12 = ((a12 * a34 * a43) + (a13 * a32 * a44) + (a14 * a33 * a42) - (a12 * a33 * a44) - (a13 * a34 * a42) - (a14 * a32 * a43)) / det
b13 = ((a12 * a23 * a44) + (a13 * a24 * a42) + (a14 * a22 * a43) - (a12 * a24 * a43) - (a13 * a22 * a44) - (a14 * a23 * a42)) / det
b14 = ((a12 * a24 * a33) + (a13 * a22 * a34) + (a14 * a23 * a32) - (a12 * a23 * a34) - (a13 * a24 * a32) - (a14 * a22 * a33)) / det
b21 = ((a21 * a34 * a43) + (a23 * a31 * a44) + (a24 * a33 * a41) - (a21 * a33 * a44) - (a23 * a34 * a41) - (a24 * a31 * a43)) / det
b22 = ((a11 * a33 * a44) + (a13 * a34 * a41) + (a14 * a31 * a43) - (a11 * a34 * a43) - (a13 * a31 * a44) - (a14 * a33 * a41)) / det
b23 = ((a11 * a24 * a43) + (a13 * a21 * a44) + (a14 * a23 * a41) - (a11 * a23 * a44) - (a13 * a24 * a41) - (a14 * a21 * a43)) / det
b24 = ((a11 * a23 * a34) + (a13 * a24 * a31) + (a14 * a21 * a33) - (a11 * a24 * a33) - (a13 * a21 * a34) - (a14 * a23 * a31)) / det
b31 = ((a21 * a32 * a44) + (a22 * a34 * a41) + (a24 * a31 * a42) - (a21 * a34 * a42) - (a22 * a31 * a44) - (a24 * a32 * a41)) / det
b32 = ((a11 * a34 * a42) + (a12 * a31 * a44) + (a14 * a32 * a41) - (a11 * a32 * a44) - (a12 * a34 * a41) - (a14 * a31 * a42)) / det
b33 = ((a11 * a22 * a44) + (a12 * a24 * a41) + (a14 * a21 * a42) - (a11 * a24 * a42) - (a12 * a21 * a44) - (a14 * a22 * a41)) / det
b34 = ((a11 * a24 * a32) + (a12 * a21 * a34) + (a14 * a22 * a31) - (a11 * a22 * a34) - (a12 * a24 * a31) - (a14 * a21 * a32)) / det
b41 = ((a21 * a33 * a42) + (a22 * a31 * a43) + (a23 * a32 * a41) - (a21 * a32 * a43) - (a22 * a33 * a41) - (a23 * a31 * a42)) / det
b42 = ((a11 * a32 * a43) + (a12 * a33 * a41) + (a13 * a31 * a42) - (a11 * a33 * a42) - (a12 * a31 * a43) - (a13 * a32 * a41)) / det
b43 = ((a11 * a23 * a42) + (a12 * a21 * a43) + (a13 * a22 * a41) - (a11 * a22 * a43) - (a12 * a23 * a41) - (a13 * a21 * a42)) / det
b44 = ((a11 * a22 * a33) + (a12 * a23 * a31) + (a13 * a21 * a32) - (a11 * a23 * a32) - (a12 * a21 * a33) - (a13 * a22 * a31)) / det
oe[0] = b11; oe[4] = b12; oe[8] = b13; oe[12] = b14;
oe[1] = b21; oe[5] = b22; oe[9] = b23; oe[13] = b24;
oe[2] = b31; oe[6] = b32; oe[10] = b33; oe[14] = b34;
oe[3] = b41; oe[7] = b42; oe[11] = b43; oe[15] = b44;
return out
###*
Copy from `m`
@param {Matrix4} m
###
copy: (m) ->
te = @elements
me = m.elements
te[0] = me[0]; te[4] = me[4]; te[8] = me[8]; te[12] = me[12];
te[1] = me[1]; te[5] = me[5]; te[9] = me[9]; te[13] = me[13];
te[2] = me[2]; te[6] = me[6]; te[10] = me[10]; te[14] = me[14];
te[3] = me[3]; te[7] = me[7]; te[11] = me[11]; te[15] = me[15];
return @
###*
4x4の変換行列を対象の1x4行列[x, y, z, 1]に適用する
1x4行列と4x4行列の掛け算を行う
|@_11 @_12 @_13 @_14|
|x y z 1| x |@_21 @_22 @_23 @_24|
|@_31 @_32 @_33 @_34|
|@_41 @_42 @_43 @_44|
@_4nは1x4行列の最後が1のため、ただ足すだけになる
@param {Array.<number>} out
@param {number} x
@param {number} y
@param {number} z
###
transVec3: (out, x, y, z) ->
te = @elements
out[0] = x * te[0] + y * te[1] + z * te[2] + te[3]
out[1] = x * te[4] + y * te[5] + z * te[6] + te[7]
out[2] = x * te[8] + y * te[9] + z * te[10] + te[11]
out[3] = x * te[12] + y * te[13] + z * te[14] + te[15]
perspectiveLH: (fov, aspect, near, far) ->
tmp = Matrix4.perspectiveLH(fov, aspect, near, far)
@copy tmp
@perspectiveLH: (fov, aspect, near, far) ->
tmp = new Matrix4
te = tmp.elements
ymax = near * tan(fov * DEG_TO_RAD * 0.5)
ymin = -ymax
xmin = ymin * aspect
xmax = ymax * aspect
vw = xmax - xmin
vh = ymax - ymin
zoomX = 2 * near / vw
zoomY = 2 * near / vh
# X軸方向のzoom値
te[0] = zoomX;
te[4] = 0
te[8] = 0
te[12] = 0
# Y軸方向のzoom値
te[1] = 0
te[5] = zoomY
te[9] = 0
te[13] = 0
# W値用の値を算出
#
# Z座標は、ニアクリップ面では z/w = -1、
# ファークリップ面では z/w = 1 になるように
# バイアスされ、スケーリングされる。
te[2] = 0
te[6] = 0
te[10] = far + near / (far - near)
te[14] = 1
te[3] = 0
te[7] = 0
te[11] = 2 * near * far / (near - far)
te[15] = 0
return tmp
multiply: (A) ->
tmp = Matrix4.multiply(@, A)
@copy tmp
return @
# multiplication
# ABふたつの行列の掛け算した結果をthisに保存
@multiply: (A, B) ->
ae = A.elements
be = B.elements
A11 = ae[0]; A12 = ae[4]; A13 = ae[8]; A14 = ae[12];
A21 = ae[1]; A22 = ae[5]; A23 = ae[9]; A24 = ae[13];
A31 = ae[2]; A32 = ae[6]; A33 = ae[10]; A34 = ae[14];
A41 = ae[3]; A42 = ae[7]; A43 = ae[11]; A44 = ae[15];
B11 = be[0]; B12 = be[4]; B13 = be[8]; B14 = be[12];
B21 = be[1]; B22 = be[5]; B23 = be[9]; B24 = be[13];
B31 = be[2]; B32 = be[6]; B33 = be[10]; B34 = be[14];
B41 = be[3]; B42 = be[7]; B43 = be[11]; B44 = be[15];
tmp = new Matrix4
te = tmp.elements
te[0] = A11 * B11 + A12 * B21 + A13 * B31 + A14 * B41
te[4] = A11 * B12 + A12 * B22 + A13 * B32 + A14 * B42
te[8] = A11 * B13 + A12 * B23 + A13 * B33 + A14 * B43
te[12] = A11 * B14 + A12 * B24 + A13 * B34 + A14 * B44
te[1] = A21 * B11 + A22 * B21 + A23 * B31 + A24 * B41
te[5] = A21 * B12 + A22 * B22 + A23 * B32 + A24 * B42
te[9] = A21 * B13 + A22 * B23 + A23 * B33 + A24 * B43
te[13] = A21 * B14 + A22 * B24 + A23 * B34 + A24 * B44
te[2] = A31 * B11 + A32 * B21 + A33 * B31 + A34 * B41
te[6] = A31 * B12 + A32 * B22 + A33 * B32 + A34 * B42
te[10] = A31 * B13 + A32 * B23 + A33 * B33 + A34 * B43
te[14] = A31 * B14 + A32 * B24 + A33 * B34 + A34 * B44
te[3] = A41 * B11 + A42 * B21 + A43 * B31 + A44 * B41
te[7] = A41 * B12 + A42 * B22 + A43 * B32 + A44 * B42
te[11] = A41 * B13 + A42 * B23 + A43 * B33 + A44 * B43
te[15] = A41 * B14 + A42 * B24 + A43 * B34 + A44 * B44
return tmp
###*
@param {Vector3} v
###
translate: (v) ->
tmp = Matrix4.translate v
@multiply tmp
return @
###*
translate by vector3
@param {Vector3} v
###
@translate: (v) ->
tmp = new Matrix4
te = tmp.elements
# As result like this
# |1 0 0 0|
# |0 1 0 0|
# |0 0 1 0|
# |x y z 1|
te[0] = 1; te[4] = 0; te[8] = 0; te[12] = 0
te[1] = 0; te[5] = 1; te[9] = 0; te[13] = 0
te[2] = 0; te[6] = 0; te[10] = 1; te[14] = 0
te[3] = v.x; te[7] = v.y; te[11] = v.z; te[15] = 1
return tmp
###*
@param {Vector3} eye
@param {Vector3} target
@param {Vector3} up
###
lookAt: do ->
#カメラに対してのX, Y, Z軸をそれぞれ定義
x = new Vector3
y = new Vector3
z = new Vector3
return (eye, target, up) ->
te = @elements
z.subVectors(eye, target).normalize()
x.crossVector(up, z).normalize()
y.crossVector(z, x).normalize()
tx = eye.dot x
ty = eye.dot y
tz = eye.dot z
te[0] = x.x; te[4] = y.x; te[8] = z.x;
te[1] = x.y; te[5] = y.y; te[9] = z.y;
te[2] = x.z; te[6] = y.z; te[10] = z.z;
te[3] = tx; te[7] = ty; te[11] = tz;
return @
###*
@param {number} r Rotate X
###
rotX: (r) ->
# X軸による回転行列
# |1 0 0 0|
# |0 cos(r) sin(r) 0|
# |0 -sin(r) cos(r) 0|
# |0 0 0 1|
te = @elements
c = cos r
s = sin r
te[0] = 1; te[4] = 0; te[8] = 0; te[12] = 0;
te[1] = 0; te[5] = c; te[9] = s; te[13] = 0;
te[2] = 0; te[6] = -s; te[10] = c; te[14] = 0;
te[3] = 0; te[7] = 0; te[11] = 0; te[15] = 1;
return @
###*
@param {number} r Rotate Y
###
rotY: (r) ->
# Y軸による回転行列
# |cos(r) 0 -sin(r) 0|
# | 0 1 0 0|
# |sin(r) 0 cos(r) 0|
# | 0 0 0 1|
te = @elements
c = cos r
s = sin r
te[0] = c; te[4] = 0; te[8] = -s; te[12] = 0;
te[1] = 0; te[5] = 1; te[9] = 0; te[13] = 0;
te[2] = s; te[6] = 0; te[10] = c; te[14] = 0;
te[3] = 0; te[7] = 0; te[11] = 0; te[15] = 1;
return @
###*
@param {number} r Rotate Z
###
rotZ: (r) ->
# Z軸による回転行列
# | cos(r) sin(r) 0 0|
# |-sin(r) cos(r) 0 0|
# | 0 0 1 0|
# | 0 0 0 1|
te = @elements
c = cos r
s = sin r
te[0] = c; te[4] = s; te[8] = 0; te[12] = 0;
te[1] = -s; te[5] = c; te[9] = 0; te[13] = 0;
te[2] = 0; te[6] = 0; te[10] = 1; te[14] = 0;
te[3] = 0; te[7] = 0; te[11] = 0; te[15] = 1;
return @
# -------------------------------------------------------------------------------
class Object3D
constructor: ->
@parent = null
@children = []
@position = new Vector3
@rotation = new Vector3
@scale = new Vector3 1, 1, 1
@up = new Vector3 0, 1, 0
@matrix = new Matrix4
@matrixWorld = new Matrix4
# -------------------------------------------------------------------------------
###*
Camera class
@constructor
@param {number} fov Field of view.
@param {number} aspect Aspect ratio.
@param {number} near Near clip.
@param {number} far far clip.
@param {Vector3} position Position vector.
###
class Camera extends Object3D
constructor: (@fov, @aspect, @near, @far, @position = new Vector3(0, 0, 20)) ->
super
@matrix = Matrix4.translate @position
@projectionMatrix = new Matrix4
@updateProjectionMatrix()
setWorld: (m) ->
@matrixWorld = m
getProjectionMatrix: ->
tmp = Matrix4.multiply @matrix, @projectionMatrix
Matrix4.multiply @matrixWorld, tmp
updateProjectionMatrix: ->
@lookAt new Vector3 0, 500, 0
@projectionMatrix.perspectiveLH(@fov, @aspect, @near, @far)
lookAt: do ->
m1 = new Matrix4
return (vector) ->
m1.lookAt @position, vector, @up
@matrix.copy m1
# -------------------------------------------------------------------------------
class Texture
constructor: (@uv_data, @uv_list) ->
# -------------------------------------------------------------------------------
class Mesh
constructor: (@vertex, @texture) ->
# -------------------------------------------------------
class Particle
constructor: (@v, @sp = 1, @size = 1000, @r = 255, @g = 255, @b = 255) ->
@vec = new Vector3 1, 0, 1
update: ->
p = new Quaternion 0, @v
rad = @sp * DEG_TO_RAD
# rad角の回転クォータニオンとその共役を生成
q = makeRotatialQuaternion(rad, @vec)
r = makeRotatialQuaternion(-rad, @vec)
# Quaternionを以下のように計算
# RPQ (RはQの共役)
p = r.multiply p
p = p.multiply q
@v = p.v
# -------------------------------------------------------------------------------
class Color
constructor: (@r, @g, @b, @a) ->
# -------------------------------------------------------------------------------
class Light
constructor: (@color) ->
# -------------------------------------------------------------------------------
class AmbientLight extends Light
constructor: (@color) ->
super
# -------------------------------------------------------------------------------
class DirectionalLight extends Light
constructor: (@color) ->
super
# -------------------------------------------------------------------------------
class Scene
constructor: ->
@materials = []
add: (material) ->
@materials.push material
sort: (func) ->
@materials.sort(func) if func
each: (func) ->
@materials.forEach(func) if func
# -------------------------------------------------------------------------------
class Renderer
constructor: (@cv, @clearColor = '#fff') ->
@g = cv.getContext '2d'
@w = cv.width
@h = cv.height
render: (scene, camera) ->
camera.updateProjectionMatrix()
matProj = camera.getProjectionMatrix()
@g.beginPath()
@g.fillStyle = @clearColor
@g.fillRect 0, 0, @w, @h
@transformAndDraw matProj, scene.materials
###*
Transform and draw.
@param {Matrix4} mat matrix.
@param {Array} materials.
###
transformAndDraw: (mat, materials) ->
g = @g
results = []
for m in materials
if m instanceof Mesh
vertex_list = material.vertex
uv_image = material.texture.uv_data
uv_list = material.texture.uv_list
@transformPoints(out_list, vertex_list, mat, @w, @h)
drawTriangle(g, uv_image, out_list, uv_list)
else if m instanceof Particle
vertex_list = [m.v.x, m.v.y, m.v.z]
out_list = new Array(6)
@transformPoints2(out_list, vertex_list, mat, @w, @h)
x = out_list[0]
y = out_list[1]
w = out_list[2]
weight = m.size / w
continue if weight < 0
results.push
material: m
x: x
y: y
w: w
r: m.r
g: m.g
b: m.b
weight: weight
results.sort (a, b) ->
b.w - a.w
for r in results
g.save()
g.fillStyle = "rgba(#{r.r}, #{r.g}, #{r.b}, #{r.weight})"
g.beginPath()
g.arc(r.x, r.y, r.weight, 0, ANGLE, true)
g.closePath()
g.fill()
g.restore()
###*
スクリーン座標変換
Transform points
@param {Array} out
@param {Array} pts
@param {Matrix4} mat matrix
@param {number} viewWidth
@param {number} viewHeight
計算された座標変換行列をスクリーンの座標系に変換するために計算する
基本はスケーリング(&Y軸反転)と平行移動。
行列で表すと
w = width / 2
h = height / 2
とすると
|w 0 0 0|
M(screen) = |0 -h 0 0|
|0 0 1 0|
|w h 0 1|
以下の計算式で言うと、
transformed_temp[0] *= viewWidth
transformed_temp[1] *= -viewHeight
transformed_temp[0] += viewWidth / 2
transformed_temp[1] += viewHeight / 2
となる。
###
transformPoints: (out, pts, mat, viewWidth, viewHeight) ->
len = pts.length
transformed_temp = [0, 0, 0, 0]
oi = 0
_w = viewWidth / 2
_h = viewHeight / 2
for i in [0...len] by 3
mat.transVec3(transformed_temp, pts[i + 0], pts[i + 1], pts[i + 2])
W = transformed_temp[3]
transformed_temp[0] /= W
transformed_temp[1] /= W
transformed_temp[2] /= W
transformed_temp[0] *= _w
transformed_temp[1] *= -_h
transformed_temp[0] += _w
transformed_temp[1] += _h
out[oi++] = transformed_temp[0]
out[oi++] = transformed_temp[1]
transformPoints2: (out, pts, mat, viewWidth, viewHeight) ->
transformed_temp = [0, 0, 0, 0]
oi = 0
_w = viewWidth / 2
_h = viewHeight / 2
mat.transVec3(transformed_temp, pts[0], pts[1], pts[2])
W = transformed_temp[3]
transformed_temp[0] /= W
transformed_temp[1] /= W
transformed_temp[2] /= W
transformed_temp[0] *= _w
transformed_temp[1] *= -_h
transformed_temp[0] += _w
transformed_temp[1] += _h
out[0] = transformed_temp[0]
out[1] = transformed_temp[1]
out[2] = W
# ---------------------------------------------------------------------
class Quaternion
constructor: (@t = 0, @v) ->
set: (@t, @v) ->
multiply: (A) ->
return Quaternion.multiply @, A
@multiply: (A, B) ->
# Quaternionの掛け算の公式は以下。
# ・は内積、?は外積、U, Vはともにベクトル。
# ;の左が実部、右が虚部。
# A = (a; U)
# B = (b; V)
# AB = (ab - U・V; aV + bU + U?V)
Av = A.v
Bv = B.v
# 実部の計算
d1 = A.t * B.t
d2 = -Av.x * Bv.x
d3 = -Av.y * Bv.y
d4 = -Av.z * Bv.z
t = parseFloat((d1 + d2 + d3 + d4).toFixed(5))
# 虚部xの計算
d1 = (A.t * Bv.x) + (B.t * Av.x)
d2 = (Av.y * Bv.z) - (Av.z * Bv.y)
x = parseFloat((d1 + d2).toFixed(5))
# 虚部yの計算
d1 = (A.t * Bv.y) + (B.t * Av.y)
d2 = (Av.z * Bv.x) - (Av.x * Bv.z)
y = parseFloat((d1 + d2).toFixed(5))
# 虚部zの計算
d1 = (A.t * Bv.z) + (B.t * Av.z)
d2 = (Av.x * Bv.y) - (Av.y * Bv.x)
z = parseFloat((d1 + d2).toFixed(5))
return new Quaternion t, new Vector3 x, y, z
###*
Make rotation quaternion
@param {number} radian.
@param {Vector3} vector.
###
makeRotatialQuaternion = (radian, vector) ->
ret = new Quaternion
ccc = 0
sss = 0
axis = new Vector3
axis.copy vector
norm = vector.norm()
return ret if norm <= 0.0
axis.normalize()
ccc = cos(0.5 * radian)
sss = sin(0.5 * radian)
t = ccc
axis.multiplyScalar sss
ret.set t, axis
return ret
exports.Matrix2 = Matrix2
exports.Matrix4 = Matrix4
exports.Camera = Camera
exports.Renderer = Renderer
exports.Scene = Scene
exports.Mesh = Mesh
exports.Particle = Particle
exports.Texture = Texture
exports.Vector3 = Vector3
exports.Quaternion = Quaternion
do (win = window, doc = window.document, exports = window) ->
{sqrt, sin, cos, tan, PI, random} = Math
isTouch = 'ontouchstart' of window
MOUSE_DOWN = if isTouch then 'touchstart' else 'mousedown'
MOUSE_MOVE = if isTouch then 'touchmove' else 'mousemove'
MOUSE_UP = if isTouch then 'touchend' else 'mouseup'
requestAnimFrame = do ->
return win.requestAnimationFrame or
win.mozRequestAnimationFrame or
win.msRequestAnimationFrame or
(callback, element) ->
win.setTimeout callback, 16
camera = null
scene = null
renderer = null
particles = []
cv = doc.querySelector '#canvas'
ctx = cv.getContext '2d'
cWidth = cv.width = win.innerWidth
cHeight = cv.height = win.innerHeight
FAR = 2000
rotX = 0
rotY = 0
rotZ = 0
dragging = false
prevX = 0
prevY = 0
# -------------------------------------------------------
init = ->
camera = new Camera 90, cWidth / cHeight, 1, FAR
camera.position.z = 1000
scene = new Scene
renderer = new Renderer cv, 'rgba(0, 0, 0, 0.08)'
hw = cWidth / 2
hh = cHeight / 2
hf = FAR / 2
base = 100
startZoom = 0
v = new Vector3 0, 0, 0
particle = new Particle v, 0, 10000, 200, 200, 0
particles[0] = particle
scene.add particle
for i in [1...300]
x = ~~(random() * cWidth) - hw
y = ~~(random() * cHeight) - hh
z = ~~(random() * FAR) - hf
r = ~~(random() * 255)
g = ~~(random() * 255)
b = ~~(random() * 255)
v = new Vector3 x, y, z
size = (~~(random() * FAR)) + 5
sp = random() * 2 + 0.1
particle = new Particle v, sp, size, r, g, b
particles[i] = particle
scene.add particle
ctx.fillStyle = '#000'
ctx.fillRect(0, 0, cWidth, cHeight)
draw()
draw = ->
for p in particles
p.update()
renderer.render scene, camera
requestAnimFrame draw
# Events
win.addEventListener 'mousewheel', (e) ->
camera.position.z -= ~~(e.wheelDelta / 10)
renderer.render scene, camera
e.preventDefault()
, false
base = 100
startZoom = 0
document.addEventListener 'gesturechange', (e) ->
num = e.scale * base - base
camera.position.z = startZoom - num
, false
document.addEventListener 'gesturestart', ->
startZoom = camera.position.z
, false
doc.addEventListener 'touchstart', (e) ->
e.preventDefault()
, false
doc.addEventListener MOUSE_DOWN, (e) ->
dragging = true
prevX = if isTouch then e.touches[0].pageX else e.pageX
prevY = if isTouch then e.touches[0].pageY else e.pageY
, false
doc.addEventListener MOUSE_MOVE, (e) ->
return if dragging is false
debugger
pageX = if isTouch then e.touches[0].pageX else e.pageX
pageY = if isTouch then e.touches[0].pageY else e.pageY
rotY += (prevX - pageX) / 100
rotX += (prevY - pageY) / 100
camera.setWorld(Matrix4.multiply((new Matrix4()).rotY(rotY), (new Matrix4()).rotX(rotX)))
prevX = pageX
prevY = pageY
renderer.render scene, camera
, false
doc.addEventListener MOUSE_UP, (e) ->
dragging = false
, false
doc.addEventListener 'DOMContentLoaded', init, false
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment