diff --git a/gz3d/build/gz3d.js b/gz3d/build/gz3d.js index 09b199b8296af9149d3443a8f89aa16395a6163d..38c6e53e7d24c2160dc5fc55983338ffa4bf07eb 100644 --- a/gz3d/build/gz3d.js +++ b/gz3d/build/gz3d.js @@ -1,5 +1,7 @@ var GZ3D = GZ3D || { - REVISION : '1' + REVISION : '1', + assetsPath: 'http://localhost:8080/assets', + webSocketUrl: 'ws://localhost:7681' }; /*global $:false */ @@ -9,7 +11,7 @@ var guiEvents = new EventEmitter2({ verbose: true }); var emUnits = function(value) { - return value*parseFloat($('body').css('font-size')); + return value*parseFloat($('#gz3d-body').css('font-size')); }; var isTouchDevice = 'ontouchstart' in window || 'onmsgesturechange' in window; @@ -29,160 +31,15 @@ var tabColors = {selected: 'rgb(34, 170, 221)', unselected: 'rgb(42, 42, 42)'}; var modelList = [ - {path:'buildings', title:'Buildings', - examplePath1:'fast_food', examplePath2:'kitchen_dining', examplePath3:'house_1', models: + {path:'virtual_room', title:'Virtual Room Objects', + examplePath1:'library_model', examplePath2:'hosta_potted_plant', examplePath3:'vr_lamp', models: [ - {modelPath:'fast_food', modelTitle:'Fast Food'}, - {modelPath:'gas_station', modelTitle:'Gas Station'}, - {modelPath:'house_1', modelTitle:'House 1'}, - {modelPath:'house_2', modelTitle:'House 2'}, - {modelPath:'house_3', modelTitle:'House 3'}, - {modelPath:'iss', modelTitle:'International Space Station'}, - {modelPath:'iss_half', modelTitle:'ISS half'}, - {modelPath:'kitchen_dining', modelTitle:'Kitchen and Dining'}, - {modelPath:'office_building', modelTitle:'Office Building'}, - {modelPath:'powerplant', modelTitle:'Power Plant'}, - {modelPath:'starting_pen', modelTitle:'Starting Pen'}, - {modelPath:'willowgarage', modelTitle:'Willow Garage'} - ]}, - - {path:'furniture', title:'Furniture', - examplePath1:'hinged_door', examplePath2:'bookshelf', examplePath3:'table', models: - [ - {modelPath:'bookshelf', modelTitle:'Book Shelf'}, - {modelPath:'cabinet', modelTitle:'Cabinet'}, - {modelPath:'drc_practice_door_4x8', modelTitle:'4x8 Doorway'}, - {modelPath:'drc_practice_ladder', modelTitle:'Ladder'}, - {modelPath:'hinged_door', modelTitle:'Hinged Door'}, - {modelPath:'table', modelTitle:'Table'}, - {modelPath:'table_marble', modelTitle:'Table Marble'}, - - {modelPath:'drc_practice_ball_valve', modelTitle:'Ball Valve'}, - {modelPath:'drc_practice_handle_wheel_valve', modelTitle:'Handle Wheel Valve'}, - {modelPath:'drc_practice_hand_wheel_valve', modelTitle:'Hand Wheel Valve'}, - {modelPath:'drc_practice_wheel_valve', modelTitle:'Wheel Valve'}, - {modelPath:'drc_practice_wheel_valve_large', modelTitle:'Wheel Valve Large'}, - {modelPath:'door_handle', modelTitle:'Door Handle'}, - - {modelPath:'drc_practice_ball_valve_wall', modelTitle:'Wall (Ball Valve)'}, - {modelPath:'drc_practice_handle_wheel_valve_wall', modelTitle:'Wall (Handle Wheel Valve)'}, - {modelPath:'drc_practice_hand_wheel_valve_wall', modelTitle:'Wall (Hand Wheel Valve)'}, - {modelPath:'drc_practice_valve_wall', modelTitle:'Wall (Valve)'}, - {modelPath:'drc_practice_wheel_valve_wall', modelTitle:'Wall (Wheel Valve)'}, - {modelPath:'drc_practice_wheel_valve_large_wall', modelTitle:'Wall (Wheel Valve Large)'}, - {modelPath:'grey_wall', modelTitle:'Grey Wall'}, - {modelPath:'asphalt_plane', modelTitle:'Asphalt Plane'}, - {modelPath:'drc_practice_base_4x8', modelTitle:'Debris base'}, - {modelPath:'ground_plane', modelTitle:'Ground Plane'}, - {modelPath:'nist_maze_wall_120', modelTitle:'120 Maze Wall'}, - {modelPath:'nist_maze_wall_240', modelTitle:'240 Maze Wall'}, - {modelPath:'nist_maze_wall_triple_holes_120', modelTitle:'120 Maze Wall Triple Holes'}, - {modelPath:'nist_simple_ramp_120', modelTitle:'Simple Ramp'}, - {modelPath:'nist_stairs_120', modelTitle:'Stairs'} - ]}, - - {path:'kitchen', title:'Kitchen', - examplePath1:'saucepan', examplePath2:'beer', examplePath3:'bowl', models: - [ - {modelPath:'beer', modelTitle:'Beer'}, - {modelPath:'bowl', modelTitle:'Bowl'}, - {modelPath:'coke_can', modelTitle:'Coke Can'}, - {modelPath:'saucepan', modelTitle:'Saucepan'} - ]}, - - {path:'robocup', title:'Robocup', examplePath1:'robocup_3Dsim_ball', - examplePath2:'robocup14_spl_goal', examplePath3:'robocup09_spl_field', models: - [ - {modelPath:'robocup09_spl_field', modelTitle:'2009 SPL Field'}, - {modelPath:'robocup14_spl_field', modelTitle:'2014 SPL Field'}, - {modelPath:'robocup_3Dsim_field', modelTitle:'3D Sim. Field'}, - {modelPath:'robocup14_spl_goal', modelTitle:'SPL Goal'}, - {modelPath:'robocup_3Dsim_goal', modelTitle:'3D Sim. Goal'}, - {modelPath:'robocup_spl_ball', modelTitle:'SPL Ball'}, - {modelPath:'robocup_3Dsim_ball', modelTitle:'3D Sim. Ball'} - ]}, - - {path:'robots', title:'Robots', - examplePath1:'pioneer3at', examplePath2:'turtlebot', examplePath3:'pr2', models: - [ - {modelPath:'create', modelTitle:'Create'}, - {modelPath:'husky', modelTitle:'Husky'}, - {modelPath:'irobot_hand', modelTitle:'iRobot Hand'}, - {modelPath:'pioneer2dx', modelTitle:'Pioneer 2DX'}, - {modelPath:'pioneer3at', modelTitle:'Pioneer 3AT'}, - {modelPath:'pr2', modelTitle:'PR2'}, - {modelPath:'robonaut', modelTitle:'Robonaut'}, - {modelPath:'simple_arm', modelTitle:'Simple Arm'}, - {modelPath:'simple_arm_gripper', modelTitle:'Simple Arm and Gripper'}, - {modelPath:'simple_gripper', modelTitle:'Simple Gripper'}, - {modelPath:'turtlebot', modelTitle:'TurtleBot'}, - {modelPath:'youbot', modelTitle:'YouBot'} - ]}, - - {path:'sensors', title:'Sensors', - examplePath1:'camera', examplePath2:'hokuyo', examplePath3:'kinect', models: - [ - {modelPath:'camera', modelTitle:'Camera'}, - {modelPath:'stereo_camera', modelTitle:'Stereo Camera'}, - {modelPath:'hokuyo', modelTitle:'Hokuyo'}, - {modelPath:'kinect', modelTitle:'Kinect'} - ]}, - - {path:'street', title:'Street', examplePath1:'dumpster', - examplePath2:'drc_practice_angled_barrier_45', examplePath3:'fire_hydrant', models: - [ - {modelPath:'cinder_block', modelTitle:'Cinder Block'}, - {modelPath:'cinder_block_2', modelTitle:'Cinder Block 2'}, - {modelPath:'cinder_block_wide', modelTitle:'Cinder Block Wide'}, - {modelPath:'construction_barrel', modelTitle:'Construction Barrel'}, - {modelPath:'construction_cone', modelTitle:'Construction Cone'}, - {modelPath:'drc_practice_angled_barrier_45', modelTitle:'Angled Barrier 45'}, - {modelPath:'drc_practice_angled_barrier_135', modelTitle:'Angled Barrier 135'}, - {modelPath:'drc_practice_block_wall', modelTitle:'Block Wall'}, - {modelPath:'drc_practice_orange_jersey_barrier', modelTitle:'Jersey Barrier (Orange)'}, - {modelPath:'drc_practice_white_jersey_barrier', modelTitle:'Jersey Barrier (White)'}, - {modelPath:'drc_practice_truss', modelTitle:'Truss'}, - {modelPath:'drc_practice_yellow_parking_block', modelTitle:'Parking Block'}, - {modelPath:'dumpster', modelTitle:'Dumpster'}, - {modelPath:'fire_hydrant', modelTitle:'Fire Hydrant'}, - {modelPath:'jersey_barrier', modelTitle:'Jersey Barrier'}, - {modelPath:'lamp_post', modelTitle:'Lamp Post'}, - {modelPath:'mailbox', modelTitle:'Mailbox'}, - {modelPath:'mud_box', modelTitle:'Mud Box'}, - {modelPath:'nist_fiducial_barrel', modelTitle:'Fiducial Barrel'}, - {modelPath:'speed_limit_sign', modelTitle:'Speed Limit Sign'}, - {modelPath:'stop_sign', modelTitle:'Stop Sign'} - - ]}, - - {path:'tools', title:'Tools', examplePath1:'hammer', - examplePath2:'polaris_ranger_ev', examplePath3:'cordless_drill', models: - [ - {modelPath:'cordless_drill', modelTitle:'Cordless Drill'}, - {modelPath:'fire_hose_long', modelTitle:'Fire Hose'}, - {modelPath:'fire_hose_long_curled', modelTitle:'Fire Hose Long Curled'}, - {modelPath:'hammer', modelTitle:'Hammer'}, - {modelPath:'monkey_wrench', modelTitle:'Monkey Wrench'}, - {modelPath:'polaris_ranger_ev', modelTitle:'Polaris Ranger EV'}, - {modelPath:'polaris_ranger_xp900', modelTitle:'Polaris Ranger XP900'}, - {modelPath:'polaris_ranger_xp900_no_roll_cage', modelTitle:'Polaris Ranger without roll cage'}, - {modelPath:'utility_cart', modelTitle:'Utility Cart'} - ]}, - - {path:'misc', title:'Misc.', examplePath1:'brick_box_3x1x3', - examplePath2:'drc_practice_4x4x20', examplePath3:'double_pendulum_with_base', models: - [ - {modelPath:'double_pendulum_with_base', modelTitle:'Double Pendulum With Base'}, - {modelPath:'breakable_test', modelTitle:'Breakable_test'}, - {modelPath:'brick_box_3x1x3', modelTitle:'Brick Box 3x1x3'}, - {modelPath:'cube_20k', modelTitle:'Cube 20k'}, - {modelPath:'drc_practice_2x4', modelTitle:'2x4 Lumber'}, - {modelPath:'drc_practice_2x6', modelTitle:'2x6 Lumber'}, - {modelPath:'drc_practice_4x4x20', modelTitle:'4x4x20 Lumber'}, - {modelPath:'drc_practice_4x4x40', modelTitle:'4x4x40 Lumber'}, - {modelPath:'drc_practice_blue_cylinder', modelTitle:'Blue Cylinder'}, - {modelPath:'drc_practice_wood_slats', modelTitle:'Wood Slats'}, - {modelPath:'nist_elevated_floor_120', modelTitle:'Elevated Floor 120'} + {modelPath:'library_model', modelTitle:'Library'}, + {modelPath:'hosta_potted_plant', modelTitle:'Hosta Plant'}, + {modelPath:'vr_lamp', modelTitle:'Stand Lamp'}, + {modelPath:'vr_screen', modelTitle:'Virtual Screen'}, + {modelPath:'viz_poster', modelTitle:'Poster 1'}, + {modelPath:'viz_poster_2', modelTitle:'Poster 2'} ]} ]; @@ -191,7 +48,7 @@ $(function() //Initialize if ('ontouchstart' in window || 'onmsgesturechange' in window) { - $('body').addClass('isTouchDevice'); + $('#gz3d-body').addClass('isTouchDevice'); } // Toggle items @@ -491,6 +348,14 @@ $(function() guiEvents.emit('show_orbit_indicator'); guiEvents.emit('closeTabs', false); }); + $('#view-shadows').click(function() + { + guiEvents.emit('show_shadows', 'toggle'); + }); + $('#view-camera-sensors').click(function() + { + guiEvents.emit('show_camera_sensors', 'toggle'); + }); $( '#snap-to-grid' ).click(function() { guiEvents.emit('snap_to_grid'); guiEvents.emit('closeTabs', false); @@ -505,13 +370,12 @@ $(function() }); // Disable Esc key to close panel - $('body').on('keyup', function(event) - { - if (event.which === 27) - { - return false; - } - }); + $('#gz3d-body').on('keyup', function(event) { + if (event.which === 27) + { + return false; + } + }); // Object menu $( '#view-transparent' ).click(function() { @@ -839,6 +703,73 @@ GZ3D.Gui.prototype.init = function() } ); + guiEvents.on('show_shadows', function(option) + { + if (option === 'show') + { + //that.emitter.emit('setShadows', true); + that.scene.setShadowMaps(true); + } + else if (option === 'hide') + { + //that.emitter.emit('setShadows', false); + that.scene.setShadowMaps(false); + } + else if (option === 'toggle') + { + var shadowsEnabled = that.scene.renderer.shadowMapEnabled; + //that.emitter.emit('setShadows', !shadowsEnabled); + that.scene.setShadowMaps(!shadowsEnabled); + } + + if(!that.scene.renderer.shadowMapEnabled) + { + $('#view-shadows').buttonMarkup({icon: 'false'}); + guiEvents.emit('notification_popup','Disabling shadows'); + } + else + { + $('#view-shadows').buttonMarkup({icon: 'check'}); + guiEvents.emit('notification_popup','Enabling shadows'); + } + } + ); + + guiEvents.on('show_camera_sensors', function(option) + { + var camerasShown = false; + if (option === 'show') + { + that.scene.viewManager.showCameras(true); + } + else if (option === 'hide') + { + that.scene.viewManager.showCameras(false); + } + else if (option === 'toggle') + { + if (that.scene.viewManager.views.length > 1) { + camerasShown = that.scene.viewManager.views[1].active; + that.scene.viewManager.showCameras(!camerasShown); + } + } + + if (that.scene.viewManager.views.length > 1) { + camerasShown = that.scene.viewManager.views[1].active; + } + if(!camerasShown) + { + $('#view-camera-sensors').buttonMarkup({icon: 'false'}); + guiEvents.emit('notification_popup','Disabling camera views'); + } + else + { + $('#view-camera-sensors').buttonMarkup({icon: 'check'}); + guiEvents.emit('notification_popup','Enabling camera views'); + } + } + ); + guiEvents.on('pause', function(paused) { that.emitter.emit('pause', paused); @@ -1081,7 +1012,7 @@ GZ3D.Gui.prototype.init = function() $( '#notification-popup' ).html(' '+notification+' '); $( '#notification-popup' ).popup('open', { y:window.innerHeight-50}); - + if (duration === undefined) { duration = 2000; @@ -1140,7 +1071,8 @@ GZ3D.Gui.prototype.init = function() lastOpenMenu[parentId] = id; $('.leftPanels').hide(); - $('#'+id).show(); + //$('#'+id).show(); //defaults as flex, but block is needed + $('#'+id).css('display','block'); $('.tab').css('border-left-color', tabColors.unselected); $('#'+parentId+'Tab').css('border-left-color', tabColors.selected); @@ -1609,17 +1541,20 @@ GZ3D.Gui.prototype.setModelStats = function(stats, action) return e.shortName === LinkShortName; }); - if (link[0].self_collide) - { - link[0].self_collide = this.trueOrFalse(stats.link[0].self_collide); - } - if (link[0].gravity) + if (link[0]) { - link[0].gravity = this.trueOrFalse(stats.link[0].gravity); - } - if (link[0].kinematic) - { - link[0].kinematic = this.trueOrFalse(stats.link[0].kinematic); + if (link[0].self_collide) + { + link[0].self_collide = this.trueOrFalse(stats.link[0].self_collide); + } + if (link[0].gravity) + { + link[0].gravity = this.trueOrFalse(stats.link[0].gravity); + } + if (link[0].kinematic) + { + link[0].kinematic = this.trueOrFalse(stats.link[0].kinematic); + } } } @@ -1696,10 +1631,10 @@ GZ3D.Gui.prototype.setLightStats = function(stats, action) var thumbnail; switch(type) { - case 2: + case GZ3D.LIGHT_SPOT: thumbnail = 'style/images/spotlight.png'; break; - case 3: + case GZ3D.LIGHT_DIRECTIONAL: thumbnail = 'style/images/directionallight.png'; break; default: @@ -1801,7 +1736,10 @@ GZ3D.Gui.prototype.findModelThumbnail = function(instanceName) GZ3D.Gui.prototype.updateStats = function() { var tree = angular.element($('#treeMenu')).scope(); - tree.updateStats(); + if (typeof(tree) !== 'undefined' && typeof tree.updateStats !== 'undefined') + { + tree.updateStats(); + } }; /** @@ -1926,10 +1864,12 @@ GZ3D.Gui.prototype.formatStats = function(stats) colorHex = {}; for (comp in diffuse) { - colorHex[comp] = diffuse[comp].toString(16); - if (colorHex[comp].length === 1) - { - colorHex[comp] = '0' + colorHex[comp]; + if (diffuse.hasOwnProperty(comp)) { + colorHex[comp] = diffuse[comp].toString(16); + if (colorHex[comp].length === 1) + { + colorHex[comp] = '0' + colorHex[comp]; + } } } color.diffuse = '#' + colorHex['r'] + colorHex['g'] + colorHex['b']; @@ -1942,10 +1882,12 @@ GZ3D.Gui.prototype.formatStats = function(stats) colorHex = {}; for (comp in specular) { - colorHex[comp] = specular[comp].toString(16); - if (colorHex[comp].length === 1) - { - colorHex[comp] = '0' + colorHex[comp]; + if (specular.hasOwnProperty(comp)) { + colorHex[comp] = specular[comp].toString(16); + if (colorHex[comp].length === 1) + { + colorHex[comp] = '0' + colorHex[comp]; + } } } color.specular = '#' + colorHex['r'] + colorHex['g'] + colorHex['b']; @@ -2079,6 +2021,8 @@ GZ3D.Gui.prototype.deleteFromStats = function(type, name) //var GAZEBO_MODEL_DATABASE_URI='http://gazebosim.org/models'; +THREE.ImageUtils.crossOrigin = 'anonymous'; // needed to allow cross-origin loading of textures + GZ3D.GZIface = function(scene, gui) { this.scene = scene; @@ -2094,6 +2038,13 @@ GZ3D.GZIface = function(scene, gui) this.numConnectionTrials = 0; this.maxConnectionTrials = 30; // try to connect 30 times this.timeToSleepBtwTrials = 1000; // wait 1 second between connection trials + + this.assetProgressData = {}; + this.assetProgressData.assets = []; + this.assetProgressData.prepared = false; + this.assetProgressCallback = undefined; + + this.webSocketConnectionCallbacks = []; }; GZ3D.GZIface.prototype.init = function() @@ -2104,11 +2055,35 @@ GZ3D.GZIface.prototype.init = function() this.connect(); }; +GZ3D.GZIface.prototype.setAssetProgressCallback = function(callback) +{ + this.assetProgressCallback = callback; +}; + +GZ3D.GZIface.prototype.registerWebSocketConnectionCallback = function(callback) { + this.webSocketConnectionCallbacks.push(callback); +}; + GZ3D.GZIface.prototype.connect = function() { // connect to websocket + var url = GZ3D.webSocketUrl; + if (!localStorage.getItem('localmode.forceuser')) { + var token = []; + if (localStorage.getItem('tokens-neurorobotics-ui@https://services.humanbrainproject.eu/oidc')) { + try { + token = JSON.parse(localStorage.getItem('tokens-neurorobotics-ui@https://services.humanbrainproject.eu/oidc')); + } catch(e) { + token[0] = { access_token : 'notoken' }; + } + url = url + '/?token=' + token[0].access_token; + } else { + url = 'ws://' + location.hostname + ':7681'; + } + } + this.webSocket = new ROSLIB.Ros({ - url : 'ws://' + location.hostname + ':7681' + url : url }); var that = this; @@ -2118,6 +2093,9 @@ GZ3D.GZIface.prototype.connect = function() this.webSocket.on('error', function() { that.onError(); }); + this.webSocket.on('close', function() { + console.log('Connection closed to websocket server: ' + that.webSocket.socket.url); + }); this.numConnectionTrials++; }; @@ -2142,6 +2120,8 @@ GZ3D.GZIface.prototype.onError = function() GZ3D.GZIface.prototype.onConnected = function() { + console.log('Connected to websocket server: ' + this.webSocket.socket.url); + this.isConnected = true; this.emitter.emit('connection'); @@ -2163,6 +2143,11 @@ GZ3D.GZIface.prototype.onConnected = function() setInterval(publishHeartbeat, 5000); + // call all the registered callbacks since we are connected now + this.webSocketConnectionCallbacks.forEach(function(callback) { + callback(); + }); + var statusTopic = new ROSLIB.Topic({ ros: this.webSocket, name: '~/status', @@ -2208,7 +2193,7 @@ GZ3D.GZIface.prototype.onConnected = function() if (message.grid === true) { - this.gui.guiEvents.emit('show_grid', 'show'); + //this.gui.guiEvents.emit('show_grid', 'show'); // do not show grid by default for now } if (message.ambient) @@ -2248,6 +2233,7 @@ GZ3D.GZIface.prototype.onConnected = function() this.gui.setModelStats(model, 'update'); } + this.assetProgressData.prepared = true; this.gui.setSceneStats(message); this.sceneTopic.unsubscribe(); }; @@ -2356,6 +2342,8 @@ GZ3D.GZIface.prototype.onConnected = function() } i++; } + } else { + this.updateModelFromMsg(this.scene.getByName(message.name), message); } this.gui.setModelStats(message, 'update'); }; @@ -2478,7 +2466,7 @@ GZ3D.GZIface.prototype.onConnected = function() messageType : 'light', }); - var publishEntityModify = function(entity) + var createEntityModifyMessage = function(entity) { var matrix = entity.matrixWorld; var translation = new THREE.Vector3(); @@ -2505,43 +2493,89 @@ GZ3D.GZIface.prototype.onConnected = function() z: quaternion.z } }; - if (entity.children[0] && - entity.children[0] instanceof THREE.Light) + return entityMsg; + }; + + /* + TODO: (Sandro Weber) + The following functions are used to change all lights at the same time through the light slider of the NRP. + Gazebo only knows attenuation factors, not intensity, so we manipulate diffuse color for now. + Probably replaced / revamped after a dedicated edit tab is introduced to allow manipulation of lights directly. + */ + var createEntityModifyMessageWithLight = function(entity, diffuse) + { + var entityMsg = createEntityModifyMessage(entity); + + var lightObj = entity.children[0]; + + if (diffuse === undefined) { + diffuse = lightObj.color; + } + entityMsg.diffuse = { - entityMsg.diffuse = - { - r: entity.children[0].color.r, - g: entity.children[0].color.g, - b: entity.children[0].color.b - }; - entityMsg.specular = - { - r: entity.serverProperties.specular.r, - g: entity.serverProperties.specular.g, - b: entity.serverProperties.specular.b - }; - entityMsg.direction = entity.direction; - entityMsg.range = entity.children[0].distance; - entityMsg.attenuation_constant = entity.serverProperties.attenuation_constant; - entityMsg.attenuation_linear = entity.serverProperties.attenuation_linear; - entityMsg.attenuation_quadratic = entity.serverProperties.attenuation_quadratic; + r: diffuse.r, + g: diffuse.g, + b: diffuse.b + }; + entityMsg.specular = + { + r: entity.serverProperties.specular.r, + g: entity.serverProperties.specular.g, + b: entity.serverProperties.specular.b + }; + entityMsg.direction = entity.direction; + entityMsg.range = lightObj.distance; + + entityMsg.attenuation_constant = entity.serverProperties.attenuation_constant; + entityMsg.attenuation_linear = entity.serverProperties.attenuation_linear; + entityMsg.attenuation_quadratic = entity.serverProperties.attenuation_quadratic; + + return entityMsg; + }; - that.lightModifyTopic.publish(entityMsg); + var publishEntityModify = function(entity) + { + var lightObj = entity.children[0]; + if (lightObj && lightObj instanceof THREE.Light) + { + that.lightModifyTopic.publish(createEntityModifyMessageWithLight(entity, undefined)); } else { - that.modelModifyTopic.publish(entityMsg); + that.modelModifyTopic.publish(createEntityModifyMessage(entity)); } }; this.scene.emitter.on('entityChanged', publishEntityModify); - // Link messages - for modifying links - this.linkModifyTopic = new ROSLIB.Topic({ - ros : this.webSocket, - name : '~/link', - messageType : 'link', - }); + var publishLightModify = function(ratio) + { + var lights = []; + that.scene.scene.traverse(function(node) { + if (node instanceof THREE.Light) { + lights.push(node); + } + }); + + var numberOfLights = lights.length; + for (var i = 0; i < numberOfLights; i+=1) { + if( lights[i] instanceof THREE.AmbientLight ) { // we don't change ambient lights + continue; + } + var entity = that.scene.getByName(lights[i].name); + var newDiffuse = new THREE.Color(); + newDiffuse.r = THREE.Math.clamp(ratio * entity.serverProperties.initial.diffuse.r, 0, 1); + newDiffuse.g = THREE.Math.clamp(ratio * entity.serverProperties.initial.diffuse.g, 0, 1); + newDiffuse.b = THREE.Math.clamp(ratio * entity.serverProperties.initial.diffuse.b, 0, 1); + + that.lightModifyTopic.publish(createEntityModifyMessageWithLight(entity, newDiffuse)); + } + }; + + this.scene.emitter.on('lightChanged', publishLightModify); + /* + end of light slider change functions + */ var publishLinkModify = function(entity, type) { @@ -2808,6 +2842,16 @@ GZ3D.GZIface.prototype.createModelFromMsg = function(model) } } } + + for (var i = 0; i < link.sensor.length; ++i) { + var sensor = link.sensor[i]; + + var sensorObj = this.createSensorFromMsg(sensor); + if (sensorObj && !sensorObj.parent) + { + linkObj.add(sensorObj); + } + } } if (model.joint) { @@ -2817,6 +2861,24 @@ GZ3D.GZIface.prototype.createModelFromMsg = function(model) return modelObj; }; +// This method uses code also to be found at GZ3D.GZIface.prototype.createModelFromMsg. +// Currently not everything is handled for an update, but this method was introduced to handle +// the updates of colors of objects; if there should be more functionality one could consider +// merging the two methods and extracting the different things to parameters (or any other means +// of configuration). +GZ3D.GZIface.prototype.updateModelFromMsg = function (modelObj, modelMsg) { + for (var j = 0; j < modelMsg.link.length; ++j) { + var link = modelMsg.link[j]; + var linkObj = modelObj.children[j]; + + for (var k = 0; k < link.visual.length; ++k) { + var visual = link.visual[k]; + var visualObj = linkObj.getObjectByName(visual.name); + this.updateVisualFromMsg(visualObj, visual); + } + } +}; + GZ3D.GZIface.prototype.createVisualFromMsg = function(visual) { if (visual.geometry) @@ -2839,21 +2901,32 @@ GZ3D.GZIface.prototype.createVisualFromMsg = function(visual) } }; +GZ3D.GZIface.prototype.updateVisualFromMsg = function (visualObj, visual) { + if (visual.geometry) { + var obj = visualObj.children[0]; + var mat = this.parseMaterial(visual.material); + + if (obj && mat) { + this.scene.setMaterial(obj, mat); + } + } +}; + GZ3D.GZIface.prototype.createLightFromMsg = function(light) { var obj, range, direction; - if (light.type === 1) + if (light.type === this.scene.LIGHT_POINT) { direction = null; range = light.range; } - else if (light.type === 2) + else if (light.type === this.scene.LIGHT_SPOT) { direction = light.direction; range = light.range; } - else if (light.type === 3) + else if (light.type === this.scene.LIGHT_DIRECTIONAL) { direction = light.direction; range = null; @@ -2891,9 +2964,66 @@ GZ3D.GZIface.prototype.createRoadsFromMsg = function(roads) return roadObj; }; +GZ3D.GZIface.prototype.createSensorFromMsg = function(sensor) +{ + var sensorObj = new THREE.Object3D(); + sensorObj.name = sensor.name; + + if (sensor.pose) { + this.scene.setPose(sensorObj, sensor.pose.position, sensor.pose.orientation); + } + + if (sensor.type === 'camera') { + // if we have a camera sensor we have a potential view that could be rendered + // camera parameters are not published by gazebo right now, so hardcoded until fixed + var displayParams = { + left: '5%', + top: '5%', + width: '20%', + height: '20%', + adjustable: true + }; + var cameraParams = { + width: 960, + height: 600, + fov: 60, + near: 0.1, + far: 100 + }; + var viewName = 'view_' + sensor.name; + var view = this.scene.viewManager.createView(viewName, displayParams, cameraParams); + if (!view) { + console.error('GZ3D.GZIface.createSensorFromMsg() - failed to create view ' + viewName); + return; + } + + // There is a problem with the width and height; it should be read from the "sensor" object. Also see: + // https://bitbucket.org/osrf/gazebo/issues/1663/sensor-camera-elements-from-sdf-not-being + + // camera sensors defined in gazebo .sdf seem to look along positive x axis, so need to adjust the rotation here + view.camera.rotateOnAxis(new THREE.Vector3(0, 1, 0), -Math.PI / 2); + view.camera.rotateOnAxis(new THREE.Vector3(0, 0, 1), -Math.PI / 2); + + // set view inactive and hide at start + this.scene.viewManager.setViewVisibility(view, false); + + // visualization - Deactivated since it causes the robot to be very big and the user + // can't barely select other objects on the scene. Reactivate for debug purposes ! + // var cameraHelper = new THREE.CameraHelper(view.camera); + // view.camera.add( cameraHelper ); + + view.type = sensor.type; + console.log('view type: ' + view.type); + + sensorObj.add(view.camera); + } + + return sensorObj; +}; + GZ3D.GZIface.prototype.parseUri = function(uri) { - var uriPath = 'assets'; + var uriPath = GZ3D.assetsPath; var idx = uri.indexOf('://'); if (idx > 0) { @@ -2905,7 +3035,7 @@ GZ3D.GZIface.prototype.parseUri = function(uri) GZ3D.GZIface.prototype.createGeom = function(geom, material, parent) { var obj; - var uriPath = 'assets'; + var uriPath = GZ3D.assetsPath; var that = this; var mat = this.parseMaterial(material); if (geom.box) @@ -2985,26 +3115,48 @@ GZ3D.GZIface.prototype.createGeom = function(geom, material, parent) var modelUri = uriPath + '/' + modelName; // Use coarse version on touch devices - if (modelUri.indexOf('.dae') !== -1 && isTouchDevice) + if (modelUri.indexOf('.dae') !== -1 /*&& isTouchDevice*/) // Modified for HBP, we do use coarse models all the time { modelUri = modelUri.substring(0,modelUri.indexOf('.dae')); - var checkModel = new XMLHttpRequest(); - checkModel.open('HEAD', modelUri+'_coarse.dae', false); - checkModel.send(); - if (checkModel.status === 404) + if(modelUri.indexOf('_coarse') !== -1) //dae is already a coarse model { modelUri = modelUri+'.dae'; } - else - { - modelUri = modelUri+'_coarse.dae'; + else { // check if a coarse version is available + var checkModel = new XMLHttpRequest(); + // We use a double technique to disable the cache for these requests: + // 1. We create a custom url by adding the time as a parameter. + // 2. We add the If-Modified-Since header with a date far in the future (end of the HBP project) + // Since browsers and servers vary in their behaviour, we use both of these tricks. + // PS: These requests do not load the dae files, they just verify if they exist on the server + // so that we can choose between coarse or reqular models. + checkModel.open('HEAD', modelUri+'_coarse.dae?timestamp=' + new Date().getTime(), false); + checkModel.setRequestHeader('If-Modified-Since', 'Sat, 1 Jan 2026 00:00:00 GMT'); + + try { checkModel.send(); } catch(err) { console.log(modelUri + ': no coarse version'); } + + if (checkModel.status === 404) { + modelUri = modelUri+'.dae'; + } + else { + modelUri = modelUri+'_coarse.dae'; + } } } var materialName = parent.name + '::' + modelUri; this.entityMaterial[materialName] = mat; + // Progress update: Add this asset to the assetProgressArray + var element = {}; + element.id = parent.name; + element.url = modelUri; + element.progress = 0; + element.totalSize = 0; + element.done = false; + this.assetProgressData.assets.push(element); + this.scene.loadMesh(modelUri, submesh, centerSubmesh, function(dae) { if (that.entityMaterial[materialName]) @@ -3023,6 +3175,19 @@ GZ3D.GZIface.prototype.createGeom = function(geom, material, parent) } parent.add(dae); loadGeom(parent); + + // Progress update: execute callback + element.done = true; + if (that.assetProgressCallback) { + that.assetProgressCallback(that.assetProgressData); + } + }, function(progress){ + element.progress = progress.loaded; + element.totalSize = progress.total; + element.error = progress.error; + if (that.assetProgressCallback) { + that.assetProgressCallback(that.assetProgressData); + } }); } } @@ -3098,7 +3263,7 @@ GZ3D.GZIface.prototype.createGeom = function(geom, material, parent) allChildren[c].castShadow = false; allChildren[c].receiveShadow = false; - allChildren[c].visible = this.scene.showCollisions; + allChildren[c].visible = that.scene.showCollisions; } } } @@ -3156,7 +3321,7 @@ GZ3D.GZIface.prototype.parseMaterial = function(material) return null; } - var uriPath = 'assets'; + var uriPath = GZ3D.assetsPath;//'assets'; var texture; var normalMap; var textureUri; @@ -4011,10 +4176,10 @@ GZ3D.Manipulator = function(camera, mobile, domElement, doc) } this.object.updateMatrixWorld(); - worldPosition.getPositionFromMatrix(this.object.matrixWorld); + worldPosition.setFromMatrixPosition(this.object.matrixWorld); this.camera.updateMatrixWorld(); - camPosition.getPositionFromMatrix(this.camera.matrixWorld); + camPosition.setFromMatrixPosition(this.camera.matrixWorld); scale = worldPosition.distanceTo(camPosition) / 6 * this.scale; this.gizmo.position.copy(worldPosition); @@ -4266,7 +4431,7 @@ GZ3D.Manipulator = function(camera, mobile, domElement, doc) worldRotationMatrix.extractRotation(scope.object.matrixWorld); parentRotationMatrix.extractRotation(scope.object.parent.matrixWorld); - parentScale.getScaleFromMatrix(tempMatrix.getInverse( + parentScale.setFromMatrixScale(tempMatrix.getInverse( scope.object.parent.matrixWorld)); offset.copy(planeIntersect.point); @@ -4381,7 +4546,7 @@ GZ3D.Manipulator = function(camera, mobile, domElement, doc) parentRotationMatrix.extractRotation( scope.object.parent.matrixWorld); - parentScale.getScaleFromMatrix(tempMatrix.getInverse( + parentScale.setFromMatrixScale(tempMatrix.getInverse( scope.object.parent.matrixWorld)); offset.copy(planeIntersect.point); @@ -4677,6 +4842,287 @@ GZ3D.Manipulator = function(camera, mobile, domElement, doc) GZ3D.Manipulator.prototype = Object.create(THREE.EventDispatcher.prototype); +/** + * Created by Sandro Weber (webers@in.tum.de). + */ + +GZ3D.MULTIVIEW_MAX_VIEW_COUNT = 10; + +/** + * GZ3D.MULTIVIEW_RENDER_VIEWPORTS uses view containers as transparent references to determine viewports for rendering (one single canvas). + * This is broken in combination with shadowmaps at the moment. See renderToViewport() method. + * + * @type {number} + */ +GZ3D.MULTIVIEW_RENDER_VIEWPORTS = 1; +/** + * GZ3D.MULTIVIEW_RENDER_COPY2CANVAS renders views offscreen then copies into the view containers (multiple separate canvases). + * @type {number} + */ +GZ3D.MULTIVIEW_RENDER_COPY2CANVAS = 2; + +GZ3D.MultiView = function(gz3dScene, mainContainer, callbackCreateRenderContainer) +{ + this.gz3dScene = gz3dScene; + this.mainContainer = mainContainer; + this.createRenderContainerCallback = callbackCreateRenderContainer; + + this.init(); +}; + +GZ3D.MultiView.prototype.init = function() +{ + this.views = []; + + this.mainContainer.style.zIndex = 0; + + this.renderMethod = GZ3D.MULTIVIEW_RENDER_COPY2CANVAS; + if (this.renderMethod === GZ3D.MULTIVIEW_RENDER_VIEWPORTS) { + this.mainContainer.appendChild(this.gz3dScene.getDomElement()); + } +}; + +GZ3D.MultiView.prototype.setCallbackCreateRenderContainer = function(callback) +{ + this.createRenderContainerCallback = callback; +}; + +/** + * + * @param name + * @param displayParams {left, top, width, height, zIndex, adjustable} + * @param cameraParams {width, height, fov, near, far} + */ +GZ3D.MultiView.prototype.createView = function(name, displayParams, cameraParams) +{ + if (this.getViewByName(name) !== undefined) { + console.error('GZ3D.MultiView.createView() - a view of that name already exists (' + name + ')'); + return null; + } + + if (this.views.length >= this.MULTIVIEW_MAX_VIEW_COUNT) { + console.warn('GZ3D.MultiView.createView() - creating new view will exceed MULTIVIEW_MAX_VIEW_COUNT(' + this.MULTIVIEW_MAX_VIEW_COUNT + '). This may cause z-ordering issues.'); + } + + var container = this.createViewContainer(displayParams, name); + if (container === null) { + return; + } + + // camera + var camera = new THREE.PerspectiveCamera(cameraParams.fov, cameraParams.width / cameraParams.height, cameraParams.near, cameraParams.far); + camera.name = name; + + // assemble view + var view = { + name: name, + active: true, + container: container, + camera: camera + }; + + this.views.push(view); + this.mainContainer.appendChild(view.container); + + return view; +}; + +GZ3D.MultiView.prototype.createViewContainer = function(displayParams, name) +{ + if (this.createRenderContainerCallback === undefined) { + console.error('GZ3D.MultiView.createViewContainer() - no callback for creating view reference container defined'); + return null; + } + + // container div + var viewContainer = this.createRenderContainerCallback(displayParams.adjustable, name); + if (!viewContainer) { + console.error('GZ3D.MultiView.createViewContainer() - could not create view container via callback'); + return null; + } + + // positioning + viewContainer.style.position = 'absolute'; + viewContainer.style.left = displayParams.left; + viewContainer.style.top = displayParams.top; + // There is a problem with the width and height; it should be read from the "sensor" object. Also see: + // https://bitbucket.org/osrf/gazebo/issues/1663/sensor-camera-elements-from-sdf-not-being + // Here we use (preliminary) percentual width. + viewContainer.style.width = displayParams.width; + viewContainer.style.height = displayParams.height; + + // We set 50px as a min-width for now and set the min height accordingly + viewContainer.style.minWidth = '50px'; + viewContainer.style.minHeight = (50 * (parseInt(displayParams.width, 10)/parseInt(displayParams.height, 10))) + 'px'; + viewContainer.style.maxWidth = '100%'; + viewContainer.style.maxHeight = '100%'; + + // transparent view-container so we can render viewport with one renderer in the same context + // view-container is only taken as reference for viewport + viewContainer.style.boxShadow = '0px 0px 0px 3px rgba(0,0,0,0.3)'; + viewContainer.style.borderRadius = '2px'; + viewContainer.style.background = 'rgba(0,0,0,0)'; + + if (this.renderMethod === GZ3D.MULTIVIEW_RENDER_COPY2CANVAS) { + // canvas + viewContainer.canvas = document.createElement('canvas'); + viewContainer.appendChild( viewContainer.canvas ); + viewContainer.canvas.style.width = '100%'; + viewContainer.canvas.style.height = '100%'; + } + + // z-index + var zIndexTop = parseInt(this.mainContainer.style.zIndex, 10) + this.views.length + 1; + viewContainer.style.zIndex = (displayParams.zIndex !== undefined) ? displayParams.zIndex : zIndexTop; + + return viewContainer; +}; + +GZ3D.MultiView.prototype.getViewByName = function(name) +{ + for (var i = 0; i < this.views.length; i = i+1) { + if (this.views[i].name === name) { + return this.views[i]; + } + } + + return undefined; +}; + +GZ3D.MultiView.prototype.setViewVisibility = function(view, visible) +{ + view.active = visible; + if (view.active) { + view.container.style.visibility = 'visible'; + } else { + view.container.style.visibility = 'hidden'; + } +}; + +GZ3D.MultiView.prototype.updateCamera = function(view) +{ + view.camera.aspect = view.container.clientWidth / view.container.clientHeight; + view.camera.updateProjectionMatrix(); +}; + +GZ3D.MultiView.prototype.getViewport = function(view) +{ + var viewport = { + x: view.container.offsetLeft, + y: this.mainContainer.clientHeight - view.container.clientHeight - view.container.offsetTop, + w: view.container.clientWidth, + h: view.container.clientHeight + }; + + return viewport; +}; + +GZ3D.MultiView.prototype.setWindowSize = function(width, height) +{ + +}; + +GZ3D.MultiView.prototype.renderViews = function() +{ + // sort views into rendering order + this.views.sort(function(a, b) { + return a.container.style.zIndex - b.container.style.zIndex; + }); + + for (var i = 0, l = this.views.length; i < l; i = i+1) { + var view = this.views[i]; + + if (view.active) { + switch(this.renderMethod) { + case GZ3D.MULTIVIEW_RENDER_VIEWPORTS: + this.renderToViewport(view); + break; + case GZ3D.MULTIVIEW_RENDER_COPY2CANVAS: + this.renderAndCopyToCanvas(view); + break; + } + } + } +}; + +/** + * !IMPORTANT! + * https://github.com/mrdoob/three.js/issues/3532 + * This is at the moment not a good idea, see issue above. ScissorTest will scramble shadowmaps. + * + * @param view + */ +GZ3D.MultiView.prototype.renderToViewport = function(view) +{ + this.updateCamera(view); //TODO: better solution with resize callback, also adjust camera helper + + var webglRenderer = this.gz3dScene.renderer; + + var viewport = this.getViewport(view); + webglRenderer.setViewport( viewport.x, viewport.y, viewport.w, viewport.h ); + webglRenderer.setScissor( viewport.x, viewport.y, viewport.w, viewport.h ); + webglRenderer.enableScissorTest ( true ); + + if (this.gz3dScene.effectsEnabled) { + this.gz3dScene.scene.overrideMaterial = this.depthMaterial; + this.gz3dScene.renderer.render(this.gz3dScene.scene, view.camera, this.depthTarget); + this.gz3dScene.scene.overrideMaterial = null; + this.gz3dScene.composer.render(); + } else { + webglRenderer.render(this.gz3dScene.scene, view.camera); + } +}; + +GZ3D.MultiView.prototype.renderAndCopyToCanvas = function(view) +{ + this.updateCamera(view); //TODO: better solution with resize callback, also adjust camera helper + + var webglRenderer = this.gz3dScene.renderer; + + var width = view.container.canvas.clientWidth; + var height = view.container.canvas.clientHeight; + view.container.canvas.width = width; + view.container.canvas.height = height; + + if (webglRenderer.context.canvas.width < width) { + webglRenderer.context.canvas.width = width; + } + if (webglRenderer.context.canvas.height < height) { + webglRenderer.context.canvas.height = height; + } + webglRenderer.setViewport( 0, 0, width, height ); + + if (this.gz3dScene.effectsEnabled) { + this.gz3dScene.scene.overrideMaterial = this.depthMaterial; + this.gz3dScene.renderer.render(this.gz3dScene.scene, view.camera, this.depthTarget); + this.gz3dScene.scene.overrideMaterial = null; + this.gz3dScene.composer.render(); + } else { + webglRenderer.render(this.gz3dScene.scene, view.camera); + } + + // copy rendered image over to view canvas + var srcX = 0; + var srcY = webglRenderer.context.canvas.height - height + 1; + var dstX = 0; + var dstY = 0; + // no cleaner solution right now, should be coming though: https://github.com/mrdoob/three.js/pull/6723#issuecomment-134129027 + // this kills performance a little ... + if ( srcX >= 0 && srcY >= 0 && width >= 1 && height >= 1 ) { + view.container.canvas.getContext('2d').drawImage( webglRenderer.context.canvas, + srcX, srcY, width, height, + dstX, dstY, width, height ); + } +}; + +GZ3D.MultiView.prototype.showCameras = function(show) +{ + for (var i = 0; i < this.views.length; i = i+1) { + if (this.views[i].type === 'camera') { + this.setViewVisibility(this.views[i], show); + } + } +}; /** * Radial menu for an object * @constructor @@ -5079,8 +5525,6 @@ GZ3D.RadialMenu.prototype.setNumberOfItems = function(number) this.offset = this.numberOfItems - 1 - Math.floor(this.numberOfItems/2); }; - - /** * The scene is where everything is placed, from objects, to lights and cameras. * @constructor @@ -5090,6 +5534,11 @@ GZ3D.Scene = function() this.init(); }; +GZ3D.Scene.prototype.LIGHT_POINT = 1; +GZ3D.Scene.prototype.LIGHT_SPOT = 2; +GZ3D.Scene.prototype.LIGHT_DIRECTIONAL = 3; +GZ3D.Scene.prototype.LIGHT_UNKNOWN = 4; + /** * Initialize scene */ @@ -5113,16 +5562,38 @@ GZ3D.Scene.prototype.init = function() this.renderer.setSize( window.innerWidth, window.innerHeight); // shadows - this.renderer.shadowMapEnabled = true; + this.renderer.shadowMapEnabled = false; this.renderer.shadowMapType = THREE.PCFSoftShadowMap; + this.container = document.getElementById( 'container' ); + + // create views manager + this.viewManager = new GZ3D.MultiView(this, this.container, function(adjustable, name){ + return document.createElement('div'); + }); + // create main view + var displayParams = { + left: '0px', + top: '0px', + width: '100%', + height: '100%', + adjustable: false + }; + var cameraParams = { + width: 960, + height: 600, + fov: 60, + near: 0.1, + far: 100 + }; + this.viewManager.createView('main_view', displayParams, cameraParams); + // lights this.ambient = new THREE.AmbientLight( 0x666666 ); this.scene.add(this.ambient); // camera - this.camera = new THREE.PerspectiveCamera( - 60, window.innerWidth / window.innerHeight, 0.1, 1000 ); + this.camera = this.viewManager.getViewByName('main_view').camera; this.defaultCameraPosition = new THREE.Vector3(0, -5, 5); this.resetView(); @@ -5181,7 +5652,12 @@ GZ3D.Scene.prototype.init = function() this.timeDown = null; - this.controls = new THREE.OrbitControls(this.camera); + var domElementForKeyBindings = document.getElementsByTagName('body')[0]; + this.controls = new THREE.FirstPersonControls(this.camera, this.container, domElementForKeyBindings); + if (this.controls instanceof THREE.FirstPersonControls) { + this.controls.movementSpeed = 0.2; + this.controls.lookSpeed = 0.005; + } if (this.controls.targetIndicator !== undefined) { this.scene.add(this.controls.targetIndicator); } @@ -5550,7 +6026,7 @@ GZ3D.Scene.prototype.onPointerDown = function(event) */ GZ3D.Scene.prototype.onPointerUp = function(event) { - event.preventDefault(); + //event.preventDefault(); // Clicks (<150ms) outside any models trigger view mode var millisecs = new Date().getTime(); @@ -5754,7 +6230,7 @@ GZ3D.Scene.prototype.getRayCastModel = function(pos, intersect) */ GZ3D.Scene.prototype.getDomElement = function() { - return this.renderer.domElement; + return this.container; }; /** @@ -5784,17 +6260,7 @@ GZ3D.Scene.prototype.render = function() this.modelManipulator.update(); this.radialMenu.update(); - if (this.effectsEnabled) - { - this.scene.overrideMaterial = this.depthMaterial; - this.renderer.render(this.scene, this.camera, this.depthTarget); - this.scene.overrideMaterial = null; - this.composer.render(); - } - else - { - this.renderer.render(this.scene, this.camera); - } + this.viewManager.renderViews(); }; /** @@ -5808,6 +6274,11 @@ GZ3D.Scene.prototype.setWindowSize = function(width, height) this.camera.updateProjectionMatrix(); this.renderer.setSize( width, height); + this.renderer.context.canvas.width = width; + this.renderer.context.canvas.height = height; + + this.viewManager.setWindowSize(width, height); + this.render(); }; @@ -5996,6 +6467,8 @@ GZ3D.Scene.prototype.createBox = function(width, height, depth) * @param {} attenuation_constant * @param {} attenuation_linear * @param {} attenuation_quadratic + * @param {} spot_angle + * @param {} spot_falloff * @returns {THREE.Object3D} */ GZ3D.Scene.prototype.createLight = function(type, diffuse, intensity, pose, @@ -6024,6 +6497,10 @@ GZ3D.Scene.prototype.createLight = function(type, diffuse, intensity, pose, specular = color.clone(); } + if (typeof(specular) === 'undefined') { + specular = 0xffffff; + } + var matrixWorld; if (pose) @@ -6047,17 +6524,17 @@ GZ3D.Scene.prototype.createLight = function(type, diffuse, intensity, pose, } var elements; - if (type === 1) + if (type === this.LIGHT_POINT) { elements = this.createPointLight(obj, diffuse, intensity, distance, cast_shadows); } - else if (type === 2) + else if (type === this.LIGHT_SPOT) { elements = this.createSpotLight(obj, diffuse, intensity, distance, cast_shadows, spot_angle, spot_falloff); } - else if (type === 3) + else if (type === this.LIGHT_DIRECTIONAL) { elements = this.createDirectionalLight(obj, diffuse, intensity, cast_shadows); @@ -6092,13 +6569,16 @@ GZ3D.Scene.prototype.createLight = function(type, diffuse, intensity, pose, obj.serverProperties.attenuation_constant = attenuation_constant; obj.serverProperties.attenuation_linear = attenuation_linear; obj.serverProperties.attenuation_quadratic = attenuation_quadratic; + obj.serverProperties.initial = {}; + obj.serverProperties.initial.diffuse = diffuse; - obj.add(lightObj); - obj.add(helper); - + helper.visible = false; lightObj.up = new THREE.Vector3(0,0,1); lightObj.shadowBias = -0.0005; + obj.add(lightObj); + obj.add(helper); + return obj; }; @@ -6565,7 +7045,7 @@ GZ3D.Scene.prototype.loadHeightmap = function(heights, width, height, * @param {function} callback */ GZ3D.Scene.prototype.loadMesh = function(uri, submesh, centerSubmesh, - callback) + callback, progressCallback) { var uriPath = uri.substring(0, uri.lastIndexOf('/')); var uriFile = uri.substring(uri.lastIndexOf('/') + 1); @@ -6573,7 +7053,7 @@ GZ3D.Scene.prototype.loadMesh = function(uri, submesh, centerSubmesh, // load urdf model if (uriFile.substr(-4).toLowerCase() === '.dae') { - return this.loadCollada(uri, submesh, centerSubmesh, callback); + return this.loadCollada(uri, submesh, centerSubmesh, callback, progressCallback); } else if (uriFile.substr(-5).toLowerCase() === '.urdf') { @@ -6619,7 +7099,7 @@ GZ3D.Scene.prototype.loadMesh = function(uri, submesh, centerSubmesh, * @param {function} callback */ GZ3D.Scene.prototype.loadCollada = function(uri, submesh, centerSubmesh, - callback) + callback, progressCallback) { var dae; var mesh = null; @@ -6642,6 +7122,7 @@ GZ3D.Scene.prototype.loadCollada = function(uri, submesh, centerSubmesh, var thatSubmesh = submesh; var thatCenterSubmesh = centerSubmesh; + var that = this; loader.load(uri, function(collada) { // check for a scale factor @@ -6653,13 +7134,22 @@ GZ3D.Scene.prototype.loadCollada = function(uri, submesh, centerSubmesh, dae = collada.scene; dae.updateMatrix(); - this.scene.prepareColladaMesh(dae); - this.scene.meshes[thatURI] = dae; + that.prepareColladaMesh(dae); + that.meshes[thatURI] = dae; dae = dae.clone(); - this.scene.useColladaSubMesh(dae, thatSubmesh, centerSubmesh); + that.useColladaSubMesh(dae, thatSubmesh, centerSubmesh); dae.name = uri; callback(dae); + },function(progress) { + if (progressCallback !== undefined) { + progress.error = false; + progressCallback(progress); + } + },function(){ + if (progressCallback !== undefined) { + progressCallback({ total: 0, loaded: 0, error: true }); + } }); }; @@ -6872,7 +7362,7 @@ GZ3D.Scene.prototype.setManipulationMode = function(mode) } else { - // Toggle manipulaion space (world / local) + // Toggle manipulation space (world / local) if (this.modelManipulator.mode === this.manipulationMode) { this.modelManipulator.space = @@ -7465,7 +7955,7 @@ GZ3D.Scene.prototype.updateLight = function(entity, msg) this.setPose(entity, msg.pose.position, msg.pose.orientation); entity.matrixWorldNeedsUpdate = true; - if (entity.direction) + if (entity.direction && lightObj.target) { dir = new THREE.Vector3(entity.direction.x, entity.direction.y, entity.direction.z); @@ -7507,6 +7997,18 @@ GZ3D.Scene.prototype.updateLight = function(entity, msg) entity.serverProperties.attenuation_quadratic = msg.attenuation_quadratic; lightObj.intensity = lightObj.intensity/(1+msg.attenuation_quadratic); } + if (msg.attenuation_linear && msg.attenuation_quadratic) + { + // equation taken from + // http://wiki.blender.org/index.php/Doc:2.6/Manual/Lighting/Lights/Light_Attenuation + var E = 1; + var D = 1; + var r = 1; + var L = msg.attenuation_linear; + var Q = msg.attenuation_quadratic; + lightObj.intensity = E*(D/(D+L*r))*(Math.pow(D,2)/(Math.pow(D,2)+Q*Math.pow(r,2))); + } + if (lightObj instanceof THREE.SpotLight) { if (msg.spot_outer_angle) { @@ -7518,7 +8020,7 @@ GZ3D.Scene.prototype.updateLight = function(entity, msg) } } - if (msg.direction) + if (msg.direction && lightObj.target) { dir = new THREE.Vector3(msg.direction.x, msg.direction.y, msg.direction.z); @@ -7531,6 +8033,29 @@ GZ3D.Scene.prototype.updateLight = function(entity, msg) } }; +GZ3D.Scene.prototype.setShadowMaps = function(enabled) { + this.renderer.shadowMapEnabled = enabled; + + var that = this; + this.scene.traverse(function(node) { + if (enabled) { + if (node.material) { + node.material.needsUpdate = true; + if (node.material.materials) { + for (var i = 0; i < node.material.materials.length; i = i+1) { + node.material.materials[i].needsUpdate = true; + } + } + } + } else { + if (node instanceof THREE.Light) { + if (node.shadowMap) { + that.renderer.clearTarget( node.shadowMap ); + } + } + } + }); +}; /** * SDF parser constructor initializes SDF parser with the given parameters * and defines a DOM parser function to parse SDF XML files @@ -7542,7 +8067,7 @@ GZ3D.SdfParser = function(scene, gui, gziface) { // set the sdf version this.SDF_VERSION = 1.5; - this.MATERIAL_ROOT = 'assets/'; + this.MATERIAL_ROOT = GZ3D.assetsPath + '/'; // set the xml parser function this.parseXML = function(xmlStr) { @@ -8072,7 +8597,7 @@ GZ3D.SdfParser.prototype.createGeom = function(geom, mat, parent) allChildren[c].castShadow = false; allChildren[c].receiveShadow = false; - allChildren[c].visible = this.scene.showCollisions; + allChildren[c].visible = that.scene.showCollisions; } break; } diff --git a/gz3d/client/index.html b/gz3d/client/index.html index 2dd977d34d2e2fc9464126d02bb1b4b653c38900..45175460bd6b5e0b4504899b88c5545ba69efdb2 100644 --- a/gz3d/client/index.html +++ b/gz3d/client/index.html @@ -114,7 +114,7 @@ </script> <body> - <div data-role="page" data-theme="b" data-content-theme="c"> + <div id="gz3d-body" data-role="page" data-theme="b" data-content-theme="c"> <!-- left panel --> <div id="leftPanel" class="gzGUI"> @@ -139,6 +139,8 @@ <li id='view-grid' data-icon="false" data-iconpos="right" style="border: none !important;"><a href="#"><span class="collapsible_item">Grid</span></a></li> <li id='view-collisions' data-icon="false" data-iconpos="right" style="border: none !important;"><a href="#"><span class="collapsible_item">Collisions</span></a></li> <li id='view-orbit-indicator' data-icon="false" data-iconpos="right" style="border: none !important;"><a href="#"><span class="collapsible_item">Orbit Indicator</span></a></li> + <li id='view-shadows' data-icon="false" data-iconpos="right" style="border: none !important;"><a href="#"><span class="collapsible_item">Shadows</span></a></li> + <li id='view-camera-sensors' data-icon="false" data-iconpos="right" style="border: none !important;"><a href="#"><span class="collapsible_item">Camera Sensors</span></a></li> </ul> </div> <div data-role="collapsible" data-inset="false" data-iconpos="left" data-collapsed-icon="carat-r" data-expanded-icon="carat-d"> @@ -1135,7 +1137,7 @@ <!-- header --> <!-- content --> - <div data-role="content" id="container"> + <div data-role="content" id="container" style="position: absolute"> <div data-role="popup" id="notification-popup" data-history="false"></div> <div data-role="popup" data-corners="false" id="model-popup" data-history="false"> <ul data-role="listview"> @@ -1165,12 +1167,21 @@ function init() { scene = new GZ3D.Scene(); + scene.viewManager.setCallbackCreateRenderContainer(function(adjustable, name) { + /*if (adjustable) { + return $compile('<div movable resizeable keep-aspect-ratio class="camera-view"><div class="camera-view-label">' + name + '</div></div>')($rootScope)[0]; + } else { + return $compile('<div keep-aspect-ratio class="camera-view"><div class="camera-view-label">' + name + '</div></div>')($rootScope)[0]; + }*/ + return document.createElement('div'); + }); + gui = new GZ3D.Gui(scene); iface = new GZ3D.GZIface(scene, gui); sdfparser = new GZ3D.SdfParser(scene, gui, iface); - container = document.getElementById( 'container' ); - container.appendChild(scene.getDomElement()); + //container = document.getElementById( 'container' ); + //container.appendChild(scene.getDomElement()); // FPS indicator stats = new Stats(); diff --git a/gz3d/client/js/include/FirstPersonControls.js b/gz3d/client/js/include/FirstPersonControls.js index 35a202ed19869d807d232809fb98e656e8179299..ab61a1b45387740c13cde55691164f37f7e67372 100644 --- a/gz3d/client/js/include/FirstPersonControls.js +++ b/gz3d/client/js/include/FirstPersonControls.js @@ -273,7 +273,7 @@ THREE.FirstPersonControls = function(object, domElement, domElementForKeyBinding vecForward.normalize(); this.zenith = Math.acos(-vecForward.z); - this.azimuth = Math.atan(vecForward.y / vecForward.x) + Math.PI; + this.azimuth = Math.atan2(vecForward.y, vecForward.x) + Math.PI; }; function bind(scope, fn) { diff --git a/gz3d/src/gz.js b/gz3d/src/gz.js index e8dd0aec0149d76faae58cb7cb9805f32b730069..54989411226664c2788855f82f86f69e4bb538fd 100644 --- a/gz3d/src/gz.js +++ b/gz3d/src/gz.js @@ -1,3 +1,5 @@ var GZ3D = GZ3D || { - REVISION : '1' + REVISION : '1', + assetsPath: 'http://localhost:8080/assets', + webSocketUrl: 'ws://localhost:7681' }; \ No newline at end of file diff --git a/gz3d/src/gzgui.js b/gz3d/src/gzgui.js index 559504cba34f546a21b26132605701df715dfa13..81ae97170e9111e5c06f3f4b2bb84d31c918b69d 100644 --- a/gz3d/src/gzgui.js +++ b/gz3d/src/gzgui.js @@ -5,7 +5,7 @@ var guiEvents = new EventEmitter2({ verbose: true }); var emUnits = function(value) { - return value*parseFloat($('body').css('font-size')); + return value*parseFloat($('#gz3d-body').css('font-size')); }; var isTouchDevice = 'ontouchstart' in window || 'onmsgesturechange' in window; @@ -25,160 +25,15 @@ var tabColors = {selected: 'rgb(34, 170, 221)', unselected: 'rgb(42, 42, 42)'}; var modelList = [ - {path:'buildings', title:'Buildings', - examplePath1:'fast_food', examplePath2:'kitchen_dining', examplePath3:'house_1', models: + {path:'virtual_room', title:'Virtual Room Objects', + examplePath1:'library_model', examplePath2:'hosta_potted_plant', examplePath3:'vr_lamp', models: [ - {modelPath:'fast_food', modelTitle:'Fast Food'}, - {modelPath:'gas_station', modelTitle:'Gas Station'}, - {modelPath:'house_1', modelTitle:'House 1'}, - {modelPath:'house_2', modelTitle:'House 2'}, - {modelPath:'house_3', modelTitle:'House 3'}, - {modelPath:'iss', modelTitle:'International Space Station'}, - {modelPath:'iss_half', modelTitle:'ISS half'}, - {modelPath:'kitchen_dining', modelTitle:'Kitchen and Dining'}, - {modelPath:'office_building', modelTitle:'Office Building'}, - {modelPath:'powerplant', modelTitle:'Power Plant'}, - {modelPath:'starting_pen', modelTitle:'Starting Pen'}, - {modelPath:'willowgarage', modelTitle:'Willow Garage'} - ]}, - - {path:'furniture', title:'Furniture', - examplePath1:'hinged_door', examplePath2:'bookshelf', examplePath3:'table', models: - [ - {modelPath:'bookshelf', modelTitle:'Book Shelf'}, - {modelPath:'cabinet', modelTitle:'Cabinet'}, - {modelPath:'drc_practice_door_4x8', modelTitle:'4x8 Doorway'}, - {modelPath:'drc_practice_ladder', modelTitle:'Ladder'}, - {modelPath:'hinged_door', modelTitle:'Hinged Door'}, - {modelPath:'table', modelTitle:'Table'}, - {modelPath:'table_marble', modelTitle:'Table Marble'}, - - {modelPath:'drc_practice_ball_valve', modelTitle:'Ball Valve'}, - {modelPath:'drc_practice_handle_wheel_valve', modelTitle:'Handle Wheel Valve'}, - {modelPath:'drc_practice_hand_wheel_valve', modelTitle:'Hand Wheel Valve'}, - {modelPath:'drc_practice_wheel_valve', modelTitle:'Wheel Valve'}, - {modelPath:'drc_practice_wheel_valve_large', modelTitle:'Wheel Valve Large'}, - {modelPath:'door_handle', modelTitle:'Door Handle'}, - - {modelPath:'drc_practice_ball_valve_wall', modelTitle:'Wall (Ball Valve)'}, - {modelPath:'drc_practice_handle_wheel_valve_wall', modelTitle:'Wall (Handle Wheel Valve)'}, - {modelPath:'drc_practice_hand_wheel_valve_wall', modelTitle:'Wall (Hand Wheel Valve)'}, - {modelPath:'drc_practice_valve_wall', modelTitle:'Wall (Valve)'}, - {modelPath:'drc_practice_wheel_valve_wall', modelTitle:'Wall (Wheel Valve)'}, - {modelPath:'drc_practice_wheel_valve_large_wall', modelTitle:'Wall (Wheel Valve Large)'}, - {modelPath:'grey_wall', modelTitle:'Grey Wall'}, - {modelPath:'asphalt_plane', modelTitle:'Asphalt Plane'}, - {modelPath:'drc_practice_base_4x8', modelTitle:'Debris base'}, - {modelPath:'ground_plane', modelTitle:'Ground Plane'}, - {modelPath:'nist_maze_wall_120', modelTitle:'120 Maze Wall'}, - {modelPath:'nist_maze_wall_240', modelTitle:'240 Maze Wall'}, - {modelPath:'nist_maze_wall_triple_holes_120', modelTitle:'120 Maze Wall Triple Holes'}, - {modelPath:'nist_simple_ramp_120', modelTitle:'Simple Ramp'}, - {modelPath:'nist_stairs_120', modelTitle:'Stairs'} - ]}, - - {path:'kitchen', title:'Kitchen', - examplePath1:'saucepan', examplePath2:'beer', examplePath3:'bowl', models: - [ - {modelPath:'beer', modelTitle:'Beer'}, - {modelPath:'bowl', modelTitle:'Bowl'}, - {modelPath:'coke_can', modelTitle:'Coke Can'}, - {modelPath:'saucepan', modelTitle:'Saucepan'} - ]}, - - {path:'robocup', title:'Robocup', examplePath1:'robocup_3Dsim_ball', - examplePath2:'robocup14_spl_goal', examplePath3:'robocup09_spl_field', models: - [ - {modelPath:'robocup09_spl_field', modelTitle:'2009 SPL Field'}, - {modelPath:'robocup14_spl_field', modelTitle:'2014 SPL Field'}, - {modelPath:'robocup_3Dsim_field', modelTitle:'3D Sim. Field'}, - {modelPath:'robocup14_spl_goal', modelTitle:'SPL Goal'}, - {modelPath:'robocup_3Dsim_goal', modelTitle:'3D Sim. Goal'}, - {modelPath:'robocup_spl_ball', modelTitle:'SPL Ball'}, - {modelPath:'robocup_3Dsim_ball', modelTitle:'3D Sim. Ball'} - ]}, - - {path:'robots', title:'Robots', - examplePath1:'pioneer3at', examplePath2:'turtlebot', examplePath3:'pr2', models: - [ - {modelPath:'create', modelTitle:'Create'}, - {modelPath:'husky', modelTitle:'Husky'}, - {modelPath:'irobot_hand', modelTitle:'iRobot Hand'}, - {modelPath:'pioneer2dx', modelTitle:'Pioneer 2DX'}, - {modelPath:'pioneer3at', modelTitle:'Pioneer 3AT'}, - {modelPath:'pr2', modelTitle:'PR2'}, - {modelPath:'robonaut', modelTitle:'Robonaut'}, - {modelPath:'simple_arm', modelTitle:'Simple Arm'}, - {modelPath:'simple_arm_gripper', modelTitle:'Simple Arm and Gripper'}, - {modelPath:'simple_gripper', modelTitle:'Simple Gripper'}, - {modelPath:'turtlebot', modelTitle:'TurtleBot'}, - {modelPath:'youbot', modelTitle:'YouBot'} - ]}, - - {path:'sensors', title:'Sensors', - examplePath1:'camera', examplePath2:'hokuyo', examplePath3:'kinect', models: - [ - {modelPath:'camera', modelTitle:'Camera'}, - {modelPath:'stereo_camera', modelTitle:'Stereo Camera'}, - {modelPath:'hokuyo', modelTitle:'Hokuyo'}, - {modelPath:'kinect', modelTitle:'Kinect'} - ]}, - - {path:'street', title:'Street', examplePath1:'dumpster', - examplePath2:'drc_practice_angled_barrier_45', examplePath3:'fire_hydrant', models: - [ - {modelPath:'cinder_block', modelTitle:'Cinder Block'}, - {modelPath:'cinder_block_2', modelTitle:'Cinder Block 2'}, - {modelPath:'cinder_block_wide', modelTitle:'Cinder Block Wide'}, - {modelPath:'construction_barrel', modelTitle:'Construction Barrel'}, - {modelPath:'construction_cone', modelTitle:'Construction Cone'}, - {modelPath:'drc_practice_angled_barrier_45', modelTitle:'Angled Barrier 45'}, - {modelPath:'drc_practice_angled_barrier_135', modelTitle:'Angled Barrier 135'}, - {modelPath:'drc_practice_block_wall', modelTitle:'Block Wall'}, - {modelPath:'drc_practice_orange_jersey_barrier', modelTitle:'Jersey Barrier (Orange)'}, - {modelPath:'drc_practice_white_jersey_barrier', modelTitle:'Jersey Barrier (White)'}, - {modelPath:'drc_practice_truss', modelTitle:'Truss'}, - {modelPath:'drc_practice_yellow_parking_block', modelTitle:'Parking Block'}, - {modelPath:'dumpster', modelTitle:'Dumpster'}, - {modelPath:'fire_hydrant', modelTitle:'Fire Hydrant'}, - {modelPath:'jersey_barrier', modelTitle:'Jersey Barrier'}, - {modelPath:'lamp_post', modelTitle:'Lamp Post'}, - {modelPath:'mailbox', modelTitle:'Mailbox'}, - {modelPath:'mud_box', modelTitle:'Mud Box'}, - {modelPath:'nist_fiducial_barrel', modelTitle:'Fiducial Barrel'}, - {modelPath:'speed_limit_sign', modelTitle:'Speed Limit Sign'}, - {modelPath:'stop_sign', modelTitle:'Stop Sign'} - - ]}, - - {path:'tools', title:'Tools', examplePath1:'hammer', - examplePath2:'polaris_ranger_ev', examplePath3:'cordless_drill', models: - [ - {modelPath:'cordless_drill', modelTitle:'Cordless Drill'}, - {modelPath:'fire_hose_long', modelTitle:'Fire Hose'}, - {modelPath:'fire_hose_long_curled', modelTitle:'Fire Hose Long Curled'}, - {modelPath:'hammer', modelTitle:'Hammer'}, - {modelPath:'monkey_wrench', modelTitle:'Monkey Wrench'}, - {modelPath:'polaris_ranger_ev', modelTitle:'Polaris Ranger EV'}, - {modelPath:'polaris_ranger_xp900', modelTitle:'Polaris Ranger XP900'}, - {modelPath:'polaris_ranger_xp900_no_roll_cage', modelTitle:'Polaris Ranger without roll cage'}, - {modelPath:'utility_cart', modelTitle:'Utility Cart'} - ]}, - - {path:'misc', title:'Misc.', examplePath1:'brick_box_3x1x3', - examplePath2:'drc_practice_4x4x20', examplePath3:'double_pendulum_with_base', models: - [ - {modelPath:'double_pendulum_with_base', modelTitle:'Double Pendulum With Base'}, - {modelPath:'breakable_test', modelTitle:'Breakable_test'}, - {modelPath:'brick_box_3x1x3', modelTitle:'Brick Box 3x1x3'}, - {modelPath:'cube_20k', modelTitle:'Cube 20k'}, - {modelPath:'drc_practice_2x4', modelTitle:'2x4 Lumber'}, - {modelPath:'drc_practice_2x6', modelTitle:'2x6 Lumber'}, - {modelPath:'drc_practice_4x4x20', modelTitle:'4x4x20 Lumber'}, - {modelPath:'drc_practice_4x4x40', modelTitle:'4x4x40 Lumber'}, - {modelPath:'drc_practice_blue_cylinder', modelTitle:'Blue Cylinder'}, - {modelPath:'drc_practice_wood_slats', modelTitle:'Wood Slats'}, - {modelPath:'nist_elevated_floor_120', modelTitle:'Elevated Floor 120'} + {modelPath:'library_model', modelTitle:'Library'}, + {modelPath:'hosta_potted_plant', modelTitle:'Hosta Plant'}, + {modelPath:'vr_lamp', modelTitle:'Stand Lamp'}, + {modelPath:'vr_screen', modelTitle:'Virtual Screen'}, + {modelPath:'viz_poster', modelTitle:'Poster 1'}, + {modelPath:'viz_poster_2', modelTitle:'Poster 2'} ]} ]; @@ -187,7 +42,7 @@ $(function() //Initialize if ('ontouchstart' in window || 'onmsgesturechange' in window) { - $('body').addClass('isTouchDevice'); + $('#gz3d-body').addClass('isTouchDevice'); } // Toggle items @@ -487,6 +342,14 @@ $(function() guiEvents.emit('show_orbit_indicator'); guiEvents.emit('closeTabs', false); }); + $('#view-shadows').click(function() + { + guiEvents.emit('show_shadows', 'toggle'); + }); + $('#view-camera-sensors').click(function() + { + guiEvents.emit('show_camera_sensors', 'toggle'); + }); $( '#snap-to-grid' ).click(function() { guiEvents.emit('snap_to_grid'); guiEvents.emit('closeTabs', false); @@ -501,13 +364,12 @@ $(function() }); // Disable Esc key to close panel - $('body').on('keyup', function(event) - { - if (event.which === 27) - { - return false; - } - }); + $('#gz3d-body').on('keyup', function(event) { + if (event.which === 27) + { + return false; + } + }); // Object menu $( '#view-transparent' ).click(function() { @@ -835,6 +697,73 @@ GZ3D.Gui.prototype.init = function() } ); + guiEvents.on('show_shadows', function(option) + { + if (option === 'show') + { + //that.emitter.emit('setShadows', true); + that.scene.setShadowMaps(true); + } + else if (option === 'hide') + { + //that.emitter.emit('setShadows', false); + that.scene.setShadowMaps(false); + } + else if (option === 'toggle') + { + var shadowsEnabled = that.scene.renderer.shadowMapEnabled; + //that.emitter.emit('setShadows', !shadowsEnabled); + that.scene.setShadowMaps(!shadowsEnabled); + } + + if(!that.scene.renderer.shadowMapEnabled) + { + $('#view-shadows').buttonMarkup({icon: 'false'}); + guiEvents.emit('notification_popup','Disabling shadows'); + } + else + { + $('#view-shadows').buttonMarkup({icon: 'check'}); + guiEvents.emit('notification_popup','Enabling shadows'); + } + } + ); + + guiEvents.on('show_camera_sensors', function(option) + { + var camerasShown = false; + if (option === 'show') + { + that.scene.viewManager.showCameras(true); + } + else if (option === 'hide') + { + that.scene.viewManager.showCameras(false); + } + else if (option === 'toggle') + { + if (that.scene.viewManager.views.length > 1) { + camerasShown = that.scene.viewManager.views[1].active; + that.scene.viewManager.showCameras(!camerasShown); + } + } + + if (that.scene.viewManager.views.length > 1) { + camerasShown = that.scene.viewManager.views[1].active; + } + if(!camerasShown) + { + $('#view-camera-sensors').buttonMarkup({icon: 'false'}); + guiEvents.emit('notification_popup','Disabling camera views'); + } + else + { + $('#view-camera-sensors').buttonMarkup({icon: 'check'}); + guiEvents.emit('notification_popup','Enabling camera views'); + } + } + ); + guiEvents.on('pause', function(paused) { that.emitter.emit('pause', paused); @@ -1077,7 +1006,7 @@ GZ3D.Gui.prototype.init = function() $( '#notification-popup' ).html(' '+notification+' '); $( '#notification-popup' ).popup('open', { y:window.innerHeight-50}); - + if (duration === undefined) { duration = 2000; @@ -1136,7 +1065,8 @@ GZ3D.Gui.prototype.init = function() lastOpenMenu[parentId] = id; $('.leftPanels').hide(); - $('#'+id).show(); + //$('#'+id).show(); //defaults as flex, but block is needed + $('#'+id).css('display','block'); $('.tab').css('border-left-color', tabColors.unselected); $('#'+parentId+'Tab').css('border-left-color', tabColors.selected); @@ -1605,17 +1535,20 @@ GZ3D.Gui.prototype.setModelStats = function(stats, action) return e.shortName === LinkShortName; }); - if (link[0].self_collide) + if (link[0]) { - link[0].self_collide = this.trueOrFalse(stats.link[0].self_collide); - } - if (link[0].gravity) - { - link[0].gravity = this.trueOrFalse(stats.link[0].gravity); - } - if (link[0].kinematic) - { - link[0].kinematic = this.trueOrFalse(stats.link[0].kinematic); + if (link[0].self_collide) + { + link[0].self_collide = this.trueOrFalse(stats.link[0].self_collide); + } + if (link[0].gravity) + { + link[0].gravity = this.trueOrFalse(stats.link[0].gravity); + } + if (link[0].kinematic) + { + link[0].kinematic = this.trueOrFalse(stats.link[0].kinematic); + } } } @@ -1692,10 +1625,10 @@ GZ3D.Gui.prototype.setLightStats = function(stats, action) var thumbnail; switch(type) { - case 2: + case GZ3D.LIGHT_SPOT: thumbnail = 'style/images/spotlight.png'; break; - case 3: + case GZ3D.LIGHT_DIRECTIONAL: thumbnail = 'style/images/directionallight.png'; break; default: @@ -1797,7 +1730,10 @@ GZ3D.Gui.prototype.findModelThumbnail = function(instanceName) GZ3D.Gui.prototype.updateStats = function() { var tree = angular.element($('#treeMenu')).scope(); - tree.updateStats(); + if (typeof(tree) !== 'undefined' && typeof tree.updateStats !== 'undefined') + { + tree.updateStats(); + } }; /** @@ -1922,10 +1858,12 @@ GZ3D.Gui.prototype.formatStats = function(stats) colorHex = {}; for (comp in diffuse) { - colorHex[comp] = diffuse[comp].toString(16); - if (colorHex[comp].length === 1) - { - colorHex[comp] = '0' + colorHex[comp]; + if (diffuse.hasOwnProperty(comp)) { + colorHex[comp] = diffuse[comp].toString(16); + if (colorHex[comp].length === 1) + { + colorHex[comp] = '0' + colorHex[comp]; + } } } color.diffuse = '#' + colorHex['r'] + colorHex['g'] + colorHex['b']; @@ -1938,10 +1876,12 @@ GZ3D.Gui.prototype.formatStats = function(stats) colorHex = {}; for (comp in specular) { - colorHex[comp] = specular[comp].toString(16); - if (colorHex[comp].length === 1) - { - colorHex[comp] = '0' + colorHex[comp]; + if (specular.hasOwnProperty(comp)) { + colorHex[comp] = specular[comp].toString(16); + if (colorHex[comp].length === 1) + { + colorHex[comp] = '0' + colorHex[comp]; + } } } color.specular = '#' + colorHex['r'] + colorHex['g'] + colorHex['b']; diff --git a/gz3d/src/gziface.js b/gz3d/src/gziface.js index e97d0e54ff7413747d719c3a07c153bd9b76a1d8..5e28028356bef6362bd97774cdd8f4bd7f5e525c 100644 --- a/gz3d/src/gziface.js +++ b/gz3d/src/gziface.js @@ -1,5 +1,7 @@ //var GAZEBO_MODEL_DATABASE_URI='http://gazebosim.org/models'; +THREE.ImageUtils.crossOrigin = 'anonymous'; // needed to allow cross-origin loading of textures + GZ3D.GZIface = function(scene, gui) { this.scene = scene; @@ -15,6 +17,13 @@ GZ3D.GZIface = function(scene, gui) this.numConnectionTrials = 0; this.maxConnectionTrials = 30; // try to connect 30 times this.timeToSleepBtwTrials = 1000; // wait 1 second between connection trials + + this.assetProgressData = {}; + this.assetProgressData.assets = []; + this.assetProgressData.prepared = false; + this.assetProgressCallback = undefined; + + this.webSocketConnectionCallbacks = []; }; GZ3D.GZIface.prototype.init = function() @@ -25,11 +34,35 @@ GZ3D.GZIface.prototype.init = function() this.connect(); }; +GZ3D.GZIface.prototype.setAssetProgressCallback = function(callback) +{ + this.assetProgressCallback = callback; +}; + +GZ3D.GZIface.prototype.registerWebSocketConnectionCallback = function(callback) { + this.webSocketConnectionCallbacks.push(callback); +}; + GZ3D.GZIface.prototype.connect = function() { // connect to websocket + var url = GZ3D.webSocketUrl; + if (!localStorage.getItem('localmode.forceuser')) { + var token = []; + if (localStorage.getItem('tokens-neurorobotics-ui@https://services.humanbrainproject.eu/oidc')) { + try { + token = JSON.parse(localStorage.getItem('tokens-neurorobotics-ui@https://services.humanbrainproject.eu/oidc')); + } catch(e) { + token[0] = { access_token : 'notoken' }; + } + url = url + '/?token=' + token[0].access_token; + } else { + url = 'ws://' + location.hostname + ':7681'; + } + } + this.webSocket = new ROSLIB.Ros({ - url : 'ws://' + location.hostname + ':7681' + url : url }); var that = this; @@ -39,6 +72,9 @@ GZ3D.GZIface.prototype.connect = function() this.webSocket.on('error', function() { that.onError(); }); + this.webSocket.on('close', function() { + console.log('Connection closed to websocket server: ' + that.webSocket.socket.url); + }); this.numConnectionTrials++; }; @@ -63,6 +99,8 @@ GZ3D.GZIface.prototype.onError = function() GZ3D.GZIface.prototype.onConnected = function() { + console.log('Connected to websocket server: ' + this.webSocket.socket.url); + this.isConnected = true; this.emitter.emit('connection'); @@ -84,6 +122,11 @@ GZ3D.GZIface.prototype.onConnected = function() setInterval(publishHeartbeat, 5000); + // call all the registered callbacks since we are connected now + this.webSocketConnectionCallbacks.forEach(function(callback) { + callback(); + }); + var statusTopic = new ROSLIB.Topic({ ros: this.webSocket, name: '~/status', @@ -129,7 +172,7 @@ GZ3D.GZIface.prototype.onConnected = function() if (message.grid === true) { - this.gui.guiEvents.emit('show_grid', 'show'); + //this.gui.guiEvents.emit('show_grid', 'show'); // do not show grid by default for now } if (message.ambient) @@ -169,6 +212,7 @@ GZ3D.GZIface.prototype.onConnected = function() this.gui.setModelStats(model, 'update'); } + this.assetProgressData.prepared = true; this.gui.setSceneStats(message); this.sceneTopic.unsubscribe(); }; @@ -277,6 +321,8 @@ GZ3D.GZIface.prototype.onConnected = function() } i++; } + } else { + this.updateModelFromMsg(this.scene.getByName(message.name), message); } this.gui.setModelStats(message, 'update'); }; @@ -399,7 +445,7 @@ GZ3D.GZIface.prototype.onConnected = function() messageType : 'light', }); - var publishEntityModify = function(entity) + var createEntityModifyMessage = function(entity) { var matrix = entity.matrixWorld; var translation = new THREE.Vector3(); @@ -426,43 +472,89 @@ GZ3D.GZIface.prototype.onConnected = function() z: quaternion.z } }; - if (entity.children[0] && - entity.children[0] instanceof THREE.Light) + return entityMsg; + }; + + /* + TODO: (Sandro Weber) + The following functions are used to change all lights at the same time through the light slider of the NRP. + Gazebo only knows attenuation factors, not intensity, so we manipulate diffuse color for now. + Probably replaced / revamped after a dedicated edit tab is introduced to allow manipulation of lights directly. + */ + var createEntityModifyMessageWithLight = function(entity, diffuse) + { + var entityMsg = createEntityModifyMessage(entity); + + var lightObj = entity.children[0]; + + if (diffuse === undefined) { + diffuse = lightObj.color; + } + entityMsg.diffuse = { - entityMsg.diffuse = - { - r: entity.children[0].color.r, - g: entity.children[0].color.g, - b: entity.children[0].color.b - }; - entityMsg.specular = - { - r: entity.serverProperties.specular.r, - g: entity.serverProperties.specular.g, - b: entity.serverProperties.specular.b - }; - entityMsg.direction = entity.direction; - entityMsg.range = entity.children[0].distance; - entityMsg.attenuation_constant = entity.serverProperties.attenuation_constant; - entityMsg.attenuation_linear = entity.serverProperties.attenuation_linear; - entityMsg.attenuation_quadratic = entity.serverProperties.attenuation_quadratic; - - that.lightModifyTopic.publish(entityMsg); + r: diffuse.r, + g: diffuse.g, + b: diffuse.b + }; + entityMsg.specular = + { + r: entity.serverProperties.specular.r, + g: entity.serverProperties.specular.g, + b: entity.serverProperties.specular.b + }; + entityMsg.direction = entity.direction; + entityMsg.range = lightObj.distance; + + entityMsg.attenuation_constant = entity.serverProperties.attenuation_constant; + entityMsg.attenuation_linear = entity.serverProperties.attenuation_linear; + entityMsg.attenuation_quadratic = entity.serverProperties.attenuation_quadratic; + + return entityMsg; + }; + + var publishEntityModify = function(entity) + { + var lightObj = entity.children[0]; + if (lightObj && lightObj instanceof THREE.Light) + { + that.lightModifyTopic.publish(createEntityModifyMessageWithLight(entity, undefined)); } else { - that.modelModifyTopic.publish(entityMsg); + that.modelModifyTopic.publish(createEntityModifyMessage(entity)); } }; this.scene.emitter.on('entityChanged', publishEntityModify); - // Link messages - for modifying links - this.linkModifyTopic = new ROSLIB.Topic({ - ros : this.webSocket, - name : '~/link', - messageType : 'link', - }); + var publishLightModify = function(ratio) + { + var lights = []; + that.scene.scene.traverse(function(node) { + if (node instanceof THREE.Light) { + lights.push(node); + } + }); + + var numberOfLights = lights.length; + for (var i = 0; i < numberOfLights; i+=1) { + if( lights[i] instanceof THREE.AmbientLight ) { // we don't change ambient lights + continue; + } + var entity = that.scene.getByName(lights[i].name); + var newDiffuse = new THREE.Color(); + newDiffuse.r = THREE.Math.clamp(ratio * entity.serverProperties.initial.diffuse.r, 0, 1); + newDiffuse.g = THREE.Math.clamp(ratio * entity.serverProperties.initial.diffuse.g, 0, 1); + newDiffuse.b = THREE.Math.clamp(ratio * entity.serverProperties.initial.diffuse.b, 0, 1); + + that.lightModifyTopic.publish(createEntityModifyMessageWithLight(entity, newDiffuse)); + } + }; + + this.scene.emitter.on('lightChanged', publishLightModify); + /* + end of light slider change functions + */ var publishLinkModify = function(entity, type) { @@ -729,6 +821,16 @@ GZ3D.GZIface.prototype.createModelFromMsg = function(model) } } } + + for (var i = 0; i < link.sensor.length; ++i) { + var sensor = link.sensor[i]; + + var sensorObj = this.createSensorFromMsg(sensor); + if (sensorObj && !sensorObj.parent) + { + linkObj.add(sensorObj); + } + } } if (model.joint) { @@ -738,6 +840,24 @@ GZ3D.GZIface.prototype.createModelFromMsg = function(model) return modelObj; }; +// This method uses code also to be found at GZ3D.GZIface.prototype.createModelFromMsg. +// Currently not everything is handled for an update, but this method was introduced to handle +// the updates of colors of objects; if there should be more functionality one could consider +// merging the two methods and extracting the different things to parameters (or any other means +// of configuration). +GZ3D.GZIface.prototype.updateModelFromMsg = function (modelObj, modelMsg) { + for (var j = 0; j < modelMsg.link.length; ++j) { + var link = modelMsg.link[j]; + var linkObj = modelObj.children[j]; + + for (var k = 0; k < link.visual.length; ++k) { + var visual = link.visual[k]; + var visualObj = linkObj.getObjectByName(visual.name); + this.updateVisualFromMsg(visualObj, visual); + } + } +}; + GZ3D.GZIface.prototype.createVisualFromMsg = function(visual) { if (visual.geometry) @@ -760,21 +880,32 @@ GZ3D.GZIface.prototype.createVisualFromMsg = function(visual) } }; +GZ3D.GZIface.prototype.updateVisualFromMsg = function (visualObj, visual) { + if (visual.geometry) { + var obj = visualObj.children[0]; + var mat = this.parseMaterial(visual.material); + + if (obj && mat) { + this.scene.setMaterial(obj, mat); + } + } +}; + GZ3D.GZIface.prototype.createLightFromMsg = function(light) { var obj, range, direction; - if (light.type === 1) + if (light.type === this.scene.LIGHT_POINT) { direction = null; range = light.range; } - else if (light.type === 2) + else if (light.type === this.scene.LIGHT_SPOT) { direction = light.direction; range = light.range; } - else if (light.type === 3) + else if (light.type === this.scene.LIGHT_DIRECTIONAL) { direction = light.direction; range = null; @@ -812,9 +943,66 @@ GZ3D.GZIface.prototype.createRoadsFromMsg = function(roads) return roadObj; }; +GZ3D.GZIface.prototype.createSensorFromMsg = function(sensor) +{ + var sensorObj = new THREE.Object3D(); + sensorObj.name = sensor.name; + + if (sensor.pose) { + this.scene.setPose(sensorObj, sensor.pose.position, sensor.pose.orientation); + } + + if (sensor.type === 'camera') { + // if we have a camera sensor we have a potential view that could be rendered + // camera parameters are not published by gazebo right now, so hardcoded until fixed + var displayParams = { + left: '5%', + top: '5%', + width: '20%', + height: '20%', + adjustable: true + }; + var cameraParams = { + width: 960, + height: 600, + fov: 60, + near: 0.1, + far: 100 + }; + var viewName = 'view_' + sensor.name; + var view = this.scene.viewManager.createView(viewName, displayParams, cameraParams); + if (!view) { + console.error('GZ3D.GZIface.createSensorFromMsg() - failed to create view ' + viewName); + return; + } + + // There is a problem with the width and height; it should be read from the "sensor" object. Also see: + // https://bitbucket.org/osrf/gazebo/issues/1663/sensor-camera-elements-from-sdf-not-being + + // camera sensors defined in gazebo .sdf seem to look along positive x axis, so need to adjust the rotation here + view.camera.rotateOnAxis(new THREE.Vector3(0, 1, 0), -Math.PI / 2); + view.camera.rotateOnAxis(new THREE.Vector3(0, 0, 1), -Math.PI / 2); + + // set view inactive and hide at start + this.scene.viewManager.setViewVisibility(view, false); + + // visualization - Deactivated since it causes the robot to be very big and the user + // can't barely select other objects on the scene. Reactivate for debug purposes ! + // var cameraHelper = new THREE.CameraHelper(view.camera); + // view.camera.add( cameraHelper ); + + view.type = sensor.type; + console.log('view type: ' + view.type); + + sensorObj.add(view.camera); + } + + return sensorObj; +}; + GZ3D.GZIface.prototype.parseUri = function(uri) { - var uriPath = 'assets'; + var uriPath = GZ3D.assetsPath; var idx = uri.indexOf('://'); if (idx > 0) { @@ -826,7 +1014,7 @@ GZ3D.GZIface.prototype.parseUri = function(uri) GZ3D.GZIface.prototype.createGeom = function(geom, material, parent) { var obj; - var uriPath = 'assets'; + var uriPath = GZ3D.assetsPath; var that = this; var mat = this.parseMaterial(material); if (geom.box) @@ -906,26 +1094,48 @@ GZ3D.GZIface.prototype.createGeom = function(geom, material, parent) var modelUri = uriPath + '/' + modelName; // Use coarse version on touch devices - if (modelUri.indexOf('.dae') !== -1 && isTouchDevice) + if (modelUri.indexOf('.dae') !== -1 /*&& isTouchDevice*/) // Modified for HBP, we do use coarse models all the time { modelUri = modelUri.substring(0,modelUri.indexOf('.dae')); - var checkModel = new XMLHttpRequest(); - checkModel.open('HEAD', modelUri+'_coarse.dae', false); - checkModel.send(); - if (checkModel.status === 404) + if(modelUri.indexOf('_coarse') !== -1) //dae is already a coarse model { modelUri = modelUri+'.dae'; } - else - { - modelUri = modelUri+'_coarse.dae'; + else { // check if a coarse version is available + var checkModel = new XMLHttpRequest(); + // We use a double technique to disable the cache for these requests: + // 1. We create a custom url by adding the time as a parameter. + // 2. We add the If-Modified-Since header with a date far in the future (end of the HBP project) + // Since browsers and servers vary in their behaviour, we use both of these tricks. + // PS: These requests do not load the dae files, they just verify if they exist on the server + // so that we can choose between coarse or reqular models. + checkModel.open('HEAD', modelUri+'_coarse.dae?timestamp=' + new Date().getTime(), false); + checkModel.setRequestHeader('If-Modified-Since', 'Sat, 1 Jan 2026 00:00:00 GMT'); + + try { checkModel.send(); } catch(err) { console.log(modelUri + ': no coarse version'); } + + if (checkModel.status === 404) { + modelUri = modelUri+'.dae'; + } + else { + modelUri = modelUri+'_coarse.dae'; + } } } var materialName = parent.name + '::' + modelUri; this.entityMaterial[materialName] = mat; + // Progress update: Add this asset to the assetProgressArray + var element = {}; + element.id = parent.name; + element.url = modelUri; + element.progress = 0; + element.totalSize = 0; + element.done = false; + this.assetProgressData.assets.push(element); + this.scene.loadMesh(modelUri, submesh, centerSubmesh, function(dae) { if (that.entityMaterial[materialName]) @@ -944,6 +1154,19 @@ GZ3D.GZIface.prototype.createGeom = function(geom, material, parent) } parent.add(dae); loadGeom(parent); + + // Progress update: execute callback + element.done = true; + if (that.assetProgressCallback) { + that.assetProgressCallback(that.assetProgressData); + } + }, function(progress){ + element.progress = progress.loaded; + element.totalSize = progress.total; + element.error = progress.error; + if (that.assetProgressCallback) { + that.assetProgressCallback(that.assetProgressData); + } }); } } @@ -1019,7 +1242,7 @@ GZ3D.GZIface.prototype.createGeom = function(geom, material, parent) allChildren[c].castShadow = false; allChildren[c].receiveShadow = false; - allChildren[c].visible = this.scene.showCollisions; + allChildren[c].visible = that.scene.showCollisions; } } } @@ -1077,7 +1300,7 @@ GZ3D.GZIface.prototype.parseMaterial = function(material) return null; } - var uriPath = 'assets'; + var uriPath = GZ3D.assetsPath;//'assets'; var texture; var normalMap; var textureUri; diff --git a/gz3d/src/gzmanipulator.js b/gz3d/src/gzmanipulator.js index 84e9cadfbd303cdb19667bda4075bdf3d3c0542d..e48ee73f46322a29e2d2b9872b605566ab35a64e 100644 --- a/gz3d/src/gzmanipulator.js +++ b/gz3d/src/gzmanipulator.js @@ -520,10 +520,10 @@ GZ3D.Manipulator = function(camera, mobile, domElement, doc) } this.object.updateMatrixWorld(); - worldPosition.getPositionFromMatrix(this.object.matrixWorld); + worldPosition.setFromMatrixPosition(this.object.matrixWorld); this.camera.updateMatrixWorld(); - camPosition.getPositionFromMatrix(this.camera.matrixWorld); + camPosition.setFromMatrixPosition(this.camera.matrixWorld); scale = worldPosition.distanceTo(camPosition) / 6 * this.scale; this.gizmo.position.copy(worldPosition); @@ -775,7 +775,7 @@ GZ3D.Manipulator = function(camera, mobile, domElement, doc) worldRotationMatrix.extractRotation(scope.object.matrixWorld); parentRotationMatrix.extractRotation(scope.object.parent.matrixWorld); - parentScale.getScaleFromMatrix(tempMatrix.getInverse( + parentScale.setFromMatrixScale(tempMatrix.getInverse( scope.object.parent.matrixWorld)); offset.copy(planeIntersect.point); @@ -890,7 +890,7 @@ GZ3D.Manipulator = function(camera, mobile, domElement, doc) parentRotationMatrix.extractRotation( scope.object.parent.matrixWorld); - parentScale.getScaleFromMatrix(tempMatrix.getInverse( + parentScale.setFromMatrixScale(tempMatrix.getInverse( scope.object.parent.matrixWorld)); offset.copy(planeIntersect.point); diff --git a/gz3d/src/gzmultiview.js b/gz3d/src/gzmultiview.js new file mode 100644 index 0000000000000000000000000000000000000000..16aaa50efd37119940004316ea4d95248e4dd786 --- /dev/null +++ b/gz3d/src/gzmultiview.js @@ -0,0 +1,281 @@ +/** + * Created by Sandro Weber (webers@in.tum.de). + */ + +GZ3D.MULTIVIEW_MAX_VIEW_COUNT = 10; + +/** + * GZ3D.MULTIVIEW_RENDER_VIEWPORTS uses view containers as transparent references to determine viewports for rendering (one single canvas). + * This is broken in combination with shadowmaps at the moment. See renderToViewport() method. + * + * @type {number} + */ +GZ3D.MULTIVIEW_RENDER_VIEWPORTS = 1; +/** + * GZ3D.MULTIVIEW_RENDER_COPY2CANVAS renders views offscreen then copies into the view containers (multiple separate canvases). + * @type {number} + */ +GZ3D.MULTIVIEW_RENDER_COPY2CANVAS = 2; + +GZ3D.MultiView = function(gz3dScene, mainContainer, callbackCreateRenderContainer) +{ + this.gz3dScene = gz3dScene; + this.mainContainer = mainContainer; + this.createRenderContainerCallback = callbackCreateRenderContainer; + + this.init(); +}; + +GZ3D.MultiView.prototype.init = function() +{ + this.views = []; + + this.mainContainer.style.zIndex = 0; + + this.renderMethod = GZ3D.MULTIVIEW_RENDER_COPY2CANVAS; + if (this.renderMethod === GZ3D.MULTIVIEW_RENDER_VIEWPORTS) { + this.mainContainer.appendChild(this.gz3dScene.getDomElement()); + } +}; + +GZ3D.MultiView.prototype.setCallbackCreateRenderContainer = function(callback) +{ + this.createRenderContainerCallback = callback; +}; + +/** + * + * @param name + * @param displayParams {left, top, width, height, zIndex, adjustable} + * @param cameraParams {width, height, fov, near, far} + */ +GZ3D.MultiView.prototype.createView = function(name, displayParams, cameraParams) +{ + if (this.getViewByName(name) !== undefined) { + console.error('GZ3D.MultiView.createView() - a view of that name already exists (' + name + ')'); + return null; + } + + if (this.views.length >= this.MULTIVIEW_MAX_VIEW_COUNT) { + console.warn('GZ3D.MultiView.createView() - creating new view will exceed MULTIVIEW_MAX_VIEW_COUNT(' + this.MULTIVIEW_MAX_VIEW_COUNT + '). This may cause z-ordering issues.'); + } + + var container = this.createViewContainer(displayParams, name); + if (container === null) { + return; + } + + // camera + var camera = new THREE.PerspectiveCamera(cameraParams.fov, cameraParams.width / cameraParams.height, cameraParams.near, cameraParams.far); + camera.name = name; + + // assemble view + var view = { + name: name, + active: true, + container: container, + camera: camera + }; + + this.views.push(view); + this.mainContainer.appendChild(view.container); + + return view; +}; + +GZ3D.MultiView.prototype.createViewContainer = function(displayParams, name) +{ + if (this.createRenderContainerCallback === undefined) { + console.error('GZ3D.MultiView.createViewContainer() - no callback for creating view reference container defined'); + return null; + } + + // container div + var viewContainer = this.createRenderContainerCallback(displayParams.adjustable, name); + if (!viewContainer) { + console.error('GZ3D.MultiView.createViewContainer() - could not create view container via callback'); + return null; + } + + // positioning + viewContainer.style.position = 'absolute'; + viewContainer.style.left = displayParams.left; + viewContainer.style.top = displayParams.top; + // There is a problem with the width and height; it should be read from the "sensor" object. Also see: + // https://bitbucket.org/osrf/gazebo/issues/1663/sensor-camera-elements-from-sdf-not-being + // Here we use (preliminary) percentual width. + viewContainer.style.width = displayParams.width; + viewContainer.style.height = displayParams.height; + + // We set 50px as a min-width for now and set the min height accordingly + viewContainer.style.minWidth = '50px'; + viewContainer.style.minHeight = (50 * (parseInt(displayParams.width, 10)/parseInt(displayParams.height, 10))) + 'px'; + viewContainer.style.maxWidth = '100%'; + viewContainer.style.maxHeight = '100%'; + + // transparent view-container so we can render viewport with one renderer in the same context + // view-container is only taken as reference for viewport + viewContainer.style.boxShadow = '0px 0px 0px 3px rgba(0,0,0,0.3)'; + viewContainer.style.borderRadius = '2px'; + viewContainer.style.background = 'rgba(0,0,0,0)'; + + if (this.renderMethod === GZ3D.MULTIVIEW_RENDER_COPY2CANVAS) { + // canvas + viewContainer.canvas = document.createElement('canvas'); + viewContainer.appendChild( viewContainer.canvas ); + viewContainer.canvas.style.width = '100%'; + viewContainer.canvas.style.height = '100%'; + } + + // z-index + var zIndexTop = parseInt(this.mainContainer.style.zIndex, 10) + this.views.length + 1; + viewContainer.style.zIndex = (displayParams.zIndex !== undefined) ? displayParams.zIndex : zIndexTop; + + return viewContainer; +}; + +GZ3D.MultiView.prototype.getViewByName = function(name) +{ + for (var i = 0; i < this.views.length; i = i+1) { + if (this.views[i].name === name) { + return this.views[i]; + } + } + + return undefined; +}; + +GZ3D.MultiView.prototype.setViewVisibility = function(view, visible) +{ + view.active = visible; + if (view.active) { + view.container.style.visibility = 'visible'; + } else { + view.container.style.visibility = 'hidden'; + } +}; + +GZ3D.MultiView.prototype.updateCamera = function(view) +{ + view.camera.aspect = view.container.clientWidth / view.container.clientHeight; + view.camera.updateProjectionMatrix(); +}; + +GZ3D.MultiView.prototype.getViewport = function(view) +{ + var viewport = { + x: view.container.offsetLeft, + y: this.mainContainer.clientHeight - view.container.clientHeight - view.container.offsetTop, + w: view.container.clientWidth, + h: view.container.clientHeight + }; + + return viewport; +}; + +GZ3D.MultiView.prototype.setWindowSize = function(width, height) +{ + +}; + +GZ3D.MultiView.prototype.renderViews = function() +{ + // sort views into rendering order + this.views.sort(function(a, b) { + return a.container.style.zIndex - b.container.style.zIndex; + }); + + for (var i = 0, l = this.views.length; i < l; i = i+1) { + var view = this.views[i]; + + if (view.active) { + switch(this.renderMethod) { + case GZ3D.MULTIVIEW_RENDER_VIEWPORTS: + this.renderToViewport(view); + break; + case GZ3D.MULTIVIEW_RENDER_COPY2CANVAS: + this.renderAndCopyToCanvas(view); + break; + } + } + } +}; + +/** + * !IMPORTANT! + * https://github.com/mrdoob/three.js/issues/3532 + * This is at the moment not a good idea, see issue above. ScissorTest will scramble shadowmaps. + * + * @param view + */ +GZ3D.MultiView.prototype.renderToViewport = function(view) +{ + this.updateCamera(view); //TODO: better solution with resize callback, also adjust camera helper + + var webglRenderer = this.gz3dScene.renderer; + + var viewport = this.getViewport(view); + webglRenderer.setViewport( viewport.x, viewport.y, viewport.w, viewport.h ); + webglRenderer.setScissor( viewport.x, viewport.y, viewport.w, viewport.h ); + webglRenderer.enableScissorTest ( true ); + + if (this.gz3dScene.effectsEnabled) { + this.gz3dScene.scene.overrideMaterial = this.depthMaterial; + this.gz3dScene.renderer.render(this.gz3dScene.scene, view.camera, this.depthTarget); + this.gz3dScene.scene.overrideMaterial = null; + this.gz3dScene.composer.render(); + } else { + webglRenderer.render(this.gz3dScene.scene, view.camera); + } +}; + +GZ3D.MultiView.prototype.renderAndCopyToCanvas = function(view) +{ + this.updateCamera(view); //TODO: better solution with resize callback, also adjust camera helper + + var webglRenderer = this.gz3dScene.renderer; + + var width = view.container.canvas.clientWidth; + var height = view.container.canvas.clientHeight; + view.container.canvas.width = width; + view.container.canvas.height = height; + + if (webglRenderer.context.canvas.width < width) { + webglRenderer.context.canvas.width = width; + } + if (webglRenderer.context.canvas.height < height) { + webglRenderer.context.canvas.height = height; + } + webglRenderer.setViewport( 0, 0, width, height ); + + if (this.gz3dScene.effectsEnabled) { + this.gz3dScene.scene.overrideMaterial = this.depthMaterial; + this.gz3dScene.renderer.render(this.gz3dScene.scene, view.camera, this.depthTarget); + this.gz3dScene.scene.overrideMaterial = null; + this.gz3dScene.composer.render(); + } else { + webglRenderer.render(this.gz3dScene.scene, view.camera); + } + + // copy rendered image over to view canvas + var srcX = 0; + var srcY = webglRenderer.context.canvas.height - height + 1; + var dstX = 0; + var dstY = 0; + // no cleaner solution right now, should be coming though: https://github.com/mrdoob/three.js/pull/6723#issuecomment-134129027 + // this kills performance a little ... + if ( srcX >= 0 && srcY >= 0 && width >= 1 && height >= 1 ) { + view.container.canvas.getContext('2d').drawImage( webglRenderer.context.canvas, + srcX, srcY, width, height, + dstX, dstY, width, height ); + } +}; + +GZ3D.MultiView.prototype.showCameras = function(show) +{ + for (var i = 0; i < this.views.length; i = i+1) { + if (this.views[i].type === 'camera') { + this.setViewVisibility(this.views[i], show); + } + } +}; \ No newline at end of file diff --git a/gz3d/src/gzscene.js b/gz3d/src/gzscene.js index d8a6c0943bdfec4c97378193999a00551369e1b8..fb41118728f91a7a9c8242d45a1033310f244da1 100644 --- a/gz3d/src/gzscene.js +++ b/gz3d/src/gzscene.js @@ -1,5 +1,3 @@ - - /** * The scene is where everything is placed, from objects, to lights and cameras. * @constructor @@ -9,6 +7,11 @@ GZ3D.Scene = function() this.init(); }; +GZ3D.Scene.prototype.LIGHT_POINT = 1; +GZ3D.Scene.prototype.LIGHT_SPOT = 2; +GZ3D.Scene.prototype.LIGHT_DIRECTIONAL = 3; +GZ3D.Scene.prototype.LIGHT_UNKNOWN = 4; + /** * Initialize scene */ @@ -32,16 +35,38 @@ GZ3D.Scene.prototype.init = function() this.renderer.setSize( window.innerWidth, window.innerHeight); // shadows - this.renderer.shadowMapEnabled = true; + this.renderer.shadowMapEnabled = false; this.renderer.shadowMapType = THREE.PCFSoftShadowMap; + this.container = document.getElementById( 'container' ); + + // create views manager + this.viewManager = new GZ3D.MultiView(this, this.container, function(adjustable, name){ + return document.createElement('div'); + }); + // create main view + var displayParams = { + left: '0px', + top: '0px', + width: '100%', + height: '100%', + adjustable: false + }; + var cameraParams = { + width: 960, + height: 600, + fov: 60, + near: 0.1, + far: 100 + }; + this.viewManager.createView('main_view', displayParams, cameraParams); + // lights this.ambient = new THREE.AmbientLight( 0x666666 ); this.scene.add(this.ambient); // camera - this.camera = new THREE.PerspectiveCamera( - 60, window.innerWidth / window.innerHeight, 0.1, 1000 ); + this.camera = this.viewManager.getViewByName('main_view').camera; this.defaultCameraPosition = new THREE.Vector3(0, -5, 5); this.resetView(); @@ -100,7 +125,12 @@ GZ3D.Scene.prototype.init = function() this.timeDown = null; - this.controls = new THREE.OrbitControls(this.camera); + var domElementForKeyBindings = document.getElementsByTagName('body')[0]; + this.controls = new THREE.FirstPersonControls(this.camera, this.container, domElementForKeyBindings); + if (this.controls instanceof THREE.FirstPersonControls) { + this.controls.movementSpeed = 0.2; + this.controls.lookSpeed = 0.005; + } if (this.controls.targetIndicator !== undefined) { this.scene.add(this.controls.targetIndicator); } @@ -469,7 +499,7 @@ GZ3D.Scene.prototype.onPointerDown = function(event) */ GZ3D.Scene.prototype.onPointerUp = function(event) { - event.preventDefault(); + //event.preventDefault(); // Clicks (<150ms) outside any models trigger view mode var millisecs = new Date().getTime(); @@ -673,7 +703,7 @@ GZ3D.Scene.prototype.getRayCastModel = function(pos, intersect) */ GZ3D.Scene.prototype.getDomElement = function() { - return this.renderer.domElement; + return this.container; }; /** @@ -703,17 +733,7 @@ GZ3D.Scene.prototype.render = function() this.modelManipulator.update(); this.radialMenu.update(); - if (this.effectsEnabled) - { - this.scene.overrideMaterial = this.depthMaterial; - this.renderer.render(this.scene, this.camera, this.depthTarget); - this.scene.overrideMaterial = null; - this.composer.render(); - } - else - { - this.renderer.render(this.scene, this.camera); - } + this.viewManager.renderViews(); }; /** @@ -727,6 +747,11 @@ GZ3D.Scene.prototype.setWindowSize = function(width, height) this.camera.updateProjectionMatrix(); this.renderer.setSize( width, height); + this.renderer.context.canvas.width = width; + this.renderer.context.canvas.height = height; + + this.viewManager.setWindowSize(width, height); + this.render(); }; @@ -915,6 +940,8 @@ GZ3D.Scene.prototype.createBox = function(width, height, depth) * @param {} attenuation_constant * @param {} attenuation_linear * @param {} attenuation_quadratic + * @param {} spot_angle + * @param {} spot_falloff * @returns {THREE.Object3D} */ GZ3D.Scene.prototype.createLight = function(type, diffuse, intensity, pose, @@ -943,6 +970,10 @@ GZ3D.Scene.prototype.createLight = function(type, diffuse, intensity, pose, specular = color.clone(); } + if (typeof(specular) === 'undefined') { + specular = 0xffffff; + } + var matrixWorld; if (pose) @@ -966,17 +997,17 @@ GZ3D.Scene.prototype.createLight = function(type, diffuse, intensity, pose, } var elements; - if (type === 1) + if (type === this.LIGHT_POINT) { elements = this.createPointLight(obj, diffuse, intensity, distance, cast_shadows); } - else if (type === 2) + else if (type === this.LIGHT_SPOT) { elements = this.createSpotLight(obj, diffuse, intensity, distance, cast_shadows, spot_angle, spot_falloff); } - else if (type === 3) + else if (type === this.LIGHT_DIRECTIONAL) { elements = this.createDirectionalLight(obj, diffuse, intensity, cast_shadows); @@ -1011,13 +1042,16 @@ GZ3D.Scene.prototype.createLight = function(type, diffuse, intensity, pose, obj.serverProperties.attenuation_constant = attenuation_constant; obj.serverProperties.attenuation_linear = attenuation_linear; obj.serverProperties.attenuation_quadratic = attenuation_quadratic; + obj.serverProperties.initial = {}; + obj.serverProperties.initial.diffuse = diffuse; - obj.add(lightObj); - obj.add(helper); - + helper.visible = false; lightObj.up = new THREE.Vector3(0,0,1); lightObj.shadowBias = -0.0005; + obj.add(lightObj); + obj.add(helper); + return obj; }; @@ -1484,7 +1518,7 @@ GZ3D.Scene.prototype.loadHeightmap = function(heights, width, height, * @param {function} callback */ GZ3D.Scene.prototype.loadMesh = function(uri, submesh, centerSubmesh, - callback) + callback, progressCallback) { var uriPath = uri.substring(0, uri.lastIndexOf('/')); var uriFile = uri.substring(uri.lastIndexOf('/') + 1); @@ -1492,7 +1526,7 @@ GZ3D.Scene.prototype.loadMesh = function(uri, submesh, centerSubmesh, // load urdf model if (uriFile.substr(-4).toLowerCase() === '.dae') { - return this.loadCollada(uri, submesh, centerSubmesh, callback); + return this.loadCollada(uri, submesh, centerSubmesh, callback, progressCallback); } else if (uriFile.substr(-5).toLowerCase() === '.urdf') { @@ -1538,7 +1572,7 @@ GZ3D.Scene.prototype.loadMesh = function(uri, submesh, centerSubmesh, * @param {function} callback */ GZ3D.Scene.prototype.loadCollada = function(uri, submesh, centerSubmesh, - callback) + callback, progressCallback) { var dae; var mesh = null; @@ -1561,6 +1595,7 @@ GZ3D.Scene.prototype.loadCollada = function(uri, submesh, centerSubmesh, var thatSubmesh = submesh; var thatCenterSubmesh = centerSubmesh; + var that = this; loader.load(uri, function(collada) { // check for a scale factor @@ -1572,13 +1607,22 @@ GZ3D.Scene.prototype.loadCollada = function(uri, submesh, centerSubmesh, dae = collada.scene; dae.updateMatrix(); - this.scene.prepareColladaMesh(dae); - this.scene.meshes[thatURI] = dae; + that.prepareColladaMesh(dae); + that.meshes[thatURI] = dae; dae = dae.clone(); - this.scene.useColladaSubMesh(dae, thatSubmesh, centerSubmesh); + that.useColladaSubMesh(dae, thatSubmesh, centerSubmesh); dae.name = uri; callback(dae); + },function(progress) { + if (progressCallback !== undefined) { + progress.error = false; + progressCallback(progress); + } + },function(){ + if (progressCallback !== undefined) { + progressCallback({ total: 0, loaded: 0, error: true }); + } }); }; @@ -1791,7 +1835,7 @@ GZ3D.Scene.prototype.setManipulationMode = function(mode) } else { - // Toggle manipulaion space (world / local) + // Toggle manipulation space (world / local) if (this.modelManipulator.mode === this.manipulationMode) { this.modelManipulator.space = @@ -2384,7 +2428,7 @@ GZ3D.Scene.prototype.updateLight = function(entity, msg) this.setPose(entity, msg.pose.position, msg.pose.orientation); entity.matrixWorldNeedsUpdate = true; - if (entity.direction) + if (entity.direction && lightObj.target) { dir = new THREE.Vector3(entity.direction.x, entity.direction.y, entity.direction.z); @@ -2426,6 +2470,18 @@ GZ3D.Scene.prototype.updateLight = function(entity, msg) entity.serverProperties.attenuation_quadratic = msg.attenuation_quadratic; lightObj.intensity = lightObj.intensity/(1+msg.attenuation_quadratic); } + if (msg.attenuation_linear && msg.attenuation_quadratic) + { + // equation taken from + // http://wiki.blender.org/index.php/Doc:2.6/Manual/Lighting/Lights/Light_Attenuation + var E = 1; + var D = 1; + var r = 1; + var L = msg.attenuation_linear; + var Q = msg.attenuation_quadratic; + lightObj.intensity = E*(D/(D+L*r))*(Math.pow(D,2)/(Math.pow(D,2)+Q*Math.pow(r,2))); + } + if (lightObj instanceof THREE.SpotLight) { if (msg.spot_outer_angle) { @@ -2437,7 +2493,7 @@ GZ3D.Scene.prototype.updateLight = function(entity, msg) } } - if (msg.direction) + if (msg.direction && lightObj.target) { dir = new THREE.Vector3(msg.direction.x, msg.direction.y, msg.direction.z); @@ -2449,3 +2505,27 @@ GZ3D.Scene.prototype.updateLight = function(entity, msg) lightObj.target.position.copy(dir); } }; + +GZ3D.Scene.prototype.setShadowMaps = function(enabled) { + this.renderer.shadowMapEnabled = enabled; + + var that = this; + this.scene.traverse(function(node) { + if (enabled) { + if (node.material) { + node.material.needsUpdate = true; + if (node.material.materials) { + for (var i = 0; i < node.material.materials.length; i = i+1) { + node.material.materials[i].needsUpdate = true; + } + } + } + } else { + if (node instanceof THREE.Light) { + if (node.shadowMap) { + that.renderer.clearTarget( node.shadowMap ); + } + } + } + }); +}; \ No newline at end of file diff --git a/gz3d/src/gzsdfparser.js b/gz3d/src/gzsdfparser.js index 9ff775e7ce7b0d8d30ba694a51bc7cdb57884aeb..96d8ed71a8b584943263013faf3bcb5fb64d90d4 100644 --- a/gz3d/src/gzsdfparser.js +++ b/gz3d/src/gzsdfparser.js @@ -9,7 +9,7 @@ GZ3D.SdfParser = function(scene, gui, gziface) { // set the sdf version this.SDF_VERSION = 1.5; - this.MATERIAL_ROOT = 'assets/'; + this.MATERIAL_ROOT = GZ3D.assetsPath + '/'; // set the xml parser function this.parseXML = function(xmlStr) { @@ -539,7 +539,7 @@ GZ3D.SdfParser.prototype.createGeom = function(geom, mat, parent) allChildren[c].castShadow = false; allChildren[c].receiveShadow = false; - allChildren[c].visible = this.scene.showCollisions; + allChildren[c].visible = that.scene.showCollisions; } break; }