基于球坐标的3D webex-ball.

  小组里有一个小小的研究, 用前端技术画一个新的webex-ball

  (什么是webex-ball ?  webex-ball 是 webex meeting 启动时的一个icon, 旋转用于显示当前的状态, 如下)

 

PIC-1

  

  以前我们的webex-ball, 是横着旋转的. 这个时候, LD们想改了. 想来个现在流行的 "注水式" 的进度读取方式, 如下:

 

PIC-2

  话说其实最推荐的处理, 当然是用 3d-max, 画一个这样的球, 然后渲染出来成png, 然后只需要用js 完成拼接动作, 即可了.

  但是, 这样就谈不上太多的前端技术了 (大家都在想, 只用css split 能够完成的活, 怎么能够体现出我们的价值呢)

 

  于是, 很多复杂的方案出来了.

  有arlen (刘国文) 的div 嵌套遮盖的

PIC-3

PIC-4

PIC-5

 

  有dh 的css3 画的

   http://www.jslab.org.cn/?tag=WebExBall&page=2

  

  有利用几个div互相遮盖, 嵌套的 (同事们的处理).

  {   

  1. 最底层是个完成的 ball a

  2. ball a上面是个div, 盖住ball a

  3. 椭圆形在div上面

  4. 最上面是一个半透明的 ball b

  满的过程: 就是 div的height从ball的 高度变到0,顶部和ball的 顶部对齐, 慢慢露出ball a.

                         椭圆形的width 从 0 -> ball width -> 0, 位置跟着移动

  }

 

  以上的都有不可避免的缺陷, 即: 不够真实. 因为无法更具体的描绘出他的变化(看椭圆切面可得之). 所以, 如果想让他真实, 还真得把他放到比较真实的情况下.

  所以, 用3d 来构建是最好不过了.

 

     我以前写了一个JS 的渲染模式, 这个时候刚刚好可以拿来用了(以前在w3c.Hangzhou 上分享过).

  但是, 我看了下后, 发现他的代码量太多拉 (我用的 直角坐标系), 他是一套完整的系统, 需要有

  1. 世界坐标系.

  2. 摄像机坐标系.

  3. 物体坐标系.

  必须把这3者都初始化, 才能够得到一个真实的世界.

  仅仅用在一个球上, 显然是, 有些 大材小用, 或者太复杂了.

  然后, 想起了一位仁兄(ONEBOYS, http://www.cssass.com/blog/index.php), 曾经也弄过这个, 并且和他有过讨论, 他的Demo 代码很省, 再次翻到的时候, 发现他用的是球坐标体系... 果然代码省了 (球坐标和直角坐标 各有用途, 这方面没有太多的具体可比, 关键看用途)

     不多说, 看代码.

     

  1 <!DOCTYPE html>
  2 <html>
  3     <head>
  4         <meta charset='gbk' />
  5         <title>WebexBall-test-3</title>
  6     </head>
  7     <body>
  8         <div style="overflow:hidden;">
  9             <button id="btn-0">
 10                 从0到100
 11             </button>
 12             <button id="btn-1">
 13                 从100到0
 14             </button>
 15             <span>从</span>
 16             <input type="text" maxlength="3" id="inp-0" value="0" />
 17             <span>到</span>
 18             <input type="text" maxlength="3" id="inp-1" value="10" />
 19             <button id="btn-2">
 20                 开始
 21             </button>
 22         </div>
 23         <canvas id="canvas" width="800" height="800"></canvas>
 24         <script type="text/javascript" >
 25         function g(e){
 26             return document.getElementById(e);
 27         }
 28         var ctx = g('canvas').getContext('2d');
 29             ctx.scale(100,100);
 30             ctx.lineWidth=0.002;
 31             ctx.lineJoin = "round";
 32         var Ball = function(x,y,r){
 33             this.theta = 0; //球坐标转角
 34             this.eleva = 0; //球坐标仰角
 35             this.radius= r || 1; //球坐标半径
 36             this.pos={ //球坐标原点
 37                 x:x || 2,
 38                 y:y || 2
 39             };
 40             this.co={ //二维上的投影坐标
 41                 x:0,
 42                 y:0
 43             };
 44             /*
 45             this.col={ //颜色
 46                 r:255,
 47                 g:255,
 48                 b:255,
 49                 a:0.6
 50             };
 51             */
 52             this.init=function(){
 53                 ctx.translate(this.pos.x,this.pos.y);
 54                 this.theta = 1.57;
 55                 this.eleva = 0.32;
 56                 this.preview(0);
 57             };
 58             this.init();
 59         };
 60         /* 球坐标系转平面直角坐标系 */
 61         Ball.prototype.iso=function(a, b, r){
 62             var x, y, z;
 63             x = r * Math.cos(a + this.theta) * Math.sin(b);
 64             y = r * Math.sin(a + this.theta) * Math.sin(b);
 65             z = r * Math.cos(b);
 66             var fact = (y * Math.cos(this.eleva) + z * Math.sin(this.eleva) + 8) / 8;
 67             y = y*Math.sin(this.eleva) + z*Math.cos(this.eleva);
 68             x *= fact;
 69             y *= fact;
 70             return {
 71                 x: x,
 72                 y: y
 73             };
 74         }
 75         Ball.prototype.preview = function(n, u){
 76             ctx.clearRect(-2,-2,4,4);
 77             var that = this, arr = this.sphere();
 78             
 79             function fillColorBall(arr, I, bgLeft, bgRight, U){
 80                 var aMid = [],
 81                     i,
 82                     ni;
 83                 for(i = -1, ni = arr.length - 1; i++ < ni;){
 84                     var _ = arr[i][I];
 85                     U && console.log(_)
 86                     if(i == 0){
 87                         ctx.beginPath();
 88                         ctx.moveTo(_[0], _[1]);
 89                     }else{
 90                         if(i === ni){
 91                             ctx.lineTo(aMid[0], aMid[1]);
 92                             ctx.closePath();
 93                             ctx.fillStyle = bgRight;
 94                             ctx.fill();
 95                         }else{
 96                             ctx.lineTo(arr[i + 1][I][0], arr[i + 1][I][1]);
 97                             if(i === Math.floor(ni / 2)){
 98                                 aMid = [arr[i][I][0], arr[i][I][1]];
 99                                 ctx.lineTo(_[0], _[1]);
100                                 ctx.closePath();
101                                 ctx.fillStyle = bgLeft;
102                                 ctx.fill();
103                                 ctx.beginPath();
104                                 ctx.moveTo(aMid[0], aMid[1]);
105                             }
106                         }
107                     }
108                 }    
109                 
110             }
111             function fillAlphaBall(arr, I, bgAll){
112                 var i,
113                     ni;
114                 for(i = -1, ni = arr.length - 1; i++ < ni;){
115                     var _ = arr[i][I];
116                     if(i === 0){
117                         ctx.beginPath();
118                         ctx.moveTo(_[0], _[1]);
119                     }else{
120                         if(i === ni){
121                             ctx.lineTo(_[0], _[1]);
122                             ctx.closePath();
123                             ctx.fillStyle = bgAll;
124                             ctx.fill();
125                         }else{
126                             ctx.lineTo(arr[i + 1][I][0], arr[i + 1][I][1]);
127                         }
128                     }
129                 }
130             }
131             function fillSection(arr){
132                 var i,
133                     ni, j, nj,
134                     A = arr;
135                 for(i = -1, ni = A[0].length - 1; i++ < ni;){
136                     if(i > Math.floor(ni / 2)){
137                         break;
138                     }
139                     for(j = -1, nj = A.length - 1; j++ < nj;){
140                         var _ = A[j][i];
141                         if(j === 0){
142                             ctx.beginPath();
143                             ctx.moveTo(_[0], _[1]);
144                         }else{
145                             if(j > Math.floor(nj / 2)){
146                                 ctx.fillStyle = 'rgba(91, 91, 91, '+ (0.2 * i / ni) +')';
147                                 ctx.lineTo(_[0], _[1]);
148                                 ctx.closePath();
149                                 ctx.fill();
150                                 break;
151                             }else{
152                                 ctx.lineTo(A[j + 1][i][0], A[j + 1][i][1]);
153                             }
154                         }
155                     }
156                 }
157             }
158             var i, j, ni, nj, _n = !n ? 0 : n;
159             for(i = arr[0].length, ni = 0, l = i - 1, d = i / 4, getI = function(z){
160                 return Math.floor(z - d > 0 ? z - d : l - z);
161             }; i-- > ni;){
162                 if(i < _n){
163                     fillColorBall(arr, i, 'rgba(12, 12, 255, 0.15)', 'rgba(5, 207, 5, 0.15)');
164                 }else{
165                     fillAlphaBall(arr, i, 'rgba(239, 239, 239, 0.85)');
166                 }
167             }
168             fillColorBall(arr, _n, 'rgba(98, 98, 255, 0.85)', 'rgba(102, 226, 102, 0.85)', u || null);
169             //fillSection(arr);
170         };
171         Ball.prototype.sphere = function(){
172             var a, b, step = 64, arr = [];
173             for(a = 0; a < 2 * Math.PI; a += Math.PI / step){
174                 var _arr = [];
175                 for(b = 0; b < 2 * Math.PI; b += Math.PI / step){
176                     //if(b == 0 || b * 100 >> 0 == Math.PI * 100 >> 0 || b * 100 >> 0 == 2 * Math.PI * 100 >> 0)  continue; /* 排除一些仰角(接近)为0/PI/2PI的点.  */
177                     this.co = this.iso(0,0,0);
178                     //ctx.moveTo(this.co.x, this.co.y);
179                     this.co = this.iso(a,b,this.radius);
180                     //ctx.lineTo(this.co.x, this.co.y);
181                     _arr.push([this.co.x, this.co.y]);
182                 }
183                 arr.push(_arr);
184             }
185             return arr;
186         };
187         var webexBall = new Ball(2,2,1);
188         
189         var hadBegin = false,
190             btn_0 = g('btn-0'),
191             btn_1 = g('btn-1'),
192             btn_2 = g('btn-2'),
193             inp_0 = g('inp-0'),
194             inp_1 = g('inp-1');
195         function checkNum(n, id){
196             if(!/^\d+$/.test(n)){
197                 n = 0;
198             }else if(n < 0){
199                 n = 0;
200             }else if(n > 100){
201                 n = 100;
202             }
203             return id.value = +n;
204         }
205         function transfor100(n){
206             return Math.floor(n * 64 / 100);
207         }
208         function fNtM(n, m){
209             if(hadBegin || (n === m)){
210                 return;
211             }
212             hadBegin = true;
213             n = checkNum(n, inp_0);
214             m = checkNum(m, inp_1);
215             var r, f0 = n < m ? function(n, m){
216                 return n >= m;
217             } : function(n, m){
218                 return n <= m;
219             }, f1 = n < m ? function(n){
220                 return n + 1;
221             } : function(n){
222                 return n - 1;
223             };
224             r = setInterval(function(){
225                 webexBall.preview(transfor100(n));
226                 n = f1(n);
227                 if(f0(n, m)){
228                     clearInterval(r);
229                     hadBegin = false;
230                     return;
231                 }
232             }, 30);
233         }
234         btn_0.onclick = function(){
235             fNtM(0, 100);
236         };
237         btn_1.onclick = function(){
238             fNtM(100, 0);
239         };
240         btn_2.onclick = function(){
241             fNtM(inp_0.value, inp_1.value);
242         }
243     </script>
244     </body>
245 </html>

 

这里他完成了一个不失真的 灌水的球.

当然, 用这个也有些不完整, 毕竟我们还是没处理好高光等等部分 (在模拟)

 

未来如果有时间, 我还是会用直角坐标系再尝试下. 至于渲染, 对于js来说, 估计还是有些麻烦了. 要做的工作也满多.

posted on 2012-08-10 22:23  Hehe123  阅读(755)  评论(0编辑  收藏  举报