/* * HomePieceOfFurniture3D.java 23 jan. 09 * * Sweet Home 3D, Copyright (c) 2007-2009 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.j3d; import java.awt.Color; import java.awt.Rectangle; import java.awt.Shape; import java.lang.ref.WeakReference; import java.util.Enumeration; import java.util.HashSet; import java.util.List; import java.util.Set; import javax.media.j3d.Appearance; import javax.media.j3d.BoundingBox; import javax.media.j3d.BoundingLeaf; import javax.media.j3d.BoundingSphere; import javax.media.j3d.Bounds; import javax.media.j3d.BranchGroup; import javax.media.j3d.CapabilityNotSetException; import javax.media.j3d.Geometry; import javax.media.j3d.GeometryArray; import javax.media.j3d.Group; import javax.media.j3d.Link; import javax.media.j3d.Material; import javax.media.j3d.Node; import javax.media.j3d.PointLight; import javax.media.j3d.PolygonAttributes; import javax.media.j3d.RenderingAttributes; import javax.media.j3d.Shape3D; import javax.media.j3d.TexCoordGeneration; import javax.media.j3d.Texture; import javax.media.j3d.TextureAttributes; import javax.media.j3d.Transform3D; import javax.media.j3d.TransformGroup; import javax.media.j3d.TransparencyAttributes; import javax.vecmath.Color3f; import javax.vecmath.Point3d; import javax.vecmath.Point3f; import javax.vecmath.Vector3d; import javax.vecmath.Vector3f; import javax.vecmath.Vector4f; import com.eteks.sweethome3d.j3d.TextureManager.TextureObserver; import com.eteks.sweethome3d.model.Content; import com.eteks.sweethome3d.model.Home; import com.eteks.sweethome3d.model.HomeEnvironment; import com.eteks.sweethome3d.model.HomeFurnitureGroup; import com.eteks.sweethome3d.model.HomeLight; import com.eteks.sweethome3d.model.HomeMaterial; import com.eteks.sweethome3d.model.HomePieceOfFurniture; import com.eteks.sweethome3d.model.HomeTexture; import com.eteks.sweethome3d.model.Level; import com.eteks.sweethome3d.model.Light; import com.eteks.sweethome3d.model.LightSource; import com.eteks.sweethome3d.model.Room; import com.eteks.sweethome3d.model.Selectable; import com.eteks.sweethome3d.model.SelectionEvent; import com.eteks.sweethome3d.model.SelectionListener; import com.sun.j3d.utils.geometry.Box; /** * Root of piece of furniture branch. */ public class HomePieceOfFurniture3D extends Object3DBranch { private static final TransparencyAttributes DEFAULT_TEXTURED_SHAPE_TRANSPARENCY_ATTRIBUTES = new TransparencyAttributes(TransparencyAttributes.NICEST, 0); private static final PolygonAttributes DEFAULT_TEXTURED_SHAPE_POLYGON_ATTRIBUTES = new PolygonAttributes(PolygonAttributes.POLYGON_FILL, PolygonAttributes.CULL_NONE, 0); private static final TextureAttributes MODULATE_TEXTURE_ATTRIBUTES = new TextureAttributes(); private static final Bounds DEFAULT_INFLUENCING_BOUNDS = new BoundingSphere(new Point3d(), 1E7); private final Home home; static { MODULATE_TEXTURE_ATTRIBUTES.setTextureMode(TextureAttributes.MODULATE); } /** * Creates the 3D piece matching the given home <code>piece</code>. */ public HomePieceOfFurniture3D(HomePieceOfFurniture piece, Home home) { this(piece, home, false, false); } /** * Creates the 3D piece matching the given home <code>piece</code>. */ public HomePieceOfFurniture3D(HomePieceOfFurniture piece, Home home, boolean ignoreDrawingMode, boolean waitModelAndTextureLoadingEnd) { setUserData(piece); this.home = home; // Allow piece branch to be removed from its parent setCapability(BranchGroup.ALLOW_DETACH); // Allow to read branch transform child setCapability(BranchGroup.ALLOW_CHILDREN_READ); setCapability(BranchGroup.ALLOW_CHILDREN_EXTEND); if (piece instanceof HomeFurnitureGroup) { for (HomePieceOfFurniture groupPiece : ((HomeFurnitureGroup)piece).getFurniture()) { addChild(new HomePieceOfFurniture3D(groupPiece, home, ignoreDrawingMode, waitModelAndTextureLoadingEnd)); } } else { createPieceOfFurnitureNode(piece, ignoreDrawingMode, waitModelAndTextureLoadingEnd); } } /** * Creates the piece node with its transform group and add it to the piece branch. */ private void createPieceOfFurnitureNode(final HomePieceOfFurniture piece, final boolean ignoreDrawingMode, final boolean waitModelAndTextureLoadingEnd) { final TransformGroup pieceTransformGroup = new TransformGroup(); // Allow the change of the transformation that sets piece size and position pieceTransformGroup.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE); pieceTransformGroup.setCapability(Group.ALLOW_CHILDREN_READ); pieceTransformGroup.setCapability(Group.ALLOW_CHILDREN_WRITE); pieceTransformGroup.setCapability(Group.ALLOW_CHILDREN_EXTEND); addChild(pieceTransformGroup); if (piece instanceof HomeLight) { BoundingLeaf bounds = new BoundingLeaf(); bounds.setCapability(BoundingLeaf.ALLOW_REGION_WRITE); addChild(bounds); } // While loading model use a temporary node that displays a white box final BranchGroup waitBranch = new BranchGroup(); waitBranch.setCapability(BranchGroup.ALLOW_DETACH); waitBranch.addChild(getModelBox(Color.WHITE)); // Allow appearance change on all children setModelCapabilities(waitBranch); pieceTransformGroup.addChild(waitBranch); // Set piece model initial location, orientation and size updatePieceOfFurnitureTransform(); // Load piece real 3D model Content model = piece.getModel(); ModelManager.getInstance().loadModel(model, waitModelAndTextureLoadingEnd, new ModelManager.ModelObserver() { public void modelUpdated(BranchGroup modelRoot) { float [][] modelRotation = piece.getModelRotation(); // Add piece model scene to a normalized transform group TransformGroup modelTransformGroup = ModelManager.getInstance().getNormalizedTransformGroup(modelRoot, modelRotation, 1); updatePieceOfFurnitureModelNode(modelRoot, modelTransformGroup, ignoreDrawingMode, waitModelAndTextureLoadingEnd); } public void modelError(Exception ex) { // In case of problem use a default red box updatePieceOfFurnitureModelNode(getModelBox(Color.RED), new TransformGroup(), ignoreDrawingMode, waitModelAndTextureLoadingEnd); } }); } @Override public void update() { HomePieceOfFurniture piece = (HomePieceOfFurniture)getUserData(); if (piece instanceof HomeFurnitureGroup) { Enumeration<?> enumeration = getAllChildren(); while (enumeration.hasMoreElements()) { ((HomePieceOfFurniture3D)enumeration.nextElement()).update(); } } else { updatePieceOfFurnitureTransform(); updatePieceOfFurnitureColorAndTexture(false); updateLight(); updatePieceOfFurnitureVisibility(); updatePieceOfFurnitureModelMirrored(); } } /** * Sets the transformation applied to piece model to match * its location, its angle and its size. */ private void updatePieceOfFurnitureTransform() { HomePieceOfFurniture piece = (HomePieceOfFurniture)getUserData(); // Set piece size Transform3D scale = new Transform3D(); float pieceWidth = piece.getWidth(); // If piece model is mirrored, inverse its width if (piece.isModelMirrored()) { pieceWidth *= -1; } scale.setScale(new Vector3d(pieceWidth, piece.getHeight(), piece.getDepth())); // Change its angle around y axis Transform3D orientation = new Transform3D(); orientation.rotY(-piece.getAngle()); orientation.mul(scale); // Translate it to its location Transform3D pieceTransform = new Transform3D(); float z = piece.getElevation() + piece.getHeight() / 2; if (piece.getLevel() != null) { z += piece.getLevel().getElevation(); } pieceTransform.setTranslation(new Vector3f(piece.getX(), z, piece.getY())); pieceTransform.mul(orientation); // Change model transformation ((TransformGroup)getChild(0)).setTransform(pieceTransform); } /** * Sets the color and the texture applied to piece model. */ private void updatePieceOfFurnitureColorAndTexture(boolean waitTextureLoadingEnd) { HomePieceOfFurniture piece = (HomePieceOfFurniture)getUserData(); Node filledModelNode = getFilledModelNode(); if (piece.getColor() != null) { setColorAndTexture(filledModelNode, piece.getColor(), null, piece.getShininess(), null, false, null, null, new HashSet<Appearance>()); } else if (piece.getTexture() != null) { setColorAndTexture(filledModelNode, null, piece.getTexture(), piece.getShininess(), null, waitTextureLoadingEnd, new Vector3f(piece.getWidth(), piece.getHeight(), piece.getDepth()), ModelManager.getInstance().getBounds(((Group)filledModelNode).getChild(0)), new HashSet<Appearance>()); } else if (piece.getModelMaterials() != null) { setColorAndTexture(filledModelNode, null, null, null, piece.getModelMaterials(), waitTextureLoadingEnd, new Vector3f(piece.getWidth(), piece.getHeight(), piece.getDepth()), ModelManager.getInstance().getBounds(((Group)filledModelNode).getChild(0)), new HashSet<Appearance>()); } else { // Set default material and texture of model setColorAndTexture(filledModelNode, null, null, piece.getShininess(), null, false, null, null, new HashSet<Appearance>()); } } /** * Sets the light color if the piece is a light. */ private void updateLight() { HomePieceOfFurniture piece = (HomePieceOfFurniture)getUserData(); if (piece instanceof HomeLight && this.home != null) { boolean enabled = this.home.getEnvironment().getSubpartSizeUnderLight() > 0 && piece.isVisible() && (piece.getLevel() == null || piece.getLevel().isVisible()); HomeLight light = (HomeLight)piece; LightSource [] lightSources = light.getLightSources(); if (numChildren() > 2) { Color homeLightColor = new Color(this.home.getEnvironment().getLightColor()); float homeLightColorRed = homeLightColor.getRed() / 3072f; float homeLightColorGreen = homeLightColor.getGreen() / 3072f; float homeLightColorBlue = homeLightColor.getBlue() / 3072f; float angle = light.getAngle(); float cos = (float)Math.cos(angle); float sin = (float)Math.sin(angle); Group lightsBranch = (Group)getChild(2); for (int i = 0; i < lightSources.length; i++) { LightSource lightSource = lightSources [i]; Color lightColor = new Color(lightSource.getColor()); float power = light.getPower(); PointLight pointLight = (PointLight)lightsBranch.getChild(i); pointLight.setColor(new Color3f( lightColor.getRed() / 255f * power + (power > 0 ? homeLightColorRed : 0), lightColor.getGreen() / 255f * power + (power > 0 ? homeLightColorGreen : 0), lightColor.getBlue() / 255f * power + (power > 0 ? homeLightColorBlue : 0))); // Compute the position of the light instead of resizing/placing it with a transformation // that has some influence on attenuation float xLightSourceInLight = -light.getWidth() / 2 + (lightSource.getX() * light.getWidth()); float yLightSourceInLight = light.getDepth() / 2 - (lightSource.getY() * light.getDepth()); float lightElevation = light.getGroundElevation(); pointLight.setPosition( light.getX() + xLightSourceInLight * cos - yLightSourceInLight * sin, lightElevation + (lightSource.getZ() * light.getHeight()), light.getY() + xLightSourceInLight * sin + yLightSourceInLight * cos); pointLight.setEnable(enabled); } if (enabled) { Bounds bounds = DEFAULT_INFLUENCING_BOUNDS; for (Room room : this.home.getRooms()) { Level roomLevel = room.getLevel(); if (light.isAtLevel(roomLevel)) { Shape roomShape = getShape(room.getPoints()); if (roomShape.contains(light.getX(), light.getY())) { Rectangle roomBounds = roomShape.getBounds(); float minElevation = roomLevel != null ? roomLevel.getElevation() : 0; float maxElevation = roomLevel != null ? minElevation + roomLevel.getHeight() : 1E7f; float epsilon = 0.1f; bounds = new BoundingBox( new Point3d(roomBounds.getMinX() - epsilon, minElevation - epsilon, roomBounds.getMinY() - epsilon), new Point3d(roomBounds.getMaxX() + epsilon, maxElevation + epsilon, roomBounds.getMaxY() + epsilon)); break; } } } ((BoundingLeaf)getChild(1)).setRegion(bounds); } } } } /** * Returns the node of the filled model. */ private Node getFilledModelNode() { TransformGroup transformGroup = (TransformGroup)getChild(0); BranchGroup branchGroup = (BranchGroup)transformGroup.getChild(0); return branchGroup.getChild(0); } /** * Returns the node of the outline model. */ private Node getOutlineModelNode() { TransformGroup transformGroup = (TransformGroup)getChild(0); BranchGroup branchGroup = (BranchGroup)transformGroup.getChild(0); if (branchGroup.numChildren() > 1) { return branchGroup.getChild(1); } else { return null; } } /** * Sets whether this piece model is visible or not. */ private void updatePieceOfFurnitureVisibility() { HomePieceOfFurniture piece = (HomePieceOfFurniture)getUserData(); Node outlineModelNode = getOutlineModelNode(); HomeEnvironment.DrawingMode drawingMode; if (outlineModelNode != null) { drawingMode = this.home.getEnvironment().getDrawingMode(); } else { drawingMode = null; } // Update visibility of filled model shapes boolean visible = piece.isVisible() && (piece.getLevel() == null || piece.getLevel().isVisible()); setVisible(getFilledModelNode(), visible && (drawingMode == null || drawingMode == HomeEnvironment.DrawingMode.FILL || drawingMode == HomeEnvironment.DrawingMode.FILL_AND_OUTLINE)); if (outlineModelNode != null) { // Update visibility of outline model shapes setVisible(outlineModelNode, visible && (drawingMode == HomeEnvironment.DrawingMode.OUTLINE || drawingMode == HomeEnvironment.DrawingMode.FILL_AND_OUTLINE)); } } /** * Sets whether this piece model is mirrored or not. */ private void updatePieceOfFurnitureModelMirrored() { HomePieceOfFurniture piece = (HomePieceOfFurniture)getUserData(); // Cull front or back model faces whether its model is mirrored or not setCullFace(getFilledModelNode(), piece.isModelMirrored() ^ piece.isBackFaceShown() ? PolygonAttributes.CULL_FRONT : PolygonAttributes.CULL_BACK); // Flip normals if back faces of model are shown if (piece.isBackFaceShown()) { setBackFaceNormalFlip(getFilledModelNode(), true); } } /** * Updates transform group children with <code>modelMode</code>. */ private void updatePieceOfFurnitureModelNode(Node modelNode, TransformGroup normalization, boolean ignoreDrawingMode, boolean waitTextureLoadingEnd) { BranchGroup modelBranch = new BranchGroup(); normalization.addChild(modelNode); normalization.setCapability(ALLOW_CHILDREN_READ); // Add model node to branch group modelBranch.addChild(normalization); if (!ignoreDrawingMode) { // Add outline model node modelBranch.addChild(createOutlineModelNode(normalization)); } setModelCapabilities(modelBranch); TransformGroup transformGroup = (TransformGroup)getChild(0); // Remove previous nodes transformGroup.removeAllChildren(); // Add model branch to live scene transformGroup.addChild(modelBranch); HomePieceOfFurniture piece = (HomePieceOfFurniture)getUserData(); if (piece instanceof HomeLight) { BranchGroup lightBranch = new BranchGroup(); lightBranch.setCapability(ALLOW_CHILDREN_READ); HomeLight light = (HomeLight)piece; for (int i = light.getLightSources().length; i > 0 ; i--) { PointLight pointLight = new PointLight(new Color3f(), new Point3f(), new Point3f(0.25f, 0, 0.0000025f)); pointLight.setCapability(PointLight.ALLOW_POSITION_WRITE); pointLight.setCapability(PointLight.ALLOW_COLOR_WRITE); pointLight.setCapability(PointLight.ALLOW_STATE_WRITE); BoundingLeaf bounds = (BoundingLeaf)getChild(1); pointLight.setInfluencingBoundingLeaf(bounds); lightBranch.addChild(pointLight); } addChild(lightBranch); } // Update piece color, visibility and model mirror in dispatch thread as // these attributes may be changed in that thread updatePieceOfFurnitureColorAndTexture(waitTextureLoadingEnd); updateLight(); updatePieceOfFurnitureVisibility(); updatePieceOfFurnitureModelMirrored(); // Manage light sources visibility if (this.home != null && getUserData() instanceof Light) { this.home.addSelectionListener(new LightSelectionListener(this)); } } /** * Selection listener bound to this object with a weak reference to avoid * strong link between home and this tree. */ private static class LightSelectionListener implements SelectionListener { private WeakReference<HomePieceOfFurniture3D> piece; public LightSelectionListener(HomePieceOfFurniture3D piece) { this.piece = new WeakReference<HomePieceOfFurniture3D>(piece); } public void selectionChanged(SelectionEvent ev) { // If piece 3D was garbage collected, remove this listener from home HomePieceOfFurniture3D piece3D = this.piece.get(); Home home = (Home)ev.getSource(); if (piece3D == null) { home.removeSelectionListener(this); } else { piece3D.updatePieceOfFurnitureVisibility(); } } } /** * Returns a box that may replace model. */ private Node getModelBox(Color color) { Material material = new Material(); material.setDiffuseColor(new Color3f(color)); material.setAmbientColor(new Color3f(color.darker())); Appearance boxAppearance = new Appearance(); boxAppearance.setMaterial(material); return new Box(0.5f, 0.5f, 0.5f, boxAppearance); } /** * Returns a clone of the given node with an outline appearance on its shapes. */ private Node createOutlineModelNode(Node modelNode) { Node node = ModelManager.getInstance().cloneNode(modelNode); setOutlineAppearance(node); return node; } /** * Sets the outline appearance on all the children of <code>node</code>. */ private void setOutlineAppearance(Node node) { if (node instanceof Group) { Enumeration<?> enumeration = ((Group)node).getAllChildren(); while (enumeration.hasMoreElements()) { setOutlineAppearance((Node)enumeration.nextElement()); } } else if (node instanceof Link) { setOutlineAppearance(((Link)node).getSharedGroup()); } else if (node instanceof Shape3D) { Appearance outlineAppearance = new Appearance(); ((Shape3D)node).setAppearance(outlineAppearance); outlineAppearance.setCapability(Appearance.ALLOW_RENDERING_ATTRIBUTES_READ); RenderingAttributes renderingAttributes = new RenderingAttributes(); renderingAttributes.setCapability(RenderingAttributes.ALLOW_VISIBLE_WRITE); outlineAppearance.setRenderingAttributes(renderingAttributes); outlineAppearance.setColoringAttributes(Object3DBranch.OUTLINE_COLORING_ATTRIBUTES); outlineAppearance.setPolygonAttributes(Object3DBranch.OUTLINE_POLYGON_ATTRIBUTES); outlineAppearance.setLineAttributes(Object3DBranch.OUTLINE_LINE_ATTRIBUTES); } } /** * Sets the capabilities to change material and rendering attributes, and to read geometries * for all children of <code>node</code>. */ private void setModelCapabilities(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()) { setModelCapabilities((Node)enumeration.nextElement()); } } else if (node instanceof Link) { node.setCapability(Link.ALLOW_SHARED_GROUP_READ); setModelCapabilities(((Link)node).getSharedGroup()); } else if (node instanceof Shape3D) { Shape3D shape = (Shape3D)node; Appearance appearance = shape.getAppearance(); if (appearance != null) { setAppearanceCapabilities(appearance); } Enumeration<?> enumeration = shape.getAllGeometries(); while (enumeration.hasMoreElements()) { setGeometryCapabilities((Geometry)enumeration.nextElement()); } node.setCapability(Shape3D.ALLOW_APPEARANCE_READ); node.setCapability(Shape3D.ALLOW_APPEARANCE_WRITE); node.setCapability(Shape3D.ALLOW_BOUNDS_READ); } } /** * Sets the material and texture attribute of all <code>Shape3D</code> children nodes of <code>node</code> * from the given <code>color</code> and <code>texture</code>. */ private void setColorAndTexture(Node node, Integer color, HomeTexture texture, Float shininess, HomeMaterial [] materials, boolean waitTextureLoadingEnd, Vector3f pieceSize, BoundingBox modelBounds, Set<Appearance> modifiedAppearances) { if (node instanceof Group) { // Set material and texture of all children Enumeration<?> enumeration = ((Group)node).getAllChildren(); while (enumeration.hasMoreElements()) { setColorAndTexture((Node)enumeration.nextElement(), color, texture, shininess, materials, waitTextureLoadingEnd, pieceSize, modelBounds, modifiedAppearances); } } else if (node instanceof Link) { setColorAndTexture(((Link)node).getSharedGroup(), color, texture, shininess, materials, waitTextureLoadingEnd, pieceSize, modelBounds, modifiedAppearances); } else if (node instanceof Shape3D) { final Shape3D shape = (Shape3D)node; String shapeName = (String)shape.getUserData(); // Change material and texture of all shapes that are not window panes if (shapeName == null || !shapeName.startsWith(ModelManager.WINDOW_PANE_SHAPE_PREFIX)) { Appearance appearance = shape.getAppearance(); if (appearance == null) { appearance = createAppearanceWithChangeCapabilities(); ((Shape3D)node).setAppearance(appearance); } // Check appearance wasn't already changed if (!modifiedAppearances.contains(appearance)) { // Use appearance user data to store shape default material DefaultMaterialAndTexture defaultMaterialAndTexture = (DefaultMaterialAndTexture)appearance.getUserData(); if (defaultMaterialAndTexture == null) { defaultMaterialAndTexture = new DefaultMaterialAndTexture(appearance); appearance.setUserData(defaultMaterialAndTexture); } float materialShininess = shininess != null ? shininess.floatValue() : (appearance.getMaterial() != null ? appearance.getMaterial().getShininess() / 128f : 0); if (color != null) { // Change material if no default texture is displayed on the shape // (textures always keep the colors of their image file) appearance.setMaterial(getMaterial(color, color, materialShininess)); appearance.setTransparencyAttributes(defaultMaterialAndTexture.getTransparencyAttributes()); appearance.setPolygonAttributes(defaultMaterialAndTexture.getPolygonAttributes()); appearance.setTexCoordGeneration(defaultMaterialAndTexture.getTexCoordGeneration()); appearance.setTextureAttributes(defaultMaterialAndTexture.getTextureAttributes()); appearance.setTexture(null); } else if (color == null && texture != null) { // Change material to white then texture appearance.setTexCoordGeneration(getTextureCoordinates(appearance, texture, pieceSize, modelBounds)); appearance.setMaterial(getMaterial(DEFAULT_COLOR, DEFAULT_AMBIENT_COLOR, materialShininess)); appearance.setTextureAttributes(MODULATE_TEXTURE_ATTRIBUTES); TextureManager.getInstance().loadTexture(texture.getImage(), waitTextureLoadingEnd, getTextureObserver(appearance)); } else if (materials != null && materials.length > 0) { boolean materialFound = false; // Apply color, texture and shininess of the material named as appearance name for (HomeMaterial material : materials) { if (material != null && material.getName().equals(appearance.getName())) { if (material.getShininess() != null) { materialShininess = material.getShininess(); } color = material.getColor(); if (color != null) { appearance.setMaterial(getMaterial(color, color, materialShininess)); appearance.setTexture(null); appearance.setTransparencyAttributes(defaultMaterialAndTexture.getTransparencyAttributes()); appearance.setPolygonAttributes(defaultMaterialAndTexture.getPolygonAttributes()); } else if (color == null && material.getTexture() != null) { if (!isTexturesCoordinatesDefined(shape)) { appearance.setTexCoordGeneration(getTextureCoordinates(appearance, material.getTexture(), pieceSize, modelBounds)); } appearance.setMaterial(getMaterial(DEFAULT_COLOR, DEFAULT_AMBIENT_COLOR, materialShininess)); appearance.setTextureAttributes(MODULATE_TEXTURE_ATTRIBUTES); TextureManager.getInstance().loadTexture(material.getTexture().getImage(), waitTextureLoadingEnd, getTextureObserver(appearance)); } else { restoreDefaultMaterialAndTexture(appearance, material.getShininess()); } materialFound = true; break; } } if (!materialFound) { restoreDefaultMaterialAndTexture(appearance, null); } } else { restoreDefaultMaterialAndTexture(appearance, shininess); } // Store modified appearances to avoid changing their values more than once modifiedAppearances.add(appearance); } } } } /** * Returns a texture observer that will update the given <code>appearance</code>. */ private TextureObserver getTextureObserver(final Appearance appearance) { return new TextureManager.TextureObserver() { public void textureUpdated(Texture texture) { if (TextureManager.getInstance().isTextureTransparent(texture)) { appearance.setTransparencyAttributes(DEFAULT_TEXTURED_SHAPE_TRANSPARENCY_ATTRIBUTES); appearance.setPolygonAttributes(DEFAULT_TEXTURED_SHAPE_POLYGON_ATTRIBUTES); } else { DefaultMaterialAndTexture defaultMaterialAndTexture = (DefaultMaterialAndTexture)appearance.getUserData(); if (defaultMaterialAndTexture != null) { appearance.setTransparencyAttributes(defaultMaterialAndTexture.getTransparencyAttributes()); } } appearance.setTexture(texture); } }; } /** * Returns a texture coordinates generator that wraps the given texture on front face. */ private TexCoordGeneration getTextureCoordinates(Appearance appearance, HomeTexture texture, Vector3f pieceSize, BoundingBox modelBounds) { Point3d lower = new Point3d(); modelBounds.getLower(lower); Point3d upper = new Point3d(); modelBounds.getUpper(upper); float minimumSize = ModelManager.getInstance().getMinimumSize(); float sx = pieceSize.x / (float)Math.max(upper.x - lower.x, minimumSize) / texture.getWidth(); float sw = texture.isLeftToRightOriented() ? (float)-lower.x * sx : 0; float ty = pieceSize.y / (float)Math.max(upper.y - lower.y, minimumSize) / texture.getHeight(); float tz = pieceSize.z / (float)Math.max(upper.z - lower.z, minimumSize) / texture.getHeight(); float tw = texture.isLeftToRightOriented() ? (float)(-lower.y * ty + upper.z * tz) : 0; return new TexCoordGeneration(TexCoordGeneration.OBJECT_LINEAR, TexCoordGeneration.TEXTURE_COORDINATE_2, new Vector4f(sx, 0, 0, sw), new Vector4f(0, ty, -tz, tw)); } /** * Returns <code>true</code> if all the geometries of the given <code>shape</code> define some texture coordinates. */ private boolean isTexturesCoordinatesDefined(Shape3D shape) { for (int i = 0, n = shape.numGeometries(); i < n; i++) { Geometry geometry = shape.getGeometry(i); if (geometry instanceof GeometryArray && (((GeometryArray)geometry).getVertexFormat() & GeometryArray.TEXTURE_COORDINATE_2) == 0) { return false; } } return true; } /** * Restores default material and texture of the given <code>appearance</code>. */ private void restoreDefaultMaterialAndTexture(Appearance appearance, Float shininess) { DefaultMaterialAndTexture defaultMaterialAndTexture = (DefaultMaterialAndTexture)appearance.getUserData(); Material defaultMaterial = defaultMaterialAndTexture.getMaterial(); if (defaultMaterial != null && shininess != null) { defaultMaterial = (Material)defaultMaterial.cloneNodeComponent(true); defaultMaterial.setSpecularColor(new Color3f(shininess, shininess, shininess)); defaultMaterial.setShininess(shininess * 128); } appearance.setMaterial(defaultMaterial); appearance.setTransparencyAttributes(defaultMaterialAndTexture.getTransparencyAttributes()); appearance.setPolygonAttributes(defaultMaterialAndTexture.getPolygonAttributes()); appearance.setTexCoordGeneration(defaultMaterialAndTexture.getTexCoordGeneration()); appearance.setTexture(defaultMaterialAndTexture.getTexture()); appearance.setTextureAttributes(defaultMaterialAndTexture.getTextureAttributes()); } /** * Sets the visible attribute of the <code>Shape3D</code> children nodes of <code>node</code>. */ private void setVisible(Node node, boolean visible) { if (node instanceof Group) { // Set visibility of all children Enumeration<?> enumeration = ((Group)node).getAllChildren(); while (enumeration.hasMoreElements()) { setVisible((Node)enumeration.nextElement(), visible); } } else if (node instanceof Link) { setVisible(((Link)node).getSharedGroup(), visible); } else if (node instanceof Shape3D) { final Shape3D shape = (Shape3D)node; Appearance appearance = shape.getAppearance(); if (appearance == null) { appearance = createAppearanceWithChangeCapabilities(); ((Shape3D)node).setAppearance(appearance); } RenderingAttributes renderingAttributes = appearance.getRenderingAttributes(); if (renderingAttributes == null) { renderingAttributes = new RenderingAttributes(); renderingAttributes.setCapability(RenderingAttributes.ALLOW_VISIBLE_WRITE); appearance.setRenderingAttributes(renderingAttributes); } String shapeName = (String)shape.getUserData(); if (visible && shapeName != null && (getUserData() instanceof Light) && shapeName.startsWith(ModelManager.LIGHT_SHAPE_PREFIX) && this.home != null && !isSelected(this.home.getSelectedItems())) { // Don't display light sources shapes of unselected lights visible = false; } // Change visibility renderingAttributes.setVisible(visible); } } /** * Returns <code>true</code> if this piece of furniture belongs to <code>selectedItems</code>. */ private boolean isSelected(List<? extends Selectable> selectedItems) { Object piece = getUserData(); for (Selectable item : selectedItems) { if (item == piece || (item instanceof HomeFurnitureGroup && isSelected(((HomeFurnitureGroup)item).getFurniture()))) { return true; } } return false; } /** * Sets the cull face of all <code>Shape3D</code> children nodes of <code>node</code>. * @param cullFace <code>PolygonAttributes.CULL_FRONT</code> or <code>PolygonAttributes.CULL_BACK</code> */ private void setCullFace(Node node, int cullFace) { if (node instanceof Group) { // Set cull face of all children Enumeration<?> enumeration = ((Group)node).getAllChildren(); while (enumeration.hasMoreElements()) { setCullFace((Node)enumeration.nextElement(), cullFace); } } else if (node instanceof Link) { setCullFace(((Link)node).getSharedGroup(), cullFace); } else if (node instanceof Shape3D) { Appearance appearance = ((Shape3D)node).getAppearance(); if (appearance == null) { appearance = createAppearanceWithChangeCapabilities(); ((Shape3D)node).setAppearance(appearance); } PolygonAttributes polygonAttributes = appearance.getPolygonAttributes(); if (polygonAttributes == null) { polygonAttributes = createPolygonAttributesWithChangeCapabilities(); appearance.setPolygonAttributes(polygonAttributes); } // Change cull face try { if (polygonAttributes.getCullFace() != PolygonAttributes.CULL_NONE) { polygonAttributes.setCullFace(cullFace); } } catch (CapabilityNotSetException ex) { // Shouldn't happen since capability is set but happens though with Java 3D 1.3 ex.printStackTrace(); } } } /** * Sets whether all <code>Shape3D</code> children nodes of <code>node</code> should have * their normal flipped or not. * @param backFaceNormalFlip <code>true</code> if normals should be flipped. */ private void setBackFaceNormalFlip(Node node, boolean backFaceNormalFlip) { if (node instanceof Group) { // Set back face normal flip of all children Enumeration<?> enumeration = ((Group)node).getAllChildren(); while (enumeration.hasMoreElements()) { setBackFaceNormalFlip((Node)enumeration.nextElement(), backFaceNormalFlip); } } else if (node instanceof Link) { setBackFaceNormalFlip(((Link)node).getSharedGroup(), backFaceNormalFlip); } else if (node instanceof Shape3D) { Appearance appearance = ((Shape3D)node).getAppearance(); if (appearance == null) { appearance = createAppearanceWithChangeCapabilities(); ((Shape3D)node).setAppearance(appearance); } PolygonAttributes polygonAttributes = appearance.getPolygonAttributes(); if (polygonAttributes == null) { polygonAttributes = createPolygonAttributesWithChangeCapabilities(); appearance.setPolygonAttributes(polygonAttributes); } // Change back face normal flip polygonAttributes.setBackFaceNormalFlip(backFaceNormalFlip); } } private PolygonAttributes createPolygonAttributesWithChangeCapabilities() { PolygonAttributes polygonAttributes = new PolygonAttributes(); polygonAttributes.setCapability(PolygonAttributes.ALLOW_CULL_FACE_READ); polygonAttributes.setCapability(PolygonAttributes.ALLOW_CULL_FACE_WRITE); polygonAttributes.setCapability(PolygonAttributes.ALLOW_NORMAL_FLIP_WRITE); return polygonAttributes; } private Appearance createAppearanceWithChangeCapabilities() { Appearance appearance = new Appearance(); setAppearanceCapabilities(appearance); return appearance; } private void setAppearanceCapabilities(Appearance appearance) { // Allow future material and rendering attributes changes appearance.setCapability(Appearance.ALLOW_MATERIAL_READ); appearance.setCapability(Appearance.ALLOW_MATERIAL_WRITE); Material material = appearance.getMaterial(); if (material != null) { material.setCapability(Material.ALLOW_COMPONENT_READ); } appearance.setCapability(Appearance.ALLOW_RENDERING_ATTRIBUTES_READ); appearance.setCapability(Appearance.ALLOW_RENDERING_ATTRIBUTES_WRITE); appearance.setCapability(Appearance.ALLOW_POLYGON_ATTRIBUTES_READ); appearance.setCapability(Appearance.ALLOW_POLYGON_ATTRIBUTES_WRITE); appearance.setCapability(Appearance.ALLOW_TEXGEN_READ); appearance.setCapability(Appearance.ALLOW_TEXGEN_WRITE); appearance.setCapability(Appearance.ALLOW_TEXTURE_READ); appearance.setCapability(Appearance.ALLOW_TEXTURE_WRITE); appearance.setCapability(Appearance.ALLOW_TEXTURE_ATTRIBUTES_READ); appearance.setCapability(Appearance.ALLOW_TEXTURE_ATTRIBUTES_WRITE); appearance.setCapability(Appearance.ALLOW_TRANSPARENCY_ATTRIBUTES_READ); appearance.setCapability(Appearance.ALLOW_TRANSPARENCY_ATTRIBUTES_WRITE); PolygonAttributes polygonAttributes = appearance.getPolygonAttributes(); if (polygonAttributes != null) { polygonAttributes.setCapability(PolygonAttributes.ALLOW_CULL_FACE_READ); polygonAttributes.setCapability(PolygonAttributes.ALLOW_CULL_FACE_WRITE); polygonAttributes.setCapability(PolygonAttributes.ALLOW_NORMAL_FLIP_WRITE); } } private void setGeometryCapabilities(Geometry geometry) { // Sets the geometry capabilities needed to read attributes saved by OBJWriter 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); } } /** * A class used to store the default material and texture of a shape. */ private static class DefaultMaterialAndTexture { private final Material material; private final TransparencyAttributes transparencyAttributes; private final PolygonAttributes polygonAttributes; private final TexCoordGeneration texCoordGeneration; private final Texture texture; private final TextureAttributes textureAttributes; public DefaultMaterialAndTexture(Appearance appearance) { this.material = appearance.getMaterial(); this.transparencyAttributes = appearance.getTransparencyAttributes(); this.polygonAttributes = appearance.getPolygonAttributes(); this.texCoordGeneration = appearance.getTexCoordGeneration(); this.texture = appearance.getTexture(); this.textureAttributes = appearance.getTextureAttributes(); } public Material getMaterial() { return this.material; } public TransparencyAttributes getTransparencyAttributes() { return this.transparencyAttributes; } public PolygonAttributes getPolygonAttributes() { return this.polygonAttributes; } public TexCoordGeneration getTexCoordGeneration() { return this.texCoordGeneration; } public Texture getTexture() { return this.texture; } public TextureAttributes getTextureAttributes() { return this.textureAttributes; } } }