Skip to content
Snippets Groups Projects
Commit 842c0d0e authored by Fabian Aichele's avatar Fabian Aichele
Browse files

[NRRPLT-2545] Merge JavaScript changes for client-side animated models into main gzweb repository.

Added JS file with AnimatedModel class prototype.

Change-Id: I389248d98fd78a10ecf768d0b21757fba64c842e
parent 998d10df
No related branches found
No related tags found
No related merge requests found
var GZ3D = GZ3D || {
REVISION : '1'
};
GZ3D.AnimatedModel = function(scene) {
this.scene = scene;
this.loader = null;
this.modelUri_animated = '';
this.animatedModelFound = false;
this.hideLinkVisuals = false;
this.modelName = '';
this.visualsToHide = [];
this.serverSideModel = null;
};
// Helper function to find Visuals that need to be hidden when loading a client-side-only animated model
GZ3D.AnimatedModel.prototype.isVisualHidden = function(visual) {
if (visual.geometry && visual.geometry.mesh) {
for (var k = 0; k < this.visualsToHide.length; k++) {
if (this.visualsToHide[k] === visual.geometry.mesh.filename) {
return true;
}
}
}
return false;
};
// Function to check if a client-side-only file for an animated model is available
GZ3D.AnimatedModel.prototype.animatedModelAvailable = function(model) {
var uriPath = GZ3D.assetsPath;
for (var j = 0; j < model.link.length; ++j) {
var link = model.link[j];
for (var k = 0; k < link.visual.length; k++) {
var geom = link.visual[k].geometry;
if (geom && geom.mesh) {
var meshUri = geom.mesh.filename;
// This saves all visuals of a model here because access to the loaded model after the loading
// process finished is more difficult to implement. The optimal solution would be to actually
// do this only after an animated model was found.
this.visualsToHide.push(geom.mesh.filename);
var uriType = meshUri.substring(0, meshUri.indexOf('://'));
if (uriType === 'file' || uriType === 'model') {
var modelUri = meshUri.substring(meshUri.indexOf('://') + 3);
var modelName = modelUri.substring(0, modelUri.indexOf('/'));
var modelUriCheck = uriPath + '/' + modelName + '/meshes/' + modelName + '_animated.dae';
var checkModel = new XMLHttpRequest();
checkModel.open('HEAD', modelUriCheck, false);
try {
checkModel.send();
} catch (err) {
console.log(modelUriCheck + ': no animated version');
}
if (checkModel.status !== 404) {
this.modelUri_animated = modelUriCheck;
this.animatedModelFound = true;
this.hideLinkVisuals = true;
this.modelName = model.name;
this.serverSideModel = model;
break;
}
}
}
}
}
return this.animatedModelFound;
};
GZ3D.AnimatedModel.prototype.loadAnimatedModel = function(modelName) {
this.loader = new THREE.ColladaLoader();
// Helper function to enable 'skinning' property so three.js treats meshes as deformable
var enableSkinning = function (skinnedMesh) {
var materials = skinnedMesh.material.materials;
if (materials !== null && materials !== undefined) {
for (var i = 0, length = materials.length; i < length; i++) {
var mat = materials[i];
mat.skinning = true;
}
}
if (skinnedMesh.material !== undefined && skinnedMesh.material !== null) {
skinnedMesh.material.skinning = true;
}
};
// Load animated model with separate COLLADA loader instance
var that = this;
this.loader.load(this.modelUri_animated, function (collada) {
var modelParent = new THREE.Object3D();
modelParent.name = modelName + '_animated';
var linkParent = new THREE.Object3D();
// Set gray, phong-shaded material for loaded model
// TODO: Texturing/GLSL shading; find out what is available in the COLLADA loader implementation
collada.scene.traverse (function (child) {
if (child instanceof THREE.Mesh) {
var transparentMaterial = new THREE.MeshPhongMaterial({color: 0x707070});
transparentMaterial.wireframe = false;
child.material = transparentMaterial;
}
});
// Enable skinning for all child meshes
collada.scene.traverse(function (child) {
if (child instanceof THREE.SkinnedMesh) {
enableSkinning(child);
}
});
linkParent.add(collada.scene);
// Hide model coordinate frames for the time being; remove as soon as position offset and rotation axis issues are fixed
/*var collada_scene_axes = new THREE.AxisHelper(2);
linkParent.add(collada_scene_axes);*/
modelParent.add(linkParent);
// Hide model coordinate frames for the time being; remove as soon as position offset and rotation axis issues are fixed
/*var model_parent_axes = new THREE.AxisHelper(4);
modelParent.add(model_parent_axes);*/
// Temporary fix for offset between mouse model defined in SDF and client-side COLLADA model; cause remains to be investigated
modelParent.position.y = modelParent.position.y + 5;
modelParent.position.z = modelParent.position.z + 0.62;
// Build list of bones in rig, and attach it to scene node (userData) for later retrieval in animation handling
var getBoneList = function (object) {
var boneList = [];
if (object instanceof THREE.Bone) {
boneList.push(object);
}
for (var i = 0; i < object.children.length; i++) {
boneList.push.apply(boneList, getBoneList(object.children[i]));
}
return boneList;
};
var boneList = getBoneList(collada.scene);
var boneHash = {};
for (var k = 0; k < boneList.length; k++) {
boneHash[boneList[k].name] = boneList[k];
}
// Skeleton visualization helper class
var helper = new THREE.SkeletonHelper(collada.scene);
boneHash['Skeleton_Visual_Helper'] = helper;
modelParent.userData = boneHash;
helper.material.linewidth = 3;
// Hide skeleton helper for the time being
// TODO: Make this configurable for visualizing the underlying skeleton
helper.visible = false;
that.scene.add(helper);
that.scene.add(modelParent);
});
};
GZ3D.AnimatedModel.prototype.updateJoint = function(robotName, jointName, jointValue, jointAxis) {
// Check if there is an animated model for it on the client side
var entity = this.scene.getByName(robotName + '_animated');
if (entity) {
if (entity.userData !== undefined && entity.userData !== null) {
var boneName = jointName;
// Retrieve bone instance from userData map of bone instances in animated model
if (entity.userData[boneName] !== undefined && entity.userData[boneName] !== null) {
var targetBone = entity.userData[boneName];
var rotationAxis = jointAxis;
var rotationAngle = jointValue;
var rotationMatrix = new THREE.Matrix4();
rotationMatrix.identity();
rotationMatrix.makeRotationAxis(rotationAxis, rotationAngle);
targetBone.setRotationFromMatrix(rotationMatrix);
// Update animation handler and skeleton helper
// TODO: Move this to the animation loop to synchronize animations with the actual frame rate.
// Alternative: Use the clock helper class from three.js for retrieving the actual frame rate delta.
entity.userData['Skeleton_Visual_Helper'].update(0.016);
THREE.AnimationHandler.update(0.016);
}
}
}
};
......@@ -14,6 +14,9 @@ GZ3D.GZIface = function(scene, gui)
this.init();
this.visualsToAdd = [];
// Stores AnimatedModel instances
this.animatedModels = {};
this.numConnectionTrials = 0;
this.maxConnectionTrials = 30; // try to connect 30 times
this.timeToSleepBtwTrials = 1000; // wait 1 second between connection trials
......@@ -253,6 +256,27 @@ GZ3D.GZIface.prototype.onConnected = function()
};
poseTopic.subscribe(poseUpdate.bind(this));
// ROS topic subscription for joint_state messages
// Requires gzserver version with support for joint state messages
var jointTopicSubscriber = new ROSLIB.Topic({ros: this.webSocket,
name: '~/joint_states',
message_type: 'jointstates',
throttle_rate: 1.0 / 25.0 * 1000.0,
});
// function for updating transformations of bones in a client-side-only animated model
var updateJoint = function(message)
{
if (message.robot_name in this.animatedModels)
{
var animatedModel = this.animatedModels[message.robot_name];
animatedModel.updateJoint(message.robot_name, message.name, message.position, message.axes);
}
};
// Subscription to joint update topic
jointTopicSubscriber.subscribe(updateJoint.bind(this));
// Requests - for deleting models
var requestTopic = new ROSLIB.Topic({
......@@ -782,6 +806,21 @@ GZ3D.GZIface.prototype.createModelFromMsg = function(model)
{
this.scene.setPose(modelObj, model.pose.position, model.pose.orientation);
}
// Check for client-side-only animated model
var animatedModel = new GZ3D.AnimatedModel(this.scene);
var animatedModelExists = animatedModel.animatedModelAvailable(model);
if (animatedModelExists)
{
animatedModel.loadAnimatedModel(model.name);
this.animatedModels[model.name] = animatedModel;
}
else
{
animatedModel = null;
}
for (var j = 0; j < model.link.length; ++j)
{
var link = model.link[j];
......@@ -804,7 +843,19 @@ GZ3D.GZIface.prototype.createModelFromMsg = function(model)
for (var k = 0; k < link.visual.length; ++k)
{
var visual = link.visual[k];
var visualObj = this.createVisualFromMsg(visual);
// Check if the current visual needs to be hidden, i. e. is replaced by a client-side-only AnimatedModel
var isVisual_Hidden = false;
if (animatedModel !== null)
{
isVisual_Hidden = animatedModel.isVisualHidden(visual);
}
var visualObj = null;
if (!isVisual_Hidden) {
visualObj = this.createVisualFromMsg(visual);
}
// Hide object if it needs to be hidden: Don't add it to the scene in that case.
if (visualObj && !visualObj.parent)
{
linkObj.add(visualObj);
......@@ -816,11 +867,13 @@ GZ3D.GZIface.prototype.createModelFromMsg = function(model)
var collision = link.collision[l];
for (var m = 0; m < link.collision[l].visual.length; ++m)
{
var collisionVisual = link.collision[l].visual[m];
var collisionVisualObj = this.createVisualFromMsg(collisionVisual);
if (collisionVisualObj && !collisionVisualObj.parent)
{
linkObj.add(collisionVisualObj);
// Only create collision visuals if this is not an animated model
if (!animatedModelExists) {
var collisionVisual = link.collision[l].visual[m];
var collisionVisualObj = this.createVisualFromMsg(collisionVisual);
if (collisionVisualObj && !collisionVisualObj.parent) {
linkObj.add(collisionVisualObj);
}
}
}
}
......
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment