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云端去了。下回再说。