By Daniel Du
In part 1 I introduced how to create an ASP.NET MVC application with ASP.NET Web API and Entity Framework to provide RESTful service. In this post, I will implement the web viewer with WebGL.
Actually it is just a common web page, to make it simple, I will use the one in MVC - index.cshtml. Switch to index.cshtml file in View –> Home folder, I will add some JavaScript here to retrieve data from server side and render it as my “magic ball”. JQuery is good option to communicate with server side, getting user’s command usage statistics, and JQuery is part of ASP.NET MVC application by default. For WebGL, I will use a popular library- Three.Js, which can be downloaded from GitHub. There are many examples about Three.Js, I found that they are very helpful to learn how to use Three.Js.
The UI is pretty simple, just a combobox to ask user to select a user, and container DIV tag.
<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>
Firstly I will ask the server for the user name list and populate the select tag when document is ready. Use JQuery to send Ajax request to “api/AcadCommands” to get all users commands statistics and get the user name. Actually it would be better to create an action in server side, just returning a list of available user name, I will leave it to you to complete this if you are interested.
$(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();
});
Another stuff need to done in document.ready is initialize ThreeJS, I will cover this part latter. Next step is to get specified user’s command usage statistic by user name when select a user from dropdown list. I use another action:
api/AcadCommands?username=" + username
The code snippet as below:
$("#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
After getting the data, I will generate the magic ball with Three.Js. To make my code more organized, I put the three.Js related code into another ACV_MagicBall.js.:
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);
}
Finally let’s list the script files I am referring to in index.cshtml, please note the helvetiker_regular.typeface.js, as it is necessary to render the command name as text in helvetiker_regular font:
<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>
Okay, I am done with the rendering magic ball with Three.Js. But it still a simple ASP.NET web application running on my laptop, in next post, I will move it up to Windows Azure cloud. Stay tuned.
Comments