package org.jcae.viewer3d.post; import java.awt.Graphics2D; import java.awt.Window; import java.awt.image.BufferedImage; import java.io.PrintStream; import java.util.ArrayList; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import javax.media.j3d.*; import javax.vecmath.*; import org.jcae.opencascade.Utilities; import org.jcae.opencascade.jni.*; import org.jcae.viewer3d.DomainProvider; import org.jcae.viewer3d.PickViewable; import org.jcae.viewer3d.View; import org.jcae.viewer3d.Viewable; import org.jcae.viewer3d.bg.ViewableBG; import org.jcae.viewer3d.cad.*; import org.jcae.viewer3d.cad.occ.OCCProvider; import com.sun.j3d.utils.image.TextureLoader; import java.awt.Color; import java.util.HashMap; import org.jcae.viewer3d.ViewableAdaptor; /** * A special View which allows to fit a texture on a given geometry. * It helps to compute the transformation which will match the texture * on the geometry. * The texture is mapped with an orthogonal projection on the geometry. * The projection is done by OpenGL. The transformation applied on the * texture before being projected is computed using 2 user specified triangles, * one in the geometry space (3D) and one in the texture space (3D with z=0). * If the specified triangles are not similar (the angles of one are equal * to the corresponding angles of the other) the computed transformation * is normalized to ensure that the projection is not deformant. * @author Jerome Robert */ public class TextureFitter extends View { private static final long serialVersionUID = 2147414791584387916L; /** * A special ViewableCAD which allows picking points on a surface. * Other kind of picking are not availables (use the ViewableCAD if * you need them). */ public static class PickViewableCAD extends ViewableCAD { private Point3d pickingPoint; private int faceID; private final TopoDS_Shape shape; public PickViewableCAD(CADProvider provider, TopoDS_Shape shape) { super(provider); this.shape=shape; } @Override public void pick(PickViewable result) { if(result != null && result.getGeometryArray() != null) { Object o=result.getGeometryArray().getUserData(); if(o instanceof ViewableCAD.FacePickingInfo) { FacePickingInfo fpi=(FacePickingInfo) o; pickingPoint = result.getIntersection().getPointCoordinates(); faceID=fpi.getID(); fireSelectionChanged(); } } } /** Return the coordinates of the last picked point on a surface */ public double[] getLastPick() { if(pickingPoint!=null) { TopoDS_Face face = Utilities.getFace(shape, faceID); Geom_Surface geom = BRep_Tool.surface(face); double[] point=new double[3]; pickingPoint.get(point); GeomAPI_ProjectPointOnSurf proj=new GeomAPI_ProjectPointOnSurf(point, geom); if(proj.nbPoints()>0) return proj.point(1); return point; } return null; } @Override public void setSelectionMode(short mode) { throw new UnsupportedOperationException(); } @Override public void unselectAll() { //do nothing, particularly do not fire event about unselecting } } private static Transform3D computeTransform( Point3d[] triangle2d, Point3d[] triangle3d, int width, int height, boolean normalize, boolean haveBorder) { if(haveBorder) { Point3d[] tmp = new Point3d[3]; for(int i=0; i<3; i++) { tmp[i]=new Point3d(triangle2d[i]); tmp[i].x++; tmp[i].y++; tmp[i].z++; } triangle2d = tmp; } //3D -> 2D matrix Transform3D trsf1=normalizeTriangle(triangle2d); trsf1.mulInverse(normalizeTriangle(triangle3d)); //force orthogonal and uniform scale matrix if(normalize) { trsf1.normalizeCP(); trsf1.setScale(trsf1.getScale()); } //bitmap 2D -> 1x1 square Matrix4d m2d=new Matrix4d(); m2d.setM00(1.0/width); m2d.setM11(1.0/height); m2d.setM22(1); m2d.setM33(1); Transform3D t2d=new Transform3D(m2d); t2d.mul(trsf1); return t2d; } private static GeometryArray createGeometry(OCCProvider occProvider) { int[] ids=occProvider.getDomainIDs(); int nbint=0; int nbfl=0; ArrayList<FaceMesh> meshes=new ArrayList<FaceMesh>(); for(int i=0; i<ids.length; i++) { CADDomain d = (CADDomain) occProvider.getDomain(i); Iterator<FaceMesh> it = d.getFaceIterator(); if(it!=null) while(it.hasNext()) { FaceMesh fm=it.next(); nbint += fm.getMesh().length; nbfl+=fm.getNodes().length; meshes.add(fm); } } int[] trias=new int[nbint]; float[] nodes=new float[nbfl]; int destPosInt=0, destPosFl=0; for(int i=0; i<meshes.size(); i++) { FaceMesh fm=meshes.get(i); int[] m=fm.getMesh(); float[] n=fm.getNodes(); System.arraycopy(n, 0, nodes, destPosFl, n.length); System.arraycopy(m, 0, trias, destPosInt, m.length); for(int j=0; j<m.length; j++) { trias[destPosInt+j]+=destPosFl/3; } destPosInt+=m.length; destPosFl+=n.length; } IndexedTriangleArray ita=new IndexedTriangleArray( nodes.length/3,GeometryArray.COORDINATES, trias.length); ita.setCoordinates(0, nodes); ita.setCoordinateIndices(0, trias); return ita; } /** * Return selected faces in a viewable * @param viewable The viewable on witch picking has been done * @param shape The shape from which faces are selected. This is the one * used to create the viewable. * @return A compound including all selected faces */ public static TopoDS_Compound getSelectFaces(ViewableCAD viewable, TopoDS_Shape shape) { CADSelection[] ss=viewable.getSelection(); ArrayList<TopoDS_Face> faces=new ArrayList<TopoDS_Face>(); for(int i=0; i<ss.length; i++) { int[] ids=ss[i].getFaceIDs(); for(int j=0; j<ids.length; j++) faces.add(Utilities.getFace(shape, ids[j])); } if(faces.size()>0) { BRep_Builder bb=new BRep_Builder(); TopoDS_Compound compound=new TopoDS_Compound(); bb.makeCompound(compound); for(int i=0; i<faces.size(); i++) bb.add(compound, faces.get(i)); return compound; } return null; } /** * Compute the normalized transformation of triangle src to triangle dst * @param dst The 3 transformed points * @param src The 3 points to transform */ public static Matrix4d getTransform(Point3d[] dst, Point3d[] src) { return getTransform(dst, src, true); } /** * Compute the transformation of triangle src to triangle dst * @param dst The 3 transformed points * @param src The 3 points to transform * @param true to normalize the transformation */ public static Matrix4d getTransform(Point3d[] dst, Point3d[] src, boolean normalize) { Matrix4d m=new Matrix4d(); Transform3D trsf1=computeTransform(dst, src, 1, 1, normalize, false); trsf1.get(m); return m; } /** * Return the scaling factor in the 3 direction for the given * transformation. For texture fitting the 3 values should be equals, so * using this method is a way to control the validity of the input points. * @return a vector containing scaling for x, y and z */ public static Vector3d getScaling(Matrix4d m) { Vector3d toReturn=new Vector3d(); Vector4d v=new Vector4d(); m.getColumn(0, v); toReturn.x=v.length(); m.getColumn(1, v); toReturn.y=v.length(); m.getColumn(2, v); toReturn.z=v.length(); return toReturn; } /** * Return the sum in absolute value of scalar product of column vector of * the matrix. A value close to 0 means that the input points used to * define the texture fitting are good. A big value means they are not. */ public static double getOrthogonality(Matrix4d m) { double toReturn=0; Vector4d v4d1=new Vector4d(); Vector4d v4d2=new Vector4d(); Vector4d v4d3=new Vector4d(); m.getColumn(0, v4d1); m.getColumn(1, v4d2); m.getColumn(2, v4d3); toReturn+=Math.abs(v4d1.dot(v4d2)); toReturn+=Math.abs(v4d1.dot(v4d3)); toReturn+=Math.abs(v4d2.dot(v4d3)); return toReturn; } public static void displayMatrixInfo(Matrix4d matrix) { System.out.println("Matrix4d: "+matrix); Vector4d vx=new Vector4d(); Vector4d vy=new Vector4d(); Vector4d vz=new Vector4d(); Vector4d vt=new Vector4d(); matrix.getColumn(0, vx); System.out.println("X scale: "+vx.length()); matrix.getColumn(1, vy); System.out.println("Y scale: "+vy.length()); matrix.getColumn(2, vz); System.out.println("Z scale: "+vz.length()); matrix.getColumn(3, vt); System.out.println("translation: "+vt); System.out.println("x.y : "+vx.dot(vy)); System.out.println("x.z : "+vx.dot(vz)); System.out.println("y.z : "+vy.dot(vz)); Vector3d eulerAngles=new Vector3d(); Quat4d rotation=new Quat4d(); matrix.get(rotation); getEulerAngles(eulerAngles, rotation); System.out.println("theta: "+eulerAngles.x*180/Math.PI); System.out.println("phi: "+eulerAngles.y*180/Math.PI); System.out.println("psi: "+eulerAngles.z*180/Math.PI); } private static Transform3D normalized(Transform3D t3d) { Matrix4d matrix=new Matrix4d(); t3d.get(matrix); Vector4d v=new Vector4d(); matrix.getColumn(0, v); double scaleX=v.length(); matrix.getColumn(1, v); double scaleY=v.length(); matrix.getColumn(2, v); double scaleZ=v.length(); Vector3d translation=new Vector3d(); t3d.get(translation); Quat4d rotation=new Quat4d(); matrix.get(rotation); return new Transform3D(rotation, translation, (scaleX+scaleY+scaleZ)/3); } /** * from * http://en.wikipedia.org/wiki/Conversion_between_quaternions_and_Euler_angles */ /*public static void getEulerAngles(Tuple3d angles, Quat4d quat) { double q0=quat.x; double q1=quat.y; double q2=quat.z; double q3=quat.w; angles.x=Math.atan2(2*(q0*q1+q2*q3), 1-2*(q1*q1+q2+q2)); angles.y=Math.asin(2*(q0*q2-q3*q1)); angles.z=Math.atan2(2*(q0*q3+q1*q2), 1-2*(q2*q2+q3*q3)); }*/ /** * from * http://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToEuler/ */ public static void getEulerAngles(Tuple3d angles, Quat4d q1) { double heading, attitude, bank; double sqw = q1.w * q1.w; double sqx = q1.x * q1.x; double sqy = q1.y * q1.y; double sqz = q1.z * q1.z; double unit = sqx + sqy + sqz + sqw; // if normalised is one, // otherwise is correction // factor double test = q1.x * q1.y + q1.z * q1.w; if (test > 0.499 * unit) { // singularity at north pole heading = 2 * Math.atan2(q1.x, q1.w); attitude = Math.PI / 2; bank = 0; } else if (test < -0.499 * unit) { // singularity at south pole heading = -2 * Math.atan2(q1.x, q1.w); attitude = -Math.PI / 2; bank = 0; } else { heading = Math.atan2(2 * q1.y * q1.w - 2 * q1.x * q1.z, sqx - sqy - sqz + sqw); attitude = Math.asin(2 * test / unit); bank = Math.atan2(2 * q1.x * q1.w - 2 * q1.y * q1.z, -sqx + sqy - sqz + sqw); } angles.x = heading; angles.y = attitude; angles.z = bank; } /** * Compute the transformation which will transform the triangle (O, x, y) to * (p1, p2, p3) */ private static Transform3D normalizeTriangle(Point3d[] p) { Vector3d imgX=new Vector3d(); Vector3d imgY=new Vector3d(); Vector3d imgZ=new Vector3d(); imgX.sub(p[1], p[0]); imgY.sub(p[2], p[0]); imgZ.cross(imgX, imgY); imgZ.scale((imgX.length()+imgY.length())/imgZ.length()/2); Matrix3d rotation=new Matrix3d(); rotation.setColumn(0, imgX); rotation.setColumn(1, imgY); rotation.setColumn(2, imgZ); Transform3D toReturn=new Transform3D(); toReturn.set(rotation, new Vector3d(p[0]), 1); return toReturn; } private Map texCoordMap = new HashMap(); private Map imageMap = new HashMap(); /** * @param frame the window owning the widget */ public TextureFitter(Window frame) { super(frame, false, true); } private static BufferedImage addTransparentBorder(BufferedImage image) { BufferedImage toReturn = new BufferedImage( image.getWidth()+2, image.getHeight()+2, BufferedImage.TYPE_INT_ARGB); Graphics2D g = toReturn.createGraphics(); g.drawImage(image, 1, 1, null); return toReturn; } private Texture createTexture(BufferedImage image) { TextureLoader tl=new TextureLoader(addTransparentBorder(image), TextureLoader.ALLOW_NON_POWER_OF_TWO); Map map=queryProperties(); int textureWidthMax=((Integer)map.get("textureWidthMax")).intValue(); int textureHeightMax=((Integer)map.get("textureHeightMax")).intValue(); boolean textureNonPowerOfTwoAvailable= ((Boolean)map.get("textureNonPowerOfTwoAvailable")).booleanValue(); while(true) { ImageComponent2D img=tl.getImage(); if(img.getWidth()<=textureWidthMax && img.getHeight()<=textureHeightMax) break; tl=new TextureLoader(tl.getScaledImage(0.5f, 0.5f).getImage(), TextureLoader.ALLOW_NON_POWER_OF_TWO); } int flags=TextureLoader.GENERATE_MIPMAP|TextureLoader.Y_UP; if(textureNonPowerOfTwoAvailable) flags=flags|TextureLoader.ALLOW_NON_POWER_OF_TWO; tl=new TextureLoader(tl.getImage().getImage(), flags); Texture toReturn = tl.getTexture(); toReturn.setBoundaryModeS(Texture.CLAMP); toReturn.setBoundaryModeT(Texture.CLAMP); return toReturn; } /** * Display the texture with a normalized projection * @param shape The shape on which the texture must be displayed * @param triangle2d The 2D points (z=0) picked on the bitmap * @param triangle3d The 3D points picked on the geometry * @param image The image to display * @param true to normalize */ public Viewable displayTexture(TopoDS_Shape shape, Point3d[] triangle2d, Point3d[] triangle3d, BufferedImage image) { return displayTexture(shape, triangle2d, triangle3d, image, true); } /** * @param shape The shape on which the texture must be displayed * @param triangle2d The 2D points (z=0) picked on the bitmap * @param triangle3d The 3D points picked on the geometry * @param image The image to display * @param true to normalize * @return */ public Viewable displayTexture(TopoDS_Shape shape, Point3d[] triangle2d, Point3d[] triangle3d, BufferedImage image, boolean normalize) { BranchGroup bg=new BranchGroup(); Viewable textureViewable=new ViewableBG(bg); OCCProvider occProvider=new OCCProvider(shape); Shape3D shape3D=new Shape3D(createGeometry(occProvider)); Appearance app=new Appearance(); Texture theTexture = createTexture(image); TexCoordGeneration texCoordGeneration = new TexCoordGeneration( TexCoordGeneration.EYE_LINEAR, TexCoordGeneration.TEXTURE_COORDINATE_2); texCoordGeneration.setCapability(TexCoordGeneration.ALLOW_PLANE_WRITE); texCoordMap.put(textureViewable, texCoordGeneration); imageMap.put(textureViewable, image); updateTexture(textureViewable, triangle2d, triangle3d, normalize); app.setTexture(theTexture); app.setTexCoordGeneration(texCoordGeneration); TransparencyAttributes trA= new TransparencyAttributes(); trA.setTransparencyMode(TransparencyAttributes.FASTEST); app.setTransparencyAttributes(trA); shape3D.setAppearance(app); PolygonAttributes pa = new PolygonAttributes(); pa.setCullFace(PolygonAttributes.CULL_NONE); app.setPolygonAttributes(pa); bg.addChild(shape3D); add(textureViewable); return textureViewable; } public void removeTexture(Viewable viewable) { texCoordMap.remove(viewable); imageMap.remove(viewable); remove(viewable); } /** * Move the texture. The projection is normalized. * @param triangle2d The 2D points (z=0) picked on the bitmap * @param triangle3d The 3D points picked on the geometry */ public void updateTexture(Viewable viewable, Point3d[] triangle2d, Point3d[] triangle3d) { updateTexture(viewable, triangle2d, triangle3d, true); } /** * Move the texture * @param triangle2d The 2D points (z=0) picked on the bitmap * @param triangle3d The 3D points picked on the geometry * @param true to normalize */ public void updateTexture(Viewable viewable, Point3d[] triangle2d, Point3d[] triangle3d, boolean normalize) { BufferedImage image = (BufferedImage) imageMap.get(viewable); TexCoordGeneration texCoordGeneration = (TexCoordGeneration) texCoordMap.get(viewable); Matrix4f m=new Matrix4f(); Transform3D trsf1=computeTransform(triangle2d, triangle3d, image.getWidth(), image.getHeight(), normalize, true); trsf1.get(m); Vector4f vS=new Vector4f(); Vector4f vT=new Vector4f(); m.getRow(0, vS); m.getRow(1, vT); texCoordGeneration.setPlaneS(vS); texCoordGeneration.setPlaneT(vT); } /** * Display 3D properties for debugging purpose * It use Canvas3D.queryProperties * @param out The PrintStream where to print (i.e. System.out) */ public void print3DProperties(PrintStream out) { Map map=queryProperties(); Iterator it=map.entrySet().iterator(); while(it.hasNext()) { Entry e = (Entry)it.next(); System.out.println(e.getKey()+" "+e.getValue()); } } public Viewable drawPoint(final double x, final double y, final double z) { Viewable toReturn = new ViewableAdaptor() { //TODO so many empty methods probably means that ViewableAdaptor sucks public void domainsChangedPerform(int[] domainId){} public DomainProvider getDomainProvider(){return null;} public void setDomainVisible(Map<Integer, Boolean> map){} public void pick(PickViewable result){} public void unselectAll(){} public Node getJ3DNode() { ColoringAttributes ca=new ColoringAttributes(); ca.setColor(new Color3f(Color.YELLOW)); PointAttributes pat=new PointAttributes(5f, false); PointArray pa=new PointArray(1, GeometryArray.COORDINATES); pa.setCoordinates(0, new double[]{x, y, z}); Appearance a=new Appearance(); a.setPointAttributes(pat); a.setColoringAttributes(ca); Shape3D s3d = new Shape3D(pa, a); return s3d; } }; add(toReturn); return toReturn; } }