Initial commit, a stable version.
[bnac-editor.git] / src / cl.uchile.dcc.bnac.editor / SimulationView.java
blob1f3475e2e93c27ea7f7aa7b628a8711bf84df628
1 /*
2 * bnac : virtual museum environment creation/manipulation project
3 * Copyright (C) 2010 Felipe Cañas Sabat
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 package cl.uchile.dcc.bnac.editor;
21 import cl.uchile.dcc.bnac.Attribute;
22 import cl.uchile.dcc.bnac.Capture;
23 import cl.uchile.dcc.bnac.Door;
24 import cl.uchile.dcc.bnac.Hall;
25 import cl.uchile.dcc.bnac.PlacedCapture;
26 import cl.uchile.dcc.bnac.Wall;
28 import java.awt.BorderLayout;
29 import java.awt.Dimension;
30 import java.awt.GraphicsConfiguration;
31 import java.awt.event.ComponentEvent;
32 import java.awt.event.ComponentListener;
33 import java.awt.event.KeyEvent;
34 import java.awt.event.KeyListener;
35 import java.awt.event.MouseEvent;
36 import java.awt.event.MouseListener;
37 import java.awt.image.BufferedImage;
38 import java.util.Hashtable;
39 import javax.media.j3d.AmbientLight;
40 import javax.media.j3d.Appearance;
41 import javax.media.j3d.BoundingSphere;
42 import javax.media.j3d.BranchGroup;
43 import javax.media.j3d.Canvas3D;
44 import javax.media.j3d.Geometry;
45 import javax.media.j3d.GraphicsConfigTemplate3D;
46 import javax.media.j3d.Locale;
47 import javax.media.j3d.Material;
48 import javax.media.j3d.PhysicalBody;
49 import javax.media.j3d.PhysicalEnvironment;
50 import javax.media.j3d.PickInfo;
51 import javax.media.j3d.PickRay;
52 import javax.media.j3d.PointLight;
53 import javax.media.j3d.Shape3D;
54 import javax.media.j3d.Transform3D;
55 import javax.media.j3d.TransformGroup;
56 import javax.media.j3d.View;
57 import javax.media.j3d.ViewPlatform;
58 import javax.media.j3d.VirtualUniverse;
59 import javax.vecmath.Color3f;
60 import javax.vecmath.Point2f;
61 import javax.vecmath.Point3d;
62 import javax.vecmath.Point3f;
63 import javax.vecmath.Vector2f;
64 import javax.vecmath.Vector3d;
65 import javax.vecmath.Vector3f;
67 class SimulationView extends ProjectView
68 implements KeyListener, MouseListener, ComponentListener
70 /* J3D garbage */
71 protected Canvas3D cv;
72 protected VirtualUniverse u;
73 protected Locale loc;
74 protected BranchGroup vproot;
75 protected TransformGroup vptg;
76 protected ViewPlatform vp;
77 protected View vi;
78 protected BranchGroup scene;
80 /* movement info */
81 protected float angY;
82 protected Vector3f lookDir;
83 protected Vector3f position;
85 HallGraph hg;
86 String sid;
88 public SimulationView (Project p, GraphicsConfiguration gc)
90 super(p);
91 cv = new Canvas3D(gc.getDevice().getBestConfiguration(
92 new GraphicsConfigTemplate3D()));
93 u = new VirtualUniverse();
94 loc = new Locale(u);
95 vproot = new BranchGroup();
96 vptg = new TransformGroup();
97 vp = new ViewPlatform();
98 vi = new View();
99 scene = new BranchGroup();
101 lookDir = new Vector3f(0.0f, 0.0f, -1.0f);
102 position = new Vector3f(1.0f, 1.70f, -1.0f);
103 angY = 0.0f;
105 buildUniverse();
107 hg = project.getCurrentHallGraph();
108 if (hg != null) { scene.addChild(hg.getHandle()); }
109 loc.addBranchGraph(scene);
111 addComponentListener(this);
112 cv.addKeyListener(project);
113 cv.addKeyListener(this);
114 cv.addMouseListener(this);
116 setPreferredSize(new Dimension(720, 480));
118 setLayout(new BorderLayout());
119 add(cv, BorderLayout.CENTER);
122 protected void buildUniverse ()
124 vptg.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
125 vptg.addChild(vp);
126 vproot.addChild(vptg);
127 loc.addBranchGraph(vproot);
129 Transform3D t3d = new Transform3D();
130 t3d.rotY(angY);
131 t3d.set(position);
132 vptg.setTransform(t3d);
134 vi.setPhysicalBody(new PhysicalBody());
135 vi.setPhysicalEnvironment(new PhysicalEnvironment());
136 vi.addCanvas3D(cv);
137 vi.attachViewPlatform(vp);
139 scene.setCapability(BranchGroup.ALLOW_CHILDREN_READ);
140 scene.setCapability(BranchGroup.ALLOW_CHILDREN_WRITE);
141 scene.setCapability(BranchGroup.ALLOW_CHILDREN_EXTEND);
143 AmbientLight al = new AmbientLight();
144 al.setInfluencingBounds(new BoundingSphere(
145 new Point3d(0.0d, 0.0d, 0.0d), Double.MAX_VALUE));
146 al.setColor(new Color3f(1.0f, 1.0f, 1.0f));
147 scene.addChild(al);
149 PointLight pl = new PointLight();
150 pl.setInfluencingBounds(new BoundingSphere(
151 new Point3d(0.0d, 0.0d, 0.0d), Double.MAX_VALUE));
152 pl.setPosition(1.0f, 1.6f, -1.0f);
153 pl.setColor(new Color3f(1.0f, 1.0f, 1.0f));
154 pl.setAttenuation(0.7f, 0.2f, 0.02f);
155 scene.addChild(pl);
158 public void setHallGraph (HallGraph hg)
160 if (this.hg != null) { this.hg.detach(); }
161 this.hg = hg;
162 if (hg != null) { scene.addChild(hg.getHandle()); }
165 public void selectElement (String id)
167 sid = id;
168 if (hg.getPortraitGraph(id) != null) {
169 hg.getPortraitGraph(id).select();
170 } else if (hg.getDoorGraph(id) != null) {
171 hg.getDoorGraph(id).select();
172 } else {
173 sid = null;
177 public void deselectElement (String id)
179 if (hg.getPortraitGraph(id) != null) {
180 hg.getPortraitGraph(id).deselect();
181 } else if (hg.getDoorGraph(id) != null) {
182 hg.getDoorGraph(id).deselect();
186 public void deselectAll ()
188 if (sid != null) { deselectElement(sid); }
189 sid = null;
192 public void updateLookDirection ()
194 if (angY < -Math.PI) { angY += 2.0*Math.PI; }
195 else if (angY > Math.PI) { angY -= 2.0*Math.PI; }
196 if (Math.abs(angY - Math.PI/2.0) < 0.001) {
197 angY = (float) (Math.PI/2.0);
198 lookDir.set(-1.0f, 0.0f, 0.0f);
199 } else if (Math.abs(angY + Math.PI/2.0) < 0.001) {
200 angY = (float) (-Math.PI/2.0);
201 lookDir.set(1.0f, 0.0f, 0.0f);
202 } else if (Math.abs(angY + Math.PI) < 0.001) {
203 angY = (float) Math.PI;
204 lookDir.set(0.0f, 0.0f, 1.0f);
205 } else if (Math.abs(angY - Math.PI) < 0.001) {
206 angY = (float) Math.PI;
207 lookDir.set(0.0f, 0.0f, 1.0f);
208 } else {
209 if (angY < Math.PI/2.0 && angY > -Math.PI/2.0) {
210 lookDir.set((float) -Math.tan(angY), 0.0f, -1.0f);
211 } else {
212 lookDir.set((float) -Math.tan(-angY), 0.0f, 1.0f);
214 lookDir.normalize();
218 public void updateView ()
220 Transform3D t3d = new Transform3D();
221 t3d.rotY(angY);
222 t3d.setTranslation(position);
223 vptg.setTransform(t3d);
226 public float getAngleY () { return angY; }
227 public Vector3f getLookDir () { return new Vector3f(lookDir); }
228 public Vector3f position () { return new Vector3f(position); }
229 public Vector2f position2f ()
231 return new Vector2f(position.x, -position.z);
234 public void keyPressed (KeyEvent e)
236 int kc = e.getKeyCode();
237 if (kc == KeyEvent.VK_U || kc == KeyEvent.VK_I) {
238 double ang = (kc == KeyEvent.VK_U) ? 5.0 : -5.0;
239 angY += (float) Math.toRadians(ang);
240 if (angY < -Math.PI) { angY += 2.0*Math.PI; }
241 else if (angY > Math.PI) { angY -= 2.0*Math.PI; }
242 updateLookDirection();
243 } else if (kc == KeyEvent.VK_W || kc == KeyEvent.VK_S) {
244 position.scaleAdd((kc==KeyEvent.VK_W) ? 0.25f : -0.25f,
245 lookDir, position);
246 } else if (kc == KeyEvent.VK_A || kc == KeyEvent.VK_D) {
247 Vector3f dir = new Vector3f();
248 dir.cross(lookDir, new Vector3f(0.0f, 1.0f, 0.0f));
249 dir.normalize();
250 position.scaleAdd((kc==KeyEvent.VK_A) ? -0.25f : 0.25f,
251 dir, position);
252 } else if (kc == KeyEvent.VK_R) {
253 position.set(1.0f, 1.70f, -1.0f);
254 lookDir.set(0.0f, 0.0f, -1.0f);
255 angY = 0.0f;
256 } else {
257 if (sid == null) { return; }
258 switch (kc) {
259 case KeyEvent.VK_DELETE:
260 deselectElement(sid);
261 if (hg.getPortraitGraph(sid) != null) {
262 project.pushCommand(new RemovePortraitGraphCommand(
263 project, hg, sid));
264 } else if (hg.getDoorGraph(sid) != null) {
265 project.pushCommand(new RemoveDoorGraphCommand(
266 project, hg, sid));
268 sid = null;
269 return;
270 case KeyEvent.VK_ESCAPE:
271 deselectAll();
272 return;
273 case KeyEvent.VK_DOWN:
274 project.pushCommand(new MoveElem2DCommand(project, hg,
275 sid, 0.0f, -0.05f));
276 break;
277 case KeyEvent.VK_UP:
278 project.pushCommand(new MoveElem2DCommand(project, hg,
279 sid, 0.0f, 0.05f));
280 break;
281 case KeyEvent.VK_RIGHT:
282 project.pushCommand(new MoveElem2DCommand(project, hg,
283 sid, 0.05f, 0.0f));
284 break;
285 case KeyEvent.VK_LEFT:
286 project.pushCommand(new MoveElem2DCommand(project, hg,
287 sid, -0.05f, 0.0f));
288 break;
289 case KeyEvent.VK_PERIOD:
290 project.pushCommand(
291 new RotateElem2DCommand(project, hg, sid,
292 (float) -Math.toRadians(-5.0)));
293 break;
294 case KeyEvent.VK_COMMA:
295 project.pushCommand(
296 new RotateElem2DCommand(project, hg, sid,
297 (float) -Math.toRadians(5.0)));
298 break;
299 case KeyEvent.VK_EQUALS:
300 project.pushCommand(
301 new ScaleElem2DCommand(project, hg, sid, 0.07f));
302 break;
303 case KeyEvent.VK_MINUS:
304 project.pushCommand(
305 new ScaleElem2DCommand(project, hg, sid, -0.07f));
306 break;
307 default:
308 return;
311 return;
313 updateView();
314 project.getPlotView().update();
317 public void keyReleased (KeyEvent e) { }
318 public void keyTyped (KeyEvent e) { }
320 public void mouseClicked (MouseEvent e)
322 Dimension d = cv.getSize();
323 double ratioX = (1.0*e.getX())/d.width;
324 double phi = Math.PI/4.0;
325 double ratioY = (1.0*e.getY())/d.height;
326 double theta = phi * d.height / d.width;
327 double angPick = angY - phi*(ratioX-0.5);
328 Vector3d dir;
330 if (angPick > Math.PI) { angPick -= Math.PI*2.0; }
331 else if (angPick < -Math.PI) { angPick += Math.PI*2.0; }
333 if (Math.abs(angPick - Math.PI/2.0) < 0.00) {
334 dir = new Vector3d(-1.0, 0.0, 0.0);
335 } else if (Math.abs(angPick + Math.PI/2.0) < 0.00) {
336 dir = new Vector3d(1.0, 0.0, 0.0);
337 } else {
338 if (angPick < Math.PI/2.0 && angPick > -Math.PI/2.0) {
339 dir = new Vector3d(-Math.tan(angPick), 0.0, -1.0);
340 } else {
341 dir = new Vector3d(-Math.tan(-angPick), 0.0, 1.0);
344 dir.normalize();
345 dir.y = -Math.tan(theta*(ratioY-0.5));
347 /* Check if it is horizonally within range, |θ| <= π/2 */
348 if (Math.abs(theta*(ratioY-0.5)) > Math.PI/2.0) {
349 deselectAll();
350 return;
352 dir.normalize();
353 BnacEditor.trace("pick dir: (%.02f,%.02f,%.02f)",
354 dir.x, dir.y, dir.z);
355 PickInfo pi = scene.pickClosest(
356 PickInfo.PICK_BOUNDS,
357 PickInfo.NODE,
358 new PickRay(
359 new Point3d(position),
362 if (pi == null) {
363 deselectAll();
364 } else if (pi.getNode() != null) {
365 String name = pi.getNode().getName();
366 BnacEditor.trace("pick hit: %s", name);
367 if (e.getClickCount() == 1) {
368 if (sid != null && !sid.equals(name)) {
369 deselectElement(sid);
371 if (sid == null || !sid.equals(name)) {
372 sid = name;
373 selectElement(sid);
375 } else if (e.getClickCount()==2 && hg.getDoorGraph(name)!=null) {
376 String d2 =
377 project.getMuseumEnvironment().getConnection(name);
379 if (d2 == null) {
380 BnacEditor.trace("%s has no link", name);
381 return;
384 BnacEditor.trace("%s -> %s", name, d2);
386 deselectAll();
388 Hall hall = null;
389 for (Hall tmp: project.getMuseumEnvironment().hallArray()) {
390 if (tmp.getDoor(d2) != null) {
391 hall = tmp;
392 project.setCurrentHall(tmp.getId());
393 break;
397 if (hall == null) {
398 BnacEditor.error("trying to go to nonexistent hall");
399 return;
402 BnacEditor.trace("going to hall %s", hall.getId());
404 Door door = hall.getDoor(d2);
405 Wall wall = hall.getWall(hall.getParentWall(d2));
407 float disp = door.getPosition().x;
409 Vector2f v = new Vector2f();
410 v.sub(wall.end2f(), wall.origin2f());
411 v.normalize();
413 Vector2f n2d = new Vector2f(v.y, -v.x);
414 n2d.normalize();
415 BnacEditor.trace("ori (%.02f,%.02f)", wall.origin2f().x,
416 wall.origin2f().y);
417 BnacEditor.trace("vec (%.02f,%.02f)", v.x, v.y);
418 BnacEditor.trace("nor (%.02f,%.02f)", n2d.x, n2d.y);
420 Vector2f pos = new Vector2f();
421 pos.scaleAdd(disp, v, wall.origin2f());
422 n2d.scale(0.80f);
423 pos.add(n2d);
424 BnacEditor.trace("pos (%.02f,%.02f)", pos.x, pos.y);
426 position.set(pos.x, 1.70f, -pos.y);
428 if (Math.abs(n2d.x) < 0.001f) {
429 angY = (n2d.y>0.0f) ? 0.0f : (float) Math.PI;
430 } else {
431 if (n2d.x > 0.0f) {
432 angY = -n2d.angle(new Vector2f(0.0f, 1.0f));
433 } else {
434 angY = n2d.angle(new Vector2f(0.0f, 1.0f));
438 BnacEditor.trace("@(%.02f,%.02f) %.02f",
439 pos.x, pos.y, (float) Math.toDegrees(angY));
441 updateLookDirection();
442 updateView();
447 public void mouseEntered (MouseEvent e) { }
448 public void mouseExited (MouseEvent e) { }
449 public void mousePressed (MouseEvent e) { }
450 public void mouseReleased (MouseEvent e) { }
452 public void componentResized (ComponentEvent e)
454 cv.setSize(getSize());
457 public void componentShown (ComponentEvent e) { }
458 public void componentHidden (ComponentEvent e) { }
459 public void componentMoved (ComponentEvent e) { }
461 abstract public class Elem2DCommand extends ProjectCommand
463 protected HallGraph hg;
464 protected String id;
466 public Elem2DCommand (Project project, String name,
467 HallGraph hg, String id)
469 super(project, name);
470 this.hg = hg;
471 this.id = id;
475 public class MoveElem2DCommand extends Elem2DCommand
477 protected float dx;
478 protected float dy;
480 public MoveElem2DCommand (Project project,HallGraph hg, String id,
481 float dx, float dy)
483 super(project, "Move", hg, id);
484 this.dx = dx;
485 this.dy = dy;
488 public float getX () { return dx; }
489 public float getY () { return dy; }
491 public void execute ()
493 hg.getElem2DGraph(id).editPosition(new Point2f(dx, dy));
495 public void undo ()
497 hg.getElem2DGraph(id).editPosition(new Point2f(-dx, -dy));
501 public class RotateElem2DCommand extends Elem2DCommand
503 protected float ang;
505 public RotateElem2DCommand (Project project, HallGraph hg,
506 String id, float ang)
508 super(project, "Rotate", hg, id);
509 this.ang = ang;
512 public float getAngle () { return ang; }
514 public void execute () { hg.getElem2DGraph(id).editAngle(ang); }
515 public void undo () { hg.getElem2DGraph(id).editAngle(-ang); }
518 public class ScaleElem2DCommand extends Elem2DCommand
520 protected float sca;
522 public ScaleElem2DCommand (Project project, HallGraph hg,
523 String id, float sca)
525 super(project, "Scale", hg, id);
526 this.sca = sca;
529 public float getScale () { return sca; }
531 public void execute () { hg.getElem2DGraph(id).editScale(sca); }
532 public void undo () { hg.getElem2DGraph(id).editScale(-sca); }
535 public class RemovePortraitGraphCommand extends ProjectCommand
537 protected HallGraph hg;
538 protected String id;
539 protected int widx;
540 protected PortraitGraph pg;
542 public RemovePortraitGraphCommand (Project project, HallGraph hg,
543 String id)
545 super(project, "REMOVE_PORTRAIT");
546 this.hg = hg;
547 this.id = id;
550 public void execute ()
552 widx = hg.getHall().getParentWall(id);
553 pg = hg.removePortraitGraph(id);
556 public void undo () { hg.addPortraitGraph(widx, pg); }
559 public class RemoveDoorGraphCommand extends ProjectCommand
561 protected HallGraph hg;
562 protected String id;
563 protected int widx;
564 protected DoorGraph pg;
566 public RemoveDoorGraphCommand (Project project, HallGraph hg,
567 String id)
569 super(project, "REMOVE_DOORS");
570 this.hg = hg;
571 this.id = id;
574 public void execute ()
576 widx = hg.getHall().getParentWall(id);
577 pg = hg.removeDoorGraph(id);
580 public void undo () { hg.addDoorGraph(widx, pg); }