| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776 | import defined from "../Core/defined.js";import PixelFormat from "../Core/PixelFormat.js";import ContextLimits from "../Renderer/ContextLimits.js";import Sampler from "../Renderer/Sampler.js";import Texture from "../Renderer/Texture.js";import TextureMagnificationFilter from "../Renderer/TextureMagnificationFilter.js";import TextureMinificationFilter from "../Renderer/TextureMinificationFilter.js";import TextureWrap from "../Renderer/TextureWrap.js";import ForEach from "./GltfPipeline/ForEach.js";// glTF does not allow an index value of 65535 because this is the primitive// restart value in some APIs.const MAX_GLTF_UINT16_INDEX = 65534;/** * Creates face outlines for glTF primitives with the `CESIUM_primitive_outline` extension. * @private */function ModelOutlineLoader() {}/** * Returns true if the model uses or requires CESIUM_primitive_outline. * @private */ModelOutlineLoader.hasExtension = function (model) {  return (    defined(model.extensionsRequired.CESIUM_primitive_outline) ||    defined(model.extensionsUsed.CESIUM_primitive_outline)  );};/** * Arranges to outline any primitives with the CESIUM_primitive_outline extension. * It is expected that all buffer data is loaded and available in * `extras._pipeline.source` before this function is called, and that vertex * and index WebGL buffers are not yet created. * @private */ModelOutlineLoader.outlinePrimitives = function (model) {  if (!ModelOutlineLoader.hasExtension(model)) {    return;  }  const gltf = model.gltf;  // Assumption: A single bufferView contains a single zero-indexed range of vertices.  // No trickery with using large accessor byteOffsets to store multiple zero-based  // ranges of vertices in a single bufferView. Use separate bufferViews for that,  // you monster.  // Note that interleaved vertex attributes (e.g. position0, normal0, uv0,  // position1, normal1, uv1, ...) _are_ supported and should not be confused with  // the above.  const vertexNumberingScopes = [];  ForEach.mesh(gltf, function (mesh, meshId) {    ForEach.meshPrimitive(mesh, function (primitive, primitiveId) {      if (!defined(primitive.extensions)) {        return;      }      const outlineData = primitive.extensions.CESIUM_primitive_outline;      if (!defined(outlineData)) {        return;      }      const vertexNumberingScope = getVertexNumberingScope(model, primitive);      if (vertexNumberingScope === undefined) {        return;      }      if (vertexNumberingScopes.indexOf(vertexNumberingScope) < 0) {        vertexNumberingScopes.push(vertexNumberingScope);      }      // Add the outline to this primitive      addOutline(        model,        meshId,        primitiveId,        outlineData.indices,        vertexNumberingScope      );    });  });  // Update all relevant bufferViews to include the duplicate vertices that are  // needed for outlining.  for (let i = 0; i < vertexNumberingScopes.length; ++i) {    updateBufferViewsWithNewVertices(      model,      vertexNumberingScopes[i].bufferViews    );  }  // Remove data not referenced by any bufferViews anymore.  compactBuffers(model);};ModelOutlineLoader.createTexture = function (model, context) {  let cache = context.cache.modelOutliningCache;  if (!defined(cache)) {    cache = context.cache.modelOutliningCache = {};  }  if (defined(cache.outlineTexture)) {    return cache.outlineTexture;  }  const maxSize = Math.min(4096, ContextLimits.maximumTextureSize);  let size = maxSize;  const levelZero = createTexture(size);  const mipLevels = [];  while (size > 1) {    size >>= 1;    mipLevels.push(createTexture(size));  }  const texture = new Texture({    context: context,    source: {      arrayBufferView: levelZero,      mipLevels: mipLevels,    },    width: maxSize,    height: 1,    pixelFormat: PixelFormat.LUMINANCE,    sampler: new Sampler({      wrapS: TextureWrap.CLAMP_TO_EDGE,      wrapT: TextureWrap.CLAMP_TO_EDGE,      minificationFilter: TextureMinificationFilter.LINEAR_MIPMAP_LINEAR,      magnificationFilter: TextureMagnificationFilter.LINEAR,    }),  });  cache.outlineTexture = texture;  return texture;};function addOutline(  model,  meshId,  primitiveId,  edgeIndicesAccessorId,  vertexNumberingScope) {  const vertexCopies = vertexNumberingScope.vertexCopies;  const extraVertices = vertexNumberingScope.extraVertices;  const outlineCoordinates = vertexNumberingScope.outlineCoordinates;  const gltf = model.gltf;  const mesh = gltf.meshes[meshId];  const primitive = mesh.primitives[primitiveId];  const accessors = gltf.accessors;  const bufferViews = gltf.bufferViews;  // Find the number of vertices in this primitive by looking at  // the first attribute. Others are required to be the same.  let numVertices;  for (const semantic in primitive.attributes) {    if (primitive.attributes.hasOwnProperty(semantic)) {      const attributeId = primitive.attributes[semantic];      const accessor = accessors[attributeId];      if (defined(accessor)) {        numVertices = accessor.count;        break;      }    }  }  if (!defined(numVertices)) {    return undefined;  }  const triangleIndexAccessorGltf = accessors[primitive.indices];  const triangleIndexBufferViewGltf =    bufferViews[triangleIndexAccessorGltf.bufferView];  const edgeIndexAccessorGltf = accessors[edgeIndicesAccessorId];  const edgeIndexBufferViewGltf = bufferViews[edgeIndexAccessorGltf.bufferView];  const loadResources = model._loadResources;  const triangleIndexBufferView = loadResources.getBuffer(    triangleIndexBufferViewGltf  );  const edgeIndexBufferView = loadResources.getBuffer(edgeIndexBufferViewGltf);  let triangleIndices =    triangleIndexAccessorGltf.componentType === 5123      ? new Uint16Array(          triangleIndexBufferView.buffer,          triangleIndexBufferView.byteOffset +            triangleIndexAccessorGltf.byteOffset,          triangleIndexAccessorGltf.count        )      : new Uint32Array(          triangleIndexBufferView.buffer,          triangleIndexBufferView.byteOffset +            triangleIndexAccessorGltf.byteOffset,          triangleIndexAccessorGltf.count        );  const edgeIndices =    edgeIndexAccessorGltf.componentType === 5123      ? new Uint16Array(          edgeIndexBufferView.buffer,          edgeIndexBufferView.byteOffset + edgeIndexAccessorGltf.byteOffset,          edgeIndexAccessorGltf.count        )      : new Uint32Array(          edgeIndexBufferView.buffer,          edgeIndexBufferView.byteOffset + edgeIndexAccessorGltf.byteOffset,          edgeIndexAccessorGltf.count        );  // Make a hash table for quick lookups of whether an edge exists between two  // vertices. The hash is a sparse array indexed by  //   `smallerVertexIndex * totalNumberOfVertices + biggerVertexIndex`  // A value of 1 indicates an edge exists between the two vertex indices; any  // other value indicates that it does not. We store the  // `edgeSmallMultipler` - that is, the number of vertices in the equation  // above - at index 0 for easy access to it later.  const edgeSmallMultiplier = numVertices;  const edges = [edgeSmallMultiplier];  let i;  for (i = 0; i < edgeIndices.length; i += 2) {    const a = edgeIndices[i];    const b = edgeIndices[i + 1];    const small = Math.min(a, b);    const big = Math.max(a, b);    edges[small * edgeSmallMultiplier + big] = 1;  }  // For each triangle, adjust vertex data so that the correct edges are outlined.  for (i = 0; i < triangleIndices.length; i += 3) {    let i0 = triangleIndices[i];    let i1 = triangleIndices[i + 1];    let i2 = triangleIndices[i + 2];    const all = false; // set this to true to draw a full wireframe.    const has01 = all || isHighlighted(edges, i0, i1);    const has12 = all || isHighlighted(edges, i1, i2);    const has20 = all || isHighlighted(edges, i2, i0);    let unmatchableVertexIndex = matchAndStoreCoordinates(      outlineCoordinates,      i0,      i1,      i2,      has01,      has12,      has20    );    while (unmatchableVertexIndex >= 0) {      // Copy the unmatchable index and try again.      let copy;      if (unmatchableVertexIndex === i0) {        copy = vertexCopies[i0];      } else if (unmatchableVertexIndex === i1) {        copy = vertexCopies[i1];      } else {        copy = vertexCopies[i2];      }      if (copy === undefined) {        copy = numVertices + extraVertices.length;        let original = unmatchableVertexIndex;        while (original >= numVertices) {          original = extraVertices[original - numVertices];        }        extraVertices.push(original);        vertexCopies[unmatchableVertexIndex] = copy;      }      if (        copy > MAX_GLTF_UINT16_INDEX &&        triangleIndices instanceof Uint16Array      ) {        // We outgrew a 16-bit index buffer, switch to 32-bit.        triangleIndices = new Uint32Array(triangleIndices);        triangleIndexAccessorGltf.componentType = 5125; // UNSIGNED_INT        triangleIndexBufferViewGltf.buffer =          gltf.buffers.push({            byteLength: triangleIndices.byteLength,            extras: {              _pipeline: {                source: triangleIndices.buffer,              },            },          }) - 1;        triangleIndexBufferViewGltf.byteLength = triangleIndices.byteLength;        triangleIndexBufferViewGltf.byteOffset = 0;        model._loadResources.buffers[          triangleIndexBufferViewGltf.buffer        ] = new Uint8Array(          triangleIndices.buffer,          0,          triangleIndices.byteLength        );        // The index componentType is also squirreled away in ModelLoadResources.        // Hackily update it, or else we'll end up creating the wrong type        // of index buffer later.        loadResources.indexBuffersToCreate._array.forEach(function (toCreate) {          if (toCreate.id === triangleIndexAccessorGltf.bufferView) {            toCreate.componentType = triangleIndexAccessorGltf.componentType;          }        });      }      if (unmatchableVertexIndex === i0) {        i0 = copy;        triangleIndices[i] = copy;      } else if (unmatchableVertexIndex === i1) {        i1 = copy;        triangleIndices[i + 1] = copy;      } else {        i2 = copy;        triangleIndices[i + 2] = copy;      }      if (defined(triangleIndexAccessorGltf.max)) {        triangleIndexAccessorGltf.max[0] = Math.max(          triangleIndexAccessorGltf.max[0],          copy        );      }      unmatchableVertexIndex = matchAndStoreCoordinates(        outlineCoordinates,        i0,        i1,        i2,        has01,        has12,        has20      );    }  }}// Each vertex has three coordinates, a, b, and c.// a is the coordinate that applies to edge 2-0 for the vertex.// b is the coordinate that applies to edge 0-1 for the vertex.// c is the coordinate that applies to edge 1-2 for the vertex.// A single triangle with all edges highlighted:////                 | a | b | c |//                 | 1 | 1 | 0 |//                       0//                      / \//                     /   \//           edge 0-1 /     \ edge 2-0//                   /       \//                  /         \// | a | b | c |   1-----------2   | a | b | c |// | 0 | 1 | 1 |     edge 1-2      | 1 | 0 | 1 |//// There are 6 possible orderings of coordinates a, b, and c:// 0 - abc// 1 - acb// 2 - bac// 3 - bca// 4 - cab// 5 - cba// All vertices must use the _same ordering_ for the edges to be rendered// correctly. So we compute a bitmask for each vertex, where the bit at// each position indicates whether that ordering works (i.e. doesn't// conflict with already-assigned coordinates) for that vertex.// Then we can find an ordering that works for all three vertices with a// bitwise AND.function computeOrderMask(outlineCoordinates, vertexIndex, a, b, c) {  const startIndex = vertexIndex * 3;  const first = outlineCoordinates[startIndex];  const second = outlineCoordinates[startIndex + 1];  const third = outlineCoordinates[startIndex + 2];  if (first === undefined) {    // If one coordinate is undefined, they all are, and all orderings are fine.    return 63; // 0b111111;  }  return (    ((first === a && second === b && third === c) << 0) +    ((first === a && second === c && third === b) << 1) +    ((first === b && second === a && third === c) << 2) +    ((first === b && second === c && third === a) << 3) +    ((first === c && second === a && third === b) << 4) +    ((first === c && second === b && third === a) << 5)  );}// popcount for integers 0-63, inclusive.// i.e. how many 1s are in the binary representation of the integer.function popcount0to63(value) {  return (    (value & 1) +    ((value >> 1) & 1) +    ((value >> 2) & 1) +    ((value >> 3) & 1) +    ((value >> 4) & 1) +    ((value >> 5) & 1)  );}function matchAndStoreCoordinates(  outlineCoordinates,  i0,  i1,  i2,  has01,  has12,  has20) {  const a0 = has20 ? 1.0 : 0.0;  const b0 = has01 ? 1.0 : 0.0;  const c0 = 0.0;  const i0Mask = computeOrderMask(outlineCoordinates, i0, a0, b0, c0);  if (i0Mask === 0) {    return i0;  }  const a1 = 0.0;  const b1 = has01 ? 1.0 : 0.0;  const c1 = has12 ? 1.0 : 0.0;  const i1Mask = computeOrderMask(outlineCoordinates, i1, a1, b1, c1);  if (i1Mask === 0) {    return i1;  }  const a2 = has20 ? 1.0 : 0.0;  const b2 = 0.0;  const c2 = has12 ? 1.0 : 0.0;  const i2Mask = computeOrderMask(outlineCoordinates, i2, a2, b2, c2);  if (i2Mask === 0) {    return i2;  }  const workingOrders = i0Mask & i1Mask & i2Mask;  let a, b, c;  if (workingOrders & (1 << 0)) {    // 0 - abc    a = 0;    b = 1;    c = 2;  } else if (workingOrders & (1 << 1)) {    // 1 - acb    a = 0;    c = 1;    b = 2;  } else if (workingOrders & (1 << 2)) {    // 2 - bac    b = 0;    a = 1;    c = 2;  } else if (workingOrders & (1 << 3)) {    // 3 - bca    b = 0;    c = 1;    a = 2;  } else if (workingOrders & (1 << 4)) {    // 4 - cab    c = 0;    a = 1;    b = 2;  } else if (workingOrders & (1 << 5)) {    // 5 - cba    c = 0;    b = 1;    a = 2;  } else {    // No ordering works.    // Report the most constrained vertex as unmatched so we copy that one.    const i0Popcount = popcount0to63(i0Mask);    const i1Popcount = popcount0to63(i1Mask);    const i2Popcount = popcount0to63(i2Mask);    if (i0Popcount < i1Popcount && i0Popcount < i2Popcount) {      return i0;    } else if (i1Popcount < i2Popcount) {      return i1;    }    return i2;  }  const i0Start = i0 * 3;  outlineCoordinates[i0Start + a] = a0;  outlineCoordinates[i0Start + b] = b0;  outlineCoordinates[i0Start + c] = c0;  const i1Start = i1 * 3;  outlineCoordinates[i1Start + a] = a1;  outlineCoordinates[i1Start + b] = b1;  outlineCoordinates[i1Start + c] = c1;  const i2Start = i2 * 3;  outlineCoordinates[i2Start + a] = a2;  outlineCoordinates[i2Start + b] = b2;  outlineCoordinates[i2Start + c] = c2;  return -1;}function isHighlighted(edges, i0, i1) {  const edgeSmallMultiplier = edges[0];  const index = Math.min(i0, i1) * edgeSmallMultiplier + Math.max(i0, i1);  // If i0 and i1 are both 0, then our index will be 0 and we'll end up  // accessing the edgeSmallMultiplier that we've sneakily squirreled away  // in index 0. But it makes no sense to have an edge between vertex 0 and  // itself, so for any edgeSmallMultiplier other than 1 we'll return the  // correct answer: false. If edgeSmallMultiplier is 1, that means there is  // only a single vertex, so no danger of forming a meaningful triangle  // with that.  return edges[index] === 1;}function createTexture(size) {  const texture = new Uint8Array(size);  texture[size - 1] = 192;  if (size === 8) {    texture[size - 1] = 96;  } else if (size === 4) {    texture[size - 1] = 48;  } else if (size === 2) {    texture[size - 1] = 24;  } else if (size === 1) {    texture[size - 1] = 12;  }  return texture;}function updateBufferViewsWithNewVertices(model, bufferViews) {  const gltf = model.gltf;  const loadResources = model._loadResources;  let i, j;  for (i = 0; i < bufferViews.length; ++i) {    const bufferView = bufferViews[i];    const vertexNumberingScope =      bufferView.extras._pipeline.vertexNumberingScope;    // Let the temporary data be garbage collected.    bufferView.extras._pipeline.vertexNumberingScope = undefined;    const newVertices = vertexNumberingScope.extraVertices;    const sourceData = loadResources.getBuffer(bufferView);    const byteStride = bufferView.byteStride || 4;    const newVerticesLength = newVertices.length;    const destData = new Uint8Array(      sourceData.byteLength + newVerticesLength * byteStride    );    // Copy the original vertices    destData.set(sourceData);    // Copy the vertices added for outlining    for (j = 0; j < newVerticesLength; ++j) {      const sourceIndex = newVertices[j] * byteStride;      const destIndex = sourceData.length + j * byteStride;      for (let k = 0; k < byteStride; ++k) {        destData[destIndex + k] = destData[sourceIndex + k];      }    }    // This bufferView is an independent buffer now. Update the model accordingly.    bufferView.byteOffset = 0;    bufferView.byteLength = destData.byteLength;    const bufferId =      gltf.buffers.push({        byteLength: destData.byteLength,        extras: {          _pipeline: {            source: destData.buffer,          },        },      }) - 1;    bufferView.buffer = bufferId;    loadResources.buffers[bufferId] = destData;    // Update the accessors to reflect the added vertices.    const accessors = vertexNumberingScope.accessors;    for (j = 0; j < accessors.length; ++j) {      const accessorId = accessors[j];      gltf.accessors[accessorId].count += newVerticesLength;    }    if (!vertexNumberingScope.createdOutlines) {      // Create the buffers, views, and accessors for the outline texture coordinates.      const outlineCoordinates = vertexNumberingScope.outlineCoordinates;      const outlineCoordinateBuffer = new Float32Array(outlineCoordinates);      const bufferIndex =        model.gltf.buffers.push({          byteLength: outlineCoordinateBuffer.byteLength,          extras: {            _pipeline: {              source: outlineCoordinateBuffer.buffer,            },          },        }) - 1;      loadResources.buffers[bufferIndex] = new Uint8Array(        outlineCoordinateBuffer.buffer,        0,        outlineCoordinateBuffer.byteLength      );      const bufferViewIndex =        model.gltf.bufferViews.push({          buffer: bufferIndex,          byteLength: outlineCoordinateBuffer.byteLength,          byteOffset: 0,          byteStride: 3 * Float32Array.BYTES_PER_ELEMENT,          target: 34962,        }) - 1;      const accessorIndex =        model.gltf.accessors.push({          bufferView: bufferViewIndex,          byteOffset: 0,          componentType: 5126,          count: outlineCoordinateBuffer.length / 3,          type: "VEC3",          min: [0.0, 0.0, 0.0],          max: [1.0, 1.0, 1.0],        }) - 1;      const primitives = vertexNumberingScope.primitives;      for (j = 0; j < primitives.length; ++j) {        primitives[j].attributes._OUTLINE_COORDINATES = accessorIndex;      }      loadResources.vertexBuffersToCreate.enqueue(bufferViewIndex);      vertexNumberingScope.createdOutlines = true;    }  }}function compactBuffers(model) {  const gltf = model.gltf;  const loadResources = model._loadResources;  let i;  for (i = 0; i < gltf.buffers.length; ++i) {    const buffer = gltf.buffers[i];    const bufferViewsUsingThisBuffer = gltf.bufferViews.filter(      usesBuffer.bind(undefined, i)    );    const newLength = bufferViewsUsingThisBuffer.reduce(function (      previous,      current    ) {      return previous + current.byteLength;    },    0);    if (newLength === buffer.byteLength) {      continue;    }    const newBuffer = new Uint8Array(newLength);    let offset = 0;    for (let j = 0; j < bufferViewsUsingThisBuffer.length; ++j) {      const bufferView = bufferViewsUsingThisBuffer[j];      const sourceData = loadResources.getBuffer(bufferView);      newBuffer.set(sourceData, offset);      bufferView.byteOffset = offset;      offset += sourceData.byteLength;    }    loadResources.buffers[i] = newBuffer;    buffer.extras._pipeline.source = newBuffer.buffer;    buffer.byteLength = newLength;  }}function usesBuffer(bufferId, bufferView) {  return bufferView.buffer === bufferId;}function getVertexNumberingScope(model, primitive) {  const attributes = primitive.attributes;  if (attributes === undefined) {    return undefined;  }  const gltf = model.gltf;  let vertexNumberingScope;  // Initialize common details for all bufferViews used by this primitive's vertices.  // All bufferViews used by this primitive must use a common vertex numbering scheme.  for (const semantic in attributes) {    if (!attributes.hasOwnProperty(semantic)) {      continue;    }    const accessorId = attributes[semantic];    const accessor = gltf.accessors[accessorId];    const bufferViewId = accessor.bufferView;    const bufferView = gltf.bufferViews[bufferViewId];    if (!defined(bufferView.extras)) {      bufferView.extras = {};    }    if (!defined(bufferView.extras._pipeline)) {      bufferView.extras._pipeline = {};    }    if (!defined(bufferView.extras._pipeline.vertexNumberingScope)) {      bufferView.extras._pipeline.vertexNumberingScope = vertexNumberingScope || {        // Each element in this array is:        // a) undefined, if the vertex at this index has no copies        // b) the index of the copy.        vertexCopies: [],        // Extra vertices appended after the ones originally included in the model.        // Each element is the index of the vertex that this one is a copy of.        extraVertices: [],        // The texture coordinates used for outlining, three floats per vertex.        outlineCoordinates: [],        // The IDs of accessors that use this vertex numbering.        accessors: [],        // The IDs of bufferViews that use this vertex numbering.        bufferViews: [],        // The primitives that use this vertex numbering.        primitives: [],        // True if the buffer for the outlines has already been created.        createdOutlines: false,      };    } else if (      vertexNumberingScope !== undefined &&      bufferView.extras._pipeline.vertexNumberingScope !== vertexNumberingScope    ) {      // Conflicting vertex numbering, let's give up.      return undefined;    }    vertexNumberingScope = bufferView.extras._pipeline.vertexNumberingScope;    if (vertexNumberingScope.bufferViews.indexOf(bufferView) < 0) {      vertexNumberingScope.bufferViews.push(bufferView);    }    if (vertexNumberingScope.accessors.indexOf(accessorId) < 0) {      vertexNumberingScope.accessors.push(accessorId);    }  }  vertexNumberingScope.primitives.push(primitive);  return vertexNumberingScope;}export default ModelOutlineLoader;
 |