/* * updated 2003/07/16 * * mail : peace@skipup.com * home : http://www.skipup.com/~peace/ */ /** * 回転オブジェクト. * ボーダー付加可能. * * ■ インスタンス変数(明示しない限り全てprivate) * double radius 回転半径.Z座標にも適用. * Point3 center 回転中心座標.上記座標系で指定. * Dimension elementSize 各要素のサイズ. * JElement[] elements 要素. * color[] colors 要素のカラー配列. * グラデーション色などを指定することでZ座標に応じ,色が変化. * double theta 回転中陣軸に関するパラメータθ. * 回転中心軸を原点を通るr=1とした極座標で指定する. * double fai 回転中陣軸に関するパラメータφ. * 回転中心軸を原点を通るr=1とした極座標で指定する. * double alpha 1ループあたりの回転角度. * 例えばこの値が2πのとき,1ループでちょうど一回転する. * double pahseRange オブジェクト全体が占める位相. * 例えばこの値が2πのとき真円,πのとき半円を描く. * 0.0のとき無回転. * int borderSize 枠線幅. * color[] borderColors 枠線のカラー配列. * グラデーション色などを指定することでZ座標に応じ,色が変化. * boolean borderPainting 枠線を表示可能な状態のとき真. * double minZ Z座標定義領域下限値. * double maxZ Z座標定義領域上限値. * boolean outsideDisplaying 要素が画面外に出たとき描画するか否か. * * ■ 基本的な使い方 * var circler = * new Circler3(19, 100, new Point3(0, 0, 50), new Dimension(20, 20)); * * window.onload = function() { * startCircler(); * }; * * var timeCircler = 250; * var timerRunCircler = null; * * function startCircler() { * if (timerRunCircler != null) * return; * runCircler(); * } * * function runCircler() { * circler.run(); * timerRunCircler = setTimeout("runCircler();", timeCircler); * } * * function stopCircler() { * if (timerRunCircler == null) * return; * * clearTimeout(timerRunCircler); * timerRunCircler = null; * } * * ■ 定義 * ・currentCountがeolCountまで到達したとき,「1ループした」と決める. * * ■ 座標系・回転系について * 右手座標系・右手回転系とし, * Y↑ * | * ◎―→X * Z * ブラウザ表示領域左上端を原点とする. * スクリーン座標系ではないことに注意. * * ■ 極座標について * 上記座標系において,θとφを * θはY軸+方向をθ=0とし,Z-X平面に回すのを正, * φをX軸からZ軸−方向に回すとき正と定義. * 直交座標変換は次のようになる. * x = r * sin(θ) * cos(φ) * y = r * cos(θ) * z = -r * sin(θ) * sin(φ) * * 逆変換(直交座標から極座標へ) * r = √(x^2 + y^2 + z^2) * tanθ = √((z^2 + x^2) / y^2) 又は tanθ = (√(z^2 + x^2)) / y * tanφ = -z / x * * ■ 円の生成方法について * Z-X平面に要素を回転させ,円を生成する. * 要素の回転の方向はY軸を回転軸とし,右ネジの回転方向に従う. * 円の中心軸は初期値を原点を通る(0 1 0)とし(Y軸), * メンバtheta, faiにより極座標回転したもので決定される. * * ■ Z座標の扱い方と背景色の決定方法について * 回転時の各要素の色の計算方法は以下のようになる. * 例えば,要素回転面がX-Y平面,回転軸がY軸とし, * 回転半径が100,回転中心のZ座標が0, * Z座標下限が0, Z座標上限が200, * カラー値を[#000000, #111111, ... #ffffff]などと与え, * * ある要素のZ座標が0(Z座標下限)の場合,背景色は#000000(d=0.0), * Z座標が50の場合,#444444(d=0.25), * Z座標が100の場合,#888888(d=0.5)となる. * 図で示すと以下. * ===================================================================== * 半径(=100) * ┌―――――――┐ * * ――|―――――――|―――|―――|―――――――|―― * Z座標 * 200(MAX) 100 50 0(MIN) -100 * カラー * #ffffff #888888 #444444 #000000 ... #000000 * カラー配列の参照位置(割合値 0.0以上1.0以下) * 1.0 0.5 0.25 0.0 0.0 .... 0.0 * ===================================================================== * 下限(上限)を超えた場合,参照位置は0.0(1.0)となる. * * ■ オブジェクト全体がとり得る位置の範囲 * X方向 : (radius + elementSize.width / 2 + borderSize) * 2 * Y方向 : (radius + elementSize.height / 2 + borderSize) * 2 * Z方向 : radius * 枠線はCSS1仕様に従い,幅(高さ)の外側に配置される. * そのためborderSizeが足されていることに注意. * (全ブラウザ共通,但し枠線未対応ブラウザは除く) * * ■ メモ * ・X-Y平面で円を生成. * p.x = r * Math.cos(t); * p.y = r * Math.sin(t); * p.z = 0; * ・Y-Z平面で円を生成. * p.x = 0; * p.y = r * Math.cos(t); * p.z = r * Math.sin(t); * ・Z-X平面で円を生成. * p.x = r * Math.sin(t); * p.y = 0; * p.z = r * Math.cos(t); * ・Math.atan(v)は [-π/2, π/2] を返す. * ・Math.atan2(y, x)は [-π, π] を返す. */ function class__Circler3__(window) { var classId = Object.getClassName(arguments.callee); var PI = Math.PI; var BW = new JClient(window); var BACK_COMPAT = BW.compatMode == 0; /** * 枠線対応ブラウザの時,真. */ var BORDER_COMPAT = BW.ie4 || BW.dom; /** * colorsのデフォルト値.黒→白のグラデーション. */ var DEFAULT_COLORS = new Array( "#000000", "#111111", "#222222", "#333333", "#444444", "#555555", "#666666", "#777777", "#888888", "#999999", "#aaaaaa", "#bbbbbb", "#cccccc", "#dddddd", "#eeeeee", "#ffffff"); /** * コンストラクタ. * Circler3(int n, double radius, Point center, Dimension elementSize) */ var F = window[classId] = function(n, radius, center, elementSize) { if (this.constructor !== F) this.constructor = F; this.$radius = Math.abs(radius); this.$center = new Point3(center); this.$elementSize = new Dimension(elementSize); this.$elements = new Array(n); this.$colors = DEFAULT_COLORS; this.$theta = 0.0; this.$fai = 0.0; this.$borderSize = 0; this.$borderColors = null; this.$borderPainting = false; for (var i = 0, n = this.$elements.length; i < n; i++) { var e = this.$elements[i] = JElement.createInstance(); e.toLaserElement(); e.setSize(this.$elementSize); /*e.style.color = "#ffffff"; e.style.fontSize = 13 + "px"; e.setBody(i + "");*/ } this.setLimitZ(); this.$alpha = 2 * PI; this.$phaseRange = 2 * PI; this.$outsideDisplaying = false; }; var FP = F.prototype; /** * 1ステップだけ移動を実行する. * スムーズに回転させるためにはこのメソッドをsetTimeoutなどで * 再帰呼び出しし,currentCountをインクリメントしながら, * 連続で呼び出す必要がある. * フィールド:outsideDisplayingを偽に設定することで * 画面外に出そうなエレメントを描画しない設定に出来るが, * 内部でブラウザ表示領域をその都度参照するため,負荷が大きくなる. * * void run(int currentCount, int eolCount) * @param currentCount 実行したいカウント値を指定.0以上推奨. * この値がeolCount以上でも問題なく, * 内部でcurrentCount%=eolCountとし, * カウント値を循環させている. * @param eolCount 1ループあたりの分割数を指定.1以上. * この値が小さいほど,荒くアニメーションされることになる. */ FP.run = function(currentCount, eolCount) { var c = (currentCount %= eolCount) / eolCount; // 初期位相(element0の位相). var alpha0 = this.$alpha * c; // 枠線を含めた要素のサイズ var realElementWidth = this.$elementSize.width + (BORDER_COMPAT ? this.$borderSize * 2 : 0), realElementHeight = this.$elementSize.height + (BORDER_COMPAT ? this.$borderSize * 2 : 0); // オフセット量. var offsetX = this.$center.x - realElementWidth * 0.5, offsetY = -this.$center.y - realElementHeight * 0.5, offsetZ = this.$center.z; // Z座標範囲. var minZ = this.$minZ, maxZ = this.$maxZ; // 背景色決定時に使用される. var zColorLength = this.$colors != null ? (this.$colors.length - 1.0e-6) : 0.0; var cosT = Math.cos(this.$theta), sinT = Math.sin(this.$theta), cosF = Math.cos(this.$fai), sinF = Math.sin(this.$fai); var WIDTH = 0, HEIGHT = 0; if (!this.$outsideDisplaying) { var SIZE = BW.getSize(); WIDTH = SIZE.width; HEIGHT = SIZE.height; } // 枠線色を設定するか // ボーダー色決定時に使用される. var borderColorLength = this.$borderPainting ? (this.$borderColors.length - 1.0e-6) : 0.0; var elements = this.$elements; for (var i = 0, n = elements.length; i < n; i++) { var alpha = alpha0 + this.$phaseRange * ((i + 0.0) / n); // 位相 var z0 = this.$radius * Math.cos(alpha), x0 = this.$radius * Math.sin(alpha); var x = cosF * cosT * x0 + sinF * z0 + offsetX, y = sinT * x0 + offsetY, // スクリーン座標に変換している. z = -sinF * cosT * x0 + cosF * z0 + offsetZ; var e = elements[i]; if (!this.$outsideDisplaying && (x + realElementWidth > WIDTH || y + realElementHeight > HEIGHT)) { e.setPosition(-realElementWidth, -realElementHeight); continue; } e.setPoint(x, y, z); var zRatio = z <= minZ ? 0.0 : (z >= maxZ ? 1.0 : (z - minZ) / (maxZ - minZ)); e.setBackgroundColor(this.$colors !== null ? this.$colors[Math.floor(zColorLength * zRatio)] : null); if (this.$borderPainting) e.style.borderColor = this.$borderColors[ Math.floor(borderColorLength * zRatio)]; } }; /** * 半径を設定. */ FP.setRadius = function(r) { this.$radius = Math.abs(r); }; /** * 半径を取得. */ FP.getRadius = function() { return this.$radius; }; /** * */ FP.getElements = function() { return this.$elements; }; /** * 円生成面(要素回転面)の法線ベクトルを設定. * 回転中心を原点にオフセットしたとき, * 指定する方向ベクトルを法線ベクトルとし,原点を通る面 * で要素が回転する(円を生成する). * 回転中心を原点にオフセットしたとき, * これが回転中心軸となることから,メソッド名をsetAxisとする. * 軸だからといって通過点座標を与える必要はない. * * void setAxis(Vector3 n) * 方向ベクトルで指定. * * void setAxis(double theta, double fai) * 極座標形式(r=1 θ φ)で指定. */ FP.setAxis = function(p0, p1) { switch (arguments.length) { case 1 : this.$theta = Math.atan2( Math.sqrt(p0.z * p0.z + p0.x * p0.x), p0.y); this.$fai = Math.atan2(-p0.z, p0.x); return; case 2 : this.$theta = p0; this.$fai = p1; return; } }; /** * 円生成面の法線ベクトルを直交座標系式で取得. * @see #getAxisAngleTheta() * @see #getAxisAngleFai() */ FP.getAxisVector = function() { var cosT = Math.cos(this.$theta), sinT = Math.sin(this.$theta), cosF = Math.cos(this.$fai), sinF = Math.sin(this.$fai); return new Vector3(sinT * cosF, cosT, -sinT * sinF); }; /** * 円生成面の法線ベクトル(極座標形式θ座標)を取得. * @see #getAxisVector() */ FP.getAxisAngleTheta = function() { return this.$theta; }; /** * 円生成面の法線ベクトル(極座標形式φ座標)を取得. * @see #getAxisVector() */ FP.getAxisAngleFai = function() { return this.$fai; }; /** * 1ループあたりの回転角度を設定. * 例えばこの値が2πのとき,1ループで要素が1回転する. * 4πのとき,1ループで要素が2回転する. */ FP.setAlpha = function(alpha) { this.$alpha = alpha; }; /** * オブジェクト全体(全エレメント)が占める位相範囲を設定. * 例えばこの値が2πのとき真円,πのとき半円,π/2のとき4分の1円を描く. * void setPhaseRange(double range) */ FP.setPhaseRange = function(v) { this.$phaseRange = v; }; /** * 回転中心点を設定. * setCenter(Point3 centerPosition) * ブラウザ左上端を原点とする上記の座標系で指定することに注意. */ FP.setCenter = function(p) { this.$center.x = p.x; this.$center.y = p.y; this.$center.z = p.z; }; /** * 回転中心点を取得. * Point3 getCenter() * ブラウザ左上端を原点とする上記の座標系で取得することに注意. */ FP.getCenter = function(p) { return new Point3(this.$center); }; /** * */ FP.setCenterX = function(x) { this.$center.x = x; }; /** * ブラウザ左上端を原点とする上記の座標系で取得することに注意. */ FP.getCenterX = function() { return this.$center.x; }; /** * ブラウザ左上端を原点とする上記の座標系で設定することに注意. */ FP.setCenterY = function(y) { this.$center.y = y; }; /** * */ FP.getCenterY = function() { return this.$center.y; }; /** * */ FP.setCenterZ = function(z) { this.$center.z = z; }; /** * */ FP.getCenterZ = function() { return this.$center.z; }; /** * Z座標の定義範囲を設定. * * void setLimitZ(int minZ, int maxZ) * Z座標の定義範囲を最小値(minZ), * 最大値(maxZ)で指定. * * void setLimitZ() * 現在のZ座標と半径を元に,自動計算される. * このメソッドを呼び出すことで, * オブジェクトがとり得るZ座標最大値がmaxZ, * 最小値がminZに設定される. */ FP.setLimitZ = function(minZ, maxZ) { if (arguments.length == 0) { this.$minZ = this.$center.z - this.$radius; this.$maxZ = this.$center.z + this.$radius; } else { this.$minZ = minZ; this.$maxZ = maxZ; } }; /** * Zの範囲を取得. */ FP.getLimitZ = function() { return new Array(this.$minZ, this.$maxZ); }; /** * エレメントのサイズを設定. * このメソッド内部ではエレメント位置を再計算しない. * void setElementSize(Dimension elementSize) */ FP.setElementSize = function(elementSize) { this.$elementSize.set(elementSize); var es = this.$elements; var n = es.length; // 枠線対応で互換モードのとき,枠線分,大きくする必要がある. // 枠線未対応ブラウザのときは一緒. if (BORDER_COMPAT && BACK_COMPAT) { var size = new Dimension( this.$elementSize.width + this.$borderSize * 2, this.$elementSize.height + this.$borderSize * 2); for (var i = 0; i < n; i++) es[i].setSize(size); } else { for (var i = 0; i < n; i++) es[i].setSize(this.$elementSize); } }; /** * */ FP.getElementSize = function() { return new Dimension(this.$elementSize); }; /** * 要素の枠線幅,枠線色を設定. * ボーダーの配置方法はCSS1仕様に添う. * 枠線未対応ブラウザはフィールド値を設定するだけ. * void setBorder(int width) * void setBorder(color[] colros) * void setBorder(int width, colors[] colors) */ FP.setBorder = function(width, colors) { switch (arguments.length) { case 1 : var a0 = arguments[0]; if (typeof a0 == "number") { this.$borderSize = Math.floor(a0); } else { this.$borderColors = a0; } break; case 2 : this.$borderSize = Math.floor(arguments[0]); this.$borderColors = arguments[1]; break; } this.$borderPainting = BORDER_COMPAT && this.$borderSize !== 0 && this.$borderColors !== null && this.$borderColors.length !== 0; if (!BORDER_COMPAT) return; var es = this.$elements; if (es[0].style.borderStyle != "solid") for (var i = 0, n = es.length; i < n; i++) es[i].style.borderStyle = "solid"; if (BACK_COMPAT) { // 枠線分サイズを大きくする. var width = this.$elementSize.width + this.$borderSize * 2, height = this.$elementSize.height + this.$borderSize * 2; for (var i = 0, n = es.length; i < n; i++) es[i].setSize(width, height); } for (var i = 0, n = es.length; i < n; i++) es[i].style.borderWidth = this.$borderSize + "px"; } /** * 要素の色を設定. * void setColors(color[] colors); * @param colors グラデーション色などの色配列. * nullを設定することでエレメントの色を透過に出来るが, * 枠線色を設定していない場合,当然何も表示されない. */ FP.setColor = function(colors) { this.$colors = colors; }; /** * 要素が画面外に配置されるとき,その要素を描画するか. * 配置しない設定にすると,画面外の要素は描画されず, * スクロールバーが表示されるのを防止出来ます. * @param f 描画するとき真. */ FP.displayAtOutside = function(f) { this.$outsideDisplaying = f; }; /** * オブジェクトの可視設定. * 回転している状態で不可視としても回転はストップしないことに注意. */ FP.setVisibility = function(v) { var es = this.$elements; for (var i = 0, n = es.length; i < n; i++) es[i].setVisibility(v); }; FP.toString = function() { return classId + ":" + "[" + ("r=" + this.$radius + ",") + ("center=" + this.$center + ",") + ("(θ,φ)=(" + this.$theta + "," + this.$fai + ")") + "]"; }; } class__Circler3__(window);