/* * Camera.java 16 juin 07 * * Sweet Home 3D, Copyright (c) 2007 Emmanuel PUYBARET / eTeks <info@eteks.com> * * 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 of the License, 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; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package com.eteks.sweethome3d.model; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.io.IOException; import java.io.ObjectInputStream; import java.io.Serializable; import java.util.Calendar; import java.util.GregorianCalendar; import java.util.TimeZone; /** * Camera characteristics in home. * @author Emmanuel Puybaret */ public class Camera implements Serializable, Cloneable { /** * The kind of lens that can be used with a camera. * @author Emmanuel Puybaret * @since 3.0 */ public enum Lens {PINHOLE, NORMAL, FISHEYE, SPHERICAL} /** * The properties of a camera that may change. <code>PropertyChangeListener</code>s added * to a camera will be notified under a property name equal to the string value of one these properties. */ public enum Property {NAME, X, Y, Z, YAW, PITCH, FIELD_OF_VIEW, TIME, LENS} private static final long serialVersionUID = 1L; private String name; private float x; private float y; private float z; private float yaw; private float pitch; private float fieldOfView; private long time; private transient Lens lens; // Lens is saved as a string to be able to keep backward compatibility // if new constants are added to Lens enum in future versions private String lensName; private transient PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this); /** * Creates a camera at given location and angles at midday and using a pinhole lens. */ public Camera(float x, float y, float z, float yaw, float pitch, float fieldOfView) { this(x, y, z, yaw, pitch, fieldOfView, midday(), Lens.PINHOLE); } /** * Creates a camera at given location and angles. * @since 3.0 */ public Camera(float x, float y, float z, float yaw, float pitch, float fieldOfView, long time, Lens lens) { this.x = x; this.y = y; this.z = z; this.yaw = yaw; this.pitch = pitch; this.fieldOfView = fieldOfView; this.time = time; this.lens = lens; } /** * Returns the time of midday today in milliseconds since the Epoch in UTC time zone. */ private static long midday() { Calendar midday = new GregorianCalendar(TimeZone.getTimeZone("UTC")); midday.set(Calendar.HOUR_OF_DAY, 12); midday.set(Calendar.MINUTE, 0); midday.set(Calendar.SECOND, 0); return midday.getTimeInMillis(); } /** * Initializes new camera transient fields * and reads its properties from <code>in</code> stream with default reading method. */ private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { this.propertyChangeSupport = new PropertyChangeSupport(this); this.time = midday(); this.lens = Lens.PINHOLE; in.defaultReadObject(); try { // Read lens from a string if (this.lensName != null) { this.lens = Lens.valueOf(this.lensName); } } catch (IllegalArgumentException ex) { // Ignore malformed enum constant } } private void writeObject(java.io.ObjectOutputStream out) throws IOException { // Write lens as a string to be able to read lens later // even if enum changed in later versions this.lensName = this.lens.name(); out.defaultWriteObject(); } /** * Adds the property change <code>listener</code> in parameter to this camera. */ public void addPropertyChangeListener(PropertyChangeListener listener) { this.propertyChangeSupport.addPropertyChangeListener(listener); } /** * Removes the property change <code>listener</code> in parameter from this camera. */ public void removePropertyChangeListener(PropertyChangeListener listener) { this.propertyChangeSupport.removePropertyChangeListener(listener); } /** * Returns the name of this camera. * @since 3.0 */ public String getName() { return this.name; } /** * Sets the name of this camera and notifies listeners of this change. * @since 3.0 */ public void setName(String name) { if (name != this.name && (name == null || !name.equals(this.name))) { String oldName = this.name; this.name = name; this.propertyChangeSupport.firePropertyChange(Property.NAME.name(), oldName, name); } } /** * Returns the yaw angle in radians of this camera. */ public float getYaw() { return this.yaw; } /** * Sets the yaw angle in radians of this camera. */ public void setYaw(float yaw) { if (yaw != this.yaw) { float oldYaw = this.yaw; this.yaw = yaw; this.propertyChangeSupport.firePropertyChange(Property.YAW.name(), oldYaw, yaw); } } /** * Returns the pitch angle in radians of this camera. */ public float getPitch() { return this.pitch; } /** * Sets the pitch angle in radians of this camera. */ public void setPitch(float pitch) { if (pitch != this.pitch) { float oldPitch = this.pitch; this.pitch = pitch; this.propertyChangeSupport.firePropertyChange(Property.PITCH.name(), oldPitch, pitch); } } /** * Returns the field of view in radians of this camera. */ public float getFieldOfView() { return this.fieldOfView; } /** * Sets the field of view in radians of this camera. */ public void setFieldOfView(float fieldOfView) { if (fieldOfView != this.fieldOfView) { float oldFieldOfView = this.fieldOfView; this.fieldOfView = fieldOfView; this.propertyChangeSupport.firePropertyChange(Property.FIELD_OF_VIEW.name(), oldFieldOfView, fieldOfView); } } /** * Returns the abscissa of this camera. */ public float getX() { return this.x; } /** * Sets the abscissa of this camera. */ public void setX(float x) { if (x != this.x) { float oldX = this.x; this.x = x; this.propertyChangeSupport.firePropertyChange(Property.X.name(), oldX, x); } } /** * Returns the ordinate of this camera. */ public float getY() { return this.y; } /** * Sets the ordinate of this camera. */ public void setY(float y) { if (y != this.y) { float oldY = this.y; this.y = y; this.propertyChangeSupport.firePropertyChange(Property.Y.name(), oldY, y); } } /** * Returns the elevation of this camera. */ public float getZ() { return this.z; } /** * Sets the elevation of this camera. */ public void setZ(float z) { if (z != this.z) { float oldZ = this.z; this.z = z; this.propertyChangeSupport.firePropertyChange(Property.Z.name(), oldZ, z); } } /** * Returns the time in milliseconds when this camera is used. * @return a time in milliseconds since the Epoch in UTC time zone * @since 3.0 */ public long getTime() { return this.time; } /** * Sets the use time in milliseconds since the Epoch in UTC time zone, * and notifies listeners of this change. * @since 3.0 */ public void setTime(long time) { if (this.time != time) { long oldTime = this.time; this.time = time; this.propertyChangeSupport.firePropertyChange(Property.TIME.name(), oldTime, time); } } /** * Returns a time expressed in UTC time zone converted to the given time zone. * @since 3.0 */ public static long convertTimeToTimeZone(long utcTime, String timeZone) { Calendar utcCalendar = new GregorianCalendar(TimeZone.getTimeZone("UTC")); utcCalendar.setTimeInMillis(utcTime); Calendar convertedCalendar = new GregorianCalendar(TimeZone.getTimeZone(timeZone)); convertedCalendar.set(Calendar.YEAR, utcCalendar.get(Calendar.YEAR)); convertedCalendar.set(Calendar.MONTH, utcCalendar.get(Calendar.MONTH)); convertedCalendar.set(Calendar.DAY_OF_MONTH, utcCalendar.get(Calendar.DAY_OF_MONTH)); convertedCalendar.set(Calendar.HOUR_OF_DAY, utcCalendar.get(Calendar.HOUR_OF_DAY)); convertedCalendar.set(Calendar.MINUTE, utcCalendar.get(Calendar.MINUTE)); convertedCalendar.set(Calendar.SECOND, utcCalendar.get(Calendar.SECOND)); convertedCalendar.set(Calendar.MILLISECOND, utcCalendar.get(Calendar.MILLISECOND)); return convertedCalendar.getTimeInMillis(); } /** * Returns the lens of this camera. * @since 3.0 */ public Lens getLens() { return this.lens; } /** * Sets the lens of this camera. * @since 3.0 */ public void setLens(Lens lens) { if (lens != this.lens) { Lens oldLens = this.lens; this.lens = lens; this.propertyChangeSupport.firePropertyChange(Property.LENS.name(), oldLens, lens); } } /** * Sets the location and angles of this camera from the <code>camera</code> in parameter. * @since 2.3 */ public void setCamera(Camera camera) { setX(camera.getX()); setY(camera.getY()); setZ(camera.getZ()); setYaw(camera.getYaw()); setPitch(camera.getPitch()); setFieldOfView(camera.getFieldOfView()); } /** * Returns a clone of this camera. * @since 2.3 */ @Override public Camera clone() { try { return (Camera)super.clone(); } catch (CloneNotSupportedException ex) { throw new IllegalStateException("Super class isn't cloneable"); } } }