text: pc.extend(pc, function () {
  
      function PartitionedVertex() {
          this.index = 0;
          this.boneIndices = [0, 0, 0, 0];
      }
  
      function SkinPartition() {
          this.partition = 0;
          this.vertexStart = 0;
          this.vertexCount = 0;
          this.indexStart = 0;
          this.indexCount = 0;
  
          // Indices of bones in this partition. skin matrices will be uploaded to the vertex shader in this order.
          this.boneIndices = [];
  
          this.vertices = []; // Partitioned vertex attributes
          this.indices = [];  // Partitioned vertex indices
          this.indexMap = {}; // Maps the index of an un-partitioned vertex to that same vertex if it has been added
                              // to this particular partition. speeds up checking for duplicate vertices so we don't
                              // add the same vertex more than once.
      }
  
      SkinPartition.prototype = {
          addVertex: function (vertex, idx, vertexArray) {
              var remappedIndex = -1;
              if (this.indexMap[idx] !== undefined) {
                  remappedIndex = this.indexMap[idx];
                  this.indices.push(remappedIndex);
              } else {
                  // Create new partitioned vertex
                  for (var influence = 0; influence < 4; influence++ ) {
                      if (vertexArray.blendWeight.data[idx * 4 + influence] === 0)
                          continue;
  
                      var originalBoneIndex = vertexArray.blendIndices.data[vertex.index * 4 + influence];
                      vertex.boneIndices[influence] = this.getBoneRemap(originalBoneIndex);
                  }
                  remappedIndex = this.vertices.length;
                  this.indices.push(remappedIndex);
                  this.vertices.push(vertex);
                  this.indexMap[idx] = remappedIndex;
              }
          },
  
          addPrimitive: function (vertices, vertexIndices, vertexArray, boneLimit) {
              // Build a list of all the bones used by the vertex that aren't currently in this partition
              var i, j;
              var bonesToAdd = [];
              var bonesToAddCount = 0;
              var vertexCount = vertices.length;
              for (i = 0; i < vertexCount; i++) {
                  var vertex = vertices[i];
                  var idx = vertex.index;
                  for (var influence = 0; influence < 4; influence++) {
                      if (vertexArray.blendWeight.data[idx * 4 + influence] > 0) {
                          var boneIndex = vertexArray.blendIndices.data[idx * 4 + influence];
                          var needToAdd = true;
                          for (j = 0; j < bonesToAddCount; j++) {
                              if (bonesToAdd[j] == boneIndex) {
                                  needToAdd = false;
                                  break;
                              }
                          }
                          if (needToAdd) {
                              bonesToAdd[bonesToAddCount] = boneIndex;
                              var boneRemap = this.getBoneRemap(boneIndex);
                              bonesToAddCount += (boneRemap === -1 ? 1 : 0);
                          }
                      }
                  }
              }
  
              // Check that we can fit more bones in this partition.
              if ((this.boneIndices.length + bonesToAddCount) > boneLimit) {
                  return false;
              }
  
              // Add bones
              for (i = 0; i < bonesToAddCount; i++) {
                  this.boneIndices.push(bonesToAdd[i]);
              }
  
              // Add vertices and indices
              for (i = 0; i < vertexCount; i++) {
                  this.addVertex(vertices[i], vertexIndices[i], vertexArray);
              }
  
              return true;
          },
  
          getBoneRemap: function (boneIndex) {
              for (var i = 0; i < this.boneIndices.length; i++ ) {
                  if (this.boneIndices[i] === boneIndex) {
                      return i;
                  }
              }
              return -1;
          }
      };
  
      function indicesToReferences(model) {
          var i;
          var vertices = model.vertices;
          var skins = model.skins;
          var meshes = model.meshes;
          var meshInstances = model.meshInstances;
  
          for (i = 0; i < meshes.length; i++) {
              meshes[i].vertices = vertices[meshes[i].vertices];
              if (meshes[i].skin !== undefined) {
                  meshes[i].skin = skins[meshes[i].skin];
              }
          }
          for (i = 0; i < meshInstances.length; i++) {
              meshInstances[i].mesh = meshes[meshInstances[i].mesh];
          }
      }
  
      function referencesToIndices(model) {
          var i;
          var vertices = model.vertices;
          var skins = model.skins;
          var meshes = model.meshes;
          var meshInstances = model.meshInstances;
  
          for (i = 0; i < meshes.length; i++) {
              meshes[i].vertices = vertices.indexOf(meshes[i].vertices);
              if (meshes[i].skin !== undefined) {
                  meshes[i].skin = skins.indexOf(meshes[i].skin);
              }
          }
          for (i = 0; i < meshInstances.length; i++) {
              meshInstances[i].mesh = meshes.indexOf(meshInstances[i].mesh);
          }
      }
  
      function partitionSkin(model, materialMappings, boneLimit) {
          var i, j, k, index;
  
          // Replace object indices with actual object references
          // This simplifies insertion/removal of array items
          indicesToReferences(model);
  
          var vertexArrays = model.vertices;
          var skins = model.skins;
          var mesh;
          var meshes = model.meshes;
          var meshInstances = model.meshInstances;
  
          var getVertex = function (idx) {
              var vert = new PartitionedVertex();
              vert.index = idx;
              return vert;
          };
  
          for (i = skins.length - 1; i >= 0; i--) {
              // This skin exceeds the bone limit. Split it!
              if (skins[i].boneNames.length > boneLimit) {
                  var skin = skins.splice(i, 1)[0];
  
                  // Build a list of meshes that use this skin
                  var meshesToSplit = [];
                  for (j = 0; j < meshes.length; j++) {
                      if (meshes[j].skin === skin) {
                          meshesToSplit.push(meshes[j]);
                      }
                  }
                  // Remove meshes from source array
                  for (j = 0; j < meshesToSplit.length; j++) {
                      index = meshes.indexOf(meshesToSplit[j]);
                      if (index !== -1) {
                          meshes.splice(index, 1);
                      }
                  }
  
                  // Error handling
                  if (meshesToSplit.length === 0) {
                      throw new Error('partitionSkin: There should be at least one mesh that references a skin');
                  }
  
                  var vertexArray = meshesToSplit[0].vertices;
                  for (j = 1; j < meshesToSplit.length; j++) {
                      if (meshesToSplit[j].vertices !== vertexArray) {
                          throw new Error('partitionSkin: All meshes that share a skin should also share the same vertex buffer');
                      }
                  }
  
                  var partition;
                  var partitions = [];
  
                  // Phase 1:
                  // Build the skin partitions
                  // Go through index list and extract primitives and add them to bone partitions
                  // Since we are working with a single triangle list, everything is a triangle
                  var primitiveVertices = [];
                  var primitiveIndices = [];
                  var basePartition = 0;
  
                  for (j = 0; j < meshesToSplit.length; j++) {
                      mesh = meshesToSplit[j];
                      var indices = mesh.indices;
                      for (var iIndex = mesh.base; iIndex < mesh.base + mesh.count; ) {
                          // Extract primitive
                          // Convert vertices
                          // There is a little bit of wasted time here if the vertex was already added previously
                          index = indices[iIndex++];
                          primitiveVertices[0] = getVertex(index);
                          primitiveIndices[0] = index;
  
                          index = indices[iIndex++];
                          primitiveVertices[1] = getVertex(index);
                          primitiveIndices[1] = index;
  
                          index = indices[iIndex++];
                          primitiveVertices[2] = getVertex(index);
                          primitiveIndices[2] = index;
  
                          // Attempt to add the primitive to an existing bone partition
                          var added = false;
                          for (var iBonePartition = basePartition; iBonePartition < partitions.length; iBonePartition++) {
                              partition = partitions[iBonePartition];
                              if (partition.addPrimitive(primitiveVertices, primitiveIndices, vertexArray, boneLimit)) {
                                  added = true;
                                  break;
                              }
                          }
  
                          // If the primitive was not added to an existing bone partition, we need to make a new bone partition and add the primitive to it
                          if (!added) {
                              partition = new SkinPartition();
                              partition.originalMesh = mesh;
                              partition.addPrimitive(primitiveVertices, primitiveIndices, vertexArray, boneLimit);
                              partitions.push(partition);
                          }
                      }
  
                      basePartition = partitions.length;
                  }
  
                  // Phase 2:
                  // Gather vertex and index lists from all the partitions, then upload to GPU
                  var partitionedVertices = [];
                  var partitionedIndices = [];
  
                  for (j = 0; j < partitions.length; j++) {
                      partition = partitions[j];
  
                      if (partition.vertices.length && partition.indices.length) {
                          // this bone partition contains vertices and indices
  
                          // Find offsets
                          var vertexStart = partitionedVertices.length;
                          var vertexCount = partition.vertices.length;
                          var indexStart = partitionedIndices.length;
                          var indexCount = partition.indices.length;
  
                          // Make a new sub set
                          partition.partition = j;
                          partition.vertexStart = vertexStart;
                          partition.vertexCount = vertexCount;
                          partition.indexStart = indexStart;
                          partition.indexCount = indexCount;
  
                          // Copy buffers
                          var iSour;
                          var iDest;
  
                          // Copy vertices to final list
                          iSour = 0;
                          iDest = vertexStart;
                          while (iSour < vertexCount) {
                              partitionedVertices[iDest++] = partition.vertices[iSour++];
                          }
  
                          // Copy indices to final list
                          iSour = 0;
                          iDest = indexStart;
                          while (iSour < indexCount) {
                              partitionedIndices[iDest++] = partition.indices[iSour++] + vertexStart;    // adjust so they reference into flat vertex list
                          }
                      }
                  }
  
                  // Phase 3:
                  // Create the split skins
                  var splitSkins = [];
                  for (j = 0; j < partitions.length; j++) {
                      partition = partitions[j];
  
                      var ibp = [];
                      var boneNames = [];
                      for (k = 0; k < partition.boneIndices.length; k++) {
                          ibp.push(skin.inverseBindMatrices[partition.boneIndices[k]]);
                          boneNames.push(skin.boneNames[partition.boneIndices[k]]);
                      }
  
                      var splitSkin = {
                          inverseBindMatrices: ibp,
                          boneNames: boneNames
                      };
                      splitSkins.push(splitSkin);
                      skins.push(splitSkin);
                  }
  
                  /////////////
                  // Phase 4 //
                  /////////////
  
                  // Create a partitioned vertex array
                  var attrib, attribName, data, components;
                  var splitVertexArray = {};
  
                  // Create a vertex array of the same format as the input to take partitioned vertex data
                  for (attribName in vertexArray) {
                      splitVertexArray[attribName] = {
                          components: vertexArray[attribName].components,
                          data: [],
                          type: vertexArray[attribName].type,
                      };
                  }
  
                  // Copy across the vertex data. Everything is the same as the source data except the remapped
                  // bone indices
                  for (attribName in vertexArray) {
                      if (attribName === 'blendIndices') {
                          var dstBoneIndices = splitVertexArray[attribName].data;
                          for (j = 0; j < partitionedVertices.length; j++) {
                              var srcBoneIndices = partitionedVertices[j].boneIndices;
                              dstBoneIndices.push(srcBoneIndices[0], srcBoneIndices[1], srcBoneIndices[2], srcBoneIndices[3]);
                          }
                      } else {
                          attrib = vertexArray[attribName];
                          data = attrib.data;
                          components = attrib.components;
                          for (j = 0; j < partitionedVertices.length; j++) {
                              index = partitionedVertices[j].index;
                              for (k = 0; k < components; k++) {
                                  splitVertexArray[attribName].data.push(data[index * components + k]);
                              }
                          }
                      }
                  }
  
                  // Replace original vertex array with split one
                  vertexArrays[vertexArrays.indexOf(vertexArray)] = splitVertexArray;
  
                  /////////////
                  // Phase 5 //
                  /////////////
  
                  // Build new mesh array
                  for (j = 0; j < partitions.length; j++) {
                      partition = partitions[j];
  
                      mesh = {
                          aabb: {
                              min: [0, 0, 0],
                              max: [0, 0, 0]
                          },
                          vertices: splitVertexArray,
                          skin: splitSkins[j],
                          indices: partitionedIndices.splice(0, partition.indexCount),
                          type: 'triangles',
                          base: 0,
                          count: partition.indexCount
                      };
  
                      meshes.push(mesh);
  
                      // Find all the original mesh instances that referred to the pre-split mesh
                      for (k = meshInstances.length - 1; k >= 0; k--) {
                          if (meshInstances[k].mesh === partition.originalMesh) {
                              meshInstances.push({
                                  mesh: mesh,
                                  node: meshInstances[k].node
                              });
                              if (materialMappings) {
                                  materialMappings.push({
                                      material: materialMappings[k].material,
                                      path: materialMappings[k].path
                                  });
                              }
                          }
                      }
                  }
  
                  for (j = 0; j < partitions.length; j++) {
                      partition = partitions[j];
  
                      // Find all the original mesh instances that referred to the pre-split mesh
                      for (k = meshInstances.length - 1; k >= 0; k--) {
                          if (meshInstances[k].mesh === partition.originalMesh) {
                              meshInstances.splice(k, 1);
                              if (materialMappings) {
                                  materialMappings.splice(k, 1);
                              }
                          }
                      }
                  }
              }
          }
  
          // Convert references back to indices
          referencesToIndices(model);
      }
  
      return {
          partitionSkin: partitionSkin
      };
  }());
  