AutoCAD 命令统计魔幻球的实现过程--(2)

第一部分中介绍了如何使用ASP.net Web API和Entity Framework实现服务器端程序,这篇博客将讲述如何使用JQuery从服务器获取数据并利用WebGL/Three.Js来实现浏览器端魔幻球的渲染。

本文地址:http://www.cnblogs.com/junqilian/archive/2013/03/14/2958698.html

这部分比较简单,就是一个html页面,为了方便,我就利用服务器端ASP.NET MVC中的view – index.cshtml好了。在这个文件中我要添加一些Javascript代码来以REST的方式从服务器获取数据,然后渲染魔幻球。Web页面中利用JavaScript与服务器进行通信,JQuery是很好的选择,实际上JQuery也已经包含在了ASP.NET MVC里面。对于WebGL的渲染,我选用了一个流行的类库Three.Js。类库和源码都可以从GitHub上下载。 里面包含好多示例,是理解和应用Three.Js很好的学习资料。

 

大家也看到了,这个程序的界面非常简单,就是一个下拉框用来选择用户,还有一个div标签作为魔幻球的渲染容器:

<div id="body">
    <section class="featured">
        <div class="content-wrapper">
            <div>
                <select id="sel_userName" >
                    <option value="All_Users">Select a User Name</option>
                </select>
           
             
</div>
        </div>

       
   
</section>
    <section class="content-wrapper main-content clear-fix">
 
       
     
>
        <div id="Containner" style="height:500px; width:800px;
 
background-color:black;" ></div>
       

    </section>
   
</div
>

 

首先在document Ready的时候从服务器请求可用的用户列表。这里我使用JQuery发送Ajax请求到“api/AcadCommands”来获取所有的用户命令统计数据,然后从中选择用户名。当然这样效率是不高的,更好的应该是在服务器端实现一个action只返回可用的用户列表就行了。如果你感兴趣,可以自己实现这部分做练习。

 

    $(document).ready(function () {
       
// Send an AJAX request
        $.getJSON("api/AcadCommands/"
,
               
function
(data) {
                   
// On success, 'data' contains a list of UserCommandsHits.
                    $.each(data, function
(key, val) {
                        $(
"select"
).append(
                           
'<option value="' + val.UserName + '">'
                                + val.UserName +
                           
'</option>');
                    });
                });
 
        var container = document.getElementById('Containner');
        initThree(container);
animate();


});

 

在document ready的时候还要初始化ThreeJs,这部分一会儿再说。先说说当用户从下列框中选择一个用户后,我们需要获取指定用户的命令统计信息。使用JQuery发送Ajax请求到 api/AcadCommands?username=" + username:

代码如下:

        $("#sel_userName").change(this, function () {

           
var username = $(this).children('option:selected'
).val();
            $(
'#txt_userName'
).text = username;

           
var commandHitDic = new
Array();

            $.getJSON(
"api/AcadCommands?username="
+ username,
                   
function
(data) {
                       
var str = data.UserName + ': $'
+ data.CommandHits;
                        cmdHits = data.CommandHits;
 
                       
//add 3D objects to WebGL scene
                        addObjectsToScene(cmdHits);


                    })
                .fail(
                   
function
(jqXHR, textStatus, err) {
                        alert(err);
                        $(
'#txt_userName').text('Error: '
+ err);
                    });



        });
//$("#sel_userName").change

 

获取到用户命令统计数据后,就可以用ThreeJs来渲染了,我把这部分放在一个单独的javascript文件中。直接上代码:


var mouseX = 0, mouseY = 0,



    SEPARATION = 200,
    AMOUNTX = 10,
    AMOUNTY = 10,



    camera, scene, renderer;

var magicBall = new
THREE.Object3D();
   

var targetRotation = 0;
var
targetRotationOnMouseDown = 0;

var mouseX = 0;
var
mouseXOnMouseDown = 0;

///////////////////////////////
var MIN_TEXT_FONT_SIZE = 10;
var
MAX_TEXT_FONT_SIZE = 80;

var MIN_LINE_LENGTH = 50;
var
MAX_LINE_LENGTH = 450;

var
PARTICLE_BALL_RADIUS = 850;

///////////////////////////////

//initThree();
//animate();


   
var
minHitNumber = 0;
   
var
maxHitNumber = 0;

   
function
computeMinMaxHitNum(cmdHits) {

       
//get min and max hit number
        for (var i in
cmdHits) {
           
var
hitNum = cmdHits[i].HitNumber;
           
var
cmd = cmdHits[i].CommandName;

           
if
(hitNum > maxHitNumber) maxHitNumber = hitNum;
           
if
(hitNum < minHitNumber) minHitNumber = hitNum;
        }

    }

function
getLineLength(hitNumber) {

   
var
ratio = (hitNumber - minHitNumber) / (maxHitNumber - minHitNumber);

   
var
lineLength = MIN_LINE_LENGTH + ratio * (MAX_LINE_LENGTH - MIN_LINE_LENGTH);

   
return
lineLength;

}

function
getTextFontSize(hitNumber) {

   
var
ratio = (hitNumber - minHitNumber) / (maxHitNumber - minHitNumber);

   
var
textSize = MIN_TEXT_FONT_SIZE + ratio * (MAX_TEXT_FONT_SIZE - MIN_TEXT_FONT_SIZE);

   
return
textSize;

}


function
initThree(container) {




   
var
separation = 100, amountX = 50, amountY = 50,
                particles, particle;


    camera =
new
THREE.PerspectiveCamera(75,
            container.clientWidth / container.clientHeight, 1, 10000);
    camera.position.z = 1000;

    scene =
new
THREE.Scene();

    renderer =
new
THREE.CanvasRenderer(); //WebGLRenderer()
    renderer.setSize(container.clientWidth, container.clientHeight);
    container.appendChild(renderer.domElement);

   
//add particles just for visual effect

   
var
PI2 = Math.PI * 2;
   
var material = new
THREE.ParticleCanvasMaterial({

        color: 0xffffff,
        program:
function
(context) {

            context.beginPath();
            context.arc(0, 0, 1, 0, PI2,
true
);
            context.closePath();
            context.fill();

        }

    });

   
for (var
i = 0; i < 1000; i++) {

        particle =
new
THREE.Particle(material);
        particle.position.x = Math.random() * 2 - 1;
        particle.position.y = Math.random() * 2 - 1;
        particle.position.z = Math.random() * 2 - 1;
        particle.position.normalize();
        particle.position.multiplyScalar(Math.random() * 10 + PARTICLE_BALL_RADIUS);
        scene.add(particle);

    }






    container.addEventListener(
'mousedown', onContainerMouseDown, false
);
    container.addEventListener(
'touchstart', onContainerTouchStart, false
);
    container.addEventListener(
'touchmove', onContainerTouchMove, false
);

  


}

function
addObjectsToScene(cmdHits){

   
//prepartion
    computeMinMaxHitNum(cmdHits);
   
   
if
(magicBall.children.length > 0){
       
       
var
obj, i;
       
for
( i = magicBall.children.length - 1; i >= 0 ; i -- ) {
            obj = magicBall.children[ i ];
           
            magicBall.remove(obj);
           
        }

        scene.remove(magicBall);
    }
   
// add line and text

   
for (var i in
cmdHits) {

       
var
hitNum = cmdHits[i].HitNumber;
       
var
cmdName = cmdHits[i].CommandName;

       
//draw line and text 3d object
        var
lineLength = getLineLength(hitNum);
       
var
textSize = getTextFontSize(hitNum);

       
var
lineAndText = creatLineAndText(lineLength, cmdName, textSize);
        magicBall.add(lineAndText);
    }



    magicBall.rotation.x = 0;
    magicBall.rotation.y = Math.PI * 2;
                           
    scene.add(magicBall);
   
}

function
creatLineAndText(lineLength, textString, textSize) {

   
var lineAndText = new
THREE.Object3D();
   
   
//draw line

   
var startVector = new THREE.Vector3(0, 0, 0);// always start from center of ball.

   
var
endX = Math.random() * 2 - 1;
   
var
endY = Math.random() * 2 - 1;
   
var
endZ = Math.random() * 2 - 1;
   
var endVector = new
THREE.Vector3(endX, endY, endZ);
    endVector = endVector.normalize();
    endVector.multiplyScalar(lineLength);

   
var geomLine = new
THREE.Geometry();
    geomLine.vertices.push(startVector);
    geomLine.vertices.push(endVector);

   
var line = new THREE.Line(geomLine, new
THREE.LineBasicMaterial(
            { color: Math.random() * 0xffffff, opacity: 0.5 }));

    lineAndText.add(line);

   
//draw text
   
       
//inline function
        function
creatTextAt(textPosition, textString, textSize) {

           
var text3d = new
THREE.TextGeometry(textString, {

                size: textSize,
                height: 5,
//thickness of the text
                curveSegments: 2,
                font:
"helvetiker"

            });

            text3d.computeBoundingBox();
           
var
centerOffset = -0.5 * (text3d.boundingBox.max.x - text3d.boundingBox.min.x);

           
var textMaterial = new
THREE.MeshBasicMaterial(
                { color: Math.random() * 0xffffff,
                    overdraw:
true
});
            text =
new
THREE.Mesh(text3d, textMaterial);

            text.position.x = textPosition.x + centerOffset;
            text.position.y = textPosition.y;
            text.position.z = textPosition.z;
                           

                   

           
return
text;
        };

   
var
text3D = creatTextAt(endVector, textString, textSize);
    lineAndText.add(text3D);

   

   
return
lineAndText;
}





function
onContainerMouseDown(event) {
   
    event.preventDefault();

   
var
container = event.srcElement;
   
    container.addEventListener(
'mousemove', onContainerMouseMove, false
);
    container.addEventListener(
'mouseup', onContainerMouseUp, false
);
    container.addEventListener(
'mouseout', onContainerMouseOut, false
);

   
var
windowHalfX = container.clientWidth / 2;
   
    mouseXOnMouseDown = event.clientX - windowHalfX;
    targetRotationOnMouseDown = targetRotation;

}

function
onContainerMouseMove(event) {

   
var
container = event.srcElement;
   
var
windowHalfX = container.clientWidth / 2;

    mouseX = event.clientX - windowHalfX;

    targetRotation = targetRotationOnMouseDown + (mouseX - mouseXOnMouseDown) * 0.02;

}

function
onContainerMouseUp(event) {

   
var
container = event.srcElement;

    container.removeEventListener(
'mousemove', onContainerMouseMove, false
);
    container.removeEventListener(
'mouseup', onContainerMouseUp, false
);
    container.removeEventListener(
'mouseout', onContainerMouseOut, false
);

}

function
onContainerMouseOut(event) {

   
var
container = event.srcElement;

    container.removeEventListener(
'mousemove', onContainerMouseMove, false
);
    container.removeEventListener(
'mouseup', onContainerMouseUp, false
);
    container.removeEventListener(
'mouseout', onContainerMouseOut, false
);

}

function
onContainerTouchStart(event) {

   
if
(event.touches.length == 1) {

        event.preventDefault();

       
var
container = event.srcElement;
       
var
windowHalfX = container.clientWidth / 2;
   
        mouseXOnMouseDown = event.touches[0].pageX - windowHalfX;
        targetRotationOnMouseDown = targetRotation;

    }

}

function
onContainerTouchMove(event) {

   
if
(event.touches.length == 1) {

        event.preventDefault();

       
var
container = event.srcElement;
       
var
windowHalfX = container.clientWidth / 2;
       
        mouseX = event.touches[0].pageX - windowHalfX;
        targetRotation = targetRotationOnMouseDown + (mouseX - mouseXOnMouseDown) * 0.05;

    }

}

//

function
animate() {

    requestAnimationFrame(animate);

    render();

}

function
render() {

    camera.position.x += (mouseX - camera.position.x) * .05;
    camera.position.y += (-mouseY + 200 - camera.position.y) * .05;
    camera.lookAt(scene.position);


   
//self rotate
    magicBall.rotation.y += ( targetRotation - magicBall.rotation.y ) * 0.05;
   
//magicBall.rotation.y += 0.05;
    renderer.render(scene, camera);

}

 

好了,到目前为止已经完成了,最后给大家提一下用到的JavaScript文件,注意这个 helvetiker_regular.typeface.js 因为我要把命令名字渲染成文本,使用了 helvetiker_regular字体,所以需要这个文件。这个文件可以在ThreeJS的下载包中找到。

<script src="../../Scripts/jquery-1.7.1.min.js" type="text/javascript"></script>
<script type="text/javascript" src="../../Scripts/Three/three.js"></script>
<
script type="text/javascript" src="../../Scripts/Three/Detector.js"></script
>
<
script type="text/javascript" src="../../Scripts/Three/fonts/helvetiker_regular.typeface.js"></script
>
<
script type="text/javascript" src="../../Scripts/ACV_MagicBall.js"></script
>

 

好了,打完收工。不过到目前为止,这个程序还是运行在本机的一个aspnet站点,下一步就是把他搬到windows Azure云端去了。下回再说。


Related Posts Plugin for WordPress, Blogger...