All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jmol.jvxl.data.JvxlCoder Maven / Gradle / Ivy

There is a newer version: 14.31.10
Show newest version
/* $RCSfile$
 * $Author: hansonr $
 * $Date: 2007-03-30 11:40:16 -0500 (Fri, 30 Mar 2007) $
 * $Revision: 7273 $
 *
 * Copyright (C) 2007 Miguel, Bob, Jmol Development
 *
 * Contact: [email protected]
 *
 *  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 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.jvxl.data;


import javax.vecmath.Point3f;

import java.util.ArrayList;
import java.util.BitSet;
import java.util.List;
import java.util.Map;

import org.jmol.g3d.Graphics3D;
import org.jmol.util.BitSetUtil;
import org.jmol.util.Escape;
import org.jmol.util.Logger;
import org.jmol.util.Parser;
import org.jmol.util.TextFormat;
import org.jmol.util.XmlUtil;

public class JvxlCoder {

  //TODO -- need to escapeXml for text data
  
  final public static String JVXL_VERSION1 = "2.0";
  final public static String JVXL_VERSION_XML = "2.2";
  
  // 1.4 adds -nContours to indicate contourFromZero for MEP data mapped onto planes
  // 2.0 adds vertex/triangle compression when no grid is present 
  // Jmol 11.7.25 -- recoded so that we do not create voxelData[nx][ny][nz] and instead
  //                 simply create a BitSet of length nx * ny * nz. This saves memory hugely.
  // 2.1 adds JvxlXmlReader
  // 2.2 adds color density Jmol 12.0.15/12.1.13
  
  public static String jvxlGetFile(VolumeData volumeData, JvxlData jvxlData,
                                   String[] title) {
    // for the simple writer
    int[] counts = volumeData.getVoxelCounts();
    jvxlData.nPointsX = counts[0];
    jvxlData.nPointsY = counts[1];
    jvxlData.nPointsZ = counts[2];
    jvxlData.jvxlVolumeDataXml = volumeData.setVolumetricXml();
    return jvxlGetFile(jvxlData, null, title, null, true, 1, null, null);
  }

  public static String jvxlGetFile(JvxlData jvxlData, MeshData meshData,
                                   String[] title, String msg,
                                   boolean includeHeader, int nSurfaces,
                                   String state, String comment) {
    return jvxlGetFileXml(jvxlData, meshData, title, msg, includeHeader, nSurfaces, state, comment);
    
    // version1 decomissioned because of jvxlExcluded[] performing so well
    
    //    if (meshData != null || jvxlData == null || jvxlData.asXml 
    //      || jvxlData.vContours != null || jvxlData.contourValues != null
    //      || jvxlData.jvxlExcluded[0] != null || jvxlData.jvxlExcluded[1] != null )
    //  return jvxlGetFileXml(jvxlData, meshData, title, msg, includeHeader, nSurfaces, state, comment);
    //return jvxlGetFileVersion1(jvxlData, meshData, title, msg, includeHeader, nSurfaces, state, comment);
  }

  private static String jvxlGetFileXml(JvxlData jvxlData, MeshData meshData,
                                       String[] title, String msg,
                                       boolean includeHeader, int nSurfaces,
                                       String state, String comment) {
    StringBuffer data = new StringBuffer();
    if ("TRAILERONLY".equals(msg)) {
      XmlUtil.closeTag(data, "jvxlSurfaceSet");
      XmlUtil.closeTag(data, "jvxl");
      return data.toString();
    }
    
    boolean vertexDataOnly = (meshData != null);
    boolean isHeaderOnly = ("HEADERONLY".equals(msg));
    if (includeHeader) {
      XmlUtil.openDocument(data);
      XmlUtil.openTag(data, "jvxl", new String[] {
          "version", JVXL_VERSION_XML,
          "jmolVersion", jvxlData.version,
          "xmlns", "http://jmol.org/jvxl_schema",
          "xmlns:cml", "http://www.xml-cml.org/schema" });
      if (jvxlData.jvxlFileTitle != null)
        XmlUtil.appendCdata(data, "jvxlFileTitle", null, "\n" + jvxlData.jvxlFileTitle);
      if (jvxlData.moleculeXml != null)
        data.append(jvxlData.moleculeXml);
      String volumeDataXml = (vertexDataOnly ? null : jvxlData.jvxlVolumeDataXml);
      if (volumeDataXml == null)
        volumeDataXml = (new VolumeData()).setVolumetricXml();
      data.append(volumeDataXml);
      XmlUtil.openTag(data,"jvxlSurfaceSet", 
          new String[] { "count", "" + (nSurfaces > 0 ? nSurfaces : 1) });
      if (isHeaderOnly)
        return data.toString();
    }
    StringBuffer sb;
    String type = (vertexDataOnly ? "pmesh"
        : jvxlData.jvxlPlane == null ? "isosurface" : "plane");
    // TODO: contours mentioned here? when discrete?
    if (jvxlData.jvxlColorData != null && jvxlData.jvxlColorData.length() > 0)
      type = "mapped " + type;
    XmlUtil.openTag(data, "jvxlSurface", new String[] { "type", type });
    data.append(jvxlGetInfo(jvxlData, vertexDataOnly));
    jvxlAppendCommandState(data, comment, state);
    if (title != null || msg != null && msg.length() > 0) {
      sb = new StringBuffer();
      if (msg != null && msg.length() > 0)
        sb.append(msg).append("\n");
      if (title != null)
        for (int i = 0; i < title.length; i++)
          sb.append(title[i]).append('\n');
      XmlUtil.appendCdata(data, "jvxlSurfaceTitle", null, sb.toString());
    }
    sb = new StringBuffer();
    
    XmlUtil.openTag(sb, "jvxlSurfaceData", (vertexDataOnly || jvxlData.jvxlPlane == null ? null :
      new String[] { "plane", Escape.escape(jvxlData.jvxlPlane) }));
    if (vertexDataOnly) {
      appendXmlVertexOnlyData(sb, jvxlData, meshData, true);
    } else if (jvxlData.jvxlPlane == null) {
      if (jvxlData.jvxlEdgeData == null)
        return "";
      appendXmlEdgeData(sb, jvxlData);
      appendXmlColorData(sb, "jvxlColorData", jvxlData.jvxlColorData,
          jvxlData.isJvxlPrecisionColor, jvxlData.valueMappedToRed,
          jvxlData.valueMappedToBlue);
    } else {
      appendXmlColorData(sb, "jvxlColorData", jvxlData.jvxlColorData,
          jvxlData.isJvxlPrecisionColor, jvxlData.valueMappedToRed,
          jvxlData.valueMappedToBlue);
    }
    appendEncodedBitSetTag(sb, "jvxlInvalidatedVertexData", jvxlData.jvxlExcluded[1], -1, null);
    if (jvxlData.excludedVertexCount > 0) {
      appendEncodedBitSetTag(sb, "jvxlExcludedVertexData", jvxlData.jvxlExcluded[0], jvxlData.excludedVertexCount, null);
      appendEncodedBitSetTag(sb, "jvxlExcludedPlaneData", jvxlData.jvxlExcluded[2], -1, null);
    }
    appendEncodedBitSetTag(sb, "jvxlExcludedTriangleData", jvxlData.jvxlExcluded[3], jvxlData.excludedTriangleCount, null);
    XmlUtil.closeTag(sb, "jvxlSurfaceData");
    int len = sb.length();
    data.append(sb);
    if (jvxlData.vContours != null && jvxlData.vContours.length > 0) {
      jvxlEncodeContourData(jvxlData.vContours, data);
    }
    if (jvxlData.vertexColorMap != null) {
      XmlUtil.openTag(data, "jvxlVertexColorData");
      for (Map.Entry entry : jvxlData.vertexColorMap.entrySet())
        appendEncodedBitSetTag(data, "jvxlColorMap", entry.getValue(), -1, new Object[] { "color", entry.getKey() });
      jvxlData.vertexColorMap = null;
      XmlUtil.closeTag(data, "jvxlVertexColorData");
    }
    XmlUtil.closeTag(data, "jvxlSurface");
    if (includeHeader) {
      XmlUtil.closeTag(data, "jvxlSurfaceSet");
      XmlUtil.closeTag(data, "jvxl");
    }
    return jvxlSetCompressionRatio(data, jvxlData, len);
  }

  private static void appendEncodedBitSetTag(StringBuffer sb, String name, BitSet bs, int count, Object[] attribs) {
    if (count < 0)
      count = BitSetUtil.cardinalityOf(bs);
    if (count == 0)
      return;
    StringBuffer sb1 = new StringBuffer("\n ");
    jvxlEncodeBitSet(bs, -1, sb1);
    XmlUtil.appendTag(sb, name, new Object[] {
        attribs,
        "bsEncoding", "base90+35",
        "count", "" + count,
        "len", "" + bs.length() }, 
        jvxlCompressString(sb1.toString(), true));
  }

  private static String jvxlSetCompressionRatio(StringBuffer data,
                                                JvxlData jvxlData, int len) {
    String s = data.toString();
    int r = (int) (jvxlData.nBytes > 0 ? ((float) jvxlData.nBytes) / len
        : ((float) (jvxlData.nPointsX
          * jvxlData.nPointsY * jvxlData.nPointsZ * 13)) / len);
    return TextFormat.simpleReplace(s, "\"not calculated\"", (r > 0 ? "\"" + r +":1\"": "\"?\""));
  }

  private static void appendXmlEdgeData(StringBuffer sb, JvxlData jvxlData) {
    XmlUtil.appendTag(sb, "jvxlEdgeData", new String[] {
        "count", "" + (jvxlData.jvxlEdgeData.length() - 1),
        "encoding", "base90f1",
        "bsEncoding", "base90+35c",
        "isXLowToHigh", "" + jvxlData.isXLowToHigh,
        "data", jvxlCompressString(jvxlData.jvxlEdgeData, true) }, "\n" 
        + jvxlCompressString(jvxlData.jvxlSurfaceData, true));
  }

  private static void jvxlAppendCommandState(StringBuffer data, String cmd,
                                             String state) {
    if (cmd != null)
      XmlUtil.appendCdata(data, "jvxlIsosurfaceCommand", null,
          "\n" + (cmd.indexOf("#") < 0 ? cmd : cmd.substring(0, cmd.indexOf("#"))) + "\n");
    if (state != null) {
      if (state.indexOf("** XML ** ") >=0) {
        state = TextFormat.split(state, "** XML **")[1].trim(); 
        XmlUtil.appendTag(data, "jvxlIsosurfaceState",  "\n" + state + "\n");
      } else {
        XmlUtil.appendCdata(data, "jvxlIsosurfaceState", null, "\n" + state);
      }
    }
  }

  private static void appendXmlColorData(StringBuffer sb, String key, 
                                         String data,
                                         boolean isPrecisionColor,
                                         float value1,
                                         float value2) {
    int n;
    if (data == null || (n = data.length() - 1) < 0)
      return;
    if (isPrecisionColor)
      n /= 2;
    XmlUtil.appendTag(sb, key, new String[] {
        "count", "" + n, 
        "encoding", "base90f" + (isPrecisionColor ? "2" : "1"),
        "min", "" + value1,
        "max", "" + value2,
        "data", jvxlCompressString(data, true) }, null);
  }

  
  public static String jvxlGetInfo(JvxlData jvxlData) {
    return jvxlGetInfo(jvxlData, jvxlData.vertexDataOnly);
  }

  public static String jvxlGetInfo(JvxlData jvxlData, boolean vertexDataOnly) {
    if (jvxlData.jvxlSurfaceData == null)
      return "";
    List attribs = new ArrayList();
     
    int nSurfaceInts = jvxlData.nSurfaceInts;// jvxlData.jvxlSurfaceData.length();
    int bytesUncompressedEdgeData = (vertexDataOnly ? 0
        : jvxlData.jvxlEdgeData.length() - 1);
    int nColorData = (jvxlData.jvxlColorData == null ? -1 : (jvxlData.jvxlColorData.length() - 1));
    if (!vertexDataOnly) {
      // informational only:
      addAttrib(attribs, "\n  cutoff", "" + jvxlData.cutoff);
      addAttrib(attribs, "\n  isCutoffAbsolute", "" + jvxlData.isCutoffAbsolute);
      addAttrib(attribs, "\n  pointsPerAngstrom", "" + jvxlData.pointsPerAngstrom);
      int n = jvxlData.jvxlSurfaceData.length() 
          + bytesUncompressedEdgeData + nColorData + 1;
      if (n > 0)
        addAttrib(attribs, "\n  nBytesData", "" + n);

      //TODO: these should only be for information purposes, but are not:
      addAttrib(attribs, "\n  isXLowToHigh", "" + jvxlData.isXLowToHigh);
      if (jvxlData.jvxlPlane == null) {
        addAttrib(attribs, "\n  nSurfaceInts", "" + nSurfaceInts);
        addAttrib(attribs, "\n  nBytesUncompressedEdgeData", "" + bytesUncompressedEdgeData);
      }
      if (nColorData > 0)
        addAttrib(attribs, "\n  nBytesUncompressedColorData", "" + nColorData); // TODO: later?
    }
    jvxlData.excludedVertexCount = BitSetUtil.cardinalityOf(jvxlData.jvxlExcluded[0]);
    jvxlData.excludedTriangleCount = BitSetUtil.cardinalityOf(jvxlData.jvxlExcluded[3]);
    if (jvxlData.excludedVertexCount > 0)
      addAttrib(attribs, "\n  nExcludedVertexes", "" + jvxlData.excludedVertexCount);
    if (jvxlData.excludedTriangleCount > 0)
      addAttrib(attribs, "\n  nExcludedTriangles", "" + jvxlData.excludedTriangleCount);
    int n = BitSetUtil.cardinalityOf(jvxlData.jvxlExcluded[1]);
    if (n > 0)
      addAttrib(attribs, "\n  nInvalidatedVertexes", "" + n);
    if (jvxlData.slabInfo != null)
      addAttrib(attribs, "\n  slabInfo", jvxlData.slabInfo);
    //next is for information only -- will be superceded by "encoding" attribute of jvxlColorData
    if (jvxlData.isJvxlPrecisionColor)
      addAttrib(attribs, "\n  precisionColor", "true");
    if (jvxlData.colorDensity)
      addAttrib(attribs, "\n  colorDensity", "true");
    else if (jvxlData.diameter != 0)
      addAttrib(attribs, "\n  diameter", "" + jvxlData.diameter);
    if (!jvxlData.allowVolumeRender)
      addAttrib(attribs, "\n  allowVolumeRender", "false");
    if (jvxlData.jvxlPlane == null || vertexDataOnly) {
      if (jvxlData.isContoured) {
        addAttrib(attribs, "\n  contoured", "true"); 
        addAttrib(attribs, "\n  colorMapped", "true");
      } else if (jvxlData.isBicolorMap) {
        addAttrib(attribs, "\n  bicolorMap", "true");
        addAttrib(attribs, "\n  colorNegative", Graphics3D.getHexCode(jvxlData.minColorIndex));
        addAttrib(attribs, "\n  colorPositive", Graphics3D.getHexCode(jvxlData.maxColorIndex));
      } else if (nColorData > 0) {
        addAttrib(attribs, "\n  colorMapped", "true");
      }
      if (jvxlData.vContours != null && jvxlData.vContours.length > 0)
        addAttrib(attribs, "\n  nContourData", "" + jvxlData.vContours.length);
    } else {
      if (jvxlData.scale3d != 0)
        addAttrib(attribs, "\n  scale3d", "" + jvxlData.scale3d);
      if (nColorData > 0)
        addAttrib(attribs, "\n  colorMapped", "true");
      addAttrib(attribs, "\n  plane", Escape.escape(jvxlData.jvxlPlane));
    }
    if (jvxlData.color != null && jvxlData.color.indexOf("null") < 0)
      addAttrib(attribs, "\n  color", jvxlData.color);
    addAttrib(attribs, "\n  translucency", "" + jvxlData.translucency);
    if (jvxlData.meshColor != null)
      addAttrib(attribs, "\n  meshColor", jvxlData.meshColor);
    if (jvxlData.colorScheme != null)
      addAttrib(attribs, "\n  colorScheme", jvxlData.colorScheme);
    if (jvxlData.rendering != null)
      addAttrib(attribs, "\n  rendering", jvxlData.rendering);
    if (jvxlData.thisSet >= 0)
      addAttrib(attribs, "\n  set", "" + (jvxlData.thisSet + 1));
    if (jvxlData.slabValue != Integer.MIN_VALUE)
      addAttrib(attribs, "\n  slabValue", "" + jvxlData.slabValue);
    if (jvxlData.isSlabbable)
      addAttrib(attribs, "\n  slabbable", "true");
    if (jvxlData.nVertexColors > 0)
      addAttrib(attribs, "\n  nVertexColors", "" + jvxlData.nVertexColors);

    float min = (jvxlData.mappedDataMin == Float.MAX_VALUE ? 0f
        : jvxlData.mappedDataMin);
    float blue = (jvxlData.isColorReversed ? jvxlData.valueMappedToRed : jvxlData.valueMappedToBlue);
    float red = (jvxlData.isColorReversed ? jvxlData.valueMappedToBlue : jvxlData.valueMappedToRed);

    if (jvxlData.jvxlColorData != null && jvxlData.jvxlColorData.length() > 0 && !jvxlData.isBicolorMap) {
      addAttrib(attribs, "\n  dataMinimum", "" + min);
      addAttrib(attribs, "\n  dataMaximum", "" + jvxlData.mappedDataMax);
      addAttrib(attribs, "\n  valueMappedToRed", "" + red);
      addAttrib(attribs, "\n  valueMappedToBlue", "" + blue);
    }
    if (jvxlData.isContoured) {
      if (jvxlData.contourValues == null || jvxlData.contourColixes == null) {
        if (jvxlData.vContours == null)
          addAttrib(attribs, "\n  nContours", "" + Math.abs(jvxlData.nContours));
      } else {
        if (jvxlData.jvxlPlane != null)
          addAttrib(attribs, "\n  contoured", "true");
        addAttrib(attribs, "\n  nContours", "" + jvxlData.contourValues.length);
        addAttrib(attribs, "\n  contourValues", Escape.escapeArray(jvxlData.contourValuesUsed == null ? jvxlData.contourValues : jvxlData.contourValuesUsed));
        addAttrib(attribs, "\n  contourColors", jvxlData.contourColors);
      }
    }
    //TODO: confusing flag insideOut:
    if (jvxlData.insideOut)
      addAttrib(attribs, "\n  insideOut", "true");
    
    // rest is information only:
    if (jvxlData.vertexDataOnly)
      addAttrib(attribs, "\n  note", "vertex/face data only");
    else if (jvxlData.isXLowToHigh)
      addAttrib(attribs, "\n  note", "progressive JVXL+ -- X values read from low(0) to high("
              + (jvxlData.nPointsX - 1) + ")");
    addAttrib(attribs, "\n  xyzMin", Escape.escape(jvxlData.boundingBox[0]));
    addAttrib(attribs, "\n  xyzMax", Escape.escape(jvxlData.boundingBox[1]));
    addAttrib(attribs, "\n  approximateCompressionRatio", "not calculated");
    addAttrib(attribs, "\n  jmolVersion", jvxlData.version);
    
    StringBuffer info = new StringBuffer();
    XmlUtil.openTag(info, "jvxlSurfaceInfo", attribs.toArray());
    XmlUtil.closeTag(info, "jvxlSurfaceInfo");
    return info.toString();
  }
  
  private static void addAttrib(List attribs, String name, String value) {
    attribs.add(new String[] { name, value });
  }

  public static final int CONTOUR_NPOLYGONS = 0;
  public static final int CONTOUR_BITSET = 1;
  public static final int CONTOUR_VALUE = 2;
  public static final int CONTOUR_COLIX = 3;
  public static final int CONTOUR_COLOR = 4;
  public static final int CONTOUR_FDATA = 5;
  public static final int CONTOUR_POINTS = 6; // must be last

  /**
   * contour data are appended to a string buffer in the form of a 
   * 
   *   triangle bitset data
   *   triangle bitset data
   *   triangle bitset data
   *   ...
   * 
   * 
   * One presumes an ordered set of triangles.
   * The contour intersects these triangles along two edges or at two vertices. 
   * (see IsosurfaceMesh for details)
   * Each contour is a Vector containing:
   *   0 Integer number of polygons (length of BitSet) 
   *   1 BitSet of critical triangles
   *   2 Float value
   *   3 int[] [colorArgb]
   *   4 StringBuffer containing encoded data for each segment:
   *     char type ('3', '6', '5') indicating which two edges
   *       of the triangle are connected: 
   *         '3' 0x011 AB-BC
   *         '5' 0x101 AB-CA
   *         '6' 0x110 BC-CA
   *     char fraction along first edge (jvxlFractionToCharacter)
   *     char fraction along second edge (jvxlFractionToCharacter)
   *   5- stream of pairs of points for rendering (created
   * 
   * 
   * @param contours
   * @param sb
   */
  private static void jvxlEncodeContourData(List[] contours, StringBuffer sb) {
    XmlUtil.openTag(sb, "jvxlContourData", new String[] { "count", "" + contours.length });
    for (int i = 0; i < contours.length; i++) {
      if (contours[i].size() < CONTOUR_POINTS) {
        continue;
      }
      int nPolygons = ((Integer) contours[i].get(CONTOUR_NPOLYGONS)).intValue();
      StringBuffer sb1 = new StringBuffer("\n");
      BitSet bs = (BitSet) contours[i].get(CONTOUR_BITSET);
      jvxlEncodeBitSet(bs, nPolygons, sb1);
      XmlUtil.appendTag(sb, "jvxlContour", new String[] {
          "index", "" + i,
          "value", "" + contours[i].get(CONTOUR_VALUE),
          "color", Escape.escapeColor(((int[]) contours[i]
              .get(CONTOUR_COLOR))[0]),
          "count", "" + bs.length(),
          "encoding", "base90iff1",
          "bsEncoding", "base90+35c",
          "data", jvxlCompressString(contours[i].get(CONTOUR_FDATA).toString(), true) }, 
          jvxlCompressString(sb1.toString(), true));
    }
    XmlUtil.closeTag(sb, "jvxlContourData");
  }

  /**
   * Interpret fractional data in terms of actual vertex positions and
   * create the elements of a Vector in Vector[] vContours starting at 
   * the CONTOUR_POINTS position.
   *  
   * @param v
   * @param polygonIndexes
   * @param vertices
   */
  public static void set3dContourVector(List v, int[][] polygonIndexes, Point3f[] vertices) {
    // we must add points only after the MarchingCubes process has completed.
    if (v.size() < CONTOUR_POINTS)
      return;
    StringBuffer fData = (StringBuffer) v.get(CONTOUR_FDATA);
    BitSet bs = (BitSet) v.get(CONTOUR_BITSET);
    //int nPolygons = ((Integer)v.get(CONTOUR_NPOLYGONS)).intValue();
    int pt = 0;
    int nBuf = fData.length();
    int type = 0;
    char c1 = ' ';
    char c2 = ' ';
    for (int i = bs.nextSetBit(0); i >= 0; i = bs.nextSetBit(i + 1)) {
      int[] vertexIndexes = polygonIndexes[i];
      while (pt < nBuf && !Character.isDigit(c1 = fData.charAt(pt++))) {
        // skip non-digit data
      }
      type = c1 - 48;
      while (pt < nBuf && Character.isWhitespace(c1 = fData.charAt(pt++))) {
        // skip whitespace
      }
      while (pt < nBuf && Character.isWhitespace(c2 = fData.charAt(pt++))) {
        // skip whitespace
      }
      float f1 = jvxlFractionFromCharacter(c1, defaultEdgeFractionBase, defaultEdgeFractionRange, 0);
      float f2 = jvxlFractionFromCharacter(c2, defaultEdgeFractionBase, defaultEdgeFractionRange, 0);
      int i1, i2, i3, i4;
      /*
       *     char type ('3', '6', '5') indicating which two edges
       *       of the triangle are connected: 
       *         '3' 0x011 AB-BC
       *         '5' 0x101 AB-CA
       *         '6' 0x110 BC-CA
       */
      if ((type & 1) == 0) { //BC-CA
        i1 = vertexIndexes[1];
        i2 = i3 = vertexIndexes[2];
        i4 = vertexIndexes[0];
      } else { //AB-BC or //AB-CA
        i1 = vertexIndexes[0];
        i2 = vertexIndexes[1];
        if ((type & 2) != 0) {
          i3 = i2;
          i4 = vertexIndexes[2];
        } else {
          i3 = vertexIndexes[2];
          i4 = i1;          
        }
      }
      v.add(getContourPoint(vertices, i1, i2, f1));
      v.add(getContourPoint(vertices, i3, i4, f2));
    }
  }

  private static Point3f getContourPoint(Point3f[] vertices, int i, int j, float f) {
    Point3f pt = new Point3f();
    pt.set(vertices[j]);
    pt.sub(vertices[i]);
    pt.scale(f);
    pt.add(vertices[i]);
    return pt;
  }

  /**
   * appends an integer (3, 5, or 6) representing two sides of a triangle ABC -- 
   * AB/BC(3), AB/CA(5), or BC/CA(6) -- along with two fractions along the edges 
   * for the intersection point base-90-encoded. This version is single precision.
   * 
   * type     f1     f2
   *  3       AB     BC 
   *  5       AB     CA
   *  6       BC     CA
   * 
   * @param type 
   * @param f1 -- character-encoded fraction
   * @param f2 -- character-encoded fraction
   * @param fData
   */
  public static void appendContourTriangleIntersection(int type, float f1, float f2, StringBuffer fData) {
    fData.append(type);
    fData.append(jvxlFractionAsCharacter(f1));
    fData.append(jvxlFractionAsCharacter(f2));    
  }
  
  /**
   * 
   * @param jvxlData
   * @param vertexValues
   */
  public static void jvxlCreateColorData(JvxlData jvxlData, float[] vertexValues) {
    if (vertexValues == null) {
      jvxlData.jvxlColorData = "";
      return;
    }
    boolean writePrecisionColor = jvxlData.isJvxlPrecisionColor;
    boolean doTruncate = jvxlData.isTruncated;
    int colorFractionBase = jvxlData.colorFractionBase;
    int colorFractionRange = jvxlData.colorFractionRange;
    float valueBlue = jvxlData.valueMappedToBlue;
    float valueRed = jvxlData.valueMappedToRed;
    int vertexCount = (jvxlData.saveVertexCount > 0 ? jvxlData.saveVertexCount
        : jvxlData.vertexCount);
    if(vertexCount > vertexValues.length)
      System.out.println("JVXLCODER ERROR");
    float min = jvxlData.mappedDataMin;
    float max = jvxlData.mappedDataMax;
    StringBuffer list1 = new StringBuffer();
    StringBuffer list2 = new StringBuffer();
    if (vertexValues.length < vertexCount)
      System.out.println("JVXLCOLOR OHOHO");  
    for (int i = 0; i < vertexCount; i++) {
      float value = vertexValues[i];
      if (Float.isNaN(value))
        value = min;
      if (doTruncate)
        value = (value > 0 ? 0.999f : -0.999f);
      if (writePrecisionColor)
        jvxlAppendCharacter2(value, min, max, colorFractionBase,
            colorFractionRange, list1, list2);
      else
        list1.append(jvxlValueAsCharacter(value, valueRed, valueBlue,
            colorFractionBase, colorFractionRange));
    }
    jvxlData.jvxlColorData = list1.append(list2).append('\n').toString();
  }

  /* ******************************************************************
   * 
   * JVXL 2.0 encoding of vertices, triangles, and vertex values:
   * 
   * 
   *   
   *   
   *   
   *   
   *   
   *   
   * 
   * 
   * 
   **********************************************************/

  private static void appendXmlVertexOnlyData(StringBuffer sb, 
                                        JvxlData jvxlData, MeshData meshData, boolean escapeXml) {
    int[] vertexIdNew = new int[meshData.vertexCount];
    if (appendXmlTriangleData(sb, meshData.polygonIndexes,
        meshData.polygonCount, meshData.bsSlabDisplay,
        vertexIdNew, escapeXml))
      appendXmlVertexData(sb, jvxlData, vertexIdNew,
          meshData.vertices, meshData.vertexValues, meshData.vertexCount,
          meshData.polygonColorData, meshData.polygonCount, 
          meshData.bsSlabDisplay, 
          jvxlData.jvxlColorData.length() > 0, escapeXml);
  }

  /**
   * encode triangle data -- [ia ib ic]  [ia ib ic]  [ia ib ic] ...
   * algorithm written by Bob Hanson, 11/2008. The principle is that
   * not all vertices may be represented -- we only need the 
   * used vertices here. Capitalizing on the fact that triangle sets
   * tend to have common edges and similar numbers for sequential triangles.
   * 
   * a) Renumbering vertices as they appear in the triangle set
   * 
   *    [2456 2457 2458] [2456 2459 2458]
   *    
   *   becomes
   *    
   *    [   1    2    3] [   1    4    3]
   * 
   * b) This allows efficient encoding of differences, not absolute numbers.
   * 
   *        0    1    2     -2    3   -1
   *        
   * c) Which can then be represented often using a single ASCII character. 
   *    I chose \ to be 0, and replace that with !.
   *    
   *    ASCII:
   *    -30       -20       -10         0       +10       +20       +30      
   *    <=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|
   *    
   *    So the above sequence would simply be:
   *    
   *      !]^Z_[
   *
   *    When the range falls outside of +/-32, we simply use a number.
   *    When a positive number follows another number, we add a "+" to it.
   *    
   *      !]^Z_[-33+250]230-210]]
   *      
   *    Preliminary trials indicated that on average a triangle
   *    can be encoded in about 7 bytes, or roughly half the 12 bytes
   *    necessary for standard binary encoding of integers. The advantage
   *    here is that we have an ASCII-readable file and no little-/big-endian issue.
   * 
   * @param sb
   * @param triangles
   * @param nData
   * @param bsSlabDisplay 
   * @param vertexIdNew
   * @param escapeXml 
   * @return (triangles are present)
   */
  private static boolean appendXmlTriangleData(StringBuffer sb, int[][] triangles, int nData,
                                              BitSet bsSlabDisplay, 
                                              int[] vertexIdNew, boolean escapeXml) {
    StringBuffer list1 = new StringBuffer();
    StringBuffer list2 = new StringBuffer();
    int ilast = 1;
    int p = 0;
    int inew = 0;
    boolean addPlus = false;
    int nTri = 0;
    
    // note that the slabbing present becomes irreversible if there is no ghosting.
    boolean removeSlabbed = (bsSlabDisplay != null);
    
    for (int i = 0; i < nData;) {
      if (triangles[i] == null || (removeSlabbed && !bsSlabDisplay.get(i))) {
        i++;
        continue;
      }
      int idata = triangles[i][p];
      if (vertexIdNew[idata] > 0) {
        idata = vertexIdNew[idata];
      } else {
        idata = vertexIdNew[idata] = ++inew;
      }
      int diff = idata - ilast;
      ilast = idata;
      if (diff == 0) {
        list1.append('!');
        addPlus = false;
      } else if (diff > 32) {
        if (addPlus)
          list1.append('+');
        list1.append(diff);
        addPlus = true;
      } else if (diff < -32) {
        list1.append(diff);
        addPlus = true;
      } else {
        list1.append((char) ('\\' + diff));
        addPlus = false;
      }
      if (++p % 3 == 0) {
        list2.append(triangles[i][3]);
        p = 0;
        i++;
        nTri++;
      }
    }
    if (list1.length() == 0)
      return true;
    XmlUtil.appendTag(sb, "jvxlTriangleData", new String[] {
        "count", "" + nTri,
        "encoding", "jvxltdiff",
        "data" , jvxlCompressString(list1.toString(), escapeXml), 
        }, null);
    XmlUtil.appendTag(sb, "jvxlTriangleEdgeData", new String[] { // Jmol 12.1.50
        "count", "" + nTri,
        "encoding", "jvxlsc",
        "data" , jvxlCompressString(list2.toString(), escapeXml) }, null);
    return true;
  }

  /**
   * encode the vertex data. This must be done AFTER encoding the triangles,
   * because the triangles redefine the order of vertices.
   * 
   * Bob Hanson 11/2008
   * 
   * If another program has created the triangles, we probably do not know the
   * grid that was used for Marching Cubes, or quite possibly no grid was used.
   * In that case, we just save the vertex/triangle/value data in a compact
   * form.
   * 
   * For the we use an extension of the way edge points are encoded. We simply
   * identify the minimum and maximum x, y, and z coordinates and then express
   * the point as a fraction along each of those directions. Thus, the x, y, and
   * z coordinate are within the interval [0,1].
   * 
   * We opt for the two-byte double-precision JVXL character compression. This
   * allows a 1 part in 8100 resolution, which is plenty for these purposes.
   * 
   * The tag will indicate the minimum and maximum values:
   * 
   *  
   * 
   * The resultant string is really two strings of length nData where the first
   * string lists the "high" part of the positions, and the second string lists
   * the "low" part of the positions.
   * 
   * @param sb
   * @param jvxlData
   * @param vertexIdNew
   * @param vertices
   * @param vertexValues
   * @param vertexCount
   * @param polygonColorData
   * @param polygonCount
   * @param bsSlabDisplay
   * @param addColorData
   * @param escapeXml
   */
  private static void appendXmlVertexData(StringBuffer sb, JvxlData jvxlData,
                                          int[] vertexIdNew,
                                          Point3f[] vertices,
                                          float[] vertexValues,
                                          int vertexCount,
                                          String polygonColorData,
                                          int polygonCount,
                                          BitSet bsSlabDisplay, 
                                          boolean addColorData, boolean escapeXml) {
    int colorFractionBase = jvxlData.colorFractionBase;
    int colorFractionRange = jvxlData.colorFractionRange;
    Point3f p;
    Point3f min = jvxlData.boundingBox[0];
    Point3f max = jvxlData.boundingBox[1];
    StringBuffer list1 = new StringBuffer();
    StringBuffer list2 = new StringBuffer();
    int[] vertexIdOld = null;
    boolean removeSlabbed = (bsSlabDisplay != null);
    if (polygonCount > 0) {
      if (removeSlabbed)
        polygonCount = bsSlabDisplay.cardinality();
      removeSlabbed = false;
      vertexIdOld = new int[vertexCount];
      for (int i = 0; i < vertexCount; i++)
        if (vertexIdNew[i] > 0) // not all vertices may be in triangle -- that's OK
          vertexIdOld[vertexIdNew[i] - 1] = i;
    }
    int n = 0;
    for (int i = 0; i < vertexCount; i++)
      if (!removeSlabbed || bsSlabDisplay.get(i)) {
        n++;
        p = vertices[(polygonCount == 0 ? i : vertexIdOld[i])];
        jvxlAppendCharacter2(p.x, min.x, max.x, colorFractionBase,
            colorFractionRange, list1, list2);
        jvxlAppendCharacter2(p.y, min.y, max.y, colorFractionBase,
            colorFractionRange, list1, list2);
        jvxlAppendCharacter2(p.z, min.z, max.z, colorFractionBase,
            colorFractionRange, list1, list2);
      }
    list1.append(list2);
    XmlUtil.appendTag(sb, "jvxlVertexData",
        new String[] { 
            "count", "" + n, 
            "min", Escape.escape(min), 
            "max", Escape.escape(max), 
            "encoding", "base90xyz2", 
            "data", jvxlCompressString(list1.toString(), escapeXml),
            }, null);
    if (polygonColorData != null)
      XmlUtil.appendTag(sb, "jvxlPolygonColorData", new String[] { "encoding",
          "jvxlnc", "count", "" + polygonCount }, "\n" + polygonColorData);
    if (!addColorData)
      return;

    // now add the color data, again as a double-precision value.

    list1 = new StringBuffer();
    list2 = new StringBuffer();
    for (int i = 0; i < vertexCount; i++) {
      float value = vertexValues[polygonCount == 0 ? i : vertexIdOld[i]];
      jvxlAppendCharacter2(value, jvxlData.mappedDataMin,
          jvxlData.mappedDataMax, colorFractionBase, colorFractionRange, list1,
          list2);
    }
    appendXmlColorData(sb, "jvxlColorData", list1.append(list2).append("\n")
        .toString(), true, jvxlData.valueMappedToRed,
        jvxlData.valueMappedToBlue);
  }

  ////////// character - fraction encoding and decoding
  
  // NEVER change the numbers for these next defaults
  
  final public static int defaultEdgeFractionBase = 35; //#$%.......
  final public static int defaultEdgeFractionRange = 90;
  final public static int defaultColorFractionBase = 35;
  final public static int defaultColorFractionRange = 90;

  /* character-encoding of factions in base 90:
   * 
   * characters ASC(35) - ASC(124) are used for this encoding with the
   * exception of ASC(92)'\\', which is encoded as ASC(33)'!'.
   * ASC(125)'}' is reserved for "NaN".
   * Double-quote is not in this range, but '<' and '>' are, so this
   * is only XML-safe when quoted as an attribute. 
   * 
   */
  public static char jvxlFractionAsCharacter(float fraction) {
    return jvxlFractionAsCharacter(fraction, defaultEdgeFractionBase, defaultEdgeFractionRange);  
  }
  
  public static char jvxlFractionAsCharacter(float fraction, int base, int range) {
    if (fraction > 0.9999f)
      fraction = 0.9999f;
    else if (Float.isNaN(fraction))
      fraction = 1.0001f;
    int ich = (int) (fraction * range + base);
    if (ich < base)
      return (char) base;
    if (ich == 92)
      return 33; // \ --> !
    //if (logCompression)
    //Logger.info("fac: " + fraction + " --> " + ich + " " + (char) ich);
    return (char) ich;
  }

  private static void jvxlAppendCharacter2(float value, float min, float max,
                                           int base, int range,
                                           StringBuffer list1,
                                           StringBuffer list2) {
    float fraction = (min == max ? value : (value - min) / (max - min));
    char ch1 = jvxlFractionAsCharacter(fraction, base, range);
    list1.append(ch1);
    fraction -= jvxlFractionFromCharacter(ch1, base, range, 0);
    list2.append(jvxlFractionAsCharacter(fraction * range, base, range));
  }

  public static float jvxlFractionFromCharacter(int ich, int base, int range,
                                                float fracOffset) {
    if (ich == base + range)
      return Float.NaN;
    if (ich < base)
      ich = 92; // ! --> \
    float fraction = (ich - base + fracOffset) / range;
    if (fraction < 0f)
      return 0f;
    if (fraction > 1f)
      return 0.999999f;
    // System.out.println("ffc: " + fraction + " <-- " + ich + " " + (char)
    // ich);
    return fraction;
  }

  public static float jvxlFractionFromCharacter2(int ich1, int ich2, int base,
                                          int range) {
    float fraction = jvxlFractionFromCharacter(ich1, base, range, 0);
    float remains = jvxlFractionFromCharacter(ich2, base, range, 0.5f);
    return fraction + remains / range;
  }

  public static char jvxlValueAsCharacter(float value, float min, float max, int base,
                                   int range) {
    float fraction = (min == max ? value : (value - min) / (max - min));
    return jvxlFractionAsCharacter(fraction, base, range);
  }

  protected static float jvxlValueFromCharacter2(int ich, int ich2, float min,
                                                 float max, int base, int range) {
    float fraction = jvxlFractionFromCharacter2(ich, ich2, base, range);
    return (max == min ? fraction : min + fraction * (max - min));
  }

  // /// differential bitset encoding and decoding (Bob Hanson [email protected] for Jmol)

  public static int jvxlEncodeBitSet0(BitSet bs, int nPoints, StringBuffer sb) {
    // nunset nset nunset ...
    // for repeated numbers:
    // 3 3 3 3 3 3 3 becomes 3 -6
    int dataCount = 0;
    int prevCount = -1;
    int nPrev = 0;
    if (nPoints < 0)
      nPoints = bs.length();
    int n = 0;
    boolean isset = false;
    int lastPoint = nPoints - 1;
    
    for (int i = 0; i < nPoints; ++i) {
      if (isset == bs.get(i)) {
        dataCount++;
      } else {
        if (dataCount == prevCount && i != lastPoint) {
          nPrev++;
        } else {
          if (nPrev > 0) {
            sb.append(' ').append(-nPrev);
            nPrev = 0;
            n++;
          }
          sb.append(' ').append(dataCount);
          n++;
          prevCount = dataCount;
        }
        dataCount = 1;
        isset = !isset;
      }
    }
    sb.append(' ').append(dataCount).append('\n');
    return n;
  }
  
  public static String jvxlEncodeBitSet(BitSet bs) {
    StringBuffer sb = new StringBuffer();
    jvxlEncodeBitSet(bs, -1, sb);
    return sb.toString();
  }
  
  public static int jvxlEncodeBitSet(BitSet bs, int nPoints, StringBuffer sb) {
    //System.out.println("jvxlcoder " + Escape.escape(bs));
    int dataCount = 0;
    int n = 0;
    boolean isset = false;
    if (nPoints < 0)
      nPoints = bs.length();
    if (nPoints == 0)
      return 0;
    sb.append("-");
    for (int i = 0; i < nPoints; ++i) {
      if (isset == bs.get(i)) {
        dataCount++;
      } else {
         jvxlAppendEncodedNumber(sb, dataCount, defaultEdgeFractionBase, defaultEdgeFractionRange);
        n++;
        dataCount = 1;
        isset = !isset;
      }
    }
    jvxlAppendEncodedNumber(sb, dataCount, defaultEdgeFractionBase, defaultEdgeFractionRange);
    sb.append('\n');
    return n;
  }

  public static void jvxlAppendEncodedNumber(StringBuffer sb, int n, int base, int range) {
    boolean isInRange = (n < range);
    if (n == 0)
      sb.append((char) base);
    else if (!isInRange)
      sb.append((char)(base + range));
    while (n > 0) {
      int n1 = n / range;
      int x = base + n - n1 * range;
      if (x == 92)
        x = 33;  // \ --> !
      sb.append((char) x);
      n = n1;
    }
    if (!isInRange)
      sb.append(" ");
  }

  public static BitSet jvxlDecodeBitSet(String data, int base, int range) {
    BitSet bs = new BitSet();
    int dataCount = 0;
    int ptr = 0;
    boolean isset = false;
    int[] next = new int[1];
    while ((dataCount = jvxlParseEncodedInt(data, base, range, next)) != Integer.MIN_VALUE) {
      if (isset)
        bs.set(ptr, ptr + dataCount);
      ptr += dataCount;
      isset = !isset;
    }
    return bs;
  }

  public static int jvxlParseEncodedInt(String str, int offset, int base, int[] next) {
    boolean digitSeen = false;
    int value = 0;
    int ich = next[0];
    int ichMax = str.length();
    if (ich < 0)
      return Integer.MIN_VALUE;
    while (ich < ichMax && Character.isWhitespace(str.charAt(ich)))
      ++ich;
    if (ich >= ichMax)
      return Integer.MIN_VALUE;
    int factor = 1;
    boolean isLong = (str.charAt(ich) == (offset + base));
    if (isLong)
      ich++;
    while (ich < ichMax && !Character.isWhitespace(str.charAt(ich))) {
      int i = str.charAt(ich);
      if (i < offset)
        i = 92;   // ! --> \ 
      value += (i - offset) * factor;
      digitSeen = true;
      ++ich;
      if (!isLong)
        break;
      factor *= base;
    }
    if (!digitSeen)
      value = Integer.MIN_VALUE;
    next[0] = ich;
    return value;
  }

  public static BitSet jvxlDecodeBitSet(String data) {
    if (data.startsWith("-"))
      return jvxlDecodeBitSet(jvxlUncompressString(data.substring(1)), defaultEdgeFractionBase, defaultEdgeFractionRange);
    // nunset nset nunset ...
    BitSet bs = new BitSet();
    int dataCount = 0;
    int lastCount = 0;
    int nPrev = 0;
    int ptr = 0;
    boolean isset = false;
    int[] next = new int[1];
    while (true) {
      dataCount = (nPrev++ < 0 ? dataCount : Parser.parseInt(data, next));
      if (dataCount == Integer.MIN_VALUE) 
        break;
      if (dataCount < 0) {
        nPrev = dataCount;
        dataCount = lastCount;
        continue;
      }
      if (isset)
        bs.set(ptr, ptr + dataCount);
      ptr += dataCount;
      lastCount = dataCount;
      isset = !isset;
    }
    return bs;
  }
  
  /////// string data compression/decompression
  
  public static String jvxlCompressString(String data, boolean escapeXml) {
    
    
    /* just a simple compression, but allows 2000-6000:1 CUBE:JVXL for planes!
     * 
     *   "X~nnn " means "nnn copies of character X" 
     *   
     *   ########## becomes "#~10 " 
     *   
     *   ~ is not encoded, as it is ASC(126), outside the range of 33--125.
     *   
     *   for escaping XML, we also do:
     *
     *   < becomes "~;0 "
     *   & becomes "~%0 "
     *   
     *   and repeats of those become:
     *   
     *   "~;nnn "
     *   "~%nnn "
     *
     */
    if (data.indexOf("~") >= 0)
      return data;
    StringBuffer dataOut = new StringBuffer();
    char chLast = '\0';
    boolean escaped = false;
    boolean lastEscaped = false;
    int nLast = 0;
    int n = data.length();
    for (int i = 0; i <= n; i++) {
      char ch = (i == n ? '\0' : data.charAt(i));
      switch (ch) {
      case '\n':
      case '\r':
        continue;
      case '&':
      case '<':
        escaped = escapeXml;
        break;
      default:
        escaped = false;
      }
      if (ch == chLast) {
        ++nLast;
        ch = '\0';
      } else if (nLast > 0 || lastEscaped) {
        if (nLast < 4 && !lastEscaped || chLast == ' '
            || chLast == '\t') {
          while (--nLast >= 0)
            dataOut.append(chLast);
        } else {
          if (lastEscaped)
            lastEscaped = false;
          else
            dataOut.append('~');
          dataOut.append(nLast);
          dataOut.append(' ');
        }
        nLast = 0;
      }
      if (ch != '\0') {
        if (escaped) {
          lastEscaped = true;
          escaped = false;
          dataOut.append('~');
          chLast = ch;
          --ch;
        } else {
          chLast = ch;          
        }
        dataOut.append(ch);
      }
    }
    
    return dataOut.toString();
  }

  public static String jvxlUncompressString(String data) {
    if (data.indexOf("~") < 0)
      return data;
    StringBuffer dataOut = new StringBuffer();
    char chLast = '\0';
    int[] next = new int[1];
    for (int i = 0; i < data.length(); i++) {
      char ch = data.charAt(i);
      if (ch == '~') {
        next[0] = ++i;
        switch (ch = data.charAt(i)) {
        case ';':
        case '%':
          next[0]++;
          dataOut.append(chLast = ++ch);
          //$FALL-THROUGH$
        case '1':
        case '2':
        case '3':
        case '4':
        case '5':
        case '6':
        case '7':
        case '8':
        case '9':
          int nChar = Parser.parseInt(data, next);
          for (int c = 0; c < nChar; c++)
            dataOut.append(chLast);
          i = next[0];
          continue;
        case '~':
          --i;
          break;
        default:
          Logger.error("Error uncompressing string " + data.substring(0, i) + "?");
        }
      }
      dataOut.append(ch);
      chLast = ch;
    }
    return dataOut.toString();
  }

  // VERSION 1 methods -- deprecated but still available through Jmol 11.9.18
  
  public static void jvxlCreateHeaderWithoutTitleOrAtoms(VolumeData v, StringBuffer bs) {
    jvxlCreateHeader(v, bs);
  }

  /**
   * Creates a two-line header for the XJVXL file. It is no longer necessary
   * to create the atom set or generate the vectors here. Please leave the 
   * commented code for posterity. 
   * 
   * @param v 
   * @param sb 
   */
  public static void jvxlCreateHeader(VolumeData v, StringBuffer sb) {
    // if the StringBuffer comes in non-empty, it should have two lines
    // that do not start with # already present.
    v.setVolumetricXml();
    if (sb.length() == 0)
      sb.append("Line 1\nLine 2\n");
    /* no longer necessary
    sb.append(nAtoms == Integer.MIN_VALUE ? "+2" 
        : nAtoms == Integer.MAX_VALUE ? "-2" : "" + (-nAtoms))
      .append(' ')
      .append(v.volumetricOrigin.x).append(' ')
      .append(v.volumetricOrigin.y).append(' ')
      .append(v.volumetricOrigin.z).append(" ANGSTROMS\n");
    for (int i = 0; i < 3; i++)
      sb.append(v.voxelCounts[i]).append(' ')
        .append(v.volumetricVectors[i].x).append(' ')
        .append(v.volumetricVectors[i].y).append(' ')
        .append(v.volumetricVectors[i].z).append('\n');
    if (nAtoms != Integer.MAX_VALUE && nAtoms != Integer.MIN_VALUE) {
      nAtoms = Math.abs(nAtoms);
      for (int i = 0, n = 0; i < nAtoms; i++)
        sb.append((n = Math.abs(atomNo[i])) + " " + n + ".0 "
            + atomXyz[i].x + " " + atomXyz[i].y + " " + atomXyz[i].z + "\n");
      return;
    }
    Point3f pt = new Point3f(v.volumetricOrigin);
    sb.append("1 1.0 ").append(pt.x).append(' ').append(pt.y).append(' ')
        .append(pt.z).append(" //BOGUS H ATOM ADDED FOR JVXL FORMAT\n");
    for (int i = 0; i < 3; i++)
      pt.scaleAdd(v.voxelCounts[i] - 1, v.volumetricVectors[i], pt);
    sb.append("2 2.0 ").append(pt.x).append(' ').append(pt.y).append(' ')
        .append(pt.z).append(" //BOGUS He ATOM ADDED FOR JVXL FORMAT\n");
    */
  }

  /*
  private static String jvxlGetFileVersion1(JvxlData jvxlData,
                                            MeshData meshData, String[] title,
                                            String msg, boolean includeHeader,
                                            int nSurfaces, String state,
                                            String comment) {
    // pre-XML
    if ("TRAILERONLY".equals(msg))
      return "";
    StringBuffer data = new StringBuffer();
    if (includeHeader) {
      String s = jvxlData.jvxlFileHeader
          + (nSurfaces > 0 ? -nSurfaces : -1) +" " + jvxlData.edgeFractionBase + " "
          + jvxlData.edgeFractionRange + " " + jvxlData.colorFractionBase + " "
          + jvxlData.colorFractionRange + " Jmol voxel format version " +  JVXL_VERSION1 + "\n";
      if (s.indexOf("#JVXL") != 0)
        data.append("#JVXL").append(jvxlData.isXLowToHigh ? "+" : "").append(
            " VERSION ").append(JVXL_VERSION1).append("\n");
      data.append(s);
    }
    if ("HEADERONLY".equals(msg))
      return data.toString();
    data.append("# ").append(msg).append('\n');
    if (title != null)
      for (int i = 0; i < title.length; i++)
        data.append("# ").append(title[i]).append('\n');
    state = (state == null ? "" : " rendering:" + state);
    String definitionLine = jvxlGetDefinitionLineVersion1(jvxlData);
    data.append(definitionLine).append(state).append('\n');
    StringBuffer sb = new StringBuffer();
    String colorData = (jvxlData.jvxlColorData == null ? "" : jvxlData.jvxlColorData);
    // if (jvxlData.vertexDataOnly) {  // see XML version
    //  sb.append("\n");
    //  jvxlAppendMeshXml(sb, jvxlData, meshData, false);
    //  sb.append("\n");
    //} else  
    if (jvxlData.jvxlPlane == null) {
      if (jvxlData.jvxlEdgeData == null)
        return "";
      //no real point in compressing this unless it's a sign-based coloring
      sb.append(jvxlData.jvxlSurfaceData);
      sb.append(jvxlCompressString(jvxlData.jvxlEdgeData, false)).append('\n').append(
          jvxlCompressString(colorData, false)).append('\n');
    } else if (colorData != null) {
      sb.append(jvxlCompressString(colorData, false)).append('\n');
    }
    int len = sb.length();
    data.append(sb);
    if (includeHeader) {
      if (msg != null && !jvxlData.vertexDataOnly)
        data.append("#-------end of jvxl file data-------\n");
      data.append(jvxlGetInfo(jvxlData, false)).append('\n');
        jvxlAppendCommandState(data, comment, state, false);
      if (includeHeader)
        XmlUtil.appendTag(data, "jvxlFileTitle", null, null, jvxlData.jvxlFileTitle, false, true);
    }
    return jvxlSetCompressionRatio(data, jvxlData, len);
  }

  private static String jvxlGetDefinitionLineVersion1(JvxlData jvxlData) {
    String definitionLine = 
    //(jvxlData.vContours == null ? ""  : "#+contourlines\n")+
       jvxlData.cutoff + " ";

    //  optional comment line for compatibility with earlier Jmol versions:
    //  #+contourlines (no longer used -- see XML version)
    //  cutoff       nInts     (+/-)bytesEdgeData (+/-)bytesColorData
    //               param1              param2         param3    
    //                 |                   |              |
    //   when          |                   |        >  0 ==> jvxlDataIsColorMapped
    //   when          |                   |       == -1 ==> not color mapped
    //   when          |                   |        < -1 ==> jvxlDataIsPrecisionColor    
    //   when        == -1     &&   == -1 ==> noncontoured plane
    //   when        == -1     &&   == -2 ==> contourable plane
    //   when        < -1*     &&    >  0 ==> contourable functionXY
    //   when        > 0       &&    <  0 ==> jvxlDataisBicolorMap

    //  nInts saved as -1 - nInts

    if (jvxlData.jvxlSurfaceData == null)
      return "";
    int nSurfaceInts = jvxlData.nSurfaceInts;// jvxlData.jvxlSurfaceData.length();
    int bytesUncompressedEdgeData = (jvxlData.vertexDataOnly ? 0
        : jvxlData.jvxlEdgeData.length() - 1);
    int nColorData = (jvxlData.jvxlColorData == null ? -1 : (jvxlData.jvxlColorData.length() - 1));
    if (jvxlData.jvxlPlane == null) {
      if (jvxlData.isContoured) {
        definitionLine += (-1 - nSurfaceInts) + " " + bytesUncompressedEdgeData;
      } else if (jvxlData.isBicolorMap) {
        definitionLine += (nSurfaceInts) + " " + (-bytesUncompressedEdgeData);
      } else {
        definitionLine += nSurfaceInts + " " + bytesUncompressedEdgeData;
      }
      definitionLine += " "
          + (jvxlData.isJvxlPrecisionColor && nColorData != -1 ? -nColorData
              : nColorData);
    } else {
      String s = " " + jvxlData.jvxlPlane.x + " " + jvxlData.jvxlPlane.y + " "
          + jvxlData.jvxlPlane.z + " " + jvxlData.jvxlPlane.w;
      definitionLine += (jvxlData.isContoured ? "-1 -2 " + (-nColorData)
          : "-1 -1 " + nColorData)
          + s;
    }
    if (jvxlData.isContoured) {
      if (jvxlData.contourValues == null || jvxlData.contourColixes == null) {
        definitionLine += " " + jvxlData.nContours;
      } else {
        definitionLine += " " + Escape.escapeArray(jvxlData.contourValues)
            + " \"" + jvxlData.contourColors + "\"";
      }
    }
    // ... mappedDataMin mappedDataMax valueMappedToRed valueMappedToBlue ...
    float min = (jvxlData.mappedDataMin == Float.MAX_VALUE ? 0f
        : jvxlData.mappedDataMin);
    definitionLine += " " + min + " " + jvxlData.mappedDataMax + " "
        + jvxlData.valueMappedToRed + " " + jvxlData.valueMappedToBlue;
    if (jvxlData.insideOut) {
      definitionLine += " insideOut";
    }
    return definitionLine;
  }

  */

}