Package com.eteks.sweethome3d.swing

Source Code of com.eteks.sweethome3d.swing.ModelPreviewComponent

/*
* 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.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics2D;
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.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 javax.imageio.ImageIO;
import javax.media.j3d.AmbientLight;
import javax.media.j3d.Appearance;
import javax.media.j3d.Background;
import javax.media.j3d.BoundingBox;
import javax.media.j3d.BoundingSphere;
import javax.media.j3d.BranchGroup;
import javax.media.j3d.Canvas3D;
import javax.media.j3d.DirectionalLight;
import javax.media.j3d.Geometry;
import javax.media.j3d.GeometryArray;
import javax.media.j3d.Group;
import javax.media.j3d.Light;
import javax.media.j3d.Link;
import javax.media.j3d.Material;
import javax.media.j3d.Node;
import javax.media.j3d.PhysicalBody;
import javax.media.j3d.PhysicalEnvironment;
import javax.media.j3d.PolygonAttributes;
import javax.media.j3d.Shape3D;
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.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.ModelManager;
import com.eteks.sweethome3d.model.Content;
import com.eteks.sweethome3d.tools.OperatingSystem;
import com.eteks.sweethome3d.tools.TemporaryURLContent;
import com.sun.j3d.utils.universe.SimpleUniverse;
import com.sun.j3d.utils.universe.ViewingPlatform;

/**
* Super class of 3D preview component for model.
*/
public class ModelPreviewComponent extends JComponent {
  private SimpleUniverse     universe;
  private Canvas3D           canvas3D;
  private BranchGroup        sceneTree;
  private float              viewYaw = (float) Math.PI / 8;
  private float              viewPitch = -(float)Math.PI / 16;
  private float              viewScale = 1;
  private Object             iconImageLock;

  /**
   * 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) {
    this.canvas3D = Component3DManager.getInstance().getOnscreenCanvas3D(
        new Component3DManager.RenderingObserver() {
          public void canvas3DPreRendered(Canvas3D canvas3d) {
          }
         
          public void canvas3DPostRendered(Canvas3D canvas3d) {
          }
         
          public void canvas3DSwapped(Canvas3D canvas3d) {
            ModelPreviewComponent.this.canvas3DSwapped();
          }           
        });

    // Layout canvas3D
    setLayout(new GridLayout(1, 1));
    add(this.canvas3D);
    this.canvas3D.setFocusable(false);     
    addMouseListeners(this.canvas3D, pitchAndScaleChangeSupported);
   
    setBorder(BorderFactory.createEtchedBorder(EtchedBorder.LOWERED));

    this.sceneTree = createSceneTree();
   
    // Add an ancestor listener to create canvas universe once this component is made visible
    // and clean up universe once its parent frame is disposed
    addAncestorListener();
  }

  /**
   * Returns component preferred size.
   */
  @Override
  public Dimension getPreferredSize() {
    if (isPreferredSizeSet()) {
      return super.getPreferredSize();
    } else {
      return new Dimension(200, 200);
    }
  }

  /**
   * Returns the canvas 3D displayed by this component.
   */
  protected Canvas3D getCanvas3D() {
    return this.canvas3D;
  }
 
  /**
   * Adds an AWT mouse listener to canvas that will update view platform transform. 
   */
  private void addMouseListeners(Canvas3D canvas3D,
                                 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 (getModel() != 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();
            }
          }
        }
      };

    canvas3D.addMouseListener(mouseListener);
    canvas3D.addMouseMotionListener(mouseListener);
   
    if (pitchAndScaleChangeSupported) {
      canvas3D.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))));
          }
        });
    }
  }

  /**
   * Adds an ancestor listener to this component to manage canvas universe
   * creation and clean up. 
   */
  private void addAncestorListener() {
    addAncestorListener(new AncestorListener() {
        public void ancestorAdded(AncestorEvent event) {
          if (universe == null) {
            createUniverse();
          }
        }
       
        public void ancestorRemoved(AncestorEvent event) {
          if (universe != null) {
            disposeUniverse();
          }
        }
       
        public void ancestorMoved(AncestorEvent event) {
        }       
      });
  }
 
  /**
   * Creates universe bound to canvas.
   */
  private void createUniverse() {
    // Link canvas 3D to a default universe
    this.universe = new SimpleUniverse(this.canvas3D);
    this.canvas3D.setFocusable(false);
    // Set viewer location
    updateViewPlatformTransform(this.universe.getViewingPlatform().getViewPlatformTransform(),
        getViewYaw(), getViewPitch(), getViewScale());
    // Link scene to universe
    this.universe.addBranchGraph(this.sceneTree);

    if (OperatingSystem.isMacOSX()) {
      final Component root = SwingUtilities.getRoot(this);
      EventQueue.invokeLater(new Runnable() {
          public void run() {
            // Force a real repaint of the component with a resize of its root,
            // otherwise some canvas 3D may not be displayed
            Dimension rootSize = root.getSize();
            root.setSize(new Dimension(rootSize.width + 1, rootSize.height));
            try {
              Thread.sleep(100);
            } catch (InterruptedException ex) {
            }
            root.setSize(new Dimension(rootSize.width, rootSize.height));
            // 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.
   */
  protected 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(getModelTree());
    root.addChild(getBackgroundNode());
    for (Light light : getLights()) {
      root.addChild(light);
    }
    return root;
  }
 
  /**
   * Returns the background node. 
   */
  private Node getBackgroundNode() {
    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 [] getLights() {
    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 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 getModelTree() {
    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 <code>model</code> displayed by this component.
   */
  public BranchGroup getModel() {
    TransformGroup modelTransformGroup = (TransformGroup)this.sceneTree.getChild(0);
    if (modelTransformGroup.numChildren() > 0) {
      return (BranchGroup)modelTransformGroup.getChild(0);
    } else {
      return null;
    }
  }
 
  /**
   * Sets the <code>model</code> displayed by this component.
   * The model is shown at its default orientation and in a box of 1 unit wide.
   */
  public void setModel(BranchGroup model) {
    TransformGroup modelTransformGroup = (TransformGroup)this.sceneTree.getChild(0);
    modelTransformGroup.removeAllChildren();
    if (model != null) {
      model = (BranchGroup)ModelManager.getInstance().cloneNode(model);
      model.setCapability(BranchGroup.ALLOW_DETACH);
      setNodeCapabilities(model);
     
      if (model.numChildren() > 0) {
        BoundingBox bounds = null;
        try {
          bounds = ModelManager.getInstance().getBounds(model);
        } catch (IllegalArgumentException ex) {
          // Model is empty
        }
        if (bounds != null) {
          Point3d lower = new Point3d();
          bounds.getLower(lower);
          Point3d upper = new Point3d();
          bounds.getUpper(upper);
         
          // Translate model to center
          Transform3D translation = new Transform3D ();
          translation.setTranslation(
              new Vector3d(-lower.x - (upper.x - lower.x) / 2,
                           -lower.y - (upper.y - lower.y) / 2,
                           -lower.z - (upper.z - lower.z) / 2));
          // Scale model to make it fit in a 1.8 unit wide box
          Transform3D modelTransform = new Transform3D();
          modelTransform.setScale (1.8 / Math.max (Math.max (upper.x -lower.x, upper.y - lower.y),
                                                   upper.z - lower.z));
          modelTransform.mul(translation);
         
          modelTransformGroup.setTransform(modelTransform);
          modelTransformGroup.addChild(model);
        }
      }
    }
  }
 
  /**
   * Sets the capability to read bounds, to write polygon and material attributes 
   * for all children of <code>node</code>.
   */
  private void setNodeCapabilities(Node node) {
    if (node instanceof Group) {
      node.setCapability(Group.ALLOW_CHILDREN_READ);
      if (node instanceof TransformGroup) {
        node.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
      }
      Enumeration<?> enumeration = ((Group)node).getAllChildren();
      while (enumeration.hasMoreElements()) {
        setNodeCapabilities((Node)enumeration.nextElement());
      }
    } else if (node instanceof Link) {
      node.setCapability(Link.ALLOW_SHARED_GROUP_READ);
      setNodeCapabilities(((Link)node).getSharedGroup());
    } else if (node instanceof Shape3D) {
      Shape3D shape = (Shape3D)node;
      node.setCapability(Node.ALLOW_BOUNDS_READ);
      Appearance appearance = ((Shape3D)node).getAppearance();
      if (appearance == null) {
        appearance = new Appearance();
        shape.setAppearance(appearance);
      }
      appearance.setCapability(Appearance.ALLOW_POLYGON_ATTRIBUTES_READ);
      appearance.setCapability(Appearance.ALLOW_MATERIAL_READ);
      appearance.setCapability(Appearance.ALLOW_MATERIAL_WRITE);
      node.setCapability(Shape3D.ALLOW_APPEARANCE_READ);

      PolygonAttributes polygonAttributes = appearance.getPolygonAttributes();
      if (polygonAttributes == null) {
        polygonAttributes = new PolygonAttributes();
        appearance.setPolygonAttributes(polygonAttributes);
      }
      polygonAttributes.setCapability(PolygonAttributes.ALLOW_CULL_FACE_WRITE);
      polygonAttributes.setCapability(PolygonAttributes.ALLOW_NORMAL_FLIP_WRITE);
     
      Enumeration<?> enumeration = shape.getAllGeometries();
      while (enumeration.hasMoreElements()) {
        Geometry geometry = (Geometry) enumeration.nextElement();
        if (!geometry.isLive()
            && geometry instanceof GeometryArray) {
          geometry.setCapability(GeometryArray.ALLOW_FORMAT_READ);
          geometry.setCapability(GeometryArray.ALLOW_COUNT_READ);
          geometry.setCapability(GeometryArray.ALLOW_COORDINATE_READ);
          geometry.setCapability(GeometryArray.ALLOW_NORMAL_READ);
          geometry.setCapability(GeometryArray.ALLOW_TEXCOORD_READ);
          geometry.setCapability(GeometryArray.ALLOW_REF_DATA_READ);
        }
      }
    }
  }
 
  /**
   * Sets the back face visibility of all <code>Shape3D</code> children nodes of displayed model.
   */
  protected void setBackFaceShown(boolean backFaceShown) {
    setBackFaceShown(this.sceneTree.getChild(0), backFaceShown);
  }
 
  /**
   * Sets the back face visibility of all <code>Shape3D</code> children nodes of <code>node</code>.
   */
  private void setBackFaceShown(Node node, boolean backFaceShown) {
    if (node instanceof Group) {
      // Set visibility of all children
      Enumeration<?> enumeration = ((Group)node).getAllChildren();
      while (enumeration.hasMoreElements()) {
        setBackFaceShown((Node)enumeration.nextElement(), backFaceShown);
      }
    } else if (node instanceof Link) {
      setBackFaceShown(((Link)node).getSharedGroup(), backFaceShown);
    } else if (node instanceof Shape3D) {
      Appearance appearance = ((Shape3D)node).getAppearance();
      PolygonAttributes polygonAttributes = appearance.getPolygonAttributes();
      // Change cull face
      polygonAttributes.setCullFace(backFaceShown
          ? PolygonAttributes.CULL_FRONT
          : PolygonAttributes.CULL_BACK);
      // Change back face normal flip
      polygonAttributes.setBackFaceNormalFlip(backFaceShown);
    }
  }

  /**
   * Updates the rotation of the model displayed by this component.
   * The model is shown at its default size.
   */
  protected void setModelRotation(float [][] modelRotation) {
    BranchGroup model = getModel();
    if (model != null && model.numChildren() > 0) {
      BoundingBox bounds = ModelManager.getInstance().getBounds(model);
      Point3d lower = new Point3d();
      bounds.getLower(lower);
      Point3d upper = new Point3d();
      bounds.getUpper(upper);
   
      // Translate model to center
      Transform3D translation = new Transform3D ();
      translation.setTranslation(
          new Vector3d(-lower.x - (upper.x - lower.x) / 2,
                       -lower.y - (upper.y - lower.y) / 2,
                       -lower.z - (upper.z - lower.z) / 2));
      // 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);
      }
      rotationTransform.mul(translation);
      // Scale model to make it fit in a 1.8 unit wide box     
      Transform3D modelTransform = new Transform3D();
      modelTransform.setScale(1.8 / Math.max(Math.max((upper.x -lower.x), (upper.z - lower.z)), (upper.y - lower.y)));
      modelTransform.mul(rotationTransform);
     
      TransformGroup modelTransformGroup = (TransformGroup)this.sceneTree.getChild(0);
      modelTransformGroup.setTransform(modelTransform);
    }
  }
 
  /**
   * Updates the rotation and the size of the model displayed by this component.
   */
  protected void setModelRotationAndSize(float [][] modelRotation,
                                         float width, float depth, float height) {
    BranchGroup model = getModel();
    if (model != null && model.numChildren() > 0) {
      BoundingBox bounds = ModelManager.getInstance().getBounds(model);
      Point3d lower = new Point3d();
      bounds.getLower(lower);
      Point3d upper = new Point3d();
      bounds.getUpper(upper);
     
      // Translate model to center
      Transform3D translation = new Transform3D ();
      translation.setTranslation(
          new Vector3d(-lower.x - (upper.x - lower.x) / 2,
                       -lower.y - (upper.y - lower.y) / 2,
                       -lower.z - (upper.z - lower.z) / 2));
      // Scale model to make it fill a 1 unit wide box
      Transform3D scaleOneTransform = new Transform3D();
      scaleOneTransform.setScale (
          new Vector3d(1 / Math.max(0.0001, upper.x -lower.x),
              1 / Math.max(0.0001, upper.y - lower.y),
              1 / Math.max(0.0001, upper.z - lower.z)));
      scaleOneTransform.mul(translation);
      // 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);
      }
      rotationTransform.mul(scaleOneTransform);
      // 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(rotationTransform);
      // 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 {
        modelTransform.setScale(1.8 / Math.max(Math.max((upper.x -lower.x), (upper.z - lower.z)), (upper.y - lower.y)));
      }
      modelTransform.mul(scaleTransform);
     
      TransformGroup modelTransformGroup = (TransformGroup)this.sceneTree.getChild(0);
      modelTransformGroup.setTransform(modelTransform);
    }
  }

  /**
   * Sets the color applied to piece model.
   */
  protected void setModelColor(Integer color) {
    if (color != null) {
      Color3f materialColor = new Color3f(((color >>> 16) & 0xFF) / 255f,
                                           ((color >>> 8) & 0xFF) / 255f,
                                                   (color & 0xFF) / 255f);
      setMaterial(this.sceneTree.getChild(0),
          new Material(materialColor, new Color3f(), materialColor, materialColor, 64));
    } else {
      // Set default material of model
      setMaterial(this.sceneTree.getChild(0), null);
    }
  }

  /**
   * Sets the material attribute of all <code>Shape3D</code> children nodes of <code>node</code>
   * with a given <code>material</code>.
   */
  private void setMaterial(Node node, Material material) {
    if (node instanceof Group) {
      // Set material of all children
      Enumeration<?> enumeration = ((Group)node).getAllChildren();
      while (enumeration.hasMoreElements()) {
        setMaterial((Node)enumeration.nextElement(), material);
      }
    } else if (node instanceof Link) {
      setMaterial(((Link)node).getSharedGroup(), material);
    } else if (node instanceof Shape3D) {
      Shape3D shape = (Shape3D)node;
      String shapeName = (String)shape.getUserData();
      // Change material of all shape that are not window panes
      if (shapeName == null
          || !shapeName.startsWith(ModelManager.WINDOW_PANE_SHAPE_PREFIX)) {
        Appearance appearance = shape.getAppearance();
        // Use appearance user data to store shape default material
        Material defaultMaterial = (Material)appearance.getUserData();
        if (defaultMaterial == null) {
          defaultMaterial = appearance.getMaterial();
          appearance.setUserData(defaultMaterial);
        }
        // Change material
        if (material != null) {
          appearance.setMaterial(material);
        } else {
          // Restore default material
          appearance.setMaterial(defaultMaterial);
        }
      }
    }
  }

  /**
   * 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 instead of
      Point canvas3DOrigin = new Point();
      SwingUtilities.convertPointToScreen(canvas3DOrigin, this.canvas3D);
     
      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(canvas3DOrigin, this.canvas3D.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(canvas3DOrigin, this.canvas3D.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 the icon content 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();
      }
    }
  }
}
TOP

Related Classes of com.eteks.sweethome3d.swing.ModelPreviewComponent

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.