Several days ago, a friend of mine contacted me and asked me the feasibility of a technical solution for a 3d human character simulation in HTML5 environment. He sent me this article which presents how to create an interactive 3d character with Three.js**. He is expecting to control the character’s real-time motion (whole-body motion) via a hardware such like a joystick.
It’s a very interesting work and it seems quite easy. Thus I have done a little dev work trying to make it work.
In file Index.html, I have defined a websocket server and an according message parser. This file then is wrapped in Electron window so it runs as a desktop software. Core part of this Index.html is the websocket communication part as below:
<script type="text/javascript" > var angle1 = 0.0; var angle2 = 0.0 const qEvent = new Event('qiu'); /* for debug */ functionoutput(s) { var out = document.getElementById("debug-area"); out.innerText += s; } output("Start running")
var msg_ready = false; var msg_reading = false; // True: package head 0xAA is received, but 0x7f has not yet been received var msg_data_buffer = []; var msg_lenth = 0;
functionprocessMsg(v) { if (v[0] == 170) // detect the beginning byte of a message: 0xAA { // data are sent in little endian, // v.buffer is a byte-array and Int16Array(v.buffer, 8, 1) means that it parses from the 8th byte on to get ONE Int16 number if ( (v[1] == 0x01) && (v[2] == 0x53) ) // 01 52 { angle1 = newInt16Array(v.buffer, 8, 1)[0]; angle2 = newInt16Array(v.buffer, 10, 1)[0]; var temp3 = newInt16Array(v.buffer, 12, 1)[0]; document.dispatchEvent(qEvent);
} else { } } }
var ws = require("nodejs-websocket"); var clients = newArray(); output("开始建立连接... "); var count = 0; var data = new Buffer.alloc(0); var server = ws.createServer(function(conn){
conn.on("text", function (str) { output("Received " + str + "! " ) var typeId = str.charAt(0); conn.sendText('Success!'); }) conn.on("close", function (code, reason) { output("Connection closed!") //clients.delete(conn); }); conn.on("binary", function (inStream) { inStream.on("readable", function () { var newData = inStream.read(); if (newData) data = Buffer.concat([data, newData], data.length + newData.length); }); inStream.on("end", function () {
if(data){ var t = '', v = newUint8Array(data);
for (var i = 0; i < v.length; i++) { // packet head 0xAA reached, now start reading the data flow if ((!msg_reading ) &&(v[i] == 0xaa)){ msg_reading = true; } if(msg_reading){ if (msg_data_buffer.length == 8) { msg_lenth = msg_data_buffer[5]*16 + msg_data_buffer[4]; // parsing the data length (bytes size) } // received the end of packet, and the length is correct if ((v[i] == 127 ) && (msg_data_buffer.length == (msg_lenth + 10))) // 10 extra bytes contained in this package for : length, scope, checksum, msg-id { var msg = newUint8Array(msg_data_buffer); processMsg(msg); msg_data_buffer = []; msg_reading = false; msg_lenth = 0; } elseif (msg_data_buffer.length == (msg_lenth + 10)) { msg_data_buffer = []; msg_reading = false; msg_lenth = 0; output("Message length error!"); } else{ msg_data_buffer.push(v[i]); } } } }else{ }; data = new Buffer.alloc(0); conn.sendText('Binary Received!'); }); }); conn.on("message", function (code, reason) { output("message! " ) }); conn.on("error", function (code, reason) { output("Error occurs!") }); }).listen(9999) output("Server is ready! "); </script>
In existing file script.js, I have defined function moveOneJoint(). For the moment for test only, this function change only the orientation of the head (namely neck joint). It will be called each time an event ‘qiu’ is dispatched.
1 2 3 4 5 6 7 8 9 10 11 12 13 14
document.addEventListener('qiu', function (e) { if (neck && waist) { moveOneJoint(neck, angle1, angle2); } });
I do not have a joystick so I simulate it with several range sliders in another web app (developed using MUI framework with HBuilder). By sliding the sliders, we can send the angle data via websocket to above-mentioned 3d character simulator. Data massage to be sent should be a dataarray like: [0xAA, 0x01,0x53, 0x01, 0x04,0x00,0x00,0x00, 0xMM,0xNN, 0xSS,0xTT, 0xYY,0xZZ, 0x7F] where 0xMM,0xNN and 0xSS,0xTT are angle values in Int16 and 0xYY,0xZZ can be any bytes (designed to be checksum, but I am not checking it in my code).
Below is a demo I’ve recorded. I am controlling the motion of the simulated 3d character’s head using sliders:
In another trial, I run my device simulator app on Android platform and run Electron in full screen. Check out the demo :