/**
   * Sort a single set of nodes into the output details of a single layer of a single viewport and
   * place in the provided GraphicsInstructions instance. The implementation of this method should
   * only concern itself with this set of nodes and not worry about dealing with nested scenes or
   * other viewports.
   *
   * @param nodes The list of nodes to perform sorting on
   * @param numNodes The number of valid items in the nodes array
   * @param data The environment data used during sorting
   * @param instr Instruction instant to put the details into
   * @param instrCount Offset of current number of valid instructions
   * @return The current instruction count after sorting
   */
  protected int sortNodes(
      GraphicsCullOutputDetails[] nodes,
      int numNodes,
      GraphicsEnvironmentData data,
      GraphicsInstructions instr,
      int instrCount) {
    int idx = instrCount;
    int fog_id = 0;

    for (int i = 0; i < numNodes && !terminate; i++) {
      // First drop all the lights into the queue.
      if (nodes[i].numLights != 0) {
        for (int j = 0; j < nodes[i].numLights; j++) {
          VisualDetails ld = nodes[i].lights[j];
          instr.renderList[idx].renderable = ld.getRenderable();

          System.arraycopy(ld.getTransform(), 0, instr.renderList[idx].transform, 0, 16);

          instr.renderList[idx].id = lastGlobalId++;
          instr.renderOps[idx] = RenderOp.START_LIGHT;
          idx++;
        }
      }

      // Next drop all the clip planes into the queue.
      if (nodes[i].numClipPlanes != 0) {
        for (int j = 0; j < nodes[i].numClipPlanes; j++) {
          VisualDetails cd = nodes[i].clipPlanes[j];
          instr.renderList[idx].renderable = cd.getRenderable();

          System.arraycopy(cd.getTransform(), 0, instr.renderList[idx].transform, 0, 16);

          instr.renderList[idx].id = lastGlobalId++;
          instr.renderOps[idx] = RenderOp.START_CLIP_PLANE;
          idx++;
        }
      }

      if (nodes[i].localFog != null) {
        fog_id = lastGlobalId++;
        instr.renderList[idx].id = fog_id;
        instr.renderList[idx].renderable = nodes[i].localFog;
        instr.renderOps[idx] = RenderOp.START_FOG;
        idx++;
      }

      if (nodes[i].renderable instanceof ShapeRenderable) {
        ShapeRenderable shape = (ShapeRenderable) nodes[i].renderable;
        Matrix4f tx = nodes[i].transform;

        if (!shape.is2D()) {
          Renderable r = shape.getAppearanceRenderable();

          if (r != null) {
            instr.renderList[idx].renderable = r;
            instr.renderOps[idx] = RenderOp.START_STATE;
            idx++;
          }

          instr.renderList[idx].transform[0] = tx.m00;
          instr.renderList[idx].transform[1] = tx.m10;
          instr.renderList[idx].transform[2] = tx.m20;
          instr.renderList[idx].transform[3] = tx.m30;

          instr.renderList[idx].transform[4] = tx.m01;
          instr.renderList[idx].transform[5] = tx.m11;
          instr.renderList[idx].transform[6] = tx.m21;
          instr.renderList[idx].transform[7] = tx.m31;

          instr.renderList[idx].transform[8] = tx.m02;
          instr.renderList[idx].transform[9] = tx.m12;
          instr.renderList[idx].transform[10] = tx.m22;
          instr.renderList[idx].transform[11] = tx.m32;

          instr.renderList[idx].transform[12] = tx.m03;
          instr.renderList[idx].transform[13] = tx.m13;
          instr.renderList[idx].transform[14] = tx.m23;
          instr.renderList[idx].transform[15] = tx.m33;

          instr.renderList[idx].renderable = shape.getGeometryRenderable();
          instr.renderOps[idx] = RenderOp.RENDER_GEOMETRY;
          idx++;

          if (r != null) {
            instr.renderList[idx].renderable = r;
            instr.renderOps[idx] = RenderOp.STOP_STATE;
            idx++;
          }
        } else {
          Renderable r = shape.getAppearanceRenderable();

          if (r != null) {
            instr.renderList[idx].renderable = r;
            instr.renderOps[idx] = RenderOp.START_RENDER;
            idx++;
          }

          instr.renderList[idx].transform[0] = tx.m00;
          instr.renderList[idx].transform[1] = 0;
          instr.renderList[idx].transform[2] = 0;
          instr.renderList[idx].transform[3] = tx.m30;

          instr.renderList[idx].transform[4] = 0;
          instr.renderList[idx].transform[5] = tx.m11;
          instr.renderList[idx].transform[6] = 0;
          instr.renderList[idx].transform[7] = tx.m31;

          instr.renderList[idx].transform[8] = 0;
          instr.renderList[idx].transform[9] = 0;
          instr.renderList[idx].transform[10] = 0;
          instr.renderList[idx].transform[11] = 0;

          instr.renderList[idx].transform[12] = 0;
          instr.renderList[idx].transform[13] = 0;
          instr.renderList[idx].transform[14] = 0;
          instr.renderList[idx].transform[15] = 0;

          instr.renderList[idx].renderable = shape.getGeometryRenderable();
          instr.renderOps[idx] = RenderOp.RENDER_GEOMETRY_2D;
          idx++;

          if (r != null) {
            instr.renderList[idx].renderable = r;
            instr.renderOps[idx] = RenderOp.STOP_RENDER;
            idx++;
          }
        }
      } else if (nodes[i].renderable instanceof CustomRenderable) {
        instr.renderList[idx].renderable = nodes[i].renderable;
        instr.renderList[idx].instructions = nodes[i].customData;

        Matrix4f tx = nodes[i].transform;

        instr.renderList[idx].transform[0] = tx.m00;
        instr.renderList[idx].transform[1] = tx.m10;
        instr.renderList[idx].transform[2] = tx.m20;
        instr.renderList[idx].transform[3] = tx.m30;

        instr.renderList[idx].transform[4] = tx.m01;
        instr.renderList[idx].transform[5] = tx.m11;
        instr.renderList[idx].transform[6] = tx.m21;
        instr.renderList[idx].transform[7] = tx.m31;

        instr.renderList[idx].transform[8] = tx.m02;
        instr.renderList[idx].transform[9] = tx.m12;
        instr.renderList[idx].transform[10] = tx.m22;
        instr.renderList[idx].transform[11] = tx.m32;

        instr.renderList[idx].transform[12] = tx.m03;
        instr.renderList[idx].transform[13] = tx.m13;
        instr.renderList[idx].transform[14] = tx.m23;
        instr.renderList[idx].transform[15] = tx.m33;

        instr.renderOps[idx] = RenderOp.RENDER_CUSTOM;
        idx++;
      } else {
        System.out.println("Non-shape node in sorter " + nodes[i].renderable);
      }

      if (nodes[i].localFog != null) {
        instr.renderList[idx].id = fog_id;
        instr.renderList[idx].renderable = nodes[i].localFog;
        instr.renderOps[idx] = RenderOp.STOP_FOG;
        idx++;
      }

      // Clean up the clip planess
      if (nodes[i].numClipPlanes != 0) {
        for (int j = nodes[i].numClipPlanes - 1; j >= 0; j--) {
          VisualDetails cd = nodes[i].clipPlanes[j];
          instr.renderList[idx].renderable = cd.getRenderable();

          instr.renderList[idx].id = lastGlobalId - (1 + j);

          // Don't need the transform for the clip stopping. Save
          // CPU cycles by not copying it.

          instr.renderOps[idx] = RenderOp.STOP_CLIP_PLANE;
          idx++;
        }
      }

      // Clean up the lights
      if (nodes[i].numLights != 0) {
        for (int j = nodes[i].numLights - 1; j >= 0; j--) {
          VisualDetails ld = nodes[i].lights[j];
          instr.renderList[idx].renderable = ld.getRenderable();

          instr.renderList[idx].id = lastGlobalId - (1 + j);

          // Don't need the transform for the light stopping. Save
          // CPU cycles by not copying it.

          instr.renderOps[idx] = RenderOp.STOP_LIGHT;
          idx++;
        }
      }
    }

    return idx;
  }
  /**
   * Sort a single set of 2D nodes into the output details of a single layer of a single viewport
   * and place in the provided GraphicsInstructions instance. The implementation of this method
   * should only concern itself with this set of nodes and not worry about dealing with nested
   * scenes or other viewports.
   *
   * @param nodes The list of nodes to perform sorting on
   * @param numNodes The number of valid items in the nodes array
   * @param data The environment data used during sorting
   * @param instr Instruction instant to put the details into
   * @param instrCount Offset of current number of valid instructions
   * @return The current instruction count after sorting
   */
  protected int sort2DNodes(
      GraphicsCullOutputDetails[] nodes,
      int numNodes,
      GraphicsEnvironmentData data,
      GraphicsInstructions instr,
      int instrCount) {
    int idx = instrCount;
    int fog_id = 0;

    for (int i = 0; i < numNodes && !terminate; i++) {
      if (nodes[i].renderable instanceof ShapeRenderable) {
        ShapeRenderable shape = (ShapeRenderable) nodes[i].renderable;

        if (!shape.is2D()) continue;

        // For this simple one, just use the basic render command.
        // Something more complex would pull the shape apart and do
        // state/depth/transparency sorting in this section.
        Matrix4f tx = nodes[i].transform;

        instr.renderList[idx].transform[0] = tx.m00;
        instr.renderList[idx].transform[1] = tx.m10;
        instr.renderList[idx].transform[2] = tx.m20;
        instr.renderList[idx].transform[3] = tx.m30;

        instr.renderList[idx].transform[4] = tx.m01;
        instr.renderList[idx].transform[5] = tx.m11;
        instr.renderList[idx].transform[6] = tx.m21;
        instr.renderList[idx].transform[7] = tx.m31;

        instr.renderList[idx].transform[8] = tx.m02;
        instr.renderList[idx].transform[9] = tx.m12;
        instr.renderList[idx].transform[10] = tx.m22;
        instr.renderList[idx].transform[11] = tx.m32;

        instr.renderList[idx].transform[12] = tx.m03;
        instr.renderList[idx].transform[13] = tx.m13;
        instr.renderList[idx].transform[14] = tx.m23;
        instr.renderList[idx].transform[15] = tx.m33;

        instr.renderList[idx].renderable = nodes[i].renderable;
        instr.renderOps[idx] = RenderOp.START_RENDER_2D;
        idx++;

        instr.renderList[idx].renderable = nodes[i].renderable;
        instr.renderOps[idx] = RenderOp.STOP_RENDER_2D;
        idx++;
      } else if (nodes[i].renderable instanceof CustomRenderable) {
        instr.renderList[idx].renderable = nodes[i].renderable;
        instr.renderList[idx].instructions = nodes[i].customData;

        Matrix4f tx = nodes[i].transform;

        instr.renderList[idx].transform[0] = tx.m00;
        instr.renderList[idx].transform[1] = tx.m10;
        instr.renderList[idx].transform[2] = tx.m20;
        instr.renderList[idx].transform[3] = tx.m30;

        instr.renderList[idx].transform[4] = tx.m01;
        instr.renderList[idx].transform[5] = tx.m11;
        instr.renderList[idx].transform[6] = tx.m21;
        instr.renderList[idx].transform[7] = tx.m31;

        instr.renderList[idx].transform[8] = tx.m02;
        instr.renderList[idx].transform[9] = tx.m12;
        instr.renderList[idx].transform[10] = tx.m22;
        instr.renderList[idx].transform[11] = tx.m32;

        instr.renderList[idx].transform[12] = tx.m03;
        instr.renderList[idx].transform[13] = tx.m13;
        instr.renderList[idx].transform[14] = tx.m23;
        instr.renderList[idx].transform[15] = tx.m33;

        instr.renderOps[idx] = RenderOp.RENDER_CUSTOM;
        idx++;
      } else {
        System.out.println("Non-shape node in sorter " + nodes[i].renderable);
      }
    }

    return idx;
  }