├── .eslintrc
├── .gitignore
├── LICENSE
├── README.md
├── example
├── index.html
├── src
│ ├── app.ts
│ ├── camera.ts
│ ├── cubemap.ts
│ ├── inputs.ts
│ ├── renderer.ts
│ └── shaders
│ │ ├── default-shader.ts
│ │ └── shader-loader.ts
└── static
│ ├── environment
│ ├── brdf_lut.png
│ ├── diffuse_back.jpg
│ ├── diffuse_bottom.jpg
│ ├── diffuse_front.jpg
│ ├── diffuse_left.jpg
│ ├── diffuse_right.jpg
│ ├── diffuse_top.jpg
│ ├── specular_back.jpg
│ ├── specular_bottom.jpg
│ ├── specular_front.jpg
│ ├── specular_left.jpg
│ ├── specular_right.jpg
│ └── specular_top.jpg
│ ├── models
│ ├── cesium
│ │ ├── cesium.bin
│ │ ├── cesium.gltf
│ │ └── cesium.jpg
│ ├── cylinder
│ │ ├── cylinder.bin
│ │ └── cylinder.gltf
│ ├── launcher
│ │ ├── launcher.bin
│ │ ├── launcher.gltf
│ │ ├── launcher_baseColor.png
│ │ ├── launcher_emissive.png
│ │ ├── launcher_normal.png
│ │ └── launcher_occlusionRoughnessMetallic.png
│ ├── platform
│ │ ├── platform.bin
│ │ ├── platform.gltf
│ │ ├── platform_baseColor.png
│ │ ├── platform_normal.png
│ │ └── platform_occlusionRoughnessMetallic.png
│ ├── robot
│ │ ├── robot.bin
│ │ ├── robot.gltf
│ │ ├── robot_baseColor.png
│ │ ├── robot_normal.png
│ │ └── robot_occlusionRoughnessMetallic.png
│ ├── suzanne
│ │ ├── baseColor.png
│ │ ├── roughness.png
│ │ ├── suzanne.bin
│ │ └── suzanne.gltf
│ └── waterbottle
│ │ ├── WaterBottle.bin
│ │ ├── base-color.png
│ │ ├── emissive.png
│ │ ├── metallic-roughness.png
│ │ ├── normal.png
│ │ ├── occlusion.png
│ │ └── waterbottle.gltf
│ └── shaders
│ ├── default.frag
│ └── default.vert
├── package-lock.json
├── package.json
├── src
├── tsconfig.json
└── webgl-gltf
│ ├── animation.ts
│ ├── animator.ts
│ ├── gltf.ts
│ ├── index.ts
│ ├── mat.ts
│ └── types
│ ├── gltf.d.ts
│ └── model.d.ts
├── tsconfig.json
├── webpack.config.js
└── yarn.lock
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "@typescript-eslint/parser",
3 | "parserOptions": {
4 | "ecmaVersion": 6,
5 | "sourceType": "module",
6 | "ecmaFeatures": {
7 | "modules": true
8 | }
9 | },
10 | "plugins": [
11 | "@typescript-eslint"
12 | ],
13 | "extends": [
14 | "plugin:@typescript-eslint/recommended"
15 | ],
16 | "rules": {
17 | "quotes": [2, "single"],
18 | "@typescript-eslint/explicit-function-return-type": "off",
19 | "@typescript-eslint/no-non-null-assertion": "off",
20 | "@typescript-eslint/explicit-module-boundary-types": "off"
21 | },
22 | "overrides": [{
23 | "files": ["*.js", "*.jsx"],
24 | "rules": {
25 | "@typescript-eslint/no-var-requires": "off"
26 | }
27 | }]
28 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | dist/
3 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | ISC License
2 |
3 | Copyright (c) 2020, Lars Jarlvik
4 |
5 | Permission to use, copy, modify, and/or distribute this software for any
6 | purpose with or without fee is hereby granted, provided that the above
7 | copyright notice and this permission notice appear in all copies.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # webgl-gltf
2 | A small GLTF loader For WebGL. Only depends on *gl-matrix*.
3 |
4 | Supports both WebGL and WebGL 2.
5 |
6 | Please see example folder for implementation.
7 |
8 | ## Documentation
9 | Please see the wiki for documentation
10 | [Wiki](https://github.com/larsjarlvik/webgl-gltf/wiki)
11 |
12 | ## Supports
13 | * Multiple meshes
14 | * Textures
15 | * PBR materials (base color, metal-roughness, normal, occlusion and emissive)
16 | * Animations
17 | * Animation blending
18 | * Multiple animation tracks
19 | * GLTF+bin files
20 | * GLTF+bin files over the Internet (CORS should be enabled for all files)
21 | ## Does NOT support
22 | * Fully binary (`.glb`) files
23 | * GLTF with embedded data
24 | * Any GLTF extensions
25 | * All animation interpolations are treated as LINEAR
26 |
27 | ## Demo
28 | * Robot: https://larsjarlvik.github.io/?model=robot
29 | * Waterbottle: https://larsjarlvik.github.io/?model=waterbottle
30 | * Cesium man (animation): https://larsjarlvik.github.io/?model=cesium
31 |
32 | ## Installation
33 | * `npm install webgl-gltf`
34 |
35 | ## Run example code
36 | * `npm install`
37 | * `npm run start`
38 |
39 | *Models curtesy of https://github.com/KhronosGroup/glTF-Sample-Models*
40 |
--------------------------------------------------------------------------------
/example/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | WebGL GLTF Viewer
6 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/example/src/app.ts:
--------------------------------------------------------------------------------
1 | import * as shader from './shaders/shader-loader';
2 | import * as defaultShader from './shaders/default-shader';
3 | import * as camera from './camera';
4 | import * as inputs from './inputs';
5 | import * as cubemap from './cubemap';
6 | import { renderModel } from './renderer';
7 | import { DefaultShader } from './shaders/default-shader';
8 |
9 | import * as gltf from 'webgl-gltf';
10 |
11 | const canvas = document.getElementById('canvas') as HTMLCanvasElement;
12 | const gl = canvas.getContext('webgl') as WebGLRenderingContext;
13 |
14 | const track = 'track';
15 | const blendTime = 300;
16 | let lastFrame = 0;
17 |
18 | const cam = {
19 | rY: 0.0,
20 | rX: 0.0,
21 | distance: 3.0,
22 | } as camera.Camera;
23 |
24 | const setSize = () => {
25 | const devicePixelRatio = window.devicePixelRatio || 1;
26 |
27 | canvas.width = window.innerWidth * devicePixelRatio;
28 | canvas.height = window.innerHeight * devicePixelRatio;
29 | gl.viewport(0, 0, canvas.width, canvas.height);
30 | }
31 |
32 | if (!gl) {
33 | alert('WebGL not available')
34 | }
35 |
36 | const listAnimations = (models: gltf.Model[]) => {
37 | models.forEach(model => {
38 | if (Object.keys(model.animations).length === 0) return;
39 |
40 | gltf.pushAnimation(track, 'default', model.name, Object.keys(model.animations)[0]);
41 |
42 | const ui = document.getElementById('ui') as HTMLElement;
43 | Object.keys(model.animations).forEach(a => {
44 | const btn = document.createElement('button');
45 | btn.innerText = a;
46 | btn.addEventListener('click', () => gltf.pushAnimation(track, 'default', model.name, a));
47 | ui.appendChild(btn);
48 | });
49 | });
50 | };
51 |
52 | const render = (uniforms: DefaultShader, models: gltf.Model[]) => {
53 | gl.clear(gl.COLOR_BUFFER_BIT);
54 |
55 | const cameraMatrix = camera.update(cam, canvas.width, canvas.height);
56 | gl.uniform3f(uniforms.cameraPosition, cameraMatrix.position[0], cameraMatrix.position[1], cameraMatrix.position[2]);
57 | gl.uniformMatrix4fv(uniforms.pMatrix, false, cameraMatrix.pMatrix);
58 | gl.uniformMatrix4fv(uniforms.vMatrix, false, cameraMatrix.vMatrix);
59 |
60 | models.forEach(model => {
61 | const animation = gltf.getActiveAnimations('default', model.name);
62 |
63 | if (animation) {
64 | const animationTransforms = gltf.getAnimationTransforms(model, animation, blendTime);
65 | gltf.applyToSkin(model, animationTransforms).forEach((x, i) => {
66 | gl.uniformMatrix4fv(uniforms.jointTransform[i], false, x);
67 | });
68 |
69 | gl.uniform1i(uniforms.isAnimated, 1);
70 | } else {
71 | gl.uniform1i(uniforms.isAnimated, 0);
72 | }
73 |
74 | renderModel(gl, model, model.rootNode, model.nodes[model.rootNode].localBindTransform, uniforms);
75 | });
76 |
77 | gltf.advanceAnimation(performance.now() - lastFrame);
78 |
79 | requestAnimationFrame(() => {
80 | render(uniforms, models);
81 | lastFrame = performance.now();
82 | });
83 | };
84 |
85 | const startup = async () => {
86 | gl.clearColor(0.3, 0.3, 0.3, 1);
87 | gl.enable(gl.DEPTH_TEST);
88 |
89 | window.onresize = () => { setSize(); };
90 | setSize();
91 |
92 | const program = shader.createProgram(gl);
93 | gl.attachShader(program, await shader.loadShader(gl, 'default.vert', gl.VERTEX_SHADER));
94 | gl.attachShader(program, await shader.loadShader(gl, 'default.frag', gl.FRAGMENT_SHADER));
95 | shader.linkProgram(gl, program);
96 |
97 | const uniforms = defaultShader.getUniformLocations(gl, program);
98 |
99 | const environment = await cubemap.load(gl);
100 | const urlParams = new URLSearchParams(window.location.search);
101 | const modelNames = urlParams.get('model') || 'robot';
102 | const models = await Promise.all(modelNames.split(',').map(m => gltf.loadModel(gl, `/models/${m}/${m}.gltf`)));
103 | listAnimations(models);
104 | console.log(models);
105 |
106 | cubemap.bind(gl, environment, uniforms.brdfLut, uniforms.environmentDiffuse, uniforms.environmentSpecular);
107 |
108 | document.getElementById('loading')?.remove();
109 |
110 | render(uniforms, models);
111 | };
112 |
113 | const rotate = (delta: inputs.Position) => {
114 | cam.rX += delta.y;
115 | cam.rY += delta.x;
116 | };
117 |
118 | const zoom = (delta: number) => {
119 | cam.distance *= 1.0 + delta;
120 | if (cam.distance < 0.0) cam.distance = 0.0;
121 | };
122 |
123 | inputs.listen(canvas, rotate, zoom);
124 | startup();
125 |
--------------------------------------------------------------------------------
/example/src/camera.ts:
--------------------------------------------------------------------------------
1 | import { mat4, vec3 } from 'gl-matrix';
2 |
3 | interface CameraMatrix {
4 | pMatrix: mat4;
5 | vMatrix: mat4;
6 | position: vec3;
7 | }
8 |
9 | interface Camera {
10 | rY: number;
11 | rX: number;
12 | distance: number;
13 | }
14 |
15 |
16 | const getPosition = (camera: Camera) => vec3.fromValues(
17 | camera.distance * Math.sin(-camera.rY) * Math.cos(-camera.rX),
18 | camera.distance * Math.sin(camera.rX),
19 | camera.distance * Math.cos(-camera.rY) * Math.cos(-camera.rX),
20 | );
21 |
22 | const update = (camera: Camera, viewportWidth: number, viewportHeight: number): CameraMatrix => {
23 | const pMatrix = mat4.create();
24 | const vMatrix = mat4.create();
25 | const position = getPosition(camera);
26 |
27 | mat4.translate(vMatrix, vMatrix, vec3.fromValues(0.0, 0.0, -camera.distance));
28 | mat4.rotateX(vMatrix, vMatrix, camera.rX);
29 | mat4.rotateY(vMatrix, vMatrix, camera.rY);
30 |
31 | mat4.perspective(pMatrix, 45.0, viewportWidth / viewportHeight, 0.1, 100.0);
32 |
33 | return { pMatrix, vMatrix, position };
34 | };
35 |
36 | export {
37 | Camera,
38 | update,
39 | };
40 |
--------------------------------------------------------------------------------
/example/src/cubemap.ts:
--------------------------------------------------------------------------------
1 |
2 |
3 | interface Environment {
4 | diffuse: WebGLTexture;
5 | specular: WebGLTexture;
6 | brdfLut: WebGLTexture;
7 | }
8 |
9 | const getImage = async (uri: string) => {
10 | return new Promise(resolve => {
11 | const img = new Image();
12 | img.onload = () => {
13 | resolve(img);
14 | }
15 | img.src = uri;
16 | });
17 | };
18 |
19 | const createCubeMap = (gl: WebGLRenderingContext, textures: HTMLImageElement[]) => {
20 | const cubeMap = gl.createTexture();
21 | gl.bindTexture(gl.TEXTURE_CUBE_MAP, cubeMap);
22 | textures.forEach((t, i) => {
23 | gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, t);
24 | });
25 | gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);
26 | gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
27 | gl.generateMipmap(gl.TEXTURE_CUBE_MAP);
28 | return cubeMap;
29 | };
30 |
31 | const load = async (gl: WebGLRenderingContext): Promise => {
32 | const names = ['right', 'left', 'top', 'bottom', 'front', 'back'];
33 | const diffuseTextures = await Promise.all(names.map(n => getImage(`environment/diffuse_${n}.jpg`)));
34 | const specularTextures = await Promise.all(names.map(n => getImage(`environment/specular_${n}.jpg`)));
35 |
36 | const diffuse = createCubeMap(gl, diffuseTextures);
37 | const specular = createCubeMap(gl, specularTextures);
38 |
39 | const brdfLutTexture = await getImage('environment/brdf_lut.png');
40 | const brdfLut = gl.createTexture();
41 | gl.bindTexture(gl.TEXTURE_2D, brdfLut);
42 | gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, brdfLutTexture);
43 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);
44 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
45 | gl.generateMipmap(gl.TEXTURE_2D);
46 |
47 | if (!diffuse || !specular || !brdfLut) {
48 | throw new Error('Failed to load environment!');
49 | }
50 |
51 | return {
52 | diffuse,
53 | specular,
54 | brdfLut,
55 | };
56 | };
57 |
58 | const bind = (gl: WebGLRenderingContext, environment: Environment, brfdLutTarget: WebGLUniformLocation, diffuseTarget: WebGLUniformLocation, specularTarget: WebGLUniformLocation) => {
59 | gl.activeTexture(gl.TEXTURE5);
60 | gl.bindTexture(gl.TEXTURE_2D, environment.brdfLut);
61 | gl.uniform1i(brfdLutTarget, 5);
62 |
63 | gl.activeTexture(gl.TEXTURE6);
64 | gl.bindTexture(gl.TEXTURE_CUBE_MAP, environment.diffuse);
65 | gl.uniform1i(diffuseTarget, 6);
66 |
67 | gl.activeTexture(gl.TEXTURE7);
68 | gl.bindTexture(gl.TEXTURE_CUBE_MAP, environment.specular);
69 | gl.uniform1i(specularTarget, 7);
70 | };
71 |
72 | export {
73 | load,
74 | bind,
75 | Environment,
76 | };
77 |
--------------------------------------------------------------------------------
/example/src/inputs.ts:
--------------------------------------------------------------------------------
1 | interface Position {
2 | x: number;
3 | y: number;
4 | }
5 |
6 | const getPinchDistance = (event: TouchEvent) => {
7 | const xyDistance = {
8 | x: Math.abs(event.touches[0].clientX - event.touches[1].clientX),
9 | y: Math.abs(event.touches[0].clientY - event.touches[1].clientY),
10 | };
11 |
12 | return Math.sqrt(xyDistance.x * xyDistance.x + xyDistance.y * xyDistance.y);
13 | }
14 |
15 | const listen = (canvas: HTMLCanvasElement, drag: (rotation: Position) => void, zoom: (zoom: number) => void) => {
16 | let lastPosition: Position | undefined;
17 | let zoomStart: number | undefined;
18 |
19 | const dragEvent = (event) => {
20 | const client = event.touches
21 | ? { x: event.touches[0].clientX, y: event.touches[0].clientY }
22 | : { x: event.clientX, y: event.clientY };
23 |
24 | if (lastPosition !== undefined) {
25 | drag({
26 | x: (client.x - lastPosition.x) / 100.0,
27 | y: (client.y - lastPosition.y) / 100.0
28 | });
29 | }
30 |
31 | lastPosition = {
32 | x: client.x,
33 | y: client.y,
34 | };
35 | };
36 |
37 | canvas.addEventListener('wheel', (event) => {
38 | zoom(event.deltaY > 0 ? 0.05: -0.05);
39 | });
40 |
41 | canvas.addEventListener('mousedown', () => {
42 | canvas.addEventListener('mousemove', dragEvent);
43 | });
44 |
45 | canvas.addEventListener('mouseup', () => {
46 | canvas.removeEventListener('mousemove', dragEvent);
47 | lastPosition = undefined;
48 | });
49 |
50 |
51 | canvas.addEventListener('touchmove', (event) => {
52 | if (event.touches.length === 1 && zoomStart === undefined) {
53 | dragEvent(event);
54 | return;
55 | }
56 |
57 | if (event.touches.length === 2 && zoomStart !== undefined) {
58 | const distance = getPinchDistance(event);
59 | zoom((zoomStart - distance) / 4000.0)
60 | }
61 |
62 | event.preventDefault();
63 | event.stopPropagation();
64 | });
65 |
66 | canvas.addEventListener('touchstart', (event) => {
67 | zoomStart = getPinchDistance(event);
68 | });
69 |
70 | canvas.addEventListener('touchend', (event) => {
71 | if (event.touches.length === 0) {
72 | zoomStart = undefined;
73 | lastPosition = undefined;
74 | }
75 | });
76 | };
77 |
78 | export {
79 | listen,
80 | Position,
81 | };
82 |
--------------------------------------------------------------------------------
/example/src/renderer.ts:
--------------------------------------------------------------------------------
1 | import { mat4 } from 'gl-matrix';
2 | import { Model, GLBuffer } from 'webgl-gltf';
3 | import { DefaultShader } from './shaders/default-shader';
4 |
5 | const bindBuffer = (gl: WebGLRenderingContext, position: number, buffer: GLBuffer | null) => {
6 | if (buffer === null) return;
7 |
8 | gl.enableVertexAttribArray(position);
9 | gl.bindBuffer(gl.ARRAY_BUFFER, buffer.buffer);
10 | gl.vertexAttribPointer(position, buffer.size, buffer.type, false, 0, 0);
11 |
12 | return buffer;
13 | };
14 |
15 | const applyTexture = (gl: WebGLRenderingContext, texture: WebGLTexture | null, textureTarget: number, textureUniform: WebGLUniformLocation, enabledUniform?: WebGLUniformLocation) => {
16 | if (texture) {
17 | gl.activeTexture(gl.TEXTURE0 + textureTarget);
18 | gl.bindTexture(gl.TEXTURE_2D, texture);
19 | gl.uniform1i(textureUniform, textureTarget);
20 | }
21 |
22 | if (enabledUniform !== undefined) gl.uniform1i(enabledUniform, texture ? 1 : 0);
23 | }
24 |
25 | const renderModel = (gl: WebGLRenderingContext, model: Model, node: number, transform: mat4, uniforms: DefaultShader) => {
26 | if (model.nodes[node].mesh !== undefined) {
27 | const mesh = model.meshes[model.nodes[node].mesh!];
28 | const material = model.materials[mesh.material];
29 |
30 | if (material) {
31 | applyTexture(gl, material.baseColorTexture, 0, uniforms.baseColorTexture, uniforms.hasBaseColorTexture);
32 | applyTexture(gl, material.metallicRoughnessTexture, 1, uniforms.metallicRoughnessTexture, uniforms.hasMetallicRoughnessTexture);
33 | applyTexture(gl, material.emissiveTexture, 2, uniforms.emissiveTexture, uniforms.hasEmissiveTexture);
34 | applyTexture(gl, material.normalTexture, 3, uniforms.normalTexture, uniforms.hasNormalTexture);
35 | applyTexture(gl, material.occlusionTexture, 4, uniforms.occlusionTexture, uniforms.hasOcclusionTexture);
36 |
37 | gl.uniform4f(uniforms.baseColorFactor, material.baseColorFactor[0], material.baseColorFactor[1], material.baseColorFactor[2], material.baseColorFactor[3]);
38 | gl.uniform1f(uniforms.metallicFactor, material.metallicFactor);
39 | gl.uniform1f(uniforms.roughnessFactor, material.roughnessFactor);
40 | gl.uniform3f(uniforms.emissiveFactor, material.emissiveFactor[0], material.emissiveFactor[1], material.emissiveFactor[2]);
41 | }
42 |
43 | bindBuffer(gl, uniforms.position, mesh.positions);
44 | bindBuffer(gl, uniforms.normal, mesh.normals);
45 | bindBuffer(gl, uniforms.tangent, mesh.tangents);
46 | bindBuffer(gl, uniforms.texCoord, mesh.texCoord);
47 | bindBuffer(gl, uniforms.joints, mesh.joints);
48 | bindBuffer(gl, uniforms.weights, mesh.weights);
49 |
50 | gl.uniformMatrix4fv(uniforms.mMatrix, false, transform);
51 |
52 | if (mesh.indices) {
53 | gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, mesh.indices);
54 | gl.drawElements(gl.TRIANGLES, mesh.elementCount, gl.UNSIGNED_SHORT, 0);
55 | } else {
56 | gl.drawArrays(gl.TRIANGLES, 0, mesh.elementCount);
57 | }
58 | }
59 |
60 | model.nodes[node].children.forEach(c => {
61 | renderModel(gl, model, c, transform, uniforms);
62 | });
63 | };
64 |
65 | export {
66 | renderModel,
67 | };
68 |
--------------------------------------------------------------------------------
/example/src/shaders/default-shader.ts:
--------------------------------------------------------------------------------
1 | export interface DefaultShader {
2 | pMatrix: WebGLUniformLocation;
3 | vMatrix: WebGLUniformLocation;
4 | mMatrix: WebGLUniformLocation;
5 | cameraPosition: WebGLUniformLocation;
6 | hasBaseColorTexture: WebGLUniformLocation;
7 | baseColorTexture: WebGLUniformLocation;
8 | hasMetallicRoughnessTexture: WebGLUniformLocation;
9 | metallicRoughnessTexture: WebGLUniformLocation;
10 | hasEmissiveTexture: WebGLUniformLocation;
11 | normalTexture: WebGLUniformLocation;
12 | hasNormalTexture: WebGLUniformLocation;
13 | emissiveTexture: WebGLUniformLocation;
14 | hasOcclusionTexture: WebGLUniformLocation;
15 | occlusionTexture: WebGLUniformLocation;
16 | baseColorFactor: WebGLUniformLocation;
17 | metallicFactor: WebGLUniformLocation;
18 | roughnessFactor: WebGLUniformLocation;
19 | emissiveFactor: WebGLUniformLocation;
20 | isAnimated: WebGLUniformLocation;
21 | jointTransform: WebGLUniformLocation[];
22 | brdfLut: WebGLUniformLocation;
23 | environmentDiffuse: WebGLUniformLocation;
24 | environmentSpecular: WebGLUniformLocation;
25 |
26 | position: number;
27 | normal: number;
28 | tangent: number;
29 | texCoord: number;
30 | joints: number;
31 | weights: number;
32 | }
33 |
34 | const getUniformLocations = (gl: WebGLRenderingContext, program: WebGLProgram): DefaultShader => {
35 | const pMatrix = gl.getUniformLocation(program, 'uProjectionMatrix')!;
36 | const vMatrix = gl.getUniformLocation(program, 'uViewMatrix')!;
37 | const mMatrix = gl.getUniformLocation(program, 'uModelMatrix')!;
38 | const cameraPosition = gl.getUniformLocation(program, 'uCameraPosition')!;
39 |
40 | const isAnimated = gl.getUniformLocation(program, 'uIsAnimated')!;
41 | const hasBaseColorTexture = gl.getUniformLocation(program, 'uHasBaseColorTexture')!;
42 | const baseColorTexture = gl.getUniformLocation(program, 'uBaseColorTexture')!;
43 | const hasMetallicRoughnessTexture = gl.getUniformLocation(program, 'uHasMetallicRoughnessTexture')!;
44 | const metallicRoughnessTexture = gl.getUniformLocation(program, 'uMetallicRoughnessTexture')!;
45 |
46 | const hasEmissiveTexture = gl.getUniformLocation(program, 'uHasEmissiveTexture')!;
47 | const emissiveTexture = gl.getUniformLocation(program, 'uEmissiveTexture')!;
48 |
49 | const baseColorFactor = gl.getUniformLocation(program, 'uBaseColorFactor')!;
50 | const metallicFactor = gl.getUniformLocation(program, 'uMetallicFactor')!;
51 | const roughnessFactor = gl.getUniformLocation(program, 'uRoughnessFactor')!;
52 | const emissiveFactor = gl.getUniformLocation(program, 'uEmissiveFactor')!;
53 |
54 | const normalTexture = gl.getUniformLocation(program, 'uNormalTexture')!;
55 | const hasNormalTexture = gl.getUniformLocation(program, 'uHasNormalTexture')!;
56 |
57 | const occlusionTexture = gl.getUniformLocation(program, 'uOcclusionTexture')!;
58 | const hasOcclusionTexture = gl.getUniformLocation(program, 'uHasOcclusionTexture')!;
59 |
60 | const brdfLut = gl.getUniformLocation(program, 'uBrdfLut')!;
61 | const environmentDiffuse = gl.getUniformLocation(program, 'uEnvironmentDiffuse')!;
62 | const environmentSpecular = gl.getUniformLocation(program, 'uEnvironmentSpecular')!;
63 |
64 | const jointTransform: WebGLUniformLocation[] = [];
65 | for (let i = 0; i < 25; i ++) {
66 | jointTransform[i] = gl.getUniformLocation(program, `uJointTransform[${i}]`)!
67 | }
68 |
69 | const position = gl.getAttribLocation(program, 'vPosition');
70 | const normal = gl.getAttribLocation(program, 'vNormal');
71 | const tangent = gl.getAttribLocation(program, 'vTangent');
72 | const texCoord = gl.getAttribLocation(program, 'vTexCoord');
73 | const joints = gl.getAttribLocation(program, 'vJoints');
74 | const weights = gl.getAttribLocation(program, 'vWeights');
75 |
76 | return {
77 | pMatrix,
78 | vMatrix,
79 | mMatrix,
80 | cameraPosition,
81 | hasBaseColorTexture,
82 | baseColorTexture,
83 | hasMetallicRoughnessTexture,
84 | metallicRoughnessTexture,
85 | hasEmissiveTexture,
86 | normalTexture,
87 | hasNormalTexture,
88 | occlusionTexture,
89 | hasOcclusionTexture,
90 | emissiveTexture,
91 | baseColorFactor,
92 | metallicFactor,
93 | roughnessFactor,
94 | emissiveFactor,
95 | isAnimated,
96 | jointTransform,
97 | brdfLut,
98 | environmentDiffuse,
99 | environmentSpecular,
100 |
101 | position,
102 | normal,
103 | tangent,
104 | texCoord,
105 | joints,
106 | weights,
107 | };
108 | };
109 |
110 | export {
111 | getUniformLocations,
112 | };
113 |
--------------------------------------------------------------------------------
/example/src/shaders/shader-loader.ts:
--------------------------------------------------------------------------------
1 |
2 | const loadShader = async (gl: WebGLRenderingContext, name: string, type: number) => {
3 | const response = await fetch(`/shaders/${name}`);
4 | const content = await response.text();
5 |
6 | const shader = gl.createShader(type);
7 | if (shader === null) throw new Error('gl.createShader returned null!');
8 |
9 | gl.shaderSource(shader, content);
10 | gl.compileShader(shader);
11 |
12 | if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
13 | console.error(`Failed to load shader ${name}`);
14 | console.error(gl.getShaderInfoLog(shader));
15 | }
16 |
17 | return shader;
18 | };
19 |
20 | const createProgram = (gl: WebGLRenderingContext) => {
21 | const program = gl.createProgram();
22 | if (program === null) throw new Error('gl.createProgram returned null!');
23 |
24 | return program as WebGLProgram;
25 | };
26 |
27 | const linkProgram = (gl: WebGLRenderingContext, program: WebGLProgram) => {
28 | gl.linkProgram(program);
29 | if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
30 | console.error(gl.getProgramInfoLog(program));
31 | }
32 |
33 | gl.useProgram(program);
34 | };
35 |
36 | export {
37 | loadShader,
38 | createProgram,
39 | linkProgram,
40 | };
41 |
--------------------------------------------------------------------------------
/example/static/environment/brdf_lut.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/larsjarlvik/webgl-gltf/46724143e451f4781eb2949ff5e50453d4c70e50/example/static/environment/brdf_lut.png
--------------------------------------------------------------------------------
/example/static/environment/diffuse_back.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/larsjarlvik/webgl-gltf/46724143e451f4781eb2949ff5e50453d4c70e50/example/static/environment/diffuse_back.jpg
--------------------------------------------------------------------------------
/example/static/environment/diffuse_bottom.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/larsjarlvik/webgl-gltf/46724143e451f4781eb2949ff5e50453d4c70e50/example/static/environment/diffuse_bottom.jpg
--------------------------------------------------------------------------------
/example/static/environment/diffuse_front.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/larsjarlvik/webgl-gltf/46724143e451f4781eb2949ff5e50453d4c70e50/example/static/environment/diffuse_front.jpg
--------------------------------------------------------------------------------
/example/static/environment/diffuse_left.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/larsjarlvik/webgl-gltf/46724143e451f4781eb2949ff5e50453d4c70e50/example/static/environment/diffuse_left.jpg
--------------------------------------------------------------------------------
/example/static/environment/diffuse_right.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/larsjarlvik/webgl-gltf/46724143e451f4781eb2949ff5e50453d4c70e50/example/static/environment/diffuse_right.jpg
--------------------------------------------------------------------------------
/example/static/environment/diffuse_top.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/larsjarlvik/webgl-gltf/46724143e451f4781eb2949ff5e50453d4c70e50/example/static/environment/diffuse_top.jpg
--------------------------------------------------------------------------------
/example/static/environment/specular_back.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/larsjarlvik/webgl-gltf/46724143e451f4781eb2949ff5e50453d4c70e50/example/static/environment/specular_back.jpg
--------------------------------------------------------------------------------
/example/static/environment/specular_bottom.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/larsjarlvik/webgl-gltf/46724143e451f4781eb2949ff5e50453d4c70e50/example/static/environment/specular_bottom.jpg
--------------------------------------------------------------------------------
/example/static/environment/specular_front.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/larsjarlvik/webgl-gltf/46724143e451f4781eb2949ff5e50453d4c70e50/example/static/environment/specular_front.jpg
--------------------------------------------------------------------------------
/example/static/environment/specular_left.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/larsjarlvik/webgl-gltf/46724143e451f4781eb2949ff5e50453d4c70e50/example/static/environment/specular_left.jpg
--------------------------------------------------------------------------------
/example/static/environment/specular_right.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/larsjarlvik/webgl-gltf/46724143e451f4781eb2949ff5e50453d4c70e50/example/static/environment/specular_right.jpg
--------------------------------------------------------------------------------
/example/static/environment/specular_top.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/larsjarlvik/webgl-gltf/46724143e451f4781eb2949ff5e50453d4c70e50/example/static/environment/specular_top.jpg
--------------------------------------------------------------------------------
/example/static/models/cesium/cesium.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/larsjarlvik/webgl-gltf/46724143e451f4781eb2949ff5e50453d4c70e50/example/static/models/cesium/cesium.bin
--------------------------------------------------------------------------------
/example/static/models/cesium/cesium.gltf:
--------------------------------------------------------------------------------
1 | {
2 | "asset": {
3 | "generator": "COLLADA2GLTF",
4 | "version": "2.0"
5 | },
6 | "scenes": [
7 | {
8 | "nodes": [
9 | 0
10 | ]
11 | }
12 | ],
13 | "scene": 0,
14 | "nodes": [
15 | {
16 | "children": [
17 | 1
18 | ],
19 | "matrix": [
20 | 1,
21 | 0,
22 | 0,
23 | 0,
24 | 0,
25 | 0,
26 | -1,
27 | 0,
28 | 0,
29 | 1,
30 | 0,
31 | 0,
32 | 0,
33 | 0,
34 | 0,
35 | 1
36 | ],
37 | "name": "Z_UP"
38 | },
39 | {
40 | "children": [
41 | 3,
42 | 2
43 | ],
44 | "matrix": [
45 | -4.371139894487897e-8,
46 | -1,
47 | 0,
48 | 0,
49 | 1,
50 | -4.371139894487897e-8,
51 | 0,
52 | 0,
53 | 0,
54 | 0,
55 | 1,
56 | 0,
57 | 0,
58 | 0,
59 | 0,
60 | 1
61 | ],
62 | "name": "Armature"
63 | },
64 | {
65 | "mesh": 0,
66 | "skin": 0,
67 | "name": "Cesium_Man"
68 | },
69 | {
70 | "children": [
71 | 12,
72 | 8,
73 | 4
74 | ],
75 | "translation": [
76 | 1.57554005397742e-8,
77 | 0.004999836906790733,
78 | 0.6789999008178711
79 | ],
80 | "rotation": [
81 | 0,
82 | -0.0378035344183445,
83 | 0,
84 | -0.9992852210998536
85 | ],
86 | "name": "Skeleton_torso_joint_1",
87 | "scale": [
88 | 1,
89 | 1,
90 | 1
91 | ]
92 | },
93 | {
94 | "children": [
95 | 5
96 | ],
97 | "translation": [
98 | 0.02855719067156315,
99 | -0.06803914159536362,
100 | -0.06295864284038544
101 | ],
102 | "rotation": [
103 | 0,
104 | -0.6898291707038879,
105 | 0,
106 | -0.7239722013473511
107 | ],
108 | "name": "leg_joint_R_1",
109 | "scale": [
110 | 1,
111 | 1,
112 | 1
113 | ]
114 | },
115 | {
116 | "children": [
117 | 6
118 | ],
119 | "translation": [
120 | 0.26089081168174744,
121 | -0.009026050567626951,
122 | 0.05167089030146599
123 | ],
124 | "rotation": [
125 | 0,
126 | -0.0941137745976448,
127 | 0,
128 | -0.9955614805221558
129 | ],
130 | "scale": [
131 | 1.0000001192092896,
132 | 1,
133 | 1.0000001192092896
134 | ],
135 | "name": "leg_joint_R_2"
136 | },
137 | {
138 | "children": [
139 | 7
140 | ],
141 | "translation": [
142 | 0.27546030282974243,
143 | -0.0014317259192466736,
144 | -0.014104830101132391
145 | ],
146 | "rotation": [
147 | 0,
148 | 0.8666408061981201,
149 | 0,
150 | 0.4989325702190399
151 | ],
152 | "name": "leg_joint_R_3",
153 | "scale": [
154 | 1,
155 | 1,
156 | 1
157 | ]
158 | },
159 | {
160 | "translation": [
161 | -0.06681963056325912,
162 | -0.001072264974936843,
163 | 0.026351310312747955
164 | ],
165 | "rotation": [
166 | 0,
167 | 0.3269147574901581,
168 | 0,
169 | -0.9450538158416748
170 | ],
171 | "name": "leg_joint_R_5",
172 | "scale": [
173 | 1,
174 | 1,
175 | 1
176 | ]
177 | },
178 | {
179 | "children": [
180 | 9
181 | ],
182 | "translation": [
183 | 0.028519999235868457,
184 | 0.06803944706916809,
185 | -0.06295935809612274
186 | ],
187 | "rotation": [
188 | 0,
189 | -0.32463353872299194,
190 | 0,
191 | -0.9458398818969728
192 | ],
193 | "name": "leg_joint_L_1",
194 | "scale": [
195 | 1,
196 | 1,
197 | 1
198 | ]
199 | },
200 | {
201 | "children": [
202 | 10
203 | ],
204 | "translation": [
205 | 0.20916390419006348,
206 | 0.009055502712726591,
207 | -0.16426950693130493
208 | ],
209 | "rotation": [
210 | 0,
211 | -0.5294369459152222,
212 | 0,
213 | -0.8483493328094482
214 | ],
215 | "scale": [
216 | 1.0000001192092896,
217 | 1,
218 | 1.0000001192092896
219 | ],
220 | "name": "leg_joint_L_2"
221 | },
222 | {
223 | "children": [
224 | 11
225 | ],
226 | "translation": [
227 | 0.27579009532928467,
228 | 0.0013972519664093852,
229 | 0.004122479818761349
230 | ],
231 | "rotation": [
232 | 0,
233 | -0.8377647399902344,
234 | 0,
235 | -0.5460314750671387
236 | ],
237 | "scale": [
238 | 1,
239 | 0.9999999403953552,
240 | 1
241 | ],
242 | "name": "leg_joint_L_3"
243 | },
244 | {
245 | "translation": [
246 | -0.06558381021022797,
247 | 0.001090653007850051,
248 | 0.02929146029055119
249 | ],
250 | "rotation": [
251 | 0,
252 | 0.3130458891391754,
253 | 0,
254 | -0.9497380256652832
255 | ],
256 | "name": "leg_joint_L_5",
257 | "scale": [
258 | 1,
259 | 1,
260 | 1
261 | ]
262 | },
263 | {
264 | "children": [
265 | 13
266 | ],
267 | "translation": [
268 | 0.0000133617004394182,
269 | -0.000013373800356930587,
270 | 0.14541690051555634
271 | ],
272 | "rotation": [
273 | 0,
274 | -0.6573964357376099,
275 | 0,
276 | -0.7535448670387268
277 | ],
278 | "name": "Skeleton_torso_joint_2",
279 | "scale": [
280 | 1,
281 | 1,
282 | 1
283 | ]
284 | },
285 | {
286 | "children": [
287 | 20,
288 | 17,
289 | 14
290 | ],
291 | "translation": [
292 | -0.2505168914794922,
293 | 6.072219775887788e-7,
294 | -0.00007290810026461259
295 | ],
296 | "rotation": [
297 | 0,
298 | 0.6227028965950012,
299 | 0,
300 | -0.7824583649635315
301 | ],
302 | "name": "torso_joint_3",
303 | "scale": [
304 | 1,
305 | 1,
306 | 1
307 | ]
308 | },
309 | {
310 | "children": [
311 | 15
312 | ],
313 | "translation": [
314 | -0.00003830249988823198,
315 | -0.09098774939775468,
316 | -0.000062032304413151
317 | ],
318 | "rotation": [
319 | 0,
320 | 0.9909319281578064,
321 | 0,
322 | -0.13436488807201385
323 | ],
324 | "name": "Skeleton_arm_joint_R",
325 | "scale": [
326 | 1,
327 | 1,
328 | 1
329 | ]
330 | },
331 | {
332 | "children": [
333 | 16
334 | ],
335 | "translation": [
336 | -0.03554634004831314,
337 | -0.2154989987611771,
338 | 0.10423289984464645
339 | ],
340 | "rotation": [
341 | 0,
342 | 0.8961479663848877,
343 | 0,
344 | 0.4437553286552429
345 | ],
346 | "scale": [
347 | 0.9999999403953552,
348 | 1,
349 | 0.9999999403953552
350 | ],
351 | "name": "Skeleton_arm_joint_R__2_"
352 | },
353 | {
354 | "translation": [
355 | 0.03137021884322167,
356 | -0.1430010050535202,
357 | -0.11761169880628586
358 | ],
359 | "rotation": [
360 | 0,
361 | 0.3792171180248261,
362 | 0,
363 | -0.9253078103065492
364 | ],
365 | "scale": [
366 | 1.0000001192092896,
367 | 1,
368 | 1.0000001192092896
369 | ],
370 | "name": "Skeleton_arm_joint_R__3_"
371 | },
372 | {
373 | "children": [
374 | 18
375 | ],
376 | "translation": [
377 | -0.00003837469921563752,
378 | 0.091013602912426,
379 | -0.00006143339851405472
380 | ],
381 | "rotation": [
382 | 0,
383 | 0.9959768652915956,
384 | 0,
385 | 0.08961082249879837
386 | ],
387 | "name": "Skeleton_arm_joint_L__4_",
388 | "scale": [
389 | 1,
390 | 1,
391 | 1
392 | ]
393 | },
394 | {
395 | "children": [
396 | 19
397 | ],
398 | "translation": [
399 | 0.01322161965072155,
400 | 0.21549950540065768,
401 | 0.10933209955692293
402 | ],
403 | "rotation": [
404 | 0,
405 | -0.0711694285273552,
406 | 0,
407 | -0.9974642395973206
408 | ],
409 | "name": "Skeleton_arm_joint_L__3_",
410 | "scale": [
411 | 1,
412 | 1,
413 | 1
414 | ]
415 | },
416 | {
417 | "translation": [
418 | -0.09332461655139924,
419 | 0.1430000960826874,
420 | 0.07814791053533554
421 | ],
422 | "rotation": [
423 | 0,
424 | -0.02254222705960274,
425 | 0,
426 | -0.9997459053993224
427 | ],
428 | "name": "Skeleton_arm_joint_L__2_",
429 | "scale": [
430 | 1,
431 | 1,
432 | 1
433 | ]
434 | },
435 | {
436 | "children": [
437 | 21
438 | ],
439 | "translation": [
440 | -0.000002366030003031483,
441 | 0.000002413989932392724,
442 | 0.06483621150255203
443 | ],
444 | "rotation": [
445 | 0,
446 | -0.660634458065033,
447 | 0,
448 | -0.750707745552063
449 | ],
450 | "name": "Skeleton_neck_joint_1",
451 | "scale": [
452 | 1,
453 | 1,
454 | 1
455 | ]
456 | },
457 | {
458 | "translation": [
459 | -0.05204017087817192,
460 | -3.3993298842460724e-8,
461 | -0.0000026607899599184748
462 | ],
463 | "rotation": [
464 | 0,
465 | 0.9996904730796814,
466 | 0,
467 | 0.024879230186343193
468 | ],
469 | "scale": [
470 | 1.0000001192092896,
471 | 1,
472 | 1.0000001192092896
473 | ],
474 | "name": "Skeleton_neck_joint_2"
475 | }
476 | ],
477 | "meshes": [
478 | {
479 | "primitives": [
480 | {
481 | "attributes": {
482 | "JOINTS_0": 1,
483 | "NORMAL": 2,
484 | "POSITION": 3,
485 | "TEXCOORD_0": 4,
486 | "WEIGHTS_0": 5
487 | },
488 | "indices": 0,
489 | "mode": 4,
490 | "material": 0
491 | }
492 | ],
493 | "name": "Cesium_Man"
494 | }
495 | ],
496 | "animations": [
497 | {
498 | "channels": [
499 | {
500 | "sampler": 0,
501 | "target": {
502 | "node": 3,
503 | "path": "translation"
504 | }
505 | },
506 | {
507 | "sampler": 1,
508 | "target": {
509 | "node": 3,
510 | "path": "rotation"
511 | }
512 | },
513 | {
514 | "sampler": 2,
515 | "target": {
516 | "node": 3,
517 | "path": "scale"
518 | }
519 | },
520 | {
521 | "sampler": 3,
522 | "target": {
523 | "node": 12,
524 | "path": "translation"
525 | }
526 | },
527 | {
528 | "sampler": 4,
529 | "target": {
530 | "node": 12,
531 | "path": "rotation"
532 | }
533 | },
534 | {
535 | "sampler": 5,
536 | "target": {
537 | "node": 12,
538 | "path": "scale"
539 | }
540 | },
541 | {
542 | "sampler": 6,
543 | "target": {
544 | "node": 13,
545 | "path": "translation"
546 | }
547 | },
548 | {
549 | "sampler": 7,
550 | "target": {
551 | "node": 13,
552 | "path": "rotation"
553 | }
554 | },
555 | {
556 | "sampler": 8,
557 | "target": {
558 | "node": 13,
559 | "path": "scale"
560 | }
561 | },
562 | {
563 | "sampler": 9,
564 | "target": {
565 | "node": 20,
566 | "path": "translation"
567 | }
568 | },
569 | {
570 | "sampler": 10,
571 | "target": {
572 | "node": 20,
573 | "path": "rotation"
574 | }
575 | },
576 | {
577 | "sampler": 11,
578 | "target": {
579 | "node": 20,
580 | "path": "scale"
581 | }
582 | },
583 | {
584 | "sampler": 12,
585 | "target": {
586 | "node": 21,
587 | "path": "translation"
588 | }
589 | },
590 | {
591 | "sampler": 13,
592 | "target": {
593 | "node": 21,
594 | "path": "rotation"
595 | }
596 | },
597 | {
598 | "sampler": 14,
599 | "target": {
600 | "node": 21,
601 | "path": "scale"
602 | }
603 | },
604 | {
605 | "sampler": 15,
606 | "target": {
607 | "node": 17,
608 | "path": "translation"
609 | }
610 | },
611 | {
612 | "sampler": 16,
613 | "target": {
614 | "node": 17,
615 | "path": "rotation"
616 | }
617 | },
618 | {
619 | "sampler": 17,
620 | "target": {
621 | "node": 17,
622 | "path": "scale"
623 | }
624 | },
625 | {
626 | "sampler": 18,
627 | "target": {
628 | "node": 18,
629 | "path": "translation"
630 | }
631 | },
632 | {
633 | "sampler": 19,
634 | "target": {
635 | "node": 18,
636 | "path": "rotation"
637 | }
638 | },
639 | {
640 | "sampler": 20,
641 | "target": {
642 | "node": 18,
643 | "path": "scale"
644 | }
645 | },
646 | {
647 | "sampler": 21,
648 | "target": {
649 | "node": 19,
650 | "path": "translation"
651 | }
652 | },
653 | {
654 | "sampler": 22,
655 | "target": {
656 | "node": 19,
657 | "path": "rotation"
658 | }
659 | },
660 | {
661 | "sampler": 23,
662 | "target": {
663 | "node": 19,
664 | "path": "scale"
665 | }
666 | },
667 | {
668 | "sampler": 24,
669 | "target": {
670 | "node": 14,
671 | "path": "translation"
672 | }
673 | },
674 | {
675 | "sampler": 25,
676 | "target": {
677 | "node": 14,
678 | "path": "rotation"
679 | }
680 | },
681 | {
682 | "sampler": 26,
683 | "target": {
684 | "node": 14,
685 | "path": "scale"
686 | }
687 | },
688 | {
689 | "sampler": 27,
690 | "target": {
691 | "node": 15,
692 | "path": "translation"
693 | }
694 | },
695 | {
696 | "sampler": 28,
697 | "target": {
698 | "node": 15,
699 | "path": "rotation"
700 | }
701 | },
702 | {
703 | "sampler": 29,
704 | "target": {
705 | "node": 15,
706 | "path": "scale"
707 | }
708 | },
709 | {
710 | "sampler": 30,
711 | "target": {
712 | "node": 16,
713 | "path": "translation"
714 | }
715 | },
716 | {
717 | "sampler": 31,
718 | "target": {
719 | "node": 16,
720 | "path": "rotation"
721 | }
722 | },
723 | {
724 | "sampler": 32,
725 | "target": {
726 | "node": 16,
727 | "path": "scale"
728 | }
729 | },
730 | {
731 | "sampler": 33,
732 | "target": {
733 | "node": 8,
734 | "path": "translation"
735 | }
736 | },
737 | {
738 | "sampler": 34,
739 | "target": {
740 | "node": 8,
741 | "path": "rotation"
742 | }
743 | },
744 | {
745 | "sampler": 35,
746 | "target": {
747 | "node": 8,
748 | "path": "scale"
749 | }
750 | },
751 | {
752 | "sampler": 36,
753 | "target": {
754 | "node": 9,
755 | "path": "translation"
756 | }
757 | },
758 | {
759 | "sampler": 37,
760 | "target": {
761 | "node": 9,
762 | "path": "rotation"
763 | }
764 | },
765 | {
766 | "sampler": 38,
767 | "target": {
768 | "node": 9,
769 | "path": "scale"
770 | }
771 | },
772 | {
773 | "sampler": 39,
774 | "target": {
775 | "node": 10,
776 | "path": "translation"
777 | }
778 | },
779 | {
780 | "sampler": 40,
781 | "target": {
782 | "node": 10,
783 | "path": "rotation"
784 | }
785 | },
786 | {
787 | "sampler": 41,
788 | "target": {
789 | "node": 10,
790 | "path": "scale"
791 | }
792 | },
793 | {
794 | "sampler": 42,
795 | "target": {
796 | "node": 11,
797 | "path": "translation"
798 | }
799 | },
800 | {
801 | "sampler": 43,
802 | "target": {
803 | "node": 11,
804 | "path": "rotation"
805 | }
806 | },
807 | {
808 | "sampler": 44,
809 | "target": {
810 | "node": 11,
811 | "path": "scale"
812 | }
813 | },
814 | {
815 | "sampler": 45,
816 | "target": {
817 | "node": 4,
818 | "path": "translation"
819 | }
820 | },
821 | {
822 | "sampler": 46,
823 | "target": {
824 | "node": 4,
825 | "path": "rotation"
826 | }
827 | },
828 | {
829 | "sampler": 47,
830 | "target": {
831 | "node": 4,
832 | "path": "scale"
833 | }
834 | },
835 | {
836 | "sampler": 48,
837 | "target": {
838 | "node": 5,
839 | "path": "translation"
840 | }
841 | },
842 | {
843 | "sampler": 49,
844 | "target": {
845 | "node": 5,
846 | "path": "rotation"
847 | }
848 | },
849 | {
850 | "sampler": 50,
851 | "target": {
852 | "node": 5,
853 | "path": "scale"
854 | }
855 | },
856 | {
857 | "sampler": 51,
858 | "target": {
859 | "node": 6,
860 | "path": "translation"
861 | }
862 | },
863 | {
864 | "sampler": 52,
865 | "target": {
866 | "node": 6,
867 | "path": "rotation"
868 | }
869 | },
870 | {
871 | "sampler": 53,
872 | "target": {
873 | "node": 6,
874 | "path": "scale"
875 | }
876 | },
877 | {
878 | "sampler": 54,
879 | "target": {
880 | "node": 7,
881 | "path": "translation"
882 | }
883 | },
884 | {
885 | "sampler": 55,
886 | "target": {
887 | "node": 7,
888 | "path": "rotation"
889 | }
890 | },
891 | {
892 | "sampler": 56,
893 | "target": {
894 | "node": 7,
895 | "path": "scale"
896 | }
897 | }
898 | ],
899 | "samplers": [
900 | {
901 | "input": 6,
902 | "interpolation": "LINEAR",
903 | "output": 7
904 | },
905 | {
906 | "input": 6,
907 | "interpolation": "LINEAR",
908 | "output": 8
909 | },
910 | {
911 | "input": 6,
912 | "interpolation": "LINEAR",
913 | "output": 9
914 | },
915 | {
916 | "input": 10,
917 | "interpolation": "LINEAR",
918 | "output": 11
919 | },
920 | {
921 | "input": 10,
922 | "interpolation": "LINEAR",
923 | "output": 12
924 | },
925 | {
926 | "input": 10,
927 | "interpolation": "LINEAR",
928 | "output": 13
929 | },
930 | {
931 | "input": 14,
932 | "interpolation": "LINEAR",
933 | "output": 15
934 | },
935 | {
936 | "input": 14,
937 | "interpolation": "LINEAR",
938 | "output": 16
939 | },
940 | {
941 | "input": 14,
942 | "interpolation": "LINEAR",
943 | "output": 17
944 | },
945 | {
946 | "input": 18,
947 | "interpolation": "LINEAR",
948 | "output": 19
949 | },
950 | {
951 | "input": 18,
952 | "interpolation": "LINEAR",
953 | "output": 20
954 | },
955 | {
956 | "input": 18,
957 | "interpolation": "LINEAR",
958 | "output": 21
959 | },
960 | {
961 | "input": 22,
962 | "interpolation": "LINEAR",
963 | "output": 23
964 | },
965 | {
966 | "input": 22,
967 | "interpolation": "LINEAR",
968 | "output": 24
969 | },
970 | {
971 | "input": 22,
972 | "interpolation": "LINEAR",
973 | "output": 25
974 | },
975 | {
976 | "input": 26,
977 | "interpolation": "LINEAR",
978 | "output": 27
979 | },
980 | {
981 | "input": 26,
982 | "interpolation": "LINEAR",
983 | "output": 28
984 | },
985 | {
986 | "input": 26,
987 | "interpolation": "LINEAR",
988 | "output": 29
989 | },
990 | {
991 | "input": 30,
992 | "interpolation": "LINEAR",
993 | "output": 31
994 | },
995 | {
996 | "input": 30,
997 | "interpolation": "LINEAR",
998 | "output": 32
999 | },
1000 | {
1001 | "input": 30,
1002 | "interpolation": "LINEAR",
1003 | "output": 33
1004 | },
1005 | {
1006 | "input": 34,
1007 | "interpolation": "LINEAR",
1008 | "output": 35
1009 | },
1010 | {
1011 | "input": 34,
1012 | "interpolation": "LINEAR",
1013 | "output": 36
1014 | },
1015 | {
1016 | "input": 34,
1017 | "interpolation": "LINEAR",
1018 | "output": 37
1019 | },
1020 | {
1021 | "input": 38,
1022 | "interpolation": "LINEAR",
1023 | "output": 39
1024 | },
1025 | {
1026 | "input": 38,
1027 | "interpolation": "LINEAR",
1028 | "output": 40
1029 | },
1030 | {
1031 | "input": 38,
1032 | "interpolation": "LINEAR",
1033 | "output": 41
1034 | },
1035 | {
1036 | "input": 42,
1037 | "interpolation": "LINEAR",
1038 | "output": 43
1039 | },
1040 | {
1041 | "input": 42,
1042 | "interpolation": "LINEAR",
1043 | "output": 44
1044 | },
1045 | {
1046 | "input": 42,
1047 | "interpolation": "LINEAR",
1048 | "output": 45
1049 | },
1050 | {
1051 | "input": 46,
1052 | "interpolation": "LINEAR",
1053 | "output": 47
1054 | },
1055 | {
1056 | "input": 46,
1057 | "interpolation": "LINEAR",
1058 | "output": 48
1059 | },
1060 | {
1061 | "input": 46,
1062 | "interpolation": "LINEAR",
1063 | "output": 49
1064 | },
1065 | {
1066 | "input": 50,
1067 | "interpolation": "LINEAR",
1068 | "output": 51
1069 | },
1070 | {
1071 | "input": 50,
1072 | "interpolation": "LINEAR",
1073 | "output": 52
1074 | },
1075 | {
1076 | "input": 50,
1077 | "interpolation": "LINEAR",
1078 | "output": 53
1079 | },
1080 | {
1081 | "input": 54,
1082 | "interpolation": "LINEAR",
1083 | "output": 55
1084 | },
1085 | {
1086 | "input": 54,
1087 | "interpolation": "LINEAR",
1088 | "output": 56
1089 | },
1090 | {
1091 | "input": 54,
1092 | "interpolation": "LINEAR",
1093 | "output": 57
1094 | },
1095 | {
1096 | "input": 58,
1097 | "interpolation": "LINEAR",
1098 | "output": 59
1099 | },
1100 | {
1101 | "input": 58,
1102 | "interpolation": "LINEAR",
1103 | "output": 60
1104 | },
1105 | {
1106 | "input": 58,
1107 | "interpolation": "LINEAR",
1108 | "output": 61
1109 | },
1110 | {
1111 | "input": 62,
1112 | "interpolation": "LINEAR",
1113 | "output": 63
1114 | },
1115 | {
1116 | "input": 62,
1117 | "interpolation": "LINEAR",
1118 | "output": 64
1119 | },
1120 | {
1121 | "input": 62,
1122 | "interpolation": "LINEAR",
1123 | "output": 65
1124 | },
1125 | {
1126 | "input": 66,
1127 | "interpolation": "LINEAR",
1128 | "output": 67
1129 | },
1130 | {
1131 | "input": 66,
1132 | "interpolation": "LINEAR",
1133 | "output": 68
1134 | },
1135 | {
1136 | "input": 66,
1137 | "interpolation": "LINEAR",
1138 | "output": 69
1139 | },
1140 | {
1141 | "input": 70,
1142 | "interpolation": "LINEAR",
1143 | "output": 71
1144 | },
1145 | {
1146 | "input": 70,
1147 | "interpolation": "LINEAR",
1148 | "output": 72
1149 | },
1150 | {
1151 | "input": 70,
1152 | "interpolation": "LINEAR",
1153 | "output": 73
1154 | },
1155 | {
1156 | "input": 74,
1157 | "interpolation": "LINEAR",
1158 | "output": 75
1159 | },
1160 | {
1161 | "input": 74,
1162 | "interpolation": "LINEAR",
1163 | "output": 76
1164 | },
1165 | {
1166 | "input": 74,
1167 | "interpolation": "LINEAR",
1168 | "output": 77
1169 | },
1170 | {
1171 | "input": 78,
1172 | "interpolation": "LINEAR",
1173 | "output": 79
1174 | },
1175 | {
1176 | "input": 78,
1177 | "interpolation": "LINEAR",
1178 | "output": 80
1179 | },
1180 | {
1181 | "input": 78,
1182 | "interpolation": "LINEAR",
1183 | "output": 81
1184 | }
1185 | ]
1186 | }
1187 | ],
1188 | "skins": [
1189 | {
1190 | "inverseBindMatrices": 82,
1191 | "skeleton": 3,
1192 | "joints": [
1193 | 3,
1194 | 12,
1195 | 13,
1196 | 20,
1197 | 21,
1198 | 17,
1199 | 14,
1200 | 18,
1201 | 15,
1202 | 19,
1203 | 16,
1204 | 8,
1205 | 4,
1206 | 9,
1207 | 5,
1208 | 10,
1209 | 6,
1210 | 11,
1211 | 7
1212 | ],
1213 | "name": "Armature"
1214 | }
1215 | ],
1216 | "accessors": [
1217 | {
1218 | "bufferView": 0,
1219 | "byteOffset": 0,
1220 | "componentType": 5123,
1221 | "count": 14016,
1222 | "max": [
1223 | 3272
1224 | ],
1225 | "min": [
1226 | 0
1227 | ],
1228 | "type": "SCALAR"
1229 | },
1230 | {
1231 | "bufferView": 1,
1232 | "byteOffset": 0,
1233 | "componentType": 5123,
1234 | "count": 3273,
1235 | "max": [
1236 | 18,
1237 | 18,
1238 | 18,
1239 | 18
1240 | ],
1241 | "min": [
1242 | 0,
1243 | 0,
1244 | 0,
1245 | 0
1246 | ],
1247 | "type": "VEC4"
1248 | },
1249 | {
1250 | "bufferView": 2,
1251 | "byteOffset": 0,
1252 | "componentType": 5126,
1253 | "count": 3273,
1254 | "max": [
1255 | 1,
1256 | 0.9999808073043824,
1257 | 0.9944415092468262
1258 | ],
1259 | "min": [
1260 | -1,
1261 | -0.9999808073043824,
1262 | -1
1263 | ],
1264 | "type": "VEC3"
1265 | },
1266 | {
1267 | "bufferView": 2,
1268 | "byteOffset": 39276,
1269 | "componentType": 5126,
1270 | "count": 3273,
1271 | "max": [
1272 | 0.1809539943933487,
1273 | 0.569136917591095,
1274 | 1.5065499544143677
1275 | ],
1276 | "min": [
1277 | -0.13100001215934753,
1278 | -0.5691370964050293,
1279 | 0
1280 | ],
1281 | "type": "VEC3"
1282 | },
1283 | {
1284 | "bufferView": 1,
1285 | "byteOffset": 26184,
1286 | "componentType": 5126,
1287 | "count": 3273,
1288 | "max": [
1289 | 0.990805983543396,
1290 | 0.9880298972129822
1291 | ],
1292 | "min": [
1293 | 0.014079390093684196,
1294 | 0.008445978164672852
1295 | ],
1296 | "type": "VEC2"
1297 | },
1298 | {
1299 | "bufferView": 3,
1300 | "byteOffset": 0,
1301 | "componentType": 5126,
1302 | "count": 3273,
1303 | "max": [
1304 | 1,
1305 | 0.989919900894165,
1306 | 0.9510763883590698,
1307 | 0.9196179509162904
1308 | ],
1309 | "min": [
1310 | 0.010080089792609217,
1311 | 0,
1312 | 0,
1313 | 0
1314 | ],
1315 | "type": "VEC4"
1316 | },
1317 | {
1318 | "bufferView": 4,
1319 | "byteOffset": 0,
1320 | "componentType": 5126,
1321 | "count": 48,
1322 | "max": [
1323 | 2
1324 | ],
1325 | "min": [
1326 | 0.04166661947965622
1327 | ],
1328 | "type": "SCALAR"
1329 | },
1330 | {
1331 | "bufferView": 5,
1332 | "byteOffset": 0,
1333 | "componentType": 5126,
1334 | "count": 48,
1335 | "max": [
1336 | 3.880559873437051e-8,
1337 | -0.02000010944902897,
1338 | 0.7110069990158081
1339 | ],
1340 | "min": [
1341 | 2.716890046272624e-9,
1342 | -0.030000129714608192,
1343 | 0.6399999856948853
1344 | ],
1345 | "type": "VEC3"
1346 | },
1347 | {
1348 | "bufferView": 6,
1349 | "byteOffset": 0,
1350 | "componentType": 5126,
1351 | "count": 48,
1352 | "max": [
1353 | 0.0001435002632206306,
1354 | -0.02146764844655991,
1355 | -0.000009948204024112783,
1356 | -0.9980905055999756
1357 | ],
1358 | "min": [
1359 | -0.012384946458041668,
1360 | -0.06042621284723282,
1361 | -0.0041049933061003685,
1362 | -0.9997026920318604
1363 | ],
1364 | "type": "VEC4"
1365 | },
1366 | {
1367 | "bufferView": 5,
1368 | "byteOffset": 576,
1369 | "componentType": 5126,
1370 | "count": 48,
1371 | "max": [
1372 | 1.0000004768371584,
1373 | 1.0000001192092896,
1374 | 1.000000238418579
1375 | ],
1376 | "min": [
1377 | 0.9999999403953552,
1378 | 0.9999998211860656,
1379 | 0.9999997615814208
1380 | ],
1381 | "type": "VEC3"
1382 | },
1383 | {
1384 | "bufferView": 4,
1385 | "byteOffset": 192,
1386 | "componentType": 5126,
1387 | "count": 48,
1388 | "max": [
1389 | 2
1390 | ],
1391 | "min": [
1392 | 0.04166661947965622
1393 | ],
1394 | "type": "SCALAR"
1395 | },
1396 | {
1397 | "bufferView": 5,
1398 | "byteOffset": 1152,
1399 | "componentType": 5126,
1400 | "count": 48,
1401 | "max": [
1402 | 0.000013520900211005937,
1403 | 0.0009866129839792848,
1404 | 0.1454171985387802
1405 | ],
1406 | "min": [
1407 | 0.000013436199878924528,
1408 | 0.0009865909814834597,
1409 | 0.145416796207428
1410 | ],
1411 | "type": "VEC3"
1412 | },
1413 | {
1414 | "bufferView": 6,
1415 | "byteOffset": 768,
1416 | "componentType": 5126,
1417 | "count": 48,
1418 | "max": [
1419 | 0.011148878373205662,
1420 | -0.7106494903564453,
1421 | 0.0006393495132215321,
1422 | -0.6787928938865662
1423 | ],
1424 | "min": [
1425 | -0.008564536459743977,
1426 | -0.7339289784431458,
1427 | -0.025856714695692062,
1428 | -0.7034341096878052
1429 | ],
1430 | "type": "VEC4"
1431 | },
1432 | {
1433 | "bufferView": 5,
1434 | "byteOffset": 1728,
1435 | "componentType": 5126,
1436 | "count": 48,
1437 | "max": [
1438 | 1.0000004768371584,
1439 | 1.000000238418579,
1440 | 0.9999998211860656
1441 | ],
1442 | "min": [
1443 | 0.9999999403953552,
1444 | 0.9999998807907104,
1445 | 0.9999989867210388
1446 | ],
1447 | "type": "VEC3"
1448 | },
1449 | {
1450 | "bufferView": 4,
1451 | "byteOffset": 384,
1452 | "componentType": 5126,
1453 | "count": 48,
1454 | "max": [
1455 | 2
1456 | ],
1457 | "min": [
1458 | 0.04166661947965622
1459 | ],
1460 | "type": "SCALAR"
1461 | },
1462 | {
1463 | "bufferView": 5,
1464 | "byteOffset": 2304,
1465 | "componentType": 5126,
1466 | "count": 48,
1467 | "max": [
1468 | -0.25051650404930115,
1469 | 5.923209869251878e-7,
1470 | -0.00007277730037458241
1471 | ],
1472 | "min": [
1473 | -0.25051701068878174,
1474 | 5.58793999516638e-7,
1475 | -0.00007287789776455611
1476 | ],
1477 | "type": "VEC3"
1478 | },
1479 | {
1480 | "bufferView": 6,
1481 | "byteOffset": 1536,
1482 | "componentType": 5126,
1483 | "count": 48,
1484 | "max": [
1485 | 0.13804565370082855,
1486 | 0.6359269618988037,
1487 | -0.003375347936525941,
1488 | -0.7641801238059998
1489 | ],
1490 | "min": [
1491 | -0.06163197010755539,
1492 | 0.6225405335426331,
1493 | -0.0653248131275177,
1494 | -0.7825579643249512
1495 | ],
1496 | "type": "VEC4"
1497 | },
1498 | {
1499 | "bufferView": 5,
1500 | "byteOffset": 2880,
1501 | "componentType": 5126,
1502 | "count": 48,
1503 | "max": [
1504 | 1.000001072883606,
1505 | 1.0000003576278689,
1506 | 1
1507 | ],
1508 | "min": [
1509 | 1.0000004768371584,
1510 | 0.9999999403953552,
1511 | 0.999999463558197
1512 | ],
1513 | "type": "VEC3"
1514 | },
1515 | {
1516 | "bufferView": 4,
1517 | "byteOffset": 576,
1518 | "componentType": 5126,
1519 | "count": 48,
1520 | "max": [
1521 | 2
1522 | ],
1523 | "min": [
1524 | 0.04166661947965622
1525 | ],
1526 | "type": "SCALAR"
1527 | },
1528 | {
1529 | "bufferView": 5,
1530 | "byteOffset": 3456,
1531 | "componentType": 5126,
1532 | "count": 48,
1533 | "max": [
1534 | -0.000002384189883741783,
1535 | 0.000002458690005369135,
1536 | 0.06483876705169678
1537 | ],
1538 | "min": [
1539 | -0.000002536919964768458,
1540 | 0.000002384189883741783,
1541 | 0.06483828276395798
1542 | ],
1543 | "type": "VEC3"
1544 | },
1545 | {
1546 | "bufferView": 6,
1547 | "byteOffset": 2304,
1548 | "componentType": 5126,
1549 | "count": 48,
1550 | "max": [
1551 | 0.0364987850189209,
1552 | -0.6325404644012451,
1553 | 0.04193282127380371,
1554 | -0.749859094619751
1555 | ],
1556 | "min": [
1557 | -0.02474863827228546,
1558 | -0.6592763066291809,
1559 | -0.03008362464606762,
1560 | -0.7735469341278076
1561 | ],
1562 | "type": "VEC4"
1563 | },
1564 | {
1565 | "bufferView": 5,
1566 | "byteOffset": 4032,
1567 | "componentType": 5126,
1568 | "count": 48,
1569 | "max": [
1570 | 1,
1571 | 1.000000238418579,
1572 | 1.000000238418579
1573 | ],
1574 | "min": [
1575 | 0.9999996423721313,
1576 | 0.9999995231628418,
1577 | 0.9999995827674866
1578 | ],
1579 | "type": "VEC3"
1580 | },
1581 | {
1582 | "bufferView": 4,
1583 | "byteOffset": 768,
1584 | "componentType": 5126,
1585 | "count": 48,
1586 | "max": [
1587 | 2
1588 | ],
1589 | "min": [
1590 | 0.04166661947965622
1591 | ],
1592 | "type": "SCALAR"
1593 | },
1594 | {
1595 | "bufferView": 5,
1596 | "byteOffset": 4608,
1597 | "componentType": 5126,
1598 | "count": 48,
1599 | "max": [
1600 | -0.0520395003259182,
1601 | 7.450579708745408e-9,
1602 | -0.000002585350102890516
1603 | ],
1604 | "min": [
1605 | -0.05204005911946297,
1606 | -5.96045985901128e-8,
1607 | -0.0000026747600259113824
1608 | ],
1609 | "type": "VEC3"
1610 | },
1611 | {
1612 | "bufferView": 6,
1613 | "byteOffset": 3072,
1614 | "componentType": 5126,
1615 | "count": 48,
1616 | "max": [
1617 | 0.04680187255144119,
1618 | 0.999507486820221,
1619 | 0.002036086050793529,
1620 | 0.09058715403079988
1621 | ],
1622 | "min": [
1623 | -0.093629889190197,
1624 | 0.9950671792030336,
1625 | -0.00258980062790215,
1626 | 0.0184526015073061
1627 | ],
1628 | "type": "VEC4"
1629 | },
1630 | {
1631 | "bufferView": 5,
1632 | "byteOffset": 5184,
1633 | "componentType": 5126,
1634 | "count": 48,
1635 | "max": [
1636 | 1.0000003576278689,
1637 | 1.000000238418579,
1638 | 1.0000009536743164
1639 | ],
1640 | "min": [
1641 | 0.9999998807907104,
1642 | 0.9999996423721313,
1643 | 1.000000238418579
1644 | ],
1645 | "type": "VEC3"
1646 | },
1647 | {
1648 | "bufferView": 4,
1649 | "byteOffset": 960,
1650 | "componentType": 5126,
1651 | "count": 48,
1652 | "max": [
1653 | 2
1654 | ],
1655 | "min": [
1656 | 0.04166661947965622
1657 | ],
1658 | "type": "SCALAR"
1659 | },
1660 | {
1661 | "bufferView": 5,
1662 | "byteOffset": 5760,
1663 | "componentType": 5126,
1664 | "count": 48,
1665 | "max": [
1666 | -0.00003742050103028305,
1667 | 0.08800023794174194,
1668 | -0.00005880460230400786
1669 | ],
1670 | "min": [
1671 | -0.000037621699448209256,
1672 | 0.08799994736909866,
1673 | -0.000059304802562110126
1674 | ],
1675 | "type": "VEC3"
1676 | },
1677 | {
1678 | "bufferView": 6,
1679 | "byteOffset": 3840,
1680 | "componentType": 5126,
1681 | "count": 48,
1682 | "max": [
1683 | 0.2951536476612091,
1684 | 0.9301012754440308,
1685 | -0.2815393805503845,
1686 | 0.3835828900337219
1687 | ],
1688 | "min": [
1689 | -0.13552021980285645,
1690 | 0.8065234422683716,
1691 | -0.4443180561065674,
1692 | -0.17752912640571597
1693 | ],
1694 | "type": "VEC4"
1695 | },
1696 | {
1697 | "bufferView": 5,
1698 | "byteOffset": 6336,
1699 | "componentType": 5126,
1700 | "count": 48,
1701 | "max": [
1702 | 1.0000005960464478,
1703 | 1.0000001192092896,
1704 | 1.0000003576278689
1705 | ],
1706 | "min": [
1707 | 0.9999999403953552,
1708 | 0.9999996423721313,
1709 | 0.9999998211860656
1710 | ],
1711 | "type": "VEC3"
1712 | },
1713 | {
1714 | "bufferView": 4,
1715 | "byteOffset": 1152,
1716 | "componentType": 5126,
1717 | "count": 48,
1718 | "max": [
1719 | 2
1720 | ],
1721 | "min": [
1722 | 0.04166661947965622
1723 | ],
1724 | "type": "SCALAR"
1725 | },
1726 | {
1727 | "bufferView": 5,
1728 | "byteOffset": 6912,
1729 | "componentType": 5126,
1730 | "count": 48,
1731 | "max": [
1732 | 0.013221889734268188,
1733 | 0.215499609708786,
1734 | 0.10933230072259904
1735 | ],
1736 | "min": [
1737 | 0.01322161965072155,
1738 | 0.2154994010925293,
1739 | 0.10933209955692293
1740 | ],
1741 | "type": "VEC3"
1742 | },
1743 | {
1744 | "bufferView": 6,
1745 | "byteOffset": 4608,
1746 | "componentType": 5126,
1747 | "count": 48,
1748 | "max": [
1749 | 0.023567700758576393,
1750 | 0.02101488783955574,
1751 | 0.176296666264534,
1752 | -0.971515953540802
1753 | ],
1754 | "min": [
1755 | -0.0574759915471077,
1756 | -0.18002526462078097,
1757 | -0.15063291788101196,
1758 | -0.998132348060608
1759 | ],
1760 | "type": "VEC4"
1761 | },
1762 | {
1763 | "bufferView": 5,
1764 | "byteOffset": 7488,
1765 | "componentType": 5126,
1766 | "count": 48,
1767 | "max": [
1768 | 0.9999998211860656,
1769 | 0.9999998211860656,
1770 | 0.9999999403953552
1771 | ],
1772 | "min": [
1773 | 0.9999991059303284,
1774 | 0.9999993443489076,
1775 | 0.9999994039535524
1776 | ],
1777 | "type": "VEC3"
1778 | },
1779 | {
1780 | "bufferView": 4,
1781 | "byteOffset": 1344,
1782 | "componentType": 5126,
1783 | "count": 48,
1784 | "max": [
1785 | 2
1786 | ],
1787 | "min": [
1788 | 0.04166661947965622
1789 | ],
1790 | "type": "SCALAR"
1791 | },
1792 | {
1793 | "bufferView": 5,
1794 | "byteOffset": 8064,
1795 | "componentType": 5126,
1796 | "count": 48,
1797 | "max": [
1798 | -0.09332455694675446,
1799 | 0.1430000960826874,
1800 | 0.07814794778823853
1801 | ],
1802 | "min": [
1803 | -0.09332473576068878,
1804 | 0.14299990236759189,
1805 | 0.07814773917198181
1806 | ],
1807 | "type": "VEC3"
1808 | },
1809 | {
1810 | "bufferView": 6,
1811 | "byteOffset": 5376,
1812 | "componentType": 5126,
1813 | "count": 48,
1814 | "max": [
1815 | 0.03372078761458397,
1816 | 0.0026474546175450087,
1817 | 0.207317128777504,
1818 | -0.9705979824066162
1819 | ],
1820 | "min": [
1821 | 0.006105833686888218,
1822 | -0.12215615808963776,
1823 | 0.003784916130825877,
1824 | -0.9994208216667176
1825 | ],
1826 | "type": "VEC4"
1827 | },
1828 | {
1829 | "bufferView": 5,
1830 | "byteOffset": 8640,
1831 | "componentType": 5126,
1832 | "count": 48,
1833 | "max": [
1834 | 1.0000007152557373,
1835 | 1.0000003576278689,
1836 | 1.0000008344650269
1837 | ],
1838 | "min": [
1839 | 1.0000001192092896,
1840 | 0.9999998211860656,
1841 | 1.000000238418579
1842 | ],
1843 | "type": "VEC3"
1844 | },
1845 | {
1846 | "bufferView": 4,
1847 | "byteOffset": 1536,
1848 | "componentType": 5126,
1849 | "count": 48,
1850 | "max": [
1851 | 2
1852 | ],
1853 | "min": [
1854 | 0.04166661947965622
1855 | ],
1856 | "type": "SCALAR"
1857 | },
1858 | {
1859 | "bufferView": 5,
1860 | "byteOffset": 9216,
1861 | "componentType": 5126,
1862 | "count": 48,
1863 | "max": [
1864 | -0.00003894419933203608,
1865 | -0.0879998430609703,
1866 | -0.00005919210161664523
1867 | ],
1868 | "min": [
1869 | -0.0000392795009247493,
1870 | -0.08800008893013,
1871 | -0.00005960090129519813
1872 | ],
1873 | "type": "VEC3"
1874 | },
1875 | {
1876 | "bufferView": 6,
1877 | "byteOffset": 6144,
1878 | "componentType": 5126,
1879 | "count": 48,
1880 | "max": [
1881 | 0.2377220243215561,
1882 | 0.942186713218689,
1883 | 0.37760788202285767,
1884 | 0.2007839232683182
1885 | ],
1886 | "min": [
1887 | -0.2700891792774201,
1888 | 0.8732703924179077,
1889 | 0.2710656225681305,
1890 | -0.2673804461956024
1891 | ],
1892 | "type": "VEC4"
1893 | },
1894 | {
1895 | "bufferView": 5,
1896 | "byteOffset": 9792,
1897 | "componentType": 5126,
1898 | "count": 48,
1899 | "max": [
1900 | 1.000000238418579,
1901 | 1.0000003576278689,
1902 | 1.0000001192092896
1903 | ],
1904 | "min": [
1905 | 0.999999701976776,
1906 | 0.9999997615814208,
1907 | 0.9999997615814208
1908 | ],
1909 | "type": "VEC3"
1910 | },
1911 | {
1912 | "bufferView": 4,
1913 | "byteOffset": 1728,
1914 | "componentType": 5126,
1915 | "count": 48,
1916 | "max": [
1917 | 2
1918 | ],
1919 | "min": [
1920 | 0.04166661947965622
1921 | ],
1922 | "type": "SCALAR"
1923 | },
1924 | {
1925 | "bufferView": 5,
1926 | "byteOffset": 10368,
1927 | "componentType": 5126,
1928 | "count": 48,
1929 | "max": [
1930 | -0.035546209663152695,
1931 | -0.21549880504608157,
1932 | 0.10423330217599867
1933 | ],
1934 | "min": [
1935 | -0.03554638102650643,
1936 | -0.21549910306930545,
1937 | 0.10423299670219421
1938 | ],
1939 | "type": "VEC3"
1940 | },
1941 | {
1942 | "bufferView": 6,
1943 | "byteOffset": 6912,
1944 | "componentType": 5126,
1945 | "count": 48,
1946 | "max": [
1947 | -0.00792065542191267,
1948 | 0.9315358996391296,
1949 | 0.0024673622101545334,
1950 | 0.41479358077049255
1951 | ],
1952 | "min": [
1953 | -0.15234939754009247,
1954 | 0.9063802361488342,
1955 | -0.08167753368616104,
1956 | 0.3280625641345978
1957 | ],
1958 | "type": "VEC4"
1959 | },
1960 | {
1961 | "bufferView": 5,
1962 | "byteOffset": 10944,
1963 | "componentType": 5126,
1964 | "count": 48,
1965 | "max": [
1966 | 1.0000005960464478,
1967 | 1.0000001192092896,
1968 | 1.000000238418579
1969 | ],
1970 | "min": [
1971 | 1,
1972 | 0.9999996423721313,
1973 | 0.9999996423721313
1974 | ],
1975 | "type": "VEC3"
1976 | },
1977 | {
1978 | "bufferView": 4,
1979 | "byteOffset": 1920,
1980 | "componentType": 5126,
1981 | "count": 48,
1982 | "max": [
1983 | 2
1984 | ],
1985 | "min": [
1986 | 0.04166661947965622
1987 | ],
1988 | "type": "SCALAR"
1989 | },
1990 | {
1991 | "bufferView": 5,
1992 | "byteOffset": 11520,
1993 | "componentType": 5126,
1994 | "count": 48,
1995 | "max": [
1996 | 0.03137049078941345,
1997 | -0.1430007964372635,
1998 | -0.11761150509119034
1999 | ],
2000 | "min": [
2001 | 0.03137030825018883,
2002 | -0.1430010050535202,
2003 | -0.11761169880628586
2004 | ],
2005 | "type": "VEC3"
2006 | },
2007 | {
2008 | "bufferView": 6,
2009 | "byteOffset": 7680,
2010 | "componentType": 5126,
2011 | "count": 48,
2012 | "max": [
2013 | 0.22148266434669495,
2014 | 0.3926030695438385,
2015 | 0.08952529728412628,
2016 | -0.9178923964500428
2017 | ],
2018 | "min": [
2019 | 0.055695075541734695,
2020 | 0.277910977602005,
2021 | -0.015314305201172829,
2022 | -0.9438881278038024
2023 | ],
2024 | "type": "VEC4"
2025 | },
2026 | {
2027 | "bufferView": 5,
2028 | "byteOffset": 12096,
2029 | "componentType": 5126,
2030 | "count": 48,
2031 | "max": [
2032 | 1.0000004768371584,
2033 | 1.0000004768371584,
2034 | 1.0000004768371584
2035 | ],
2036 | "min": [
2037 | 0.9999997615814208,
2038 | 0.9999997615814208,
2039 | 0.9999998807907104
2040 | ],
2041 | "type": "VEC3"
2042 | },
2043 | {
2044 | "bufferView": 4,
2045 | "byteOffset": 2112,
2046 | "componentType": 5126,
2047 | "count": 48,
2048 | "max": [
2049 | 2
2050 | ],
2051 | "min": [
2052 | 0.04166661947965622
2053 | ],
2054 | "type": "SCALAR"
2055 | },
2056 | {
2057 | "bufferView": 5,
2058 | "byteOffset": 12672,
2059 | "componentType": 5126,
2060 | "count": 48,
2061 | "max": [
2062 | 0.028520189225673676,
2063 | 0.06762184202671051,
2064 | -0.06295985728502274
2065 | ],
2066 | "min": [
2067 | 0.028520019724965096,
2068 | 0.06762173771858215,
2069 | -0.06296010315418243
2070 | ],
2071 | "type": "VEC3"
2072 | },
2073 | {
2074 | "bufferView": 6,
2075 | "byteOffset": 8448,
2076 | "componentType": 5126,
2077 | "count": 48,
2078 | "max": [
2079 | 0.013129070401191711,
2080 | 0.10440785437822342,
2081 | 0.004284336231648922,
2082 | -0.7728573679924011
2083 | ],
2084 | "min": [
2085 | -0.013805897906422617,
2086 | -0.6344362497329712,
2087 | -0.03212129324674606,
2088 | -0.9994977116584778
2089 | ],
2090 | "type": "VEC4"
2091 | },
2092 | {
2093 | "bufferView": 5,
2094 | "byteOffset": 13248,
2095 | "componentType": 5126,
2096 | "count": 48,
2097 | "max": [
2098 | 1.0000001192092896,
2099 | 1.0000004768371584,
2100 | 1.000000238418579
2101 | ],
2102 | "min": [
2103 | 0.9999995231628418,
2104 | 1,
2105 | 0.9999995827674866
2106 | ],
2107 | "type": "VEC3"
2108 | },
2109 | {
2110 | "bufferView": 4,
2111 | "byteOffset": 2304,
2112 | "componentType": 5126,
2113 | "count": 48,
2114 | "max": [
2115 | 2
2116 | ],
2117 | "min": [
2118 | 0.04166661947965622
2119 | ],
2120 | "type": "SCALAR"
2121 | },
2122 | {
2123 | "bufferView": 5,
2124 | "byteOffset": 13824,
2125 | "componentType": 5126,
2126 | "count": 48,
2127 | "max": [
2128 | 0.209164097905159,
2129 | 0.009055494330823421,
2130 | -0.16426970064640045
2131 | ],
2132 | "min": [
2133 | 0.20916390419006348,
2134 | 0.009055464528501034,
2135 | -0.1642698049545288
2136 | ],
2137 | "type": "VEC3"
2138 | },
2139 | {
2140 | "bufferView": 6,
2141 | "byteOffset": 9216,
2142 | "componentType": 5126,
2143 | "count": 48,
2144 | "max": [
2145 | 0.009955321438610554,
2146 | -0.2965533435344696,
2147 | 0.003957682754844427,
2148 | -0.1911347657442093
2149 | ],
2150 | "min": [
2151 | -0.00983923487365246,
2152 | -0.9813112020492554,
2153 | -0.02193812094628811,
2154 | -0.9549583792686462
2155 | ],
2156 | "type": "VEC4"
2157 | },
2158 | {
2159 | "bufferView": 5,
2160 | "byteOffset": 14400,
2161 | "componentType": 5126,
2162 | "count": 48,
2163 | "max": [
2164 | 1.000000238418579,
2165 | 0.999999463558197,
2166 | 1.0000001192092896
2167 | ],
2168 | "min": [
2169 | 0.999999463558197,
2170 | 0.999998927116394,
2171 | 0.9999993443489076
2172 | ],
2173 | "type": "VEC3"
2174 | },
2175 | {
2176 | "bufferView": 4,
2177 | "byteOffset": 2496,
2178 | "componentType": 5126,
2179 | "count": 48,
2180 | "max": [
2181 | 2
2182 | ],
2183 | "min": [
2184 | 0.04166661947965622
2185 | ],
2186 | "type": "SCALAR"
2187 | },
2188 | {
2189 | "bufferView": 5,
2190 | "byteOffset": 14976,
2191 | "componentType": 5126,
2192 | "count": 48,
2193 | "max": [
2194 | 0.2757900059223175,
2195 | 0.0013972820015624166,
2196 | 0.004122554790228605
2197 | ],
2198 | "min": [
2199 | 0.27578991651535034,
2200 | 0.0013972449814900756,
2201 | 0.004122436046600342
2202 | ],
2203 | "type": "VEC3"
2204 | },
2205 | {
2206 | "bufferView": 6,
2207 | "byteOffset": 9984,
2208 | "componentType": 5126,
2209 | "count": 48,
2210 | "max": [
2211 | 0.01264618057757616,
2212 | -0.8448027968406677,
2213 | 0.03285584971308708,
2214 | -0.15347692370414737
2215 | ],
2216 | "min": [
2217 | -0.045710742473602295,
2218 | -0.9879721403121948,
2219 | 0.007757793180644512,
2220 | -0.5345877408981323
2221 | ],
2222 | "type": "VEC4"
2223 | },
2224 | {
2225 | "bufferView": 5,
2226 | "byteOffset": 15552,
2227 | "componentType": 5126,
2228 | "count": 48,
2229 | "max": [
2230 | 1.0000008344650269,
2231 | 1.0000009536743164,
2232 | 1.0000004768371584
2233 | ],
2234 | "min": [
2235 | 1.000000238418579,
2236 | 1.0000003576278689,
2237 | 0.9999997615814208
2238 | ],
2239 | "type": "VEC3"
2240 | },
2241 | {
2242 | "bufferView": 4,
2243 | "byteOffset": 2688,
2244 | "componentType": 5126,
2245 | "count": 48,
2246 | "max": [
2247 | 2
2248 | ],
2249 | "min": [
2250 | 0.04166661947965622
2251 | ],
2252 | "type": "SCALAR"
2253 | },
2254 | {
2255 | "bufferView": 5,
2256 | "byteOffset": 16128,
2257 | "componentType": 5126,
2258 | "count": 48,
2259 | "max": [
2260 | -0.06558377295732498,
2261 | 0.00109061598777771,
2262 | 0.029291389510035515
2263 | ],
2264 | "min": [
2265 | -0.06558384746313095,
2266 | 0.001090570935048163,
2267 | 0.029291240498423576
2268 | ],
2269 | "type": "VEC3"
2270 | },
2271 | {
2272 | "bufferView": 6,
2273 | "byteOffset": 10752,
2274 | "componentType": 5126,
2275 | "count": 48,
2276 | "max": [
2277 | 0.022798636928200725,
2278 | 0.5332140922546387,
2279 | -0.003377946326509118,
2280 | -0.844382643699646
2281 | ],
2282 | "min": [
2283 | 0.007516560610383749,
2284 | 0.22626954317092896,
2285 | -0.04913739487528801,
2286 | -0.972984254360199
2287 | ],
2288 | "type": "VEC4"
2289 | },
2290 | {
2291 | "bufferView": 5,
2292 | "byteOffset": 16704,
2293 | "componentType": 5126,
2294 | "count": 48,
2295 | "max": [
2296 | 1.0000008344650269,
2297 | 1.0000003576278689,
2298 | 1.0000003576278689
2299 | ],
2300 | "min": [
2301 | 0.9999998211860656,
2302 | 0.999999701976776,
2303 | 0.9999995231628418
2304 | ],
2305 | "type": "VEC3"
2306 | },
2307 | {
2308 | "bufferView": 4,
2309 | "byteOffset": 2880,
2310 | "componentType": 5126,
2311 | "count": 48,
2312 | "max": [
2313 | 2
2314 | ],
2315 | "min": [
2316 | 0.04166661947965622
2317 | ],
2318 | "type": "SCALAR"
2319 | },
2320 | {
2321 | "bufferView": 5,
2322 | "byteOffset": 17280,
2323 | "componentType": 5126,
2324 | "count": 48,
2325 | "max": [
2326 | 0.028557300567626953,
2327 | -0.0684543326497078,
2328 | -0.06295845657587051
2329 | ],
2330 | "min": [
2331 | 0.028557060286402702,
2332 | -0.06845436990261078,
2333 | -0.0629587471485138
2334 | ],
2335 | "type": "VEC3"
2336 | },
2337 | {
2338 | "bufferView": 6,
2339 | "byteOffset": 11520,
2340 | "componentType": 5126,
2341 | "count": 48,
2342 | "max": [
2343 | 0.04037770628929138,
2344 | -0.2803998589515686,
2345 | 0.02151232957839966,
2346 | -0.32386553287506104
2347 | ],
2348 | "min": [
2349 | -0.009615562856197357,
2350 | -0.9458208084106444,
2351 | -0.006491139996796846,
2352 | -0.9590301513671876
2353 | ],
2354 | "type": "VEC4"
2355 | },
2356 | {
2357 | "bufferView": 5,
2358 | "byteOffset": 17856,
2359 | "componentType": 5126,
2360 | "count": 48,
2361 | "max": [
2362 | 1.000000238418579,
2363 | 1.0000005960464478,
2364 | 1.0000005960464478
2365 | ],
2366 | "min": [
2367 | 0.9999994039535524,
2368 | 0.9999999403953552,
2369 | 0.9999998211860656
2370 | ],
2371 | "type": "VEC3"
2372 | },
2373 | {
2374 | "bufferView": 4,
2375 | "byteOffset": 3072,
2376 | "componentType": 5126,
2377 | "count": 48,
2378 | "max": [
2379 | 2
2380 | ],
2381 | "min": [
2382 | 0.04166661947965622
2383 | ],
2384 | "type": "SCALAR"
2385 | },
2386 | {
2387 | "bufferView": 5,
2388 | "byteOffset": 18432,
2389 | "componentType": 5126,
2390 | "count": 48,
2391 | "max": [
2392 | 0.2608909010887146,
2393 | -0.00902603566646576,
2394 | 0.05167100951075554
2395 | ],
2396 | "min": [
2397 | 0.2608906924724579,
2398 | -0.009026064537465572,
2399 | 0.05167080089449883
2400 | ],
2401 | "type": "VEC3"
2402 | },
2403 | {
2404 | "bufferView": 6,
2405 | "byteOffset": 12288,
2406 | "componentType": 5126,
2407 | "count": 48,
2408 | "max": [
2409 | 0.02468797937035561,
2410 | 0.19154119491577148,
2411 | 0.017835097387433052,
2412 | -0.6250466108322144
2413 | ],
2414 | "min": [
2415 | -0.013421673327684404,
2416 | -0.7804162502288818,
2417 | -0.031287722289562225,
2418 | -0.9999792575836182
2419 | ],
2420 | "type": "VEC4"
2421 | },
2422 | {
2423 | "bufferView": 5,
2424 | "byteOffset": 19008,
2425 | "componentType": 5126,
2426 | "count": 48,
2427 | "max": [
2428 | 1.0000003576278689,
2429 | 1.0000007152557373,
2430 | 1.000001072883606
2431 | ],
2432 | "min": [
2433 | 0.999999463558197,
2434 | 1,
2435 | 0.999999701976776
2436 | ],
2437 | "type": "VEC3"
2438 | },
2439 | {
2440 | "bufferView": 4,
2441 | "byteOffset": 3264,
2442 | "componentType": 5126,
2443 | "count": 48,
2444 | "max": [
2445 | 2
2446 | ],
2447 | "min": [
2448 | 0.04166661947965622
2449 | ],
2450 | "type": "SCALAR"
2451 | },
2452 | {
2453 | "bufferView": 5,
2454 | "byteOffset": 19584,
2455 | "componentType": 5126,
2456 | "count": 48,
2457 | "max": [
2458 | 0.2754603922367096,
2459 | -0.0014316890155896544,
2460 | -0.014104750007390976
2461 | ],
2462 | "min": [
2463 | 0.27546021342277527,
2464 | -0.0014317409368231893,
2465 | -0.014104840345680714
2466 | ],
2467 | "type": "VEC3"
2468 | },
2469 | {
2470 | "bufferView": 6,
2471 | "byteOffset": 13056,
2472 | "componentType": 5126,
2473 | "count": 48,
2474 | "max": [
2475 | 0.022092316299676895,
2476 | 0.9990847110748292,
2477 | 0.04779285565018654,
2478 | 0.4428757429122925
2479 | ],
2480 | "min": [
2481 | -0.001671039150096476,
2482 | 0.8965795040130615,
2483 | 0.002310338197275996,
2484 | 0.0384783074259758
2485 | ],
2486 | "type": "VEC4"
2487 | },
2488 | {
2489 | "bufferView": 5,
2490 | "byteOffset": 20160,
2491 | "componentType": 5126,
2492 | "count": 48,
2493 | "max": [
2494 | 0.9999999403953552,
2495 | 0.9999996423721313,
2496 | 1.000000238418579
2497 | ],
2498 | "min": [
2499 | 0.9999994039535524,
2500 | 0.9999991655349731,
2501 | 0.9999996423721313
2502 | ],
2503 | "type": "VEC3"
2504 | },
2505 | {
2506 | "bufferView": 4,
2507 | "byteOffset": 3456,
2508 | "componentType": 5126,
2509 | "count": 48,
2510 | "max": [
2511 | 2
2512 | ],
2513 | "min": [
2514 | 0.04166661947965622
2515 | ],
2516 | "type": "SCALAR"
2517 | },
2518 | {
2519 | "bufferView": 5,
2520 | "byteOffset": 20736,
2521 | "componentType": 5126,
2522 | "count": 48,
2523 | "max": [
2524 | -0.06681966781616211,
2525 | -0.0010721459984779358,
2526 | 0.026351390406489372
2527 | ],
2528 | "min": [
2529 | -0.06681978702545166,
2530 | -0.001072190934792161,
2531 | 0.02635126002132893
2532 | ],
2533 | "type": "VEC3"
2534 | },
2535 | {
2536 | "bufferView": 6,
2537 | "byteOffset": 13824,
2538 | "componentType": 5126,
2539 | "count": 48,
2540 | "max": [
2541 | 0.003402489935979247,
2542 | 0.4966025054454804,
2543 | 0.1101396307349205,
2544 | -0.8675833940505981
2545 | ],
2546 | "min": [
2547 | -0.027623889967799187,
2548 | 0.26874634623527527,
2549 | -0.02591408602893353,
2550 | -0.9565747380256652
2551 | ],
2552 | "type": "VEC4"
2553 | },
2554 | {
2555 | "bufferView": 5,
2556 | "byteOffset": 21312,
2557 | "componentType": 5126,
2558 | "count": 48,
2559 | "max": [
2560 | 1.0000004768371584,
2561 | 0.9999998211860656,
2562 | 0.9999994039535524
2563 | ],
2564 | "min": [
2565 | 0.9999995231628418,
2566 | 0.999999225139618,
2567 | 0.9999986886978148
2568 | ],
2569 | "type": "VEC3"
2570 | },
2571 | {
2572 | "bufferView": 7,
2573 | "byteOffset": 0,
2574 | "componentType": 5126,
2575 | "count": 19,
2576 | "max": [
2577 | 0.9971418380737304,
2578 | -4.371139894487897e-8,
2579 | 0.9996265172958374,
2580 | 0,
2581 | 4.3586464215650273e-8,
2582 | 1,
2583 | 4.3695074225524884e-8,
2584 | 0,
2585 | 0.9999366402626038,
2586 | 0,
2587 | 0.9971418380737304,
2588 | 0,
2589 | 1.1374080181121828,
2590 | 0.44450080394744873,
2591 | 1.0739599466323853,
2592 | 1
2593 | ],
2594 | "min": [
2595 | -0.9999089241027832,
2596 | -4.371139894487897e-8,
2597 | -0.9999366402626038,
2598 | 0,
2599 | -4.3707416352845037e-8,
2600 | 1,
2601 | -4.37086278282095e-8,
2602 | 0,
2603 | -0.9996265172958374,
2604 | 0,
2605 | -0.9999089241027832,
2606 | 0,
2607 | -1.189831018447876,
2608 | -0.45450031757354736,
2609 | -1.058603048324585,
2610 | 1
2611 | ],
2612 | "type": "MAT4"
2613 | }
2614 | ],
2615 | "materials": [
2616 | {
2617 | "pbrMetallicRoughness": {
2618 | "baseColorTexture": {
2619 | "index": 0,
2620 | "texCoord": 0
2621 | },
2622 | "metallicFactor": 0,
2623 | "baseColorFactor": [
2624 | 1,
2625 | 1,
2626 | 1,
2627 | 1
2628 | ],
2629 | "roughnessFactor": 1
2630 | },
2631 | "emissiveFactor": [
2632 | 0,
2633 | 0,
2634 | 0
2635 | ],
2636 | "name": "Cesium_Man-effect",
2637 | "alphaMode": "OPAQUE",
2638 | "doubleSided": false
2639 | }
2640 | ],
2641 | "textures": [
2642 | {
2643 | "sampler": 0,
2644 | "source": 0
2645 | }
2646 | ],
2647 | "images": [
2648 | {
2649 | "uri": "cesium.jpg"
2650 | }
2651 | ],
2652 | "samplers": [
2653 | {
2654 | "magFilter": 9729,
2655 | "minFilter": 9986,
2656 | "wrapS": 10497,
2657 | "wrapT": 10497
2658 | }
2659 | ],
2660 | "bufferViews": [
2661 | {
2662 | "buffer": 0,
2663 | "byteOffset": 0,
2664 | "byteLength": 28032,
2665 | "target": 34963
2666 | },
2667 | {
2668 | "buffer": 0,
2669 | "byteOffset": 28032,
2670 | "byteLength": 52368,
2671 | "byteStride": 8,
2672 | "target": 34962
2673 | },
2674 | {
2675 | "buffer": 0,
2676 | "byteOffset": 80400,
2677 | "byteLength": 78552,
2678 | "byteStride": 12,
2679 | "target": 34962
2680 | },
2681 | {
2682 | "buffer": 0,
2683 | "byteOffset": 158952,
2684 | "byteLength": 52368,
2685 | "byteStride": 16,
2686 | "target": 34962
2687 | },
2688 | {
2689 | "buffer": 0,
2690 | "byteOffset": 211320,
2691 | "byteLength": 3648
2692 | },
2693 | {
2694 | "buffer": 0,
2695 | "byteOffset": 214968,
2696 | "byteLength": 21888
2697 | },
2698 | {
2699 | "buffer": 0,
2700 | "byteOffset": 236856,
2701 | "byteLength": 14592
2702 | },
2703 | {
2704 | "buffer": 0,
2705 | "byteOffset": 251448,
2706 | "byteLength": 1216
2707 | }
2708 | ],
2709 | "buffers": [
2710 | {
2711 | "uri": "cesium.bin",
2712 | "byteLength": 252664
2713 | }
2714 | ]
2715 | }
--------------------------------------------------------------------------------
/example/static/models/cesium/cesium.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/larsjarlvik/webgl-gltf/46724143e451f4781eb2949ff5e50453d4c70e50/example/static/models/cesium/cesium.jpg
--------------------------------------------------------------------------------
/example/static/models/cylinder/cylinder.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/larsjarlvik/webgl-gltf/46724143e451f4781eb2949ff5e50453d4c70e50/example/static/models/cylinder/cylinder.bin
--------------------------------------------------------------------------------
/example/static/models/cylinder/cylinder.gltf:
--------------------------------------------------------------------------------
1 | {
2 | "asset" : {
3 | "generator" : "Khronos glTF Blender I/O v1.1.45",
4 | "version" : "2.0"
5 | },
6 | "scene" : 0,
7 | "scenes" : [
8 | {
9 | "name" : "Scene",
10 | "nodes" : [
11 | 3
12 | ]
13 | }
14 | ],
15 | "nodes" : [
16 | {
17 | "name" : "Bone.001",
18 | "rotation" : [
19 | -0.001818173099309206,
20 | -5.644409184820943e-17,
21 | -5.624599863511524e-17,
22 | 0.9999984502792358
23 | ],
24 | "translation" : [
25 | 0,
26 | 1.963363528251648,
27 | -5.877471754111438e-39
28 | ]
29 | },
30 | {
31 | "children" : [
32 | 0
33 | ],
34 | "name" : "Bone",
35 | "rotation" : [
36 | -6.047974270962333e-33,
37 | -5.447530934610462e-17,
38 | -5.447530934610462e-17,
39 | 1
40 | ]
41 | },
42 | {
43 | "mesh" : 0,
44 | "name" : "Cylinder",
45 | "skin" : 0
46 | },
47 | {
48 | "children" : [
49 | 2,
50 | 1
51 | ],
52 | "name" : "Armature"
53 | }
54 | ],
55 | "animations" : [
56 | {
57 | "channels" : [
58 | {
59 | "sampler" : 0,
60 | "target" : {
61 | "node" : 1,
62 | "path" : "translation"
63 | }
64 | },
65 | {
66 | "sampler" : 1,
67 | "target" : {
68 | "node" : 1,
69 | "path" : "rotation"
70 | }
71 | },
72 | {
73 | "sampler" : 2,
74 | "target" : {
75 | "node" : 1,
76 | "path" : "scale"
77 | }
78 | },
79 | {
80 | "sampler" : 3,
81 | "target" : {
82 | "node" : 0,
83 | "path" : "translation"
84 | }
85 | },
86 | {
87 | "sampler" : 4,
88 | "target" : {
89 | "node" : 0,
90 | "path" : "rotation"
91 | }
92 | },
93 | {
94 | "sampler" : 5,
95 | "target" : {
96 | "node" : 0,
97 | "path" : "scale"
98 | }
99 | }
100 | ],
101 | "name" : "ArmatureAction",
102 | "samplers" : [
103 | {
104 | "input" : 6,
105 | "interpolation" : "LINEAR",
106 | "output" : 7
107 | },
108 | {
109 | "input" : 6,
110 | "interpolation" : "LINEAR",
111 | "output" : 8
112 | },
113 | {
114 | "input" : 6,
115 | "interpolation" : "LINEAR",
116 | "output" : 9
117 | },
118 | {
119 | "input" : 6,
120 | "interpolation" : "LINEAR",
121 | "output" : 10
122 | },
123 | {
124 | "input" : 6,
125 | "interpolation" : "LINEAR",
126 | "output" : 11
127 | },
128 | {
129 | "input" : 6,
130 | "interpolation" : "LINEAR",
131 | "output" : 12
132 | }
133 | ]
134 | }
135 | ],
136 | "meshes" : [
137 | {
138 | "name" : "Cylinder",
139 | "primitives" : [
140 | {
141 | "attributes" : {
142 | "POSITION" : 0,
143 | "NORMAL" : 1,
144 | "JOINTS_0" : 2,
145 | "WEIGHTS_0" : 3
146 | },
147 | "indices" : 4
148 | }
149 | ]
150 | }
151 | ],
152 | "skins" : [
153 | {
154 | "inverseBindMatrices" : 5,
155 | "joints" : [
156 | 1,
157 | 0
158 | ],
159 | "name" : "Armature"
160 | }
161 | ],
162 | "accessors" : [
163 | {
164 | "bufferView" : 0,
165 | "componentType" : 5126,
166 | "count" : 2074,
167 | "max" : [
168 | 1,
169 | 3.9558234214782715,
170 | 1
171 | ],
172 | "min" : [
173 | -1.0000001192092896,
174 | 0,
175 | -1
176 | ],
177 | "type" : "VEC3"
178 | },
179 | {
180 | "bufferView" : 1,
181 | "componentType" : 5126,
182 | "count" : 2074,
183 | "type" : "VEC3"
184 | },
185 | {
186 | "bufferView" : 2,
187 | "componentType" : 5123,
188 | "count" : 2074,
189 | "type" : "VEC4"
190 | },
191 | {
192 | "bufferView" : 3,
193 | "componentType" : 5126,
194 | "count" : 2074,
195 | "type" : "VEC4"
196 | },
197 | {
198 | "bufferView" : 4,
199 | "componentType" : 5123,
200 | "count" : 3828,
201 | "type" : "SCALAR"
202 | },
203 | {
204 | "bufferView" : 5,
205 | "componentType" : 5126,
206 | "count" : 2,
207 | "type" : "MAT4"
208 | },
209 | {
210 | "bufferView" : 6,
211 | "componentType" : 5126,
212 | "count" : 50,
213 | "max" : [
214 | 2.0833333333333335
215 | ],
216 | "min" : [
217 | 0.041666666666666664
218 | ],
219 | "type" : "SCALAR"
220 | },
221 | {
222 | "bufferView" : 7,
223 | "componentType" : 5126,
224 | "count" : 50,
225 | "type" : "VEC3"
226 | },
227 | {
228 | "bufferView" : 8,
229 | "componentType" : 5126,
230 | "count" : 50,
231 | "type" : "VEC4"
232 | },
233 | {
234 | "bufferView" : 9,
235 | "componentType" : 5126,
236 | "count" : 50,
237 | "type" : "VEC3"
238 | },
239 | {
240 | "bufferView" : 10,
241 | "componentType" : 5126,
242 | "count" : 50,
243 | "type" : "VEC3"
244 | },
245 | {
246 | "bufferView" : 11,
247 | "componentType" : 5126,
248 | "count" : 50,
249 | "type" : "VEC4"
250 | },
251 | {
252 | "bufferView" : 12,
253 | "componentType" : 5126,
254 | "count" : 50,
255 | "type" : "VEC3"
256 | }
257 | ],
258 | "bufferViews" : [
259 | {
260 | "buffer" : 0,
261 | "byteLength" : 24888,
262 | "byteOffset" : 0
263 | },
264 | {
265 | "buffer" : 0,
266 | "byteLength" : 24888,
267 | "byteOffset" : 24888
268 | },
269 | {
270 | "buffer" : 0,
271 | "byteLength" : 16592,
272 | "byteOffset" : 49776
273 | },
274 | {
275 | "buffer" : 0,
276 | "byteLength" : 33184,
277 | "byteOffset" : 66368
278 | },
279 | {
280 | "buffer" : 0,
281 | "byteLength" : 7656,
282 | "byteOffset" : 99552
283 | },
284 | {
285 | "buffer" : 0,
286 | "byteLength" : 128,
287 | "byteOffset" : 107208
288 | },
289 | {
290 | "buffer" : 0,
291 | "byteLength" : 200,
292 | "byteOffset" : 107336
293 | },
294 | {
295 | "buffer" : 0,
296 | "byteLength" : 600,
297 | "byteOffset" : 107536
298 | },
299 | {
300 | "buffer" : 0,
301 | "byteLength" : 800,
302 | "byteOffset" : 108136
303 | },
304 | {
305 | "buffer" : 0,
306 | "byteLength" : 600,
307 | "byteOffset" : 108936
308 | },
309 | {
310 | "buffer" : 0,
311 | "byteLength" : 600,
312 | "byteOffset" : 109536
313 | },
314 | {
315 | "buffer" : 0,
316 | "byteLength" : 800,
317 | "byteOffset" : 110136
318 | },
319 | {
320 | "buffer" : 0,
321 | "byteLength" : 600,
322 | "byteOffset" : 110936
323 | }
324 | ],
325 | "buffers" : [
326 | {
327 | "byteLength" : 111536,
328 | "uri" : "cylinder.bin"
329 | }
330 | ]
331 | }
332 |
--------------------------------------------------------------------------------
/example/static/models/launcher/launcher.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/larsjarlvik/webgl-gltf/46724143e451f4781eb2949ff5e50453d4c70e50/example/static/models/launcher/launcher.bin
--------------------------------------------------------------------------------
/example/static/models/launcher/launcher.gltf:
--------------------------------------------------------------------------------
1 | {
2 | "asset" : {
3 | "generator" : "Khronos glTF Blender I/O v1.1.45",
4 | "version" : "2.0"
5 | },
6 | "scene" : 0,
7 | "scenes" : [
8 | {
9 | "name" : "Scene",
10 | "nodes" : [
11 | 0
12 | ]
13 | }
14 | ],
15 | "nodes" : [
16 | {
17 | "mesh" : 0,
18 | "name" : "Cube",
19 | "translation" : [
20 | 0,
21 | -0.051240984350442886,
22 | 0
23 | ]
24 | }
25 | ],
26 | "materials" : [
27 | {
28 | "doubleSided" : true,
29 | "emissiveFactor" : [
30 | 1,
31 | 1,
32 | 1
33 | ],
34 | "emissiveTexture" : {
35 | "index" : 0,
36 | "texCoord" : 0
37 | },
38 | "name" : "Material.002",
39 | "normalTexture" : {
40 | "index" : 1,
41 | "texCoord" : 0
42 | },
43 | "pbrMetallicRoughness" : {
44 | "baseColorTexture" : {
45 | "index" : 2,
46 | "texCoord" : 0
47 | },
48 | "metallicFactor" : 0,
49 | "metallicRoughnessTexture" : {
50 | "index" : 3,
51 | "texCoord" : 0
52 | }
53 | }
54 | }
55 | ],
56 | "meshes" : [
57 | {
58 | "name" : "Cube.001",
59 | "primitives" : [
60 | {
61 | "attributes" : {
62 | "POSITION" : 0,
63 | "NORMAL" : 1,
64 | "TEXCOORD_0" : 2
65 | },
66 | "indices" : 3,
67 | "material" : 0
68 | }
69 | ]
70 | }
71 | ],
72 | "textures" : [
73 | {
74 | "source" : 0
75 | },
76 | {
77 | "source" : 1
78 | },
79 | {
80 | "source" : 2
81 | },
82 | {
83 | "source" : 3
84 | }
85 | ],
86 | "images" : [
87 | {
88 | "mimeType" : "image/png",
89 | "name" : "launcher_emissive",
90 | "uri" : "launcher_emissive.png"
91 | },
92 | {
93 | "mimeType" : "image/png",
94 | "name" : "launcher_normal",
95 | "uri" : "launcher_normal.png"
96 | },
97 | {
98 | "mimeType" : "image/png",
99 | "name" : "launcher_baseColor",
100 | "uri" : "launcher_baseColor.png"
101 | },
102 | {
103 | "mimeType" : "image/png",
104 | "name" : "launcher_occlusionRoughnessMetallic",
105 | "uri" : "launcher_occlusionRoughnessMetallic.png"
106 | }
107 | ],
108 | "accessors" : [
109 | {
110 | "bufferView" : 0,
111 | "componentType" : 5126,
112 | "count" : 124,
113 | "max" : [
114 | 1.6412103176116943,
115 | 0.09945563971996307,
116 | 0.39042335748672485
117 | ],
118 | "min" : [
119 | 1.363183617591858,
120 | -0.04249188303947449,
121 | -0.39042335748672485
122 | ],
123 | "type" : "VEC3"
124 | },
125 | {
126 | "bufferView" : 1,
127 | "componentType" : 5126,
128 | "count" : 124,
129 | "type" : "VEC3"
130 | },
131 | {
132 | "bufferView" : 2,
133 | "componentType" : 5126,
134 | "count" : 124,
135 | "type" : "VEC2"
136 | },
137 | {
138 | "bufferView" : 3,
139 | "componentType" : 5123,
140 | "count" : 204,
141 | "type" : "SCALAR"
142 | }
143 | ],
144 | "bufferViews" : [
145 | {
146 | "buffer" : 0,
147 | "byteLength" : 1488,
148 | "byteOffset" : 0
149 | },
150 | {
151 | "buffer" : 0,
152 | "byteLength" : 1488,
153 | "byteOffset" : 1488
154 | },
155 | {
156 | "buffer" : 0,
157 | "byteLength" : 992,
158 | "byteOffset" : 2976
159 | },
160 | {
161 | "buffer" : 0,
162 | "byteLength" : 408,
163 | "byteOffset" : 3968
164 | }
165 | ],
166 | "buffers" : [
167 | {
168 | "byteLength" : 4376,
169 | "uri" : "launcher.bin"
170 | }
171 | ]
172 | }
173 |
--------------------------------------------------------------------------------
/example/static/models/launcher/launcher_baseColor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/larsjarlvik/webgl-gltf/46724143e451f4781eb2949ff5e50453d4c70e50/example/static/models/launcher/launcher_baseColor.png
--------------------------------------------------------------------------------
/example/static/models/launcher/launcher_emissive.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/larsjarlvik/webgl-gltf/46724143e451f4781eb2949ff5e50453d4c70e50/example/static/models/launcher/launcher_emissive.png
--------------------------------------------------------------------------------
/example/static/models/launcher/launcher_normal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/larsjarlvik/webgl-gltf/46724143e451f4781eb2949ff5e50453d4c70e50/example/static/models/launcher/launcher_normal.png
--------------------------------------------------------------------------------
/example/static/models/launcher/launcher_occlusionRoughnessMetallic.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/larsjarlvik/webgl-gltf/46724143e451f4781eb2949ff5e50453d4c70e50/example/static/models/launcher/launcher_occlusionRoughnessMetallic.png
--------------------------------------------------------------------------------
/example/static/models/platform/platform.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/larsjarlvik/webgl-gltf/46724143e451f4781eb2949ff5e50453d4c70e50/example/static/models/platform/platform.bin
--------------------------------------------------------------------------------
/example/static/models/platform/platform.gltf:
--------------------------------------------------------------------------------
1 | {
2 | "asset" : {
3 | "generator" : "Khronos glTF Blender I/O v1.1.45",
4 | "version" : "2.0"
5 | },
6 | "scene" : 0,
7 | "scenes" : [
8 | {
9 | "name" : "Scene",
10 | "nodes" : [
11 | 0
12 | ]
13 | }
14 | ],
15 | "nodes" : [
16 | {
17 | "mesh" : 0,
18 | "name" : "Cylinder",
19 | "translation" : [
20 | 0,
21 | -0.051240984350442886,
22 | 0
23 | ]
24 | }
25 | ],
26 | "materials" : [
27 | {
28 | "doubleSided" : true,
29 | "emissiveFactor" : [
30 | 0,
31 | 0,
32 | 0
33 | ],
34 | "name" : "Material.001",
35 | "normalTexture" : {
36 | "index" : 0,
37 | "texCoord" : 0
38 | },
39 | "pbrMetallicRoughness" : {
40 | "baseColorTexture" : {
41 | "index" : 1,
42 | "texCoord" : 0
43 | },
44 | "metallicFactor" : 0,
45 | "metallicRoughnessTexture" : {
46 | "index" : 2,
47 | "texCoord" : 0
48 | }
49 | }
50 | }
51 | ],
52 | "meshes" : [
53 | {
54 | "name" : "Cylinder",
55 | "primitives" : [
56 | {
57 | "attributes" : {
58 | "POSITION" : 0,
59 | "NORMAL" : 1,
60 | "TANGENT" : 2,
61 | "TEXCOORD_0" : 3
62 | },
63 | "indices" : 4,
64 | "material" : 0
65 | }
66 | ]
67 | }
68 | ],
69 | "textures" : [
70 | {
71 | "source" : 0
72 | },
73 | {
74 | "source" : 1
75 | },
76 | {
77 | "source" : 2
78 | }
79 | ],
80 | "images" : [
81 | {
82 | "mimeType" : "image/png",
83 | "name" : "platform_normal",
84 | "uri" : "platform_normal.png"
85 | },
86 | {
87 | "mimeType" : "image/png",
88 | "name" : "platform_baseColor",
89 | "uri" : "platform_baseColor.png"
90 | },
91 | {
92 | "mimeType" : "image/png",
93 | "name" : "platform_occlusionRoughnessMetallic",
94 | "uri" : "platform_occlusionRoughnessMetallic.png"
95 | }
96 | ],
97 | "accessors" : [
98 | {
99 | "bufferView" : 0,
100 | "componentType" : 5126,
101 | "count" : 318,
102 | "max" : [
103 | 1.3858191967010498,
104 | 0.05000000074505806,
105 | 1.3858191967010498
106 | ],
107 | "min" : [
108 | -1.385819435119629,
109 | -0.05000000447034836,
110 | -1.3858193159103394
111 | ],
112 | "type" : "VEC3"
113 | },
114 | {
115 | "bufferView" : 1,
116 | "componentType" : 5126,
117 | "count" : 318,
118 | "type" : "VEC3"
119 | },
120 | {
121 | "bufferView" : 2,
122 | "componentType" : 5126,
123 | "count" : 318,
124 | "type" : "VEC4"
125 | },
126 | {
127 | "bufferView" : 3,
128 | "componentType" : 5126,
129 | "count" : 318,
130 | "type" : "VEC2"
131 | },
132 | {
133 | "bufferView" : 4,
134 | "componentType" : 5123,
135 | "count" : 324,
136 | "type" : "SCALAR"
137 | }
138 | ],
139 | "bufferViews" : [
140 | {
141 | "buffer" : 0,
142 | "byteLength" : 3816,
143 | "byteOffset" : 0
144 | },
145 | {
146 | "buffer" : 0,
147 | "byteLength" : 3816,
148 | "byteOffset" : 3816
149 | },
150 | {
151 | "buffer" : 0,
152 | "byteLength" : 5088,
153 | "byteOffset" : 7632
154 | },
155 | {
156 | "buffer" : 0,
157 | "byteLength" : 2544,
158 | "byteOffset" : 12720
159 | },
160 | {
161 | "buffer" : 0,
162 | "byteLength" : 648,
163 | "byteOffset" : 15264
164 | }
165 | ],
166 | "buffers" : [
167 | {
168 | "byteLength" : 15912,
169 | "uri" : "platform.bin"
170 | }
171 | ]
172 | }
173 |
--------------------------------------------------------------------------------
/example/static/models/platform/platform_baseColor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/larsjarlvik/webgl-gltf/46724143e451f4781eb2949ff5e50453d4c70e50/example/static/models/platform/platform_baseColor.png
--------------------------------------------------------------------------------
/example/static/models/platform/platform_normal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/larsjarlvik/webgl-gltf/46724143e451f4781eb2949ff5e50453d4c70e50/example/static/models/platform/platform_normal.png
--------------------------------------------------------------------------------
/example/static/models/platform/platform_occlusionRoughnessMetallic.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/larsjarlvik/webgl-gltf/46724143e451f4781eb2949ff5e50453d4c70e50/example/static/models/platform/platform_occlusionRoughnessMetallic.png
--------------------------------------------------------------------------------
/example/static/models/robot/robot.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/larsjarlvik/webgl-gltf/46724143e451f4781eb2949ff5e50453d4c70e50/example/static/models/robot/robot.bin
--------------------------------------------------------------------------------
/example/static/models/robot/robot_baseColor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/larsjarlvik/webgl-gltf/46724143e451f4781eb2949ff5e50453d4c70e50/example/static/models/robot/robot_baseColor.png
--------------------------------------------------------------------------------
/example/static/models/robot/robot_normal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/larsjarlvik/webgl-gltf/46724143e451f4781eb2949ff5e50453d4c70e50/example/static/models/robot/robot_normal.png
--------------------------------------------------------------------------------
/example/static/models/robot/robot_occlusionRoughnessMetallic.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/larsjarlvik/webgl-gltf/46724143e451f4781eb2949ff5e50453d4c70e50/example/static/models/robot/robot_occlusionRoughnessMetallic.png
--------------------------------------------------------------------------------
/example/static/models/suzanne/baseColor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/larsjarlvik/webgl-gltf/46724143e451f4781eb2949ff5e50453d4c70e50/example/static/models/suzanne/baseColor.png
--------------------------------------------------------------------------------
/example/static/models/suzanne/roughness.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/larsjarlvik/webgl-gltf/46724143e451f4781eb2949ff5e50453d4c70e50/example/static/models/suzanne/roughness.png
--------------------------------------------------------------------------------
/example/static/models/suzanne/suzanne.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/larsjarlvik/webgl-gltf/46724143e451f4781eb2949ff5e50453d4c70e50/example/static/models/suzanne/suzanne.bin
--------------------------------------------------------------------------------
/example/static/models/suzanne/suzanne.gltf:
--------------------------------------------------------------------------------
1 | {
2 | "accessors" : [
3 | {
4 | "bufferView" : 0,
5 | "byteOffset" : 0,
6 | "componentType" : 5123,
7 | "count" : 11808,
8 | "max" : [
9 | 11807
10 | ],
11 | "min" : [
12 | 0
13 | ],
14 | "type" : "SCALAR"
15 | },
16 | {
17 | "bufferView" : 1,
18 | "byteOffset" : 0,
19 | "componentType" : 5126,
20 | "count" : 11808,
21 | "max" : [
22 | 1.336914,
23 | 0.950195,
24 | 0.825684
25 | ],
26 | "min" : [
27 | -1.336914,
28 | -0.974609,
29 | -0.800781
30 | ],
31 | "type" : "VEC3"
32 | },
33 | {
34 | "bufferView" : 2,
35 | "byteOffset" : 0,
36 | "componentType" : 5126,
37 | "count" : 11808,
38 | "max" : [
39 | 0.996339,
40 | 0.999958,
41 | 0.999929
42 | ],
43 | "min" : [
44 | -0.996339,
45 | -0.985940,
46 | -0.999994
47 | ],
48 | "type" : "VEC3"
49 | },
50 | {
51 | "bufferView" : 3,
52 | "byteOffset" : 0,
53 | "componentType" : 5126,
54 | "count" : 11808,
55 | "max" : [
56 | 0.998570,
57 | 0.999996,
58 | 0.999487,
59 | 1.000000
60 | ],
61 | "min" : [
62 | -0.999233,
63 | -0.999453,
64 | -0.999812,
65 | 1.000000
66 | ],
67 | "type" : "VEC4"
68 | },
69 | {
70 | "bufferView" : 4,
71 | "byteOffset" : 0,
72 | "componentType" : 5126,
73 | "count" : 11808,
74 | "max" : [
75 | 0.999884,
76 | 0.884359
77 | ],
78 | "min" : [
79 | 0.000116,
80 | 0.000116
81 | ],
82 | "type" : "VEC2"
83 | }
84 | ],
85 | "asset" : {
86 | "generator" : "VKTS glTF 2.0 exporter",
87 | "version" : "2.0"
88 | },
89 | "bufferViews" : [
90 | {
91 | "buffer" : 0,
92 | "byteLength" : 23616,
93 | "byteOffset" : 0,
94 | "target" : 34963
95 | },
96 | {
97 | "buffer" : 0,
98 | "byteLength" : 141696,
99 | "byteOffset" : 23616,
100 | "target" : 34962
101 | },
102 | {
103 | "buffer" : 0,
104 | "byteLength" : 141696,
105 | "byteOffset" : 165312,
106 | "target" : 34962
107 | },
108 | {
109 | "buffer" : 0,
110 | "byteLength" : 188928,
111 | "byteOffset" : 307008,
112 | "target" : 34962
113 | },
114 | {
115 | "buffer" : 0,
116 | "byteLength" : 94464,
117 | "byteOffset" : 495936,
118 | "target" : 34962
119 | }
120 | ],
121 | "buffers" : [
122 | {
123 | "byteLength" : 590400,
124 | "uri" : "suzanne.bin"
125 | }
126 | ],
127 | "images" : [
128 | {
129 | "uri" : "baseColor.png"
130 | },
131 | {
132 | "uri" : "roughness.png"
133 | }
134 | ],
135 | "materials" : [
136 | {
137 | "name" : "Suzanne",
138 | "pbrMetallicRoughness" : {
139 | "baseColorTexture" : {
140 | "index" : 0
141 | },
142 | "metallicRoughnessTexture" : {
143 | "index" : 1
144 | }
145 | }
146 | }
147 | ],
148 | "meshes" : [
149 | {
150 | "name" : "Suzanne",
151 | "primitives" : [
152 | {
153 | "attributes" : {
154 | "NORMAL" : 2,
155 | "POSITION" : 1,
156 | "TANGENT" : 3,
157 | "TEXCOORD_0" : 4
158 | },
159 | "indices" : 0,
160 | "material" : 0,
161 | "mode" : 4
162 | }
163 | ]
164 | }
165 | ],
166 | "nodes" : [
167 | {
168 | "mesh" : 0,
169 | "name" : "Suzanne"
170 | }
171 | ],
172 | "samplers" : [
173 | {}
174 | ],
175 | "scene" : 0,
176 | "scenes" : [
177 | {
178 | "nodes" : [
179 | 0
180 | ]
181 | }
182 | ],
183 | "textures" : [
184 | {
185 | "sampler" : 0,
186 | "source" : 0
187 | },
188 | {
189 | "sampler" : 0,
190 | "source" : 1
191 | }
192 | ]
193 | }
194 |
--------------------------------------------------------------------------------
/example/static/models/waterbottle/WaterBottle.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/larsjarlvik/webgl-gltf/46724143e451f4781eb2949ff5e50453d4c70e50/example/static/models/waterbottle/WaterBottle.bin
--------------------------------------------------------------------------------
/example/static/models/waterbottle/base-color.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/larsjarlvik/webgl-gltf/46724143e451f4781eb2949ff5e50453d4c70e50/example/static/models/waterbottle/base-color.png
--------------------------------------------------------------------------------
/example/static/models/waterbottle/emissive.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/larsjarlvik/webgl-gltf/46724143e451f4781eb2949ff5e50453d4c70e50/example/static/models/waterbottle/emissive.png
--------------------------------------------------------------------------------
/example/static/models/waterbottle/metallic-roughness.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/larsjarlvik/webgl-gltf/46724143e451f4781eb2949ff5e50453d4c70e50/example/static/models/waterbottle/metallic-roughness.png
--------------------------------------------------------------------------------
/example/static/models/waterbottle/normal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/larsjarlvik/webgl-gltf/46724143e451f4781eb2949ff5e50453d4c70e50/example/static/models/waterbottle/normal.png
--------------------------------------------------------------------------------
/example/static/models/waterbottle/occlusion.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/larsjarlvik/webgl-gltf/46724143e451f4781eb2949ff5e50453d4c70e50/example/static/models/waterbottle/occlusion.png
--------------------------------------------------------------------------------
/example/static/models/waterbottle/waterbottle.gltf:
--------------------------------------------------------------------------------
1 | {
2 | "accessors": [
3 | {
4 | "bufferView": 0,
5 | "componentType": 5126,
6 | "count": 2549,
7 | "type": "VEC2"
8 | },
9 | {
10 | "bufferView": 1,
11 | "componentType": 5126,
12 | "count": 2549,
13 | "type": "VEC3"
14 | },
15 | {
16 | "bufferView": 2,
17 | "componentType": 5126,
18 | "count": 2549,
19 | "type": "VEC4"
20 | },
21 | {
22 | "bufferView": 3,
23 | "componentType": 5126,
24 | "count": 2549,
25 | "type": "VEC3",
26 | "max": [
27 | 0.05445001,
28 | 0.130220339,
29 | 0.0544500239
30 | ],
31 | "min": [
32 | -0.05445001,
33 | -0.130220339,
34 | -0.0544500239
35 | ]
36 | },
37 | {
38 | "bufferView": 4,
39 | "componentType": 5123,
40 | "count": 13530,
41 | "type": "SCALAR"
42 | }
43 | ],
44 | "asset": {
45 | "generator": "glTF Tools for Unity",
46 | "version": "2.0"
47 | },
48 | "bufferViews": [
49 | {
50 | "buffer": 0,
51 | "byteLength": 20392
52 | },
53 | {
54 | "buffer": 0,
55 | "byteOffset": 20392,
56 | "byteLength": 30588
57 | },
58 | {
59 | "buffer": 0,
60 | "byteOffset": 50980,
61 | "byteLength": 40784
62 | },
63 | {
64 | "buffer": 0,
65 | "byteOffset": 91764,
66 | "byteLength": 30588
67 | },
68 | {
69 | "buffer": 0,
70 | "byteOffset": 122352,
71 | "byteLength": 27060
72 | }
73 | ],
74 | "buffers": [
75 | {
76 | "uri": "WaterBottle.bin",
77 | "byteLength": 149412
78 | }
79 | ],
80 | "extensionsUsed": [
81 | "KHR_materials_pbrSpecularGlossiness"
82 | ],
83 | "images": [
84 | {
85 | "uri": "base-color.png"
86 | },
87 | {
88 | "uri": "metallic-roughness.png"
89 | },
90 | {
91 | "uri": "normal.png"
92 | },
93 | {
94 | "uri": "emissive.png"
95 | },
96 | {
97 | "uri": "occlusion.png"
98 | },
99 | {
100 | "uri": "WaterBottle_diffuse.png"
101 | },
102 | {
103 | "uri": "WaterBottle_specularGlossiness.png"
104 | }
105 | ],
106 | "meshes": [
107 | {
108 | "primitives": [
109 | {
110 | "attributes": {
111 | "TEXCOORD_0": 0,
112 | "NORMAL": 1,
113 | "TANGENT": 2,
114 | "POSITION": 3
115 | },
116 | "indices": 4,
117 | "material": 0
118 | }
119 | ],
120 | "name": "WaterBottle"
121 | }
122 | ],
123 | "materials": [
124 | {
125 | "pbrMetallicRoughness": {
126 | "baseColorTexture": {
127 | "index": 0
128 | },
129 | "metallicRoughnessTexture": {
130 | "index": 1
131 | }
132 | },
133 | "normalTexture": {
134 | "index": 2
135 | },
136 | "occlusionTexture": {
137 | "index": 4
138 | },
139 | "emissiveFactor": [
140 | 1.0,
141 | 1.0,
142 | 1.0
143 | ],
144 | "emissiveTexture": {
145 | "index": 3
146 | },
147 | "name": "BottleMat",
148 | "extensions": {
149 | "KHR_materials_pbrSpecularGlossiness": {
150 | "diffuseTexture": {
151 | "index": 5
152 | },
153 | "specularGlossinessTexture": {
154 | "index": 6
155 | }
156 | }
157 | }
158 | }
159 | ],
160 | "nodes": [
161 | {
162 | "mesh": 0,
163 | "name": "WaterBottle"
164 | }
165 | ],
166 | "scene": 0,
167 | "scenes": [
168 | {
169 | "nodes": [
170 | 0
171 | ]
172 | }
173 | ],
174 | "textures": [
175 | {
176 | "source": 0
177 | },
178 | {
179 | "source": 1
180 | },
181 | {
182 | "source": 2
183 | },
184 | {
185 | "source": 3
186 | },
187 | {
188 | "source": 4
189 | },
190 | {
191 | "source": 5
192 | },
193 | {
194 | "source": 6
195 | }
196 | ]
197 | }
--------------------------------------------------------------------------------
/example/static/shaders/default.frag:
--------------------------------------------------------------------------------
1 | #version 100
2 |
3 | precision highp float;
4 |
5 | #define LIGHT_INTENSITY 1.0
6 | #define LIGHT_DIRECTION vec3(-0.7, -0.7, 1.0)
7 | #define LIGHT_COLOR vec3(1.0)
8 | #define M_PI 3.141592653589793
9 |
10 | uniform sampler2D uBaseColorTexture;
11 | uniform int uHasBaseColorTexture;
12 | uniform vec4 uBaseColorFactor;
13 |
14 | uniform sampler2D uMetallicRoughnessTexture;
15 | uniform int uHasMetallicRoughnessTexture;
16 | uniform float uMetallicFactor;
17 | uniform float uRoughnessFactor;
18 |
19 | uniform sampler2D uEmissiveTexture;
20 | uniform int uHasEmissiveTexture;
21 | uniform vec3 uEmissiveFactor;
22 |
23 | uniform sampler2D uNormalTexture;
24 | uniform int uHasNormalTexture;
25 |
26 | uniform sampler2D uOcclusionTexture;
27 | uniform int uHasOcclusionTexture;
28 |
29 | uniform sampler2D uBrdfLut;
30 | uniform samplerCube uEnvironmentDiffuse;
31 | uniform samplerCube uEnvironmentSpecular;
32 |
33 | uniform vec3 uCameraPosition;
34 |
35 | varying vec2 texCoord;
36 | varying vec3 normal;
37 | varying vec3 position;
38 | varying mat3 tangent;
39 |
40 | struct MaterialInfo {
41 | vec3 reflectance0;
42 | float alphaRoughness;
43 | vec3 diffuseColor;
44 | vec3 specularColor;
45 | vec3 reflectance90;
46 | float perceptualRoughness;
47 | };
48 |
49 |
50 | vec3 linearToSrgb(vec3 color) {
51 | return pow(color, vec3(1.0 / 2.2));
52 | }
53 |
54 | vec4 srgbToLinear(vec4 srgbIn) {
55 | return vec4(pow(srgbIn.xyz, vec3(2.2)), srgbIn.w);
56 | }
57 |
58 | vec3 specularReflection(MaterialInfo materialInfo, float VdotH) {
59 | return materialInfo.reflectance0 + (materialInfo.reflectance90 - materialInfo.reflectance0) * pow(clamp(1.0 - VdotH, 0.0, 1.0), 5.0);
60 | }
61 |
62 | float microfacetDistribution(MaterialInfo materialInfo, float NdotH) {
63 | float alphaRoughnessSq = materialInfo.alphaRoughness * materialInfo.alphaRoughness;
64 | float f = (NdotH * alphaRoughnessSq - NdotH) * NdotH + 1.0;
65 | return alphaRoughnessSq / (M_PI * f * f);
66 | }
67 |
68 | vec3 calculateDirectionalLight(MaterialInfo materialInfo, vec3 normal, vec3 view) {
69 | vec3 pointToLight = -LIGHT_DIRECTION;
70 |
71 | vec3 n = normalize(normal); // Outward direction of surface point
72 | vec3 v = normalize(view); // Direction from surface point to view
73 | vec3 l = normalize(pointToLight); // Direction from surface point to light
74 | vec3 h = normalize(l + v); // Direction of the vector between l and v
75 |
76 | float NdotL = clamp(dot(n, l), 0.0, 1.0);
77 | float NdotH = clamp(dot(n, h), 0.0, 1.0);
78 | float VdotH = clamp(dot(v, h), 0.0, 1.0);
79 | float NdotV = clamp(dot(n, v), 0.0, 1.0);
80 |
81 | if (NdotL > 0.0 || NdotV > 0.0) {
82 | vec3 F = specularReflection(materialInfo, VdotH);
83 | float D = microfacetDistribution(materialInfo, NdotH);
84 | vec3 diffuseContrib = (1.0 - F) * (materialInfo.diffuseColor / M_PI);
85 | vec3 specContrib = F * D;
86 |
87 | return LIGHT_INTENSITY * LIGHT_COLOR * NdotL * (diffuseContrib + specContrib);
88 | }
89 |
90 | return vec3(0.0);
91 | }
92 |
93 | vec3 getIBLContribution(MaterialInfo materialInfo, vec3 n, vec3 v) {
94 | float NdotV = clamp(dot(n, v), 0.0, 1.0);
95 | vec2 brdfSamplePoint = clamp(vec2(NdotV, materialInfo.perceptualRoughness), vec2(0.0, 0.0), vec2(1.0, 1.0));
96 |
97 | vec2 brdf = texture2D(uBrdfLut, brdfSamplePoint).rg;
98 | vec4 diffuseSample = vec4(0.1, 0.1, 0.1, 1.0);
99 | vec4 specularSample = vec4(0.3);
100 |
101 | vec3 diffuseLight = srgbToLinear(textureCube(uEnvironmentDiffuse, n)).rgb * 0.1;
102 | vec3 specularLight = srgbToLinear(textureCube(uEnvironmentSpecular, n)).rgb * 0.2;
103 |
104 | vec3 diffuse = diffuseLight * materialInfo.diffuseColor;
105 | vec3 specular = specularLight * (materialInfo.specularColor * brdf.x + brdf.y);
106 |
107 | return diffuse + specular;
108 | }
109 |
110 | vec4 getBaseColor() {
111 | if (uHasBaseColorTexture == 1) {
112 | return srgbToLinear(texture2D(uBaseColorTexture, texCoord)) * uBaseColorFactor;
113 | }
114 | return uBaseColorFactor;
115 | }
116 |
117 | vec2 getRoughnessMetallic() {
118 | if (uHasMetallicRoughnessTexture == 1) {
119 | return texture2D(uMetallicRoughnessTexture, texCoord).gb;
120 | }
121 | return vec2(1.0, 1.0);
122 | }
123 |
124 | vec4 getEmissive() {
125 | if (uHasEmissiveTexture == 1) {
126 | return texture2D(uEmissiveTexture, texCoord) * vec4(uEmissiveFactor, 1.0);
127 | }
128 | return vec4(0.0);
129 | }
130 |
131 | float getOcclusion() {
132 | if (uHasOcclusionTexture == 1) {
133 | return texture2D(uOcclusionTexture, texCoord).r;
134 | }
135 | return 1.0;
136 | }
137 |
138 | MaterialInfo getMaterialInfo() {
139 | vec3 f0 = vec3(0.04);
140 |
141 | vec2 mrSample = getRoughnessMetallic();
142 | float perceptualRoughness = mrSample.r * uRoughnessFactor;
143 | float metallic = mrSample.g * uMetallicFactor;
144 |
145 | vec4 baseColor = getBaseColor();
146 | vec3 diffuseColor = baseColor.rgb * (vec3(1.0) - f0) * (1.0 - metallic);
147 | vec3 specularColor = mix(f0, baseColor.rgb, metallic);
148 |
149 | float reflectance = max(max(specularColor.r, specularColor.g), specularColor.b);
150 | vec3 reflectance0 = specularColor.rgb;
151 | vec3 reflectance90 = vec3(clamp(reflectance * 50.0, 0.0, 1.0));
152 | float alphaRoughness = perceptualRoughness * perceptualRoughness;
153 |
154 | return MaterialInfo(
155 | reflectance0,
156 | alphaRoughness,
157 | diffuseColor,
158 | specularColor,
159 | reflectance90,
160 | perceptualRoughness
161 | );
162 | }
163 |
164 | void main() {
165 | MaterialInfo materialInfo = getMaterialInfo();
166 |
167 | vec3 n = normal;
168 | if (uHasNormalTexture == 1) {
169 | n = texture2D(uNormalTexture, texCoord).rgb;
170 | n = normalize(tangent * (2.0 * n - 1.0));
171 | }
172 |
173 | vec3 view = normalize(uCameraPosition - position);
174 | vec3 color = calculateDirectionalLight(materialInfo, n, view);
175 |
176 | color += getIBLContribution(materialInfo, n, view);
177 | color += getEmissive().rgb;
178 | color = clamp(color, 0.0, 1.0);
179 | color = mix(color, color * getOcclusion(), 1.0);
180 |
181 | gl_FragColor = vec4(linearToSrgb(color), 1.0);
182 | }
183 |
--------------------------------------------------------------------------------
/example/static/shaders/default.vert:
--------------------------------------------------------------------------------
1 | #version 100
2 |
3 | attribute vec3 vPosition;
4 | attribute vec3 vNormal;
5 | attribute vec4 vTangent;
6 | attribute vec2 vTexCoord;
7 | attribute vec4 vJoints;
8 | attribute vec4 vWeights;
9 |
10 | uniform mat4 uProjectionMatrix;
11 | uniform mat4 uViewMatrix;
12 | uniform mat4 uModelMatrix;
13 | uniform mat4 uJointTransform[25];
14 |
15 | uniform int uIsAnimated;
16 |
17 | varying vec2 texCoord;
18 | varying vec3 normal;
19 | varying vec3 position;
20 | varying mat3 tangent;
21 |
22 | void main() {
23 | mat4 skinMatrix = mat4(1.0);
24 | if (uIsAnimated == 1) {
25 | skinMatrix = vWeights.x * uJointTransform[int(vJoints.x)] +
26 | vWeights.y * uJointTransform[int(vJoints.y)] +
27 | vWeights.z * uJointTransform[int(vJoints.z)] +
28 | vWeights.w * uJointTransform[int(vJoints.w)];
29 | }
30 |
31 | vec3 n = normalize(vNormal);
32 | vec4 t = normalize(vTangent);
33 |
34 | mat4 normalMatrix = skinMatrix;
35 | vec3 normalW = normalize(vec3(normalMatrix * vec4(n.xyz, 0.0)));
36 | vec3 tangentW = normalize(vec3(uModelMatrix * vec4(t.xyz, 0.0)));
37 | vec3 bitangentW = cross(normalW, tangentW) * t.w;
38 |
39 | tangent = mat3(tangentW, bitangentW, normalW);
40 | normal = normalize(mat3(normalMatrix) * vec3(skinMatrix * vec4(vNormal, 1.0)));
41 | texCoord = vTexCoord;
42 | position = vec3(mat3(uModelMatrix) * vPosition);
43 |
44 | gl_Position = uProjectionMatrix * uViewMatrix * uModelMatrix * skinMatrix * vec4(vPosition, 1.0);
45 | }
46 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "webgl-gltf",
3 | "version": "0.3.3",
4 | "description": "GLTF 2.0 loader for WebGL",
5 | "keywords": [
6 | "WebGL",
7 | "glTF",
8 | "3D",
9 | "Mesh",
10 | "loader"
11 | ],
12 | "homepage": "https://github.com/larsjarlvik/webgl-gltf",
13 | "repository": {
14 | "type": "git",
15 | "url": "https://github.com/larsjarlvik/webgl-gltf.git"
16 | },
17 | "bugs": {
18 | "url": "https://github.com/larsjarlvik/webgl-gltf/issues"
19 | },
20 | "main": "dist/index.js",
21 | "types": "dist/index.d.ts",
22 | "files": [
23 | "dist"
24 | ],
25 | "scripts": {
26 | "start": "webpack-dev-server --mode development",
27 | "build": "webpack --mode production",
28 | "prepublish": "rimraf dist/ && node node_modules/typescript/bin/tsc --project ./src && cp -r src/webgl-gltf/types dist/"
29 | },
30 | "author": "Lars Jarlvik",
31 | "license": "ISC",
32 | "devDependencies": {
33 | "@typescript-eslint/eslint-plugin": "^4.32.0",
34 | "@typescript-eslint/parser": "^4.32.0",
35 | "clean-webpack-plugin": "^4.0.0",
36 | "eslint": "^7.32.0",
37 | "eslint-loader": "^3.0.3",
38 | "html-webpack-plugin": "^5.3.2",
39 | "rimraf": "^3.0.2",
40 | "ts-loader": "^9.2.6",
41 | "typescript": "^4.4.3",
42 | "webpack": "^5.55.0",
43 | "webpack-cli": "^4.8.0",
44 | "webpack-dev-server": "^4.3.0"
45 | },
46 | "dependencies": {
47 | "gl-matrix": "^3.3.0"
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowJs": false,
4 | "target": "es6",
5 | "module": "esnext",
6 | "moduleResolution": "node",
7 | "outDir": "../dist",
8 | "baseUrl": "webgl-gltf",
9 | "allowSyntheticDefaultImports": true,
10 | "noImplicitAny": false,
11 | "strictNullChecks": true,
12 | "noImplicitThis": true,
13 | "alwaysStrict": true,
14 | "noUnusedLocals": true,
15 | "noUnusedParameters": true,
16 | "forceConsistentCasingInFileNames": true,
17 | "esModuleInterop": true,
18 | "sourceMap": true,
19 | "declaration": true,
20 | }
21 | }
--------------------------------------------------------------------------------
/src/webgl-gltf/animation.ts:
--------------------------------------------------------------------------------
1 | interface ActiveAnimation {
2 | key: string;
3 | elapsed: number;
4 | }
5 |
6 | interface Animations {
7 | [model: string]: ActiveAnimation[][];
8 | }
9 |
10 | const activeAnimations: Animations = {};
11 |
12 | const getAnimationFromLast = (track: string, key: string, offset = 0) => {
13 | if (activeAnimations[track] === undefined || activeAnimations[track][key] === undefined || activeAnimations[track][key].length - offset - 1 < 0) {
14 | return null;
15 | }
16 |
17 | return activeAnimations[track][key][activeAnimations[track][key].length - offset - 1];
18 | }
19 |
20 | /**
21 | * Sets the active animation
22 | * @param track Animation track
23 | * @param key Animation set key
24 | * @param model GLTF Model
25 | * @param animation Animation key
26 | */
27 | const pushAnimation = (track: string, key: string, model: string, animation: string) => {
28 | const k = `${key}_${model}`;
29 | if (!activeAnimations[track]) activeAnimations[track] = [];
30 | if (!activeAnimations[track][k]) activeAnimations[track][k] = [];
31 | if (getAnimationFromLast(track, k)?.key === animation) return;
32 |
33 | activeAnimations[track][k].push({
34 | key: animation,
35 | elapsed: 0,
36 | });
37 |
38 | activeAnimations[track][k].slice(activeAnimations[track][k].length - 2);
39 | };
40 |
41 | /**
42 | * Gets the current and previous animation
43 | * @param key Animation set key
44 | * @param model GLTF Model
45 | */
46 | const getActiveAnimations = (key: string, model: string) => {
47 | const k = `${key}_${model}`;
48 | const aa = {};
49 |
50 | if (Object.keys(activeAnimations).length === 0) return null;
51 |
52 | Object.keys(activeAnimations).forEach(c => {
53 | if (!activeAnimations[c][k]) return;
54 | aa[c] = activeAnimations[c][k].slice(activeAnimations[c][k].length - 2);
55 | });
56 |
57 | return aa;
58 | };
59 |
60 | /**
61 | * Advances the animation
62 | * @param elapsed Time elasped since last update
63 | * @param key Animation set key
64 | */
65 | const advanceAnimation = (elapsed: number, key?: string) => {
66 | Object.keys(activeAnimations).forEach(c => {
67 | Object.keys(activeAnimations[c]).forEach(m => {
68 | if (key && m.indexOf(key) !== 0) return;
69 |
70 | const current = getAnimationFromLast(c, m);
71 | const previous = getAnimationFromLast(c, m, 1);
72 |
73 | if (current) current.elapsed += elapsed;
74 | if (previous) previous.elapsed += elapsed;
75 | });
76 | });
77 | };
78 |
79 | export {
80 | pushAnimation,
81 | getActiveAnimations,
82 | advanceAnimation,
83 | ActiveAnimation,
84 | }
85 |
--------------------------------------------------------------------------------
/src/webgl-gltf/animator.ts:
--------------------------------------------------------------------------------
1 |
2 | import { mat4, vec3, quat } from 'gl-matrix';
3 | import { KeyFrame, Model, Skin, Transform } from './types/model';
4 | import { ActiveAnimation } from './animation';
5 |
6 | const getPreviousAndNextKeyFrame = (keyFrames: KeyFrame[], animationTime: number) => {
7 | let next = keyFrames[0];
8 | let previous = keyFrames[0];
9 |
10 | for (let i = 1; i < keyFrames.length; i ++) {
11 | next = keyFrames[i];
12 | if (next.time > animationTime) break;
13 |
14 | previous = keyFrames[i];
15 | }
16 |
17 | return { previous, next };
18 | };
19 |
20 | const getTransform = (keyFrames: KeyFrame[], duration: number) => {
21 | if (keyFrames.length === 1) {
22 | switch(keyFrames[0].type) {
23 | case 'translation':
24 | case 'scale':
25 | return keyFrames[0].transform as vec3;
26 | case 'rotation':
27 | return keyFrames[0].transform as quat;
28 | }
29 | }
30 |
31 | const animationTime = duration / 1000.0 % keyFrames[keyFrames.length - 1].time;
32 | const frames = getPreviousAndNextKeyFrame(keyFrames, animationTime);
33 | const progression = (animationTime - frames.previous.time) / (frames.next.time - frames.previous.time);
34 |
35 | switch(frames.previous.type) {
36 | case 'translation':
37 | case 'scale': {
38 | const result = vec3.create();
39 | vec3.lerp(result, frames.previous.transform as vec3, frames.next.transform as vec3, progression);
40 | return result;
41 | }
42 | case 'rotation': {
43 | const result = quat.create();
44 | quat.slerp(result, frames.previous.transform as quat, frames.next.transform as quat, progression);
45 | return result;
46 | }
47 | }
48 | };
49 |
50 | interface TransformMatrices {
51 | [key: number]: mat4;
52 | }
53 |
54 | const get = (c: Transform, elapsed: number) => {
55 | const t = c && c.translation.length > 0 ? getTransform(c.translation, elapsed) as vec3 : vec3.create();
56 | const r = c && c.rotation.length > 0 ? getTransform(c.rotation, elapsed) as quat : quat.create();
57 | const s = c && c.scale.length > 0 ? getTransform(c.scale, elapsed) as vec3 : vec3.fromValues(1, 1, 1);
58 | return { t, r, s };
59 | };
60 |
61 | const applyTransform = (model: Model, appliedTransforms: mat4[], transforms: TransformMatrices, matrix: mat4, skin: Skin, nodeIndex: number, inverse: boolean) => {
62 | const node = model.nodes[nodeIndex];
63 | const transformIndex = skin.joints.indexOf(node.id);
64 |
65 | if (transforms[node.id] !== undefined) {
66 | mat4.multiply(matrix, matrix, transforms[node.id]);
67 | }
68 |
69 | if (inverse) {
70 | const ibt = skin.inverseBindTransforms[transformIndex];
71 |
72 | if (ibt) {
73 | appliedTransforms[transformIndex] = mat4.create();
74 | mat4.multiply(appliedTransforms[transformIndex], matrix, ibt);
75 | }
76 | } else {
77 | appliedTransforms[transformIndex] = matrix;
78 | }
79 |
80 | node.children.forEach(childNode => {
81 | applyTransform(model, appliedTransforms, transforms, mat4.clone(matrix), skin, childNode, inverse);
82 | });
83 | };
84 |
85 | /**
86 | * Blends two animations and returns their transform matrices
87 | * @param model GLTF Model
88 | * @param activeAnimations Currently running animations
89 | * @param blendTime Length of animation blend in milliseconds
90 | */
91 | const getAnimationTransforms = (model: Model, activeAnimations: Record, blendTime = 0) => {
92 | const transforms: { [key: number]: mat4 } = {};
93 |
94 | Object.keys(activeAnimations).forEach(track => {
95 | activeAnimations[track].forEach(rootAnimation => {
96 | const blend = -((rootAnimation.elapsed - blendTime) / blendTime);
97 |
98 | Object.keys(model.animations[rootAnimation.key]).forEach(c => {
99 | const transform = get(model.animations[rootAnimation.key][c], rootAnimation.elapsed);
100 |
101 | activeAnimations[track].forEach(ac => {
102 | if (rootAnimation.key == ac.key || blend <= 0) return;
103 |
104 | const cTransform = get(model.animations[ac.key][c], ac.elapsed);
105 | vec3.lerp(transform.t, transform.t, cTransform.t, blend);
106 | quat.slerp(transform.r, transform.r, cTransform.r, blend);
107 | vec3.lerp(transform.s, transform.s, cTransform.s, blend);
108 | });
109 |
110 | const localTransform = mat4.create();
111 | const rotTransform = mat4.create();
112 | mat4.fromQuat(rotTransform, transform.r as quat);
113 |
114 | mat4.translate(localTransform, localTransform, transform.t as vec3);
115 | mat4.multiply(localTransform, localTransform, rotTransform);
116 | mat4.scale(localTransform, localTransform, transform.s as vec3);
117 |
118 | transforms[c] = localTransform;
119 | });
120 | });
121 | });
122 |
123 | return transforms;
124 | };
125 |
126 | /**
127 | * Applies transforms to skin
128 | * @param model GLTF Model
129 | * @param transforms Raw transforms
130 | * @param blendTime Use inverse bind transform
131 | */
132 | const applyToSkin = (model: Model, transforms: { [key: number]: mat4 }, inverse = true) => {
133 | const appliedTransforms: mat4[] = [];
134 |
135 | model.skins.forEach(skin => {
136 | const root = model.rootNode;
137 | applyTransform(model, appliedTransforms, transforms, mat4.create(), skin, root, inverse);
138 | });
139 |
140 | return appliedTransforms;
141 | };
142 |
143 | export {
144 | getAnimationTransforms,
145 | applyToSkin,
146 | };
147 |
--------------------------------------------------------------------------------
/src/webgl-gltf/gltf.ts:
--------------------------------------------------------------------------------
1 | import * as gltf from './types/gltf';
2 | import { mat4, quat, vec3, vec4 } from 'gl-matrix';
3 | import { createMat4FromArray, applyRotationFromQuat } from './mat';
4 | import { Channel, Node, Mesh, Model, KeyFrame, Skin, Material, GLBuffer, Animation } from './types/model';
5 |
6 | type GLContext = WebGLRenderingContext | WebGL2RenderingContext;
7 |
8 | const accessorSizes = {
9 | 'SCALAR': 1,
10 | 'VEC2': 2,
11 | 'VEC3': 3,
12 | 'VEC4': 4,
13 | 'MAT2': 4,
14 | 'MAT3': 9,
15 | 'MAT4': 16
16 | };
17 |
18 | export interface Buffer {
19 | data: Float32Array | Int16Array;
20 | size: number;
21 | type: string;
22 | componentType: BufferType;
23 | glBuffer: WebGLBuffer;
24 | }
25 |
26 | export enum BufferType {
27 | Float = 5126,
28 | Short = 5123,
29 | }
30 |
31 | const resolveEmbeddedBuffer = (uri: string): string => {
32 | const content = uri.split(',')[1];
33 | const binaryData = atob(content);
34 | const arrayBuffer = new ArrayBuffer(binaryData.length);
35 | const uint8Array = new Uint8Array(arrayBuffer);
36 |
37 | for (let i = 0; i < binaryData.length; i++) {
38 | uint8Array[i] = binaryData.charCodeAt(i);
39 | }
40 |
41 | const blob = new Blob([uint8Array], { type: 'application/octet-stream' }); // Crea un Blob
42 | return URL.createObjectURL(blob);
43 | }
44 |
45 | const EMBEDDED_DATA_REGEXP = /(.*)data:(.*?)(;base64)?,(.*)$/;
46 |
47 | const getBuffer = async (path: string, buffer: string) => {
48 | const dir = path.split('/').slice(0, -1).join('/');
49 | const finalPath = EMBEDDED_DATA_REGEXP.test(buffer) ? resolveEmbeddedBuffer(buffer) : `${dir}/${buffer}`;
50 | const response = await fetch(finalPath);
51 | return await response.arrayBuffer();
52 | };
53 |
54 | const getTexture = async (gl: GLContext, uri: string) => {
55 | return new Promise(resolve => {
56 | const img = new Image();
57 | img.onload = () => {
58 | const texture = gl.createTexture();
59 | gl.bindTexture(gl.TEXTURE_2D, texture);
60 | gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img);
61 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);
62 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
63 |
64 | const ext = gl.getExtension('EXT_texture_filter_anisotropic');
65 | if (ext) {
66 | const max = gl.getParameter(ext.MAX_TEXTURE_MAX_ANISOTROPY_EXT);
67 | gl.texParameterf(gl.TEXTURE_2D, ext.TEXTURE_MAX_ANISOTROPY_EXT, max);
68 | }
69 |
70 | gl.generateMipmap(gl.TEXTURE_2D);
71 | resolve(texture!);
72 | }
73 | img.src = EMBEDDED_DATA_REGEXP.test(uri) ? resolveEmbeddedBuffer(uri) : uri;
74 | img.crossOrigin = 'undefined';
75 | });
76 | };
77 |
78 | const readBufferFromFile = (gltf: gltf.GlTf, buffers: ArrayBuffer[], accessor: gltf.Accessor) => {
79 | const bufferView = gltf.bufferViews![accessor.bufferView as number];
80 | const size = accessorSizes[accessor.type];
81 | const componentType = accessor.componentType as BufferType;
82 | const type = accessor.type;
83 |
84 | const data = componentType == BufferType.Float
85 | ? new Float32Array(buffers[bufferView.buffer], (accessor.byteOffset || 0) + (bufferView.byteOffset || 0), accessor.count * size)
86 | : new Int16Array(buffers[bufferView.buffer], (accessor.byteOffset || 0) + (bufferView.byteOffset || 0), accessor.count * size);
87 |
88 | return {
89 | size,
90 | data,
91 | type,
92 | componentType,
93 | } as Buffer;
94 | };
95 |
96 | const getAccessor = (gltf: gltf.GlTf, mesh: gltf.Mesh, attributeName: string) => {
97 | const attribute = mesh.primitives[0].attributes[attributeName];
98 | return gltf.accessors![attribute];
99 | };
100 |
101 | const getBufferFromName = (gl: GLContext, gltf: gltf.GlTf, buffers: ArrayBuffer[], mesh: gltf.Mesh, name: string) => {
102 | if (mesh.primitives[0].attributes[name] === undefined) {
103 | return null;
104 | }
105 |
106 | const accessor = getAccessor(gltf, mesh, name);
107 | const bufferData = readBufferFromFile(gltf, buffers, accessor);
108 |
109 | const buffer = gl.createBuffer();
110 | gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
111 | gl.bufferData(gl.ARRAY_BUFFER, bufferData.data, gl.STATIC_DRAW);
112 |
113 | return {
114 | buffer,
115 | size: bufferData.size,
116 | type: bufferData.componentType,
117 | } as GLBuffer;
118 | };
119 |
120 | const loadNodes = (index: number, node: gltf.Node): Node => {
121 | const transform = mat4.create();
122 |
123 | if (node.translation !== undefined) mat4.translate(transform, transform, vec3.fromValues(node.translation[0], node.translation[1], node.translation[1]));
124 | if (node.rotation !== undefined) applyRotationFromQuat(transform, node.rotation);
125 | if (node.scale !== undefined) mat4.scale(transform, transform, vec3.fromValues(node.scale[0], node.scale[1], node.scale[1]));
126 | if (node.matrix !== undefined) createMat4FromArray(node.matrix);
127 |
128 | return {
129 | id: index,
130 | name: node.name,
131 | children: node.children || [],
132 | localBindTransform: transform,
133 | animatedTransform: mat4.create(),
134 | skin: node.skin,
135 | mesh: node.mesh
136 | } as Node;
137 | };
138 |
139 | const loadAnimation = (gltf: gltf.GlTf, animation: gltf.Animation, buffers: ArrayBuffer[]) => {
140 | const channels = animation.channels.map(c => {
141 | const sampler = animation.samplers[c.sampler];
142 | const time = readBufferFromFile(gltf, buffers, gltf.accessors![sampler.input]);
143 | const buffer = readBufferFromFile(gltf, buffers, gltf.accessors![sampler.output]);
144 |
145 | return {
146 | node: c.target.node,
147 | type: c.target.path,
148 | time,
149 | buffer,
150 | interpolation: sampler.interpolation ? sampler.interpolation : 'LINEAR',
151 | };
152 | });
153 |
154 | const c: Channel = {};
155 | channels.forEach((channel) => {
156 | if (c[channel.node!] === undefined) {
157 | c[channel.node!] = {
158 | translation: [],
159 | rotation: [],
160 | scale: [],
161 | };
162 | }
163 |
164 | for (let i = 0; i < channel.time.data.length; i ++) {
165 | const size = channel.interpolation === 'CUBICSPLINE' ? channel.buffer.size * 3 : channel.buffer.size;
166 | const offset = channel.interpolation === 'CUBICSPLINE' ? channel.buffer.size : 0;
167 |
168 | const transform = channel.type === 'rotation'
169 | ? quat.fromValues(
170 | channel.buffer.data[i * size + offset],
171 | channel.buffer.data[i * size + offset + 1],
172 | channel.buffer.data[i * size + offset + 2],
173 | channel.buffer.data[i * size + offset + 3]
174 | )
175 | : vec3.fromValues(
176 | channel.buffer.data[i * size + offset],
177 | channel.buffer.data[i * size + offset + 1],
178 | channel.buffer.data[i * size + offset + 2]
179 | );
180 |
181 | c[channel.node!][channel.type].push({
182 | time: channel.time.data[i],
183 | transform: transform,
184 | type: channel.type,
185 | } as KeyFrame)
186 | }
187 | });
188 |
189 | return c;
190 | };
191 |
192 | const loadMesh = (gl: GLContext, gltf: gltf.GlTf, mesh: gltf.Mesh, buffers: ArrayBuffer[]) => {
193 | let indices: WebGLBuffer | null = null;
194 | let elementCount = 0;
195 |
196 | if (mesh.primitives[0].indices !== undefined) {
197 | const indexAccessor = gltf.accessors![mesh.primitives[0].indices!];
198 | const indexBuffer = readBufferFromFile(gltf, buffers, indexAccessor);
199 |
200 | indices = gl.createBuffer();
201 | gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indices);
202 | gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indexBuffer.data, gl.STATIC_DRAW);
203 |
204 | elementCount = indexBuffer.data.length;
205 | } else {
206 | const accessor = getAccessor(gltf, mesh, 'POSITION');
207 | elementCount = accessor.count;
208 | }
209 |
210 | return {
211 | indices,
212 | elementCount,
213 | positions: getBufferFromName(gl, gltf, buffers, mesh, 'POSITION'),
214 | normals: getBufferFromName(gl, gltf, buffers, mesh, 'NORMAL'),
215 | tangents: getBufferFromName(gl, gltf, buffers, mesh, 'TANGENT'),
216 | texCoord: getBufferFromName(gl, gltf, buffers, mesh, 'TEXCOORD_0'),
217 | joints: getBufferFromName(gl, gltf, buffers, mesh, 'JOINTS_0'),
218 | weights: getBufferFromName(gl, gltf, buffers, mesh, 'WEIGHTS_0'),
219 | material: mesh.primitives[0].material,
220 | } as Mesh;
221 | };
222 |
223 | const loadMaterial = async (gl: GLContext, material: gltf.Material, path: string, images?: gltf.Image[]): Promise => {
224 | const dir = path.split('/').slice(0, -1).join('/');
225 |
226 | let baseColorTexture: WebGLTexture | null = null;
227 | let metallicRoughnessTexture: WebGLTexture | null = null;
228 | let emissiveTexture: WebGLTexture | null = null;
229 | let normalTexture: WebGLTexture | null = null;
230 | let occlusionTexture: WebGLTexture | null = null;
231 |
232 | let baseColorFactor = vec4.fromValues(1.0, 1.0, 1.0, 1.0);
233 | let roughnessFactor = 0.0;
234 | let metallicFactor = 1.0;
235 | let emissiveFactor = vec3.fromValues(1.0, 1.0, 1.0);
236 |
237 | const pbr = material.pbrMetallicRoughness;
238 | if (pbr) {
239 | if (pbr.baseColorTexture) {
240 | const uri = images![pbr.baseColorTexture.index].uri!;
241 | baseColorTexture = await getTexture(gl, `${dir}/${uri}`);
242 | }
243 | if (pbr.baseColorFactor) {
244 | baseColorFactor = vec4.fromValues(pbr.baseColorFactor[0], pbr.baseColorFactor[1], pbr.baseColorFactor[2], pbr.baseColorFactor[3]);
245 | }
246 |
247 | if (pbr.metallicRoughnessTexture) {
248 | const uri = images![pbr.metallicRoughnessTexture.index].uri!;
249 | metallicRoughnessTexture = await getTexture(gl, `${dir}/${uri}`);
250 | }
251 |
252 | metallicFactor = pbr.metallicFactor !== undefined ? pbr.metallicFactor : 1.0;
253 | roughnessFactor = pbr.roughnessFactor !== undefined ? pbr.roughnessFactor : 1.0;
254 | }
255 |
256 | if (material.emissiveTexture) {
257 | const uri = images![material.emissiveTexture.index].uri!;
258 | emissiveTexture = await getTexture(gl, `${dir}/${uri}`);
259 | }
260 |
261 | if (material.normalTexture) {
262 | const uri = images![material.normalTexture.index].uri!;
263 | normalTexture = await getTexture(gl, `${dir}/${uri}`);
264 | }
265 |
266 | if (material.occlusionTexture) {
267 | const uri = images![material.occlusionTexture.index].uri!;
268 | occlusionTexture = await getTexture(gl, `${dir}/${uri}`);
269 | }
270 |
271 | if (material.emissiveFactor) {
272 | emissiveFactor = vec3.fromValues(material.emissiveFactor[0], material.emissiveFactor[1], material.emissiveFactor[2])
273 | }
274 |
275 |
276 | return {
277 | baseColorTexture,
278 | baseColorFactor,
279 | metallicRoughnessTexture,
280 | metallicFactor,
281 | roughnessFactor,
282 | emissiveTexture,
283 | emissiveFactor,
284 | normalTexture,
285 | occlusionTexture,
286 | } as Material;
287 | };
288 |
289 | /**
290 | * Loads a GLTF model and its assets
291 | * @param gl Web GL context
292 | * @param uri URI to model
293 | */
294 | const loadModel = async (gl: GLContext, uri: string) => {
295 | const response = await fetch(uri);
296 | const gltf = await response.json() as gltf.GlTf;
297 |
298 | if (gltf.accessors === undefined || gltf.accessors.length === 0) {
299 | throw new Error('GLTF File is missing accessors')
300 | }
301 |
302 | const buffers = await Promise.all(
303 | gltf.buffers!.map(async (b) => await getBuffer(uri, b.uri!)
304 | ));
305 |
306 | const scene = gltf.scenes![gltf.scene || 0];
307 | const meshes = gltf.meshes!.map(m => loadMesh(gl, gltf, m, buffers));
308 | const materials = gltf.materials ? await Promise.all(gltf.materials.map(async (m) => await loadMaterial(gl, m, uri, gltf.images))) : [];
309 |
310 | const rootNode = scene.nodes![0];
311 | const nodes = gltf.nodes!.map((n, i) => loadNodes(i, n));
312 |
313 | const animations = {} as Animation;
314 | gltf.animations?.forEach(anim => animations[anim.name as string] = loadAnimation(gltf, anim, buffers));
315 |
316 | const skins = gltf.skins ? gltf.skins.map(x => {
317 | const bindTransforms = readBufferFromFile(gltf, buffers, gltf.accessors![x.inverseBindMatrices!]);
318 | const inverseBindTransforms = x.joints.map((_, i) => createMat4FromArray(bindTransforms.data.slice(i * 16, i * 16 + 16)));
319 |
320 | return {
321 | joints: x.joints,
322 | inverseBindTransforms,
323 | };
324 | }) : [] as Skin[];
325 |
326 | const name = uri.split('/').slice(-1)[0];
327 | return {
328 | name,
329 | meshes,
330 | nodes,
331 | rootNode,
332 | animations,
333 | skins,
334 | materials,
335 | } as Model;
336 | };
337 |
338 |
339 | /**
340 | * Deletes GL buffers and textures
341 | * @param gl Web GL context
342 | * @param model Model to dispose
343 | */
344 | const dispose = (gl: GLContext, model: Model) => {
345 | model.meshes.forEach(m => {
346 | gl.deleteBuffer(m.indices);
347 | if (m.joints) gl.deleteBuffer(m.joints.buffer);
348 | if (m.normals) gl.deleteBuffer(m.normals.buffer);
349 | if (m.positions) gl.deleteBuffer(m.positions.buffer);
350 | if (m.tangents) gl.deleteBuffer(m.tangents.buffer);
351 | if (m.texCoord) gl.deleteBuffer(m.texCoord.buffer);
352 | if (m.weights) gl.deleteBuffer(m.weights.buffer);
353 |
354 | m.indices = null
355 | m.joints = null;
356 | m.normals = null;
357 | m.tangents = null;
358 | m.texCoord = null;
359 | m.weights = null;
360 | });
361 |
362 | model.materials.forEach(m => {
363 | if (m.baseColorTexture) gl.deleteTexture(m.baseColorTexture);
364 | if (m.emissiveTexture) gl.deleteTexture(m.emissiveTexture);
365 | if (m.normalTexture) gl.deleteTexture(m.normalTexture);
366 | if (m.occlusionTexture) gl.deleteTexture(m.occlusionTexture);
367 | if (m.metallicRoughnessTexture) gl.deleteTexture(m.metallicRoughnessTexture);
368 |
369 | m.baseColorTexture = null;
370 | m.emissiveTexture = null;
371 | m.normalTexture = null;
372 | m.occlusionTexture = null;
373 | m.metallicRoughnessTexture = null;
374 | });
375 | };
376 |
377 | export {
378 | loadModel,
379 | dispose,
380 | };
381 |
--------------------------------------------------------------------------------
/src/webgl-gltf/index.ts:
--------------------------------------------------------------------------------
1 | export {
2 | loadModel,
3 | dispose,
4 | } from './gltf';
5 |
6 | export {
7 | getAnimationTransforms,
8 | applyToSkin,
9 | } from './animator';
10 |
11 | export {
12 | pushAnimation,
13 | getActiveAnimations,
14 | advanceAnimation,
15 | ActiveAnimation,
16 | } from './animation';
17 |
18 | export {
19 | Model,
20 | GLBuffer,
21 | Node,
22 | Skin,
23 | Animation,
24 | Channel,
25 | Transform,
26 | KeyFrame,
27 | Mesh,
28 | Material,
29 | } from './types/model'
30 |
--------------------------------------------------------------------------------
/src/webgl-gltf/mat.ts:
--------------------------------------------------------------------------------
1 | import { mat4, quat } from 'gl-matrix';
2 |
3 | export const createMat4FromArray = (array: Float32Array | Int16Array | number[]) => {
4 | return mat4.fromValues(
5 | array[0], array[1], array[2], array[3],
6 | array[4], array[5], array[6], array[7],
7 | array[8], array[9], array[10], array[11],
8 | array[12], array[13], array[14], array[15]
9 | );
10 | };
11 |
12 | export const applyRotationFromQuat = (transform: mat4, rotation: number[]) => {
13 | const rotationMatrix = mat4.create();
14 | mat4.fromQuat(rotationMatrix, quat.fromValues(rotation[0], rotation[1], rotation[2], rotation[3]));
15 | mat4.multiply(transform, rotationMatrix, transform);
16 | }
--------------------------------------------------------------------------------
/src/webgl-gltf/types/gltf.d.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/no-explicit-any */
2 | export type GlTfId = number;
3 | /**
4 | * Indices of those attributes that deviate from their initialization value.
5 | */
6 | export interface AccessorSparseIndices {
7 | /**
8 | * The index of the bufferView with sparse indices. Referenced bufferView can't have ARRAY_BUFFER or ELEMENT_ARRAY_BUFFER target.
9 | */
10 | 'bufferView': GlTfId;
11 | /**
12 | * The offset relative to the start of the bufferView in bytes. Must be aligned.
13 | */
14 | 'byteOffset'?: number;
15 | /**
16 | * The indices data type.
17 | */
18 | 'componentType': 5121 | 5123 | 5125 | number;
19 | 'extensions'?: any;
20 | 'extras'?: any;
21 | [k: string]: any;
22 | }
23 | /**
24 | * Array of size `accessor.sparse.count` times number of components storing the displaced accessor attributes pointed by `accessor.sparse.indices`.
25 | */
26 | export interface AccessorSparseValues {
27 | /**
28 | * The index of the bufferView with sparse values. Referenced bufferView can't have ARRAY_BUFFER or ELEMENT_ARRAY_BUFFER target.
29 | */
30 | 'bufferView': GlTfId;
31 | /**
32 | * The offset relative to the start of the bufferView in bytes. Must be aligned.
33 | */
34 | 'byteOffset'?: number;
35 | 'extensions'?: any;
36 | 'extras'?: any;
37 | [k: string]: any;
38 | }
39 | /**
40 | * Sparse storage of attributes that deviate from their initialization value.
41 | */
42 | export interface AccessorSparse {
43 | /**
44 | * Number of entries stored in the sparse array.
45 | */
46 | 'count': number;
47 | /**
48 | * Index array of size `count` that points to those accessor attributes that deviate from their initialization value. Indices must strictly increase.
49 | */
50 | 'indices': AccessorSparseIndices;
51 | /**
52 | * Array of size `count` times number of components, storing the displaced accessor attributes pointed by `indices`. Substituted values must have the same `componentType` and number of components as the base accessor.
53 | */
54 | 'values': AccessorSparseValues;
55 | 'extensions'?: any;
56 | 'extras'?: any;
57 | [k: string]: any;
58 | }
59 | /**
60 | * A typed view into a bufferView. A bufferView contains raw binary data. An accessor provides a typed view into a bufferView or a subset of a bufferView similar to how WebGL's `vertexAttribPointer()` defines an attribute in a buffer.
61 | */
62 | export interface Accessor {
63 | /**
64 | * The index of the bufferView.
65 | */
66 | 'bufferView'?: GlTfId;
67 | /**
68 | * The offset relative to the start of the bufferView in bytes.
69 | */
70 | 'byteOffset'?: number;
71 | /**
72 | * The datatype of components in the attribute.
73 | */
74 | 'componentType': 5120 | 5121 | 5122 | 5123 | 5125 | 5126 | number;
75 | /**
76 | * Specifies whether integer data values should be normalized.
77 | */
78 | 'normalized'?: boolean;
79 | /**
80 | * The number of attributes referenced by this accessor.
81 | */
82 | 'count': number;
83 | /**
84 | * Specifies if the attribute is a scalar, vector, or matrix.
85 | */
86 | 'type': 'SCALAR' | 'VEC2' | 'VEC3' | 'VEC4' | 'MAT2' | 'MAT3' | 'MAT4' | string;
87 | /**
88 | * Maximum value of each component in this attribute.
89 | */
90 | 'max'?: number[];
91 | /**
92 | * Minimum value of each component in this attribute.
93 | */
94 | 'min'?: number[];
95 | /**
96 | * Sparse storage of attributes that deviate from their initialization value.
97 | */
98 | 'sparse'?: AccessorSparse;
99 | 'name'?: any;
100 | 'extensions'?: any;
101 | 'extras'?: any;
102 | [k: string]: any;
103 | }
104 | /**
105 | * The index of the node and TRS property that an animation channel targets.
106 | */
107 | export interface AnimationChannelTarget {
108 | /**
109 | * The index of the node to target.
110 | */
111 | 'node'?: GlTfId;
112 | /**
113 | * The name of the node's TRS property to modify, or the "weights" of the Morph Targets it instantiates. For the "translation" property, the values that are provided by the sampler are the translation along the x, y, and z axes. For the "rotation" property, the values are a quaternion in the order (x, y, z, w), where w is the scalar. For the "scale" property, the values are the scaling factors along the x, y, and z axes.
114 | */
115 | 'path': 'translation' | 'rotation' | 'scale' | 'weights' | string;
116 | 'extensions'?: any;
117 | 'extras'?: any;
118 | [k: string]: any;
119 | }
120 | /**
121 | * Targets an animation's sampler at a node's property.
122 | */
123 | export interface AnimationChannel {
124 | /**
125 | * The index of a sampler in this animation used to compute the value for the target.
126 | */
127 | 'sampler': GlTfId;
128 | /**
129 | * The index of the node and TRS property to target.
130 | */
131 | 'target': AnimationChannelTarget;
132 | 'extensions'?: any;
133 | 'extras'?: any;
134 | [k: string]: any;
135 | }
136 | /**
137 | * Combines input and output accessors with an interpolation algorithm to define a keyframe graph (but not its target).
138 | */
139 | export interface AnimationSampler {
140 | /**
141 | * The index of an accessor containing keyframe input values, e.g., time.
142 | */
143 | 'input': GlTfId;
144 | /**
145 | * Interpolation algorithm.
146 | */
147 | 'interpolation'?: 'LINEAR' | 'STEP' | 'CUBICSPLINE' | string;
148 | /**
149 | * The index of an accessor, containing keyframe output values.
150 | */
151 | 'output': GlTfId;
152 | 'extensions'?: any;
153 | 'extras'?: any;
154 | [k: string]: any;
155 | }
156 | /**
157 | * A keyframe animation.
158 | */
159 | export interface Animation {
160 | /**
161 | * An array of channels, each of which targets an animation's sampler at a node's property. Different channels of the same animation can't have equal targets.
162 | */
163 | 'channels': AnimationChannel[];
164 | /**
165 | * An array of samplers that combines input and output accessors with an interpolation algorithm to define a keyframe graph (but not its target).
166 | */
167 | 'samplers': AnimationSampler[];
168 | 'name'?: any;
169 | 'extensions'?: any;
170 | 'extras'?: any;
171 | [k: string]: any;
172 | }
173 | /**
174 | * Metadata about the glTF asset.
175 | */
176 | export interface Asset {
177 | /**
178 | * A copyright message suitable for display to credit the content creator.
179 | */
180 | 'copyright'?: string;
181 | /**
182 | * Tool that generated this glTF model. Useful for debugging.
183 | */
184 | 'generator'?: string;
185 | /**
186 | * The glTF version that this asset targets.
187 | */
188 | 'version': string;
189 | /**
190 | * The minimum glTF version that this asset targets.
191 | */
192 | 'minVersion'?: string;
193 | 'extensions'?: any;
194 | 'extras'?: any;
195 | [k: string]: any;
196 | }
197 | /**
198 | * A buffer points to binary geometry, animation, or skins.
199 | */
200 | export interface Buffer {
201 | /**
202 | * The uri of the buffer.
203 | */
204 | 'uri'?: string;
205 | /**
206 | * The length of the buffer in bytes.
207 | */
208 | 'byteLength': number;
209 | 'name'?: any;
210 | 'extensions'?: any;
211 | 'extras'?: any;
212 | [k: string]: any;
213 | }
214 | /**
215 | * A view into a buffer generally representing a subset of the buffer.
216 | */
217 | export interface BufferView {
218 | /**
219 | * The index of the buffer.
220 | */
221 | 'buffer': GlTfId;
222 | /**
223 | * The offset into the buffer in bytes.
224 | */
225 | 'byteOffset'?: number;
226 | /**
227 | * The total byte length of the buffer view.
228 | */
229 | 'byteLength': number;
230 | /**
231 | * The stride, in bytes.
232 | */
233 | 'byteStride'?: number;
234 | /**
235 | * The target that the GPU buffer should be bound to.
236 | */
237 | 'target'?: 34962 | 34963 | number;
238 | 'name'?: any;
239 | 'extensions'?: any;
240 | 'extras'?: any;
241 | [k: string]: any;
242 | }
243 | /**
244 | * An orthographic camera containing properties to create an orthographic projection matrix.
245 | */
246 | export interface CameraOrthographic {
247 | /**
248 | * The floating-point horizontal magnification of the view. Must not be zero.
249 | */
250 | 'xmag': number;
251 | /**
252 | * The floating-point vertical magnification of the view. Must not be zero.
253 | */
254 | 'ymag': number;
255 | /**
256 | * The floating-point distance to the far clipping plane. `zfar` must be greater than `znear`.
257 | */
258 | 'zfar': number;
259 | /**
260 | * The floating-point distance to the near clipping plane.
261 | */
262 | 'znear': number;
263 | 'extensions'?: any;
264 | 'extras'?: any;
265 | [k: string]: any;
266 | }
267 | /**
268 | * A perspective camera containing properties to create a perspective projection matrix.
269 | */
270 | export interface CameraPerspective {
271 | /**
272 | * The floating-point aspect ratio of the field of view.
273 | */
274 | 'aspectRatio'?: number;
275 | /**
276 | * The floating-point vertical field of view in radians.
277 | */
278 | 'yfov': number;
279 | /**
280 | * The floating-point distance to the far clipping plane.
281 | */
282 | 'zfar'?: number;
283 | /**
284 | * The floating-point distance to the near clipping plane.
285 | */
286 | 'znear': number;
287 | 'extensions'?: any;
288 | 'extras'?: any;
289 | [k: string]: any;
290 | }
291 | /**
292 | * A camera's projection. A node can reference a camera to apply a transform to place the camera in the scene.
293 | */
294 | export interface Camera {
295 | /**
296 | * An orthographic camera containing properties to create an orthographic projection matrix.
297 | */
298 | 'orthographic'?: CameraOrthographic;
299 | /**
300 | * A perspective camera containing properties to create a perspective projection matrix.
301 | */
302 | 'perspective'?: CameraPerspective;
303 | /**
304 | * Specifies if the camera uses a perspective or orthographic projection.
305 | */
306 | 'type': 'perspective' | 'orthographic' | string;
307 | 'name'?: any;
308 | 'extensions'?: any;
309 | 'extras'?: any;
310 | [k: string]: any;
311 | }
312 | /**
313 | * Image data used to create a texture. Image can be referenced by URI or `bufferView` index. `mimeType` is required in the latter case.
314 | */
315 | export interface Image {
316 | /**
317 | * The uri of the image.
318 | */
319 | 'uri'?: string;
320 | /**
321 | * The image's MIME type. Required if `bufferView` is defined.
322 | */
323 | 'mimeType'?: 'image/jpeg' | 'image/png' | string;
324 | /**
325 | * The index of the bufferView that contains the image. Use this instead of the image's uri property.
326 | */
327 | 'bufferView'?: GlTfId;
328 | 'name'?: any;
329 | 'extensions'?: any;
330 | 'extras'?: any;
331 | [k: string]: any;
332 | }
333 | /**
334 | * Reference to a texture.
335 | */
336 | export interface TextureInfo {
337 | /**
338 | * The index of the texture.
339 | */
340 | 'index': GlTfId;
341 | /**
342 | * The set index of texture's TEXCOORD attribute used for texture coordinate mapping.
343 | */
344 | 'texCoord'?: number;
345 | 'extensions'?: any;
346 | 'extras'?: any;
347 | [k: string]: any;
348 | }
349 | /**
350 | * A set of parameter values that are used to define the metallic-roughness material model from Physically-Based Rendering (PBR) methodology.
351 | */
352 | export interface MaterialPbrMetallicRoughness {
353 | /**
354 | * The material's base color factor.
355 | */
356 | 'baseColorFactor'?: number[];
357 | /**
358 | * The base color texture.
359 | */
360 | 'baseColorTexture'?: TextureInfo;
361 | /**
362 | * The metalness of the material.
363 | */
364 | 'metallicFactor'?: number;
365 | /**
366 | * The roughness of the material.
367 | */
368 | 'roughnessFactor'?: number;
369 | /**
370 | * The metallic-roughness texture.
371 | */
372 | 'metallicRoughnessTexture'?: TextureInfo;
373 | 'extensions'?: any;
374 | 'extras'?: any;
375 | [k: string]: any;
376 | }
377 | export interface MaterialNormalTextureInfo {
378 | 'index'?: any;
379 | 'texCoord'?: any;
380 | /**
381 | * The scalar multiplier applied to each normal vector of the normal texture.
382 | */
383 | 'scale'?: number;
384 | 'extensions'?: any;
385 | 'extras'?: any;
386 | [k: string]: any;
387 | }
388 | export interface MaterialOcclusionTextureInfo {
389 | 'index'?: any;
390 | 'texCoord'?: any;
391 | /**
392 | * A scalar multiplier controlling the amount of occlusion applied.
393 | */
394 | 'strength'?: number;
395 | 'extensions'?: any;
396 | 'extras'?: any;
397 | [k: string]: any;
398 | }
399 | /**
400 | * The material appearance of a primitive.
401 | */
402 | export interface Material {
403 | 'name'?: any;
404 | 'extensions'?: any;
405 | 'extras'?: any;
406 | /**
407 | * A set of parameter values that are used to define the metallic-roughness material model from Physically-Based Rendering (PBR) methodology. When not specified, all the default values of `pbrMetallicRoughness` apply.
408 | */
409 | 'pbrMetallicRoughness'?: MaterialPbrMetallicRoughness;
410 | /**
411 | * The normal map texture.
412 | */
413 | 'normalTexture'?: MaterialNormalTextureInfo;
414 | /**
415 | * The occlusion map texture.
416 | */
417 | 'occlusionTexture'?: MaterialOcclusionTextureInfo;
418 | /**
419 | * The emissive map texture.
420 | */
421 | 'emissiveTexture'?: TextureInfo;
422 | /**
423 | * The emissive color of the material.
424 | */
425 | 'emissiveFactor'?: number[];
426 | /**
427 | * The alpha rendering mode of the material.
428 | */
429 | 'alphaMode'?: 'OPAQUE' | 'MASK' | 'BLEND' | string;
430 | /**
431 | * The alpha cutoff value of the material.
432 | */
433 | 'alphaCutoff'?: number;
434 | /**
435 | * Specifies whether the material is double sided.
436 | */
437 | 'doubleSided'?: boolean;
438 | [k: string]: any;
439 | }
440 | /**
441 | * Geometry to be rendered with the given material.
442 | */
443 | export interface MeshPrimitive {
444 | /**
445 | * A dictionary object, where each key corresponds to mesh attribute semantic and each value is the index of the accessor containing attribute's data.
446 | */
447 | 'attributes': {
448 | [k: string]: GlTfId;
449 | };
450 | /**
451 | * The index of the accessor that contains the indices.
452 | */
453 | 'indices'?: GlTfId;
454 | /**
455 | * The index of the material to apply to this primitive when rendering.
456 | */
457 | 'material'?: GlTfId;
458 | /**
459 | * The type of primitives to render.
460 | */
461 | 'mode'?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | number;
462 | /**
463 | * An array of Morph Targets, each Morph Target is a dictionary mapping attributes (only `POSITION`, `NORMAL`, and `TANGENT` supported) to their deviations in the Morph Target.
464 | */
465 | 'targets'?: {
466 | [k: string]: GlTfId;
467 | }[];
468 | 'extensions'?: any;
469 | 'extras'?: any;
470 | [k: string]: any;
471 | }
472 | /**
473 | * A set of primitives to be rendered. A node can contain one mesh. A node's transform places the mesh in the scene.
474 | */
475 | export interface Mesh {
476 | /**
477 | * An array of primitives, each defining geometry to be rendered with a material.
478 | */
479 | 'primitives': MeshPrimitive[];
480 | /**
481 | * Array of weights to be applied to the Morph Targets.
482 | */
483 | 'weights'?: number[];
484 | 'name'?: any;
485 | 'extensions'?: any;
486 | 'extras'?: any;
487 | [k: string]: any;
488 | }
489 | /**
490 | * A node in the node hierarchy. When the node contains `skin`, all `mesh.primitives` must contain `JOINTS_0` and `WEIGHTS_0` attributes. A node can have either a `matrix` or any combination of `translation`/`rotation`/`scale` (TRS) properties. TRS properties are converted to matrices and postmultiplied in the `T * R * S` order to compose the transformation matrix; first the scale is applied to the vertices, then the rotation, and then the translation. If none are provided, the transform is the identity. When a node is targeted for animation (referenced by an animation.channel.target), only TRS properties may be present; `matrix` will not be present.
491 | */
492 | export interface Node {
493 | /**
494 | * The index of the camera referenced by this node.
495 | */
496 | 'camera'?: GlTfId;
497 | /**
498 | * The indices of this node's children.
499 | */
500 | 'children'?: GlTfId[];
501 | /**
502 | * The index of the skin referenced by this node.
503 | */
504 | 'skin'?: GlTfId;
505 | /**
506 | * A floating-point 4x4 transformation matrix stored in column-major order.
507 | */
508 | 'matrix'?: number[];
509 | /**
510 | * The index of the mesh in this node.
511 | */
512 | 'mesh'?: GlTfId;
513 | /**
514 | * The node's unit quaternion rotation in the order (x, y, z, w), where w is the scalar.
515 | */
516 | 'rotation'?: number[];
517 | /**
518 | * The node's non-uniform scale, given as the scaling factors along the x, y, and z axes.
519 | */
520 | 'scale'?: number[];
521 | /**
522 | * The node's translation along the x, y, and z axes.
523 | */
524 | 'translation'?: number[];
525 | /**
526 | * The weights of the instantiated Morph Target. Number of elements must match number of Morph Targets of used mesh.
527 | */
528 | 'weights'?: number[];
529 | 'name'?: any;
530 | 'extensions'?: any;
531 | 'extras'?: any;
532 | [k: string]: any;
533 | }
534 | /**
535 | * Texture sampler properties for filtering and wrapping modes.
536 | */
537 | export interface Sampler {
538 | /**
539 | * Magnification filter.
540 | */
541 | 'magFilter'?: 9728 | 9729 | number;
542 | /**
543 | * Minification filter.
544 | */
545 | 'minFilter'?: 9728 | 9729 | 9984 | 9985 | 9986 | 9987 | number;
546 | /**
547 | * s wrapping mode.
548 | */
549 | 'wrapS'?: 33071 | 33648 | 10497 | number;
550 | /**
551 | * t wrapping mode.
552 | */
553 | 'wrapT'?: 33071 | 33648 | 10497 | number;
554 | 'name'?: any;
555 | 'extensions'?: any;
556 | 'extras'?: any;
557 | [k: string]: any;
558 | }
559 | /**
560 | * The root nodes of a scene.
561 | */
562 | export interface Scene {
563 | /**
564 | * The indices of each root node.
565 | */
566 | 'nodes'?: GlTfId[];
567 | 'name'?: any;
568 | 'extensions'?: any;
569 | 'extras'?: any;
570 | [k: string]: any;
571 | }
572 | /**
573 | * Joints and matrices defining a skin.
574 | */
575 | export interface Skin {
576 | /**
577 | * The index of the accessor containing the floating-point 4x4 inverse-bind matrices. The default is that each matrix is a 4x4 identity matrix, which implies that inverse-bind matrices were pre-applied.
578 | */
579 | 'inverseBindMatrices'?: GlTfId;
580 | /**
581 | * The index of the node used as a skeleton root.
582 | */
583 | 'skeleton'?: GlTfId;
584 | /**
585 | * Indices of skeleton nodes, used as joints in this skin.
586 | */
587 | 'joints': GlTfId[];
588 | 'name'?: any;
589 | 'extensions'?: any;
590 | 'extras'?: any;
591 | [k: string]: any;
592 | }
593 | /**
594 | * A texture and its sampler.
595 | */
596 | export interface Texture {
597 | /**
598 | * The index of the sampler used by this texture. When undefined, a sampler with repeat wrapping and auto filtering should be used.
599 | */
600 | 'sampler'?: GlTfId;
601 | /**
602 | * The index of the image used by this texture. When undefined, it is expected that an extension or other mechanism will supply an alternate texture source, otherwise behavior is undefined.
603 | */
604 | 'source'?: GlTfId;
605 | 'name'?: any;
606 | 'extensions'?: any;
607 | 'extras'?: any;
608 | [k: string]: any;
609 | }
610 | /**
611 | * The root object for a glTF asset.
612 | */
613 | export interface GlTf {
614 | /**
615 | * Names of glTF extensions used somewhere in this asset.
616 | */
617 | 'extensionsUsed'?: string[];
618 | /**
619 | * Names of glTF extensions required to properly load this asset.
620 | */
621 | 'extensionsRequired'?: string[];
622 | /**
623 | * An array of accessors.
624 | */
625 | 'accessors'?: Accessor[];
626 | /**
627 | * An array of keyframe animations.
628 | */
629 | 'animations'?: Animation[];
630 | /**
631 | * Metadata about the glTF asset.
632 | */
633 | 'asset': Asset;
634 | /**
635 | * An array of buffers.
636 | */
637 | 'buffers'?: Buffer[];
638 | /**
639 | * An array of bufferViews.
640 | */
641 | 'bufferViews'?: BufferView[];
642 | /**
643 | * An array of cameras.
644 | */
645 | 'cameras'?: Camera[];
646 | /**
647 | * An array of images.
648 | */
649 | 'images'?: Image[];
650 | /**
651 | * An array of materials.
652 | */
653 | 'materials'?: Material[];
654 | /**
655 | * An array of meshes.
656 | */
657 | 'meshes'?: Mesh[];
658 | /**
659 | * An array of nodes.
660 | */
661 | 'nodes'?: Node[];
662 | /**
663 | * An array of samplers.
664 | */
665 | 'samplers'?: Sampler[];
666 | /**
667 | * The index of the default scene.
668 | */
669 | 'scene'?: GlTfId;
670 | /**
671 | * An array of scenes.
672 | */
673 | 'scenes'?: Scene[];
674 | /**
675 | * An array of skins.
676 | */
677 | 'skins'?: Skin[];
678 | /**
679 | * An array of textures.
680 | */
681 | 'textures'?: Texture[];
682 | 'extensions'?: any;
683 | 'extras'?: any;
684 | [k: string]: any;
685 | }
--------------------------------------------------------------------------------
/src/webgl-gltf/types/model.d.ts:
--------------------------------------------------------------------------------
1 | import { mat4, vec3, quat, vec4 } from 'gl-matrix';
2 |
3 | /**
4 | * Model root
5 | */
6 | export interface Model {
7 | name: string;
8 | meshes: Mesh[];
9 | nodes: Node[];
10 | rootNode: number;
11 | animations: Animation;
12 | skins: Skin[];
13 | materials: Material[];
14 | }
15 |
16 | /**
17 | * Model node hiearchy with animation transforms and reference to mesh + skin
18 | */
19 | export interface Node {
20 | id: number;
21 | name: string;
22 | children: number[];
23 | localBindTransform: mat4;
24 | skin?: number;
25 | mesh?: number;
26 | }
27 |
28 | /**
29 | * Skinning information with the inversed bind transform and affected joints
30 | */
31 | export interface Skin {
32 | joints: number[];
33 | inverseBindTransforms: mat4[];
34 | }
35 |
36 | /**
37 | * Root for each animation
38 | */
39 | export interface Animation {
40 | [name: string]: Channel;
41 | }
42 |
43 | /**
44 | * List of keyframes for each animation
45 | */
46 | export interface Channel {
47 | [key: number]: Transform;
48 | }
49 |
50 | /**
51 | * Animation keyFrames
52 | */
53 | export interface Transform {
54 | translation: KeyFrame[];
55 | rotation: KeyFrame[];
56 | scale: KeyFrame[];
57 | }
58 |
59 | /**
60 | * Transform executed at specific time.
61 | */
62 | export interface KeyFrame {
63 | time: number;
64 | transform: vec3 | quat;
65 | type: 'translation' | 'rotation' | 'scale';
66 | }
67 |
68 | /**
69 | * WebGL buffer information
70 | */
71 | export interface GLBuffer {
72 | buffer: WebGLBuffer;
73 | type: number;
74 | size: number;
75 | }
76 |
77 | /**
78 | * Mesh buffers and associated material
79 | */
80 | export interface Mesh {
81 | elementCount: number;
82 | indices: WebGLBuffer | null;
83 | positions: GLBuffer;
84 | normals: GLBuffer | null;
85 | tangents: GLBuffer | null;
86 | texCoord: GLBuffer | null;
87 | joints: GLBuffer | null;
88 | weights: GLBuffer | null;
89 | material: number;
90 | }
91 |
92 | /**
93 | * Textures and material info for PBR.
94 | */
95 | export interface Material {
96 | baseColorTexture: WebGLTexture | null;
97 | baseColorFactor: vec4;
98 | metallicRoughnessTexture: WebGLTexture | null;
99 | metallicFactor: number;
100 | roughnessFactor: number;
101 | emissiveTexture: WebGLTexture | null;
102 | emissiveFactor: vec3;
103 | normalTexture: WebGLTexture | null;
104 | occlusionTexture: WebGLTexture | null;
105 | }
106 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowJs": false,
4 | "target": "es6",
5 | "module": "esnext",
6 | "moduleResolution": "node",
7 | "outDir": "./dist",
8 | "baseUrl": "src",
9 | "allowSyntheticDefaultImports": true,
10 | "noImplicitAny": false,
11 | "strictNullChecks": true,
12 | "noImplicitThis": true,
13 | "alwaysStrict": true,
14 | "noUnusedLocals": true,
15 | "noUnusedParameters": true,
16 | "forceConsistentCasingInFileNames": true,
17 | "esModuleInterop": true,
18 | "sourceMap": true,
19 | }
20 | }
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const HtmlWebpackPlugin = require('html-webpack-plugin');
3 | const { CleanWebpackPlugin } = require('clean-webpack-plugin');
4 |
5 | module.exports = {
6 | entry: './example/src/app.ts',
7 | resolve: {
8 | extensions: ['.ts', '.tsx', '.js'],
9 | modules: [
10 | path.resolve('./src'),
11 | path.resolve('./node_modules'),
12 | ],
13 | },
14 | devtool: 'source-map',
15 | module: {
16 | rules: [{
17 | test: /\.ts$/,
18 | include: [path.resolve('./example'), path.resolve('./src')],
19 | use: [{ loader: 'ts-loader' }]
20 | },
21 | {
22 | test: /\.ts$/,
23 | enforce: 'pre',
24 | use: [{
25 | options: { eslintPath: require.resolve('eslint') },
26 | loader: require.resolve('eslint-loader'),
27 | }],
28 | exclude: /node_modules/,
29 | }
30 | ]},
31 | output: {
32 | path: __dirname + '/example/dist',
33 | publicPath: '/',
34 | filename: 'bundle.[hash].js',
35 | },
36 | plugins: [
37 | new CleanWebpackPlugin(),
38 | new HtmlWebpackPlugin({ template: './example/index.html' }),
39 | ],
40 | devServer: {
41 | port: 8080,
42 | static: {
43 | directory: path.join(__dirname, 'example/static'),
44 | },
45 | },
46 | }
47 |
--------------------------------------------------------------------------------