You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

466 lines
12 KiB
JavaScript

import { defined, RuntimeError } from "../../Source/Cesium.js";
import findAccessorMinMax from "../../Source/ThirdParty/GltfPipeline/findAccessorMinMax.js";
/**
* A fluent interface for programmatically building a glTF.
* @alias GltfBuilder
* @constructor
* @private
*/
function GltfBuilder() {
this.gltf = {
asset: {
generator: "cesium-tests",
version: "2.0",
},
extensionsUsed: [],
accessors: [],
buffers: [],
bufferViews: [],
materials: [],
meshes: [],
nodes: [
{
mesh: 0,
},
],
scenes: [
{
nodes: [0],
},
],
scene: 0,
};
this.bufferBuilders = [];
}
/**
* Creates a new buffer.
* @param {string} [name] The name of the buffer.
* @returns {GltfBufferBuilder}
*/
GltfBuilder.prototype.buffer = function (name) {
var index =
this.gltf.buffers.push({
name: name,
byteLength: 0,
}) - 1;
var bufferBuilder = new GltfBufferBuilder(this, index);
this.bufferBuilders.push(bufferBuilder);
return bufferBuilder;
};
/**
* Creates a new mesh.
* @param {string} [name] The name of the mesh.
* @returns {GltfMeshBuilder}
*/
GltfBuilder.prototype.mesh = function (name) {
var index =
this.gltf.meshes.push({
name: name,
primitives: [],
}) - 1;
var meshBuilder = new GltfMeshBuilder(this, index);
return meshBuilder;
};
/**
* Creates a new material.
* @param {string} [name] The name of the material.
* @returns {GltfMaterialBuilder}
*/
GltfBuilder.prototype.material = function (name) {
var index =
this.gltf.materials.push({
name: name,
}) - 1;
var materialBuilder = new GltfMaterialBuilder(this, index);
return materialBuilder;
};
/**
* Gets the built glTF JSON from this builder. Calling this a second
* time will cause the glTF returned in the first call to be invalidated.
* Specifically, the `uri` properties of its buffers will no longer be
* resolvable.
*
* After calling this method, be sure to call {@link GltfBuilder#destroy}
* when done with the glTF, to a void leaking buffer memory.
*/
GltfBuilder.prototype.toGltf = function () {
for (var i = 0; i < this.bufferBuilders.length; ++i) {
var bufferBuilder = this.bufferBuilders[i];
var byteLength = bufferBuilder.viewBuilders.reduce(function (
byteLength,
viewBuilder
) {
return byteLength + viewBuilder.bufferView.byteLength;
},
0);
var buffer = new ArrayBuffer(byteLength);
var nextStart = 0;
for (var j = 0; j < bufferBuilder.viewBuilders.length; ++j) {
var viewBuilder = bufferBuilder.viewBuilders[j];
viewBuilder.bufferView.byteOffset = nextStart;
var destBuffer =
viewBuilder.componentType === 5126
? new Float32Array(buffer, nextStart, viewBuilder._data.length)
: new Uint16Array(buffer, nextStart, viewBuilder._data.length);
destBuffer.set(viewBuilder._data);
nextStart += viewBuilder.bufferView.byteLength;
}
bufferBuilder.buffer.byteLength = byteLength;
if (bufferBuilder.buffer.uri) {
URL.revokeObjectURL(bufferBuilder.buffer.uri);
}
bufferBuilder.buffer.uri = URL.createObjectURL(new Blob([buffer]));
bufferBuilder.buffer.extras = {
_pipeline: {
source: new Uint8Array(buffer, 0, buffer.byteLength),
},
};
}
var gltf = this.gltf;
gltf.accessors.forEach(function (accessor) {
var minMax = findAccessorMinMax(gltf, accessor);
accessor.min = minMax.min;
accessor.max = minMax.max;
});
this.bufferBuilders.forEach(function (builder) {
delete builder.buffer.extras;
});
return this.gltf;
};
/**
* Frees memory allocated for buffers in {@link GltfBuilder@toGltf}.
*/
GltfBuilder.prototype.destroy = function () {
this.bufferBuilders.forEach(function (bufferBuilder) {
URL.revokeObjectURL(bufferBuilder.buffer.uri);
bufferBuilder.buffer.uri = undefined;
});
};
/**
* A fluent interface for programmatically building a glTF material.
* @param {GltfBuilder} gltfBuilder The glTF builder.
* @param {number} materialIndex The index of this material within the glTF.
* @private
*/
function GltfMaterialBuilder(gltfBuilder, materialIndex) {
this.gltfBuilder = gltfBuilder;
this.materialIndex = materialIndex;
this.material = this.gltfBuilder.gltf.materials[materialIndex];
}
/**
* Defines the material using JSON.
* @param {*} json The JSON definition of the material.
* @returns {GltfMaterialBuilder}
*/
GltfMaterialBuilder.prototype.json = function (json) {
for (var property in json) {
if (!json.hasOwnProperty(property)) {
continue;
}
this.material[property] = json[property];
}
return this;
};
/**
* A fluent interface for building a glTF mesh.
* @param {GltfBuilder} gltfBuilder The glTF builder.
* @param {number} meshIndex The index of this mesh within the glTF.
* @private
*/
function GltfMeshBuilder(gltfBuilder, meshIndex) {
this.gltfBuilder = gltfBuilder;
this.meshIndex = meshIndex;
this.mesh = gltfBuilder.gltf.meshes[this.meshIndex];
}
/**
* Creates a new primitive within this mesh.
* @param {string} [name] The name of the primitive.
* @returns {GltfPrimitiveBuilder}
*/
GltfMeshBuilder.prototype.primitive = function (name) {
var index =
this.mesh.primitives.push({
name: name,
attributes: {},
}) - 1;
var meshBuilder = new GltfPrimitiveBuilder(this, index);
return meshBuilder;
};
/**
* A fluent interface for building a glTF primitive.
* @param {GltfMeshBuilder} gltfMeshBuilder The mesh builder.
* @param {number} primitiveIndex The index of this primitive within the mesh.
* @private
*/
function GltfPrimitiveBuilder(gltfMeshBuilder, primitiveIndex) {
this.gltfMeshBuilder = gltfMeshBuilder;
this.primitiveIndex = primitiveIndex;
this.primitive = this.gltfMeshBuilder.mesh.primitives[this.primitiveIndex];
}
/**
* Adds a new attribute to the primitive.
* @param {string} semantic The semantic of the attribute.
* @param {string} accessorName The name of the accessor referenced by this attribute.
* @returns {GltfPrimitiveBuilder}
*/
GltfPrimitiveBuilder.prototype.attribute = function (semantic, accessorName) {
var gltf = this.gltfMeshBuilder.gltfBuilder.gltf;
var accessorId = findAccessorByName(gltf, accessorName);
if (accessorId < 0) {
throw new RuntimeError("Accessor named " + accessorName + " not found.");
}
this.primitive.attributes[semantic] = accessorId;
return this;
};
/**
* Sets the name of the accessor providing this primitive's indices.
* @param {string} accessorName The name of the accessor providing the indices.
* @returns {GltfPrimitiveBuilder}
*/
GltfPrimitiveBuilder.prototype.indices = function (accessorName) {
var gltf = this.gltfMeshBuilder.gltfBuilder.gltf;
var accessorId = findAccessorByName(gltf, accessorName);
if (accessorId < 0) {
throw new RuntimeError("Accessor named " + accessorName + " not found.");
}
this.primitive.indices = accessorId;
return this;
};
/**
* Indicates that this primitive is TRIANGLES.
* @returns {GltfPrimitiveBuilder}
*/
GltfPrimitiveBuilder.prototype.triangles = function () {
this.primitive.mode = 4;
return this;
};
/**
* Sets the material applied to this primitive.
* @param {string} materialName The name of the material.
* @returns {GltfPrimitiveBuilder}
*/
GltfPrimitiveBuilder.prototype.material = function (materialName) {
var gltf = this.gltfMeshBuilder.gltfBuilder.gltf;
for (var i = 0; i < gltf.materials.length; ++i) {
if (gltf.materials[i].name === materialName) {
this.primitive.material = i;
return this;
}
}
throw new RuntimeError("Material named " + materialName + " not found.");
};
/**
* A fluent interface for building a glTF buffer.
* @param {GltfBuilder} gltfBuilder The glTF builder.
* @param {number} bufferIndex The index of this buffer in the glTF.
* @private
*/
function GltfBufferBuilder(gltfBuilder, bufferIndex) {
this.gltfBuilder = gltfBuilder;
this.bufferIndex = bufferIndex;
this.buffer = this.gltfBuilder.gltf.buffers[bufferIndex];
this.viewBuilders = [];
}
/**
* Creates a new vertex bufferView in this buffer.
* @param {string} name The name of the bufferView.
* @returns {GltfBufferViewBuilder}
*/
GltfBufferBuilder.prototype.vertexBuffer = function (name) {
var index =
this.gltfBuilder.gltf.bufferViews.push({
name: name,
buffer: this.bufferIndex,
byteLength: 0,
target: 34962,
}) - 1;
var viewBuilder = new GltfBufferViewBuilder(this, index, 5126);
this.viewBuilders.push(viewBuilder);
return viewBuilder;
};
/**
* Creates a new index bufferView in this buffer.
* @param {string} name The name of the bufferView.
* @returns {GltfBufferViewBuilder}
*/
GltfBufferBuilder.prototype.indexBuffer = function (name) {
var index =
this.gltfBuilder.gltf.bufferViews.push({
name: name,
buffer: this.bufferIndex,
byteLength: 0,
target: 34963,
}) - 1;
var viewBuilder = new GltfBufferViewBuilder(this, index, 5123);
this.viewBuilders.push(viewBuilder);
return viewBuilder;
};
/**
* A fluent interface for building a glTF bufferView.
* @param {GltfBufferBuilder} bufferBuilder The buffer builder.
* @param {GltfBufferViewBuilder} bufferViewIndex The bufferView builder.
* @param {number} componentType The glTF `componentType` of this bufferView.
* @private
*/
function GltfBufferViewBuilder(bufferBuilder, bufferViewIndex, componentType) {
this.bufferBuilder = bufferBuilder;
this.bufferViewIndex = bufferViewIndex;
this.bufferView = this.bufferBuilder.gltfBuilder.gltf.bufferViews[
this.bufferViewIndex
];
this.componentType = componentType;
this.elementStride = 0;
this.nextOffset = 0;
this._data = undefined;
}
Object.defineProperties(GltfBufferViewBuilder.prototype, {
/**
* Gets the number of bytes in each numeric element of this bufferView.
*/
elementByteLength: {
get: function () {
return this.componentType === 5126 ? 4 : 2;
},
},
});
/**
* Defines a `VEC3` element in this bufferView.
* @param {string} name The name of the accessor for this element.
* @returns {GltfBufferViewBuilder}
*/
GltfBufferViewBuilder.prototype.vec3 = function (name) {
var gltf = this.bufferBuilder.gltfBuilder.gltf;
gltf.accessors.push({
name: name,
bufferView: this.bufferViewIndex,
byteOffset: this.nextOffset,
componentType: this.componentType,
count: 0,
type: "VEC3",
});
this.elementStride += 3;
var newStride = 3 * this.elementByteLength;
if (!defined(this.bufferView.byteStride)) {
this.bufferView.byteStride = newStride;
} else {
this.bufferView.byteStride += newStride;
}
this.nextOffset += newStride;
return this;
};
/**
* Defines a `SCALAR` element in this bufferView.
* @param {string} name The name of the accessor for this element.
* @returns {GltfBufferViewBuilder}
*/
GltfBufferViewBuilder.prototype.scalar = function (name) {
var gltf = this.bufferBuilder.gltfBuilder.gltf;
gltf.accessors.push({
name: name,
bufferView: this.bufferViewIndex,
byteOffset: this.nextOffset,
componentType: this.componentType,
count: 0,
type: "SCALAR",
});
this.elementStride += 1;
var newStride = this.elementByteLength;
if (!defined(this.bufferView.byteStride)) {
this.bufferView.byteStride = newStride;
} else {
this.bufferView.byteStride += newStride;
}
this.nextOffset += newStride;
return this;
};
/**
* Provides this bufferView's data. All elements should be defined with
* {@link GltfBufferViewBuilder#vec3} and {@link GltfBufferViewBuilder#scalar}
* before calling this method.
* @param {Array|Float32Array|Uint16Array|Uint32Array} data The data.
* @returns {GltfBufferViewBuilder}
*/
GltfBufferViewBuilder.prototype.data = function (data) {
this.bufferView.byteLength = data.length * this.elementByteLength;
this._data = data;
var count = data.length / this.elementStride;
var gltf = this.bufferBuilder.gltfBuilder.gltf;
var bufferViewIndex = this.bufferViewIndex;
gltf.accessors
.filter(function (accessor) {
return accessor.bufferView === bufferViewIndex;
})
.forEach(function (accessor) {
accessor.count = count;
});
return this;
};
function findAccessorByName(gltf, accessorName) {
var accessors = gltf.accessors;
for (var i = 0; i < accessors.length; ++i) {
if (accessors[i].name === accessorName) {
return i;
}
}
return -1;
}
export default GltfBuilder;