/*
* HomeFurnitureGroup.java 4 f�vr. 2010
*
* Sweet Home 3D, Copyright (c) 2010 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.awt.geom.AffineTransform;
import java.awt.geom.GeneralPath;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* A group of furniture of furniture.
* @since 2.3
* @author Emmanuel Puybaret
*/
public class HomeFurnitureGroup extends HomePieceOfFurniture {
private static final long serialVersionUID = 1L;
private static final float [][] IDENTITY = new float [][] {{1, 0, 0}, {0, 1, 0}, {0, 0, 1}};
private List<HomePieceOfFurniture> furniture;
private boolean resizable;
private boolean deformable;
private boolean texturable;
private boolean doorOrWindow;
private float fixedWidth;
private float fixedDepth;
private float fixedHeight;
private String currency;
private List<Integer> furnitureDefaultColors;
private List<HomeTexture> furnitureDefaultTextures;
/**
* Creates a group from the given <code>furniture</code> list.
* The level of each piece of furniture of the group will be reset to <code>null</code> and if they belong to levels
* with different elevations, their elevation will be updated to be relative to the elevation of the lowest level.
*/
public HomeFurnitureGroup(List<HomePieceOfFurniture> furniture,
String name) {
super(furniture.get(0));
this.furniture = Collections.unmodifiableList(furniture);
// Search the size of the furniture group
HomePieceOfFurniture firstPiece = furniture.get(0);
AffineTransform rotation = AffineTransform.getRotateInstance(-firstPiece.getAngle());
Rectangle2D unrotatedBoundingRectangle = null;
for (HomePieceOfFurniture piece : getFurnitureWithoutGroups(furniture)) {
GeneralPath pieceShape = new GeneralPath();
float [][] points = piece.getPoints();
pieceShape.moveTo(points [0][0], points [0][1]);
for (int i = 1; i < points.length; i++) {
pieceShape.lineTo(points [i][0], points [i][1]);
}
pieceShape.closePath();
if (unrotatedBoundingRectangle == null) {
unrotatedBoundingRectangle = pieceShape.createTransformedShape(rotation).getBounds2D();
} else {
unrotatedBoundingRectangle.add(pieceShape.createTransformedShape(rotation).getBounds2D());
}
}
// Search center of the group
Point2D center = new Point2D.Float((float)unrotatedBoundingRectangle.getCenterX(), (float)unrotatedBoundingRectangle.getCenterY());
rotation.setToRotation(firstPiece.getAngle());
rotation.transform(center, center);
float elevation = Float.MAX_VALUE;
float height = 0;
boolean movable = true;
this.resizable = true;
this.deformable = true;
this.texturable = true;
this.doorOrWindow = true;
boolean visible = false;
boolean modelMirrored = true;
this.currency = firstPiece.getCurrency();
// Search the lowest level elevation among grouped furniture
Level minLevel = null;
for (HomePieceOfFurniture piece : furniture) {
Level level = piece.getLevel();
if (level != null
&& (minLevel == null
|| level.getElevation() < minLevel.getElevation())) {
minLevel = level;
}
}
for (HomePieceOfFurniture piece : furniture) {
Level level = piece.getLevel();
if (level != null) {
elevation = Math.min(elevation, piece.getGroundElevation() - minLevel.getElevation());
} else {
elevation = Math.min(elevation, piece.getElevation());
}
}
for (HomePieceOfFurniture piece : furniture) {
if (piece.getLevel() != null) {
piece.setElevation(piece.getGroundElevation() - minLevel.getElevation());
}
piece.setLevel(null);
height = Math.max(height, piece.getElevation() + piece.getHeight());
movable &= piece.isMovable();
this.resizable &= piece.isResizable();
this.deformable &= piece.isDeformable();
this.texturable &= piece.isTexturable();
this.doorOrWindow &= piece.isDoorOrWindow();
visible |= piece.isVisible();
modelMirrored &= piece.isModelMirrored();
if (this.currency != null) {
if (piece.getCurrency() == null
|| !piece.getCurrency().equals(this.currency)) {
this.currency = null;
}
}
}
if (this.resizable) {
super.setWidth((float)unrotatedBoundingRectangle.getWidth());
super.setDepth((float)unrotatedBoundingRectangle.getHeight());
super.setHeight(height - elevation);
super.setModelMirrored(modelMirrored);
} else {
this.fixedWidth = (float)unrotatedBoundingRectangle.getWidth();
this.fixedDepth = (float)unrotatedBoundingRectangle.getHeight();
this.fixedHeight = height - elevation;
}
setName(name);
setNameVisible(false);
setNameXOffset(0);
setNameYOffset(0);
setNameStyle(null);
setDescription(null);
setMovable(movable);
setVisible(visible);
if (this.texturable) {
super.setColor(null);
super.setTexture(null);
}
super.setX((float)center.getX());
super.setY((float)center.getY());
super.setAngle(firstPiece.getAngle());
super.setElevation(elevation);
}
/**
* Initializes new piece fields to their default values
* and reads piece from <code>in</code> stream with default reading method.
*/
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
this.deformable = true;
this.texturable = true;
in.defaultReadObject();
}
/**
* Returns all the pieces of the given <code>furniture</code> list.
*/
private List<HomePieceOfFurniture> getFurnitureWithoutGroups(List<HomePieceOfFurniture> furniture) {
List<HomePieceOfFurniture> pieces = new ArrayList<HomePieceOfFurniture>();
for (HomePieceOfFurniture piece : furniture) {
if (piece instanceof HomeFurnitureGroup) {
pieces.addAll(getFurnitureWithoutGroups(((HomeFurnitureGroup)piece).getFurniture()));
} else {
pieces.add(piece);
}
}
return pieces;
}
/**
* Returns an unmodifiable list of the furniture of this group.
*/
public List<HomePieceOfFurniture> getFurniture() {
return this.furniture;
}
/**
* Returns <code>null</code>.
*/
@Override
public String getCatalogId() {
return null;
}
/**
* Returns <code>true</code> if all furniture of this group are movable.
*/
@Override
public boolean isMovable() {
return super.isMovable();
}
/**
* Sets whether this piece is movable or not.
* @since 3.1
*/
@Override
public void setMovable(boolean movable) {
super.setMovable(movable);
for (HomePieceOfFurniture piece : this.furniture) {
piece.setMovable(movable);
}
}
/**
* Returns <code>true</code> if all furniture of this group are doors or windows.
*/
@Override
public boolean isDoorOrWindow() {
return this.doorOrWindow;
}
/**
* Returns <code>true</code> if all furniture of this group are resizable.
*/
@Override
public boolean isResizable() {
return this.resizable;
}
/**
* Returns <code>true</code> if all furniture of this group are deformable.
* @since 3.0
*/
@Override
public boolean isDeformable() {
return this.deformable;
}
/**
* Returns <code>true</code> if all furniture of this group are texturable.
* @since 3.5
*/
@Override
public boolean isTexturable() {
return this.texturable;
}
/**
* Returns the width of this group.
*/
@Override
public float getWidth() {
if (!this.resizable) {
return this.fixedWidth;
} else {
return super.getWidth();
}
}
/**
* Returns the depth of this group.
*/
@Override
public float getDepth() {
if (!this.resizable) {
return this.fixedDepth;
} else {
return super.getDepth();
}
}
/**
* Returns the height of this group.
*/
@Override
public float getHeight() {
if (!this.resizable) {
return this.fixedHeight;
} else {
return super.getHeight();
}
}
/**
* Returns <code>null</code>.
*/
public Content getIcon() {
return null;
}
/**
* Returns <code>null</code>.
*/
public Content getPlanIcon() {
return null;
}
/**
* Returns <code>null</code>.
*/
public Content getModel() {
return null;
}
/**
* Returns an identity matrix.
*/
@Override
public float [][] getModelRotation() {
return IDENTITY;
}
/**
* Returns <code>null</code>.
* @since 3.5
*/
@Override
public String getStaircaseCutOutShape() {
return null;
}
/**
* Returns the price of the furniture of this group with a price.
*/
@Override
public BigDecimal getPrice() {
BigDecimal price = null;
for (HomePieceOfFurniture piece : this.furniture) {
if (piece.getPrice() != null) {
if (price == null) {
price = piece.getPrice();
} else {
price = price.add(piece.getPrice());
}
}
}
if (price == null) {
return super.getPrice();
} else {
return price;
}
}
/**
* Sets the price of this group.
* @throws UnsupportedOperationException if the price of one of the pieces is set
* @since 4.0
*/
@Override
public void setPrice(BigDecimal price) {
for (HomePieceOfFurniture piece : this.furniture) {
if (piece.getPrice() != null) {
throw new UnsupportedOperationException("Can't change the price of a group containing pieces with a price");
}
}
super.setPrice(price);
}
/**
* Returns the VAT percentage of the furniture of this group
* or <code>null</code> if one piece has no VAT percentage
* or has a VAT percentage different from the other furniture.
*/
@Override
public BigDecimal getValueAddedTaxPercentage() {
BigDecimal valueAddedTaxPercentage = this.furniture.get(0).getValueAddedTaxPercentage();
if (valueAddedTaxPercentage != null) {
for (HomePieceOfFurniture piece : this.furniture) {
BigDecimal pieceValueAddedTaxPercentage = piece.getValueAddedTaxPercentage();
if (pieceValueAddedTaxPercentage == null
|| !pieceValueAddedTaxPercentage.equals(valueAddedTaxPercentage)) {
return null;
}
}
}
return valueAddedTaxPercentage;
}
/**
* Returns the currency of the furniture of this group
* or <code>null</code> if one piece has no currency
* or has a currency different from the other furniture.
* @since 3.5
*/
@Override
public String getCurrency() {
return this.currency;
}
/**
* Returns the VAT of the furniture of this group.
*/
@Override
public BigDecimal getValueAddedTax() {
BigDecimal valueAddedTax = null;
for (HomePieceOfFurniture piece : furniture) {
BigDecimal pieceValueAddedTax = piece.getValueAddedTax();
if (pieceValueAddedTax != null) {
if (valueAddedTax == null) {
valueAddedTax = pieceValueAddedTax;
} else {
valueAddedTax = valueAddedTax.add(pieceValueAddedTax);
}
}
}
return valueAddedTax;
}
/**
* Returns the total price of the furniture of this group.
*/
@Override
public BigDecimal getPriceValueAddedTaxIncluded() {
BigDecimal priceValueAddedTaxIncluded = null;
for (HomePieceOfFurniture piece : furniture) {
if (piece.getPrice() != null) {
if (priceValueAddedTaxIncluded == null) {
priceValueAddedTaxIncluded = piece.getPriceValueAddedTaxIncluded();
} else {
priceValueAddedTaxIncluded = priceValueAddedTaxIncluded.add(piece.getPriceValueAddedTaxIncluded());
}
}
}
return priceValueAddedTaxIncluded;
}
/**
* Returns <code>false</code>.
*/
@Override
public boolean isBackFaceShown() {
return false;
}
/**
* Sets the <code>color</code> of the furniture of this group.
*/
@Override
public void setColor(Integer color) {
super.setColor(color);
if (color != null) {
storeDefaultColorsAndTextures();
for (HomePieceOfFurniture piece : this.furniture) {
piece.setTexture(null);
piece.setColor(color);
}
} else if (getTexture() == null) {
restoreDefaultColorsAndTextures();
}
}
/**
* Sets the <code>texture</code> of the furniture of this group.
*/
@Override
public void setTexture(HomeTexture texture) {
super.setTexture(texture);
if (texture != null) {
storeDefaultColorsAndTextures();
for (HomePieceOfFurniture piece : this.furniture) {
piece.setColor(null);
piece.setTexture(texture);
}
} else if (getColor() == null) {
restoreDefaultColorsAndTextures();
}
}
/**
* Stores default colors and textures.
*/
private void storeDefaultColorsAndTextures() {
if (this.furnitureDefaultColors == null) {
// Retrieve default color and texture of child furniture
Integer [] furnitureDefaultColors = new Integer [this.furniture.size()];
HomeTexture [] furnitureDefaultTextures = new HomeTexture [this.furniture.size()];
for (int i = 0; i < this.furniture.size(); i++) {
furnitureDefaultColors [i] = this.furniture.get(i).getColor();
furnitureDefaultTextures [i] = this.furniture.get(i).getTexture();
}
this.furnitureDefaultColors = Arrays.asList(furnitureDefaultColors);
this.furnitureDefaultTextures = Arrays.asList(furnitureDefaultTextures);
}
}
/**
* Restores default colors and textures
*/
private void restoreDefaultColorsAndTextures() {
if (this.furnitureDefaultColors != null) {
for (int i = 0; i < this.furniture.size(); i++) {
this.furniture.get(i).setColor(this.furnitureDefaultColors.get(i));
this.furniture.get(i).setTexture(this.furnitureDefaultTextures.get(i));
}
this.furnitureDefaultColors = null;
this.furnitureDefaultTextures = null;
}
}
/**
* Sets the <code>angle</code> of the furniture of this group.
*/
@Override
public void setAngle(float angle) {
if (angle != getAngle()) {
float angleDelta = angle - getAngle();
super.setAngle(angle);
double cosAngleDelta = Math.cos(angleDelta);
double sinAngleDelta = Math.sin(angleDelta);
for (HomePieceOfFurniture piece : this.furniture) {
piece.setAngle(piece.getAngle() + angleDelta);
float newX = getX() + (float)((piece.getX() - getX()) * cosAngleDelta - (piece.getY() - getY()) * sinAngleDelta);
float newY = getY() + (float)((piece.getX() - getX()) * sinAngleDelta + (piece.getY() - getY()) * cosAngleDelta);
piece.setX(newX);
piece.setY(newY);
}
}
}
/**
* Sets the <code>abscissa</code> of this group and moves its furniture accordingly.
*/
@Override
public void setX(float x) {
if (x != getX()) {
float dx = x - getX();
super.setX(x);
for (HomePieceOfFurniture piece : this.furniture) {
piece.setX(piece.getX() + dx);
}
}
}
/**
* Sets the <code>ordinate</code> of this group and moves its furniture accordingly.
*/
@Override
public void setY(float y) {
if (y != getY()) {
float dy = y - getY();
super.setY(y);
for (HomePieceOfFurniture piece : this.furniture) {
piece.setY(piece.getY() + dy);
}
}
}
/**
* Sets the <code>width</code> of this group, then moves and resizes its furniture accordingly.
*/
@Override
public void setWidth(float width) {
if (width != getWidth()) {
float widthFactor = width / getWidth();
super.setWidth(width);
float angle = getAngle();
for (HomePieceOfFurniture piece : this.furniture) {
float angleDelta = piece.getAngle() - angle;
float pieceWidth = piece.getWidth();
float pieceDepth = piece.getDepth();
piece.setWidth(pieceWidth + pieceWidth * (widthFactor - 1) * Math.abs((float)Math.cos(angleDelta)));
piece.setDepth(pieceDepth + pieceDepth * (widthFactor - 1) * Math.abs((float)Math.sin(angleDelta)));
// Rotate piece to angle 0
double cosAngle = Math.cos(angle);
double sinAngle = Math.sin(angle);
float newX = getX() + (float)((piece.getX() - getX()) * cosAngle + (piece.getY() - getY()) * sinAngle);
float newY = getY() + (float)((piece.getX() - getX()) * -sinAngle + (piece.getY() - getY()) * cosAngle);
// Update its abscissa
newX = getX() + (newX - getX()) * widthFactor;
// Rotate piece back to its angle
piece.setX(getX() + (float)((newX - getX()) * cosAngle - (newY - getY()) * sinAngle));
piece.setY(getY() + (float)((newX - getX()) * sinAngle + (newY - getY()) * cosAngle));
}
}
}
/**
* Sets the <code>depth</code> of this group, then moves and resizes its furniture accordingly.
*/
@Override
public void setDepth(float depth) {
if (depth != getDepth()) {
float depthFactor = depth / getDepth();
super.setDepth(depth);
float angle = getAngle();
for (HomePieceOfFurniture piece : this.furniture) {
float angleDelta = piece.getAngle() - angle;
float pieceWidth = piece.getWidth();
float pieceDepth = piece.getDepth();
piece.setWidth(pieceWidth + pieceWidth * (depthFactor - 1) * Math.abs((float)Math.sin(angleDelta)));
piece.setDepth(pieceDepth + pieceDepth * (depthFactor - 1) * Math.abs((float)Math.cos(angleDelta)));
// Rotate piece to angle 0
double cosAngle = Math.cos(angle);
double sinAngle = Math.sin(angle);
float newX = getX() + (float)((piece.getX() - getX()) * cosAngle + (piece.getY() - getY()) * sinAngle);
float newY = getY() + (float)((piece.getX() - getX()) * -sinAngle + (piece.getY() - getY()) * cosAngle);
// Update its ordinate
newY = getY() + (newY - getY()) * depthFactor;
// Rotate piece back to its angle
piece.setX(getX() + (float)((newX - getX()) * cosAngle - (newY - getY()) * sinAngle));
piece.setY(getY() + (float)((newX - getX()) * sinAngle + (newY - getY()) * cosAngle));
}
}
}
/**
* Sets the <code>height</code> of this group, then moves and resizes its furniture accordingly.
*/
@Override
public void setHeight(float height) {
if (height != getHeight()) {
float heightFactor = height / getHeight();
super.setHeight(height);
for (HomePieceOfFurniture piece : this.furniture) {
piece.setHeight(piece.getHeight() * heightFactor);
piece.setElevation(getElevation()
+ (piece.getElevation() - getElevation()) * heightFactor);
}
}
}
/**
* Sets the <code>elevation</code> of this group, then moves its furniture accordingly.
*/
@Override
public void setElevation(float elevation) {
if (elevation != getElevation()) {
float elevationDelta = elevation - getElevation();
super.setElevation(elevation);
for (HomePieceOfFurniture piece : this.furniture) {
piece.setElevation(piece.getElevation() + elevationDelta);
}
}
}
/**
* Sets whether the furniture of this group should be mirrored or not.
*/
@Override
public void setModelMirrored(boolean modelMirrored) {
if (modelMirrored != isModelMirrored()) {
super.setModelMirrored(modelMirrored);
float angle = getAngle();
for (HomePieceOfFurniture piece : this.furniture) {
piece.setModelMirrored(!piece.isModelMirrored());
// Rotate piece to angle 0
double cosAngle = Math.cos(angle);
double sinAngle = Math.sin(angle);
float newX = getX() + (float)((piece.getX() - getX()) * cosAngle + (piece.getY() - getY()) * sinAngle);
float newY = getY() + (float)((piece.getX() - getX()) * -sinAngle + (piece.getY() - getY()) * cosAngle);
// Update its abscissa
newX = getX() - (newX - getX());
// Rotate piece back to its angle
piece.setX(getX() + (float)((newX - getX()) * cosAngle - (newY - getY()) * sinAngle));
piece.setY(getY() + (float)((newX - getX()) * sinAngle + (newY - getY()) * cosAngle));
}
}
}
/**
* Sets whether the furniture of this group should be visible or not.
*/
@Override
public void setVisible(boolean visible) {
super.setVisible(visible);
for (HomePieceOfFurniture piece : this.furniture) {
piece.setVisible(visible);
}
}
/**
* Set the level of this group and the furniture it contains.
*/
@Override
public void setLevel(Level level) {
super.setLevel(level);
for (HomePieceOfFurniture piece : this.furniture) {
piece.setLevel(level);
}
}
/**
* Returns <code>true</code> if one of the pieces of this group intersects
* with the horizontal rectangle which opposite corners are at points
* (<code>x0</code>, <code>y0</code>) and (<code>x1</code>, <code>y1</code>).
* @since 3.5
*/
@Override
public boolean intersectsRectangle(float x0, float y0,
float x1, float y1) {
for (HomePieceOfFurniture piece : this.furniture) {
if (piece.intersectsRectangle(x0, y0, x1, y1)) {
return true;
}
}
return false;
}
/**
* Returns <code>true</code> if one of the pieces of this group contains
* the point at (<code>x</code>, <code>y</code>)
* with a given <code>margin</code>.
* @since 3.5
*/
@Override
public boolean containsPoint(float x, float y, float margin) {
for (HomePieceOfFurniture piece : this.furniture) {
if (piece.containsPoint(x, y, margin)) {
return true;
}
}
return false;
}
/**
* Returns a clone of this group with cloned furniture.
*/
@Override
public HomeFurnitureGroup clone() {
HomeFurnitureGroup clone = (HomeFurnitureGroup)super.clone();
// Deep clone furniture managed by this group
clone.furniture = new ArrayList<HomePieceOfFurniture>(this.furniture.size());
for (HomePieceOfFurniture piece : this.furniture) {
clone.furniture.add(piece.clone());
}
clone.furniture = Collections.unmodifiableList(clone.furniture);
if (this.furnitureDefaultColors != null) {
clone.furnitureDefaultColors = new ArrayList<Integer>(this.furnitureDefaultColors);
}
if (this.furnitureDefaultTextures != null) {
clone.furnitureDefaultTextures = new ArrayList<HomeTexture>(this.furnitureDefaultTextures);
}
return clone;
}
}