1 /*
2  * ObserverCameraController.java 09 mars 2012
3  *
4  * Sweet Home 3D, Copyright (c) 2012 Emmanuel PUYBARET / eTeks <info@eteks.com>
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  */
20 package com.eteks.sweethome3d.viewcontroller;
21 
22 import java.beans.PropertyChangeListener;
23 import java.beans.PropertyChangeSupport;
24 import java.util.List;
25 
26 import com.eteks.sweethome3d.model.Home;
27 import com.eteks.sweethome3d.model.HomeEnvironment;
28 import com.eteks.sweethome3d.model.Level;
29 import com.eteks.sweethome3d.model.ObserverCamera;
30 import com.eteks.sweethome3d.model.UserPreferences;
31 
32 /**
33  * A MVC controller for observer camera attributes view.
34  * @author Emmanuel Puybaret
35  */
36 public class ObserverCameraController implements Controller {
37   /**
38    * The properties that may be edited by the view associated to this controller.
39    */
40   public enum Property {X, Y, ELEVATION, MINIMUM_ELEVATION,
41       YAW_IN_DEGREES, YAW, PITCH_IN_DEGREES, PITCH, FIELD_OF_VIEW_IN_DEGREES, FIELD_OF_VIEW,
42       OBSERVER_CAMERA_ELEVATION_ADJUSTED}
43 
44   private final Home                  home;
45   private final UserPreferences       preferences;
46   private final ViewFactory           viewFactory;
47   private final PropertyChangeSupport propertyChangeSupport;
48   private DialogView                  observerCameraView;
49 
50   private float             x;
51   private float             y;
52   private float             elevation;
53   private float             minimumElevation;
54   private int               yawInDegrees;
55   private float             yaw;
56   private int               pitchInDegrees;
57   private float             pitch;
58   private int               fieldOfViewInDegrees;
59   private float             fieldOfView;
60   private boolean           elevationAdjusted;
61 
62   /**
63    * Creates the controller of 3D view with undo support.
64    */
ObserverCameraController(Home home, UserPreferences preferences, ViewFactory viewFactory)65   public ObserverCameraController(Home home,
66                                   UserPreferences preferences,
67                                   ViewFactory viewFactory) {
68     this.home = home;
69     this.preferences = preferences;
70     this.viewFactory = viewFactory;
71     this.propertyChangeSupport = new PropertyChangeSupport(this);
72 
73     updateProperties();
74   }
75 
76   /**
77    * Returns the view associated with this controller.
78    */
getView()79   public DialogView getView() {
80     // Create view lazily only once it's needed
81     if (this.observerCameraView == null) {
82       this.observerCameraView = this.viewFactory.createObserverCameraView(this.preferences, this);
83     }
84     return this.observerCameraView;
85   }
86 
87   /**
88    * Displays the view controlled by this controller.
89    */
displayView(View parentView)90   public void displayView(View parentView) {
91     getView().displayView(parentView);
92   }
93 
94   /**
95    * Adds the property change <code>listener</code> in parameter to this controller.
96    */
addPropertyChangeListener(Property property, PropertyChangeListener listener)97   public void addPropertyChangeListener(Property property, PropertyChangeListener listener) {
98     this.propertyChangeSupport.addPropertyChangeListener(property.name(), listener);
99   }
100 
101   /**
102    * Removes the property change <code>listener</code> in parameter from this controller.
103    */
removePropertyChangeListener(Property property, PropertyChangeListener listener)104   public void removePropertyChangeListener(Property property, PropertyChangeListener listener) {
105     this.propertyChangeSupport.removePropertyChangeListener(property.name(), listener);
106   }
107 
108   /**
109    * Updates edited properties from the 3D attributes of the home edited by this controller.
110    */
updateProperties()111   protected void updateProperties() {
112     ObserverCamera observerCamera = this.home.getObserverCamera();
113     setX(observerCamera.getX());
114     setY(observerCamera.getY());
115     List<Level> levels = this.home.getLevels();
116     setMinimumElevation(levels.size() == 0
117         ? 10
118         : 10 + levels.get(0).getElevation());
119     setElevation(observerCamera.getZ());
120     setYaw(observerCamera.getYaw());
121     setPitch(observerCamera.getPitch());
122     setFieldOfView(observerCamera.getFieldOfView());
123     HomeEnvironment homeEnvironment = this.home.getEnvironment();
124     setElevationAdjusted(homeEnvironment.isObserverCameraElevationAdjusted());
125   }
126 
127   /**
128    * Sets the edited abscissa.
129    */
setX(float x)130   public void setX(float x) {
131     if (x != this.x) {
132       float oldX = this.x;
133       this.x = x;
134       this.propertyChangeSupport.firePropertyChange(Property.X.name(), oldX, x);
135     }
136   }
137 
138   /**
139    * Returns the edited abscissa.
140    */
getX()141   public float getX() {
142     return this.x;
143   }
144 
145   /**
146    * Sets the edited ordinate.
147    */
setY(float y)148   public void setY(float y) {
149     if (y != this.y) {
150       float oldY = this.y;
151       this.y = y;
152       this.propertyChangeSupport.firePropertyChange(Property.Y.name(), oldY, y);
153     }
154   }
155 
156   /**
157    * Returns the edited ordinate.
158    */
getY()159   public float getY() {
160     return this.y;
161   }
162 
163   /**
164    * Sets the edited camera elevation.
165    */
setElevation(float elevation)166   public void setElevation(float elevation) {
167     if (elevation != this.elevation) {
168       float oldObserverCameraElevation = this.elevation;
169       this.elevation = elevation;
170       this.propertyChangeSupport.firePropertyChange(Property.ELEVATION.name(), oldObserverCameraElevation, elevation);
171     }
172   }
173 
174   /**
175    * Returns the edited camera elevation.
176    */
getElevation()177   public float getElevation() {
178     return this.elevation;
179   }
180 
181   /**
182    * Sets the minimum elevation.
183    */
setMinimumElevation(float minimumElevation)184   private void setMinimumElevation(float minimumElevation) {
185     if (minimumElevation != this.minimumElevation) {
186       float oldMinimumElevation = this.minimumElevation;
187       this.minimumElevation = minimumElevation;
188       this.propertyChangeSupport.firePropertyChange(Property.MINIMUM_ELEVATION.name(), oldMinimumElevation, minimumElevation);
189     }
190   }
191 
192   /**
193    * Returns the minimum elevation.
194    */
getMinimumElevation()195   public float getMinimumElevation() {
196     return this.minimumElevation;
197   }
198 
199   /**
200    * Returns <code>true</code> if the observer elevation should be adjusted according
201    * to the elevation of the selected level.
202    */
isElevationAdjusted()203   public boolean isElevationAdjusted() {
204     return this.elevationAdjusted;
205   }
206 
207   /**
208    * Sets whether the observer elevation should be adjusted according
209    * to the elevation of the selected level.
210    */
setElevationAdjusted(boolean observerCameraElevationAdjusted)211   public void setElevationAdjusted(boolean observerCameraElevationAdjusted) {
212     if (this.elevationAdjusted != observerCameraElevationAdjusted) {
213       this.elevationAdjusted = observerCameraElevationAdjusted;
214       this.propertyChangeSupport.firePropertyChange(Property.OBSERVER_CAMERA_ELEVATION_ADJUSTED.name(),
215           !observerCameraElevationAdjusted, observerCameraElevationAdjusted);
216       Level selectedLevel = this.home.getSelectedLevel();
217       if (selectedLevel != null) {
218         if (observerCameraElevationAdjusted) {
219           setElevation(getElevation() - selectedLevel.getElevation());
220         } else {
221           setElevation(getElevation() + selectedLevel.getElevation());
222         }
223       }
224     }
225   }
226 
227   /**
228    * Returns <code>true</code> if the adjustment of the observer camera according to the current level is modifiable.
229    */
isObserverCameraElevationAdjustedEditable()230   public boolean isObserverCameraElevationAdjustedEditable() {
231     return this.home.getLevels().size() > 1;
232   }
233 
234   /**
235    * Sets the edited yaw in degrees.
236    */
setYawInDegrees(int yawInDegrees)237   public void setYawInDegrees(int yawInDegrees) {
238     setYawInDegrees(yawInDegrees, true);
239   }
240 
setYawInDegrees(int yawInDegrees, boolean updateYaw)241   private void setYawInDegrees(int yawInDegrees, boolean updateYaw) {
242     if (yawInDegrees != this.yawInDegrees) {
243       int oldYawInDegrees = this.yawInDegrees;
244       this.yawInDegrees = yawInDegrees;
245       this.propertyChangeSupport.firePropertyChange(Property.YAW_IN_DEGREES.name(), oldYawInDegrees, yawInDegrees);
246       if (updateYaw) {
247         setYaw((float)Math.toRadians(yawInDegrees), false);
248       }
249     }
250   }
251 
252   /**
253    * Returns the edited yaw in degrees.
254    */
getYawInDegrees()255   public int getYawInDegrees() {
256     return this.yawInDegrees;
257   }
258 
259   /**
260    * Sets the edited yaw in radians.
261    * @since 5.5
262    */
setYaw(float yaw)263   public void setYaw(float yaw) {
264     setYaw(yaw, true);
265   }
266 
setYaw(float yaw, boolean updateYawInDegrees)267   private void setYaw(float yaw, boolean updateYawInDegrees) {
268     if (yaw != this.yaw) {
269       float oldYaw = this.yaw;
270       this.yaw = yaw;
271       this.propertyChangeSupport.firePropertyChange(Property.YAW.name(), oldYaw, yaw);
272       if (updateYawInDegrees) {
273         setYawInDegrees((int)Math.round(Math.toDegrees(yaw)), false);
274       }
275     }
276   }
277 
278   /**
279    * Returns the edited yaw in radians.
280    * @since 5.5
281    */
getYaw()282   public float getYaw() {
283     return this.yaw;
284   }
285 
286   /**
287    * Sets the edited pitch in degrees.
288    */
setPitchInDegrees(int pitchInDegrees)289   public void setPitchInDegrees(int pitchInDegrees) {
290     setPitchInDegrees(pitchInDegrees, true);
291   }
292 
setPitchInDegrees(int pitchInDegrees, boolean updatePitch)293   private void setPitchInDegrees(int pitchInDegrees, boolean updatePitch) {
294     if (pitchInDegrees != this.pitchInDegrees) {
295       int oldPitchInDegrees = this.pitchInDegrees;
296       this.pitchInDegrees = pitchInDegrees;
297       this.propertyChangeSupport.firePropertyChange(Property.PITCH_IN_DEGREES.name(), oldPitchInDegrees, pitchInDegrees);
298       if (updatePitch) {
299         setPitch((float)Math.toRadians(pitchInDegrees), false);
300       }
301     }
302   }
303 
304   /**
305    * Returns the edited pitch in degrees.
306    */
getPitchInDegrees()307   public int getPitchInDegrees() {
308     return this.pitchInDegrees;
309   }
310 
311   /**
312    * Sets the edited pitch in radians.
313    * @since 5.5
314    */
setPitch(float pitch)315   public void setPitch(float pitch) {
316     setPitch(pitch, true);
317   }
318 
setPitch(float pitch, boolean updatePitchInDegrees)319   private void setPitch(float pitch, boolean updatePitchInDegrees) {
320     if (pitch != this.pitch) {
321       float oldPitch = this.pitch;
322       this.pitch = pitch;
323       this.propertyChangeSupport.firePropertyChange(Property.PITCH.name(), oldPitch, pitch);
324       if (updatePitchInDegrees) {
325         setPitchInDegrees((int)(Math.round(Math.toDegrees(pitch))), false);
326       }
327     }
328   }
329 
330   /**
331    * Returns the edited pitch in radians.
332    * @since 5.5
333    */
getPitch()334   public float getPitch() {
335     return this.pitch;
336   }
337 
338   /**
339    * Sets the edited observer field of view in degrees.
340    */
setFieldOfViewInDegrees(int fieldOfViewInDegrees)341   public void setFieldOfViewInDegrees(int fieldOfViewInDegrees) {
342     setFieldOfViewInDegrees(fieldOfViewInDegrees, true);
343   }
344 
setFieldOfViewInDegrees(int fieldOfViewInDegrees, boolean updateFieldOfView)345   public void setFieldOfViewInDegrees(int fieldOfViewInDegrees, boolean updateFieldOfView) {
346     if (fieldOfViewInDegrees != this.fieldOfViewInDegrees) {
347       int oldFieldOfViewInDegrees = this.fieldOfViewInDegrees;
348       this.fieldOfViewInDegrees = fieldOfViewInDegrees;
349       this.propertyChangeSupport.firePropertyChange(Property.FIELD_OF_VIEW_IN_DEGREES.name(),
350           oldFieldOfViewInDegrees, fieldOfViewInDegrees);
351       if (updateFieldOfView) {
352         setFieldOfView((float)Math.toRadians(fieldOfViewInDegrees), false);
353       }
354     }
355   }
356 
357   /**
358    * Returns the edited observer field of view in degrees.
359    */
getFieldOfViewInDegrees()360   public int getFieldOfViewInDegrees() {
361     return this.fieldOfViewInDegrees;
362   }
363 
364   /**
365    * Sets the edited observer field of view in radians.
366    * @since 5.5
367    */
setFieldOfView(float fieldOfView)368   public void setFieldOfView(float fieldOfView) {
369     setFieldOfView(fieldOfView, true);
370   }
371 
setFieldOfView(float fieldOfView, boolean updateFieldOfViewInDegrees)372   private void setFieldOfView(float fieldOfView, boolean updateFieldOfViewInDegrees) {
373     if (fieldOfView != this.fieldOfView) {
374       float oldFieldOfView = this.fieldOfView;
375       this.fieldOfView = fieldOfView;
376       this.propertyChangeSupport.firePropertyChange(Property.FIELD_OF_VIEW.name(), oldFieldOfView, fieldOfView);
377       if (updateFieldOfViewInDegrees) {
378         setFieldOfViewInDegrees((int)(Math.round(Math.toDegrees(fieldOfView))), false);
379       }
380     }
381   }
382 
383   /**
384    * Returns the edited observer field of view in radians.
385    * @since 5.5
386    */
getFieldOfView()387   public float getFieldOfView() {
388     return this.fieldOfView;
389   }
390 
391   /**
392    * Controls the modification of the observer camera of the edited home.
393    */
modifyObserverCamera()394   public void modifyObserverCamera() {
395     float x = getX();
396     float y = getY();
397     float z = getElevation();
398     boolean observerCameraElevationAdjusted = isElevationAdjusted();
399     Level selectedLevel = this.home.getSelectedLevel();
400     if (observerCameraElevationAdjusted && selectedLevel != null) {
401       z += selectedLevel.getElevation();
402       List<Level> levels = this.home.getLevels();
403       z = Math.max(z, levels.size() == 0  ? 10  : 10 + levels.get(0).getElevation());
404     }
405     float yaw = getYaw();
406     float pitch = getPitch();
407     float fieldOfView = getFieldOfView();
408 
409     // Apply modification with no undo
410     ObserverCamera observerCamera = this.home.getObserverCamera();
411     observerCamera.setX(x);
412     observerCamera.setY(y);
413     observerCamera.setZ(z);
414     observerCamera.setYaw(yaw);
415     observerCamera.setPitch(pitch);
416     observerCamera.setFieldOfView(fieldOfView);
417     HomeEnvironment homeEnvironment = this.home.getEnvironment();
418     homeEnvironment.setObserverCameraElevationAdjusted(observerCameraElevationAdjusted);
419   }
420 }
421