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 */
function output(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;
function processMsg(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 = new Int16Array(v.buffer, 8, 1)[0];
angle2 = new Int16Array(v.buffer, 10, 1)[0];
var temp3 = new Int16Array(v.buffer, 12, 1)[0];
document.dispatchEvent(qEvent);
}
else
{
}
}
}
var ws = require("nodejs-websocket");
var clients = new Array();
output("开始建立连接... ");
var count = 0;
var data = new Buffer.alloc(0);
var server = ws.createServer(function(conn){
conn.id = count;
count += 1;
clients["conn"+count] = 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 = new Uint8Array(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 = new Uint8Array(msg_data_buffer);
processMsg(msg);
msg_data_buffer = [];
msg_reading = false;
msg_lenth = 0;
} else if (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()
. It will be called each time an event 'qiu' is dispatched.
document.addEventListener('qiu', function (e) {
if (neck && waist) {
moveOneJoint(neck, angle1, angle2);
}
});
function moveOneJoint(joint, x, y) {
joint.rotation.y = THREE.Math.degToRad(x);
joint.rotation.x = THREE.Math.degToRad(y);
}
Entire code has been pushed to github repo:
3d_character_simulation
Execution
Run the following cmd:
cd Interactive3DCharacter
npm install
npm start
Control singal sending
One can write his own program to send angle via websockt. Angle data (two int16) to be sent should be written into msg_send_posture[8:9] and msg_send_posture[10:11].
Example code:
var wsUrl = "ws://localhost:9999";
websocket = new WebSocket(wsUrl);
var msg_send_posture = new Uint8Array([0xAA, 0x01,0x53, 0x01, 0x04,0x00,0x00,0x00, 0x01,0x00, 0x00,0x00, 0x00,0x00, 0x7F];
websocket.send(msg_send_posture);
Original Project: Interactive 3D Character with Three.js
Demo for the tutorial on how to add an interactive 3D character to a website.
Credits
License
This resource can be used freely if integrated or build upon in personal or commercial projects such as websites, web apps and web templates intended for sale. It is not allowed to take the resource "as-is" and sell it, redistribute, re-publish it, or sell "pluginized" versions of it. Free plugins built using this resource should have a visible…
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 :
Top comments (2)
Wow, this is really cool!
Thanks 😊