/*
* ModelPreviewComponent 16 jan. 2010
*
* Sweet Home 3D, Copyright (c) 2007-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.swing;
import java.awt.AWTException;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsEnvironment;
import java.awt.GridLayout;
import java.awt.KeyboardFocusManager;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.Toolkit;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.awt.image.BufferedImage;
import java.awt.image.MemoryImageSource;
import java.io.File;
import java.io.IOException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import javax.imageio.ImageIO;
import javax.media.j3d.AmbientLight;
import javax.media.j3d.Appearance;
import javax.media.j3d.Background;
import javax.media.j3d.BoundingSphere;
import javax.media.j3d.BranchGroup;
import javax.media.j3d.Canvas3D;
import javax.media.j3d.DirectionalLight;
import javax.media.j3d.GraphicsConfigTemplate3D;
import javax.media.j3d.Group;
import javax.media.j3d.Light;
import javax.media.j3d.Link;
import javax.media.j3d.Node;
import javax.media.j3d.PhysicalBody;
import javax.media.j3d.PhysicalEnvironment;
import javax.media.j3d.Shape3D;
import javax.media.j3d.Texture;
import javax.media.j3d.Transform3D;
import javax.media.j3d.TransformGroup;
import javax.media.j3d.View;
import javax.swing.BorderFactory;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.border.EtchedBorder;
import javax.swing.event.AncestorEvent;
import javax.swing.event.AncestorListener;
import javax.swing.event.MouseInputAdapter;
import javax.vecmath.Color3f;
import javax.vecmath.Matrix3f;
import javax.vecmath.Point3d;
import javax.vecmath.Vector3d;
import javax.vecmath.Vector3f;
import com.eteks.sweethome3d.j3d.Component3DManager;
import com.eteks.sweethome3d.j3d.HomePieceOfFurniture3D;
import com.eteks.sweethome3d.j3d.ModelManager;
import com.eteks.sweethome3d.model.CatalogPieceOfFurniture;
import com.eteks.sweethome3d.model.Content;
import com.eteks.sweethome3d.model.HomeMaterial;
import com.eteks.sweethome3d.model.HomePieceOfFurniture;
import com.eteks.sweethome3d.tools.OperatingSystem;
import com.eteks.sweethome3d.tools.TemporaryURLContent;
import com.sun.j3d.exp.swing.JCanvas3D;
import com.sun.j3d.utils.universe.SimpleUniverse;
import com.sun.j3d.utils.universe.Viewer;
import com.sun.j3d.utils.universe.ViewingPlatform;
/**
* Super class of 3D preview component for model.
*/
public class ModelPreviewComponent extends JComponent {
private SimpleUniverse universe;
private JPanel component3DPanel;
private Component component3D;
private BranchGroup sceneTree;
private float viewYaw = (float) Math.PI / 8;
private float viewPitch = -(float) Math.PI / 16;
private float viewScale = 1;
private Object iconImageLock;
private HomePieceOfFurniture previewedPiece;
private Map<Texture, Texture> pieceTextures = new HashMap<Texture, Texture>();
/**
* Returns an 3D model preview component.
*/
public ModelPreviewComponent() {
this(false);
}
/**
* Returns an 3D model preview component that lets the user change its pitch and scale
* if <code>pitchAndScaleChangeSupported</code> is <code>true</code>.
*/
public ModelPreviewComponent(boolean pitchAndScaleChangeSupported) {
setBorder(BorderFactory.createEtchedBorder(EtchedBorder.LOWERED));
this.sceneTree = createSceneTree();
this.component3DPanel = new JPanel();
setLayout(new BorderLayout());
add(this.component3DPanel);
GraphicsEnvironment graphicsEnvironment = GraphicsEnvironment.getLocalGraphicsEnvironment();
if (graphicsEnvironment.getScreenDevices().length == 1) {
// If only one screen device is available, create 3D component immediately,
// otherwise create it once the screen device of the parent is known
createComponent3D(graphicsEnvironment.getDefaultScreenDevice().getDefaultConfiguration(), pitchAndScaleChangeSupported);
}
// Add an ancestor listener to create 3D component and its universe once this component is made visible
// and clean up universe once its parent frame is disposed
addAncestorListener(pitchAndScaleChangeSupported);
}
/**
* Returns component preferred size.
*/
@Override
public Dimension getPreferredSize() {
if (isPreferredSizeSet()) {
return super.getPreferredSize();
} else {
return new Dimension(200, 200);
}
}
@Override
public void addMouseMotionListener(final MouseMotionListener l) {
super.addMouseMotionListener(l);
if (this.component3D != null) {
this.component3D.addMouseMotionListener(new MouseMotionListener() {
public void mouseMoved(MouseEvent ev) {
l.mouseMoved(SwingUtilities.convertMouseEvent(component3D, ev, ModelPreviewComponent.this));
}
public void mouseDragged(MouseEvent ev) {
l.mouseDragged(SwingUtilities.convertMouseEvent(component3D, ev, ModelPreviewComponent.this));
}
});
}
}
@Override
public void addMouseListener(final MouseListener l) {
super.addMouseListener(l);
if (this.component3D != null) {
this.component3D.addMouseListener(new MouseListener() {
public void mouseReleased(MouseEvent ev) {
l.mouseReleased(SwingUtilities.convertMouseEvent(component3D, ev, ModelPreviewComponent.this));
}
public void mousePressed(MouseEvent ev) {
l.mousePressed(SwingUtilities.convertMouseEvent(component3D, ev, ModelPreviewComponent.this));
}
public void mouseExited(MouseEvent ev) {
l.mouseExited(SwingUtilities.convertMouseEvent(component3D, ev, ModelPreviewComponent.this));
}
public void mouseEntered(MouseEvent ev) {
l.mouseEntered(SwingUtilities.convertMouseEvent(component3D, ev, ModelPreviewComponent.this));
}
public void mouseClicked(MouseEvent ev) {
l.mouseClicked(SwingUtilities.convertMouseEvent(component3D, ev, ModelPreviewComponent.this));
}
});
}
}
/**
* Returns the 3D component of this component.
*/
JComponent getComponent3D() {
return this.component3DPanel;
}
/**
* Adds an ancestor listener to this component to manage the creation of the 3D component and its universe
* and clean up the universe.
*/
private void addAncestorListener(final boolean pitchAndScaleChangeSupported) {
addAncestorListener(new AncestorListener() {
public void ancestorAdded(AncestorEvent ev) {
if (component3D == null) {
createComponent3D(ev.getAncestor().getGraphicsConfiguration(), pitchAndScaleChangeSupported);
}
if (universe == null) {
createUniverse();
}
}
public void ancestorRemoved(AncestorEvent ev) {
if (universe != null) {
disposeUniverse();
}
}
public void ancestorMoved(AncestorEvent ev) {
}
});
}
/**
* Creates the 3D component associated with the given <code>configuration</code> device.
*/
private void createComponent3D(GraphicsConfiguration graphicsConfiguration,
boolean pitchAndScaleChangeSupported) {
if (Boolean.valueOf(System.getProperty("com.eteks.sweethome3d.j3d.useOffScreen3DView", "false"))) {
GraphicsConfigTemplate3D gc = new GraphicsConfigTemplate3D();
gc.setSceneAntialiasing(GraphicsConfigTemplate3D.PREFERRED);
try {
// Instantiate JCanvas3DWithNotifiedPaint inner class by reflection
// to be able to run under Java 3D 1.3
this.component3D = (Component)Class.forName(ModelPreviewComponent.class.getName() + "$JCanvas3DWithNotifiedPaint").
getConstructor(ModelPreviewComponent.class, GraphicsConfigTemplate3D.class).newInstance(this, gc);
} catch (ClassNotFoundException ex) {
throw new UnsupportedOperationException("Java 3D 1.5 required to display an offscreen 3D view");
} catch (Exception ex) {
UnsupportedOperationException ex2 = new UnsupportedOperationException();
ex2.initCause(ex);
throw ex2;
}
} else {
this.component3D = Component3DManager.getInstance().getOnscreenCanvas3D(graphicsConfiguration,
new Component3DManager.RenderingObserver() {
public void canvas3DPreRendered(Canvas3D canvas3d) {
}
public void canvas3DPostRendered(Canvas3D canvas3d) {
}
public void canvas3DSwapped(Canvas3D canvas3d) {
ModelPreviewComponent.this.canvas3DSwapped();
}
});
}
this.component3D.setBackground(new Color(0xE5E5E5));
// Layout 3D component
this.component3DPanel.setLayout(new GridLayout());
this.component3DPanel.add(this.component3D);
this.component3D.setFocusable(false);
addMouseListeners(this.component3D, pitchAndScaleChangeSupported);
}
/**
* A <code>JCanvas</code> canvas that sends a notification when it's drawn.
*/
private static class JCanvas3DWithNotifiedPaint extends JCanvas3D {
private final ModelPreviewComponent homeComponent3D;
public JCanvas3DWithNotifiedPaint(ModelPreviewComponent component,
GraphicsConfigTemplate3D template) {
super(template);
this.homeComponent3D = component;
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
this.homeComponent3D.canvas3DSwapped();
}
}
/**
* Adds an AWT mouse listener to component that will update view platform transform.
*/
private void addMouseListeners(final Component component3D,
final boolean pitchAndScaleChangeSupported) {
final float ANGLE_FACTOR = 0.02f;
final float ZOOM_FACTOR = 0.02f;
MouseInputAdapter mouseListener = new MouseInputAdapter() {
private int xLastMouseMove;
private int yLastMouseMove;
@Override
public void mousePressed(MouseEvent ev) {
this.xLastMouseMove = ev.getX();
this.yLastMouseMove = ev.getY();
}
@Override
public void mouseDragged(MouseEvent ev) {
if (getModelNode() != null) {
// Mouse move along X axis changes yaw
setViewYaw(getViewYaw() - ANGLE_FACTOR * (ev.getX() - this.xLastMouseMove));
this.xLastMouseMove = ev.getX();
if (pitchAndScaleChangeSupported) {
if (ev.isAltDown()) {
// Mouse move along Y axis with Alt down changes scale
setViewScale(Math.max(0.5f, Math.min(1.3f, getViewScale() * (float)Math.exp((ev.getY() - this.yLastMouseMove) * ZOOM_FACTOR))));
} else {
// Mouse move along Y axis changes pitch
setViewPitch(Math.max(-(float)Math.PI / 4, Math.min(0, getViewPitch() - ANGLE_FACTOR * (ev.getY() - this.yLastMouseMove))));
}
this.yLastMouseMove = ev.getY();
}
}
}
};
component3D.addMouseListener(mouseListener);
component3D.addMouseMotionListener(mouseListener);
if (pitchAndScaleChangeSupported) {
component3D.addMouseWheelListener(new MouseWheelListener() {
public void mouseWheelMoved(MouseWheelEvent ev) {
// Mouse move along Y axis with Alt down changes scale
setViewScale(Math.max(0.5f, Math.min(1.3f, getViewScale() * (float)Math.exp(ev.getWheelRotation() * ZOOM_FACTOR))));
}
});
}
// Redirect mouse events to the 3D component
for (final MouseMotionListener l : getListeners(MouseMotionListener.class)) {
component3D.addMouseMotionListener(new MouseMotionListener() {
public void mouseMoved(MouseEvent ev) {
l.mouseMoved(SwingUtilities.convertMouseEvent(component3D, ev, ModelPreviewComponent.this));
}
public void mouseDragged(MouseEvent ev) {
l.mouseDragged(SwingUtilities.convertMouseEvent(component3D, ev, ModelPreviewComponent.this));
}
});
}
for (final MouseListener l : getListeners(MouseListener.class)) {
component3D.addMouseListener(new MouseListener() {
public void mouseReleased(MouseEvent ev) {
l.mouseReleased(SwingUtilities.convertMouseEvent(component3D, ev, ModelPreviewComponent.this));
}
public void mousePressed(MouseEvent ev) {
l.mousePressed(SwingUtilities.convertMouseEvent(component3D, ev, ModelPreviewComponent.this));
}
public void mouseExited(MouseEvent ev) {
l.mouseExited(SwingUtilities.convertMouseEvent(component3D, ev, ModelPreviewComponent.this));
}
public void mouseEntered(MouseEvent ev) {
l.mouseEntered(SwingUtilities.convertMouseEvent(component3D, ev, ModelPreviewComponent.this));
}
public void mouseClicked(MouseEvent ev) {
l.mouseClicked(SwingUtilities.convertMouseEvent(component3D, ev, ModelPreviewComponent.this));
}
});
}
}
/**
* Creates universe bound to the 3D component.
*/
private void createUniverse() {
Canvas3D canvas3D;
if (this.component3D instanceof Canvas3D) {
canvas3D = (Canvas3D)this.component3D;
} else {
try {
// Call JCanvas3D#getOffscreenCanvas3D by reflection to be able to run under Java 3D 1.3
canvas3D = (Canvas3D)Class.forName("com.sun.j3d.exp.swing.JCanvas3D").getMethod("getOffscreenCanvas3D").invoke(this.component3D);
} catch (Exception ex) {
UnsupportedOperationException ex2 = new UnsupportedOperationException();
ex2.initCause(ex);
throw ex2;
}
}
// Create a universe bound to component 3D
ViewingPlatform viewingPlatform = new ViewingPlatform();
Viewer viewer = new Viewer(canvas3D);
this.universe = new SimpleUniverse(viewingPlatform, viewer);
// Set viewer location
updateViewPlatformTransform(this.universe.getViewingPlatform().getViewPlatformTransform(),
getViewYaw(), getViewPitch(), getViewScale());
// Link scene to universe
this.universe.addBranchGraph(this.sceneTree);
revalidate();
repaint();
if (OperatingSystem.isMacOSX()) {
final Component root = SwingUtilities.getRoot(this);
EventQueue.invokeLater(new Runnable() {
public void run() {
// Request focus again even if dialog isn't supposed to have lost focus !
if (KeyboardFocusManager.getCurrentKeyboardFocusManager().getActiveWindow() != root) {
root.requestFocus();
}
}
});
}
}
/**
* Disposes universe bound to canvas.
*/
private void disposeUniverse() {
// Unlink scene to universe
this.universe.getLocale().removeBranchGraph(this.sceneTree);
this.universe.cleanup();
this.universe = null;
}
/**
* Creates a view bound to the universe that views current model from a point of view oriented with
* <code>yaw</code> and <code>pitch</code> angles.
*/
View createView(float yaw, float pitch, float scale, int projectionPolicy) {
if (this.universe == null) {
createUniverse();
}
// Reuse same physical body and environment
PhysicalBody physicalBody = this.universe.getViewer().getPhysicalBody();
PhysicalEnvironment physicalEnvironment = this.universe.getViewer().getPhysicalEnvironment();
// Create a view associated with canvas3D
View view = new View();
view.setPhysicalBody(physicalBody);
view.setPhysicalEnvironment(physicalEnvironment);
view.setProjectionPolicy(projectionPolicy);
// Create a viewing platform and attach it to view and universe locale
ViewingPlatform viewingPlatform = new ViewingPlatform();
viewingPlatform.setUniverse(this.universe);
this.universe.getLocale().addBranchGraph(
(BranchGroup)viewingPlatform.getViewPlatformTransform().getParent());
view.attachViewPlatform(viewingPlatform.getViewPlatform());
// Set user point of view depending on yaw and pitch angles
updateViewPlatformTransform(viewingPlatform.getViewPlatformTransform(), yaw, pitch, scale);
return view;
}
/**
* Returns the <code>yaw</code> angle used by view platform transform.
*/
protected float getViewYaw() {
return this.viewYaw;
}
/**
* Sets the <code>yaw</code> angle used by view platform transform.
*/
protected void setViewYaw(float viewYaw) {
this.viewYaw = viewYaw;
if (this.universe != null) {
updateViewPlatformTransform(this.universe.getViewingPlatform().getViewPlatformTransform(),
getViewYaw(), getViewPitch(), getViewScale());
}
}
/**
* Returns the zoom factor used by view platform transform.
*/
protected float getViewScale() {
return this.viewScale;
}
/**
* Sets the zoom factor used by view platform transform.
*/
protected void setViewScale(float viewScale) {
this.viewScale = viewScale;
if (this.universe != null) {
updateViewPlatformTransform(this.universe.getViewingPlatform().getViewPlatformTransform(),
getViewYaw(), getViewPitch(), getViewScale());
}
}
/**
* Returns the <code>pitch</code> angle used by view platform transform.
*/
protected float getViewPitch() {
return this.viewPitch;
}
/**
* Sets the <code>pitch</code> angle used by view platform transform.
*/
protected void setViewPitch(float viewPitch) {
this.viewPitch = viewPitch;
if (this.universe != null) {
updateViewPlatformTransform(this.universe.getViewingPlatform().getViewPlatformTransform(),
getViewYaw(), getViewPitch(), getViewScale());
}
}
/**
* Updates the given view platform transformation from yaw angle, pitch angle and scale.
*/
private void updateViewPlatformTransform(TransformGroup viewPlatformTransform,
float viewYaw, float viewPitch,
float viewScale) {
// Default distance used to view a 2 unit wide scene
double nominalDistanceToCenter = 1.4 / Math.tan(Math.PI / 8);
// We don't use a TransformGroup in scene tree to be able to share the same scene
// in the different views displayed by OrientationPreviewComponent class
Transform3D pitchRotation = new Transform3D();
pitchRotation.rotX(viewPitch);
Transform3D yawRotation = new Transform3D();
yawRotation.rotY(viewYaw);
Transform3D transform = new Transform3D();
transform.setTranslation(
new Vector3d(Math.sin(viewYaw) * nominalDistanceToCenter * Math.cos(viewPitch),
nominalDistanceToCenter * Math.sin(-viewPitch),
Math.cos(viewYaw) * nominalDistanceToCenter * Math.cos(viewPitch)));
Transform3D scale = new Transform3D();
scale.setScale(viewScale);
yawRotation.mul(pitchRotation);
transform.mul(yawRotation);
scale.mul(transform);
viewPlatformTransform.setTransform(scale);
}
/**
* Returns scene tree root.
*/
private BranchGroup createSceneTree() {
BranchGroup root = new BranchGroup();
root.setCapability(BranchGroup.ALLOW_DETACH);
root.setCapability(BranchGroup.ALLOW_CHILDREN_READ);
// Build scene tree
root.addChild(createModelTree());
root.addChild(createBackgroundNode());
for (Light light : createLights()) {
root.addChild(light);
}
return root;
}
/**
* Returns the background node.
*/
private Node createBackgroundNode() {
Background background = new Background(new Color3f(0.9f, 0.9f, 0.9f));
background.setCapability(Background.ALLOW_COLOR_WRITE);
background.setApplicationBounds(new BoundingSphere(new Point3d(0, 0, 0), 100));
return background;
}
/**
* Sets the background color.
*/
public void setBackground(Color backgroundColor) {
super.setBackground(backgroundColor);
((Background)this.sceneTree.getChild(1)).setColor(new Color3f(backgroundColor));
}
/**
* Returns the lights of the scene.
*/
private Light [] createLights() {
Light [] lights = {
new DirectionalLight(new Color3f(0.9f, 0.9f, 0.9f), new Vector3f(1.732f, -0.8f, -1)),
new DirectionalLight(new Color3f(0.9f, 0.9f, 0.9f), new Vector3f(-1.732f, -0.8f, -1)),
new DirectionalLight(new Color3f(0.9f, 0.9f, 0.9f), new Vector3f(0, -0.8f, 1)),
new DirectionalLight(new Color3f(0.66f, 0.66f, 0.66f), new Vector3f(0, 1f, 0)),
new AmbientLight(new Color3f(0.2f, 0.2f, 0.2f))};
for (Light light : lights) {
light.setInfluencingBounds(new BoundingSphere(new Point3d(0, 0, 0), 100));
}
return lights;
}
/**
* Returns the root of model tree.
*/
private Node createModelTree() {
TransformGroup modelTransformGroup = new TransformGroup();
// Allow transform group to have new children
modelTransformGroup.setCapability(Group.ALLOW_CHILDREN_READ);
modelTransformGroup.setCapability(Group.ALLOW_CHILDREN_WRITE);
modelTransformGroup.setCapability(Group.ALLOW_CHILDREN_EXTEND);
// Allow the change of the transformation that sets model size and position
modelTransformGroup.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
return modelTransformGroup;
}
/**
* Returns the 3D model content displayed by this component.
*/
public Content getModel() {
return this.previewedPiece.getModel();
}
/**
* Sets the 3D model content displayed by this component.
* The model is shown at its default orientation and in a box 1 unit wide.
*/
public void setModel(final Content model) {
final TransformGroup modelTransformGroup = (TransformGroup)this.sceneTree.getChild(0);
modelTransformGroup.removeAllChildren();
this.previewedPiece = null;
this.pieceTextures.clear();
if (model != null) {
final AtomicReference<IllegalArgumentException> exception = new AtomicReference<IllegalArgumentException>();
// Load content model synchronously (or get it from cache)
ModelManager.getInstance().loadModel(model, true, new ModelManager.ModelObserver() {
public void modelUpdated(BranchGroup modelRoot) {
if (modelRoot.numChildren() > 0) {
try {
Vector3f size = ModelManager.getInstance().getSize(modelRoot);
previewedPiece = new HomePieceOfFurniture(
new CatalogPieceOfFurniture(null, null, model,
size.x, size.z, size.y, 0, false, null, null, false, 0, false));
previewedPiece.setX(0);
previewedPiece.setY(0);
previewedPiece.setElevation(-previewedPiece.getHeight() / 2);
Transform3D modelTransform = new Transform3D();
modelTransform.setScale(1.8 / Math.max(Math.max(size.x, size.z), size.y));
modelTransformGroup.setTransform(modelTransform);
modelTransformGroup.addChild(new HomePieceOfFurniture3D(previewedPiece, null, true, true));
} catch (IllegalArgumentException ex) {
// Model is empty
}
}
}
public void modelError(Exception ex) {
exception.set(new IllegalArgumentException("Couldn't load model", ex));
}
});
if (exception.get() != null) {
throw exception.get();
}
}
}
/**
* Sets the back face visibility of the children nodes of the displayed 3D model.
*/
protected void setBackFaceShown(boolean backFaceShown) {
if (this.previewedPiece != null) {
// Create a new piece from the existing one with an updated backFaceShown flag
this.previewedPiece = new HomePieceOfFurniture(
new CatalogPieceOfFurniture(null, null, this.previewedPiece.getModel(),
this.previewedPiece.getWidth(),
this.previewedPiece.getDepth(),
this.previewedPiece.getHeight(),
0, false, this.previewedPiece.getColor(), null, backFaceShown, 0, false));
this.previewedPiece.setX(0);
this.previewedPiece.setY(0);
this.previewedPiece.setElevation(-previewedPiece.getHeight() / 2);
TransformGroup modelTransformGroup = (TransformGroup)this.sceneTree.getChild(0);
modelTransformGroup.addChild(new HomePieceOfFurniture3D(this.previewedPiece, null, true, true));
if (modelTransformGroup.numChildren() > 1) {
modelTransformGroup.removeChild(0);
}
}
}
/**
* Returns the 3D model node displayed by this component.
*/
private HomePieceOfFurniture3D getModelNode() {
TransformGroup modelTransformGroup = (TransformGroup)this.sceneTree.getChild(0);
if (modelTransformGroup.numChildren() > 0) {
return (HomePieceOfFurniture3D)modelTransformGroup.getChild(0);
} else {
return null;
}
}
/**
* Updates the rotation of the 3D model displayed by this component.
* The model is shown at its default size.
*/
protected void setModelRotation(float [][] modelRotation) {
BranchGroup modelNode = getModelNode();
if (modelNode != null && modelNode.numChildren() > 0) {
// Apply model rotation
Transform3D rotationTransform = new Transform3D();
if (modelRotation != null) {
Matrix3f modelRotationMatrix = new Matrix3f(modelRotation [0][0], modelRotation [0][1], modelRotation [0][2],
modelRotation [1][0], modelRotation [1][1], modelRotation [1][2],
modelRotation [2][0], modelRotation [2][1], modelRotation [2][2]);
rotationTransform.setRotation(modelRotationMatrix);
}
// Scale model to make it fit in a 1.8 unit wide box
Transform3D modelTransform = new Transform3D();
Vector3f size = ModelManager.getInstance().getSize(modelNode);
modelTransform.setScale(1.8 / Math.max(Math.max(size.x, size.z), size.y));
modelTransform.mul(rotationTransform);
TransformGroup modelTransformGroup = (TransformGroup)this.sceneTree.getChild(0);
modelTransformGroup.setTransform(modelTransform);
}
}
/**
* Updates the rotation and the size of the 3D model displayed by this component.
*/
protected void setModelRotationAndSize(float [][] modelRotation,
float width, float depth, float height) {
BranchGroup modelNode = getModelNode();
if (modelNode != null && modelNode.numChildren() > 0) {
Transform3D normalization = ModelManager.getInstance().getNormalizedTransform(modelNode, modelRotation, 1f);
// Scale model to its size
Transform3D scaleTransform = new Transform3D();
if (width != 0 && depth != 0 && height != 0) {
scaleTransform.setScale(new Vector3d(width, height, depth));
}
scaleTransform.mul(normalization);
// Scale model to make it fit in a 1.8 unit wide box
Transform3D modelTransform = new Transform3D();
if (width != 0 && depth != 0 && height != 0) {
modelTransform.setScale(1.8 / Math.max(Math.max(width, height), depth));
} else {
Vector3f size = ModelManager.getInstance().getSize(modelNode);
modelTransform.setScale(1.8 / Math.max(Math.max(size.x, size.z), size.y));
}
modelTransform.mul(scaleTransform);
TransformGroup modelTransformGroup = (TransformGroup)this.sceneTree.getChild(0);
modelTransformGroup.setTransform(modelTransform);
}
}
/**
* Sets the color applied to 3D model.
*/
protected void setModelColor(Integer color) {
if (this.previewedPiece != null
&& this.previewedPiece.getColor() != color) {
this.previewedPiece.setColor(color);
getModelNode().update();
}
}
/**
* Sets the materials applied to 3D model.
*/
public void setModelMaterials(HomeMaterial [] materials) {
if (this.previewedPiece != null) {
this.previewedPiece.setModelMaterials(materials);
getModelNode().update();
// Replace textures by clones because Java 3D doesn't accept all the time to share textures
cloneTexture(getModelNode(), this.pieceTextures);
}
}
/**
* Replace the textures set on <code>node</code> shapes by clones.
*/
private void cloneTexture(Node node, Map<Texture, Texture> replacedTextures) {
if (node instanceof Group) {
// Enumerate children
Enumeration<?> enumeration = ((Group)node).getAllChildren();
while (enumeration.hasMoreElements()) {
cloneTexture((Node)enumeration.nextElement(), replacedTextures);
}
} else if (node instanceof Link) {
cloneTexture(((Link)node).getSharedGroup(), replacedTextures);
} else if (node instanceof Shape3D) {
Appearance appearance = ((Shape3D)node).getAppearance();
if (appearance != null) {
Texture texture = appearance.getTexture();
if (texture != null) {
Texture replacedTexture = replacedTextures.get(texture);
if (replacedTexture == null) {
replacedTexture = (Texture)texture.cloneNodeComponent(false);
replacedTextures.put(texture, replacedTexture);
}
appearance.setTexture(replacedTexture);
}
}
}
}
/**
* Returns the icon image matching the displayed view.
*/
private BufferedImage getIconImage(int maxWaitingDelay) {
Color backgroundColor = getBackground();
BufferedImage imageWithWhiteBackgound = null;
BufferedImage imageWithBlackBackgound = null;
this.iconImageLock = new Object();
try {
// Instead of using off screen images that may cause some problems
// use Robot to capture canvas 3D image
Point component3DOrigin = new Point();
SwingUtilities.convertPointToScreen(component3DOrigin, this.component3D);
Robot robot = new Robot();
// Render scene with a white background
if (this.iconImageLock != null) {
synchronized (this.iconImageLock) {
setBackground(Color.WHITE);
try {
this.iconImageLock.wait(maxWaitingDelay / 2);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
}
imageWithWhiteBackgound = robot.createScreenCapture(
new Rectangle(component3DOrigin, this.component3D.getSize()));
// Render scene with a black background
if (this.iconImageLock != null) {
synchronized (this.iconImageLock) {
setBackground(Color.BLACK);
try {
this.iconImageLock.wait(maxWaitingDelay / 2);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
}
imageWithBlackBackgound = robot.createScreenCapture(
new Rectangle(component3DOrigin, this.component3D.getSize()));
} catch (AWTException ex) {
throw new RuntimeException(ex);
} finally {
this.iconImageLock = null;
setBackground(backgroundColor);
}
int [] imageWithWhiteBackgoundPixels = imageWithWhiteBackgound.getRGB(
0, 0, imageWithWhiteBackgound.getWidth(), imageWithWhiteBackgound.getHeight(), null,
0, imageWithWhiteBackgound.getWidth());
int [] imageWithBlackBackgoundPixels = imageWithBlackBackgound.getRGB(
0, 0, imageWithBlackBackgound.getWidth(), imageWithBlackBackgound.getHeight(), null,
0, imageWithBlackBackgound.getWidth());
// Create an image with transparent pixels where model isn't drawn
for (int i = 0; i < imageWithBlackBackgoundPixels.length; i++) {
if (imageWithBlackBackgoundPixels [i] != imageWithWhiteBackgoundPixels [i]
&& imageWithBlackBackgoundPixels [i] == 0xFF000000
&& imageWithWhiteBackgoundPixels [i] == 0xFFFFFFFF) {
imageWithWhiteBackgoundPixels [i] = 0;
}
}
BufferedImage iconImage = new BufferedImage(imageWithWhiteBackgound.getWidth(), imageWithWhiteBackgound.getHeight(),
BufferedImage.TYPE_INT_ARGB);
Graphics2D g2D = (Graphics2D)iconImage.getGraphics();
g2D.drawImage(Toolkit.getDefaultToolkit().createImage(new MemoryImageSource(
imageWithWhiteBackgound.getWidth(), imageWithWhiteBackgound.getHeight(),
imageWithWhiteBackgoundPixels, 0, imageWithWhiteBackgound.getWidth())), null, null);
g2D.dispose();
return iconImage;
}
/**
* Returns a temporary content of the icon matching the displayed view.
*/
public Content getIcon(int maxWaitingDelay) throws IOException {
File tempIconFile = OperatingSystem.createTemporaryFile("icon", ".png");
ImageIO.write(getIconImage(maxWaitingDelay), "png", tempIconFile);
return new TemporaryURLContent(tempIconFile.toURI().toURL());
}
/**
* Notifies the canvas 3D displayed by this component was swapped.
*/
private void canvas3DSwapped() {
if (this.iconImageLock != null) {
synchronized (this.iconImageLock) {
this.iconImageLock.notify();
}
}
}
}