forest-net
an overlay networks for large-scale virtual worlds
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator
Main.java
1 package mygame;
2 
3 import com.jme3.app.SimpleApplication;
4 import com.jme3.bullet.BulletAppState;
5 import com.jme3.bullet.collision.shapes.CapsuleCollisionShape;
6 import com.jme3.bullet.collision.shapes.CollisionShape;
7 import com.jme3.bullet.control.CharacterControl;
8 import com.jme3.bullet.control.RigidBodyControl;
9 import com.jme3.bullet.util.CollisionShapeFactory;
10 import com.jme3.input.KeyInput;
11 import com.jme3.input.controls.ActionListener;
12 import com.jme3.input.controls.KeyTrigger;
13 import com.jme3.light.AmbientLight;
14 import com.jme3.light.DirectionalLight;
15 import com.jme3.material.Material;
16 import com.jme3.math.ColorRGBA;
17 import com.jme3.math.FastMath;
18 import com.jme3.math.Vector3f;
19 import com.jme3.scene.Geometry;
20 import com.jme3.scene.Node;
21 import com.jme3.scene.Spatial;
22 import com.jme3.scene.shape.Box;
23 import com.jme3.system.AppSettings;
24 import java.io.File;
25 import java.io.FileNotFoundException;
26 import java.io.IOException;
27 import java.net.InetSocketAddress;
28 import java.nio.ByteBuffer;
29 import java.nio.channels.SocketChannel;
30 import java.util.HashMap;
31 import java.util.HashSet;
32 import java.util.Scanner;
33 import java.util.logging.Level;
34 import java.util.logging.Logger;
35 import forest.common.*;
36 import java.io.DataInputStream;
37 import java.io.DataOutputStream;
38 import java.net.DatagramPacket;
39 import java.net.DatagramSocket;
40 import java.net.InetAddress;
41 import java.net.Socket;
42 import java.util.ArrayList;
43 
48 public class Main extends SimpleApplication
49  implements ActionListener {
50 
51  private final boolean LET_ME_FLY = false;
52 
53  private Spatial sceneModel;
54  private BulletAppState bulletAppState;
55  private RigidBodyControl landscape;
56  private CharacterControl player;
57  private Vector3f walkDirection = new Vector3f();
58  private static int worldSize;
59  private boolean left = false, right = false, up = false, down = false;
60  private int comtree = 1001;
61  private static String hostname;
62  private static String mapfile;
63 
64  public static void main(String[] args) {
65  Main app = new Main();
66  //limit framerate to 60 fps
67  AppSettings newSetting = new AppSettings(true);
68  newSetting.setFrameRate(60);
69  newSetting.setTitle("Forest Overlay Network");
70  app.setSettings(newSetting);
71  //hide jmonkey splash screen
72  app.setShowSettings(false);
73  if (args.length != 5) {
74  System.out.println("usage: java -jar MyGame.jar hostname mapfile gridSize myIpAdr [tcp|udp]");
75  System.exit(1);
76  }
77  hostname = args[0];
78  mapfile = args[1];
79  worldSize = Integer.parseInt(args[2]);
80  String myIpAdrStr = args[3];
81  myIpAdr = Forest.ipAddress(myIpAdrStr);
82  needCliProxy = args[4].equals("tcp");
83  java.util.logging.Logger.getLogger("").setLevel(Level.SEVERE);
84  app.start();
85  }
86 
87  public void simpleInitApp() {
88  //hide the framerate
89  guiNode.detachAllChildren();
91  //physics model for floor/camera
92  bulletAppState = new BulletAppState();
93  stateManager.attach(bulletAppState);
94  // We re-use the flyby camera for rotation, while positioning is handled by physics
95  viewPort.setBackgroundColor(new ColorRGBA(0.7f, 0.8f, 1f, 1f));
96  flyCam.setMoveSpeed(100);
97  setUpKeys();
98  setUpLight();
99  setUpWalls();
100  login();
101  //use the plain 512x512 floor I made in assets
102  Box b = new Box(Vector3f.ZERO, 512f, 1.0f, 512f);
103  Geometry g = new Geometry("Box", b);
104  g.setLocalTranslation(0,-.5f,0);
105  Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
106  mat.setColor("Color", new ColorRGBA(175f/255, 194f/255, 170f/255, 1f));
107 
108  g.setMaterial(mat);
109  rootNode.attachChild(g);
110  RigidBodyControl wallControl = new RigidBodyControl(0);
111  g.addControl(wallControl);
112  bulletAppState.getPhysicsSpace().add(wallControl);
113  //sceneModel = assetManager.loadModel("Scenes/newScene.j3o");
114  //sceneModel.setLocalScale(2f);
115 
116  // We set up collision detection for the scene by creating a
117  // compound collision shape and a static RigidBodyControl with mass zero.
118  //CollisionShape sceneShape =
119  // CollisionShapeFactory.createMeshShape((Node) sceneModel);
120  //landscape = new RigidBodyControl(sceneShape, 0);
121  //sceneModel.addControl(landscape);
122 
123  // We set up collision detection for the camera
124  // Some of this is probably unnecessary
125  CapsuleCollisionShape capsuleShape = new CapsuleCollisionShape(13f, 6f, 1);
126  player = new CharacterControl(capsuleShape, 0.05f);
127  player.setJumpSpeed(20);
128  player.setFallSpeed(60);
129  if(LET_ME_FLY) player.setGravity(0);
130  else player.setGravity(100);
131  player.setPhysicsLocation(new Vector3f(0, 10, 0));
132 
133 
134  // We attach the scene and the player to the rootNode and the physics space,
135  // to make them appear in the game world.
136  //rootNode.attachChild(sceneModel);
137  //bulletAppState.getPhysicsSpace().add(landscape);
138  bulletAppState.getPhysicsSpace().add(player);
139  //wait to ssh to port
140  if (needCliProxy) {
141  System.out.println("Press enter once you've tunnelled...");
142  (new Scanner(System.in)).nextLine();
143  }
144  try {
145  chan = SocketChannel.open(new InetSocketAddress(hostname, CP_PORT));
146  chan.configureBlocking(false);
147  } catch (IOException ex) {
148  System.out.println("Couldn't open channel to Client Proxy");
149  System.out.println(ex);
150  System.exit(1);
151  }
152  recentIds = new HashSet<Integer>();
153  seqNum = 1;
154  idCounter = 1;
155  mySubs = new HashSet<Integer>();
156  waiting4comtCtl = true;
157  buf = ByteBuffer.allocate(1500);
158  now = nextTime = (int) System.nanoTime() / 1000000;
159  status = new HashMap<Integer, AvatarGraphic>();
160  connect();
161  sendCtlPkt2CC(true, comtree);
162  }
163  //setup all of the data structures needed in ShowWorld
164  private HashMap<Integer, AvatarGraphic> status;
165  private HashSet<Integer> recentIds;
166  private int idCounter;
167  private SocketChannel chan;
168  private DatagramSocket udpSock;
169  private final int CM_PORT = 30140;
170  private final int CP_PORT = 30182;
171  private ByteBuffer buf;
172  private String uname = "user";
173  private String pword = "pass";
174  private int rtrAdr;
175  private int myAdr;
176  private int rtrIpAdr;
177  private int ccAdr;
178  private long seqNum;
179  private int[] walls;
180  private final int GRID = 10000;
181  private final int MAXNEAR = 1000;
182  private final int STATUS_REPORT = 1;
183  private final int UPDATE_PERIOD = 50;
184  private HashSet<Integer>[] visSet;
185  private static int myIpAdr;
186  private HashSet<Integer> mySubs;
187  private HashMap<Integer, Integer> nearAvatars;
188  private HashMap<Integer, Integer> visibleAvatars;
189  private int numNear;
190  private int numVisible;
191  private boolean waiting4comtCtl;
192  private int newcomt;
193  private int now;
194  private int nextTime;
195  private static boolean needCliProxy;
196  private int cpIpAdr;
197  private int cpPort;
198  private int cpForestPort;
199  //logic taken from ShowWorld::main() before the while(true) loop
200 
201  private void login() {
202  System.out.println("got to login");
203  Socket cm_sock = null;
204  try {
205  cm_sock = new Socket(hostname, CM_PORT);
206  String portString = String.valueOf(cm_sock.getPort());
207  String strBuf = uname + " " + pword + " " + portString;
208  if (needCliProxy) {
209  strBuf += " proxy";
210  } else {
211  strBuf += " noproxy";
212  }
213  System.out.println(strBuf);
214  DataOutputStream dos = new DataOutputStream(cm_sock.getOutputStream());
215  dos.writeInt(strBuf.length());
216  dos.writeBytes(strBuf);
217  DataInputStream dis = new DataInputStream(cm_sock.getInputStream());
218  rtrAdr = dis.readInt();
219  if (rtrAdr == -1) {
220  System.out.println("Couldn't connect, negative reply");
221  System.exit(1);
222  }
223  if (needCliProxy) {
224  myAdr = dis.readInt();
225  cpIpAdr = dis.readInt();
226  cpPort = dis.readInt();
227  cpForestPort = dis.readInt();
228  ccAdr = dis.readInt();
229  System.out.println("assigned client proxy ip " +
230  Forest.ip2string(cpIpAdr));
231  System.out.println("assigned forest address " +
232  Forest.fAdr2string(myAdr));
233  System.out.println("assigned client proxy port " + cpPort);
234  System.out.println("router address " + Forest.fAdr2string(rtrAdr));
235  System.out.println("comtree controller address " +
236  Forest.fAdr2string(ccAdr));
237  } else {
238 
239  myAdr = dis.readInt();
240  rtrIpAdr = dis.readInt();
241  ccAdr = dis.readInt();
242  System.out.println("assigned address " + Forest.fAdr2string(myAdr));
243  System.out.println("router address " + Forest.fAdr2string(rtrAdr));
244  System.out.println(" comtree controller address " + Forest.fAdr2string(ccAdr));
245  }
246  cm_sock.close();
247  } catch (Exception e) {
248  System.out.println("Couldn't open socket to client manager");
249  System.out.println(e);
250  System.exit(1);
251  }
252  }
253  //make walls from the map file
254  //then make walls around the edges
255  //note: walls are weird in that giving them a width of x makes them 2x wide
256 
257  private void setUpWalls() {
258  try {
259 
260  Material[] mats = {new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"),
261  new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"),
262  new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"),
263  new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"),
264  new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md")};
265 
266  //Dark Grey
267  mats[0].setColor("Color", new ColorRGBA(64f/255, 63f/255, 51f/255, 1f));
268  //Medium Grey
269  mats[1].setColor("Color", new ColorRGBA(110f/255, 117f/255, 95f/255, 1f));
270  //Light Grey
271  mats[2].setColor("Color", new ColorRGBA(175f/255, 194f/255, 170f/255, 1f));
272  //Skin Color
273  mats[3].setColor("Color", new ColorRGBA(255f/255, 222f/255, 161f/255, 1f));
274  //Reddish-Orange
275  mats[4].setColor("Color", new ColorRGBA(229f/255, 76f/255, 16f/255, 1f));
276 
277 
278  float wallWidth = 1.0f;
279  float wallHeight = 15f;
280  float wallLength = 256f / worldSize;
281  Node walls = new Node();
282  Scanner in = new Scanner(new File(mapfile));
283  int y = 0;
284  Geometry g;
285  Box b;
286  Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
287  mat.setTexture("ColorMap", assetManager.loadTexture("Textures/BrickWall.jpg"));
288  RigidBodyControl wallControl;
289  while (in.hasNextLine()) {
290  String s = in.nextLine();
291  for (int i = 0; i < s.length(); i++) {
292 
293  char c = s.charAt(i);
294  int x = i;
295  float tx = x * 2 * wallLength - 256f;//transformed x
296  float ty = y * 2 * wallLength - 256f; //transformed y
297 
298  switch (c) {
299  case '+':
300  b = new Box(Vector3f.ZERO, wallLength, wallHeight, wallWidth);
301  g = new Geometry("Box", b);
302  g.setLocalTranslation((tx + b.xExtent), b.yExtent, ty);
303  g.setMaterial(mats[Math.abs((int)(tx*ty%5))]);
304  walls.attachChild(g);
305  wallControl = new RigidBodyControl(0);
306  g.addControl(wallControl);
307  bulletAppState.getPhysicsSpace().add(wallControl);
308 
309  b = new Box(Vector3f.ZERO, wallWidth, wallHeight, wallLength);
310  g = new Geometry("Box", b);
311  g.setLocalTranslation(tx, b.yExtent, (ty + b.zExtent));
312  g.setMaterial(mats[Math.abs((int)(tx*ty%5))]);
313  walls.attachChild(g);
314  wallControl = new RigidBodyControl(0);
315  g.addControl(wallControl);
316  bulletAppState.getPhysicsSpace().add(wallControl);
317  break;
318  case '-':
319  b = new Box(Vector3f.ZERO, wallLength, wallHeight, wallWidth);
320  g = new Geometry("Box", b);
321  g.setLocalTranslation((tx + b.xExtent), b.yExtent, ty);
322  g.setMaterial(mats[Math.abs((int)(tx*ty%5))]);
323  walls.attachChild(g);
324  wallControl = new RigidBodyControl(0);
325  g.addControl(wallControl);
326  bulletAppState.getPhysicsSpace().add(wallControl);
327  break;
328  case '|':
329  b = new Box(Vector3f.ZERO, wallWidth, wallHeight, wallLength);
330  g = new Geometry("Box", b);
331  g.setLocalTranslation(tx, b.yExtent, (ty + b.zExtent));
332  g.setMaterial(mats[Math.abs((int)(tx*ty%5))]);
333  walls.attachChild(g);
334  wallControl = new RigidBodyControl(0);
335  g.addControl(wallControl);
336  bulletAppState.getPhysicsSpace().add(wallControl);
337  break;
338  case ' ':
339  break;
340  }
341  }
342  y++;
343  }
344  //NOW DO EDGE walls
345  b = new Box(Vector3f.ZERO, wallWidth, wallHeight, 256);
346  g = new Geometry("Box", b);
347  g.setLocalTranslation(-256, b.yExtent, 0);
348  g.setMaterial(mat);
349  walls.attachChild(g);
350  wallControl = new RigidBodyControl(0);
351  g.addControl(wallControl);
352  bulletAppState.getPhysicsSpace().add(wallControl);
353 
354  b = new Box(Vector3f.ZERO, wallWidth, wallHeight, 256);
355  g = new Geometry("Box", b);
356  g.setLocalTranslation(256, b.yExtent, 0);
357  g.setMaterial(mat);
358  walls.attachChild(g);
359  wallControl = new RigidBodyControl(0);
360  g.addControl(wallControl);
361  bulletAppState.getPhysicsSpace().add(wallControl);
362 
363  b = new Box(Vector3f.ZERO, 256, wallHeight, wallWidth);
364  g = new Geometry("Box", b);
365  g.setLocalTranslation(0, b.yExtent, -256);
366  g.setMaterial(mat);
367  walls.attachChild(g);
368  wallControl = new RigidBodyControl(0);
369  g.addControl(wallControl);
370  bulletAppState.getPhysicsSpace().add(wallControl);
371 
372  b = new Box(Vector3f.ZERO, 256, wallHeight, wallWidth);
373  g = new Geometry("Box", b);
374  g.setLocalTranslation(0, b.yExtent, 256);
375  g.setMaterial(mat);
376  walls.attachChild(g);
377  wallControl = new RigidBodyControl(0);
378  g.addControl(wallControl);
379  bulletAppState.getPhysicsSpace().add(wallControl);
380 
381  rootNode.attachChild(walls);
382  } catch (FileNotFoundException ex) {
383  Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
384  }
385 
386  }
387  //need two types of light; one for sky, one for mesh models
388 
389  private void setUpLight() {
390  // We add light so we see the scene
391  AmbientLight al = new AmbientLight();
392  al.setColor(ColorRGBA.White.mult(1.3f));
393  rootNode.addLight(al);
394 
395  DirectionalLight dl = new DirectionalLight();
396  dl.setColor(ColorRGBA.White);
397  dl.setDirection(new Vector3f(2.8f, -2.8f, -2.8f).normalizeLocal());
398  rootNode.addLight(dl);
399  }
400 
403  private void setUpKeys() {
404  inputManager.addMapping("Left", new KeyTrigger(KeyInput.KEY_A));
405  inputManager.addMapping("Right", new KeyTrigger(KeyInput.KEY_D));
406  inputManager.addMapping("Up", new KeyTrigger(KeyInput.KEY_W));
407  inputManager.addMapping("Down", new KeyTrigger(KeyInput.KEY_S));
408  inputManager.addListener(this, "Left");
409  inputManager.addListener(this, "Right");
410  inputManager.addListener(this, "Up");
411  inputManager.addListener(this, "Down");
412  }
413 
416  public void onAction(String binding, boolean value, float tpf) {
417  if (binding.equals("Left")) {
418  left = value;
419  } else if (binding.equals("Right")) {
420  right = value;
421  } else if (binding.equals("Up")) {
422  up = value;
423  } else if (binding.equals("Down")) {
424  down = value;
425  }
426  }
427 
432  @Override
433  public void simpleUpdate(float tpf) {
434  Vector3f camDir = cam.getDirection().clone().multLocal(1.5f);
435  Vector3f camLeft = cam.getLeft().clone().multLocal(1.5f);
436  walkDirection.set(0, 0, 0);
437  if (left) {
438  walkDirection.addLocal(camLeft);
439  }
440  if (right) {
441  walkDirection.addLocal(camLeft.negate());
442  }
443  if (up) {
444  walkDirection.addLocal(camDir);
445  }
446  if (down) {
447  walkDirection.addLocal(camDir.negate());
448  }
449  if(!LET_ME_FLY) walkDirection.y = 0;
450  player.setWalkDirection(walkDirection);
451 
452  cam.setLocation(player.getPhysicsLocation());
453 
454  now = (int) System.nanoTime() / 1000000;
455  if (!waiting4comtCtl) {
456  updateSubs();
457  }
458 
459  idCounter = (++idCounter)%5;
460  if(idCounter == 0) {
461  HashSet<Integer> tmp = new HashSet<Integer>();
462  for(Integer i : status.keySet())
463  if(!recentIds.contains(i)) tmp.add(i);
464  recentIds.clear();
465  for(Integer i : tmp) {
466  status.get(i).remove(rootNode);
467  status.remove(i);
468  }
469  }
470 
471  Forest.PktBuffer b;
472  while ((b = receive()) != null) {
473  PktHeader h = new PktHeader();
474  h.unpack(b);
475  Forest.PktTyp ptyp = h.getPtype();
476  if (!waiting4comtCtl && ptyp == Forest.PktTyp.CLIENT_DATA) {
477  updateNearby(b);
478  draw(b);
479  } else if (waiting4comtCtl && ptyp == Forest.PktTyp.CLIENT_SIG) {
480  CtlPkt cp = new CtlPkt();
481  cp.unpack(b);
482  if (cp.getCpType() == CpTyp.CLIENT_JOIN_COMTREE
483  && cp.getRrType() == CtlPkt.CpRrTyp.POS_REPLY) {
484  waiting4comtCtl = false;
485  } else if (cp.getCpType() == CpTyp.CLIENT_LEAVE_COMTREE
486  && cp.getRrType() == CtlPkt.CpRrTyp.POS_REPLY) {
487  comtree = newcomt;
488  sendCtlPkt2CC(true, comtree);
489  }
490  }
491  }
492  if (!waiting4comtCtl) {
493  sendStatus(now);
494  }
495  now = (int) System.nanoTime() / 1000000;
496  int delay = nextTime - now;
497  if (delay < (1 << 31)) {
498  try {
499  Thread.sleep(delay);
500  } catch (InterruptedException ex) {
501  Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
502  }
503  } else {
504  nextTime = now + 1000 * UPDATE_PERIOD;
505  }
506  }
511  public void sendStatus(int now) {
512  Forest.PktBuffer b = new Forest.PktBuffer();
513  PktHeader h = new PktHeader();
514  h.setLength(4 * (5 + 8));
515  h.setPtype(Forest.PktTyp.CLIENT_DATA);
516  h.setFlags((short) 0);
517  h.setComtree(comtree);
518  h.setSrcAdr(myAdr);
519  h.setDstAdr(-groupNum(getX(), getY()));
520  h.pack(b);
521 
522  b.set(Forest.HDR_LENG/4 , STATUS_REPORT);
523  b.set(Forest.HDR_LENG/4 + 1, now);
524  b.set(Forest.HDR_LENG/4 + 2, getX());
525  b.set(Forest.HDR_LENG/4 + 3, getY());
526  b.set(Forest.HDR_LENG/4 + 4, getDirection());
527  b.set(Forest.HDR_LENG/4 + 5, 0);//TODO
528  b.set(Forest.HDR_LENG/4 + 6, numNear);
529  b.set(Forest.HDR_LENG/4 + 7, numVisible);
530  send(b, h.getLength());
531  }
536  public Forest.PktBuffer receive() {
537  if (needCliProxy) {
538  Forest.PktBuffer b = new Forest.PktBuffer();
539  buf.clear();
540  buf.limit(4);
541  try {
542  int nbytes = chan.read(buf);
543  if (nbytes <= 0) {
544  return null;
545  }
546  int length = buf.getInt(0);
547  buf.clear();
548  buf.limit(length);
549  while (buf.position() != buf.limit()) {
550  chan.read(buf);
551  }
552  for (int i = 0; i < length / 4; i++) {
553  b.set(i, buf.getInt(i * 4));
554  }
555  } catch (IOException ex) {
556  System.out.println("I/O exception when reading");
557  System.out.println(ex);
558  System.exit(1);
559  }
560 
561  return b;
562  } else {
563  byte[] buff = new byte[1500];
564  Forest.PktBuffer b = new Forest.PktBuffer();
565  DatagramPacket p = new DatagramPacket(buff,1500);
566  try {
567  udpSock.receive(p);
568  for(int i = 0; i < p.getLength(); i+=4) {
569  int k = (buff[i] << 24) | (buff[i+1] << 16) |
570  (buff[i+2] << 8) | (buff[i+3]);
571  b.set(i/4, k);
572  }
573  } catch (IOException ex) {
574  System.out.println("I/O exception when reading");
575  System.out.println(ex);
576  System.exit(1);
577  }
578  return b;
579  }
580  }
584  public void connect() {
585  Forest.PktBuffer buff = new Forest.PktBuffer();
586  PktHeader h = new PktHeader();
587  h.setLength(4 * (5 + 1));
588  h.setPtype(Forest.PktTyp.CONNECT);
589  h.setFlags((short) 0);
590  h.setComtree(Forest.CLIENT_CON_COMT);
591  h.setSrcAdr(myAdr);
592  h.setDstAdr(rtrAdr);
593  h.pack(buff);
594  send(buff, h.getLength());
595  }
599  public void disconnect() {
600  Forest.PktBuffer buff = new Forest.PktBuffer();
601  PktHeader h = new PktHeader();
602  h.setLength(4 * (5 + 1));
603  h.setPtype(Forest.PktTyp.DISCONNECT);
604  h.setFlags((short) 0);
605  h.setComtree(Forest.CLIENT_CON_COMT);
606  h.setSrcAdr(myAdr);
607  h.setDstAdr(rtrAdr);
608  h.pack(buff);
609  send(buff, h.getLength());
610  }
616  public void send(Forest.PktBuffer b, int length) {
617  if (needCliProxy) {
618  try {
619  b.put2BytBuf(buf, length);
620  buf.flip();
621  chan.write(buf);
622  chan.socket().getOutputStream().flush();
623  } catch (IOException ex) {
624  System.out.println("Could not send tcp packet");
625  System.out.println(ex);
626  System.exit(1);
627  }
628  } else {
629  try {
630  byte[] pkt = b.toByteArray(length);
631  InetAddress ip = InetAddress.getByAddress(Forest.ip2byteArr(rtrIpAdr));
632  udpSock.send(new DatagramPacket(pkt,pkt.length,ip,Forest.ROUTER_PORT));
633  } catch (Exception ex) {
634  System.out.println("Could not send datagram packet");
635  System.out.println(ex);
636  System.exit(1);
637  }
638  }
639  }
645  public void sendCtlPkt2CC(boolean join, int comt) {
646  Forest.PktBuffer buff = new Forest.PktBuffer();
647  CpTyp cpType = (join ? CpTyp.CLIENT_JOIN_COMTREE : CpTyp.CLIENT_LEAVE_COMTREE);
648  CtlPkt cp = new CtlPkt(cpType, CtlPkt.CpRrTyp.REQUEST, seqNum++);
649  cp.setAttr(CpAttr.COMTREE_NUM, comt);
650  cp.setAttr(CpAttr.CLIENT_IP, needCliProxy ? cpIpAdr : myIpAdr);
651  cp.setAttr(CpAttr.CLIENT_PORT, needCliProxy ? cpForestPort : udpSock.getLocalPort());
652  int leng = cp.pack(buff);
653  if (leng == 0) {
654  System.out.println("Couldn't pack control packt");
655  System.exit(1);
656  }
657  PktHeader h = new PktHeader();
658  h.setLength(Forest.OVERHEAD + leng);
659  h.setPtype(Forest.PktTyp.CLIENT_SIG);
660  h.setComtree(Forest.CLIENT_SIG_COMT);
661  h.setSrcAdr(myAdr);
662  h.setDstAdr(ccAdr);
663  h.pack(buff);
664  send(buff, Forest.OVERHEAD + leng);
665  }
672  public boolean isVis(int g1, int g2) {
673  int region1 = g1 - 1; int region2 = g2 - 1;
674  int[] region1xs = new int[4];
675  int[] region1ys = new int[4];
676  int[] region2xs = new int[4];
677  int[] region2ys = new int[4];
678 
679  int row1 = region1 / worldSize;
680  int col1 = region1 % worldSize;
681  int row2 = region2 / worldSize;
682  int col2 = region2 % worldSize;
683 
684  region1xs[0] = region1xs[2] = col1 * GRID + 1;
685  region1ys[0] = region1ys[1] = (row1 + 1) * GRID - 1;
686  region1xs[1] = region1xs[3] = (col1 + 1) * GRID - 1;
687  region1ys[2] = region1ys[3] = row1 * GRID + 1;
688  region2xs[0] = region2xs[2] = col2 * GRID + 1;
689  region2ys[0] = region2ys[1] = (row2 + 1) * GRID - 1;
690  region2xs[1] = region2xs[3] = (col2 + 1) * GRID - 1;
691  region2ys[2] = region2ys[3] = row2 * GRID + 1;
692 
693  int minRow = Math.min(row1, row2);
694  int maxRow = Math.max(row1, row2);
695  int minCol = Math.min(col1, col2);
696  int maxCol = Math.max(col1, col2);
697 
698  for (int i = 0; i < 4; i++) {
699  for (int j = 0; j < 4; j++) {
700  boolean canSee = true;
701  double ax = (double) region1xs[i];
702  double ay = (double) region1ys[i];
703  double bx = (double) region2xs[j];
704  double by = (double) region2ys[j];
705  // line segment ab connects a corner of region1
706  // to a corner of region2
707  for (int y = minRow; y <= maxRow; y++) {
708  for (int z = minCol; z <= maxCol; z++) {
709  double cx = (double) z * GRID;
710  double cy = (double) (y + 1) * GRID;
711  // (cx,cy) is top left corner of region (i,j)
712  int k = y * worldSize + z; // k=region index
713  if (walls[k] == 1 || walls[k] == 3) {
714  // region k has a left side wall
715  // check if ab intersects it
716  double dx = cx;
717  double dy = cy - GRID;
718  if (linesIntersect(ax, ay, bx, by,
719  cx, cy, dx, dy)) {
720  canSee = false;
721  break;
722  } else {
723  }
724  }
725  if (walls[k] == 2 || walls[k] == 3) {
726  // region k has a top wall
727  // check if ab intersects it
728  double dx = cx + GRID;
729  double dy = cy;
730  if (linesIntersect(ax, ay, bx, by,
731  cx, cy, dx, dy)) {
732  canSee = false;
733  break;
734  } else {
735  }
736  }
737  }
738  if (!canSee) {
739  break;
740  }
741  }
742  if (canSee) {
743  return true;
744  }
745  }
746  }
747  return false;
748  }
761  public boolean linesIntersect(double ax, double ay, double bx, double by,
762  double cx, double cy, double dx, double dy) {
763  double epsilon = .0001; // two lines are considered vertical if
764  // x values differ by less than epsilon
765  // first handle special case of two vertical lines
766  if (Math.abs(ax - bx) < epsilon && Math.abs(cx - dx) < epsilon) {
767  return Math.abs(ax - cx) < epsilon && Math.max(ay, by) >= Math.min(cy, dy)
768  && Math.min(ay, by) <= Math.max(cy, dy);
769  }
770  // now handle cases of one vertical line
771  if (Math.abs(ax - bx) < epsilon) {
772  double s2 = (dy - cy) / (dx - cx); // slope of second line
773  double i2 = cy - s2 * cx; // y-intercept of second line
774  double y = s2 * ax + i2; // lines intersect at (ax,y)
775  return (y >= Math.min(ay, by) && y <= Math.max(ay, by)
776  && y >= Math.min(cy, dy) && y <= Math.max(cy, dy));
777  }
778  if (Math.abs(cx - dx) < epsilon) {
779  double s1 = (by - ay) / (bx - ax); // slope of first line
780  double i1 = ay - s1 * ax; // y-intercept of first line
781  double y = s1 * cx + i1; // lines intersect at (cx,y)
782  return (y >= Math.min(ay, by) && y <= Math.max(ay, by)
783  && y >= Math.min(cy, dy) && y <= Math.max(cy, dy));
784  }
785  double s1 = (by - ay) / (bx - ax); // slope of first line
786  double i1 = ay - s1 * ax; // y-intercept of first line
787  double s2 = (dy - cy) / (dx - cx); // slope of second line
788  double i2 = cy - s2 * cx; // y-intercept of second line
789 
790  // handle special case of lines with equal slope
791  // treat the slopes as equal if both slopes have very small
792  // magnitude, or if their relative difference is small
793  if (Math.abs(s1) + Math.abs(s2) <= epsilon
794  || Math.abs(s1 - s2) / (Math.abs(s1) + Math.abs(s2)) < epsilon) {
795  return (Math.abs(i1 - i2) < epsilon
796  && Math.min(ax, bx) <= Math.max(cx, dx)
797  && Math.max(ax, bx) >= Math.min(cx, dx));
798  }
799  // now, to the common case
800  double x = (i2 - i1) / (s1 - s2); // x value where the lines interesect
801 
802  return (x >= Math.min(ax, bx) && x <= Math.max(ax, bx)
803  && x >= Math.min(cx, dx) && x <= Math.max(cx, dx));
804  }
808  public void readWalls() {
809  walls = new int[worldSize * worldSize];
810  Scanner in = null;
811  try {
812  in = new Scanner(new File(mapfile));
813  } catch (FileNotFoundException ex) {
814  System.out.println("couldn't read mapfile");
815  System.exit(1);
816  }
817  int lineCnt = 1;
818  while (true) {
819  String line;
820  line = in.nextLine();
821  if (line.length() < worldSize) {
822  System.out.println("format error, all lines must have same length");
823  System.exit(1);
824  }
825  for (int i = 0; i < worldSize; i++) {
826  if (line.charAt(i) == '+') {
827  walls[(worldSize - lineCnt) * worldSize + i] = 3;
828  } else if (line.charAt(i) == '-') {
829  walls[(worldSize - lineCnt) * worldSize + i] = 2;
830  } else if (line.charAt(i) == '|') {
831  walls[(worldSize - lineCnt) * worldSize + i] = 1;
832  } else if (line.charAt(i) == ' ') {
833  walls[(worldSize - lineCnt) * worldSize + i] = 0;
834  } else {
835  System.out.println("Unrecognized symbol in map file!");
836  System.exit(1);
837  }
838  }
839  if (lineCnt == worldSize) {
840  break;
841  }
842  lineCnt++;
843  }
844  in.close();
845  }
849  public void setupVisibility() {
850  readWalls();
851  visSet = new HashSet[worldSize * worldSize + 1];
852  for(int i = 0; i <= worldSize * worldSize; i++) {
853  visSet[i] = new HashSet<Integer>();
854  }
855  for (int x1 = 0; x1 < worldSize; x1++) {
856  for (int y1 = 0; y1 < worldSize; y1++) {
857  int g1 = 1 + x1 + y1 * worldSize;
858  //start with upper right quadrant
859  for (int d = 1; d < worldSize; d++) {
860  boolean done = true;
861  for (int x2 = x1; x2 <= Math.min(x1 + d, worldSize - 1); x2++) {
862  int y2 = d + y1 - (x2 - x1);
863  if (y2 >= worldSize) continue;
864  int g2 = 1 + x2 + y2 * worldSize;
865  if (isVis(g1, g2)) {
866  visSet[g1].add(g2);
867  done = false;
868  }
869  }
870  if (done) break;
871  }
872  //now upper left quadrant
873  for (int d = 1; d < worldSize; d++) {
874  boolean done = true;
875  for (int x2 = x1; x2 >= Math.max(0, x1 - d); x2--) {
876  int y2 = d + y1 - (x1 - x2);
877  if (y2 >= worldSize) continue;
878  int g2 = 1 + x2 + y2 * worldSize;
879  if (isVis(g1, g2)) {
880  visSet[g1].add(g2);
881  done = false;
882  }
883  }
884  if (done) break;
885  }
886  //lower left quadrant
887  for (int d = 1; d < worldSize; d++) {
888  boolean done = true;
889  for (int x2 = x1; x2 >= Math.max(0, x1 - d); x2--) {
890  int y2 = (x1 - x2) + y1 - d;
891  if (y2 < 0) continue;
892  int g2 = 1 + x2 + y2 * worldSize;
893  if (isVis(g1, g2)) {
894  visSet[g1].add(g2);
895  done = false;
896  }
897  }
898  if (done) break;
899  }
900  //lower right quadrant
901  for (int d = 1; d < worldSize; d++) {
902  boolean done = true;
903  for (int x2 = x1; x2 <= Math.min(x1 + d, worldSize - 1); x2++) {
904  int y2 = (x2 - x1) + y1 - d;
905  if (y2 < 0) continue;
906  int g2 = 1 + x2 + y2 * worldSize;
907  if (isVis(g1, g2)) {
908  visSet[g1].add(g2);
909  done = false;
910  }
911  }
912  if (done) break;
913  }
914  }
915  }
916  int maxVis = 0; int totVis = 0;
917  for(int g = 1; g <= worldSize*worldSize; g++) {
918  int vis = visSet[g].size();
919  maxVis = Math.max(vis,maxVis); totVis += vis;
920  }
921  System.out.println("avg visible: " + totVis/(worldSize*worldSize) +
922  " max visible: " + maxVis);
923  }
930  public int groupNum(int x1, int y1) {
931  return (1 + x1 / GRID) + (y1 / GRID) * worldSize;
932  }
936  public void unsubscribe(ArrayList<Integer> glist) {
937  if(glist.isEmpty()) return;
938  PktHeader h = new PktHeader();
939  Forest.PktBuffer b = new Forest.PktBuffer();
940  int nunsub = 0;
941  for (Integer g : glist) {
942  nunsub++;
943  if (nunsub > 350) {
944  b.set(Forest.HDR_LENG/4 , 0);
945  b.set(Forest.HDR_LENG/4 + 1, nunsub - 1);
946  h.setLength(Forest.OVERHEAD + 4 * (2 + nunsub));
947  h.setPtype(Forest.PktTyp.SUB_UNSUB);
948  h.setFlags((short) 0);
949  h.setComtree(comtree);
950  h.setSrcAdr(myAdr);
951  h.setDstAdr(rtrAdr);
952  h.pack(b);
953  send(b, h.getLength());
954  }
955  b.set(Forest.HDR_LENG/4 + nunsub + 1, -g);
956  }
957  b.set(Forest.HDR_LENG/4 , 0);
958  b.set(Forest.HDR_LENG/4 + 1, nunsub);
959 
960  h.setLength(Forest.OVERHEAD + 4 * (2 + nunsub));
961  h.setPtype(Forest.PktTyp.SUB_UNSUB);
962  h.setFlags((short) 0);
963  h.setComtree(comtree);
964  h.setSrcAdr(myAdr);
965  h.setDstAdr(rtrAdr);
966  h.pack(b);
967  send(b, h.getLength());
968  }
969  /*
970  * Send subscription packet for all groups in glist
971  */
972  public void subscribe(ArrayList<Integer> glist) {
973  if(glist.isEmpty()) return;
974  PktHeader h = new PktHeader();
975  Forest.PktBuffer b = new Forest.PktBuffer();
976  int nsub = 0;
977  for (Integer g : glist) {
978  nsub++;
979  if (nsub > 350) {
980  b.set(Forest.HDR_LENG/4, nsub - 1);
981  b.set(Forest.HDR_LENG/4 + nsub, 0);
982  h.setLength(Forest.OVERHEAD + 4 * (2 + nsub));
983  h.setPtype(Forest.PktTyp.SUB_UNSUB);
984  h.setFlags((short) 0);
985  h.setComtree(comtree);
986  h.setSrcAdr(myAdr);
987  h.setDstAdr(rtrAdr);
988  h.pack(b);
989  send(b, h.getLength());
990  }
991  b.set(Forest.HDR_LENG/4 + nsub, -g);
992  }
993  b.set(Forest.HDR_LENG/4 , nsub);
994  b.set(Forest.HDR_LENG/4 + nsub + 1, 0);
995  h.setLength(Forest.OVERHEAD + 4 * (2 + nsub));
996  h.setPtype(Forest.PktTyp.SUB_UNSUB);
997  h.setFlags((short) 0);
998  h.setComtree(comtree);
999  h.setSrcAdr(myAdr);
1000  h.setDstAdr(rtrAdr);
1001  h.pack(b);
1002  send(b, h.getLength());
1003  }
1004  /*
1005  * Update subscriptions based on multicast group location
1006  */
1007  public void updateSubs() {
1008  int myGroup = groupNum(getX(), getY());
1009  ArrayList<Integer> glist = new ArrayList<Integer>();
1010  for (Integer g : mySubs) {
1011  if (!visSet[myGroup].contains(g)) {
1012  glist.add(g);
1013  }
1014  }
1015  mySubs.removeAll(glist);
1016  unsubscribe(glist);
1017 
1018  glist.clear();
1019 
1020  for (Integer g : visSet[myGroup]) {
1021  if (!mySubs.contains(g)) {
1022  mySubs.add(g);
1023  glist.add(g);
1024  }
1025  }
1026  subscribe(glist);
1027  }
1028  /*
1029  * Redraw avatars in their new locations as specified by the pkt
1030  */
1031  public void draw(Forest.PktBuffer b) {
1032  PktHeader h = new PktHeader();
1033  h.unpack(b);
1034  AvatarStatus as = new AvatarStatus();
1035  as.id = h.getSrcAdr();
1036  as.when = b.get(Forest.HDR_LENG/4 + 1);
1037  as.x = b.get(Forest.HDR_LENG/4 + 2);
1038  as.x = (as.x / (GRID * worldSize) * 512 - 256);
1039  as.y = b.get(Forest.HDR_LENG/4 + 3);
1040  as.y = -1 * (as.y / (GRID * worldSize) * 512 - 256);
1041  as.dir = b.get(Forest.HDR_LENG/4 + 4);
1042  as.numNear = b.get(Forest.HDR_LENG/4 + 6);
1043  as.numVisible = b.get(Forest.HDR_LENG/4 + 7);
1044 
1045  recentIds.add(as.id);
1046  AvatarGraphic ag = status.get(as.id);
1047  if(ag == null) {
1048  ag = new AvatarGraphic(as,assetManager,rootNode);
1049  status.put(as.id, ag);
1050  } else ag.update(as);
1051  }
1052  /*
1053  * Similar to the Avatar.cpp updateNearby, keeps track of how many avatars
1054  * visible/nearby
1055  */
1056  public void updateNearby(Forest.PktBuffer b) {
1057  PktHeader h = new PktHeader();
1058  h.unpack(b);
1059  if (b.get(0) != STATUS_REPORT) {
1060  return;
1061  }
1062 
1063  int x1 = b.get(Forest.HDR_LENG/4 + 2);
1064  int y1 = b.get(Forest.HDR_LENG/4 + 3);
1065  int key = h.getSrcAdr();
1066  if (!nearAvatars.containsKey(key)) {
1067  if (numNear <= MAXNEAR) {
1068  nearAvatars.put(key, ++numNear);
1069  }
1070  }
1071  boolean canSee = true;
1072  for (int i = 0; i < worldSize * worldSize; i++) {
1073  if (walls[i] == 1 || walls[i] == 3) {
1074  int l2x1 = (i % worldSize) * GRID;
1075  int l2x2 = l2x1;
1076  int l2y1 = (i / worldSize) * GRID;
1077  int l2y2 = l2y1 + GRID;
1078  if (linesIntersect(x1, y1, getX(), getY(), l2x1, l2y1, l2x2, l2y2)) {
1079  canSee = false;
1080  break;
1081  }
1082  }
1083  if (walls[i] == 2 || walls[i] == 3) {
1084  int l2x1 = (i % worldSize) * GRID;
1085  int l2x2 = l2x1 + GRID;
1086  int l2y1 = (i / worldSize) * GRID + GRID;
1087  int l2y2 = l2y1;
1088  if (linesIntersect(x1, y1, getX(), getY(), l2x1, l2y1, l2x2, l2y2)) {
1089  canSee = false;
1090  break;
1091  }
1092  }
1093  }
1094  if (canSee) {
1095  if (!visibleAvatars.containsKey(key)) {
1096  if (numVisible <= MAXNEAR) {
1097  visibleAvatars.put(key, ++numVisible);
1098  }
1099  }
1100  }
1101  }
1102  /*
1103  * Computes x in the forest context from the local context
1104  */
1105  public int getX() {
1106  return (int) (((player.getPhysicsLocation().x + 256) / 512.0) * worldSize * GRID);
1107  }
1108  /*
1109  * Computes y in the forest context from the local context
1110  */
1111  public int getY() {
1112  return ((int) (((-1*player.getPhysicsLocation().z + 256) / 512.0) * worldSize * GRID));
1113  }
1118  public int getDirection() {
1119  float atan = FastMath.atan2(player.getWalkDirection().x, player.getWalkDirection().z);
1120  return (int) (atan * (180 / Math.PI));
1121  }
1122 }