@nakednous/host 0.0.4 → 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -5
- package/dist/index.js +466 -90
- package/dist/index.js.map +1 -1
- package/package.json +3 -5
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { WEBGL, cameraView, cameraProj, mat4Mul, mat4Invert, PLANE, createConstraint, WORLD, AXIS, DIAL, POINT, mapDirection, pixelRatio, rayHitSphere, unproject, SPHERE, dirFromAzEl, DIRECTION, mapLocation, MATRIX, PoseHelm, cameraToPose, EYE, SELF, qToMat4, cameraEye, cameraFromPose, CameraTrack, PoseTrack, qFromAxisAngle, SCREEN, cameraCopy, createCamera, cameraOrbit, cameraPan, cameraDolly } from '@nakednous/tree';
|
|
1
|
+
import { WEBGL, cameraView, cameraProj, mat4Mul, mat4Invert, PLANE, createConstraint, WORLD, AXIS, DIAL, POINT, mapDirection, pixelRatio, rayHitSphere, unproject, SPHERE, dirFromAzEl, DIRECTION, mapLocation, MATRIX, PoseHelm, cameraToPose, EYE, SELF, qToMat4, cameraEye, cameraFromPose, CameraTrack, PoseTrack, qFromAxisAngle, mat4ToTransform, meshBounds, meshFlatten, meshNormals, meshGroups, SCREEN, cameraCopy, createCamera, coastAlive, coastStep, cameraOrbit, cameraPan, cameraDolly } from '@nakednous/tree';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* @file The view bag — the matrices every pick, solve, label and orbit reads.
|
|
@@ -401,7 +401,7 @@ function createPlayers() {
|
|
|
401
401
|
return players;
|
|
402
402
|
}
|
|
403
403
|
|
|
404
|
-
const _now$
|
|
404
|
+
const _now$2 = () => (typeof performance !== 'undefined' && performance.now) ? performance.now() : Date.now();
|
|
405
405
|
|
|
406
406
|
/**
|
|
407
407
|
* Create a frame loop. `onFrame(dt)` runs once per animation frame with the
|
|
@@ -417,7 +417,7 @@ function createLoop(opts) {
|
|
|
417
417
|
const o = opts || {};
|
|
418
418
|
const raf = o.raf || ((cb) => requestAnimationFrame(cb));
|
|
419
419
|
const caf = o.caf || ((id) => cancelAnimationFrame(id));
|
|
420
|
-
const now = o.now || _now$
|
|
420
|
+
const now = o.now || _now$2;
|
|
421
421
|
const clamp = (o.clampMs ?? 50) / 1000;
|
|
422
422
|
let handle = null, last = 0;
|
|
423
423
|
const loop = {
|
|
@@ -1847,59 +1847,279 @@ function createGamepad(host, opts) {
|
|
|
1847
1847
|
return host.register(s);
|
|
1848
1848
|
}
|
|
1849
1849
|
|
|
1850
|
-
|
|
1851
|
-
|
|
1850
|
+
/**
|
|
1851
|
+
* @file OBJ text → the arrays shape a bridge's buffer upload takes.
|
|
1852
|
+
* @module host/obj
|
|
1853
|
+
* @license AGPL-3.0-only
|
|
1854
|
+
*
|
|
1855
|
+
* Positions, texture coordinates and normals. Faces are fan-triangulated — a
|
|
1856
|
+
* quad as 0 1 2 · 2 3 0 — and each distinct position / uv / normal triple
|
|
1857
|
+
* becomes one vertex, in order of first use. Indices are 1-based, or negative
|
|
1858
|
+
* counting back from the last declared element. Groups, objects, smoothing and
|
|
1859
|
+
* materials are ignored.
|
|
1860
|
+
*/
|
|
1861
|
+
|
|
1862
|
+
|
|
1863
|
+
const _index = (s, count) => {
|
|
1864
|
+
const i = parseInt(s, 10);
|
|
1865
|
+
return i < 0 ? count + i : i - 1;
|
|
1866
|
+
};
|
|
1867
|
+
|
|
1868
|
+
/**
|
|
1869
|
+
* Parse OBJ text into `{ position, normal?, texcoord?, indices }`; normal and
|
|
1870
|
+
* texcoord only when some face vertex names one.
|
|
1871
|
+
* @param {string} text
|
|
1872
|
+
* @returns {{ position:{numComponents:number,data:Float32Array},
|
|
1873
|
+
* normal?:{numComponents:number,data:Float32Array},
|
|
1874
|
+
* texcoord?:{numComponents:number,data:Float32Array},
|
|
1875
|
+
* indices:{numComponents:number,data:Uint32Array} }}
|
|
1876
|
+
*/
|
|
1877
|
+
function parseObj(text) {
|
|
1878
|
+
const v = [], vt = [], vn = [];
|
|
1879
|
+
const position = [], texcoord = [], normal = [], indices = [];
|
|
1880
|
+
const seen = new Map();
|
|
1881
|
+
let uvs = false, normals = false;
|
|
1882
|
+
|
|
1883
|
+
const vertex = (token) => {
|
|
1884
|
+
const [ps, ts, ns] = token.split('/');
|
|
1885
|
+
const p = _index(ps, v.length / 3);
|
|
1886
|
+
const t = ts ? _index(ts, vt.length / 2) : -1;
|
|
1887
|
+
const n = ns ? _index(ns, vn.length / 3) : -1;
|
|
1888
|
+
const key = p + '/' + t + '/' + n;
|
|
1889
|
+
let id = seen.get(key);
|
|
1890
|
+
if (id !== undefined) return id;
|
|
1891
|
+
position.push(v[3 * p], v[3 * p + 1], v[3 * p + 2]);
|
|
1892
|
+
if (t >= 0) { texcoord.push(vt[2 * t], vt[2 * t + 1]); uvs = true; } else texcoord.push(0, 0);
|
|
1893
|
+
if (n >= 0) { normal.push(vn[3 * n], vn[3 * n + 1], vn[3 * n + 2]); normals = true; } else normal.push(0, 0, 0);
|
|
1894
|
+
id = position.length / 3 - 1;
|
|
1895
|
+
seen.set(key, id);
|
|
1896
|
+
return id;
|
|
1897
|
+
};
|
|
1898
|
+
|
|
1899
|
+
for (const raw of text.split('\n')) {
|
|
1900
|
+
const line = raw.trim();
|
|
1901
|
+
if (!line || line[0] === '#') continue;
|
|
1902
|
+
const parts = line.split(/\s+/);
|
|
1903
|
+
switch (parts[0]) {
|
|
1904
|
+
case 'v': v.push(+parts[1], +parts[2], +parts[3]); break;
|
|
1905
|
+
case 'vt': vt.push(+parts[1], +(parts[2] ?? 0)); break;
|
|
1906
|
+
case 'vn': vn.push(+parts[1], +parts[2], +parts[3]); break;
|
|
1907
|
+
case 'f': {
|
|
1908
|
+
const ids = parts.slice(1).map(vertex);
|
|
1909
|
+
if (ids.length === 4) indices.push(ids[0], ids[1], ids[2], ids[2], ids[3], ids[0]);
|
|
1910
|
+
else for (let i = 1; i + 1 < ids.length; i++) indices.push(ids[0], ids[i], ids[i + 1]);
|
|
1911
|
+
break;
|
|
1912
|
+
}
|
|
1913
|
+
}
|
|
1914
|
+
}
|
|
1915
|
+
|
|
1916
|
+
const out = {
|
|
1917
|
+
position: { numComponents: 3, data: new Float32Array(position) },
|
|
1918
|
+
indices: { numComponents: 3, data: new Uint32Array(indices) },
|
|
1919
|
+
};
|
|
1920
|
+
if (normals) out.normal = { numComponents: 3, data: new Float32Array(normal) };
|
|
1921
|
+
if (uvs) out.texcoord = { numComponents: 2, data: new Float32Array(texcoord) };
|
|
1922
|
+
return out;
|
|
1852
1923
|
}
|
|
1853
1924
|
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
/*! exports provided: TYPES, DuplicateAttributeException, Attribute, Layout */function(module,__webpack_exports__,__webpack_require__){eval('__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "TYPES", function() { return TYPES; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "DuplicateAttributeException", function() { return DuplicateAttributeException; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Attribute", function() { return Attribute; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Layout", function() { return Layout; });\nvar TYPES;\n(function (TYPES) {\n TYPES["BYTE"] = "BYTE";\n TYPES["UNSIGNED_BYTE"] = "UNSIGNED_BYTE";\n TYPES["SHORT"] = "SHORT";\n TYPES["UNSIGNED_SHORT"] = "UNSIGNED_SHORT";\n TYPES["FLOAT"] = "FLOAT";\n})(TYPES || (TYPES = {}));\n/**\n * An exception for when two or more of the same attributes are found in the\n * same layout.\n * @private\n */\nclass DuplicateAttributeException extends Error {\n /**\n * Create a DuplicateAttributeException\n * @param {Attribute} attribute - The attribute that was found more than\n * once in the {@link Layout}\n */\n constructor(attribute) {\n super(`found duplicate attribute: ${attribute.key}`);\n }\n}\n/**\n * Represents how a vertex attribute should be packed into an buffer.\n * @private\n */\nclass Attribute {\n /**\n * Create an attribute. Do not call this directly, use the predefined\n * constants.\n * @param {string} key - The name of this attribute as if it were a key in\n * an Object. Use the camel case version of the upper snake case\n * const name.\n * @param {number} size - The number of components per vertex attribute.\n * Must be 1, 2, 3, or 4.\n * @param {string} type - The data type of each component for this\n * attribute. Possible values:<br/>\n * "BYTE": signed 8-bit integer, with values in [-128, 127]<br/>\n * "SHORT": signed 16-bit integer, with values in\n * [-32768, 32767]<br/>\n * "UNSIGNED_BYTE": unsigned 8-bit integer, with values in\n * [0, 255]<br/>\n * "UNSIGNED_SHORT": unsigned 16-bit integer, with values in\n * [0, 65535]<br/>\n * "FLOAT": 32-bit floating point number\n * @param {boolean} normalized - Whether integer data values should be\n * normalized when being casted to a float.<br/>\n * If true, signed integers are normalized to [-1, 1].<br/>\n * If true, unsigned integers are normalized to [0, 1].<br/>\n * For type "FLOAT", this parameter has no effect.\n */\n constructor(key, size, type, normalized = false) {\n this.key = key;\n this.size = size;\n this.type = type;\n this.normalized = normalized;\n switch (type) {\n case "BYTE":\n case "UNSIGNED_BYTE":\n this.sizeOfType = 1;\n break;\n case "SHORT":\n case "UNSIGNED_SHORT":\n this.sizeOfType = 2;\n break;\n case "FLOAT":\n this.sizeOfType = 4;\n break;\n default:\n throw new Error(`Unknown gl type: ${type}`);\n }\n this.sizeInBytes = this.sizeOfType * size;\n }\n}\n/**\n * A class to represent the memory layout for a vertex attribute array. Used by\n * {@link Mesh}\'s TBD(...) method to generate a packed array from mesh data.\n * <p>\n * Layout can sort of be thought of as a C-style struct declaration.\n * {@link Mesh}\'s TBD(...) method will use the {@link Layout} instance to\n * pack an array in the given attribute order.\n * <p>\n * Layout also is very helpful when calling a WebGL context\'s\n * <code>vertexAttribPointer</code> method. If you\'ve created a buffer using\n * a Layout instance, then the same Layout instance can be used to determine\n * the size, type, normalized, stride, and offset parameters for\n * <code>vertexAttribPointer</code>.\n * <p>\n * For example:\n * <pre><code>\n *\n * const index = glctx.getAttribLocation(shaderProgram, "pos");\n * glctx.vertexAttribPointer(\n * layout.position.size,\n * glctx[layout.position.type],\n * layout.position.normalized,\n * layout.position.stride,\n * layout.position.offset);\n * </code></pre>\n * @see {@link Mesh}\n */\nclass Layout {\n /**\n * Create a Layout object. This constructor will throw if any duplicate\n * attributes are given.\n * @param {Array} ...attributes - An ordered list of attributes that\n * describe the desired memory layout for each vertex attribute.\n * <p>\n *\n * @see {@link Mesh}\n */\n constructor(...attributes) {\n this.attributes = attributes;\n this.attributeMap = {};\n let offset = 0;\n let maxStrideMultiple = 0;\n for (const attribute of attributes) {\n if (this.attributeMap[attribute.key]) {\n throw new DuplicateAttributeException(attribute);\n }\n // Add padding to satisfy WebGL\'s requirement that all\n // vertexAttribPointer calls have an offset that is a multiple of\n // the type size.\n if (offset % attribute.sizeOfType !== 0) {\n offset += attribute.sizeOfType - (offset % attribute.sizeOfType);\n console.warn("Layout requires padding before " + attribute.key + " attribute");\n }\n this.attributeMap[attribute.key] = {\n attribute: attribute,\n size: attribute.size,\n type: attribute.type,\n normalized: attribute.normalized,\n offset: offset,\n };\n offset += attribute.sizeInBytes;\n maxStrideMultiple = Math.max(maxStrideMultiple, attribute.sizeOfType);\n }\n // Add padding to the end to satisfy WebGL\'s requirement that all\n // vertexAttribPointer calls have a stride that is a multiple of the\n // type size. Because we\'re putting differently sized attributes into\n // the same buffer, it must be padded to a multiple of the largest\n // type size.\n if (offset % maxStrideMultiple !== 0) {\n offset += maxStrideMultiple - (offset % maxStrideMultiple);\n console.warn("Layout requires padding at the back");\n }\n this.stride = offset;\n for (const attribute of attributes) {\n this.attributeMap[attribute.key].stride = this.stride;\n }\n }\n}\n// Geometry attributes\n/**\n * Attribute layout to pack a vertex\'s x, y, & z as floats\n *\n * @see {@link Layout}\n */\nLayout.POSITION = new Attribute("position", 3, TYPES.FLOAT);\n/**\n * Attribute layout to pack a vertex\'s normal\'s x, y, & z as floats\n *\n * @see {@link Layout}\n */\nLayout.NORMAL = new Attribute("normal", 3, TYPES.FLOAT);\n/**\n * Attribute layout to pack a vertex\'s normal\'s x, y, & z as floats.\n * <p>\n * This value will be computed on-the-fly based on the texture coordinates.\n * If no texture coordinates are available, the generated value will default to\n * 0, 0, 0.\n *\n * @see {@link Layout}\n */\nLayout.TANGENT = new Attribute("tangent", 3, TYPES.FLOAT);\n/**\n * Attribute layout to pack a vertex\'s normal\'s bitangent x, y, & z as floats.\n * <p>\n * This value will be computed on-the-fly based on the texture coordinates.\n * If no texture coordinates are available, the generated value will default to\n * 0, 0, 0.\n * @see {@link Layout}\n */\nLayout.BITANGENT = new Attribute("bitangent", 3, TYPES.FLOAT);\n/**\n * Attribute layout to pack a vertex\'s texture coordinates\' u & v as floats\n *\n * @see {@link Layout}\n */\nLayout.UV = new Attribute("uv", 2, TYPES.FLOAT);\n// Material attributes\n/**\n * Attribute layout to pack an unsigned short to be interpreted as a the index\n * into a {@link Mesh}\'s materials list.\n * <p>\n * The intention of this value is to send all of the {@link Mesh}\'s materials\n * into multiple shader uniforms and then reference the current one by this\n * vertex attribute.\n * <p>\n * example glsl code:\n *\n * <pre><code>\n * // this is bound using MATERIAL_INDEX\n * attribute int materialIndex;\n *\n * struct Material {\n * vec3 diffuse;\n * vec3 specular;\n * vec3 specularExponent;\n * };\n *\n * uniform Material materials[MAX_MATERIALS];\n *\n * // ...\n *\n * vec3 diffuse = materials[materialIndex];\n *\n * </code></pre>\n * TODO: More description & test to make sure subscripting by attributes even\n * works for webgl\n *\n * @see {@link Layout}\n */\nLayout.MATERIAL_INDEX = new Attribute("materialIndex", 1, TYPES.SHORT);\nLayout.MATERIAL_ENABLED = new Attribute("materialEnabled", 1, TYPES.UNSIGNED_SHORT);\nLayout.AMBIENT = new Attribute("ambient", 3, TYPES.FLOAT);\nLayout.DIFFUSE = new Attribute("diffuse", 3, TYPES.FLOAT);\nLayout.SPECULAR = new Attribute("specular", 3, TYPES.FLOAT);\nLayout.SPECULAR_EXPONENT = new Attribute("specularExponent", 3, TYPES.FLOAT);\nLayout.EMISSIVE = new Attribute("emissive", 3, TYPES.FLOAT);\nLayout.TRANSMISSION_FILTER = new Attribute("transmissionFilter", 3, TYPES.FLOAT);\nLayout.DISSOLVE = new Attribute("dissolve", 1, TYPES.FLOAT);\nLayout.ILLUMINATION = new Attribute("illumination", 1, TYPES.UNSIGNED_SHORT);\nLayout.REFRACTION_INDEX = new Attribute("refractionIndex", 1, TYPES.FLOAT);\nLayout.SHARPNESS = new Attribute("sharpness", 1, TYPES.FLOAT);\nLayout.MAP_DIFFUSE = new Attribute("mapDiffuse", 1, TYPES.SHORT);\nLayout.MAP_AMBIENT = new Attribute("mapAmbient", 1, TYPES.SHORT);\nLayout.MAP_SPECULAR = new Attribute("mapSpecular", 1, TYPES.SHORT);\nLayout.MAP_SPECULAR_EXPONENT = new Attribute("mapSpecularExponent", 1, TYPES.SHORT);\nLayout.MAP_DISSOLVE = new Attribute("mapDissolve", 1, TYPES.SHORT);\nLayout.ANTI_ALIASING = new Attribute("antiAliasing", 1, TYPES.UNSIGNED_SHORT);\nLayout.MAP_BUMP = new Attribute("mapBump", 1, TYPES.SHORT);\nLayout.MAP_DISPLACEMENT = new Attribute("mapDisplacement", 1, TYPES.SHORT);\nLayout.MAP_DECAL = new Attribute("mapDecal", 1, TYPES.SHORT);\nLayout.MAP_EMISSIVE = new Attribute("mapEmissive", 1, TYPES.SHORT);\n\n\n//# sourceURL=webpack:///./src/layout.ts?');},"./src/material.ts":
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
/*! exports provided: Material, MaterialLibrary */function(module,__webpack_exports__,__webpack_require__){eval('__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Material", function() { return Material; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "MaterialLibrary", function() { return MaterialLibrary; });\n/**\n * The Material class.\n */\nclass Material {\n constructor(name) {\n this.name = name;\n /**\n * Constructor\n * @param {String} name the unique name of the material\n */\n // The values for the following attibutes\n // are an array of R, G, B normalized values.\n // Ka - Ambient Reflectivity\n this.ambient = [0, 0, 0];\n // Kd - Defuse Reflectivity\n this.diffuse = [0, 0, 0];\n // Ks\n this.specular = [0, 0, 0];\n // Ke\n this.emissive = [0, 0, 0];\n // Tf\n this.transmissionFilter = [0, 0, 0];\n // d\n this.dissolve = 0;\n // valid range is between 0 and 1000\n this.specularExponent = 0;\n // either d or Tr; valid values are normalized\n this.transparency = 0;\n // illum - the enum of the illumination model to use\n this.illumination = 0;\n // Ni - Set to "normal" (air).\n this.refractionIndex = 1;\n // sharpness\n this.sharpness = 0;\n // map_Kd\n this.mapDiffuse = emptyTextureOptions();\n // map_Ka\n this.mapAmbient = emptyTextureOptions();\n // map_Ks\n this.mapSpecular = emptyTextureOptions();\n // map_Ns\n this.mapSpecularExponent = emptyTextureOptions();\n // map_d\n this.mapDissolve = emptyTextureOptions();\n // map_aat\n this.antiAliasing = false;\n // map_bump or bump\n this.mapBump = emptyTextureOptions();\n // disp\n this.mapDisplacement = emptyTextureOptions();\n // decal\n this.mapDecal = emptyTextureOptions();\n // map_Ke\n this.mapEmissive = emptyTextureOptions();\n // refl - when the reflection type is a cube, there will be multiple refl\n // statements for each side of the cube. If it\'s a spherical\n // reflection, there should only ever be one.\n this.mapReflections = [];\n }\n}\nconst SENTINEL_MATERIAL = new Material("sentinel");\n/**\n * https://en.wikipedia.org/wiki/Wavefront_.obj_file\n * http://paulbourke.net/dataformats/mtl/\n */\nclass MaterialLibrary {\n constructor(data) {\n this.data = data;\n /**\n * Constructs the Material Parser\n * @param mtlData the MTL file contents\n */\n this.currentMaterial = SENTINEL_MATERIAL;\n this.materials = {};\n this.parse();\n }\n /* eslint-disable camelcase */\n /* the function names here disobey camelCase conventions\n to make parsing/routing easier. see the parse function\n documentation for more information. */\n /**\n * Creates a new Material object and adds to the registry.\n * @param tokens the tokens associated with the directive\n */\n parse_newmtl(tokens) {\n const name = tokens[0];\n // console.info(\'Parsing new Material:\', name);\n this.currentMaterial = new Material(name);\n this.materials[name] = this.currentMaterial;\n }\n /**\n * See the documenation for parse_Ka below for a better understanding.\n *\n * Given a list of possible color tokens, returns an array of R, G, and B\n * color values.\n *\n * @param tokens the tokens associated with the directive\n * @return {*} a 3 element array containing the R, G, and B values\n * of the color.\n */\n parseColor(tokens) {\n if (tokens[0] == "spectral") {\n throw new Error("The MTL parser does not support spectral curve files. You will " +\n "need to convert the MTL colors to either RGB or CIEXYZ.");\n }\n if (tokens[0] == "xyz") {\n throw new Error("The MTL parser does not currently support XYZ colors. Either convert the " +\n "XYZ values to RGB or create an issue to add support for XYZ");\n }\n // from my understanding of the spec, RGB values at this point\n // will either be 3 floats or exactly 1 float, so that\'s the check\n // that i\'m going to perform here\n if (tokens.length == 3) {\n const [x, y, z] = tokens;\n return [parseFloat(x), parseFloat(y), parseFloat(z)];\n }\n // Since tokens at this point has a length of 3, we\'re going to assume\n // it\'s exactly 1, skipping the check for 2.\n const value = parseFloat(tokens[0]);\n // in this case, all values are equivalent\n return [value, value, value];\n }\n /**\n * Parse the ambient reflectivity\n *\n * A Ka directive can take one of three forms:\n * - Ka r g b\n * - Ka spectral file.rfl\n * - Ka xyz x y z\n * These three forms are mutually exclusive in that only one\n * declaration can exist per material. It is considered a syntax\n * error otherwise.\n *\n * The "Ka" form specifies the ambient reflectivity using RGB values.\n * The "g" and "b" values are optional. If only the "r" value is\n * specified, then the "g" and "b" values are assigned the value of\n * "r". Values are normally in the range 0.0 to 1.0. Values outside\n * of this range increase or decrease the reflectivity accordingly.\n *\n * The "Ka spectral" form specifies the ambient reflectivity using a\n * spectral curve. "file.rfl" is the name of the ".rfl" file containing\n * the curve data. "factor" is an optional argument which is a multiplier\n * for the values in the .rfl file and defaults to 1.0 if not specified.\n *\n * The "Ka xyz" form specifies the ambient reflectivity using CIEXYZ values.\n * "x y z" are the values of the CIEXYZ color space. The "y" and "z" arguments\n * are optional and take on the value of the "x" component if only "x" is\n * specified. The "x y z" values are normally in the range of 0.0 to 1.0 and\n * increase or decrease ambient reflectivity accordingly outside of that\n * range.\n *\n * @param tokens the tokens associated with the directive\n */\n parse_Ka(tokens) {\n this.currentMaterial.ambient = this.parseColor(tokens);\n }\n /**\n * Diffuse Reflectivity\n *\n * Similar to the Ka directive. Simply replace "Ka" with "Kd" and the rules\n * are the same\n *\n * @param tokens the tokens associated with the directive\n */\n parse_Kd(tokens) {\n this.currentMaterial.diffuse = this.parseColor(tokens);\n }\n /**\n * Spectral Reflectivity\n *\n * Similar to the Ka directive. Simply replace "Ks" with "Kd" and the rules\n * are the same\n *\n * @param tokens the tokens associated with the directive\n */\n parse_Ks(tokens) {\n this.currentMaterial.specular = this.parseColor(tokens);\n }\n /**\n * Emissive\n *\n * The amount and color of light emitted by the object.\n *\n * @param tokens the tokens associated with the directive\n */\n parse_Ke(tokens) {\n this.currentMaterial.emissive = this.parseColor(tokens);\n }\n /**\n * Transmission Filter\n *\n * Any light passing through the object is filtered by the transmission\n * filter, which only allows specific colors to pass through. For example, Tf\n * 0 1 0 allows all of the green to pass through and filters out all of the\n * red and blue.\n *\n * Similar to the Ka directive. Simply replace "Ks" with "Tf" and the rules\n * are the same\n *\n * @param tokens the tokens associated with the directive\n */\n parse_Tf(tokens) {\n this.currentMaterial.transmissionFilter = this.parseColor(tokens);\n }\n /**\n * Specifies the dissolve for the current material.\n *\n * Statement: d [-halo] `factor`\n *\n * Example: "d 0.5"\n *\n * The factor is the amount this material dissolves into the background. A\n * factor of 1.0 is fully opaque. This is the default when a new material is\n * created. A factor of 0.0 is fully dissolved (completely transparent).\n *\n * Unlike a real transparent material, the dissolve does not depend upon\n * material thickness nor does it have any spectral character. Dissolve works\n * on all illumination models.\n *\n * The dissolve statement allows for an optional "-halo" flag which indicates\n * that a dissolve is dependent on the surface orientation relative to the\n * viewer. For example, a sphere with the following dissolve, "d -halo 0.0",\n * will be fully dissolved at its center and will appear gradually more opaque\n * toward its edge.\n *\n * "factor" is the minimum amount of dissolve applied to the material. The\n * amount of dissolve will vary between 1.0 (fully opaque) and the specified\n * "factor". The formula is:\n *\n * dissolve = 1.0 - (N*v)(1.0-factor)\n *\n * @param tokens the tokens associated with the directive\n */\n parse_d(tokens) {\n // this ignores the -halo option as I can\'t find any documentation on what\n // it\'s supposed to be.\n this.currentMaterial.dissolve = parseFloat(tokens.pop() || "0");\n }\n /**\n * The "illum" statement specifies the illumination model to use in the\n * material. Illumination models are mathematical equations that represent\n * various material lighting and shading effects.\n *\n * The illumination number can be a number from 0 to 10. The following are\n * the list of illumination enumerations and their summaries:\n * 0. Color on and Ambient off\n * 1. Color on and Ambient on\n * 2. Highlight on\n * 3. Reflection on and Ray trace on\n * 4. Transparency: Glass on, Reflection: Ray trace on\n * 5. Reflection: Fresnel on and Ray trace on\n * 6. Transparency: Refraction on, Reflection: Fresnel off and Ray trace on\n * 7. Transparency: Refraction on, Reflection: Fresnel on and Ray trace on\n * 8. Reflection on and Ray trace off\n * 9. Transparency: Glass on, Reflection: Ray trace off\n * 10. Casts shadows onto invisible surfaces\n *\n * Example: "illum 2" to specify the "Highlight on" model\n *\n * @param tokens the tokens associated with the directive\n */\n parse_illum(tokens) {\n this.currentMaterial.illumination = parseInt(tokens[0]);\n }\n /**\n * Optical Density (AKA Index of Refraction)\n *\n * Statement: Ni `index`\n *\n * Example: Ni 1.0\n *\n * Specifies the optical density for the surface. `index` is the value\n * for the optical density. The values can range from 0.001 to 10. A value of\n * 1.0 means that light does not bend as it passes through an object.\n * Increasing the optical_density increases the amount of bending. Glass has\n * an index of refraction of about 1.5. Values of less than 1.0 produce\n * bizarre results and are not recommended\n *\n * @param tokens the tokens associated with the directive\n */\n parse_Ni(tokens) {\n this.currentMaterial.refractionIndex = parseFloat(tokens[0]);\n }\n /**\n * Specifies the specular exponent for the current material. This defines the\n * focus of the specular highlight.\n *\n * Statement: Ns `exponent`\n *\n * Example: "Ns 250"\n *\n * `exponent` is the value for the specular exponent. A high exponent results\n * in a tight, concentrated highlight. Ns Values normally range from 0 to\n * 1000.\n *\n * @param tokens the tokens associated with the directive\n */\n parse_Ns(tokens) {\n this.currentMaterial.specularExponent = parseInt(tokens[0]);\n }\n /**\n * Specifies the sharpness of the reflections from the local reflection map.\n *\n * Statement: sharpness `value`\n *\n * Example: "sharpness 100"\n *\n * If a material does not have a local reflection map defined in its material\n * defintions, sharpness will apply to the global reflection map defined in\n * PreView.\n *\n * `value` can be a number from 0 to 1000. The default is 60. A high value\n * results in a clear reflection of objects in the reflection map.\n *\n * Tip: sharpness values greater than 100 introduce aliasing effects in\n * flat surfaces that are viewed at a sharp angle.\n *\n * @param tokens the tokens associated with the directive\n */\n parse_sharpness(tokens) {\n this.currentMaterial.sharpness = parseInt(tokens[0]);\n }\n /**\n * Parses the -cc flag and updates the options object with the values.\n *\n * @param values the values passed to the -cc flag\n * @param options the Object of all image options\n */\n parse_cc(values, options) {\n options.colorCorrection = values[0] == "on";\n }\n /**\n * Parses the -blendu flag and updates the options object with the values.\n *\n * @param values the values passed to the -blendu flag\n * @param options the Object of all image options\n */\n parse_blendu(values, options) {\n options.horizontalBlending = values[0] == "on";\n }\n /**\n * Parses the -blendv flag and updates the options object with the values.\n *\n * @param values the values passed to the -blendv flag\n * @param options the Object of all image options\n */\n parse_blendv(values, options) {\n options.verticalBlending = values[0] == "on";\n }\n /**\n * Parses the -boost flag and updates the options object with the values.\n *\n * @param values the values passed to the -boost flag\n * @param options the Object of all image options\n */\n parse_boost(values, options) {\n options.boostMipMapSharpness = parseFloat(values[0]);\n }\n /**\n * Parses the -mm flag and updates the options object with the values.\n *\n * @param values the values passed to the -mm flag\n * @param options the Object of all image options\n */\n parse_mm(values, options) {\n options.modifyTextureMap.brightness = parseFloat(values[0]);\n options.modifyTextureMap.contrast = parseFloat(values[1]);\n }\n /**\n * Parses and sets the -o, -s, and -t u, v, and w values\n *\n * @param values the values passed to the -o, -s, -t flag\n * @param {Object} option the Object of either the -o, -s, -t option\n * @param {Integer} defaultValue the Object of all image options\n */\n parse_ost(values, option, defaultValue) {\n while (values.length < 3) {\n values.push(defaultValue.toString());\n }\n option.u = parseFloat(values[0]);\n option.v = parseFloat(values[1]);\n option.w = parseFloat(values[2]);\n }\n /**\n * Parses the -o flag and updates the options object with the values.\n *\n * @param values the values passed to the -o flag\n * @param options the Object of all image options\n */\n parse_o(values, options) {\n this.parse_ost(values, options.offset, 0);\n }\n /**\n * Parses the -s flag and updates the options object with the values.\n *\n * @param values the values passed to the -s flag\n * @param options the Object of all image options\n */\n parse_s(values, options) {\n this.parse_ost(values, options.scale, 1);\n }\n /**\n * Parses the -t flag and updates the options object with the values.\n *\n * @param values the values passed to the -t flag\n * @param options the Object of all image options\n */\n parse_t(values, options) {\n this.parse_ost(values, options.turbulence, 0);\n }\n /**\n * Parses the -texres flag and updates the options object with the values.\n *\n * @param values the values passed to the -texres flag\n * @param options the Object of all image options\n */\n parse_texres(values, options) {\n options.textureResolution = parseFloat(values[0]);\n }\n /**\n * Parses the -clamp flag and updates the options object with the values.\n *\n * @param values the values passed to the -clamp flag\n * @param options the Object of all image options\n */\n parse_clamp(values, options) {\n options.clamp = values[0] == "on";\n }\n /**\n * Parses the -bm flag and updates the options object with the values.\n *\n * @param values the values passed to the -bm flag\n * @param options the Object of all image options\n */\n parse_bm(values, options) {\n options.bumpMultiplier = parseFloat(values[0]);\n }\n /**\n * Parses the -imfchan flag and updates the options object with the values.\n *\n * @param values the values passed to the -imfchan flag\n * @param options the Object of all image options\n */\n parse_imfchan(values, options) {\n options.imfChan = values[0];\n }\n /**\n * This only exists for relection maps and denotes the type of reflection.\n *\n * @param values the values passed to the -type flag\n * @param options the Object of all image options\n */\n parse_type(values, options) {\n options.reflectionType = values[0];\n }\n /**\n * Parses the texture\'s options and returns an options object with the info\n *\n * @param tokens all of the option tokens to pass to the texture\n * @return {Object} a complete object of objects to apply to the texture\n */\n parseOptions(tokens) {\n const options = emptyTextureOptions();\n let option;\n let values;\n const optionsToValues = {};\n tokens.reverse();\n while (tokens.length) {\n // token is guaranteed to exists here, hence the explicit "as"\n const token = tokens.pop();\n if (token.startsWith("-")) {\n option = token.substr(1);\n optionsToValues[option] = [];\n }\n else if (option) {\n optionsToValues[option].push(token);\n }\n }\n for (option in optionsToValues) {\n if (!optionsToValues.hasOwnProperty(option)) {\n continue;\n }\n values = optionsToValues[option];\n const optionMethod = this[`parse_${option}`];\n if (optionMethod) {\n optionMethod.bind(this)(values, options);\n }\n }\n return options;\n }\n /**\n * Parses the given texture map line.\n *\n * @param tokens all of the tokens representing the texture\n * @return a complete object of objects to apply to the texture\n */\n parseMap(tokens) {\n // according to wikipedia:\n // (https://en.wikipedia.org/wiki/Wavefront_.obj_file#Vendor_specific_alterations)\n // there is at least one vendor that places the filename before the options\n // rather than after (which is to spec). All options start with a \'-\'\n // so if the first token doesn\'t start with a \'-\', we\'re going to assume\n // it\'s the name of the map file.\n let optionsString;\n let filename = "";\n if (!tokens[0].startsWith("-")) {\n [filename, ...optionsString] = tokens;\n }\n else {\n filename = tokens.pop();\n optionsString = tokens;\n }\n const options = this.parseOptions(optionsString);\n options.filename = filename.replace(/\\\\/g, "/");\n return options;\n }\n /**\n * Parses the ambient map.\n *\n * @param tokens list of tokens for the map_Ka direcive\n */\n parse_map_Ka(tokens) {\n this.currentMaterial.mapAmbient = this.parseMap(tokens);\n }\n /**\n * Parses the diffuse map.\n *\n * @param tokens list of tokens for the map_Kd direcive\n */\n parse_map_Kd(tokens) {\n this.currentMaterial.mapDiffuse = this.parseMap(tokens);\n }\n /**\n * Parses the specular map.\n *\n * @param tokens list of tokens for the map_Ks direcive\n */\n parse_map_Ks(tokens) {\n this.currentMaterial.mapSpecular = this.parseMap(tokens);\n }\n /**\n * Parses the emissive map.\n *\n * @param tokens list of tokens for the map_Ke direcive\n */\n parse_map_Ke(tokens) {\n this.currentMaterial.mapEmissive = this.parseMap(tokens);\n }\n /**\n * Parses the specular exponent map.\n *\n * @param tokens list of tokens for the map_Ns direcive\n */\n parse_map_Ns(tokens) {\n this.currentMaterial.mapSpecularExponent = this.parseMap(tokens);\n }\n /**\n * Parses the dissolve map.\n *\n * @param tokens list of tokens for the map_d direcive\n */\n parse_map_d(tokens) {\n this.currentMaterial.mapDissolve = this.parseMap(tokens);\n }\n /**\n * Parses the anti-aliasing option.\n *\n * @param tokens list of tokens for the map_aat direcive\n */\n parse_map_aat(tokens) {\n this.currentMaterial.antiAliasing = tokens[0] == "on";\n }\n /**\n * Parses the bump map.\n *\n * @param tokens list of tokens for the map_bump direcive\n */\n parse_map_bump(tokens) {\n this.currentMaterial.mapBump = this.parseMap(tokens);\n }\n /**\n * Parses the bump map.\n *\n * @param tokens list of tokens for the bump direcive\n */\n parse_bump(tokens) {\n this.parse_map_bump(tokens);\n }\n /**\n * Parses the disp map.\n *\n * @param tokens list of tokens for the disp direcive\n */\n parse_disp(tokens) {\n this.currentMaterial.mapDisplacement = this.parseMap(tokens);\n }\n /**\n * Parses the decal map.\n *\n * @param tokens list of tokens for the map_decal direcive\n */\n parse_decal(tokens) {\n this.currentMaterial.mapDecal = this.parseMap(tokens);\n }\n /**\n * Parses the refl map.\n *\n * @param tokens list of tokens for the refl direcive\n */\n parse_refl(tokens) {\n this.currentMaterial.mapReflections.push(this.parseMap(tokens));\n }\n /**\n * Parses the MTL file.\n *\n * Iterates line by line parsing each MTL directive.\n *\n * This function expects the first token in the line\n * to be a valid MTL directive. That token is then used\n * to try and run a method on this class. parse_[directive]\n * E.g., the `newmtl` directive would try to call the method\n * parse_newmtl. Each parsing function takes in the remaining\n * list of tokens and updates the currentMaterial class with\n * the attributes provided.\n */\n parse() {\n const lines = this.data.split(/\\r?\\n/);\n for (let line of lines) {\n line = line.trim();\n if (!line || line.startsWith("#")) {\n continue;\n }\n const [directive, ...tokens] = line.split(/\\s/);\n const parseMethod = this[`parse_${directive}`];\n if (!parseMethod) {\n console.warn(`Don\'t know how to parse the directive: "${directive}"`);\n continue;\n }\n // console.log(`Parsing "${directive}" with tokens: ${tokens}`);\n parseMethod.bind(this)(tokens);\n }\n // some cleanup. These don\'t need to be exposed as public data.\n delete this.data;\n this.currentMaterial = SENTINEL_MATERIAL;\n }\n}\nfunction emptyTextureOptions() {\n return {\n colorCorrection: false,\n horizontalBlending: true,\n verticalBlending: true,\n boostMipMapSharpness: 0,\n modifyTextureMap: {\n brightness: 0,\n contrast: 1,\n },\n offset: { u: 0, v: 0, w: 0 },\n scale: { u: 1, v: 1, w: 1 },\n turbulence: { u: 0, v: 0, w: 0 },\n clamp: false,\n textureResolution: null,\n bumpMultiplier: 1,\n imfChan: null,\n filename: "",\n };\n}\n\n\n//# sourceURL=webpack:///./src/material.ts?');},"./src/mesh.ts":
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
/*! exports provided: default */function(module,__webpack_exports__,__webpack_require__){eval('__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "default", function() { return Mesh; });\n/* harmony import */ var _layout__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./layout */ "./src/layout.ts");\n\n/**\n * The main Mesh class. The constructor will parse through the OBJ file data\n * and collect the vertex, vertex normal, texture, and face information. This\n * information can then be used later on when creating your VBOs. See\n * OBJ.initMeshBuffers for an example of how to use the newly created Mesh\n */\nclass Mesh {\n /**\n * Create a Mesh\n * @param {String} objectData - a string representation of an OBJ file with\n * newlines preserved.\n * @param {Object} options - a JS object containing valid options. See class\n * documentation for options.\n * @param {bool} options.enableWTextureCoord - Texture coordinates can have\n * an optional "w" coordinate after the u and v coordinates. This extra\n * value can be used in order to perform fancy transformations on the\n * textures themselves. Default is to truncate to only the u an v\n * coordinates. Passing true will provide a default value of 0 in the\n * event that any or all texture coordinates don\'t provide a w value.\n * Always use the textureStride attribute in order to determine the\n * stride length of the texture coordinates when rendering the element\n * array.\n * @param {bool} options.calcTangentsAndBitangents - Calculate the tangents\n * and bitangents when loading of the OBJ is completed. This adds two new\n * attributes to the Mesh instance: `tangents` and `bitangents`.\n */\n constructor(objectData, options) {\n this.name = "";\n this.indicesPerMaterial = [];\n this.materialsByIndex = {};\n this.tangents = [];\n this.bitangents = [];\n options = options || {};\n options.materials = options.materials || {};\n options.enableWTextureCoord = !!options.enableWTextureCoord;\n // the list of unique vertex, normal, texture, attributes\n this.vertexNormals = [];\n this.textures = [];\n // the indicies to draw the faces\n this.indices = [];\n this.textureStride = options.enableWTextureCoord ? 3 : 2;\n /*\n The OBJ file format does a sort of compression when saving a model in a\n program like Blender. There are at least 3 sections (4 including textures)\n within the file. Each line in a section begins with the same string:\n * \'v\': indicates vertex section\n * \'vn\': indicates vertex normal section\n * \'f\': indicates the faces section\n * \'vt\': indicates vertex texture section (if textures were used on the model)\n Each of the above sections (except for the faces section) is a list/set of\n unique vertices.\n\n Each line of the faces section contains a list of\n (vertex, [texture], normal) groups.\n\n **Note:** The following documentation will use a capital "V" Vertex to\n denote the above (vertex, [texture], normal) groups whereas a lowercase\n "v" vertex is used to denote an X, Y, Z coordinate.\n\n Some examples:\n // the texture index is optional, both formats are possible for models\n // without a texture applied\n f 1/25 18/46 12/31\n f 1//25 18//46 12//31\n\n // A 3 vertex face with texture indices\n f 16/92/11 14/101/22 1/69/1\n\n // A 4 vertex face\n f 16/92/11 40/109/40 38/114/38 14/101/22\n\n The first two lines are examples of a 3 vertex face without a texture applied.\n The second is an example of a 3 vertex face with a texture applied.\n The third is an example of a 4 vertex face. Note: a face can contain N\n number of vertices.\n\n Each number that appears in one of the groups is a 1-based index\n corresponding to an item from the other sections (meaning that indexing\n starts at one and *not* zero).\n\n For example:\n `f 16/92/11` is saying to\n - take the 16th element from the [v] vertex array\n - take the 92nd element from the [vt] texture array\n - take the 11th element from the [vn] normal array\n and together they make a unique vertex.\n Using all 3+ unique Vertices from the face line will produce a polygon.\n\n Now, you could just go through the OBJ file and create a new vertex for\n each face line and WebGL will draw what appears to be the same model.\n However, vertices will be overlapped and duplicated all over the place.\n\n Consider a cube in 3D space centered about the origin and each side is\n 2 units long. The front face (with the positive Z-axis pointing towards\n you) would have a Top Right vertex (looking orthogonal to its normal)\n mapped at (1,1,1) The right face would have a Top Left vertex (looking\n orthogonal to its normal) at (1,1,1) and the top face would have a Bottom\n Right vertex (looking orthogonal to its normal) at (1,1,1). Each face\n has a vertex at the same coordinates, however, three distinct vertices\n will be drawn at the same spot.\n\n To solve the issue of duplicate Vertices (the `(vertex, [texture], normal)`\n groups), while iterating through the face lines, when a group is encountered\n the whole group string (\'16/92/11\') is checked to see if it exists in the\n packed.hashindices object, and if it doesn\'t, the indices it specifies\n are used to look up each attribute in the corresponding attribute arrays\n already created. The values are then copied to the corresponding unpacked\n array (flattened to play nice with WebGL\'s ELEMENT_ARRAY_BUFFER indexing),\n the group string is added to the hashindices set and the current unpacked\n index is used as this hashindices value so that the group of elements can\n be reused. The unpacked index is incremented. If the group string already\n exists in the hashindices object, its corresponding value is the index of\n that group and is appended to the unpacked indices array.\n */\n const verts = [];\n const vertNormals = [];\n const textures = [];\n const materialNamesByIndex = [];\n const materialIndicesByName = {};\n // keep track of what material we\'ve seen last\n let currentMaterialIndex = -1;\n let currentObjectByMaterialIndex = 0;\n // unpacking stuff\n const unpacked = {\n verts: [],\n norms: [],\n textures: [],\n hashindices: {},\n indices: [[]],\n materialIndices: [],\n index: 0,\n };\n const VERTEX_RE = /^v\\s/;\n const NORMAL_RE = /^vn\\s/;\n const TEXTURE_RE = /^vt\\s/;\n const FACE_RE = /^f\\s/;\n const WHITESPACE_RE = /\\s+/;\n const USE_MATERIAL_RE = /^usemtl/;\n // array of lines separated by the newline\n const lines = objectData.split("\\n");\n for (let line of lines) {\n line = line.trim();\n if (!line || line.startsWith("#")) {\n continue;\n }\n const elements = line.split(WHITESPACE_RE);\n elements.shift();\n if (VERTEX_RE.test(line)) {\n // if this is a vertex\n verts.push(...elements);\n }\n else if (NORMAL_RE.test(line)) {\n // if this is a vertex normal\n vertNormals.push(...elements);\n }\n else if (TEXTURE_RE.test(line)) {\n let coords = elements;\n // by default, the loader will only look at the U and V\n // coordinates of the vt declaration. So, this truncates the\n // elements to only those 2 values. If W texture coordinate\n // support is enabled, then the texture coordinate is\n // expected to have three values in it.\n if (elements.length > 2 && !options.enableWTextureCoord) {\n coords = elements.slice(0, 2);\n }\n else if (elements.length === 2 && options.enableWTextureCoord) {\n // If for some reason W texture coordinate support is enabled\n // and only the U and V coordinates are given, then we supply\n // the default value of 0 so that the stride length is correct\n // when the textures are unpacked below.\n coords.push("0");\n }\n textures.push(...coords);\n }\n else if (USE_MATERIAL_RE.test(line)) {\n const materialName = elements[0];\n // check to see if we\'ve ever seen it before\n if (!(materialName in materialIndicesByName)) {\n // new material we\'ve never seen\n materialNamesByIndex.push(materialName);\n materialIndicesByName[materialName] = materialNamesByIndex.length - 1;\n // push new array into indices\n // already contains an array at index zero, don\'t add\n if (materialIndicesByName[materialName] > 0) {\n unpacked.indices.push([]);\n }\n }\n // keep track of the current material index\n currentMaterialIndex = materialIndicesByName[materialName];\n // update current index array\n currentObjectByMaterialIndex = currentMaterialIndex;\n }\n else if (FACE_RE.test(line)) {\n // if this is a face\n /*\n split this face into an array of Vertex groups\n for example:\n f 16/92/11 14/101/22 1/69/1\n becomes:\n [\'16/92/11\', \'14/101/22\', \'1/69/1\'];\n */\n const triangles = triangulate(elements);\n for (const triangle of triangles) {\n for (let j = 0, eleLen = triangle.length; j < eleLen; j++) {\n const hash = triangle[j] + "," + currentMaterialIndex;\n if (hash in unpacked.hashindices) {\n unpacked.indices[currentObjectByMaterialIndex].push(unpacked.hashindices[hash]);\n }\n else {\n /*\n Each element of the face line array is a Vertex which has its\n attributes delimited by a forward slash. This will separate\n each attribute into another array:\n \'19/92/11\'\n becomes:\n Vertex = [\'19\', \'92\', \'11\'];\n where\n Vertex[0] is the vertex index\n Vertex[1] is the texture index\n Vertex[2] is the normal index\n Think of faces having Vertices which are comprised of the\n attributes location (v), texture (vt), and normal (vn).\n */\n const vertex = triangle[j].split("/");\n // it\'s possible for faces to only specify the vertex\n // and the normal. In this case, vertex will only have\n // a length of 2 and not 3 and the normal will be the\n // second item in the list with an index of 1.\n const normalIndex = vertex.length - 1;\n /*\n The verts, textures, and vertNormals arrays each contain a\n flattend array of coordinates.\n\n Because it gets confusing by referring to Vertex and then\n vertex (both are different in my descriptions) I will explain\n what\'s going on using the vertexNormals array:\n\n vertex[2] will contain the one-based index of the vertexNormals\n section (vn). One is subtracted from this index number to play\n nice with javascript\'s zero-based array indexing.\n\n Because vertexNormal is a flattened array of x, y, z values,\n simple pointer arithmetic is used to skip to the start of the\n vertexNormal, then the offset is added to get the correct\n component: +0 is x, +1 is y, +2 is z.\n\n This same process is repeated for verts and textures.\n */\n // Vertex position\n unpacked.verts.push(+verts[(+vertex[0] - 1) * 3 + 0]);\n unpacked.verts.push(+verts[(+vertex[0] - 1) * 3 + 1]);\n unpacked.verts.push(+verts[(+vertex[0] - 1) * 3 + 2]);\n // Vertex textures\n if (textures.length) {\n const stride = options.enableWTextureCoord ? 3 : 2;\n unpacked.textures.push(+textures[(+vertex[1] - 1) * stride + 0]);\n unpacked.textures.push(+textures[(+vertex[1] - 1) * stride + 1]);\n if (options.enableWTextureCoord) {\n unpacked.textures.push(+textures[(+vertex[1] - 1) * stride + 2]);\n }\n }\n // Vertex normals\n unpacked.norms.push(+vertNormals[(+vertex[normalIndex] - 1) * 3 + 0]);\n unpacked.norms.push(+vertNormals[(+vertex[normalIndex] - 1) * 3 + 1]);\n unpacked.norms.push(+vertNormals[(+vertex[normalIndex] - 1) * 3 + 2]);\n // Vertex material indices\n unpacked.materialIndices.push(currentMaterialIndex);\n // add the newly created Vertex to the list of indices\n unpacked.hashindices[hash] = unpacked.index;\n unpacked.indices[currentObjectByMaterialIndex].push(unpacked.hashindices[hash]);\n // increment the counter\n unpacked.index += 1;\n }\n }\n }\n }\n }\n this.vertices = unpacked.verts;\n this.vertexNormals = unpacked.norms;\n this.textures = unpacked.textures;\n this.vertexMaterialIndices = unpacked.materialIndices;\n this.indices = unpacked.indices[currentObjectByMaterialIndex];\n this.indicesPerMaterial = unpacked.indices;\n this.materialNames = materialNamesByIndex;\n this.materialIndices = materialIndicesByName;\n this.materialsByIndex = {};\n if (options.calcTangentsAndBitangents) {\n this.calculateTangentsAndBitangents();\n }\n }\n /**\n * Calculates the tangents and bitangents of the mesh that forms an orthogonal basis together with the\n * normal in the direction of the texture coordinates. These are useful for setting up the TBN matrix\n * when distorting the normals through normal maps.\n * Method derived from: http://www.opengl-tutorial.org/intermediate-tutorials/tutorial-13-normal-mapping/\n *\n * This method requires the normals and texture coordinates to be parsed and set up correctly.\n * Adds the tangents and bitangents as members of the class instance.\n */\n calculateTangentsAndBitangents() {\n console.assert(!!(this.vertices &&\n this.vertices.length &&\n this.vertexNormals &&\n this.vertexNormals.length &&\n this.textures &&\n this.textures.length), "Missing attributes for calculating tangents and bitangents");\n const unpacked = {\n tangents: [...new Array(this.vertices.length)].map(_ => 0),\n bitangents: [...new Array(this.vertices.length)].map(_ => 0),\n };\n // Loop through all faces in the whole mesh\n const indices = this.indices;\n const vertices = this.vertices;\n const normals = this.vertexNormals;\n const uvs = this.textures;\n for (let i = 0; i < indices.length; i += 3) {\n const i0 = indices[i + 0];\n const i1 = indices[i + 1];\n const i2 = indices[i + 2];\n const x_v0 = vertices[i0 * 3 + 0];\n const y_v0 = vertices[i0 * 3 + 1];\n const z_v0 = vertices[i0 * 3 + 2];\n const x_uv0 = uvs[i0 * 2 + 0];\n const y_uv0 = uvs[i0 * 2 + 1];\n const x_v1 = vertices[i1 * 3 + 0];\n const y_v1 = vertices[i1 * 3 + 1];\n const z_v1 = vertices[i1 * 3 + 2];\n const x_uv1 = uvs[i1 * 2 + 0];\n const y_uv1 = uvs[i1 * 2 + 1];\n const x_v2 = vertices[i2 * 3 + 0];\n const y_v2 = vertices[i2 * 3 + 1];\n const z_v2 = vertices[i2 * 3 + 2];\n const x_uv2 = uvs[i2 * 2 + 0];\n const y_uv2 = uvs[i2 * 2 + 1];\n const x_deltaPos1 = x_v1 - x_v0;\n const y_deltaPos1 = y_v1 - y_v0;\n const z_deltaPos1 = z_v1 - z_v0;\n const x_deltaPos2 = x_v2 - x_v0;\n const y_deltaPos2 = y_v2 - y_v0;\n const z_deltaPos2 = z_v2 - z_v0;\n const x_uvDeltaPos1 = x_uv1 - x_uv0;\n const y_uvDeltaPos1 = y_uv1 - y_uv0;\n const x_uvDeltaPos2 = x_uv2 - x_uv0;\n const y_uvDeltaPos2 = y_uv2 - y_uv0;\n const rInv = x_uvDeltaPos1 * y_uvDeltaPos2 - y_uvDeltaPos1 * x_uvDeltaPos2;\n const r = 1.0 / Math.abs(rInv < 0.0001 ? 1.0 : rInv);\n // Tangent\n const x_tangent = (x_deltaPos1 * y_uvDeltaPos2 - x_deltaPos2 * y_uvDeltaPos1) * r;\n const y_tangent = (y_deltaPos1 * y_uvDeltaPos2 - y_deltaPos2 * y_uvDeltaPos1) * r;\n const z_tangent = (z_deltaPos1 * y_uvDeltaPos2 - z_deltaPos2 * y_uvDeltaPos1) * r;\n // Bitangent\n const x_bitangent = (x_deltaPos2 * x_uvDeltaPos1 - x_deltaPos1 * x_uvDeltaPos2) * r;\n const y_bitangent = (y_deltaPos2 * x_uvDeltaPos1 - y_deltaPos1 * x_uvDeltaPos2) * r;\n const z_bitangent = (z_deltaPos2 * x_uvDeltaPos1 - z_deltaPos1 * x_uvDeltaPos2) * r;\n // Gram-Schmidt orthogonalize\n //t = glm::normalize(t - n * glm:: dot(n, t));\n const x_n0 = normals[i0 * 3 + 0];\n const y_n0 = normals[i0 * 3 + 1];\n const z_n0 = normals[i0 * 3 + 2];\n const x_n1 = normals[i1 * 3 + 0];\n const y_n1 = normals[i1 * 3 + 1];\n const z_n1 = normals[i1 * 3 + 2];\n const x_n2 = normals[i2 * 3 + 0];\n const y_n2 = normals[i2 * 3 + 1];\n const z_n2 = normals[i2 * 3 + 2];\n // Tangent\n const n0_dot_t = x_tangent * x_n0 + y_tangent * y_n0 + z_tangent * z_n0;\n const n1_dot_t = x_tangent * x_n1 + y_tangent * y_n1 + z_tangent * z_n1;\n const n2_dot_t = x_tangent * x_n2 + y_tangent * y_n2 + z_tangent * z_n2;\n const x_resTangent0 = x_tangent - x_n0 * n0_dot_t;\n const y_resTangent0 = y_tangent - y_n0 * n0_dot_t;\n const z_resTangent0 = z_tangent - z_n0 * n0_dot_t;\n const x_resTangent1 = x_tangent - x_n1 * n1_dot_t;\n const y_resTangent1 = y_tangent - y_n1 * n1_dot_t;\n const z_resTangent1 = z_tangent - z_n1 * n1_dot_t;\n const x_resTangent2 = x_tangent - x_n2 * n2_dot_t;\n const y_resTangent2 = y_tangent - y_n2 * n2_dot_t;\n const z_resTangent2 = z_tangent - z_n2 * n2_dot_t;\n const magTangent0 = Math.sqrt(x_resTangent0 * x_resTangent0 + y_resTangent0 * y_resTangent0 + z_resTangent0 * z_resTangent0);\n const magTangent1 = Math.sqrt(x_resTangent1 * x_resTangent1 + y_resTangent1 * y_resTangent1 + z_resTangent1 * z_resTangent1);\n const magTangent2 = Math.sqrt(x_resTangent2 * x_resTangent2 + y_resTangent2 * y_resTangent2 + z_resTangent2 * z_resTangent2);\n // Bitangent\n const n0_dot_bt = x_bitangent * x_n0 + y_bitangent * y_n0 + z_bitangent * z_n0;\n const n1_dot_bt = x_bitangent * x_n1 + y_bitangent * y_n1 + z_bitangent * z_n1;\n const n2_dot_bt = x_bitangent * x_n2 + y_bitangent * y_n2 + z_bitangent * z_n2;\n const x_resBitangent0 = x_bitangent - x_n0 * n0_dot_bt;\n const y_resBitangent0 = y_bitangent - y_n0 * n0_dot_bt;\n const z_resBitangent0 = z_bitangent - z_n0 * n0_dot_bt;\n const x_resBitangent1 = x_bitangent - x_n1 * n1_dot_bt;\n const y_resBitangent1 = y_bitangent - y_n1 * n1_dot_bt;\n const z_resBitangent1 = z_bitangent - z_n1 * n1_dot_bt;\n const x_resBitangent2 = x_bitangent - x_n2 * n2_dot_bt;\n const y_resBitangent2 = y_bitangent - y_n2 * n2_dot_bt;\n const z_resBitangent2 = z_bitangent - z_n2 * n2_dot_bt;\n const magBitangent0 = Math.sqrt(x_resBitangent0 * x_resBitangent0 +\n y_resBitangent0 * y_resBitangent0 +\n z_resBitangent0 * z_resBitangent0);\n const magBitangent1 = Math.sqrt(x_resBitangent1 * x_resBitangent1 +\n y_resBitangent1 * y_resBitangent1 +\n z_resBitangent1 * z_resBitangent1);\n const magBitangent2 = Math.sqrt(x_resBitangent2 * x_resBitangent2 +\n y_resBitangent2 * y_resBitangent2 +\n z_resBitangent2 * z_resBitangent2);\n unpacked.tangents[i0 * 3 + 0] += x_resTangent0 / magTangent0;\n unpacked.tangents[i0 * 3 + 1] += y_resTangent0 / magTangent0;\n unpacked.tangents[i0 * 3 + 2] += z_resTangent0 / magTangent0;\n unpacked.tangents[i1 * 3 + 0] += x_resTangent1 / magTangent1;\n unpacked.tangents[i1 * 3 + 1] += y_resTangent1 / magTangent1;\n unpacked.tangents[i1 * 3 + 2] += z_resTangent1 / magTangent1;\n unpacked.tangents[i2 * 3 + 0] += x_resTangent2 / magTangent2;\n unpacked.tangents[i2 * 3 + 1] += y_resTangent2 / magTangent2;\n unpacked.tangents[i2 * 3 + 2] += z_resTangent2 / magTangent2;\n unpacked.bitangents[i0 * 3 + 0] += x_resBitangent0 / magBitangent0;\n unpacked.bitangents[i0 * 3 + 1] += y_resBitangent0 / magBitangent0;\n unpacked.bitangents[i0 * 3 + 2] += z_resBitangent0 / magBitangent0;\n unpacked.bitangents[i1 * 3 + 0] += x_resBitangent1 / magBitangent1;\n unpacked.bitangents[i1 * 3 + 1] += y_resBitangent1 / magBitangent1;\n unpacked.bitangents[i1 * 3 + 2] += z_resBitangent1 / magBitangent1;\n unpacked.bitangents[i2 * 3 + 0] += x_resBitangent2 / magBitangent2;\n unpacked.bitangents[i2 * 3 + 1] += y_resBitangent2 / magBitangent2;\n unpacked.bitangents[i2 * 3 + 2] += z_resBitangent2 / magBitangent2;\n // TODO: check handedness\n }\n this.tangents = unpacked.tangents;\n this.bitangents = unpacked.bitangents;\n }\n /**\n * @param layout - A {@link Layout} object that describes the\n * desired memory layout of the generated buffer\n * @return The packed array in the ... TODO\n */\n makeBufferData(layout) {\n const numItems = this.vertices.length / 3;\n const buffer = new ArrayBuffer(layout.stride * numItems);\n buffer.numItems = numItems;\n const dataView = new DataView(buffer);\n for (let i = 0, vertexOffset = 0; i < numItems; i++) {\n vertexOffset = i * layout.stride;\n // copy in the vertex data in the order and format given by the\n // layout param\n for (const attribute of layout.attributes) {\n const offset = vertexOffset + layout.attributeMap[attribute.key].offset;\n switch (attribute.key) {\n case _layout__WEBPACK_IMPORTED_MODULE_0__["Layout"].POSITION.key:\n dataView.setFloat32(offset, this.vertices[i * 3], true);\n dataView.setFloat32(offset + 4, this.vertices[i * 3 + 1], true);\n dataView.setFloat32(offset + 8, this.vertices[i * 3 + 2], true);\n break;\n case _layout__WEBPACK_IMPORTED_MODULE_0__["Layout"].UV.key:\n dataView.setFloat32(offset, this.textures[i * 2], true);\n dataView.setFloat32(offset + 4, this.textures[i * 2 + 1], true);\n break;\n case _layout__WEBPACK_IMPORTED_MODULE_0__["Layout"].NORMAL.key:\n dataView.setFloat32(offset, this.vertexNormals[i * 3], true);\n dataView.setFloat32(offset + 4, this.vertexNormals[i * 3 + 1], true);\n dataView.setFloat32(offset + 8, this.vertexNormals[i * 3 + 2], true);\n break;\n case _layout__WEBPACK_IMPORTED_MODULE_0__["Layout"].MATERIAL_INDEX.key:\n dataView.setInt16(offset, this.vertexMaterialIndices[i], true);\n break;\n case _layout__WEBPACK_IMPORTED_MODULE_0__["Layout"].AMBIENT.key: {\n const materialIndex = this.vertexMaterialIndices[i];\n const material = this.materialsByIndex[materialIndex];\n if (!material) {\n console.warn(\'Material "\' +\n this.materialNames[materialIndex] +\n \'" not found in mesh. Did you forget to call addMaterialLibrary(...)?"\');\n break;\n }\n dataView.setFloat32(offset, material.ambient[0], true);\n dataView.setFloat32(offset + 4, material.ambient[1], true);\n dataView.setFloat32(offset + 8, material.ambient[2], true);\n break;\n }\n case _layout__WEBPACK_IMPORTED_MODULE_0__["Layout"].DIFFUSE.key: {\n const materialIndex = this.vertexMaterialIndices[i];\n const material = this.materialsByIndex[materialIndex];\n if (!material) {\n console.warn(\'Material "\' +\n this.materialNames[materialIndex] +\n \'" not found in mesh. Did you forget to call addMaterialLibrary(...)?"\');\n break;\n }\n dataView.setFloat32(offset, material.diffuse[0], true);\n dataView.setFloat32(offset + 4, material.diffuse[1], true);\n dataView.setFloat32(offset + 8, material.diffuse[2], true);\n break;\n }\n case _layout__WEBPACK_IMPORTED_MODULE_0__["Layout"].SPECULAR.key: {\n const materialIndex = this.vertexMaterialIndices[i];\n const material = this.materialsByIndex[materialIndex];\n if (!material) {\n console.warn(\'Material "\' +\n this.materialNames[materialIndex] +\n \'" not found in mesh. Did you forget to call addMaterialLibrary(...)?"\');\n break;\n }\n dataView.setFloat32(offset, material.specular[0], true);\n dataView.setFloat32(offset + 4, material.specular[1], true);\n dataView.setFloat32(offset + 8, material.specular[2], true);\n break;\n }\n case _layout__WEBPACK_IMPORTED_MODULE_0__["Layout"].SPECULAR_EXPONENT.key: {\n const materialIndex = this.vertexMaterialIndices[i];\n const material = this.materialsByIndex[materialIndex];\n if (!material) {\n console.warn(\'Material "\' +\n this.materialNames[materialIndex] +\n \'" not found in mesh. Did you forget to call addMaterialLibrary(...)?"\');\n break;\n }\n dataView.setFloat32(offset, material.specularExponent, true);\n break;\n }\n case _layout__WEBPACK_IMPORTED_MODULE_0__["Layout"].EMISSIVE.key: {\n const materialIndex = this.vertexMaterialIndices[i];\n const material = this.materialsByIndex[materialIndex];\n if (!material) {\n console.warn(\'Material "\' +\n this.materialNames[materialIndex] +\n \'" not found in mesh. Did you forget to call addMaterialLibrary(...)?"\');\n break;\n }\n dataView.setFloat32(offset, material.emissive[0], true);\n dataView.setFloat32(offset + 4, material.emissive[1], true);\n dataView.setFloat32(offset + 8, material.emissive[2], true);\n break;\n }\n case _layout__WEBPACK_IMPORTED_MODULE_0__["Layout"].TRANSMISSION_FILTER.key: {\n const materialIndex = this.vertexMaterialIndices[i];\n const material = this.materialsByIndex[materialIndex];\n if (!material) {\n console.warn(\'Material "\' +\n this.materialNames[materialIndex] +\n \'" not found in mesh. Did you forget to call addMaterialLibrary(...)?"\');\n break;\n }\n dataView.setFloat32(offset, material.transmissionFilter[0], true);\n dataView.setFloat32(offset + 4, material.transmissionFilter[1], true);\n dataView.setFloat32(offset + 8, material.transmissionFilter[2], true);\n break;\n }\n case _layout__WEBPACK_IMPORTED_MODULE_0__["Layout"].DISSOLVE.key: {\n const materialIndex = this.vertexMaterialIndices[i];\n const material = this.materialsByIndex[materialIndex];\n if (!material) {\n console.warn(\'Material "\' +\n this.materialNames[materialIndex] +\n \'" not found in mesh. Did you forget to call addMaterialLibrary(...)?"\');\n break;\n }\n dataView.setFloat32(offset, material.dissolve, true);\n break;\n }\n case _layout__WEBPACK_IMPORTED_MODULE_0__["Layout"].ILLUMINATION.key: {\n const materialIndex = this.vertexMaterialIndices[i];\n const material = this.materialsByIndex[materialIndex];\n if (!material) {\n console.warn(\'Material "\' +\n this.materialNames[materialIndex] +\n \'" not found in mesh. Did you forget to call addMaterialLibrary(...)?"\');\n break;\n }\n dataView.setInt16(offset, material.illumination, true);\n break;\n }\n case _layout__WEBPACK_IMPORTED_MODULE_0__["Layout"].REFRACTION_INDEX.key: {\n const materialIndex = this.vertexMaterialIndices[i];\n const material = this.materialsByIndex[materialIndex];\n if (!material) {\n console.warn(\'Material "\' +\n this.materialNames[materialIndex] +\n \'" not found in mesh. Did you forget to call addMaterialLibrary(...)?"\');\n break;\n }\n dataView.setFloat32(offset, material.refractionIndex, true);\n break;\n }\n case _layout__WEBPACK_IMPORTED_MODULE_0__["Layout"].SHARPNESS.key: {\n const materialIndex = this.vertexMaterialIndices[i];\n const material = this.materialsByIndex[materialIndex];\n if (!material) {\n console.warn(\'Material "\' +\n this.materialNames[materialIndex] +\n \'" not found in mesh. Did you forget to call addMaterialLibrary(...)?"\');\n break;\n }\n dataView.setFloat32(offset, material.sharpness, true);\n break;\n }\n case _layout__WEBPACK_IMPORTED_MODULE_0__["Layout"].ANTI_ALIASING.key: {\n const materialIndex = this.vertexMaterialIndices[i];\n const material = this.materialsByIndex[materialIndex];\n if (!material) {\n console.warn(\'Material "\' +\n this.materialNames[materialIndex] +\n \'" not found in mesh. Did you forget to call addMaterialLibrary(...)?"\');\n break;\n }\n dataView.setInt16(offset, material.antiAliasing ? 1 : 0, true);\n break;\n }\n }\n }\n }\n return buffer;\n }\n makeIndexBufferData() {\n const buffer = new Uint16Array(this.indices);\n buffer.numItems = this.indices.length;\n return buffer;\n }\n makeIndexBufferDataForMaterials(...materialIndices) {\n const indices = new Array().concat(...materialIndices.map(mtlIdx => this.indicesPerMaterial[mtlIdx]));\n const buffer = new Uint16Array(indices);\n buffer.numItems = indices.length;\n return buffer;\n }\n addMaterialLibrary(mtl) {\n for (const name in mtl.materials) {\n if (!(name in this.materialIndices)) {\n // This material is not referenced by the mesh\n continue;\n }\n const material = mtl.materials[name];\n // Find the material index for this material\n const materialIndex = this.materialIndices[material.name];\n // Put the material into the materialsByIndex object at the right\n // spot as determined when the obj file was parsed\n this.materialsByIndex[materialIndex] = material;\n }\n }\n}\nfunction* triangulate(elements) {\n if (elements.length <= 3) {\n yield elements;\n }\n else if (elements.length === 4) {\n yield [elements[0], elements[1], elements[2]];\n yield [elements[2], elements[3], elements[0]];\n }\n else {\n for (let i = 1; i < elements.length - 1; i++) {\n yield [elements[0], elements[i], elements[i + 1]];\n }\n }\n}\n\n\n//# sourceURL=webpack:///./src/mesh.ts?');},"./src/utils.ts":
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
/*! exports provided: downloadModels, downloadMeshes, initMeshBuffers, deleteMeshBuffers */function(module,__webpack_exports__,__webpack_require__){eval('__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "downloadModels", function() { return downloadModels; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "downloadMeshes", function() { return downloadMeshes; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "initMeshBuffers", function() { return initMeshBuffers; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "deleteMeshBuffers", function() { return deleteMeshBuffers; });\n/* harmony import */ var _mesh__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./mesh */ "./src/mesh.ts");\n/* harmony import */ var _material__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./material */ "./src/material.ts");\n\n\nfunction downloadMtlTextures(mtl, root) {\n const mapAttributes = [\n "mapDiffuse",\n "mapAmbient",\n "mapSpecular",\n "mapDissolve",\n "mapBump",\n "mapDisplacement",\n "mapDecal",\n "mapEmissive",\n ];\n if (!root.endsWith("/")) {\n root += "/";\n }\n const textures = [];\n for (const materialName in mtl.materials) {\n if (!mtl.materials.hasOwnProperty(materialName)) {\n continue;\n }\n const material = mtl.materials[materialName];\n for (const attr of mapAttributes) {\n const mapData = material[attr];\n if (!mapData || !mapData.filename) {\n continue;\n }\n const url = root + mapData.filename;\n textures.push(fetch(url)\n .then(response => {\n if (!response.ok) {\n throw new Error();\n }\n return response.blob();\n })\n .then(function (data) {\n const image = new Image();\n image.src = URL.createObjectURL(data);\n mapData.texture = image;\n return new Promise(resolve => (image.onload = resolve));\n })\n .catch(() => {\n console.error(`Unable to download texture: ${url}`);\n }));\n }\n }\n return Promise.all(textures);\n}\nfunction getMtl(modelOptions) {\n if (!(typeof modelOptions.mtl === "string")) {\n return modelOptions.obj.replace(/\\.obj$/, ".mtl");\n }\n return modelOptions.mtl;\n}\n/**\n * Accepts a list of model request objects and returns a Promise that\n * resolves when all models have been downloaded and parsed.\n *\n * The list of model objects follow this interface:\n * {\n * obj: \'path/to/model.obj\',\n * mtl: true | \'path/to/model.mtl\',\n * downloadMtlTextures: true | false\n * mtlTextureRoot: \'/models/suzanne/maps\'\n * name: \'suzanne\'\n * }\n *\n * The `obj` attribute is required and should be the path to the\n * model\'s .obj file relative to the current repo (absolute URLs are\n * suggested).\n *\n * The `mtl` attribute is optional and can either be a boolean or\n * a path to the model\'s .mtl file relative to the current URL. If\n * the value is `true`, then the path and basename given for the `obj`\n * attribute is used replacing the .obj suffix for .mtl\n * E.g.: {obj: \'models/foo.obj\', mtl: true} would search for \'models/foo.mtl\'\n *\n * The `name` attribute is optional and is a human friendly name to be\n * included with the parsed OBJ and MTL files. If not given, the base .obj\n * filename will be used.\n *\n * The `downloadMtlTextures` attribute is a flag for automatically downloading\n * any images found in the MTL file and attaching them to each Material\n * created from that file. For example, if material.mapDiffuse is set (there\n * was data in the MTL file), then material.mapDiffuse.texture will contain\n * the downloaded image. This option defaults to `true`. By default, the MTL\'s\n * URL will be used to determine the location of the images.\n *\n * The `mtlTextureRoot` attribute is optional and should point to the location\n * on the server that this MTL\'s texture files are located. The default is to\n * use the MTL file\'s location.\n *\n * @returns {Promise} the result of downloading the given list of models. The\n * promise will resolve with an object whose keys are the names of the models\n * and the value is its Mesh object. Each Mesh object will automatically\n * have its addMaterialLibrary() method called to set the given MTL data (if given).\n */\nfunction downloadModels(models) {\n const finished = [];\n for (const model of models) {\n if (!model.obj) {\n throw new Error(\'"obj" attribute of model object not set. The .obj file is required to be set \' +\n "in order to use downloadModels()");\n }\n const options = {\n indicesPerMaterial: !!model.indicesPerMaterial,\n calcTangentsAndBitangents: !!model.calcTangentsAndBitangents,\n };\n // if the name is not provided, dervive it from the given OBJ\n let name = model.name;\n if (!name) {\n const parts = model.obj.split("/");\n name = parts[parts.length - 1].replace(".obj", "");\n }\n const namePromise = Promise.resolve(name);\n const meshPromise = fetch(model.obj)\n .then(response => response.text())\n .then(data => {\n return new _mesh__WEBPACK_IMPORTED_MODULE_0__["default"](data, options);\n });\n let mtlPromise;\n // Download MaterialLibrary file?\n if (model.mtl) {\n const mtl = getMtl(model);\n mtlPromise = fetch(mtl)\n .then(response => response.text())\n .then((data) => {\n const material = new _material__WEBPACK_IMPORTED_MODULE_1__["MaterialLibrary"](data);\n if (model.downloadMtlTextures !== false) {\n let root = model.mtlTextureRoot;\n if (!root) {\n // get the directory of the MTL file as default\n root = mtl.substr(0, mtl.lastIndexOf("/"));\n }\n // downloadMtlTextures returns a Promise that\n // is resolved once all of the images it\n // contains are downloaded. These are then\n // attached to the map data objects\n return Promise.all([Promise.resolve(material), downloadMtlTextures(material, root)]);\n }\n return Promise.all([Promise.resolve(material), undefined]);\n })\n .then((value) => {\n return value[0];\n });\n }\n const parsed = [namePromise, meshPromise, mtlPromise];\n finished.push(Promise.all(parsed));\n }\n return Promise.all(finished).then(ms => {\n // the "finished" promise is a list of name, Mesh instance,\n // and MaterialLibary instance. This unpacks and returns an\n // object mapping name to Mesh (Mesh points to MTL).\n const models = {};\n for (const model of ms) {\n const [name, mesh, mtl] = model;\n mesh.name = name;\n if (mtl) {\n mesh.addMaterialLibrary(mtl);\n }\n models[name] = mesh;\n }\n return models;\n });\n}\n/**\n * Takes in an object of `mesh_name`, `\'/url/to/OBJ/file\'` pairs and a callback\n * function. Each OBJ file will be ajaxed in and automatically converted to\n * an OBJ.Mesh. When all files have successfully downloaded the callback\n * function provided will be called and passed in an object containing\n * the newly created meshes.\n *\n * **Note:** In order to use this function as a way to download meshes, a\n * webserver of some sort must be used.\n *\n * @param {Object} nameAndAttrs an object where the key is the name of the mesh and the value is the url to that mesh\'s OBJ file\n *\n * @param {Function} completionCallback should contain a function that will take one parameter: an object array where the keys will be the unique object name and the value will be a Mesh object\n *\n * @param {Object} meshes In case other meshes are loaded separately or if a previously declared variable is desired to be used, pass in a (possibly empty) json object of the pattern: { \'<mesh_name>\': OBJ.Mesh }\n *\n */\nfunction downloadMeshes(nameAndURLs, completionCallback, meshes) {\n if (meshes === undefined) {\n meshes = {};\n }\n const completed = [];\n for (const mesh_name in nameAndURLs) {\n if (!nameAndURLs.hasOwnProperty(mesh_name)) {\n continue;\n }\n const url = nameAndURLs[mesh_name];\n completed.push(fetch(url)\n .then(response => response.text())\n .then(data => {\n return [mesh_name, new _mesh__WEBPACK_IMPORTED_MODULE_0__["default"](data)];\n }));\n }\n Promise.all(completed).then(ms => {\n for (const [name, mesh] of ms) {\n meshes[name] = mesh;\n }\n return completionCallback(meshes);\n });\n}\nfunction _buildBuffer(gl, type, data, itemSize) {\n const buffer = gl.createBuffer();\n const arrayView = type === gl.ARRAY_BUFFER ? Float32Array : Uint16Array;\n gl.bindBuffer(type, buffer);\n gl.bufferData(type, new arrayView(data), gl.STATIC_DRAW);\n buffer.itemSize = itemSize;\n buffer.numItems = data.length / itemSize;\n return buffer;\n}\n/**\n * Takes in the WebGL context and a Mesh, then creates and appends the buffers\n * to the mesh object as attributes.\n *\n * @param {WebGLRenderingContext} gl the `canvas.getContext(\'webgl\')` context instance\n * @param {Mesh} mesh a single `OBJ.Mesh` instance\n *\n * The newly created mesh attributes are:\n *\n * Attrbute | Description\n * :--- | ---\n * **normalBuffer** |contains the model's Vertex Normals\n * normalBuffer.itemSize |set to 3 items\n * normalBuffer.numItems |the total number of vertex normals\n * |\n * **textureBuffer** |contains the model's Texture Coordinates\n * textureBuffer.itemSize |set to 2 items\n * textureBuffer.numItems |the number of texture coordinates\n * |\n * **vertexBuffer** |contains the model's Vertex Position Coordinates (does not include w)\n * vertexBuffer.itemSize |set to 3 items\n * vertexBuffer.numItems |the total number of vertices\n * |\n * **indexBuffer** |contains the indices of the faces\n * indexBuffer.itemSize |is set to 1\n * indexBuffer.numItems |the total number of indices\n *\n * A simple example (a lot of steps are missing, so don\'t copy and paste):\n *\n * const gl = canvas.getContext(\'webgl\'),\n * mesh = OBJ.Mesh(obj_file_data);\n * // compile the shaders and create a shader program\n * const shaderProgram = gl.createProgram();\n * // compilation stuff here\n * ...\n * // make sure you have vertex, vertex normal, and texture coordinate\n * // attributes located in your shaders and attach them to the shader program\n * shaderProgram.vertexPositionAttribute = gl.getAttribLocation(shaderProgram, "aVertexPosition");\n * gl.enableVertexAttribArray(shaderProgram.vertexPositionAttribute);\n *\n * shaderProgram.vertexNormalAttribute = gl.getAttribLocation(shaderProgram, "aVertexNormal");\n * gl.enableVertexAttribArray(shaderProgram.vertexNormalAttribute);\n *\n * shaderProgram.textureCoordAttribute = gl.getAttribLocation(shaderProgram, "aTextureCoord");\n * gl.enableVertexAttribArray(shaderProgram.textureCoordAttribute);\n *\n * // create and initialize the vertex, vertex normal, and texture coordinate buffers\n * // and save on to the mesh object\n * OBJ.initMeshBuffers(gl, mesh);\n *\n * // now to render the mesh\n * gl.bindBuffer(gl.ARRAY_BUFFER, mesh.vertexBuffer);\n * gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, mesh.vertexBuffer.itemSize, gl.FLOAT, false, 0, 0);\n * // it\'s possible that the mesh doesn\'t contain\n * // any texture coordinates (e.g. suzanne.obj in the development branch).\n * // in this case, the texture vertexAttribArray will need to be disabled\n * // before the call to drawElements\n * if(!mesh.textures.length){\n * gl.disableVertexAttribArray(shaderProgram.textureCoordAttribute);\n * }\n * else{\n * // if the texture vertexAttribArray has been previously\n * // disabled, then it needs to be re-enabled\n * gl.enableVertexAttribArray(shaderProgram.textureCoordAttribute);\n * gl.bindBuffer(gl.ARRAY_BUFFER, mesh.textureBuffer);\n * gl.vertexAttribPointer(shaderProgram.textureCoordAttribute, mesh.textureBuffer.itemSize, gl.FLOAT, false, 0, 0);\n * }\n *\n * gl.bindBuffer(gl.ARRAY_BUFFER, mesh.normalBuffer);\n * gl.vertexAttribPointer(shaderProgram.vertexNormalAttribute, mesh.normalBuffer.itemSize, gl.FLOAT, false, 0, 0);\n *\n * gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, model.mesh.indexBuffer);\n * gl.drawElements(gl.TRIANGLES, model.mesh.indexBuffer.numItems, gl.UNSIGNED_SHORT, 0);\n */\nfunction initMeshBuffers(gl, mesh) {\n mesh.normalBuffer = _buildBuffer(gl, gl.ARRAY_BUFFER, mesh.vertexNormals, 3);\n mesh.textureBuffer = _buildBuffer(gl, gl.ARRAY_BUFFER, mesh.textures, mesh.textureStride);\n mesh.vertexBuffer = _buildBuffer(gl, gl.ARRAY_BUFFER, mesh.vertices, 3);\n mesh.indexBuffer = _buildBuffer(gl, gl.ELEMENT_ARRAY_BUFFER, mesh.indices, 1);\n return mesh;\n}\nfunction deleteMeshBuffers(gl, mesh) {\n gl.deleteBuffer(mesh.normalBuffer);\n gl.deleteBuffer(mesh.textureBuffer);\n gl.deleteBuffer(mesh.vertexBuffer);\n gl.deleteBuffer(mesh.indexBuffer);\n}\n\n\n//# sourceURL=webpack:///./src/utils.ts?');},0:
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1925
|
+
/**
|
|
1926
|
+
* @file glTF 2.0 → meshes in the arrays shape, the node hierarchy, skins and animation clips.
|
|
1927
|
+
* @module host/gltf
|
|
1928
|
+
* @license AGPL-3.0-only
|
|
1929
|
+
*
|
|
1930
|
+
* The format is glTF 2.0 (Khronos Group): a JSON document plus binary buffers,
|
|
1931
|
+
* or both in one GLB container. parseGlb splits a container; parseGltf reads
|
|
1932
|
+
* the document over its buffers into what a bridge uploads and what tree's
|
|
1933
|
+
* skin functions (clipSample, poseWorld, jointPalette) take as they are.
|
|
1934
|
+
*
|
|
1935
|
+
* Read: triangle primitives (POSITION, NORMAL, TANGENT, TEXCOORD_0, JOINTS_0,
|
|
1936
|
+
* WEIGHTS_0, indices), morph targets (POSITION and NORMAL deltas), the
|
|
1937
|
+
* material's base colour factor, nodes, skins, animations. Normalised integer
|
|
1938
|
+
* accessors arrive as floats; interleaved views are unpacked. Not read:
|
|
1939
|
+
* textures, cameras, other primitive modes, sparse accessors and compressed
|
|
1940
|
+
* geometry (Draco, meshopt) — the last two throw.
|
|
1941
|
+
*
|
|
1942
|
+
* Nodes are re-indexed parents first, and every node index in the result
|
|
1943
|
+
* (a mesh's, a skin's joints, a channel's) is the new one.
|
|
1944
|
+
*/
|
|
1945
|
+
|
|
1946
|
+
|
|
1947
|
+
const GLB_MAGIC = 0x46546C67, CHUNK_JSON = 0x4E4F534A, CHUNK_BIN = 0x004E4942;
|
|
1948
|
+
|
|
1949
|
+
const COMPONENTS = { SCALAR: 1, VEC2: 2, VEC3: 3, VEC4: 4, MAT2: 4, MAT3: 9, MAT4: 16 };
|
|
1950
|
+
const TYPES = {
|
|
1951
|
+
5120: Int8Array, 5121: Uint8Array, 5122: Int16Array, 5123: Uint16Array, 5125: Uint32Array, 5126: Float32Array,
|
|
1952
|
+
};
|
|
1953
|
+
const NORMALISED = { 5120: 127, 5121: 255, 5122: 32767, 5123: 65535 };
|
|
1954
|
+
const ATTRIBUTES = {
|
|
1955
|
+
POSITION: 'position', NORMAL: 'normal', TANGENT: 'tangent', TEXCOORD_0: 'texcoord',
|
|
1956
|
+
JOINTS_0: 'joints', WEIGHTS_0: 'weights',
|
|
1957
|
+
};
|
|
1958
|
+
const IDENTITY = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
|
|
1959
|
+
|
|
1960
|
+
/**
|
|
1961
|
+
* Split a GLB container into its JSON document and binary chunk.
|
|
1962
|
+
* @param {ArrayBuffer} buffer
|
|
1963
|
+
* @returns {{ json:object, bin:ArrayBuffer|null }}
|
|
1964
|
+
*/
|
|
1965
|
+
function parseGlb(buffer) {
|
|
1966
|
+
const view = new DataView(buffer);
|
|
1967
|
+
if (view.getUint32(0, true) !== GLB_MAGIC) throw new Error('[host] gltf: not a GLB container.');
|
|
1968
|
+
if (view.getUint32(4, true) !== 2) throw new Error('[host] gltf: GLB version ' + view.getUint32(4, true) + ', 2 expected.');
|
|
1969
|
+
const end = Math.min(view.getUint32(8, true), buffer.byteLength);
|
|
1970
|
+
let json = null, bin = null;
|
|
1971
|
+
for (let at = 12; at + 8 <= end;) {
|
|
1972
|
+
const length = view.getUint32(at, true), type = view.getUint32(at + 4, true);
|
|
1973
|
+
const start = at + 8;
|
|
1974
|
+
if (type === CHUNK_JSON) json = JSON.parse(new TextDecoder().decode(new Uint8Array(buffer, start, length)));
|
|
1975
|
+
else if (type === CHUNK_BIN && !bin) bin = buffer.slice(start, start + length);
|
|
1976
|
+
at = start + length + (-length & 3);
|
|
1977
|
+
}
|
|
1978
|
+
if (!json) throw new Error('[host] gltf: GLB without a JSON chunk.');
|
|
1979
|
+
return { json, bin };
|
|
1891
1980
|
}
|
|
1892
1981
|
|
|
1893
|
-
|
|
1894
|
-
|
|
1982
|
+
/**
|
|
1983
|
+
* Read a glTF document over its buffers.
|
|
1984
|
+
*
|
|
1985
|
+
* A part is one triangle primitive under one node: `mesh` in the
|
|
1986
|
+
* arrays shape (position, normal?, tangent?, texcoord?, joints?, weights?,
|
|
1987
|
+
* indices), `targets` its morph targets as delta arrays, `color` the
|
|
1988
|
+
* material's base colour factor, `node` the node it hangs from and `skin` its
|
|
1989
|
+
* skin's index or −1. A rigid mesh draws under its node's world matrix; a
|
|
1990
|
+
* skinned one under its skin's joint palette.
|
|
1991
|
+
*
|
|
1992
|
+
* @param {object} json The glTF document.
|
|
1993
|
+
* @param {ArrayBuffer[]} buffers One per `json.buffers` entry.
|
|
1994
|
+
* @returns {{ parts:{ name:string, node:number, skin:number, mesh:object,
|
|
1995
|
+
* targets:{ name:string, position?:object, normal?:object }[], color:number[] }[],
|
|
1996
|
+
* nodes:{ names:string[], parents:Int32Array, rest:Float32Array },
|
|
1997
|
+
* skins:{ name:string, joints:Uint16Array, inverseBind:Float32Array }[],
|
|
1998
|
+
* clips:{ name:string, duration:number, channels:object[] }[] }}
|
|
1999
|
+
* nodes.rest: the rest pose, ten numbers per node (translation, rotation
|
|
2000
|
+
* [x,y,z,w], scale). A clip channel: { node, path, interp, times, values }.
|
|
2001
|
+
*/
|
|
2002
|
+
function parseGltf(json, buffers) {
|
|
2003
|
+
for (const ext of json.extensionsRequired || []) {
|
|
2004
|
+
if (/draco|meshopt/i.test(ext)) throw new Error('[host] gltf: ' + ext + ' is not supported.');
|
|
2005
|
+
}
|
|
2006
|
+
const cache = new Map();
|
|
2007
|
+
|
|
2008
|
+
const accessor = (index) => {
|
|
2009
|
+
if (cache.has(index)) return cache.get(index);
|
|
2010
|
+
const a = json.accessors[index];
|
|
2011
|
+
if (a.sparse) throw new Error('[host] gltf: sparse accessors are not supported.');
|
|
2012
|
+
const n = COMPONENTS[a.type], Type = TYPES[a.componentType], scale = a.normalized && NORMALISED[a.componentType];
|
|
2013
|
+
const data = new (scale ? Float32Array : Type)(a.count * n);
|
|
2014
|
+
if (a.bufferView !== undefined) {
|
|
2015
|
+
const bv = json.bufferViews[a.bufferView];
|
|
2016
|
+
const base = (bv.byteOffset || 0) + (a.byteOffset || 0), size = Type.BYTES_PER_ELEMENT;
|
|
2017
|
+
const stride = bv.byteStride || n * size;
|
|
2018
|
+
const view = new DataView(buffers[bv.buffer]);
|
|
2019
|
+
const get = {
|
|
2020
|
+
5120: 'getInt8', 5121: 'getUint8', 5122: 'getInt16', 5123: 'getUint16', 5125: 'getUint32', 5126: 'getFloat32',
|
|
2021
|
+
}[a.componentType];
|
|
2022
|
+
for (let i = 0; i < a.count; i++) {
|
|
2023
|
+
for (let c = 0; c < n; c++) {
|
|
2024
|
+
const v = view[get](base + i * stride + c * size, true);
|
|
2025
|
+
data[i * n + c] = scale ? Math.max(v / scale, -1) : v;
|
|
2026
|
+
}
|
|
2027
|
+
}
|
|
2028
|
+
}
|
|
2029
|
+
const out = { numComponents: n, data };
|
|
2030
|
+
cache.set(index, out);
|
|
2031
|
+
return out;
|
|
2032
|
+
};
|
|
2033
|
+
|
|
2034
|
+
// nodes, parents first
|
|
2035
|
+
const src = json.nodes || [], parentOf = new Array(src.length).fill(-1);
|
|
2036
|
+
src.forEach((node, i) => { for (const c of node.children || []) parentOf[c] = i; });
|
|
2037
|
+
const order = [], remap = new Array(src.length).fill(-1);
|
|
2038
|
+
const visit = (i) => { remap[i] = order.length; order.push(i); for (const c of src[i].children || []) visit(c); };
|
|
2039
|
+
src.forEach((_, i) => { if (parentOf[i] < 0) visit(i); });
|
|
2040
|
+
|
|
2041
|
+
const names = [], parents = new Int32Array(order.length), rest = new Float32Array(order.length * 10);
|
|
2042
|
+
const xf = { pos: [0, 0, 0], rot: [0, 0, 0, 1], scl: [1, 1, 1] };
|
|
2043
|
+
order.forEach((old, i) => {
|
|
2044
|
+
const node = src[old];
|
|
2045
|
+
names.push(node.name || '');
|
|
2046
|
+
parents[i] = parentOf[old] < 0 ? -1 : remap[parentOf[old]];
|
|
2047
|
+
let t = node.translation || [0, 0, 0], r = node.rotation || [0, 0, 0, 1], s = node.scale || [1, 1, 1];
|
|
2048
|
+
if (node.matrix) { mat4ToTransform(xf, node.matrix); t = xf.pos; r = xf.rot; s = xf.scl; }
|
|
2049
|
+
rest.set(t, i * 10); rest.set(r, i * 10 + 3); rest.set(s, i * 10 + 7);
|
|
2050
|
+
});
|
|
2051
|
+
|
|
2052
|
+
// parts: one per triangle primitive per node
|
|
2053
|
+
const primitives = (json.meshes || []).map((mesh) => {
|
|
2054
|
+
const out = [];
|
|
2055
|
+
for (const prim of mesh.primitives) {
|
|
2056
|
+
if ((prim.mode ?? 4) !== 4 || prim.attributes.POSITION === undefined) continue;
|
|
2057
|
+
const arrays = {};
|
|
2058
|
+
for (const [semantic, key] of Object.entries(ATTRIBUTES)) {
|
|
2059
|
+
if (prim.attributes[semantic] !== undefined) arrays[key] = accessor(prim.attributes[semantic]);
|
|
2060
|
+
}
|
|
2061
|
+
const count = arrays.position.data.length / 3;
|
|
2062
|
+
const data = prim.indices !== undefined
|
|
2063
|
+
? Uint32Array.from(accessor(prim.indices).data)
|
|
2064
|
+
: Uint32Array.from({ length: count }, (_, i) => i);
|
|
2065
|
+
arrays.indices = { numComponents: 3, data };
|
|
2066
|
+
const targetNames = (mesh.extras && mesh.extras.targetNames) || [];
|
|
2067
|
+
const targets = (prim.targets || []).map((target, i) => {
|
|
2068
|
+
const t = { name: targetNames[i] || '' };
|
|
2069
|
+
if (target.POSITION !== undefined) t.position = accessor(target.POSITION);
|
|
2070
|
+
if (target.NORMAL !== undefined) t.normal = accessor(target.NORMAL);
|
|
2071
|
+
return t;
|
|
2072
|
+
});
|
|
2073
|
+
const material = prim.material !== undefined ? json.materials[prim.material] : null;
|
|
2074
|
+
const pbr = material && material.pbrMetallicRoughness;
|
|
2075
|
+
out.push({ arrays, targets, color: (pbr && pbr.baseColorFactor ? pbr.baseColorFactor : [1, 1, 1, 1]).slice() });
|
|
2076
|
+
}
|
|
2077
|
+
return { name: mesh.name || '', primitives: out };
|
|
2078
|
+
});
|
|
2079
|
+
|
|
2080
|
+
const parts = [];
|
|
2081
|
+
order.forEach((old, i) => {
|
|
2082
|
+
const node = src[old];
|
|
2083
|
+
if (node.mesh === undefined) return;
|
|
2084
|
+
const mesh = primitives[node.mesh];
|
|
2085
|
+
for (const p of mesh.primitives) {
|
|
2086
|
+
parts.push({ name: mesh.name || names[i], node: i, skin: node.skin ?? -1, mesh: p.arrays, targets: p.targets, color: p.color });
|
|
2087
|
+
}
|
|
2088
|
+
});
|
|
2089
|
+
|
|
2090
|
+
const skins = (json.skins || []).map((skin) => {
|
|
2091
|
+
const joints = Uint16Array.from(skin.joints, j => remap[j]);
|
|
2092
|
+
const inverseBind = skin.inverseBindMatrices !== undefined
|
|
2093
|
+
? Float32Array.from(accessor(skin.inverseBindMatrices).data)
|
|
2094
|
+
: Float32Array.from({ length: joints.length * 16 }, (_, k) => IDENTITY[k % 16]);
|
|
2095
|
+
return { name: skin.name || '', joints, inverseBind };
|
|
2096
|
+
});
|
|
2097
|
+
|
|
2098
|
+
const clips = (json.animations || []).map((anim) => {
|
|
2099
|
+
let duration = 0;
|
|
2100
|
+
const channels = [];
|
|
2101
|
+
for (const ch of anim.channels) {
|
|
2102
|
+
if (ch.target.node === undefined) continue;
|
|
2103
|
+
const sampler = anim.samplers[ch.sampler];
|
|
2104
|
+
const times = accessor(sampler.input).data, values = accessor(sampler.output).data;
|
|
2105
|
+
if (times.length) duration = Math.max(duration, times[times.length - 1]);
|
|
2106
|
+
channels.push({ node: remap[ch.target.node], path: ch.target.path, interp: sampler.interpolation || 'LINEAR', times, values });
|
|
2107
|
+
}
|
|
2108
|
+
return { name: anim.name || '', duration, channels };
|
|
2109
|
+
});
|
|
2110
|
+
|
|
2111
|
+
return { parts, nodes: { names, parents, rest }, skins, clips };
|
|
2112
|
+
}
|
|
1895
2113
|
|
|
1896
2114
|
/**
|
|
1897
|
-
* @file Media sources — images, video and Canvas2D rasters for a bridge's textures,
|
|
2115
|
+
* @file Media sources — images, video and Canvas2D rasters for a bridge's textures, models for its buffers.
|
|
1898
2116
|
* @module host/media
|
|
1899
2117
|
* @license AGPL-3.0-only
|
|
1900
2118
|
*
|
|
1901
|
-
* A
|
|
1902
|
-
*
|
|
2119
|
+
* A mesh arrives as arrays for a bridge's buffer upload, its bounds beside
|
|
2120
|
+
* them: loadMesh. A model keeps a file's structure — parts, hierarchy, skins,
|
|
2121
|
+
* clips: loadModel. Both read OBJ (host/obj) and glTF 2.0 (host/gltf), or
|
|
2122
|
+
* another format through a parser the caller passes.
|
|
1903
2123
|
*
|
|
1904
2124
|
* What a texture upload reads from: an ImageBitmap fetched from a URL, a
|
|
1905
2125
|
* hidden <video> element from a file or the user's camera, a bitmap drawn
|
|
@@ -1926,35 +2146,151 @@ async function loadImage(url, opts) {
|
|
|
1926
2146
|
return o.bitmap ? createImageBitmap(blob, o.bitmap) : createImageBitmap(blob);
|
|
1927
2147
|
}
|
|
1928
2148
|
|
|
2149
|
+
const _ONE_NODE = () => ({ names: [''], parents: Int32Array.of(-1), rest: Float32Array.of(0, 0, 0, 0, 0, 0, 1, 1, 1, 1) });
|
|
2150
|
+
const _part = (mesh) => ({ name: '', node: 0, skin: -1, mesh, targets: [], color: [1, 1, 1, 1] });
|
|
2151
|
+
|
|
2152
|
+
// A morph target's deltas expanded through its mesh's indices; a target carries no indices of its own.
|
|
2153
|
+
function _flatTarget(target, indices) {
|
|
2154
|
+
const flat = meshFlatten(target, indices);
|
|
2155
|
+
delete flat.indices;
|
|
2156
|
+
return flat;
|
|
2157
|
+
}
|
|
2158
|
+
|
|
2159
|
+
// The normals option applied to every part, then each mesh's bounds; parts sharing a mesh share the result.
|
|
2160
|
+
// undefined / true the file's normals; smooth ones where a mesh has none
|
|
2161
|
+
// 'smooth' always recomputed, summed per position (tree's meshGroups), so a faceted file smooths
|
|
2162
|
+
// 'flat' always recomputed on the flattened mesh (tree's meshFlatten), morph targets flattened with it
|
|
2163
|
+
// false nothing computed
|
|
2164
|
+
function _finish(model, o) {
|
|
2165
|
+
const mode = o.normals === undefined || o.normals === true ? 'fill' : o.normals;
|
|
2166
|
+
if (mode !== 'fill' && mode !== 'smooth' && mode !== 'flat' && mode !== false) {
|
|
2167
|
+
throw new Error("[host] model: normals is one of 'smooth', 'flat', false, or left out.");
|
|
2168
|
+
}
|
|
2169
|
+
const done = new Map();
|
|
2170
|
+
for (const part of model.parts) {
|
|
2171
|
+
const source = part.mesh;
|
|
2172
|
+
if (done.has(source)) {
|
|
2173
|
+
part.mesh = done.get(source);
|
|
2174
|
+
if (mode === 'flat') part.targets = part.targets.map(t => _flatTarget(t, source.indices.data));
|
|
2175
|
+
continue;
|
|
2176
|
+
}
|
|
2177
|
+
let mesh = source;
|
|
2178
|
+
const bounds = meshBounds({ min: [0, 0, 0], max: [0, 0, 0], center: [0, 0, 0], diag: 0 }, mesh.position.data);
|
|
2179
|
+
if (mode === 'flat') {
|
|
2180
|
+
mesh = meshFlatten(source);
|
|
2181
|
+
const p = mesh.position.data;
|
|
2182
|
+
mesh.normal = { numComponents: 3, data: meshNormals(new Float32Array(p.length), p, mesh.indices.data) };
|
|
2183
|
+
part.targets = part.targets.map(t => _flatTarget(t, source.indices.data));
|
|
2184
|
+
} else if (mode === 'smooth' || (mode === 'fill' && !mesh.normal)) {
|
|
2185
|
+
const p = mesh.position.data;
|
|
2186
|
+
const groups = meshGroups(new Int32Array(p.length / 3), p, 1e-6 * bounds.diag);
|
|
2187
|
+
mesh.normal = { numComponents: 3, data: meshNormals(new Float32Array(p.length), p, mesh.indices.data, { groups }) };
|
|
2188
|
+
}
|
|
2189
|
+
mesh.bounds = bounds;
|
|
2190
|
+
done.set(source, mesh);
|
|
2191
|
+
part.mesh = mesh;
|
|
2192
|
+
}
|
|
2193
|
+
return model;
|
|
2194
|
+
}
|
|
2195
|
+
|
|
2196
|
+
// Fetch and parse into the model shape, before the normals and bounds.
|
|
2197
|
+
async function _load(url, o, what) {
|
|
2198
|
+
const get = async (at, init) => {
|
|
2199
|
+
const res = await fetch(at, init);
|
|
2200
|
+
if (!res.ok) throw new Error('[host] ' + what + ': ' + at + ' → ' + res.status);
|
|
2201
|
+
return res;
|
|
2202
|
+
};
|
|
2203
|
+
if (typeof o.parse === 'function') {
|
|
2204
|
+
const mesh = await o.parse(await (await get(url, o.fetch)).arrayBuffer());
|
|
2205
|
+
return { parts: [_part(mesh)], nodes: _ONE_NODE(), skins: [], clips: [] };
|
|
2206
|
+
}
|
|
2207
|
+
const format = (o.format || (/\.(\w+)(?:[?#].*)?$/.exec(url) || [])[1] || '').toLowerCase();
|
|
2208
|
+
if (format !== 'obj' && format !== 'glb' && format !== 'gltf') {
|
|
2209
|
+
throw new Error('[host] ' + what + ': ' + url + ' → unknown format; opts.format is one of obj, glb, gltf, or pass opts.parse.');
|
|
2210
|
+
}
|
|
2211
|
+
const res = await get(url, o.fetch);
|
|
2212
|
+
if (format === 'obj') return { parts: [_part(parseObj(await res.text()))], nodes: _ONE_NODE(), skins: [], clips: [] };
|
|
2213
|
+
if (format === 'glb') {
|
|
2214
|
+
const { json, bin } = parseGlb(await res.arrayBuffer());
|
|
2215
|
+
return parseGltf(json, [bin]);
|
|
2216
|
+
}
|
|
2217
|
+
const json = await res.json();
|
|
2218
|
+
const base = typeof location !== 'undefined' ? new URL(url, location.href) : url;
|
|
2219
|
+
const resolve = (uri) => {
|
|
2220
|
+
if (/^data:/.test(uri)) return uri;
|
|
2221
|
+
try { return String(new URL(uri, base)); } catch (e) { return url.replace(/[^/]*$/, '') + uri; }
|
|
2222
|
+
};
|
|
2223
|
+
const buffers = await Promise.all((json.buffers || []).map(async b => (await get(resolve(b.uri), o.fetch)).arrayBuffer()));
|
|
2224
|
+
return parseGltf(json, buffers);
|
|
2225
|
+
}
|
|
2226
|
+
|
|
2227
|
+
/**
|
|
2228
|
+
* Fetch a mesh: one object in the arrays shape — position, indices, and
|
|
2229
|
+
* normal, tangent, texcoord, color, joints, weights when the file carries them
|
|
2230
|
+
* — with `bounds`, its extent `{ min, max, center, diag }`, beside them. A
|
|
2231
|
+
* bridge's buffer upload takes it as it is; `size / mesh.bounds.diag` is the
|
|
2232
|
+
* scale that fits any file to `size`.
|
|
2233
|
+
*
|
|
2234
|
+
* Formats: OBJ (host/obj), and glTF 2.0 as `.glb` or `.gltf` (host/gltf) when
|
|
2235
|
+
* the file holds one part. A file of several parts rejects, naming loadModel:
|
|
2236
|
+
* a mesh is never silently the first of many. `opts.parse` reads any other
|
|
2237
|
+
* format: it receives the fetched ArrayBuffer and returns the arrays shape.
|
|
2238
|
+
*
|
|
2239
|
+
* Normals. Left out, the option keeps the file's normals and gives smooth
|
|
2240
|
+
* ones to a mesh that has none. 'smooth' always recomputes them, summed per
|
|
2241
|
+
* position, so a file that arrives faceted or cut along a texture seam
|
|
2242
|
+
* smooths too. 'flat' always recomputes them on the flattened mesh — every
|
|
2243
|
+
* triangle owning its vertices, so the vertex count grows and the indices
|
|
2244
|
+
* count up. false computes nothing. The procedure, its limits (no crease
|
|
2245
|
+
* angle; grouping by grid cell, at 1e-6 of the bounds' diagonal) and its
|
|
2246
|
+
* sources are tree/mesh's. glTF viewers compute flat normals for a file
|
|
2247
|
+
* without any; here that is `normals: 'flat'`.
|
|
2248
|
+
*
|
|
2249
|
+
* @param {string} url
|
|
2250
|
+
* @param {{ fetch?:object, format?:string, parse?:function(ArrayBuffer):object,
|
|
2251
|
+
* normals?:'smooth'|'flat'|false }} [opts] fetch: the fetch init. format: 'obj' |
|
|
2252
|
+
* 'glb' | 'gltf' when the URL's extension does not say. parse: a parser for another
|
|
2253
|
+
* format. normals: as above.
|
|
2254
|
+
* @returns {Promise<object>} The mesh.
|
|
2255
|
+
*/
|
|
2256
|
+
async function loadMesh(url, opts) {
|
|
2257
|
+
const o = opts || {};
|
|
2258
|
+
const model = await _load(url, o, 'mesh');
|
|
2259
|
+
if (model.parts.length !== 1) {
|
|
2260
|
+
throw new Error('[host] mesh: ' + url + ' → ' + model.parts.length + ' parts; loadModel keeps them apart.');
|
|
2261
|
+
}
|
|
2262
|
+
return _finish(model, o).parts[0].mesh;
|
|
2263
|
+
}
|
|
2264
|
+
|
|
1929
2265
|
/**
|
|
1930
|
-
* Fetch
|
|
1931
|
-
*
|
|
1932
|
-
* takes the result as it is.
|
|
2266
|
+
* Fetch a model with its structure — what a rig, morph targets or animation
|
|
2267
|
+
* need; a single mesh is loadMesh's:
|
|
1933
2268
|
*
|
|
1934
|
-
*
|
|
1935
|
-
*
|
|
2269
|
+
* { parts: [{ name, node, skin, mesh, targets, color }], nodes, skins, clips }
|
|
2270
|
+
*
|
|
2271
|
+
* A part is one triangle primitive under one node: `mesh` as loadMesh returns
|
|
2272
|
+
* it (the arrays shape with `bounds`, in the part's own space), `targets` its
|
|
2273
|
+
* morph targets as delta arrays, `color` its base colour [r, g, b, a], `node`
|
|
2274
|
+
* the node it hangs from and `skin` its skin's index or −1. `nodes` is the
|
|
2275
|
+
* hierarchy `{ names, parents, rest }`, parents first, `rest` a pose of ten
|
|
2276
|
+
* numbers per node; `skins` are `{ name, joints, inverseBind }` and `clips`
|
|
2277
|
+
* `{ name, duration, channels }` — what tree's clipSample, poseWorld and
|
|
2278
|
+
* jointPalette take. A rigid part draws under its node's world matrix, a
|
|
2279
|
+
* skinned one under its skin's joint palette. An OBJ file is one white part
|
|
2280
|
+
* under one identity node.
|
|
2281
|
+
*
|
|
2282
|
+
* The options are loadMesh's; `normals` applies to every part, 'flat'
|
|
2283
|
+
* flattening each part's morph targets with its mesh.
|
|
1936
2284
|
*
|
|
1937
2285
|
* @param {string} url
|
|
1938
|
-
* @param {{ fetch?:object
|
|
1939
|
-
*
|
|
1940
|
-
*
|
|
1941
|
-
*
|
|
1942
|
-
* indices:{numComponents:number,data:Uint32Array} }>}
|
|
2286
|
+
* @param {{ fetch?:object, format?:string, parse?:function(ArrayBuffer):object,
|
|
2287
|
+
* normals?:'smooth'|'flat'|false }} [opts]
|
|
2288
|
+
* @returns {Promise<{ parts:object[], nodes:{ names:string[], parents:Int32Array, rest:Float32Array },
|
|
2289
|
+
* skins:object[], clips:object[] }>}
|
|
1943
2290
|
*/
|
|
1944
2291
|
async function loadModel(url, opts) {
|
|
1945
2292
|
const o = opts || {};
|
|
1946
|
-
|
|
1947
|
-
if (!res.ok) throw new Error('[host] model: ' + url + ' → ' + res.status);
|
|
1948
|
-
const mesh = new OBJ.Mesh(await res.text());
|
|
1949
|
-
const out = {
|
|
1950
|
-
position: { numComponents: 3, data: new Float32Array(mesh.vertices) },
|
|
1951
|
-
indices: { numComponents: 3, data: new Uint32Array(mesh.indices) },
|
|
1952
|
-
};
|
|
1953
|
-
// a file without normals leaves the loader's normals unset, one per component
|
|
1954
|
-
const n = mesh.vertexNormals;
|
|
1955
|
-
if (n.length && n.every(Number.isFinite)) out.normal = { numComponents: 3, data: new Float32Array(n) };
|
|
1956
|
-
if (mesh.textures.length) out.texcoord = { numComponents: mesh.textureStride, data: new Float32Array(mesh.textures) };
|
|
1957
|
-
return out;
|
|
2293
|
+
return _finish(await _load(url, o, 'model'), o);
|
|
1958
2294
|
}
|
|
1959
2295
|
|
|
1960
2296
|
/**
|
|
@@ -2265,9 +2601,14 @@ function createLabels(host, opts) {
|
|
|
2265
2601
|
* Two-pointer motion is computed from the pointers' own coordinates, never
|
|
2266
2602
|
* from a browser's pinch synthesis, so it reads the same on every platform;
|
|
2267
2603
|
* the canvas gets touch-action: none while the orbit lives so the browser
|
|
2268
|
-
* does not scroll or zoom the page instead.
|
|
2269
|
-
*
|
|
2270
|
-
*
|
|
2604
|
+
* does not scroll or zoom the page instead. `damping`, seconds, makes the
|
|
2605
|
+
* camera trail the gesture through a first-order lag: each step of a drag —
|
|
2606
|
+
* orbit, pan and dolly lanes — and each wheel notch is an impulse the camera
|
|
2607
|
+
* drains with that time constant through tree's coast, so it follows the
|
|
2608
|
+
* finger about `damping` behind and, after a release, travels the rest of
|
|
2609
|
+
* what the finger did and no more, a flick easing out. A fresh touch drops
|
|
2610
|
+
* what is still draining, so do home() and enabled = false. The default is
|
|
2611
|
+
* 0.2 s; 0 makes the orbit exact.
|
|
2271
2612
|
*
|
|
2272
2613
|
* Screen y runs down; which world direction that is depends on the
|
|
2273
2614
|
* projection installed in the view bag: a y-up projection (GL's, tree's
|
|
@@ -2278,19 +2619,23 @@ function createLabels(host, opts) {
|
|
|
2278
2619
|
|
|
2279
2620
|
|
|
2280
2621
|
const _clamp = { min: 0, max: Infinity };
|
|
2622
|
+
const _now$1 = () => (typeof performance !== 'undefined' && performance.now) ? performance.now() : Date.now();
|
|
2623
|
+
const EPS = 1e-4; // a rate below this in every lane ends the drain
|
|
2624
|
+
const DT_MAX = 0.1; // seconds, the measured frame's clamp
|
|
2281
2625
|
|
|
2282
2626
|
/**
|
|
2283
2627
|
* Create an orbit on a camera state.
|
|
2284
2628
|
*
|
|
2285
2629
|
* @param {object} host
|
|
2286
2630
|
* @param {object} cam The camera state written.
|
|
2287
|
-
* @param {{ rotate?:number, pan?:number, zoom?:number, wheel?:number,
|
|
2631
|
+
* @param {{ rotate?:number, pan?:number, zoom?:number, wheel?:number, damping?:number,
|
|
2288
2632
|
* minDistance?:number, maxDistance?:number, enabled?:boolean }} [opts]
|
|
2289
2633
|
* rotate: radians per pixel (default 0.005). pan / zoom / wheel:
|
|
2290
2634
|
* scalars on the two-pointer pan, the pinch and the wheel dolly
|
|
2291
|
-
* (default 1).
|
|
2292
|
-
*
|
|
2293
|
-
*
|
|
2635
|
+
* (default 1). damping: the lag's time constant in seconds (default
|
|
2636
|
+
* 0.2; 0 exact). minDistance / maxDistance: gaze-distance clamps.
|
|
2637
|
+
* @returns {object} The orbit: { cam, enabled, rotate, pan, zoom, wheel, damping,
|
|
2638
|
+
* minDistance, maxDistance, update(dt), home(), dispose() }.
|
|
2294
2639
|
*/
|
|
2295
2640
|
function createOrbit(host, cam, opts) {
|
|
2296
2641
|
const o = opts || {};
|
|
@@ -2298,6 +2643,16 @@ function createOrbit(host, cam, opts) {
|
|
|
2298
2643
|
const home = cameraCopy(createCamera(), cam);
|
|
2299
2644
|
const tracked = new Map(); // pointer id → { x, y } as last consumed
|
|
2300
2645
|
let wheelAcc = 0;
|
|
2646
|
+
const rate = [0, 0, 0, 0, 0]; // what is still draining: dAz, dEl, panX, panY, log dolly — per second
|
|
2647
|
+
const step = [0, 0, 0, 0, 0]; // one frame of it
|
|
2648
|
+
let lastNow = 0, wasPressed = false;
|
|
2649
|
+
|
|
2650
|
+
const cancel = () => { for (let i = 0; i < 5; i++) rate[i] = 0; };
|
|
2651
|
+
const apply = (s) => {
|
|
2652
|
+
if (s[0] !== 0 || s[1] !== 0) cameraOrbit(cam, s[0], s[1]);
|
|
2653
|
+
if (s[2] !== 0 || s[3] !== 0) cameraPan(cam, s[2], s[3]);
|
|
2654
|
+
if (s[4] !== 0) dolly(Math.exp(s[4]));
|
|
2655
|
+
};
|
|
2301
2656
|
|
|
2302
2657
|
const onWheel = (e) => {
|
|
2303
2658
|
if (typeof e.preventDefault === 'function') e.preventDefault();
|
|
@@ -2335,16 +2690,23 @@ function createOrbit(host, cam, opts) {
|
|
|
2335
2690
|
zoom: o.zoom ?? 1,
|
|
2336
2691
|
/** Scalar on the wheel dolly. */
|
|
2337
2692
|
wheel: o.wheel ?? 1,
|
|
2693
|
+
/** The lag's time constant, seconds; 0 makes the orbit exact. */
|
|
2694
|
+
damping: o.damping ?? 0.2,
|
|
2338
2695
|
/** Gaze-distance clamps for every dolly. */
|
|
2339
2696
|
minDistance: o.minDistance ?? 0,
|
|
2340
2697
|
maxDistance: o.maxDistance ?? Infinity,
|
|
2341
2698
|
|
|
2342
2699
|
/**
|
|
2343
|
-
* Consume this frame's unclaimed pointer motion and wheel into cam
|
|
2700
|
+
* Consume this frame's unclaimed pointer motion and wheel into cam —
|
|
2701
|
+
* through the lag when damping is on.
|
|
2702
|
+
* @param {number} [dt] The frame in seconds; measured since the last update when omitted.
|
|
2344
2703
|
* @returns {boolean} Whether the camera moved.
|
|
2345
2704
|
*/
|
|
2346
|
-
update() {
|
|
2347
|
-
|
|
2705
|
+
update(dt) {
|
|
2706
|
+
const now = _now$1();
|
|
2707
|
+
if (typeof dt !== 'number') dt = lastNow ? Math.min((now - lastNow) / 1000, DT_MAX) : 0;
|
|
2708
|
+
lastNow = now;
|
|
2709
|
+
if (!orbit.enabled) { tracked.clear(); wheelAcc = 0; cancel(); wasPressed = false; return false; }
|
|
2348
2710
|
const src = host.pointer;
|
|
2349
2711
|
let a = null, b = null;
|
|
2350
2712
|
for (const p of src.pointers.values()) {
|
|
@@ -2356,14 +2718,19 @@ function createOrbit(host, cam, opts) {
|
|
|
2356
2718
|
for (const id of tracked.keys()) {
|
|
2357
2719
|
if ((a === null || id !== a.id) && (b === null || id !== b.id)) tracked.delete(id);
|
|
2358
2720
|
}
|
|
2721
|
+
const tau = orbit.damping > 0 ? orbit.damping : 0;
|
|
2722
|
+
const pressed = a !== null;
|
|
2723
|
+
if (pressed && !wasPressed) cancel(); // a fresh touch catches the scene: what was still draining is dropped
|
|
2724
|
+
wasPressed = pressed;
|
|
2359
2725
|
let moved = false;
|
|
2726
|
+
let az = 0, el = 0, px = 0, py = 0, ld = 0; // this frame's step
|
|
2360
2727
|
const view = host.view;
|
|
2361
2728
|
const ys = view.mat4Proj[5] < 0 ? -1 : 1; // screen-down is the eye's −up under a y-up projection, +up under p5's flip
|
|
2362
2729
|
if (a !== null && b === null) {
|
|
2363
2730
|
const t = tracked.get(a.id);
|
|
2364
2731
|
if (t) {
|
|
2365
2732
|
const dx = a.x - t.x, dy = a.y - t.y;
|
|
2366
|
-
if (dx !== 0 || dy !== 0) {
|
|
2733
|
+
if (dx !== 0 || dy !== 0) { az = -dx * orbit.rotate; el = ys * dy * orbit.rotate; }
|
|
2367
2734
|
}
|
|
2368
2735
|
track(a);
|
|
2369
2736
|
} else if (a !== null && b !== null) {
|
|
@@ -2372,30 +2739,37 @@ function createOrbit(host, cam, opts) {
|
|
|
2372
2739
|
const dmx = (a.x + b.x - ta.x - tb.x) / 2, dmy = (a.y + b.y - ta.y - tb.y) / 2;
|
|
2373
2740
|
if (dmx !== 0 || dmy !== 0) {
|
|
2374
2741
|
const ratio = pixelRatio(view.mat4Proj, -view.vp[3] || 1, -distance(), view.ndcZMin);
|
|
2375
|
-
|
|
2376
|
-
moved = true;
|
|
2742
|
+
px = -dmx * ratio * orbit.pan; py = ys * dmy * ratio * orbit.pan;
|
|
2377
2743
|
}
|
|
2378
2744
|
const d0 = Math.hypot(ta.x - tb.x, ta.y - tb.y), d1 = Math.hypot(a.x - b.x, a.y - b.y);
|
|
2379
|
-
if (d0 > 0 && d1 > 0 && d0 !== d1)
|
|
2745
|
+
if (d0 > 0 && d1 > 0 && d0 !== d1) ld = orbit.zoom * Math.log(d0 / d1);
|
|
2380
2746
|
}
|
|
2381
2747
|
track(a); track(b);
|
|
2382
2748
|
}
|
|
2749
|
+
// the step: exact, or — with damping — an impulse the drain below spreads over time
|
|
2750
|
+
if (az !== 0 || el !== 0 || px !== 0 || py !== 0 || ld !== 0) {
|
|
2751
|
+
if (tau > 0) { rate[0] += az / tau; rate[1] += el / tau; rate[2] += px / tau; rate[3] += py / tau; rate[4] += ld / tau; }
|
|
2752
|
+
else { step[0] = az; step[1] = el; step[2] = px; step[3] = py; step[4] = ld; apply(step); moved = true; }
|
|
2753
|
+
}
|
|
2383
2754
|
if (wheelAcc !== 0) {
|
|
2384
|
-
|
|
2755
|
+
const w = wheelAcc * 0.001 * orbit.wheel;
|
|
2385
2756
|
wheelAcc = 0;
|
|
2386
|
-
|
|
2757
|
+
if (tau > 0) rate[4] += w / tau;
|
|
2758
|
+
else { dolly(Math.exp(w)); moved = true; }
|
|
2387
2759
|
}
|
|
2760
|
+
if (tau > 0 && coastAlive(rate, EPS)) { apply(coastStep(step, rate, dt, tau)); moved = true; }
|
|
2388
2761
|
return moved;
|
|
2389
2762
|
},
|
|
2390
2763
|
|
|
2391
|
-
/** Restore the pose captured at creation. */
|
|
2392
|
-
home() { cameraCopy(cam, home); return orbit; },
|
|
2764
|
+
/** Restore the pose captured at creation; whatever was draining is dropped. */
|
|
2765
|
+
home() { cameraCopy(cam, home); cancel(); return orbit; },
|
|
2393
2766
|
|
|
2394
2767
|
/** Remove the wheel listener, restore touch-action, unregister from the host. */
|
|
2395
2768
|
dispose() {
|
|
2396
2769
|
canvas.removeEventListener('wheel', onWheel);
|
|
2397
2770
|
if (canvas.style) canvas.style.touchAction = touchAction;
|
|
2398
2771
|
tracked.clear();
|
|
2772
|
+
cancel();
|
|
2399
2773
|
host.unregister(orbit);
|
|
2400
2774
|
return orbit;
|
|
2401
2775
|
},
|
|
@@ -2420,7 +2794,7 @@ function createOrbit(host, cam, opts) {
|
|
|
2420
2794
|
* host.cameraHelm(cam, opts) · host.poseHelm(opts)
|
|
2421
2795
|
* host.poseTrack(opts) · host.cameraTrack(cam, opts)
|
|
2422
2796
|
* host.hid(opts) · host.gamepad(opts)
|
|
2423
|
-
* host.image(url) · host.video(opts) · host.raster(draw, w, h) · host.model(url)
|
|
2797
|
+
* host.image(url) · host.video(opts) · host.raster(draw, w, h) · host.mesh(url) · host.model(url)
|
|
2424
2798
|
* host.labels // the DOM label layer, created on first access
|
|
2425
2799
|
* host.orbit(cam, opts)
|
|
2426
2800
|
* host.tick(dt) // external-loop mode
|
|
@@ -2515,7 +2889,9 @@ function createHost(canvas, opts) {
|
|
|
2515
2889
|
video(opts) { return host.register(createVideo(opts)); },
|
|
2516
2890
|
/** Draw through a 2D context into a texture source — see host/media. */
|
|
2517
2891
|
raster(draw, w, h, opts) { return raster(draw, w, h, opts); },
|
|
2518
|
-
/** Fetch
|
|
2892
|
+
/** Fetch one mesh — the arrays shape with its bounds — from an OBJ or glTF file, or any format through opts.parse — see host/media. */
|
|
2893
|
+
mesh(url, opts) { return loadMesh(url, opts); },
|
|
2894
|
+
/** Fetch a model with its structure: parts, nodes, skins, clips — see host/media. */
|
|
2519
2895
|
model(url, opts) { return loadModel(url, opts); },
|
|
2520
2896
|
/**
|
|
2521
2897
|
* The label layer — see host/labels. Created on first access, so a
|
|
@@ -2572,5 +2948,5 @@ function createHost(canvas, opts) {
|
|
|
2572
2948
|
return host;
|
|
2573
2949
|
}
|
|
2574
2950
|
|
|
2575
|
-
export { GAMEPAD_MAP, HID_FILTERS, Handle, PointerRouter, TrackHandles, VIEW, cameraHelm, cameraTrack, createGamepad, createHid, createHost, createLabels, createLoop, createOrbit, createPlayers, createPointer, createVideo, createView, decodeSpaceNavigator, helmBasis, isConstraint, loadImage, loadModel, measureCanvas, observeCanvas, poseHelm, poseTrack, raster, validConstraint };
|
|
2951
|
+
export { GAMEPAD_MAP, HID_FILTERS, Handle, PointerRouter, TrackHandles, VIEW, cameraHelm, cameraTrack, createGamepad, createHid, createHost, createLabels, createLoop, createOrbit, createPlayers, createPointer, createVideo, createView, decodeSpaceNavigator, helmBasis, isConstraint, loadImage, loadMesh, loadModel, measureCanvas, observeCanvas, poseHelm, poseTrack, raster, validConstraint };
|
|
2576
2952
|
//# sourceMappingURL=index.js.map
|