Package org.jmol.shapespecial

Source Code of org.jmol.shapespecial.Polyhedra$Polyhedron

/* $RCSfile$
* $Author: hansonr $
* $Date: 2007-03-29 04:39:40 -0500 (Thu, 29 Mar 2007) $
* $Revision: 7248 $
*
* Copyright (C) 2004-2005  The Jmol Development Team
*
* Contact: jmol-developers@lists.sf.net
*
*  This library is free software; you can redistribute it and/or
*  modify it under the terms of the GNU Lesser General Public
*  License as published by the Free Software Foundation; either
*  version 2.1 of the License, or (at your option) any later version.
*
*  This library 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
*  Lesser General Public License for more details.
*
*  You should have received a copy of the GNU Lesser General Public
*  License along with this library; if not, write to the Free Software
*  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*/

package org.jmol.shapespecial;

import org.jmol.api.AtomIndexIterator;
import org.jmol.g3d.Graphics3D;
import org.jmol.modelset.Atom;
import org.jmol.modelset.Bond;
import org.jmol.shape.AtomShape;
import org.jmol.util.Escape;
import org.jmol.util.ArrayUtil;
import org.jmol.util.Logger;

import java.util.BitSet;
import java.util.Hashtable;
import javax.vecmath.Point3i;
import javax.vecmath.Point3f;
import javax.vecmath.Vector3f;

import org.jmol.util.Measure;
import org.jmol.viewer.JmolConstants;

public class Polyhedra extends AtomShape {

  private final static float DEFAULT_DISTANCE_FACTOR = 1.85f;
  private final static float DEFAULT_FACECENTEROFFSET = 0.25f;
  private final static int EDGES_NONE = 0;
  final static int EDGES_ALL = 1;
  final static int EDGES_FRONT = 2;
  private final static int MAX_VERTICES = 150;
  private final static int FACE_COUNT_MAX = MAX_VERTICES - 3;
  private Point3f[] otherAtoms = new Point3f[MAX_VERTICES + 1];

  int polyhedronCount;
  Polyhedron[] polyhedrons = new Polyhedron[32];
  int drawEdges;

  private float radius;
  private int nVertices;

  float faceCenterOffset;
  float distanceFactor;
  boolean isCollapsed;

  private boolean iHaveCenterBitSet;
  private boolean bondedOnly;
  private boolean haveBitSetVertices;

  private BitSet centers;
  private BitSet bsVertices;
  private BitSet bsVertexCount;

  public void setProperty(String propertyName, Object value, BitSet bs) {

    if ("init" == propertyName) {
      faceCenterOffset = DEFAULT_FACECENTEROFFSET;
      distanceFactor = DEFAULT_DISTANCE_FACTOR;
      radius = 0.0f;
      nVertices = 0;
      bsVertices = null;
      centers = null;
      bsVertexCount = new BitSet();
      bondedOnly = isCollapsed = iHaveCenterBitSet = false;
      drawEdges = EDGES_NONE;
      haveBitSetVertices = false;
      return;
    }

    if ("generate" == propertyName) {
      if (!iHaveCenterBitSet)
        centers = bs;
      deletePolyhedra();
      buildPolyhedra();
      return;
    }

    if ("collapsed" == propertyName) {
      isCollapsed = ((Boolean) value).booleanValue();
      return;
    }

    if ("nVertices" == propertyName) {
      nVertices = ((Integer) value).intValue();
      bsVertexCount.set(nVertices);
      return;
    }

    if ("centers" == propertyName) {
      centers = (BitSet) value;
      iHaveCenterBitSet = true;
      return;
    }

    if ("to" == propertyName) {
      bsVertices = (BitSet) value;
      return;
    }

    if ("toBitSet" == propertyName) {
      bsVertices = (BitSet) value;
      haveBitSetVertices = true;
      return;
    }

    if ("faceCenterOffset" == propertyName) {
      faceCenterOffset = ((Float) value).floatValue();
      return;
    }

    if ("distanceFactor" == propertyName) {
      // not a general user option
      distanceFactor = ((Float) value).floatValue();
      return;
    }

    if ("bonds" == propertyName) {
      bondedOnly = true;
      return;
    }

    if ("delete" == propertyName) {
      if (!iHaveCenterBitSet)
        centers = bs;
      deletePolyhedra();
      return;
    }
    if ("on" == propertyName) {
      if (!iHaveCenterBitSet)
        centers = bs;
      setVisible(true);
      return;
    }
    if ("off" == propertyName) {
      if (!iHaveCenterBitSet)
        centers = bs;
      setVisible(false);
      return;
    }
    if ("noedges" == propertyName) {
      drawEdges = EDGES_NONE;
      return;
    }
    if ("edges" == propertyName) {
      drawEdges = EDGES_ALL;
      return;
    }
    if ("frontedges" == propertyName) {
      drawEdges = EDGES_FRONT;
      return;
    }
    if (propertyName.indexOf("color") == 0) {
      // from polyhedra command, we may not be using the prior select
      // but from Color we need to identify the centers.
      if ("colorThis" == propertyName && iHaveCenterBitSet)
        bs = centers;
      else
        andBitSet(bs);
      propertyName = "color";
      //allow super
    }

    if (propertyName.indexOf("translucency") == 0) {
      // from polyhedra command, we may not be using the prior select
      // but from Color we need to identify the centers.
      if ("translucencyThis" == propertyName && iHaveCenterBitSet)
        bs = centers;
      else
        andBitSet(bs);
      //allow super
    }

    if ("radius" == propertyName) {
      radius = ((Float) value).floatValue();
      return;
    }

    if (propertyName == "deleteModelAtoms") {
      int modelIndex = ((int[]) ((Object[]) value)[2])[0];
      for (int i = polyhedronCount; --i >= 0;) {
        if (polyhedrons[i].modelIndex == modelIndex) {
          polyhedronCount--;
          polyhedrons = (Polyhedron[]) ArrayUtil.deleteElements(polyhedrons, i, 1);
        } else if (polyhedrons[i].modelIndex > modelIndex) {
          polyhedrons[i].modelIndex--;
        }
      }
      //pass on to AtomShape
    }

    super.setProperty(propertyName, value, bs);
  }

  private void andBitSet(BitSet bs) {
    BitSet bsCenters = new BitSet();
    for (int i = polyhedronCount; --i >= 0;)
      bsCenters.set(polyhedrons[i].centralAtom.getIndex());
    bs.and(bsCenters);
  }

  private void deletePolyhedra() {
    int newCount = 0;
    byte pid = JmolConstants.pidOf(null);
    for (int i = 0; i < polyhedronCount; ++i) {
      Polyhedron p = polyhedrons[i];
      int iAtom = p.centralAtom.getIndex();
      if (centers.get(iAtom))
        setColixAndPalette(Graphics3D.INHERIT_ALL, pid, iAtom);
      else
        polyhedrons[newCount++] = p;
    }
    for (int i = newCount; i < polyhedronCount; ++i)
      polyhedrons[i] = null;
    polyhedronCount = newCount;
  }

  private void setVisible(boolean visible) {
    for (int i = polyhedronCount; --i >= 0;) {
      Polyhedron p = polyhedrons[i];
      if (p != null && centers.get(p.centralAtom.getIndex()))
        p.visible = visible;
    }
  }

  private void buildPolyhedra() {
    boolean useBondAlgorithm = radius == 0 || bondedOnly;
    AtomIndexIterator iter = modelSet.getSelectedAtomIterator(null, false, false, false);
    for (int i = centers.nextSetBit(0); i >= 0; i = centers.nextSetBit(i + 1)) {
      Polyhedron p = (haveBitSetVertices ? constructBitSetPolyhedron(i)
          : useBondAlgorithm ? constructBondsPolyhedron(i)
              : constructRadiusPolyhedron(i, iter));
      if (p != null) {
        if (polyhedronCount == polyhedrons.length)
          polyhedrons = (Polyhedron[]) ArrayUtil.doubleLength(polyhedrons);
        polyhedrons[polyhedronCount++] = p;
      }
      if (haveBitSetVertices)
        break;
    }
    iter.release();
  }

  private Polyhedron constructBondsPolyhedron(int atomIndex) {
    Atom atom = atoms[atomIndex];
    Bond[] bonds = atom.getBonds();
    if (bonds == null)
      return null;
    int bondCount = 0;
    for (int i = bonds.length; --i >= 0;) {
      Bond bond = bonds[i];
      Atom otherAtom = bond.getAtom1() == atom ? bond.getAtom2() : bond
          .getAtom1();
      if (bsVertices != null && !bsVertices.get(otherAtom.getIndex()))
        continue;
      if (radius > 0f && bond.getAtom1().distance(bond.getAtom2()) > radius)
        continue;
      otherAtoms[bondCount++] = otherAtom;
      if (bondCount == MAX_VERTICES)
        break;
    }
    if (bondCount < 3 || nVertices > 0 && !bsVertexCount.get(bondCount))
      return null;
    return validatePolyhedronNew(atom, bondCount, otherAtoms);
  }

  private Polyhedron constructBitSetPolyhedron(int atomIndex) {
    int otherAtomCount = 0;
    for (int i = bsVertices.nextSetBit(0); i >= 0; i = bsVertices
        .nextSetBit(i + 1))
      otherAtoms[otherAtomCount++] = atoms[i];
    return validatePolyhedronNew(atoms[atomIndex], otherAtomCount, otherAtoms);
  }

  private Polyhedron constructRadiusPolyhedron(int atomIndex, AtomIndexIterator iter) {
    Atom atom = atoms[atomIndex];
    int otherAtomCount = 0;
    viewer.setIteratorForAtom(iter, atomIndex, radius);
    while (iter.hasNext()) {
      Atom other = atoms[iter.next()];
      if (bsVertices != null && !bsVertices.get(other.getIndex())
          || atom.distance(other) > radius)
        continue;
      if (other.getAlternateLocationID() != atom.getAlternateLocationID()
          && other.getAlternateLocationID() != 0
          && atom.getAlternateLocationID() != 0)
        continue;
      if (otherAtomCount == MAX_VERTICES)
        break;
      otherAtoms[otherAtomCount++] = other;
    }
    if (otherAtomCount < 3 || nVertices > 0
        && !bsVertexCount.get(otherAtomCount))
      return null;
    return validatePolyhedronNew(atom, otherAtomCount, otherAtoms);
  }

  private short[] normixesT = new short[MAX_VERTICES];
  private byte[] planesT = new byte[MAX_VERTICES * 3];
  private final static Point3f randomPoint = new Point3f(3141f, 2718f, 1414f);

  private Polyhedron validatePolyhedronNew(Atom centralAtom, int vertexCount,
                                   Point3f[] otherAtoms) {
    Vector3f normal = new Vector3f();
    int planeCount = 0;
    int ipt = 0;
    int ptCenter = vertexCount;
    int nPoints = ptCenter + 1;
    float distMax = 0;
    float dAverage = 0;

    Point3f[] points = new Point3f[MAX_VERTICES * 3];
    points[ptCenter] = otherAtoms[ptCenter] = centralAtom;
    for (int i = 0; i < ptCenter; i++) {
      points[i] = otherAtoms[i];
      dAverage += points[ptCenter].distance(points[i]);
    }
    dAverage = dAverage / ptCenter;
    float factor = distanceFactor;
    BitSet bs = new BitSet(ptCenter);
    boolean isOK = (dAverage == 0);

    // here we are assuring that at least ONE face is drawn to
    // all matching vertices

    while (!isOK && factor < 10.0f) {
      distMax = dAverage * factor;
      for (int i = 0; i < ptCenter; i++)
        bs.set(i);
      for (int i = 0; i < ptCenter - 2; i++)
        for (int j = i + 1; j < ptCenter - 1; j++) {
          if (points[i].distance(points[j]) > distMax)
            continue;
          for (int k = j + 1; k < ptCenter; k++) {
            if (points[i].distance(points[k]) > distMax
                || points[j].distance(points[k]) > distMax)
              continue;
            bs.clear(i);
            bs.clear(j);
            bs.clear(k);
          }
        }
      isOK = true;
      for (int i = 0; i < ptCenter; i++)
        if (bs.get(i)) {
          isOK = false;
          factor *= 1.05f;
          if (Logger.debugging) {
            Logger.debug("Polyhedra distanceFactor for " + ptCenter
                + " atoms increased to " + factor + " in order to include "
                + ((Atom) otherAtoms[i]).getInfo());
          }
          break;
        }
    }

    /*  Start by defining a face to be when all three distances
     *  are < distanceFactor * (longest central) but if a vertex is missed,
     *  then expand the range. The collapsed trick is to introduce
     *  a "simple" atom near the center but not quite the center,
     *  so that our planes on either side of the facet don't overlap.
     *  We step out faceCenterOffset * normal from the center.
     * 
     *  Alan Hewat pointed out the issue of faces that CONTAIN the center --
     *  square planar, trigonal and square pyramids, see-saw. In these cases with no
     *  additional work, you get a brilliance effect when two faces are drawn over
     *  each other. The solution is to identify this sort of face and, if not collapsed,
     *  to cut them into smaller pieces and only draw them ONCE by producing a little
     *  catalog. This uses the Point3i().toString() method.
     * 
     *  For these special cases, then, we define a reference point just behind the plane
     */

    // produce face-centered catalog and facet-aligned catalog
    String faceCatalog = "";
    String facetCatalog = "";
    for (int i = 0; i < ptCenter - 2; i++)
      for (int j = i + 1; j < ptCenter - 1; j++)
        for (int k = j + 1; k < ptCenter; k++)
          if (isPlanar(points[i], points[j], points[k], points[ptCenter]))
            faceCatalog += faceId(i, j, k);
    for (int j = 0; j < ptCenter - 1; j++)
      for (int k = j + 1; k < ptCenter; k++) {
        if (isAligned(points[j], points[k], points[ptCenter]))
          facetCatalog += faceId(j, k, -1);
      }
    Point3f ptRef = new Point3f();
    // this next check for distance allows for bond AND distance constraints
    BitSet bsTemp = new BitSet();
    for (int i = 0; i < ptCenter - 2; i++)
      for (int j = i + 1; j < ptCenter - 1; j++) {
        if (points[i].distance(points[j]) > distMax)
          continue;
        for (int k = j + 1; k < ptCenter; k++) {
          if (points[i].distance(points[k]) > distMax
              || points[j].distance(points[k]) > distMax)
            continue;

          if (planeCount >= FACE_COUNT_MAX) {
            Logger.error("Polyhedron error: maximum face(" + FACE_COUNT_MAX
                + ") -- reduce RADIUS or DISTANCEFACTOR");
            return null;
          }
          if (nPoints >= MAX_VERTICES) {
            Logger.error("Polyhedron error: maximum vertex count("
                + MAX_VERTICES + ") -- reduce RADIUS");
            return null;
          }
          boolean isFlat = (faceCatalog.indexOf(faceId(i, j, k)) >= 0);
          // if center is on the face, then we need a different point to
          // define the normal
          //System.out.println("# polyhedra\n");
          boolean isWindingOK =
            (isFlat ?
            Measure.getNormalFromCenter(randomPoint, points[i], points[j],
                points[k], false, normal) :
            Measure.getNormalFromCenter(points[ptCenter], points[i],
                points[j], points[k], true, normal)
                );
          normal.scale(isCollapsed && !isFlat ? faceCenterOffset
              : 0.001f);
          int nRef = nPoints;
          ptRef.set(points[ptCenter]);
          if (isCollapsed && !isFlat) {
            points[nPoints] = new Point3f(points[ptCenter]);
            points[nPoints].add(normal);
            otherAtoms[nPoints] = points[nPoints];
          } else if (isFlat) {
            ptRef.sub(normal);
            nRef = ptCenter;
          }
          String facet;
          facet = faceId(i, j, -1);
          if (isCollapsed || isFlat && facetCatalog.indexOf(facet) < 0) {
            facetCatalog += facet;
            planesT[ipt++] = (byte) (isWindingOK ? i : j);
            planesT[ipt++] = (byte) (isWindingOK ? j : i);
            planesT[ipt++] = (byte) nRef;
            Measure.getNormalFromCenter(points[k], points[i], points[j],
                ptRef, false, normal);
            normixesT[planeCount++] = (isFlat ? Graphics3D
                .get2SidedNormix(normal, bsTemp) : Graphics3D.getNormix(normal, bsTemp));
          }
          facet = faceId(i, k, -1);
          if (isCollapsed || isFlat && facetCatalog.indexOf(facet) < 0) {
            facetCatalog += facet;
            planesT[ipt++] = (byte) (isWindingOK ? i : k);
            planesT[ipt++] = (byte) nRef;
            planesT[ipt++] = (byte) (isWindingOK ? k : i);
            Measure.getNormalFromCenter(points[j], points[i], ptRef,
                points[k], false, normal);
            normixesT[planeCount++] = (isFlat ? Graphics3D
                .get2SidedNormix(normal, bsTemp) : Graphics3D.getNormix(normal, bsTemp));
          }
          facet = faceId(j, k, -1);
          if (isCollapsed || isFlat && facetCatalog.indexOf(facet) < 0) {
            facetCatalog += facet;
            planesT[ipt++] = (byte) nRef;
            planesT[ipt++] = (byte) (isWindingOK ? j : k);
            planesT[ipt++] = (byte) (isWindingOK ? k : j);
            Measure.getNormalFromCenter(points[i], ptRef, points[j],
                points[k], false, normal);
            normixesT[planeCount++] = (isFlat ? Graphics3D
                .get2SidedNormix(normal, bsTemp) : Graphics3D.getNormix(normal, bsTemp));
          }
          if (!isFlat) {
            if (isCollapsed) {
              nPoints++;
            } else {
              // finally, the standard face:
              planesT[ipt++] = (byte) (isWindingOK ? i : j);
              planesT[ipt++] = (byte) (isWindingOK ? j : i);
              planesT[ipt++] = (byte) k;
              normixesT[planeCount++] = Graphics3D.getNormix(normal, bsTemp);
            }
          }
        }
      }
    //Logger.debug("planeCount="+planeCount + " nPoints="+nPoints);
    return new Polyhedron(centralAtom, ptCenter, nPoints, planeCount,
        otherAtoms, normixesT, planesT);
  }

  private String faceId(int i, int j, int k) {
    return (new Point3i(i, j, k)).toString();
  }

  private Vector3f align1 = new Vector3f();
  private Vector3f align2 = new Vector3f();

  private boolean isAligned(Point3f pt1, Point3f pt2, Point3f pt3) {
    align1.sub(pt1, pt3);
    align2.sub(pt2, pt3);
    float angle = align1.angle(align2);
    return (angle < 0.01f || angle > 3.13f);
  }

  private final Vector3f vAB = new Vector3f();
  private final Vector3f vAC = new Vector3f();

  private static float minDistanceForPlanarity = 0.1f;

  private boolean isPlanar(Point3f pt1, Point3f pt2, Point3f pt3, Point3f ptX) {
    /*
     * what is the quickest way to find out if four points are planar?
     * here we determine the plane through three and then the distance to that plane
     * of the fourth
     *
     */
    Vector3f norm = new Vector3f();
    float w = Measure.getNormalThroughPoints(pt1, pt2, pt3, norm, vAB, vAC);
    float d = Measure.distanceToPlane(norm, w, ptX);
    return (Math.abs(d) < minDistanceForPlanarity);
  }

  class Polyhedron {
    int modelIndex;
    final Atom centralAtom;
    final Point3f[] vertices;
    int ptCenter;
    boolean visible;
    final short[] normixes;
    byte[] planes;
    //int planeCount;
    int visibilityFlags = 0;
    boolean collapsed = false;
    float myFaceCenterOffset, myDistanceFactor;

    Polyhedron(Atom centralAtom, int ptCenter, int nPoints, int planeCount,
        Point3f[] otherAtoms, short[] normixes, byte[] planes) {
      this.collapsed = isCollapsed;
      this.centralAtom = centralAtom;
      modelIndex = centralAtom.getModelIndex();
      this.ptCenter = ptCenter;
      this.vertices = new Point3f[nPoints];
      this.visible = true;
      this.normixes = new short[planeCount];
      //this.planeCount = planeCount;
      this.planes = new byte[planeCount * 3];
      myFaceCenterOffset = faceCenterOffset;
      myDistanceFactor = distanceFactor;
      for (int i = nPoints; --i >= 0;)
        vertices[i] = otherAtoms[i];
      for (int i = planeCount; --i >= 0;)
        this.normixes[i] = normixes[i];
      for (int i = planeCount * 3; --i >= 0;)
        this.planes[i] = planes[i];
    }

    protected String getState(Hashtable temp) {
      BitSet bs = new BitSet();
      for (int i = 0; i < ptCenter; i++)
        bs.set(((Atom) vertices[i]).getIndex());
      return "  polyhedra ({" + centralAtom.getIndex() + "}) "
          + (myDistanceFactor == DEFAULT_DISTANCE_FACTOR ? ""
              : " distanceFactor " + myDistanceFactor)
          + (myFaceCenterOffset == DEFAULT_FACECENTEROFFSET ? ""
              : " faceCenterOffset " + myFaceCenterOffset) + " to "
          + Escape.escape(bs) + (collapsed ? " collapsed" : "") + ";"
          + (visible ? "" : "polyhedra off;") + "\n";
    }
  }

  public void setVisibilityFlags(BitSet bs) {
    /*
     * set all fixed objects visible; others based on model being displayed note
     * that this is NOT done with atoms and bonds, because they have mads. When
     * you say "frame 0" it is just turning on all the mads.
     */
    for (int i = polyhedronCount; --i >= 0;) {
      Polyhedron p = polyhedrons[i];
      p.visibilityFlags = (p.visible && bs.get(p.modelIndex)
          && !modelSet.isAtomHidden(p.centralAtom.getIndex()) ? myVisibilityFlag
          : 0);
    }
  }

  public String getShapeState() {
    Hashtable temp = new Hashtable();
    StringBuffer s = new StringBuffer();
    for (int i = 0; i < polyhedronCount; i++)
      s.append(polyhedrons[i].getState(temp));
    if (drawEdges == EDGES_FRONT)
      appendCmd(s, "polyhedra frontedges");
    else if (drawEdges == EDGES_ALL)
      appendCmd(s, "polyhedra edges");
    s.append(super.getShapeState());
    return s.toString();
  }
}
TOP

Related Classes of org.jmol.shapespecial.Polyhedra$Polyhedron

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.