function generateOctree(that, sampleCount, levelBlendFactor) {
  const targetSse = that._primitive._screenSpaceError;
  const keyframeLocation = that._keyframeLocation;
  const frameNumber = that._frameNumber;
  const useLeafNodes = sampleCount >= 2;

  let internalNodeCount = 0;
  let leafNodeCount = 0;
  const internalNodeOctreeData = [];
  const leafNodeOctreeData = [];

  /**
   * @param {SpatialNode} node
   * @param {number} childOctreeIndex
   * @param {number} childEntryIndex
   * @param {number} parentOctreeIndex
   * @param {number} parentEntryIndex
   */
  function buildOctree(
    node,
    childOctreeIndex,
    childEntryIndex,
    parentOctreeIndex,
    parentEntryIndex,
  ) {
    let hasRenderableChildren = false;
    if (defined(node.children)) {
      for (let c = 0; c < 8; c++) {
        const childNode = node.children[c];
        childNode.computeSurroundingRenderableKeyframeNodes(keyframeLocation);
        if (childNode.isRenderable(frameNumber)) {
          hasRenderableChildren = true;
        }
      }
    }

    if (hasRenderableChildren) {
      // Point the parent and child octree indexes at each other
      internalNodeOctreeData[parentEntryIndex] =
        (GpuOctreeFlag.INTERNAL << 16) | childOctreeIndex;
      internalNodeOctreeData[childEntryIndex] = parentOctreeIndex;
      internalNodeCount++;

      // Recurse over children
      parentOctreeIndex = childOctreeIndex;
      parentEntryIndex = parentOctreeIndex * 9 + 1;
      for (let cc = 0; cc < 8; cc++) {
        const child = node.children[cc];
        childOctreeIndex = internalNodeCount;
        childEntryIndex = childOctreeIndex * 9 + 0;
        buildOctree(
          child,
          childOctreeIndex,
          childEntryIndex,
          parentOctreeIndex,
          parentEntryIndex + cc,
        );
      }
    } else {
      // Store the leaf node information instead
      // Recursion stops here because there are no renderable children
      that._primitive.tileVisible.raiseEvent();
      if (useLeafNodes) {
        const baseIdx = leafNodeCount * 5;
        const keyframeNode = node.renderableKeyframeNodePrevious;
        const levelDifference = node.level - keyframeNode.spatialNode.level;

        const parentNode = keyframeNode.spatialNode.parent;
        const parentKeyframeNode = defined(parentNode)
          ? parentNode.renderableKeyframeNodePrevious
          : keyframeNode;

        const lodLerp = getLodLerp(node, targetSse, levelBlendFactor);
        const levelDifferenceChild = levelDifference;
        const levelDifferenceParent = 1;
        const megatextureIndexChild = keyframeNode.megatextureIndex;
        const megatextureIndexParent = parentKeyframeNode.megatextureIndex;

        leafNodeOctreeData[baseIdx + 0] = lodLerp;
        leafNodeOctreeData[baseIdx + 1] = levelDifferenceChild;
        leafNodeOctreeData[baseIdx + 2] = levelDifferenceParent;
        leafNodeOctreeData[baseIdx + 3] = megatextureIndexChild;
        leafNodeOctreeData[baseIdx + 4] = megatextureIndexParent;

        internalNodeOctreeData[parentEntryIndex] =
          (GpuOctreeFlag.LEAF << 16) | leafNodeCount;
      } else {
        const keyframeNode = node.renderableKeyframeNodePrevious;
        const levelDifference = node.level - keyframeNode.spatialNode.level;
        const flag =
          levelDifference === 0
            ? GpuOctreeFlag.LEAF
            : GpuOctreeFlag.PACKED_LEAF_FROM_PARENT;
        internalNodeOctreeData[parentEntryIndex] =
          (flag << 16) | keyframeNode.megatextureIndex;
      }
      leafNodeCount++;
    }
  }

  const rootNode = that.rootNode;
  rootNode.computeSurroundingRenderableKeyframeNodes(keyframeLocation);
  if (rootNode.isRenderable(frameNumber)) {
    buildOctree(rootNode, 0, 0, 0, 0);
  }

  copyToInternalNodeTexture(
    internalNodeOctreeData,
    9,
    that.internalNodeTilesPerRow,
    that.internalNodeTexture,
  );
  if (useLeafNodes) {
    copyToLeafNodeTexture(
      leafNodeOctreeData,
      2,
      that.leafNodeTilesPerRow,
      that.leafNodeTexture,
    );
  }
}
@function

@param {VoxelTraversal} that
@param {number} sampleCount
@param {number} levelBlendFactor
@private