public void render(int offset, int left, int right) {
      ShadedSurface texture = (ShadedSurface) currentTexture;
      float u = SCALE * a.getDotProduct(viewPos);
      float v = SCALE * b.getDotProduct(viewPos);
      float z = c.getDotProduct(viewPos);
      float du = INTERP_SIZE * SCALE * a.x;
      float dv = INTERP_SIZE * SCALE * b.x;
      float dz = INTERP_SIZE * c.x;
      int nextTx = (int) (u / z);
      int nextTy = (int) (v / z);
      int depth = (int) (w * z);
      int dDepth = (int) (w * c.x);
      int x = left;
      while (x <= right) {
        int tx = nextTx;
        int ty = nextTy;
        int maxLength = right - x + 1;
        if (maxLength > INTERP_SIZE) {
          u += du;
          v += dv;
          z += dz;
          nextTx = (int) (u / z);
          nextTy = (int) (v / z);
          int dtx = (nextTx - tx) >> INTERP_SIZE_BITS;
          int dty = (nextTy - ty) >> INTERP_SIZE_BITS;
          int endOffset = offset + INTERP_SIZE;
          while (offset < endOffset) {
            if (zBuffer.checkDepth(offset, (short) (depth >> SCALE_BITS))) {
              doubleBufferData[offset] = texture.getColor(tx >> SCALE_BITS, ty >> SCALE_BITS);
            }
            offset++;
            tx += dtx;
            ty += dty;
            depth += dDepth;
          }
          x += INTERP_SIZE;
        } else {
          // variable interpolation size
          int interpSize = maxLength;
          u += interpSize * SCALE * a.x;
          v += interpSize * SCALE * b.x;
          z += interpSize * c.x;
          nextTx = (int) (u / z);
          nextTy = (int) (v / z);

          // make sure tx, ty, nextTx, and nextTy are
          // all within bounds
          tx = checkBounds(tx, texture.getWidth());
          ty = checkBounds(ty, texture.getHeight());
          nextTx = checkBounds(nextTx, texture.getWidth());
          nextTy = checkBounds(nextTy, texture.getHeight());

          int dtx = (nextTx - tx) / interpSize;
          int dty = (nextTy - ty) / interpSize;
          int endOffset = offset + interpSize;
          while (offset < endOffset) {
            if (zBuffer.checkDepth(offset, (short) (depth >> SCALE_BITS))) {
              doubleBufferData[offset] = texture.getColor(tx >> SCALE_BITS, ty >> SCALE_BITS);
            }
            offset++;
            tx += dtx;
            ty += dty;
            depth += dDepth;
          }
          x += interpSize;
        }
      }
    }
    public void parseLine(String line)
        throws IOException, NumberFormatException, NoSuchElementException {
      StringTokenizer tokenizer = new StringTokenizer(line);
      String command = tokenizer.nextToken();
      if (command.equals("v")) {
        // create a new vertex
        vertices.add(
            new Vector3D(
                Float.parseFloat(tokenizer.nextToken()),
                Float.parseFloat(tokenizer.nextToken()),
                Float.parseFloat(tokenizer.nextToken())));
      } else if (command.equals("f")) {
        // create a new face (flat, convex polygon)
        List currVertices = new ArrayList();
        while (tokenizer.hasMoreTokens()) {
          String indexStr = tokenizer.nextToken();

          // ignore texture and normal coords
          int endIndex = indexStr.indexOf('/');
          if (endIndex != -1) {
            indexStr = indexStr.substring(0, endIndex);
          }

          currVertices.add(getVector(indexStr));
        }

        // create textured polygon
        Vector3D[] array = new Vector3D[currVertices.size()];
        currVertices.toArray(array);
        TexturedPolygon3D poly = new TexturedPolygon3D(array);

        // set the texture
        ShadedSurface.createShadedSurface(
            poly, currentMaterial.texture, lights, ambientLightIntensity);

        // add the polygon to the current group
        currentGroup.addPolygon(poly);
      } else if (command.equals("g")) {
        // define the current group
        if (tokenizer.hasMoreTokens()) {
          String name = tokenizer.nextToken();
          currentGroup = new PolygonGroup(name);
        } else {
          currentGroup = new PolygonGroup();
        }
        object.addPolygonGroup(currentGroup);
      } else if (command.equals("mtllib")) {
        // load materials from file
        String name = tokenizer.nextToken();
        parseFile(name);
      } else if (command.equals("usemtl")) {
        // define the current material
        String name = tokenizer.nextToken();
        currentMaterial = (Material) materials.get(name);
        if (currentMaterial == null) {
          System.out.println("no material: " + name);
        }
      } else {
        // unknown command - ignore it
      }
    }