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
71 protected Canvas3D cv
;
72 protected VirtualUniverse u
;
74 protected BranchGroup vproot
;
75 protected TransformGroup vptg
;
76 protected ViewPlatform vp
;
78 protected BranchGroup scene
;
82 protected Vector3f lookDir
;
83 protected Vector3f position
;
88 public SimulationView (Project p
, GraphicsConfiguration gc
)
91 cv
= new Canvas3D(gc
.getDevice().getBestConfiguration(
92 new GraphicsConfigTemplate3D()));
93 u
= new VirtualUniverse();
95 vproot
= new BranchGroup();
96 vptg
= new TransformGroup();
97 vp
= new ViewPlatform();
99 scene
= new BranchGroup();
101 lookDir
= new Vector3f(0.0f
, 0.0f
, -1.0f
);
102 position
= new Vector3f(1.0f
, 1.70f
, -1.0f
);
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
);
126 vproot
.addChild(vptg
);
127 loc
.addBranchGraph(vproot
);
129 Transform3D t3d
= new Transform3D();
132 vptg
.setTransform(t3d
);
134 vi
.setPhysicalBody(new PhysicalBody());
135 vi
.setPhysicalEnvironment(new PhysicalEnvironment());
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
));
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
);
158 public void setHallGraph (HallGraph hg
)
160 if (this.hg
!= null) { this.hg
.detach(); }
162 if (hg
!= null) { scene
.addChild(hg
.getHandle()); }
165 public void selectElement (String id
)
168 if (hg
.getPortraitGraph(id
) != null) {
169 hg
.getPortraitGraph(id
).select();
170 } else if (hg
.getDoorGraph(id
) != null) {
171 hg
.getDoorGraph(id
).select();
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
); }
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
);
209 if (angY
< Math
.PI
/2.0 && angY
> -Math
.PI
/2.0) {
210 lookDir
.set((float) -Math
.tan(angY
), 0.0f
, -1.0f
);
212 lookDir
.set((float) -Math
.tan(-angY
), 0.0f
, 1.0f
);
218 public void updateView ()
220 Transform3D t3d
= new Transform3D();
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
,
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
));
250 position
.scaleAdd((kc
==KeyEvent
.VK_A
) ?
-0.25f
: 0.25f
,
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
);
257 if (sid
== null) { return; }
259 case KeyEvent
.VK_DELETE
:
260 deselectElement(sid
);
261 if (hg
.getPortraitGraph(sid
) != null) {
262 project
.pushCommand(new RemovePortraitGraphCommand(
264 } else if (hg
.getDoorGraph(sid
) != null) {
265 project
.pushCommand(new RemoveDoorGraphCommand(
270 case KeyEvent
.VK_ESCAPE
:
273 case KeyEvent
.VK_DOWN
:
274 project
.pushCommand(new MoveElem2DCommand(project
, hg
,
278 project
.pushCommand(new MoveElem2DCommand(project
, hg
,
281 case KeyEvent
.VK_RIGHT
:
282 project
.pushCommand(new MoveElem2DCommand(project
, hg
,
285 case KeyEvent
.VK_LEFT
:
286 project
.pushCommand(new MoveElem2DCommand(project
, hg
,
289 case KeyEvent
.VK_PERIOD
:
291 new RotateElem2DCommand(project
, hg
, sid
,
292 (float) -Math
.toRadians(-5.0)));
294 case KeyEvent
.VK_COMMA
:
296 new RotateElem2DCommand(project
, hg
, sid
,
297 (float) -Math
.toRadians(5.0)));
299 case KeyEvent
.VK_EQUALS
:
301 new ScaleElem2DCommand(project
, hg
, sid
, 0.07f
));
303 case KeyEvent
.VK_MINUS
:
305 new ScaleElem2DCommand(project
, hg
, sid
, -0.07f
));
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);
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);
338 if (angPick
< Math
.PI
/2.0 && angPick
> -Math
.PI
/2.0) {
339 dir
= new Vector3d(-Math
.tan(angPick
), 0.0, -1.0);
341 dir
= new Vector3d(-Math
.tan(-angPick
), 0.0, 1.0);
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) {
353 BnacEditor
.trace("pick dir: (%.02f,%.02f,%.02f)",
354 dir
.x
, dir
.y
, dir
.z
);
355 PickInfo pi
= scene
.pickClosest(
356 PickInfo
.PICK_BOUNDS
,
359 new Point3d(position
),
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
)) {
375 } else if (e
.getClickCount()==2 && hg
.getDoorGraph(name
)!=null) {
377 project
.getMuseumEnvironment().getConnection(name
);
380 BnacEditor
.trace("%s has no link", name
);
384 BnacEditor
.trace("%s -> %s", name
, d2
);
389 for (Hall tmp
: project
.getMuseumEnvironment().hallArray()) {
390 if (tmp
.getDoor(d2
) != null) {
392 project
.setCurrentHall(tmp
.getId());
398 BnacEditor
.error("trying to go to nonexistent hall");
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());
413 Vector2f n2d
= new Vector2f(v
.y
, -v
.x
);
415 BnacEditor
.trace("ori (%.02f,%.02f)", wall
.origin2f().x
,
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());
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
;
432 angY
= -n2d
.angle(new Vector2f(0.0f
, 1.0f
));
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();
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
;
466 public Elem2DCommand (Project project
, String name
,
467 HallGraph hg
, String id
)
469 super(project
, name
);
475 public class MoveElem2DCommand
extends Elem2DCommand
480 public MoveElem2DCommand (Project project
,HallGraph hg
, String id
,
483 super(project
, "Move", hg
, id
);
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
));
497 hg
.getElem2DGraph(id
).editPosition(new Point2f(-dx
, -dy
));
501 public class RotateElem2DCommand
extends Elem2DCommand
505 public RotateElem2DCommand (Project project
, HallGraph hg
,
506 String id
, float ang
)
508 super(project
, "Rotate", hg
, id
);
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
522 public ScaleElem2DCommand (Project project
, HallGraph hg
,
523 String id
, float sca
)
525 super(project
, "Scale", hg
, id
);
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
;
540 protected PortraitGraph pg
;
542 public RemovePortraitGraphCommand (Project project
, HallGraph hg
,
545 super(project
, "REMOVE_PORTRAIT");
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
;
564 protected DoorGraph pg
;
566 public RemoveDoorGraphCommand (Project project
, HallGraph hg
,
569 super(project
, "REMOVE_DOORS");
574 public void execute ()
576 widx
= hg
.getHall().getParentWall(id
);
577 pg
= hg
.removeDoorGraph(id
);
580 public void undo () { hg
.addDoorGraph(widx
, pg
); }