Source: user-agents/world/representations/ThreeWheelDistanceSensingRobotRepresentation.js

'use strict';

/**
 * @classdesc Class representing a three-wheeled distance-sensing robot's representation.
 * @constructor
 * @author Loris Tissino (http://loris.tissino.it)
 * @release 0.71
 * @license MIT
 * @extends RobotRepresentation
 */
var ThreeWheelDistanceSensingRobotRepresentation = function () {
};

$.extend ( ThreeWheelDistanceSensingRobotRepresentation.prototype, RobotRepresentation.prototype );

/**
 * Builds the robot's representation.
 * @returns {boolean} - Whether the robot's representation could be built
 */
ThreeWheelDistanceSensingRobotRepresentation.prototype.build = function build () {
    
    if ( !this.isBuilt ) {
        console.log( "Building robot: " + this.id );
        this
            .addBody()
            .addFrontWheel()
            .addSonars()
            .addBuzzer()
            .addVirtualLocator()
            .addVirtualCompass()
            .addVirtualScanner()
            .addVirtualPen( this.initialValues.chassis.color )
            .addVirtualCamera()
            .finalizeBody()
            .addWheels()
            //.addArm()
            ;
        this.isBuilt = true;
        return true;
    }
    else {
        console.log( "Already built: " + this.id );
        return false;
    }
}

/**
 * Adds the body of the robot to the scene
 * @returns {ThreeWheelDistanceSensingRobotRepresentation} - The robot
 */
ThreeWheelDistanceSensingRobotRepresentation.prototype.addBody = function addBody () {
    
    var values = $.extend ( {
        chassis: {
            color: 0xffffff,
            opacity: .5,
            mass: 600,
            castShadow: false,
            receiveShadow: true
        },
        board: {
            color: 0x444444,
            opacity: 1,
            mass: 50
        },
        batterypack: {
            color: 0x222222,
            opacity: 1,
            mass: 20
        }
    }, this.initialValues);

    this.chassis = new Physijs.BoxMesh(
        new THREE.BoxGeometry(16, 5, 9.7),
        this.getLambertPjsMaterial( { color: values.chassis.color, opacity: values.chassis.opacity } ),
        values.chassis.mass
    );
    this.chassis.position.set(0, 3.5, 0);
    this.chassis.name = 'chassis';
    this.chassis.castShadow = values.chassis.castShadow;
    this.chassis.receiveShadow = values.chassis.receiveShadow;

    this.board = new Physijs.BoxMesh (
        new THREE.BoxGeometry( 0.5, 4.5, 4 ),
        this.getLambertPjsMaterial( { color: values.board.color, opacity: values.board.opacity } ),
        values.board.mass
    );
    this.board.position.set( -3, 4.75, 0 );
    this.board.name = 'board';
    this.board.castShadow = true;
    this.board.receiveShadow = true;
    
    var leds = {
        led1: {
            color: 0xff0000,
            position: new THREE.Vector3 ( 0.27, 1.8, -1.6 ),
            shininess: 200
        },
        led2: {
            color: 0x00ff00,
            position: new THREE.Vector3 ( 0.27, 1.8, -1.1 ),
            shininess: 100
        },
        led3: {
            color: 0x0000ff,
            position: new THREE.Vector3 ( 0.27, 1.8, -0.6 ),
            shininess: 300
        }
    }
    
    var board = this.board; // a reference
    var robot = this;
    
    $.each ( leds, function ( name, led ) {
        board[name] = new THREE.Mesh (
            new THREE.BoxGeometry ( 0.2, 0.5, 0.5 ),
            new THREE.MeshPhongMaterial( { color: led.color, emissive: led.color, shininess: led.shininess, shading: THREE.FlatShading } )
        );
        board[name].visible = false;
        board[name].position.copy ( led.position );
        board[name].name = name;
        board.add ( board[name] );
        robot.dataPropertiesIn.push ( name );
    });
    
    this.chassis.add ( this.board );

    this.batterypack = new Physijs.BoxMesh(
        new THREE.BoxGeometry( 3, 5, 6.5 ),
        this.getLambertPjsMaterial( { color: values.batterypack.color, opacity: values.batterypack.opacity } ),
        values.batterypack.mass
    );
    this.batterypack.position.set( 5, 5, 0 );
    this.batterypack.name = 'batterypack';
    this.batterypack.castShadow = true;
    this.batterypack.receiveShadow = true;
    this.batterypack.userData.normalColor = this.batterypack.material.color.clone();
    
    this.chassis.add ( this.batterypack );

    var cylinderGeometry = new THREE.CylinderGeometry( 9, 9, 0.4, 32 /* number of "sides" */ );
    var cylinderMesh = new THREE.Mesh ( 
        cylinderGeometry,
        this.chassis.material
    );
    
    cylinderMesh.position.set ( 4, 2.5, 0 );
    
    var squareGeometry = new THREE.BoxGeometry( 18, 1, 18 );
    var squareMesh = new THREE.Mesh ( 
        squareGeometry,
        this.chassis.material
    );
    
    squareMesh.position.set ( -1, 2.5, 0 );
    
    var cylinderBSP = new ThreeBSP( cylinderMesh );
    var squareBSP = new ThreeBSP( squareMesh );

    var resultBSP = cylinderBSP.subtract ( squareBSP );
    
    var result = resultBSP.toMesh();
    result.geometry.computeFaceNormals();
    result.geometry.computeVertexNormals();
    
    this.back = new Physijs.ConvexMesh(
        result.geometry,
        this.chassis.material,
        20
    );
    
    this.back.castShadow = values.chassis.castShadow;
    this.back.receiveShadow = values.chassis.receiveShadow;
    
    this.back.position.set ( 4, 2.3, 0 );
    this.back.name = 'back';
    this.back.__dirtyPosition = true;
    this.back.__dirtyRotation = true;
    
    this.centralPoint = new THREE.Object3D();
    this.centralPoint.position.set( 4.5, 3.6, 0 );  // the point between the wheels
    this.chassis.add ( this.centralPoint );
    
    this.chassis.add ( this.back );
    
    return this;
}

/**
 * Adds the driving wheels of the robot to the scene.
 * @returns {ThreeWheelDistanceSensingRobotRepresentation} - The robot
 */
ThreeWheelDistanceSensingRobotRepresentation.prototype.addWheels = function addWheels () {
    
    var color = 0xD9D900;
    
    var wheels = {
        left: {
            position: new THREE.Vector3( this.centralPoint.position.x, this.centralPoint.position.y, 6.5 ),
        },
        right: {
            position: new THREE.Vector3( this.centralPoint.position.x, this.centralPoint.position.y, -6.5 ),
        }
    }
    
    var robot = this;  // a reference
    
    $.each ( wheels, function ( name, wheel ) {
        var wheelName = name + 'Wheel';
        var constraintName = wheelName + 'Constraint';
        
        robot.dataPropertiesIn.push ( wheelName );

        robot[wheelName] = robot.createWheel ({
            position: wheel.position,
            radius: robot.centralPoint.position.y, // assuming that the wheel touches the bottom
            thickness: 2.5,
            mass: 500,
            color: color
        });
        robot[wheelName].name = wheelName;
        robot.scene.add ( robot[wheelName] );

        robot[constraintName] = robot.createDOFConstraint ( robot.chassis, robot[wheelName], wheel.position );
        robot.scene.addConstraint ( robot[constraintName], true );
        
        robot[constraintName].setAngularLowerLimit({x: 0, y: 0, z: 0});
        robot[constraintName].setAngularUpperLimit({x: 0, y: 0, z: 0});
        robot[constraintName].configureAngularMotor(2, 0.1, 0, 0, 1500);
        robot[constraintName].enableAngularMotor(2);
        
        robot.components.push ( robot[wheelName] );

    });
    
    return this;
}

ThreeWheelDistanceSensingRobotRepresentation.prototype.addArm = function addArm () {
    
    // this is an experiment, not yet completed
    
    this.arm = new Physijs.BoxMesh(
        new THREE.BoxGeometry( 100, 5, 1 ),
        this.getLambertPjsMaterial( { color: 0x333333, opacity: 0.5 } ),
        20
    );
    this.arm.position.set( 3, 14, 0 );
    this.arm.name = 'arm';
    this.arm.castShadow = true;
    this.arm.receiveShadow = true;
    this.arm.__dirtyPosition = true;
    this.arm.__dirtyRotation = true;

    //this.chassis.add ( this.arm );
    this.scene.add (this.arm);
    
    var constraintPosition = this.arm.position.clone().add( new THREE.Vector3 ( 0, -2.5, 0 ) );
    
    this.armConstraint = this.createDOFConstraint( this.chassis, this.arm, constraintPosition, new THREE.Vector3 ( 0, 1, 0 ));
    
    this.scene.addConstraint( this.armConstraint, true );
    
    this.armConstraint.setLinearLowerLimit( new THREE.Vector3( 0, 0, 0 ) ); // sets the lower end of the linear movement along the x, y, and z axes.
    this.armConstraint.setLinearUpperLimit( new THREE.Vector3( 0, 0, 0 ) ); // sets the upper end of the linear movement along the x, y, and z axes.
    this.armConstraint.setAngularLowerLimit( new THREE.Vector3( 0, -Math.PI, 0 ) ); // sets the lower end of the angular movement, in radians, along the x, y, and z axes.
    this.armConstraint.setAngularUpperLimit( new THREE.Vector3( 0, Math.PI, 0 ) ); // sets the upper end of the angular movement, in radians, along the x, y, and z axes.
    /*
    this.armConstraint.configureAngularMotor(
        which, // which angular motor to configure - 0,1,2 match x,y,z
        low_limit, // lower limit of the motor
        high_limit, // upper limit of the motor
        velocity, // target velocity
        max_force // maximum force the motor can apply
    );
    */
    // this.armConstraint.enableAngularMotor( which ); // which angular motor to configure - 0,1,2 match x,y,z
    // this.armConstraint.disableAngularMotor( which ); /
        
    this.components.push ( this.arm );
    
    return this;

}

/**
 * Adds the front (not driving) wheel of the robot to the scene.
 * @returns {ThreeWheelDistanceSensingRobotRepresentation} - The robot
 */
ThreeWheelDistanceSensingRobotRepresentation.prototype.addFrontWheel = function addFrontWheel () {

    this.frontWheel = new Physijs.BoxMesh(
        new THREE.BoxGeometry( 1, 1, 1) ,
        this.getLambertPjsMaterial( { color: 0xE5E5E5, opacity: 1 , friction: 0.01 } ),
        20
    );
    this.frontWheel.position.set( -7.5, -3, 0 );
    this.frontWheel.name = 'frontWheel';
    this.frontWheel.castShadow = true;
    this.frontWheel.receiveShadow = true;
    
    this.chassis.add ( this.frontWheel );

    // without this, the robot does a wheelie every time it starts moving forward. 
    this.counterBalanceWheel = new Physijs.BoxMesh(
        new THREE.BoxGeometry( 1, 1, 1) ,
        this.getLambertPjsMaterial( { color: 0xE5E5E5, opacity: 0 , friction: 0.01 } ),
        2
    );
    this.counterBalanceWheel.position.set( 9, -3, 0 );
    this.counterBalanceWheel.name = 'counterBalanceWheel';
    this.counterBalanceWheel.castShadow = false;
    this.counterBalanceWheel.receiveShadow = false;
    
    this.chassis.add ( this.counterBalanceWheel );

    return this;
}

/**
 * Adds the sonars of the robot to the scene and registers their process function.
 * @returns {ThreeWheelDistanceSensingRobotRepresentation} - The robot
 */
ThreeWheelDistanceSensingRobotRepresentation.prototype.addSonars = function addSonars () {

    this.frontSonarReference = new THREE.Object3D();
    this.frontSonarReference.position.set ( 0, 3.5, 0 );
    this.chassis.add ( this.frontSonarReference );
    
    this.dataPropertiesIn.push ( 'sonars' );
    this.data.sonars = {};

    this.sonars = {
        left:  {
            geometry: new THREE.BoxGeometry( 4.5, 2, 1),
            position: new THREE.Vector3 ( -5.5, 3.5, 4.35 ),
            reference: 'rightSonar'
        },
        front: {
            geometry: new THREE.BoxGeometry( 1, 2, 4.5),
            position: new THREE.Vector3 ( -7.5, 3.5, 0 ),
            reference: 'frontSonarReference'
        },
        right:  {
            geometry: new THREE.BoxGeometry( 4.5, 2, 1),
            position: new THREE.Vector3 ( -5.5, 3.5, -4.35 ),
            reference: 'leftSonar'
        }
    }
    
    var robot = this; // a reference

    this.sonarData = {
        variants : [
            { type: 'none', color: 0x000000 },
            { type: 'horizontal', axis: new THREE.Vector3 ( 0, 1, 0 ), color: 0xff0000, value: 0.087 },
            { type: 'horizontal', axis: new THREE.Vector3 ( 0, 1, 0 ), color: 0x00ff00, value: -0.087 },
            { type: 'vertical', color: 0x0000ff, value: 0.087 },
            { type: 'vertical', color: 0xff00ff, value: -0.087 }
        ]
    };
    
    $.each ( this.sonars, function ( key, sonar ) {
        var name = key + 'Sonar';
        robot[name] = new Physijs.BoxMesh(
            sonar.geometry,
            robot.getLambertPjsMaterial( { color: 0xBFBFBF, opacity: 1.0 } ),
            40
        );
        robot[name].position.copy( sonar.position );
        robot[name].name = name;
        robot[name].castShadow = true;
        robot[name].receiveShadow = true;
        robot[name].userData.reference = sonar.reference;
        robot[name].userData.raycasters = [];
        robot.chassis.add ( robot[name] );
        
        for ( var i=0; i < robot.sonarData.variants.length; i++ ) {
            robot[name].userData.raycasters[i] = {
                raycaster: new THREE.Raycaster(),
                direction: new THREE.Vector3( -1, 0, 0)
            };
            robot[name].userData.raycasters[i].arrowHelper = new THREE.ArrowHelper( 
                robot[name].userData.raycasters[i].direction,
                robot[name].position,
                30,
                robot.sonarData.variants[i].color
            );
            robot[name].userData.raycasters[i].arrowHelper.visible = false;
            robot.scene.add( robot[name].userData.raycasters[i].arrowHelper );
        };
        
        robot[name].userData.matchArrowHelper = new THREE.ArrowHelper( new THREE.Vector3 ( 0, 1, 0), robot[name].position, 1, 0x111111, 1, 1 );
        robot.scene.add( robot[name].userData.matchArrowHelper );
        robot[name].userData.matchArrowHelper.visible = false;
    });

    this.registeredProcessFunctions.push ( function ( ) {
        var intersects;
        $.each ( robot.sonars, function ( key, name ) {
            
            var i;
            var name = key + 'Sonar';
            if ( robot.data.sonars[key].enabled ) {
                var asp = robot.getAbsolutePositionForObject( robot[name], true );
                var ref = robot.getAbsolutePositionForObject( robot[robot[name].userData.reference], true );
                var dir = asp.clone().sub(ref).normalize();
                robot.data.sonars[key].distance = Infinity;

                for ( i=0; i < robot.sonarData.variants.length; i++ ) {
                    robot[name].userData.raycasters[i].direction = dir.clone();
                    
                    switch ( robot.sonarData.variants[i].type ) {
                        case 'none':
                            break;
                        case 'horizontal':
                            robot[name].userData.raycasters[i].direction.applyAxisAngle (
                                robot.sonarData.variants[i].axis,
                                robot.sonarData.variants[i].value
                            )
                            break;
                        case 'vertical':
                            robot[name].userData.raycasters[i].direction.y = Math.sin ( Math.asin ( robot[name].userData.raycasters[i].direction.y ) + robot.sonarData.variants[i].value );
                            break;
                        default:
                            // should never happen
                    }
                    
                    robot[name].userData.raycasters[i].direction.normalize();
                    
                    robot[name].userData.raycasters[i].raycaster.set ( asp, robot[name].userData.raycasters[i].direction, 3, 600 );

                    robot[name].userData.raycasters[i].arrowHelper.visible = robot.robotsManager.simulator.gui.userData.controls.showSonarDetection;

                    if ( robot[name].userData.raycasters[i].arrowHelper.visible ) {
                        robot[name].userData.raycasters[i].arrowHelper.position.copy ( asp );
                        robot[name].userData.raycasters[i].arrowHelper.setDirection ( robot[name].userData.raycasters[i].direction );
                    }

                    intersects = robot[name].userData.raycasters[i].raycaster.intersectObjects( robot.scene.children );

                    if ( intersects.length > 0 )
                    {
                        if ( intersects[0].distance <  robot.data.sonars[key].distance ) {
                            robot.data.sonars[key].distance = intersects[0].distance;
                            robot[name].userData.matchArrowHelper.position.copy ( intersects[0].point );
                            robot[name].userData.matchArrowHelper.setDirection ( robot[name].userData.raycasters[i].direction.negate() );
                            robot[name].userData.matchArrowHelper.setColor ( robot.sonarData.variants[i].color );
                        }
                    }
                    robot[name].userData.matchArrowHelper.visible = isFinite( robot.data.sonars[key].distance ) && robot[name].userData.raycasters[i].arrowHelper.visible;
                }
            }
            else {
                for ( i=0; i < robot.sonarData.variants.length; i++) {
                    robot[name].userData.raycasters[i].arrowHelper.visible = false;
                }
                robot[name].userData.matchArrowHelper.visible = false;
            }
            
        });
    } );

    return this;
}

/**
 * Finalizes the body of the robot. After calling this function, no other object can be added to the chassis.
 * @returns {ThreeWheelDistanceSensingRobotRepresentation} - The robot
 */
ThreeWheelDistanceSensingRobotRepresentation.prototype.finalizeBody = function finalizeBody () {
    this.scene.add ( this.chassis );
    return this;
}

/**
 * Adds the buzzer and registers its process function.
 * @returns {ThreeWheelDistanceSensingRobotRepresentation} - The robot
 */
ThreeWheelDistanceSensingRobotRepresentation.prototype.addBuzzer = function addBuzzer () {
    // see https://developer.mozilla.org/en/docs/Web/API/AudioContext
    // see http://chimera.labs.oreilly.com/books/1234000001552/ch01.html
    // http://shop.oreilly.com/product/0636920025948.do
    var robot = this;
    this.buzzer = {
        audioCtx: new (window.AudioContext || window.webkitAudioContext || window.audioContext)
    };
    this.buzzer.beep = function beep ( duration, frequency, volume, type, callback ) {
        var oscillator = robot.buzzer.audioCtx.createOscillator();
        var gainNode = robot.buzzer.audioCtx.createGain();
        oscillator.connect(gainNode);
        gainNode.connect(robot.buzzer.audioCtx.destination);
        if ( volume ) { gainNode.gain.value = volume; };
        if ( frequency ) { oscillator.frequency.value = frequency; }
        if ( type ) { oscillator.type = type; }
        if ( callback ) { oscillator.onended = callback; }
        oscillator.start();
        setTimeout( function() {
            oscillator.stop();
        }, (duration ? duration : 500) );
    };
    robot.dataPropertiesIn.push ( 'buzzer' );
    robot.registeredProcessFunctions.push ( function () {
        if ( robot.data.buzzer.status == 1 ) {
            robot.buzzer.beep ( robot.data.buzzer.duration, robot.data.buzzer.frequency );
            robot.data.buzzer.status = 2;
        }
    } );
    return this;
};

/**
 * Adds the virtual camera and registers its process function.
 * @returns {ThreeWheelDistanceSensingRobotRepresentation} - The robot
 */
ThreeWheelDistanceSensingRobotRepresentation.prototype.addVirtualCamera = function addVirtualCamera () {

    this.camera = new THREE.PerspectiveCamera(
        35,
        window.innerWidth / window.innerHeight,
        1,
        1000
    );
    
    this.cameraPosition = new THREE.Object3D();
    this.cameraPosition.position.set ( -3, 8, 0 );
    this.chassis.add ( this.cameraPosition );
    
    this.camera.position.copy ( this.cameraPosition );

    this.cameraReference = new THREE.Object3D();
    this.cameraReference.position.set ( -6, 8, 0 );
    this.chassis.add ( this.cameraReference );

    this.camera.lookAt ( this.cameraReference );
    this.camera.name = this.id;

    this.scene.add ( this.camera );

    /*
    this.cameraHelper = new THREE.CameraHelper( this.camera );
    this.scene.add ( this.cameraHelper );
    */
    
    this.robotsManager.simulator.availableCameras[this.camera.uuid] = this.camera;
    
    var robot = this;
    
    this.registeredProcessFunctions.push ( function () {
        var cref = robot.getAbsolutePositionForObject( robot.cameraReference, true ); 
        var cpos = robot.getAbsolutePositionForObject( robot.cameraPosition, true ); 
        robot.camera.position.copy ( cpos );
        robot.camera.lookAt ( cref );
        /* */
        // TODO: we should take care of camera rotation too.
    } );
    
    return this;
}

/**
 * Adds the virtual locator and registers its process function.
 * @returns {ThreeWheelDistanceSensingRobotRepresentation} - The robot
 */
ThreeWheelDistanceSensingRobotRepresentation.prototype.addVirtualLocator = function addVirtualLocator () {
    var robot = this;
    this.registeredProcessFunctions.push ( function () {
        var coords = robot.getAbsolutePositionForObject( robot.centralPoint, true );
        robot.data.location = { x: coords.x, y: coords.z };
        robot.initialValues.debugging && robot.robotsManager.simulator.pushDebugText( { location: { x: robot.data.location.x.toFixed(2), y: robot.data.location.x.toFixed(2) } } );
    } );
    return this;
}

/**
 * Adds the virtual compass and registers its process function.
 * @returns {ThreeWheelDistanceSensingRobotRepresentation} - The robot
 */
ThreeWheelDistanceSensingRobotRepresentation.prototype.addVirtualCompass = function addVirtualCompass () {
    var robot = this;
    this.registeredProcessFunctions.push ( function ( ) {
        var fsp = robot.getAbsolutePositionForObject( robot.frontSonar, true );
        var cpp = robot.getAbsolutePositionForObject( robot.centralPoint, false );
        robot.data.heading = robot.getAngle( fsp, cpp, ['z','x'] );
        robot.initialValues.debugging && robot.robotsManager.simulator.pushDebugText( { heading: robot.data.heading.toFixed(2) } );
        //robot.initialValues.debugging && robot.robotsManager.simulator.pushDebugText( { rotation: [ robot.chassis.rotation._x.toFixed(3), robot.chassis.rotation._y.toFixed(3), robot.chassis.rotation._z.toFixed(3)] } );
    } );
    return this;
}

/**
 * Adds the virtual scanner and registers its process function.
 * @returns {ThreeWheelDistanceSensingRobotRepresentation} - The robot
 */
ThreeWheelDistanceSensingRobotRepresentation.prototype.addVirtualScanner = function addVirtualScanner ( ) {
    var robot = this;
    this.registeredProcessFunctions.push ( function ( ) {
        var coords = robot.getBottomImagePixelCoordinatesForObject( robot.centralPoint, false );
        robot.data.pixels = robot.robotsManager.simulator.bottom.canvas.getContext('2d').getImageData(
            coords.x,
            coords.y,
            1, 1
        ).data;
    } );
    return this;
}

/**
 * Adds the virtual pen and registers its process function.
 * @returns {ThreeWheelDistanceSensingRobotRepresentation} - The robot
 */
ThreeWheelDistanceSensingRobotRepresentation.prototype.addVirtualPen = function addVirtualPen ( color ) {
    var robot = this;
    this.pen = {
        enabled: false,
        color: new THREE.Color ( color ),
        radius: 9,
        alpha: .05,
        context: this.robotsManager.simulator.bottom.canvas.getContext('2d')
    };
    this.registeredProcessFunctions.push ( function ( ) {
        $.extend ( robot.pen, robot.receivedData.pen );
        if ( robot.pen.enabled ) {
            robot.batterypack.material.color.copy ( robot.pen.color );
        }
        if ( robot.pen.enabled && robot.isMoving ) {
            var coords = robot.getBottomImagePixelCoordinatesForObject( robot.centralPoint, false );
            robot.pen.context.fillStyle = "#" + robot.pen.color.getHexString();
            robot.pen.context.globalAlpha = robot.pen.alpha;
            robot.pen.context.beginPath();
            robot.pen.context.arc(
                coords.x,
                coords.y,
                THREE.Math.mapLinear ( robot.pen.radius, 0, robot.robotsManager.simulator.bottom.width, 0, robot.robotsManager.simulator.bottom.canvas.width ),
                0,
                2 * Math.PI,
                false
            );
            robot.pen.context.fill();
            robot.pen.context.closePath();
            robot.robotsManager.simulator.bottom.canvasMap.image = robot.robotsManager.simulator.bottom.canvas;
            robot.robotsManager.simulator.bottom.canvasMap.needsUpdate = true;
        }
        robot.data.penStatus = robot.pen.enabled ? 'down': 'up';
    } );
    return this;
}

/**
 * Processes incoming data and prepares outgoing data.
 * @returns {ThreeWheelDistanceSensingRobotRepresentation} - The robot
 */
ThreeWheelDistanceSensingRobotRepresentation.prototype.process = function process ( ) {
    
    var p;
    
    /* manage inputs */
    this.updateWheelSpeed( 'left', this.data.leftWheel );
    this.updateWheelSpeed( 'right', this.data.rightWheel );
    
    this.isMoving = this.data.leftWheel !==0 || this.data.rightWheel !==0;

    for ( var i = 1; i<=3; i++ ) {
        p = 'led' + i; 
        this.board[p].visible = this.data[p] === 1;
    }
    
    /* manage outputs */
    
    
    //this.arm.__dirtyPosition = true;
    //this.arm.__dirtyRotation = true;
    //this.arm.rotateOnAxis ( new THREE.Vector3(0, 1, 0), 0.01 );
    
    for ( var i = 0; i< this.registeredProcessFunctions.length; i++ ) {
        this.registeredProcessFunctions[i]( );
    }
    
    return this;
}

/**
 * Updates the data to/from the robot's behavior.
 * @override
 * @param {Object} data - The data received/transmitted
 */
ThreeWheelDistanceSensingRobotRepresentation.prototype.update = function update ( data ) {
    
    if ( !this.isBuilt ) {
        return;
    }
    
    this.batterypack.material.color.copy ( this.batterypack.userData.normalColor );
    
    this.receivedData = data;
    
    if ( typeof data !== 'undefined' ) {
        
        for ( var i=0; i<this.dataPropertiesIn.length; i++ ) {
            this.data[this.dataPropertiesIn[i]] = data[this.dataPropertiesIn[i]];
        }
        
        this.process( );
    }
    
    
    //console.log ( this.data );
    
    return this.data;
}

/**
 * Manages the fact that there has been a communication failure.
 * @override
 * @param {Object} data - The data received/transmitted
 */
ThreeWheelDistanceSensingRobotRepresentation.prototype.manageCommunicationFailure = function manageCommunicationFailure () {
    if ( this.isBuilt ) {
        this.batterypack.material.color.setHex( this.robotsManager.values.failureColor );
    }
    return this.data;
}

window["ThreeWheelDistanceSensingRobotRepresentation"] = ThreeWheelDistanceSensingRobotRepresentation;  // we need a reference to this function to be shared through a global object.