// copyright (c) 1997,1998 stephen f. white
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2, or (at your option)
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; see the file COPYING. If not, write to
// the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
package XOO_CODE;
import java.io.*;
import java.net.*;
import java.util.*;
import rtpi.*;
import rtpi.util.*;
import rtpi.demoRtpi.*;
import rtpi.Message;
import vrml.*;
import vrml.external.*;
import vrml.external.field.*;
import vrml.external.FreeWRLEAI.*;
import vrml.external.FreeWRLEAI.VField;
public class SMClientThread extends Thread implements
ProximityThreadObserver, RtpiRecipient
{
// Base reliability transmission delay (in milliseconds)
static long delay = 10000;
// Toggle used to indicate whether this client is connected
boolean connected;
// Reference to class which receives messages from the SMClientThread
SMClientThreadObserver observer;
// Multicast Network stuff.
Rtpi rtpi;
MulticastSocket iosocket;
DatagramSocket diosocket;
// RTPI participant ID
int participantID;
long subIDBase;
// RTPI components
long subcomponentNumber = 0;
SyncQueue messages = new SyncQueue();
Hashtable subcomponents = new Hashtable();
Hashtable timers = new Hashtable();
// Proximity Calculations
ProximityThread proximitythread;
// link into the audio controller of RAT.
DatagramSocket RAT_out_socket;
DatagramSocket RAT_in_socket;
int RAT_port = 3333;
InetAddress localaddr;
// Send RAT the following; it'll need them for
// communicating to other participants!
String auaddress;
int auport;
int auttl;
// RTPI variables: avatar subID, byte mask used to calculate RTP/I IDs
private static final int BYTE_MASK = 0x000000FF;
// Constructor for non-eye machines
SMClientThread(SMClientThreadObserver observer, Rtpi rtpi,
String auaddress, int auport, int auttl, int participantID) {
this.observer = observer;
this.rtpi = rtpi;
this.auaddress = auaddress;
this.auport = auport;
this.auttl = auttl;
this.participantID = participantID;
// Set this class as the recipient for RTP/I packets
rtpi.setRtpiRecipient(this);
// Discover local host
try {
localaddr = InetAddress.getLocalHost();
} catch (UnknownHostException e) {
System.out.println ("SMClientThread - can't get local host!");
}
// Create base subcomponent ID
byte[] baseBytes = localaddr.getAddress();
//System.out.println("bytes: " + baseBytes);
//System.out.println("3: " + baseBytes[3] + " 2: " + baseBytes[2] + " 1: " + baseBytes[1] + " 0: " + baseBytes[0]);
subIDBase = (baseBytes[3] & BYTE_MASK) | ((baseBytes[2]&BYTE_MASK) << 8) | ((baseBytes[1]&BYTE_MASK) << 16) | ((baseBytes[0]&BYTE_MASK) << 24);
subIDBase = subIDBase & 0xFFFFFFFFl;
subIDBase = subIDBase - 1000000000;
//System.out.println("subIDBase: " + subIDBase);
}
// Set connected to false
public void closeNet() {
// stop this thing....
connected = false;
}
// Main loop of this class
public void run() {
// Connecting to RAT UDP socket
// Isabelle's code but in UDP ipv4
try {
RAT_out_socket = new DatagramSocket();
RAT_in_socket = new DatagramSocket(RAT_port+1);
} catch (IOException e) {
System.err.println("Failed connecting to RAT");
}
// Send to RAT the address/port/ttl needed.
// Rat will return to us the SSRC that it is using;
// we can use that SSRC as our own, or keep it for
// sending out packets showing others what tools
// we are using.
try {
String init_code = auaddress + " " + auport + " " + auttl;
int strlen = init_code.length();
byte buf[] = new byte[strlen];
buf = init_code.getBytes();
RAT_out_socket.send(new DatagramPacket(buf,buf.length, localaddr,RAT_port));
System.out.println("sent init code");
} catch (IOException e) {
System.err.println("Failed sending initial settings to RAT");
}
// wait for the ssrc ...
try {
byte[] buf = new byte[100];
DatagramPacket recv = new DatagramPacket(buf, buf.length);
System.out.println("about to ask for packet from rat");
RAT_in_socket.receive(recv);
String received = new String(recv.getData(), 0).trim();
//String received = new String("12345");
observer.setSSRC(received);
System.out.println("participantID is: " + participantID);
System.out.println("ssrc recieved is: " + received);
Subcomponent comp = this.getSubcomponent(participantID);
while (comp == null) {
Thread.sleep(10);
comp = this.getSubcomponent(participantID);
}
send(participantID, VIP.SSRC, new VSFString(received));
System.out.println("after send");
} catch (Exception e) {
System.out.println(e + " Unable to get SSRC from RAT");
}
// Start the Proximity calculations
proximitythread = new ProximityThread (this,RAT_out_socket,RAT_port);
proximitythread.start();
connected = true;
// RTP/I Application Message
ApplicationMessage message = null;
// While this computer is still connected using XOO process all incoming Application Messages
while (connected)
{
System.out.println("In LOOP");
// Get the next application message
try {
message = (ApplicationMessage)messages.get();
System.out.println("Got Message " + message.opCode);
}
catch (Exception e) {
System.out.println(e + " Could not get next message");
System.exit(0);
}
// Determine which type of message has been received
switch (message.opCode) {
// An event packet has been received
case ApplicationMessage.RECEIVE_EVENT:
{
ApplicationMessageArgs args = (ApplicationMessageArgs)message.args;
execReceiveEvent(args.rtpi, (RtpiEvent)args.data);
break;
}
// A state update packet has been received
case ApplicationMessage.RECEIVE_STATE:
{
ApplicationMessageArgs args = (ApplicationMessageArgs)message.args;
execReceiveState(args.rtpi, (RtpiState)args.data);
break;
}
// A delta state update packet has been received
case ApplicationMessage.RECEIVE_DELTA_STATE:
{
ApplicationMessageArgs args = (ApplicationMessageArgs)message.args;
execReceiveDeltaState(args.rtpi, (RtpiDeltaState)args.data);
break;
}
// A state query packet has been received
case ApplicationMessage.RECEIVE_STATE_QUERY:
{
ApplicationMessageArgs args = (ApplicationMessageArgs)message.args;
execReceiveStateQuery(args.rtpi, (RtpiStateQuery)args.data);
break;
}
}
try {
this.sleep(1);
}
catch (Exception e) {
System.out.println(e + " Thread interrupted");
}
}
// When the loop is exited we are disconnected
observer.onNetDisconnect();
}
// Send state information to RTP/I
public void send(long id, short field, VField value)
{
//System.out.println("In send, id: " + id + " field " + field + " value " + value);
// We can only send information if we are connected
if (connected) {
RtpiDeltaState state = null;
// Get the subcomponent information for the requested subID
Subcomponent comp = this.getSubcomponent(id);
// Store current state information in subcomponent
if (field == VIP.POSITION)
comp.setTranslation((VSFVec3f) value);
if (field == VIP.ORIENTATION)
comp.setRotation((VSFRotation) value);
// Create a new RTP/I state for this subcomponent
try {
state = new RtpiDeltaState((int) participantID, (long) id, (int)comp.getNextStateSequenceNumber(), 10, 3, System.currentTimeMillis(), rtpi.getCombinedHeaderSize());
}
catch (Exception e) {
System.out.println(e + " Could not create rtpi state for transmission");
return;
}
// Write field type information (rotation or transformation), and data to state
try {
OutputStream os = state.getOutputStream();
DataOutputStream dos = new DataOutputStream(os);
dos.writeShort(field);
value.write(dos);
}
catch (Exception e) {
System.out.println(e + " Unable to print to packet");
return;
}
state.outputComplete();
// Transmit state using RTP/I
rtpi.transmitDeltaState(state);
}
}
public void sendEvent(long id, int field, VField value)
{
if (connected) {
RtpiEvent event = null;
Subcomponent comp = this.getSubcomponent(id);
// If this event is a gesture then set the appropriate gesture flag
if (field > VIP.NUM_FIELDS)
{
if (((VSFBool)value).getValue())
comp.setGestureState((field - VIP.NUM_FIELDS), (short)1);
else
comp.setGestureState((field-VIP.NUM_FIELDS), (short)0);
}
try {
event = new RtpiEvent((int) participantID, (long) id, (int) comp.getNextEventSequenceNumber(), 10, System.currentTimeMillis(), rtpi.getCombinedHeaderSize());
} catch (Exception e) {
System.err.println(e + " unable to create Rtpi Event");
System.exit(0);
}
try {
OutputStream os = event.getOutputStream();
DataOutputStream dos = new DataOutputStream(os);
dos.writeInt(field);
value.write(dos);
}
catch (Exception e) {
System.out.println("sendEvent");
System.out.println(e + " Unable to print to packet");
System.exit(0);
}
event.outputComplete();
rtpi.transmitEvent(event);
}
}
// An error has occurred, diconnect from XOO
public void onError(Exception e)
{
System.err.println(e + ", disconnecting");
connected = false;
}
//Proximity calculation method.
public String calculateProximity() {
String names = observer.audioProximityDetect();
return names;
}
// Create a new RTP/I subcomponent
Subcomponent createSubcomponent(String avatar, String uname)
{
// New subID for subcomponent
long id = subIDBase + (subcomponentNumber++);
// Construct a new subcomponent object
Subcomponent comp = new Subcomponent(this, "", id, participantID);
// Set the fields for the subcomponent
comp.setAvatar(avatar);
comp.setUsername(uname);
comp.setStateReceived();
comp.setRotation(new VSFRotation((float) 0.0, (float) 0.0, (float) 0.0, (float) 0.0));
comp.setTranslation(new VSFVec3f((float) 0.0, (float) 0.0, (float) 0.0));
// Add this subcomponent to the hash table
subcomponents.put(new Long(id), comp);
// Start a timer thread for this subcomponent
Timer timer = new Timer(true);
timer.schedule(new XOOTimer(id, this, timer), delay);
timers.put(new Long(id), timer);
// Add this subcomponent to those monitored by RTP/I
try {
rtpi.addSubcomponent(id, null, true, false);
}
catch (Exception e) {
System.out.println(e + " Unable to add subcomponent to RTP/I");
System.exit(0);
}
// If this is not an eye computer then transmit an XOO ADD_OBJECT packet
sendState(id);
return comp;
}
// Get the subcomponent information for the passed subID from the subcomponent hash table
Subcomponent getSubcomponent(long id) {
return (Subcomponent)subcomponents.get(new Long(id));
}
public String getSSRC(long id) {
return observer.getSSRC(id);
}
// Make the subcomponent with the passed subID active
public void activateSubcomponent(Rtpi urtpi, long id)
{
rtpi.activateSubcomponent(id);
}
// Add the subcomponent with the passed ID to the
public void addSubcomponent(Rtpi rtpi, long id, boolean active, byte[] name)
{
RtpiStateQuery query = null;
try {
query = new RtpiStateQuery(participantID, id, 0, 0, 0, 0, rtpi.getCombinedHeaderSize());
} catch (Exception e) {
System.out.println("Unable to create new state query");
}
rtpi.transmitStateQuery(query);
}
// Change the source information for a participant
public void changeSourceInfo(Rtpi rtpi, RtpiSourceInfo participant)
{
// We don't need to keep track of participant info at the moment, as we don't use this information.
}
// Exit this class
public void connectionClosed(Rtpi rtpi)
{
System.exit(0);
}
// Set a subcomponent to be passive
public void deactivateSubcomponent(Rtpi urtpi, long id)
{
rtpi.deactivateSubcomponent(id);
}
// A delta state has been received by RTP/I, add it to the queue of messages to be processed
public void receiveDeltaState(Rtpi rtpi, RtpiDeltaState deltaState)
{
messages.put(new ApplicationMessage(ApplicationMessage.RECEIVE_DELTA_STATE, new ApplicationMessageArgs(rtpi, deltaState)));
}
// An event has been received by RTP/I, add it to the queue of messages to be processed
public void receiveEvent(Rtpi rtpi, RtpiEvent event)
{
messages.put(new ApplicationMessage(ApplicationMessage.RECEIVE_EVENT, new ApplicationMessageArgs(rtpi, event)));
}
// A state has been received by RTP/I, add it to the queue of messages to be processed
public void receiveState(Rtpi rtpi, RtpiState state)
{
messages.put(new ApplicationMessage(ApplicationMessage.RECEIVE_STATE, new ApplicationMessageArgs(rtpi, state)));
}
// A state query has been received by RTP/I, add it to the queue of messages to be processed
public void receiveStateQuery(Rtpi rtpi, RtpiStateQuery stateQuery)
{
messages.put(new ApplicationMessage(ApplicationMessage.RECEIVE_STATE_QUERY, new ApplicationMessageArgs(rtpi, stateQuery)));
}
// A remove source message has been received by RTP/I
public void removeSource(Rtpi rtpi, RtpiSourceInfo participant)
{
// We don't keep track of participant info at the moment.
}
// A remove subcomponent message has been received by RTP/I, add it to the queue of messages to be processed
public void removeSubcomponent(Rtpi rtpi, long subcomponentID)
{
observer.removeSubcomponent(subcomponentID);
}
// A RTP/I ADU lost message has been received by RTP/I
public void rtpiAduLost(Rtpi rtpi, int participantID, long subID, int type, int sequenceNumber, int timeStamp)
{
}
// A RTP/I could not recover ADU message has been received by RTP/I
public void rtpiCouldNotRecover(Rtpi rtpi, int participantID, long subID, int type, int sequenceNumber, int timestamp)
{
}
// Process a received RTP/I state
private void execReceiveState(Rtpi r, RtpiState state)
{
// Get the subcomponent ID associated with the received state
long id = state.getSubcomponentID();
// Get the subcomponent associated with the received state
Subcomponent comp = getSubcomponent(id);
// If the subcomponent is null this is a new subcomponent. Create a new subcomponent and add it to the subcomponents
// monitored by RTP/I and to the subcomponent hash table.
if (comp == null) {
comp = new Subcomponent(this, "", id, state.getParticipantID());
subcomponents.put(new Long(id), comp);
try {
rtpi.addSubcomponent(id, null, false, true);
}
catch (Exception e) {
System.out.println(e + " Unable to add subcomponent to rtpi");
}
}
// Pass the state information to the observer thread so that it may update the VRML scene graph
observer.onStateReceived(state);
}
// Process a received RTP/I event
private void execReceiveEvent(Rtpi r, RtpiEvent event)
{
// Get the subcomponent ID associated with the received event
long id = event.getSubcomponentID();
// Get the subcomponent associated with the received event
Subcomponent comp = getSubcomponent(id);
// Check if this subcomponent exists
if (comp == null) {
System.out.println("Event received for null subcomponent");
return;
}
else {
observer.onEventReceived(event);
}
}
// Process a received RTP/I delta state
private void execReceiveDeltaState(Rtpi r, RtpiDeltaState state)
{
// Get the subcomponent ID associated with the received delta state
long id = state.getSubcomponentID();
// Get the subcomponent associated with the received delta state
Subcomponent comp = getSubcomponent(id);
// If the subcomponent is null this is a new subcomponent. Create a new subcomponent and add it to the subcomponents
// monitored by RTP/I and to the subcomponent hash table.
if (comp == null) {
comp = new Subcomponent(this, "", id, state.getParticipantID());
subcomponents.put(new Long(id), comp);
try {
rtpi.addSubcomponent(id, null, false, true);
}
catch (Exception e) {
System.out.println(e + " Unable to add subcomponent to rtpi");
}
}
// Pass the state information to the observer thread so that it may update the VRML scene graph
observer.onNetInput(state);
}
//Process a received RTP/I state query
private void execReceiveStateQuery(Rtpi r, RtpiStateQuery query)
{
// Get the subcomponent ID associated with the received state query
long id = query.getSubcomponentID();
// Get the subcomponent associated with the received delta state
Subcomponent comp = getSubcomponent(id);
// Check if this subcomponent exists
if (comp == null) {
System.out.println("State query received for null subcomponent");
return;
}
else if (comp.getpid() == participantID) {
sendState(id);
}
}
private void sendState(long id)
{
VField value;
VRMLObject obj;
// We can only send information if we are connected
if (connected) {
RtpiState state = null;
// Get the subcomponent information for the requested subID
Subcomponent comp = this.getSubcomponent(id);
obj = observer.getObject((int) id);
System.out.println("In send STATE for sub: " + comp + " obj: " + obj);
// Create a new RTP/I state for this subcomponent
try {
state = new RtpiState((int) participantID, (long) id, (int)comp.getNextStateSequenceNumber(), 10, 3, System.currentTimeMillis(), rtpi.getCombinedHeaderSize());
}
catch (Exception e) {
System.out.println(e + " Could not create rtpi state for transmission");
return;
}
// Write field type information (rotation or transformation), and data to state
try {
OutputStream os = state.getOutputStream();
DataOutputStream dos = new DataOutputStream(os);
//System.out.println("should be writing avatar: " + comp.getAvatar());
//System.out.println("should be writing name: " + comp.getUsername());
//System.out.println("should be writing ssrc: " + observer.getSSRC(id));
//System.out.println("should be writing translation: " + comp.getTranslation());
//System.out.println("should be writing rotation: " + comp.getRotation());
dos.writeUTF(comp.getAvatar());
dos.writeUTF(comp.getUsername());
dos.writeUTF(observer.getSSRC(id));
value = comp.getTranslation();
value.write(dos);
value = comp.getRotation();
value.write(dos);
// Fake event & owned object fields for now
dos.writeInt(comp.getNumActions());
for (int i = 0; i < comp.getNumActions(); i++) {
dos.writeShort(comp.getGestureState(i));
}
dos.writeInt((int)obj.getParent());
}
catch (Exception e) {
System.out.println(e + " Unable to print to packet");
return;
}
state.outputComplete();
// Transmit state using RTP/I
rtpi.transmitState(state);
}
}
public long getDelay() {
return delay;
}
public void timedSendState(long id) {
// We only send out info for this object if the world is the parent
VRMLObject obj = observer.getObject((int) id);
if (obj == null)
return;
sendState(id);
}
public void removeSubcomponent(long id) {
Long longID = new Long(id);
Timer timer = (Timer) timers.get(longID);
timer.cancel();
timers.remove(longID);
}
public void addTimer(long id) {
Timer timer = new Timer(true);
timer.schedule(new XOOTimer(id, this, timer), delay);
timers.put(new Long(id), timer);
}
public void setNumGestures(long id, int numGestures) {
Subcomponent comp = (Subcomponent) subcomponents.get(new Long(id));
comp.setNumActions(numGestures);
}
public void setGesture(long id, int index, short value) {
VSFBool boolValue;
Subcomponent comp = this.getSubcomponent(id);
short currentValue = comp.getGestureState(index);
if (currentValue != value) {
comp.setGestureState(index, value);
if (value == 1)
boolValue = new VSFBool(true);
else
boolValue = new VSFBool(false);
observer.setField(id, (short)(index + VIP.NUM_FIELDS), (VField) boolValue);
}
}
}